news 2026/9/4 6:36:32

LLM推理性能基准测试指南:关键指标、压测方法与工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LLM推理性能基准测试指南:关键指标、压测方法与工程实践

在接触大语言模型(LLM)推理优化、模型选型或服务化部署时,很多人会遇到一个问题:模型在单卡上看起来跑得很快,但一旦上线给多个用户并发使用,响应就变得很慢;换一个推理框架后,同样的模型性能差异却很大。要解释清楚这些现象,不能靠“感觉”,必须依赖一套统一的测试方法和量化指标,这就是 LLM Inference Benchmarking。本文将梳理推理基准测试的核心概念、常用指标、主流工具和完整测试思路,帮助你在模型部署前后能用数据做决策。

这篇文章适合两类读者:一类是刚开始接触 LLM 服务化部署,想知道怎么评测推理性能的新手;另一类是在实际项目里被响应延时、吞吐量、显存占用等问题困扰,希望建立一套可复用评测流程的开发者。读完你会理解推理基准测试到底要测什么、怎么设计测试场景、如何用脚本压测服务,以及如何避免只盯着某个单一指标而陷入优化陷阱。

1. 为什么要单独讨论“LLM 推理基准测试”

1.1 训练与推理的性能评估是两回事

大模型领域经常提到 “Benchmark”,比如模型排行榜上常见的 MMLU、GSM8K、HumanEval。很多人会把它们和推理性能测试混在一起,但实际上它们解决的问题完全不同。

  • 模型能力评测(Evaluation):关心模型“回答得对不对”,比如做数学题的正确率、代码生成能不能通过单测。这种评测需要固定权重,用一批标准化测试集来打分,结果体现的是模型的知识储备和泛化能力。
  • 推理性能评测(Inference Benchmarking):关心模型“回答得快不快、占不占资源、能不能支撑业务量”,比如首字延迟多长、每秒能生成多少 Token、能支持多少并发请求、显存占用是否在可控范围内。

这两种评估在模型选型阶段缺一不可。一个准确率很高的模型,如果推理速度慢到无法接受,即使放上生产环境也可能需要额外付出大量工程成本;反过来,一个推理速度很快但能力很弱的模型,同样无法满足业务要求。

1.2 推理基准测试要解决的典型业务问题

在实际项目中,推理基准测试通常在几个环节发挥作用:

  • 模型选型对比:同样参数量、不同架构的模型,哪一个更适合部署在现有 GPU 上。
  • 推理框架选型:vLLM、TensorRT-LLM、SGLang、Ollama 等框架对同一模型的加速效果差异很大,只有通过测试才能做选择。
  • 硬件容量规划:预计日请求量较大时,需要估算单卡 QPS 能到多少、平均响应时间是否满足服务质量要求,从而决定买几张 GPU。
  • 服务端参数调优:比如 batch size、max-token 限制、并行度、量化精度,这些参数的变化会让性能出现明显波动。
  • 线上问题排查:当用户反馈模型“转圈很久”时,需要区分是模型推理慢、网络开销大,还是检索流程、外部 API 占用了太多时间。

归根结底,推理基准测试不是一项活动,而是一个工程化手段,用数据告诉你模型在特定软硬件条件下、特定负载形态下的真实表现。

1.3 性能结果不可直接复用的原因

很多人在别的博客或仓库里看到一个测速数据,以为自己部署后也能同样达到,结果往往达不到预期。原因在于,大模型推理性能与以下变量强相关:

  • GPU 型号与显存带宽:对生成式模型来说,显存带宽严重影响 Token 生成速度。
  • 模型量化方式:FP16、INT8、INT4 的速度和显存占用完全不同。
  • 输入和输出长度:输入越长,首字延迟越高;输出越长,单次请求总耗时越高。
  • 并发请求数:并发增加时,吞吐量会上升,但响应延迟也可能变长。
  • 推理框架和调度策略:是否有 Continuous Batching、PagedAttention 等优化,直接影响吞吐量。

因此,推理基准测试的第一步不是寻找一个“普适数字”,而是先定义清楚测试条件,再在同等条件下做横向对比。

