v0.3.0: MQ4R + Redline across RDNA - #534
Conversation
Possible gfx1100 MQ4R long-prefill regression in betaThank your effort in putting together this v0.3.0 promotion PR. While validating the headline Qwen3.6-35B-A3B MQ4R path, I came across a result that might indicate a gfx1100 regression in beta and might be useful to consider as part of the final hardware matrix. Environment: 2 × Radeon Pro W7900 ( The matched results were:
The clean-beta result might suggest that my uncommitted local changes are not the primary cause. The MQ4R artifact also has the expected size and digest, which makes weight corruption less likely. Reproduction: HIPFIRE_KV_MODE=q8 HIPFIRE_DPM_WARMUP_SECS=3 ./target/release/examples/bench_qwen35_mq4 ~/.hipfire/models/qwen3.6-35b-a3b.mq4r --prefill 8192 --prefill-runs 1 --warmup 2 --gen 8The 8,192-token prefill completes normally, after which beta fails or stalls at The same boundary also failed after a 32,768-token prefill. Short ordinary AR/MTP inference can still work, so this might be narrower than a general MQ4R failure. The reproducible trigger appears to be the long-prefill → first-single-token transition. In this test series, That commit changes Possible next steps
I would be happy to rerun any additional configuration that might help narrow this down further. |
|
mq4r vs mq3p VRAM A/B on gfx1100 — build flags, results, and repro Ran an isolated A/B measuring load-time VRAM for Environment
Build ( Load config (identical for both models)
VRAM measurement Peak taken as the max over rapid repeated reads during load through warm-up. Results (peak VRAM at warm-up, MTP off, kv=q8)
Per-allocation ledger (via an Reproduce
|
Replay system integration auditInvestigated whether the Redline replay system ( Current stateThe replay controller is completely inert in the daemon's inference path:
Root causeThe replay state machine starts at The replay system was wired into the old Performance impact: zeroMeasured DeepSeek V4 MTP on both branches (clean builds, post-warmup, 3 runs each):
The 36 beta commits (~23K net lines) — including the Redline crates, gfx1151 i8-MMQ prefill path, attention tile tuning, and additional kernels — produce no measurable performance delta for DSv4 daemon-based spec-decode inference on this hardware. The i8-MMQ and tile32 gating conditions (large-batch prefill only) never trigger during MTP decode verify (batch_size ∈ {2,3}). Work needed to activate replay for all model families
This is not a beta-vs-master regression — the infrastructure was added but the integration into the production inference path across arch families was not completed. The PR description correctly lists Redline as an in-tree substrate; the notes above describe the remaining gap for daemon-side activation. |
|
@fivetide — thank you for digging into this. Your gfx1151 result found a real integration gap: One scope correction, because it points toward a useful contribution: There is now a better non-Qwen reference than the Qwen adapter: Useful source landmarks:
Please use that checkpoint as a reference, not a wholesale cherry-pick; it is a large branch checkpoint. If you want to help with DeepSeek, the clean first contribution would be a DeepSeek plain-AR capture boundary on gfx1151, with no automatic-default or speedup claim yet:
A first PR that adds only the plain-AR adapter boundary, negative gates, and a stable/reconciled capture fingerprint would already be valuable. We can review that boundary and help work through the gfx1151 PM4 lowering from there. The immediate goal is not “make the env flag fast”; it is “make one exact DeepSeek forward safely lowerable and observable,” then optimize it deliberately. |
|
@ghazni101 — thank you; your 23.79 GB observation was real and led us to the right pressure point. This is now fixed on We reproduced with the same MQ4R artifact digest (
That recovers 4,349,300,736 bytes (~4.35 GB / 18.3%) without dropping any requested tensor payload. The trace narrowed the non-weight delta further than the original cross-format ledger: uniform MQ4R created 20,480 separately owned expert-weight BOs ( Validation on the fixed binary:
The optimization is deliberately gfx11-only. An initial gfx12 A/B exposed a HipGraph regression from the large shared BOs, so gfx12 retains its prior allocation behavior rather than admitting that regression. Please rerun your exact container recipe against |
Follow-up: gfx1100 long-prefill to first-decode failure isolatedThank you for the additional investigation and for fixing the separate MQ4R allocation-count issue in I isolated the latter failure to
This explains why setting I opened PR #538 against
The memory cost is only 8 KiB per scratch instance, so it is unrelated to the multi-gigabyte residency issue fixed in Validation used the same MQ4R artifact digest reported earlier:
There is no performance claim from the single-run on/off timing; the important result is restoring correctness at the reproducible transition boundary while retaining the default-enabled fusion. This PR intentionally does not change the fused-kernel algorithm or dispatch policy: it only corrects scratch capacity and adds pre-launch validation. The resulting VRAM increase is just 8 KiB per scratch instance. |
Verification: commit 0e5762b resolves the ~4 GB mq4r vs mq3p VRAM gapTested on gfx1100 (RX 7900 XTX, 24 GB VRAM, ROCm core-7.14, HIP 7.14). BuildBinary confirmed: 22 MB ELF, Methodology
ResultsWith MTP sidecar loaded (446 MB on disk / ~582 MB in VRAM)
Without MTP sidecar (moved to /tmp, zero MTP lines in log)
Both show the same ~320 MB gap regardless of MTP (MTP adds ~582 MB equally to both models) and regardless of KV cache size (tested both max_seq=512 and max_seq=8192 — identical delta). File size vs VRAM delta
The 1.46 GB file-size delta does not carry through to VRAM because the two quant formats (MQ4 vs MQ3 group schemes, e.g. VerdictThe original gap of ~4,200 MB (measured July 17 on the beta build) is now ~320 MB under commit 0e5762b. The fix for the per-expert 512-BO allocation overhead in |
|
Rust-only control plane landed on What changed:
Validation:
Scope note: this validates the control-plane migration and script wiring. It is not a fresh physical-GPU or retained-PM4 certification, and this commit does not change Redline kernels or the daemon protocol. The Nix expressions were source-reviewed, but Nix tooling was not installed locally for parser evaluation. |
|
Performance and 200K context on gfx1100 (beta@2a960e1) Re-ran the headline Qwen3.6-35B-A3B MQ4R path against the latest beta HEAD. Performance — TG128 after proper thermal soakMeasured through the daemon FIFO protocol after 5x pre-heat generations (GPU at thermal steady state). The daemon's Decode is flat at ~211-213 tok/s across all tested generation lengths (64-8192, sigma < 0.5 tok/s) and prompt lengths (16-1024). First generation after a cold GPU is 5-15% slower — heat soak matters for reproducible numbers. Your 253.3 tok/s reproduces on this card at 212.3 tok/s (16% gap). GPU hits full boost (3139 MHz core, 1249 MHz memory) at 306W draw vs the 339W cap — the remaining gap is card-level power/clock variation, not software. Tested both ROCm 7.14 and 7.2, HIPFIRE_GRAPH=1, and COMPUTE power profile — none moved the needle. 200K contextThe daemon loaded and served with
200K context fits on 24 GiB with ~1 GiB headroom for generation scratch. Decode speed does not degrade with the larger KV allocation — only attention prefill costs increase. |
|
@ghazni101 — thanks, this result was useful. We reproduced your ~212 tok/s band as the ordinary HIP/HipGraph arm. The 253.3 tok/s headline is the retained-PM4 route; I have fast-forwarded For a matched TG128 reproduction, please use the benchmark below rather than hand-driving the daemon FIFO: git fetch origin beta
git switch beta
git pull --ff-only origin beta
cargo build --release --locked --features deltanet \
-p hipfire-runtime --example daemon
sha256sum ~/.hipfire/models/qwen3.6-35b-a3b.mq4r
# expected:
# 4685c140c46b1a6f31a0fd9053bf09d5faf1d2529d715b84794249b66cde0428
python3 scripts/redline_product_bench.py \
--model ~/.hipfire/models/qwen3.6-35b-a3b.mq4r \
--daemon target/release/examples/daemon \
--context 128 --iterations 128 \
--warmups 10 --warmup-iterations 32 --runs 10 \
--transport pm4 --kv-mode q8 --max-seq 2048 \
--work-dir .redline-work/gfx1100-pm4 \
--out .redline-work/gfx1100-pm4/report.json
jq '{valid, hip: .hip.tok_s, pm4: .auto.tok_s, speedup, route: .auto.route_proof}' \
.redline-work/gfx1100-pm4/report.jsonLeave the stationarity defaults intact. A valid result must have:
Our same-binary gfx1100 rerun measured HIP 218.32 tok/s and retained PM4 251.57 tok/s (251.17–251.70), with all ten timed rows positively observing replay. The retained tape was 604 launches / 22 kernels, sequence hash For ordinary hipfire config set hardware.devices 0
hipfire config set memory.kv_cache q8
hipfire config set speculation.mode off
hipfire config set replay.backend redline
hipfire config set replay.transport pm4
hipfire config list
|
|
also tested. git fetch origin beta
git switch beta
git pull --ff-only origin beta
cargo build --release --locked --features deltanet \
-p hipfire-runtime --example daemon
sha256sum ~/.hipfire/models/qwen3.6-35b-a3b.mq4r
# expected:
# 4685c140c46b1a6f31a0fd9053bf09d5faf1d2529d715b84794249b66cde0428
python3 scripts/redline_product_bench.py \
--model ~/.hipfire/models/qwen3.6-35b-a3b.mq4r \
--daemon target/release/examples/daemon \
--context 128 --iterations 128 \
--warmups 10 --warmup-iterations 32 --runs 10 \
--transport pm4 --kv-mode q8 --max-seq 2048 \
--work-dir .redline-work/gfx1201-pm4 \
--out .redline-work/gfx1201-pm4/report.json
jq '{valid, hip: .hip.tok_s, pm4: .auto.tok_s, speedup, route: .auto.route_proof}' \
.redline-work/gfx1201-pm4/report.json
...
4685c140c46b1a6f31a0fd9053bf09d5faf1d2529d715b84794249b66cde0428 /opt/zen/.hipfire/models/qwen3.6-35b-a3b.mq4r
hip: warming caches... took 2.45s
hip: stationary after 20 full-tg rows (15.81s, median=173.363, slope=-0.0078%/row, spread=0.221%)
hip: measured median=173.493 tok/s valid=True
hip: timed route proof valid=True retained_rows=0 positions=[] lifecycle_valid=True
auto: warming caches... took 3.00s
auto: stationary after 39 full-tg rows (37.16s, median=141.778, slope=-0.0003%/row, spread=0.555%)
auto: measured median=141.683 tok/s valid=True
auto: timed route proof valid=True retained_rows=10 positions=[128, 255] lifecycle_valid=True
hip=173.493 tok/s auto=141.683 tok/s speedup=0.81665 valid=True report=.redline-work/gfx1201-pm4/report.json
{
"valid": true,
"hip": {
"min": 173.26213295632,
"median": 173.49262094976075,
"max": 173.6050426579327
},
"pm4": {
"min": 141.52877635006118,
"median": 141.68284429407555,
"max": 141.8537463981145
},
"speedup": 0.8166505498530884,
"route": {
"valid": true,
"backend": "auto",
"transport": "pm4",
"rows": 10,
"require_complete_replay": true,
"retained_rows": 10,
"observed_positions": [
128,
255
],
"prepared_identities": [
[
733,
1,
2,
15960
]
],
"sequences": [
[
733,
23,
"3318ffca3daf2338"
]
],
"errors": []
}
} |
|
@Kaden-Schutt — reran the matched TG128 reproduction with the route-proof benchmark as requested. Result: valid, PM4 route positively proven on gfx1100. Environment
Result
This matches your same-binary gfx1100 rerun (HIP 218.32 / PM4 251.57) in direction and magnitude — ~10% retained-PM4 gain over the ordinary HIP arm. One deviation, disclosedWith everything at defaults, the run aborted twice with So the passing run used You may want to consider whether the default 60-row cap is tight for gfx1100 cold starts; happy to rerun with any settings you prefer. |
|
@taniguchi-taku-softm — no, you did not make a mistake. Your run was correct, and it found a real gfx12 defect. Thank you for posting the full report rather than just the speedup number. Your tape was already the right one. Comparing your report against the now-sealed gfx1201 fixture:
Identical dispatch count, identical kernel count, identical sequence hash, and HIP arms within 0.7% — so you captured exactly the intended route on comparable hardware. The only structural difference was the command-dword count, and that was the bug. Root cause: the retained PM4 path was caching register state that gfx12 requires re-emitted per dispatch. On gfx11 that state is invariant across replay and caching it is a win; on gfx12 part of it is compiler-dependent and must be re-emitted, so the retained tape was under-emitting and the replay arm lost to ordinary HIP. Fixed in Your report also drove two follow-ups:
To re-run against the sealed fixture: git fetch origin beta && git switch beta && git pull --ff-only origin beta
python3 scripts/golden-redline.py --arch gfx1201That verifies the registry card, sampling profile, model size and SHA-256, builds the release daemon if absent, runs the TG128 HIP-versus-retained-PM4 product benchmark with the sealed PM4 policy and stationarity parameters, and requires the exact 733-launch / 23-kernel / Expected: PM4 >= 197.0 tok/s at >= 1.10x. If it lands under that with a valid route proof, please post the JSON report again — the fixture is recorded evidence, not a timeless guarantee, and a second board disagreeing with it is exactly what I want to know about. See |
Apache-2.0 becomes the license offered for the work as a whole. No contributor file is relicensed in absentia: every per-file SPDX tag is unchanged (1073 Apache-2.0 / 69 dual / 48 MIT), LICENSE-MIT is retained as the operative grant for MIT-tagged files, and #305 remains the only mechanism for changing them. v0.2.1 and earlier stay dual-licensed. Recorded in docs/governance/relicense-2026-05.md. Fixes two manifest bugs: the workspace declared license = "MIT" (under-declaring even the dual posture), and redline-dispatch / redline-rocr pinned "MIT OR Apache-2.0" despite every source file in both carrying SPDX Apache-2.0. All 29 crates now report Apache-2.0. Removes the four agentic gate workflows (claude-review, gpu-gates, gate-merge, gate-sweep). None reached the release line: every beta CI run since 2026-07-17 died at startup_failure, and both PR-triggered gates are draft-guarded. GPU validation is unchanged and remains manual per docs/VALIDATION.md. Allows BSD-3-Clause (subtle <- rustls) and CDLA-Permissive-2.0 (webpki-roots <- ureq <- hipfire-cli), which cargo-deny rejected once CI could actually start. Both Apache-2.0-compatible, notice-retention only. This unblocks the blocking supply-chain job on #534. Points canonical URLs at warpfront/hipfire after the org transfer, including the registry fetch URL and all installers. uninstall.sh deliberately still recognizes the old Kaden-Schutt origin so pre-transfer installs remain cleanable; covered by a new regression test.
Only one Glimmer size exists, so `:30b` carried no information. The canonical tag is now bare `muse-glimmer` (colonless tags already exist here, e.g. `deepseek-v4-flash`, `north-mini-code`), and the two artifacts are separate SKUs: muse-glimmer muse-glimmer-30b.mq4 18.61 GB MQ4 body + Q8 attention muse-glimmer:fast muse-glimmer-30b.mq4r 16.26 GB MQ4 body + MQ4 attention muse-glimmer:draft muse-glimmer-30b-dflash.mq4 1.36 GB arch 23, pairs with either The trunk is the quality SKU; :fast is 2.35 GB smaller and ~14% quicker on AR decode (32.3 vs 28.4 tok/s, gfx1201) because MQ4 attention streams half the bytes per token. Under DFlash the gap nearly vanishes (229.3 vs 225.4 on the merge_sort fixture) since batched verify amortizes the extra attention bytes. `muse-glimmer` was previously an alias, so it had to stop being one to become a tag. `muse-glimmer:30b` is retained as a back-compat alias deliberately, not for politeness: scripts/serve_harness.py `infer_tag` derives it from the artifact filename via `muse-glimmer-(\d+(?:\.\d+)?b)`, so dropping it would break sampling resolution for every harness run. `:latest` and `:quality` also point at the trunk. The registry test now defends the new contract instead of tracking it: canonical tag and file, all three aliases resolving to the trunk, `:fast` being a distinct entry on a different artifact rather than an alias, and the drafter at arch 23. registry_gen --check: up to date. v1.json diff touches only the Glimmer tags and aliases; every other entry is byte-identical.
The sampled path downloaded the draft probability rows into a local `downloaded` binding and never assigned it to `q_rows_host`, then indexed `q_rows_host` — an empty Vec — to sample each draft and read q at the drafted token. Any sampled window with n_draft_rows > 0 slices an empty Vec. rustc was reporting this as `unused variable: downloaded`. My build check filtered on `^crates.*error`, so the one diagnostic that named the bug was discarded before I read it. The measured sampled figures from 5bd212401 (tau 7.353, 47.3->48.6 tok/s, 1.73x) are therefore not trustworthy and are re-measured on this commit. Fix is the missing assignment, placed with a comment recording why the store must happen before verify overwrites bundle.state.logits_batch.
Reverts 5bd212401 and its support fix 9aea9fe17. The naive chain-sample path is the one with a real measurement behind it; rejection sampling never worked. What actually happened, in order: - f18c0bf8c (naive chain-sample) was measured by a script that DID rebuild (daemon md5 923a6031...): sampled 47.3 tok/s avg, tau 2.98-8.52, coherent, empty=0 — 1.68x over AR 28.1. Verified. - 5bd212401 (rejection) was "measured" by a script that did NOT rebuild, so it re-ran the naive binary and I reported 48.6 tok/s as a rejection result. That +2.7% was run-to-run noise. - First honest run of the rejection binary: tau 1.000, accepted 0/135 proposed, 27.2 tok/s against 28.1 on plain AR. Every window rejects at position 0 and commits only the bonus. Output stays coherent, so it fails silently. The root cause is not yet identified. It is NOT the tau/Z nucleus truncation I first blamed: norm.rs:676-685 documents that `probs` is the full NORMALIZED softmax and only truncation is left to the consumer, so sampling an untruncated row is a valid distribution and cannot explain systematic rejection. Likely a row/token misalignment between the draft q rows and the target p rows, but I am not shipping a guess. Rejection sampling remains the right algorithm — accept min(1, p/q) dominates "target's single draw happens to match" — and the GPU `chain_accept_spec_f32` path is mapped and ready. It goes back in when it demonstrably beats 1.68x, on a binary that was rebuilt in the same run. Keeps b5fe63935 (drafter window 512) and 008b66cf6 (registry retag); both are independent and separately validated.
The drafter's logits rank tokens well but carry almost no dynamic range, so their softmax is not a usable distribution: measured draft peak 0.0007 against target peak 0.9998 on the same token, both rows summing to 1.0, argmax agreeing. A uniform distribution over the 202048-token vocab would be 4.95e-6, so the draft's peak is barely above uniform. Costs nothing today — greedy takes the argmax and the sampled path accepts on p_t(draft) from the target's distribution, both scale-invariant. It is fatal to anything that treats the drafter's softmax as a distribution: speculative rejection sampling accepted 0 of 135 proposals (tau 1.000, 27.2 tok/s vs 28.1 on plain AR) while still emitting coherent text, i.e. it failed silently. Records the mechanism (draft path skips the target final_norm that verify applies), the two traps for anyone recalibrating (per-channel norm would move the argmax and so must re-validate greedy; and calibration buys no throughput because naive chain-sample already accepts at ~p_t where the target is confident), and where the evidence came from.
Verdict: nothing structural to change. 3.8 self-identifies as the Qwen3.5 family
(architectures Qwen3_5MoeForCausalLM, model_type qwen3_5_moe_text) and every
config field it carries already appears in Qwen3.6, which we ship support for.
Inspected upstream config/tokenizer metadata only, no weights. The apparent 39
new fields are a nesting artifact: 3.6 is VL-wrapped so its fields live under
text_config.*, while the text-only 3.8 proxy has them at top level, and both the
router (safetensors_source.rs:198) and the parser (qwen35.rs:491) resolve that
with the same config.get("text_config").unwrap_or(config) fallback.
Also confirms: vocab_size 248320 is identical to 3.6 so the tokenizer is
unchanged; layer_types 3-linear:1-full is 3.6's pattern and already data-driven
into DeltaNet; head_dim 256 is exactly what adaptive KV requires;
attn_output_gate and norm_topk_prob are both already covered.
Records the day-zero checklist (config-diff first, arch_id before quantizing,
serve route before any number), the ranked residual risks (a new layer_types
value is the highest-consequence unknown because unknown strings collapse
silently to FullAttention), and the non-risks so they are not re-litigated.
safetensors_to_ggml_name had no arm for Glimmer's attention output gate, so all 52 self_attn.gate_proj tensors fell through to None and silently missed AWQ — even though awq_eligible matches gate_proj.weight and the published imatrix carries blk.N.attn_gate.weight.in_sum2 (K=6656) for every layer. Glimmer-specific: main.rs:15302 documents self_attn.gate_proj as the new Glimmer name gating attention output before o_proj; Qwen's gate is linear_attn.in_proj_z, which already mapped. No collision, since a layer is either full- or linear-attention. Matters for an MQ4-attention AWQ build, where main.rs:15282 asserts the gate follows --format rather than joining the Q8 fixed tier. With this arm all 8 slots x 52 layers = 416 lookups resolve, matching the imatrix's 416 in_sum2 entries.
…able
Three MQ4 Glimmer artifacts now exist with an identical recipe differing only in
AWQ scope: no-AWQ trunk, F1 (312 sidecars), F2 (416). AWQ costs 4-7 MB of F16
sidecars and nothing at runtime.
hfim was obtained rather than generated: bartowski/Muse-Glimmer-30B-GGUF ships a
13.4 MB imatrix, verified structurally against Glimmer (416 in_sum2, 52 layers,
K in {6656,4096,19968} = hidden/q_dim/intermediate, all 256-divisible). Preferred
over unsloth on June evidence (b7507ef: +11pt HumanEval; 0dfb81dc7: unsloth-imat
measured worse) — and unsloth's Glimmer GGUF ships no imatrix at all.
Records why no native collector was needed, and that generating hfhs still would
require a new tap: Gpu::hessian_capture is consumed only inside
run_moe_decode_cpu_fallback, which a dense model never reaches. The consumer side
(BlockHessianAcc, .hblk E8H1, load_hessian_blocks) is not MoE-gated, so GPTQ-E8
does apply to dense.
Recipe now evidence-backed rather than guessed: alpha 0.55 (9ed82fc kept it
per-arch for transformers; May's 0.5 was for AWQ+aware-GPTQ), F1 scope
(41b3efc shipped v3-awq-f1), paper formula not AutoAWQ (which measured 1.8257).
Key negative result, recorded so it is not mistaken for a win: AWQ F1's greedy
output is BYTE-IDENTICAL to the non-AWQ trunk on the merge_sort fixture
(329b4372...), and the tau spread across all three is 1-3 committed tokens over
10 cycles. That fixture cannot measure AWQ quality; these numbers must not be
cited for or against AWQ.
The blocker is named precisely: eval_hipfire is qwen35-only, there is no arch-14
evaluator and no Glimmer kldref, so AWQ quality is currently unmeasurable.
Closing it needs eval_hipfire_glimmer (template: eval_hipfire_gemma4) plus a
Glimmer F32 reference via build_kld_ref_native. Until then neither AWQ artifact
should become the default.
The Oh My Pi harness writes per-session bookkeeping, worktrees, and artifact caches to .omp/ in the repo root. It was untracked but not ignored, so it showed up in every `git status` and was one `git add -A` away from being committed. Ignore it alongside .claude/.
Adds the arch-14 arm of the llama-free KLD pair, mirroring the gemma4
(arch 13) arm:
build_kld_ref_native_glimmer — runs an F32 oracle over the corpus and
writes top-K reference log-probs
eval_hipfire_glimmer — scores a candidate against that reference
Both share the HFKLDR beta format byte-for-byte with the qwen35 and
gemma4 arms: 32-byte header, n_chunk*n_ctx u32 token block, then
top_k*8+8 per scored position. Verified against a real artifact —
(16842528 - 32) / (255*2056 + 512*4) = 32.0 chunks exactly for
q36a3b-wt2-f32.kldref.bin.
Arch-14 specifics that differ from the gemma4 arm:
* KV is Q8 and dual, allocated inside GlimmerState::new_with_max_seq
(39 sliding window=2048 + 13 full window=0/NoPE). Glimmer has no F32
KV path at all — the forward is Q8-only (kv_cache_write_q8_0 /
attention_q8_0_kv_swa) — so gemma4's F32-vs-asym3 reasoning does not
apply and asym3 is never involved.
* Both arms pin HIPFIRE_GLIMMER_KV_VMM=1 explicitly. Glimmer picks
between new_gpu_q8_vmm_capped_filtered and new_gpu_q8 on that var, so
leaving it implicit in one arm would let the two diverge silently for
anyone who has it set.
* BOS is prepended manually (bos_token 200000); raw encode() does not
add it, matching daemon.rs and dflash_spec_demo.rs.
* No Glimmer-specific graph env var exists, so only the two generic
determinism knobs are set.
The evaluator validates the reference magic and version BEFORE GPU init
and the vocab cross-check before the weight upload, so a wrong --ref path
fails in milliseconds instead of after a 111 GB load.
Known limit, documented in the runbook: because Glimmer's KV is Q8 on
both sides and the two arms quantize K/V derived from different weights,
the Q8 noise does NOT cancel — it leaves a floor. These numbers support a
relative ranking of candidates under a fixed Q8-KV regime, not an
absolute "MQ4 costs X KLD" claim.
The runbook records why the F32 oracle needs rented CDNA3 (111 GB
resident, pp>1 refused, and the loader widens F16 to F32 on upload so the
file format cannot shrink the footprint), and that the oracle feeds three
consumers, not one: the native HFIM collector, this KLD reference, and
the PyTorch-side GPTQ Hessian.
…pfire- prefix Publishes the Glimmer quants and moves every first-party model repo into the hipfire-models org without the redundant name prefix, so the path is hipfire-models/qwen3.6-27b rather than schuttdev/hipfire-qwen3.6-27b or hipfire-models/hipfire-LFM2.5-350M. New: hipfire-models/muse-glimmer-30b — the three local-only Glimmer entries now resolve. Uploaded and verified size + sha256 against the local hashes: 87bc776a… muse-glimmer-30b.mq4 18614785024 47ccfccd… muse-glimmer-30b.mq4r 16261223424 6fc0988e… muse-glimmer-30b-dflash.mq4 1357990400 The card discloses text-only scope (the build's 262 Q8_F16 + 156 MQ4G256 census is exactly 52*5 attention + embed + lm_head and 52*3 MLP, leaving nothing for a perception encoder despite upstream being image-text-to-text), the explicit HIPFIRE_DFLASH_DRAFT requirement (Glimmer has no filename auto-pairing), that .mq4r is not Redline-lowered, and that quality is unmeasured. The AWQ builds are deliberately withheld: they are derived from a GGUF imatrix and carry its tokenizer confound. 25 repos moved on the HF side (17 out of schuttdev/, 8 renamed in place). HF redirects renames automatically and preserves download counts, verified end-to-end: schuttdev/hipfire-qwen3.6-35b-a3b and hipfire-models/qwen3.6-35b-a3b resolve to the same Xet CAS object. So clients running against master's current registry keep working until this lands there. Third-party namespaces are untouched — nwoolmer/ and xfivetide/ keep their names, 4 references preserved. 55 of 59 repo strings rewritten. Also fixed: the artifact_urls_honor_endpoint_precedence test asserted a schuttdev path against the *bundled* registry and would have failed, and PROBE_NAMESPACES still probed the now-empty schuttdev namespace. The AGENTS.md DFlash endpoints and pinned 3.6 bench fixture are updated; the fixture's commit and digest pins are unchanged by the move.
nwoolmer, the DeepSeek V4 contributor, recommended and blessed promoting MQ2R to the default SKU. It is 4.0 GB smaller than MQ2-Lloyd (82.19 vs 86.18 GB) and is the certified gfx1201 TP route — a fact the old default's own description already stated. Implemented as two tag renames, so every artifact field (file, sha256, size_bytes, dspark, quant_recipe) travels with its own entry and there is no opportunity to mismatch a digest against a filename: deepseek-v4-flash -> deepseek-v4-flash-0731.mq2r (default) deepseek-v4-flash:mq2lloyd -> deepseek-v4-flash-0731.mq2lloyd (new tag) Both digests re-verified against HF: cbf2bbcf… / 82191359851 and 521c9687… / 86184309934. The subtle part is the aliases. deepseek-v4-flash-0731:mq2lloyd pointed at the *default* tag, so flipping the default naively would have served 82 GB of MQ2R under an mq2lloyd name. It now points at the :mq2lloyd tag. The five :mq2r aliases target the default, plus a new deepseek-v4-flash:mq2r alias so the explicit name still resolves. Version-generic names (deepseek4, deepseek-v4, :0731) follow the default as intended. No alias points at a missing tag. Two registry tests encoded the old contract and are updated rather than weakened — bundled_0731_mq2r_is_default_and_mq2lloyd_stays_addressable now asserts the bare tag and :mq2r serve the SAME artifact while :mq2lloyd stays distinct (assert_ne on the digests), and that the superseded artifact is still reachable by its own filename. tools/change_gate/routes.py is deliberately left pinned to the non-0731 deepseek-v4-flash.mq2lloyd / -mtp.mq2lloyd artifacts: the 0731 repo has no MTP file, and a validation route should be fixture-bound rather than follow a moving default.
…t forms `ds4` is the name the tree already uses everywhere — ds4-adapter-r128.bin, examples/ds4_length_sweep.sh, crates/hipfire-arch-deepseek4, docs/investigations/2026-08-01-ds4-parent-hessian-handoff.md — but it did not resolve. It now mirrors the full deepseek4:* surface: ds4, ds4:0731, ds4:mq2r, ds4:0731-mq2r, ds4:preview. Also closes a gap that promoting MQ2R to default exposed: :mq2r had short forms (deepseek4:mq2r, deepseek-v4:mq2r) but :mq2lloyd had none, so the superseded SKU was only reachable as deepseek-v4-flash:mq2lloyd or deepseek-v4-flash-0731:mq2lloyd. Anyone wanting the old default would naturally type deepseek4:mq2lloyd and get nothing. Added for both long prefixes and ds4, with and without the 0731 infix. 11 new aliases, 56 -> 67. The test asserts the mq2lloyd short forms as a loop over every spelling, so adding a :mq2r name without its :mq2lloyd counterpart fails rather than silently stranding the demoted SKU.
Operationalizes the readiness analysis (17ad3659b) into a runbook with
explicit stop conditions, so the release window is spent executing rather
than deciding.
Phase 0 is a metadata-only gate — config.json diff against Qwen3.6-27B's
text_config, no weights — with three named halt conditions: an unknown
layer_types value (collapses to FullAttention with no error, the
highest-consequence unknown), a model_type containing 3_8/3.8 (falls
through to arch 0 with only a warning), and MoE at Max fan-out meeting the
EP/TP refusals.
Adds the two things the analysis did not cover:
* Parent-model-card conformance as its own phase. The registry entry's
recommended_settings are transcribed from the card's
generation_config/prose, asserted by a test on the Glimmer precedent,
and verified to survive to a live generation. Absent card guidance
means a profile is omitted, not invented.
* An honest hardware matrix. gfx1100 (hipx 7900 XTX), gfx1151 (hipx
Strix Halo), gfx1201 (hiptrx R9700 / local 9070 XT) are testable;
gfx1101, gfx1102, gfx1150, and gfx1200 have no hardware, so claims say
"verified on gfx1100, gfx1151, gfx1201" and never "gfx11/gfx12".
Records that day 1 ships plain MQ4: AWQ needs an imatrix nobody will have
published yet, and hipfire's native HFIM collector is unmerged and wants a
111 GB F32 oracle. No DFlash draft exists for 3.8, so AR numbers must not
be reported as speculative.
Phase 5 flags the release-blocking detail: clients fetch the registry from
master, not beta, so both qwen3.8:27b and muse-glimmer stay unresolvable
until beta merges to master.
…non-thinking
Qwen3.8's published effort ladder is `xhigh` (default) > `medium` > `low`, and
its card states thinking mode is on by default. Two layers disagreed with that:
* `hipfire-config`'s REASONING_EFFORTS enum did not list `xhigh`, so
`RecommendedSettings::config_layer()` would have failed outright when
setting `reasoning.effort` from a card-faithful registry entry.
* `ThinkMode::from_str` ends in `_ => Self::NonThink`, so an unmapped
`xhigh` did not error — it silently resolved the model's own default
setting to NON-THINKING output. That is the worst shape of failure: no
panic, no warning, just a model that stops reasoning.
Adds `xhigh` to both value sets and maps `"max" | "xhigh" => Max`.
`medium` is deliberately NOT added to the config enum. The ladders do not
align — OpenAI's is `minimal < low < medium < high` — and `from_str` folds
`medium` into `Low` for cross-model compatibility. Accepting it as config
would let a curated entry claim a level the engine does not distinguish, so
Qwen3.8's `medium` and `low` collapse here and only the default (`xhigh`),
which is what the registry ships, is represented exactly. Recorded in the
doc comment rather than left for someone to rediscover.
The new test asserts both that `xhigh` maps to Max and that it is *not* the
NonThink fallthrough, plus pins the fallthrough itself so a future ladder
value added to the config enum but forgotten in `from_str` fails in review
instead of degrading to non-thinking in production.
Found by Phase 3 of the day-1 plan (card conformance) before any weights were
quantized — which is the phase existing for exactly this class of defect.
`qwen3.8:27b` named `qwen3.8-27b.mq4` but carried the MQ4R artifact's sha256/size_bytes (61072980…, 14980361216). Hugging Face served the same mq4r bytes under the .mq4 name, so `hipfire pull qwen3.8:27b` fetched the wrong artifact. Republished both artifacts to hipfire-models/qwen3.8-27b and split the SKUs: qwen3.8:27b qwen3.8-27b.mq4 15655791616 d220334a… quality trunk qwen3.8:27b-fast qwen3.8-27b.mq4r 14980361216 61072980… speed SKU Aliases qwen3.8 / qwen3.8:latest keep resolving to the trunk; qwen3.8:fast resolves to the new speed SKU. Regenerated registry/v1.json from the corrected HF tree (registry_gen.py --check reports up to date). Every existing assertion in bundled_qwen38_matches_the_release_contract passed for the entire time the entry was wrong, because none pinned content identity. Tests now assert sha256 + size_bytes for both SKUs and assert_ne on file AND digest, so an artifact swap cannot pass again.
Gemma 4 (13) and Muse Glimmer (14) were missing from the architecture-id table. Also notes that arch 22 (Gemma4 EAGLE draft) is claimed by Gemma4Carrier alongside 13.
Streaming decode arrived in bursts: ~41 tokens instantly, then ~0.5-1.1s of silence, repeating. Reported against a container on 4x R9700, but reproduced native, bare-metal, single-GPU — it is neither container- nor TP-specific. respond_streaming passed data_length: None, so tiny_http selected chunked encoding and wrapped the socket in chunked_transfer::Encoder. Encoder::new hardcodes with_chunks_size(output, 8192) and leaves flush_after_write off, and raw_print is documented "does not flush the writer" — so frames accumulated until 8 KiB, then burst. ChannelReader was already correct, returning one frame per read. Measured before (DS4-0731-mq2r TP3, gfx1201): mean 8169 bytes/burst, spread 3.0%. Frames per burst tracked bytes/frame (41 at 197 B for qwen, 37 at 219 B for DS4) while byte count stayed at ~8192 — a byte boundary, not a token one. Take the socket with Request::into_writer(), write the status line and headers, then frame each SSE chunk and flush it. The per-frame flush also drains tiny_http's 1 KB socket BufWriter (client.rs:63), which sits below the encoder. Acks now fire after write AND flush, so an ack means the bytes left the process — strictly stronger than the previous read-based timing that deliver_sse_terminal_ack depends on. After, same fixture: p50 17.1ms, p99 17.6ms, max 18.6ms, 0 stalls in 293 gaps, decode unchanged at 53.8 tok/s. Transport overhead measures below the noise floor between client-observed gaps and daemon-internal timing. No benchmark could have caught this: decode_tok_s is measured inside the daemon, upstream of the transport, and aggregate throughput is unaffected by buffering. Tradeoff: after into_writer() we cannot participate in tiny_http's keep-alive bookkeeping, so streaming responses now send Connection: close. Every other endpoint still uses request.respond and keeps keep-alive.
The RDNA4 table listed only the MQ2-Lloyd preview at 25.6 tok/s. MQ2-Lloyd is `deepseek-v4-flash-preview`; the shipping `deepseek-v4-flash` SKU is deepseek-v4-flash-0731.mq2r, which measures roughly double. Anyone benchmarking against the old row targets half the real number. Adds TP3 (53.1) and TP4 (54.3) MQ2R rows, keeps the Lloyd row labelled as superseded, and records the fixture (digest, greedy, speculation off, kv f32, 2052-token prompt, n=3 medians) so the numbers are reproducible.
Both families are already in the curated registry table but were absent from the release headline and the changelog, so the v0.3.0 notes understated what ships. Describes each architecture and states plainly that Glimmer's .mq4r is not PM4-lowered yet, so it does not take automatic Redline admission. No performance figures: neither family has an admission-gated gfx1201 measurement, and the Glimmer registry text is explicitly not a live baseline.
…#542) Apache-2.0 becomes the license offered for the work as a whole. No contributor file is relicensed in absentia: every per-file SPDX tag is unchanged (1073 Apache-2.0 / 69 dual / 48 MIT), LICENSE-MIT is retained as the operative grant for MIT-tagged files, and warpfront#305 remains the only mechanism for changing them. v0.2.1 and earlier stay dual-licensed. Recorded in docs/governance/relicense-2026-05.md. Fixes two manifest bugs: the workspace declared license = "MIT" (under-declaring even the dual posture), and redline-dispatch / redline-rocr pinned "MIT OR Apache-2.0" despite every source file in both carrying SPDX Apache-2.0. All 29 crates now report Apache-2.0. Removes the four agentic gate workflows (claude-review, gpu-gates, gate-merge, gate-sweep). None reached the release line: every beta CI run since 2026-07-17 died at startup_failure, and both PR-triggered gates are draft-guarded. GPU validation is unchanged and remains manual per docs/VALIDATION.md. Allows BSD-3-Clause (subtle <- rustls) and CDLA-Permissive-2.0 (webpki-roots <- ureq <- hipfire-cli), which cargo-deny rejected once CI could actually start. Both Apache-2.0-compatible, notice-retention only. This unblocks the blocking supply-chain job on warpfront#534. Points canonical URLs at warpfront/hipfire after the org transfer, including the registry fetch URL and all installers. uninstall.sh deliberately still recognizes the old Kaden-Schutt origin so pre-transfer installs remain cleanable; covered by a new regression test.
v0.3.0: MQ4R + Redline across RDNA
Summary
This draft promotes the accumulated
betastaging line toward hipfire v0.3.0.The release integrates Redline as an in-tree RDNA dispatch and retained-replay substrate, establishes Qwen 3.6 35B-A3B MQ4R as the headline performance SKU, refreshes the model/serving surface, and carries cleanly extracted contributor deltas from the PR backlog.
Contributor work was rebased or cherry-picked as a delta onto the current beta line rather than merged wholesale. That keeps unrelated older code from overwriting the newer modular runtime and Redline implementations.
Headline: MQ4R + Redline
Redline records hipfire's actual decode graph, derives resource dependencies, retains invariant command state, and lowers validated routes through public ROCr queue interfaces. The substrate supports the RDNA family from RDNA1 through RDNA4; optimized routes remain architecture- and workload-specific and fail closed to ordinary HIP dispatch.
Qwen 3.6 35B-A3B MQ4R ordinary AR with Q8 KV:
Short-context ranges were 253.04-253.48 tok/s on gfx1100, 115.02-115.18 tok/s on gfx1151, and 203.42-204.04 tok/s on gfx1201.
These are ordinary autoregressive results: no MTP, DFlash, speculative acceptance, reduced-output benchmark, or manual clock pinning. The gfx1201 campaign raised the path from approximately 110 to 203.9 tok/s.
User-facing release changes
hipfire list -ras the live source of truth.Contributor deltas included
PR #496 remains default-off behind its feature flag; no physical-GPU release claim is made for that experiment.
Additional beta work
redline-dispatchandredline-rocrcrates.Validation
Release metadata commit:
cargo check --workspace --libcargo test --lib --workspace --lockedpython3 scripts/registry_gen.py --checkbun run typecheckbun test: 288 passed, 0 failedgit diff --checkThe registry generator re-probed every curated Hugging Face repository and produced a valid strict superset with 54 models and 38 aliases.
The Rust commands complete with existing unused/dead-code warnings; no new build error was introduced by the release version bump.
Hardware evidence for the headline MQ4R table comes from the recorded beta/Redline product and eight-turn serving artifacts on gfx1100, gfx1151, and gfx1201. This final release-metadata commit did not rerun the physical GPU matrix.
Remaining before promotion