模型加载与基础推理:从零跑通LLaMA
好,咱们今天进入实操环节。说实话,很多同学学大模型,理论背得滚瓜烂熟,一上手加载模型就报错。我当年第一次加载LLaMA时,光一个tokenizer就折腾了半小时——后来发现是路径写错了。嗯,这种坑咱们今天一并填上。
1. 用Transformers加载LLaMA模型
加载模型,说白了就两行代码的事。但这两行背后,Transformers库帮你做了大量脏活累活。
from transformers import AutoModelForCausalLM, AutoTokenizer
model_name = "meta-llama/Llama-2-7b-chat-hf"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)
你看,就这么简单。但我要提醒你——第一次跑的时候,确保网络通畅。模型文件动辄十几GB,下载时间够你喝两杯咖啡。
加载完成后,你可以用 model.device 看看模型在哪个设备上。默认是CPU,想用GPU得手动指定:
model = AutoModelForCausalLM.from_pretrained(
model_name,
device_map="auto", # 自动分配到GPU
torch_dtype=torch.float16 # 半精度,省显存
)
我个人习惯用 device_map="auto",它会自动把模型各层分配到可用的GPU上。如果你的显卡只有8GB显存,跑7B模型有点吃力,可以考虑量化版本。
2. AutoModel与AutoTokenizer详解
这两个类,是Transformers库的「万能钥匙」。你想想看,不同模型的结构千差万别——BERT是编码器,GPT是解码器,LLaMA是纯解码器。如果每个模型都写一个加载类,代码得膨胀成什么样?
AutoModel 和 AutoTokenizer 的作用,就是根据你传入的模型名称,自动匹配对应的类。
| 类名 | 作用 | 典型返回类型 |
|---|---|---|
| AutoModel | 加载模型本体(无LM头) | LlamaModel |
| AutoModelForCausalLM | 加载带因果语言模型头的版本 | LlamaForCausalLM |
| AutoTokenizer | 加载对应的分词器 | LlamaTokenizer |
这里有个细节:AutoModelForCausalLM 和 AutoModel 的区别。前者在模型顶部加了一个线性层,用于预测下一个token。如果你只是想做特征提取,用AutoModel就够了;但要做文本生成,必须用带LM头的版本。
AutoModelForCausalLM.from_pretrained(..., output_hidden_states=True) 来获取每一层的隐藏状态,这在做模型分析时非常有用。
Tokenizer这边,我踩过一个坑:LLaMA的tokenizer默认没有pad_token。如果你要做batch推理,必须手动设置:
tokenizer.pad_token = tokenizer.eos_token
# 或者
tokenizer.add_special_tokens({'pad_token': '[PAD]'})
不设置的话,你会看到类似 ValueError: pad_token_id must be set 的错误。嗯,这个错误我至少见过五次。
3. 单次推理:让模型说第一句话
模型加载好了,咱们让它说句话试试。单次推理的流程很简单:
- 用tokenizer把文本转成token ID
- 喂给模型
- 把输出的token ID转回文本
input_text = "什么是人工智能?"
inputs = tokenizer(input_text, return_tensors="pt")
# 推理
outputs = model.generate(
inputs.input_ids,
max_new_tokens=50
)
# 解码
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(response)
你看,核心就三步。但这里有个容易忽略的点——return_tensors="pt" 表示返回PyTorch张量。如果你用TensorFlow,就改成 "tf"。
另外,skip_special_tokens=True 这个参数很重要。如果不加,你会看到一堆 <s>、</s> 之类的特殊token,影响阅读。
4. 生成参数:temperature、top_p、top_k
好,现在模型能说话了,但说的内容可能很「死板」。为什么?因为默认的生成策略是贪心搜索——每次都选概率最高的token。这会导致生成结果缺乏多样性。
这时候就需要调整生成参数了。我重点讲三个最常用的:
4.1 Temperature(温度)
Temperature控制的是概率分布的「尖锐程度」。值越小,模型越倾向于选高概率词;值越大,低概率词也有机会被选中。
- temperature=0.1:几乎确定性的输出,适合事实性问答
- temperature=0.7:平衡创造性和准确性,日常对话常用
- temperature=1.5:输出很随机,适合创意写作
outputs = model.generate(
inputs.input_ids,
max_new_tokens=100,
temperature=0.7,
do_sample=True # 必须开启,否则temperature无效
)
do_sample=True。我见过有人调了半天temperature没效果,最后发现忘了加这个参数。
4.2 Top-k 采样
Top-k的思路很简单:每次只从概率最高的k个token中采样。比如k=50,就只考虑前50个最可能的词。
这样做的好处是——避免选出概率极低的「离谱词」。我做过实验,不加top-k时,模型偶尔会蹦出一些完全不相关的词,比如在讨论天气时突然说「恐龙」。
outputs = model.generate(
inputs.input_ids,
max_new_tokens=100,
do_sample=True,
top_k=50
)
4.3 Top-p(核采样)
Top-p和top-k类似,但更灵活。它不是固定选前k个,而是选概率累积和超过p的最小集合。比如p=0.9,就选概率加起来超过90%的那些token。
我个人更偏爱top-p,因为它能动态调整候选集大小。当模型很确定时,候选集小;当模型不确定时,候选集大。
outputs = model.generate(
inputs.input_ids,
max_new_tokens=100,
do_sample=True,
top_p=0.9
)
4.4 组合使用
实际项目中,这三个参数经常组合使用。我常用的配置是:
outputs = model.generate(
inputs.input_ids,
max_new_tokens=200,
do_sample=True,
temperature=0.7,
top_k=50,
top_p=0.9
)
这个组合的效果是:先用top-k砍掉尾部低概率词,再用top-p动态调整候选集,最后用temperature微调概率分布。三层过滤下来,生成质量会稳定很多。
5. 知识体系总览
为了让你更直观地理解本章的知识结构,我画了一张流程图:
这张图把整个流程串起来了。从加载模型开始,到分词、推理,再到生成参数的调优,每一步都有对应的代码和注意事项。
6. 避坑指南
最后,我总结几个实战中容易踩的坑:
- 显存不足:7B模型用float16大概占14GB显存。如果你的卡只有8GB,考虑用4bit量化版本,或者用
device_map="sequential"手动分配。 - 生成速度慢:默认是CPU推理。记得用
model.to("cuda")或者device_map="auto"。 - 输出重复:可以设置
repetition_penalty=1.1来惩罚重复的token。 - 中文乱码:确保tokenizer支持中文。LLaMA原版对中文支持一般,建议用中文微调版本。
我曾经在部署一个对话系统时,发现模型总是重复「好的好的好的」,调了半天参数才发现是 repetition_penalty 没设置。嗯,这种细节往往最容易被忽略。
好了,模型加载和基础推理就讲到这里。你现在应该能自己跑通一个LLaMA模型,并且通过调整生成参数来控制输出风格了。下一章咱们会深入聊推理加速,那才是真正考验工程能力的地方。