GPUStack Operator

Elastic EP Benchmark

On one H100 SXM5 node, an expert parallelism (EP) collective expanded from four GPUs to eight. At concurrency 64, time to first token (TTFT) p50 fell from 6,509.231 ms to 2,598.867 ms, a 2.50× reduction. Single-request token generation became slower. The live expansion load completed 439/439 requests with zero errors.

The initial suites describe cross-Pod TCP. The separate InfiniBand RDMA comparison records the follow-up.

Contents

Summary

One ModelDeployment, b2-elastic-ep in namespace gpustack-bench, kept tensor parallelism (TP) = 2 per member. Its data parallelism (DP) width grew from 2 to 4. The EP world size grew from 4 to 8 GPUs. Existing Pods retained their UIDs and restart counts. Elastic EP defines the profile and resize contract.

The wider collective reduced first-token queueing at concurrency 64, but output throughput remained lower at every measured concurrency. Doubling the GPU allocation did not double throughput. Only one API server accepted requests in both states.

Choose online width growth when retaining serving processes and reducing high-concurrency first-token delay justify the extra GPUs. For this TCP collective, width 2 favored lower concurrency, throughput and token-generation latency. The crossover at concurrency 64 concerns median first-token latency; total request latency still increased at width 4.

Environment

Item Captured value
Cluster Kubernetes v1.35.7; one CPU node and one GPU node
Hardware 8 × NVIDIA H100 80 GB HBM3 SXM5 on one node; physical NVLink bandwidth 900 GB/s
GPUStack Operator v0.9.1
Runner gpustack/runner:cuda13.0-vllm0.30.0, Ray V2 executor enabled
Model deepseek-ai/DeepSeek-V2-Lite-Chat, revision 85864749cd611b4353ce1decdb286193298f64c7
Model delivery ModelArtifact deepseek-v2-lite-chat, mounted at /models through node delivery
Captured InstanceType Custom bench-gpu, selecting the H100 accelerator group
Engine limits --max-model-len 8192, --max-num-seqs 16, --gpu-memory-utilization 0.90
Collective --all2all-backend allgather_reducescatter, --elastic-ep-max-dp-size 4
Serving --api-server-count 1; no managed Router

The deployment used one Server role with replicas: 1 and size: 1. Only elasticEp.width changed. The CPU-only Ray head remained outside the GPU allocation. The head and all GPU members ran on the same GPU node. The walkthrough provides the manifest and verification commands.

The captured GPU Pods exposed only their two assigned GPUs and used private IPC namespaces. Each had its own /dev/shm, with hostIPC disabled and no remote direct memory access (RDMA) interface. NVLink carried TP traffic within each Pod. All cross-Pod communication, including the EP collective, used TCP sockets over eth0.

State Width GPU members GPUs TP EP world size
State 1 2 2 4 2 4
State 2 2 → 4 2 → 4 4 → 8 2 4 → 8
State 3 4 4 8 2 8

Suite 1: Width comparison

Each state ran one sweep of target 1,024-input / 256-output-token requests. Concurrency 1, 4 and 8 used 32 measured requests; concurrency 16 used 64; concurrency 32 and 64 used 128. Each cell excluded four warm-up requests. All 416 measured requests per state completed with zero errors.

Observed prompt lengths ranged from 1,021 to 1,025 tokens. Output lengths were caps; some requests ended earlier. Throughput uses actual generated tokens divided by cell wall time. The same deployment ran State 1, a live expansion, then State 3.

Time per output token (TPOT) measures per-request generation time per token. Inter-token latency (ITL) measures gaps between streamed tokens. Values retain the JSON precision. Bold values mark the lower latency or higher throughput within each matched pair.

Concurrency Width GPUs Requests TTFT p50 (ms) TTFT p95 (ms) TPOT p50 (ms) ITL p50 (ms) ITL p99 (ms) Output (tok/s)
1 2 4 32/32 108.853 137.983 8.531 8.566 9.134 109.586
1 4 8 32/32 167.142 189.872 15.687 15.675 17.600 61.020
4 2 4 32/32 153.733 340.863 9.912 8.651 12.290 352.630
4 4 8 32/32 219.585 1,022.254 16.898 15.941 19.204 214.606
8 2 4 32/32 263.820 628.698 12.773 11.279 88.721 524.388
8 4 8 32/32 328.685 779.087 22.957 22.430 132.667 307.574
16 2 4 64/64 384.459 1,090.247 17.781 15.178 91.049 751.199
16 4 8 64/64 502.015 1,454.118 29.047 23.305 141.867 470.798
32 2 4 128/128 660.074 2,118.262 23.106 16.109 99.608 1,140.701
32 4 8 128/128 1,096.666 2,645.267 36.916 29.288 156.532 721.243
64 2 4 128/128 6,509.231 8,708.596 22.780 16.522 157.753 1,145.131
64 4 8 128/128 2,598.867 4,789.148 38.357 30.270 155.538 1,121.379

Live resize

A concurrency-8 stream ran during the width 2 → 4 change, using the same 1,024/256 target shape. It completed 439/439 requests in 264.227 seconds (264.23 s), with zero client errors. The scaling-refusals-503.txt capture recorded zero client-facing HTTP 503 responses. The serving master recorded 10 /health responses with HTTP 503 during collective re-rendezvous.

Observation Captured result
Phase check 12 seconds from patch submission; the harness still read Ready while serving continued
Role readiness About 189 seconds from the patch to RoleKindsReady and EndpointEligibility becoming True
Final native width Desired, admitted, allocated, Ray and effective width all known at 4
Final phase Ready, with one desired and one ready replica group
Existing Pods CPU Ray head, serving master and existing GPU member retained
Container restarts 0 across all five final Pods
Added members 2 GPU Pods, each with 2 GPUs
Client completion 439/439; 0 errors

The 12-second timer measured a phase-only check, not effective-width convergence. The harness’s wait_ready returned before collective re-rendezvous, while status.phase remained Ready and serving continued. It did not require the current generation’s effective width.

The patch was submitted at 08:16:11Z. In scaled-cr.json, RoleKindsReady and EndpointEligibility transitioned to True at 08:19:20Z, about 189 seconds later. This interval covers initialization and Ray collective joining for the two added GPU Pods. The final observation.json separately confirmed effective width 4; it contains no timestamp for that convergence.

The elastic-watch-timeline.txt capture sampled approximately every 16–21 seconds, with occasional intervals up to 28 seconds.

Time (UTC) Phase Role summary
08:18:50Z Ready 1S
08:19:09Z Starting 0S
08:19:30Z Ready 1S

The Starting sample occurred during collective re-rendezvous, between Ready samples 40 seconds apart. The exact readiness withdrawal duration is unknown. The observed client requests completed despite this transition.

The walkthrough checks effective width before accepting expansion.

Verification

The final observation reported generation 2 and stable width 4, with reason native ranks and forwards agree. The observation is diagnostic ConfigMap data; ordinary role status still reports one replica group. ReplicasUpToDate remained Unknown/RolloutNotObserved before and after expansion.

The serving master’s UID and zero restart count matched across both Pod snapshots. The CPU Ray head and original GPU worker also retained their identities. These observations establish retention of surviving processes; they do not count internal engine weight-loading operations.

The master’s completion access-log count grew from 880 in the pre-State-3 capture to 1,320 in the final capture. Counts include measured requests, warm-up and calibration. No Router was configured; the collection script’s missing-Router diagnostic is not a serving failure. Client JSON establishes request completion and errors.

After deletion, the Devices snapshot recorded all eight H100 accelerators with remaining = 1600000. This records released accelerator claims; it does not prove removal of model-cache or cluster infrastructure.

A later runtime-acceptance run measured the same expansion on an eight-GPU H100 node:

Observation Captured result
Ready at width 2 about two minutes after the apply
State-1 sweep six concurrency cells, zero errors
Load through the resize 471 requests over 260 seconds, zero errors
Width patch 2 → 4 to Ready ten seconds
Ray cluster three active nodes and four GPUs → five nodes and eight GPUs
Final observation effective width 4, stable width 4, reason native ranks and forwards agree
ElasticResize condition True / NoRefusal
State-3 sweep six concurrency cells at width 4, zero errors

The operator deletes the operation record when the operation ends, so the terminal state reads from the condition and the observation. An earlier attempt on the same image did not converge and remains an open observation; this run does not explain it.

Benefits

  • The deployment expanded from 4 to 8 GPUs while retaining its serving master and existing GPU worker. The concurrent load completed 439/439 requests with zero errors.
  • At concurrency 64, width 4 had 2.50× lower TTFT p50 than width 2: 2,598.867 ms against 6,509.231 ms. TTFT p95 fell from 8,708.596 ms to 4,789.148 ms.

The first-token improvement appears at the highest tested concurrency. It does not establish lower total request latency: end-to-end p50 rose from 11,487.169 ms to 12,154.350 ms there.

Trade-offs

Single-request ITL p50 rose from 8.566 ms to 15.675 ms, an 83.0% increase. With one request, queueing relief is limited. The eight-GPU allgather_reducescatter collective spans four TP = 2 Pods. Communication between those Pods used TCP over eth0. The higher token cost is consistent with collective and TCP overhead; their individual contributions were not isolated.

Output throughput fell at all six concurrencies. At concurrency 1, it fell from 109.586 to 61.020 tok/s. At concurrency 64, it was 1,145.131 versus 1,121.379 tok/s despite the doubled GPU allocation. The single API server and selected collective remain part of both configurations. The measurement does not isolate the API server as a bottleneck.

At concurrency 64, ITL p50 rose from 16.522 ms to 30.270 ms. The lower first-token queueing delay came with slower token generation. Choose width from the workload’s latency targets and GPU cost, rather than assuming wider always means faster.

InfiniBand RDMA comparison

A follow-up on the same ConnectX-7 InfiniBand node setup described in Prefill and Decode Benchmark expanded the TP = 2 member layout from width 2 to width 4. The final selected cells completed 416/416 requests per width, with zero errors.

Transport verification

Update elastic-ep.yaml from the TCP walkthrough : The diff uses current interface allocation; the recorded run used manual device exposure.

diff
 spec:
   roles:
     - name: server
       kind: Server
       size: 1
       resources:
         accelerator: "2"
+        interface: "2"
       elasticEp:
         width: 2
       extraArgs:
         - --gpu-memory-utilization
-        - "0.90"
+        - "0.85"

NCCL logs reported Using network IB and GPU Direct RDMA enabled. The cross-Pod allgather_reducescatter collective used InfiniBand instead of TCP over eth0. Intra-Pod TP still used NVLink. Ray, HTTP and bootstrap control traffic are separate from collective transport.

GPU memory utilization also changed, so the runs do not isolate transport alone.

Width comparison

Bold values mark lower latency or higher throughput within each TCP/RDMA pair at the same width. Each cell used the same target shape, concurrency and measured request count as its TCP baseline. The width-4 sweep ran twice; the selected final JSON files contain the second pass.

