02_eval_and_infer.ipynb 说明文档这份 notebook 的任务不是继续训练,而是做另一件同样重要的事:
这一步非常关键,因为很多同学第一次做 LoRA 实验时,会以为:
其实不是。
真正完整的闭环应该是:
02_eval_and_infer.ipynb 就是在验证第 3、4 步。
它做了 4 件事:
所以,这份 notebook 其实是在回答:
# 02_eval_and_infer
这个 notebook 用于加载训练后的输出目录,做简单推理测试。这一格没有代码,只是在告诉你:
代码:
from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import PeftModel
import torch
base_model_name = "/root/course_lora/models/tiny-gpt2"
adapter_dir = "outputs/notebook_demo"
tokenizer = AutoTokenizer.from_pretrained(adapter_dir)
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
base_model = AutoModelForCausalLM.from_pretrained(base_model_name)
model = PeftModel.from_pretrained(base_model, adapter_dir)
model.eval()
if torch.cuda.is_available():
model = model.cuda()
print("Model loaded.")这是整份 notebook 最重要的一格。
它在做一件 LoRA 实验里非常关键的事:
这一步会帮助你真正理解:
from transformers import AutoTokenizer, AutoModelForCausalLM导入两个 Hugging Face 常用类:
AutoTokenizer
AutoModelForCausalLM
这里和 01_lora_demo.ipynb 一样,说明当前实验仍然在 GPT 风格的因果语言模型框架里工作。
from peft import PeftModel导入 PeftModel。
这是这一格的新重点。
如果前一个 notebook 主要在讲:
get_peft_model() 怎么把 LoRA 接到模型上那么这里要看的就是:
PeftModel.from_pretrained(...) 怎么把训练后的 adapter 再加载回来import torch导入 PyTorch。
后面会用它判断 GPU 是否可用,并把模型搬到 GPU 上。
base_model_name = "/root/course_lora/models/tiny-gpt2"定义 base model 的本地路径。
这个目录里放的是:
它是你训练之前的底座模型。
adapter_dir = "outputs/notebook_demo"定义 adapter 的保存目录。
这是前一个 notebook 训练结束后保存的结果目录。
里面重点存的是:
tokenizer = AutoTokenizer.from_pretrained(adapter_dir)从输出目录里加载 tokenizer。
为什么不是从 base_model_name 里加载?
因为课程代码在训练结束后把 tokenizer 也保存到了输出目录里。
这样后续推理时:
这是一种很常见的工程习惯。
if tokenizer.pad_token is None:再次检查 tokenizer 是否有 pad token。
为什么推理阶段还要检查?
因为 notebook 想保持稳妥,不假设一切默认都已经配置好。
tokenizer.pad_token = tokenizer.eos_token如果没有 pad token,就继续沿用:
eos_token 充当 pad token这和前一个 notebook 保持一致。
base_model = AutoModelForCausalLM.from_pretrained(base_model_name)从原始模型目录加载 base model。
注意这里得到的还只是:
model = PeftModel.from_pretrained(base_model, adapter_dir)这一行是整格最核心的一行。
它的意思是:
base_modeladapter_dir 里读取 LoRA adapterbase_model 上也就是说,这一步之后的 model 才是:
这一行其实正好回答了很多同学最容易糊涂的问题:
答案是:
model.eval()把模型切换到评估模式。
eval() 的作用可以简单理解成:
这样像 dropout 这类训练时才会随机生效的机制,在推理时就会关闭。
这是一个标准动作:
model.train()model.eval()if torch.cuda.is_available():如果当前机器有可用 GPU,就继续执行下面一行。
model = model.cuda()把模型放到 GPU 上。
这里的 .cuda() 可以先简单理解成:
如果不把模型搬过去,而后面输入又在 GPU 上,就会报设备不一致错误。
print("Model loaded.")打印加载完成提示。
这一格跑通之后,说明:
代码:
prompt = "Instruction: 将下面这句话改写得更清楚。\nInput: 学生要先学会检查 GPU,再开始训练。\nResponse:"
inputs = tokenizer(prompt, return_tensors="pt")
if torch.cuda.is_available():
inputs = {k: v.cuda() for k, v in inputs.items()}
with torch.no_grad():
outputs = model.generate(**inputs, max_new_tokens=50)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))这格是在做一次最小推理实验。
它的逻辑很简单:
generate()prompt = "..."定义一个字符串 prompt。
这里的 prompt 格式和训练时故意保持一致:
Instruction: ...
Input: ...
Response:
最后只写到 Response:,没有把答案写出来。
这样模型就会尝试继续生成它认为合适的回复。
这一点非常重要,因为它体现了一个课程关键点:
inputs = tokenizer(prompt, return_tensors="pt")把 prompt token 化。
这里的 return_tensors="pt" 很关键:
pt 表示返回 PyTorch tensor如果不加这项,tokenizer 可能只返回普通 Python 列表。
而模型推理通常需要的是张量。
所以这行代码做的事情是:
通常里面会包括:
input_idsattention_maskif torch.cuda.is_available():如果有 GPU,就继续把输入也搬到 GPU。
inputs = {k: v.cuda() for k, v in inputs.items()}这一行是一个字典推导式。
意思是:
inputs 这个字典里的每个键值对.cuda()为什么要这样做?
因为 inputs 不是单个张量,而是一个字典,例如:
{
"input_ids": tensor(...),
"attention_mask": tensor(...)
}所以必须把字典里的每个张量都搬到 GPU。
这一步也非常重要,因为:
with torch.no_grad():这是 PyTorch 推理时很常见的一句。
意思是:
为什么?
因为现在是推理,不是训练。
不计算梯度的好处是:
outputs = model.generate(**inputs, max_new_tokens=50)开始真正生成文本。
这是 Hugging Face 模型推理里最常见的接口之一。
generate(...)表示:
**inputs这里的 ** 是 Python 里的“字典展开”。
它的意思是:
inputs 字典里的内容拆开,当作函数参数传进去例如如果 inputs 是:
{"input_ids": ..., "attention_mask": ...}那么:
model.generate(**inputs)就相当于:
model.generate(input_ids=..., attention_mask=...)max_new_tokens=50表示:
为什么不是越大越好?
因为这只是最小演示:
print(tokenizer.decode(outputs[0], skip_special_tokens=True))这行是在把模型输出重新变成人能读懂的文本。
outputs[0]因为这里通常只输入了一条 prompt,所以只取第一条输出。
tokenizer.decode(...)decode 的作用是:
skip_special_tokens=True表示:
例如不要把某些控制 token 直接原样打印出来。
如果这格能顺利跑完,说明:
generate() 能正常工作换句话说,这一步证明:
这里的结果主要用于验证“训练输出可被加载、推理流程正常”,不追求真正高质量的生成效果。这句话非常重要。
很多同学第一次看到生成结果时,会立刻问:
但这份 notebook 的目标不是生成高质量答案,而是验证:
也就是说,这里真正想确认的是:
而不是:
这就是为什么推理时要先:
再:
你训练时用的是:
Instruction: ...
Input: ...
Response: ...
那推理时最好也按这个格式来。
推理阶段至少有两个标准动作:
model.eval()torch.no_grad()这是大模型工程里非常基础的习惯。
如果说:
01_lora_demo.ipynb
那么:
02_eval_and_infer.ipynb
这两份 notebook 加在一起,才构成了真正完整的最小闭环:
如果你真的理解了它,至少应该能回答:
PeftModel.from_pretrained(...) 在做什么?model.eval()?inputs 也要搬到 GPU?generate() 输出后还要 decode()?如果这些问题你都能用自己的话说清楚,那说明你已经不只是“跑通了实验”,而是已经开始真正理解 LoRA 推理链路。