Skip to content

refactor(engine): training Engine API - #3614

Draft
HuiyingLi wants to merge 87 commits into
mainfrom
huiyingl/feat/datum-forward-backward
Draft

refactor(engine): training Engine API#3614
HuiyingLi wants to merge 87 commits into
mainfrom
huiyingl/feat/datum-forward-backward

Conversation

@HuiyingLi

@HuiyingLi HuiyingLi commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

What does this PR do ?

Adds training Engine and migrates the LLM/VLM finetune recipes onto it. The earlier Datum-based design (structured collation metadata, forward_backward with loss callbacks) was dropped after review; the boundary is now the same one OpenRLHF uses on DeepSpeed:

out = engine(**batch)          # ordinary nn.Module forward
loss = caller_computed_loss(out.logits, ...)
engine.backward(loss)
engine.step()                  # clip + optimizer + scheduler at the accumulation boundary

Engine (nemo_automodel/engine/_engine.py, ~300 lines) wraps an already-distributed model and owns backward, gradient finalization/clipping, optimizer update, gradient clearing, and explicit gradient-accumulation windows (set_gradient_accumulation_steps). It does not know about losses, RL, packing, or collation — callers compute the loss and prepare inputs. This is the execution boundary the molt RL integration builds on (NVIDIA-NeMo/labs-molt#92 pins this branch).

Changelog

  • Add nemo_automodel/engine/_engine.py: eager forward / backward(loss) / step() / zero_grad() / get_global_grad_norm() with explicit accumulation windows.
  • Add components/loss/vocab_parallel.py: token_log_probs / token_entropy unified over dense tensors and vocab-sharded DTensor logits (distributed reductions, no vocabulary gather).
  • Extend components/moe/router_replay.py: routing-replay adapter covering forward, activation recomputation, and backward, including pipeline-split model parts.
  • Equalize and align per-rank token counts inside the HybridEP dispatcher so dynamic (variable-length/packed) batches work; also proposed standalone as fix(moe): equalize and align per-rank token counts for HybridEP dispatch #3641.
  • Migrate recipes/llm/train_ft.py and recipes/vlm/finetune.py to drive training through Engine, removing duplicated backward/clip/step logic.
  • VLM data: generic media collation and variable-resolution pixel padding; preserve media axes for THD forwards (Qwen3-VL, Qwen3.5-MoE).
  • Checkpoint: stream passthrough model weights; preserve a supplied config in custom loading; avoid re-enabling input gradients being lost after evaluation under PP.

Validation

  • Unit: 1590 tests green in the 26.06 container, including tests/unit_tests/test_engine.py (new), loss/test_vocab_parallel.py (new), moe/test_router_replay.py (new), and moe/test_token_dispatcher.py equalization coverage.
  • Functional: tests/functional_tests/moe/run_hybridep_unequal_tokens.py (2×H100, bitwise forward+backward parity for unequal/unaligned token counts); DeepSeek-V4 PP2/EP2 and CP+TP dense-packed THD parity runs match main.
  • Downstream: the molt SFT/RL integration (labs-molt#92) runs SFT parity, MoE RL, and critic smokes against this exact revision.

Before your PR is "Ready for review"

Pre checks:

  • Make sure you read and followed Contributor guidelines
  • Did you write any new necessary tests?
  • Did you add or update any necessary documentation?

Draft while full CI and reviewer feedback are pending.

Additional Information

🤖 Generated with Claude Code

Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
…orward-backward

# Conflicts:
#	nemo_automodel/recipes/llm/train_ft.py
#	nemo_automodel/recipes/vlm/finetune.py
#	tests/unit_tests/recipes/test_finetune_vlm_helpers.py
#	tests/unit_tests/recipes/test_train_ft.py

Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
@copy-pr-bot

copy-pr-bot Bot commented Aug 21, 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.

…orward-backward

Signed-off-by: HuiyingLi <willwin.lee@gmail.com>

# Conflicts:
#	nemo_automodel/components/distributed/pipelining/autopipeline.py
#	nemo_automodel/recipes/llm/train_ft.py
#	nemo_automodel/recipes/vlm/finetune.py
@HuiyingLi
HuiyingLi force-pushed the huiyingl/feat/datum-forward-backward branch from a238fa5 to 645e5ae Compare August 21, 2026 18:54
@HuiyingLi HuiyingLi changed the title feat(engine): unify Datum execution and optimizer lifecycle feat(engine): add Datum-based forward, backward, and optimizer APIs Aug 21, 2026
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Expose CP-prepared loss side channels to eager batch contexts for model-scoped router replay. Remove the unused whole-batch AutoPipeline step API and planned multi-call accumulation, making one forward_backward call the complete optimizer window while retaining single-window lifecycle safety.

Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
…orward-backward

Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Molt owns the critic value head again, installed after the FSDP wrap as a
plain replicated module with trainer-side DP grad sync — the pre-integration
design. Reverts the pre_fsdp_hook plumbing (auto_model, infrastructure), the
task-head registry (shared/task_heads.py), its TP-plan exclusion
(parallelizers), FSDP-unit management and replica sync (infrastructure,
fsdp_mixin), the branch-added base-load scrubbing (checkpointing), and their
tests. Main's generic skip_task_head_prefixes checkpoint filtering is upstream
code and stays.

Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
torch's pipeline schedules already detect the eval-to-train has_backward
switch and rebuild the forward receive buffers with the correct
requires_grad; a 2-rank GPipe and 1F1B eval-then-step run trains fine
without this shim, and its unit test only exercised a fake schedule.

Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
The microbatch_size/input_tensor parameters and the first_stage_input_meta
threading were added for the old engine-driven PP path; after the
DeepSpeed-style rewrite no caller passes them — every recipe still calls
update_seq_len(seq_len), packed THD keeps using the recipe-level
pp_microbatch_size=1 override, and the VLM embeds first stage sets
stage.inputs_meta directly. Restores main's single-parameter form.

Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
…s THD path

Restore main's versions of thd_utils, context_parallel utils/sharder, and the
generic HF pipeline forward. This removes the single-packed-row width
splitting (split_final_thd_batch and the batch_size==1 branch), the
stack_thd_chunks helper, per-chunk ShardLayout.chunk_layouts/chunk_index
verbs, and the generic-HF THD pipeline-stage branch — all built for the old
engine-driven flow and without production callers on this branch. Row-group
chunking for SFT+PP THD is unchanged (it is main's own path).

Validated on 8xH100: DSV4 PP2 and EP2 parity, CP and TP+CP dense-packed THD
functional tests, the 2-rank CP sharder token-verb test, 1590 distributed/
model unit tests, and molt's full unit suite against the reverted files.

Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
@HuiyingLi HuiyingLi changed the title feat(engine): add Datum-based forward, backward, and optimizer APIs feat(engine): add a DeepSpeed-style eager training Engine Aug 25, 2026
HuiyingLi and others added 10 commits August 24, 2026 19:45
vocab_parallel_log_probs/entropy aliases only ever existed between two
commits on this branch; validation was written three times (public dense
paths + the DTensor helper); the empty-batch gradient trick ran after a
loop that never executes. Hoist validation to the public entry points,
early-return the empty case, and trim private-helper docstrings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
The mixed calculate_per_token_loss error, the nn.Module/max_grad_norm
constructor checks, zero_grad's module fallback, and the steps isinstance
half can't trigger from any caller; _gradient_reduction_compensation had
one call site. Simplify and align zero_grad's no-optimizer behavior with
backward/step.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Model-parts input, the per-device topology cache, the async route
validation, and per-call flag save/restore have no caller outside their
own tests (molt binds one module and rollout routes are recorded, never
hand-built). Also drop muse-glimmer's never-filled _thd_local_indices
placeholder and a dead test mock left by the offload revert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Two models carried the same pop/squeeze/re-add workaround because the
helper squeezed dim 0 of every tensor kwarg, dropping the item axis of a
single-media batch. Skipping media keys in the helper deletes both copies
and covers the VLM callers that never got the workaround.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
…ad contexts

VLM inlined a byte-for-byte copy of the LLM recipe's
_step_pipeline_optimizer; the grad_ctx no_grad wrappers are redundant
(validation callers are already @torch.no_grad and Engine.forward falls
through when grads are off); VLM validation goes back to calling the
model part directly; benchmark uses ga_steps instead of len(batches) and
collapses the duplicated engine.step() arms. The duplicated
_RecipeEngineStub moves to one shared module with Engine's real
backward signature.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
The datasets.vlm package re-exports had zero consumers and made every
submodule import execute collate_fns' import-time monkeypatching; the
magi return_local_indices=False arms had no production caller; the
media-token helpers move back beside their only caller in datasets.py;
pp_media's list branch validated states no producer can create. Unify
the qwen3 passthrough adapter signature with llama/qwen2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
…tion

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
The embed-input-meta, non-first-stage sunk-model, and validation
CP-reduction tests guarded code that is still live; restore them from
main adapted to the current recipe internals.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
The drafter log_denominator test targets a parameter that no longer
exists; the partial-CUDA-graph stub predates the two-argument
_run_train_optim_step call.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
@HuiyingLi HuiyingLi changed the title feat(engine): add a DeepSpeed-style eager training Engine refac(engine): training Engine API Aug 25, 2026
@HuiyingLi HuiyingLi changed the title refac(engine): training Engine API refactor(engine): training Engine API Aug 25, 2026
@zyzhou5 zyzhou5 mentioned this pull request Aug 27, 2026
HuiyingLi and others added 2 commits August 30, 2026 10:29
…orward-backward

Signed-off-by: HuiyingLi <willwin.lee@gmail.com>

# Conflicts:
#	tests/functional_tests/parallelism/compare_parallel_parity.py
#	tests/unit_tests/moe/test_parallelizer.py
The run-loop enablement was already reverted with the Datum Engine; the
remaining periphery (validation drop_last in example configs, the parity
script's val_loss metric, the Gemma4 PP2 parity extension, and the
enable/disable lifecycle tests) moves to a dedicated VLM PP-validation PR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
HuiyingLi and others added 2 commits August 30, 2026 11:35
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
The mixtral packed-example switch, nemotron-parse zero-loss fix, MTP THD
squeeze, fsdp_mixin dedup, parallelizer test stubs, auto_model test churn,
the layer2-sink reformat, and the media-token helper relocation all came
from earlier engine-era commits but are not needed by the final Engine
design; drop them to keep this PR reviewable. Candidates worth their own
PRs: the nemotron-parse autograd fix and the MTP THD squeeze.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
HuiyingLi and others added 6 commits August 30, 2026 12:09
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
A microstep that raises (e.g. OOM between forward and backward) leaves an
open gradient-sync context and a nonzero microstep counter that poison
every later window; callers recovering from such failures need an
explicit reset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
A NaN/inf norm means the gradients themselves are non-finite; clipping
with error_if_nonfinite=False then writes NaN into every weight and the
run can only be recovered from a checkpoint. Drop the update with a
warning instead, matching veRL's engine behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant