Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
79f5788
docs: Escha-W2 port design
nwoolmer Sep 2, 2026
4584359
docs: verify Escha-W2 format claims against golden vectors
nwoolmer Sep 2, 2026
78d0717
docs: third review pass — coverage gap, unrunnable gate, sizing
nwoolmer Sep 2, 2026
eef9e09
docs: fourth review pass — Escha's own loader contract
nwoolmer Sep 2, 2026
276f4cc
docs: fifth review pass — ran the MoE fixture
nwoolmer Sep 2, 2026
8f96230
docs: correct the quant-type id survey
nwoolmer Sep 2, 2026
8180668
docs: Escha-W2 Phase 1 implementation plan (35B-A3B)
nwoolmer Sep 2, 2026
5e67627
fix(plan): the G1 round-trip verifier did not work
nwoolmer Sep 2, 2026
be9824a
plan: resolve three pre-flight rubric conflicts
nwoolmer Sep 2, 2026
1ed615a
feat(escha): trellis codec CPU reference, gated on golden vectors
nwoolmer Sep 2, 2026
8fd733b
plan: escha f16 rounding is RNE, not hipfire's truncating helper
nwoolmer Sep 2, 2026
41106f3
feat(escha): H128 transforms and scale folding in the CPU reference
nwoolmer Sep 2, 2026
464b4f8
test(escha): cover input/output transform ordering and fold_scales mi…
nwoolmer Sep 2, 2026
b971a41
plan: Task 3 builds no moe_block — title and rationale corrected
nwoolmer Sep 2, 2026
b75203a
feat(escha): expert linear, SwiGLU and w8a16 in the CPU reference
nwoolmer Sep 2, 2026
4fb616e
fix(escha): expert_linear must not skip zero activations
nwoolmer Sep 2, 2026
5f7b1a7
plan: drop expert_linear's zero-activation skip
nwoolmer Sep 2, 2026
765f46c
feat(escha): register ESCHA2T16/ESCHA3T16 and the EschaH128 rotation …
nwoolmer Sep 2, 2026
fdd7ad7
fix(escha): fail closed on Unimplemented arch predicate, explicit Esc…
nwoolmer Sep 2, 2026
b29ceb2
fix(escha): gemv_steps returns Result instead of panicking on EschaH128
nwoolmer Sep 2, 2026
12856d4
feat(escha): safetensors -> hfq converter with the leaf contract enfo…
nwoolmer Sep 2, 2026
352760b
plan: converter must embed the parsed config in .hfq metadata
nwoolmer Sep 2, 2026
fb82481
fix(escha): embed config in .hfq metadata, fail closed not panic, add…
nwoolmer Sep 2, 2026
213fa2c
plan: Tasks 7-8 host wrappers used a launch API that does not exist
nwoolmer Sep 3, 2026
79ff405
feat(escha): G1 verbatim round-trip verifier + config load smoke test
nwoolmer Sep 3, 2026
fe2f128
feat(escha): GPU tile decode kernel, bit-exact against the CPU reference
nwoolmer Sep 3, 2026
2d99ce6
fix(escha): correct kernel doc, validate decode inputs, drop select-c…
nwoolmer Sep 3, 2026
2a73edd
feat(escha): H128 input/output transform kernels, gated against the r…
nwoolmer Sep 3, 2026
61e3ab8
perf(escha): parallelise the H128 butterfly across all 128 threads
nwoolmer Sep 3, 2026
76eb452
docs(escha): narrow the __float2half sign-of-zero comment, widen the …
nwoolmer Sep 3, 2026
eae4ff1
plan: Task 10 must batch H128 across experts (measured hard requirement)
nwoolmer Sep 3, 2026
264933b
test(escha): G4b — arch-6 router selects the same experts as escha
nwoolmer Sep 3, 2026
126a461
fix(escha): round router logits to f16 before top-k (escha only) + fi…
nwoolmer Sep 3, 2026
ea9e9f9
feat(escha): load escha experts as Q8_0 and run the H128 pair in arch-6
nwoolmer Sep 3, 2026
f488b96
fix(escha): resolve escha tensor names through the loader's candidate…
nwoolmer Sep 3, 2026
ed0d21e
fix(escha): fail closed on the indexed GPU-top-K path, gate prefill, …
nwoolmer Sep 3, 2026
5d552fb
perf(escha): run the routed experts on the indexed GPU-top-K path
nwoolmer Sep 3, 2026
f1ecea5
fix(escha): embed the tokenizer, chat template and generation_config
nwoolmer Sep 3, 2026
5e092e6
feat(escha): registry entry, coherence and the G5 KLD result for Phase 1
nwoolmer Sep 3, 2026
6547c78
perf(escha): pack the routed experts into one buffer per (layer, proj…
nwoolmer Sep 3, 2026
2e549c7
perf(escha): stop computing logits for prefill tokens that discard them
nwoolmer Sep 3, 2026
d4dff4a
fix(rdna): stop the FP16 activation scratch returning another tensor'…
nwoolmer Sep 3, 2026
06ab4db
perf(escha): make batched prefill reachable — 45.3 -> ~163 tok/s
nwoolmer Sep 3, 2026
a63d04d
fix(escha): make the G1 verifier fail when it compares nothing
nwoolmer Sep 3, 2026
4ef29bc
fix(rdna): delete HIPFIRE_FP16_X_LEGACY_CACHE
nwoolmer Sep 3, 2026
571d3a9
fix(dispatch): fail closed when an escha layer takes a non-escha pref…
nwoolmer Sep 3, 2026
2de41e4
fix(dispatch): dtype_needs_rotation must answer true for the escha dt…
nwoolmer Sep 3, 2026
2dec2ff
fix(qwen35): refuse an escha MoE in the MTP head instead of skipping …
nwoolmer Sep 3, 2026
a5e37d1
perf/cleanup(escha): OnceLock the debug hooks, drop a divergent-barri…
nwoolmer Sep 3, 2026
e113254
test(escha): make the gates enforce their own contracts
nwoolmer Sep 3, 2026
ce22593
docs(escha): correct §10.3, record the gates and the limitations, reg…
nwoolmer Sep 3, 2026
1d7d1ea
feat(escha): fused routed GEMV that decodes the trellis code in-register
nwoolmer Sep 3, 2026
02c36d3
perf(escha): default the routed experts to the native trellis store
nwoolmer Sep 3, 2026
e193b4d
perf(escha): expert-grouped GEMM for batched prefill
nwoolmer Sep 3, 2026
7a85963
fix(escha): grouping threshold must not depend on tile height
nwoolmer Sep 3, 2026
2f8be99
fix(qwen35): detect Escha-W2 before the EP-shard block
nwoolmer Sep 3, 2026
4cee172
perf(escha): WMMA grouped GEMM — measured, and it is NOT the prefill fix
nwoolmer Sep 3, 2026
1945a9e
perf(escha): WMMA grouped GEMM, with B read from global — prefill 375…
nwoolmer Sep 3, 2026
5642adc
perf(escha): measure WMMA at decode — negative, and it says why
nwoolmer Sep 3, 2026
456748a
feat(escha): dense down-quant tooling + the KLD, and a dispatch bug i…
nwoolmer Sep 3, 2026
db226d9
perf(escha): down-quant dense weights to MQ6, +17% decode
nwoolmer Sep 3, 2026
7ebcdf7
fix(kernels): 1-byte overread past every 6-bit group, 9 WMMA kernels
nwoolmer Sep 3, 2026
2b5a4c8
refactor(escha): generalise HIPFIRE_ESCHA_DENSE parsing to <fmt>[:|!]…
nwoolmer Sep 3, 2026
a952a60
fix(kernels): gemv_mq6g256v2_multirow_r2 produced garbage off gfx1100…
nwoolmer Sep 3, 2026
5f7b676
perf(escha): route gate_up through the wide expert GEMV, -8.7% on tha…
nwoolmer Sep 3, 2026
3b6d92b
feat(escha): three shipped dense recipes, q8 the default
nwoolmer Sep 3, 2026
6c26e8f
registry: three escha SKUs, local-only until the HF upload lands
nwoolmer Sep 3, 2026
0e5cc10
registry: publish the three escha SKUs
nwoolmer Sep 4, 2026
211d43e
registry: extension names the quant format, not the dense recipe
nwoolmer Sep 4, 2026
bc41962
fix(escha): converter silently dropped the 27B's MTP head
nwoolmer Sep 4, 2026
28df9f2
feat(escha): dense-projection leaf naming and detection
nwoolmer Sep 4, 2026
d8a01b5
feat(escha): load one dense escha linear (weight + rin/rout + bias)
nwoolmer Sep 4, 2026
0e20917
feat(escha): preflight accepts escha-coded dense projections
nwoolmer Sep 4, 2026
1085ef9
feat(escha): dense linear forward primitive (H128 in / GEMV / H128 ou…
nwoolmer Sep 4, 2026
40d6d06
fix(escha): dense forward must use the BATCHED H128, and a gate that …
nwoolmer Sep 4, 2026
91d164f
test(escha): prove the H128 rotations fold into the weight
nwoolmer Sep 4, 2026
6fe050b
feat(escha): fold H128 rotations and diagonals into the weight
nwoolmer Sep 4, 2026
ce3b4f3
feat(escha): converter fold mode — the 27B now runs end to end
nwoolmer Sep 4, 2026
eacf76e
feat(escha): load the 27B's per-projection biases
nwoolmer Sep 4, 2026
a5f622e
feat(escha): apply the 27B's biases — PPL 13.9142 -> 13.6957
nwoolmer Sep 4, 2026
7dd1712
feat(escha): biases on the full-attention prefill paths
nwoolmer Sep 4, 2026
0f0776e
fix(escha): H128/SwiGLU raced the capture stream — G5 is usable again
nwoolmer Sep 4, 2026
58df40b
feat(escha): native trellis GEMV for dense linears — no decode, no fold
nwoolmer Sep 4, 2026
64bd257
test(escha): time the dense stores — native is 1.7x faster than Q8_0
nwoolmer Sep 4, 2026
94f6f69
feat(escha): loader accepts trellis quant types 42/43
nwoolmer Sep 4, 2026
bcd3796
feat(escha): EschaProj — batched trellis projection, gate-verified
nwoolmer Sep 4, 2026
210eeaf
feat(escha): layers carry trellis metadata; loading side complete
nwoolmer Sep 4, 2026
f554698
feat(escha): native trellis 27B RUNS — decode path, gate passed
nwoolmer Sep 4, 2026
56aa9c8
feat(escha): route native trellis through batched prefill
nwoolmer Sep 4, 2026
20707f7
perf(escha): grouped GEMM for batched prefill — 14.0 -> 71.8 tok/s
nwoolmer Sep 4, 2026
8a735af
bench: escha native GEMV at dense-decode shapes
nwoolmer Sep 4, 2026
5863fd2
fix(saddle-lab): stop the n-gram blocker banning the prompt's own tokens
nwoolmer Sep 4, 2026
8fd307e
Merge remote-tracking branch 'warpfront/master' into nw_escha_w2
nwoolmer Sep 4, 2026
d4d4e16
perf(escha): batched prefill was broken, then unamortised — 52 -> 108…
nwoolmer Sep 4, 2026
9b77400
feat(infer): use batched prefill for text-only prompts
nwoolmer Sep 4, 2026
bb77ff8
perf(escha): nt-major tile grid for dense codes, 24% on the decode GEMV
nwoolmer Sep 4, 2026
7813a8a
registry: escha SKUs on the xt/base/pro convention, and add the 27B
nwoolmer Sep 5, 2026
388ffc2
registry: measured min_vram for the 27B escha SKUs
nwoolmer Sep 5, 2026
df45930
registry: put both escha models' min_vram on the same measured basis
nwoolmer Sep 5, 2026
e1d2d55
feat(qwen35): load MTP heads carried as in-trunk mtp.* tensors
nwoolmer Sep 5, 2026
33ba046
perf(generate): add HIPFIRE_DECODE_PROFILE to the AR decode loop
nwoolmer Sep 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 11 additions & 2 deletions crates/hipfire-arch-cohere2moe/src/forward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ use crate::cohere2moe::{Cohere2MoeState, Cohere2MoeWeights, Ffn};
use crate::config::{AttnKind, Cohere2MoeConfig};
use hipfire_dispatch::context::DispatchCtx;
use hipfire_dispatch::families::moe::{MoeDtypes, MoePrefillParams};
use hipfire_runtime::llama::KvCacheExt;
use hipfire_runtime::llama::{
fused_silu_mul_rotate_mq_batched_for, moe_family, rotate_x_mq_batched_for, rotate_x_mq_for,
weight_gemv, weight_gemv_residual};
use hipfire_runtime::llama::KvCacheExt;
weight_gemv, weight_gemv_residual,
};
use rdna_compute::{DType, Gpu, GpuTensor};

/// Grouped-MoE prefill tiling constant — must match `run_moe_prefill`'s
Expand Down Expand Up @@ -854,6 +855,7 @@ pub fn forward_batch(
routed_has_mixed_experts: false,
per_expert_gate_up: None,
per_expert_down: None,
routed_escha_transforms: false,
has_paro_shared: false,
},
batch_size: b,
Expand Down Expand Up @@ -889,6 +891,13 @@ pub fn forward_batch(
paro_down: None,
down_awq_scale: None,
routed_out: None,
// Not an escha model: the escha branch in
// `run_moe_prefill` is skipped and Path 1 / Path 2 run
// exactly as before, and `check_moe_prefill_supported` is
// a no-op for `layer_is_escha == false`.
escha: None,
layer_is_escha: false,
hidden,
};
moe_family()
.run_prefill(&ctx, gpu, &params)
Expand Down
26 changes: 26 additions & 0 deletions crates/hipfire-arch-qwen35/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,32 @@ hipfire-reap = { path = "../hipfire-reap" }
saddle-core = { path = "../saddle-core" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

[dev-dependencies]
# G4b (Escha-W2 Task 9): `examples/escha_router_contract.rs` reads the escha
# fixture's f16 golden buffers via the same decoder the frozen CPU oracle
# uses (`hipfire_quantize::float16::f16_to_f32`).
hipfire-quantize = { path = "../hipfire-quantize" }

[[example]]
name = "escha_model_smoke"
path = "examples/escha_model_smoke.rs"
required-features = ["deltanet"]

[[example]]
name = "escha_moe_block_gate"
path = "examples/escha_moe_block_gate.rs"
required-features = ["deltanet"]

[[example]]
name = "escha_router_contract"
path = "examples/escha_router_contract.rs"

[[example]]
name = "escha_prefill_batch_gate"
path = "examples/escha_prefill_batch_gate.rs"
required-features = ["deltanet"]

[[example]]
name = "test_qwen35_load_multi"
path = "examples/test_qwen35_load_multi.rs"
Expand Down
16 changes: 16 additions & 0 deletions crates/hipfire-arch-qwen35/examples/escha_config_smoke.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! Prove the converted .hfq's metadata satisfies arch-6's config loader.
fn main() {
let path = std::env::args().nth(1).expect("usage: <model.hfq>");
let hfq = hipfire_runtime::hfq::HfqFile::open(std::path::Path::new(&path))
.expect("open hfq");
println!("arch_id = {}", hfq.arch_id);
println!("tensors = {}", hfq.tensors().len());
match hipfire_arch_qwen35::qwen35::config::config_from_hfq(&hfq) {
Ok(c) => println!(
"config OK: dim={} layers={} experts={} top_k={} moe_inter={} vocab={} is_vl_text={}",
c.dim, c.n_layers, c.num_experts, c.num_experts_per_tok,
c.moe_intermediate_size, c.vocab_size, c.is_vl_text
),
Err(e) => { eprintln!("config FAILED: {e}"); std::process::exit(1); }
}
}
265 changes: 265 additions & 0 deletions crates/hipfire-arch-qwen35/examples/escha_model_smoke.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026 Kaden Schutt
// hipfire — see LICENSE and NOTICE in the project root.
//! Escha-W2 end-to-end smoke (Task 10): load the WHOLE `.hfq` single-GPU and
//! run PREFILL and then decode through the production forward paths.
//!
//! The G4 block gate (`escha_moe_block_gate`) calls the routed executor
//! directly, with routing injected. That proves the maths; it does NOT prove
//! that a real `qwen35::forward` ever reaches it. This does: it asserts layer
//! 0 came through the escha loader, then decodes and reads the H128 launch
//! counter, which must be exactly `4 * n_layers` per token — the batched
//! budget. A regression to a per-expert wiring shows up here as `4 * k *
//! n_layers` (1280 at A3B) rather than 160, with no numerical change at all.
//!
//! # The prefill phase, and why the launch counter is the load-bearing assert
//!
//! Escha layers route to an escha executor in prefill too, but through
//! `escha_routed_prefill_indexed` rather than the decode one. The H128 launch
//! budget is what identifies which of THREE things happened, and the three are
//! indistinguishable by looking at the logits:
//!
//! | launches (8-token prompt, 40 layers) | what ran |
//! |---|---|
//! | **160** = `4 * n_layers` per CHUNK | batched escha prefill — correct |
//! | 1 280 = `n * 4 * n_layers` | silently fell back to the per-token loop |
//! | 0 | a batched MoE body with NO escha awareness |
//!
//! Zero is the dangerous one: the generic batched routed body would run the
//! Q8_0 experts without the H128 pair and emit finite, fluent, ~1e-1-wrong
//! hidden state, which finiteness alone would never catch. 1 280 is not wrong,
//! just 3.6x slower — but a silent fallback is exactly how a performance fix
//! rots, so it fails here too rather than being tolerated.
//!
//! The count is per CHUNK because that is the whole point of the batched body:
//! one launch of `escha_h128_in_batched` covers `n_tokens * k` slots. A prompt
//! longer than the prefill chunk ceiling would legitimately show one budget
//! per chunk; this gate keeps the prompt inside one chunk so the expected
//! number is exact.
//!
//! Token ids are arbitrary here on purpose: this gate is about the launch
//! budget and the structural invariants (finite logits, a non-degenerate
//! argmax), not about semantics. Semantic checking is Task 11's — the
//! converter now embeds the tokenizer, chat template and generation_config, so
//! the daemon DOES drive this checkpoint (`scripts/_coherence_runner.py`, and
//! §10.4 of the design doc).
//!
//! COST: **37.6 GB resident** (37 587 996 672 B), measured as an amdgpu GTT
//! delta on gfx1151 (`scripts/escha-gtt-probe.sh`: 40.94 GB peak over a
//! 3.36 GB idle baseline). 34.2 GB of that is the Q8_0 routed experts and
//! ~3.3 GB is everything else. It was 67.9 GB until the experts were packed
//! one device buffer per (layer, projection): while each of the 20,480
//! per-expert buffers was its own allocation, the HIP allocator's 2 MiB
//! granule rounded the 2.125 MiB gate_up up to 4 MiB and the 1.0625 MiB down
//! up to 2 MiB, spending 30 GB on rounding. Still not free on a 128 GB
//! workstation with other applications running — check headroom first.
//! See design doc §10.3, which now records this figure rather than the 67.9 GB
//! it predated.
//!
//! Run:
//! cargo run --release -p hipfire-arch-qwen35 \
//! --example escha_model_smoke -- /data/hipfire-models/escha-35b.hfq
use hipfire_arch_qwen35::qwen35;
use hipfire_runtime::hfq::HfqFile;
use hipfire_runtime::loader_api::{CaskConfig, LoadCtx, ModelSource, SpecLoadCfg};
use rdna_compute::Gpu;
use std::path::Path;

fn main() -> Result<(), String> {
let path = std::env::args()
.nth(1)
.unwrap_or_else(|| "/data/hipfire-models/escha-35b.hfq".to_string());
let hfq = HfqFile::open(Path::new(&path)).map_err(|e| format!("open: {e:?}"))?;
let mut gpu = Gpu::init().map_err(|e| format!("gpu: {e:?}"))?;
let cask = CaskConfig::default();
let src = ModelSource::Hfq(hfq);
let mut ctx = LoadCtx {
path: &path,
max_seq: 512,
deepseek4_compute_placement: Default::default(),
deepseek4_experts_per_token: None,
draft_path: None,
kv_mode_override: None,
kv_backend: hipfire_runtime::kv_backend::KvBackend::Contiguous,
kv_adaptive_override: None,
state_quant_override: None,
cask: &cask,
pp: 1,
spec: SpecLoadCfg::default(),
gpu: &mut gpu,
gemma4_drafter_path: None,
gemma4_draft_len: 3,
};
let t0 = std::time::Instant::now();
let mut b = hipfire_arch_qwen35::load_qwen35_bundle(src, &mut ctx)?;
eprintln!("loaded in {:?}", t0.elapsed());

// Layer 0 must have come through the escha loader, and its experts must
// hold one of the containers that loader produces — not whatever the
// generic per-expert path would have found.
//
// The exact container depends on `HIPFIRE_ESCHA_EXPERT_STORE` and is not
// what this gate is about, so it is asserted as a SET rather than pinned
// to one value. It is asserted at all because the failure it catches is
// "the escha loader did not run and some other path filled these slots",
// which is a different bug from a wrong store.
match &b.weights.layers[0] {
qwen35::LayerWeights::DeltaNetMoe(l) => {
assert!(l.ffn.escha.is_some(), "layer 0 carries no escha tables");
assert!(
matches!(
l.ffn.experts[0].gate_up.gpu_dtype,
rdna_compute::DType::Escha2T16
| rdna_compute::DType::Escha3T16
| rdna_compute::DType::Q8_0
),
"layer 0 routed experts are {:?}, which no escha store produces",
l.ffn.experts[0].gate_up.gpu_dtype
);
eprintln!(
"layer0: escha=Some experts={} gate_up dtype={:?} m={} k={}",
l.ffn.experts.len(),
l.ffn.experts[0].gate_up.gpu_dtype,
l.ffn.experts[0].gate_up.m,
l.ffn.experts[0].gate_up.k
);
}
_ => panic!("layer 0 is not a DeltaNet+MoE layer"),
}

let want_launches =
hipfire_dispatch::pipeline::escha::escha_launches_per_token(b.config.n_layers);

// ── Phase 1: PREFILL ─────────────────────────────────────────────────
// 8 tokens, matching the G4 fixture width, through the real batched
// prefill entry point (which is expected to fall through to its per-token
// loop — see the module docs).
const PROMPT: [u32; 8] = [1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000];
let before_prefill = rdna_compute::escha_h128_launches();
let t = std::time::Instant::now();
qwen35::forward_prefill_batch(
ctx.gpu,
&b.weights,
&b.config,
&PROMPT,
0,
&mut b.kv_cache,
&mut b.dn_state,
&b.scratch,
None, // hidden ring
None, // per-token hidden out — keep last-token logits enabled
None, // gdn tape
None, // tree verify
)
.map_err(|e| format!("prefill: {e:?}"))?;
ctx.gpu
.hip
.device_synchronize()
.map_err(|e| format!("sync: {e:?}"))?;
let prefill_launches = rdna_compute::escha_h128_launches() - before_prefill;
let prefill_logits = ctx
.gpu
.download_f32(&b.scratch.logits)
.map_err(|e| format!("download prefill logits: {e:?}"))?;
let prefill_bad = prefill_logits
.iter()
.take(b.config.vocab_size)
.filter(|v| !v.is_finite())
.count();
eprintln!(
"prefill n={}: H128 launches={prefill_launches} (want {want_launches} for one \
batched chunk; {} would be the per-token fallback), non-finite logits={prefill_bad}/{}, \
{:?}",
PROMPT.len(),
PROMPT.len() * want_launches,
b.config.vocab_size,
t.elapsed()
);
assert_eq!(
prefill_bad,
0,
"non-finite logits after an {}-token prefill",
PROMPT.len()
);
// BOTH correct routes are accepted, and everything else fails.
//
// `HIPFIRE_PREFILL_BATCHED=0` is a supported escape hatch, so a gate that
// demanded the batched count would fail the model under a configuration it
// is meant to survive — and a gate that has to be run with one specific
// env is a gate people stop running. What must never be accepted is ZERO:
// that is the generic batched MoE body running escha weights without the
// H128 pair, the finite-fluent-wrong case no logit check would catch.
let per_token_total = PROMPT.len() * want_launches;
let route = match prefill_launches as usize {
n if n == want_launches => "batched escha prefill body",
n if n == per_token_total => "per-token fallback (HIPFIRE_PREFILL_BATCHED=0?)",
_ => "UNKNOWN",
};
eprintln!("prefill route: {route}");
assert!(
prefill_launches as usize == want_launches || prefill_launches as usize == per_token_total,
"PREFILL issued {prefill_launches} H128 launches, which is neither the batched \
budget ({want_launches} = 4 x {} layers, once for the whole chunk) nor the \
per-token one ({per_token_total}). ZERO in particular means the model reached a \
BATCHED MoE body with NO escha awareness: it omits both Hadamard transforms and \
emits finite, fluent, ~1e-1-wrong hidden state that no finiteness or argmax check \
would catch. Check that the escha branch at the top of `run_moe_prefill` still \
fires before Path 1 / Path 2.",
b.config.n_layers
);
// Under the DEFAULT configuration the batched body is the expected route;
// a silent fall back to per-token is correct but 3.6x slower, and a
// performance fix that quietly stops applying is how this regresses.
if hipfire_runtime::config::get().prefill_batched {
assert_eq!(
prefill_launches as usize, want_launches,
"default config, but prefill took the per-token route ({prefill_launches} \
launches). Run with HIPFIRE_DEBUG_BATCH=1 to see which layer refused."
);
}

// ── Phase 2: DECODE, continuing from the prefilled context ───────────
let mut prev = rdna_compute::escha_h128_launches();
for (i, &tok) in [9000u32, 10000, 11000, 12000].iter().enumerate() {
let pos = PROMPT.len() + i;
let t = std::time::Instant::now();
let logits = qwen35::forward(
ctx.gpu,
&b.weights,
&b.config,
tok,
pos,
&mut b.kv_cache,
&mut b.dn_state,
)
.map_err(|e| format!("forward: {e:?}"))?;

let n_bad = logits.iter().filter(|v| !v.is_finite()).count();
let mut best = f32::NEG_INFINITY;
let mut argmax = 0usize;
for (j, &v) in logits.iter().enumerate() {
if v > best {
best = v;
argmax = j;
}
}
let mean = logits.iter().sum::<f32>() / logits.len() as f32;
let now = rdna_compute::escha_h128_launches();
let launches = now - prev;
prev = now;
eprintln!(
"pos {pos} tok {tok}: {} logits, non-finite={n_bad}, argmax={argmax} ({best:.4}), \
mean={mean:.4}, H128 launches={launches}, {:?}",
logits.len(),
t.elapsed()
);
assert_eq!(n_bad, 0, "non-finite logits at pos {pos}");
assert!(best > mean, "degenerate logit distribution at pos {pos}");
assert_eq!(
launches as usize, want_launches,
"H128 launches per token drifted from the batched budget"
);
}
eprintln!("escha_model_smoke: PASS");
Ok(())
}
Loading
Loading