Preserve standalone model chat templates

This commit is contained in:
2026-09-30 11:47:29 +08:00
parent 67946deb82
commit f8937cc505
3 changed files with 18 additions and 2 deletions

View File

@@ -16,7 +16,8 @@
- 对明确写成 `TokenizersBackend` 且带 `tokenizer.json` 的仓库,改用 - 对明确写成 `TokenizersBackend` 且带 `tokenizer.json` 的仓库,改用
`PreTrainedTokenizerFast`。 `PreTrainedTokenizerFast`。
- 对 `model_type=qwen2`、`eos_token=<|im_end|>` 且缺少聊天模板的模型,补充 - 对 `model_type=qwen2`、`eos_token=<|im_end|>` 且缺少聊天模板的模型,补充
简单的 Qwen ChatML 模板,避免 `/v1/chat/completions` 返回 400。 简单的 Qwen ChatML 模板,避免 `/v1/chat/completions` 返回 400;已有独立
`chat_template.jinja` 或 `chat_template.json` 的模型不补写。
启动时在 `/tmp` 建立新模型视图,只重写 `tokenizer_config.json`。权重、模型代码和 启动时在 `/tmp` 建立新模型视图,只重写 `tokenizer_config.json`。权重、模型代码和
其他文件以符号链接指回只读 `/model`;源模型不变。没有命中上述问题时直接调用原始 其他文件以符号链接指回只读 `/model`;源模型不变。没有命中上述问题时直接调用原始

View File

@@ -81,7 +81,14 @@ def repair_tokenizer_config(
patched["tokenizer_class"] = "PreTrainedTokenizerFast" patched["tokenizer_class"] = "PreTrainedTokenizerFast"
repairs.append("TokenizersBackend -> PreTrainedTokenizerFast") repairs.append("TokenizersBackend -> PreTrainedTokenizerFast")
if not patched.get("chat_template"): # Transformers can load a standalone template with higher precedence than
# tokenizer_config.json. Do not synthesize one when the repository already
# supplies a template in that format.
has_standalone_template = any(
(model_dir / name).is_file()
for name in ("chat_template.jinja", "chat_template.json")
)
if not patched.get("chat_template") and not has_standalone_template:
config_path = model_dir / "config.json" config_path = model_dir / "config.json"
if config_path.is_file(): if config_path.is_file():
model_config = _json_object(config_path) model_config = _json_object(config_path)

View File

@@ -53,6 +53,14 @@ class EngineCompatTests(unittest.TestCase):
self.assertIsNone(prepare_model(source, work_root=root)) self.assertIsNone(prepare_model(source, work_root=root))
self.assertEqual([p.name for p in root.iterdir()], ["model"]) self.assertEqual([p.name for p in root.iterdir()], ["model"])
def test_standalone_chat_template_is_preserved(self):
for filename in ("chat_template.jinja", "chat_template.json"):
with self.subTest(filename=filename), tempfile.TemporaryDirectory() as directory:
root = Path(directory)
source = self._model(root, {"eos_token": "<|im_end|>"})
(source / filename).write_text("{{ messages }}", encoding="utf-8")
self.assertIsNone(prepare_model(source, work_root=root))
def test_known_tokenizer_class_is_replaced_only_with_tokenizer_json(self): def test_known_tokenizer_class_is_replaced_only_with_tokenizer_json(self):
with tempfile.TemporaryDirectory() as directory: with tempfile.TemporaryDirectory() as directory:
root = Path(directory) root = Path(directory)