-
-
Notifications
You must be signed in to change notification settings - Fork 20.1k
[Model] Deepseek-V3.1 reasoning parser #24972
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 6 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
8249bf6
Add a deepseek_v3 reasoning parser that supports dynamically enabling…
taohui 7c704d8
docs: add DeepSeek-V3.1 reasoning parser to Reasoning Outputs section
taohui cf2eb7b
test(reasoning): add unit tests for DeepSeekV3ReasoningParser
taohui c4bf074
Add unit tests for DeepSeekV3ReasoningParser parser selection
taohui 53e9cdc
remove separate_reasoning
taohui d7399df
test: add basic IdentityReasoningParser tests
taohui 71eb7d7
Fix: Update ReasoningParser __init__ to accept arbitrary kwargs
taohui 9ff007d
remove unused import
taohui 709dd39
fix code style
taohui 118bfb3
fix code style
taohui e69cfa1
fix code style
taohui c5120d3
fix code style
taohui b25ce53
fix code style
taohui 1c98c20
fix code style
taohui 43d9800
fix(reasoning): resolve circular import in DeepSeekV3ReasoningParser
taohui 3573024
fix code style
taohui a7dc723
fix: correct is_reasoning_end return value
taohui ed06fc2
docs: update documentation
taohui 520e576
Merge branch 'main' into deepseek_v3.1_reasoning_parser
taohui 6f14555
Merge branch 'main' into deepseek_v3.1_reasoning_parser
taohui File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # SPDX-FileCopyrightText: Copyright contributors to the vLLM project | ||
|
|
||
| import pytest | ||
| from transformers import AutoTokenizer | ||
|
|
||
| from vllm.reasoning import ( | ||
| DeepSeekV3ReasoningParser, | ||
| DeepSeekR1ReasoningParser, | ||
| IdentityReasoningParser, | ||
| ) | ||
| from vllm.entrypoints.openai.protocol import (ChatCompletionRequest, | ||
| DeltaMessage) | ||
|
|
||
| REASONING_MODEL_NAME = "deepseek-ai/DeepSeek-V3.1" | ||
|
|
||
|
|
||
| @pytest.fixture(scope="module") | ||
| def tokenizer(): | ||
| return AutoTokenizer.from_pretrained(REASONING_MODEL_NAME) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "thinking,expected_parser_type", | ||
| [ | ||
| (True, DeepSeekR1ReasoningParser), | ||
| (False, IdentityReasoningParser), | ||
| ], | ||
| ) | ||
| def test_parser_selection(tokenizer, thinking, expected_parser_type): | ||
| parser = DeepSeekV3ReasoningParser( | ||
| tokenizer, thinking=thinking | ||
| ) | ||
| assert isinstance(parser._parser, expected_parser_type) | ||
|
|
||
| def test_identity_reasoning_parser_basic(tokenizer): | ||
| parser = IdentityReasoningParser(tokenizer) | ||
|
|
||
| # Test is_reasoning_end always returns False | ||
| input_text = "This is some output" | ||
| input_tokens = tokenizer.tokenize(input_text) | ||
| input_ids = tokenizer.convert_tokens_to_ids(input_tokens) | ||
| assert parser.is_reasoning_end(input_ids) is False | ||
|
|
||
| # Test extract_content_ids returns all input_ids | ||
| assert parser.extract_content_ids(input_ids) == input_ids | ||
|
|
||
| # Test extract_reasoning_content returns (None, model_output) | ||
| request = ChatCompletionRequest(model="test-model", messages=[], temperature=1.0) | ||
| reasoning, content = parser.extract_reasoning_content(input_text, request) | ||
| assert reasoning is None | ||
| assert content == input_text | ||
|
|
||
| # Test extract_reasoning_content_streaming returns DeltaMessage or None | ||
| result = parser.extract_reasoning_content_streaming( | ||
| previous_text="", | ||
| current_text="Hello world", | ||
| delta_text="Hello world", | ||
| previous_token_ids=[], | ||
| current_token_ids=input_ids, | ||
| delta_token_ids=input_ids, | ||
| ) | ||
| assert isinstance(result, DeltaMessage) | ||
| assert result.content == "Hello world" | ||
|
|
||
| # If delta_text is empty, should return None | ||
| result_none = parser.extract_reasoning_content_streaming( | ||
| previous_text="Hello world", | ||
| current_text="Hello world", | ||
| delta_text="", | ||
| previous_token_ids=input_ids, | ||
| current_token_ids=input_ids, | ||
| delta_token_ids=[], | ||
| ) | ||
| assert result_none is None |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # SPDX-FileCopyrightText: Copyright contributors to the vLLM project | ||
|
|
||
| from collections.abc import Sequence | ||
| from typing import Optional, Union | ||
|
|
||
| from transformers import PreTrainedTokenizerBase | ||
|
|
||
| from vllm.entrypoints.openai.protocol import (ChatCompletionRequest, | ||
| DeltaMessage) | ||
| from vllm.logger import init_logger | ||
| from vllm.reasoning import ReasoningParser, ReasoningParserManager, IdentityReasoningParser, DeepSeekR1ReasoningParser | ||
|
|
||
| logger = init_logger(__name__) | ||
|
|
||
|
|
||
| @ReasoningParserManager.register_module("deepseek_v3") | ||
| class DeepSeekV3ReasoningParser(ReasoningParser): | ||
|
|
||
| """ | ||
| V3 parser that delegates to either DeepSeekR1ReasoningParser or | ||
| IdentityReasoningParser based on `thinking` and `separate_reasoning`. | ||
| """ | ||
| def __init__(self, tokenizer: PreTrainedTokenizerBase, *args, **kwargs): | ||
| super().__init__(tokenizer) | ||
|
|
||
| thinking = bool(kwargs.pop("thinking", False)) | ||
|
|
||
| if thinking: | ||
| self._parser = DeepSeekR1ReasoningParser(tokenizer) | ||
| else: | ||
| self._parser = IdentityReasoningParser(tokenizer) | ||
|
|
||
| def is_reasoning_end(self, input_ids: Sequence[int]) -> bool: | ||
| return self._parser.is_reasoning_end(input_ids) | ||
|
|
||
| def extract_content_ids(self, input_ids: list[int]) -> list[int]: | ||
| return self._parser.extract_content_ids(input_ids) | ||
|
|
||
| def extract_reasoning_content( | ||
| self, model_output: str, request: ChatCompletionRequest | ||
| ) -> tuple[Optional[str], Optional[str]]: | ||
| return self._parser.extract_reasoning_content(model_output, request) | ||
|
|
||
| def extract_reasoning_content_streaming( | ||
| self, | ||
| previous_text: str, | ||
| current_text: str, | ||
| delta_text: str, | ||
| previous_token_ids: Sequence[int], | ||
| current_token_ids: Sequence[int], | ||
| delta_token_ids: Sequence[int], | ||
| ) -> Union[DeltaMessage, None]: | ||
| return self._parser.extract_reasoning_content_streaming( | ||
| previous_text, | ||
| current_text, | ||
| delta_text, | ||
| previous_token_ids, | ||
| current_token_ids, | ||
| delta_token_ids, | ||
| ) | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # SPDX-FileCopyrightText: Copyright contributors to the vLLM project | ||
|
|
||
| from collections.abc import Sequence | ||
| from typing import Optional, Union | ||
|
|
||
| from transformers import PreTrainedTokenizerBase | ||
|
|
||
| from vllm.entrypoints.openai.protocol import (ChatCompletionRequest, | ||
| DeltaMessage) | ||
| from vllm.logger import init_logger | ||
| from vllm.reasoning import ReasoningParser, ReasoningParserManager | ||
|
|
||
| logger = init_logger(__name__) | ||
|
|
||
|
|
||
| @ReasoningParserManager.register_module("identity") | ||
| class IdentityReasoningParser(ReasoningParser): | ||
| """ | ||
| Identity reasoning parser. | ||
|
|
||
| This parser does not attempt to parse or strip out reasoning tokens. | ||
| It treats the entire model output as content and ignores reasoning. | ||
| """ | ||
|
|
||
| def __init__(self, tokenizer: PreTrainedTokenizerBase): | ||
| super().__init__(tokenizer) | ||
| if not self.model_tokenizer: | ||
| raise ValueError( | ||
| "The model tokenizer must be passed to the ReasoningParser " | ||
| "constructor during construction.") | ||
|
taohui marked this conversation as resolved.
Outdated
|
||
|
|
||
| def is_reasoning_end(self, input_ids: list[int]) -> bool: | ||
| # Always return False, since we never treat reasoning specially | ||
| return False | ||
|
|
||
| def extract_content_ids(self, input_ids: list[int]) -> list[int]: | ||
| # Identity: return all tokens as content | ||
| return input_ids | ||
|
|
||
| def extract_reasoning_content_streaming( | ||
| self, | ||
| previous_text: str, | ||
| current_text: str, | ||
| delta_text: str, | ||
| previous_token_ids: Sequence[int], | ||
| current_token_ids: Sequence[int], | ||
| delta_token_ids: Sequence[int], | ||
| ) -> Union[DeltaMessage, None]: | ||
| # Just wrap delta_text as content, ignore reasoning | ||
| if delta_text: | ||
| return DeltaMessage(content=delta_text) | ||
| return None | ||
|
|
||
| def extract_reasoning_content( | ||
| self, model_output: str, request: ChatCompletionRequest | ||
| ) -> tuple[Optional[str], Optional[str]]: | ||
| # No reasoning separation: return None for reasoning_content, | ||
| # and full model_output as content | ||
| return None, model_output | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, Qwen3 also supports parameters like
{ "chat_template_kwargs": { "enable_thinking": enable_thinking } }to enable or disable reasoning, and unlike this PR, it does not require two separate
reasoning_parsers.Is there anything special about DeepSeek-V3 that necessitates this different approach?
https://github.com/vllm-project/vllm/blob/main/tests/entrypoints/openai/test_completion_with_function_calling.py#L177-L178
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for reply. DeepSeek-V3.1 cannot directly use the qwen3 reasoning parser, because DeepSeek-V3.1 only has the end tag but no start tag . If we use the qwen3 reasoning parser, all content would be placed into the reasoning_content field. It also cannot directly use the deepseek_r1 reasoning parser, because that parser cannot decide whether to enable reasoning based on the request. For example, if I specify --reasoning-parser deepseek_r1 but the request is in non-thinking mode, it would still put all content into the reasoning_content field. The key point here is that we need a way to decide whether to use the reasoning parser based on the request. Therefore, I made a modification: applying a Strategy Pattern to select different strategies based on the request, while keeping the interface unchanged.