一次推理是两幕戏 —— 先读题,再逐字写
同一个问题,提示词贴了 3 万字就等得心焦,可一旦第一个字蹦出来,后面就哗哗地流。这不是错觉,也不玄学:一次推理其实是两幕戏,两幕的瓶颈根本不是同一种资源。看懂这一章,后面量化的「为什么压完反而快」才有因果落点。
用后厨打比方:用户的问题是一张订单。Prefill 是读题审菜——把订单上所有要求一次性看完、备好料;Decode 是颠勺出菜——一道菜一道菜地往外端,端一道才能准备下一道。
两个时间指标,各归各的幕
TTFT(Time To First Token,首 token 延迟)由第一幕 Prefill 决定——题越长,读题越久,等你感觉就是「按了回车半天没动静」。每 token 间隔(其倒数即 decode 速度 tokens/s)由第二幕决定——只跟「权重要读多少字节」和「显存带宽多粗」有关,跟你 prompt 有多长基本无关。下面的直觉表由本页脚本按共享公式(14B 量化到 4-bit、RTX 4090)实时算出:
| 提示词长度 | TTFT(理论量级) | 之后的 decode 速度 | 体感 |
|---|
深挖 · 为什么两幕的瓶颈不同?
并行度是根。Prefill 一次有几百上千个 token 同时进矩阵乘法,几千个 GPU 核心都有活干,此时算力(FLOPS)是稀缺品;Decode 一步只有 1 个 token,算它只需要极小的算力,但注意力要回看全部历史 KV、前馈要读全部权重——时间都花在「把数据从显存搬过来」的路上。这就是 roofline 模型里 compute-bound 与 memory-bound 的分界,也是 FlashAttention、Tensor Core 这些优化主要惠及 prefill、而 decode 更吃带宽的原因。出处:vLLM 论文 §2(arXiv:2309.06180)对此有标准论述。章内小自测:为什么把 prompt 缩短能明显加快「第一个字」,却几乎不影响「后面每个字」的节奏?
量化 —— 把精装菜谱压成口袋本
14B 模型 BF16 要 ≈29.6GB 显存(按 config 的 14.8B 参数 × 2 字节,共享公式库口径),一张 4090 装不下;压到 4-bit 只剩约四分之一,还顺手变快了。天底下没有免费的午餐——效果掉了多少?掉的速度值不值?这一章给你一套能拍板的判断框架。
精度谱系:质量、显存、速度的取舍三角
把参数从「精装版」抄成「缩印口袋本」,字变小、本子变薄,但极端情况下会抄错字。每参数字节数来自共享公式库 GPU_CALC.PRECISIONS:
| 精度 | 字节/参数 | 14B 权重 | 一句话画像 |
|---|---|---|---|
| FP32 | 4 B | — | 训练主权重口径;推理几乎不用,纯浪费 |
| BF16 / FP16 | 2 B | — | 开源模型发布的主流精度;质量基准线 |
| FP8 / INT8 | 1 B | — | 数据中心推理新贵(H 系卡原生支持),几乎无损 |
| INT4 / NF4 | 0.5 B | — | 本地部署与 QLoRA 的主力档;质量损失通常很小(经验值) |
4-bit 也分门派——都是「训练后量化(PTQ)」,压法不同:
- GGUF(K-quants,如 Q4_K_M):llama.cpp 生态通用格式,按块混合位宽,Q4_K_M 实际平均约 4.8 bit/参数,是本地部署的事实标准。
- GPTQ:逐层最小化重构误差的校准式量化,GPU 服务端老牌选择(Frantar et al., 2022)。
- AWQ:激活感知——只精确保护对输出影响大的少数关键权重,其余狠压(Lin et al., 2023),vLLM 服务端常见。
- bitsandbytes NF4:QLoRA 的存放格式(信息论上 4-bit 的优分布),c 页微调时再细讲。
跑起来 —— Ollama / llama.cpp / vLLM / SGLang 怎么选
教程一会儿让你 ollama run,一会儿让你装 vLLM,它们是同类东西吗?不是——它们处在不同楼层:底下是引擎,中间是开箱即用的封装,上面是生产级服务框架。选错楼层不会报错,但会让你在错误的战场上挣扎。
工具定位分层(口径核对 2026-09)
| 层级 | 工具 | 定位一句话 |
|---|---|---|
| 引擎层 | llama.cpp | C/C++ 推理引擎,GGUF 格式发源地,CPU/GPU 混合,Ollama 与 LM Studio 的底层后端(查证 2026-09,官方仓库口径) |
| 开箱即用层 | Ollama / LM Studio | Ollama = 一条命令的 CLI;LM Studio = 给非命令行同事的 GUI。个人本地首选,但服务并发能力弱 |
| 生产服务层 | vLLM | 在线服务引擎主力:continuous batching + PagedAttention(章 4 主角),单卡/多卡生产部署的事实标准 |
| 生产服务层 | SGLang | 与 vLLM 同层竞争:RadixAttention 前缀缓存自动复用(基准命中 50~99%),结构化输出性能突出(查证 2026-09,SGLang 论文 arXiv:2312.07104 及官方仓库) |
| 极限优化层 | TensorRT-LLM | NVIDIA 深度优化,追极限吞吐再用;代价是构建链路重、绑 NVIDIA 生态(查证 2026-09,官方 README 口径) |
硬件路线同样分层:Mac(统一内存当显存使,14B 4-bit 装得下,带宽 546GB/s 量级 → 理论 decode ≈74 tok/s、实际打 5~7 折,安静省电,但服务生态弱);NVIDIA 消费卡(生态全、快,24GB 是 14B 的甜点);云 GPU(免持有成本、按时计费,个人长年开机反而贵)。带宽/显存数字均取自共享公式库 GPU_CALC.GPUS。
① 任务
② 硬件
③ 推荐工具
起步命令
贯穿案例第二幕落地:把 14B 变成你的代码助手
现在主角登场。在自己的机器上把它跑起来,全程三条命令:
# 1. 拉模型并对话(默认即 Q4_K_M 量化,下载约 9.0 GB,查证 2026-09)
ollama run qwen2.5:14b # 想要代码特化:ollama run qwen2.5-coder:14b
# 2. 看它此刻的真实占用(权重 + KV + 运行时开销,一并列在 SIZE)
ollama ps # SIZE 约 10~12 GB、PROCESSOR 显示 100% GPU 即全程在卡上
# 3. 打开本地 API(默认 11434 端口,OpenAI 兼容接口)
ollama serveollama ps 看到的量级一致。自己用(并发 1)24GB 卡绰绰有余。至于快不快:理论 decode ≈136 tok/s(1008GB/s ÷ 7.4GB),打 5~7 折即 70~95;社区实测 4090 跑 14B Q4 约 80~110 tok/s(查证 2026-09,llama.cpp 社区基准)。回 A 页显存估算器亲手验一遍 →单人能用,为什么 8 个人就崩 —— 并发、批处理与显存排队
自己用行云流水,拉个群里 8 个人同时提问,全都转圈,nvidia-smi 一看 GPU 利用率还不高——机器没满载却集体卡死,这是新手服务化的第一课。三个原因叠加,一个比一个隐蔽。
原因一:并发 × 上下文,KV Cache 的乘法爆雷
A 页讲过:每 token 的 KV ≈ 0.19 MB(14B 的 GQA 配置,BF16)。单人 4K 上下文才 0.8 GB,无感;但 KV 是乘法——总 KV = 并发数 × 上下文 × 每 token 开销。下表由本页脚本调 GPU_CALC.kvTotalGB 实时计算,目标是「24GB 卡、14B 4-bit」的 KV 预算 ≈13.7 GB(可用显存 22.8 − 权重 7.4 − 开销 1.7):
| 场景 | 总 KV | 占 KV 预算 | 判定 |
|---|
「代码助手」恰恰是长上下文场景(贴一整个文件、repo map),8 个人 × 32K 就要 ≈51 GB KV——三张 4090 都填不满。崩的种子在 A 页就埋下了,这里只是爆雷。
原因二:逐个处理,GPU 大部分时间在等
朴素做法是排队:一个请求做完再做下一个。可 decode 阶段每步只算 1 个 token 却要读一遍全部权重——带宽被一个请求独占,算力闲得发慌。与其一人一份单独炒,不如大锅一起炒:把多个请求的 decode 步拼成一个批次,读一遍权重同时给 8 个人各算一个 token,几乎不花额外时间。
原因三:显存分配的长尾与 OOM
就算算力够,KV 显存的分配方式还能再坑你一次:传统做法给每个请求预留「最长序列」的连续显存,实际平均只用三四成,剩下的全部碎片化浪费——「明明还剩 3GB 却分配不出 2GB」。vLLM 的 PagedAttention 像操作系统管理内存分页一样把 KV 切成小块按需分配,把浪费压到近零(原论文口径:KV 显存浪费 <4%,对比原系统的实际利用率仅 20~38%;查证 2026-09,arXiv:2309.06180)。
max_num_seqs:同时处理的序列上限,默认 256——显存紧就调小;② gpu_memory_utilization:允许引擎吃掉的显存比例,默认 0.9;③ 队列与超时:排队的上限和等待超时,配合章 5 的限流一起用。聚合吞吐(tok/s)
平均 TTFT(s)
显存水位(% of 24GB)
| 逐个处理 | 连续批处理 |
|---|
深挖 · 模拟器做了哪些简化?
为了在浏览器里跑出干净的对比,本模拟做了取舍:① prefill 与 decode 各走各的流水线,未模拟 chunked prefill 与两阶段争抢算力;② 批内每请求吞吐取 min(单流速度, 聚合算力上限/批大小),未细化注意力随上下文的二次项;③ OOM 建模为「KV 达到预算即拒绝新请求」,真实 vLLM 会先排队、再抢占(preempt)重算,最终表现相近:都是显存墙在限流。定性结论(吞吐墙在显存准入、连续批碾压串行)与论文一致;要精确数字请以 vLLM/SGLang 实测为准。章内小自测:同样是 24GB 卡跑 14B 4-bit,为什么「8 人 × 4K」能活、「8 人 × 32K」必死?
限流与降级 —— 崩之前的工程动作
章 4 的结论有点反直觉:高明的服务不是不拒绝,而是拒绝得漂亮。与其让所有人一起卡死、让显存爆掉把整组进程带走,不如主动拦住一部分请求——被拒绝的用户重试就好,崩掉的进程会连没被请求的无辜者也一起拖下水。
限流三件套
- 并发上限:同时处理的请求数封顶(服务端
max_num_seqs+ 应用层信号量双重保险),超出直接快速失败。 - 排队 + 超时:峰量先排队缓冲,但队列要有长度上限和等待超时——「排 30 秒还没到就明确告诉他再试」,好过让人盯着转圈两分钟。
- 按用户配额:防止单个用户(或一个失控脚本)吃光全部容量;按 API key / 租户分桶计数。
降级路径:一条预演好的撤退路线
降级不是临时拍脑袋,而是提前写进代码的分支。按代价从小到大依次退:
| 触发 | 动作 | 用户感知 |
|---|---|---|
| 请求带超长上下文 | 引导缩短/摘要后再来,或服务端截断 | 几乎无感 |
| 高峰(水位 > 阈值) | 临时切更小的量化档 / 更小的模型 | 回答略变笨,但还在流式出字 |
| 本地集群濒临崩 | 兜底转发商业 API | 无感(成本变高,钱包有感) |
省算力的正道:前缀缓存复用
限流是「少花钱」,缓存是「少干活」。如果所有请求共享同一段很长的 system prompt(代码助手几乎都是这样),那这段前缀的 prefill 结果——KV Cache——本可以跨请求复用,不必每个请求都重读一遍题。vLLM 的 automatic prefix caching、SGLang 的 RadixAttention(用基数树管理前缀复用,基准命中率 50~99%,查证 2026-09)都是服务端实现;托管 API 的 prompt caching 则把同一机制反映到账单上(命中部分按一折左右计费)。
监控最小集(d 页细化)
限流和降级都要靠指标触发。最小四件套先记住名字:TTFT p95(体验崩没崩)、tokens/s(吞吐够不够)、显存水位(离墙多远)、错误率(是不是已经在拒绝)。阈值怎么定、告警发给谁、崩了怎么演练,留到 D 页 · 选型与运维 收官。
终极测验:推理与部署,你过关了吗?
5 题验收。答对 4 题以上,你就有资格去谈「给我们团队部署一个模型」了。
参考资料 · References
本页所有关键数字的出处。带「查证 2026-09」的条目为写作时逐一核对过的事实;硬件量级来自共享公式库 calc.js(a/b/c/d 四页唯一数据源)。
- vLLM / PagedAttention:Kwon et al., Efficient Memory Management for LLM Serving with PagedAttention, SOSP'23 — arxiv.org/abs/2309.06180(KV 浪费 <4%、对比系统利用率 20~38%)· 查证 2026-09
- Orca:Yu et al., A Distributed Serving System for Transformer-Based Generative Models, OSDI'22(迭代级/连续批调度出处)— usenix.org/conference/osdi22/presentation/yu · 查证 2026-09
- vLLM 官方文档 · Engine Arguments(
gpu_memory_utilization默认 0.9、max_num_seqs默认 256)— docs.vllm.ai/en/stable/configuration/engine_args · 查证 2026-09 - SGLang:Zheng et al., SGLang: Efficient Execution of Structured Language Model Programs(RadixAttention)— arxiv.org/abs/2312.07104 · 仓库 github.com/sgl-project/sglang(前缀缓存命中 50~99% 为论文及社区基准口径)· 查证 2026-09
- GPTQ:Frantar et al., 2022 — arxiv.org/abs/2210.17323;AWQ:Lin et al., 2023 — arxiv.org/abs/2306.00978 · 查证 2026-09
- llama.cpp(GGUF / K-quants)— github.com/ggml-org/llama.cpp;Ollama(
qwen2.5:14b默认 Q4_K_M、约 9.0 GB)— ollama.com/library/qwen2.5 · 查证 2026-09 - LM Studio(llama.cpp 后端的桌面 GUI)— lmstudio.ai;TensorRT-LLM(NVIDIA 极限优化引擎)— github.com/NVIDIA/TensorRT-LLM · 查证 2026-09
- 消费卡 14B Q4 实测量级(4090 约 80~110 tok/s)为 r/LocalLLaMA 等社区基准汇总,非官方数字 · 查证 2026-09
- 本仓库:llm-prompt-caching/a.html(前缀缓存 API 侧)· A 页(显存公式与估算器)· calc.js(四页共享公式)