AI Infra 训练营
总览
  • 总览
  • 完整安装
  • 核心 K8s
  • Cilium 网络
  • Longhorn 存储
  • 监控日志
  • CI / GitOps
  • 安全准入
  • CI/CD 实战(MySQL+Go+Vue)
  • HPA/Ingress/Hubble 实战
  • 面试速查 + 真实踩坑
  • Day 0 · 新手接管 Runbook
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
命令手册
HiHuo 主站
GitHub
总览
  • 总览
  • 完整安装
  • 核心 K8s
  • Cilium 网络
  • Longhorn 存储
  • 监控日志
  • CI / GitOps
  • 安全准入
  • CI/CD 实战(MySQL+Go+Vue)
  • HPA/Ingress/Hubble 实战
  • 面试速查 + 真实踩坑
  • Day 0 · 新手接管 Runbook
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
命令手册
HiHuo 主站
GitHub
  • Day 0 · 环境与硬件

    • Day 0 新手现场接管 Runbook:先看懂,再动手
    • Day 0:5 节点裸 Ubuntu → K8s 装机基线
  • Week 1:K8s 内核 + 周边基础设施

    • Day 1:3 CP HA 集群 + CNI 选型 + DNS 调优
    • Day 2:控制面 deep dive + etcd 内核 + chaos drill
    • Day 3:CRD + Operator (kubebuilder 从 0 写)
    • Day 4:Longhorn 存储 + Cilium 二探(Hubble / NetworkPolicy / L7)
    • Day 5:PVC 在线扩容 + K8s 安全基线(RBAC / PSA / Secret 加密 / Kyverno)
    • Day 6:调度策略 + Prometheus / Loki 观测栈
    • Day 7:Harbor 私有镜像 + ArgoCD GitOps + Cilium WireGuard
  • Week 2:制品 + GitOps + AI Infra + 综合

    • Day 8:k3s 单节点 + NVIDIA Device Plugin + vLLM 跑 Qwen2.5-3B
    • Day 8(attempt 1):跨 WAN GPU 加入主集群(失败复盘)
    • Day 8:AlertManager 真接入 + PrometheusRule 实战
    • Day 8:集群内 CI 闭环 — Gitea + Jenkins + Kaniko
    • Day 9:Triton 多框架推理 + DCGM 跨集群可观测 + vLLM 实测
    • Day 10:MIG 硬切片 + AWQ 量化 + HPA Custom Metrics
    • Day 11:AI 业务端到端 —— chainlit + GitOps + 跨 WAN vLLM
    • Day 12:灾难恢复 + 生产事故注入
    • Day 13:LLM Operator + 多集群联邦 + Ambient Mesh + RAG
    • Day 14:CKA / CKS 真题演练 + 14 天技术栈横向汇总

Day 9:Triton 多框架推理 + DCGM 跨集群可观测 + vLLM 实测

Day 8 跑通了单实例 vLLM。Day 9 把推理这层做到「生产可对外」:换上多框架推理服务器(Triton),加上 GPU 跨集群可观测(DCGM Exporter → 主集群 Prometheus),最后做一次真实压测拿到数据,给 vLLM 和 Triton 的选型一个有依据的答案。

3 段独立又相互验证:

  • A — Triton model repository(ONNX + Python backend)+ 部署 yaml,遇到 16GB 镜像跨 WAN 拉不动的真坑
  • B — DCGM Exporter DaemonSet + SSH tunnel 跨 WAN + ServiceMonitor,主集群 Prometheus 实测 19 个 GPU metrics
  • C — vLLM 1 / 5 / 30 并发 benchmark,A800-40G 跑出 2901 tok/s,给 vLLM vs Triton 选型矩阵

环境拓扑

