ByteFisher AI 工程化深度实战(六):本地模型量化与推理优化

前五篇我们一直在优化线上 API 调用和检索质量。但有一个现实问题悬而未决:ByteBot 完全依赖 DeepSeek 云端 API,如果上游故障或密钥过期,导览功能就只剩 BM25 兜底。

本篇讨论的是”降级路径 3”:用本地模型做兜底。博客早就有本地模型的基础——《Ollama + DeepSeek-Coder-V2 + Visual Studio Copilot 插件》那篇验证了本地 AI 编程助手。本篇把它从”本地跑起来”推进到”本地跑得快、跑得稳、能接进生产链路”。

一、为什么需要量化

1.1 显存是第一约束

一个 7B 参数的模型,fp16 权重就要约 14GB 显存,家用 8GB 显卡根本装不下。量化把权重从 fp16(16bit)压到 4bit 或 8bit,7B 模型只需 4~5GB。

精度 每参数字节 7B 模型权重 13B 模型权重 适合显存
fp16 2 14 GB 26 GB 24GB+
int8 / Q8 1 7 GB 13 GB 12GB+
int4 / Q4 0.5 4 GB 7 GB 8GB
int3 / Q3 0.375 3 GB 5 GB 6GB

1.2 量化不是”精度换体积”这么简单

现代量化(GGUF 的 GPTQ/AWQ 路线)不只是把浮点砍成整数,关键技术在三点:

  1. 按块缩放(Group Quantization):权重按小块(如 128 个一组)分别算缩放因子和零点的偏移,把信息损失控制在块内。
  2. 校准(Calibration):用一批代表数据(校准集)微调量化参数,让量化后的模型在”你实际会用到的数据分布”上损失最小。
  3. 混合精度:敏感层(通常是 attention 的 QKV 投影)保留更高精度。

对应用开发者来说,不需要自己写量化器——直接用 HuggingFace 上别人量化好的 GGUF 文件,或者用 llama.cpp / mlx 工具自己转。

二、量化等级实测对比

用一份统一的测试集(中文问答 + 代码生成 + 摘要),在同一个 7B 模型(以 Qwen2.5-7B-Instruct 为例)的不同量化等级下对比。以下数字为典型值,实际应在本机复测:

量化 文件大小 首 token 延迟(8GB) 输出速度 中文问答质量 适用
Q2_K ~2.9 GB 低 快 明显下降 不推荐
Q4_K_M ~4.4 GB 低 快 可接受 内存小、日常用
Q5_K_M ~5.1 GB 中 中 接近原始 质量优先
Q8_0 ~7.2 GB 高 慢 几乎无损 显存够用

工程结论:

  • Q4_K_M 是性价比甜点:质量损失在可接受范围内,显存要求低,速度最快。
  • Q5_K_M 是质量优先选择:如果显存允许(12GB),值得多花 1GB 换质量。
  • 不要用 Q2:语言质量崩塌,省的那点内存没有意义。
  • 注意:量化主要影响”复杂度任务”的表现。ByteBot 的导览型任务(推荐文章、简单问答)对量化不敏感,Q4 完全够用;但让它写代码或做复杂推理会明显变差——所以本地兜底只承担降级导览,不强求全能。

2.1 实测方法

1
2
3
4
5
6
# 用 llama-bench 量化前先测基线
ollama run qwen2.5:7b "用一句话解释什么是对象池"
# 换成 Q4 版再测同一问题,对比输出质量与速度

# 完整 bench
ollama show qwen2.5:7b-instruct-q4_K_M --modelfile

更科学的做法是接进第 05 篇的评测集,把量化模型跑一遍 RAGAS——Faithfulness 下降幅度就是量化的真实代价。

三、推理引擎对比:Ollama vs vLLM

3.1 定位差异

维度 Ollama vLLM
定位 本地一键部署,开发者友好 生产级推理服务器,面向高并发
底层 llama.cpp 自研 PagedAttention
并发 串行为主(支持并发但有限) 高并发 + 连续批处理
内存复用 KV cache 固定 PagedAttention 按页分配,碎片少
部署成本 极低,一条命令 较高,需 Python 环境 + GPU
OpenAI 兼容 API 原生支持 原生支持

3.2 并发与吞吐的真相

本地推理的吞吐瓶颈在 prefill(处理输入)与 decode(逐 token 生成)的争抢。vLLM 的核心创新 PagedAttention 把 KV cache 切成固定大小页,支持连续批处理(Continuous Batching)——新请求不用等当前批次结束就能插入。这让 vLLM 在并发场景下的吞吐远超 Ollama/llama.cpp。

对 ByteBot 的实测建议:

  • 单用户、低并发(博客场景):Ollama 足够。一次一个请求,Ollama 的优势(部署简单、内存占用可控)完胜。
  • 多用户并发(比如评论系统接 AI、多 Agent 并行):vLLM。第 10 篇的多 Agent 流水线如果本地化,必须上 vLLM。

一个量化对比表(7B 模型,8GB 显存):

引擎 并发 1 时吞吐 并发 8 时吞吐 退化幅度
Ollama (llama.cpp) 22 tok/s 9 tok/s -59%
vLLM 21 tok/s 38 tok/s(批处理) +81%

(示例值,需本机复测。核心结论:Ollama 单并发不输,多并发被 vLLM 反超。)

四、本地模型接入 ByteBot 的降级链路

目标架构:

1
2
3
4
5
6
POST /api/chat
→ 尝试 DeepSeek(云端主链路)
→ 失败/超时/限流
→ 降级到本地模型(Ollama/vLLM 的 /v1/chat/completions)
→ 仍失败
→ 返回 BM25 推荐文章(纯前端兜底,第 02 篇已实现)

api/chat.js 里的实现思路:

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
async function callWithFallback(outboundMessages, stream) {
const cloudOk = await tryCallCloud(outboundMessages, stream);
if (cloudOk) return cloudOk;

// 降级:本地模型(配置了 LOCAL_LLM_BASE_URL 时才启用)
if (process.env.LOCAL_LLM_BASE_URL) {
const localOk = await tryCallLocal(outboundMessages, stream);
if (localOk) return localOk;
}
// 双层都失败,交给前端的 BM25 兜底
throw new Error('all_llm_unavailable');
}

async function tryCallLocal(messages, stream) {
// 本地模型 base url 兼容 OpenAI 格式,替换 endpoint + 密钥即可
const response = await fetch(`${process.env.LOCAL_LLM_BASE_URL}/v1/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: process.env.LOCAL_LLM_MODEL || 'qwen2.5:7b',
messages,
stream
}),
signal: AbortSignal.timeout(LOCAL_LLM_TIMEOUT) // 本地慢,超时给 60s
});
return response;
}

三个工程细节:

  1. 本地模型超时给足:本地 CPU/小显卡推理慢,30 秒可能不够,单独给 60 秒。
  2. 降级要打标:回答里或响应头加 x-llm-source: local,第 11 篇观测能统计”降级发生频率”,反哺到评估和成本。
  3. 本地兜底只跑导览任务:降级时 system prompt 应切到”最简导览模式”(去掉复杂指令),因为量化小模型吃不下长指令。

五、落地清单与小结

优先级 改造 收益
P0 本机量化对比实验(Q4 vs Q5) 明确本地模型质量边界
P0 Ollama 部署 + OpenAI 兼容 API 本地链路就绪
P1 降级链路接入 chat.js + 打标 DeepSeek 挂时仍有 AI 回答
P2 vLLM 部署(并发场景才需要) 多 Agent 本地化时用
P2 量化模型跑 RAGAS 评测 量化代价有数据
P3 模型路由:重任务仍走云端 质量与成本平衡

核心结论:

  • 量化是本地推理的前提,Q4_K_M 是性价比甜点,Q5 质量优先;Q2 不要用。
  • 量化对小模型的语言能力影响主要体现在复杂任务,导览型降级任务影响可控。
  • 单并发用 Ollama,多并发用 vLLM;PagedAttention + 连续批处理让 vLLM 在并发场景吞吐反超。
  • 本地兜底要有明确的职责边界(只做导览),且必须打标可观测。
  • 降级链路分三层:云端 → 本地 → BM25,逐层兜底,可用性最大化。

下一篇进入编排层:LangGraph 源码剖析。我会深入 LangGraph 的状态图核心,逐行拆解节点执行、检查点持久化、流式执行原理,并演示一个”文章分析 Agent”工作流。


本系列持续更新中,每周一篇。

ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
分享: