vLLM推理性能压测

vLLM推理性能压测

vLLM内存分配

vLLM 的 PageAttention 并不是为每个请求“预留出模型限定的长度的整个内存空间”,而是 按需分配 KV Cache 的“页面”(pages)。

  • KV Cache (Key-Value Cache): 在 Attention 机制中,每个 token 的 Key (K) 和 Value (V) 向量在计算 Attention 时会被缓存起来,避免重复计算。这些缓存是推理过程中内存消耗的主要部分。
  • PageAttention 的精髓:不定长分配,虚拟内存式管理。
    • 传统方式 (非 PageAttention): 很多推理框架会为每个请求预先分配一个固定大小的 KV Cache 缓冲区,这个大小通常就是模型的 max_model_len (例如 32K Token)。这意味着即使你只输入了 10 个 token,也会预分配 32K token 的空间。这导致大量的内存浪费,尤其是在处理大量短请求时。
    • vLLM 的 PageAttention: 它将 KV Cache 划分为固定大小的“页面”(pages)。当一个请求进入时,vLLM 不会立即分配整个 32K 的空间。它只会分配当前输入所需要的页面数量。随着生成过程的进行,如果需要更多空间来存储新的 KV 缓存,它会按需动态地分配新的页面。
    • “预留”的概念: 这里的“预留”更多指的是 逻辑上的最大上限。vLLM 知道每个请求最多可以扩展到 32K token 的长度,但它不会在物理内存中一次性分配 32K 这么大的一个连续块。它通过页面的方式,将这些 KV Cache 分散存储在 GPU 内存中,并且可以在多个请求之间共享物理内存页面(如果它们恰好有空闲页面)。

vLLM 为每个请求 逻辑上支持 到模型限定的最大长度,但 物理内存的分配是按需和按页进行的。它避免了传统方法中对短请求的巨大内存浪费,从而显著提高了 GPU 内存的利用率和吞吐量。

生成token预留

对于生成过程:

  1. 输入阶段 (Prompt Processing): 当用户输入 N 个 token (例如 “你是谁” 是 3 个 token) 时,vLLM 会为这 N 个 token 分配相应的 KV Cache 页面。
  2. 生成阶段 (Token Generation):
    • 每生成一个新的 token,这个新 token 的 KV 向量也需要存储到 KV Cache 中。
    • vLLM 会 按需分配新的 KV Cache 页面 来存储这些新生成的 token 的 KV 向量。
    • 这个过程会一直持续,直到:
      • 模型生成了结束符 (EOS token)。
      • 生成达到用户指定的最大生成长度 (e.g., max_new_tokens=50)。
      • 整个序列的长度 (输入 + 生成) 达到了模型的最大上下文长度 (例如 32K token)。

所以,对于生成,vLLM 也是按需分配,直到达到最大上下文长度。 如果模型最大支持 32K token,用户输入了 10 个 token,那么理论上还可以生成 32K - 10 = 31990 个 token。vLLM 会在生成过程中,每生成一个 token,就为其分配所需的 KV Cache 空间,直到达到这个上限。

模型分桶 (Model Bucketing)

  • 为什么要分桶?

    • 性能优化 (吞吐量): 尽管 vLLM 的 PageAttention 已经很高效,但模型本身在处理不同长度的序列时,计算量还是有差异的。对于短请求,如果它们都涌向一个配置为 128K 长度的模型,虽然内存浪费小了,但可能还是不如一个专门为短请求优化的模型(例如 32K 上下文)来得快。
    • 资源利用率: 如果所有请求都使用最大上下文的模型,即使是处理短请求,也可能因为模型内部的一些固定开销而效率不高。分桶可以确保不同长度的请求使用最适合它们的模型实例。
    • 成本控制: 运行一个支持 128K 上下文的模型通常需要更多的 GPU 显存和计算资源。如果大多数请求都是短的,那么为它们分配一个较小的模型可以节省资源。
  • 如何分桶?

    • 可以根据请求的输入长度或预期的输出长度,将其路由到不同 max_model_len 配置的模型实例。
    • 例如,一个服务可能部署了两个 deepseek-r1 模型实例:
      • 实例 A: 配置 max_model_len=8K,处理短请求。
      • 实例 B: 配置 max_model_len=32K,处理中等请求。
      • 实例 C: 配置 max_model_len=128K,处理长请求。
    • 当一个新请求到达时,根据其输入长度或预估总长度,将其发送到合适的实例。

性能测试指标

普通产品的测试指标主要包含平均响应时间和 QPS 等。而大模型由于在用户体验方面有一些特点,所以评估大模型性能的指标时就有些许不同。

吐字率(平均输出 token 速率/s):大模型问答多为流式输出的交互形式,系统不会等模型把答案都计算完毕才返回给用户,而是模型计算出一个 token 就会返回给用户一个 token(有些系统可能也会有合并若干个 token 然后输出给用户),这是因为模型思考的时间通常都会很长,如果不及时给反馈就会流失用户。所以在这种交互模式下,模型每秒钟能返回给用户多少个 token 就成为了一个非常重要的性能指标。单位时间内计算出的 token 越多,就证明模型的计算性能更好。
首 token 耗时(从用户发送问题到返回第一个 token 的时间): 吐字率的计算也是排除了首 token 的。计算第一个 token 和后续 token 是不一样的过程就可以了。 而首 token 的耗时直接影响了用户体验,毕竟如果用户问一个问题,结果几十秒都没有返回第一个字,那么也可能会流失用户。
QPM:这个指标比较容易理解,毕竟我们都是 QPS 是什么。但因为大模型问答的时间都太长了, 所以我们统计的是分钟维度的。

其他指标:

输入平均 token 数量(input tokens):用户输入的问题长度很大程度上影响了计算性能,输入越长,首 token 的耗时就越长,吐字率也会受到影响,所以我们测试的时候需要对齐测试数据是在什么量级上的。 一般来说可以把测试数据按长度进行分组,比如测试长文本的时候,可以分成 16k~32k, 32k ~ 48k,48K ~ 64k 以此类推。
输出平均 token 数量(output tokens):模型输出的答案有多长,这一点也影响到了 QPM 指标。 有些模型生成的答案和思考过程言简意赅,而有些模型的输出很长。所以在对比两个模型性能的时候,需要对齐输出的答案长度是否在一个量级。

Prefill 与 Decode

上述的推理过程其实被划分成了两个大的阶段,分别是 prefill(预填充)和 decode(解码)阶段。prefill 阶段负责计算 K 矩阵和 V 矩阵并生成首 token,并且 K,V 会保存到 cache 中,而 decode 负责用 Q 矩阵去从 kv cache 中拿出 K 和 V,与 K 矩阵计算注意力机制,再与 V 矩阵计算特征,并输出一个又一个的 token 返回给用户。 其中的 Q,K,V 可以先不用纠结,如果大家想知道是怎么回事,那就大概可以理解成 Q 代表着用户的问题,K 是自注意力机制,Q 与 K 计算是计算出模型应该在上下文中的哪些 token 放更多的注意力(也可以叫权重),而 V 暂时理解成特征矩阵,当 Q 与 K 计算后,要与 V 计算生成最终的 token。

prefill 和 decode 阶段明确以下几点:

  • prefill 和 decode 阶段是可以各自优化的(后面会说 PD 分离)
  • 通常首 token 是 prefill 阶段计算出来的,decode 阶段负责输出其余 token。所以首 token 耗时评估的是 prefill 阶段的性能,吐字率是评估 decode 阶段的性能,这个一定要记清楚。后面做单独优化的时候要知道什么时候关注首 token,什么时候关注吐字率。
  • prefill 针对相同的用户问题,计算出来的 k,v 一定是一模一样的。 所以 kv 才会被缓存到 cache 中,decode 要从 cache 里拉 k,v 去计算。 所以有一种优化方向就是这种缓存的分布式化(毕竟有很多模型实例,如果能共享 k,v cache 就能提升性能)。或者缓存还是本地的,但是上层的路由要做哈希一致性计算,保证相同前缀的用户问题能路由到同样的 prefill 节点上,这样才能保证缓存命中。 同时需要注意的是缓存的命中做的是前缀匹配,就是不需要用户问一模一样的问题还会命中缓存,哪怕只有前几个 token 是一样的,那这几个 token 也会使用缓存中的 kv 而不是重新计算。

所以除了上面提到的精度测试外,第二个测试场景出来了,那就是针对 prefill 的优化,很常见的一个方式就是针对 kv cache 的优化,比如原来这些 cache 是存到 GPU 显存中的,我们可以把它优化到存到内存中以节省显存。 这里的测试方法大体上还是之前的那一套,但是需要注意的是:

  • 在正常的性能测试中,为了避免命中 kv cache,我们通常都会在测试数据的最开始加上一个 uuid,只要前缀没有匹配到,那么就肯定无法命中缓存。而在优化 kv cache 的测试场景中,我们则要把 uuid 去掉,验证命中缓存后的性能收益。 PS:decode 阶段无法缓存,它必须每一次都重新计算生成 token,所以 kv cache 的优化在理论上对吐字率是没有明显影响的。
  • 有些问题的输出很长,比如数学计算的思考过程超级长,经常思考个几分钟,10 几分钟甚至几十分钟的。 而我们现在的测试场景是在优化 prefill 阶段,关注的是首 token 的提升,而非评价 decode 阶段的吐字率,所以有时候为了压缩测试时间,可以设置模型的 max_tokens 参数,该参数限制模型输出的 token 长度。所以可以把它设置成一个很小的值来减少测试需要的时间。
  • 测试方法可以使用同样的数据发送两次,验证第二次命中缓存后相比第一次节省了多少时间,但这只能作为基准测试,我们仍需评估上线后的缓存命中率和收益情况。因为:
    • 线上模型是分桶的,且每个桶有 N 多个模型实例,如果 kv cache 是保存在本地,那么想命中就一定要把相同前缀(可以是前 N 个字符)的用户请求路由到同一个模型实例上,同时还要保证负载均衡,不能让某个模型实例负载过高。 所以这个调度策略其实是决定了缓存的命中率的。
    • 线上每次命中多少 token 的前缀缓存是不确定的,也许命中了前 100 个 token,也可能命中 1W 个 token,所以不用真实的数据是评估不出来的。
    • 缓存是有上限的,我们粗算下来 1K 的 kv cache 差不多占用 75M 的内存(这是在我们的环境中的表现),对于长文本(比如 100K),一条用户请求下来就占用了 7.5G,所以在线上流量比较大的情况下,内存空间很快就会被沾满,这样就需要把老的 kv cache 淘汰掉。 这导致原本可能会命中的数据因为之前的缓存被淘汰掉而无法命中。

P/D 分离

P/D 分离是一年多前才开始有规模进行推广的一项优化技术,原本推理的 prefill 阶段和 decode 阶段是保存在一个模型实例中的,但我们发现如果优化了 prefill,就会影响 decode,而优化 decode 则会影响 prefill。而如果把它们分开,变成两个模型实例,一份用户请求过来后,先通过 prefill 实例去计算 kv cache 和首 token, 再传送到 decode 中计算其余 token,由 decode 来把答案反馈给用户。

把 P 和 D 分离后,就可以单独针对它们进行优化, 就不会再出现优化了 P 会影响 D 的情况,理论上就可以有更好的性能。但引入了 P/D 分离就又引入了很高的复杂度:

  • P 和 D 之间的通信是否可靠,上层路由调度的服务是否稳定可靠。如果他们之前的通信出现了问题,要如何处理。 这需要进行一些故障的测试。 比如 P 挂了,那按照设计,D 节点要降级成不分离方案继续为用户提供服务,同样如果 D 挂了,P 也要降级成不分离方案继续提供服务。 如果 P 和 D 都没有挂,但 P/D 之间出现了网络分区故障,那调度器要感知到并选择一个实例降级成不分离方案继续提供服务。
  • P 和 D 的数量配比要进行测试:1P/1D, 1P/3D , 2P/4D, 10P/10D 等等
  • P 和 D 除了要有 NP/ND 的配比外, 还需要分不同的 group, 每个 group 下都有 NP/ND,这是以防一个 group 出现问题,其他 group 能继续为用户服务,并且 group 也是一种分桶的形式。 比如 1 号和 2 号 group 是 16k 以下的短文本桶, 而 3 号和 4 号 group 是 16k 到 48k 的长文本桶, 5 号和 6 号 group 是 48k 到 128k 的超长文本桶。 测试人员需要在多 group 的形式下进行测试。 验证性能情况,调度策略(比如 kv cache 走前缀匹配,但如果第一个问题是长文本,第二个问题是短文本,但它又命中的第一个问题的前缀,那调度系统应该是遵循前缀匹配,让他命中缓存,还是遵循长文本走大桶,短文本走小桶的策略? 这个是需要讨论的)
  • P 和 D 降级之后的压测,因为 P 和 D 要保证在对方出问题后,自己能降级成不分离方案进行测试。我们要去测试它降级后的性能表现, 而且因为 P 和 D 的启动参数是不一样的,所以他们降级之后的性能表现可能也是不一样的。
  • P 和 D 的任务积压情况也要时刻关注,在不同的 P/D 数量配比下,有时候可能 P 的算力到极限了,导致首 token 指标激增。 有时候可能 D 的算力到极限了。所以要测试出不同的配比下,最合适的并发数量(模型是要限频的,超过并发数就要拦截,否则模型就被压爆了)。