主集群在 Day 1 起的 5 节点 K8s 上(10.0.24.0/24 内网),跑 Prometheus / Grafana / AlertManager。GPU 节点是独立的 k3s 单机,A800-40G,公网 IP ***.109.239.32,跑 vLLM + Triton + DCGM Exporter。两边不在同一个 L2,只能通过公网过 SSH tunnel。GPU 节点用 k3s 是因为只一台,不值得起 3 CP HA,且 nvidia-container-runtime + k3s 路径成熟。


A. Triton Inference Server:model repo + 部署 yaml

vLLM 只能跑 LLM transformer,CV / ASR / 自定义 Python 逻辑都跑不了。生产里只要有第二种模型(哪怕只是个 embedding 模型)就得 Triton。Day 9 这一段把 Triton 的 model repo 骨架搭出来,覆盖两类典型 backend:ONNX(标准模型)+ Python(自定义逻辑)。

A.1 Model repository 目录结构

Triton 的 model repo 是约定大于配置 —— 一个目录就是一个模型,config.pbtxt 是配置,子目录 1/ / 2/ 是版本:

/opt/triton-models/
├── densenet_onnx/{config.pbtxt, labels.txt, 1/model.onnx}    # ONNX,imagenet 分类
└── simple_echo/  {config.pbtxt,             1/model.py}      # Python,自定义逻辑

版本号目录是关键设计 —— 同一个模型可以同时挂多版本,config.pbtxt 里的 version_policy 控制服务哪几个,灰度发布零停机。比起 vLLM 启动时绑死一个模型路径,Triton 这套是生产规范。

A.2 ONNX 模型配置(densenet_onnx/config.pbtxt)

name: "densenet_onnx"
platform: "onnxruntime_onnx"
max_batch_size: 8
input [
  { name: "data_0", data_type: TYPE_FP32, format: FORMAT_NCHW, dims: [ 3, 224, 224 ] }
]
output [
  { name: "fc6_1", data_type: TYPE_FP32, dims: [ 1000 ], label_filename: "labels.txt" }
]
instance_group [ { count: 1, kind: KIND_GPU } ]
dynamic_batching {
  preferred_batch_size: [ 4, 8 ]
  max_queue_delay_microseconds: 100
}

几个真正生效的字段:

  • dynamic_batching 是 Triton 杀手锏 —— 跨请求自动 batch,「100us 内攒齐 4-8 个请求一起跑」。vLLM 的 continuous batching 只针对 LLM iteration-level,dynamic batching 是通用 CV / 嵌入模型的吞吐放大器。max_queue_delay 小延迟好但 batch 小吞吐差,CV 任务通常 100-500us、对话场景几 ms。
  • format: FORMAT_NCHW 是 PyTorch / ONNX 标准(channels first),TF 默认 NHWC。模型导出时啥 layout 这里就填啥,填错了不会报错只会算错(输入维度对得上但语义错位)。
  • instance_group count: 1, kind: KIND_GPU —— 1 个 GPU instance。改成 count: 2 同卡跑两份实例(适合小模型);KIND_CPU 切回 CPU。

A.3 Python backend(simple_echo/model.py)

import numpy as np
import triton_python_backend_utils as pb_utils

class TritonPythonModel:
    def execute(self, requests):
        responses = []
        for request in requests:
            in_tensor = pb_utils.get_input_tensor_by_name(request, "INPUT_TEXT")
            in_data = in_tensor.as_numpy()
            out_data = np.array([b"echo: " + x[0] for x in in_data], dtype=object).reshape(in_data.shape)
            out_tensor = pb_utils.Tensor("OUTPUT_TEXT", out_data.astype(object))
            responses.append(pb_utils.InferenceResponse(output_tensors=[out_tensor]))
        return responses

Python backend 的真正威力是「把任意 Python 推理逻辑塞进 Triton 的生产壳」—— 多框架 / 多模型共享一个 Triton 进程 + 同一块 GPU + 同一份 metrics / health / gRPC stream,业务代码只写 execute()。生产常见用法是用 Python backend wrap 一个 vLLM 实例,对外暴露 Triton 协议,多模型场景吃 Triton 生态、单模型场景吃 vLLM 性能。

A.4 部署 yaml

apiVersion: apps/v1
kind: Deployment
metadata: {name: triton, namespace: triton}
spec:
  replicas: 1
  strategy: {type: Recreate}
  selector: {matchLabels: {app: triton}}
  template:
    metadata: {labels: {app: triton}}
    spec:
      runtimeClassName: nvidia
      dnsPolicy: None
      dnsConfig:
        nameservers: ["223.5.5.5", "8.8.8.8"]
      containers:
      - name: triton
        image: nvcr.m.daocloud.io/nvidia/tritonserver:24.10-py3
        args:
        - tritonserver
        - "--model-repository=/models"
        - "--allow-metrics=true"
        - "--metrics-port=8002"
        env:
        - {name: NVIDIA_VISIBLE_DEVICES, value: "all"}
        - {name: NVIDIA_DRIVER_CAPABILITIES, value: "compute,utility"}
        ports:
        - {containerPort: 8000, name: http}      # REST
        - {containerPort: 8001, name: grpc}      # gRPC(生产用)
        - {containerPort: 8002, name: metrics}   # Prometheus
        volumeMounts:
        - {name: models, mountPath: /models}
        - {name: dshm, mountPath: /dev/shm}
        readinessProbe:
          httpGet: {path: /v2/health/ready, port: 8000}
          initialDelaySeconds: 30
      volumes:
      - {name: models, hostPath: {path: /opt/triton-models}}
      - {name: dshm, emptyDir: {medium: Memory, sizeLimit: 4Gi}}

两个细节:

  • NVIDIA_VISIBLE_DEVICES=all 而不是 resources.limits.nvidia.com/gpu: 1 —— 后者会让 K8s device plugin 把整卡独占给一个 Pod,vLLM 和 Triton 在同卡时只有一个能跑。用 env 方式由 nvidia-container-runtime 直接挂卡,多 Pod 可以共享同一块 GPU(靠 gpu-memory-utilization 互不踩)。生产有 MIG 切片 / MPS 更优,单机学习场景 env 方式够用。
  • /dev/shm 4Gi tmpfs:Triton 默认走 cuda IPC 在 backend 间传 tensor,/dev/shm 太小(容器默认 64MB)会报 shared memory failed。

A.5 真坑:Triton image 16GB,跨 WAN 拉无穷无尽

nvcr.m.daocloud.io/nvidia/tritonserver:24.10-py3   ~16GB

NVIDIA 官方 image 把 PyTorch / TensorRT / TensorRT-LLM / ONNX Runtime / OpenVINO 全打进去,单镜像 16GB。GPU 节点跨 WAN 拉 12 分钟还没拉完,daocloud mirror 也救不了 —— 公网带宽 + 供应商 NAT 的限速摆在那。

生产对策从轻到重:节点装机阶段 ctr -n k8s.io images pull 预拉;Harbor 内部 mirror 从 nvcr.io 同步一次集群内零 WAN(中大规模团队必备);构建 tritonserver-base-min 去掉不用的 backend 可压到 3GB;纯 LLM 场景直接用 TGI(~5GB)或 vLLM OpenAI server,把 Triton 留给真的需要多框架的场景。


B. DCGM Exporter + 跨 WAN Prometheus:3 步打通

GPU 节点不在主集群里,但 Prometheus 在主集群。要让主集群的 Grafana / AlertManager 看到 GPU 节点的温度 / 利用率 / 显存 / 功率,必须把 metrics 拉过来。生产姿势是 WireGuard / Tailscale / Cilium ClusterMesh,但这些得改 IDC 网关配置,调试期先用 SSH tunnel 走通整条链路。

B.1 装 DCGM Exporter(GPU 节点 DaemonSet)

