HelloSun commited on
Commit
45f1d0b
·
verified ·
1 Parent(s): bbddfa2

Add NVFP4 CPU kernels results, integrate model card, add handoff

Browse files
README.md CHANGED
@@ -12,24 +12,28 @@ tags:
12
  - amx
13
  - moe
14
  - flint
 
15
  ---
16
 
17
- # flint CPU optimization (llama.cpp patch + two GGUF files)
18
 
19
  [Recursive Flint](https://huggingface.co/RecursiveSI/flint) is a 39.8B-parameter mixture-of-experts coding model with about 2.5B
20
  active parameters per token (256 experts, top-6, 49 layers). This repo holds a **llama.cpp CPU patch** that speeds it up on
21
- Intel Xeon (AVX512-VNNI / AMX) and the two GGUF files it was tuned and measured on. **The GGUF files are unmodified**; every gain
22
  comes from code (`patches/flint-cpu-opt.patch`, base commit `de5122d87`). It is in the style of
23
  [HelloSun/Qwen3.6-35B-A3B-GGUF_CPU_OPT](https://huggingface.co/HelloSun/Qwen3.6-35B-A3B-GGUF_CPU_OPT).
24
 
25
- | file | source | quant | size |
26
- |---|---|---|---|
27
- | `flint-UD-Q4_K_XL.gguf` | [unsloth/flint-GGUF](https://huggingface.co/unsloth/flint-GGUF) | Q5_K 11.6 GiB + IQ4_XS 9.0 GiB + Q8_0 2.0 GiB (+ small Q4_K/Q6_K/IQ3_S) | 23.3 GiB |
28
- | `flint-int4-q4_1.gguf` | [RecursiveSI/flint-int4-gguf](https://huggingface.co/RecursiveSI/flint-int4-gguf) | Q4_1 for every weight (token_embd and router F32) | 24.2 GiB |
 
29
 
30
  ## Which file to use
31
 
32
- Measured on Intel Xeon Platinum 8559C, 16 threads, with the patch (median of 3 interleaved rounds, tokens/s):
 
 
33
 
34
  | file | prefill pp256 | prefill pp2048 | decode tg128 | PPL (wikitext-2, 32x512) |
35
  |---|---|---|---|---|
@@ -37,11 +41,15 @@ Measured on Intel Xeon Platinum 8559C, 16 threads, with the patch (median of 3 i
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
 
46
  ## Gain of the patch (vs stock llama.cpp, same file)
47
 
@@ -49,19 +57,24 @@ Measured on Intel Xeon Platinum 8559C, 16 threads, with the patch (median of 3 i
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).
 
 
55
  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. **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
  |---|---|---|---|
@@ -70,22 +83,19 @@ Wikitext-2 test, 32 chunks x 512, KL divergence against the stated baseline logi
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
-
81
- Measured DRAM read bandwidth on this host is 125-175 GB/s with 16 threads (single thread 15 GB/s). Decode reads about 2 GB of
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
 
@@ -94,19 +104,24 @@ sub-block layout used by the K-quants.
94
  taskset -c 0-15 llama.cpp/build/bin/llama-cli -m flint-UD-Q4_K_XL.gguf -t 16 -fa on -lm mlock
95
  ```
96
 
 
 
97
  `GGML_IQ4XS_PF`, `GGML_Q41_PF`, `GGML_K4_PF`, `GGML_K5_PF`, `GGML_Q6_PF` tune the prefetch distances (defaults are the tuned
98
- values). `patches/optional-profiler.patch` adds `GGML_PROF=1` per-op timing.
99
 
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
 
 
12
  - amx
13
  - moe
14
  - flint
15
+ - nvfp4
16
  ---
17
 
18
+ # flint CPU optimization (llama.cpp patch + GGUF files)
19
 
20
  [Recursive Flint](https://huggingface.co/RecursiveSI/flint) is a 39.8B-parameter mixture-of-experts coding model with about 2.5B
21
  active parameters per token (256 experts, top-6, 49 layers). This repo holds a **llama.cpp CPU patch** that speeds it up on
22
+ Intel Xeon (AVX512-VNNI / AMX) and the GGUF files it was tuned and measured on. **The GGUF files are unmodified**; every gain
23
  comes from code (`patches/flint-cpu-opt.patch`, base commit `de5122d87`). It is in the style of
24
  [HelloSun/Qwen3.6-35B-A3B-GGUF_CPU_OPT](https://huggingface.co/HelloSun/Qwen3.6-35B-A3B-GGUF_CPU_OPT).
25
 
26
+ | file | source | quant | size | in this repo |
27
+ |---|---|---|---|---|
28
+ | `flint-UD-Q4_K_XL.gguf` | [unsloth/flint-GGUF](https://huggingface.co/unsloth/flint-GGUF) | Q5_K 11.6 GiB + IQ4_XS 9.0 GiB + Q8_0 2.0 GiB (+ small Q4_K/Q6_K/IQ3_S) | 23.3 GiB | yes |
29
+ | `flint-int4-q4_1.gguf` | [RecursiveSI/flint-int4-gguf](https://huggingface.co/RecursiveSI/flint-int4-gguf) | Q4_1 for every weight (token_embd and router F32) | 24.2 GiB | yes |
30
+ | `flint-nvfp4.gguf` | [RecursiveSI/flint-nvfp4-gguf](https://huggingface.co/RecursiveSI/flint-nvfp4-gguf) | NVFP4 weights (485 tensors, 20.7 GiB), F32 per-tensor `.scale`, BF16 token_embd | 22.9 GB | **no**, download from the link |
31
 
32
  ## Which file to use
33
 
34
+ Measured on Intel Xeon Platinum 8559C, 16 threads, with the patch (median of 3 interleaved rounds, tokens/s).
35
+ Q4_K_XL and Q4_1 rows are from the earlier measurement on a quieter host; the NVFP4 rows are from a later run on a busier
36
+ host (see "Measurement notes"), so compare NVFP4 only with the NVFP4 rows.
37
 
38
  | file | prefill pp256 | prefill pp2048 | decode tg128 | PPL (wikitext-2, 32x512) |
39
  |---|---|---|---|---|
 
41
  | **Q4_K_XL, patched** | **296** | **307** | 42.8 | 18.277 +- 0.787 |
42
  | Q4_1, stock llama.cpp | 183 | 189 | 48.3 | 18.260 +- 0.780 |
43
  | **Q4_1, patched** | 266 | 297 | **55.7** | 18.240 +- 0.779 |
44
+ | NVFP4, stock llama.cpp | 83.7 | 83.6 | 30.6 | 24.417 +- 1.566 (wikitext-2, 16x512) |
45
+ | **NVFP4, patched** | **178.5** | **211.1** | 32.1 | 24.414 +- 1.568 (wikitext-2, 16x512) |
46
 
47
+ - **Long prompts / large contexts (prefill-heavy): Q4_K_XL** still prefills fastest of the three in the earlier measurement,
48
+ and patched Q4_1 is within about 10 %.
49
+ - **Chat and agent loops with a cached prefix (decode-heavy): Q4_1**. It decodes about 30 % faster than Q4_K_XL.
50
+ - **NVFP4 on CPU**: prefill is about 2.1x (pp256) to 2.5x (pp2048) faster than stock after the patch. Decode is unchanged
51
+ within noise. NVFP4 is primarily intended for CUDA; on this CPU it is still slower than Q4_1 for both phases.
52
+ - Quality is equivalent within measurement error: PPL differences are much smaller than the standard errors.
53
 
54
  ## Gain of the patch (vs stock llama.cpp, same file)
55
 
 
57
  |---|---|---|---|
58
  | Q4_K_XL | 1.28x | 1.12x | 1.12x |
59
  | Q4_1 | 1.46x | 1.57x | 1.15x |
60
+ | NVFP4 | 2.13x | 2.52x | 1.05x (noise) |
61
 
62
+ ## What the patch changes
63
+
64
+ For Q4_K_XL most of the gain comes from the Qwen3.6 patch kernels (Q4_K/Q5_K/Q6_K VNNI, AMX tweaks, MoE tile grouping) also
65
+ applying to flint. Against the Qwen3.6 patch alone the flint-specific part is small (pp256 +12 %, decode +2 %, pp2048 equal).
66
  The flint-specific changes:
67
 
68
  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.
69
  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.
70
  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.
71
  4. Q5_K 5th-bit merge with `vptestmb` + masked add (bit-identical to the previous version, no speed change).
72
+ 5. **NVFP4 x Q8_0 `vec_dot` in AVX512-VNNI** (decode). Codes `|w|` (0..12) form the unsigned operand, the weight sign is moved onto the int8 activations. Each 64-element block has four 16-element subblocks with UE4M3 scales.
73
+ 6. **NVFP4 in the tiled VNNI GEMM** (prefill). Weights are stored as `LUT + 12` (unsigned 0..24) with one f32 scale per 16-wide subblock (`sd16`). The bias is removed exactly through the per-16 activation code sums. Activations are Q8_0 with one scale per 32-block. Enabled only in VNNI builds.
74
 
75
  ## Accuracy
76
 
77
+ Wikitext-2 test, KL divergence against the stated baseline logits of the same file (Q4_K_XL / Q4_1):
78
 
79
  | comparison | KLD | same top-1 | noise floor: stock vs stock with `-fa off` |
80
  |---|---|---|---|
 
83
  | Q4_1, tiled GEMM vs `vec_dot` patch | 0.0372 +- 0.0026 | 94.2 % | 0.0385 |
84
 
85
  The KLD is **inside the noise floor of stock itself**: flint is very sensitive to tiny numeric differences (changing only the
86
+ attention path of stock gives the same KLD), so it does not indicate a defect.
87
+
88
+ NVFP4 (not KLD-tested): perplexity on wikitext-2 test, 16 chunks x 512 tokens, `llama-perplexity`: stock 24.4165 +- 1.566,
89
+ patched 24.4135 +- 1.568. Kernel-level checks: AVX512 `vec_dot` vs generic reference, relative error <= 1.3e-6; tiled vs
90
+ `vec_dot`, relative L2 1.4e-7 .. 2.5e-7 over several shapes including ragged row counts and the narrow path.
91
+ Integer dot products are exact; only float accumulation order changes.
92
+
93
+ ## Where the time goes
94
+
95
+ Measured DRAM read bandwidth on this host is 125-175 GB/s with 16 threads (single thread 15 GB/s). Decode of the Q4_K_XL / Q4_1
96
+ files is mostly memory-bound (about 90 GB/s effective). Prefill is compute-bound. For NVFP4, `GGML_PROF` per-op timing showed
97
+ that prefill `MUL_MAT` / `MUL_MAT_ID` with 256 rows dominated the run time before the tiled kernel was added; decode spends its
98
+ time spread over many small ops.
 
 
 
99
 
100
  ## Build and run
101
 
 
104
  taskset -c 0-15 llama.cpp/build/bin/llama-cli -m flint-UD-Q4_K_XL.gguf -t 16 -fa on -lm mlock
105
  ```
106
 
107
+ For NVFP4, download `flint-nvfp4.gguf` from RecursiveSI and run the same binary with `-m flint-nvfp4.gguf`.
108
+
109
  `GGML_IQ4XS_PF`, `GGML_Q41_PF`, `GGML_K4_PF`, `GGML_K5_PF`, `GGML_Q6_PF` tune the prefetch distances (defaults are the tuned
110
+ values). `patches/optional-profiler.patch` adds `GGML_PROF=1` per-op timing. `GGML_CPU_TILED_MM=0` disables the tiled GEMM.
111
 
112
+ ## Measurement notes
113
 
114
  - 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).
115
+ - Single runs on this shared host vary widely. The NVFP4 round-to-round spread was up to 2x for decode while the host load average was 55-63. Use medians of interleaved rounds; the raw data is in `benchmarks/nvfp4/`.
116
+ - 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.
117
+ - PPL and KLD use short tests (16 or 32 chunks of 512 tokens); no downstream task evaluation was run.
118
 
119
  ## Files
120
 
121
  - `patches/flint-cpu-opt.patch` (base `patches/BASE_COMMIT`), `patches/optional-profiler.patch`, `build.sh`, `machine.txt`
122
  - `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`
123
+ - `benchmarks/nvfp4/` (NVFP4 summary `README.md`, raw llama-bench CSV per round `bin_*_r*.csv`, flat `summary.csv`, PPL logs `ppl_*.log`, run script, kernel test sources)
124
+ - `handoff/HANDOFF.md`: state of the work and next steps for the next agent
125
 
126
  ## Credits and license
127
 
benchmarks/nvfp4/README.md ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # RecursiveSI/flint-nvfp4-gguf (flint-nvfp4.gguf, 22.9 GB) with the CPU patch
2
+
3
+ NVFP4 weights (485 tensors, 20.7 GiB) plus F32 per-tensor `.scale` tensors and BF16 token_embd. Only the NVFP4 file was
4
+ measured here; the Q4_1 and Q4_K_XL numbers in `README.md` / `q41-summary.md` were not re-run.
5
+
6
+ ## What was added to the patch (`patches/flint-cpu-opt.patch`)
7
+
8
+ 1. **AVX512-VNNI `vec_dot` for NVFP4 x Q8_0** (decode, `ggml/src/ggml-cpu/arch/x86/quants.c`). Weight codes are
9
+ `|w|` (0..12, unsigned operand of `vpdpbusd`), the sign of `w` is moved onto the int8 activations
10
+ (`_mm256_sign_epi8`). The 64-element block is split into 4 subblocks of 16 with per-subblock UE4M3 scales.
11
+ 2. **Tiled VNNI GEMM for NVFP4** (prefill, `ggml/src/ggml-cpu/tiled/`). Same framework as the Q4_1 path. Weights are
12
+ stored as `LUT + 12` (unsigned, 0..24) with one f32 scale per 16-wide subblock (`sd16`). The bias is removed exactly
13
+ with the per-16 activation code sums (`bsums`): `sum(w*a) = sum((w+12)*a) - 12*sum(a)`. Activations use Q8_0 with
14
+ one scale per 32-block. `tiled_unpk_fp4_64` is an AVX2 unpacker (scalar fallback).
15
+ Enabled only in VNNI builds (`__AVX512VNNI__ && __AVX512VL__`).
16
+
17
+ Integer dot products are exact; only the float accumulation order changes.
18
+
19
+ ## Results
20
+
21
+ Xeon Platinum 8559C, `taskset -c 0-15`, `-t 16 -fa 1 -b 1024 -ub 1024 -lm mmap+mlock -p 256,2048 -n 128 -r 3`.
22
+ Three interleaved rounds per build, median of the per-round means (tokens/s). Raw CSV: `bin_*_r{1,2,3}.csv`,
23
+ flat list: `summary.csv`.
24
+
25
+ | build | pp256 | pp2048 | tg128 |
26
+ |---|---|---|---|
27
+ | **before**: stock llama.cpp (de5122d87), no patch | 83.7 | 83.6 | 30.6 |
28
+ | patch (Q4_1 kernels), NVFP4 path unchanged | 82.3 | 84.4 | 31.8 |
29
+ | **after**: patch + NVFP4 VNNI vec_dot + tiled VNNI GEMM | **178.5** | **211.1** | 32.1 |
30
+
31
+ | gain | pp256 | pp2048 | tg128 |
32
+ |---|---|---|---|
33
+ | after vs stock | 2.13x | 2.52x | 1.05x (noise) |
34
+ | after vs patch-before | 2.17x | 2.50x | 1.01x (noise) |
35
+
36
+ Per-round values: pp256 after = 153.1 / 178.5 / 181.6, pp2048 after = 206.7 / 218.6 / 211.1, tg128 after = 32.1 / 29.8 / 32.4.
37
+
38
+ Decode (tg128) did not change measurably. The VNNI vec_dot was about 1 % faster in an earlier single-run check, which is within
39
+ noise. Per-op profiling (`GGML_PROF`) showed that prefill `MUL_MAT`/`MUL_MAT_ID` with 256 rows dominated the run time.
40
+
41
+ ## Correctness
42
+
43
+ - `vec_dot` AVX512 vs generic reference (random blocks, n = 1920..12352): relative error <= 1.3e-6 (float ordering only).
44
+ - Dense `mul_mat`, tiled (M=64) vs vec_dot (M=1): relative L2 1.4e-7 .. 2.5e-7 for K in {1024, 2048, 4096, 8192} and N in {8, 16, 24, 100, 256, 512}
45
+ (covers ragged row counts and the narrow path). Versus f32 reference: about 3.7e-3 (same order as Q4_1 in the HANDOFF).
46
+ - wikitext-2 test, 16 chunks x 512, llama-perplexity:
47
+
48
+ | build | PPL |
49
+ |---|---|
50
+ | stock | 24.4165 +- 1.566 |
51
+ | patch + NVFP4 opt | 24.4135 +- 1.568 |
52
+
53
+ ## Notes
54
+
55
+ - The host was shared and heavily loaded during these runs (load average 55..63). Single runs ranged widely
56
+ (stock tg128: 14.1 / 32.3 / 30.6), so use the medians. Absolute numbers are lower than the Q4_1 reference figures in
57
+ `q41-summary.md`, which were measured on a quieter host. Compare only within this table.
58
+ - After the patch, NVFP4 prefill (pp2048 211) is still below Q4_1 (pp2048 297 in `q41-summary.md`). Decode is about 32 t/s versus
59
+ 55.7 t/s for Q4_1 on the reference host, which is not a fair comparison because the hosts differ.
60
+ - Reproduce: `bench_run.sh` runs three copies of llama-bench (stock, patch before, patch after) from the directories
61
+ set in `LD_LIBRARY_PATH`/paths in the script. Those copies live under `/tmp/opencode` and are not kept. Rebuild them from
62
+ `de5122d87` (stock) and from `build.sh` (patched). `test_nvfp4.c` and `tiled_test.c` are the kernel checks above.
benchmarks/nvfp4/bench_run.sh ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # interleaved A/B: each round runs stock, patched-before, patched-after for NVFP4 only
3
+ M=/root/flint_cpu_opt/flint-nvfp4.gguf
4
+ OUT=/tmp/opencode/bench
5
+ for R in 1 2 3; do
6
+ for B in bin_stock bin_patched_before bin_ppl_new; do
7
+ LD_LIBRARY_PATH=/tmp/opencode/$B taskset -c 0-15 /tmp/opencode/$B/llama-bench -m $M -t 16 -fa 1 -b 1024 -ub 1024 -lm mmap+mlock -p 256,2048 -n 128 -r 3 -o csv > $OUT/${B}_r$R.csv 2> $OUT/${B}_r$R.err
8
+ echo "$B round$R exit $? $(uptime | sed 's/.*average://')" >> $OUT/done.log
9
+ done
10
+ done
11
+ echo ALLDONE >> $OUT/done.log
benchmarks/nvfp4/bin_patched_before_r1.csv ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ build_commit,build_number,cpu_info,gpu_info,backends,model_filename,model_type,model_size,model_n_params,n_batch,n_ubatch,n_threads,cpu_mask,cpu_strict,poll,type_k,type_v,n_gpu_layers,n_cpu_moe,split_mode,main_gpu,no_kv_offload,flash_attn,devices,tensor_split,tensor_buft_overrides,load_mode,lazy_mode,embeddings,no_op_offload,no_host,repack,fit_target,fit_min_ctx,n_prompt,n_gen,n_depth,test_time,avg_ns,stddev_ns,avg_ts,stddev_ts
2
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","256","0","0","2026-11-02T20:02:06Z","3312164723","272821630","77.654558","6.627089"
3
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","2048","0","0","2026-11-02T20:02:21Z","24422205324","3043096286","83.861574","0.659045"
4
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","0","128","0","2026-11-02T20:04:01Z","3997120387","118519237","32.041668","0.941769"
benchmarks/nvfp4/bin_patched_before_r2.csv ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ build_commit,build_number,cpu_info,gpu_info,backends,model_filename,model_type,model_size,model_n_params,n_batch,n_ubatch,n_threads,cpu_mask,cpu_strict,poll,type_k,type_v,n_gpu_layers,n_cpu_moe,split_mode,main_gpu,no_kv_offload,flash_attn,devices,tensor_split,tensor_buft_overrides,load_mode,lazy_mode,embeddings,no_op_offload,no_host,repack,fit_target,fit_min_ctx,n_prompt,n_gen,n_depth,test_time,avg_ns,stddev_ns,avg_ts,stddev_ts
2
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","256","0","0","2026-11-02T20:07:24Z","3055729123","36750733","83.785192","1.014280"
3
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","2048","0","0","2026-11-02T20:07:37Z","24271223135","273666724","84.386872","0.946025"
4
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","0","128","0","2026-11-02T20:09:14Z","4029829668","17507005","31.763528","0.137680"
benchmarks/nvfp4/bin_patched_before_r3.csv ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ build_commit,build_number,cpu_info,gpu_info,backends,model_filename,model_type,model_size,model_n_params,n_batch,n_ubatch,n_threads,cpu_mask,cpu_strict,poll,type_k,type_v,n_gpu_layers,n_cpu_moe,split_mode,main_gpu,no_kv_offload,flash_attn,devices,tensor_split,tensor_buft_overrides,load_mode,lazy_mode,embeddings,no_op_offload,no_host,repack,fit_target,fit_min_ctx,n_prompt,n_gen,n_depth,test_time,avg_ns,stddev_ns,avg_ts,stddev_ts
2
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","256","0","0","2026-11-02T20:12:41Z","3115243871","174625727","82.344664","4.499385"
3
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","2048","0","0","2026-11-02T20:12:54Z","24047580535","123875970","85.166000","0.438822"
4
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","0","128","0","2026-11-02T20:14:30Z","4156609034","39183991","30.796166","0.291850"
benchmarks/nvfp4/bin_ppl_new_r1.csv ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ build_commit,build_number,cpu_info,gpu_info,backends,model_filename,model_type,model_size,model_n_params,n_batch,n_ubatch,n_threads,cpu_mask,cpu_strict,poll,type_k,type_v,n_gpu_layers,n_cpu_moe,split_mode,main_gpu,no_kv_offload,flash_attn,devices,tensor_split,tensor_buft_overrides,load_mode,lazy_mode,embeddings,no_op_offload,no_host,repack,fit_target,fit_min_ctx,n_prompt,n_gen,n_depth,test_time,avg_ns,stddev_ns,avg_ts,stddev_ts
2
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","256","0","0","2026-11-02T20:04:17Z","1701202055","265548301","153.080592","25.051629"
3
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","2048","0","0","2026-11-02T20:04:24Z","9925366157","487402521","206.674830","10.231588"
4
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","0","128","0","2026-11-02T20:05:03Z","3984507398","114876303","32.142090","0.919293"
benchmarks/nvfp4/bin_ppl_new_r2.csv ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ build_commit,build_number,cpu_info,gpu_info,backends,model_filename,model_type,model_size,model_n_params,n_batch,n_ubatch,n_threads,cpu_mask,cpu_strict,poll,type_k,type_v,n_gpu_layers,n_cpu_moe,split_mode,main_gpu,no_kv_offload,flash_attn,devices,tensor_split,tensor_buft_overrides,load_mode,lazy_mode,embeddings,no_op_offload,no_host,repack,fit_target,fit_min_ctx,n_prompt,n_gen,n_depth,test_time,avg_ns,stddev_ns,avg_ts,stddev_ts
2
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","256","0","0","2026-11-02T20:09:31Z","1440909288","122602118","178.486780","14.476883"
3
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","2048","0","0","2026-11-02T20:09:37Z","9376213979","351329487","218.631981","8.285264"
4
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","0","128","0","2026-11-02T20:10:14Z","4316434407","422247907","29.834551","2.767181"
benchmarks/nvfp4/bin_ppl_new_r3.csv ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ build_commit,build_number,cpu_info,gpu_info,backends,model_filename,model_type,model_size,model_n_params,n_batch,n_ubatch,n_threads,cpu_mask,cpu_strict,poll,type_k,type_v,n_gpu_layers,n_cpu_moe,split_mode,main_gpu,no_kv_offload,flash_attn,devices,tensor_split,tensor_buft_overrides,load_mode,lazy_mode,embeddings,no_op_offload,no_host,repack,fit_target,fit_min_ctx,n_prompt,n_gen,n_depth,test_time,avg_ns,stddev_ns,avg_ts,stddev_ts
2
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","256","0","0","2026-11-02T20:14:47Z","1409540222","17310901","181.637662","2.217434"
3
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","2048","0","0","2026-11-02T20:14:54Z","9740323935","742766637","211.051780","15.582107"
4
+ "de5122d87","1","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","0","128","0","2026-11-02T20:15:32Z","3955095681","36151817","32.365110","0.294863"
benchmarks/nvfp4/bin_stock_r1.csv ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ build_commit,build_number,cpu_info,gpu_info,backends,model_filename,model_type,model_size,model_n_params,n_batch,n_ubatch,n_threads,cpu_mask,cpu_strict,poll,type_k,type_v,n_gpu_layers,n_cpu_moe,split_mode,main_gpu,no_kv_offload,flash_attn,devices,tensor_split,tensor_buft_overrides,load_mode,lazy_mode,embeddings,no_op_offload,no_host,repack,fit_target,fit_min_ctx,n_prompt,n_gen,n_depth,test_time,avg_ns,stddev_ns,avg_ts,stddev_ts
2
+ "unknown","0","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","256","0","0","2026-11-02T19:59:33Z","3051342444","30706918","83.903179","0.846589"
3
+ "unknown","0","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","2048","0","0","2026-11-02T19:59:46Z","26896699672","1704117578","76.355246","5.020894"
4
+ "unknown","0","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","0","128","0","2026-11-02T20:01:31Z","10405205386","1600748572","14.122672","6.412075"
benchmarks/nvfp4/bin_stock_r2.csv ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ build_commit,build_number,cpu_info,gpu_info,backends,model_filename,model_type,model_size,model_n_params,n_batch,n_ubatch,n_threads,cpu_mask,cpu_strict,poll,type_k,type_v,n_gpu_layers,n_cpu_moe,split_mode,main_gpu,no_kv_offload,flash_attn,devices,tensor_split,tensor_buft_overrides,load_mode,lazy_mode,embeddings,no_op_offload,no_host,repack,fit_target,fit_min_ctx,n_prompt,n_gen,n_depth,test_time,avg_ns,stddev_ns,avg_ts,stddev_ts
2
+ "unknown","0","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","256","0","0","2026-11-02T20:05:19Z","3057753126","7591527","83.721949","0.207641"
3
+ "unknown","0","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","2048","0","0","2026-11-02T20:05:31Z","24160641638","134679578","84.767711","0.471008"
4
+ "unknown","0","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","0","128","0","2026-11-02T20:07:09Z","3961015788","80138381","32.323836","0.659357"
benchmarks/nvfp4/bin_stock_r3.csv ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ build_commit,build_number,cpu_info,gpu_info,backends,model_filename,model_type,model_size,model_n_params,n_batch,n_ubatch,n_threads,cpu_mask,cpu_strict,poll,type_k,type_v,n_gpu_layers,n_cpu_moe,split_mode,main_gpu,no_kv_offload,flash_attn,devices,tensor_split,tensor_buft_overrides,load_mode,lazy_mode,embeddings,no_op_offload,no_host,repack,fit_target,fit_min_ctx,n_prompt,n_gen,n_depth,test_time,avg_ns,stddev_ns,avg_ts,stddev_ts
2
+ "unknown","0","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","256","0","0","2026-11-02T20:10:31Z","3526759564","83460948","72.614765","1.704199"
3
+ "unknown","0","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","2048","0","0","2026-11-02T20:10:45Z","24491094581","185574398","83.625447","0.636362"
4
+ "unknown","0","Intel(R) Xeon(R) Platinum 8559C","","CPU","/root/flint_cpu_opt/flint-nvfp4.gguf","flint ?B NVFP4","22857561568","39785323896","1024","1024","16","0x0","0","50","f16","f16","-1","0","layer","0","0","1","auto","0.00","none","mmap+mlock","auto","0","0","0","1","0","0","0","128","0","2026-11-02T20:12:24Z","4183101976","62041693","30.603761","0.450938"
benchmarks/nvfp4/ppl_nvfp4_opt.log ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ 0.01.321.361 I cmn init: llama threadpool init, n_threads = 16
2
+ 0.01.446.694 I
3
+ 0.01.446.805 I system_info: n_threads = 16 (n_threads_batch = 16) / 192 | CPU : SSE3 = 1 | SSSE3 = 1 | AVX = 1 | AVX_VNNI = 1 | AVX2 = 1 | F16C = 1 | FMA = 1 | BMI2 = 1 | AVX512 = 1 | AVX512_VBMI = 1 | AVX512_VNNI = 1 | AVX512_BF16 = 1 | AMX_INT8 = 1 | LLAMAFILE = 1 | OPENMP = 1 | REPACK = 1 |
4
+ 0.01.446.823 I perplexity: tokenizing the input ..
5
+ 0.02.276.737 I perplexity: tokenization took 829.903 ms
6
+ 0.02.276.945 I perplexity: calculating perplexity over 16 chunks, n_ctx=512, batch_size=512, n_seq=1
7
+ 0.05.073.679 I perplexity: 2.80 seconds per pass - ETA 0.73 minutes
8
+ [1]18.2490,[2]22.6034,[3]23.5732,[4]24.1180,[5]22.7978,[6]24.8210,[7]25.0443,[8]26.6022,[9]26.3313,[10]26.4278,[11]26.0816,[12]26.7523,[13]26.3263,[14]25.5603,[15]26.2636,[16]24.4135,
9
+ 0.46.897.942 I Final estimate: PPL = 24.4135 +/- 1.56769
10
+
benchmarks/nvfp4/ppl_stock.log ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ 0.05.265.549 I cmn init: llama threadpool init, n_threads = 16
2
+ 0.05.391.697 I
3
+ 0.05.391.798 I system_info: n_threads = 16 (n_threads_batch = 16) / 192 | CPU : SSE3 = 1 | SSSE3 = 1 | AVX = 1 | AVX_VNNI = 1 | AVX2 = 1 | F16C = 1 | FMA = 1 | BMI2 = 1 | AVX512 = 1 | AVX512_VBMI = 1 | AVX512_VNNI = 1 | AVX512_BF16 = 1 | AMX_INT8 = 1 | LLAMAFILE = 1 | OPENMP = 1 | REPACK = 1 |
4
+ 0.05.391.816 I perplexity: tokenizing the input ..
5
+ 0.06.315.987 I perplexity: tokenization took 924.156 ms
6
+ 0.06.316.239 I perplexity: calculating perplexity over 16 chunks, n_ctx=512, batch_size=512, n_seq=1
7
+ 0.13.030.958 I perplexity: 6.71 seconds per pass - ETA 1.78 minutes
8
+ [1]18.5652,[2]22.5217,[3]23.1922,[4]24.0153,[5]22.5697,[6]24.5359,[7]24.7302,[8]26.3839,[9]26.1684,[10]26.2347,[11]25.8563,[12]26.5899,[13]26.1893,[14]25.4773,[15]26.1958,[16]24.4165,
9
+ 1.49.754.158 I Final estimate: PPL = 24.4165 +/- 1.56606
10
+
benchmarks/nvfp4/summary.csv ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ build,round,test,t_s
2
+ stock,1,pp256,83.903
3
+ stock,1,pp2048,76.355
4
+ stock,1,tg128,14.123
5
+ stock,2,pp256,83.722
6
+ stock,2,pp2048,84.768
7
+ stock,2,tg128,32.324
8
+ stock,3,pp256,72.615
9
+ stock,3,pp2048,83.625
10
+ stock,3,tg128,30.604
11
+ patch_before,1,pp256,77.655
12
+ patch_before,1,pp2048,83.862
13
+ patch_before,1,tg128,32.042
14
+ patch_before,2,pp256,83.785
15
+ patch_before,2,pp2048,84.387
16
+ patch_before,2,tg128,31.764
17
+ patch_before,3,pp256,82.345
18
+ patch_before,3,pp2048,85.166
19
+ patch_before,3,tg128,30.796
20
+ patch_nvfp4_opt,1,pp256,153.081
21
+ patch_nvfp4_opt,1,pp2048,206.675
22
+ patch_nvfp4_opt,1,tg128,32.142
23
+ patch_nvfp4_opt,2,pp256,178.487
24
+ patch_nvfp4_opt,2,pp2048,218.632
25
+ patch_nvfp4_opt,2,tg128,29.835
26
+ patch_nvfp4_opt,3,pp256,181.638
27
+ patch_nvfp4_opt,3,pp2048,211.052
28
+ patch_nvfp4_opt,3,tg128,32.365
benchmarks/nvfp4/test_nvfp4.c ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Compare ggml_vec_dot_nvfp4_q8_0 (arch path) against ggml_vec_dot_nvfp4_q8_0_generic on random data.
2
+ #include <stdint.h>
3
+ #include <stdio.h>
4
+ #include <stdlib.h>
5
+ #include <string.h>
6
+ #include <math.h>
7
+ #include <stddef.h>
8
+
9
+ typedef struct { uint8_t d[4]; uint8_t qs[32]; } block_nvfp4; // 64 elems: 4 UE4M3 scales + 32 nibble bytes
10
+ typedef struct { _Float16 d; int8_t qs[32]; } block_q8_0; // 32 elems
11
+
12
+ void ggml_vec_dot_nvfp4_q8_0(int n, float * s, size_t bs, const void * vx, size_t bx, const void * vy, size_t by, int nrc);
13
+ void ggml_vec_dot_nvfp4_q8_0_generic(int n, float * s, size_t bs, const void * vx, size_t bx, const void * vy, size_t by, int nrc);
14
+
15
+ static uint32_t rng_state = 12345;
16
+ static uint32_t rnd(void) { rng_state = rng_state * 1664525u + 1013904223u; return rng_state >> 8; }
17
+
18
+ void ggml_cpu_init(void);
19
+ int main(void) {
20
+ ggml_cpu_init();
21
+ int fails = 0;
22
+ for (int trial = 0; trial < 20; ++trial) {
23
+ const int n = 64 * (1 + (trial * 37) % 200);
24
+ const int nb = n / 64;
25
+ block_nvfp4 * w = aligned_alloc(64, sizeof(block_nvfp4) * nb);
26
+ block_q8_0 * a = aligned_alloc(64, sizeof(block_q8_0) * (n / 32));
27
+ for (int i = 0; i < nb; ++i) {
28
+ for (int s = 0; s < 4; ++s) w[i].d[s] = (uint8_t)(rnd() % 127); // avoid NaN code 0x7F
29
+ for (int j = 0; j < 32; ++j) w[i].qs[j] = (uint8_t)rnd();
30
+ }
31
+ for (int i = 0; i < n / 32; ++i) {
32
+ float amax = 0.f; float xs[32];
33
+ for (int j = 0; j < 32; ++j) { xs[j] = ((int)(rnd() % 20001) - 10000) / 1000.0f; if (fabsf(xs[j]) > amax) amax = fabsf(xs[j]); }
34
+ float d = amax / 127.0f; float id = d ? 1.0f / d : 0.0f;
35
+ a[i].d = (_Float16) d;
36
+ for (int j = 0; j < 32; ++j) a[i].qs[j] = (int8_t) lrintf(xs[j] * id);
37
+ }
38
+ float r_gen = 0.f, r_arch = 0.f;
39
+ ggml_vec_dot_nvfp4_q8_0_generic(n, &r_gen, 0, w, 0, a, 0, 1);
40
+ ggml_vec_dot_nvfp4_q8_0(n, &r_arch, 0, w, 0, a, 0, 1);
41
+ double rel = fabs((double) r_arch - r_gen) / (fabs((double) r_gen) + 1e-9);
42
+ printf("n=%6d generic=% .6f arch=% .6f rel=%.3e %s\n", n, r_gen, r_arch, rel, rel < 1e-5 ? "OK" : "FAIL");
43
+ if (rel >= 1e-5) fails++;
44
+ free(w); free(a);
45
+ }
46
+ printf("%s (%d failures)\n", fails ? "FAILED" : "PASSED", fails);
47
+ return fails != 0;
48
+ }
benchmarks/nvfp4/tiled_test.c ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Dense mul_mat check for NVFP4: tiled path (M=64 columns) vs vec_dot path (M=1) vs float reference.
2
+ #include "ggml.h"
3
+ #include "ggml-cpu.h"
4
+ #include <math.h>
5
+ #include <stdint.h>
6
+ #include <stdio.h>
7
+ #include <stdlib.h>
8
+ #include <string.h>
9
+
10
+ void ggml_cpu_init(void);
11
+ void dequantize_row_nvfp4(const void * x, float * y, int64_t k);
12
+
13
+ static uint32_t st = 777;
14
+ static uint32_t rnd(void) { st = st * 1664525u + 1013904223u; return st >> 8; }
15
+ static float frnd(void) { return ((int) (rnd() % 20001) - 10000) / 10000.0f; }
16
+
17
+ int main(int argc, char ** argv) {
18
+ const int K = argc > 1 ? atoi(argv[1]) : 2048;
19
+ const int N = argc > 2 ? atoi(argv[2]) : 512;
20
+ const int M = 64;
21
+
22
+ ggml_cpu_init();
23
+ struct ggml_init_params ip = { .mem_size = 512u * 1024 * 1024, .mem_buffer = NULL, .no_alloc = false };
24
+ struct ggml_context * ctx = ggml_init(ip);
25
+
26
+ struct ggml_tensor * w = ggml_new_tensor_2d(ctx, GGML_TYPE_NVFP4, K, N);
27
+ struct ggml_tensor * xM = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, K, M);
28
+ struct ggml_tensor * x1 = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, K, 1);
29
+
30
+ // random valid nvfp4 blocks (UE4M3 codes below the NaN code)
31
+ uint8_t * wd = (uint8_t *) w->data;
32
+ const size_t blocks = (size_t) N * (K / 64);
33
+ for (size_t b = 0; b < blocks; b++) {
34
+ uint8_t * blk = wd + b * 36;
35
+ for (int s = 0; s < 4; s++) blk[s] = (uint8_t) (rnd() % 0x70);
36
+ for (int j = 0; j < 32; j++) blk[4 + j] = (uint8_t) rnd();
37
+ }
38
+ float * xd = (float *) xM->data;
39
+ for (int m = 0; m < M; m++) for (int k = 0; k < K; k++) xd[(size_t) m * K + k] = frnd();
40
+ for (int k = 0; k < K; k++) ((float *) x1->data)[k] = xd[k]; // column 0
41
+
42
+ struct ggml_tensor * yM = ggml_mul_mat(ctx, w, xM);
43
+ struct ggml_tensor * y1 = ggml_mul_mat(ctx, w, x1);
44
+ struct ggml_cgraph * gM = ggml_new_graph(ctx);
45
+ ggml_build_forward_expand(gM, yM);
46
+ ggml_graph_compute_with_ctx(ctx, gM, 16);
47
+ struct ggml_cgraph * g1 = ggml_new_graph(ctx);
48
+ ggml_build_forward_expand(g1, y1);
49
+ ggml_graph_compute_with_ctx(ctx, g1, 16);
50
+
51
+ // float reference from the dequantized weights
52
+ float * wf = malloc(sizeof(float) * (size_t) K * N);
53
+ for (int n = 0; n < N; n++) dequantize_row_nvfp4(wd + (size_t) n * (K / 64) * 36, wf + (size_t) n * K, K);
54
+
55
+ double e_tiled = 0, n_tiled = 0, e_ref = 0, n_ref = 0, e_ref1 = 0;
56
+ const float * yMd = (const float *) yM->data;
57
+ const float * y1d = (const float *) y1->data;
58
+ for (int n = 0; n < N; n++) {
59
+ double ref = 0;
60
+ for (int k = 0; k < K; k++) ref += (double) wf[(size_t) n * K + k] * xd[k];
61
+ // column 0 of the tiled result vs the vec_dot result
62
+ const double a = yMd[n], b = y1d[n];
63
+ e_tiled += (a - b) * (a - b); n_tiled += b * b;
64
+ e_ref += (b - ref) * (b - ref); n_ref += ref * ref;
65
+ for (int m = 1; m < M; m++) {
66
+ double r = 0;
67
+ for (int k = 0; k < K; k++) r += (double) wf[(size_t) n * K + k] * xd[(size_t) m * K + k];
68
+ const double t = yMd[(size_t) m * N + n];
69
+ e_ref1 += (t - r) * (t - r);
70
+ n_ref += r * r;
71
+ }
72
+ }
73
+ printf("K=%d N=%d M=%d\n", K, N, M);
74
+ printf("tiled(M=64) vs vec_dot(M=1), col0: rel L2 = %.3e\n", sqrt(e_tiled / n_tiled));
75
+ printf("vec_dot(M=1) vs f32 ref (col0): rel L2 = %.3e\n", sqrt(e_ref / n_ref));
76
+ printf("tiled(M=64) vs f32 ref (cols 1..): rel L2 = %.3e\n", sqrt(e_ref1 / n_ref));
77
+ free(wf);
78
+ ggml_free(ctx);
79
+ return 0;
80
+ }
handoff/HANDOFF.md ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # HANDOFF: flint CPU optimization (Q4_1 + NVFP4)
2
+
3
+ Last updated: 2026-11-02. Repo: `/root/flint_cpu_opt` (mirrored to `HelloSun/flint_cpu_opt` on HF).
4
+ Read `/root/flint_cpu_opt/README.md` (model card) for the public numbers. This file is for continuing the work.
5
+
6
+ ## 1. Current state
7
+
8
+ | item | state |
9
+ |---|---|
10
+ | llama.cpp source | `/root/flint_cpu_opt/llama.cpp`, base `de5122d879d00d2895c36be7e0cee94105aeeabd`, working tree = base + `patches/flint-cpu-opt.patch` (verified: `git diff` == patch file) |
11
+ | patch | Q4_1 kernels (vec_dot + tiled), IQ4_XS, Q4_K/Q5_K/Q6_K tweaks, **NVFP4 vec_dot + tiled (new)** |
12
+ | models | `flint-int4-q4_1.gguf` (24.2 GiB, served), `flint-nvfp4.gguf` (22.9 GB, sha256 `97cc8009...b8bde`), `flint-UD-Q4_K_XL.gguf` not in local dir |
13
+ | llama-server | running on `127.0.0.1:20000`, alias `flint-q4_1`, `-t 16 -fa on --jinja -c 131072`, PID in `/root/llama-server.pid`, log `/root/llama-server.log` |
14
+ | built binaries | `llama.cpp/build/bin/` (llama-server, llama-bench, llama-perplexity; rebuilt with final code) |
15
+ | benchmarks | NVFP4 in `benchmarks/nvfp4/` (3 interleaved rounds, medians in README). Q4_1 / Q4_K_XL in `benchmarks/*.md` (from HF, not re-run) |
16
+ | HF | `HelloSun/flint_cpu_opt`: model card + patch + handoff uploaded (see section 6) |
17
+
18
+ ## 2. NVFP4 results (median of 3 interleaved rounds, tokens/s)
19
+
20
+ | build | pp256 | pp2048 | tg128 |
21
+ |---|---|---|---|
22
+ | stock de5122d87 | 83.7 | 83.6 | 30.6 |
23
+ | patch, NVFP4 untouched | 82.3 | 84.4 | 31.8 |
24
+ | patch + NVFP4 kernels | 178.5 | 211.1 | 32.1 |
25
+
26
+ Correctness: AVX512 vec_dot vs generic rel err <= 1.3e-6; tiled vs vec_dot rel L2 ~2e-7; PPL wikitext-2 16x512: stock 24.4165, new 24.4135.
27
+
28
+ Q4_1 reference (from earlier, quieter host): pp256 266, pp2048 297, tg128 55.7. Decode of NVFP4 (32 t/s) is far below Q4_1, and NVFP4 prefill is still below Q4_1.
29
+
30
+ ## 3. Code map (NVFP4 work)
31
+
32
+ - `ggml/src/ggml-cpu/arch/x86/quants.c`: `ggml_vec_dot_nvfp4_q8_0`, first branch `#if AVX512F && AVX512VL && AVX512VNNI && AVX2` (one 64-block per iteration; `alo/ahi` built with `_mm512_permutexvar_epi64`; lane k of the int32 sum = subblock k/2).
33
+ - `ggml/src/ggml-cpu/tiled/tiled-kernel.h`: `tiled_tile_src0::sd16` (per-16 f32 scale), `tiled_unpk_fp4_64` (AVX2 + scalar).
34
+ - `ggml/src/ggml-cpu/tiled/tiled-kernel.cpp`: `tiled_run_micro_nv4_vnni_8x16` / `tiled_run_microtile_nv4_vnni`; routed from `tiled_run_microtile` when `Q41 && SUBBLK==16`; explicit instantiation `<16,false,0,false,true>` at the bottom.
35
+ - `ggml/src/ggml-cpu/tiled/tiled.cpp`: `tiled_unpack_src0(const block_nvfp4*)`, `tiled_unpack_src1(const block_q8_0*)`, NVFP4 in `ggml_tiled_supported` list and in `ggml_tiled_matmul_type_dispatch` (VNNI-only guard).
36
+ - Kernel tests: `benchmarks/nvfp4/test_nvfp4.c` (vec_dot vs generic) and `benchmarks/nvfp4/tiled_test.c` (tiled vs vec_dot vs f32 ref). Build with `gcc ... -L<build>/bin -lggml-cpu -lggml-base -Wl,-rpath,<build>/bin` and call `ggml_cpu_init()` first (the UE4M3/fp16 tables are otherwise zero, which gives a false pass).
37
+
38
+ Bias trick (important for any change): codes are `LUT+12` (0..24, unsigned), so `sum(w*a) = sum((w+12)*a) - 12*bsum16`. `bsums` is the per-16 sum of the int8 activation codes.
39
+
40
+ ## 4. How to reproduce
41
+
42
+ ```bash
43
+ cd /root/flint_cpu_opt
44
+ # rebuild (after editing llama.cpp sources)
45
+ cmake --build llama.cpp/build -j16 --target llama-bench llama-server llama-perplexity
46
+ # verify the patch file matches the tree
47
+ (cd llama.cpp && git diff) | cmp - patches/flint-cpu-opt.patch
48
+ # nvfp4 bench (copy binaries first; the script expects bin_* copies, see benchmarks/nvfp4/bench_run.sh)
49
+ taskset -c 0-15 llama.cpp/build/bin/llama-bench -m flint-nvfp4.gguf -t 16 -fa 1 -b 1024 -ub 1024 -lm mmap+mlock -p 256,2048 -n 128 -r 3 -o csv
50
+ # PPL check (needs wikitext-2 test; HF: datasets/Salesforce/wikitext wikitext-2-raw-v1)
51
+ taskset -c 0-15 llama.cpp/build/bin/llama-perplexity -m flint-nvfp4.gguf -f wt2_test.txt -c 512 --chunks 16 -t 16 -fa 1 -b 512 -ub 512
52
+ ```
53
+
54
+ Stock baseline: `git archive de5122d879d00d2895c36be7e0cee94105aeeabd | tar -x -C <dir>` and build with `cmake -B build -DGGML_NATIVE=ON -DGGML_CUDA=OFF -DLLAMA_CURL=OFF`.
55
+
56
+ Restart server (same args as running server):
57
+ ```bash
58
+ setsid nohup llama.cpp/build/bin/llama-server -m /root/flint_cpu_opt/flint-int4-q4_1.gguf --alias flint-q4_1 -t 16 -fa on --jinja -c 131072 --host 127.0.0.1 --port 20000 > /root/llama-server.log 2>&1 < /dev/null &
59
+ ```
60
+ Note: `build.sh` defaults to `CONTEXT_SIZE=32768`; the running server uses 131072.
61
+
62
+ ## 5. Protocol and gotchas
63
+
64
+ - The host is shared. Load average was 55-63 during the NVFP4 runs; decode single runs swung from 14 to 32 t/s. Use interleaved rounds (A,B,C,A,B,C,...) and medians. Differences under ~8 % are noise.
65
+ - Stop `llama-server` (port 20000) before benchmarks (it uses ~25 GB RSS and CPUs); restart afterwards. The server serves the user's pi agent.
66
+ - Memory guard: processes you start must stay below 80 GiB total; use `mmap+mlock` on the 22 GB file, do not load it twice in parallel.
67
+ - Downloads: HF single-stream curl was about 7-19 MB/s. 8 parallel `-r` range requests were much faster (about 15 GB in 2 min). Use `setsid nohup` for background jobs; a plain `&` in the tool shell hung the shell.
68
+ - Do not `pkill -f <pattern>` with a pattern that appears in your own command line (it killed the shell once).
69
+ - The GGML_PROF profiler patch (`optional-profiler.patch`) changes `ggml-cpu.c`; apply it only temporarily, build to a copy directory, then `git apply -R`.
70
+ - The HF token was pasted in chat. Rotate it when convenient. Never write it into files; use `HF_TOKEN` env.
71
+ - `git diff` of the llama.cpp tree is the source of truth for the patch. Regenerate with `git diff > patches/flint-cpu-opt.patch` after every kernel change, and check it applies to `BASE_COMMIT` (`git archive` into a temp dir + `git apply --check`).
72
+
73
+ ## 6. HF upload
74
+
75
+ Uploaded to `HelloSun/flint_cpu_opt`: `README.md` (model card, integrated with NVFP4), `patches/flint-cpu-opt.patch` (updated), `benchmarks/nvfp4/*`, `handoff/*`. The 22.9 GB NVFP4 GGUF is NOT in this repo (link to RecursiveSI). Old Q4_K_XL / Q4_1 files and benchmarks are unchanged.
76
+
77
+ ## 7. Open items / next steps (priority order)
78
+
79
+ 1. **NVFP4 decode is not improved** (32 t/s, ~1.0x). The vec_dot per 64 block has about 25 ops and the tiled path is not used at batch 1. Next: microbenchmark `ggml_vec_dot_nvfp4_q8_0` on a cold MoE shape (`mul_mat_id` K=2048, 256 experts, 6 active) and check memory-bandwidth share (the Q4_1 patch got +15 % this way with prefetch, `GGML_Q41_PF`). Try the same prefetch knob for NVFP4.
80
+ 2. **NVFP4 prefill is still ~30 % below Q4_1** (pp2048 211 vs 297, different host state). Profile `MUL_MAT_ID` with `GGML_PROF=1` (apply the profiler patch temporarily). Check unpack cost (`tiled_unpk_fp4_64` is per 64 elements) and the per-16 accumulation (2 int-sums per 32 block).
81
+ 3. Accuracy: run KLD for NVFP4 patched vs stock (save baseline logits with llama-perplexity, then compare; check `llama-perplexity --help` for the KL flags, and follow the method in `q41-kld.log`). Only PPL was measured.
82
+ 4. Re-run Q4_1 / Q4_K_XL only if asked. They were not re-run in this round.
83
+ 5. Non-VNNI x86 (AVX2 only): NVFP4 tiled is intentionally disabled there; the AVX2 vec_dot path is unchanged. Not tested on AVX2-only hardware.
84
+ 6. Optional: update `build.sh` default `CONTEXT_SIZE` note or leave as is.
85
+
86
+ ## 8. Where things are
87
+
88
+ - Work tree: `/root/flint_cpu_opt/llama.cpp`
89
+ - Model card: `/root/flint_cpu_opt/README.md`; handoff: `/root/flint_cpu_opt/handoff/HANDOFF.md`
90
+ - NVFP4 benchmarks + tests: `/root/flint_cpu_opt/benchmarks/nvfp4/`
91
+ - Temporary binaries and downloads: `/tmp/opencode/` (bin_stock, bin_patched_before, bin_ppl_new, nvfp4_test, bench). These are not persistent; rebuild if needed.
92
+ - Earlier handoff (Q4_1 only): on HF `HANDOFF.md`, kept for history.
patches/flint-cpu-opt.patch CHANGED
@@ -172,7 +172,7 @@ index 9f3a744b5..d23e4d971 100644
172
 
173
  parallel_for_ggml(params, n_batch * NB, [&](int begin, int end) {
174
  diff --git a/ggml/src/ggml-cpu/arch/x86/quants.c b/ggml/src/ggml-cpu/arch/x86/quants.c
175
- index 21cd6312a..07d4feb37 100644
176
  --- a/ggml/src/ggml-cpu/arch/x86/quants.c
177
  +++ b/ggml/src/ggml-cpu/arch/x86/quants.c
178
  @@ -941,6 +941,49 @@ void ggml_vec_dot_q4_1_q8_1(int n, float * GGML_RESTRICT s, size_t bs, const voi
@@ -234,7 +234,52 @@ index 21cd6312a..07d4feb37 100644
234
  #else
235
  UNUSED(nb);
236
  UNUSED(x);
237
- @@ -2118,7 +2161,89 @@ void ggml_vec_dot_q4_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
 
239
  uint32_t utmp[4];
240
 
@@ -325,7 +370,7 @@ index 21cd6312a..07d4feb37 100644
325
 
326
  const __m256i m4 = _mm256_set1_epi8(0xF);
327
 
328
- @@ -2296,7 +2421,104 @@ void ggml_vec_dot_q5_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
329
 
330
  uint32_t utmp[4];
331
 
@@ -431,7 +476,7 @@ index 21cd6312a..07d4feb37 100644
431
 
432
  const __m256i m4 = _mm256_set1_epi8(0xF);
433
  const __m128i mzero = _mm_setzero_si128();
434
- @@ -2500,7 +2722,81 @@ void ggml_vec_dot_q6_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
435
 
436
  const int nb = n / QK_K;
437
 
@@ -514,7 +559,7 @@ index 21cd6312a..07d4feb37 100644
514
 
515
  const __m256i m3 = _mm256_set1_epi8(3);
516
  const __m256i m15 = _mm256_set1_epi8(15);
517
- @@ -4078,7 +4374,75 @@ void ggml_vec_dot_iq4_xs_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const v
518
 
519
  const int nb = n / QK_K;
520
 
@@ -758,10 +803,10 @@ index 9282ca299..0431b7ada 100644
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
 
@@ -838,11 +883,78 @@ index 5819ac07c..4d4eaae6f 100644
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
 
@@ -898,6 +1010,15 @@ index 5819ac07c..4d4eaae6f 100644
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 {
@@ -924,7 +1045,7 @@ index 5819ac07c..4d4eaae6f 100644
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
@@ -932,21 +1053,24 @@ index 5819ac07c..4d4eaae6f 100644
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
@@ -960,10 +1084,12 @@ index 1a97f406a..4bd9642cc 100644
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];
@@ -972,10 +1098,24 @@ index 1a97f406a..4bd9642cc 100644
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);
@@ -985,17 +1125,25 @@ index 1a97f406a..4bd9642cc 100644
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)
@@ -1007,10 +1155,10 @@ index 1a97f406a..4bd9642cc 100644
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
 
@@ -1035,6 +1183,30 @@ index 77e1a9945..225ec0e78 100644
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
@@ -1046,7 +1218,7 @@ index 77e1a9945..225ec0e78 100644
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
 
@@ -1082,19 +1254,52 @@ index 77e1a9945..225ec0e78 100644
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,7 +1310,7 @@ index 77e1a9945..225ec0e78 100644
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
@@ -1119,7 +1324,7 @@ index 77e1a9945..225ec0e78 100644
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;
@@ -1128,7 +1333,7 @@ index 77e1a9945..225ec0e78 100644
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
@@ -1151,7 +1356,7 @@ index 77e1a9945..225ec0e78 100644
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);
@@ -1170,7 +1375,7 @@ index 77e1a9945..225ec0e78 100644
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
@@ -1179,7 +1384,7 @@ index 77e1a9945..225ec0e78 100644
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
 
@@ -1206,7 +1411,7 @@ index 77e1a9945..225ec0e78 100644
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;
@@ -1215,7 +1420,7 @@ index 77e1a9945..225ec0e78 100644
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
@@ -1227,7 +1432,7 @@ index 77e1a9945..225ec0e78 100644
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
@@ -1250,7 +1455,7 @@ index 77e1a9945..225ec0e78 100644
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);
@@ -1269,7 +1474,7 @@ index 77e1a9945..225ec0e78 100644
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
 
@@ -1278,7 +1483,7 @@ index 77e1a9945..225ec0e78 100644
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
 
@@ -1287,7 +1492,7 @@ index 77e1a9945..225ec0e78 100644
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
@@ -1296,7 +1501,7 @@ index 77e1a9945..225ec0e78 100644
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) {
@@ -1308,12 +1513,16 @@ index 77e1a9945..225ec0e78 100644
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:
 
172
 
173
  parallel_for_ggml(params, n_batch * NB, [&](int begin, int end) {
174
  diff --git a/ggml/src/ggml-cpu/arch/x86/quants.c b/ggml/src/ggml-cpu/arch/x86/quants.c
175
+ index 21cd6312a..37efb7fae 100644
176
  --- a/ggml/src/ggml-cpu/arch/x86/quants.c
177
  +++ b/ggml/src/ggml-cpu/arch/x86/quants.c
178
  @@ -941,6 +941,49 @@ void ggml_vec_dot_q4_1_q8_1(int n, float * GGML_RESTRICT s, size_t bs, const voi
 
234
  #else
235
  UNUSED(nb);
236
  UNUSED(x);
237
+ @@ -1080,7 +1123,43 @@ void ggml_vec_dot_nvfp4_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const vo
238
+ int ib = 0;
239
+ float sumf = 0;
240
+
241
+ -#if defined(__AVX2__)
242
+ +#if defined(__AVX512F__) && defined(__AVX512VL__) && defined(__AVX512VNNI__) && defined(__AVX2__)
243
+ + // AVX512-VNNI path: one 64-element block per iteration. |w| (0..12) is the unsigned vpdpbusd operand and the
244
+ + // sign of w moves onto the int8 activations. Int32 lane k sums sub-block k/2 (lo + hi nibbles of 4 bytes).
245
+ + {
246
+ + const __m256i lut = _mm256_broadcastsi128_si256(_mm_loadu_si128((const __m128i *) kvalues_fp4));
247
+ + const __m256i m4b = _mm256_set1_epi8(0x0f);
248
+ + const __m512i perm_lo = _mm512_setr_epi64(0, 2, 4, 6, 0, 0, 0, 0);
249
+ + const __m512i perm_hi = _mm512_setr_epi64(1, 3, 5, 7, 0, 0, 0, 0);
250
+ + __m256 acc = _mm256_setzero_ps();
251
+ + for (; ib < nb; ++ib) {
252
+ + const __m256i qbits = _mm256_loadu_si256((const __m256i *) x[ib].qs);
253
+ + const __m256i wlo = _mm256_shuffle_epi8(lut, _mm256_and_si256(qbits, m4b));
254
+ + const __m256i whi = _mm256_shuffle_epi8(lut, _mm256_and_si256(_mm256_srli_epi16(qbits, 4), m4b));
255
+ +
256
+ + // activation bytes in weight byte order: lo -> elems 16s+j, hi -> elems 16s+8+j
257
+ + const __m512i z = _mm512_inserti64x4(_mm512_castsi256_si512(_mm256_loadu_si256((const __m256i *) y[2*ib + 0].qs)),
258
+ + _mm256_loadu_si256((const __m256i *) y[2*ib + 1].qs), 1);
259
+ + const __m256i alo = _mm512_castsi512_si256(_mm512_permutexvar_epi64(perm_lo, z));
260
+ + const __m256i ahi = _mm512_castsi512_si256(_mm512_permutexvar_epi64(perm_hi, z));
261
+ +
262
+ + const __m256i dot = _mm256_dpbusd_epi32(_mm256_dpbusd_epi32(_mm256_setzero_si256(),
263
+ + _mm256_abs_epi8(wlo), _mm256_sign_epi8(alo, wlo)),
264
+ + _mm256_abs_epi8(whi), _mm256_sign_epi8(ahi, whi));
265
+ +
266
+ + const float dy0 = GGML_CPU_FP16_TO_FP32(y[2*ib + 0].d);
267
+ + const float dy1 = GGML_CPU_FP16_TO_FP32(y[2*ib + 1].d);
268
+ + const float s0 = GGML_CPU_UE4M3_TO_FP32(x[ib].d[0]) * dy0;
269
+ + const float s1 = GGML_CPU_UE4M3_TO_FP32(x[ib].d[1]) * dy0;
270
+ + const float s2 = GGML_CPU_UE4M3_TO_FP32(x[ib].d[2]) * dy1;
271
+ + const float s3 = GGML_CPU_UE4M3_TO_FP32(x[ib].d[3]) * dy1;
272
+ + const __m256 scales = _mm256_set_ps(s3, s3, s2, s2, s1, s1, s0, s0);
273
+ +
274
+ + acc = _mm256_fmadd_ps(_mm256_cvtepi32_ps(dot), scales, acc);
275
+ + }
276
+ + sumf = hsum_float_8(acc);
277
+ + }
278
+ +#elif defined(__AVX2__)
279
+
280
+ const __m128i values128 = _mm_loadu_si128((const __m128i*)kvalues_fp4);
281
+ const __m128i m4b = _mm_set1_epi8(0x0f);
282
+ @@ -2118,7 +2197,89 @@ void ggml_vec_dot_q4_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
283
 
284
  uint32_t utmp[4];
285
 
 
370
 
371
  const __m256i m4 = _mm256_set1_epi8(0xF);
372
 
373
+ @@ -2296,7 +2457,104 @@ void ggml_vec_dot_q5_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
374
 
375
  uint32_t utmp[4];
376
 
 
476
 
477
  const __m256i m4 = _mm256_set1_epi8(0xF);
478
  const __m128i mzero = _mm_setzero_si128();
479
+ @@ -2500,7 +2758,81 @@ void ggml_vec_dot_q6_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
480
 
481
  const int nb = n / QK_K;
482
 
 
559
 
560
  const __m256i m3 = _mm256_set1_epi8(3);
561
  const __m256i m15 = _mm256_set1_epi8(15);
562
+ @@ -4078,7 +4410,75 @@ void ggml_vec_dot_iq4_xs_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const v
563
 
564
  const int nb = n / QK_K;
565
 
 
803
  return &q4_K_8x8_q8_K;
804
  }
805
  diff --git a/ggml/src/ggml-cpu/tiled/tiled-kernel.cpp b/ggml/src/ggml-cpu/tiled/tiled-kernel.cpp
806
+ index 5819ac07c..b28255b79 100644
807
  --- a/ggml/src/ggml-cpu/tiled/tiled-kernel.cpp
808
  +++ b/ggml/src/ggml-cpu/tiled/tiled-kernel.cpp
809
+ @@ -204,6 +204,147 @@ static void tiled_run_microtile_vnni(const tiled_tile_src0 & src0, const tiled_t
810
  tiled_run_micro_vnni_8x16<SUBBLK, HAS_MIN, BIAS, NK>(src0, src1, i0 + 8, j0, num_k, slab, buf, buf_stride);
811
  }
812
 
 
883
  + tiled_run_micro_q41_vnni_8x16<NK>(src0, src1, i0, j0, num_k, slab, buf, buf_stride);
884
  + tiled_run_micro_q41_vnni_8x16<NK>(src0, src1, i0 + 8, j0, num_k, slab, buf, buf_stride);
885
  +}
886
+ +
887
+ +// nvfp4: 16 subblocks of 16 per 256-slab, each with its own f32 weight scale (sd16); q8_0 activations carry one
888
+ +// f32 scale per 32-block (sd). Codes are LUT + 12 (unsigned), so the bias is removed per subblock with the
889
+ +// int32 code sums (bsums): sum(w*a) = sum((w+12)*a) - 12*sum(a). Integer sums are exact.
890
+ +template <int NK>
891
+ +static void tiled_run_micro_nv4_vnni_8x16(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1,
892
+ + int i0, int j0, int num_k, int slab, float * buf, int buf_stride) {
893
+ + constexpr int NB32 = TILED_TILE_K / 32; // 8 activation blocks
894
+ + constexpr int NB16 = TILED_TILE_K / 16; // 16 weight subblocks
895
+ + constexpr int NG = 4; // 4-element groups per 16-subblock
896
+ + constexpr int NUM_ROWS = 8;
897
+ +
898
+ + const int nkr = (NK > 0) ? NK : num_k;
899
+ + const int seff = (NK > 0) ? 0 : slab;
900
+ + const int qk_stride = nkr * TILED_TILE_K;
901
+ + const int qk_off = seff * TILED_TILE_K;
902
+ + const int nb_stride = NB16 * nkr;
903
+ + const int nb_off = seff * NB16;
904
+ + const int bs_stride = (nkr == 1) ? TILED_TILE_ROWS : TILED_MICRO;
905
+ +
906
+ + __m512 facc[NUM_ROWS];
907
+ + for (int t = 0; t < NUM_ROWS; t++) { facc[t] = _mm512_setzero_ps(); }
908
+ +
909
+ + const int32_t * bsums = src1.bsums;
910
+ + const __m512i twelve = _mm512_set1_epi32(12);
911
+ + for (int sb = 0; sb < NB16; sb++) {
912
+ + const __m512i bsum12 = _mm512_mullo_epi32(twelve,
913
+ + _mm512_load_si512((const __m512i *) &bsums[(seff * TILED_MICRO + sb) * bs_stride + j0]));
914
+ +
915
+ + __m512i acc16[NUM_ROWS];
916
+ + for (int t = 0; t < NUM_ROWS; t++) { acc16[t] = _mm512_setzero_si512(); }
917
+ +
918
+ +#ifdef __GNUC__
919
+ + #pragma GCC unroll 4
920
+ +#endif
921
+ + for (int g = 0; g < NG; g++) {
922
+ + const int kg = sb * NG + g;
923
+ + const __m512i codes = _mm512_load_si512((const __m512i *) &src1.q[(j0 / TILED_MICRO) * (TILED_MICRO * qk_stride)
924
+ + + qk_off + (kg % TILED_MICRO) * qk_stride + (kg / TILED_MICRO) * (TILED_MICRO * 4)]);
925
+ + for (int t = 0; t < NUM_ROWS; t++) {
926
+ + const uint32_t u4 = *(const uint32_t *) &src0.q[(i0 + t) * qk_stride + qk_off + kg * 4];
927
+ + const __m512i u4b = _mm512_set1_epi32((int) u4);
928
+ + acc16[t] = _mm512_dpbusd_epi32(acc16[t], u4b, codes);
929
+ + }
930
+ + }
931
+ +
932
+ + const __m512 dav = _mm512_loadu_ps(&src1.sd[(seff * NB32 + sb / 2) * bs_stride + j0]);
933
+ + for (int t = 0; t < NUM_ROWS; t++) {
934
+ + const int ar = i0 + t;
935
+ + const float dw = src0.sd16[ar * nb_stride + nb_off + sb];
936
+ + const __m512 raw = _mm512_cvtepi32_ps(_mm512_sub_epi32(acc16[t], bsum12));
937
+ + facc[t] = _mm512_fmadd_ps(raw, _mm512_mul_ps(_mm512_set1_ps(dw), dav), facc[t]);
938
+ + }
939
+ + }
940
+ +
941
+ + for (int t = 0; t < NUM_ROWS; t++) {
942
+ + float * p = &buf[(i0 + t) * buf_stride + j0];
943
+ + _mm512_store_ps(p, _mm512_add_ps(_mm512_load_ps(p), facc[t]));
944
+ + }
945
+ +}
946
+ +
947
+ +template <int NK>
948
+ +static void tiled_run_microtile_nv4_vnni(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1,
949
+ + int i0, int j0, int num_k, int slab, float * buf, int buf_stride) {
950
+ + tiled_run_micro_nv4_vnni_8x16<NK>(src0, src1, i0, j0, num_k, slab, buf, buf_stride);
951
+ + tiled_run_micro_nv4_vnni_8x16<NK>(src0, src1, i0 + 8, j0, num_k, slab, buf, buf_stride);
952
+ +}
953
  +
954
  #endif // __AVX512VNNI__ && __AVX512VL__
955
 
956
  #if defined(__AVX2__)
957
+ @@ -496,23 +637,88 @@ static void tiled_run_microtile_avx(const tiled_tile_src0 & src0, const tiled_ti
958
 
959
  #endif // __AVX__ && !__AVX2__
960
 
 
1010
  const bool standard = (num_k == 1);
1011
  #if defined(__AVX512VNNI__) && defined(__AVX512VL__) && defined(__AVX512DQ__)
1012
  + if constexpr (Q41) {
1013
+ + if constexpr (SUBBLK == 16) {
1014
+ + // nvfp4 (per-16 weight scales), see tiled_run_micro_nv4_vnni_8x16
1015
+ + if (standard) {
1016
+ + tiled_run_microtile_nv4_vnni<1>(src0, src1, i0, j0, num_k, slab, buf, buf_stride);
1017
+ + } else {
1018
+ + tiled_run_microtile_nv4_vnni<0>(src0, src1, i0, j0, num_k, slab, buf, buf_stride);
1019
+ + }
1020
+ + return;
1021
+ + }
1022
  + if (standard) {
1023
  + tiled_run_microtile_q41_vnni<1>(src0, src1, i0, j0, num_k, slab, buf, buf_stride);
1024
  + } else {
 
1045
  if (standard) {
1046
  tiled_run_microtile_avx2<SUBBLK, HAS_MIN, BIAS, 1, ACTBIAS>(src0, src1, i0, j0, num_k, slab, buf, buf_stride);
1047
  } else {
1048
+ @@ -531,6 +737,7 @@ void tiled_run_microtile(const tiled_tile_src0 & src0, const tiled_tile_src1 & s
1049
  tiled_run_microtile_scalar<SUBBLK, HAS_MIN, BIAS, 0>(src0, src1, i0, j0, num_k, slab, buf, buf_stride);
1050
  }
1051
  #endif
 
1053
  }
1054
 
1055
  // explicit instantiations for the in-use formats (q4_K and q5_K share the constants)
1056
+ @@ -556,6 +763,12 @@ template void tiled_run_microtile<16, false, 4, true>(const tiled_tile_src0 & sr
1057
  // q2_K: BIAS = 0
1058
  template void tiled_run_microtile<16, true, 0, false>(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1,
1059
  int i0, int j0, int num_k, int slab, float * buf, int buf_stride);
1060
  +// q4_1: per-32-block f32 scales, q8_1 activations
1061
  +template void tiled_run_microtile<32, false, 0, false, true>(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1,
1062
+ + int i0, int j0, int num_k, int slab, float * buf, int buf_stride);
1063
+ +// nvfp4: per-16-subblock f32 scales, q8_0 activations
1064
+ +template void tiled_run_microtile<16, false, 0, false, true>(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1,
1065
  + int i0, int j0, int num_k, int slab, float * buf, int buf_stride);
1066
 
1067
 
1068
  #define MIN(a, b) ((a) < (b) ? (a) : (b))
1069
  diff --git a/ggml/src/ggml-cpu/tiled/tiled-kernel.h b/ggml/src/ggml-cpu/tiled/tiled-kernel.h
1070
+ index 1a97f406a..c5d081420 100644
1071
  --- a/ggml/src/ggml-cpu/tiled/tiled-kernel.h
1072
  +++ b/ggml/src/ggml-cpu/tiled/tiled-kernel.h
1073
+ @@ -29,12 +29,18 @@
1074
  // SUBBLK=32 formats index at stride 8 and leave the slack unused.
1075
  struct tiled_tile_src0 {
1076
  static constexpr int NB_MAX = TILED_TILE_K / 16; // max subblocks per 256-elem block
 
1084
  + // q4_1: no super-block scale, each 32-wide block carries its own f32 scale/min
1085
  + float sd[TILED_TILE_ROWS * NB32]; // per-32-block scale
1086
  + float sm[TILED_TILE_ROWS * NB32]; // per-32-block min
1087
+ + // nvfp4: one f32 scale per 16-wide subblock (UE4M3 decoded), codes are LUT + 12 (0..24)
1088
+ + float sd16[TILED_TILE_ROWS * NB_MAX];
1089
  };
1090
 
1091
  // src1 tile: built from q8_K (wdata)
1092
+ @@ -45,6 +51,8 @@ struct tiled_tile_src1 {
1093
  alignas(64) int32_t bsums[(TILED_TILE_K / 16) * TILED_TILE_ROWS];
1094
  // f32 (not f16): q8_k stores fp16, the unpack converts once
1095
  float d[TILED_TILE_ROWS];
 
1098
  };
1099
 
1100
  // per-thread workspace: all tiled state lives here, allocated in wdata (one slot per thread)
1101
+ @@ -71,6 +79,26 @@ inline void tiled_unpk_nib4(const uint8_t * src, uint8_t * lo, uint8_t * hi) {
1102
  _mm256_storeu_si256((__m256i *) lo, _mm256_and_si256(v, _mm256_set1_epi8(0x0F)));
1103
  _mm256_storeu_si256((__m256i *) hi, _mm256_srli_epi32(_mm256_and_si256(v, _mm256_set1_epi8((int8_t) 0xF0)), 4));
1104
  }
1105
+ +// nvfp4: 32 packed bytes (one 64-element block) -> 64 codes, LUT(kvalues_fp4) + 12 in 0..24.
1106
+ +// byte B = 8s+j: lo -> elem 16s+j, hi -> elem 16s+8+j
1107
+ +inline void tiled_unpk_fp4_64(const uint8_t * src, uint8_t * dst) {
1108
+ + const __m256i lut = _mm256_setr_epi8(12, 13, 14, 15, 16, 18, 20, 24, 12, 11, 10, 9, 8, 6, 4, 0,
1109
+ + 12, 13, 14, 15, 16, 18, 20, 24, 12, 11, 10, 9, 8, 6, 4, 0);
1110
+ + const __m256i v = _mm256_loadu_si256((const __m256i *) src);
1111
+ + const __m256i lo = _mm256_shuffle_epi8(lut, _mm256_and_si256(v, _mm256_set1_epi8(0x0F)));
1112
+ + const __m256i hi = _mm256_shuffle_epi8(lut, _mm256_and_si256(_mm256_srli_epi16(v, 4), _mm256_set1_epi8(0x0F)));
1113
+ + // x = [s0 | s2], y = [s1 | s3] (128-bit halves)
1114
+ + const __m256i x = _mm256_unpacklo_epi64(lo, hi);
1115
+ + const __m256i y = _mm256_unpackhi_epi64(lo, hi);
1116
+ + _mm256_storeu_si256((__m256i *) dst, _mm256_permute2x128_si256(x, y, 0x20));
1117
+ + _mm256_storeu_si256((__m256i *) (dst + 32), _mm256_permute2x128_si256(x, y, 0x31));
1118
+ +}
1119
  +// 16-byte source (one q4_1 block: lo -> elems 0..15, hi -> elems 16..31)
1120
  +inline void tiled_unpk_nib4_16(const uint8_t * src, uint8_t * lo, uint8_t * hi) {
1121
  + const __m128i v = _mm_loadu_si128((const __m128i *) src);
 
1125
  // 2-bit values at bit offset S
1126
  template <int S> inline void tiled_unpk_2bit(const uint8_t * src, uint8_t * dst) {
1127
  _mm256_storeu_si256((__m256i *) dst, _mm256_and_si256(
1128
+ @@ -129,6 +157,17 @@ inline void tiled_unpk_tern8(const uint8_t * src, int8_t delta, uint8_t * dst) {
1129
  inline void tiled_unpk_nib4(const uint8_t * src, uint8_t * lo, uint8_t * hi) {
1130
  for (int l = 0; l < 32; l++) { lo[l] = (uint8_t) (src[l] & 0xF); hi[l] = (uint8_t) (src[l] >> 4); }
1131
  }
1132
+ +inline void tiled_unpk_fp4_64(const uint8_t * src, uint8_t * dst) {
1133
+ + static const uint8_t lut[16] = { 12, 13, 14, 15, 16, 18, 20, 24, 12, 11, 10, 9, 8, 6, 4, 0 };
1134
+ + for (int B = 0; B < 32; B++) {
1135
+ + const int s = B / 8, j = B % 8;
1136
+ + dst[16 * s + j] = lut[src[B] & 0xF];
1137
+ + dst[16 * s + 8 + j] = lut[src[B] >> 4];
1138
+ + }
1139
+ +}
1140
  +inline void tiled_unpk_nib4_16(const uint8_t * src, uint8_t * lo, uint8_t * hi) {
1141
  + for (int l = 0; l < 16; l++) { lo[l] = (uint8_t) (src[l] & 0xF); hi[l] = (uint8_t) (src[l] >> 4); }
1142
  +}
1143
  template <int S>
1144
  inline void tiled_unpk_2bit(const uint8_t * src, uint8_t * dst) {
1145
  for (int l = 0; l < 32; l++) { dst[l] = (uint8_t) ((src[l] >> S) & 3); }
1146
+ @@ -164,7 +203,9 @@ inline void tiled_unpk_tern8(const uint8_t * src, int8_t delta, uint8_t * dst) {
1147
  // ACTBIAS (AVX2 only): the activation is pre-biased +128 by tiled_repack_src1,
1148
  // num_k = K-blocks per row: the tile holds num_k slabs at row stride num_k*256
1149
  // (Default case is num_k=1, 256x256 tiles, we go to longer num_k to improve memory bandwidth when num_rows is small)
 
1155
  int i0, int j0, int num_k, int slab, float * buf, int buf_stride);
1156
 
1157
  diff --git a/ggml/src/ggml-cpu/tiled/tiled.cpp b/ggml/src/ggml-cpu/tiled/tiled.cpp
1158
+ index 77e1a9945..96640b50d 100644
1159
  --- a/ggml/src/ggml-cpu/tiled/tiled.cpp
1160
  +++ b/ggml/src/ggml-cpu/tiled/tiled.cpp
1161
+ @@ -534,11 +534,57 @@ static void tiled_unpack_src0(const block_iq1_m * rows, int64_t row_stride, int
1162
  }
1163
  }
1164
 
 
1183
  + }
1184
  + }
1185
  +}
1186
+ +
1187
+ +// nvfp4: 4 x 64-element blocks per 256-slab, each block 4 UE4M3 subblock scales (16 elems each) + 32 nibble bytes.
1188
+ +// Scales go to sd16 (one per 16-wide subblock); codes are LUT + 12 (see tiled_unpk_fp4_64).
1189
+ +static void tiled_unpack_src0(const block_nvfp4 * rows, int64_t row_stride, int n_rows, tiled_tile_src0 * tile, int num_k) {
1190
+ + GGML_ASSERT(n_rows <= TILED_TILE_ROWS);
1191
+ + constexpr int NBLK = TILED_TILE_K / 64; // 4 nvfp4 blocks per 256-slab
1192
+ + constexpr int NB16 = TILED_TILE_K / 16; // 16 subblocks per 256-slab
1193
+ +
1194
+ + const int qk_stride = num_k * TILED_TILE_K;
1195
+ + const int nb_stride = NB16 * num_k;
1196
+ + for (int slab = 0; slab < num_k; slab++) {
1197
+ + for (int r = 0; r < n_rows; r++) {
1198
+ + const int q_off = r * qk_stride + slab * TILED_TILE_K;
1199
+ + const int s_off = r * nb_stride + slab * NB16;
1200
+ + const block_nvfp4 * x = rows + r * row_stride + slab * NBLK;
1201
+ + for (int b = 0; b < NBLK; b++) {
1202
+ + for (int s = 0; s < 4; s++) {
1203
+ + tile->sd16[s_off + 4 * b + s] = ggml_ue4m3_to_fp32(x[b].d[s]);
1204
+ + }
1205
+ + tiled_unpk_fp4_64(x[b].qs, &tile->q[q_off + b * 64]);
1206
+ + }
1207
+ + }
1208
+ + }
1209
+ +}
1210
  +
1211
  // unpack src1 tile from q8_K rows. num_k = K-blocks per row: each row decodes num_k consecutive
1212
  // source blocks (rows[r][kblk + slab]) into the tile at row stride num_k*256, so each activation
 
1218
  GGML_ASSERT(n_rows <= TILED_TILE_ROWS);
1219
  const int n_padded = (n_rows + TILED_MICRO - 1) & ~(TILED_MICRO - 1);
1220
  const int bs_stride = (num_k == 1) ? TILED_TILE_ROWS : TILED_MICRO;
1221
+ @@ -569,6 +615,71 @@ static void tiled_unpack_src1_q8_K(const block_q8_K * const * rows, int n_rows,
1222
  }
1223
  }
1224
 
 
1254
  + }
1255
  + }
1256
  +}
1257
+ +
1258
+ +// nvfp4 src1: q8_0 activation rows (one f32 scale per 32-wide block, per-16 sums recomputed from the codes).
1259
+ +static void tiled_unpack_src1(const block_q8_0 * const * rows, int n_rows, tiled_tile_src1 * tile,
1260
+ + int kblk, int num_k) {
1261
+ + GGML_ASSERT(n_rows <= TILED_TILE_ROWS);
1262
+ + const int n_padded = (n_rows + TILED_MICRO - 1) & ~(TILED_MICRO - 1);
1263
+ + const int bs_stride = (num_k == 1) ? TILED_TILE_ROWS : TILED_MICRO;
1264
+ + constexpr int NBLK = TILED_TILE_K / 32; // 8 q8_0 blocks per 256-slab
1265
+ + constexpr int n16 = TILED_TILE_K / TILED_MICRO;
1266
+ + for (int slab = 0; slab < num_k; slab++) {
1267
+ + const int q_off = slab * TILED_TILE_K;
1268
+ + for (int r = 0; r < n_padded; r++) {
1269
+ + if (r < n_rows) {
1270
+ + const block_q8_0 * xb = rows[r] + (kblk + slab) * NBLK;
1271
+ + for (int b = 0; b < NBLK; b++) {
1272
+ + memcpy(&tile->q[r * (num_k * TILED_TILE_K) + q_off + b * 32], xb[b].qs, 32);
1273
+ + int32_t s0 = 0, s1 = 0;
1274
+ + for (int e = 0; e < 16; e++) { s0 += xb[b].qs[e]; s1 += xb[b].qs[16 + e]; }
1275
+ + tile->bsums[(slab * n16 + 2 * b + 0) * bs_stride + r] = s0;
1276
+ + tile->bsums[(slab * n16 + 2 * b + 1) * bs_stride + r] = s1;
1277
+ + tile->sd[(slab * NBLK + b) * bs_stride + r] = ggml_fp16_to_fp32(xb[b].d);
1278
+ + }
1279
+ + tile->d[slab * TILED_MICRO + r] = 1.0f; // per-32 scales are applied in the kernel
1280
+ + } else {
1281
+ + memset(&tile->q[r * (num_k * TILED_TILE_K) + q_off], 0, TILED_TILE_K);
1282
+ + for (int i = 0; i < n16; i++) { tile->bsums[(slab * n16 + i) * bs_stride + r] = 0; }
1283
+ + for (int b = 0; b < NBLK; b++) { tile->sd[(slab * NBLK + b) * bs_stride + r] = 0.0f; }
1284
+ + tile->d[slab * TILED_MICRO + r] = 1.0f;
1285
+ + }
1286
+ + }
1287
+ + }
1288
+ +}
1289
  +
1290
  // GGML_CPU_TILED_MM: master switch, on by default. If off, we fast return false and normal vec_dot mul_mat resumes
1291
  static bool ggml_tiled_matmul_enabled(void) {
1292
  static bool enabled = true;
1293
+ @@ -617,6 +728,8 @@ static bool ggml_tiled_supported(const struct ggml_tensor * src0,
1294
  case GGML_TYPE_Q6_K:
1295
  case GGML_TYPE_Q5_K:
1296
  case GGML_TYPE_Q4_K:
1297
  + case GGML_TYPE_Q4_1:
1298
+ + case GGML_TYPE_NVFP4:
1299
  case GGML_TYPE_Q3_K:
1300
  case GGML_TYPE_Q2_K:
1301
  case GGML_TYPE_IQ4_XS:
1302
+ @@ -716,7 +829,9 @@ static void tiled_store_window(const float * buf, int n_src0, int n_src1, int bu
1303
  // unpack and microtile are reused unchanged from mul_mat.
1304
 
1305
  // src0 rows per g group; finer than the TILED_TILE_K kernel tile so all threads stay busy at small ne01
 
1310
 
1311
  // like tiled_store_window, but the dst columns are not contiguous: col_ptrs[j] points at the
1312
  // start of dst column j, whose rows are contiguous (dim 0, nb0 == 4 bytes).
1313
+ @@ -756,11 +871,11 @@ static void tiled_store_window_scatter(const float * buf, int n_src0, int n_src1
1314
 
1315
  // one (g, k) macrotile: zero the acc window, sweep K in 256 element slabs, scatter the result rows
1316
  // rows points at this k window's routed src1 rows, row r at its base block
 
1324
  tiled_ws * ws) {
1325
  const int64_t ne00 = src0->ne[0];
1326
  const int64_t ne01 = src0->ne[1];
1327
+ @@ -770,6 +885,8 @@ static void tiled_mmid_gemm_window(struct ggml_tensor * dst, const struct ggml_t
1328
 
1329
  const size_t src0_bs = ggml_type_size(src0->type);
1330
  const int64_t src0_stride = src0->nb[1] / src0_bs;
 
1333
 
1334
  // the window is at most TILED_MMID_GROUP x TILED_TILE_K, so zero only that region of acc
1335
  for (int64_t i = 0; i < n_src0; i++) {
1336
+ @@ -792,19 +909,19 @@ static void tiled_mmid_gemm_window(struct ggml_tensor * dst, const struct ggml_t
1337
  const int kstart = (int) (k0 / TILED_TILE_K);
1338
  // activation: one 16-row band (nrows <= 16). Unpack the whole k_extent chunk (all
1339
  // slabs) so each activation row is a long stream, then repack each slab in place
 
1356
  }
1357
  }
1358
  }
1359
+ @@ -812,15 +929,15 @@ static void tiled_mmid_gemm_window(struct ggml_tensor * dst, const struct ggml_t
1360
  // standard per-slab path: K is stepped in 256-element slabs
1361
  for (int64_t ib = 0; ib < ne00; ib += TILED_TILE_K) {
1362
  const int kblk = (int) (ib / TILED_TILE_K);
 
1375
  (int) (ir0 - r), (int) ir1, 1, 0,
1376
  ws->acc, TILED_TILE_ROWS);
1377
  }
1378
+ @@ -834,7 +951,7 @@ static void tiled_mmid_gemm_window(struct ggml_tensor * dst, const struct ggml_t
1379
  // one expert of MUL_MAT_ID: each k window's dispatched rows are pointed at by a per-window
1380
  // row pointer list and swept over the thread's row windows, the dst rows are scattered back
1381
  // per window
 
1384
  static void ggml_compute_forward_mul_mat_id_tiled_one_expert(
1385
  const struct ggml_compute_params * params,
1386
  struct ggml_tensor * dst,
1387
+ @@ -877,22 +994,22 @@ static void ggml_compute_forward_mul_mat_id_tiled_one_expert(
1388
  for (int64_t k = 0; k < cne1; k += TILED_TILE_K) {
1389
  const int64_t nrows = MIN(TILED_TILE_K, cne1 - k);
1390
 
 
1411
  static void ggml_compute_forward_mul_mat_tiled_one_chunk(
1412
  const struct ggml_compute_params * params,
1413
  struct ggml_tensor * dst,
1414
+ @@ -928,6 +1045,8 @@ static void ggml_compute_forward_mul_mat_tiled_one_chunk(
1415
 
1416
  const int64_t src0_stride = nb01 / src0_bs; // blocks between src0 rows
1417
  const int64_t src1_stride = (src1->type == vec_dot_type ? src1->nb[1] : row_size) / src1_bs;
 
1420
 
1421
  const int64_t TILE = 256;
1422
  const int64_t MICRO = 16;
1423
+ @@ -960,9 +1079,9 @@ static void ggml_compute_forward_mul_mat_tiled_one_chunk(
1424
  char * dst_col = (char *) dst->data + i12 * nb2 + i13 * nb3;
1425
 
1426
  // rows[r] is the window's row r base block; the k-slab offset is applied in the unpack
 
1432
  }
1433
 
1434
  for (int64_t iir0 = ir0_start; iir0 < ir0_end; iir0 += TILE) {
1435
+ @@ -981,19 +1100,19 @@ static void ggml_compute_forward_mul_mat_tiled_one_chunk(
1436
  const int kstart = (int) (k0 / TILED_TILE_K);
1437
  // activation: one 16-row band (n_src1 <= 16). Unpack the whole k_extent chunk
1438
  // (all slabs) so each activation row is a long stream, then repack each slab in place
 
1455
  }
1456
  }
1457
  }
1458
+ @@ -1001,15 +1120,15 @@ static void ggml_compute_forward_mul_mat_tiled_one_chunk(
1459
  // standard per-slab path: K is stepped in 256-element chunks
1460
  for (int64_t ib = 0; ib < ne00; ib += TILE) {
1461
  const int kblk = (int) (ib / TILE);
 
1474
  (int) (ir0 - iir0), (int) (ir1 - iir1), 1, 0,
1475
  ws->acc, TILED_TILE_ROWS);
1476
  }
1477
+ @@ -1024,7 +1143,7 @@ static void ggml_compute_forward_mul_mat_tiled_one_chunk(
1478
  }
1479
  }
1480
 
 
1483
  static void ggml_compute_forward_mul_mat_tiled_driver(
1484
  const struct ggml_compute_params * params,
1485
  struct ggml_tensor * dst) {
1486
+ @@ -1133,7 +1252,7 @@ static void ggml_compute_forward_mul_mat_tiled_driver(
1487
  const int64_t ir1_start = dr1 * ith1;
1488
  const int64_t ir1_end = MIN(ir1_start + dr1, nr1);
1489
 
 
1492
 
1493
  if (nth >= nchunk0 * nchunk1) {
1494
  break;
1495
+ @@ -1145,7 +1264,7 @@ static void ggml_compute_forward_mul_mat_tiled_driver(
1496
 
1497
  // src0 type dispatch, shared by the MUL_MAT and MUL_MAT_ID entries: one expert for
1498
  // MUL_MAT_ID (expert_rows != NULL), the full op for MUL_MAT
 
1501
  static bool tiled_matmul_dispatch(const struct ggml_compute_params * params,
1502
  struct ggml_tensor * dst,
1503
  const int32_t * expert_rows,
1504
+ @@ -1153,9 +1272,9 @@ static bool tiled_matmul_dispatch(const struct ggml_compute_params * params,
1505
  int64_t cne1,
1506
  char * scratch) {
1507
  if (expert_rows == NULL) {
 
1513
  }
1514
  return true;
1515
  }
1516
+ @@ -1174,6 +1293,12 @@ static bool ggml_tiled_matmul_type_dispatch(const struct ggml_compute_params * p
1517
  return tiled_matmul_dispatch<block_q5_K, 32, true, 0, false>(params, dst, expert_rows, cur_a, cne1, scratch);
1518
  case GGML_TYPE_Q4_K:
1519
  return tiled_matmul_dispatch<block_q4_K, 32, true, 0, false>(params, dst, expert_rows, cur_a, cne1, scratch);
1520
  + case GGML_TYPE_Q4_1:
1521
  + return tiled_matmul_dispatch<block_q4_1, 32, false, 0, false, block_q8_1, true>(params, dst, expert_rows, cur_a, cne1, scratch);
1522
+ +#if defined(__AVX512VNNI__) && defined(__AVX512VL__) && defined(__AVX512DQ__)
1523
+ + case GGML_TYPE_NVFP4:
1524
+ + return tiled_matmul_dispatch<block_nvfp4, 16, false, 0, false, block_q8_0, true>(params, dst, expert_rows, cur_a, cne1, scratch);
1525
+ +#endif
1526
  case GGML_TYPE_Q3_K:
1527
  return tiled_matmul_dispatch<block_q3_K, 16, false, 4, true>(params, dst, expert_rows, cur_a, cne1, scratch);
1528
  case GGML_TYPE_Q2_K: