开源模型调用

使用 Hugging Face 的 transformers 库可以比较方便地调用开源大语言模型。为了先把调用链路跑通,这里尽量选择一个小模型:

Qwen/Qwen2.5-0.5B-Instruct

这个模型只有 0.5B 参数,适合本地学习和调试。它的能力不能和 7B、14B 甚至更大的模型相比,但胜在下载快、显存占用低、启动成本小。

为什么先选小模型

刚开始学习开源模型调用时,不建议一上来就跑很大的模型,原因是:

  1. 大模型对显存要求更高,环境问题会掩盖调用逻辑本身。
  2. 小模型启动快,适合反复调试 prompt、tokenizer 和采样参数。
  3. 如果只是学习 tokenizer -> model.generate -> decode 这条链路,0.5B 或 1.5B 已经足够。
  4. 小模型可以在很多普通 GPU 甚至 CPU 上跑通,虽然 CPU 会慢一些。

模型权重大致显存可以这样估算:

$$ \text{显存} \approx \text{参数量} \times \text{每个参数占用字节数} $$

以 0.5B 参数为例:

精度 每个参数 权重大致占用
fp32 4 bytes 约 2GB
fp16 / bf16 2 bytes 约 1GB
int8 1 byte 约 0.5GB
int4 0.5 byte 约 0.25GB

实际推理时还会有 KV cache、中间激活和框架开销,所以真实占用会比权重本身更高。

环境准备

建议新建一个单独的 conda 环境:

1
2
conda create -n llm-call python=3.10
conda activate llm-call

安装依赖:

1
pip install torch transformers accelerate sentencepiece

如果使用 GPU,需要安装和 CUDA 版本匹配的 PyTorch。第一次运行时,模型会自动从 Hugging Face 下载到本地缓存。

最小调用示例

下面是一个最小可运行版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer


model_name = "Qwen/Qwen2.5-0.5B-Instruct"
device = "cuda" if torch.cuda.is_available() else "cpu"

tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16 if device == "cuda" else torch.float32,
).to(device)

messages = [
{"role": "system", "content": "你是一个简洁、准确的中文助手。"},
{"role": "user", "content": "用三句话解释什么是 Transformer。"},
]

text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)

inputs = tokenizer([text], return_tensors="pt").to(device)

outputs = model.generate(
**inputs,
max_new_tokens=256,
do_sample=True,
temperature=0.7,
top_p=0.9,
)

generated_ids = outputs[0][inputs.input_ids.shape[-1]:]
response = tokenizer.decode(generated_ids, skip_special_tokens=True)

print(response)

这段代码的核心流程是:

1
2
3
4
5
6
messages
-> apply_chat_template
-> tokenizer 编码
-> model.generate 生成
-> tokenizer 解码
-> 文本回答

chat template 的作用

指令模型通常不是直接把用户输入拼接给模型,而是有固定的对话格式。例如:

1
2
3
4
messages = [
{"role": "system", "content": "你是一个有帮助的助手。"},
{"role": "user", "content": "请解释什么是 KV Cache。"},
]

apply_chat_template 会把这种结构化对话转换成模型训练时熟悉的文本格式。不同模型的模板可能不同,所以一般不要手写模板,优先使用 tokenizer 自带的模板:

1
2
3
4
5
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)

其中 add_generation_prompt=True 表示前面是上下文,接下来应该轮到 assistant 生成回答。

generate 常用参数

generate() 里的参数会直接影响输出风格:

参数 含义 常见设置
max_new_tokens 最多生成多少个新 token 128256512
do_sample 是否采样 创作类用 True,确定性任务用 False
temperature 控制随机性 0.3 更稳定,0.7 较自然,1.0 更发散
top_p nucleus sampling 常用 0.80.95
repetition_penalty 抑制重复 常用 1.051.2

如果希望输出更稳定:

1
2
3
4
5
outputs = model.generate(
**inputs,
max_new_tokens=256,
do_sample=False,
)

如果希望输出更有创造性:

1
2
3
4
5
6
7
outputs = model.generate(
**inputs,
max_new_tokens=256,
do_sample=True,
temperature=0.8,
top_p=0.9,
)

如果模型容易重复,可以加入:

1
2
3
4
5
6
7
8
outputs = model.generate(
**inputs,
max_new_tokens=256,
do_sample=True,
temperature=0.7,
top_p=0.9,
repetition_penalty=1.1,
)

封装成一个类

实际使用时可以把加载模型和生成逻辑封装起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer


class LocalLLM:
def __init__(self, model_name: str = "Qwen/Qwen2.5-0.5B-Instruct"):
self.device = "cuda" if torch.cuda.is_available() else "cpu"
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
self.model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
).to(self.device)

