Skip to content

build: bump vllm to 0.29.0 - #4080

Open
yfw wants to merge 47 commits into
mainfrom
yifu/vllm0_29
Open

yfw wants to merge 47 commits into
mainfrom
yifu/vllm0_29

Conversation

@yfw

@yfw yfw commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

What does this PR do ?

Bumps vLLM from 0.26.0 to 0.29.0 (this branch started from 0.25.1; main moved to 0.26.0 with #3566 on Sept 19). The vLLM 0.29.0 wheels are built against torch 2.13, so the vLLM / Megatron / Automodel / FSDP venvs move with it: torch 2.11.0 → 2.13.0, torchvision 0.26.0 → 0.28.0, triton 3.6.0 → 3.7.1, flashinfer 0.6.14 → 0.6.18, nvidia-cutlass-dsl 4.6.0 → 4.6.2, tilelang 0.1.9 → 0.1.12 in the vLLM venv, llguidance 1.8.0 → 1.7.6 (vLLM 0.29's own <1.8 bound), numpy 2.5.3 → 2.4.6. The sglang and TRT-LLM venvs stay on torch 2.11.0, because their kernels are built against it; torch is now pinned per backend extra and a small IPC-handle shim lets the 2.11 inference venvs open weight handles produced by the 2.13 trainer.

Full nightly suites on H100 (cw_dfw) and GB200 (oci_hsg) were compared recipe by recipe against main's own scheduled nightlies at this PR's merge point. Every regression the bump introduced is fixed on this branch and validated on hardware. The remaining failures are pre-existing on main, infrastructure, or goldens sitting at their bounds, each with the main control that shows it.

Changes

Bullets are prefixed with the commit that made the change.

Dependencies (pyproject.toml / uv.lock)

  • 83fa485 / e179799 — vLLM 0.26.00.29.0 from PyPI (default cu130 wheels for both arches), torch 2.11.02.13.0, torchvision 0.26.00.28.0, flashinfer-python/-cubin/-jit-cache 0.6.140.6.18 (cu130 wheels for both arches, matching the ABI of the 0.29.0 vLLM wheels), nvidia-cutlass-dsl[cu13] 4.6.04.6.2 in the vllm extra (and 4.5.24.6.2 in mcore), tilelang <0.1.13 (0.1.9 → 0.1.12 in the vLLM venv), llguidance>=1.7.0,<1.8.0 (1.8.0 → 1.7.6: vLLM's own bound, which main's >=1.3.0 floor bypassed). flash-attn switches from the pinned cu13torch2.10 GitHub wheels to flash-attn==2.8.1 (sdist) because no 2.8.x wheel exists for torch ≥ 2.11.
  • e179799instanttensor kept to x86_64 via an override. vLLM 0.29 lists instanttensor>=0.1.9 unconditionally; it is the optional load_format="instanttensor" safetensors loader, imported lazily, and NeMo-RL never selects it. 0.2.0 ships aarch64 wheels, so this is a choice to leave an unused dependency out of the GB200 image, not a wheel-availability limit.
  • 2e0bdb8 — lockfile regenerated for the Docker uv version.
  • 9d8f808cutlass-dsl 4.6.2 in every fork that can take it. main already removed the nvidia-cutlass-dsl-libs-base exclusion in feat: add end-to-end TE NVFP4 training with per-token vLLM rollout #3566 (4.6 split the libs-base / libs-cu13 wheels cleanly; the 4.5.x pair overwrote 180 shared files). This PR moves the vllm extra 4.6.0 → 4.6.2 (flashinfer 0.6.18's requirement) and the mcore pin 4.5.2 → 4.6.2 so the training venv does not inherit the overlapping 4.5.x pair, drops feat: add end-to-end TE NVFP4 training with per-token vLLM rollout #3566's quack-kernels==0.6.1 pin (0.29 pins 0.6.4), and keeps a NOTE in pyproject.toml on why the exclusion must not come back. The trtllm fork stays on 4.5.0 via tensorrt-llm.
  • 9d8f808flash-attn source build bounded with MAX_JOBS=4 via [tool.uv.extra-build-variables]. flash-attn's setup.py sizes its parallelism from the host's free memory (not the cgroup) and cpu_count()//2, with --threads 4 per nvcc; every nemo-ci build attempt on both architectures died with ResourceExhausted after dozens of Killed "$CICC_PATH/cicc". 4×4 builds cleanly on aarch64 (2h47m) and x86 (~2h, alongside TE and flash-mla).
  • 9d8f808numpy override capped at <2.5 (resolves 2.4.6). vLLM 0.29 pins numba==0.65.0, whose metadata requires numpy<2.5; the numpy>=2.1.0 override forced 2.5.1 past it and numba refused to import in the vLLM venv ("Numba needs NumPy 2.4 or less"). Not fatal by itself — vLLM's optional-module probe catches it — but it left a broken numba in the venv and polluted every trace.
  • e991336ModelOpt stays at main's 43fd41a58, now with a do-not-move note. The pin must not move past 2ded5aba3 (ray.sub requires submission from NeMo RL home directory, blocking external workflow organization #1550): that commit introduces GroupedQuantizer, which breaks the Megatron fakequant DDP hooks and real-quant dist-ckpt loading (observed on ModelOpt main, 613e5e8b). 43fd41a58 is a release cherry-pick that carries Direct support for megatron bridge checkpoints #2054 (the RoutedExperts vLLM plugin this bump needs; the pre-Direct support for megatron bridge checkpoints #2054 plugin registered on fused_moe.layer.FusedMoE, which 0.29 no longer has) and excludes ray.sub requires submission from NeMo RL home directory, blocking external workflow organization #1550.
  • 5200efaflash-linear-attention / fla-core relocked 0.5.1 → 0.5.2, the version main's lock resolves. fla-core 0.5.1 enabled flash-linear-attention's TileLang kernels wherever tilelang imports (the mcore extra installs it for the fused DSA kernels), including Blackwell, and its TileLang gated-delta-rule backward faults there with CUDA error: misaligned address; 0.5.2 ([Bug] GDN precision error in triton3.5 and h20 fla-org/flash-linear-attention#640) turns TileLang on by default only on Hopper with Triton ≥ 3.4 and keeps Triton elsewhere. The GB200 grpo-qwen3.5-35ba3b-6n4g-async-1off-bf16-trtllm recipe died at step 1 of MegatronPolicyWorker.train() on every run of this branch while main (0.5.2) passes it; it had been mis-cleared against a Sept 10 main control that still carried 0.5.1. This branch's lock had lagged because each main merge kept our uv.lock and re-locked without upgrading transitive packages.
  • be440deevery transitive package relocked to at least main's resolution (uv lock --upgrade, with Ray held at main's 2.56.1). About 150 packages had resolved older here than on main for the reason above (pandas 2.3 vs 3.0, starlette, xgrammar, tensordict, nccl4py, cuda-pathfinder, …); 103 move, none of the pinned ones (vLLM, torch per extra, triton, flashinfer, cutlass-dsl, TE, ModelOpt, sglang, TRT-LLM, DeepEP, nixl) change, and the only packages still below main are the deliberate numpy <2.5, llguidance <1.8 and the flash-attn sdist. The nightlies in the Test plan ran on this lock.
  • ab6b3f2the trtllm venv stays on cuda-bindings / cuda-python 13.3. The relock above moved them to 13.4.x, and cuda-bindings 13.4 removed the reserved field of cudaIpcMemHandle_t that tensorrt_llm 1.3.0rc21's _ipc_utils.py reads while allocating the TP>1 all-reduce fusion workspace; the GB200 grpo-qwen3-1.7b-2n4g-fsdp2-trtllm recipe died at engine start with AttributeError: … has no attribute 'reserved' (450733567) while the TP=1 TRT-LLM recipes passed. The trtllm extra pins both <13.4 (main's versions); the torch-2.13 forks keep 13.4.x, which the vLLM and Megatron recipes passed with.

vLLM source patches / worker (nemo_rl/models/generation/vllm/)

  • ae4ebcfVllmInternalWorkerExtension.synchronize_device renamed to synchronize_sparse_refit_device. vLLM 0.29 adds WorkerBase.synchronize_device ([Bugfix][DP] Synchronize the device on pause completion vllm-project/vllm#52914) and WorkerBase.init_worker asserts that a worker extension never shadows a Worker attribute, so every vLLM engine died at init with AssertionError: Worker class … already has an attribute synchronize_device, which conflicts with the worker extension class. The extension method only drained peers before the sparse-delta refit removed staged batch files; its RPC caller and test follow the rename. An AST scan of both extension classes against 0.29's Worker/WorkerBase shows no other overlap, and a new vllm-marked unit test mirrors vLLM's collision predicate so the next upstream name clash fails in CI rather than on a GPU job.
  • ae6507cErrorResponse imported from vllm.entrypoints.serve.engine.protocol. vLLM 0.29 moved openai/engine/protocol.py out of the openai package ([Frontend] Move engine/protocol.py out openai folder vllm-project/vllm#54492); VllmAsyncGenerationWorker.post_init_async imported it from the old path when bringing up the OpenAI-compatible server, so every recipe that serves generation over HTTP (Gym, swe1, mopd) died with ModuleNotFoundError: No module named 'vllm.entrypoints.openai.engine'. A new vllm-marked test walks nemo_rl/models/generation/vllm with ast, collects every unguarded from vllm… import statement in the vLLM, Dynamo and ModelOpt generation trees (109 today, mostly lazy, method-local imports no unit test executes) and resolves them against the installed vLLM.
  • 710d278check_admission proxied through _AsyncLLMHTTPClient. vLLM 0.29's OpenAIServing._preflight calls engine_client.check_admission(n) before every response ([Core] Add max_num_queued_reqs and max_num_queued_tokens for queue size management vllm-project/vllm#49445, queue admission control). The async worker's engine-client wrapper exposes an explicit member surface by design, so every /v1/chat/completions 500ed with AttributeError: '_AsyncLLMHTTPClient' object has no attribute 'check_admission' and the Gym-path recipes died on AsyncTrajectoryCollector aborting: … batch-worker failure(s) exceeded max_generation_failures. Forwarded like the other status-only members; unit-tested.
  • e1c16e8Tied-embedding aliases dropped before load_weights. vLLM 0.29's AutoWeightsLoader (Fix weight tying vllm-project/vllm#51665) skips a tied alias such as lm_head.weight and then asserts that its canonical model.embed_tokens.weight was loaded in the same load_weights call. Refit streams weights in transport-sized batches, so the two routinely land in different calls and every tied-embedding model refit from a DTensor/automodel policy died at its first weight update (ppo_qwen2_5_1_5b_gsm8k_1n8g_automodel_noncolocated{,_async}, dapo_gemma4_e2b_it_1n8g_fsdp2_automodel, vlm_grpo_gemma4_e4b_geo3k_1n8g_automodel) with ValueError: 'lm_head.weight' was skipped because it is tied to 'model.embed_tokens.weight' … was not found in the checkpoint. The alias never loads anything, so it is now dropped up front using vLLM's own _get_tied_embedding_params and the model's hf_to_vllm_mapper (so Gemma-style renamed parameters are covered), on both the batched refit and the native reload_weights path; the MTP drafter still sees the unfiltered stream. Unit-tested; a vllm-marked test pins the helper's existence.
  • a419679_patch_vllm_ray_executor_v2_tcpstore_port recognises the upstream fix. vLLM 0.29 ships [Bugfix] Avoid TCPStore port collision for co-located non-DP Ray engines vllm-project/vllm#53666 and #50969: RayWorkerProc.create_dist_init_method binds the torch.distributed TCPStore itself on a kernel-assigned port and holds the socket (self._dist_init_store = store) until init_process_group reuses it, so the probe/bind window this patch closed on 0.25 is gone and _select_tcpstore_port no longer exists. Without this change the patch found no anchor and logged "Engines spanning nodes may fail with EADDRINUSE" on every worker start. It now logs at info and leaves the file alone; the port-arithmetic unit tests skip when the installed vLLM carries the upstream marker, and a new test pins the no-warn/no-edit behaviour against a 0.29-style source. The reserved VLLM_PORT band still governs the MessageQueue and API-server ports.
  • 83fa485_patch_vllm_shm_broadcast_bind_retry re-targeted: vLLM 0.29 binds the MessageQueue remote socket to port 0 directly (no probe/bind race any more) but ignores VLLM_PORT; the patch restores reserved-band selection with bind retries so engine sockets stay out of the ephemeral range.
  • f6993c8mm_device_do_normalize defaults to False. vLLM 0.29 ([Model] Fused mm preprocess normalisation on the Device vllm-project/vllm#50411) calls the HF image processor with do_rescale=False, do_normalize=False and re-applies both on the GPU in the vision tower's dtype. The policy normalizes the same images on the CPU in fp32 through the same processor, and the nightly probability-error checks assume identical inputs on both sides. Verified on CPU with transformers 5.12.1: the fused path is exact in fp32 but differs by up to 0.019 (≈1.7 % of the pixel std) in bf16; with the default flipped the Qwen2.5-VL clevr DTensor recipe is back at main-level metrics (token_mult_prob_error 1.019, gen_kl 0.0008, vs 198 / 0.14 with on-device normalization). Opt back in with policy.generation.vllm_kwargs.mm_device_do_normalize=true.

fp8 generation (quantization/fp8.py)

  • 1734158make_fp8_moe_kernel no longer takes a layer kwarg (0.25 forwarded it only to the FlashInfer TRTLLM experts). Both call sites updated; the unit test's expected kwargs follow.
  • 1734158process_weights_after_loading_kv re-mirrored on 0.29's BaseKVCacheMethod.process_weights_after_loading: the attention layer no longer has calculate_kv_scales (dynamic scales are a KV-cache dtype now, kv_cache_uses_per_token_head_scales), the static-scale branch keys off is_quantized_kv_cache, and the host _k_scale_cpu/_v_scale_cpu copies are refreshed on refit. Parameters are still kept so refit can update them.

ModelOpt fakequant / real-quant refit (nemo_rl/modelopt/models/generation/)

  • ee35b44 / f3fea21per-expert input_quantizer._amax routed around vLLM's expert loader, with checkpoint names mapped through hf_to_vllm_mapper first. The Megatron side exports one input_quantizer._amax per expert projection (…experts.16.up_proj.input_quantizer._amax); the vLLM ModelOpt MoE module owns a single fused quantizer per projection group (w13_input_quantizer / w2_input_quantizer). vLLM 0.25 loaded these through the model-level parameter dict, which NeMo-RL patches to include quantizer buffers; 0.29's AutoWeightsLoader hands every experts.* name to RoutedExperts.load_weights, which rewrites it with the expert mapping and resolves the result with a single getattr — a dotted buffer name cannot resolve that way and every fakequant nvfp4 MoE refit died with AttributeError: Layer … has no parameter 'w13_input_quantizer._amax'. New vllm_quant_moe_amax.route_moe_input_quantizer_amax applies the model's hf_to_vllm_mapper (Nemotron-H: backbone.model.) and then the same rewrite vLLM does, walks the dotted path on the expert module, fans the values in with max, and hands the remaining weights to vLLM's loader. Unit-tested (mapped and unmapped cases); validated by the GB200 distillation_nano3_30ba3b_4n4g_megatron_qa_nvfp4_modelopt_spec recipe.

Distributed (collectives.py)

  • 3558f0aall_gather_into_tensorall_gather_single (torch 2.13 API).

Docker (docker/Dockerfile, 3rdparty/TensorRT-LLM-workspace/_backend.py)

  • 40f5bcc / fe6945aTRT-LLM wheel mirror: skip the copy when it is already there. main's fix(docker): persist the TRT-LLM wheel #4153 mirrors the content-addressed wheel into /opt/trtllm_wheels in the hermetic layer and has the release stage read it from there. This PR's residual is 3rdparty/TensorRT-LLM-workspace/_backend.py not re-copying a mirror that already exists (a stage inheriting the mirror from an earlier layer no longer duplicates it into its own) and the matching Dockerfile comment.

Audio deps (tools/install_audio_deps.sh, docs/guides/grpo-audio-visual.md)

  • 2c0ad3c / 72b3b35torchcodec==0.11.10.16.0 in the test-time audio installer. Per the torchcodec compatibility table, 0.11 is built for torch 2.11 only while 0.12+ use the stable ABI for torch ≥ 2.11; the image now ships torch 2.13. 0.16.0 is what Gym's docker/install_codec_deps.sh already installs, so the two venvs agree. torchaudio==2.11.0 stays: it is the final torchaudio release and carries no torch pin. Guide updated to state the exact pins.

Tests (tests/test_suites/)

  • 3e3dea6grpo-qwen3-30ba3b-4n8g-megatron-qa-nvfp4.sh accepts ModelOpt's Inserted 723 quantizers line as well as 723 TensorQuantizers found in model. The latter comes from mtq.print_quant_summary, which the Megatron quant worker only calls when it quantizes from scratch; when the cached quantized checkpoint (<model>_modelopt_<cfg-hash>, keyed on quant config + calibration settings, not on the ModelOpt version) exists it restores it and only the Inserted … line is printed.

torch per backend extra (pyproject.toml / uv.lock)

torch 2.13 / vLLM 0.29 fallout found by the GitHub CICD and the rerun nightly (0cc6fab, 8e97d91)

  • 0cc6fabAsync checkpoint daemons join the training TCPStore instead of binding a port. torch 2.13's process-based DCP checkpointer (_async_process_executor) rendezvous its save daemons on a port rank 0 probes with get_free_port(); the port was re-taken before the daemon bound it and every DTensor async save died at the first checkpoint with DistNetworkError … EADDRINUSE (the L1 GRPO resume test, the converter round-trip test, and the llama3.2-1b fsdp2tp2 tq_mooncake nightly recipe at step 10). Surfaced by Automodel 72daceffa making daemon creation synchronous across ranks; main on torch 2.11 does not reach it. init_checkpointer now sets torch's DCP_USE_PREFIX_STORE=1 when MASTER_ADDR/MASTER_PORT are present, so the daemons attach to the process group's store under a prefix. Three unit tests.
  • 0cc6fabnvfp4_pertoken.py (from feat: add end-to-end TE NVFP4 training with per-token vLLM rollout #3566) no longer passes layer= to make_nvfp4_moe_kernel. vLLM 0.29 dropped the parameter (it takes per_token_activation); the new GB200 te_nvfp4_pertoken_quick recipe died with TypeError: unexpected keyword argument 'layer'; a unit test binds the rebuild's kwargs to the installed factory's signature.
  • 0cc6fab / 8d1f98a2.long_generation_decode_vs_prefill.py gains --gpu-memory-utilization; the L1 test passes 0.7 on GB200 (at 0.8 the FlashInfer TRT-LLM BF16 MoE workspace no longer fit next to 0.29's KV cache) and keeps 0.8 on 80 GiB parts, where 0.7 leaves no KV cache at all.
  • 8e97d91test_vllm_http_server ignores the usage.completion_tokens_details field vLLM 0.29 added to UsageInfo.
  • af7d9e0Routed-experts capture for the per-token NVFP4 MoE goes through the router on 0.29. Router replay worked on 0.26 because NeMo-RL's _patch_vllm_moe_routed_experts_capture fires router.select_experts on the monolithic fused-MoE branch when the router carries a capture callback. 0.29's bind_routed_experts_capturer binds monolithic kernels to the experts object instead (the FlashInfer launch then fills routing_replay_out itself) and rejects kernels without in-kernel capture; the per-token method rebuilds its kernel on every refit, so that binding would be lost after the first weight update and replay would silently see all-zero routes. host_captured_experts_cls wraps the method's experts class to report no in-kernel capture, and _patch_vllm_routed_experts_capture_router_fallback makes the 0.29 binder fall back to router.set_capture_fn for such kernels instead of raising. Kernels that support in-kernel capture keep using it. 7 unit tests (anchor against the installed vLLM, idempotency, fail-closed, an executable check of the patched binder, the wrapper).
  • a204116 — review round 1: the test_init_checkpointer_* tests carry the automodel mark so the Automodel shard runs them; a per-token test binds the kernel rebuild to the installed make_nvfp4_moe_kernel signature; the import-target walk covers the Dynamo and ModelOpt generation trees; the DeepSeek V4 tests pin vLLM's two distinct skip sets; normalize_cuda_ipc_handle is annotated; stale comments (RayWorkerProc, TRT-LLM mirror, instanttensor, fp8 docstring) and two docs mentions of vLLM 0.25.1 are corrected.
  • Also in the diff: docs/guides/dynamo-generation.md and docs/guides/grpo-audio-visual.md version / pin one-liners, and pyrefly.toml includes for vllm_quant_moe_amax.py and cuda_ipc.py.

Known issues not fixed here

Issue Recipes Status / proposed fix
grpo-deepseek-v4-flash-0731-16n8g-automodel-cp8ep128 (H100) — fails on the trainer side, as on main 1 With the 0.29 refit fixes (10e1a86, 28beca5) the refit and step-1 generation complete and the run dies in DTensorPolicyWorkerV2.get_logprobs() with DeepEP error: timeout (dispatch CPU) — the same point and error as the main control at this PR's earlier base (446267140). main's Sept 20 / 21 nightlies fail it earlier, at vLLM engine start. vLLM path at parity; trainer side broken on main.
grpo-qwen3-30ba3b-4n4g-megatron-te-nvfp4-pertoken-quick (GB200) — resolved; kept here as the record of a 0.29 behaviour change 1 Recipe landed on Sept 19 (#3566); three 0.29 changes hit it. (1) make_nvfp4_moe_kernel(layer=) was dropped in 0.29 — fixed in 0cc6fab. (2) 0.29 binds routed-experts capture for monolithic kernels to the kernel's experts object and lets the FlashInfer launch fill routing_replay_out itself; this method rebuilds its kernel on every refit, so router replay would have silently lost its routes after the first refit. af7d9e0 wraps the experts class to report no in-kernel capture and adds a source patch so 0.29's binder falls back to the router hook (the path NeMo-RL's existing _patch_vllm_moe_routed_experts_capture fires, and how this recipe captured on 0.26) instead of raising. (3) With (1) in place the recipe died in the first post-refit generation with CUDA error: an illegal memory access in the FlashInfer TRT-LLM NVFP4 MoE kernel (450220649). That launch had the in-kernel replay buffer attached — the one FlashInfer launch this recipe exercises that 0.26 never ran. With af7d9e0 routing the capture through the router (so the kernel launches without a replay buffer) the recipe passes 20/20 (450730474) with gen-KL 0.0083 and median prob-error 1.060, matching main's 0.0082 / 1.058, and router-replay validation on. The per-token FlashInfer launch with routing_replay_out attached remains unusable on flashinfer 0.6.18 and is worth reporting upstream. main passes the recipe on Sept 20 and aborts on Sept 21 in the HybridEP handle import (see W4A4 below).
Colocated Megatron + sglang dies at the step-10 async checkpointAttributeError: module 'torch.multiprocessing.reductions' has no attribute '_rebuild_cuda_tensor_original' 2 (H100 2n8g and GB200 1n4g megatrontp1_sglang) Pre-existing on main (Sept 20 and 21 fail identically); refits 1–10 through the new torch-2.13→2.11 IPC path work. The colocated fsdp2 + sglang recipe passes on H100 (450/450) and on GB200 reaches step ~295/450 before its 150-min Slurm limit, which main also hits. Non-colocated sglang passes on both SKUs.
NVFP4 W4A4 real-quant on GB200grpo-qwen3-30ba3b-4n4g-megatron-qa-nvfp4-w4a4-real 1 Two failure modes, both seen on main too. (a) MegatronQuantPolicyWorker ranks abort at step 1 in DeepEP's HybridEP buffer allocation (cuMemImportFromShareableHandle in NVLCoordinator::open_handles_from_other_ranks, then SIGABRT) — main Sept 21, 19 and 18 abort identically, Sept 20 and 16 pass; node/fabric dependent. (b) When it runs, it trains 2/2 with reward and accuracy passing and js_divergence_error[2] at the 0.007 bound (0.00694 / 0.00718 on the two 0.29 samples vs 0.0039–0.0044 on main; gen-KL 0.023 vs 0.015). The H100 W4A16 recipe matches main exactly, so (b) is the NVFP4 activation-quant kernels on Blackwell, not NVFP4 weights generally.
nixl-cu13 1.3.0 nixl_ep extension built against torch 2.11nixl_ep_cpp.cpython-313-…so: undefined symbol _ZN3c104impl3cow23materialize_cow_storage… none Non-fatal: vLLM's has_nixl_ep() probe catches it and disables the optional nixl_ep all2all backend, but the traceback appears in every vLLM worker log and nemo-ci's error extractor picks it as the "cause" of unrelated failures. Bump nixl to a torch-2.13 build in a follow-up.
grpo-nemotron3-super-120BA12B-8n4g-megatron (GB200) — host OOM during the step-5 checkpoint save 1 On main this recipe dies at its first refit (Unquantized FlashInfer TRTLLM refit does not yet support a co-trained MTP drafter; Sept 20 and 21). On 0.29 it refits and trains all 5 steps, then the Megatron dist-ckpt save host-OOMs the 4-GPU GB200 nodes. The save had never been reached before; not a vLLM path.
mopd-qwen3-1.7b-3n4g-megatron-pack-single-controller-fullvocab (GB200) — never starts: pyxis cannot extract the sandbox squashfs 1 Only USES_SANDBOX=1 recipe in the GB200 nightly; main Sept 20 and 21 fail identically at Slurm step 0. nemo-ci provisioning on oci_hsg. The H100 3n8g twin passes.
Goldens at their bounds 4 llama3_2_1b_instruct_1n4g_fsdp2tp1_v3 (GB200) step time 14.8–15.1 s vs < 14.5 (main 13.7–15.1 s). clevr_8n4g_megatron_generation_v1 (GB200, Megatron generation) final accuracy 0.59–0.62 against > 0.6 / +0.01 (main 0.59–0.64). mxfp8_rollouts_flashinfer (GB200, Megatron generation): 10/10, one-step prob-error spike and 57 s steps vs < 48 (main fails the same recipe on prob-error and step time). sft_gpt_oss_20b_1n8g grad_norm[50] > 10.0: PR 9.74–10.12, main 9.83–10.20.
Gym server spin-up flake 3 A Gym server process exits during spin-up with no output and the NemoGym actor loses the GCS; main Sept 20 hit it on both mopd recipes. All three passed on retry.
New 0.29 default: FlashInfer TP all-reduce (VLLM_ALLREDUCE_USE_FLASHINFER=1) TP > 1 recipes No regression seen; opt out via vllm_cfg.env_vars if one appears.

Notes for reviewers

  • Two torch versions in one lockfile. Each extra pins its own torch inside a uv conflict fork; uv sync --extra sglang and uv sync --extra trtllm produce torch-2.11 venvs, everything else torch 2.13. The base range torch>=2.11,<2.14 is what the driver venv resolves from; the ceiling is deliberate so a relock cannot move the driver past what the inference venvs can talk to. If a future sglang-miles / TRT-LLM release moves to torch 2.13, drop their pins; the shim then sees only version-3 handles on both sides and can be removed.
  • The IPC shim only touches 'c' handles, and never consults the consumer. It lowers the version byte of any 'c' handle above 2. torch 2.13 changed the wire format of expandable-segment ('e') handles along with the version byte; those are left alone. The trainer emits 'e' handles under PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True, which vLLM (same torch) opens fine and a torch 2.11 consumer (sglang / TRT-LLM, colocated) rejects with torch's generic "future version" error, so keep that allocator setting off for those two backends; none of the shipped sglang / TRT-LLM recipes sets it.
  • Lock parity with main. After be440de the lock is at or above main's resolution for every package except the deliberate pins; the lock diff is therefore larger than the dependency bullets alone would suggest. Compare against main with git show origin/main:uv.lock if in doubt.
  • Async checkpoints and DCP_USE_PREFIX_STORE. The flag is torch's own opt-in (torch/distributed/checkpoint/_async_process_executor.py); it is set only when the training rendezvous variables are present, and an explicit value in the environment wins. The daemons then need no free port at all, which is also the right behaviour on hosts where the ephemeral range is contended.
  • Gym. docker/Dockerfile still forces Gym's server venvs to NEMO_GYM_VLLM_VERSION=0.25.1, which works against this branch (all Gym-path recipes pass). Gym is being bumped separately in build: bump vllm to 0.29.0 Gym#3505 (vLLM 0.29 + flashinfer 0.6.18; it has to drop Gym's pycountry exclusion because vLLM 0.29 imports mistral_common eagerly); a follow-up RL PR will bump the Gym submodule and that ARG together once it merges.
  • The cutlass change removes a [tool.uv] exclusion that was load-bearing at 4.5.x (180 overlapping files, verified from the wheels; 0 at 4.6.x). uv pip install of the overlapping pair silently picks a winner per file, which is why the mcore pin moved to 4.6.2 too.
  • instanttensor is the one uv override that drops a dependency on one arch. 0.2.0 does ship aarch64 wheels; the skip keeps an unused loader out of the GB200 image, and lifting the marker is all it takes to enable load_format="instanttensor" there.
  • mm_device_do_normalize=False flips a vLLM default that, in 0.29, only Qwen2-VL / Qwen2.5-VL opt into (every other VLM is forced off by vLLM itself), so it affects those two families. It restores the 0.25 behaviour byte-for-byte on the processor side; the cost is the CPU normalize vLLM's change was avoiding.
  • ModelOpt stays at main's 43fd41a58; this PR adds the "do not move past ray.sub requires submission from NeMo RL home directory, blocking external workflow organization #1550" note in pyproject.toml (GroupedQuantizer, see the e991336 bullet).
  • The vllm-marked unit suite (429 items under tests/unit/models/generation, utils, policy on this head; 2 need a CUDA device) was re-run against the branch lock + vLLM 0.29 after every main merge, alongside the no-vLLM pass the L0 shards do over the same files.

Test plan

Nightlies. Full nightly suites on both SKUs on the final lock (be440de; the later commits touch a functional-test script, the per-token NVFP4 capture path, tests, comments, docs, and — in ab6b3f2 — the trtllm venv's cuda-bindings pin, validated separately below). H100 lane 69151691 and GB200 lane 69183689, both complete. Both are compared recipe by recipe with main's Sept 20 scheduled nightly (68861701, RL 880a37a — exactly the merge point of d104085, vLLM 0.26.0) and Sept 21 (69003177, RL 612d527, one commit behind the 094a932 that 51d7ee4 merged). Two earlier full passes drove the fixes: fe6945a (H100 68529938, GB200 68535477) found the DeepSeek V4 refit changes; 8e97d91 (H100 68945599, GB200 68937624) found the three torch-2.13 items in 0cc6fab and the fla-core lag in 5200efa.

Targeted validation of the fixes made after 8e97d91. EADDRINUSE fix, H100 69074478: llama3_2_1b_instruct_1n8g_fsdp2tp2_temp0_8_topp0_9_topk50_tq_mooncake saves every checkpoint and runs to step 470/500 before the recipe's own 180-min Slurm limit, exactly where main stops. fla-core relock, GB200 69076114: qwen3_5_35ba3b_6n4g_async_1off_bf16_trtllm trains 16 steps with gen-KL 0.0010–0.0012 and no fault, where every earlier 0.29 run died at step 1, then hits Disk quota exceeded on the oci_hsg CI filesystem at step 17 (450537401); w4a4_real aborted a fourth time in the DeepEP handle import that main hits too. trtllm cuda-bindings pin, GB200 69220858: qwen3_1_7b_2n4g_fsdp2_trtllm on ab6b3f2 passes 10/10 (gen-KL 0.0010, reward 0.875, 22 s/step; 451393519). Per-token NVFP4 capture path, GB200 69135677: te_nvfp4_pertoken_quick passes 20/20 with main-level goldens. clevr_8n4g_megatron_generation_v1 rerun (GB200 69074482): 50/50 steps, lands in the same 0.59–0.64 final-accuracy band as main.

SKU Recipes Pass Fail Running PR passes, main fails PR fails, main passes
H100 (cw_dfw) 134 109 25 0 1 5
GB200 (oci_hsg) 47 32 15 0 1 6

The "PR fails, main passes" rows are itemised in the table below with their cause. Several GB200 jobs on this lane spent an hour or more in the code-snapshot copy while the oci_hsg CI project's Lustre quota was full; the ones that then died on Disk quota exceeded are marked as infrastructure.

GitHub CICD (L0 unit shards incl. the no-vLLM and --vllm-only passes over the vLLM tests, L1 functional tests on both SKUs) is green on the current head ab6b3f2 (run 35711529567), as it was on af7d9e0. One H100 shard, L1_Functional_Tests_Megatron_4, is flaky on this branch and on main's runners: in two of five runs grpo_megatron_generation_colocated.sh trains its 2 steps and the driver then exits non-zero without output; a plain re-run passes, and the GB200 copy of the shard passes every time.

Results — nightly, final lock be440de (both lanes complete)

PR test pipelines: H100 69151691 · GB200 69183689. main controls: the Sept 21 scheduled nightly 69003177 (RL 612d527, one commit behind the 094a932 that 51d7ee4 merged) and the Sept 20 one 68861701 (RL 880a37a, exactly the merge point of d104085; vLLM 0.26.0 on both). One row per recipe (checkout bounces retried; latest attempt shown).

  • H100: 109 pass · 25 fail · 0 running
  • GB200: 32 pass · 15 fail · 0 running

PR fails, main passes (11)

Test PR main Sept 21 main Sept 20 Notes
llm_grpo_llama3_2_1b_instruct_1n4g_fsdp2tp1_v3
GB200
failed 165 min Marginal step-time golden. 500/500 steps, correctness checks pass; mean(total_step_time, -6, -1) < 14.5 misses at 14.9–15.1 s on every PR lane (14.92 on 8e97d91, 15.07 on be440de). PR runs 14.8–15.1 s; main 13.9 s on Sept 20 (pass), 13.7–15.1 s over Sept 8–9. About 5 % on a 1-GPU-per-node recipe whose generation share is unchanged; see Known issues.
llm_grpo_nanov3_30BA3B_4n4g_megatron_generation_noncolocated_mxfp8_rollouts
GB200
failed 93 min Single-step prob-error outlier (Megatron generation, MXFP8 rollouts). 10/10 steps; gen-KL 0.028–0.043 on every step, median token_mult_prob_error 1.25 and step time 24.8 s all pass, but token_mult_prob_error[10] < 2.0 reads 464.6 — one outlier token on the last step while the distribution-level check stays flat. Same shape as the _flashinfer twin's step-10 spike (22.8) on the previous lane and main's Sept 20 miss on that twin (median 1.56). Passed on the 8e97d91 lane and on main Sept 21; vLLM is not in this recipe's loop.
llm_grpo_qwen2_5_math_1_5b_instruct_1n4g_megatron_single_controller_sync
GB200
failed 377 min Infra: oci_hsg CI Lustre quota. The job sat in the code-snapshot copy while the project quota was full, then died on Disk quota exceeded before its first step. Passed on the 8e97d91 lane (single-controller sync, 450/450) and on main Sept 20; main Sept 21 failed the same recipe without a metric table. (main Sept 21: failed.)
llm_grpo_qwen3_1_7b_2n4g_fsdp2_trtllm
GB200
failed 251 min Regression from the transitive lock upgrade (be440de), fixed in the follow-up commit. AttributeError: 'cuda.bindings.runtime.cudaIpcMemHandle_t' object has no attribute 'reserved' in tensorrt_llm/_ipc_utils.py::open_ipc_memory while allocating the TP>1 all-reduce fusion workspace at engine start. uv lock --upgrade had moved cuda-bindings / cuda-python 13.3.1 → 13.4.x (main is on 13.3.1), and 13.4 dropped the reserved field TensorRT-LLM 1.3.0rc21 reads. The trtllm extra now pins both <13.4; on that pin the recipe passes 10/10 (451393519, gen-KL 0.0010). The TP=1 TRT-LLM recipes (qwen2_5_0_5b_1n4g_megatron_trtllm_noncolocated_async, qwen3_5_35ba3b_6n4g_async_1off_bf16_trtllm) never allocate that workspace and passed on the same lock.
llm_grpo_qwen3_30ba3b_4n4g_megatron_qa_nvfp4_w4a4_real
GB200
failed 152 min Pre-existing on main. MegatronQuantPolicyWorker ranks abort at step 1 in DeepEP's HybridEP buffer allocation: cuMemImportFromShareableHandle fails in NVLCoordinator::open_handles_from_other_ranks and the process SIGABRTs before any golden. main fails identically on Sept 21 (449569744), Sept 19 (447326502) and Sept 18 (445908944); it passed on Sept 20 and 16; our retry (449824224) aborted the same way. Node/fabric-dependent. When the recipe does run on 0.29 (two earlier samples) it trains 2/2 with reward and accuracy passing and js_divergence_error[2] at the 0.007 bound (0.00694 / 0.00718; main 0.0039–0.0044) — see Known issues. (main Sept 21: Signal/OOM-Kill.)
llm_grpo_qwen3_30ba3b_4n4g_megatron_te_nvfp4_pertoken_quick
GB200
failed 246 min Regression (new recipe from #3566); fixed in 0cc6fab + af7d9e0, validated. make_nvfp4_moe_kernel() got an unexpected keyword argument 'layer' (vLLM 0.29 dropped the parameter) is fixed in 0cc6fab. With that fix (450220649) the recipe refits and dies in the first generation with CUDA error: an illegal memory access in the FlashInfer TRT-LLM NVFP4 MoE kernel. 0.29 also changed how routed experts are captured for monolithic kernels (bound to the kernel's experts object, which this method rebuilds on every refit, so router replay would have silently lost its routes after the first refit); af7d9e0 routes the capture through the router hook NeMo-RL already patches in, as on 0.26, so the kernel launches without a replay buffer. With both fixes the recipe passes 20/20 (450730474: gen-KL 0.0083, median prob-error 1.060 — main Sept 20: 0.0082 / 1.058; router replay validation on). main passes on Sept 20 and aborts on Sept 21 in the HybridEP handle import (see W4A4). (main Sept 21: Uncaught Exception.)
llm_grpo_gemma3_1b_it_1n8g_fsdp2tp1_tq_simple
H100
failed 141 min Step-time golden under lane contention. 400/400 steps, median(token_mult_prob_error) < 1.1 passes at 1.009; mean(total_step_time, -6, -1) < 14 misses at 19.3 s while ~120 lane jobs shared the cluster. Passed on the 8e97d91 rerun and on main Sept 20/21 in quieter lanes.
llm_grpo_llama3_2_1b_instruct_1n8g_megatron_temp0_8_topp0_9_topk50_tq_mooncake
H100
failed 184 min Borderline recipe duration (main too). 489/500 steps at the 150-min Slurm limit with healthy refits; the same recipe finished 500/500 on the 8e97d91 lane (187 min wall, incl. queue) and main sits on the same edge (Sept 20 pass at 171 min, Sept 21 TIMEOUT at 176 min). (main Sept 21: Slurm time limit.)
llm_mopd_qwen3_1_7b_3n8g_megatron_pack
H100
failed 48 min Completed; Slurm TIMEOUT at teardown (launch contention). 5/5 steps, both goldens pass, exit 0; 12 min to cluster ready against an 18-min limit. Passed on the 8e97d91 rerun (retry) and on main Sept 21. Not retried for the same reason as the SFT recipe above. (main Sept 20: Signal/OOM-Kill.)
llm_sft_gpt_oss_20b_1n8g_fsdp8ep8_automodel
H100
failed 42 min Marginal golden (SFT, no vLLM). grad_norm[50] > 10.0 misses at 9.88; PR runs 9.74–10.12, main 9.83–10.20 (fails Sept 20 at 9.83, passes Sept 21). The bound sits inside the noise of this recipe on both branches.
llm_sft_qwen3_0_6B_1n8g_megatron_yarn_128k
H100
failed 48 min Completed; Slurm TIMEOUT at teardown (launch contention). 20/20 steps, all three loss goldens pass and the recipe exits 0, but the job took 11 min to reach cluster ready (about 120 lane jobs launched together after the checkout-bounce retries) and the 15-min Slurm limit fired during teardown. SFT, no vLLM. Not retried: a retry of a completed recipe short-circuits on the existing metrics.

PR fails, main had no usable result (2)

Test PR main Sept 21 main Sept 20 Notes
llm_dpo_qwen2_5_math7b_1n8g_megatron_fused_linear_logprobs
H100
failed 48 min Pre-existing on main. DPO, no vLLM; train/accuracy[10] >= 0.5 at 0.406. main bounced at checkout on Sept 20 and 21; fails on Sept 8 and 9. (main Sept 21: died at checkout (shared-workspace Bad address); Sept 20: died at checkout (shared-workspace Bad address).)
llm_grpo_nanov3_30ba3b_3n8g_megatron_dynamo_swe1
H100
failed 47 min Pre-existing on main. ValueError: NeMo Gym does not support generation backend 'dynamo'; main fails whenever it gets past checkout (Sept 8, 9; bounced Sept 20/21). (main Sept 21: died at checkout (shared-workspace Bad address); Sept 20: died at checkout (shared-workspace Bad address).)

PR fails, main fails (27)

Test PR main Sept 21 main Sept 20 Notes
llm_dpo_nanov3_30B3AB_1n4g_fsdp4ep4_automodel
GB200
failed 254 min Pre-existing on main. DPO, no vLLM. mean(total_step_time, -5, -1) < 5 at 17.3 s; main Sept 20 / 21 fail the same check at 16.5 / 16.1 s plus loss goldens.
llm_grpo_moonlight_16ba3b_4n4g_megatron
GB200
failed 29 min Pre-existing on main. KeyError: decoder.layers.N.self_attention.linear_q_proj.layer_norm_weight from model not in checkpoint at Megatron load; main Sept 20 and 21 fail identically.
llm_grpo_nanov3_30BA3B_4n4g_megatron_generation_colocated_reshard_async_gym
GB200
failed 257 min Pre-existing on main. Megatron generation; Gym rejects the rollouts (pydantic ValidationError … NeMoGymChatCompletionMessageForTraining) until the time limit. main Sept 20 and 21 fail identically. On the be440de lane the node died first (CUDA driver error: uncorrectable NVLink error).
llm_grpo_nanov3_30BA3B_4n4g_megatron_generation_noncolocated_mxfp8_rollouts_flashinfer
GB200
failed 256 min Pre-existing on main. Now trains 10/10 (main's #3630 fixed the earlier abort) and misses two goldens: token_mult_prob_error[10] < 2.0 (a single-step spike to 22.8; median 1.32 passes, gen-KL 0.042 passes) and median(total_step_time) < 48 (57.2 s; 55.9 s on the be440de lane, where the prob-error checks passed). main Sept 20 fails the same recipe on median(token_mult_prob_error) < 1.5 (1.56) and the same step-time bound (55.1 s); Sept 15–19 fail too. Megatron generation with FlashInfer MXFP8 rollouts; vLLM is not in the loop.
llm_grpo_nanov3_30ba3b_4n4g_megatron_qa_nvfp4_w4a16_real
GB200
failed 251 min Pre-existing on main. Completes 1/1 and misses gen_kl_error[1] < 0.003 (0.0116) and max(token_mult_prob_error) < 1.05 (1.094); main Sept 20 misses the same two checks with the same values (0.0112 / 1.094).
llm_grpo_nemotron3_super_120BA12B_8n4g_megatron
GB200
failed 44 min Changed, not vLLM. On main the recipe dies at its first refit (Unquantized FlashInfer TRTLLM refit does not yet support a co-trained MTP drafter; Sept 20 and 21). On 0.29 it refits and trains all 5 steps, then the Megatron dist-ckpt save host-OOMs the 4-GPU nodes. See Known issues.
llm_grpo_qwen2_5_math_1_5b_instruct_1n4g_fsdp2tp1_sglang
GB200
failed 170 min Pre-existing on main. Colocated sglang on GB200 reaches step ~295/450 and hits the 150-min Slurm limit; main Sept 20 and 21 hit the same limit.
llm_grpo_qwen2_5_math_1_5b_instruct_1n4g_megatrontp1_sglang
GB200
failed 366 min Pre-existing on main. Colocated sglang dies at the step-10 async checkpoint on torch.multiprocessing.reductions._rebuild_cuda_tensor_original; main Sept 20/21 fail identically (same step, same attribute) and then sit until the Slurm limit.
llm_mopd_qwen3_1_7b_3n4g_megatron_pack_single_controller_fullvocab
GB200
failed 215 min Infra (main too). Slurm step 0 fails before Ray starts: pyxis failed to create container filesystem extracting the sandbox squashfs on oci_hsg. main Sept 20 and 21 fail identically; the H100 3n8g twin passes.
llm_dapo_nanov3_5_30BA3B_4n8g_automodel
H100
failed 12 min Pre-existing on main. ValueError: Couldn't instantiate the backend tokenizer (HF asset not in the CI cache); main Sept 20 and 21 fail identically.
llm_dpo_mistral_nemo_instruct_2407_1n8g_fsdp2tp8_actckpt_long
H100
failed 76 min Pre-existing on main. DPO, no vLLM; train/loss[1] < 0.70 at 0.7087 — main Sept 20 fails on the identical value (0.7087241); Sept 21 could not submit to Slurm.
llm_dpo_nanov3_30B3AB_1n8g_fsdp8ep8_automodel_v2
H100
failed 57 min Pre-existing on main. DPO, no vLLM; mean(total_step_time, -5, -1) < 5 at 5.32 s; main Sept 20 fails the same check (5.20 s) plus two loss goldens; Sept 21 could not submit to Slurm.
llm_grpo_deepseek_v4_flash_0731_16n8g_automodel_cp8ep128
H100
failed 37 min vLLM-side regressions fixed (10e1a86, 28beca5); still fails on the trainer side like main. The refit and step-1 generation complete; the run dies in DTensorPolicyWorkerV2.get_logprobs() with DeepEP error: timeout (dispatch CPU), the same point and error as the main control at this PR's earlier base (446267140). main's Sept 20 / 21 nightlies (vLLM 0.26.0) fail the recipe earlier, at vLLM engine start (RuntimeError: Worker failed with error '' ×11, then the step is cancelled).
llm_grpo_llama3_2_1b_instruct_1n8g_fsdp2tp2_temp0_8_topp0_9_topk50_tq_mooncake
H100
failed 215 min Regression, fixed in 0cc6fab. Dies at the step-10 async checkpoint: torch 2.13's process-based DCP checkpointer rendezvous the save daemons on a get_free_port() probe that is re-taken before the daemon binds it (DistNetworkError … EADDRINUSE). Same crash in the GitHub L1 GRPO checkpoint test and the converter test on this head. init_checkpointer now opts the daemons into DCP_USE_PREFIX_STORE=1 so they join the training TCPStore instead of binding a port. Validated on the fixed head (450067839: no EADDRINUSE, every 10-step checkpoint saved, 470/500 steps until the recipe's 180-min Slurm limit — exactly where main (torch 2.11, never reaches the bug) stops on Sept 20 and 21). What remains is that pre-existing time limit.
llm_grpo_moonlight_16ba3b_4n8g_megatron
H100
failed 17 min Pre-existing on main. KeyError: decoder.layers.N.self_attention.linear_q_proj.layer_norm_weight from model not in checkpoint at Megatron load; main Sept 20 and 21 fail identically.
llm_grpo_moonlight_16ba3b_4n8g_megatron_fp8_e2e
H100
failed 12 min Pre-existing on main. main Sept 20 and 21 fail this recipe at the same point (Megatron load).
llm_grpo_moonlight_16ba3b_4n8g_megatron_tq_simple
H100
failed 38 min Pre-existing on main. KeyError: decoder.layers.N.self_attention.linear_q_proj.layer_norm_weight from model not in checkpoint at Megatron load; main Sept 20 fails identically, Sept 21 bounced at checkout.
llm_grpo_qwen2_5_math_1_5b_instruct_2n8g_megatrontp1_sglang
H100
failed 42 min Pre-existing on main. Colocated sglang dies at the step-10 async checkpoint on torch.multiprocessing.reductions._rebuild_cuda_tensor_original; main Sept 20/21 fail identically (same step, same attribute) and then sit until the Slurm limit.
llm_grpo_qwen3_30ba3b_4n8g_megatron_qa_nvfp4
H100
failed 39 min Pre-existing on main. Fake-quant NVFP4 ranks abort at step 1 (Fatal Python error: Aborted, cumem_allocator.cpp on main); main Sept 20 fails identically, Sept 21 bounced at checkout.
llm_grpo_qwen3_5_35ba3b_2n8g_automodel_ep16
H100
failed 27 min Pre-existing on main. RuntimeError: DeepEP error: timeout (dispatch CPU) at step 1; main Sept 20 and 21 fail identically.
llm_grpo_qwen3_8_27b_2n8g_megatron_tp4pp2cp2
H100
failed 17 min Pre-existing on main. Dies in transformers at model load; main Sept 20 and 21 fail identically.
llm_ppo_qwen2_5_1_5b_gsm8k_2n8g_megatron_valuetp2sp_dynbatch_noncolocated_async_single_controller
H100
failed 40 min Pre-existing on main. Config validation: SingleController checkpointing with a replay-checkpoint-capable sampler requires checkpointing.save_period; main Sept 20 and 21 fail identically.
llm_prorlv2_qwen2_5_math_1_5b_instruct_1n8g_fsdp2tp1_v2_tq_mooncake
H100
failed 185 min Pre-existing on main. Slurm time limit at step 376/450 (refits healthy); main Sept 20 and 21 hit the same limit.
vlm_vlm_grpo_nemotron_omni_30ba3b_clevr_1n8g_megatron_tp8ep8_v1
H100
failed 36 min Pre-existing on main. CUDA OOM at step 1; main Sept 20 and 21 fail identically.
vlm_vlm_grpo_nemotron_omni_30ba3b_mmpr_4n8g_megatron_tp8ep16_v1
H100
failed 45 min Pre-existing on main. AssertionError: defer_fp32_logits must be True if logprob_chunk_size is set; main Sept 20 and 21 fail identically.
vlm_vlm_grpo_qwen3_5_35ba3b_geo3k_2n8g_automodel_ep16
H100
failed 43 min Pre-existing on main. RuntimeError: DeepEP error: timeout (dispatch CPU) at step 1; main Sept 21 (Sept 20 bounced at checkout) fail identically.
vlm_vlm_grpo_qwen3_5_35ba3b_geo3k_2n8g_automodel_ep16_tq_simple
H100
failed 22 min Pre-existing on main. RuntimeError: DeepEP error: timeout (dispatch CPU) at step 1; main Sept 21 (Sept 20 bounced at checkout) fail identically.

PR passes, main fails (2)

  • H100 (1): llm_dapo_gemma4_e2b_it_1n8g_fsdp2_automodel
  • GB200 (1): llm_sft_gpt_oss_20b_1n4g_fsdp4ep4_automodel

Pass on both PR and main: 139 (H100 108, GB200 31); the list is in a comment below to keep this description under GitHub's size limit.

🤖 Generated with Claude Code

yfw and others added 6 commits September 10, 2026 10:05
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
(cherry picked from commit 717ee4a)
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
(cherry picked from commit 03b9ee2)
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
(cherry picked from commit c759c71)
Four independent dependency problems surfaced when running the nightly
suites on the vllm 0.28.0 / torch 2.13.0 bump. All of them are in the
resolved environment rather than in vLLM itself.

* cutlass-dsl: stop excluding `nvidia-cutlass-dsl-libs-base`. The exclusion
  worked around cutlass#3259 (4.5.x libs-base and libs-cu13 overwrite 180
  shared files, 99 with different content). At 4.6.x the wheels are split
  cleanly: libs-base carries the whole Python `cutlass` DSL and libs-cu13
  only the CUDA-13 `_cutlass_ir` extension, with zero overlapping files.
  flashinfer 0.6.16's `gdn_prefill` imports `cutlass.cute` at module load
  and vLLM 0.28 imports `gdn_prefill` eagerly, so with libs-base excluded
  every Qwen3.5 worker and every Blackwell vLLM worker died with
  `ModuleNotFoundError: No module named 'cutlass.cute'`. The mcore extra's
  own `nvidia-cutlass-dsl==4.5.2` pin moves to 4.6.2 so the training venv
  does not pick up the overlapping 4.5.x pair (flashinfer 0.6.8.post1
  accepts >=4.4.2). The trtllm fork still resolves 4.5.0 through
  tensorrt-llm; that venv is already unusable on torch 2.13 (see the PR).

* flash-attn: bound its source build with `MAX_JOBS=4`. No flash-attn 2.8.x
  wheel exists for torch >= 2.11, so the bump switched both architectures
  to an sdist build. flash-attn's setup.py sizes MAX_JOBS from the host's
  free memory (not the cgroup) and cpu_count()//2 and runs nvcc with
  --threads 4, which OOM-killed cicc on every nemo-ci runner
  (`ResourceExhausted`). 4x4 built cleanly on both architectures.

* nvidia-modelopt: c3b913b9 -> 613e5e8b. The old vLLM plugin registers a
  quant module on `fused_moe.layer.FusedMoE`, which 0.28 removed. ModelOpt
  swallows the AttributeError inside `import_plugin`, leaving the linear
  classes registered but the module absent from sys.modules; NeMo-RL's
  explicit re-import in `vllm_quant_patch.py` then re-runs the decorators
  and fails with `RowParallelLinear already registered`, killing every
  fakequant nvfp4 test. 5dde396bd (Aug 4) ported the plugin to
  `RoutedExperts`.

* numpy: cap the override at `<2.5`. vLLM 0.28 pins `numba==0.65.0`, whose
  metadata requires `numpy<2.5`; the `numpy>=2.1.0` override forced 2.5.1
  past it and numba refused to import in the vLLM venv ("Numba needs NumPy
  2.4 or less"). Resolves to 2.4.6, still above tensorrt-llm's `<2.4` cap.

Lock diff beyond marker churn: numpy 2.5.1 -> 2.4.6, cutlass-dsl 4.5.2
dropped, libs-base 4.5.0/4.6.0/4.6.2 and libs-core 4.6.0/4.6.2 added,
modelopt 0.46.0.dev86 -> 0.47.0rc1.dev36.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
(cherry picked from commit 5ae0cee)
… vLLM 0.28

Two vLLM-internal APIs that the fp8 rollout integration relies on changed
between 0.25.1 and 0.28.0. Both broke at worker construction on the
nightlies and were misreported by the error extractor as import errors.

* `make_fp8_moe_kernel` (now in `fused_moe/oracle/fp8.py`, still re-exported
  from `quantization/fp8.py`) dropped its `layer` kwarg; 0.25 only forwarded
  it to the FlashInfer TRTLLM experts. Passing it raised
  `TypeError: make_fp8_moe_kernel() got an unexpected keyword argument 'layer'`
  in every fp8 MoE test (qwen3.5-35B ep16tp2_fp8, moonlight fp8_e2e).

* The attention layer no longer carries `calculate_kv_scales`; dynamic scales
  are a KV-cache dtype now (`kv_cache_uses_per_token_head_scales`) and the
  static-scale branch keys off `is_quantized_kv_cache`. Our refit-friendly
  copy of `BaseKVCacheMethod.process_weights_after_loading` failed with
  `AttributeError: 'Attention' object has no attribute 'calculate_kv_scales'`
  (qwen3-8b fp8_kvcache). Re-mirror upstream 0.28: per-token-head dtypes
  short-circuit, the fp8 branch uses `is_quantized_kv_cache`, and the host
  `_k_scale_cpu`/`_v_scale_cpu` copies are refreshed on refit. Parameters are
  still not deleted so refit can keep updating them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
(cherry picked from commit 8318c80)
vLLM 0.28 (vllm-project/vllm#50411) calls the HF image processor with
`do_rescale=False, do_normalize=False` and re-applies both on the GPU in
the vision tower's dtype. The policy side normalizes the same images on
the CPU in fp32 through the same processor, and the nightly
token_mult_prob_error / gen_kl_error checks assume identical inputs on
both sides, so default `mm_device_do_normalize` to False and keep the
0.25 path. Verified on CPU with transformers 5.12.1 that the fused path
is exact in fp32 but differs by up to 0.019 (about 1.7% of the pixel
std) in bf16; upstream has also already shipped a silent-corruption fix
for the device path (vllm-project/vllm#55370). Users can opt back in via
policy.generation.vllm_kwargs.mm_device_do_normalize=true.

This is a parity fix, not a confirmed root cause: on the 0.28 nightlies
both Qwen2.5-VL-3B recipes (DTensor and Megatron) show token_mult_prob_error
of 2e2-2e4 against 1.02 on main with reward collapsing to 0.3, and the
same-image-path Qwen2.5-Omni audio recipe is unaffected. See the PR
description for the open investigation.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
(cherry picked from commit 170beee)
@copy-pr-bot

copy-pr-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added the Documentation Improvements or additions to documentation label Sep 10, 2026
yfw added a commit that referenced this pull request Sep 11, 2026
… find it

nemo-ci runs `docker buildx rm` / `docker buildx create` around every build
job, so the `trtllm-wheel-cache-*` cache mount lives exactly as long as one
build. The hermetic layer that compiles the wheel into that mount is shared
through the registry cache, though, so a rebuild that changes only NeMo-RL
source reuses the layer on a fresh builder, re-executes the release stage,
and dies at `[release 5/8]` with

    RuntimeError: TRT-LLM cached wheel is required but was not found at
    /root/.cache/trtllm-wheels/<key>. Refusing to compile TRT-LLM because
    TRTLLM_REQUIRE_CACHED_WHEEL=1.

on every runner (PR #4080 pass 2: 4 images x 3 attempts, all identical).
Retrying cannot help because no runner keeps the mount between jobs.

Have the hermetic stage set TRTLLM_WHEEL_CACHE_MIRROR_DIR so the backend
also writes the content-addressed wheel into /opt/trtllm_wheels inside the
layer, and have the release stage rsync that mirror back into the (possibly
empty) mount before the trtllm venv prefetch. The backend skips the mirror
copy when the file already exists, so the release layer no longer
duplicates the wheel it inherits from hermetic; the final image content is
unchanged (/opt/trtllm_wheels already held this wheel).

This changes the hermetic TRT-LLM step, so the next build recompiles the
wheel once; after that any source-only rebuild works on any runner.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw yfw added the CI:L2 Run doctests, unit tests, functional tests, and convergence tests label Sep 14, 2026
@yfw

yfw commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 037c678

yfw and others added 16 commits September 14, 2026 01:30
The second nightly pass showed that ModelOpt 613e5e8b (main) breaks the
Megatron side of the nvfp4 recipes while fixing the vLLM side:

* fakequant (`grpo-qwen3-30ba3b-4n8g-megatron-qa-nvfp4`):
  `KeyError: GroupedQuantizer(...)` from Megatron-LM's
  `DistributedDataParallel.disable_forward_pre_hook`, reached through
  `MegatronQuantPolicyWorker.__init__`. The per-expert `GroupedQuantizer`
  modules are new in ModelOpt #1550 (2ded5aba3, 2026-08-07) and are not
  in the DDP hook bookkeeping NeMo-RL iterates.
* real-quant (`grpo-nanov3-30ba3b-4n4g-megatron-qa-nvfp4-w4a16-real`):
  `CheckpointingException: Invalid access pattern for
  ShardedTensor(key='decoder.layers.1.mlp.experts.experts.16.linear_fc1.weight_quantizer._amax')`
  while saving the imported Megatron checkpoint; same per-expert quantizer
  layout.

5dde396bd (2026-08-04, "Fix vLLM 0.24+ compatibility") is the first commit
whose vLLM plugin survives vLLM 0.28 (the `FusedMoE` registration is behind
`_has_fused_moe_cls`) and it predates #1550 by 17 commits. Lock moves
nvidia-modelopt 0.47.0rc1.dev36 -> 0.47.0.dev25; nothing else changes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
(cherry picked from commit e776e5820b1e32a39bba88997dd2bbaa6e71f8d8)
…ert loader

On the second nightly pass the fakequant nvfp4 recipes got past worker
construction and then failed at the first refit with

  IPCWeightManifestError: weight load failed: AttributeError: Layer
  model.layers.1.mixer.experts has no parameter 'w13_input_quantizer._amax'

The Megatron side exports one `input_quantizer._amax` per expert projection
(`...experts.16.up_proj.input_quantizer._amax`) and the vLLM-side ModelOpt
MoE module owns a single fused `w13_input_quantizer` / `w2_input_quantizer`,
so the values fan in with `max`. vLLM 0.25 loaded those names through the
model-level parameter dict, which `_patch_named_parameters_to_include_buffers`
extends with quantizer buffers. vLLM 0.28's `AutoWeightsLoader` hands every
`experts.*` name to `RoutedExperts.load_weights`
(`vllm/model_executor/layers/fused_moe/routed_experts.py`), which rewrites the
name with the expert mapping (`experts.16.up_proj.` -> `experts.w13_`),
strips the layer prefix and resolves the remainder with a single
`getattr(self, param_name)`. A dotted buffer path cannot resolve that way.

Apply the same rewrite ourselves, walk the dotted path on the expert module,
fan the values in with `max`, and hand vLLM only the remaining weights. The
routing keys off `get_expert_mapping` + `layer_name`, the two attributes
vLLM's loader itself relies on, so it follows whatever prefix a model passes
(`routed_experts_prefix` or none). Non-MoE amax buffers (dense layers,
attention K/V) still take the existing path.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
(cherry picked from commit e7c1848282e399b8820b13866bc1d42534a6252e)
vLLM 0.29.0 keeps the same torch 2.13.0 / CUDA 13 stack as 0.28.0; the
only dependency changes are flashinfer-python/-cubin/-jit-cache
0.6.16.post3 → 0.6.18 (cu130 wheels for both arches) and a new
unconditional `instanttensor>=0.1.9` requirement. instanttensor is the
optional `load_format="instanttensor"` safetensors loader, only publishes
x86_64 wheels, and its sdist compiles libaio/liburing/boost; vLLM imports
it lazily and NeMo-RL never selects that load format, so an override
keeps the wheel on x86_64 and skips it on aarch64 (GB200) instead of
building it from source.

`uv lock --check` passes on the pristine parent and on the new lock; the
lock diff is limited to vllm, the three flashinfer packages and the new
instanttensor entry. transformers stays at the already-locked 5.12.1
(0.29 requires >=5.10.4).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
vLLM 0.29 ships vllm-project/vllm#53666 and #50969: RayWorkerV2 now
binds the torch.distributed TCPStore itself on a kernel-assigned port
and holds the socket (`self._dist_init_store = store`) until
init_process_group reuses it, so the probe/bind window that let the
MessageQueue take the same port is gone, and `_select_tcpstore_port`
no longer exists. `_patch_vllm_ray_executor_v2_tcpstore_port` therefore
found no anchor and logged "may fail with EADDRINUSE" on every worker
start.

Detect the upstream marker, log at info level and leave the file alone.
The port-arithmetic tests skip when the installed vLLM carries the
upstream fix; a new test pins the no-warn/no-edit behaviour against a
synthetic 0.29-style source.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
…e extension

vLLM 0.29 adds `WorkerBase.synchronize_device` (vllm-project/vllm#52914,
used by the pause-completion path) and `WorkerBase.init_worker` asserts
that a worker extension never carries an attribute the Worker already
has. `VllmInternalWorkerExtension.synchronize_device`, which the
sparse-delta refit invokes through `collective_rpc` to drain peers before
cleaning up staged batch files, therefore killed every vLLM engine at
init on the first 0.29 nightly:

    AssertionError: Worker class <class 'vllm.v1.worker.gpu_worker.Worker'>
    already has an attribute synchronize_device, which conflicts with the
    worker extension class <class '...VllmInternalWorkerExtension'>.

Rename the extension method to `synchronize_sparse_refit_device` (the
applier's own `synchronize_device` is untouched; it is not on the worker
class) and update the RPC caller and its test. Add a `vllm`-marked unit
test that mirrors vLLM's collision predicate over both extension classes
so the next upstream name clash fails in CI instead of on a GPU job.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
… find it

nemo-ci runs `docker buildx rm` / `docker buildx create` around every build
job, so the `trtllm-wheel-cache-*` cache mount lives exactly as long as one
build. The hermetic layer that compiles the wheel into that mount is shared
through the registry cache, though, so a rebuild that changes only NeMo-RL
source reuses the layer on a fresh builder, re-executes the release stage,
and dies at `[release 5/8]` with

    RuntimeError: TRT-LLM cached wheel is required but was not found at
    /root/.cache/trtllm-wheels/<key>. Refusing to compile TRT-LLM because
    TRTLLM_REQUIRE_CACHED_WHEEL=1.

on every runner (PR #4080 pass 2: 4 images x 3 attempts, all identical).
Retrying cannot help because no runner keeps the mount between jobs.

Have the hermetic stage set TRTLLM_WHEEL_CACHE_MIRROR_DIR so the backend
also writes the content-addressed wheel into /opt/trtllm_wheels inside the
layer, and have the release stage rsync that mirror back into the (possibly
empty) mount before the trtllm venv prefetch. The backend skips the mirror
copy when the file already exists, so the release layer no longer
duplicates the wheel it inherits from hermetic; the final image content is
unchanged (/opt/trtllm_wheels already held this wheel).

This changes the hermetic TRT-LLM step, so the next build recompiles the
wheel once; after that any source-only rebuild works on any runner.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
vLLM 0.29 moved `vllm/entrypoints/openai/engine/protocol.py` to
`vllm/entrypoints/serve/engine/protocol.py` (vllm-project/vllm#54492).
`VllmAsyncGenerationWorker.post_init_async` imported `ErrorResponse` from
the old path when bringing up the OpenAI-compatible server, so every
recipe that serves generation over HTTP (Gym / swe1 recipes) died on the
first 0.29 nightly with

    ModuleNotFoundError: No module named 'vllm.entrypoints.openai.engine'

Every other name the async worker imports from vLLM still resolves in
0.29.0, and the serving classes it subclasses changed only for this move.

Add a `vllm`-marked test that walks `nemo_rl/models/generation/vllm` with
`ast`, collects every `from vllm... import X` (they are mostly lazy,
method-local imports that no unit test executes) and resolves them against
the installed vLLM, so the next upstream module move fails in CI instead
of in a 16-node nightly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
vLLM 0.29's AutoWeightsLoader (vllm-project/vllm#51665) skips a tied
alias such as `lm_head.weight` and then asserts that its canonical
`model.embed_tokens.weight` was loaded in the *same* load_weights call:

    ValueError: 'lm_head.weight' was skipped because it is tied to
    'model.embed_tokens.weight' in Qwen2ForCausalLM, but
    'model.embed_tokens.weight' was not found in the checkpoint, so the
    tied weight is uninitialized.

Refit streams weights in transport-sized batches, so the two routinely
land in different calls and every tied-embedding model refit from a
DTensor/automodel policy died at its first weight update on the first
0.29 nightly (Qwen2.5-1.5B PPO, Gemma 4 E2B DAPO, ...).

The alias never loads anything (the loader skips it), so drop it before
the call. Use vLLM's own `_get_tied_embedding_params` so the alias set is
exactly what the loader skips, and map checkpoint names through the
model's `hf_to_vllm_mapper` first, as the loader does, so models whose
vLLM parameter names differ from the checkpoint (Gemma) are covered.
Applied to both the batched IPC/NCCL refit and the native reload_weights
path; the MTP drafter still receives the unfiltered policy stream. On a
vLLM without the helper the filter is a no-op, and that vLLM has no alias
check either.

A vllm-marked test pins the helper's existence so an upstream rename
cannot silently turn the filter off.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
…LLM >= 0.28

The nanov3 w4a16 real-quant recipe has failed since the vLLM 0.28 bump with

    ModelOpt layerwise reload is incomplete for 23 layer(s):
    model.layers.1.mixer.experts.routed_experts: 134701312/179601664 elements

i.e. exactly three quarters of every expert layer arriving, on 0.28 and 0.29
alike. Nemotron-H experts are non-gated (`ckpt_gate_proj_name="up_proj"`,
no up shard), and vLLM >= 0.28's `RoutedExperts.make_expert_params_mapping`
builds no fused gate/up mapping for that naming ("Unexpected gate/up
projection names: up_proj, . Fused gate/up mapping will be skipped").
`_batch_fused_modelopt_moe_weights` still emitted the non-gated w13 weight
and block scale as one batched 3-D tensor under `experts.0.up_proj`, which
therefore matched the per-expert entry for expert 0 and, being 3-D, took
`load_weights`' fused branch. That branch assumes a gate/up concatenation
and loads `chunk(2, dim=1)[expert_id]`: half of every expert's rows. Half
of w13 plus all of w2 is the 3/4.

Emit per-expert 2-D shards instead, the path the gated case already uses
and the one the initial disk load takes. w2 stays batched: its orientation
survives the fused branch's heuristic (last dim != hidden size) and it
loads correctly today (the gated w4a4 recipe passes).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
…ient

vLLM 0.29's `OpenAIServing._preflight` calls
`self.engine_client.check_admission(n)` before every response
(vllm-project/vllm#49445, `max_num_queued_reqs` / `max_num_queued_tokens`
admission control). `_AsyncLLMHTTPClient`, the wrapper that keeps HTTP
generation on the loop owning AsyncLLM request state, exposes an explicit
member surface by design, so on the first 0.29 nightly that got past the
`ErrorResponse` import every chat completion served to NeMo Gym 500ed with

    AttributeError: '_AsyncLLMHTTPClient' object has no attribute
    'check_admission'

and the Gym recipes died with `AsyncTrajectoryCollector aborting: 4
batch-worker failure(s) exceeded max_generation_failures=3`.

Forward it like the other status-only members (it reads scheduler config
and unfinished-request counters, so it does not need the engine loop) and
let vLLM's HTTP-mapped overflow errors propagate.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
…3 nvfp4 recipe

The recipe asserts `MegatronQuantPolicyWorker.*723 TensorQuantizers found
in model`, a line that only `mtq.print_quant_summary` emits, and the
Megatron quant worker only calls it when it quantizes from scratch. When
the cached quantized checkpoint (`<model>_modelopt_<cfg-hash>`, keyed on
the quant config and calibration settings but not on the ModelOpt
version) already exists on the cluster, the worker restores it instead and
ModelOpt prints only `Inserted 723 quantizers`. Both 0.29 nightly runs
took the restore path, trained fine (gen_kl 0.0017, reward 0.56) and then
failed on this grep; the two most recent `main` nightlies never got a
result for this recipe (checkout bounces), so the flake had no control.

Accept either wording so the assertion does not depend on the cluster's
cache state. The vLLM-side `720 TensorQuantizers found in model` check is
unchanged (that path always prints the summary).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
…ller

tools/install_audio_deps.sh installed `torchcodec==0.11.1` next to
`torchaudio==2.11.0` into the Megatron worker venv at test time. Per the
torchcodec compatibility table, 0.11 is built for torch 2.11 only; 0.12+
use the stable ABI for torch >= 2.11. The image now ships torch 2.13, so
the audio recipes were loading a torch-2.11 torchcodec against torch 2.13
(they passed on the 0.29 nightlies, but the pairing is unsupported).

Move to torchcodec 0.16.0 — the version Gym's docker/install_codec_deps.sh
already installs, so the two venvs agree — and keep torchaudio 2.11.0,
which is the final torchaudio release and carries no torch pin. Update
the audio-visual guide accordingly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
The audio-visual guide described tools/install_audio_deps.sh as pinning
`torchcodec>=0.3.0`; the script pins an exact version, now 0.16.0 to match
the torch 2.13 image (see the previous commit, whose message mentioned this
guide update ahead of it landing).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
…rt amax

`route_moe_input_quantizer_amax` matched incoming fakequant amax names
against the expert module's vLLM `layer_name` and expert mapping, but refit
sends checkpoint names and vLLM only renames them inside `load_weights`
via the model's `hf_to_vllm_mapper`. For Nemotron-H that mapper turns
`backbone.` into `model.`, so
`backbone.layers.N.mixer.experts.E.up_proj.input_quantizer._amax` never
matched the module prefix `model.layers.N.mixer.experts.`, fell through to
`RoutedExperts.load_weights`, and the nano3 fakequant recipe died on

    AttributeError: Layer model.layers.1.mixer.experts has no parameter
    'w13_input_quantizer._amax' for checkpoint weight
    'model.layers.1.mixer.experts.0.up_proj.input_quantizer._amax'

on the first 0.29 nightly that reached its refit (pass 5). Apply the
mapper to the name before matching, exactly as AutoWeightsLoader does, and
pass the model's mapper from the quant backend. Names the mapper drops
pass through untouched. Two tests cover the mapped and unmapped cases.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
…ibute

VllmInternalWorkerExtension has no __init__ (vLLM mixes it into its
Worker), so pyrefly flags the one-shot `_logged_tied_alias_drop` flag
set in `_without_tied_embedding_aliases` as implicitly defined. Annotate
it the way the class's other lazily created attributes are, which is
what the lint check on the PR was failing on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw

yfw commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 6ec3dd6

The lint workflow requires every pyrefly-clean file under nemo_rl/ to be
listed in pyrefly.toml's project-includes; the new amax-routing module
was not, which failed the Lint check.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
yfw and others added 2 commits September 18, 2026 17:45
- `tests/unit/L0_Unit_Tests_Vllm_*.sh` first run the vLLM test files in the
  base venv, where vLLM is not installed, and rely on the `vllm` marker to
  deselect anything that imports it. Six tied-embedding-alias tests from
  e1c16e8 import `vllm_backend` in their bodies but were unmarked, so each
  shard failed on the first of them with `ModuleNotFoundError: No module
  named 'vllm'`. They now carry `@pytest.mark.vllm` like their neighbours.
- `test_vllm_chat_template_wiring.py` stubs the vLLM surface the OpenAI
  server setup imports; it lacked `vllm.entrypoints.serve.engine.protocol`,
  where ae6507c imports `ErrorResponse` on vLLM 0.29, so all five wiring
  tests failed in the same base-venv pass. The fake tree now provides it.
- torch 2.13 removed the named-tensor API, and with it `Tensor.names`; the
  Mooncake TQ checkpoint adapter's fast-path guard read
  `produced.names == (None, None)` and raised `AttributeError` in
  `_physical_keys`, failing `test_tq_mooncake_checkpoint.py`. The guard now
  goes through `_has_no_named_dims`, which treats a missing attribute as
  "no named dimensions" (torch 2.11 reported `(None, None)` for the same
  tensor).

Verified in the worktree's torch-2.13 venv without vLLM: the shards' first
pass over the vLLM test files and `tests/unit/data_plane` are green apart
from the GPU-only generation tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
Conflicts: pyproject.toml (kept this branch's `numpy>=2.1.0,<2.5` override and
took main's PyAV exclusion), uv.lock (regenerated from this branch's lock; the
only changes are main's mooncake 0.3.11.post1 -> 0.3.13.post1 and wandb
0.28.1 -> 0.30.0 bumps, PyAV removed, huggingface-hub deduplicated to 1.32.0).

Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw

yfw commented Sep 19, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test fc89153

Follow-up to 31dc3a8. The stand-in guard sat in the two callers of
`_tied_embedding_aliases`, so tests that monkeypatch the detector to return
aliases for a `SimpleNamespace` model (`test_prepare_reload_weight_iterator_drops_tied_aliases`
and friends, now run in the `vllm`-only pass) saw the guard skip the filter
and keep `lm_head.weight`. The guard now lives in `_tied_embedding_aliases`
itself: a `None` model or one without `named_modules` yields no aliases,
while a patched detector is honoured.

Also adds vLLM 0.29's `vllm.entrypoints.serve.engine.protocol` to the fake
vLLM tree in `test_vllm_generation.py`, the last stub that still only knew
the 0.25 `openai.engine.protocol` location; the two OpenAI-server tests in
the base-venv pass failed on the `ErrorResponse` import.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw

yfw commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 000cd24

Brings #3566 (end-to-end TE NVFP4 training with per-token vLLM rollout) and
three smaller commits onto the vLLM 0.29 bump.

Conflicts and resolution:
- pyproject.toml: main bumped vLLM to 0.26.0 / flashinfer 0.6.14 /
  nvidia-cutlass-dsl 4.6.0 and pinned quack-kernels==0.6.1; this branch keeps
  vLLM 0.29.0 / flashinfer 0.6.18 / cutlass-dsl 4.6.2 and takes no quack pin
  (vLLM 0.29 requires quack-kernels==0.6.4). TransformerEngine 2.18.0 (PyPI)
  and ModelOpt 43fd41a58 are taken from main: that ModelOpt revision is a
  release cherry-pick that carries #2054's RoutedExperts vLLM plugin and does
  not contain #1550's GroupedQuantizer, so the pin note is kept and reworded.
  flashinfer-cubin index and the cutlass 4.5.x dependency-metadata are main's.
- uv.lock: regenerated from this branch's lock; only ModelOpt (0.46.0 @
  43fd41a5), TransformerEngine 2.15.0+git -> 2.18.0 (+ -cu13/-torch wheels,
  nvdlfw-inspect) and the dropped cutlass libs-base 4.5.0 entry changed.
- quantization/fp8.py: main's refactor of the module lookup into
  quantization/utils.py::resolve_module_from_param_name is taken; the
  ambiguous-shard fix from 28beca5 (vLLM 0.29 lists `wkv` under two fused
  modules) is ported into the new resolver, and its tests call the renamed
  public get_module_from_param_name.
- modelopt vllm_quant_backend.py and its test: main's per-expert non-gated
  W13 emission (superset of a3aa6cd) is taken.

Also fixes the pyrefly error from 000cd24: `_tied_embedding_aliases` now
accepts `torch.nn.Module | None`.

Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw

yfw commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test d104085

…server check

vLLM 0.29 added `completion_tokens_details` to the OpenAI `UsageInfo`
response model, so `test_vllm_http_server`'s exact comparison against the
0.25-shaped expected `usage` block failed on the extra key. Drop it in the
test's version-normalisation step next to the other fields vLLM may or may
not include.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw

yfw commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 8e97d91

… vLLM 0.29 / torch 2.13

Three failures from the first full CI + nightly pass on the merged head:

- Async checkpoint saves died at the first save with
  `DistNetworkError: ... EADDRINUSE` inside torch's process-based DCP
  checkpointer (`_async_process_executor`): rank 0 probes a port with
  `get_free_port()` and the daemons then rendezvous on it with a fresh GLOO
  group, but the port was taken again before the daemon bound it. It hit
  every DTensor save in the run (L1 GRPO resume, the converter round-trip
  test, the llama3.2-1b fsdp2tp2 tq_mooncake nightly recipe) after the
  Automodel bump made daemon creation synchronous across ranks. The
  Automodel checkpoint manager now opts the daemons into torch's
  `DCP_USE_PREFIX_STORE=1` mode when `MASTER_ADDR`/`MASTER_PORT` are
  present, so they join the training process group's TCPStore under a
  prefix and bind nothing. Unit-tested.
- `nvfp4_pertoken.py` (from #3566) called `make_nvfp4_moe_kernel(...,
  layer=layer)`; vLLM 0.29 dropped that parameter (it already takes
  `per_token_activation`), so the new `te_nvfp4_pertoken_quick` GB200 recipe
  died with `TypeError: unexpected keyword argument 'layer'`.
- `test_decode_vs_prefill.sh` OOMed on GB200 at the first MoE forward: with
  `gpu_memory_utilization=0.8` vLLM 0.29 sized a 93 GiB KV cache for
  Nemotron-3-Nano-30B (TP2) and the FlashInfer TRT-LLM BF16 MoE kernel then
  could not allocate its 4 GiB workspace (the same test is memory-marginal
  on `main`'s H100 runners too). The diagnostics tool gains a
  `--gpu-memory-utilization` flag and the functional test passes 0.7.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw

yfw commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 0cc6fab

Brings #4213 (rebuild the PPO critic optimizer on a warm start) and #4206
(strip staged token arrays from vLLM capture responses). No conflicts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw

yfw commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 51d7ee4

… gated-delta-net training stops using the TileLang backward kernel

`grpo-qwen3.5-35ba3b-6n4g-async-1off-bf16-trtllm` (GB200) died on every run of this
branch at step 1 of `MegatronPolicyWorker.train()` with
`torch.AcceleratorError: CUDA error: misaligned address` raised from
`fla/ops/common/backends/tilelang/chunk_bwd.py::chunk_bwd_dqkwg_tilelang`, the
gated-delta-rule backward. `main`'s Sept 20 and Sept 21 nightlies pass the same
recipe (20/20), and their Megatron worker logs contain no TileLang compiles at all.

The difference is the flash-linear-attention version in the lockfile. `main`
resolves fla-core / flash-linear-attention 0.5.2; this branch's lock still carried
0.5.1 because every `main` merge kept our `uv.lock` and re-locked without
upgrading transitive packages. In fla-core 0.5.1 `TileLangBackend.is_enabled()`
is the base-class default, so the TileLang kernels are used wherever `tilelang`
imports (the `mcore` extra installs it for the fused DSA kernels), including
Blackwell. fla-core 0.5.2 (fla-org/flash-linear-attention#640) turns TileLang on
by default only on Hopper with Triton >= 3.4, keeps Triton the default everywhere
else, and also fixes the kernel's value-head indexing; on GB200 that means the
Triton backward that `main` runs.

`uv lock --upgrade-package flash-linear-attention --upgrade-package fla-core`;
the lock moves only those two entries (0.5.1 -> 0.5.2). `uv lock --check` and
`tests/unit/test_dependency_pins.py` pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw

yfw commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 5200efa

…ution

Each `main` merge on this branch kept our `uv.lock` and re-locked without
upgrading, while `main` had relocked with upgrades, so ~150 transitive
packages resolved older here than on `main` (pandas 2.3 vs 3.0, starlette,
xgrammar, tensordict, nccl4py, cuda-pathfinder, ...). One of them, fla-core,
caused the GB200 regression fixed in 5200efa; the rest are brought to parity
here so the nightly compares the intended bumps and nothing else.

`uv lock --upgrade`, then `uv lock --upgrade-package ray==2.56.1` to keep Ray at
the version `main`'s nightlies run (the upgrade would have moved it to 2.58.0).
103 packages move; none of the pinned ones (vLLM 0.29.0, torch 2.13.0 / 2.11.0
per extra, triton, flashinfer 0.6.18, cutlass-dsl 4.6.2, TE 2.18.0, ModelOpt
43fd41a58, sglang, TRT-LLM, DeepEP, nixl) change. The only packages that still
resolve below `main` are the deliberate ones: numpy 2.4.6 (numba 0.65 needs
<2.5), llguidance 1.7.6 (vLLM 0.29's range) and the flash-attn sdist.
`uv lock --check`, taplo and `tests/unit/test_dependency_pins.py` pass; the
per-extra torch/triton exports are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw

yfw commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test be440de

@copy-pr-bot

copy-pr-bot Bot commented Sep 21, 2026

Copy link
Copy Markdown

/ok to test be440de

@yfw, there was an error processing your request: E2

See the following link for more information: https://docs.gha-runners.nvidia.com/cpr/e/2/

@yfw

yfw commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test be440de

…m the device size

0cc6fab lowered the test's gpu_memory_utilization to 0.7 so the FlashInfer TRT-LLM
MoE workspace fits next to the KV cache on 186 GiB GB200 parts. On 80 GiB H100 parts
that is too low: Nemotron-3-Nano-30B at TP2 takes 29.5 GiB per rank and vLLM's
profiling peak another ~28 GiB, so 0.7 leaves no KV cache at all and the engine fails
with `No available memory for the cache blocks` (L1_Functional_Tests_Other_1 on
be440de). Use 0.7 above 120 GB of device memory and the old 0.8 below it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw

yfw commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 8d1f98a

… the router on vLLM 0.29

Router replay for the per-token NVFP4 rollout (`te_nvfp4_pertoken_quick`) worked
on vLLM 0.26 because NeMo-RL's `_patch_vllm_moe_routed_experts_capture` fires
`router.select_experts` on the monolithic fused-MoE branch: vLLM 0.26 bound the
`RoutedExpertsCapturer` callback to every layer's router, monolithic kernels
never call the router, and the patch made them do so when capture is on.

vLLM 0.29 changed the binding (`bind_routed_experts_capturer`): a monolithic
kernel must report `supports_routing_replay_capture()`, the callback is bound to
the kernel's experts *object*, the FlashInfer launch fills `routing_replay_out`
itself, and any other monolithic kernel is rejected with ValueError. The router
hook is only set for modular kernels, so NeMo-RL's runner patch became inert for
the TRT-LLM NVFP4 kernel. Two problems follow for the per-token method:

- `ModelOptNvFp4PerTokenFusedMoE.process_weights_after_loading` rebuilds the
  kernel on every cold and warm refit, so the experts-bound capture function is
  gone after the first weight update and every later rollout would return
  all-zero routes to Megatron's router replay.
- The per-token FlashInfer launch with a replay buffer attached is the one
  combination this recipe exercises that 0.26 never ran; on the fixed head the
  recipe dies in the first post-refit generation with `CUDA error: an illegal
  memory access` (nemo-ci job 450220649).

The per-token method now wraps its experts class (`host_captured_experts_cls`)
so it reports no in-kernel capture, and a new vLLM source patch
(`_patch_vllm_routed_experts_capture_router_fallback`) makes the 0.29 binder fall
back to `router.set_capture_fn` for such kernels instead of raising. Capture then
goes through the router hook the runner patch already fires, survives kernel
rebuilds, and the kernel is launched without `routing_replay_out`, exactly as on
0.26. Kernels that support in-kernel capture keep using it. Both patches are
`required` together. Unit tests: patch anchor against the installed vLLM,
idempotency, fail-closed, an executable check of the patched binder for both
kernel kinds, and the experts-class wrapper.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw

yfw commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test af7d9e0

@yfw

yfw commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

Recipes that pass on both this PR (final lock be440de: H100 lane 69151691, GB200 lane 69183689) and on main's Sept 20 / 21 nightlies (139):

H100 (108)
  • llm_dapo_gemma4_12b_it_2n8g_fsdp2_automodel
  • llm_distillation_qwen3_1_7b_1n8g_megatron_qa_nvfp4
  • llm_distillation_qwen3_32b_to_1_7b_base_1n8g_fsdp2tp1_v1
  • llm_distillation_qwen3_32b_to_1_7b_base_1n8g_megatron_tp2pp2cp2_pack
  • llm_dpo_llama3_1_8b_instruct_4n8g_megatrontp2pp2_quick
  • llm_dpo_llama3_2_1b_instruct_1n8g_fsdp2tp1_v2
  • llm_gdpo_qwen2_5_1_5b_1n8g_gym_multireward
  • llm_grpo_cispo_mm1_async_lag1_highoffpolicy_qwen3_30ba3b_3n8g_megatron_cispo
  • llm_grpo_deepscaler_1_5b_16K
  • llm_grpo_deepscaler_1_5b_24K
  • llm_grpo_deepscaler_1_5b_8K
  • llm_grpo_deepscaler_1_5b_8K_tq_simple
  • llm_grpo_gemma3_1b_it_1n8g_fsdp2tp1
  • llm_grpo_gspo_deepscaler_1_5b_8K
  • llm_grpo_gspo_deepscaler_1_5b_8K_tq_simple
  • llm_grpo_llama3_1_8b_instruct_1n8g_megatron_fp8_rollouts_v3
  • llm_grpo_llama3_1_8b_instruct_1n8g_megatron_fp8_rollouts_v3_tq_simple
  • llm_grpo_llama3_1_8b_instruct_2n8g_async_1off_single_controller_streaming2
  • llm_grpo_llama3_1_8b_instruct_2n8g_fsdp2tp1_noncolocated
  • llm_grpo_llama3_1_8b_instruct_2n8g_fsdp2tp1_noncolocated_tq_simple
  • llm_grpo_llama3_1_8b_instruct_2n8g_megatron_fp8_e2e
  • llm_grpo_llama3_1_8b_instruct_2n8g_megatron_fp8_e2e_tq_mooncake
  • llm_grpo_llama3_2_1b_instruct_1n8g_fsdp2tp1_v3
  • llm_grpo_llama3_2_1b_instruct_1n8g_fsdp2tp1_v3_tq_simple
  • llm_grpo_llama3_2_1b_instruct_1n8g_fsdp2tp2_temp0_8_topp0_9_topk50
  • llm_grpo_llama3_2_1b_instruct_1n8g_megatron
  • llm_grpo_llama3_2_1b_instruct_1n8g_megatron_generation
  • llm_grpo_llama3_2_1b_instruct_1n8g_megatron_generation_tq_mooncake
  • llm_grpo_llama3_2_1b_instruct_1n8g_megatron_temp0_8_topp0_9_topk50
  • llm_grpo_llama3_2_1b_instruct_1n8g_megatron_tq_simple
  • llm_grpo_math_qwen3_30ba3b_megatron_tp4_32k
  • llm_grpo_moonlight_16b_automodel_1n8g_ep8
  • llm_grpo_nano_v2_12b_1n8g_megatron
  • llm_grpo_nano_v2_12b_2n8g_fsdp2tp1
  • llm_grpo_nanov3_30BA3B_1n8g_fsdp2_tq_mooncake_v2
  • llm_grpo_nanov3_30BA3B_1n8g_fsdp2_v2
  • llm_grpo_nanov3_30BA3B_2n8g_fsdp2_lora
  • llm_grpo_nanov3_30BA3B_2n8g_megatron_generation_noncolocated_async_gym
  • llm_grpo_nanov3_30BA3B_2n8g_megatron_lora
  • llm_grpo_nanov3_30BA3B_2n8g_megatron_pack_cp
  • llm_grpo_nanov3_30BA3B_2n8g_megatron_pack_cp_tq_simple
  • llm_grpo_nemotron3_super_120BA12B_16n8g_automodel_ep8_v2
  • llm_grpo_nemotron3_super_120BA12B_16n8g_megatron
  • llm_grpo_qwen2_5_1_5B_4n8g_megatron_yarn_256k
  • llm_grpo_qwen2_5_32b_32n8g_fsdp2tp8_actckpt_v3
  • llm_grpo_qwen2_5_math_1_5b_instruct_1n8g_fsdp2tp1_v3
  • llm_grpo_qwen2_5_math_1_5b_instruct_1n8g_fsdp2tp1_v3_tq_simple
  • llm_grpo_qwen2_5_math_1_5b_instruct_1n8g_megatron_generation_noncolocated_single_controller_async_lag4
  • llm_grpo_qwen2_5_math_1_5b_instruct_1n8g_megatron_generation_single_controller_async_lag4
  • llm_grpo_qwen2_5_math_1_5b_instruct_1n8g_megatron_single_controller_sync
  • llm_grpo_qwen2_5_math_1_5b_instruct_1n8g_megatron_single_controller_sync_tq_mooncake
  • llm_grpo_qwen2_5_math_1_5b_instruct_2n8g_fsdp2tp1_sglang
  • llm_grpo_qwen2_5_math_1_5b_instruct_2n8g_megatrontp1_sglang_noncolocated
  • llm_grpo_qwen3_1_7b_1n8g_megatron_eagle3
  • llm_grpo_qwen3_1_7b_1n8g_megatron_eagle3_tq_mooncake
  • llm_grpo_qwen3_1_7b_1n8g_megatron_super_swe1
  • llm_grpo_qwen3_1_7b_2n8g_megatron_super_rlhf
  • llm_grpo_qwen3_1_7b_6n8g_megatron_super_rlvr
  • llm_grpo_qwen3_30ba3b_10n8g_megatron_cp2_r3_async
  • llm_grpo_qwen3_30ba3b_10n8g_megatron_cp2_r3_async_single_controller
  • llm_grpo_qwen3_30ba3b_2n8g_megatron_fused_linear_logprobs
  • llm_grpo_qwen3_30ba3b_4n8g_megatron_zmq_deltaweight_noncolocated
  • llm_grpo_qwen3_30ba3b_8n8g_megatron_cp2_r3
  • llm_grpo_qwen3_30ba3b_8n8g_megatron_cp2_r3_tq_simple
  • llm_grpo_qwen3_30ba3b_thinking_swe1_16n8g_megatron_cp2_r3_async_gym
  • llm_grpo_qwen3_5_35ba3b_2n8g_megatron_ep16tp2_fp8
  • llm_grpo_qwen3_5_35ba3b_2n8g_megatron_ep16tp2cp2
  • llm_grpo_qwen3_5_9b_1n8g_megatron
  • llm_grpo_qwen3_5_9b_1n8g_megatron_fp8
  • llm_grpo_qwen3_8B_base_1n8g_fsdp2_lora
  • llm_grpo_qwen3_8B_base_1n8g_fsdp2_lora_tq_mooncake
  • llm_grpo_qwen3_8b_base_1n8g_fp8_kvcache_megatron
  • llm_grpo_qwen3_8b_base_1n8g_megatron_lora
  • llm_grpo_qwen3_8b_base_dapo_2n8g_long_megatron_qa_nvfp4_w4a16
  • llm_mopd_qwen3_1_7b_3n8g_megatron_pack_single_controller
  • llm_mopd_qwen3_1_7b_3n8g_megatron_pack_single_controller_fullvocab
  • llm_ppo_qwen2_5_1_5b_gsm8k_1n8g_automodel_noncolocated
  • llm_ppo_qwen2_5_1_5b_gsm8k_1n8g_automodel_noncolocated_async
  • llm_ppo_qwen2_5_1_5b_gsm8k_1n8g_automodel_valuetp2sp
  • llm_ppo_qwen2_5_1_5b_gsm8k_1n8g_megatron_valuetp2sp_dynbatch
  • llm_ppo_qwen2_5_1_5b_gsm8k_1n8g_megatron_valuetp2sp_pp2cp2_pack
  • llm_ppo_qwen2_5_1_5b_gsm8k_2n8g_megatron_valuetp2sp_dynbatch_noncolocated
  • llm_ppo_qwen2_5_1_5b_gsm8k_2n8g_megatron_valuetp2sp_dynbatch_noncolocated_async
  • llm_prorlv2_qwen2_5_math_1_5b_instruct_1n8g_fsdp2tp1_v2
  • llm_sft_llama3_1_8b_1n8g_fsdp2tp1_lora
  • llm_sft_llama3_1_8b_1n8g_fsdp2tp2
  • llm_sft_llama3_1_8b_1n8g_fsdp2tp4_dynamicbatch
  • llm_sft_llama3_1_8b_1n8g_megatron
  • llm_sft_llama3_1_8b_1n8g_megatron_lora
  • llm_sft_llama3_1_8b_1n8g_megatron_seqpack
  • llm_sft_llama3_2_1b_1n8g_fsdp2tp1_v3
  • llm_sft_llama3_2_1b_2n8g_hsdp
  • llm_sft_nanov3_30BA3B_2n8g_fsdp2
  • llm_sft_nanov3_30BA3B_2n8g_fsdp2_lora
  • llm_sft_qwen2_5_32b_4n8g_fsdp2tp8sp_actckpt_v3
  • llm_sft_qwen2_5_math7b_1n8g_megatron_fused_linear_logprobs
  • llm_sft_qwen2_5_math7b_2n8g_megatron
  • vlm_vlm_grpo_gemma4_e4b_geo3k_1n8g_automodel
  • vlm_vlm_grpo_nemotron_omni_30ba3b_clevr_1n8g_automodel_ep8_v2
  • vlm_vlm_grpo_nemotron_omni_30ba3b_clevr_2n8g_megatron_tp8ep8_v1
  • vlm_vlm_grpo_nemotron_omni_30ba3b_clevr_2n8g_megatron_tp8ep8_v1_tq_mooncake
  • vlm_vlm_grpo_nemotron_omni_30ba3b_mmpr_4n8g_automodel_ep8_v1
  • vlm_vlm_grpo_qwen2_5_omni_3b_avqa_1n8g_megatron_v1
  • vlm_vlm_grpo_qwen2_5_omni_7b_audiomcq_1n8g_megatron_v1
  • vlm_vlm_grpo_qwen2_5_omni_7b_intent_1n8g_megatron_v1
  • vlm_vlm_grpo_qwen2_5_vl_3b_instruct_clevr_1n8g_dtensor2tp1_v1
  • vlm_vlm_grpo_qwen2_5_vl_3b_instruct_clevr_1n8g_megatrontp2_v1
  • vlm_vlm_grpo_qwen3_omni_30ba3b_audiomcq_4n8g_megatron_v1
GB200 (31)
  • llm_distillation_nano3_30ba3b_4n4g_megatron_qa_nvfp4_modelopt_spec
  • llm_distillation_qwen3_32b_to_1_7b_base_1n4g_fsdp2tp1_v1
  • llm_distillation_qwen3_32b_to_1_7b_base_1n4g_megatron_tp1pp2cp2_pack
  • llm_dpo_llama3_1_8b_instruct_4n4g_megatrontp1pp2_quick
  • llm_dpo_llama3_2_1b_instruct_1n4g_fsdp2tp1_v2
  • llm_grpo_deepscaler_1_5b_1n4g_8K
  • llm_grpo_gemma3_1b_it_1n4g_fsdp2tp1
  • llm_grpo_llama3_1_8b_instruct_2n4g_async_1off_single_controller_streaming2
  • llm_grpo_llama3_1_8b_instruct_2n4g_fsdp2tp1_noncolocated
  • llm_grpo_llama3_2_1b_instruct_1n4g_megatron
  • llm_grpo_llama3_2_1b_instruct_1n4g_megatron_generation
  • llm_grpo_nano_v2_12b_1n4g_megatron
  • llm_grpo_nano_v2_12b_2n4g_fsdp2tp1
  • llm_grpo_nanov3_30BA3B_4n4g_megatron_generation_noncolocated_async_gym
  • llm_grpo_nanov3_30BA3B_4n4g_megatron_generation_noncolocated_mxfp8_rollouts_packed_refit
  • llm_grpo_qwen2_5_0_5b_1n4g_megatron_trtllm_noncolocated_async
  • llm_grpo_qwen2_5_7b_instruct_4n4g_fsdp2tp2_v3
  • llm_grpo_qwen2_5_math_1_5b_instruct_1n4g_fsdp2tp1_v3
  • llm_grpo_qwen2_5_math_1_5b_instruct_1n4g_megatron_generation_noncolocated_single_controller_async_lag4
  • llm_grpo_qwen2_5_math_1_5b_instruct_1n4g_megatron_generation_single_controller_async_lag4
  • llm_grpo_qwen2_5_math_1_5b_instruct_2n4g_megatrontp1_sglang_noncolocated
  • llm_grpo_qwen3_1_7b_1n4g_megatron_eagle3
  • llm_grpo_qwen3_5_35ba3b_6n4g_async_1off_bf16_trtllm
  • llm_sft_llama3_2_1b_1n4g_fsdp2tp1_v3
  • llm_sft_nanov3_30BA3B_2n4g_fsdp2
  • llm_sft_nanov3_30BA3B_2n4g_fsdp2_lora
  • llm_sft_qwen2_5_math7b_2n4g_megatron
  • vlm_vlm_grpo_nemotron_omni_30ba3b_clevr_8n4g_megatron_generation_v1
  • vlm_vlm_grpo_nemotron_omni_30ba3b_clevr_8n4g_megatron_single_controller_async_v1
  • vlm_vlm_grpo_qwen2_5_vl_3b_instruct_clevr_1n4g_dtensor2tp1_v1
  • vlm_vlm_grpo_qwen2_5_vl_3b_instruct_clevr_1n4g_megatrontp1_v1

@yfw yfw left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Team review: PR #4080, vLLM 0.26 → 0.29 and torch 2.11 → 2.13

Reviewed at head af7d9e0 by 8 agents (RL codebase, vLLM upstream, torch/packaging, bug finder, design, tests, description/threads, devil's advocate). No correctness bug was found in the code. Inline: one CI-coverage gap that should be fixed here (the three new DCP_USE_PREFIX_STORE tests never run), a handful of low-severity test/doc hardening items with committable blocks, and a description refresh.

Verified against the pinned sources (vLLM v0.29.0 98dff2a8, torch v2.13.0 cf30153c / v2.11.0 70d99e99): all ten source-patch anchors occur exactly once in 0.29 and every patch was executed against a scratch copy of its target file; the TCPStore patch takes the "upstream fixed" branch; all five injected worker extensions are collision-free against Worker/WorkerBase; 239 from vllm... import targets across the repo resolve; check_admission, make_fp8_moe_kernel, make_nvfp4_moe_kernel, _get_tied_embedding_params, SKIP_TENSORS/SKIP_LOAD_TENSORS, the KV-scale mirror and the DeepSeek V4 hook shadow (every exit path) match 0.29 line by line; the tied-alias failure and its fix were reproduced on CPU with real vLLM classes; the amax routing was probed with vLLM's real expert-mapping strings; the CUDA-IPC 'c' payload is byte-identical between the two torch tags and the 2.11 consumer checks version <= own; DCP_USE_PREFIX_STORE and the MASTER_ADDR/MASTER_PORT assert hold in torch 2.13; the lock is below main only for numpy, llguidance and the flash-attn local label (100 packages above, 399 equal).

Local runs (this host has no usable CUDA driver, so GPU-gated tests are unverified). --vllm-only over tests/unit/models/generation in a venv built from this lock's vllm extra: 415 passed, 7 skipped, 8 environment-only failures; every test this PR adds passed. No-vLLM pass (the L0 shards' mode): generation 506 passed, utils 486, data_plane 497. pre-commit run --all-files: clean. 55 mutants were run against the PR's new tests; every pinned fix went red except the vacuities called out inline.

Upstream root causes worth filing (the workarounds are correct, keep them):

  • vLLM: DeepseekV4ForCausalLM.load_weights ends with self.process_weights_after_loading() on every call (model.py#L1813-L1816) although the loader already runs the model-level hook (model_loader/utils.py#L145-L146); per-call invocation breaks partial/streamed loads (this PR's Cannot copy out of meta tensor).
  • vLLM: AutoWeightsLoader._check_skipped_aliases requires a tied alias and its canonical to arrive in the same load_weights call (utils.py#L442-L470), which no batched weight-update client can guarantee.

FYI, no action. all_gather_single does not exist on torch 2.11, which the sglang/trtllm venvs (also installing nemo_rl) now pin; its only caller BatchedDataDict.all_gather has no production call site, and the rename does remove a per-call FutureWarning on 2.13. check_admission is a no-op today (queue limits default None). The ambiguous-shard detection in quantization/utils.py is generic but only DeepSeek V4 resolves it; four other 0.29 models share the shape and keep the pre-existing bf16-refit fallback (tracking issue at most).

Optional test hardening (verified candidates, not staged): an engine_client member sweep for _AsyncLLMHTTPClient; a WeightsMapper._map_name pin; an IPC tuple-position pin against torch's rebuild_cuda_tensor signature; the mapper hand-off at the tied-alias call sites; RPC caller/callee parity for synchronize_sparse_refit_device; the <= boundary in normalize_cuda_ipc_handle; || true on the nvidia-smi probe in test_decode_vs_prefill.sh; the remaining three injected worker extensions in test_vllm_worker_extension_names.py. Available on request.

Generated by Claude Code

Comment thread tests/unit/models/automodel/test_automodel_checkpoint.py Outdated
Comment thread tests/unit/models/automodel/test_automodel_checkpoint.py
Comment thread tests/unit/models/automodel/test_automodel_checkpoint.py
Comment thread tests/unit/models/generation/test_vllm_nvfp4_pertoken.py
Comment thread docker/Dockerfile Outdated
Comment thread tests/unit/models/generation/test_deepseek_v4_fp8.py
Comment thread nemo_rl/models/generation/vllm/patches.py Outdated
Comment thread nemo_rl/models/generation/vllm/quantization/fp8.py
Comment thread pyproject.toml
Comment thread docs/guides/dynamo-generation.md
yfw and others added 2 commits September 21, 2026 23:45
Addresses the first review round on #4080.

- tests/unit/models/automodel/test_automodel_checkpoint.py: the three
  `test_init_checkpointer_*` tests now carry `@pytest.mark.automodel`, so the
  Automodel shard collects them (the Models shards module-skip this file
  without nemo_automodel); the first one records the absence of
  `DCP_USE_PREFIX_STORE` before `init_checkpointer` writes it, so the value no
  longer leaks past teardown.
- tests/unit/models/generation/test_vllm_nvfp4_pertoken.py: a test drives
  `ModelOptNvFp4PerTokenFusedMoE.process_weights_after_loading` and binds the
  recorded `make_nvfp4_moe_kernel` kwargs to the installed vLLM's signature,
  pinning both the dropped `layer=` kwarg and the host-captured experts class.
- tests/unit/models/generation/test_vllm_import_targets.py: the walk also
  covers `nemo_rl/models/generation/dynamo` and
  `nemo_rl/modelopt/models/generation`, whose lazy `from vllm...` imports no
  unit test executes either.
- tests/unit/models/generation/test_deepseek_v4_fp8.py: pins that vLLM ships
  `SKIP_LOAD_TENSORS` and `SKIP_TENSORS` as two distinct sets, so a rename
  cannot silently fall back to the single-set (double conversion) shape.
- nemo_rl/utils/cuda_ipc.py: `normalize_cuda_ipc_handle(handle: Any) -> Any`;
  the docstring says what `'e'` handles mean for the torch split (the trainer
  emits them under `PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True`, and a
  torch 2.11 consumer still rejects them).
- pyproject.toml: the instanttensor override keeps the aarch64 skip but states
  the real reason; 0.2.0 ships aarch64 wheels, so it is a choice, not a
  wheel-availability limit.
- docker/Dockerfile: the release stage reads the TRT-LLM wheel from the
  mirror; it no longer re-seeds the cache mount.
- patches.py / test_vllm_tcpstore_port.py: `RayWorkerProc`, the class that
  owns `create_dist_init_method` in vLLM 0.29 (`RayWorkerV2` does not exist).
- fp8.py: `process_weights_after_loading_moe`'s docstring no longer names the
  `layer=` kwarg 0.29 removed.
- docs: the two remaining "vLLM 0.25.1" statements in the GLM-5 and Nemotron
  Omni guides.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw

yfw commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test e222247

The transitive relock in be440de moved cuda-bindings and cuda-python from
13.3.1 to 13.4.x (`main` is on 13.3.1). cuda-bindings 13.4 removed the
`reserved` field of `cudaIpcMemHandle_t`, which tensorrt_llm 1.3.0rc21's
`_ipc_utils.py::open_ipc_memory` reads while allocating the TP>1 all-reduce
fusion workspace, so every multi-GPU TRT-LLM engine died at start with
`AttributeError: 'cuda.bindings.runtime.cudaIpcMemHandle_t' object has no
attribute 'reserved'` (GB200 nightly `grpo-qwen3-1.7b-2n4g-fsdp2-trtllm`,
nemo-ci job 450733567). TP=1 TRT-LLM recipes never allocate that workspace
and passed on the same lock; verified locally that 13.3.1 has the attribute
and 13.4.2 does not.

The `trtllm` extra pins `cuda-python<13.4` and `cuda-bindings<13.4`; the lock
resolves the trtllm fork to 13.3.1 / cuda-core 1.0.1 while the torch-2.13
forks keep 13.4.x, which the vLLM and Megatron recipes passed with.
`uv lock --check`, taplo and `tests/unit/test_dependency_pins.py` pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw

yfw commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test ab6b3f2

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI:L2 Run doctests, unit tests, functional tests, and convergence tests Documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant