Skip to content

feat(sglang): plumb per-request sampling_seed for replayable sampling - #1524

Open
SushantDaga wants to merge 7 commits into
areal-project:mainfrom
SushantDaga:keyed-rollouts
Open

feat(sglang): plumb per-request sampling_seed for replayable sampling#1524
SushantDaga wants to merge 7 commits into
areal-project:mainfrom
SushantDaga:keyed-rollouts

Conversation

@SushantDaga

@SushantDaga SushantDaga commented Jul 13, 2026

Copy link
Copy Markdown

Description short

Add an opt-in, default-off sampling_seed field to GenerationHyperparameters and an enable_deterministic_inference flag to SGLangConfig, forwarded into SGLang's sampling_params by both the v1 (SGLangBackend) and v2 (SGLangBridgeBackend) request builders. SGLang already ships a production per-request seeded sampler (multinomial_with_seed) documented for RL/GRPO debugging; AReaL never sent it.

Both vLLM request builders (VLLMBackend, VLLMBridgeBackend) raise NotImplementedError on sampling_seed rather than silently ignoring it, and PPOConfig.post_init warns at config-construction time if the seed is set without the server flag.

Scoped to primitive plumbing only. Per-rollout seed minting (threading a sample identity through RolloutWorkflow/GroupedRolloutWorkflow so a GRPO group's K samples get distinct seeds) is deferred as an open design question, not resolved in this PR; see the PR description for why, and for a known footgun if sampling_seed is set naively on a multi-sample workflow.

11 new tests; no regressions in the 261 pre-existing tests for touched files. docs/en|zh/cli_reference.md regenerated for the two new fields.

Related Issue

#1523

Description, detailed

This PR plumbs SGLang's existing per-request sampling_seed primitive through AReaL's rollout path.

SGLang's inference server (AReaL's default backend) already ships a production per-request seeded sampler (multinomial_with_seed: murmur-hashed (seed, position, vocab-index) → Gumbel → argmax), documented by SGLang specifically for RL/GRPO debugging ("diverse yet reproducible responses"; docs.sglang.io/advanced_features/deterministic_inference.html). It's gated on a per-request sampling_params["sampling_seed"] plus the server flag --enable-deterministic-inference. AReaL currently sends neither: sample_params in build_generation_request has no seed key, and GenerationHyperparameters has no seed field. This PR is just the plumbing so the primitive is reachable from AReaL configs.

Small, additive, default-off changes; when unset, no request, config, or behavior changes in any way:

  1. GenerationHyperparameters.sampling_seed: int | None = None (areal/api/cli_args.py), a new optional per-request field, added to _OPENAI_UNSUPPORTED_ARGS since it has no OpenAI-API equivalent.
  2. SGLangConfig.enable_deterministic_inference: bool = False (areal/api/cli_args.py), flows through the existing conf_as_dict() to CLI-flag passthrough with no further wiring; matches SGLang's own ServerArgs.enable_deterministic_inference field name.
  3. Both SGLang request builders forward gconfig.sampling_seed into sampling_params["sampling_seed"] when set: SGLangBackend.build_generation_request (areal/engine/sglang_remote.py, the v1 remote-engine path) and SGLangBridgeBackend.build_generation_request (areal/v2/inference_service/sglang/bridge.py, the v2 data-proxy path, which mirrors the v1 backend by its own docstring and would otherwise have silently diverged).
  4. Both vLLM request builders now raise NotImplementedError if sampling_seed is set: VLLMBackend.build_generation_request (areal/engine/vllm_remote.py) and VLLMBridgeBackend.build_generation_request (areal/v2/inference_service/vllm/bridge.py). There's no vLLM wiring in this PR (see "Not the vLLM path" below), so failing loudly here matches how SGLangBackend already rejects its own unsupported use_beam_search. That's better than silently no-op'ing and leaving a caller believing their vLLM rollouts are seeded when they aren't.
  5. PPOConfig.__post_init__ (areal/api/cli_args.py) warns if gconfig.sampling_seed is set but sglang.enable_deterministic_inference is False; see "No request-time warning" below for why this only catches the common case, not every deployment shape.
  6. docs/en/cli_reference.md / docs/zh/cli_reference.md regenerated to include both new fields (uv run python docs/generate_cli_docs.py).

That's it. This PR does not touch workflows, the training loop, schedulers, or weight updates.

