Skip to content

Commit 98c8d32

Browse files
authored
Fuse compatible Q8 CUDA residual graph patterns (0xShug0#154)
1 parent 1778b23 commit 98c8d32

3 files changed

Lines changed: 102 additions & 36 deletions

File tree

external/ggml/src/ggml-cuda/common.cuh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,10 +1518,12 @@ struct ggml_cuda_mm_fusion_args_host {
15181518
const ggml_tensor * gate = nullptr;
15191519
const ggml_tensor * gate_bias = nullptr;
15201520
ggml_glu_op glu_op;
1521+
bool residual_only = false;
15211522
};
15221523
struct ggml_cuda_mm_fusion_args_device {
15231524
const void * x_bias = nullptr;
15241525
const void * gate = nullptr;
15251526
const void * gate_bias = nullptr;
15261527
ggml_glu_op glu_op;
1528+
bool residual_only = false;
15271529
};

external/ggml/src/ggml-cuda/ggml-cuda.cu

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3771,6 +3771,43 @@ static bool ggml_cuda_check_fusion_memory_ranges(const ggml_cgraph * cgraph,
37713771
return is_ok;
37723772
}
37733773

3774+
// Some model graphs reshape a matvec result before adding the residual. RESHAPE
3775+
// is metadata-only and therefore cannot pass the generic compute-node fusion
3776+
// validator. Validate this exact chain explicitly so the residual-only Q8_0
3777+
// specialization can write the final result directly.
3778+
static bool ggml_cuda_can_fuse_q8_0_mul_mat_reshape_add(
3779+
const struct ggml_cgraph * cgraph, int node_idx) {
3780+
if (node_idx + 2 >= cgraph->n_nodes) {
3781+
return false;
3782+
}
3783+
3784+
const ggml_tensor * mul_mat = cgraph->nodes[node_idx + 0];
3785+
const ggml_tensor * reshape = cgraph->nodes[node_idx + 1];
3786+
const ggml_tensor * add = cgraph->nodes[node_idx + 2];
3787+
3788+
if (mul_mat->op != GGML_OP_MUL_MAT ||
3789+
!mul_mat->src[0] ||
3790+
mul_mat->src[0]->type != GGML_TYPE_Q8_0 ||
3791+
reshape->op != GGML_OP_RESHAPE ||
3792+
reshape->src[0] != mul_mat ||
3793+
add->op != GGML_OP_ADD ||
3794+
(add->src[0] != reshape && add->src[1] != reshape)) {
3795+
return false;
3796+
}
3797+
3798+
if (ggml_nelements(mul_mat) != ggml_nelements(reshape) ||
3799+
ggml_nelements(reshape) != ggml_nelements(add) ||
3800+
ggml_node_get_use_count(cgraph, node_idx + 0) != 1 ||
3801+
ggml_node_get_use_count(cgraph, node_idx + 1) != 1 ||
3802+
(mul_mat->flags & GGML_TENSOR_FLAG_OUTPUT) ||
3803+
(reshape->flags & GGML_TENSOR_FLAG_OUTPUT)) {
3804+
return false;
3805+
}
3806+
3807+
const int out_nodes[] = { node_idx + 2 };
3808+
return ggml_cuda_check_fusion_memory_ranges(cgraph, node_idx, 3, out_nodes, 1);
3809+
}
3810+
37743811

37753812
static bool ggml_cuda_can_fuse(const struct ggml_cgraph * cgraph,
37763813
int node_idx,
@@ -4291,22 +4328,29 @@ static int ggml_cuda_try_fuse(ggml_backend_cuda_context * cuda_ctx, ggml_cgraph
42914328
fused_mul_mat_vec = false;
42924329
fused_node_count = 0;
42934330

4294-
// gate + add + glu + up + add
4331+
// mul_mat + optional metadata-only reshape + add
42954332
for (ggml_op op : { GGML_OP_MUL_MAT, GGML_OP_MUL_MAT_ID }) {
42964333
const ggml_op bias_op = op == GGML_OP_MUL_MAT ? GGML_OP_ADD : GGML_OP_ADD_ID;
42974334

4298-
if (!ggml_can_fuse(cgraph, i, { op, bias_op })) {
4335+
const bool reshape_bridge =
4336+
op == GGML_OP_MUL_MAT &&
4337+
ggml_cuda_can_fuse_q8_0_mul_mat_reshape_add(cgraph, i);
4338+
if (!reshape_bridge && !ggml_can_fuse(cgraph, i, { op, bias_op })) {
42994339
continue;
43004340
}
43014341

43024342
ggml_tensor * mm_node = cgraph->nodes[i];
4303-
ggml_tensor * bias_node = cgraph->nodes[i + 1];
4343+
ggml_tensor * mm_output = reshape_bridge ? cgraph->nodes[i + 1] : mm_node;
4344+
ggml_tensor * bias_node = cgraph->nodes[i + (reshape_bridge ? 2 : 1)];
4345+
if (reshape_bridge && mm_output->src[0] != mm_node) {
4346+
continue;
4347+
}
43044348

43054349
ggml_tensor * bias_tensor = nullptr;
43064350
if (bias_op == GGML_OP_ADD) {
4307-
if (bias_node->src[0] == mm_node) {
4351+
if (bias_node->src[0] == mm_output) {
43084352
bias_tensor = bias_node->src[1];
4309-
} else if (bias_node->src[1] == mm_node) {
4353+
} else if (bias_node->src[1] == mm_output) {
43104354
bias_tensor = bias_node->src[0];
43114355
} else {
43124356
continue;
@@ -4331,19 +4375,20 @@ static int ggml_cuda_try_fuse(ggml_backend_cuda_context * cuda_ctx, ggml_cgraph
43314375
}
43324376

43334377
ggml_cuda_mm_fusion_args_host fusion_data{};
4334-
fusion_data.x_bias = bias_tensor;
4378+
fusion_data.x_bias = bias_tensor;
4379+
fusion_data.residual_only = reshape_bridge;
43354380

43364381
if (ggml_cuda_should_fuse_mul_mat_vec_f(mm_node)) {
43374382
ggml_cuda_mul_mat_vec_f(*cuda_ctx, src0, src1, ids, bias_node, &fusion_data);
43384383
fused_mul_mat_vec = true;
4339-
fused_node_count = 2;
4384+
fused_node_count = reshape_bridge ? 3 : 2;
43404385
break;
43414386
}
43424387

43434388
if (ggml_cuda_should_fuse_mul_mat_vec_q(mm_node)) {
43444389
ggml_cuda_mul_mat_vec_q(*cuda_ctx, src0, src1, ids, bias_node, &fusion_data);
43454390
fused_mul_mat_vec = true;
4346-
fused_node_count = 2;
4391+
fused_node_count = reshape_bridge ? 3 : 2;
43474392
break;
43484393
}
43494394
}

external/ggml/src/ggml-cuda/mmvq.cu

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ static constexpr __host__ __device__ int calc_rows_per_block(int ncols_dst, int
391391
return 1;
392392
}
393393

394-
template <ggml_type type, int ncols_dst, bool has_fusion, bool small_k = false>
394+
template <ggml_type type, int ncols_dst, bool has_fusion, bool small_k = false, bool residual_only = false>
395395
__launch_bounds__(calc_nwarps(type, ncols_dst, get_device_table_id())*ggml_cuda_get_physical_warp_size(), 1)
396396
static __global__ void mul_mat_vec_q(
397397
const void * __restrict__ vx, const void * __restrict__ vy, const int32_t * __restrict__ ids, const ggml_cuda_mm_fusion_args_device fusion, float * __restrict__ dst,
@@ -401,6 +401,8 @@ static __global__ void mul_mat_vec_q(
401401
const uint32_t stride_sample_x, const uint32_t stride_sample_y, const uint32_t stride_sample_dst,
402402
const uint32_t ids_stride) {
403403

404+
static_assert(!residual_only || has_fusion, "residual-only MMVQ requires fusion");
405+
404406
constexpr int qk = ggml_cuda_type_traits<type>::qk;
405407
constexpr int qi = ggml_cuda_type_traits<type>::qi;
406408
constexpr int vdr = get_vdr_mmvq(type);
@@ -437,7 +439,10 @@ static __global__ void mul_mat_vec_q(
437439
const float * gate_bias = nullptr;
438440
ggml_glu_op active_glu;
439441

440-
if constexpr (has_fusion) {
442+
if constexpr (residual_only) {
443+
use_bias = true;
444+
x_bias = (const float *) fusion.x_bias;
445+
} else if constexpr (has_fusion) {
441446
use_gate = fusion.gate != nullptr;
442447
use_bias = fusion.x_bias != nullptr;
443448
use_gate_bias = fusion.gate_bias != nullptr && use_gate;
@@ -478,7 +483,7 @@ static __global__ void mul_mat_vec_q(
478483

479484
// partial sum for each thread
480485
float tmp[ncols_dst][rows_per_cuda_block] = {{0.0f}};
481-
float tmp_gate[ncols_dst][rows_per_cuda_block] = {{0.0f}};
486+
float tmp_gate[ncols_dst][residual_only ? 1 : rows_per_cuda_block] = {{0.0f}};
482487

483488
const block_q8_1 * y = ((const block_q8_1 *) vy) + sample_y*stride_sample_y + channel_y*stride_channel_y;
484489
const int kbx_offset = sample_x*stride_sample_x + channel_x*stride_channel_x + row0*stride_row_x;
@@ -495,7 +500,7 @@ static __global__ void mul_mat_vec_q(
495500
for (int i = 0; i < rows_per_cuda_block; ++i) {
496501
tmp[j][i] += vec_dot_q_cuda(
497502
vx, &y[j*stride_col_y + kby], kbx_offset + i*stride_row_x + kbx, kqs);
498-
if constexpr (has_fusion) {
503+
if constexpr (has_fusion && !residual_only) {
499504
if (use_gate) {
500505
tmp_gate[j][i] += vec_dot_q_cuda(
501506
vgate, &y[j*stride_col_y + kby], kbx_offset + i*stride_row_x + kbx, kqs);
@@ -506,8 +511,8 @@ static __global__ void mul_mat_vec_q(
506511
}
507512

508513
__shared__ float tmp_shared[nwarps-1 > 0 ? nwarps-1 : 1][ncols_dst][rows_per_cuda_block][warp_size];
509-
__shared__ float tmp_shared_gate[(has_fusion && (nwarps-1 > 0)) ? nwarps-1 : 1][ncols_dst][rows_per_cuda_block][warp_size];
510-
if constexpr (!has_fusion) {
514+
__shared__ float tmp_shared_gate[(has_fusion && !residual_only && (nwarps-1 > 0)) ? nwarps-1 : 1][ncols_dst][residual_only ? 1 : rows_per_cuda_block][warp_size];
515+
if constexpr (!has_fusion || residual_only) {
511516
(void) tmp_shared_gate;
512517
} else if (!use_gate) {
513518
(void) tmp_shared_gate;
@@ -519,7 +524,7 @@ static __global__ void mul_mat_vec_q(
519524
#pragma unroll
520525
for (int i = 0; i < rows_per_cuda_block; ++i) {
521526
tmp_shared[threadIdx.y-1][j][i][threadIdx.x] = tmp[j][i];
522-
if constexpr (has_fusion) {
527+
if constexpr (has_fusion && !residual_only) {
523528
if (use_gate) {
524529
tmp_shared_gate[threadIdx.y-1][j][i][threadIdx.x] = tmp_gate[j][i];
525530
}
@@ -542,14 +547,14 @@ static __global__ void mul_mat_vec_q(
542547
#pragma unroll
543548
for (int l = 0; l < nwarps-1; ++l) {
544549
tmp[j][i] += tmp_shared[l][j][i][threadIdx.x];
545-
if constexpr (has_fusion) {
550+
if constexpr (has_fusion && !residual_only) {
546551
if (use_gate) {
547552
tmp_gate[j][i] += tmp_shared_gate[l][j][i][threadIdx.x];
548553
}
549554
}
550555
}
551556
tmp[j][i] = warp_reduce_sum<warp_size>(tmp[j][i]);
552-
if constexpr (has_fusion) {
557+
if constexpr (has_fusion && !residual_only) {
553558
if (use_gate) {
554559
tmp_gate[j][i] = warp_reduce_sum<warp_size>(tmp_gate[j][i]);
555560
}
@@ -562,33 +567,35 @@ static __global__ void mul_mat_vec_q(
562567
if (use_bias) {
563568
result += x_biases[j];
564569
}
565-
if (use_gate) {
566-
float gate_value = tmp_gate[j][threadIdx.x];
567-
if (use_gate_bias) {
568-
gate_value += gate_biases[j];
569-
}
570-
switch (active_glu) {
571-
case GGML_GLU_OP_SWIGLU:
572-
result *= ggml_cuda_op_silu_single(gate_value);
573-
break;
574-
case GGML_GLU_OP_GEGLU:
575-
result *= ggml_cuda_op_gelu_single(gate_value);
576-
break;
577-
case GGML_GLU_OP_SWIGLU_OAI: {
578-
result = ggml_cuda_op_swiglu_oai_single(gate_value, result);
579-
break;
570+
if constexpr (!residual_only) {
571+
if (use_gate) {
572+
float gate_value = tmp_gate[j][threadIdx.x];
573+
if (use_gate_bias) {
574+
gate_value += gate_biases[j];
575+
}
576+
switch (active_glu) {
577+
case GGML_GLU_OP_SWIGLU:
578+
result *= ggml_cuda_op_silu_single(gate_value);
579+
break;
580+
case GGML_GLU_OP_GEGLU:
581+
result *= ggml_cuda_op_gelu_single(gate_value);
582+
break;
583+
case GGML_GLU_OP_SWIGLU_OAI: {
584+
result = ggml_cuda_op_swiglu_oai_single(gate_value, result);
585+
break;
586+
}
587+
default:
588+
result = result * gate_value;
589+
break;
580590
}
581-
default:
582-
result = result * gate_value;
583-
break;
584591
}
585592
}
586593
}
587594
dst[j*stride_col_dst + threadIdx.x] = result;
588595
}
589596
}
590597

591-
if constexpr (!has_fusion) {
598+
if constexpr (!has_fusion || residual_only) {
592599
GGML_UNUSED_VARS(use_gate, use_bias, use_gate_bias, active_glu, gate_bias, x_bias, tmp_gate);
593600
}
594601
}
@@ -681,6 +688,17 @@ static void mul_mat_vec_q_switch_fusion(
681688
const bool has_fusion = fusion.gate != nullptr || fusion.x_bias != nullptr || fusion.gate_bias != nullptr;
682689
if constexpr (c_ncols_dst == 1) {
683690
if (has_fusion) {
691+
if constexpr (type == GGML_TYPE_Q8_0) {
692+
if (fusion.residual_only) {
693+
mul_mat_vec_q<type, c_ncols_dst, true, small_k, true><<<block_nums, block_dims, nbytes_shared, stream>>>
694+
(vx, vy, ids, fusion, dst, ncols_x, nchannels_y, stride_row_x, stride_col_y, stride_col_dst,
695+
channel_ratio, stride_channel_x, stride_channel_y, stride_channel_dst,
696+
sample_ratio, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride);
697+
return;
698+
}
699+
}
700+
701+
GGML_ASSERT(!fusion.residual_only && "residual-only MMVQ is supported only for Q8_0");
684702
mul_mat_vec_q<type, c_ncols_dst, true, small_k><<<block_nums, block_dims, nbytes_shared, stream>>>
685703
(vx, vy, ids, fusion, dst, ncols_x, nchannels_y, stride_row_x, stride_col_y, stride_col_dst,
686704
channel_ratio, stride_channel_x, stride_channel_y, stride_channel_dst,
@@ -1078,6 +1096,7 @@ void ggml_cuda_mul_mat_vec_q(
10781096
fusion_local.gate_bias = fusion->gate_bias->data;
10791097
}
10801098
fusion_local.glu_op = fusion->glu_op;
1099+
fusion_local.residual_only = fusion->residual_only;
10811100
}
10821101

10831102
// If src0 is a temporary compute buffer, clear any potential padding.

0 commit comments

Comments
 (0)