Skip to content

test: multimodal tensor-transport + EPD disaggregation coverage#1898

Open
slin1237 wants to merge 23 commits into
mainfrom
test/mm-transport-epd-coverage
Open

test: multimodal tensor-transport + EPD disaggregation coverage#1898
slin1237 wants to merge 23 commits into
mainfrom
test/mm-transport-epd-coverage

Conversation

@slin1237

@slin1237 slin1237 commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Why

Establishes CI ground truth for the multimodal tensor-transport and EPD (encode-prefill-decode) paths before an upcoming refactor that generalizes the RDMA transport (extract it into its own crate and add the remote/RDMA payload for vLLM). Today those paths are thinly covered: the only multimodal end-to-end test is image-only on the default in-message transport, there is no shared-memory or EPD end-to-end test, and the EPD encode planning has no unit tests. Without a net, a refactor bug would ship silently.

What's added (one commit each)

1. Rust unit tests (no GPU) — payload resolution + EPD planning

  • The transport decision (resolve_mm_shm_enabled) is now covered: in-message and RDMA modes stay off; shared-memory follows /dev/shm writability; auto requires a matching shared-memory namespace. Previously untested.
  • vLLM tensor assembly is asserted to emit the in-message vs shared-memory payload variant (the shared-memory arm was only exercised with it disabled before).
  • Extracted plan_encode_jobs (behavior-preserving) so the EPD encode item↔worker assignment matching, count/order checks, and bootstrap-info assembly are unit-testable without a live TokenSpeed worker; added coverage for those.

2. Shared-memory multimodal e2e (1 GPU, vLLM)

  • Runs the multimodal suite with the gateway forced to the /dev/shm transport (gateway + worker are co-located in CI, so they share /dev/shm) and asserts the pixel tensor actually traveled over shared memory (via the smg_mm_tensors_total{path="shm"} metric) — not that it silently fell back to the in-message path.
  • Adds a small Prometheus /metrics scraper to the e2e infra (none existed).

3. EPD multimodal e2e (4 GPUs, TokenSpeed)

  • New e2e harness for EPD disaggregation: launches encode + prefill + decode workers, wires the gateway's EPD mode, and covers four topologies — 1e1p1d, 1e2p1d, 2e1p1d, 1e1p2d — sending an image and asserting a correct description. The encode worker runs the vision tower; prefill/decode run the language model.
  • Model: Qwen/Qwen3.6-35B-A3B-FP8. Its architecture is in TokenSpeed's multimodal registry, only 3B parameters are active, and FP8 (Hopper-native, so it runs on the h100 runner) fits one card — so every topology fits the existing 4-GPU lane at one worker per card.
  • CI: a tokenspeed leg on the 4-GPU chat job runs it per-PR.

Verification

  • Rust: the new unit tests pass (transport 8, proto_wrapper vLLM 3, epd_encode 5); cargo fmt/clippy clean for the changed code.
  • Python: ruff check + ruff format clean; py_compile clean; CI YAML validated.
  • The two GPU end-to-end tests run in the CI GPU lanes.

Note

The EPD test needs the Qwen/Qwen3.6-35B-A3B-FP8 checkpoint present in the CI model cache; it fails fast (like other model-dependent e2e tests) if absent.

Summary by CodeRabbit

  • New Features
    • Added TokenSpeed EPD multimodal chat completion end-to-end coverage on 4‑GPU setups, including full encode/prefill/decode orchestration and multimodal prompt/usage checks.
    • Added EPD gateway startup support plus Prometheus metric retrieval helpers to verify real execution paths.
    • Expanded multimodal SHM transport test coverage and enabled an added EPD multimodal model.
  • Bug Fixes
    • Improved encode job planning/dispatch validation and SHM payload selection reliability.
    • Prevented unintended CI model downloads for tier-skipped specs.
  • Tests
    • Added EPD and SHM assertions using worker-log markers and SHM-path verification.
  • Chores
    • Tuned TokenSpeed 4‑GPU E2E timeouts, added per-test timeout wiring, and added a dedicated EPD model download step.

slin1237 added 3 commits July 9, 2026 08:24
…nning

GPU-free regression net ahead of generalizing the multimodal tensor
transport (crate extraction + adding the RDMA/remote payload path):

- transport.rs: resolve_mm_shm_enabled matrix (inline/rdma -> off, shm ->
  dev-writable, auto -> shm-namespace match), previously untested.