MTP

MTP:多头预测,同样是对吐字率有巨大影响的加速方案,根据我们上面讲解到,模型的推理原理是使用用户的问题预测下一个 token,再用用户问题 + 这个 token 去预测再下一个 token,每一次只去计算一个 token。 而 MTP 则是一次性去计算多个 token, 那它是怎么做的呢?原理很复杂,我用大白话说一下:

  • 增加一个小模型(可能只有几层神经网络),让大模型预测一个 token A1,然后把这个 token 给 MTP 小模型,让小模型去使用 token A1 去预测下一个 token token A2’ (注意 A2 后面有一撇 ‘ 代表这是 MTP 小模型预测的)。
  • 再让大模型正常预测 token A2,让 MTP 小模型根据 token A2 去预测 token A3’,然后通过一个算法去校验 A2 和 A2’,这是为了验证 MTP 小模型预测的准确性,毕竟 MTP 小模型可能只有 1 层神经网络,它的准确性可能就是不好的,确定如果 MTP 预测的 A2’是准确的,那么就相信它预测的 A3’也是准确的。
  • 这样大模型不用再预测 A3 了, 直接使用小模型计算出的 A3’即可。 也就达到了多头预测的结果,如果有一个 MTP 小模型,那么就可以每次多预测一个 token,而如果有 3 个 MTP 小模型,就可以每次多预测 3 个 token。这样就可以大大的提升吐字率。

MTP 带来的收益取决于那个小模型的准确度,毕竟它可能只有 几 层神经网络,如果它计算不准,通不过校验,那每次就还是使用大模型来计算的,这样反而带来了负收益。 所以往往要关注 MTP 小模型的准确度,

注意:牢记 MTP 是提升吐字率的加速方案,测试时要注意首 token 不能出现波动,根据 MTP 小模型的数量来预期应该提升多少吐字率。

MTP 的核心思想:

  • 传统自回归推理: 逐个生成 token,每次生成一个 token 后,将新的序列作为输入,再次进行模型推理。这个过程是串行的,每次推理都涉及整个上下文。
  • MTP (多头预测/ speculative decoding / assisted generation / look-ahead decoding): 模型在生成当前 token 的同时,预测未来可能的几个 token。这些预测可以是:
    • 并行生成多个候选 token: 模型内部可能在同一个时间步尝试生成多个可能的下一个 token。
    • 草稿模型/小模型预测: 使用一个更小、更快的模型(草稿模型/draft model)快速预测未来几个 token 的序列。
    • 验证机制: 主模型(大模型)对草稿模型预测的多个 token 进行一次性验证。如果验证通过,这些 token 就被接受;如果验证不通过,则从第一个不通过的 token 处回退,然后主模型进行正常的自回归生成。

多头预测在实际场景中使用的多吗?

非常多,并且是当前大语言模型推理加速领域的一个重要研究方向和实际应用技术。

在当前主流的 LLM 推理加速文献中,更常见的名称是:

  1. Speculative Decoding (推测解码/草稿解码):这是目前最流行和最有效的 MTP 变体。它使用一个较小的 “草稿模型” (draft model) 快速生成一个短序列的候选 token,然后用一个较大的 “主模型” (main model) 并行验证这些 token。如果验证成功,可以一次性接受多个 token,从而显著提高吞吐量。
  2. Assisted Generation (辅助生成):与 Speculative Decoding 类似,通常指使用某种辅助机制来加速生成。
  3. Look-ahead Decoding (前瞻解码):广义上也可以指 MTP,即在当前时间步尝试预测未来的 token。

为什么它被广泛使用?

  • 显著的加速效果: Speculative Decoding 在保持生成质量不变的情况下,可以实现 2-3 倍甚至更高的推理速度提升。
  • 保持生成质量: 关键在于,最终输出的序列与不使用 MTP(即纯自回归解码)时主模型生成的序列是 完全一致的(或者说,是数学上等价的)。这是因为它有一个验证步骤,只有主模型确认的 token 才会被接受。
  • 通用性强: 适用于各种自回归大语言模型,无需修改模型架构。
  • 硬件友好: 虽然涉及到多次前向传播,但草稿模型的计算量很小,而主模型的验证可以并行进行,有效利用了 GPU 的并行计算能力。

实际应用案例:

  • Google 的 MedLM (PaLM 2-S): Google 在其 AI 产品中广泛使用了 Speculative Decoding。
  • Hugging Face transformers 库: 已经原生支持 Speculative Decoding,用户可以轻松地在自己的模型上启用。
  • 各大模型推理框架和库: 如 vLLM、DeepSpeed-MII 等高性能推理框架都在积极集成和优化 Speculative Decoding。
  • 开源社区和研究领域: 围绕 Speculative Decoding 的优化和改进(如如何选择草稿模型、如何更有效地验证等)是当前热门的研究方向。

TP/DP/EP/PP 并行加速

并行加速策略也是行业中非常常见的一种优化方法,我们都知道模型非常大,大到了一张 GPU 卡都装不下的程度。 而用户的问题也大到了可能有上百 K 的程度。所以其实大模型在真实场景下计算的是挺慢的。所以我们希望把模型按一定的策略进行拆分,拆成 N 个部分然后让 N 个 GPU 卡进行加载, 每个 GPU 只计算完整的神经网络中的一部分,从而达到并行计算,推理加速的目的。

如果每台机器是 8 块 GPU 卡,那么 P/D 分离至少要保证 2 台机器 16 张 GPU 卡。并且这 16 张卡中最多只有 8 个卡在做 prefill,8 个卡在做 decode,这是 P/D 分离架构限制死的,也就是说 P/D 分离最多就是 TP8 了。而我们在做性能测试的时候肯定是要对比的,跟历史版本对比,跟竞品对比,sglang 和 vllm 之间对比等等。 这里最常见的是 P/D 分离跟不分离的对比。而 P/D 不分离的方案就可以做 TP16 或者即便也是 TP8,但是两台机器的所有 GPU 都可以去算 prefill 阶段,并且一般不分离方案都是 prefill 优先策略,也就是 prefill 能抢到更多的算力去计算 kv cache 和首 token。

P/D 分离方案天然就是对首 token 不友好的,在同样的模型下,P/D 分离理论上在首 token 上是打不过非分离方案的,尤其会发现并发到达一定程度后,首 token 会激增,这是因为 P 的算力到达了极限,开始排队了。 但 P/D 分离在吐字率上是可以胜过非分离方案的。

evalscope 模型推理性能压测

本地Transformer推理

1
2
3
4
5
6
7
8
9
10
11
12
CUDA_VISIBLE_DEVICES=0 evalscope perf \
--parallel 1 \
--model Qwen/Qwen2.5-0.5B-Instruct \
--attn-implementation flash_attention_2 \
--log-every-n-query 5 \
--connect-timeout 6000 \
--read-timeout 6000 \
--max-tokens 2048 \
--min-tokens 2048 \
--api local \
--dataset speed_benchmark \
--debug

本地vLLM推理

1
2
3
4
5
6
7
8
9
10
11
CUDA_VISIBLE_DEVICES=0 evalscope perf \
--parallel 20 \
--model '/root/resume_summary/Qwen3-4B' \
--log-every-n-query 10 \
--connect-timeout 6000 \
--read-timeout 6000 \
--min-prompt-length 1024 \
--max-prompt-length 2048 \
--api local_vllm \
--dataset random

1
2
3
4
5
6
7
8
evalscope perf  \
--model '/root/resume_summary/Qwen3-4B'\
--number 20 \
--parallel 30 \
--api local_vllm \
--dataset openqa \
--tokenizer-path '/root/resume_summary/Qwen3-4B' \
--number 1000

测试日志:

radom数据集默认测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
2025-07-21 15:49:11,603 - evalscope - INFO - {
"Time taken for tests (s)": 524.3913,
"Number of concurrency": 1,
"Total requests": 20,
"Succeed requests": 20,
"Failed requests": 0,
"Output token throughput (tok/s)": 49.6633,
"Total token throughput (tok/s)": 50.7789,
"Request throughput (req/s)": 0.0381,
"Average latency (s)": 26.2191,
"Average time to first token (s)": 0.0254,
"Average time per output token (s)": 0.0201,
"Average input tokens per request": 29.25,
"Average output tokens per request": 1302.15,
"Average package latency (s)": 0.0201,
"Average package per request": 1302.15
}
Processing: 100%|█████████████████████████████████████████████████████████████████████████████████████| 20/20 [08:44<00:00, 26.22s/it]
2025-07-21 15:49:11,675 - evalscope - INFO -
Benchmarking summary:
+-----------------------------------+-----------+

| Key | Value |
| ---- | ----- |
| | |
| Time taken for tests (s) | 524.391 |
| ------------------------ | ------- |
| | |
| Number of concurrency | 1 |
| --------------------- | ---- |
| | |
| Total requests | 20 |
| -------------- | ---- |
| | |
| Succeed requests | 20 |
| ---------------- | ---- |
| | |
| Failed requests | 0 |
| --------------- | ---- |
| | |
| Output token throughput (tok/s) | 49.6633 |
| ------------------------------- | ------- |
| | |
| Total token throughput (tok/s) | 50.7789 |
| ------------------------------ | ------- |
| | |
| Request throughput (req/s) | 0.0381 |
| -------------------------- | ------ |
| | |
| Average latency (s) | 26.2191 |
| ------------------- | ------- |
| | |
| Average time to first token (s) | 0.0254 |
| ------------------------------- | ------ |
| | |
| Average time per output token (s) | 0.0201 |
| --------------------------------- | ------ |
| | |
| Average input tokens per request | 29.25 |
| -------------------------------- | ----- |
| | |
| Average output tokens per request | 1302.15 |
| --------------------------------- | ------- |
| | |
| Average package latency (s) | 0.0201 |
| --------------------------- | ------ |
| | |

| Average package per request | 1302.15 |
| --------------------------- | ------- |
| | |

2025-07-21 15:49:11,685 - evalscope - INFO -
Percentile results:

| Percentiles | TTFT (s) | ITL (s) | TPOT (s) | Latency (s) | Input tokens | Output tokens | Output (tok/s) | Total (tok/s) |
| ----------- | -------- | ------- | -------- | ----------- | ------------ | ------------- | ----------

| 10% | 0.0244 | 0.0199 | 0.02 | 17.5403 | 21 | 876 | 49.4577 | 50.3642 |
| ---- | ------ | ------ | ---- | ------- | ---- | ---- | ------- | ------- |
| | | | | | | | | |

| 25% | 0.0248 | 0.02 | 0.02 | 20.1436 | 26 | 1003 | 49.6068 | 50.5037 |
| ---- | ------ | ---- | ---- | ------- | ---- | ---- | ------- | ------- |
| | | | | | | | | |

| 50% | 0.025 | 0.0201 | 0.0201 | 26.651 | 28 | 1325 | 49.7166 | 50.866 |
| ---- | ----- | ------ | ------ | ------ | ---- | ---- | ------- | ------ |
| | | | | | | | | |

| 66% | 0.0251 | 0.0202 | 0.0201 | 29.7055 | 31 | 1474 | 49.7925 | 51.108 |
| ---- | ------ | ------ | ------ | ------- | ---- | ---- | ------- | ------ |
| | | | | | | | | |

| 75% | 0.0265 | 0.0203 | 0.0201 | 31.8914 | 34 | 1580 | 49.9372 | 51.1559 |
| ---- | ------ | ------ | ------ | ------- | ---- | ---- | ------- | ------- |
| | | | | | | | | |

| 80% | 0.0265 | 0.0203 | 0.0202 | 33.7972 | 37 | 1682 | 49.9423 | 51.2321 |
| ---- | ------ | ------ | ------ | ------- | ---- | ---- | ------- | ------- |
| | | | | | | | | |

| 90% | 0.0277 | 0.0205 | 0.0203 | 41.5339 | 41 | 2048 | 50.0938 | 52.6426 |
| ---- | ------ | ------ | ------ | ------- | ---- | ---- | ------- | ------- |
| | | | | | | | | |

| 95% | 0.0278 | 0.0206 | 0.0203 | 41.6199 | 45 | 2048 | 50.2308 | 52.8591 |
| ---- | ------ | ------ | ------ | ------- | ---- | ---- | ------- | ------- |
| | | | | | | | | |

| 98% | 0.0278 | 0.0207 | 0.0203 | 41.6199 | 45 | 2048 | 50.2308 | 52.8591 |
| ---- | ------ | ------ | ------ | ------- | ---- | ---- | ------- | ------- |
| | | | | | | | | |

| 99% | 0.0278 | 0.0207 | 0.0203 | 41.6199 | 45 | 2048 | 50.2308 | 52.8591 |
| ---- | ------ | ------ | ------ | ------- | ---- | ---- | ------- | ------- |
| | | | | | | | | |

+-------------+----------+---------+----------+-------------+--------------+---------------+----------------+---------------+
2025-07-21 15:49:11,685 - evalscope - INFO - Save the summary to: outputs/20250721_153833/Qwen3-4B
2025-07-21 15:49:11,685 - evalscope - INFO - Terminating the child process...
INFO 07-21 15:49:11 [launcher.py:80] Shutting down FastAPI HTTP server.
[rank0]:[W721 15:49:12.630540875 ProcessGroupNCCL.cpp:1476] Warning: WARNING: destroy_process_group() was not called before program exit, which can leak resources. For more info, please see https://pytorch.org/docs/stable/distributed.html#shutdown (function operator())
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
2025-07-21 15:49:14,001 - evalscope - INFO - Child process terminated.

radom 数据集1024-2048 input tokens测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
2025-07-21 17:00:41,554 - evalscope - INFO - {
"Time taken for tests (s)": 1554.3859,
"Number of concurrency": 1,
"Total requests": 50,
"Succeed requests": 50,
"Failed requests": 0,
"Output token throughput (tok/s)": 47.6079,
"Total token throughput (tok/s)": 99.9269,
"Request throughput (req/s)": 0.0322,
"Average latency (s)": 31.0872,
"Average time to first token (s)": 0.2238,
"Average time per output token (s)": 0.0208,
"Average input tokens per request": 1626.48,
"Average output tokens per request": 1480.02,
"Average package latency (s)": 0.0209,
"Average package per request": 1480.02
}
Processing: 100%|█████████████████████████████████████████████████████████████████████████████████████| 50/50 [25:54<00:00, 31.09s/it]
2025-07-21 17:00:41,726 - evalscope - INFO -
Benchmarking summary:
+-----------------------------------+-----------+
| Key | Value |
+===================================+===========+
| Time taken for tests (s) | 1554.39 |
+-----------------------------------+-----------+
| Number of concurrency | 1 |
+-----------------------------------+-----------+
| Total requests | 50 |
+-----------------------------------+-----------+
| Succeed requests | 50 |
+-----------------------------------+-----------+
| Failed requests | 0 |
+-----------------------------------+-----------+
| Output token throughput (tok/s) | 47.6079 |
+-----------------------------------+-----------+
| Total token throughput (tok/s) | 99.9269 |
+-----------------------------------+-----------+
| Request throughput (req/s) | 0.0322 |
+-----------------------------------+-----------+
| Average latency (s) | 31.0872 |
+-----------------------------------+-----------+
| Average time to first token (s) | 0.2238 |
+-----------------------------------+-----------+
| Average time per output token (s) | 0.0208 |
+-----------------------------------+-----------+
| Average input tokens per request | 1626.48 |
+-----------------------------------+-----------+
| Average output tokens per request | 1480.02 |
+-----------------------------------+-----------+
| Average package latency (s) | 0.0209 |
+-----------------------------------+-----------+
| Average package per request | 1480.02 |
+-----------------------------------+-----------+
2025-07-21 17:00:41,754 - evalscope - INFO -
Percentile results:
+-------------+----------+---------+----------+-------------+--------------+---------------+----------------+---------------+
| Percentiles | TTFT (s) | ITL (s) | TPOT (s) | Latency (s) | Input tokens | Output tokens | Output (tok/s) | Total (tok/s) |
+-------------+----------+---------+----------+-------------+--------------+---------------+----------------+---------------+
| 10% | 0.1728 | 0.0206 | 0.0205 | 10.4894 | 1212 | 495 | 47.0958 | 79.2054 |
| 25% | 0.1876 | 0.0207 | 0.0207 | 17.4452 | 1343 | 825 | 47.3555 | 85.8073 |
| 50% | 0.2227 | 0.0209 | 0.0208 | 38.006 | 1664 | 1798 | 47.6273 | 93.9511 |
| 66% | 0.2551 | 0.021 | 0.0209 | 42.8781 | 1817 | 2048 | 47.7724 | 110.4161 |
| 75% | 0.2576 | 0.021 | 0.0209 | 43.0006 | 1865 | 2048 | 47.8362 | 130.1493 |
| 80% | 0.2601 | 0.0211 | 0.021 | 43.1035 | 1885 | 2048 | 47.9719 | 145.9114 |
| 90% | 0.2742 | 0.0211 | 0.021 | 43.2356 | 1986 | 2048 | 48.113 | 237.1679 |
| 95% | 0.2764 | 0.0212 | 0.021 | 43.2852 | 2006 | 2048 | 48.2052 | 916.1667 |
| 98% | 0.3366 | 0.0213 | 0.0212 | 43.6579 | 2332 | 2048 | 48.3238 | 1156.3212 |
| 99% | 0.3366 | 0.0214 | 0.0212 | 43.6579 | 2332 | 2048 | 48.3238 | 1156.3212 |
+-------------+----------+---------+----------+-------------+--------------+---------------+----------------+---------------+
2025-07-21 17:00:41,754 - evalscope - INFO - Save the summary to: outputs/20250721_163343/Qwen3-4B
2025-07-21 17:00:41,754 - evalscope - INFO - Terminating the child process...
INFO 07-21 17:00:41 [launcher.py:80] Shutting down FastAPI HTTP server.
[rank0]:[W721 17:00:42.531920666 ProcessGroupNCCL.cpp:1476] Warning: WARNING: destroy_process_group() was not called before program exit, which can leak resources. For more info, please see https://pytorch.org/docs/stable/distributed.html#shutdown (function operator())
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
2025-07-21 17:00:43,670 - evalscope - INFO - Child process terminated.

30并发数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
2025-07-21 18:12:35,205 - evalscope - INFO - {
"Time taken for tests (s)": 159.331,
"Number of concurrency": 30,
"Total requests": 50,
"Succeed requests": 50,
"Failed requests": 0,
"Output token throughput (tok/s)": 441.9164,
"Total token throughput (tok/s)": 952.3255,
"Request throughput (req/s)": 0.3138,
"Average latency (s)": 70.9088,
"Average time to first token (s)": 4.5971,
"Average time per output token (s)": 0.049,
"Average input tokens per request": 1626.48,
"Average output tokens per request": 1408.22,
"Average package latency (s)": 0.0471,
"Average package per request": 1408.22
}
Processing: 100%|████████████████████████████████████████████████████████████| 50/50 [02:39<00:00, 3.19s/it]
2025-07-21 18:12:35,384 - evalscope - INFO -
Benchmarking summary:
+-----------------------------------+-----------+
| Key | Value |
+===================================+===========+
| Time taken for tests (s) | 159.331 |
+-----------------------------------+-----------+
| Number of concurrency | 30 |
+-----------------------------------+-----------+
| Total requests | 50 |
+-----------------------------------+-----------+
| Succeed requests | 50 |
+-----------------------------------+-----------+
| Failed requests | 0 |
+-----------------------------------+-----------+
| Output token throughput (tok/s) | 441.916 |
+-----------------------------------+-----------+
| Total token throughput (tok/s) | 952.326 |
+-----------------------------------+-----------+
| Request throughput (req/s) | 0.3138 |
+-----------------------------------+-----------+
| Average latency (s) | 70.9088 |
+-----------------------------------+-----------+
| Average time to first token (s) | 4.5971 |
+-----------------------------------+-----------+
| Average time per output token (s) | 0.049 |
+-----------------------------------+-----------+
| Average input tokens per request | 1626.48 |
+-----------------------------------+-----------+
| Average output tokens per request | 1408.22 |
+-----------------------------------+-----------+
| Average package latency (s) | 0.0471 |
+-----------------------------------+-----------+
| Average package per request | 1408.22 |
+-----------------------------------+-----------+
2025-07-21 18:12:35,409 - evalscope - INFO -
Percentile results:
+-------------+----------+---------+----------+-------------+--------------+---------------+----------------+---------------+
| Percentiles | TTFT (s) | ITL (s) | TPOT (s) | Latency (s) | Input tokens | Output tokens | Output (tok/s) | Total (tok/s) |
+-------------+----------+---------+----------+-------------+--------------+---------------+----------------+---------------+
| 10% | 0.3015 | 0.0298 | 0.0413 | 28.8175 | 1212 | 335 | 13.2633 | 35.1993 |
| 25% | 0.3762 | 0.0403 | 0.0456 | 35.2627 | 1343 | 644 | 18.9711 | 37.195 |
| 50% | 3.3229 | 0.0449 | 0.0491 | 81.9319 | 1664 | 1813 | 19.3318 | 42.0375 |
| 66% | 5.1278 | 0.0471 | 0.05 | 99.0622 | 1817 | 2048 | 19.9106 | 46.6728 |
| 75% | 5.9427 | 0.0474 | 0.0504 | 105.2283 | 1865 | 2048 | 20.6739 | 54.8875 |
| 80% | 6.2452 | 0.0475 | 0.0507 | 106.4079 | 1885 | 2048 | 21.015 | 62.8967 |
| 90% | 7.2961 | 0.0479 | 0.0528 | 106.6621 | 1986 | 2048 | 23.6787 | 119.0351 |
| 95% | 23.5217 | 0.0481 | 0.0732 | 106.8906 | 2006 | 2048 | 24.1472 | 193.4843 |
| 98% | 31.062 | 0.0489 | 0.0972 | 106.9622 | 2332 | 2048 | 27.1139 | 203.4513 |
| 99% | 31.062 | 0.2826 | 0.0972 | 106.9622 | 2332 | 2048 | 27.1139 | 203.4513 |
+-------------+----------+---------+----------+-------------+--------------+---------------+----------------+---------------+
2025-07-21 18:12:35,409 - evalscope - INFO - Save the summary to: outputs/20250721_180852/Qwen3-4B

300并发数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
+-----------------------------------+-----------+
| Key | Value |
+===================================+===========+
| Time taken for tests (s) | 171.463 |
+-----------------------------------+-----------+
| Number of concurrency | 300 |
+-----------------------------------+-----------+
| Total requests | 50 |
+-----------------------------------+-----------+
| Succeed requests | 50 |
+-----------------------------------+-----------+
| Failed requests | 0 |
+-----------------------------------+-----------+
| Output token throughput (tok/s) | 450.949 |
+-----------------------------------+-----------+
| Total token throughput (tok/s) | 925.244 |
+-----------------------------------+-----------+
| Request throughput (req/s) | 0.2916 |
+-----------------------------------+-----------+
| Average latency (s) | 104.164 |
+-----------------------------------+-----------+
| Average time to first token (s) | 10.4115 |
+-----------------------------------+-----------+
| Average time per output token (s) | 0.0801 |
+-----------------------------------+-----------+
| Average input tokens per request | 1626.48 |
+-----------------------------------+-----------+
| Average output tokens per request | 1546.42 |
+-----------------------------------+-----------+
| Average package latency (s) | 0.0606 |
+-----------------------------------+-----------+
| Average package per request | 1546.42 |
+-----------------------------------+-----------+
2025-07-21 18:26:47,167 - evalscope - INFO -
Percentile results:
+-------------+----------+---------+----------+-------------+--------------+---------------+----------------+---------------+
| Percentiles | TTFT (s) | ITL (s) | TPOT (s) | Latency (s) | Input tokens | Output tokens | Output (tok/s) | Total (tok/s) |
+-------------+----------+---------+----------+-------------+--------------+---------------+----------------+---------------+
| 10% | 1.6911 | 0.0295 | 0.0532 | 36.4678 | 1212 | 399 | 8.1124 | 20.4868 |
| 25% | 3.0504 | 0.0461 | 0.0544 | 88.4334 | 1343 | 1098 | 12.7055 | 27.7091 |
| 50% | 6.2555 | 0.0474 | 0.0551 | 116.5973 | 1664 | 2048 | 16.963 | 33.0331 |
| 66% | 8.3319 | 0.0486 | 0.0594 | 117.0038 | 1817 | 2048 | 17.5325 | 35.8655 |
| 75% | 9.3036 | 0.049 | 0.0666 | 120.7334 | 1865 | 2048 | 17.5716 | 37.48 |
| 80% | 9.9621 | 0.0496 | 0.0712 | 141.4979 | 1885 | 2048 | 17.6667 | 43.7002 |
| 90% | 11.3036 | 0.05 | 0.1137 | 154.7253 | 1986 | 2048 | 17.9381 | 72.2866 |
| 95% | 14.5828 | 0.0502 | 0.1282 | 165.228 | 2006 | 2048 | 18.0516 | 115.6813 |
| 98% | 116.3881 | 0.2272 | 0.8727 | 171.3481 | 2332 | 2048 | 18.1686 | 145.1348 |
| 99% | 116.3881 | 0.3302 | 0.8727 | 171.3481 | 2332 | 2048 | 18.1686 | 145.1348 |
+-------------+----------+---------+----------+-------------+--------------+---------------+----------------+---------------+
2025-07-21 18:26:47,167 - evalscope - INFO - Save the summary to: outputs/20250721_182242/Qwen3-4B

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
025-07-21 18:30:37,015 - evalscope - INFO - {
"Time taken for tests (s)": 124.9001,
"Number of concurrency": 300,
"Total requests": 40,
"Succeed requests": 40,
"Failed requests": 0,
"Output token throughput (tok/s)": 458.2943,
"Total token throughput (tok/s)": 986.1163,
"Request throughput (req/s)": 0.3203,
"Average latency (s)": 92.9427,
"Average time to first token (s)": 10.0523,
"Average time per output token (s)": 0.0617,
"Average input tokens per request": 1648.125,
"Average output tokens per request": 1431.025,
"Average package latency (s)": 0.0579,
"Average package per request": 1431.025
}
Processing: 15%|████████▊ | 45/300 [02:29<18:56, 4.46s/it

300并发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
Benchmarking summary:
+-----------------------------------+-----------+
| Key | Value |
+===================================+===========+
| Time taken for tests (s) | 862.26 |
+-----------------------------------+-----------+
| Number of concurrency | 300 |
+-----------------------------------+-----------+
| Total requests | 300 |
+-----------------------------------+-----------+
| Succeed requests | 300 |
+-----------------------------------+-----------+
| Failed requests | 0 |
+-----------------------------------+-----------+
| Output token throughput (tok/s) | 521.695 |
+-----------------------------------+-----------+
| Total token throughput (tok/s) | 1094.9 |
+-----------------------------------+-----------+
| Request throughput (req/s) | 0.3479 |
+-----------------------------------+-----------+
| Average latency (s) | 453.685 |
+-----------------------------------+-----------+
| Average time to first token (s) | 364.146 |
+-----------------------------------+-----------+
| Average time per output token (s) | 0.0612 |
+-----------------------------------+-----------+
| Average input tokens per request | 1647.5 |
+-----------------------------------+-----------+
| Average output tokens per request | 1499.46 |
+-----------------------------------+-----------+
| Average package latency (s) | 0.0597 |
+-----------------------------------+-----------+
| Average package per request | 1499.46 |
+-----------------------------------+-----------+
2025-07-21 18:42:55,422 - evalscope - INFO -
Percentile results:
+-------------+----------+---------+----------+-------------+--------------+---------------+----------------+---------------+
| Percentiles | TTFT (s) | ITL (s) | TPOT (s) | Latency (s) | Input tokens | Output tokens | Output (tok/s) | Total (tok/s) |
+-------------+----------+---------+----------+-------------+--------------+---------------+----------------+---------------+
| 10% | 7.6619 | 0.0472 | 0.0548 | 119.9869 | 1193 | 444 | 0.9646 | 3.755 |
| 25% | 125.1115 | 0.0477 | 0.0567 | 239.9528 | 1371 | 891 | 2.1703 | 4.7951 |
| 50% | 356.3014 | 0.0484 | 0.0578 | 461.2453 | 1657 | 1883 | 3.2939 | 6.8899 |
| 66% | 474.6099 | 0.0491 | 0.059 | 585.7884 | 1849 | 2048 | 4.7121 | 9.9237 |
| 75% | 580.7949 | 0.0505 | 0.0621 | 670.6546 | 1920 | 2048 | 6.1084 | 12.6201 |
| 80% | 593.6266 | 0.0509 | 0.0639 | 708.4548 | 1972 | 2048 | 7.7167 | 15.7069 |
| 90% | 708.7958 | 0.0513 | 0.0723 | 799.7401 | 2062 | 2048 | 16.028 | 30.2447 |
| 95% | 763.0271 | 0.0518 | 0.084 | 820.0354 | 2132 | 2048 | 16.967 | 38.0616 |
| 98% | 789.6511 | 0.3434 | 0.1006 | 857.1797 | 2264 | 2048 | 17.1682 | 43.895 |
| 99% | 790.7011 | 0.3534 | 0.1368 | 861.3407 | 2340 | 2048 | 17.2395 | 50.9503 |
+-------------+----------+---------+----------+-------------+--------------+---------------+----------------+---------------+
2025-07-21 18:42:55,422 - evalscope - INFO - Save the summary to: outputs/20250721_182728/Qwen3-4B

各个指标含义:

指标 含义
Number of concurrency 并发数
Total requests 一共跑了 50 个 prompt
Output token throughput (tok/s) 每秒生成多少个输出 token,约 47.6 tok/s
Total token throughput (tok/s) 每秒处理的总 token(输入+输出),约 99.9 tok/s
Request throughput (req/s) 每秒能完成多少个完整请求,0.032 req/s(非常慢)
Average latency (s) 每个请求从提交到生成完成平均耗时(含排队+生成),约 31.1 秒
Average time per output token (s) 单个输出 token 平均生成耗时,约 20.8 ms/token
Average time to first token (s) 从提交请求到拿到第一个 token 的时间,约 0.22 秒
1. Percentiles (百分位数)
  • 意义: 百分位数用于描述数据分布,尤其在延迟测量中非常有用。它表示在某个特定百分位点上,有多少比例的请求的延迟低于该值。
  • 如何计算: 将所有测量到的数据点(例如,所有请求的延迟时间)从小到大排序。某个百分位数 P 的值就是排序后位于 P% 位置的数据点。
    • P50 (50th percentile) / Median (中位数): 表示 50% 的请求的延迟低于这个值。它是最常用的平均指标,因为它不受极端值(离群值)的影响。
    • P90 (90th percentile): 表示 90% 的请求的延迟低于这个值。这对于了解大多数用户体验到的“最差”情况很有用。
    • P95 (95th percentile): 表示 95% 的请求的延迟低于这个值。
    • P99 (99th percentile): 表示 99% 的请求的延迟低于这个值。这通常用于识别服务的尾部延迟(tail latency),即少数请求可能遇到的最长延迟。
  • 含义:
    • 低百分位数(如 P50) 告诉你大多数用户的体验。
    • 高百分位数(如 P90, P95, P99) 告诉你服务在最繁忙或最困难情况下的表现。高百分位数的延迟越低,说明服务越稳定,对所有用户都提供了一致的良好体验。

2. TTFT (s) - Time To First Token (秒)
  • 意义: 从发送请求到接收到模型生成的第一个令牌所需的时间。
  • 如何计算:
    TTFT = (收到第一个输出令牌的时间) - (发送请求的时间)
  • 含义: 这是用户感知到的初始响应速度。TTFT 越低,用户会感觉模型响应越快,等待时间越短。对于交互式应用(如聊天机器人),这是一个非常关键的指标。

3. ITL (s) - Inter-Token Latency (秒)
  • 意义: 模型生成连续两个令牌之间的时间间隔。
  • 如何计算:
    ITL = (生成第二个令牌的时间 - 生成第一个令牌的时间)
    ITL = (生成第三个令牌的时间 - 生成第二个令牌的时间)
    …以此类推,然后取这些时间间隔的平均值或百分位数。
  • 含义: ITL 反映了模型生成后续内容的流畅性持续输出速度。ITL 越低,模型输出内容的速度就越快,用户在等待完整回复时会感觉内容是连续“流”出来的,而不是卡顿。

4. TPOT (s) - Time Per Output Token (秒)
  • 意义: 模型每生成一个输出令牌所需的平均时间。
  • 如何计算:
    TPOT = (总输出生成时间) / (总输出令牌数)
    其中,总输出生成时间通常是 (收到最后一个输出令牌的时间) - (收到第一个输出令牌的时间)
    或者更严格地,对于单个请求:TPOT = (Total Latency - TTFT) / (Output Tokens - 1)
  • 含义: TPOT 是 ITL 的一个广义概念,它衡量了整个输出过程的效率。TPOT 越低,意味着模型生成每个令牌的速度越快。它与 Output (tok/s) 互为倒数关系。

5. La5tency (s) - Total Latency (秒)
  • 意义: 从发送请求到接收到模型生成的所有令牌所需的时间(即完整响应的时间)。
  • 如何计算:
    Total Latency = (收到最后一个输出令牌的时间) - (发送请求的时间)
  • 含义: 这是用户感知到的总响应时间,即从提问到看到完整答案的总等待时间。对于需要完整回复才能进行下一步操作的应用,这是一个非常重要的指标。
  • 关系: Total Latency ≈ TTFT + (Output Tokens - 1) * ITL (这是一个近似值,因为 ITL 通常是平均值)。

6. Input tokens
  • 意义: 单个请求中提示(prompt)的令牌数量。
  • 如何计算: 统计请求文本在模型分词器(tokenizer)下产生的令牌数量。
  • 含义: 衡量了输入内容的长度。输入令牌越多,模型处理的负担越大,通常会导致更长的 TTFT 和 Total Latency。

7. Output tokens
  • 意义: 单个请求中模型生成的响应的令牌数量。
  • 如何计算: 统计模型生成响应文本在模型分词器下产生的令牌数量。
  • 含义: 衡量了输出内容的长度。输出令牌越多,Total Latency 越长,但如果 ITL 稳定,Output (tok/s) 也会相对稳定。

8. Output (tok/s) - Output Tokens Per Second
  • 意义: 模型每秒生成输出令牌的数量。
  • 如何计算:
    Output (tok/s) = (Output tokens) / (Total Latency - TTFT) 或者 Output (tok/s) = 1 / TPOT
    或者对于整个测试会话,是所有请求的 Total num output tokens / 总生成时间
  • 含义: 这是衡量模型纯粹生成速度的关键指标。数值越高,表示模型生成内容越快。

9. Total (tok/s) - Total Tokens Per Second
  • 意义: 模型每秒处理的总令牌数量(包括输入令牌和输出令牌)。
  • 如何计算:
    Total (tok/s) = (Input tokens + Output tokens) / Total Latency
    或者对于整个测试会话,是所有请求的 (Total num prompt tokens + Total num output tokens) / 总运行时间
  • 含义: 这是衡量模型整体吞吐量的指标。它反映了模型处理给定负载的效率。数值越高,表示系统整体处理能力越强。

总结图表:
参数 意义 理想值 影响因素
Percentiles 数据分布(如延迟的 P50, P90, P99) 越低越好 (延迟) 模型架构、硬件、负载、网络
TTFT (s) 首次响应时间 越低越好 模型大小、提示长度、硬件、网络
ITL (s) 连续令牌生成间隔 越低越好 模型架构、硬件、负载
TPOT (s) 每输出令牌时间 越低越好 模型架构、硬件、负载
Latency (s) 总响应时间 越低越好 TTFT, ITL, 输出令牌数, 提示长度
Input tokens 输入提示的令牌数 - 用户输入长度
Output tokens 输出响应的令牌数 - 模型生成内容长度
Output (tok/s) 每秒输出令牌数 (生成速度) 越高越好 模型架构、硬件、负载、输出令牌复杂度
Total (tok/s) 每秒总令牌数 (整体吞吐量) 越高越好 模型架构、硬件、负载、输入/输出令牌总数

max_num_batched_seq和max_num_batched_tokens配置

已有条件: Qwen3-4B,GPU 为 NVIDIA A10 24GB,输入 prompt 平均 1000 tokens,目标是找出 最合理的 max_num_batched_seq(最大并发请求数)和 max_num_batched_tokens(最大token数)配置


一、核心思路

这两个参数背后的瓶颈是:KV Cache 在显存中所占空间

显存消耗主要来自三个部分:

  1. ✅ 模型本体权重(不可变)
  2. ✅ 每个 token 的 KV cache(attention 缓存,持续增长)
  3. ✅ 临时显存(调度器 buffer、activation、显卡系统开销)

二、KV Cache 计算公式(近似)

每个 token 的 KV cache 占用量:

1
KV size ≈ num_layers × num_heads × head_dim × 2 × bytes_per_fp

对 Qwen3-4B(推测配置):

参数
层数 L 32
注意力头数 H 32
每头维度 D 128
数据类型 FP16 (2 bytes)
单 token KV 空间 32×32×128×2×2 = 524,288 bytes ≈ 0.5 MB/token

一个 token ≈ 0.5MB KV Cache


三、计算你能容纳多少 token?

A10 24GB 显卡,要预留空间:

  • 模型参数:Qwen3-4B FP16 大概占 ~8GB(估算)
  • buffer+activation:~4GB
  • 系统保留+上下文处理开销:~2GB

剩余用于 KV Cache 的大概是:
24GB - 8GB - 4GB - 2GB = ~10GB

所以:最多可支持的 tokens ≈ 10GB / 0.5MB ≈ 20,000 tokens


四、根据你的 prompt 情况来推算
  • 输入 prompt ≈ 1000 tokens
  • 假设输出也要 200 tokens
  • 单请求 token ≈ 1200 tokens

那我们最多能支持的请求数(即 batch 大小)为:20000 / 1200 ≈ 16.6 → 最大并发请求数 ≈ 16


五、推荐设置参数
参数 推荐值 理由
--max-num-batched-seq 16 符合 A10 显存承受上限
--max-num-batched-tokens 20480 16 请求 × 1280 tokens(富余一些)

六、如需更激进一点压榨显存:

可以尝试:

1
2
--max-num-batched-seq 20
--max-num-batched-tokens 24000

但你需要:

  • 控制输出长度,避免生成 token 无限增长;
  • 启用 --max-model-len 限制上下文总长(如 2048);
  • 启用 swap-to-CPU(A10 没 NVLink,不建议频繁 swap);
    • 在显存不够时,把暂时不用的中间张量(如 KV cache)转移到 CPU 内存里,等用到时再搬回来。
    • A10 没有 NVLink:GPU <-> CPU 之间的数据传输只能走 PCIe 通道,带宽低、延迟高。
    • swap 操作涉及 频繁搬运大块张量数据(比如几个 GB 的 KV cache),这在 PCIe 上非常慢,直接拖垮性能。

❗ 注意事项
  • 如果你发现吞吐不高,优先关注日志中的:GPU KV cache usage 是否爆满。
  • 建议配合 --disable-log-requests 以避免调度器被打印卡住。
  • A10 是消费级卡,吞吐和调度能力远不如 A100、H100,需要合理 batch 大小来避免队列长时间 pending。