Skip to content

fix: keep bounded sampling masks sparse - #3464

Open
mikasenghaas wants to merge 1 commit into
mainfrom
fix/sampling-replay-perf
Open

fix: keep bounded sampling masks sparse#3464
mikasenghaas wants to merge 1 commit into
mainfrom
fix/sampling-replay-perf

Conversation

@mikasenghaas

@mikasenghaas mikasenghaas commented Sep 2, 2026

Copy link
Copy Markdown
Member

Summary

  • keep sampling masks with at most 512 IDs sparse during device-to-host transfer
  • preserve vLLM's scheduler and response formats
  • fall back to vLLM's full-vocabulary bitset for wider masks
  • restore the bounded sampling-mask optimization from feat: top-p/top-k train sampling with sampling replay #3235 on current main

Verification

  • uv run --no-sync pytest -q tests/unit/inference (3 passed)
  • uv run --no-sync ruff check src/prime_rl/inference/patches.py
  • uv run --no-sync ruff format --check src/prime_rl/inference/patches.py

The paired live-training benchmark ran 10 steps per revision with the same
Qwen3-0.6B model, Reverse Text environment, V2 runner, sampling replay
(top_p = 0.95, bounded to top_k = 512), nodes, and warm caches. main ran
first and this PR ran immediately afterward. Request- and token-normalized
metrics account for the different trajectories sampled during live training.

Metric main This PR Change
Complete 10-step orchestrator loop 58.3 s 52.9 s -9.3%
Mean vLLM request inference time 890 ms 354 ms -60.2% / 2.51x faster
vLLM decode time per generated token 9.72 ms 3.95 ms -59.4% / 2.46x faster
Mean agent model-call time, steps 2-10 837 ms 391 ms -53.2% / 2.14x faster

A targeted batch-384 sampling-mask benchmark reduced device-to-host data from
6.94 MiB to 0.75 MiB and CPU mask materialization from 211 ms to 1.20 ms. This
isolates the regression to vLLM's dense vocabulary-width bitset conversion;
the HTTP payload, router, environment server, and trainer are downstream of
that work.


Note

Medium Risk
Monkey-patches core vLLM v1 sampler/mask conversion on every worker; incorrect sparse logic could break sampling replay or mask payloads, though behavior is gated to bounded top-k and falls back to upstream.

Overview
Restores a sparse path for vLLM sampling masks when return_sampling_mask is on and effective top_k is at most 512, avoiding the dense full-vocabulary bitset that regressed device-to-host transfer and CPU materialization.

A new monkey_patch_bounded_sampling_mask() is wired into apply_shared_vllm_patches(). It wraps Sampler.__call__ (via a ContextVar) and replaces SamplingMaskTensors.from_logits to build SparseSamplingMaskTensors from a bounded topk over logits, with to_cpu_nonblocking / tolists still emitting vLLM’s SamplingMaskLists so scheduler and response formats stay unchanged. Wider masks or disabled sparse mode fall back to stock vLLM behavior; the patch is idempotent via _prime_rl_uses_sparse_sampling_masks.

Reviewed by Cursor Bugbot for commit ae64d7f. Bugbot is set up for automated code reviews on this repo. Configure here.

vLLM expands every sampling mask to a full-vocabulary bitset before the
asynchronous host copy. The engine then unpacks that bitset on the CPU for
every decode step. This stalls sampling replay even though training caps
each kept set at 512 tokens.

Capture bounded masks as fixed-width token IDs instead. Preserve the
existing scheduler and response formats, and use vLLM's native bitset for
wider masks.
@mikasenghaas mikasenghaas changed the title fix: Keep bounded sampling masks sparse fix: keep bounded sampling masks sparse Sep 2, 2026
@mikasenghaas
mikasenghaas marked this pull request as ready for review September 2, 2026 07:53
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.

1 participant