HelloSun commited on
Commit
616131c
·
verified ·
1 Parent(s): 7a0f53f

Add Q4_1 tiled VNNI GEMM for prefill: pp256 +34%, pp2048 +42% vs vec_dot patch

Browse files
README.md CHANGED
@@ -36,10 +36,10 @@ Measured on Intel Xeon Platinum 8559C, 16 threads, with the patch (median of 3 i
36
  | Q4_K_XL, stock llama.cpp | 231 | 274 | 38.2 | 18.363 +- 0.795 |
37
  | **Q4_K_XL, patched** | **296** | **307** | 42.8 | 18.277 +- 0.787 |
38
  | Q4_1, stock llama.cpp | 183 | 189 | 48.3 | 18.260 +- 0.780 |
39
- | **Q4_1, patched** | 199 | 209 | **54.0** | 18.240 +- 0.779 |
40
 
41
- - **Long prompts / large contexts (prefill-heavy): Q4_K_XL**. Its K-quant weights use the tiled VNNI matmul; patched prefill is ~47 % faster than Q4_1.
42
- - **Chat and agent loops with a cached prefix (decode-heavy): Q4_1**. It reads fewer bytes per weight (0.625) and decodes ~26 % faster.
43
  - Quality is equivalent within measurement error: the PPL differences above are smaller than the +-0.8 standard error of a 32-chunk test.
44
  - Single runs on this shared host vary by about +-8 %; the numbers are medians, raw data in `benchmarks/`.
45
 
@@ -48,7 +48,7 @@ Measured on Intel Xeon Platinum 8559C, 16 threads, with the patch (median of 3 i
48
  | file | pp256 | pp2048 | tg128 |
49
  |---|---|---|---|
50
  | Q4_K_XL | 1.28x | 1.12x | 1.12x |
51
- | Q4_1 | 1.09x | 1.10x | 1.12x |
52
 
53
  For Q4_K_XL most of this comes from the Qwen3.6 patch kernels (Q4_K/Q5_K/Q6_K VNNI, AMX tweaks, MoE tile grouping) also
54
  applying to flint; against the Qwen3.6 patch alone the flint-specific part is small (pp256 +12 %, decode +2 %, pp2048 equal).
@@ -56,22 +56,25 @@ The flint-specific changes:
56
 
57
  1. **IQ4_XS x Q8_K `vec_dot` in AVX512-VNNI** (x86 had AVX2 only). Unsigned LUT (+128) with `vpdpbusd`, bias removed through `bsums`, L2 prefetch.
58
  2. **Q4_1 x Q8_1 `vec_dot` in AVX-512VL VNNI**. Two blocks per iteration, packed into one ymm with memory-source `vinserti128`, low/high nibbles each through one `vpdpbusd`, fp16 headers converted together. A 512-bit version using cross-lane shuffles was slower than stock and dropped.
59
- 3. Q5_K 5th-bit merge with `vptestmb` + masked add (bit-identical to the previous version, no speed change).
 
60
 
61
  ## Accuracy
62
 
63
- Wikitext-2 test, 32 chunks x 512, KL divergence against stock llama.cpp logits of the same file:
64
 
65
- | file | KLD patched vs stock | same top-1 | noise floor: stock vs stock with `-fa off` |
66
  |---|---|---|---|
67
- | Q4_K_XL | 0.0666 +- 0.0049 | ~92 % | 0.0762 (also `-ub 128`: 0.0700) |
68
- | Q4_1 | 0.0364 +- 0.0029 | 93.8 % | 0.0385 |
 
69
 
70
  The KLD is **inside the noise floor of stock itself**: flint is very sensitive to tiny numeric differences (changing only the
71
  attention path of stock gives the same KLD), so it does not indicate a defect. Supporting checks: a graph-level `mul_mat` /
72
  `mul_mat_id` test gives the same relative L2 error versus a float reference as stock (Q4_K/Q5_K/Q6_K/IQ4_XS ~7e-3, Q4_1 ~5.4e-3, Q8_0
73
- ~5.4e-3). Integer dot products are exact; only float accumulation order changes. Why small differences get amplified was not
74
- verified (top-6-of-256 expert routing ties is a guess). The earlier version of this repo reported KLD 0.2058 for a different patch.
 
75
 
76
  ## Where the time goes (why further kernel work is limited)
77
 
@@ -79,9 +82,10 @@ Measured DRAM read bandwidth on this host is 125-175 GB/s with 16 threads (singl
79
  weights per token (~1.6 GB dense Q8_0 attention/shared-expert/lm_head, ~0.5 GB for the 6 selected experts), i.e. ~90 GB/s effective:
80
  mostly memory-bound. Per-type effective bandwidth during decode (Q4_K_XL): Q8_0 dense ~100-170 GB/s (lm_head ~170), IQ4_XS experts ~100,
81
  Q5_K experts ~50. The Q5_K gap is not kernel throughput (cache-resident Q5_K and IQ4_XS run the same, 232 vs 218 GB/s); a
82
- prefetch-all-experts experiment did not help and was dropped; the cause was not found. Prefill is compute-bound; FlashAttention
83
- (24 %) and MoE matmul are the largest items. Q4_1 prefill still uses the per-row `vec_dot` path (60 % of prefill time is
84
- `MUL_MAT_ID q4_1`); adding it to the tiled GEMM would need per-32-block float scales in the tiled kernel and was not done.
 
85
 
86
  ## Build and run
87
 
@@ -96,13 +100,13 @@ values). `patches/optional-profiler.patch` adds `GGML_PROF=1` per-op timing.
96
  ## Limitations
97
 
98
  - Validated only on Intel Xeon Platinum 8559C (AVX512-VNNI + AMX), 16 threads, on a shared host. Other CPUs are untested; the new kernels need AVX512-VNNI (the Q4_1 one also AVX512VL/F16C).
99
- - The Q4_K_XL numbers were measured before the Q4_1 kernel was added; the K-quant code path is unchanged except for removing unused constants, and was not re-benchmarked.
100
  - PPL and KLD use a short test (32 x 512 tokens); no downstream task evaluation was run.
101
 
102
  ## Files
103
 
104
  - `patches/flint-cpu-opt.patch` (base `patches/BASE_COMMIT`), `patches/optional-profiler.patch`, `build.sh`, `machine.txt`
105
- - `benchmarks/summary.md` (Q4_K_XL), `benchmarks/q41-summary.md` (Q4_1), raw A/B CSVs, KLD logs, op profiles, KLD bisect, `graph_accuracy_test.cpp`
106
 
107
  ## Credits and license
108
 
 
36
  | Q4_K_XL, stock llama.cpp | 231 | 274 | 38.2 | 18.363 +- 0.795 |
37
  | **Q4_K_XL, patched** | **296** | **307** | 42.8 | 18.277 +- 0.787 |
38
  | Q4_1, stock llama.cpp | 183 | 189 | 48.3 | 18.260 +- 0.780 |
39
+ | **Q4_1, patched** | 266 | 297 | **55.7** | 18.240 +- 0.779 |
40
 
41
+ - **Long prompts / large contexts (prefill-heavy): Q4_K_XL** still prefills a little faster, but patched Q4_1 is now within ~10 % (it uses the tiled VNNI matmul too).
42
+ - **Chat and agent loops with a cached prefix (decode-heavy): Q4_1**. It reads fewer bytes per weight (0.625) and decodes ~30 % faster.
43
  - Quality is equivalent within measurement error: the PPL differences above are smaller than the +-0.8 standard error of a 32-chunk test.
44
  - Single runs on this shared host vary by about +-8 %; the numbers are medians, raw data in `benchmarks/`.
45
 
 
48
  | file | pp256 | pp2048 | tg128 |
49
  |---|---|---|---|
50
  | Q4_K_XL | 1.28x | 1.12x | 1.12x |
51
+ | Q4_1 | 1.46x | 1.57x | 1.15x |
52
 
53
  For Q4_K_XL most of this comes from the Qwen3.6 patch kernels (Q4_K/Q5_K/Q6_K VNNI, AMX tweaks, MoE tile grouping) also
54
  applying to flint; against the Qwen3.6 patch alone the flint-specific part is small (pp256 +12 %, decode +2 %, pp2048 equal).
 
56
 
57
  1. **IQ4_XS x Q8_K `vec_dot` in AVX512-VNNI** (x86 had AVX2 only). Unsigned LUT (+128) with `vpdpbusd`, bias removed through `bsums`, L2 prefetch.
58
  2. **Q4_1 x Q8_1 `vec_dot` in AVX-512VL VNNI**. Two blocks per iteration, packed into one ymm with memory-source `vinserti128`, low/high nibbles each through one `vpdpbusd`, fp16 headers converted together. A 512-bit version using cross-lane shuffles was slower than stock and dropped.
59
+ 3. **Q4_1 in the tiled VNNI GEMM** for prefill. Q4_1 has no super-block scale, so the tiles gain per-32-block f32 scale/min arrays (`sd`/`sm`) and a `Q41` kernel flag; activations reuse the existing Q8_1 `vec_dot_type`. Per 32-block: `d_a * (d_w * dot(q, code) + m_w * sum(code))`, integer dot/sum exact. This lifts pp256/pp2048 by ~34 %/42 % over the `vec_dot` path.
60
+ 4. Q5_K 5th-bit merge with `vptestmb` + masked add (bit-identical to the previous version, no speed change).
61
 
62
  ## Accuracy
63
 
64
+ Wikitext-2 test, 32 chunks x 512, KL divergence against the stated baseline logits of the same file:
65
 
66
+ | comparison | KLD | same top-1 | noise floor: stock vs stock with `-fa off` |
67
  |---|---|---|---|
68
+ | Q4_K_XL, patched vs stock | 0.0666 +- 0.0049 | ~92 % | 0.0762 (also `-ub 128`: 0.0700) |
69
+ | Q4_1, `vec_dot` patch vs stock | 0.0364 +- 0.0029 | 93.8 % | 0.0385 |
70
+ | Q4_1, tiled GEMM vs `vec_dot` patch | 0.0372 +- 0.0026 | 94.2 % | 0.0385 |
71
 
72
  The KLD is **inside the noise floor of stock itself**: flint is very sensitive to tiny numeric differences (changing only the
73
  attention path of stock gives the same KLD), so it does not indicate a defect. Supporting checks: a graph-level `mul_mat` /
74
  `mul_mat_id` test gives the same relative L2 error versus a float reference as stock (Q4_K/Q5_K/Q6_K/IQ4_XS ~7e-3, Q4_1 ~5.4e-3, Q8_0
75
+ ~5.4e-3; the tiled Q4_1 path gives the same 5.29e-3/5.35e-3 as the Q4_1 `vec_dot` path). Integer dot products are exact; only
76
+ float accumulation order changes. Why small differences get amplified was not verified (top-6-of-256 expert routing ties is a
77
+ guess). The earlier version of this repo reported KLD 0.2058 for a different patch.
78
 
79
  ## Where the time goes (why further kernel work is limited)
80
 
 
82
  weights per token (~1.6 GB dense Q8_0 attention/shared-expert/lm_head, ~0.5 GB for the 6 selected experts), i.e. ~90 GB/s effective:
83
  mostly memory-bound. Per-type effective bandwidth during decode (Q4_K_XL): Q8_0 dense ~100-170 GB/s (lm_head ~170), IQ4_XS experts ~100,
84
  Q5_K experts ~50. The Q5_K gap is not kernel throughput (cache-resident Q5_K and IQ4_XS run the same, 232 vs 218 GB/s); a
85
+ prefetch-all-experts experiment did not help and was dropped; the cause was not found. Prefill is compute-bound; once Q4_1 is
86
+ in the tiled GEMM, FlashAttention and the tiled MoE matmul are the largest items (the per-row Q4_1 `vec_dot` remains only on the
87
+ decode path). The Q4_1 tiled kernel carries per-32-block f32 scales/mins in the tiles (`sd`/`sm`) on top of the integer
88
+ sub-block layout used by the K-quants.
89
 
90
  ## Build and run
91
 
 
100
  ## Limitations
101
 
102
  - Validated only on Intel Xeon Platinum 8559C (AVX512-VNNI + AMX), 16 threads, on a shared host. Other CPUs are untested; the new kernels need AVX512-VNNI (the Q4_1 one also AVX512VL/F16C).
103
+ - The Q4_K_XL numbers were measured before the Q4_1 kernels were added; the K-quant code path is unchanged except for removing unused constants, and was not re-benchmarked (the Q4_1 tiled kernel was measured after).
104
  - PPL and KLD use a short test (32 x 512 tokens); no downstream task evaluation was run.
105
 
106
  ## Files
107
 
108
  - `patches/flint-cpu-opt.patch` (base `patches/BASE_COMMIT`), `patches/optional-profiler.patch`, `build.sh`, `machine.txt`
109
+ - `benchmarks/summary.md` (Q4_K_XL), `benchmarks/q41-summary.md` (Q4_1), raw A/B CSVs (`ab-interleaved-flint.csv`, `q41-ab-interleaved.csv`, `q41-tiled-ab-interleaved.csv`), KLD logs (`kld_vs_stock.log`, `q41-kld.log`, `q41-tiled-kld.log`), op profiles, KLD bisect, `graph_accuracy_test.cpp`
110
 
111
  ## Credits and license
112
 
benchmarks/graph_accuracy_test.cpp CHANGED
@@ -10,7 +10,7 @@
10
  int main(){
11
  ggml_backend_t be = ggml_backend_cpu_init(); ggml_backend_cpu_set_n_threads(be,16);
12
  std::mt19937 g(7); std::normal_distribution<float> nd(0,1);
13
- ggml_type types[]={GGML_TYPE_Q4_K,GGML_TYPE_Q5_K,GGML_TYPE_Q6_K,GGML_TYPE_IQ4_XS,GGML_TYPE_Q8_0};
14
  const int K=2048,N=512,E=64,NU=6;
15
  for(ggml_type t:types) for(int mode=0;mode<2;mode++) for(int T: {1,64}){
16
  ggml_quantize_init(t);
 
10
  int main(){
11
  ggml_backend_t be = ggml_backend_cpu_init(); ggml_backend_cpu_set_n_threads(be,16);
12
  std::mt19937 g(7); std::normal_distribution<float> nd(0,1);
13
+ ggml_type types[]={GGML_TYPE_Q4_K,GGML_TYPE_Q5_K,GGML_TYPE_Q6_K,GGML_TYPE_IQ4_XS,GGML_TYPE_Q4_1,GGML_TYPE_Q8_0};
14
  const int K=2048,N=512,E=64,NU=6;
15
  for(ggml_type t:types) for(int mode=0;mode<2;mode++) for(int T: {1,64}){
16
  ggml_quantize_init(t);
benchmarks/q41-summary.md CHANGED
@@ -2,18 +2,31 @@
2
 
3
  The Q4_1 file (24.2 GiB, every weight tensor Q4_1; token_embd and the router are F32) is not hosted here; download it from
4
  [RecursiveSI/flint-int4-gguf](https://huggingface.co/RecursiveSI/flint-int4-gguf). The patch adds an AVX512-VNNI `vec_dot` for
5
- Q4_1 x Q8_1 (the x86 path had AVX2 only).
6
 
7
- `llama-bench -t 16 -fa on -lm mlock -b 1024 -ub 1024`, 3 interleaved rounds, median t/s (raw: `q41-ab-interleaved.csv`),
 
8
  Xeon Platinum 8559C, same host as the other benchmarks (shared, +-8 % per run).
9
 
10
  | | pp256 | pp2048 | tg128 |
11
  |---|---|---|---|
12
  | stock llama.cpp (de5122d) | 182.7 | 188.7 | 48.3 |
13
- | this patch | **198.8** | **208.5** | **54.0** |
14
- | gain | +9 % | +10 % | +12 % |
15
-
16
- ## Kernel
 
 
 
 
 
 
 
 
 
 
 
 
17
  Two 32-wide blocks per iteration. The two blocks' nibbles are packed into one ymm with memory-source `vinserti128`
18
  (no shuffle-port work), low and high nibbles each go through one `vpdpbusd` against the matching Q8_1 halves, the four fp16
19
  header values of the two blocks are converted at once, and the per-block `d*dy` scale is spread with a single `vpermps`.
@@ -21,22 +34,24 @@ A 512-bit version that re-ordered nibbles with cross-lane shuffles was slower th
21
  Prefetch distance `GGML_Q41_PF` (blocks, default 192; 192-320 equal in the cold-MoE microbenchmark).
22
  Microbenchmarks (16 threads, K=2048): cache-resident mul_mat 183 -> 220 GB/s, cold MoE decode (`mul_mat_id`) 43 -> 57 GB/s.
23
 
24
- ## Accuracy (wikitext-2 test, 32 x 512, vs stock logits)
 
 
25
  | | KLD | same top-1 |
26
  |---|---|---|
27
- | this patch | 0.0364 +- 0.0029 | 93.8 % |
 
28
  | stock with `-fa off` (noise floor) | 0.0385 +- 0.0027 | 93.9 % |
29
 
30
- PPL 18.240 (patch) vs 18.260 (stock). Graph-level `mul_mat` / `mul_mat_id` error versus a float reference is identical to stock
31
- (5.3e-3 .. 5.4e-3). Integer dot products are exact; only float accumulation order differs.
 
32
 
33
  ## Versus the Q4_K_XL file (same patch, same host)
34
  | file | pp256 | pp2048 | tg128 |
35
  |---|---|---|---|
36
  | flint-UD-Q4_K_XL (25.1 GB), patched | 296 | 307 | 42.8 |
37
- | flint-int4-q4_1 (26.0 GB), patched | 199 | 209 | 54.0 |
38
 
39
- Q4_1 decodes about 26 % faster (0.625 B/weight and a simple layout) but prefills about 32 % slower: K-quant weights go through the
40
- tiled VNNI GEMM, while Q4_1 still uses the per-row `vec_dot` path (60 % of prefill time is `MUL_MAT_ID q4_1`). Supporting Q4_1 in
41
- the tiled path would need per-32-block float scales in the tiled kernel (its tiles carry integer sub-block scales under one
42
- super-block scale) and was not done. PPL of the two files is similar (18.26 vs 18.36 stock).
 
2
 
3
  The Q4_1 file (24.2 GiB, every weight tensor Q4_1; token_embd and the router are F32) is not hosted here; download it from
4
  [RecursiveSI/flint-int4-gguf](https://huggingface.co/RecursiveSI/flint-int4-gguf). The patch adds an AVX512-VNNI `vec_dot` for
5
+ Q4_1 x Q8_1 (the x86 path had AVX2 only) and puts Q4_1 into the tiled VNNI GEMM used for prefill.
6
 
7
+ `llama-bench -t 16 -fa on -lm mlock -b 1024 -ub 1024`, 3 interleaved rounds, median t/s (raw: `q41-ab-interleaved.csv` for the
8
+ vec_dot kernel, `q41-tiled-ab-interleaved.csv` for the tiled GEMM),
9
  Xeon Platinum 8559C, same host as the other benchmarks (shared, +-8 % per run).
10
 
11
  | | pp256 | pp2048 | tg128 |
12
  |---|---|---|---|
13
  | stock llama.cpp (de5122d) | 182.7 | 188.7 | 48.3 |
14
+ | this patch, vec_dot only | 198.8 | 208.5 | 54.0 |
15
+ | this patch, tiled GEMM | **266.5** | **297.2** | **55.7** |
16
+ | gain vs stock | +46 % | +57 % | +15 % |
17
+ | gain vs vec_dot only | +34 % | +42 % | +3 % (noise) |
18
+
19
+ Decode is unchanged by the tiled path (single-row work stays below the tiled row threshold); the small tg difference is noise.
20
+
21
+ ## Tiled Q4_1 GEMM (prefill)
22
+ Q4_1 tiles carry no super-block scale: each 32-wide block of a 256-element slab has its own f32 `d` (scale) and `m` (min).
23
+ The tiled kernel gains `sd`/`sm` arrays (per-32-block f32, indexed per tile row) and a `Q41` template flag; activations use the
24
+ existing Q8_1 `vec_dot_type`, so no new quantizer was needed. Per 32-block the kernel computes
25
+ `d_a * (d_w * dot(q, code) + m_w * sum(code))`: the integer dot and integer code sum are exact and only the f32 accumulation
26
+ order differs from `vec_dot`. VNNI uses `vpdpbusd` with the 0..15 weight nibbles as the unsigned side. The driver scales the
27
+ k-block byte offset by `256/blck_size` (8 for Q4_1) instead of assuming one block per 256 elements.
28
+
29
+ ## vec_dot kernel (decode)
30
  Two 32-wide blocks per iteration. The two blocks' nibbles are packed into one ymm with memory-source `vinserti128`
31
  (no shuffle-port work), low and high nibbles each go through one `vpdpbusd` against the matching Q8_1 halves, the four fp16
32
  header values of the two blocks are converted at once, and the per-block `d*dy` scale is spread with a single `vpermps`.
 
34
  Prefetch distance `GGML_Q41_PF` (blocks, default 192; 192-320 equal in the cold-MoE microbenchmark).
35
  Microbenchmarks (16 threads, K=2048): cache-resident mul_mat 183 -> 220 GB/s, cold MoE decode (`mul_mat_id`) 43 -> 57 GB/s.
36
 
37
+ ## Accuracy
38
+ wikitext-2 test, 32 x 512, tiled Q4_1 vs the vec_dot path of the same patch (logits baseline):
39
+
40
  | | KLD | same top-1 |
41
  |---|---|---|
42
+ | tiled Q4_1 vs vec_dot (`q41-tiled-kld.log`) | 0.0372 +- 0.0026 | 94.2 % |
43
+ | vec_dot patch vs stock | 0.0364 +- 0.0029 | 93.8 % |
44
  | stock with `-fa off` (noise floor) | 0.0385 +- 0.0027 | 93.9 % |
45
 
46
+ The tiled path is inside the patch's own noise floor. Graph-level `mul_mat` / `mul_mat_id` error versus a float reference is
47
+ identical for tiled and vec_dot (`5.29e-3` vs `5.29e-3` for T=1, `5.35e-3` vs `5.39e-3` for T=64; Q8_0 reference 5.35e-3).
48
+ Integer dot products are exact; only float accumulation order differs.
49
 
50
  ## Versus the Q4_K_XL file (same patch, same host)
51
  | file | pp256 | pp2048 | tg128 |
52
  |---|---|---|---|
53
  | flint-UD-Q4_K_XL (25.1 GB), patched | 296 | 307 | 42.8 |
54
+ | flint-int4-q4_1 (26.0 GB), patched | 266 | 297 | 55.7 |
55
 
56
+ Q4_1 decodes about 30 % faster (0.625 B/weight and a simple layout) and now prefills within about 10 % of Q4_K_XL (the tiled
57
+ Q4_1 GEMM closed most of the earlier gap). PPL of the two files is similar (18.26 vs 18.36 stock).
 
 
benchmarks/q41-tiled-ab-interleaved.csv ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ mode,round,test,t_s
2
+ tiled_off,1,pp256,155.25
3
+ tiled_off,1,pp2048,197.85
4
+ tiled_off,1,tg128,55.74
5
+ tiled_on,1,pp256,279.03
6
+ tiled_on,1,pp2048,297.21
7
+ tiled_on,1,tg128,51.19
8
+ tiled_off,2,pp256,213.80
9
+ tiled_off,2,pp2048,204.47
10
+ tiled_off,2,tg128,55.76
11
+ tiled_on,2,pp256,266.45
12
+ tiled_on,2,pp2048,299.49
13
+ tiled_on,2,tg128,55.67
14
+ tiled_off,3,pp256,209.90
15
+ tiled_off,3,pp2048,205.69
16
+ tiled_off,3,tg128,58.09
17
+ tiled_on,3,pp256,247.40
18
+ tiled_on,3,pp2048,293.17
19
+ tiled_on,3,tg128,56.45
benchmarks/q41-tiled-kld.log ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ == base logits (this patch, Q4_1 prefill via vec_dot; GGML_CPU_TILED_MM=0)
2
+ 1.38.076.158 I Final estimate: PPL = 18.2396 +/- 0.77914
3
+
4
+ == tiled Q4_1 vs vec_dot base (GGML_CPU_TILED_MM=1, --kl-divergence)
5
+ chunk PPL ln(PPL(Q)/PPL(base)) KL Divergence Δp RMS Same top p
6
+ Mean PPL(Q) : 18.272596 ± 0.781391
7
+ Mean PPL(base) : 17.736553 ± 0.734102
8
+ Cor(ln(PPL(Q)), ln(PPL(base))): 98.98%
9
+ Mean ln(PPL(Q)/PPL(base)) : 0.029775 ± 0.006166
10
+ Mean KLD: 0.037245 ± 0.002587
11
+ Same top p: 94.154 ± 0.260 %
12
+
13
+ wikitext-2 test, 32 chunks x 512, Xeon Platinum 8559C, 16 threads.
14
+ For reference, the existing vec_dot patch is 0.036404 +- 0.002873 vs stock, and the stock -fa off noise floor is 0.038469 +- 0.002729.
patches/flint-cpu-opt.patch CHANGED
@@ -757,11 +757,344 @@ index 9282ca299..0431b7ada 100644
757
  if (cur->ne[1] % 8 == 0) {
758
  return &q4_K_8x8_q8_K;
759
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
760
  diff --git a/ggml/src/ggml-cpu/tiled/tiled.cpp b/ggml/src/ggml-cpu/tiled/tiled.cpp
761
- index 77e1a9945..06fd9a2d5 100644
762
  --- a/ggml/src/ggml-cpu/tiled/tiled.cpp
763
  +++ b/ggml/src/ggml-cpu/tiled/tiled.cpp
764
- @@ -716,7 +716,9 @@ static void tiled_store_window(const float * buf, int n_src0, int n_src1, int bu
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
765
  // unpack and microtile are reused unchanged from mul_mat.
766
 
767
  // src0 rows per g group; finer than the TILED_TILE_K kernel tile so all threads stay busy at small ne01
@@ -772,6 +1105,218 @@ index 77e1a9945..06fd9a2d5 100644
772
 
773
  // like tiled_store_window, but the dst columns are not contiguous: col_ptrs[j] points at the
774
  // start of dst column j, whose rows are contiguous (dim 0, nb0 == 4 bytes).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
775
  diff --git a/ggml/src/ggml.c b/ggml/src/ggml.c
776
  index 5d8449a88..203f15e43 100644
777
  --- a/ggml/src/ggml.c
 
757
  if (cur->ne[1] % 8 == 0) {
758
  return &q4_K_8x8_q8_K;
759
  }
760
+ diff --git a/ggml/src/ggml-cpu/tiled/tiled-kernel.cpp b/ggml/src/ggml-cpu/tiled/tiled-kernel.cpp
761
+ index 5819ac07c..4d4eaae6f 100644
762
+ --- a/ggml/src/ggml-cpu/tiled/tiled-kernel.cpp
763
+ +++ b/ggml/src/ggml-cpu/tiled/tiled-kernel.cpp
764
+ @@ -204,6 +204,80 @@ static void tiled_run_microtile_vnni(const tiled_tile_src0 & src0, const tiled_t
765
+ tiled_run_micro_vnni_8x16<SUBBLK, HAS_MIN, BIAS, NK>(src0, src1, i0 + 8, j0, num_k, slab, buf, buf_stride);
766
+ }
767
+
768
+ +// q4_1: every 32-wide block has its own f32 scale and min on both sides (weights: d,m; activations: d).
769
+ +// weight = d_w*q + m_w (q in 0..15), act = d_a*code (code int8), so one 32-block contributes
770
+ +// d_a * (d_w * dot(q, code) + m_w * sum(code)).
771
+ +// The integer dot and the integer sum are exact; the four per-block f32 scale factors are applied in f32,
772
+ +// which only changes the accumulation order (same as the reference vec_dot, which also accumulates in f32).
773
+ +template <int NK>
774
+ +static void tiled_run_micro_q41_vnni_8x16(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1,
775
+ + int i0, int j0, int num_k, int slab, float * buf, int buf_stride) {
776
+ + constexpr int SUBBLK = 32;
777
+ + constexpr int NB = TILED_TILE_K / SUBBLK; // 8
778
+ + constexpr int NS = SUBBLK / 16; // 2
779
+ + constexpr int NG = SUBBLK / 4; // 8
780
+ + constexpr int NUM_ROWS = 8;
781
+ +
782
+ + const int nkr = (NK > 0) ? NK : num_k;
783
+ + const int seff = (NK > 0) ? 0 : slab;
784
+ + const int qk_stride = nkr * TILED_TILE_K;
785
+ + const int qk_off = seff * TILED_TILE_K;
786
+ + const int nb_stride = NB * nkr;
787
+ + const int nb_off = seff * NB;
788
+ + const int bs_stride = (nkr == 1) ? TILED_TILE_ROWS : TILED_MICRO;
789
+ +
790
+ + __m512 facc[NUM_ROWS];
791
+ + for (int t = 0; t < NUM_ROWS; t++) { facc[t] = _mm512_setzero_ps(); }
792
+ +
793
+ + const int32_t * bsums = src1.bsums;
794
+ + for (int s = 0; s < NB; s++) {
795
+ + __m512i bsums32 = _mm512_load_si512((const __m512i *) &bsums[(seff * TILED_MICRO + s * NS) * bs_stride + j0]);
796
+ + for (int u = 1; u < NS; u++) {
797
+ + bsums32 = _mm512_add_epi32(bsums32, _mm512_load_si512((const __m512i *) &bsums[(seff * TILED_MICRO + s * NS + u) * bs_stride + j0]));
798
+ + }
799
+ + const __m512 bterm = _mm512_cvtepi32_ps(bsums32);
800
+ +
801
+ + __m512i acc16[NUM_ROWS];
802
+ + for (int t = 0; t < NUM_ROWS; t++) { acc16[t] = _mm512_setzero_si512(); }
803
+ +
804
+ +#ifdef __GNUC__
805
+ + #pragma GCC unroll 8
806
+ +#endif
807
+ + for (int g = 0; g < NG; g++) {
808
+ + const int kg = s * NG + g;
809
+ + const __m512i codes = _mm512_load_si512((const __m512i *) &src1.q[(j0 / TILED_MICRO) * (TILED_MICRO * qk_stride)
810
+ + + qk_off + (kg % TILED_MICRO) * qk_stride + (kg / TILED_MICRO) * (TILED_MICRO * 4)]);
811
+ + for (int t = 0; t < NUM_ROWS; t++) {
812
+ + const uint32_t u4 = *(const uint32_t *) &src0.q[(i0 + t) * qk_stride + qk_off + kg * 4];
813
+ + const __m512i u4b = _mm512_set1_epi32((int) u4);
814
+ + acc16[t] = _mm512_dpbusd_epi32(acc16[t], u4b, codes);
815
+ + }
816
+ + }
817
+ +
818
+ + const __m512 dav = _mm512_loadu_ps(&src1.sd[(seff * NB + s) * bs_stride + j0]);
819
+ + for (int t = 0; t < NUM_ROWS; t++) {
820
+ + const int ar = i0 + t;
821
+ + const float dw = src0.sd[ar * nb_stride + nb_off + s];
822
+ + const float mw = src0.sm[ar * nb_stride + nb_off + s];
823
+ + const __m512 raw = _mm512_cvtepi32_ps(acc16[t]);
824
+ + const __m512 tv = _mm512_fmadd_ps(_mm512_set1_ps(dw), raw, _mm512_mul_ps(_mm512_set1_ps(mw), bterm));
825
+ + facc[t] = _mm512_fmadd_ps(tv, dav, facc[t]);
826
+ + }
827
+ + }
828
+ +
829
+ + for (int t = 0; t < NUM_ROWS; t++) {
830
+ + float * p = &buf[(i0 + t) * buf_stride + j0];
831
+ + _mm512_store_ps(p, _mm512_add_ps(_mm512_load_ps(p), facc[t]));
832
+ + }
833
+ +}
834
+ +
835
+ +template <int NK>
836
+ +static void tiled_run_microtile_q41_vnni(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1,
837
+ + int i0, int j0, int num_k, int slab, float * buf, int buf_stride) {
838
+ + tiled_run_micro_q41_vnni_8x16<NK>(src0, src1, i0, j0, num_k, slab, buf, buf_stride);
839
+ + tiled_run_micro_q41_vnni_8x16<NK>(src0, src1, i0 + 8, j0, num_k, slab, buf, buf_stride);
840
+ +}
841
+ +
842
+ #endif // __AVX512VNNI__ && __AVX512VL__
843
+
844
+ #if defined(__AVX2__)
845
+ @@ -496,23 +570,79 @@ static void tiled_run_microtile_avx(const tiled_tile_src0 & src0, const tiled_ti
846
+
847
+ #endif // __AVX__ && !__AVX2__
848
+
849
+ +// q4_1 reference/scalar path (non-VNNI builds): same math as the VNNI kernel, per 32-block f32 scales.
850
+ +template <int NK>
851
+ +static void tiled_run_microtile_q41_scalar(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1,
852
+ + int i0, int j0, int num_k, int slab, float * buf, int buf_stride) {
853
+ + constexpr int SUBBLK = 32;
854
+ + constexpr int NB = TILED_TILE_K / SUBBLK;
855
+ + constexpr int NS = SUBBLK / 16;
856
+ +
857
+ + const int nkr = (NK > 0) ? NK : num_k;
858
+ + const int seff = (NK > 0) ? 0 : slab;
859
+ + const int qk_stride = nkr * TILED_TILE_K;
860
+ + const int qk_off = seff * TILED_TILE_K;
861
+ + const int nb_stride = NB * nkr;
862
+ + const int nb_off = seff * NB;
863
+ + const int bs_stride = (nkr == 1) ? TILED_TILE_ROWS : TILED_MICRO;
864
+ +
865
+ + for (int t = 0; t < TILED_MICRO; t++) {
866
+ + const int ar = i0 + t;
867
+ + const uint8_t * q0 = &src0.q[ar * qk_stride + qk_off];
868
+ + for (int j = 0; j < TILED_MICRO; j++) {
869
+ + const int br = j0 + j;
870
+ + const int8_t * q1 = &src1.q[br * qk_stride + qk_off];
871
+ + float acc = 0.0f;
872
+ + for (int s = 0; s < NB; s++) {
873
+ + int32_t bsum = 0;
874
+ + for (int u = 0; u < NS; u++) { bsum += src1.bsums[(seff * TILED_MICRO + s * NS + u) * bs_stride + br]; }
875
+ + int32_t raw = 0;
876
+ + for (int e = 0; e < SUBBLK; e++) { raw += (int32_t) q0[s * SUBBLK + e] * (int32_t) q1[s * SUBBLK + e]; }
877
+ + const float da = src1.sd[(seff * NB + s) * bs_stride + br];
878
+ + const float dw = src0.sd[ar * nb_stride + nb_off + s];
879
+ + const float mw = src0.sm[ar * nb_stride + nb_off + s];
880
+ + acc += da * (dw * (float) raw + mw * (float) bsum);
881
+ + }
882
+ + buf[ar * buf_stride + j0 + j] += acc;
883
+ + }
884
+ + }
885
+ +}
886
+ +
887
+ // main microtile entry point. num_k = K-blocks per row (the narrow path reads num_k slabs at row
888
+ // stride num_k*256 so each weight row is one long stream); slab = this call's slab. num_k=1,
889
+ // slab=0 is the standard single-slab path.
890
+ // num_k==1 dispatches to the NK=1 instantiation, whose strides/offsets are compile-time
891
+ // constants (shifts/scaled-leas). num_k>1 (the narrow path) uses the NK=0 runtime-strided
892
+ // version; it is memory-bound, so one version serves all of it.
893
+ -template <int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS>
894
+ +// Q41: q4_1 weights + q8_1 activations (per-32-block f32 scales) use the dedicated kernels above.
895
+ +template <int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS, bool Q41>
896
+ void tiled_run_microtile(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1,
897
+ int i0, int j0, int num_k, int slab, float * buf, int buf_stride) {
898
+ const bool standard = (num_k == 1);
899
+ #if defined(__AVX512VNNI__) && defined(__AVX512VL__) && defined(__AVX512DQ__)
900
+ + if constexpr (Q41) {
901
+ + if (standard) {
902
+ + tiled_run_microtile_q41_vnni<1>(src0, src1, i0, j0, num_k, slab, buf, buf_stride);
903
+ + } else {
904
+ + tiled_run_microtile_q41_vnni<0>(src0, src1, i0, j0, num_k, slab, buf, buf_stride);
905
+ + }
906
+ + return;
907
+ + }
908
+ if (standard) {
909
+ tiled_run_microtile_vnni<SUBBLK, HAS_MIN, BIAS, 1>(src0, src1, i0, j0, num_k, slab, buf, buf_stride);
910
+ } else {
911
+ tiled_run_microtile_vnni<SUBBLK, HAS_MIN, BIAS, 0>(src0, src1, i0, j0, num_k, slab, buf, buf_stride);
912
+ }
913
+ -#elif defined(__AVX2__)
914
+ +#else
915
+ + if constexpr (Q41) {
916
+ + if (standard) {
917
+ + tiled_run_microtile_q41_scalar<1>(src0, src1, i0, j0, num_k, slab, buf, buf_stride);
918
+ + } else {
919
+ + tiled_run_microtile_q41_scalar<0>(src0, src1, i0, j0, num_k, slab, buf, buf_stride);
920
+ + }
921
+ + return;
922
+ + }
923
+ +#if defined(__AVX2__)
924
+ if (standard) {
925
+ tiled_run_microtile_avx2<SUBBLK, HAS_MIN, BIAS, 1, ACTBIAS>(src0, src1, i0, j0, num_k, slab, buf, buf_stride);
926
+ } else {
927
+ @@ -531,6 +661,7 @@ void tiled_run_microtile(const tiled_tile_src0 & src0, const tiled_tile_src1 & s
928
+ tiled_run_microtile_scalar<SUBBLK, HAS_MIN, BIAS, 0>(src0, src1, i0, j0, num_k, slab, buf, buf_stride);
929
+ }
930
+ #endif
931
+ +#endif
932
+ }
933
+
934
+ // explicit instantiations for the in-use formats (q4_K and q5_K share the constants)
935
+ @@ -556,6 +687,9 @@ template void tiled_run_microtile<16, false, 4, true>(const tiled_tile_src0 & sr
936
+ // q2_K: BIAS = 0
937
+ template void tiled_run_microtile<16, true, 0, false>(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1,
938
+ int i0, int j0, int num_k, int slab, float * buf, int buf_stride);
939
+ +// q4_1: per-32-block f32 scales, q8_1 activations
940
+ +template void tiled_run_microtile<32, false, 0, false, true>(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1,
941
+ + int i0, int j0, int num_k, int slab, float * buf, int buf_stride);
942
+
943
+
944
+ #define MIN(a, b) ((a) < (b) ? (a) : (b))
945
+ diff --git a/ggml/src/ggml-cpu/tiled/tiled-kernel.h b/ggml/src/ggml-cpu/tiled/tiled-kernel.h
946
+ index 1a97f406a..4bd9642cc 100644
947
+ --- a/ggml/src/ggml-cpu/tiled/tiled-kernel.h
948
+ +++ b/ggml/src/ggml-cpu/tiled/tiled-kernel.h
949
+ @@ -29,12 +29,16 @@
950
+ // SUBBLK=32 formats index at stride 8 and leave the slack unused.
951
+ struct tiled_tile_src0 {
952
+ static constexpr int NB_MAX = TILED_TILE_K / 16; // max subblocks per 256-elem block
953
+ + static constexpr int NB32 = TILED_TILE_K / 32; // 32-wide subblocks (q4_1)
954
+
955
+ alignas(64) uint8_t q[TILED_TILE_ROWS * TILED_TILE_K]; // unsigned quants, widened to uint8
956
+ float d[TILED_TILE_ROWS]; // One d from each input block, widened to f32
957
+ float dmin[TILED_TILE_ROWS]; // dmin from each input block (if applicable), widened to F32
958
+ int32_t scales[TILED_TILE_ROWS * NB_MAX]; // per-subblock scale, stored as int32_t
959
+ int32_t mins[TILED_TILE_ROWS * NB_MAX]; // per-subblock min, used when HAS_MIN
960
+ + // q4_1: no super-block scale, each 32-wide block carries its own f32 scale/min
961
+ + float sd[TILED_TILE_ROWS * NB32]; // per-32-block scale
962
+ + float sm[TILED_TILE_ROWS * NB32]; // per-32-block min
963
+ };
964
+
965
+ // src1 tile: built from q8_K (wdata)
966
+ @@ -45,6 +49,8 @@ struct tiled_tile_src1 {
967
+ alignas(64) int32_t bsums[(TILED_TILE_K / 16) * TILED_TILE_ROWS];
968
+ // f32 (not f16): q8_k stores fp16, the unpack converts once
969
+ float d[TILED_TILE_ROWS];
970
+ + // q4_1: q8_1 activations carry a scale per 32-wide block (no super-block d)
971
+ + float sd[(TILED_TILE_K / 32) * TILED_TILE_ROWS];
972
+ };
973
+
974
+ // per-thread workspace: all tiled state lives here, allocated in wdata (one slot per thread)
975
+ @@ -71,6 +77,12 @@ inline void tiled_unpk_nib4(const uint8_t * src, uint8_t * lo, uint8_t * hi) {
976
+ _mm256_storeu_si256((__m256i *) lo, _mm256_and_si256(v, _mm256_set1_epi8(0x0F)));
977
+ _mm256_storeu_si256((__m256i *) hi, _mm256_srli_epi32(_mm256_and_si256(v, _mm256_set1_epi8((int8_t) 0xF0)), 4));
978
+ }
979
+ +// 16-byte source (one q4_1 block: lo -> elems 0..15, hi -> elems 16..31)
980
+ +inline void tiled_unpk_nib4_16(const uint8_t * src, uint8_t * lo, uint8_t * hi) {
981
+ + const __m128i v = _mm_loadu_si128((const __m128i *) src);
982
+ + _mm_storeu_si128((__m128i *) lo, _mm_and_si128(v, _mm_set1_epi8(0x0F)));
983
+ + _mm_storeu_si128((__m128i *) hi, _mm_srli_epi32(_mm_and_si128(v, _mm_set1_epi8((int8_t) 0xF0)), 4));
984
+ +}
985
+ // 2-bit values at bit offset S
986
+ template <int S> inline void tiled_unpk_2bit(const uint8_t * src, uint8_t * dst) {
987
+ _mm256_storeu_si256((__m256i *) dst, _mm256_and_si256(
988
+ @@ -129,6 +141,9 @@ inline void tiled_unpk_tern8(const uint8_t * src, int8_t delta, uint8_t * dst) {
989
+ inline void tiled_unpk_nib4(const uint8_t * src, uint8_t * lo, uint8_t * hi) {
990
+ for (int l = 0; l < 32; l++) { lo[l] = (uint8_t) (src[l] & 0xF); hi[l] = (uint8_t) (src[l] >> 4); }
991
+ }
992
+ +inline void tiled_unpk_nib4_16(const uint8_t * src, uint8_t * lo, uint8_t * hi) {
993
+ + for (int l = 0; l < 16; l++) { lo[l] = (uint8_t) (src[l] & 0xF); hi[l] = (uint8_t) (src[l] >> 4); }
994
+ +}
995
+ template <int S>
996
+ inline void tiled_unpk_2bit(const uint8_t * src, uint8_t * dst) {
997
+ for (int l = 0; l < 32; l++) { dst[l] = (uint8_t) ((src[l] >> S) & 3); }
998
+ @@ -164,7 +179,9 @@ inline void tiled_unpk_tern8(const uint8_t * src, int8_t delta, uint8_t * dst) {
999
+ // ACTBIAS (AVX2 only): the activation is pre-biased +128 by tiled_repack_src1,
1000
+ // num_k = K-blocks per row: the tile holds num_k slabs at row stride num_k*256
1001
+ // (Default case is num_k=1, 256x256 tiles, we go to longer num_k to improve memory bandwidth when num_rows is small)
1002
+ -template <int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS>
1003
+ +// Q41: src0 is q4_1 (per-32-block f32 scale/min in sd/sm) and src1 is q8_1 (per-32-block f32 scale in sd);
1004
+ +// the other format constants (SUBBLK=32, HAS_MIN=false, BIAS=0, ACTBIAS=false) are unused in that mode.
1005
+ +template <int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS, bool Q41 = false>
1006
+ void tiled_run_microtile(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1,
1007
+ int i0, int j0, int num_k, int slab, float * buf, int buf_stride);
1008
+
1009
  diff --git a/ggml/src/ggml-cpu/tiled/tiled.cpp b/ggml/src/ggml-cpu/tiled/tiled.cpp
1010
+ index 77e1a9945..225ec0e78 100644
1011
  --- a/ggml/src/ggml-cpu/tiled/tiled.cpp
1012
  +++ b/ggml/src/ggml-cpu/tiled/tiled.cpp
1013
+ @@ -534,11 +534,33 @@ static void tiled_unpack_src0(const block_iq1_m * rows, int64_t row_stride, int
1014
+ }
1015
+ }
1016
+
1017
+ +// q4_1: 8 x 32-wide blocks per 256-slab, each with its own f32 d (scale) and m (min).
1018
+ +static void tiled_unpack_src0(const block_q4_1 * rows, int64_t row_stride, int n_rows, tiled_tile_src0 * tile, int num_k) {
1019
+ + GGML_ASSERT(n_rows <= TILED_TILE_ROWS);
1020
+ + constexpr int NB = TILED_TILE_K / 32; // 8 x 32-wide blocks
1021
+ +
1022
+ + const int qk_stride = num_k * TILED_TILE_K;
1023
+ + const int nb_stride = NB * num_k;
1024
+ + for (int slab = 0; slab < num_k; slab++) {
1025
+ + for (int r = 0; r < n_rows; r++) {
1026
+ + const int q_off = r * qk_stride + slab * TILED_TILE_K;
1027
+ + const int s_off = r * nb_stride + slab * NB;
1028
+ + const block_q4_1 * x = rows + r * row_stride + slab * NB;
1029
+ + for (int s = 0; s < NB; s++) {
1030
+ + tile->sd[s_off + s] = ggml_fp16_to_fp32(x[s].data.data.d);
1031
+ + tile->sm[s_off + s] = ggml_fp16_to_fp32(x[s].data.data.m);
1032
+ + // 16 bytes -> elems 0..15 (low nibble) and 16..31 (high nibble)
1033
+ + tiled_unpk_nib4_16(x[s].qs, &tile->q[q_off + s * 32], &tile->q[q_off + s * 32 + 16]);
1034
+ + }
1035
+ + }
1036
+ + }
1037
+ +}
1038
+ +
1039
+ // unpack src1 tile from q8_K rows. num_k = K-blocks per row: each row decodes num_k consecutive
1040
+ // source blocks (rows[r][kblk + slab]) into the tile at row stride num_k*256, so each activation
1041
+ // row is one long stream. num_k=1 is the standard single-slab path (byte-identical to before).
1042
+ -static void tiled_unpack_src1_q8_K(const block_q8_K * const * rows, int n_rows, tiled_tile_src1 * tile,
1043
+ - int kblk, int num_k) {
1044
+ +static void tiled_unpack_src1(const block_q8_K * const * rows, int n_rows, tiled_tile_src1 * tile,
1045
+ + int kblk, int num_k) {
1046
+ GGML_ASSERT(n_rows <= TILED_TILE_ROWS);
1047
+ const int n_padded = (n_rows + TILED_MICRO - 1) & ~(TILED_MICRO - 1);
1048
+ const int bs_stride = (num_k == 1) ? TILED_TILE_ROWS : TILED_MICRO;
1049
+ @@ -569,6 +591,39 @@ static void tiled_unpack_src1_q8_K(const block_q8_K * const * rows, int n_rows,
1050
+ }
1051
+ }
1052
+
1053
+ +// q4_1 src1: q8_1 activation rows. 8 blocks of 32 per 256-slab, each block has its own f32 scale;
1054
+ +// the integer per-16 sums are recomputed from the codes (q8_1 only stores d and d*sum).
1055
+ +static void tiled_unpack_src1(const block_q8_1 * const * rows, int n_rows, tiled_tile_src1 * tile,
1056
+ + int kblk, int num_k) {
1057
+ + GGML_ASSERT(n_rows <= TILED_TILE_ROWS);
1058
+ + const int n_padded = (n_rows + TILED_MICRO - 1) & ~(TILED_MICRO - 1);
1059
+ + const int bs_stride = (num_k == 1) ? TILED_TILE_ROWS : TILED_MICRO;
1060
+ + constexpr int NBLK = TILED_TILE_K / 32; // 8 q8_1 blocks per 256-slab
1061
+ + constexpr int n16 = TILED_TILE_K / TILED_MICRO;
1062
+ + for (int slab = 0; slab < num_k; slab++) {
1063
+ + const int q_off = slab * TILED_TILE_K;
1064
+ + for (int r = 0; r < n_padded; r++) {
1065
+ + if (r < n_rows) {
1066
+ + const block_q8_1 * xb = rows[r] + (kblk + slab) * NBLK;
1067
+ + for (int b = 0; b < NBLK; b++) {
1068
+ + memcpy(&tile->q[r * (num_k * TILED_TILE_K) + q_off + b * 32], xb[b].qs, 32);
1069
+ + int32_t s0 = 0, s1 = 0;
1070
+ + for (int e = 0; e < 16; e++) { s0 += xb[b].qs[e]; s1 += xb[b].qs[16 + e]; }
1071
+ + tile->bsums[(slab * n16 + 2 * b + 0) * bs_stride + r] = s0;
1072
+ + tile->bsums[(slab * n16 + 2 * b + 1) * bs_stride + r] = s1;
1073
+ + tile->sd[(slab * NBLK + b) * bs_stride + r] = ggml_fp16_to_fp32(xb[b].data.data.d);
1074
+ + }
1075
+ + tile->d[slab * TILED_MICRO + r] = 1.0f; // per-32 scales are applied in the kernel
1076
+ + } else {
1077
+ + memset(&tile->q[r * (num_k * TILED_TILE_K) + q_off], 0, TILED_TILE_K);
1078
+ + for (int i = 0; i < n16; i++) { tile->bsums[(slab * n16 + i) * bs_stride + r] = 0; }
1079
+ + for (int b = 0; b < NBLK; b++) { tile->sd[(slab * NBLK + b) * bs_stride + r] = 0.0f; }
1080
+ + tile->d[slab * TILED_MICRO + r] = 1.0f;
1081
+ + }
1082
+ + }
1083
+ + }
1084
+ +}
1085
+ +
1086
+ // GGML_CPU_TILED_MM: master switch, on by default. If off, we fast return false and normal vec_dot mul_mat resumes
1087
+ static bool ggml_tiled_matmul_enabled(void) {
1088
+ static bool enabled = true;
1089
+ @@ -617,6 +672,7 @@ static bool ggml_tiled_supported(const struct ggml_tensor * src0,
1090
+ case GGML_TYPE_Q6_K:
1091
+ case GGML_TYPE_Q5_K:
1092
+ case GGML_TYPE_Q4_K:
1093
+ + case GGML_TYPE_Q4_1:
1094
+ case GGML_TYPE_Q3_K:
1095
+ case GGML_TYPE_Q2_K:
1096
+ case GGML_TYPE_IQ4_XS:
1097
+ @@ -716,7 +772,9 @@ static void tiled_store_window(const float * buf, int n_src0, int n_src1, int bu
1098
  // unpack and microtile are reused unchanged from mul_mat.
1099
 
1100
  // src0 rows per g group; finer than the TILED_TILE_K kernel tile so all threads stay busy at small ne01
 
1105
 
1106
  // like tiled_store_window, but the dst columns are not contiguous: col_ptrs[j] points at the
1107
  // start of dst column j, whose rows are contiguous (dim 0, nb0 == 4 bytes).
1108
+ @@ -756,11 +814,11 @@ static void tiled_store_window_scatter(const float * buf, int n_src0, int n_src1
1109
+
1110
+ // one (g, k) macrotile: zero the acc window, sweep K in 256 element slabs, scatter the result rows
1111
+ // rows points at this k window's routed src1 rows, row r at its base block
1112
+ -template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS>
1113
+ +template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS, typename BA = block_q8_K, bool Q41 = false>
1114
+ static void tiled_mmid_gemm_window(struct ggml_tensor * dst, const struct ggml_tensor * src0,
1115
+ const char * src0_cur, int64_t r, int64_t k, int64_t nrows,
1116
+ const int32_t * expert_rows,
1117
+ - const block_q8_K * const * rows,
1118
+ + const BA * const * rows,
1119
+ tiled_ws * ws) {
1120
+ const int64_t ne00 = src0->ne[0];
1121
+ const int64_t ne01 = src0->ne[1];
1122
+ @@ -770,6 +828,8 @@ static void tiled_mmid_gemm_window(struct ggml_tensor * dst, const struct ggml_t
1123
+
1124
+ const size_t src0_bs = ggml_type_size(src0->type);
1125
+ const int64_t src0_stride = src0->nb[1] / src0_bs;
1126
+ + // source blocks per 256-element slab (1 for K-quants, 8 for q4_1's 32-wide blocks)
1127
+ + const int kblk_scale = (int) (TILED_TILE_K / ggml_blck_size(src0->type));
1128
+
1129
+ // the window is at most TILED_MMID_GROUP x TILED_TILE_K, so zero only that region of acc
1130
+ for (int64_t i = 0; i < n_src0; i++) {
1131
+ @@ -792,19 +852,19 @@ static void tiled_mmid_gemm_window(struct ggml_tensor * dst, const struct ggml_t
1132
+ const int kstart = (int) (k0 / TILED_TILE_K);
1133
+ // activation: one 16-row band (nrows <= 16). Unpack the whole k_extent chunk (all
1134
+ // slabs) so each activation row is a long stream, then repack each slab in place
1135
+ - tiled_unpack_src1_q8_K(rows, nrows, &ws->src1, kstart, num_k);
1136
+ + tiled_unpack_src1(rows, nrows, &ws->src1, kstart, num_k);
1137
+ // repack the whole k_extent chunk in place: num_k slabs x 4 tiles, 16 rows, row stride num_k*256
1138
+ tiled_repack_src1(&ws->src1, 0, num_k, ACTBIAS);
1139
+ // weight groups (16 at a time): unpack the k_extent chunk of the group (the long per-row
1140
+ // read), then one standard MAC per slab accumulating into the same acc rows
1141
+ for (int64_t ir0 = r; ir0 < r_end; ir0 += TILED_MICRO) {
1142
+ const int n0 = (int) MIN(TILED_MICRO, r_end - ir0);
1143
+ - const B * wbase = (const B *) (src0_cur + ir0 * src0->nb[1] + kstart * src0_bs);
1144
+ + const B * wbase = (const B *) (src0_cur + ir0 * src0->nb[1] + kstart * kblk_scale * src0_bs);
1145
+ tiled_unpack_src0(wbase, src0_stride, n0, &ws->src0, num_k);
1146
+ tiled_repack_src0<SUBBLK>(&ws->src0, n0, num_k, BIAS, ACTBIAS);
1147
+ float * buf = ws->acc + (ir0 - r) * TILED_TILE_ROWS;
1148
+ for (int slab = 0; slab < num_k; slab++) {
1149
+ - tiled_run_microtile<SUBBLK, HAS_MIN, BIAS, ACTBIAS>(ws->src0, ws->src1, 0, 0, num_k, slab, buf, TILED_TILE_ROWS);
1150
+ + tiled_run_microtile<SUBBLK, HAS_MIN, BIAS, ACTBIAS, Q41>(ws->src0, ws->src1, 0, 0, num_k, slab, buf, TILED_TILE_ROWS);
1151
+ }
1152
+ }
1153
+ }
1154
+ @@ -812,15 +872,15 @@ static void tiled_mmid_gemm_window(struct ggml_tensor * dst, const struct ggml_t
1155
+ // standard per-slab path: K is stepped in 256-element slabs
1156
+ for (int64_t ib = 0; ib < ne00; ib += TILED_TILE_K) {
1157
+ const int kblk = (int) (ib / TILED_TILE_K);
1158
+ - tiled_unpack_src0((const B *) (src0_cur + r * src0->nb[1] + kblk * src0_bs), src0_stride, n_src0, &ws->src0, 1);
1159
+ + tiled_unpack_src0((const B *) (src0_cur + r * src0->nb[1] + kblk * kblk_scale * src0_bs), src0_stride, n_src0, &ws->src0, 1);
1160
+ tiled_repack_src0<SUBBLK>(&ws->src0, n_src0, 1, BIAS, ACTBIAS);
1161
+ - tiled_unpack_src1_q8_K(rows, nrows, &ws->src1, kblk, 1);
1162
+ + tiled_unpack_src1(rows, nrows, &ws->src1, kblk, 1);
1163
+ // 16x16 microtiles sweeping the window; repack each src1 band just-in-time
1164
+ // (j0-outer) so only the bands actually used are repacked and each is L1-hot
1165
+ for (int64_t ir1 = 0; ir1 < nrows; ir1 += TILED_MICRO) {
1166
+ tiled_repack_src1(&ws->src1, (int) ir1, 1, ACTBIAS);
1167
+ for (int64_t ir0 = r; ir0 < r_end; ir0 += TILED_MICRO) {
1168
+ - tiled_run_microtile<SUBBLK, HAS_MIN, BIAS, ACTBIAS>(ws->src0, ws->src1,
1169
+ + tiled_run_microtile<SUBBLK, HAS_MIN, BIAS, ACTBIAS, Q41>(ws->src0, ws->src1,
1170
+ (int) (ir0 - r), (int) ir1, 1, 0,
1171
+ ws->acc, TILED_TILE_ROWS);
1172
+ }
1173
+ @@ -834,7 +894,7 @@ static void tiled_mmid_gemm_window(struct ggml_tensor * dst, const struct ggml_t
1174
+ // one expert of MUL_MAT_ID: each k window's dispatched rows are pointed at by a per-window
1175
+ // row pointer list and swept over the thread's row windows, the dst rows are scattered back
1176
+ // per window
1177
+ -template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS>
1178
+ +template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS, typename BA = block_q8_K, bool Q41 = false>
1179
+ static void ggml_compute_forward_mul_mat_id_tiled_one_expert(
1180
+ const struct ggml_compute_params * params,
1181
+ struct ggml_tensor * dst,
1182
+ @@ -877,22 +937,22 @@ static void ggml_compute_forward_mul_mat_id_tiled_one_expert(
1183
+ for (int64_t k = 0; k < cne1; k += TILED_TILE_K) {
1184
+ const int64_t nrows = MIN(TILED_TILE_K, cne1 - k);
1185
+
1186
+ - const block_q8_K * rows[TILED_TILE_ROWS];
1187
+ + const BA * rows[TILED_TILE_ROWS];
1188
+ for (int64_t i = 0; i < nrows; i++) {
1189
+ const int64_t i11 = expert_rows[2 * (k + i) + 0] % ne11;
1190
+ const int64_t i12 = expert_rows[2 * (k + i) + 1];
1191
+ - rows[i] = (const block_q8_K *) ((const char *) wdata + (i11 + i12 * ne11) * nbw1);
1192
+ + rows[i] = (const BA *) ((const char *) wdata + (i11 + i12 * ne11) * nbw1);
1193
+ }
1194
+
1195
+ for (int64_t g = g0; g < g1; g++) {
1196
+ const int64_t r = g * TILED_MMID_GROUP;
1197
+
1198
+ - tiled_mmid_gemm_window<B, SUBBLK, HAS_MIN, BIAS, ACTBIAS>(dst, src0, src0_cur, r, k, nrows, expert_rows, rows, ws);
1199
+ + tiled_mmid_gemm_window<B, SUBBLK, HAS_MIN, BIAS, ACTBIAS, BA, Q41>(dst, src0, src0_cur, r, k, nrows, expert_rows, rows, ws);
1200
+ }
1201
+ }
1202
+ }
1203
+
1204
+ -template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS>
1205
+ +template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS, typename BA = block_q8_K, bool Q41 = false>
1206
+ static void ggml_compute_forward_mul_mat_tiled_one_chunk(
1207
+ const struct ggml_compute_params * params,
1208
+ struct ggml_tensor * dst,
1209
+ @@ -928,6 +988,8 @@ static void ggml_compute_forward_mul_mat_tiled_one_chunk(
1210
+
1211
+ const int64_t src0_stride = nb01 / src0_bs; // blocks between src0 rows
1212
+ const int64_t src1_stride = (src1->type == vec_dot_type ? src1->nb[1] : row_size) / src1_bs;
1213
+ + // source blocks per 256-element slab (1 for K-quants, 8 for q4_1's 32-wide blocks)
1214
+ + const int kblk_scale = (int) (TILED_TILE_K / ggml_blck_size(src0->type));
1215
+
1216
+ const int64_t TILE = 256;
1217
+ const int64_t MICRO = 16;
1218
+ @@ -960,9 +1022,9 @@ static void ggml_compute_forward_mul_mat_tiled_one_chunk(
1219
+ char * dst_col = (char *) dst->data + i12 * nb2 + i13 * nb3;
1220
+
1221
+ // rows[r] is the window's row r base block; the k-slab offset is applied in the unpack
1222
+ - const block_q8_K * rows[TILED_TILE_ROWS];
1223
+ + const BA * rows[TILED_TILE_ROWS];
1224
+ for (int r = 0; r < n_src1; r++) {
1225
+ - rows[r] = (const block_q8_K *) ((const char *) wdata + (iir1 + r) * src1_stride * src1_bs);
1226
+ + rows[r] = (const BA *) ((const char *) wdata + (iir1 + r) * src1_stride * src1_bs);
1227
+ }
1228
+
1229
+ for (int64_t iir0 = ir0_start; iir0 < ir0_end; iir0 += TILE) {
1230
+ @@ -981,19 +1043,19 @@ static void ggml_compute_forward_mul_mat_tiled_one_chunk(
1231
+ const int kstart = (int) (k0 / TILED_TILE_K);
1232
+ // activation: one 16-row band (n_src1 <= 16). Unpack the whole k_extent chunk
1233
+ // (all slabs) so each activation row is a long stream, then repack each slab in place
1234
+ - tiled_unpack_src1_q8_K(rows, n_src1, &ws->src1, kstart, num_k);
1235
+ + tiled_unpack_src1(rows, n_src1, &ws->src1, kstart, num_k);
1236
+ // repack the whole k_extent chunk in place, if kernel wants to: num_k slabs x 4 tiles, 16 rows, row stride num_k*256
1237
+ tiled_repack_src1(&ws->src1, 0, num_k, ACTBIAS);
1238
+ // weight groups: unpack the k_extent chunk of each 16-row group (the long per-row
1239
+ // read), then one standard MAC per slab accumulating into the same acc rows
1240
+ for (int64_t ir0 = iir0; ir0 < iir0_end; ir0 += MICRO) {
1241
+ const int n0 = (int) MIN(MICRO, iir0_end - ir0);
1242
+ - const B * wbase = (const B *) (src0_row + ir0 * nb01 + kstart * src0_bs);
1243
+ + const B * wbase = (const B *) (src0_row + ir0 * nb01 + kstart * kblk_scale * src0_bs);
1244
+ tiled_unpack_src0(wbase, src0_stride, n0, &ws->src0, num_k);
1245
+ tiled_repack_src0<SUBBLK>(&ws->src0, n0, num_k, BIAS, ACTBIAS);
1246
+ float * buf = ws->acc + (ir0 - iir0) * TILED_TILE_ROWS;
1247
+ for (int slab = 0; slab < num_k; slab++) {
1248
+ - tiled_run_microtile<SUBBLK, HAS_MIN, BIAS, ACTBIAS>(ws->src0, ws->src1, 0, 0, num_k, slab, buf, TILED_TILE_ROWS);
1249
+ + tiled_run_microtile<SUBBLK, HAS_MIN, BIAS, ACTBIAS, Q41>(ws->src0, ws->src1, 0, 0, num_k, slab, buf, TILED_TILE_ROWS);
1250
+ }
1251
+ }
1252
+ }
1253
+ @@ -1001,15 +1063,15 @@ static void ggml_compute_forward_mul_mat_tiled_one_chunk(
1254
+ // standard per-slab path: K is stepped in 256-element chunks
1255
+ for (int64_t ib = 0; ib < ne00; ib += TILE) {
1256
+ const int kblk = (int) (ib / TILE);
1257
+ - tiled_unpack_src0((const B *) (src0_row + iir0 * nb01 + kblk * src0_bs), src0_stride, n_src0, &ws->src0, 1);
1258
+ + tiled_unpack_src0((const B *) (src0_row + iir0 * nb01 + kblk * kblk_scale * src0_bs), src0_stride, n_src0, &ws->src0, 1);
1259
+ tiled_repack_src0<SUBBLK>(&ws->src0, n_src0, 1, BIAS, ACTBIAS);
1260
+ - tiled_unpack_src1_q8_K(rows, n_src1, &ws->src1, kblk, 1);
1261
+ + tiled_unpack_src1(rows, n_src1, &ws->src1, kblk, 1);
1262
+
1263
+ // 16x16 microtiles sweeping the window; repack each src1 band before first use
1264
+ for (int64_t ir1 = iir1; ir1 < iir1_end; ir1 += MICRO) {
1265
+ tiled_repack_src1(&ws->src1, (int) (ir1 - iir1), 1, ACTBIAS);
1266
+ for (int64_t ir0 = iir0; ir0 < iir0_end; ir0 += MICRO) {
1267
+ - tiled_run_microtile<SUBBLK, HAS_MIN, BIAS, ACTBIAS>(ws->src0, ws->src1,
1268
+ + tiled_run_microtile<SUBBLK, HAS_MIN, BIAS, ACTBIAS, Q41>(ws->src0, ws->src1,
1269
+ (int) (ir0 - iir0), (int) (ir1 - iir1), 1, 0,
1270
+ ws->acc, TILED_TILE_ROWS);
1271
+ }
1272
+ @@ -1024,7 +1086,7 @@ static void ggml_compute_forward_mul_mat_tiled_one_chunk(
1273
+ }
1274
+ }
1275
+
1276
+ -template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS>
1277
+ +template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS, typename BA = block_q8_K, bool Q41 = false>
1278
+ static void ggml_compute_forward_mul_mat_tiled_driver(
1279
+ const struct ggml_compute_params * params,
1280
+ struct ggml_tensor * dst) {
1281
+ @@ -1133,7 +1195,7 @@ static void ggml_compute_forward_mul_mat_tiled_driver(
1282
+ const int64_t ir1_start = dr1 * ith1;
1283
+ const int64_t ir1_end = MIN(ir1_start + dr1, nr1);
1284
+
1285
+ - ggml_compute_forward_mul_mat_tiled_one_chunk<B, SUBBLK, HAS_MIN, BIAS, ACTBIAS>(params, dst, ir0_start, ir0_end, ir1_start, ir1_end, ws);
1286
+ + ggml_compute_forward_mul_mat_tiled_one_chunk<B, SUBBLK, HAS_MIN, BIAS, ACTBIAS, BA, Q41>(params, dst, ir0_start, ir0_end, ir1_start, ir1_end, ws);
1287
+
1288
+ if (nth >= nchunk0 * nchunk1) {
1289
+ break;
1290
+ @@ -1145,7 +1207,7 @@ static void ggml_compute_forward_mul_mat_tiled_driver(
1291
+
1292
+ // src0 type dispatch, shared by the MUL_MAT and MUL_MAT_ID entries: one expert for
1293
+ // MUL_MAT_ID (expert_rows != NULL), the full op for MUL_MAT
1294
+ -template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS>
1295
+ +template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS, typename BA = block_q8_K, bool Q41 = false>
1296
+ static bool tiled_matmul_dispatch(const struct ggml_compute_params * params,
1297
+ struct ggml_tensor * dst,
1298
+ const int32_t * expert_rows,
1299
+ @@ -1153,9 +1215,9 @@ static bool tiled_matmul_dispatch(const struct ggml_compute_params * params,
1300
+ int64_t cne1,
1301
+ char * scratch) {
1302
+ if (expert_rows == NULL) {
1303
+ - ggml_compute_forward_mul_mat_tiled_driver<B, SUBBLK, HAS_MIN, BIAS, ACTBIAS>(params, dst);
1304
+ + ggml_compute_forward_mul_mat_tiled_driver<B, SUBBLK, HAS_MIN, BIAS, ACTBIAS, BA, Q41>(params, dst);
1305
+ } else {
1306
+ - ggml_compute_forward_mul_mat_id_tiled_one_expert<B, SUBBLK, HAS_MIN, BIAS, ACTBIAS>(params, dst, cur_a, cne1, expert_rows, scratch);
1307
+ + ggml_compute_forward_mul_mat_id_tiled_one_expert<B, SUBBLK, HAS_MIN, BIAS, ACTBIAS, BA, Q41>(params, dst, cur_a, cne1, expert_rows, scratch);
1308
+ }
1309
+ return true;
1310
+ }
1311
+ @@ -1174,6 +1236,8 @@ static bool ggml_tiled_matmul_type_dispatch(const struct ggml_compute_params * p
1312
+ return tiled_matmul_dispatch<block_q5_K, 32, true, 0, false>(params, dst, expert_rows, cur_a, cne1, scratch);
1313
+ case GGML_TYPE_Q4_K:
1314
+ return tiled_matmul_dispatch<block_q4_K, 32, true, 0, false>(params, dst, expert_rows, cur_a, cne1, scratch);
1315
+ + case GGML_TYPE_Q4_1:
1316
+ + return tiled_matmul_dispatch<block_q4_1, 32, false, 0, false, block_q8_1, true>(params, dst, expert_rows, cur_a, cne1, scratch);
1317
+ case GGML_TYPE_Q3_K:
1318
+ return tiled_matmul_dispatch<block_q3_K, 16, false, 4, true>(params, dst, expert_rows, cur_a, cne1, scratch);
1319
+ case GGML_TYPE_Q2_K:
1320
  diff --git a/ggml/src/ggml.c b/ggml/src/ggml.c
1321
  index 5d8449a88..203f15e43 100644
1322
  --- a/ggml/src/ggml.c