Skip to content

llama : load per-layer embedding tables from a separate GGUF (--ple) - #152

Merged
dzannotti merged 4 commits into
halo-box:masterfrom
michal-zurkowski:ple-sidecar
Oct 7, 2026
Merged

dzannotti merged 4 commits into
halo-box:masterfrom
michal-zurkowski:ple-sidecar

Conversation

@michal-zurkowski

@michal-zurkowski michal-zurkowski commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Overview

Adds --ple FILE: a GGUF whose tensors replace the lookup tables of the same name in the model. The model's own copy of a replaced table is never read.

Why: Qwen3.8-Flash-Next (qwen4exp) has a hashed n-gram table, per_layer_token_embd.weight, of 160 x 320,001,536 values (102.4 GB in BF16). Most GGUF quants ship it as IQ4_NL (28.8 GB), some as Q5_0 or Q8_0, whatever the precision of the rest of the model. On Strix Halo the table stays on disk (--lazy-mode on-direct) and only the rows of each batch are read, so a better table costs disk space, not RAM - but until now the only way to use one was a rebuilt model file (hand-patched split GGUFs). With --ple one table file serves every quant of the model.

llama-server -m Qwen3.8-Flash-Next-UD-Q4_K_XL-00001-of-00004.gguf \
  --ple Qwen3.8-Flash-Next-PLE-Q8_0.gguf --load-mode dio --lazy-mode on-direct

What it does:

  • llama_model_loader::load_ple(), called from llama_model_load right after the loader is built, adds the file as one more model file and points the matching weights_map entries at it. Everything downstream resolves tensors through weights_map, so every load and lazy mode follows, including the qwen4exp on-disk row reader (llama_ple_disk takes ml.fnames[idx] and offs).
  • Checks before any weight is read:
    • every key of the file except split.* and general.* (other than general.architecture) must match the model in type and value; a file with none of them (e.g. a non-first shard of another split model) loads with a warning
    • a replaced tensor must keep its shape; only get_rows lookup tables can be replaced (a tied token_embd is refused because of its output use); a file without tensors is an error
    • a tensor the model does not have is added (a model file with the table stripped out loads); if the model does not use it, the "wrong number of tensors" error says that the extra tensors came from the PLE file
  • The replaced byte ranges of the model files are passed to llama_mmap together with the lazy ranges, so the mmap prefetch skips them.
  • common: the path is a member of common_params_model, so a draft or speculative-prefill model built from a copy of the params does not get the file; an MTP head that runs on the target weights shares the target's table.
  • Vocab-only loads skip the file. path_ple is the last member of llama_model_params.
  • llama-bench --ple <file|none> (multi-value, new ple output field).
  • gguf-py/gguf/scripts/gguf_extract_ple.py writes the table of a model (any shard) to its own GGUF with the arch's ple.* keys, as a byte copy or converted to f32/f16/bf16/q8_0/q5_x/q4_x.

Tested with qwen4exp (UD-Q4_K_XL) and gemma4 E2B. gemma3n uses the same tensor and key but was not run.

Without --ple nothing changes: outputs are byte-identical to the merge-base (below).

Commits: 4e5b6913d llama/common, c67218b4e llama-bench, 3727eee22 gguf-py, 49cd270b7 gguf-py fix for the ty type-check (writes the tables without faking an ndarray; output byte-identical).

Measurements

