Skip to content

v0.3.0: MQ4R + Redline across RDNA - #534

Merged
Kaden-Schutt merged 1276 commits into
masterfrom
beta
Aug 15, 2026
Merged

Kaden-Schutt merged 1276 commits into
masterfrom
beta

Conversation

@Kaden-Schutt

Copy link
Copy Markdown
Collaborator

Summary

This draft promotes the accumulated beta staging 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:

Architecture TG128 AR 8-turn average Final turn Session health
gfx1100 253.3 tok/s 191.0 tok/s 160.3 tok/s at 18.2K 8/8 clean
gfx1151 115.1 tok/s 92.2 tok/s 82.5 tok/s at 21.3K 8/8 clean
gfx1201 203.9 tok/s 169.5 tok/s 146.7 tok/s at 22.2K 8/8 clean; both recall probes 3/3

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

  • Rewrites the README around MQ4R/Redline, the measured cross-arch results, and the actual curated registry rather than mixing runtime architectures with published pull tags.
  • Documents all 54 registry entries by their real, fully qualified tags and points users to hipfire list -r as the live source of truth.
  • Updates the MQ4R model card description with current gfx1100/gfx1151/gfx1201 measurements.
  • Bumps the Rust workspace and lockfile packages from 0.2.1 to 0.3.0.
  • Adds the standalone v0.3.0 release history to CHANGELOG.md.

Contributor deltas included

PR Contributor Extracted scope
#465 @fivetide LLaMA Site A attention dispatch and expanded HFQ KV policy
#466 @fivetide Qwen2 instruct chat-template application
#468 @fivetide dots.ocr text-only daemon generation
#473 @fivetide VibeThinker-3B MQ4/MQ6 registry entries
#476 @fivetide DeepSeek V4 gfx1151 i8-WMMA prefill
#479 @fivetide MiniMax gfx1151 grouped/dense prefill and fail-closed guards
#480 @fivetide MiniMax projections through execute_steps
#482 @fivetide Fused QKV bias across per-row dtypes, isolated from Redline's ABI
#487 @fivetide Architecture-generic MMQ screening
#496 @HUSRCF Default-off RDNA3 QKVZA split-tail experiment
#497 @fivetide Multi-stage runtime and GPU gate-runner Containerfile
#501 @fivetide DSpark request telemetry and non-fatal pre-warm recovery
#513 @nwoolmer Native Qwen XML tool calls across CLI, daemon, and cached history
#528 @nwoolmer DeepSeek V4 DSpark sidecar registration and re-pull discovery
#529 @HUSRCF Quickstart refresh and historical benchmark labeling

PR #496 remains default-off behind its feature flag; no physical-GPU release claim is made for that experiment.

Additional beta work

  • Intentional integration of the current Redline line.
  • gfx1100, gfx1151, and gfx1201 MQ4R kernel/replay tuning with architecture isolation.
  • In-tree redline-dispatch and redline-rocr crates.
  • DSpark telemetry, drafter discovery, and pre-warm recovery.
  • Qwen native XML tool-call routing.
  • DeepSeek V4 and MiniMax gfx1151 prefill work.
  • LLaMA KV dispatch, Qwen2 template handling, dots.ocr text-only serving, generic MMQ screening, and fused-QKV bias.
  • Bun/TypeScript, TUI, HTTP, hashing, and dynamic-loading dependency refresh.
  • Adoption of clap, safetensors, half, tracing, and proptest.
  • Redline compiled-plan/property coverage and cargo-deny CI integration.
  • Removal of the stale modular rebase helper and dead AWQ router exclusion.

Validation

Release metadata commit:

  • cargo check --workspace --lib
  • cargo test --lib --workspace --locked
  • python3 scripts/registry_gen.py --check
  • bun run typecheck
  • bun test: 288 passed, 0 failed
  • git diff --check

