File size: 7,047 Bytes
8913aea
 
2001ca9
 
 
 
 
 
8913aea
50b9bdb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187ab00
50b9bdb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187ab00
50b9bdb
 
187ab00
50b9bdb
 
 
187ab00
50b9bdb
 
 
 
 
f6589a4
50b9bdb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187ab00
50b9bdb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
00e8f64
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
---
license: mit
tags:
- vllm
- inference-optimization
- benchmarking
- moe
- rocm
---
<!-- TODO: add the upstream model's `license:` value to the frontmatter above before publishing. -->

# GLM-5.2 — Tuned vLLM Serving Recipe (ROCm)

Docker build, launch script, and benchmark-reproduction script for serving
[zai-org/GLM-5.2](https://huggingface.co/zai-org/GLM-5.2) with vLLM on AMD
Instinct GPUs.

This repository does not contain model weights. `vllm serve` pulls them
directly from the upstream repo above (and caches them) when the server
starts.

## Model Overview

- MoE causal LM using MLA (multi-head latent attention) with dynamic sparse attention
- Served at 65,536 max context; requires `--trust-remote-code`
- Workload is prefill-dominated and compute-bound

## Environment

| | |
|---|---|
| Hardware | 8x AMD Instinct MI325X 256GB, single node |
| Software | ROCm 7.2.3 · vLLM v0.25.1+rocm723 |
| Input Length | 409–3,686 tokens · nominal 2,048 · uniform |
| Output Length | 51–460 tokens · nominal 256 · uniform |
| Framework | vllm bench serve |
| vLLM | 0.25.1 |
| Dataset | random (synthetic) · range ratio 0.8 |
| Base Docker image | `vllm/vllm-openai-rocm:v0.25.1` |
| Parallelism | Tensor-parallel 8, pipeline-parallel 1, `mp` distributed executor |
| Attention | `ROCM_AITER_MLA_SPARSE` decode backend, `ROCM_AITER_FA` MLA prefill |
| Features | AITER kernels, FP8 KV cache, prefix caching, async scheduling, CUDA graphs |
| Prompts Measured | 1,000 · warm-ups excluded: 10 |
| Request Rate | inf |
| Tested Concurrency | 512 |

## Optimization Changes

- FP8 (e4m3) KV cache
- `--gpu-memory-utilization` lowered 0.95 → 0.90 to leave headroom for graph capture
- `--max-num-seqs 256`, `--max-num-batched-tokens 16384`
- `--long-prefill-token-threshold 4096` so long prefills stop blocking decode
- Enabled prefix caching and async scheduling
- `--compilation-config` with `cudagraph_mode FULL_AND_PIECEWISE`, max capture size 256
- `VLLM_ROCM_USE_AITER=1`, `VLLM_WORKER_MULTIPROC_METHOD=spawn`
- Speculative decoding deliberately omitted — MTP is the only speculator shipped with GLM-5.2, and it deadlocks on gfx942 at TP8 ([vllm-project/vllm#48568](https://github.com/vllm-project/vllm/issues/48568))

### Docker

### Installation

Build the image:

```bash
docker build -t your-org/glm-5.2-vllm:latest .
```

Run it (ROCm containers use `--device`/`--group-add`, not `--gpus`):

```bash
docker run \
  --device=/dev/kfd --device=/dev/dri \
  --group-add video --ipc=host --shm-size 16g \
  --security-opt seccomp=unconfined \
  -p 8000:8000 \
  -v ~/.cache/huggingface:/root/.cache/huggingface \
  your-org/glm-5.2-vllm:latest optimized
```

Pass `baseline` instead of `optimized` as the final argument to run the
unoptimized configuration used for the comparison below.

### Baseline

```bash
export VLLM_ROCM_USE_AITER=1
vllm serve zai-org/GLM-5.2 \  --tensor-parallel-size 8 \  --trust-remote-code \  --gpu-memory-utilization 0.95 \  --max-model-len 65536
```

### Optimized

```bash
xport VLLM_ROCM_USE_AITER=1
export VLLM_WORKER_MULTIPROC_METHOD=spawn
vllm serve zai-org/GLM-5.2 \  --host 0.0.0.0 --port 8000 \  --tensor-parallel-size 8 \  --pipeline-parallel-size 1 \  
--distributed-executor-backend mp \  --max-model-len 65536 \  --gpu-memory-utilization 0.90 \  --kv-cache-dtype fp8_e4m3 \  --max-num-seqs 256 \  --max-num-batched-tokens 16384 \  --long-prefill-token-threshold 4096 \  
--enable-prefix-caching \  --async-scheduling \  --compilation-config ‘{“cudagraph_mode”: “FULL_AND_PIECEWISE”, “max_cudagraph_capture_size”: 256}’ \  --trust-remote-code
```

### Validation

Once the container is up, confirm the server is healthy and serving:

```bash
curl -sf http://localhost:8000/health && echo "server is healthy"

curl -s http://localhost:8000/v1/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "zai-org/GLM-5.2", "prompt": "The capital of France is", "max_tokens": 16}'
```

### Files in this repository

| File | Purpose |
|---|---|
| `Dockerfile` | `vllm/vllm-openai-rocm:v0.25.1`-based image with the two scripts below baked in |
| `launch_server.sh` | Starts `vllm serve` with the baseline or optimized flags |
| `run_benchmark.sh` | Starts baseline + optimized servers, benchmarks each, and prints the comparison table below — fully automated |
| `README.md` | This model card |

### Shell Script

### Launch Server

```bash
./launch_server.sh optimized   # or: ./launch_server.sh baseline
```

This is also the image's default command — `docker run ... <image> optimized`
runs the same thing. Inside a running container, invoke it with
`docker exec -it <container> ./launch_server.sh <mode>`.

### Reproduce Benchmark

This is a separate container invocation from "Launch Server" above — it
overrides the entrypoint so `run_benchmark.sh` (not `launch_server.sh`)
drives the container. It starts the baseline server itself, benchmarks it,
shuts it down, starts the optimized server, benchmarks that, shuts it down,
and prints the comparison table — nothing to copy in by hand:

```bash
docker run \
  --device=/dev/kfd --device=/dev/dri \
  --group-add video --ipc=host --shm-size 16g \
  --security-opt seccomp=unconfined \
  -v ~/.cache/huggingface:/root/.cache/huggingface \
  --entrypoint ./run_benchmark.sh \
  your-org/glm-5.2-vllm:latest both
```

That prints the full table in the terminal when it finishes (large model,
8-GPU node — expect this to take a while: two model loads plus two
1,000-prompt runs). Pass `baseline` or `optimized` instead of `both` to run
just one side. The raw per-run JSON (`benchmark_baseline.json`,
`benchmark_optimized.json`) and each server's startup log
(`server_baseline.log`, `server_optimized.log`) are left in the container's
`/workspace` if you want them too:

```bash
docker cp <container>:/workspace/benchmark_optimized.json .
```

**Methodology** (`vllm bench serve`):

- Dataset: `random` (synthetic), range ratio 0.8
- Input length: 409–3,686 tokens (nominal 2,048, uniform)
- Output length: 51–460 tokens (nominal 256, uniform)
- 1,000 measured prompts, 10 warm-up prompts (discarded, not saved)
- Request rate: unbounded (`inf`) — max concurrency is the only throttle
- Max concurrency: 512 (best command tuned at this concurrency; override with `-e MAX_CONCURRENCY=...`)

## Results

8x AMD Instinct MI325X 256GB, vLLM v0.25.1+rocm723. This is exactly the
table `run_benchmark.sh both` prints — running it is the only step required
to reproduce it:

| Metric | Baseline | Optimized |
|---|---|---|
| Request waiting (peak) | 491 | 478 | 
| Request waiting (avg) | 169.5 | 95.7 | 
| Output tok/s | 1019.0 | 1292.2 | 
| TTFT mean (ms) | 49744 | 23744 | 
| TTFT median (ms) | 51841 | 11448 | 
| TTFT p99 (ms) | 104700 | 77224 | 
| TPOT mean (ms) | 269.7 | 294.2 | 
| TPOT p99 (ms) | 524.6 | 744.4 | 
| ITL mean (ms) | 258.1 | 272.2 | 
| ITL p99 (ms) | 738.9 | 1079.2 | 
| E2E median (ms) | 110457 | 85579 | 
| KV cache peak | 100% | 100% | 
| Duration (s) | 254.8 | 200.9 |