99import argparse
1010import logging
1111import os
12+ import re
1213from pathlib import Path
1314
1415from executorch .extension .llm .server .python .chat_template import ChatTemplate
1516from executorch .extension .llm .server .python .serving_chat import ServingChat
1617from executorch .extension .llm .server .python .session_runtime import SessionRuntime
1718from executorch .extension .llm .server .python .tool_parsers import (
19+ GemmaToolCallDetector ,
1820 HermesDetector ,
1921 QwenFunctionCallDetector ,
2022)
2123from executorch .extension .llm .server .python .worker_client import spawn_worker
2224
2325logger = logging .getLogger (__name__ )
2426
27+ _GEMMA_CHANNEL_SPECIALS = {"<|channel>" , "<channel|>" , "<|think|>" }
28+ _GEMMA_CHANNEL_BLOCK = re .compile (r"<\|channel>.*?<channel\|>" , re .DOTALL )
29+
30+
31+ def _strip_gemma_channels (text : str ) -> str :
32+ text = _GEMMA_CHANNEL_BLOCK .sub ("" , text )
33+ open_idx = text .find ("<|channel>" )
34+ if open_idx != - 1 :
35+ text = text [:open_idx ]
36+ return text .replace ("<channel|>" , "" ).replace ("<|think|>" , "" ).strip ()
37+
2538
2639def _default_worker_bin () -> str :
2740 repo_root = Path (__file__ ).resolve ().parents [3 ]
@@ -62,6 +75,8 @@ def _spawn(args):
6275
6376
6477def _tool_detector (name : str ):
78+ if name == "gemma" :
79+ return GemmaToolCallDetector
6580 if name == "hermes" :
6681 return HermesDetector
6782 if name == "qwen" :
@@ -72,7 +87,13 @@ def _tool_detector(name: str):
7287
7388
7489def build_app_from_args (args ):
75- template = ChatTemplate (args .hf_tokenizer )
90+ template = ChatTemplate (
91+ args .hf_tokenizer ,
92+ assistant_header = "<|turn>model\n " ,
93+ # Gemma's HF template starts with bos_token text. Strip that text before
94+ # C++ tokenization; the worker prepends the numeric BOS id.
95+ strip_rendered_bos = True ,
96+ )
7697 worker = _spawn (args )
7798 runtime = SessionRuntime (worker )
7899 serving = ServingChat (
@@ -82,6 +103,8 @@ def build_app_from_args(args):
82103 max_context = args .max_context ,
83104 tool_detector_cls = _tool_detector (args .tool_parser ),
84105 prompt_token_offset = 1 ,
106+ content_filter = _strip_gemma_channels ,
107+ content_filter_specials = _GEMMA_CHANNEL_SPECIALS ,
85108 )
86109
87110 from executorch .extension .llm .server .python .server import build_app
@@ -132,11 +155,17 @@ def main() -> None:
132155 )
133156 p .add_argument (
134157 "--tool-parser" ,
135- choices = ("hermes" , "qwen" , "none" ),
136- default = "hermes " ,
158+ choices = ("gemma" , " hermes" , "qwen" , "none" ),
159+ default = "gemma " ,
137160 help = "Tool-call format parser to apply to model output." ,
138161 )
139- p .add_argument ("--bos-id" , type = int , default = 2 )
162+ p .add_argument (
163+ "--bos-id" ,
164+ type = int ,
165+ default = 2 ,
166+ help = "BOS token id to prepend in the worker. The launcher strips the "
167+ "HF template's literal <bos> before C++ tokenization." ,
168+ )
140169 p .add_argument ("--eos-id" , type = int , default = 1 )
141170 p .add_argument (
142171 "--worker-bin" ,
0 commit comments