Skip to content
Blog

Part 6 of 15 · One node, eight H200s

Part 6: Parallelism on two islands

Compare TP8, independent replicas and expert parallelism on the actual topology, then investigate the anomaly and the fixes that did not help.

IndieRouter Team · · 17 min read

After making the giant models fit, we returned to models that did not need all eight GPUs for one copy. Would the second quad make one request faster, or would it be more useful serving another request? We compared cross-socket tensor parallelism, independent replicas and MoE expert parallelism. The answer depended on both the model and the user count, and one apparent hardware limit turned out to be our proxy.

Qwen3.8-27B · FP8 weights · FP8 KV Qwen3.6-35B-A3B · FP8 weights · FP8 KV vLLM 0.31.0 · 8× H200 NVL

The question

We first ran a common 256K-configured grid on three eight-GPU deployments, using vLLM 0.31.0, FP8 weights and FP8 KV. Two layouts used dense Qwen3.8-27B; the DEP8 layout used MoE Qwen3.6-35B-A3B. That first comparison asks what those complete deployments deliver. It cannot attribute every difference to parallelism because the model changes too. The later same-model tables are the stronger evidence for choosing a layout.

  • 8 replicas (TP1 × 8): Qwen3.8-27B, one independent vLLM server per GPU, each pinned to its own socket's CPUs, with a small round-robin proxy in front.
  • TP8: Qwen3.8-27B as one engine with every layer split across all eight GPUs (--tensor-parallel-size 8), with NCCL_P2P_LEVEL=SYS so NCCL uses direct GPU-to-GPU traffic between the sockets [S24].
  • DEP8: Qwen3.6-35B-A3B, a mixture-of-experts model, with --data-parallel-size 8 --enable-expert-parallel. Attention runs as eight data-parallel copies, and the experts are split across all eight GPUs [S05] [S04].

The grid: 1,024-token prompts with 1,024-token answers at 1, 64 and 256 users at once, 8,192-token prompts at 64 users, and for TP8 an extra 16-user point. DEP8 runs a different model (a 35B MoE with about 3B active parameters, against a 27B dense model), so TP8 vs replicas is the like-for-like comparison, and DEP8 shows what the MoE layout does on the same node.

The node: two islands

This matters more than anything else here. The eight GPUs are two groups of four. Inside a group, every pair has six bonded NVLinks (NV6). Between the groups there is no NVLink: traffic crosses PCIe and the link between the two CPU sockets (SYS) (Part 1). NVIDIA's guidance for bridged H200 NVL cards is to keep each NVLink group within one CPU domain [S01], and this server does that, so any layout that spans all eight GPUs has to cross sockets.

Drawn from results/preflight/topo_matrix.txt (nvidia-smi topo -m) on this node.
Drawn from results/preflight/topo_matrix.txt (nvidia-smi topo -m) on this node. Open full-size chart

Cross-island GPU-to-GPU traffic didn't even work properly at first. With the Intel IOMMU in translated mode, direct writes between the sockets failed, and NCCL quietly fell back to host shared memory. We switched to IOMMU passthrough (intel_iommu=on iommu=pt) and rebooted, following NCCL's troubleshooting guidance. After that, cross-socket copies ran at 38.5 GB/s and an 8-GPU all_reduce with NCCL_P2P_LEVEL=SYS reached 83.1 GB/s of bus bandwidth, against 337 GB/s inside one quad (our measurements, Part 1 has the details and logs). The TP8 run below depends on that fix.

nccl-tests bus bandwidth at 8 GB before and after the IOMMU fix (our measurement).
nccl-tests bus bandwidth at 8 GB before and after the IOMMU fix (our measurement). Open full-size chart

Results: users vs throughput and speed

