Running a frontier-scale model locally means keeping all its weights in GPU memory; the largest models no longer fit on one machine. Two NVIDIA DGX Stations connected over ConnectX-8 RDMA serve one model through a single endpoint, with tensor parallelism splitting the model weights and computation across both GPUs.
The NVIDIA GLM-5.3 NVFP4 model, a quantized version of Z.ai’s mixture-of-experts (MoE) model, has 753 billion parameters, with 40 billion activated per token. Sparse activation reduces computation, but all model weights still need to remain available because each token can be routed to different experts. At about 464 GB, the checkpoint exceeds the 252 GB of HBM3e on a single GB300 GPU. This tutorial uses two DGX Stations to keep the weights in GPU memory, with CPU weight offload disabled.
Before starting, connect the two Stations with the Connect Two DGX Stations playbook.
This post shows how to serve GLM-5.3 locally across two NVIDIA DGX Stations with vLLM, send a request, and measure behavior under concurrent load. Tensor parallelism (TP=2) splits the model’s tensors across the two GB300 GPUs. Both Stations compute every request, and Station A returns the answer through one OpenAI-compatible API. By the end, you’ll have a working endpoint and a baseline for how it behaves under load.

Prerequisites
- Two GB300 DGX Stations running Linux, connected and validated with the Connect Two DGX Stations playbook, including its GPU-memory RDMA test on both rails.
- SSH and sudo access on both Stations with the same Linux username. Docker and the NVIDIA Container Toolkit come preinstalled with the DGX Station software stack. Allow outbound access to the container registry, Hugging Face, and package repositories.
- At least 464 GB of free disk space on each Station for its copy of the checkpoint, plus room for the vLLM container image and temporary files.
- Acceptance of the NVIDIA Open Model Agreement and the original GLM-5.3 license terms, listed on the model card. SPEED-Bench is governed by the NVIDIA Evaluation Dataset License Agreement.
- A Hugging Face account and read access token, with access to the gated cais/hle dataset: sign in, open its page, and select Agree and access repository. Step 5 downloads SPEED-Bench prompts from this dataset.
The companion video follows the same workflow.
Step 1. Prepare the two Stations
This tutorial uses a separate computer, such as your laptop, to manage both Stations. Start by opening an SSH session to each Station from that computer. Alternatively, work from a local terminal on each Station. Run every command in this step on both Stations unless it starts with “On A.”
The two Stations run the same container and checkpoint in different roles: Station A (rank 0) hosts the API, and Station B (rank 1) runs only a worker, with no API server.
First, check Docker access and collect the network values you’ll use later:
docker version
ibdev2netdev
ip -br addr
Note: If the docker version output has no Server section, the client cannot reach the Docker daemon. If the error is permission denied, follow the Docker post-installation procedure: add your user to the docker group with sudo usermod -aG docker "$USER", run exit, then reconnect and rerun docker version. The docker group grants root-level privileges. |
|---|
The ibdev2netdev output lists the two direct rails, cx8r0 and cx8r1. Pick one for serving traffic, for example cx8r1, and note two things for it on each Station:
- Its RDMA device, on the
ibdev2netdevline that ends with the rail’s name. Inmlx5_1 port 1 ==> cx8r1 (Up), the device ismlx5_1. - Its IPv4 address, on the
ip -br addrline that starts with the rail’s name. Incx8r1 UP 192.168.101.1/24, the address is192.168.101.1.
The rail name and device are the same on both Stations; only the IPv4 address differs.
Next, create ~/glm53.env on each Station. This file stores the values that later commands use:
nano ~/glm53.env
Paste the following block and replace each <...> placeholder, keeping the quotes, with the values you noted.
RANK="<NODE_RANK>" # RANK="0" on Station A, RANK="1" on Station B
HEAD_IP="<STATION_A_IPV4_ADDRESS>" # IPv4 address you noted on Station A; same in both files
WORKER_IP="<STATION_B_IPV4_ADDRESS>" # IPv4 address you noted on Station B; same in both files
RAIL_IFACE="<RAIL_NAME>" # Rail you picked, for example cx8r1; same in both files
RAIL_HCA="<RDMA_DEVICE>" # RDMA device you noted, for example mlx5_1; same in both files
IMG=vllm/vllm-openai:v0.30.0-aarch64
IMG+=@sha256:4864d46625cbc3307623e29ac742030655e27249feba7b97ec925ce4cc4dfb56
MODEL_REPO=nvidia/GLM-5.3-NVFP4
MODEL_REV=e3b8f9f2fad29727dfa95e766c5cac18ea1414bf
MODEL_HOST="$HOME/models/GLM-5.3-NVFP4"
CONTAINER=glm53-full
PORT=8000
if [ "$RANK" = 0 ]; then
NODE_IP=$HEAD_IP; PEER_IP=$WORKER_IP
else
NODE_IP=$WORKER_IP; PEER_IP=$HEAD_IP
fi
GPU_UUID=$(nvidia-smi --query-gpu=uuid,name --format=csv,noheader |
awk -F, '$2 ~ /GB300/ {gsub(/ /,"",$1); print $1; exit}')
For example, with the default addresses on cx8r1, Station A’s file starts like this. Station B’s file is identical except for RANK="1":
RANK="0"
HEAD_IP="192.168.101.1"
WORKER_IP="192.168.101.2"
RAIL_IFACE="cx8r1"
RAIL_HCA="mlx5_1"
MODEL_HOST is built from your home directory, and Step 2 copies the checkpoint over SSH to the same path on Station B.
Note: Later steps start with source ~/glm53.env to load these values. Run this command again whenever you open a new SSH session. |
|---|
On both Stations, load the values and check storage, connectivity, and baseline memory:
source ~/glm53.env
mkdir -p "$MODEL_HOST" && df -h "$MODEL_HOST"
ethtool "$RAIL_IFACE"
ip link show dev "$RAIL_IFACE"
ping -I "$RAIL_IFACE" -M do -s 8972 -c 3 "$PEER_IP"
timedatectl show -p NTPSynchronized --value
nvidia-smi
These checks catch cabling and addressing mistakes before they surface as NCCL timeouts. Expect a 400000Mb/s link, MTU 9000, successful jumbo-frame pings, and synchronized time. nvidia-smi should show no compute processes on the GB300. On a Station with a display GPU, Xorg can appear on both GPUs with a few MiB.
Tip: Note the GPU memory use shown by nvidia-smi; Step 4 compares it with the loaded model. |
|---|
Step 2 copies the checkpoint from A to B over SSH, so A needs passwordless access to B. On A, create a key without a passphrase (see SSH keys for details), copy its public key to B after checking B’s host fingerprint, and confirm the login:
ssh-keygen -t ed25519
ssh-copy-id "$WORKER_IP"
ssh -o BatchMode=yes "$WORKER_IP" hostname
Step 2. Download and copy the model
Tensor parallelism requires both ranks to run the same software against the same weights. Start by pulling the pinned image on both Stations:
docker pull "$IMG" &&
docker image inspect "$IMG" --format '{{.Architecture}} {{.RepoDigests}}'
The pull can take several minutes. Matching arm64 digests mean both ranks run identical software.
On A, download the pinned checkpoint. At about 464 GB, it takes roughly an hour at 1 Gb/s, or about six minutes at 10 Gb/s:
mkdir -p "$MODEL_HOST"
docker run --rm --network host -v "$MODEL_HOST:/model" \
--entrypoint bash "$IMG" -lc \
"exec hf download '$MODEL_REPO' --revision '$MODEL_REV' --local-dir /model"
When hf download prints Downloaded, copy the files to B. On A:
ssh "$WORKER_IP" "mkdir -p '$MODEL_HOST'" &&
rsync -aH --info=progress2 --exclude '.cache/' \
"$MODEL_HOST/" "$WORKER_IP:$MODEL_HOST/"
Each disk keeps the full checkpoint; vLLM partitions the weights across the GPUs at load time, with no further quantization.
On A, compare both copies (the check reads B’s file list over SSH), then validate the checkpoint:
(
set -euo pipefail
L=$(find "$MODEL_HOST" -path '*/.cache' -prune -o \
-type f -printf '%P %s\n' | LC_ALL=C sort | sha256sum)
R=$(ssh "$WORKER_IP" "set -o pipefail; find '$MODEL_HOST' \
-path '*/.cache' -prune -o -type f -printf '%P %s\\n' | \
LC_ALL=C sort | sha256sum")
test "$L" = "$R"
test "$(find "$MODEL_HOST" -maxdepth 1 \
-name 'model-*-of-00050.safetensors' | wc -l)" = 50
python3 -c "import json; c=json.load(open('$MODEL_HOST/config.json')); \
assert c['architectures']==['GlmMoeDsaForCausalLM']; \
assert c['quantization_config']['quant_algo']=='MIXED_PRECISION'"
echo MODEL_READY
)
MODEL_READY means both Stations hold the same file names and sizes in the expected layout. MIXED_PRECISION is expected: NVFP4 quantization targets MoE-block linear weights and activations, not every tensor.
Step 3. Launch the distributed service
This step turns two machines into one service: the same launch script runs on both Stations, reads ~/glm53.env, and starts one vLLM rank on each. Two settings matter most if you adapt it: --max-model-len 98304, the context ceiling in tokens, which covers input plus output, and --gpu-memory-utilization 0.93, which gives vLLM 93% of GPU memory. If you change other flags, change one at a time and test again.
For the latest recommended configurations, see NVIDIA’s Local AI recipe repository.
Security note: --host 0.0.0.0 with --network host exposes the unauthenticated API on every Station A interface, including management. Firewall the port to intended clients, and add authentication and TLS before wider access. |
|---|
On both Stations, create a file named ~/launch-glm53.sh:
nano ~/launch-glm53.sh
The script enables multi-token prediction (MTP), a form of speculative decoding: the model’s MTP layer drafts four tokens per step, and vLLM verifies them with the full model before accepting them, so each step can produce several tokens. The gain depends on how often drafts are accepted, which varies with the prompt.
FP8 KV-cache storage halves cache memory compared with 16-bit storage, leaving room for more tokens. Prefix caching is enabled to avoid reprocessing prompt prefixes the server has already seen. Paste the script below; it checks your configuration, finds the RoCE GID index for the selected rail, and starts this Station’s vLLM rank in a container:
set -euo pipefail
source "$HOME/glm53.env"
case "$RANK" in 0|1) ;; *) echo "Invalid RANK" >&2; exit 1;; esac
test -n "$GPU_UUID"
test -f "$MODEL_HOST/config.json"
GID_INDEX=$(/usr/sbin/show_gids 2>/dev/null |
awk -v d="$RAIL_HCA" -v ip="$NODE_IP" \
'$1==d && $5==ip && $6=="v2" {g=$3} END {if(g!="") print g}')
test -n "$GID_INDEX"
HEADLESS=
if [ "$RANK" = 1 ]; then HEADLESS=--headless; fi
SPEC_CONFIG='{"method":"mtp","num_speculative_tokens":4}'
KERNEL_CONFIG='{"enable_flashinfer_autotune":false,"moe_backend":"flashinfer_cutlass"}'
COMPILE_CONFIG='{"cudagraph_mode":"FULL_DECODE_ONLY","cudagraph_capture_sizes":[5,10,20,40,80],"pass_config":{"fuse_allreduce_rms":false}}'
docker run -d --name "$CONTAINER" \
--gpus device="$GPU_UUID" --network host --ipc host --shm-size 32g \
--device /dev/infiniband --cap-add IPC_LOCK \
--ulimit memlock=-1 --ulimit stack=67108864 \
-v "$MODEL_HOST:/model:ro" \
-e RANK="$RANK" -e HEADLESS="$HEADLESS" \
-e MASTER_ADDR="$HEAD_IP" -e MODEL_REPO="$MODEL_REPO" -e PORT="$PORT" \
-e SPEC_CONFIG="$SPEC_CONFIG" -e KERNEL_CONFIG="$KERNEL_CONFIG" \
-e COMPILE_CONFIG="$COMPILE_CONFIG" \
-e NCCL_CUMEM_ENABLE=1 -e NCCL_IB_HCA="$RAIL_HCA" \
-e NCCL_IB_GID_INDEX="$GID_INDEX" \
-e NCCL_IB_TIMEOUT=22 -e NCCL_IB_RETRY_CNT=7 \
-e VLLM_ALLREDUCE_USE_FLASHINFER=0 -e VLLM_ALLREDUCE_USE_SYMM_MEM=0 \
-e NCCL_SOCKET_IFNAME="$RAIL_IFACE" -e GLOO_SOCKET_IFNAME="$RAIL_IFACE" \
-e NCCL_NET_GDR_LEVEL=5 -e NCCL_DEBUG=INFO -e NCCL_DEBUG_SUBSYS=INIT,NET \
-e NCCL_DEBUG_FILE='/tmp/nccl_%h_%p.log' -e VLLM_HOST_IP="$NODE_IP" \
-e VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=1800 \
-e PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True \
-e VLLM_ALLOW_LONG_MAX_MODEL_LEN=1 -e FLASHINFER_DISABLE_VERSION_CHECK=1 \
--entrypoint bash "$IMG" -lc '
set -euo pipefail
# Use NCCL 2.30.7 from the image: older versions can deadlock
# cross-node CUDA graph replays over RoCE (vLLM issue #52504).
export VLLM_NCCL_SO_PATH=$(python3 -c "import nvidia.nccl,os;
print(os.path.join(list(nvidia.nccl.__path__)[0], \"lib\", \"libnccl.so.2\"))")
test -f "$VLLM_NCCL_SO_PATH"
EXTRA=()
if [ -n "$HEADLESS" ]; then EXTRA+=("$HEADLESS"); fi
exec vllm serve /model --served-model-name "$MODEL_REPO" \
--host 0.0.0.0 --port "$PORT" \
--nnodes 2 --node-rank "$RANK" --master-addr "$MASTER_ADDR" \
--master-port 29501 --tensor-parallel-size 2 \
--speculative-config "$SPEC_CONFIG" \
--max-model-len 98304 --max-num-batched-tokens 8192 \
--block-size 128 --max-num-seqs 16 \
--trust-remote-code --disable-custom-all-reduce \
--kv-cache-dtype fp8 --enable-prefix-caching \
--gpu-memory-utilization 0.93 \
--enable-prompt-tokens-details \
--kernel-config "$KERNEL_CONFIG" --compilation-config "$COMPILE_CONFIG" \
--distributed-timeout-seconds 1800 \
--enable-auto-tool-choice --tool-call-parser glm47 --reasoning-parser glm47 \
"${EXTRA[@]}"
'
Run the script on B first, then on A:
bash ~/launch-glm53.sh
Each command prints a container ID and returns immediately; the service is not ready yet. B’s worker waits for A to join, then each Station loads its share of the weights from local storage, prepares kernels, and captures CUDA graphs. This takes several minutes.
On A, follow the log to see when the service is ready (--since hides output from earlier runs):
docker logs --follow --since "$(docker inspect \
--format '{{.State.StartedAt}}' "$CONTAINER")" "$CONTAINER"
When the log shows Application startup complete, press Ctrl+C to stop following it; the service keeps running.
At this point one model spans two machines. Neither GPU holds a complete copy, and neither can answer a request alone: every token needs both ranks to exchange partial results over the RoCE rail.
The remaining GPU memory holds the KV cache, which stores the context of in-flight requests. The startup log reports its capacity (GPU KV cache size). All requests in flight share it, so longer prompts or more users need more of it. If you run out, lower --max-model-len or send fewer requests at once.
Step 4. Verify serving and send a request
On A, check the API (127.0.0.1 is A itself):
docker exec "$CONTAINER" curl -fsS --max-time 10 \
"http://127.0.0.1:$PORT/health" >/dev/null && echo HEALTH_OK
HEALTH_OK proves that Station A answers. Next, on both Stations, check that the ranks communicate over GPU RDMA rather than falling back to TCP sockets. The check reads the NCCL logs that the launcher writes through NCCL_DEBUG=INFO, and the same block shows the loaded memory:
docker exec "$CONTAINER" bash -lc '
set -euo pipefail
files=(/tmp/nccl_*.log)
test -e "${files[0]}"
grep -hE "NET/IB.*GDRDMA|GPU Direct RDMA Enabled" "${files[@]}" >/dev/null
grep -hF "$NCCL_IB_HCA" "${files[@]}" >/dev/null
if grep -hE "NET/Socket|NCCL WARN|NCCL ERROR" "${files[@]}"; then
exit 1
fi
echo RDMA_CHECK_OK
'
nvidia-smi
RDMA_CHECK_OK means the check passed with no warnings. In nvidia-smi, A shows the VLLM::Worker_TP0 process and B shows VLLM::Worker_TP1, one rank per GPU. The startup log reports about 218 GiB of weights per GPU, roughly half the checkpoint, which confirms that the model is split across the two GPUs.
| Measurement | Station A | Station B |
|---|---|---|
| GB300 GPU used (GiB) | 0.01 → 231.35 | 0.00 → 231.34 |
On A, send a chat request through the OpenAI-compatible API:
docker exec "$CONTAINER" curl -fsS --max-time 120 \
"http://127.0.0.1:$PORT/v1/chat/completions" \
-H 'Content-Type: application/json' \
-d '{"model":"nvidia/GLM-5.3-NVFP4",
"messages":[{"role":"user","content":"Quarterly units sold: Q1 = 120; Q2 = 180; Q3 = 150. Summarize the trend in one sentence and calculate the percentage change from Q1 to Q2."}],
"temperature":0,"max_tokens":4096,
"chat_template_kwargs":{"reasoning_effort":"low"}}' |
python3 -c 'import json,sys; c=json.load(sys.stdin)["choices"][0];
print(c["message"]["content"]); print("finish_reason:",c["finish_reason"])'
The response should look like this:
**Trend:** Units sold rose sharply in Q2 before partially declining in Q3, remaining above the Q1 level.
**Q1 → Q2 percentage change:** (180 - 120) / 120 × 100 = **50% increase**
finish_reason: stop
finish_reason: stop means the model finished its answer instead of hitting the token limit. The request sets reasoning_effort to low in chat_template_kwargs. GLM-5.3 also accepts high and max, which let it reason longer before answering, at the cost of latency. Because the launcher enables tool calling (--enable-auto-tool-choice), agent frameworks that use the OpenAI tools API can call the same endpoint.
Step 5. Concurrency and release resources
AIPerf, the NVIDIA benchmarking tool for inference endpoints, measures throughput and latency. Installing it inside the serving container can change vLLM’s dependencies, so on A, install it in its own virtual environment:
sudo apt update &&
sudo apt install -y python3.12-venv python3.12-dev build-essential &&
python3.12 -m venv "$HOME/.venvs/aiperf-0.12.0" &&
"$HOME/.venvs/aiperf-0.12.0/bin/pip" install 'aiperf==0.12.0' &&
"$HOME/.venvs/aiperf-0.12.0/bin/pip" check
The install should end with No broken requirements found.
AIPerf can send synthetic prompts or replay an existing dataset. This tutorial uses the 80 Coding conversations from SPEED-Bench, a set of real prompts built to evaluate speculative decoding, so MTP is measured on realistic text. SPEED-Bench’s preparation script fetches these prompts from several Hugging Face datasets, including the gated cais/hle: if you haven’t already, open its page and select Agree and access repository before you continue. Then, on A, log in to Hugging Face:
"$HOME/.venvs/aiperf-0.12.0/bin/hf" auth login
Next, download and prepare the dataset. See the official AIPerf guide for details:
SPEED_BENCH_DIR="$HOME/datasets/speed-bench"
mkdir -p "$SPEED_BENCH_DIR"
curl -fLsS https://raw.githubusercontent.com/NVIDIA-NeMo/Skills/refs/heads/main/nemo_skills/dataset/speed-bench/prepare.py | \
"$HOME/.venvs/aiperf-0.12.0/bin/python" - --config qualitative --output_dir "$SPEED_BENCH_DIR"
On A, check the dataset and service. Continue when the check prints BENCHMARK_READY:
(
set -euo pipefail
source ~/glm53.env
curl -fsS "http://$HEAD_IP:$PORT/health" >/dev/null
"$HOME/.venvs/aiperf-0.12.0/bin/python" - <<'PY_CHECK'
import json
from pathlib import Path
root = Path.home()
path = root / "datasets/speed-bench/qualitative.jsonl"
assert path.exists(), "Dataset missing: rerun the SPEED-Bench preparation"
rows = [json.loads(line) for line in path.read_text().splitlines() if line.strip()]
coding = [row for row in rows if row["category"] == "coding"]
assert len(coding) == 80, "Expected 80 Coding conversations"
assert sum(len(row["messages"]) for row in coding) == 89
assert all("FULL BENCHMARK DATA SHOULD BE FETCHED" not in message["content"]
for row in coding for message in row["messages"])
print("BENCHMARK_READY: dataset has 80 Coding conversations, 89 turns in total.")
PY_CHECK
)
The full sweep covers concurrency 1, 8, 16, and 32. At each level, AIPerf sends 3×C warmup requests, then measures 10×C requests, where C is the concurrency. Some conversations are sent more than once at higher concurrency.
Benchmark requests use the model’s default reasoning effort (max) and an 8,192-token output cap; responses can finish before reaching it. Full sweeps can take several hours.
A two-hour request timeout leaves room for long responses, --server-metrics collects server counters used to measure MTP acceptance, and --export-level raw keeps each response for inspection.
For a quick test, run the block below on A with both containers running and no other API traffic. It tests C1 and C8 with prefix caching enabled, as in the reference measurements in Table 2, below.
(
set -euo pipefail
source ~/glm53.env
CODING_DATASET="$HOME/datasets/speed-bench/qualitative.jsonl"
test -f "$CODING_DATASET"
export CONCURRENCIES=${CONCURRENCIES:-"1 8"} # Quick run: C1 and C8.
# Optional full benchmark: follow the reproduction instructions below.
# Step 3: --max-num-seqs 16 -> export CONCURRENCIES="1 8 16"
# Step 3: --max-num-seqs 32 -> export CONCURRENCIES=32
RUN_DIR="$HOME/aiperf-results/blog-coding-$(date +%Y%m%d-%H%M%S)"
for C in $CONCURRENCIES; do
ART="$RUN_DIR/c$C"
env -u HF_HUB_OFFLINE -u TRANSFORMERS_OFFLINE \
"$HOME/.venvs/aiperf-0.12.0/bin/aiperf" profile \
-m "$MODEL_REPO" --url "http://$HEAD_IP:$PORT" \
--endpoint-type chat --streaming --tokenizer "$MODEL_HOST" \
--custom-dataset-type speed_bench_coding --input-file "$CODING_DATASET" \
--request-count "$((10 * C))" --warmup-request-count "$((3 * C))" \
--osl 8192 \
--use-server-token-count --concurrency "$C" --random-seed 42 \
--request-timeout-seconds 7200 \
--server-metrics "http://$HEAD_IP:$PORT/metrics" \
--export-level raw --artifact-dir "$ART"
printf "Results: %s\n" "$ART"
done
printf "Results directory: %s\n" "$RUN_DIR"
)
AIPerf saves metrics, raw responses, and logs in the results directory. Warmup requests are excluded from the reported metrics.
Optional: Reproduce Table 2. In ~/launch-glm53.sh from Step 3 on both Stations, keep --enable-prefix-caching and set --max-num-seqs to 16 or 32 for the corresponding group shown in the comments above. Before each group, stop and remove the current containers on A, then B (docker stop --timeout 60 "$CONTAINER", followed by docker rm "$CONTAINER"). Relaunch the updated script on B, then A with bash ~/launch-glm53.sh and wait for Application startup complete. On A, run the matching export CONCURRENCIES command from the comments, then rerun the AIPerf block.
To test without prefix caching, replace --enable-prefix-caching with --no-enable-prefix-caching in the Step 3 script on both Stations, then recreate the containers as above. Performance may differ from Table 2.
Figure 2 compares overall output throughput across measured requests with mean per-user throughput. Token counts include reasoning.

Table 2, below, shows how throughput and latency change as more requests share the service.
| Configured concurrency | Overall throughput (tok/s) | Mean request latency (s) | Mean TTFT (ms) | Mean inter-token latency (ms/token) |
| 1 | 107.4 | 49.84 | 479.75 | 8.78 |
| 8 | 386.0 | 114.17 | 433.79 | 19.01 |
| 16 | 607.8 | 136.46 | 482.90 | 23.77 |
| 32 | 385.0 | 453.54 | 899.36 | 77.90 |
Table 2. SPEED-Bench Coding results with prefix caching enabled, four MTP draft tokens per step, and an FP8 KV cache. Latency metrics are means over measured requests; warmup is excluded. TTFT measures time to the first token, which may be reasoning
Overall throughput peaks at 607.8 tok/s at C16. It falls to 385.0 tok/s at C32, where mean request latency is 453.5 s. Higher concurrency also reduces mean per-user throughput, from 115.3 tok/s at C1 to 13.0 tok/s at C32.
After the requests finish, stop the vLLM container on A, then on B:
docker stop --timeout 60 "$CONTAINER"
docker inspect --format \
'Status={{.State.Status}} Running={{.State.Running}}' "$CONTAINER"
nvidia-smi
The status should read Status=exited Running=false, with no vLLM processes in nvidia-smi; the checkpoint, containers, and results stay on disk. To resume, run docker start "$CONTAINER" on B, then A, and repeat the Step 4 checks.
Troubleshoot common blockers
- Missing variables or SSH failure:
source ~/glm53.envafter reconnecting; verify A-to-B key authentication and B’s fingerprint. - SPEED-Bench preparation fails with a gated-dataset error: sign in to Hugging Face with the same account, open the cais/hle page, select Agree and access repository, then rerun the preparation.
- Startup or RDMA check failure: inspect
docker logs "$CONTAINER"on both nodes; verify the checkpoint and the GID, and rerun the playbook’s validation (script 07). - CUDA out of memory: lower
--max-model-lenor concurrency, then retest.
Put the endpoint to work
You now have a 753-billion-parameter model running behind one local API on two NVIDIA DGX Stations, with every weight in GPU memory. That opens up work that’s hard to do on a hosted endpoint: running inference on sensitive or proprietary data, prototyping agents locally, and iterating on prompts without per-token costs.
Connect your own application using the vLLM serving documentation, then rerun AIPerf with your prompts to find the concurrency that fits your workload. A frontier-scale model is now ready to build on, right from your desk.