Repository navigation
Conversation
…utput store so the rollout process never reads it
…llout's outputs can wait there for their reward
… are far faster than tcp
Rockdu
force-pushed
the
rockdu/output-store-reward-actor-read
branch
from
November 12, 2026 06:29
ab565f0 to
a56d1f7
Compare
3 tasks done
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
With
--use-output-store, the rollout process no longer reads a training rollout's decoded output. The reward actors read it from the store.AsyncRewardActorPoolwraps its actor class with_StoredOutputRewardActor. The wrapper reads the stored outputs it is given, then calls the scorer'sscore_batch. The reward actor classes keep their scoring code.--pickscore-num-framesof its frames. They are the frames the rollout process picks for an inline output.release_generated_outputsdeletes the samples' output fields from the store and setsgenerated_outputtoNone.--wandb-log-num-imagesscored samples are read back as tensors before the delete. This is the only read left in the rollout process. A rollout that logs no media reads nothing there.RegisteredTensorReaderreads stored outputs into one buffer that stays registered with the store. It registers the buffer when first needed and again only to grow.OutputStore.read_manyregisters fresh memory on every call.load_generated_outputsand itsstore_readdashboard stage from feat(rollout): read the generated output from the output store right after generation #376.--output-store-global-segment-sizegoes from16gbto72gb, so a rollout's outputs fit while they wait for their reward. The krea2 async recipe drops its64gboverride.--output-store-protocolgoes fromtcptordma. A node without an RDMA device passestcp.Why
#376 reads every output in the rollout manager right after generation. Every output then passes through that one process twice: store to manager, then manager to reward actor over Ray. The manager also holds a whole rollout's outputs until the rollout is converted (about 17 GB for LTX-2.3).
Now an output goes engine, store, reward actor, and the rollout process holds only the media samples.
Contract changes
sample.generated_outputis aStoredTensoruntil the reward andNoneafter it, except the kept media samples. Hooks that run after scoring see that.--custom-rm-pathfunction cannot read the tensor itself. It scores through a reward pool: a built-in*_rm,--custom-api-rm-config, or its ownAsyncRewardActorPool(args=args, ...).weighted_mixture_rmalready does.--group-rmrewards get refs the same way; the outputs are released after the group reward.AsyncRewardActorPooltakesargs.--wandb-log-num-imagessamples: a group dropped by a filter can use up a slot.Store capacity
The slot is released after generation, so outputs wait in the store until they are scored, with no cap.
72gbof host memory per training node; under RDMA it is pinned. The old16gbwas just too small for LTX-2.3: 51 engine puts failed on a full store and were regenerated, with metrics still bitwise equal.--output-store-global-segment-sizehelp): the segments should hold one rollout's outputs plus two rollouts of latents.Transport and the read buffer
Measured on one H200 node with a 1.23 GB object, the size of one MiniMax-H3 output, in a segment lent by another process:
read_many(registers per call)In the H3 e2e, where the reward actor reads one 1.33 GB output per sample:
read_manyread_manyread_manyloses its time: timing its three steps in the H3 e2e shows the transfer always takes 0.03 to 0.06 s and the registration takes the rest. Each slow registration comes with direct reclaim and compaction in the kernel (for the 21.6 s read: 608 allocation stalls, 1218 compaction stalls, 77k pages migrated); fast ones have none.read_manyallocates fresh memory per call, and an RDMA registration pins it at once. On a NUMA node whose free memory is all page cache (2 GB free of 1 TB on the test node), the kernel has to reclaim and move pages inside the registration.LEASE_EXPIRED), which fails the rollout. It was seen once and not reproduced.Not in this PR
read_many. Its reads are about 150 MB, and train time was the same on both transports (305 s against 304 s).Validation
tests/fast/rollout/test_generated_output_store.py, 13 test functions, numbered in its header diagram:pickscore_rmhands a stored video over unsliced, and the actor scores a whole video as the frames picked inline, picking before the uint8 quantisation.tests/fast/rollout+tests/fast/utils+tests/fast/dashboard: 421 passed on the current head, and on each of its three commits.weighted_mixture_rmrun (SD3.5 OCR + PickScore through--custom-rm-path) succeeds.Files
miles/utils/output_store.py:RegisteredTensorReader.miles/rollout/rm_hub/core.py:_StoredOutputRewardActor; the pool takesargs.miles/rollout/rm_hub/{pickscore,hps,hpsv3,ocr,api}.py: passargs; the PickScore actor takes its frame count, andpickscore_rmhands a stored video over unsliced.miles/rollout/sglang_diffusion_rollout.py:release_generated_outputsafter both reward sites; the media counter;load_generated_outputsremoved.miles/utils/diffusion_rollout_response.py:read_generated_outputsreads through the reader.miles/utils/tracking_utils.py,miles/ray/rollout.py:logs_rollout_media; media logging takes the samples that hold a tensor.miles/dashboard/events.py: thestore_readkinds removed.miles/utils/arguments.py: the--use-output-store, segment size and protocol help; the72gbandrdmadefaults.scripts/run_diffusion_grpo_krea2_async.py: no segment override.miles/utils/types.py,docs/user-guide/customization.md,docs/advanced/streaming-reward.md: comment and docs for the contract above.test_generated_output_store.pyrewritten; four test fakes get the fields the pool, the actor and the rollout now read; the default assertion intest_output_store_args.py.Checklist
pre-commitpasses on the changed filespytestis green (tests/fast/rollout,tests/fast/utils,tests/fast/dashboard)--output-store-global-segment-size,--output-store-protocol)