尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

vLLM在线服务延迟优化:H100上降低p95 TTFT与ITL的配置实战

vLLM在线服务延迟优化:H100上降低p95 TTFT与ITL的配置实战 如果你的 H100 集群只用来跑离线吞吐压测那大概率不会注意到服务配置有什么问题。可一旦把 vLLM 接到线上 API几十个用户同时开始对话延迟指标就会立刻暴露出一堆麻烦。很多团队遇到的情况都是同一个模型一样、GPU 一样、权重一样唯一不同的只是服务端 config但 p95 的 TTFT 和 ITL 能差出一大截。这背后的原因并不神秘vLLM 的默认配置偏向“跑通”而不是偏向“低延迟”。而真正影响在线体验的恰恰是 p95 分位数的首 token 延迟和每 token 生成延迟。这篇文章会从 TTFT、ITL 这两个指标出发拆解 vLLM 在 H100s 上做服务化部署时的关键配置项然后给出一套可复现的基线对比实验方法帮助你理解为什么“config beats the baseline”在在线推理场景里是真实存在的以及怎样在自己的环境里把配置调出效果。1. 为什么在线 LLM 服务要用 p95 看延迟先看一个很常见的误区很多人汇报模型延迟习惯报“平均延迟”。平均延迟低不代表用户体验好。一次推理请求在并发高峰期可能排队等了好几百毫秒但因为大多数请求都很快平均值依然好看。问题的关键在于用户感受到的并不是平均值而是他最慢的那一次响应。在线 LLM 服务有两个核心延迟指标TTFTTime To First Token从请求发出到收到第一个 token 的时间。ITLInter-Token Latency生成阶段每两个 token 之间的间隔时间。用户对“响应快不快”的感知几乎完全由这两个指标决定。首 token 等太久用户会觉得服务卡死后续 token 间隔太长用户会觉得模型在“一个字一个字往外蹦”。因此生产环境真正应该盯的是 p95甚至 p99 分位数。p95 的含义是在 100 个请求里有 95 个请求的延迟低于这个值剩下 5 个可能明显更慢。这 5% 的慢请求往往是并发升高、调度排队、显存不足、前缀缓存未命中等因素造成的长尾。如果 p95 TTFT 居高不下说明服务在高负载下会周期性出现“卡顿”。而平均值恰好会把这些长尾淹没掩盖真实问题。所以这篇文章里所有实验对比都拿 p95 TTFT 和 p95 ITL 作为核心评判口径。这不是为了追求指标好看而是为了贴近用户真实体感。2. TTFT 和 ITL 到底在衡量什么要理解配置的作用必须先理解 TTFT 和 ITL 在服务端经历了什么。TTFT 的时间消耗主要来自三个阶段第一是排队等待时间。请求到达服务后vLLM 的调度器需要决定当前请求是立即进入计算还是等上一批请求结束。并发越高排队概率越大。第二是 prefill 阶段。模型需要把用户输入的 prompt 一次性处理完生成第一个 token 的隐藏状态。prompt 越长prefill 计算量越大TTFT 也会越高。第三是调度与显存分配。包括 KV cache 分配、序列状态初始化、CUDA Graph 捕获等开销。这些开销虽然单次不大但高并发时会被放大。ITL 的消耗则来自 decode 阶段。LLM 是自回归模型每生成一个 token 都需要一次完整的前向传播。也就是说ITL 基本等于单次 decode 前向的耗时再加上并发请求之间切换和显存带宽竞争的开销。一个容易被忽略的点是同一次生成过程中TTFT 和 ITL 并不是独立变化的。调大并发上限可能提升吞吐但 prefill 和 decode 同时挤占 GPU 资源TTFT 和 ITL 都会恶化。调小并发延迟稳了但 GPU 利用率掉下去吞吐又不划算。这正是配置调优的难点所在。另外MoE 模型的 TTFT 往往比同尺寸 Dense 模型更敏感。比如基于 Qwen3-35B-A3B 这类 MoE 模型做服务时虽然激活参数只有约 3B显存占用不大但 prefill 阶段涉及专家路由计算再加上 H100 的算力极高计算时间很短调度开销反而更容易成为 TTFT 的主要成分。这也是“为什么 Qwen3 35B A3B 的 TTFT 比较高”这一现象的常见原因之一不是模型变慢了而是 memory-bound 场景下调度和访存时间占比更高了。可以用一张表来快速对比两个指标指标衡量内容用户感知主要影响因素TTFT首 token 延迟响应是否跟手排队时间、prompt 长度、KV cache 命中、prefill 调度ITL每 token 生成间隔生成是否流畅decode 前向耗时、并发切换、显存带宽、CUDA GraphTPOT每输出 token 耗时与 ITL 类似与 ITL 高度相关vLLM 指标体系中对应 Time Per Output Token结论很明确如果 TTFT 太高优先看调度排队和 prefill 优化如果 ITL 太高优先看 decode 效率和并发资源竞争。两者的优化方向并不完全相同。3. H100s 上的 vLLM 部署环境与前置条件讨论配置调优之前先过一遍环境。H100 系列 GPU 在显存和算力上非常强常见的 H100 SXM 单卡 80GBH100 NVL 则有更大显存或双卡模组形态。但硬件强不意味着软件配置可以随意。vLLM 对运行环境有明确要求。vLLM 官方对 Linux 的支持最完整生产环境建议使用 Ubuntu 20.04/22.04/24.04 这类发行版。如果你在 Windows 10 上直接尝试安装 vLLM大概率会遇到各种兼容性问题因为 vLLM 的官方 wheel 包主要是面向 Linux 的。Windows 下更可行的方式是 WSL2 或 Docker Desktop但真正跑生产推理服务还是建议直接使用 Linux 服务器。驱动和 CUDA 方面通常需要较新的 NVIDIA 驱动配合 CUDA 11.8 或 12.x 环境。vLLM 的新版本往往依赖特定的 PyTorch 和 CUDA 版本因此最稳妥的方式是使用官方 Docker 镜像或者在干净的 Python 虚拟环境中安装。先检查 GPU 环境nvidia-smi输出中应该能看到 H100 显卡、显存大小和驱动版本。如果这里看不到 GPU后面所有配置都不用谈。接着创建虚拟环境并安装 vLLMpython -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install vllm如果你需要从源码构建或者要在昇腾等非 NVIDIA 硬件上使用需要参考对应后端的安装文档。要注意的是昇腾 910b 这类硬件上跑 vLLM通常需要特定的 Ascend 后端插件并且不是所有模型类型都能直接启动。比如在昇腾 910b-a2 服务器上通过 vLLM 启动 embedding 向量模型或 reranker 模型时可能会报后端不支持的错误。这不是 vLLM 主流程的问题而是后端适配范围的限制建议先确认你的硬件后端到底支持哪些模型类型再决定是否引入 vLLM。Docker 方式安装更省心docker run --runtime nvidia --gpus all \ -v ~/.cache/huggingface:/root/.cache/huggingface \ -p 8000:8000 \ vllm/vllm-openai:latest \ --model Qwen/Qwen3-35B-A3B-Instruct \ --gpu-memory-utilization 0.9这里的镜像版本和模型名称都要以实际为准重点是理解容器启动 vLLM 的基本方式。4. vLLM 服务配置项的影响分析vLLM 的启动参数非常多但真正影响 p95 TTFT 和 ITL 的配置可以归为三类内存与 KV cache、调度与并发、计算模式。下面逐个拆解。4.1 gpu-memory-utilization这个参数控制 vLLM 最多使用多少比例的 GPU 显存。默认值一般是 0.9也就是预留 90% 显存给 vLLM 使用剩下 10% 留给模型权重、CUDA context 和其他开销。它的直接影响是 KV cache 的可用空间。KV cache 越大能同时容纳的序列越多调度排队时间越短。但如果把 gpu-memory-utilization 设置得太高很容易在并发升高时触发显存溢出。尤其当你同时在 GPU 上跑其他进程时需要预留出足够余量。4.2 max-model-lenmax-model-len 决定模型支持的上下文最大长度。它的一个隐藏成本是KV cache 预分配会按照最大长度计算。也就是说max-model-len 设得越大每个序列消耗的 KV cache 越多能并发处理的请求就越少。常见的爆显存问题往往和这个参数有关。比如部署一个 9B 模型如果 max-model-len 设置成 128K显存会被庞大的 KV cache 预算吃掉一大块导致实际并发量很低甚至加载模型时就提示显存不足。对于大多数业务场景如果 prompt 长度并不长就没必要把 max-model-len 顶到模型上限。按业务实际最长输入来设性价比更高。4.3 max-num-seqsmax-num-seqs 控制 vLLM 最大并发序列数。vLLM 的 continuous batching 机制可以在一个 batch 内动态调度不同序列的生成进度所以这个值不等同于 GPU 一次能计算的序列数但它会限制系统最多同时跟踪多少请求。这个参数对 p95 TTFT 的影响很直接。max-num-seqs 设置过大大量请求同时涌入prefill 和 decode 混在一起GPU 资源被切碎每个请求都要排队最慢的请求会非常慢。设置过小GPU 利用率不足单位时间能处理的请求变少请求堆积后 TTFT 同样会恶化。经验上max-num-seqs 需要根据模型大小、显存和业务并发量一起调。没有一个固定值适用所有场景。4.4 max-num-batched-tokensmax-num-batched-tokens 表示单次前向计算最多处理多少个 token。它影响一个 batch 的大小上限。调大这个值单次前向能塞进更多 tokenGPU 算力利用率更高但过大会增加单次计算耗时导致 ITL 上升。如果发现 TTFT 高但 ITL 稳定可以适当调大 max-num-batched-tokens让 prefill 更快地批量处理。但如果 ITL 也在上升说明单次前向已经太重反而应该调小。4.5 tensor-parallel-sizetensor-parallel-size 表示张量并行使用的 GPU 数量。对大模型来说单卡显存放不下权重必须用多卡分片。H100 的 80GB 显存虽然大但 70B 级别模型依然需要多卡。张量并行会引入卡间通信开销。并行度从 1 升到 2、从 2 升到 4都会增加 NCCL 通信时延对 ITL 的负面影响更明显。因此能用单卡跑起来就不必强行多卡模型实在放不下时才考虑增加并行度。实际项目里也常遇到“L20 双卡跑不动”的问题这往往不是并行度本身的问题而是通信库版本、NCCL 环境变量或者模型分片方式没配好。4.6 enable-prefix-caching前缀缓存Prefix Caching是降低 TTFT 的利器。如果多个请求共享相同的 system prompt 或多轮对话前缀vLLM 可以直接复用前缀的 KV cache跳过重复的 prefill 计算。在较老版本的 vLLM 中需要显式加上--enable-prefix-caching开启新版本已经默认开启。对于 prompt 前缀重复度高的业务场景这个配置能显著降低 p95 TTFT。4.7 enforce-eager 的影响--enforce-eager是一个容易踩坑的参数。它的作用是强制关闭 CUDA Graph使用 eager 模式执行模型。CUDA Graph 能把模型计算过程捕获成一张图减少 kernel 启动开销。对 decode 这种大量小算子执行的阶段来说CUDA Graph 的收益很大。强制关闭后显存占用会降低但 ITL 会明显变长。如果部署时因为显存紧张而开启了 enforce-eager建议优先通过调小 max-model-len 或 max-num-seqs 来节省显存而不是牺牲延迟。4.8 reasoning-parser 的作用推理模型如 DeepSeek-R1、Qwen3 系列在输出时常带有 reasoning 内容。--reasoning-parser的作用是让 vLLM 正确解析这些推理内容把 reasoning 和最终回答区分开。这个配置不直接决定 TTFT 或 ITL但会影响返回内容的结构。如果模型本身输出 reasoning 标签而解析器没配对可能导致客户端拿到额外的思维链文本响应体变大间接影响用户感知延迟。下面用一张表总结这些配置和性能指标的关联配置项主要影响调大/开启的后果调小/关闭的后果gpu-memory-utilizationKV cache 容量支持更多并发OOM 风险升高并发受限排队增多max-model-lenKV cache 预算支持更长上下文显存压力大上下文受限显存释放max-num-seqs调度并发上限吞吐提升长尾延迟升高排队时间变短吞吐下降max-num-batched-tokens单次前向 token 数更充分的算力利用ITL 上升调度更频繁利用率降低tensor-parallel-size模型分片与通信支撑更大模型通信开销增加通信减少显存压力增大enable-prefix-cachingprefill 复用降低首 token 延迟相同前缀重复计算enforce-eagerCUDA Graph 执行显存降低延迟恶化延迟更好显存占用增加5. 实验设计从基线到 config 调优理解了配置项下面进入正题如何设计一个能说明“config beats the baseline”的实验。实验目标很简单同一台 H100 服务器、同一个模型、同一批测试请求只改变 vLLM 启动配置对比 p95 TTFT 和 p95 ITL。首先要固定测试条件。模型选择一个实际部署的模型比如 Qwen3-35B-A3B-Instruct。请求集要尽量贴近线上真实场景包含不同 prompt 长度模拟一定并发数覆盖多轮对话或共享 system prompt 的情况。测试过程中建议先用少量请求做预热再进行正式测量。5.1 基线配置示例所谓基线就是直接使用 vLLM 默认参数启动。python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen3-35B-A3B-Instruct \ --tensor-parallel-size 1 \ --served-model-name online-llm这个配置能跑通但未必能发挥出 H100 的性能。特别是 max-num-seqs、max-num-batched-tokens 等参数都保持默认值时在并发场景下很可能出现排队加剧的问题。5.2 调优配置示例调优方向如下把 gpu-memory-utilization 提高到 0.9让 KV cache 空间更充足根据实际业务把 max-model-len 设置到合理值把 max-num-seqs 和 max-num-batched-tokens 从默认值调整到匹配并发负载的水平保持 CUDA Graph 开启开启前缀缓存。python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen3-35B-A3B-Instruct \ --tensor-parallel-size 1 \ --max-model-len 32768 \ --max-num-seqs 64 \ --max-num-batched-tokens 8192 \ --gpu-memory-utilization 0.9 \ --enable-prefix-caching \ --served-model-name online-llm需要说明的是这里的 max-num-seqs 和 max-num-batched-tokens 不是灵丹妙药。具体数值要结合模型、显存和并发量做几轮实验。更稳妥的思路是先跑两三个候选配置观察 p95 的变化趋势再往最优区间收敛。5.3 压测脚本设计压测脚本需要能够记录每次请求的 TTFT 和 ITL。这里用 Python 标准库实现一个最小示例避免引入额外依赖。# 文件路径client_ttft_itl.py import json import time import urllib.request def stream_chat(prompt, urlhttp://localhost:8000/v1/chat/completions): payload { model: online-llm, messages: [{role: user, content: prompt}], stream: True, } req urllib.request.Request( url, datajson.dumps(payload).encode(utf-8), headers{Content-Type: application/json}, ) start time.perf_counter() first_token_time None token_count 0 with urllib.request.urlopen(req) as resp: for raw_line in resp: line raw_line.decode(utf-8).strip() if not line or not line.startswith(data:): continue data line[5:].strip() if data [DONE]: break chunk json.loads(data) delta chunk[choices][0].get(delta, {}) if delta: if first_token_time is None: first_token_time time.perf_counter() token_count 1 end time.perf_counter() ttft (first_token_time - start) * 1000 if first_token_time else None itl ((end - first_token_time) * 1000 / token_count) if token_count else None return ttft, itl if __name__ __main__: test_prompts [ 请介绍一下 H100 GPU 的主要特点。, 用 Python 写一个快速排序算法并解释时间复杂度。, ] * 50 # 实际测试请按需扩充 for idx, prompt in enumerate(test_prompts): ttft, itl stream_chat(prompt) print(f请求 {idx 1}: TTFT{ttft:.1f}ms, ITL{itl:.1f}ms)这个脚本虽然是单线程串行请求但已经足够验证基础延迟指标。如果要做并发压测可以用线程池或 asyncio 改造也可以直接使用 vLLM 仓库自带的 benchmark 脚本。5.4 计算 p95 指标请求跑完后把上一步输出保存下来用一个小脚本计算 p95。# 文件路径calc_p95.py import numpy as np ttfts [100.0, 120.0, 150.0, 500.0, 200.0] itls [50.0, 55.0, 60.0, 120.0, 70.0] print(fp95 TTFT {np.percentile(ttfts, 95):.1f} ms) print(fp95 ITL {np.percentile(itls, 95):.1f} ms) print(fmean TTFT {np.mean(ttfts):.1f} ms)p95 和 mean 的差异会很清楚。如果 p95 明显高于 mean说明存在长尾请求正是配置调优要解决的重点。6. 运行结果与效果验证跑完基线配置和调优配置后把两组的 p95 TTFT、p95 ITL 放在一起对比。这里给出一组示意输出只是为了展示记录格式不代表任何固定基准 基线配置 请求数: 200 p95 TTFT: 3421.5 ms p95 ITL: 158.2 ms 调优配置 请求数: 200 p95 TTFT: 1876.3 ms p95 ITL: 121.7 ms从形态上看调优配置的 p95 TTFT 和 p95 ITL 都低于基线。这符合预期因为调优配置在 KV cache 容量、并发上限、前缀缓存方面都做了针对性调整。判断实验是否有效的标准有几个第一p95 TTFT 是否下降。如果下降幅度明显说明调度排队和 prefill 效率得到了改善。第二p95 ITL 是否下降或者至少没有恶化。如果只降 TTFT但 ITL 大幅上升那么用户虽然能更快看到第一个字但后续生成更慢整体体验未必更好。第三成功率是否保持 100%。如果配置调得过激进可能会出现显存溢出或请求超时这种情况下延迟指标再好也没有意义。需要特别提醒的是不同模型、不同 prompt 分布、不同并发模型下最优配置是完全不同的。你不需要照抄某个配置而应该把实验方法跑通在自己的负载下找到平衡点。7. 常见问题与排查思路vLLM 部署和调优过程中问题往往集中在几个方向。下面的表格整理了最常见的现象、可能原因和排查方式。问题现象可能原因排查方式解决方案请求启动后长时间无响应TTFT 很高并发请求过多调度排队严重查看 vLLM 日志中请求排队信息观察 GPU 利用率下调 max-num-seqs或增加服务实例ITL 明显偏高模型逐字输出太慢禁用了 CUDA Graph或显存带宽竞争检查启动命令是否带 --enforce-eager去掉 --enforce-eager优先用其他方式省显存加载模型时显存不足max-model-len 过大KV cache 预算太大用 nvidia-smi 观察显存占用调小 max-model-len或降低 gpu-memory-utilization同一 prompt 反复请求TTFT 依然高前缀缓存未生效或命中率低检查 vLLM 日志中的 prefix cache 命中统计开启 enable-prefix-caching确保 system prompt 完全一致多卡跑模型时报 NCCL 错误驱动、NCCL 版本或通信库不匹配查看完整报错堆栈使用官方 Docker 镜像更新驱动Windows 下安装或启动失败vLLM 官方不支持 Windows确认操作系统和环境使用 WSL2 或 Linux 服务器昇腾 910b 上无法启动 embedding / reranker 模型后端适配范围有限确认 Ascend 后端支持的模型类型换用后端支持的模型架构或用更通用的任务执行方式另外要专门提一下--enforce-eager。它在显存不足时看起来是救命稻草因为 eager 模式省去了 CUDA Graph 捕获所需的部分显存但代价是 decode 性能下降。之前有不少团队因为单卡显存不够加了这个参数后让 ITL 翻倍最后发现反而是调小 max-model-len 更划算。8. 最佳实践与工程建议8.1 先定义 SLO再调配置没有目标就调参很容易陷入“调完看数字数字变了但不知道好不好”的窘境。建议先明确线上服务的延迟目标比如 p95 TTFT 小于 2000msp95 ITL 小于 120ms。有了 SLO每个配置改动的好坏就一目了然。8.2 单变量调整别一次改一堆配置项之间存在耦合。比如把 max-num-seqs 和 max-num-batched-tokens 同时调大TTFT 下降了但你不知道是哪个参数起了作用。规范的做法是每次只改一个变量记录前后结果形成一张实验记录表。8.3 压测数据要贴近线上压测时如果只用一两句短 prompt测出来的结果离真实体验差距很大。真实场景中用户输入长度不一system prompt 可能很长多轮对话还会累积历史。建议从线上日志抽样一批真实 prompt构造成压测数据集。这样测出来的 p95 才有参考价值。8.4 保持 CUDA Graph 开启除非显存实在不够否则不要轻易使用--enforce-eager。decode 阶段大量小 kernel 的启动开销在 eager 模式下会被成倍放大ITL 和 TPOT 都会显著恶化。8.5 关注前缀缓存命中率如果业务有固定 system prompt或者大量用户请求共享相同前缀前缀缓存是最值得优化的方向之一。新版 vLLM 通常默认开启 prefix caching但你可以通过日志或指标观察命中率。命中率低时检查 prompt 是否因为包含时间戳、随机数等不固定内容而导致前缀无法复用。8.6 配置变更要可回滚生产环境的配置变更最好通过环境变量或版本化的启动脚本管理而不是直接在服务器上手工改。一旦新的配置导致 p95 恶化可以快速回退到上一个稳定配置。8.7 监控指标用 vLLM metricsvLLM 服务默认会在端口 8000 暴露 Prometheus 指标路径是 /metrics。通过它可以看到吞吐、排队请求数、KV cache 使用率等信息。虽然 TTFT 和 ITL 不能完全靠内置指标拿到但排队请求数和 KV cache 使用率能帮你判断配置调整的方向是否正确。9. 总结与后续学习方向回到最初的问题为什么 config 能 beats baseline因为基线配置的目标是“跑通”而不是“在给定的并发和延迟目标下跑好”。vLLM 的调度器、KV cache 分配、前缀缓存、CUDA Graph 执行模式每一项都会显著影响 p95 TTFT 和 ITL。在 H100s 上硬件算力强显存大默认配置更不容易暴露问题但一旦进入高并发在线服务场景配置差异就会被成倍放大。这篇文章把 TTFT 和 ITL 的含义、vLLM 关键配置、实验设计方法和常见问题梳理了一遍。建议你先拿一个模型用基线配置跑一轮再用调优配置跑一轮对比 p95 数据形成自己的实验记录。如果还想深入可以继续研究 continuous batching 的调度机制、Prefix Caching 的命中率统计方式以及使用 vLLM 的 metrics 接口搭建延迟监控面板。等负载模型更复杂了还可以关注 PD 分离架构和更细粒度的 prefill/decode 资源隔离方案。
返回列表