Skip to content

feat(engine): nemo_automodel engine for unified engine workers (SFT + PPO/GRPO) - #1

Draft
HuiyingLi wants to merge 9 commits into
mainfrom
huiyingl/automodel-engine
Draft

feat(engine): nemo_automodel engine for unified engine workers (SFT + PPO/GRPO)#1
HuiyingLi wants to merge 9 commits into
mainfrom
huiyingl/automodel-engine

Conversation

@HuiyingLi

Copy link
Copy Markdown
Owner

Summary

Adds an automodel model-engine backend that delegates training to
nemo_automodel's in-process
Engine, so the unified engine workers can use Automodel as the actor/ref for
both SFT and PPO/GRPO (previously the automodel engine was SFT-only).

Depends on the Automodel-side Engine API (NVIDIA-NeMo/Automodel#2556). Install
nemo_automodel from that branch until it lands.

What's added

  • verl/workers/engine/automodel/engine_tinker_impl.pyAutomodelEngine(BaseEngine):
    • builds the model via the current Automodel API (build_model +
      create_distributed_setup_from_config) and wraps it in
      nemo_automodel.engine.Engine;
    • forward_backward_batch runs verl's THD micro-batches through the Engine's
      PackedBatch pass-through (multi-microbatch grad accumulation), bridging the
      Engine's per-datum ModelOutput back to verl's flat
      model_output["log_probs"] / ["entropy"] so the existing ppo_loss /
      sft_loss run unchanged;
    • infer_batch (compute_log_prob), get_per_tensor_param (HF-named rollout
      weight sync), save/load_checkpoint, disable_adapter.
  • AutomodelActorConfig + config groups (actor/automodel_actor,
    ref/automodel_ref, critic/automodel_critic, model_engine/automodel) for
    the model_engine=automodel PPO/GRPO path.

Usage

python -m verl.trainer.main_ppo model_engine=automodel \
  algorithm.adv_estimator=grpo \
  actor_rollout_ref.actor.strategy=automodel \
  actor_rollout_ref.rollout.name=vllm ...

Validation

End-to-end 8-GPU GRPO on gsm8k (Qwen2.5-0.5B): rollout (vLLM) →
compute_old_log_prob → GRPO advantage → actor update → vLLM weight sync, two
training steps with full metrics (actor/pg_loss, actor/grad_norm,
critic/rewards, timing_s/update_weights). Also a single-GPU functional
forward/backward step.

Notes

  • The legacy automodel/transformer_impl.py (pinned to an older Automodel API)
    is superseded by this engine; __init__ now registers the new one. Removing
    the legacy file is left as a follow-up.
  • Deferred: pipeline parallelism, fused-kernel logprobs, per-sample temperature
    scaling in extraction.

🤖 Generated with Claude Code

HuiyingLi and others added 9 commits June 14, 2026 10:48
…e workers

Adds a model-engine backend that delegates training to nemo_automodel's
in-process Engine (forward/extraction/microbatch-lifecycle/optimizer), so the
unified engine workers (SFT + PPO/GRPO) can use Automodel as the actor/ref.

- verl/workers/engine/automodel/engine_tinker_impl.py: AutomodelEngine
  (BaseEngine), builds the model via the current nemo_automodel API
  (build_model + create_distributed_setup_from_config) and delegates the step
  to nemo_automodel.engine.Engine via its PackedBatch pass-through. Bridges the
  Engine's per-datum ModelOutput to verl's flat model_output (log_probs +
  entropy). Implements infer_batch (compute_log_prob), get_per_tensor_param
  (HF-named rollout weight sync), save/load_checkpoint, disable_adapter.
- AutomodelActorConfig + config groups (actor/ref/critic, model_engine) so the
  PPO/GRPO path can select `model_engine=automodel` (previously automodel was
  SFT-only).

Validated end-to-end: 8-GPU GRPO on gsm8k (Qwen2.5-0.5B) — rollout (vLLM) ->
compute_old_log_prob -> GRPO advantage -> actor update -> weight sync, two steps
with full metrics.

Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
…gine.py move

The Automodel engine loads a custom Qwen2 forward whose rotary defaults to fused
TE RoPE (BackendConfig.rope_fusion = HAVE_TE and cuda). The fused kernel indexes
rotary angles by physical sequence position and assumes contiguous [0, seq_len),
so it ignores the per-sequence position_id resets of a THD-packed micro-batch and
corrupts RoPE for every non-first sequence in a pack. In GRPO this made the actor's
recomputed logprobs drift from the vLLM rollout (rollout_actor_probs_pearson_corr
~0.62 vs fsdp's 0.9997), breaking the importance ratio so the policy didn't learn.

Build the model with backend rope_fusion=False so the non-fused path gathers
cos/sin by position_id value (packing-correct). corr -> 0.9997, matching fsdp.

Also update the import to follow nemo_automodel.engine -> components.training.engine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: HuiyingLi <willwin.lee@gmail.com>
train_mode/eval_mode previously only flipped module train()/eval() and skipped
offload ("offload omitted for now"), so with param_offload/optimizer_offload
enabled the actor was pushed to CPU after rollout weight-sync but never onloaded
back before the next step — breaking the actor<->vLLM swap for large models that
must offload to fit the rollout engine.

Replace the custom _ModeCtx with EngineTrainModeCtx/EngineEvalModeCtx subclassing
BaseEngineCtx, exactly like the fsdp/megatron/torchtitan/veomni backends:
BaseEngineCtx._context_switch drives onload-on-enter / offload-on-exit (gated by
is_param_offload_enabled / is_optimizer_offload_enabled, honoring
disable_auto_offload) via the engine's to(), and the subclass only adds the
per-part train()/eval() toggle (+ zero-grad before offload on train exit).

Verified single-GPU round-trip with offload enabled: params/optimizer move
cuda->cpu on context exit and back cuda on the next enter. With offload disabled
(default) _context_switch is a no-op, preserving the co-resident path.

Co-Authored-By: Claude Opus 4.8 <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