Skip to content

[feat] support whitespace control for structural tag#660

Merged
Seven-Streams merged 3 commits into
mlc-ai:mainfrom
izhuhaoran:feat/structural-tag-whitespace-control
Jul 6, 2026
Merged

[feat] support whitespace control for structural tag#660
Seven-Streams merged 3 commits into
mlc-ai:mainfrom
izhuhaoran:feat/structural-tag-whitespace-control

Conversation

@izhuhaoran

Copy link
Copy Markdown
Contributor

Some models, in bad-case generations, emit unbounded runs of whitespace, which inflates the Earley parser's state set and can blow up grammar compilation/matching.

Adds two whitespace controls — any_whitespace and max_whitespace_cnt — as parameters of the structural-tag compile interface (GrammarCompiler.compile_structural_tag / Grammar.from_structural_tag), mirroring compile_json_schema.

@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 whitespace control parameters (any_whitespace and max_whitespace_cnt) to the structural tag compilation and grammar generation interfaces to prevent state explosion. Feedback suggests optimizing the C++ style_to_grammar_converter map by replacing std::function with raw function pointers to avoid dynamic allocation overhead. Additionally, it is recommended to validate max_whitespace_cnt in Python to ensure it is a positive integer before passing it to the C++ backend.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread cpp/structural_tag.cc
Comment on lines +1845 to 1848
const static std::unordered_map<
std::string,
std::function<std::string(const std::string&, bool, std::optional<int>)>>
style_to_grammar_converter = {

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

Using std::function in a const static map introduces unnecessary overhead due to dynamic allocation and virtual dispatch. Since the lambdas do not capture any variables, they can be implicitly converted to raw function pointers, which are more efficient.

  const static std::unordered_map<\n      std::string,\n      std::string (*)(const std::string&, bool, std::optional<int>)>\n      style_to_grammar_converter = {

Comment thread python/xgrammar/compiler.py Outdated
Comment on lines +296 to +297
any_whitespace = kwargs.pop("any_whitespace", True)
max_whitespace_cnt = kwargs.pop("max_whitespace_cnt", None)

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

Validate max_whitespace_cnt in Python to ensure it is a positive integer before passing it to the C++ backend. This prevents potential C++ fatal log crashes and provides a clearer Python ValueError to the user.

        any_whitespace = kwargs.pop(\"any_whitespace\", True)\n        max_whitespace_cnt = kwargs.pop(\"max_whitespace_cnt\", None)\n        if max_whitespace_cnt is not None and (not isinstance(max_whitespace_cnt, int) or isinstance(max_whitespace_cnt, bool) or max_whitespace_cnt <= 0):\n            raise ValueError(\"max_whitespace_cnt must be a positive integer\")

Comment thread python/xgrammar/grammar.py Outdated
Comment on lines +368 to +369
any_whitespace = kwargs.pop("any_whitespace", True)
max_whitespace_cnt = kwargs.pop("max_whitespace_cnt", None)

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

Validate max_whitespace_cnt in Python to ensure it is a positive integer before passing it to the C++ backend. This prevents potential C++ fatal log crashes and provides a clearer Python ValueError to the user.

        any_whitespace = kwargs.pop(\"any_whitespace\", True)\n        max_whitespace_cnt = kwargs.pop(\"max_whitespace_cnt\", None)\n        if max_whitespace_cnt is not None and (not isinstance(max_whitespace_cnt, int) or isinstance(max_whitespace_cnt, bool) or max_whitespace_cnt <= 0):\n            raise ValueError(\"max_whitespace_cnt must be a positive integer\")

@izhuhaoran

Copy link
Copy Markdown
Contributor Author

@Ubospica @Seven-Streams Could you please review this PR when you have time?

Comment thread cpp/support/compact_2d_array.h
Comment thread python/xgrammar/grammar.py Outdated
def from_structural_tag(tags: List[StructuralTagItem], triggers: List[str]) -> "Grammar": ...
def from_structural_tag(
tags: List[StructuralTagItem],
triggers: List[str],

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.

We may need to mention it in the doc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Added a short note to docs/structural_tag/tool_calling_and_reasoning.md.

@izhuhaoran

Copy link
Copy Markdown
Contributor Author

@Seven-Streams I've addressed all the comments. Could you please take another look when you have a chance?

@Seven-Streams Seven-Streams 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.

@izhuhaoran

Copy link
Copy Markdown
Contributor Author

@Ubospica cc

@Seven-Streams @Ubospica Could you please help merge this PR manually?

@Seven-Streams

Copy link
Copy Markdown
Collaborator

Hello @izhuhaoran. I discussed the function with @Ubospica today, and I'll make some modifications tomorrow, and then the PR will be merged. Thanks for your contribution!

@Seven-Streams
Seven-Streams force-pushed the feat/structural-tag-whitespace-control branch 3 times, most recently from 3b5998b to 069c208 Compare June 24, 2026 01:51
Signed-off-by: zhuhaoran <zhuhaoran.zhr@alibaba-inc.com>

use XGRAMMAR_DCHECK for the int32 overflow guard and document whitespace controls

Signed-off-by: zhuhaoran <zhuhaoran.zhr@alibaba-inc.com>

test.

Signed-off-by: yuchuan <yuchuan.7streams@gmail.com>

simplify.

Signed-off-by: yuchuan <yuchuan.7streams@gmail.com>
@Seven-Streams
Seven-Streams force-pushed the feat/structural-tag-whitespace-control branch from 069c208 to 902e2fb Compare June 24, 2026 01:59
@izhuhaoran

Copy link
Copy Markdown
Contributor Author

@Seven-Streams Thank you for taking the time to further improve this PR. I really appreciate it.

I noticed it hasn’t been merged yet, so I just wanted to check whether there are any remaining concerns or anything else needed from my side.

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

LGTM! @Seven-Streams if you have time plz cross check

@Seven-Streams
Seven-Streams merged commit 7079838 into mlc-ai:main Jul 6, 2026
46 checks passed
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.

3 participants