diff --git a/README.md b/README.md index 3c7a5a3..45637c1 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ - 对明确写成 `TokenizersBackend` 且带 `tokenizer.json` 的仓库,改用 `PreTrainedTokenizerFast`。 - 对 `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`。权重、模型代码和 其他文件以符号链接指回只读 `/model`;源模型不变。没有命中上述问题时直接调用原始 diff --git a/engine_compat.py b/engine_compat.py index d35fd12..d8a81a8 100644 --- a/engine_compat.py +++ b/engine_compat.py @@ -81,7 +81,14 @@ def repair_tokenizer_config( patched["tokenizer_class"] = "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" if config_path.is_file(): model_config = _json_object(config_path) diff --git a/tests/test_engine_compat.py b/tests/test_engine_compat.py index f68d0d9..62a1bc7 100644 --- a/tests/test_engine_compat.py +++ b/tests/test_engine_compat.py @@ -53,6 +53,14 @@ class EngineCompatTests(unittest.TestCase): self.assertIsNone(prepare_model(source, work_root=root)) 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): with tempfile.TemporaryDirectory() as directory: root = Path(directory)