From f829cafa3e08a05d18fa8ab83617c303671fea44 Mon Sep 17 00:00:00 2001 From: Khoa Pham Date: Thu, 21 May 2026 22:45:54 -0700 Subject: [PATCH] [perf] skip add_special_tokens=False kwarg on chat-template tokenize for slow tokenizers (#25953) Co-authored-by: Claude Opus 4.7 (1M context) --- .../srt/entrypoints/openai/serving_chat.py | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/python/sglang/srt/entrypoints/openai/serving_chat.py b/python/sglang/srt/entrypoints/openai/serving_chat.py index d5381b10a..973ae28ce 100644 --- a/python/sglang/srt/entrypoints/openai/serving_chat.py +++ b/python/sglang/srt/entrypoints/openai/serving_chat.py @@ -181,6 +181,18 @@ class OpenAIServingChat(OpenAIServingBase): # Per-request response parser for custom decoding (set by _encode_messages) self._response_parser: Optional[ResponseParserProtocol] = None + # Probe whether ``encode("")`` returns specials. If it does, we must + # keep ``add_special_tokens=False`` at the chat-template encode site + # to avoid double BOS; otherwise the kwarg is a no-op and dropping it + # lets slow tokenizers (e.g. Kimi's TikTokenTokenizer) stay on the + # fast internal path. + try: + self._tokenizer_auto_adds_specials = ( + len(self.tokenizer_manager.tokenizer.encode("")) > 0 + ) + except Exception: + self._tokenizer_auto_adds_specials = True + def _handle_last_assistant_message( self, messages: List[Dict[str, Any]], @@ -729,15 +741,28 @@ class OpenAIServingChat(OpenAIServingBase): if request.chat_template_kwargs: extra_template_kwargs.update(request.chat_template_kwargs) + # Split apply_chat_template(tokenize=True) into render + encode so we + # can skip add_special_tokens=False on tokenizers that don't auto-add + # specials (Kimi-like, OpenAI-chat analogue of #25265). Chat + # templates already include role/special tokens, so the encode must + # avoid double BOS on tokenizers that would add it. + encode_kwargs = ( + {"add_special_tokens": False} + if self._tokenizer_auto_adds_specials + else {} + ) try: - prompt_ids = self.tokenizer_manager.tokenizer.apply_chat_template( + rendered_prompt = self.tokenizer_manager.tokenizer.apply_chat_template( openai_compatible_messages, - tokenize=True, + tokenize=False, add_generation_prompt=True, tools=tools, return_dict=False, **extra_template_kwargs, ) + prompt_ids = self.tokenizer_manager.tokenizer.encode( + rendered_prompt, **encode_kwargs + ) except Exception as e: # If the first attempt fails, try with flat function-only format. # Some templates (e.g. Mistral) expect tools without the OpenAI wrapper. @@ -747,13 +772,18 @@ class OpenAIServingChat(OpenAIServingBase): else None ) try: - prompt_ids = self.tokenizer_manager.tokenizer.apply_chat_template( - openai_compatible_messages, - tokenize=True, - add_generation_prompt=True, - tools=tools, - return_dict=False, - **extra_template_kwargs, + rendered_prompt = ( + self.tokenizer_manager.tokenizer.apply_chat_template( + openai_compatible_messages, + tokenize=False, + add_generation_prompt=True, + tools=tools, + return_dict=False, + **extra_template_kwargs, + ) + ) + prompt_ids = self.tokenizer_manager.tokenizer.encode( + rendered_prompt, **encode_kwargs ) except jinja2.TemplateError as template_error: # Template errors (e.g., from raise_exception in Jinja templates)