Skip to content

Commit 5cf79e1

Browse files
test(case2): real two-process socket + real-network cross-host confirmation (#124)
* test(case2): real two-process socket + tc netem cross-host measurement socket_echo_server.py: length-prefixed TCP echo = the remote-host network boundary. k3_specdecode_gpu_bench.py --socket-echo-addr round-trips the REAL per-block aux payload (verifier->proposer hidden states + draft tokens) through the socket once per block; --netem-sweep applies real tc netem latency on the loopback between runs. Replaces the injected-sleep RTT model with a true two-process socket + real-network measurement to corroborate the WAN-penalty curve. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * test(case2): real two-process socket + real-network confirmation Added socket_echo_server.py (remote-host boundary) + --socket-echo-addr socket round-trip of the REAL per-block aux payload (~156KB/block) in the fused engine. tc netem is blocked in the vast container (no NET_ADMIN), so real latency came from a real inter-host link (reverse SSH tunnel, ~102ms RTT): loopback (~0ms): 51.5 tok/s = 2.02x AR real net (~102ms): 14.1 tok/s = 0.56x AR (net loss; network = 71% wall time) End-to-end real-models + real-network proof that the cross-host token-level draft data plane is WAN-infeasible. Updated ADR 0014 + README. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * docs(adr0014): clarify ~102ms is inter-region+SSH-relay (not gRPC/not a floor); RTT decomposition + optimization levers Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * test(case2): direct-gRPC cross-host RTT probe (raw-bytes echo, no SSH tunnel) Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * test(case2): add --grpc-echo-addr direct-gRPC transport for the per-block round-trip Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * test(case2): direct-gRPC transport result + RTT decomposition gRPC echo probe (grpc_echo_probe.py) + --grpc-echo-addr fused-loop transport. Direct-gRPC over the real ~102ms path: 15.95 tok/s = 0.63x AR (vs raw-socket 0.56x) — modestly better (efficient 156KB serialization) but still a net loss; the ~102ms geographic RTT dominates, not the transport. Loopback gRPC 156KB = 1.1ms, so on a low-RTT link gRPC is free and the 1.8-2.2x win returns. (True non-SSH ingress blocked by vast forwarder; gRPC ran over the same SSH path.) Fixed net_bytes_per_block reporting for the grpc path. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> * docs(adr0014): Appendix A — consolidated test report index, evidence map, and one-line verdict (Case 1 + Case 2) Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com>
1 parent fb472a5 commit 5cf79e1

8 files changed

Lines changed: 1234 additions & 2 deletions

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,10 @@ H200 compute by injecting one proposer↔verifier round-trip per block:
267267
| vs AR | **2.20×** | 1.81× | 1.50× | 1.22× | **0.98×** (break-even) | 0.77× (loss) |
268268

269269
**Break-even ≈100 ms/block**: a cloud↔desk WAN (30–150 ms) straddles/exceeds it,
270-
while a LAN (≤15 ms) keeps the 1.8–2.2× win. So the realizable split is **WAN =
270+
while a LAN (≤15 ms) keeps the 1.8–2.2× win. Confirmed end-to-end with a **real
271+
two-process socket over a real ~102 ms network** (reverse SSH tunnel, real
272+
156 KB/block aux payload): co-located **2.02×** → real-network **0.56× AR** (a
273+
net loss; network was 71 % of wall time). So the realizable split is **WAN =
271274
control + tool plane** (the Mac bridge) and **LAN = co-located data plane**. See
272275
[ADR 0014](docs/adr/0014-agent-connection-capacity-and-cross-host-topology-tests.md)
273276
for the full plan, evidence, and the served-MLX-gemma gap found during testing.

docs/adr/0014-agent-connection-capacity-and-cross-host-topology-tests.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,76 @@ straddles or exceeds break-even, while a LAN/Thunderbolt link (≤15 ms) preserv
183183
the 1.8–2.2× win. This is the architecture's prediction (design doc §4.2),
184184
**now quantified on real compute** — and it is why the data plane must be LAN.
185185

186+
### 4.4 Real two-process socket over a real network (no simulation)
187+
188+
To remove the "injected sleep" caveat, the per-block exchange was run through a
189+
**real TCP socket to a second process** (`scripts/research/socket_echo_server.py`),
190+
serializing the **actual** per-block payload — the verifier→proposer aux hidden
191+
states + draft tokens, **≈156 KB/block** — once per block
192+
(`--socket-echo-addr`). Two transports were measured (H200 NVL,
193+
`results/research/k3_crosshost_{socket_loopback,realnet}_gpu.json`):
194+
195+
| transport | RTT | decode tok/s | vs AR |
196+
| --- | --- | --- | --- |
197+
| loopback socket (same host) | ~0 ms | 51.5 | **2.02×** |
198+
| **real network** (GPU↔cloud-agent, reverse SSH tunnel) | **~102 ms** | **14.1** | **0.56×** |
199+
200+
- **Loopback** matches the co-located number → the socket + serialization of the
201+
156 KB payload are themselves cheap; the killer is purely the round-trip.
202+
- **Real network**: at a genuine ~102 ms RTT the cross-host loop collapses to
203+
**0.56× AR — a net loss, worse than running AR alone**, and *worse* than the
204+
latency-only model (0.98× at 100 ms) because the real transport also pays the
205+
**156 KB/block bandwidth** (network was **71 %** of decode wall time). This is
206+
an end-to-end real-models + real-network confirmation that the token-level
207+
draft data plane is WAN-infeasible.
208+
- Note: `tc netem` artificial latency could **not** be applied inside the vast
209+
container (`RTNETLINK: Operation not permitted` — no `NET_ADMIN` in the
210+
restricted netns), so real latency was obtained from a real inter-host link
211+
(the reverse-tunnel RTT) rather than synthetic netem.
212+
213+
**What the ~102 ms is (and is not).** It is **not** a gRPC RTT and **not** a
214+
floor — it is a raw TCP round-trip through a **reverse SSH tunnel between two
215+
different-region hosts**. Decomposed by payload over the real path:
216+
217+
| payload | median RTT |
218+
| --- | --- |
219+
| 64 B | 102.6 ms |
220+
| 40 KB | 205.2 ms |
221+
| 80 KB | 206.0 ms |
222+
| 156 KB | 206.9 ms |
223+
224+
The 64 B point (~102 ms) is the **pure inter-region latency + SSH-relay
225+
overhead**; the flat 205–207 ms from 40 KB up is **one extra tunnel round-trip**
226+
(TCP windowing / SSH framing), not linear bandwidth.
227+
228+
**Direct-gRPC transport, re-tested.** Swapping the raw socket for a real
229+
**gRPC (HTTP/2)** channel (`grpc_echo_probe.py`, `--grpc-echo-addr`):
230+
231+
| transport / path | 156 KB RTT | fused tok/s | vs AR |
232+
| --- | --- | --- | --- |
233+
| loopback gRPC (same host) | **1.1 ms** |||
234+
| raw socket over the ~102 ms path | 207 ms | 14.1 | 0.56× |
235+
| **gRPC over the ~102 ms path** | 208 ms | **15.95** | **0.63×** |
236+
237+
gRPC is **modestly better** (0.63× vs 0.56×; ~197 vs ~232 ms/block network) —
238+
it serializes the 156 KB more efficiently — but **still a net loss**, because
239+
the **~102 ms geographic RTT dominates, not the transport**. gRPC's real value
240+
shows at **loopback (1.1 ms for 156 KB)** → on a low-RTT link the transport is
241+
free and the engine returns to its 1.8–2.2× win. (A *true* non-SSH ingress to
242+
the GPU could not be established — vast's non-SSH mapped ports accept SYNs but
243+
do not forward data end-to-end, so the gRPC run used the same reverse-SSH path;
244+
this only adds relay overhead, so the real direct-gRPC number would be ≤ these.) So this is a **worst-case
245+
far-WAN + SSH artifact**, not a deployment floor. Optimization room is large and
246+
is exactly the architecture's prescription: (1) **latency** — co-locate the
247+
draft loop on a low-RTT link (same region ~5–20 ms, LAN ~0.5–2 ms, Thunderbolt
248+
sub-ms; at ≤15 ms the engine is back to 1.8–2.2×, cf. §4.3 / loopback 2.02×);
249+
(2) **transport** — a direct **gRPC/QUIC/RDMA** path drops the SSH relay + the
250+
extra round-trip (gRPC would be *lower*, not higher); (3) **payload** — the
251+
156 KB/block fp16 aux can be fp8/int8/top-k compressed 2–4×; (4) **fewer
252+
round-trips** — larger blocks. The invariant is the ratio *per-block RTT :
253+
per-block compute* (break-even ~100 ms/block): the strategy is to keep the loop
254+
on a low-RTT link, not to chase a faster WAN.
255+
186256
## 5. Decision
187257

188258
1. **Case 1 is validated**: the session-bound gRPC runtime admits and serves
@@ -224,3 +294,47 @@ the 1.8–2.2× win. This is the architecture's prediction (design doc §4.2),
224294
- **Hold live cloud→Mac gRPC sessions for Case 1.** Impossible: the Mac has no
225295
inbound path (the reason the bridge exists). The load test runs co-located on
226296
the Mac, dispatched via the bridge.
297+
298+
## Appendix A — Test report index & evidence
299+
300+
Consolidated record of every run behind this ADR (harnesses, how to reproduce,
301+
the committed evidence JSON, and the headline result).
302+
303+
### A.1 Harnesses & how to reproduce
304+
305+
| Test | Harness / preset | Reproduce |
306+
| --- | --- | --- |
307+
| Case 1 — agent connections (light) | `scripts/research/grpc_agent_capacity_loadtest.py`; preset `agent-capacity-loadtest` | `kakeya_mac.py run --preset agent-capacity-loadtest` |
308+
| Case 1 — agent connections (stress) | same; preset `agent-capacity-stress` (`--context-len`, FD raise) | `kakeya_mac.py run --preset agent-capacity-stress` |
309+
| Case 2 — injected-RTT sweep | `scripts/research/k3_specdecode_gpu_bench.py --rtt-sweep` | H200, real models |
310+
| Case 2 — raw socket (real net) | `socket_echo_server.py` + `k3_specdecode_gpu_bench.py --socket-echo-addr` | echo on host B; reverse-SSH path |
311+
| Case 2 — direct gRPC | `grpc_echo_probe.py` + `k3_specdecode_gpu_bench.py --grpc-echo-addr` | gRPC echo on host B |
312+
313+
### A.2 Consolidated results
314+
315+
**Case 1 (Mac mini M4, gRPC `RuntimeService`, cpu Qwen3-0.6B):**
316+
317+
| run | result | evidence |
318+
| --- | --- | --- |
319+
| light sessions | **256/256 agents, 0 errors**; per-session KV 7.80 MB; node bound ≈2.0 GB; RSS flat ~3.85 GB | `results/research/k3_agent_capacity_mac.json` |
320+
| stress (ctx prefill, FD 100k, cap 2048) | FD not the limit; mem = cap×window (cap 2048→11.5 GB, bound 61 GB>RAM); serialization caps heavy-ctx concurrency at **~8** | `results/research/k3_agent_capacity_stress_mac.json` |
321+
322+
**Case 2 (H200 NVL, Gemma-4-26B + DFlash, fused spec-decode vs AR):**
323+
324+
| transport / RTT | tok/s | vs AR | evidence |
325+
| --- | --- | --- | --- |
326+
| co-located (0 network) | 44–52 | **1.85–2.20×** | (all four JSONs) |
327+
| injected-RTT sweep || 2.20× @0**0.98× @100 ms** → 0.77× @150 ms | `k3_crosshost_rtt_gpu.json` |
328+
| loopback gRPC (156 KB) || 1.1 ms round-trip | `k3_crosshost_grpc_gpu.json` |
329+
| raw socket over ~102 ms path | 14.1 | 0.56× | `k3_crosshost_realnet_gpu.json` |
330+
| direct gRPC over ~102 ms path | 15.95 | 0.63× | `k3_crosshost_grpc_gpu.json` |
331+
| RTT decomposition | 64 B → 102.6 ms; 156 KB → 206–208 ms (both raw + gRPC) || `k3_crosshost_socket_loopback_gpu.json` |
332+
333+
### A.3 One-line verdict
334+
335+
Bounded-memory, admission-controlled multi-agent serving is **validated** (Case 1:
336+
256+ connections, ~2.0 GB node KV ceiling, flat RSS). Cross-host token-level
337+
spec-decode is a **co-located/LAN win (1.8–2.2×)** and a **WAN net loss**
338+
(~0.56–0.63× at ~102 ms RTT, transport-independent) — **WAN = control + tool
339+
plane, LAN = data plane.** The lever is RTT (co-location), not the transport;
340+
gRPC only helps once the link is already low-RTT (loopback 1.1 ms).
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
{
2+
"kind": "k3_specdecode_gpu_bench",
3+
"config": {
4+
"verifier_id": "google/gemma-4-26B-A4B-it",
5+
"drafter_id": "z-lab/gemma-4-26B-A4B-it-DFlash",
6+
"f_theta_dir": "results/research/f_theta_v5_s5_sliding",
7+
"haystack_lines": 160,
8+
"n_samples": 2,
9+
"max_new_tokens": 64,
10+
"block_size": 16,
11+
"sink": 4,
12+
"window": 64,
13+
"seed": 0,
14+
"skip_unfused": true,
15+
"rtt_sweep": null,
16+
"socket_echo_addr": null,
17+
"netem_sweep": null,
18+
"netem_dev": "lo",
19+
"grpc_echo_addr": "127.0.0.1:53997",
20+
"output": "results/research/k3_crosshost_grpc_gpu3.json"
21+
},
22+
"env": {
23+
"gpu": "NVIDIA H200 NVL",
24+
"torch": "2.12.0+cu130"
25+
},
26+
"prompt_tokens": {
27+
"min": 3238,
28+
"max": 3238
29+
},
30+
"ar_incremental": {
31+
"decode_tokens_per_s_mean": 25.385,
32+
"recall": 1.0
33+
},
34+
"restored_pertoken": {
35+
"decode_tokens_per_s_mean": 25.508,
36+
"recall": 1.0
37+
},
38+
"restored_specdecode": {
39+
"skipped": true,
40+
"decode_tokens_per_s_mean": null,
41+
"mean_accept_len": 0.0,
42+
"recall": 0.0,
43+
"per_sample": [
44+
{
45+
"decode_tokens_per_s": null,
46+
"mean_accept_len": 0.0,
47+
"time_breakdown_s": {
48+
"aux_clean_forward": 0.0,
49+
"drafter": 0.0,
50+
"incremental_verify": 0.0
51+
},
52+
"tokens": []
53+
},
54+
{
55+
"decode_tokens_per_s": null,
56+
"mean_accept_len": 0.0,
57+
"time_breakdown_s": {
58+
"aux_clean_forward": 0.0,
59+
"drafter": 0.0,
60+
"incremental_verify": 0.0
61+
},
62+
"tokens": []
63+
}
64+
]
65+
},
66+
"restored_specdecode_fused": {
67+
"decode_tokens_per_s_mean": 46.992,
68+
"mean_accept_len": 3.49,
69+
"time_breakdown_s_mean": {
70+
"drafter_cached": 0.168,
71+
"incremental_verify": 1.187,
72+
"ctx_kv_extend": 0.025
73+
},
74+
"recall": 1.0,
75+
"per_sample": [
76+
{
77+
"tokens": [
78+
818,
79+
6789,
80+
3393,
81+
563,
82+
5213,
83+
28487,
84+
1618,
85+
236772,
86+
236832,
87+
236828,
88+
236819,
89+
236771,
90+
84750,
91+
106,
92+
106,
93+
107,
94+
45518,
95+
107,
96+
101,
97+
818,
98+
6789,
99+
3393,
100+
563,
101+
5213,
102+
28487,
103+
1618,
104+
236772,
105+
236832,
106+
236828,
107+
236819,
108+
236771,
109+
84750,
110+
106,
111+
106,
112+
107,
113+
45518,
114+
107,
115+
101,
116+
818,
117+
6789,
118+
3393,
119+
563,
120+
5213,
121+
28487,
122+
1618,
123+
236772,
124+
236832,
125+
236828,
126+
236819,
127+
236771,
128+
84750,
129+
106,
130+
106,
131+
106,
132+
45518,
133+
107,
134+
101,
135+
818,
136+
6789,
137+
3393,
138+
563,
139+
5213,
140+
28487,
141+
1618
142+
],
143+
"decode_s": 1.2198792919516563,
144+
"prefill_s": 0.81,
145+
"decode_tokens_per_s": 52.464,
146+
"time_breakdown_s": {
147+
"drafter_cached": 0.055,
148+
"incremental_verify": 1.14,
149+
"ctx_kv_extend": 0.024,
150+
"network_rtt": 0.0
151+
},
152+
"blocks": 14,
153+
"mean_accept_len": 3.64,
154+
"decode_tokens": 64,
155+
"block_rtt_ms": 0.0,
156+
"net_bytes_per_block": 0
157+
},
158+
{
159+
"tokens": [
160+
818,
161+
6789,
162+
3393,
163+
563,
164+
5213,
165+
236777,
166+
59790,
167+
236772,
168+
236828,
169+
236819,
170+
236825,
171+
236770,
172+
84750,
173+
106,
174+
106,
175+
45518,
176+
107,
177+
101,
178+
818,
179+
6789,
180+
3393,
181+
563,
182+
5213,
183+
236777,
184+
59790,
185+
236772,
186+
236828,
187+
236819,
188+
236825,
189+
236770,
190+
84750,
191+
106,
192+
106,
193+
106,
194+
107,
195+
45518,
196+
107,
197+
101,
198+
818,
199+
6789,
200+
3393,
201+
563,
202+
5213,
203+
236777,
204+
59790,
205+
236772,
206+
236828,
207+
236819,
208+
236825,
209+
236770,
210+
84750,
211+
106,
212+
106,
213+
106,
214+
107,
215+
45518,
216+
107,
217+
101,
218+
818,
219+
6789,
220+
3393,
221+
563,
222+
5213,
223+
236777
224+
],
225+
"decode_s": 1.5413908399641514,
226+
"prefill_s": 0.81,
227+
"decode_tokens_per_s": 41.521,
228+
"time_breakdown_s": {
229+
"drafter_cached": 0.282,
230+
"incremental_verify": 1.234,
231+
"ctx_kv_extend": 0.025,
232+
"network_rtt": 0.0
233+
},
234+
"blocks": 15,
235+
"mean_accept_len": 3.33,
236+
"decode_tokens": 64,
237+
"block_rtt_ms": 0.0,
238+
"net_bytes_per_block": 0
239+
}
240+
],
241+
"speedup_over_ar_x": 1.85
242+
},
243+
"crosshost_grpc_realnet": {
244+
"ar_baseline_tps": 25.385,
245+
"colocated_fused_tps": 46.992,
246+
"transport": "direct gRPC (HTTP/2) per-block round-trip",
247+
"decode_tokens_per_s": 15.952,
248+
"vs_ar_x": 0.628,
249+
"blocks": 14,
250+
"mean_accept_len": 3.64,
251+
"net_bytes_per_block": 0,
252+
"network_s": 2.76,
253+
"decode_s": 4.0120589807629585
254+
}
255+
}

0 commit comments

Comments
 (0)