[model] add Qwen3.8 model support (#10749)

Co-authored-by: hiyouga <hiyouga@buaa.edu.cn>
This commit is contained in:
Hertz
2026-09-02 15:52:22 +08:00
committed by GitHub
parent d6bb97ddff
commit 4451765a6b
13 changed files with 272 additions and 9 deletions

View File

@@ -281,6 +281,14 @@ def test_qwen_tool_extractor():
assert formatter.extract(result) == [("test_tool", """{"foo": "bar", "size": 10}""")]
@pytest.mark.runs_on(["cpu", "mps"])
def test_qwen38_tool_formatter():
formatter = ToolFormatter(tool_format="qwen3_8")
wrapped_tool = {"type": "function", "function": TOOLS[0]}
output = formatter.apply(content=json.dumps(TOOLS))[0]
assert json.dumps(wrapped_tool, ensure_ascii=False) in output
@pytest.mark.runs_on(["cpu", "mps"])
def test_qwen_multi_tool_extractor():
formatter = ToolFormatter(tool_format="qwen")

View File

@@ -13,6 +13,7 @@
# limitations under the License.
import os
from copy import deepcopy
from typing import TYPE_CHECKING
import pytest
@@ -22,6 +23,7 @@ from llamafactory.data import get_template_and_fix_tokenizer
from llamafactory.data.template import TEMPLATES, parse_template
from llamafactory.extras.constants import (
DEFAULT_TEMPLATE,
MCA_SUPPORTED_MODELS,
MULTIMODAL_SUPPORTED_MODELS,
SUPPORTED_MODELS,
DownloadSource,
@@ -54,6 +56,16 @@ MESSAGES_WITH_THOUGHT = [
]
class CharTokenizer:
r"""Minimal reversible tokenizer for testing rendered template text."""
def encode(self, text: str, add_special_tokens: bool = False) -> list[int]:
return [ord(char) for char in text]
def decode(self, token_ids: list[int]) -> str:
return "".join(chr(token_id) for token_id in token_ids)
def _check_tokenization(
tokenizer: "PreTrainedTokenizer", batch_input_ids: list[list[int]], batch_text: list[str]
) -> None:
@@ -113,6 +125,20 @@ def test_moss_vl_registration():
assert TEMPLATES["moss_vl"].mm_plugin.time_eos_token == "<|time_end|>"
def test_qwen38_registration():
multimodal_model = "Qwen3.8-27B"
text_model = "Qwen3.8-2.4T-A95B-Thinking"
assert SUPPORTED_MODELS[multimodal_model][DownloadSource.DEFAULT] == "Qwen/Qwen3.8-27B"
assert DEFAULT_TEMPLATE[multimodal_model] == "qwen3_8"
assert multimodal_model in MULTIMODAL_SUPPORTED_MODELS
assert SUPPORTED_MODELS[text_model][DownloadSource.DEFAULT] == "Qwen/Qwen3.8-2.4T-A95B"
assert DEFAULT_TEMPLATE[text_model] == "qwen3_8"
assert text_model not in MULTIMODAL_SUPPORTED_MODELS
assert {"qwen3_5_moe_text", "qwen3_5_text"} <= MCA_SUPPORTED_MODELS
@pytest.mark.runs_on(["cpu", "mps"])
def test_encode_oneturn():
tokenizer = AutoTokenizer.from_pretrained(TINY_LLAMA3)
@@ -240,6 +266,48 @@ def test_reasoning_encode_multiturn_discarding_history_cot(enable_thinking: bool
)
@pytest.mark.runs_on(["cpu", "mps"])
def test_qwen38_reasoning_instruction():
tokenizer = CharTokenizer()
template = deepcopy(TEMPLATES["qwen3_8"])
encoded_pairs = template.encode_multiturn(tokenizer, MESSAGES[:2], system="Medical assistant")
prompt_text = tokenizer.decode(encoded_pairs[0][0])
answer_text = tokenizer.decode(encoded_pairs[0][1])
assert prompt_text == (
"<|im_start|>system\n"
"Reasoning effort is set to xhigh. Please think carefully through the task, validate key assumptions, "
"consider plausible alternatives, and prioritize correctness, consistency, and clarity in the final answer."
"\n\nMedical assistant<|im_end|>\n"
"<|im_start|>user\nHow are you<|im_end|>\n<|im_start|>assistant\n"
)
assert answer_text == "<think>\n\n</think>\n\nI am fine!<|im_end|>\n"
@pytest.mark.runs_on(["cpu", "mps"])
def test_qwen38_tool_system_order():
tokenizer = CharTokenizer()
template = deepcopy(TEMPLATES["qwen3_8"])
tools = '[{"name":"get_weather","parameters":{"type":"object","properties":{}}}]'
messages = [
{"role": "user", "content": "How is the weather?"},
{"role": "function", "content": '{"name":"get_weather","arguments":{}}'},
]
encoded_pairs = template.encode_multiturn(tokenizer, messages, system="Use tools safely", tools=tools)
prompt_text = tokenizer.decode(encoded_pairs[0][0])
answer_text = tokenizer.decode(encoded_pairs[0][1])
reasoning_text = template._get_reasoning_instruction()
tool_text = template.format_tools.apply(content=tools)[0].lstrip("\n")
assert prompt_text == (
f"<|im_start|>system\n{reasoning_text}\n\n{tool_text}\n\nUse tools safely<|im_end|>\n"
"<|im_start|>user\nHow is the weather?<|im_end|>\n<|im_start|>assistant\n"
)
assert answer_text == (
"<think>\n\n</think>\n\n<tool_call>\n<function=get_weather>\n</function>\n</tool_call><|im_end|>\n"
)
@pytest.mark.runs_on(["cpu", "mps"])
def test_jinja_template():
tokenizer = AutoTokenizer.from_pretrained(TINY_LLAMA3)