news 2026/9/28 16:49:35

Nori LLM百万token每秒吞吐优化实战:连续批处理与PagedAttention

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Nori LLM百万token每秒吞吐优化实战:连续批处理与PagedAttention

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/s,A100 上能到 100 多。

第二种是系统总吞吐,也就是所有并发请求加起来,每秒总共产出多少 token。这个数字可以远高于单请求速度,因为多个请求在并行计算。Nori LLM 说的 1M tok/s 显然是这个口径。

第三种是有效吞吐,扣掉被截断、被丢弃、被重试的 token 之后,真正有用的产出。这个最容易被忽略,但最影响实际成本。

提示:看到任何吞吐数字,第一件事是问清楚是哪种口径,以及 batch size 和序列长度是多少。脱离这两个前提的吞吐数字没有意义。

2.2 一个可复算的估算模型

假设你有一个 8 卡的节点,每张卡显存 80GB,跑一个 7B 的模型,用 FP16 权重,权重占用约 14GB,KV Cache 占用剩下的空间。KV Cache 的大小可以用这个公式估算:

KV Cache 每 token 字节数 = 2 * num_layers * num_kv_heads * head_dim * dtype_bytes

以 Llama 2 7B 为例,32 层,32 个 KV head,head_dim 128,FP16 每元素 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 卡 TP,70B 用 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 分钟就掉下来的情况,原因是显存碎片累积和请求长度分布的长尾效应。短时间压测会骗人,长时间稳定才是真的稳。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 16:49:22

CV论文日更工作流:三层过滤+领域词典实现精准推送

1. 这不是“论文搬运工”,而是一套可复用的CV领域日更信息流工作流你有没有过这种体验:早上打开ArXiv,面对每天300篇新提交的计算机视觉(CV)论文,点开摘要扫两行就关掉——不是不想看,是根本筛不…

作者头像 李华
网站建设 2026/9/28 16:48:36

风光储微电网Simulink建模与仿真:架构、控制与参数配置

搞新能源微电网仿真这事的感受,和之前只做单机控制完全不同:风电、光伏、储能三个单元摆在一个系统里,每个单元都有自己的一堆控制逻辑,连到一起后还要保证母线电压稳、功率平衡、模式切换不停电。项目标题“基于风光储互补微电网…

作者头像 李华
网站建设 2026/9/28 16:48:34

Vibecoding持久化工作区:Web端管理Claude Code与Codex会话

你有没有碰到过这种场景:在终端里用 Claude Code 或 Codex 写一下午代码,prompt、AI 回复、命令、报错全部挤在一个黑窗口里,切个分支、关个终端,第二天想找回昨天的上下文,发现一切归零。Vibecoding 这个词最近确实是…

作者头像 李华
网站建设 2026/9/28 16:47:27

R语言医学分析实战:心脏病术后复发预测全流程教程

简介:这份资源是面向医学统计与临床研究方向的R语言实战教程,围绕心脏病术后复发预测这一具体课题,帮助具备基础R语法或统计背景的读者完成从数据到模型的完整分析链路。压缩包共906个文件,约41.52MB,以91个R脚本、20个…

作者头像 李华
网站建设 2026/9/28 16:47:26

GitHub打不开?从访问排查到开源项目评估选型实战

这周的GitHub热门话题观察,我想从一个有点反直觉的现象说起:搜“GitHub开源项目”的人非常多,但与此同时,“GitHub打不开”“GitHub镜像”“GitHub下载加速”这几类词的搜索量也高得惊人。也就是说,很多人不是不想看开…

作者头像 李华
网站建设 2026/9/28 16:47:20

STM32定时器触发ADC+DMA双缓冲实现多通道高频数据采集

做多通道数据采集模块时,我遇到过最典型的需求就是“一边连续采,一边还要忙着把数据打包发出去”。一开始在主循环里轮询ADC等EOC标志,采样间隔抖得没法看,稍微加点业务逻辑,采样率就掉到几千赫兹。换成ADC中断读取缓冲…

作者头像 李华