2. 核心性能指标:从首字延迟到吞吐量

2.1 首 Token 延迟(Time To First Token,TTFT)

首 Token 延迟指的是客户端发起请求到收到第一个生成 Token 的时间。它直接影响用户的“体感等待时间”。在交互式聊天场景中,TTFT 尤其重要;如果用户发送一句话后超过几秒还没有任何反馈,体验会明显变差。

TTFT 为什么和总延迟不同?因为 LLM 解码过程分为两个阶段:

  • Prefill(预填充)阶段:处理用户输入的 Prompt,得到中间状态。输入越长,这个阶段计算量越大。
  • Decode(解码)阶段:逐个 Token 生成输出。第一个 Token 在 Prefill 完成后才产生,因此 TTFT 主要由 Prefill 耗时和调度排队时间决定。

2.2 Token 生成延迟与每个输出 Token 时间(Time Per Output Token,TPOT)

TPOT 表示生成一个输出 Token 的平均耗时。它和模型参数量、显存带宽、量化方式、Batch 大小密切相关。假设模型正在处理一个 Batch,Batch 越大,平均每个请求分到的算力比重就会下降,TPOT 可能上升。

用户侧看到的“打字机速度”通常由 TPOT 决定。如果 TPOT 是 50ms,那么模型大约每秒输出 20 个 Token,已经比较流畅;如果 TPOT 是 200ms,每秒只输出 5 个 Token,用户就会感觉生成非常慢。

2.3 端到端总延迟(End-to-End Latency)

端到端总延迟 = TTFT + 后续生成所有 Token 的时间 + 网络传输时间。在测试时需要注意区分:

  • 只测推理服务的指标,通常以模型服务内部日志为准。
  • 测完整业务链路则包括网关、鉴权、外部工具调用、网络等。

两者都值得测,但解读时要分开。若完整链路慢了,先排查哪个环节时间占比最高,再决定优化方向。

2.4 吞吐量(Throughput,通常用 tokens/s 表示)

吞吐量大体验证的指标。常见两种口径:

  • 单请求吞吐量:一个请求内模型每秒生成的 Token 数量,等于 1000 / TPOT(单位 ms 时)。
  • 系统吞吐量:在一段时间内,模型服务处理的所有输出 Token 总量除以时间。系统吞吐量更能反映 GPU 的利用效率,因为它把并发请求合在一起计算。

用公式表示:

系统吞吐量(tokens/s)= 总生成 Token 数 / 总耗时(s)

如果增加并发请求,单请求生成速度可能会变慢,但系统吞吐量往往先上升,到达饱和点后趋于平缓甚至下降。因此,压测时要画出“并发数-吞吐量”曲线,而不只是记录最大吞吐量。

2.5 每秒请求数(QPS / RPS)与成功率

在业务侧,大家更习惯用 QPS 来描述系统能承受多高的请求量。例如某客服机器人服务需要支持 20 QPS,如果模型处理一个请求平均需要 5 秒,那么 20 QPS 意味着同时约有 100 个请求在排队或被处理。这个简单的换算关系经常被忽略。

成功率同样重要。高并发下很容易出现请求超时、连接失败、显存溢出退出。压测时要把 99% 响应时间、超时率、错误率都记录下来,单纯看平均延迟会掩盖极端情况。

2.6 显存占用与设备利用率

显存占用决定模型能否在一张卡上跑起来。同一个模型使用不同的推理框架、不同量化方式,显存占用可能差距很大。除了模型权重本身,KV Cache、中间计算图、框架运行时也会占显存。高并发下 KV Cache 增长很快,如果超过显存上限,会出现 OOM 或请求排队。

设备利用率主要看 GPU 算力利用率和显存带宽利用率。通过nvidia-sminvtop或 NVIDIA DCGM 可以观测。

2.7 成本指标:每 Token 成本

在云上部署时,成本是一个不可回避的指标。成本可以表示为:

  • 单位时间 GPU 租用成本 / 系统吞吐量,得到“每个 Token 的成本”。
  • 如果是按 GPU 实例小时计费,先估算处理一百万 Token 需要多少小时。

这样你就能用一个可量化的方式回答“换一个更大的模型是否划算”这类问题。

3. 评测对象与测试工具选型

3.1 评测对象:模型、框架、硬件如何拆解

一次推理基准测试可以只测“模型 + 框架”的表现,也可以测“模型 + 框架 + 硬件”的整体方案。建议先把变量拆开:

模型权重版本(含量化方式) + 推理框架(vLLM / TensorRT-LLM / SGLang / llama.cpp / Ollama) + 框架参数(max batch、并发、KV Cache策略、调度策略) + 硬件环境(GPU型号、驱动、CUDA版本) + 负载模型(Prompt长度、输出长度、并发模式) → 性能指标

做横向对比时,至少保证硬件一致、负载一致,然后只改一个变量,例如框架,或量化精度。否则得到的结果很难说明问题是出在框架、模型还是硬件。

3.2 主流推理框架概览

开源社区里常见的推理框架各有侧重点:

  • vLLM:使用 PagedAttention 和 Continuous Batching,吞吐量高,提供 OpenAI 兼容 API,部署方便,是目前最流行的自托管推理服务之一。
  • TensorRT-LLM:NVIDIA 推出的推理优化框架,把模型编译成 TensorRT Engine,能达到很强的延迟和吞吐优化,但构建流程相对复杂,需要处理 Graph 优化、算子融合、KV Cache 参数配置等。
  • SGLang:主打复杂结构化输出和高效调度,在多轮对话、并行采样方面有优化。
  • llama.cpp:以 CPU/GPU 混合推理和量化支持为特色,适合本地运行、边缘设备或在显存受限环境测试。
  • Ollama:把模型运行封装成非常简单的命令,适合个人开发环境快速试玩,但要做深度性能调优时,参数灵活性不如 vLLM 这类专业推理框架。

框架选择要看部署目标。如果是做企业内部高并发服务,一般会选 vLLM、TensorRT-LLM 或云厂商托管推理服务;如果是个人电脑上搭建知识库、跑完演示,Ollama、llama.cpp 更轻量。不同框架的适合场景不同,因此基准测试的脚本也应当尽量贴合部署方式。

3.3 自动评测工具:从生成式指标到稳定性记录

除了自己写脚本模拟请求,也可以借助已有工具:

  • lm-evaluation-harness:更多是做模型能力评测,如跑 MMLU、GSM8K 等数据集,但它也要求模型服务具备很高的吞吐能力,能间接反映框架的稳定程度。
  • vLLM 自带的 Benchmark 脚本benchmark_serving.py等脚本已经封装了常用拍平过程,支持固定的请求数量和并发模式,适合快速摸底。
  • 负载压测工具:如 Locust、k6、wrk 等,用于模拟 HTTP 并发。如果模型服务提供 OpenAI 兼容接口,也可以用这些通用工具直接打请求。
  • 自建压测脚本:最灵活,可以根据业务日志构造请求分布,并对结果做详细统计。

需要提醒的是,不要只依赖某一类工具。工具给的是理想场景下的测试结果,并不能替代基于真实业务请求形态的压测。

4. 环境准备与测试设计

4.1 基础设施准备

在进行推理基准测试前,建议先搭建一套干净的测试环境:

  • 准备一台 GPU 服务器,记录 GPU 型号、驱动版本、CUDA 版本。
  • 安装 Python 虚拟环境,避免依赖冲突。
  • 准备自己的模型权重,或使用 Hugging Face 上可下载的开源模型。
  • 安装目标推理框架,按照官方文档部署。

示例环境说明如下,具体版本需要根据自己的环境调整:

操作系统:Linux(Ubuntu 22.04) GPU:NVIDIA A100 / RTX 4090 等(按实际环境为准) 驱动:NVIDIA Driver 最新稳定版与 CUDA 工具包 Python:3.10+ 模型示例:Qwen/Qwen2.5-7B-Instruct(开源权重) 推理框架:vLLM 压测工具:Python requests + concurrent.futures

版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。

4.2 用 vLLM 启动一个模型服务

如果已经安装好 vLLM,可以执行下面的命令启动一个 OpenAI 兼容的服务:

# 需要注意:模型名称需要替换成你实际准备使用的模型权重路径或模型 ID vllm serve Qwen/Qwen2.5-7B-Instruct \ --served-model-name qwen-demo \ --tensor-parallel-size 1 \ --max-model-len 8192 \ --gpu-memory-utilization 0.9

常用参数含义:

  • --served-model-name:对外暴露的模型名称,后续请求时用到。
  • --tensor-parallel-size:使用几张 GPU 做张量并行,单卡场景通常设置 1。
  • --max-model-len:模型最大上下文长度,包含输入和输出。超过这个限制的请求会被拒绝。
  • --gpu-memory-utilization:允许框架使用的显存比例。剩余显存一般预留给 KV Cache,设太高可能因显存不足而启动失败。

启动后,服务默认监听http://localhost:8000,可以用 curl 做一次简单验证:

curl http://localhost:8000/v1/models

如果返回模型列表,说明服务启动成功。

4.3 明确测试边界:单包测试还是服务压测

基准测试可以先从两个层级进行:

  • 单请求性能摸底:发一个请求,记录 TTFT、TPOT、总延迟和生成 Token 数。这适合验证模型服务是否正常工作,也适合对比不同量化方式带来的速度差异。
  • 并发压测:同时发送多个请求,观察吞吐量和延迟变化。这更贴近生产环境。

建议先做单请求摸底,再逐步增加并发,防止一上来就因为并发导致框架配置问题和服务崩溃交织在一起,难以排查。

5. 自建并发压测脚本:从零记录关键指标

下面给出一套可以运行的最小压测脚本。它不依赖复杂测试库,只依赖 Python 标准库与requests

5.1 安装依赖

pip install requests

5.2 Python 压测脚本

# 文件路径:benchmark_inference.py import json import time import requests import statistics from concurrent.futures import ThreadPoolExecutor, as_completed API_URL = "http://localhost:8000/v1/chat/completions" MODEL_NAME = "qwen-demo" requests_payload = { "model": MODEL_NAME, "messages": [ {"role": "user", "content": "请用 200 字介绍大语言模型推理优化的基本思路。"} ], "max_tokens": 200, "temperature": 0.0 } CONCURRENCY = 10 TOTAL_REQUESTS = 50 def send_one_request(_): ttft = None first_token_time = None # 记录请求级指标 payload = json.loads(json.dumps(requests_payload)) start = time.time() response = requests.post(API_URL, json=payload, timeout=180) # 在这里通过 SSE 解析首字延迟比较复杂, # 我们先记录端到端延迟与返回 Token 数量。 # 若要精确测量 TTFT,需要使用 stream=True 并逐行处理响应内容。 end = time.time() if response.status_code != 200: return { "ok": False, "latency": end - start, "error": response.text[:200], } data = response.json() output_text = data["choices"][0]["message"]["content"] usage = data.get("usage", {}) completion_tokens = usage.get("completion_tokens", len(output_text)) total_latency = end - start return { "ok": True, "latency": total_latency, "completion_tokens": completion_tokens, "tps": completion_tokens / total_latency if total_latency > 0 else 0, } def run_benchmark(): success_cases = [] error_cases = [] with ThreadPoolExecutor(max_workers=CONCURRENCY) as executor: future_map = {executor.submit(send_one_request, i): i for i in range(TOTAL_REQUESTS)} for future in as_completed(future_map): try: result = future.result() except Exception as exc: error_cases.append(str(exc)) continue if result["ok"]: success_cases.append(result) else: error_cases.append(result["error"]) if success_cases: latencies = [item["latency"] for item in success_cases] tps_list = [item["tps"] for item in success_cases] total_tokens = sum(item["completion_tokens"] for item in success_cases) total_time = sum(latencies) # 这里需要区分:如果所有请求是并行发出的,系统吞吐量更合理的算法是 # total_tokens / wall_time,但由于 ThreadPoolExecutor 的调度, # 更精确做法是记录全局开始和结束时间。为了演示,采用请求耗时之和估算。 print("========== Benchmark Result ==========") print(f"总请求数: {TOTAL_REQUESTS}") print(f"成功请求数: {len(success_cases)}") print(f"失败请求数: {len(error_cases)}") print(f"平均端到端延迟: {statistics.mean(latencies):.2f}s") print(f"P95 端到端延迟: {sorted(latencies)[int(len(latencies) * 0.95) - 1]:.2f}s") print(f"平均单请求 Token 生成速度: {statistics.mean(tps_list):.2f} tokens/s") print(f"估算系统吞吐量: {total_tokens / total_time if total_time else 0:.2f} tokens/s") else: print("没有成功请求,请检查服务状态。") if __name__ == "__main__": run_benchmark()

这段脚本只是一个起点,它有几个明显的简化:

  • 没有精确测量 TTFT。精确测量 TTFT 需要把请求设为流式响应,逐行读取 SSE 数据,记录第一个 token 出现的时间。
  • 系统吞吐量的计算方式不够严谨。并发请求并不是从同一时刻开始,严谨做法需要记录全局开始时间与结束时间。
  • 没有对输入不同长度进行细分。

所以,实际做基准测试时,你需要把这段脚本继续升级。

5.3 带全局时间统计的改进版

我们可以用更严谨的方式统计全局耗时,并加上流式模式下的 TTFT 测量片段:

# 文件路径:benchmark_streaming.py import json import time import requests import statistics from concurrent.futures import ThreadPoolExecutor, as_completed API_URL = "http://localhost:8000/v1/chat/completions" MODEL_NAME = "qwen-demo" STREAM_PAYLOAD = { "model": MODEL_NAME, "messages": [ {"role": "user", "content": "请列出 5 条提高后端服务稳定性的建议,每条一句话。"} ], "max_tokens": 300, "temperature": 0.0, "stream": True, } def measure_single_stream(_): start = time.time() ttft = None first_token_time = None streamed_text = "" with requests.post(API_URL, json=STREAM_PAYLOAD, stream=True, timeout=180) as resp: if resp.status_code != 200: return {"ok": False} for line in resp.iter_lines(): if not line: continue line = line.decode("utf-8") if not line.startswith("data:"): continue data_str = line[len("data:"):].strip() if data_str == "[DONE]": break try: chunk = json.loads(data_str) delta = chunk["choices"][0].get("delta", {}) token_content = delta.get("content", "") if token_content: if ttft is None: ttft = time.time() - start streamed_text += token_content except json.JSONDecodeError: continue end_total = time.time() total_latency = end_total - start completion_tokens_estimate = len(streamed_text) return { "ok": True, "ttft": ttft, "total_latency": total_latency, "completion_tokens": completion_tokens_estimate, } def main(): wall_start = time.time() results = [] with ThreadPoolExecutor(max_workers=5) as executor: futures = [executor.submit(measure_single_stream, i) for i in range(20)] for future in as_completed(futures): r = future.result() if r["ok"]: results.append(r) wall_end = time.time() if results: ttfts = [r["ttft"] for r in results if r["ttft"] is not None] latencies = [r["total_latency"] for r in results] total_tokens = sum(r["completion_tokens"] for r in results) print(f"成功: {len(results)}") print(f"平均 TTFT: {statistics.mean(ttfts):.2f}s") print(f"平均总延迟: {statistics.mean(latencies):.2f}s") print(f"墙钟耗时: {wall_end - wall_start:.2f}s") print(f"实际系统吞吐量估算: {total_tokens / (wall_end - wall_start):.2f} tokens/s") else: print("没有成功结果。") if __name__ == "__main__": main()

这里有一个需要留意的地方:用len(streamed_text)来估算 Token 数并不准确,因为一个 Token 可能对应多个字符。更稳妥的做法的让服务端返回 usage 信息,或者在离线脚本里用分词器计算实际 token 数。如果要得到精确指标,建议从服务端日志中获取真实 token 数,或者把流式响应的usage字段捕获下来。

6. 准确率评测与推理速度评测要分开

6.1 推理性能测试不关注回答质量

在压测阶段,我们不关心模型回答得是否合理,只关心在规定时间内能不能返回内容。为了让压测更稳定,可以把模型的temperature设为 0,并固定一个较长的max_tokens,以避免因输出太长导致测试过程不可控。

但如果要做模型选型,还需要单独跑一轮“能力评测”。目前开源社区使用较广的是lm-evaluation-harness,它支持 MMLU、GSM8K、BBH、HumanEval 等数据集。运行方式类似:

# 这是示意命令,实际参数和数据集名称请以官方文档为准 lm_eval --model hf \ --model_args pretrained=Qwen/Qwen2.5-7B-Instruct \ --tasks mmlu \ --num_fewshot 5 \ --batch_size auto

注意,lm-evaluation-harness在跑复杂任务时会给模型服务带来很大的计算压力,也可以把它看作一种特殊的压力测试,但它返回的核心指标还是准确率,而不是性能指标。

6.2 业务评测集需要按场景自建

公开数据集只能反映通用能力。实际业务建议构造一套私有评测集,包含真实用户问题、边界情况、长文本输入、指令干扰等。比如客服场景,需要整理典型问法、相似问法、超长上下文、空输入等。只有用贴近线上请求的输入文本测试,吞吐和延迟才有参考价值。

7. 结果解读与常见误区

7.1 不要只盯着“每秒生成 Token 数”

在很多框架的 README 中,你会看到类似“比 XX 框架快 2 倍”的宣传。但这些数字是在特定并发、特定输入输出长度、特定硬件上测出来的。实际业务中,如果输入长度很长,Prefill 耗时可能成为瓶颈;如果输出长度很短,Decode 阶段的优化效果又不明显。因此要读懂数据背后的测试条件。

7.2 TTFT 与 TPOT 之间存在折中

增加并发能提高 GPU 利用率和系统吞吐量,但请求排队时间增加,TTFT 会明显上升,同时由于 Batch 变大,TPOT 也可能变长。这就是延迟和吞吐量的经典折中。

在生产系统设计中,需要通过压测找到“最大可接受 TTFT”对应的并发上限,而不是无限追求吞吐量。

7.3 平均延迟会掩盖长尾问题

压测结果要重点关注 P95、P99 延迟。平均值平滑了异常情况。如果 P99 延迟远大于 P95,说明系统在极端并发或长文本请求下可能不稳定,需要进一步观察是否发生了 CPU 调度抖动、显存峰值、垃圾回收或模型服务排队。

7.4 长时间运行与短时间突发的差异

有些问题只有在持续压测半小时以上才会出现,例如内存泄漏、显存碎片化、连接句柄耗尽。因此建议把短时间压测和长时间稳定性测试结合起来。

测试类型建议时长观察重点
冒烟测试1-2 分钟服务是否能处理请求,基本指标是否正常
常规压测10-30 分钟不同并发下的吞吐、延迟、错误率
稳定性测试数小时显存趋势、错误率、是否出现 OOM、响应延迟是否逐渐劣化

8. 常见问题与排查思路

下表总结了推理基准测试过程中最常遇到的问题。

问题现象常见原因解决思路
请求返回 429 或超时并发超过推理服务能承受的上限查看服务日志,降低并发;如果确认硬件充足,调整 batch 策略或 max-num-seqs 等参数
CUDA Out Of Memory显存不足以容纳模型权重 + KV Cache + 并发请求减小并发、降低 max-model-len、启用更低的量化精度、调整 gpu-memory-utilization
首次请求很慢,后续变快模型权重尚未完全加载到显存,或经历了首次算子编译测试前先进行 warm-up 请求;通常建议先发 2-3 个请求后再记录指标
压测结果不稳定,波动很大输入输出 token 长度不一致固定输入模板和 max_tokens;测量时要记录实际输入/输出 token 数
单请求速度正常,并发后吞吐不升反降可能触达显存/CPU 瓶颈,或调度开销过大绘制并发与吞吐量曲线;观察 GPU 利用率、显存带宽利用率和 CPU 占用
流式返回首 token 很慢,但总耗时可接受Prefill 阶段过长,或请求排队检查输入文本长度,尝试使用前缀缓存或减少无关 system 提示词
测试结果在不同轮次有差异环境存在共享资源、热温差异固定 GPU 频率、关闭其他任务;多次重复取中位数或最小值

8.1 解决“输出内容长度不一致导致指标失真”

最直接的方式是在请求中固定max_tokens,并设置temperature=0,同时用一个稳定的输入文本。但即使这样,模型输出的实际 Token 数也可能不同,因为它可能在达到max_tokens之前就遇到了结束标记。

更规范的指标计算方式是记录服务端usage中的prompt_tokenscompletion_tokens,再结合耗时计算速度。如果使用流式输出,SSE 事件最后一般会带usage字段;如果没有,可以通过后端日志或者用分词器对输入输出文本做精确统计。

8.2 解决“框架参数不知道从何调起”

不同推理框架的可调参数差异较大。以 vLLM 为例,常见的参数包括:

  • --max-num-seqs:最大并发序列数。增大它通常可以提升吞吐量,但会增加显存压力。
  • --max-num-batched-tokens:每次前向计算最多处理的 Token 数。
  • --enable-prefix-caching:如果系统有大量相似前缀,可以开启前缀缓存来降低 TTFT。
  • --quantization:指定量化方式,例如 awq、gptq、fp8。

调整参数时,建议每次只调一个参数并重新执行完整压测,用表格记录参数与结果之间的关系,避免变量混杂。

9. 最佳实践:搭一套可持续运行的基准测试流程

9.1 把你的测试场景做成固定资产

把压测脚本、模型权重版本、框架版本、参数、请求负载、测试结果都记录在同一个目录或 Git 仓库中,形成一套可回归的测试资产。比如:

llm-benchmark-project/ ├── configs/ │ ├── model-a.yaml │ └── model-b.yaml ├── datasets/ │ ├── real_query_sample.jsonl │ └── synthetic_query_sample.jsonl ├── scripts/ │ ├── start_server.sh │ ├── run_benchmark.py │ └── analyze_results.py ├── results/ │ ├── 2025-01-01_vllm_qwen-7b.json │ └── 2025-01-02_tensorrt-llm_qwen-7b.json └── README.md

这样做的好处是,当框架升级、模型升级或硬件更换时,可以重新运行同一套测试,对比版本间的性能变化。

9.2 测试数据尽量贴近真实负载

真实负载不只是请求内容,还包括:

  • Prompt 长度分布:短问题与长文档可能混合出现。
  • 输出长度分布:客服答复通常较短,代码生成或长文写作通常较长。
  • 并发到达曲线:存在高峰期和低峰期。

线上没有积累日志时,可以先构造一个初始数据集,上线后再用真实访问日志迭代。

9.3 记录环境信息,保证可复现

在每次运行压测前,建议把以下信息写入结果文件的元数据:

{ "gpu": "NVIDIA A100 80G", "cuda": "12.2", "framework": "vllm", "framework_version": "0.x.x", "model": "Qwen/Qwen2.5-7B-Instruct", "quantization": "None", "tensor_parallel_size": 1, "max_model_len": 8192, "concurrency": 20, "dataset": "real_query_sample.jsonl" }

没有这些信息,后人可能无法理解为什么同一份报告里的数字在别的环境复现不出来。

9.4 权限与生产变更安全

如果你需要在一个已经服务于线上流量的 GPU 服务器上做压测,务必先确认这台机器的资源是否可用,最好在独立环境执行测试。如果必须复用线上环境,建议:

  • 在测试前通过监控系统记录基准 GPU 利用率、显存使用率、网络延迟。
  • 压测从低并发开始,逐步提升,不要直接使用过高并发。
  • 不要在业务高峰期做占用型压测。
  • 压测结束后确认显存释放、服务恢复稳定,避免影响线上业务。

9.5 不要忽略峰值显存监控

压测过程中要持续监控显存变化。使用nvidia-smi的间隔采样可以简单做到:

nvidia-smi --query-gpu=timestamp,memory.used,utilization.gpu,power.draw --format=csv -l 1 > gpu_monitor.csv

压测结束后,查看显存是否在请求结束后回落到基线。如果显存持续增长,说明可能存在 KV Cache 释放不及时或内存泄漏。

9.6 理性看待“最快框架”的结论

技术发展很快,推理框架每几个月就可能发布明显优化。不建议在没有做实测的情况下长期绑定某个框架。每次引入新框架或新版本前,用同一套基准测试跑一遍,量化性能提升是否值得迁移成本。

10. 总结与后续学习方向

通过本文,你应该已经掌握了一套完整的 LLM 推理基准测试框架:理解了核心性能指标的含义,知道了为什么 TTFT 和 TPOT 会被业务对延迟的感知影响,也清楚了测试结果与模型、框架、硬件、负载模型之间的强耦合关系。从实操角度,你也学会了如何用 vLLM 启动一个模型服务,并用 Python 脚本发起单请求与并发压测,同时记录了延迟、吞吐量和显存监控方法。

接下来可以按照下面的顺序继续深入:

  • 完善自己的压测脚本,在流式模式下精确统计 TTFT、TPOT。
  • 使用lm-evaluation-harness跑一轮能力评测,和能力型结果一起形成选型报告。
  • 尝试换不同量化方式,对比 FP16、AWQ、GPTQ、FP8 的性能变化。
  • 对框架参数做 Grid Search,比如max-num-seqs从 16 调到 256,观察吞吐量和延迟的拐点。
  • 进一步学习 Continuous Batching、PagedAttention、Prefix Caching、投机采样这些推理优化概念,从而解释你在压测中观察到的现象。

推理性能优化最忌讳拍脑袋。把测试脚本、负载数据和结果分析沉淀成一套持续运行的基准测试流程,后续每次模型升级、框架切换或硬件扩容都会有一份客观依据,这比临时手忙脚乱地调参要可靠得多。建议现在就从一个简单的模型服务开始,跑通最小压测脚本,再逐步丰富你的测试数据集和并发模式。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 6:36:30

C与Python跨语言通信实战:基于UDP的高效数据传输方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/4 6:34:42

《恐龙猎人:起源》画面技术解析:从Nanite到Lumen的图形渲染实践

这次我们来看一个让老玩家兴奋的消息:经典IP《恐龙猎人》以《恐龙猎人:起源》的形式回归了。从最新放出的实机演示来看,这款游戏在画面表现上带来了不小的惊喜,无论是场景的宏大感、恐龙的细节刻画,还是光影特效的运用…

作者头像 李华
网站建设 2026/9/4 6:33:59

Java EE图书管理系统课程设计:从Servlet到MVC的完整开发实战

简介:本资源是一套完整的Java EE课程设计级图书管理系统实现方案,面向高校计算机相关专业学生及Java Web初学者,用于完成期末作业或课程实践任务。系统采用B/S架构,支持借阅者(学生、教师等)与管理员两类角…

作者头像 李华
网站建设 2026/9/4 6:33:00

320张香烟盒YOLO数据集:小而精的工业检测实战入口

简介:本资源是专为YOLO系列目标检测算法研发者与初学者打造的香烟盒子专用数据集,适用于工业质检、零售货架识别等实际场景中的小目标检测任务,支持YOLOv5至YOLOv11全版本模型训练与验证。压缩包共961个文件,包含320张高质量JPG图…

作者头像 李华
网站建设 2026/9/4 6:31:40

基于CLIP模型构建本地语义图片搜索系统:从原理到实践

最近在整理本地漫画资源时,遇到一个挺有意思的“小麻烦”。我有一套《非人哉》的漫画图包,里面角色众多,场景丰富。某天,我想快速找出所有包含“敖烈”这个角色的图片——可能是想做个角色合集,或者单纯想看看这位西海…

作者头像 李华
网站建设 2026/9/4 6:31:03

机器学习中的旋转等变性:原理、与不变性的区别及PyTorch实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华