资讯动态

H100上vLLM推理性能调优:降低p95 TTFT与ITL的配置实战

发布时间:2026/8/28 9:07:13 来源:尧图企业网站定制
这次我们来看一个很实在的话题在 H100 上跑 vLLM Serving为什么同样的模型、同样的算力别人家 p95 TTFT 和 ITL 就是比你低如果你已经在用 vLLM 部署大模型并且开始关注延迟的百分位分布而不是只看平均 latency那这篇文章可以直接帮你把推理服务性能再抠出一个档位。先说项目内容本身。vLLM 是目前主流的大模型推理与服务框架核心优势是 PagedAttention、continuous batching 和高效 KV Cache 管理。标题里的 config beats the baseline on p95 TTFT, ITL 想表达的核心观点是在 H100 这类高性能 GPU 上模型权重和显存往往不是瓶颈真正拉开差距的是 vLLM 的配置参数。同一个模型用默认配置启动和用针对长尾延迟调优后的配置启动p95 TTFT首 token 延迟和 p95 ITLtoken 间延迟可能相差很大。这篇文章会带你先理解 TTFT、ITL、p95 这些指标怎么算再讲 H100 环境下 vLLM 的部署和启动方式重点拆解影响 p95 性能的关键配置最后给出一套可复制的实验思路包括基准测试脚本、API 压测方法和显存观察手段。适合正在用 vLLM 做线上推理服务、或者准备做服务压测的性能工程师。1. 核心能力速览能力项说明项目类型大模型推理服务框架 vLLM 的部署与性能调优主要功能高并发 LLM 推理服务、OpenAI 兼容 API、批处理调度、KV Cache 管理推荐硬件H100 / A100 等大规模显存 GPU支持多卡张量并行显存需求取决于模型规模和 KV CacheH100 80GB 可覆盖主流 70B 以下模型启动方式命令行启动、Python 启动、Docker 启动是否支持 API支持内置 OpenAI 兼容 API是否支持批量任务支持 continuous batching天然支持并发请求队列调优重点max-num-seqs、max-num-batched-tokens、chunked prefill、scheduling policy、prefix caching 等配置适合场景大模型在线服务、性能压测、多用户并发推理、RAG 系统接入需要说明的是标题中的 H100s 是实验硬件平台实际调参思路同样适用于 A100、A800 等大显存 GPU。具体能达到什么 p95 数值必须结合模型版本、请求长度、并发模型和自己的显存配置来测不同环境没有统一答案。2. 先搞清楚 p95 TTFT 和 ITL 到底在衡量什么很多人在看 vLLM 性能报告时只关注 “Throughput tokens/s” 和 “Average latency”其实线上用户体感更接近百分位延迟。TTFTTime To First Token是从请求发出到收到第一个返回 token 的时间。它直接决定用户输入后多久能看到第一个字尤其是流式对话场景TTFT 过长会让人感觉“卡住没反应”。p95 TTFT 表示 95% 的请求 TTFT 小于这个值它比平均 TTFT 更能反映最差一批请求的体验。ITLInter-Token Latency是相邻两个输出 token 之间的时间间隔。它决定生成过程中的“连贯感”。如果 ITL 抖动大输出文字会忽快忽慢用户会明显感觉到不稳定。p95 ITL 关注的是大多数时间里 token 输出节奏是否稳定。为什么要特别关注 p95因为平均延迟容易被少数极快请求拉低也可能被少数慢请求抬高无法代表真实用户感受。在服务调优中p95 和 p99 是 SLA 的重要指标。vLLM 自身会输出 TTFT、ITL 等统计数据配合 Prometheus 指标可以完整观察分布。在 H100 上做优化时重点在于硬件很强正常负载下平均延迟都不差但一旦并发上来、请求变长、调度策略不合理p95 就会迅速恶化。配置调优的核心就是压低长尾延迟让绝大多数请求都稳定在可接受的延迟区间。3. 环境准备与前置条件3.1 硬件和驱动H100 属于 Hopper 架构推荐使用较新的 NVIDIA 驱动和 CUDA 环境。具体版本需要以 vLLM 官方文档和你的驱动版本为准。一个稳妥的组合是操作系统Ubuntu 22.04 或兼容 Linux 发行版GPUNVIDIA H100建议 80GB 显存版本CUDACUDA 12.x 及以上驱动满足 CUDA 12.x 要求的驱动版本如果使用多卡需要有 NVLink 或 PCIe 连接并且提前确认nvidia-smi能识别所有 GPU。3.2 Python 环境vLLM 依赖 PyTorch建议使用 Python 3.10 或 3.11创建独立的虚拟环境避免和系统环境冲突。python -m venv vllm-env source vllm-env/bin/activate3.3 安装 vLLMvLLM 提供了 pip 和 Docker 两种主流安装方式。如果网络环境正常pip 安装最快pip install --upgrade pip pip install vllm如果需要更灵活的依赖控制或者想基于 CUDA 12 运行推荐 Docker 方式docker pull vllm/vllm-openai:latest安装完成后可以先运行一个最简单的模型验证环境python -c from vllm import LLM; print(vLLM import OK)如果没有报错说明安装成功。安装过程中如果遇到torch版本冲突建议先在干净的虚拟环境里安装 vLLM它会自动拉取对应版本的 PyTorch。3.4 模型准备vLLM 支持 HuggingFace 格式模型也支持 Safetensors 格式。如果是国内环境建议提前把模型下载到本地路径再通过--model /path/to/model指定避免服务启动时反复拉取。以 Qwen 系列、Llama 系列模型为例模型目录中至少需要包含config.jsontokenizer.json 或 tokenizer.model模型权重文件如 pytorch_model.bin 或 model-00001-of-0000X.safetensors准备好模型后可以先用一个 7B 级别的模型做功能验证再用 70B 级别模型做性能压测。4. vLLM 启动方式与基础服务配置启动一个 OpenAI 兼容 API 服务是 vLLM 最常见的用法。命令如下vllm serve /path/to/model \ --port 8000 \ --host 0.0.0.0 \ --gpu-memory-utilization 0.9 \ --tensor-parallel-size 1 \ --max-model-len 8192参数说明--host 0.0.0.0允许远程访问如果只是本机测试可以改成127.0.0.1。--tensor-parallel-size多卡并行时设置例如 2 表示将模型切分到 2 张 GPU 上。--gpu-memory-utilization控制 vLLM 使用的显存比例0.9 表示最多使用 90% 显存留一部分给 CUDA context。--max-model-len最大序列长度影响 KV Cache 分配。启动成功后日志中会出现Uvicorn running on http://0.0.0.0:8000一类信息。此时可以访问/health检查服务状态curl http://127.0.0.1:8000/health返回OK表示服务正常。如果是多卡环境注意--tensor-parallel-size必须能被 GPU 数量整除且模型显存占用会按张量并行切分。H100 单卡 80GB 显存跑 70B 模型 4-bit 量化时很有优势但如果是 BF16 全精度建议用 2 卡或 4 卡并行。5. 影响 p95 TTFT 和 ITL 的关键配置这是本文的核心。vLLM 的默认配置更偏向提升吞吐而不是压平长尾延迟。要根据 p95 指标优化需要重点调整以下几类参数。5.1 max-num-seqs--max-num-seqs控制同一时刻可以同时处理的序列数量。默认值通常是 256。这个值越大并发能力越强但显存中 KV Cache 被分得越碎调度开销越高p95 延迟也会上升。如果服务主要面向在线交互场景建议把max-num-seqs调低比如 128 或 64。较小的并发上限可以让每个请求占用更连续的资源减少排队和抢占p95 TTFT 更可控。vllm serve /path/to/model \ --max-num-seqs 128 \ --max-num-batched-tokens 40965.2 max-num-batched-tokens这个参数控制一个 batch 内最多容纳的 token 数量。默认值可能比较大它会限制 single batch 的最大尺寸。如果经常出现长 prompt 请求较大的max-num-batched-tokens允许更长序列进入同一 batch但也会增加单次 prefill 的时间从而推高 TTFT。针对低 TTFT 场景可以适当减小该值。不过需要结合max-num-seqs一起调整避免并发上来后 batch 过小导致吞吐下降。5.3 chunked prefill分块预填充vLLM 从 0.4.x 开始支持 chunked prefill。它把长 prompt 的 prefill 阶段切分成多个 chunk交错执行 prefill 和 decode避免一个长请求独占 GPU。默认情况下如果请求的 prompt 很短chunked prefill 影响不明显但如果压测数据包含大量长文档、长上下文请求它几乎直接决定 p95 TTFT 的稳定性。启用方式vllm serve /path/to/model \ --enable-chunked-prefill也可以显式设置 chunk 大小vllm serve /path/to/model \ --enable-chunked-prefill \ --max-num-batched-tokens 2048启用后长 prefill 会被拆开执行GPU 不会长时间被单个请求占用其他短请求的 TTFT 会明显改善。代价是长请求自身完成时间变长但 p95 整体更平滑。5.4 scheduling policyvLLM 默认使用 FIFO 调度策略。如果所有请求排在一个队列里前面的慢请求会阻塞后面的快请求p95 很容易被长请求拖累。vLLM 提供了--scheduling-policy参数可选值一般是fcfs默认和priority。priority 模式会根据请求优先级调度。如果业务能区分重要请求和普通请求可以用 priority 策略保证高优请求优先插队降低 p95 TTFT。vllm serve /path/to/model \ --scheduling-policy priority注意priority 模式下需要在请求参数中指定优先级标记实际效果取决于你使用的 vLLM 版本和客户端支持程度。5.5 enable-prefix-caching如果线上请求经常包含相同的前缀比如系统提示词、固定上下文、RAG 的公共部分开启 prefix caching 可以显著减少 prefill 计算量。vllm serve /path/to/model \ --enable-prefix-caching开启后相同前缀的 KV Cache 会被复用用户请求的 prefill 时间变短TTFT 自然下降。这在多轮对话和 Agent 场景中效果非常明显。5.6 enforce-eager 的影响--enforce-eager会禁用 CUDA Graph改用 eager 模式执行。启动速度会变快但执行效率可能降低显存占用也会增加。理由是新版本 vLLM 会自动捕获 CUDA Graph 加速推理默认行为通常比 eager 好。如果压测中发现 p95 延迟偏高先不要开--enforce-eager除非你遇到 CUDA Graph 导致显存不足或模型兼容性问题。该参数更适合排查启动报错不适合做性能调优。5.7 KV Cache 和显存分配--gpu-memory-utilization影响 KV Cache 可用的显存总量。设得太低KV Cache 不足会频繁触发 preemptionp95 延迟飙升设得太高可能没有足够显存给 CUDA Context 和临时张量导致 OOM。适合的起点是 0.85~0.92。如果服务启动时显存不足可以降低到 0.8如果压测时出现Killed或者 OOM 日志优先降低该值。如果想观察 KV Cache 使用情况可以查看 vLLM 启动日志中的 KV Cache size 信息例如INFO 07-31 12:00:00 model_runner.py:808] GPU KV cache size: 5000 tokens这个数字高表示 KV Cache 空间充足低则可能需要调高显存利用率或减小max-model-len。6. 基准测试与实验设计如何验证 config 优于 baseline“config beats the baseline” 这句话不能空口说需要一组可复现的对比实验。下面给出一套通用实验流程。6.1 确定 baselineBaseline 指使用 vLLM 默认配置启动服务除必要参数模型路径、端口外不做任何调优。记录此时 p95 TTFT 和 p95 ITL。6.2 确定 tuned config基于第 5 节的参数选择一组针对低延迟调优的配置。建议一次只修改一个变量避免多变量混杂。一个典型的 tuned config 示例vllm serve /path/to/model \ --max-num-seqs 64 \ --max-num-batched-tokens 2048 \ --enable-chunked-prefill \ --enable-prefix-caching \ --gpu-memory-utilization 0.9 \ --scheduling-policy fcfs6.3 压测工具与采样方法压测请求需要模拟真实业务负载。可以写一个 Python 脚本并发发送多个请求记录每个请求的 TTFT 和 ITL。以下脚本使用openai库从 vLLM 的 OpenAI 兼容接口获取流式输出并统计指标import asyncio import time import numpy as np from openai import AsyncOpenAI client AsyncOpenAI( base_urlhttp://127.0.0.1:8000/v1, api_keyEMPTY ) async def single_request(prompt: str, max_tokens: int 256): start time.perf_counter() first_token_time None tokens 0 buffer stream await client.chat.completions.create( model/path/to/model, messages[{role: user, content: prompt}], max_tokensmax_tokens, streamTrue ) async for chunk in stream: delta chunk.choices[0].delta.content if delta: if first_token_time is None: first_token_time time.perf_counter() - start buffer delta tokens 1 total_time time.perf_counter() - start itl (total_time - first_token_time) / max(tokens - 1, 1) return { ttft: first_token_time, itl: itl, total: total_time, tokens: tokens } async def run_benchmark(concurrency: int 16, requests: int 200): prompts [ 请写一篇关于大模型推理优化的文章字数不少于800字。, Explain the concept of KV cache in large language models., What are the differences between TTFT and ITL in LLM serving? ] results [] sem asyncio.Semaphore(concurrency) async def worker(i): async with sem: prompt prompts[i % len(prompts)] results.append(await single_request(prompt)) await asyncio.gather(*[worker(i) for i in range(requests)]) ttfts [r[ttft] for r in results if r[ttft] is not None] itls [r[itl] for r in results if r[itl] is not None] print(fTTFT p50: {np.percentile(ttfts, 50):.4f}s) print(fTTFT p95: {np.percentile(ttfts, 95):.4f}s) print(fITL p50: {np.percentile(itls, 50):.4f}s) print(fITL p95: {np.percentile(itls, 95):.4f}s) asyncio.run(run_benchmark(concurrency16, requests200))注意脚本中的model参数要填服务启动时的模型名或路径。压测时建议先跑 20 个请求预热再正式记录数据。6.4 对比维度对比 baseline 和 tuned config 时至少记录以下指标TTFT p50 / p95 / p99ITL p50 / p95 / p99总吞吐 tokens/s显存占用峰值请求失败率如果 tuned config 的 p95 TTFT 下降但 p95 ITL 上升需要继续调整 batch 大小和 chunked prefill 参数。通常可在吞吐小幅下降的代价下换取延迟稳定。7. 接口 API 调用与批量任务vLLM 提供 OpenAI 兼容接口/v1/chat/completions和/v1/completions均可使用。对于非流式请求可以直接用requestsimport requests import json url http://127.0.0.1:8000/v1/chat/completions payload { model: /path/to/model, messages: [{role: user, content: 你好请介绍一下 vLLM}], max_tokens: 100, temperature: 0.2, stream: False } response requests.post(url, jsonpayload, timeout60) data response.json() print(data[choices][0][message][content])流式调用可以使用 SSE 格式curl -N http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: /path/to/model, messages: [{role: user, content: 用三句话介绍 vLLM}], max_tokens: 128, stream: true }批量任务方面vLLM 没有独立的“批量作业菜单”而是通过持续发送请求由 continuous batching 自动合并优化。前端可以按照业务需求控制并发数例如用 Python 的concurrent.futures.ThreadPoolExecutor同时发送多个请求import concurrent.futures import requests def call_api(prompt): resp requests.post( http://127.0.0.1:8000/v1/chat/completions, json{ model: /path/to/model, messages: [{role: user, content: prompt}], max_tokens: 200, temperature: 0.1 }, timeout120 ) return resp.json() prompts [题目1模型调优, 题目2显存管理, 题目3API 设计] with concurrent.futures.ThreadPoolExecutor(max_workers8) as executor: results executor.map(call_api, prompts) for result in results: print(result[choices][0][message][content][:50])生产环境做批量任务时建议在任务层加超时重试和数据落盘避免服务重启导致任务丢失。8. 资源占用与性能观察8.1 显存观察在服务运行期间可以用nvidia-smi观察显存占用watch -n 1 nvidia-smi重点看Memory-Usage和GPU-Util。vLLM 会预分配固定比例的显存作为 KV Cache所以服务启动后显存占用可能直接接近设定上限这属于正常现象。如果显存占用比gpu-memory-utilization设置值低很多说明模型较小或 KV Cache 分配不足。如果显存占满并且频繁 OOM则需要降低显存利用率或减小max-model-len。8.2 vLLM 内置 metricsvLLM 启动时增加--enable-metrics和--metrics-port可以暴露 Prometheus 指标vllm serve /path/to/model \ --enable-metrics \ --metrics-port 8001访问http://127.0.0.1:8001/metrics可以看到vllm:num_requests_running、vllm:num_requests_waiting、vllm:cache_usage等指标。通过这些指标可以直接判断是队列积压还是资源耗尽导致 p95 升高。8.3 CPU 与 GPU 差异H100 场景下 CPU 不是主要瓶颈但大量请求的 tokenizer 和调度仍会消耗 CPU。如果压测发现 GPU 利用率不高但 p95 高可以先观察 CPU 使用率可能需要调整max-num-seqs减少过度调度或者增加 worker 数量。8.4 降低显存占用的通用方法使用量化模型AWQ、GPTQ、FP8vLLM 原生支持部分量化格式。降低--max-model-len限制最大序列长度。减少并发数--max-num-seqs。开启--enforce-eager排查显存问题但生产环境不建议长期开启。9. 常见问题与排查方法问题现象可能原因排查方式解决方案服务启动后页面打不开端口被占用或服务未启动查看启动日志确认 Uvicorn 线程是否监听端口换端口--port 8001或杀掉占用进程p95 TTFT 持续偏高并发请求过多、max-num-seqs 过大、长 prompt 阻塞短请求查看num_requests_waiting指标调低 max-num-seqs开启 chunked prefill开启 prefix cachingp95 ITL 抖动明显batch 内请求长度差异大、KV Cache 预分配不足观察 GPU 利用率曲线和cache_usage调整 max-num-batched-tokens启用 chunked prefill检查显存利用率显存不足或 OOMgpu-memory-utilization 过高或 max-model-len 过大看启动日志和 nvidia-smi 峰值调低gpu-memory-utilization或max-model-len尝试量化模型接口超时或无法调用API 路径错误、服务未就绪、请求体超长curl 测试 /v1/models 和 /health确认模型路径和服务状态检查超时与 max-model-len 限制批量任务卡住并发数过高导致排队、请求超时查看日志和 pending 请求数降低并发增加超时重试拆分大请求CUDA Graph 相关报错模型与 CUDA Graph 兼容性差查看完整报错堆栈临时添加--enforce-eager验证或升级 vLLM 版本CPU 占用过高tokenizer 和调度开销密集观察 top 进程减少max-num-seqs增加机器 CPU 核数如果使用 Docker 部署注意端口映射和共享内存设置。vLLM 在容器中运行时建议添加--shm-size10g避免共享内存不足导致异常退出。10. 最佳实践与使用建议在 H100 上做 vLLM Serving 性能优化有几个工程层面的经验值得直接采用。第一第一次压测一定要小规模预热。刚启动的服务内部缓存未生效CUDA 模块也处于冷启动状态直接压测得到的数据偏低。建议先发 20~50 个简单请求等 GPU 利用率稳定后再记录正式数据。第二保留 baseline 和 tuned config 两份启动配置。可以把配置写入脚本或 YAML 文件方便切换复现。比如# baseline.sh vllm serve /path/to/model --port 8000# tuned.sh vllm serve /path/to/model \ --port 8000 \ --max-num-seqs 64 \ --max-num-batched-tokens 2048 \ --enable-chunked-prefill \ --enable-prefix-caching \ --gpu-memory-utilization 0.9这样每次实验都有明确的配置快照不会出现“上次调的什么参数忘了”的尴尬。第三调参时一次只改一个变量。虽然标题说 config beats baseline但这个结论必须建立在变量可控的基础上。你可以在同一份压测目录下保存多组指标文件命名包含 config 标识方便归类。第四关注 p95 的同时也要守底线。如果为了压低 p95 TTFT 把max-num-seqs压得太低整体吞吐会明显下降服务成本变高。建议根据业务 SLA 求一个平衡点比如要求 p95 TTFT 1sp95 ITL 100ms则在这两个约束下最大化吞吐。第五合规和安全边界必须明确。无论是本地实验还是线上服务模型训练和推理数据的来源要合法涉及用户数据、人脸、声音、版权文档等内容时必须获得授权。接口服务如果部署在公网要加鉴权或绑定内网避免被恶意调用消耗算力。11. 总结与下一步这个项目最值得尝试的点就是同一套 vLLM 环境通过调整配置把 p95 TTFT 和 ITL 压下来。先验证 vLLM 能正常启动然后跑一轮 baseline 压测接着按第 5 节的参数逐项调整再跑一轮对比拿到自己的 p95 曲线。最容易踩的坑是盯着平均延迟不放、忽略长尾请求以及多个参数一起改导致问题定位困难。后续可以扩展的方向包括对比sglang和 vLLM 在同类场景下的 p95 表现把 vLLM 接入 Prometheus Grafana 做实时监控在昇腾 910B 等非 NVIDIA 硬件上验证配置差异或者用 vLLM 的 Function Calling 能力接入 Agent 系统进一步测试复杂提示词下的 TTFT 稳定性。建议把这份实验流程保存成脚本之后换模型、换 GPU 时直接复用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价