1313import torch
1414import torch .nn .functional as F
1515
16+ _CHUNK_SIZE = 64
17+
18+
19+ def _chunked_gdr_single_seq (
20+ query : torch .Tensor ,
21+ key : torch .Tensor ,
22+ value : torch .Tensor ,
23+ g : torch .Tensor ,
24+ beta : torch .Tensor ,
25+ initial_state : torch .Tensor | None ,
26+ output_final_state : bool ,
27+ use_qk_l2norm_in_kernel : bool = False ,
28+ ) -> tuple [torch .Tensor , torch .Tensor | None ]:
29+ """Chunked GDN delta-rule for a single sequence.
30+
31+ All inputs are [1, T, H, D] layout (batch=1).
32+ Processes tokens in chunks of 64 using batched matmul —
33+ far fewer kernel launches than the per-timestep loop.
34+
35+ Based on Huawei vllm-ascend's _torch_chunk_gated_delta_rule_chunked.
36+ """
37+ chunk_size = _CHUNK_SIZE
38+ initial_dtype = query .dtype
39+ if use_qk_l2norm_in_kernel :
40+ query = F .normalize (query , p = 2 , dim = - 1 , eps = 1e-6 ).to (query .dtype )
41+ key = F .normalize (key , p = 2 , dim = - 1 , eps = 1e-6 ).to (key .dtype )
42+
43+ # Transpose to [B, H, T, D] and cast to float32 for precision
44+ query , key , value , beta , g = [
45+ x .transpose (1 , 2 ).contiguous ().to (torch .float32 )
46+ for x in (query , key , value , beta , g )
47+ ]
48+
49+ batch_size , num_heads , sequence_length , k_head_dim = key .shape
50+ v_head_dim = value .shape [- 1 ]
51+ pad_size = (chunk_size - sequence_length % chunk_size ) % chunk_size
52+
53+ query = F .pad (query , (0 , 0 , 0 , pad_size ))
54+ key = F .pad (key , (0 , 0 , 0 , pad_size ))
55+ value = F .pad (value , (0 , 0 , 0 , pad_size ))
56+ beta = F .pad (beta , (0 , pad_size ))
57+ g = F .pad (g , (0 , pad_size ))
58+
59+ total_sequence_length = sequence_length + pad_size
60+ scale = 1 / (query .shape [- 1 ] ** 0.5 )
61+ query = query * scale
62+
63+ v_beta = value * beta .unsqueeze (- 1 )
64+ k_beta = key * beta .unsqueeze (- 1 )
65+
66+ # Reshape to chunks: [B, H, num_chunks, chunk_size, D]
67+ query , key , value , k_beta , v_beta = [
68+ x .reshape (x .shape [0 ], x .shape [1 ], - 1 , chunk_size , x .shape [- 1 ])
69+ for x in (query , key , value , k_beta , v_beta )
70+ ]
71+ g = g .reshape (g .shape [0 ], g .shape [1 ], - 1 , chunk_size )
72+
73+ mask_diag = torch .triu (
74+ torch .ones (chunk_size , chunk_size , dtype = torch .bool , device = query .device ),
75+ diagonal = 0 ,
76+ )
77+
78+ # Cumulative gating within chunks
79+ g = g .cumsum (dim = - 1 )
80+ decay_mask = ((g .unsqueeze (- 1 ) - g .unsqueeze (- 2 )).tril ().exp ().float ()).tril ()
81+
82+ # Intra-chunk attention with triangular solve (WY representation)
83+ attn = - ((k_beta @ key .transpose (- 1 , - 2 )) * decay_mask ).masked_fill (mask_diag , 0 )
84+ for i in range (1 , chunk_size ):
85+ row = attn [..., i , :i ].clone ()
86+ sub = attn [..., :i , :i ].clone ()
87+ attn [..., i , :i ] = row + (row .unsqueeze (- 1 ) * sub ).sum (- 2 )
88+ attn = attn + torch .eye (chunk_size , dtype = attn .dtype , device = attn .device )
89+
90+ value = attn @ v_beta
91+ k_cumdecay = attn @ (k_beta * g .exp ().unsqueeze (- 1 ))
92+
93+ # Initialize recurrent state
94+ last_recurrent_state = (
95+ torch .zeros (
96+ batch_size , num_heads , v_head_dim , k_head_dim ,
97+ device = value .device , dtype = value .dtype ,
98+ )
99+ if initial_state is None
100+ else initial_state .to (value )
101+ )
102+ core_attn_out = torch .zeros_like (value )
103+
104+ mask_upper = torch .triu (
105+ torch .ones (chunk_size , chunk_size , dtype = torch .bool , device = query .device ),
106+ diagonal = 1 ,
107+ )
108+
109+ # Inter-chunk recurrence — iterates over T/64 chunks (not T timesteps)
110+ num_chunks = total_sequence_length // chunk_size
111+ for i in range (num_chunks ):
112+ q_i = query [:, :, i ] # [B, H, chunk_size, K]
113+ k_i = key [:, :, i ]
114+ v_i = value [:, :, i ]
115+
116+ attn_inter_chunk = (
117+ q_i @ k_i .transpose (- 1 , - 2 ) * decay_mask [:, :, i ]
118+ ).masked_fill_ (mask_upper , 0 )
119+
120+ v_prime = k_cumdecay [:, :, i ] @ last_recurrent_state .transpose (- 1 , - 2 )
121+ v_new = v_i - v_prime
122+ inter_state = (
123+ (q_i * g [:, :, i , :, None ].exp ())
124+ @ last_recurrent_state .transpose (- 1 , - 2 )
125+ )
126+ core_attn_out [:, :, i ] = inter_state + attn_inter_chunk @ v_new
127+ last_recurrent_state = (
128+ last_recurrent_state * g [:, :, i , - 1 , None , None ].exp ()
129+ + v_new .transpose (- 1 , - 2 )
130+ @ (k_i * (g [:, :, i , - 1 , None ] - g [:, :, i ]).exp ()[..., None ])
131+ )
132+
133+ if not output_final_state :
134+ last_recurrent_state = None
135+
136+ # Reshape back and trim padding
137+ core_attn_out = core_attn_out .reshape (
138+ core_attn_out .shape [0 ], core_attn_out .shape [1 ], - 1 , core_attn_out .shape [- 1 ]
139+ )
140+ core_attn_out = core_attn_out [:, :, :sequence_length ]
141+ # Back to [B, T, H, D]
142+ core_attn_out = core_attn_out .transpose (1 , 2 ).contiguous ().to (initial_dtype )
143+ return core_attn_out , last_recurrent_state
144+
16145
17146def chunk_gated_delta_rule_torch (
18147 q : torch .Tensor ,
@@ -27,10 +156,12 @@ def chunk_gated_delta_rule_torch(
27156 head_first : bool = False ,
28157 use_qk_l2norm_in_kernel : bool = False ,
29158) -> tuple [torch .Tensor , torch .Tensor | None ]:
30- """Pure-PyTorch recurrent implementation of chunk_gated_delta_rule.
159+ """Chunked PyTorch implementation of chunk_gated_delta_rule.
160+
161+ Processes tokens in chunks of 64 using batched matmul operations,
162+ reducing NPU kernel launches from O(T) to O(T/64) per layer.
31163
32164 Handles GQA where num_v_heads (HV) != num_k_heads (H).
33- Vectorized across all HV heads per timestep using batched matmul.
34165 q, k: [B, T, H, K], v: [B, T, HV, V], g/beta: [B, T, HV]
35166 state: [N, HV, V, K]
36167 """
@@ -46,123 +177,86 @@ def chunk_gated_delta_rule_torch(
46177 o = torch .zeros_like (v )
47178
48179 if initial_state is not None :
49- h = initial_state .clone ().float () # [N, HV, V, K]
180+ states = initial_state .clone ().float () # [N, HV, V, K]
50181 else :
51- h = torch .zeros (N , HV , V , K , dtype = torch .float32 , device = q .device )
182+ states = torch .zeros (N , HV , V , K , dtype = torch .float32 , device = q .device )
52183
53- # Expand k/q heads to match v heads: [B, T, H, K] -> [B, T, HV, K]
184+ # Expand q/k heads to match v heads for GQA
54185 if groups > 1 :
55186 q_exp = q .repeat_interleave (groups , dim = 2 ) # [B, T, HV, K]
56187 k_exp = k .repeat_interleave (groups , dim = 2 ) # [B, T, HV, K]
57188 else :
58189 q_exp = q
59190 k_exp = k
60191
192+ # Process each sequence using the chunked algorithm
61193 if cu_seqlens is not None :
62194 cu_cpu = cu_seqlens .cpu ().tolist ()
63195 for i_n in range (N ):
64196 bos , eos = cu_cpu [i_n ], cu_cpu [i_n + 1 ]
65- if bos >= eos :
197+ seq_len = eos - bos
198+ if seq_len <= 0 :
66199 continue
67- hi = h [ i_n ] # [HV, V, K]
68- for t in range ( bos , eos ):
69- qt = q_exp [0 , t ]. float () * scale # [HV, K]
70- kt = k_exp [0 , t ]. float ( ) # [HV, K]
71- vt = v [0 , t ]. float ( ) # [HV, V]
72- gt = g [0 , t ]. float ( ) # [HV]
73- bt = beta [0 , t ]. float ( ) # [HV]
74-
75- # Gated decay: h *= exp(g) [HV, V, K] *= [HV, 1, 1]
76- hi = hi * torch . exp ( gt ). unsqueeze ( - 1 ). unsqueeze ( - 1 )
77- # h @ k: [HV, V, K] x [HV, K] -> [HV, V]
78- hk = torch . bmm ( hi , kt . unsqueeze ( - 1 )). squeeze ( - 1 )
79- # v' = beta * (v - hk)
80- vp = ( vt - hk ) * bt . unsqueeze ( - 1 )
81- # h += v' outer k: [HV, V, 1] x [HV, 1, K] -> [HV, V, K]
82- hi = hi + torch . bmm ( vp . unsqueeze ( - 1 ), kt . unsqueeze ( - 2 ) )
83- # o = h @ q: [HV, V, K] x [HV, K] -> [HV, V ]
84- o [ 0 , t ] = torch . bmm ( hi , qt . unsqueeze ( - 1 )). squeeze ( - 1 ). to ( o . dtype )
85- h [i_n ] = hi
200+ b_idx = 0 if ( cu_seqlens is not None and B == 1 ) else i_n
201+
202+ q_seq = q_exp [b_idx , bos : eos ]. unsqueeze ( 0 ) # [1, T_seq, HV, K]
203+ k_seq = k_exp [b_idx , bos : eos ]. unsqueeze ( 0 ) # [1, T_seq, HV, K]
204+ v_seq = v [b_idx , bos : eos ]. unsqueeze ( 0 ) # [1, T_seq, HV, V]
205+ g_seq = g [b_idx , bos : eos ]. unsqueeze ( 0 ) # [1, T_seq, HV]
206+ beta_seq = beta [b_idx , bos : eos ]. unsqueeze ( 0 ) # [1, T_seq, HV]
207+ init_seq = states [ i_n ]. unsqueeze ( 0 ) # [1, HV, V, K]
208+
209+ out_seq , final_state = _chunked_gdr_single_seq (
210+ query = q_seq , key = k_seq , value = v_seq ,
211+ g = g_seq , beta = beta_seq ,
212+ initial_state = init_seq ,
213+ output_final_state = True ,
214+ use_qk_l2norm_in_kernel = use_qk_l2norm_in_kernel ,
215+ )
216+ o [ b_idx , bos : eos ] = out_seq [ 0 ]
217+ if final_state is not None :
218+ states [i_n ] = final_state [ 0 ]
86219 else :
87220 for i_n in range (B ):
88- hi = h [i_n ]
89- for t in range (T ):
90- qt = q_exp [i_n , t ].float () * scale
91- kt = k_exp [i_n , t ].float ()
92- vt = v [i_n , t ].float ()
93- gt = g [i_n , t ].float ()
94- bt = beta [i_n , t ].float ()
95-
96- hi = hi * torch .exp (gt ).unsqueeze (- 1 ).unsqueeze (- 1 )
97- hk = torch .bmm (hi , kt .unsqueeze (- 1 )).squeeze (- 1 )
98- vp = (vt - hk ) * bt .unsqueeze (- 1 )
99- hi = hi + torch .bmm (vp .unsqueeze (- 1 ), kt .unsqueeze (- 2 ))
100- o [i_n , t ] = torch .bmm (hi , qt .unsqueeze (- 1 )).squeeze (- 1 ).to (o .dtype )
101- h [i_n ] = hi
221+ q_seq = q_exp [i_n :i_n + 1 ] # [1, T, HV, K]
222+ k_seq = k_exp [i_n :i_n + 1 ]
223+ v_seq = v [i_n :i_n + 1 ]
224+ g_seq = g [i_n :i_n + 1 ]
225+ beta_seq = beta [i_n :i_n + 1 ]
226+ init_seq = states [i_n ].unsqueeze (0 )
227+
228+ out_seq , final_state = _chunked_gdr_single_seq (
229+ query = q_seq , key = k_seq , value = v_seq ,
230+ g = g_seq , beta = beta_seq ,
231+ initial_state = init_seq ,
232+ output_final_state = True ,
233+ use_qk_l2norm_in_kernel = use_qk_l2norm_in_kernel ,
234+ )
235+ o [i_n ] = out_seq [0 ]
236+ if final_state is not None :
237+ states [i_n ] = final_state [0 ]
102238
103239 if output_final_state :
104- return o , h . to ( initial_state . dtype if initial_state is not None else torch . float32 )
240+ return o , states
105241 return o , None
106242
107243
108- def l2norm_fwd_torch (
109- x : torch .Tensor , eps : float = 1e-6 , output_dtype : torch .dtype | None = None
110- ) -> torch .Tensor :
111- """Pure-PyTorch L2 normalization along the last dimension."""
112- x_shape_og = x .shape
113- x_flat = x .reshape (- 1 , x .shape [- 1 ]).float ()
114- norm = torch .norm (x_flat , p = 2 , dim = - 1 , keepdim = True )
115- y = x_flat / (norm + eps )
116- if output_dtype is not None :
117- y = y .to (output_dtype )
118- else :
119- y = y .to (x .dtype )
120- return y .view (x_shape_og )
121-
122-
123- def _softplus (x : torch .Tensor , beta : float = 1.0 , threshold : float = 20.0 ):
124- """Numerically stable softplus matching the Triton kernel implementation."""
125- # Use the stable formulation: softplus(x) = x + log(1+exp(-x)) for x > 0
126- # softplus(x) = log(1+exp(x)) for x <= 0
127- bx = beta * x
128- sp = torch .where (
129- bx > 0 ,
130- bx + torch .log (1.0 + torch .exp (- bx )),
131- torch .log (1.0 + torch .exp (bx )),
132- )
133- sp = sp / beta
134- return torch .where (bx <= threshold , sp , x )
244+ def _softplus (x : torch .Tensor ) -> torch .Tensor :
245+ """Numerically stable softplus."""
246+ return torch .where (x > 20.0 , x , torch .log1p (torch .exp (x )))
247+
248+
249+ def l2norm_fwd_torch (x : torch .Tensor ) -> torch .Tensor :
250+ """L2 normalize along the last dimension."""
251+ return F .normalize (x .float (), p = 2 , dim = - 1 , eps = 1e-6 ).to (x .dtype )
135252
136253
137254def fused_gdn_gating_torch (
138- A_log : torch .Tensor ,
139- a : torch .Tensor ,
140- b : torch .Tensor ,
141- dt_bias : torch .Tensor ,
142- beta : float = 1.0 ,
143- threshold : float = 20.0 ,
144- ) -> tuple [torch .Tensor , torch .Tensor ]:
145- """Pure-PyTorch fused GDN gating.
146-
147- Computes:
148- g = -exp(A_log) * softplus(a + dt_bias)
149- beta_output = sigmoid(b)
150-
151- Args:
152- A_log: [num_heads]
153- a: [batch, num_heads]
154- b: [batch, num_heads]
155- dt_bias: [num_heads]
156-
157- Returns:
158- g: [1, batch, num_heads] float32
159- beta_output: [1, batch, num_heads] same dtype as b
160- """
161- x = a .float () + dt_bias .float ().unsqueeze (0 )
162- sp = _softplus (x , beta , threshold )
163- g = - torch .exp (A_log .float ()).unsqueeze (0 ) * sp
164- beta_output = torch .sigmoid (b .float ()).to (b .dtype )
165- return g .unsqueeze (0 ), beta_output .unsqueeze (0 )
255+ gate : torch .Tensor ,
256+ x : torch .Tensor ,
257+ ) -> torch .Tensor :
258+ """Fused sigmoid gating: output = sigmoid(gate) * x"""
259+ return torch .sigmoid (gate .float ()).to (x .dtype ) * x
166260
167261
168262def fused_post_conv_prep_torch (
@@ -177,30 +271,26 @@ def fused_post_conv_prep_torch(
177271 apply_l2norm : bool = True ,
178272 output_g_exp : bool = False ,
179273) -> tuple [torch .Tensor , torch .Tensor , torch .Tensor , torch .Tensor , torch .Tensor ]:
180- """Pure-PyTorch fused post-conv1d prep: split + l2norm + gating.
181-
182- Args:
183- conv_output: [L, qkv_dim] contiguous conv'd mixed_qkv
184- a: [L, HV] gating input
185- b: [L, HV] gating input
186- A_log: [HV] log decay parameter
187- dt_bias: [HV] dt bias parameter
188- num_k_heads: number of K heads (H)
189- head_k_dim: dimension per K head (K)
190- head_v_dim: dimension per V head (V)
191- apply_l2norm: whether to L2-normalize q and k
192- output_g_exp: if True, output exp(g) instead of g
193-
194- Returns:
195- q: [L, H, K], k: [L, H, K], v: [L, HV, V], g: [L, HV], beta: [L, HV]
274+ """Pure-PyTorch fused post-conv preparation for GDN prefill.
275+
276+ Splits conv output into q, k, v, computes gating g and beta.
277+ conv_output: [L, H*K + H*K + HV*V]
278+ a: [L, HV] (pre-sigmoid gate input)
279+ b: [L, HV] (pre-sigmoid beta input)
280+ A_log: [HV] (log of decay rate)
281+ dt_bias: [HV] (bias for gate computation)
282+ num_k_heads: number of K heads (H)
283+ head_k_dim: dimension per K head (K)
284+ head_v_dim: dimension per V head (V)
196285 """
197- L = conv_output .shape [0 ]
198286 H = num_k_heads
199287 K = head_k_dim
200288 V = head_v_dim
201- HV = A_log .shape [0 ]
289+ HV = A_log .shape [0 ] # num value heads derived from A_log shape
290+
202291 dtype = conv_output .dtype
203292 device = conv_output .device
293+ L = conv_output .shape [0 ]
204294
205295 if L == 0 :
206296 q = torch .empty (L , H , K , dtype = dtype , device = device )
0 commit comments