miles/rollout/rm_hub/ and is invoked
from sglang_diffusion_rollout.generate_and_rm_microgroup.
For --custom-rm-path, --custom-reward-post-process-path, and other
--*-path hooks, see Customization.
1. At a glance
2. Built-in reward models
PickScore (--rm-type pickscore)
Implementation: miles/rollout/rm_hub/pickscore.py.
PickScore scores text–image alignment using a CLIP model pair:
- Processor:
--pickscore-processor-path(e.g.laion/CLIP-ViT-H-14-laion2B-s32B-b79K) - Model:
--pickscore-model-path(e.g.yuvalkirstain/PickScore_v1)
/ 26.0 scaling maps raw PickScore logits (~0–26) to roughly 0–1.
PickScore runs as a Ray actor pool (PickScoreRewardActor) with round-robin
batching. For video outputs, frames are uniformly sampled
(--pickscore-num-frames) and scores are averaged.
Example from
scripts/run_diffusion_nft_sd3_pickscore.py:
HPS (--rm-type hps)
Implementation: miles/rollout/rm_hub/hps.py.
HPSv2 scores text–image alignment using a preference-tuned CLIP model:
- Model:
ViT-H-14 - Version:
--hps-version(v2.0orv2.1) - Checkpoint:
--hps-checkpoint-path(optional; defaults toxswu/HPSv2)
HPSRewardActor) with round-robin batching.
It currently accepts image outputs only (generated_output with one frame).
Use a custom RM when defining video frame aggregation semantics.
Example from
scripts/run_diffusion_grpo_sd3_hps_sglang.py:
Reward placement
Every GPU reward pool is placed one of two ways:--<rm>-reward-colocate: every worker takes one slot on a rollout placement-group bundle, sharing that GPU with the train actor and the rollout engine. Colocated pools share one slot ledger, sohpsandpickscorecan both colocate without overlapping; more workers than bundles is rejected at parse time. Requires--colocate.- Otherwise the pool is standalone: default-scheduled at
--<rm>-num-gpus-per-worker, which only lands on GPUs outside every placement group (placement groups reserve their GPUs, so Ray never packs these onto rollout GPUs).
RolloutManager seats the colocated pools before the first rollout; standalone pools are
built on first use.
Combining rewards
--custom-rm-path receives (args, samples) and can call the built-in scorers
directly; --custom-rm-args is an opaque string the framework hands to that function
through args, so the function owns its own config grammar. The shipped example
miles/rollout/rm_hub/weighted_mixture_rm.py reads name=weight,name=weight, with names
from hps, pickscore, ocr, custom_api, openai_api:
{"hps": ..., "pickscore": ..., "weighted": ...}).
--reward-key picks the entry GRPO trains on, and every entry of a dict reward gets its own
rollout/reward/<name>_mean panel, so the components stay visible while the sum is optimized.
Weights apply to raw scores (HPSv2.1 ≈ 0.25–0.35, PickScore/26 ≈ 0.8–0.9, OCR ∈ [0, 1], default
OpenAI API rubric ∈ [0, 4]), so pick them with the scales in mind. Colocated pools share one slot ledger, so several rewards
can colocate without overlapping. Rewards receive generated_output itself, and every reward actor
quantises it to uint8 on its own terms.
A shipped recipe uses it: scripts/run_diffusion_grpo_sd3_ocr_pickscore_sglang.py trains SD3.5 on
0.8 OCR + 0.2 PickScore with --rollout-shuffle; see SD3 § 5.4 for the
recipe and numbers and § 9 for the curves. Shuffling matters more than usual there: with 8 prompts per rollout one hard
batch moves the per-rollout mean visibly.
OCR (--rm-type ocr)
Implementation: miles/rollout/rm_hub/ocr.py.
OCR reward compares PaddleOCR output against target text embedded in the
prompt. The target is the string between the first pair of double quotes.
Simplified scoring:
dist=0), and caps dist at len(target) — see
miles/rollout/rm_hub/ocr.py.
OCR runs on CPU Ray actors (--ocr-num-workers, default 4). Used by the
SD3 Flow-GRPO OCR and OCR & PickScore recipes, the three Krea-2 DiffusionNFT recipes, and the
smoke mode of the SD3 DiffusionNFT recipe.
API rewards (--rm-type openai_api / custom_api)
Implementation: miles/rollout/rm_hub/openai_api.py and miles/rollout/rm_hub/api.py.
Each sample is one HTTP request, sent by a single zero-GPU Ray actor that runs up to
max_concurrency (default 8) requests at once. The config is a YAML file or inline
base64:<payload> with actor_class, actor_kwargs, and max_concurrency; a flat mapping is
taken as the actor_kwargs of the default actor.
OpenAIImageRewardActor sends the generation prompt and the image (PNG data URL) to an
OpenAI-compatible Chat Completions endpoint, with the config prompt as the system rubric, and asks
for a JSON score. Its kwargs are model and api_key_env (required), base_url, prompt or
prompt_path (resolved relative to the config file, not allowed in an inline base64: config; with neither, a
built-in 0–4 rubric is used), score_min / score_max (default 0 / 4), and timeout_s; scores outside that range
raise. It accepts image outputs only (one frame).
Remote RM (--rm-type remote_rm)
The CLI exposes --rm-url for a remote reward service, but rm_hub does not
implement remote_rm today — selecting it raises NotImplementedError.
To call an external service, use the API rewards above or a --custom-rm-path
function that reads args.rm_url.
3. Call chain
RolloutManager._post_process_rewards subtracts the mean and
optionally divides by std to produce normalized advantages for training.
Override that path with --custom-reward-post-process-path — see
Customization § Reward.
4. Prompt data
JSONL format
Training prompts are loaded from.jsonl files via miles/utils/diffusion_data.py and permuted
once per epoch (seeded by --rollout-seed); pass --no-rollout-shuffle to read them in file order:
Dataset subsets
Dataset repo:rockdu/miles-diffusion-datasets
Per-sample rm_type override
JSONLmetadata.rm_type overrides the global --rm-type for that sample:

