main
cu 818 lines 32.3 KB
Raw
1 #include <torch/all.h>
2 #include <ATen/cuda/CUDAContext.h>
3 #include <c10/cuda/CUDAGuard.h>
4
5 #include <cuda_runtime.h>
6 #include <cuda_fp16.h>
7 #include <cuda_bf16.h>
8
9 #include <stdexcept>
10 #include <algorithm>
11
12 #include "../cuda_compat.h"
13 #include "dispatch_utils.h"
14
15 // Combined RDNA macro (gfx11 + gfx12) - both use 32-wide wavefronts
16 #if defined(__GFX11__) || defined(__GFX12__)
17 #define __HIP__GFX1X__
18 #endif
19
20 #define LDS_SIZE 64 * 1024
21
22 static constexpr int get_lds_size_int4() { return 64 * 1024; }
23
24 #if defined(NDEBUG)
25 #undef NDEBUG
26 #include <assert.h>
27 #define UNREACHABLE_CODE assert(false);
28 #define NDEBUG
29 #else
30 #define UNREACHABLE_CODE assert(false);
31 #endif
32
33 template <typename T>
34 __device__ __forceinline__ float __s2float(T v);
35
36 template <>
37 __device__ __forceinline__ float __s2float(half v) {
38 return __half2float(v);
39 }
40
41 template <>
42 __device__ __forceinline__ float __s2float(__hip_bfloat16 v) {
43 return __bfloat162float(v);
44 }
45
46 template <typename T>
47 __device__ __forceinline__ T __float2s(float v);
48
49 template <>
50 __device__ __forceinline__ half __float2s(float v) {
51 return __float2half(v);
52 }
53
54 template <>
55 __device__ __forceinline__ __hip_bfloat16 __float2s(float v) {
56 return __float2bfloat16(v);
57 }
58
59 template <typename T>
60 __device__ __forceinline__ T loadnt(T* addr) {
61 return __builtin_nontemporal_load(addr);
62 }
63
64 template <typename T>
65 struct scalar {};
66
67 template <>
68 struct scalar<c10::Half> {
69 using type = half;
70 };
71
72 template <>
73 struct scalar<c10::BFloat16> {
74 using type = __hip_bfloat16;
75 };
76
77 #define DOT2C(V0, V2, V3) \
78 if constexpr (std::is_same_v<scalar_t, half>) { \
79 V0 = __builtin_amdgcn_fdot2(*((half2*)(&(V2))), *((half2*)(&(V3))), V0, \
80 false); \
81 } else if constexpr (std::is_same_v<scalar_t, __hip_bfloat16>) { \
82 typedef short __attribute__((ext_vector_type(2))) bf16x2_t; \
83 V0 = __builtin_amdgcn_fdot2_f32_bf16(*((bf16x2_t*)(&(V2))), \
84 *((bf16x2_t*)(&(V3))), V0, false); \
85 }
86
87 // DPP-based wave32 reduction for GFX1X (matches skinny_gemms.cu).
88 // row_shr within 16-lane rows, then cross-row __shfl_xor.
89 // Result accumulates in lane (THRDS - 1).
90 #if defined(__HIP__GFX1X__)
91 #define REDUCE_SUM_DPP_WAVE32(val) \
92 do { \
93 val += __builtin_amdgcn_mov_dpp(val, 0x118, 0xf, 0xf, 1); /* shr8 */ \
94 val += __builtin_amdgcn_mov_dpp(val, 0x114, 0xf, 0xf, 1); /* shr4 */ \
95 val += __builtin_amdgcn_mov_dpp(val, 0x112, 0xf, 0xf, 1); /* shr2 */ \
96 val += __builtin_amdgcn_mov_dpp(val, 0x111, 0xf, 0xf, 1); /* shr1 */ \
97 val += __shfl_xor(val, 16); \
98 } while (0)
99 #endif
100
101 __device__ inline unsigned int min__(uint32_t a, uint32_t b) {
102 return min(a, b);
103 }
104
105 // Reads row `row` of packed zero points [M/8, num_groups] uint32.
106 //
107 // The parameters must be signed and the indexing must use shift/mask: with
108 // unsigned parameters and row/8, row%8, the (A_CHUNK=32, UNRL=8)
109 // instantiation needs 256 VGPRs and 68 bytes of scratch instead of 239.
110 __device__ __forceinline__ uint32_t zp_nibble(const uint32_t* zp_packed,
111 const int row, const int group,
112 const int num_groups) {
113 const uint32_t w = zp_packed[(row >> 3) * num_groups + group];
114 return (w >> (4 * (row & 7))) & 0xFu;
115 }
116
117 // W4A16 skinny GEMM kernel: packed int4 weights, fp16/bf16 activations
118 // Targets the "sml" case where activations fit in LDS.
119 // A_CHUNK: number of K-elements processed per thread per step.
120 // Weights are packed as 2 int4 values per byte: [M, K/2] bytes.
121 // GROUP_SIZE: 0 = per-channel scale [M], >0 = per-group scale [M,
122 // K/GROUP_SIZE].
123 // Requires GROUP_SIZE % A_CHUNK == 0 when GROUP_SIZE > 0.
124 #if defined(__HIP__GFX1X__)
125 template <typename scalar_t, int THRDS, int YTILE, int WvPrGrp, int A_CHUNK,
126 int UNRL, int N, int GROUP_SIZE = 0, bool HAS_ZERO_POINTS = false>
127 __global__ void __launch_bounds__(WvPrGrp* THRDS)
128 wvSplitK_int4_hf_sml_(const int K, const int M, const int Bx, const int By,
129 const uint8_t* B_packed,
130 const scalar_t* __restrict__ A, const scalar_t* scale,
131 const uint32_t* zero_points,
132 const scalar_t* __restrict__ BIAS, scalar_t* C,
133 const int _WvPrGrp, const int CuCount) {
134 constexpr int max_lds_len = LDS_SIZE / 2;
135 const int K_packed = K / 2;
136
137 union bigTypeA {
138 scalar_t h[A_CHUNK];
139 float f[A_CHUNK / 2];
140 };
141
142 union bigTypeW {
143 uint8_t b[A_CHUNK / 2];
144 uint32_t u32[A_CHUNK / 8];
145 float f[A_CHUNK / 8];
146 };
147
148 __shared__ scalar_t s[max_lds_len];
149
150 for (uint32_t k = 0; k < min__(K * N, max_lds_len);
151 k += THRDS * WvPrGrp * A_CHUNK) {
152 uint32_t k_in = k + ((threadIdx.y * THRDS + threadIdx.x) * A_CHUNK);
153
154 if (k_in >= min__(K * N, max_lds_len)) break;
155
156 *((bigTypeA*)(&s[k_in])) = *((bigTypeA*)(&A[k_in]));
157 }
158 __syncthreads();
159
160 if (threadIdx.y >= _WvPrGrp) return;
161
162 uint32_t m = (blockIdx.x * _WvPrGrp + (threadIdx.y % _WvPrGrp)) * YTILE;
163
164 // For per-group, precompute num_groups and scale stride
165 [[maybe_unused]] const int num_groups =
166 (GROUP_SIZE > 0) ? (K / GROUP_SIZE) : 0;
167
168 float sum[N][YTILE];
169
170 while (m < M) {
171 for (int i = 0; i < YTILE; i++)
172 for (int n = 0; n < N; n++) sum[n][i] = 0;
173
174 bigTypeA bigA[N][UNRL];
175 bigTypeW bigB[YTILE][UNRL];
176
177 for (uint32_t k1 = 0; k1 < K; k1 += THRDS * A_CHUNK * UNRL) {
178 #pragma unroll
179 for (uint32_t k2 = 0; k2 < UNRL; k2++) {
180 uint32_t k = k1 + k2 * THRDS * A_CHUNK;
181 uint32_t k_ = k + threadIdx.x * A_CHUNK;
182 if (k_ >= K) break;
183
184 const uint8_t* B_ = &B_packed[(m + 0) * K_packed + k_ / 2];
185 for (int y = 0; y < YTILE; y++) {
186 const float* src = (const float*)(&B_[y * K_packed]);
187 #pragma unroll
188 for (int i = 0; i < A_CHUNK / 8; i++)
189 bigB[y][k2].f[i] = loadnt((float*)&src[i]);
190 }
191 }
192
193 #pragma unroll
194 for (uint32_t k2 = 0; k2 < UNRL; k2++) {
195 uint32_t k = k1 + k2 * THRDS * A_CHUNK;
196 uint32_t k_ = k + threadIdx.x * A_CHUNK;
197 if (k_ >= K) break;
198
199 for (int n = 0; n < N; n++) {
200 bigA[n][k2] = *((const bigTypeA*)(&(s[k_ + K * n])));
201 }
202 }
203
204 #pragma unroll
205 for (uint32_t k2 = 0; k2 < UNRL; k2++) {
206 uint32_t k = k1 + k2 * THRDS * A_CHUNK;
207 uint32_t k_ = k + threadIdx.x * A_CHUNK;
208 if (k_ >= K) break;
209
210 #pragma unroll
211 for (uint32_t n = 0; n < N; n++) {
212 #pragma unroll
213 for (int y = 0; y < YTILE; y++) {
214 bigTypeA cvtB;
215
216 if constexpr (std::is_same_v<scalar_t, half>) {
217 constexpr uint32_t FP16_MAGIC = 0x64006400u;
218 // When HAS_ZERO_POINTS, store raw nibble values;
219 // the zero-point subtraction below handles the full shift.
220 // When symmetric, bake -8 into the constants.
221 constexpr uint32_t BIAS_LO =
222 HAS_ZERO_POINTS ? 0x64006400u : 0x64086408u;
223 constexpr uint32_t SCALE16 = 0x2C002C00u;
224 constexpr uint32_t BIAS_HI =
225 HAS_ZERO_POINTS ? 0xD400D400u : 0xD480D480u;
226 #pragma unroll
227 for (uint32_t w = 0; w < A_CHUNK / 8; w++) {
228 uint32_t qa = bigB[y][k2].u32[w];
229 uint32_t lo0 = (qa & 0x000F000Fu) | FP16_MAGIC;
230 uint32_t hi0 = (qa & 0x00F000F0u) | FP16_MAGIC;
231 qa >>= 8;
232 uint32_t lo1 = (qa & 0x000F000Fu) | FP16_MAGIC;
233 uint32_t hi1 = (qa & 0x00F000F0u) | FP16_MAGIC;
234
235 *(half2*)&cvtB.f[w * 4 + 0] =
236 __hsub2(*(half2*)&lo0, *(const half2*)&BIAS_LO);
237 *(half2*)&cvtB.f[w * 4 + 1] =
238 __hfma2(*(half2*)&hi0, *(const half2*)&SCALE16,
239 *(const half2*)&BIAS_HI);
240 *(half2*)&cvtB.f[w * 4 + 2] =
241 __hsub2(*(half2*)&lo1, *(const half2*)&BIAS_LO);
242 *(half2*)&cvtB.f[w * 4 + 3] =
243 __hfma2(*(half2*)&hi1, *(const half2*)&SCALE16,
244 *(const half2*)&BIAS_HI);
245 }
246 } else {
247 // bf16 path: marlin-style magic-number trick.
248 // Skip the expensive bias subtraction (avoids fp32 round-trip
249 // on gfx1151); use magic values directly in DOT2C and correct
250 // the accumulator with bias * sum(activations) afterward.
251 constexpr uint32_t BF16_MAGIC = 0x43004300u;
252 #pragma unroll
253 for (uint32_t w = 0; w < A_CHUNK / 8; w++) {
254 uint32_t qa = bigB[y][k2].u32[w];
255 *(uint32_t*)&cvtB.f[w * 4 + 0] =
256 (qa & 0x000F000Fu) | BF16_MAGIC;
257 qa >>= 4;
258 *(uint32_t*)&cvtB.f[w * 4 + 1] =
259 (qa & 0x000F000Fu) | BF16_MAGIC;
260 qa >>= 4;
261 *(uint32_t*)&cvtB.f[w * 4 + 2] =
262 (qa & 0x000F000Fu) | BF16_MAGIC;
263 qa >>= 4;
264 *(uint32_t*)&cvtB.f[w * 4 + 3] =
265 (qa & 0x000F000Fu) | BF16_MAGIC;
266 }
267 }
268
269 if constexpr (!std::is_same_v<scalar_t, __hip_bfloat16>) {
270 if constexpr (HAS_ZERO_POINTS && GROUP_SIZE > 0) {
271 uint32_t group_idx = k_ / GROUP_SIZE;
272 scalar_t zp = __float2s<scalar_t>(static_cast<float>(
273 zp_nibble(zero_points, m + y, group_idx, num_groups)));
274 #pragma unroll
275 for (uint32_t b = 0; b < A_CHUNK; b++) {
276 cvtB.h[b] = cvtB.h[b] - zp;
277 }
278 }
279 }
280
281 if constexpr (GROUP_SIZE > 0) {
282 float partial = 0;
283 #pragma unroll
284 for (uint32_t b = 0; b < A_CHUNK / 2; b++) {
285 DOT2C(partial, bigA[n][k2].f[b], cvtB.f[b])
286 }
287 if constexpr (std::is_same_v<scalar_t, __hip_bfloat16>) {
288 constexpr uint32_t BF16_ONES = 0x3F803F80u;
289 float act_sum = 0.0f;
290 #pragma unroll
291 for (uint32_t b = 0; b < A_CHUNK / 2; b++) {
292 DOT2C(act_sum, bigA[n][k2].f[b], *(const float*)&BF16_ONES)
293 }
294 if constexpr (HAS_ZERO_POINTS) {
295 uint32_t group_idx_zp = k_ / GROUP_SIZE;
296 float zp_f = static_cast<float>(
297 zp_nibble(zero_points, m + y, group_idx_zp, num_groups));
298 partial -= (128.0f + zp_f) * act_sum;
299 } else {
300 partial -= 136.0f * act_sum;
301 }
302 }
303 uint32_t group_idx = k_ / GROUP_SIZE;
304 sum[n][y] +=
305 partial * __s2float(scale[(m + y) * num_groups + group_idx]);
306 } else {
307 #pragma unroll
308 for (uint32_t b = 0; b < A_CHUNK / 2; b++) {
309 DOT2C(sum[n][y], bigA[n][k2].f[b], cvtB.f[b])
310 }
311 if constexpr (std::is_same_v<scalar_t, __hip_bfloat16>) {
312 constexpr float BIAS_VAL = HAS_ZERO_POINTS ? 128.0f : 136.0f;
313 constexpr uint32_t BF16_ONES = 0x3F803F80u;
314 float act_sum = 0.0f;
315 #pragma unroll
316 for (uint32_t b = 0; b < A_CHUNK / 2; b++) {
317 DOT2C(act_sum, bigA[n][k2].f[b], *(const float*)&BF16_ONES)
318 }
319 sum[n][y] -= BIAS_VAL * act_sum;
320 }
321 }
322 }
323 }
324 }
325 }
326
327 for (int n = 0; n < N; n++)
328 for (int y = 0; y < YTILE; y++) REDUCE_SUM_DPP_WAVE32(sum[n][y]);
329
330 if (threadIdx.x == (THRDS - 1)) {
331 for (int n = 0; n < N; n++) {
332 for (int i = 0; i < YTILE; i++) {
333 if constexpr (GROUP_SIZE == 0) {
334 sum[n][i] *= __s2float(scale[m + i]);
335 }
336 if (BIAS) sum[n][i] += __s2float(BIAS[(m + i) % Bx + (n % By) * M]);
337 C[m + i + n * M] = __float2s<scalar_t>(sum[n][i]);
338 }
339 }
340 }
341 m += CuCount * _WvPrGrp * YTILE;
342 }
343 }
344 #else // !defined(__HIP__GFX1X__)
345 template <typename scalar_t, int THRDS, int YTILE, int WvPrGrp, int A_CHUNK,
346 int UNRL, int N, int GROUP_SIZE = 0, bool HAS_ZERO_POINTS = false>
347 __global__ void wvSplitK_int4_hf_sml_(const int K, const int M, const int Bx,
348 const int By, const uint8_t* B_packed,
349 const scalar_t* __restrict__ A,
350 const scalar_t* scale,
351 const uint32_t* zero_points,
352 const scalar_t* __restrict__ BIAS,
353 scalar_t* C, const int _WvPrGrp,
354 const int CuCount) {
355 UNREACHABLE_CODE
356 }
357 #endif // defined(__HIP__GFX1X__)
358
359 // W4A16 skinny GEMM "medium" kernel: activation matrix marginally exceeds LDS.
360 // Loads as much of A into LDS as fits; overflowing rows fall back to global
361 // memory. Also handles M not divisible by YTILE via commitColumn tracking.
362 #if defined(__HIP__GFX1X__)
363 template <typename scalar_t, int THRDS, int YTILE, int WvPrGrp, int A_CHUNK,
364 int UNRL, int N, int GROUP_SIZE = 0, bool HAS_ZERO_POINTS = false>
365 __global__ void __launch_bounds__(WvPrGrp* THRDS)
366 wvSplitK_int4_hf_(const int K, const int M, const int Bx, const int By,
367 const uint8_t* B_packed, const scalar_t* __restrict__ A,
368 const scalar_t* scale, const uint32_t* zero_points,
369 const scalar_t* __restrict__ BIAS, scalar_t* C,
370 const int _WvPrGrp, const int CuCount) {
371 constexpr int max_lds_len = LDS_SIZE / 2;
372 const int K_packed = K / 2;
373
374 union bigTypeA {
375 scalar_t h[A_CHUNK];
376 float f[A_CHUNK / 2];
377 };
378
379 union bigTypeW {
380 uint8_t b[A_CHUNK / 2];
381 uint32_t u32[A_CHUNK / 8];
382 float f[A_CHUNK / 8];
383 };
384
385 __shared__ scalar_t s[max_lds_len];
386
387 uint32_t commitColumn[YTILE];
388 for (uint32_t i = 0; i < YTILE; i++) {
389 commitColumn[i] = 1;
390 }
391
392 uint32_t m = (blockIdx.x * _WvPrGrp + (threadIdx.y % _WvPrGrp)) * YTILE;
393
394 if (m < M && (m + YTILE) >= M) {
395 uint32_t startColumn = M - YTILE;
396 for (uint32_t i = 0; i < (m - startColumn); i++) {
397 commitColumn[i] = 0;
398 }
399 m = startColumn;
400 }
401
402 for (uint32_t k = 0; k < min__(K * N, max_lds_len);
403 k += THRDS * WvPrGrp * A_CHUNK) {
404 uint32_t k_in = k + ((threadIdx.y * THRDS + threadIdx.x) * A_CHUNK);
405
406 if (k_in >= min__(K * N, max_lds_len)) break;
407
408 *((bigTypeA*)(&s[k_in])) = *((bigTypeA*)(&A[k_in]));
409 }
410 __syncthreads();
411
412 if (threadIdx.y >= _WvPrGrp) return;
413
414 [[maybe_unused]] const int num_groups =
415 (GROUP_SIZE > 0) ? (K / GROUP_SIZE) : 0;
416
417 float sum[N][YTILE];
418
419 while (m < M) {
420 for (int i = 0; i < YTILE; i++)
421 for (int n = 0; n < N; n++) sum[n][i] = 0;
422
423 bigTypeA bigA[N][UNRL];
424 bigTypeW bigB[YTILE][UNRL];
425
426 for (uint32_t k1 = 0; k1 < K; k1 += THRDS * A_CHUNK * UNRL) {
427 #pragma unroll
428 for (uint32_t k2 = 0; k2 < UNRL; k2++) {
429 uint32_t k = k1 + k2 * THRDS * A_CHUNK;
430 uint32_t k_ = k + threadIdx.x * A_CHUNK;
431 if (k_ >= K) break;
432
433 const uint8_t* B_ = &B_packed[(m + 0) * K_packed + k_ / 2];
434 for (int y = 0; y < YTILE; y++) {
435 const float* src = (const float*)(&B_[y * K_packed]);
436 #pragma unroll
437 for (int i = 0; i < A_CHUNK / 8; i++)
438 bigB[y][k2].f[i] = loadnt((float*)&src[i]);
439 }
440 }
441
442 #pragma unroll
443 for (uint32_t k2 = 0; k2 < UNRL; k2++) {
444 uint32_t k = k1 + k2 * THRDS * A_CHUNK;
445 uint32_t k_ = k + threadIdx.x * A_CHUNK;
446 if (k_ >= K) break;
447
448 for (int n = 0; n < N; n++) {
449 if (k_ + K * n < max_lds_len)
450 bigA[n][k2] = *((const bigTypeA*)(&(s[k_ + K * n])));
451 else
452 bigA[n][k2] = *((const bigTypeA*)(&(A[k_ + K * n])));
453 }
454 }
455
456 #pragma unroll
457 for (uint32_t k2 = 0; k2 < UNRL; k2++) {
458 uint32_t k = k1 + k2 * THRDS * A_CHUNK;
459 uint32_t k_ = k + threadIdx.x * A_CHUNK;
460 if (k_ >= K) break;
461
462 #pragma unroll
463 for (uint32_t n = 0; n < N; n++) {
464 #pragma unroll
465 for (int y = 0; y < YTILE; y++) {
466 bigTypeA cvtB;
467
468 if constexpr (std::is_same_v<scalar_t, half>) {
469 constexpr uint32_t FP16_MAGIC = 0x64006400u;
470 // When HAS_ZERO_POINTS, store raw nibble values;
471 // the zero-point subtraction below handles the full shift.
472 // When symmetric, bake -8 into the constants.
473 constexpr uint32_t BIAS_LO =
474 HAS_ZERO_POINTS ? 0x64006400u : 0x64086408u;
475 constexpr uint32_t SCALE16 = 0x2C002C00u;
476 constexpr uint32_t BIAS_HI =
477 HAS_ZERO_POINTS ? 0xD400D400u : 0xD480D480u;
478 #pragma unroll
479 for (uint32_t w = 0; w < A_CHUNK / 8; w++) {
480 uint32_t qa = bigB[y][k2].u32[w];
481 uint32_t lo0 = (qa & 0x000F000Fu) | FP16_MAGIC;
482 uint32_t hi0 = (qa & 0x00F000F0u) | FP16_MAGIC;
483 qa >>= 8;
484 uint32_t lo1 = (qa & 0x000F000Fu) | FP16_MAGIC;
485 uint32_t hi1 = (qa & 0x00F000F0u) | FP16_MAGIC;
486
487 *(half2*)&cvtB.f[w * 4 + 0] =
488 __hsub2(*(half2*)&lo0, *(const half2*)&BIAS_LO);
489 *(half2*)&cvtB.f[w * 4 + 1] =
490 __hfma2(*(half2*)&hi0, *(const half2*)&SCALE16,
491 *(const half2*)&BIAS_HI);
492 *(half2*)&cvtB.f[w * 4 + 2] =
493 __hsub2(*(half2*)&lo1, *(const half2*)&BIAS_LO);
494 *(half2*)&cvtB.f[w * 4 + 3] =
495 __hfma2(*(half2*)&hi1, *(const half2*)&SCALE16,
496 *(const half2*)&BIAS_HI);
497 }
498 } else {
499 // bf16 path: marlin-style magic-number trick.
500 // Skip the expensive bias subtraction (avoids fp32 round-trip
501 // on gfx1151); use magic values directly in DOT2C and correct
502 // the accumulator with bias * sum(activations) afterward.
503 constexpr uint32_t BF16_MAGIC = 0x43004300u;
504 #pragma unroll
505 for (uint32_t w = 0; w < A_CHUNK / 8; w++) {
506 uint32_t qa = bigB[y][k2].u32[w];
507 *(uint32_t*)&cvtB.f[w * 4 + 0] =
508 (qa & 0x000F000Fu) | BF16_MAGIC;
509 qa >>= 4;
510 *(uint32_t*)&cvtB.f[w * 4 + 1] =
511 (qa & 0x000F000Fu) | BF16_MAGIC;
512 qa >>= 4;
513 *(uint32_t*)&cvtB.f[w * 4 + 2] =
514 (qa & 0x000F000Fu) | BF16_MAGIC;
515 qa >>= 4;
516 *(uint32_t*)&cvtB.f[w * 4 + 3] =
517 (qa & 0x000F000Fu) | BF16_MAGIC;
518 }
519 }
520
521 if constexpr (!std::is_same_v<scalar_t, __hip_bfloat16>) {
522 if constexpr (HAS_ZERO_POINTS && GROUP_SIZE > 0) {
523 uint32_t group_idx = k_ / GROUP_SIZE;
524 scalar_t zp = __float2s<scalar_t>(static_cast<float>(
525 zp_nibble(zero_points, m + y, group_idx, num_groups)));
526 #pragma unroll
527 for (uint32_t b = 0; b < A_CHUNK; b++) {
528 cvtB.h[b] = cvtB.h[b] - zp;
529 }
530 }
531 }
532
533 if constexpr (GROUP_SIZE > 0) {
534 float partial = 0;
535 #pragma unroll
536 for (uint32_t b = 0; b < A_CHUNK / 2; b++) {
537 DOT2C(partial, bigA[n][k2].f[b], cvtB.f[b])
538 }
539 if constexpr (std::is_same_v<scalar_t, __hip_bfloat16>) {
540 constexpr uint32_t BF16_ONES = 0x3F803F80u;
541 float act_sum = 0.0f;
542 #pragma unroll
543 for (uint32_t b = 0; b < A_CHUNK / 2; b++) {
544 DOT2C(act_sum, bigA[n][k2].f[b], *(const float*)&BF16_ONES)
545 }
546 if constexpr (HAS_ZERO_POINTS) {
547 uint32_t group_idx_zp = k_ / GROUP_SIZE;
548 float zp_f = static_cast<float>(
549 zp_nibble(zero_points, m + y, group_idx_zp, num_groups));
550 partial -= (128.0f + zp_f) * act_sum;
551 } else {
552 partial -= 136.0f * act_sum;
553 }
554 }
555 uint32_t group_idx = k_ / GROUP_SIZE;
556 sum[n][y] +=
557 partial * __s2float(scale[(m + y) * num_groups + group_idx]);
558 } else {
559 #pragma unroll
560 for (uint32_t b = 0; b < A_CHUNK / 2; b++) {
561 DOT2C(sum[n][y], bigA[n][k2].f[b], cvtB.f[b])
562 }
563 if constexpr (std::is_same_v<scalar_t, __hip_bfloat16>) {
564 constexpr float BIAS_VAL = HAS_ZERO_POINTS ? 128.0f : 136.0f;
565 constexpr uint32_t BF16_ONES = 0x3F803F80u;
566 float act_sum = 0.0f;
567 #pragma unroll
568 for (uint32_t b = 0; b < A_CHUNK / 2; b++) {
569 DOT2C(act_sum, bigA[n][k2].f[b], *(const float*)&BF16_ONES)
570 }
571 sum[n][y] -= BIAS_VAL * act_sum;
572 }
573 }
574 }
575 }
576 }
577 }
578
579 for (int n = 0; n < N; n++)
580 for (int y = 0; y < YTILE; y++) REDUCE_SUM_DPP_WAVE32(sum[n][y]);
581
582 if (threadIdx.x == (THRDS - 1)) {
583 for (int n = 0; n < N; n++) {
584 for (int i = 0; i < YTILE; i++) {
585 if (commitColumn[i]) {
586 if constexpr (GROUP_SIZE == 0) {
587 sum[n][i] *= __s2float(scale[m + i]);
588 }
589 if (BIAS) sum[n][i] += __s2float(BIAS[(m + i) % Bx + (n % By) * M]);
590 C[m + i + n * M] = __float2s<scalar_t>(sum[n][i]);
591 }
592 }
593 }
594 }
595 m += CuCount * _WvPrGrp * YTILE;
596
597 if (m < M && (m + YTILE) >= M) {
598 uint32_t startColumn = M - YTILE;
599 for (uint32_t i = 0; i < (m - startColumn); i++) {
600 commitColumn[i] = 0;
601 }
602 m = startColumn;
603 }
604 }
605 }
606 #else // !defined(__HIP__GFX1X__)
607 template <typename scalar_t, int THRDS, int YTILE, int WvPrGrp, int A_CHUNK,
608 int UNRL, int N, int GROUP_SIZE = 0, bool HAS_ZERO_POINTS = false>
609 __global__ void wvSplitK_int4_hf_(const int K, const int M, const int Bx,
610 const int By, const uint8_t* B_packed,
611 const scalar_t* __restrict__ A,
612 const scalar_t* scale,
613 const uint32_t* zero_points,
614 const scalar_t* __restrict__ BIAS,
615 scalar_t* C, const int _WvPrGrp,
616 const int CuCount) {
617 UNREACHABLE_CODE
618 }
619 #endif // defined(__HIP__GFX1X__)
620
621 static int mindiv_int4(int N, int div1, int div2) {
622 int nPrRnd = div1 * div2;
623 int limit = div2 < 13 ? div2 : 13;
624 int rnds[16];
625 for (int i = 0; i < limit; i++) {
626 rnds[i] = (N + nPrRnd - 1) / nPrRnd;
627 nPrRnd -= div1;
628 }
629 for (int i = limit - 1; i >= 0; i--)
630 if (rnds[0] == rnds[i]) return (div2 - i);
631 return 0;
632 }
633
634 // Per-group W4A16 skinny GEMM: packed int4 weights with group-wise scales.
635 // in_a: packed int4 weights [M, K/2] (int8) or [M, K/8] (int32)
636 // in_b: activations [N, K] (fp16/bf16)
637 // in_scale: group scales [M, K/group_size] (fp16/bf16)
638 // in_zero_points: optional raw zero points [M/8, K/group_size] (int32,
639 // 8 uint4 per word along dim 0)
640 // If provided, kernel dequants as (nibble - zp_raw) * scale (asymmetric).
641 // If absent, kernel dequants as (nibble - 8) * scale (symmetric uint4b8).
642 // group_size: 32, 64, or 128
643
644 torch::Tensor wvSplitK_int4_g(const at::Tensor& in_a, const at::Tensor& in_b,
645 const at::Tensor& in_scale,
646 const std::optional<at::Tensor>& in_zero_points,
647 const std::optional<at::Tensor>& in_bias,
648 const int64_t CuCount, const int64_t group_size) {
649 auto M_in = in_a.size(0);
650 auto K_in = in_b.size(1);
651 auto N_in = in_b.size(0);
652 auto Bx_in =
653 (in_bias.has_value() && in_bias->numel() > 0)
654 ? (in_bias->sizes().size() == 2) ? in_bias->size(1) : in_bias->size(0)
655 : 1;
656 auto By_in = (in_bias.has_value() && in_bias->numel() > 0 &&
657 in_bias->sizes().size() == 2)
658 ? in_bias->size(0)
659 : 1;
660
661 int64_t expected_weight_bytes = M_in * K_in / 2;
662 int64_t actual_weight_bytes = in_a.numel() * in_a.element_size();
663 TORCH_CHECK(actual_weight_bytes == expected_weight_bytes,
664 "Weight tensor must contain M*K/2 bytes for int4 packing");
665 TORCH_CHECK(
666 in_b.dtype() == torch::kFloat16 || in_b.dtype() == torch::kBFloat16,
667 "Activation must be float16 or bfloat16");
668 TORCH_CHECK(in_scale.dtype() == in_b.dtype(),
669 "Scale dtype must match activation dtype");
670 TORCH_CHECK(group_size == 32 || group_size == 64 || group_size == 128,
671 "group_size must be 32, 64, or 128, got ", group_size);
672 TORCH_CHECK(K_in % group_size == 0,
673 "K must be divisible by group_size=", group_size);
674 int64_t num_groups = K_in / group_size;
675 TORCH_CHECK(in_scale.dim() == 2,
676 "Scale must be 2D [M, K/group_size], got shape ",
677 in_scale.sizes());
678 TORCH_CHECK(in_scale.size(0) == M_in && in_scale.size(1) == num_groups,
679 "Scale must be [M, K/group_size] = [", M_in, ", ", num_groups,
680 "] but got [", in_scale.size(0), ", ", in_scale.size(1), "]");
681 if (in_zero_points.has_value()) {
682 // The kernel reads the words as uint32, so either signedness is accepted.
683 TORCH_CHECK(in_zero_points->dtype() == at::kInt ||
684 in_zero_points->dtype() == at::kUInt32,
685 "Zero points must be int32 or uint32 (packed 8x uint4 along "
686 "dim 0), got ",
687 in_zero_points->dtype());
688 TORCH_CHECK(in_zero_points->dim() == 2,
689 "Zero points must be 2D [M/8, K/group_size], got shape ",
690 in_zero_points->sizes());
691 TORCH_CHECK(M_in % 8 == 0,
692 "M must be divisible by 8 for packed zero points, got ", M_in);
693 TORCH_CHECK(in_zero_points->is_contiguous(),
694 "Zero points must be contiguous");
695 TORCH_CHECK(in_zero_points->size(0) == M_in / 8 &&
696 in_zero_points->size(1) == num_groups,
697 "Zero points must be [M/8, K/group_size] = [", M_in / 8, ", ",
698 num_groups, "] but got [", in_zero_points->size(0), ", ",
699 in_zero_points->size(1), "]");
700 }
701 TORCH_CHECK(K_in % 16 == 0, "K must be divisible by 16");
702
703 const int max_lds_len = get_lds_size_int4() / 2;
704 TORCH_CHECK(K_in * N_in <= (int64_t)(max_lds_len * 1.2),
705 "K*N exceeds LDS capacity (medium limit). K=", K_in, " N=", N_in);
706
707 auto out_c = torch::empty(
708 {N_in, M_in},
709 torch::TensorOptions().dtype(in_b.dtype()).device(in_b.device()));
710
711 dim3 grid(CuCount);
712
713 const at::cuda::OptionalCUDAGuard device_guard(device_of(in_a));
714 const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
715
716 // Dispatch macro: _HAS_ZP selects the HAS_ZERO_POINTS template parameter
717 #define WVSPLITK_INT4G_LAUNCH(_THRDS, _YTILE, _UNRL, _N, _GS, _HAS_ZP) \
718 { \
719 dim3 block(_THRDS, 16); \
720 int __wvPrGrp = mindiv_int4(M_in, CuCount * _YTILE, 16); \
721 if (K_in * N_in <= max_lds_len && M_in % _YTILE == 0) \
722 wvSplitK_int4_hf_sml_<fptype, _THRDS, _YTILE, 16, 16, _UNRL, _N, _GS, \
723 _HAS_ZP><<<grid, block, 0, stream>>>( \
724 K_in, M_in, Bx_in, By_in, wptr, aptr, sptr, zpptr, biasptr, cptr, \
725 __wvPrGrp, CuCount); \
726 else \
727 wvSplitK_int4_hf_<fptype, _THRDS, _YTILE, 16, 16, _UNRL, _N, _GS, \
728 _HAS_ZP><<<grid, block, 0, stream>>>( \
729 K_in, M_in, Bx_in, By_in, wptr, aptr, sptr, zpptr, biasptr, cptr, \
730 __wvPrGrp, CuCount); \
731 }
732
733 #define WVSPLITK_INT4G(_YTILE, _UNRL, _N, _GS, _HAS_ZP) \
734 WVSPLITK_INT4G_LAUNCH(32, _YTILE, _UNRL, _N, _GS, _HAS_ZP)
735
736 #define WVSPLIT_INT4G_GS(_YTILE, _UNRL, _N, _HAS_ZP) \
737 if (group_size == 32) \
738 WVSPLITK_INT4G(_YTILE, _UNRL, _N, 32, _HAS_ZP) \
739 else if (group_size == 64) \
740 WVSPLITK_INT4G(_YTILE, _UNRL, _N, 64, _HAS_ZP) \
741 else \
742 WVSPLITK_INT4G(_YTILE, _UNRL, _N, 128, _HAS_ZP)
743
744 #define WVSPLIT_INT4G_TILE(_sYT, __N, _HAS_ZP) \
745 { \
746 if (K_in * N_in > max_lds_len) { \
747 if (_sYT < 30) \
748 WVSPLIT_INT4G_GS(4, 2, __N, _HAS_ZP) \
749 else \
750 WVSPLIT_INT4G_GS(4, 1, __N, _HAS_ZP) \
751 } else if (__N >= 4 && _sYT >= 480) \
752 WVSPLIT_INT4G_GS(4, 1, __N, _HAS_ZP) \
753 else if (__N >= 3 && _sYT >= 40) \
754 WVSPLIT_INT4G_GS(4, 1, __N, _HAS_ZP) \
755 else if (__N >= 3 && _sYT < 40 && (K_in <= 2048 || K_in >= 4096)) \
756 WVSPLIT_INT4G_GS(2, 4, __N, _HAS_ZP) \
757 else if (__N >= 3 && _sYT < 40) \
758 WVSPLIT_INT4G_GS(2, 2, __N, _HAS_ZP) \
759 else if (__N >= 2) \
760 WVSPLIT_INT4G_GS(2, 2, __N, _HAS_ZP) \
761 else if (_sYT >= 30) \
762 WVSPLIT_INT4G_GS(2, 4, __N, _HAS_ZP) \
763 else \
764 WVSPLIT_INT4G_GS(1, 4, __N, _HAS_ZP) \
765 }
766
767 // Inner dispatch: shared by both symmetric and asymmetric paths
768 #define WVSPLIT_INT4G_DISPATCH(_HAS_ZP) \
769 { \
770 int sYT = (M_in + CuCount * 4 - 1) / (CuCount * 4); \
771 switch (N_in) { \
772 case 1: \
773 WVSPLIT_INT4G_TILE(sYT, 1, _HAS_ZP) break; \
774 case 2: \
775 WVSPLIT_INT4G_TILE(sYT, 2, _HAS_ZP) break; \
776 case 3: \
777 WVSPLIT_INT4G_TILE(sYT, 3, _HAS_ZP) break; \
778 case 4: \
779 WVSPLIT_INT4G_TILE(sYT, 4, _HAS_ZP) break; \
780 case 5: \
781 WVSPLIT_INT4G_TILE(sYT, 5, _HAS_ZP) break; \
782 default: \
783 throw std::runtime_error("Unsupported N value: " + \
784 std::to_string(N_in)); \
785 } \
786 }
787
788 AT_DISPATCH_REDUCED_FLOATING_TYPES(
789 in_b.scalar_type(), "wvSplitK_int4_g", [&] {
790 using fptype = typename scalar<scalar_t>::type;
791 const uint8_t* wptr = reinterpret_cast<const uint8_t*>(in_a.data_ptr());
792 const fptype* aptr = reinterpret_cast<const fptype*>(in_b.data_ptr());
793 const fptype* sptr =
794 reinterpret_cast<const fptype*>(in_scale.data_ptr());
795 const uint32_t* zpptr =
796 in_zero_points.has_value()
797 ? reinterpret_cast<const uint32_t*>(in_zero_points->data_ptr())
798 : nullptr;
799 const fptype* biasptr =
800 (in_bias.has_value() && in_bias->numel() > 0)
801 ? reinterpret_cast<const fptype*>(in_bias->data_ptr())
802 : nullptr;
803 fptype* cptr = reinterpret_cast<fptype*>(out_c.data_ptr());
804
805 if (in_zero_points.has_value())
806 WVSPLIT_INT4G_DISPATCH(true)
807 else
808 WVSPLIT_INT4G_DISPATCH(false)
809 });
810
811 #undef WVSPLITK_INT4G_LAUNCH
812 #undef WVSPLITK_INT4G
813 #undef WVSPLIT_INT4G_GS
814 #undef WVSPLIT_INT4G_TILE
815 #undef WVSPLIT_INT4G_DISPATCH
816
817 return out_c;
818 }