Skip to content

Commit c821a04

Browse files
committed
fix: address code review comments for kunlunxin vendor backend
1 parent 33abe8f commit c821a04

8 files changed

Lines changed: 182 additions & 77 deletions

File tree

vllm_fl/dispatch/backends/vendor/kunlunxin/impl/causal_conv1d.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,22 @@ def causal_conv1d_update_kunlunxin(
118118
Returns:
119119
output tensor (same shape as input x)
120120
"""
121+
# Reject speculative decoding: not supported by native kernel
122+
if num_accepted_tokens is not None:
123+
raise NotImplementedError(
124+
"Kunlunxin causal_conv1d_update does not support speculative decoding. "
125+
"The native xtorch_ops.causal_conv1d_update kernel does not accept "
126+
"num_accepted_tokens parameter."
127+
)
128+
129+
# Reject varlen mode with query_start_loc: not supported by native kernel
130+
if query_start_loc is not None and max_query_len > 0:
131+
raise NotImplementedError(
132+
"Kunlunxin causal_conv1d_update does not support varlen with query_start_loc. "
133+
"The native xtorch_ops.causal_conv1d_update kernel does not accept "
134+
"query_start_loc and max_query_len parameters."
135+
)
136+
121137
if activation not in [None, "silu", "swish"]:
122138
raise NotImplementedError(
123139
f"activation must be None, silu, or swish, actual: {activation}"

vllm_fl/dispatch/backends/vendor/kunlunxin/impl/fla/chunk.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
Contains both the low-level kernel wrapper (chunk_gated_delta_rule_fwd) and
1717
the top-level entry (chunk_gated_delta_rule).
1818
19-
The top-level ChunkGatedDeltaRuleFunction.forward **skips l2norm** because the
20-
Kunlunxin kernel handles it internally (use_qk_l2norm_in_kernel=True).
19+
The Kunlunxin native kernel supports use_qk_l2norm_in_kernel natively,
20+
so normalization is delegated to the kernel when enabled, rather than
21+
being done in Python like other backends.
2122
"""
2223

2324
from __future__ import annotations
@@ -41,6 +42,7 @@ def chunk_gated_delta_rule_fwd(
4142
initial_state: torch.Tensor,
4243
output_final_state: bool,
4344
cu_seqlens: Optional[torch.LongTensor] = None,
45+
use_qk_l2norm_in_kernel: bool = True,
4446
) -> tuple:
4547
"""
4648
Kunlunxin chunked gated delta rule forward pass.
@@ -74,6 +76,9 @@ def chunk_gated_delta_rule_fwd(
7476

7577
cu_seqlens_cpu = cu_seqlens.cpu() if cu_seqlens is not None else None
7678

79+
# Use -1 for automatic scale (1/sqrt(K)) only when caller does not provide one
80+
kernel_scale = scale if scale is not None else -1
81+
7782
final_state = torch.empty_like(state_input)
7883
o = torch.empty_like(v)
7984
xtorch_ops.chunk_gated_delta_rule(
@@ -82,13 +87,13 @@ def chunk_gated_delta_rule_fwd(
8287
v,
8388
g_input,
8489
beta_input,
85-
-1, # uses -1 for automatic scale
90+
kernel_scale,
8691
state_input,
8792
o,
8893
final_state,
8994
cu_seqlens_cpu,
9095
head_first=False,
91-
use_qk_l2norm_in_kernel=True, # always True in Kunlunxin
96+
use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
9297
)
9398

9499
# Transpose final_state back to [N, H, K, V]
@@ -115,8 +120,8 @@ def forward(
115120
cu_seqlens: Optional[torch.LongTensor] = None,
116121
use_qk_l2norm_in_kernel: bool = False,
117122
):
118-
# klx diff: skip l2norm — kernel handles it internally
119-
# (upstream would do l2norm_fwd(q), l2norm_fwd(k) here)
123+
# When use_qk_l2norm_in_kernel=True, the native kernel handles L2-norm
124+
# internally. When False, the caller has already normalized q/k externally.
120125

121126
# [N, H, K, V] -> [N, H, V, K] for kunlunxin kernel
122127
if initial_state is not None:
@@ -132,6 +137,7 @@ def forward(
132137
initial_state=initial_state,
133138
output_final_state=output_final_state,
134139
cu_seqlens=cu_seqlens,
140+
use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
135141
)
136142
ctx.scale = scale
137143
ctx.use_qk_l2norm_in_kernel = use_qk_l2norm_in_kernel

vllm_fl/dispatch/backends/vendor/kunlunxin/impl/fused_moe/experts_selector.py

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ def vllm_topk_softmax(
2626
) -> tuple[torch.Tensor, ...]:
2727
if renormalize:
2828
xtorch_ops.moe_softmax_topk_norm(
29-
gating_output, topk_weights, topk_indices, None,
29+
gating_output, topk_weights, topk_indices, token_expert_indices,
3030
)
3131
else:
3232
xtorch_ops.moe_softmax_topk(
33-
gating_output, topk_weights, topk_indices, None,
33+
gating_output, topk_weights, topk_indices, token_expert_indices,
3434
)
3535
return topk_weights, topk_indices
3636

@@ -78,44 +78,60 @@ def fused_topk_bias(
7878

7979

8080
def grouped_topk(
81-
hidden_states: torch.Tensor,
82-
gating_output: torch.Tensor,
81+
scores: torch.Tensor,
82+
n_group: int,
83+
topk_group: int,
8384
topk: int,
8485
renormalize: bool,
85-
num_expert_group: int = 0,
86-
topk_group: int = 0,
87-
scoring_func: str = "softmax",
88-
routed_scaling_factor: float = 1.0,
89-
e_score_correction_bias: Optional[torch.Tensor] = None,
86+
routed_scaling_factor: float,
87+
bias: torch.Tensor,
88+
scoring_func: int = 0,
9089
) -> tuple[torch.Tensor, torch.Tensor]:
91-
"""Grouped top-k selection."""
92-
seq_num = gating_output.shape[0]
93-
94-
if scoring_func == "softmax":
95-
scores = gating_output.softmax(dim=-1)
96-
elif scoring_func == "sigmoid":
97-
scores = gating_output.sigmoid()
98-
else:
99-
raise ValueError(f"Unsupported scoring_func: {scoring_func}")
100-
101-
if e_score_correction_bias is not None:
102-
assert e_score_correction_bias.dtype == torch.float32
103-
scores_for_choice = scores + e_score_correction_bias.unsqueeze(0)
90+
"""Grouped top-k selection with unified dispatch ABI.
91+
92+
Args:
93+
scores: Already computed scores tensor (after softmax/sigmoid if needed)
94+
n_group: Number of expert groups
95+
topk_group: Number of groups to select
96+
topk: Total number of experts to select
97+
renormalize: Whether to renormalize weights
98+
routed_scaling_factor: Scaling factor for routing weights
99+
bias: Score correction bias tensor
100+
scoring_func: 0=none (scores already processed), 1=sigmoid
101+
102+
Returns:
103+
topk_weights: Selected expert weights
104+
topk_ids: Selected expert indices
105+
"""
106+
seq_num = scores.shape[0]
107+
108+
# Apply sigmoid if scoring_func=1 (dispatcher may pass raw logits in this case)
109+
if scoring_func == 1:
110+
scores = scores.sigmoid()
111+
112+
# Apply bias for expert selection
113+
if bias is not None and bias.numel() > 0:
114+
assert bias.dtype == torch.float32
115+
scores_for_choice = scores + bias.unsqueeze(0)
104116
else:
105117
scores_for_choice = scores
106118

107-
topk_weights = torch.empty((seq_num, topk), dtype=torch.float, device=gating_output.device)
108-
topk_ids = torch.empty((seq_num, topk), dtype=torch.int32, device=gating_output.device)
119+
topk_weights = torch.empty((seq_num, topk), dtype=torch.float, device=scores.device)
120+
topk_ids = torch.empty((seq_num, topk), dtype=torch.int32, device=scores.device)
109121

110122
xtorch_ops.moe_group_topk(
111-
scores_for_choice, num_expert_group, topk_group,
123+
scores_for_choice, n_group, topk_group,
112124
topk_weights, topk_ids, None,
113125
)
114126

115-
if e_score_correction_bias is not None:
127+
# If bias was used for selection, gather original scores for weights
128+
if bias is not None and bias.numel() > 0:
116129
topk_weights = scores.gather(1, topk_ids)
117130

118131
if renormalize:
119132
topk_weights = topk_weights / topk_weights.sum(dim=-1, keepdim=True)
120133

134+
if routed_scaling_factor != 1.0:
135+
topk_weights = topk_weights * routed_scaling_factor
136+
121137
return topk_weights, topk_ids

vllm_fl/dispatch/backends/vendor/kunlunxin/impl/fused_moe/fused_moe.py

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,23 @@ def _klx_fused_experts(
3030
w2: torch.Tensor,
3131
topk_weights: torch.Tensor,
3232
topk_ids: torch.Tensor,
33+
activation: str = "silu",
3334
use_int8_w8a8: bool = False,
3435
use_int8_w4a8: bool = False,
3536
w1_scale: Optional[torch.Tensor] = None,
3637
w2_scale: Optional[torch.Tensor] = None,
38+
w1_bias: Optional[torch.Tensor] = None,
39+
w2_bias: Optional[torch.Tensor] = None,
3740
) -> None:
3841
"""
3942
Fused MoE expert computation using xtorch_ops (sorted path).
4043
41-
Pipeline: gen_block_statistic -> moe_pre_sorted -> moe_fc(w1) -> swiglu -> moe_fc(w2) -> post (weight+sum)
44+
Pipeline: gen_block_statistic -> moe_pre_sorted -> moe_fc(w1) -> activation -> moe_fc(w2) -> post (weight+sum)
45+
46+
Args:
47+
activation: Activation function type. Supported: "silu", "gelu", "relu"
48+
w1_bias: Optional bias for gate+up projection [E, 2*ffn_hd]
49+
w2_bias: Optional bias for down projection [E, hidden_dim]
4250
"""
4351
if use_int8_w8a8 or use_int8_w4a8:
4452
raise NotImplementedError("_klx_fused_experts is not supported for int8 w8a8 and w4a8.")
@@ -73,18 +81,43 @@ def _klx_fused_experts(
7381
inner_fc_out = torch.empty(seq_num, moe_topk, double_ffn_hd, dtype=dtype, device=device)
7482
xtorch_ops.moe_fc(
7583
moe_expand, w1, sorted_tokens_num_lod, moe_index, moe_topk, inner_fc_out,
84+
bias=w1_bias,
7685
)
7786
inner_fc_out = inner_fc_out.view(moe_input_num, double_ffn_hd)
7887

79-
# Step 4: SwiGLU activation (in-place on first half)
88+
# Step 4: Activation
8089
ffn_hd = double_ffn_hd // 2
81-
swiglu_out = torch.empty(moe_input_num, ffn_hd, dtype=dtype, device=device)
82-
xtorch_ops.swiglu(inner_fc_out, swiglu_out)
90+
if activation == "silu":
91+
# SwiGLU: silu(gate) * up
92+
swiglu_out = torch.empty(moe_input_num, ffn_hd, dtype=dtype, device=device)
93+
xtorch_ops.swiglu(inner_fc_out, swiglu_out)
94+
elif activation == "gelu":
95+
# GeGLU: gelu(gate) * up
96+
gate = inner_fc_out[:, :ffn_hd]
97+
up = inner_fc_out[:, ffn_hd:]
98+
swiglu_out = xtorch_ops.gelu(gate) * up
99+
elif activation == "relu":
100+
# ReLU: relu(gate) * up
101+
gate = inner_fc_out[:, :ffn_hd]
102+
up = inner_fc_out[:, ffn_hd:]
103+
swiglu_out = xtorch_ops.relu(gate) * up
104+
elif activation in ["gelu_no_mul", "silu_no_mul"]:
105+
# No mul variant: only apply activation, no gating
106+
if activation == "gelu_no_mul":
107+
swiglu_out = xtorch_ops.gelu(inner_fc_out)
108+
else: # silu_no_mul
109+
swiglu_out = xtorch_ops.silu(inner_fc_out)
110+
else:
111+
raise ValueError(
112+
f"Unsupported activation '{activation}'. "
113+
f"Supported: ['silu', 'gelu', 'relu', 'gelu_no_mul', 'silu_no_mul']"
114+
)
83115

84116
# Step 5: Outer FC (down projection)
85117
outer_fc_out = torch.empty(seq_num, moe_topk, hidden_dim, dtype=dtype, device=device)
86118
xtorch_ops.moe_fc(
87119
swiglu_out, w2, sorted_tokens_num_lod, moe_index, moe_topk, outer_fc_out,
120+
bias=w2_bias,
88121
)
89122
outer_fc_out = outer_fc_out.view(moe_input_num, hidden_dim)
90123

@@ -127,13 +160,37 @@ def fused_experts_impl(
127160
) -> torch.Tensor:
128161
"""
129162
Kunlunxin fused experts implementation.
130-
163+
131164
This function matches the signature of vllm_fl.ops.fused_moe.fused_moe.fused_experts_impl
132165
and is patched in by the Kunlunxin patch system.
166+
167+
Args:
168+
activation: Activation function. Supported: "silu", "gelu", "relu", "gelu_no_mul", "silu_no_mul"
169+
apply_router_weight_on_input: If True, apply router weights on input (NOT SUPPORTED)
170+
w1_bias: Optional bias for gate+up projection
171+
w2_bias: Optional bias for down projection
133172
"""
173+
# Stage 1: Explicit rejections for unsupported features
174+
175+
# 1.1: Reject unsupported quantization schemes
134176
if use_fp8_w8a8 or use_int8_w8a16 or use_int4_w4a16:
135177
raise NotImplementedError(
136-
"Kunlunxin fused_experts does not support fp8/int8_w8a16/int4 quantization yet."
178+
"Kunlunxin fused_experts does not support fp8_w8a8/int8_w8a16/int4_w4a16 quantization yet."
179+
)
180+
181+
# 1.2: Reject apply_router_weight_on_input=True
182+
if apply_router_weight_on_input:
183+
raise NotImplementedError(
184+
"Kunlunxin fused_experts does not support apply_router_weight_on_input=True. "
185+
"Router weights are always applied in the moe_post stage (after down projection)."
186+
)
187+
188+
# 1.3: Validate activation function
189+
SUPPORTED_ACTIVATIONS = ["silu", "gelu", "relu", "gelu_no_mul", "silu_no_mul"]
190+
if activation not in SUPPORTED_ACTIVATIONS:
191+
raise NotImplementedError(
192+
f"Kunlunxin fused_experts does not support activation '{activation}'. "
193+
f"Supported activations: {SUPPORTED_ACTIVATIONS}"
137194
)
138195

139196
num_tokens = hidden_states.size(0)
@@ -157,9 +214,12 @@ def fused_experts_impl(
157214
w2=w2,
158215
topk_weights=topk_weights,
159216
topk_ids=topk_ids,
217+
activation=activation,
160218
use_int8_w8a8=use_int8_w8a8,
161219
w1_scale=w1_scale,
162220
w2_scale=w2_scale,
221+
w1_bias=w1_bias,
222+
w2_bias=w2_bias,
163223
)
164224

165225
return output

vllm_fl/dispatch/backends/vendor/kunlunxin/kunlunxin.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,9 @@ def attention_backend(self, use_mla: bool = False, use_sparse: bool = False) ->
135135
Returns:
136136
Fully qualified class path string
137137
"""
138+
# TODO: Implement MLA (Multi-head Latent Attention) support for Kunlunxin
138139
if use_mla:
140+
# TODO: Implement MLA with sparse attention support for Kunlunxin
139141
if use_sparse:
140142
raise NotImplementedError("MLA with sparse attention is not implemented for Kunlunxin yet.")
141143
raise NotImplementedError("MLA attention is not implemented for Kunlunxin yet.")
@@ -193,22 +195,23 @@ def topk_softmax(
193195

194196
def grouped_topk(
195197
self,
196-
hidden_states: 'torch.Tensor',
197-
gating_output: 'torch.Tensor',
198+
scores: 'torch.Tensor',
199+
n_group: int,
200+
topk_group: int,
198201
topk: int,
199202
renormalize: bool,
200-
num_expert_group: int = 0,
201-
topk_group: int = 0,
202-
scoring_func: str = 'softmax',
203-
routed_scaling_factor: float = 1.0,
204-
e_score_correction_bias: 'torch.Tensor | None' = None,
203+
routed_scaling_factor: float,
204+
bias: 'torch.Tensor',
205+
scoring_func: int = 0,
205206
) -> tuple:
206207
from .impl.fused_moe.experts_selector import grouped_topk
207208
return grouped_topk(
208-
hidden_states, gating_output, topk, renormalize,
209-
num_expert_group=num_expert_group,
210-
topk_group=topk_group,
211-
scoring_func=scoring_func,
212-
routed_scaling_factor=routed_scaling_factor,
213-
e_score_correction_bias=e_score_correction_bias,
209+
scores,
210+
n_group,
211+
topk_group,
212+
topk,
213+
renormalize,
214+
routed_scaling_factor,
215+
bias,
216+
scoring_func,
214217
)

vllm_fl/dispatch/backends/vendor/kunlunxin/patch.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def apply_kunlunxin_patches():
4343
patch_fused_gdn_gating()
4444
patch_ssm_cache_update()
4545
patch_sampler_rng()
46+
patch_decode_attention()
4647
logger.info("Applied all Kunlunxin patches")
4748

4849

vllm_fl/dispatch/config/kunlunxin.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,19 @@ op_backends:
3333
- vendor
3434
- reference
3535

36-
# rms_norm: prioritize vendor (Kunlunxin) implementation
36+
# rms_norm: prioritize flagos (FlagGems) implementation, fallback to vendor
3737
rms_norm:
3838
- flagos
3939
- vendor
4040
- reference
4141

42-
# silu_and_mul: prioritize vendor (Kunlunxin) implementation
42+
# silu_and_mul: prioritize flagos (FlagGems) implementation, fallback to vendor
4343
silu_and_mul:
4444
- flagos
4545
- vendor
4646
- reference
4747

48-
# rotary_embedding: prioritize vendor (Kunlunxin) implementation
48+
# rotary_embedding: prioritize flagos (FlagGems) implementation, fallback to vendor
4949
rotary_embedding:
5050
- flagos
5151
- vendor

0 commit comments

Comments
 (0)