1# Copyright 2026 Huawei Technologies Co., Ltd
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ============================================================================
15# -*- coding: utf-8 -*-
16# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
17# Copyright (c) 2026, Huawei Technologies Co., Ltd. All rights reserved.
18
19# pylint: disable=line-too-long,missing-public-type-hints,missing-public-docstring
20# pylint: disable=invalid-name,missing-module-docstring,missing-function-docstring
21
22from typing import Optional, Tuple
23
24import torch
25import triton
26import triton.language as tl
27
28from .utils import prepare_chunk_indices, exp
29
30
31@triton.heuristics({
32 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None
33})
34@triton.jit(do_not_specialize=['T'])
35def prepare_wy_repr_bwd_kernel(
36 k,
37 v,
38 beta,
39 g,
40 A,
41 dw,
42 du,
43 dk,
44 dv,
45 dbeta,
46 dg,
47 cu_seqlens,
48 chunk_indices,
49 T,
50 B,
51 H: tl.constexpr,
52 K: tl.constexpr,
53 V: tl.constexpr,
54 NT: tl.constexpr,
55 BT: tl.constexpr,
56 BK: tl.constexpr,
57 BV: tl.constexpr,
58 IS_VARLEN: tl.constexpr
59):
60 core_id = tl.program_id(0)
61 total_cores = tl.num_programs(0)
62 T_max = T
63
64 base_chunks_per_pid = NT // total_cores
65 remainder_chunks = NT % total_cores
66
67 if core_id < remainder_chunks:
68 chunks_this_pid = base_chunks_per_pid + 1
69 start_idx = core_id * chunks_this_pid
70 else:
71 chunks_this_pid = base_chunks_per_pid
72 start_idx = core_id * chunks_this_pid + remainder_chunks
73
74 for idx in range(start_idx, start_idx + chunks_this_pid):
75 for i_b in range(B):
76 if IS_VARLEN:
77 i_n, i_t = tl.load(chunk_indices + idx * 2).to(tl.int32), tl.load(chunk_indices + idx * 2 + 1).to(tl.int32)
78 bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32)
79 T = eos - bos
80 else:
81 i_t = idx
82 bos, eos = i_b * T, i_b * T + T
83
84 o_t = i_t * BT + tl.arange(0, BT)
85 m_t = o_t < T
86 m_A = (o_t[:, None] > o_t[None, :]) & (m_t[:, None] & m_t)
87 for i_h in range(0, H):
88 if IS_VARLEN:
89 offset = bos + i_h * T_max
90 else:
91 offset = bos * H + i_h * T_max
92
93 p_beta = tl.make_block_ptr(beta + offset, (T,), (1,), (i_t * BT,), (BT,), (0,))
94 p_g = tl.make_block_ptr(g + offset, (T,), (1,), (i_t * BT,), (BT,), (0,))
95 p_A = tl.make_block_ptr(A + (bos * H + i_h) * BT, (BT, T), (1, H * BT), (0, i_t * BT), (BT, BT), (0, 1))
96
97 b_A = tl.load(p_A, boundary_check=(0, 1))
98 b_beta = tl.load(p_beta, boundary_check=(0,))
99 b_g = tl.load(p_g, boundary_check=(0,))
100 b_g_exp = tl.exp(b_g)
101
102 b_dbeta = tl.zeros([BT], dtype=tl.float32)
103 b_dA = tl.zeros([BT, BT], dtype=tl.float32)
104 b_dg = tl.zeros([BT], dtype=tl.float32)
105
106 for i_k in range(tl.cdiv(K, BK)):
107 p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (T, K), (H * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0))
108 p_dk = tl.make_block_ptr(dk + (bos * H + i_h) * K, (T, K), (H * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0))
109 p_dw = tl.make_block_ptr(dw + (bos * H + i_h) * K, (T, K), (H * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0))
110 b_k = tl.load(p_k, boundary_check=(0, 1))
111 b_k_beta_g = (b_k * b_beta[:, None] * b_g_exp[:, None]).to(b_k.dtype)
112 b_dw = tl.load(p_dw, boundary_check=(0, 1))
113 b_dA += tl.dot(b_dw, tl.trans(b_k_beta_g))
114 b_dk_beta_g = tl.dot(b_A, b_dw)
115 b_dk = b_dk_beta_g * b_beta[:, None] * b_g_exp[:, None]
116 b_dbeta += tl.sum(b_dk_beta_g * b_k * b_g_exp[:, None], 1)
117 b_dg += tl.sum(b_dk_beta_g * b_k * b_g_exp[:, None] * b_beta[:, None], 1)
118 tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1))
119
120 for i_v in range(tl.cdiv(V, BV)):
121 p_v = tl.make_block_ptr(v + (bos * H + i_h) * V, (T, V), (H * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0))
122 p_dv = tl.make_block_ptr(dv + (bos * H + i_h) * V, (T, V), (H * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0))
123 p_du = tl.make_block_ptr(du + (bos * H + i_h) * V, (T, V), (H * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0))
124 b_v = tl.load(p_v, boundary_check=(0, 1))
125 b_v_beta = (b_v * b_beta[:, None]).to(b_v.dtype)
126 b_du = tl.load(p_du, boundary_check=(0, 1))
127 b_dA += tl.dot(b_du, tl.trans(b_v_beta))
128 b_dv_beta = tl.dot(b_A, b_du)
129 b_dv = b_dv_beta * b_beta[:, None]
130 b_dbeta += tl.sum(b_dv_beta * b_v, 1)
131 tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1))
132
133 b_dA = tl.where(m_A, b_dA, 0)
134 b_dA = tl.dot(b_dA.to(b_A.dtype), b_A)
135 b_dA = tl.dot(b_A, b_dA.to(b_A.dtype))
136 b_dA = tl.where(m_A, -b_dA * exp(b_g[:, None] - b_g[None, :]), 0)
137 b_dA = b_dA.to(k.dtype.element_ty)
138 b_A = tl.zeros([BT, BT], dtype=tl.float32)
139
140 for i_k in range(tl.cdiv(K, BK)):
141 p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (T, K), (H * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0))
142 p_dk = tl.make_block_ptr(dk + (bos * H + i_h) * K, (T, K), (H * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0))
143 b_k = tl.load(p_k, boundary_check=(0, 1))
144 b_dk = tl.load(p_dk, boundary_check=(0, 1))
145 b_k_beta = (b_k * b_beta[:, None]).to(b_k.dtype)
146 b_A += tl.dot(b_k_beta, tl.trans(b_k))
147 b_dk_beta = tl.dot(b_dA, b_k)
148 b_dbeta += tl.sum(b_dk_beta * b_k, 1)
149 b_dk += tl.dot(tl.trans(b_dA), b_k_beta)
150 b_dk += b_dk_beta * b_beta[:, None]
151 tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1))
152
153 b_dA_A = b_dA * b_A
154 b_dg += tl.sum(b_dA_A, axis=1) - tl.sum(b_dA_A, axis=0)
155 p_dg = tl.make_block_ptr(dg + offset, (T,), (1,), (i_t * BT,), (BT,), (0,))
156 p_dbeta = tl.make_block_ptr(dbeta + offset, (T,), (1,), (i_t * BT,), (BT,), (0,))
157 tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0,))
158 tl.store(p_dbeta, b_dbeta.to(p_dbeta.dtype.element_ty), boundary_check=(0,))
159
160
161@triton.heuristics({
162 'USE_G': lambda args: args['g'] is not None,
163 'USE_GK': lambda args: args['gk'] is not None,
164 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None
165})
166@triton.jit(do_not_specialize=['T'])
167def recompute_w_u_fwd_kernel(
168 k,
169 v,
170 beta,
171 w,
172 u,
173 A,
174 g,
175 gk,
176 cu_seqlens,
177 chunk_indices,
178 T_tmp,
179 B,
180 H: tl.constexpr,
181 K: tl.constexpr,
182 V: tl.constexpr,
183 NT: tl.constexpr,
184 BT: tl.constexpr,
185 BK: tl.constexpr,
186 BV: tl.constexpr,
187 USE_G: tl.constexpr,
188 USE_GK: tl.constexpr,
189 IS_VARLEN: tl.constexpr
190):
191 core_id = tl.program_id(0)
192 total_cores = tl.num_programs(0)
193 T_max = T_tmp
194
195 base_chunks_per_pid = NT // total_cores
196 remainder_chunks = NT % total_cores
197
198 if core_id < remainder_chunks:
199 chunks_this_pid = base_chunks_per_pid + 1
200 start_idx = core_id * chunks_this_pid
201 else:
202 chunks_this_pid = base_chunks_per_pid
203 start_idx = core_id * chunks_this_pid + remainder_chunks
204
205 for idx in range(start_idx, start_idx + chunks_this_pid):
206 for i_b in range(B):
207 for i_h in range(0, H):
208
209 if IS_VARLEN:
210 i_n, i_t = tl.load(chunk_indices + idx * 2).to(tl.int32), tl.load(chunk_indices + idx * 2 + 1).to(tl.int32)
211 bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32)
212 offset = bos + i_h * T_max
213 T = eos - bos
214 else:
215 T = T_tmp
216 i_t = idx
217 bos, eos = i_b * T, i_b * T + T
218 offset = bos * H + i_h * T_max
219
220 p_beta = tl.make_block_ptr(beta + offset, (T,), (1,), (i_t * BT,), (BT,), (0,))
221 b_beta = tl.load(p_beta, boundary_check=(0,))
222
223 p_A = tl.make_block_ptr(A + (bos * H + i_h) * BT, (T, BT), (H * BT, 1), (i_t * BT, 0), (BT, BT), (1, 0))
224 b_A = tl.load(p_A, boundary_check=(0, 1))
225
226 for i_v in range(tl.cdiv(V, BV)):
227 p_v = tl.make_block_ptr(v + (bos * H + i_h) * V, (T, V), (H * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0))
228 p_u = tl.make_block_ptr(u + (bos * H + i_h) * V, (T, V), (H * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0))
229 b_v = tl.load(p_v, boundary_check=(0, 1))
230 b_vb = (b_v * b_beta[:, None]).to(b_v.dtype)
231 b_u = tl.dot(b_A, b_vb, allow_tf32=False)
232 tl.store(p_u, b_u.to(p_u.dtype.element_ty), boundary_check=(0, 1))
233
234 if USE_G:
235 p_g = tl.make_block_ptr(g + offset, (T,), (1,), (i_t * BT,), (BT,), (0,))
236 b_g = tl.exp(tl.load(p_g, boundary_check=(0,)))
237
238 for i_k in range(tl.cdiv(K, BK)):
239 p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (T, K), (H * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0))
240 p_w = tl.make_block_ptr(w + (bos * H + i_h) * K, (T, K), (H * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0))
241 b_k = tl.load(p_k, boundary_check=(0, 1))
242 b_kb = b_k * b_beta[:, None]
243 if USE_G:
244 b_kb *= b_g[:, None]
245 if USE_GK:
246 p_gk = tl.make_block_ptr(gk + (bos * H + i_h) * K, (T, K), (H * K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0))
247 b_kb *= tl.exp(tl.load(p_gk, boundary_check=(0, 1)))
248 b_w = tl.dot(b_A, b_kb.to(b_k.dtype))
249 tl.store(p_w, b_w.to(p_w.dtype.element_ty), boundary_check=(0, 1))
250
251
252def recompute_w_u_fwd(
253 k: torch.Tensor,
254 v: torch.Tensor,
255 beta: torch.Tensor,
256 A: torch.Tensor,
257 g: Optional[torch.Tensor] = None,
258 gk: Optional[torch.Tensor] = None,
259 cu_seqlens: Optional[torch.LongTensor] = None,
260) -> Tuple[torch.Tensor, torch.Tensor]:
261 B, T, H, K, V = *k.shape, v.shape[-1]
262 BT = A.shape[-1]
263 BK = 128
264 BV = 128
265
266 chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None
267 NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices)
268 g = g.transpose(1, 2).contiguous() if g is not None else None
269 beta = beta.transpose(1, 2).contiguous()
270
271 w = torch.empty_like(k)
272 u = torch.empty_like(v)
273 cv_kernel_num = 24
274 recompute_w_u_fwd_kernel[(cv_kernel_num,)](
275 k=k,
276 v=v,
277 beta=beta,
278 w=w,
279 u=u,
280 A=A,
281 g=g,
282 gk=gk,
283 cu_seqlens=cu_seqlens,
284 chunk_indices=chunk_indices,
285 T_tmp=T,
286 B=B,
287 H=H,
288 K=K,
289 V=V,
290 NT=NT,
291 BT=BT,
292 BK=BK,
293 BV=BV,
294 )
295 return w, u
296
297
298def prepare_wy_repr_bwd(
299 k: torch.Tensor,
300 v: torch.Tensor,
301 g: torch.Tensor,
302 beta: torch.Tensor,
303 A: torch.Tensor,
304 dw: torch.Tensor,
305 du: torch.Tensor,
306 cu_seqlens: Optional[torch.LongTensor],
307 chunk_size: int = 64,
308) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
309 B, T, H, K, V = *k.shape, v.shape[-1]
310 BT = chunk_size
311 chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None
312 NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices)
313 BK = 128
314 BV = 128
315 beta = beta.transpose(1, 2).contiguous()
316 g = g.transpose(1, 2).contiguous()
317
318 dk = torch.empty_like(k)
319 dv = torch.empty_like(v)
320 dbeta = torch.empty_like(beta)
321 dg = torch.empty_like(g)
322
323 cv_kernel_num = 24
324 prepare_wy_repr_bwd_kernel[(cv_kernel_num,)](
325 k=k,
326 v=v,
327 beta=beta,
328 g=g,
329 A=A,
330 dw=dw,
331 du=du,
332 dk=dk,
333 dv=dv,
334 dbeta=dbeta,
335 dg=dg,
336 cu_seqlens=cu_seqlens,
337 chunk_indices=chunk_indices,
338 T=T,
339 B=B,
340 H=H,
341 K=K,
342 V=V,
343 NT=NT,
344 BT=BT,
345 BK=BK,
346 BV=BV,
347 )
348
349 dbeta = dbeta.transpose(1, 2).contiguous()
350 dg = dg.transpose(1, 2).contiguous()
351
352 return dk, dv, dbeta, dg
353
354
355bwd_prepare_wy_repr = prepare_wy_repr_bwd
356
357fwd_recompute_w_u = recompute_w_u_fwd