跳到主要内容

L40S 大模型部署实录①:单卡 48GB,能跑多大的模型?

阅读需 3 分钟

单卡 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):

对比项L40SA100 80GB
显存48GB GDDR680GB HBM2e
FP8 算力733 TFLOPS不支持原生 FP8
BF16 算力366 TFLOPS312 TFLOPS
显存带宽864 GB/s2,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 推理基础设施,核心是三件事:

  1. GPU Operator 自动化——不要手动装驱动
  2. NVMe 存储——模型文件必须在本地盘,EBS 太慢
  3. vLLM 配置调优——kv-cache-dtype=fp8 是 L40S 上的必选项

下一篇讲多模型 GPU 共享的显存优化,那才是真正的地狱。