Skip to content

[Bugfix][Rollout] Reject missing or misaligned sampled-token log probabilities - #426

Closed
0z5a wants to merge 2 commits into
vllm-project:0z5a-branchfrom
0z5a:fix/rollout-logprob-validation
Closed

0z5a wants to merge 2 commits into
vllm-project:0z5a-branchfrom
0z5a:fix/rollout-logprob-validation

Conversation

@0z5a

@0z5a 0z5a commented Sep 12, 2026 •

Copy link
Copy Markdown

Incremental review: Files changed.

Base: 0z5a-branch at 791f101e20db377b28e15f12a00863baaea2605f.

Reject malformed sampled-token log probabilities before appending generated data or calculating rewards. Previously, two token IDs with one probability could be padded with zero and accepted as training data.

Validate non-negative integer token IDs, aligned entries and finite numeric values. Preserve real zeros, finite vLLM sentinels, empty terminal responses, non-trainable tool tokens and multimodal canonical prefixes. Error messages include the local request ID without prompt contents.

The diff contains the parser and two updated expectations in existing tests. Newly added cases were removed.

Validation of the implementation Result
Publication-checkout rollout checks 99 passed
Repository hooks All 9 passed
Earlier local/Linux rollout and Sample checks 111 passed
Qwen3-0.6B integration 2 complete RL rounds; 16 samples verified the parser hash
Checkpoint-to-serving reload Both checkpoints matched all 226 served tensors byte-for-byte

The integration used real generation, response-dependent reward, full backpropagation, AdamW, checkpoint persistence and weight reload. Its local-attention/runtime compatibility changes were outside this patch. Archived checks include the now-removed test harnesses; cleanup preserves production code. This is a correctness fix with no speedup claim.

@read-the-docs-community

read-the-docs-community Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

@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 strict validation for vLLM generation metadata (such as token IDs and log probabilities) to prevent silent fallbacks, ensures prompt preparation changes remain local until validation succeeds, and adds unique request IDs to headers and error messages. It also significantly expands the test suite. The reviewer suggests simplifying the validation logic in _inference_generate_tokens_and_logprobs by checking for empty token_ids early to avoid redundant checks and improve clarity.

Comment on lines +244 to 277
if not isinstance(choice, dict):
raise ValueError("choice must be an object")
token_ids = choice.get("token_ids")
if not isinstance(token_ids, list) or not all(isinstance(token_id, int) for token_id in token_ids):
return [], []
if not isinstance(token_ids, list) or any(type(token_id) is not int or token_id < 0 for token_id in token_ids):
raise ValueError("token_ids must be a list of non-negative integers")

logprobs = choice.get("logprobs")
content = logprobs.get("content") if isinstance(logprobs, dict) else []
content = content or []
log_probs = [
float(content[index].get("logprob", 0.0)) if index < len(content) and isinstance(content[index], dict) else 0.0
for index in range(len(token_ids))
]
if not token_ids and logprobs is None:
return [], []
if not isinstance(logprobs, dict):
raise ValueError("logprobs must be an object for nonempty trainable token_ids")
content = logprobs.get("content")
if not token_ids and content is None:
return [], []
if not isinstance(content, list):
raise ValueError("logprobs.content must be a list")
if len(content) != len(token_ids):
raise ValueError(f"token/logprob length mismatch: {len(token_ids)} tokens, {len(content)} entries")

log_probs = []
for index, entry in enumerate(content):
if not isinstance(entry, dict) or "logprob" not in entry:
raise ValueError(f"missing logprob at token index {index}")
value = entry["logprob"]
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise ValueError(f"non-numeric logprob at token index {index}")
try:
value = float(value)
except OverflowError as exc:
raise ValueError(f"non-finite logprob at token index {index}") from exc
if not math.isfinite(value):
raise ValueError(f"non-finite logprob at token index {index}")
log_probs.append(value)
return token_ids, log_probs

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 validation logic for empty token_ids can be significantly simplified. By checking if not token_ids: early, we can immediately return [], [] and avoid redundant checks for logprobs and content being None when token_ids is empty. This also prevents potentially misleading error messages (e.g., raising 'logprobs must be an object for nonempty trainable token_ids' when token_ids is actually empty but logprobs is of an invalid type).

    if not isinstance(choice, dict):
        raise ValueError("choice must be an object")
    token_ids = choice.get("token_ids")
    if not isinstance(token_ids, list) or any(type(token_id) is not int or token_id < 0 for token_id in token_ids):
        raise ValueError("token_ids must be a list of non-negative integers")

    if not token_ids:
        return [], []

    logprobs = choice.get("logprobs")
    if not isinstance(logprobs, dict):
        raise ValueError("logprobs must be an object for nonempty trainable token_ids")
    content = logprobs.get("content")
    if not isinstance(content, list):
        raise ValueError("logprobs.content must be a list")
    if len(content) != len(token_ids):
        raise ValueError(f"token/logprob length mismatch: {len(token_ids)} tokens, {len(content)} entries")

    log_probs = []
    for index, entry in enumerate(content):
        if not isinstance(entry, dict) or "logprob" not in entry:
            raise ValueError(f"missing logprob at token index {index}")
        value = entry["logprob"]
        if isinstance(value, bool) or not isinstance(value, (int, float)):
            raise ValueError(f"non-numeric logprob at token index {index}")
        try:
            value = float(value)
        except OverflowError as exc:
            raise ValueError(f"non-finite logprob at token index {index}") from exc
        if not math.isfinite(value):
            raise ValueError(f"non-finite logprob at token index {index}")
        log_probs.append(value)
    return token_ids, log_probs

@0z5a
0z5a marked this pull request as ready for review September 13, 2026 03:55
@0z5a
0z5a changed the base branch from main to 0z5a-branch September 18, 2026 18:56
@0z5a
0z5a force-pushed the fix/rollout-logprob-validation branch 2 times, most recently from 3451411 to dce48c9 Compare September 18, 2026 19:36
@0z5a
0z5a changed the base branch from 0z5a-branch to main September 19, 2026 01:24
@0z5a
0z5a changed the base branch from main to 0z5a-branch September 19, 2026 06:03
@0z5a
0z5a force-pushed the fix/rollout-logprob-validation branch from dce48c9 to e052c29 Compare September 24, 2026 05:54
Signed-off-by: 0z5a <192209249+0z5a@users.noreply.github.com>
Signed-off-by: 0z5a <0z5a@users.noreply.github.com>
Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de>
@0z5a
0z5a force-pushed the fix/rollout-logprob-validation branch from e052c29 to 89737bd Compare September 24, 2026 07:08
Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de>
@0z5a

0z5a commented Oct 6, 2026

Copy link
Copy Markdown
Author

Superseded by #460, now merged into 0z5a-branch. The consolidated implementation and validation are retained there; closing this duplicate PR.

@0z5a 0z5a closed this Oct 6, 2026
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