* CUDA: dedup MoE gate/up activation quantization (fp4) For MoE gate/up projections the src1 activation is broadcast across the routed experts (ne11 == 1), so ids_src1 maps every one of a token's n_expert_used slots to the same physical row. The MMQ path therefore re-quantized each token's activation n_expert_used times. For fp4 (NVFP4/MXFP4) src0, quantize each unique token row once instead of once per expert. For NVFP4 a single quantize+scatter kernel (quantize_scatter_mmq_nvfp4) quantizes each token once and writes the resulting block_fp4_mmq straight to all n_expert_used slots, using an inverse token->compact-row map (build_tok2c). MXFP4, and GGML_CUDA_MOE_QUANT_GATHER=1, use a two-kernel variant: quantize unique rows then gather into the expert-sorted layout (gather_mmq_fp4_blocks). Both are bit-identical to the previous gather-then-quantize path (identical source data, deterministic per-block quantization), verified by test-backend-ops MUL_MAT_ID (type_a=nvfp4, broadcast b=1; 790/790 for the default, gather, and per-expert paths) and by coherent end-to-end generation. Set GGML_CUDA_NO_MOE_QUANT_DEDUP=1 to force the original per-expert path. Same-binary A/B on RTX 5090 (sm_120), Qwen3.6-35B-A3B-NVFP4 prefill @8192 (nsys, graphs-off; the unchanged mul_mat_q GEMM confirms stable clocks): activation-quant GPU-busy drops 61% (78.2 -> 30.4 ms) with the fused quantize+scatter, vs 33% (78.2 -> 52.8 ms) for the two-kernel gather. The fused path avoids materializing and re-reading the 8x compact buffer, writing the expert copies directly from registers. * CUDA: bounds-check token ids in build_tok2c_kernel Guard against malformed ids_src1: skip out-of-range token ids (t < 0 or t >= n_tokens) and drop entries beyond n_expert_used per token instead of writing past the token's tok2c region. No behavior change for valid MoE routing data; test-backend-ops MUL_MAT_ID 790/790. * Refactor the code based on review comments - Removed previously added kernels that were not necessary anymore\ - Added an inverse mapping from (token, slot) to compact row. Each token is quantized once and scattered to its compact rows. * Adding q8_1 support for dedup and addressing review comments * Add pragma unrolls * Remove redundant cudaMemsetAsync call * Removing follow up redundancies --------- Co-authored-by: praneshgo <227579474+praneshgo@users.noreply.github.com>
192 lines
7.7 KiB
Plaintext
192 lines
7.7 KiB
Plaintext
#include "ggml.h"
|
|
#include "mmf.cuh"
|
|
#include "mmid.cuh"
|
|
|
|
static __forceinline__ int mmf_get_rows_per_block(const int cc) {
|
|
if (GGML_CUDA_CC_IS_CDNA(cc)) {
|
|
return MMF_ROWS_PER_BLOCK_CDNA;
|
|
} else {
|
|
return MMF_ROWS_PER_BLOCK;
|
|
}
|
|
}
|
|
|
|
void ggml_cuda_mul_mat_f(ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, const ggml_tensor * ids, ggml_tensor * dst) {
|
|
GGML_ASSERT( src1->type == GGML_TYPE_F32);
|
|
GGML_ASSERT(!ids || ids->type == GGML_TYPE_I32);
|
|
GGML_ASSERT( dst->type == GGML_TYPE_F32);
|
|
|
|
|
|
GGML_TENSOR_BINARY_OP_LOCALS;
|
|
|
|
const size_t ts_src0 = ggml_type_size(src0->type);
|
|
const size_t ts_src1 = ggml_type_size(src1->type);
|
|
const size_t ts_dst = ggml_type_size(dst->type);
|
|
|
|
GGML_ASSERT(ne13 == ne3);
|
|
|
|
GGML_ASSERT( nb00 == ts_src0);
|
|
GGML_ASSERT( nb10 == ts_src1);
|
|
GGML_ASSERT(!ids || ids->nb[0] == ggml_type_size(ids->type));
|
|
GGML_ASSERT( nb0 == ts_dst);
|
|
|
|
const float * src1_d = (const float *) src1->data;
|
|
const int32_t * ids_d = ids ? (const int32_t *) ids->data : nullptr;
|
|
float * dst_d = (float *) dst->data;
|
|
|
|
const int64_t s01 = src0->nb[1] / ts_src0;
|
|
const int64_t s11 = src1->nb[1] / ts_src1;
|
|
const int64_t s1 = dst->nb[1] / ts_dst;
|
|
const int64_t s02 = src0->nb[2] / ts_src0;
|
|
const int64_t s12 = src1->nb[2] / ts_src1;
|
|
const int64_t s2 = dst->nb[2] / ts_dst;
|
|
const int64_t s03 = src0->nb[3] / ts_src0;
|
|
const int64_t s13 = src1->nb[3] / ts_src1;
|
|
const int64_t s3 = dst->nb[3] / ts_dst;
|
|
|
|
const int64_t ids_s0 = ids ? ids->nb[0] / ggml_type_size(ids->type) : 0;
|
|
const int64_t ids_s1 = ids ? ids->nb[1] / ggml_type_size(ids->type) : 0;
|
|
|
|
mmf_ids_data ids_info{};
|
|
mmf_ids_data * ids_info_ptr = nullptr;
|
|
ggml_cuda_pool_alloc<int32_t> ids_src_compact_dev;
|
|
ggml_cuda_pool_alloc<int32_t> ids_dst_compact_dev;
|
|
ggml_cuda_pool_alloc<int32_t> expert_bounds_dev;
|
|
|
|
// For MUL_MAT_ID the memory layout is different than for MUL_MAT:
|
|
const int64_t ncols_dst = ids ? ne2 : ne1;
|
|
const int64_t nchannels_dst = ids ? ne1 : ne2;
|
|
|
|
const int64_t stride_col_dst = ids ? s2 : s1;
|
|
const int64_t stride_col_y = ids ? s12 : s11;
|
|
const int64_t stride_channel_dst = ids ? s1 : s2;
|
|
|
|
int64_t stride_channel_y = ids ? s11 : s12;
|
|
int64_t nchannels_y = ids ? ne11 : ne12;
|
|
|
|
//mul_mat_id: handle broadcast
|
|
if (ids && nchannels_y == 1) {
|
|
stride_channel_y = 0;
|
|
nchannels_y = ids->ne[0];
|
|
}
|
|
|
|
if (ids && ncols_dst > 16) {
|
|
const int64_t n_expert_used = ids->ne[0];
|
|
const int64_t n_experts = ne02;
|
|
const int64_t n_tokens = ne12;
|
|
const int64_t ne_get_rows = n_tokens * n_expert_used;
|
|
|
|
ids_src_compact_dev.alloc(ctx.pool(), ne_get_rows);
|
|
ids_dst_compact_dev.alloc(ctx.pool(), ne_get_rows);
|
|
expert_bounds_dev.alloc(ctx.pool(), n_experts + 1);
|
|
|
|
const int si1 = static_cast<int>(ids_s1);
|
|
const int sis1 = static_cast<int>(src1->nb[2] / src1->nb[1]);
|
|
|
|
GGML_ASSERT(sis1 > 0);
|
|
|
|
ggml_cuda_launch_mm_ids_helper(ids_d, ids_src_compact_dev.get(), ids_dst_compact_dev.get(), expert_bounds_dev.get(),
|
|
static_cast<int>(n_experts), static_cast<int>(n_tokens), static_cast<int>(n_expert_used), static_cast<int>(ne11), si1, sis1, /*write_inverse =*/ false, ctx.stream());
|
|
CUDA_CHECK(cudaGetLastError());
|
|
|
|
ids_info.ids_src_compact = ids_src_compact_dev.get();
|
|
ids_info.ids_dst_compact = ids_dst_compact_dev.get();
|
|
ids_info.expert_bounds_dev = expert_bounds_dev.get();
|
|
ids_info.n_experts = static_cast<int>(n_experts);
|
|
ids_info.sis1 = sis1;
|
|
ids_info_ptr = &ids_info;
|
|
}
|
|
|
|
const int device = ggml_cuda_get_device();
|
|
const int cc = ggml_cuda_info().devices[device].cc;
|
|
const int rows_per_block = mmf_get_rows_per_block(cc);
|
|
|
|
switch (src0->type) {
|
|
case GGML_TYPE_F32: {
|
|
const float * src0_d = (const float *) src0->data;
|
|
constexpr int vals_per_T = 1;
|
|
mul_mat_f_switch_rows_per_block<float>(
|
|
rows_per_block, src0_d, src1_d, ids_d, dst_d, ne00/vals_per_T, ne01, ncols_dst, s01/vals_per_T, stride_col_y/vals_per_T, stride_col_dst,
|
|
ids_s0, ids_s1, ne02, nchannels_y, nchannels_dst, s02/vals_per_T, stride_channel_y, stride_channel_dst,
|
|
ne03, ne3, s03/vals_per_T, s13, s3, ctx.stream(), ids_info_ptr);
|
|
} break;
|
|
case GGML_TYPE_F16: {
|
|
const half2 * src0_d = (const half2 *) src0->data;
|
|
constexpr int vals_per_T = 2;
|
|
mul_mat_f_switch_rows_per_block<half2>(
|
|
rows_per_block, src0_d, src1_d, ids_d, dst_d, ne00/vals_per_T, ne01, ncols_dst, s01/vals_per_T, stride_col_y/vals_per_T, stride_col_dst,
|
|
ids_s0, ids_s1, ne02, nchannels_y, nchannels_dst, s02/vals_per_T, stride_channel_y, stride_channel_dst,
|
|
ne03, ne3, s03/vals_per_T, s13, s3, ctx.stream(), ids_info_ptr);
|
|
} break;
|
|
case GGML_TYPE_BF16: {
|
|
const nv_bfloat162 * src0_d = (const nv_bfloat162 *) src0->data;
|
|
constexpr int vals_per_T = 2;
|
|
mul_mat_f_switch_rows_per_block<nv_bfloat162>(
|
|
rows_per_block, src0_d, src1_d, ids_d, dst_d, ne00/vals_per_T, ne01, ncols_dst, s01/vals_per_T, stride_col_y/vals_per_T, stride_col_dst,
|
|
ids_s0, ids_s1, ne02, nchannels_y, nchannels_dst, s02/vals_per_T, stride_channel_y, stride_channel_dst,
|
|
ne03, ne3, s03/vals_per_T, s13, s3, ctx.stream(), ids_info_ptr);
|
|
} break;
|
|
default:
|
|
GGML_ABORT("unsupported type: %s", ggml_type_name(src0->type));
|
|
}
|
|
}
|
|
|
|
bool ggml_cuda_should_use_mmf(enum ggml_type type, int cc, int warp_size, const int64_t * src0_ne,
|
|
const size_t * src0_nb, const int src1_ncols, bool mul_mat_id) {
|
|
if (ggml_is_quantized(type)) {
|
|
return false;
|
|
}
|
|
|
|
const size_t ts = ggml_type_size(type);
|
|
if (src0_ne[0] % (warp_size * (4/ts)) != 0) {
|
|
return false;
|
|
}
|
|
|
|
if (src0_nb[0] != ts) {
|
|
return false;
|
|
}
|
|
|
|
// Pointers not aligned to the size of half2/nv_bfloat162/float2 would result in a crash:
|
|
for (size_t i = 1; i < GGML_MAX_DIMS; ++i) {
|
|
if (src0_nb[i] % (2*ts) != 0) {
|
|
return false;
|
|
}
|
|
}
|
|
if (src0_ne[1] % mmf_get_rows_per_block(cc) != 0) {
|
|
return false;
|
|
}
|
|
|
|
if (GGML_CUDA_CC_IS_CDNA3(cc) && type == GGML_TYPE_BF16) {
|
|
return false;
|
|
}
|
|
|
|
if (mul_mat_id) {
|
|
if (src0_ne[1] <= 1024 && src1_ncols > 512) {
|
|
return false;
|
|
} else if(src0_ne[1] > 1024 && src1_ncols > 128) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (GGML_CUDA_CC_IS_RDNA3_0(cc) && src1_ncols > 8) {
|
|
return false;
|
|
} else if (GGML_CUDA_CC_IS_CDNA2(cc) && (type == GGML_TYPE_F16 || type == GGML_TYPE_BF16)) {
|
|
//TODO: truse CDNA2 as CDNA1, tune the perf when CDNA2 is available.
|
|
return false;
|
|
} else if (GGML_CUDA_CC_IS_CDNA1(cc) && (type == GGML_TYPE_F16 || type == GGML_TYPE_BF16)) {
|
|
return false;
|
|
} else if (src1_ncols > 16) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
switch (type) {
|
|
case GGML_TYPE_F32:
|
|
return ampere_mma_available(cc) || amd_mfma_available(cc);
|
|
case GGML_TYPE_F16:
|
|
return volta_mma_available(cc) || turing_mma_available(cc) || amd_wmma_available(cc) || amd_mfma_available(cc);
|
|
case GGML_TYPE_BF16:
|
|
return ampere_mma_available(cc) || amd_wmma_available(cc) || amd_mfma_available(cc);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|