Conversation
|
would you check if it would be doable ontop of #527 ? that one does a lot of changes into weight loading and might be fully incompatible otherwise.. |
Compatibility seams for #527 (device mesh / model parallelism)#527 unifies device mesh, TP and EP, and touches all seven of the files this PR does. I Two seams are in, both inert today and both proven by negative control (neutering each one Fill transform — Expert ownership — Also worth recording: paging replaces EP's compaction rather than composing with it. EP Both guards stay in place. TP and EP are still refused at load, because neither can be Verified: neutrality gate PASSES on the identity/ Caveats I have not resolved:
|
…s Lloyd
The routed-expert GEMV was hardcoded to the MQ2-Lloyd kernels at every
site, with no dtype branch anywhere. A checkpoint whose experts are
stored in any other format was therefore decoded through a Lloyd
codebook — no error, no warning, just wrong numbers. DeepSeek V4's
native FP4 (qt 21, HFP4G32) produced NaN in the FIRST prefill layer,
which poisoned the residual stream, so every generated token was id 0.
Record what the file actually holds (`expert_quant_type`, from
`HfqTensorInfo::quant_type`) and branch on it in all three places:
* run_moe_decode_bias_aware — layers 3..42 decode
* ffn_hash_routed — layers 0..2 decode
* ffn_batched_routed_paged — prefill under paging
The Lloyd formats bake an FWHT into their weights and so consume the
ROTATED activation and a rotated SwiGLU output; HFP4G32 bakes in nothing
and consumes both plain. `dtype_needs_rotation` is the authority and
lists the Lloyd formats but not HFP4G32. So the FP4 arms read `x_plain`
and skip `rotate_x_mq_batched`, and their down GEMV reads `gate_batch`
(the unrotated SwiGLU output) instead of `rot_batch`. Both buffers
already existed — `fused_rmsnorm_rotate_mq_plain` writes them in one
launch — so carrying `x_plain` through the params costs nothing.
Paged prefill needed a different shape of fix. Its band pipeline walks a
scattered, expert-sorted buffer that only the grouped Lloyd GEMM can
consume (`dispatch_grouped_lloyd` has Lloyd arms only). FP4 instead walks
the batch in TOKEN chunks, each grown while its distinct-expert union
still fits the slot pool, running the indexed-batched FP4 kernels over
`sub_offset` views. A chunk closes before it would overflow the pool and
a token is never split, so the working set is bounded by construction.
Verified end to end rather than by inspection — every component of this
path tested correct in isolation while the model still emitted garbage:
* artifact bytes decode element-for-element identical to the source
safetensors (`fp4_artifact_check`)
* kernels pass f64 parity on gfx1151 (6e-8 .. 4e-6)
* FP4 now generates coherent text; MQ2-Lloyd is unchanged on the same
build (regression-checked, since the FP4 branch returns early in
front of the band loop MQ2 depends on)
Two limits, deliberate and worth knowing:
* FP4 takes the ATOMIC self-combining down at decode and prefill —
there is no HFP4G32 counterpart of `moe_down_expanded_k4`, so FP4
greedy decode is not bit-reproducible until that kernel exists.
* FP4 forgoes the grouped prefill path's throughput. Grouped HFP4G32
GEMMs would restore it; correctness first.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G6rH6Y8vMnBpNn2sj3rzqu
The FP4 passthrough was covered by a roundtrip unit test, which only proves the repack agrees with ITSELF — it cannot catch the quantizer writing the wrong tensor's bytes, or a consistently wrong scale/nibble pairing. This dumps leading values plus min/max/rms/zeros so an artifact can be checked against the same tensor dequantized from the original safetensors. That comparison is what ruled the artifact out while hunting the all-token-id-0 bug: 0731's experts decode element-for-element identical to source, which moved the search to the dispatch path where the fault actually was. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G6rH6Y8vMnBpNn2sj3rzqu
Two vocabularies reach `memory.kv_cache` and the policy enum only listed one. The arch-level resolver owns asym/fwht/turbo; the loader's `StateQuant` parser owns q8/int8, f32/fp32 and q4/int4. Only "auto" and "q8" were common to both, so once validated config replaced the raw env reads, every legacy spelling became a startup panic: invalid value for memory.kv_cache: string does not satisfy Enum(["auto","q8","asym4",...]) `build_kld_ref_native` sets HIPFIRE_KV_MODE=f32 and died before processing a single token — the KLD reference oracle wants UNQUANTIZED KV precisely so its own attention error is not folded into every quant scored against it. Adds f32/fp32 only. q4/int4/int8 are also loader-accepted and stay out until something needs them, so this list remains a statement of what is supported rather than a union of every string ever parsed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G6rH6Y8vMnBpNn2sj3rzqu
ds4 had no route to a KLD number: `build_kld_ref_native` and `eval_hipfire` are both hardcoded to qwen35 (config reader, weight loader, scratch, `forward_prefill_batch`), and ds4 has no batched prefill at all — `forward_prefill_batch` is a per-token fallback and `..._chunk` computes lm_head for the last position only. So all-position logits can only come from per-token capture in the decode path. Two env-gated hooks in the ds4 generate loop, both off by default: HIPFIRE_DS4_LOGIT_DUMP=<path> append [u32 pos][u32 vocab][f32*vocab] HIPFIRE_DS4_FORCE_TOKENS=<ids> commit these tokens, not the argmax Capture sits before grammar masking and before sampling, so it is the model's own predictive distribution at that position. Teacher forcing is what makes the comparison mean anything. Two quants of one checkpoint diverge in greedy decoding almost immediately (measured: token 0 on 3 of 4 prompts), after which each position is conditioned on different text, and a position-wise KL compares unrelated distributions — that scored ~30 nats, a property of the method, not the quant. Forcing both models along one sequence fixes it; `scripts/ds4-kld-two-model.sh` verifies the two committed streams are identical and refuses to report otherwise. Forcing must cover the FIRST generated token, which is sampled before the decode loop, and must be keyed on a call counter rather than `pos` — the pre-loop site and the loop's first iteration share a `pos`, so a pos-keyed lookup issues forced[0] twice and shifts the rest. Both bugs produced plausible-looking numbers (18.4 nats) that the identity check caught. Measured, 0731 MQ2-Lloyd vs FP4 source precision, 64 positions, exact full-vocab KL: mean 0.325, median 0.052, p90 1.226, max 3.691 nats. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G6rH6Y8vMnBpNn2sj3rzqu
|
Context for anyone picking this up — I started merging this into What happened on 2026-08-05
Why it was revertedFrom
The recertification confirms the revert restored the golden set: decoded output byte-identical across both serving arms (MD5 That doc is careful to say startup variance is not solely attributable to this merge — "Startup therefore remains a separate measurement and loader-pipeline problem" — so the regression is real but the full attribution is open. Where that leaves this PRThe decode-path analysis still holds and is good: the new path is gated on So what this needs before re-landing is not conflict resolution, it is a load-time measurement against the pre-#563 baseline on the same fixture — specifically that the paged path does not push cold-load past the response timeout. Also relevant: since the revert, Re: @fivetide's question about #527 — @nwoolmer's seam analysis above is still accurate as far as the decode surface goes, but the paged-loading half now has no counterpart on Leaving this open and unmerged. cc @nwoolmer |
This reverts commit 2511103. # Conflicts: # crates/hipfire-arch-deepseek4/src/arch.rs # registry/models.json # registry/v1.json
|
Brought this branch up to date with current What changed
GitHub now reports the PR as mergeable. ValidationValidation is still pending. Per request, I did not run builds, tests, Hipfire code, examples, benchmarks, or repository gates. The update has only received static diff/API review so far. |
|
Triage: HOLD_WIP. Registry SKUs are already on master; paging/adapter remain unique but were reverted for startup/load timeouts and still depend on the unfinished mesh/TP gather path. Re-land only after the recorded load-time gate clears. |
beta moved 182 commits ahead and refactored three files this branch owns, so the four conflicts were relocation collisions rather than semantic ones. Resolved by porting this branch's work onto beta's new structure. - crates/hipfire-runtime/examples/daemon.rs — beta split the 28k-line example into hipfire-daemon + hipfire-generate. Accepted the deletion and ported the three DS4 KLD helpers (`ds4_reset_force_tokens`, `ds4_force_token`, `ds4_dump_logits`) and their four call sites into hipfire-generate/src/dense.rs, where `generate_deepseek4` now lives. The call-counter keying and the pre-loop forcing site are preserved — both are load-bearing for teacher forcing and neither is obvious from the signature. - crates/hipfire-quantize/src/main.rs — beta decomposed the 15k-line monolith into modules and this branch still had the monolith, so the whole file conflicted. Took beta's thin main.rs and redistributed this branch's additions: `repack_e2m1_ue8m0_to_hfp4g32` + its bit-exactness test into quant_hfp4.rs, `passes_prefix_filter` + its tests into model_filter.rs, `--exclude-prefix` into cli.rs, and the FP4-passthrough arm, `deepseek4-fp4`/`deepseek4-dense-precise` format gates and `keep_f16_dense` into pipeline.rs. - crates/hipfire-arch-deepseek4/src/forward.rs — beta relocated forward_ep and the mtp_* family into new `ep`/`mtp` modules. Took beta's re-exports and ported the one paging change in that block (the extra `None` pager argument at mtp_forward_batched's ffn_batched call) into mtp.rs. Kept this branch's five new paging helpers and took beta's widened visibility on ffn_routed and ffn_batched. - crates/hipfire-arch-deepseek4/src/lib.rs — union of both module lists. This is conflict resolution only. The load-time gate that caused the 2026-08-05 revert has NOT been run and nothing here claims it has. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
beta is green on leanup-ratchets and 39/39 on crate maps, so every violation here was this branch's. All three are registrations, not behaviour changes. - Six research probes move behind `--features lab`: `pinned_smoke` (hip-bridge), `expert_policy_sim` (arch-deepseek4), `async_prefetch_smoke` and `fp4_artifact_check` (runtime), `d2h_probe` and `hfp4_moe_indexed_parity` (rdna-compute). Verified they still compile under `--features lab`, so the gating hides nothing. ungated_examples returns to beta's 32. - The dispatch-bypass ledger records hipfire-arch-deepseek4 at 21. The +2 are both in this branch's new `expert_adapter.rs` (`gpu.gemv_f16_xf32` for the adapter's A and B projections), not pre-existing debt. - Regenerated the ten crate maps this branch drifted, including the three new arch-deepseek4 modules (expert_adapter, expert_pager, expert_policy) and its new `half` dependency. RATCHET-RAISE: bypass_total 222 -> 224, traded for the expert adapter's two GEMV call sites, recorded per-crate in the debt table. Still conflict resolution plus gate hygiene only. The load-time gate behind the 2026-08-05 revert has not been run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Merged current This is conflict resolution only. The load-time gate behind the 2026-08-05 revert has not been run, and nothing below claims it has. @Kaden-Schutt's HOLD_WIP still stands on its own terms. The conflicts were relocation, not disagreement
Gate hygiene
Verified
One failure, pre-existing on What this still does not answerEverything in @Kaden-Schutt's 2026-08-15 comment stands:
Leaving as draft. |
What
Adds DeepSeek-V4-Flash-0731 end to end: the model in the registry, the routed-expert
paging that lets it run when the experts do not fit, and an experimental
expert-prediction adapter for speculative prefetch.
The paging is the shippable part. The adapter is published as a measured negative
result — it is off by default and, as implemented, a regression. Details below rather
than buried.
Measured on starling (gfx1151, Radeon 8060S),
deepseek-v4-flash-0731.mq2lloyd.1. The model
deepseek-v4-flash-0731→hipfire-models/hipfire-deepseek-v4-flash-0731,arch_id=9,43 layers, 256 routed experts + 1 shared, top-6. Alias
deepseek4:0731.Ships the 3-stage DSpark draft chain instead of the classic nextn MTP head, so it
pairs with a
-dsparksidecar, not a-mtpone.temp=1.0mirrors the basecheckpoint's serving guidance.
registry_gen.py'sarch_id_for()matcheddeepseek-v4-flashexactly and failedclosed on the dated tag ("no arch_id mapping"). Widened to a prefix match so future
dated checkpoints do not each need an arm — the fail-closed behaviour was correct and
is preserved.
Model card:
docs/models/deepseek-v4-flash-0731.md.2. Routed-expert paging
Per-layer expert blobs become bounded slot pools; experts are
preadfrom the HFQon demand and the device pointer table is repointed before each MoE dispatch.
Default OFF. With
HIPFIRE_DEEPSEEK4_EXPERT_CACHE_GBunset the loader uploads alln_routed_expertsand the forward path is the code it was. An 8 GiB expert cachereplaces 72.2 GiB of resident experts, byte-identical output.
EXPERT_CACHE_GB=auto— the main performance dial (+28.8%)autotakes whatever fits after non-expert weights, KV and headroom, clamped againstMemAvailable; if everything fits it disables paging rather than paging pointlessly.
Hit rate saturates at 91.3% (rest is compulsory cold-miss), so past ~32 GiB the curve
flattens — 64 GiB buys another 4.5pp.
Picking the number by hand costs real throughput and fails silently, which is why
autoexists. Guarded: if/proc/meminfois unreadable the sentinel would have sizeda pool from
u64::MAX, so that now errors with an actionable message.Where decode time actually goes
Ablating routed MoE (8 GiB budget):
The routed-MoE path costs 74 ms/token and 80% of that is I/O, not compute — an
I/O-bound workload wearing a compute-bound costume. This is why cache size dominates
and why hiding latency does not.
Correctness
scripts/paging-neutrality-gate.shcompares committed token IDs (not text — BPE canrender two different token sequences identically) across three arms: fully resident, a
cache large enough for what the prompt touches, and one small enough to thrash. All
byte-identical over 192 greedy tokens.
PAGING_GATE_FAST=1runs it in 45 s instead of50 min by restricting routed experts to the first N layers.
Also fixes a pre-existing HIP-graph correctness bug found by the gate: the APE path
baked a host-resolved pointer at capture time, so graph replay diverged from direct
dispatch on every ds4 run, paged or not.
3. Expert-prediction adapter (experimental — OFF by default)
deepseek-v4-flash-0731-adapter-r128.bin, 44.6 MB. Predicts layer L+1's expertselection from layer L's hidden state so the pager can fetch a layer early:
The frozen native router still makes the real selection — the adapter only ranks
prefetch candidates, so a wrong prediction costs a wasted fetch, never a wrong token.
Output byte-identical with it on or off (greedy, 9/9 arms).
Listing it in the registry does not enable it; the runtime loads one only when
HIPFIRE_DEEPSEEK4_EXPERT_ADAPTERpoints at a file.It works as a predictor
ExpertRecall@M — of the 6 experts actually chosen, how many appear in the top-M:
Training-free alternatives were measured and rejected first — recency covers 0.1%
of the miss stream, a token-conditioned profile 13.0% (nearly all of it the 3
hash-routed layers we already stage exactly), and running the real
gate_{L+1}onh_L4.2% (27.6% recall). ds4's mHC replaces the residual stream with 4Sinkhorn-mixed streams, so there is no slowly-evolving residual to read through.
Rank matters more here than in published work: SpecPrefetch reports ~84% at r=32 on
64-expert DeepSeek-VL2; ds4 is 256-choose-6 and manages 54.9% at r=32, 75.6% at r=128.
It is still a regression
Cause, measured directly on the same workload:
Speculation more than doubles bytes read. Hit rate stays flat (~64%) while bytes
double — churn, not caching: staged experts evict entries the real dispatch then needs.
No amount of overlap pays for 2.1x the I/O.
Tightening the cache made it worse, so "constrained caches are where prefetch pays"
is refuted, not merely untested. Prefetch overhead is a fixed per-layer cost while its
benefit scales with how much I/O there is to hide, so it competes with the cache
budget rather than composing with it.
Supporting infrastructure (useful independently)
hipHostMalloc/hipHostFreebound,memcpy_htod_offset_asyncadded, pinned staging + dedicated copy stream + event wait, and the
preadmoved toa worker thread. Verified byte-exact on out-of-order ranges with staging reuse
(
examples/async_prefetch_smoke.rs). Pinning is load-bearing, not an optimisation —hipMemcpyAsyncfrom pageable memory silently degrades to a synchronousbounce-buffer copy.
probe showing per-dispatch D2H is 98% pipeline drain (6.1 µs idle vs 317.8 µs
with work queued).
Where it might still pay (untested)
is a plain memcpy with nothing to hide; on a discrete card it is a real PCIe DMA.
dispatch copies routing back to the host, including the ~64% that are pure hits
needing no host action.
Compatibility
a zeroed dummy, which paging must never leave in place).
expert_tp_row_gatherfrom [Superseded] refactor: unify device mesh, model parallelism, and generation dispatch #527. Every bufferon the fill path is already sized for it (
ExpertFillTransform, exercised bysynthetic slicing tests); only the concrete gather is missing. A 3-step removal recipe
is in the guard.
Risk
Default-off for both paging and the adapter. The one change on the always-on path is
the HIP-graph APE fix, which corrects a real pre-existing bug and was verified
bit-identical over 96 steps.