refactor: unify device mesh, model parallelism, and generation dispatch - #527
refactor: unify device mesh, model parallelism, and generation dispatch#527fivetide wants to merge 866 commits into
Conversation
Physical PP Validation Findings on 2× R9700 — Partial, BlockedDate: 2026-07-14 Environment
Artifact fingerprints
Note: HW-003's requirement specifies Results1. VRAM split:
|
| Requirement | Status | Notes |
|---|---|---|
| Qwen35 physical weight/state split | ✅ | Equal per-stage allocation (2.638 GiB/card) |
| Physical peer access | ✅ | Bidirectional P2P enabled + verified |
| Physical stage-boundary execution | Qwen35 forward ran; explicit boundary-copy trace not captured | |
| Qwen35 token parity (PP=1 vs PP=2) | ❌ | Diverges at token 58; cause unconfirmed |
| Dense LLaMA physical placement | ❌ Blocked | Placement confirmed under emulation only; real forward crashes gfx1201 |
| Dense LLaMA forward parity | ❌ Blocked | gfx1201 illegal memory access in forward_scratch_compute |
| Physical unload/reload stability | ⏳ Partially tested | 100 emulated cycles clean; physical per-GPU teardown not done |
| PP-only capacity gain | ⏳ Not demonstrated | Test model (5.3 GB) fits on one GPU — no PP-required workload tested |
| Daemon serving parity | ⏳ Not tested | Required for HW-003 closure |
Remaining work
- Fix gfx1201 LLaMA forward crash (Issue A) — the PR blocker for HW-003 dense
- Isolate DeltaNet PP divergence cause (Issue B) — synchronization, accumulator, or state-ownership
- Run physical per-GPU VRAM teardown test (HW-003 § load/unload criterion)
- Run HW-003 manifest gate (
store-pp-gate.sh) on physical PP after Issue A fix - Run end-to-end daemon serving with token hash comparison
- Retest with the canonical
qwen3-0.6b-llama.mq4artifact (pure LLaMA, arch 0)
Sorry, i dont have ai pro x4...
|
Thank you—two real R9700s are exactly the validation we needed. The balanced PP allocation and verified P2P result are valuable evidence. We’re recording the gfx1201 LLaMA illegal-access result as a separate classification blocker; the HF4 artifact and emulated The Qwen35 PP divergence remains a hard blocker for HW-004, not expected numerical drift. After the pending generation-path work, we’ll re-run deterministically with pinned artifacts/prompts and capture boundary, recurrent-state, and copy-order diagnostics. Thanks again for the thorough environment, artifact, and VRAM data—and no apology needed; a real two-GPU report is precisely what this draft needed. |
Replace the hardcoded Q8-shaped KvTierInputs literal (quant_asym*: false, v_mode_bits: 8) with kv_cache.tier_inputs() so asym, FWHT, HFQ8, and all other KV tiers are dispatched correctly. Thread givens_cos/sin from the cache into AttnParams (asym attend kernels unwrap these), flash_partials from ForwardScratch (required for flash attend tiles), and k_scales/v_scales conditionally for HFQ8 (attend.rs panics on None for that tier). Also use gpu.graphs.capture_mode instead of the hardcoded false. Byte-identical no-op for Q8 caches; prevents panics/garbage when the arch-llama modular forward is wired to asym or HFQ8 KV caches. Deferred item from PR warpfront#463. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9 GPU-free tier-routing tests covering Q8, asym2/3/4, FWHT-asym4, HFQ4, HFQ8, Q4, and F32. Each constructs the KvTierInputs that tier_inputs() now produces for that cache mode and verifies: - uses_givens is true iff rotation buffers are needed (asym/fwht) - write_key and attend_key select the correct paired kernel tier Validates the dispatch path that arch.rs Site A (hipfire-arch-llama) now exercises via kv_cache.tier_inputs() instead of the hardcoded Q8-shaped literal. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d_dim gates Three changes: 1. llama.rs::KvCache::from_mode: add a clean-error gate for Asym3 alongside the existing Asym4 gate. Both require head_dim=256 (validated on Qwen 3.5); at head_dim=128 the constructors assert and the flash-decode paths are untested. Previously asym3 would panic in new_gpu_asym3_capped; now it returns a user-readable HipError. 2. kv_mode.rs::LLAMA_HFQ_POLICY: expand accepted from [Q8] to [Q8, Asym3, Asym4]. Asym2/Fwht* have no Flat KV constructor and silently fall to Q8 (unimplemented, not broken). Truth-table test updated. 3. carrier.rs::load_bundle: pass ctx.kv_mode_override to resolve() instead of hardcoding "". The override was being silently ignored since the carrier was created — explicit --kv-mode requests had no effect on HFQ llama loads. Result: --kv-mode asym3/asym4 on a head_dim=128 HFQ llama model now gives a clean "requires head_dim=256" error instead of either silent Q8 or a constructor panic. At head_dim=256, asym3/asym4 load and run correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Carry-forward notes from two PRs being closed in favor of this device-mesh ownership spine:
Neither stale branch is being merged wholesale into beta; this comment preserves their intent where the new ownership model can absorb it cleanly. |
Gate the QKVZA split-tail route behind HIPFIRE_QKVZA_SPLIT_TAIL=1 and arch_caps.is_rdna3_dgpu(), limiting it to gfx1100/1101/1102. Add a reproducible Qwen3.6-27B A/B prefill benchmark script.\n\nLocal W7900 evidence with scripts/bench_qwen36_qkvza_split_tail_ab.sh, PREFILL=4096, PREFILL_RUNS=3, q8 KV:\n- off median: 589.5 tok/s\n- on median: 613.9 tok/s\n- delta: +4.14% prefill tok/s
Task 4 overreached into pre-checkpoint code. Restore from a32da6b^: GDN/PrefillBandCtx docs, pbs_opts mut params + mask validation, from_ffn cfg(test), five removed expectations, PARO pairs/theta borrows and krot casts (scales cleanup kept), arch_has_wmma + four MQ3 branches, q_dim, duplicate fused_fa3_lloyd_mq4, kv_dim, kv_layer_idx increments, triattn_tap borrow, moe_ffn_dispatch_ep, six unused Lloyd locals. Checkpoint-introduced cleanups stay. Pre-existing diagnostics return. Assisted-by: OpenCode:deepseek/deepseek-v4-flash
…ferences Task 4 quality minor: comments at Qwen35Scratch and the ParoQ4G128 panic diagnostic referenced deleted symbols. moe_ffn_decode -> moe_ffn_decode_impl (graph-capture/allocation claim) and moe_ffn_decode_with_scratch (stream-capture claim); the loader regression hint now names the live ParoBackend -> ParoAugmentor -> load_paro_weight chain. No behavior or error wording change beyond the names. Assisted-by: OpenCode:deepseek/deepseek-v4-flash
…_dtypes Restore commit 6b599c1 reinstated the pre-existing non-cfg dtype projection seam (MoeFfnView::prefill_dtypes -> MoeDtypeSnapshot), returning its dead_code diagnostic in production builds. The method is the projection seam for MoePrefillDtypes::from_ffn (legacy constructor) and the dtype-projection tests, so gate a production-only expect on it with cfg_attr(not(test), ...); from_ffn and test behavior untouched. Verified: qwen35 lib tests 380 passed; all-target clippy no-deps shows no unfulfilled expectation and no prefill_dtypes warning; rustfmt check clean; moe residency + weight-store hybrid boundary scripts pass. Assisted-by: OpenCode:deepseek/deepseek-v4-flash
- c2_reject_unknown_router_dtype: execute validate_frozen_moe_dispatch with a RotationPlan::None, unwhitelisted router dtype (Q8HFQ) and assert the section-6 check_proj rejection names router + dtype (was a no-op fixture). - delta_net_state_abort_checked: drop empty-shell 'ok on empty' test (never called abort_checked; Gpu is not constructible on CPU); strengthen the compile-contract test to assert the empty-state Ok-path precondition and document that the Ok outcome is GPU-bound. - moe_ffn_view_layer_out_of_range_is_result: really exercise moe_ffn_view OOB on a null-tensor Qwen35Weights (was only constructing the error enum); add GPU-ignored frozen_moe_resident_bind_layer_out_of_range twin. Assisted-by: Fixer:deepseek/deepseek-v4-flash
PR #527 slices every expert to inter/tp per rank, so a paged fill must READ the full expert and WRITE a row-gathered subset. Our fill path assumed read length == write length — baked into fetch_batch_into, which read and copied in one step. That assumption, not the gather, is what would have made #527 a painful merge, so it is removed now while the tree is quiet. - Transport::read_batch reads ranges into staging and returns them, with no device involvement. Better layering regardless of TP: the transport no longer needs to know about GPU buffers. - ExpertFillTransform is the disk-bytes -> slot-bytes seam. `None` is identity and keeps today's single-step path, so there is no cost or behaviour change on the current route. - set_fill_transform sizes its scratch once at load, keeping fills free of allocation. Proven without #527: a synthetic EveryNthBlock transform has the same shape as expert_tp_row_gather (full in, 1/tp out) and three tests pin what TP needs — packed_len agrees with real output, ranks partition the expert exactly once, and indivisible geometry fails closed instead of truncating. So read-len != write-len is exercised today rather than discovered later. Until #527 lands, TP + paging is REFUSED at load, and the guard carries its own removal recipe: thread #527's tp_slice into the pool allocator with the packed stride, install a TpRowGather wrapping expert_tp_row_gather, delete the guard. The catalog needs no change either way — it records FULL HFQ ranges and the on-disk layout is TP-independent. #527 requires no changes; the only shared surface is a pub fn it already exports. TP detection is duck-typed on the env knob so this file compiles identically before and after it lands. Neutrality gate PASSES on the identity path; guard verified to fire. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G6rH6Y8vMnBpNn2sj3rzqu
Integration points with #563 (routed-expert paging) — for later, no action needed here#563 pages routed experts from the HFQ into bounded per-layer slot pools. Both PRs touch Recording it here so whoever merges second has the map. Already compatible, verified against this branch:
TP expert slicing — the one real interaction. Under TP a paged fill must READ the full
The expert catalog needs no change: it records full HFQ ranges, and the on-disk layout EP — the seam is now in too ( That last point is the whole correctness argument, and it has no runtime error to catch it: Worth knowing: paging replaces EP's compaction rather than composing with it. This branch To enable EP + paging: have Known gaps, flagged not fixed:
One process note: #527 targets |
Seal MoE lowering and manifest authority across DeepSeek4, MiniMax, and Qwen35. Add deterministic EP/TP parity harnesses, fail-closed topology validation, and complete STEP-002 tracking evidence. Assisted-by: OpenCode:openai/gpt-5.6-sol
Reconciles 488 beta commits with the 363-commit device-mesh branch. Base for both: 5d3683a. 64 conflict events resolved across 155 files (60 content, 2 modify/delete, 2 file-location); 683 files staged. ## Architecture decisions - **Beta is the canonical base for the daemon and shared runtime types.** The merged daemon = upstream/beta's daemon + the device-mesh load-plan (DaemonLoadPlan / daemon_load_plan / pp_bands_from_env) now WIRED into the load handler (admission-first planning, execute_load_plan dispatch). - **LoadedModel layout is beta's flat canonical shape** (arch_id, pp, pp_gpus, ep, kv_cache, dn_state, mtp_*, qwen35_mtp_head, …) plus genuinely-new state only: tp_model, pp_model, vl_image_state. SessionState/PersistState (dual representation) removed; their reset semantics (checkpoint frees with failed-free retention) ported onto LoadedModel::reset_cpu/reset/reset_checked/reset_context. - **superop module + ep.rs restored** (both are beta's; the feature branch had deleted them). ForwardBindings impls adapted to the merged MoeFfnStorage enum; spec_advance is the 6-arg (reset: bool) contract everywhere; all 8 arch impls honor reset via reset_recurrent. - **TS CLI dropped** (beta is Rust-only; crates/hipfire-cli supersedes it). cli/ removed; the Rust control plane is canonical per AGENTS.md. - **TP/dense-PP carriers** (tp_model/pp_model) are wired into unload_model (explicit free()), reset_context (carrier no-op branch), and the daemon's new MeshCarrier generate route (generate_mesh_carrier: render → prefill → host-sampled decode → emit; full-context replay per request). tp/pp parity examples exercise the same carrier API. - **qwen35 hand-coded PP preserved as beta legacy** (Gap 2, advisory- driven): the device-mesh policy table marks qwen35 Pp "Planned" (GEN-001) and load_admitted cannot recreate beta's carrier, so daemon_load_plan classifies qwen35 dense/MoE pp>1 (not VL) into DaemonLoadPlan::LegacyQwen35Pp → load_model_with_kv_backend's restored carrier-registry dispatch (load_qwen35_pp → pp_gpus → generate_multi). load_model_with_kv_backend regained beta's pp>1 registry path incl. the DFlash lm_head/MQ2/MQ3 preflight (extracted into preflight_dflash_draft, shared with load_single_admitted) and the VMM-carrier guard. ## Port debt (follow-ups, not part of this merge) 1. RESOLVED: HIPFIRE_TP / HIPFIRE_EP / HIPFIRE_PP / HIPFIRE_MTP_K env→params routing in crates/hipfire-cli (load_params + apply_draft_max; >1 forwarded only; MTP_K 1..=8; env beats --draft-max for the MTP window; pure parse helpers + unit tests). 2. RESOLVED: daemon_load_plan / admit_path (CAP-001) wired into the daemon load handler (admission precedes unload/GPU work; deferred vs eager lifecycle; ep param; guards extended to ep>1) + MeshCarrier generate branch for tp_model/pp_model. 3. OPEN: #[allow(dead_code)] launch_decode_rotate / launch_gate_side in hipfire-dispatch/src/pipeline/mod.rs — still no callers; cleanup when a caller exists. ## Known - Upstream flake (not merge-caused, crates/hipfire-client byte-identical to beta): load_eligible_then_malformed_clears_and_fails / reset_ack_rejects_missing_and_bad_fields intermittently ETXTBSY on the dummy-daemon spawn under full-workspace parallel load; pass standalone. - hipfire run's generate request was missing attempt_id (daemon requires it since the attempt-correlation protocol); fixed with next_attempt_id(). - daemon example tests did not compile at the merge commit (FinishSummary gained finalized, EmitOutcome gained generation_advanced in a later amend); literals fixed; one beta-era test expectation updated to the merged emitter's stop-sequence quarantine semantics. - PRE-EXISTING (reported, not fixed): qwen3-8b.mq4 + qwen3-8b-dspark.hfq sidecar → RefCell double-borrow panic in hipfire-arch-llama dspark_body.rs load_qwen3_dspark (main_norm pread guard outlives markov load_global_tensor). Single-GPU llama/qwen3 loads with a -dspark sidecar need --spec off until fixed (separate correctness route; out of reconcile scope). - PRE-EXISTING (upstream): redline_daemon_harness.py's prefill capture loop KeyErrors because bench_prefill never emits redline_capture (only bench_decode does) — use --skip-prefill. - ENVIRONMENT (this box): this ROCm 7.2.3 hipcc emits compressed clang offload bundles (CCOB v3, zstd payload); HIP's loader unwraps them, redline-rocr's raw-HSA loader only handles uncompressed __CLANG_OFFLOAD_BUNDLE__ → INVALID_CODE_OBJECT on every cached .hsaco (upstream issue warpfront#569). Worked around for parity validation: officially unbundled (clang-offload-bundler) the exact cached blobs into .hipfire_kernels_plain/ — byte-identical inner gfx1151 ELFs, hash sidecars preserved, selected via HIPFIRE_KERNEL_CACHE. Native CCOB loading in redline-rocr remains unsupported. ROCm split across Nix store paths; ~/.hipfire/rocm-merged symlink root (clr 7.2.3 libamdhip64 + rocm-runtime 7.2.3 libhsa-runtime64) makes the probe reach ROCr. - coherence-gate-dflash.sh is RETIRED per AGENTS.md §0/§3.5 (absent from the tree); correctness route = serve_harness battery/chain + decoded- text inspection (27B) + redline_daemon_harness.py for load/dispatch changes. ## Verification (this session) - cargo check --workspace --examples; cargo test --workspace (exit 0; upstream client flake excluded); clippy --no-deps on hipfire-cli bin, hipfire-loader lib, daemon example; rustfmt scoped (pre-existing non-conformant spots in daemon.rs/main.rs left untouched). - GPU smokes (gfx1151, HIP 7.2): - 9B DFlash canonical pair: τ=5.8571 / accept 0.3905, output "return x + y" — token-identical to pre-Gap baseline (draft md5 590f35403cd7f1d634945233234a12b7). - daemon TP: HIPFIRE_TP=2 HIPFIRE_EMULATE_GPUS=2 hipfire run qwen3-8b.mq4 → admission Tp → TpModel → MeshCarrier, coherent. - daemon dense-PP: HIPFIRE_PP=2 same model → PpModel → MeshCarrier, coherent. - daemon single-GPU: qwen3-8b.mq4 --spec off → 4096d/36L, coherent. - 27B gate (qwen3.5-27b.mq4 + qwen35-27b-dflash-mq4.hfq, draft md5 7b6df2a4ee1c8d933f0a52e187d1860b matches AGENTS.md): - serve_harness battery: 5 turns, 0 attractor / 0 empty, τ 7.5 / 7.28 / 3.25 / 0.97 / 1.68; 3/5 clean stop, 2/5 length-capped (t1 code, t3 factual — t3 also violated the exact-three-sentences constraint with a Thinking-Process preamble); prose τ 0.97 is the documented draft-target prose mismatch, not a regression. (serve-27b-battery.json) - serve_harness chain: 4/5 clean stop, 1/5 length-cap, prefix-cache hits on turns 3-5 (serve-27b-chain.json). - redline_daemon_harness: decode capture STABLE across repeats (hash 993441c8656e5b0c, 947 launches, 21 kernels) — the load+dispatch path is Redline-deterministic. AQL contract probe passed (21 kernels, all loader-kernarg validated); HIP/PM4 parity BIT-EXACT on both backends over the full 947-dispatch sequence vs live-HIP forward (aql_packets exact=True, pm4_ib exact=True; reports .slim/deepwork/evidence-27b/redline-27b-plain-{aql,pm4}.json). Method-qualified: runs used officially unbundled byte-identical inner ELFs in a parallel cache (env bullet); native CCOB loading in redline-rocr remains unsupported (issue warpfront#569). - GitNexus (restored this session): impact analysis on changed symbols (load_params HIGH blast radius, additive-only; dependants match manual grep), detect-changes --scope unstaged: 23 symbols, all expected; daemon example symbols not indexable (example target). - Three deepwork oracle gates (A/B/C) passed; all advisory rounds incorporated.
launch_decode_rotate / launch_gate_side were introduced WITH callers in b1d54d5 (device-mesh MoE refactor) and orphaned by the beta merge: the conflict resolution adopted beta's inline run_moe_decode (activation rotation + gate-side inlined with identical kernel calls), dropping the only call sites. The reconcile kept them under #[allow(dead_code)] as port-debt item 3; this closes it. Verified: zero references repo-wide; GitNexus impact 0 processes / 0 modules for both symbols; cargo check + clippy (lib) on hipfire-dispatch clean of new findings; 223 tests pass (210 ok / 13 ignored) including the run_moe_decode coverage contracts. The gate-side shared_prerot optimization survives via the extracted moe::launch_shared_gate_side / launch_fused_shared_gate helpers, still used by the Step lane.
run_moe_decode_mixed (REAP SP2 Task 3) was never wired: no res.mixed branch exists in run_moe_decode, zero callers, GPU-GATE DEFERRED under #[allow(dead_code)]. Removed it and its private-only dtype_name helper. build_contiguous_permutation + the mixed_dispatch_tests CPU suite are kept (test-covered equivalence invariant; the ignored GPU bucketing stub stays as the owed-validation record).
…parity Merge-consistency fixes found by post-merge revalidation (session 2): - dots-ocr daemon panic: loader (device-mesh) stores dots-ocr state in the ModelState::DotsOcr bundle; daemon (beta) read dead flat fields (dots_ocr_config/dots_ocr_weights/qwen2_state) -> None.unwrap() on any generate. Daemon now reads the bundle via new dots_ocr()/dots_ocr_mut() accessors; resets added at 3 sites; dims/vl flags from the bundle. - deepseek4 daemon panic: deepseek4_pbs flat field dead (moved into Deepseek4Bundle); generate_deepseek4 read it -> panic. Now read from the bundle. Also added missing ModelState::Deepseek4/Minimax dims-match arms. - emulated-ep2-harness compile break vs merged qwen35.rs: Qwen35Scratch init missing ep2_gu_partial0/1, moe_ffn_decode_impl calls missing defer_routed_combine arg, forward_prefill_chunk call missing cfg'd ep2 arg, DeltaNetState::reset_checked -> reset (x2). Verification: qwen35 487 (emulated-ep2-harness) / 425 (plain), loader 106, hipfire-runtime full suite — all green. dots-ocr 1536d load + coherent answer; deepseek4 AR decode coherent; EP2 parity probe tokens identical (max_abs_logit_delta 0.624146 < pinned 0.708164275).
…lently truncated gemv_hfq4g256_moe_down_k8_indexed_batched_expanded (the default routed-down kernel) was specialized to EXACTLY two 256-groups (K=512, the qwen35-A3B routed-down shape) by the 2026-07-11 R17c1 perf rewrite (ec9ec35). For any other K it processed only groups 0-1 and silently dropped the rest: lfm2moe's down K=1792 (7 groups) lost 71% of the dot product, producing deterministic degenerate output (token pinning) at the first MoE layer and compounding through the stack. Fix: guard the 2-group fast path with groups_per_row == 2 (A3B keeps its perf path byte-identical) and restore the generic quads+tail loop for all other group counts, keeping the 4-rows-per-block structure + LAST_COMBINE epilogue. Evidence (gfx1151): - tiny-oracle K=256: MoE-layer cosines 0.989-0.993 -> 0.999+ (the old '4-bit expert noise' gap WAS this bug; OOB group-1 reads) - tiny-oracle real-K (hidden=2048, moe_inter=1792, 32 experts): first MoE layer 0.954 -> 0.995, L4 0.885 -> 0.979 (residual = genuine MQ4 noise) - real LFM2.5-8B-A1B (both the registry fixture and a fresh mq4 re-quant): degenerate pinning -> coherent hash-map explanation with think trace - daemon serve of lfm2.5-8b-a1b.mq4: coherent, banner now shows '2048d 24L 128000 vocab' (added the missing ModelState::Lfm2Moe dims-match arm, same gap previously fixed for ds4/minimax) - qwen3.6-35b-a3b (K=512 fast path): coherent, unchanged - hipfire-runtime suite: 821 passed
- lru_cache_pep8_4nl.txt (md5 6934d440845edbe6118f110b5afdb3a9): 3x four-newline runs over lru_cache_pep8_strict — first A/B round - pep8_40func.txt (md5 7248f5557a7fea19d914fd96e6cb6a22): 40-function PEP-8 code file with 39 triple-newline runs (2500 tokens) — canonical A/B stimulus; normalize flips 39x1358 to 39x271 (token-ID alignment)
STEP-002R + full pivot: checked, exact-retention GPU teardown replaces
best-effort frees in construction rollback and unload.
Generic core (hipfire-runtime/src/gpu_cleanup.rs):
- RetainedGpuTensor / GpuCleanupFailure (tensors + boxed RetryableOwner
category, category-preserving retry, redacted Debug)
- BundleTeardown trait; retain_free! macro; free_tensor_retained /
free_weight_all_checked / free_weight_sidecars_checked / retain_kv_failures
- RetryableOwner impl for SingleFreeFailed (frozen stores stay whole)
Qwen35 (STEP-002R):
- load_bundle -> Qwen35BundleLoadError carrying cleanup owners; checked
rollback (KvCache::free_checked, abort_checked x2, free_gpu_checked)
- Qwen35Scratch/PrefillBatchScratch abort_checked; construct_kv_cache
returns the built KV on reconfig failure
- MtpHeadStaging transactional MTP load (MtpHeadLoadError carries retained)
- store rollback keeps real dtype/shape (try_free_all_checked); orphaned
rejected-replacement buffers thread out of assembly into staging_retained
- frozen-fault-inject feature: HIPFIRE_FROZEN_FAIL_STAGE (live env) +
HIPFIRE_FROZEN_FAIL_FREE one-shot in free_tensor_checked
All arches:
- free_checked on every GPU-owning type; BundleTeardown impls for qwen2,
qwen35, llama (incl. runtime LlamaWeights/ForwardScratch/PrefillBatchScratch/
dspark), lfm2moe, minimax, cohere2moe, deepseek4, dots-ocr
- unload_model dispatches through ModelState::free_checked (exhaustive),
retries once before logging; #[must_use] on owner-carrying errors
Forensic fixes found while verifying:
- dspark_body load_qwen3_dspark held fn-scoped Ref<Vec<u8>> pread guards,
panicking ('RefCell already borrowed') on any sidecar load; block-scoped
all four guards
- free_moe_ffn_checked lacked the packed_expert_owners branch: on RDNA3
packed-MQ4 models it pooled interior expert-view pointers and leaked the
owner blobs; now frees sidecars only + blobs once (GPU-tested with a
synthetic packing fixture)
- free_tensor_checked pooled VMM-owner buffers without arena release (and
pooled borrowed views); now arena-aware with checked-retention semantics
(VMM teardown verified exact-zero)
Tests: GPU fault battery (13), per-arch load/unload VRAM teardown (7 arches
+ VMM variant), packed-MQ4 teardown, serve_harness battery. CPU suites:
runtime 821, loader 106, qwen35 424, qwen2 8, llama 1, lfm2moe 2, minimax 28,
cohere2moe 9, deepseek4 117, dots-ocr 27. Residual debt tracked in
NEXT-STEPS.md (mid-constructor leaks, PP load rollback).
Terminal teardown surfaces (load_model, unload_model, the qwen35 carrier
error path, rollback_unfinished_qwen35, the MTP-head failure logger) used to
retry once and then DROP owners that still failed while allocated. They now
enqueue into a process-local retained-owner backlog
(hipfire_runtime::gpu_cleanup::{enqueue_cleanup_failure, enqueue_retained,
retry_backlog, backlog_pending}): whole GpuCleanupFailure entries (both
owner categories, never flattened) are retained until the next load_model /
unload_model boundary drains them. Owners that still fail stay enqueued
(exact-retention) and are reported, so VRAM is reclaimed at the next
operation instead of lost until restart.
unload_model defers its failure note to the trailing retry_backlog drain:
a transient failure this same call reclaims must not report unload as
failed with a stale message. No public drop-live-owners API exists
(backlog_clear was removed) — the backlog is drained, never silently
cleared.
HIPFIRE_FROZEN_FAIL_FREE is now continuous-while-set (any non-empty value)
instead of one-shot: the initial teardown AND its retry can both be made to
fail, which the retained-backlog test needs (the one-shot could never fail
a retry, since the retry runs after all injected failures are consumed).
The existing cleanup-failure fault test still passes (it clears the env
before the retry phase).
Test: retained_backlog_enqueues_after_double_failure_and_recovers_on_next_load
(GPU): 620 owners enqueued after a double-failed unload, drained by the next
load, VRAM recovered. Verified 5/5 runs; per-arch teardown 7/7 + VMM exact,
fault battery 14/14, serve_harness 5/5, CPU suites (runtime 821, loader 106,
qwen35 424, dots-ocr 27).
NEXT-STEPS.md: STEP-002R section marks the terminal log-and-drop gap closed
(item 0) alongside the tracked mid-constructor (item 1) and PP-path (item 2)
debt.
STEP-002R shipped as PR #18 mirror commits 064e26e + c7f142a (owner-preserving BundleTeardown + retained-owner backlog). GEN-001 flip to complete in b1d54d5 was premature: evidence pending, STEP-003 not complete, PR warpfront#527 and docs/MODELS.md still record Qwen35 PP as partial arch-resident. Refresh Current Status and Execution Priority queue: STEP-003 next, then GEN-002/SPEC-001/VL-001.
…EP-003) Replace the hand-built DeltaNet la_to_device sidecar with placement derived from the state manifest + mesh, defining the sidecar out of existence (device-mesh plan §4 / STEP-001 spec deferred scope). - arch: qwen35_la_devices(cfg, gpus) filters state_manifest Recurrent entries (global-layer keyed) through Gpus::device_for_layer — the compact LA order and owning devices the sidecar duplicated. - qwen35: new_with_quant_multi returns Self only (no Vec<u8>); free_gpu_multi takes &Qwen35Config and derives placement internally. - loader: drop LoadedModel.pp_dn_la_to_device and skeleton_pp param; validate_qwen35_pipeline_layout / reset_qwen35_pipeline_recurrent / validate_reset_layout derive from config + gpus instead of the sidecar; single-model rejection keys off pp_gpus presence. - daemon: reset_qwen35_recurrent, generate_multi context-full + jinja resets, and reset_pp_uncommitted_state derive the map from bundle config + gpus. - examples/tests: update tuple destructures; add GPU-gated test qwen35_la_devices_matches_mesh_placement proving the manifest map equals the mesh and allocations land on owning devices. Loader 106 passed, qwen35 424 passed (21 ignored), daemon example 192 passed, workspace cargo check --all-targets clean. Emulated PP parity gate diverges at step 1 identically with and without this change (pre-existing, HW-004-class).
Increment 1 (9dcb186): manifest-derived state placement, la_to_device sidecar deleted. Increment 2 verification: forward_scratch_layers_multi already runs all DeltaNet recurrent/conv ops through build_delta_net_decode_steps + execute_steps_mesh on the per-device handle. Remaining bespoke ops (QKVZA, FFN, norm) are STEP-004 scope. GEN-001 unblocked (all deps now complete).
…ning forward path Migrate the remaining architecture forward entry points to Step/manifest execution (gates AXIS-001..004). Inventory: .agent-progress/step-004-inventory.md. Qwen35 PP decode (forward_scratch_layers_multi): QKVZA/QKV/gate-up now run through the shared qkvza/qkv/gate_up_via_execute_steps helpers and the KV write + attention ladder through kv_cache_attention_dispatch (new per-device givens override params for asym KV replicas); ~260 lines of bespoke inline dispatch deleted, plus the now-dead run_fused_qkvza_scalar_key and scalar_qkvza_key (+ its obsolete dispatch-pinning test). LFM2 / Cohere2 / MiniMax decode: norms, projections, qk-norm, RoPE, dense FFN, router, and head through Step lists. LFM2 rotation is dtype-driven (dtype_rotation_plan — the plan's Q8-only assumption was wrong for the .mq4 models and produced a token-0 parity break, caught by GPU bisect). Follow-ups (north star): - LFM2/Cohere2 carriers now own the manifest machinery: weight_manifest with ExpertSharded packed-fused surrogates, policy-aware expert_group_manifest (sigmoid_topk + indexed_quantized), state_manifest, and model-owned config-keyed moe_group_plans caches; the routed-MoE expert phases lower through lower_moe_steps + execute_lowered_moe (Single). LFM2's router phase carries ScoreActivation + MoeRoute; Cohere2's is empty (its norm_topk_prob= false top-k stays bespoke, documented exception). - fix(qwen35): DeltaNetState::new_with_quant_multi did not wire the error-feedback residual — the recurrence used the stochastic requantization while the single-GPU ctor wires EF by default, so emulated pp=2 drifted from pp=1 from the first state write (per-layer hidden-state bisect). EF residual now allocated per LA layer on its manifest-derived device. pp_parity cargo test passes 50/50 under HIPFIRE_EMULATE_GPUS=2 (was red at step 1). PHYSICAL PP parity (HW-003/HW-004) is NOT claimed closed — the external token-58 divergence on gfx1201 R9700s remains open pending re-validation. - LFM2's five dead 'mirror' block helpers deleted (user-approved; zero callers crate-wide, stale vs the migrated live path). Parity (pre-migration baselines in .agent-progress/step-004-*-baseline.txt): LFM2 8B-a1b / 350m.mq4 / 350m.q8, Cohere2 North-Mini-Code-1.0, MiniMax M2.7.mq2 — token-identical pre/post migration and pre/post MoE lowering. Workspace check + lib tests green; changed files rustfmt-clean.
…r update - step-004-inventory.md: arch x forward-entry-point table with Step status and justified non-decoder exceptions (vision/MLA/embedding/conv-mixer/interleaved RoPE/per-expert fallback/MiniMax RoPE), follow-up log with resolved items. - step-004-*-baseline.txt / *-migrated.txt: pre/post migration deterministic decode token sequences (LFM2 8B-a1b, 350m.mq4/q8, Cohere2 North-Mini-Code-1.0, MiniMax M2.7.mq2) and the emulated pp=2 baseline. - device-mesh-refactor-tracker.md: STEP-004 marked complete; emulated-PP EF fix recorded with explicit note that HW-003/HW-004 physical gates remain blocked and are not closed by it.
…pass
- Repo split: resolved to workspace-boundary-first. warpfront/redline already
exists as a public repo while crates/redline{,-dispatch,-rocr} remain
in-tree, so the split produced two homes rather than a boundary.
- Trainer: located at origin/feat/mtp-dflash-training, 21 ahead / 1832 behind,
last touched 2026-06-26, +6747/41 files. This is Path C and it is decaying
on the same trajectory that left #527 817 commits behind.
- Quantizer competitive claim: deferred to stage 2, post-refactor.
- arch/release-and-layering: retired; arch/ksml is the refactor branch.
- Parent oracles: hipfire-quantize is the thematic home but is itself 62%
main.rs (15,522 of 24,863), so the proposal is a *-quant family rather than
a dump.
- Naming: recorded that ggml/GGUF are personal initials and the exception
rather than the pattern; the objection to ksml is adoption mechanics, not
modesty. Name marked PLACEHOLDER.
Ordered plan of record for the saddle re-layering. Companion to the design grounding doc, which holds the measurements. Nineteen items across three tracks: legibility (examples triage, daemon to a real bin, glossary, positioning), ratio (grammar/spec dedup, pflash and quantize eviction, saddle-core extraction, arch slimming), and rescue (Path C trainer, PR #527 disposition). Each item carries a measured size, a risk, its dependencies, and a gate that must pass before it counts as done. Eight ratchets, the headline being the compute:arch line ratio -- 0.70:1 at 8510ca5, 0.85:1 after the ds4 parent eviction, against llama.cpp's 9.7:1. Records four conflicts to resolve rather than paper over, chief among them that AGENTS.md calls PFlash retained legacy research while 2,030 lines of it sit in a production arch crate with 206 daemon references. Explicitly out of scope: rdna-compute, the kernel family, Redline/PM4, radiowave, and the quant formats -- 124,348 lines where the performance advantage actually lives.
E1 (Path C trainer, feat/mtp-dflash-training) is closed: a failed month-1 experiment, out of scope. E2 (#527 disposition) is deferred until the refactor is complete. Drops C2. Harvesting #527's weight_manifest/weight_store/moe_plan was scope imported from that PR: manifests and placement are multi-device *placement*, which is the parallelism concern this refactor is explicitly orthogonal to. saddle-core's contents are therefore grammar, KV, spec orchestration, the capability contract and sampling policy -- and deliberately exclude weight manifests and device placement. Nothing here blocks #527 and #527 blocks nothing here. Records a fifth conflict: with Path C dead, AGENTS.md still lists it as an open investigation (S8), as the roadmap fix for the DDTree gfx1100 regression (S4), and tells 3.6-A3B users to wait for it before using DFlash (S6 pitfall table). The last is actively misdirecting users. Same failure class as the PFlash conflict already recorded.
Tested every remaining lever rather than asserting the conclusion:
counting tests consistently on both sides 1.983:1 (compute has MORE test
code; neutral)
arch-crate weight loading 3,128 lines #527, deferred
arch-crate kernel dispatch 674 lines section 6
generic arch code -> saddle-core 3,100 lines makes it worse
de-duplicate identical fns 62 lines intentional
The tempting one is the third. Of 186 sanctioned generic functions in arch
crates, only 13 appear in more than one crate; 173 are single-use. Moving them
removes no duplication and turns the substrate into a drawer of unrelated
helpers to reclassify lines -- the opposite of legibility.
Also documents a real trap found while testing the fifth lever: argmax is
duplicated on purpose. The runtime copy has an is_finite() guard because it is
the degenerate fallback for sample_top_p; the two arch copies use a bare '>' to
match kernels/src/argmax.hip:13 bit-for-bit on the spec-decode path, where
disagreeing on +Inf would cause spurious rejections. Both files now say so.
Planning artifact only; authorizes no code changes. Records the measured baseline on 8510ca5 and the finding that motivates the proposal: hipfire's compute-to-architecture line ratio is 0.7:1 where llama.cpp's is 9.7:1. Every generic concern ggml owns once is reimplemented per arch here, which is why DeepSeek V4 is 51,084 lines against llama.cpp's 1,546 for the same model. Also records ~30k lines that are misplaced rather than arch-specific (ds4 parent/ at 20,782 with zero inference-path references, grammar.rs duplicated at 3,935, spec plumbing at 3,370, pflash at 2,030), the correctly-located unsafe surface, the proposed layering with the abstract-the-model-never-the-kernel rule, seven ratchets, and the relationship to PR warpfront#527's completed third.
Summary
This draft is the cumulative device-mesh and generation-driver refactor. It introduces a named-axis
DeviceMesh, manifest-driven weight/state placement, dense TP and PP serving, MoE EP/TP execution work, and collapses the daemon's parallelism and architecture-specific generation ownership.The branch is intentionally opened as a draft because important real-hardware validation remains unavailable locally. In particular, we need contributor help with physical multi-GPU PP and RCCL-backed EP validation before this should be considered production-ready.
Current status
DeviceMesh, manifest-derived placement, andModelParallelown topology and execution modeb1d54d5c2; Gate 3:GATE3_APPROVED_WITH_DEFERRED_DEBT10d29940; source-aware admission and dense-EP normalizationThis PR remains a cumulative draft. STEP-002 is closed, but the PR does not claim arbitrary mesh composition or production Qwen35 TP/EP. Physical RCCL and remaining multi-GPU validation are still required before final merge.
Delivered changes
hipfire-hardwareas the topology and collective owner.DeviceMeshtopology and stage/device grouping.ArchDispatch+ar_generatefor most non-vision text paths.StreamParseras the shared EOS/output/reasoning/tool-event hook.ModelParallel::{Single,Tp,Pp,Ep}.LoadedModelfrom roughly 50 loose fields to 9 grouped fields.ModelMeta::eos_tok.Architecture support
Legend: Yes means implemented through the unified path; Partial means new ownership but bespoke execution remains; Target means technically appropriate but not implemented; N/A means the axis does not apply.
0PpModel+DenseDispatchhas_qk_norm=true/ Qwen3-family metadata-eligible artifacts; non-qk-norm LLaMA/Mistral is refusedHW-006physical closure for eligible artifacts15Pp(ArchResident), stillgenerate_multi678910[e~[filtering1112pp > 1HFQ path bypasses VL detection and does not explicitly refuse at load; no PP+VL support claimDenseDispatchCross-cutting generation status
GrammarMatcherhookis_eos,EosDecision, and filtering hooksStreamParserar_generategenerate_speccoreParallelism implementation
Pipeline parallelism
There are two implementations:
PpModel,DenseDispatch, andar_generate.ModelParallel::Pp, but generation still usesgenerate_multi.Qwen35 cannot simply pretend to be dense PP because DeltaNet state and recurrent scratch are stage-resident. It should still be possible to share the request lifecycle through a Qwen35 mesh dispatch.
Tensor parallelism
TP is structurally unified but currently focused on dense LLaMA/Qwen3 execution. Qwen35 requires DeltaNet and attention projection placement to become first-class Step/manifest operations. MoE families need explicit TP-of-experts or EP semantics rather than reuse of dense TP by accident.
Expert parallelism
EP is implemented for DeepSeek4 and MiniMax. It is applicable but absent for Qwen35-MoE, LFM2-MoE, and Cohere2-MoE.
The earlier EP manifest replication defect is fixed: non-expert weights and state now use the complete compute grid for their owning PP stage.
Speculation implementation
Speculation is more unified than some historical design notes suggest.
generate_specis generic over:SpeculatorSpecTargetGuardSpecEmitThis covers substantial portions of DFlash, DDTree, DSpark/MTP, and model-free n-gram speculation.
It is nevertheless a separate engine from
ar_generate:ArchDispatch::as_spec_target()is effectively dead; target acquisition usesCarrier::spec_target_guard.The likely clean end state is one request-lifecycle orchestrator with separate AR and speculative strategies, not one giant merged token loop.
Verification
STEP-002 CPU and static checks
git diff --checkpassed.moe_plan.rsandweight_manifest.rswere resolved.STEP-002 hardware and coherence
0.00e0.0x26a13602bedf99260x887c2e7717e9c3bfEarlier branch gates still relevant
Remaining work and known limitations
Hardware validation blockers
ModelParallel::Epmigration.Structural gaps
ar_generate.ArchDispatch.Explicitly deferred STEP-002 debt
interversusinter_localcleanup.Contributor help wanted: AMD multi-GPU validation
We specifically need contributors with supported AMD multi-GPU systems and RCCL installed.
Please comment with your available topology before starting so runs are not duplicated. Useful systems include:
/opt/rocm/lib/librccl.so.1for DeepSeek4/MiniMax EP.Requested validation:
DeepSeek4 EP-2 and MiniMax EP-2
ModelParallel::Eppath.Dense PP-2 and Qwen35 PP-2
scripts/store-pp-gate.shwhere applicable.Dense TP-2
Please attach raw logs. Performance numbers without model hashes, prompt hashes, GPU topology, and ROCm/RCCL versions are not actionable.
Authoritative work tracker
The authoritative tracker is
feature/device-mesh/.agent-progress/device-mesh-refactor-tracker.md. If this PR body disagrees with the tracker, the tracker wins and this body must be corrected.PAR-003is retired and must not be reused.COMP-001owns the optional TP × EP composition decision;DOC-002is the final validation and merge gate.Completed
mtp_kMetadatab1d54d5c2; Gate 3 approved with deferred debt064e26e0d+c7f142af8; owner-preserving BundleTeardown + retained-owner backlog; residual debt tracked in NEXT-STEPS.md9dcb1862a+ increment 2 verification; all DeltaNet ops Step-based on owning deviceDependency-driven implementation
Axis implementation
Physical validation and final gate
Review guide