
做这一行久了你会发现“让 70B 模型跑在单卡上”这句话往往是带着怀疑说出来的。70B 参数光是 FP16 权重就要占 140GB 显存一张 A100/H100 80GB 的卡连权重都放不下更别说 KV Cache 和临时激活值。可这几年的工程实践偏偏证明这条路是走得通的——核心就是把“放不下”变成“放得下”再把“放得下”优化成“跑得快”。这篇东西想聊的就是我在单卡上跑 70B 模型的完整思路以及背后那套可以复用的“五层技术栈”量化、KV Cache 优化、推理引擎调度、内存调度、应用层策略。无论你是想在自己 48GB 或 80GB 的卡上跑通一个 70B 服务还是想搞清楚量化后的模型为什么有的快有的慢这篇文章都能给你一条可落地的路径。先说清楚一件事单卡跑 70B 不是“能不能”的问题而是“怎么分层压”的问题。每一层都能省出几倍到十几倍的空间但省下来的代价、踩坑方式、适用场景全都不一样。我做了不少实测之后发现大多数翻车案例都不是某一层出了问题而是层与层之间的配合没想明白。1. 为什么 70B 模型默认跑不动先算清三笔账动手之前大脑里得有一张“显存资产负债表”。70B 模型推理时的显存开销至少来自三个地方模型权重、KV Cache、以及激活值和临时缓冲。很多人只盯着权重结果一跑长上下文就 OOM根本原因就是没把另外两笔账算进去。1.1 权重账精度直接决定下限模型参数总量是 70B每个参数在 FP16/BF16 下占 2 字节所以权重就是 70 × 10^9 × 2 ≈ 140GB。这就是默认无法单卡运行的根本原因。注意这里还没有加上 embedding、LM head、以及推理引擎自身的开销。如果降成 INT8每个参数占 1 字节权重变成约 70GB降到 INT4约 35GB。从 140GB 到 35GB省出 75% 的空间。这也是量化被当成第一层核心的原因它直接把“能不能装下”的门槛降下来。提示市面上 GPTQ-Int4 模型的实占空间通常略高于纯理论值因为还有分组缩放因子、Act Order 元数据和嵌入层开销。比如 70B 的 Int4 文件常见在 38-42GB 左右。1.2 KV Cache 账上下文越长越要命KV Cache 是自回归模型必须保留的历史 K/V 张量。它的显存开销公式是KV Cache 显存 2K 和 V × batch_size × seq_len × num_layers × num_kv_heads × head_dim × bytes_per_element以 Qwen2.5-72B 为例层数 80、KV heads 8、head_dim 128如果单请求、FP16 存储、上下文 4096那大约要 2 × 1 × 4096 × 80 × 8 × 128 × 2 ≈ 0.67GB。看起来不多但并发 16 个请求时直接变成 10.7GB。再把上下文拉到 8192又是翻倍。所以“单卡能不能跑”不只看权重还看你想让模型同时服务多少人、支持多长上下文。这也是为什么很多 70B 单卡部署方案会刻意压低--max-model-len和--max-num-seqs。1.3 带宽账显存放得下速度不一定快显存塞下了还有第二道坎HBM 带宽。自回归生成时每生成一个 token 几乎要把所有权重读一遍。A100 80GB 的 HBM 带宽约 2TB/s如果权重是 40GB理论上每 token 至少需要 40000MB / 2000MB/ms 20ms也就是大约 50 token/s 的上限。但实际还有 KV Cache、激活值和注意力计算通常只能跑到理论值的 60%-80%。这就是为什么 FP8 量化在支持它的硬件上很香它不仅能省显存还能减少显存读取量直接提升每秒 token 数。而 INT4 虽然省得更多但有些引擎在反量化时会有额外开销速度反而不一定比 INT8/FP8 快。2. 五层技术栈全景层与层之间是怎么配合的很多人把“单卡跑 70B”简单理解成“下个量化模型改个参数跑起来”。实际上一个稳定可用的部署方案至少涉及五层。2.1 五层划分逻辑第一层权重量化。把 FP16/BF16 的权重压成 INT4/INT8/FP8/GGUF 等紧凑格式。第二层KV Cache 与注意力优化。压缩推理时的动态显存消耗包括 KV 量化、PagedAttention、GQA 结构的利用。第三层推理引擎调度。选择 vLLM、TensorRT-LLM、llama.cpp 这类引擎开启连续批处理、投机解码等能力。第四层内存调度。单卡显存实在不够时用 swap、offload、CPU 内存扩展来兜底。第五层应用层策略。前缀缓存、请求合并、并发上限控制、端到端基准测试保证实际服务的吞吐和延迟。2.2 每一层分别解决什么问题权重层解决的是“静态显存”KV Cache 层解决的是“动态显存”引擎调度层解决的是“GPU 利用率”内存调度层解决的是“极限场景可用性”应用层解决的是“业务侧指标”。层与层是乘法关系权重省 4 倍、KV Cache 省 2 倍、并发调度再省 3 倍最终可能把单卡极限从 1 个请求提到几十个并发。我在实测中最大的感受是不要追求某一层做到极端。比如为了省显存把 KV Cache 压成 INT8结果精度下降为了吞吐把并发拉满结果显存耗尽触发 swap速度反而陡降。五层之间要有一个系统级调优过程而不是各层单独调到最大。3. 第一层权重量化——把 140GB 压到 40GB这一层是整个方案的基石。量化听起来只是“把 float 变成 int”但实际有非常多的分支各有各的适用场景。3.1 主流量化方案对比我自己常用的几种量化方案按适用场景列一下方案格式/工具权重占用70B 级精度表现适合场景FP8TensorRT-LLM / vLLM约 70GB几乎无损H100/Blackwell 等原生支持 FP8 的卡INT8 (W8A8)TensorRT-LLM / PyTorch约 70GB损失很小兼容性好老架构也能用GPTQ-Int4GPTQ / vLLM / ExLlama约 38-42GB中等长尾知识略损服务端高并发需要批量推理AWQ-Int4AWQ / vLLM / TensorRT-LLM约 38-42GB比 GPTQ 略好对输出质量敏感的服务场景GGUF (Q4_K_M)llama.cpp约 40-42GB中上K-quant 保护了关键通道本地部署、CPUGPU 混合、边缘设备GGUF (Q8_0)llama.cpp约 75GB接近无损有 80GB 大卡但想要更好质量3.2 量化原理为什么 INT4 能不塌方量化本质是把连续的 FP16 数值映射到离散的整数网格。关键技巧是分桶group-wise quantization把权重矩阵按块分组每组有独立的缩放因子和零点偏移。比如 GPTQ 常用 group_size128意思是每 128 个连续参数共享一组统计量。这样做的原因是模型权重虽然整体分布广但局部非常均匀。一个很直观的类比全城房价从 1 万到 20 万每平但每一个小区内部的房价差异并不大你只要针对每个小区给一个“地段系数”就能用很小的整数精准表达。3.3 实操一个 70B 模型的量化选型流程假设我拿到一个 70B 模型的原始权重目标是一张 48GB 的 L40S 或 80GB 的 A100。我的操作路径一般是先跑一段校准数据上的困惑度测试确认原始模型的精度基线。如果卡有 FP8 原生支持优先尝试 FP8否则直接看 INT4。在 INT4 里服务端并发场景用 AWQ 或 GPTQ本地交互场景用 GGUF Q4_K_M。量化完一定要跑至少 300 条问题对比原始模型的输出。不能只看 loss 曲线要看真实生成质量。注意AWQ 的“A”是 Activation-aware它量化时不是平均分配误差而是特意保护对激活值影响大的权重通道。实测在一些代码生成、数学推理任务上AWQ 的稳定性比 GPTQ 好一截。3.4 量化后最常见的坑量化省显存很爽但坑也不少。我最常遇到的是不可训练的 embedding 权重被忽略有些量化工具默认不对 embedding 做量化导致显存比预想高几 GB。Act Order 引起兼容性问题GPTQ 的 Act Order 在老版 vLLM 上可能不生效表现为推理变慢或显存异常。校准数据集过小有些人拿 128 条文本做校准结果模型在专业领域输出崩坏。建议校准集要覆盖目标任务类型至少 512 条批量 1、序列长度 2048 是个比较稳的起点。4. 第二层KV Cache 与注意力优化——把长上下文塞进显存权重压完之后你的显存账本还掉一大块但 KV Cache 这张“动态账单”才开始出现。很多人在 70B 单卡部署中遇到的 OOM不是权重放不下而是并发一上去 KV Cache 爆了。4.1 KV Cache 到底怎么算回到那条公式真正可调的变量是batch_size、seq_len、KV heads 和 bytes。70B 级别的现代模型大多使用 GQAGrouped Query AttentionKV heads 比 Query heads 小很多。比如 Qwen2.5-72B 的 KV heads 是 8而 Q heads 是 64K/V 显存直接少了 8 倍。选模型时一定要看有没有 GQA这比任何后续优化都省事。以 48GB 卡为例权重 40GB剩下 8GB 可用。如果单请求 4096 上下文KV Cache 约 0.7GB看起来还能冲一冲但并发到 8 个请求KV Cache 涨到 5.6GB加上推理引擎预留的 CUDA context 和激活值基本就是贴着显存跑。4.2 KV 量化INT8 与 FP8KV 量化是把 K 和 V 张量从 FP16 降到 INT8/FP8。显存直接砍半但注意力分数的计算会有微小误差。实测中对绝大多数任务来说 INT8 KV 量化损失很小但极端长上下文比如超过 16K时误差会累积需要观察。vLLM 里可以这样开 KV 量化python -m vllm.entrypoints.openai.api_server \ --model /models/70B-AWQ-Int4 \ --quantization awq \ --kv-cache-dtype fp8 \ --max-model-len 8192这里fp8只在支持 FP8 的硬件上有效。如果卡不支持可以选择int8或不开启。KV 量化的收益是直接的同样显存能塞的并发请求数翻倍。4.3 PagedAttention 和显存碎片KV Cache 在朴素实现里是预分配的一块连续显存不同请求的长度不一样很容易造成碎片。vLLM 的 PagedAttention 核心思路是把 KV Cache 切成固定大小的块按需分配像操作系统分页一样。这个机制在单卡 70B 场景下尤其关键本来显存就吃紧碎片化浪费是不可接受的。我在实际使用中会把显存利用率上限加到 0.92-0.95靠 PagedAttention 的按需分页来吸收临时波动。如果不开保守只能 0.85白白少了几 GB 可用空间。提示TensorRT-LLM 也有类似机制叫 KV Cache Manager。llama.cpp 则是通过内存池和--parallel参数控制。选引擎之前先确认它有没有做 KV Cache 动态管理否则四五十 GB 权重的模型单请求也许还行多并发一定崩。5. 第三层推理引擎调度——从串行到连续批处理单卡跑 70B 不只是“能出字”还得考虑效率。推理引擎选型决定了你到底是在用 5% 的 GPU 闲逛还是把 GPU 吃到 90% 以上。5.1 三大引擎怎么选我用下来最有代表性的是这三个引擎内核机制优势劣势vLLMPagedAttention Continuous Batching吞吐高、并发能力强、社区生态好、上手快长上下文下调度开销略大TensorRT-LLM编译器模式、内核融合、图优化单请求延迟低、部署性能极致、FP8 丝滑构建时间长、对模型结构支持有限、上手门槛高llama.cppGGUF 自定义调度CPU/GPU 混合、offload 灵活、内存占用极小高并发服务能力弱、更适合单机本地5.2 Continuous Batching为什么并发不拖累速度朴素推理引擎是典型的串行等一个请求的所有 token 生成完才处理下一个。这意味着每一次 forward pass 都只有一小部分 GPU 在干活显存却一直占着。Continuous Batching 把不同请求合并到同一个 forward pass 里每个迭代动态决定谁继续、谁结束、谁新进入。这个机制对单卡 70B 部署非常友好权重 40GB 的读取成本已经被固定了同一轮多批几个请求每 token 的边际成本几乎为零。理论吞吐可以提升 5-10 倍。启动 vLLM 时可以用--max-num-seqs控制一个迭代批处理的最大请求数。我在 A100 80GB 跑 AWQ-Int4 70B 时max-num-seqs设 16、max-model-len设 4096整体吞吐接近 170 token/s而单请求延迟只有 25 token/s 左右。这就是“牺牲单用户延迟换全系统吞吐”的典型场景。5.3 Speculative Decoding用“小模型带大模型”另一个实用的调度优化是投机解码。它的思路是先用一个很小的草稿模型快速生成 4-8 个 token再用 70B 大模型一次验证。如果草稿猜对了一次 forward 就能产生多个 token猜错了顶多回退一步计算。在 70B 量化模型上投机解码能达到 1.3-1.8 倍的加速具体看 draft model 贴近度。要注意草稿模型本身也会占显存。TinyLlama 大约 1GB在一个 48GB 的卡上还能接受。但显存极度吃紧时这点额外开销可能让你从“跑得起”变成“跑不起”要斟酌。6. 第四层内存调度——单卡显存不够时的最后一公里就算前面四层优化完还是可能遇到这种情况权重 40GB、KV Cache 预留 4GB但一张 40GB 的公开云 GPU 就是差那么 5GB。这种“差一口气”的时刻才是真正考验工程能力的时候。6.1 CPU Offload 与 MMAPllama.cpp 的思路是把模型切层一部分放 GPU一部分放 CPU通过-nglnumber of GPU layers控制。比如llama-cli -m /models/70B-Q4_K_M.gguf -ngl 60 -c 4096 -t 8如果总层数 80 层-ngl 60意味着 60 层在 GPU、20 层在 CPU。CPU 上的层速度慢但至少能跑。这里有个经验值对于 70B Q4 模型-ngl低于总层数的 50% 时生成速度会掉到 2-3 token/s 以下基本不可用至少要让 80% 的层在 GPU否则不如纯 CPU 推理。6.2 vLLM 的 Swap 与 CPU 内存扩展vLLM 支持把 KV Cache 的一部分换到 CPU 内存--swap-space单位是 GB。这在公共云 GPU 上是个“保命”功能python -m vllm.entrypoints.openai.api_server \ --model /models/70B-AWQ-Int4 \ --quantization awq \ --max-model-len 4096 \ --gpu-memory-utilization 0.94 \ --swap-space 16 \ --max-num-seqs 8--swap-space 16表示最多从 CPU 换入 16GB 的 KV Cache。当 GPU 上 Cache 满了新请求先走 CPU。代价是速度陡降因为 CPU 和 GPU 之间的 PCIe 带宽远小于 HBM。实测中一旦开始 swap生成速度可能从 20 token/s 掉到 3-4 token/s。我的建议是swap-space只是兜底不要让系统常态性 swap。判断标准看vllm日志里的swap_in/swap_out统计如果每百次迭代都有 swap就说明并发或上下文设高了该降max-num-seqs。6.3 什么时候真正需要 offload讲实话如果量化压完权重依然放不进显存说明这张卡本来就不该跑这个模型。offload 只适合“临时跑通、验证效果”的场景不适合生产服务。例如你在 24GB 的 RTX 4090 上想验证一个 70B Q4 模型的效果可以用 llama.cpp 跑 CPUGPU 混合模式但别指望吞吐。如果你要部署服务我还是建议物理显存至少 48GB最好 80GB。7. 第五层应用层优化——前缀缓存、并发控制与基准测试最后一层不直接碰模型却决定了整个服务能否真实使用。很多同学在上线前一天才去测并发结果发现一堆请求排队这就是没提前做“应用层压测”。7.1 Prefix Caching重复用户头的救星vLLM 的--enable-prefix-caching值得打开。它的原理是如果多个请求共享系统提示词、Few-shot 示例或相同的历史消息前面的 KV Cache 可以直接复用不用重新计算。开启后长系统提示词场景下的首 token 延迟能下降 30%-50%。这个优化对单卡部署尤为重要——单个 70B 模型的算力本来就紧张每省一次重复计算都是实打实的吞吐提升。7.2 并发和上下文参数的核心约束关系单卡上配置不是拍脑袋定的它遵循一条铁律权重视显存 KV Cache 激活值 引擎预留 ≤ 显存总容量 × gpu-memory-utilization其中 KV Cache 单请求 KV × max-num-seqs × 平均上下文长度。我在测试 70B 模型时通常这样做固定--max-model-len。短任务设 2048长文档场景设 8192。从低到高调整--max-num-seqs观察显存峰值和 OOM 是否出现。用压测脚本同时发 10-20 个请求记录 TTFT首 token 延迟和吞吐。建议记录一张表格不同max-num-seqs下的吞吐和延迟对比最后选一个“吞吐高但延迟可接受”的甜点值。当max-num-seqs从 1 涨到 8吞吐可能涨 5 倍从 8 涨到 16可能只有 20% 增益显存却快爆了。这时候就该停了。7.3 端到端压测命令可以用简单脚本做一轮压测不必上复杂工具。比如用hey或 Python 的aiohttp并发请求 OpenAI 兼容接口。import asyncio, aiohttp async def send_question(session, i): async with session.post( http://localhost:8000/v1/completions, json{model: 70B, prompt: 写一段 300 字的文章主题效率工具, max_tokens: 256} ) as resp: data await resp.json() return data async def main(): async with aiohttp.ClientSession() as session: tasks [send_question(session, i) for i in range(20)] results await asyncio.gather(*tasks) print(完成请求数:, len(results)) asyncio.run(main())一次跑完能得到一个大致的“平均每请求耗时”结合服务端日志中的 token/s 数据再做参数调整。7.4 常见问题排查速查表症状原因检查与解法启动时 CUDA OOM权重 预留显存超限压低gpu-memory-utilization到 0.85确认量化档位没选错跑一段后 OOM并发或上下文长度超过 KV 预算降max-num-seqs或降max-model-len开启 KV 量化速度突然掉到几 token/s触发了 swap 或 CPU offload查看日志 swap 统计减少并发关闭投机解码释放显存首 token 很慢没有开启 Prefix Caching加--enable-prefix-caching检查请求是否重复系统词生成质量明显下降量化过狠或校准集不匹配尝试 AWQ 或 Q8 档重跑校准集对比原始模型输出INT4 显示占用比文件大反量化或中间缓冲导致检查引擎是否在加载时转成 FP16开启 weight-only 推理写在最后我的一点真实体会这套“五层技术栈”在我手里跑了小半年最深的感受是优化单卡 70B 推理本质上不是炫技而是做取舍。量化省下的每 1GB 显存都可能用精度或延迟换回来并发拉高的每一档吞吐都可能把显存推向 OOM 的边缘。真正稳定的配置往往不是某一层的极限而是所有层的“余量艺术”。最后分享一个我自己的习惯每次调整完参数我都把显存曲线、吞吐、首 token 延迟一起记录。跑 70B 模型就像是解一个多变量方程没有这一份记录下一次遇到问题你还是得从头猜。单卡 70B 这件事技术方案已经成熟到任何人按图索骥都能跑通剩下的就是在这些细节里慢慢磨出属于自己的最佳配置。