Skip to content

Commit dfb0609

Browse files
lancellylfr-0531
andauthored
[None][perf] disagg: opt-in ctx-side base64 int32 transport for prompt_token_ids (#15698)
Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com> Co-authored-by: Fanrong Li <23290157+lfr-0531@users.noreply.github.com>
1 parent 2f5ffc9 commit dfb0609

4 files changed

Lines changed: 43 additions & 1 deletion

File tree

tensorrt_llm/llmapi/disagg_utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ class DisaggServerConfig():
9090
# worker's request body / JSON-parse GIL cost at high concurrency. Enable
9191
# ONLY for text-only, non-harmony deployments (see _get_gen_request).
9292
gen_strip_message_history: bool = False
93+
# Ask context workers to return prompt_token_ids as a base64 int32 buffer so
94+
# the orchestrator relays a string instead of materializing the token-id list
95+
# on its event loop. Text-only, non-harmony deployments (see _get_ctx_request).
96+
gen_tokids_ctxbytes: bool = False
9397

9498

9599
@dataclass
@@ -140,6 +144,7 @@ def extract_disagg_cfg(hostname: str = 'localhost',
140144
'context_first',
141145
'generation_first'] = 'context_first',
142146
gen_strip_message_history: bool = False,
147+
gen_tokids_ctxbytes: bool = False,
143148
**kwargs: Any) -> DisaggServerConfig:
144149
context_servers = context_servers or {}
145150
generation_servers = generation_servers or {}
@@ -188,6 +193,7 @@ def extract_disagg_cfg(hostname: str = 'localhost',
188193
if schedule_style:
189194
config.schedule_style = schedule_style
190195
config.gen_strip_message_history = gen_strip_message_history
196+
config.gen_tokids_ctxbytes = gen_tokids_ctxbytes
191197
return config
192198

193199

tensorrt_llm/serve/openai_disagg_service.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ def __init__(
7878
self._health_check_interval_secs = health_check_interval_secs
7979
# Opt-in body-shrink for generation_only requests; see _get_gen_request.
8080
self._strip_gen_message_history = config.gen_strip_message_history
81+
# Opt-in: ask context workers to return prompt_token_ids as base64 int32.
82+
self._tokids_ctxbytes = config.gen_tokids_ctxbytes
8183

8284
self._ctx_client = None
8385
self._gen_client = None
@@ -320,6 +322,7 @@ def _get_ctx_request(
320322
request_type="context_only",
321323
disagg_request_id=disagg_request_id,
322324
schedule_style=self._schedule_style,
325+
return_prompt_token_ids_b64=self._tokids_ctxbytes,
323326
),
324327
"stream": False,
325328
"stream_options": None,
@@ -342,7 +345,12 @@ def _get_gen_request(
342345
if isinstance(request, CompletionRequest):
343346
request.prompt = ctx_response.prompt_token_ids
344347
elif isinstance(request, ChatCompletionRequest):
345-
request.prompt_token_ids = ctx_response.prompt_token_ids
348+
# Relay the base64 token-id string verbatim (no int-list
349+
# materialization on the orchestrator loop), else the int array.
350+
if ctx_response.prompt_token_ids_b64 is not None:
351+
request.prompt_token_ids_b64 = ctx_response.prompt_token_ids_b64
352+
else:
353+
request.prompt_token_ids = ctx_response.prompt_token_ids
346354
# Opt-in: drop conversation history so the gen worker doesn't
347355
# re-parse the full conversation JSON (dominates its GIL at high
348356
# concurrency). It uses prompt_token_ids and only reads the last

tensorrt_llm/serve/openai_protocol.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ class DisaggregatedParams(OpenAIBaseModel):
137137
ctx_dp_rank: Optional[int] = None
138138
ctx_info_endpoint: Optional[str] = None
139139
schedule_style: Optional[DisaggScheduleStyle] = None
140+
# Orchestrator -> context-worker instruction: return prompt_token_ids as a
141+
# base64 int32 buffer (prompt_token_ids_b64) instead of a JSON int array.
142+
return_prompt_token_ids_b64: bool = False
140143

141144

142145
class ConversationParams(OpenAIBaseModel):
@@ -618,6 +621,9 @@ class ChatCompletionResponse(OpenAIBaseModel):
618621
# Add prompt_tokens_ids to the response to remove the tokenization
619622
# in the generation server in disaggreated serving
620623
prompt_token_ids: Optional[List[int]] = None
624+
# base64 int32 buffer alternative to prompt_token_ids; set by the context
625+
# worker so the orchestrator can relay a string instead of the int list.
626+
prompt_token_ids_b64: Optional[str] = None
621627

622628

623629
class DeltaMessage(OpenAIBaseModel):
@@ -675,6 +681,9 @@ class ChatCompletionRequest(OpenAIBaseModel):
675681
# Add prompt_tokens_ids to the request to remove the tokenization
676682
# in the generation server in disaggreated serving
677683
prompt_token_ids: Optional[List[int]] = None
684+
# base64 int32 buffer relayed by the orchestrator from the ctx response;
685+
# decoded back to prompt_token_ids on the generation worker. Not for clients.
686+
prompt_token_ids_b64: Optional[str] = None
678687
model: str
679688
frequency_penalty: Optional[float] = 0.0
680689
logit_bias: Optional[Dict[str, float]] = None

tensorrt_llm/serve/openai_server.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,13 @@ async def chat_stream_generator(
11811181
self.multimodal_server_config,
11821182
request_media_io_kwargs=request.media_io_kwargs)
11831183

1184+
# Decode base64 int32 prompt_token_ids relayed by the orchestrator.
1185+
if request.prompt_token_ids is None and request.prompt_token_ids_b64:
1186+
import numpy as np
1187+
request.prompt_token_ids = np.frombuffer(
1188+
base64.b64decode(request.prompt_token_ids_b64),
1189+
dtype=np.int32).tolist()
1190+
11841191
if request.prompt_token_ids is not None:
11851192
prompt = request.prompt_token_ids
11861193
else:
@@ -1263,6 +1270,18 @@ async def chat_stream_generator(
12631270
else:
12641271
response = await self._create_chat_response(
12651272
promise, postproc_params, raw_request, disaggregated_params)
1273+
# Context-only: optionally return prompt_token_ids as a base64
1274+
# int32 buffer (one string) so the disagg orchestrator relays it
1275+
# without materializing the int list on its event loop. The encode
1276+
# runs here on the context worker (1-of-N), not the orchestrator.
1277+
if (request.disaggregated_params is not None and
1278+
request.disaggregated_params.return_prompt_token_ids_b64
1279+
and response.prompt_token_ids is not None):
1280+
import numpy as np
1281+
response.prompt_token_ids_b64 = base64.b64encode(
1282+
np.asarray(response.prompt_token_ids,
1283+
dtype=np.int32).tobytes()).decode("ascii")
1284+
response.prompt_token_ids = None
12661285
return JSONResponse(content=response.model_dump())
12671286
except CppExecutorError:
12681287
logger.error(traceback.format_exc())

0 commit comments

Comments
 (0)