Skip to content

refactor(grpc): unify regular/PD/EPD into one Mode-parameterized router + EncodeStage - #1923

Merged
slin1237 merged 12 commits into
mainfrom
refactor/grpc-router-mode-abstraction
Jul 15, 2026
Merged

refactor(grpc): unify regular/PD/EPD into one Mode-parameterized router + EncodeStage#1923
slin1237 merged 12 commits into
mainfrom
refactor/grpc-router-mode-abstraction

Conversation

@slin1237

@slin1237 slin1237 commented Jul 14, 2026

Copy link
Copy Markdown
Member

Description

Problem

The regular / PD / EPD axis — which disaggregation topology a request uses — was modeled in three places, three different ways:

  • Router layer: two structs (GrpcRouter for regular vs GrpcPDRouter for PD/EPD) plus a stringly-typed router_type label field. GrpcPDRouter was a strict subset of GrpcRouter (fewer pipelines) plus that label.
  • Pipeline layer: ~9 near-duplicate constructors (new_regular / new_pd / new_epd / new_messages{,_pd,_epd} / new_completion{,_pd,_epd} / new_harmony{,_pd} / new_embeddings / new_classify). The stage lists were identical across modes for a given endpoint; only the baked-in (WorkerSelectionMode, ExecutionPlanKind, inject_pd_metadata) args differed.
  • Stage layer: clean (WorkerSelectionMode / ExecutionPlanKind constructor args).

On top of that, EPD's distinctive step — encode — was not a pipeline stage at all. It was smeared across epd_encode.rs (adapters), plan_epd_encode in common/stages/helpers.rs, a duplicated block in both the chat and messages request_building stages, and an EncodeDispatchPlan carried inside the ExecutionPlan::EncodePrefillDecode enum variant. The MultimodalIntermediate was owned by PreparationOutput and move-consumed in request-building, so encode could not be a separate borrowing stage.

Solution

