Skip to content

Updating tensorwave fork of axolotl#2

Open
nikhil-tensorwave wants to merge 1405 commits into
tensorwavecloud:mainfrom
axolotl-ai-cloud:main
Open

Updating tensorwave fork of axolotl#2
nikhil-tensorwave wants to merge 1405 commits into
tensorwavecloud:mainfrom
axolotl-ai-cloud:main

Conversation

@nikhil-tensorwave

Copy link
Copy Markdown

Description

Motivation and Context

How has this been tested?

Screenshots (if appropriate)

Types of changes

Social Handles (Optional)

bekk02 and others added 30 commits March 5, 2026 09:59
* Fix fsdp2 sharding. Fix validation of ao version for lr groups

* remove validation since axolotl requires ao>0.13.0 already

* Move fully_shard of entire module for lora_embedding_A/B out of loop

* chore: lint

---------

Co-authored-by: bekk02 <ID+bekk02@users.noreply.github.com>
Co-authored-by: Wing Lian <wing@axolotl.ai>
…roup in posthog (#3455)

* include number of params and rounded est of params so we can easily group in posthog

* fix typing
…parallelism (#3444)

* fix: correct total_num_steps and batch_size calculation with context parallelism

* feat: add test for CP batch size

---------

Co-authored-by: NanoCode012 <nano@axolotl.ai>
* mxfp4 axo

* import lint

* test for qat mxfp4

* config for mxfp4

* add qat:

* pass base config

* MXFakeQuantizeConfig

* lint

* tune config so it fits in 32GB VRAM

---------

Co-authored-by: Wing Lian <wing@axolotl.ai>
* feat: add sonicmoe

* feat: add torch compile for routing

* feat: add routing smoke test

* feat: add qwen3_5_moe, qwen3_vl_moe, qwen3_omni_moe

* fix: disable mlp kernel for sonicmoe too

* feat: update to sonicmoe release

* chore: update import following new sonicmoe changes

* feat: update handling for blackwell

* feat: add sonicmoe e2e test

* fix: installation for updated sonicmoe

* fix: git commit

* fix: ignore py req and fix metadata

* fix: increase min hidden size to match sonicmoe kernel min

* fix: attempt properly interleave and handle unpatch mid-test

* chore: refactor teardown better

* chore: refactor to re-use rearrange

* fix: add idempotency guard

* fix: address comments on CI memory and interleave

* fix: tests grad, param doublewrapped
…kip ci]

* extend pytest-sdist timeout to 30 min for slow/flaky tests

* Also preload the cdn cache so it doesn't get stampeded

* fix yaml syntax

* missing fields

* can't pipe to dev/null

* Fix nightlies and add 2.10.0 to multi-gpu suite
Co-authored-by: SalmanMohammadi <25081738+SalmanMohammadi@users.noreply.github.com>
* upgrade transformers==5.3.0 trl==0.29.0 kernels

* use latest deepspeed fixes

* use corect image for cleanup

* fix test outputs for tokenizer fixes upstream

* fix import:

* keep trl at 0.28.0

* handle updated API

* use latest trl since 0.28.0 doesn't work with latest transformers

* use trl experimental for pad to length

* monkeypatch trl with ORPOTrainer so liger doesn't croak

* upgrade accelerate

* more fixes

* move patch for orpotrainer

* load the imports later

* remove use_logits_to_keep

* fix loss_type arg as a list

* fetch hf cache from s3

* just manually download the missing model for now

* lint for pre-commit update

* a few more missing models on disk

* fix: loss_type internally now list

* fix: remove deprecated code and raise deprecate

* fix: remove unneeded blocklist

* fix: remove reliance on transformers api to find package available

* chore: refactor shim for less sideeffect

* fix: silent trl experimental warning

---------

Co-authored-by: NanoCode012 <nano@axolotl.ai>
…experts (#3461)

* fix: add validation for lora target linear with quantize experts

* chore: fix lint

* chore: comment

* fix: missing link on readme
* add: qwen 3.5

* test for qwen , patch

* lint

* qwen3 fix on main

* Apply suggestions from code review

Co-authored-by: NanoCode012 <kevinvong@rocketmail.com>

* moe config

* config moe

* configs and chore

* Update examples/qwen3.5/122b-a10b-moe-qlora.yaml

Co-authored-by: NanoCode012 <kevinvong@rocketmail.com>

* Update examples/qwen3.5/35b-a3b-moe-qlora.yaml

Co-authored-by: NanoCode012 <kevinvong@rocketmail.com>

* chore for qwen + vlm patch

* chore lint

* qwen lint

* 3_5_moe

* Update examples/qwen3.5/README.md

---------

Co-authored-by: NanoCode012 <kevinvong@rocketmail.com>
* use new tf32 APIs for torch 2.9+

* also upgrade cce for tf32 fixes and lint
* fix: position_ids casted to int64 for qwen35 patch

* fix: to use view instead of reshape to ensure noncontiguous error explicitly

* chore: lint
* install flash-linear-attention

* handle prequant weights for fsdp2 and ensure loss is not zero

* fix type for cu_seqlen, uninstall causal_conv1d

* chore: lint

* uv pip uninstall doesn't need confirmation
)

* update setuptools so trl can be installed from main for nightlies

* run the nightly in the PR CI on change

* use range request, don't use cu129 in CI since it's not supported with AO

* run multigpu ci if CCE install script changes
* remove cloud configuration we don't base image for

* but we do want it for uv
thad0ctor and others added 30 commits July 2, 2026 16:03
#3732)

* perf(lora): route GatedDeltaNet projections through fused LoRA kernels

Qwen3.5's linear-attention (GatedDeltaNet) layers call five LoRA-wrapped
projections per layer (in_proj_qkv/z/b/a, out_proj) through the plain peft
module forward. peft's _cast_input_dtype round-trips the full [B, S, hidden]
activation bf16 -> fp32 (fp32 adapters) -> bf16 (autocast) on every forward,
every gradient-checkpoint recompute, and again in backward — numerics-free
copy traffic that scales with the activation, not the rank.

Route them through axolotl's fused LoRA kernel (the path apply_qkv/apply_o
already use), which keeps the activation in its compute dtype and casts only
the skinny rank-r adapters:

