@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / a17cef70ea
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 15, 11:00 UTC

Removed unused marlin cuda code (#17684)

Signed-off-by: mgoin <mgoin64@gmail.com>

Michael Goin committed May 6, 2025 at 20:59 UTC a17cef70eacba81577e1eaa91f2b5dd18624e5d5
9 files changed -3666
csrc/moe/marlin_kernels/marlin_moe_kernel.h deleted
-1616
@@ -1,1616 +0,0 @@
1 -#pragma once
2 -
3 -#include <torch/all.h>
4 -
5 -#include <ATen/cuda/CUDAContext.h>
6 -#include <c10/cuda/CUDAGuard.h>
7 -#include <cuda.h>
8 -#include <cuda_fp16.h>
9 -#include <cuda_runtime.h>
10 -
11 -#include <iostream>
12 -
13 -#include "core/scalar_type.hpp"
14 -
15 -namespace marlin_moe {
16 -
17 -constexpr int ceildiv(int a, int b) { return (a + b - 1) / b; }
18 -
19 -#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 800
20 -
21 -// Instances of `Vec` are used to organize groups of >>registers<<, as needed
22 -// for instance as inputs to tensor core operations. Consequently, all
23 -// corresponding index accesses must be compile-time constants, which is why we
24 -// extensively use `#pragma unroll` throughout the kernel code to guarantee
25 -// this.
26 -template <typename T, int n>
27 -struct Vec {
28 - T elems[n];
29 - __device__ T& operator[](int i) { return elems[i]; }
30 -};
31 -
32 -using I4 = Vec<int, 4>;
33 -
34 -// Matrix fragments for tensor core instructions; their precise layout is
35 -// documented here:
36 -// https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#matrix-fragments-for-mma-m16n8k16-with-floating-point-type
37 -using FragA = Vec<half2, 4>;
38 -using FragB = Vec<half2, 2>;
39 -using FragC = Vec<float, 4>;
40 -using FragS = Vec<half2, 1>; // quantization scales
41 -using FragZP = Vec<half2, 4>;
42 -
43 -// Predicated asynchronous global->shared copy; used for inputs A where we apply
44 -// predication to handle batchsizes that are not multiples of 16.
45 -__device__ inline void cp_async4_pred(void* smem_ptr, const void* glob_ptr,
46 - bool pred = true) {
47 - const int BYTES = 16;
48 - uint32_t smem = static_cast<uint32_t>(__cvta_generic_to_shared(smem_ptr));
49 - asm volatile(
50 - "{\n"
51 - " .reg .pred p;\n"
52 - " setp.ne.b32 p, %0, 0;\n"
53 - " @p cp.async.cg.shared.global [%1], [%2], %3;\n"
54 - "}\n" ::"r"((int)pred),
55 - "r"(smem), "l"(glob_ptr), "n"(BYTES));
56 -}
57 -
58 -// Asynchronous global->shared copy
59 -__device__ inline void cp_async4(void* smem_ptr, const void* glob_ptr) {
60 - const int BYTES = 16;
61 - uint32_t smem = static_cast<uint32_t>(__cvta_generic_to_shared(smem_ptr));
62 - asm volatile(
63 - "{\n"
64 - " cp.async.cg.shared.global [%0], [%1], %2;\n"
65 - "}\n" ::"r"(smem),
66 - "l"(glob_ptr), "n"(BYTES));
67 -}
68 -
69 -// Async copy fence.
70 -__device__ inline void cp_async_fence() {
71 - asm volatile("cp.async.commit_group;\n" ::);
72 -}
73 -
74 -// Wait until at most `n` async copy stages are still pending.
75 -template <int n>
76 -__device__ inline void cp_async_wait() {
77 - asm volatile("cp.async.wait_group %0;\n" ::"n"(n));
78 -}
79 -
80 -// m16n8k16 tensor core mma instruction with fp16 inputs and fp32
81 -// output/accumulation.
82 -__device__ inline void mma(const FragA& a_frag, const FragB& frag_b,
83 - FragC& frag_c) {
84 - const uint32_t* a = reinterpret_cast<const uint32_t*>(&a_frag);
85 - const uint32_t* b = reinterpret_cast<const uint32_t*>(&frag_b);
86 - float* c = reinterpret_cast<float*>(&frag_c);
87 - asm volatile(
88 - "mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32 "
89 - "{%0,%1,%2,%3}, {%4,%5,%6,%7}, {%8,%9}, {%10,%11,%12,%13};\n"
90 - : "=f"(c[0]), "=f"(c[1]), "=f"(c[2]), "=f"(c[3])
91 - : "r"(a[0]), "r"(a[1]), "r"(a[2]), "r"(a[3]), "r"(b[0]), "r"(b[1]),
92 - "f"(c[0]), "f"(c[1]), "f"(c[2]), "f"(c[3]));
93 -}
94 -
95 -// Instruction for loading a full 16x16 matrix fragment of operand A from shared
96 -// memory, directly in tensor core layout.
97 -__device__ inline void ldsm4(FragA& frag_a, const void* smem_ptr) {
98 - uint32_t* a = reinterpret_cast<uint32_t*>(&frag_a);
99 - uint32_t smem = static_cast<uint32_t>(__cvta_generic_to_shared(smem_ptr));
100 - asm volatile("ldmatrix.sync.aligned.m8n8.x4.shared.b16 {%0,%1,%2,%3}, [%4];\n"
101 - : "=r"(a[0]), "=r"(a[1]), "=r"(a[2]), "=r"(a[3])
102 - : "r"(smem));
103 -}
104 -
105 -// Lookup-table based 3-input logical operation; explicitly used for
106 -// dequantization as the compiler does not seem to automatically recognize it in
107 -// all cases.
108 -template <int lut>
109 -__device__ inline int lop3(int a, int b, int c) {
110 - int res;
111 - asm volatile("lop3.b32 %0, %1, %2, %3, %4;\n"
112 - : "=r"(res)
113 - : "r"(a), "r"(b), "r"(c), "n"(lut));
114 - return res;
115 -}
116 -
117 -// Constructs destination register by taking bytes from 2 sources (based on
118 -// mask)
119 -template <int start_byte, int mask>
120 -__device__ inline uint32_t prmt(uint32_t a) {
121 - uint32_t res;
122 - asm volatile("prmt.b32 %0, %1, %2, %3;\n"
123 - : "=r"(res)
124 - : "r"(a), "n"(start_byte), "n"(mask));
125 - return res;
126 -}
127 -
128 -template <vllm::ScalarTypeId w_type_id>
129 -__device__ inline FragB dequant(int q);
130 -
131 -// Efficiently dequantize 4bit values packed in an int32 value into a full
132 -// B-fragment of 4 fp16 values. We mostly follow the strategy in the link below,
133 -// with some small changes:
134 -// https://github.com/NVIDIA/FasterTransformer/blob/release/v5.3_tag/src/fastertransformer/cutlass_extensions/include/cutlass_extensions/interleaved_numeric_conversion.h#L215-L287
135 -template <>
136 -__device__ inline FragB dequant<vllm::kU4B8.id()>(int q) {
137 - const int LO = 0x000f000f;
138 - const int HI = 0x00f000f0;
139 - const int EX = 0x64006400;
140 - // Guarantee that the `(a & b) | c` operations are LOP3s.
141 - int lo = lop3<(0xf0 & 0xcc) | 0xaa>(q, LO, EX);
142 - int hi = lop3<(0xf0 & 0xcc) | 0xaa>(q, HI, EX);
143 - // We want signed int4 outputs, hence we fuse the `-8` symmetric zero point
144 - // directly into `SUB` and `ADD`.
145 - const int SUB = 0x64086408;
146 - const int MUL = 0x2c002c00;
147 - const int ADD = 0xd480d480;
148 - FragB frag_b;
149 - frag_b[0] = __hsub2(*reinterpret_cast<half2*>(&lo),
150 - *reinterpret_cast<const half2*>(&SUB));
151 - frag_b[1] = __hfma2(*reinterpret_cast<half2*>(&hi),
152 - *reinterpret_cast<const half2*>(&MUL),
153 - *reinterpret_cast<const half2*>(&ADD));
154 - return frag_b;
155 -}
156 -
157 -// Fast Int8ToFp16: Efficiently dequantize 8bit int values to fp16
158 -// Reference:
159 -// https://github.com/NVIDIA/FasterTransformer/blob/release/v5.3_tag/src/fastertransformer/cutlass_extensions/include/cutlass_extensions/interleaved_numeric_conversion.h#L53-L85
160 -template <>
161 -__device__ inline FragB dequant<vllm::kU8B128.id()>(int q) {
162 - static constexpr uint32_t mask_for_elt_01 = 0x5250;
163 - static constexpr uint32_t mask_for_elt_23 = 0x5351;
164 - static constexpr uint32_t start_byte_for_fp16 = 0x64646464;
165 -
166 - uint32_t lo = prmt<start_byte_for_fp16, mask_for_elt_01>(q);
167 - uint32_t hi = prmt<start_byte_for_fp16, mask_for_elt_23>(q);
168 -
169 - static constexpr uint32_t I8s_TO_F16s_MAGIC_NUM = 0x64806480;
170 -
171 - FragB frag_b;
172 - frag_b[0] = __hsub2(*reinterpret_cast<half2*>(&lo),
173 - *reinterpret_cast<const half2*>(&I8s_TO_F16s_MAGIC_NUM));
174 - frag_b[1] = __hsub2(*reinterpret_cast<half2*>(&hi),
175 - *reinterpret_cast<const half2*>(&I8s_TO_F16s_MAGIC_NUM));
176 - return frag_b;
177 -}
178 -
179 -template <>
180 -__device__ inline FragB dequant<vllm::kU4.id()>(int q) {
181 - const int LO = 0x000f000f;
182 - const int HI = 0x00f000f0;
183 - const int EX = 0x64006400;
184 - // Guarantee that the `(a & b) | c` operations are LOP3s.
185 - int lo = lop3<(0xf0 & 0xcc) | 0xaa>(q, LO, EX);
186 - int hi = lop3<(0xf0 & 0xcc) | 0xaa>(q, HI, EX);
187 -
188 - const int SUB = 0x64006400;
189 - const int MUL = 0x2c002c00;
190 - const int ADD = 0xd400d400;
191 - FragB frag_b;
192 - frag_b[0] = __hsub2(*reinterpret_cast<half2*>(&lo),
193 - *reinterpret_cast<const half2*>(&SUB));
194 - frag_b[1] = __hfma2(*reinterpret_cast<half2*>(&hi),
195 - *reinterpret_cast<const half2*>(&MUL),
196 - *reinterpret_cast<const half2*>(&ADD));
197 - return frag_b;
198 -}
199 -
200 -template <>
201 -__device__ inline FragB dequant<vllm::kU8.id()>(int q) {
202 - static constexpr uint32_t mask_for_elt_01 = 0x5250;
203 - static constexpr uint32_t mask_for_elt_23 = 0x5351;
204 - static constexpr uint32_t start_byte_for_fp16 = 0x64646464;
205 -
206 - uint32_t lo = prmt<start_byte_for_fp16, mask_for_elt_01>(q);
207 - uint32_t hi = prmt<start_byte_for_fp16, mask_for_elt_23>(q);
208 -
209 - static constexpr uint32_t I8s_TO_F16s_MAGIC_NUM = 0x64006400;
210 -
211 - FragB frag_b;
212 - frag_b[0] = __hsub2(*reinterpret_cast<half2*>(&lo),
213 - *reinterpret_cast<const half2*>(&I8s_TO_F16s_MAGIC_NUM));
214 - frag_b[1] = __hsub2(*reinterpret_cast<half2*>(&hi),
215 - *reinterpret_cast<const half2*>(&I8s_TO_F16s_MAGIC_NUM));
216 - return frag_b;
217 -}
218 -
219 -// Multiply dequantized values by the corresponding quantization scale; used
220 -// only for grouped quantization.
221 -__device__ inline void scale(FragB& frag_b, FragS& frag_s, int i) {
222 - half2 s = __half2half2(reinterpret_cast<__half*>(&frag_s)[i]);
223 - frag_b[0] = __hmul2(frag_b[0], s);
224 - frag_b[1] = __hmul2(frag_b[1], s);
225 -}
226 -
227 -__device__ inline void sub_zp(FragB& frag_b, half2& frag_zp, int i) {
228 - half2 zp = __half2half2(reinterpret_cast<__half*>(&frag_zp)[i]);
229 - frag_b[0] = __hsub2(frag_b[0], zp);
230 - frag_b[1] = __hsub2(frag_b[1], zp);
231 -}
232 -
233 -// Same as above, but for act_order (each K is multiplied individually)
234 -__device__ inline void scale4(FragB& frag_b, FragS& frag_s_1, FragS& frag_s_2,
235 - FragS& frag_s_3, FragS& frag_s_4, int i) {
236 - __half2 s_val_1_2;
237 - s_val_1_2.x = reinterpret_cast<__half*>(&frag_s_1)[i];
238 - s_val_1_2.y = reinterpret_cast<__half*>(&frag_s_2)[i];
239 -
240 - __half2 s_val_3_4;
241 - s_val_3_4.x = reinterpret_cast<__half*>(&frag_s_3)[i];
242 - s_val_3_4.y = reinterpret_cast<__half*>(&frag_s_4)[i];
243 -
244 - frag_b[0] = __hmul2(frag_b[0], s_val_1_2);
245 - frag_b[1] = __hmul2(frag_b[1], s_val_3_4);
246 -}
247 -
248 -// Given 2 floats multiply by 2 scales (halves)
249 -__device__ inline void scale_float(float* c, FragS& s) {
250 - __half* s_ptr = reinterpret_cast<__half*>(&s);
251 - c[0] = __fmul_rn(c[0], __half2float(s_ptr[0]));
252 - c[1] = __fmul_rn(c[1], __half2float(s_ptr[1]));
253 -}
254 -
255 -// Wait until barrier reaches `count`, then lock for current threadblock.
256 -__device__ inline void barrier_acquire(int* lock, int count) {
257 - if (threadIdx.x == 0) {
258 - int state = -1;
259 - do
260 - // Guarantee that subsequent writes by this threadblock will be visible
261 - // globally.
262 - asm volatile("ld.global.acquire.gpu.b32 %0, [%1];\n"
263 - : "=r"(state)
264 - : "l"(lock));
265 - while (state != count);
266 - }
267 - __syncthreads();
268 -}
269 -
270 -// Release barrier and increment visitation count.
271 -__device__ inline void barrier_release(int* lock, bool reset = false) {
272 - __syncthreads();
273 - if (threadIdx.x == 0) {
274 - if (reset) {
275 - lock[0] = 0;
276 - return;
277 - }
278 - int val = 1;
279 - // Make sure that all writes since acquiring this barrier are visible
280 - // globally, while releasing the barrier.
281 - asm volatile("fence.acq_rel.gpu;\n");
282 - asm volatile("red.relaxed.gpu.global.add.s32 [%0], %1;\n"
283 - :
284 - : "l"(lock), "r"(val));
285 - }
286 -}
287 -
288 -template <const vllm::ScalarTypeId w_type_id, // weight ScalarType id
289 - const int threads, // number of threads in a threadblock
290 - const int thread_m_blocks, // number of 16x16 blocks in the m
291 - // dimension (batchsize) of the
292 - // threadblock
293 - const int thread_n_blocks, // same for n dimension (output)
294 - const int thread_k_blocks, // same for k dimension (reduction)
295 - const int stages, // number of stages for the async global->shared
296 - // fetch pipeline
297 - const bool has_act_order, // whether act_order is enabled
298 - const bool has_zp, // whether zero-points are enabled
299 - const int group_blocks = -1 // number of consecutive 16x16 blocks
300 - // with a separate quantization scale
301 - >
302 -__device__ void MarlinMoESingle(
303 - const int4* __restrict__ A, // fp16 input matrix of shape mxk
304 - const int4* __restrict__ B, // 4bit quantized weight matrix of shape kxn
305 - int4* __restrict__ C, // fp16 output buffer of shape mxn
306 - const int* __restrict__ sorted_ids, // int32 sorted ids of experts
307 - const float* __restrict__ topk_weights, // float topk weights
308 - const int4* __restrict__ scales_ptr, // fp16 quantization scales of shape
309 - // (k/groupsize)xn
310 - const int4* __restrict__ zp_ptr, // 4bit packed zero-points of shape
311 - // (k/groupsize)x(n/pack_factor)
312 - const int* __restrict__ g_idx, // int32 group indices of shape k
313 - const int* __restrict__ expert_offsets,
314 - int num_groups, // number of scale groups per output channel
315 - int expert_idx, // idx of current expert
316 - int num_experts, // number of experts
317 - int topk, // topk parameter of moe
318 - int prob_m, // batch dimension m
319 - int prob_n, // output dimension n
320 - int prob_k, // reduction dimension k
321 - int tot_m, // total number of rows in A and C
322 - int* locks, // extra global storage for barrier synchronization
323 - bool replicate_input, // do we use the same input for each expert?
324 - bool apply_weights, // apply weights to output
325 - int current_m_block // current m block to start kernel computation from
326 -) {
327 - static constexpr auto w_type = vllm::ScalarType::from_id(w_type_id);
328 - constexpr int pack_factor = 32 / w_type.size_bits();
329 -
330 - // For larger GEMMs we run multiple batchsize 64 versions in parallel for a
331 - // better partitioning with less reductions
332 - int parallel = 1;
333 - if (prob_m > 16 * thread_m_blocks) {
334 - parallel = prob_m / (16 * thread_m_blocks);
335 - prob_m = 16 * thread_m_blocks;
336 - }
337 -
338 - int k_tiles = prob_k / 16 / thread_k_blocks;
339 - int n_tiles = prob_n / 16 / thread_n_blocks;
340 - int iters = ceildiv(k_tiles * n_tiles * parallel, gridDim.x);
341 -
342 - if constexpr (!has_act_order && group_blocks != -1) {
343 - if (group_blocks >= thread_k_blocks) {
344 - // Ensure that the number of tiles in each stripe is a multiple of the
345 - // groupsize; this avoids an annoying special case where a stripe starts
346 - // in the middle of group.
347 - iters = (group_blocks / thread_k_blocks) *
348 - ceildiv(iters, (group_blocks / thread_k_blocks));
349 - }
350 - }
351 -
352 - int slice_row = (iters * blockIdx.x) % k_tiles;
353 - int slice_col_par = (iters * blockIdx.x) / k_tiles;
354 - int slice_col = slice_col_par;
355 - int slice_iters; // number of threadblock tiles in the current slice
356 - int slice_count =
357 - 0; // total number of active threadblocks in the current slice
358 - int slice_idx; // index of threadblock in current slice; numbered bottom to
359 - // top
360 -
361 - // We can easily implement parallel problem execution by just remapping
362 - // indices and advancing global pointers
363 - if (slice_col_par >= n_tiles) {
364 - locks += (slice_col_par / n_tiles) * n_tiles;
365 - slice_col = slice_col_par % n_tiles;
366 - sorted_ids += (slice_col_par / n_tiles) * 16 * thread_m_blocks;
367 - }
368 -
369 - // Compute all information about the current slice which is required for
370 - // synchronization.
371 - auto init_slice = [&]() {
372 - slice_iters =
373 - iters * (blockIdx.x + 1) - (k_tiles * slice_col_par + slice_row);
374 - if (slice_iters < 0 || slice_col_par >= n_tiles * parallel) slice_iters = 0;
375 - if (slice_iters == 0) return;
376 - if (slice_row + slice_iters > k_tiles) slice_iters = k_tiles - slice_row;
377 - slice_count = 1;
378 - slice_idx = 0;
379 - int col_first = iters * ceildiv(k_tiles * slice_col_par, iters);
380 - if (col_first <= k_tiles * (slice_col_par + 1)) {
381 - int col_off = col_first - k_tiles * slice_col_par;
382 - slice_count = ceildiv(k_tiles - col_off, iters);
383 - if (col_off > 0) slice_count++;
384 - int delta_first = iters * blockIdx.x - col_first;
385 - if (delta_first < 0 || (col_off == 0 && delta_first == 0))
386 - slice_idx = slice_count - 1;
387 - else {
388 - slice_idx = slice_count - 1 - delta_first / iters;
389 - if (col_off > 0) slice_idx--;
390 - }
391 - }
392 - if (slice_col == n_tiles) {
393 - sorted_ids += 16 * thread_m_blocks;
394 - locks += n_tiles;
395 - slice_col = 0;
396 - }
397 - };
398 - init_slice();
399 -
400 - // A sizes/strides
401 -
402 - // stride of the A matrix in global memory
403 - int a_gl_stride = prob_k / 8;
404 - // stride of an A matrix tile in shared memory
405 - constexpr int a_sh_stride = 16 * thread_k_blocks / 8;
406 - // delta between subsequent A tiles in global memory
407 - constexpr int a_gl_rd_delta_o = 16 * thread_k_blocks / 8;
408 - // between subsequent accesses within a tile
409 - int a_gl_rd_delta_i = a_gl_stride * (threads / a_gl_rd_delta_o);
410 - // between shared memory writes
411 - constexpr int a_sh_wr_delta = a_sh_stride * (threads / a_gl_rd_delta_o);
412 - // between shared memory tile reads
413 - constexpr int a_sh_rd_delta_o = 2 * ((threads / 32) / (thread_n_blocks / 4));
414 - // within a shared memory tile
415 - constexpr int a_sh_rd_delta_i = a_sh_stride * 16;
416 - // overall size of a tile
417 - constexpr int a_sh_stage = a_sh_stride * (16 * thread_m_blocks);
418 - // number of shared write iterations for a tile
419 - constexpr int a_sh_wr_iters = ceildiv(a_sh_stage, a_sh_wr_delta);
420 -
421 - // B sizes/strides
422 - int b_gl_stride = 16 * prob_n / (pack_factor * 4);
423 - constexpr int b_sh_stride = ((thread_n_blocks * 16) * 16 / pack_factor) / 4;
424 - constexpr int b_thread_vecs = w_type.size_bits() == 4 ? 1 : 2;
425 - constexpr int b_sh_stride_threads = b_sh_stride / b_thread_vecs;
426 -
427 - int b_gl_rd_delta_o = b_gl_stride * thread_k_blocks;
428 - int b_gl_rd_delta_i = b_gl_stride * (threads / b_sh_stride_threads);
429 - constexpr int b_sh_wr_delta = threads * b_thread_vecs;
430 - constexpr int b_sh_rd_delta = threads * b_thread_vecs;
431 - constexpr int b_sh_stage = b_sh_stride * thread_k_blocks;
432 - constexpr int b_sh_wr_iters = b_sh_stage / b_sh_wr_delta;
433 -
434 - // Scale sizes/strides without act_order
435 - int s_gl_stride = prob_n / 8;
436 - constexpr int s_sh_stride = 16 * thread_n_blocks / 8;
437 - constexpr int s_tb_groups =
438 - !has_act_order && group_blocks != -1 && group_blocks < thread_k_blocks
439 - ? thread_k_blocks / group_blocks
440 - : 1;
441 - constexpr int s_sh_stage = s_tb_groups * s_sh_stride;
442 - int s_gl_rd_delta = s_gl_stride;
443 - // Scale size/strides with act_order
444 - constexpr int tb_k = 16 * thread_k_blocks;
445 - constexpr int g_idx_stage = has_act_order ? (tb_k * sizeof(int)) / 16 : 0;
446 - // constexpr int act_s_row_stride = 1;
447 - // int act_s_col_stride = act_s_row_stride * num_groups;
448 - int act_s_col_stride = 1;
449 - int act_s_col_warp_stride = act_s_col_stride * 8;
450 - int tb_n_warps = thread_n_blocks / 4;
451 - int act_s_col_tb_stride = act_s_col_warp_stride * tb_n_warps;
452 -
453 - // Zero-points sizes/strides
454 - int zp_gl_stride = (prob_n / pack_factor) / 4;
455 - constexpr int zp_sh_stride = ((16 * thread_n_blocks) / pack_factor) / 4;
456 - constexpr int zp_tb_groups = s_tb_groups;
457 - constexpr int zp_sh_stage = has_zp ? zp_tb_groups * zp_sh_stride : 0;
458 - int zp_gl_rd_delta = zp_gl_stride;
459 -
460 - // Global A read index of current thread.
461 - int a_gl_rd = a_gl_stride * (threadIdx.x / a_gl_rd_delta_o) +
462 - (threadIdx.x % a_gl_rd_delta_o);
463 - a_gl_rd += a_gl_rd_delta_o * slice_row;
464 - // Shared write index of current thread.
465 - int a_sh_wr = a_sh_stride * (threadIdx.x / a_gl_rd_delta_o) +
466 - (threadIdx.x % a_gl_rd_delta_o);
467 - // Shared read index.
468 - int a_sh_rd =
469 - a_sh_stride * ((threadIdx.x % 32) % 16) + (threadIdx.x % 32) / 16;
470 - a_sh_rd += 2 * ((threadIdx.x / 32) / (thread_n_blocks / 4));
471 -
472 - int b_gl_rd = b_gl_stride * (threadIdx.x / b_sh_stride_threads) +
473 - (threadIdx.x % b_sh_stride_threads) * b_thread_vecs;
474 - b_gl_rd += b_sh_stride * slice_col;
475 - b_gl_rd += b_gl_rd_delta_o * slice_row;
476 - int b_sh_wr = threadIdx.x * b_thread_vecs;
477 - int b_sh_rd = threadIdx.x * b_thread_vecs;
478 -
479 - // For act_order
480 - constexpr int k_iter_size = tb_k / b_sh_wr_iters;
481 - int slice_k_start = tb_k * slice_row;
482 - int slice_k_finish = slice_k_start + tb_k * slice_iters;
483 - int slice_k_start_shared_fetch = slice_k_start;
484 - int slice_n_offset = act_s_col_tb_stride * slice_col;
485 -
486 - // No act_order
487 - int s_gl_rd;
488 - if constexpr (!has_act_order) {
489 - if constexpr (group_blocks == -1) {
490 - s_gl_rd = s_sh_stride * slice_col + threadIdx.x;
491 - } else {
492 - s_gl_rd = s_gl_stride * ((thread_k_blocks * slice_row) / group_blocks) +
493 - s_sh_stride * slice_col + threadIdx.x;
494 - }
495 - }
496 - int s_sh_wr = threadIdx.x;
497 - bool s_sh_wr_pred = threadIdx.x < s_sh_stride;
498 -
499 - // Zero-points
500 - int zp_gl_rd;
501 - if constexpr (has_zp) {
502 - if constexpr (group_blocks == -1) {
503 - zp_gl_rd = zp_sh_stride * slice_col + threadIdx.x;
504 - } else {
505 - zp_gl_rd = zp_gl_stride * ((thread_k_blocks * slice_row) / group_blocks) +
506 - zp_sh_stride * slice_col + threadIdx.x;
507 - }
508 - }
509 - int zp_sh_wr = threadIdx.x;
510 - bool zp_sh_wr_pred = threadIdx.x < zp_sh_stride;
511 -
512 - // We use a different scale layout for grouped and column-wise quantization as
513 - // we scale a `half2` tile in column-major layout in the former and in
514 - // row-major in the latter case.
515 - int s_sh_rd;
516 - if constexpr (group_blocks != -1)
517 - s_sh_rd = 8 * ((threadIdx.x / 32) % (thread_n_blocks / 4)) +
518 - (threadIdx.x % 32) / 4;
519 - else
520 - s_sh_rd = 8 * ((threadIdx.x / 32) % (thread_n_blocks / 4)) +
521 - (threadIdx.x % 32) % 4;
522 -
523 - // Zero-points have the same read layout as the scales
524 - // (without column-wise case)
525 - constexpr int num_col_threads = 8;
526 - constexpr int num_row_threads = 4;
527 - constexpr int num_ints_per_thread = 8 / pack_factor;
528 - int zp_sh_rd;
529 - if constexpr (has_zp) {
530 - zp_sh_rd = num_ints_per_thread * num_col_threads *
531 - ((threadIdx.x / 32) % (thread_n_blocks / 4)) +
532 - num_ints_per_thread * ((threadIdx.x % 32) / num_row_threads);
533 - }
534 -
535 - int sh_first_group_id = -1;
536 - int sh_num_groups = -1;
537 - constexpr int sh_max_num_groups = 32;
538 -
539 - extern __shared__ int4 sh[];
540 - // Shared memory storage for global fetch pipelines.
541 - int4* sh_a = sh;
542 - int4* sh_b = sh_a + (stages * a_sh_stage);
543 - int4* sh_g_idx = sh_b + (stages * b_sh_stage);
544 - int4* sh_zp = sh_g_idx + (stages * g_idx_stage);
545 - int4* sh_s = sh_zp + (stages * zp_sh_stage);
546 -
547 - // Precompute which thread should not read memory in which iterations; this is
548 - // needed if there are more threads than required for a certain tilesize or
549 - // when the batchsize is not a multiple of 16.
550 - bool a_sh_wr_pred[a_sh_wr_iters];
551 - #pragma unroll
552 - for (int i = 0; i < a_sh_wr_iters; i++) {
553 - int a_idx = a_sh_wr_delta * i + a_sh_wr;
554 - int row = a_idx / a_gl_rd_delta_o;
555 - if (row >= prob_m) {
556 - a_sh_wr_pred[i] = false;
557 - } else {
558 - a_sh_wr_pred[i] = a_sh_wr_delta * i + a_sh_wr < a_sh_stride * prob_m;
559 - }
560 - }
561 -
562 - // To ensure that writing and reading A tiles to/from shared memory, the
563 - // latter in fragment format, is fully bank conflict free, we need to use a
564 - // rather fancy XOR-based layout. The key here is that neither reads nor
565 - // writes of the 16-byte `int4` blocks of 8 consecutive threads involve the
566 - // same shared memory banks. Further, it seems (based on NSight-Compute) that
567 - // each warp must also write a consecutive memory segment?
568 - auto transform_a = [&](int i) {
569 - int row = i / a_gl_rd_delta_o;
570 - return a_gl_rd_delta_o * row + (i % a_gl_rd_delta_o) ^ row;
571 - };
572 - // Since the computation of this remapping is non-trivial and, due to our main
573 - // loop unrolls, all shared memory accesses are static, we simply precompute
574 - // both transformed reads and writes.
575 - int a_sh_wr_trans[a_sh_wr_iters];
576 - #pragma unroll
577 - for (int i = 0; i < a_sh_wr_iters; i++)
578 - a_sh_wr_trans[i] = transform_a(a_sh_wr_delta * i + a_sh_wr);
579 - int a_sh_rd_trans[b_sh_wr_iters][thread_m_blocks];
580 - #pragma unroll
581 - for (int i = 0; i < b_sh_wr_iters; i++) {
582 - #pragma unroll
583 - for (int j = 0; j < thread_m_blocks; j++)
584 - a_sh_rd_trans[i][j] =
585 - transform_a(a_sh_rd_delta_o * i + a_sh_rd_delta_i * j + a_sh_rd);
586 - }
587 -
588 - // Since B-accesses have non-constant stride they have to be computed at
589 - // runtime; we break dependencies between subsequent accesses with a tile by
590 - // maintining multiple pointers (we have enough registers), a tiny
591 - // optimization.
592 - const int4* B_ptr[b_sh_wr_iters];
593 - #pragma unroll
594 - for (int i = 0; i < b_sh_wr_iters; i++)
595 - B_ptr[i] = B + b_gl_rd_delta_i * i + b_gl_rd;
596 -
597 - // Register storage for double buffer of shared memory reads.
598 - FragA frag_a[2][thread_m_blocks];
599 - I4 frag_b_quant[2][b_thread_vecs];
600 - FragC frag_c[thread_m_blocks][4][2];
601 - FragS frag_s[2][4]; // No act-order
602 - FragS act_frag_s[2][4][4]; // For act-order
603 - int frag_qzp[2][num_ints_per_thread]; // Zero-points
604 - FragZP frag_zp; // Zero-points in fp16
605 -
606 - // Zero accumulators.
607 - auto zero_accums = [&]() {
608 - #pragma unroll
609 - for (int i = 0; i < thread_m_blocks * 4 * 2 * 4; i++)
610 - reinterpret_cast<float*>(frag_c)[i] = 0;
611 - };
612 -
613 - auto fetch_scales_to_shared = [&](bool is_async, int first_group_id,
614 - int last_group_id) {
615 - sh_first_group_id = first_group_id;
616 - sh_num_groups = last_group_id - first_group_id + 1;
617 -
618 - if (sh_num_groups < sh_max_num_groups) {
619 - sh_num_groups = sh_max_num_groups;
620 - }
621 -
622 - if (sh_first_group_id + sh_num_groups > num_groups) {
623 - sh_num_groups = num_groups - sh_first_group_id;
624 - }
625 -
626 - int row_offset = first_group_id * s_gl_stride;
627 -
628 - if (is_async) {
629 - for (int i = 0; i < sh_num_groups; i++) {
630 - if (threadIdx.x < s_sh_stride) {
631 - cp_async4_pred(&sh_s[(i * s_sh_stride) + threadIdx.x],
632 - &scales_ptr[row_offset + (i * s_gl_stride) +
633 - slice_n_offset + threadIdx.x]);
634 - }
635 - }
636 - } else {
637 - for (int i = 0; i < sh_num_groups; i++) {
638 - if (threadIdx.x < s_sh_stride) {
639 - sh_s[(i * s_sh_stride) + threadIdx.x] =
640 - scales_ptr[row_offset + (i * s_gl_stride) + slice_n_offset +
641 - threadIdx.x];
642 - }
643 - }
644 - }
645 - };
646 - // Asynchronously fetch the next A, B and s tile from global to the next
647 - // shared memory pipeline location.
648 - auto fetch_to_shared = [&](int pipe, int a_off, bool pred = true) {
649 - if (pred) {
650 - int4* sh_a_stage = sh_a + a_sh_stage * pipe;
651 - #pragma unroll
652 - for (int i = 0; i < a_sh_wr_iters; i++) {
653 - int a_idx = a_gl_rd_delta_i * i + a_gl_rd + a_gl_rd_delta_o * a_off;
654 - int row = a_idx / a_gl_stride;
655 - int sorted_row =
656 - replicate_input ? sorted_ids[row] / topk : sorted_ids[row];
657 - int new_idx = sorted_row * a_gl_stride + a_idx % a_gl_stride;
658 - if (sorted_row < tot_m * (replicate_input ? 1 : topk) &&
659 - new_idx < a_gl_stride * tot_m * (replicate_input ? 1 : topk)) {
660 - cp_async4_pred(&sh_a_stage[a_sh_wr_trans[i]], &A[new_idx],
661 - a_sh_wr_pred[i]);
662 - }
663 - }
664 - int4* sh_b_stage = sh_b + b_sh_stage * pipe;
665 - #pragma unroll
666 - for (int i = 0; i < b_sh_wr_iters; i++) {
667 - #pragma unroll
668 - for (int j = 0; j < b_thread_vecs; j++) {
669 - cp_async4(&sh_b_stage[b_sh_wr_delta * i + b_sh_wr + j], B_ptr[i] + j);
670 - }
671 - B_ptr[i] += b_gl_rd_delta_o;
672 - }
673 -
674 - if constexpr (has_act_order) {
675 - // Fetch g_idx thread-block portion
676 - int full_pipe = a_off;
677 - int cur_k = slice_k_start_shared_fetch + tb_k * full_pipe;
678 - if (cur_k < prob_k && cur_k < slice_k_finish) {
679 - int4* sh_g_idx_stage = sh_g_idx + g_idx_stage * pipe;
680 -
681 - int4 const* cur_g_idx_stage_ptr =
682 - reinterpret_cast<int4 const*>(&g_idx[cur_k]);
683 -
684 - if (threadIdx.x < g_idx_stage) {
685 - cp_async4_pred(&sh_g_idx_stage[threadIdx.x],
686 - &cur_g_idx_stage_ptr[threadIdx.x]);
687 - }
688 - }
689 - } else {
690 - if constexpr (group_blocks != -1) {
691 - int4* sh_s_stage = sh_s + s_sh_stage * pipe;
692 -
693 - if constexpr (group_blocks >= thread_k_blocks) {
694 - // Only fetch scales if this tile starts a new group
695 - if (pipe % (group_blocks / thread_k_blocks) == 0) {
696 - if (s_sh_wr_pred) {
697 - cp_async4(&sh_s_stage[s_sh_wr], &scales_ptr[s_gl_rd]);
698 - }
699 - s_gl_rd += s_gl_rd_delta;
700 - }
701 - } else {
702 - for (int i = 0; i < s_tb_groups; i++) {
703 - if (s_sh_wr_pred) {
704 - cp_async4(&sh_s_stage[i * s_sh_stride + s_sh_wr],
705 - &scales_ptr[s_gl_rd]);
706 - }
707 - s_gl_rd += s_gl_rd_delta;
708 - }
709 - }
710 - }
711 -
712 - if constexpr (has_zp && group_blocks != -1) {
713 - int4* sh_zp_stage = sh_zp + zp_sh_stage * pipe;
714 -
715 - if constexpr (group_blocks >= thread_k_blocks) {
716 - // Only fetch zero-points if this tile starts a new group
717 - if (pipe % (group_blocks / thread_k_blocks) == 0) {
718 - if (zp_sh_wr_pred) {
719 - cp_async4(&sh_zp_stage[zp_sh_wr], &zp_ptr[zp_gl_rd]);
720 - }
721 - zp_gl_rd += zp_gl_rd_delta;
722 - }
723 - } else {
724 - for (int i = 0; i < zp_tb_groups; i++) {
725 - if (zp_sh_wr_pred) {
726 - cp_async4(&sh_zp_stage[i * zp_sh_stride + zp_sh_wr],
727 - &zp_ptr[zp_gl_rd]);
728 - }
729 - zp_gl_rd += zp_gl_rd_delta;
730 - }
731 - }
732 - }
733 - }
734 - }
735 - // Insert a fence even when we are winding down the pipeline to ensure that
736 - // waiting is also correct at this point.
737 - cp_async_fence();
738 - };
739 -
740 - auto fetch_zp_to_shared = [&]() {
741 - if (zp_sh_wr_pred) {
742 - cp_async4(&sh_zp[zp_sh_wr], &zp_ptr[zp_gl_rd]);
743 - }
744 - };
745 -
746 - // Wait until the next thread tile has been loaded to shared memory.
747 - auto wait_for_stage = [&]() {
748 - // We only have `stages - 2` active fetches since we are double buffering
749 - // and can only issue the next fetch when it is guaranteed that the previous
750 - // shared memory load is fully complete (as it may otherwise be
751 - // overwritten).
752 - cp_async_wait<stages - 2>();
753 - __syncthreads();
754 - };
755 -
756 - // Load the next sub-tile from the current location in the shared memory pipe
757 - // into the current register buffer.
758 - auto fetch_to_registers = [&](int k, int pipe) {
759 - int4* sh_a_stage = sh_a + a_sh_stage * pipe;
760 - #pragma unroll
761 - for (int i = 0; i < thread_m_blocks; i++)
762 - ldsm4(frag_a[k % 2][i], &sh_a_stage[a_sh_rd_trans[k % b_sh_wr_iters][i]]);
763 - int4* sh_b_stage = sh_b + b_sh_stage * pipe;
764 -
765 - #pragma unroll
766 - for (int i = 0; i < b_thread_vecs; i++) {
767 - frag_b_quant[k % 2][i] = *reinterpret_cast<I4*>(
768 - &sh_b_stage[b_sh_rd_delta * (k % b_sh_wr_iters) + b_sh_rd + i]);
769 - }
770 - };
771 -
772 - bool is_same_group[stages];
773 - int same_group_id[stages];
774 -
775 - auto init_same_group = [&](int pipe) {
776 - if constexpr (!has_act_order) {
777 - is_same_group[pipe] = false;
778 - same_group_id[pipe] = 0;
779 - return;
780 - }
781 -
782 - int4* sh_g_idx_stage = sh_g_idx + g_idx_stage * pipe;
783 - int* sh_g_idx_int_ptr = reinterpret_cast<int*>(sh_g_idx_stage);
784 -
785 - int group_id_1 = sh_g_idx_int_ptr[0];
786 - int group_id_2 = sh_g_idx_int_ptr[tb_k - 1];
787 -
788 - is_same_group[pipe] = group_id_1 == group_id_2;
789 - same_group_id[pipe] = group_id_1;
790 - };
791 -
792 - auto fetch_scales_to_registers = [&](int k, int full_pipe) {
793 - int pipe = full_pipe % stages;
794 -
795 - if constexpr (!has_act_order) {
796 - // No act-order case
797 - if constexpr (group_blocks != -1) {
798 - if constexpr (group_blocks >= thread_k_blocks) {
799 - int4* sh_s_stage =
800 - sh_s + s_sh_stage * ((group_blocks / thread_k_blocks) *
801 - (pipe / (group_blocks / thread_k_blocks)));
802 - reinterpret_cast<int4*>(&frag_s[k % 2])[0] = sh_s_stage[s_sh_rd];
803 - } else {
804 - int warp_id = threadIdx.x / 32;
805 - int n_warps = thread_n_blocks / 4;
806 -
807 - int warp_row = warp_id / n_warps;
808 -
809 - int cur_k = warp_row * 16;
810 - cur_k += k_iter_size * (k % b_sh_wr_iters);
811 -
812 - int k_blocks = cur_k / 16;
813 - int cur_group_id = k_blocks / group_blocks;
814 -
815 - int4* sh_s_stage = sh_s + s_sh_stage * pipe;
816 -
817 - reinterpret_cast<int4*>(&frag_s[k % 2])[0] =
818 - sh_s_stage[s_sh_rd + cur_group_id * s_sh_stride];
819 - }
820 - }
821 -
822 - return;
823 - }
824 -
825 - // Act-order case
826 -
827 - // Determine K of the "current" thread-block
828 - int cur_k = slice_k_start + tb_k * full_pipe;
829 - if (cur_k >= prob_k || cur_k >= slice_k_finish) {
830 - return;
831 - }
832 -
833 - // Reset (to current thread-block) since we read g_idx portion from the
834 - // shared memory
835 - cur_k = 0;
836 -
837 - // Progress to current iteration
838 - cur_k += k_iter_size * (k % b_sh_wr_iters);
839 -
840 - // Determine "position" inside the thread-block (based on warp and
841 - // thread-id)
842 - int warp_id = threadIdx.x / 32;
843 - int n_warps =
844 - thread_n_blocks / 4; // Each warp processes 4 16-size tiles over N
845 -
846 - int warp_row = warp_id / n_warps;
847 - int warp_col = warp_id % n_warps;
848 -
849 - cur_k += warp_row * 16;
850 -
851 - int th_id = threadIdx.x % 32;
852 - cur_k += (th_id % 4) * 2; // Due to tensor-core layout for fp16 B matrix
853 -
854 - int s_col_shift =
855 - /*slice_n_offset +*/ (act_s_col_warp_stride * warp_col) +
856 - (th_id / 4) * act_s_col_stride;
857 -
858 - if (is_same_group[pipe]) {
859 - if (k % 2 == 0) {
860 - *(reinterpret_cast<int4*>(&(act_frag_s[k % 2][0][0]))) =
861 - sh_s[(same_group_id[pipe] - sh_first_group_id) * s_sh_stride +
862 - s_col_shift];
863 - } else {
864 - *(reinterpret_cast<int4*>(&(act_frag_s[k % 2][0][0]))) =
865 - *(reinterpret_cast<int4*>(&(act_frag_s[(k - 1) % 2][0][0])));
866 - }
867 -
868 - for (int i = 1; i < 4; i++) {
869 - *(reinterpret_cast<int4*>(&(act_frag_s[k % 2][i][0]))) =
870 - *(reinterpret_cast<int4*>(&(act_frag_s[k % 2][0][0])));
871 - }
872 - return;
873 - }
874 -
875 - int4* sh_g_idx_stage = sh_g_idx + g_idx_stage * pipe;
876 - int* sh_g_idx_int_ptr = reinterpret_cast<int*>(sh_g_idx_stage);
877 -
878 - constexpr int k_frag_offsets[4] = {0, 1, 8,
879 - 9}; // Tensor core offsets per thread
880 -
881 - #pragma unroll
882 - for (int i = 0; i < 4; i++) {
883 - int actual_k = cur_k + k_frag_offsets[i];
884 -
885 - int group_id = sh_g_idx_int_ptr[actual_k];
886 - int rel_group_id = group_id - sh_first_group_id;
887 -
888 - *(reinterpret_cast<int4*>(&(act_frag_s[k % 2][i][0]))) =
889 - sh_s[rel_group_id * s_sh_stride + s_col_shift];
890 - }
891 - };
892 -
893 - auto fetch_zp_to_registers = [&](int k, int full_pipe) {
894 - // This code does not handle group_blocks == 0,
895 - // which signifies act_order.
896 - // has_zp implies AWQ, which doesn't have act_order,
897 - static_assert(!has_zp || group_blocks != 0);
898 -
899 - if constexpr (has_zp) {
900 - int pipe = full_pipe % stages;
901 -
902 - if constexpr (group_blocks == -1) {
903 - for (int i = 0; i < num_ints_per_thread; i++) {
904 - frag_qzp[k % 2][i] = (reinterpret_cast<int*>(sh_zp))[zp_sh_rd + i];
905 - }
906 -
907 - } else if constexpr (group_blocks >= thread_k_blocks) {
908 - int4* sh_zp_stage =
909 - sh_zp + zp_sh_stage * ((group_blocks / thread_k_blocks) *
910 - (pipe / (group_blocks / thread_k_blocks)));
911 - for (int i = 0; i < num_ints_per_thread; i++) {
912 - frag_qzp[k % 2][i] =
913 - (reinterpret_cast<int*>(sh_zp_stage))[zp_sh_rd + i];
914 - }
915 - } else {
916 - int warp_id = threadIdx.x / 32;
917 - int n_warps = thread_n_blocks / 4;
918 -
919 - int warp_row = warp_id / n_warps;
920 -
921 - int cur_k = warp_row * 16;
922 - cur_k += k_iter_size * (k % b_sh_wr_iters);
923 -
924 - int k_blocks = cur_k / 16;
925 - int cur_group_id = 0;
926 -
927 - // Suppress bogus and persistent divide-by-zero warning
928 - #pragma nv_diagnostic push
929 - #pragma nv_diag_suppress divide_by_zero
930 - cur_group_id = k_blocks / group_blocks;
931 - #pragma nv_diagnostic pop
932 -
933 - int4* sh_zp_stage = sh_zp + zp_sh_stage * pipe;
934 -
935 - sh_zp_stage += cur_group_id * zp_sh_stride;
936 -
937 - for (int i = 0; i < num_ints_per_thread; i++) {
938 - frag_qzp[k % 2][i] =
939 - (reinterpret_cast<int*>(sh_zp_stage))[zp_sh_rd + i];
940 - }
941 - }
942 - }
943 - };
944 -
945 - // Execute the actual tensor core matmul of a sub-tile.
946 - auto matmul = [&](int k) {
947 - if constexpr (has_zp) {
948 - FragB frag_zp_0;
949 - FragB frag_zp_1;
950 - int zp_quant_0, zp_quant_1;
951 -
952 - if constexpr (w_type.size_bits() == 4) {
953 - zp_quant_0 = frag_qzp[k % 2][0];
954 - zp_quant_1 = zp_quant_0 >> 8;
955 - } else {
956 - static_assert(w_type.size_bits() == 8);
957 - zp_quant_0 = frag_qzp[k % 2][0];
958 - zp_quant_1 = frag_qzp[k % 2][1];
959 - }
960 -
961 - frag_zp_0 = dequant<w_type_id>(zp_quant_0);
962 - frag_zp_1 = dequant<w_type_id>(zp_quant_1);
963 -
964 - frag_zp[0] = frag_zp_0[0];
965 - frag_zp[1] = frag_zp_0[1];
966 - frag_zp[2] = frag_zp_1[0];
967 - frag_zp[3] = frag_zp_1[1];
968 - }
969 -
970 - // We have the m dimension as the inner loop in order to encourage overlapping
971 - // dequantization and matmul operations.
972 - #pragma unroll
973 - for (int j = 0; j < 4; j++) {
974 - int b_quant_0, b_quant_1;
975 - if constexpr (w_type.size_bits() == 4) {
976 - b_quant_0 = frag_b_quant[k % 2][0][j];
977 - b_quant_1 = b_quant_0 >> 8;
978 - } else {
979 - static_assert(w_type.size_bits() == 8);
980 - int* frag_b_quant_ptr = reinterpret_cast<int*>(frag_b_quant[k % 2]);
981 - b_quant_0 = frag_b_quant_ptr[j * 2 + 0];
982 - b_quant_1 = frag_b_quant_ptr[j * 2 + 1];
983 - }
984 -
985 - FragB frag_b0 = dequant<w_type_id>(b_quant_0);
986 - FragB frag_b1 = dequant<w_type_id>(b_quant_1);
987 - // Apply zero-point to frag_b0
988 - if constexpr (has_zp) {
989 - sub_zp(frag_b0, frag_zp[j], 0);
990 - }
991 -
992 - // Apply scale to frag_b0
993 - if constexpr (has_act_order) {
994 - scale4(frag_b0, act_frag_s[k % 2][0][j], act_frag_s[k % 2][1][j],
995 - act_frag_s[k % 2][2][j], act_frag_s[k % 2][3][j], 0);
996 - } else {
997 - if constexpr (group_blocks != -1) {
998 - scale(frag_b0, frag_s[k % 2][j], 0);
999 - }
1000 - }
1001 -
1002 - // Apply zero-point to frag_b1
1003 - if constexpr (has_zp) {
1004 - sub_zp(frag_b1, frag_zp[j], 1);
1005 - }
1006 -
1007 - // Apply scale to frag_b1
1008 - if constexpr (has_act_order) {
1009 - scale4(frag_b1, act_frag_s[k % 2][0][j], act_frag_s[k % 2][1][j],
1010 - act_frag_s[k % 2][2][j], act_frag_s[k % 2][3][j], 1);
1011 -
1012 - } else {
1013 - if constexpr (group_blocks != -1) {
1014 - scale(frag_b1, frag_s[k % 2][j], 1);
1015 - }
1016 - }
1017 -
1018 - #pragma unroll
1019 - for (int i = 0; i < thread_m_blocks; i++) {
1020 - mma(frag_a[k % 2][i], frag_b0, frag_c[i][j][0]);
1021 - mma(frag_a[k % 2][i], frag_b1, frag_c[i][j][1]);
1022 - }
1023 - }
1024 - };
1025 -
1026 - // Since we slice across the k dimension of a tile in order to increase the
1027 - // number of warps while keeping the n dimension of a tile reasonable, we have
1028 - // multiple warps that accumulate their partial sums of the same output
1029 - // location; which we have to reduce over in the end. We do in shared memory.
1030 - auto thread_block_reduce = [&]() {
1031 - constexpr int red_off = threads / b_sh_stride_threads / 2;
1032 - if (red_off >= 1) {
1033 - int red_idx = threadIdx.x / b_sh_stride_threads;
1034 - constexpr int red_sh_stride = b_sh_stride_threads * 4 * 2;
1035 - constexpr int red_sh_delta = b_sh_stride_threads;
1036 - int red_sh_rd = red_sh_stride * (threadIdx.x / b_sh_stride_threads) +
1037 - (threadIdx.x % b_sh_stride_threads);
1038 -
1039 - // Parallel logarithmic shared memory reduction. We make sure to avoid any
1040 - // unnecessary read or write iterations, e.g., for two warps we write only
1041 - // once by warp 1 and read only once by warp 0.
1042 -
1043 - #pragma unroll
1044 - for (int m_block = 0; m_block < thread_m_blocks; m_block++) {
1045 - #pragma unroll
1046 - for (int i = red_off; i > 0; i /= 2) {
1047 - if (i <= red_idx && red_idx < 2 * i) {
1048 - #pragma unroll
1049 - for (int j = 0; j < 4 * 2; j++) {
1050 - int red_sh_wr =
1051 - red_sh_delta * j + (red_sh_rd - red_sh_stride * i);
1052 - if (i < red_off) {
1053 - float* c_rd =
1054 - reinterpret_cast<float*>(&sh[red_sh_delta * j + red_sh_rd]);
1055 - float* c_wr = reinterpret_cast<float*>(&sh[red_sh_wr]);
1056 - #pragma unroll
1057 - for (int k = 0; k < 4; k++)
1058 - reinterpret_cast<FragC*>(frag_c)[4 * 2 * m_block + j][k] +=
1059 - c_rd[k] + c_wr[k];
1060 - }
1061 - sh[red_sh_wr] =
1062 - reinterpret_cast<int4*>(&frag_c)[4 * 2 * m_block + j];
1063 - }
1064 - }
1065 - __syncthreads();
1066 - }
1067 - if (red_idx == 0) {
1068 - #pragma unroll
1069 - for (int i = 0; i < 4 * 2; i++) {
1070 - float* c_rd =
1071 - reinterpret_cast<float*>(&sh[red_sh_delta * i + red_sh_rd]);
1072 - #pragma unroll
1073 - for (int j = 0; j < 4; j++)
1074 - reinterpret_cast<FragC*>(frag_c)[4 * 2 * m_block + i][j] +=
1075 - c_rd[j];
1076 - }
1077 - }
1078 - __syncthreads();
1079 - }
1080 - }
1081 - };
1082 -
1083 - // Since multiple threadblocks may process parts of the same column slice, we
1084 - // finally have to globally reduce over the results. As the striped
1085 - // partitioning minimizes the number of such reductions and our outputs are
1086 - // usually rather small, we perform this reduction serially in L2 cache.
1087 - auto global_reduce = [&](bool first = false, bool last = false) {
1088 - // We are very careful here to reduce directly in the output buffer to
1089 - // maximize L2 cache utilization in this step. To do this, we write out
1090 - // results in FP16 (but still reduce with FP32 compute).
1091 - constexpr int active_threads = 32 * thread_n_blocks / 4;
1092 - if (threadIdx.x < active_threads) {
1093 - int c_gl_stride = prob_n / 8;
1094 - int c_gl_wr_delta_o = 8 * c_gl_stride;
1095 - int c_gl_wr_delta_i = 4 * (active_threads / 32);
1096 - int c_gl_wr = c_gl_stride * ((threadIdx.x % 32) / 4) +
1097 - 4 * (threadIdx.x / 32) + threadIdx.x % 4;
1098 - c_gl_wr += (2 * thread_n_blocks) * slice_col;
1099 - constexpr int c_sh_wr_delta = active_threads;
1100 - int c_sh_wr = threadIdx.x;
1101 -
1102 - int row = (threadIdx.x % 32) / 4;
1103 -
1104 - if (!first) {
1105 - // Interestingly, doing direct global accesses here really seems to mess up
1106 - // the compiler and lead to slowdowns, hence we also use async-copies even
1107 - // though these fetches are not actually asynchronous.
1108 - #pragma unroll
1109 - for (int i = 0; i < thread_m_blocks * 4; i++) {
1110 - int c_idx =
1111 - c_gl_wr + c_gl_wr_delta_o * (i / 2) + c_gl_wr_delta_i * (i % 2);
1112 - int sorted_row = sorted_ids[c_idx / c_gl_stride];
1113 - int new_idx = sorted_row * c_gl_stride + c_idx % c_gl_stride;
1114 - cp_async4_pred(&sh[c_sh_wr + c_sh_wr_delta * i], &C[new_idx],
1115 - sorted_row < tot_m * topk &&
1116 - (8 * (i / 2) + row < prob_m &&
1117 - (i < (thread_m_blocks - 1) * 4 ||
1118 - sorted_ids[8 * (i / 2) + row] < tot_m * topk)));
1119 - }
1120 - cp_async_fence();
1121 - cp_async_wait<0>();
1122 - }
1123 -
1124 - #pragma unroll
1125 - for (int i = 0; i < thread_m_blocks * 4; i++) {
1126 - if (8 * (i / 2) + row < prob_m &&
1127 - (i < (thread_m_blocks - 1) * 4 ||
1128 - sorted_ids[8 * (i / 2) + row] < tot_m * topk)) {
1129 - if (!first) {
1130 - int4 c_red = sh[c_sh_wr + i * c_sh_wr_delta];
1131 - #pragma unroll
1132 - for (int j = 0; j < 2 * 4; j++) {
1133 - reinterpret_cast<float*>(
1134 - &frag_c)[4 * 2 * 4 * (i / 4) + 4 * j + (i % 4)] +=
1135 - __half2float(reinterpret_cast<__half*>(&c_red)[j]);
1136 - }
1137 - }
1138 - if (!last) {
1139 - int4 c;
1140 - #pragma unroll
1141 - for (int j = 0; j < 2 * 4; j++) {
1142 - reinterpret_cast<__half*>(&c)[j] =
1143 - __float2half(reinterpret_cast<float*>(
1144 - &frag_c)[4 * 2 * 4 * (i / 4) + 4 * j + (i % 4)]);
1145 - }
1146 - int c_idx =
1147 - c_gl_wr + c_gl_wr_delta_o * (i / 2) + c_gl_wr_delta_i * (i % 2);
1148 - int row = sorted_ids[c_idx / c_gl_stride];
1149 - if (row < tot_m * topk) {
1150 - int new_idx = row * c_gl_stride + c_idx % c_gl_stride;
1151 - C[new_idx] = c;
1152 - }
1153 - }
1154 - }
1155 - }
1156 - }
1157 - };
1158 -
1159 - // Write out the reduce final result in the correct layout. We only actually
1160 - // reshuffle matrix fragments in this step, the reduction above is performed
1161 - // in fragment layout.
1162 - auto write_result = [&]() {
1163 - int c_gl_stride = prob_n / 8;
1164 - constexpr int c_sh_stride = 2 * thread_n_blocks + 1;
1165 - int c_gl_wr_delta = c_gl_stride * (threads / (2 * thread_n_blocks));
1166 - constexpr int c_sh_rd_delta =
1167 - c_sh_stride * (threads / (2 * thread_n_blocks));
1168 -
1169 - int c_gl_wr = c_gl_stride * (threadIdx.x / (2 * thread_n_blocks)) +
1170 - (threadIdx.x % (2 * thread_n_blocks));
1171 - c_gl_wr += (2 * thread_n_blocks) * slice_col;
1172 - int c_sh_wr =
1173 - (4 * c_sh_stride) * ((threadIdx.x % 32) / 4) + (threadIdx.x % 32) % 4;
1174 - c_sh_wr += 32 * (threadIdx.x / 32);
1175 - int c_sh_rd = c_sh_stride * (threadIdx.x / (2 * thread_n_blocks)) +
1176 - (threadIdx.x % (2 * thread_n_blocks));
1177 -
1178 - int c_gl_wr_end = c_gl_stride * prob_m;
1179 -
1180 - // We first reorder in shared memory to guarantee the most efficient final
1181 - // global write patterns
1182 - auto write = [&](int idx, float c0, float c1, FragS& s) {
1183 - half2 res = __halves2half2(__float2half(c0), __float2half(c1));
1184 -
1185 - // For per-column quantization we finally apply the scale here (only for
1186 - // 4-bit)
1187 - if constexpr (!has_act_order && group_blocks == -1 &&
1188 - w_type.size_bits() == 4) {
1189 - res = __hmul2(res, s[0]);
1190 - }
1191 -
1192 - ((half2*)sh)[idx] = res;
1193 - };
1194 - if (threadIdx.x / 32 < thread_n_blocks / 4) {
1195 - #pragma unroll
1196 - for (int i = 0; i < thread_m_blocks; i++) {
1197 - #pragma unroll
1198 - for (int j = 0; j < 4; j++) {
1199 - int wr = c_sh_wr + 8 * j;
1200 - write(wr + (4 * c_sh_stride) * 0 + 0, frag_c[i][j][0][0],
1201 - frag_c[i][j][0][1], frag_s[j / 2][2 * (j % 2) + 0]);
1202 - write(wr + (4 * c_sh_stride) * 8 + 0, frag_c[i][j][0][2],
1203 - frag_c[i][j][0][3], frag_s[j / 2][2 * (j % 2) + 0]);
1204 - write(wr + (4 * c_sh_stride) * 0 + 4, frag_c[i][j][1][0],
1205 - frag_c[i][j][1][1], frag_s[j / 2][2 * (j % 2) + 1]);
1206 - write(wr + (4 * c_sh_stride) * 8 + 4, frag_c[i][j][1][2],
1207 - frag_c[i][j][1][3], frag_s[j / 2][2 * (j % 2) + 1]);
1208 - }
1209 - c_sh_wr += 16 * (4 * c_sh_stride);
1210 - }
1211 - }
1212 - __syncthreads();
1213 -
1214 - #pragma unroll
1215 - for (int i = 0;
1216 - i < ceildiv(16 * thread_m_blocks, threads / (2 * thread_n_blocks));
1217 - i++) {
1218 - if (c_gl_wr < c_gl_wr_end) {
1219 - int row = sorted_ids[c_gl_wr / c_gl_stride];
1220 - if (row < tot_m * topk) {
1221 - int off = row * c_gl_stride + c_gl_wr % c_gl_stride;
1222 - if (!apply_weights) {
1223 - C[off] = sh[c_sh_rd];
1224 - } else {
1225 - __half* ctrg = reinterpret_cast<__half*>(&C[off]);
1226 - __half* csrc = reinterpret_cast<__half*>(&sh[c_sh_rd]);
1227 - for (int j = 0; j < 8; ++j) {
1228 - ctrg[j] = __float2half(topk_weights[row] * __half2float(csrc[j]));
1229 - }
1230 - }
1231 - c_gl_wr += c_gl_wr_delta;
1232 - c_sh_rd += c_sh_rd_delta;
1233 - }
1234 - }
1235 - }
1236 - };
1237 -
1238 - // Start global fetch and register load pipelines.
1239 - auto start_pipes = [&]() {
1240 -
1241 - #pragma unroll
1242 - for (int i = 0; i < stages - 1; i++) {
1243 - if (has_act_order && i == 0) {
1244 - int last_g_idx = slice_k_start + stages * tb_k * 2;
1245 - if (last_g_idx >= prob_k) {
1246 - last_g_idx = prob_k - 1;
1247 - }
1248 - fetch_scales_to_shared(true, g_idx[slice_k_start], g_idx[last_g_idx]);
1249 - }
1250 -
1251 - if constexpr (has_zp && group_blocks == -1) {
1252 - if (i == 0) {
1253 - fetch_zp_to_shared();
1254 - }
1255 - }
1256 - fetch_to_shared(i, i, i < slice_iters);
1257 - }
1258 -
1259 - zero_accums();
1260 - wait_for_stage();
1261 - init_same_group(0);
1262 - fetch_to_registers(0, 0);
1263 - fetch_scales_to_registers(0, 0);
1264 - fetch_zp_to_registers(0, 0);
1265 - a_gl_rd += a_gl_rd_delta_o * (stages - 1);
1266 - slice_k_start_shared_fetch += tb_k * (stages - 1);
1267 - };
1268 - if (slice_iters) {
1269 - start_pipes();
1270 - }
1271 -
1272 - // Main loop.
1273 - while (slice_iters) {
1274 - // We unroll over both the global fetch and the register load pipeline to
1275 - // ensure all shared memory accesses are static. Note that both pipelines
1276 - // have even length meaning that the next iteration will always start at
1277 - // index 0.
1278 - #pragma unroll
1279 - for (int pipe = 0; pipe < stages;) {
1280 - #pragma unroll
1281 - for (int k = 0; k < b_sh_wr_iters; k++) {
1282 - fetch_to_registers(k + 1, pipe % stages);
1283 - fetch_scales_to_registers(k + 1, pipe);
1284 - fetch_zp_to_registers(k + 1, pipe);
1285 - if (k == b_sh_wr_iters - 2) {
1286 - fetch_to_shared((pipe + stages - 1) % stages, pipe,
1287 - slice_iters >= stages);
1288 - pipe++;
1289 - wait_for_stage();
1290 - init_same_group(pipe % stages);
1291 - }
1292 - matmul(k);
1293 - }
1294 - slice_iters--;
1295 - if (slice_iters == 0) {
1296 - break;
1297 - }
1298 - }
1299 -
1300 - a_gl_rd += a_gl_rd_delta_o * stages;
1301 - slice_k_start += tb_k * stages;
1302 - slice_k_start_shared_fetch += tb_k * stages;
1303 -
1304 - if constexpr (has_act_order) {
1305 - int first_group_id = g_idx[slice_k_start];
1306 - int last_g_idx = slice_k_start + stages * tb_k * 2;
1307 - if (last_g_idx >= prob_k) {
1308 - last_g_idx = prob_k - 1;
1309 - }
1310 - int last_group_id = g_idx[last_g_idx];
1311 - if (last_group_id >= sh_first_group_id + sh_num_groups) {
1312 - fetch_scales_to_shared(false, first_group_id, last_group_id);
1313 - __syncthreads();
1314 - }
1315 - }
1316 -
1317 - // Process results and, if necessary, proceed to the next column slice.
1318 - // While this pattern may not be the most readable, other ways of writing
1319 - // the loop seemed to noticeably worse performance after compilation.
1320 - if (slice_iters == 0) {
1321 - cp_async_wait<0>();
1322 - bool last = slice_idx == slice_count - 1;
1323 - if constexpr (!has_act_order && group_blocks == -1) {
1324 - if constexpr (w_type.size_bits() == 8) {
1325 - if (s_sh_wr_pred) {
1326 - cp_async4(&sh_s[s_sh_wr], &scales_ptr[s_gl_rd]);
1327 - }
1328 - cp_async_fence();
1329 - } else {
1330 - // For 4-bit per-column scales, we only fetch them here in the
1331 - // final step before write-out
1332 - if (last) {
1333 - if (s_sh_wr_pred) {
1334 - cp_async4(&sh_s[s_sh_wr], &scales_ptr[s_gl_rd]);
1335 - }
1336 - cp_async_fence();
1337 - }
1338 - }
1339 - }
1340 -
1341 - thread_block_reduce();
1342 - if constexpr (!has_act_order && group_blocks == -1) {
1343 - if constexpr (w_type.size_bits() == 8) {
1344 - cp_async_wait<0>();
1345 - __syncthreads();
1346 - if (threadIdx.x / 32 < thread_n_blocks / 4) {
1347 - reinterpret_cast<int4*>(&frag_s)[0] = sh_s[s_sh_rd + 0];
1348 - reinterpret_cast<int4*>(&frag_s)[1] = sh_s[s_sh_rd + 4];
1349 - }
1350 -
1351 - } else {
1352 - if (last) {
1353 - cp_async_wait<0>();
1354 - __syncthreads();
1355 - if (threadIdx.x / 32 < thread_n_blocks / 4) {
1356 - reinterpret_cast<int4*>(&frag_s)[0] = sh_s[s_sh_rd + 0];
1357 - reinterpret_cast<int4*>(&frag_s)[1] = sh_s[s_sh_rd + 4];
1358 - }
1359 - }
1360 - }
1361 - }
1362 -
1363 - // For 8-bit channelwise, we apply the scale before the global reduction
1364 - // that converts the fp32 results to fp16 (so that we avoid possible
1365 - // overflow in fp16)
1366 - if constexpr (!has_act_order && group_blocks == -1 &&
1367 - w_type.size_bits() == 8) {
1368 - if (threadIdx.x / 32 < thread_n_blocks / 4) {
1369 - #pragma unroll
1370 - for (int i = 0; i < thread_m_blocks; i++) {
1371 - #pragma unroll
1372 - for (int j = 0; j < 4; j++) {
1373 - scale_float(reinterpret_cast<float*>(&frag_c[i][j][0][0]),
1374 - frag_s[j / 2][2 * (j % 2) + 0]);
1375 - scale_float(reinterpret_cast<float*>(&frag_c[i][j][0][2]),
1376 - frag_s[j / 2][2 * (j % 2) + 0]);
1377 -
1378 - scale_float(reinterpret_cast<float*>(&frag_c[i][j][1][0]),
1379 - frag_s[j / 2][2 * (j % 2) + 1]);
1380 - scale_float(reinterpret_cast<float*>(&frag_c[i][j][1][2]),
1381 - frag_s[j / 2][2 * (j % 2) + 1]);
1382 - }
1383 - }
1384 - }
1385 - }
1386 -
1387 - if (slice_count > 1) { // only globally reduce if there is more than one
1388 - // block in a slice
1389 - barrier_acquire(&locks[slice_col], slice_idx);
1390 - global_reduce(slice_idx == 0, last);
1391 - barrier_release(&locks[slice_col], last);
1392 - }
1393 - if (last) // only the last block in a slice actually writes the result
1394 - write_result();
1395 - slice_row = 0;
1396 - slice_col_par++;
1397 - slice_col++;
1398 - init_slice();
1399 - if (slice_iters) {
1400 - a_gl_rd = a_gl_stride * (threadIdx.x / a_gl_rd_delta_o) +
1401 - (threadIdx.x % a_gl_rd_delta_o);
1402 - #pragma unroll
1403 - for (int i = 0; i < b_sh_wr_iters; i++)
1404 - B_ptr[i] += b_sh_stride - b_gl_rd_delta_o * k_tiles;
1405 - if (slice_col == 0) {
1406 - #pragma unroll
1407 - for (int i = 0; i < b_sh_wr_iters; i++) B_ptr[i] -= b_gl_stride;
1408 - }
1409 -
1410 - // Update slice k/n for scales loading
1411 - if constexpr (has_act_order) {
1412 - slice_k_start = tb_k * slice_row;
1413 - slice_k_finish = slice_k_start + tb_k * slice_iters;
1414 - slice_k_start_shared_fetch = slice_k_start;
1415 - slice_n_offset = act_s_col_tb_stride * slice_col;
1416 -
1417 - } else {
1418 - s_gl_rd = s_sh_stride * slice_col + threadIdx.x;
1419 - zp_gl_rd = zp_sh_stride * slice_col + threadIdx.x;
1420 - }
1421 -
1422 - start_pipes();
1423 - }
1424 - }
1425 - }
1426 -}
1427 -
1428 -template <const vllm::ScalarTypeId w_type_id, // weight ScalarType id
1429 - const int threads, // number of threads in a threadblock
1430 - const int thread_n_blocks, // same for n dimension (output)
1431 - const int thread_k_blocks, // same for k dimension (reduction)
1432 - const int stages, // number of stages for the async global->shared
1433 - // fetch pipeline
1434 - const bool has_act_order, // whether act_order is enabled
1435 - const bool has_zp, // whether zero-points are enabled
1436 - const int group_blocks = -1 // number of consecutive 16x16 blocks
1437 - // with a separate quantization scale
1438 - >
1439 -__global__ void MarlinMoE(
1440 - const int4* __restrict__ A, // fp16 input matrix of shape mxk
1441 - const int4* __restrict__ B, // 4bit quantized weight matrix of shape kxn
1442 - int4* __restrict__ C, // fp16 output buffer of shape mxn
1443 - const int* __restrict__ sorted_ids_base, // int32 sorted ids of experts
1444 - const float* __restrict__ topk_weights, // float topk weights
1445 - const int4* __restrict__ scales_ptr, // fp16 quantization scales of shape
1446 - // (k/groupsize)xn
1447 - const int4* __restrict__ zp_ptr, // 4bit packed zero-points of shape
1448 - // (k/groupsize)x(n/pack_factor)
1449 - const int* __restrict__ g_idx, // int32 group indices of shape k
1450 - const int* __restrict__ expert_offsets,
1451 - int num_groups, // number of scale groups per output channel
1452 - int expert_idx, // idx of current expert
1453 - int num_experts, // number of experts
1454 - int topk, // topk parameter of moe
1455 - int prob_m, // batch dimension m
1456 - int prob_n, // output dimension n
1457 - int prob_k, // reduction dimension k
1458 - int tot_m, // total number of rows in A and C
1459 - int* locks, // extra global storage for barrier synchronization
1460 - bool replicate_input, // do we use the same input for each expert?
1461 - bool apply_weights, // apply weights to output
1462 - int current_m_block, // current m block to start kernel computation from
1463 - int max_par, // maximum parallelism
1464 - int cfg_max_m_blocks // upper bound on m blocks
1465 -) {
1466 - int m_block_ctr = current_m_block;
1467 -
1468 - const int* sorted_ids_expert =
1469 - sorted_ids_base + expert_offsets[expert_idx] + m_block_ctr * 4 * max_par;
1470 - int tot_its = expert_offsets[expert_idx + 1] - expert_offsets[expert_idx];
1471 - if (tot_its == 0) {
1472 - return;
1473 - }
1474 - int tot_m_blocks = ceildiv(tot_its, 16);
1475 - int pad = 16 * tot_m_blocks - tot_its;
1476 -
1477 - if (m_block_ctr >= tot_m_blocks) {
1478 - return;
1479 - }
1480 -
1481 - int max_block = tot_m_blocks - m_block_ctr;
1482 - prob_m = tot_its - 16 * m_block_ctr;
1483 -
1484 - int par = 1;
1485 - if (max_block > cfg_max_m_blocks) {
1486 - // Note that parallel > 1 currently only works for inputs without any
1487 - // padding
1488 - par = (16 * max_block - pad) / (16 * cfg_max_m_blocks);
1489 - if (par > max_par) par = max_par;
1490 - prob_m = (16 * cfg_max_m_blocks) * par;
1491 - m_block_ctr += cfg_max_m_blocks * (par - 1);
1492 - max_block = cfg_max_m_blocks;
1493 - }
1494 -
1495 - if (max_block == 1) {
1496 - MarlinMoESingle<w_type_id, threads, 1, thread_n_blocks, thread_k_blocks,
1497 - stages, has_act_order, has_zp, group_blocks>(
1498 - A, B, C, sorted_ids_expert, topk_weights, scales_ptr, zp_ptr, g_idx,
1499 - expert_offsets, num_groups, expert_idx, num_experts, topk, prob_m,
1500 - prob_n, prob_k, tot_m, locks, replicate_input, apply_weights,
1501 - current_m_block);
1502 - } else if (max_block == 2) {
1503 - MarlinMoESingle<w_type_id, threads, 2, thread_n_blocks, thread_k_blocks,
1504 - stages, has_act_order, has_zp, group_blocks>(
1505 - A, B, C, sorted_ids_expert, topk_weights, scales_ptr, zp_ptr, g_idx,
1506 - expert_offsets, num_groups, expert_idx, num_experts, topk, prob_m,
1507 - prob_n, prob_k, tot_m, locks, replicate_input, apply_weights,
1508 - current_m_block);
1509 - } else if (max_block == 3) {
1510 - MarlinMoESingle<w_type_id, threads, 3, thread_n_blocks, thread_k_blocks,
1511 - stages, has_act_order, has_zp, group_blocks>(
1512 - A, B, C, sorted_ids_expert, topk_weights, scales_ptr, zp_ptr, g_idx,
1513 - expert_offsets, num_groups, expert_idx, num_experts, topk, prob_m,
1514 - prob_n, prob_k, tot_m, locks, replicate_input, apply_weights,
1515 - current_m_block);
1516 - } else {
1517 - MarlinMoESingle<w_type_id, threads, 4, thread_n_blocks, thread_k_blocks,
1518 - stages, has_act_order, has_zp, group_blocks>(
1519 - A, B, C, sorted_ids_expert, topk_weights, scales_ptr, zp_ptr, g_idx,
1520 - expert_offsets, num_groups, expert_idx, num_experts, topk, prob_m,
1521 - prob_n, prob_k, tot_m, locks, replicate_input, apply_weights,
1522 - current_m_block);
1523 - }
1524 -}
1525 -
1526 -#else
1527 -
1528 -template <const vllm::ScalarTypeId w_type_id, // weight ScalarType id
1529 - const int threads, // number of threads in a threadblock
1530 - const int thread_n_blocks, // same for n dimension (output)
1531 - const int thread_k_blocks, // same for k dimension (reduction)
1532 - const int stages, // number of stages for the async global->shared
1533 - // fetch pipeline
1534 - const bool has_act_order, // whether act_order is enabled
1535 - const bool has_zp, // whether zero-points are enabled
1536 - const int group_blocks = -1 // number of consecutive 16x16 blocks
1537 - // with a separate quantization scale
1538 - >
1539 -__global__ void MarlinMoE(
1540 - const int4* __restrict__ A, // fp16 input matrix of shape mxk
1541 - const int4* __restrict__ B, // 4bit quantized weight matrix of shape kxn
1542 - int4* __restrict__ C, // fp16 output buffer of shape mxn
1543 - const int* __restrict__ sorted_ids, // int32 sorted ids of experts
1544 - const float* __restrict__ topk_weights, // float topk weights
1545 - const int4* __restrict__ scales_ptr, // fp16 quantization scales of shape
1546 - // (k/groupsize)xn
1547 - const int4* __restrict__ zp_ptr, // 4bit packed zero-points of shape
1548 - // (k/groupsize)x(n/pack_factor)
1549 - const int* __restrict__ g_idx, // int32 group indices of shape k
1550 - const int* __restrict__ expert_offsets,
1551 - int num_groups, // number of scale groups per output channel
1552 - int expert_idx, // idx of current expert
1553 - int num_experts, // number of experts
1554 - int topk, // topk parameter of moe
1555 - int prob_m, // batch dimension m
1556 - int prob_n, // output dimension n
1557 - int prob_k, // reduction dimension k
1558 - int tot_m, // total number of rows in A and C
1559 - int* locks, // extra global storage for barrier synchronization
1560 - bool replicate_input, // do we use the same input for each expert?
1561 - bool apply_weights, // apply weights to output
1562 - int current_m_block, // current m block to start kernel computation from
1563 - int max_par, // maximum parallelism
1564 - int cfg_max_m_blocks // upper bound on m blocks
1565 -) {
1566 - // Marlin is not implemented yet for SM < 8.0
1567 - assert(false);
1568 - return;
1569 -}
1570 -
1571 -#endif
1572 -
1573 -// 8 warps are a good choice since every SM has 4 schedulers and having more
1574 -// than 1 warp per schedule allows some more latency hiding. At the same time,
1575 -// we want relatively few warps to have many registers per warp and small tiles.
1576 -const int USER_THREADS =
1577 - 256; // Note: This is only used with user-provided thread_k/n
1578 -const int STAGES = 4; // 4 pipeline stages fit into shared memory
1579 -
1580 -static constexpr int min_thread_n = 64;
1581 -static constexpr int min_thread_k = 64;
1582 -
1583 -#define __CALL_IF_MOE(W_TYPE, THREAD_N_BLOCKS, THREAD_K_BLOCKS, HAS_ACT_ORDER, \
1584 - HAS_ZP, GROUP_BLOCKS, NUM_THREADS) \
1585 - else if (q_type == W_TYPE && thread_n_blocks == THREAD_N_BLOCKS && \
1586 - thread_k_blocks == THREAD_K_BLOCKS && \
1587 - has_act_order == HAS_ACT_ORDER && has_zp == HAS_ZP && \
1588 - group_blocks == GROUP_BLOCKS && num_threads == NUM_THREADS) { \
1589 - cudaFuncSetAttribute( \
1590 - MarlinMoE<W_TYPE.id(), NUM_THREADS, THREAD_N_BLOCKS, THREAD_K_BLOCKS, \
1591 - STAGES, HAS_ACT_ORDER, HAS_ZP, GROUP_BLOCKS>, \
1592 - cudaFuncAttributeMaxDynamicSharedMemorySize, max_shared_mem); \
1593 - MarlinMoE<W_TYPE.id(), NUM_THREADS, THREAD_N_BLOCKS, THREAD_K_BLOCKS, \
1594 - STAGES, HAS_ACT_ORDER, HAS_ZP, GROUP_BLOCKS> \
1595 - <<<blocks, NUM_THREADS, max_shared_mem, stream>>>( \
1596 - A_ptr, B_ptr, C_ptr, sorted_ids_ptr, topk_weights_ptr, s_ptr, \
1597 - zp_ptr, g_idx_ptr, expert_offsets_ptr, num_groups, expert_idx, \
1598 - num_experts, topk, prob_m, prob_n, prob_k, tot_m, locks, \
1599 - replicate_input, apply_weights, m_block, max_par, \
1600 - cfg_max_m_blocks); \
1601 - }
1602 -
1603 -#define GPTQ_CALL_IF_MOE(W_TYPE, N_BLOCKS, K_BLOCKS, NUM_THREADS) \
1604 - __CALL_IF_MOE(W_TYPE, N_BLOCKS, K_BLOCKS, true, false, 0, NUM_THREADS) \
1605 - __CALL_IF_MOE(W_TYPE, N_BLOCKS, K_BLOCKS, false, false, -1, NUM_THREADS) \
1606 - __CALL_IF_MOE(W_TYPE, N_BLOCKS, K_BLOCKS, false, false, 2, NUM_THREADS) \
1607 - __CALL_IF_MOE(W_TYPE, N_BLOCKS, K_BLOCKS, false, false, 4, NUM_THREADS) \
1608 - __CALL_IF_MOE(W_TYPE, N_BLOCKS, K_BLOCKS, false, false, 8, NUM_THREADS)
1609 -
1610 -#define AWQ_CALL_IF_MOE(W_TYPE, N_BLOCKS, K_BLOCKS, NUM_THREADS) \
1611 - __CALL_IF_MOE(W_TYPE, N_BLOCKS, K_BLOCKS, false, true, -1, NUM_THREADS) \
1612 - __CALL_IF_MOE(W_TYPE, N_BLOCKS, K_BLOCKS, false, true, 2, NUM_THREADS) \
1613 - __CALL_IF_MOE(W_TYPE, N_BLOCKS, K_BLOCKS, false, true, 4, NUM_THREADS) \
1614 - __CALL_IF_MOE(W_TYPE, N_BLOCKS, K_BLOCKS, false, true, 8, NUM_THREADS)
1615 -
1616 -} // namespace marlin_moe
csrc/moe/marlin_kernels/marlin_moe_kernel_ku4.cu deleted
-31
@@ -1,31 +0,0 @@
1 -#include "marlin_moe_kernel_ku4.h"
2 -
3 -namespace marlin_moe {
4 -
5 -// We return bool so we can create these different kernel calls as a sequence
6 -// of if-elseif's.
7 -bool call_marlin_moe_kernel_ku4(
8 - vllm::ScalarType const& q_type, int thread_n_blocks, int thread_k_blocks,
9 - bool has_act_order, int group_blocks, int num_threads, int blocks,
10 - int max_shared_mem, cudaStream_t stream, const int4* A_ptr,
11 - const int4* B_ptr, int4* C_ptr, const int* sorted_ids_ptr,
12 - const float* topk_weights_ptr, const int4* s_ptr, const int4* zp_ptr,
13 - const int* g_idx_ptr, int* expert_offsets_ptr, int num_groups,
14 - int expert_idx, int num_experts, int topk, int prob_m, int prob_n,
15 - int prob_k, int tot_m, int* locks, bool replicate_input, bool apply_weights,
16 - int m_block, int max_par, int cfg_max_m_blocks) {
17 - bool has_zp = true;
18 -
19 - if (false) {
20 - }
21 - AWQ_CALL_IF_MOE(vllm::kU4, 16, 4, 256)
22 - AWQ_CALL_IF_MOE(vllm::kU4, 8, 8, 256)
23 - AWQ_CALL_IF_MOE(vllm::kU4, 8, 4, 128)
24 - AWQ_CALL_IF_MOE(vllm::kU4, 4, 8, 128)
25 - else {
26 - return false;
27 - }
28 - return true;
29 -}
30 -
31 -} // namespace marlin_moe
csrc/moe/marlin_kernels/marlin_moe_kernel_ku4.h deleted
-20
@@ -1,20 +0,0 @@
1 -#pragma once
2 -
3 -#include "marlin_moe_kernel.h"
4 -
5 -namespace marlin_moe {
6 -
7 -// We return bool so we can create these different kernel calls as a sequence
8 -// of if-elseif's.
9 -bool call_marlin_moe_kernel_ku4(
10 - vllm::ScalarType const& q_type, int thread_n_blocks, int thread_k_blocks,
11 - bool has_act_order, int group_blocks, int num_threads, int blocks,
12 - int max_shared_mem, cudaStream_t stream, const int4* A_ptr,
13 - const int4* B_ptr, int4* C_ptr, const int* sorted_ids_ptr,
14 - const float* topk_weights_ptr, const int4* s_ptr, const int4* zp_ptr,
15 - const int* g_idx_ptr, int* expert_offsets_ptr, int num_groups,
16 - int expert_idx, int num_experts, int topk, int prob_m, int prob_n,
17 - int prob_k, int tot_m, int* locks, bool replicate_input, bool apply_weights,
18 - int m_block, int max_par, int cfg_max_m_blocks);
19 -
20 -} // namespace marlin_moe
csrc/moe/marlin_kernels/marlin_moe_kernel_ku4b8.cu deleted
-31
@@ -1,31 +0,0 @@
1 -#include "marlin_moe_kernel_ku4b8.h"
2 -
3 -namespace marlin_moe {
4 -
5 -// We return bool so we can create these different kernel calls as a sequence
6 -// of if-elseif's.
7 -bool call_marlin_moe_kernel_ku4b8(
8 - vllm::ScalarType const& q_type, int thread_n_blocks, int thread_k_blocks,
9 - bool has_act_order, int group_blocks, int num_threads, int blocks,
10 - int max_shared_mem, cudaStream_t stream, const int4* A_ptr,
11 - const int4* B_ptr, int4* C_ptr, const int* sorted_ids_ptr,
12 - const float* topk_weights_ptr, const int4* s_ptr, const int4* zp_ptr,
13 - const int* g_idx_ptr, int* expert_offsets_ptr, int num_groups,
14 - int expert_idx, int num_experts, int topk, int prob_m, int prob_n,
15 - int prob_k, int tot_m, int* locks, bool replicate_input, bool apply_weights,
16 - int m_block, int max_par, int cfg_max_m_blocks) {
17 - bool has_zp = false;
18 -
19 - if (false) {
20 - }
21 - GPTQ_CALL_IF_MOE(vllm::kU4B8, 16, 4, 256)
22 - GPTQ_CALL_IF_MOE(vllm::kU4B8, 8, 8, 256)
23 - GPTQ_CALL_IF_MOE(vllm::kU4B8, 8, 4, 128)
24 - GPTQ_CALL_IF_MOE(vllm::kU4B8, 4, 8, 128)
25 - else {
26 - return false;
27 - }
28 - return true;
29 -}
30 -
31 -} // namespace marlin_moe
csrc/moe/marlin_kernels/marlin_moe_kernel_ku4b8.h deleted
-20
@@ -1,20 +0,0 @@
1 -#pragma once
2 -
3 -#include "marlin_moe_kernel.h"
4 -
5 -namespace marlin_moe {
6 -
7 -// We return bool so we can create these different kernel calls as a sequence
8 -// of if-elseif's.
9 -bool call_marlin_moe_kernel_ku4b8(
10 - vllm::ScalarType const& q_type, int thread_n_blocks, int thread_k_blocks,
11 - bool has_act_order, int group_blocks, int num_threads, int blocks,
12 - int max_shared_mem, cudaStream_t stream, const int4* A_ptr,
13 - const int4* B_ptr, int4* C_ptr, const int* sorted_ids_ptr,
14 - const float* topk_weights_ptr, const int4* s_ptr, const int4* zp_ptr,
15 - const int* g_idx_ptr, int* expert_offsets_ptr, int num_groups,
16 - int expert_idx, int num_experts, int topk, int prob_m, int prob_n,
17 - int prob_k, int tot_m, int* locks, bool replicate_input, bool apply_weights,
18 - int m_block, int max_par, int cfg_max_m_blocks);
19 -
20 -} // namespace marlin_moe
csrc/moe/marlin_kernels/marlin_moe_kernel_ku8b128.cu deleted
-31
@@ -1,31 +0,0 @@
1 -#include "marlin_moe_kernel_ku8b128.h"
2 -
3 -namespace marlin_moe {
4 -
5 -// We return bool so we can create these different kernel calls as a sequence
6 -// of if-elseif's.
7 -bool call_marlin_moe_kernel_ku8b128(
8 - vllm::ScalarType const& q_type, int thread_n_blocks, int thread_k_blocks,
9 - bool has_act_order, int group_blocks, int num_threads, int blocks,
10 - int max_shared_mem, cudaStream_t stream, const int4* A_ptr,
11 - const int4* B_ptr, int4* C_ptr, const int* sorted_ids_ptr,
12 - const float* topk_weights_ptr, const int4* s_ptr, const int4* zp_ptr,
13 - const int* g_idx_ptr, int* expert_offsets_ptr, int num_groups,
14 - int expert_idx, int num_experts, int topk, int prob_m, int prob_n,
15 - int prob_k, int tot_m, int* locks, bool replicate_input, bool apply_weights,
16 - int m_block, int max_par, int cfg_max_m_blocks) {
17 - bool has_zp = false;
18 -
19 - if (false) {
20 - }
21 - GPTQ_CALL_IF_MOE(vllm::kU8B128, 16, 4, 256)
22 - GPTQ_CALL_IF_MOE(vllm::kU8B128, 8, 8, 256)
23 - GPTQ_CALL_IF_MOE(vllm::kU8B128, 8, 4, 128)
24 - GPTQ_CALL_IF_MOE(vllm::kU8B128, 4, 8, 128)
25 - else {
26 - return false;
27 - }
28 - return true;
29 -}
30 -
31 -} // namespace marlin_moe
csrc/moe/marlin_kernels/marlin_moe_kernel_ku8b128.h deleted
-18
@@ -1,18 +0,0 @@
1 -#pragma once
2 -
3 -#include "marlin_moe_kernel.h"
4 -
5 -namespace marlin_moe {
6 -
7 -bool call_marlin_moe_kernel_ku8b128(
8 - vllm::ScalarType const& q_type, int thread_n_blocks, int thread_k_blocks,
9 - bool has_act_order, int group_blocks, int num_threads, int blocks,
10 - int max_shared_mem, cudaStream_t stream, const int4* A_ptr,
11 - const int4* B_ptr, int4* C_ptr, const int* sorted_ids_ptr,
12 - const float* topk_weights_ptr, const int4* s_ptr, const int4* zp_ptr,
13 - const int* g_idx_ptr, int* expert_offsets_ptr, int num_groups,
14 - int expert_idx, int num_experts, int topk, int prob_m, int prob_n,
15 - int prob_k, int tot_m, int* locks, bool replicate_input, bool apply_weights,
16 - int m_block, int max_par, int cfg_max_m_blocks);
17 -
18 -}
csrc/moe/marlin_moe_ops.cu deleted
-588
@@ -1,588 +0,0 @@
1 -/*
2 - * Modified by Neural Magic
3 - * Copyright (C) Marlin.2024 Elias Frantar
4 - *
5 - * Licensed under the Apache License, Version 2.0 (the "License");
6 - * you may not use this file except in compliance with the License.
7 - * You may obtain a copy of the License at
8 - *
9 - * http://www.apache.org/licenses/LICENSE-2.0
10 - *
11 - * Unless required by applicable law or agreed to in writing, software
12 - * distributed under the License is distributed on an "AS IS" BASIS,
13 - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 - * See the License for the specific language governing permissions and
15 - * limitations under the License.
16 - */
17 -
18 -#include <torch/all.h>
19 -
20 -#include <ATen/cuda/CUDAContext.h>
21 -#include <c10/cuda/CUDAGuard.h>
22 -#include <cuda.h>
23 -#include <cuda_fp16.h>
24 -#include <cuda_runtime.h>
25 -
26 -#include <iostream>
27 -
28 -#include "core/exception.hpp"
29 -#include "core/scalar_type.hpp"
30 -#include "core/registration.h"
31 -#include "marlin_kernels/marlin_moe_kernel_ku4b8.h"
32 -#include "marlin_kernels/marlin_moe_kernel_ku8b128.h"
33 -#include "marlin_kernels/marlin_moe_kernel_ku4.h"
34 -
35 -template <typename T>
36 -inline std::string str(T x) {
37 - return std::to_string(x);
38 -}
39 -
40 -namespace marlin_moe {
41 -
42 -#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 800
43 -
44 -// For a given "a" of size [M,K] performs a permutation of the K columns based
45 -// on the given "perm" indices.
46 -__global__ void permute_cols_kernel(int4 const* __restrict__ a_int4_ptr,
47 - int const* __restrict__ perm_int_ptr,
48 - int4* __restrict__ out_int4_ptr, int size_m,
49 - int size_k, int block_rows) {
50 - int start_row = block_rows * blockIdx.x;
51 - int finish_row = start_row + block_rows;
52 - if (finish_row > size_m) {
53 - finish_row = size_m;
54 - }
55 - int cur_block_rows = finish_row - start_row;
56 -
57 - int row_stride = size_k * sizeof(half) / 16;
58 -
59 - auto permute_row = [&](int row) {
60 - int iters = size_k / blockDim.x;
61 - int rest = size_k % blockDim.x;
62 -
63 - int offset = row * row_stride;
64 -
65 - half const* a_row_half = reinterpret_cast<half const*>(a_int4_ptr + offset);
66 - half* out_half = reinterpret_cast<half*>(out_int4_ptr + offset);
67 -
68 - int base_k = 0;
69 -
70 - for (int i = 0; i < iters; i++) {
71 - int cur_k = base_k + threadIdx.x;
72 - int src_pos = perm_int_ptr[cur_k];
73 -
74 - out_half[cur_k] = a_row_half[src_pos];
75 -
76 - base_k += blockDim.x;
77 - }
78 -
79 - if (rest) {
80 - if (threadIdx.x < rest) {
81 - int cur_k = base_k + threadIdx.x;
82 - int src_pos = perm_int_ptr[cur_k];
83 -
84 - out_half[cur_k] = a_row_half[src_pos];
85 - }
86 - }
87 - };
88 -
89 - for (int i = 0; i < cur_block_rows; i++) {
90 - int cur_row = start_row + i;
91 - if (cur_row < size_m) {
92 - permute_row(cur_row);
93 - }
94 - }
95 -}
96 -
97 -__global__ void compute_expert_offsets(int const* __restrict__ topk_ids,
98 - int* __restrict__ expert_offsets,
99 - int topk_length, int block_size) {
100 - int expert_id = threadIdx.x;
101 - int num_experts = blockDim.x;
102 -
103 - int occurrences = 0;
104 - for (int i = 0; i < topk_length; ++i) {
105 - occurrences += (topk_ids[i] == expert_id);
106 - }
107 - expert_offsets[expert_id + 1] = occurrences;
108 - __syncthreads();
109 -
110 - if (threadIdx.x == 0) {
111 - int tot_offset = 0;
112 - expert_offsets[0] = 0;
113 - for (int i = 0; i < num_experts; ++i) {
114 - tot_offset += ceildiv(expert_offsets[i + 1], block_size) * block_size;
115 - expert_offsets[i + 1] = tot_offset;
116 - }
117 - }
118 - __syncthreads();
119 -}
120 -
121 -#else
122 -
123 -__global__ void permute_cols_kernel(int4 const* __restrict__ a_int4_ptr,
124 - int const* __restrict__ perm_int_ptr,
125 - int4* __restrict__ out_int4_ptr, int size_m,
126 - int size_k, int block_rows) {
127 - // Marlin is not implemented yet for SM < 8.0
128 - assert(false);
129 - return;
130 -}
131 -
132 -__global__ void compute_expert_offsets(int const* __restrict__ topk_ids,
133 - int* __restrict__ expert_offsets,
134 - int topk_length, int block_size) {
135 - // Marlin is not implemented yet for SM < 8.0
136 - assert(false);
137 - return;
138 -}
139 -
140 -#endif
141 -
142 -typedef struct {
143 - int thread_k;
144 - int thread_n;
145 - int num_threads;
146 -} thread_config_t;
147 -
148 -typedef struct {
149 - int max_m_blocks;
150 - thread_config_t tb_cfg;
151 -} exec_config_t;
152 -
153 -thread_config_t small_batch_thread_configs[] = {
154 - // Ordered by priority
155 -
156 - // thread_k, thread_n, num_threads
157 - {128, 128, 256}, // Default
158 - {128, 64, 128}, // Reduce N 2X, same K
159 - {64, 256, 256}, // Reduce K 2X, increase N 2X
160 - {64, 128, 128}, // Reduce K 2X, same N
161 - {64, 64, 128}, // Reduce both 2X
162 -};
163 -
164 -thread_config_t large_batch_thread_configs[] = {
165 - // Ordered by priority
166 -
167 - // thread_k, thread_n, num_threads
168 - {64, 256, 256}, // Default
169 - {128, 128, 256}, // Reduce N 2X, increase K 2X
170 - {64, 128, 128}, // Reduce N 2X, same K
171 - {128, 64, 128}, // Reduce N 4X, increase K 2X
172 - {64, 64, 128}, // Reduce N 4X, same K
173 -};
174 -
175 -int get_scales_cache_size(thread_config_t const& th_config, int prob_m,
176 - int prob_n, int prob_k, int num_bits, int group_size,
177 - bool has_act_order, bool is_k_full) {
178 - bool cache_scales_chunk = has_act_order && !is_k_full;
179 -
180 - int tb_n = th_config.thread_n;
181 - int tb_k = th_config.thread_k;
182 -
183 - // Get max scale groups per thread-block
184 - int tb_groups;
185 - if (group_size == -1) {
186 - tb_groups = 1;
187 - } else if (group_size == 0) {
188 - tb_groups = ceildiv(tb_k, 32); // Worst case is 32 group size
189 - } else {
190 - tb_groups = ceildiv(tb_k, group_size);
191 - }
192 -
193 - if (cache_scales_chunk) {
194 - int load_groups =
195 - tb_groups * STAGES * 2; // Chunk size is 2x pipeline over dim K
196 - load_groups = max(load_groups, 32); // We load at least 32 scale groups
197 - return load_groups * tb_n * 4;
198 -
199 - } else {
200 - int tb_scales = tb_groups * tb_n * 2;
201 -
202 - return tb_scales * STAGES;
203 - }
204 -}
205 -
206 -bool is_valid_cache_size(thread_config_t const& th_config, int max_m_blocks,
207 - int prob_m, int prob_n, int prob_k, int num_bits,
208 - int scales_cache_size, int max_shared_mem) {
209 - int pack_factor = 32 / num_bits;
210 -
211 - // Get B size
212 - int tb_k = th_config.thread_k;
213 - int tb_n = th_config.thread_n;
214 -
215 - int b_size = (tb_k * tb_n / pack_factor) * 4;
216 -
217 - // Get A size
218 - int m_blocks = ceildiv(prob_m, 16);
219 - int tb_max_m = 16;
220 -
221 - while (true) {
222 - if (m_blocks >= max_m_blocks) {
223 - tb_max_m *= max_m_blocks;
224 - break;
225 - }
226 -
227 - max_m_blocks--;
228 - if (max_m_blocks == 0) {
229 - TORCH_CHECK(false, "Unexpected m_blocks = ", m_blocks);
230 - }
231 - }
232 -
233 - int a_size = (tb_max_m * tb_k) * 2;
234 -
235 - float pipe_size = (a_size + b_size) * STAGES;
236 -
237 - TORCH_CHECK(max_shared_mem / 2 > scales_cache_size); // Sanity
238 -
239 - return pipe_size < 0.95f * (max_shared_mem - scales_cache_size);
240 -}
241 -
242 -bool is_valid_config(thread_config_t const& th_config, int max_m_blocks,
243 - int prob_m, int prob_n, int prob_k, int num_bits,
244 - int group_size, bool has_act_order, bool is_k_full,
245 - int max_shared_mem) {
246 - // Sanity
247 - if (th_config.thread_k == -1 || th_config.thread_n == -1 ||
248 - th_config.num_threads == -1) {
249 - return false;
250 - }
251 -
252 - // Verify K/N are divisible by thread K/N
253 - if (prob_k % th_config.thread_k != 0 || prob_n % th_config.thread_n != 0) {
254 - return false;
255 - }
256 -
257 - // thread_k can be only 128 or 64 (because it must be less than groupsize
258 - // which is 128)
259 - if (th_config.thread_k != 128 && th_config.thread_k != 64) {
260 - return false;
261 - }
262 -
263 - // Verify min for thread K/N
264 - if (th_config.thread_n < min_thread_n || th_config.thread_k < min_thread_k) {
265 - return false;
266 - }
267 -
268 - // num_threads must be at least 128 (= 4 warps)
269 - if (th_config.num_threads < 128) {
270 - return false;
271 - }
272 -
273 - // Determine cache for scales
274 - int scales_cache_size =
275 - get_scales_cache_size(th_config, prob_m, prob_n, prob_k, num_bits,
276 - group_size, has_act_order, is_k_full);
277 -
278 - // Check that pipeline fits into cache
279 - if (!is_valid_cache_size(th_config, max_m_blocks, prob_m, prob_n, prob_k,
280 - num_bits, scales_cache_size, max_shared_mem)) {
281 - return false;
282 - }
283 -
284 - return true;
285 -}
286 -
287 -exec_config_t determine_thread_config(int prob_m, int prob_n, int prob_k,
288 - int num_bits, int group_size,
289 - bool has_act_order, bool is_k_full,
290 - int max_shared_mem) {
291 - int max_m_blocks = 4;
292 - while (max_m_blocks > 0) {
293 - if (prob_m <= 16) {
294 - for (auto th_config : small_batch_thread_configs) {
295 - if (is_valid_config(th_config, max_m_blocks, prob_m, prob_n, prob_k,
296 - num_bits, group_size, has_act_order, is_k_full,
297 - max_shared_mem)) {
298 - return exec_config_t{max_m_blocks, th_config};
299 - }
300 - }
301 - } else {
302 - for (auto th_config : large_batch_thread_configs) {
303 - if (is_valid_config(th_config, max_m_blocks, prob_m, prob_n, prob_k,
304 - num_bits, group_size, has_act_order, is_k_full,
305 - max_shared_mem)) {
306 - return exec_config_t{max_m_blocks, th_config};
307 - }
308 - }
309 - }
310 -
311 - max_m_blocks--; // Process less M blocks per invocation to reduce cache
312 - // usage
313 - }
314 -
315 - return exec_config_t{0, {-1, -1, -1}};
316 -}
317 -
318 -#define CALL_MOE_KERNEL_FUNCTION(KERNEL_FUNCTION) \
319 - else if (KERNEL_FUNCTION( \
320 - q_type, thread_n_blocks, thread_k_blocks, has_act_order, \
321 - group_blocks, num_threads, blocks, max_shared_mem, stream, \
322 - A_ptr, B_ptr, C_ptr, sorted_ids_ptr, topk_weights_ptr, s_ptr, \
323 - zp_ptr, g_idx_ptr, expert_offsets_ptr, num_groups, expert_idx, \
324 - num_experts, topk, prob_m, prob_n, prob_k, tot_m, locks, \
325 - replicate_input, apply_weights, m_block, max_par, \
326 - exec_cfg.max_m_blocks)) { \
327 - }
328 -
329 -void marlin_mm_moe(const void* A, const void* B, void* C,
330 - const void* sorted_ids, const void* topk_weights,
331 - const void* topk_ids, const void* s, void* zp,
332 - const void* g_idx, const void* perm, void* a_tmp,
333 - void* expert_offsets, int prob_m, int prob_n, int prob_k,
334 - void* workspace, vllm::ScalarType const& q_type,
335 - bool has_act_order, bool is_k_full, bool has_zp,
336 - int num_groups, int group_size, int num_experts, int topk,
337 - int moe_block_size, int dev, cudaStream_t stream,
338 - int thread_k, int thread_n, int sms, int max_par,
339 - bool replicate_input, bool apply_weights) {
340 - TORCH_CHECK(prob_m > 0 && prob_n > 0 && prob_k > 0, "Invalid MNK = [", prob_m,
341 - ", ", prob_n, ", ", prob_k, "]");
342 -
343 - if (sms == -1) {
344 - cudaDeviceGetAttribute(&sms, cudaDevAttrMultiProcessorCount, dev);
345 - }
346 -
347 - int max_shared_mem = 0;
348 - cudaDeviceGetAttribute(&max_shared_mem,
349 - cudaDevAttrMaxSharedMemoryPerBlockOptin, dev);
350 - TORCH_CHECK(max_shared_mem > 0);
351 -
352 - int num_bits = q_type.size_bits();
353 -
354 - // Set thread config
355 - exec_config_t exec_cfg;
356 - if (thread_k != -1 && thread_n != -1) {
357 - // User-defined config
358 - exec_cfg =
359 - exec_config_t{4, thread_config_t{thread_k, thread_n, USER_THREADS}};
360 - } else {
361 - // Auto config
362 - exec_cfg =
363 - determine_thread_config(prob_m, prob_n, prob_k, num_bits, group_size,
364 - has_act_order, is_k_full, max_shared_mem);
365 - }
366 -
367 - TORCH_CHECK(exec_cfg.max_m_blocks > 0 &&
368 - is_valid_config(exec_cfg.tb_cfg, exec_cfg.max_m_blocks,
369 - prob_m, prob_n, prob_k, num_bits, group_size,
370 - has_act_order, is_k_full, max_shared_mem),
371 - "Invalid thread config: max_m_blocks = ", exec_cfg.max_m_blocks,
372 - ", thread_k = ", exec_cfg.tb_cfg.thread_k,
373 - ", thread_n = ", exec_cfg.tb_cfg.thread_n,
374 - ", num_threads = ", exec_cfg.tb_cfg.num_threads, " for MKN = [",
375 - prob_m, ", ", prob_k, ", ", prob_n, "] and num_bits = ", num_bits,
376 - ", group_size = ", group_size,
377 - ", has_act_order = ", has_act_order, ", is_k_full = ", is_k_full,
378 - ", max_shared_mem = ", max_shared_mem);
379 -
380 - int num_threads = exec_cfg.tb_cfg.num_threads;
381 - thread_k = exec_cfg.tb_cfg.thread_k;
382 - thread_n = exec_cfg.tb_cfg.thread_n;
383 -
384 - int thread_k_blocks = thread_k / 16;
385 - int thread_n_blocks = thread_n / 16;
386 -
387 - int blocks = sms;
388 -
389 - TORCH_CHECK(prob_n % thread_n == 0, "prob_n = ", prob_n,
390 - " is not divisible by thread_n = ", thread_n);
391 - TORCH_CHECK(prob_k % thread_k == 0, "prob_k = ", prob_k,
392 - " is not divisible by thread_k = ", thread_k);
393 -
394 - int group_blocks = 0;
395 - if (has_act_order) {
396 - if (is_k_full) {
397 - TORCH_CHECK(group_size != -1);
398 - group_blocks = group_size / 16;
399 - TORCH_CHECK(prob_k % group_blocks == 0, "prob_k = ", prob_k,
400 - " is not divisible by group_blocks = ", group_blocks);
401 - } else {
402 - TORCH_CHECK(group_size == 0);
403 - group_blocks = 0;
404 - }
405 -
406 - } else {
407 - if (group_size == -1) {
408 - group_blocks = -1;
409 - } else {
410 - group_blocks = group_size / 16;
411 - TORCH_CHECK(prob_k % group_blocks == 0, "prob_k = ", prob_k,
412 - " is not divisible by group_blocks = ", group_blocks);
413 - }
414 - }
415 -
416 - int tot_m = prob_m;
417 -
418 - const int* topk_ids_ptr = (const int*)topk_ids;
419 - int* expert_offsets_ptr = (int*)expert_offsets;
420 - compute_expert_offsets<<<1, num_experts, 0, stream>>>(
421 - topk_ids_ptr, expert_offsets_ptr, tot_m * topk, moe_block_size);
422 -
423 - bool do_permute_a = has_act_order;
424 -
425 - // If we have a full K, then we can run the non-act-order version of Marlin
426 - // (since the weight rows are reordered by increasing group ids, and by
427 - // having a full K, we have full original groups)
428 - if (is_k_full) {
429 - has_act_order = false;
430 - }
431 -
432 - int pack_factor = 32 / q_type.size_bits();
433 -
434 - for (int expert_idx = 0; expert_idx < num_experts; ++expert_idx) {
435 - const int4* A_ptr = (const int4*)A;
436 - int4* a_tmp_ptr = (int4*)a_tmp;
437 - const int4* B_ptr =
438 - (const int4*)B + (prob_n * prob_k / (pack_factor * 4)) * expert_idx;
439 - int4* C_ptr = (int4*)C;
440 - const float* topk_weights_ptr = (const float*)topk_weights;
441 - const int* sorted_ids_ptr = (const int*)sorted_ids;
442 - const int4* s_ptr = (const int4*)s + num_groups * prob_n / 8 * expert_idx;
443 - const int4* zp_ptr =
444 - (const int4*)zp + num_groups * prob_n / (pack_factor * 4) * expert_idx;
445 - const int* g_idx_ptr = (const int*)g_idx + prob_k * expert_idx;
446 - const int* perm_ptr = (const int*)perm + prob_k * expert_idx;
447 - int* locks = (int*)workspace;
448 -
449 - if (do_permute_a) {
450 - // Permute A columns
451 - int topk_rows = replicate_input ? tot_m : tot_m * topk;
452 - int block_rows = ceildiv(topk_rows, blocks);
453 - permute_cols_kernel<<<blocks, num_threads, 0, stream>>>(
454 - A_ptr, perm_ptr, a_tmp_ptr, topk_rows, prob_k, block_rows);
455 - A_ptr = a_tmp_ptr;
456 - }
457 -
458 - int tot_m_blocks = ceildiv(tot_m, 16);
459 - for (int m_block = 0; m_block < tot_m_blocks;
460 - m_block += 4 * exec_cfg.max_m_blocks) {
461 - if (false) {
462 - }
463 - CALL_MOE_KERNEL_FUNCTION(call_marlin_moe_kernel_ku4b8)
464 - CALL_MOE_KERNEL_FUNCTION(call_marlin_moe_kernel_ku8b128)
465 - CALL_MOE_KERNEL_FUNCTION(call_marlin_moe_kernel_ku4)
466 - else {
467 - TORCH_CHECK(false, "Unsupported shapes: MNK = [" + str(prob_m) + ", " +
468 - str(prob_n) + ", " + str(prob_k) + "]" +
469 - ", has_act_order = " + str(has_act_order) +
470 - ", num_groups = " + str(num_groups) +
471 - ", group_size = " + str(group_size) +
472 - ", thread_n_blocks = " + str(thread_n_blocks) +
473 - ", thread_k_blocks = " + str(thread_k_blocks));
474 - }
475 - }
476 - }
477 -}
478 -
479 -} // namespace marlin_moe
480 -
481 -torch::Tensor marlin_gemm_moe(
482 - const torch::Tensor& a, const torch::Tensor& b_q_weights,
483 - const torch::Tensor& sorted_ids, const torch::Tensor& topk_weights,
484 - const torch::Tensor& topk_ids, const torch::Tensor& b_scales,
485 - torch::Tensor& b_zeros, const torch::Tensor& g_idx,
486 - const torch::Tensor& perm, torch::Tensor& workspace,
487 - vllm::ScalarTypeId const b_q_type_id, int64_t size_m, int64_t size_n,
488 - int64_t size_k, bool is_k_full, int64_t num_experts, int64_t topk,
489 - int64_t moe_block_size, bool replicate_input, bool apply_weights) {
490 - vllm::ScalarType const b_q_type = vllm::ScalarType::from_id(b_q_type_id);
491 - bool has_zp = b_zeros.size(1) != 0;
492 - if (has_zp) {
493 - TORCH_CHECK(
494 - b_q_type == vllm::kU4,
495 - "b_q_type must be u4 when has_zp = True. Got = ", b_q_type.str());
496 - } else {
497 - TORCH_CHECK(
498 - b_q_type == vllm::kU4B8 || b_q_type == vllm::kU8B128,
499 - "b_q_type must be uint4b8 or uint8b128. Got = ", b_q_type.str());
500 - }
501 -
502 - int pack_factor = 32 / b_q_type.size_bits();
503 -
504 - int max_par = 4;
505 -
506 - int dev = a.get_device();
507 -
508 - auto options_dtype =
509 - torch::TensorOptions().dtype(a.dtype()).device(a.device());
510 - auto options_int =
511 - torch::TensorOptions().dtype(torch::kInt).device(a.device());
512 - torch::Tensor c = torch::zeros({size_m, topk, size_n}, options_dtype);
513 - torch::Tensor a_tmp =
514 - replicate_input ? torch::zeros({size_m, size_k}, options_dtype)
515 - : torch::zeros({size_m, topk, size_k}, options_dtype);
516 - torch::Tensor expert_offsets = torch::empty({num_experts + 1}, options_int);
517 -
518 - // thread_k: `k` size of a thread_tile in `weights` (can usually be left as
519 - // auto -1)
520 - int thread_k = -1;
521 - // thread_n: `n` size of a thread_tile in `weights` (can usually be left as
522 - // auto -1)
523 - int thread_n = -1;
524 - // sms: number of SMs to use for the kernel (can usually be left as auto -1)
525 - int sms = -1;
526 -
527 - // Detect groupsize and act_order
528 - int num_groups = -1;
529 - int group_size = -1;
530 - bool has_act_order = g_idx.size(1) != 0;
531 -
532 - int b_rank = b_scales.sizes().size();
533 - TORCH_CHECK(b_rank == 3, "b_scales rank = ", b_rank, " is not 3");
534 - TORCH_CHECK(b_scales.size(2) == size_n, "b_scales dim 2 = ", b_scales.size(2),
535 - " is not size_n = ", size_n);
536 - num_groups = b_scales.size(1);
537 -
538 - TORCH_CHECK(VLLM_IMPLIES(!is_k_full, has_act_order),
539 - "if is_k_full is false, has_act_order must be true");
540 -
541 - if (has_act_order) {
542 - if (is_k_full) {
543 - TORCH_CHECK(num_groups > 1, "For act_order, num_groups must be > 1");
544 - TORCH_CHECK(size_k % num_groups == 0, "size_k = ", size_k,
545 - ", is not divisible by num_groups = ", num_groups);
546 - group_size = size_k / num_groups;
547 - } else {
548 - group_size = 0;
549 - }
550 -
551 - } else {
552 - if (num_groups > 1) {
553 - TORCH_CHECK(
554 - size_k % num_groups == 0, "size_k = ", size_k,
555 - ", is not divisible by b_scales.size(0) = ", b_scales.size(0));
556 - group_size = size_k / num_groups;
557 - } else {
558 - group_size = -1;
559 - }
560 - }
561 -
562 - // Verify b_zeros
563 - if (has_zp) {
564 - int rank = b_zeros.sizes().size();
565 - TORCH_CHECK(rank == 3, "b_zeros rank = ", rank, " is not 3");
566 - TORCH_CHECK(b_zeros.size(1) == num_groups,
567 - "b_zeros dim 1 = ", b_zeros.size(1),
568 - " is not num_groups = ", num_groups);
569 - TORCH_CHECK(b_zeros.size(2) == size_n / pack_factor,
570 - "b_zeros dim 2 = ", b_zeros.size(2),
571 - " is not size_n / pack_factor = ", size_n / pack_factor);
572 - }
573 -
574 - marlin_moe::marlin_mm_moe(
575 - a.data_ptr(), b_q_weights.data_ptr(), c.data_ptr(), sorted_ids.data_ptr(),
576 - topk_weights.data_ptr(), topk_ids.data_ptr(), b_scales.data_ptr(),
577 - b_zeros.data_ptr(), g_idx.data_ptr(), perm.data_ptr(), a_tmp.data_ptr(),
578 - expert_offsets.data_ptr(), size_m, size_n, size_k, workspace.data_ptr(),
579 - b_q_type, has_act_order, is_k_full, has_zp, num_groups, group_size,
580 - num_experts, topk, moe_block_size, dev,
581 - at::cuda::getCurrentCUDAStream(dev), thread_k, thread_n, sms, max_par,
582 - replicate_input, apply_weights);
583 - return c;
584 -}
585 -
586 -TORCH_LIBRARY_IMPL_EXPAND(TORCH_EXTENSION_NAME, CUDA, m) {
587 - m.impl("marlin_gemm_moe", &marlin_gemm_moe);
588 -}
csrc/quantization/fp8/fp8_marlin.cu deleted
-1311
@@ -1,1311 +0,0 @@
1 -/*
2 - * Modified by Neural Magic
3 - * Copyright (C) Marlin.2024 Elias Frantar
4 - *
5 - * Licensed under the Apache License, Version 2.0 (the "License");
6 - * you may not use this file except in compliance with the License.
7 - * You may obtain a copy of the License at
8 - *
9 - * http://www.apache.org/licenses/LICENSE-2.0
10 - *
11 - * Unless required by applicable law or agreed to in writing, software
12 - * distributed under the License is distributed on an "AS IS" BASIS,
13 - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 - * See the License for the specific language governing permissions and
15 - * limitations under the License.
16 - */
17 -
18 -/*
19 - * Adapted from https://github.com/IST-DASLab/marlin
20 - */
21 -
22 -#include "../gptq_marlin/marlin.cuh"
23 -#include "../gptq_marlin/marlin_dtypes.cuh"
24 -
25 -#include "core/registration.h"
26 -
27 -using namespace marlin;
28 -
29 -#define STATIC_ASSERT_SCALAR_TYPE_VALID(scalar_t) \
30 - static_assert(std::is_same<scalar_t, half>::value || \
31 - std::is_same<scalar_t, nv_bfloat16>::value, \
32 - "only float16 and bfloat16 is supported");
33 -
34 -template <typename T>
35 -inline std::string str(T x) {
36 - return std::to_string(x);
37 -}
38 -
39 -namespace fp8_marlin {
40 -
41 -#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ < 800
42 -
43 -template <typename scalar_t, // compute dtype, half or nv_float16
44 - const int num_bits, // number of bits used for weights
45 - const int threads, // number of threads in a threadblock
46 - const int thread_m_blocks, // number of 16x16 blocks in the m
47 - // dimension (batchsize) of the
48 - // threadblock
49 - const int thread_n_blocks, // same for n dimension (output)
50 - const int thread_k_blocks, // same for k dimension (reduction)
51 - const int stages, // number of stages for the async global->shared
52 - // fetch pipeline
53 - const int group_blocks = -1 // number of consecutive 16x16 blocks
54 - // with a separate quantization scale
55 - >
56 -__global__ void Marlin(
57 - const int4* __restrict__ A, // fp16 input matrix of shape mxk
58 - const int4* __restrict__ B, // 4bit quantized weight matrix of shape kxn
59 - int4* __restrict__ C, // fp16 output buffer of shape mxn
60 - const int4* __restrict__ scales_ptr, // fp16 quantization scales of shape
61 - // (k/groupsize)xn
62 - int num_groups, // number of scale groups per output channel
63 - int prob_m, // batch dimension m
64 - int prob_n, // output dimension n
65 - int prob_k, // reduction dimension k
66 - int* locks // extra global storage for barrier synchronization
67 -) {}
68 -
69 -} // namespace fp8_marlin
70 -
71 -torch::Tensor fp8_marlin_gemm(torch::Tensor& a, torch::Tensor& b_q_weight,
72 - torch::Tensor& b_scales, torch::Tensor& workspace,
73 - int64_t num_bits, int64_t size_m, int64_t size_n,
74 - int64_t size_k) {
75 - TORCH_CHECK_NOT_IMPLEMENTED(false,
76 - "marlin_gemm(..) requires CUDA_ARCH >= 8.0");
77 - return torch::empty({1, 1});
78 -}
79 -
80 -#else
81 -
82 -// m16n8k16 tensor core mma instruction with fp16 inputs and fp32
83 -// output/accumulation.
84 -template <typename scalar_t>
85 -__device__ inline void mma(const typename ScalarType<scalar_t>::FragA& a_frag,
86 - const typename ScalarType<scalar_t>::FragB& frag_b,
87 - typename ScalarType<scalar_t>::FragC& frag_c) {
88 - const uint32_t* a = reinterpret_cast<const uint32_t*>(&a_frag);
89 - const uint32_t* b = reinterpret_cast<const uint32_t*>(&frag_b);
90 - float* c = reinterpret_cast<float*>(&frag_c);
91 - if constexpr (std::is_same<scalar_t, half>::value) {
92 - asm volatile(
93 - "mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32 "
94 - "{%0,%1,%2,%3}, {%4,%5,%6,%7}, {%8,%9}, {%10,%11,%12,%13};\n"
95 - : "=f"(c[0]), "=f"(c[1]), "=f"(c[2]), "=f"(c[3])
96 - : "r"(a[0]), "r"(a[1]), "r"(a[2]), "r"(a[3]), "r"(b[0]), "r"(b[1]),
97 - "f"(c[0]), "f"(c[1]), "f"(c[2]), "f"(c[3]));
98 - } else if constexpr (std::is_same<scalar_t, nv_bfloat16>::value) {
99 - asm volatile(
100 - "mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32 "
101 - "{%0,%1,%2,%3}, {%4,%5,%6,%7}, {%8,%9}, {%10,%11,%12,%13};\n"
102 - : "=f"(c[0]), "=f"(c[1]), "=f"(c[2]), "=f"(c[3])
103 - : "r"(a[0]), "r"(a[1]), "r"(a[2]), "r"(a[3]), "r"(b[0]), "r"(b[1]),
104 - "f"(c[0]), "f"(c[1]), "f"(c[2]), "f"(c[3]));
105 - } else {
106 - STATIC_ASSERT_SCALAR_TYPE_VALID(scalar_t);
107 - }
108 -}
109 -
110 -// Instruction for loading a full 16x16 matrix fragment of operand A from shared
111 -// memory, directly in tensor core layout.
112 -template <typename scalar_t>
113 -__device__ inline void ldsm4(typename ScalarType<scalar_t>::FragA& frag_a,
114 - const void* smem_ptr) {
115 - uint32_t* a = reinterpret_cast<uint32_t*>(&frag_a);
116 - uint32_t smem = static_cast<uint32_t>(__cvta_generic_to_shared(smem_ptr));
117 - asm volatile("ldmatrix.sync.aligned.m8n8.x4.shared.b16 {%0,%1,%2,%3}, [%4];\n"
118 - : "=r"(a[0]), "=r"(a[1]), "=r"(a[2]), "=r"(a[3])
119 - : "r"(smem));
120 -}
121 -
122 -// Fast FP8ToFp16/FP8ToBf16: Efficiently dequantize 8bit fp8_e4m3 values to fp16
123 -// bf16 Reference:
124 -// - FP16:
125 -// https://github.com/NVIDIA/FasterTransformer/blob/release/v5.3_tag/src/fastertransformer/cutlass_extensions/include/cutlass_extensions/interleaved_numeric_conversion.h#L53-L85
126 -// - BF16:
127 -// https://github.com/NVIDIA/FasterTransformer/blob/release/v5.3_tag/src/fastertransformer/cutlass_extensions/include/cutlass_extensions/interleaved_numeric_conversion.h#L125-L175
128 -template <typename scalar_t>
129 -__device__ inline typename ScalarType<scalar_t>::FragB dequant_8bit(int q) {
130 - STATIC_ASSERT_SCALAR_TYPE_VALID(scalar_t);
131 -}
132 -
133 -template <>
134 -__device__ inline typename ScalarType<half>::FragB dequant_8bit<half>(int q) {
135 - // Constants for FP8 (E4M3) and FP16 formats
136 - constexpr int FP8_EXPONENT = 4, FP8_MANTISSA = 3, FP16_EXPONENT = 5;
137 - constexpr int RIGHT_SHIFT = FP16_EXPONENT - FP8_EXPONENT;
138 -
139 - // Calculate MASK for extracting mantissa and exponent
140 - constexpr int MASK1 = 0x80000000;
141 - constexpr int MASK2 = MASK1 >> (FP8_EXPONENT + FP8_MANTISSA);
142 - constexpr int MASK3 = MASK2 & 0x7fffffff;
143 - constexpr int MASK = MASK3 | (MASK3 >> 16);
144 - // Final MASK value: 0x7F007F00
145 -
146 - // Extract and shift FP8 values to FP16 format
147 - int Out1 = (q & 0x80008000) | ((q & MASK) >> RIGHT_SHIFT);
148 - int Out2 = ((q << 8) & 0x80008000) | (((q << 8) & MASK) >> RIGHT_SHIFT);
149 -
150 - // Construct and apply exponent bias
151 - constexpr int BIAS_OFFSET =
152 - (1 << (FP16_EXPONENT - 1)) - (1 << (FP8_EXPONENT - 1));
153 - const half2 bias_reg = __float2half2_rn(float(1 << BIAS_OFFSET));
154 -
155 - // Convert to half2 and apply bias
156 - typename ScalarType<half>::FragB frag_b;
157 - // Note: reverse indexing is intentional because weights are permuted
158 - frag_b[1] = __hmul2(*reinterpret_cast<const half2*>(&Out1), bias_reg);
159 - frag_b[0] = __hmul2(*reinterpret_cast<const half2*>(&Out2), bias_reg);
160 - return frag_b;
161 -}
162 -
163 -template <>
164 -__device__ inline typename ScalarType<nv_bfloat16>::FragB
165 -dequant_8bit<nv_bfloat16>(int q) {
166 - // Constants for FP8 (E4M3) and BF16 formats
167 - constexpr int FP8_EXPONENT = 4, FP8_MANTISSA = 3, BF16_EXPONENT = 8;
168 - constexpr int RIGHT_SHIFT = BF16_EXPONENT - FP8_EXPONENT;
169 -
170 - // Calculate MASK for extracting mantissa and exponent
171 - constexpr int MASK1 = 0x80000000;
172 - constexpr int MASK2 = MASK1 >> (FP8_EXPONENT + FP8_MANTISSA);
173 - constexpr int MASK3 = MASK2 & 0x7fffffff;
174 - constexpr int MASK = MASK3 | (MASK3 >> 16);
175 - // Final MASK value: 0x7F007F00
176 -
177 - // Extract and shift FP8 values to BF16 format
178 - int Out1 = (q & 0x80008000) | ((q & MASK) >> RIGHT_SHIFT);
179 - int Out2 = ((q << 8) & 0x80008000) | (((q << 8) & MASK) >> RIGHT_SHIFT);
180 -
181 - // Construct and apply exponent bias
182 - constexpr int BIAS_OFFSET =
183 - (1 << (BF16_EXPONENT - 1)) - (1 << (FP8_EXPONENT - 1));
184 - // Add 127 (float exponent bias) to BIAS_OFFSET and shift to float exponent
185 - // position
186 - constexpr uint32_t BIAS = (BIAS_OFFSET + 127) << 23;
187 - const nv_bfloat162 bias_reg =
188 - __float2bfloat162_rn(*reinterpret_cast<const float*>(&BIAS));
189 -
190 - // Convert to bfloat162 and apply bias
191 - typename ScalarType<nv_bfloat16>::FragB frag_b;
192 - // Note: reverse indexing is intentional because weights are permuted
193 - frag_b[1] = __hmul2(*reinterpret_cast<const nv_bfloat162*>(&Out1), bias_reg);
194 - frag_b[0] = __hmul2(*reinterpret_cast<const nv_bfloat162*>(&Out2), bias_reg);
195 - return frag_b;
196 -}
197 -
198 -// Multiply dequantized values by the corresponding quantization scale; used
199 -// only for grouped quantization.
200 -template <typename scalar_t>
201 -__device__ inline void scale(typename ScalarType<scalar_t>::FragB& frag_b,
202 - typename ScalarType<scalar_t>::FragS& frag_s,
203 - int i) {
204 - using scalar_t2 = typename ScalarType<scalar_t>::scalar_t2;
205 - scalar_t2 s =
206 - ScalarType<scalar_t>::num2num2(reinterpret_cast<scalar_t*>(&frag_s)[i]);
207 - frag_b[0] = __hmul2(frag_b[0], s);
208 - frag_b[1] = __hmul2(frag_b[1], s);
209 -}
210 -
211 -// Given 2 floats multiply by 2 scales (halves)
212 -template <typename scalar_t>
213 -__device__ inline void scale_float(float* c,
214 - typename ScalarType<scalar_t>::FragS& s) {
215 - scalar_t* s_ptr = reinterpret_cast<scalar_t*>(&s);
216 - c[0] = __fmul_rn(c[0], ScalarType<scalar_t>::num2float(s_ptr[0]));
217 - c[1] = __fmul_rn(c[1], ScalarType<scalar_t>::num2float(s_ptr[1]));
218 -}
219 -
220 -// Wait until barrier reaches `count`, then lock for current threadblock.
221 -__device__ inline void barrier_acquire(int* lock, int count) {
222 - if (threadIdx.x == 0) {
223 - int state = -1;
224 - do
225 - // Guarantee that subsequent writes by this threadblock will be visible
226 - // globally.
227 - asm volatile("ld.global.acquire.gpu.b32 %0, [%1];\n"
228 - : "=r"(state)
229 - : "l"(lock));
230 - while (state != count);
231 - }
232 - __syncthreads();
233 -}
234 -
235 -// Release barrier and increment visitation count.
236 -__device__ inline void barrier_release(int* lock, bool reset = false) {
237 - __syncthreads();
238 - if (threadIdx.x == 0) {
239 - if (reset) {
240 - lock[0] = 0;
241 - return;
242 - }
243 - int val = 1;
244 - // Make sure that all writes since acquiring this barrier are visible
245 - // globally, while releasing the barrier.
246 - asm volatile("fence.acq_rel.gpu;\n");
247 - asm volatile("red.relaxed.gpu.global.add.s32 [%0], %1;\n"
248 - :
249 - : "l"(lock), "r"(val));
250 - }
251 -}
252 -
253 -template <typename scalar_t, // compute dtype, half or nv_float16
254 - const int num_bits, // number of bits used for weights
255 - const int threads, // number of threads in a threadblock
256 - const int thread_m_blocks, // number of 16x16 blocks in the m
257 - // dimension (batchsize) of the
258 - // threadblock
259 - const int thread_n_blocks, // same for n dimension (output)
260 - const int thread_k_blocks, // same for k dimension (reduction)
261 - const int stages, // number of stages for the async global->shared
262 - // fetch pipeline
263 - const int group_blocks = -1 // number of consecutive 16x16 blocks
264 - // with a separate quantization scale
265 - >
266 -__global__ void Marlin(
267 - const int4* __restrict__ A, // fp16 input matrix of shape mxk
268 - const int4* __restrict__ B, // 4bit quantized weight matrix of shape kxn
269 - int4* __restrict__ C, // fp16 output buffer of shape mxn
270 - const int4* __restrict__ scales_ptr, // fp16 quantization scales of shape
271 - // (k/groupsize)xn
272 - int num_groups, // number of scale groups per output channel
273 - int prob_m, // batch dimension m
274 - int prob_n, // output dimension n
275 - int prob_k, // reduction dimension k
276 - int* locks // extra global storage for barrier synchronization
277 -) {
278 - // Each threadblock processes one "stripe" of the B matrix with (roughly) the
279 - // same size, which might involve multiple column "slices" (of width 16 *
280 - // `thread_n_blocks`). Stripes are defined as shown in the 3x3 matrix 5 SM
281 - // example:
282 - // 0 1 3
283 - // 0 2 3
284 - // 1 2 4
285 - // While this kind of partitioning makes things somewhat more complicated, it
286 - // ensures good utilization of all SMs for many kinds of shape and GPU
287 - // configurations, while requiring as few slow global cross-threadblock
288 - // reductions as possible.
289 - using Dtype = ScalarType<scalar_t>;
290 - using scalar_t2 = typename ScalarType<scalar_t>::scalar_t2;
291 - using FragA = typename ScalarType<scalar_t>::FragA;
292 - using FragB = typename ScalarType<scalar_t>::FragB;
293 - using FragC = typename ScalarType<scalar_t>::FragC;
294 - using FragS = typename ScalarType<scalar_t>::FragS;
295 -
296 - constexpr int pack_factor = 32 / num_bits;
297 -
298 - // For larger GEMMs we run multiple batchsize 64 versions in parallel for a
299 - // better partitioning with less reductions
300 - int parallel = 1;
301 - if (prob_m > 16 * thread_m_blocks) {
302 - parallel = prob_m / (16 * thread_m_blocks);
303 - prob_m = 16 * thread_m_blocks;
304 - }
305 -
306 - int k_tiles = prob_k / 16 / thread_k_blocks;
307 - int n_tiles = prob_n / 16 / thread_n_blocks;
308 - int iters = div_ceil(k_tiles * n_tiles * parallel, gridDim.x);
309 -
310 - int slice_row = (iters * blockIdx.x) % k_tiles;
311 - int slice_col_par = (iters * blockIdx.x) / k_tiles;
312 - int slice_col = slice_col_par;
313 - int slice_iters; // number of threadblock tiles in the current slice
314 - int slice_count =
315 - 0; // total number of active threadblocks in the current slice
316 - int slice_idx; // index of threadblock in current slice; numbered bottom to
317 - // top
318 -
319 - // We can easily implement parallel problem execution by just remapping
320 - // indices and advancing global pointers
321 - if (slice_col_par >= n_tiles) {
322 - A += (slice_col_par / n_tiles) * 16 * thread_m_blocks * prob_k / 8;
323 - C += (slice_col_par / n_tiles) * 16 * thread_m_blocks * prob_n / 8;
324 - locks += (slice_col_par / n_tiles) * n_tiles;
325 - slice_col = slice_col_par % n_tiles;
326 - }
327 -
328 - // Compute all information about the current slice which is required for
329 - // synchronization.
330 - auto init_slice = [&]() {
331 - slice_iters =
332 - iters * (blockIdx.x + 1) - (k_tiles * slice_col_par + slice_row);
333 - if (slice_iters < 0 || slice_col_par >= n_tiles * parallel) slice_iters = 0;
334 - if (slice_iters == 0) return;
335 - if (slice_row + slice_iters > k_tiles) slice_iters = k_tiles - slice_row;
336 - slice_count = 1;
337 - slice_idx = 0;
338 - int col_first = iters * div_ceil(k_tiles * slice_col_par, iters);
339 - if (col_first <= k_tiles * (slice_col_par + 1)) {
340 - int col_off = col_first - k_tiles * slice_col_par;
341 - slice_count = div_ceil(k_tiles - col_off, iters);
342 - if (col_off > 0) slice_count++;
343 - int delta_first = iters * blockIdx.x - col_first;
344 - if (delta_first < 0 || (col_off == 0 && delta_first == 0))
345 - slice_idx = slice_count - 1;
346 - else {
347 - slice_idx = slice_count - 1 - delta_first / iters;
348 - if (col_off > 0) slice_idx--;
349 - }
350 - }
351 - if (slice_col == n_tiles) {
352 - A += 16 * thread_m_blocks * prob_k / 8;
353 - C += 16 * thread_m_blocks * prob_n / 8;
354 - locks += n_tiles;
355 - slice_col = 0;
356 - }
357 - };
358 - init_slice();
359 -
360 - // A sizes/strides
361 -
362 - // stride of the A matrix in global memory
363 - int a_gl_stride = prob_k / 8;
364 - // stride of an A matrix tile in shared memory
365 - constexpr int a_sh_stride = 16 * thread_k_blocks / 8;
366 - // delta between subsequent A tiles in global memory
367 - constexpr int a_gl_rd_delta_o = 16 * thread_k_blocks / 8;
368 - // between subsequent accesses within a tile
369 - int a_gl_rd_delta_i = a_gl_stride * (threads / a_gl_rd_delta_o);
370 - // between shared memory writes
371 - constexpr int a_sh_wr_delta = a_sh_stride * (threads / a_gl_rd_delta_o);
372 - // between shared memory tile reads
373 - constexpr int a_sh_rd_delta_o = 2 * ((threads / 32) / (thread_n_blocks / 4));
374 - // within a shared memory tile
375 - constexpr int a_sh_rd_delta_i = a_sh_stride * 16;
376 - // overall size of a tile
377 - constexpr int a_sh_stage = a_sh_stride * (16 * thread_m_blocks);
378 - // number of shared write iterations for a tile
379 - constexpr int a_sh_wr_iters = div_ceil(a_sh_stage, a_sh_wr_delta);
380 -
381 - // B sizes/strides
382 - int b_gl_stride = 16 * prob_n / (pack_factor * 4);
383 - constexpr int b_sh_stride = ((thread_n_blocks * 16) * 16 / pack_factor) / 4;
384 - constexpr int b_thread_vecs = num_bits == 4 ? 1 : 2;
385 - constexpr int b_sh_stride_threads = b_sh_stride / b_thread_vecs;
386 -
387 - int b_gl_rd_delta_o = b_gl_stride * thread_k_blocks;
388 - int b_gl_rd_delta_i = b_gl_stride * (threads / b_sh_stride_threads);
389 - constexpr int b_sh_wr_delta = threads * b_thread_vecs;
390 - constexpr int b_sh_rd_delta = threads * b_thread_vecs;
391 - constexpr int b_sh_stage = b_sh_stride * thread_k_blocks;
392 - constexpr int b_sh_wr_iters = b_sh_stage / b_sh_wr_delta;
393 -
394 - // Scale sizes/strides without act_order
395 - int s_gl_stride = prob_n / 8;
396 - constexpr int s_sh_stride = 16 * thread_n_blocks / 8;
397 -
398 - // Scale size/strides with act_order
399 - constexpr int tb_k = 16 * thread_k_blocks;
400 - constexpr int g_idx_stage = 0;
401 - // constexpr int act_s_row_stride = 1;
402 - // int act_s_col_stride = act_s_row_stride * num_groups;
403 - int act_s_col_stride = 1;
404 - int act_s_col_warp_stride = act_s_col_stride * 8;
405 - int tb_n_warps = thread_n_blocks / 4;
406 - int act_s_col_tb_stride = act_s_col_warp_stride * tb_n_warps;
407 -
408 - // Global A read index of current thread.
409 - int a_gl_rd = a_gl_stride * (threadIdx.x / a_gl_rd_delta_o) +
410 - (threadIdx.x % a_gl_rd_delta_o);
411 - a_gl_rd += a_gl_rd_delta_o * slice_row;
412 - // Shared write index of current thread.
413 - int a_sh_wr = a_sh_stride * (threadIdx.x / a_gl_rd_delta_o) +
414 - (threadIdx.x % a_gl_rd_delta_o);
415 - // Shared read index.
416 - int a_sh_rd =
417 - a_sh_stride * ((threadIdx.x % 32) % 16) + (threadIdx.x % 32) / 16;
418 - a_sh_rd += 2 * ((threadIdx.x / 32) / (thread_n_blocks / 4));
419 -
420 - int b_gl_rd = b_gl_stride * (threadIdx.x / b_sh_stride_threads) +
421 - (threadIdx.x % b_sh_stride_threads) * b_thread_vecs;
422 - b_gl_rd += b_sh_stride * slice_col;
423 - b_gl_rd += b_gl_rd_delta_o * slice_row;
424 - int b_sh_wr = threadIdx.x * b_thread_vecs;
425 - int b_sh_rd = threadIdx.x * b_thread_vecs;
426 -
427 - // For act_order
428 - int slice_k_start = tb_k * slice_row;
429 - int slice_k_start_shared_fetch = slice_k_start;
430 - int slice_n_offset = act_s_col_tb_stride * slice_col;
431 -
432 - // No act_order
433 - int s_gl_rd = s_sh_stride * slice_col + threadIdx.x;
434 - int s_sh_wr = threadIdx.x;
435 - bool s_sh_wr_pred = threadIdx.x < s_sh_stride;
436 -
437 - // We scale a `half2` tile in row-major layout for column-wise quantization.
438 - int s_sh_rd =
439 - 8 * ((threadIdx.x / 32) % (thread_n_blocks / 4)) + (threadIdx.x % 32) % 4;
440 -
441 - // Precompute which thread should not read memory in which iterations; this is
442 - // needed if there are more threads than required for a certain tilesize or
443 - // when the batchsize is not a multiple of 16.
444 - bool a_sh_wr_pred[a_sh_wr_iters];
445 - #pragma unroll
446 - for (int i = 0; i < a_sh_wr_iters; i++)
447 - a_sh_wr_pred[i] = a_sh_wr_delta * i + a_sh_wr < a_sh_stride * prob_m;
448 -
449 - // To ensure that writing and reading A tiles to/from shared memory, the
450 - // latter in fragment format, is fully bank conflict free, we need to use a
451 - // rather fancy XOR-based layout. The key here is that neither reads nor
452 - // writes of the 16-byte `int4` blocks of 8 consecutive threads involve the
453 - // same shared memory banks. Further, it seems (based on NSight-Compute) that
454 - // each warp must also write a consecutive memory segment?
455 - auto transform_a = [&](int i) {
456 - int row = i / a_gl_rd_delta_o;
457 - return a_gl_rd_delta_o * row + (i % a_gl_rd_delta_o) ^ row;
458 - };
459 - // Since the computation of this remapping is non-trivial and, due to our main
460 - // loop unrolls, all shared memory accesses are static, we simply precompute
461 - // both transformed reads and writes.
462 - int a_sh_wr_trans[a_sh_wr_iters];
463 - #pragma unroll
464 - for (int i = 0; i < a_sh_wr_iters; i++)
465 - a_sh_wr_trans[i] = transform_a(a_sh_wr_delta * i + a_sh_wr);
466 - int a_sh_rd_trans[b_sh_wr_iters][thread_m_blocks];
467 - #pragma unroll
468 - for (int i = 0; i < b_sh_wr_iters; i++) {
469 - #pragma unroll
470 - for (int j = 0; j < thread_m_blocks; j++)
471 - a_sh_rd_trans[i][j] =
472 - transform_a(a_sh_rd_delta_o * i + a_sh_rd_delta_i * j + a_sh_rd);
473 - }
474 -
475 - // Since B-accesses have non-constant stride they have to be computed at
476 - // runtime; we break dependencies between subsequent accesses with a tile by
477 - // maintining multiple pointers (we have enough registers), a tiny
478 - // optimization.
479 - const int4* B_ptr[b_sh_wr_iters];
480 - #pragma unroll
481 - for (int i = 0; i < b_sh_wr_iters; i++)
482 - B_ptr[i] = B + b_gl_rd_delta_i * i + b_gl_rd;
483 -
484 - extern __shared__ int4 sh[];
485 - // Shared memory storage for global fetch pipelines.
486 - int4* sh_a = sh;
487 - int4* sh_b = sh_a + (stages * a_sh_stage);
488 - int4* sh_g_idx = sh_b + (stages * b_sh_stage);
489 - int4* sh_s = sh_g_idx + (stages * g_idx_stage);
490 -
491 - // Register storage for double buffer of shared memory reads.
492 - FragA frag_a[2][thread_m_blocks];
493 - I4 frag_b_quant[2][b_thread_vecs];
494 - FragC frag_c[thread_m_blocks][4][2];
495 - FragS frag_s[2][4];
496 -
497 - // Zero accumulators.
498 - auto zero_accums = [&]() {
499 - #pragma unroll
500 - for (int i = 0; i < thread_m_blocks * 4 * 2 * 4; i++)
501 - reinterpret_cast<float*>(frag_c)[i] = 0;
502 - };
503 -
504 - int sh_first_group_id = -1;
505 - int sh_num_groups = -1;
506 - constexpr int sh_max_num_groups = 32;
507 -
508 - auto fetch_scales_to_shared = [&](bool is_async, int first_group_id,
509 - int last_group_id) {
510 - sh_first_group_id = first_group_id;
511 - sh_num_groups = last_group_id - first_group_id + 1;
512 -
513 - if (sh_num_groups < sh_max_num_groups) {
514 - sh_num_groups = sh_max_num_groups;
515 - }
516 -
517 - if (sh_first_group_id + sh_num_groups > num_groups) {
518 - sh_num_groups = num_groups - sh_first_group_id;
519 - }
520 -
521 - int row_offset = first_group_id * s_gl_stride;
522 -
523 - if (is_async) {
524 - for (int i = 0; i < sh_num_groups; i++) {
525 - if (threadIdx.x < s_sh_stride) {
526 - cp_async4_pred(&sh_s[(i * s_sh_stride) + threadIdx.x],
527 - &scales_ptr[row_offset + (i * s_gl_stride) +
528 - slice_n_offset + threadIdx.x]);
529 - }
530 - }
531 - } else {
532 - for (int i = 0; i < sh_num_groups; i++) {
533 - if (threadIdx.x < s_sh_stride) {
534 - sh_s[(i * s_sh_stride) + threadIdx.x] =
535 - scales_ptr[row_offset + (i * s_gl_stride) + slice_n_offset +
536 - threadIdx.x];
537 - }
538 - }
539 - }
540 - };
541 - // Asynchronously fetch the next A, B and s tile from global to the next
542 - // shared memory pipeline location.
543 - auto fetch_to_shared = [&](int pipe, int a_off, bool pred = true) {
544 - if (pred) {
545 - int4* sh_a_stage = sh_a + a_sh_stage * pipe;
546 - #pragma unroll
547 - for (int i = 0; i < a_sh_wr_iters; i++) {
548 - cp_async4_pred(
549 - &sh_a_stage[a_sh_wr_trans[i]],
550 - &A[a_gl_rd_delta_i * i + a_gl_rd + a_gl_rd_delta_o * a_off],
551 - a_sh_wr_pred[i]);
552 - }
553 - int4* sh_b_stage = sh_b + b_sh_stage * pipe;
554 - #pragma unroll
555 - for (int i = 0; i < b_sh_wr_iters; i++) {
556 - #pragma unroll
557 - for (int j = 0; j < b_thread_vecs; j++) {
558 - cp_async4(&sh_b_stage[b_sh_wr_delta * i + b_sh_wr + j], B_ptr[i] + j);
559 - }
560 -
561 - B_ptr[i] += b_gl_rd_delta_o;
562 - }
563 - }
564 - // Insert a fence even when we are winding down the pipeline to ensure that
565 - // waiting is also correct at this point.
566 - cp_async_fence();
567 - };
568 -
569 - // Wait until the next thread tile has been loaded to shared memory.
570 - auto wait_for_stage = [&]() {
571 - // We only have `stages - 2` active fetches since we are double buffering
572 - // and can only issue the next fetch when it is guaranteed that the previous
573 - // shared memory load is fully complete (as it may otherwise be
574 - // overwritten).
575 - cp_async_wait<stages - 2>();
576 - __syncthreads();
577 - };
578 -
579 - // Load the next sub-tile from the current location in the shared memory pipe
580 - // into the current register buffer.
581 - auto fetch_to_registers = [&](int k, int pipe) {
582 - int4* sh_a_stage = sh_a + a_sh_stage * pipe;
583 - #pragma unroll
584 - for (int i = 0; i < thread_m_blocks; i++)
585 - ldsm4<scalar_t>(frag_a[k % 2][i],
586 - &sh_a_stage[a_sh_rd_trans[k % b_sh_wr_iters][i]]);
587 - int4* sh_b_stage = sh_b + b_sh_stage * pipe;
588 -
589 - #pragma unroll
590 - for (int i = 0; i < b_thread_vecs; i++) {
591 - frag_b_quant[k % 2][i] = *reinterpret_cast<I4*>(
592 - &sh_b_stage[b_sh_rd_delta * (k % b_sh_wr_iters) + b_sh_rd + i]);
593 - }
594 - };
595 -
596 - bool is_same_group[stages];
597 - int same_group_id[stages];
598 -
599 - auto init_same_group = [&](int pipe) {
600 - is_same_group[pipe] = false;
601 - same_group_id[pipe] = 0;
602 - return;
603 - };
604 -
605 - // Execute the actual tensor core matmul of a sub-tile.
606 - auto matmul = [&](int k) {
607 - // We have the m dimension as the inner loop in order to encourage overlapping
608 - // dequantization and matmul operations.
609 - #pragma unroll
610 - for (int j = 0; j < 4; j++) {
611 - FragB frag_b0;
612 - FragB frag_b1;
613 -
614 - int* frag_b_quant_ptr = reinterpret_cast<int*>(frag_b_quant[k % 2]);
615 - int b_quant_0 = frag_b_quant_ptr[j * 2 + 0];
616 - int b_quant_1 = frag_b_quant_ptr[j * 2 + 1];
617 -
618 - frag_b0 = dequant_8bit<scalar_t>(b_quant_0);
619 - frag_b1 = dequant_8bit<scalar_t>(b_quant_1);
620 -
621 - #pragma unroll
622 - for (int i = 0; i < thread_m_blocks; i++) {
623 - mma<scalar_t>(frag_a[k % 2][i], frag_b0, frag_c[i][j][0]);
624 - mma<scalar_t>(frag_a[k % 2][i], frag_b1, frag_c[i][j][1]);
625 - }
626 - }
627 - };
628 -
629 - // Since we slice across the k dimension of a tile in order to increase the
630 - // number of warps while keeping the n dimension of a tile reasonable, we have
631 - // multiple warps that accumulate their partial sums of the same output
632 - // location; which we have to reduce over in the end. We do in shared memory.
633 - auto thread_block_reduce = [&]() {
634 - constexpr int red_off = threads / b_sh_stride_threads / 2;
635 - if (red_off >= 1) {
636 - int red_idx = threadIdx.x / b_sh_stride_threads;
637 - constexpr int red_sh_stride = b_sh_stride_threads * 4 * 2;
638 - constexpr int red_sh_delta = b_sh_stride_threads;
639 - int red_sh_rd = red_sh_stride * (threadIdx.x / b_sh_stride_threads) +
640 - (threadIdx.x % b_sh_stride_threads);
641 -
642 - // Parallel logarithmic shared memory reduction. We make sure to avoid any
643 - // unnecessary read or write iterations, e.g., for two warps we write only
644 - // once by warp 1 and read only once by warp 0.
645 -
646 - #pragma unroll
647 - for (int m_block = 0; m_block < thread_m_blocks; m_block++) {
648 - #pragma unroll
649 - for (int i = red_off; i > 0; i /= 2) {
650 - if (i <= red_idx && red_idx < 2 * i) {
651 - #pragma unroll
652 - for (int j = 0; j < 4 * 2; j++) {
653 - int red_sh_wr =
654 - red_sh_delta * j + (red_sh_rd - red_sh_stride * i);
655 - if (i < red_off) {
656 - float* c_rd =
657 - reinterpret_cast<float*>(&sh[red_sh_delta * j + red_sh_rd]);
658 - float* c_wr = reinterpret_cast<float*>(&sh[red_sh_wr]);
659 - #pragma unroll
660 - for (int k = 0; k < 4; k++)
661 - reinterpret_cast<FragC*>(frag_c)[4 * 2 * m_block + j][k] +=
662 - c_rd[k] + c_wr[k];
663 - }
664 - sh[red_sh_wr] =
665 - reinterpret_cast<int4*>(&frag_c)[4 * 2 * m_block + j];
666 - }
667 - }
668 - __syncthreads();
669 - }
670 - if (red_idx == 0) {
671 - #pragma unroll
672 - for (int i = 0; i < 4 * 2; i++) {
673 - float* c_rd =
674 - reinterpret_cast<float*>(&sh[red_sh_delta * i + red_sh_rd]);
675 - #pragma unroll
676 - for (int j = 0; j < 4; j++)
677 - reinterpret_cast<FragC*>(frag_c)[4 * 2 * m_block + i][j] +=
678 - c_rd[j];
679 - }
680 - }
681 - __syncthreads();
682 - }
683 - }
684 - };
685 -
686 - // Since multiple threadblocks may process parts of the same column slice, we
687 - // finally have to globally reduce over the results. As the striped
688 - // partitioning minimizes the number of such reductions and our outputs are
689 - // usually rather small, we perform this reduction serially in L2 cache.
690 - auto global_reduce = [&](bool first = false, bool last = false) {
691 - // We are very careful here to reduce directly in the output buffer to
692 - // maximize L2 cache utilization in this step. To do this, we write out
693 - // results in FP16 (but still reduce with FP32 compute).
694 - constexpr int active_threads = 32 * thread_n_blocks / 4;
695 - if (threadIdx.x < active_threads) {
696 - int c_gl_stride = prob_n / 8;
697 - int c_gl_wr_delta_o = 8 * c_gl_stride;
698 - int c_gl_wr_delta_i = 4 * (active_threads / 32);
699 - int c_gl_wr = c_gl_stride * ((threadIdx.x % 32) / 4) +
700 - 4 * (threadIdx.x / 32) + threadIdx.x % 4;
701 - c_gl_wr += (2 * thread_n_blocks) * slice_col;
702 - constexpr int c_sh_wr_delta = active_threads;
703 - int c_sh_wr = threadIdx.x;
704 -
705 - int row = (threadIdx.x % 32) / 4;
706 -
707 - if (!first) {
708 - // Interestingly, doing direct global accesses here really seems to mess up
709 - // the compiler and lead to slowdowns, hence we also use async-copies even
710 - // though these fetches are not actually asynchronous.
711 - #pragma unroll
712 - for (int i = 0; i < thread_m_blocks * 4; i++) {
713 - cp_async4_pred(
714 - &sh[c_sh_wr + c_sh_wr_delta * i],
715 - &C[c_gl_wr + c_gl_wr_delta_o * (i / 2) +
716 - c_gl_wr_delta_i * (i % 2)],
717 - i < (thread_m_blocks - 1) * 4 || 8 * (i / 2) + row < prob_m);
718 - }
719 - cp_async_fence();
720 - cp_async_wait<0>();
721 - }
722 -
723 - #pragma unroll
724 - for (int i = 0; i < thread_m_blocks * 4; i++) {
725 - if (i < (thread_m_blocks - 1) * 4 || 8 * (i / 2) + row < prob_m) {
726 - if (!first) {
727 - int4 c_red = sh[c_sh_wr + i * c_sh_wr_delta];
728 - #pragma unroll
729 - for (int j = 0; j < 2 * 4; j++) {
730 - reinterpret_cast<float*>(
731 - &frag_c)[4 * 2 * 4 * (i / 4) + 4 * j + (i % 4)] +=
732 - Dtype::num2float(reinterpret_cast<scalar_t*>(&c_red)[j]);
733 - }
734 - }
735 - if (!last) {
736 - int4 c;
737 - #pragma unroll
738 - for (int j = 0; j < 2 * 4; j++) {
739 - reinterpret_cast<scalar_t*>(&c)[j] =
740 - Dtype::float2num(reinterpret_cast<float*>(
741 - &frag_c)[4 * 2 * 4 * (i / 4) + 4 * j + (i % 4)]);
742 - }
743 - C[c_gl_wr + c_gl_wr_delta_o * (i / 2) + c_gl_wr_delta_i * (i % 2)] =
744 - c;
745 - }
746 - }
747 - }
748 - }
749 - };
750 -
751 - // Write out the reduce final result in the correct layout. We only actually
752 - // reshuffle matrix fragments in this step, the reduction above is performed
753 - // in fragment layout.
754 - auto write_result = [&]() {
755 - int c_gl_stride = prob_n / 8;
756 - constexpr int c_sh_stride = 2 * thread_n_blocks + 1;
757 - int c_gl_wr_delta = c_gl_stride * (threads / (2 * thread_n_blocks));
758 - constexpr int c_sh_rd_delta =
759 - c_sh_stride * (threads / (2 * thread_n_blocks));
760 -
761 - int c_gl_wr = c_gl_stride * (threadIdx.x / (2 * thread_n_blocks)) +
762 - (threadIdx.x % (2 * thread_n_blocks));
763 - c_gl_wr += (2 * thread_n_blocks) * slice_col;
764 - int c_sh_wr =
765 - (4 * c_sh_stride) * ((threadIdx.x % 32) / 4) + (threadIdx.x % 32) % 4;
766 - c_sh_wr += 32 * (threadIdx.x / 32);
767 - int c_sh_rd = c_sh_stride * (threadIdx.x / (2 * thread_n_blocks)) +
768 - (threadIdx.x % (2 * thread_n_blocks));
769 -
770 - int c_gl_wr_end = c_gl_stride * prob_m;
771 -
772 - // We first reorder in shared memory to guarantee the most efficient final
773 - // global write patterns
774 - auto write = [&](int idx, float c0, float c1, FragS& s) {
775 - scalar_t2 res =
776 - Dtype::nums2num2(Dtype::float2num(c0), Dtype::float2num(c1));
777 -
778 - ((scalar_t2*)sh)[idx] = res;
779 - };
780 -
781 - if (threadIdx.x / 32 < thread_n_blocks / 4) {
782 - #pragma unroll
783 - for (int i = 0; i < thread_m_blocks; i++) {
784 - #pragma unroll
785 - for (int j = 0; j < 4; j++) {
786 - int wr = c_sh_wr + 8 * j;
787 - write(wr + (4 * c_sh_stride) * 0 + 0, frag_c[i][j][0][0],
788 - frag_c[i][j][0][1], frag_s[j / 2][2 * (j % 2) + 0]);
789 - write(wr + (4 * c_sh_stride) * 8 + 0, frag_c[i][j][0][2],
790 - frag_c[i][j][0][3], frag_s[j / 2][2 * (j % 2) + 0]);
791 - write(wr + (4 * c_sh_stride) * 0 + 4, frag_c[i][j][1][0],
792 - frag_c[i][j][1][1], frag_s[j / 2][2 * (j % 2) + 1]);
793 - write(wr + (4 * c_sh_stride) * 8 + 4, frag_c[i][j][1][2],
794 - frag_c[i][j][1][3], frag_s[j / 2][2 * (j % 2) + 1]);
795 - }
796 - c_sh_wr += 16 * (4 * c_sh_stride);
797 - }
798 - }
799 - __syncthreads();
800 -
801 - #pragma unroll
802 - for (int i = 0;
803 - i < div_ceil(16 * thread_m_blocks, threads / (2 * thread_n_blocks));
804 - i++) {
805 - if (c_gl_wr < c_gl_wr_end) {
806 - C[c_gl_wr] = sh[c_sh_rd];
807 - c_gl_wr += c_gl_wr_delta;
808 - c_sh_rd += c_sh_rd_delta;
809 - }
810 - }
811 - };
812 -
813 - // Start global fetch and register load pipelines.
814 - auto start_pipes = [&]() {
815 -
816 - #pragma unroll
817 - for (int i = 0; i < stages - 1; i++) {
818 - fetch_to_shared(i, i, i < slice_iters);
819 - }
820 -
821 - zero_accums();
822 - wait_for_stage();
823 - init_same_group(0);
824 - fetch_to_registers(0, 0);
825 - a_gl_rd += a_gl_rd_delta_o * (stages - 1);
826 - slice_k_start_shared_fetch += tb_k * (stages - 1);
827 - };
828 - if (slice_iters) {
829 - start_pipes();
830 - }
831 -
832 - // Main loop.
833 - while (slice_iters) {
834 - // We unroll over both the global fetch and the register load pipeline to
835 - // ensure all shared memory accesses are static. Note that both pipelines
836 - // have even length meaning that the next iteration will always start at
837 - // index 0.
838 -
839 - #pragma unroll
840 - for (int pipe = 0; pipe < stages;) {
841 - #pragma unroll
842 - for (int k = 0; k < b_sh_wr_iters; k++) {
843 - fetch_to_registers(k + 1, pipe % stages);
844 - if (k == b_sh_wr_iters - 2) {
845 - fetch_to_shared((pipe + stages - 1) % stages, pipe,
846 - slice_iters >= stages);
847 - pipe++;
848 - wait_for_stage();
849 - init_same_group(pipe % stages);
850 - }
851 - matmul(k);
852 - }
853 - slice_iters--;
854 - if (slice_iters == 0) {
855 - break;
856 - }
857 - }
858 -
859 - a_gl_rd += a_gl_rd_delta_o * stages;
860 - slice_k_start += tb_k * stages;
861 - slice_k_start_shared_fetch += tb_k * stages;
862 -
863 - // Process results and, if necessary, proceed to the next column slice.
864 - // While this pattern may not be the most readable, other ways of writing
865 - // the loop seemed to noticeably worse performance after compilation.
866 - if (slice_iters == 0) {
867 - cp_async_wait<0>();
868 - bool last = slice_idx == slice_count - 1;
869 - // For per-column scales, we only fetch them here in the final step before
870 - // write-out
871 - if (s_sh_wr_pred) {
872 - cp_async4(&sh_s[s_sh_wr], &scales_ptr[s_gl_rd]);
873 - }
874 - cp_async_fence();
875 -
876 - thread_block_reduce();
877 -
878 - cp_async_wait<0>();
879 - __syncthreads();
880 - if (threadIdx.x / 32 < thread_n_blocks / 4) {
881 - reinterpret_cast<int4*>(&frag_s)[0] = sh_s[s_sh_rd + 0];
882 - reinterpret_cast<int4*>(&frag_s)[1] = sh_s[s_sh_rd + 4];
883 - }
884 -
885 - // For 8-bit channelwise, we apply the scale before the global reduction
886 - // that converts the fp32 results to fp16 (so that we avoid possible
887 - // overflow in fp16)
888 - if (threadIdx.x / 32 < thread_n_blocks / 4) {
889 - #pragma unroll
890 - for (int i = 0; i < thread_m_blocks; i++) {
891 - #pragma unroll
892 - for (int j = 0; j < 4; j++) {
893 - scale_float<scalar_t>(reinterpret_cast<float*>(&frag_c[i][j][0][0]),
894 - frag_s[j / 2][2 * (j % 2) + 0]);
895 - scale_float<scalar_t>(reinterpret_cast<float*>(&frag_c[i][j][0][2]),
896 - frag_s[j / 2][2 * (j % 2) + 0]);
897 -
898 - scale_float<scalar_t>(reinterpret_cast<float*>(&frag_c[i][j][1][0]),
899 - frag_s[j / 2][2 * (j % 2) + 1]);
900 - scale_float<scalar_t>(reinterpret_cast<float*>(&frag_c[i][j][1][2]),
901 - frag_s[j / 2][2 * (j % 2) + 1]);
902 - }
903 - }
904 - }
905 -
906 - if (slice_count > 1) { // only globally reduce if there is more than one
907 - // block in a slice
908 - barrier_acquire(&locks[slice_col], slice_idx);
909 - global_reduce(slice_idx == 0, last);
910 - barrier_release(&locks[slice_col], last);
911 - }
912 - if (last) // only the last block in a slice actually writes the result
913 - write_result();
914 - slice_row = 0;
915 - slice_col_par++;
916 - slice_col++;
917 - init_slice();
918 - if (slice_iters) {
919 - a_gl_rd = a_gl_stride * (threadIdx.x / a_gl_rd_delta_o) +
920 - (threadIdx.x % a_gl_rd_delta_o);
921 - #pragma unroll
922 - for (int i = 0; i < b_sh_wr_iters; i++)
923 - B_ptr[i] += b_sh_stride - b_gl_rd_delta_o * k_tiles;
924 - if (slice_col == 0) {
925 - #pragma unroll
926 - for (int i = 0; i < b_sh_wr_iters; i++) B_ptr[i] -= b_gl_stride;
927 - }
928 -
929 - // Update slice k/n for scales loading
930 - s_gl_rd = s_sh_stride * slice_col + threadIdx.x;
931 -
932 - start_pipes();
933 - }
934 - }
935 - }
936 -}
937 -
938 - #define __CALL_IF(NUM_BITS, THREAD_M_BLOCKS, THREAD_N_BLOCKS, \
939 - THREAD_K_BLOCKS, GROUP_BLOCKS, NUM_THREADS) \
940 - else if (num_bits == NUM_BITS && thread_m_blocks == THREAD_M_BLOCKS && \
941 - thread_n_blocks == THREAD_N_BLOCKS && \
942 - thread_k_blocks == THREAD_K_BLOCKS && \
943 - group_blocks == GROUP_BLOCKS && num_threads == NUM_THREADS) { \
944 - cudaFuncSetAttribute( \
945 - Marlin<scalar_t, NUM_BITS, NUM_THREADS, THREAD_M_BLOCKS, \
946 - THREAD_N_BLOCKS, THREAD_K_BLOCKS, pipe_stages, GROUP_BLOCKS>, \
947 - cudaFuncAttributeMaxDynamicSharedMemorySize, max_shared_mem); \
948 - Marlin<scalar_t, NUM_BITS, NUM_THREADS, THREAD_M_BLOCKS, \
949 - THREAD_N_BLOCKS, THREAD_K_BLOCKS, pipe_stages, GROUP_BLOCKS> \
950 - <<<blocks, NUM_THREADS, max_shared_mem, stream>>>( \
951 - A_ptr, B_ptr, C_ptr, s_ptr, num_groups, prob_m, prob_n, prob_k, \
952 - locks); \
953 - }
954 -
955 -typedef struct {
956 - int thread_k;
957 - int thread_n;
958 - int num_threads;
959 -} thread_config_t;
960 -
961 -typedef struct {
962 - int max_m_blocks;
963 - thread_config_t tb_cfg;
964 -} exec_config_t;
965 -
966 -thread_config_t small_batch_thread_configs[] = {
967 - // Ordered by priority
968 -
969 - // thread_k, thread_n, num_threads
970 - {128, 128, 256},
971 - {64, 128, 128},
972 - {128, 64, 128},
973 -};
974 -
975 -thread_config_t large_batch_thread_configs[] = {
976 - // Ordered by priority
977 -
978 - // thread_k, thread_n, num_threads
979 - {64, 256, 256},
980 - {64, 128, 128},
981 - {128, 64, 128},
982 -
983 -};
984 -
985 -int get_scales_cache_size(thread_config_t const& th_config, int prob_m,
986 - int prob_n, int prob_k, int num_bits,
987 - int group_size) {
988 - int tb_n = th_config.thread_n;
989 -
990 - // Get max scale groups per thread-block
991 - // Fixed for channelwise
992 - int tb_groups = 1;
993 - int tb_scales = tb_groups * tb_n * 2;
994 -
995 - return tb_scales * pipe_stages;
996 -}
997 -
998 -bool is_valid_cache_size(thread_config_t const& th_config, int max_m_blocks,
999 - int prob_m, int prob_n, int prob_k, int num_bits,
1000 - int scales_cache_size, int max_shared_mem) {
1001 - int pack_factor = 32 / num_bits;
1002 -
1003 - // Get B size
1004 - int tb_k = th_config.thread_k;
1005 - int tb_n = th_config.thread_n;
1006 -
1007 - int b_size = (tb_k * tb_n / pack_factor) * 4;
1008 -
1009 - // Get A size
1010 - int m_blocks = div_ceil(prob_m, 16);
1011 - int tb_max_m = 16;
1012 -
1013 - while (true) {
1014 - if (m_blocks >= max_m_blocks) {
1015 - tb_max_m *= max_m_blocks;
1016 - break;
1017 - }
1018 -
1019 - max_m_blocks--;
1020 - if (max_m_blocks == 0) {
1021 - TORCH_CHECK(false, "Unexpected m_blocks = ", m_blocks);
1022 - }
1023 - }
1024 -
1025 - int a_size = (tb_max_m * tb_k) * 2;
1026 -
1027 - float pipe_size = (a_size + b_size) * pipe_stages;
1028 -
1029 - TORCH_CHECK(max_shared_mem / 2 > scales_cache_size); // Sanity
1030 -
1031 - return pipe_size < 0.95f * (max_shared_mem - scales_cache_size);
1032 -}
1033 -
1034 -bool is_valid_config(thread_config_t const& th_config, int max_m_blocks,
1035 - int prob_m, int prob_n, int prob_k, int num_bits,
1036 - int group_size, int max_shared_mem) {
1037 - // Sanity
1038 - if (th_config.thread_k == -1 || th_config.thread_n == -1 ||
1039 - th_config.num_threads == -1) {
1040 - return false;
1041 - }
1042 -
1043 - // Verify K/N are divisible by thread K/N
1044 - if (prob_k % th_config.thread_k != 0 || prob_n % th_config.thread_n != 0) {
1045 - return false;
1046 - }
1047 -
1048 - // Verify min for thread K/N
1049 - if (th_config.thread_n < min_thread_n || th_config.thread_k < min_thread_k) {
1050 - return false;
1051 - }
1052 -
1053 - // num_threads must be at least 128 (= 4 warps)
1054 - if (th_config.num_threads < 128) {
1055 - return false;
1056 - }
1057 -
1058 - // Determine cache for scales
1059 - int scales_cache_size = get_scales_cache_size(th_config, prob_m, prob_n,
1060 - prob_k, num_bits, group_size);
1061 -
1062 - // Check that pipeline fits into cache
1063 - if (!is_valid_cache_size(th_config, max_m_blocks, prob_m, prob_n, prob_k,
1064 - num_bits, scales_cache_size, max_shared_mem)) {
1065 - return false;
1066 - }
1067 -
1068 - return true;
1069 -}
1070 -
1071 -exec_config_t determine_thread_config(int prob_m, int prob_n, int prob_k,
1072 - int num_bits, int group_size,
1073 - int max_shared_mem) {
1074 - int max_m_blocks = 4;
1075 - while (max_m_blocks > 0) {
1076 - if (prob_m <= 16) {
1077 - for (auto th_config : small_batch_thread_configs) {
1078 - if (is_valid_config(th_config, max_m_blocks, prob_m, prob_n, prob_k,
1079 - num_bits, group_size, max_shared_mem)) {
1080 - return exec_config_t{max_m_blocks, th_config};
1081 - }
1082 - }
1083 - } else {
1084 - for (auto th_config : large_batch_thread_configs) {
1085 - if (is_valid_config(th_config, max_m_blocks, prob_m, prob_n, prob_k,
1086 - num_bits, group_size, max_shared_mem)) {
1087 - return exec_config_t{max_m_blocks, th_config};
1088 - }
1089 - }
1090 - }
1091 -
1092 - max_m_blocks--; // Process less M blocks per invocation to reduce cache
1093 - // usage
1094 - }
1095 -
1096 - return exec_config_t{0, {-1, -1, -1}};
1097 -}
1098 -
1099 - #define CALL_IF(NUM_BITS, N_BLOCKS, K_BLOCKS, NUM_THREADS) \
1100 - __CALL_IF(NUM_BITS, 1, N_BLOCKS, K_BLOCKS, -1, NUM_THREADS) \
1101 - __CALL_IF(NUM_BITS, 2, N_BLOCKS, K_BLOCKS, -1, NUM_THREADS) \
1102 - __CALL_IF(NUM_BITS, 3, N_BLOCKS, K_BLOCKS, -1, NUM_THREADS) \
1103 - __CALL_IF(NUM_BITS, 4, N_BLOCKS, K_BLOCKS, -1, NUM_THREADS)
1104 -
1105 -template <typename scalar_t>
1106 -void marlin_mm_f16i4(const void* A, const void* B, void* C, void* s, int prob_m,
1107 - int prob_n, int prob_k, void* workspace, int num_bits,
1108 - int num_groups, int group_size, int dev,
1109 - cudaStream_t stream, int thread_k, int thread_n, int sms,
1110 - int max_par) {
1111 - TORCH_CHECK(num_bits == 8, "num_bits must be 8. Got = ", num_bits);
1112 - TORCH_CHECK(prob_m > 0 && prob_n > 0 && prob_k > 0, "Invalid MNK = [", prob_m,
1113 - ", ", prob_n, ", ", prob_k, "]");
1114 -
1115 - int tot_m = prob_m;
1116 - int tot_m_blocks = div_ceil(tot_m, 16);
1117 - int pad = 16 * tot_m_blocks - tot_m;
1118 -
1119 - if (sms == -1) {
1120 - cudaDeviceGetAttribute(&sms, cudaDevAttrMultiProcessorCount, dev);
1121 - }
1122 -
1123 - int max_shared_mem = 0;
1124 - cudaDeviceGetAttribute(&max_shared_mem,
1125 - cudaDevAttrMaxSharedMemoryPerBlockOptin, dev);
1126 - TORCH_CHECK(max_shared_mem > 0);
1127 -
1128 - // Set thread config
1129 - exec_config_t exec_cfg;
1130 - if (thread_k != -1 && thread_n != -1) {
1131 - // User-defined config
1132 - exec_cfg =
1133 - exec_config_t{4, thread_config_t{thread_k, thread_n, default_threads}};
1134 - } else {
1135 - // Auto config
1136 - exec_cfg = determine_thread_config(prob_m, prob_n, prob_k, num_bits,
1137 - group_size, max_shared_mem);
1138 - }
1139 -
1140 - TORCH_CHECK(
1141 - exec_cfg.max_m_blocks > 0 &&
1142 - is_valid_config(exec_cfg.tb_cfg, exec_cfg.max_m_blocks, prob_m,
1143 - prob_n, prob_k, num_bits, group_size, max_shared_mem),
1144 - "Invalid thread config: max_m_blocks = ", exec_cfg.max_m_blocks,
1145 - ", thread_k = ", exec_cfg.tb_cfg.thread_k,
1146 - ", thread_n = ", exec_cfg.tb_cfg.thread_n,
1147 - ", num_threads = ", exec_cfg.tb_cfg.num_threads, " for MKN = [", prob_m,
1148 - ", ", prob_k, ", ", prob_n, "] and num_bits = ", num_bits,
1149 - ", group_size = ", group_size, ", max_shared_mem = ", max_shared_mem);
1150 -
1151 - int num_threads = exec_cfg.tb_cfg.num_threads;
1152 - thread_k = exec_cfg.tb_cfg.thread_k;
1153 - thread_n = exec_cfg.tb_cfg.thread_n;
1154 -
1155 - int thread_k_blocks = thread_k / 16;
1156 - int thread_n_blocks = thread_n / 16;
1157 -
1158 - int blocks = sms;
1159 -
1160 - TORCH_CHECK(prob_n % thread_n == 0, "prob_n = ", prob_n,
1161 - " is not divisible by thread_n = ", thread_n);
1162 - TORCH_CHECK(prob_k % thread_k == 0, "prob_k = ", prob_k,
1163 - " is not divisible by thread_k = ", thread_k);
1164 -
1165 - int group_blocks = -1;
1166 -
1167 - const int4* A_ptr = (const int4*)A;
1168 - const int4* B_ptr = (const int4*)B;
1169 - int4* C_ptr = (int4*)C;
1170 - const int4* s_ptr = (const int4*)s;
1171 -
1172 - int* locks = (int*)workspace;
1173 -
1174 - // Main loop
1175 - for (int i = 0; i < tot_m_blocks; i += exec_cfg.max_m_blocks) {
1176 - int thread_m_blocks = tot_m_blocks - i;
1177 - prob_m = tot_m - 16 * i;
1178 - int par = 1;
1179 - if (thread_m_blocks > exec_cfg.max_m_blocks) {
1180 - // Note that parallel > 1 currently only works for inputs without any
1181 - // padding
1182 - par = (16 * thread_m_blocks - pad) / (16 * exec_cfg.max_m_blocks);
1183 - if (par > max_par) par = max_par;
1184 - prob_m = (16 * exec_cfg.max_m_blocks) * par;
1185 - i += exec_cfg.max_m_blocks * (par - 1);
1186 - thread_m_blocks = exec_cfg.max_m_blocks;
1187 - }
1188 -
1189 - // Define kernel configurations
1190 - if (false) {
1191 - }
1192 - CALL_IF(8, 32, 2, 256)
1193 - CALL_IF(8, 16, 4, 256)
1194 - CALL_IF(8, 8, 8, 256)
1195 - CALL_IF(8, 8, 4, 128)
1196 - CALL_IF(8, 4, 8, 128)
1197 - else {
1198 - TORCH_CHECK(false, "Unsupported shapes: MNK = [" + str(prob_m) + ", " +
1199 - str(prob_n) + ", " + str(prob_k) + "]" +
1200 - ", num_groups = " + str(num_groups) +
1201 - ", group_size = " + str(group_size) +
1202 - ", thread_m_blocks = " + str(thread_m_blocks) +
1203 - ", thread_n_blocks = " + str(thread_n_blocks) +
1204 - ", thread_k_blocks = " + str(thread_k_blocks));
1205 - }
1206 -
1207 - A_ptr += 16 * thread_m_blocks * (prob_k / 8) * par;
1208 - C_ptr += 16 * thread_m_blocks * (prob_n / 8) * par;
1209 - }
1210 -}
1211 -
1212 -} // namespace fp8_marlin
1213 -
1214 -torch::Tensor fp8_marlin_gemm(torch::Tensor& a, torch::Tensor& b_q_weight,
1215 - torch::Tensor& b_scales, torch::Tensor& workspace,
1216 - int64_t num_bits, int64_t size_m, int64_t size_n,
1217 - int64_t size_k) {
1218 - // Verify num_bits
1219 - TORCH_CHECK(num_bits == 8, "num_bits must be 8. Got = ", num_bits);
1220 - int pack_factor = 32 / num_bits;
1221 -
1222 - // Verify A
1223 - TORCH_CHECK(a.size(0) == size_m, "Shape mismatch: a.size(0) = ", a.size(0),
1224 - ", size_m = ", size_m);
1225 - TORCH_CHECK(a.size(1) == size_k, "Shape mismatch: a.size(1) = ", a.size(1),
1226 - ", size_k = ", size_k);
1227 -
1228 - // Verify B
1229 - TORCH_CHECK(size_k % marlin::tile_size == 0, "size_k = ", size_k,
1230 - " is not divisible by tile_size = ", marlin::tile_size);
1231 - TORCH_CHECK((size_k / marlin::tile_size) == b_q_weight.size(0),
1232 - "Shape mismatch: b_q_weight.size(0) = ", b_q_weight.size(0),
1233 - ", size_k = ", size_k, ", tile_size = ", marlin::tile_size);
1234 - TORCH_CHECK(b_q_weight.size(1) % marlin::tile_size == 0,
1235 - "b_q_weight.size(1) = ", b_q_weight.size(1),
1236 - " is not divisible by tile_size = ", marlin::tile_size);
1237 - int actual_size_n = (b_q_weight.size(1) / marlin::tile_size) * pack_factor;
1238 - TORCH_CHECK(size_n == actual_size_n, "size_n = ", size_n,
1239 - ", actual_size_n = ", actual_size_n);
1240 -
1241 - // Verify device and strides
1242 - TORCH_CHECK(a.device().is_cuda(), "A is not on GPU");
1243 - TORCH_CHECK(a.is_contiguous(), "A is not contiguous");
1244 -
1245 - TORCH_CHECK(b_q_weight.device().is_cuda(), "b_q_weight is not on GPU");
1246 - TORCH_CHECK(b_q_weight.is_contiguous(), "b_q_weight is not contiguous");
1247 -
1248 - TORCH_CHECK(b_scales.device().is_cuda(), "b_scales is not on GPU");
1249 - TORCH_CHECK(b_scales.is_contiguous(), "b_scales is not contiguous");
1250 -
1251 - // Alloc buffers
1252 - const at::cuda::OptionalCUDAGuard device_guard(device_of(a));
1253 - auto options = torch::TensorOptions().dtype(a.dtype()).device(a.device());
1254 - torch::Tensor c = torch::empty({size_m, size_n}, options);
1255 -
1256 - // thread_k: `k` size of a thread_tile in `weights` (can usually be left as
1257 - // auto -1)
1258 - int thread_k = -1;
1259 - // thread_n: `n` size of a thread_tile in `weights` (can usually be left as
1260 - // auto -1)
1261 - int thread_n = -1;
1262 - // sms: number of SMs to use for the kernel (can usually be left as auto -1)
1263 - int sms = -1;
1264 -
1265 - // Detect groupsize and act_order
1266 - int num_groups = -1;
1267 - int group_size = -1;
1268 -
1269 - int b_rank = b_scales.sizes().size();
1270 - TORCH_CHECK(b_rank == 2, "b_scales rank = ", b_rank, " is not 2");
1271 - TORCH_CHECK(b_scales.size(1) == size_n, "b_scales dim 1 = ", b_scales.size(1),
1272 - " is not size_n = ", size_n);
1273 - // Channelwise only for FP8
1274 - TORCH_CHECK(b_scales.size(0) == 1)
1275 - num_groups = b_scales.size(0);
1276 -
1277 - // Verify workspace size
1278 - TORCH_CHECK(size_n % marlin::min_thread_n == 0, "size_n = ", size_n,
1279 - ", is not divisible by min_thread_n = ", marlin::min_thread_n);
1280 - int min_workspace_size = (size_n / marlin::min_thread_n) * marlin::max_par;
1281 - TORCH_CHECK(workspace.numel() >= min_workspace_size,
1282 - "workspace.numel = ", workspace.numel(),
1283 - " is below min_workspace_size = ", min_workspace_size);
1284 -
1285 - int dev = a.get_device();
1286 - if (a.scalar_type() == at::ScalarType::Half) {
1287 - fp8_marlin::marlin_mm_f16i4<half>(
1288 - a.data_ptr<at::Half>(), b_q_weight.data_ptr(), c.data_ptr<at::Half>(),
1289 - b_scales.data_ptr<at::Half>(), size_m, size_n, size_k,
1290 - workspace.data_ptr(), num_bits, num_groups, group_size, dev,
1291 - at::cuda::getCurrentCUDAStream(dev), thread_k, thread_n, sms,
1292 - marlin::max_par);
1293 - } else if (a.scalar_type() == at::ScalarType::BFloat16) {
1294 - fp8_marlin::marlin_mm_f16i4<nv_bfloat16>(
1295 - a.data_ptr<at::BFloat16>(), b_q_weight.data_ptr(),
1296 - c.data_ptr<at::BFloat16>(), b_scales.data_ptr<at::BFloat16>(), size_m,
1297 - size_n, size_k, workspace.data_ptr(), num_bits, num_groups, group_size,
1298 - dev, at::cuda::getCurrentCUDAStream(dev), thread_k, thread_n, sms,
1299 - marlin::max_par);
1300 - } else {
1301 - TORCH_CHECK(false, "fp8_marlin_gemm only supports bfloat16 and float16");
1302 - }
1303 -
1304 - return c;
1305 -}
1306 -
1307 -#endif
1308 -
1309 -TORCH_LIBRARY_IMPL_EXPAND(TORCH_EXTENSION_NAME, CUDA, m) {
1310 - m.impl("fp8_marlin_gemm", &fp8_marlin_gemm);
1311 -}
\ No newline at end of file