The 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

  • Run the final release hardware matrix from the exact PR head.
  • Preserve the gfx1100/gfx1151 long-session artifacts in the PR or a repository checkpoint.
  • Finalize and build the per-family/all-RDNA Linux release assets.
  • Change the README's “next release” line to “current release” only when v0.3.0 is tagged.

@HUSRCF

HUSRCF commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Possible gfx1100 MQ4R long-prefill regression in beta

Thank 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 (gfx1100), HIP 7.2, Q8 KV, and qwen3.6-35b-a3b.mq4r. The model SHA-256 is 4685c140c46b1a6f31a0fd9053bf09d5faf1d2529d715b84794249b66cde0428.

The matched results were:

  • master@5d3683a7 (clean): PASS — 41.3 ms warmup and 132.6 tok/s decode.
  • beta@9ffb18da (clean): FAIL — HIP illegal memory access during the first warmup/decode forward.
  • beta@9ffb18da (local dirty worktree): STALL — stopped at the same warmup point.
  • beta (local worktree with HIPFIRE_GATED_NORM_MQ_ROTATE=0): PASS — 143.0 tok/s decode.

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 8

The 8,192-token prefill completes normally, after which beta fails or stalls at === warmup (2 tokens — untimed, lets JIT settle) ===.

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, git bisect points to 12a9842c, perf(gfx1100): enable gated norm MQ fusion by default, as the first bad commit.

That commit changes HIPFIRE_GATED_NORM_MQ_ROTATE from explicit opt-in to default-on. Since setting it to 0 restores successful decode in the tested beta worktree, the default-enabled fused path might be involved.

Possible next steps

  • Might it make sense to keep this fusion default-off until the 8K/32K gfx1100 transition has been fixed and revalidated from the exact PR head?
  • Could we confirm whether the recorded gfx1100 headline runs had HIPFIRE_GATED_NORM_MQ_ROTATE=0 set in their environment?
  • Would it be useful to include the reproduction above in the final gfx1100 release hardware matrix?

I would be happy to rerun any additional configuration that might help narrow this down further.

@ghazni101

Copy link
Copy Markdown
Contributor

mq4r vs mq3p VRAM A/B on gfx1100 — build flags, results, and repro

Ran an isolated A/B measuring load-time VRAM for qwen3.6-35b-a3b.mq4r vs qwen3.6-35b-a3b.mq3p on a single RX 7900 XTX (gfx1100).

Environment

  • GPU: RX 7900 XTX (gfx1100), 25.75 GB VRAM
  • Host ROCm 7.14; container base rocm/dev-ubuntu-24.04:7.2.4 with host ROCm overmounted at /opt/rocm/core-7.14
  • Isolated Docker container (no ~/.hipfire/models.json, no per-model config overrides) so nothing overrides context/KV

Build

cargo build --release --locked --features deltanet --example daemon -p hipfire-runtime

(deltanet is required; the bench/daemon path needs it.)

Load config (identical for both models)

  • MTP: off
  • KV cache mode: q8
  • context: 8192

VRAM measurement
Read from kernel sysfs, which is reliable through the container:

cat /sys/class/drm/card1/device/mem_info_vram_used

Peak taken as the max over rapid repeated reads during load through warm-up.

Results (peak VRAM at warm-up, MTP off, kv=q8)

model peak VRAM notes
mq4r 23.79 GB uniform MQ4G256, 18.68 GB file, ~36.8B params
mq3p ~19.24 GB mixed MQ4G256+MQ6G256+HFQ8G256+F8E4M3, 17.23 GB file, ~24.1B params
Δ 4.55 GB of which 1.46 GB is the weight file-size difference

Per-allocation ledger (via an HIPMALLOC_LOG-gated trace build) attributed the non-weight portion to forward/DeltaNet scratch that mq4r's uniform-4bit decode path allocates and holds resident (Qwen35Scratch, speculative.rs:1008-1064); the large KV buffers were identical between the two models. FP16 shadow did not fire on gfx1100 (FP16_SHADOW_CALLED=0 for both; HIPFIRE_ROCBLAS_OFF=1 changed VRAM by 0 bytes).

Reproduce

  1. Build the daemon with the flags above.
  2. Run in a clean container with GPU passthrough and host ROCm overmounted:
docker run -d --name hipfire-ab --privileged \
  --device /dev/kfd --device /dev/dri \
  -v /path/to/models:/models:ro \
  -v /opt/rocm/core-7.14:/opt/rocm/core-7.14 \
  -e HIP_PATH=/opt/rocm/core-7.14 \
  -e LD_LIBRARY_PATH=/opt/rocm/core-7.14/lib:/opt/rocm/core-7.14/lib64 \
  --entrypoint /bin/sh <image> -c "while true; do sleep 3600; done"
  1. Drive the daemon over a FIFO, loading each model with {"type":"load","model":"...","params":{"max_seq":8192,"kv_mode":"q8","mtp_mode":"off"}}, and poll /sys/class/drm/card1/device/mem_info_vram_used for the peak until the debug log prints warm-up complete.

@fivetide

Copy link
Copy Markdown
Collaborator

Replay system integration audit

Investigated whether the Redline replay system (HIPFIRE_REPLAY_BACKEND=auto) has an effect on daemon-based inference, using DeepSeek V4 Flash MTP spec-decode on gfx1151 (RYZEN AI MAX+ 395 / Radeon 8060S, ROCm 7.2).

Current state

The replay controller is completely inert in the daemon's inference path:

HIPFIRE_REPLAY_BACKEND Result
hip (default/unset) Works. ReplayController sits on Gpu, is_recording() returns false, no effect.
auto Hangs — daemon gets stuck in generate, 900s timeout.
shadow Hangs — same as auto.

Root cause

The replay state machine starts at Armed and needs begin_auto_capture_if_armed() to transition Armed → RecordingWarmup → Captured → Ready. That lifecycle function is never called from any daemon per-request path. Even if it were, the actual dispatch from every arch crate (deepseek4, qwen35, minimax, llama, etc.) goes through crate::scratch::launch_maybe_blob or raw hip.launch_kernel — neither path checks the replay controller.

The replay system was wired into the old launch_maybe_blob_bound dispatch wrapper (since refactored into crate::scratch::launch_maybe_blob on master), but the central dispatch point no longer goes through the replay recording gate either.

Performance impact: zero

Measured DeepSeek V4 MTP on both branches (clean builds, post-warmup, 3 runs each):

Config Master (tok/s) Beta (tok/s) Delta
AR code 13.93 14.13 +1.4% (noise)
MTP K=2 code 21.29 21.23 -0.3% (noise)
MTP K=3 code 21.66 21.70 +0.2% (noise)

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

  1. Centralize dispatch: Every arch crate's forward pass must route to a replay-aware launch point. Currently each calls crate::scratch::launch_maybe_blob or hip.launch_kernel directly, bypassing the replay wrapper.

  2. Lifecycle hooks per request: begin_auto_capture_if_armed() must fire at the start of every forward pass, and the capture/replay cycle must complete within a single generate request. This requires the replay controller to be aware of each architecture's generate-loop boundaries.

  3. Backend availability: Even once capture works, actual PM4 replay requires redline-rocr's AQL/PM4 packet construction and direct KMD access (device file handles, queue setup, fence sync). That path is currently only validated in standalone Redline examples, not in the daemon's multi-request lifecycle.

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.

Copy link
Copy Markdown
Collaborator Author

@fivetide — thank you for digging into this. Your gfx1151 result found a real integration gap: HIPFIRE_REPLAY_BACKEND=auto/shadow is not currently a generic “turn PM4 on for any model” switch, and an unsupported route should fail closed or refuse early rather than sit for 900 seconds.

One scope correction, because it points toward a useful contribution: ReplayController is wired into the Qwen plain-AR adapter, but it is not wired into DeepSeek V4’s model boundary. Your test combined two currently unadmitted dimensions—DeepSeek V4 and MTP/speculative decode—so it could not exercise the narrow retained route behind the Qwen MQ4R result. MTP proposal/reseed/verify is deliberately outside the current immutable single-token tape contract.

There is now a better non-Qwen reference than the Qwen adapter: origin/lfm-redline@d335cc993. It proves that Redline can be integrated into a second model architecture rather than being Qwen-specific. The admitted fixture is still deliberately narrow—LFM2.5-350M MQ4, gfx1201, Q8 KV, single-GPU plain AR—but it produced a complete 161-dispatch / 15-kernel tape, one retained PM4-IB, bit-exact model-state shadowing, and observed product replay with no fallback.

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:

  1. Freeze one exact DeepSeek artifact/digest, gfx1151, single GPU, Q8 KV, graph off, and MTP/spec off.
  2. Add a DeepSeek-owned execution-mode/eligibility predicate like LFM’s, with explicit negatives for prefill, batching, HipGraph, MTP proposal/reseed/verify, reset, model swap, and non-sequential positions.
  3. Use DeepSeek’s existing decode_step / decode_step_body seam: stage token/position inputs outside the retained body, set one-forward eligibility, arm capture, route AQL/PM4, and finalize/prepare after the ordinary-HIP warmup.
  4. Preserve host-side state on replay. In particular, decode_step_body currently advances state.n_tokens; a retained replay bypasses that Rust statement, so the adapter must advance the cursor explicitly after successful replay, as LFM does.
  5. Reconcile the entire compute body: compute launches = retained launches + explicitly named external launches. Any raw in-body launch must become recorder-aware or be deliberately externalized with a state/lifetime justification.
  6. Build the DeepSeek state oracle before optimizing: logits, main/indexer KV, SWA rings, compressor/indexer state, residual streams, mtp_last_hidden, cursor state, guards, and captured kernarg blobs.
  7. Start single-queue and conservatively ordered. First prove stable tape identity and multi-position HIP/blob/PM4 parity with scripts/redline_daemon_harness.py; only afterward use scripts/redline_product_bench.py for same-report timed-arm route proof. A capture report and a separate timing report cannot be stitched into route proof.

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.

Copy link
Copy Markdown
Collaborator Author

@ghazni101 — thank you; your 23.79 GB observation was real and led us to the right pressure point. This is now fixed on beta in 0e5762b3c.

We reproduced with the same MQ4R artifact digest (4685c140c46b1a6f31a0fd9053bf09d5faf1d2529d715b84794249b66cde0428) on gfx1100 and ran a same-model, same-tip allocation A/B:

Before Fixed
Resident VRAM 23,756,001,280 B 19,406,700,544 B
GPU allocations 21,423 1,023
Requested allocation bytes 18,896,841,988 B 18,896,841,988 B

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 (2 projections × 256 experts × 40 layers). HIP's per-BO/allocation overhead inflated physical residency. The fix packs each layer into two owning BOs—gate/up and down, 80 owners total—while preserving the existing per-expert pointer/offset, dtype, and AWQ-sidecar views, so the kernel ABI is unchanged.

Validation on the fixed binary:

  • gfx1100 product: HIP 221.053 tok/s, PM4 250.248 tok/s
  • exact replay: 604 launches / 22 kernels, stable sequence and bit-exact output
  • real serve_harness.py session: 8/8 turns stopped normally, 0 runaway/empty/attractor turns; average decode 188.8 tok/s, final turn 154.7 tok/s at 18,186 context
  • MQ3P fallback remained on the old loader path and its resident VRAM changed by only 2 KB
  • cargo test -p hipfire-arch-qwen35 --features deltanet --lib: 147/147 passed

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 beta@0e5762b3c. On the RX 7900 XTX, the expected post-load residency is approximately 19.41 GB. If your transient warm-up peak materially exceeds that, please post the peak and trace and we can compare it directly.

@HUSRCF

HUSRCF commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Follow-up: gfx1100 long-prefill to first-decode failure isolated

Thank you for the additional investigation and for fixing the separate MQ4R allocation-count issue in 0e5762b3. It appears that there were two independent problems: the multi-BO residency overhead explained the ~4.35 GB VRAM delta, while the long-prefill-to-first-decode failure came from a much smaller but correctness-critical scratch-buffer overflow.

I isolated the latter failure to Qwen35Scratch::x_rot:

  • Qwen3.6-35B-A3B has dim=2048, hidden_dim=0, and DeltaNet v_dim=32*128=4096.
  • x_rot was allocated as max(dim, hidden_dim), giving 2048 F32 elements.
  • gated_norm_mq_rotate_gfx1100 writes all 4096 F32 output elements.
  • The fused launch therefore wrote 2048 F32 elements, or exactly 8192 bytes, beyond the allocation.

This explains why setting HIPFIRE_GATED_NORM_MQ_ROTATE=0 avoided the failure: it bypassed the kernel writing the undersized rotation buffer. The default-on change in 12a9842c exposed the existing allocation mismatch rather than introducing a weight-file problem.

I opened PR #538 against beta. It:

  1. Sizes x_rot as max(dim, hidden_dim, v_dim).
  2. Checks the exact fused shape and all tensor capacities before launch.
  3. Adds a regression test for the Qwen3.6-35B-A3B dimensions.

The memory cost is only 8 KiB per scratch instance, so it is unrelated to the multi-gigabyte residency issue fixed in 0e5762b3. GitHub currently reports #538 as cleanly mergeable against that beta tip.

Validation used the same MQ4R artifact digest reported earlier:

4685c140c46b1a6f31a0fd9053bf09d5faf1d2529d715b84794249b66cde0428

  • The old allocation with the new launch guard fails cleanly and reports x_rot=2048, required=4096, rather than issuing an illegal write.
  • The fixed default-on fused path completes the 8192-token prefill, 2-token warmup, and 8-token decode without HIP 700 or a stall.
  • The final regression run measured 145.9 tok/s decode.
  • Fusion on/off produced identical token IDs in the diagnostic parity run.
  • test_kernels: 16/16 passed on Radeon Pro W7900 / gfx1100.
  • Workspace build, library tests, and no-GPU CI passed.

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.

@ghazni101

Copy link
Copy Markdown
Contributor

Verification: commit 0e5762b resolves the ~4 GB mq4r vs mq3p VRAM gap

Tested on gfx1100 (RX 7900 XTX, 24 GB VRAM, ROCm core-7.14, HIP 7.14).

Build

git fetch origin 0e5762b3c92d0f60f2dd454b8018d0b8dadfebc9
cargo build --release --locked --features deltanet --example daemon -p hipfire-runtime

Binary confirmed: 22 MB ELF, PackedExpertOwners symbols present in nm.

Methodology

  • Fresh Docker container per load (isolated, no cross-contamination)
  • Daemon driven via FIFO: {"type":"load","model":"...","params":{"max_seq":512,"kv_mode":"q8","mtp_mode":"off"}}
  • VRAM read from host: /sys/class/drm/card1/device/mem_info_vram_used (5 consecutive stable samples)
  • MTP sidecar moved to /tmp for the clean baseline; verified no speculator/spec_step/MTP speculator enabled in daemon debug for any run
  • The MTP head loaded message (weight load only) is absent when the sidecar is removed

Results

With MTP sidecar loaded (446 MB on disk / ~582 MB in VRAM)

Model Peak VRAM
mq4r 19,838 MB
mq3p 19,517 MB
Δ 321 MB

Without MTP sidecar (moved to /tmp, zero MTP lines in log)

Model Peak VRAM
mq4r 19,256 MB
mq3p 18,934 MB
Δ 322 MB

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

Metric mq4r mq3p Δ
On-disk file 18.70 GB 17.24 GB 1.46 GB
Post-load VRAM (no MTP) 19,256 MB 18,934 MB 322 MB

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. quant_type=13 vs quant_type=3 on lm_head.weight) have different on-disk compression ratios but decompress to the same fp16 runtime layout for the weight tensors. The ~320 MB VRAM residual is the remaining real difference in weight footprint + scratch after the packed-expert fix.

Verdict

The 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 PackedExpertOwners works correctly on this gfx1100 card.

Copy link
Copy Markdown
Collaborator Author

Rust-only control plane landed on beta in 2a960e19017b3e108b168d653510f9be349397ee.

What changed:

  • Removed the tracked TypeScript/Bun CLI surface and replaced it with native hipfire-cli, hipfire-client, hipfire-config, and hipfire-registry crates.
  • Moved user configuration and per-model overrides to typed TOML with legacy JSON/env migration.
  • Moved the OpenAI-compatible HTTP/SSE service, registry handling, TUI integration, installers, container packaging, and serve_harness.py launch path onto the Rust control plane.
  • The committed beta tree contains no tracked .ts/.tsx, package.json, tsconfig.json, or Bun lockfiles.

Validation:

  • HIPFIRE_NO_REGISTRY_FETCH=1 bash scripts/no-gpu-ci.sh passed: workspace/example checks, 87 rdna-compute tests, 9 Qwen MoE-prefill tests, the new Rust control-plane crate suites, and 381 Python CPU tests.
  • A fresh release build of hipfire-cli passed.
  • Real MQ4R registry/TOML resolution through serve_harness.py --show-config produced the intended q8 / xhigh / max-4096 configuration.
  • A protocol-faithful synthetic daemon completed a 5/5 serve_harness.py battery through the full native CLI -> HTTP/SSE -> JSONL daemon path, including prewarm, health, streaming, usage/timings, and cleanup.
  • redline_daemon_harness.py and redline_product_bench.py continue to speak directly to the daemon rather than the CLI; their unit tests and complete synthetic control flows passed.

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.

@ghazni101

Copy link
Copy Markdown
Contributor

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 soak

Measured through the daemon FIFO protocol after 5x pre-heat generations (GPU at thermal steady state). The daemon's decode_tok_s field is the clean decode metric:

decode_tok_s: 212.3  (TG128, ~20-tok prompt, 5 gen warmup)
prefill_tok_s: 900.6
TTFT: 22.2 ms

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 context

The daemon loaded and served with max_seq=200000 at q8 KV:

  • VRAM at load: 22.88 GiB (95.4% of 23.98 GiB)
  • Decode at 200K: 210.7 tok/s (statistically identical to short-context)
  • Prefill cost at 200K increased marginally (TTFT ~47 ms vs ~22 ms at short ctx)

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.

@Kaden-Schutt

Copy link
Copy Markdown
Collaborator Author

@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; HIPFIRE_GRAPH=1 enables HipGraph, but it does not select or prove Redline PM4.

I have fast-forwarded beta to 78b75f04a. This head adds a route-proof-capable product benchmark and fixes the typed replay selection reaching the product arm.

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.json

Leave the stationarity defaults intact. A valid result must have:

  • top-level valid: true;
  • .hip.route_proof.retained_rows == 0;
  • .auto.route_proof.valid == true;
  • .auto.route_proof.retained_rows == 10;
  • .auto.route_proof.errors == [].

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 be77b87a0f994622.

For ordinary hipfire serve, use the typed config surface instead of adding more environment variables:

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

hardware.devices is the physical ROCr selector; Hipfire derives and synchronizes the matching logical HIP selector. If the benchmark reports a fallback, state != "ready", or no retained rows, please post the JSON report rather than comparing its throughput to the PM4 headline.

@taniguchi-taku-softm

Copy link
Copy Markdown

also tested.
did i mistake anything?

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": []
  }
}

@ghazni101

Copy link
Copy Markdown
Contributor

@Kaden-Schutt — reran the matched TG128 reproduction with the route-proof benchmark as requested. Result: valid, PM4 route positively proven on gfx1100.

Environment

  • GPU: AMD Radeon RX 7900 XTX (gfx1100), 24 GiB
  • ROCm 7.2.4 (containerized: rocm/dev-ubuntu-24.04:7.2.4-complete base), HIP 7.2
  • hipfire beta @ 78b75f04a, built exactly as specified:
    cargo build --release --locked --features deltanet -p hipfire-runtime --example daemon
  • Model: qwen3.6-35b-a3b.mq4r, sha256 verified 4685c140c46b1a6f31a0fd9053bf09d5faf1d2529d715b84794249b66cde0428
  • Bench: scripts/redline_product_bench.py --context 128 --iterations 128 --warmups 10 --warmup-iterations 32 --runs 10 --transport pm4 --kv-mode q8 --max-seq 2048

Result

Arm tok/s (min / median / max) route proof
HIP (ordinary) 223.08 / 223.22 / 223.41 retained_rows=0, valid
PM4 (retained) 245.87 / 247.59 / 247.79 retained_rows=10, valid
  • Speedup: 1.109×
  • Top-level valid: true; .auto.route_proof.valid == true; .auto.route_proof.errors == []; all ten timed rows positively observed replay (observed_positions [128, 255])
  • Retained tape: 604 launches / 22 kernels, prepared identity [604, 1, 2, 16832], sequence hash 43754a60ca25f47c

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, disclosed

With everything at defaults, the run aborted twice with auto failed to become stationary after 60 full-tg rows. The PM4 arm's rolling window was already inside your gates near the cap (run 2: slope −0.021 %/row vs 0.05 limit, spread 0.598 % vs 1.0 limit, stable: true at rows 51–60) — it just didn't accumulate the 10 confirmation rows before --settle-max-runs 60 cut it off. HIP settles in ~20–30 rows here; PM4 needs ~57.

So the passing run used --settle-min-runs 20 --settle-max-runs 120 — i.e. a larger measurement budget only. The stationarity criteria (slope/spread/median-drift gates and confirmation count) were left at defaults, per your instruction. Final PM4 settle: stationary after 57 rows, slope −0.029 %/row, spread 0.800 %.

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.

@Kaden-Schutt

Copy link
Copy Markdown
Collaborator Author

@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:

your run @ 78b75f04a sealed fixture @ 319905cb4
dispatches 733 733
unique kernels 23 23
sequence hash 3318ffca3daf2338 3318ffca3daf2338
command dwords 15,960 20,511
HIP median 173.493 tok/s 174.670 tok/s
PM4 median 141.683 tok/s 202.460 tok/s

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 319905cb4 by defaulting retained PM4 to static register emission while still caching queue-global invariants. That is what moves 15,960 → 20,511 dwords and 141.68 → 202.46 tok/s.

Your report also drove two follow-ups:

  • The measured route is now sealed in registry/redline-golden-v1.json with the gfx1201 acceptance floor at 197.0 tok/s and 1.10x, so this class of regression fails closed rather than depending on whoever happens to re-run the bench.
  • The stationarity ceiling moved from 60 to 120 rows. Your PM4 arm settled in 39, but @ghazni101's gfx1100 PM4 arm needed 57 and was being cut off at the old default.

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 gfx1201

That 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 / 3318ffca3daf2338 tape plus positive timed-arm route proof before it evaluates throughput at all. Add --device N if gfx1201 is not physical device 0, and --dry-run first if you want to see the exact command without building, hashing, or touching the GPU.

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 docs/GOLDEN-REDLINE.md for the full pass criteria.

Kaden-Schutt added a commit that referenced this pull request Jul 26, 2026
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.
@Kaden-Schutt
Kaden-Schutt marked this pull request as ready for review August 15, 2026 00:37
@Kaden-Schutt
Kaden-Schutt merged commit acb4784 into master Aug 15, 2026
5 of 6 checks passed
fivetide pushed a commit to fivetide/hipfire that referenced this pull request Aug 29, 2026
…#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.
fivetide pushed a commit to fivetide/hipfire that referenced this pull request Aug 29, 2026
v0.3.0: MQ4R + Redline across RDNA
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

help wanted Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants