@@ -3776,35 +3776,48 @@ kernel void kernel_mul_mv_q2_0_f32(
37763776}
37773777
37783778// Fused RMS_NORM(x)*norm_w -> quantized mat-vec, batch=1 decode only.
3779- // Replaces two dispatches (kernel_rms_norm_mul_f32 + kernel_mul_mv_{q1_0,q2_0}_f32) with one:
3780- // the normed vector never leaves threadgroup memory. Reuses this file's own block_q1_0/
3781- // block_q2_0 dequant/dot-product primitives verbatim (block_q_n_dot_y overloads above) --
3782- // same NSG/NR0 simdgroup tiling convention as kernel_mul_mv_{q1_0,q2_0}_f32_impl.
3783- #define NSG_RMSNORM_QMV 4
3779+ // REDESIGNED (v2): v1 staged the full ne00-element normed vector in THREADGROUP memory
3780+ // (~20KB/threadgroup for ne00=5120) to avoid a device round-trip. Measured on real hardware:
3781+ // that regressed tg128 decode ~38% (41.35->25.62 t/s) despite cutting node/barrier counts,
3782+ // because a ~20KB threadgroup memory footprint caps occupancy to ~1 resident threadgroup/core
3783+ // on Apple GPUs -- exactly wrong for a bandwidth-bound batch-1 decode kernel, where occupancy
3784+ // is what hides DRAM latency while streaming the (much larger) quantized weight matrix.
3785+ //
3786+ // The v1 premise was wrong: eliminating "redundant" per-threadgroup reads of x/norm_w was
3787+ // never the real saving -- x and norm_w are tiny (~20KB each) and stay resident in Apple
3788+ // Silicon's SLC after the first touch, so every threadgroup re-reading them from "device"
3789+ // memory is an L2-class cache hit, not DRAM traffic. The actual, real saving from fusion is
3790+ // removing kernel_rms_norm_mul_f32's device-memory WRITE of the normed vector (and the
3791+ // unfused mul_mv's corresponding first read of it) -- one dispatch instead of two, zero
3792+ // extra threadgroup memory. v2: compute the norm SCALE once per threadgroup (tiny reduction,
3793+ // unchanged from v1) but never materialize the normed vector anywhere -- apply
3794+ // x[i]*scale*norm_w[i] inline at the same point kernel_mul_mv_{q1_0,q2_0}_f32_impl reads y[i],
3795+ // reading x/norm_w straight from device memory exactly like that kernel does. Threadgroup
3796+ // memory drops from ~20KB to 128 bytes (the reduction scratch only) -- occupancy should now
3797+ // match the baseline kernel's. NSG/NR0 also switched from an arbitrary 4/8 to match upstream's
3798+ // proven-tuned N_SG_Q1_0/N_SG_Q2_0=2, N_R0_Q1_0/N_R0_Q2_0=8 for this exact quant format.
3799+ #define NSG_RMSNORM_QMV 2
37843800#define NR0_RMSNORM_QMV 8
37853801
3786- template <typename block_t , int QK , int nr0>
3787- void kernel_rmsnorm_qmv_impl (
3788- constant ggml_metal_kargs_rmsnorm_qmv & args,
3789- device const char * src0, // quantized weight [ne00, ne01]
3790- device const float * x, // [ne00]
3791- device const float * norm_w, // [ne00]
3792- device float * dst, // [ne01]
3793- threadgroup float * xn, // ne00 floats
3794- threadgroup float * red, // >= 32 floats
3802+ // Shared norm-scale reduction: collaboratively computes 1/sqrt(mean(x^2)+eps) across the whole
3803+ // threadgroup. Reads x straight from device memory (cache-resident after first touch, see
3804+ // comment above) -- writes nothing, returns the scalar in every thread.
3805+ inline float rmsnorm_scale (
3806+ device const float * x,
3807+ int32_t ne00,
3808+ float eps,
3809+ threadgroup float * red, // >= 32 floats
37953810 ushort3 tpitg,
37963811 ushort3 ntg,
37973812 ushort tiisg,
3798- ushort sgitg,
3799- ushort sgpg, // simdgroups per threadgroup (== NSG_RMSNORM_QMV at dispatch)
3800- uint tgx) {
3813+ ushort sgitg) {
38013814 if (sgitg == 0 ) {
38023815 red[tiisg] = 0 .0f ;
38033816 }
38043817 threadgroup_barrier (mem_flags::mem_threadgroup);
38053818
38063819 float sumsq = 0 .0f ;
3807- for (int i = tpitg.x ; i < args. ne00 ; i += ntg.x ) {
3820+ for (int i = tpitg.x ; i < ne00; i += ntg.x ) {
38083821 const float v = x[i];
38093822 sumsq += v * v;
38103823 }
@@ -3818,12 +3831,24 @@ void kernel_rmsnorm_qmv_impl(
38183831 float total = red[tiisg];
38193832 total = simd_sum (total);
38203833
3821- const float scale = 1 .0f / sqrt (total / (float ) args.ne00 + args.eps );
3834+ return 1 .0f / sqrt (total / (float ) ne00 + eps);
3835+ }
38223836
3823- for (int i = tpitg.x ; i < args.ne00 ; i += ntg.x ) {
3824- xn[i] = x[i] * scale * norm_w[i];
3825- }
3826- threadgroup_barrier (mem_flags::mem_threadgroup);
3837+ template <typename block_t , int QK , int nr0>
3838+ void kernel_rmsnorm_qmv_impl (
3839+ constant ggml_metal_kargs_rmsnorm_qmv & args,
3840+ device const char * src0, // quantized weight [ne00, ne01]
3841+ device const float * x, // [ne00]
3842+ device const float * norm_w, // [ne00]
3843+ device float * dst, // [ne01]
3844+ threadgroup float * red, // >= 32 floats
3845+ ushort3 tpitg,
3846+ ushort3 ntg,
3847+ ushort tiisg,
3848+ ushort sgitg,
3849+ ushort sgpg, // simdgroups per threadgroup (== NSG_RMSNORM_QMV at dispatch)
3850+ uint tgx) {
3851+ const float scale = rmsnorm_scale (x, args.ne00 , args.eps , red, tpitg, ntg, tiisg, sgitg);
38273852
38283853 const int nb = args.ne00 / QK ;
38293854 const int first_row = (int (tgx) * sgpg + sgitg) * nr0;
@@ -3840,18 +3865,20 @@ void kernel_rmsnorm_qmv_impl(
38403865 const short ix = tiisg / 8 ;
38413866 const short il = (tiisg % 8 ) * 16 ;
38423867
3843- threadgroup const float * yb = xn + ix * QK + il;
3868+ device const float * xb = x + ix * QK + il;
3869+ device const float * wb = norm_w + ix * QK + il;
38443870
38453871 for (int ib = ix; ib < nb; ib += N_SIMDWIDTH / 8 ) {
38463872 float sumy = 0 .0f ;
38473873 FOR_UNROLL (short i = 0 ; i < 16 ; i++) {
3848- yl[i] = yb [i];
3849- sumy += yb [i];
3874+ yl[i] = xb[i] * scale * wb [i];
3875+ sumy += yl [i];
38503876 }
38513877 FOR_UNROLL (short row = 0 ; row < nr0; row++) {
38523878 sumf[row] += block_q_n_dot_y (ax[row] + ib, sumy, yl, il);
38533879 }
3854- yb += QK * (N_SIMDWIDTH / 8 );
3880+ xb += QK * (N_SIMDWIDTH / 8 );
3881+ wb += QK * (N_SIMDWIDTH / 8 );
38553882 }
38563883
38573884 for (int row = 0 ; row < nr0; ++row) {
@@ -3869,15 +3896,14 @@ kernel void kernel_rmsnorm_mv_q1_0_f32(
38693896 device const float * x [[buffer(2)]] ,
38703897 device const float * norm_w [[buffer(3)]] ,
38713898 device float * dst [[buffer(4)]] ,
3872- threadgroup float * xn [[threadgroup(0)]] ,
3873- threadgroup float * red [[threadgroup(1)]] ,
3899+ threadgroup float * red [[threadgroup(0)]] ,
38743900 uint3 tgpig [[threadgroup_position_in_grid]] ,
38753901 ushort3 tpitg [[thread_position_in_threadgroup]] ,
38763902 ushort3 ntg [[threads_per_threadgroup]] ,
38773903 ushort tiisg [[thread_index_in_simdgroup]] ,
38783904 ushort sgitg [[simdgroup_index_in_threadgroup]] ) {
38793905 kernel_rmsnorm_qmv_impl<block_q1_0, QK1_0 , NR0_RMSNORM_QMV >(
3880- args, src0, x, norm_w, dst, xn, red, tpitg, ntg, tiisg, sgitg, NSG_RMSNORM_QMV , tgpig.x );
3906+ args, src0, x, norm_w, dst, red, tpitg, ntg, tiisg, sgitg, NSG_RMSNORM_QMV , tgpig.x );
38813907}
38823908
38833909[[host_name("kernel_rmsnorm_mv_q2_0_f32")]]
@@ -3887,67 +3913,43 @@ kernel void kernel_rmsnorm_mv_q2_0_f32(
38873913 device const float * x [[buffer(2)]] ,
38883914 device const float * norm_w [[buffer(3)]] ,
38893915 device float * dst [[buffer(4)]] ,
3890- threadgroup float * xn [[threadgroup(0)]] ,
3891- threadgroup float * red [[threadgroup(1)]] ,
3916+ threadgroup float * red [[threadgroup(0)]] ,
38923917 uint3 tgpig [[threadgroup_position_in_grid]] ,
38933918 ushort3 tpitg [[thread_position_in_threadgroup]] ,
38943919 ushort3 ntg [[threads_per_threadgroup]] ,
38953920 ushort tiisg [[thread_index_in_simdgroup]] ,
38963921 ushort sgitg [[simdgroup_index_in_threadgroup]] ) {
38973922 kernel_rmsnorm_qmv_impl<block_q2_0, QK2_0 , NR0_RMSNORM_QMV >(
3898- args, src0, x, norm_w, dst, xn, red, tpitg, ntg, tiisg, sgitg, NSG_RMSNORM_QMV , tgpig.x );
3923+ args, src0, x, norm_w, dst, red, tpitg, ntg, tiisg, sgitg, NSG_RMSNORM_QMV , tgpig.x );
38993924}
39003925
39013926// Multi-consumer generalization: ONE shared RMSNorm(x)*norm_w feeds N independently-shaped
39023927// quantized mat-vecs (gate+up MLP N=2, wqkv+wqkv_gate GDN input N=2, attention QKV-family
39033928// N=3/4 -- exact fan-outs measured live on Bonsai-27B, see bonsai-27b-megakernel-repo memory).
39043929// Grid: tgpig.y selects which of the N (weight, dst) pairs this threadgroup serves; tgpig.x
3905- // tiles that matrix's output rows. The norm is recomputed redundantly per y-slot (cheap --
3906- // O(ne00) vs the O(ne00*ne01) matvec it feeds) in exchange for N-1 fewer kernel dispatches and
3907- // N-1 fewer device-memory round-trips of the shared normed vector.
3930+ // tiles that matrix's output rows. `args.which` selects the sibling THIS dispatch serves --
3931+ // dispatched once per sibling with a grid sized to that sibling's own ne01 (see host-side
3932+ // comment: a uniform (max_tg, N) grid wasted >99% of threadgroups when siblings are wildly
3933+ // imbalanced, e.g. observed ne01=[10240,48,48,6144] on one real fusion site). Norm SCALE only
3934+ // (no threadgroup-staged vector), x/norm_w read straight from device memory, recomputed
3935+ // redundantly per dispatch (cheap scalar reduction vs the O(ne00*ne01) matvec it feeds).
39083936template <typename block_t , int QK , int nr0, int N>
39093937void kernel_rmsnorm_qmv_multi_impl (
39103938 constant ggml_metal_kargs_rmsnorm_qmv_multi & args,
39113939 device const char * srcs[N],
39123940 device const float * x,
39133941 device const float * norm_w,
39143942 device float * dsts[N],
3915- threadgroup float * xn,
39163943 threadgroup float * red,
39173944 ushort3 tpitg,
39183945 ushort3 ntg,
39193946 ushort tiisg,
39203947 ushort sgitg,
39213948 ushort sgpg,
39223949 uint3 tgpig) {
3923- if (sgitg == 0 ) {
3924- red[tiisg] = 0 .0f ;
3925- }
3926- threadgroup_barrier (mem_flags::mem_threadgroup);
3950+ const float scale = rmsnorm_scale (x, args.ne00 , args.eps , red, tpitg, ntg, tiisg, sgitg);
39273951
3928- float sumsq = 0 .0f ;
3929- for (int i = tpitg.x ; i < args.ne00 ; i += ntg.x ) {
3930- const float v = x[i];
3931- sumsq += v * v;
3932- }
3933- sumsq = simd_sum (sumsq);
3934-
3935- if (tiisg == 0 ) {
3936- red[sgitg] = sumsq;
3937- }
3938- threadgroup_barrier (mem_flags::mem_threadgroup);
3939-
3940- float total = red[tiisg];
3941- total = simd_sum (total);
3942-
3943- const float scale = 1 .0f / sqrt (total / (float ) args.ne00 + args.eps );
3944-
3945- for (int i = tpitg.x ; i < args.ne00 ; i += ntg.x ) {
3946- xn[i] = x[i] * scale * norm_w[i];
3947- }
3948- threadgroup_barrier (mem_flags::mem_threadgroup);
3949-
3950- const uint n = tgpig.y ; // which (weight, dst) pair, 0..N-1
3952+ const uint n = (uint) args.which ;
39513953 if (n >= (uint) N || args.ne01 [n] <= 0 ) {
39523954 return ;
39533955 }
@@ -3970,18 +3972,20 @@ void kernel_rmsnorm_qmv_multi_impl(
39703972 const short ix = tiisg / 8 ;
39713973 const short il = (tiisg % 8 ) * 16 ;
39723974
3973- threadgroup const float * yb = xn + ix * QK + il;
3975+ device const float * xb = x + ix * QK + il;
3976+ device const float * wb = norm_w + ix * QK + il;
39743977
39753978 for (int ib = ix; ib < nb; ib += N_SIMDWIDTH / 8 ) {
39763979 float sumy = 0 .0f ;
39773980 FOR_UNROLL (short i = 0 ; i < 16 ; i++) {
3978- yl[i] = yb [i];
3979- sumy += yb [i];
3981+ yl[i] = xb[i] * scale * wb [i];
3982+ sumy += yl [i];
39803983 }
39813984 FOR_UNROLL (short row = 0 ; row < nr0; row++) {
39823985 sumf[row] += block_q_n_dot_y (ax[row] + ib, sumy, yl, il);
39833986 }
3984- yb += QK * (N_SIMDWIDTH / 8 );
3987+ xb += QK * (N_SIMDWIDTH / 8 );
3988+ wb += QK * (N_SIMDWIDTH / 8 );
39853989 }
39863990
39873991 for (int row = 0 ; row < nr0; ++row) {
@@ -4006,8 +4010,7 @@ kernel void NAME( \
40064010 device float * dst_1 [[buffer(8)]] , \
40074011 device float * dst_2 [[buffer(9)]] , \
40084012 device float * dst_3 [[buffer(10)]] , \
4009- threadgroup float * xn [[threadgroup(0)]] , \
4010- threadgroup float * red [[threadgroup(1)]] , \
4013+ threadgroup float * red [[threadgroup(0)]] , \
40114014 uint3 tgpig [[threadgroup_position_in_grid]] , \
40124015 ushort3 tpitg [[thread_position_in_threadgroup]] , \
40134016 ushort3 ntg [[threads_per_threadgroup]] , \
@@ -4016,7 +4019,7 @@ kernel void NAME( \
40164019 device const char * srcs[4 ] = { src0_0, src0_1, src0_2, src0_3 }; \
40174020 device float * dsts[4 ] = { dst_0, dst_1, dst_2, dst_3 }; \
40184021 kernel_rmsnorm_qmv_multi_impl<BLOCK_T , QK , NR0_RMSNORM_QMV , N>( \
4019- args, srcs, x, norm_w, dsts, xn, red, tpitg, ntg, tiisg, sgitg, NSG_RMSNORM_QMV , tgpig); \
4022+ args, srcs, x, norm_w, dsts, red, tpitg, ntg, tiisg, sgitg, NSG_RMSNORM_QMV , tgpig); \
40204023}
40214024
40224025QMV_MULTI_KERNEL (kernel_rmsnorm_mv2_q1_0_f32, block_q1_0, QK1_0 , 2 )
0 commit comments