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 | |