Skip to content

Commit b3e3cb8

Browse files
authored
[Doc] Add V100 (CC 7.0) legacy GPU deployment tutorial (#973)
Signed-off-by: AmitChaubey <amit.katyayana@gmail.com>
1 parent aef9df4 commit b3e3cb8

4 files changed

Lines changed: 418 additions & 0 deletions

File tree

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# Tutorial: Deploying on Tesla V100 and Other Legacy (Volta, CC 7.0) GPUs
2+
3+
## Introduction
4+
5+
This tutorial is for users who run the vLLM Production Stack on **Volta-generation NVIDIA GPUs** such as the Tesla V100 (CUDA Compute Capability 7.0). Newer prebuilt vLLM images (`vllm/vllm-openai:latest`, vLLM v0.9+) **dropped support for Compute Capability below 8.0**, so a V100 deployment that works on Ampere/Hopper out of the box will fail on Volta with errors like:
6+
7+
```plaintext
8+
RuntimeError: CUDA error: no kernel image is available for execution on the device
9+
```
10+
11+
The failures are silent or cryptic, and none of them are documented in the existing tutorials. This guide walks through the exact image pin and flags needed to get single-GPU and multi-GPU (tensor-parallel) deployments running on V100 hardware, plus a reference table of the errors we hit and how we fixed them.
12+
13+
All configurations here were validated on a bare-metal OpenShift (OKD 4.19, Kubernetes v1.32.8) cluster with `vllm-stack-0.1.11`, on Tesla V100 SXM2 16GB nodes, serving `Qwen2.5-0.5B/7B/14B-Instruct`.
14+
15+
## Table of Contents
16+
17+
- [Tutorial: Deploying on Tesla V100 and Other Legacy (Volta, CC 7.0) GPUs](#tutorial-deploying-on-tesla-v100-and-other-legacy-volta-cc-70-gpus)
18+
- [Introduction](#introduction)
19+
- [Table of Contents](#table-of-contents)
20+
- [Prerequisites](#prerequisites)
21+
- [Why V100 Needs Special Handling](#why-v100-needs-special-handling)
22+
- [Step 1: Single GPU Deployment (TP=1)](#step-1-single-gpu-deployment-tp1)
23+
- [Step 2: Multi-GPU Deployment (TP=2 and TP=4)](#step-2-multi-gpu-deployment-tp2-and-tp4)
24+
- [Step 3: Common Errors and Fixes](#step-3-common-errors-and-fixes)
25+
- [Step 4: Benchmark Reference Numbers](#step-4-benchmark-reference-numbers)
26+
- [Conclusion](#conclusion)
27+
28+
## Prerequisites
29+
30+
- A Kubernetes environment with GPU support, as set up in the [00-install-kubernetes-env tutorial](00-install-kubernetes-env.md).
31+
- One or more nodes with **Tesla V100 (or other Volta, CC 7.0) GPUs**, with the NVIDIA device plugin and `nvidia` runtime class installed.
32+
- Helm installed on your system.
33+
- Access to a HuggingFace token (`HF_TOKEN`) if your model is gated. The Qwen2.5 models used here are public.
34+
- Familiarity with [01-minimal-helm-installation.md](01-minimal-helm-installation.md) and [02-basic-vllm-config.md](02-basic-vllm-config.md).
35+
36+
## Why V100 Needs Special Handling
37+
38+
The Tesla V100 is a Volta GPU with **CUDA Compute Capability 7.0**. Modern vLLM builds and kernels increasingly target Ampere (CC 8.0) and newer, which leads to several hard constraints on Volta:
39+
40+
- **No native BF16 datapath.** Volta has no hardware `bfloat16` support. If vLLM loads a model with the default `dtype=bfloat16`, kernel launches fail with `no kernel image is available for execution on the device`. You must run in FP16 by setting `dtype: "half"`.
41+
- **No FP8.** FP8 weights/KV cache (Hopper-era features) are unavailable on Volta. Stick to FP16.
42+
- **vLLM V1 engine assumes CC ≥ 8.0.** The newer V1 engine and its CUDA-graph / FlashAttention paths are not reliable on Volta. You must fall back to the legacy **V0 engine** (`v0: "1"`, which the chart maps to `VLLM_USE_V1=0`).
43+
- **Attention backend.** On Volta the supported attention path is XFormers (FlashAttention-2/3 require Ampere+). The V0 engine selects an appropriate Volta-compatible backend automatically once you are on the pinned image and FP16.
44+
- **The `latest` image dropped Volta.** As of vLLM **v0.9+**, the published `vllm/vllm-openai:latest` image no longer ships kernels compiled for CC < 8.0. You must **pin the image tag to `v0.8.5`**, the last release with Volta-compatible CUDA kernels.
45+
46+
Putting it together, every V100 deployment in this tutorial pins `tag: "v0.8.5"`, sets `dtype: "half"`, forces `v0: "1"`, passes `--enforce-eager` (CUDA-graph capture is unstable on Volta with this image), and sets `VLLM_ENABLE_CUDA_COMPATIBILITY=1` to allow PTX JIT for forward compatibility.
47+
48+
## Step 1: Single GPU Deployment (TP=1)
49+
50+
Start with the smallest model to confirm the image and flags are correct before scaling out. The example file is [`tutorials/assets/values-25-v100-single-gpu.yaml`](assets/values-25-v100-single-gpu.yaml), serving `Qwen2.5-0.5B-Instruct` on a single V100.
51+
52+
```yaml
53+
servingEngineSpec:
54+
runtimeClassName: "nvidia"
55+
strategy:
56+
type: Recreate
57+
modelSpec:
58+
- name: "qwen05b"
59+
repository: "vllm/vllm-openai"
60+
tag: "v0.8.5"
61+
modelURL: "Qwen/Qwen2.5-0.5B-Instruct"
62+
replicaCount: 1
63+
requestCPU: 6
64+
requestMemory: "16Gi"
65+
requestGPU: 1
66+
pvcStorage: "20Gi"
67+
pvcAccessMode:
68+
- ReadWriteOnce
69+
vllmConfig:
70+
dtype: "half"
71+
v0: "1"
72+
tensorParallelSize: 1
73+
maxModelLen: 16384
74+
gpuMemoryUtilization: 0.9
75+
extraArgs:
76+
- "--enforce-eager"
77+
env:
78+
- name: VLLM_ENABLE_CUDA_COMPATIBILITY
79+
value: "1"
80+
nodeSelectorTerms:
81+
- matchExpressions:
82+
- key: "kubernetes.io/hostname"
83+
operator: "In"
84+
values:
85+
- "gpu-node-01"
86+
```
87+
88+
**Why each V100-specific field is needed:**
89+
90+
- **`tag: "v0.8.5"`** — the last image with kernels compiled for Compute Capability 7.0. `latest` (v0.9+) will not run on Volta.
91+
- **`vllmConfig.dtype: "half"`** — Volta has no BF16 hardware. Forcing FP16 avoids the `no kernel image available` crash you get from the default `bfloat16`.
92+
- **`vllmConfig.v0: "1"`** — selects the legacy V0 engine (the chart sets `VLLM_USE_V1=0`). The V1 engine assumes CC ≥ 8.0.
93+
- **`vllmConfig.tensorParallelSize: 1`** — a single 16GB V100 is enough for the 0.5B model.
94+
- **`extraArgs: ["--enforce-eager"]`** — disables CUDA-graph capture, which is unreliable on Volta with this image. The chart has no `enforceEager` field, so this must be passed through `extraArgs`.
95+
- **`env: VLLM_ENABLE_CUDA_COMPATIBILITY=1`** — enables PTX JIT so kernels can run on Volta via CUDA forward compatibility.
96+
- **`nodeSelectorTerms`** — pins the pod to a known V100 host via node affinity. Use `nodeSelectorTerms`, **not** `nodeSelector`; the chart only renders `nodeSelectorTerms`.
97+
98+
Deploy it:
99+
100+
```bash
101+
helm repo add vllm https://vllm-project.github.io/production-stack
102+
helm install vllm vllm/vllm-stack -f tutorials/assets/values-25-v100-single-gpu.yaml
103+
```
104+
105+
Verify the pod is running and check the logs to confirm the V0 engine and FP16 are active:
106+
107+
```bash
108+
kubectl get pods
109+
kubectl logs -f <vllm-pod-name> | grep -Ei "dtype|engine|compute capability"
110+
```
111+
112+
Refer to Step 3 in [01-minimal-helm-installation.md](01-minimal-helm-installation.md) for querying the deployed service.
113+
114+
## Step 2: Multi-GPU Deployment (TP=2 and TP=4)
115+
116+
Models that do not fit in a single 16GB V100 must be sharded across GPUs with tensor parallelism. A 7B model in FP16 needs ~14GB just for weights (leaving no room for the KV cache), so it needs **TP=2** minimum; a 14B model needs **TP=4**.
117+
118+
The example files are [`tutorials/assets/values-25-v100-multi-gpu-tp2.yaml`](assets/values-25-v100-multi-gpu-tp2.yaml) (`Qwen2.5-7B-Instruct`, TP=2) and [`tutorials/assets/values-25-v100-multi-gpu-tp4.yaml`](assets/values-25-v100-multi-gpu-tp4.yaml) (`Qwen2.5-14B-Instruct`, TP=4).
119+
120+
TP=2 snippet:
121+
122+
```yaml
123+
servingEngineSpec:
124+
runtimeClassName: "nvidia"
125+
strategy:
126+
type: Recreate
127+
modelSpec:
128+
- name: "qwen7b"
129+
repository: "vllm/vllm-openai"
130+
tag: "v0.8.5"
131+
modelURL: "Qwen/Qwen2.5-7B-Instruct"
132+
replicaCount: 1
133+
requestCPU: 8
134+
requestMemory: "32Gi"
135+
requestGPU: 2
136+
pvcStorage: "50Gi"
137+
pvcAccessMode:
138+
- ReadWriteOnce
139+
vllmConfig:
140+
dtype: "half"
141+
v0: "1"
142+
tensorParallelSize: 2
143+
maxModelLen: 8192
144+
gpuMemoryUtilization: 0.9
145+
extraArgs:
146+
- "--enforce-eager"
147+
env:
148+
- name: VLLM_ENABLE_CUDA_COMPATIBILITY
149+
value: "1"
150+
shmSize: "4Gi"
151+
nodeSelectorTerms:
152+
- matchExpressions:
153+
- key: "kubernetes.io/hostname"
154+
operator: "In"
155+
values:
156+
- "gpu-node-01"
157+
```
158+
159+
In addition to the single-GPU requirements, multi-GPU adds the following:
160+
161+
- **`tensorParallelSize` must live in `vllmConfig`, NOT in `extraArgs`.** This is the easiest mistake to make. The chart renders `vllmConfig.tensorParallelSize` into the `--tensor-parallel-size` flag for you. If you instead add `--tensor-parallel-size` (or `--tensor_parallel_size`) to `extraArgs`, the value is **silently ignored**, tensor parallelism stays at 1, and the pod then OOMs while loading the model. Always set it in `vllmConfig`.
162+
- **`shmSize: "4Gi"`** — tensor-parallel all-reduce uses NCCL, which communicates between the per-GPU worker processes through `/dev/shm`. The Kubernetes default `/dev/shm` (64Mi) is too small for multi-GPU NCCL and causes init hangs or bus errors. The chart already mounts a 20Gi `tmpfs` whenever `tensorParallelSize` is set, so the default is sufficient; here we pin a smaller, resource-friendly `4Gi` that is still plenty for these models.
163+
- **`strategy.type: Recreate`** — set at the `servingEngineSpec` level. This is required on GPU nodes. With the default `RollingUpdate`, on a redeploy Kubernetes creates the new pod **before** terminating the old one. The new pod then waits for GPUs the old pod still holds, while the old pod is not torn down until the new one is `Ready` — a **deadlock** that leaves both pods `Pending` until you manually delete the old one. `Recreate` tears the old pod down first, releasing the GPUs.
164+
165+
For **TP=4**, the same pattern applies with `requestGPU: 4` and `tensorParallelSize: 4` (see the TP=4 asset file).
166+
167+
**NVLink topology awareness.** Tensor parallelism generates all-reduce traffic on every layer, so inter-GPU bandwidth matters. Place the parallel group on directly NVLink-connected GPUs:
168+
169+
- On our cluster, `gpu-node-01` has 3x V100 in an NVLink "NV2" triangle, so any two GPUs on that host are directly NVLink-connected — a good fit for a **TP=2** pair.
170+
- `gpu-node-02` has 4x V100 in a **full NVLink mesh** (every GPU connected to every other), which is what **TP=4** wants.
171+
172+
If the TP group spans GPUs that are only connected over PCIe, the all-reduce falls back to PCIe and throughput drops sharply. You can inspect the topology on a node with `nvidia-smi topo -m`.
173+
174+
Deploy and verify the same way as Step 1:
175+
176+
```bash
177+
helm install vllm vllm/vllm-stack -f tutorials/assets/values-25-v100-multi-gpu-tp2.yaml
178+
kubectl get pods
179+
```
180+
181+
## Step 3: Common Errors and Fixes
182+
183+
These are the failures we hit on our test cluster and the exact fix for each.
184+
185+
| Symptom / Error | Root Cause | Fix |
186+
| --- | --- | --- |
187+
| `RuntimeError: CUDA error: no kernel image is available for execution on the device` | Model loaded as `bfloat16` (the default); Volta has no BF16 kernels. | Set `vllmConfig.dtype: "half"`. |
188+
| `CUDA error: invalid device function` | Using `vllm/vllm-openai:latest` (v0.9+), which dropped CC < 8.0 kernels. | Pin `tag: "v0.8.5"`. |
189+
| Pod OOM / killed while loading a 7B+ model on a single GPU | The model does not fit in 16GB of V100 memory at TP=1. | Use TP=2 (7B) or TP=4 (14B) via `requestGPU` + `tensorParallelSize`. |
190+
| Pod scheduled on the wrong (non-V100) node | Used `nodeSelector`, which this chart does not render. | Use `nodeSelectorTerms` (node affinity) instead. |
191+
| Both old and new pods stuck `Pending` after a redeploy | Default `RollingUpdate` starts the new pod before the old one frees its GPUs — GPU deadlock. | Set `servingEngineSpec.strategy.type: Recreate`. |
192+
| Engine reports `tensor_parallel_size=1` even though you set it | `--tensor-parallel-size` was placed in `extraArgs`, where it is silently ignored. | Set `tensorParallelSize` inside `vllmConfig`. |
193+
| NCCL init hang or bus error on multi-GPU startup | `/dev/shm` too small (Kubernetes default 64Mi). | Set `shmSize` (e.g. `4Gi`); the chart's `20Gi` default also works. |
194+
195+
## Step 4: Benchmark Reference Numbers
196+
197+
The following throughput numbers are from real runs on our V100 SXM2 16GB nodes with the configurations in this tutorial. Use them as a sanity check that your deployment is in the right ballpark, not as guaranteed performance.
198+
199+
| Model | TP | Approx. throughput | Notes |
200+
| --- | --- | --- | --- |
201+
| `Qwen2.5-0.5B-Instruct` | 1 | ~435 tok/s/GPU | Single V100, fits easily. |
202+
| `Qwen2.5-7B-Instruct` | 2 | ~155 tok/s/GPU | NVLink pair on `gpu-node-01`. |
203+
| `Qwen2.5-14B-Instruct` | 4 | ~56 tok/s/GPU | Full NVLink mesh on `gpu-node-02`. |
204+
205+
Per-GPU throughput drops as the tensor-parallel degree grows because all-reduce communication overhead increases with the size of the TP group — this is expected, and is exactly why NVLink topology matters.
206+
207+
For interactive serving, regardless of model size, plan for roughly **8–16 concurrent requests per deployment** before latency-sensitive (interactive SLO) requests start to degrade. Scale out with additional replicas (and additional GPUs) rather than pushing a single deployment past that range.
208+
209+
## Conclusion
210+
211+
Volta-generation GPUs like the Tesla V100 are still capable inference accelerators, but they require deliberate configuration on the modern vLLM Production Stack: pin the image to `v0.8.5`, run in FP16 (`dtype: "half"`), force the V0 engine (`v0: "1"`), use `--enforce-eager`, and set `VLLM_ENABLE_CUDA_COMPATIBILITY=1`. For multi-GPU, keep `tensorParallelSize` in `vllmConfig`, set an adequate `shmSize` for NCCL, use the `Recreate` strategy to avoid GPU deadlocks, and align the tensor-parallel group with the NVLink topology. With these settings, single-GPU and tensor-parallel deployments run reliably on CC 7.0 hardware.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Tesla V100 SXM2 16GB (Volta, CUDA Compute Capability 7.0) - Multi GPU (TP=2)
2+
# Validated on: OKD 4.19 / Kubernetes v1.32.8, vllm-stack-0.1.11
3+
# Model: Qwen/Qwen2.5-7B-Instruct
4+
#
5+
# A 7B model in FP16 needs ~14GB just for weights, which does not leave room
6+
# for the KV cache on a single 16GB V100. TP=2 shards the model across two
7+
# GPUs. Schedule on an NVLink-connected pair so the per-layer all-reduce does
8+
# not fall back to slow PCIe.
9+
#
10+
# Deploy with:
11+
# helm install vllm vllm/vllm-stack -f values-25-v100-multi-gpu-tp2.yaml
12+
servingEngineSpec:
13+
runtimeClassName: "nvidia"
14+
15+
# Recreate is REQUIRED for multi-GPU deployments. With the default
16+
# RollingUpdate, the new pod is created before the old one is deleted, so it
17+
# waits for GPUs the old pod still owns while the old pod waits to be
18+
# evicted - a deadlock that leaves both pods Pending until manual deletion.
19+
strategy:
20+
type: Recreate
21+
22+
modelSpec:
23+
- name: "qwen7b"
24+
# Pin to v0.8.5 - the last image with Volta (CC 7.0) CUDA kernels.
25+
repository: "vllm/vllm-openai"
26+
tag: "v0.8.5"
27+
modelURL: "Qwen/Qwen2.5-7B-Instruct"
28+
replicaCount: 1
29+
30+
requestCPU: 8
31+
requestMemory: "32Gi"
32+
requestGPU: 2
33+
34+
pvcStorage: "50Gi"
35+
pvcAccessMode:
36+
- ReadWriteOnce
37+
38+
vllmConfig:
39+
# FP16 - V100 (CC 7.0) has no native BF16 datapath.
40+
dtype: "half"
41+
# Legacy V0 engine - the V1 engine needs CC >= 8.0 kernels.
42+
v0: "1"
43+
# tensorParallelSize MUST live in vllmConfig. The chart renders this into
44+
# the --tensor-parallel-size flag. Passing it via extraArgs instead
45+
# silently falls back to TP=1 and the pod then OOMs loading a 7B model.
46+
tensorParallelSize: 2
47+
maxModelLen: 8192
48+
gpuMemoryUtilization: 0.9
49+
# Disable CUDA graphs - unstable on Volta with the v0.8.5 image.
50+
extraArgs:
51+
- "--enforce-eager"
52+
53+
env:
54+
# CUDA forward compatibility (PTX JIT) for Volta.
55+
- name: VLLM_ENABLE_CUDA_COMPATIBILITY
56+
value: "1"
57+
58+
# shmSize backs the /dev/shm (tmpfs) that NCCL uses for inter-GPU
59+
# shared-memory transport during tensor-parallel all-reduce. The Kubernetes
60+
# default (64Mi) causes NCCL bus errors. The chart already defaults this to
61+
# 20Gi when tensorParallelSize is set; we pin a smaller, resource-friendly
62+
# 4Gi that is still plenty.
63+
shmSize: "4Gi"
64+
65+
# gpu-node-01 has 3x V100 in an NVLink "NV2" triangle, so any two GPUs on
66+
# this host are directly NVLink-connected - ideal for a TP=2 pair.
67+
nodeSelectorTerms:
68+
- matchExpressions:
69+
- key: "kubernetes.io/hostname"
70+
operator: "In"
71+
values:
72+
- "gpu-node-01"
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Tesla V100 SXM2 16GB (Volta, CUDA Compute Capability 7.0) - Multi GPU (TP=4)
2+
# Validated on: OKD 4.19 / Kubernetes v1.32.8, vllm-stack-0.1.11
3+
# Model: Qwen/Qwen2.5-14B-Instruct
4+
#
5+
# A 14B model in FP16 needs ~28GB for weights alone, so it must be sharded
6+
# across at least 4x 16GB V100. TP=4 generates 4-way all-reduce traffic on
7+
# every layer, so schedule on a host where all four GPUs share a full NVLink
8+
# mesh; otherwise PCIe becomes the bottleneck.
9+
#
10+
# Deploy with:
11+
# helm install vllm vllm/vllm-stack -f values-25-v100-multi-gpu-tp4.yaml
12+
servingEngineSpec:
13+
runtimeClassName: "nvidia"
14+
15+
# Recreate is REQUIRED for multi-GPU. RollingUpdate deadlocks on GPU nodes:
16+
# the new pod cannot get GPUs the old pod still holds, and the old pod is
17+
# not torn down until the new one is Ready.
18+
strategy:
19+
type: Recreate
20+
21+
modelSpec:
22+
- name: "qwen14b"
23+
# Pin to v0.8.5 - the last image with Volta (CC 7.0) CUDA kernels.
24+
repository: "vllm/vllm-openai"
25+
tag: "v0.8.5"
26+
modelURL: "Qwen/Qwen2.5-14B-Instruct"
27+
replicaCount: 1
28+
29+
requestCPU: 16
30+
requestMemory: "64Gi"
31+
requestGPU: 4
32+
33+
pvcStorage: "80Gi"
34+
pvcAccessMode:
35+
- ReadWriteOnce
36+
37+
vllmConfig:
38+
# FP16 - V100 (CC 7.0) has no native BF16 datapath.
39+
dtype: "half"
40+
# Legacy V0 engine - the V1 engine needs CC >= 8.0 kernels.
41+
v0: "1"
42+
# tensorParallelSize MUST be in vllmConfig (renders --tensor-parallel-size).
43+
# If placed in extraArgs it is silently ignored, TP stays at 1, and the
44+
# 14B model cannot fit on one GPU.
45+
tensorParallelSize: 4
46+
maxModelLen: 8192
47+
gpuMemoryUtilization: 0.9
48+
# Disable CUDA graphs - unstable on Volta with the v0.8.5 image.
49+
extraArgs:
50+
- "--enforce-eager"
51+
52+
env:
53+
# CUDA forward compatibility (PTX JIT) for Volta.
54+
- name: VLLM_ENABLE_CUDA_COMPATIBILITY
55+
value: "1"
56+
57+
# /dev/shm (tmpfs) size for 4-way NCCL shared-memory transport. The
58+
# Kubernetes default (64Mi) is too small; the chart defaults to 20Gi when
59+
# tensorParallelSize is set. We pin a smaller, resource-friendly 4Gi that
60+
# is still sufficient.
61+
shmSize: "4Gi"
62+
63+
# gpu-node-02 has 4x V100 in a full NVLink mesh - every GPU is directly
64+
# connected to every other, which is what TP=4 needs.
65+
nodeSelectorTerms:
66+
- matchExpressions:
67+
- key: "kubernetes.io/hostname"
68+
operator: "In"
69+
values:
70+
- "gpu-node-02"

0 commit comments

Comments
 (0)