def chat(self, prompt: str, system_prompt: str = "你是一个简洁、准确的中文助手。") -> str:
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt},
]

text = self.tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)

inputs = self.tokenizer([text], return_tensors="pt").to(self.device)
outputs = self.model.generate(
**inputs,
max_new_tokens=256,
do_sample=True,
temperature=0.7,
top_p=0.9,
)

generated_ids = outputs[0][inputs.input_ids.shape[-1]:]
return self.tokenizer.decode(generated_ids, skip_special_tokens=True)


if __name__ == "__main__":
llm = LocalLLM()
print(llm.chat("什么是大语言模型的推理?"))

这样后续想更换模型时,只需要修改 model_name

使用 pipeline 快速调用

pipeline 写法更短,适合快速验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from transformers import pipeline


pipe = pipeline(
"text-generation",
model="Qwen/Qwen2.5-0.5B-Instruct",
device_map="auto",
)

messages = [
{"role": "user", "content": "请用一段话解释什么是 tokenizer。"},
]

result = pipe(
messages,
max_new_tokens=256,
do_sample=True,
temperature=0.7,
top_p=0.9,
)

print(result[0]["generated_text"][-1]["content"])

pipeline 的优点是简单,缺点是很多细节被封装起来了。如果目的是理解推理流程,建议先掌握 AutoTokenizer + AutoModelForCausalLM 的写法。

写成命令行聊天脚本

可以新建一个 chat.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch


model_name = "Qwen/Qwen2.5-0.5B-Instruct"
device = "cuda" if torch.cuda.is_available() else "cpu"

tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16 if device == "cuda" else torch.float32,
).to(device)


def generate(prompt: str) -> str:
messages = [
{"role": "system", "content": "你是一个简洁、准确的中文助手。"},
{"role": "user", "content": prompt},
]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)
inputs = tokenizer([text], return_tensors="pt").to(device)
outputs = model.generate(
**inputs,
max_new_tokens=256,
do_sample=True,
temperature=0.7,
top_p=0.9,
)
generated_ids = outputs[0][inputs.input_ids.shape[-1]:]
return tokenizer.decode(generated_ids, skip_special_tokens=True)


while True:
prompt = input("\nUser: ")
if prompt.lower() in {"exit", "quit"}:
break
print("Assistant:", generate(prompt))

运行:

1
python chat.py

本地服务化调用

如果希望其他程序通过 HTTP 调用本地模型,可以用 FastAPI 包一层。

安装:

1
pip install fastapi uvicorn

示例 server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from fastapi import FastAPI
from pydantic import BaseModel
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch


class ChatRequest(BaseModel):
prompt: str


app = FastAPI()

model_name = "Qwen/Qwen2.5-0.5B-Instruct"
device = "cuda" if torch.cuda.is_available() else "cpu"

tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16 if device == "cuda" else torch.float32,
).to(device)


@app.post("/chat")
def chat(req: ChatRequest):
messages = [
{"role": "system", "content": "你是一个简洁、准确的中文助手。"},
{"role": "user", "content": req.prompt},
]

text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)

inputs = tokenizer([text], return_tensors="pt").to(device)
outputs = model.generate(
**inputs,
max_new_tokens=256,
do_sample=True,
temperature=0.7,
top_p=0.9,
)

generated_ids = outputs[0][inputs.input_ids.shape[-1]:]
response = tokenizer.decode(generated_ids, skip_special_tokens=True)
return {"response": response}

启动:

1
uvicorn server:app --host 127.0.0.1 --port 8000

调用:

1
2
3
curl -X POST http://127.0.0.1:8000/chat \
-H "Content-Type: application/json" \
-d "{\"prompt\":\"解释一下什么是注意力机制\"}"

常见问题

下载模型很慢

第一次运行会下载模型权重。如果 Hugging Face 访问慢,可以配置镜像,或者提前手动下载模型到本地。

显存不够

可以按这个顺序处理:

  1. 换更小的模型。
  2. 减小 max_new_tokens
  3. 尝试量化模型。
  4. 先用 CPU 跑通流程。

输出重复

可以尝试:

1
repetition_penalty=1.1

也可以降低 temperature

输出不符合预期

优先检查:

  1. 是否使用了正确的 chat template。
  2. system prompt 是否太模糊。
  3. max_new_tokens 是否太小。
  4. 采样参数是否过于发散。

小结

开源模型调用的核心流程可以概括为:

1
2
3
4
5
6
文本输入
-> chat template
-> tokenizer 编码
-> model.generate 自回归生成
-> tokenizer 解码
-> 文本输出

刚开始建议用 Qwen/Qwen2.5-0.5B-Instruct 这样的小模型跑通完整流程。理解 tokenizer、prompt、采样参数和显存占用之后,再切换到 1.5B、3B、7B 等更大的模型。