/* SPDX-License-Identifier: AGPL-3.0-or-later */ /* Copyright (C) 2026 David (Corey) Herrera */ /* Validate + benchmark the large-M prefill GEMM fm_gemm_q4_k_f16 against a CPU * F32 reference (dequant of the same Q4_K bytes, then GEMM). F16 tolerance. * * Usage: test_gemm_q4_k_f16 [M] [N] [K] (defaults 1369 x 2048 x 1024) */ #include "metal/fm_metal.h" #include #include #include #include #include #include static uint16_t f32_to_f16(float f) { uint32_t x; memcpy(&x, &f, 4); uint32_t sign = (x >> 16) & 0x8000u; int32_t exp = (int32_t)((x >> 23) & 0xFF) - 127 + 15; uint32_t mant = x & 0x7FFFFFu; if (exp <= 0) return (uint16_t)sign; if (exp >= 31) return (uint16_t)(sign | 0x7C00u); return (uint16_t)(sign | ((uint32_t)exp << 10) | (mant >> 13)); } static float f16_to_f32(uint16_t h) { uint32_t sign = (uint32_t)(h & 0x8000u) << 16; uint32_t exp = (h >> 10) & 0x1F, mant = h & 0x3FF, out; if (exp == 0) { if (!mant) out = sign; else { exp = 127-15+1; while(!(mant&0x400)){mant<<=1;exp--;} mant&=0x3FF; out=sign|(exp<<23)|(mant<<13);} } else if (exp == 31) out = sign | 0x7F800000u | (mant<<13); else out = sign | ((exp-15+127)<<23) | (mant<<13); float f; memcpy(&f,&out,4); return f; } static double now_ms(void){ struct timespec t; clock_gettime(CLOCK_MONOTONIC,&t); return t.tv_sec*1000.0+t.tv_nsec/1e6; } static uint32_t rng=0x9e3779b9u; static uint32_t ur(void){rng^=rng<<13;rng^=rng>>17;rng^=rng<<5;return rng;} static float fr(void){ return (float)(ur()%2000)/1000.0f-1.0f; } #define QK 144u static void deq_block(const uint8_t*blk,float*o){ float d=f16_to_f32(((const uint16_t*)blk)[0]), dmin=f16_to_f32(((const uint16_t*)blk)[1]); const uint8_t*sr=blk+4,*qs=blk+16; uint8_t sc[8],mn[8]; for(int j=0;j<4;j++){sc[j]=sr[j]&63;mn[j]=sr[j+4]&63;} for(int j=4;j<8;j++){sc[j]=(sr[j+4]&0xF)|((sr[j-4]>>6)<<4);mn[j]=(sr[j+4]>>4)|((sr[j]>>6)<<4);} for(int p=0;p<4;p++){float d1=d*sc[2*p],m1=dmin*mn[2*p],d2=d*sc[2*p+1],m2=dmin*mn[2*p+1];const uint8_t*q=qs+p*32;int o0=p*64; for(int l=0;l<32;l++){o[o0+l]=d1*(q[l]&0xF)-m1;o[o0+32+l]=d2*(q[l]>>4)-m2;}} } int main(int argc,char**argv){ int M=argc>1?atoi(argv[1]):1369, N=argc>2?atoi(argv[2]):2048, K=argc>3?atoi(argv[3]):1024; if(K%256){fprintf(stderr,"K must be multiple of 256\n");return 1;} if(fm_init()||!fm_available()){fprintf(stderr,"Metal unavailable\n");return 77;} int nblk=K/256; size_t rs=(size_t)nblk*QK, wb=(size_t)N*rs; uint8_t*W=malloc(wb); for(int r=0;rmaxabs)maxabs=e;sumsq+=(double)ref[i]*ref[i];} double rms=sqrt(sumsq/((double)M*N)), rel=maxabs/(rms+1e-9); printf("gemm_q4_k_f16 M=%d N=%d K=%d\n",M,N,K); printf(" err/RMS=%.4g (RMS=%.3g) -> %s\n", rel, rms, rel<2e-2?"PASS":"FAIL"); printf(" GPU %.2f ms/call CPU-ref(F32) %.0f ms\n", gpu_ms, cpu_ms); fm_buffer_free(bW);fm_buffer_free(bX);fm_buffer_free(bO);free(W);free(X);free(ref); return rel<2e-2?0:1; }