Known limitation, please read before trying this on a GRPO group: this field has no in-repo consumer yet, and its one obvious naive use is a footgun worth stating plainly rather than leaving for someone to discover the hard way. AReaL's inference engine rejects n_samples > 1 outright (RemoteInfEngine.agenerate, "Inference engines do not support n_samples > 1. Please call generate multiple times with n_samples = 1."), so a GRPO group's K samples of one prompt are produced by calling generate K times, each building its request from self.gconfig.new(n_samples=1) (e.g. RLVRWorkflow.arun_episode, VisionRLVRWorkflow.arun_episode, MultiTurnWorkflow.arun_episode). .new() is asdict(self) plus overrides, so if a caller sets sampling_seed once on a workflow's gconfig, every one of that group's K rollouts inherits the identical seed. With enable_deterministic_inference=True and an identical prompt, SGLang's seeded sampler is then a pure function of (seed, position, vocab) for all K calls, so the group collapses to byte-identical completions and GRPO's advantage degenerates to zero.

This is exactly the failure mode the deferred per-rollout seed-minting (below) exists to prevent. Read the field as "the wire," not yet "a feature you can turn on for GRPO today." Until group-aware seed minting lands (in this PR or a follow-up, your call), the safe use is a fixed seed across a batch of distinct prompts (e.g. a held-out eval set), not repeated same-prompt samples.

What this deliberately does NOT do (open questions, not resolved here):

  • No automatic per-rollout seed minting. A useful pattern is deriving each request's seed from a stable identity (e.g. H(master_seed, prompt_hash, group_index)) so a GRPO group's K samples of one prompt get diverse-but-reproducible seeds instead of colliding on SGLang's default seed (worth flagging: under --enable-deterministic-inference, SGLang assigns seed 42 to any request that doesn't carry its own, see sampling_batch_info.py, so same-prompt group members would otherwise share identical noise). Doing this generically means threading a distinguishing index through GroupedRolloutWorkflow.arun_episode and by extension the RolloutWorkflow ABC (areal/api/workflow_api.py), which ~9 concrete workflows implement, including experimental ones. That's a design/API decision, not something this PR should decide unilaterally. Happy to build it however you'd place it (kwarg, contextvar, data field) once there's agreement.
  • No logging of the seed into the stored rollout batch. Once seeds are minted somewhere, logging sampling_seed beside the per-token versions AReaL already stores (ModelResponse.output_versions) would give a rollout a full replay coordinate. Deferred for the same reason as above; it depends on where minting lands.
  • No request-time warning when sampling_seed is set but the server flag is off. SGLangBackend is stateless (no config reference) and SGLangConfig (launch-time, server-side) isn't available where build_generation_request runs (client-side, and can legitimately be a different process from whatever launched the server); wiring a check there needs new cross-process config plumbing, out of scope here. What this PR does add: PPOConfig.__post_init__ warns at config-construction time if gconfig.sampling_seed is set but sglang.enable_deterministic_inference is False, since both fields live on the same top-level config object there (mirrors the existing reward_norm/eval_gconfig cross-field checks already in __post_init__), so it catches the common case (AReaL launches the SGLang server itself from this same config) without needing the cross-process plumbing. It won't catch the case where someone points AReaL at an SGLang server they launched separately.
  • Not the vLLM path. vLLM's native per-request seed is stream-based and doesn't survive AReaL's interruption/resumption (which re-submits prompt plus generated-so-far as a new request, restarting the stream and misaligning the noise). We don't have a validated design for vLLM yet; happy to discuss if there's interest. Both vLLM builders raise NotImplementedError on sampling_seed for now rather than staying silent.
  • Not full determinism. This is sampling-layer replayability only, and only activates anything when both flags are set. Forward-pass numerics under batch-shape/kernel-scheduling changes are a separate layer that SGLang's own deterministic-mode kernels address (at SGLang's documented cost, which is why both flags here are opt-in).

A question: under --enable-deterministic-inference, SGLang assigns sampling_seed=42 to any request that doesn't carry its own. Since the noise is a pure function of (seed, position, vocab-index), doesn't that mean a GRPO group's same-prompt members all get identical noise today if this flag is ever turned on, collapsing group diversity? If so, per-rollout seeds aren't just a debugging nicety, they're a prerequisite for using this flag with GRPO at all. Happy to be corrected if I'm misreading sampling_batch_info.py.

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 💥 Breaking change
  • 📝 Documentation update
  • ♻️ Refactoring
  • ⚡ Performance improvement
  • ✅ Test coverage improvement

Checklist

  • I have read the Contributing Guide
  • Pre-commit hooks pass (pre-commit run --files <touched files>; ran on every file this PR touches)
  • Relevant tests pass; new tests added for new functionality
  • Documentation updated (docs/en/cli_reference.md / docs/zh/cli_reference.md regenerated via docs/generate_cli_docs.py)
  • Branch is up to date with main
  • Self-reviewed via /review-pr command (ran, findings addressed in follow-up commits)
  • This PR was created by a coding agent via /create-pr
  • This PR is a breaking change

Additional Context