- proto_wrapper.rs: vLLM into_proto payload variant — inline vs shm
  (the shm arm was only exercised with shm_enabled=false before).
- epd_encode.rs: extract plan_encode_jobs() (behavior-preserving) so the
  encode item/assignment count+order checks and bootstrap-info assembly
  are unit-testable without a live TokenSpeed client; add coverage for
  item assembly, count/order mismatch, and bootstrap host/port/room.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
The multimodal e2e only exercised the default inline transport. Add a
Qwen3-VL/vLLM class that launches the gateway with
`--multimodal-tensor-transport shm` (gateway + worker are co-located in
CI, so they share /dev/shm) and asserts the pixel tensor actually
traveled over shm via `smg_mm_tensors_total{path="shm"}` — not that it
silently fell back to the inline gRPC payload.

Adds a small Prometheus `/metrics` scraper (`Gateway.metric_sum`) to the
e2e infra, since none existed.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
Adds a live Encode-Prefill-Decode e2e: the encode worker runs the vision
tower, prefill/decode run the LM, and the gateway stitches encode ->
prefill -> decode. Covers the EPD path (TokenSpeed-only) that had no e2e.

- harness: `WorkerType.ENCODE` + `--disaggregation-mode encode|prefill|
  decode` (encode/prefill get a mooncake bootstrap port); gateway EPD mode
  (`--epd-disaggregation --encode/--prefill/--decode`); `_setup_epd` mirrors
  `_setup_pd` with encode-first GPU offsets; `epd` topology marker.
- model: Qwen/Qwen3.6-35B-A3B-FP8 — arch Qwen3_5MoeForConditionalGeneration
  is in TokenSpeed's multimodal registry, 3B active, FP8 (~35GB, Hopper-
  native so it runs on h100; NVFP4 would need Blackwell) fits one card at
  tp=1, so every topology (1e1p1d/1e2p1d/2e1p1d/1e1p2d) fits the 4-GPU lane.
- test: the four topologies, one image, assert a correct description.
- CI: a tokenspeed leg on e2e-4gpu-chat runs it per-PR.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
@github-actions github-actions Bot added ci CI/CD configuration changes grpc gRPC client and router changes tests Test changes model-gateway Model gateway crate changes labels Jul 9, 2026
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds TokenSpeed EPD disaggregation for multimodal workloads, including worker and gateway orchestration, EPD fixtures, encode planning, SHM transport validation, E2E coverage, and CI configuration.

Changes

EPD multimodal disaggregation

Layer / File(s) Summary
EPD worker and gateway wiring
e2e_test/infra/constants.py, e2e_test/infra/worker.py, e2e_test/infra/gateway.py
Adds encode worker configuration, tensor-parallel overrides, EPD gateway startup, bootstrap ports, and Prometheus metric helpers.
EPD backend fixture
e2e_test/fixtures/setup_backend.py
Parses EPD topologies and starts encode, prefill, and decode workers with gateway teardown.
TokenSpeed model and CI setup
e2e_test/infra/model_specs.py, .github/workflows/*, scripts/ci_download_model.sh, scripts/ci_install_tokenspeed.sh
Adds the EPD model specification, conditional model download, workflow timeouts, download filtering, and TokenSpeed build settings.
Encode planning and E2E validation
model_gateway/src/routers/grpc/epd_encode.rs, e2e_test/chat_completions/test_epd_multimodal.py, grpc_servicer/.../encoder_servicer.py
Validates encode assignments and tests multimodal requests across four EPD topologies with encode-worker logging.
SHM transport validation
model_gateway/src/routers/grpc/multimodal/transport.rs, model_gateway/src/routers/grpc/proto_wrapper.rs, e2e_test/chat_completions/test_multimodal.py
Tests SHM resolution, payload selection, cleanup, and gateway SHM metric increments.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: multimodal, dependencies

Suggested reviewers: CatherineSue, key4ng, gongwei-130

Poem

A rabbit sends a picture down the lane,
Encode and prefill catch the train,
Decode brings tokens, soft and bright,
SHM counters glow tonight. 🐇

🚥 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 reflects the main additions: multimodal tensor-transport tests and EPD disaggregation coverage.
Docstring Coverage ✅ Passed Docstring coverage is 88.24% 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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/mm-transport-epd-coverage

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

@claude

claude Bot commented Jul 9, 2026

Copy link
Copy Markdown

👋 The PR description doesn't fully follow
PULL_REQUEST_TEMPLATE.md:

  • Missing header: ## Description
  • Missing header: ### Problem
  • Missing header: ### Solution
  • Missing header: ## Changes
  • Missing header: ## Test Plan

Please update the PR description so reviewers have the context they need.

Comment thread e2e_test/infra/gateway.py Outdated

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

Clean test-focused PR — well-structured EPD and SHM transport coverage with solid Rust unit tests. One minor nit on an error message. LGTM.

0 🔴 Important · 1 🟡 Nit · 0 🟣 Pre-existing

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

@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: cf5966ef79

ℹ️ 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".

Comment thread e2e_test/infra/model_specs.py
Comment thread .github/workflows/pr-test-rust.yml
Comment thread e2e_test/fixtures/setup_backend.py Outdated

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@e2e_test/chat_completions/test_epd_multimodal.py`:
- Around line 39-42: Tighten the `_make_image_content` docstring to match its
actual behavior: it should only describe accepting an already-formed image URL
string, not a raw local path. Update the helper documentation in
`_make_image_content` so callers are directed to convert local files with
`_image_to_base64_url` before passing the result into this function.

In `@model_gateway/src/routers/grpc/epd_encode.rs`:
- Around line 186-196: Add a debug_assert in plan_encode_jobs to enforce the
documented non-empty items precondition before any planning logic runs. Use the
existing plan_encode_jobs symbol and check that items is not empty, so future
callers don’t rely only on build_plan’s guard. Keep the runtime behavior
unchanged; this is just a defensive invariant check that makes the documented
requirement explicit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: db4de777-d85a-47a6-babe-619443403c58

📥 Commits

Reviewing files that changed from the base of the PR and between 377981a and cf5966e.

📒 Files selected for processing (12)
  • .github/workflows/pr-test-rust.yml
  • e2e_test/chat_completions/test_epd_multimodal.py
  • e2e_test/chat_completions/test_multimodal.py
  • e2e_test/fixtures/hooks.py
  • e2e_test/fixtures/setup_backend.py
  • e2e_test/infra/constants.py
  • e2e_test/infra/gateway.py
  • e2e_test/infra/model_specs.py
  • e2e_test/infra/worker.py
  • model_gateway/src/routers/grpc/epd_encode.rs
  • model_gateway/src/routers/grpc/multimodal/transport.rs
  • model_gateway/src/routers/grpc/proto_wrapper.rs

Comment thread e2e_test/chat_completions/test_epd_multimodal.py
Comment thread model_gateway/src/routers/grpc/epd_encode.rs
- setup_backend: pass the EPD topology via request.param, not a class-scoped
  `epd` marker. The per-param marks weren't visible on the class-scoped
  fixture, so 1e2p1d/2e1p1d/1e1p2d all silently ran 1/1/1 — the topology
  matrix wasn't actually testing different topologies. (Codex)
- test_epd_multimodal: make it verify *real* EPD, not just that an answer
  came back (a single-worker fallback would pass that). Mirrors the PD
  KV-transfer tests: assert the encode worker itself logged accepting the
  request, and that the image was tokenized into the prompt. Adds the
  per-request accept log to the TokenSpeed encode servicer.
- model_specs + ci_download_model: `skip_tier_download` so the ~35GB EPD
  model isn't pre-pulled by every tier lane; the tokenspeed 4-GPU lane
  downloads it by id. (Codex P1)
- pr-test-rust: pass a larger pytest `test_timeout` for the EPD lane, so it
  isn't killed at the 25m default while relaunching worker sets. (Codex P2)
- gateway: clearer multi-mode error message. (Claude)
- tighten _make_image_content docstring. (CodeRabbit)

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
@slin1237 slin1237 requested a review from gongwei-130 as a code owner July 9, 2026 20:08
Comment thread e2e_test/chat_completions/test_epd_multimodal.py

@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: a33e33dc17

ℹ️ 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".

Comment thread e2e_test/chat_completions/test_epd_multimodal.py
Comment thread .github/workflows/pr-test-rust.yml

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
e2e_test/fixtures/setup_backend.py (1)

329-413: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the shared launch/teardown skeleton with _setup_pd.

_setup_epd duplicates the "start role workers at computed GPU offset -> _start_gateway -> yield -> finally: shutdown + stop_workers" structure from _setup_pd (Lines 260-321), just with an extra encode stage. A small helper (e.g., one that takes a list of (WorkerType, count, gpu_offset) tuples) would reduce duplication and keep future EPD/PD changes in sync.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e_test/fixtures/setup_backend.py` around lines 329 - 413, _extract the
shared launch/teardown flow currently duplicated between _setup_epd and
_setup_pd into a small helper that handles starting worker groups from
(WorkerType, count, gpu_offset) tuples, then calling _start_gateway, yielding,
and performing the finally shutdown/stop_workers cleanup; keep _setup_epd
focused on assembling encode/prefill/decode groups and passing them to the
helper, using the existing _start_workers_tracked, _start_gateway, and
stop_workers symbols to preserve behavior while reducing duplication.
e2e_test/infra/gateway.py (1)

90-93: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

No minimum-count validation for EPD worker lists.

is_epd_mode triggers on encode_workers is not None, but nothing enforces that encode_workers/prefill_workers/decode_workers are non-empty before building --epd-disaggregation args. Calling start(encode_workers=[], prefill_workers=[], decode_workers=[]) silently launches an EPD gateway with zero worker args, deferring the failure to an opaque subprocess/CLI error rather than a clear Python-side ValueError (as regular mode does at Lines 216-219 for worker_urls).

💡 Proposed validation
         elif is_epd_mode:
             self.pd_mode = False
             self.epd_mode = True
             self.igw_mode = False
             encodes = encode_workers or []
             prefills = prefill_workers or []
             decodes = decode_workers or []
+            if not encodes or not prefills or not decodes:
+                raise ValueError(
+                    "EPD mode requires non-empty encode_workers, prefill_workers, "
+                    "and decode_workers"
+                )

Also applies to: 166-199

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e_test/infra/gateway.py` around lines 90 - 93, The EPD path in
Gateway.start lacks the same minimum-count validation that regular mode applies
to worker URLs. In the Gateway.start logic that builds the epd-disaggregation
args from encode_workers, prefill_workers, and decode_workers, reject empty
lists with a clear ValueError before launching the subprocess, using the
existing validation pattern around worker_urls as a guide. Make sure the check
runs before is_epd_mode leads into the EPD argument assembly so
start(encode_workers=[], prefill_workers=[], decode_workers=[]) fails fast in
Python rather than in the CLI.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@e2e_test/fixtures/setup_backend.py`:
- Around line 329-413: _extract the shared launch/teardown flow currently
duplicated between _setup_epd and _setup_pd into a small helper that handles
starting worker groups from (WorkerType, count, gpu_offset) tuples, then calling
_start_gateway, yielding, and performing the finally shutdown/stop_workers
cleanup; keep _setup_epd focused on assembling encode/prefill/decode groups and
passing them to the helper, using the existing _start_workers_tracked,
_start_gateway, and stop_workers symbols to preserve behavior while reducing
duplication.

In `@e2e_test/infra/gateway.py`:
- Around line 90-93: The EPD path in Gateway.start lacks the same minimum-count
validation that regular mode applies to worker URLs. In the Gateway.start logic
that builds the epd-disaggregation args from encode_workers, prefill_workers,
and decode_workers, reject empty lists with a clear ValueError before launching
the subprocess, using the existing validation pattern around worker_urls as a
guide. Make sure the check runs before is_epd_mode leads into the EPD argument
assembly so start(encode_workers=[], prefill_workers=[], decode_workers=[])
fails fast in Python rather than in the CLI.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0c4df285-6b6a-4f55-ac42-f78ed508260d

📥 Commits

Reviewing files that changed from the base of the PR and between cf5966e and a33e33d.

📒 Files selected for processing (8)
  • .github/workflows/e2e-gpu-job.yml
  • .github/workflows/pr-test-rust.yml
  • e2e_test/chat_completions/test_epd_multimodal.py
  • e2e_test/fixtures/setup_backend.py
  • e2e_test/infra/gateway.py
  • e2e_test/infra/model_specs.py
  • grpc_servicer/smg_grpc_servicer/tokenspeed/encoder_servicer.py
  • scripts/ci_download_model.sh

The EPD e2e's encode worker died at startup with
`argument --disaggregation-mode: invalid choice: 'encode'`: the pinned
TokenSpeed SHA (5e145af) predated the EPD encode pipeline (tokenspeed
#548, b5c762d), whose `--disaggregation-mode encode` the SMG encode
servicer already relies on. Bump to current tokenspeed main (69091e1),
which includes it. The embedding transfer backend defaults to mooncake,
and encode mode requires DP=1 (satisfied by the tp=1 encode worker), so
no extra worker args are needed.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>

@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: 707fca4893

ℹ️ 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".

Comment thread .github/workflows/pr-test-rust.yml
The bumped TokenSpeed's kernel failed to compile on CI's CUDA 13 with
`error: '__cudaLaunch' was not declared` (trtllm_allreduce_fusion, sm_90a).
Root cause matched against TokenSpeed's own test/ci_system/install_deps.sh
(which builds cu130 successfully): CUDA 13 relocated the CCCL headers into
`include/cccl`, and it sets `TOKENSPEED_KERNEL_BACKEND=cuda` on the kernel
build — both of which SMG's install script was missing. Add the
`include/cccl` dir to the C/C++ include path (first) and export
`TOKENSPEED_KERNEL_BACKEND=cuda`, matching the upstream recipe.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
Comment thread bindings/python/tests/test_cli.py Outdated

@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: 5a9d91f528

ℹ️ 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".

Comment thread e2e_test/infra/gateway.py
The kernel's `__cudaLaunch was not declared` failure persisted after adding
the CCCL include path. Root cause is a torch/CUDA mismatch: the runner
installs the CUDA 13.0 toolkit from scratch (nvcc 13), but pip pulled the
default PyPI `torch-2.11.0` (a CUDA 12.x build). Its `nvidia-cuda-runtime-cu12`
dependency puts a cu12 `crt/host_runtime.h` on the include path, so nvcc 13's
cudafe++ generates a host stub that then fails to compile against the cu12
headers.

TokenSpeed's own CI avoids this by running on a `cu130-torch-2.11.0` base
image; its install_deps.sh only sets `PIP_EXTRA_INDEX_URL=.../cu130` as a
fallback. Replicate that on the generic runner: point pip/uv at the cu130
wheel index and install `torch==2.11.0+cu130` explicitly before the
`--no-build-isolation` kernel compile.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/ci_install_tokenspeed.sh (1)

121-144: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Pin torch in the constraints file too, for defense-in-depth.

TOKENSPEED_CONSTRAINTS only pins nvidia-cutlass-dsl. The subsequent uv pip install -e tokenspeed-kernel/python/ --no-build-isolation (line 145) and -e "./python" (line 147) resolve their own dependency trees; if either declares a torch spec that isn't satisfied by the already-installed 2.11.0+cu130 (e.g. an exact/narrower pin in requirements/cuda.txt), the resolver could silently swap in a different torch build, reintroducing the exact CUDA-13/cu12-header mismatch this script exists to prevent. Adding torch to the same constraints file used for nvidia-cutlass-dsl would guard against that without extra installs.

Proposed fix
 TOKENSPEED_CONSTRAINTS="$(mktemp)"
 echo "nvidia-cutlass-dsl==4.5.2" > "$TOKENSPEED_CONSTRAINTS"
+echo "torch==2.11.0+cu130" >> "$TOKENSPEED_CONSTRAINTS"
 export UV_CONSTRAINT="$TOKENSPEED_CONSTRAINTS"
 export PIP_CONSTRAINT="$TOKENSPEED_CONSTRAINTS"

Worth checking TokenSpeed's requirements/cuda.txt at the pinned ref to see if it declares its own torch version spec that could conflict.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/ci_install_tokenspeed.sh` around lines 121 - 144, Pin
torch==2.11.0+cu130 in TOKENSPEED_CONSTRAINTS alongside
nvidia-cutlass-dsl==4.5.2, before exporting UV_CONSTRAINT and PIP_CONSTRAINT.
Verify the pinned TokenSpeed requirements do not introduce an incompatible torch
constraint, so the subsequent editable installs cannot replace the CUDA 13 torch
build.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/ci_install_tokenspeed.sh`:
- Around line 110-119: Persist the cu130 package indexes for subsequent CI
steps, not only the current shell. Update the environment setup in
scripts/ci_install_tokenspeed.sh, including the corresponding later block, to
append the resolved PIP_EXTRA_INDEX_URL and UV_EXTRA_INDEX_URL values to
GITHUB_ENV alongside the other CUDA variables; verify downstream installs in
e2e-gpu-job.yml inherit them.

---

Outside diff comments:
In `@scripts/ci_install_tokenspeed.sh`:
- Around line 121-144: Pin torch==2.11.0+cu130 in TOKENSPEED_CONSTRAINTS
alongside nvidia-cutlass-dsl==4.5.2, before exporting UV_CONSTRAINT and
PIP_CONSTRAINT. Verify the pinned TokenSpeed requirements do not introduce an
incompatible torch constraint, so the subsequent editable installs cannot
replace the CUDA 13 torch build.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fbe3ca2a-19f2-4cb3-822f-4e3ef17f3bfe

📥 Commits

Reviewing files that changed from the base of the PR and between 5a9d91f and 23b3a2e.

📒 Files selected for processing (1)
  • scripts/ci_install_tokenspeed.sh

Comment thread scripts/ci_install_tokenspeed.sh
The `__cudaLaunch was not declared` failure survived both the CCCL include
path and the torch->cu130 switch. The real cause is a header-shadowing
conflict, visible in the compiler note:

  site-packages/nvidia/cu13/include/crt/host_runtime.h:130:
    note: macro "__cudaLaunch" defined here

torch's +cu130 wheels ship their own crt/host_runtime.h under
nvidia/cu13/include, and torch's CUDAExtension puts that dir on the nvcc
command line, shadowing nvcc's built-in system headers. The pip cu13 patch
level differs from the apt-installed system nvcc, so the stub the system nvcc
generates calls the 2-arg __cudaLaunch while the pip header only defines the
1-arg macro -> "not declared".

Fix, adopting the proven TRT-LLM lane's pattern:
- install the full cuda-toolkit-13-0 (like ci_install_trtllm.sh) so the system
  headers are a complete, self-consistent match for the system nvcc, and
- set NVCC_PREPEND_FLAGS=-I$CUDA_HOME/include so the system include wins over
  torch's bundled cu13 headers on every nvcc invocation (host stub included),
  persisted to GITHUB_ENV for the worker-side JIT builds pytest triggers.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
slin1237 added 5 commits July 9, 2026 17:20
The `__cudaLaunch was not declared` failure was NOT an include-ordering
problem: the full nvcc command shows `-I/usr/local/cuda/include` already
precedes torch's `-I .../nvidia/cu13/include`, so the previous
NVCC_PREPEND_FLAGS attempt was a no-op (identical error). The compiler note
pinpoints the real cause:

  .venv/.../nvidia/cu13/include/crt/host_runtime.h:130:
    note: macro "__cudaLaunch" defined here   (#define __cudaLaunch(fun), 1-arg)

torch's +cu130 wheels bundle a crt/host_runtime.h at a different CUDA patch
level than the apt system nvcc (cuda-13.0.88). nvcc feeds the bundled dir to
the host-stub compile ahead of the system include via its host-compiler flags
(which command-line -I ordering can't override), so the 13.0.88 nvcc emits a
2-arg __cudaLaunch stub the bundled 1-arg macro can't satisfy.

Fix: after installing the +cu130 torch, replace each bundled
site-packages/nvidia/cu*/include/crt with a symlink to the system
$CUDA_HOME/include/crt, so the launch stubs always resolve the crt headers
that match the nvcc generating them, regardless of include order. Drop the
ineffective NVCC_PREPEND_FLAGS and its misleading comment.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
setup.py's _resolve_include_dirs puts $CUDA_HOME/include ahead of torch's
bundled nvidia/cu13/include, so for the kernel's host-stub compile to still
resolve crt/host_runtime.h to the (mismatched) pip copy, the system header at
$CUDA_HOME/include/crt/host_runtime.h must be absent. CUDA_HOME is
/usr/local/cuda, and on this runner that symlink is stale/partial: its include/
has cuda_runtime.h (so setup.py adds the dir) but no crt/, so crt falls through
to torch's cu13 crt whose 1-arg __cudaLaunch macro can't satisfy the 2-arg stub
the 13.0.88 nvcc emits.

