Skip to content

Commit b82d64b

Browse files
committed
docs(colocate): documentation
Signed-off-by: Xing Han <h13008009668@gmail.com>
1 parent e74745b commit b82d64b

12 files changed

Lines changed: 5443 additions & 1 deletion

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,6 @@ wandb/log.txt
8989

9090
.claude/
9191
wandb/
92+
93+
# Colocate knowledge docs (keep local only, not for PR)
94+
docs/colocate/knowledge*.md

docs/colocate/cheap_host_test_plan.md

Lines changed: 403 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
# GPU-rental testing runbook (for agents)
2+
3+
> **Audience: an agent running colocate GPU tests on a rental platform
4+
> without a human in the loop.** It tells you how to provision, run,
5+
> monitor, and — critically — tear down a rented GPU pod safely and
6+
> cheaply. Everything here was learned the hard way across the RunPod
7+
> sessions in [`implementation_log.md`](implementation_log.md).
8+
>
9+
> Companion docs: [`cheap_host_test_plan.md`](cheap_host_test_plan.md)
10+
> (cost-tier matrix, test plan) and [`sglang_patch.md`](sglang_patch.md)
11+
> (the sglang patch the tests exercise).
12+
13+
## When you need this
14+
15+
The colocate tests (`tests/colocate/test_*`, phases 4/6/7) need **NVIDIA
16+
MPS**, which needs a container started with `--ipc=host`. Use this
17+
runbook whenever a task asks you to GPU-validate colocate.
18+
19+
**Modal does not work for colocate.** Modal sandboxes run under gVisor,
20+
whose nvproxy does not implement MPS multiplexing — the MPS-required
21+
tests `pytest.skip` there, they do not run. Use a real `--ipc=host`
22+
host: **RunPod** (default here), Vast.ai, Lambda, or bare metal.
23+
24+
## Hard rules — follow these every time
25+
26+
You are spending real money and sharing an account with other agents.
27+
28+
1. **Check for other pods before you provision.**
29+
`runpodctl pod list -o json`. If a pod you did **not** create is
30+
running, never `stop`/`delete` it. (The deprecated `runpodctl get
31+
pod` can print an *empty* list while pods exist — always use
32+
`pod list -o json`.)
33+
2. **Always pass `--terminate-after`** (≈3 h out) when creating a pod.
34+
It is a backstop: if you lose track, the pod self-destructs instead
35+
of billing forever.
36+
3. **Always tear the pod down** as soon as the run finishes — pass or
37+
fail. Then verify: `runpodctl pod get <id>` must say `pod not found`.
38+
4. **Watch the balance.** `runpodctl user`. A 4×H100 is ~$13/hr. Do not
39+
start a run that would drain the balance toward $0 — that stops
40+
*every* pod on the account, including other agents'.
41+
5. **One run, then capture and tear down.** Do not open-endedly iterate
42+
on a billing pod. If a real (non-environment) failure needs code
43+
changes, tear down first, fix locally, re-provision.
44+
6. **Surface, don't silently proceed,** if you find another agent's pod
45+
that your run would starve, or if the balance is too low for one run.
46+
47+
## Prerequisites (already set up on this machine)
48+
49+
- `runpodctl` installed and authenticated — API key in
50+
`~/.runpod/config.toml`, SSH key at `~/.runpod/ssh/runpodctl-ssh-key`
51+
(registered on the account). Check: `runpodctl user` prints a balance.
52+
- An **`HF_TOKEN`** is required for the Qwen3-8B tests (unauthenticated
53+
HF Hub requests get rate-limited — see failure modes). The tiny
54+
Qwen3-0.6B tests do not need it. Ask the user for the token if you do
55+
not have one; never commit it anywhere.
56+
57+
## Workflow (RunPod — the ready path)
58+
59+
RunPod is the platform set up on this machine and used for every GPU
60+
run to date. Vast.ai is a working alternative — see the section after
61+
this one.
62+
63+
### 1 — Provision
64+
65+
```bash
66+
runpodctl pod create --name colocate-<purpose> \
67+
--gpu-id "NVIDIA H100 80GB HBM3" --gpu-count <N> \
68+
--template-id runpod-torch-v240 \
69+
--container-disk-in-gb 200 --ports "22/tcp" \
70+
--terminate-after "$(date -u -v+3H +%Y-%m-%dT%H:%M:%SZ)" -o json
71+
```
72+
73+
- GPU: `"NVIDIA H100 80GB HBM3"` (H100 SXM). `runpodctl gpu list` for
74+
others. Only **sm90+** (H100 / H200 / B200) — the bundled `sgl_kernel`
75+
wheel has no Ampere/Ada kernels.
76+
- Template `runpod-torch-v240` = `runpod/pytorch:2.4.0-py3.11-cuda12.4.1-devel-ubuntu22.04`
77+
— the validated image. RunPod "Pods" get `--ipc=host` by default.
78+
- `--gpu-count`: see the sizing table below.
79+
- The create call returns the pod `id` — keep it.
80+
81+
### 2 — Wait for SSH (it is slow: 1–8+ min)
82+
83+
The `.ssh.ip` / `.ssh.port` fields appear in `runpodctl pod get <id>
84+
-o json` **before** SSH actually accepts connections. Poll until a real
85+
connection succeeds:
86+
87+
```bash
88+
ssh -i ~/.runpod/ssh/runpodctl-ssh-key -o StrictHostKeyChecking=no \
89+
-o UserKnownHostsFile=/dev/null -o ConnectTimeout=15 \
90+
-p <port> root@<ip> 'echo ok'
91+
```
92+
93+
> **zsh gotcha:** do not put ssh options in a shell variable — zsh does
94+
> not word-split unquoted variables, so `ssh $OPTS ...` passes them as
95+
> one bad argument. Inline every option.
96+
97+
### 3 — Deploy
98+
99+
```bash
100+
ssh ... 'cd /root && git clone --depth=1 -b feature/colocate-training-inference \
101+
https://github.com/zhubohao911/TorchSpec.git'
102+
```
103+
104+
If the code/patch you want to test is **committed and pushed**, the
105+
clone already has it. If it is only local (uncommitted), `scp` the
106+
files onto the pod after cloning.
107+
108+
### 4 — Run (detached, with an exit-code file)
109+
110+
Write a launcher on the pod and run it with `nohup … & disown` so it
111+
survives the SSH session closing. Capture the exit code to a file you
112+
can poll:
113+
114+
```bash
115+
# /root/launcher.sh on the pod:
116+
cd /root/TorchSpec
117+
export HF_TOKEN=<token> # for Qwen3-8B tests
118+
export SGLANG_PATCH_VERSION=v0.5.10.post1
119+
export SGLANG_COMMIT=94f03a39dbd39edfc2b118b5357bbbadaaa9ad28
120+
export CUDA_VISIBLE_DEVICES=0,1,2,3 # see note below
121+
bash scripts/colocate/run_smoke_host.sh [--full | --tests=a.py,b.py]
122+
echo $? > /root/run.rc
123+
```
124+
125+
Launch: `nohup bash /root/launcher.sh > /root/run.log 2>&1 & disown`.
126+
127+
- `run_smoke_host.sh` defaults to `SGLANG_PATCH_VERSION=v0.5.10.post1`;
128+
it clones sglang, applies the patches, builds, and runs pytest.
129+
- `--full` runs the whole matrix; `--tests=` runs specific files (use
130+
this to skip already-passed tests on a re-run).
131+
- **`CUDA_VISIBLE_DEVICES` note:** `run_smoke_host.sh` only auto-sets
132+
all 4 GPUs for `--full`. With `--tests=`, pre-export
133+
`CUDA_VISIBLE_DEVICES=0,1,2,3` yourself or the multi-GPU tests see
134+
one GPU and skip.
135+
136+
### 5 — Monitor
137+
138+
Poll the **remote** files, not a local background job:
139+
140+
```bash
141+
ssh ... 'cat /root/run.rc 2>/dev/null || echo RUNNING; tail -8 /root/run.log'
142+
```
143+
144+
`run.rc` existing = run finished (`0` = all passed). The colocate
145+
failure signature is a **hang on the first P2P recv** — if the log
146+
stops advancing for many minutes mid-step, that is the diagnostic.
147+
148+
### 6 — Tear down (every time)
149+
150+
```bash
151+
scp ... root@<ip>:/root/TorchSpec/colocate-smoke-report.txt /tmp/ # keep the report
152+
runpodctl pod stop <id> && runpodctl pod delete <id>
153+
runpodctl pod get <id> # must say: pod not found
154+
runpodctl user # confirm currentSpendPerHr dropped
155+
```
156+
157+
## Vast.ai (alternative platform)
158+
159+
Vast.ai is a documented alternative — it ran the 4×H100 `--full` suite
160+
in sessions #4/#5 ([`implementation_log.md`](implementation_log.md)),
161+
and is often cheaper than RunPod. The `vastai` CLI (v1.0.x) is
162+
installed, **but not authenticated on this machine.** Before an agent
163+
can use Vast autonomously, the user must run it once:
164+
165+
```bash
166+
vastai set api-key <KEY> # key from the vast.ai console
167+
```
168+
169+
All the same constraints and **hard rules** above apply (check other
170+
instances, watch balance, tear down every time). Vast On-Demand
171+
instances default to `--ipc=host`; choose a "Direct" net-type host with
172+
a good reliability score and a CUDA 12.x + Python 3.11 PyTorch image.
173+
sm90+ only, same as RunPod.
174+
175+
The workflow mirrors the RunPod one — only the CLI differs:
176+
177+
| Step | RunPod | Vast.ai |
178+
|---|---|---|
179+
| find capacity | `runpodctl gpu list` | `vastai search offers 'gpu_name=H100_SXM num_gpus=4 reliability>0.98'` |
180+
| provision | `runpodctl pod create …` | `vastai create instance <offer-id> --image <pytorch-cu124-img> --disk 200 --ssh --direct` |
181+
| list | `runpodctl pod list -o json` | `vastai show instances` |
182+
| SSH endpoint | `.ssh.ip` / `.ssh.port` | `vastai ssh-url <id>` |
183+
| **tear down** | `pod stop` + `pod delete` | **`vastai destroy instance <id>`** |
184+
185+
Run `vastai search offers --help` / `vastai create instance --help` for
186+
exact field syntax — query fields and image flags change between CLI
187+
versions.
188+
189+
> **Two Vast-specific cautions:**
190+
> - **`stop instance` is not enough** — a stopped Vast instance still
191+
> **bills for storage**. Only `destroy instance` (irreversible —
192+
> deletes the disk) fully stops billing. Always `destroy` when done.
193+
> - **No `--terminate-after` backstop.** RunPod self-destructs a lost
194+
> pod; Vast does not. The "always tear down" rule is therefore
195+
> load-bearing on Vast — never leave an instance unattended.
196+
197+
## GPU sizing
198+
199+
| Test | GPUs | Model | ~Time (after setup) |
200+
|---|---|---|---|
201+
| `test_colocate_tiny.py` | 1 | Qwen3-0.6B | ~4 min |
202+
| `test_colocate_tp2.py` (`engine_tp_size=2`) | 2 | Qwen3-0.6B | ~2 min |
203+
| `run_smoke_host.sh --full` (13 tests) | 4 | Qwen3-0.6B + Qwen3-8B | ~22 min |
204+
205+
Setup (pip install + sglang build) adds ~5–12 min on top, once per pod.
206+
207+
## Known failure modes — NOT your patch's bug
208+
209+
| Symptom | Cause | Action |
210+
|---|---|---|
211+
| `libnuma.so.1: cannot open shared object file` | RunPod image lacks it | `run_smoke_host.sh` already apt-installs it; if running sglang by hand, `apt-get install -y libnuma1` |
212+
| HF Hub `429 Too Many Requests` on Qwen3-8B | unauthenticated HF requests rate-limited | set `HF_TOKEN` |
213+
| pod returns `404 pod not found` / SSH dies mid-run | RunPod infra flakiness (some datacenters worse) | re-provision once; if it repeats, report |
214+
| SSH never comes up after ~10 min | slow/bad pod | delete it, re-provision |
215+
| multi-GPU test SKIPs (sees 1 GPU) | `--tests=` didn't set `CUDA_VISIBLE_DEVICES` | pre-export `CUDA_VISIBLE_DEVICES=0,1,2,3` |
216+
| `Unknown RoPE scaling type default` | old TorchSpec checkout (pre-`be399a0`) | clone current `feature/colocate-training-inference` |
217+
218+
## Cost reference
219+
220+
| Pod | Rate | One run (incl. setup) |
221+
|---|---|---|
222+
| 1×H100 SXM | ~$3.3/hr | tiny smoke ≈ $1–2 |
223+
| 2×H100 SXM | ~$6.6/hr | tp2 ≈ $3–4 |
224+
| 4×H100 SXM | ~$13/hr | `--full` ≈ $8–12 |
225+
226+
Rates above are RunPod. Vast.ai spot is usually cheaper (~$2/hr for
227+
1×H100, ~$10–11/hr for 4×H100) but availability and host reliability
228+
vary more.
229+
230+
Keep the pod alive only for the run. Idle time is pure waste — tear
231+
down immediately on completion.

docs/colocate/handoff_followups.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Colocate (PR #92 / issue #81) — leftover follow-ups (handoff)
2+
3+
> Handoff summary as of 2026-05-21. Self-contained — an agent picking this
4+
> up should not need prior conversation context.
5+
6+
## Current state (grounding)
7+
8+
- **Branch:** `feature/colocate-training-inference`;
9+
**PR #92** (still `[WIP]` DRAFT) on `github.com/lightseekorg/TorchSpec`.
10+
Repo fork remote: `zhubohao911/TorchSpec`. The round-10
11+
transport-optimization work is merged in (`8905c55`); the PR
12+
description was rewritten concise — full detail preserved in
13+
`docs/colocate/pr92_detail.md`.
14+
- **Transport:** CUDA IPC zero-copy is the **default**;
15+
`TORCHSPEC_COLOCATE_IPC=0` opts back to gloo CPU-staging. Three pieces:
16+
`e166c21` (non-destructive IPC capability probe — the old
17+
`reduce_tensor` probe wedged CUDA under MPS), `e62c941`
18+
(factory/train_group actively clear `expandable_segments` for IPC
19+
actors), and **round 10** (transport optimization investigated — no
20+
C++/CUDA/Triton kernel needed; `ipc-pipe` ack pipelining is a
21+
low-priority protocol-level 3.9× win, now wired into `cuda_ipc.py`
22+
behind the opt-in `TORCHSPEC_COLOCATE_IPC_PIPELINE` flag, GPU-validated
23+
2026-05-21 — see `implementation_log.md` round 11).
24+
- **Validated:** `run_smoke_host.sh --full` matrix is **green on 4×H100
25+
under IPC default** — 13 colocate tests pass (single-node). A
26+
3000-step 4-GPU multi-engine soak (round 10) ran clean. sglang patch:
27+
`v0.5.10.post1` is the default (`v0.5.8.post1` still selectable via
28+
`SGLANG_PATCH_VERSION`).
29+
- **Docs of record:** `docs/colocate/implementation_log.md` (rounds
30+
1–11), `docs/colocate/transport_benchmark.md`,
31+
`docs/colocate/transport_optimization.md` (transport
32+
kernel-vs-protocol investigation + MPS-validated A/B),
33+
`docs/colocate/pr92_detail.md` (full PR narrative).
34+
- **GPU access:** `runpodctl` is configured; SSH key
35+
`~/.runpod/ssh/runpodctl-ssh-key`; recipe = clone the branch +
36+
`bash scripts/colocate/run_smoke_host.sh --full`.
37+
38+
## Leftover items
39+
40+
| # | Item | Status | What "done" needs |
41+
|---|---|---|---|
42+
| 1 | **Multi-node 2-node run** | code-complete, untested | Run colocate on 2 nodes × 8 GPU. Code: `ensure_mps_on_all_nodes` (`torchspec/colocate/mps.py`), config `configs/colocate_qwen3_8b_2node.yaml`. Needs a 2-node rented cluster with cross-node networking. |
43+
| 2 | **Large `engine_tp_size` (8-GPU TP per engine)** | validated only at `engine_tp_size=2` | Issue #81 scale-out wants 1 engine × 8-GPU TP. Rank math (`engine_global_rank`, `build_engine_tp_ranks`) + data plane (`colocate_loop.py` dispatch, `build_hidden_states_writer(tp_rank)`, `_send_hidden_states_to_nccl` in `colocate.patch`) handle any TP size but are only GPU-tested at tp=2 (`test_colocate_tp2.py`) + 2-engine fan-out (`test_colocate_multi_engine.py`). Needs an 8-GPU config + run. |
44+
| 3 | **`pp_size > 1`** | open, **out-of-scope by agreement** | Pipeline parallelism — blocked by an explicit guard in `colocate.patch`. Listed for completeness; not planned. |
45+
46+
Items **#1 and #2 are the only remaining issue-#81 "Scale-out" work**
47+
both need different hardware (2 nodes / 8 GPUs), not code. The
48+
`--stability`, convergence-vs-Mooncake, Qwen3-8B grad-parity, and
49+
`ipc-pipe`-productionization follow-ups were **GPU-validated 2026-05-21**
50+
on a 4×H100 pod — see `implementation_log.md` round 11 for the results.
51+
52+
## PR #92 description
53+
54+
Kept concise — the full phase / round / bug detail lives in
55+
`docs/colocate/pr92_detail.md` and the PR body links there. The body's
56+
"Open follow-ups" line matches the leftover-items table above: 2-node
57+
(#1), 8-GPU-TP (#2), and out-of-scope `pp_size>1` (#3).
58+
59+
## Environment gotchas for the GPU work
60+
61+
- HF-Hub **429 rate-limits** unauthenticated Qwen3-8B fetches mid-`--full`;
62+
set `HF_TOKEN`, or pre-cache models + `HF_HUB_OFFLINE=1`.
63+
- RunPod **community-cloud H100s are usually unavailable** — secure cloud
64+
(~$3.29/GPU/hr) works.
65+
- This container type **blocks `py-spy`/ptrace**; for hung-process
66+
diagnosis use `faulthandler.dump_traceback_later` via a
67+
`sitecustomize.py`, not a SIGUSR1 handler.

0 commit comments

Comments
 (0)