先声明一下本文说的量化不是金融圈那个量化交易而是把模型权重从 FP16 压缩到 INT4/FP8 的模型量化。上周有个朋友问我手里只有一块 RTX 409024GB 显存能不能把 70B 模型跑起来我说能但你得先把“FP16 一路跑到底”这个念头扔了。70B 模型的 FP16 权重就有大概 140GB一张卡连门都进不去。但如果你把它量化到 4bit权重会缩到 40GB 左右再加上 KV Cache 压缩、推理引擎调度、显存精算这几层操作24GB 显卡确实能把它推进去只是要在质量和速度上做一些取舍。这篇文章把我实际部署 70B 级模型Llama-3-70B、Qwen2.5-72B 这类的经验拆成五层技术栈从“为什么必须量化”讲到“量化之后怎么调得又快又稳”最后给一份按显存分档的可行配置单踩过的坑也都写在里面了。1. 先算一笔显存账70B 上单卡为什么 FP16 连门都进不去1.1 权重、KV Cache、激活值决定显存命运的三角很多人以为“模型能不能跑”只取决于参数量其实不完全对。推理时显存被三样东西占据权重、KV Cache、激活值与临时缓冲区。权重最好理解参数量乘以每个参数占的字节数。以 70B 为例FP1670 × 10^9 × 2 bytes ≈ 140GBFP8约 70GBINT4约 35GB所以单卡跑 70BFP16 是绝对没戏的因为 80GB 的 H100 都只能勉强塞下 FP8而 INT4 才是消费级显卡唯一现实的方向。KV Cache 是很多人忽略的第二块显存黑洞。它是 transformer 解码时必须缓存的历史 key 和 value大小跟模型层数、KV 头数、序列长度、缓存位宽直接相关。我拿 Qwen2.5-72B 的实际结构算给你看它约 80 层hidden size 是 8192使用 GQA 且 KV 头数为 8每个头维度 128。那么在 8K 上下文、FP16 缓存下KV Cache 大小大约是2K 和 V 两组× 8192序列长度× 80层数× 8KV 头数× 128头维度× 2字节 ≈ 2.7GB听着不大对吧如果这个模型没有 GQA也就是 80 个 KV 头MHA 结构其他条件不变KV Cache 会膨胀到 27GB 左右。这就是为什么我在选模型时会优先看有没有 GQA以及 KV 头数是多少——它直接决定了长上下文场景下显存会不会瞬间失控。第三块是激活值和中间缓冲区这部分跟 batch size、序列长度强相关。单条请求下通常几个 GB但一旦开了大批并发激活值可以轻松吃满剩余显存。1.2 “量化很伤模型”是个被高估的恐惧“70B 量化到 4bit模型不就废了”这是我被问得最多的一句话。实际测试下来70B 这个体量的模型量化容忍度相当高INT4 权重量化在多数任务上的损失远小于从 7B 升到 13B 带来的收益。模型越大冗余度越高量化的相对损失越小。这也是为什么社区里 Q4_K_M 的 70B 模型成为了“最甜点”的选择——显存省一半质量只掉一两个点。真正要小心的反而是那些“更小但更激进”的量化档位比如 Q2_K 甚至 IQ2_XXS。这类档位权重只有约 24~29GB24GB 卡勉强能塞进去但生成质量已经开始肉眼可见地下降。我的原则是能用 Q4 就不用 Q3能用 Q3 就别碰 Q2除非你只是在做技术验证。2. 前两层技术栈量化格式与推理引擎必须绑定选型2.1 主流量化方案横评GGUF、GPTQ、AWQ、FP8、EXL2量化格式从来不是孤立存在的它和推理引擎强绑定。同一个权重GGUF 版本只能用 llama.cpp/llama-server 跑GPTQ 和 AWQ 则由 vLLM、ExLlamaV2 等引擎支持。选错组合后面全白搭。先看 GGUF。它由 llama.cpp 生态定义支持分片、mmap 加载、CPU/GPU 混合 offload。它内部使用 K-quant 方案会对模型里的关键张量比如 attention 的各个投影矩阵保留更高精度其他矩阵用更低 bit属于“花小钱办大事”的策略。文件后缀常见的 Q4_K_M、Q5_K_M、Q6_K 等就是不同量化档位M 代表 medium 混合精度。GPTQ 是较早的 GPU 推理量化方案基于二阶近似做逐层误差补偿需要一小部分校准数据比如 wikitext。量化后的模型跑起来快但对显存略挑剔比较适合交给 vLLM 做服务端部署。AWQ 的思路跟 GPTQ 不同它观察激活值分布找出对精度影响最大的约 1%“显著权重”通过缩放因子保护这些权重而不是反过来去补偿损失。优点是量化速度快不需要反向传播而且掉点通常比同 bit 的 GPTQ 更小。现在 vLLM 对 AWQ 的支持非常成熟我生产环境里默认就是 AWQ。FP8 是另一条路。它本质上是半精度向更低精度的自然延伸且 H100 这类新硬件的张量核心原生支持 FP8 计算速度优势明显。但消费级显卡4090、3090、A6000 等没有 FP8 加速单元即便你加载了 FP8 权重也可能只是省了显存计算层面并没有收益所以它更适合 80GB 显存的 A100/H100 用户。EXL2 是 ExLlamaV2 引擎的自定义格式可以在同一模型内部按层分配不同的 bit 数类似 GGUF 的混合精度思路但完全面向 GPU 推理优化单序列生成速度极快。适合“自己一个人跟模型对话”的场景不太适合高并发服务。2.2 推理引擎对照llama.cpp、vLLM、ExLlamaV2、TensorRT-LLM引擎核心优势推荐场景支持的量化格式llama.cpp / llama-server部署最简单内存友好支持 CPU/GPU 混合单卡自用、实验、消费级显卡GGUF 全系列vLLMPagedAttention 显存管理连续批处理吞吐高单卡或多卡 API 服务、多用户并发AWQ、GPTQ、FP8、GGUF有限ExLlamaV2单序列低延迟算子极致优化个人交互、单路低延迟推理EXL2、GPTQTensorRT-LLMNVIDIA 官方优化性能上限最高企业生产、固定硬件环境INT4/INT8/FP8 等选引擎时不要只看跑分要看你的场景是“自己玩”还是“给别人提供服务”。自己玩llama.cpp 的省心程度无与伦比做服务vLLM 的吞吐优势是碾压级的。2.3 我的默认组合这几年折腾下来我的选型逻辑大致是24GB 消费卡自用llama.cpp GGUF Q4_K_M必要时 offload 部分层到内存。48GB 到 80GB 单卡做 API 服务vLLM AWQ 4bit或 FP8仅限支持 FP8 的卡。追求单路生成速度ExLlamaV2 EXL2 4bit。企业级、硬件固定、有运维人力TensorRT-LLM。这套规则同样适用于国产模型Qwen2.5-72B 的 GGUF/AWQ 量化版在社区里都有现成文件直接下载就能用如果你想自己转llama.cpp 仓库里有 convert_hf_to_gguf.py 脚本AutoAWQ 和 GPTQ 也有对应的转换工具注意校准数据集要跟模型领域匹配尽量别用默认 wikitext 糊弄过去。3. 第三层KV Cache 是隐藏在权重背后的显存黑洞3.1 算清楚 KV Cache 的账再决定上下文长度回到前面那个公式。KV Cache 大小由序列长度、层数、KV 头数、每个头的维度和缓存数据类型共同决定。我再写一个更通用的版本方便你自己代入KV Cache 字节数 2 × 序列长度 × 层数 × KV 头数 × 头维度 × 缓存位宽字节数拿 Llama-3-70B 举例80 层GQA 8 个 KV 头头维度 128。如果序列长度拉到 32KFP16 缓存2 × 32768 × 80 × 8 × 128 × 2 ≈ 10.7GB还没算权重就这 10.7GB 已经快吃掉半张 24GB 卡了。所以我把上下文长度看成显存预算的一部分而不是单纯的“模型能力”。3.2 三个压制 KV Cache 的手段KV 量化、PagedAttention、chunked prefill第一是 KV Cache 量化。llama.cpp 里通过-ctk和-ctv参数控制 key 和 value 的缓存类型可以设为q8_0甚至q4_0。vLLM 也支持通过--kv-cache-dtype fp8把缓存压到 FP8。实测下来KV Cache 用 FP8 对质量的影响通常很小但能省接近一半的缓存显存。对长上下文场景来说这个优化几乎是白送的。第二是 PagedAttention。这是 vLLM 的核心创新它把 KV Cache 切成固定大小的块按需分配避免显存碎片化带来的浪费。传统推理框架要预先为最大序列长度预留连续显存而 PagedAttention 只需要为实际用到的块付显存。这也是为什么同样是 70B 模型vLLM 能支撑的并发数远高于朴素实现。第三是 chunked prefill。它把很长的 prompt 切成小块逐步处理防止一次性 prefill 把显存和算力一起打满导致第一个 token 要等很久。vLLM 里有相关配置llama.cpp 在长 prompt 场景下也可以通过调整 batch 来间接实现类似效果。3.3 显存分配实操llama.cpp 和 vLLM 的参数怎么设llama.cpp 启动一个 70B Q4_K_M 模型我一般这么给参数llama-server \ -m ./qwen2.5-72b-q4_k_m.gguf \ -ngl 99 \ -c 8192 \ -ctk q8_0 \ -ctv q8_0 \ --parallel 1-ngl 99表示把能卸载到 GPU 的层全部放 GPU-ctk q8_0 -ctv q8_0把 KV Cache 压到 8bit。如果你的显存还是不够优先砍-c上下文长度而不是降量化档位——短上下文顶多影响长文本能力低量化档位影响的是每一次回答的质量。vLLM 启动 70B AWQ 模型的典型配置python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-72B-AWQ \ --quantization awq \ --gpu-memory-utilization 0.9 \ --max-model-len 8192 \ --kv-cache-dtype fp8gpu-memory-utilization 0.9表示允许 vLLM 使用 90% 的显存剩下的留给 CUDA 上下文和临时缓冲。这个值不要设成 0.99否则系统容易在极端情况下 OOM。4. 第四层并发调度优化从“能跑”到“跑得好”4.1 连续批处理才是吞吐的关键模型加载进去只是第一步。如果你是做服务接下来要解决的是“我一个人用很快两个人用就卡十个人用直接超时。”这里的关键技术是 continuous batching连续批处理。传统批处理是“凑够一批一起算完再等下一批”可一个批次里只要有人生成了长文本其他人就得陪跑而连续批处理允许请求动态加入和退出当前的计算批次。vLLM 对这一点的实现非常成熟实测在 70B AWQ 模型上8 个并发用户同时请求每个请求的排队时间比朴素批处理下降了 5 倍以上。4.2 vLLM 单卡场景下的关键参数在 48GB 或 80GB 的单卡上跑 vLLM我会重点调这几个参数--max-num-seqs最大并发序列数。设太小浪费算力设太大容易 OOM。48GB 卡上我一般设 8~1680GB 卡上设 16~32。--max-num-batched-tokens单次前向最多处理的 token 数。这个参数决定计算吞吐和显存占用的平衡点默认值通常是 2048 或 4096显存紧张时适当调小。--max-model-len限制最大序列长度防止某个用户把上下文拉满导致整卡显存被 KV Cache 独占。--enable-prefix-caching这个我强烈建议打开。多轮对话里用户消息的前几轮往往是重复的开启前缀缓存后相同的前缀 token 可以直接复用 KV Cache省掉重复计算。4.3 llama.cpp 的并发能力llama.cpp 的 server 端也支持多用户并发但它的强项从来不是高吞吐而是低门槛。用--parallel参数可以指定并行序列数比如--parallel 2表示同时处理两个请求。如果对并发要求高我通常建议直接上 vLLM而不是在 llama.cpp 上硬挤因为 vLLM 的调度器在抢占、排队、超时等机制上要完善得多。5. 第五层精度回落与质量兜底不能只盯着显存5.1 量化之后怎么验收“能不能用”很多人量化完模型只测一句“你好”觉得能回答就是成功。我自己的验收流程至少包含三层。第一层是 perplexity 对比。用同一个测试集分别跑 FP16 和量化模型的困惑度如果上升幅度在 5% 以内说明量化基本可靠。第二层是跑几个常用基准比如 MMLU、GSM8K用 lm-evaluation-harness 一次性跑完对比量化前后的分数差。第三层也是最实用的一层把你自己业务里的真实问题准备 50~100 条量化前后各跑一遍人工对比回答质量。三层都通过我才会把模型放上生产。5.2 量化敏感层与混合精度的思路不是所有层对量化都一视同仁。attention 里的 query/key/value 投影矩阵通常比 feed-forward 矩阵更敏感embedding 层和最后的 lm_head 对精度变化也很敏感。GGUF 的 K-quant 之所以好用是因为它已经替你做了混合精度策略关键张量保持较高 bit非关键张量压得更狠。AWQ 的“显著权重保护”走的也是类似路线。如果你发现某个模型在特定任务上量化后掉点明显不要立刻换更贵的档位先检查是不是量化方案选型的问题。有时候从 GPTQ 换成 AWQ同一个 bit 数下质量就能回来不少。5.3 我的验收 SOP我的固定流程很简单固定 20 条 prompt量化前后各跑一遍记录输出文本、首 token 延迟、生成速度和显存占用。跑一遍 MMLU 或对应垂直领域测试集记录分数差。如果掉点在可接受范围继续看速度如果速度也能接受再上并发压测。压测时重点观察 OOM 次数和排队延迟的 p95 分位数。这套流程走完基本就能判断这个量化档位在你的场景下到底可不可用。6. 实测配置单从 24GB 到 80GB 显存能怎么跑 70B6.1 三档显存配置参考显存推荐组合可用的上下文长度典型用途24GB4090/3090llama.cpp GGUF Q4_K_M部分层 offload 到内存4K 以内技术验证、个人实验48GBA6000/L40SvLLM AWQ 4bit或 llama.cpp 全 GPU 加载8K~16K小型服务、团队内部工具80GBA100/H100vLLM AWQ/FP8开启 prefix caching16K~32K生产 API、多人并发需要说清楚的是24GB 跑 70B 属于“极限操作”。Q4_K_M 权重大约有 40GB24GB 卡就算开满-ngl 99也还有十几 GB 的层要放在内存里走 CPU 计算。每生成一个 tokenGPU 和 CPU 之间都要来回传递数据速度会明显下降。我的建议是24GB 卡如果只是验证“能不能跑”可以试一次但真正干活至少准备 48GB 显存。6.2 我踩过的三个典型坑第一个坑是 PCIe 带宽瓶颈。当模型部分层 offload 到内存时每生成一个 token 都要经过 PCIe 传输中间结果。PCIe 4.0 x16 的峰值带宽约 32GB/s看起来不小但大模型每层传输的数据量也是 GB 级别的算下来每 token 延迟会从几十毫秒暴涨到几百毫秒。解决办法只有一个尽量把整个模型塞进显存offload 是最后手段。第二个坑是 vLLM 的 KV Cache 预估失败导致 OOM。我刚开始用 vLLM 时gpu-memory-utilization设成 0.95结果并发一上来直接 OOM。后来我把这个值降到 0.85~0.9并把max-model-len显式写死才稳定下来。记住vLLM 不是“显存越多越好”它要为 KV Cache 块预留连续空间太高反而容易在极端情况下崩。第三个坑是 llama.cpp 在 Windows 下的 offload 选择。Windows 上 llama.cpp 的 CUDA 构建有时无法正确识别 GPU导致-ngl 99实际效果跟-ngl 0一样模型全跑在 CPU 上。遇到这种情况先去看启动日志里有没有 “offloaded 80/80 layers to GPU” 字样如果没看到就检查是不是用了纯 CPU 版构建或者改用 Vulkan 后端。6.3 不要盲目追求最低比特最后说点个人体会。很多朋友一上来就想用 Q2_K 把 70B 塞进 24GB 卡结果生成质量惨不忍睹然后得出结论“量化不行”。其实不是量化不行是量化档位选错了。我的建议永远是先用 Q4_K_M 这一类“标准甜点档位”跑通再根据实际需求逐步尝试更低的档位过程中用第 5 节那套 SOP 持续对比质量。单卡跑 70B 的本质是质量、速度、成本三者之间的显式权衡而不是某一个参数的极致优化。把五层技术栈每层都调到“合适”你就离稳定跑起来不远了。