单卡 L40S,48GB 显存。公司给了一台 EKS GPU 节点,有 3 张 L40S 可用,每张卡独立部署一个模型做横向对比。目标:找出单卡 L40S 上跑 27B-35B 大模型的最优方案。最终结论——Qwen3.5-27B-FP8 单卡 18 tok/s,96% 显存利用率。这篇记录从零搭建的完整过程。
为什么是 L40S
选 GPU 之前先明确需求:我们要在一个 EKS 集群上同时跑多个 20B-35B 参数的大模型,提供 OpenAI 兼容 API,给内部多个业务系统调用。
L40S 的定位是"推理专用卡",和 A100 的区别(数据来自 NVIDIA 官方 spec sheet):
| 对比项 | L40S | A100 80GB |
|---|---|---|
| 显存 | 48GB GDDR6 | 80GB HBM2e |
| FP8 算力 | 733 TFLOPS | 不支持原生 FP8 |
| BF16 算力 | 366 TFLOPS | 312 TFLOPS |
| 显存带宽 | 864 GB/s | 2,039 GB/s |
| 每卡每小时 | ~$2.62 (g6e) | ~$2.74 (p4d) |
L40S 的优势是 FP8 原生支持:同样跑 FP8 量化模型,L40S 的算力是 A100 的两倍多。劣势是显存带宽低(864 vs 2039 GB/s),大模型推理的 decode 阶段(memory-bound)会比 A100 慢。但我们的核心目标是在有限的 GPU 性能下找出效果最好的模型,不追求极致速度。
最终用的是 g6e.12xlarge:3×L40S 可用 + 48 vCPU + 384GB 内存 + 2×1.8TB NVMe SSD。
EKS GPU 环境搭建
NVIDIA GPU Operator
不要手动装驱动。用 NVIDIA GPU Operator 一键搞定:
helm install gpu-operator nvidia/gpu-operator \
--namespace gpu-operator --create-namespace \
--set driver.enabled=true \
--set toolkit.enabled=true \
--set devicePlugin.enabled=true \
--set dcgmExporter.enabled=true
它会自动安装:驱动、Container Toolkit、Device Plugin(让 K8s 识别 GPU 资源)、DCGM Exporter(GPU 监控指标)。
验证:
kubectl get pods -n gpu-operator
nvidia-smi # 应该看到 3×L40S
NVMe 存储——别忘了挂载
g6e.12xlarge 自带 2×1.8TB NVMe SSD,但默认不挂载。模型文件动辄 20-30GB,必须用本地 NVMe 而不是 EBS。
我写了一个 DaemonSet 自动格式化和挂载:
# nvme-auto-mount DaemonSet 核心逻辑
mkfs.xfs /dev/nvme1n1 && mount /dev/nvme1n1 /data1
mkfs.xfs /dev/nvme2n1 && mount /dev/nvme2n1 /data2
坑:EKS 节点扩缩容后,新节点的 NVMe 没有挂载,模型 Pod 会卡在 PodInitializing,hostPath PV 挂载失败但不报错。必须确保 DaemonSet 先于模型 Pod 运行。
模型下载策略
模型存在 NVMe 上,用 Init Container 从 HuggingFace 镜像站下载:
initContainers:
- name: model-downloader
command:
- sh
- -c
- |
if [ -z "$(ls -A $MODEL_DIR)" ]; then
huggingface-cli download $MODEL_NAME --local-dir $MODEL_DIR
fi
坑:ls -A 检查目录非空就跳过下载。如果上次下载中断,目录存在但文件不完整,vLLM 启动时报 "Cannot find model weights"。解决方案:加一个完整性校验,或者在下载完成后写一个 .done 标记文件。
vLLM 关键配置
vLLM 是目前最成熟的开源推理框架。核心配置:
python -m vllm.entrypoints.openai.api_server \
--model $MODEL_PATH \
--served-model-name $MODEL_NAME \
--max-model-len 128000 \
--gpu-memory-utilization 0.92 \
--kv-cache-dtype fp8 \
--enable-prefix-caching \
--enable-chunked-prefill \
--tensor-parallel-size 1
逐个解释:
--kv-cache-dtype fp8:KV Cache 用 FP8 存储,显存占用减半,质量几乎无损。这是 L40S 上的必选项。--gpu-memory-utilization 0.92:vLLM 预分配 92% 显存给模型+KV Cache。留 8% 给 CUDA context。--enable-prefix-caching:相同前缀的请求共享 KV Cache,多轮对话场景提升明显。--enable-chunked-prefill:长文本分块预填充,避免单个长请求阻塞其他请求。--max-model-len:最大上下文长度。这个值直接影响 KV Cache 大小,设太大会 OOM。
enforce-eager:什么时候需要
默认情况下 vLLM 会用 CUDA Graph 加速推理。但有些模型架构不兼容:
- Qwen3.6 的 Mamba/DeltaNet 混合架构:Mamba cache 和 CUDA Graph 不兼容,必须
--enforce-eager - MoE + BF16 + Tensor Parallel:某些组合下 CUDA Graph capture 会 OOM
--enforce-eager 的代价是 TPOT 增加约 40%(Qwen3.6: 88ms vs Qwen3.5: 53ms)。
部署架构
3 张 L40S,每张卡独立部署一个模型,做横向对比:
EKS GPU Node (3×L40S 48GB)
├── GPU 0: Qwen3.6-27B-FP8 + Embedding-0.6B
├── GPU 1: Qwen3.6-35B-A3B-FP8 + Embedding-0.6B
├── GPU 2: Gemma4-31B-FP8 + Embedding-0.6B
└── LiteLLM → 统一 API 网关,路由到各模型
核心思路是单卡部署:每张卡跑一个 LLM + 一个 Embedding 模型,通过 LiteLLM 做统一路由。3 张卡 3 个模型并行跑,方便做性能和质量的横向对比。这个"一卡双模型"的方案踩了非常多坑,下一篇详细讲。
小结
搭建 GPU 推理基础设施,核心是三件事:
- GPU Operator 自动化——不要手动装驱动
- NVMe 存储——模型文件必须在本地盘,EBS 太慢
- vLLM 配置调优——
kv-cache-dtype=fp8是 L40S 上的必选项
下一篇讲多模型 GPU 共享的显存优化,那才是真正的地狱。