LLM Intro(五):用 Hugging Face 调用与评估模型

理解网络结构以后,使用预训练模型还需要把模型权重、tokenizer、输入格式和生成配置连接起来。一个模型能正常输出文字,不代表输入处理正确;两个模型得分不同,也可能只是模板或解码设置不同。

本文沿着一次完整调用展开:先加载匹配的组件,再构造模型认识的输入,最后用与任务相符的方法评估结果。

模型仓库里不只有权重

一个可使用的模型通常还需要架构配置、词表、tokenizer 规则、特殊 token 定义,以及生成或对话模板配置。权重决定参数数值,配置决定怎样搭建网络,tokenizer 决定整数 ID 的含义。

Hugging Face 的 transformers 库用 Auto 类根据配置选择具体实现。AutoTokenizer 负责文本编码与解码;AutoModel 通常提供基础隐藏状态;AutoModelForCausalLM 则包含用于自回归语言建模的词表输出头和生成接口。选错类可能得到一个可以前向运行、却没有预期任务输出的对象。

模型 ID 标识一个仓库,revision 可以进一步固定提交或版本。记录模型 ID 而不记录 revision,在仓库内容更新后未必能够完全重现实验。

先区分 base 与 instruct 模型

Base model 主要从文本续写目标中学习。例如输入“法国的首都是”,它会继续这段文本。Instruct 或 chat 模型经过额外训练,学习按特定消息格式接收任务并给出回答。

两者最终都进行 token 预测,但训练分布不同。给 base model 输入一段聊天角色标记,不会自动赋予它对应的对话能力;给 chat model 随意拼接角色名称,也可能偏离它训练时使用的格式。

常见消息角色包括 system、user 和 assistant,分别用于应用级说明、用户输入和模型回答。具体模型支持哪些角色、如何编码,仍由模板决定。

用 chat template 构造输入

消息列表是应用中的数据结构,模型真正接收的是 token 序列。Chat template 把角色、消息边界和内容转换为模型约定的序列,并在需要时加上 assistant 开始生成的位置标记。

下面以 Qwen2.5-0.5B-Instruct 为例完成一次单设备调用。需要安装 torch 与 transformers,并下载对应模型;默认在 CPU 上运行,速度会受硬件影响。

import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

model_id = "Qwen/Qwen2.5-0.5B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)
model.eval()

messages = [
    {"role": "system", "content": "用中文解释技术概念。"},
    {"role": "user", "content": "softmax 为什么要减去最大值?"},
]
prompt = tokenizer.apply_chat_template(
    messages, tokenize=False, add_generation_prompt=True
)
inputs = tokenizer(prompt, add_special_tokens=False, return_tensors="pt")
with torch.inference_mode():
    output = model.generate(**inputs, max_new_tokens=128, do_sample=False)

new_ids = output[0, inputs["input_ids"].shape[1]:]
print(tokenizer.decode(new_ids, skip_special_tokens=True))

这里先得到模板文本,再单独 tokenize,所以设置 add_special_tokens=False,避免把模板已经包含的特殊 token 重复加入。generate() 对这类 decoder-only 模型返回包含输入前缀的完整序列,因此要根据输入长度截取新生成部分。

多轮对话需要将之前的 user 和 assistant 消息一起保留。仅重复发送最后一句话,模型就看不到前面的约定。消息过长时,还需要明确如何截断或压缩历史。模板接口说明见 Hugging Face Chat templates。

批量输入时,补齐方向也影响生成

Tokenizer 可以同时处理多段文本,返回 input_ids 和 attention_mask。它们的形状通常是 (B,T),其中 TT 是这个 batch 补齐后的长度。

对常见 decoder-only 生成接口,左侧补齐能让每条输入的最后一列都是有效前缀的末尾,便于从最后位置的 logits 继续生成。若使用右侧补齐而接口仍读取最后一列,就可能从 PAD 位置开始预测。具体行为应与模型及生成接口约定一致。

Pad token 也需要在模型配置中定义。部分模型用 EOS 兼作 PAD,此时 attention mask 尤其重要:不能只看某个 ID 等于 EOS,就把所有相同 ID 都当作补齐内容。

设备放置同样要一致。若将单设备模型移到 GPU,输入张量也需要移动;若使用设备映射把模型分到多个设备,则应遵循该加载方式的输入约定,不能再简单地对整个模型调用一次 .to()。

生成参数分别改变什么

max_new_tokens 限制新生成的 token 数,通常比同时包含输入长度的上限更容易解释。达到上限会截断回答,因此评测时要检查输出是否完整。

do_sample=False 且 num_beams=1 时使用贪心策略;启用采样后,温度、top-k、top-p 才会参与相应的候选选择。降低温度可能减少输出变化,但不会修复模型缺少的知识。终止 token、停止字符串和重复惩罚等配置也会改变可见结果。

比较模型时,应固定输入模板、长度预算和解码策略,再记录模型版本、数据集版本及随机种子。否则同一个问题产生不同回答,可能来自采样或模板,而不完全来自参数能力。

先定义任务,再选择指标

对于分类或选择题,可以计算准确率:正确样本数除以总样本数。但必须先定义答案如何解析。例如要求输出 A、B、C、D 时,额外解释不应被随意误判为错误,也不能从长回答中挑一个偶然出现的字母作为答案。

有唯一标准答案的问答可以使用 Exact Match,并事先规定大小写、空格、标点等归一化规则。BLEU、ROUGE 等指标比较文本片段重合,适合某些翻译、摘要评估,但不能直接证明事实正确。

这些指标关注最终输出。语言建模还可以直接评估模型对真实文本分配的概率。

困惑度测量的是目标文本的平均可预测程度

给定真实 token 序列,计算每个目标 token 的条件概率,再取平均负对数似然:

NLL=−1N∑t=1Nlog⁡pθ(xt∣x<t),PPL=exp⁡(NLL).\mathrm{NLL}=-\frac1N\sum_{t=1}^{N}\log p_\theta(x_t\mid x_{<t}), \qquad \mathrm{PPL}=\exp(\mathrm{NLL}).

如果模型在每一步都均匀分配概率给 VV 个候选,那么每个真实目标概率都是 1/V1/V,困惑度就是 VV。如果模型更集中地预测正确目标,NLL 和 PPL 就会降低。

计算时应将输入与目标正确错位,并排除 padding。批次长度不同时,应该累计有效 token 的损失总和与数量,再求整体平均;简单平均每个 batch 的均值可能错误地给短 batch 更大权重。

PPL 不直接衡量是否遵循指令。不同 tokenizer 的 token 单位也不同,因此不能把不同切分体系下的 token-level PPL 当作完全相同的量直接比较。

开放式回答怎样比较

开放式回答可能有很多合理表达,词面匹配会遗漏质量差异。成对人工评审可以在同一问题下比较两个回答,统计胜率,并把平局单独处理。Elo 等排序方法可以根据多次对比形成相对评分,但评分依赖参与比较的模型集合和样本分布。

用模型充当评审可以降低评估成本,但它也可能偏好更长的回答、特定格式或排在前面的候选。比较时可以交换答案顺序,使用明确的评分标准,并抽样人工复核。模型评分应该与具体任务证据一起解释。

最后应保留失败样本。格式解析错误、知识错误、推理错误、上下文截断和生成提前停止,往往需要不同的解决办法。总分告诉我们表现如何,错误分析才告诉我们下一步应修改哪里。


系列目录 · 上一篇:从注意力到完整的 Transformer · 下一篇:GPU 执行模型与 Triton 算子

本文采用 CC BY-NC-SA 4.0 协议