main
py 279 lines 8.47 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import torch
5
6 from vllm.triton_utils import LOG2E, LOGE2, tl, triton
7
8
9 @triton.jit
10 def _bf16_mla_sparse_kernel(
11 q_buffer,
12 k_buffer,
13 v_buffer,
14 indices_ptr,
15 out_ptr,
16 softmax_lse_ptr,
17 max_logits_ptr,
18 seq_q,
19 seq_kv,
20 h_q,
21 dim_qk,
22 dim_v,
23 stride_q_token,
24 stride_q_head,
25 stride_k_token,
26 stride_k_head,
27 stride_v_token,
28 stride_v_head,
29 stride_out_token,
30 stride_out_head,
31 stride_lse,
32 stride_indices_token,
33 stride_indices_head,
34 sm_scale,
35 kv_group_num: tl.constexpr,
36 index_topk: tl.constexpr,
37 BLOCK_H: tl.constexpr, # block size for num heads
38 BLOCK_M: tl.constexpr, # block size for num tokens
39 BLOCK_N: tl.constexpr, # block size for indices
40 BLOCK_DV: tl.constexpr, # block size for dim_v
41 BLOCK_DMODEL: tl.constexpr, # block size for dim_nope
42 BLOCK_DPE: tl.constexpr, # block size for positional embedding
43 LOGE2: tl.constexpr,
44 ):
45 cur_q = tl.program_id(0)
46 cur_head_id = tl.program_id(1)
47 cur_kv_head_id = cur_head_id // tl.cdiv(kv_group_num, BLOCK_H)
48
49 VALID_BLOCK_H: tl.constexpr = BLOCK_H if kv_group_num > BLOCK_H else kv_group_num
50 cur_head = cur_head_id * VALID_BLOCK_H + tl.arange(0, BLOCK_H)
51 mask_h = cur_head < (cur_head_id + 1) * VALID_BLOCK_H
52 mask_h = mask_h & (cur_head < h_q)
53
54 offs_d = tl.arange(0, BLOCK_DMODEL)
55 offs_dv = tl.arange(0, BLOCK_DV)
56
57 off_q = cur_q * stride_q_token + cur_head[:, None] * stride_q_head + offs_d[None, :]
58 mask_dmodel = offs_d < BLOCK_DMODEL
59 q = tl.load(
60 q_buffer + off_q, mask=(mask_h[:, None]) & (mask_dmodel[None, :]), other=0.0
61 )
62
63 if BLOCK_DPE > 0:
64 offs_dpe = BLOCK_DMODEL + tl.arange(0, BLOCK_DPE)
65 off_qpe = (
66 cur_q * stride_q_token
67 + cur_head[:, None] * stride_q_head
68 + offs_dpe[None, :]
69 )
70 # assume dim_qk == BLOCK_DMODEL + BLOCK_DPE
71 mask_dpe = offs_dpe < dim_qk
72 qpe = tl.load(
73 q_buffer + off_qpe, mask=(mask_h[:, None]) & (mask_dpe[None, :]), other=0.0
74 )
75
76 # Use a finite sentinel rather than -inf: if every key in a chunk is
77 # masked (e.g. leading -1 padding in `indices`) an -inf running max gives
78 # re_scale = exp2(-inf - -inf) = NaN, permanently poisoning acc / e_sum.
79 e_max = tl.zeros([BLOCK_H], dtype=tl.float32) - 1.0e30
80 e_sum = tl.zeros([BLOCK_H], dtype=tl.float32)
81 acc = tl.zeros([BLOCK_H, BLOCK_DV], dtype=tl.float32)
82
83 for start_indice in range(0, index_topk, BLOCK_N):
84 offs_indice = start_indice + tl.arange(0, BLOCK_N)
85 mask_indice = offs_indice < index_topk
86 indices = tl.load(
87 indices_ptr
88 + (
89 cur_q * stride_indices_token
90 + cur_kv_head_id * stride_indices_head
91 + offs_indice
92 ),
93 mask=mask_indice,
94 other=-1,
95 )
96
97 mask_kv = (indices >= 0) & (indices < seq_kv)
98 mask_kv_d = mask_dmodel
99 offs_k = (
100 indices[None, :] * stride_k_token
101 + cur_kv_head_id * stride_k_head
102 + offs_d[:, None]
103 )
104
105 # q_nope @ k_nope
106 k = tl.load(
107 k_buffer + offs_k, mask=(mask_kv[None, :]) & (mask_kv_d[:, None]), other=0.0
108 )
109 qk = tl.dot(q, k.to(q.dtype))
110
111 if BLOCK_DPE > 0:
112 # q_rope @ k_rope
113 offs_kpe = (
114 indices[None, :] * stride_k_token
115 + cur_kv_head_id * stride_k_head
116 + offs_dpe[:, None]
117 )
118 mask_k_dpe = offs_dpe < dim_qk
119 kpe = tl.load(
120 k_buffer + offs_kpe,
121 mask=(mask_kv[None, :]) & (mask_k_dpe[:, None]),
122 other=0.0,
123 )
124 qk += tl.dot(qpe, kpe.to(q.dtype))
125
126 # apply scaling
127 qk *= sm_scale
128 qk = tl.where((mask_h[:, None]) & (mask_kv[None, :]), qk, -1.0e30)
129
130 # load v
131 mask_v_d = offs_dv < dim_v
132 offs_v = (
133 indices[:, None] * stride_v_token
134 + cur_kv_head_id * stride_v_head
135 + offs_dv[None, :]
136 )
137 v = tl.load(
138 v_buffer + offs_v, mask=(mask_kv[:, None]) & (mask_v_d[None, :]), other=0.0
139 )
140
141 # online softmax
142 n_e_max = tl.maximum(tl.max(qk, 1), e_max)
143 re_scale = tl.exp2(e_max - n_e_max)
144 p = tl.exp2(qk - n_e_max[:, None])
145 acc *= re_scale[:, None]
146
147 # score @ v
148 acc += tl.dot(p.to(v.dtype), v)
149
150 # update global sum and max
151 e_sum = e_sum * re_scale + tl.sum(p, 1)
152 e_max = n_e_max
153
154 # rescaling
155 acc /= e_sum[:, None]
156
157 max_logits = e_max * LOGE2
158 # calculate lse
159 lse = max_logits + tl.log2(e_sum) * LOGE2
160
161 # write output
162 offs_o = (
163 cur_q * stride_out_token
164 + cur_head[:, None] * stride_out_head
165 + offs_dv[None, :]
166 )
167 mask_out_d = offs_dv < dim_v
168 tl.store(
169 out_ptr + offs_o,
170 acc.to(tl.bfloat16),
171 mask=(mask_h[:, None]) & (mask_out_d[None, :]),
172 )
173
174 offs_lse = cur_q * stride_lse + cur_head
175 tl.store(softmax_lse_ptr + offs_lse, lse, mask=mask_h)
176 tl.store(max_logits_ptr + offs_lse, max_logits, mask=mask_h)
177
178
179 # reference implementation of bf16 sparse prefill kernel
180 def triton_bf16_mla_sparse_interface(
181 q: torch.Tensor, # [num_tokens, num_heads_q, dim_qk]
182 kv: torch.Tensor, # [num_tokens, num_heads_kv, dim_qk]
183 indices: torch.Tensor, # [num_tokens, num_heads_kv, topk]
184 sm_scale: float,
185 d_v: int = 512,
186 block_dpe: int = 64,
187 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
188 """Out : [num_tokens, num_heads_q, d_v]
189 max_logits : [num_tokens, num_heads_q]
190 lse : logsumexp, [num_tokens, num_heads_q]
191
192 Args:
193 q: Query tensor, [num_tokens, num_heads_q, dim_qk].
194 kv: Latent KV cache tensor.
195 indices: Sparse KV indices selected for each query token.
196 sm_scale: Softmax scale applied to the QK product.
197 d_v: Size of the value dimension.
198 block_dpe: Size of positional embedding portion of dim_qk.
199 Set to 0 when q/kv contain only the nope latent (e.g. DSv4
200 prefill where RoPE is not split out).
201
202 """
203 num_tokens, num_heads_q, dim_qk = q.shape
204 _, num_heads_kv, _ = kv.shape
205 assert dim_qk == kv.shape[2], "q and kv have different head dimensions"
206
207 # for deepseek v3.2, index topk should be 2048
208 _, _, index_topk = indices.shape
209
210 BLOCK_H = 16
211 BLOCK_DPE = block_dpe
212 BLOCK_DMODEL = dim_qk - BLOCK_DPE
213 BLOCK_M = 32
214 BLOCK_N = 16
215 BLOCK_DV = 512
216 assert d_v == BLOCK_DV, "only support d_v = 512"
217
218 assert dim_qk == BLOCK_DMODEL + BLOCK_DPE, (
219 "dim_qk does not match BLOCK_DMODEL + BLOCK_DPE"
220 )
221 assert num_heads_kv == 1, "only support kv head = 1 for now"
222 assert index_topk % BLOCK_N == 0, "index_topk must be multiple of BLOCK_N"
223
224 sm_scale *= LOG2E
225
226 kv_group_num = num_heads_q // num_heads_kv
227 grid = (
228 num_tokens,
229 triton.cdiv(num_heads_q, min(BLOCK_H, kv_group_num)),
230 )
231
232 out = torch.zeros((num_tokens, num_heads_q, d_v), dtype=q.dtype, device=q.device)
233 softmax_lse = torch.zeros(
234 (num_tokens, num_heads_q), dtype=torch.float32, device=q.device
235 )
236 max_logits = torch.zeros(
237 (num_tokens, num_heads_q), dtype=torch.float32, device=q.device
238 )
239
240 k = kv
241 v = kv[..., :d_v]
242
243 _bf16_mla_sparse_kernel[grid](
244 q_buffer=q,
245 k_buffer=k,
246 v_buffer=v,
247 indices_ptr=indices,
248 out_ptr=out,
249 softmax_lse_ptr=softmax_lse,
250 max_logits_ptr=max_logits,
251 seq_q=num_tokens,
252 seq_kv=kv.shape[0],
253 h_q=num_heads_q,
254 dim_qk=dim_qk,
255 dim_v=d_v,
256 stride_q_token=q.stride(0),
257 stride_q_head=q.stride(1),
258 stride_k_token=k.stride(0),
259 stride_k_head=k.stride(1),
260 stride_v_token=v.stride(0),
261 stride_v_head=v.stride(1),
262 stride_out_token=out.stride(0),
263 stride_out_head=out.stride(1),
264 stride_lse=softmax_lse.stride(0),
265 stride_indices_token=indices.stride(0),
266 stride_indices_head=indices.stride(1),
267 sm_scale=sm_scale,
268 kv_group_num=kv_group_num,
269 index_topk=index_topk,
270 BLOCK_H=BLOCK_H,
271 BLOCK_M=BLOCK_M,
272 BLOCK_N=BLOCK_N,
273 BLOCK_DV=BLOCK_DV,
274 BLOCK_DMODEL=BLOCK_DMODEL,
275 BLOCK_DPE=BLOCK_DPE,
276 LOGE2=LOGE2,
277 )
278
279 return out, max_logits, softmax_lse