Model the axis once:

  • Mode { Regular, PrefillDecode, EncodePrefillDecode } (grpc/mode.rs) is the single source of truth. It maps totally to the three stage params and the metrics label: worker_selection(), plan_kind(), inject_pd_metadata(), router_type(). A grpc_mode(cfg) helper derives it once from (ConnectionMode, RoutingMode).
  • One GrpcRouter, parameterized by Mode. GrpcPDRouter is deleted. The router carries the regular superset of pipelines with the regular-only ones (harmony, embedding, classify, responses, harmony_responses) as Option, built only when mode == Regular; PD/EPD leave them None and thus return the same trait-default 501 those endpoints returned before. router_type() returns self.mode.router_type().
  • One pipeline builder, RequestPipeline::build(endpoint, mode, deps) (grpc/pipeline.rs), replacing the ~9 constructors. It composes the stage list over the valid (endpoint, mode) matrix and returns None for invalid combos (harmony×EPD, embeddings/classify×PD/EPD — preserving today's gaps). It threads mode into the stages that already accept it.
  • Encode is a first-class EncodeStage (grpc/common/stages/encode.rs), inserted between ClientAcquisition and RequestBuilding only for EPD pipelines. MultimodalIntermediate is re-homed onto ProcessingState, where PreparationStage writes it, EncodeStage borrows it (encode payload, with pixels), and RequestBuilding consumes it (prefill payload, without pixels). Encode results land in a new ProcessingState::encode_outputs { bootstrap_info, dispatch }; RequestBuilding reads the bootstrap info and RequestExecution takes the dispatch. The ExecutionPlan::EncodePrefillDecode variant is slimmed to { request }.

Behavior is preserved across every mode: metrics label strings ("grpc" / "grpc_pd" / "grpc_epd"), per-mode retry worker labels, 501 responses for regular-only endpoints under PD/EPD, the harmony×EPD gap, encode RPC semantics (fire-and-supervise, wire bootstrap rooms), and the SHM/RDMA cleanup guards (which now move with the owning state, so a cancellation between EncodeStage and execution still reclaims /dev/shm segments via Drop). The only intentional behavior change is one sanctioned bugfix (below).

Changes

Create

  • grpc/mode.rsMode enum + worker_selection / plan_kind / inject_pd_metadata / router_type, and grpc_mode(cfg) derivation.
  • grpc/common/stages/encode.rsEncodeStage plus the encode plan/dispatch types and adapters relocated from epd_encode.rs.

Modify

  • grpc/router.rs — add mode; make regular-only pipelines/contexts Option; build via RequestPipeline::build; router_type() and Debug from mode; per-mode metric labels via match.
  • grpc/pipeline.rs — replace the new_* constructors with build(endpoint, mode) + an Endpoint enum + the validity matrix; insert EncodeStage for EPD.
  • grpc/context.rs — re-home multimodal_intermediate onto ProcessingState; add encode_outputs (EncodeOutputs { bootstrap_info, dispatch }); slim ExecutionPlan::EncodePrefillDecode to { request }.
  • grpc/common/stages/helpers.rs — remove plan_epd_encode (moved into EncodeStage).
  • grpc/common/stages/request_execution.rs — take the dispatch from encode_outputs instead of the enum field.
  • grpc/regular/stages/{chat,messages}/request_building.rs — delete the duplicated inline encode block; read encode_outputs / multimodal_intermediate from state.
  • grpc/regular/stages/{chat,messages}/preparation.rs — write multimodal_intermediate into ProcessingState.
  • routers/factory.rscreate_router and create_igw_routers derive Mode and build the unified GrpcRouter; create_grpc_pd_router / create_grpc_epd_router folded into create_grpc_router(ctx, mode).

Delete

  • grpc/pd_router.rs (GrpcPDRouter) — 535 lines.
  • grpc/epd_encode.rs (logic relocated under stages/encode.rs) — 298 lines.

Sanctioned bugfix (only intentional behavior change). Pre-refactor GrpcPDRouter::route_completion_impl used self.retry_config directly, skipping the per-model retry override every other method applied. Routing PD/EPD completion through the unified route_completion fixes that by construction: the per-model override now applies uniformly for every mode.

Net effect. grpc subsystem: 1645 insertions / 1716 deletions (net -71). Split into production vs test code: production code shrank by ~572 lines, while test code grew by ~501 lines (the parity goldens, the EncodeStage SHM lifecycle tests, and new Mode/router unit tests).

Test Plan

  • Pipeline build parity (the key guard). build_matches_frozen_goldens (grpc/pipeline.rs) asserts build(endpoint, mode) reproduces a hand-transcribed frozen golden — the exact stage-signature sequence (encoding WorkerSelectionMode, ExecutionPlanKind, and inject_pd_metadata) plus the metrics backend label — for every valid (endpoint, mode). The goldens are transcribed from origin/main, not derived from build, so a flipped param or swapped mode diverges and fails. Invalid combos (harmony×EPD, embeddings/classify×PD/EPD) assert None.
  • Mode mapping (unit). Mode → (WorkerSelectionMode, ExecutionPlanKind, inject_pd_metadata, router_type) matches the design table exactly.
  • SHM / cancellation Drop test (grpc/common/stages/encode.rs, Linux-gated, matching the existing multimodal SHM tests). Backs encode jobs with real /dev/shm segments and proves: dropping the owning ProcessingState before dispatch (cancellation / early return) reclaims the segment via Drop; a dispatched item transfers SHM ownership off the guard (no double-unlink).
  • Per-mode behavior preserved. router_type() returns "grpc" / "grpc_pd" / "grpc_epd"; is_pd_mode() still counts "pd" | "grpc_pd" (EPD excluded); regular-only endpoints return 501 under PD/EPD; IGW builds all three router instances under the same RouterIds.
  • Full suite green. cargo build -p smg, cargo test -p smg (lib: 1193 passed / 5 ignored; every integration binary — api_tests, routing_tests, spec_test, security_tests, reliability_tests, etc. — 0 failed), cargo clippy -p smg --all-targets, cargo +nightly fmt --check.
Checklist
  • cargo +nightly fmt passes
  • cargo clippy --all-targets --all-features -- -D warnings passes
  • (Optional) Documentation updated
  • (Optional) Please join us on Slack #sig-smg to discuss, review, and merge PRs

Summary by CodeRabbit

  • New Features

    • Added mode-aware gRPC routing and a unified pipeline builder for Regular, Prefill/Decode, and Encode/Prefill/Decode workflows.
    • Introduced an encode pipeline stage and centralized encode/disaggregation artifacts in router state.
    • Mode-aware endpoint handling now returns “Not Implemented” where applicable, with improved router diagnostics and retry behavior.
  • Bug Fixes

    • Improved shared-memory cleanup behavior during encode processing.
  • Tests

    • Added coverage for router type mapping, pipeline parity, retry overrides, and shared-memory lifecycle behavior.

slin1237 added 11 commits July 14, 2026 07:54
…D/EPD

Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
…y guard

Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
…vacuous guard)

Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
…cRouter

Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
…lot on ProcessingState

Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
…ed by build()

Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
…ctors

Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
…ants

Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d8321c7b-2e9f-4a21-8882-349a126a913a

📥 Commits

Reviewing files that changed from the base of the PR and between 87454ec and 8a99c4d.

📒 Files selected for processing (1)
  • model_gateway/src/routers/grpc/common/stages/encode.rs

📝 Walkthrough

Walkthrough

The gRPC routing refactor introduces explicit Regular, PD, and EPD modes, consolidates endpoint pipeline construction, moves multimodal and encode artifacts into shared processing state, and updates router behavior and tests for mode-specific execution.

Changes

gRPC mode and factory wiring

Layer / File(s) Summary
Mode contract and router factory
model_gateway/src/routers/factory.rs, model_gateway/src/routers/grpc/mode.rs, model_gateway/src/routers/grpc/mod.rs
gRPC modes map configuration to worker selection, execution plans, metadata injection, and router labels; factory construction now registers policies before creating a mode-aware router.
Mode-aware router behavior
model_gateway/src/routers/grpc/router.rs
GrpcRouter conditionally creates endpoint pipelines and response contexts, returns 501 NOT_IMPLEMENTED for unsupported mode/endpoint combinations, centralizes retry configuration and metrics, and reports mode-specific diagnostics.
Unified endpoint pipeline builder
model_gateway/src/routers/grpc/pipeline.rs, model_gateway/src/routers/grpc/common/stages/*, model_gateway/src/routers/grpc/harmony/stages/request_building.rs, model_gateway/src/routers/grpc/regular/stages/*
RequestPipeline::build replaces endpoint-specific constructors, derives stage configuration from (Endpoint, Mode), rejects invalid combinations, and validates stage parity.
Shared multimodal and EPD state flow
model_gateway/src/routers/grpc/context.rs, model_gateway/src/routers/grpc/common/stages/*, model_gateway/src/routers/grpc/regular/stages/{chat,messages}/*, model_gateway/src/routers/grpc/utils/*
Multimodal intermediates and encode dispatch outputs are stored in ProcessingState; encode planning, request construction, execution, and SHM ownership handling consume the centralized state.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RouterFactory
  participant GrpcRouter
  participant RequestPipeline
  participant EncodeStage
  participant RequestExecutionStage
  RouterFactory->>GrpcRouter: create_router(ctx, mode)
  GrpcRouter->>RequestPipeline: build(endpoint, mode, deps)
  RequestPipeline->>EncodeStage: execute multimodal EPD stage
  EncodeStage->>GrpcRouter: store encode_outputs in ProcessingState
  RequestExecutionStage->>GrpcRouter: take encode_outputs.dispatch
  RequestExecutionStage->>RequestExecutionStage: execute encode/prefill/decode dispatch
Loading

Possibly related PRs

Suggested labels: tests

Suggested reviewers: key4ng, chenht2022

Poem

I’m a rabbit with routes in a row,
Through Regular and PD modes I go.
Encode hops into state,
Pipelines coordinate—
While SHM guards rest safely below.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main refactor: unifying gRPC routing modes into one mode-parameterized router and adding EncodeStage.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/grpc-router-mode-abstraction

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added grpc gRPC client and router changes model-gateway Model gateway crate changes labels Jul 14, 2026

@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 refactors the gRPC router implementation by consolidating the separate GrpcPDRouter and GrpcRouter into a single, mode-parameterized GrpcRouter capable of serving Regular, PrefillDecode (PD), and EncodePrefillDecode (EPD) modes. It unifies the request pipelines into a builder pattern based on endpoint and mode, introduces a dedicated EncodeStage for EPD, and cleans up redundant files. The review feedback points out a naming collision in encode.rs between the tracing::error! macro and the crate::routers::error module, suggesting an alias to improve clarity.

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.

use super::PipelineStage;
use crate::{
routers::{
error,

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

There is a naming collision and ambiguity between the tracing::error! macro (imported on line 24) and the crate::routers::error module (imported on line 30). While Rust allows this due to separate namespaces for macros and modules, it is highly confusing for readers since error refers to two completely different concepts in the same file. Consider aliasing the router error module to router_error to improve code clarity. If you apply this alias, please also update the usages of the module (such as error::bad_request on line 82) to router_error::bad_request.

Suggested change
error,
error as router_error,

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 87454ec7ec

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

.ok_or_else(|| format!("gRPC router: no completion pipeline for mode {mode:?}"))?;

// Regular-only pipelines; `None` in PD/EPD (which 501 these endpoints).
let harmony_pipeline = RequestPipeline::build(Endpoint::Harmony, mode, &configured_deps);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep Harmony routing disabled in PD mode

When the router is constructed with Mode::PrefillDecode, this line still builds a Harmony pipeline because RequestPipeline::build(Endpoint::Harmony, ...) only returns None for EPD. route_chat_impl then uses self.harmony_pipeline.is_some() before HarmonyDetector, so any grpc_pd deployment whose model id/card looks like GPT-OSS is diverted into HarmonyPreparation/HarmonyResponseProcessing; the deleted GrpcPDRouter always sent PD chat through the normal PD chat pipeline. Gate this on mode == Mode::Regular (or make the builder return None for PD) to preserve the previous PD routing behavior.

Useful? React with 👍 / 👎.

@lightseek-bot
lightseek-bot requested a review from chenht2022 July 14, 2026 20:34
@key4ng

key4ng commented Jul 14, 2026

Copy link
Copy Markdown
Member

Tested this PR with TokenSpeed EPD on a single 8xH100 node. The end-to-end multimodal path passed with three dedicated GPUs.

Result

  • SMG PR head: 87454ec7ec9d81308ae6695545d1bf231358fdbd
  • SMG binary SHA-256: 94fc6cd81e1f32b08e52087fd5c16cc615412d0eec11d39faa4c9699ec13b77f
  • TokenSpeed: 03f644e8914ac84082e43bb442cb71d7819b8549
  • Model: Qwen3.5-9B
  • GPU 0: Encode
  • GPU 1: Prefill
  • GPU 2: Decode
  • Transfer: Mooncake over intra-node NVLink
Image HTTP Response Latency
Red 200 Red 1.097 s
Blue 200 Blue 0.809 s

The latency numbers are smoke-test observations after initialization, not benchmark results.

SMG started in the new unified mode:

mode: EncodePrefillDecode {
  encode_urls: [("grpc://127.0.0.1:50104", Some(18995))],
  prefill_urls: [("grpc://127.0.0.1:50101", Some(19311))],
  decode_urls: ["grpc://127.0.0.1:50111"]
}

For the successful requests, Prefill's Mooncake sender and Decode's receiver logged the same rendezvous rooms:

  • Red request chatcmpl-019f625f-dee1-75e3-8114-9ead8c1798b9: room 6779238311409506535
  • Blue request chatcmpl-019f625f-e369-78c2-83ee-e41e54dbbc2e: room 9196872384937384241

Decode logged both requests as Finish!. All three workers selected Using Intra-Node NVLink transport (MC_INTRANODE_NVLINK set). The Encode worker ran in encoder-only mode. Its current INFO logs do not print the per-request E-to-P rendezvous room, so E-to-P is verified by the successful end-to-end image classification rather than a directly logged room ID.

No SMG or TokenSpeed runtime source changes were needed. This PR's router/EncodeStage refactor preserved the tested TokenSpeed EPD behavior.

Environment notes that were necessary for this H100 setup, but do not appear to be regressions from this PR:

  • Use --attention-backend fa3; trtllm failed on the first real H100 forward pass with TllmGenFmhaRunner: Unsupported architecture.
  • Prefill needed --kvstore-ratio 0.5; the current default 2.0 produced a negative KV token-pool size in this configuration.
  • Set CUDA_VISIBLE_DEVICES explicitly. On this host, --privileged exposed every GPU despite Docker's --gpus selection.
  • The gateway must be able to read the worker-advertised tokenizer path. I ran the PR binary inside the TokenSpeed container environment and mounted the model at /models/Qwen3.5-9B.
  • I selected a free Prometheus port (29080) because the default metrics port was already in use.
Build and run commands

Build the exact PR head in an isolated checkout:

git clone https://github.com/lightseekorg/smg.git /raid/smg-pr1923
git -C /raid/smg-pr1923 fetch origin pull/1923/head
git -C /raid/smg-pr1923 checkout --detach 87454ec7ec9d81308ae6695545d1bf231358fdbd

CARGO_TARGET_DIR=/raid/smg-pr1923-target \
  cargo build --release -p smg \
  --manifest-path /raid/smg-pr1923/Cargo.toml

sha256sum /raid/smg-pr1923-target/release/smg

Shared values:

export TS_IMAGE=codex/tokenspeed-epd:main-03f644e-ready
export MODEL_HOST=/raid/models/Qwen/Qwen3.5-9B
export MODEL_CONTAINER=/models/Qwen3.5-9B

Encode on physical GPU 0:

docker run -d --name ts-pr1923-encode \
  --gpus '"device=0"' \
  --network host --ipc host --pid host --privileged \
  -e CUDA_VISIBLE_DEVICES=0 \
  -e MC_INTRANODE_NVLINK=1 \
  -e TOKENSPEED_SKIP_GRPC_WARMUP=1 \
  -v "${MODEL_HOST}:${MODEL_CONTAINER}:ro" \
  "${TS_IMAGE}" \
  python3 -m smg_grpc_servicer.tokenspeed \
    --model "${MODEL_CONTAINER}" \
    --served-model-name Qwen3.5-9B \
    --host 0.0.0.0 --port 50104 \
    --tensor-parallel-size 1 \
    --max-model-len 8192 --max-num-seqs 4 \
    --gpu-memory-utilization 0.8 \
    --attention-backend fa3 \
    --disaggregation-mode encode \
    --disaggregation-bootstrap-port 18995 \
    --disaggregation-transfer-backend mooncake \
    --dist-init-addr 127.0.0.1:25000 \
    --skip-server-warmup

Prefill on physical GPU 1:

docker run -d --name ts-pr1923-prefill \
  --gpus '"device=1"' \
  --network host --ipc host --pid host --privileged \
  -e CUDA_VISIBLE_DEVICES=1 \
  -e MC_INTRANODE_NVLINK=1 \
  -e TOKENSPEED_SKIP_GRPC_WARMUP=1 \
  -v "${MODEL_HOST}:${MODEL_CONTAINER}:ro" \
  "${TS_IMAGE}" \
  python3 -m smg_grpc_servicer.tokenspeed \
    --model "${MODEL_CONTAINER}" \
    --served-model-name Qwen3.5-9B \
    --host 0.0.0.0 --port 50101 \
    --tensor-parallel-size 1 \
    --max-model-len 8192 --max-num-seqs 4 \
    --gpu-memory-utilization 0.8 \
    --attention-backend fa3 \
    --disaggregation-mode prefill \
    --disaggregation-bootstrap-port 19311 \
    --disaggregation-transfer-backend mooncake \
    --dist-init-addr 127.0.0.1:26000 \
    --kvstore-ratio 0.5 \
    --enable-prefix-caching \
    --enforce-eager \
    --skip-server-warmup

Decode on physical GPU 2:

docker run -d --name ts-pr1923-decode \
  --gpus '"device=2"' \
  --network host --ipc host --pid host --privileged \
  -e CUDA_VISIBLE_DEVICES=2 \
  -e MC_INTRANODE_NVLINK=1 \
  -e TOKENSPEED_SKIP_GRPC_WARMUP=1 \
  -v "${MODEL_HOST}:${MODEL_CONTAINER}:ro" \
  "${TS_IMAGE}" \
  python3 -m smg_grpc_servicer.tokenspeed \
    --model "${MODEL_CONTAINER}" \
    --served-model-name Qwen3.5-9B \
    --host 0.0.0.0 --port 50111 \
    --tensor-parallel-size 1 \
    --max-model-len 8192 --max-num-seqs 4 \
    --gpu-memory-utilization 0.8 \
    --attention-backend fa3 \
    --disaggregation-mode decode \
    --disaggregation-transfer-backend mooncake \
    --dist-init-addr 127.0.0.1:32000 \
    --enable-prefix-caching \
    --skip-server-warmup

Run the exact PR binary as the EPD gateway:

docker run -d --name ts-pr1923-gateway \
  --network host --ipc host \
  -v /raid/smg-pr1923-target/release/smg:/opt/smg-pr1923:ro \
  -v "${MODEL_HOST}:${MODEL_CONTAINER}:ro" \
  "${TS_IMAGE}" \
  /opt/smg-pr1923 launch \
    --epd-disaggregation \
    --encode grpc://127.0.0.1:50104 18995 \
    --prefill grpc://127.0.0.1:50101 19311 \
    --decode grpc://127.0.0.1:50111 \
    --encode-policy consistent_hashing \
    --prefill-policy round_robin \
    --decode-policy round_robin \
    --policy cache_aware \
    --multimodal-tensor-transport inline \
    --model-path "${MODEL_CONTAINER}" \
    --disable-health-check \
    --prometheus-port 29080 \
    --host 0.0.0.0 --port 12345

Wait for all workers and the gateway:

for container in ts-pr1923-encode ts-pr1923-prefill ts-pr1923-decode; do
  until docker logs "${container}" 2>&1 | grep -q 'health status -> SERVING'; do
    sleep 2
  done
done

until curl -fsS http://127.0.0.1:12345/v1/models >/dev/null; do
  sleep 2
done

Example multimodal request (<BASE64_PNG> was replaced with a valid 32x32 red or blue PNG):

curl -sS http://127.0.0.1:12345/v1/chat/completions \
  -H 'Content-Type: application/json' \
  --data-binary @- <<'JSON'
{
  "model": "Qwen3.5-9B",
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "image_url",
          "image_url": {
            "url": "data:image/png;base64,<BASE64_PNG>"
          }
        },
        {
          "type": "text",
          "text": "What is the color of the image? Reply with only the color."
        }
      ]
    }
  ],
  "temperature": 0,
  "max_tokens": 128,
  "stream": false
}
JSON

@lightseek-bot lightseek-bot 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
Thanks for the hard work!!

The Linux-gated dispatch SHM test asserted the segment survived dispatch, but
dispatch() disarms the item's own Drop and reclaims the segment via the
send-path guard when it returns (success or failure). Assert that real,
origin-preserved behavior: a dispatch reclaims the segment exactly once.

Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
@slin1237
slin1237 merged commit cf93cc4 into main Jul 15, 2026
42 of 46 checks passed
@slin1237
slin1237 deleted the refactor/grpc-router-mode-abstraction branch July 15, 2026 00:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

grpc gRPC client and router changes model-gateway Model gateway crate changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants