尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

揭秘大模型推理速度:1.4万token/s的实现与优化

揭秘大模型推理速度:1.4万token/s的实现与优化 最近 Taalas 对外展示的推理结果里生成速度达到每秒 1.4 万 token。换算成人更容易感知的速度大约是每秒生成一万个英文单词或者几秒钟生成一整本十万字的中文小说。这个数字对做过推理服务的人来说相当显眼因为普通单卡模型的推理输出往往只有每秒几十到几百个 token1.4 万意味着两个数量级的差距。这篇文章要做的不是复述新闻而是把这个指标拆开看token 到底是什么token/s 这个单位怎么读1.4 万 token/s 在什么条件下才可能成立以及如何在自己的环境里测量和优化推理速度。读完以后你可以带着一套可执行的测试方法回到真实项目里判断自己的推理服务距离这个量级还差多远。1. 先理解 token 和 token/s 这两个核心概念1.1 token 是大模型处理文本的最小单位大模型并不能直接理解人类语言。输入文本会先被分词器切碎成 token模型实际处理的是 token 序列而不是原始字符。一个 token 可以是一个英文单词的一部分、一个完整英文单词、一个中文汉字也可能连续覆盖多个汉字具体取决于分词器的词表设计和训练方式。英文场景下常见的经验估算是 1 个 token 约等于 0.75 个英文单词。中文场景下1 个 token 大约对应 1 到 2 个汉字。不同模型的分词器差异很大所以这个换算只能用于估算不能用于精确计费或精确性能对比。这里要额外说明大模型领域说的 token和 Web 登录体系里的 token 完全不是一回事。前者是文本处理的最小单元后者是身份凭证。把两者混淆会在排查问题时走很多弯路。1.2 评价推理速度不能只看一个数字推理速度至少有三个层次TTFTTime To First Token用户发起请求到收到第一个输出 token 的时间。它决定用户的“首字等待”体验。TPOTTime Per Output Token生成单个输出 token 的平均时间。它决定模型输出的“打字机速度”。聚合吞吐单位时间内系统所有请求生成的 token 总数。它决定服务的整体吞吐能力。对应关系如下指标含义关注阶段用户感知TTFT首次返回 token 的耗时Prefill 阶段等待多久才开始出字TPOT每个输出 token 的平均耗时Decode 阶段出字快不快Decode tokens/s1 除以 TPOTDecode 阶段每秒生成多少个 tokenTotal tokens/s单位时间所有请求的生成量整个服务系统能撑多少并发很多厂商宣称的最高速度往往指的是某个特定模型、特定精度、特定批大小下的聚合吞吐而不是单个请求的流式生成速度。单请求速度才是用户真实感受到的速度聚合吞吐才是平台成本核算的速度。1.3 1.4 万 token/s 放到实际场景是什么体感按中文场景估算1 个 token 约等于 1.5 个汉字1.4 万 token/s 大约对应每秒 2 万字生成一本十万字的中文书大约需要 5 秒。按英文场景估算1.4 万 token/s 约对应每秒一万个英文单词约等于每秒 20 页英文文档。这个体感说明 1.4 万 token/s 属于“近乎实时生成一本书”的级别。但要注意这里说的是纯生成阶段不包含排队、网络传输、首字延迟和显存调度的时间。如果是交互式对话用户实际等待时间还要加上 TTFT。另外1.4 万 token/s 大概率不是单请求、单流式解码的速度而在批量并发条件下测得的聚合值。这一点会在第 4 章详细展开。2. 推理速度由哪几个环节决定2.1 Prefill 和 Decode 是两段完全不同的计算大模型生成文本时计算过程明显分成两个阶段。Prefill 阶段一次性处理用户输入的 prompt计算量集中在矩阵乘上属于计算密集型。TTFT 主要由这个阶段决定。输入的 prompt 越长prefill 计算量越大首字返回越慢。Decode 阶段逐 token 生成输出。每一步生成一个 token并且要把当前 token 对应的新 Key 和 Value 写入 KV Cache然后读取完整的 KV Cache 参与注意力计算。这个阶段访存密集decode 速度主要受显存带宽限制。明白了这个区别就能理解为什么“每秒 token 数”不能只看模型参数量。一个 7B 模型在 decode 阶段的主要开销往往不是算力而是从显存读取权重和 KV Cache 的带宽。2.2 硬件瓶颈显存带宽、算力、KV Cache 容量推理服务选型时第一个要看的是显存带宽。以常见的数据中心显卡为例A100 的显存带宽大约在 2TB/s 级别H100 大约在 3TB/s 级别。这只是常见规格的参考值不同型号、不同批次会有差异落地前要查询对应硬件手册。decode 阶段每生成一个 token理论上需要把模型权重和 KV Cache 从显存读一遍。显存带宽越高每秒能完成的读取次数越多生成的 token 数也就越多。这也是为什么一些推理速度优化方案会优先压缩权重、减少 KV Cache 大小而不是单纯堆算力。KV Cache 的显存占用可以按下面的公式估算KV Cache 字节数 2 × layers × num_kv_heads × head_dim × seq_len × batch_size × bytes_per_element公式里的 2 表示 K 和 V 两份缓存。举例来说一个 7B 模型如果层数为 32、kv_heads 为 32、head_dim 为 128使用 FP16 存储在序列长度 2048、batch size 为 1 时2 × 32 × 32 × 128 × 2048 × 1 × 2 1,073,741,824 字节 ≈ 1GB这只是一个请求的 KV Cache。如果并发 16 个请求KV Cache 就会达到 16GB 级别。显存容量不够时系统只能降低并发或者调小 max_model_len吞吐自然上不去。2.3 软件调度策略决定硬件能发挥多少同样的显卡用不同的推理框架吞吐差距可能非常大。原因是软件层面存在几个关键调度机制Continuous Batching传统静态批处理必须等一个批次全部生成完才释放资源。Continuous Batching 允许请求动态加入、动态离开请求完成一个就补一个新请求显著提高 GPU 利用率。PagedAttentionKV Cache 不再一次性分配整块连续显存而是按固定大小的块管理类似操作系统分页减少显存碎片和浪费。算子融合把多个小算子合并成一个大算子减少显存读写次数。这些机制叠加之后推理服务可以在同一张卡上服务更多并发请求整体吞吐和“宣传的峰值速度”就产生了明显差距。3. 用一套最小脚本实测自己的推理速度3.1 环境准备先建立可对比的测量基线要理解 1.4 万 token/s最好的方式是在自己的环境里跑一次测速建立可对比的基线。学习环境不需要复现这个量级重点是把测量方法跑通。推荐使用以下组合pip install torch transformers vllm如果只是想快速体验也可以安装 Ollama 运行小模型。Ollama 的/set verbose模式会直接输出生成速度。本小节示例使用 Qwen2.5-0.5B-Instruct 作为测试模型。模型很小在普通显卡上也能跑起来。实际操作时可以把模型名替换成自己项目使用的模型但要记录参数量、量化精度和 prompt 长度否则结果无法对比。3.2 用 transformers 测单请求生成速度下面的脚本用 Hugging Face transformers 完成推理并统计平均生成 token 数import time from transformers import AutoTokenizer, AutoModelForCausalLM model_name Qwen/Qwen2.5-0.5B-Instruct tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForCausalLM.from_pretrained(model_name, device_mapauto) prompt 请用一段文字解释什么是大模型推理性能。 inputs tokenizer(prompt, return_tensorspt).to(model.device) input_len inputs[input_ids].shape[1] start time.time() outputs model.generate(**inputs, max_new_tokens128) elapsed time.time() - start new_tokens outputs.shape[1] - input_len print(finput tokens: {input_len}) print(fnew tokens: {new_tokens}) print(felapsed: {elapsed:.2f}s) print(faverage speed: {new_tokens / elapsed:.2f} tokens/s)这里要特别说明脚本计算的是“总生成时间 / 新增 token 数”其中包含 prefill 时间和 decode 时间。prompt 越长prefill 占比越高平均值越偏低。它适合用来横向对比同一个模型在不同硬件上的表现但不适合代表纯 decode 速度。3.3 用 vLLM 测聚合吞吐vLLM 更接近生产环境内置了 Continuous Batching 和 PagedAttention。使用方式如下import time from vllm import LLM, SamplingParams model_name Qwen/Qwen2.5-0.5B-Instruct llm LLM( modelmodel_name, gpu_memory_utilization0.8, max_model_len4096, ) sampling_params SamplingParams(max_tokens128, temperature0.7) prompts [写一段关于分布式系统设计的介绍。] * 16 start time.time() outputs llm.generate(prompts, sampling_params) elapsed time.time() - start total_generated sum(len(o.outputs[0].token_ids) for o in outputs) print(fgenerated tokens: {total_generated}) print(fthroughput: {total_generated / elapsed:.2f} tokens/s)vLLM 仓库还自带吞吐基准脚本进入源码目录后可以这样运行python -m vllm.benchmarks.benchmark_throughput \ --model Qwen/Qwen2.5-0.5B-Instruct \ --input-len 256 \ --output-len 128 \ --num-prompts 16这个命令会模拟多个请求并发输入统计聚合吞吐。路径在不同 vLLM 版本中可能有差异使用前先确认版本说明。3.4 结果怎么读才是对的0.5B 小模型在消费级显卡上可能有数百到上千 token/s7B 模型通常只有几十到几百 token/s。如果你测试的模型是 7B 甚至更大却得出几千 token/s 的结果建议检查两件事是否开启了大 batch 并发还是只发了一个请求。是否使用了量化精度还是原始 FP16。同一个模型单请求串行测出来的速度和 16 请求并发测出来的速度数字可能相差数倍。记录测试结果时必须同时写清楚条件否则这个数字没有对比价值。4. 从 1.4 万 token/s 反推哪些优化手段在起作用4.1 批量推理和 Continuous Batching 是吞吐放大镜单请求 decode 速度受硬件的显存带宽限制提升空间有限。但一台推理服务可以同时处理多个请求只要显存够用批量请求可以共用一次前向计算。假设单请求 decode 是 50 token/s生成一个 token 需要完整读取一次权重。如果一次性处理 8 个请求权重读取次数并不会变成 8 倍因为计算可以复用实际吞吐可能变成 200 到 300 token/s。这个倍数关系决定了 1.4 万 token/s 更可能是批量模式下的聚合吞吐而不是单个用户独占一张卡的速度。Continuous Batching 进一步放大了这个效应。传统批处理必须等最慢的请求结束后才能释放批次Continuous Batching 按 token 级别动态调度某个请求生成完成后立刻让出位置新请求可以补入。4.2 KV Cache 复用与 PagedAttention 节省显存显存决定了同时能驻留多少请求。KV Cache 如果按最大长度一次性分配很多请求实际用不满浪费严重。PagedAttention 把 KV Cache 拆成固定大小的块按需分配显存利用率明显提高。同样的 80GB 显存在静态分配模式下可能只能服务十几个长上下文请求在 PagedAttention 模式下可能服务几十个。并发数上去后聚合吞吐自然提升。4.3 量化、投机解码、算子融合怎么影响速度量化是推理优化里最常见的手段。模型权重从 FP16 降到 INT8 或 INT4显存占用减少单位时间能读取的 token 权重更多decode 速度可能提升。常见量化方案包括 AWQ、GPTQ、Int8 等。精度显存占用常见场景注意事项FP16原始大小精度优先显存消耗最大INT8约 FP16 的一半精度损失较小依赖算子优化不一定更快INT4约 FP16 的四分之一长上下文、高并发质量可能有轻微下降需评测投机解码是另一种思路。它先用小模型生成候选 token再用大模型一次性验证多个 token验证通过的 token 可以同时输出。这样大模型实际执行的 decode 步数变少速度得到提升。但投机解码依赖草稿模型与目标模型的输出一致性一致性越高收益越大。算子融合和内核优化则是框架层面的事。FlashAttention 通过减少显存读写显著降低注意力计算开销TensorRT-LLM 会把计算图编译成更适合特定 GPU 的执行计划。这些优化叠加在一起才有了单个模型推理速度的大幅提升。4.4 参数调优速查表使用 vLLM 这类框架时几个参数会直接影响吞吐和延迟参数作用设置偏大的影响设置偏小的影响max_num_seqs最大并发序列数吞吐高TTFT 增加并发低吞吐受限gpu_memory_utilization允许使用的显存比例可驻留更多请求但容易与其他进程冲突显存浪费并发下降max_model_len最大序列长度支持长文档KV Cache 预留大长文本被截断tensor_parallel_size张量并行卡数适合超大模型需高速卡间互联无法容纳大模型这些参数没有固定最优值需要结合模型大小、显存容量、业务请求长度和并发目标一起调。5. 常见问题排查为什么我测不到宣称的速度5.1 单请求速度与聚合吞吐混为一谈现象自己测单请求只有几十 token/s但厂商宣称一万多 token/s。原因两者不是一个口径。单请求测的是用户体验聚合吞吐测的是服务能力。1.4 万 token/s 如果是在 64 并发、长输出、批量测定的条件下得到在单请求场景下没有任何可比性。检查方式确认测试脚本中是否使用了多 prompt 并发是否记录了并发数。处理建议把指标拆成 TTFT、TPOT、总吞吐三个维度分别记录不要用单一数字描述整个系统。5.2 GPU 利用率低但速度仍然慢现象显存占用很高但 GPU 利用率只有 20% 左右生成速度也不理想。可能原因模型权重被加载到了 CPU或者 GPU 被其他任务共享数据在 PCIe 上反复搬运。检查方式nvidia-smi观察进程列表里是否有自己的进程显存占用是否正常。也可以使用持续采样nvidia-smi --query-gpuutilization.gpu,memory.used --formatcsv -l 1处理建议确认模型确实加载到 GPU关闭其他占用显存的任务。如果显存不足导致部分层 offload 到 CPU日志里通常会有 warning需要优先解决显存容量。5.3 TTFT 高首字迟迟不出来现象一旦开始生成速度不慢但用户等很久才看到第一个字。可能原因输入 prompt 过长prefill 计算量大或者系统排队严重请求没有第一时间进入调度。检查方式分别记录 prefill 耗时和 decode 耗时。vLLM 日志里会输出每轮迭代的时间统计可以对比同长度 prompt 在不同并发下的首字延迟。处理建议对超长 prompt 做截断或摘要预处理降低 max_num_seqs控制排队深度开启 prefix caching让相同前缀的请求复用 KV Cache。5.4 显存溢出导致并发上不去现象nvidia-smi 显示显存已满vLLM 报 CUDA OOM服务直接崩溃或拒绝新请求。原因max_model_len 设置过大KV Cache 预留过多或者 batch 设置太大超过显存容量。检查方式看启动日志中的 KV Cache 分配信息例如GPU KV cache size: 32.00 GB Maximum concurrency for 2048 tokens per request: 8处理建议下调 max_model_len减少每个请求的 KV Cache 预留减少 max_num_seqs如果显存依然不足考虑 INT8 或 INT4 量化。5.5 排查顺序建议遇到速度异常时按下面的顺序排查先确认测试脚本是否合理输入长度、输出长度、并发数是否被记录。再看硬件状态GPU 利用率、显存占用、显卡温度是否正常。然后检查模型运行位置是否被加载到 CPU。接着看显存容量KV Cache 是否因为 max_model_len 过大而挤占并发空间。最后检查框架配置是否开启量化、是否启用 Continuous Batching、是否使用多卡并行。6. 学习环境与生产环境的性能实践建议6.1 学习环境怎么测最有效学习阶段不必直接追求每秒上万 token重点是把测量方法学明白。建议从 0.5B 或 1.5B 小模型开始在同一台机器上分别完成三轮测试第一轮单请求串行记录 TTFT 和平均速度。第二轮16 个请求并发记录聚合吞吐。第三轮启用量化重复第二轮对比差异。这样能直观理解单请求延迟和系统吞吐之间的区别也能看到硬件条件对性能的约束。每次测试结束后把模型、精度、并发数、输入长度、输出长度和结果一起写进记录。6.2 生产环境怎么做性能优化生产环境不能只看峰值速度还要看稳定性、成本和控制性。首先推理框架优先选择 vLLM、TensorRT-LLM 或推理厂商提供的专业引擎它们内部的 Continuous Batching、PagedAttention、算子优化比自己手写推理循环更成熟。其次根据业务请求长度设置合理的 max_model_len而不是给所有请求预留最大序列长度。再次量化前要做质量回归不能只看显存下降INT4 虽然显存占用低但部分场景下表现不如 INT8。生产环境还需要补上监控和压测。用 benchmark 脚本做基线压测记录 TTFT 的 P50、P95以及吞吐量随并发数的变化曲线。发布新模型或新框架版本前先跑一遍回归基线对比是否出现明显劣化。6.3 性能测试前检查清单下面的清单可以直接复制到项目文档里作为每次推理性能验证前的固定检查项目模型文件、参数量、量化精度是否明确。GPU 型号、显存容量、是否被其他进程占用。测试脚本是否记录了输入长度、输出长度、并发数。是否区分单请求延迟和聚合吞吐。是否记录了 TTFT、TPOT、Decode tokens/s 三个指标。是否记录了 max_model_len、max_num_seqs、gpu_memory_utilization 等关键参数。是否在日志里确认模型加载到 GPU没有 CPU offload。是否在同一条件下做了量化前后对比。是否保留了原始测量数据方便后续复现。这张清单最有用的场景是两次性能数据对不上时。逐个核对后通常很快能定位是测试口径不一致还是部署参数发生变化。以后再看到每秒 1.4 万 token 这类数字第一件事不是背下来而是问清楚这是什么模型、什么精度、什么批大小、什么输入输出长度、什么硬件条件下测出来的。回答完这些条件数字才有意义。回到自己的部署环境里把上面的测试脚本跑一遍记录好条件再和公开数据做对比。这个习惯比记住任何单点数字都有用。
返回列表