Point CUDA_HOME at the freshly apt-installed, complete /usr/local/cuda-13.0
(ships cuda-crt-13-0) -- exactly what the working TRT-LLM lane does
(ci_install_trtllm.sh). Add a diagnostic that asserts crt/host_runtime.h is
present under CUDA_HOME. Drops the earlier crt-symlink block, which never ran:
the pip crt headers are pulled by the kernel build itself, after that block.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
Diagnostics disproved the include-order theory: with CUDA_HOME=/usr/local/
cuda-13.0 the system crt/host_runtime.h is present AND precedes torch's
nvidia/cu13/include in setup.py's -I list, yet the host-stub compile still
binds the bundled copy (nvcc resolves it via a path -I ordering can't control).
The bundled crt is nvidia-cuda-runtime 13.0.96 -- a newer patch than the apt
system nvcc (13.0.88) -- so the 88 nvcc's 2-arg __cudaLaunch stub can't match
the 96 header's 1-arg macro.

The bundled crt is pulled by the kernel build's own dependency resolution, so
external include tweaks can't win and pre-build alignment misses it. Instead:
run the kernel build once to materialize the deps (tolerate the expected
compile failure), symlink every site-packages/nvidia/cu*/include/crt to the
system $CUDA_HOME/include/crt, then build for real -- deps are satisfied so the
crt isn't re-pulled, and the stub now compiles against the matching 13.0.88
headers. The site-packages dir is resolved via sysconfig (CWD-independent,
since the build runs from the TokenSpeed checkout).

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
The EPD workers (encode/prefill/decode) loaded the model fine but then hung
in mooncake transfer-engine init and never became healthy, timing out the
4-GPU tokenspeed lane. The worker logs show the transfer engine enumerating
all 18 RoCE NICs on the H100 runner and logging "Local segment descriptor not
found" before dead-ending.

The SGLang PD branch already guards against this by passing
--disaggregation-ib-device (from detect_ib_device()); the EPD branch detected
and stored the device but never put it on the command line. Add the flag,
mirroring the PD branch, so mooncake binds a single NIC instead of hanging on
the full enumeration.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
The EPD ib-device fix was a no-op because detect_ib_device() returned None:
it shells out to `ibv_devinfo`, which isn't installed on the tokenspeed GPU
runner (the image ships libibverbs but not ibverbs-utils), so the caller never
added --disaggregation-ib-device and the mooncake transfer engine kept
enumerating all 18 RoCE NICs and hanging.

Read /sys/class/infiniband directly instead (works for InfiniBand and RoCE,
no CLI dependency), scanning devices in numeric order and returning the first
with an ACTIVE port. Keep ibv_devinfo as a fallback and log when nothing is
found so a future None is diagnosable.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
The EPD workers hang right after mooncake's transfer engine starts listening;
the next step is register_memory on a GPU buffer, which needs GPUDirect
(nvidia_peermem/dmabuf). Add a timeout-guarded probe to the tokenspeed install
verify that dumps the runner's RDMA/GPUDirect state and runs the exact mooncake
init + GPU register_memory TokenSpeed does, so the fast 1-GPU tokenspeed lane
tells us definitively whether the H100 CI box supports it. Never hangs or fails
the lane. Temporary — to be removed once the EPD transport path is settled.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
Comment thread scripts/ci_install_tokenspeed.sh

@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: a5e969146d

ℹ️ 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".

Comment thread scripts/ci_install_tokenspeed.sh Outdated
slin1237 added 6 commits July 10, 2026 08:44
…RMEM=false)

EPD workers loaded the model then hung in mooncake's transfer engine and never
became healthy. Root-caused on a GB300 box: mooncake's register_memory fails on
GPU buffers with "Bad address" (EFAULT) because it uses the legacy
nvidia_peermem GPUDirect path (WITH_NVIDIA_PEERMEM defaults to true), and the
NVIDIA Open Kernel driver on the GPU runners has no nvidia_peermem module.
TokenSpeed's register() swallows the failure, so the worker deadlocks -> the
600s health timeout.

mooncake compiles in the dmabuf path too; it's selected at runtime via
WITH_NVIDIA_PEERMEM=false. With that set, GPU-buffer registration succeeds on
the open driver (verified on GB300: peermem -> EFAULT, dmabuf -> rc=0, for both
cudaMalloc and cuMem allocations). Set it in the encode/prefill/decode worker
env so EPD comes up. Host memory and RDMA were always fine; this is purely the
GPUDirect registration path, not the hardware.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
Extend the temporary tokenspeed probe to register a GPU buffer twice on the
H100 runner: once with the default (peermem) path and once with
WITH_NVIDIA_PEERMEM=false (dmabuf). Confirms on the actual CI box that dmabuf
is the working path (matches the GB300 finding). Also stop relying on an
8.8.8.8 route for the local IP (restricted runners); use hostname -I. Temporary.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
Reproduce the TokenSpeed EPD (encode-prefill-decode) multimodal flow on a single
local GPU-RDMA node and drive the EPD e2e against it, building the stack from
local source so the transport can be iterated in minutes instead of 30-min CI
rounds. Captures the non-obvious EPD env: WITH_NVIDIA_PEERMEM=false (mooncake
dmabuf GPUDirect on the NVIDIA open kernel driver), LD_PRELOAD libnuma, and
MC_INTRANODE_NVLINK for the same-node encode->prefill transfer. Parameterized via
env (TORCH_PY, TS_SRC, EPD_MODEL, CUDA_HOME, EPD_TOPOLOGY).

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
--system-site-packages inherited the conda env's torch-2.12 C-extensions
(torchcomms), which broke the moment tokenspeed-kernel pinned torch 2.11
("libtorchcomms.so: undefined symbol"). Build in a clean venv and install
torch==2.11.0+cu130 up front instead; verified it runs on GB300 (sm_103).

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
…wnloads)

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>