apiVersion: apps/v1
kind: DaemonSet
metadata: {name: dcgm-exporter, namespace: gpu-monitoring}
spec:
  selector: {matchLabels: {app: dcgm-exporter}}
  template:
    metadata: {labels: {app: dcgm-exporter}}
    spec:
      runtimeClassName: nvidia
      dnsPolicy: None
      dnsConfig: {nameservers: ["223.5.5.5", "8.8.8.8"]}
      containers:
      - name: dcgm
        image: nvcr.m.daocloud.io/nvidia/k8s/dcgm-exporter:3.3.7-3.5.0-ubuntu22.04
        env:
        - {name: NVIDIA_VISIBLE_DEVICES, value: "all"}
        - {name: NVIDIA_DRIVER_CAPABILITIES, value: "compute,utility"}
        ports: [{containerPort: 9400, name: metrics, hostPort: 9400}]
        securityContext: {capabilities: {add: [SYS_ADMIN]}}
        volumeMounts:
        - {name: pod-gpu-resources, mountPath: /var/lib/kubelet/pod-resources, readOnly: true}
      volumes:
      - {name: pod-gpu-resources, hostPath: {path: /var/lib/kubelet/pod-resources}}

两个关键:

  • SYS_ADMIN capability —— DCGM 直接读 GPU 性能寄存器(NVML 之下的 perfworks),需要这个 cap。普通容器没有 → 容器起来但所有 metric 全 0 / N/A。
  • /var/lib/kubelet/pod-resources hostPath 挂载 —— DCGM 通过这个 unix socket 跟 kubelet 拿「哪个 Pod / Container 占了哪块 GPU UUID」的映射,metrics 才能带上 Pod label。没挂的话只有节点级 metric。

B.2 真坑:DCGM Exporter 启动找不到 NVML

第一次起 dcgm-exporter Pod 直接 CrashLoopBackOff:

Failed to initialize NVML: Driver/library version mismatch
# 或:libnvidia-ml.so.1: cannot open shared object file

GPU 节点上明明 nvidia-smi 正常,为啥容器里找不到?两个常见根因:

  1. runtimeClassName: nvidia 没设或者 k3s 没装 nvidia-container-runtime —— 容器没注入 NVIDIA driver libraries(/usr/lib/x86_64-linux-gnu/libnvidia-ml.so)。验证:kubectl exec dcgm-exporter -- ls /usr/lib/x86_64-linux-gnu | grep nvidia,应该有十几个 .so。
  2. 驱动版本 vs DCGM image 不匹配:image tag 3.3.7-3.5.0-ubuntu22.04 里第二段 3.5.0 是 DCGM 版本,要求宿主 driver ≥ 535。宿主驱动太旧(如 470)就报 NVML mismatch。先 nvidia-smi 看 driver version,旧的话要么升 driver,要么换更老的 dcgm-exporter tag。

排查顺序:nvidia-smi(宿主)→ kubectl exec ... nvidia-smi(容器内有没有 driver)→ kubectl logs dcgm-exporter 真错。

B.3 DCGM 输出的 19 个核心 metric

Metric含义告警价值
DCGM_FI_DEV_GPU_TEMP / MEMORY_TEMP核心 / 显存温度(℃)> 85 / 95 报警
DCGM_FI_DEV_GPU_UTILGPU 利用率(%)长期 < 30% = 浪费
DCGM_FI_DEV_MEM_COPY_UTIL显存带宽利用率(%)看是否带宽瓶颈
DCGM_FI_DEV_FB_USED / FB_FREE显存占用 / 可用(MiB)OOM 预警
DCGM_FI_DEV_POWER_USAGE当前功率(W)能效核算
DCGM_FI_DEV_SM_CLOCK / MEM_CLOCKSM / 显存频率(MHz)突降 = 过热降频
DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION累计能耗(mJ)成本核算
DCGM_FI_DEV_PCIE_REPLAY_COUNTERPCIe 重传次数> 0 即链路异常
DCGM_FI_DEV_NVLINK_*NVLink 带宽多卡才有效
DCGM_FI_DEV_CORRECTABLE_REMAPPED_ROWSECC 修复显存行显存衰退预警

每个 metric 带 label:gpu / UUID / pci_bus_id / device / modelName / Hostname,配合 ServiceMonitor 的 relabel 可以加 pod / container label,做到「这个 Pod 在这块 GPU 上用了多少功率」。

B.4 跨 WAN 三步:SSH tunnel + 手动 Endpoints + ServiceMonitor

Step 1:主集群 m1 上起 SSH tunnel,把 GPU 节点的 DCGM 9400 反向暴露在 m1 的 0.0.0.0:9400。

ssh -fN -p 15128 \
  -L 0.0.0.0:9400:<gpu1-dcgm-pod-IP>:9400 \
  root@***.109.239.32

-fN 后台运行 + 不开 shell。-L 0.0.0.0:9400 是关键 —— 默认 -L 9400 只 bind 127.0.0.1,K8s 里其他 Pod 访问 m1 的 hostIP:9400 接不上,必须显式 0.0.0.0。

Step 2:建一个 headless Service + 手动 Endpoints 指向 m1 的 hostIP。

apiVersion: v1
kind: Service
metadata: {name: gpu-dcgm-exporter, namespace: monitoring}
spec:
  ports: [{port: 9400, name: metrics, protocol: TCP}]
  clusterIP: None       # headless,不要 ClusterIP,手动 Endpoints 接管
---
apiVersion: v1
kind: Endpoints
metadata: {name: gpu-dcgm-exporter, namespace: monitoring}    # 名字必须跟 Service 一模一样
subsets:
- addresses: [{ip: 10.0.24.31}]    # m1 内网 IP,SSH tunnel 在这里 listen
  ports: [{port: 9400, name: metrics}]

为什么 headless:有 ClusterIP 的 Service,K8s 会根据 selector 自动生成 Endpoints;这里 Service 没有 selector(也不能有,endpoint 不在集群里),所以必须手写一个同名 Endpoints 覆盖。Endpoints 的 name 必须跟 Service 一致,这是 K8s 约定。

Step 3:ServiceMonitor 让 kube-prometheus-stack 自动 scrape。

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: gpu-dcgm-exporter
  namespace: monitoring
  labels: {release: kps}        # 关键:chart 的 serviceMonitorSelector 抓这个 label
spec:
  selector: {matchLabels: {app: gpu-dcgm-exporter}}
  endpoints:
  - port: metrics
    interval: 15s
    relabelings:
    - sourceLabels: [__address__]
      targetLabel: instance
      replacement: 'gpu1-ubuntu22-a800'   # 不然 instance 显示成 m1,混淆

labels.release: kps 不写整个 ServiceMonitor 不会被 Prometheus 发现 —— kube-prometheus-stack(helm release 名 kps)默认 serviceMonitorSelector.matchLabels.release: kps,只抓带这个 label 的。装了 chart 半天找不到 target 大概率是这个。

B.5 验证

curl 'http://prometheus:9090/api/v1/query?query=DCGM_FI_DEV_GPU_TEMP'
# {"metric":{"modelName":"NVIDIA A800-SXM4-40GB","instance":"gpu1-ubuntu22-a800"}, "value":[..., "32"]}

跑 C 段 benchmark 时实时看到 GPU_TEMP 33°C、GPU_UTIL 0%→79%、FB_USED 33743 MiB / FREE 6762 MiB、POWER 57W→250W、SM_CLOCK 1095 MHz。

生产替换路径:SSH tunnel → WireGuard / Tailscale / Cilium ClusterMesh。三者都做到「跨集群 Pod 互通」,把上面 Endpoints 的 IP 改成 GPU 集群里 dcgm-exporter Service 的 ClusterIP 就完事。


C. vLLM benchmark:1 / 5 / 30 并发实测 + 选型矩阵

GPU 节点 vLLM 服务 Qwen2.5-3B-Instruct(Day 8 起好的),gpu-memory-utilization=0.85 占 33GB 显存。用 Python urllib + ThreadPoolExecutor 做并发压测。

C.1 Benchmark 设计

测 4 个指标:wall time、throughput(tok/s)、P50 / P95 / P99 单请求延迟、req/s。并发梯度选 1 / 5 / 30,max_tokens=128,固定 prompt。

为什么不测更高(50 / 100):从 GPU util 79% 看 30 并发已经接近饱和,再加只增 queueing latency 不增吞吐,曲线已经能讲清楚故事。

C.2 实测数据(A800-40G,Qwen2.5-3B-Instruct)

并发WallTok/sOutput Tok/sReq/sP50P95P99GPU util
11.11s122.6115.40.901.1081.1081.108低
51.04s545.9511.34.811.0361.0391.039中
301.30s2901.42718.723.141.2831.2941.29579%

5 个关键洞察:

  1. 吞吐增长 24×:1 → 30 并发,122 → 2901 tok/s,亚线性扩展但效率 79%(理论线性 30×=3660)。亚线性是因为单请求 prefill 阶段算力消耗大,decode 阶段才并行得起来,并发越多越受 prefill 抢占影响。
  2. 30 并发延迟只比单并发慢 17%(1.11 → 1.30s):continuous batching 的威力 —— 不等 batch 凑齐,每次 decode iteration 都可以让新请求加入。传统 padded batching 30 并发延迟会是 30× 慢。
  3. P50 ≈ P99:没有 tail latency 问题,vLLM 调度公平,不会有「最后一个请求被饿死」。比 throughput 更难得,生产 SLA 关键。
  4. GPU util 79% @ 30 并发:接近饱和,再加并发只增 queueing delay 不增 throughput,30 并发是这个模型 / 这块卡的甜蜜点。
  5. 错误率 0 / 65:稳定性 100%。

C.3 真坑:benchmark 并发数怎么选

第一次直接选 100 并发:客户端线程池被本地 OS 限流(默认 ulimit -n 1024,每 connection 一个 fd 很快爆);vLLM 排队队列堆 60+,P99 飙到 8s+ 但 throughput 没涨(GPU 早饱和了);数据全是 outlier 决策讲不清。

正确姿势:从 1 开始翻倍递增(1 / 2 / 4 / 8 / 16 / 32),直到 GPU util 接近饱和或 P99 开始非线性恶化。本质上是「吞吐 / 延迟取舍点」—— 业务 P99 SLA 倒推并发上限。这里 P99 1.3s 对话场景可接受,30 并发是合理工作点。

C.4 vLLM 强在哪:PagedAttention + Continuous Batching

vLLM 跑出 24× 单卡并发,靠两个核心创新:

  • PagedAttention(vLLM paper, Berkeley 2023)—— KV cache 像 OS 虚拟内存分页管理。传统 LLM 推理给每个 sequence 预分配连续显存放 KV cache,按 max_seq_len 取,padding 浪费 ~60% 显存。PagedAttention 把 KV cache 切成固定大小 block(默认 16 token),按需分配,显存利用率提升 4× —— 同样 40GB 显存能跑 4× 多的并发请求。
  • Continuous Batching(iteration-level scheduling)—— 不等 batch 凑齐才启动,每个 decode iteration 检查队列,新请求随时加入、完成的请求随时离开。传统 static batching 必须等 batch 内最长请求跑完才放新的,短请求会被长请求拖死。continuous batching 让 GPU 永不空转。

两者结合:单 GPU 服务 24× 更多并发,p99 latency 只涨 17%。这是 vLLM 比 HuggingFace transformers.generate 快 24× 的原理。

C.5 vLLM vs Triton 选型矩阵

维度vLLMTriton + TensorRT-LLMTriton + Python(vLLM as backend)
专注LLM onlyLLM + CV / ASR / 任何LLM + 多模型
吞吐基线TRT 优化 +20-40%Python overhead 略低
延迟极低极低略高(Python GIL)
部署难度1 行命令model repo + TRT 编译model repo + Python
多模型同实例不支持支持(ensemble)支持
多框架不支持PyTorch / ONNX / TRT / Python看子 backend
量化AWQ / GPTQ / FP8 内置TensorRT INT8 / FP8看 backend
OpenAI 兼容默认 /v1需适配层需适配层
生态成熟度2023+ 新兴2019+ 工业事实标准—

选型决策树:

单一 LLM 服务?
├─ 是 → vLLM(简单 + 快)
└─ 否 → Triton
    多模型多框架?
    ├─ 是 → Triton + Python backend(灵活,可 wrap vLLM)
    └─ 否(纯 CV / ASR)→ Triton + ONNX / TensorRT(性能极致)

生产组合常见做法:主业务 LLM 用 vLLM、多模态用 Triton ensemble、长尾 / 自训练模型用 Triton Python backend。把 vLLM 当 Triton 的 Python backend 用,是「既要 vLLM 性能、又要 Triton 多模型生态」的折中,但要承担 Python backend 序列化层的延迟代价(实测 +20-50ms)。

C.6 架构图

主集群 (5 节点 K8s)                       GPU 节点 (k3s, A800-40G)
Grafana → Prometheus(kps)                 dcgm-exporter:9400
   ▲ 15s scrape                            vllm-qwen (Qwen2.5-3B)
ServiceMonitor (release=kps)               triton (densenet + simple_echo)
   ▲                                       NVIDIA Device Plugin
Service(headless) + Endpoints
   ▲ 10.0.24.31:9400                      生产: WireGuard / Tailscale /
m1 0.0.0.0:9400 ◄── ssh -L tunnel ──────       Cilium ClusterMesh
                       公网

面试常见题

Q1:LLM 推理服务器选型 —— vLLM / Triton / TGI / Ollama 怎么选?

  • 生产高并发 LLM(单模型):vLLM。PagedAttention + continuous batching SOTA,单卡 24× 吞吐,OpenAI 兼容 API 零成本接入
  • 多模型 / 多模态:Triton。多 backend(ONNX / TRT / Python / vLLM)一锅,model ensemble 跨模型 pipeline,NVIDIA 工业事实标准
  • 极致延迟 / 显存优化:Triton + TensorRT-LLM。INT4 / FP8 量化 + 算子融合,比 vLLM 再快 20-40%,但编译模型耗时长
  • HuggingFace 生态强依赖:TGI(HF 出品)。开箱即用,性能比 vLLM 差 10-20%
  • 本地 / 桌面:Ollama。封装 llama.cpp 零配置,但不是生产推理服务器

深问「为什么生产不直接 Ollama?」—— 没 continuous batching 顺序执行 QPS 上不去;没 Prometheus metrics / health check / K8s readiness;模型加载 lazy 第一个请求触发 load 几十秒。只适合本地 demo。

Q2:DCGM Exporter 暴露哪些关键 metrics?怎么做 SRE 告警?

19 个 metric 分 4 类:利用率(GPU_UTIL + MEM_COPY_UTIL,长期 < 30% 浪费,> 95% 瓶颈)、显存(FB_USED / FB_FREE,> 90% OOM 预警)、温度 / 功率(GPU_TEMP > 85℃ 接近降频、MEMORY_TEMP > 95℃、POWER_USAGE 跑能效比 tok/s ÷ W)、硬件健康(PCIE_REPLAY_COUNTER > 0 链路异常、CORRECTABLE_REMAPPED_ROWS ECC 骤增告警、SM_CLOCK 突降 = thermal throttle)。

集群层面看 P99 GPU util(识别热点卡)+ tok/s ÷ W 能效比(识别低效模型部署)。

Q3:跨集群 Prometheus 怎么联邦?三种姿势对比?

  • Prometheus Federation:远端暴露 /federate,中心来拉。原生支持但拉模式(远端要公网),标签膨胀,不适合高频
  • Remote Write:远端主动推到中心(Thanos / Cortex / VictoriaMetrics)。推模式 + 中心做长期存储 + 全局查询,但远端 WAL 双写增内存。规模化首选
  • SSH tunnel / VPN + 手动 Endpoints:把远端 exporter 当作中心集群里的 Service。30 分钟搭通不改架构,但 tunnel 单点不适合长期

Day 9 用第三种走通链路,生产换 WireGuard / Tailscale / Cilium ClusterMesh 跨集群 Pod 互通,Endpoints / ServiceMonitor 上层不动。

Q4:vLLM 的 PagedAttention 跟 KV cache 是什么关系?

LLM 推理每个 token 的 attention 需要历史所有 token 的 Key / Value,这就是 KV cache。传统姿势每 sequence 预分配连续显存按 max_seq_len 取,短序列浪费 ~60% 显存。

PagedAttention 借鉴 OS 虚拟内存分页:KV cache 切成固定大小 block(默认 16 token)不要求连续;每 sequence 维护一张 block table(类似页表)按需分配;多个 sequence 可共享 prefix 的 block(prompt sharing / beam search)。

效果:显存利用率提升 4×,同样 40GB 能跑 4× 并发;配合 continuous batching 让单卡并发上限从几十到几百;vLLM 比 HuggingFace 快 24× 的核心。

深问有什么代价? —— Block 不连续,attention kernel 需按 block table 查找比连续访存慢 ~5%,但显存换吞吐绝对划算,vLLM 写了 CUDA kernel 优化访存模式。

Q5:benchmark 推理服务,并发数怎么选?

并发数选择不是固定值,是「业务 P99 SLA + GPU util 饱和点」的交集:从 1 开始翻倍递增(1 / 2 / 4 / 8 / 16 / 32),不要一上来就 100;观察 3 条曲线 throughput / P99 latency / GPU util;三个拐点(throughput 增长放缓进入饱和区、P99 非线性恶化 queueing 堆积、GPU util > 80% 算力瓶颈);工作点选 P99 < SLA 且 throughput 接近饱和的并发数。

Day 9 实测:30 并发 GPU util 79%、P99 1.3s。SLA P99 < 2s → 30 并发是工作点;SLA < 500ms → 回退到 5 并发(P95 1.04s),牺牲 5× 吞吐换 latency。

要避免的坑:只测平均延迟不测 P99(长尾被掩盖);只测 throughput 不测 latency(服务已经卡死);客户端瓶颈(先 ulimit -n 65536);prompt 长度固定(实际 prefill 开销差异巨大)。


总结:Day 9 后能力

模块状态关键证据
A — Triton model repo已完成(image 拉取阻塞)densenet_onnx ONNX + simple_echo Python backend,yaml 完整可一键启
B — DCGM + 跨 WAN Prometheus完成主集群 Prometheus 实测 19 个 GPU metrics,SSH tunnel + 手动 Endpoints + ServiceMonitor 链路全通
C — vLLM benchmark完成30 并发 2901 tok/s,P99 1.3s,GPU util 79%,vLLM vs Triton 决策矩阵

下一步

Day 9 把推理服务和 GPU 观测做到了「生产可对外」。Day 10 进入 LLM 推理优化深水区:TensorRT-LLM 编译 Qwen 模型走 INT8 / FP8 量化路径、对比未量化 vLLM 的吞吐与精度损失、Triton + TensorRT-LLM backend 整合,把 Day 9 选型矩阵最右一列(TensorRT-LLM)补成真实数据。

在 GitHub 上编辑此页
Prev
Day 8:集群内 CI 闭环 — Gitea + Jenkins + Kaniko
Next
Day 10:MIG 硬切片 + AWQ 量化 + HPA Custom Metrics