vLLM Serving延迟调优:H100上TTFT与ITL的配置优化实战

发布时间:2026/8/29 9:23:28
vLLM Serving延迟调优:H100上TTFT与ITL的配置优化实战 实际做 LLM 在线推理服务时“服务能启动”和“服务的延迟指标达标”是两件完全不同的事。最近在 8 卡 H100 上做 vLLM Serving 压测实验时最大的体会是同一个模型、同一份请求负载、同样的并发数只调整 vLLM 的启动配置p95 TTFT 和 p95 ITL 就能出现明显差异配置调优之后的效果可以稳定超过默认基线。这里的 TTFT 是 Time To First Token即首 token 延迟ITL 是 Inter-Token Latency即 token 间延迟p95 是 95 分位延迟用来衡量尾部性能。这批实验的价值不在于某一个具体参数的值而在于一套可复现的调优流程先搭建环境并记录基线再理解关键配置项对 prefill 和 decode 两个阶段的真实影响然后用对照实验验证每一项改动的收益最后把结论沉淀成可复用的发布检查清单。本文会按这条主线展开先讲清楚 TTFT、ITL 和 p95 的测量口径再给出 H100 环境下的 vLLM 部署步骤接着逐项拆解影响延迟的关键配置参数然后给出一份可直接使用的实验脚本和排查清单。这个主题适合已经能跑通 vLLM 基础服务、但在压测时发现首 token 偏慢、并发上来后 tail latency 明显抬高的开发者。无论你是要给生产环境选参数还是给团队做容量评估下面的方法都可以直接复用。1. 先搞清楚优化对象TTFT、ITL 和 p95 分别度量什么1.1 一次推理请求的两个阶段一次 LLM 在线推理请求在 vLLM 内部大体分成两个阶段预填充prefill和解码decode。预填充阶段对用户输入的全部 prompt token 做一次前向计算产出第一个输出 token。从请求到达服务端算起到客户端收到第一个输出 token 的时间就是 TTFT。解码阶段则是逐 token 生成后续内容每两个连续输出 token 之间的时间间隔就是 ITL。vLLM 在线服务的日志和 metrics 里常出现的 TPOTTime Per Output Token与 ITL 含义相近都是衡量输出阶段单步耗时的指标。实际项目中要对齐几个边界否则指标会失真TTFT 是端到端概念包含网络传输、排队、调度、prefill 计算、首 token 返回这几段。单独看模型耗时不够要同时观察客户端侧耗时和服务端指标。ITL 关注的是稳定程度。平均值能反映整体吞吐但 p95 ITL 能暴露并发升高后的调度抖动、显存不足导致的抢占、以及 batch 中长输入拖慢短请求等问题。p95 表示 95% 的请求延迟都低于这个值。它比平均值更敏感也是生产环境 SLA 常用的统计口径。1.2 平均值、p95、p99 怎么选只优化平均值很容易掩盖尾部问题。一个典型场景是默认配置下短 prompt 请求的平均 ITL 看起来不错但短请求和长请求混跑时长请求的 prefill 会阻塞解码阶段导致短请求的 ITL 尾部明显上抬。此时平均值可能只涨了 10%p95 却涨了 40%。所以每轮实验至少记录四组数据指标平均值p95p99TTFT每轮压测记录每轮压测记录每轮压测记录ITL每轮压测记录每轮压测记录每轮压测记录TPOT每轮压测记录每轮压测记录每轮压测记录吞吐tokens/s按轮统计无无如果只记录平均值调优方向很容易被误导。p95 和 p99 才是判断“配置是否真的超过基线”的关键口径。1.3 为什么在 H100 上做这类实验H100 SXM 80GB 配备 HBM3 显存显存带宽高、容量大NVLink 互联带宽也很可观。这些硬件特性决定了它能承受更大的 batch 和更长的上下文。但大显存不会自动带来低延迟相反显存越大默认配置越容易把 batch 拉大batch 一旦变大prefill 阶段和 decode 阶段对 GPU 计算资源的争抢就更明显。这也是标题里“config beats the baseline”这句话的实际含义硬件相同、模型相同、负载相同差异来自 vLLM 如何分配显存、如何调度请求、如何切分模型并行。H100 只是放大了这些配置项的效果让问题更容易被观察到。2. H100 环境准备先复现基线再谈优化2.1 硬件、驱动、Python 版本确认拿到服务器之后别急着起服务。先把环境确认一遍避免后面所有结果都被驱动或 CUDA 版本污染。检查项推荐值说明GPU 型号与数量H100 SXM 80GB8 卡4 卡也可以并行参数需要相应调整驱动版本550 或更新新版 vLLM 对驱动有最低版本要求CUDA 版本12.1 以上优先看 vLLM 官方要求的 cu 版本Python3.10 或 3.11部分依赖在 3.12 下兼容性不稳定模型格式HuggingFace 权重第一步先不量化方便定位问题验证命令nvidia-smi python -c import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))这里要强调一个常见坑torch.cuda.is_available()返回True不代表 PyTorch、CUDA、vLLM 三者版本匹配。环境问题应该在实验开始前解决而不是在压测结果异常时怀疑配置参数。2.2 安装 vLLM 并在 Ubuntu 上启动官方推荐用 pip 安装编译好的 wheel。以 Ubuntu 24.04 部署为例先建虚拟环境再安装python -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install vllm如果原始环境没有锁定版本落地前要先确认 vLLM 与 PyTorch、CUDA 的匹配关系。生产环境建议把requirements.txt或pyproject.toml里的版本固定住并记录在实验说明里。注意不要直接在 Windows 上尝试用 pip 安装 vLLM 做生产部署。vLLM 原生依赖 CUDA 生态和 Linux 内核特性Windows 下的支持非常有限实验里见到的问题大多是环境问题而不是配置问题。2.3 用默认参数跑出第一版基线基线阶段要尽量“不改任何优化参数”只设置必要参数模型路径、端口、服务名、张量并行数。CUDA_VISIBLE_DEVICES0,1,2,3,4,5,6,7 \ python -m vllm.entrypoints.openai.api_server \ --model /data/models/Qwen2.5-72B-Instruct \ --served-model-name qwen2.5-72b \ --tensor-parallel-size 8 \ --host 0.0.0.0 \ --port 8000 \ --disable-log-requests--disable-log-requests是为了避免压测时请求日志刷屏。压测阶段建议保留默认的 metrics 输出稍后可以用/metrics接口观察服务端指标。基线启动后不要立刻压测。先用一个低并发请求确认服务和模型加载正常curl http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -d {model:qwen2.5-72b,messages:[{role:user,content:你好}],max_tokens:16}能正常返回之后再记录第一版基线数据。基线数据是后面所有对照实验的参照物一定要把启动参数、模型路径、请求配置完整保存下来。3. 影响 TTFT 和 ITL 的关键配置项3.1 显存分配gpu-memory-utilization 与 KV cache--gpu-memory-utilization控制 vLLM 最多占用多少比例的 GPU 显存。默认值通常是 0.9也就是预留 10% 给模型权重之外的计算开销。这个参数直接影响 KV cache 能分配多少空间调大KV cache 空间更多能容纳更多并发请求排队和抢占减少TTFT 的尾部通常会下降。调小显存更安全但并发能力下降请求排队变长p95 TTFT 会抬升。调得过大模型加载、CUDA context、临时 buffer 分配时可能直接 OOM。配合它的是--swap-space默认约 4GB代表 CPU 内存里预留多少空间做 KV cache 换出。实验阶段不建议依赖 swap因为它会显著抬升 ITL。3.2 批处理上限max-num-seqs 与 max-num-batched-tokensvLLM 的核心优势是 continuous batching也就是不需要等一个 batch 全部结束再接收新请求而是在每个迭代步结束后动态调整 batch。--max-num-seqs控制一个迭代步里最多同时处理的请求数默认常见值是 256--max-num-batched-tokens控制一个迭代步里最多处理的 token 总数。这两个参数需要一起理解max-num-seqs太小并发请求被拒或排队TTFT 尾部变高。max-num-seqs太大batch 变大单次前向计算时间变长ITL 和 TPOT 上升。max-num-batched-tokens太小GPU 利用率不足吞吐下降。max-num-batched-tokens太大一个迭代步耗时超过预期输出节奏变慢。不同版本的默认值差异较大有的版本默认 2048有的版本跟随max-model-len。实验前先执行python -m vllm.entrypoints.openai.api_server --help确认当前版本的实际默认值不要凭记忆配置。3.3 上下文长度上限max-model-len 与调度预分配--max-model-len是模型支持的上下文长度上限。vLLM 的调度器会按这个上限为每个请求预留 KV cache 空间。如果设置得过大可容纳的并发请求数会下降请求更容易排队p95 TTFT 会明显抬升如果设置得过小长请求会直接报错或被拒绝。实验阶段建议先统计业务里 prompt 长度和期望输出长度的分布再决定max-model-len。例如业务中 95% 的请求总长度在 4K token 以内就不要为了偶尔几个长请求把上限设到 32K可以考虑单独为一个长上下文服务实例配置更大的长度。这里的取舍本质是“资源预留粒度”的取舍不是越大越好。3.4 预填充与解码的协调chunked prefill长请求的 prefill 会占用一整段 GPU 计算时间期间若 batch 里有短请求在 decode短请求的输出就会被推迟。chunked prefill 会把一次 prefill 的输入 token 切分成多个 chunk插入到 decode 迭代之间执行避免长 prefill 长时间阻塞其他请求。实际效果对混合长短请求的在线服务chunked prefill 能明显降低 p95 ITL。代价是单条长请求的 TTFT 可能略有上升因为 prefill 被分片后不是一次性算完。在较新版本的 vLLM 中chunked prefill 可能默认开启旧版本需要用--enable-chunked-prefill显式开启。落地前先确认版本行为。这个配置是“配置超过基线”的常见来源之一尤其适合在线对话类负载。3.5 并行策略与实践参数tensor-parallel、enforce-eager--tensor-parallel-size决定模型权重如何切分到多张卡上。对 72B 级别的模型在 8 卡 H100 上TP8 是常见选择因为单卡放不下完整权重。对较小模型TP 过大反而引入不必要的通信开销ITL 可能变差。实验时应按模型实际显存需求选择最小可用 TP。--enforce-eager是个经常被误用的参数。它关闭 CUDA graph 加速降低显存占用但会牺牲一部分执行速度和稳定性通常只用于调试或显存极度紧张的场景。生产环境不要默认加这个参数。量化参数也会间接影响延迟。FP8、AWQ、GPTQ 等量化方式能减少模型权重占用从而释放显存给 KV cache提高并发能力。但量化会改变计算特性必须单独做一轮对照实验不能假定一定更快。3.6 前缀缓存与推测解码--enable-prefix-caching允许多请求共享相同前缀的 KV cache对多轮对话、system prompt 固定的场景收益明显能降低重复 prefill 带来的 TTFT。推测解码speculative decoding用一个小模型先草拟多个 token再由大模型一次性验证。在 batch 较小的场景下它能降低单请求的 ITL在 batch 很大的场景下收益可能被稀释。接入方式因版本而异实验前先查看当前版本的--speculative-model相关参数说明。3.7 容易被忽略的辅助配置配置项作用建议--enable-metrics输出 Prometheus 格式指标到/metrics压测和生产都建议开启--disable-log-requests关闭每个请求的访问日志压测时减少日志 I/O 干扰--served-model-name对外暴露的模型名固定名称方便客户端对齐--max-parallel-loading-workers模型加载并行数大模型加载时可适当调大提速4. 配置对照实验如何证明“配置超过基线”4.1 设计请求负载对照实验最怕负载不固定。每次请求长度不同、输出长度不同、并发忽高忽低结果就无法归因到配置项上。推荐固定以下维度prompt 长度固定例如统一 512 token。输出长度固定例如统一max_tokens128。并发数固定例如 32、64、128 三档分别压测。压测时长固定例如预热 2 分钟正式 5 分钟。用 OpenAI SDK 写一个最小压测脚本记录每个请求的 TTFT 和完整 token 序列import statistics import time from concurrent.futures import ThreadPoolExecutor from openai import OpenAI client OpenAI(base_urlhttp://127.0.0.1:8000/v1, api_keyEMPTY) def send_one(prompt, max_tokens128): start time.perf_counter() resp client.chat.completions.create( modelqwen2.5-72b, messages[{role: user, content: prompt}], max_tokensmax_tokens, streamTrue, ) first_token_time None token_times [] last_time time.perf_counter() for _ in resp: now time.perf_counter() if first_token_time is None: first_token_time now - start token_times.append(now - last_time) last_time now return { ttft: first_token_time, itls: token_times, total: time.perf_counter() - start, } def run_load(concurrency, rounds50): results [] with ThreadPoolExecutor(max_workersconcurrency) as pool: futures [pool.submit(send_one, 你好 * 256) for _ in range(rounds)] for f in futures: results.append(f.result()) return results这里注意两点使用streamTrue否则拿不到首 token 时间。记录每个 token 的间隔而不是只记录总耗时否则 ITL 只能靠总耗时除以 token 数去近似会掩盖抖动。4.2 记录指标与保存实验现场每轮实验结束后把结果序列化保存并同时采集服务端/metricscurl -s http://127.0.0.1:8000/metrics | grep vllm | tee metrics_round_01.txt客户端脚本负责输出round01 concurrency64 requests50 p95_ttft1.23s avg_ttft0.98s p99_ttft1.51s p95_itl42ms avg_itl31ms p99_itl58ms throughput... tokens/s/metrics里重点看vllm:num_requests_running、vllm:num_requests_waiting用来判断延迟抬升是来自执行时间还是排队。4.3 实验矩阵与执行顺序建议按下面顺序推进每轮只改一个变量轮次实验目的改动点0基线默认配置1验证 chunked prefill开启--enable-chunked-prefill2验证前缀缓存增加--enable-prefix-caching3验证 batch 容量调整--max-num-seqs4验证显存分配调整--gpu-memory-utilization5最终组合把有效改动合并每轮都必须重启服务同一个进程内改参数不生效。重启后先低并发冒烟再进入正式压测。把启动命令、负载参数、结果文件放在同一个目录避免后期追查时不知道数据来源。5. 实验示例从基线到优化配置的完整对照5.1 基线配置示例python -m vllm.entrypoints.openai.api_server \ --model /data/models/Qwen2.5-72B-Instruct \ --served-model-name qwen2.5-72b \ --tensor-parallel-size 8 \ --host 0.0.0.0 --port 8000 \ --disable-log-requests5.2 优化配置示例python -m vllm.entrypoints.openai.api_server \ --model /data/models/Qwen2.5-72B-Instruct \ --served-model-name qwen2.5-72b \ --tensor-parallel-size 8 \ --gpu-memory-utilization 0.92 \ --max-num-seqs 128 \ --max-num-batched-tokens 4096 \ --max-model-len 8192 \ --enable-chunked-prefill \ --enable-prefix-caching \ --host 0.0.0.0 --port 8000 \ --disable-log-requests \ --enable-metrics注意这份参数是针对 72B 模型和固定业务负载的示例不是通用最优解。--max-num-seqs从默认值降到 128看起来是“降低并发”但实际作用是控制单步 batch 过大导致 ITL 变差配合更大的--max-num-batched-tokens和 chunked prefill让 GPU 在吞吐和延迟之间更平衡。5.3 结果与分析思路下面这组数字只用于演示分析方法不同模型、不同请求分布下结果会有差异场景p95 TTFTp95 ITL备注基线1.85s68ms长请求 prefill 周期性阻塞 decode开 chunked prefill1.76s41msITL 明显改善TTFT 变化不大再开 prefix caching1.52s38ms重复前缀请求受益调整 batch 上限后1.31s35ms排队和单步 batch 同时改善最终组合1.28s33ms整体超过基线判断配置是否有效的标准不是单一指标变好而是p95 TTFT 和 p95 ITL 都要看不能只挑好看的说。对比/metrics里 running 和 waiting 请求数区分延迟来自执行还是排队。如果 ITL 全面下降但吞吐下降超过 20%要确认业务更在意延迟还是吞吐。6. 常见问题与排查路径6.1 现象到原因排查表问题现象常见原因检查方式处理建议服务启动后显存 OOMgpu-memory-utilization过大或 CUDA context 占用超预期查看nvidia-smi中进程占用降低该配置或开启--enforce-eager临时验证并发上来后 TTFT 突然抬升请求排队max-num-seqs或 KV cache 不足看/metrics中vllm:num_requests_waiting调大gpu-memory-utilization或降低max-model-lenp95 ITL 偏高长 prefill 阻塞 decode或 batch 单步过大关闭其他配置单独压测开启 chunked prefill降低max-num-seqs双卡/多卡无法启动tensor-parallel-size与CUDA_VISIBLE_DEVICES不一致nvidia-smi核对卡序显式指定CUDA_VISIBLE_DEVICES确认卡数量足够/metrics没有输出未开启--enable-metrics或网络策略屏蔽端口curl /metrics检查启动命令增加--enable-metrics--enforce-eager加不加都纠结误以为它一定提升性能对比两轮压测生产默认关闭调试显存问题时再开MoE 模型 TTFT 偏高参数量大但激活参数少的模型prefill 阶段仍要处理完整权重对比同规模 Dense 模型优先优化 prefill 阶段的 batch 调度必要时量化6.2 排查链路遇到异常指标按顺序排查不要跳步输入是否正确请求模型名、prompt、max_tokens是否符合预期。文件路径和命名模型路径、served-model-name是否写错。依赖版本vLLM、PyTorch、CUDA、驱动版本是否匹配。配置是否生效重启后检查启动日志中的实际参数。资源状态显存、CPU、网络、端口是否被占用。日志关键字vllm相关 ERROR、WARNING、OOM 信息。版本限制当前 vLLM 版本是否支持你使用的参数和模型架构。6.3 enforce-eager 到底要不要用实践中经常有人因为显存不足给启动命令加上--enforce-eager然后发现首 token 变慢。原因很简单CUDA graph 能减少 kernel launch 开销关闭后每次前向计算都变慢。它适合用来排除显存问题但不要把它当成常态优化手段。如果加了--enforce-eager仍然 OOM说明模型权重、KV cache 和 CUDA context 的总需求已经超过显存物理容量。正确做法是降低max-model-len、max-num-seqs或使用量化模型而不是继续压榨默认显存上限。7. 最佳实践与扩展方向7.1 vLLM 生产发布前检查清单固定 vLLM、PyTorch、CUDA 版本保存启动命令到代码仓库。按业务峰值预留显存缓冲gpu-memory-utilization不要在生产环境拉满。max-model-len与业务请求长度分布对齐不要贪长。开启--enable-metrics对 TTFT、ITL、waiting 请求数配置告警。压测前先预热预热时间不少于 2 分钟。客户端设置超时和重试避免服务端排队拖垮调用方。每轮实验保存完整现场启动命令、负载参数、原始结果、metrics 快照。上线前用真实业务流量回放做一次回归确认配置收益在真实分布下仍然成立。7.2 进一步优化方向如果基础配置调优已经做完下一步可以从这几个方向继续推测解码在小并发场景下降低单请求 ITL需要评估草稿模型额外显存成本。量化选型FP8、AWQ、GPTQ 在不同模型上的延迟特性不同必须逐模型实验。调度与版本升级新版本 vLLM 的调度策略和默认参数会变化升级后要重新跑一轮对照。与其他推理引擎做横向对比SGLang 等方案在部分前缀复用和调度场景下表现不同选型要基于自己的负载而不是别人的基准测试。模型结构本身的优化MoE 类模型 prefill 阶段计算量大TTFT 偏高是结构特性服务层可以配合 batch 调度缓解但根本改善依赖模型侧优化。做 vLLM 调优最忌讳的是“一次改十个参数看起来变好了就上线”。真正有效的方法是把每一项改动单独验证确认收益来源再组合出稳定方案。这套流程在 H100 上成立换到其他型号的 GPU 也成立差别只是具体的推荐参数不同。把基线、对照、复现这三件事做好vLLM Serving 的延迟问题就变成了一个有清晰路径的工程问题而不是靠运气调参。