
1. 百万级吞吐背后的真实命题第一次看到“1M tok/s”这个数字我的反应和大多数人一样先怀疑再好奇。大语言模型推理领域里单卡每秒几千 token 是常态上万已经算优化得不错百万级吞吐听起来像是把“每秒请求数”和“每秒 token 数”混为一谈了。但把 Nori LLM 这套思路拆开看之后我发现它并不是在吹牛而是在换一个维度回答问题——它追求的不是单个请求跑得多快而是整个系统在批量场景下单位时间能吐出多少 token。这个区别非常关键。你如果做过线上推理服务就会知道用户感知的是首 token 延迟和单请求生成速度而老板和账单感知的是吞吐和成本。这两件事经常是矛盾的为了压低单请求延迟你会用小 batch、频繁调度为了拉高吞吐你会把 batch 堆大、把 GPU 喂满。Nori LLM 这类方案的核心取向很明确它服务的是离线批处理、数据合成、大规模评测、知识库预生成这类场景在这些场景里延迟根本不重要重要的是“一晚上能跑完多少数据”。所以这篇内容我想聊的不是“怎么让模型变快”这种泛泛的话题而是围绕 Nori LLM 这个标题把百万 token 每秒这个目标拆成可理解、可复现的工程问题。适合谁看如果你正在做 LLM 推理部署、批量数据生成、RAG 知识库的离线索引构建或者单纯好奇“吞吐到底能压榨到什么程度”那接下来的内容应该对你有用。我会尽量把每一步的“为什么”讲清楚而不是甩一堆参数让你抄。先给一个整体判断达到百万 tok/s 这个量级靠的绝不是某一个神奇技巧而是连续批处理、PagedAttention 类显存管理、量化、张量并行、请求调度这几件事叠在一起再加上一个足够大的硬件池。单卡单模型想摸到百万基本不现实但一个 8 卡节点跑一个中等规模模型配合激进的批处理策略摸到几十万 tok/s 是够得着的多节点堆叠上百万也就顺理成章了。2. 吞吐量到底是怎么算出来的2.1 先搞清楚 tok/s 的三种口径很多人讨论吞吐时鸡同鸭讲是因为大家说的根本不是同一个指标。我把它分成三种口径你对照一下自己关心的是哪种。第一种是单请求生成速度也就是一个请求从开始生成到结束平均每秒产出多少 token。这个数字通常受限于显存带宽和模型大小7B 模型在消费级卡上大概 30 到 60 tok/sA100 上能到 100 多。第二种是系统总吞吐也就是所有并发请求加起来每秒总共产出多少 token。这个数字可以远高于单请求速度因为多个请求在并行计算。Nori LLM 说的 1M tok/s 显然是这个口径。第三种是有效吞吐扣掉被截断、被丢弃、被重试的 token 之后真正有用的产出。这个最容易被忽略但最影响实际成本。提示看到任何吞吐数字第一件事是问清楚是哪种口径以及 batch size 和序列长度是多少。脱离这两个前提的吞吐数字没有意义。2.2 一个可复算的估算模型假设你有一个 8 卡的节点每张卡显存 80GB跑一个 7B 的模型用 FP16 权重权重占用约 14GBKV Cache 占用剩下的空间。KV Cache 的大小可以用这个公式估算KV Cache 每 token 字节数 2 * num_layers * num_kv_heads * head_dim * dtype_bytes以 Llama 2 7B 为例32 层32 个 KV headhead_dim 128FP16 每元素 2 字节2 * 32 * 32 * 128 * 2 524288 字节 ≈ 0.5 MB / token也就是说每个 token 的 KV Cache 要占 0.5MB。如果每张卡留 60GB 给 KV Cache那单卡能缓存约 12 万个 token 的上下文。8 卡就是接近 100 万 token 的并发上下文容量。再看计算侧。7B 模型每生成一个 token 大约需要 2 * 7B 14 GFLOPs 的计算量前向传播的粗略估算。A100 的 FP16 算力约 312 TFLOPS理论峰值下每秒能算 312000 / 14 ≈ 22000 个 token。8 卡理论峰值约 17.6 万 tok/s。注意这是理论峰值实际能到 30% 到 50% 就不错了。那 1M tok/s 怎么来的要么模型更小比如 1B 到 3B要么用了 INT8/INT4 量化把算力需求降下来要么就是多节点堆叠。比如 4 个 8 卡节点每个节点跑到 25 万 tok/s加起来就是百万。这个拆解让目标变得可信也告诉你优化的着力点在哪。2.3 为什么 batch 越大吞吐越高但有上限GPU 是吞吐型设备它喜欢大矩阵乘法。batch size 从 1 涨到 32吞吐可能翻十几倍因为计算单元被填满了。但 batch 继续涨收益会递减直到撞上两个墙显存墙和调度墙。显存墙好理解KV Cache 装不下就得排队。调度墙更隐蔽当 batch 里不同请求的生成长度差异很大时短请求早就结束了长请求还在跑GPU 在等最慢的那个这就是所谓的“长尾拖累”。连续批处理continuous batching就是为解决这个问题生的它允许请求在生成过程中动态进出 batch而不是等一整批全部结束。3. 核心加速技术逐层拆解3.1 连续批处理吞吐提升的第一功臣传统静态批处理是这样的凑够 N 个请求一起送进模型等这 N 个全部生成完再收下一批。问题在于如果其中一个请求要生成 2000 token其他只要 50 token那 GPU 在大部分时间里都在为那一个请求空转。连续批处理也叫 iteration-level scheduling改成了按“步”调度。每一步只生成一个 token生成完立刻检查哪些请求结束了把结束的踢出去把排队的加进来。这样 GPU 的利用率能维持在很高水平。我用 vLLM 做过对比测试同样的硬件、同样的模型静态批处理在混合长度请求下吞吐只有连续批处理的 40% 左右。这个差距在长尾明显的场景里还会更大。实现连续批处理的关键是调度器和显存管理器的配合。调度器决定每一步哪些请求参与计算显存管理器负责给新进来的请求分配 KV Cache 空间。这两者如果耦合太紧调度就会变慢反而拖累吞吐。3.2 PagedAttention把显存碎片问题解决掉KV Cache 的传统分配方式是给每个请求预留一段连续显存按最大可能长度预留。这造成两个浪费一是内部碎片实际用了 500 token 但预留了 2048二是外部碎片显存里到处是小空洞装不下新请求。PagedAttention 借鉴了操作系统虚拟内存的分页思想把 KV Cache 切成固定大小的 block比如每块存 16 个 token然后通过页表把逻辑上连续的 KV 映射到物理上不连续的 block。这样显存利用率能从 60% 左右提到 90% 以上等于凭空多出 50% 的并发容量。这个技术对吞吐的贡献是间接但巨大的显存利用率上去了能同时跑的请求就多了batch 就能开大吞吐自然上去。而且它让“按需分配”成为可能短请求不会浪费长请求的预留空间。3.3 量化用精度换吞吐的取舍量化是另一个大杀器。FP16 换成 INT8权重和 KV Cache 都减半显存容量翻倍同时整数运算在某些硬件上更快。换成 INT4显存再减半但精度损失开始明显。我实测下来的经验是权重量化对吞吐帮助大KV Cache 量化对并发帮助大。权重 INT8 基本无损INT4 在 7B 以上模型上也能接受但在小模型上会明显掉点。KV Cache 量化到 INT8 通常没问题INT4 就要看任务做分类和抽取还行做长文生成容易崩。这里有个坑不是所有推理框架的量化实现都一样。有的框架量化后吞吐反而下降因为反量化开销吃掉了收益。选框架时一定要看它的量化 kernel 是不是融合进计算图了。3.4 张量并行与流水线并行多卡怎么分工单卡装不下或者跑不快就得上多卡。张量并行TP是把每一层的矩阵乘法切开分到多张卡上算卡间通信频繁但延迟低。流水线并行PP是把不同层分到不同卡上通信少但有流水线气泡。对于吞吐优先的场景TP 通常更合适因为它能让每张卡都满负荷计算。但 TP 的通信开销随卡数增长8 卡以上收益递减明显。实践中 7B 到 13B 模型用 2 到 4 卡 TP70B 用 8 卡 TP是比较常见的配置。注意TP 要求卡间有高速互联NVLink 和 PCIe 的差距在吞吐上能体现出来。如果你用 PCIe 做 8 卡 TP通信可能成为瓶颈这时候考虑 PP 或者混合并行。4. 从零搭一套高吞吐推理服务4.1 框架选型别只看 benchmark市面上主流的推理框架有 vLLM、TensorRT-LLM、SGLang、TGI 等。选哪个不能只看官方 benchmark因为那些数字往往是在最优条件下测的。我建议按这几个维度评估维度关注点我的经验吞吐上限连续批处理效率vLLM 和 SGLang 在通用场景领先延迟表现首 token 时间TensorRT-LLM 在 NVIDIA 卡上最优量化支持INT8/INT4/FP8TensorRT-LLM 最全vLLM 够用部署复杂度依赖、编译vLLM 最省心TensorRT-LLM 要编译生态兼容OpenAI API基本都支持差异在细节如果你追求极致吞吐且有 NVIDIA 卡TensorRT-LLM 值得投入时间编译优化。如果想要快速上线、迭代方便vLLM 是更稳的选择。SGLang 在结构化生成和前缀缓存上有独特优势适合 RAG 场景。4.2 关键参数配置实录以 vLLM 为例几个对吞吐影响最大的参数python -m vllm.entrypoints.openai.api_server \ --model /path/to/model \ --tensor-parallel-size 4 \ --max-model-len 8192 \ --gpu-memory-utilization 0.95 \ --max-num-seqs 256 \ --max-num-batched-tokens 8192 \ --enable-chunked-prefill \ --quantization awq逐个解释为什么这么设--gpu-memory-utilization 0.95是把 95% 显存交给 KV Cache留 5% 给临时张量。设太高会 OOM设太低浪费容量。0.9 到 0.95 是安全区间。--max-num-seqs 256是同时处理的最大请求数。这个值要结合显存算不是越大越好。太大反而因为调度开销和显存压力导致吞吐下降。--max-num-batched-tokens 8192控制单步计算的最大 token 数包括 prefill 和 decode。chunked prefill 开启后长 prompt 会被切成小块避免一个长请求阻塞整个 batch。--enable-chunked-prefill这个开关对混合负载场景提升明显它让 prefill 和 decode 能交错进行GPU 不会因为等一个长 prompt 而空转。4.3 压测方法与真实数据搭好服务后必须压测不能凭感觉。我用的是 vLLM 自带的 benchmark 脚本配合不同长度的输入输出组合。python benchmarks/benchmark_serving.py \ --backend vllm \ --model /path/to/model \ --dataset-name sharegpt \ --num-prompts 1000 \ --request-rate 50压测时要关注三个曲线吞吐随并发数的变化、延迟随并发数的变化、显存占用随并发数的变化。吞吐曲线通常先升后平找到那个拐点就是最优并发。延迟曲线在拐点之后会陡增说明开始排队了。我实测一个 7B 模型在 4 卡 A100 上输入 512、输出 256 的混合负载连续批处理下能跑到约 8 万 tok/s。要上百万要么模型更小要么卡更多要么量化更激进。这个数字供你参考实际会因模型、数据、硬件而异。5. 踩过的坑与排查手册5.1 吞吐上不去的五个常见原因第一个是 batch 开不大。很多人以为设了 max-num-seqs 就能开大 batch实际上显存不够时框架会自动限制。用 nvidia-smi 看显存占用如果接近满说明 KV Cache 是瓶颈要么量化要么减 max-model-len。第二个是 prefill 阻塞 decode。长 prompt 的 prefill 计算量大会占住 GPU 好几秒期间 decode 请求全在等。开启 chunked prefill 能缓解但根本解法是控制输入长度或者把长 prompt 请求单独排队。第三个是调度开销。请求数太多时调度器每步要遍历所有请求CPU 成为瓶颈。表现是 GPU 利用率不高但吞吐也上不去。解法是限制并发数或者用更高效的调度实现。第四个是通信瓶颈。多卡 TP 时如果卡间带宽不够all-reduce 会拖慢每一步。用 nvtop 或 nsys 看通信占比超过 20% 就要考虑换并行策略。第五个是量化反效果。前面提过某些量化实现的反量化开销大。判断方法是关掉量化跑一遍如果吞吐反而更高说明这个量化不适合你的场景。5.2 常见问题速查表现象可能原因排查方向解决手段吞吐远低于预期batch 太小看显存和并发数量化、减长度、加卡GPU 利用率低CPU 调度瓶颈看 CPU 占用减并发、换调度器延迟忽高忽低长尾请求拖累看请求长度分布chunked prefill、分队列显存 OOMKV Cache 超限算 KV 总量降 gpu-mem-util、量化多卡无加速通信瓶颈看通信占比换并行策略、查互联输出质量下降量化过度对比 FP16 输出降量化等级、只量化权重5.3 几个反直觉的经验并发不是越高越好。我见过有人把 max-num-seqs 设到 1024结果吞吐比 256 时还低因为调度和显存管理开销吃掉了收益。找到拐点比盲目堆并发重要。小模型不一定比大模型吞吐高。如果小模型因为精度问题需要更长的输出才能达到同样效果实际有效吞吐可能更低。要算“有效吞吐”而不是“原始吞吐”。前缀缓存能救命。RAG 场景里大量请求共享相同的系统提示和检索上下文开启前缀缓存后这部分 prefill 只算一次吞吐能提升好几倍。vLLM 的--enable-prefix-caching和 SGLang 的 RadixAttention 都是干这个的。压测数据要贴近真实分布。用固定长度压测出来的数字和真实混合长度负载下的数字能差一倍。ShareGPT 数据集比随机生成长度更接近真实。6. 这套东西还能怎么扩展百万 tok/s 不是一个终点而是一个工程取向的宣言。沿着这个思路往下走有几个方向值得关注。一是投机解码。用小模型草拟、大模型验证能在不损失精度的前提下提升 2 到 3 倍吞吐。代价是显存要多装一个草稿模型适合显存充裕的场景。二是MoE 架构。混合专家模型每次只激活部分参数计算量小但参数量大天然适合高吞吐场景。难点在负载均衡和通信工程复杂度比稠密模型高不少。三是分离式架构。把 prefill 和 decode 拆到不同节点各自用最适合的硬件和并行策略。prefill 是计算密集型decode 是访存密集型分开部署能各自优化。这是目前比较前沿的方向落地案例还不多。四是和 RAG 知识库结合。前面热词里提到的 llm wiki、graphrag 这些本质是把检索结果作为上下文喂给模型。高吞吐推理让“为每个文档预生成摘要和问答对”变得可行这对知识库的构建效率是数量级的提升。我个人在实际操作中的体会是吞吐优化这件事80% 的收益来自前 20% 的工作选对框架、开连续批处理、做权重量化、调好 batch。剩下的 20% 收益要花 80% 的精力去抠比如手写 kernel、调通信、改调度。除非你的业务量真的到了那个级别否则先把前 20% 做扎实性价比最高。最后分享一个小技巧压测时一定要用真实数据而且要跑够长时间。我遇到过跑 5 分钟吞吐很漂亮、跑 30 分钟就掉下来的情况原因是显存碎片累积和请求长度分布的长尾效应。短时间压测会骗人长时间稳定才是真的稳。