Skip to content

feat: parse vLLM's native sampling_mask field - #144

Merged
hallerite merged 3 commits into
mainfrom
feat/native-sampling-mask
Sep 1, 2026
Merged

feat: parse vLLM's native sampling_mask field#144
hallerite merged 3 commits into
mainfrom
feat/native-sampling-mask

Conversation

@mikasenghaas

@mikasenghaas mikasenghaas commented Aug 29, 2026

Copy link
Copy Markdown
Member

Summary

  • Surface vLLM's native sampling_mask field on the generate result.
  • Preserve one list of surviving token ids for each completion token.
  • Remove the custom base64 kept_tokens response splice.

Consumed by PrimeIntellect-ai/verifiers#2460 and prime-rl's native sampling-replay PR.

Breaking

  • Replace the renderer result key kept_tokens with sampling_mask.
  • Update consumers to read vLLM's native list-of-lists payload.

Verification

  • uv run pytest tests/test_client.py -q: 21 passed, 5 skipped.
  • Ruff, format, and diff checks pass.

🤖 Generated with Claude Code

Note

Replace kept_tokens with vLLM's native sampling_mask field

  • Updates build_trajectory_step in renderers/base.py to expose sampling_mask (set to None) instead of kept_tokens in the returned trajectory step dict.
  • Removes KEPT_TOKENS_IDS_PREFIX and the stripping/decoding of kept_tokens.ids in parse_generate_response in renderers/client.py.
  • Updates generate async function in renderers/client.py to read sampling_mask from the engine response instead of kept_tokens, matching vLLM's kept-set sampling mask.
  • Behavioral Change: all downstream consumers that read kept_tokens from trajectory steps or generation results must switch to sampling_mask; the old key is no longer present.

Macroscope summarized a9dccfb.


Note

Medium Risk
Breaking change to the generate/trajectory result schema; downstream verifiers and prime-rl must switch field names and payload shape, but logic is limited to optional metadata parsing.

Overview
Breaking: Renderer generate results and trajectory steps now expose sampling_mask instead of kept_tokens. Consumers must read vLLM’s native list-of-lists (one surviving vocab-id list per completion token when --return-sampling-mask is enabled).

In renderers/client.py, the custom kept_tokens.ids base64 splice (KEPT_TOKENS_IDS_PREFIX in parse_generate_response) is removed. generate takes choice.get("sampling_mask") and returns it on the result dict.

In renderers/base.py, build_trajectory_step initializes the step payload with sampling_mask: None instead of kept_tokens.

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

vLLM 0.28 returns kept-set sampling masks natively on
/inference/v1/generate choices as sampling_mask (one list of surviving
vocab ids per completion token, plain JSON). Surface that field on the
generate result and drop the custom base64 kept_tokens splice, which no
server emits anymore.
mikasenghaas added a commit to PrimeIntellect-ai/prime-rl that referenced this pull request Aug 29, 2026
Truncated train sampling (top_p < 1, top_k) renormalizes the rollout
distribution over the surviving kept set; rollout logprobs reflect that
(processed_logprobs) while the trainer normalizes over the full vocab,
biasing every importance ratio. Record the kept set at sampling time
and renormalize trainer logprobs over the same set (DeepSeek V3.2's
Keep Sampling Mask, arXiv:2512.02556 3.1).

Same user API as #3235: [orchestrator.train.sampling] top_p/top_k, no
replay flags. Truncating policy sampling auto-enables
inference.enable_return_sampling_mask, bounds top_k to 512 (trainer
mask tensors pad to the largest kept set), and rejects opd/opsd and
temperature 0.

Unlike #3235 the capture is vLLM's native --return-sampling-mask
(>= 0.28, V2 model runner) instead of custom engine patches: the
/generate response carries sampling_mask natively, renderers parse it
(PrimeIntellect-ai/renderers#144) and verifiers carry it as KeptTokens
arrays (PrimeIntellect-ai/verifiers#2460). Capture is engine-wide:
vLLM rejects requests with temperature <= 0 or top_k <= 0 while it is
on, and it is incompatible with router replay (V1-only).
@hallerite
hallerite marked this pull request as ready for review September 1, 2026 21:30
@hallerite
hallerite merged commit f91c3e7 into main Sep 1, 2026
11 checks passed
@hallerite
hallerite deleted the feat/native-sampling-mask branch September 1, 2026 21:30
mikasenghaas added a commit to PrimeIntellect-ai/verifiers that referenced this pull request Sep 1, 2026
## Summary

- Build `SamplingMask` from a `sampling_mask` payload.
- Keep sampling-mask trace types independent of the inference producer.
- Require the published `renderers==0.1.12.dev2` feature floor.
- Store masks as flat int32 `ids` and per-token `counts` arrays.
- Use `sampling_mask` across response tokens, message nodes, branch
views, wire serialization, and public exports.
- Keep `Branch.sampling_mask` aligned to the branch token ids for
prime-rl.

Pairs with
[PrimeIntellect-ai/renderers#144](PrimeIntellect-ai/renderers#144)
and prime-rl's native sampling-replay PR.

## Breaking

- Rename `vf.KeptTokens` to `vf.SamplingMask`.
- Rename `TurnTokens.kept_tokens`, `MessageNode.kept_tokens`, and
`Branch.kept_tokens` to `sampling_mask`.
- Update serialized trace consumers to read the `sampling_mask` field.

## Verification

- Merged current `main` without conflicts.
- `uv sync --upgrade-package renderers`: lockfile regenerated with
`renderers==0.1.12.dev2`.
- `uv run pytest tests/v1/test_graph.py tests/v1/test_trace.py -q`: 21
passed.
- Ruff, format, type, lock, and diff checks pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- Macroscope's pull request summary starts here -->
<!-- Macroscope will only edit the content between these invisible
markers, and the markers themselves will not be visible in the GitHub
rendered markdown. -->
<!-- If you delete either of the start / end markers from your PR's
description, Macroscope will append its summary at the bottom of the
description. -->
> [!NOTE]
> ### Replace `KeptTokens` with `SamplingMask` across v1 verifier
pipeline
> - Adds the `SamplingMask` dataclass in
[types.py](https://github.com/PrimeIntellect-ai/verifiers/pull/2460/files#diff-88a4e4ee0c58ac5dc7344161a011374082a3767eda2ad32af97c942dca982112)
with `ids` and `counts` fields plus `from_sampling_mask`, and swaps
`kept_tokens` for `sampling_mask` on `TurnTokens` and `MessageNode`
> -
[train.py](https://github.com/PrimeIntellect-ai/verifiers/pull/2460/files#diff-c9bcd6c691890b1de6942fe012d2e74678460b22bf335721a8bba5e939d139c3)
now reads `sampling_mask` from the generate result and builds it via
`SamplingMask.from_sampling_mask`; `response_from_generate` populates
`TurnTokens.sampling_mask`
> -
[graph.py](https://github.com/PrimeIntellect-ai/verifiers/pull/2460/files#diff-64c9d635eb215902a512c9e8ab316fd9b9967c36db03d4049aacc812cdfbd740)
renames `_attribute_kept_tokens` to `_attribute_sampling_mask`, which
validates `counts`/`ids` lengths and assigns the `SamplingMask` directly
(no base64 decode or buffer copy)
> -
[trace.py](https://github.com/PrimeIntellect-ai/verifiers/pull/2460/files#diff-834820c3aa80d87ee1c1c57fb54c74f09c66ac727036d964e6005c8613c00b5e)
excludes `sampling_mask` from disk serialization and exposes
`Branch.sampling_mask`, which scatters per-node `counts` onto sampled
positions and concatenates `ids`
> - Bumps `renderers[multimodal]` dependency from `>=0.1.11` to
`>=0.1.12.dev2` in
[pyproject.toml](https://github.com/PrimeIntellect-ai/verifiers/pull/2460/files#diff-50c86b7ed8ac2cf95bd48334961bf0530cdc77b5a56f852c5c61b89d735fd711)
> - Behavioral Change: `KeptTokens` is removed from the `verifiers.v1`
public API (`__all__`) and `SamplingMask` replaces it; any downstream
code importing `KeptTokens` or reading `kept_tokens` on `TurnTokens`,
`MessageNode`, or `Branch` must switch to `sampling_mask`.
`_attribute_sampling_mask` no longer base64-decodes or copies buffers —
callers must provide a valid `SamplingMask` payload directly
>
> <!-- Macroscope's changelog starts here -->
> #### Changes since #2460 opened
>
> - Removed producer-specific references from sampling mask
documentation [3ff2553]
> <!-- Macroscope's changelog ends here -->
>
> <!-- Macroscope's review summary starts here -->
>
> <sup><a href="https://app.macroscope.com">Macroscope</a> summarized
67af753.</sup>
> <!-- Macroscope's review summary ends here -->
>
<!-- Macroscope's pull request summary ends here -->

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Breaking rename across the v1 training/trace wire contract; incorrect
mask alignment would silently drop masks at attribution, and consumers
must upgrade renderers in lockstep.
> 
> **Overview**
> **Replaces the `KeptTokens` / `kept_tokens` surface with
`SamplingMask` / `sampling_mask`** so training traces align with the new
`renderers` generate payload (`sampling_mask` as nested token-id rows).
Public exports, `TurnTokens`, `MessageNode`, wire serialization, and
`Branch.sampling_mask` all use the new names; disk dumps still exclude
the tensor field via `EXCLUDE_FIELDS`.
> 
> **`SamplingMask`** holds flat int32 `ids` and per-completion-token
`counts`, with **`from_sampling_mask`** to build from the engine’s
`list[list[int]]`. The train client maps `result["sampling_mask"]` into
`TurnTokens`; graph attribution validates row counts against sampled
positions and stores the mask on the assistant node (no base64
decode/copy path like the old kept-set flow).
> 
> **Dependency:** `renderers[multimodal]` is pinned to
**`>=0.1.12.dev2`** (lockfile updated). Downstream code that imported
`KeptTokens` or read `kept_tokens` on traces must switch to
`sampling_mask`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
3ff2553. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
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