Skip to content

Commit 4416dc2

Browse files
authored
Qwen3.5 moe optimizations (#113)
* tilelang gated RMSNorm for Qwen3.5 GDN output (cherry picked from commit 6dcdb3f) * fused zba spilt (cherry picked from commit 58d9e4a) * support decode gdn mate (cherry picked from commit 8b463e9) * qwen3.5 tilelang prefill causal conv1d (cherry picked from commit 55c085e) * feed mate no-copy strided q/k/v views and fused gdn gating (cherry picked from commit e5f6b40) * del causal_conv1d d2h and gemmarmsnorm 3D to 2D (cherry picked from commit e947cdb) Series patch renumbered 0086 -> 0081; v0.24.0-dev already uses 0086. * use mate output to reduce dtod and del cu_seqlens to int64 (cherry picked from commit 32b7287) * del fused moe deepseek v4 name and fused glue deepgemm moe prefill (cherry picked from commit 088e0fb) Keep a default-on VLLM_MUSA_MOE_DEEPGEMM_PREFILL kill switch so the generalized prefill path can be turned off without a code change. * shared expert folded into the routed FP8 grouped GEMM as slot 256 (cherry picked from commit 7312722) Widen the existing fold gate to accept FP8 [128,128] block weights in addition to unquantized, keep it off under expert parallelism / EPLB, and add the VLLM_MUSA_MOE_SHARED_EXPERT_FUSION switch. * row-tiled fused silu+mul+FP8-quant kernel replace silu_and_mul_per_token_group_fp8_quant (cherry picked from commit 44268a4) * optional separate contiguous mamba state pool for in-place GDN decode (cherry picked from commit 7e8c9b3) Only the dedicated BlockPool half: the contiguous per-state striding it also carried is already unconditional in the gpu_model_runner patch. Regenerated the kv_cache_utils patch, whose upstream copy was truncated. * default the separate mamba pool on Sharing one KV tensor between a mamba layer and an attention layer is only safe while both index it as block_id * page_size. The segregated per-state mamba layout breaks that congruence, so without the dedicated pool the two overlap and decode output degrades past ~9 concurrent requests. Verified on Qwen3.5-35B-A3B-FP8, tp1, 16 capture sizes: shared pool is clean at 4 and 8 concurrent and garbage at 16; the dedicated pool is 16/16 clean. * per token group quant fp8 and del some redundant op (cherry picked from commit 8eae317) Series patch renumbered 0090 -> 0085. Also aligns the GDN decode env default with the kv-cache one so the dedicated pool takes the in-place path instead of falling back to gather/scatter. * rebase two build patches onto the current series 0060 carried pre-#108 context for the PrivateUse1 registration block that 0044 now emits two lines longer, so the final hunk no longer matched. 0085 re-applied the is_musa() branch that 0012 already adds to the per-token-group quant dispatch; it now only introduces the vec fast path. Both were caught by running vllm_musa/patches/build_apply.py, which is what setup.py invokes; a plain three-way apply absorbs the duplicate and hides it. * fuse QK-RMSNorm and MRoPE for interleaved MRoPE gated attention Ports the MUSA kernel from the qwen35_moe branch. The IR provider registered for gated_qkv_rms_norm_rope only enters the priority list under Inductor and excludes routed MoE, so Qwen3.5 ran the eager rotary path; this dispatches the fused kernel ahead of the IR call and leaves it as the fallback. Measured on Qwen3.5-35B-A3B-FP8 tp1, 2500/1500 x16 @ concurrency 64: output throughput 509.45 -> 557.54 tok/s, mean TPOT 30.65 -> 27.99 ms. * perf: dispatch residual Gemma RMSNorm through the fused JIT kernel
1 parent 68b5ef7 commit 4416dc2

26 files changed

Lines changed: 9320 additions & 74 deletions

vllm_musa/jit_kernel/csrc/norm.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,138 @@ def _fused_add_rmsnorm_custom_fake(
150150
mutates_args=["input", "residual"],
151151
fake_impl=_fused_add_rmsnorm_custom_fake,
152152
)
153+
154+
155+
@cache_once
156+
def _qk_mrope_module():
157+
import tilelang
158+
159+
tilelang_dir = Path(tilelang.__file__).resolve().parent
160+
return load_musa_jit(
161+
"vllm_musa_norm_qk_mrope",
162+
("norm/qk_mrope.mu",),
163+
extra_musa_cflags=(
164+
f"-I{(tilelang_dir / 'src').resolve()}",
165+
f"-I{(tilelang_dir / '3rdparty' / 'mutlass' / 'include').resolve()}",
166+
"-Wno-error=address-of-temporary",
167+
"-fmusa-flush-denormals-to-zero",
168+
"-fno-signed-zeros",
169+
"-D__MUSA_ARCH_LIST__=310",
170+
"-mllvm",
171+
"-mtgpu-opt-level=1",
172+
"-mllvm",
173+
"-mtgpu-load-store-opt=1",
174+
"-mllvm",
175+
"-mtgpu-fold-global-ldst=1",
176+
"-mllvm",
177+
"-mtgpu-load-cluster-mutation=1",
178+
"-mllvm",
179+
"-mtgpu-store-cluster-mutation=1",
180+
"-mllvm",
181+
"-mtgpu-memory-sched-mutation=1",
182+
"-mllvm",
183+
"-mtgpu-alloc-shared-memory-from-zero=1",
184+
),
185+
)
186+
187+
188+
def fused_qk_rmsnorm_mrope(
189+
q: torch.Tensor,
190+
k: torch.Tensor,
191+
q_weight: torch.Tensor,
192+
k_weight: torch.Tensor,
193+
positions: torch.Tensor,
194+
cos_sin_cache: torch.Tensor,
195+
is_neox: bool,
196+
mrope_section_t: int,
197+
mrope_section_h: int,
198+
mrope_section_w: int,
199+
is_interleaved: bool,
200+
eps: float = 1e-6,
201+
gemma: bool = False,
202+
) -> tuple[torch.Tensor, torch.Tensor]:
203+
"""QK-RMSNorm + MRoPE in one kernel. q/k are (tokens, heads, head_dim)."""
204+
q_out = torch.empty_like(q)
205+
k_out = torch.empty_like(k)
206+
torch.ops.vllm.musa_csrc_fused_qk_rmsnorm_mrope(
207+
q,
208+
k,
209+
q_weight,
210+
k_weight,
211+
positions,
212+
cos_sin_cache,
213+
q_out,
214+
k_out,
215+
bool(is_neox),
216+
int(mrope_section_t),
217+
int(mrope_section_h),
218+
int(mrope_section_w),
219+
bool(is_interleaved),
220+
float(eps),
221+
bool(gemma),
222+
)
223+
return q_out, k_out
224+
225+
226+
def _fused_qk_rmsnorm_mrope_custom(
227+
q: torch.Tensor,
228+
k: torch.Tensor,
229+
q_weight: torch.Tensor,
230+
k_weight: torch.Tensor,
231+
positions: torch.Tensor,
232+
cos_sin_cache: torch.Tensor,
233+
q_out: torch.Tensor,
234+
k_out: torch.Tensor,
235+
is_neox: bool,
236+
mrope_section_t: int,
237+
mrope_section_h: int,
238+
mrope_section_w: int,
239+
is_interleaved: bool,
240+
eps: float,
241+
gemma: bool,
242+
) -> None:
243+
_qk_mrope_module().sgl_musa_fused_qk_rmsnorm_mrope(
244+
q,
245+
k,
246+
q_weight,
247+
k_weight,
248+
positions,
249+
cos_sin_cache,
250+
q_out,
251+
k_out,
252+
bool(is_neox),
253+
int(mrope_section_t),
254+
int(mrope_section_h),
255+
int(mrope_section_w),
256+
bool(is_interleaved),
257+
float(eps),
258+
bool(gemma),
259+
)
260+
261+
262+
def _fused_qk_rmsnorm_mrope_custom_fake(
263+
q: torch.Tensor,
264+
k: torch.Tensor,
265+
q_weight: torch.Tensor,
266+
k_weight: torch.Tensor,
267+
positions: torch.Tensor,
268+
cos_sin_cache: torch.Tensor,
269+
q_out: torch.Tensor,
270+
k_out: torch.Tensor,
271+
is_neox: bool,
272+
mrope_section_t: int,
273+
mrope_section_h: int,
274+
mrope_section_w: int,
275+
is_interleaved: bool,
276+
eps: float,
277+
gemma: bool,
278+
) -> None:
279+
return
280+
281+
282+
direct_register_custom_op(
283+
op_name="musa_csrc_fused_qk_rmsnorm_mrope",
284+
op_func=_fused_qk_rmsnorm_mrope_custom,
285+
mutates_args=["q_out", "k_out"],
286+
fake_impl=_fused_qk_rmsnorm_mrope_custom_fake,
287+
)
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
template <typename T>
2+
struct __align__(16) Vec8Storage {
3+
T elem[8];
4+
};
5+
6+
struct __align__(32) Float8Storage {
7+
float elem[8];
8+
};
9+
10+
template <typename T>
11+
struct __align__(16) Vec8 {
12+
union {
13+
Vec8Storage<T> storage;
14+
T elem[8];
15+
} val;
16+
17+
__device__ __forceinline__ Vec8() {}
18+
19+
template <typename Offset>
20+
static __device__ __forceinline__ Vec8 load(const T* ptr, Offset idx) {
21+
return *(const Vec8*)(ptr + idx);
22+
}
23+
24+
template <typename Offset>
25+
static __device__ __forceinline__ Vec8 load_byp_slc(const T* ptr, Offset idx) {
26+
#if ((defined __MUSA_ARCH__) && (__MUSA_ARCH__ == 310))
27+
Vec8 dst;
28+
const T* addr = ptr + idx;
29+
asm volatile(
30+
"LSU.LD.B128 %0, %1, _, 16, 1, 1, inner_persist=0, outer_persist=2, "
31+
"chrnt=l2_l3, slc=byp, persist=0, stride_add_first=0"
32+
: "=R"(dst)
33+
: "R"(addr));
34+
return dst;
35+
#else
36+
return *(const Vec8*)(ptr + idx);
37+
#endif
38+
}
39+
};
40+
41+
struct __align__(32) Float8 {
42+
union {
43+
Float8Storage storage;
44+
float elem[8];
45+
} val;
46+
47+
__device__ __forceinline__ Float8() {}
48+
};
49+
50+
__device__ __forceinline__ int mrope_24_20_20_interleaved_axis(int rot_offset) {
51+
constexpr unsigned long long axis1_mask = 0x492492492492492ULL;
52+
constexpr unsigned long long axis2_mask = 0x924924924924924ULL;
53+
const unsigned long long bit = 1ULL << rot_offset;
54+
return ((axis1_mask & bit) != 0ULL) + (((axis2_mask & bit) != 0ULL) << 1);
55+
}
56+
57+
__device__ __forceinline__ int mrope_11_11_10_interleaved_axis(int rot_offset) {
58+
constexpr unsigned int axis1_mask = 0x92492492U;
59+
constexpr unsigned int axis2_mask = 0x24924924U;
60+
const unsigned int bit = 1U << rot_offset;
61+
return ((axis1_mask & bit) != 0U) + (((axis2_mask & bit) != 0U) << 1);
62+
}
63+
64+
__device__ __forceinline__ float fast_rsqrt(float value) {
65+
#if ((defined __MUSA_ARCH__) && (__MUSA_ARCH__ == 310))
66+
const float half_value = 0.5f * value;
67+
float y = __frsqrt_rn(value);
68+
y = y * (1.5f - half_value * y * y);
69+
return y;
70+
#else
71+
return rsqrtf(value);
72+
#endif
73+
}
74+
75+
__device__ __forceinline__ float block_sum(float value, float* warp_sums) {
76+
const int tid = (int)threadIdx.x;
77+
const int lane = tid & 31;
78+
const int warp = tid >> 5;
79+
const int num_warps = ((int)blockDim.x + 31) >> 5;
80+
81+
#pragma unroll
82+
for (int offset = 16; offset > 0; offset >>= 1) {
83+
value += __shfl_down_sync(0xffffffff, value, offset, 32);
84+
}
85+
if (lane == 0) {
86+
warp_sums[warp] = value;
87+
}
88+
__syncthreads_lm();
89+
90+
value = tid < num_warps ? warp_sums[lane] : 0.0f;
91+
if (warp == 0) {
92+
#pragma unroll
93+
for (int offset = 16; offset > 0; offset >>= 1) {
94+
value += __shfl_down_sync(0xffffffff, value, offset, 32);
95+
}
96+
if (lane == 0) {
97+
warp_sums[0] = value;
98+
}
99+
}
100+
__syncthreads_lm();
101+
return warp_sums[0];
102+
}
103+
104+
__device__ __forceinline__ float block_sum_8warps(float value, float* warp_sums) {
105+
const int tid = (int)threadIdx.x;
106+
const int lane = tid & 31;
107+
const int warp = tid >> 5;
108+
109+
#pragma unroll
110+
for (int offset = 16; offset > 0; offset >>= 1) {
111+
value += __shfl_down_sync(0xffffffff, value, offset, 32);
112+
}
113+
if (lane == 0) {
114+
warp_sums[warp] = value;
115+
}
116+
__syncthreads_lm();
117+
118+
value = lane < 8 ? warp_sums[lane] : 0.0f;
119+
if (warp == 0) {
120+
#pragma unroll
121+
for (int offset = 16; offset > 0; offset >>= 1) {
122+
value += __shfl_down_sync(0xffffffff, value, offset, 32);
123+
}
124+
if (lane == 0) {
125+
warp_sums[0] = value;
126+
}
127+
}
128+
__syncthreads_lm();
129+
return warp_sums[0];
130+
}
131+
132+
__device__ __forceinline__ float block_sum_4warps(float value, float* warp_sums) {
133+
const int tid = (int)threadIdx.x;
134+
const int lane = tid & 31;
135+
const int warp = tid >> 5;
136+
137+
#pragma unroll
138+
for (int offset = 16; offset > 0; offset >>= 1) {
139+
value += __shfl_down_sync(0xffffffff, value, offset, 32);
140+
}
141+
if (lane == 0) {
142+
warp_sums[warp] = value;
143+
}
144+
__syncthreads_lm();
145+
146+
value = lane < 4 ? warp_sums[lane] : 0.0f;
147+
if (warp == 0) {
148+
#pragma unroll
149+
for (int offset = 16; offset > 0; offset >>= 1) {
150+
value += __shfl_down_sync(0xffffffff, value, offset, 32);
151+
}
152+
if (lane == 0) {
153+
warp_sums[0] = value;
154+
}
155+
}
156+
__syncthreads_lm();
157+
return warp_sums[0];
158+
}

0 commit comments

Comments
 (0)