Left: total output tokens/s for the whole node. Right: time per output token for each user (dot = median, dash = p99). Runs: qwen38_27b__r_par_r1x8__256k__vllm__1010-162104, qwen38_27b__r_par_tp8__256k__vllm__1010-153414, qwen36_35b__r_par_dep8__256k__vllm__1010-163633.
Left: total output tokens/s for the whole node. Right: time per output token for each user (dot = median, dash = p99). Runs: qwen38_27b__r_par_r1x8__256k__vllm__1010-162104, qwen38_27b__r_par_tp8__256k__vllm__1010-153414, qwen36_35b__r_par_dep8__256k__vllm__1010-163633. Open full-size chart
≥ 6,388tok/s, 8 replicas, 256 users (proxy-capped)
2,565tok/s, TP8, 256 users
7,582tok/s, DEP8 (Qwen3.6), 256 users
9.9 msfastest single user (DEP8)

All numbers are our measurement.

  • Eight copies beat one engine split eight ways, everywhere. At 64 users the 8 replicas produced 3,912 tokens/s at 13.3 ms per token, and TP8 634 at 25.2 ms. At 256 users it was at least 6,388 against 2,565 (the replica point is proxy-limited, see below).
  • TP8 didn't even help a single user. One request alone took 13.2 ms per token on TP8 and 11.5 ms on one GPU of the replica layout. Every layer of a TP8 model ends in an all_reduce across all eight GPUs, so every token waits on the cross-socket link, which carries about a quarter of the in-quad bandwidth by our nccl-tests numbers. We think that's why, but we didn't profile it.
  • TP8 decode was spiky. At 64 users its median was 25.2 ms per token, but the p99 was 273 ms (the replicas' p99 was 13.9 ms).
  • DEP8 was the fastest single user and the biggest total, at 9.9 ms per token alone and 7,582 tokens/s at 256 users (31.3 ms per token each). That's a smaller-active-parameter model, so it's not a verdict on DEP vs replicas. The vLLM docs describe this layout for MoE models: attention replicated per data-parallel rank, experts sharded across all ranks [S05].
  • The replicas' 256-user point is limited by our own proxy, so it's a lower bound. Median first token was 117 ms, but the p90 was 131 s, and the engines' counters, summed over all eight, never showed more than 102 requests running while 256 were in flight. Our proxy opens its upstream connections through aiohttp's default connector, which allows 100 simultaneous connections aiohttp docs: TCPConnector, limit defaults to 100. So roughly 150 requests waited in the proxy, not in vLLM. The 64-user points are under that limit and unaffected. A fair 256-user replica number needs a rerun with that limit lifted.

Longer prompts: 8K in, 1K out, 64 users

8,192-token prompts, 1,024-token answers, 64 users at once.
8,192 input tokens and 1,024 output tokens, with 64 users. The replica and DEP8 arms use different models. TP8 is omitted because a 7.3% engine prefix-cache hit rate invalidated its intended cold-prompt workload. Open full-size chart

With 8K-token prompts the replicas produced 3,001 tokens/s (17.5 ms per token) and DEP8 2,445 (23.6 ms). The TP8 point is left out: its engine counters show a 7.3% prefix-cache hit rate in a grid meant to start cold, so it fails our cache rule (cold_cache_hit in final.csv).

The 16-user anomaly

We added a 16-user point to the TP8 run because 8-GPU runs elsewhere in the campaign had shown 200-290 ms per token at batch 16. It reproduced: 182.5 ms per token and 83.5 tokens/s for the whole node. Sixteen users together got barely more than one user alone (75.0 tokens/s), and far less than 64 users (634 tokens/s). It wasn't a few bad steps. The median gap between tokens was 183.7 ms and the p99 193.8 ms, so every step was slow. No errors in the server log, all 48/48 requests completed, and the median first token was 532 ms (our measurement).

WARNING [symm_mem.py:107] SymmMemCommunicator: symmetric memory multicast operations are not supported.
WARNING [custom_all_reduce.py:265] Custom allreduce is disabled because it's not supported on more than two PCIe-only GPUs.
WARNING [flashinfer_all_reduce.py:108] Failed to initialize FlashInfer All Reduce workspace: [SymmDeviceMemory] Device
        does not support multicasting.. This is expected on GPUs without NVSwitch (e.g., NVLink bridge-only or PCIe topologies).
WARNING [flashinfer_all_reduce.py:216] FlashInfer mnnvl allreduce workspace unavailable (likely no NVSwitch multicast
        support); falling back to trtllm backend for single node.
INFO    [flashinfer_all_reduce.py:224] Initialized FlashInfer Allreduce norm fusion workspace with backend=trtllm
...  'pass_config': {... 'fuse_allreduce_rms': True, ...}
Our TP8 server log (results/runs/qwen38_27b__r_par_tp8__256k__vllm__1010-153414/server.log), lines shortened

The suspected cause. By default, vLLM fuses each layer's all_reduce with the RMSNorm that follows it (fuse_allreduce_rms). Its preferred fused kernel needs NVLink multicast across every GPU in the tensor-parallel group, which only an NVSwitch provides. Our log shows exactly that check failing, followed by a fallback to the trtllm fused backend. A public bug report on the same hardware class (8× H200 NVL, two bridged NVLink islands, TP8) describes this fallback as working but very slow: 719.6 ms median inter-token latency at 32 concurrent requests with fusion on, against 20.7 ms with fuse_allreduce_rms turned off [S96]. The proposed fix in vLLM PR #48075 checks for multicast and, if it's missing, turns the fusion off and uses plain NCCL plus a separate RMSNorm. The PR's own H200 NVL numbers: 733 ms with the forced trtllm path, 21 ms with the fix. As of this build the PR is still open.

The upstream report gave us a testable hypothesis, but our pattern did not fully match it. TP8 was slow at 16 users, near-normal at one user (13.2 ms/token), and mostly fast but spiky at 64 and 256. We therefore treated the fused all-reduce fallback as a suspect and ran two interventions. The completed A/B below did not reproduce the upstream improvement: neither disabling the fusion nor disabling FlashInfer all-reduce removed our cliff.

The fusion-off A/B: neither fix helped

Arm (Qwen3.8-27B TP8, NCCL_P2P_LEVEL=SYS)1 user ms/token16 users out tok/s16 users ms/token64 users out tok/sRun
Baseline (fusion on)13.283.5182.56341010-153414
fuse_allreduce_rms false (vLLM #48071 workaround)13.679.7191.96351010-165539
VLLM_ALLREDUCE_USE_FLASHINFER=013.379.7191.76321010-172355

Both interventions reproduced the 16-user cliff: about 192 ms/token versus 182 ms in the baseline, with little movement at one or 64 users. They failed our proposed pass rule of at least a 3× improvement at 16 users, so we did not carry the workaround into the giant-model launches. This weakens the specific fusion-fallback hypothesis for our configuration; it does not prove that every possible interaction with that path is irrelevant. The unresolved next step was a profile of the TP8 16-user cell.

Four GPUs or eight? Same users, twice the hardware

The question a buyer actually asks: if the users don't change, what do the other four GPUs buy? We ran the same 1K/1K user steps (1, 64 and 256 users at once) and the 8K-prompt step at 64 users on one quad and on the whole node. The 4-GPU layouts ran on Saturday 10 Oct; the 8-GPU ones on Saturday evening and, for the layouts marked new below, on Sunday 11 Oct. All numbers are our own measurements.

In vLLM, a MoE model with --enable-expert-parallel spreads its experts over TP × DP GPUs, so TEP4 × DP2 means four-way tensor parallel attention, two data-parallel ranks and experts split eight ways vLLM docs: expert parallel deployment. Our TP4 × DP2 for the dense Qwen3.8-27B keeps each TP4 engine inside one NVLink quad, so no tensor traffic crosses the sockets.

Output tokens per GPU as users grow, 4-GPU layouts (dashed) against 8-GPU layouts (solid). Our measurements.
Output tokens per GPU at each user count: four-GPU layouts dashed, eight-GPU layouts solid. The same-model comparison shows whether additional GPUs improve useful output enough to justify their cost. Runs took place at different times and have no repeat-based error bars. Open full-size chart
ModelLayout1 user ms/token64 users tok/s256 users tok/s256 users tok/s per GPU256 users ms/token8K prompt, 64 users tok/s8K, 64 users TTFT msRun
Qwen3.8-27B4 GPUs · 4 copies (TP1)11.53,5315,3141,32817.62,2617441010-010602
Qwen3.8-27B4 GPUs · 2 copies (TP2)8.34,0175,8971,47416.22,2235561010-012340
Qwen3.8-27B8 GPUs · 8 copies (TP1)11.43,88511,2681,40918.82,8717151010-205644
Qwen3.8-27B8 GPUs · 4 copies (TP2)8.34,77512,3281,54117.83,1985161010-211609
Qwen3.8-27B8 GPUs · TP4 × DP2 (one engine per island) **(new, 11 Oct)**6.35,11711,4391,43020.43,4263441011-071732
Qwen3.8-27B8 GPUs · TP8 across islands13.26342,56532162.3--1010-153414
Qwen3.6-35B-A3B4 GPUs · 4 copies (TP1)4.45,8878,5332,13311.24,8422601010-015516
Qwen3.6-35B-A3B4 GPUs · DEP4 (DP4 attention + EP4)6.04,4709,2282,30726.23,0213011010-022232
Qwen3.6-35B-A3B8 GPUs · 8 copies (TP1) **(new, 11 Oct)**4.47,44418,3912,29912.26,8182571011-070943
Qwen3.6-35B-A3B8 GPUs · DEP8 (DP8 attention + EP8)9.93,6577,58294831.32,4453841010-163633
Qwen3.6-35B-A3B8 GPUs · TEP4 × DP2 (TP4, EP8) **(new, 11 Oct)**7.73,4106,64683136.5--1011-065213

The extra quad paid when the workload could use it. At 256 users, Qwen3.8-27B replicas produced 11,268 tokens/s on eight GPUs versus 5,314 on four (2.1×). TP2 pairs produced 12,328 versus 5,897 (2.1×). At 64 users the replica gain was only 1.1×, 3,885 versus 3,531. One request still uses one copy, so simply adding copies is not a single-request acceleration technique.

For Qwen3.6-35B-A3B, DEP8 produced less than DEP4 at 256 users: 7,582 versus 9,228 tokens/s. Cross-socket expert communication is a plausible contributor, but we did not profile it. Eight independent one-GPU copies reached 18,391 tokens/s, 2.2× four copies. TEP4×DP2 reached 6,646, or 0.88× DEP8. On the same eight GPUs the independent copies therefore delivered 2.8× TEP4×DP2 and 2.4× DEP8 at 256 users, and also led at one and 64 users. That is the direct same-model evidence for avoiding unnecessary distributed experts in this short-prompt grid.

Qwen3.8-27B made the choice less absolute. Two quad-local TP4 engines delivered 11,439 tokens/s at 256 users (1.02× eight one-GPU copies) and 6.3 ms/token for one user. This was the strongest measured Qwen3.8-27B layout at 64 users, including 8K prompts: 3,426 tokens/s with a 344 ms first token. Keeping each tensor-parallel group within a quad was materially better than our cross-socket TP8 run. Qwen3.6 TEP4×DP2 has no valid 8K point because prefix-cache hits contaminated that run.

Field measurement

Fit the model, then replicate the queue.

Qwen3.6-35B-A3B output tokens/s ↑
8 × TP1 replicas18,391
2 × TEP4 groups6,646
Qwen3.8-27B FP8 output tokens/s ↑
4 × TP2 replicas12,328
2 × TP4 replicas11,439
Original campaign · nominal 1,024 input / 1,024 output tokens · concurrency 256 · eight GPUs per layout. Bars are scaled within each model; compare layouts, not model quality.
What this comparison does and doesn't show

One run per layout, one node, short random prompts with distinct warm-up prompts. The 4-GPU runs used the same grid and software, early on Saturday 10 Oct. Replica layouts sit behind our round-robin proxy; on the 4-GPU replica runs we had no counter for requests actually running, so we can't rule out the same proxy cap there. Throughput per GPU is total output divided by the GPUs the layout occupies, not a measure of how busy each GPU was. None of these layout runs had a needle check of its own; under our rule each inherits the latest check for the same model at the same 256K maximum context, which passed 3/3 for both Qwen models.

Dropped for time

The GLM-5.3-Flash 8-GPU layouts (TP4 × DP2, TP8, TP8 + DCP8) were planned for this chapter and dropped on Sunday to fit the 16:00 IST deadline.

In our main grid, Qwen3.6's dp4 layouts are labelled data-parallel attention + TP4 experts (no EP). Without --enable-expert-parallel, vLLM forms the expert layers into one tensor-parallel group across the data-parallel ranks [S04]. The dense Qwen3.8-27B's dp4 is four plain data-parallel copies.

Replica size and precision (Sunday 02:26 to 04:08 IST)

Same Qwen3.8-27B, 256K maximum context, vLLM 0.31.0, all eight GPUs in use, with a round-robin proxy in front. We compared one copy per GPU (8 × TP1) against one copy per GPU pair (4 × TP2), each in FP8 and in an INT4 build, plus ThinkingCap-Qwen3.8-27B (a fine-tune of the same base, FP8) as 8 × TP1. Each run had a fresh server, distinct warm-up prompts, a needle gate (all passed 1.0), the 1K/1K user grid, a 250K-token document at 1 and 4 users, and 250 GSM8K questions.

Model · layout1 user ms/token256 users out tok/s256 users ms/token250K doc TTFT p50250K doc ms/tokenKV pool (all copies)GSM8K (250)Run
FP8 · 8 × TP111.411,26818.844.4 s16.823.32M0.7441010-205644
FP8 · 4 × TP28.312,32817.825.2 s13.127.12M0.7401010-211609
INT4 · 8 × TP111.410,84119.755.8 s17.026.09M0.7161010-213412
INT4 · 4 × TP29.010,89120.530.5 s14.128.52M0.7521010-215751
ThinkingCap FP8 · 8 × TP111.511,30918.844.2 s16.923.32M0.7681010-221630

What it shows. At 256 short-chat users the two layouts were close (about 11.3K vs 12.3K output tokens/s in FP8). Pairs gave the fastest single user and the shortest time to first token on a 250K-token document (pairs split the prefill), and a bigger KV pool (27.1M vs 23.3M tokens across all copies), because each copy's weights are spread over two GPUs. INT4 bought more KV room but not more speed here: it was slower than FP8 at 256 users in both layouts. ThinkingCap ran at the same speed as the base model in every cell, which is what you'd expect from a fine-tune with identical architecture and precision. Its GSM8K score was a little higher (0.768 vs 0.744), but on 250 questions that gap is about one standard error, so we don't call it a quality difference.

About these A/B numbers

One run per cell, one node. GSM8K on 250 questions has a standard error of about 2.7 points, so scores within about 3 points of each other are a tie. The KV pool is what vLLM reported at start-up, summed over copies.

Every point

All measured points (results/final.csv)
LayoutIn/out tokensUsersCompletedOut tok/sPer GPUTTFT p50TTFT p90ms/token p50ms/token p99Note
Qwen3.8-27B · 8 replicas (TP1 ×8) + load balancer1,024/1,024116/1687110.08 s0.14 s11.511.5
Qwen3.8-27B · 8 replicas (TP1 ×8) + load balancer1,024/1,02464256/2563,9124890.12 s0.57 s13.313.9
Qwen3.8-27B · 8 replicas (TP1 ×8) + load balancer1,024/1,0242561024/10246,3887980.12 s130.50 s14.515.3proxy-capped (about 100 running)
Qwen3.8-27B · 8 replicas (TP1 ×8) + load balancer8,192/1,02464256/2563,0013750.73 s3.22 s17.524.8
Qwen3.8-27B · TP8 across both islands (NCCL_P2P_LEVEL=SYS)1,024/1,024116/167590.14 s0.21 s13.213.2
Qwen3.8-27B · TP8 across both islands (NCCL_P2P_LEVEL=SYS)1,024/1,0241648/4883100.53 s0.81 s182.5184.6anomaly
Qwen3.8-27B · TP8 across both islands (NCCL_P2P_LEVEL=SYS)1,024/1,02464256/256634790.22 s2.10 s25.2272.8
Qwen3.8-27B · TP8 across both islands (NCCL_P2P_LEVEL=SYS)1,024/1,0242561024/10242,5653210.34 s7.26 s62.3110.9
Qwen3.8-27B · TP8 across both islands (NCCL_P2P_LEVEL=SYS)8,192/1,02464256/256------excluded: cache hits in a cold grid
Qwen3.6-35B-A3B · DEP8 (DP8 attention + EP8 experts)1,024/1,024116/16101130.12 s0.14 s9.910.1
Qwen3.6-35B-A3B · DEP8 (DP8 attention + EP8 experts)1,024/1,02464256/2563,6574570.12 s0.86 s14.916.9
Qwen3.6-35B-A3B · DEP8 (DP8 attention + EP8 experts)1,024/1,0242561024/10247,5829480.15 s2.30 s31.338.0
Qwen3.6-35B-A3B · DEP8 (DP8 attention + EP8 experts)8,192/1,02464256/2562,4453060.38 s4.97 s23.631.3
What this does and doesn't show

One grid, three runs, one node, all our measurement. The 1K and 8K prompts are random tokens, with prefix caching on but distinct warm-up prompts. DEP8 runs a different (MoE) model from the other two. TP8 used NCCL_P2P_LEVEL=SYS, the replicas and DEP8 used NCCL defaults. No long-context points are in this grid yet. The suspected cause of the TP8 anomaly comes from public reports and our log lines, not from a profile.

What we learned
  • Compare replicas before extending a small model across sockets. In the initial grid, eight Qwen3.8-27B replicas gave 6.2× TP8’s throughput at 64 users and at least 2.5× at the proxy-capped 256-user point, with faster single-user decode. Quad-local TP4 and TP2 pairs then showed that the useful choice was the size of each independent serving group, not simply “parallelism versus replicas.”
  • Use startup warnings to form a hypothesis, then keep the negative result. The multicast/fallback warnings resembled a public H200 NVL issue. Our two fusion interventions still produced roughly 192 ms/token at 16 users. The resemblance did not establish the cause, and the cliff remained unresolved in this campaign.
  • Keep the same-model comparison in view. DEP8 reached 7,582 tokens/s at 256 users, but the later eight-replica Qwen3.6 run reached 18,391. Its strong showing against a different dense model in the first table was not evidence that DEP8 was the best way to serve that MoE.
  • Check the harness before the hardware. Our replica proxy quietly capped itself at about 100 requests in flight. The engines' running-request counters showed it, and the latency numbers alone would have blamed the replicas.

Before tuning further, audit the failures

The replica results were useful only after we separated a proxy cap, cache contamination and an unresolved TP8 cliff from real model behaviour. Part 7 collects that diagnostic record, including the fixes that worked and the plausible hypotheses that did not.

Sources

Bracketed ids [S..] refer to the campaign bibliography (docs/SOURCES.md). Numbers not attributed to a source are our own measurements on this node.

  1. [S24] NVIDIA, NCCL environment variables
  2. [S05] vLLM project, Expert Parallel Deployment (docs)
  3. [S04] vLLM project, Data Parallel Deployment (docs)
  4. [S01] NVIDIA, H200 NVL product brief (PB-12243): NVLink bridge topology guidance
  5. [S96] vLLM project, https://github.com/vllm-project/vllm/issues/48071
  6. aiohttp, https://docs.aiohttp.org/en/stable/client_reference.html#tcpconnector (aiohttp docs: TCPConnector, limit defaults to 100)
  7. qtris123, https://github.com/vllm-project/vllm/pull/48075 (vLLM PR #48075)
  8. vLLM, https://docs.vllm.ai/en/latest/serving/expert_parallel_deployment.html (vLLM docs: expert parallel deployment)

← Back to Blog