编辑
2026-08-04
AI Infra
00

目录

step_comparison.csv 口头讲解稿
一、先看一整行代表什么
二、字段含义
1. case
2. step
3. request_ids
4. slot_ids
5. seq_ids
6. states
7. query_lens
8. kvbefore 和 kvafter
9. logicalbatchtokens
10. ubatch_tokens
11. mask_shapes
12. 各阶段耗时字段
三、按照 CSV 实际运行过程讲解
第 0 周期:首次 prefill
第 1 周期:第一次 decode
第 2、3 周期:稳定 decode
第 4 周期:动态加入第 5 个请求
第 5 周期:5 个请求全部 decode
四、推荐的完整口头串讲
五、看这份文件时最重要的三条规律

step_comparison.csv 口头讲解稿

本文配合文件:

text
E:\Code\llama.cpp_experiment\repro_20260804\repro_20260804_dynamic_4plus1\step_comparison.csv

这份 CSV 记录的是 llama.cpp 连续批处理过程中,每一个调度周期的状态。讲解时可以把每一行理解为:

“服务器在这一轮选择了哪些请求?每个请求处理了多少 token?KV Cache 发生了什么变化?这一轮花了多少时间?”

一、先看一整行代表什么

CSV 的每一行对应一个 step,也就是一次调度周期。

例如第 4 行:

text
dynamic_4plus1,4,"[0,1,2,3,4]","[2,1,0,3,4]","[2,1,0,3,4]","[decode,decode,decode,decode,prefill]","[1,1,1,1,640]","[515,259,131,1027,0]","[516,260,132,1028,640]",644,"[512,132]",...,43.333

口头上可以这样说:

“这是 dynamic_4plus1 实验的第 4 个调度周期。此时一共有 5 个请求,其中前 4 个请求正在 decode,每个请求处理 1 个 token;第 5 个请求刚刚加入,正在 prefill,一次处理 640 个 token。整个逻辑批次一共 644 个 token,受 512 的 ubatch 大小限制,被拆成 512 和 132 两个小批次。这一轮总共耗时 43.333 毫秒。”

二、字段含义

1. case

实验名称。

本文件中都是:

text
dynamic_4plus1

它表示 4 个请求先运行,第 5 个请求动态加入。

2. step

调度周期编号,从 0 开始。

可以理解为服务器循环中的第几轮,而不是某个请求的第几个 token。

例如:

text
step=0:首次处理 prompt step=1:第一次 decode step=4:新请求加入 step=5:5 个请求全部 decode

3. request_ids

本轮参与计算的请求编号。

例如:

text
[0,1,2,3]

表示本轮只有请求 0、1、2、3。

到了第 4 周期:

text
[0,1,2,3,4]

表示请求 4 已经被调度器加入。

4. slot_ids

本轮请求对应的逻辑槽位:

text
request 0 -> slot 2 request 1 -> slot 1 request 2 -> slot 0 request 3 -> slot 3 request 4 -> slot 4

例如第 0 周期:

text
request_ids = [0,1,2,3] slot_ids = [2,1,0,3]

应解释为:请求 0 使用槽位 2,请求 1 使用槽位 1,而不是请求 0 使用槽位 0。两个数组按照相同位置一一对应。

5. seq_ids

KV Cache 使用的序列编号。

本实验中它与 slot 的编号恰好相同,但概念不同。slot_id 表示服务器槽位,seq_id 用于识别 KV Cache 所属序列。

它保证不同请求虽然被放在同一 batch 中计算,但不会互相读取上下文。

6. states

表示每个请求本轮的处理阶段:

text
prefill:处理输入 prompt decode:根据上下文生成后续 token

数组与 request_ids 一一对应。

例如:

text
request_ids = [0,1,2,3,4] states = [decode,decode,decode,decode,prefill]

表示请求 0~3 继续生成,第 5 个请求刚开始处理 prompt。

7. query_lens

每个请求在本轮送入模型的 token 数量。

第 0 周期:

text
[512,256,128,1024]

表示 4 个请求分别处理对应长度的 prompt。

