IDOT: NEON single-token int4 gate (g_i4s/I4S) — SDOT makes S=1 decode pay on Apple Silicon (+14%, expert-matmul -16%) (#10)

This commit is contained in:
RDouglas
2026-07-09 11:49:06 +01:00
committed by GitHub
parent 8040dac645
commit 3daceaf5ec
+15 -8
View File
@@ -241,7 +241,14 @@ static void matmul_i2(float *y, const float *x, const uint8_t *q2, const float *
#define IDOT_KERNEL "scalar" #define IDOT_KERNEL "scalar"
#endif #endif
static int g_idot=1; static int g_idot=1;
static int g_i4s=2; /* min batch S for int4 IDOT; VNNI default 1 (set in main), I4S=n overrides */ #if defined(__ARM_NEON) && defined(__ARM_FEATURE_DOTPROD)
static int g_i4s=1; /* SDOT presente: int4 IDOT conviene anche a S=1 (decode). Misurato
* su Apple M-series: +14%%, expert-matmul -16%%. EN: with SDOT, int4
* IDOT pays even at S=1 (decode); measured on Apple M-series. */
#else
static int g_i4s=2; /* senza SDOT / altrove: soglia originale (misura AVX2 dell'autore).
* EN: without SDOT / elsewhere: original threshold (author's AVX2). */
#endif
static inline float qrow_i8(const float *x, int8_t *q, int I){ static inline float qrow_i8(const float *x, int8_t *q, int I){
float amax=0; for(int i=0;i<I;i++){ float a=fabsf(x[i]); if(a>amax)amax=a; } float amax=0; for(int i=0;i<I;i++){ float a=fabsf(x[i]); if(a>amax)amax=a; }
float s=amax/127.f; if(s<1e-12f) s=1e-12f; float inv=1.f/s; float s=amax/127.f; if(s<1e-12f) s=1e-12f; float inv=1.f/s;
@@ -380,8 +387,12 @@ static void matmul_i4_idot(float *y, const int8_t *xq, const float *sx, const ui
static void matmul_qt(float *y, const float *x, const QT *w, int S){ static void matmul_qt(float *y, const float *x, const QT *w, int S){
if(w->fmt==0){ matmul(y,x,w->qf,S,w->I,w->O); return; } if(w->fmt==0){ matmul(y,x,w->qf,S,w->I,w->O); return; }
/* misurato (mmbench, 12 core): int8 IDOT vince sempre (1.4-2.5x); int4 IDOT vince /* int8 IDOT vince sempre (1.4-2.5x). int4 IDOT: l'autore su AVX2 trovo' che a S=1
* solo con S>=2 (1.8x) e PERDE a S=1 (unpack+sign non ripagano su una riga) */ * non ripaga (soglia S>=2); ma su ARM/SDOT il singolo token CONVIENE (vedi g_i4s /
* PR #9 per il gemello VNNI). Soglia configurabile con I4S.
* EN: int8 IDOT always wins (1.4-2.5x). int4 IDOT: on AVX2 the author found S=1 didn't
* pay (S>=2 gate); on ARM/SDOT single-token DOES pay (see g_i4s / PR #9 for the VNNI
* twin). Threshold configurable via I4S. */
if(g_idot && (w->fmt==1 || (w->fmt==2 && S>=g_i4s))){ if(g_idot && (w->fmt==1 || (w->fmt==2 && S>=g_i4s))){
int I=w->I; int I=w->I;
int8_t *xq=malloc((size_t)S*I); float sxb[64]; float *sx=S<=64?sxb:falloc(S); int8_t *xq=malloc((size_t)S*I); float sxb[64]; float *sx=S<=64?sxb:falloc(S);
@@ -1802,11 +1813,7 @@ int main(int argc, char **argv){
g_draft = getenv("DRAFT")?atoi(getenv("DRAFT")):-1; /* -1 = auto: 3 se MTP, 0 senza */ g_draft = getenv("DRAFT")?atoi(getenv("DRAFT")):-1; /* -1 = auto: 3 se MTP, 0 senza */
g_direct = getenv("DIRECT")?atoi(getenv("DIRECT")):0; g_direct = getenv("DIRECT")?atoi(getenv("DIRECT")):0;
g_idot = getenv("IDOT")?atoi(getenv("IDOT")):1; /* 0 = kernel f32 esatti (A/B) */ g_idot = getenv("IDOT")?atoi(getenv("IDOT")):1; /* 0 = kernel f32 esatti (A/B) */
#if defined(__AVX512VNNI__) && defined(__AVX512BW__) g_i4s = getenv("I4S")?atoi(getenv("I4S")):g_i4s; /* soglia S per int4 IDOT (default: 1 con SDOT) / S threshold for int4 IDOT (default 1 with SDOT) */
g_i4s = getenv("I4S")?atoi(getenv("I4S")):1; /* VNNI: int4 IDOT pays even at S=1 */
#else
g_i4s = getenv("I4S")?atoi(getenv("I4S")):2; /* AVX2: single row loses (mmbench) */
#endif
g_absorb = getenv("ABSORB")?atoi(getenv("ABSORB")):-1; /* -1 auto: assorbita per S<=4 */ g_absorb = getenv("ABSORB")?atoi(getenv("ABSORB")):-1; /* -1 auto: assorbita per S<=4 */
g_dsa_force = getenv("DSA_FORCE")?atoi(getenv("DSA_FORCE")):0; g_dsa_force = getenv("DSA_FORCE")?atoi(getenv("DSA_FORCE")):0;
g_temp = getenv("TEMP")?atof(getenv("TEMP")):-1; /* -1 = auto (1.0 chat/testo, greedy altrove) */ g_temp = getenv("TEMP")?atof(getenv("TEMP")):-1; /* -1 = auto (1.0 chat/testo, greedy altrove) */