标准 Attention 的可拆分性

标准 attention 对一个 query token \(q\) 的计算是:

\[o = \text{softmax}\left(\frac{q K^T}{\sqrt{d}}\right) V\]

关键性质:如果 KV 可以分成两段不相交的子集 \(K = [K_A; K_B]\),则:

\[\text{LSE}_A = \log \sum_{j \in A} \exp\left(\frac{q k_j}{\sqrt{d}}\right), \quad o_A = \text{softmax}(q K_A^T) V_A\]

\[\text{LSE}_{total} = \log\left(e^{\text{LSE}_A} + e^{\text{LSE}_B}\right)\]

\[o = e^{\text{LSE}_A - \text{LSE}_{total}} \cdot o_A + e^{\text{LSE}_B - \text{LSE}_{total}} \cdot o_B\]

这就是 merge_state_v2 做的事,两段分开算再合并,数学上完全等价于一次完整 attention。


Topk = 1 时没有问题

设前缀 KV 长度为 \(L\),draft tokens 长度为 \(S\):

Request i:  [prefix_KV (L tokens)] + [draft_KV (S tokens)]

一次 flash_attn_with_kvcache 搞定,cache_seqlens = L + S。


Topk > 1 引入了什么问题

以 topk=4、S=3 为例,Target Verify 阶段:

Request i 有 4 条候选树,每条 3 个 draft tokens:
  Branch 0: [d_00, d_01, d_02]  各自需要 attend: prefix_KV_i + 本分支祖先
  Branch 1: [d_10, d_11, d_12]  各自需要 attend: prefix_KV_i + 本分支祖先
  Branch 2: [d_20, d_21, d_22]  ──────────────────────────────────────────
  Branch 3: [d_30, d_31, d_32]        共享同一段 prefix_KV_i !!

朴素展开(Naive Expand)方案:将 batch 展成 B × topk 个虚拟请求:

虚拟请求 (i, k) Q KV
(i, 0) 3 个 query prefix_KV_i (L) + branch0_KV (3)
(i, 1) 3 个 query prefix_KV_i (L) + branch1_KV (3)
(i, 2) 3 个 query prefix_KV_i (L) + branch2_KV (3)
(i, 3) 3 个 query prefix_KV_i (L) + branch3_KV (3)

prefix_KV_i 被重复读取 topk 次,总读 KV 量:

\[\text{Naive} = B \times topk \times (L + S) = B \times 4 \times (L + 3)\]

当 \(L = 1000\) 时,主要开销都在反复读那 1000 token 的 prefix。


Cascade 方案的数学结构

将 KV 拆成两段,利用 prefix 被所有分支共享:

第一段 attention(共享 prefix,metadata.page_table):

\[B \text{ 个请求,每个请求的 Q} = \text{所有 topk 分支的全部 query tokens}\] \[\text{KV} = \text{prefix\_KV}_i \quad (\text{每个请求只读一次!})\]

输出:\(o^{(A)}\),\(\text{LSE}^{(A)}\)

第二段 attention(各自独立的 draft KV,forward_metadata_spec_decode_expand.page_table):

\[B \times topk \text{ 个虚拟请求,每个请求的 Q} = \text{本分支 query tokens}\] \[\text{KV} = \text{各自分支的 draft tokens (很短,只有 } S \text{ 个)}\]

输出:\(o^{(B)}\),\(\text{LSE}^{(B)}\)

合并:merge_state_v2(o_A, LSE_A, o_B, LSE_B) → 精确等价于原始完整 attention。

总读 KV 量:

\[\text{Cascade} = \underbrace{B \times L}_{\text{prefix 只读一次}} + \underbrace{B \times topk \times S}_{\text{draft 各自读}}\]


多条候选 token(topk)影响的核心

方案 prefix KV 读取次数 draft KV 读取次数
Naive Expand \(B \times topk \times L\) \(B \times topk \times S\)
Cascade \(B \times L\) \(B \times topk \times S\)
节省 topk 倍 无节省

当 \(topk = 4\),\(L = 2000\),\(S = 5\): - Naive 每请求读 8020 个 KV token - Cascade 每请求读 2020 个 KV token - 节省约 4 倍,且 \(L\) 越长、\(topk\) 越大,优势越明显

这就是为什么 use_cascade_attn 的触发条件是 topk > 1——topk = 1 时没有共享 prefix 可利用,cascade 无意义;topk 越大,prefix KV 的重复读取浪费越严重,cascade 收益越大。