Sync the OrbitQuant 0.9.0 W3 CUDA pipeline
Browse files
orbitquant_packed_matmul_cuda/packed_matmul.cu
CHANGED
|
@@ -28,7 +28,7 @@ inline bool orbitquant_mma64_pipeline_disabled() {
|
|
| 28 |
}
|
| 29 |
|
| 30 |
// ORBITQUANT_MMA64_FORCE_PIPELINE=1 extends the pipeline to every eligible
|
| 31 |
-
// width
|
| 32 |
inline bool orbitquant_mma64_pipeline_forced() {
|
| 33 |
static const bool forced = []() {
|
| 34 |
char const *value = std::getenv("ORBITQUANT_MMA64_FORCE_PIPELINE");
|
|
@@ -294,18 +294,32 @@ __global__ void orbitquant_packed_matmul_mma64_kernel(
|
|
| 294 |
#endif // __CUDA_ARCH__ >= 800
|
| 295 |
}
|
| 296 |
|
| 297 |
-
|
|
|
|
| 298 |
void *__restrict__ destination,
|
| 299 |
void const *__restrict__ source) {
|
| 300 |
#if __CUDA_ARCH__ >= 800
|
| 301 |
const uint32_t shared_address =
|
| 302 |
static_cast<uint32_t>(__cvta_generic_to_shared(destination));
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
#else
|
| 307 |
-
|
| 308 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 309 |
#endif
|
| 310 |
}
|
| 311 |
|
|
@@ -321,10 +335,9 @@ __device__ __forceinline__ void wait_for_async_copies() {
|
|
| 321 |
#endif
|
| 322 |
}
|
| 323 |
|
| 324 |
-
// cp.async-pipelined variant of the mma64 kernel (sm80+
|
| 325 |
-
//
|
| 326 |
-
// the
|
| 327 |
-
// edge blocks keep the guarded synchronous path.
|
| 328 |
template <typename storage_t, typename mma_t, int Bits>
|
| 329 |
__global__ void orbitquant_packed_matmul_mma64_pipelined_kernel(
|
| 330 |
storage_t *__restrict__ out,
|
|
@@ -348,11 +361,13 @@ __global__ void orbitquant_packed_matmul_mma64_pipelined_kernel(
|
|
| 348 |
constexpr int x_vector_values = 8;
|
| 349 |
constexpr int x_vectors_per_row = tile_k / x_vector_values;
|
| 350 |
constexpr int seg_stride = tile_k * Bits / 8;
|
| 351 |
-
constexpr int weight_segment_values = 16;
|
| 352 |
constexpr int weight_segments_per_row = tile_k / weight_segment_values;
|
| 353 |
constexpr int segment_bytes = weight_segment_values * Bits / 8;
|
|
|
|
| 354 |
static_assert(sizeof(storage_t) == sizeof(mma_t));
|
| 355 |
-
static_assert(Bits == 2 || Bits == 4 || Bits == 6);
|
|
|
|
| 356 |
|
| 357 |
extern __shared__ __align__(16) uint8_t dynamic_shared[];
|
| 358 |
mma_t *x_tiles = reinterpret_cast<mma_t *>(dynamic_shared);
|
|
@@ -380,23 +395,23 @@ __global__ void orbitquant_packed_matmul_mma64_pipelined_kernel(
|
|
| 380 |
|
| 381 |
if (interior) {
|
| 382 |
constexpr int x_copies = tile_m * x_vectors_per_row;
|
| 383 |
-
constexpr int w_copies = tile_n * seg_stride /
|
| 384 |
auto stage_tile = [&](int buffer, int64_t k_start) {
|
| 385 |
mma_t *x_destination = x_tiles + buffer * tile_m * padded_k;
|
| 386 |
for (int task = threadIdx.x; task < x_copies; task += blockDim.x) {
|
| 387 |
const int local_row = task / x_vectors_per_row;
|
| 388 |
const int local_vector = task - local_row * x_vectors_per_row;
|
| 389 |
-
|
| 390 |
x_destination + local_row * padded_k + local_vector * x_vector_values,
|
| 391 |
x + (block_row + local_row) * in_features + k_start +
|
| 392 |
local_vector * x_vector_values);
|
| 393 |
}
|
| 394 |
uint8_t *stage_destination = packed_stage + buffer * tile_n * seg_stride;
|
| 395 |
for (int task = threadIdx.x; task < w_copies; task += blockDim.x) {
|
| 396 |
-
const int byte_offset = task *
|
| 397 |
const int local_col = byte_offset / seg_stride;
|
| 398 |
const int seg_byte = byte_offset - local_col * seg_stride;
|
| 399 |
-
|
| 400 |
stage_destination + byte_offset,
|
| 401 |
packed_weight_indices + (block_col + local_col) * packed_row_bytes +
|
| 402 |
(k_start * Bits) / 8 + seg_byte);
|
|
@@ -804,7 +819,7 @@ __global__ void orbitquant_packed_w4a4_int8_mma_kernel(
|
|
| 804 |
const int byte_offset = vector * 16;
|
| 805 |
const int local_row = byte_offset / packed_tile_k;
|
| 806 |
const int local_k_byte = byte_offset - local_row * packed_tile_k;
|
| 807 |
-
|
| 808 |
packed_activation_stage + byte_offset,
|
| 809 |
packed_activations +
|
| 810 |
(block_row + local_row) * packed_row_stride + local_k_byte);
|
|
@@ -816,14 +831,14 @@ __global__ void orbitquant_packed_w4a4_int8_mma_kernel(
|
|
| 816 |
if constexpr (KMajorWeight) {
|
| 817 |
const int local_k_byte = byte_offset / TileN;
|
| 818 |
const int local_col = byte_offset - local_k_byte * TileN;
|
| 819 |
-
|
| 820 |
packed_weight_stage + byte_offset,
|
| 821 |
packed_weight_indices + local_k_byte * out_features + block_col +
|
| 822 |
local_col);
|
| 823 |
} else {
|
| 824 |
const int local_col = byte_offset / packed_tile_k;
|
| 825 |
const int local_k_byte = byte_offset - local_col * packed_tile_k;
|
| 826 |
-
|
| 827 |
packed_weight_stage + byte_offset,
|
| 828 |
packed_weight_indices +
|
| 829 |
(block_col + local_col) * packed_row_stride + local_k_byte);
|
|
@@ -890,7 +905,7 @@ __global__ void orbitquant_packed_w4a4_int8_mma_kernel(
|
|
| 890 |
const int byte_offset = vector * 16;
|
| 891 |
const int local_row = byte_offset / packed_tile_k;
|
| 892 |
const int local_k_byte = byte_offset - local_row * packed_tile_k;
|
| 893 |
-
|
| 894 |
packed_activation_stage + byte_offset,
|
| 895 |
packed_activations +
|
| 896 |
(block_row + local_row) * packed_row_stride + next_k_byte +
|
|
@@ -903,7 +918,7 @@ __global__ void orbitquant_packed_w4a4_int8_mma_kernel(
|
|
| 903 |
if constexpr (KMajorWeight) {
|
| 904 |
const int local_k_byte = byte_offset / TileN;
|
| 905 |
const int local_col = byte_offset - local_k_byte * TileN;
|
| 906 |
-
|
| 907 |
packed_weight_stage + byte_offset,
|
| 908 |
packed_weight_indices +
|
| 909 |
(next_k_byte + local_k_byte) * out_features + block_col +
|
|
@@ -911,7 +926,7 @@ __global__ void orbitquant_packed_w4a4_int8_mma_kernel(
|
|
| 911 |
} else {
|
| 912 |
const int local_col = byte_offset / packed_tile_k;
|
| 913 |
const int local_k_byte = byte_offset - local_col * packed_tile_k;
|
| 914 |
-
|
| 915 |
packed_weight_stage + byte_offset,
|
| 916 |
packed_weight_indices +
|
| 917 |
(block_col + local_col) * packed_row_stride + next_k_byte +
|
|
@@ -1453,14 +1468,16 @@ void matmul_packed_weight(
|
|
| 1453 |
// (each layer's weights are evicted between calls in a real model) it is
|
| 1454 |
// 1.35-1.46x faster for W2/W4/W6 on RTX 4060 Ti, A40, and RTX 4090 whenever
|
| 1455 |
// the grid fits in one wave (blocks <= SM count), and it regresses up to 15%
|
| 1456 |
-
// once the grid oversubscribes the device.
|
| 1457 |
-
//
|
|
|
|
| 1458 |
// ORBITQUANT_MMA64_FORCE_PIPELINE=1 bypasses the grid gate,
|
| 1459 |
// ORBITQUANT_MMA64_DISABLE_PIPELINE=1 forces the legacy kernel everywhere.
|
| 1460 |
#define ORBITQUANT_MMA64_USE_PIPELINE(BITS_VALUE) \
|
| 1461 |
((orbitquant_mma64_pipeline_forced() || \
|
| 1462 |
-
|
| 1463 |
-
|
|
|
|
| 1464 |
!orbitquant_mma64_pipeline_disabled() && \
|
| 1465 |
mma_properties->major >= 8 && \
|
| 1466 |
(2 * 128 * 72 * 2 + 128 * 72 * 2 + 8 * 16 * 16 * 4 + \
|
|
@@ -1490,7 +1507,11 @@ void matmul_packed_weight(
|
|
| 1490 |
}
|
| 1491 |
break;
|
| 1492 |
case 3:
|
| 1493 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1494 |
break;
|
| 1495 |
case 4:
|
| 1496 |
if (ORBITQUANT_MMA64_USE_PIPELINE(4)) {
|
|
@@ -1594,7 +1615,11 @@ void matmul_packed_weight(
|
|
| 1594 |
}
|
| 1595 |
break;
|
| 1596 |
case 3:
|
| 1597 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1598 |
break;
|
| 1599 |
case 4:
|
| 1600 |
if (ORBITQUANT_MMA64_USE_PIPELINE(4)) {
|
|
|
|
| 28 |
}
|
| 29 |
|
| 30 |
// ORBITQUANT_MMA64_FORCE_PIPELINE=1 extends the pipeline to every eligible
|
| 31 |
+
// width for re-benchmarking on other GPUs.
|
| 32 |
inline bool orbitquant_mma64_pipeline_forced() {
|
| 33 |
static const bool forced = []() {
|
| 34 |
char const *value = std::getenv("ORBITQUANT_MMA64_FORCE_PIPELINE");
|
|
|
|
| 294 |
#endif // __CUDA_ARCH__ >= 800
|
| 295 |
}
|
| 296 |
|
| 297 |
+
template <int Bytes>
|
| 298 |
+
__device__ __forceinline__ void copy_async(
|
| 299 |
void *__restrict__ destination,
|
| 300 |
void const *__restrict__ source) {
|
| 301 |
#if __CUDA_ARCH__ >= 800
|
| 302 |
const uint32_t shared_address =
|
| 303 |
static_cast<uint32_t>(__cvta_generic_to_shared(destination));
|
| 304 |
+
if constexpr (Bytes == 16) {
|
| 305 |
+
asm volatile(
|
| 306 |
+
"cp.async.ca.shared.global [%0], [%1], 16;\n" : : "r"(shared_address),
|
| 307 |
+
"l"(source));
|
| 308 |
+
} else {
|
| 309 |
+
static_assert(Bytes == 8);
|
| 310 |
+
asm volatile(
|
| 311 |
+
"cp.async.ca.shared.global [%0], [%1], 8;\n" : : "r"(shared_address),
|
| 312 |
+
"l"(source));
|
| 313 |
+
}
|
| 314 |
#else
|
| 315 |
+
if constexpr (Bytes == 16) {
|
| 316 |
+
*reinterpret_cast<uint4 *>(destination) =
|
| 317 |
+
*reinterpret_cast<uint4 const *>(source);
|
| 318 |
+
} else {
|
| 319 |
+
static_assert(Bytes == 8);
|
| 320 |
+
*reinterpret_cast<uint2 *>(destination) =
|
| 321 |
+
*reinterpret_cast<uint2 const *>(source);
|
| 322 |
+
}
|
| 323 |
#endif
|
| 324 |
}
|
| 325 |
|
|
|
|
| 335 |
#endif
|
| 336 |
}
|
| 337 |
|
| 338 |
+
// cp.async-pipelined variant of the mma64 kernel (sm80+). Interior blocks
|
| 339 |
+
// double-buffer the X tile and the packed weight bytes so global loads overlap
|
| 340 |
+
// the MMA work; edge blocks keep the guarded synchronous path.
|
|
|
|
| 341 |
template <typename storage_t, typename mma_t, int Bits>
|
| 342 |
__global__ void orbitquant_packed_matmul_mma64_pipelined_kernel(
|
| 343 |
storage_t *__restrict__ out,
|
|
|
|
| 361 |
constexpr int x_vector_values = 8;
|
| 362 |
constexpr int x_vectors_per_row = tile_k / x_vector_values;
|
| 363 |
constexpr int seg_stride = tile_k * Bits / 8;
|
| 364 |
+
constexpr int weight_segment_values = Bits == 3 ? 32 : 16;
|
| 365 |
constexpr int weight_segments_per_row = tile_k / weight_segment_values;
|
| 366 |
constexpr int segment_bytes = weight_segment_values * Bits / 8;
|
| 367 |
+
constexpr int weight_copy_bytes = Bits == 3 ? 8 : 16;
|
| 368 |
static_assert(sizeof(storage_t) == sizeof(mma_t));
|
| 369 |
+
static_assert(Bits == 2 || Bits == 3 || Bits == 4 || Bits == 6);
|
| 370 |
+
static_assert(seg_stride % weight_copy_bytes == 0);
|
| 371 |
|
| 372 |
extern __shared__ __align__(16) uint8_t dynamic_shared[];
|
| 373 |
mma_t *x_tiles = reinterpret_cast<mma_t *>(dynamic_shared);
|
|
|
|
| 395 |
|
| 396 |
if (interior) {
|
| 397 |
constexpr int x_copies = tile_m * x_vectors_per_row;
|
| 398 |
+
constexpr int w_copies = tile_n * seg_stride / weight_copy_bytes;
|
| 399 |
auto stage_tile = [&](int buffer, int64_t k_start) {
|
| 400 |
mma_t *x_destination = x_tiles + buffer * tile_m * padded_k;
|
| 401 |
for (int task = threadIdx.x; task < x_copies; task += blockDim.x) {
|
| 402 |
const int local_row = task / x_vectors_per_row;
|
| 403 |
const int local_vector = task - local_row * x_vectors_per_row;
|
| 404 |
+
copy_async<16>(
|
| 405 |
x_destination + local_row * padded_k + local_vector * x_vector_values,
|
| 406 |
x + (block_row + local_row) * in_features + k_start +
|
| 407 |
local_vector * x_vector_values);
|
| 408 |
}
|
| 409 |
uint8_t *stage_destination = packed_stage + buffer * tile_n * seg_stride;
|
| 410 |
for (int task = threadIdx.x; task < w_copies; task += blockDim.x) {
|
| 411 |
+
const int byte_offset = task * weight_copy_bytes;
|
| 412 |
const int local_col = byte_offset / seg_stride;
|
| 413 |
const int seg_byte = byte_offset - local_col * seg_stride;
|
| 414 |
+
copy_async<weight_copy_bytes>(
|
| 415 |
stage_destination + byte_offset,
|
| 416 |
packed_weight_indices + (block_col + local_col) * packed_row_bytes +
|
| 417 |
(k_start * Bits) / 8 + seg_byte);
|
|
|
|
| 819 |
const int byte_offset = vector * 16;
|
| 820 |
const int local_row = byte_offset / packed_tile_k;
|
| 821 |
const int local_k_byte = byte_offset - local_row * packed_tile_k;
|
| 822 |
+
copy_async<16>(
|
| 823 |
packed_activation_stage + byte_offset,
|
| 824 |
packed_activations +
|
| 825 |
(block_row + local_row) * packed_row_stride + local_k_byte);
|
|
|
|
| 831 |
if constexpr (KMajorWeight) {
|
| 832 |
const int local_k_byte = byte_offset / TileN;
|
| 833 |
const int local_col = byte_offset - local_k_byte * TileN;
|
| 834 |
+
copy_async<16>(
|
| 835 |
packed_weight_stage + byte_offset,
|
| 836 |
packed_weight_indices + local_k_byte * out_features + block_col +
|
| 837 |
local_col);
|
| 838 |
} else {
|
| 839 |
const int local_col = byte_offset / packed_tile_k;
|
| 840 |
const int local_k_byte = byte_offset - local_col * packed_tile_k;
|
| 841 |
+
copy_async<16>(
|
| 842 |
packed_weight_stage + byte_offset,
|
| 843 |
packed_weight_indices +
|
| 844 |
(block_col + local_col) * packed_row_stride + local_k_byte);
|
|
|
|
| 905 |
const int byte_offset = vector * 16;
|
| 906 |
const int local_row = byte_offset / packed_tile_k;
|
| 907 |
const int local_k_byte = byte_offset - local_row * packed_tile_k;
|
| 908 |
+
copy_async<16>(
|
| 909 |
packed_activation_stage + byte_offset,
|
| 910 |
packed_activations +
|
| 911 |
(block_row + local_row) * packed_row_stride + next_k_byte +
|
|
|
|
| 918 |
if constexpr (KMajorWeight) {
|
| 919 |
const int local_k_byte = byte_offset / TileN;
|
| 920 |
const int local_col = byte_offset - local_k_byte * TileN;
|
| 921 |
+
copy_async<16>(
|
| 922 |
packed_weight_stage + byte_offset,
|
| 923 |
packed_weight_indices +
|
| 924 |
(next_k_byte + local_k_byte) * out_features + block_col +
|
|
|
|
| 926 |
} else {
|
| 927 |
const int local_col = byte_offset / packed_tile_k;
|
| 928 |
const int local_k_byte = byte_offset - local_col * packed_tile_k;
|
| 929 |
+
copy_async<16>(
|
| 930 |
packed_weight_stage + byte_offset,
|
| 931 |
packed_weight_indices +
|
| 932 |
(block_col + local_col) * packed_row_stride + next_k_byte +
|
|
|
|
| 1468 |
// (each layer's weights are evicted between calls in a real model) it is
|
| 1469 |
// 1.35-1.46x faster for W2/W4/W6 on RTX 4060 Ti, A40, and RTX 4090 whenever
|
| 1470 |
// the grid fits in one wave (blocks <= SM count), and it regresses up to 15%
|
| 1471 |
+
// once the grid oversubscribes the device. W3 uses denser 8-byte copies and
|
| 1472 |
+
// wins from 128 rows even for oversubscribed grids on RTX 6000 Ada; shorter
|
| 1473 |
+
// W3 launches stay on the legacy kernel. Measured 2026-07.
|
| 1474 |
// ORBITQUANT_MMA64_FORCE_PIPELINE=1 bypasses the grid gate,
|
| 1475 |
// ORBITQUANT_MMA64_DISABLE_PIPELINE=1 forces the legacy kernel everywhere.
|
| 1476 |
#define ORBITQUANT_MMA64_USE_PIPELINE(BITS_VALUE) \
|
| 1477 |
((orbitquant_mma64_pipeline_forced() || \
|
| 1478 |
+
((BITS_VALUE) == 3 ? x.size(0) >= 128 \
|
| 1479 |
+
: static_cast<int64_t>(mma_grid.x) * mma_grid.y <= \
|
| 1480 |
+
mma_properties->multiProcessorCount)) && \
|
| 1481 |
!orbitquant_mma64_pipeline_disabled() && \
|
| 1482 |
mma_properties->major >= 8 && \
|
| 1483 |
(2 * 128 * 72 * 2 + 128 * 72 * 2 + 8 * 16 * 16 * 4 + \
|
|
|
|
| 1507 |
}
|
| 1508 |
break;
|
| 1509 |
case 3:
|
| 1510 |
+
if (ORBITQUANT_MMA64_USE_PIPELINE(3)) {
|
| 1511 |
+
ORBITQUANT_LAUNCH_MMA64_PIPELINED(c10::BFloat16, __nv_bfloat16, 3);
|
| 1512 |
+
} else {
|
| 1513 |
+
ORBITQUANT_LAUNCH_MMA64_BF16(3);
|
| 1514 |
+
}
|
| 1515 |
break;
|
| 1516 |
case 4:
|
| 1517 |
if (ORBITQUANT_MMA64_USE_PIPELINE(4)) {
|
|
|
|
| 1615 |
}
|
| 1616 |
break;
|
| 1617 |
case 3:
|
| 1618 |
+
if (ORBITQUANT_MMA64_USE_PIPELINE(3)) {
|
| 1619 |
+
ORBITQUANT_LAUNCH_MMA64_PIPELINED(c10::Half, half, 3);
|
| 1620 |
+
} else {
|
| 1621 |
+
ORBITQUANT_LAUNCH_MMA64_HALF(3);
|
| 1622 |
+
}
|
| 1623 |
break;
|
| 1624 |
case 4:
|
| 1625 |
if (ORBITQUANT_MMA64_USE_PIPELINE(4)) {
|