Skip to content

fix(frontend): return SGLang chat logprobs - #12820

Merged
jain-ria merged 7 commits into
mainfrom
jain-ria/sglang-chat-logprobs
Aug 11, 2026
Merged

fix(frontend): return SGLang chat logprobs#12820
jain-ria merged 7 commits into
mainfrom
jain-ria/sglang-chat-logprobs

Conversation

@jain-ria

@jain-ria jain-ria commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Overview:

Fix SGLang chat-completion responses returning logprobs.content: null when logprobs were requested, including requests with logprobs=true and top_logprobs=0.

The SGLang backend calculated and returned token logprobs, but the frontend discarded them before postprocessing. The postprocessor also hardcoded the response’s logprobs field to None.

Details:

  • Preserve log_probs and top_logprobs while batching generated tokens in the SGLang frontend.
  • Forward those values to the SGLang postprocessor.
  • Convert backend logprobs into the OpenAI-compatible per-token format:
    • token
    • logprob
    • bytes
    • top_logprobs
  • Return populated logprobs instead of hardcoded null.
  • Preserve the existing behavior when logprobs are not requested.

Where should the reviewer start?

  • components/src/dynamo/frontend/sglang_processor.py
    • Preserves and forwards backend logprob data while batching tokens.
  • components/src/dynamo/frontend/sglang_prepost.py
    • Converts backend logprob data into the OpenAI response contract.

Related Issues


Open in Devin Review

Summary by CodeRabbit

  • New Features

    • Added OpenAI-compatible log probability details to streamed responses.
    • Streaming output can include per-token scores, top candidate tokens, and UTF-8 byte values.
    • Log probability data is now preserved across streamed chunks and attached to text, completion, and parsed-output responses.
  • Bug Fixes

    • Prevented incomplete or mismatched token probability data from being emitted.

Signed-off-by: jain-ria <riajain@NVIDIA.com>
@jain-ria
jain-ria requested a review from a team as a code owner August 7, 2026 16:55
@copy-pr-bot

copy-pr-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added fix frontend `python -m dynamo.frontend` and `dynamo-run in=http|text|grpc` labels Aug 7, 2026

@devin-ai-integration devin-ai-integration 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.

Devin Review found 4 potential issues.

Open in Devin Review

Comment thread components/src/dynamo/frontend/sglang_prepost.py
Comment thread components/src/dynamo/frontend/sglang_processor.py Outdated
Comment thread components/src/dynamo/frontend/sglang_processor.py Outdated
Comment thread components/src/dynamo/frontend/sglang_prepost.py
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Streaming SGLang responses now carry per-token log probabilities and optional top candidates. The frontend converts these values into OpenAI-compatible content entries, forwards them during chunk processing, and resets the associated buffers after each emitted chunk.

Changes

Streaming log probability support

Layer / File(s) Summary
OpenAI logprob conversion
components/src/dynamo/frontend/sglang_prepost.py
process_output converts token scores and candidate alternatives into OpenAI-compatible logprob entries. The payload is included in plain-text, finish, and parsed responses.
Stream probability accumulation
components/src/dynamo/frontend/sglang_processor.py
Streaming processing accumulates log_probs and top_logprobs, forwards them during chunk flushes, and clears both buffers after emission.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely identifies the fix for missing SGLang chat logprobs.
Description check ✅ Passed The description includes the required overview, details, reviewer guidance, and linked issue information.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/src/dynamo/frontend/sglang_prepost.py (1)

1146-1160: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Keep probability arrays aligned after EOS removal.

Line 1149 removes trailing EOS token IDs. It does not remove the matching log_probs and top_logprobs entries. _build_openai_logprobs then detects a length mismatch and returns None.

Trim both probability arrays to the retained token count after validating their original alignment. Add a terminal-chunk test with generated tokens followed by EOS.

Proposed fix
         if finish_reason is not None:
+            raw_token_count = len(token_ids)
             token_ids = self._strip_trailing_eos_token_ids(list(token_ids))
+            if log_probs is not None and len(log_probs) == raw_token_count:
+                log_probs = log_probs[: len(token_ids)]
+            if top_logprobs and len(top_logprobs) == raw_token_count:
+                top_logprobs = top_logprobs[: len(token_ids)]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/src/dynamo/frontend/sglang_prepost.py` around lines 1146 - 1160,
Update the EOS handling in the response-processing flow around
_strip_trailing_eos_token_ids to validate that log_probs and top_logprobs are
aligned with the original token_ids, then trim both arrays to the retained token
count after EOS removal. Keep _build_openai_logprobs receiving matching token
and probability lengths, and add a terminal-chunk test covering generated tokens
followed by EOS.
🧹 Nitpick comments (1)
components/src/dynamo/frontend/sglang_prepost.py (1)

1057-1057: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Set an explicit zip() strictness value.

The project requires Python 3.10 or later, and Ruff reports B905 here. Use strict=True.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/src/dynamo/frontend/sglang_prepost.py` at line 1057, Update the
zip call in the token iteration loop to pass the explicit strict=True argument,
resolving Ruff B905 while preserving the existing enumerate behavior.

Sources: Coding guidelines, Path instructions, Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/src/dynamo/frontend/sglang_prepost.py`:
- Around line 1156-1177: Update the incremental decoding flow around
`_incremental_decode` and `_pending_decode_ids` to retain logprob entries when
decoding returns an empty string for a deferred byte-fallback sequence. Queue
the probability data with the pending token IDs, then emit it only when those
matching IDs produce decoded text, rather than allowing `sglang_processor.py` to
clear it prematurely. Add a test covering one UTF-8 byte-fallback sequence split
across stream chunks and verify its text and logprobs are preserved.

---

Outside diff comments:
In `@components/src/dynamo/frontend/sglang_prepost.py`:
- Around line 1146-1160: Update the EOS handling in the response-processing flow
around _strip_trailing_eos_token_ids to validate that log_probs and top_logprobs
are aligned with the original token_ids, then trim both arrays to the retained
token count after EOS removal. Keep _build_openai_logprobs receiving matching
token and probability lengths, and add a terminal-chunk test covering generated
tokens followed by EOS.

---

Nitpick comments:
In `@components/src/dynamo/frontend/sglang_prepost.py`:
- Line 1057: Update the zip call in the token iteration loop to pass the
explicit strict=True argument, resolving Ruff B905 while preserving the existing
enumerate behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8458afde-f517-4bba-8d85-c456e71b19b2

📥 Commits

Reviewing files that changed from the base of the PR and between ee62866 and 5723620.

📒 Files selected for processing (2)
  • components/src/dynamo/frontend/sglang_prepost.py
  • components/src/dynamo/frontend/sglang_processor.py

Comment thread components/src/dynamo/frontend/sglang_prepost.py
Signed-off-by: jain-ria <riajain@NVIDIA.com>
Signed-off-by: jain-ria <riajain@NVIDIA.com>
@rmccorm4

rmccorm4 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Thanks @jain-ria ! Please address the devin comments 🙏

Signed-off-by: jain-ria <riajain@NVIDIA.com>
@pull-request-size pull-request-size Bot added size/L and removed size/M labels Aug 10, 2026
@datadog-official

datadog-official Bot commented Aug 10, 2026

Copy link
Copy Markdown

Pipelines

🎯 Code Coverage (details)
Patch Coverage: 14.71%
Overall Coverage: 45.78% (-13.10%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: ec3b64b | Docs | Datadog PR Page | Give us feedback!

Signed-off-by: jain-ria <riajain@NVIDIA.com>
Signed-off-by: jain-ria <riajain@NVIDIA.com>
@jain-ria
jain-ria force-pushed the jain-ria/sglang-chat-logprobs branch from 8eba427 to 31f8c33 Compare August 10, 2026 02:34
@rmccorm4

Copy link
Copy Markdown
Contributor

/ok to test 31f8c33

Signed-off-by: jain-ria <riajain@NVIDIA.com>
@jain-ria

Copy link
Copy Markdown
Contributor Author

/ok to test ec3b64b

@rmccorm4

Copy link
Copy Markdown
Contributor

Some of the more problematic looking findings from local review:

[P1] return_tokens_as_token_ids=true remains ignored for selected tokens at sglang_prepost.py (line 1061). Exact-head repro still returns selected "A" beside candidate "token_id:65".

[P1] Contextual reconstruction corrupts per-token Unicode attribution at sglang_prepost.py (line 1106). Qwen3-0.6B encodes 𐍈 as IDs [126190, 230], representing bytes [240,144,141] and [136]. The PR reports:
token 126190: token="", bytes=null
token 230: token="𐍈", bytes=[240,144,141,136]
This moves the first token’s bytes onto the second token. A valid U+FFFD token is also reported as token="", bytes=null, despite emitted content being �.

[P1] Non-finite values remain unsanitized at sglang_prepost.py (line 1080). The repro emits bare -Infinity; the Python/Rust typed transport rewrites this incompatibly and can discard the requested logprobs payload.

However, I don't think any of these issues are blocking to the release, so these can be followed up in separate PRs to main as you see fit.

@rmccorm4 rmccorm4 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.

Some comments for future follow-up, not blocking to this PR: #12820 (comment)

LGTM to unblock the release fix.

@jain-ria
jain-ria merged commit bb5b141 into main Aug 11, 2026
184 of 187 checks passed
@jain-ria
jain-ria deleted the jain-ria/sglang-chat-logprobs branch August 11, 2026 00:09
dagil-nvidia added a commit that referenced this pull request Aug 11, 2026
Second stale generator on main, same class as the release tables in the
previous commit and blocking every PR the same way: gen_python_api.py --check
fails on pristine origin/main, so any PR that runs pre-merge inherits it.

Cause is #12820 (fix(frontend): return SGLang chat logprobs), which moved code
in components/src/dynamo/frontend/. The reference deep-links to exact source
lines, so shifting sglang_processor.py and sglang_prepost.py by ~54 and ~104
lines invalidated eight of them. Nothing about the docstrings changed; the line
anchors did.

That is worth noting for anyone editing that package: this reference goes stale
on any line-number shift in the source it documents, not only on docstring
edits. #12820 had no way to know -- the generator landed with #12110 after it
was already open.

Output only, no hand edits.

Validation: all four generators (--check on python, rust, kubernetes, plus
gen_llms_tables) now pass on this branch; python and llms_tables both fail on
pristine main.

Signed-off-by: Dan Gil <dagil@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix frontend `python -m dynamo.frontend` and `dynamo-run in=http|text|grpc` size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants