Skip to content

Commit 011774c

Browse files
committed
fix code
1 parent c3c6276 commit 011774c

3 files changed

Lines changed: 223 additions & 113 deletions

File tree

vllm_fl/dispatch/backends/vendor/ascend/impl/fla/gdn_torch_ops.py

Lines changed: 203 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,135 @@
1313
import torch
1414
import 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

17146
def 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

137254
def 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

168262
def 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)

vllm_fl/dispatch/backends/vendor/ascend/impl/mm_encoder_attention.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,4 @@ def _sdpa(qx, kx, vx):
134134
context_layer = context_layer.transpose(1, 2).contiguous()
135135
context_layer = context_layer.view(bsz, -1, self.num_heads * self.head_size)
136136
return context_layer
137+

0 commit comments

Comments
 (0)