- kernels/lora.py: generic apply_lora_linear() for a single peft-wrapped
  Linear, through the existing LoRA_O autograd Function (quantized frozen
  bases via the W_quant slot; bias/dropout/DoRA supported).
- monkeypatch/lora_kernels.py: attach apply_<proj> bound methods to every
  LoRA-wrapped GDN projection, gated on lora_qkv_kernel | lora_o_kernel.
  Name-matched (in_proj_qkv/...), so qwen3_next (in_proj_qkvz/ba) is untouched;
  find_linear_attn_in_layer is a no-op on non-GDN models.
- qwen3_5/modeling.py: _la_proj_fwd routes the five call sites through the
  attached helper, with an exact peft fallback when the kernel isn't installed.

Numerics-neutral: forward output and grad_X are bit-identical to the peft
path; adapter-grad cosine ~1.0. Was-vs-is LoRA SFT on Qwen3.5-0.8B and -2B
tracks within the run-to-run nondeterminism floor (step-1 loss bit-identical).
Existing lora-kernel patching suite green on non-GDN models (SmolLM2, Qwen3-MoE,
gemma).

* test(lora): cover fused-LoRA routing of GatedDeltaNet projections

Regression tests for the GatedDeltaNet (linear-attention) fused-LoRA routing:

- find_linear_attn_in_layer selects Qwen3.5 / Qwen3.5-MoE GatedDeltaNet layers
  (in_proj_qkv/z/b/a + out_proj) and excludes everything else — most importantly
  qwen3_next, whose projections are fused as in_proj_qkvz/in_proj_ba and must stay
  on the plain peft path. Also covers non-GDN layers and partial/missing layouts.
- apply_lora_linear matches the plain peft LoRA module forward and grad to bf16
  float noise (the routing only removes peft's _cast_input_dtype activation
  round-trip; the GEMM math is unchanged), including the DoRA branch.

Added to the existing lora_kernels patching module rather than a new file: a
separate test module collected before test_lora_kernel_patching perturbs the
import graph enough that transformers reloads modeling_llama, leaving
test_attention_patching_integration asserting against a stale LlamaAttention
class object.

* refactor(lora): address review — concise docs + per-call GDN patch log

- Tighten the apply_lora_linear / _la_proj_fwd docstrings and the
  LINEAR_ATTN_PROJS comment to a single concise line each.
- Replace the module-global one-time _LINEAR_ATTN_PATCH_LOGGED guard (which
  logged only the first layer's projections, once per process) with a
  per-call summary: count patched GDN layers and log the projection set with
  the layer count once after patching. Drops persistent module state and
  restores visibility across repeated apply_lora_kernel_patches calls.

* perf(lora): fuse GatedDeltaNet input projections into one autograd node

The GatedDeltaNet input projections (in_proj_qkv/z/b/a) all consume the same
post-norm hidden states. Route them through a single fused LoRA autograd
Function (LoRA_FusedProj) instead of one per projection: the dropout mask is
computed and saved once for the whole group, and the input gradient accumulates
in a single buffer rather than N separate dY @ W reductions. With lora_dropout>0
this cuts activation memory substantially (one saved mask/layer instead of four,
held across the forward); at dropout=0 it is memory-neutral. Supports bias,
dropout, DoRA, and base-only (unadapted) projections.

out_proj has a different input and stays on the single-projection kernel.

Also:
- split LINEAR_ATTN_PROJS into explicit LINEAR_ATTN_IN_PROJS / LINEAR_ATTN_OUT_PROJ
  (the find_linear_attn selection no longer relies on a positional [:-1] slice).
- add integration tests asserting apply_lora_kernel_patches wires the fused
  in-proj + out_proj onto a real Qwen3.5 GatedDeltaNet layer, plus fused numeric
  parity / subset-adapter / DoRA tests.

* perf(lora): address GDN fused-kernel review feedback

- gate GDN in_proj on lora_qkv_kernel and out_proj on lora_o_kernel
  independently, mirroring self-attn (was: either flag patched both)
- accumulate patched-projection names across layers for the INFO log
- document the shared-dropout-mask divergence from peft at
  lora_dropout > 0; trim the new docstrings
- drop the dead is-not-None in_proj filter; dedupe the in-proj name
  tuple to LINEAR_ATTN_IN_PROJS (single source of truth)
- add tests: independent gating, backward-under-dropout self-consistency
  (plain LoRA + DoRA, incl. d_mag)

---------

Co-authored-by: Wing Lian <wing@axolotl.ai>
Co-authored-by: Your Name <you@example.com>
…atches (#3778) [skip ci]

DeepEP main restructured setup.py and removed the disable_nvshmem code
path (deepseek-ai/DeepEP#664), so the two README patches no longer apply
(patch failed: setup.py:19). v1.2.1 is the last release they apply to.

- Ampere section: drop --depth 1 and add git checkout v1.2.1 (a shallow
  clone cannot check out the tag)
- Notes: state the v1.2.1 pin is required; remove the build-from-HEAD
  guidance

Fixes #3766
…ooleans (#3765) [skip ci]

* fix(cli/vllm_serve): let explicit CLI False override config-enabled booleans

enable_prefix_caching and enable_reasoning were combined with config values
using `cli or cfg`, so a config that enabled either option could not be
disabled from the CLI (`False or True` -> `True`). Use explicit None checks,
matching the existing enforce_eager precedence, so an explicit CLI value
always wins and an omitted CLI value falls back to the config. Adds focused
tests for both override and fallback.

Fixes #3764

* style: add trailing newline to satisfy end-of-file-fixer pre-commit hook

* refactor: trim boolean precedence handling and simplify test

Address review: fold the enable_reasoning None fallback into `or False`,
shorten the explanatory comment, and collapse the two precedence tests
into one parametrized test to reduce bloat.

* style: fix trailing newline at EOF (pre-commit)

* test fix
#3673) [skip ci]

* feat(lora): per-module rank/alpha overrides via rank_pattern / alpha_pattern

Adds support for PEFT's rank_pattern and alpha_pattern at training time
(new config fields lora_rank_pattern / lora_alpha_pattern) and at merge
time (memory-efficient merger now honors them per-module).

Also fixes a pre-existing dead check in the AdaLoRA gate
(target_rank -> target_r / peft_type == "ADALORA").

* docs(lora): register lora.qmd in _quarto.yml so it builds

* fix(lora): mismatch-aware pattern warning on lora_model_dir + rank_pattern merge tests

Review feedback from #3673: warning under lora_model_dir now compares cfg
patterns against the saved adapter_config.json and only fires on genuine
mismatch (generic fallback when the adapter config isn't readable locally);
adds end-to-end merge tests for rank_pattern alone and rank+alpha combined;
documents resume/continue-training semantics in docs/lora.qmd.

* chore: regenerate CLI config options for lora_rank_pattern/lora_alpha_pattern
* fix(lora): stop writing input grads into saved activations in fused LoRA backward

LoRA_MLP/LoRA_QKV/LoRA_QK backward aliased the input-gradient buffer onto the
saved activation X (torch.mm(..., out=X)). When X is a view created inside
another custom autograd Function (e.g. liger RMSNorm returns Y.view(*shape)),
dynamo refuses to trace the Function (ApplyTemplateBackward view+inplace
error), silently fragmenting decoder layers (gradient checkpointing off) or
evicting them from compilation entirely (checkpointing on) under
suppress_errors. It also corrupted the saved activation for any second
backward with retain_graph.

Allocate the input gradient fresh instead. The inplace flag is kept as a
no-op for API compatibility. Verified: liger->LoRA_QKV topology compiles
fullgraph with zero breaks on torch 2.12; grads match eager; whole-layer
graphs restored in both checkpointing modes.

* test(lora): cover LoRA_QK and LoRA_MLP in saved-activation regression; drop redundant comment

Parameterize the non-mutation test across all three fused kernels changed in
the previous commit (each fails on the old aliasing independently) and remove
a what-comment flagged in review. Compile-traceability test stays QKV-only:
all three share the identical backward code shape.

* test(lora): skip CUDA-only kernel tests cleanly on CPU-only hosts
… ci]

* docs: document CI/lint/test requirements and e2e-local setup for contributors

Document the pinned tool versions CI uses (ruff/ruff-format 0.15.8, mypy
1.19.1, bandit 1.9.4) and add guidance to run pre-commit instead of a
system-installed ruff/mypy so local lint output matches CI.

Also document the CI test matrix and parallel test invocation, GPU e2e-local
setup via the public Docker image (including the cu130 tag for Blackwell),
and reference the PR template from the contribution steps.

* docs: fix e2e heading level and scope local test command to CPU suite

- Address CodeRabbit: bump 'Running e2e tests locally' from h4 to h3 to close
  the heading-hierarchy gap under Getting Started.
- Mirror-CI command excludes tests/e2e (GPU e2e runs in a separate CI job),
  so the documented local run works on a CPU box.

* chore: add Makefile wrapping pinned lint/format/test commands

One canonical command per task (make lint/format/test/test-e2e) running the
exact CI-pinned tooling, so contributors and agents don't fall back to a
system ruff/pytest. Referenced from the contributing guide.

* docs: make .pre-commit-config.yaml the single source of truth for lint versions

Drop the hardcoded ruff/mypy/bandit version tables from CONTRIBUTING/AGENTS
and the literal ruff pin from the Makefile; the Makefile now derives ruff's
version from .pre-commit-config.yaml. CI's pre-commit job already reads that
file, so versions can no longer drift (the monthly autoupdate bot bumps it).

* fix: make 'make test' mirror CI's 'not slow' marker selection

The test target claimed to match CI but omitted CI's default -m 'not slow'
filter, so local runs could execute a different suite than CI.

* docs: condense agent lint/test section, make CI references version-agnostic

Review feedback from #3748: AGENTS.md lint/test section trimmed for context economy; CONTRIBUTING no longer hardcodes the Python/PyTorch matrix or Docker tags (tests.yml is the source of truth); make test reworded to approximate CI rather than claim parity.

* docs: drop Makefile, point contributor/agent docs at direct commands

Per maintainer review on #3748: remove the Makefile wrapper and update
CONTRIBUTING.md and AGENTS.md to invoke pre-commit/ruff/pytest directly
instead of via make targets.
…n attacks (#3760) [skip ci]

* ci: pin all GitHub Actions to full commit SHAs to prevent supply-chain attacks

* feat: add exact tags for ref

---------

Co-authored-by: XananasX7 <XananasX7@users.noreply.github.com>
Co-authored-by: NanoCode012 <nano@axolotl.ai>
* feat: add support matrix

* fix: simplify

* fix: simplify docs

* docs: add support matrix link and remove em-dashes in readme
… [skip ci]

* feat(ci): harden workflow checkout credentials + add dependabot

* fix(ci): disable setup-uv cache on pypi publish path (cache poisoning)

* chore(deps): pin ranged deps to exact versions, drop uv prerelease=allow
* fix(expert_parallel): renormalize gate after capacity drop; restore DeepSpeed validator branches; guard dead tied-keys patch

* fix: review pass
* feat(kernels): add NVFP4 LoRA sonicmoe support

* feat(kernels): sonicmoe SM100 NVFP4 W4A4 host driver + correctness gates

Compose quack's GemmGatedSm100/GemmDefaultSm100 with sf_vec_size=16
(blockscaled NVFP4 mainloop + gated epilogue, zero quack kernel changes)
behind our own compile driver, since quack's stock drivers never wire the
two together and guard fp4 off the varlen path.

- libs/sonicmoe/fp4_cute.py: grouped varlen_m engine (expert-sorted packed
  rows, cu_seqlens, per-expert weights) + dense composition-smoke path
- libs/sonicmoe/sf_layout.py: blocked SF atom packing, dQaccum-padded
  varlen SFA, per-expert per_tensor_scale folding, gate/up interleave
- libs/sonicmoe/nvfp4_quant.py: pure-torch NVFP4 quant/dequant oracle
- benchmarks/sonicmoe_nvfp4/: staged B200 smoke gates (upstream baseline,
  dense gated composition, grouped varlen) vs fp32 dequantized oracles
- CPU tests for the host-prep layer (SF layout vs independent formula)

Verified against quack f4f54db0 (v0.5.3) source; GPU validation pending
on the pod via the smoke scripts.

* fix(kernels): sonicmoe fp4_cute fixes from B200 validation

All three smoke gates now pass on B200 (torch 2.10+cu130, quack f4f54db,
cutlass-dsl 4.6.0.dev0, apache-tvm-ffi git):

- call-time GemmDefaultSm100 EpilogueArguments must pass add_to_output=None
  (Constexpr field; the namedtuple False default fails the FFI signature)
- fake tensors must be all-symbolic (mixed concrete/sym dims crash varlen
  ragged-TMA construction with std::bad_variant_access) and the fp4 K dim
  needs an even-divisibility hint for the packed-nibble shape check
- smoke_03 oracle dequantizes with the pts-FOLDED e4m3 scales the kernel
  actually consumes; folding cost reported separately (~1e-2 mean rel for
  non-power-of-2 pts)

Measured: dense and varlen gated/default all at max_rel ~3.9e-3 vs the fp32
dequantized-operand oracle (pure bf16 output rounding); second forward with
different total_m reuses the compiled kernel.

* feat(kernels): wire sonicmoe fp4_cute W4A4 backend into the NVFP4 MoE-LoRA seam

backend="fp4_cute" now runs end to end: fp4_cute_ops unpacks the torchao
NVFP4Tensor, caches one engine per frozen weight, quantizes activations on
the expert-sorted rows, and wraps the grouped GEMM in an autograd.Function
whose backward does per-expert chunked-dequant dX (never through packed fp4).
_lora_backward_per_group splits dx = g @ W + s*(g @ B) @ A so W_eff is never
materialized and an NVFP4 base dequantizes one expert slice at a time.
_sonicmoe_nvfp4_forward auto-selects fp4_cute on SM100/SM110 when dims align
(AXOLOTL_SONICMOE_NVFP4_BACKEND overrides). smoke_04 gates the seam on a real
NVFP4Tensor: forward plus LoRA/hidden grads vs a pure-torch STE oracle.

* fix(kernels): apply NVFP4 per-tensor scale post-GEMM instead of folding into SFB

Folding pts into the e4m3 block scales truncates: the folded scale is
~amax_block/6, which sits in e4m3's subnormal range (3 mantissa bits at
2^-9 granularity) at real LLM weight magnitudes, costing up to ~11% per
block in smoke tests and more on real checkpoints. Not exact even for
power-of-2 pts once subnormal. The fp4_cute path now keeps SFB at the
stored full-range scales and multiplies the output rows by the per-expert
pts in fp32, which also makes the forward weights identical to the exact
dequant the backward uses.

smoke_04 oracle mirrors the post-scale scheme (forward now matches
bitwise) and gradient checks use a relative-Frobenius metric: impl and
oracle contract the LoRA grads in different orders (dW-first vs z-first),
so elementwise bounds turn bf16 associativity noise into false failures
(verified against fp64 on CPU). All four smokes pass on B200.

* feat(kernels): qwen3_moe NVFP4 adapter + real-checkpoint smoke for the fp4_cute path

Thin clone of the glm_moe_dsa adapter minus the DSA patching: the converter
patterns and the direct expert load already use Qwen3-MoE's checkpoint naming,
so only the model_type gate and registration are new. Matches on either expert
backend (scattermoe or sonicmoe).

smoke_05 reruns the smoke-4 end-to-end comparison on a real MoE layer of
nvidia/Qwen3-30B-A3B-NVFP4, fused through the adapter's own load helpers, and
reports the W4A4-vs-dequant error at real weight magnitudes (OQ1) plus the
fraction of block scales that would go e4m3-subnormal if pts were folded.

* feat(kernels): full-model forward smoke + tiny LoRA train gate for sonicmoe NVFP4

Relax the quantized-training guard under use_sonicmoe too (same frozen
NVFP4 base + LoRA pattern as scattermoe). smoke_06 loads the whole
Qwen3-30B-A3B-NVFP4 through KernelsPlugin/Qwen3MoeAdapter and compares
fp4_cute vs dequant logits (NLL, top-1, KL) on real text.

* fix(kernels): bypass PEFT parametrization merge for sonicmoe expert LoRA

PEFT target_parameters wraps the experts module in ParamWrapper whose
forward materializes base + delta via _activate_lora, which cannot run
on an NVFP4 base (aten.add unimplemented) and defeats the fused low-rank
path. Mirror the scattermoe ParamWrapper fastpath: hand the LoRA tuples
to the base module (_sonicmoe_lora) and call its forward directly.

* feat(kernels): phase 2 driver for sonicmoe NVFP4 quality + shape-sweep benchmarks

200-step LoRA quality runs (fp4_cute vs dequant, shared eval split) plus
12-step throughput/memory probes sweeping context (2k/8k/16k) and
tokens/step (mbs 4), with the scattermoe Marlin W4A16 incumbent.

* perf(kernels): vectorize sonicmoe grouped path with torch._grouped_mm

The LoRA delta/backward, base dX, dense grouped GEMM, and varlen SFA
build all looped over 128 experts in Python with a host sync per
iteration (int(offsets[e]) / tolist), ~24k synced iterations per
training step at 48 layers. On sm90+ these become single-launch
torch._grouped_mm calls (ragged unaligned segments and empty groups
verified exact on B200) and one scatter for the SFA; the loops remain
as the CPU/reference fallback.

* perf(kernels): triton NVFP4 dequant + activation quantizer for the sonicmoe path

torchao's table-gather dequantize dominates the fp4_cute backward (~15ms
per layer at Qwen3-30B shapes) and the ~10-kernel pure-torch activation
quantizer dominates the remaining forward. Both become single Triton
kernels with identical rounding (amax/6 clamp 448, RN e4m3, ties-down
E2M1); torch paths remain the CPU/no-triton fallback.

* fix(kernels): IEEE-RN division in the triton NVFP4 quantizer

Triton's / lowers to reciprocal-multiply; values landing exactly on an
E2M1 bucket boundary flipped vs the torch reference (0.36% of codes).

* perf(kernels): fuse activation quant + swizzled varlen SFA into one triton kernel

The e4m3 scales are written straight to their dQaccum-padded swizzled
addresses, dropping the intermediate scale rows, the scatter, and the
pack/permute chain. Byte-identical output; torch path stays as fallback.

* fix(kernels): unpack all three triton kernels in dequant_nvfp4_triton

* perf(kernels): gather-only route/combine, no atomic index_add in the MoE hot path

Every token appears exactly top_k times in the grouped layout, so the
combine is a gather + fixed-order fp32 sum and the route gather's
backward is the same trick; index_add (atomic-bound, top kernel in the
layer profile) disappears from both directions.

* perf(kernels): one-pass in-place row scaling for the NVFP4 per-tensor scale

Replaces float() -> mul -> to(bf16) (three kernels + an fp32
materialization) with a single bit-identical triton pass.

* perf(kernels): fuse LoRA delta add into the fp4 GEMM epilogue, drop per-layer host syncs

- route_and_group: skip the routed-id guard on CUDA (EP is rejected upstream,
  non-EP ids come from a width-E topk) and replace bincount+cumsum with
  searchsorted on the already-sorted ids; both host-synced every MoE layer
- LoRA grouped path: stash the contiguous B copy for backward reuse; apply
  scaling on the [T, r] intermediates instead of the [T, dim] products
- fp4_cute: compile an add_to_output epilogue variant (constexpr, one callable
  cached per value); the LoRA delta is written into the out buffer and the base
  GEMM reduce-adds alpha*acc into it, deleting the separate elementwise add and
  the pts rowscale pass
- per-expert pts: fold pts_e/pts_ref ratios into the e4m3 SFB (max rel err
  5.9e-2 on Qwen3-30B, 0 underflowed blocks) with alpha=pts_ref; backward
  dequantizes with the same folded scales so forward and backward weights stay
  identical; fold saturation or AXOLOTL_SONICMOE_NVFP4_PTS_FOLD=0 falls back to
  stored scales + post-GEMM rowscale (pre-fold numerics)

12-step probe at seq 2048: 0.558 -> 0.498 s/step (+12% tok/s), peak max_active
30.8 -> 28.7 GiB. Smokes 4/5 bitwise vs the folded-scale oracle; 30-step loss
tracks a same-day HEAD control within noise; full-model NLL cost of the fold
+0.017 nats (toggle recovers the old path). Pod suites 89 passed / 1 skipped.

* fix(cli): skip MoE expert fusion in merge-lora when a shard holds a partial expert list

The fuse->merge->unfuse pass in merge_lora_sharded_efficient processes one
shard at a time but assumed each layer's full expert list lives inside that
shard. Checkpoints routinely split expert lists across shard boundaries
(nvidia/Qwen3-30B-A3B-NVFP4 splits layers 12, 26, 40), which either crashed
in torch.cat on mismatched per-projection expert counts or silently fused a
partial subset of experts. Also skip groups whose tensors are still
quantized: stacking raw qdata orphans the per-expert scale siblings.

Expert-count completeness is validated against config.json (num_experts and
variants); incomplete or quantized groups fall through to the per-tensor
pass unchanged.

* docs(kernels): document the NVFP4 sonicmoe backend and correct the quack pin

The validated runtime pin is quack-kernels==0.5.0 from PyPI; f4f54db0 is the
upstream source commit the internals were cross-checked against, not the
installed version.

* perf(kernels): optional fp8 backward weight cache over DeepGEMM for NVFP4 MoE dX

Caches the frozen expert weights once as fp8 e4m3 (128x128-block ue8m0
scales, built from the same folded dequant the forward consumes) and runs
backward dX through DeepGEMM's m-grouped contiguous fp8 GEMM instead of
per-backward whole-weight dequant + bf16 torch._grouped_mm. Microbench on
B200: 1.5-1.8x over the bf16 grouped GEMM at Qwen3-30B backward shapes,
dX rel err ~7e-4 (DeepSeek's validated 1x128 training recipe).

DeepGEMM is an optional source-built dependency; without it the bf16
dequant path is used unchanged. torch._scaled_grouped_mm is deliberately
not in the chain: its rowwise fp8 grouped kernel device-aborts on SM100
(torch 2.10+cu130) and kills the CUDA context. AXOLOTL_SONICMOE_NVFP4_BWD
forces deepgemm or bf16; unset auto-selects.

* perf(kernels): cut fp8 backward host overhead below the GEMM win

The index-plan build (a dozen tiny kernels) and the torch pad+quant chain
cost 3x the fp8 GEMM itself. The plan (dest/m_indices/src) is now cached on
the offsets tensor (up and down projections of a layer share it, and it dies
with the tensor), the pad+quant is one 2D-grid triton kernel that reads only
real rows and rounds bit-identically to per_token_cast_to_fp8 (pow2 scales),
and the contiguous alignment drops to 128 (all padded-layout costs scale
with E * alignment; the GEMM itself also measured slightly faster). B200 at
Qwen3-30B shapes, E=128: dX gate_up 1.96x, down 1.30x over bf16
dequant+grouped_mm with the plan cached.

Smokes 4/5 pin AXOLOTL_SONICMOE_NVFP4_BWD=bf16: they validate the
bf16-backward oracle; smoke 7 owns the fp8 deltas (~4e-2 rel_fro on
dX-dependent grads, matching DeepGEMM's own calc_diff<1e-3 gate).

* fix(kernels): make the fp8 DeepGEMM backward explicit opt-in only

The fp8 weight cache is inherently a full fp8 copy of all expert weights:
+27 GiB persistent on Qwen3-30B for +9% tok/s, which fails the memory
gate as a default. AXOLOTL_SONICMOE_NVFP4_BWD=deepgemm enables it for
VRAM-rich setups; anything else, including unset, keeps the bf16 dequant
backward and never imports deep_gemm.

* feat(kernels): extend the qwen3_moe NVFP4 adapter to qwen3_next

nvidia/Qwen3-Next-80B-A3B-Instruct-NVFP4 stores its routed experts in the
same modelopt per-expert layout; the layout inspection already handles the
model's extras (shared expert, linear_attn.out_proj / self_attn.o_proj
NVFP4 linears, per-projection input_scale, the never-loaded mtp.* head).
The conversion mapping is now registered under the model's actual
model_type instead of hardcoded qwen3_moe. Adds the 80B validation config.

* fix(qwen3_next): accept cache_position in the packed GatedDeltaNet patch

The patched decoder layer passes cache_position to linear_attn (matching
current transformers), but the patched GatedDeltaNet forward did not accept
it, so any packed qwen3_next training crashed with an unexpected-kwarg
TypeError on the first linear-attention layer.

* perf(kernels): exact per-row per-tensor scales in the fp4 GEMM epilogue

Replaces the lossy SFB pts-ratio fold as the default: the engine keeps the
stored e4m3 block scales and multiplies the full per-expert scales into the
fp32 accumulator via quack's composable epilogue (ColVecLoad activated by a
compile-time mColVecBroadcast operand + a vec_multiply override on
GemmDefaultSm100; zero quack file changes). Single bf16 rounding at the D
store, composes with the add_to_output fusion, and the backward dequant
returns to plain stored-scales-times-pts, so forward==backward holds
without folded copies. Erases the fold's +0.017 nats full-model NLL cost.

Forward is bitwise against the exact-pts fp32 oracle on both the synthetic
smoke and the real Qwen3-30B checkpoint layer. The old fold stays reachable
via AXOLOTL_SONICMOE_NVFP4_PTS_FOLD=1 for A/B numerics debugging.

* perf(kernels): skip random init of direct-loaded fused expert params

With the routed converters skipped for the direct-load fast path, the fused
experts.gate_up_proj/down_proj land as MISSING keys and transformers'
_initialize_missing_keys fills them with CPU normal_() right before
direct_load_nvfp4_experts overwrites every byte. At Qwen3-Next-80B scale that
is ~155 GB of bf16 randn at a measured 0.11 GB/s, ~23 min of waste per launch
(the actual direct expert load is 48.9s). Mark the fused 3D expert params
_is_hf_initialized before init runs; installed only when the direct load is
armed, from both the qwen3_moe and glm_moe_dsa adapters.

* perf(kernels): fuse up-GEMM + gated activation + LoRA delta into one kernel

Epilogue item (a): GemmGatedAuxAddSm100 subclasses quack's GemmGatedSm100
with a preact-space TileLoad aux (the LoRA delta, added to the fp32
accumulator), the exact per-row pts colvec multiply, and the stock gated
activation, in one blockscaled varlen kernel launch. The up weights stay in
CONCAT [gate; up] layout consumed zero-copy through quack's concat_layout
interleaved view (only the block-scale copy is row-permuted for SFB), so no
packed-weight duplication and the LoRA delta / backward stay in concat space.
The INTERLEAVED preact D is stashed for the swiglu backward.

New GroupedUpProjActLoRA autograd node replaces up-GEMM + separate
gated_activation when backend=fp4_cute, act=silu/swiglu, concat weights, no
bias/limit. Kill switch: AXOLOTL_SONICMOE_NVFP4_FUSED_UP=0. Smokes 04/05 pin
the unfused semantics; smoke 08 owns the fused validation (kernel-level
bitwise preact vs fp32 oracle + module-level fused-vs-unfused grads).

* fix(kernels): fused-up aux must be interleaved in memory, not a concat view

Two GPU findings from smoke 8 on B200: (1) a class-body NamedTuple under
'from __future__ import annotations' stores string annotations that quack's
Constexpr converter cannot eval outside quack's module (NameError: cute);
build EpilogueArguments with the functional NamedTuple form instead.
(2) applying concat_to_interleave to the epilogue TileLoad device-crashes
with an illegal instruction (the epi TMA-load path does not survive the
hierarchical N mode), while the same view on mainloop B is fine and preact
comes out BITWISE vs the fp32 oracle. So concat_layout stays ("B",) only
and the LoRA delta is computed directly in interleaved memory by permuting
the small [2I, r*E] LoRA-B factor rows (no [T, 2I] gather).

Smoke 8 passes: kernel preact bitwise (both token distributions), stage
fused-vs-unfused postact 4.7e-3 rel_fro, module grads 2.4-2.8e-2 (bf16
noise); the 3.3e-2 end-to-end forward diff is the down-GEMM NVFP4
requantization amplifying activation-rounding shifts (bucket flips), bounded
by the stage-level check. Smokes 01-05 and the sonicmoe unit tests pass.

* docs(kernels): document the fused up-projection and its kill switch

* feat(cli): nvfp4 expert-merge writer for fused expert LoRA over per-expert bases

merge-lora previously DROPPED a fused expert LoRA (PEFT ParamWrapper over
experts.gate_up_proj/down_proj) when the base stores experts per-expert
unfused as modelopt NVFP4, since no base key matches the adapter keys.

- _find_param_wrapper_lora accepts both 3D orientations: gpt-oss [E, in, out]
  and Qwen3-style [E, out, in] (the latter was rejected, so fused expert
  adapters never matched their base params)
- _Nvfp4ExpertMergeWriter claims the per-expert weight/weight_scale/
  weight_scale_2 triples of LoRA-targeted expert modules out of each shard
  (buffering layers split across shard boundaries), dequantizes via torchao,
  fuses to the runtime 3D layout, folds the ParamWrapper delta, then unfuses
  and requantizes each expert back to NVFP4 with fresh scales. Output keeps
  the base checkpoint layout; under --dequant it emits the merged fused bf16
  param instead. Per expert the output is bitwise requant(dequant(base)+delta).
- bases the writer cannot handle (no torchao, non-packed weights) keep the
  MERGE INCOMPLETE warning; a packed base without an expert count in
  config.json raises instead of silently dropping the adapter

* fix(cli): run the expert-merge writer under no_grad

The PEFT layers built for the delta have requires_grad LoRA weights, so every
emitted tensor dragged a per-layer autograd graph (with its CUDA intermediates)
into the output dict: ~3.6 GiB retained per layer, OOM at 48 layers on a 30B
merge. The other merge paths detach; the writer now disables grad entirely.

* fix(cli): share one nvfp4 outer scale across fused expert group members in the merge writer

Per-member amax recomputation let gate/up weight_scale_2 diverge under the
LoRA delta (ratios up to 4.3x on the real 30B adapter), which the loader's
fuse fold then multiplied into e4m3 block scales already at the 448 max,
overflowing to NaN and poisoning the loaded model (loss 0, grad_norm nan).

* fix(kernels): fold nvfp4 fuse scale ratios toward the max outer scale

Folding proj i's ratio relative to proj 0 casts group scales times a ratio
that can exceed 1 into e4m3, and values past 464 become NaN (the cast only
saturates to 448 below that). Using the elementwise max outer scale keeps
every fold ratio at or below 1, so the cast can never overflow.

* fix(cli): requantize merged nvfp4 weights onto the base checkpoint's grid

Recomputing scales shifts every dequant grid point by the amax drift, so all
elements re-round and a small LoRA delta is replaced by uncorrelated noise:
on the real 30B adapter the emitted delta had cosine ~0.0 with the wanted
delta and attention projections retained ~3-5% of it. Reusing the base block
and outer scales keeps unchanged elements bitwise identical and only moves
codes the delta pushes across a boundary, which preserves the delta's
direction; block scales are bumped only where the merged value outgrows the
original block range. This also inherits gate/up outer-scale equality from
the base, which the loader's fuse step relies on.

Also make the ParamWrapper LoRA finder exhaust all nesting levels in the
exact [E, in, out] orientation before trying the transposed one, so a
transposed outer LoRA cannot shadow an exact inner match (regression from
the orientation fix caught by test_param_wrapper_nesting_dim_filter).

* docs(examples): NVFP4 MoE-LoRA configs for Qwen3-30B-A3B and Qwen3-Next-80B

* chore: cleanup benchmarks

* feat: improve doc for release

* chore: improve doc and cleanup comments

* fix: cleanup nvfp4 example configs

* chore: update base readme

* Update README.md
)

The gating bmm in the ScatterMoE forward is autocast-eligible, so under
bf16/fp16 autocast the autograd Function output takes the autocast dtype
while the saved output_expanded (written by the autocast-unaware Triton
kernel from fp32 input) stays fp32. Backward then evaluated fp32 @ bf16 in
the d_gates matmul and raised "expected scalar type Float but found
BFloat16", crashing bf16 mixed-precision full fine-tuning and LoRA on MoE
models. Upcast the branch-local grad_out to output_expanded.dtype at the
top of the gates branch in both ParallelLinear and ScatterMoELoRA so the
whole gates backward runs in a single dtype; it is a no-op when the dtypes
already match.
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.