Update: Ran /review-pr (this repo's project-specific review) plus addressed two
gemini-code-assist findings -- fixed a backend-prefix parsing gap that missed
bracket-named vLLM backends (vllm[name]:dims), corrected a couple of misleading
comments, and removed dead text from the deterministic-inference warning message.

Tests: eleven new unit tests, pure config/dict assertions, no GPU, no live SGLang/vLLM server:

  • tests/test_sglang_generation_request.py (new, 2 tests): sampling_seed reaches sample_params when set on SGLangBackend, is absent when None.
  • tests/test_sglang_bridge_generation_request.py (new, 2 tests): same, for SGLangBridgeBackend (the v2 path).
  • tests/test_sglang_deterministic_inference_flag.py (new, 2 tests): enable_deterministic_inference=True produces --enable-deterministic-inference via the real SGLangConfig.build_cmd/build_args path; False (default) omits it. (build_args asserts the installed sglang package version, which isn't installed on a non-GPU dev machine; the test monkeypatches that one check and exercises the actual production code path otherwise.)
  • tests/test_vllm_generation_request.py (extended, +1 test): VLLMBackend raises NotImplementedError when sampling_seed is set.
  • tests/v2/inference_service/test_inf_bridge.py (extended, +1 test): same, for VLLMBridgeBackend.
  • tests/test_sampling_seed_deterministic_inference_warning.py (new, 3 tests): PPOConfig warns when sampling_seed is set without enable_deterministic_inference, and doesn't warn when they're consistent or the seed is unset.

All 11 new tests pass locally, alongside the full pre-existing suites for every touched file (261 total, no regressions):

uv sync --group dev && uv run pytest tests/test_sglang_generation_request.py \
  tests/test_sglang_deterministic_inference_flag.py tests/test_vllm_generation_request.py \
  tests/test_sglang_bridge_generation_request.py tests/test_sampling_seed_deterministic_inference_warning.py \
  tests/v2/inference_service/test_inf_bridge.py tests/test_adv_norm_config.py \
  tests/infra/data_service/test_controller.py -v

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for a per-request sampling_seed for replayable sampling on the SGLang backend, along with an enable_deterministic_inference flag in SGLangConfig to ensure SGLang honors the seed. If sampling_seed is set but deterministic inference is disabled, a warning is raised, while the vLLM backend raises a NotImplementedError if a seed is provided. The review feedback points out that the warning in __post_init__ should also check eval_gconfig.sampling_seed in addition to gconfig.sampling_seed, and suggests adding a corresponding test case to verify this behavior.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread areal/api/cli_args.py
Comment on lines +3191 to +3204
if (
self.gconfig.sampling_seed is not None
and not self.sglang.enable_deterministic_inference
):
warnings.warn(
"gconfig.sampling_seed is set but sglang.enable_deterministic_inference "
"is False: SGLang silently ignores per-request sampling_seed unless the "
"server is launched with --enable-deterministic-inference. Rollouts will "
"not be seeded as expected. (Not applicable if you're launching SGLang "
"servers yourself outside this config, or using the vLLM backend, which "
"rejects sampling_seed outright.)",
UserWarning,
stacklevel=2,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The warning only checks self.gconfig.sampling_seed, but self.eval_gconfig can also have sampling_seed set. If self.eval_gconfig.sampling_seed is set without sglang.enable_deterministic_inference being enabled, the evaluation rollouts will silently run without deterministic sampling. We should check both self.gconfig.sampling_seed and self.eval_gconfig.sampling_seed.

Suggested change
if (
self.gconfig.sampling_seed is not None
and not self.sglang.enable_deterministic_inference
):
warnings.warn(
"gconfig.sampling_seed is set but sglang.enable_deterministic_inference "
"is False: SGLang silently ignores per-request sampling_seed unless the "
"server is launched with --enable-deterministic-inference. Rollouts will "
"not be seeded as expected. (Not applicable if you're launching SGLang "
"servers yourself outside this config, or using the vLLM backend, which "
"rejects sampling_seed outright.)",
UserWarning,
stacklevel=2,
)
if (
(self.gconfig.sampling_seed is not None or self.eval_gconfig.sampling_seed is not None)
and not self.sglang.enable_deterministic_inference
):
warnings.warn(
"gconfig.sampling_seed or eval_gconfig.sampling_seed is set but sglang.enable_deterministic_inference "
"is False: SGLang silently ignores per-request sampling_seed unless the "
"server is launched with --enable-deterministic-inference. Rollouts will "
"not be seeded as expected. (Not applicable if you're launching SGLang "
"servers yourself outside this config, or using the vLLM backend, which "
"rejects sampling_seed outright.)",
UserWarning,
stacklevel=2,
)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed as suggested. Also left a comment noting eval_gconfig is currently only read for .n_samples elsewhere in the codebase, so eval_gconfig.sampling_seed doesn't reach SGLang yet either way, this check is honest about the flag mismatch, not evidence the field does anything.

trial_name="trial",
gconfig=GenerationHyperparameters(sampling_seed=42),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since eval_gconfig can also have sampling_seed set, we should add a test case to verify that PPOConfig warns when eval_gconfig.sampling_seed is set without deterministic inference enabled.

        )


def test_ppo_config_warns_when_eval_sampling_seed_set_without_deterministic_inference():
    with pytest.warns(UserWarning, match="sampling_seed is set but"):
        PPOConfig(
            experiment_name="exp",
            trial_name="trial",
            eval_gconfig=GenerationHyperparameters(sampling_seed=42),
        )

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added, thanks: test_ppo_config_warns_when_eval_sampling_seed_set_without_deterministic_inference, plus a "does not warn when consistent" case for symmetry.

@SushantDaga

Copy link
Copy Markdown
Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds support for a per-request sampling_seed parameter to enable replayable sampling on the SGLang backend, along with a new enable_deterministic_inference configuration option. It also implements validation warnings for misconfigured seeds and raises a NotImplementedError on the vLLM backend where the parameter is unsupported. The feedback highlights a potential AttributeError in areal/api/cli_args.py if self.sglang is None during configuration validation, and suggests a robust guard to prevent initialization crashes.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread areal/api/cli_args.py Outdated
Comment on lines +3196 to +3199
if (
self.gconfig.sampling_seed is not None
or self.eval_gconfig.sampling_seed is not None
) and not self.sglang.enable_deterministic_inference:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If self.sglang is configured as null (which is common when using alternative backends like vLLM to avoid SGLang configuration overhead), accessing self.sglang.enable_deterministic_inference will raise an AttributeError. Guarding against None ensures the configuration initialization is robust and doesn't crash.

        sglang_deterministic = (
            self.sglang.enable_deterministic_inference
            if self.sglang is not None
            else False
        )
        if (
            self.gconfig.sampling_seed is not None
            or self.eval_gconfig.sampling_seed is not None
        ) and not sglang_deterministic:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is reachable if PPOConfig is constructed directly in Python with sglang=None, bypassing the loader (dataclasses don't enforce type hints at runtime). Adding the guard and a test for that case specifically.

@SushantDaga

Copy link
Copy Markdown
Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds support for a per-request sampling_seed parameter in the SGLang backend, which requires enabling the new enable_deterministic_inference flag. It also explicitly rejects sampling_seed with a NotImplementedError on the vLLM backend. The reviewer suggests validating the unsupported sampling_seed for the vLLM backend early during configuration initialization in PPOConfig.__post_init__ to prevent wasting resources before starting servers.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread areal/api/cli_args.py
Comment on lines +3205 to +3219
if (
self.gconfig.sampling_seed is not None
or self.eval_gconfig.sampling_seed is not None
) and not sglang_deterministic:
warnings.warn(
"gconfig.sampling_seed or eval_gconfig.sampling_seed is set but "
"sglang.enable_deterministic_inference is False: SGLang silently "
"ignores per-request sampling_seed unless the server is launched "
"with --enable-deterministic-inference. Rollouts will not be seeded "
"as expected. (Not applicable if you're launching SGLang servers "
"yourself outside this config, or using the vLLM backend, which "
"rejects sampling_seed outright.)",
UserWarning,
stacklevel=2,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of waiting for a runtime NotImplementedError when the first generation request is built, we should validate that sampling_seed is not used with the vllm backend during configuration initialization in PPOConfig.__post_init__. This prevents wasting time and resources (such as starting up servers and loading models) on an unsupported configuration.

        rollout_backend = getattr(self.rollout, "backend", None) if self.rollout is not None else None
        if rollout_backend is not None and rollout_backend is not MISSING and "vllm" in rollout_backend:
            if (
                self.gconfig.sampling_seed is not None
                or self.eval_gconfig.sampling_seed is not None
            ):
                raise ValueError(
                    "gconfig.sampling_seed or eval_gconfig.sampling_seed is set, "
                    "but the vLLM backend does not support sampling_seed."
                )
        if (
            self.gconfig.sampling_seed is not None
            or self.eval_gconfig.sampling_seed is not None
        ) and not sglang_deterministic:
            warnings.warn(
                "gconfig.sampling_seed or eval_gconfig.sampling_seed is set but "
                "sglang.enable_deterministic_inference is False: SGLang silently "
                "ignores per-request sampling_seed unless the server is launched "
                "with --enable-deterministic-inference. Rollouts will not be seeded "
                "as expected. (Not applicable if you're launching SGLang servers "
                "yourself outside this config.)",
                UserWarning,
                stacklevel=2,
            )

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already handled — PPOConfig.post_init now raises a ValueError when rollout.backend is vLLM and sampling_seed is set (see the check immediately above this warning). This comment was made against an earlier revision of the diff, before that check was added. No further action needed here.

@SushantDaga
SushantDaga marked this pull request as draft July 13, 2026 12:21
@SushantDaga

Copy link
Copy Markdown
Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a sampling_seed parameter to GenerationHyperparameters to support replayable sampling, along with a new enable_deterministic_inference flag for SGLang. It also adds validation to raise errors when sampling_seed is used with the unsupported vLLM backend, and includes comprehensive tests and documentation updates. The review feedback suggests mapping sampling_seed to seed for OpenAI-compatible backends instead of marking it as unsupported, and recommends removing unreachable mentions of vLLM from the SGLang warning message.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread areal/api/cli_args.py
Comment on lines 289 to 293
"lora_name", # Not supported by OpenAI
"use_beam_search", # Not supported by OpenAI
"max_tokens", # deprecated by "completions", not used in "responses", should be `max_new_tokens` in "openai-agents"
"sampling_seed", # SGLang-specific; not an OpenAI-compatible parameter
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

OpenAI's Chat Completions and Completions APIs actually support a seed parameter for deterministic sampling. Instead of treating sampling_seed as unsupported by OpenAI, we can map it to seed in to_openai_args_dict so that OpenAI-compatible backends can also benefit from deterministic sampling.

To do this:

  1. Remove "sampling_seed" from _OPENAI_UNSUPPORTED_ARGS (as suggested below).
  2. Map "sampling_seed" to "seed" in to_openai_args_dict's mapping dictionary (around line 303):
        mapping = {"n_samples": "n", "sampling_seed": "seed"}
        "lora_name",  # Not supported by OpenAI
        "use_beam_search",  # Not supported by OpenAI
        "max_tokens",  # deprecated by "completions", not used in "responses", should be max_new_tokens in "openai-agents"
    }

@SushantDaga SushantDaga Jul 13, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion, but I don't think this is safe as proposed. to_openai_args_dict/to_openai_agents_model_settings_dict are outbound-only converters used to hand GenerationHyperparameters to the external openai-agents SDK, they're not part of AReaL's own request-building path. ArealOpenAI.create() builds a fresh GenerationHyperparameters from its own explicit kwargs and never reads sampling_seed/seed back in, so mapping it here wouldn't actually make AReaL's own rollouts seeded.

More importantly, if a mapped seed value ever reached a real OpenAI-compatible endpoint via the openai-agents SDK backed by vLLM, it would hit exactly the problem called out under "Not the vLLM path" in the PR description: vLLM's own per-request seed is stream-based and doesn't survive AReaL's interruption/resumption re-submits, so it wouldn't give the reproducibility guarantee the name implies. sampling_seed here is deliberately scoped to SGLang's own sampling_params['sampling_seed'] + --enable-deterministic-inference mechanism, which is why it's excluded rather than remapped. Happy to revisit once there's a validated vLLM-seed design.

Comment thread areal/api/cli_args.py
Comment on lines +3233 to +3243
warnings.warn(
"gconfig.sampling_seed or eval_gconfig.sampling_seed is set but "
"sglang.enable_deterministic_inference is False: SGLang silently "
"ignores per-request sampling_seed unless the server is launched "
"with --enable-deterministic-inference. Rollouts will not be seeded "
"as expected. (Not applicable if you're launching SGLang servers "
"yourself outside this config, or using the vLLM backend, which "
"rejects sampling_seed outright.)",
UserWarning,
stacklevel=2,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since the vLLM backend check above raises a ValueError and terminates execution immediately if sampling_seed is set, the warning's mention of or using the vLLM backend, which rejects sampling_seed outright is unreachable dead code. Removing this part makes the warning message cleaner and more accurate.

            warnings.warn(
                "gconfig.sampling_seed or eval_gconfig.sampling_seed is set but "
                "sglang.enable_deterministic_inference is False: SGLang silently "
                "ignores per-request sampling_seed unless the server is launched "
                "with --enable-deterministic-inference. Rollouts will not be seeded "
                "as expected. (Not applicable if you're launching SGLang servers "
                "yourself outside this config.)",
                UserWarning,
                stacklevel=2,
            )

SushantDaga added a commit to SushantDaga/AReaL that referenced this pull request Jul 13, 2026
The deterministic-inference UserWarning only ever fires when
rollout.backend is not vLLM: the ValueError check immediately above
it already raises and aborts construction whenever both a vLLM
backend and a sampling_seed are set, regardless of the SGLang
deterministic-inference flag. The warning text's parenthetical
"...or using the vLLM backend, which rejects sampling_seed outright"
therefore described a state that can never co-occur with the warning
itself, per gemini-code-assist feedback on PR areal-project#1524. Reworded to
state that plainly instead of implying it's a live alternative case.

Refs: areal-project#1523
@SushantDaga

Copy link
Copy Markdown
Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a sampling_seed parameter to GenerationHyperparameters for replayable sampling, along with an enable_deterministic_inference flag in SGLangConfig. It implements seed forwarding for the SGLang remote engine and bridge, while raising a NotImplementedError for the vLLM backend. Additionally, validation is added to PPOConfig to warn or fail early on misconfigurations. The reviewer suggests mapping sampling_seed to seed in to_openai_args_dict instead of marking it as unsupported, since OpenAI's API natively supports a seed parameter.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread areal/api/cli_args.py
"lora_name", # Not supported by OpenAI
"use_beam_search", # Not supported by OpenAI
"max_tokens", # deprecated by "completions", not used in "responses", should be `max_new_tokens` in "openai-agents"
"sampling_seed", # SGLang-specific; not an OpenAI-compatible parameter

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

OpenAI's Chat Completions and Completions APIs actually support a seed parameter (introduced in late 2023) to make a best effort to sample deterministically.

Instead of marking sampling_seed as an unsupported OpenAI argument, it would be highly beneficial to map it to seed in to_openai_args_dict so that any OpenAI-compatible backends can also leverage this parameter.

You can achieve this by:

  1. Removing "sampling_seed" from _OPENAI_UNSUPPORTED_ARGS.
  2. Adding "sampling_seed": "seed" to the mapping dictionary inside to_openai_args_dict.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

already replied above why this is not a good idea.

@SushantDaga
SushantDaga marked this pull request as ready for review July 13, 2026 13:21
@SushantDaga SushantDaga changed the title feat(sglang): Keyed (replayable) rollout sampling, plumb per-request sampling_seed through rollout path feat(sglang): plumb per-request sampling_seed for replayable sampling Jul 13, 2026
@github-actions

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity within the last 14 days.

Please add a comment or push new commits to keep it active.

Thank you for your contribution!

@github-actions github-actions Bot added the stale label Jul 28, 2026
Comment thread areal/api/cli_args.py
if self.sglang is not None
else False
)
if (

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there should be a check here: when the flag is enabled, no seed is set, and gconfig.n_samples > 1, all requests fall back to SGLang's default seed 42, sharing the same noise across prompts, leading to diversity collapse within the group.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, thanks. That confirms the open question at the end of the PR description. I've added the guard in PPOConfig.__post_init__ at this spot.

I made it a ValueError rather than a warning. My reasoning, and the alternative I weighed:

  • This combination (enable_deterministic_inference on, n_samples > 1, no sampling_seed) has no working use. The group's same-prompt rollouts collapse to identical completions and GRPO's advantage goes to zero, so it quietly defeats the flag's own purpose.
  • enable_deterministic_inference is new in this PR and defaults to off, so failing fast can't break any existing config. It's the same fail-fast this PR already does for sampling_seed on the vLLM backend.
  • The alternative was a warning, which would match the existing mean_level='group' + group_size=1 check in cli_args.py (that one warns for a similar group-quantity degeneracy). I went with raising because that check guards a pre-existing surface where a warning avoids breaking someone, whereas this flag doesn't exist until the PR merges, so there's nobody to break. Happy to switch it to a warning if you'd rather keep the two consistent.
  • It only fires for stochastic sampling. Under greedy or temperature == 0 the request builders decode at temperature 0.0, so the group collapses regardless of any seed, and the guard skips that case to avoid a misleading error. It also skips the vLLM backend, since enable_deterministic_inference is SGLang-only and inert on vLLM, so an SGLang-worded error there would just point someone at the wrong subsystem (it reuses the same rollout.backend parse the vLLM guard above already does).

Tests are in tests/test_sampling_seed_deterministic_inference_warning.py: it raises for grouped + deterministic + unseeded, and does not raise with a seed set, at n_samples == 1, under greedy or temperature == 0, or on a vLLM backend.

@sitabulaixizawaluduo sitabulaixizawaluduo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution, please rebase the code and resolve the conflicts

SushantDaga added a commit to SushantDaga/AReaL that referenced this pull request Aug 11, 2026
The deterministic-inference UserWarning only ever fires when
rollout.backend is not vLLM: the ValueError check immediately above
it already raises and aborts construction whenever both a vLLM
backend and a sampling_seed are set, regardless of the SGLang
deterministic-inference flag. The warning text's parenthetical
"...or using the vLLM backend, which rejects sampling_seed outright"
therefore described a state that can never co-occur with the warning
itself, per gemini-code-assist feedback on PR areal-project#1524. Reworded to
state that plainly instead of implying it's a live alternative case.

Refs: areal-project#1523
Signed-off-by: Sushant Daga <dsush2307@gmail.com>
@SushantDaga

SushantDaga commented Aug 11, 2026

Copy link
Copy Markdown
Author

@sitabulaixizawaluduo Thanks for the review!

Done, rebased onto latest main. The conflicts were in cli_args.py (your new reward_normalization / drop_incomplete_group fields sit right next to sampling_seed) and the two generated cli_reference.md files, which I regenerated.

Plus, added DCO to earlier commits and this one as per contribution guideline.

Add an opt-in, default-off sampling_seed field to GenerationHyperparameters
and an enable_deterministic_inference flag to SGLangConfig, forwarded into
SGLang's sampling_params by both the v1 (SGLangBackend) and v2
(SGLangBridgeBackend) request builders. SGLang already ships a production
per-request seeded sampler (multinomial_with_seed) documented for RL/GRPO
debugging; AReaL never sent it.

Both vLLM request builders (VLLMBackend, VLLMBridgeBackend) raise
NotImplementedError on sampling_seed rather than silently ignoring it, and
PPOConfig.__post_init__ warns at config-construction time if the seed is
set without the server flag.

Scoped to primitive plumbing only. Per-rollout seed minting (threading a
sample identity through RolloutWorkflow/GroupedRolloutWorkflow so a GRPO
group's K samples get distinct seeds) is deferred as an open design
question, not resolved in this PR; see the PR description for why, and for
a known footgun if sampling_seed is set naively on a multi-sample workflow.

11 new tests; no regressions in the 261 pre-existing tests for touched
files. docs/en|zh/cli_reference.md regenerated for the two new fields.

Signed-off-by: Sushant Daga <dsush2307@gmail.com>
…ic-inference warning

Gemini Code Assist flagged that PPOConfig.__post_init__'s new warning
(added in b2895ae) only checked gconfig.sampling_seed, missing the case
where eval_gconfig carries its own independent sampling_seed. Fixed as
suggested.

While verifying the fix, found that eval_gconfig is currently consumed
nowhere in the codebase except rl_trainer.py reading its n_samples field;
no stock eval workflow builds a request from eval_gconfig's other fields.
So eval_gconfig.sampling_seed never reaches SGLang regardless of this
warning. Added a code comment documenting this so "no warning fired" is
never misread as "eval_gconfig.sampling_seed works" - the check is honest
about the flag mismatch, not about the field being live.

Two new tests for the eval_gconfig warning cases (5 total in the file now,
263 passing overall, no regressions).

Signed-off-by: Sushant Daga <dsush2307@gmail.com>
…heck

Gemini Code Assist flagged that self.sglang.enable_deterministic_inference
would raise AttributeError if self.sglang were None. Verified: the YAML/CLI
config loader rejects `sglang: null` (OmegaConf ValidationError, field is
not Optional), so this is unreachable through any supported config-file
path. It is reachable via direct Python construction
(PPOConfig(sglang=None, ...)), which bypasses that loader and isn't
type-checked at runtime. Added the guard for that case, plus a test.

This guard is the only unconditional access point to config.sglang in the
codebase; every other consumer (areal/infra/launcher/{local,ray,slurm,
sglang_server}.py) gates access behind `alloc_mode.gen_backend ==
"sglang"`, so config.sglang is never dereferenced when the backend is
vLLM. Nothing downstream is silently broken for the realistic
sglang=None-with-vLLM case.

Signed-off-by: Sushant Daga <dsush2307@gmail.com>
Gemini Code Assist suggested validating this in PPOConfig.__post_init__
instead of waiting for the runtime NotImplementedError that
VLLMBackend/VLLMBridgeBackend already raise on the first generation
request, so an unsupported config fails before wasting time on server
launch and model load.

Applied with two changes from the suggested code: compare
rollout_backend.split(":")[0] == "vllm" instead of a substring check
(matches how ModelAllocation, the codebase's own parser, splits this
string, so a hypothetical future backend name containing "vllm" as a
substring would not false-positive), and isinstance(rollout_backend, str)
instead of comparing against the omegaconf MISSING sentinel directly
(MISSING is literally the string "???", so this also covers any other
non-string case without needing that import).

Verified rollout.backend is the current, authoritative field (not the
deprecated top-level allocation_mode) by tracing RolloutController.__init__,
which parses config.backend where config is the InferenceEngineConfig.

Five new tests: vLLM+seed raises, vLLM-without-seed does not, SGLang+seed
does not (goes through the existing warning path instead), and unset
backend (the omegaconf MISSING default) does not crash or misfire as vLLM.

Signed-off-by: Sushant Daga <dsush2307@gmail.com>
…d guard

PPOConfig.__post_init__'s new vLLM+sampling_seed check only matched
plain "vllm:dims" backend strings via a bare split(":")[0], missing
the "vllm[name]:dims" form that RolloutController.__init__ actually
accepts (ModelAllocation.from_str's grammar allows an optional
"[name]" between backend and dims). A vLLM rollout configured with
a named backend and a sampling_seed would have silently skipped
this validation.

Also correct two comments found during self-review via /review-pr:
the claimed symmetry between the self.rollout and self.sglang
None-guards isn't real (self.rollout is never None in practice here,
since the use_lora check above already dereferences it unconditionally),
and both vLLM builders' "fail loudly" claim held only for
construction-time validation -- a seed set dynamically per-request
on the live rollout path still raises, but is caught by a generic
exception handler upstream and surfaces as a silently-rejected
rollout rather than a crash.

Key changes:
- Strip an optional "[name]" from rollout.backend before comparing
  to "vllm" (areal/api/cli_args.py)
- Fix the misleading rollout/sglang guard-symmetry comment
- Fix VLLMBackend's incorrect same-file "below" reference and note
  the construction-time-only scope of the fail-loud guarantee in
  both vLLM builders (v1 and v2)

Refs: areal-project#1523
Signed-off-by: Sushant Daga <dsush2307@gmail.com>
The deterministic-inference UserWarning only ever fires when
rollout.backend is not vLLM: the ValueError check immediately above
it already raises and aborts construction whenever both a vLLM
backend and a sampling_seed are set, regardless of the SGLang
deterministic-inference flag. The warning text's parenthetical
"...or using the vLLM backend, which rejects sampling_seed outright"
therefore described a state that can never co-occur with the warning
itself, per gemini-code-assist feedback on PR areal-project#1524. Reworded to
state that plainly instead of implying it's a live alternative case.

Refs: areal-project#1523
Signed-off-by: Sushant Daga <dsush2307@gmail.com>
PPOConfig.__post_init__ already warns when sampling_seed is set without
enable_deterministic_inference. A reviewer flagged the inverse footgun on
the PR: with enable_deterministic_inference on, no sampling_seed set, and
gconfig.n_samples > 1, SGLang gives every seedless request the same default
seed. Its noise is a pure function of (seed, position, vocab-index), so a
group's same-prompt rollouts collapse to identical completions and zero out
GRPO's advantage.

Raise a ValueError rather than warn. Unlike the seed-ignored case above
(harmless -- the seed is just dropped), this combination has no working use,
and enable_deterministic_inference is new in this PR and default-off, so
failing fast breaks no existing config -- the same fail-fast this PR already
applies to sampling_seed on the vLLM backend. (A warning would instead be
consistent with the existing mean_level='group' + group_size=1 singleton-
group check, which warns for a similar degeneracy; that check guards a
pre-existing config surface where raising could break someone, which this
new default-off flag does not.)

Scoped to stochastic sampling: under greedy or temperature == 0 the request
builders decode at temperature 0.0, so the group collapses regardless of any
seed and a per-request seed would not help. Also scoped to a non-vLLM
backend, since enable_deterministic_inference is an SGLang-only flag that is
inert on vLLM and this SGLang-worded error would otherwise misdirect a vLLM
user (reusing the rollout_backend parse the vLLM guard already does).

Six new tests: raises when grouped + deterministic + unseeded; does not
raise with a seed, at n_samples == 1, under greedy / temperature == 0, or on
a vLLM backend. Also regenerates docs/en|zh/cli_reference.md for the two
fields (the rebase had reset them to upstream during conflict resolution).

Signed-off-by: Sushant Daga <dsush2307@gmail.com>
@SushantDaga

Copy link
Copy Markdown
Author

@sitabulaixizawaluduo
Rebased, and conflicts resolved again. The two cli_reference.md files are regenerated with docs/generate_cli_docs.py rather than hand-edits.

I have noticed that #1607 touches the same five files this PR does. They seems to be different change than this PR, but it will require a yet another rebase if that lands before merging.

@SushantDaga

Copy link
Copy Markdown
Author

@sitabulaixizawaluduo

You can close this as its superseded by #1625

Two things:

  1. Is there something I should do differently on a future contribution here? I followed the contributing guide: issue first, then PR, tests, docs, DCO sign-off, conventional commits, and a reply to every review round. This PR stayed open 40 days, and the work ended up merging in a different PR.
  2. If the overlapping work was already in progress internally, assumption for 1607 and 1625, please consider telling contributors up front. I raised the overlap with fix: complete deterministic rollout sampling determinism concurrent rollout #1607 here on Aug 20; fix: make rollout sampling deterministic #1625 merged the next day.

Also, two things you might want to look at in current main: a seed set without sglang.enable_deterministic_inference is silently dropped, and rollout.deterministic_sampling=True doesn't validate that sglang.enable_deterministic_inference is on, which the field's own help text says is required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants