Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/getting_started/installation/cpu.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,19 +142,25 @@ VLLM_USE_PRECOMPILED=1 VLLM_PRECOMPILED_WHEEL_VARIANT=cpu VLLM_TARGET_DEVICE=cpu
=== "IBM Z (S390X)"
--8<-- "docs/getting_started/installation/cpu.s390x.inc.md:build-image-from-source"

## AMD Zen optimizations {#amd-zen-optimizations}

--8<-- "docs/getting_started/installation/cpu.x86.inc.md:amd-zen-optimizations"

## Related runtime environment variables

- `VLLM_CPU_KVCACHE_SPACE`: specify the KV Cache size (e.g, `VLLM_CPU_KVCACHE_SPACE=40` means 40 GiB space for KV cache), larger setting will allow vLLM to run more requests in parallel. This parameter should be set based on the hardware configuration and memory management pattern of users. Default value is `0`.
- `VLLM_CPU_OMP_THREADS_BIND`: specify the CPU cores dedicated to the OpenMP threads, can be set as CPU id lists, `auto` (by default), or `nobind` (to disable binding to individual CPU cores and to inherit user-defined OpenMP variables). For example, `VLLM_CPU_OMP_THREADS_BIND=0-31` means there will be 32 OpenMP threads bound on 0-31 CPU cores. `VLLM_CPU_OMP_THREADS_BIND=0-31|32-63` means there will be 2 tensor parallel processes, 32 OpenMP threads of rank0 are bound on 0-31 CPU cores, and the OpenMP threads of rank1 are bound on 32-63 CPU cores. By setting to `auto`, the OpenMP threads of each rank are bound to the CPU cores in each NUMA node respectively. If set to `nobind`, the number of OpenMP threads is determined by the standard `OMP_NUM_THREADS` environment variable.
- `VLLM_CPU_NUM_OF_RESERVED_CPU`: specify the number of CPU cores which are not dedicated to the OpenMP threads for each rank. The variable only takes effect when VLLM_CPU_OMP_THREADS_BIND is set to `auto`. Default value is `None`. If the value is not set and use `auto` thread binding, no CPU will be reserved for `world_size == 1`, 1 CPU per rank will be reserved for `world_size > 1`.
- `CPU_VISIBLE_MEMORY_NODES`: specify visible NUMA memory nodes for vLLM CPU workers, similar to ```CUDA_VISIBLE_DEVICES```. The variable only takes effect when VLLM_CPU_OMP_THREADS_BIND is set to `auto`. The variable provides more control for the auto thread-binding feature, such as masking nodes and changing nodes binding sequence.
- `VLLM_CPU_SGL_KERNEL` (x86 only, Experimental): whether to use small-batch optimized kernels for linear layer and MoE layer, especially for low-latency requirements like online serving. The kernels require AMX instruction set, BFloat16 weight type and weight shapes divisible by 32. Default is `0` (False).
- `VLLM_ZENTORCH_WEIGHT_PREPACK` (AMD Zen only): when `ZenCpuPlatform` is active, eagerly prepack linear weights into ZenDNN's blocked layout at model load time, eliminating per-inference layout conversion overhead. Default is `1` (enabled). See [AMD Zen optimizations](#amd-zen-optimizations).

## FAQ

### Which `dtype` should be used?

- Currently, vLLM CPU uses model default settings as `dtype`. However, due to unstable float16 support in torch CPU, it is recommended to explicitly set `dtype=bfloat16` if there are any performance or accuracy problem.
- On AMD Zen CPUs (`ZenCpuPlatform`), `float16` is **not** supported. Only `bfloat16` and `float32` are accepted; models declared with `float16` are auto-downcast to `bfloat16` at model load time. See [AMD Zen optimizations](#amd-zen-optimizations).

### How to launch a vLLM service on CPU?

Expand Down Expand Up @@ -227,6 +233,25 @@ By providing MODEL_FILTER and DTYPE_FILTER, only commands for related model ID a
ON_CPU=1 SERVING_JSON=serving-tests-cpu-text.json DRY_RUN=1 MODEL_FILTER=meta-llama/Llama-3.1-8B-Instruct DTYPE_FILTER=bfloat16 bash .buildkite/performance-benchmarks/scripts/run-performance-benchmarks.sh
```

### How do I enable AMD Zen optimizations? {#how-do-i-enable-amd-zen-optimizations}

On an AMD Zen 4 / Zen 5 CPU, install the CPU wheel with the `zen` extra so vLLM pulls the tested `zentorch` version for that release:

```bash
export VLLM_VERSION=$(curl -s https://api.github.com/repos/vllm-project/vllm/releases/latest | jq -r .tag_name | sed 's/^v//')
uv pip install "vllm[zen]" --extra-index-url https://wheels.vllm.ai/${VLLM_VERSION}/cpu --index-strategy first-index --torch-backend cpu
```
Comment thread
tlrmchlsmth marked this conversation as resolved.

vLLM auto-detects the platform and routes linear layers through ZenDNN-optimized kernels - no flag needed. To verify it is engaged, look for the platform-selection line in the server's startup logs:

```bash
vllm serve Qwen/Qwen3-0.6B 2>&1 | grep "AMD Zen CPU detected with zentorch installed"
```

For per-backend dispatch details (which kernel each linear layer was bound to), re-run with `VLLM_LOGGING_LEVEL=DEBUG` and grep for `CPU unquantized GEMM dispatch`.

See [AMD Zen optimizations](#amd-zen-optimizations) for detection rules, supported dtypes, and the `VLLM_ZENTORCH_WEIGHT_PREPACK` knob.

### How to decide `VLLM_CPU_OMP_THREADS_BIND`?

- Default `auto` thread-binding is recommended for most cases. Ideally, each OpenMP thread will be bound to a dedicated physical core respectively, threads of each rank will be bound to the same NUMA node respectively, and 1 CPU per rank will be reserved for other vLLM components when `world_size > 1`. If you have any performance problems or unexpected binding behaviours, please try to bind threads as following.
Expand Down
47 changes: 45 additions & 2 deletions docs/getting_started/installation/cpu.x86.inc.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- markdownlint-disable MD041 -->
<!-- markdownlint-disable MD041 MD051 -->
--8<-- [start:installation]

vLLM supports basic model inferencing and serving on x86 CPU platform, with data types FP32, FP16 and BF16.
Expand Down Expand Up @@ -200,7 +200,19 @@ docker build -f docker/Dockerfile.cpu \
--target vllm-openai .
```

#### Launching the OpenAI server
#### Building with AMD Zen optimizations

For AMD Zen 4 / Zen 5 hosts (`linux/amd64` only), use the `vllm-openai-zen` target. It extends the default `vllm-openai` image and adds `zentorch` via the `vllm[zen]` extra so `ZenCpuPlatform` auto-activates at runtime:

```bash
docker build -f docker/Dockerfile.cpu \
--tag vllm-cpu-zen-env \
--target vllm-openai-zen .
```

The resulting image accepts the same arguments and environment variables as `vllm-openai` (see [Launching the OpenAI server](#launching-the-openai-server) below); no extra flag is needed to engage Zen optimizations. See [AMD Zen optimizations](cpu.md#amd-zen-optimizations) for runtime behavior and the supported-dtype caveats.

#### Launching the OpenAI server {#launching-the-openai-server}

```bash
docker run --rm \
Expand All @@ -216,5 +228,36 @@ docker run --rm \
```

--8<-- [end:build-image-from-source]
--8<-- [start:amd-zen-optimizations]

On AMD Zen CPUs, vLLM auto-selects `ZenCpuPlatform` (a subclass of `CpuPlatform`) which dispatches linear layers through [`zentorch`](https://github.com/amd/ZenDNN-pytorch-plugin)'s ZenDNN-optimized kernels. See the FAQ entry [How do I enable AMD Zen optimizations?](#how-do-i-enable-amd-zen-optimizations) for the install command.

### Detection rules

`ZenCpuPlatform` is selected when **all** of the following hold:

- vLLM is built for CPU
- `/proc/cpuinfo` reports `AuthenticAMD` and `avx512`
- `import zentorch` succeeds

Otherwise, vLLM falls back to the default `CpuPlatform` (oneDNN / sgl-kernel paths).

### Supported dtypes

`float16` is **not** supported on `ZenCpuPlatform`. `ZenCpuPlatform.supported_dtypes` advertises only `bfloat16` and `float32`, so models declared with `torch_dtype=float16` are auto-downcast to `bfloat16` at load time with the standard `"Your device 'cpu' doesn't support torch.float16. Falling back to torch.bfloat16 for compatibility."` warning emitted from `vllm/config/model.py`.

### Environment variables

- `VLLM_ZENTORCH_WEIGHT_PREPACK` (default `1`): eagerly prepacks linear weights into ZenDNN's blocked layout at model load time, eliminating per-inference layout conversion overhead. Set to `0` to disable.

### Docker

The `vllm-openai-zen` Docker target (in `docker/Dockerfile.cpu`) extends the default `vllm-openai` image with `vllm[zen]`. Build it with `docker build -f docker/Dockerfile.cpu --target vllm-openai-zen .` — see [Building with AMD Zen optimizations](#building-with-amd-zen-optimizations) for the full command and run instructions.

### Reference

For the design rationale, see [RFC #35089: In-Tree AMD Zen CPU Backend via zentorch](https://github.com/vllm-project/vllm/issues/35089).

--8<-- [end:amd-zen-optimizations]
--8<-- [start:extra-information]
--8<-- [end:extra-information]
3 changes: 3 additions & 0 deletions docs/models/hardware_supported_models/cpu.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# CPU - Intel® Xeon®

!!! note "AMD Zen CPUs"
On AMD Zen 4 / Zen 5 CPUs, AMD Zen optimizations are auto-enabled when the [`zentorch`](https://github.com/amd/ZenDNN-pytorch-plugin) package is installed. All models supported by vLLM on CPU are supported on AMD Zen as well; model compatibility does not change. This page reflects the current CPU reference validation matrix on Intel systems. See [AMD Zen optimizations](../../getting_started/installation/cpu.md#amd-zen-optimizations) for details.

## Validated Hardware

| Hardware |
Expand Down
23 changes: 23 additions & 0 deletions tests/model_executor/test_cpu_unquantized_gemm_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,26 @@ def test_dispatch_cpu_unquantized_gemm_zen_remove_weight(monkeypatch):
utils.dispatch_cpu_unquantized_gemm(layer, remove_weight=True)

assert layer.weight.numel() == 0


@pytest.mark.usefixtures("_mock_zentorch_linear_unary")
def test_dispatch_cpu_unquantized_gemm_logs_zentorch_dispatch(monkeypatch):
Comment on lines +71 to +72

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be skipped if platform is not zen_cpu?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is intentional. The test is a unit test for the dispatcher logic.

It uses the _mock_zentorch_linear_unary fixture to register a fake zentorch_linear_unary op via torch.library and monkeypatches current_platform.is_zen_cpu to True. This makes the test hardware-independent by design so the dispatch path is exercised on any CI runner.

Skipping on non-Zen hardware would mean the new logging code is never validated in CI. The pattern matches the two existing tests above it (test_dispatch_cpu_unquantized_gemm_uses_zentorch_on_zen, test_dispatch_cpu_unquantized_gemm_zen_remove_weight) that were accepted in PR #35970, and the four test_zen_cpu_platform_detection.py tests (also from #35970) that mock os.path.exists + open to make /proc/cpuinfo reads hardware-independent. Verified passing locally: 4/4 in this file + 4/4 in test_zen_cpu_platform_detection.py = 8/8 PASSED.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

although, would love to get your thoughts on adding UT for logging

monkeypatch.setattr(current_platform, "is_zen_cpu", lambda: True)
expected_prepacked = bool(utils.envs.VLLM_ZENTORCH_WEIGHT_PREPACK) and hasattr(
torch.ops.zentorch, "zentorch_weight_prepack_for_linear"
)

log_calls = []
monkeypatch.setattr(
utils.logger, "debug_once", lambda *args: log_calls.append(args)
)

layer = torch.nn.Linear(16, 8, bias=True)
utils.dispatch_cpu_unquantized_gemm(layer, remove_weight=False)

assert log_calls == [
(
"CPU unquantized GEMM dispatch: using zentorch_linear_unary (prepacked=%s)",
expected_prepacked,
)
]
11 changes: 11 additions & 0 deletions vllm/model_executor/layers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,10 @@ def dispatch_cpu_unquantized_gemm(
)
if remove_weight:
layer.weight = torch.nn.Parameter(torch.empty(0), requires_grad=False)
logger.debug_once(
"CPU unquantized GEMM dispatch: using zentorch_linear_unary (prepacked=%s)",
is_prepacked,
)
return

if envs.VLLM_CPU_SGL_KERNEL and check_cpu_sgl_kernel(N, K, dtype):
Expand All @@ -285,6 +289,9 @@ def dispatch_cpu_unquantized_gemm(
)
if remove_weight:
layer.weight = torch.nn.Parameter(torch.empty(0), requires_grad=False)
logger.debug_once(
"CPU unquantized GEMM dispatch: using sgl-kernel weight_packed_linear"
)
return
elif (
ops._supports_onednn
Expand All @@ -296,6 +303,7 @@ def dispatch_cpu_unquantized_gemm(
layer.cpu_linear = lambda x, weight, bias: ops.onednn_mm(handler, x, bias)
if remove_weight:
layer.weight = torch.nn.Parameter(torch.empty(0), requires_grad=False)
logger.debug_once("CPU unquantized GEMM dispatch: using oneDNN onednn_mm")
return
except RuntimeError as e:
logger.warning_once(
Expand All @@ -307,6 +315,9 @@ def dispatch_cpu_unquantized_gemm(
layer.cpu_linear = lambda x, weight, bias: torch.nn.functional.linear(
x, weight, bias
)
logger.debug_once(
"CPU unquantized GEMM dispatch: using torch.nn.functional.linear (fallback)"
)


def cpu_unquantized_gemm(
Expand Down
2 changes: 1 addition & 1 deletion vllm/platforms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def cpu_platform_plugin() -> str | None:
try:
import zentorch # noqa: F401

logger.debug(
logger.info(
"AMD Zen CPU detected with zentorch installed, using ZenCpuPlatform."
)
return "vllm.platforms.zen_cpu.ZenCpuPlatform"
Expand Down
Loading