Full Determinism for Reproducible RL Training
Authors: Haichuan Hu, Yongxiang Huang, Jiawei Zhang, Nguyen Long
Last updated: 06/16/2026.
Overview
By default, RL training in verl is not bitwise reproducible: identical configs run twice can produce different reward curves due to nondeterminism in GPU kernels, request scheduling, hash-based routing, and batch composition. The full determinism feature closes these gaps, enabling two identical runs to produce bitwise-aligned reward curves.
Useful for:
Debugging: reproduce a training failure exactly, step-by-step
Regression testing: verify that a code change has no silent effect on training outcomes
Research: ensure fair comparison when evaluating algorithmic changes
Quick Start
full_determinism is supported on all training engines (FSDP, Megatron, etc.) via each engine’s config. The example below uses FSDP; for Megatron set actor_rollout_ref.actor.megatron_config.full_determinism=true (and similarly for the ref model).
actor_rollout_ref:
rollout:
full_determinism: true
seed: 42
# If the policy model is not covered by vLLM batch invariance, set to 1.
# max_num_seqs: 1
actor:
fsdp_config:
full_determinism: true
ref:
fsdp_config:
full_determinism: true
reward:
reward_model:
enable: true
rollout:
full_determinism: true
seed: 42
# If the RM model is not covered by vLLM batch invariance, set to 1.
# max_num_seqs: 1
trainer:
# REQUIRED: use the V0 trainer backend. The default V1 backend collects rollout
# outputs in completion order (via a TransferQueue), which varies across runs and
# breaks bitwise reproducibility. V0 uses asyncio.gather (submission order).
use_v1: false
Both actor rollout and generative RM (VLM scoring) rely on vLLM batch invariance for co-batching. If your model or hardware is not covered, set max_num_seqs=1 to serialize. Discriminative RM (score-outputting, e.g. Skywork-Reward) is forced to 1 automatically (see Reward model routing).
Or via Hydra overrides:
python -m verl.trainer.main_ppo \
actor_rollout_ref.rollout.full_determinism=true \
actor_rollout_ref.rollout.seed=42 \
actor_rollout_ref.actor.fsdp_config.full_determinism=true \
actor_rollout_ref.ref.fsdp_config.full_determinism=true \
reward.reward_model.enable=true \
reward.reward_model.rollout.full_determinism=true \
reward.reward_model.rollout.seed=42 \
trainer.use_v1=false \
[other config overrides...]
If the policy or RM model is not covered by vLLM batch invariance, add actor_rollout_ref.rollout.max_num_seqs=1 and/or reward.reward_model.rollout.max_num_seqs=1 to serialize. Discriminative RM (score-outputting) is forced to 1 automatically.
Configuration Reference
Parameter |
Default |
Scope |
Description |
|---|---|---|---|
|
|
Rollout |
Enables deterministic rollout generation |
|
|
Rollout |
Set to |
|
|
Rollout |
Base seed; each replica uses |
|
|
Actor |
Enables deterministic PyTorch ops for actor |
|
|
Ref model |
Enables deterministic PyTorch ops for reference model |
|
|
Reward model |
Enables deterministic RM inference |
|
|
Reward model |
Discriminative RM forced to 1 under full_determinism; set to 1 for generative RM if not covered by batch invariance |
|
|
Reward model |
Base seed for RM vLLM server |
|
|
Trainer |
Must be |
How It Works
full_determinism=true is enforced at five layers. The training entrypoint sets PYTHONHASHSEED (from rollout.seed), VERL_FULL_DETERMINISM, VLLM_BATCH_INVARIANT, and the NCCL/cuBLAS determinism env vars (CUBLAS_WORKSPACE_CONFIG, FLASH_ATTENTION_DETERMINISTIC, NCCL_DETERMINISTIC, NCCL_ALGO) before ray.init() and forwards them to all Ray actors via PPO_RAY_RUNTIME_ENV. These must be set before torch/NCCL init, so the entrypoint exports them rather than enable_full_determinism() (which runs after the actor is already up). Do NOT set PYTHONHASHSEED manually — the entrypoint handles it.
Floating-point determinism
enable_full_determinism(seed) sets CUBLAS_WORKSPACE_CONFIG, FLASH_ATTENTION_DETERMINISTIC, NCCL_DETERMINISTIC, NCCL_ALGO, seeds all RNGs, calls torch.use_deterministic_algorithms(True, warn_only=True), and disables cuDNN benchmarking. Applied in all training engine implementations (FSDP, Megatron, etc.). The flash-attn Triton cross-entropy kernel (used to compute log-probs) has a non-deterministic reduction not covered by the env vars above, so VERL_DISABLE_FLASH_ATTN_CE=1 is set by default to force the pure-PyTorch log_softmax+gather path.
Sampling seeds
Replica seed: each replica uses
replica_rank + config.seed, producing different but internally reproducible outputs across replicas. Two runs with the same config produce bitwise-aligned results.Per-request seed: each
generate()call injectsSamplingParams.seed = replica_rank + config.seedto reset the sampler RNG per request, so the same prompt+seed yields the same tokens regardless of batch.
Deterministic routing
Actor rollout:
SingleTurnAgentLoopusesrequest_id=f"det-{priority}"(priority fromnon_tensor_batch["priority"]), andGlobalRequestLoadBalancer(withfull_determinism=True) routes withhash(request_id) % len(servers)— the same request always routes to the same vLLM server across runs. (priorityis vLLM-only;LLMServerClient.generate()filters it for non-vLLM backends.) Whenfull_determinism=True,LLMServerClient._vllm_request_id()forwards this stablerequest_idto vLLM itself.Reward:
NaiveRouterroutes withbinascii.crc32(request body) % len(candidates)among equally-loaded RM replicas, so the same reward request always routes to the same replica. This neutralizes replica-level floating-point differences that seed alone cannot equalize.
Trainer backend (V0 required)
Full determinism requires the V0 trainer backend (trainer.use_v1=false, i.e. AgentLoopManager). The default V1 backend (AgentLoopManagerTQ) decouples rollout submission from collection via a fire-and-forget TransferQueue: outputs are collected in completion order, which depends on per-request latency and varies across runs, so the output concatenation order (and thus the training batch) differs run-to-run. V0 instead uses asyncio.gather, which returns outputs in submission order — stable across runs.
Set trainer.use_v1=false explicitly when enabling full_determinism.
Batch invariance
VLLM_BATCH_INVARIANT=1 makes vLLM outputs independent of batch composition. 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.
For reward specifically:
Discriminative RM (score-outputting, e.g. Skywork-Reward; no custom reward fn):
max_num_seqsis forced to 1 — batch invariance is verified on generation models, not score-outputting RM architectures.Generative RM (VLM that outputs text scores, via custom reward fn):
max_num_seqsis not forced — user-managed; rely on batch invariance + per-request seed.
Side Effects
Performance: deterministic PyTorch kernels are slower and cuDNN benchmarking is disabled. Discriminative RM is serialized (
max_num_seqs=1) under full_determinism.Recommendation: Only enable for debugging, regression testing, or research. Leave disabled for production training.
Limitations
Hardware: vLLM batch invariance (and some deterministic GPU ops) requires specific hardware — see the vLLM batch invariance docs for requirements. On unsupported hardware, set
max_num_seqs=1to serialize.torch.use_deterministic_algorithms(True, warn_only=True)warns when a deterministic kernel is unavailable.Backend: only vLLM is supported.
Trainer backend: the V0 trainer (
trainer.use_v1=false) is required. The default V1 backend’s TransferQueue collects outputs in completion order, which is nondeterministic (see Trainer backend above).Multi-turn agent: not supported. Full determinism only works for single-turn rollouts (
single_turn_agent_loop). Multi-turn rollouts (tool_agent_loop) are not bitwise reproducible —tool_agent_loopuses a random UUID per trajectory asrequest_id, does not passpriority, and each turn is interleaved with external tool calls whose timing varies across runs. Usesingle_turn_agent_loopfor bitwise-reproducible rollouts.
Verifying Determinism
Rollout determinism (bitwise reproducible vLLM generation):
VLLM_DETERMINISM_DENSE_MODEL_PATH=${HOME}/models/Qwen/Qwen2.5-0.5B-Instruct \
VLLM_DETERMINISM_N_GPUS=2 \
pytest tests/workers/rollout/rollout_vllm/test_vllm_generation_determinism.py -v -s
E2E training (bitwise-aligned reward curves across two full PPO runs). Runs PPO twice with identical seeds, compares per-step reward curves in float32; exit code 0 = aligned, 1 = not aligned / error (usable as a CI gate):
python tests/experimental/reward_loop/run_determinism_e2e_with_rm.py \
--policy_model ~/models/Qwen/Qwen2.5-0.5B-Instruct \
--rm_model ~/models/Skywork/Skywork-Reward-V2-Llama-3.2-1B \
--train_files ~/data/gsm8k/train.parquet \
--val_files ~/data/gsm8k/test.parquet \
--n_gpus 2 --n_steps 2
See the script’s module docstring for the full argument reference (e.g. --plot, --save_metrics, multi-replica coverage, and why the RM max_num_seqs is forced to 1 for discriminative RMs but allowed >1 for generative RMs).