Skip to content

Dev (Qwen 3.5 recipe)#22

Merged
haizhongzheng merged 18 commits into
mainfrom
dev
Jul 13, 2026
Merged

Dev (Qwen 3.5 recipe)#22
haizhongzheng merged 18 commits into
mainfrom
dev

Conversation

@haizhongzheng

Copy link
Copy Markdown
Member

Summary

Brings the Qwen3.5-4B (hybrid Gated-DeltaNet) math-RL recipes and moves the
validated stack to transformers 5.8.1 / sglang 0.5.13.post1 / torch 2.11
(18 commits, previously reviewed as PR #21 plus follow-up fixes).

New

  • Qwen3.5-4B math recipes (qwen3.5-4b-m2po-full / -delta): M2PO, ctx 8k,
    full & delta TCP weight transfer. FSDP-only (mbridge has no qwen3_5 bridge
    and Megatron-Core has no GDN layer spec — documented in the recipe READMEs).
  • Dense Qwen3 recipes run on the new stack (attn_impl: kernels-community/flash-attn2).
  • Docs: Docker workflow now teaches "image = environment, local checkout = code"
    (mount over /workspace/astraflow, no rebuild for code changes); recipes doc
    gains the Qwen3.5 section; image tags synced to v0.1.2.

Fixes

  • chunked_prefill_size=8192 for Qwen3.5 (SGLang rollout starvation → detokenizer
    health-check crash on H100 without it).
  • Hopper (sm_90) auto-env for the GDN backward: shared
    astraflow_setup_qwen35_hopper_env() sets FLA_TILELANG=1 + CUDA_HOME
    (fla blocks its triton path on Hopper; the tilelang JIT needs nvcc + CCCL).
    No-op on Ada/L40 and when the user pre-sets the vars.
  • eval_at_start no longer silently skipped: the pre-train eval now waits for
    RaaS-pool readiness (it used to fire ~40 s before the SGLang servers registered
    and drop the step-0 baseline).
  • Megatron docker image builds/trains on the transformers 5.8 stack
    (flash-attn install, mbridge rope_parameters patch).

Validation (8×H100, docker images astraflowai/astraflow:v0.1.2*)

400-step math-RL runs with sync eval every 50 steps (overall avg@k across
AIME24/AIME25/AMC/Minerva/MATH500; Megatron scope: AIME24/25 + MATH500):

Image × recipe step 0 best Δ
v0.1.2 × Qwen3.5-4B (FSDP, GDN) — full 400 steps, clean finish 48.4 58.6 (step 400) +10.2
v0.1.2.megatron × Qwen3-8B (Megatron) 44.2 60.4 (step 250, running) +16.2
v0.1.2 × Qwen3-8B (dense FSDP) 44.3 58.0 (step 150) +13.8
  • Qwen3.5 step-0 baseline reproduces the recipe README's L40 value (47.8 vs 47.9–48.4).
  • Same Qwen3-8B model served by both images gives statistically identical baselines.
  • The Qwen3.5 run used the image + this branch mounted with zero injected env,
    i.e. the out-of-the-box workflow documented in docker/README.md.

haizhongzheng and others added 18 commits June 22, 2026 10:06
New recipes examples/math/qwen3.5-4b-m2po-{full,delta} for training Qwen3.5
(dense, hybrid Gated-DeltaNet text backbone; model_type qwen3_5) with M2PO on
AstraFlow, mirroring the existing qwen3-8b-m2po recipe structure. Trained
text-only for math (the checkpoint ships as Qwen3_5ForConditionalGeneration).

Verified end-to-end on NVIDIA L40 (Ada, sm_89): a full run trained 86+ steps
with no crash and steadily rising eval — overall avg@k 47.8% -> 57.4% (+9.6) and
pass@k 56.5% -> 67.4% over the first 80 steps (AIME24/AIME25/AMC/Minerva/MATH500,
eval every 10 steps).

Minimal framework changes for Qwen3.5 / transformers>=5 compatibility:
- model.py: register qwen3_5 + is_qwen3_5_model()
- fsdp_engine.py: pass attention_mask=None for qwen3_5 (transformers>=5
  create_causal_mask calls .ndim; the old dict form raised AttributeError)
- fsdp/__init__.py: normalize _no_split_modules set->list (qwen3_5 exposes a set)
- rlvr.py: unwrap BatchEncoding from apply_chat_template (transformers>=5)

Recipes use the standard packed training forward. attention_backend=flashinfer
(fa3 dispatches a Hopper-only kernel that fails on Ada/L40 for the GDN path;
flashinfer + triton both verified); max_running_requests=32, mem_fraction_static=0.7
on inference and FSDP dp=4 + max_tokens_per_mb=8192 on the trainer to fit 44GB L40.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The version bump (transformers 5.8.1, torch 2.11+cu130, sglang dev) broke the
existing plain-Qwen3 math recipes (qwen3-1.7b, qwen3-8b). Two fixes:

1. fsdp_engine.py: always pass attention_mask=None for the packed/varlen forward.
   The old dict(full_attention=None, sliding_attention=None) form is a
   transformers-4.x relic; on transformers>=5 a dense model (qwen3/qwen2) treats
   that dict as a *precomputed* mask, skips creation, and crashes ('dict' object
   has no attribute 'ndim'). None is correct for all archs (dense, moe, vl,
   qwen3.5/GDN) — masking is driven by cu_seqlens + position_ids. Subsumes the
   prior qwen3_5/moe/vl special-case (drop now-unused imports).

2. recipes + cli_args: set attn_impl: sdpa in actor+ref for qwen3-1.7b and
   qwen3-8b recipes (flash_attn is ABI-broken vs torch 2.11+cu130 -> import crash;
   default was flash_attention_2). Expand attn_impl choices to sdpa/eager.

Verified end-to-end: qwen3-1.7b full + delta train cleanly to step 100 on the
bumped stack; math500 avg@k rises 73.0->77.0 (full) / 72.2->78.8 (delta);
importance_weight=1.0000 (packed forward correct); zero crashes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Remove the unused is_qwen3_5_model() helper (its only caller, the mask-branch
  special-case, went away when the packed-forward mask was made unconditional).
- Note in model.py why qwen3_5 is in VALID_VISION_MODELS (the checkpoint ships
  as Qwen3_5ForConditionalGeneration -> loaded via the ImageTextToText path).
- Simplify the packed-forward attention_mask comment in fsdp_engine.py.

No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document the validated runtime stack (transformers 5.8.1, kernels 0.14.1, SGLang
dev with qwen3_5 + TritonGDNKernel, fla 0.5.0, flashinfer 0.6.11.post1, torch
2.11.0+cu130, attn_impl sdpa), GPU layout, run commands, and the validated eval
(overall avg@k 47.8 -> 57.4, pass@k 56.5 -> 67.4 over 80 steps).

The default pyproject pins load qwen3_5, but the validated stack is installed out
of band; a pin bump is deferred to a separate, tested PR (the validated SGLang is
a dev build, not a clean release).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump the pyproject transformers pin (core dep + uv override) from 5.6.1 to the
5.8.1 that the Qwen3.5 and Qwen3-dense math recipes were validated on, and move
the coupled kernels constraint from <0.13 to >=0.14,<0.15 (what transformers
5.8.1 was validated against). torch is already 2.11.0; flashinfer comes in
transitively via sglang. sglang stays pinned at the published 0.5.12.post1 — the
Qwen3.5 inference path was validated on an sglang dev build that ships qwen3_5,
as noted in the recipe README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
….8.1)

The published sglang 0.5.12.post1 predates qwen3_5 and pins transformers 5.6.x,
so it is incompatible with the transformers==5.8.1 the recipes require. Pin
sglang to the validated main-branch build (sgl-project/sglang @ 373cadc9): it
ships qwen3_5 + TritonGDNKernel and itself requires transformers==5.8.1,
flashinfer 0.6.11.post1, torch 2.11.0, kernels<0.15 -- all matching the
validated env. Update the [tool.uv] comments to match.

Verified: every pin/override matches the working astraflow35 env (transformers
5.8.1, kernels 0.14.1, outlines-core 0.1.26, torch 2.11.0, sglang @ g373cadc92).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment-only alignment of the Qwen3.5 recipes + touched code to the repo's
conventions (qwen3-8b recipes / surrounding code as the baseline):
- recipe experiment.yaml/raas.yaml: restore the "# -- ... --" section banners
  and the auto-derives / adaptive-availability rationale comments the dense
  recipes use; restyle the attn_impl comment to sentence-case prose.
- model.py: capitalize the qwen3_5 registry comment to match the file.
- fsdp_engine.py: reflow the attention_mask comment so dict(...) is not split
  mid-token, matching the file's other multi-line comments.
- pyproject.toml: reflow the sglang-extra comment to the column band.
- README: tag the GPU-layout code fence (text).
No functional content changed (verified: all yaml/sh/py/toml parse; only comment
lines were added/removed).

Also refresh the recipe README install note, which still described SGLang as
pinned at 0.5.12.post1 before the git-build pin landed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… 3.6.1

Two pyproject fixes so a clean install reproduces the validated astraflow35 env:
- Add flash-linear-attention==0.5.0 to the sglang extra: the fast Gated-DeltaNet
  kernels Qwen3.5 GDN training used in the validated runs (optional --
  transformers falls back to a slower pure-torch path when absent). Pulls
  fla-core==0.5.0.
- networkx==3.3 -> 3.6.1 to match the validated env (the only pinned version that
  differed from astraflow35).

Verified: uv pip install --dry-run -e ".[sglang]" resolves cleanly (299
packages, exit 0) to the validated versions (flash-linear-attention 0.5.0,
fla-core 0.5.0, networkx 3.6.1, transformers 5.8.1, sglang @ 373cadc9, ...).
All other pinned ML versions already matched astraflow35; the ~60 loose utility
deps are left flexible per the repo's convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch the dense Qwen3 math recipes (qwen3-1.7b-m2po-2gpus-{full,delta},
qwen3-8b-m2po-{full,delta}) and the cli_args attn_impl default to
kernels-community/flash-attn2 -- a prebuilt, ABI-matched FlashAttention-2
kernel from the HF kernels hub (cached on first use, no source build).

The literal flash_attention_2 loads the local flash-attn wheel, which is
ABI-broken on torch 2.11+cu130 (undefined symbol); is_flash_attn_2_available()
only checks package metadata so it reports available and then crashes on import.
The kernels-hub variant is ABI-matched and is the upstream-faithful FA2 the
recipes were tuned with. A paired step-25 A/B/C on qwen3-1.7b-m2po-2gpus-full
gave overall avg@k FA2 >= sdpa >= sdpa+recompute_logprob, all within eval noise;
FA2 varlen also derives the packed block-diagonal mask from cu_seqlens, avoiding
sdpa's reliance on position_ids resets. Qwen3.5 recipes stay on sdpa.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ild)

Switch the [sglang] extra from the git build (373cadc9) to the published
sglang==0.5.13.post1 -- the release the recipes were recently validated against,
which ships qwen3_5 support (sglang/srt/models/qwen3_5.py) so it covers both the
Qwen3.5 and Qwen3-dense recipes. Update the Qwen3.5 READMEs' validated-stack
tables to match (sglang 0.5.13.post1, flashinfer 0.6.12). The flash-attn-4
pre-release / transformers 5.8.1 / kernels 0.14.x [tool.uv] overrides are
unchanged (0.5.13.post1 still pulls flash-attn-4 4.0.0b15).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both full and delta variants re-run end-to-end on the published-release pin
(sglang==0.5.13.post1): training completes, full + delta weight transfer both
work, eval holds at baseline (~49-51% overall avg@k) -- no regression vs the
predecessor git build the step0->step80 table was produced on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SGLang's default chunked_prefill_size=-1 (off) makes a batch's prefill upcast the LM-head logits to fp32 in one shot, stalling/OOMing the worker -> rollout starvation -> 'detokenizer unresponsive' health-check failures -> scheduler crash in forward_extend. Observed on 8xH100 during the 1000-step validation run. The sibling qwen3-8b recipe already sets this; the GDN hybrid needs it too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two build fixes required for the Megatron backend on the current
transformers 5.8 / torch 2.11 / CUDA 13 stack, plus a docs sync. Both
were found by running an actual 2-step training smoke inside freshly
built images (FSDP Qwen3.5-4B and Megatron Qwen3-1.7B).

