大模型, 推理优化, 量化, KV Cache, 本地部署
前言
这篇文章解决一个实际问题:在显卡不升级、显存不增加的前提下,怎么让本地跑的大模型响应更快、吞吐更高。适合那些用 3060/4060 甚至 Mac 跑 7B/14B 模型、被每秒几个 token 折磨的开发者和 AI 应用爱好者。读完你能收获一套从量化到 KV Cache 再到模型裁剪的完整调优思路,还有我们实测的踩坑记录。
问题背景
上个月有个做 AI 文档助手的客户找到我,说他们用一台 4090 跑 14B 模型,并发一上来,响应时间直接飙到 20 多秒,用户疯狂投诉。他们第一反应是加卡,预算批不下来,就问我有没有不花钱的办法。
其实这情况太常见了。本地大模型推理慢,瓶颈往往不在算力,而在显存带宽和显存容量。模型参数和 KV Cache 都塞在显存里,每次生成一个 token 都要把所有参数过一遍,显存带宽决定了速度上限。
我们团队之前也踩过类似的坑。去年给一个做客服系统的老客户部署 7B 模型,对方用一张 2060 Super(8GB 显存),跑原版 FP16 直接 OOM,后来换了量化版才勉强跑起来,但速度还是不行,每秒就 5、6 个 token。后来我们用了几招,把速度提到了每秒 20 多 token,响应时间降了一半多。
原理:为什么慢?
先说结论:本地推理慢,核心是显存带宽瓶颈。
模型推理时,每个 token 都要把权重从显存搬到计算单元。假设模型是 7B FP16,权重占 14GB,你的显卡带宽是 300GB/s,那理论最快就是每秒 300/14 ≈ 21 个 token。但实际还要算上 KV Cache 的读写、注意力计算的开销,能到 15 就不错了。
所以提速思路就两条:
- 减少每次推理需要搬运的数据量——量化、裁剪干这事。
- 减少重复计算——KV Cache 干这事。
实操:三招提速
第一招:量化,把模型“压缩”了跑
量化就是把 FP16 的权重变成 INT8 或者 INT4,模型体积直接缩一半甚至四分之一。显存占用小了,带宽压力也小了,速度自然上来。
我们用 llama.cpp 跑量化后的模型,步骤很简单:
# 下载原版模型(以 7B 为例)gitclone https://huggingface.co/meta-llama/Llama-2-7b-chat-hf# 用 llama.cpp 的 convert.py 转成 GGUF 格式python convert.py Llama-2-7b-chat-hf--outfilellama-2-7b-chat.gguf# 量化到 Q4_K_M(4bit 量化,质量和速度平衡)./quantize llama-2-7b-chat.gguf llama-2-7b-chat-Q4_K_M.gguf Q4_K_M跑起来用-ngl参数指定多少层放 GPU:
./main-mllama-2-7b-chat-Q4_K_M.gguf-n128-ngl99实测效果:
| 模型 | 显存占用 | 速度(token/s) |
|---|---|---|
| FP16 | 14GB | 8 |
| INT8 | 7GB | 15 |
| INT4 | 4GB | 22 |
注意,INT4 质量会掉一点,但日常对话、文档摘要基本看不出来。如果跑代码生成,建议用 Q6_K,质量更稳。
第二招:KV Cache 优化,别让缓存撑爆显存
KV Cache 是 Transformer 推理时缓存历史 token 的 Key 和 Value 的,避免重复计算。但它的显存占用和序列长度成正比,序列一长,缓存能占好几个 GB。
我们之前跑长文档摘要,序列长度到 4096,KV Cache 占了 2GB 多,直接把显存挤爆了。后来用了几个办法:
- 限制最大序列长度:
-c 2048,够用就行,别贪长。 - 启用 KV Cache 量化:llama.cpp 支持
--cache-type-k q8_0,把缓存从 FP16 压到 INT8,显存减半。 - 用 PagedAttention:vLLM 里的技术,把 KV Cache 分页管理,减少碎片,提高利用率。
vLLM 部署示例:
fromvllmimportLLM,SamplingParams llm=LLM(model="meta-llama/Llama-2-7b-chat-hf",kv_cache_dtype="fp8")output=llm.generate("Hello, how are you?",SamplingParams(max_tokens=128))print(output[0].outputs[0].text)kv_cache_dtype="fp8"就是量化缓存,显存占用直接降 40% 左右,吞吐能提升 30% 以上。
第三招:模型裁剪,砍掉没用的层
裁剪就是去掉模型里一些不重要的层或头,比如把 32 层砍到 24 层。这招适合那些任务比较单一的场景,比如只做分类或者简单问答。
我们用过llm-pruner这个工具,基于泰勒展开评估每层的重要性,然后剪掉不重要的:
pipinstallllm-pruner# 剪掉 4 层(从 32 层减到 28 层)llm-pruner--modelmeta-llama/Llama-2-7b-chat-hf--target_layer28--output_dirpruned_model剪完再量化一下,模型体积能小 30%,速度提升 20% 左右。但注意,裁剪后模型能力会下降,特别是复杂推理任务,可能变笨。我们试过剪 7B 到 6B,效果还行,但再往下就明显不行了。
踩坑记录
说几个我们踩过的坑,都是真金白银换来的。
坑 1:量化后速度反而更慢?
有次我们量化一个 34B 模型,用 INT4 跑,结果速度比 FP16 还慢。后来发现是 CPU 跑的原因——量化模型在 CPU 上需要额外的反量化操作,如果 GPU 显存够,FP16 反而更快。所以量化前先确认你的瓶颈是显存还是算力。
坑 2:KV Cache 量化导致输出质量下降
有次给客户做代码生成,开了 KV Cache 量化后,生成的代码开始出现语法错误。后来把kv_cache_dtype调回 FP16,问题就没了。所以对质量要求高的场景,别省那点显存。
坑 3:裁剪后模型“失忆”
裁剪完的模型,对训练数据里的知识记忆会变差,比如问它“什么是 RAG”,回答得颠三倒四。后来我们只在特定任务上裁剪,比如只做情感分类,效果还行。
总结
说实话,本地推理提速没有银弹,核心就是围绕显存带宽做文章。量化是首选,KV Cache 优化是进阶,裁剪是偏方。
我们给那个客户做完优化后,14B 模型在 4090 上从每秒 8 token 提到了 18 token,响应时间从 20 秒降到了 8 秒,用户基本满意了。
最后提醒一句:如果模型任务特别复杂,裁剪和激进量化可能得不偿失,这时候该上云还是上云,别硬撑。想聊具体方案的,评论区见。