系列文章导航:AI系列文章导航目录-持续更新中
第05课:本地部署开源小模型实战
📝 本文摘要:本文指导在本地部署开源模型,包括硬件需求评估(显存估算)、Ollama一键部署方案(安装、运行、API调用)、量化技术(FP16/INT8/INT4及GGUF格式质量对比)、HuggingFace+transformers灵活方案、vLLM生产级加速(PagedAttention/连续批处理),以及从HuggingFace下载原始权重自行加载的完整实操(理解config.json/model.safetensors/tokenizer三大核心文件和KV Cache机制)。
这节课你要动手了------在你的电脑上跑起来一个大模型。理解"模型是怎么跑的"比只会调API重要100倍。
一、为什么要在本地跑模型
| 场景 | 本地部署 | API调用 |
|---|---|---|
| 数据安全 | ✅ 数据不出本机 | ❌ 数据发到云端 |
| 成本 | ✅ 免费但有硬件成本 | 按Token收费 |
| 延迟 | ✅ 无网络延迟 | 取决于网络和服务端 |
| 模型选择 | ❌ 受限于本地硬件 | ✅ 所有模型可选 |
| 离线使用 | ✅ 可以 | ❌ 不行 |
| 学习理解 | ✅ 深入理解模型运行 | ❌ 黑盒调用 |
结论:作为大模型应用开发者,你必须两种方式都会。
二、硬件需求评估
2.1 显存估算
模型参数量 × 2字节(FP16) = 最小显存需求
7B模型: 7B × 2 = 14GB (FP16)
7B × 1 = 7GB (INT8量化)
7B × 0.5 = 3.5GB (INT4量化) ← 8GB显卡可跑
14B模型: 14B × 2 = 28GB (FP16)
14B × 1 = 14GB (INT8)
14B × 0.5 = 7GB (INT4) ← 16GB显卡可跑
72B模型: 72B × 0.5 = 36GB (INT4) ← 需要2×24GB显卡
2.2 你的机器能跑什么
| 显卡 | 推荐模型(INT4量化) |
|---|---|
| 无独显(仅CPU) | Qwen2.5-0.5B, 1.5B |
| 8GB (M1/M2 MacBook) | Qwen2.5-7B, Llama3.1-8B |
| 16GB (M3/M4 Pro) | Qwen2.5-14B, Llama3.1-8B |
| 24GB (4090/3090) | Qwen2.5-32B, DeepSeek-R1-8B |
| 48GB+ (M4 Max/双卡) | Qwen2.5-72B, DeepSeek-V3-0324 |
三、Ollama:最简单的本地部署方案
3.1 安装
bash
# macOS
brew install ollama
# Linux
curl -fsSL https://ollama.com/install.sh | sh
# 验证
ollama --version
3.2 运行第一个模型
bash
# 拉取模型(自动选择量化版本)
ollama pull qwen2.5:7b
# 运行
ollama run qwen2.5:7b
# 现在你可以直接在终端对话了
>>> 你好,请介绍一下你自己
>>> 用Python写一个快速排序
3.3 常用模型一键部署
bash
# 中文能力强
ollama pull qwen2.5:7b
# 推理模型
ollama pull deepseek-r1:8b
# 代码模型
ollama pull qwen2.5-coder:7b
# 小模型(低配机器)
ollama pull qwen2.5:1.5b
ollama pull qwen2.5:0.5b
# 嵌入模型
ollama pull bge-m3
3.4 API调用
Ollama提供OpenAI兼容的API,你的应用代码不需要改:
python
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama" # 任意值
)
response = client.chat.completions.create(
model="qwen2.5:7b",
messages=[
{"role": "system", "content": "你是Python编程专家。"},
{"role": "user", "content": "写一个装饰器,计算函数执行时间"}
],
temperature=0.3
)
print(response.choices[0].message.content)
3.5 curl调用
bash
curl http://localhost:11434/api/chat -d '{
"model": "qwen2.5:7b",
"messages": [
{"role": "user", "content": "你好"}
],
"stream": false
}'
四、深入理解:量化(Quantization)
4.1 什么是量化
量化(Quantization):降低模型参数的数值精度,以减少存储和计算开销
FP16(Float Point 16,16位浮点数): 每个参数用16位浮点数存储 (如: 0.123456789012)
INT8(Integer 8,8位整数): 每个参数用8位整数存储 (如: 127,映射回浮点)
INT4(Integer 4,4位整数): 每个参数用4位存储 (如: 7,映射回浮点)
效果:
FP16 → INT4: 显存降75%,速度提升2-3x,精度损失约1-3%
4.2 GGUF格式(GPT-Generated Unified Format,GPT生成的统一格式)
Ollama使用的模型格式,由Georgi Gerganov创建(GG = Georgi Gerganov的缩写):
GGUF特性:
- 单文件分发(一个.gguf文件包含所有内容)
- 支持多种量化级别(Q4_0, Q5_1, Q8_0等)
- 支持CPU/GPU混合推理
- 元数据内置(不需要额外的config文件)
量化级别选择:
Q4_K_M: 4bit量化,推荐日常使用(性价比最高)
Q5_K_M: 5bit量化,质量更好,显存够就用这个
Q8_0: 8bit量化,接近原始质量,显存需求翻倍
F16: 无量化,原始质量,显存需求最大
4.3 量化对质量的影响
精度 速度 显存
FP16(原始) ★★★★★ ★★ ★
Q8_0 ★★★★☆ ★★★ ★★
Q5_K_M ★★★★ ★★★★ ★★★
Q4_K_M ★★★☆ ★★★★ ★★★★
Q3_K_M ★★★ ★★★★★ ★★★★★
建议:7B模型用Q5_K_M,14B模型用Q4_K_M,32B+用Q4_K_M。
五、HuggingFace + transformers:更灵活的方案
5.1 安装
bash
pip install transformers torch accelerate
5.2 加载模型
python
from transformers import AutoModelForCausalLM, AutoTokenizer
model_name = "Qwen/Qwen2.5-7B-Instruct"
# 加载tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 加载模型(自动选择设备)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype="auto", # 自动选择精度
device_map="auto", # 自动分配GPU/CPU
)
# 推理
messages = [
{"role": "system", "content": "你是编程助手。"},
{"role": "user", "content": "什么是递归?"}
]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
inputs = tokenizer(text, return_tensors="pt").to(model.device)
outputs = model.generate(
**inputs,
max_new_tokens=512,
temperature=0.7,
do_sample=True
)
# 只取新生成的部分
response = tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
print(response)
5.3 使用vLLM加速推理(生产环境)
bash
pip install vllm
python
from vllm import LLM, SamplingParams
llm = LLM(model="Qwen/Qwen2.5-7B-Instruct")
sampling_params = SamplingParams(temperature=0.7, max_tokens=512)
outputs = llm.generate(["什么是递归?"], sampling_params)
print(outputs[0].outputs[0].text)
vLLM的核心优化:
- PagedAttention:类似操作系统的虚拟内存,高效管理KV Cache
- 连续批处理:动态合并请求,GPU利用率接近100%
- 比transformers快5-20倍
六、模型文件结构理解
下载一个模型后,你看到的是什么:
Qwen2.5-7B-Instruct/
├── config.json # 模型配置:层数、维度、词表大小等
├── tokenizer.json # Tokenizer配置
├── tokenizer_config.json
├── special_tokens_map.json
├── generation_config.json # 生成参数默认值
├── model-00001-of-00004.safetensors # 模型权重(分片存储,safetensors是一种安全的权重存储格式)
├── model-00002-of-00004.safetensors
├── model-00003-of-00004.safetensors
├── model-00004-of-00004.safetensors
└── model.safetensors.index.json # 权重索引(记录每个分片包含哪些层的参数)
**config.json关键参数**:
```json
{
"hidden_size": 3584, // d_model: 隐藏层维度
"intermediate_size": 18944, // FFN中间层维度(约4×hidden_size)
"num_attention_heads": 28, // 注意力头数
"num_hidden_layers": 28, // Transformer层数
"num_key_value_heads": 4, // GQA(Grouped Query Attention,分组查询注意力)的KV头数
// 4<<28,即28个Query头共享4组Key/Value头
// KV Cache只需1/7的显存(4/28=1/7)
"vocab_size": 152064, // 词表大小 "max_position_embeddings": 131072, // 最大上下文长度
"rope_theta": 1000000.0 // RoPE(Rotary Position Embedding,旋转位置编码)频率基数
// 控制位置编码的"粒度"------值越大,长距离位置区分越精细
}
📝 作业
作业1:在本地跑起来一个7B模型
步骤:
- 安装Ollama
- 拉取qwen2.5:7b
- 用终端对话测试
- 用Python OpenAI兼容API调用
- 尝试不同的temperature(0.0, 0.5, 1.0),观察输出差异
参考答案:
bash
# Step 1: 安装
brew install ollama
# Step 2: 启动服务
ollama serve
# Step 3: 拉取模型
ollama pull qwen2.5:7b
# Step 4: 终端测试
ollama run qwen2.5:7b
# 输入: "用Python写一个二分查找" 验证能正常回复
# 输入: /bye 退出
python
# Step 5: Python API调用 + Temperature对比
from openai import OpenAI
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
prompt = "用一句话解释什么是递归"
for temp in [0.0, 0.5, 1.0]:
response = client.chat.completions.create(
model="qwen2.5:7b",
messages=[{"role": "user", "content": prompt}],
temperature=temp,
max_tokens=100
)
print(f"Temperature {temp}: {response.choices[0].message.content}\n")
# 预期观察:
# temp=0.0: 每次输出完全相同,确定性最高
# temp=0.5: 略有变化,但语义一致
# temp=1.0: 每次措辞不同,可能语义也略有偏移
作业2:阅读模型的config.json
用HuggingFace找到Qwen2.5-7B-Instruct的config.json,回答:
- 这个模型有多少层Transformer?
- GQA的压缩比是多少?(attention_heads / kv_heads)
- 最大上下文长度是多少?
参考答案:
1. 28层(num_hidden_layers: 28)
2. 压缩比 = 28/4 = 7x(28个注意力头,4个KV头,KV Cache只需1/7的显存)
3. 131,072 tokens(128K上下文)
七、进阶动手:从 HuggingFace 下载源码权重,自己把模型跑起来
这一节不用 Ollama,而是一步步 clone 模型权重、用
transformers加载,体会"模型是怎么被代码加载进内存并推理的"。
7.1 先评估你的机器能跑什么
参考机器(Apple M4 Pro / 48GB 统一内存),可选的小模型:
| 模型 | 参数量 | FP16显存 | 推荐场景 |
|---|---|---|---|
| Qwen3-4B | 4B | ~8GB | ✅ 首选------结构简单,支持think模式,学习体验完整 |
| Qwen3-1.7B | 1.7B | ~3.4GB | 更快但能力稍弱 |
| DeepSeek-R1-Distill-Qwen-7B | 7B | ~14GB | 推理能力强,但结构稍复杂 |
本节以 Qwen3-4B 为例。
7.2 下载模型权重(方法一:git-lfs)
bash
# 1. 安装 git-lfs(如未安装)
brew install git-lfs
git lfs install
# 2. 创建一个目录存放模型
mkdir -p ~/models
cd ~/models
# 3. Clone 模型仓库(只下载4B版本)
git clone https://huggingface.co/Qwen/Qwen3-4B
# 说明:这个仓库里包含的是模型的原始权重(PyTorch格式)
# 不是GGUF,不是Ollama格式,是 huggingface transformers 原生支持的格式
⚠️ 模型仓库约 8GB,下载时间取决于你的网速(国内建议开代理)。
7.3 看看你下载了什么东西
bash
cd ~/models/Qwen3-4B
ls -lh
你会看到类似这样的文件结构:
Qwen3-4B/
├── config.json # ← 模型架构配置(层数、维度、头数等)
├── tokenizer.json # ← Tokenizer 词表
├── tokenizer_config.json # ← Tokenizer 配置
├── generation_config.json # ← 生成参数默认值
├── model.safetensors # ← 模型权重(单文件约8GB)
├── merged_all_*.txt # ← 训练数据说明等
└── README.md
关键理解:
config.json定义了"这个网络长什么样"(多少层、每层的维度)model.safetensors是训练好的参数值(千亿个浮点数)tokenizer.json是文本↔数字的映射表- 这三个东西合在一起,才是一个"能运行的模型"
7.4 加载模型并推理
创建一个 Python 文件 run_qwen3.py:
python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
# 模型路径(指向你clone下来的目录)
model_path = "~/models/Qwen3-4B"
# ========== 第一步:加载 Tokenizer ==========
# Tokenizer 负责把人类文字转成模型能理解的数字(token IDs)
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
print("Tokenizer loaded!")
print(f"词汇表大小: {len(tokenizer)}")
print(f"示例 encode: '你好' → {tokenizer.encode('你好')}")
# ========== 第二步:加载模型权重 ==========
# AutoModelForCausalLM 会自动根据 config.json 创建网络结构,
# 然后从 safetensors 文件填充参数值
model = AutoModelForCausalLM.from_pretrained(
model_path,
torch_dtype=torch.bfloat16, # M系列芯片用 bfloat16 效率最高
device_map="auto", # 自动分配到 MPS(Apple GPU)
trust_remote_code=True,
)
print(f"\nModel loaded!")
print(f"参数量: {sum(p.numel() for p in model.parameters()) / 1e9:.2f}B")
print(f"设备: {next(model.parameters()).device}")
# ========== 第三步:准备输入 ==========
# Qwen3 使用 ChatML 格式的对话模板
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "你是谁?请简要介绍自己。"}
]
# apply_chat_template 把对话转成模型认识的格式
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True # 加上 assistant 的标记,告诉模型"该你回复了"
)
print(f"\nPrompt:\n{text}")
# 转成 token IDs 并移到 GPU
inputs = tokenizer(text, return_tensors="pt").to(model.device)
print(f"Input token count: {inputs['input_ids'].shape[1]}")
# ========== 第四步:生成回复 ==========
with torch.no_grad(): # 推理不需要计算梯度
outputs = model.generate(
**inputs,
max_new_tokens=256, # 最多生成256个新token
temperature=0.7, # 采样温度
top_p=0.9, # nucleus sampling
do_sample=True, # 启用采样(false则贪婪解码)
)
# 只解码新生成的部分(去掉输入prompt)
new_tokens = outputs[0][inputs["input_ids"].shape[1]:]
response = tokenizer.decode(new_tokens, skip_special_tokens=True)
print(f"\n{'='*40}")
print(f"模型回复:\n{response}")
print(f"{'='*40}")
print(f"生成 token 数: {len(new_tokens)}")
运行:
bash
cd ~/models
python3 run_qwen3.py
第一次加载会比较慢(需要从磁盘读取8GB权重),之后如果保持进程运行,再次生成就很快了。
7.5 观察输出,理解发生了什么
运行后你会看到类似输出:
Tokenizer loaded!
词汇表大小: 151936
示例 encode: '你好' → [108386, 11319]
Model loaded!
参数量: 4.00B
设备: mps:0
Prompt:
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
你是谁?请简要介绍自己。<|im_end|>
<|im_start|>assistant
Input token count: 25
========================================
模型回复:
我是 Qwen,由阿里云开发的大型语言模型...
========================================
生成 token 数: 58
理解这些数字:
词汇表大小: 151936→ 模型认识 15 万个不同的"词片段"参数量: 4.00B→ 40 亿个可训练参数Input token count: 25→ 你的输入被切成了25个token生成 token 数: 58→ 模型生成了58个token(约等于中文几十个字)device: mps:0→ 模型跑在 Apple Silicon 的 GPU 上(不是CPU!)
7.6 进阶:观察思考过程(Think Mode)
Qwen3 支持 /think 和 /no_think 指令来控制是否显示思考过程:
python
messages = [
{"role": "user", "content": "/think 用Python写一个快速排序,并解释每一行代码。"}
]
# 其余代码相同...
此时模型的回复会先输出思考链(在 <think>...</think> 标签内),再输出正式回答。这让你直观看到"模型是怎么一步步推导出答案的"。
7.7 进阶:理解 KV Cache(在你机器上量化的实操)
生成第 N 个 token 时,模型需要用到前面所有 token 的 Key 和 Value。为了避免重复计算,transformers 内部会自动维护一个 KV Cache。
你可以观察到它的增长:
python
# 在 generate 之后,查看 KV Cache 的占用
if hasattr(model, "cache_params"):
# 不同模型实现不同,这里示意
pass
# 更直观的方式:看显存占用
if torch.backends.mps.is_available():
# MPS 没有直接的显存查询API,可以通过生成时间感受
import time
# 第一次生成(cold start,无cache)
start = time.time()
outputs = model.generate(**inputs, max_new_tokens=100)
print(f"第一次生成(无cache): {time.time()-start:.2f}s")
# 第二次继续生成(warm,有cache)
start = time.time()
outputs = model.generate(**inputs, max_new_tokens=100)
print(f"第二次生成(有cache): {time.time()-start:.2f}s")
# 你会发现第二次快得多,因为 KV 已缓存
7.8 清理与总结
bash
# 不用时,模型文件可以保留(以后还能用),也可以删除
rm -rf ~/models/Qwen3-4B
这一节你实际做了什么:
| 步骤 | 你做的 | 学到的 |
|---|---|---|
| git clone | 下载原始权重文件 | 模型 = 配置文件 + 权重 + tokenizer |
| from_pretrained | 加载模型到内存/GPU | 网络结构由 config.json 自动构建 |
| tokenizer.encode | 把文字变成数字 | 模型只认数字,不认文字 |
| model.generate | 执行前向传播 | 自回归:一次生成一个token,拼回输入再生成下一个 |
| 观察输出 | 看到 device=mps:0 | Apple Silicon 用 MPS 后端跑模型 |
🎉 Part 1完成! 你已经理解了LLM的原理,并且能本地跑模型了。⏭️ 可以进入Part 2了
下一篇文章见:AI系列文章导航目录-持续更新中