1. 为什么我要把三套推理框架放在同一台机器上跑做 LLM 服务选型时最容易踩的坑不是框架本身不好而是对比基准不一致。我见过太多团队拿 vLLM 的官方 benchmark 数据去对标 TGI 的生产日志结果发现吞吐差了 3 倍最后排查半天才发现是 batch size、并发数、输入输出长度全对不上。这种对比没有意义。这篇内容面向的是需要在本地或云端做推理框架选型的工程师。核心目标很明确用同一套硬件、同一组压测参数、同一个模型权重把 vLLM、TensorRT-LLM、TGI 三套框架的吞吐、首 token 延迟、显存占用跑出来让你拿到可以直接复现的数字而不是看别人转述的二手结论。三套框架的定位差异决定了它们的性能曲线形状完全不同。vLLM 靠 PagedAttention 做显存分页管理高并发下吞吐爬升最猛TensorRT-LLM 把算子融合和量化做到极致单卡低延迟场景优势明显TGI 的持续批处理调度器在请求长度参差不齐时表现最稳。但这些结论必须用你自己的模型和流量特征验证因为 7B 和 70B 的最优配置可能完全相反。压测过程中还有一个容易被忽略的环节模型下载和权重管理。三套框架对模型格式的要求不同TensorRT-LLM 需要先做 engine 编译TGI 对 safetensors 支持最好vLLM 则兼容 HuggingFace 原生格式。如果每次换框架都要重新走一遍鉴权和下载流程效率极低。我的做法是用 TaoToken 统一管理 API Key把模型访问和推理服务的鉴权层解耦这样切换框架时只需要改启动参数不用动凭证配置。下面从环境准备开始一步步给出三套框架的可复制配置和压测脚本。2. TaoToken 前置统一 Key 管理让三套框架共用一套凭证2.1 为什么推理压测也需要统一 Key很多人觉得压测就是本地起服务、本地发请求不需要外部凭证。但实际场景里你的推理服务往往需要拉取模型权重、调用 tokenizer 服务、或者对接上游网关做鉴权。如果每个框架都配一套独立的 Key切换时就要反复改环境变量容易出错。TaoToken 在这里的角色是统一凭证层。你可以在控制台创建一组 API Key然后让 vLLM、TensorRT-LLM、TGI 的启动脚本都从同一个环境变量读取。这样做的另一个好处是当你要把压测环境从本地迁移到云端时只需要在云端注入同一个 Key不用重新配置三套框架的鉴权逻辑。2.2 获取 Key 和配置环境变量先到控制台创建 Key地址是 https://taotoken.net/console 。创建时建议按用途命名比如bench-vllm、bench-trtllm、bench-tgi方便后续排查是哪个框架的请求出了问题。拿到 Key 之后在压测机器的 shell 里统一注入export TAOTOKEN_API_KEYsk-你的实际Key export TAOTOKEN_BASE_URLhttps://taotoken.net/api如果你用 Docker 跑推理服务启动时把这两个变量传进去docker run --gpus all \ -e TAOTOKEN_API_KEY$TAOTOKEN_API_KEY \ -e TAOTOKEN_BASE_URL$TAOTOKEN_BASE_URL \ -v /models:/models \ your-inference-image注意不要把 Key 硬编码在 Dockerfile 或启动脚本里。用环境变量注入配合.env文件做本地管理.env记得加进.gitignore。2.3 验证 Key 可用性在正式跑压测之前先用一个最小请求确认 Key 能通curl -s $TAOTOKEN_BASE_URL/v1/models \ -H Authorization: Bearer $TAOTOKEN_API_KEY | head -c 500如果返回模型列表说明 Key 和网络都没问题。这一步花 10 秒能避免后面压测跑了一半才发现鉴权失败。关于接入文档和更多参数说明可以看 https://taotoken.net/doc 。模型对话的调试入口在 https://taotoken.net/chat 压测前想快速确认模型行为是否正常可以先在那里发几条请求看看输出质量。3. 三套框架的可复制启动配置3.1 vLLM 启动配置与参数说明vLLM 的启动命令相对简洁核心参数是--tensor-parallel-size和--gpu-memory-utilization。我用的测试模型是 Llama-2-7B-chat单卡 A100 80GB 场景python -m vllm.entrypoints.openai.api_server \ --model /models/Llama-2-7b-chat-hf \ --tensor-parallel-size 1 \ --gpu-memory-utilization 0.90 \ --max-model-len 4096 \ --dtype float16 \ --port 8000 \ --api-key $TAOTOKEN_API_KEY--gpu-memory-utilization 0.90表示预留 90% 显存给 KV Cache这个值在压测时很关键。设太低会导致并发上不去设太高可能 OOM。实测 7B 模型在 A100 上设 0.90 比较稳。--max-model-len要和你的压测输入长度匹配。如果你压测的 prompt 最长 2048 token输出最长 512 token那设 4096 足够。设太大浪费显存设太小会截断请求。多卡场景改--tensor-parallel-size 2或 4vLLM 会自动做张量并行。注意多卡时--gpu-memory-utilization是每张卡的利用率不是总和。3.2 TensorRT-LLM 编译与启动TensorRT-LLM 的流程比 vLLM 多一步 engine 编译。先把 HuggingFace 权重转成 TensorRT-LLM 格式python convert_checkpoint.py \ --model_dir /models/Llama-2-7b-chat-hf \ --output_dir /models/trtllm/llama2-7b-ckpt \ --dtype float16然后用trtllm-build编译 enginetrtllm-build \ --checkpoint_dir /models/trtllm/llama2-7b-ckpt \ --output_dir /models/trtllm/llama2-7b-engine \ --gemm_plugin float16 \ --max_batch_size 64 \ --max_input_len 2048 \ --max_output_len 512 \ --max_beam_width 1--max_batch_size决定了 engine 支持的最大并发 batch。压测时如果并发超过这个值请求会排队。建议先按预期峰值并发设比如你打算压到 64 并发就设 64。编译完成后启动服务python -m tensorrt_llm.serve \ --engine_dir /models/trtllm/llama2-7b-engine \ --tokenizer_dir /models/Llama-2-7b-chat-hf \ --port 8001 \ --api_key $TAOTOKEN_API_KEYTensorRT-LLM 的 engine 编译时间较长7B 模型大概 10-20 分钟。但编译是一次性的后续启动直接加载 engine秒级完成。3.3 TGI 启动配置TGI 的启动最接近“开箱即用”对 HuggingFace 模型格式支持最好docker run --gpus all \ -p 8002:80 \ -v /models:/data \ -e TAOTOKEN_API_KEY$TAOTOKEN_API_KEY \ ghcr.io/huggingface/text-generation-inference:latest \ --model-id /data/Llama-2-7b-chat-hf \ --max-input-length 2048 \ --max-total-tokens 4096 \ --max-batch-prefill-tokens 4096 \ --max-concurrent-requests 128 \ --dtype float16--max-concurrent-requests是 TGI 的关键参数控制同时处理的请求数。设太小吞吐上不去设太大可能导致显存溢出。128 是我在 A100 上跑 7B 模型的经验值你可以根据实际显存调整。--max-batch-prefill-tokens控制 prefill 阶段的最大 token 数和输入长度分布有关。如果压测输入普遍较短可以调低这个值来节省显存。三套框架的端口我分别用了 8000、8001、8002方便同时启动做对比。实际压测时建议一次只跑一个避免显存争抢影响数据。4. 压测脚本与验证请求4.1 用 locust 写统一压测脚本三套框架都兼容 OpenAI 风格的/v1/completions接口所以可以用同一套压测脚本。我用 locust 写了一个最小可用的版本from locust import HttpUser, task, between import os API_KEY os.environ[TAOTOKEN_API_KEY] class InferenceUser(HttpUser): wait_time between(0.1, 0.5) task def completions(self): headers { Authorization: fBearer {API_KEY}, Content-Type: application/json } payload { model: llama-2-7b-chat, prompt: Explain the difference between paged attention and continuous batching in one paragraph., max_tokens: 256, temperature: 0.7 } self.client.post(/v1/completions, jsonpayload, headersheaders)把self.client的 host 指向对应框架的端口比如 vLLM 是http://localhost:8000TGI 是http://localhost:8002。启动压测locust -f bench.py --headless -u 64 -r 8 --run-time 5m \ --host http://localhost:8000-u 64是模拟 64 个并发用户-r 8是每秒启动 8 个用户--run-time 5m跑 5 分钟。这三个参数决定了压测的强度曲线建议先用小并发跑 1 分钟确认服务稳定再逐步加并发。4.2 采集吞吐和延迟数据locust 的 headless 模式会在结束后输出汇总。但为了拿到更细的 P50/P95/P99 延迟我建议同时开一个 Prometheus 抓取各框架的 metrics 端点vLLM:http://localhost:8000/metricsTensorRT-LLM:http://localhost:8001/metricsTGI:http://localhost:8002/metrics关键指标是request_success_total、time_to_first_token_seconds、time_per_output_token_seconds。这三个指标能直接反映吞吐和延迟。显存占用用nvidia-smi定时采样nvidia-smi --query-gpumemory.used,memory.total \ --formatcsv -l 5 gpu_mem_vllm.csv-l 5表示每 5 秒采样一次。压测跑完后用 pandas 算峰值和均值。4.3 验证请求是否真正成功压测脚本跑完不代表请求都成功了。一定要检查返回内容curl -s http://localhost:8000/v1/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: llama-2-7b-chat, prompt: What is PagedAttention?, max_tokens: 128 } | python -m json.tool确认返回的choices[0].text是连贯的文本而不是空字符串或报错信息。有些框架在显存不足时会静默返回空结果压测数据看起来吞吐很高实际全是无效请求。我试过在 TGI 上把--max-concurrent-requests设到 256结果部分请求返回 503但 locust 默认把 503 也算作成功响应。后来在脚本里加了状态码断言才抓到这个问题。所以压测脚本里最好加一行assert self.client.post(...).status_code 200这样非 200 的请求会被标记为失败不会污染吞吐数据。5. 本篇常见错排查5.1 vLLM 启动报 CUDA out of memory最常见的原因是--gpu-memory-utilization设太高或者--max-model-len设太大导致 KV Cache 预留过多。先降到 0.85 试试如果还不行就检查是否有其他进程占用显存nvidia-smi fuser -v /dev/nvidia*另一个隐蔽原因是--dtype设成了autovLLM 可能加载成 float32 导致显存翻倍。显式指定--dtype float16或--dtype bfloat16。5.2 TensorRT-LLM engine 编译失败编译失败通常看两个地方一是trtllm-build的日志里有没有Unsupported operator二是 CUDA 版本和 TensorRT 版本是否匹配。TensorRT-LLM 对版本很敏感建议用官方 Docker 镜像而不是自己配环境。如果报max_batch_size相关错误检查--max_batch_size是否超过了 GPU 显存能承载的上限。7B 模型在 A100 上设 64 一般没问题设 256 就可能编译不过。5.3 TGI 请求超时或返回 503TGI 的--max-concurrent-requests是硬限制超过就返回 503。压测时如果并发数设得比这个值高就会看到大量 503。解决办法是把--max-concurrent-requests调到比压测并发高 20% 左右或者降低压测并发。另一个原因是--max-total-tokens设太小输入加输出超过了这个限制。检查你的压测 prompt 长度和max_tokens之和是否在限制内。5.4 三套框架的 tokenizer 行为不一致同一个 prompt三套框架返回的 token 数可能不同。这是因为 tokenizer 版本或配置有差异。压测时如果发现某套框架的吞吐异常高先检查是不是 tokenizer 把输入截断了。统一 tokenizer 的方法是显式指定 tokenizer 路径--tokenizer /models/Llama-2-7b-chat-hf三套框架都支持这个参数确保它们加载的是同一份 tokenizer 文件。5.5 TaoToken Key 在压测中突然失效如果压测跑到一半开始大量 401先检查 Key 是否过期或被限流。到控制台看用量面板确认没有触发配额上限。另外检查环境变量是否被 shell 会话覆盖echo $TAOTOKEN_API_KEY | head -c 8确认输出的前缀和你在控制台看到的一致。如果用了多个终端窗口注意每个窗口都要重新 export。6. 选型建议与后续压测入口跑完三套框架的压测后选型决策其实取决于你的流量特征。如果请求长度分布集中、并发高vLLM 的 PagedAttention 优势最明显如果对首 token 延迟极度敏感、且模型固定TensorRT-LLM 的编译优化值得投入如果团队人手有限、需要快速上线TGI 的运维成本最低。压测数据只是参考真正做决定前建议用你的真实流量回放一遍。TaoToken 的 API Key 可以同时用于三套框架的鉴权切换时只需要改启动命令里的端口和 engine 路径。需要创建新 Key 做多环境隔离的话入口在 https://taotoken.net/api-keys 。如果你打算把压测脚本接入 CI 做长期性能回归Coding Plan 提供了更灵活的调用配额适合持续集成场景https://taotoken.net/coding-plan 。最后提醒一点压测完记得把三套服务都停掉A100 的显存不会自动释放。用nvidia-smi确认没有残留进程再开始下一轮测试。