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