Skip to content

Commit 5dc3409

Browse files
committed
ggml-cpu: fuse RMS_NORM + MUL on CPU backend
Add a fused rms_norm + mul kernel that computes the output in a single pass, avoiding materialization of the intermediate rms_norm result. The graph compute loop detects the pattern and dispatches to the fused path. fusion can be disabled via the GGML_CPU_DISABLE_FUSION environment variable.
1 parent 5594d13 commit 5dc3409

3 files changed

Lines changed: 116 additions & 1 deletion

File tree

ggml/src/ggml-cpu/ggml-cpu.c

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2959,6 +2959,53 @@ struct ggml_cplan ggml_graph_plan(
29592959
return cplan;
29602960
}
29612961

2962+
2963+
// Read env GGML_CPU_DISABLE_FUSION once and cache it
2964+
static bool get_disable_fusion(void) {
2965+
static bool value = false;
2966+
static bool initialized = false;
2967+
if (!initialized) {
2968+
value = (getenv("GGML_CPU_DISABLE_FUSION") != NULL);
2969+
initialized = true;
2970+
}
2971+
return value;
2972+
}
2973+
2974+
// Try to fuse the current node with subsequent nodes for better performance.
2975+
// Returns true if fusion was applied.
2976+
static bool ggml_cpu_try_fuse_ops(
2977+
const struct ggml_cgraph * cgraph,
2978+
const int node_n,
2979+
const struct ggml_compute_params * params,
2980+
const struct ggml_cplan * cplan) {
2981+
if (get_disable_fusion() || cplan->use_ref) {
2982+
return false;
2983+
}
2984+
2985+
struct ggml_tensor * node = cgraph->nodes[node_n];
2986+
2987+
if (node->op == GGML_OP_RMS_NORM) {
2988+
// RMS_NORM + MUL fusion
2989+
const enum ggml_op fuse_ops[] = { GGML_OP_RMS_NORM, GGML_OP_MUL };
2990+
if (ggml_can_fuse(cgraph, node_n, fuse_ops, 2)) {
2991+
struct ggml_tensor * mul_node = cgraph->nodes[node_n + 1];
2992+
const struct ggml_tensor * mul_w = (mul_node->src[0] == node)
2993+
? mul_node->src[1] : mul_node->src[0];
2994+
if (node->src[0]->type == GGML_TYPE_F32 &&
2995+
mul_node->type == GGML_TYPE_F32 &&
2996+
mul_w->type == GGML_TYPE_F32 &&
2997+
mul_w->ne[0] == node->ne[0] &&
2998+
mul_w->nb[0] == sizeof(float)) {
2999+
3000+
ggml_compute_forward_rms_norm_mul_fused(params, node, mul_node);
3001+
return true;
3002+
}
3003+
}
3004+
}
3005+
3006+
return false;
3007+
}
3008+
29623009
static thread_ret_t ggml_graph_compute_thread(void * data) {
29633010
struct ggml_compute_state * state = (struct ggml_compute_state *) data;
29643011
struct ggml_threadpool * tp = state->threadpool;
@@ -2995,7 +3042,12 @@ static thread_ret_t ggml_graph_compute_thread(void * data) {
29953042
continue;
29963043
}
29973044

2998-
ggml_compute_forward(&params, node);
3045+
// Try fused ops, fall back to normal compute
3046+
if (ggml_cpu_try_fuse_ops(cgraph, node_n, &params, cplan)) {
3047+
node_n++;
3048+
} else {
3049+
ggml_compute_forward(&params, node);
3050+
}
29993051

30003052
if (state->ith == 0 && cplan->abort_callback &&
30013053
cplan->abort_callback(cplan->abort_callback_data)) {

ggml/src/ggml-cpu/ops.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3782,6 +3782,68 @@ void ggml_compute_forward_rms_norm(
37823782
}
37833783
}
37843784

3785+
// Fused RMS_NORM + MUL: computes dst = rms_norm(src0) * src1 in a single pass.
3786+
// This avoids materializing the intermediate rms_norm result in memory.
3787+
void ggml_compute_forward_rms_norm_mul_fused(
3788+
const ggml_compute_params * params,
3789+
ggml_tensor * rms_norm_dst,
3790+
ggml_tensor * mul_dst) {
3791+
3792+
// src0 = rms_norm input, src1 = mul weight, dst = mul output
3793+
const ggml_tensor * src0 = rms_norm_dst->src[0];
3794+
const ggml_tensor * src1 = (mul_dst->src[0] == rms_norm_dst) ? mul_dst->src[1] : mul_dst->src[0];
3795+
ggml_tensor * dst = mul_dst;
3796+
GGML_ASSERT(mul_dst->src[0] == rms_norm_dst || mul_dst->src[1] == rms_norm_dst);
3797+
3798+
GGML_ASSERT(ggml_are_same_shape(src0, dst));
3799+
GGML_ASSERT(src0->type == GGML_TYPE_F32);
3800+
GGML_ASSERT(src1->type == GGML_TYPE_F32);
3801+
GGML_ASSERT( dst->type == GGML_TYPE_F32);
3802+
GGML_ASSERT(src0->nb[0] == sizeof(float));
3803+
GGML_ASSERT(src1->nb[0] == sizeof(float));
3804+
3805+
const int ith = params->ith;
3806+
const int nth = params->nth;
3807+
3808+
GGML_TENSOR_BINARY_OP_LOCALS
3809+
3810+
GGML_ASSERT(ne10 == ne00); // no column broadcasting on the weight
3811+
3812+
float eps;
3813+
memcpy(&eps, rms_norm_dst->op_params, sizeof(float));
3814+
GGML_ASSERT(eps >= 0.0f);
3815+
for (int64_t i03 = 0; i03 < ne03; i03++) {
3816+
for (int64_t i02 = 0; i02 < ne02; i02++) {
3817+
for (int64_t i01 = ith; i01 < ne01; i01 += nth) {
3818+
const float * x = (float *) ((char *) src0->data + i01*nb01 + i02*nb02 + i03*nb03);
3819+
3820+
ggml_float sum = 0.0;
3821+
// worth switching to explicit SIMD?
3822+
for (int64_t i00 = 0; i00 < ne00; i00++) {
3823+
sum += (ggml_float)(x[i00] * x[i00]);
3824+
}
3825+
3826+
const float mean = sum/ne00;
3827+
const float scale = 1.0f / sqrtf(mean + eps);
3828+
3829+
assert(scale > 0.0f);
3830+
3831+
const int64_t i11 = i01 % ne11;
3832+
const int64_t i12 = i02 % ne12;
3833+
const int64_t i13 = i03 % ne13;
3834+
const float * w = (float *) ((char *) src1->data + i11*nb11 + i12*nb12 + i13*nb13);
3835+
3836+
float * y = (float *) ((char *) dst->data + i01*nb1 + i02*nb2 + i03*nb3);
3837+
3838+
// worth switching to explicit SIMD?
3839+
for (int64_t i00 = 0; i00 < ne00; i00++) {
3840+
y[i00] = x[i00] * scale * w[i00];
3841+
}
3842+
}
3843+
}
3844+
}
3845+
}
3846+
37853847
static void ggml_compute_forward_rms_norm_back_f32(
37863848
const ggml_compute_params * params,
37873849
ggml_tensor * dst) {

ggml/src/ggml-cpu/ops.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ void ggml_compute_forward_concat(const struct ggml_compute_params * params, stru
4444
void ggml_compute_forward_silu_back(const struct ggml_compute_params * params, struct ggml_tensor * dst);
4545
void ggml_compute_forward_norm(const struct ggml_compute_params * params, struct ggml_tensor * dst);
4646
void ggml_compute_forward_rms_norm(const struct ggml_compute_params * params, struct ggml_tensor * dst);
47+
void ggml_compute_forward_rms_norm_mul_fused(const struct ggml_compute_params * params, struct ggml_tensor * rms_norm_dst, struct ggml_tensor * mul_dst);
4748
void ggml_compute_forward_rms_norm_back(const struct ggml_compute_params * params, struct ggml_tensor * dst);
4849
void ggml_compute_forward_group_norm(const struct ggml_compute_params * params, struct ggml_tensor * dst);
4950
void ggml_compute_forward_l2_norm(const struct ggml_compute_params * params, struct ggml_tensor * dst);

0 commit comments

Comments
 (0)