Skip to content

Commit b140416

Browse files
authored
[Model] Add reason parser for Hunyuan A13B Model. (#20625)
Signed-off-by: Asher Zhang <asherszhang@tencent.com>
1 parent 5b8366b commit b140416

File tree

3 files changed

+402
-0
lines changed

3 files changed

+402
-0
lines changed
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3+
4+
import pytest
5+
from transformers import AutoTokenizer
6+
7+
from tests.reasoning.utils import run_reasoning_extraction
8+
from vllm.reasoning import ReasoningParser, ReasoningParserManager
9+
10+
parser_name = "hunyuan_a13b"
11+
START_REASONING = "<think>\n"
12+
START_RESPONSE = "\n</think>\n<answer>\n"
13+
END_RESPONSE = "\n</answer>"
14+
15+
NO_REASONING_QUICK_THROUGHT = {
16+
"output":
17+
f"{START_REASONING}{START_RESPONSE}This is the rest{END_RESPONSE}", #noqa: E501
18+
"reasoning_content": None,
19+
"content": "This is the rest",
20+
}
21+
22+
SIMPLE_REASONING = {
23+
"output":
24+
f"{START_REASONING}This is a reasoning section{START_RESPONSE}This is the rest{END_RESPONSE}", #noqa: E501
25+
"reasoning_content": "This is a reasoning section",
26+
"content": "This is the rest",
27+
}
28+
COMPLETE_REASONING = {
29+
"output": f"{START_REASONING}This is a reasoning section{START_RESPONSE}",
30+
"reasoning_content": "This is a reasoning section",
31+
"content": None,
32+
}
33+
NO_REASONING = {
34+
"output": "This is content",
35+
"reasoning_content": None,
36+
"content": "This is content",
37+
}
38+
MULTIPLE_LINES = {
39+
"output":
40+
f"{START_REASONING}This\nThat{START_RESPONSE}This is the rest\nThat",
41+
"reasoning_content": "This\nThat",
42+
"content": "This is the rest\nThat",
43+
}
44+
REASONING_WITH_THINK = {
45+
"output":
46+
f"{START_REASONING}This is a reasoning section{START_RESPONSE}This is the rest", #noqa: E501
47+
"reasoning_content": "This is a reasoning section",
48+
"content": "This is the rest",
49+
}
50+
COMPLETE_REASONING_WITH_THINK = {
51+
"output": f"{START_REASONING}This is a reasoning section{START_RESPONSE}",
52+
"reasoning_content": "This is a reasoning section",
53+
"content": None,
54+
}
55+
MULTIPLE_LINES_WITH_THINK = {
56+
"output":
57+
f"{START_REASONING}This\nThat{START_RESPONSE}This is the rest\nThat",
58+
"reasoning_content": "This\nThat",
59+
"content": "This is the rest\nThat",
60+
}
61+
62+
TEST_CASES = [
63+
pytest.param(
64+
False,
65+
SIMPLE_REASONING,
66+
id="simple_reasoning",
67+
),
68+
pytest.param(
69+
False,
70+
COMPLETE_REASONING,
71+
id="complete_reasoning",
72+
),
73+
pytest.param(
74+
False,
75+
NO_REASONING,
76+
id="no_reasoning",
77+
),
78+
pytest.param(False, NO_REASONING_QUICK_THROUGHT, id="no_reasoning_quick"),
79+
pytest.param(
80+
False,
81+
MULTIPLE_LINES,
82+
id="multiple_lines",
83+
),
84+
pytest.param(
85+
False,
86+
REASONING_WITH_THINK,
87+
id="reasoning_with_think",
88+
),
89+
pytest.param(
90+
False,
91+
COMPLETE_REASONING_WITH_THINK,
92+
id="complete_reasoning_with_think",
93+
),
94+
pytest.param(
95+
False,
96+
MULTIPLE_LINES_WITH_THINK,
97+
id="multiple_lines_with_think",
98+
),
99+
pytest.param(
100+
True,
101+
SIMPLE_REASONING,
102+
id="simple_reasoning_streaming",
103+
),
104+
pytest.param(
105+
True,
106+
COMPLETE_REASONING,
107+
id="complete_reasoning_streaming",
108+
),
109+
pytest.param(
110+
True,
111+
NO_REASONING,
112+
id="no_reasoning_streaming",
113+
),
114+
pytest.param(True,
115+
NO_REASONING_QUICK_THROUGHT,
116+
id="no_reasoning_quick_stream"),
117+
pytest.param(
118+
True,
119+
MULTIPLE_LINES,
120+
id="multiple_lines_streaming",
121+
),
122+
pytest.param(
123+
True,
124+
REASONING_WITH_THINK,
125+
id="reasoning_with_think_streaming",
126+
),
127+
pytest.param(
128+
True,
129+
COMPLETE_REASONING_WITH_THINK,
130+
id="complete_reasoning_with_think_streaming",
131+
),
132+
pytest.param(
133+
True,
134+
MULTIPLE_LINES_WITH_THINK,
135+
id="multiple_lines_with_think_streaming",
136+
),
137+
]
138+
139+
# Global tokenizer initialization to avoid repeated loading
140+
tokenizer = AutoTokenizer.from_pretrained("tencent/Hunyuan-A13B-Instruct",
141+
trust_remote_code=True)
142+
143+
144+
@pytest.mark.parametrize("streaming, param_dict", TEST_CASES)
145+
def test_reasoning(
146+
streaming: bool,
147+
param_dict: dict,
148+
):
149+
output = tokenizer.tokenize(param_dict["output"])
150+
# decode everything to tokens
151+
output_tokens: list[str] = [
152+
tokenizer.convert_tokens_to_string([token]) for token in output
153+
]
154+
parser: ReasoningParser = ReasoningParserManager.get_reasoning_parser(
155+
parser_name)(tokenizer)
156+
157+
reasoning, content = run_reasoning_extraction(parser,
158+
output_tokens,
159+
streaming=streaming)
160+
161+
assert reasoning == param_dict["reasoning_content"]
162+
assert content == param_dict["content"]

vllm/reasoning/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
from .abs_reasoning_parsers import ReasoningParser, ReasoningParserManager
55
from .deepseek_r1_reasoning_parser import DeepSeekR1ReasoningParser
66
from .granite_reasoning_parser import GraniteReasoningParser
7+
from .hunyuan_a13b_reasoning_parser import HunyuanA13BReasoningParser
78
from .qwen3_reasoning_parser import Qwen3ReasoningParser
89

910
__all__ = [
1011
"ReasoningParser",
1112
"ReasoningParserManager",
1213
"DeepSeekR1ReasoningParser",
1314
"GraniteReasoningParser",
15+
"HunyuanA13BReasoningParser",
1416
"Qwen3ReasoningParser",
1517
]

0 commit comments

Comments
 (0)