Deterministic Post-Training
Last updated: 08/15/2026.
By default, verl-omni RL training is not bitwise reproducible: identical configs run twice can produce different reward curves due to nondeterminism in GPU kernels, batch composition, request routing, and sampling. This page documents the effort to make post-training deterministic across multiple aspects (reward inference, rollout generation, diffusion sampling, and eventually end-to-end bitwise-aligned reward curves).
Supported: reward inference determinism (VLM reward-model / GRM scoring). The remaining aspects are tracked as future work — see Scope.
Scope
Component |
Status |
|---|---|
GRM inference — floating-point determinism |
✅ Supported |
GRM inference — batch invariance ( |
✅ Supported |
GRM inference — deterministic multi-replica routing ( |
✅ Supported |
GRM inference — per-request sampling seed |
✅ Supported |
Actor-rollout generation determinism |
🚧 Future |
Diffusion sampling determinism |
🚧 Future |
End-to-end bitwise-aligned reward curves |
🚧 Future |
For the upstream full-determinism feature (actor rollout, FSDP/Megatron, trainer backend), see verl’s docs/advance/determinism.md. verl-omni will adopt those layers incrementally, and a single top-level determinism switch (covering both rollout and reward) will replace the current per-component full_determinism flags.
When to use
Debugging — reproduce a reward-score anomaly exactly.
Regression testing — verify a code change has no silent effect on GRM scores.
Research — fair comparison of reward-side algorithmic changes.
Leave determinism off for production training — deterministic kernels are slower, cuDNN benchmarking is disabled, and batch invariance can serialize.
Enable
Set reward.reward_model.rollout.full_determinism=true and a seed under the reward_model.rollout block:
reward:
reward_model:
enable: true
rollout:
name: vllm_omni
full_determinism: true
seed: 42
Or via Hydra overrides:
python -m verl_omni.trainer.main_diffusion \
reward.reward_model.enable=true \
reward.reward_model.rollout.full_determinism=true \
reward.reward_model.rollout.seed=42 \
[other config overrides...]
Sampling params (temperature, top_k, top_p) remain user-configured under reward.reward_model.rollout; the flag does not override them. Greedy decoding uses temperature=0; controlled pseudorandom sampling uses temperature>0 plus the per-request seed. The scorer’s own tuned defaults (e.g. genrm_ocr’s temperature=0.7, top_p=0.8) apply unless overridden.
Configuration Reference
Parameter |
Default |
Description |
|---|---|---|
|
|
Enables reward inference determinism (all layers below) |
|
|
Floating-point + per-request sampling seed |
|
— |
Use |
|
— |
User-configured sampling temperature |
|
— |
User-configured top-k |
|
— |
User-configured top-p |
How it works
verl-omni’s GRM scores images via /v1/chat/completions (a VLM such as Qwen3-VL transcribes the image). Reproducibility is enforced at three layers.
Env export before ray.init()
When determinism is requested (currently reward.reward_model.rollout.full_determinism=true; a single top-level switch covering rollout + reward is future work — see Scope), main_diffusion.run_diffusion calls _export_full_determinism_env in the main process before ray.init() to set the determinism switch env vars:
VERL_FULL_DETERMINISM=1,VLLM_BATCH_INVARIANT=1,PYTHONHASHSEED=<seed>
These are forwarded to every Ray actor via verl’s get_ppo_ray_runtime_env (the runtime_env forward-list). They must be set before ray.init() so actors inherit them — too late if only set inside _post_init. The NaiveRouter subprocess (a multiprocessing.Process forked from an actor) reads VERL_FULL_DETERMINISM to activate crc32 routing, and the vLLM server reads VLLM_BATCH_INVARIANT to activate batch invariance.
Only switch flags are exported here (mirroring verl’s main_ppo); the floating-point vars (CUBLAS_WORKSPACE_CONFIG, FLASH_ATTENTION_DETERMINISTIC, NCCL_DETERMINISTIC, NCCL_ALGO, NCCL_PROTO, VERL_DISABLE_FLASH_ATTN_CE) are set inside each actor’s enable_full_determinism(), so they do not pollute the normal training flow’s environment.
1. Batch invariance
VLLM_BATCH_INVARIANT=1 makes a request’s output independent of which other requests are co-batched with it. This applies to the generative /v1/chat/completions path the GRM uses (unlike vLLM’s /classify pooling endpoint, which is not covered by batch invariance — that path would require serializing with max_num_seqs=1; verl-omni’s GRM does not use it).
Coverage is model- and hardware-dependent — see the vLLM batch invariance docs (and tested models). If not covered, set max_num_seqs=1 to serialize.
2. Floating-point determinism
The RM server (vLLMOmniHttpServer, reached via rollout.name=vllm_omni) applies enable_full_determinism(seed) in _post_init, before model load:
Setting |
Effect |
|---|---|
|
freezes Python |
|
deterministic cuBLAS |
|
deterministic flash-attn |
|
deterministic NCCL |
|
bypass the non-deterministic flash-attn Triton cross-entropy kernel in |
|
read by rollout worker subprocesses (verl |
seeded |
fixed RNG state |
|
deterministic ops |
|
deterministic cuDNN |
full_determinism/seed live on the reward_model block (a non-structured config node), so the trainer propagates them onto reward_model.rollout via OmegaConf.open_dict — the server process’s self.config is the rollout config and can read them.
Each RM replica uses the same seed (not replica_rank + seed), so every replica starts from identical state.
3. Deterministic routing
The reward router (verl’s NaiveRouter) is least-loaded; under VERL_FULL_DETERMINISM=1 it tie-breaks among equally-loaded replicas with crc32(request_body) % len(candidates), so the same request lands on the same replica across runs. Combined with identical per-replica seeds and batch invariance, the same request yields the same output regardless of which replica serves it.
4. Per-request sampling seed
VisualRewardManager injects seed = reward_model.rollout.seed into each GRM request’s sampling_params. vLLM’s OpenAI-compatible endpoint honors the seed field, so the same image + same sampling params + same seed yields the same transcription, and therefore the same reward score. This is controlled pseudorandom sampling, not greedy — temperature/top_k/top_p are respected, and the seed makes the randomness reproducible.
do_sample is not a valid vLLM SamplingParams key (silently ignored), so it is not passed. max_tokens is always supplied (default 4096) so generation length is stable.
VLM scoring path
Enabling the RM requires an explicit reward.custom_reward_function — the trainer raises ValueError when reward.reward_model.enable=true without one. Use the OCR GRM scorer (verl_omni/utils/reward_score/genrm_ocr.py::compute_score_ocr) or provide your own scorer via path/name.
Side Effects
Performance: deterministic kernels are slower and cuDNN benchmarking is disabled; expect throughput loss.
Recommendation: enable for debugging, regression testing, or research only.
Limitations
Batch invariance not directly asserted:
genrm_ocrsends one image per/v1/chat/completionsrequest serially, so requests are not explicitly co-batched andVLLM_BATCH_INVARIANTcannot be reliably triggered or observed in the test. Batch invariance is still set by_post_initfor correctness; its coverage is model/hardware-dependent (see vLLM batch invariance docs).Nondeterministic fallbacks: some GPU ops have no deterministic implementation.
warn_only=Trueemits warnings; these are expected.Multi-replica routing not asserted: every replica uses the same seed, so replica outputs are identical regardless of routing, and crc32 routing (gated on
VERL_FULL_DETERMINISM) cannot be distinguished from least-loaded routing by the assertions. Routing correctness is guaranteed by code, not by the test suite.Hardware: deterministic ops and batch invariance require specific hardware — see the vLLM batch invariance docs. On unsupported hardware, set
max_num_seqs=1.Generative GRM only: this covers the generative VLM reward path. Full E2E determinism (rollout + diffusion sampling) is future work.
Verify
pytest tests/reward_loop/test_visual_reward_manager.py -v -s -k deterministic_reward_reproducibility
Each run spins up its own RewardLoopManager (fresh RM server process(es) via ray.init/ray.shutdown). Floating-point determinism (enable_full_determinism), VERL_SEED, and VLLM_BATCH_INVARIANT=1 are all set by the RM server’s _post_init (gated on full_determinism=true in the rollout config, which the test propagates via OmegaConf.open_dict), so no determinism env vars are needed in the Ray runtime_env. Two variants cover single replica (test_deterministic_reward_reproducibility_single_replica) and multi replica (test_deterministic_reward_reproducibility_multi_replica). Each asserts:
Same seed → bitwise-aligned: two independent runs with the same seed produce bitwise-equal
rm_scores(torch.equal) and identicalgenrm_response.