首页 / 文章 / 如何自行基准测试LLM推理:值得信赖的数字设计标准
← 返回
IT技术

如何自行基准测试LLM推理:值得信赖的数字设计标准

✍️ zhirenhun 📅 2026/8/30 👁 51 阅读 ⏱ 61 分钟
如何自行基准测试LLM推理:值得信赖的数字设计标准

有人会给你发送一个推理基准测试。一种配置胜出。图表简洁,数字具体,而最重要的问题是图表无法回答的那个问题:你也能得到同样的数字吗?

可能不是。我在同一天早上两次运行了完全相同的测试,相隔十二分钟,期间系统没有被任何人触碰。最慢的 1% 请求在两次运行之间波动了 26%。除了时间之外,什么都没有改变。如果我只发布较好的那次运行,我可以说这是 26% 的提升,而你将无法核实我的说法。

当大约十五个微小的设置选择未被提及时,就会发生这种情况。你让模型预热的时长。你一次发送多少请求。每个提示是否都很短。发送请求的机器是否忙到无法正确保持时间。每个选择都会以可预测的方向移动基准的头条数字,这使得每个选择都成为基准可能倾斜的地方,无论是有意还是无意。几乎总是无意中发生的,出自善意发布的人。

因此,单独的一个数字只是一种主张。方法才是将其转化为证据的关键。

下面我将逐一介绍这十五个选择,展示它们中最糟的一种在DigitalOcean Serverless Inference上的实际运行中的影响,并给出一个在你发布自己的数字之前可以勾选的清单,或者用来核对他人的数字。

我还为本文创建了一个 GitHub 仓库 anishsingh20/inference-benchmark-design-standard。你可以在该仓库中找到原始记录、测试 harness 和图表。

TL;DR

  • 十五个杠杆会移动基准的头条数字,并且它们分为四个家族:预热、负载、工作负载和测量。大多数情况下是不经意出现的。了解此列表的读者会以不同的方式解读每个基准。
  • 并发度为 1 的中位数无法预测负载。在实际的 DigitalOcean 运行中,并发度从 1 增至 50 时,中位 TTFT 上升了 18%(窗口 1 从 202.1 ms 增至 238.4 ms)。TTFT p95 上升了 2.8 倍(445.4 ms 增至 1,245.1 ms)。总完成时间 p50 上升了 3.0 倍(741.4 ms 增至 2,241.2 ms)。
  • 对同一实时测试进行两次诚实的重复运行仍会导致 p99 出现变化。两个连续的并发度为 1 的窗口,每个窗口包含 300 个请求,在 TTFT p99 上相差 26%(822.3 ms 对比 634.4 ms),在 TTFT p50 上相差 5%。如果 p99 的“提升”小于你的重复运行波动,那就不是一个发现。
  • 客户端是仪器的一部分。 在并发度为 50 时,OS‑thread 客户端每秒钟消耗 1.02 到 1.24 CPU‑秒。asyncio 客户端保持在 0.24 到 0.28。TTFT p50 在 3 毫秒以内一致。TTFT p99 则不一致。未披露客户端架构和生成器利用率的基准测试存在未披露的仪器误差。
  • 可交付成果是一个版本化且可链接的一页披露标准, 它有两个明确的用途:用它来运行自己的基准测试,以及用它来阅读他人的基准测试作为审查列表。
  • 何时使用此标准,以及何时不使用

    使用它 当您即将发布延迟或吞吐量数字时,当您阅读供应商或聚合器的基准测试时,或者当您需要判断报告的 p99 增幅是否大于噪声底线时。

    不要使用 将并发度为 1 的中位数作为生产证据。 不要读取 下面的实时表格作为 “DigitalOcean 无服务器推理延迟为 X”。 它们是在一次早晨的单个模型上的协议增量。 不要将 客户端 CPU/墙时比超过 0.7 的 c=50 单元格视为纯服务器测量。

    本文使用的术语

    下面表格中的每一行对应一个术语,并附有一张实际示例图。示例数字与本文自身的运行结果一致。

    术语 含义 简单示例
    TTFT 客户端在第一个流式内容块到达时,首个 内容 令牌的时间戳。仅推理的块不计入。 您发送 “总结这封邮件”。回复的第一个可见单词在 202 毫秒后出现。这 202 毫秒即为 TTFT。如果模型在任何文本出现前静默思考两秒,则 TTFT 为两秒,而不是 HTTP 头到达的时刻。
    总完成时间 客户端从请求开始到最后一个内容块的时钟。这是用户或代理步骤所等待的时长。 同上请求。最后一个单词在您发送后 741 毫秒到达。聊天用户感受到 TTFT。等待完整答案的代理需付出 741 毫秒。
    p50 / p95 / p99 低于此值的请求占测量请求的 50%、95% 或 99%。p50 是中位数。p99 是尾部指标。 您将本文的 300 个并发度为 1 的请求按耗时排序。一半请求在 202 毫秒内完成(p50)。95% 请求在 445 毫秒内完成(p95)。99% 请求在 822 毫秒内完成(p99)。强调 p50 的供应商是在描述典型情况,而非不走运的情况。
    冷启动 请求落在未准备好的副本或空缓存上,因而进入比温暖多数更慢的模式。 空闲后的第一个请求可能比后面的九十九个请求慢很多。如果您丢弃这个第一个请求,则公布的数字仅代表温暖情况。
    预热丢弃 在测量样本之前被丢弃的请求。本文丢弃前 10 个 trace IDs,而非前 10 个完成请求。 您发送 310 个请求。前 10 个用于 CUDA‑graph 捕获和分配器设置。您丢弃这 10 个,并对剩余的 300 个请求报告百分位数。在负载下丢弃前 10 个 completions 与之不同:最短的请求会先完成,因此您实际上会丢弃短请求而保留长请求。
    前缀缓存 / 提示缓存 对先前计算得到的提示前缀进行重复使用。在启用缓存的重复提示基准测试中,实际上是在测试缓存。 您两次发送相同的 3,110‑token 系统提示。
    并发 一次有多少请求同时进行。闭环意味着已完成的请求会被立即替换,这就是此测试 harness 所做的。 并发度 1 表示一次只处理一个请求,就像单个用户。并发度 50 表示有 50 个请求重叠进行。当一个请求完成时,此 harness 会立即启动下一个请求,这就是闭环。开环则会在计时器到时发送请求,即使前面的请求尚未完成。 标记间隔 在单个流式响应中,连续内容块之间的最长停顿。 标记流式输出为 “The” 然后 “cat” 然后 “sat”。大多数间隔为 12 ms。有一次停顿持续 865 ms,界面看起来卡住了。这 865 ms 是该请求的最坏标记间隔。首 token 延迟(TTFT)正常,但流式输出不正常。 客户端 CPU/墙钟时间 进程 CPU 秒数除以墙钟秒数。当该比率超过约 0.7 时,负载生成器将大部分核心用于自身。 基准机器每经过 1.00 秒墙钟时间,消耗 1.02 秒 CPU 时间。负载生成器使用了超过一个完整的核心,因此你记录的尾部延迟混合了服务器延迟和客户端延迟。若该值为 0.006,表示客户端处于空闲状态,你实际上是在测量服务器。 带种子的混合长度追踪 固定的随机种子用于构建 70/20/10 的短、中、长提示混合,使两个臂能够重放相同的工作负载。 种子 7 始终生成相同的混合:70% 短提示,20% 中等提示,10% 长提示,每种都带有唯一的 nonce,以防止缓存作弊。因此,线程臂和 asyncio 臂看到的提示完全相同。任何差异都来源于客户端,而非更幸运的问题集。 运行间波动 在系统未变化的情况下,两次独立运行之间的相对差异。任何声称的改进若小于此波动,则属于噪声。 你在 UTC 03:35 和 03:47 两次运行相同的并发度为 1 的测试。TTFT p99 分别为 822 ms 和 634 ms,波动幅度为 26%,系统未发生任何变化。声称的 “20% p99 改进” 位于此噪声范围内,因而不是真实发现。 披露标准 在你能够判断其数值之前,基准必须填写的十五项检查清单。推理基准设计标准。 在您发布 “p99 是 800 ms” 之前,您需要确认使用哪个时钟、进行多少次试验、并发度如何、使用哪种提示组合、使用哪个客户端以及原始 JSON 存放在何处。如果这些信息都未填写,那么该数字只是一种声明,而非证据。

    游戏分类法:影响数值的十五个杠杆

    每个下列杠杆说明其机制、其偏向的方向,以及它通常是故意出现还是意外出现。家族的重要性胜过数量:一旦你看到这四个家族,你就能自己生成质询问题。

    四个家族,十五个杠杆。每个杠杆都会以可预知的方向影响头条指标,这正是每个杠杆成为游戏向量(无论是故意还是意外)的原因。

    让我们详细拆解每个杠杆。

    预热杠杆

    1. 仅热身测量。 模型加载时,首批请求较慢;后续请求变快。如果您丢弃慢速请求,仅发布快速请求的结果,那么您实际上是在以最佳情况来代表整体表现,而该数字看起来总是更好。这种情况通常是意外发生的:在调试过程中您发送了一些测试调用,随后在已经预热好的系统上运行了基准测试。无服务器一致性研究表明,这一差距在测量时机不同情况下可达约 10 倍。

    2. 未报告的缓存状态。 如果你两次发送相同的提示,引擎可以复用第一次调用的工作。此时你实际上是在测量缓存,而不是模型。提示缓存研究 在一个 3,110-token 的提示上进行了测量:第一次 462 毫秒,第二次 39 毫秒。这相当于下降了 92%,如果文章未提及缓存,读者永远不会看到这一点。vLLM 默认开启此复用。请参阅 vLLM 引擎文档。因此,如果你的基准在默认的 vLLM 构建上重复使用相同的提示,除非你关闭了缓存或改变了每个提示的开头,否则实际上是在进行缓存测试。更多内容请参见 如何使用提示缓存提示缓存实战

    3. 首次命中排除。 在全新 GPU 上的第一个请求会特别慢。引擎正在编译代码并设置内存。这种慢速是启动成本,而不是系统启动后的运行方式。如果你明确说明你删除了该请求,那么删除第一个请求是可以接受的。如果你保持沉默,这就具有误导性,因为读者无法判断 “200 ms” 是否包含启动时间。Ornith 9B 微调基准 遇到了这个慢速的首次请求,并将其记录下来。这就是诚实的版本。

    负载杠杆

    4. 将单用户测试当作生产环境。 一次只处理一个请求的测试会将 GPU 分配给单个请求。没有其他请求在等待,因此数值看起来很快。实际流量中有许多请求共享同一个 GPU。它们会排队、批处理,互相影响。在并发度为 1 时,这些情况不会体现出来。提高并发度不会拉伸同样的数字。它改变了你所测量的内容。请参阅 当你的 vLLM p99 比 p50 更差时调试 p99 TTFT

    5. 将吞吐量当作延迟来售卖。 “此 GPU 每秒可处理 2,000 个 token” 是一个批处理数字。它不会告诉你你的请求实际花费了多长时间。将更多请求装入一个批次时,总体每秒 token 数常常会上升,而每个用户的等待时间则会变长。混合使用这两个指标总是让供应商看起来更好。

    6. 精心挑选的并发。 模型 A 在 5 用户时胜出,模型 B 在 50 用户时胜出。若只发布 5 用户的测试,就看不到两者的交叉点。解决办法不是承诺公平,而是进行全面扫描:至少三种负载水平并排测试。

    7. 未报告的持续时间。 60 秒的测试会忽略几分钟后才出现的问题:队列不断增长、GPU 持续升温、副本逐渐变慢。如果系统在第五分钟崩溃,一分钟的基准只会报告容易的部分,而把故障漏掉。

    工作负载杠杆

    8. 有利的长度。 如果模型在读取长输入方面较弱,短提示会让首 token 时间看起来很好;如果模型在写长输出方面较强,长答案会让总时间看起来很好。你挑选的长度决定了谁胜出。人工分析研究因此把默认输入调到了 10,000 token,因为之前的短默认输入不符合实际使用。

    9. 所有请求长度相同。 生产流量是短长混合的:有些短,有些长。长提示会抢夺已经在流式传输的短请求的 GPU 时间。如果每个测试请求大小相同,你永远看不到这种堆积。你实际上在测试一个不存在的工作负载。服务端的观点在连续批处理 vs 静态批处理教程中有说明。

    10. 未报告的采样设置。 温度、top-p 和 max token 会影响答案的长度。答案越长,耗时越多。同一模型在不同隐藏设置下的两次测试并不相同,也没有人能够重新运行它们。更多内容请参见如何使用 Chat Completions API 发送提示给模型

    11. 同名不同模型。 FP8 和 BF16 是不同的精度。其中一个体积更小且通常更快。在同一个模型名下比较它们是不匹配的。同时标注两者才是公平的测试。一致性研究发现,一些提供者在未声明的不同精度下提供同名模型。这种情况在实际使用和生产环境中都会出现。

    测量杠杆

    12. 使用平均值而非百分位数。 平均值将快速多数与慢速少数混合,得到的数字几乎没有人实际体验到。在这篇文章的实际运行中,并发度为1的中位数 TTFT 为 202.1 毫秒,而 p99 为 822.3 毫秒。仅发布中位数会掩盖尾部情况。您可以在以下链接中了解更多:p50 与 p99 延迟调试 p99 TTFT

    13. 最佳 N 次运行选择。 您运行测试五次。您发布其中最好的一次。这不是典型结果。这是最幸运的结果。常见的误操作:您反复运行直到数字“看起来稳定”,然后保留最后一次运行。

    14. 客户端瓶颈。 发送请求的笔记本电脑或 Droplet 是计时的一部分。如果该机器繁忙,它会增加延迟,而您会把责任归咎于服务器。Chandrasekar 和 Kramberger 2026 年的一篇论文表明,单进程异步测试器可以将工作排队到自身并导致 TTFT 被夸大。来源:识别和缓解生产环境 LLM 推理基准中的系统性测量偏差。如果一篇文章从未说明客户端是如何运行的,或者其繁忙程度如何,您就无法区分服务器延迟和测试器延迟。本文后面我进行的实际运行在 DigitalOcean 无服务器推理 上进行了该测量。

    15. 未披露的网络位置。 如果您从与服务器相邻的机器进行测量,您就会跳过用户所经历的路程。光在光纤中已经需要在纽约和旧金山之间往返 41.3 毫秒,在纽约和新加坡之间往返 153.3 毫秒,而在模型开始任何工作之前。来源:数据局部性税。原始记录:anishsingh20/data-locality-tax。没有位置信息的延迟数字缺少一个与效应大小相当的项。

    此列表涉及速度测试。质量测试有其自身的技巧,包括不同的测试套件对同一模型给出不同的评分。选择适合您推理用例的模型:生产环境推理系列 对此进行了说明。

    实测运行:DigitalOcean Serverless Inference 上的负载与客户端调节手段

    本节内容来自一次真实的 API 实测,而非模拟。它是对已发布的 p50 与 p99 延迟测试的延伸,于 2026 年 8 月 24 日以单个脚本对 https://inference.do-ai.run/v1 运行完成。原始记录见:anishsingh20/inference-benchmark-design-standard。同一端点系列的相关实测证据见:anishsingh20/serverless-inference-tail-latency-study

    实测内容

    运行 A,负载杠杆。 在并发度为 1、8、50 时,使用相同种子的追踪,线程臂。

    单元格 TTFT p50 TTFT p95 TTFT p99 最差差距 p99 总计 p50 总计 p99 客户端 CPU/墙时 错误数
    线程, c=1, 窗口 1 202.1 毫秒 445.4 毫秒 822.3 毫秒 864.7 毫秒 741.4 毫秒 9,102.1 毫秒 0.006 0
    线程, c=8, 窗口 1 195.8 毫秒 457.7 毫秒 631.5 毫秒 1,098.4 毫秒 1,625.9 毫秒 7,705.7 毫秒 0.061 0
    线程, c=50, 窗口 1 238.4 毫秒 1,245.1 毫秒 1,305.6 毫秒 1,209.7 毫秒 2,241.2 毫秒 12,341.5 毫秒 1.024 0
    线程, c=1, 窗口 2 192.0 毫秒 363.7 毫秒 634.4 毫秒 732.3 毫秒 748.2 毫秒 10,538.0 毫秒 0.006 0
    线程, c=8, 窗口 2 192.6 毫秒 343.1 毫秒 698.6 毫秒 745.0 毫秒 1,488.1 毫秒 10,708.9 毫秒 0.060 0
    线程, c=50, 窗口 2 234.8 毫秒 1,174.7 毫秒 1,500.0 毫秒 852.9 毫秒 2,176.4 毫秒 9,345.9 毫秒 1.186 0

    四个读数,按读者应采取的顺序。

    1. 中位 TTFT 是一个弱负载检测器。 窗口 1 在 c=1 时从 202.1 毫秒上升到 c=50 时的 238.4 毫秒,上升了 18%。窗口 2 从 192.0 毫秒上升到 234.8 毫秒,上升了 22%。仅凭 c=1 的 TTFT 中位数几乎无法得出 c=50 的情况。
    2. p95 TTFT 是负载显现的地方。 窗口 1:从 445.4 毫秒增加到 1,245.1 毫秒,增长 2.8 倍。窗口 2:从 363.7 毫秒增加到 1,174.7 毫秒,增长 3.2 倍。p50 vs p99 研究 认为尾部(而非中位数)能够预测生产环境表现。此次扫描以表格形式呈现了该论点。
    3. 总完成时间会累积。 窗口 1 的总 p50 从 741.4 毫秒上升到 2,241.2 毫秒,增长 3.0 倍。窗口 2 从 748.2 毫秒上升到 2,176.4 毫秒,增长 2.9 倍。如果您的工作负载是一个需要等待完整答案的代理步骤,那么在本次运行中,并发度为 1 的总中位数会低估约 3 倍的 c=50 中位数。
    4. c=50 的线程客户端是测量的一部分。 CPU/wall 为 1.024 和 1.186。当超过约 0.7 时,生成器会将大部分核心用于自身。应将 c=50 的尾部视为混合的服务器加客户端数值,而非纯粹的服务声明。

    TTFT p99 was 非单调 意味着,随着并发度的增加,它并未始终朝同一个方向变化。在此上下文中,这表示 p99 值在提升并发度时只会单向增加或单向减少,而不会出现方向反转。然而,窗口 1 中观测到的序列是 822.3 ms → 631.5 ms → 1,305.6 ms。因此,如果只比较 c=1 和 c=8,可能会错误地得出更高并发度能改善 p99 延迟的结论,但在 c=50 的第三个点出现后,趋势发生了逆转。

    此外,在 c=1(顺序窗口)的两次运行之间,TTFT p99 仍然相差 26%(822.3 ms 对比 634.4 ms),TTFT p50 相差 5%(202.1 ms 对比 192.0 ms)。如果报告的 p99 变化小于仅通过重复测试所看到的变化,则该发现无效。此处的测量数据正好展示了这种变化。

    运行 A。中位 TTFT 保持在约 200 ms。p95 是在 c=50 处弯曲的线条。来源:每个单元 300 次测量请求,mistral-3-14B,DigitalOcean Serverless 推理,2026 年 8 月 24 日。证据仓库中的原始 JSON。

    如何读取此图表。我们因为在十二分钟间隔内两次运行了相同的测试,所以有两个面板。左侧是第一个窗口。右侧是重复。底部横轴表示一次性处于飞行状态的请求数量:一次一个,然后是 8 个重叠,然后是 50 个重叠。左侧纵轴表示等待回复第一个词的时间,单位为毫秒。

    蓝色线 表示典型等待时间:有一半的请求比这更快。即使在 50 个重叠请求时,它仍然接近 200 ms。如果只发布蓝色线,你会说负荷几乎没有影响。橙色线代表不走运的 5%。它在 1 和 8 时保持安静,然后在 50 时跳升至约 1,200 ms。这就是负荷实际显现的地方。红色线代表不走运的 1%。它不会呈直线上升。在左侧,它甚至从 822 ms 下降到 632 ms,随后升至 1,306 ms。在右侧,它从 634 ms 开始,结束于 1,500 ms。相同的提示,相同的机器,两次诚实的重新运行,最慢的 1% 已经出现分歧。

    运行 B,客户端杠杆。 相同的追踪,相同的并发度 50,一次由单进程 asyncio 客户端驱动,一次由 OS 线程客户端驱动。请求构建、解析和时间戳是共享代码。两臂之间的唯一区别是并发机制。

    主要区别:

    简而言之:Asyncio 是事件驱动的,适用于 I/O 受限的并发场景;线程客户端使用真正的 OS 线程,并且可能在每个请求上产生更高的开销,特别是在 Python 中。

    以下是测试结果:

    单元格 TTFT p50 TTFT p99 最差间隔 p99 总计 p99 客户端 CPU/wall 错误
    asyncio, c=50, window 1 228.5 ms 1,148.6 ms 1,480.1 ms 10,746.6 ms 0.239 0
    threads, c=50, window 1 231.1 ms 1,412.5 ms 845.7 ms 10,988.2 ms 1.235 0
    asyncio, c=50, window 2 229.4 ms 745.5 ms 908.4 ms 9,426.5 ms 0.284 0
    threads, c=50, window 2 227.1 ms 1,382.1 ms 1,156.4 ms 10,698.6 ms 1.102 0

    测量偏论文预测,饱和的单进程 asyncio 客户端会导致 TTFT 和 token 间尾部延迟升高。本次运行未将 asyncio 放入该状态。asyncio 端的 CPU/wall 保持在 0.24 到 0.28 之间。线程端则保持在 1.10 到 1.24。两个时间窗口内 TTFT p50 一致,误差在 3 毫秒以内。在两个窗口中,饱和线程客户端的 TTFT p99 更高(1,412.5 毫秒对比 1,148.6 毫秒,随后 1,382.1 毫秒对比 745.5 毫秒)。最差差距的 p99 在窗口间方向相反,因此该差值落在噪声底噪内。

    可发表的结论并不是“asyncio 更快”或“线程更快”。可发表的结论是:决定哪个客户端在测量自身的数字是 CPU/wall,而非运行时名称;若尾部比较省略了这个数字,则无法进行评估。在服务器 TTFT 几百毫秒时,并发数 50 未能饱和单个 asyncio 事件循环。但它确实让 50 个 OS 线程超过了客户端 CPU 的一个核心。

    如果您需要论文中的 M/G/1 状态,请提高并发数或 QPS(每秒查询数,即负载生成器每秒发送的请求数量),直到 asyncio 端的自身 CPU/wall 接近 0.7,然后进行比较。

    这张图展示了不同线程池和并发度的性能比较,包括同步和异步线程池,以及不同并发度的测试结果

    如何读取此图表。相同的提示,相同的 50 个重叠请求,由两个不同的程序发送。橙色条表示 asyncio 客户端。蓝色条表示 OS 线程客户端。左侧为第一个窗口。右侧为重复窗口。每组条形代表不同的时钟:首字典等待时间(TTFT p50),不幸运的 1% 首字典等待时间(TTFT p99),以及不幸运的 1% 完整答案等待时间(总 p99)。

    每个面板左侧的短条表示典型等待时间。橙色和蓝色几乎相同,误差在 3 毫秒以内。大多数用户不会察觉差别。中间的条表示慢首字情况,此时两端分道扬镳:在两个窗口中,线程更慢(1,413 毫秒对比 1,149 毫秒,随后 1,382 毫秒对比 746 毫秒)。右侧的高条表示慢完整回答情况,大约为 10 秒左右。条形图未显示,而上表所示的原因是:线程客户端自身消耗了超过一个完整 CPU 核心(CPU/wall 1.10 到 1.24)。而 asyncio 客户端则没有(0.24 到 0.28)。请不要将其理解为“asyncio 是更快的服务器”。应理解为:发送请求的程序可能会自行引入延迟,而典型等待时间不会提醒您这一点。

    如何在 DigitalOcean 上自行运行

    我从一个 GPU Droplet 运行了这个。你其实不需要 GPU。一个配有 4 个 vCPU 的普通 Droplet 就足够了。该机器仅负责发送请求。模型位于其他地方:DigitalOcean Serverless Inference,或者你在 GPU Droplet 上托管的 vLLM 服务器。

    对于 Serverless Inference,您需要三样东西:模型访问密钥预付余额,以及从 GET https://inference.do-ai.run/v1/models 获取的确切模型 ID。如果您自己托管 vLLM,请按照 连续批处理操作手册 进行操作,并说明前缀缓存是开启还是关闭。

    Python 3 已经预装在 DigitalOcean 镜像中。线程测试不需要额外的东西。异步测试需要 pip install httpx

    步骤 1. 创建发送请求的机器

    启动 Droplet。您可以参考我们的官方文档:如何创建和配置 GPU Droplet

    记下其地区、大小和 vCPU 数量。您在发布时会需要这些信息。没有位置的数字会缺少一个延迟大小的部分。

    步骤 2. 获取密钥,然后发送一个请求

    将密钥放入环境中:

    export BENCH_KEY=<your key>
    

    首先列出模型,按照 检索可用模型 文档所述的方式。然后发送一个请求。在确认端点能够响应之前,不要启动 300 请求的运行。

    步骤 3. 运行负载扫描(运行 A)

    下载 harness/protocol_demos.py 脚本,来自 GitHub 仓库。以 1、然后 8、然后 50 个重叠请求运行相同的提示混合,全部使用线程:

    python3 protocol_demos.py --model <model-id> --arm threads --concurrency 1  --n 300 --out a_c1.json
    python3 protocol_demos.py --model <model-id> --arm threads --concurrency 8  --n 300 --out a_c8.json
    python3 protocol_demos.py --model <model-id> --arm threads --concurrency 50 --n 300 --out a_c50.json
    

    每条命令发送 310 个请求,丢弃前 10 个,保留 300 个。我在同一天早上运行了两次,即使是不幸的 1% 等待时间也仍然移动了 26%。这就是为什么单独的 300 请求的 p99 不是一个独立的发现。逐个处理的单元很慢。我的运行分别用了 442 秒和 460 秒。

    步骤 4. 比较两个客户端(运行 B)

    现在,使用相同的 50 个重叠请求发送两次。一次使用 asyncio,一次使用线程。

    pip install httpx
    python3 protocol_demos.py --model <model-id> --arm asyncio --concurrency 50 --n 300 --out b_async_c50.json
    python3 protocol_demos.py --model <model-id> --arm threads --concurrency 50 --n 300 --out b_threads_c50.json
    

    查看每个摘要中的 client_cpu_seconds_per_wall_second。如果该数字接近 0.7 或更高,则发送请求的机器自身很忙。将其放在延迟旁边,而不是放在脚注中。在我运行时,线程越过了该阈值。Asyncio 没有。

    步骤 5. 再次执行

    在不同的窗口中重复步骤 3 和 4。夜间与 daytime 的对比是诚实的一对。两次运行之间的差距就是你的噪声。如果一个“胜利”小于这个差距,那就不是胜利。如果你的第二个窗口与我的表格不一致,这种不一致就是发现。终点不是实验室。

    步骤 6. 发布文件及写作

    将 JSON 放在文章旁边。诚实的说法不是 “模型是 X 毫秒。” 而是 “在此系统中,此更改使数字变动了 Y,这里是我所做的一切。”

    此测试套件中的每个提示都以独特的前缀开头。vLLM 和若干托管 API 默认会重复使用同一个前缀。如果没有这个独特的起始,你就在测量缓存,而不是模型。如果你想测量缓存,去掉前缀并说明。任意选择都可以。安静版本是陷阱。

    诚实的测试设置是什么样子

    十五个杠杆是陷阱。这就是你关闭它们的方法。

    发送请求的程序。 使用真实线程或多于一个进程,除非你测量该程序的繁忙程度并且它保持在大约 0.7 CPU-seconds 每 wall-second 以下。如果发送方很忙,你就在测量发送方。解决办法是使用第二台机器,而不是更大的爆发。在你的机器上加时间戳:第一个可见单词,单词之间最长的停顿,以及最后一个单词。在此次运行中,50 个线程越过了该阈值。50 个 asyncio 任务没有。

    请求数量和次数各是多少? 我对每个单元使用了 300 次测量请求,重复了两次。即使是两次逐个进行的窗口,在 1% 的不利情况下仍有 26% 的差异,在典型等待情况下有 5% 的差异。这个差距就是我实际观察到的噪声。任何小于这个差距的所谓“改进”都只是运气。按 prompt ID 去掉预热,并说明你去掉了多少。该规则来源于 Serverless 推理中重要的指标 文章。

    提示词的组合。 不要发送 300 条相同的短问题。混合使用短、中、长三种提示词。记录下组合和随机种子,以便他人可以重现。此测试 harness 使用 70% 短、20% 中、10% 长。除非你想测量缓存,否则请更改每个提示词的开头。

    记录你运行时的环境。 模型 ID。如果可见,则包括引擎和镜像。温度。令牌上限。地区。你相对于服务器的位置。时间。保留每个请求的完整 JSON,而不仅仅是摘要,这样文章中的任何数字都可以从文件中重建。背景负载会随一天变化。一个窗口相当于一次天气报告。两个窗口是最低要求。您可以查看 同一模型下 Serverless 推理一致性为何会变化的研究

    如何读取此图。 每个框代表一种数字可能作弊的方式的锁。顶部行,从左到右:

    来源:本文中的实时 harness,基于以下协议构建:p50 与 p99 研究Serverless 推理一致性研究Serverless 推理重要指标

    LLM 推理基准测试清单

    在发布前先思考一下。每个框都需要一句可以放在图表下方的话。如果你写不出这句话,说明这个数字还不成熟。

    我已经填好了,这样你可以看到‘完成’是什么样子。你可以勾选的副本位于 GitHub 仓库。版本 1.0。

    你计时了什么?

    模型已经预热了吗?

    系统有多忙?

  • [ ] 9. 每次运行持续多久? 30秒的测试可能会错过在第3分钟爆发的队列。 本文:逐个处理的单元大约用了7分钟。第二次运行在12分钟后开始,不是过夜的。这就是一个差距。
  • 您发送了什么给模型?

    谁在计时?

    如果所有 15 项都有答案,其他人可以重新运行您的测试。如果 1、2、3 项为空,他们甚至无法检查。空框意味着“相信我”。

    我在自己的表格中使用了这个方法。第 9 和第 12 个框是薄弱环节,它们作为差距保留在文章中,而不是我所掩盖的内容。

    您应该相信一个基准测试,只要它能说明它是如何制作的。而不仅仅是因为页面上的标志。

    十五个杠杆描述了数字变化的方式。实际运行显示,在负载下,典型的首词等待时间上升了 18%,而不走运的 5% 几乎增加了三倍,完整答案的等待时间增加了 3 倍。它还表明,发送请求的机器可能会使尾部变脏。

    上面的设置就是阻止这种情况的方法。十五个框的检查清单是读者检查您是否如此做的方式。

    这张技术文章配图列出了15个有关benchmark的技术术语的定义

    如果你想得到帮助决定 什么 进行计时,可以先阅读 p50 与 p99 延迟研究。如果你想运行我使用的相同基准,请使用 此仓库。如果你想在既定协议下获得测量数值,请阅读 DigitalOcean 的 LLM 推理基准测试文章。如果你想了解为什么某个数字总会产生误导,请阅读 推理三难题

    然后在你发布的每个数字和你阅读的每个数字上都使用同样的十五个框核对。清单只有一页。没有它你只是在猜测。

    常见问题

    为什么使用 300 个请求?这足够吗?

    我在每个单元格中使用了 300 个保持请求,进行了两次。两次逐个请求的窗口在不幸的 1% 和典型等待的 5% 仍有 26% 的差异。这个差距是我实际观察到的噪声。任何小于这个差距的“改进”都是运气,而非发现。请根据你发送的提示 ID 来丢弃预热,而不是根据哪些请求先完成,并记录下你丢弃了多少。无服务器推理中重要的指标文章正是该规则的来源。

    为什么你使用线程而不是 asyncio?

    在负载扫描中,我特意使用了线程。在 50 个重叠请求下,线程客户端每墙秒消耗 1.02 到 1.24 CPU 秒,因此慢尾部将服务器延迟与繁忙的发送方混合在一起。asyncio 客户端保持在 0.24 到 0.28。典型的首字等待时间在 3 毫秒以内一致。不幸的 1% 没有在 3 毫秒内一致。如果你一直只运行 asyncio,你会错过这个陷阱。如果你只在高负载下运行线程且不提及 CPU/wall,你就会发布一个不准确的数字。在 protocol_demos.py 中使用 --arm threads--arm asyncio,并将 CPU/wall 数值放在延迟旁边。

    我需要 GPU Droplet 来运行这个吗?

    不。Droplet 仅发送请求。具有 4 个 vCPU 的普通 Droplet 已足够。我在 NYC2 使用了 GPU Droplet,因为它已经与 API 处于同一云中。模型位于其他地方:DigitalOcean 无服务器推理,或者你在其上托管 vLLM 服务器的 GPU Droplet

    为什么不直接发布中位数?

    因为中位数可能看起来不错,而不幸的人却在受苦。在窗口 1 中,典型的首字等待时间从一次一个请求增加到 50 个重叠请求,上升了 18%(从 202 ms 增至 238 ms)。不幸的 5% 几乎增加了三倍(从 445 ms 增至 1,245 ms)。完整答案的等待时间大约增加了 3 倍。如果你只发布蓝色线,负载几乎不起作用。如果你发布橙色和红色线,则负载起作用。聊天用户感知首字。等待完整答案的代理感知整个过程。p50 与 p99 延迟研究 是此答案的更详细版本。

    这与 LLM 基准排行榜有何不同?

    大多数“LLM 基准”衡量答案质量:MMLU、编码套件、Arena 投票。本文测量在负载下回复开始和结束所需的时间。两者都很重要。将它们混淆就会导致一篇关于延迟的博客文章被质量排行榜掩盖,而质量上的胜利又被宣传为“更快”。如果你关心正确性,请使用质量套件。如果你关心用户是否在等待,请计时流并填满十五个框。

    结论

    我发起这项研究是因为我一直在读取无法重现的延迟数字。一份干净的图表。一个获胜的配置。没有办法判断作者是测了模型、缓存,还是只是碰巧早上运气好。

    于是我亲自运行了测试。使用相同的提示词、相同的机器,进行了两次。即使是最慢的 1% 也仍然提升了 26%。负载几乎没有影响典型等待时间,但几乎将不幸的 5% 的延迟提高了三倍。在 50 个并发请求下,线程客户端的繁忙程度超过了一个完整的 CPU 核心,这种情况在尾部延迟中可见。这一切都不算特殊。这是在保留原始文件并进行观察时会发生的事情。这也是这项研究与其他基准不同的原因。

    末尾的十五个框是我想让您记住的部分。在发布之前请勾选它们。当他人发布时,请将它们举出来。

    测试套件、原始 JSON 和图表位于 GitHub 仓库。请使用它们,对它们提出异议,并将您自己的差距与您自己的数字并列发布。这就是全部要点。

    参考文献

    DigitalOcean 文档与工程内容

  • 无服务器推理中重要的指标 (试运行次数、预热和多窗口协议)
  • 为什么同一模型的无服务器推理一致性会有差异 (面向方法论的发布模式;精度不透明的发现)
  • 提示缓存如何工作:成本盈亏平衡点 (缓存状态杠杆,已量化)
  • 提示缓存的实际应用:命中率从 7% 提升至 74% (在生产流量中的同一杠杆)
  • 当您的 vLLM p99 比 p50 更差时 (并发杠杆的机制)
  • 当其他一切看似正常时,为什么 p99 TTFT 会偏高? (对同一尾部的运营调试)
  • LLM 推理中的隐藏瓶颈 (服务端杠杆:批处理、KV 缓存、预填充)
  • Ornith 9B 微调成本基准 (一份记录的首次命中产物,诚实披露)
  • 为您的推理用例选择合适的模型 (质量基准杠杆集合,此处不在讨论范围内)
  • LLM 推理三难困境 (为什么单一数值基准在结构上会产生误导)
  • 专用 GPU 上的代币经济学 (吞吐量披露的利用率上下文)
  • 数据局部性税 (网络位置杠杆,按物理定价)
  • 使用无服务器推理
  • 如何创建和管理模型访问密钥
  • 如何检索可用模型
  • 聊天补全 API
  • 推理定价
  • 证据仓库

    外部资料来源

    ——

    🧑‍💻

    zhirenhun

    一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。

    ← 上一篇
    如何从LLM中获取可靠的结构化数据
    下一篇 →
    构建市场时光机:使用 Python 和 WebSocket 重放交易会话

    📌 相关推荐

    GraphRAG 是推理问题,而非数据库问题
    2026/8/30
    构建市场时光机:使用 Python 和 WebSocket 重放交易会话
    2026/8/30
    如何从LLM中获取可靠的结构化数据
    2026/8/28
    ← 返回文章列表