@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: a858cd0ed4

ℹ️ 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".

CUDA_HOME="${CUDA_HOME:-/usr/local/cuda}"
if [ ! -x "${CUDA_HOME}/bin/nvcc" ]; then
echo "Installing CUDA toolkit (nvcc not found at ${CUDA_HOME}/bin/nvcc)..."
if [ ! -x "${CUDA_HOME}/bin/nvcc" ] && [ ! -x "/usr/local/cuda-13.0/bin/nvcc" ]; then

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 Install CUDA when required headers are missing

When $CUDA_HOME/bin/nvcc already exists but the toolkit is incomplete (the stale /usr/local/cuda case described immediately below), this guard skips cuda-toolkit-13-0; the later host_runtime.h check only warns, and the TokenSpeed kernel build still proceeds into the missing-header failure the comments describe. Include the required header in the install condition or fail before starting the build.

Useful? React with 👍 / 👎.

slin1237 added 3 commits July 10, 2026 11:35
The EPD prefill worker hangs silently after mooncake's transfer engines come
up and never becomes healthy; its worker log dead-ends with no traceback, so
neither the job log nor the uploaded artifact reveals where it's stuck. Enable
PYTHONFAULTHANDLER on the EPD workers and SIGABRT the process on the health
timeout so CPython dumps every thread's Python stack into the worker log before
it dies. Temporary — revert once the prefill hang is located and fixed.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
The EPD prefill/decode workers loaded the model and stood up mooncake's
transfer engines, then hung silently until the 600s health timeout, while the
encode worker came up fine. Root cause (server.py:_wait_and_warmup): the smg
tokenspeed servicer runs a gRPC warmup that drives stub.Generate to produce a
token; the encode role self-skips it (no LM -- which is exactly why encode was
healthy), but a disaggregated prefill/decode worker can't complete a lone
Generate and blocks forever, so it never reaches SERVING.

Mirror tokenspeed's own EPD serve script (serve_qwen35_122b_nvfp4_epd_1e2p1d.sh,
tokenspeed#549): set TOKENSPEED_SKIP_GRPC_WARMUP=1 and pass --skip-server-warmup
so prefill/decode reach SERVING, plus the runtime knobs that make the actual
same-node transfer work -- MC_INTRANODE_NVLINK/MC_INTRA_NVLINK (NVLink-IPC
intranode; RoCE loopback fails on one host), NO_PROXY for the local mooncake
bootstrap, a 2GB gRPC limit for inline pixels, and the explicit
--disaggregation-transfer-backend mooncake / --disaggregation-layerwise-interval.

The temporary faulthandler stack-dump is kept for now as a safety net; remove it
together with the ci_install_tokenspeed.sh probe once the lane is green.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
With the warmup fix, all EPD workers reach healthy and the request flows, but
prefill/decode then OOM mid-generation (GPU 1/2 at 79/79 GiB) -> the scheduler
raises, the response comes back empty, and all four topologies fail
`assert text is not None`. TokenSpeed's defaults are too aggressive for a 35B
FP8 LM on one card: auto gpu-memory-utilization (~0.9), kvstore-ratio 2.0, and a
131K-token KV pool leave no headroom for generation activations + the
mooncake/EPD buffers.

Size the LM conservatively via the model spec's tokenspeed_args
(--gpu-memory-utilization 0.75, --max-model-len 8192, --max-num-seqs 8,
--kvstore-ratio 0.5) -- ample for the single-image smoke test -- and set
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True on the workers to avoid
fragmentation OOM (as the error message recommends).

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci CI/CD configuration changes grpc gRPC client and router changes model-gateway Model gateway crate changes tests Test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant