Repository navigation
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a 2-deep background staging pipeline and concurrent method execution on the TrainerWorker to significantly reduce training stalls. By pre-staging micro-batches while the trainer is busy with weight updates and utilizing a multi-threaded executor for staging tasks, the system effectively hides latency between micro-batch processing and eliminates significant idle gaps on the TPU. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request implements background staging of training micro-batches to overlap data transfer with computation, updating the orchestrator, distributed RL engine, and trainer worker. It also introduces a thread pool in the remote execution server to run synchronous executor methods off the main event loop. The review feedback highlights several violations of the style guide's "One well-lit path" and "Fail loud and fast" principles, specifically advising against speculative getattr calls and redundant checks on strictly typed contracts in favor of direct attribute access and standard exception handling.
b2c258d to
8c0f93a
Compare
…orker.stage_batch
f73bf4e to
d7ecd41
Compare
…p boundary With the pipelined boundary (#2878) the next step's first micro-batch is staged on the trainer's slot before the step's update. With the intra-step queue (#2906 / maxtext#5751) the step then stages its later micro-batches only once its first train call is pending, so the queue starts every step empty and the early, slow transfers of a step are never hidden. This stages the next step's second micro-batch through the intra-step queue right after the first one is staged (`_prestage_across_boundary`), so its transfer overlaps the step's update, metrics, checkpoint and weight sync. The next step adopts it (`_StepState.prestaged` / `prestage_task`) as the first entry of its queue, or as its active stage task when still in flight, and keeps filling the queue from there. Same refusals as the first micro-batch (closes its step, rescored under live weights; scored checkpoint boundaries are not staged at all); a stage the trainer declines falls back to the inline call. The intake for the second micro-batch is the step's own (`_intake_task`); `_send_first_microbatch_ahead` no longer starts a second one beside it. The boundary's memory bound holds: the first micro-batch's payload is dropped once staged, so taking the second one ahead holds at most one feed's output, as the plain take-ahead does. On the trainer, `apply_deferred_update` joins `stage_batch` and `fwd_bwd` on the worker's executor pool under `TUNIX_STAGE_BATCH=stage`, so its device wait at the boundary does not hold the stage request back on the loop thread. No MaxText change: the slot (`prepare_fwd_bwd_input`) and the keyed queue (`stage_batch`) coexist on the engine, and `fwd_bwd(prepared)` does not consume keyed entries. Active only with pipeline_step_boundary + stage_next_microbatch + stage_train_batches + max_staged_batches >= 2; no new flag. Tests: rl_program_prestage_across_boundary_test.py runs the pipelined boundary harness with a trainer double carrying maxtext#5751's keyed queue: the trainer calls, checkpoints and metrics match the frozen base loop in every scenario; the second micro-batch of every non-scored boundary is staged after the step's last micro-batch and before the next step's first train call, keyed to the step it belongs to, and before that step's weight sync; nothing is staged ahead when it would close its step, at scored boundaries, without the queue, or with a single-slot queue; a declined stage goes whole; a stage still in flight at the step start is adopted; no unshipped micro-batch is held at any take-ahead. Also fixes rl_program_pipelined_boundary_robustness_test's `_PrepackProbe._take_group` for the `count_exposed` keyword #2906 added (72 of its 100 cases raised TypeError at #2906's head). Modelled on siv-sk8's measured stage times against the merged boundary (image trellis-experimental:1014c): ~46 s over six steps with a ~10 s boundary window, vs ~13 s for the queue without this; to be confirmed by an A/B on hardware. TAG=agy CONV=14094c8e-2be4-4c24-87b4-d7102f101303
Summary
Ports intra-step
stage_batchontoatwigg/mlperfand extends it to continuous 2-deep intra-stepstage_batchpipelining (_stage_ahead_while_pending,max_staged_batches=2) across the orchestrator, RPC server,TrainerWorker, and K8s launch recipes (matched withAI-Hypercomputer/maxtext#5751andgoogle/tunix#2905).Value Breakdown (Measured & Estimated from
siv-sk8)stage_batchontoatwigg/mlperf(~20–35 s / stepestimated savings vs. inlinetrain_step(payload)):atwigg/mlperfcurrently has no intra-step staging, putting each micro-batch's wire transfer (8–10 s) plus H2D copy (~3.9 sfor5.03 GB) on the critical path (estimated~2–4 s x 9 ≈ 20–35 s/stepvs. inlinetrain_step(payload)).siv-sk8Step 1: when every stage completed in7.8–10.0 s < 10.35 s,stage_batchallowed the TPU to executeMB 1..8back-to-back with0.00 smid-step idle (ready(k) - ready(k-1) = 10.35 sfor everyk)._stage_ahead_while_pending,max_staged_batches=2):siv-sk8Step 2, the raw5.03 GBstages forMB 1..8summed to94.9 sagainst82.8 s(8 x 10.35 s) of compute windows — a12.1 sbandwidth deficit that requiresgoogle/tunix#2905(wire compression) to remove. Standalone on Step 2's front-loaded stall pattern, 2-deep over 1-deep is estimated to recover up to~5.6 s.google/tunix#2905(which shrinks stage transfers to~4–5 s, leaving~5–6 sof spare wire time per10.35 scompute window) and step-boundary pipelining (#2878+#5718, which dispatchesMB 0ahead so_stage_ahead_while_pendingbuilds a 2-deep cushion from the start of the step), 2-deep staging absorbs2xtransient gRPC contention spikes without stalling TPU dispatch.Solution (
atwigg/mlperfbase)RemoteExecutionServerBackground Method Executor (tunix/experimental/worker/remote_execution.py):ThreadPoolExecutor(_method_executor,max_workers=max(2, len(remote_executor_methods)), cleaned up viaclose_executor()) for methods listed in an instance'sremote_executor_methods.TUNIX_STAGE_BATCH=stage,TrainerWorker.remote_executor_methodsincludes{"stage_batch", "fwd_bwd"}sostage_batch(MB k+1)can run its host preparation and H2D transfer on one pool thread whilefwd_bwd(MB k)blocks inInflightThrottler.wait_for_next()on the other pool thread.TrainerWorker.stage_batch&reset_staged(tunix/experimental/worker/trainer_worker.py):CompactRoutedExpertsleaf via_with_dense_routingbefore forwarding(payload, key=request.request_id)toself._trainer.stage_batch(ensuring seamless composition withgoogle/tunix#2905).hasattr(self._trainer, "stage_batch")up front and degrades gracefully on trainer exceptions (staged=False, reason=...) while keeping the worker inWorkerState.READYso the orchestrator falls back to inlinetrain_step(payload).DistributedRLEngine.stage_train_batch&reset_staged_batches(tunix/experimental/orchestrator/distributed_rl_engine.py):stage_batchto the actor worker and returnsstage_keywhenstaged=True, catching(AttributeError, RuntimeError)to fall back cleanly to inlinetrain_step(payload)if staging is unsupported or fails.StandardRLProgram._train_loop(tunix/experimental/orchestrator/rl_program.py,run_deepswe_dist.py):self._pending_train(MB k-1) is in flight on the TPU,_stage_ahead_while_pending()keeps up tomax_staged_batches=2micro-batches staged ahead (oneactive_stage_taskRPC in flight at a time from the orchestrator).MB kis instaged_queue[0], both background group pulls (active_pull_taskwithcount_exposed=False) andactive_stage_task(MB k+1) race againstself._pending_trainviaasyncio.wait(..., return_when=asyncio.FIRST_COMPLETED)soMB kdispatches immediately as soon asMB k-1finishes.self._stage_fallbacksand logsstage/fallbacksin_collect_and_log_step_metricswheneverstage_train_batchraises or returnsFalse.stage_train_batchesdefaults toTUNIX_STAGE_BATCH=stagewhenpipeline_train_microbatches=Trueand is plumbed throughrun_deepswe_dist.py(--stage_train_batches,--max_staged_batches).mlperf_base.sh,k8s_launcher.sh):TUNIX_STAGE_BATCH="${TUNIX_STAGE_BATCH:-off}",TUNIX_STAGE_MAX_DEVICE_BATCHES="${TUNIX_STAGE_MAX_DEVICE_BATCHES:-1}", andTUNIX_MAX_STAGED_BATCHES="${TUNIX_MAX_STAGED_BATCHES:-2}"inmlperf_base.shandk8s_launcher.sh, forwardingTUNIX_STAGE_BATCHandTUNIX_MAX_STAGED_BATCHESto the orchestrator container andTUNIX_STAGE_BATCHandTUNIX_STAGE_MAX_DEVICE_BATCHESto the trainer container.Sequencing
google/tunix#2905->google/tunix#2906(withAI-Hypercomputer/maxtext#5751on the MaxText side).Testing
pytest tests/experimental/worker/trainer_worker_stage_batch_test.py(14 passed)Update (commit
d8342a169): stage the next step's second micro-batch across the step boundaryWith the pipelined boundary (
#2878) the next step's first micro-batch is already staged on the trainer's slot before the update. The intra-step queue above only starts filling once a step's firstfwd_bwdis pending, so every step began with an empty queue and its early, slow transfers were never hidden. This commit:_prestage_across_boundary(rl_program.py): right after the next step's first micro-batch is staged, its second micro-batch is sent through the intra-step queue (stage_batch, keyedstep_<k+1>_stage_…), so that transfer overlaps the current step's update, metrics, checkpoint and weight sync. The next step adopts it (_StepState.prestaged/prestage_task) as the first queue entry, or as its in-flightactive_stage_task, and keeps filling from there.apply_deferred_updatejoinsstage_batch/fwd_bwdon the worker's executor pool underTUNIX_STAGE_BATCH=stage, so its device wait at the boundary does not block the stage request on the server loop thread. No MaxText change beyondAI-Hypercomputer/maxtext#5751.pipeline_step_boundary+stage_next_microbatch+stage_train_batches+max_staged_batches >= 2; no new flag. Launch:TUNIX_STAGE_BATCH=stage(forwarded to both containers bymlperf_base.sh/k8s_launcher.sh).rl_program_pipelined_boundary_robustness_test's_PrepackProbe._take_groupfor thecount_exposedkeyword this PR added (72 of its 100 cases raisedTypeErrorat the previous head).Modelled on
siv-sk8's measured stage times against the merged boundary (imagetrellis-experimental:1014c): ~46 s saved over six steps with a ~10 s boundary window, vs ~13 s for the queue alone; to be confirmed by an A/B on hardware.Testing (jax 0.10.1, py3.12):
tests/experimental/orchestrator/rl_program_prestage_across_boundary_test.py(18 tests + 10 subtests pass),rl_program_stage_before_update_test(165) andrl_program_pipelined_boundary_test(199) green,rl_program_pipelined_boundary_robustness_test100/100,tests/experimental/worker/*stage_batch*75/75.