mirror of
https://github.com/hiyouga/LLaMA-Factory.git
synced 2026-09-08 00:05:46 +08:00
[model] add Qwen3.8 model support (#10749)
Co-authored-by: hiyouga <hiyouga@buaa.edu.cn>
This commit is contained in:
@@ -522,6 +522,80 @@ class ReasoningTemplate(Template):
|
|||||||
return [(encoded_messages[i], encoded_messages[i + 1]) for i in range(0, len(encoded_messages), 2)]
|
return [(encoded_messages[i], encoded_messages[i + 1]) for i in range(0, len(encoded_messages), 2)]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Qwen38ReasoningTemplate(ReasoningTemplate):
|
||||||
|
r"""Qwen3.8 template with reasoning-effort instructions and official system ordering."""
|
||||||
|
|
||||||
|
reasoning_effort: str = "xhigh"
|
||||||
|
|
||||||
|
def _get_reasoning_instruction(self) -> str:
|
||||||
|
if self.enable_thinking is False:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if self.reasoning_effort == "xhigh":
|
||||||
|
return (
|
||||||
|
"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."
|
||||||
|
)
|
||||||
|
elif self.reasoning_effort == "medium":
|
||||||
|
return ""
|
||||||
|
elif self.reasoning_effort == "low":
|
||||||
|
return (
|
||||||
|
"Reasoning effort is set to low. Keep your thinking brief and focused, moving directly to the "
|
||||||
|
"conclusion without unnecessary elaboration."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Defensive validation for callers that configure the template without DataArguments.
|
||||||
|
raise ValueError(
|
||||||
|
f"Unexpected reasoning effort {self.reasoning_effort}. "
|
||||||
|
"Supported types are xhigh (default), medium, and low."
|
||||||
|
)
|
||||||
|
|
||||||
|
@override
|
||||||
|
def _encode(
|
||||||
|
self,
|
||||||
|
tokenizer: "PreTrainedTokenizer",
|
||||||
|
messages: list[dict[str, str]],
|
||||||
|
system: Optional[str],
|
||||||
|
tools: Optional[str],
|
||||||
|
) -> list[list[int]]:
|
||||||
|
system = (system or self.default_system).strip()
|
||||||
|
reasoning_instruction = self._get_reasoning_instruction()
|
||||||
|
encoded_messages = []
|
||||||
|
for i, message in enumerate(messages):
|
||||||
|
elements = []
|
||||||
|
|
||||||
|
if i == 0:
|
||||||
|
elements += self.format_prefix.apply()
|
||||||
|
system_parts = []
|
||||||
|
if reasoning_instruction:
|
||||||
|
system_parts.append(reasoning_instruction)
|
||||||
|
if tools:
|
||||||
|
system_parts.append(self.format_tools.apply(content=tools)[0].lstrip("\n"))
|
||||||
|
if system:
|
||||||
|
system_parts.append(system)
|
||||||
|
if system_parts:
|
||||||
|
elements += self.format_system.apply(content="\n\n".join(system_parts))
|
||||||
|
|
||||||
|
if message["role"] == Role.USER:
|
||||||
|
elements += self.format_user.apply(content=message["content"], idx=str(i // 2))
|
||||||
|
elif message["role"] == Role.ASSISTANT:
|
||||||
|
elements += self.format_assistant.apply(content=message["content"])
|
||||||
|
elif message["role"] == Role.OBSERVATION:
|
||||||
|
elements += self.format_observation.apply(content=message["content"])
|
||||||
|
elif message["role"] == Role.FUNCTION:
|
||||||
|
elements += self.format_function.apply(
|
||||||
|
content=message["content"], thought_words=self.thought_words, tool_call_words=self.tool_call_words
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise NotImplementedError("Unexpected role: {}".format(message["role"]))
|
||||||
|
|
||||||
|
encoded_messages.append(self._convert_elements_to_ids(tokenizer, elements))
|
||||||
|
|
||||||
|
return encoded_messages
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Glm47ReasoningTemplate(ReasoningTemplate):
|
class Glm47ReasoningTemplate(ReasoningTemplate):
|
||||||
r"""GLM-4.7 uses only the closing </think> tag for empty thinking blocks."""
|
r"""GLM-4.7 uses only the closing </think> tag for empty thinking blocks."""
|
||||||
@@ -693,6 +767,9 @@ def get_template_and_fix_tokenizer(tokenizer: "PreTrainedTokenizer", data_args:
|
|||||||
if data_args.train_on_prompt and template.efficient_eos:
|
if data_args.train_on_prompt and template.efficient_eos:
|
||||||
raise ValueError("Current template does not support `train_on_prompt`.")
|
raise ValueError("Current template does not support `train_on_prompt`.")
|
||||||
|
|
||||||
|
if isinstance(template, Qwen38ReasoningTemplate) and data_args.tool_format not in {None, "qwen3_8"}:
|
||||||
|
raise ValueError("Template `qwen3_8` uses its built-in tool format; remove the incompatible `tool_format`.")
|
||||||
|
|
||||||
if data_args.tool_format is not None:
|
if data_args.tool_format is not None:
|
||||||
logger.info_rank0(f"Using tool format: {data_args.tool_format}.")
|
logger.info_rank0(f"Using tool format: {data_args.tool_format}.")
|
||||||
default_slots = ["{{content}}"] if template.efficient_eos else ["{{content}}", {"eos_token"}]
|
default_slots = ["{{content}}"] if template.efficient_eos else ["{{content}}", {"eos_token"}]
|
||||||
@@ -712,6 +789,10 @@ def get_template_and_fix_tokenizer(tokenizer: "PreTrainedTokenizer", data_args:
|
|||||||
"e.g., qwen3_vl_nothink"
|
"e.g., qwen3_vl_nothink"
|
||||||
)
|
)
|
||||||
template.enable_thinking = data_args.enable_thinking
|
template.enable_thinking = data_args.enable_thinking
|
||||||
|
if isinstance(template, Qwen38ReasoningTemplate):
|
||||||
|
template.reasoning_effort = data_args.reasoning_effort
|
||||||
|
template.preserve_thinking = True if data_args.preserve_thinking is None else data_args.preserve_thinking
|
||||||
|
elif data_args.preserve_thinking is not None:
|
||||||
template.preserve_thinking = data_args.preserve_thinking
|
template.preserve_thinking = data_args.preserve_thinking
|
||||||
|
|
||||||
template.fix_special_tokens(tokenizer)
|
template.fix_special_tokens(tokenizer)
|
||||||
@@ -2291,6 +2372,24 @@ register_template(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
register_template(
|
||||||
|
name="qwen3_8",
|
||||||
|
format_user=StringFormatter(slots=["<|im_start|>user\n{{content}}<|im_end|>\n<|im_start|>assistant\n"]),
|
||||||
|
format_assistant=StringFormatter(slots=["{{content}}<|im_end|>\n"]),
|
||||||
|
format_system=StringFormatter(slots=["<|im_start|>system\n{{content}}<|im_end|>\n"]),
|
||||||
|
format_function=FunctionFormatter(slots=["{{content}}<|im_end|>\n"], tool_format="qwen3_8"),
|
||||||
|
format_observation=StringFormatter(
|
||||||
|
slots=["<|im_start|>user\n<tool_response>\n{{content}}\n</tool_response><|im_end|>\n<|im_start|>assistant\n"]
|
||||||
|
),
|
||||||
|
format_tools=ToolFormatter(tool_format="qwen3_8"),
|
||||||
|
stop_words=["<|im_end|>"],
|
||||||
|
replace_eos=True,
|
||||||
|
preserve_thinking=True,
|
||||||
|
mm_plugin=get_mm_plugin(name="qwen3_vl", image_token="<|image_pad|>", video_token="<|video_pad|>"),
|
||||||
|
template_class=Qwen38ReasoningTemplate,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
register_template(
|
register_template(
|
||||||
name="sailor",
|
name="sailor",
|
||||||
format_user=StringFormatter(slots=["<|im_start|>question\n{{content}}<|im_end|>\n<|im_start|>answer\n"]),
|
format_user=StringFormatter(slots=["<|im_start|>question\n{{content}}<|im_end|>\n<|im_start|>answer\n"]),
|
||||||
|
|||||||
@@ -758,6 +758,20 @@ class Qwen35ToolUtils(ToolUtils):
|
|||||||
return results if results else content
|
return results if results else content
|
||||||
|
|
||||||
|
|
||||||
|
class Qwen38ToolUtils(Qwen35ToolUtils):
|
||||||
|
r"""Qwen 3.8 tool template preserving the OpenAI function wrapper."""
|
||||||
|
|
||||||
|
@override
|
||||||
|
@staticmethod
|
||||||
|
def tool_formatter(tools: list[dict[str, Any]]) -> str:
|
||||||
|
tool_text = ""
|
||||||
|
for tool in tools:
|
||||||
|
wrapped_tool = tool if tool.get("type") == "function" else {"type": "function", "function": tool}
|
||||||
|
tool_text += "\n" + json.dumps(wrapped_tool, ensure_ascii=False)
|
||||||
|
|
||||||
|
return QWEN35_TOOL_PROMPT.format(tool_text=tool_text)
|
||||||
|
|
||||||
|
|
||||||
class GLM4MOEToolUtils(QwenToolUtils):
|
class GLM4MOEToolUtils(QwenToolUtils):
|
||||||
r"""GLM-4-MOE tool using template."""
|
r"""GLM-4-MOE tool using template."""
|
||||||
|
|
||||||
@@ -970,6 +984,7 @@ TOOLS = {
|
|||||||
"mistral": MistralToolUtils(),
|
"mistral": MistralToolUtils(),
|
||||||
"qwen": QwenToolUtils(),
|
"qwen": QwenToolUtils(),
|
||||||
"qwen3_5": Qwen35ToolUtils(),
|
"qwen3_5": Qwen35ToolUtils(),
|
||||||
|
"qwen3_8": Qwen38ToolUtils(),
|
||||||
"glm4_moe": GLM4MOEToolUtils(),
|
"glm4_moe": GLM4MOEToolUtils(),
|
||||||
"seed_oss": SeedToolUtils(),
|
"seed_oss": SeedToolUtils(),
|
||||||
"ling": LingToolUtils(),
|
"ling": LingToolUtils(),
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ MCA_SUPPORTED_MODELS = {
|
|||||||
"qwen3_next",
|
"qwen3_next",
|
||||||
"qwen3_5",
|
"qwen3_5",
|
||||||
"qwen3_5_moe",
|
"qwen3_5_moe",
|
||||||
|
"qwen3_5_moe_text",
|
||||||
|
"qwen3_5_text",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Text LLM model_types supported by the Megatron Bridge PT/SFT path (gpt_step).
|
# Text LLM model_types supported by the Megatron Bridge PT/SFT path (gpt_step).
|
||||||
@@ -3000,6 +3002,37 @@ register_model_group(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
register_model_group(
|
||||||
|
models={
|
||||||
|
"Qwen3.8-27B": {
|
||||||
|
DownloadSource.DEFAULT: "Qwen/Qwen3.8-27B",
|
||||||
|
DownloadSource.MODELSCOPE: "Qwen/Qwen3.8-27B",
|
||||||
|
},
|
||||||
|
"Qwen3.8-27B-FP8": {
|
||||||
|
DownloadSource.DEFAULT: "Qwen/Qwen3.8-27B-FP8",
|
||||||
|
DownloadSource.MODELSCOPE: "Qwen/Qwen3.8-27B-FP8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template="qwen3_8",
|
||||||
|
multimodal=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
register_model_group(
|
||||||
|
models={
|
||||||
|
"Qwen3.8-2.4T-A95B-Thinking": {
|
||||||
|
DownloadSource.DEFAULT: "Qwen/Qwen3.8-2.4T-A95B",
|
||||||
|
DownloadSource.MODELSCOPE: "Qwen/Qwen3.8-2.4T-A95B",
|
||||||
|
},
|
||||||
|
"Qwen3.8-2.4T-A95B-Thinking-FP8": {
|
||||||
|
DownloadSource.DEFAULT: "Qwen/Qwen3.8-2.4T-A95B-FP8",
|
||||||
|
DownloadSource.MODELSCOPE: "Qwen/Qwen3.8-2.4T-A95B-FP8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template="qwen3_8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
register_model_group(
|
register_model_group(
|
||||||
models={
|
models={
|
||||||
"Qwen2-Audio-7B": {
|
"Qwen2-Audio-7B": {
|
||||||
|
|||||||
@@ -125,9 +125,18 @@ class DataArguments:
|
|||||||
default=True,
|
default=True,
|
||||||
metadata={"help": "Whether or not to enable thinking mode for reasoning models."},
|
metadata={"help": "Whether or not to enable thinking mode for reasoning models."},
|
||||||
)
|
)
|
||||||
preserve_thinking: bool = field(
|
reasoning_effort: str = field(
|
||||||
default=False,
|
default="xhigh",
|
||||||
metadata={"help": "Whether or not to preserve thinking content in historical turns for reasoning models."},
|
metadata={"help": "Reasoning effort for supported reasoning models (xhigh, medium, or low)."},
|
||||||
|
)
|
||||||
|
preserve_thinking: bool | None = field(
|
||||||
|
default=None,
|
||||||
|
metadata={
|
||||||
|
"help": (
|
||||||
|
"Whether or not to preserve thinking content in historical turns for reasoning models. "
|
||||||
|
"Uses the template default when unspecified."
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
tokenized_path: str | None = field(
|
tokenized_path: str | None = field(
|
||||||
default=None,
|
default=None,
|
||||||
@@ -182,6 +191,9 @@ class DataArguments:
|
|||||||
if self.mask_history and self.train_on_prompt:
|
if self.mask_history and self.train_on_prompt:
|
||||||
raise ValueError("`mask_history` is incompatible with `train_on_prompt`.")
|
raise ValueError("`mask_history` is incompatible with `train_on_prompt`.")
|
||||||
|
|
||||||
|
if self.reasoning_effort not in {"xhigh", "medium", "low"}:
|
||||||
|
raise ValueError("`reasoning_effort` must be one of xhigh, medium, or low.")
|
||||||
|
|
||||||
if self.neat_packing:
|
if self.neat_packing:
|
||||||
self.packing = True
|
self.packing = True
|
||||||
|
|
||||||
|
|||||||
@@ -82,8 +82,12 @@ def apply_liger_kernel(
|
|||||||
from liger_kernel.transformers import apply_liger_kernel_to_qwen3_next as apply_liger_kernel
|
from liger_kernel.transformers import apply_liger_kernel_to_qwen3_next as apply_liger_kernel
|
||||||
elif model_type == "qwen3_5":
|
elif model_type == "qwen3_5":
|
||||||
from liger_kernel.transformers import apply_liger_kernel_to_qwen3_5 as apply_liger_kernel
|
from liger_kernel.transformers import apply_liger_kernel_to_qwen3_5 as apply_liger_kernel
|
||||||
|
elif model_type == "qwen3_5_text":
|
||||||
|
from liger_kernel.transformers import apply_liger_kernel_to_qwen3_5_text as apply_liger_kernel
|
||||||
elif model_type == "qwen3_5_moe":
|
elif model_type == "qwen3_5_moe":
|
||||||
from liger_kernel.transformers import apply_liger_kernel_to_qwen3_5_moe as apply_liger_kernel
|
from liger_kernel.transformers import apply_liger_kernel_to_qwen3_5_moe as apply_liger_kernel
|
||||||
|
elif model_type == "qwen3_5_moe_text":
|
||||||
|
from liger_kernel.transformers import apply_liger_kernel_to_qwen3_5_moe_text as apply_liger_kernel
|
||||||
elif model_type == "gpt_oss":
|
elif model_type == "gpt_oss":
|
||||||
try:
|
try:
|
||||||
from liger_kernel.transformers import apply_liger_kernel_to_gpt_oss as apply_liger_kernel
|
from liger_kernel.transformers import apply_liger_kernel_to_gpt_oss as apply_liger_kernel
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ def add_z3_leaf_module(model: "PreTrainedModel") -> None:
|
|||||||
|
|
||||||
_set_z3_leaf_modules(model, [Qwen3NextSparseMoeBlock])
|
_set_z3_leaf_modules(model, [Qwen3NextSparseMoeBlock])
|
||||||
|
|
||||||
if model_type == "qwen3_5_moe":
|
if model_type in ("qwen3_5_moe", "qwen3_5_moe_text"):
|
||||||
from transformers.models.qwen3_5_moe.modeling_qwen3_5_moe import Qwen3_5MoeSparseMoeBlock
|
from transformers.models.qwen3_5_moe.modeling_qwen3_5_moe import Qwen3_5MoeSparseMoeBlock
|
||||||
|
|
||||||
_set_z3_leaf_modules(model, [Qwen3_5MoeSparseMoeBlock])
|
_set_z3_leaf_modules(model, [Qwen3_5MoeSparseMoeBlock])
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ def patch_qwen3_5_forward_npu(model: "PreTrainedModel") -> None:
|
|||||||
|
|
||||||
|
|
||||||
def patch_qwen3_5_forward_gpu(model: "PreTrainedModel") -> None:
|
def patch_qwen3_5_forward_gpu(model: "PreTrainedModel") -> None:
|
||||||
"""Patch the forward method of Qwen3_5ForConditionalGeneration to support cu_seqlens input only patch when do training.
|
"""Patch Qwen3.5 decoder forward methods to support cu_seqlens during training.
|
||||||
|
|
||||||
Refer to: https://github.com/axolotl-ai-cloud/axolotl/blob/main/src/axolotl/monkeypatch/models/qwen3_5/modeling.py.
|
Refer to: https://github.com/axolotl-ai-cloud/axolotl/blob/main/src/axolotl/monkeypatch/models/qwen3_5/modeling.py.
|
||||||
"""
|
"""
|
||||||
@@ -286,12 +286,12 @@ def patch_qwen3_5_forward_gpu(model: "PreTrainedModel") -> None:
|
|||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
if model.config.architectures[0] == "Qwen3_5ForConditionalGeneration":
|
if model.config.architectures[0] in ("Qwen3_5ForCausalLM", "Qwen3_5ForConditionalGeneration"):
|
||||||
from transformers.models.qwen3_5.modeling_qwen3_5 import Qwen3_5DecoderLayer, Qwen3_5GatedDeltaNet
|
from transformers.models.qwen3_5.modeling_qwen3_5 import Qwen3_5DecoderLayer, Qwen3_5GatedDeltaNet
|
||||||
|
|
||||||
Qwen3_5DecoderLayer.forward = _patched_decoder_forward
|
Qwen3_5DecoderLayer.forward = _patched_decoder_forward
|
||||||
Qwen3_5GatedDeltaNet.forward = _patch_gdn_forward
|
Qwen3_5GatedDeltaNet.forward = _patch_gdn_forward
|
||||||
elif model.config.architectures[0] == "Qwen3_5MoeForConditionalGeneration":
|
elif model.config.architectures[0] in ("Qwen3_5MoeForCausalLM", "Qwen3_5MoeForConditionalGeneration"):
|
||||||
from transformers.models.qwen3_5_moe.modeling_qwen3_5_moe import (
|
from transformers.models.qwen3_5_moe.modeling_qwen3_5_moe import (
|
||||||
Qwen3_5MoeDecoderLayer,
|
Qwen3_5MoeDecoderLayer,
|
||||||
Qwen3_5MoeGatedDeltaNet,
|
Qwen3_5MoeGatedDeltaNet,
|
||||||
@@ -484,7 +484,12 @@ def patch_model(
|
|||||||
autocast_projector_dtype(model, model_args)
|
autocast_projector_dtype(model, model_args)
|
||||||
add_z3_leaf_module(model)
|
add_z3_leaf_module(model)
|
||||||
|
|
||||||
if getattr(model.config, "model_type", None) in ["qwen3_5", "qwen3_5_moe"]:
|
if getattr(model.config, "model_type", None) in [
|
||||||
|
"qwen3_5",
|
||||||
|
"qwen3_5_moe",
|
||||||
|
"qwen3_5_moe_text",
|
||||||
|
"qwen3_5_text",
|
||||||
|
]:
|
||||||
if is_torch_npu_available():
|
if is_torch_npu_available():
|
||||||
patch_qwen3_5_forward_npu(model)
|
patch_qwen3_5_forward_npu(model)
|
||||||
elif is_torch_cuda_available() and model_args.flash_attn == "fa2":
|
elif is_torch_cuda_available() and model_args.flash_attn == "fa2":
|
||||||
|
|||||||
@@ -363,6 +363,9 @@ _V5_MODEL_TYPE_TO_PATCHES = {
|
|||||||
"qwen3_5_moe": {
|
"qwen3_5_moe": {
|
||||||
"Qwen3_5MoeExperts": NpuMoeFusedV5.experts_forward,
|
"Qwen3_5MoeExperts": NpuMoeFusedV5.experts_forward,
|
||||||
},
|
},
|
||||||
|
"qwen3_5_moe_text": {
|
||||||
|
"Qwen3_5MoeExperts": NpuMoeFusedV5.experts_forward,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_MODEL_TYPE_TO_PATCHES = (
|
_MODEL_TYPE_TO_PATCHES = (
|
||||||
|
|||||||
@@ -83,9 +83,15 @@ _MODEL_TYPE_TO_PATCHES = {
|
|||||||
"qwen3_5": {
|
"qwen3_5": {
|
||||||
"Qwen3_5MLP": npu_swiglu_forward,
|
"Qwen3_5MLP": npu_swiglu_forward,
|
||||||
},
|
},
|
||||||
|
"qwen3_5_text": {
|
||||||
|
"Qwen3_5MLP": npu_swiglu_forward,
|
||||||
|
},
|
||||||
"qwen3_5_moe": {
|
"qwen3_5_moe": {
|
||||||
"Qwen3_5MoeMLP": npu_swiglu_forward,
|
"Qwen3_5MoeMLP": npu_swiglu_forward,
|
||||||
},
|
},
|
||||||
|
"qwen3_5_moe_text": {
|
||||||
|
"Qwen3_5MoeMLP": npu_swiglu_forward,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -148,10 +148,18 @@ _MODEL_TYPE_TO_PATCHES = {
|
|||||||
"Qwen3_5RMSNorm": npu_residual_rms_norm_forward,
|
"Qwen3_5RMSNorm": npu_residual_rms_norm_forward,
|
||||||
"Qwen3_5RMSNormGated": npu_gated_rms_norm_forward,
|
"Qwen3_5RMSNormGated": npu_gated_rms_norm_forward,
|
||||||
},
|
},
|
||||||
|
"qwen3_5_text": {
|
||||||
|
"Qwen3_5RMSNorm": npu_residual_rms_norm_forward,
|
||||||
|
"Qwen3_5RMSNormGated": npu_gated_rms_norm_forward,
|
||||||
|
},
|
||||||
"qwen3_5_moe": {
|
"qwen3_5_moe": {
|
||||||
"Qwen3_5MoeRMSNorm": npu_residual_rms_norm_forward,
|
"Qwen3_5MoeRMSNorm": npu_residual_rms_norm_forward,
|
||||||
"Qwen3_5MoeRMSNormGated": npu_gated_rms_norm_forward,
|
"Qwen3_5MoeRMSNormGated": npu_gated_rms_norm_forward,
|
||||||
},
|
},
|
||||||
|
"qwen3_5_moe_text": {
|
||||||
|
"Qwen3_5MoeRMSNorm": npu_residual_rms_norm_forward,
|
||||||
|
"Qwen3_5MoeRMSNormGated": npu_gated_rms_norm_forward,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -121,7 +121,9 @@ _MODEL_TYPE_TO_PATCHES = {
|
|||||||
"qwen3_vl": _default_rope_patch("qwen3_vl"),
|
"qwen3_vl": _default_rope_patch("qwen3_vl"),
|
||||||
"qwen3_vl_moe": _default_rope_patch("qwen3_vl_moe"),
|
"qwen3_vl_moe": _default_rope_patch("qwen3_vl_moe"),
|
||||||
"qwen3_5": _default_rope_patch("qwen3_5"),
|
"qwen3_5": _default_rope_patch("qwen3_5"),
|
||||||
|
"qwen3_5_text": _default_rope_patch("qwen3_5"),
|
||||||
"qwen3_5_moe": _default_rope_patch("qwen3_5_moe"),
|
"qwen3_5_moe": _default_rope_patch("qwen3_5_moe"),
|
||||||
|
"qwen3_5_moe_text": _default_rope_patch("qwen3_5_moe"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -281,6 +281,14 @@ def test_qwen_tool_extractor():
|
|||||||
assert formatter.extract(result) == [("test_tool", """{"foo": "bar", "size": 10}""")]
|
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"])
|
@pytest.mark.runs_on(["cpu", "mps"])
|
||||||
def test_qwen_multi_tool_extractor():
|
def test_qwen_multi_tool_extractor():
|
||||||
formatter = ToolFormatter(tool_format="qwen")
|
formatter = ToolFormatter(tool_format="qwen")
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from copy import deepcopy
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import pytest
|
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.data.template import TEMPLATES, parse_template
|
||||||
from llamafactory.extras.constants import (
|
from llamafactory.extras.constants import (
|
||||||
DEFAULT_TEMPLATE,
|
DEFAULT_TEMPLATE,
|
||||||
|
MCA_SUPPORTED_MODELS,
|
||||||
MULTIMODAL_SUPPORTED_MODELS,
|
MULTIMODAL_SUPPORTED_MODELS,
|
||||||
SUPPORTED_MODELS,
|
SUPPORTED_MODELS,
|
||||||
DownloadSource,
|
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(
|
def _check_tokenization(
|
||||||
tokenizer: "PreTrainedTokenizer", batch_input_ids: list[list[int]], batch_text: list[str]
|
tokenizer: "PreTrainedTokenizer", batch_input_ids: list[list[int]], batch_text: list[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -113,6 +125,20 @@ def test_moss_vl_registration():
|
|||||||
assert TEMPLATES["moss_vl"].mm_plugin.time_eos_token == "<|time_end|>"
|
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"])
|
@pytest.mark.runs_on(["cpu", "mps"])
|
||||||
def test_encode_oneturn():
|
def test_encode_oneturn():
|
||||||
tokenizer = AutoTokenizer.from_pretrained(TINY_LLAMA3)
|
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"])
|
@pytest.mark.runs_on(["cpu", "mps"])
|
||||||
def test_jinja_template():
|
def test_jinja_template():
|
||||||
tokenizer = AutoTokenizer.from_pretrained(TINY_LLAMA3)
|
tokenizer = AutoTokenizer.from_pretrained(TINY_LLAMA3)
|
||||||
|
|||||||
Reference in New Issue
Block a user