Skip to content

[Bugfix][Frontend] Count reasoning tokens for the Muse Glimmer parser - #54238

Open
corona10 wants to merge 1 commit into
vllm-project:mainfrom
corona10:glimmer-reasoing-token
Open

corona10 wants to merge 1 commit into
vllm-project:mainfrom
corona10:glimmer-reasoing-token

Conversation

@corona10

@corona10 corona10 commented Aug 28, 2026

Copy link
Copy Markdown

Purpose

MuseGlimmerReasoningParser inherited the base count_reasoning_tokens() that returns 0, so usage.completion_tokens_details.reasoning_tokens was always 0 with --reasoning-parser muse_glimmer.

The ATEM framing markers are not guaranteed to be single vocab tokens (e.g. to=self is two tokens), so tokens cannot be classified by id. Instead, decode each token once (cached per request), record its character offset, locate to=self bodies in the decoded text, and count the tokens starting inside those spans with bisect. This counts the original tokens exactly rather than re-encoding the reasoning text.

The body-end rule (<|eom|> / <|eot|> / next channel header / EOF) is factored into _body_end() and shared with _classify_bodies(), so the streamed reasoning text and the reported count cut bodies identically.

Relation to #54091: that PR fixes the same bug via decode → _classify_bodies → re-encode. This variant is exact at span boundaries and ~12x cheaper per call (247 µs vs 2984 µs at 4k reasoning tokens with the real tokenizer), which matters because chat streaming re-counts the full token list on every delta.

Developed with AI assistance (Claude Code); reviewed and validated by the author.

Test Plan

# unit tests (fake multi-token tokenizer), 3 new cases
pytest tests/tool_use/test_muse_glimmer.py

# existing streaming tests with the real tokenizer (only tokenizer files needed)
MUSE_GLIMMER_CKPT=<dir with meta-models/Muse-Glimmer-30B tokenizer> \
  pytest tests/tool_use/test_muse_glimmer_parse_delta.py

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment /ci run for upstream CI or /amd-ci run for AMD CI only whenever CI signals are needed.

Once the PR is approved or has the ready label, the PR author can also use the corresponding /ci run, /ci retry, and /ci cancel commands, or their /amd-ci variants. New commits do not start upstream CI automatically.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban.

🚀

@corona10

Copy link
Copy Markdown
Author

Cc @jeejeelee

@jeejeelee

jeejeelee commented Aug 29, 2026

Copy link
Copy Markdown
Member

@chaunceyjiang PTAL

MuseGlimmerReasoningParser inherited the base count_reasoning_tokens()
that returns 0, so usage.completion_tokens_details.reasoning_tokens was
always 0 for --reasoning-parser muse_glimmer.

The ATEM framing markers are not guaranteed to be single vocab tokens
(e.g. " to=self" is two tokens), so tokens cannot be classified by id.
Instead, decode each token once (cached per request), record its
character offset, locate to=self bodies in the decoded text, and count
the tokens starting inside those spans with bisect. This counts the
original tokens exactly rather than re-encoding the reasoning text.

The body-end rule (<|eom|> / <|eot|> / next channel header / EOF) is
factored into _body_end() and shared with _classify_bodies(), so the
streamed reasoning text and the reported count cut bodies identically.

Related: vllm-project#54091 takes the decode -> classify -> re-encode approach for
the same bug; this variant is exact at span boundaries and ~12x cheaper
per call (247us vs 2984us at 4k reasoning tokens with the real
tokenizer), which matters because chat streaming re-counts the full
token list on every delta.

Signed-off-by: Donghee Na <donghee.na92@gmail.com>

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

Just a quick question — are you from the official MuseGlimmer team?

The approach here still looks like the traditional one. We recently introduced a new unified approach in #45802.

However, that approach relies on Parser Engine, and MuseGlimmerReasoningParser hasn't been migrated to Parser Engine yet.

I think it would make more sense to first refactor MuseGlimmerReasoningParser to use Parser Engine. Once that's done, support for counting reasoning tokens should come naturally.

@corona10

corona10 commented Aug 31, 2026

Copy link
Copy Markdown
Author

Just a quick question — are you from the official MuseGlimmer team?

No, I am not the Meta employee (but we internally use Muse Glimmer)

I think it would make more sense to first refactor MuseGlimmerReasoningParser to use Parser Engine. Once that's done, support for counting reasoning tokens should come naturally.

Let me take a look if you are okay.

@corona10

Copy link
Copy Markdown
Author

@chaunceyjiang

Hmm, I think this will be a huge task if we migrate the current implementation to the Parser Engine, since the engine would need to be updated to support the ATEM protocol first.
Would it be okay to just add an inline TODO comment and handle the migration separately? I'm interested in getting involved in that work, so I can file a new issue and I'm willing to start on it.
Realistically though, we need this feature fairly soon for an internal team, so I'd prefer not to gate this fix on the larger refactoring. What do you think?

cache = self._token_text_cache
offsets: list[int] = []
text = ""
for token_id in token_ids:

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.

In practice, the performance may not be very good, especially with long contexts. Even with this feature added, the overall experience might still not be great.

/cc @sfeng33 WDYT?

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.

Okay if we need to calculate this in incremental way, let me start to work on #54238

@corona10

Copy link
Copy Markdown
Author

@chaunceyjiang I think that #54585 is something you want.

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

Labels

bug Something isn't working tool-calling

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants