Skip to content

perf(gdn-prefill): ldmatrix A+B GDN-projection GEMM — 2.1x kernel, warm-TTFT median -5.8% / p90 -10.3%#296

Open
tbraun96 wants to merge 8 commits into
mainfrom
perf/small-m-prefill
Open

perf(gdn-prefill): ldmatrix A+B GDN-projection GEMM — 2.1x kernel, warm-TTFT median -5.8% / p90 -10.3%#296
tbraun96 wants to merge 8 commits into
mainfrom
perf/small-m-prefill

Conversation

@tbraun96

@tbraun96 tbraun96 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

The GDN-projection prefill GEMM (fp8_gemm_t) was the single largest kernel in warm prefill (34% of warm GPU time) and was badly untuned: ncu measured 10.7% SM throughput with a 56% MIO-queue stall — it read every A+B MMA fragment with ~32 scalar shared-memory loads per K-step.

This ports the proven int8_gemm_8w_ldmab ldmatrix.x4 A+B fragment loads to FP8. E4M3 shares the m16n8k32 8-bit fragment layout with s8, so the loads transfer verbatim; only the MMA (s8e4m3) and the epilogue (direct f32 accumulate — fp8_gemm_t is unscaled, the scale is folded into the weights upstream) change. 8 ldmatrix.x4 replace 32 scalar loads.

Result: 2.1× on the kernel (14.6 → 30.5 TFLOP/s, cosine 1.000000) and the first prefill change to move end-to-end latency above the noise floor.

Kernel

before (fp8_gemm_t) after (fp8_fp8_gemm_ldmab)
TFLOP/s @ GDN in_proj (N=10240, K=5120, M=512) 14.6 30.5 (+109%)
ncu MIO stall 74 cyc 44 cyc
kernel duration 3.30 ms 1.84 ms
cosine vs fp8_fp8_gemm_t 1.000000 (bit-exact)

End-to-end (agentic subset, seed 42, all runs 174/174, IoU 0.6264 identical)

Same-box A/B (same image, only the env toggled) and cross-box replication:

warm TTFT baseline (scalar) ldmab delta
median 1481 1397 −5.7%
avg 1655 1537 −7.1%
p90 2307 2074 −10.1%
max 5145 4270 −17.0%

Cross-box confirms: ldmab dgx1 1397/1537/2074 · ldmab dgx3 1385/1522/2054 · baseline dgx1 1481/1655/2307 · baseline dgx2 1470/1644/2296. Averaged: median −5.8%, avg −7.3%, p90 −10.3%.

Accuracy gates

GDN is drift-sensitive, so this was gated hard:

  • KL logit drift: 100% token agreement (519/519), mean_KL = max_KL = 0.000000 — bit-identical to baseline (10 diverse prompts, top-5 logprobs).
  • Microtest cosine 1.000000 vs the validated fp8_fp8_gemm_t.
  • Agentic IoU 0.6264, identical across all four A/B runs.
  • CI: radix_tree 41 pass / 0 fail. Coherence (factual / haiku / math / parallel tool-calls) pass.

Stability: full golden e2e

Full agentic run on the golden config (unsloth Qwen3.6-27B-NVFP4, qwen3_coder parser, --disable-tool-grammar true, 128 slots / interval 32, MTP --num-drafts 1 bf16, util 0.85, bf16 KV, 32k), ldmab default-on:

1007 samples issued / 1007 completed / 0 failed, duration 5714s, TTFT median 1318 ms / avg 1591 ms, agentic score 0.6202.

For reference, llama.cpp full-run on the same suite is 1431 median / 1666 avg — so Atlas now wins warm-TTFT median (−7.9%) and avg (−4.5%) on top of the existing decode and wall-clock leads.

Default-on

ATLAS_FP8_LDMAB is an opt-out (=0 falls back to the scalar path), not an opt-in — the win is unconditional and accuracy-neutral, so it should not sit behind a flag. Routing lives in ops::fp8_gemm_n128 (K must be a multiple of 32, the ldmab K-tile); it quantizes the BF16 activation to E4M3 once into a persistent grow-only scratch, then launches the ldmatrix GEMM.

Commit notes for the reviewer

  • ad92cd66 / ec1159a8 / cc2ec62f — the win (kernel, routing, default-on). These are the commits that matter.
  • 026d7aa9 — the DIAG-dump gating fix this PR originally carried (finalize_last fired a full-vocab 248k logits D2H + sort on every deep prefill turn; now env-gated only). Production hygiene, unchanged and still included.
  • 006ea421 (half-batched B-frag register pre-stage) and 8c7c5b94 (4-stage cp.async pipeline + the TFLOP/s microtest harness) touch fp8_gemm_t, which is now only the fallback path. Both are cosine-verified but measured perf-neutral end-to-end; they were in the tree for the gated build. 8c7c5b94 is still WIP-labeled. Recommend squash-merge, or I can rebase those two out if you'd rather ship only the ldmab commits.

What this does NOT fix

p90 at full-suite scale (2436 ms) still trails llama's 2276 ms. That residual is not this kernel — it's fixed per-turn overhead and the attention-at-depth path. Separately verified as dead ends this cycle: fp8-KV / KV-compression (neutral-to-worse → attention is not bandwidth-bound), and a deeper NST=4 pipeline on the ldmab kernel itself (correct at cosine 1.0 once a loop-counter/threadIdx shadow was fixed, but a regression at every shape — the 32KB smem halves occupancy and the 2-stage double-buffer already hides the latency).

The post-norm and logits diagnostics auto-fired on every last chunk above
16384 tokens — i.e. every deep agentic turn — doing an unconditional
gpu.synchronize + bf16 readback + full-vocab (248k) D2H copy and sort,
plus WARN-level log spam, all on the production TTFT path. Single-stream
TTFT impact measures within noise, but the forced synchronize serializes
concurrent-sequence batches and the per-turn WARN dump is production
noise. Gate both behind the existing ATLAS_DIAG_GEMMA4 env only.

Co-Authored-By: Azeez Ishaqui <azeez@avarok.net>
AzeezIsh and others added 5 commits July 10, 2026 16:33
…arness

fp8_gemm_t (GDN in_proj/out_proj prefill, 34% of warm GPU time) ran a
2-stage cp.async loop with wait_group-0 full drain every K-step -> memory
latency unhidden -> ~20 TFLOP/s (vs llama Q4_K 60, int8-faith2 44 proven
achievable on this GB10). Deepened to a 4-stage pipeline (NSTAGE-1 tiles
in flight, staged wait_group<NSTAGE-2> drain). Microtest: cosine 0.9997
PASS (accuracy-neutral, same FP8 MMA); +12% M=128 / +4% M=256 / +3% M=512
(warm-suffix regime, where prefill is latency-bound). NOT model-gated yet
(GDN drift-sensitive -> needs coherence+BFCL+agentic IoU N>=10). Full
faith2 port (big-K-128 loaded once + register frag pre-stage) is the
larger remaining lever toward 40+ TFLOP/s.

Co-Authored-By: Azeez Ishaqui <azeez@avarok.net>
…eutral warm-TTFT -0.8%

E2E-gated (dgx1 agentic subset, seed 42, 174/174, IoU 0.6264 byte-identical
to baseline): warm TTFT median 1478->1466, avg 1656->1643, p90 2307->2288
(-0.8% every percentile, consistent = real, not noise). Stacks the 4-stage
cp.async pipeline + KT=64 big-K + half-batched (8-at-a-time) weight-fragment
register pre-stage to break the load->MMA latency chain at +16 regs (full
16-frag hoist crossed the spill threshold on acc[16][4]). fp8_gemm_t is 34%
of warm GPU time but ~15% of TTFT, so kernel gains cap ~2-3% e2e; this is a
clean partial toward the faith2 2x. Coherence + parallel-toolcall pass.

Co-Authored-By: Azeez Ishaqui <azeez@avarok.net>
…->30.5 TFLOP/s), cosine 1.0

ncu diagnosed fp8_gemm_t at 10.7% SM / 56% MIO-queue stall: it read every A+B
MMA fragment with ~32 scalar smem loads/K-step. FP8 e4m3 shares the m16n8k32
8-bit fragment layout with s8, so ported the proven int8_gemm_8w_ldmab
ldmatrix.x4 A+B loads verbatim (8 ldmatrix replace 32 scalar), swapping only
the MMA (s8->e4m3, direct f32 accum; fp8_gemm_t is unscaled). Microtest @ GDN
in_proj shape (N=10240,K=5120,M=512): 30.5 TFLOP/s vs 14.6 baseline (+109%),
bit-identical to fp8_fp8_gemm_t (cosine 1.000000). ncu: MIO stall 74->44 cyc,
duration 3.30->1.84ms. vs llama.cpp 63 TFLOP/s (closes half the gap); Atlas
nvfp4_mmq FFN already 114 via ldmatrix. Still MIO-bound -> deeper-pipeline
headroom. Model wiring (quant A->ldmab in GDN prefill) + e2e = next.

Co-Authored-By: Azeez Ishaqui <azeez@avarok.net>
…_FP8_LDMAB)

Routes the GDN-projection prefill GEMM through fp8_fp8_gemm_ldmab (ldmatrix
A+B, 2.1x) when ATLAS_FP8_LDMAB=1: quantizes the bf16 activation to e4m3 once
into a persistent grow-only scratch (bf16_to_fp8), then launches the ldmatrix
kernel. K%32==0 guard; cached kernel handles. Falls through to the scalar
fp8_gemm_t otherwise (default, unchanged).

Same-box A/B (dgx1, agentic subset, seed 42, 174/174, IoU 0.6264 identical):
warm TTFT median 1481->1397 (-5.7%), avg 1655->1537 (-7.1%), p90 2307->2074
(-10.1%), max 5145->4270 (-17%). Beats llama full-run (1431/1666/2276) on
median/avg/p90. Env opt-in pending full BFCL + N>=10 gate before default.

Co-Authored-By: Azeez Ishaqui <azeez@avarok.net>
…-out ATLAS_FP8_LDMAB=0)

The ldmatrix routing is a confirmed accuracy-neutral perf win (2x2 same-box +
cross-box: warm TTFT median -5.8%, p90 -10.3%, max -17%, IoU 0.6264 identical),
so it should not hide behind an opt-in flag. Default-on; ATLAS_FP8_LDMAB=0
restores the scalar fp8_gemm_t fallback.

Co-Authored-By: Azeez Ishaqui <azeez@avarok.net>
@SeedSource

Copy link
Copy Markdown
Collaborator

[BETA] Automated PR review — SeedSource AI pipeline

Advisory only, not a merge gate. AI-generated (internal risk-triage, Atlas serve gate, GLM-5.2 review), attributed per Atlas convention.

Triage

  • Risk: low - serve-test-required (triage advisory): False - path-heuristic (authority): True
  • Rationale: Removed diagnostic syncs based on context length, making them env-gated to improve TTFT performance.
  • Serving-path files: crates/spark-model/src/model/trait_impl/prefill_b/finalize_last.rs

Serve gate

  • Build: 31s - Qwen3.6-35B-A3B-FP8 on GB10 node1
  • Top-k KL vs main: 0.0009 (no shift vs main) - decode delta -0.2%
  • Coherence probe: coherence=pass ping=10/10 (note: our test_coherence.py is flaky here; KL is the reliable serve signal)

GLM-5.2 code review

Correctness: Sound. Both gates correctly preserve the env-var path; the auto-trigger removal is semantically clean. No behavioral change when ATLAS_DIAG_GEMMA4 is unset (the new default — no sync/readback).

Risk: Low-to-moderate. This is a default-behavior change: previously, any prefill with chunk_start + chunk_len > 16384 silently emitted diagnostics (sync + D2H + sort on 248k logits). If anyone in CI/staging relied on those dumps appearing unconditionally for long-context runs, they'll silently disappear. Verify no dashboards/alerts scrape these tracing::warn lines. The env-var escape hatch remains, so on-call debugging is unaffected.

Quality: Comment is accurate and useful. Minor: is_ok_and(|v| v == "1" || v == "true") is duplicated at both sites — not worth a helper for two call sites, but note if a third appears. Consider documenting the env var in a central diagnostics README so operators know it exists.

Verdict: Approve. Confirm no CI/log consumers depend on the auto-trigger output.

Pipeline: build+serve on GB10, GLM-5.2 via NVIDIA NIM, internal risk-triage classifier. External diff treated as untrusted input. Public repo.

@tbraun96 tbraun96 changed the title fix(prefill): gate finalize_last DIAG dumps behind env (production hygiene) perf(gdn-prefill): ldmatrix A+B GDN-projection GEMM — 2.1x kernel, warm-TTFT median -5.8% / p90 -10.3% Jul 11, 2026
…t of gemm_dense

fp8_fp8_gemm_ldmab is only compiled for the qwen3.6-27b/nvfp4 target, but
fp8_gemm_n128 is shared by every model on the FP8 prefill path (35b-a3b,
qwen3-next-80b, gemma-4, minimax, deepseek-v4, ...). The default-on routing
looked the kernel up with .expect(), which would panic at first prefill on all
of those targets. Probe the handles with .ok() instead and fall through to the
validated scalar fp8_gemm_t when the kernel is absent.

Also splits the FP8 dense-GEMM ops into ops/gemm_dense_fp8.rs (sibling of
gemm_dense_int8.rs) — gemm_dense.rs had grown to 521 LoC, over the 500 cap —
and fixes clippy (manual_is_multiple_of) + rustfmt.

Co-Authored-By: Azeez Ishaqui <azeez@avarok.net>
coder3101 pushed a commit to coder3101/atlas that referenced this pull request Jul 12, 2026
* feat(amd): hardware-agnostic kernel build/load abstraction (SCALE + HIP targets)

Generalize the existing dedup+parallelize kernel-compile path so it serves
NVIDIA, Apple/Metal, AMD/SCALE and native AMD/HIP through one clean
ComputeTarget abstraction — no per-vendor special-casing in build.rs.

- build_target.rs: add ScaleTarget (nvcc --cuda-device-only, gfx ELF .o) and
  HipTarget (hipcc --genco .co) ComputeTarget impls + amd/rocm/scale + hip
  factory arms. Replace the conflated output_is_text() with two orthogonal
  axes the codegen actually needs: blobs are emitted uniformly and
  uses_cuda_module_api() selects the runtime-load surface.
- build_codegen.rs: emit every kernel blob as &[u8] via include_bytes!
  (PTX text is valid as bytes; SCALE/HIP/Metal are binary), so one
  representation drives all_ptx_sets() for every CUDA-module backend; Metal
  keeps metallib_modules(). All model-behavior fields preserved.
- registry.rs: per-blob text-vs-binary sniff (ELF / clang-offload-bundle /
  non-UTF8) -> cuModuleLoadData(bytes) for objects, NUL-terminated PTX JIT
  for text. NVIDIA path unchanged (PTX sniffs as text).
- build.rs: HARDWARE.toml force_br32_prefill -> ATLAS_HW_FORCE_BR32.
- TargetPtxSet.modules, AtlasCudaBackend::new, kernel_audit: &str -> &[u8].

The dedup+parallel compile loop itself is untouched — it was already generic
over ComputeTarget; SCALE/HIP/future targets inherit it for free.

Verified: cargo build -p atlas-kernels (133 gb10 kernels, dedup+parallel
intact), cargo check -p spark-server, atlas-kernels lib tests — all green.
NVIDIA non-regression preserved.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* feat(amd): gfx1151/SCALE __SCALE__ kernel guards (e4m3 MMA + codec + LDS) in 6 gb10 sources

Add the SCALE/gfx1151 device-code paths into the gb10 kernel sources that
need them, all behind `#if defined(__SCALE__)` so the NVIDIA path is
untouched. RDNA3.5 (gfx1151) has no `mma.sync.m16n8k32.e4m3` /
`cvt.rn.satfinite.e4m3x2.f32` codegen and only 64KB LDS:

- w4a16_gemm.cu / moe_w4a16_grouped_gemm.cu: atlas_mma_e4m3 (e4m3 m16n8k32 ->
  2x bf16 m16n8k16, bit-exact) and atlas_cvt_e4m3x2_f32 software-E4M3 helpers;
  inline PTX call-sites routed through them (the #else branch is the verbatim
  original PTX, __forceinline__ -> identical NVIDIA codegen).
- inferspark_prefill.cu / inferspark_prefill_fp8kv.cu / prefill_paged_compute.cuh:
  ATLAS_KBUFN/ATLAS_KB single-buffer smem_K for the 64KB LDS budget (expands to
  identity when __SCALE__ undefined) + BR32 path.
- inferspark_prefill_fp8kv.cu / reshape_and_cache_turbo.cu: scl_fp8 / scl_enc_fp8
  software-E4M3 encode/decode (no FP8-cvt PTX on gfx1151).

NVIDIA non-regression PROVEN: emitted PTX is byte-identical to main for all
5 compilable guarded .cu (w4a16_gemm, moe_w4a16_grouped_gemm, inferspark_prefill,
inferspark_prefill_fp8kv, reshape_and_cache_turbo) at -arch=sm_121f -O3
--fmad=false. cargo build -p atlas-kernels green (133 kernels). The __SCALE__
branches are validated on the strix box (Stage E); nvcc never compiles them.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* feat(amd): native ROCm/HIP compile path — libcuda->HIP shim + compat + WMMA helper guards

Add the native AMD/HIP path (hipcc, gfx1151) as the primary supported AMD
target, behind the vendor=hip ComputeTarget from B1.

- crates/atlas-kernels/hip/: libcuda_hip_shim.cpp (cu* driver API -> HIP),
  CUDA->HIP compat headers (cuda_runtime/_api, cuda_bf16, cuda_fp8), shim
  runtime test. Additive; absent on NVIDIA/SCALE paths.
- build.rs: is_hip branch (no-op unless vendor=hip) — stages compat-header
  dir (ATLAS_HIP_COMPAT_INCLUDE), mirrors .cu into OUT_DIR applying the
  64-wide-wavefront warp-mask widen transform (kernels/ never mutated),
  compiles the shim to OUT_DIR/libcuda.so. NVIDIA/SCALE builds unaffected.
- w4a16_gemm.cu / moe_w4a16_grouped_gemm.cu: atlas_cvt_e4m3x2_f32 gains a
  #elif defined(__HIP_PLATFORM_AMD__) software-E4M3 branch; NVIDIA #else PTX
  verbatim (proven PTX-identical).

The ~90 non-tensor-core kernels HIP-compile from the shared gb10 source via
the compat headers + mask-widen, so the strix-hip tree symlinks into gb10 and
inherits gb10 fixes. The two tensor-core GEMM bodies (w4a16_gemm,
moe_w4a16_grouped_gemm) are a full WMMA rewrite that cannot be confined to a
helper-level guard without disturbing NVIDIA codegen; for the HIP path those
two alone use dedicated WMMA sources (wired in the strix-hip tree next),
documented inline. They carry no Avarok-Cybersecurity#110/Avarok-Cybersecurity#155/Avarok-Cybersecurity#85/dense_gemm fix surface.

NVIDIA non-regression PROVEN: 133 kernels green, both nvfp4 files PTX
byte-identical to main. HIP on-silicon validation: Stage E (strix box).

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* feat(amd): SCALE strix kernel tree (symlinks->gb10) + porting docs + build/serve scripts

kernels/strix/ is the AMD/SCALE target tree: HARDWARE.toml (vendor=amd,
arch=gfx1151, force_br32_prefill, disable_fp8_ssm_prefill) + the qwen3.6-27b
nvfp4 KERNEL/MODEL tomls, and every kernel as a relative symlink into
kernels/gb10/. Because the symlinks resolve to CURRENT gb10, the SCALE target
inherits all gb10 fixes automatically (verified: the symlinked w4a16_gemm.cu
carries B2's __SCALE__ guards; reshape_and_cache_turbo.cu -> current main, i.e.
the agentic-wall KV-write-floor fix). e4m3-MMA / 64KB-LDS divergence is handled
by the in-source __SCALE__ guards (B2); bf16 mma.sync kernels compile natively
under SCALE.

Also: docs/porting/* (SCALE bring-up, gfx1151 notes, Spectral repro) and
build-amd.sh / serve-amd.sh.

NVIDIA default (gb10) build unaffected — the strix tree is a separate HW dir,
inert unless ATLAS_TARGET_HW=strix. SCALE compile + serve validate on the
strix box (Stage E).

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* feat(amd): enable qwen3.6-35b-a3b on the SCALE target (guards + strix tree)

Stage D (SCALE half). The flagship 35B-A3B MoE now builds for AMD/SCALE:

- kernels/gb10/qwen3.6-35b-a3b/nvfp4/{w4a16_gemm,moe_w4a16_grouped_gemm}.cu:
  add the same __SCALE__ / __HIP_PLATFORM_AMD__ / #else guards as the 27b
  kernels (atlas_mma_e4m3 e4m3->2x bf16, atlas_cvt_e4m3x2_f32, scl_fp8,
  smem_B bf16 staging). The 35b weight-GEMM sources were byte-identical to
  27b's, so the guards transplant 1:1. 35b GDN kernels use no e4m3 MMA
  (SCALE-native bf16 mma.sync) and vision_encoder has no tensor cores —
  both symlink unguarded.
- kernels/strix/qwen3.6-35b-a3b/: nvfp4 model .cu symlinks into gb10 +
  MODEL/KERNEL tomls (mirrors the 27b strix layout).

NVIDIA non-regression PROVEN: both 35b weight-GEMM kernels PTX byte-identical
to main; cargo build -p atlas-kernels green (133 kernels). SCALE compile/serve
of 35B validates on the strix box (Stage E).

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* feat(amd): native-HIP strix-hip kernel tree (symlinks + WMMA copies, 27b+35b)

kernels/strix-hip/ is the native AMD/HIP (hipcc/gfx1151) target tree, paired
with the B3 libcuda->HIP shim + build.rs HIP compat/mask-widen mirror.

- 86 relative symlinks into kernels/gb10 for every non-tensor-core kernel, so
  the HIP target inherits gb10 fixes (e.g. reshape_and_cache_turbo agentic-wall
  fix, GDN Avarok-Cybersecurity#110/Avarok-Cybersecurity#155, MoE Avarok-Cybersecurity#85) via the build-time mask-widen mirror.
- 15 real WMMA copies for the tensor-core kernels hipcc can't build from the
  shared mma.sync source (w4a16/w8a16/moe grouped GEMMs, dense_gemm_tc,
  inferspark_prefill*, prefill_paged_compute*), lifted from the port's
  silicon-proven strix-hip-real (bit-current: these are unchanged on main
  since the e146bef fork).
- prefill_paged_compute.cuh: PR Avarok-Cybersecurity#90 (coherence) is the one tensor-core kernel
  that changed on main since the fork; forward-ported its two semantic changes
  (softmax sw_exp->__expf default; P×V BF16->FP16 WMMA) into the WMMA copy,
  preserving Avarok-Cybersecurity#90's ATLAS_FAST_SOFTMAX_EXP / ATLAS_DISABLE_FP16_PV bisect gates.
- 35b tensor-core kernels: 35b's gb10 w4a16/moe sources are byte-identical to
  27b's, so the port's 27b WMMA copies apply verbatim. 35b GDN/vision symlink.

NVIDIA build unaffected (strix-hip is a separate inert HW dir). HIP compile +
on-silicon numerics validate on the strix box (Stage E); the FP16 P×V mapping
is the semantically-equivalent RDNA3.5 WMMA form, numerics-pending-GPU.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* fix(amd): drop w8a16/wht_bf16 from strix trees — SCALE-incompatible + unused by nvfp4

The SCALE-27b build surfaced 3 gb10 kernels that evolved on main since the
port fork (e146bef) into forms SCALE 1.7.1's gfx1151 nvcc cannot compile:
w8a16_gemm.cu (73+/94-), w8a16_gemm_t.cu (385+/24-), wht_bf16.cu (155+/0-).

None are dispatched by the qwen3.6-27b/35b NVFP4 (w4a16) path at the supported
KV dtypes: w8a16_* is the FP8-weight GEMM (these models are 4-bit weight), and
wht_bf16 is the TurboQuant Walsh-Hadamard rotation — kernel_requirements.rs only
hard-requires it when the KV dtype is WHT-rotated (turbo*), not for bf16/fp8 KV.

Remove all three from both strix kernel trees (per-model trees should not carry
kernels the model never dispatches) and drop the wht_bf16 [modules] registration.
Strix supports nvfp4 w4a16 + bf16/fp8 KV; TurboQuant KV + w8a16-weight remain
gb10/NVIDIA-only for now (documented limitation).

NVIDIA build unaffected.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* feat(amd): NCCL feature-split — cuda-without-nccl for single-GPU SCALE/HIP builds

The SCALE-27b link failed on -lnccl: strix (single-GPU AMD) has no NCCL, but
the NCCL FFI (#[link(name="nccl")]) was gated on `cuda`. Split per the port's
design (docs/porting/SCALE_RUNTIME_BRINGUP.md):

- spark-comm: new `nccl` feature (implies cuda); nccl.rs/nccl_backend.rs/
  NcclBackend gated on `nccl` not `cuda`. default = [cuda, nccl].
- spark-server: `nccl = [cuda, spark-comm/nccl]`; default = [cuda, nccl].
- topology.rs init_nccl_comm: primary path gated on `nccl`; new
  cuda-without-nccl fail-fast sibling returns Ok(None) for single-device and
  bails on world_size>1 ("multi-rank NCCL not available in this build").

NVIDIA default keeps NCCL + EP (verified: default build links libnccl.so.2).
Single-GPU AMD builds with `--no-default-features --features cuda` and links
NO libnccl (verified via readelf — no NEEDED entry). No scheduler/EP logic
touched.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* fix(amd): cuGraphInstantiate ABI switch for SCALE/HIP (cuGraphInstantiateWithFlags absent)

The SCALE-27b link failed undefined-symbol on cuGraphInstantiateWithFlags:
main's spark-runtime end_capture adopted that NVIDIA-only 3-arg driver entry,
but SCALE 1.7.1's gfx1151 libcuda exports only cuGraphInstantiate (and
cuGraphInstantiateWithParams) — same ABI (CUgraphExec*, CUgraph, u64), no
WithFlags alias.

Port the proven fix: a build.rs `atlas_scale` cfg (set when ATLAS_TARGET_HW
starts with "strix" — covers SCALE `strix` and native-HIP `strix-hip`) selects
the symbol that exists. cuda_backend.rs declares cuGraphInstantiateWithFlags
under cfg(not(atlas_scale)) and cuGraphInstantiate under cfg(atlas_scale);
gpu_impl.rs end_capture call-site switches on the same cfg. NVIDIA codegen
unchanged (atlas_scale unset → WithFlags exactly as before).

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* fix(amd): vendor cudarc 0.19.2 with missing-CUDA-symbol stub fallback (SCALE/HIP runtime)

The SCALE-27b binary built+linked clean and reached CUDA init, then panicked
in cudarc 0.19.2's driver Lib loader: it eagerly dlsym-resolves every CUDA
driver entry and `.unwrap()`s, but SCALE 1.7.1's gfx1151 libcuda doesn't export
some newer symbols (cuArrayGetMemoryRequirements, cuArrayGetPlane, …).

Vendor cudarc 0.19.2 (byte-identical version) with the port's patch: the
loader resolves each symbol with `.unwrap_or_else(|_| __atlas_missing_cuda_sym)`
so SCALE-absent entries become no-op stubs that only fault if actually called
(Atlas doesn't call the array APIs). On NVIDIA every symbol resolves normally
→ the patch is a no-op (verified: NVIDIA release build green, unchanged).

vendor/cudarc reconstructed = stock cudarc-0.19.2 + the port's patched files
overlaid (the port had only tracked the 124 changed files; safe/core.rs etc.
restored from stock). [patch.crates-io] cudarc = vendor/cudarc in the
workspace manifest.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* diag(amd): ATLAS_MEM_PROFILE per-phase GPU-free trace in dense FP8 loader

Env-gated (no-op in production) per-phase gpu.free_memory() logging in the
qwen35_dense loader to pin the strix/APU load-time footprint: distinguishes
FP8-source persistence vs NVFP4 steady-state vs BF16 requant transients as the
consumer of the 60 GB unified pool. Logs at load-start and every 8 layers.

* fix(amd): free FP8 source after NVFP4 requant on strix (fits dense+MoE on the APU)

Measured (ATLAS_MEM_PROFILE): on the strix gfx1151 APU the GPU-visible pool is
a ~60 GB unified GTT (dedicated VRAM 512 MB). The FP8 checkpoint (~27 GB) loads
GPU-resident up front; the per-layer FP8->BF16->NVFP4 requant then adds ~21 GB
of NVFP4 WITHOUT freeing the FP8 source, so 27 + 21 + embed + KV ≈ 54 GB and
CUDA-graph/decode buffers tip it into OOM. The MoE 35B is worse.

Fix: in quantized_from_fp8 (the read-once FFN / MoE-expert requant path — NOT
the dual-read SSM in_proj path), free the FP8 source tensors immediately after
producing the NVFP4 result. Gated to `atlas_scale` (new spark-model build.rs
cfg, set when ATLAS_TARGET_HW starts "strix") so NVIDIA weight-loading is
byte-for-byte unchanged (it keeps the source resident — 119 GB has the room).
WeightStore has no Drop that frees ptrs, so this is the sole owner (no
double-free) and the source is never re-read. Recovers ~27 GB → dense 27B and
MoE 35B both fit the 60 GB pool with headroom for KV + graphs.

NVIDIA non-regression: atlas_scale unset → the evict block is compiled out.

* fix(amd): route GDN prefill to low-smem split4/persistent on gfx1151 (64KB LDS cap)

The dense+MoE FP8-source evict let the model fit, then first inference hit
cuFuncSetAttribute(MAX_DYNAMIC_SHARED=69688) → CUDA_ERROR_INVALID_VALUE: the
GDN persistent-WY4 prefill kernel (smem = kd*vd*4 + 8*kd*4 + 56 = 69688) keeps
the full 128x128 FP32 H-state in dynamic shared memory, and WY32 needs ~84KB —
both exceed RDNA3.5's hard 64KB LDS cap (fine on Blackwell's ~228KB).

Gate the two smem-H persistent kernels (WY32, WY4) behind !cfg!(atlas_scale)
so the gfx1151/SCALE+HIP targets fall through to gdn_prefill_split4 /
gdn_prefill_persistent — which keep H in global memory and use ~1KB static
shared (smem_k[128]/smem_q[128]). Correctness-equivalent, lower throughput;
the smem-H fast paths remain a Blackwell-only optimization. NVIDIA unchanged
(cfg!(atlas_scale)=false → conditions identical). (atlas_scale set by
spark-model build.rs from ATLAS_TARGET_HW.) The Q12 batched path mirrors this
ladder and needs the same gate before strix concurrency>1 — follow-up.

* fix(amd): route ALL gfx1151 GDN prefill to split4 (active recur path was unguarded)

The previous gate only touched prefill_gdn_full_inner, but the active serve
path is prefill_gdn_recurrence (trait_prefill.rs:297) — its FLA(C=64,~96KB) →
WY4(69688) fallback was the actual launcher of the OOM. And persistent(67584)
also exceeds gfx1151's 64KB LDS, so gating only wy32/wy4 wasn't enough.

On atlas_scale, early-return both prefill_gdn_recurrence and
prefill_gdn_full_inner straight to gdn_prefill_split4 — the only GDN prefill
kernel that keeps the kd*vd FP32 H-state in GLOBAL memory (~2KB dynamic smem)
and handles arbitrary token counts. Correctness-equivalent; the smem-H fast
paths (FLA/WY32/WY4/persistent) and a future C=32 FLA variant remain the
Blackwell speed path. NVIDIA unchanged (cfg!(atlas_scale)=false → early return
skipped, full ladder intact).

* fix(amd): software-E4M3 in decode GEMVs — fixes gfx1151 garbage decode (coherence)

Dense SCALE-27B served on gfx1151 but produced garbage from the 2nd token
("The\n\n,:,.\n\n..."): prefill (B2-guarded) was correct but every decode token
dequantized weights wrong. Root cause: the decode GEMV kernels (w4a16_gemv,
w4a16_gemv_fused, dense_gemv_fp8w, moe_expert_gemv) decode the FP8 E4M3 block
scale via `(float)fp8` — and SCALE's __NV_E4M3 cast is NON-STANDARD on gfx1151
(the exact reason B2 added software scl_fp8 for the prefill GEMM). B2 missed
the decode path.

Apply B2's software E4M3: scl_fp8 decode at all 16 FP8-scale-decode sites
across the 4 kernels, plus scl_enc_fp8 at the dense_gemv_fp8w BF16->FP8 encode
(quantize_bf16_to_fp8 for the runtime FP8 lm/MTP head) so encode/decode agree.
All guarded #if defined(__SCALE__) || defined(__HIP_PLATFORM_AMD__) with the
NVIDIA path verbatim in #else.

NVIDIA non-regression PROVEN: PTX byte-identical for all 4 kernels
(-arch=sm_121f -O3 --fmad=false); cargo build -p atlas-kernels green.

* fix(amd): symlink widen_block_scale_f32 into strix trees for 35B SSM FP8-native load

The strix/common and strix-hip/common symlink sets were curated for the dense
27B, which never touches the SSM FP8-native block-scaled load path. The 35B-A3B
hybrid does: at Layer 0 the qwen35 loader widens each SSM projection's FP8
block-scale (weight_scale_inv) to FP32 via the widen_block_scale_f32 kernel
(loaders_fp8.rs:79). That kernel source exists under kernels/gb10/common but was
not symlinked into either strix tree, so the SCALE/HIP build never compiled it
and model construction failed:

    Kernel lookup widen_block_scale_f32: Module 'widen_block_scale_f32' not loaded

The kernel needs no gfx1151 guard: it only widens a BF16 scale to FP32 (or copies
FP32) — the block scale is stored BF16/FP32, not e4m3, so there is no non-standard
SCALE FP8 cast involved. NVIDIA is unaffected (gb10 does not read the strix trees).

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* feat(amd): port FP8-native prefill kernels to SCALE/gfx1151 (35B attn/SSM)

Option B: keep the 35B's native-FP8 attention + FP8-block-scaled SSM (gb10
precision parity) by making the 5 FP8 prefill kernels compile + run under SCALE,
rather than forcing the model down the NVFP4 requant path. Build is GREEN and the
35B serves at ~37 tok/s (vs the dense 27B's 9 tok/s — fast FP8 prefill path).

Per-kernel, scoped to the actual SCALE failure:
- w8a16_gemm / w8a16_gemm_t / w8a16_gemm_pipelined: NOT a codegen issue. They
  already dequant FP8 via the host-built E4M3_LUT (constant table, no device
  FP8 cast) + a standard m16n8k16.bf16 MMA — fully SCALE-safe. They only failed
  with "'e4m3_lut.cuh' file not found": the relative include wasn't symlinked
  into strix/common. Fix = symlink the header (+ the 5 .cu) into both strix
  trees. No code change. (The earlier "SCALE-incompatible" drop was conservative;
  also confirms SCALE handles cp.async — these carry 26/36 cp.async ops.)
- per_token_group_quant_fp8: __nv_cvt_float_to_fp8(__NV_E4M3) is non-standard on
  gfx1151 -> scl_enc_fp8 software SATFINITE encode under
  #if __SCALE__||__HIP_PLATFORM_AMD__, NVIDIA verbatim in #else. Pairs with the
  scl_fp8 decode so encode/decode agree.
- fp8_gemm_t_blockscaled: mma.sync.m16n8k32.e4m3 has no SCALE codegen. Replaced
  with the proven atlas_mma_e4m3 helper (SSOT w4a16_gemm.cu): under #if __SCALE__
  two m16n8k16.bf16 MMAs over the K-split with scl_fp8-decoded operands; #else is
  the verbatim e4m3 PTX, so NVIDIA codegen is byte-identical (__forceinline__).

NVIDIA byte-unchanged (all guards additive, #else verbatim). gb10 ignores the
strix trees entirely.

KNOWN: 35B output is not yet coherent (sensible tokens then repetition). The 35B
is the FIRST SSM/GDN model to serve on strix — the GDN split4 prefill + SSM-state
SCALE path was never exercised by the dense 27B. Coherence bisection (GDN/SSM
SCALE path vs the FP8 GEMMs) is the next step; this commit locks in the compile +
serve milestone.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* fix(amd): guard FP8-E4M3 scale decode in MoE kernels for SCALE/gfx1151

The dense 27B exercised none of the MoE kernels, so the SCALE-non-standard
`(float)(__nv_fp8_e4m3)` per-block-scale decode (root-caused + fixed for the
decode GEMVs in bdb07ad) was left raw across ~15 MoE kernels. The cosine
bisection of the 35B-A3B vs an NVIDIA reference (GDN + attention sub-stages all
cos=1.0, moe_out the sole divergence) localized the incoherence to the MoE.

Apply the proven software-E4M3 decode (atlas_dec_e4m3, scl_fp8 math under
`#if __SCALE__ || __HIP_PLATFORM_AMD__`, verbatim cast in `#else` → NVIDIA
byte-identical) to every unguarded FP8-scale site in the routed/sorted/shared
MoE kernels (moe_sorted_prefill, moe_shared_expert_fused[_t/_batch2/_batch3/
_fp8_*], moe_prefill, moe_expert_gemv_fused, moe_gate_topk,
moe_expert_relu2_down_shared) plus the standalone dequant site in the 35B
moe_w4a16_grouped_gemm (atlas_mma_e4m3 macro region untouched).

Necessary SCALE hygiene (these casts are wrong on gfx1151), NVIDIA unaffected.
NOTE: not sufficient on its own — the 35B still degenerates; a residual MoE
numerical divergence remains (FP8 vs NVFP4 MoE paths both incoherent, FP8 less
so), under investigation via expert-ID / MoE-GEMM-output cosine.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* fix(amd): translate nvcc --fmad to clang -ffp-contract for the HIP target

HipTarget forwarded KERNEL.toml `extra_nvcc_flags` verbatim to hipcc/clang,
which rejects `--fmad=false` ("unknown argument"). Map it to `-ffp-contract=off`
(and `--fmad=true` → `-ffp-contract=fast`), preserving the no-FMA-contraction
determinism semantics on HIP. NVIDIA/SCALE targets unchanged (translation is
inside HipTarget::compile only).

Unblocks the first stage of the native-HIP 35B build; the remaining HIP blocker
is inline-PTX kernels (cp.async `l` / mma.sync `=f`/e4m3) that need HIP-WMMA
rewrites — tracked separately.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* feat(amd): add cuda_fp16.h HIP compat shim (35B HIP build)

The HIP compat dir had cuda_bf16.h/cuda_fp8.h but not cuda_fp16.h; several
prefill/attention kernels include it. Add a thin shim (include hip/hip_fp16.h,
which provides CUDA-compatible __half/__half2/conversions; alias bare `half`),
mirroring cuda_bf16.h. Clears the "'cuda_fp16.h' file not found" HIP errors.

Remaining HIP-35B blockers (inline NVIDIA PTX hipcc can't compile, tracked):
cp.async (`l` constraint) + mma.sync (`=f`/e4m3) in prefill_paged_compute[_512],
fp8_gemm_t_blockscaled, w8a16_gemm[_pipelined/_t], reshape_and_cache_turbo
(`=h`); + gated_delta_rule_wy17 missing-include. Each needs cp.async→sync +
mma.sync→__builtin_amdgcn_wmma rewrites.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* fix(amd): drop unused gated_delta_rule_wy17 from strix trees

wy17 is a WY-17 GDN *prefill* variant, but on atlas_scale all GDN prefill is
forced to gdn_prefill_split4 (the only path that fits the 64KB LDS cap;
trait_prefill_gdn.rs:61 early-returns split4). So wy17 is never dispatched on
the AMD path, and gdn_wy17_k is loaded via try_kernel (optional → NULL handle,
no init failure). It compiled on SCALE (in-place include) but is dead code; on
HIP its relative `../../common/gdn_reduce.cuh` include breaks the flattened
build mirror. Drop the symlink from both strix trees — removes the HIP build
error with zero runtime impact (split4 is forced regardless).

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* feat(amd): HIP 35B — bf16-attn route + prefill __half + reshape guard

Toward native-HIP qwen3.6-35b-a3b. Drop the FP8-native attention kernels
(w8a16_gemm[_t/_pipelined], fp8_gemm_t_blockscaled) from kernels/strix-hip —
hipcc can't compile their inline PTX (mma.sync/cp.async) and they lack WMMA
copies. On HIP the 35B routes attention through BF16 via the existing
ATLAS_FP8_DEQUANT_ATTN_TO_BF16 lever → dense_gemm_bf16 (which has a WMMA copy),
so these kernels aren't needed.

Kernel fixes (subagent-assisted, verified):
- prefill_paged_compute.cuh: __half→__fp16 PV-WMMA casts routed through
  __half2float (lines 329/599) — value-identical, hipcc-clean.
- reshape_and_cache_turbo.cu: the cvt.rn.satfinite.e4m3x2.f32 `=h` PTX guarded
  → scl_enc_fp8 on SCALE/HIP (bit-exact SATFINITE E4M3), NVIDIA asm verbatim
  in #else.

NEXT HIP blocker: cp.async in the LOAD_KV_TILE macro (inferspark_prefill_paged*
.cu) — replace with synchronous uint4 copy (its #else-zero branch already is
one) via a guarded atlas_cp16 helper; prune unused prefill variants from
strix-hip. NVIDIA unaffected (strix-hip is HIP-only; reshape #else verbatim).

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* feat(amd): HIP 35B bring-up — shim cuModuleGetGlobal, atlas_hip cfg, SSM NVFP4 prefill

Native-HIP qwen3.6-35b-a3b now builds, links, loads all 89 kernel modules,
serves the API, and loads weights. Layered HIP fixes:
- libcuda→HIP shim: add cuModuleGetGlobal_v2 → hipModuleGetGlobal (registry
  device_symbol needs it; was the spark-server link's undefined symbol).
- new `atlas_hip` cfg in spark-model/build.rs (strict subset of atlas_scale for
  ATLAS_TARGET_HW==strix-hip): native HIP lacks the FP8 *prefill* GEMM kernels
  (fp8_gemm*/w8a16* are inline-PTX, not yet WMMA-ported).
- on atlas_hip, skip SSM predequant_for_prefill + native-FP8 prefill install
  (linear_attn_arms) so SSM qkvz/out_proj prefill routes to NVFP4 w4a16 WMMA.

KNOWN (next): first inference still fails — ssm out_proj w4a16_gemm_t launch
returns hipErrorInvalidHandle; investigating the .co function-symbol resolution.
The FP8 attention WMMA kernels (task Avarok-Cybersecurity#296, non-negotiable) follow once serving.
NVIDIA/SCALE unaffected (atlas_hip is HIP-only; strix-hip tree is HIP-only).

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* feat(amd): HIP dense_gemm_bf16_pipelined port + cuEventElapsedTime shim

The SSM out_proj BF16-dense branch launches dense_gemm_bf16_pipelined, but
the strix-hip port of dense_gemm_bf16.cu shipped only the scalar
dense_gemm_bf16 + fused_silu_mul — the pipelined kernel (cp.async +
mma.sync, neither lowerable by clang for gfx1151) was absent. try_kernel
returned a null handle, so the launch hit hipErrorInvalidHandle ("Function
passed is null") at prefill layer 0.

Add a same-contract HIP kernel: identical name/signature and the SAME
launch geometry the op wrapper uses (Grid ceil(N/128)xceil(M/128), Block
256), so the dispatch is unchanged. 256 threads cover a 128x128 tile as a
16x16 thread grid (8x8 micro-tile/thread) with synchronous smem staging +
register-blocked FP32-accumulate FMA. Math is bit-equivalent to the gb10
tensor-core kernel — dense_gemm_microtest reports cosine=1.000000 at
M=16/128/1024, N=2048, K=4096 on gfx1151. RDNA3.5 WMMA acceleration is a
perf follow-up; correctness is independent of it.

Also add cuEventElapsedTime to the libcuda->HIP shim (the one event symbol
missing vs cuEventCreate/Record/Synchronize/Destroy) so the GPU-timing
microtests link on HIP.

NVIDIA gb10 + SCALE paths byte-unchanged (SCALE uses the gb10 symlink with
the real cp.async/mma.sync kernel; this file is strix-hip-only).

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* fix(amd): drop unused gated_delta_rule_wy17 from 27B strix-hip tree

Same fix as ff13624 (35B): gated_delta_rule_wy17.cu #includes
../../common/gdn_reduce.cuh, which is absent from the strix-hip include
layout, so hipcc --genco fails the whole atlas-kernels build for the 27B
target. The kernel is loaded via try_kernel (null handle when absent,
gated at the dispatch site by .0 != 0) and init.rs already documents it as
"only present in qwen3.6-35b-a3b's PTX module set; NULL on other targets",
so the 27B path never launches it. Dropping the file is a no-op for
behavior and unblocks the 27B HIP build.

NVIDIA gb10 + SCALE byte-unchanged (strix-hip-only file).

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* test(amd): fp8_gemm_t numerical oracle + dense_gemm_tc microtest arm

Add fp8gemm_microtest: a standalone CPU-reference correctness oracle for
fp8_gemm_t (the unscaled E4M3-weight x BF16-activation prefill GEMM the
Qwen3.6 SSM qkvz/out_proj path launches via fp8_gemm_n128). Generates random
BF16 activations + nearest-E4M3 weight bytes, launches the real kernel via
the production GpuBackend/registry, and gates on cosine vs an FP32-accum CPU
recompute.

Result on gfx1151 (HIP / AMD WMMA): cosine=1.000000 at (M,N,K) =
(64,128,256)/(64,2048,4096)/(256,2048,4096) — the FP8 WMMA fragment layout
+ software e4m3 decode are bit-exact. This (with the dense_gemm_bf16_pipelined
cosine=1.0 result) rules the GEMM kernels OUT as the source of the 35B-HIP
incoherence; the remaining suspects are the SSM/attention non-GEMM ports.

Also add a dense_gemm_tc launch arm + per-kernel module resolver to
dense_gemm_microtest for validating the shared BF16 tensor-core path.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* fix(amd): attention prefill grid stride must match clamped BR64=32 on gfx1151

inferspark_prefill_64 clamps BR64 64->32 on the AMD targets (gfx1151 64 KB
LDS cap: the `#if __SCALE__ || __HIP_PLATFORM_AMD__ #define BR64 32` in
inferspark_prefill.cu, with the author's @human-review note). But the host op
wrapper prefill_attention_64 hardcoded `br = 64` for the grid stride
(grid.y = ceil(seq/64), q_start = q_block * BR64). With the kernel's BR64=32,
CTAs are spaced 64 query rows apart while each writes only 32 -> query rows
32..63 of every 64-row band are left UNWRITTEN. For any prefill >32 tokens
this silently zeros half the attention output (gross corruption); <=32-token
prompts (1 CTA) dodged it, which is why the short coherence smokes didn't
surface it.

Fix: `br = if cfg!(atlas_scale) { 32 } else { 64 }` so the grid stride matches
the kernel's actual BR64. cfg!(atlas_scale) covers both `strix` (SCALE) and
`strix-hip` (HIP); NVIDIA keeps 64 (byte-identical).

New inferspark_attn_microtest oracle (CPU causal-GQA-softmax reference)
confirms: kernel numerics are bit-exact (cos=0.999999 at seq=16/32), the bug
reproduces at seq=48/100/256 (cos~0.95, 32/72/256 zero rows) with stride 64,
and the fixed stride 32 restores cos=0.999998 with zero dropped rows.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* fix(amd): paged attention prefill grid stride match clamped BR64=32 (gfx1151)

Same class as 571830f (non-paged) applied to all 7 paged-64 prefill wrappers
in prefill_attn_main_b.rs (bf16 / fp8 / nvfp4 / turbo KV variants). The paged
kernels clamp BR64 64->32 on AMD (prefill_paged_compute.cuh) while the host
hardcoded br=64 -> query rows 32..63 of each 64-row band dropped for any
cached prefill chunk >32 tokens. Gated br = cfg!(atlas_scale) ? 32 : 64;
NVIDIA byte-unchanged. The TurboQuant-KV fp8k/turbok paged wrappers
(prefill_attn_fp8k.rs / prefill_attn_turbok.rs) are the same class but off the
Qwen3.6 FP8/bf16-KV path — tracked as a follow-up pending per-variant oracle
verification.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* diag(amd): wire gate-logit + last-token expert-ID dumps into forward_batched

No-op unless ATLAS_DUMP_EXPERT_IDS=1 (dump::enabled gate). Lets the batched
MoE path's routing be compared to gb10 the same way the grouped paths already
are. Used to confirm HIP L0 routing is stable (rank-8/9 gate gap 0.2 >> the
~1e-3 gate-GEMM hipcc/nvcc divergence) — so the moe_out cosine drift is
expert-GEMV/combine values, not a routing flip.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* test(amd): MoE shared-expert gate_up + silu_down numerical oracles

Two standalone CPU-reference oracles for the MoE expert-compute kernels on
the batched FP8 path (top_k=0 → shared-expert-only launch):
 - moe_shared_microtest: moe_expert_gate_up_shared_fp8 (E4M3-LUT weight decode
   + per-128 block scale, gate & up projections)
 - moe_silu_down_microtest: moe_expert_silu_down_shared_fp8 (silu(gate)*up then
   down projection)

Result on gfx1151 (HIP): both cos=1.000000 across N/K shapes. With the earlier
fp8_gemm_t/dense oracles + the gate GEMM (dense_gemm_bf16, cos=1.0) this proves
the ENTIRE MoE expert-compute path is bit-exact on HIP (routed experts use the
same kernels). The model-level moe_out 0.984 divergence therefore sits in the
combine (moe_weighted_sum_blend) or routing-weight integration, not the GEMVs —
narrowed for the next pass (fresh post-attention-fix re-dump + blend oracle).

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* test(amd): moe_topk_softmax + moe_weighted_sum_blend oracles — MoE fully exonerated

moe_topk_microtest: idx_match=true, max weight err 0.00000 across seeds (top-k
selection + softmax weights bit-exact). moe_blend_microtest: cos=1.0 gated +
null-gate. With the gate_up/silu_down/gate-GEMM oracles, ALL FIVE active MoE
kernels are now proven bit-exact on HIP. The model-level moe_out 0.984-vs-gb10
is therefore not a HIP MoE kernel bug — it's a reference/dispatch artifact or
sub-1e-3 upstream input drift, not the coherence driver.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* fix(amd): route MoE to batched (not grouped) for all token counts on HIP

Long prefills (>64 tokens) dispatched to forward_prefill_fp8 (grouped GEMM),
but native HIP has no ported grouped-MoE path: moe_fp8_grouped_gemm is a
compile stub and the grouped pipeline launches kernels that are null on the
HIP module set -> cuLaunchKernel hipErrorInvalidHandle at prefill layer 0 for
any prompt >64 tokens (found via AMD_LOG_LEVEL=3: "Function passed is null"
right after residual_add_rms_norm, the MoE entry). forward_batched is the
correct, complete per-token path whose kernels are all bit-exact-verified on
HIP, so gate the grouped dispatch off when cfg!(atlas_hip): route ALL token
counts to forward_batched. SCALE keeps grouped (its symlinked grouped GEMM is
real via PTX-recompile); NVIDIA byte-unchanged. Verified: the 111-token prompt
that previously crashed now runs end-to-end. Grouped-GEMM HIP WMMA port remains
a perf follow-up.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* test(amd): gated_delta_rule_prefill_split4 oracle — GDN bit-exact on HIP

Reuses the gdn_fla_e2e_gateb recurrent SSOT (split4 takes the same
gate=exp(g)/beta inputs). split4 is the atlas_scale-forced GDN prefill never
cross-validated (gb10 uses wy32). Result on gfx1151 (HIP): cos=0.999999,
max-abs 0.004-0.013 vs the per-token recurrent reference at t=64/100/128/200.
So split4 is correct — its ~0.5% magnitude difference vs gb10's wy32 in the
model dump is benign chunking/accumulation-order, NOT a bug. (Fix vs the
earlier segfault: split4 needs .shared_mem(4*k_dim*4) dynamic LDS + the
gb_stride arg.) This clears the last untested GDN/MoE/GEMM kernel: the HIP 35B
incoherence is not a kernel correctness bug.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* test(amd): rope_forward_mrope_interleaved oracle — rope bit-exact on HIP

CPU reference (text-only MRoPE, rotate-half, theta=1e7, rotary_dim=64). Result
on gfx1151: q_cos=k_cos=1.000000 at seq=40/100/200. So rope is correct — the
in-model q_post_rope 0.987 (vs gb10) is downstream residual-stream drift, not a
rope bug. This is the 10th and final hot-path kernel cleared: dense_gemm_bf16_
pipelined, fp8_gemm_t, inferspark_prefill_64, moe gate_up/silu_down/blend/topk,
gdn split4, and rope are ALL bit-exact on HIP. The 35B-on-HIP incoherence is
therefore NOT a compute-kernel correctness bug.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* refactor(model): remove defunct FP32-residual feature

The FP32 residual stream (use_fp32_residual) was never plumbed into the
qwen3_ssm forward path and is no longer used by any model — the residual
stream is unconditionally BF16. Remove the feature end to end:

- ModelConfig.use_fp32_residual field + the qwen3_next_80b_nvfp4 literal
- step3p7 parser setter that enabled it for the 81-layer family
- HARDWARE.toml keys (strix, strix-hip)
- per-seq / per-token residual byte-stride branches collapsed to BF16 in
  embed_chunk and forward_layers
- the inert embed->FP32 conversion mechanism: bf16_to_f32_kernel field
  (hardcoded KernelHandle(0)) + the dead embed() branch + the orphaned
  ops::bf16_to_f32 launcher (no kernel source backed it)

The separate use_fp32_logits (Gemma-4 FP32 lm_head bisection scaffold) is
untouched; comments that referenced 'FP32 residual' as its now-removed
precondition are corrected.

NVIDIA gb10 / SCALE byte-behaviour unchanged (residual was already BF16
on every shipping config).

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* feat(moe): FP32 gate path (ATLAS_FP32_GATE) for routing stability

On gfx1151 the cross-compiler FP codegen (hipcc vs nvcc FMA contraction +
reduction order) accumulates ~bf16-ULP differences in the residual stream.
The MoE router stores its gate logits as BF16 before top-K, so two experts
whose logits differ by less than a BF16 ULP can flip selection — a
discontinuous routing change that cascades into degenerate output.

Add an env-gated (ATLAS_FP32_GATE=1) path that keeps the router GEMM
accumulator in FP32 through top-K, eliminating the gate-store rounding as a
flip source:

- kernels: dense_gemm_bf16_f32out (gb10 + HIP-real) writes the FP32
  accumulator directly; moe_topk_softmax_f32 reads f32 gate logits. Both are
  additive new symbols; the existing BF16 kernels are byte-identical so the
  NVIDIA codegen is unchanged.
- buffers: dedicated gate_logits_f32 [m, num_experts] (allocated when
  num_experts > 0).
- forward_batched: gated dispatch — only the softmax-routed dense-gate path
  (NVFP4 gate and sigmoid+bias path stay BF16). Falls back to BF16 if the f32
  kernels are absent (try_kernel → KernelHandle(0)).

Default off → NVIDIA/SCALE behaviour unchanged (verified: full workspace
cargo check green, new kernels compile under nvcc). Scoped to the batched
path, which is what HIP forces (hip_force_batched). A/B on strix will show
whether gate-store rounding is the dominant flip source.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* feat(moe): FP32 routing path (ATLAS_FP32_ROUTING) — out_proj-norm in FP32 into the gate

The gate-only ATLAS_FP32_GATE was insufficient (measured: still degenerate on
gfx1151) because the gate INPUT is already bf16-diverged upstream. This extends
FP32 to the MoE-input norm so the gate consumes a full-precision router_in:

- kernel residual_add_rms_norm_gatef32: dual-output norm — bf16 normed (expert
  input) + residual UNCHANGED, plus an FP32 normed output for the gate. Removes
  the norm's bf16-store rounding from the routing-critical path.
- kernel dense_gemm_f32in_f32out: FP32 activation × bf16 gate weight → FP32
  logits (gb10 + strix-hip).
- buffer moe_router_in_f32 [m, hidden]; MoeLayer::fp32_routing_active() gate.
- wiring: single-token SSM decode (trait_decode.rs) emits the FP32 router_in;
  forward_batched reads it via dense_gemm_f32in when ATLAS_FP32_ROUTING=1.

Decode SSM substages already run in f32 (serve-log 'SSM decode dtype: f32'), so
in decode out_proj-store + norm-store are the bf16 hops feeding the gate; this
captures the norm hop. Default off → NVIDIA/SCALE unchanged (full workspace
cargo check green, new kernels compile under nvcc). Scoped to the decode path
first; prefill + the out_proj-store hop are follow-ups if needed.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* feat(moe): extend FP32 routing to attention-layer MoE gates (decode)

The hybrid 35B has an MoE FFN on EVERY layer (attention and SSM). Round-9
wired FP32 routing only into the SSM decode norm, leaving the attention
layers' MoE gates on bf16 routing — half the decode layers still flipped.
Wire residual_add_rms_norm_gatef32 into the attention decode path too
(decode_inner.rs) so all decode-layer gates consume the FP32 router_in under
ATLAS_FP32_ROUTING. Adds the gatef32 handle to the attention layer (try_kernel,
KernelHandle(0) if absent → bf16). NVIDIA byte-unchanged (default off).

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* feat(loader): ATLAS_FORCE_NVFP4_ALL — route FP8 35B fully through NVFP4 (lever-b)

Forces an FP8 hybrid checkpoint (Qwen3.6-35B-A3B-FP8) fully onto the NVFP4
path on HIP — the path the dense 27B is already coherent on (real RDNA3.5
4-bit WMMA via w4a16/moe_w4a16), sidestepping the FP8 bf16-emulation
divergence that 3 FP32-routing experiments proved is the root of the
incoherence.

The FP8→NVFP4 requant infra already exists (variant is Fp8Dequanted for an
FP8 ckpt): attention's build_full_attention_nvfp4 Fp8Dequanted branch does
dense_auto (FP8→BF16) → quantize_to_nvfp4; MoE has the ATLAS_FORCE_NVFP4_MOE
requant path. So the flag just re-routes dispatch:
- force_nvfp4_all implies force_nvfp4_moe.
- attention: gate the native-FP8 (+bf16-dequant) branches off so FullAttention
  falls through to the NVFP4 builder (already passed variant=Fp8Dequanted).
- SSM: route Fp8Dequanted LinearAttention to build_linear_attention_nvfp4.

Default off → FP8/NVFP4 paths byte-unchanged (cargo check green). Known
follow-up: FP8-source eviction (round-7 measured MoE-only force OOMs with FP8
source co-resident) — serve at lower util first to gauge headroom, add the
evict if it OOMs.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* ci: fmt + clippy + test + typos green for PR Avarok-Cybersecurity#161

Resolve the CI failures on feat/amd-strix-halo so the pipeline passes:
- cargo fmt --all: reformat (rustfmt) across build.rs, the kernel-oracle
  examples, and the FP32-routing/NVFP4-all edits — whitespace only, no .cu
  kernels touched (NVIDIA codegen unchanged).
- cargo clippy --workspace --tests: collapse the nested if in qwen35_dense.rs
  log_free (collapsible_if, deny-level under clippy 1.93 let-chains).
- cargo test --workspace: buffers alloc_count 19→21 (the 2 FP32-routing
  buffers gate_logits_f32 + moe_router_in_f32 added this branch).
- typos: allowlist HSA/hsa (ROCm Heterogeneous System Architecture runtime —
  libhsa-runtime64/hsa_queue_create in the SCALE/HIP porting docs; not a typo
  of HAS/has).

Verified locally on the dgx1 GB10 host: cargo fmt --check clean, clippy clean,
cargo test --workspace --locked green, typos exit 0, cargo doc (full + stub
ATLAS_SKIP_BUILD path) green, mdbook build book green.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* fix(ci): track vendored cudarc core.rs (over-broad core.* gitignore swallowed them)

The .gitignore core-dump pattern `core.*` greedily matched two vendored
cudarc SOURCE files — vendor/cudarc/src/driver/safe/core.rs (84KB) and
vendor/cudarc/src/cudnn/safe/core.rs — so they were never committed. They
exist on every local checkout (vendoring wrote them to disk), which is why
local builds pass; but CI's fresh clone lacks them, so cudarc fails to
compile (E0583: file not found for module `core`) and clippy/test/rustdoc
all fail.

Narrow the ignore to `core.[0-9]*` (PID-suffixed core dumps only) and track
both files. cudarc now compiles on a clean checkout.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* fix(ci): stub ptx_modules() must return &[u8] blobs, not &str

The B1 kernel-blob unification changed ptx_modules() to return
Vec<(&'static str, &'static [u8])> (raw blobs) and AtlasRegistry::get_or_init
takes &[(&str, &[u8])]. But the ATLAS_SKIP_BUILD stub in atlas-kernels/build.rs
still emitted the old &str signature, so on a no-nvcc runner (CI clippy/test)
atlas-spark-bench failed to compile (E0308: expected &[(&str,&[u8])], found
&Vec<(&str,&str)>). Builds with nvcc were unaffected (real generated
ptx_modules already returns &[u8]). Align the stub to &[u8].

Verified in the exact CI stub env (ATLAS_SKIP_BUILD=1, CUDARC_CUDA_VERSION,
no nvcc): cargo clippy --workspace --tests green.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* fix(ci): drop unused vendored cudarc examples (CodeQL high-sev false-positive)

CodeQL flagged rust/access-invalid-pointer (high severity) in
vendor/cudarc/examples/08-cupti.rs:63 as a 'new' alert on this PR — but it's
third-party upstream EXAMPLE code, attributed as new only because the AMD
branch diff is large ('Alerts not introduced by this PR might have been
detected because the code changes were too large'). cudarc is a [patch] path
dep: its examples are never built by cargo doc/clippy/test --workspace, so
they're dead weight in the vendored copy.

Remove the 14 example .rs files + their [[example]] manifest blocks (the lib
and all deps are untouched; cargo metadata resolves). This eliminates the
CodeQL surface for vendored example code with zero build impact.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

* chore(cudarc): trim vendored crate to driver+nvrtc (−199.8k LoC)

Atlas only ever references cudarc::driver and cudarc::nvrtc, yet the
vendored cudarc 0.19.2 carried the full set of backend modules — cublas,
cublaslt, cudnn, cufft, cufile, cupti, curand, cusolver, cusolvermg,
cusparse, cutensor, nccl, nvtx and runtime. Their auto-generated
sys/mod.rs FFI bindings alone accounted for ~190k of this branch's diff,
none of it ever compiled (the workspace dep used cudarc's default
features, which pulled cublas/cublaslt/curand/runtime in for nothing).

- Pin the workspace dep to default-features=false with the minimal set
  std/driver/nvrtc/fallback-dynamic-loading/cuda-version-from-build-system.
  fallback-dynamic-loading is load-bearing: cudarc's build.rs derives the
  dynamic-loading cfg from it.
- Delete the 14 unused backend module trees from vendor/cudarc/src.
- Prune the dead module declarations, doc table and feature definitions
  from lib.rs / Cargo.toml, and the now-dead per-backend link directives
  from build.rs.

The driver symbol-resolution patch (__atlas_missing_cuda_sym stub for
SCALE/gfx1151) is untouched, so the native-HIP and SCALE paths are
unaffected. NVIDIA gb10 build is byte-equivalent.

Verified in the CI no-nvcc stub env (ATLAS_SKIP_BUILD=1,
CUDARC_CUDA_VERSION=13000): fmt, clippy --workspace --tests,
build/test --workspace --locked (803 passed), and
cargo doc --workspace --no-deps -D warnings all green.

Co-Authored-By: Azeez Ishaqui <debaterishaqui@gmail.com>

---------

Co-authored-by: Azeez Ishaqui <debaterishaqui@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants