Roland2531 commited on
Commit
2d166d5
·
verified ·
1 Parent(s): 130df92

Publish versioned FPX optimization and fix scripts 20261021-01

Browse files

Add pinned source patch, verified application script, runtime environment preset and CK import support. Preserve existing runtime release artifacts; update only mutable README index. No model weights or full source snapshot.

AiTable-ROCmFPX-Optimizations-Fixes-20261021-01.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0b11b7f5b1c95f69524efcad44d4f1c3f1b9102d403380b1ca7dea0e5102c087
3
+ size 44071
FPX-Fixes-SHA256SUMS-20261021-01.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ dcdd6a8ebca68467f3faa9fd07e2f69f11e25446f100b4c214cc2619fc681f6b aitable-fpx-native-fixes-20261021-01.patch
2
+ 0b11b7f5b1c95f69524efcad44d4f1c3f1b9102d403380b1ca7dea0e5102c087 AiTable-ROCmFPX-Optimizations-Fixes-20261021-01.zip
3
+ 1972b0408bd74f1d0e5695bbd1dae20844d043528f464ae26ca6069564d89d4d apply-fpx-fixes-20261021-01.py
4
+ 6ffc49b30005d3d0343710ee4f8ed4a28f4fc16d334db24fe251a9ef7d60dcd2 configure-fpx-env-20261021-01.cmd
5
+ bee1ef0d41e4fe3b63bf5b95367ff7a760b63bc56a9296bb925e40e7f4a480f5 fpx-fixes-manifest-20261021-01.json
6
+ 1e52743e8668db18f93be8e2396ec0bddb5f9ca68d473a2fbf99be1b1092c761 FPX-Optimizations-20261021-01.md
FPX-Optimizations-20261021-01.md ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AiTable ROCmFPX optimizations and fixes — 20261021-01
2
+
3
+ ## 下載與配對 / Downloads and pairing
4
+
5
+ This is the **script/source-diff add-on** for the existing [Windows gfx1201 Runtime 20261021-01](https://huggingface.co/Roland2531/llama.cpp-ROCmFPX-gfx1201-Windows-x64/resolve/main/AiTable-ROCmFPX-gfx1201-Windows-x64-20261021-01.zip). It is not a replacement model or a new inference binary.
6
+
7
+ - [Complete scripts, patch and build support ZIP](https://huggingface.co/Roland2531/llama.cpp-ROCmFPX-gfx1201-Windows-x64/resolve/main/AiTable-ROCmFPX-Optimizations-Fixes-20261021-01.zip)
8
+ - [Application script](https://huggingface.co/Roland2531/llama.cpp-ROCmFPX-gfx1201-Windows-x64/blob/main/apply-fpx-fixes-20261021-01.py)
9
+ - [Native patch](https://huggingface.co/Roland2531/llama.cpp-ROCmFPX-gfx1201-Windows-x64/blob/main/aitable-fpx-native-fixes-20261021-01.patch)
10
+ - [Source/hash manifest](https://huggingface.co/Roland2531/llama.cpp-ROCmFPX-gfx1201-Windows-x64/blob/main/fpx-fixes-manifest-20261021-01.json)
11
+ - [Runtime environment preset](https://huggingface.co/Roland2531/llama.cpp-ROCmFPX-gfx1201-Windows-x64/blob/main/configure-fpx-env-20261021-01.cmd)
12
+ - [Add-on checksums](https://huggingface.co/Roland2531/llama.cpp-ROCmFPX-gfx1201-Windows-x64/resolve/main/FPX-Fixes-SHA256SUMS-20261021-01.txt)
13
+ - [Paired model weights](https://huggingface.co/Roland2531/Ornith-1.5-9B-uncensored-ROCmI4-Q6IO-A8)
14
+
15
+ 已經下載 Runtime 的使用者不用對 EXE 套 patch。Runtime 已包含該版原生修改;套用腳本是提供建置者將修改帶到指定上游來源,環境腳本則供現有 Runtime 設定目前命令列環境。所有檔案以固定版本名稱發布,之後新增版本,不覆蓋舊版。
16
+
17
+ ## Scope and provenance
18
+
19
+ Upstream: [charlie12345/ROCmFPX](https://github.com/charlie12345/ROCmFPX), exact base **fb08d7cdb67082ddda15a25e64dc7ec413afc41c**. The native delta was taken from the source snapshot frozen alongside the already-published Runtime. All 23 differing source files also matched the current project source at preparation time. Existing upstream features, quantization formats and dependencies remain upstream work; the diff is an integration record, not a claim that AiTable invented the underlying methods.
20
+
21
+ This add-on includes a source patch, application/environment scripts, license notices and the tiny Windows CK import library/DEF required for linking. It excludes the full C/C++ source snapshot, build caches/objects, SDK, private logs, prompts, conversations, model weights and credentials. It does not include any newly trained model or new quantization.
22
+
23
+ ## What is included
24
+
25
+ | Area | Files / mechanism | Value and limits |
26
+ | --- | --- | --- |
27
+ | Short prefix checkpoints | `prefix-short-policy.h`, server context; 1024/2048 token boundaries, four checkpoint slots | Enables earlier compatible-prefix reuse on the supported hybrid model path. It cannot reuse changed token content or guarantee a fixed 3K-prompt latency. |
28
+ | RAM checkpoint restore | `src/llama-context.cpp` plus server cache integration | Retains the installed restore correction. RAM prompt cache must be explicitly enabled; it is host memory, separate from GPU KV and model weights. Background tasks can still require new work when no compatible checkpoint exists. |
29
+ | Tool grammar and parsing | `common/chat-auto-parser-generator.cpp`, chat, PEG, JSON-Schema grammar and sampling files | Tagged tool routing, string/JSON escape and bounded-length handling, delimiter/whitespace behavior, prefill/finish/error diagnostics. Grammar syntax constraints do not prove correct tool semantics or delivery. |
30
+ | Native Flash Attention routing | `fattn.cu`, `fattn-common.cuh` | Retains integrated routing adjustments for the older fork, including the supported gfx1201/head256 path. Not every attention shape takes that route. |
31
+ | RDNA4 hardware transpose | `fattn-mma-f16.cuh` | Uses guarded gfx1201 F16 transposed V loads with alignment, shape and existing LDS-capacity checks. It is not universal FP8 KV or native W4A4 acceleration. |
32
+ | Projector lifecycle | server projector/queue/context files | Retains installed idle/unload and active-work coordination. No vision model is included or loaded by these scripts. |
33
+ | Optional offline prefix probe | `tools/cli/prefix-offline-probe.cpp` and target | A measurement helper, not an extra inference optimization. Its own usage requires controlled one-slot/checkpoint inputs and no RAM cache; do not treat it as the full application/HTTP playback path. |
34
+ | Experimental CK bridge | guarded dispatch and matching import library | Present for binary compatibility. The environment preset leaves CK off unless explicitly opted in; prior input-dependent output differences remain documented. No fix for all CK numerical differences is claimed. |
35
+
36
+ These are retained integration changes, not a new benchmark. The add-on does not add rejected RDNA4 attention v2, research GDN, norm/SiLU fusion or experimental Q8/FP8 attention kernels. Disabled environment toggles do not prove such kernels exist in the binary.
37
+
38
+ ## Option A — use the existing runtime / 使用現有後端
39
+
40
+ Extract the existing Runtime ZIP, preserving `runtime/` and `.kpack/`. Extract the add-on separately. In **Command Prompt**, call the preset with the Runtime package root:
41
+
42
+ ```bat
43
+ call "D:\FPX-Fixes\configure-fpx-env-20261021-01.cmd" "D:\FPX-Runtime"
44
+ "D:\FPX-Runtime\runtime\llama-server.exe" --model "D:\Models\your-model.gguf" --host 127.0.0.1 --port 8080 --parallel 1 --ctx-size 8192 --n-gpu-layers 99 --flash-attn on --cache-type-k q8_0 --cache-type-v q8_0 --cache-ram 1024
45
+ ```
46
+
47
+ Paths and context are examples. The script only sets the current CMD environment; it does not launch, stop or replace a service, modify persistent AiTable settings, change model temperature/top-p/token limits or force a model's thinking mode. Keep the model/application's intended sampling and thinking configuration. Do not add thinking-disable flags that the selected model/template does not support.
48
+
49
+ Preset contents:
50
+
51
+ - `FPX_SHORT_PREFIX_CHECKPOINT=1` for the installed short-prefix policy.
52
+ - `LLAMA_ARG_CACHE_RAM=1024`: a **1024 MiB host-RAM cap**, not reserved VRAM. Explicit CLI `--cache-ram` can set a different cap. `0` disables it; this preset does not use unlimited `-1`.
53
+ - Package-local rocBLAS Tensile path and `ROCBLAS_USE_HIPBLASLT=0`, preserving this release's rocBLAS choice.
54
+ - `GGML_CUDA_DISABLE_ROPE_FWHT_SET_ROWS=1` and the published release's retired research switches set to 0.
55
+ - CK default `GGML_HIP_CK_ATTENTION=0` if the caller has not explicitly set it. Set 0 explicitly to clear a previous opt-in. Enabling CK is experimental and needs your own output checks.
56
+
57
+ The previously published `run-server.cmd` example did not explicitly enable RAM cache. Use the example above or pass the cache cap in your own integration when RAM prompt caching is intended. Short checkpoint creation and RAM cache storage are different mechanisms. Observe actual server logs/cache-token/timing counters to verify reuse; an enabled setting alone is not acceleration evidence.
58
+
59
+ ## Option B — apply native fixes to source / 套用原生修正
60
+
61
+ Requires Python 3.9+ and Git. Use a separate clean checkout of the exact upstream commit, preserving LF in both clone and checkout:
62
+
63
+ ```bat
64
+ git -c core.autocrlf=false clone https://github.com/charlie12345/ROCmFPX.git ROCmFPX-work
65
+ git -C ROCmFPX-work -c core.autocrlf=false checkout fb08d7cdb67082ddda15a25e64dc7ec413afc41c
66
+ python apply-fpx-fixes-20261021-01.py --source ROCmFPX-work
67
+ python apply-fpx-fixes-20261021-01.py --source ROCmFPX-work --apply
68
+ ```
69
+
70
+ Default is check-only. The script validates patch SHA256 and each affected original file, rejects wrong/already-patched sources, backs up originals and lists new files, applies atomically with `git apply`, then checks all 23 resulting hashes. Git automatic newline conversion is disabled for patch application. Backups are placed beside the checkout with a unique name and `restore-manifest.json`. To restore, verify no later edits would be overwritten, restore backed-up originals and remove only manifest-listed originally absent files. The script does not silently merge changes, compile or install anything.
71
+
72
+ ### CK linking and source build boundary
73
+
74
+ The native snapshot linked CK via an AiTable-specific sibling folder. **The only publication adjustment to that snapshot** is replacing that CMake path with the explicit cache parameter `FPX_CK_IMPORT_LIBRARY`. At configure time, supply the full path to the import library in the complete add-on ZIP, for example:
75
+
76
+ ```text
77
+ -DFPX_CK_IMPORT_LIBRARY=D:/FPX-Fixes/dependencies/CK/ck-attention.lib
78
+ ```
79
+
80
+ The import library is 1692 bytes; it contains Windows DLL import records, not the full CK implementation. The matching `ck-attention.dll` remains in the Runtime package; SHA256 `96ecc9b94fc7ecc4f2e2dcccc3230b8b5bb950c5774ea7a166665f07a4376ea2`. Keep its license and runtime dependencies. This add-on does not include CK kernel sources or reconstruct that DLL.
81
+
82
+ Follow upstream's build instructions with your own Windows/MSVC/CMake/Ninja and HIP/ROCm setup; the published binary's provenance records LLVM24/ROCm10.1 and gfx1201. Dependencies, compiler/SDK compatibility and a full clean build must be validated separately. No retained object files, local absolute SDK paths or automated overwrite of an installed runtime are supplied. A fresh full build and bit-identical reproduction are **not** claimed by this release.
83
+
84
+ ## Checks performed for this add-on
85
+
86
+ - Patch preflight on the fixed upstream checkout: passed.
87
+ - Actual application plus all 23 output hashes: passed.
88
+ - Repeat application, locally modified affected source, and tampered patch: rejected; the modified-source case made no writes.
89
+ - Runtime environment variables and existing server `--version`: passed; no model or HTTP service started.
90
+ - Complete add-on ZIP CRC and download checksums: checked separately at publication.
91
+ - No new GPU, model-quality, tool-accuracy, prefill, decode, TTFT or VRAM benchmark was run. Earlier results remain workload-specific; no new performance gain is asserted.
92
+
93
+ ## License and release retention
94
+
95
+ Native core changes retain upstream MIT terms (`LICENSE`); CK import support has its preserved CK license (`CK-LICENSE.txt`), and runtime/SDK dependencies retain their own notices. Model license terms are separate. Keep prior versioned archives/scripts/patches/manifests/notes/checksums unchanged. This add-on only adds new files and updates the mutable repository README index; it does not replace Runtime 20261021-01.
README.md CHANGED
@@ -59,7 +59,7 @@ Short-prefix reuse is content-dependent. No universal decode speedup or full-wor
59
 
60
  `build-manifest-20261021-01.json` records the actual included binary hashes. `SHA256SUMS-20261021-01.txt` at the repository root covers downloadable files; the runtime ZIP contains a separate checksum list for extracted payloads. Native `--version` / `--help` launch checks are performed from the standalone package; no new GPU model benchmark or other-GPU validation is implied.
61
 
62
- The integrated C/C++ source snapshot is retained locally and is not uploaded in this binary release. Upstream source links and all bundled license notices remain provided. The installed build used retained build objects and locally prepared dependency wiring; a fully reproducible public source build is not claimed.
63
 
64
  ## Versioned releases / 舊版本保留
65
 
@@ -72,3 +72,14 @@ Each release has an immutable version identifier. Future updates add a new ZIP,
72
  ## License
73
 
74
  llama.cpp/ROCmFPX core code is MIT; preserve `LICENSE` and `runtime/licenses/`. Bundled AMD and other dependencies retain their own licenses, including MIT and Apache-2.0 with LLVM exceptions where applicable. See `THIRD_PARTY_NOTICES.md`. The repository's MIT metadata does not relicense the paired model weights or every third-party binary. Model terms are documented in the model repository.
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
  `build-manifest-20261021-01.json` records the actual included binary hashes. `SHA256SUMS-20261021-01.txt` at the repository root covers downloadable files; the runtime ZIP contains a separate checksum list for extracted payloads. Native `--version` / `--help` launch checks are performed from the standalone package; no new GPU model benchmark or other-GPU validation is implied.
61
 
62
+ The complete integrated C/C++ source snapshot is retained locally and is not uploaded. A curated native patch/application-script add-on is now available below. Upstream source links and all bundled license notices remain provided. The installed build used retained build objects and locally prepared dependency wiring; a fully reproducible public source build is not claimed.
63
 
64
  ## Versioned releases / 舊版本保留
65
 
 
72
  ## License
73
 
74
  llama.cpp/ROCmFPX core code is MIT; preserve `LICENSE` and `runtime/licenses/`. Bundled AMD and other dependencies retain their own licenses, including MIT and Apache-2.0 with LLVM exceptions where applicable. See `THIRD_PARTY_NOTICES.md`. The repository's MIT metadata does not relicense the paired model weights or every third-party binary. Model terms are documented in the model repository.
75
+
76
+ ## Optimization and fix scripts / 優化與修正腳本
77
+
78
+ - [Complete add-on ZIP — 20261021-01](https://huggingface.co/Roland2531/llama.cpp-ROCmFPX-gfx1201-Windows-x64/resolve/main/AiTable-ROCmFPX-Optimizations-Fixes-20261021-01.zip)
79
+ - [Scope, configuration, source application and limits](https://huggingface.co/Roland2531/llama.cpp-ROCmFPX-gfx1201-Windows-x64/blob/main/FPX-Optimizations-20261021-01.md)
80
+ - [Source application script](https://huggingface.co/Roland2531/llama.cpp-ROCmFPX-gfx1201-Windows-x64/blob/main/apply-fpx-fixes-20261021-01.py)
81
+ - [Runtime environment preset](https://huggingface.co/Roland2531/llama.cpp-ROCmFPX-gfx1201-Windows-x64/blob/main/configure-fpx-env-20261021-01.cmd)
82
+
83
+ Includes installed short-prefix/RAM restore, tool grammar/parser/sampler fixes and guarded Flash Attention/transpose integration. The environment preset enables a bounded 1024 MiB host-RAM prompt cache; the earlier standalone run-server example did not explicitly enable it. CK remains off by default in the standalone preset. No new inference binary, model weights or fresh GPU speedup is claimed. The source script was checked/applied on the pinned upstream commit and verifies all 23 changed files; full rebuild reproducibility remains unverified.
84
+
85
+ The existing Runtime ZIP, version-specific notes, manifest and checksum files remain unchanged. Initial Runtime checksums describe its original release revision; this root README is a mutable index.
aitable-fpx-native-fixes-20261021-01.patch ADDED
@@ -0,0 +1,1959 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --- a/common/chat-auto-parser-generator.cpp
2
+ +++ b/common/chat-auto-parser-generator.cpp
3
+ @@ -94,6 +94,26 @@
4
+ return data;
5
+ }
6
+
7
+ +// A required tagged call with thinking explicitly off has no generated prose
8
+ +// channel. Preserve templates that prefill an open reasoning block: those need
9
+ +// their existing parser to finish it before the call can begin.
10
+ +static bool require_tagged_call_only(const generation_params & inputs, const analyze_reasoning & reasoning) {
11
+ + if (inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED || inputs.enable_thinking) {
12
+ + return false;
13
+ + }
14
+ + if (reasoning.mode == reasoning_mode::NONE) {
15
+ + return true;
16
+ + }
17
+ + const auto start = trim_whitespace(reasoning.start);
18
+ + const auto end = trim_whitespace(reasoning.end);
19
+ + if (start.empty() || end.empty()) {
20
+ + return false;
21
+ + }
22
+ + const auto opened = inputs.generation_prompt.rfind(start);
23
+ + const auto closed = inputs.generation_prompt.rfind(end);
24
+ + return opened == std::string::npos || (closed != std::string::npos && closed > opened);
25
+ +}
26
+ +
27
+ common_peg_arena autoparser::build_parser(const generation_params & inputs) const {
28
+ if (!analysis_complete) {
29
+ throw std::invalid_argument("Cannot call build_parser on autoparser without performing analysis first, call analyze_template(...)");
30
+ @@ -108,12 +128,17 @@
31
+
32
+ // Build reasoning parser
33
+ ctx.reasoning_parser = reasoning.build_parser(ctx);
34
+ -
35
+ auto parser = p.eps();
36
+
37
+ bool has_tools = inputs.tools.is_array() && !inputs.tools.empty();
38
+ bool has_response_format = inputs.json_schema.is_object() && !inputs.json_schema.empty();
39
+ bool pure_content = reasoning.mode == reasoning_mode::NONE;
40
+ + const bool direct_tagged = !has_response_format && has_tools && jinja_caps.supports_tool_calls &&
41
+ + tools.format.mode == tool_format::TAG_WITH_TAGGED &&
42
+ + require_tagged_call_only(inputs, reasoning);
43
+ + if (direct_tagged) {
44
+ + ctx.reasoning_parser = p.eps();
45
+ + }
46
+
47
+ if (has_response_format) {
48
+ auto response_format = p.rule("response-format", p.content(p.schema(p.json(), "response-format-schema", inputs.json_schema)));
49
+ @@ -128,7 +153,10 @@
50
+ } else {
51
+ parser = content.build_parser(ctx);
52
+ }
53
+ - return pure_content ? p.prefix(inputs.generation_prompt, reasoning.start) + parser : p.prefix(inputs.generation_prompt, reasoning.start) << parser;
54
+ + // A closed no-thinking prefill is fixed template text, not permission
55
+ + // to start another reasoning block in the generated suffix.
56
+ + const auto prefix = p.prefix(inputs.generation_prompt, direct_tagged ? "" : reasoning.start);
57
+ + return pure_content ? prefix + parser : prefix << parser;
58
+ });
59
+ }
60
+
61
+ @@ -357,7 +385,20 @@
62
+ auto & p = ctx.p;
63
+ const auto & inputs = ctx.inputs;
64
+
65
+ - auto until_suffix = p.rule("until-suffix", p.until(arguments.value_suffix));
66
+ + // Tagged empty values may share whitespace between their opening and
67
+ + // closing delimiters. Only an exact whitespace overlap can be reused.
68
+ + const auto value_open = arguments.name_suffix + arguments.value_prefix;
69
+ + size_t shared_space = 0;
70
+ + for (size_t n = 1; n < arguments.value_suffix.size() && n <= value_open.size(); ++n) {
71
+ + const auto prefix = arguments.value_suffix.substr(0, n);
72
+ + if (prefix.find_first_not_of(" \t\r\n") != std::string::npos) { break; }
73
+ + if (value_open.compare(value_open.size() - n, n, prefix) == 0) { shared_space = n; }
74
+ + }
75
+ + const auto empty_close = arguments.value_suffix.substr(shared_space);
76
+ + // Stop at either close, so the ordinary string branch cannot consume an
77
+ + // empty value's closing tag and reinterpret later tools as its contents.
78
+ + auto until_suffix = p.rule("until-suffix", shared_space ?
79
+ + p.until_one_of({arguments.value_suffix, empty_close}) : p.until(arguments.value_suffix));
80
+
81
+ common_peg_parser tool_choice = p.choice();
82
+
83
+ @@ -381,15 +422,20 @@
84
+ for (const auto & [param_name, param_schema] : properties.items()) {
85
+ bool is_required = required.find(param_name) != required.end();
86
+
87
+ - auto arg =
88
+ - p.tool_arg(p.tool_arg_open(arguments.name_prefix + p.tool_arg_name(p.literal(param_name)) +
89
+ - arguments.name_suffix) +
90
+ - arguments.value_prefix +
91
+ - (schema_info.resolves_to_string(param_schema) ?
92
+ - p.tool_arg_string_value(until_suffix) :
93
+ - p.tool_arg_json_value(p.schema(
94
+ - p.json(), "tool-" + name + "-arg-" + param_name + "-schema", param_schema, false))) +
95
+ - p.tool_arg_close(p.literal(arguments.value_suffix)));
96
+ + const bool is_string = schema_info.resolves_to_string(param_schema);
97
+ + auto value_and_close =
98
+ + (is_string ? p.tool_arg_string_value(until_suffix) :
99
+ + p.tool_arg_json_value(p.schema(
100
+ + p.json(), "tool-" + name + "-arg-" + param_name + "-schema", param_schema, false))) +
101
+ + p.tool_arg_close(p.literal(arguments.value_suffix));
102
+ + if (is_string && shared_space) {
103
+ + value_and_close = p.choice({
104
+ + p.tool_arg_string_value(p.eps()) + p.tool_arg_close(p.literal(empty_close)),
105
+ + value_and_close});
106
+ + }
107
+ + auto arg = p.tool_arg(
108
+ + p.tool_arg_open(arguments.name_prefix + p.tool_arg_name(p.literal(param_name)) + arguments.name_suffix) +
109
+ + arguments.value_prefix + value_and_close);
110
+
111
+ auto named_arg = p.rule("tool-" + name + "-arg-" + param_name, arg);
112
+ if (is_required) {
113
+ @@ -478,6 +524,9 @@
114
+ tool_calls = p.optional(tool_calls);
115
+ }
116
+
117
+ + if (require_tagged_call_only(inputs, *ctx.reasoning)) {
118
+ + return p.space() + tool_calls + p.end();
119
+ + }
120
+ std::string trigger_marker = !format.section_start.empty() ? format.section_start : format.per_call_start;
121
+ auto content_before_tools = trigger_marker.empty() ? p.eps() : p.until(trigger_marker);
122
+ return ctx.reasoning_parser + p.optional(p.content(content_before_tools)) + tool_calls + p.end();
123
+ --- a/common/chat.cpp
124
+ +++ b/common/chat.cpp
125
+ @@ -1,4 +1,6 @@
126
+ #include "chat.h"
127
+ +#include "jinja/utils.h"
128
+ +#include <iomanip>
129
+
130
+ #include "chat-auto-parser-helpers.h"
131
+ #include "chat-auto-parser.h"
132
+ @@ -2364,6 +2366,66 @@
133
+ return common_chat_peg_parse(params.parser, input, is_partial, params);
134
+ }
135
+
136
+ +
137
+ +static int64_t peg_diag_bound(size_t value, size_t limit = 2147483647) {
138
+ + return static_cast<int64_t>(std::min(value, limit));
139
+ +}
140
+ +
141
+ +static bool peg_diag_utf8(const std::string & s) {
142
+ + for (size_t i = 0; i < s.size();) {
143
+ + unsigned char c = static_cast<unsigned char>(s[i++]);
144
+ + if (c < 0x80) continue;
145
+ + int n = c >= 0xc2 && c <= 0xdf ? 1 : c >= 0xe0 && c <= 0xef ? 2 : c >= 0xf0 && c <= 0xf4 ? 3 : -1;
146
+ + if (n < 0 || i + n > s.size()) return false;
147
+ + uint32_t cp = c & (n == 1 ? 0x1f : n == 2 ? 0x0f : 0x07);
148
+ + for (int j = 0; j < n; ++j) {
149
+ + unsigned char b = static_cast<unsigned char>(s[i++]);
150
+ + if ((b & 0xc0) != 0x80) return false;
151
+ + cp = (cp << 6) | (b & 0x3f);
152
+ + }
153
+ + if ((n == 1 && cp < 0x80) || (n == 2 && cp < 0x800) || (n == 3 && cp < 0x10000)
154
+ + || cp > 0x10ffff || (cp >= 0xd800 && cp <= 0xdfff)) return false;
155
+ + }
156
+ + return true;
157
+ +}
158
+ +
159
+ +static json peg_diag_structure(const common_peg_arena & parser, const std::string & input,
160
+ + const std::string & effective, const std::string & prefix, bool partial, size_t end) {
161
+ + json data={{"schema","aitable.native_peg@1"},{"phase",partial?"partial":"final"},
162
+ + {"result_end",peg_diag_bound(end)},{"input_bytes",peg_diag_bound(input.size())},
163
+ + {"effective_bytes",peg_diag_bound(effective.size())},{"generation_prompt_bytes",peg_diag_bound(prefix.size())},
164
+ + {"utf8_valid",peg_diag_utf8(effective)}};
165
+ + const std::pair<const char *, const char *> markers[]={
166
+ + {"tool_call_open_count","<tool_call>"},{"tool_call_close_count","</tool_call>"},
167
+ + {"function_open_count","<function="},{"function_close_count","</function>"},
168
+ + {"parameter_open_count","<parameter="},{"parameter_close_count","</parameter>"},
169
+ + {"think_open_count","<think>"},{"think_close_count","</think>"}};
170
+ + for (const auto & marker:markers) {
171
+ + size_t count=0,pos=0;
172
+ + while ((pos=effective.find(marker.second,pos))!=std::string::npos) {
173
+ + ++count;pos+=std::char_traits<char>::length(marker.second);
174
+ + if (count==65535) break;
175
+ + }
176
+ + data[marker.first]=peg_diag_bound(count,65535);
177
+ + }
178
+ + const auto last=effective.find_last_not_of(" \t\r\n");
179
+ + std::string tail=effective.empty()?"empty":last==std::string::npos?"whitespace":"other";
180
+ + if (last!=std::string::npos) {
181
+ + const std::pair<const char *,const char *> endings[]={
182
+ + {"tool_call_close","</tool_call>"},{"function_close","</function>"},
183
+ + {"parameter_close","</parameter>"},{"json_object_close","}"},{"json_array_close","]"}};
184
+ + for (const auto & ending:endings) {
185
+ + size_t n=std::char_traits<char>::length(ending.second);
186
+ + if (last+1>=n && effective.compare(last+1-n,n,ending.second)==0) {tail=ending.first;break;}
187
+ + }
188
+ + }
189
+ + data["tail"]=tail;
190
+ + jinja::hasher hash;hash.update(parser.save());
191
+ + std::ostringstream fingerprint;fingerprint<<std::hex<<std::setfill('0')<<std::setw(16)<<hash.digest();
192
+ + data["parser_hash"]=fingerprint.str();data["parser_hash_alg"]="jinja-hasher64";
193
+ + return data;
194
+ +}
195
+ +
196
+ common_chat_msg common_chat_peg_parse(const common_peg_arena & src_parser,
197
+ const std::string & input,
198
+ bool is_partial,
199
+ @@ -2411,10 +2473,11 @@
200
+ }
201
+ return msg;
202
+ }
203
+ - LOG_WRN("%s: unparsed %s output: %s\n", __func__, common_chat_format_name(params.format),
204
+ - effective_input.substr(result.end).c_str());
205
+ - throw std::runtime_error(std::string("The model produced output that does not match the expected ") +
206
+ - common_chat_format_name(params.format) + " format");
207
+ + const auto diagnostics=peg_diag_structure(parser,input,effective_input,params.generation_prompt,is_partial,result.end);
208
+ + LOG_WRN("%s: unparsed %s output (phase=%s, bytes=%zu, result_end=%zu)\n", __func__,
209
+ + common_chat_format_name(params.format), is_partial?"partial":"final", effective_input.size(), result.end);
210
+ + throw common_chat_peg_error(std::string("The model produced output that does not match the expected ") +
211
+ + common_chat_format_name(params.format) + " format",diagnostics.dump());
212
+ }
213
+
214
+ common_chat_msg msg;
215
+ --- a/common/chat.h
216
+ +++ b/common/chat.h
217
+ @@ -11,6 +11,7 @@
218
+ #include "nlohmann/json_fwd.hpp"
219
+
220
+ #include <chrono>
221
+ +#include <stdexcept>
222
+ #include <functional>
223
+ #include <map>
224
+ #include <string>
225
+ @@ -18,6 +19,15 @@
226
+
227
+ using chat_template_caps = jinja::caps;
228
+ using json = nlohmann::ordered_json;
229
+ +
230
+ +// Metadata contains only the finite native PEG diagnostic contract. Keep the
231
+ +// original exception message intact across the common DLL/server boundary.
232
+ +class common_chat_peg_error : public std::runtime_error {
233
+ +public:
234
+ + std::string native_peg_json;
235
+ + common_chat_peg_error(const std::string & message, const std::string & diagnostics)
236
+ + : std::runtime_error(message), native_peg_json(diagnostics) {}
237
+ +};
238
+
239
+ struct common_chat_templates;
240
+
241
+ --- a/common/common.h
242
+ +++ b/common/common.h
243
+ @@ -609,9 +609,9 @@
244
+ int32_t n_cache_reuse = 0; // min chunk size to reuse from the cache via KV shifting
245
+ bool cache_prompt = true; // whether to enable prompt caching
246
+ bool cache_idle_slots = true; // save and clear idle slots upon starting a new task
247
+ - int32_t n_ctx_checkpoints = 32; // max number of context checkpoints per slot
248
+ - int32_t checkpoint_every_nt = 8192; // make a checkpoint every n tokens during prefill
249
+ - int32_t cache_ram_mib = 8192; // -1 = no limit, 0 - disable, 1 = 1 MiB, etc.
250
+ + int32_t n_ctx_checkpoints = 4; // max number of context checkpoints per slot
251
+ + int32_t checkpoint_every_nt = 2048; // make a checkpoint every n tokens during prefill
252
+ + int32_t cache_ram_mib = 0; // -1 = no limit, 0 - disable, 1 = 1 MiB, etc.
253
+ int32_t cache_disk_limit_mib = 8192; // disk prompt-cache limit when cache_disk_path is set
254
+
255
+ std::string hostname = "127.0.0.1";
256
+ --- a/common/json-schema-to-grammar.cpp
257
+ +++ b/common/json-schema-to-grammar.cpp
258
+ @@ -343,6 +343,115 @@
259
+ rules.push_back(visit(alt_schemas[i], name + (name.empty() ? "alternative-" : "-") + std::to_string(i)));
260
+ }
261
+ return string_join(rules, " | ");
262
+ + }
263
+ +
264
+ + // A repeated ASCII character class is a language of decoded Unicode scalars,
265
+ + // not JSON source characters. Lower it through JSON encoding before applying
266
+ + // length bounds. Other regex forms continue through the existing converter.
267
+ + bool _visit_encoded_charclass(const json & schema, const std::string & name, std::string & rule) {
268
+ + const std::string pattern = schema.at("pattern");
269
+ + if (pattern.size() < 6 || pattern[0] != '^' || pattern[1] != '[' ||
270
+ + pattern.substr(pattern.size()-3) != "]*$") { return false; }
271
+ + size_t pos = 2, end = pattern.size()-3;
272
+ + bool negated = pos < end && pattern[pos] == '^';
273
+ + if (negated) { ++pos; }
274
+ + bool included[128] = {};
275
+ + auto hex_value = [](char c) -> int {
276
+ + if (c >= '0' && c <= '9') return c-'0';
277
+ + if (c >= 'a' && c <= 'f') return c-'a'+10;
278
+ + if (c >= 'A' && c <= 'F') return c-'A'+10;
279
+ + return -1;
280
+ + };
281
+ + auto atom = [&](int & value) -> bool {
282
+ + if (pos >= end) return false;
283
+ + unsigned char c = pattern[pos++];
284
+ + if (c >= 128 || c == ']') return false;
285
+ + if (c != '\\') { value=c; return true; }
286
+ + if (pos >= end) return false;
287
+ + char escape=pattern[pos++];
288
+ + if (escape=='x' || escape=='u') {
289
+ + const size_t count=escape=='x'?2:4;
290
+ + if (pos+count>end) return false;
291
+ + value=0;
292
+ + for(size_t i=0;i<count;++i){int digit=hex_value(pattern[pos++]);if(digit<0)return false;value=value*16+digit;}
293
+ + return value<128;
294
+ + }
295
+ + switch(escape){
296
+ + case 'n':value=10;return true; case 'r':value=13;return true;
297
+ + case 't':value=9;return true; case 'b':value=8;return true;
298
+ + case 'f':value=12;return true;
299
+ + default:
300
+ + if(std::string("\\][^-/.+*?(){}|$\"").find(escape)==std::string::npos)return false;
301
+ + value=static_cast<unsigned char>(escape);return true;
302
+ + }
303
+ + };
304
+ + bool any=false;
305
+ + while(pos<end){
306
+ + int lo,hi;if(!atom(lo))return false;hi=lo;
307
+ + if(pos+1<end && pattern[pos]=='-'){++pos;if(!atom(hi)||hi<lo)return false;}
308
+ + for(int c=lo;c<=hi;++c)included[c]=true;
309
+ + any=true;
310
+ + }
311
+ + if(!any)return false;
312
+ + bool allowed[128];for(int c=0;c<128;++c)allowed[c]=negated?!included[c]:included[c];
313
+ + int min_len=schema.value("minLength",0),max_len=schema.value("maxLength",std::numeric_limits<int>::max());
314
+ + if(min_len<0||max_len<min_len){_errors.push_back("Invalid string length bounds");rule="";return true;}
315
+ + std::vector<std::string> alternatives;
316
+ + std::string raw="[";
317
+ + for(int c=32;c<128;++c){
318
+ + if(!allowed[c]||c=='"'||c=='\\')continue;
319
+ + int lo=c;while(c+1<128&&allowed[c+1]&&c+1!='"'&&c+1!='\\')++c;
320
+ + char buf[12];snprintf(buf,sizeof(buf),"\\x%02X",lo);raw+=buf;
321
+ + if(c!=lo){snprintf(buf,sizeof(buf),"-\\x%02X",c);raw+=buf;}
322
+ + }
323
+ + if(raw.size()>1)alternatives.push_back(raw+"]");
324
+ + if(negated)alternatives.push_back("[\\u0080-\\uD7FF\\uE000-\\U0010FFFF]");
325
+ + for(const auto & item:std::vector<std::pair<int,std::string>>{{34,"\\\""},{92,"\\\\"},{47,"\\/"},{8,"\\b"},{12,"\\f"},{10,"\\n"},{13,"\\r"},{9,"\\t"}}){
326
+ + if(allowed[item.first])alternatives.push_back(format_literal(item.second));
327
+ + }
328
+ + const auto hex=_add_rule("json-pattern-hex","[0-9a-fA-F]");
329
+ + auto hex_class=[](unsigned mask){
330
+ + std::string result="[";
331
+ + for(unsigned i=0;i<16;++i)if(mask&(1u<<i)){
332
+ + result+=char(i<10?'0'+i:'a'+i-10);if(i>=10)result+=char('A'+i-10);
333
+ + }
334
+ + return result+"]";
335
+ + };
336
+ + std::map<unsigned,unsigned> groups;
337
+ + for(unsigned high=0;high<8;++high){unsigned lowmask=0;for(unsigned low=0;low<16;++low)if(allowed[high*16+low])lowmask|=1u<<low;if(lowmask)groups[lowmask]|=1u<<high;}
338
+ + std::vector<std::string> ascii_unicode;
339
+ + for(auto group:groups)ascii_unicode.push_back(hex_class(group.second)+" "+hex_class(group.first));
340
+ + if(!ascii_unicode.empty())alternatives.push_back(format_literal("\\u00")+" ("+string_join(ascii_unicode," | ")+")");
341
+ + if(negated){
342
+ + const auto u=format_literal("\\u");
343
+ + alternatives.push_back(u+" (\"00\" [89aAbBcCdDeEfF] "+hex+" | \"0\" [1-9a-fA-F] "+hex+"{2} | [1-9a-cA-CeEfF] "+hex+"{3} | [dD] [0-7] "+hex+"{2})");
344
+ + // One surrogate pair encodes one scalar for min/maxLength.
345
+ + alternatives.push_back(u+" [dD] [89aAbB] "+hex+"{2} "+u+" [dD] [c-fC-F] "+hex+"{2}");
346
+ + }
347
+ + if(alternatives.empty()){
348
+ + if(min_len){_errors.push_back("Pattern has no allowed JSON scalar");rule="";return true;}
349
+ + rule=_add_rule(name,format_literal("\"\"")+" space");return true;
350
+ + }
351
+ + const auto encoded_char=_add_rule("json-pattern-char",string_join(alternatives," | "));
352
+ + // Reuse powers of two instead of expanding a {0,4096} quantifier beyond
353
+ + // the native grammar parser's repetition guard. This preserves the bound.
354
+ + std::function<std::string(int)> block = [&](int count) -> std::string {
355
+ + if(count==1)return encoded_char;
356
+ + const auto half=block(count/2);
357
+ + return _add_rule(encoded_char+"-block-"+std::to_string(count),half+" "+half);
358
+ + };
359
+ + std::function<std::string(int)> upto = [&](int count) -> std::string {
360
+ + if(!count)return format_literal("");
361
+ + if(count==1)return _add_rule(encoded_char+"-upto-1",encoded_char+"?");
362
+ + int power=1;while(power<=count/2)power*=2;
363
+ + return _add_rule(encoded_char+"-upto-"+std::to_string(count),upto(power-1)+" | "+block(power)+" "+upto(count-power));
364
+ + };
365
+ + std::string repetition;
366
+ + int remaining=min_len,power=1;
367
+ + while(remaining){if(remaining&1)repetition+=block(power)+" ";remaining/=2;if(remaining)power*=2;}
368
+ + repetition+=schema.contains("maxLength")?upto(max_len-min_len):encoded_char+"*";
369
+ + rule=_add_rule(name,format_literal("\"")+" "+repetition+" "+format_literal("\"")+" space");
370
+ + return true;
371
+ }
372
+
373
+ std::string _visit_pattern(const std::string & pattern, const std::string & name) {
374
+ @@ -959,6 +1068,8 @@
375
+ return _add_rule(rule_name, "\"[\" space " + build_repetition(item_rule_name, min_items, max_items, "\",\" space") + " \"]\" space");
376
+ }
377
+ if ((schema_type.is_null() || schema_type == "string") && schema.contains("pattern")) {
378
+ + std::string encoded;
379
+ + if (_visit_encoded_charclass(schema, rule_name, encoded)) return encoded;
380
+ return _visit_pattern(schema["pattern"], rule_name);
381
+ }
382
+ if ((schema_type.is_null() || schema_type == "string") && std::regex_match(schema_format, std::regex("^uuid[1-5]?$"))) {
383
+ --- a/common/peg-parser.cpp
384
+ +++ b/common/peg-parser.cpp
385
+ @@ -8,6 +8,7 @@
386
+ #include <algorithm>
387
+ #include <initializer_list>
388
+ #include <map>
389
+ +#include <set>
390
+ #include <memory>
391
+ #include <nlohmann/json.hpp>
392
+ #include <regex>
393
+ @@ -93,6 +94,10 @@
394
+ std::vector<uint32_t> next_chars;
395
+ };
396
+
397
+ + // Keep the existing exported trie instantiations in the frozen Windows ABI.
398
+ +#if defined(__clang__) || defined(__GNUC__)
399
+ + __attribute__((used, noinline))
400
+ +#endif
401
+ std::vector<prefix_and_next> collect_prefix_and_next() {
402
+ std::vector<uint32_t> prefix;
403
+ std::vector<prefix_and_next> result;
404
+ @@ -1342,7 +1347,7 @@
405
+ common_peg_parser common_peg_parser_builder::json_array() {
406
+ return rule("json-array", [this]() {
407
+ auto ws = space();
408
+ - auto elements = sequence({json(), zero_or_more(sequence({literal(","), ws, json()}))});
409
+ + auto elements = sequence({json(), zero_or_more(sequence({ws, literal(","), ws, json()}))});
410
+ return sequence({
411
+ literal("["),
412
+ ws,
413
+ @@ -1502,54 +1507,114 @@
414
+ return std::string(buf);
415
+ }
416
+
417
+ -static std::string gbnf_excluding_pattern(const std::vector<std::string> & strings) {
418
+ - trie matcher(strings);
419
+ - auto pieces = matcher.collect_prefix_and_next();
420
+ -
421
+ - std::string pattern;
422
+ - std::string trailing; // optional proper-prefix of a delimiter, allowed only at the very end
423
+ - for (size_t i = 0; i < pieces.size(); ++i) {
424
+ - if (i > 0) {
425
+ - pattern += " | ";
426
+ - }
427
+ -
428
+ - const auto & pre = pieces[i].prefix;
429
+ - const auto & chars = pieces[i].next_chars;
430
+ -
431
+ - std::string cls;
432
+ - cls.reserve(chars.size());
433
+ - for (uint32_t ch : chars) {
434
+ - cls += gbnf_escape_char_class(ch);
435
+ - }
436
+ -
437
+ - if (!pre.empty()) {
438
+ - std::string pre_literal = gbnf_format_literal(common_unicode_cpts_to_utf8(pre));
439
+ - pattern += pre_literal + " [^" + cls + "]";
440
+ - // Each interior alternative consumes a delimiter-prefix plus a disambiguating
441
+ - // char, so the repetition alone cannot match a value that *ends* on a proper
442
+ - // prefix of a delimiter (e.g. a trailing "\n" when the delimiter is
443
+ - // "\n</parameter>\n"). The runtime until() (greedy first-match) accepts such
444
+ - // values, so without this the grammar would reject input the parser accepts.
445
+ - // Allow the value to terminate on any proper prefix as an optional tail.
446
+ - // This makes the grammar a slight superset of the runtime language (a value
447
+ - // may end on the longest prefix, which greedy first-match would not itself
448
+ - // produce); harmless for constrained generation, which only needs to admit
449
+ - // every runtime-valid string.
450
+ - if (!trailing.empty()) {
451
+ - trailing += " | ";
452
+ - }
453
+ - trailing += pre_literal;
454
+ - } else {
455
+ - pattern += "[^" + cls + "]";
456
+ - }
457
+ - }
458
+ -
459
+ - std::string result = "(" + pattern + ")*";
460
+ - if (!trailing.empty()) {
461
+ - result += " (" + trailing + ")?";
462
+ +// Track the longest suffix that is also a delimiter prefix. Resetting at each
463
+ +// prefix/mismatch fragment loses overlaps such as "<<" before "</...>".
464
+ +static std::vector<uint32_t> until_codepoints(const std::string & text) {
465
+ + std::vector<uint32_t> result;
466
+ + for (size_t pos = 0; pos < text.size();) {
467
+ + const auto decoded = common_parse_utf8_codepoint(text, pos);
468
+ + if (decoded.status != utf8_parse_result::SUCCESS) {
469
+ + throw std::invalid_argument("Invalid UTF-8 in PEG delimiter");
470
+ + }
471
+ + result.push_back(decoded.codepoint);
472
+ + pos += decoded.bytes_consumed;
473
+ }
474
+ return result;
475
+ }
476
+ +
477
+ +using until_word = std::vector<uint32_t>;
478
+ +
479
+ +static bool until_has_suffix(const until_word & value, const until_word & suffix) {
480
+ + return value.size() >= suffix.size() &&
481
+ + std::equal(suffix.rbegin(), suffix.rend(), value.rbegin());
482
+ +}
483
+ +
484
+ +static bool until_can_join(const std::vector<std::string> & delimiters, const std::string & close) {
485
+ + if (close.empty() || std::find(delimiters.begin(), delimiters.end(), close) == delimiters.end()) {
486
+ + return false;
487
+ + }
488
+ + const auto close_length = until_codepoints(close).size();
489
+ + // This covers every pending earlier start before this continuation ends.
490
+ + // Longer lookahead-dependent continuations retain their separate grammar;
491
+ + // they cannot be fused by inspecting only these two PEG nodes.
492
+ + return std::all_of(delimiters.begin(), delimiters.end(), [&](const std::string & delimiter) {
493
+ + return until_codepoints(delimiter).size() <= close_length + 1;
494
+ + });
495
+ +}
496
+ +
497
+ +static std::string gbnf_excluding_pattern(const std::vector<std::string> & strings,
498
+ + const common_grammar_builder & builder,
499
+ + const std::string & base,
500
+ + const std::string * close = nullptr) {
501
+ + std::vector<until_word> delimiters;
502
+ + std::set<until_word> prefixes{until_word{}};
503
+ + std::set<uint32_t> alphabet;
504
+ + for (const auto & text : strings) {
505
+ + if (text.empty()) { continue; } // runtime trie does not match an empty word
506
+ + auto word = until_codepoints(text);
507
+ + alphabet.insert(word.begin(), word.end());
508
+ + for (size_t size = 1; size < word.size(); ++size) {
509
+ + prefixes.emplace(word.begin(), word.begin() + size);
510
+ + }
511
+ + delimiters.push_back(std::move(word));
512
+ + }
513
+ + if (delimiters.empty()) { return ".*"; }
514
+ +
515
+ + const std::vector<until_word> states(prefixes.begin(), prefixes.end());
516
+ + auto rule_name = [&](size_t state) { return base + "-q" + std::to_string(state); };
517
+ + std::string excluded;
518
+ + for (auto cp : alphabet) { excluded += gbnf_escape_char_class(cp); }
519
+ + const auto continuation = close ? until_codepoints(*close) : until_word{};
520
+ +
521
+ + for (size_t i = 0; i < states.size(); ++i) {
522
+ + std::vector<std::string> alternatives;
523
+ + bool may_close = true;
524
+ + if (close) {
525
+ + auto joined = states[i];
526
+ + joined.insert(joined.end(), continuation.begin(), continuation.end());
527
+ + // No delimiter may start in the value and complete across its end.
528
+ + for (size_t start = 0; start < states[i].size() && may_close; ++start) {
529
+ + for (const auto & delimiter : delimiters) {
530
+ + if (start + delimiter.size() <= joined.size() &&
531
+ + std::equal(delimiter.begin(), delimiter.end(), joined.begin() + start)) {
532
+ + may_close = false;
533
+ + break;
534
+ + }
535
+ + }
536
+ + }
537
+ + }
538
+ + if (may_close) { alternatives.push_back(close ? gbnf_format_literal(*close) : "\"\""); }
539
+ +
540
+ + std::map<size_t, std::vector<uint32_t>> transitions;
541
+ + for (auto cp : alphabet) {
542
+ + auto next = states[i];
543
+ + next.push_back(cp);
544
+ + if (std::any_of(delimiters.begin(), delimiters.end(), [&](const until_word & delimiter) {
545
+ + return until_has_suffix(next, delimiter);
546
+ + })) { continue; }
547
+ + size_t target = 0, longest = 0;
548
+ + for (size_t j = 1; j < states.size(); ++j) {
549
+ + if (states[j].size() > longest && until_has_suffix(next, states[j])) {
550
+ + target = j;
551
+ + longest = states[j].size();
552
+ + }
553
+ + }
554
+ + transitions[target].push_back(cp);
555
+ + }
556
+ + for (const auto & [target, chars] : transitions) {
557
+ + std::string cls;
558
+ + for (auto cp : chars) { cls += gbnf_escape_char_class(cp); }
559
+ + alternatives.push_back("[" + cls + "] " + rule_name(target));
560
+ + }
561
+ + alternatives.push_back("[^" + excluded + "] " + rule_name(0));
562
+ + const auto name = rule_name(i);
563
+ + if (builder.add_rule(name, string_join(alternatives, " | ")) != name) {
564
+ + throw std::runtime_error("PEG delimiter grammar rule name collision");
565
+ + }
566
+ + }
567
+ + return rule_name(0);
568
+ +}
569
+ +
570
+
571
+ static std::unordered_set<std::string> collect_reachable_rules(
572
+ const common_peg_arena & arena,
573
+ @@ -1658,6 +1723,27 @@
574
+ }
575
+ };
576
+
577
+ + // A rule/tag is transparent for matching an until + literal pair, while
578
+ + // its PEG AST and serialized parser stay unchanged.
579
+ + auto terminal_parser = [&](common_peg_parser_id id) -> const common_peg_parser_variant & {
580
+ + while (true) {
581
+ + const auto & parser = effective_parser(id);
582
+ + if (const auto * rule = std::get_if<common_peg_rule_parser>(&parser)) { id = rule->child; }
583
+ + else if (const auto * ref = std::get_if<common_peg_ref_parser>(&parser)) { id = get_rule(ref->name); }
584
+ + else { return parser; }
585
+ + }
586
+ + };
587
+ + std::map<std::string, std::string> until_grammars;
588
+ + auto until_grammar = [&](const common_peg_until_parser & until, const std::string * close = nullptr) {
589
+ + const auto key = nlohmann::json({until.delimiters, close ? nlohmann::json(*close) : nlohmann::json(nullptr)}).dump();
590
+ + auto found = until_grammars.find(key);
591
+ + if (found != until_grammars.end()) { return found->second; }
592
+ + const auto rule = gbnf_excluding_pattern(until.delimiters, builder,
593
+ + "aitable-until-" + std::to_string(until_grammars.size()), close);
594
+ + until_grammars.emplace(key, rule);
595
+ + return rule;
596
+ + };
597
+ +
598
+ // Generate GBNF for a parser
599
+ std::function<std::string(common_peg_parser_id)> to_gbnf = [&](common_peg_parser_id id) -> std::string {
600
+ const auto & parser = parsers_.at(id);
601
+ @@ -1673,8 +1759,22 @@
602
+ return gbnf_format_literal(p.literal);
603
+ } else if constexpr (std::is_same_v<T, common_peg_sequence_parser>) {
604
+ std::string s;
605
+ - for (const auto & child : p.children) {
606
+ - auto child_gbnf = to_gbnf(child);
607
+ + for (size_t i = 0; i < p.children.size(); ++i) {
608
+ + const auto child = p.children[i];
609
+ + std::string child_gbnf;
610
+ + bool joined = false;
611
+ + if (i + 1 < p.children.size()) {
612
+ + const auto * until = std::get_if<common_peg_until_parser>(&terminal_parser(child));
613
+ + const auto * close = std::get_if<common_peg_literal_parser>(&terminal_parser(p.children[i + 1]));
614
+ + if (until && close && until_can_join(until->delimiters, close->literal)) {
615
+ + // Compile the terminator with the scan: a value suffix
616
+ + // must not create an earlier delimiter across this join.
617
+ + child_gbnf = until_grammar(*until, &close->literal);
618
+ + joined = true;
619
+ + ++i;
620
+ + }
621
+ + }
622
+ + if (!joined) { child_gbnf = to_gbnf(child); }
623
+ if (child_gbnf.empty()) {
624
+ continue;
625
+ }
626
+ @@ -1682,8 +1782,8 @@
627
+ s += " ";
628
+ }
629
+ const auto & child_parser = effective_parser(child);
630
+ - if (std::holds_alternative<common_peg_choice_parser>(child_parser) ||
631
+ - std::holds_alternative<common_peg_sequence_parser>(child_parser)) {
632
+ + if (!joined && (std::holds_alternative<common_peg_choice_parser>(child_parser) ||
633
+ + std::holds_alternative<common_peg_sequence_parser>(child_parser))) {
634
+ s += "(" + child_gbnf + ")";
635
+ } else {
636
+ s += child_gbnf;
637
+ @@ -1765,7 +1865,7 @@
638
+ if (p.delimiters.empty()) {
639
+ return ".*";
640
+ }
641
+ - return gbnf_excluding_pattern(p.delimiters);
642
+ + return until_grammar(p);
643
+ } else if constexpr (std::is_same_v<T, common_peg_schema_parser>) {
644
+ if (schema_delegates(p)) {
645
+ return to_gbnf(p.child);
646
+ --- a/common/sampling.cpp
647
+ +++ b/common/sampling.cpp
648
+ @@ -1,4 +1,5 @@
649
+ #include "sampling.h"
650
+ +#include "nlohmann/json.hpp"
651
+
652
+ #include "common.h"
653
+ #include "fit.h"
654
+ @@ -109,6 +110,17 @@
655
+ std::vector<T> data;
656
+ };
657
+
658
+ +struct sampling_diagnostic_state {
659
+ + uint64_t sample_steps = 0, grammar_mask_calls = 0, grammar_single_checks = 0;
660
+ + uint64_t skip_no_grammar = 0, skip_reasoning = 0, grammar_accepts = 0, generated_accepts = 0;
661
+ + uint64_t grammar_checked_steps = 0, skip_sampler_error = 0, skip_other = 0;
662
+ + uint64_t invalid_selections = 0;
663
+ + size_t prefill_tokens = 0;
664
+ + bool prefill_applied = false;
665
+ + std::string failure_reason;
666
+ + std::vector<llama_token> eog_probes;
667
+ +};
668
+ +
669
+ struct common_sampler {
670
+ common_params_sampling params;
671
+
672
+ @@ -121,6 +133,7 @@
673
+ std::vector<llama_token_data> cur;
674
+
675
+ llama_token_data_array cur_p;
676
+ + sampling_diagnostic_state diagnostic;
677
+
678
+ void reset() {
679
+ prev.clear();
680
+ @@ -409,6 +422,13 @@
681
+ /* .cur_p = */ {},
682
+ };
683
+
684
+ + result->diagnostic.prefill_tokens = prefill_tokens.size();
685
+ + result->diagnostic.prefill_applied = grmr && !params.grammar_lazy && common_grammar_needs_prefill(params.grammar);
686
+ + for (const auto id : {llama_vocab_eos(vocab), llama_vocab_eot(vocab)}) {
687
+ + if (id >= 0 && std::find(result->diagnostic.eog_probes.begin(), result->diagnostic.eog_probes.end(), id) == result->diagnostic.eog_probes.end()) {
688
+ + result->diagnostic.eog_probes.push_back(id);
689
+ + }
690
+ + }
691
+ return result;
692
+ }
693
+
694
+ @@ -448,6 +468,8 @@
695
+
696
+ // grammar_should_apply() checks the reasoning budget state, so calculate this before we accept
697
+ const auto accept_grammar = is_generated && grammar_should_apply(gsmpl);
698
+ + if (is_generated) { ++gsmpl->diagnostic.generated_accepts; }
699
+ + if (accept_grammar) { ++gsmpl->diagnostic.grammar_accepts; }
700
+
701
+ if (gsmpl->rbudget && is_generated) {
702
+ llama_sampler_accept(gsmpl->rbudget, token);
703
+ @@ -479,6 +501,7 @@
704
+ /* .prev = */ gsmpl->prev,
705
+ /* .cur = */ gsmpl->cur,
706
+ /* .cur_p = */ gsmpl->cur_p,
707
+ + /* .diagnostic = */ gsmpl->diagnostic,
708
+ };
709
+ }
710
+
711
+ @@ -536,41 +559,66 @@
712
+ return gsmpl->chain;
713
+ }
714
+
715
+ -static llama_token common_sampler_validate_or_select_fallback(
716
+ - const char * func,
717
+ - struct llama_context * ctx,
718
+ - llama_token_data_array & cur_p,
719
+ - llama_token id) {
720
+ - const llama_vocab * vocab = llama_model_get_vocab(llama_get_model(ctx));
721
+ - const int32_t n_vocab = llama_vocab_n_tokens(vocab);
722
+ -
723
+ - if (id >= 0 && id < n_vocab) {
724
+ +static llama_token common_sampler_validate_selected(
725
+ + const char *, struct llama_context * ctx, llama_token_data_array & cur_p, llama_token id, common_sampler * gsmpl) {
726
+ + const int32_t n_vocab = llama_vocab_n_tokens(llama_model_get_vocab(llama_get_model(ctx)));
727
+ + const bool selected_in_range = cur_p.data != nullptr && cur_p.selected >= 0 &&
728
+ + static_cast<size_t>(cur_p.selected) < cur_p.size;
729
+ + // Normal selection remains O(1). Masked alternatives are expected and do not
730
+ + // invalidate a finite selected token. Failure classification alone scans candidates.
731
+ + if (selected_in_range && id >= 0 && id < n_vocab &&
732
+ + cur_p.data[cur_p.selected].id == id && std::isfinite(cur_p.data[cur_p.selected].logit)) {
733
+ return id;
734
+ }
735
+ -
736
+ static std::atomic<int> n_warn_invalid_sample { 0 };
737
+ const int n_warn = n_warn_invalid_sample.fetch_add(1, std::memory_order_relaxed);
738
+ if (n_warn < 16) {
739
+ - LOG_WRN("%s: sampler selected invalid token %d (n_vocab=%d); selecting a valid fallback candidate\n",
740
+ - func, id, n_vocab);
741
+ - } else {
742
+ - LOG_DBG("%s: sampler selected invalid token %d (n_vocab=%d); selecting a valid fallback candidate\n",
743
+ - func, id, n_vocab);
744
+ - }
745
+ -
746
+ - for (size_t i = 0; i < cur_p.size; ++i) {
747
+ - const llama_token id_fallback = cur_p.data[i].id;
748
+ - if (id_fallback >= 0 && id_fallback < n_vocab) {
749
+ - cur_p.selected = i;
750
+ - return id_fallback;
751
+ - }
752
+ - }
753
+ -
754
+ - cur_p.selected = -1;
755
+ - return LLAMA_TOKEN_NULL;
756
+ + LOG_WRN("sampling_error: refusing invalid selection (token=%d, n_vocab=%d)\n", id, n_vocab);
757
+ + }
758
+ + auto fail = [&](const char * reason) -> llama_token {
759
+ + ++gsmpl->diagnostic.invalid_selections;
760
+ + gsmpl->diagnostic.failure_reason = reason;
761
+ + throw common_sampler_error(reason);
762
+ + };
763
+ + bool available = false;
764
+ + if (cur_p.data != nullptr) {
765
+ + for (size_t i = 0; i < cur_p.size; ++i) {
766
+ + const auto & item = cur_p.data[i];
767
+ + if (std::isnan(item.logit) || item.logit == INFINITY) {
768
+ + return fail("nonfinite_candidate_logits");
769
+ + }
770
+ + available |= item.id >= 0 && item.id < n_vocab && std::isfinite(item.logit);
771
+ + }
772
+ + }
773
+ + if (!available) { return fail("no_available_token"); }
774
+ + if (!selected_in_range) { return fail("invalid_selected_index"); }
775
+ + if (id < 0 || id >= n_vocab) { return fail("invalid_selected_token"); }
776
+ + if (cur_p.data[cur_p.selected].id != id) { return fail("selected_token_mismatch"); }
777
+ + return fail("masked_selected_token");
778
+ +}
779
+ +
780
+ +static llama_token common_sampler_selected_id(const llama_token_data_array & cur_p) {
781
+ + if (cur_p.data == nullptr || cur_p.selected < 0 || static_cast<size_t>(cur_p.selected) >= cur_p.size) {
782
+ + return LLAMA_TOKEN_NULL;
783
+ + }
784
+ + return cur_p.data[cur_p.selected].id;
785
+ }
786
+
787
+ llama_token common_sampler_sample(struct common_sampler * gsmpl, struct llama_context * ctx, int idx, bool grammar_first) {
788
+ + ++gsmpl->diagnostic.sample_steps;
789
+ + struct diagnostic_step_scope {
790
+ + common_sampler * smpl;
791
+ + uint64_t calls, failures;
792
+ + ~diagnostic_step_scope() {
793
+ + auto & d = smpl->diagnostic;
794
+ + if (d.grammar_mask_calls + d.grammar_single_checks != calls) { ++d.grammar_checked_steps; }
795
+ + else if (!smpl->grmr) { ++d.skip_no_grammar; }
796
+ + else if (d.invalid_selections != failures) { ++d.skip_sampler_error; }
797
+ + else if (!grammar_should_apply(smpl)) { ++d.skip_reasoning; }
798
+ + else { ++d.skip_other; }
799
+ + }
800
+ + } diagnostic_scope {gsmpl,gsmpl->diagnostic.grammar_mask_calls + gsmpl->diagnostic.grammar_single_checks,
801
+ + gsmpl->diagnostic.invalid_selections};
802
+ llama_synchronize(ctx);
803
+
804
+ // start measuring sampling time after the llama_context synchronization in order to not measure any ongoing async operations
805
+ @@ -606,22 +654,8 @@
806
+ }
807
+ }
808
+
809
+ - id = common_sampler_validate_or_select_fallback(__func__, ctx, cur_p, id);
810
+ - if (id == id_backend) {
811
+ - return id;
812
+ - }
813
+ -
814
+ - static std::atomic<int> n_warn_backend_sample_invalid { 0 };
815
+ - const int n_warn = n_warn_backend_sample_invalid.fetch_add(1, std::memory_order_relaxed);
816
+ - if (n_warn < 16) {
817
+ - LOG_WRN("%s: backend sampler returned a token outside the current candidates (id=%d, found=%d); falling back to CPU sampler\n",
818
+ - __func__, id_backend, (int) found);
819
+ - } else {
820
+ - LOG_DBG("%s: backend sampler returned a token outside the current candidates (id=%d, found=%d); falling back to CPU sampler\n",
821
+ - __func__, id_backend, (int) found);
822
+ - }
823
+ - cur_p.selected = -1;
824
+ - gsmpl->set_logits(ctx, idx, false);
825
+ + return common_sampler_validate_selected(__func__, ctx, cur_p, id, gsmpl);
826
+ +
827
+ }
828
+ }
829
+
830
+ @@ -629,30 +663,14 @@
831
+ llama_sampler_apply(rbudget, &cur_p);
832
+
833
+ if (grammar_first && grammar_should_apply(gsmpl)) {
834
+ + ++gsmpl->diagnostic.grammar_mask_calls;
835
+ llama_sampler_apply(grmr, &cur_p);
836
+ }
837
+
838
+ llama_sampler_apply(chain, &cur_p);
839
+
840
+ - id = cur_p.data[cur_p.selected].id;
841
+ - id = common_sampler_validate_or_select_fallback(__func__, ctx, cur_p, id);
842
+ - if (id == LLAMA_TOKEN_NULL) {
843
+ - gsmpl->set_logits(ctx, idx, false);
844
+ -
845
+ - llama_sampler_apply(rbudget, &cur_p);
846
+ -
847
+ - if (grammar_first && grammar_should_apply(gsmpl)) {
848
+ - llama_sampler_apply(grmr, &cur_p);
849
+ - }
850
+ -
851
+ - llama_sampler_apply(chain, &cur_p);
852
+ -
853
+ - GGML_ASSERT(cur_p.selected != -1 && "no selected token during sampling - check your sampling configuration");
854
+ -
855
+ - id = cur_p.data[cur_p.selected].id;
856
+ - id = common_sampler_validate_or_select_fallback(__func__, ctx, cur_p, id);
857
+ - return id;
858
+ - }
859
+ + id = common_sampler_selected_id(cur_p);
860
+ + id = common_sampler_validate_selected(__func__, ctx, cur_p, id, gsmpl);
861
+
862
+ if (grammar_first || !grammar_should_apply(gsmpl)) {
863
+ return id;
864
+ @@ -663,6 +681,7 @@
865
+ llama_token_data single_token_data = { id, 1.0f, 0.0f };
866
+ llama_token_data_array single_token_data_array = { &single_token_data, 1, -1, false };
867
+
868
+ + ++gsmpl->diagnostic.grammar_single_checks;
869
+ llama_sampler_apply(grmr, &single_token_data_array);
870
+
871
+ const bool is_valid = single_token_data_array.data[0].logit != -INFINITY;
872
+ @@ -678,6 +697,7 @@
873
+ llama_sampler_apply(rbudget, &cur_p);
874
+
875
+ if (grammar_should_apply(gsmpl)) {
876
+ + ++gsmpl->diagnostic.grammar_mask_calls;
877
+ llama_sampler_apply(grmr, &cur_p);
878
+ }
879
+
880
+ @@ -685,8 +705,8 @@
881
+
882
+ GGML_ASSERT(cur_p.selected != -1 && "no selected token during sampling - check your sampling configuration");
883
+
884
+ - id = cur_p.data[cur_p.selected].id;
885
+ - id = common_sampler_validate_or_select_fallback(__func__, ctx, cur_p, id);
886
+ + id = common_sampler_selected_id(cur_p);
887
+ + id = common_sampler_validate_selected(__func__, ctx, cur_p, id, gsmpl);
888
+
889
+ return id;
890
+ }
891
+ @@ -965,3 +985,56 @@
892
+
893
+ return samplers;
894
+ }
895
+ +
896
+ +std::string common_sampler_diagnostics_json(struct common_sampler * gsmpl) {
897
+ + using json = nlohmann::ordered_json;
898
+ + if (!gsmpl) { return json({{"schema","aitable.native_sampling@1"},{"sampler_attached",false}}).dump(); }
899
+ + const auto & d = gsmpl->diagnostic;
900
+ + const auto & grammar = common_grammar_value(gsmpl->params.grammar);
901
+ + uint64_t hash = 14695981039346656037ull;
902
+ + for (unsigned char c : grammar) { hash = (hash ^ c) * 1099511628211ull; }
903
+ + char hash_text[17]; snprintf(hash_text,sizeof(hash_text),"%016llx",static_cast<unsigned long long>(hash));
904
+ + json out = {{"schema","aitable.native_sampling@1"},{"sampler_attached",true},
905
+ + {"grammar_configured",!grammar.empty()},{"grammar_attached",gsmpl->grmr!=nullptr},
906
+ + {"grammar_lazy",gsmpl->params.grammar_lazy},{"grammar_hash_alg","fnv1a64"},{"grammar_hash",hash_text},
907
+ + {"generation_prompt_bytes",gsmpl->params.generation_prompt.size()},{"prefill_tokens",d.prefill_tokens},
908
+ + {"prefill_applied",d.prefill_applied},{"ignore_eos",gsmpl->params.ignore_eos},
909
+ + {"sample_steps",d.sample_steps},{"grammar_full_mask_calls",d.grammar_mask_calls},
910
+ + {"grammar_single_checks",d.grammar_single_checks},{"skip_no_grammar_steps",d.skip_no_grammar},
911
+ + {"skip_reasoning_steps",d.skip_reasoning},{"generated_accepts",d.generated_accepts},
912
+ + {"grammar_checked_steps",d.grammar_checked_steps},{"skip_sampler_error_steps",d.skip_sampler_error},
913
+ + {"skip_other_steps",d.skip_other},
914
+ + {"grammar_accepts",d.grammar_accepts},{"invalid_selections",d.invalid_selections},
915
+ + {"failure_reason",d.failure_reason.empty()?json(nullptr):json(d.failure_reason)},
916
+ + {"grammar_should_apply_now",grammar_should_apply(gsmpl)},
917
+ + {"eog_probe_scope","eos_eot_only"},{"eog_probe_count",d.eog_probes.size()},
918
+ + {"grammar_allows_probe_eog",nullptr},{"last_chain_probe_eog_present",nullptr},
919
+ + {"last_chain_probe_eog_finite",nullptr},{"lazy_trigger_state","not_observed"}};
920
+ + if (!d.eog_probes.empty()) {
921
+ + bool present=false,finite=false;
922
+ + for (size_t i=0; gsmpl->cur_p.data && i<gsmpl->cur_p.size; ++i) {
923
+ + for (auto id:d.eog_probes) if (gsmpl->cur_p.data[i].id==id) {
924
+ + present=true;finite|=std::isfinite(gsmpl->cur_p.data[i].logit);
925
+ + }
926
+ + }
927
+ + out["last_chain_probe_eog_present"]=present;
928
+ + out["last_chain_probe_eog_finite"]=present?json(finite):json(nullptr);
929
+ + if (gsmpl->grmr) {
930
+ + std::vector<llama_token_data> probe;
931
+ + for (auto id:d.eog_probes) probe.push_back({id,0.0f,0.0f});
932
+ + llama_token_data_array a{probe.data(),probe.size(),-1,false};
933
+ + llama_sampler_apply(gsmpl->grmr,&a);
934
+ + bool allowed=false;for(const auto & item:probe) allowed|=std::isfinite(item.logit);
935
+ + out["grammar_allows_probe_eog"]=allowed;
936
+ + }
937
+ + }
938
+ + out["diagnostic_saturated"]=false;
939
+ + for (const char * key : {"generation_prompt_bytes","prefill_tokens","sample_steps","grammar_full_mask_calls",
940
+ + "grammar_single_checks","skip_no_grammar_steps","skip_reasoning_steps","generated_accepts",
941
+ + "grammar_accepts","invalid_selections","grammar_checked_steps","skip_sampler_error_steps","skip_other_steps"}) {
942
+ + if (out[key].get<uint64_t>() > 2147483647u) {
943
+ + out[key]=2147483647u;out["diagnostic_saturated"]=true;
944
+ + }
945
+ + }
946
+ + return out.dump();
947
+ +}
948
+ --- a/common/sampling.h
949
+ +++ b/common/sampling.h
950
+ @@ -5,6 +5,7 @@
951
+ #include "common.h"
952
+
953
+ #include <string>
954
+ +#include <stdexcept>
955
+ #include <vector>
956
+
957
+ // common_sampler extends llama_sampler with additional functionality:
958
+ @@ -31,6 +32,14 @@
959
+ //
960
+ // TODO: measure grammar performance
961
+ //
962
+ +
963
+ +// Sampling failure is terminal for this generation. The finite code contains no model text.
964
+ +class common_sampler_error : public std::runtime_error {
965
+ +public:
966
+ + explicit common_sampler_error(const char * reason)
967
+ + : std::runtime_error(std::string("sampling_error: ") + reason), code(reason) {}
968
+ + const std::string code;
969
+ +};
970
+
971
+ struct common_sampler;
972
+
973
+ @@ -121,3 +130,6 @@
974
+ };
975
+
976
+ typedef std::unique_ptr<common_sampler, common_sampler_deleter> common_sampler_ptr;
977
+ +
978
+ +// Finite, no-text snapshot. Called only when a generation finishes.
979
+ +std::string common_sampler_diagnostics_json(struct common_sampler * gsmpl);
980
+ --- a/ggml/src/ggml-cuda/fattn-common.cuh
981
+ +++ b/ggml/src/ggml-cuda/fattn-common.cuh
982
+ @@ -1719,7 +1719,7 @@
983
+ const int tiles_nwaves = (ntiles_dst + max_blocks - 1) / max_blocks;
984
+ const int tiles_efficiency_percent = 100 * ntiles_dst / (max_blocks*tiles_nwaves);
985
+
986
+ - const bool use_stream_k = cc >= GGML_CUDA_CC_ADA_LOVELACE || amd_wmma_available(cc) || tiles_efficiency_percent < 75;
987
+ + const bool use_stream_k = (GGML_CUDA_CC_IS_NVIDIA(cc) && cc >= GGML_CUDA_CC_ADA_LOVELACE) || (amd_wmma_available(cc) && Q->ne[0] == 64) || tiles_efficiency_percent < 75;
988
+
989
+ blocks_num.x = ntiles_dst;
990
+ blocks_num.y = 1;
991
+ --- a/ggml/src/ggml-cuda/fattn-mma-f16.cuh
992
+ +++ b/ggml/src/ggml-cuda/fattn-mma-f16.cuh
993
+ @@ -157,8 +157,8 @@
994
+
995
+ GGML_CUDA_FATTN_MMA_CONFIG_CASE(256, 256, 8, 64, 2, 32, 128, 128, 128, 1, true);
996
+ GGML_CUDA_FATTN_MMA_CONFIG_CASE(256, 256, 16, 64, 2, 32, 128, 128, 128, 1, true);
997
+ - GGML_CUDA_FATTN_MMA_CONFIG_CASE(256, 256, 32, 128, 2, 64, 128, 128, 64, 1, true);
998
+ - GGML_CUDA_FATTN_MMA_CONFIG_CASE(256, 256, 64, 128, 2, 64, 128, 128, 64, 1, true);
999
+ + GGML_CUDA_FATTN_MMA_CONFIG_CASE(256, 256, 32, 256, 2, 64, 128, 128, 64, 1, true);
1000
+ + GGML_CUDA_FATTN_MMA_CONFIG_CASE(256, 256, 64, 256, 2, 64, 128, 128, 64, 1, true);
1001
+
1002
+ GGML_CUDA_FATTN_MMA_CONFIG_CASE(320, 256, 32, 128, 2, 32, 160, 128, 128, 1, true);
1003
+ GGML_CUDA_FATTN_MMA_CONFIG_CASE(320, 256, 64, 128, 2, 32, 160, 128, 128, 1, true);
1004
+ @@ -359,6 +359,39 @@
1005
+ }
1006
+
1007
+ // ------------------------------------------------------------------------------------------------------------------
1008
+ +
1009
+ +
1010
+ +// F16 global V -> transposed LDS [D, K]. Only the guarded RDNA4 path calls this.
1011
+ +// The original temporary dequantization and tensor strides remain unchanged.
1012
+ +template<int KB, int DB, int NW, int LDS_STRIDE>
1013
+ +static __device__ __forceinline__ void fpx_rdna4_load_v_transposed(
1014
+ + const half2 * src, half2 * dst, const int stride_src) {
1015
+ +#if defined(__gfx1201__)
1016
+ + using half8 = __fp16 __attribute__((ext_vector_type(8)));
1017
+ + constexpr int stride_dst = LDS_STRIDE; // half2 units; caller proves pool capacity
1018
+ + constexpr int dtiles = (DB + 31)/32;
1019
+ + const int lane = threadIdx.x % 32;
1020
+ + for (int tile = threadIdx.y; tile < (KB/16)*dtiles; tile += NW) {
1021
+ + const int kd = (tile/dtiles)*16;
1022
+ + const int dd = (tile%dtiles)*32;
1023
+ +#pragma unroll
1024
+ + for (int part = 0; part < 2; ++part) {
1025
+ + const half * address = (const half *)(src + (kd+part*8+lane/4)*stride_src) + dd+(lane%4)*8;
1026
+ + const half8 values = __builtin_amdgcn_global_load_tr_b128_v8f16(
1027
+ + (__attribute__((address_space(1))) half8 *)address);
1028
+ +#pragma unroll
1029
+ + for (int e=0;e<8;++e) {
1030
+ + const int d = dd+(e%4)*8+lane%8;
1031
+ + const int k = kd+part*8+(lane/8)*2+e/4;
1032
+ + ((half *)dst)[d*(2*stride_dst)+k] = ((const half *)&values)[e];
1033
+ + }
1034
+ + }
1035
+ + }
1036
+ +#else
1037
+ + GGML_UNUSED_VARS(src,dst,stride_src);
1038
+ + NO_DEVICE_CODE;
1039
+ +#endif
1040
+ +}
1041
+
1042
+ template<int stride_tile, int nwarps, int nbatch_fa, bool use_cp_async, bool oob_check>
1043
+ static __device__ __forceinline__ void flash_attn_ext_f16_load_tile(
1044
+ @@ -949,6 +982,19 @@
1045
+ }
1046
+
1047
+
1048
+ +
1049
+ + // No extra LDS allocation: the transposed tile must fit the existing K/V pool.
1050
+ + constexpr int fpx_tr_capacity = nbatch_fa*(stride_tile_K>stride_tile_V ? stride_tile_K : stride_tile_V)/(2*nbatch_V2);
1051
+ + constexpr int fpx_tr_stride = fpx_tr_capacity < nbatch_fa/2+4 ? fpx_tr_capacity : nbatch_fa/2+4;
1052
+ +#if defined(__gfx1201__)
1053
+ + constexpr bool fpx_tr_eligible = DKQ==256 && DV==256 && ncols2>1 && nstages==0 && !V_is_K_view && !oob_check &&
1054
+ + nbatch_fa%16==0 && nbatch_V2%16==0 &&
1055
+ + (2*nbatch_V2)*fpx_tr_stride <= nbatch_fa*(stride_tile_K>stride_tile_V ? stride_tile_K : stride_tile_V);
1056
+ + const bool fpx_use_tr_v = fpx_tr_eligible && stride_V%4==0 && (reinterpret_cast<uintptr_t>(V_h2)&15)==0;
1057
+ +#else
1058
+ + constexpr bool fpx_use_tr_v = false;
1059
+ +#endif
1060
+ +
1061
+ // Calculate VKQ tile, need to use logical rather than physical elements for i0 due to transposition of V:
1062
+ #pragma unroll
1063
+ for (int i0_start = 0; i0_start < DV; i0_start += 2*nbatch_V2) {
1064
+ @@ -959,8 +1005,13 @@
1065
+ if constexpr (nstages <= 1) {
1066
+ if (!V_is_K_view || i0_stop > 2*nbatch_K2) {
1067
+ constexpr bool use_cp_async = nstages == 1;
1068
+ + if (fpx_use_tr_v) {
1069
+ + fpx_rdna4_load_v_transposed<nbatch_fa, 2*nbatch_V2, nwarps, fpx_tr_stride>(
1070
+ + V_h2 + int64_t(k_VKQ_0)*stride_V + i0_start/2, tile_V, stride_V);
1071
+ + } else {
1072
+ flash_attn_ext_f16_load_tile<stride_tile_V, nwarps, nbatch_fa, use_cp_async, oob_check>
1073
+ (V_h2 + int64_t(k_VKQ_0)*stride_V + i0_start/2, tile_V, i0_diff/2, stride_V, k_VKQ_sup);
1074
+ + }
1075
+ if (use_cp_async) {
1076
+ cp_async_wait_all();
1077
+ }
1078
+ @@ -978,7 +1029,14 @@
1079
+ const int k0 = k00 + (threadIdx.y % np)*T_A_VKQ::J;
1080
+
1081
+ T_A_VKQ A; // Transposed in SRAM but not in registers, gets transposed on load.
1082
+ + if (fpx_use_tr_v) {
1083
+ +#pragma unroll
1084
+ + for (int l=0;l<A.ne;++l) {
1085
+ + A.x[l] = tile_V[(i_VKQ_0-i0_start+A.get_i(l))*fpx_tr_stride+k0+A.get_j(l)];
1086
+ + }
1087
+ + } else {
1088
+ load_ldmatrix_trans(A, tile_V_i + 2*k0*stride_tile_V + (i_VKQ_0 - i0_start)/2, stride_tile_V);
1089
+ + }
1090
+ if constexpr (T_B_KQ::I == 8) {
1091
+ mma(VKQ_C[i_VKQ_0/T_A_VKQ::I], A, B[k00/(np*T_A_VKQ::J)]);
1092
+ } else {
1093
+ @@ -1750,7 +1808,7 @@
1094
+ #endif // __CUDA_ARCH__ == GGML_CUDA_CC_TURING
1095
+
1096
+ #if defined(AMD_WMMA_AVAILABLE)
1097
+ - if (ncols1*ncols2 < 16 || ncols2 == 1 || DKQ > 128) {
1098
+ + if (ncols1*ncols2 < 16 || ncols2 == 1 || DKQ > 256) {
1099
+ NO_DEVICE_CODE;
1100
+ return;
1101
+ }
1102
+ --- a/ggml/src/ggml-cuda/fattn.cu
1103
+ +++ b/ggml/src/ggml-cuda/fattn.cu
1104
+ @@ -7,6 +7,52 @@
1105
+ #include "fattn.cuh"
1106
+ #include "convert.cuh"
1107
+
1108
+ +#ifdef GGML_USE_HIP
1109
+ +extern "C" __declspec(dllimport) float ck_attention_bias(void*,void*,void*,void*,void*,void*,int,int,int,int,int,float,void*,int);
1110
+ +static bool ggml_try_ck_attention(ggml_backend_cuda_context &ctx, ggml_tensor *dst) {
1111
+ + const char *enable = getenv("GGML_HIP_CK_ATTENTION");
1112
+ + if (!enable || strcmp(enable,"1")) return false;
1113
+ + const auto *q=dst->src[0], *k=dst->src[1], *v=dst->src[2], *m=dst->src[3];
1114
+ + float scale,max_bias,softcap;
1115
+ + memcpy(&scale,dst->op_params,sizeof(float));
1116
+ + memcpy(&max_bias,(const float*)dst->op_params+1,sizeof(float));
1117
+ + memcpy(&softcap,(const float*)dst->op_params+2,sizeof(float));
1118
+ + // Experimental narrow boundary: prefill only, one sequence, standard additive mask.
1119
+ + if(q->type!=GGML_TYPE_F32 || q->ne[0]!=256 || q->ne[1]<64 || q->ne[3]!=1 ||
1120
+ + k->ne[0]!=256 || v->ne[0]!=256 || k->ne[3]!=1 || v->ne[3]!=1 ||
1121
+ + k->ne[1]!=v->ne[1] || k->ne[2]!=v->ne[2] || k->ne[1]<q->ne[1] ||
1122
+ + q->ne[2]%k->ne[2] || dst->src[4] || max_bias!=0 || softcap!=0 ||
1123
+ + !ggml_is_contiguous(dst) || dst->type!=GGML_TYPE_F32) return false;
1124
+ + if(k->type!=GGML_TYPE_Q8_0) return false;
1125
+ + if(v->type!=GGML_TYPE_Q8_0) return false;
1126
+ + if(m && (m->type!=GGML_TYPE_F16 || m->ne[2]!=1 || m->ne[3]!=1 ||
1127
+ + m->nb[0]!=sizeof(half) || m->ne[0]<k->ne[1] || m->ne[1]<q->ne[1])) return false;
1128
+ + for(auto *t:{q,k,v}) if(t->nb[0]!=ggml_type_size(t->type)) return false;
1129
+ + ggml_cuda_pool_alloc<half> qp(ctx.pool()),kp(ctx.pool()),vp(ctx.pool()),op(ctx.pool());
1130
+ + ggml_cuda_pool_alloc<float> lse(ctx.pool());
1131
+ + auto stream=ctx.stream();
1132
+ + auto pack=[&](const ggml_tensor *t,ggml_cuda_pool_alloc<half>&buf) {
1133
+ + buf.alloc(ggml_nelements(t));
1134
+ + auto convert=ggml_get_to_fp16_nc_cuda(t->type);
1135
+ + GGML_ASSERT(convert);
1136
+ + const size_t ts=ggml_type_size(t->type);
1137
+ + convert(t->data,buf.ptr,t->ne[0],t->ne[1],t->ne[2],t->ne[3],t->nb[1]/ts,t->nb[2]/ts,t->nb[3]/ts,stream);
1138
+ + };
1139
+ + pack(q,qp);pack(k,kp);pack(v,vp);
1140
+ + op.alloc(ggml_nelements(dst));lse.alloc(q->ne[1]*q->ne[2]);
1141
+ + static int reports=0;
1142
+ + int log=reports<8?1:0;
1143
+ + if(log) {fprintf(stderr,"\nCK_ATTENTION_DISPATCH q=%lld k=%lld h=%lld hk=%lld mask=%d\n",(long long)q->ne[1],(long long)k->ne[1],(long long)q->ne[2],(long long)k->ne[2],m!=nullptr);++reports;}
1144
+ + float ret=ck_attention_bias(qp.ptr,kp.ptr,vp.ptr,op.ptr,lse.ptr,m?m->data:nullptr,
1145
+ + q->ne[1],k->ne[1],q->ne[2],k->ne[2],m?m->nb[1]/sizeof(half):0,scale,stream,0);
1146
+ + if(ret<0) GGML_ABORT("CK attention rejected eligible FPX tensor: %f",ret);
1147
+ + CUDA_CHECK(cudaGetLastError());
1148
+ + ggml_get_to_fp32_cuda(GGML_TYPE_F16)(op.ptr,(float*)dst->data,ggml_nelements(dst),stream);
1149
+ + return true;
1150
+ +}
1151
+ +#endif
1152
+ +
1153
+ +
1154
+ template <int DKQ, int DV, int ncols2>
1155
+ static void ggml_cuda_flash_attn_ext_mma_f16_switch_ncols1(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
1156
+ const int cc = ggml_cuda_info().devices[ggml_cuda_get_device()].cc;
1157
+ @@ -90,6 +136,12 @@
1158
+ }
1159
+ }
1160
+
1161
+ + // PR28102: RDNA GQA divisibility routing, preserving Volta above.
1162
+ + if (amd_wmma_available(cc)) {
1163
+ + if (use_gqa_opt && gqa_ratio % 8 == 0) { ggml_cuda_flash_attn_ext_mma_f16_switch_ncols1<DKQ, DV, 8>(ctx,dst); return; }
1164
+ + if (use_gqa_opt && gqa_ratio % 4 == 0) { ggml_cuda_flash_attn_ext_mma_f16_switch_ncols1<DKQ, DV, 4>(ctx,dst); return; }
1165
+ + if (use_gqa_opt && gqa_ratio % 2 == 0) { ggml_cuda_flash_attn_ext_mma_f16_switch_ncols1<DKQ, DV, 2>(ctx,dst); return; }
1166
+ + }
1167
+ if (use_gqa_opt && gqa_ratio > 4) {
1168
+ ggml_cuda_flash_attn_ext_mma_f16_switch_ncols1<DKQ, DV, 8>(ctx, dst);
1169
+ return;
1170
+ @@ -682,6 +734,12 @@
1171
+ return BEST_FATTN_KERNEL_MMA_F16;
1172
+ }
1173
+
1174
+ + // PR28102 compatibility: this older fork has a legacy rocWMMA early return.
1175
+ + // Let only the newly supported RDNA head256/GQA shapes reach native MMA first.
1176
+ + if (amd_wmma_available(cc) && gqa_opt_applies && Q->ne[0] == 256 && Q->ne[1] * gqa_ratio_eff > 16) {
1177
+ + return BEST_FATTN_KERNEL_MMA_F16;
1178
+ + }
1179
+ +
1180
+ // Use the WMMA kernel if possible:
1181
+ if (ggml_cuda_should_use_wmma_fattn(cc) && K->ne[1] % FATTN_KQ_STRIDE == 0 && Q->ne[0] != 40 && Q->ne[0] != 72 && Q->ne[0] != 192 && Q->ne[0] != 512 && Q->ne[0] != 576) {
1182
+ if (can_use_vector_kernel && Q->ne[1] <= 2) {
1183
+ @@ -704,7 +762,7 @@
1184
+ }
1185
+
1186
+ // AMD WMMA is always faster than the tile kernel if the full tile width of 16 can be utilized.
1187
+ - if ((amd_wmma_available(cc) && gqa_opt_applies && Q->ne[0] <= 128) && Q->ne[0] != 40 && Q->ne[0] != 72 && Q->ne[1] * gqa_ratio_eff > 8) {
1188
+ + if ((amd_wmma_available(cc) && gqa_opt_applies && Q->ne[0] <= 256) && Q->ne[0] != 40 && Q->ne[0] != 72 && Q->ne[1] * gqa_ratio_eff > (Q->ne[0] <= 128 ? 8 : 16)) {
1189
+ return BEST_FATTN_KERNEL_MMA_F16;
1190
+ }
1191
+
1192
+ @@ -761,7 +819,18 @@
1193
+
1194
+ void ggml_cuda_flash_attn_ext(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
1195
+ ggml_cuda_set_device(ctx.device);
1196
+ -
1197
+ +#ifdef GGML_USE_HIP
1198
+ + if (ggml_try_ck_attention(ctx,dst)) return;
1199
+ +#endif
1200
+ +
1201
+ +#ifdef GGML_USE_HIP
1202
+ + static int dispatch_reports = 0;
1203
+ + if (dispatch_reports < 8) {
1204
+ + const auto selected = ggml_cuda_get_best_fattn_kernel(ggml_cuda_get_device(), dst);
1205
+ + fprintf(stderr,"FA28102_DISPATCH kernel=%d mma=%d q=%lld k=%lld head=%lld\n",int(selected),int(selected==BEST_FATTN_KERNEL_MMA_F16),(long long)dst->src[0]->ne[1],(long long)dst->src[1]->ne[1],(long long)dst->src[0]->ne[0]);
1206
+ + ++dispatch_reports;
1207
+ + }
1208
+ +#endif
1209
+ const ggml_tensor * K = dst->src[1];
1210
+ const ggml_tensor * V = dst->src[2];
1211
+
1212
+ --- a/ggml/src/ggml-hip/CMakeLists.txt
1213
+ +++ b/ggml/src/ggml-hip/CMakeLists.txt
1214
+ @@ -180,3 +180,10 @@
1215
+ # argmax on RDNA3.5, making MTP speculative decode diverge from the
1216
+ # non-speculative baseline. gfx1151 is RDNA3.5, and -ffinite-math-only also
1217
+ # breaks inf handling outright (EXPM1 returned NaN where the CPU returns inf).
1218
+ +
1219
+ +# CK forward bridge with explicit supported-shape dispatch.
1220
+ +set(FPX_CK_IMPORT_LIBRARY "" CACHE FILEPATH "Import library for the Windows CK attention bridge")
1221
+ +if (NOT EXISTS "${FPX_CK_IMPORT_LIBRARY}")
1222
+ + message(FATAL_ERROR "Set FPX_CK_IMPORT_LIBRARY to the matching ck-attention.lib from the AiTable fix bundle")
1223
+ +endif()
1224
+ +target_link_libraries(ggml-hip PRIVATE "${FPX_CK_IMPORT_LIBRARY}")
1225
+ --- a/src/llama-context.cpp
1226
+ +++ b/src/llama-context.cpp
1227
+ @@ -2984,7 +2984,7 @@
1228
+ return false;
1229
+ }
1230
+
1231
+ - for (size_t i = 0; i < mbuf_cur.org.size(); ++i) {
1232
+ + for (size_t i = 0; i < mbuf.org.size(); ++i) { // cloned storage owns cpy, not source views
1233
+ ggml_backend_tensor_copy(mbuf_cur.cpy[i], mbuf.org[i]);
1234
+ }
1235
+ }
1236
+ --- a/tools/cli/CMakeLists.txt
1237
+ +++ b/tools/cli/CMakeLists.txt
1238
+ @@ -8,3 +8,7 @@
1239
+ if(LLAMA_TOOLS_INSTALL)
1240
+ install(TARGETS ${TARGET} RUNTIME)
1241
+ endif()
1242
+ +
1243
+ +add_executable(prefix-offline-probe prefix-offline-probe.cpp)
1244
+ +target_link_libraries(prefix-offline-probe PRIVATE server-context PUBLIC llama-common ${CMAKE_THREAD_LIBS_INIT})
1245
+ +target_compile_features(prefix-offline-probe PRIVATE cxx_std_17)
1246
+ --- /dev/null
1247
+ +++ b/tools/cli/prefix-offline-probe.cpp
1248
+ @@ -0,0 +1,58 @@
1249
+ +#include "arg.h"
1250
+ +#include "common.h"
1251
+ +#include "server-context.h"
1252
+ +#include "server-task.h"
1253
+ +#include <chrono>
1254
+ +#include <fstream>
1255
+ +#include <thread>
1256
+ +#include <iostream>
1257
+ +#include <exception>
1258
+ +int main(int argc,char **argv) {
1259
+ + try {
1260
+ + if(argc<4)throw std::invalid_argument("manifest output then native model arguments required");
1261
+ + json cases;std::ifstream in(argv[1]);if(!in)throw std::runtime_error("manifest open failed");in>>cases;
1262
+ + std::ofstream out(argv[2]);if(!out)throw std::runtime_error("output open failed");
1263
+ + std::vector<char*> args{argv[0]};for(int i=3;i<argc;++i)args.push_back(argv[i]);
1264
+ + common_init();common_params params;
1265
+ + if(!common_params_parse((int)args.size(),args.data(),params,LLAMA_EXAMPLE_SERVER))return 2;
1266
+ + if(params.n_parallel!=1 || params.n_ctx_checkpoints!=4 || params.checkpoint_every_nt!=2048 || params.cache_ram_mib!=0)throw std::invalid_argument("probe requires one slot, four checkpoints, interval2048, no RAM cache");
1267
+ + llama_backend_init();
1268
+ + server_context ctx;ctx.set_projector_idle_seconds(0);
1269
+ + if(!ctx.load_model(params))throw std::runtime_error("model load failed");
1270
+ + std::exception_ptr worker_error;
1271
+ + std::thread worker([&]{try{ctx.start_loop();}catch(...){worker_error=std::current_exception();ctx.terminate();}});
1272
+ + try {
1273
+ + for(const auto & c:cases) {
1274
+ + const std::string path=c.at("prompt");std::ifstream f(path,std::ios::binary);if(!f)throw std::runtime_error("prompt open failed");
1275
+ + const std::string prompt((std::istreambuf_iterator<char>(f)),{});
1276
+ + auto rd=ctx.get_response_reader();server_task task(SERVER_TASK_TYPE_COMPLETION);
1277
+ + task.id=rd.get_new_id();task.id_slot=0;task.index=0;task.cli=true;task.cli_prompt=prompt;
1278
+ + task.params.sampling=params.sampling;task.params.n_predict=c.value("n_predict",64);
1279
+ + task.params.stream=true;task.params.return_tokens=true;task.params.timings_per_token=true;
1280
+ + task.params.cache_prompt=c.value("cache_prompt",true);
1281
+ + auto start=std::chrono::steady_clock::now();double first=-1;bool final_seen=false;std::string collected;llama_tokens collected_tokens;
1282
+ + rd.post_task({std::move(task)});
1283
+ + auto elapsed=[&]{return std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-start).count();};
1284
+ + auto cancel=[&]{return elapsed()>60000;};
1285
+ + while(rd.has_next()) {
1286
+ + auto result=rd.next(cancel);if(!result)throw std::runtime_error("cancelled or missing result");
1287
+ + if(result->is_error())throw std::runtime_error(result->to_json().dump());
1288
+ + if(auto partial=dynamic_cast<server_task_result_cmpl_partial*>(result.get())) {
1289
+ + if(first<0 && (!partial->content.empty() || !partial->tokens.empty()))first=elapsed();
1290
+ + if(!partial->is_progress){collected+=partial->content;collected_tokens.insert(collected_tokens.end(),partial->tokens.begin(),partial->tokens.end());}
1291
+ + }
1292
+ + if(auto fin=dynamic_cast<server_task_result_cmpl_final*>(result.get())) {
1293
+ + json row={{"name",c.at("name")},{"timings",fin->timings.to_json()},{"first_native_chunk_ms",first},
1294
+ + {"wall_ms",elapsed()},{"stop_type",(int)fin->stop},{"truncated",fin->truncated},
1295
+ + {"prompt_tokens",fin->n_prompt_tokens},{"cache_tokens",fin->n_prompt_tokens_cache},
1296
+ + {"content",collected},{"tokens",collected_tokens},{"stream_final_content",fin->content},{"stream_final_tokens",fin->tokens},{"native",fin->to_json()}};
1297
+ + out<<row.dump()<<"\n";out.flush();if(!out)throw std::runtime_error("result write failed");final_seen=true;break;
1298
+ + }
1299
+ + }
1300
+ + if(!final_seen)throw std::runtime_error("missing native final result");
1301
+ + }
1302
+ + }catch(...){ctx.terminate();worker.join();if(worker_error)std::rethrow_exception(worker_error);throw;}
1303
+ + ctx.terminate();worker.join();if(worker_error)std::rethrow_exception(worker_error);
1304
+ + }catch(const std::exception&e){std::cerr<<e.what()<<"\n";return 1;}
1305
+ + return 0;
1306
+ +}
1307
+ --- /dev/null
1308
+ +++ b/tools/server/prefix-short-policy.h
1309
+ @@ -0,0 +1,21 @@
1310
+ +#pragma once
1311
+ +#include <cstdint>
1312
+ +#include <cstdlib>
1313
+ +#include <cstring>
1314
+ +#include <stdexcept>
1315
+ +namespace prefix_short {
1316
+ +inline bool requested() {
1317
+ + const char *v=std::getenv("FPX_SHORT_PREFIX_CHECKPOINT");
1318
+ + if(!v || !*v || !std::strcmp(v,"0"))return false;
1319
+ + if(std::strcmp(v,"1"))throw std::invalid_argument("FPX_SHORT_PREFIX_CHECKPOINT must be 0 or 1");
1320
+ + return true;
1321
+ +}
1322
+ +inline bool boundary(int64_t tokens){return tokens==1024 || tokens==2048;}
1323
+ +template<class Checkpoints> auto victim(Checkpoints & checkpoints) {
1324
+ + // Exactly two prefix snapshots are pinned; total capacity remains four.
1325
+ + // Source/token invalidation is still owned by the existing restore path.
1326
+ + for(auto it=checkpoints.begin();it!=checkpoints.end();++it)
1327
+ + if(!boundary(it->n_tokens))return it;
1328
+ + throw std::logic_error("No evictable checkpoint in four-entry short-prefix policy");
1329
+ +}
1330
+ +}
1331
+ --- a/tools/server/server-common.h
1332
+ +++ b/tools/server/server-common.h
1333
+ @@ -202,6 +202,9 @@
1334
+ size_t size() const { return tokens.size(); }
1335
+
1336
+ bool empty() const { return tokens.empty(); }
1337
+ +
1338
+ + // has_mtmd enables media handling; this tests actual media in this prompt.
1339
+ + bool has_media_chunks() const { return !map_idx_to_media.empty(); }
1340
+
1341
+ void clear() {
1342
+ map_idx_to_media.clear();
1343
+ --- a/tools/server/server-context.cpp
1344
+ +++ b/tools/server/server-context.cpp
1345
+ @@ -1,5 +1,7 @@
1346
+
1347
+ #include "server-context.h"
1348
+ +#include "prefix-short-policy.h"
1349
+ +#include "server-projector.h"
1350
+ #include "server-chat.h"
1351
+ #include "server-common.h"
1352
+ #include "server-http.h"
1353
+ @@ -7,6 +9,7 @@
1354
+ #include "server-queue.h"
1355
+
1356
+ #include "build-info.h"
1357
+ +#include "ggml-backend.h"
1358
+ #include "common.h"
1359
+ #include "diffusion.h"
1360
+ #include "diffusion-output.h"
1361
+ @@ -682,6 +685,14 @@
1362
+ // server_context_impl (private implementation)
1363
+ //
1364
+
1365
+ +static bool projector_prompt_has_media(const json & value) {
1366
+ + if (value.is_object()) return value.contains("multimodal_data");
1367
+ + if (value.is_array()) {
1368
+ + for (const auto & part : value) if (projector_prompt_has_media(part)) return true;
1369
+ + }
1370
+ + return false;
1371
+ +}
1372
+ +
1373
+ struct server_context_impl {
1374
+ friend struct server_context;
1375
+ friend struct server_routes;
1376
+ @@ -765,20 +776,53 @@
1377
+ std::set<std::string> model_aliases; // additional names for the model
1378
+ std::set<std::string> model_tags; // informational tags
1379
+
1380
+ + mutable server_projector_lifecycle projector;
1381
+ + std::atomic<int> llm_load_count{0};
1382
+ + std::atomic<bool> llm_loaded{false};
1383
+ bool sleeping = false;
1384
+
1385
+ + void load_projector() const {
1386
+ + if (params_base.mmproj.path.empty()) throw std::runtime_error("projector is not configured");
1387
+ + mtmd_context_params p = mtmd_context_params_default();
1388
+ + p.use_gpu = params_base.mmproj_use_gpu;
1389
+ + p.print_timings = false;
1390
+ + p.n_threads = params_base.cpuparams.n_threads;
1391
+ + p.flash_attn_type = params_base.flash_attn_type;
1392
+ + p.warmup = params_base.warmup;
1393
+ + p.image_min_tokens = params_base.image_min_tokens;
1394
+ + p.image_max_tokens = params_base.image_max_tokens;
1395
+ + p.media_marker = get_media_marker();
1396
+ + auto & self = const_cast<server_context_impl &>(*this);
1397
+ + self.mctx = mtmd_init_from_file(params_base.mmproj.path.c_str(), model_tgt, p);
1398
+ + if (!self.mctx) throw std::runtime_error("projector initialization failed");
1399
+ + for (auto & slot : self.slots) slot.mctx = self.mctx;
1400
+ + }
1401
+ +
1402
+ + void release_projector() {
1403
+ + mtmd_free(mctx);
1404
+ + mctx = nullptr;
1405
+ + for (auto & slot : slots) slot.mctx = nullptr;
1406
+ + }
1407
+ +
1408
+ + void expire_projector() {
1409
+ + projector.expire([this] {
1410
+ + if (queue_tasks.has_pending_tasks()) return false;
1411
+ + for (const auto & slot : slots) if (slot.is_processing()) return false;
1412
+ + return true;
1413
+ + }, [this] { release_projector(); });
1414
+ + }
1415
+ +
1416
+ void destroy() {
1417
+ + projector.release_all([this] { release_projector(); });
1418
+ spec.reset();
1419
+ ctx_dft.reset();
1420
+ model_dft.reset();
1421
+
1422
+ + llm_loaded.store(false);
1423
+ llama_init.reset();
1424
+
1425
+ ctx_tgt = nullptr;
1426
+ model_tgt = nullptr;
1427
+ -
1428
+ - mtmd_free(mctx);
1429
+ - mctx = nullptr;
1430
+
1431
+ llama_batch_free(batch);
1432
+ }
1433
+ @@ -806,6 +850,7 @@
1434
+
1435
+ params_base = params;
1436
+
1437
+ + llm_load_count.fetch_add(1);
1438
+ llama_init = common_init_from_params(params_base);
1439
+
1440
+ model_tgt = llama_init->model();
1441
+ @@ -816,6 +861,7 @@
1442
+ return false;
1443
+ }
1444
+
1445
+ + llm_loaded.store(true);
1446
+ vocab = llama_model_get_vocab(model_tgt);
1447
+
1448
+ n_ctx = llama_n_ctx(ctx_tgt);
1449
+ @@ -961,20 +1007,11 @@
1450
+
1451
+ std::string & mmproj_path = params_base.mmproj.path;
1452
+ if (!mmproj_path.empty()) {
1453
+ - mtmd_context_params mparams = mtmd_context_params_default();
1454
+ -
1455
+ - mparams.use_gpu = params_base.mmproj_use_gpu;
1456
+ - mparams.print_timings = false;
1457
+ - mparams.n_threads = params_base.cpuparams.n_threads;
1458
+ - mparams.flash_attn_type = params_base.flash_attn_type;
1459
+ - mparams.warmup = params_base.warmup;
1460
+ - mparams.image_min_tokens = params_base.image_min_tokens;
1461
+ - mparams.image_max_tokens = params_base.image_max_tokens;
1462
+ - mparams.media_marker = get_media_marker();
1463
+ -
1464
+ - mctx = mtmd_init_from_file(mmproj_path.c_str(), model_tgt, mparams);
1465
+ - if (mctx == nullptr) {
1466
+ - SRV_ERR("failed to load multimodal model, '%s'\n", mmproj_path.c_str());
1467
+ + try {
1468
+ + load_projector();
1469
+ + projector.adopt_loaded();
1470
+ + } catch (const std::exception & error) {
1471
+ + SRV_ERR("projector load failed: %s\n", error.what());
1472
+ return false;
1473
+ }
1474
+ SRV_INF("loaded multimodal model, '%s'\n", mmproj_path.c_str());
1475
+ @@ -1167,15 +1204,21 @@
1476
+
1477
+ // wiring up server queues
1478
+ queue_tasks.on_new_task([this](server_task && task) {
1479
+ - process_single_task(std::move(task));
1480
+ + auto lease = projector.acquire(false, {}, {});
1481
+ + if (lease) process_single_task(std::move(task));
1482
+ });
1483
+ queue_tasks.on_update_slots([this]() {
1484
+ - update_slots();
1485
+ + {
1486
+ + auto lease = projector.acquire(false, {}, {});
1487
+ + if (lease) update_slots();
1488
+ + }
1489
+ + expire_projector();
1490
+ });
1491
+ queue_tasks.on_sleeping_state([this](bool sleeping) {
1492
+ handle_sleeping_state(sleeping);
1493
+ });
1494
+
1495
+ + queue_tasks.on_idle([this] { expire_projector(); });
1496
+ metrics.init();
1497
+
1498
+ if (params_base.cache_idle_slots) {
1499
+ @@ -2019,14 +2062,27 @@
1500
+
1501
+ // n_tokens_cur: the number of tokens added to the batch for the current slot
1502
+ void create_checkpoint(server_slot & slot, const int64_t n_tokens_cur, llama_pos pos_min, llama_pos pos_max) {
1503
+ + const int64_t probe_save_started = ggml_time_us();
1504
+ + const bool short_prefix = prefix_short::requested() && params_base.n_parallel == 1 &&
1505
+ + params_base.n_ctx_checkpoints == 4 && params_base.checkpoint_every_nt == 2048 &&
1506
+ + !slot.prompt.tokens.has_media_chunks() && !slot.task->tokens.has_media_chunks() &&
1507
+ + !ctx_dft && slot.lora.empty();
1508
+ while (slot.prompt.checkpoints.size() >= (size_t) params_base.n_ctx_checkpoints) {
1509
+ - // make room for the new checkpoint, if needed
1510
+ - const auto & cur = slot.prompt.checkpoints.front();
1511
+ + // Retain the initial aligned prefix when possible, while keeping
1512
+ + // the configured checkpoint count bounded. Later checkpoints remain
1513
+ + // oldest-first; ordinary source changes still invalidate checkpoints.
1514
+ + auto victim = slot.prompt.checkpoints.begin();
1515
+ + if (slot.prompt.checkpoints.size() > 1 &&
1516
+ + victim->n_tokens <= params_base.checkpoint_every_nt) {
1517
+ + ++victim;
1518
+ + }
1519
+ + if (short_prefix) victim = prefix_short::victim(slot.prompt.checkpoints);
1520
+ + const auto & cur = *victim;
1521
+
1522
+ SLT_WRN(slot, "erasing old context checkpoint (pos_min = %d, pos_max = %d, n_tokens = %" PRId64 ", size = %.3f MiB)\n",
1523
+ cur.pos_min, cur.pos_max, cur.n_tokens, (float) cur.size() / 1024 / 1024);
1524
+
1525
+ - slot.prompt.checkpoints.erase(slot.prompt.checkpoints.begin());
1526
+ + slot.prompt.checkpoints.erase(victim);
1527
+ }
1528
+
1529
+ auto & cur = slot.prompt.checkpoints.emplace_back();
1530
+ @@ -2050,6 +2106,7 @@
1531
+ "created context checkpoint %d of %d (pos_min = %d, pos_max = %d, n_tokens = %" PRId64 ", size = %.3f MiB)\n",
1532
+ (int) slot.prompt.checkpoints.size(), params_base.n_ctx_checkpoints, cur.pos_min,
1533
+ cur.pos_max, cur.n_tokens, (float) cur.size() / 1024 / 1024);
1534
+ + SLT_INF(slot, "checkpoint_probe_save n_tokens=%" PRId64 " wall_ms=%.3f\n", cur.n_tokens, (ggml_time_us()-probe_save_started)/1e3);
1535
+ }
1536
+
1537
+ void process_single_task(server_task && task) {
1538
+ @@ -2984,9 +3041,12 @@
1539
+
1540
+ if (!do_reset) {
1541
+ // restore the context checkpoint
1542
+ + const int64_t restore_probe_started = ggml_time_us();
1543
+
1544
+ const bool restored_tgt = it->load_tgt(ctx_tgt, slot.id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY | LLAMA_STATE_SEQ_FLAGS_ON_DEVICE);
1545
+ const bool restored_dft = it->load_dft(ctx_dft.get(), slot.id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY | LLAMA_STATE_SEQ_FLAGS_ON_DEVICE);
1546
+ +
1547
+ + SLT_INF(slot, "checkpoint_probe_restore n_tokens=%" PRId64 " wall_ms=%.3f target_ok=%d draft_ok=%d\n", it->n_tokens, (ggml_time_us()-restore_probe_started)/1e3, (int) restored_tgt, (int) restored_dft);
1548
+
1549
+ if (!restored_tgt || !restored_dft) {
1550
+ SLT_WRN(slot,
1551
+ @@ -3146,6 +3206,11 @@
1552
+ has_mtmd = true;
1553
+ }
1554
+
1555
+ + const bool short_prefix = prefix_short::requested() && params_base.n_parallel == 1 &&
1556
+ + params_base.n_ctx_checkpoints == 4 && params_base.checkpoint_every_nt == 2048 &&
1557
+ + !slot.prompt.tokens.has_media_chunks() && !slot.task->tokens.has_media_chunks() &&
1558
+ + !ctx_dft && slot.lora.empty();
1559
+ +
1560
+ // add prompt tokens for processing in the current batch
1561
+ while (slot.prompt.n_tokens() < slot.task->n_tokens() && batch.n_tokens < n_batch) {
1562
+ // get next token to process
1563
+ @@ -3172,6 +3237,9 @@
1564
+ slot.prompt.tokens.push_back(cur_tok);
1565
+
1566
+ slot.n_prompt_tokens_processed++;
1567
+ +
1568
+ + // Split before decode so the next iteration can snapshot actual state at 1024/2048.
1569
+ + if (short_prefix && do_checkpoint && prefix_short::boundary(slot.prompt.n_tokens())) break;
1570
+
1571
+ // process the last few tokens of the prompt separately in order to allow for a checkpoint to be created.
1572
+ // create checkpoints that many tokens before the end of the prompt:
1573
+ @@ -3225,7 +3293,10 @@
1574
+ last_checkpoint = slot.prompt.checkpoints.back().n_tokens;
1575
+ }
1576
+
1577
+ - do_checkpoint = do_checkpoint && slot.prompt.n_tokens() - batch.n_tokens - last_checkpoint >= params_base.checkpoint_every_nt;
1578
+ + const int64_t processed = slot.prompt.n_tokens() - n_tokens_cur;
1579
+ + do_checkpoint = do_checkpoint && (
1580
+ + (short_prefix && prefix_short::boundary(processed)) ||
1581
+ + slot.prompt.n_tokens() - batch.n_tokens - last_checkpoint >= params_base.checkpoint_every_nt);
1582
+
1583
+ if (do_checkpoint) {
1584
+ SLT_INF(slot, "%d tokens since last checkpoint at %d, creating new checkpoint during processing at position %d\n", params_base.checkpoint_every_nt, last_checkpoint, slot.prompt.n_tokens());
1585
+ @@ -3973,12 +4044,17 @@
1586
+ return impl->load_model(params);
1587
+ }
1588
+
1589
+ +void server_context::set_projector_idle_seconds(int seconds) {
1590
+ + impl->projector.configure(seconds);
1591
+ +}
1592
+ +
1593
+ void server_context::start_loop() {
1594
+ auto & params = impl->params_base;
1595
+ impl->queue_tasks.start_loop(params.sleep_idle_seconds * 1000);
1596
+ }
1597
+
1598
+ void server_context::terminate() {
1599
+ + impl->projector.begin_close();
1600
+ impl->queue_tasks.terminate();
1601
+ }
1602
+
1603
+ @@ -4037,6 +4113,7 @@
1604
+ // generator-like API for HTTP response generation
1605
+ // may have bypass_sleep = true if the task does not use ctx_server
1606
+ struct server_res_generator : server_http_res {
1607
+ + std::unique_ptr<server_projector_lifecycle::lease> projector_lease;
1608
+ server_response_reader rd;
1609
+ server_res_generator(server_queue & queue_tasks, server_response & queue_results, int sleep_idle_seconds, bool bypass_sleep = false)
1610
+ : rd(queue_tasks, queue_results, HTTP_POLLING_SECONDS) {
1611
+ @@ -4073,7 +4150,7 @@
1612
+ task_response_type res_type) {
1613
+ GGML_ASSERT(type == SERVER_TASK_TYPE_COMPLETION || type == SERVER_TASK_TYPE_INFILL);
1614
+
1615
+ - auto res = create_response();
1616
+ + auto res = create_response(false, !files.empty() || projector_prompt_has_media(data.at("prompt")), req.should_stop);
1617
+ auto completion_id = gen_chatcmplid();
1618
+ auto & rd = res->rd;
1619
+
1620
+ @@ -4294,8 +4371,14 @@
1621
+ return res;
1622
+ }
1623
+
1624
+ -std::unique_ptr<server_res_generator> server_routes::create_response(bool bypass_sleep) {
1625
+ - return std::make_unique<server_res_generator>(queue_tasks, queue_results, params.sleep_idle_seconds, bypass_sleep);
1626
+ +std::unique_ptr<server_res_generator> server_routes::create_response(bool bypass_sleep, bool visual,
1627
+ + const std::function<bool()> & cancelled) {
1628
+ + auto response = std::make_unique<server_res_generator>(queue_tasks, queue_results, params.sleep_idle_seconds, bypass_sleep);
1629
+ + if (!bypass_sleep && !params.mmproj.path.empty()) {
1630
+ + response->projector_lease = ctx_server.projector.acquire(visual, cancelled, [this] { ctx_server.load_projector(); });
1631
+ + if (!response->projector_lease) throw std::runtime_error("projector request cancelled or server closing");
1632
+ + }
1633
+ + return response;
1634
+ }
1635
+
1636
+ server_routes::server_routes(const common_params & params, server_context & ctx_server)
1637
+ @@ -4307,6 +4390,24 @@
1638
+ }
1639
+
1640
+ void server_routes::init_routes() {
1641
+ + this->get_projector_status = [this](const server_http_req &) {
1642
+ + auto res = create_response(true);
1643
+ + const auto p = ctx_server.projector.status();
1644
+ + json memory = json::array();
1645
+ + for (size_t i = 0; i < ggml_backend_dev_count(); ++i) {
1646
+ + auto dev = ggml_backend_dev_get(i);
1647
+ + if (ggml_backend_dev_type(dev) != GGML_BACKEND_DEVICE_TYPE_GPU) continue;
1648
+ + size_t available = 0, total = 0;
1649
+ + ggml_backend_dev_memory(dev, &available, &total);
1650
+ + memory.push_back({{"device", ggml_backend_dev_name(dev)}, {"free_bytes", available}, {"total_bytes", total}});
1651
+ + }
1652
+ + res->ok({{"projector_loaded", p.loaded}, {"closing", p.closing},
1653
+ + {"request_leases", p.readers}, {"visual_leases", p.visual_readers},
1654
+ + {"load_count", p.loads}, {"unload_count", p.unloads},
1655
+ + {"idle_seconds", p.idle_ms / 1000}, {"llm_loaded", ctx_server.llm_loaded.load()},
1656
+ + {"llm_load_count", ctx_server.llm_load_count.load()}, {"gpu_memory", memory}});
1657
+ + return res;
1658
+ + };
1659
+ // IMPORTANT: all lambda functions must start with create_response()
1660
+ // this is to ensure that the server_res_generator can handle sleeping case correctly
1661
+
1662
+ @@ -4635,6 +4736,7 @@
1663
+ );
1664
+
1665
+ std::vector<raw_buffer> files; // dummy
1666
+ + res.reset(); // Hand preprocessing ownership to the inference response before a possible reload.
1667
+ return handle_completions_impl(
1668
+ req,
1669
+ SERVER_TASK_TYPE_INFILL,
1670
+ @@ -4647,6 +4749,7 @@
1671
+ auto res = create_response();
1672
+ std::vector<raw_buffer> files; // dummy
1673
+ const json body = json::parse(req.body);
1674
+ + res.reset(); // Hand preprocessing ownership to the inference response before a possible reload.
1675
+ return handle_completions_impl(
1676
+ req,
1677
+ SERVER_TASK_TYPE_COMPLETION,
1678
+ @@ -4659,6 +4762,7 @@
1679
+ auto res = create_response();
1680
+ std::vector<raw_buffer> files; // dummy
1681
+ const json body = json::parse(req.body);
1682
+ + res.reset(); // Hand preprocessing ownership to the inference response before a possible reload.
1683
+ return handle_completions_impl(
1684
+ req,
1685
+ SERVER_TASK_TYPE_COMPLETION,
1686
+ @@ -4675,6 +4779,7 @@
1687
+ body,
1688
+ meta->chat_params,
1689
+ files);
1690
+ + res.reset(); // Hand preprocessing ownership to the inference response before a possible reload.
1691
+ return handle_completions_impl(
1692
+ req,
1693
+ SERVER_TASK_TYPE_COMPLETION,
1694
+ @@ -4693,6 +4798,7 @@
1695
+ body,
1696
+ meta->chat_params,
1697
+ files);
1698
+ + res.reset(); // Hand preprocessing ownership to the inference response before a possible reload.
1699
+ return handle_completions_impl(
1700
+ req,
1701
+ SERVER_TASK_TYPE_COMPLETION,
1702
+ @@ -4721,6 +4827,7 @@
1703
+ body,
1704
+ meta->chat_params,
1705
+ files);
1706
+ + res.reset(); // Hand preprocessing ownership to the inference response before a possible reload.
1707
+ return handle_completions_impl(
1708
+ req,
1709
+ SERVER_TASK_TYPE_COMPLETION,
1710
+ @@ -4739,6 +4846,7 @@
1711
+ body,
1712
+ meta->chat_params,
1713
+ files);
1714
+ + res.reset(); // Hand preprocessing ownership to the inference response before a possible reload.
1715
+ return handle_completions_impl(
1716
+ req,
1717
+ SERVER_TASK_TYPE_COMPLETION,
1718
+ @@ -5181,6 +5289,11 @@
1719
+ }
1720
+ }
1721
+
1722
+ + if (projector_prompt_has_media(prompt)) {
1723
+ + res->projector_lease.reset();
1724
+ + res->projector_lease = ctx_server.projector.acquire(true, req.should_stop, [this] { ctx_server.load_projector(); });
1725
+ + if (!res->projector_lease) throw std::runtime_error("projector request cancelled or server closing");
1726
+ + }
1727
+ auto tokenized_prompts = tokenize_input_prompts(ctx_server.vocab, ctx_server.mctx, prompt, true, true);
1728
+ for (const auto & tokens : tokenized_prompts) {
1729
+ // this check is necessary for models that do not add BOS token to the input
1730
+ --- a/tools/server/server-context.h
1731
+ +++ b/tools/server/server-context.h
1732
+ @@ -67,6 +67,9 @@
1733
+ // terminate main loop (will unblock start_loop)
1734
+ void terminate();
1735
+
1736
+ + // Set before load_model; 0 disables independent projector expiry.
1737
+ + void set_projector_idle_seconds(int seconds);
1738
+ +
1739
+ // get the underlaying llama_context, can return nullptr if sleeping
1740
+ // not thread-safe, should only be used from the main thread
1741
+ llama_context * get_llama_context() const;
1742
+ @@ -122,6 +125,7 @@
1743
+ server_http_context::handler_t post_rerank;
1744
+ server_http_context::handler_t get_lora_adapters;
1745
+ server_http_context::handler_t post_lora_adapters;
1746
+ + server_http_context::handler_t get_projector_status;
1747
+
1748
+ // to be used in router mode
1749
+ json get_model_info() const;
1750
+ @@ -146,5 +150,6 @@
1751
+
1752
+ server_queue & queue_tasks;
1753
+ server_response & queue_results;
1754
+ - std::unique_ptr<server_res_generator> create_response(bool bypass_sleep = false);
1755
+ + std::unique_ptr<server_res_generator> create_response(bool bypass_sleep = false, bool visual = false,
1756
+ + const std::function<bool()> & cancelled = {});
1757
+ };
1758
+ --- /dev/null
1759
+ +++ b/tools/server/server-projector.h
1760
+ @@ -0,0 +1,111 @@
1761
+ +#pragma once
1762
+ +
1763
+ +#include <atomic>
1764
+ +#include <chrono>
1765
+ +#include <functional>
1766
+ +#include <memory>
1767
+ +#include <mutex>
1768
+ +#include <shared_mutex>
1769
+ +#include <stdexcept>
1770
+ +#include <thread>
1771
+ +
1772
+ +// Protects projector ownership across HTTP preprocessing, queued requests and
1773
+ +// native inference. Only the main loop invokes expire(); it checks slots/queue
1774
+ +// before entering this boundary. No model or KV resource is owned here.
1775
+ +class server_projector_lifecycle {
1776
+ + static int64_t now_ms() {
1777
+ + return std::chrono::duration_cast<std::chrono::milliseconds>(
1778
+ + std::chrono::steady_clock::now().time_since_epoch()).count();
1779
+ + }
1780
+ + mutable std::shared_mutex mutex;
1781
+ + std::atomic<bool> closing{false};
1782
+ + std::atomic<bool> loaded{false};
1783
+ + std::atomic<int64_t> last_visual_ms{0};
1784
+ + std::atomic<int> readers{0};
1785
+ + std::atomic<int> visual_readers{0};
1786
+ + std::atomic<int> loads{0};
1787
+ + std::atomic<int> unloads{0};
1788
+ + int64_t idle_ms = 120000;
1789
+ +
1790
+ +public:
1791
+ + struct snapshot {
1792
+ + bool loaded;
1793
+ + bool closing;
1794
+ + int readers;
1795
+ + int visual_readers;
1796
+ + int loads;
1797
+ + int unloads;
1798
+ + int64_t idle_ms;
1799
+ + };
1800
+ + class lease {
1801
+ + server_projector_lifecycle & owner;
1802
+ + std::shared_lock<std::shared_mutex> lock;
1803
+ + bool visual;
1804
+ + public:
1805
+ + lease(server_projector_lifecycle & owner, std::shared_lock<std::shared_mutex> lock, bool visual)
1806
+ + : owner(owner), lock(std::move(lock)), visual(visual) {
1807
+ + owner.readers.fetch_add(1);
1808
+ + if (visual) owner.visual_readers.fetch_add(1);
1809
+ + }
1810
+ + ~lease() {
1811
+ + if (visual) {
1812
+ + owner.last_visual_ms.store(now_ms());
1813
+ + owner.visual_readers.fetch_sub(1);
1814
+ + }
1815
+ + owner.readers.fetch_sub(1);
1816
+ + }
1817
+ + lease(const lease &) = delete;
1818
+ + lease & operator=(const lease &) = delete;
1819
+ + };
1820
+ +
1821
+ + void configure(int seconds) {
1822
+ + if (seconds < 0 || seconds > 86400) throw std::invalid_argument("projector idle seconds must be 0..86400");
1823
+ + idle_ms = int64_t(seconds) * 1000; // 0 explicitly disables expiry.
1824
+ + }
1825
+ + void adopt_loaded() {
1826
+ + loaded.store(true); loads.fetch_add(1); last_visual_ms.store(now_ms());
1827
+ + }
1828
+ + std::unique_ptr<lease> acquire(bool visual, const std::function<bool()> & cancelled,
1829
+ + const std::function<void()> & load) {
1830
+ + auto stopped = [&] { return closing.load() || (cancelled && cancelled()); };
1831
+ + while (!stopped()) {
1832
+ + std::shared_lock<std::shared_mutex> read(mutex, std::try_to_lock);
1833
+ + if (read.owns_lock()) {
1834
+ + if (!visual || loaded.load()) {
1835
+ + if (stopped()) return nullptr;
1836
+ + return std::make_unique<lease>(*this, std::move(read), visual);
1837
+ + }
1838
+ + read.unlock();
1839
+ + std::unique_lock<std::shared_mutex> write(mutex, std::try_to_lock);
1840
+ + if (write.owns_lock() && !stopped() && !loaded.load()) {
1841
+ + load(); // Failed load remains unavailable; no fake success.
1842
+ + loaded.store(true); loads.fetch_add(1); last_visual_ms.store(now_ms());
1843
+ + }
1844
+ + }
1845
+ + std::this_thread::sleep_for(std::chrono::milliseconds(2));
1846
+ + }
1847
+ + return nullptr;
1848
+ + }
1849
+ + bool expire(const std::function<bool()> & safe, const std::function<void()> & release,
1850
+ + int64_t time_ms = -1) {
1851
+ + if (!idle_ms || !loaded.load() || closing.load()) return false;
1852
+ + const int64_t now = time_ms < 0 ? now_ms() : time_ms;
1853
+ + if (now - last_visual_ms.load() < idle_ms) return false;
1854
+ + std::unique_lock<std::shared_mutex> write(mutex, std::try_to_lock);
1855
+ + if (!write.owns_lock() || !loaded.load() || closing.load() || !safe()) return false;
1856
+ + release();
1857
+ + loaded.store(false); unloads.fetch_add(1);
1858
+ + return true;
1859
+ + }
1860
+ + void begin_close() { closing.store(true); }
1861
+ + void release_all(const std::function<void()> & release) {
1862
+ + std::unique_lock<std::shared_mutex> write(mutex);
1863
+ + if (loaded.load()) {
1864
+ + release(); loaded.store(false); unloads.fetch_add(1);
1865
+ + }
1866
+ + }
1867
+ + snapshot status() const {
1868
+ + return {loaded.load(), closing.load(), readers.load(), visual_readers.load(),
1869
+ + loads.load(), unloads.load(), idle_ms};
1870
+ + }
1871
+ +};
1872
+ --- a/tools/server/server-queue.cpp
1873
+ +++ b/tools/server/server-queue.cpp
1874
+ @@ -202,6 +202,8 @@
1875
+ if (res) {
1876
+ break; // new task arrived or terminate
1877
+ }
1878
+ + lock.unlock();
1879
+ + if (callback_idle) callback_idle();
1880
+ // otherwise, loop again to check sleeping condition
1881
+ }
1882
+ }
1883
+ --- a/tools/server/server-queue.h
1884
+ +++ b/tools/server/server-queue.h
1885
+ @@ -29,6 +29,7 @@
1886
+ std::function<void(server_task &&)> callback_new_task;
1887
+ std::function<void(void)> callback_update_slots;
1888
+ std::function<void(bool)> callback_sleeping_state;
1889
+ + std::function<void(void)> callback_idle;
1890
+
1891
+ public:
1892
+ // Add a new task to the end of the queue
1893
+ @@ -75,6 +76,12 @@
1894
+ */
1895
+ void start_loop(int64_t idle_sleep_ms = -1);
1896
+
1897
+ + bool has_pending_tasks() {
1898
+ + std::unique_lock<std::mutex> lock(mutex_tasks);
1899
+ + return !queue_tasks.empty() || !queue_tasks_deferred.empty();
1900
+ + }
1901
+ + void on_idle(std::function<void(void)> callback) { callback_idle = std::move(callback); }
1902
+ +
1903
+ // for metrics
1904
+ size_t queue_tasks_deferred_size() {
1905
+ std::unique_lock<std::mutex> lock(mutex_tasks);
1906
+ --- a/tools/server/server.cpp
1907
+ +++ b/tools/server/server.cpp
1908
+ @@ -102,6 +102,24 @@
1909
+ int main(int argc, char ** argv) {
1910
+ std::setlocale(LC_NUMERIC, "C");
1911
+
1912
+ + // Candidate-only option; do not change common_params/DLL ABI.
1913
+ + int projector_idle_seconds = 120;
1914
+ + std::vector<char *> filtered_args;
1915
+ + try {
1916
+ + for (int i = 0; i < argc; ++i) {
1917
+ + if (std::string(argv[i]) == "--projector-idle-seconds") {
1918
+ + if (++i >= argc) throw std::invalid_argument("projector idle seconds missing");
1919
+ + size_t end = 0;
1920
+ + projector_idle_seconds = std::stoi(argv[i], &end);
1921
+ + if (argv[i][end] || projector_idle_seconds < 0 || projector_idle_seconds > 86400)
1922
+ + throw std::invalid_argument("projector idle seconds must be 0..86400");
1923
+ + } else filtered_args.push_back(argv[i]);
1924
+ + }
1925
+ + } catch (const std::exception & error) {
1926
+ + fprintf(stderr, "%s\n", error.what()); return 1;
1927
+ + }
1928
+ + argc = int(filtered_args.size()); filtered_args.push_back(nullptr); argv = filtered_args.data();
1929
+ +
1930
+ // own arguments required by this example
1931
+ common_params params;
1932
+
1933
+ @@ -111,6 +129,10 @@
1934
+ return 1;
1935
+ }
1936
+
1937
+ + if (!params.mmproj.path.empty() && params.sleep_idle_seconds >= 0) {
1938
+ + fprintf(stderr, "Independent projector lifecycle requires whole-model sleep disabled.\n");
1939
+ + return 1;
1940
+ + }
1941
+ llama_backend_init();
1942
+ llama_numa_init(params.numa);
1943
+
1944
+ @@ -151,6 +173,7 @@
1945
+
1946
+ // struct that contains llama context and inference
1947
+ server_context ctx_server;
1948
+ + ctx_server.set_projector_idle_seconds(projector_idle_seconds);
1949
+
1950
+ server_http_context ctx_http;
1951
+ if (!ctx_http.init(params)) {
1952
+ @@ -210,6 +233,7 @@
1953
+ ctx_http.get ("/health", ex_wrapper(routes.get_health)); // public endpoint (no API key check)
1954
+ ctx_http.get ("/v1/health", ex_wrapper(routes.get_health)); // public endpoint (no API key check)
1955
+ ctx_http.get ("/metrics", ex_wrapper(routes.get_metrics));
1956
+ + if (!params.mmproj.path.empty()) ctx_http.get("/projector/status", ex_wrapper(routes.get_projector_status));
1957
+ ctx_http.get ("/props", ex_wrapper(routes.get_props));
1958
+ ctx_http.post("/props", ex_wrapper(routes.post_props));
1959
+ ctx_http.get ("/models", ex_wrapper(routes.get_models)); // public endpoint (no API key check)
apply-fpx-fixes-20261021-01.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Check or apply the version-pinned AiTable ROCmFPX native patch. No build/start."""
2
+ import argparse
3
+ import hashlib
4
+ import json
5
+ from pathlib import Path, PurePosixPath
6
+ import shutil
7
+ import subprocess
8
+ import uuid
9
+
10
+
11
+ def digest(path):
12
+ return hashlib.sha256(path.read_bytes()).hexdigest() if path.is_file() else None
13
+
14
+
15
+ def main():
16
+ parser = argparse.ArgumentParser(description=__doc__)
17
+ parser.add_argument('--source', required=True, type=Path, help='Separate upstream source checkout; do not target installed runtimes')
18
+ parser.add_argument('--apply', action='store_true', help='Apply after checks; default checks only')
19
+ args = parser.parse_args()
20
+ bundle = Path(__file__).resolve().parent
21
+ manifest = json.loads((bundle / 'fpx-fixes-manifest-20261021-01.json').read_text(encoding='utf-8'))
22
+ patch = bundle / 'aitable-fpx-native-fixes-20261021-01.patch'
23
+ if digest(patch) != manifest['patch_sha256']:
24
+ raise RuntimeError('Patch checksum mismatch')
25
+ source = args.source.resolve(strict=True)
26
+ if not source.is_dir():
27
+ raise RuntimeError('Source must be a directory')
28
+ targets = {}
29
+ for name, hashes in manifest['files'].items():
30
+ relative = PurePosixPath(name)
31
+ if relative.is_absolute() or '..' in relative.parts or ':' in name or '\\' in name:
32
+ raise RuntimeError('Unsafe manifest path: ' + name)
33
+ target = source.joinpath(*relative.parts)
34
+ if not target.resolve().is_relative_to(source) or target.is_symlink():
35
+ raise RuntimeError('Target escapes the source tree: ' + name)
36
+ if target.exists() and not target.is_file():
37
+ raise RuntimeError('Target is not a regular file: ' + name)
38
+ if digest(target) != hashes['before']:
39
+ raise RuntimeError('Source differs from pinned upstream (or already patched): ' + name)
40
+ targets[name] = target
41
+ git_apply = ['git', '-c', 'core.autocrlf=false', '-c', 'apply.whitespace=nowarn', 'apply']
42
+ subprocess.run([*git_apply, '--check', str(patch)], cwd=source, check=True, timeout=30)
43
+ if not args.apply:
44
+ print('CHECK PASSED: upstream ' + manifest['upstream_commit'] + '; no files modified')
45
+ return
46
+ backup = source.parent / (source.name + '-aitable-backup-' + uuid.uuid4().hex)
47
+ backup.mkdir()
48
+ for name, target in targets.items():
49
+ if target.exists():
50
+ saved = backup / name
51
+ saved.parent.mkdir(parents=True, exist_ok=True)
52
+ shutil.copy2(target, saved)
53
+ (backup / 'restore-manifest.json').write_text(json.dumps({
54
+ 'source': str(source), 'files': manifest['files'],
55
+ 'restore': 'Stop builds first. Restore saved files; remove only files whose before hash is null. Refuse to overwrite later changes unless verified.'
56
+ }, indent=2), encoding='utf-8')
57
+ print('BACKUP: ' + str(backup), flush=True)
58
+ # Git apply is atomic by default; never pass --reject or allow partial application.
59
+ subprocess.run([*git_apply, str(patch)], cwd=source, check=True, timeout=30)
60
+ for name, target in targets.items():
61
+ if digest(target) != manifest['files'][name]['after']:
62
+ raise RuntimeError('Post-apply checksum mismatch; inspect retained backup: ' + name)
63
+ print('APPLIED AND VERIFIED. No compilation, model download or service start performed.')
64
+
65
+
66
+ if __name__ == '__main__':
67
+ main()
configure-fpx-env-20261021-01.cmd ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ if "%~1"=="" (
3
+ echo Usage: call configure-fpx-env-20261021-01.cmd "extracted-runtime-package-root"
4
+ exit /b 2
5
+ )
6
+ if not exist "%~1\runtime\llama-server.exe" (
7
+ echo Runtime package root is invalid.
8
+ exit /b 2
9
+ )
10
+ set "PATH=%~1\runtime;%PATH%"
11
+ set "ROCBLAS_TENSILE_LIBPATH=%~1\runtime\rocblas\library"
12
+ set "ROCBLAS_USE_HIPBLASLT=0"
13
+ set "FPX_SHORT_PREFIX_CHECKPOINT=1"
14
+ set "GGML_FPX_NORM_SILU_FUSION=0"
15
+ set "GGML_CUDA_GDN_CHUNKED=0"
16
+ set "GGML_CUDA_GDN_CHUNKED_BF16=0"
17
+ set "GGML_FPX_Q8_ATTENTION=0"
18
+ set "GGML_FA_RDNA4_RESEARCH=0"
19
+ set "GGML_CUDA_DISABLE_ROPE_FWHT_SET_ROWS=1"
20
+ if not defined GGML_HIP_CK_ATTENTION set "GGML_HIP_CK_ATTENTION=0"
21
+ rem RAM checkpoint cache is a separate, bounded host-memory setting in MiB.
22
+ set "LLAMA_ARG_CACHE_RAM=1024"
23
+ exit /b 0
fpx-fixes-manifest-20261021-01.json ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "upstream_commit": "fb08d7cdb67082ddda15a25e64dc7ec413afc41c",
3
+ "paired_runtime_release": "20261021-01",
4
+ "patch_sha256": "dcdd6a8ebca68467f3faa9fd07e2f69f11e25446f100b4c214cc2619fc681f6b",
5
+ "files": {
6
+ "common/chat-auto-parser-generator.cpp": {
7
+ "before": "fbb122eb03840a8b2cf1b55acf1d5a0ee9c2f51de60a50a387ae5c55e5f29a18",
8
+ "after": "7448939113929b3e1b904b87fbb9fbc076fce84fbc486f3fd1cb0e25482bd7d4",
9
+ "frozen_native": "7448939113929b3e1b904b87fbb9fbc076fce84fbc486f3fd1cb0e25482bd7d4"
10
+ },
11
+ "common/chat.cpp": {
12
+ "before": "7569be8fe13a892ec5c629f0198109ac5efcb9f01276ad718c8ab614b707850b",
13
+ "after": "1a296ae1b0af51b1d847639cd1c99cd2700863d42e9286c62f2effbbbff15b81",
14
+ "frozen_native": "1a296ae1b0af51b1d847639cd1c99cd2700863d42e9286c62f2effbbbff15b81"
15
+ },
16
+ "common/chat.h": {
17
+ "before": "0eda44d1f9c2f0e456aaa11730d3119f99258ded03ca76d1473769f864610782",
18
+ "after": "0625c27306172d7cddadc227780a4e3a11b1f8e39872e9f6421097b40006681c",
19
+ "frozen_native": "0625c27306172d7cddadc227780a4e3a11b1f8e39872e9f6421097b40006681c"
20
+ },
21
+ "common/common.h": {
22
+ "before": "91e3190fd56820bad0bdff38b96c5bf5b3ef5d2e616a87a86596880f9135308e",
23
+ "after": "96c417eafc179c159f7fcef5b28e69f62396d3c15bd953584199b5299e3a0de3",
24
+ "frozen_native": "96c417eafc179c159f7fcef5b28e69f62396d3c15bd953584199b5299e3a0de3"
25
+ },
26
+ "common/json-schema-to-grammar.cpp": {
27
+ "before": "589c76fbc72159a28a9b2fd6a46248ce570960daeb688a92732315633069703d",
28
+ "after": "b3b4f0139cf41cdfa695b8d6c9c78c8a08f8e6f5fc7bfe0f85684b52b549f67b",
29
+ "frozen_native": "b3b4f0139cf41cdfa695b8d6c9c78c8a08f8e6f5fc7bfe0f85684b52b549f67b"
30
+ },
31
+ "common/peg-parser.cpp": {
32
+ "before": "71c7e355f89d99ff4c00b4142932b1a8033a07f2aa9db7b156af5ed479c4a4eb",
33
+ "after": "c3d0597d8bc219cde799ac5ae9ab52fb11414da2cec58bc4d613c2d4a1c17b2f",
34
+ "frozen_native": "c3d0597d8bc219cde799ac5ae9ab52fb11414da2cec58bc4d613c2d4a1c17b2f"
35
+ },
36
+ "common/sampling.cpp": {
37
+ "before": "9682ef2df8ae8ffb43d5dc561ed326a2e9e4b2e1a46388b59b9aee739ce01333",
38
+ "after": "bafabee9e05e436d960bd00d5fd24ea8d013899387fa616d2a3f5f6ceb412913",
39
+ "frozen_native": "bafabee9e05e436d960bd00d5fd24ea8d013899387fa616d2a3f5f6ceb412913"
40
+ },
41
+ "common/sampling.h": {
42
+ "before": "477c5336f273c0f584c71391ef82f459f8ad4f505d2343b68de87765c29028e0",
43
+ "after": "35b21345e60fccaee2b45159352a1ca20bd5ab57aee534edf62eea9359abe7bc",
44
+ "frozen_native": "35b21345e60fccaee2b45159352a1ca20bd5ab57aee534edf62eea9359abe7bc"
45
+ },
46
+ "ggml/src/ggml-cuda/fattn-common.cuh": {
47
+ "before": "8345bbd1f857d1b496e9375c3dd01d6c503e01dace76f2759ebb6fb8e25584d0",
48
+ "after": "74cfd9e838da8ddf71537c1e8e98bb11c2dd8625c7bd2242546ec7934db29ed7",
49
+ "frozen_native": "74cfd9e838da8ddf71537c1e8e98bb11c2dd8625c7bd2242546ec7934db29ed7"
50
+ },
51
+ "ggml/src/ggml-cuda/fattn-mma-f16.cuh": {
52
+ "before": "2523ee0bc7b00c62d3966c642b9a8804803183090161d0ffe92cbaedfd89983e",
53
+ "after": "c21bdec94bd3550ac69b730b621462a2604bc381fb227ef5aa2ec568e2855c8b",
54
+ "frozen_native": "c21bdec94bd3550ac69b730b621462a2604bc381fb227ef5aa2ec568e2855c8b"
55
+ },
56
+ "ggml/src/ggml-cuda/fattn.cu": {
57
+ "before": "16c3a9135b22f63e26653f5c639826b16c6884c64240d829d5d78641d1a15cfc",
58
+ "after": "60340eaee4b21f45fd02802c67db79f10842737cdf92bf289f5c54c4cddca4d3",
59
+ "frozen_native": "60340eaee4b21f45fd02802c67db79f10842737cdf92bf289f5c54c4cddca4d3"
60
+ },
61
+ "ggml/src/ggml-hip/CMakeLists.txt": {
62
+ "before": "dbee00eff8426df3d3f5f163636c48ab63ded77e8c868201cae441c0d27a9e02",
63
+ "after": "3470d7d8acf3fa09beb84a1e5b049afc840e1cb1483a9e881f1f75deea60fa0f",
64
+ "frozen_native": "9a9c2ee0c7844e44a47e4cd0b0bf82b53da5a195a83aa5dd744b62a92b143791"
65
+ },
66
+ "src/llama-context.cpp": {
67
+ "before": "49c33c06947c32390ebe81911ebfff25bf3b4803ca976ea825d2fcaa63696619",
68
+ "after": "2a1cc998d7051109a57d20b0795c617ab4ba3ac3f4fb0820907a33449bbb885c",
69
+ "frozen_native": "2a1cc998d7051109a57d20b0795c617ab4ba3ac3f4fb0820907a33449bbb885c"
70
+ },
71
+ "tools/cli/CMakeLists.txt": {
72
+ "before": "0af665c052ca6b89c3f6600b85ebee9183b928821f6ebec37dc2495cacd94917",
73
+ "after": "41f52f0f4f526fb835c65f2c2d1064daec059e735c2e164a53c91c2a1380d827",
74
+ "frozen_native": "41f52f0f4f526fb835c65f2c2d1064daec059e735c2e164a53c91c2a1380d827"
75
+ },
76
+ "tools/cli/prefix-offline-probe.cpp": {
77
+ "before": null,
78
+ "after": "00bb6eee48673acf0e8855f5368d096a3dd4ef707d4fb0e996d5ebc4d9437e9f",
79
+ "frozen_native": "00bb6eee48673acf0e8855f5368d096a3dd4ef707d4fb0e996d5ebc4d9437e9f"
80
+ },
81
+ "tools/server/prefix-short-policy.h": {
82
+ "before": null,
83
+ "after": "d7af7a6e5af8fa37e4ccddfba14068867f18e6c22194c9de93205cb3d95d32f6",
84
+ "frozen_native": "d7af7a6e5af8fa37e4ccddfba14068867f18e6c22194c9de93205cb3d95d32f6"
85
+ },
86
+ "tools/server/server-common.h": {
87
+ "before": "ba92ff90b21488b002d6d8d92a1d94abc5c39bc388d738939b3e4b32aabcb120",
88
+ "after": "4f99ce2a792d289f73caf6058e43285b950d994ae2151bf251bdef252ae2402a",
89
+ "frozen_native": "4f99ce2a792d289f73caf6058e43285b950d994ae2151bf251bdef252ae2402a"
90
+ },
91
+ "tools/server/server-context.cpp": {
92
+ "before": "154a0ee8158084109a12bdaaff8521ef1ccc829d48172beb7b816fd7011aafd6",
93
+ "after": "0cd397b0a02b784bf7ac382ede9ed9dd95ef18b3476250ee4ec1aa391ee33e0b",
94
+ "frozen_native": "0cd397b0a02b784bf7ac382ede9ed9dd95ef18b3476250ee4ec1aa391ee33e0b"
95
+ },
96
+ "tools/server/server-context.h": {
97
+ "before": "8f649d8246b40dd512e2cad39d8dc1ba59f73e23415a4a4c7fc190314fce211b",
98
+ "after": "10ddb15fbf1e72ce33baffece689c3b1f6c3fc6baf257efbf7ff1f5d22ee0415",
99
+ "frozen_native": "10ddb15fbf1e72ce33baffece689c3b1f6c3fc6baf257efbf7ff1f5d22ee0415"
100
+ },
101
+ "tools/server/server-projector.h": {
102
+ "before": null,
103
+ "after": "43293cbafc896484aeb3353cfc8afc67746f0699822678c13decd17c8ad7397d",
104
+ "frozen_native": "43293cbafc896484aeb3353cfc8afc67746f0699822678c13decd17c8ad7397d"
105
+ },
106
+ "tools/server/server-queue.cpp": {
107
+ "before": "2a4bff8757dd0dcb31c3633843271b0a26aaa8b36e07e809639ce19491f0fc64",
108
+ "after": "4e3269ae54b4a8fab30f86d4eaa74fb0930e131d49f1de94d0fad46d34b7e30d",
109
+ "frozen_native": "4e3269ae54b4a8fab30f86d4eaa74fb0930e131d49f1de94d0fad46d34b7e30d"
110
+ },
111
+ "tools/server/server-queue.h": {
112
+ "before": "da8c527f9d37d65f3457fe57c6042813b3581bbd36fcf82ab9dacd3f5fc74813",
113
+ "after": "6fbfcf333c8468ba6cfc67b21fb47d73427dec5d31dd3bad1f670b8d63b10e2c",
114
+ "frozen_native": "6fbfcf333c8468ba6cfc67b21fb47d73427dec5d31dd3bad1f670b8d63b10e2c"
115
+ },
116
+ "tools/server/server.cpp": {
117
+ "before": "67e8b3c9da2a5fd8443dfb1dd6e82b3b77a3581f632d119f194bbb103fc23364",
118
+ "after": "be77401cf215275b1216d005ebf0c0efe3c0441d18250be4276d9a176f9963df",
119
+ "frozen_native": "be77401cf215275b1216d005ebf0c0efe3c0441d18250be4276d9a176f9963df"
120
+ }
121
+ },
122
+ "publication_adjustment": "Only ggml-hip/CMakeLists.txt differs from frozen native code: local sibling CK import path is replaced by explicit FPX_CK_IMPORT_LIBRARY CMake parameter.",
123
+ "build_dependencies": {
124
+ "dependencies/CK/ck-attention.def": {
125
+ "bytes": 68,
126
+ "sha256": "ac30a0da25d6a9a9974ae8adf18382b6a13cca67821dacab5392fe9a983ed867"
127
+ },
128
+ "dependencies/CK/ck-attention.lib": {
129
+ "bytes": 1692,
130
+ "sha256": "3ae9dddd19e9b212d2ce401d47801e5f83745032f3c12f7b47bc6ac7eeecca17"
131
+ }
132
+ },
133
+ "ck_runtime_dll_sha256": "96ecc9b94fc7ecc4f2e2dcccc3230b8b5bb950c5774ea7a166665f07a4376ea2",
134
+ "no_models": true,
135
+ "full_source_archive": false,
136
+ "full_rebuild_verified": false,
137
+ "gpu_test_this_publication": false
138
+ }