lm-evaluation-harness Chat Template 分隔符处理更新:apply_chat_template 模式下 target_delimiter 置空机制解析
【免费下载链接】lm-evaluation-harnessA framework for few-shot evaluation of language models.项目地址: https://gitcode.com/GitHub_Trending/lm/lm-evaluation-harness
导读
本文围绕 lm-evaluation-harness 中docs/chat-template-readme.md记录的变更展开,讲解在请求构造过程中应用 Chat Template 时,系统如何将 target delimiter 由默认空白符改为空字符串,从而避免 Chat 模型自带格式化约定与默认分隔符系统相互干扰。读者将掌握apply_chat_template的完整行为、target_delimiter在loglikelihood与multiple_choice两类任务中的不同处理逻辑、以及 gen_prefix 对分隔符决策的例外影响,并能将这套规则直接用于自建任务 YAML 与 CLI 评估命令的配置。
背景:默认分隔符与 Prompt 拼接模型
在 lm-evaluation-harness 中,每个任务对每条样本构造 Prompt 时遵循一个统一约定:上下文文本与目标文本之间插入一个target_delimiter。默认值为单个空格" ",该默认值定义在任务配置类TaskConfig中(见 lm_eval/config/task.py)。
整体拼接关系可表示为:
doc_to_text(doc) + target_delimiter + doc_to_target(doc)doc_to_text(doc):从样本中提取问题/上下文;target_delimiter:默认" ",可在任务 YAML 中以同名键覆盖;doc_to_target(doc):从样本中提取标准答案/目标文本。
这一设计对基础模型(base model)很友好:模型被要求预测“一个空白符 + 答案”,从而以最大似然(loglikelihood)方式打分。但对于经过指令微调、自带对话格式的 Chat 模型,空白符插入的位置往往与模板自带的换行、缩进约定冲突,导致生成或打分的目标序列与模型在训练时见过的格式不一致。
变更内容:apply_chat_template 下分隔符置空
本次更新的核心改动可概括为一条规则:
当
apply_chat_template为True时,构造请求所用的 target delimiter 置为空字符串"",而不再使用配置的分隔符(默认" ")。
其意义在于:
- 防止 Chat Template 的格式化结果与默认分隔符系统相互干扰;
- 对 multiple-choice 类任务尤其关键,因为模板本身已经负责处理空格与换行。
原文档给出的直观对比(变更前 vs 变更后)如下:
# Before(默认分隔符 " ") <user>Question: What color is the sky?\nAnswer:<assistant> blue # After(分隔符为空) <user>Question: What color is the sky?\nAnswer:<assistant>blue可以看出,变更前答案前残留一个多余空格,变更后答案紧贴<assistant>之后的模板原生格式,更符合 Chat 模型的训练分布。
源码级实现:分隔符在请求构造链中的真实走向
上述行为并非仅在文档层面声明,而是落实在任务请求构造代码中。核心逻辑位于Task.construct_requests()(见 lm_eval/api/task.py)。
对于loglikelihood输出类型,请求直接以(ctx, doc_to_target(doc))作为参数对,不经过分隔符拼接:
if self.OUTPUT_TYPE == "loglikelihood": arguments = (ctx, self.doc_to_target(doc))而对于multiple_choice输出类型,分隔符逻辑最为关键:
elif self.OUTPUT_TYPE == "multiple_choice": choices = self.doc_to_choice(doc) target_delimiter = self.config.target_delimiter if apply_chat_template: target_delimiter = ( self.config.target_delimiter if self.config.gen_prefix and not ends_with_whitespace(self.config.gen_prefix) else "" ) ... arguments = [(ctx, f"{target_delimiter}{cont}") for cont in choices]代码显示,apply_chat_template=True时存在一个带条件的例外:
- 若任务配置了
gen_prefix,且gen_prefix不以空白字符结尾,则仍保留target_delimiter; - 其余情况(无 gen_prefix,或 gen_prefix 以空白结尾)一律置空。
这意味着“置空分隔符”规则在 gen_prefix(例如Answer:)场景下会被有意放宽,以保证Answer:与答案之间保留必要的间隔;而一旦Answer:自带尾部空格,间隔又回归空串,避免出现双重空格。从源码结构看,这一分支是为了同时兼容“模板自带间隔”与“gen_prefix 显式要求间隔”两种诉求。
gen_prefix的取值优先级同样定义在任务类中(见 lm_eval/api/task.py):若其在样本features中则为字段引用,否则会经apply_template模板渲染得到。
apply_chat_template 的开启路径:从 CLI 到 Evaluator
apply_chat_template的取值可以是布尔值或字符串:True表示应用模型默认 Chat Template,字符串则按名称指定模板(见 lm_eval/evaluator.py)。在 CLI 层面对应--apply_chat_template参数,Python API 的simple_evaluate/evaluate亦接受同名关键字参数。
在 evaluator 中,开启后通过以下方式把模板能力注入任务上下文(见 lm_eval/evaluator.py):
chat_template=lm.chat_template(apply_chat_template) if apply_chat_template随后在Task.fewshot_context()中,chat_template与apply_chat_template一并被使用(见 lm_eval/api/task.py):消息列表(system / few-shot / 评测样本)先按build_qa_turn()构造为Message序列,apply_chat_template为真时再统一交给chat_template(res)渲染为单轮或对话式文本。
需要特别说明的是:置空分隔符的生效点位于请求构造(construct_requests)阶段,即实际送入模型的 loglikelihood 打分对;而fewshot_context生成的展示文本仍会保留target_delimiter(通过build_qa_turn(..., tgt_delim=self.config.target_delimiter, ...),见 lm_eval/api/task.py)。也就是说,--write_out输出的样例文本与真正打分的 continuation 之间可能存在细微差异,这正是文档所描述“防止模板格式化与默认分隔符系统相互干扰”的底层原因。
如何配置与验证
1. 任务侧:在 YAML 中显式控制分隔符
在自建任务配置中可按需覆盖分隔符相关字段:
task: my_chat_mc_task output_type: multiple_choice target_delimiter: " " # 默认即 " ",可显式声明或留空 doc_to_text: "Question: {{question}}" doc_to_choice: ["A", "B", "C", "D"] doc_to_target: "{{answer}}"配合运行时开启 Chat Template 后,multiple_choice 请求中的分隔符会自动置空(除非命中 gen_prefix 例外分支),无需在 YAML 中手工清空。
2. 评估侧:CLI 与 Python API
CLI 方式:
lm_eval --model hf \ --model_args pretrained=your-chat-model \ --tasks my_chat_mc_task \ --apply_chat_templatePython 方式(等价):
from lm_eval import simple_evaluate results = simple_evaluate( model="hf", model_args="pretrained=your-chat-model", tasks=["my_chat_mc_task"], apply_chat_template=True, fewshot_as_multiturn=True, # 将 few-shot 示例作为多轮对话 )其中fewshot_as_multiturn控制 few-shot 示例是作为独立 user/assistant 多轮消息(True)还是折叠进单条 user 消息(False),与 Chat Template 渲染密切相关(默认True,见 lm_eval/evaluator.py)。
3. 验证输出
可通过--write_out导出构造出的实际输入文本,检查<assistant>后是否紧贴答案、无多余空格:
lm_eval --model hf \ --model_args pretrained=your-chat-model \ --tasks my_chat_mc_task \ --apply_chat_template \ --write_out \ --limit 10同时,请求缓存指纹会因apply_chat_template而异——任务缓存键在开启时追加-chat_template后缀(见 lm_eval/api/task.py),--cache_requests场景下模板开关不会串用旧缓存。
常见陷阱与边界
- 不要同时依赖默认空格与模板自带空格:对 Chat 模型,模板通常已在
<assistant>后包含换行/空格,若仍保留默认" "会出现双空格或错位,这正是本变更消除的问题; - gen_prefix 例外:配置了不带尾部空格的
gen_prefix(如Answer:)时,分隔符不会被置空,以免Answer:与答案粘连;这是刻意保留的行为,不是 bug; - fewshot 与评测样本的分隔符来源不同:few-shot 示例使用
FewshotConfig中的target_delimiter(由fewshot_config或全局配置继承,见 lm_eval/config/task.py),评测样本使用任务级target_delimiter,两者在 Chat 模板场景下的最终渲染均以模板为准; - 缓存指纹区分:
apply_chat_template会修改缓存键与 tokenizer 指纹(如 lm_eval/models/api_models.py 的apply_chat_template实现),切换开关后建议清理旧请求缓存,避免命中过期结果。
总结
lm-evaluation-harness 通过将apply_chat_template=True场景下的 target delimiter 置空,消除了 Chat 模板格式化与默认空格分隔符之间的冲突,使 Chat 模型的 loglikelihood 打分与多选任务请求构造更贴近模型原生对话分布。理解construct_requests中置空规则、gen_prefix 例外分支与fewshot_context渲染路径的差异,是正确配置--apply_chat_template与编写 Chat 任务 YAML 的关键。相关实现细节可在 lm_eval/api/task.py、lm_eval/evaluator.py 与 lm_eval/config/task.py 中继续深入。
【免费下载链接】lm-evaluation-harnessA framework for few-shot evaluation of language models.项目地址: https://gitcode.com/GitHub_Trending/lm/lm-evaluation-harness
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考