- Dockerfile.sglang: flash-attn 2.8.3 was silently not installed. The
  pyproject [tool.uv] exclude-dependencies entry also suppresses an
  explicit `uv pip install flash-attn` run from the project dir, so the
  image shipped without FA2. Install it from /tmp so uv ignores the
  project [tool.uv] and actually builds it from source (ABI-matched to
  torch; sm_80/90/100/120). Required by the Megatron TransformerEngine
  `flash` backend for THD/packed sequences (and dense-Qwen3
  flash_attention_2); the FSDP image needs it too.

- Dockerfile.sglang.megatron: transformers 5.8 moved rope_theta into a
  nested config.rope_parameters dict; mbridge 0.13.0 reads the old
  top-level attr and crashes converting Qwen3 configs to Megatron.
  Patch the installed mbridge to read either location.

- docs: sync installation.md transformers references 5.6.1 -> 5.8.1.
Qwen3.5 is a Gated-DeltaNet hybrid; mbridge has no qwen3_5 bridge and Megatron-Core has no GDN layer spec, so the Megatron backend cannot build or weight-load it. Document this in both Qwen3.5 recipe READMEs (train on FSDP instead) and list the mbridge-supported architectures in the Megatron recipe README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tart isn't skipped

The eval_at_start (version=0) eval fired at trainer loop entry, ~40s before the SGLang inference servers finished starting and registered with the dataflow pool. With no registered RaaS instance, EvalManager raised 'no healthy RaaS instance available for eval', the service caught it and skipped the eval, and the v=0 baseline was silently dropped (only 'Initial eval complete' printed, no eval-avg logged). Poll /status until >=1 RaaS instance is registered (300s cap) then let init settle before firing the pre-train eval. Verified in-container: the pre-train eval now waits for RaaSPool registration and runs (eval_start) instead of skipping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…training

On sm_90 the Gated-DeltaNet backward must use fla's tilelang kernel (fla blocks its triton path on Hopper as numerically wrong, fla#640), and the tilelang wgmma JIT needs a full CUDA toolkit (nvcc + CCCL headers; the pip-shipped nvcc has none). Without these env vars the trainer crashes in the first backward on H100. Detect Hopper via nvidia-smi compute_cap in both Qwen3.5 trainer scripts and set FLA_TILELANG=1 and CUDA_HOME=/usr/local/cuda (only when unset, so user overrides win; no-op on Ada/L40 which stays on its validated default backend). Validated on 8xH100 in the v0.1.2 docker image: baseline overall avg@k 47.9 (matches L40 47.8), step 50 -> 57.8.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The previous commit (425221b) put the FLA_TILELANG/CUDA_HOME auto-setup
only in 3_trainer_model0.sh, but the all-in-one run_*.sh scripts inline
the trainer launch and never call that file — so the primary entry point
still crashed in the first GDN backward on H100.

Move the logic into a shared helper astraflow_setup_qwen35_hopper_env()
in examples/_common/utils.sh and call it from all four Qwen3.5 entry
points (both run_*.sh before the trainer launch, both 3_trainer_model0.sh
replacing the duplicated block). Behavior unchanged: detect sm_90 via
nvidia-smi compute_cap, default FLA_TILELANG=1 and CUDA_HOME=/usr/local/cuda
only when unset; no-op on Ada/L40.

Verified end-to-end in the astraflowai/astraflow:v0.1.2 image on 8xH100
with zero injected env: the launcher prints the Hopper banner, the GDN
backward runs, and a full 400-step math-RL run completes cleanly
(overall avg@k 48.4 -> 58.6, matching the reference curve).
- docker/README.md, installation.md: bump image tags v0.1.1 -> v0.1.2
  and make the recommended Docker workflow explicit — the image provides
  the environment, a local checkout mounted over /workspace/astraflow
  provides the code (editable install picks it up; no rebuild for code
  changes). Quick Start now shows the full command with the repo mount,
  HF-cache mount, and WANDB key, plus the baked-code interactive mode as
  the alternative. Note the v0.1.2 images' 8xH100 end-to-end validation.
- docs/en/recipes/math.md: add the missing Qwen3.5-4B section (run
  command, settings table, FSDP-only note, Hopper auto-env note).
- README.md: add the 2026/07 v0.1.2 news entry.
@haizhongzheng haizhongzheng merged commit 2cf637f into main Jul 13, 2026
1 check failed
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.

1 participant