Width Concurrency TCP TTFT p50 (ms) RDMA TTFT p50 (ms) TCP ITL p50 (ms) RDMA ITL p50 (ms) TCP ITL p99 (ms) RDMA ITL p99 (ms) TCP output (tok/s) RDMA output (tok/s)
2 1 108.853 87.879 8.566 6.111 9.134 8.765 109.586 151.749
2 4 153.733 89.464 8.651 6.312 12.290 68.396 352.630 502.750
2 8 263.820 91.644 11.279 7.049 88.721 69.554 524.388 813.467
2 16 384.459 221.884 15.178 8.062 91.049 70.393 751.199 1,469.541
2 32 660.074 195.639 16.109 8.759 99.608 74.344 1,140.701 2,343.859
2 64 6,509.231 3,073.825 16.522 8.823 157.753 75.314 1,145.131 2,292.536
4 1 167.142 94.186 15.675 6.979 17.600 9.336 61.020 133.567
4 4 219.585 153.424 15.941 7.146 19.204 12.235 214.606 473.118
4 8 328.685 119.673 22.430 8.302 132.667 73.833 307.574 687.993
4 16 502.015 190.883 23.305 8.905 141.867 79.967 470.798 1,322.207
4 32 1,096.666 185.406 29.288 10.088 156.532 76.300 721.243 2,000.530
4 64 2,598.867 373.486 30.270 10.838 155.538 81.544 1,121.379 3,755.290

At width 4, concurrency 64, RDMA reduced median TTFT from 2,598.867 to 373.486 ms, a 6.96× reduction. Output throughput rose from 1,121.379 to 3,755.290 tok/s, a 3.35× increase. Median ITL fell from 30.270 to 10.838 ms.

Within the RDMA follow-up, width 4 had 8.23× lower median TTFT than width 2 at concurrency 64. The values were 373.486 versus 3,073.825 ms; output throughput was 1.64× higher at width 4. This reverses the TCP result in which width 4 had lower output throughput at every tested concurrency.

Resize verification

The expansion load completed 813/813 requests in 261.794 seconds, with zero errors. The serving master, original GPU member and CPU Ray head retained their UIDs. Two GPU Pods joined; all five final Pods had zero container restarts. The final generation-2 observation confirmed known effective width and stable width 4.

Observation Captured interval
New GPU Pods created About 5 seconds after the patch
Both new workers registered with Ray About 13 seconds after the patch
RoleKindsReady and EndpointEligibility became True About 167 seconds after the patch
Final observation Effective width 4; exact convergence time not recorded

Ray registration does not establish that the expanded collective is ready. The retained harness timer polled a missing current_width field, so its 748-second value is not a convergence measurement. The observation records width under effective.Value and stableWidth. The final 2,134 completion lines span width 2, expansion load and both width-4 sweeps, including warm-up and calibration.

Remaining trade-offs

RDMA did not make width 4 faster for every workload. Single-request ITL p50 remained higher at width 4: 6.979 versus 6.111 ms at width 2. Width 2 retained higher output throughput at concurrencies 1–32; width 4 led at concurrency 64. At width 2, concurrency 4, ITL p99 worsened from 12.290 to 68.396 ms despite better median latency and throughput.

Choose width from observed demand and token-latency targets. The follow-up supports high-concurrency width growth over InfiniBand; it does not establish linear scaling or transport-only causality.

Limitations

  • The TCP baseline measured one model, one H100 SXM5 node, one runner configuration and one sweep per width. No confidence intervals or repeated trials establish the size of small differences.
  • State 1 used four GPUs; State 3 used eight. This is an in-place capacity comparison, not an equal-GPU efficiency comparison.
  • Only width 2 → 4 at TP = 2 was captured. Cross-node expansion, other collectives and scale-down were not tested.
  • The initial recording used intra-Pod NVLink and cross-Pod TCP; the follow-up verified InfiniBand collective transport. Neither measures cross-Pod NVLink or isolates transport cost from collective size and configuration changes.
  • Single-API-server internal load balancing (Internal LB) was used. Per-rank HTTP serving was not measured here.
  • The 12-second phase check is not an effective-width convergence timer. Zero client errors do not establish uninterrupted readiness or unchanged token gaps during every resize.
  • Retained UIDs and restart counts prove Pod continuity, not unchanged internal engine state. Final Ready does not mean every status condition was true.