Device:     Ryzen AI Max+ 395 / Framework Desktop (FRANMFCP06)
Memory:     128 GB LPDDR5X-8000
Power:      STAPM 132 W, PPT fast / slow 140 W, APU PPT 100 W (ryzenadj -i); platform_profile balanced, governor powersave
BIOS:       UMA split 512 MB (VRAM), GTT 126976 MiB
Kernel:     7.2.3-1-cachyos-server, amdgpu.gttsize=126976 ttm.pages_limit=32505856 ttm.page_pool_limit=32505856 amdgpu.runpm=0
Backend:    ROCm 10.0.0 (HIP_LAUNCH_BLOCKING unset), and Vulkan RADV, Mesa 26.2.2
Build:      HIP: -DGGML_HIP=ON -DGPU_TARGETS=gfx1151 -DGGML_HIP_GRAPHS=ON -DGGML_HIP_NO_VMM=ON -DGGML_NATIVE=ON -DGGML_OPENMP=ON, clang 22.1.8,
            run from the build tree with RUNPATH set to the ROCm 10.0 lib dirs (patchelf)
            Vulkan: -DGGML_VULKAN=ON -DGGML_NATIVE=ON -DGGML_OPENMP=ON, clang 22.1.8
Baseline:   4e0d91775 (merge-base = current master), built and run in this session, separate worktree and build dirs
Change:     3ba6fad80 (the later commits change only gguf_extract_ple.py)
Model:      unsloth/Qwen3.8-Flash-Next-GGUF UD-Q4_K_XL; unsloth/gemma-4-E2B-it-GGUF UD-IQ3_XXS
Env:        GGML_HIP_ENABLE_UNIFIED_MEMORY=1 ROCBLAS_USE_HIPBLASLT=1 DEBUG_HIP_GRAPH_PM4=1 (same for baseline and change)

This is a feature, not a speedup: no compute path changes. The tables show that neither the change nor the table precision moves speed. llama-bench -p 2048 -n 128 -b 2048 -ub 2048 -r 4 -ngl 99 -fa on -ctk f16 -ctv f16 --load-mode dio --lazy-mode on-direct (ROCm also -d 0,12000); llama-bench feeds random tokens, the worst case for the table (every row is a cache miss). The three table files and the model shard that holds the embedded table were dropped from the page cache first. Run order: ROCm - baseline, change in two passes of opposite order, baseline again; Vulkan - baseline and change interleaved (baseline, change, baseline, change with all tables, baseline).

Baseline (ROCm 10, merge-base, run first and last):

| model                          |       size |     params | backend    | ngl | n_ubatch |  fa |         lm | lazy_mode  |            test |                  t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | -------: | --: | ---------: | ---------- | --------------: | -------------------: |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  |          pp2048 |      1524.72 ± 37.04 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  |           tg128 |         24.22 ± 0.04 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | pp2048 @ d12000 |      1409.75 ± 14.10 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  |  tg128 @ d12000 |         22.75 ± 0.25 |

build: 4e0d91775 (11596)
| model                          |       size |     params | backend    | ngl | n_ubatch |  fa |         lm | lazy_mode  |            test |                  t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | -------: | --: | ---------: | ---------- | --------------: | -------------------: |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  |          pp2048 |      1527.49 ± 33.98 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  |           tg128 |         24.26 ± 0.05 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | pp2048 @ d12000 |      1422.76 ± 29.17 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  |  tg128 @ d12000 |         22.84 ± 0.21 |

build: 4e0d91775 (11596)

After (ROCm 10, 3ba6fad80, two passes in opposite order):

pass 1: none -> Q8_0 -> BF16
| model                          |       size |     params | backend    | ngl | n_ubatch |  fa |         lm | lazy_mode  | ple        |            test |                  t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | -------: | --: | ---------: | ---------- | ---------- | --------------: | -------------------: |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | none       |          pp2048 |      1522.56 ± 34.22 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | none       |           tg128 |         24.22 ± 0.04 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | none       | pp2048 @ d12000 |      1419.19 ± 30.13 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | none       |  tg128 @ d12000 |         22.80 ± 0.27 |
| qwen4exp A3B Q4_K - Medium     | 127.52 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-Q8_0.gguf |          pp2048 |      1520.56 ± 35.10 |
| qwen4exp A3B Q4_K - Medium     | 127.52 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-Q8_0.gguf |           tg128 |         24.18 ± 0.02 |
| qwen4exp A3B Q4_K - Medium     | 127.52 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-Q8_0.gguf | pp2048 @ d12000 |      1408.03 ± 16.99 |
| qwen4exp A3B Q4_K - Medium     | 127.52 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-Q8_0.gguf |  tg128 @ d12000 |         22.84 ± 0.20 |
| qwen4exp A3B Q4_K - Medium     | 172.22 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-BF16.gguf |          pp2048 |      1517.56 ± 39.58 |
| qwen4exp A3B Q4_K - Medium     | 172.22 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-BF16.gguf |           tg128 |         24.26 ± 0.04 |
| qwen4exp A3B Q4_K - Medium     | 172.22 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-BF16.gguf | pp2048 @ d12000 |      1407.77 ± 15.22 |
| qwen4exp A3B Q4_K - Medium     | 172.22 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-BF16.gguf |  tg128 @ d12000 |         22.83 ± 0.23 |

build: 3ba6fad80 (11599)
pass 2: BF16 -> Q8_0 -> none
| model                          |       size |     params | backend    | ngl | n_ubatch |  fa |         lm | lazy_mode  | ple        |            test |                  t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | -------: | --: | ---------: | ---------- | ---------- | --------------: | -------------------: |
| qwen4exp A3B Q4_K - Medium     | 172.22 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-BF16.gguf |          pp2048 |      1521.31 ± 36.11 |
| qwen4exp A3B Q4_K - Medium     | 172.22 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-BF16.gguf |           tg128 |         24.20 ± 0.04 |
| qwen4exp A3B Q4_K - Medium     | 172.22 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-BF16.gguf | pp2048 @ d12000 |      1400.64 ± 15.44 |
| qwen4exp A3B Q4_K - Medium     | 172.22 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-BF16.gguf |  tg128 @ d12000 |         22.79 ± 0.26 |
| qwen4exp A3B Q4_K - Medium     | 127.52 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-Q8_0.gguf |          pp2048 |      1520.29 ± 40.96 |
| qwen4exp A3B Q4_K - Medium     | 127.52 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-Q8_0.gguf |           tg128 |         24.27 ± 0.04 |
| qwen4exp A3B Q4_K - Medium     | 127.52 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-Q8_0.gguf | pp2048 @ d12000 |      1409.13 ± 16.27 |
| qwen4exp A3B Q4_K - Medium     | 127.52 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-Q8_0.gguf |  tg128 @ d12000 |         22.85 ± 0.21 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | none       |          pp2048 |      1522.35 ± 44.07 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | none       |           tg128 |         24.22 ± 0.06 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | none       | pp2048 @ d12000 |      1410.54 ± 13.72 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | ROCm       |  99 |     2048 |   1 |        dio | on-direct  | none       |  tg128 @ d12000 |         22.76 ± 0.24 |

build: 3ba6fad80 (11599)

The size column counts the table file (103.68 GiB with no --ple, 127.52 GiB with Q8_0, 172.22 GiB with BF16). pp2048: baseline 1524.7 / 1527.5, change 1517.6 - 1522.6 for all tables in both passes (+- 34-44 each); pp2048 @ d12000 1400.6 - 1419.2 vs baseline 1409.8 / 1422.8; tg128 24.2 t/s and tg128 @ d12000 22.8 t/s everywhere. No difference outside the standard deviations.

Vulkan, interleaved (same command without -d):

baseline, change (none), baseline, change (none, Q8_0, BF16), baseline
| model                          |       size |     params | backend    | ngl | n_ubatch |  fa |         lm | lazy_mode  |            test |                  t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | -------: | --: | ---------: | ---------- | --------------: | -------------------: |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | Vulkan     |  99 |     2048 |   1 |        dio | on-direct  |          pp2048 |        848.37 ± 3.01 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | Vulkan     |  99 |     2048 |   1 |        dio | on-direct  |           tg128 |         27.57 ± 0.11 |

build: 4e0d91775 (11596)
| model                          |       size |     params | backend    | ngl | n_ubatch |  fa |         lm | lazy_mode  |            test |                  t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | -------: | --: | ---------: | ---------- | --------------: | -------------------: |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | Vulkan     |  99 |     2048 |   1 |        dio | on-direct  |          pp2048 |        847.04 ± 4.04 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | Vulkan     |  99 |     2048 |   1 |        dio | on-direct  |           tg128 |         27.64 ± 0.02 |

build: 3ba6fad80 (11599)
| model                          |       size |     params | backend    | ngl | n_ubatch |  fa |         lm | lazy_mode  |            test |                  t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | -------: | --: | ---------: | ---------- | --------------: | -------------------: |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | Vulkan     |  99 |     2048 |   1 |        dio | on-direct  |          pp2048 |        848.58 ± 3.99 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | Vulkan     |  99 |     2048 |   1 |        dio | on-direct  |           tg128 |         27.65 ± 0.02 |

build: 4e0d91775 (11596)
| model                          |       size |     params | backend    | ngl | n_ubatch |  fa |         lm | lazy_mode  | ple        |            test |                  t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | -------: | --: | ---------: | ---------- | ---------- | --------------: | -------------------: |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | Vulkan     |  99 |     2048 |   1 |        dio | on-direct  | none       |          pp2048 |        847.55 ± 2.84 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | Vulkan     |  99 |     2048 |   1 |        dio | on-direct  | none       |           tg128 |         27.66 ± 0.02 |
| qwen4exp A3B Q4_K - Medium     | 127.52 GiB |   176.94 B | Vulkan     |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-Q8_0.gguf |          pp2048 |        846.57 ± 1.97 |
| qwen4exp A3B Q4_K - Medium     | 127.52 GiB |   176.94 B | Vulkan     |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-Q8_0.gguf |           tg128 |         27.66 ± 0.04 |
| qwen4exp A3B Q4_K - Medium     | 172.22 GiB |   176.94 B | Vulkan     |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-BF16.gguf |          pp2048 |        847.53 ± 2.08 |
| qwen4exp A3B Q4_K - Medium     | 172.22 GiB |   176.94 B | Vulkan     |  99 |     2048 |   1 |        dio | on-direct  | Qwen3.8-Flash-Next-PLE-BF16.gguf |           tg128 |         27.61 ± 0.06 |

build: 3ba6fad80 (11599)
| model                          |       size |     params | backend    | ngl | n_ubatch |  fa |         lm | lazy_mode  |            test |                  t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | -------: | --: | ---------: | ---------- | --------------: | -------------------: |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | Vulkan     |  99 |     2048 |   1 |        dio | on-direct  |          pp2048 |        848.69 ± 2.33 |
| qwen4exp A3B Q4_K - Medium     | 103.68 GiB |   176.94 B | Vulkan     |  99 |     2048 |   1 |        dio | on-direct  |           tg128 |         27.64 ± 0.03 |

build: 4e0d91775 (11596)

Vulkan pp2048: baseline 848.4 / 848.6 / 848.7, change 847.0 (none), then 847.6 / 846.6 / 847.5 (none / Q8_0 / BF16), +- 2-4 each; tg128 27.57-27.66 everywhere. A first, non-interleaved Vulkan run (baseline first) had shown the change 1.5 % lower; interleaved, the difference is gone, so that was run order.

Correctness:

Exact-logit harness (token ids, fnv64 of the logits of every prefill position, full final logits, 32 greedy decode steps with full logits), 4 repo corpora + the 4 concatenated 6x (16176 tokens), -c 16384 -b 512 -ub 512:

Backend Run Compared with Result
ROCm 10, Vulkan Qwen, no --ple, dio + on-direct merge-base byte-identical
ROCm 10, Vulkan Qwen, --ple with a byte copy of its own IQ4_NL table merge-base byte-identical
ROCm 10, Vulkan Qwen, --ple BF16 table a hand-built model file that carries the same BF16 table byte-identical
ROCm 10, Vulkan --ple IQ4_NL with --lazy-mode on (mmap) merge-base, same mode byte-identical
ROCm 10, Vulkan, CPU gemma4 E2B, no --ple and --ple with its own table; resident, lazy, dio; lazy + mmap on CPU merge-base, same mode byte-identical
ROCm 10, Vulkan gemma4 E2B with the table stripped from the model file + --ple merge-base byte-identical
ROCm 10, Vulkan llama-server, --spec-type draft-mtp with a shared MTP head, 6 prompts: no --ple vs --ple IQ4_NL; hand-built BF16 file vs --ple BF16 (ROCm 10) merge-base / hand-built file same text and draft counts per prompt

Errors (all at load, before any weight is read; ROCm 10 build, CPU): architecture mismatch, changed key value, same value with another int type, shape mismatch, matmul weight in the file, tied token_embd in the file, file without tensors, unused tensor, missing file; a valid file loads. Corrupted table with --check-tensors (both the read and the mapped path) (*).

mmap prefetch (*): after an mmap load on CPU, 0.00-0.02 % of the replaced 28.8 GB range was in the page cache; with the init_mappings hunk reverted, 46 % (13.2 GB) after 60 s and rising.

Table quality on ROCm 10 (llama-perplexity, 150 x 512, reference = UD-Q4_K_XL with the BF16 table, held-out corpus written after the model's cutoff, and wikitext-2 as a memorized-text control); every value matches an earlier run on ROCm develop-3821 to all printed digits:

Table Corpus PPL ratio Mean KLD Same top-1
Q8_0 held-out 1.0019 +- 0.0013 0.0162 +- 0.0004 96.20 +- 0.10 %
IQ4_NL (embedded) held-out 1.0022 +- 0.0016 0.0255 +- 0.0005 95.02 +- 0.11 %
Q8_0 wikitext-2 0.9987 +- 0.0010 0.0122 +- 0.0002 96.44 +- 0.10 %
IQ4_NL (embedded) wikitext-2 1.0044 +- 0.0014 0.0239 +- 0.0004 94.71 +- 0.11 %

MTP draft acceptance, 24 prompts (*): BF16 65.2 %, Q8_0 64.8 %, IQ4_NL 66.0 %; all pairwise 95 % bootstrap intervals contain 0.

(*) measured on the previous base e3af5e8 with ROCm develop-3821 (+PM4 patch); the patch is byte-identical. Everything else was run on 4e0d917 / 3ba6fad80 with ROCm 10.0 and Vulkan.

test-backend-ops: not run, no ggml op changed.

Additional information

  • Scope: the loader change itself is not Strix specific. It is here because its main use only exists in this fork: the qwen4exp table read from disk row by row (--lazy-mode on-direct, src/llama-ple-disk.cpp) is what makes a 54-102 GB table usable on a 128 GB unified-memory box, and all measurements are from here. The loader and gguf-py commits cherry-pick cleanly onto halo-box/llama.cpp master (d15f754, 2026-10-07) and compile there (CPU build, not run), so the change can be proposed there later; the llama-bench commit needs a small port.
  • Known limits:
    • mmap+mlock with CPU-mapped weights (-ngl 0, --cpu-moe): load_all_data grows the lock from the start of the mapping, so the replaced range of the model file gets locked too. The same happens today for lazy tables. Not testable here (RLIMIT_MEMLOCK hard limit 8 MiB); GPU-offloaded weights do not take that path.
    • With --load-mode mmap (or auto), a model file that holds a replaced table is mapped without MAP_POPULATE and gets MADV_WILLNEED on the rest instead, the same as a file with a lazy range today. For qwen4exp the table is lazy anyway, so nothing changes; for a model whose table is not lazy (gemma4 E2B) the first forward pass faults in the pages the prefetch did not reach yet.
    • The key check proves the layout and hashing match, not that the table belongs to the model.
    • scripts/compare-llama-bench.py ignores the new ple field (as it does lazy_mode): adding it as a key would make the script reject output of builds without it.
    • (*) One run with the table on an NFS share (a Q8_0 shard of another quant) took 3.5x the wall time of the same correctness run with the table on local NVMe (292 s vs 83 s, model files local in both). Keep the table on fast local storage.
  • Table files (IQ4_NL / Q8_0 / BF16): https://huggingface.co/Delgon/Qwen3.8-Flash-Next-PLE-GGUF

Requirements

  • I have read and agree with the contributing guidelines
  • This change is Strix Halo specific, or justified by measurements on Strix Halo. General llama.cpp improvements belong in halo-box/llama.cpp instead (see Scope above)
  • AI usage disclosure: AGENT-AUTHORED. Code, tests, measurements and this description by Claude Opus 5.5 (Claude Code), directed by @michal-zurkowski. Two review passes by Claude Fable 5.1: the first found one bug (a draft / prefill model inherited the --ple file; fixed in the first commit) and asked for the stricter checks that are in it; the second found no further code defect and checked this text against the logs. Re-tested after rebasing onto current master with ROCm 10.0.
  • What was NOT verified: mlock load modes; Qwen with the table resident (--lazy-mode off, too large next to the weights); Windows (the mmap range code is shared, not run); CUDA, Metal and other backends; gemma3n and gemma4 E4B; contexts above 16K tokens in the logit comparison (llama-bench ran at depth 12000); scripts/compare-llama-bench.py; the test binaries (tests/) were not built locally, only llama-server, llama-bench, llama-perplexity and llama-completion; the halo-box/llama.cpp cherry-pick was compiled, not run.

🤖 Generated with Claude Code

michal-zurkowski and others added 2 commits October 7, 2026 18:37
--ple FILE opens one more GGUF after the model files. Each tensor in it
replaces the lookup table of the same name in the model, so a model can
use a PLE / n-gram table of another precision (e.g. BF16 or Q8_0 instead
of the IQ4_NL table it ships) without a rebuilt model file. The table of
the model file is never read. Works for every load and lazy mode,
including the qwen4exp on-direct reader, which reads the rows from the
new file.

- every key of the file must match the model in type and value, except
  split.* and general.* (general.architecture is compared); a file without
  such keys loads with a warning
- a replaced tensor must keep its shape; only get_rows lookup tables can
  be replaced; a file without tensors is an error
- the replaced byte ranges of the model are kept out of the mmap prefetch
- the path is part of the model params in common, so a draft or prefill
  model made from a copy of the params does not get the file
- vocab-only loads do not open the file
- path_ple is the last member of llama_model_params

Assisted-by: Claude Opus 5.5
Assisted-by: Claude Fable 5.1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
--ple <file|none> takes several values like the other params, so tables of
different precision can be compared in one run. Adds a ple output field.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation server conversion labels Oct 7, 2026
@michal-zurkowski
michal-zurkowski marked this pull request as ready for review October 7, 2026 18:31
Copies the PLE table of a model (any shard of a split model) to its own
GGUF with the arch's ple.* keys, as a byte copy or converted to f32 / f16 /
bf16 / q8_0 / q5_x / q4_x. --metadata-from takes the keys from another
file, for a lone shard that has none.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RowStream duck-typed the ndarray that GGUFWriter.write_tensor_data()
declares, which ty rejects; the previous commit silenced that with an
ignore. Let GGUFWriter write the header, keys and tensor infos only, and
append the data section with its alignment from write_rows(). The output
is byte-identical to before.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dzannotti
dzannotti merged commit c7f11bc into halo-box:master Oct 7, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conversion documentation Improvements or additions to documentation server

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants