fix(bagel): force eval() for replay's packed-query KV-context rebuild - #278
fix(bagel): force eval() for replay's packed-query KV-context rebuild#278CjhHa1 wants to merge 1 commit into
Conversation
BAGEL FlowGRPO training against a deferred-prompt rollout engine (the
vllm_omni / sglang recipes) dies in the first optimizer step with:
TypeError: Qwen2Model.forward_train() got an unexpected keyword
argument 'packed_query_sequence'
The vendored navit dispatches on module mode -- Qwen2Model.forward routes to
forward_train() when self.training and forward_inference() otherwise -- and the
RL replay path uses the packed-query (inference) signature. Since #214,
TrainStack.train_track switches the model to train() before the gradient-bearing
replay so HF gradient checkpointing engages, so replay now runs in train() mode
and the packed kwargs are routed into forward_train().
forward_flow already forces eval() for its own call, but
BagelDiffusionStage._build_contexts_from_prompt did not. That is the one replay
entry point reached BEFORE any forward_flow, and it only runs when the rollout
engine ships prompts instead of opaque KV caches -- so the trainside/colocate
recipes never hit it, and it was latent until train() mode arrived.
Adds rl_ops.inference_dispatch, naming the rule forward_flow already applies:
force eval() for a packed-query region, and restore the previous mode only when
no backward follows, because activation-checkpointing recompute re-enters these
forwards during .backward() and a restored train() would mis-dispatch them
again. Guarding the rebuild itself covers all three of its callers.
Note the train() switch buys BAGEL nothing: the vendored navit has no
self.training-gated gradient checkpointing, it uses FSDP activation
checkpointing. Narrowing that switch is a separate, riskier change.
|
Closing as a duplicate of #277, which fixes the same bug in the same function and is the one we are keeping. Both PRs diagnosed the identical root cause (navit dispatches
I have pushed the simplification to #277: Verification carried over to #277: the failure was reproduced on 1x8 H20 ( |
Summary
BAGEL FlowGRPO training against a deferred-prompt rollout engine (the
vllm_omni/sglang_diffusionrecipes) dies in the first optimizer step with:Three things line up to produce it:
Qwen2Model.forwardroutes toforward_train()whenself.trainingandforward_inference()otherwise (unirl/models/bagel/vendor/modeling/bagel/qwen2_navit.py:974-978).TrainStack.train_trackswitches the model totrain()before the gradient-bearing replay so HF gradient checkpointing engages (unirl/train/stack/base.py:508). Before that commit the replay inherited theeval()set a few lines above for the π_old anchor freeze, so the whole replay ran in eval().BagelDiffusionStage._build_contexts_from_promptissues packed-query LM forwards (update_context_text→forward_cache_update_text→forward_inference) with no eval() guard.forward_flowalready forces eval() for its own call — this is the one replay entry point reached before anyforward_flow, so it is the only unguarded one.It stayed latent because it needs both halves. Only deferred-prompt engines reach the rebuild at all: trainside/colocate carry the opaque KV contexts, so
_resolve_singlereturns early and never issues these forwards. And only post-#214 is replay intrain()mode.The fix adds
rl_ops.inference_dispatch, which names the ruleforward_flowalready applies: force eval() for a packed-query region, and restore the previous mode only when no backward follows. Activation-checkpointing recompute re-enters these forwards during.backward(), so a restoredtrain()would mis-dispatch them again — the same asymmetry_forward_flow_train_safedocuments. Guarding the rebuild itself (rather than one call site) covers all three of its callers:diffuse,replay, andbuild_forward_kwargs.Related Issue
N/A.
Complements #277 (
fix(vllm_omni): stop PR_SET_PDEATHSIG from killing booted DiffusionWorkers), which explicitly names this as needing its own fix. The two are independent and both are required to run a separate-slab BAGEL FlowGRPO job on currentmain: #277 unblocks the four-replica engine boot, this unblocks the train step immediately after it. No file overlap — #277 touches onlypatches/runtime.py.Test Plan
Static + unit, on this branch rebased onto
main:ruff checkandruff format --checkon both changed files: clean. Full repository pre-commit passed via the pre-push hook.inference_dispatchsemantics, exercised against a stub module that mimics the navitif self.trainingdispatch:train()underno_grad→ region dispatches inference, previoustrain()restored;train()with grad enabled → region dispatches inference and is deliberately left in eval(), so the backward's AC recompute also dispatches inference;eval()→ no-op.PR_SET_PDEATHSIG/ mode probes used during diagnosis are not part of this change.GPU end-to-end: not run; reason: reaching the train step on a separate-slab BAGEL job first requires #277 — without it every
DiffusionWorkeris SIGKILLed during engine boot and the job dies at the firstgenerate, before any of this code runs.What I did confirm on GPU (1×8 H20,
diffusion/bagel/bagel_vllmomni_asyncviaunirl.train_diffusion,num_rollouts=1, 4 train + 4 rollout,transport_kind=colocate_store, with the watchdog temporarily gated off locally): all four engines boot,generateandreward.score_and_attachboth complete, and the run then raises exactly thepacked_query_sequenceTypeError above inTrainStack._run_update→flowgrpo.compute_loss_and_backward→BagelDiffusionStage.replay. That pins the failure this PR removes; verifying the fix itself needs one run stacked on #277, which I will post here.Compatibility / Risk
unirl/models/bagel/rl_ops.py, exported via__all__.forward_flowleaves it for the remainder of the step, so the mode at the end of a replay is unchanged either way.forward_flowalready forces eval() for the heavy velocity forward, so onmainthe LM is effectively already in eval() for the expensive part of replay.self.fsdp_backend.model.train()atunirl/train/stack/base.py:508buys BAGEL nothing, because the vendored navit has noself.training-gated gradient checkpointing and BAGEL relies on FSDP activation checkpointing instead. Narrowing that switch would affect every other model, so this PR fixes the BAGEL side only.Reviewer Notes
inference_dispatch(the "only when no backward follows" branch). That asymmetry is the subtle part, and it intentionally mirrors_forward_flow_train_saferather than inventing a second convention.bagel/fate/replay/dispatch/forward_trainkeywords. Only fix(bagel): rebuild replay KV contexts in eval() so navit keeps inference dispatch #277 is adjacent, and it does not touch these files.#214diff rather than inference, and every changed line is accounted for above.Checklist
No test file added: the repository ships no unit-test suite for this area (pytest is a dev dependency but there is no test tree), so the guard's three semantics were verified with the inline stub described in the Test Plan. The stale contract in
BagelDiffusionStage.replay's docstring ("caller owns.train()mode") is corrected in this PR, since that is precisely the assumption that does not hold for BAGEL.