第 1 周期:

text
[1,1,1,1]

表示每个请求各处理 1 个 decode token。

第 4 周期:

text
[1,1,1,1,640]

表示旧请求各处理 1 个 token,新请求一次处理 640 个 prompt token。

8. kv_beforekv_after

分别表示本轮开始和结束时,每个请求拥有的 KV Cache 长度。

第 4 周期:

text
kv_before = [515,259,131,1027,0] kv_after = [516,260,132,1028,640]

逐项解释:

text
request 0:515 -> 516,新增 1 个 decode token request 1:259 -> 260,新增 1 个 decode token request 2:131 -> 132,新增 1 个 decode token request 3:1027 -> 1028,新增 1 个 decode token request 4:0 -> 640,完成 640 个 prompt token 的 prefill

因此可以用 kv_after - kv_before 判断本轮每个请求实际写入了多少 KV。

9. logical_batch_tokens

本轮所有请求要处理的 token 总数。

第 0 周期:

text
512 + 256 + 128 + 1024 = 1920

所以:

text
logical_batch_tokens = 1920

第 4 周期:

text
1 + 1 + 1 + 1 + 640 = 644

所以:

text
logical_batch_tokens = 644

10. ubatch_tokens

表示 logical batch 被拆成了哪些物理小批次。

第 0 周期:

text
logical batch = 1920 ubatch_tokens = [512,512,512,384]

第 4 周期:

text
logical batch = 644 ubatch_tokens = [512,132]

口头解释为:

“逻辑上这一轮需要处理 644 个 token,但单个物理批次最多容纳 512 个,所以先执行 512 个,再执行剩下的 132 个。”

11. mask_shapes

表示 attention mask 的形状,记录模型本轮如何组织 query token 和可见 KV 范围。

例如第 0 周期:

text
[[512,512,1,1], [1024,512,1,1], [1536,512,1,1], [2048,384,1,1]]

重点看前两个数字:

  • 第一个数字表示可见 KV 范围;
  • 第二个数字表示本次 query token 数。

所以 [1024,512,1,1] 不是说本轮输入了 1024 个 query,而是说本轮 query 为 512 个,但 attention 可以看到的 KV 范围已经达到 1024。

第 4 周期:

text
[[2560,512,1,1],[2816,132,1,1]]

这对应 644 个 token 被拆成 512 和 132 两个 ubatch。

12. 各阶段耗时字段

这些字段的单位都是毫秒:

字段口头解释
batch_build_latency_ms组织本轮 batch 的时间
kv_allocate_latency_ms分配 KV Cache 空间的时间
mask_build_latency_ms构造 attention mask 的时间
graph_build_latency_ms构造或准备计算图的时间
ubatch_compute_latency_ms执行一个或多个 ubatch 的模型计算时间
decode_call_latency_ms调用 decode 执行接口的整体时间
sampling_latency_ms根据 logits 采样下一个 token 的时间
tensor_dump_latency_ms导出 tensor 用于调试跟踪的时间
step_total_latency_ms这一整个调度周期的总耗时

这些时间不一定简单相加,因为部分操作可能包含内部调用、同步或统计开销。实际性能比较时,优先关注 step_total_latency_ms,并区分 prefill 周期和 decode 周期。

三、按照 CSV 实际运行过程讲解

第 0 周期:首次 prefill

text
states = [prefill,prefill,prefill,prefill] query_lens = [512,256,128,1024] kv_before = [0,0,0,0] kv_after = [512,256,128,1024] logical = 1920 ubatches = [512,512,512,384] total = 200.751 ms

口头讲解:

“服务器先同时接收 4 个请求。因为它们都还没有上下文,所以状态全是 prefill。四个 prompt 总共 1920 个 token。由于单个 ubatch 只能处理 512 个 token,实际分成 4 次计算。这一轮结束后,4 个请求的 KV 长度分别变成 512、256、128 和 1024。因为这是大规模 prompt 计算,所以耗时最高,为 200.751 毫秒。”

第 1 周期:第一次 decode

text
states = [decode,decode,decode,decode] query_lens = [1,1,1,1] kv_before = [512,256,128,1024] kv_after = [513,257,129,1025] logical = 4 ubatches = [4] total = 15.052 ms

口头讲解:

“第 0 周期已经完成 prompt,因此现在 4 个请求都进入 decode。每个请求本轮只输入 1 个 token,总共 4 个 token,一个 ubatch 就能完成。每个请求的 KV 长度增加 1,周期耗时下降到 15.052 毫秒。”

第 2、3 周期:稳定 decode

第 2 周期:

text
kv_before = [513,257,129,1025] kv_after = [514,258,130,1026] total = 13.061 ms

第 3 周期:

text
kv_before = [514,258,130,1026] kv_after = [515,259,131,1027] total = 10.213 ms

口头讲解:

“这两轮没有新请求加入,4 个请求每轮各生成一个 token。所以每一轮的 query 长度都为 [1,1,1,1],KV 长度逐项加 1,batch 大小保持为 4。”

第 4 周期:动态加入第 5 个请求

text
states = [decode,decode,decode,decode,prefill] query_lens = [1,1,1,1,640] kv_before = [515,259,131,1027,0] kv_after = [516,260,132,1028,640] logical = 644 ubatches = [512,132] total = 43.333 ms

口头讲解:

“这一轮可以看到连续批处理最核心的行为。原来的 4 个请求没有停下来,它们继续 decode,每个处理 1 个 token。同时第 5 个请求加入,状态是 prefill,需要处理 640 个 prompt token。于是本轮总共处理 644 个 token,拆成 512 和 132 两个 ubatch。旧请求的 KV 各增加 1,而新请求的 KV 从 0 直接建立到 640。”

第 5 周期:5 个请求全部 decode

text
states = [decode,decode,decode,decode,decode] query_lens = [1,1,1,1,1] kv_before = [516,260,132,1028,640] kv_after = [517,261,133,1029,641] logical = 5 ubatches = [5] total = 27.659 ms

口头讲解:

“第 4 周期已经完成了请求 4 的 prompt 处理,所以第 5 周期 5 个请求全部进入 decode。每个请求各处理一个 token,总共 5 个 token,一个 ubatch 就够。请求 4 的 KV 从 640 增加到 641,说明它现在已经和其他请求一样进入逐 token 生成阶段。”

四、推荐的完整口头串讲

可以直接按照下面这段话讲解:

“我们先看第 0 行。这里有 4 个请求,状态全部是 prefill,说明服务器正在处理输入 prompt。它们的长度是 512、256、128 和 1024,加起来一共 1920 个 token。因为 ubatch-size 是 512,所以被拆成 512、512、512 和 384 四个物理批次。处理完成后,KV Cache 长度分别变成对应的 prompt 长度。

接着看第 1 到第 3 行,状态全部变成 decode。每个请求每轮只处理一个 token,所以 query_lens 是 [1,1,1,1],KV 长度每轮分别增加 1,batch 中一共只有 4 个 token,执行时间也明显下降。

再看第 4 行,这是动态加入请求的地方。此时请求 0 到 3 继续 decode,而请求 4 进入 prefill。前四个请求各处理 1 个 token,第五个请求处理 640 个 prompt token,所以总数是 644。这个 batch 又被拆成 512 和 132 两个 ubatch。旧请求的 KV 只增加 1,新请求的 KV 从 0 建立到 640。

最后看第 5 行,五个请求的状态已经全部是 decode,query_lens 变成五个 1,说明新请求已经完成 prefill,开始和其他请求一起逐 token 生成。整个过程中,batch 是合并计算的,但每个请求的 slot、seq_id、KV Cache 和 attention 范围仍然独立。”

五、看这份文件时最重要的三条规律

  1. statesprefill 变成 decode,表示请求从处理 prompt 进入生成阶段。
  2. kv_after 通常等于 kv_before + query_lens,它反映本轮新增的 KV Cache。
  3. logical_batch_tokens 是逻辑总量,ubatch_tokens 是受容量限制后实际执行的拆分结果。

本文作者:WarF

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!