main
cu 319 lines 12.5 KB
Raw
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 //
4 // Router GEMM: activation(T) x weight(fp32) -> fp32, M<=32, for the
5 // supported (E, H) pairs listed at the bottom of this file.
6 // Supports bf16 or fp32 activation; weight is always fp32.
7
8 #include <cuda_bf16.h>
9 #include <cuda_runtime.h>
10
11 #include <type_traits>
12
13 // ---------------------------------------------------------------------------
14 // Load helpers
15 // ---------------------------------------------------------------------------
16
17 // Load VPT fp32 values from the weight matrix (always fp32).
18 // VPT=4 when activation is fp32 (one float4 load)
19 // VPT=8 when activation is bf16 (two float4 loads)
20 template <int VPT>
21 __device__ __forceinline__ void load_weight(float const* ptr, float* dst);
22
23 template <>
24 __device__ __forceinline__ void load_weight<4>(float const* ptr, float* dst) {
25 float4 v = *reinterpret_cast<float4 const*>(ptr);
26 dst[0] = v.x;
27 dst[1] = v.y;
28 dst[2] = v.z;
29 dst[3] = v.w;
30 }
31
32 template <>
33 __device__ __forceinline__ void load_weight<8>(float const* ptr, float* dst) {
34 float4 v0 = *reinterpret_cast<float4 const*>(ptr);
35 float4 v1 = *reinterpret_cast<float4 const*>(ptr + 4);
36 dst[0] = v0.x;
37 dst[1] = v0.y;
38 dst[2] = v0.z;
39 dst[3] = v0.w;
40 dst[4] = v1.x;
41 dst[5] = v1.y;
42 dst[6] = v1.z;
43 dst[7] = v1.w;
44 }
45
46 // Load VPT activation values and convert to fp32.
47 template <typename T, int VPT>
48 __device__ __forceinline__ void load_activation(T const* ptr, float* dst);
49
50 // fp32 activation: one float4 load, no conversion needed.
51 template <>
52 __device__ __forceinline__ void load_activation<float, 4>(float const* ptr,
53 float* dst) {
54 float4 v = *reinterpret_cast<float4 const*>(ptr);
55 dst[0] = v.x;
56 dst[1] = v.y;
57 dst[2] = v.z;
58 dst[3] = v.w;
59 }
60
61 // bf16 activation: one uint4 load (8 × bf16) + element-wise conversion.
62 template <>
63 __device__ __forceinline__ void load_activation<__nv_bfloat16, 8>(
64 __nv_bfloat16 const* ptr, float* dst) {
65 uint4 v = *reinterpret_cast<uint4 const*>(ptr);
66 __nv_bfloat16 const* bf16_ptr = reinterpret_cast<__nv_bfloat16 const*>(&v);
67 #pragma unroll
68 for (int i = 0; i < 8; i++) dst[i] = __bfloat162float(bf16_ptr[i]);
69 }
70
71 // ---------------------------------------------------------------------------
72 // Kernel
73 // ---------------------------------------------------------------------------
74
75 // InputT : type of activation (float or __nv_bfloat16)
76 // Weight is always fp32; output is always fp32.
77 // VPT = 16 / sizeof(InputT): 4 for fp32, 8 for bf16
78 // Each block computes kEPB expert columns; wider blocks / kEPB > 1 are
79 // selected per (shape, M) in invokeFp32RouterGemm (B300-tuned, see below).
80 // kTGroups > 1 splits the tokens across groups of kBlockSize threads within
81 // the block: all groups scan the same weight K-slices (group 0 misses to
82 // DRAM, later groups hit L1) so weight traffic stays 1x, while per-thread
83 // accumulator registers drop by kTGroups (at M=16 the 32 fp32 accumulators
84 // push the kernel to 128 regs/thread and 1 block/SM).
85 template <typename InputT, int kBlockSize, int kNumTokens, int kEPB,
86 int kNumExperts, int kHiddenDim, int kTGroups = 1>
87 __global__ __launch_bounds__(
88 kBlockSize* kTGroups, 1) void fp32_router_gemm_kernel(float* out,
89 InputT const* mat_a,
90 float const* mat_b) {
91 constexpr int VPT = 16 / sizeof(InputT);
92 constexpr int k_elems_per_k_iteration = VPT * kBlockSize;
93 constexpr int k_iterations = kHiddenDim / k_elems_per_k_iteration;
94 static_assert(kHiddenDim % k_elems_per_k_iteration == 0);
95 static_assert(kNumTokens % kTGroups == 0);
96 constexpr int kWarpSize = 32;
97 constexpr int kNumWarps = kBlockSize / kWarpSize; // per token group
98 constexpr int kMG = kNumTokens / kTGroups; // tokens per group
99
100 int const e_base = blockIdx.x * kEPB;
101 int const tid = threadIdx.x % kBlockSize;
102 int const m0 = (threadIdx.x / kBlockSize) * kMG;
103 int const warpId = tid / kWarpSize;
104 int const laneId = tid % kWarpSize;
105
106 float acc[kMG][kEPB] = {};
107 __shared__ float sm_reduction[kNumTokens][kEPB][kNumWarps];
108
109 #if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900)
110 cudaGridDependencySynchronize();
111 // Fire the PDL trigger right after our own wait instead of at kernel end:
112 // a gridsync-ing consumer is unaffected (its wait always targets full grid
113 // completion), while a consumer that reads none of our outputs (e.g. the
114 // NVFP4 activation quant, which reads the same hidden_states) can launch
115 // now and fully overlap this kernel's body.
116 cudaTriggerProgrammaticLaunchCompletion();
117 #endif
118
119 #pragma unroll
120 for (int ki = 0; ki < k_iterations; ki++) {
121 int const k_base = ki * k_elems_per_k_iteration + tid * VPT;
122
123 float b_float[kEPB][VPT];
124 #pragma unroll
125 for (int e = 0; e < kEPB; e++) {
126 load_weight<VPT>(mat_b + (e_base + e) * kHiddenDim + k_base, b_float[e]);
127 }
128
129 #pragma unroll
130 for (int m_idx = 0; m_idx < kMG; m_idx++) {
131 float a_float[VPT];
132 load_activation<InputT, VPT>(
133 mat_a + (size_t)(m0 + m_idx) * kHiddenDim + k_base, a_float);
134 #pragma unroll
135 for (int e = 0; e < kEPB; e++) {
136 #pragma unroll
137 for (int k = 0; k < VPT; k++) {
138 acc[m_idx][e] += a_float[k] * b_float[e][k];
139 }
140 }
141 }
142 }
143
144 // Warp-level butterfly reduction
145 #pragma unroll
146 for (int m = 0; m < kMG; m++) {
147 #pragma unroll
148 for (int e = 0; e < kEPB; e++) {
149 float sum = acc[m][e];
150 sum += __shfl_xor_sync(0xffffffff, sum, 16);
151 sum += __shfl_xor_sync(0xffffffff, sum, 8);
152 sum += __shfl_xor_sync(0xffffffff, sum, 4);
153 sum += __shfl_xor_sync(0xffffffff, sum, 2);
154 sum += __shfl_xor_sync(0xffffffff, sum, 1);
155 if (laneId == 0) sm_reduction[m0 + m][e][warpId] = sum;
156 }
157 }
158
159 __syncthreads();
160
161 // Parallel finalize: one thread per (m, e) output.
162 for (int idx = threadIdx.x; idx < kNumTokens * kEPB;
163 idx += kBlockSize * kTGroups) {
164 int const m = idx / kEPB;
165 int const e = idx % kEPB;
166 float final_sum = 0.0f;
167 #pragma unroll
168 for (int w = 0; w < kNumWarps; w++) final_sum += sm_reduction[m][e][w];
169 out[m * kNumExperts + e_base + e] = final_sum;
170 }
171 }
172
173 // ---------------------------------------------------------------------------
174 // Launcher
175 // ---------------------------------------------------------------------------
176
177 template <typename InputT, int kBlockSize, int kEPB, int kNumTokens,
178 int kNumExperts, int kHiddenDim, int kTGroups = 1>
179 static void launchFp32RouterGemm(float* output, InputT const* mat_a,
180 float const* mat_b, cudaStream_t stream) {
181 static_assert(kNumExperts % kEPB == 0);
182 cudaLaunchConfig_t config;
183 config.gridDim = kNumExperts / kEPB;
184 config.blockDim = kBlockSize * kTGroups;
185 config.dynamicSmemBytes = 0;
186 config.stream = stream;
187 cudaLaunchAttribute attrs[1];
188 attrs[0].id = cudaLaunchAttributeProgrammaticStreamSerialization;
189 attrs[0].val.programmaticStreamSerializationAllowed = 1;
190 config.numAttrs = 1;
191 config.attrs = attrs;
192 cudaLaunchKernelEx(
193 &config,
194 fp32_router_gemm_kernel<InputT, kBlockSize, kNumTokens, kEPB, kNumExperts,
195 kHiddenDim, kTGroups>,
196 output, mat_a, mat_b);
197 }
198
199 static bool isBlackwellFamily() {
200 static int sm = []() {
201 int dev = 0, major = 0, minor = 0;
202 cudaGetDevice(&dev);
203 cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, dev);
204 cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, dev);
205 return major * 10 + minor;
206 }();
207 return sm >= 100;
208 }
209
210 template <typename InputT, int kNumTokens, int kNumExperts, int kHiddenDim>
211 void invokeFp32RouterGemm(float* output, InputT const* mat_a,
212 float const* mat_b, cudaStream_t stream) {
213 // Geometry tuned on B300 per supported shape, bf16 activation, under a
214 // production-fidelity harness (CUDA-graph replay, per-layer cold weights).
215 // Only enabled on the Blackwell family where it was validated; Hopper and
216 // other shapes / fp32 activation keep the legacy geometry.
217 if constexpr (std::is_same_v<InputT, __nv_bfloat16> && kNumExperts == 128 &&
218 kHiddenDim == 6144) {
219 // MiniMax-M3. Legacy 128/1 only fills 128 blocks and pays the same
220 // accumulator register cliffs; B300 sweep:
221 // even M in [6, 10] : BS=384, EPB=1, 2 token groups (1.26-1.43x)
222 // even M >= 12 : BS=192, EPB=1, 2 token groups (1.59-1.66x at
223 // M >= 18; re-measured on B300+B200: 192 also wins
224 // M=12/14 by 5-11%% on both, ties 384 at 16)
225 // M <= 5 / odd : BS=384, EPB=1 (1.03-1.19x)
226 if (!isBlackwellFamily()) {
227 launchFp32RouterGemm<InputT, 128, 1, kNumTokens, kNumExperts, kHiddenDim>(
228 output, mat_a, mat_b, stream);
229 return;
230 }
231 if constexpr (kNumTokens >= 12 && kNumTokens % 2 == 0) {
232 launchFp32RouterGemm<InputT, 192, 1, kNumTokens, kNumExperts, kHiddenDim,
233 2>(output, mat_a, mat_b, stream);
234 } else if constexpr (kNumTokens >= 6 && kNumTokens % 2 == 0) {
235 launchFp32RouterGemm<InputT, 384, 1, kNumTokens, kNumExperts, kHiddenDim,
236 2>(output, mat_a, mat_b, stream);
237 } else {
238 launchFp32RouterGemm<InputT, 384, 1, kNumTokens, kNumExperts, kHiddenDim>(
239 output, mat_a, mat_b, stream);
240 }
241 } else if constexpr (std::is_same_v<InputT, __nv_bfloat16> &&
242 kNumExperts == 256 && kHiddenDim == 3072) {
243 // MiniMax-M2/M2.5. The 3.1MB weight is latency-floor bound at small M
244 // (legacy already optimal); token groups win only at even M >= 8
245 // (1.05-1.17x). EPB crossover measured between 12 and 16.
246 if (!isBlackwellFamily()) {
247 launchFp32RouterGemm<InputT, 128, 1, kNumTokens, kNumExperts, kHiddenDim>(
248 output, mat_a, mat_b, stream);
249 return;
250 }
251 if constexpr (kNumTokens >= 14 && kNumTokens % 2 == 0) {
252 // M=14 originally measured 0.91x and stayed on legacy; two fresh
253 // sweeps (B300 dev1 + B200) both put 192/2/tg2 ahead by 3.5-4%%.
254 launchFp32RouterGemm<InputT, 192, 2, kNumTokens, kNumExperts, kHiddenDim,
255 2>(output, mat_a, mat_b, stream);
256 } else if constexpr (kNumTokens >= 8 && kNumTokens <= 12 &&
257 kNumTokens % 2 == 0) {
258 launchFp32RouterGemm<InputT, 192, 1, kNumTokens, kNumExperts, kHiddenDim,
259 2>(output, mat_a, mat_b, stream);
260 } else {
261 launchFp32RouterGemm<InputT, 128, 1, kNumTokens, kNumExperts, kHiddenDim>(
262 output, mat_a, mat_b, stream);
263 }
264 } else {
265 launchFp32RouterGemm<InputT, 128, 1, kNumTokens, kNumExperts, kHiddenDim>(
266 output, mat_a, mat_b, stream);
267 }
268 }
269
270 // ---------------------------------------------------------------------------
271 // Explicit instantiations: M=1..32, for both input types, for the supported
272 // (E, H) pairs: (256, 3072) [MiniMax-M2/M2.5] and (128, 6144) [MiniMax-M3].
273 // ---------------------------------------------------------------------------
274
275 #define INSTANTIATE(T, M, E, H) \
276 template void invokeFp32RouterGemm<T, M, E, H>(float*, T const*, \
277 float const*, cudaStream_t);
278
279 #define INSTANTIATE_ALL(T, E, H) \
280 INSTANTIATE(T, 1, E, H) \
281 INSTANTIATE(T, 2, E, H) \
282 INSTANTIATE(T, 3, E, H) \
283 INSTANTIATE(T, 4, E, H) \
284 INSTANTIATE(T, 5, E, H) \
285 INSTANTIATE(T, 6, E, H) \
286 INSTANTIATE(T, 7, E, H) \
287 INSTANTIATE(T, 8, E, H) \
288 INSTANTIATE(T, 9, E, H) \
289 INSTANTIATE(T, 10, E, H) \
290 INSTANTIATE(T, 11, E, H) \
291 INSTANTIATE(T, 12, E, H) \
292 INSTANTIATE(T, 13, E, H) \
293 INSTANTIATE(T, 14, E, H) \
294 INSTANTIATE(T, 15, E, H) \
295 INSTANTIATE(T, 16, E, H) \
296 INSTANTIATE(T, 17, E, H) \
297 INSTANTIATE(T, 18, E, H) \
298 INSTANTIATE(T, 19, E, H) \
299 INSTANTIATE(T, 20, E, H) \
300 INSTANTIATE(T, 21, E, H) \
301 INSTANTIATE(T, 22, E, H) \
302 INSTANTIATE(T, 23, E, H) \
303 INSTANTIATE(T, 24, E, H) \
304 INSTANTIATE(T, 25, E, H) \
305 INSTANTIATE(T, 26, E, H) \
306 INSTANTIATE(T, 27, E, H) \
307 INSTANTIATE(T, 28, E, H) \
308 INSTANTIATE(T, 29, E, H) \
309 INSTANTIATE(T, 30, E, H) \
310 INSTANTIATE(T, 31, E, H) \
311 INSTANTIATE(T, 32, E, H)
312
313 INSTANTIATE_ALL(float, 256, 3072)
314 INSTANTIATE_ALL(__nv_bfloat16, 256, 3072)
315 INSTANTIATE_ALL(float, 128, 6144)
316 INSTANTIATE_ALL(__nv_bfloat16, 128, 6144)
317
318 #undef INSTANTIATE_ALL
319 #undef INSTANTIATE