-
Notifications
You must be signed in to change notification settings - Fork 141
test: multimodal tensor-transport + EPD disaggregation coverage #1898
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
b73f02f
test(multimodal): cover transport payload resolution + EPD encode pla…
slin1237 0856a6c
test(e2e): verify /dev/shm multimodal transport engages
slin1237 cf5966e
test(e2e): EPD multimodal disaggregation e2e (TokenSpeed, 4-GPU)
slin1237 a33e33d
test(e2e): address review — real-EPD assertions + topology/CI fixes
slin1237 707fca4
fix(ci): bump TokenSpeed pin to include the EPD encode pipeline
slin1237 5a9d91f
fix(ci): build the TokenSpeed kernel with CUDA 13's CCCL headers
slin1237 23b3a2e
fix(ci): install the cu130 torch build for the TokenSpeed kernel
slin1237 0d32816
fix(ci): stop torch's cu13 headers from shadowing the system nvcc
slin1237 56ec01f
fix(ci): point torch's bundled CUDA crt headers at the system toolkit
slin1237 17c5d12
fix(ci): use the versioned /usr/local/cuda-13.0 as CUDA_HOME
slin1237 336a580
fix(ci): replace torch's bundled cu13 crt with the system toolkit's
slin1237 0ea6fae
fix(e2e): pin mooncake to one IB device for EPD workers
slin1237 b8fca80
fix(e2e): detect RDMA device via sysfs, not the ibv_devinfo CLI
slin1237 a5e9691
chore(ci): probe H100 runner for mooncake RDMA GPU-registration [temp]
slin1237 5073b10
fix(e2e): force mooncake dmabuf path for EPD workers (WITH_NVIDIA_PEE…
slin1237 183499d
chore(ci): probe peermem-vs-dmabuf mooncake GPU registration [temp]
slin1237 22f1bd5
chore(scripts): add local EPD bring-up script (run_epd_local.sh)
slin1237 0465414
fix(scripts): use a clean venv + torch 2.11+cu130 for local EPD
slin1237 f97733e
fix(scripts): seed pip in the local EPD venv (kernel setup.py needs it)
slin1237 a858cd0
fix(scripts): pip socket timeout+retries for local EPD (avoid hung do…
slin1237 80d9d90
test(e2e): dump EPD worker stack on health timeout [temp diag]
slin1237 84f8bb0
fix(e2e): skip warmup + NVLink-IPC transport for EPD workers
slin1237 5bda7ae
fix(e2e): size the EPD model to fit one 80GB H100 (avoid generation OOM)
slin1237 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| """EPD (Encode-Prefill-Decode) multimodal Chat Completions E2E Tests. | ||
|
|
||
| Exercises TokenSpeed's EPD disaggregation on a vision-language model: the | ||
| encode worker runs the vision tower, prefill/decode run the LM, and the gateway | ||
| stitches encode -> prefill -> decode. | ||
|
|
||
| Like the PD KV-transfer tests (``test_pd_mooncake``/``test_pd_nixl``), these do | ||
| NOT stop at "a plausible answer came back" — a single-worker fallback would pass | ||
| that. They assert a worker-side signal that the disaggregation actually happened: | ||
| the encode worker's own per-request accept log. | ||
|
|
||
| EPD is TokenSpeed-only. On a small MoE VLM (Qwen3.6-35B-A3B, 3B active) at tp=1 | ||
| per worker, every topology fits the 4-GPU runner: 1e1p1d=3 GPUs and | ||
| 1e2p1d/2e1p1d/1e1p2d=4 GPUs (EPD needs >=3 cards since encode/prefill/decode are | ||
| separate workers). | ||
|
|
||
| Usage: | ||
| pytest e2e_test/chat_completions/test_epd_multimodal.py -v | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import base64 | ||
| import logging | ||
| import os | ||
| import tempfile | ||
| from pathlib import Path | ||
|
|
||
| import pytest | ||
| from infra.pd_logs import assert_worker_logs_captured, wait_for_marker, worker_log_dir | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| # Local test image (checked into repo) — a black labrador puppy. | ||
| FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" / "images" | ||
| DOG_IMAGE_PATH = FIXTURES_DIR / "dog.jpg" | ||
|
|
||
| # Router + worker logs land here (per-pid) via the gateway marker below. Worker | ||
| # logs actually go to E2E_LOG_DIR in CI; ``worker_log_dir`` resolves both. | ||
| _LOG_DIR = Path(tempfile.gettempdir()) / f"smg-e2e-epd-{os.getpid()}" | ||
| # Emitted once per Encode RPC by the TokenSpeed encode servicer | ||
| # (grpc_servicer/.../tokenspeed/encoder_servicer.py). Its presence proves the | ||
| # image reached a dedicated encode worker — the defining EPD step. | ||
| ENCODE_ACCEPTED_MARKER = "EPD encode: accepted" | ||
|
|
||
|
|
||
| def _image_to_base64_url(path: Path) -> str: | ||
| """Convert a local image file to a base64 data URL.""" | ||
| data = base64.b64encode(path.read_bytes()).decode("utf-8") | ||
| return f"data:image/jpeg;base64,{data}" | ||
|
|
||
|
|
||
| def _make_image_content(image_source: str) -> dict: | ||
| """Create an image_url content part from a URL or data URL string. | ||
|
|
||
| Local file paths must be pre-converted via ``_image_to_base64_url``. | ||
| """ | ||
| return {"type": "image_url", "image_url": {"url": image_source}} | ||
|
|
||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| # The four EPD topologies to cover. Every worker runs at tp=1 (a 3B-active MoE | ||
| # that fits one card), so 1e1p1d uses 3 GPUs and the rest use 4 — all fit the | ||
| # 4-GPU runner. The (encode, prefill, decode) counts ride in the param tuple, not | ||
| # a marker: setup_backend is class-scoped, so per-param marks aren't visible there. | ||
| _EPD_TOPOLOGIES = [ | ||
| pytest.param(("epd_grpc", (1, 1, 1)), id="1e1p1d"), | ||
| pytest.param(("epd_grpc", (1, 2, 1)), id="1e2p1d"), | ||
| pytest.param(("epd_grpc", (2, 1, 1)), id="2e1p1d"), | ||
| pytest.param(("epd_grpc", (1, 1, 2)), id="1e1p2d"), | ||
| ] | ||
|
|
||
|
|
||
| @pytest.mark.engine("tokenspeed") | ||
| @pytest.mark.gpu(4) | ||
| @pytest.mark.e2e | ||
| @pytest.mark.model("Qwen/Qwen3.6-35B-A3B-FP8") | ||
| @pytest.mark.gateway(log_level="debug", log_dir=str(_LOG_DIR)) | ||
| @pytest.mark.parametrize("setup_backend", _EPD_TOPOLOGIES, indirect=True) | ||
| class TestEPDMultimodal: | ||
| """Verify the image really flows encode -> prefill -> decode. | ||
|
|
||
| A naive content check can't distinguish a real 3-worker EPD pipeline from a | ||
| single-worker fallback; the encode worker's own log can — so that's what this | ||
| asserts, mirroring how the PD tests assert the KV transfer from logs. | ||
| """ | ||
|
|
||
| def test_single_image_base64(self, model, setup_backend): | ||
| """One dog image through encode -> prefill -> decode, with the encode | ||
| worker's participation verified from its logs.""" | ||
| _, _, client, *_ = setup_backend | ||
|
|
||
| response = client.chat.completions.create( | ||
| model=model, | ||
| messages=[ | ||
| { | ||
| "role": "user", | ||
| "content": [ | ||
| {"type": "text", "text": "What animal is in this image?"}, | ||
| _make_image_content(_image_to_base64_url(DOG_IMAGE_PATH)), | ||
| ], | ||
| } | ||
| ], | ||
| temperature=0, | ||
| max_tokens=100, | ||
| ) | ||
|
|
||
| # (1) The model saw the image and described it correctly. | ||
| text = response.choices[0].message.content | ||
| assert text is not None and len(text) > 0 | ||
| assert any(k in text.lower() for k in ["dog", "puppy", "labrador"]), ( | ||
| f"Expected dog-related content, got: {text}" | ||
| ) | ||
| # (2) The image was tokenized INTO the prompt: the bare text question is | ||
| # ~10 tokens, so a large prompt confirms the vision tokens were spliced in | ||
| # (encode -> prefill actually delivered the image), not dropped. | ||
| assert response.usage.prompt_tokens > 50, ( | ||
| f"prompt_tokens={response.usage.prompt_tokens} is too low; " | ||
| "the image tokens were likely not delivered to prefill" | ||
| ) | ||
| assert response.usage.completion_tokens > 0 | ||
|
|
||
| # (3) REAL EPD: the encode worker itself logged accepting the dispatch, so | ||
| # the vision stage ran on a separate encode worker rather than degrading to | ||
| # a single-worker path. This is the EPD analog of the PD KV-transfer check. | ||
| worker_dir = worker_log_dir(_LOG_DIR) | ||
| worker_logs = wait_for_marker(worker_dir, "worker-*.log", ENCODE_ACCEPTED_MARKER) | ||
|
slin1237 marked this conversation as resolved.
|
||
| assert_worker_logs_captured(worker_logs, "EPD encode dispatch") | ||
| assert ENCODE_ACCEPTED_MARKER in worker_logs, ( | ||
| "encode worker never logged accepting the request — the image did not " | ||
| f"flow through the EPD encode stage; checked {worker_dir}/worker-*.log" | ||
| ) | ||
|
slin1237 marked this conversation as resolved.
|
||
| logger.info("EPD single image (encode worker engaged): %s", text) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.