You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ci(lab): nightly fast-verbs CRUX smoke + tok/s on the ARM64 CUDA edge runner, ranked gap list #5254
No aprender workflow targets the ARM64 CUDA edge runner (labels cuda, ARM64). It is online and idle. Its standing role is quick CUDA tests plus a nightly end-to-end run of the fast verbs, and nothing exercises it.
Ask
Add one nightly LAB job on that runner:
CRUX smoke for the fast verbs.
Decode tokens/s for each fast verb.
One ranked "what to improve" list, biggest gap first, posted as a comment on this issue each night.
Constraints
LAB only. The job never runs inside a PR, merge-queue or release job, and it never blocks a merge or a release.
A red night updates this issue and stays silent. If it is red or unmeasured for 7 days, it stops until its owner brings it back.
No report-only, advisory or continue-on-error rows on any PR or release path.
Acceptance
The workflow runs on schedule plus workflow_dispatch on the ARM64 CUDA runner only.
Each night appends one comment to this issue with the run id, apr version and sha, a CRUX smoke pass/fail per verb, tok/s per verb, and the ranked gap list.
A planted regression (a verb forced to fail) shows up at the top of the list.
CRUX serve run: apr correct on 47 of 129; 6 wrong, 76 unanswered (top reason: refused: route POST /v1/completions takes a raw prompt and no reference renderer was given: unrendered text would ask a different question)
CRUX serve stream: apr correct on 64 of 129; 8 wrong, 57 unanswered (top reason: refused: route POST /v1/completions takes a raw prompt and no reference renderer was given: unrendered text would ask a different question)
CRUX chat: apr correct on 21 of 23; 2 wrong, 0 unanswered
CRUX run: apr correct on 17 of 19; 2 wrong, 0 unanswered
comparator ollama refused 304 cells: refused: ollama 0.17.6 imported the GGUF with no chat template (TEMPLATE {{ .Prompt }}), so it would complete the raw text rather than chat
Model Qwen3.5-2B-Q4_K_M.gguf (sha256 aaf42c8b7c3c…), apr 0.71.2 built --features cuda from #5292 head cebe27dc, ollama 0.17.6 on the same file. Every number below is the engine's own report (apr bench / apr qa / CRUX receipts); nothing is timed by the harness.
refused: qwen35 is not in the dense profile loader
stated refusal
Template fix confirmed. With #5292's ChatML template built from the GGUF's own tokenizer.chat_template, ollama answers every cell (it refused 304/304 before).
Every CRUX verdict is RED for one stated reason, "no ground-truth control (neither hf nor vllm answered)": this device has no reference engine. The correct counts above are per engine. This was a reduced run (run + chat, GPU, thinking off), because the full matrix (5 verbs × off/on × gpu/cpu) takes hours on this device. The nightly in #5292 runs all of it, and every not run cell above is a missing measurement there (exit 1), not a skip. Uncovered: serve and code on GPU and everything on CPU, in the next full nightly; apr code on CPU is #3978; CPU decode is #5276.
A first run this morning was void: host memory was held by an unrelated stray process, apr refused host RAM on every cell, and ollama ran at 1 tok/s. It was killed and the rows above re-measured on a clean box.
Five-whys: "CRUX 0/304 GREEN" while ollama refused every prompt
Problem. Nightly run 38531159254 reported CRUX RED, 0 GREEN of 304, and the job was green. ollama refused all 304 cells with "imported the GGUF with no chat template (TEMPLATE {{ .Prompt }})". No gate or alarm saw it.
#
Why
Answer
Fix
1
Why did ollama refuse every cell?
ollama 0.17.6 imported the Qwen3.5 GGUF with TEMPLATE {{ .Prompt }}, so every chat turn became a raw completion, and the harness correctly refused to judge that.
#5292: build a ChatML Go template from the GGUF's own tokenizer.chat_template, re-create the model, and check that the stored template is byte-identical. Otherwise refuse by name.
2
Why did ollama import it with no template?
The GGUF does carry tokenizer.chat_template. ollama 0.17.6 cannot translate that Jinja (the enable_thinking branches), and it silently falls back to {{ .Prompt }}.
#5292 (above). The import is checked with ollama show --template, never assumed.
3
Why was the template never written, for GGUFs that apr exports itself?
append_tokenizer_to_metadata returned early on the tokenizer.json path without writing tokenizer.chat_template. Only the APR-metadata fallback and the qwen35 path wrote it.
#5292: the tokenizer.json path now writes it (sources in order: the tokenizer, APR metadata, chat_template.jinja, tokenizer_config.json), with a unit test. The nightly now fails when the model carries no template.
4
Why did no gate see 304 refusals?
The CRUX judge maps a comparator that answered nothing to RED by design (no third state), and the nightly only printed the verdict. Nothing asked "did each engine answer anything?".
#5292: a fixed verb×device×engine matrix. Any combination with no cells or zero answers is a missing measurement, and the run exits 1.
5
Why was the nightly built without that check?
Its definition of done was "a verdict is printed", not "every row is measured". A RED from a dead comparator looks the same as a RED from wrong answers.
#5292: every row has a measured value or a stated, ticketed reason (#5276 CPU bench, #3978apr code on CPU). An absent or zero tok/s, TTFT or RSS fails the run.
The same chain for the skipped ollama_parity row
#
Why
Answer
Fix
1
Why was ollama_parity skipped?
apr qa skips the parity gate for model families its dense loader refuses, and Qwen3.5 is one of them.
#5292: --ollama-from-file runs the gate on this file.
2
Why did it not compare this file anyway?
The gate compared against a size-matched qwen2.5-coder:<size> tag, not the file under test.
#5292: ollama decodes the same GGUF through a pid-named import that is unloaded and removed on drop.
3
Why was a skip not a failure?
A skipped gate read as "not applicable" in the nightly.
#5292: a skipped parity gate is now a missing measurement (exit 1).
Why the gap rows were invisible until today
#
Why
Answer
Fix
1
Why was a 0.15x decode gap and a 6.9 GB RSS never reported?
No row measured either on this device: the parity gate skipped Qwen3.5 and then, once it ran, could not co-host both engines.
#5292 (rows are mandatory), #5302 (sequential loads)
2
Why does apr hold 6.9 GB for a 1.2 GB file?
The host build keeps an f32 token embedding plus prefill copies in the same unified pool.
The chat-template fix works: ollama now answers every run/chat cell. In the previous nightly it refused all 304.
Defects this run exposes (none of them is an apr correctness result):
76–78 serve cells per device are unanswered, on both engines. The route is POST /v1/completions, which takes a raw prompt, and the harness gives no reference renderer, so the cell is refused rather than judged. That is a harness gap: render with the GGUF's own template, or drop the route from the cell set and count it as missing.
The CRUX smoke verdict says 0 GREEN, 304 RED while the table above shows apr correct on 149 GPU cells. The verdict line and the per-cell table disagree; the verdict must be traced before anyone reads it.
ollama_parity could not run: the GPU capacity planner refused because only 1172 MiB of unified memory was free. The likely cause, not yet proven, is that ollama still held the model from the CRUX cells. Unload it (keep_alive: 0) before apr qa, then re-measure.
Peak RSS of 6.7 GiB for a 1.2 GiB Q4_K_M model is worth its own look. VmHWM covers the whole bench process, so split it by phase before calling it a leak.
Activity
fix(perf-matrix): re-vendor the matrix after adding the edge host
fix(edge-nightly): pin apr, compare to ollama through apr qa, clear S…
fix(edge-nightly): no silent truncation of reasons on the gap list
github-actions commentedon Oct 14, 2026
Edge nightly · model Qwen3.5-2B-Q4_K_M.gguf · not_measured: no gap list (build or tooling failed)
Run: https://github.com/paiml/aprender/actions/runs/38530433463
github-actions commentedon Oct 14, 2026
Edge nightly · model
Qwen3.5-2B-Q4_K_M· quantQ4_K_M· sha256aaf42c8b7c3cab2bf3d69c355048d4a0ee9973d48f16c731c0520ee914699223apr
apr 0.71.2 (494b63aa)(binary sha2563d4e03660b53) · backend: GPU (CUDA, Orin, 7607 MB VRAM)apr bench --fast)apr qa)Ranked gaps
serve run: apr correct on 47 of 129; 6 wrong, 76 unanswered (top reason: refused: route POST /v1/completions takes a raw prompt and no reference renderer was given: unrendered text would ask a different question)serve stream: apr correct on 64 of 129; 8 wrong, 57 unanswered (top reason: refused: route POST /v1/completions takes a raw prompt and no reference renderer was given: unrendered text would ask a different question)chat: apr correct on 21 of 23; 2 wrong, 0 unansweredrun: apr correct on 17 of 19; 2 wrong, 0 unansweredapr qaollama_parity skipped (Skipped: Dense-loader gate: Gated DeltaNet runs on the CPU (CPU: Qwen3.5 / Qwen3.8 hybrid GGUFs are refused entirely — no fallback exists #3091) and the GPU (GPU: implement Gated DeltaNet / SSM inference for Qwen3.5 / Qwen3.8 hybrid GGUFs #3090), but this gate measures through OwnedQuantizedModel::from_mapped, which has no hybrid path)Run: https://github.com/paiml/aprender/actions/runs/38531159254
noahgift commentedon Oct 14, 2026
Edge parity rows, measured (runner label
jetson, 8 GB unified-memory Orin)Model
Qwen3.5-2B-Q4_K_M.gguf(sha256aaf42c8b7c3c…), apr 0.71.2 built--features cudafrom #5292 headcebe27dc, ollama 0.17.6 on the same file. Every number below is the engine's own report (apr bench / apr qa / CRUX receipts); nothing is timed by the harness.apr bench --fastrun, median of 19 cellsapr bench)psSIZE, not the same measure)apr qa --ollama-from-file)bench --no-gpuapr profileTemplate fix confirmed. With #5292's ChatML template built from the GGUF's own
tokenizer.chat_template, ollama answers every cell (it refused 304/304 before).Verb × device matrix (correct / answered / cells, thinking off)
runchatserve runserve streamcodeEvery CRUX verdict is RED for one stated reason, "no ground-truth control (neither hf nor vllm answered)": this device has no reference engine. The correct counts above are per engine. This was a reduced run (run + chat, GPU, thinking off), because the full matrix (5 verbs × off/on × gpu/cpu) takes hours on this device. The nightly in #5292 runs all of it, and every
not runcell above is a missing measurement there (exit 1), not a skip. Uncovered:serveandcodeon GPU and everything on CPU, in the next full nightly;apr codeon CPU is #3978; CPU decode is #5276.A first run this morning was void: host memory was held by an unrelated stray process, apr refused host RAM on every cell, and ollama ran at 1 tok/s. It was killed and the rows above re-measured on a clean box.
Five-whys: "CRUX 0/304 GREEN" while ollama refused every prompt
Problem. Nightly run 38531159254 reported CRUX RED, 0 GREEN of 304, and the job was green. ollama refused all 304 cells with "imported the GGUF with no chat template (
TEMPLATE {{ .Prompt }})". No gate or alarm saw it.TEMPLATE {{ .Prompt }}, so every chat turn became a raw completion, and the harness correctly refused to judge that.tokenizer.chat_template, re-create the model, and check that the stored template is byte-identical. Otherwise refuse by name.tokenizer.chat_template. ollama 0.17.6 cannot translate that Jinja (theenable_thinkingbranches), and it silently falls back to{{ .Prompt }}.ollama show --template, never assumed.append_tokenizer_to_metadatareturned early on thetokenizer.jsonpath without writingtokenizer.chat_template. Only the APR-metadata fallback and the qwen35 path wrote it.tokenizer.jsonpath now writes it (sources in order: the tokenizer, APR metadata,chat_template.jinja,tokenizer_config.json), with a unit test. The nightly now fails when the model carries no template.apr codeon CPU). An absent or zero tok/s, TTFT or RSS fails the run.The same chain for the skipped
ollama_parityrowollama_parityskipped?apr qaskips the parity gate for model families its dense loader refuses, and Qwen3.5 is one of them.--ollama-from-fileruns the gate on this file.qwen2.5-coder:<size>tag, not the file under test.Why the gap rows were invisible until today
audit(surface): re-point the rows this branch's two qa/profiling edit…
fix(export): qwen35 GGUF wrote tokenizer.chat_template twice
noahgift commentedon Oct 14, 2026
Edge nightly, first full run with the #5292 harness (Orin,
Qwen3.5-2B-Q4_K_Msha256aaf42c8b…, apr 0.71.2 binary sha256afbf4b654285)apr bench --fast)bench --no-gpuflag (#5276)apr codeon CPUVerb × device (correct / answered / cells):
runchatserve runserve streamcodeThe chat-template fix works: ollama now answers every
run/chatcell. In the previous nightly it refused all 304.Defects this run exposes (none of them is an apr correctness result):
servecells per device are unanswered, on both engines. The route isPOST /v1/completions, which takes a raw prompt, and the harness gives no reference renderer, so the cell is refused rather than judged. That is a harness gap: render with the GGUF's own template, or drop the route from the cell set and count it as missing.0 GREEN, 304 REDwhile the table above shows apr correct on 149 GPU cells. The verdict line and the per-cell table disagree; the verdict must be traced before anyone reads it.ollama_paritycould not run: the GPU capacity planner refused because only 1172 MiB of unified memory was free. The likely cause, not yet proven, is that ollama still held the model from the CRUX cells. Unload it (keep_alive: 0) beforeapr qa, then re-measure.apr profilerefusesqwen35(dense loader only). Profiling needs the hybrid path (GPU: implement Gated DeltaNet / SSM inference for Qwen3.5 / Qwen3.8 hybrid GGUFs #3090/CPU: Qwen3.5 / Qwen3.8 hybrid GGUFs are refused entirely — no fallback exists #3091) or this row stays missing.The run exits 1 on the missing rows, as designed.
7 remaining items
github-actions commentedon Oct 16, 2026
Edge nightly · model Qwen3.5-2B-Q4_K_M.gguf · not_measured: no gap list (build or tooling failed)
Run: https://github.com/paiml/aprender/actions/runs/38894944048
github-actions commentedon Oct 16, 2026
Edge nightly · model Qwen3.5-2B-Q4_K_M.gguf · not_measured: no gap list (build or tooling failed)
Run: https://github.com/paiml/aprender/actions/runs/38941050008
github-actions commentedon Oct 17, 2026
Edge nightly · model Qwen3.5-2B-Q4_K_M.gguf · not_measured: no gap list (build or tooling failed)
Run: https://github.com/paiml/aprender/actions/runs/38982365249
github-actions commentedon Oct 17, 2026
Edge nightly · model Qwen3.5-2B-Q4_K_M.gguf · not_measured: no gap list (build or tooling failed)
Run: https://github.com/paiml/aprender/actions/runs/39006904903
github-actions commentedon Oct 18, 2026
Edge nightly · model Qwen3.5-2B-Q4_K_M.gguf · not_measured: no gap list (build or tooling failed)
Run: https://github.com/paiml/aprender/actions/runs/39107365210
github-actions commentedon Oct 21, 2026
Edge nightly · model Qwen3.5-2B-Q4_K_M.gguf · not_measured: no gap list (build or tooling failed)
Run: https://github.com/paiml/aprender/actions/runs/39565283712
noahgift commentedon Oct 22, 2026
Tracked in #3999 (triage 2026-10-22).
github-actions commentedon Oct 22, 2026
Edge nightly · model Qwen3.5-2B-Q4_K_M.gguf · not_measured: no gap list (build or tooling failed)
Run: https://github.com/paiml/aprender/actions/runs/39743335295
noahgift commentedon Oct 22, 2026
tracked in #5348
github-actions commentedon Oct 25, 2026
Edge nightly · model Qwen3.5-2B-Q4_K_M.gguf · not_measured: no gap list (build or tooling failed)
Run: https://github.com/paiml/aprender/actions/runs/40163902584
github-actions commentedon Oct 26, 2026
Edge nightly · model Qwen3.5-2B-Q4_K_M.gguf · not_measured: no gap list (build or tooling failed)
Run: https://github.com/paiml/aprender/actions/runs/40272245739
github-actions commentedon Oct 29, 2026
Edge nightly · model Qwen3.5-2B-Q4_K_M.gguf · not_measured: no gap list (build or tooling failed)
Run: https://github.com/paiml/aprender/actions/runs/40827106719
github-actions commentedon Oct 30, 2026
Edge nightly · model Qwen3.5-2B-Q4_K_M.gguf · not_measured: no gap list (build or tooling failed)
Run: https://github.com/paiml/aprender/actions/runs/41012146002