1. 先把"中台"的边界划清楚:能力盘点决定技术栈走向
聊开源AI中台的部署运行之前,我习惯先泼一盆冷水:绝大多数团队栽跟头,不是因为技术难,而是因为一开始就没搞清楚自己要的是"模型仓库"还是"能力复用层"。这两者的部署形态差得非常远——前者一台带显卡的机器加上一个推理框架就能交差,后者要从身份认证、配额、灰度、审计一路搭到监控告警。
我自己的判断标准很粗暴:如果公司内部超过三个业务方要调模型,而且他们对"用哪个模型""怎么限流""怎么计费"有各自的要求,那你就必须按中台来做;如果只是两个人在做Demo,直接起一个兼容OpenAI协议的推理服务,别碰K8s,省下的时间够你调三轮Prompt。
1.1 中台真正要交付的四类能力
把需求翻译成技术组件之前,先明确中台对外到底承诺了什么。我的经验是分四层:接入层(统一协议、密钥、配额)、推理层(模型加载、批处理、显存调度)、知识层(向量检索、文档解析、切片)、治理层(监控、审计、成本归集)。这四层里最容易被低估的是接入层和治理层,因为它们不产出"能跑通"的爽感,但一旦缺失,中台就会退化成"谁都能调、谁都说不清花了多少钱"的黑盒。
一个很现实的现象:很多团队上线第一周就在群里被问"我们部门上个月消耗了多少token",而这时候才发现网关根本没有记录用量。这种返工成本远高于一开始就选个带用量统计的网关。
1.2 一张能力清单表,把模糊需求钉死
我建议在动手之前,花半天时间填这张表。填不满的格子就是你没想清楚的地方,也是后面会反复返工的地方。
| 维度 | 需要确认的问题 | 影响的技术选型 |
|---|---|---|
| 模型数量 | 同时在线几个模型,是否需要多版本共存 | 显存总量、是否需要动态加载 |
| 请求特征 | 平均输入长度、输出长度、峰值QPS | max-model-len、批处理参数 |
| 调用方数量 | 几个业务方、是否需要隔离 | 多租户网关、命名空间划分 |
| 知识库规模 | 文档总量、切片数量、是否增量更新 | 向量库选型、索引重建策略 |
| 合规要求 | 是否需要留痕、是否有数据出境限制 | 审计日志、私有化部署程度 |
| 预算 | 一次性硬件投入上限、月度电费与云费 | 自建 vs 混合部署 |
这张表最有用的一点是:它逼着你把"我要一个AI中台"这种空话,变成"我要支撑5个模型、峰值30 QPS、3个业务方隔离、50万条切片"这样能算账的输入。
1.3 三种规模对应的最小可行架构
我不建议一上来就照着大厂架构图抄。按规模分三档,越级就是浪费:
- 轻量档(单机、1-2块卡、内部试用):Docker Compose 起推理服务 + 网关 + 向量库 + 控制台,模型文件直接挂本地盘。这套东西我搭过多次,一台双卡4090的机器半天就能跑起来。
- 标准档(3-10块卡、多业务线):K8s集群 + GPU 调度插件 + 独立向量库 + 对象存储放模型 + Prometheus 监控。这时才需要认真考虑模型分发和显存碎片。
- 规模化档(20块卡以上、多机房):引入模型网关分级路由、多副本推理、权重预分发、灰度发布和成本分摊。这一档的复杂度主要来自"变更管理"而不是"能不能跑"。
踩过几次之后我的结论是:从轻量档起步,但目录结构和配置从第一天就按标准档来组织。比如模型文件路径、环境变量命名、镜像Tag规范,这些后期迁移时的改动成本极高,一开始做对了不花什么钱。
2. 硬件与依赖的账:算力、显存、存储、网络的四本明细
部署运行的第一步不是敲命令,是算账。我遇到过最典型的情况是:采购按"7B模型单卡够用"的标准配了机器,结果业务转头要上70B,显存直接差一个数量级,只能重新走采购流程,项目延期两个月。所以这部分我想把计算过程写清楚,你拿着公式就能自己估。
2.1 显存怎么算:把参数量换算成GB
显存占用主要三块:权重、KV Cache、运行时开销。权重最好算,参数量乘以精度字节数:
| 精度 | 每参数字节数 | 7B模型权重大致占用 | 70B模型权重大致占用 |
|---|---|---|---|
| FP32 | 4 | 约28GB | 约280GB |
| FP16/BF16 | 2 | 约14GB | 约140GB |
| INT8 | 1 | 约7GB | 约70GB |
| INT4 | 0.5~0.6 | 约4GB | 约40GB |
注意:量化不是白拿的,INT4在长文本推理和代码类任务上掉点比较明显,我在实际项目里的做法是"权重INT8 + KV Cache FP8",兼顾质量和显存,比纯INT4稳得多。
KV Cache 才是真正决定你能开多少并发的因素。以常见的8B级别模型为例,32层、8个KV头(GQA)、head_dim 128,单token单层的KV占用是 2 × 8 × 128 × 2字节 = 4KB,32层就是每token约128KB。按8K上下文算,一条序列约1GB;如果并发批大小是32,光KV Cache就要32GB。这就是为什么"14GB权重"的模型在单张24GB卡上跑长上下文会OOM——权重只是入场券,KV Cache才是真正吃钱的地方。
2.2 存储分层:镜像盘、模型盘、数据盘
存储这块几乎每个项目都会翻车一次。我的分层方案是:
- 系统盘:只放容器运行时和系统,容量不用大,但要注意
/var/lib/docker或/var/lib/containerd默认在系统盘,镜像一多很容易把根分区撑满,最好单独挂一块盘再把目录软链过去。 - 模型盘:NVMe优先。一个70B的BF16权重就是140GB,加载时是顺序大文件读,机械盘会把启动时间从3分钟拉到20分钟以上。多副本场景下这块盘还会被多个Pod同时挂载读取。
- 数据盘:向量库、日志、对象存储的落盘位置。向量库对随机IO敏感,别和模型盘混在一起,否则一次索引重建能把推理服务的模型加载拖慢一个档。
2.3 网络与驱动:容易被忽略的前置条件
多机多卡部署时,网络是隐性杀手。我的检查清单大致是:驱动版本与 CUDA 版本是否匹配、容器内是否有nvidia-smi、节点间的 RDMA 或高速内网是否可用、NCCL 的网卡选择是否正确。这几个问题不解决,多机通信会出现"服务起来了但吞吐只有单卡水平"或者干脆卡死。
# 容器内快速自检 nvidia-smi nvidia-smi topo -m # 看卡间互联拓扑,NVLink还是PCIe python -c "import torch; print(torch.cuda.device_count(), torch.version.cuda)" # 多机通信排查时打开NCCL日志 export NCCL_DEBUG=INFO export NCCL_SOCKET_IFNAME=eth0 export NCCL_IB_DISABLE=1 # 没有IB网络时先关掉,避免长时间重试提示:把驱动版本、CUDA版本、PyTorch版本、推理框架版本记在一张表里存进仓库,这类"版本四件套"的兼容问题排查起来最耗时,有记录就能秒级定位。
3. 从单机 Docker Compose 到集群 K8s:部署路径的两段式走法
我坚持两段式部署:先在一台机器上把全链路跑通,再搬到集群。原因很直接——单机环境下所有日志都在一个终端里,哪一层出错一目了然;到了K8s,一个问题会分散在Pod事件、容器日志、节点日志、Ingress日志四个地方,排查成本翻好几倍。如果业务逻辑本身还没验证过,直接在集群里调就是给自己上强度。
3.1 单机验证:先跑通全链路再谈高可用
单机的目标是验证"请求能从网关进来、经过鉴权、打到推理服务、带上检索结果、正常返回"。我通常用 Compose 编排四个服务:
# docker-compose.yml 片段(示意,按实际镜像调整) services: llm: image: your-registry/vllm-openai:latest command: > --model /models/qwen2.5-7b-instruct --served-model-name qwen-7b --max-model-len 8192 --gpu-memory-utilization 0.85 --enable-prefix-caching volumes: - /data/models:/models deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] ports: - "8000:8000" vector-db: image: qdrant/qdrant:latest volumes: - /data/qdrant:/qdrant/storage ports: - "6333:6333" gateway: image: your-registry/llm-gateway:latest environment: - UPSTREAM_BASE_URL=http://llm:8000/v1 - DB_URL=postgres://user:pass@pg:5432/gw ports: - "4000:4000"这套东西跑起来之后,我一般做三件事验收:一是用curl直连推理服务,确认模型能出字;二是经过网关调一次,确认密钥和配额生效;三是带检索调一次,确认向量库返回的切片确实被拼进了Prompt。三件事都过,才往K8s搬。
3.2 GPU 调度:device plugin 与 MIG 的选择
K8s 里 GPU 是扩展资源,靠 device plugin 上报。装完之后你在节点上能看到nvidia.com/gpu这个资源,Pod 通过resources.limits申请。这里有个坑:申请了GPU就必须配 limit,只写 request 不写 limit 是无效的,很多人第一次写YAML会掉进去。
resources: limits: nvidia.com/gpu: 1 requests: nvidia.com/gpu: 1如果你们是A100/H100这类支持MIG的卡,可以把一张卡切成多个小实例分给不同的小模型,提升利用率。但MIG不是万能的:切分之后单实例显存固定,大模型反而跑不了,而且MIG下部分P2P通信受限。我的建议是,只在"多个小模型共存、每个模型负载都不高"的场景下用MIG,主力大模型还是独占整卡更稳。
3.3 模型分发:别让 30GB 权重每次都从零下载
这是K8s部署里最容易被忽视的性能问题。容器启动时去下载模型权重,会导致:启动时间不可控(几分钟到几十分钟)、并发扩容时带宽打满、下载失败直接 CrashLoopBackOff。
我常用的三种做法,按优先级排:
- 宿主机预置 + hostPath 挂载:模型文件提前同步到每台GPU节点的固定目录,Pod 只读挂载。简单、快、不依赖网络,缺点是节点扩容要手动同步。
- 只读 PVC(对象存储 CSI):模型放在对象存储或NAS上,通过 CSI 驱动挂载。扩容方便,但首次读取有网络延迟,热缓存之后才好用。
- initContainer 拉取 + emptyDir 缓存:适合模型频繁更新的场景,initContainer 负责校验和下载,主容器直接读本地。要注意 emptyDir 会占节点盘,得设 sizeLimit。
顺带提一句,模型目录的权限和文件名大小写在跨平台同步时经常出问题。我有一次从对象存储同步下来的模型,配置文件名大小写不一致,本地能跑,集群里报"找不到配置文件",查了两个小时。同步脚本里加一行校验文件清单的步骤,能省掉这类折腾。
4. 推理服务的落地细节:vLLM 参数与实际显存占用
推理层是AI中台的心脏。开源推理框架里,vLLM 的 PagedAttention 和连续批处理是目前工程落地最成熟的路线之一,TGI、SGLang 也各有优势。我下面拿 vLLM 举例,但参数背后的逻辑是通用的——你换成别的框架,要调的维度基本一致。
4.1 关键启动参数逐条解释
很多人是抄一份启动命令就上,参数什么意思不清楚,出了问题无从下手。我把最常调的几条列出来:
| 参数 | 作用 | 取值建议 |
|---|---|---|
--tensor-parallel-size | 张量并行数,等于使用的GPU数 | 单卡填1,多卡按卡数整倍填 |
--gpu-memory-utilization | 允许占用的显存比例 | 0.85~0.90,留出余量给CUDA上下文 |
--max-model-len | 单条序列最大长度 | 按业务最长上下文定,别默认拉满 |
--max-num-seqs | 最大并发序列数 | 决定批处理上限,直接影响吞吐与显存 |
--max-num-batched-tokens | 单批次token上限 | 长输入场景调大,短输入场景调小 |
--enable-prefix-caching | 缓存公共前缀的KV | 系统提示词长的场景几乎必开 |
--kv-cache-dtype fp8 | KV Cache 用FP8存储 | 显存紧张时开,质量损失可接受 |
关于--gpu-memory-utilization,我想强调一点:它指的是"预留给模型和KV Cache的总显存比例",不是"能省下来多少"。设成0.95看似利用充分,但一旦同一张卡上还有别的进程(比如监控采集、日志边车),就会出现莫名其妙的OOM。我一般在共享卡上设0.8,独占卡设0.9。
4.2 张量并行与流水线并行的取舍
卡多起来之后,并行策略就成了必须做对的选择。张量并行(TP)把每层的矩阵切到多卡,通信频繁但延迟低,适合单机多卡;流水线并行(PP)按层切分,通信少但有流水线气泡,适合跨机;数据并行(DP)是多个完整副本,吞吐最好但显存占用翻倍。
我的经验规则:单机多卡优先TP,跨机扩吞吐优先DP,只有卡数多到TP通信扛不住时才考虑PP。TP数尽量取2的幂,且不要超过单机GPU数,跨机TP对互联带宽要求极高,没有高速内网基本没法用。
# 双卡TP示例 python -m vllm.entrypoints.openai.api_server \ --model /models/qwen2.5-14b-instruct \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.88 \ --max-model-len 16384 \ --max-num-seqs 64 \ --enable-prefix-caching \ --served-model-name qwen-14b4.3 常见的OOM与首token延迟问题
OOM排查顺序:先看nvidia-smi是权重加载阶段就炸还是跑起来之后才炸。加载阶段炸说明权重本身超了,要么换量化要么加卡;运行阶段炸说明KV Cache不够,降max-num-seqs或max-model-len,或者开kv-cache-dtype fp8。容器退出码是137的话,还要看是不是被系统OOM Killer杀了内存而不是显存。
首token延迟(TTFT)高,通常三个原因:没开prefix caching导致系统提示词反复计算、max-num-batched-tokens太小导致长输入被切得太碎、请求排队太长。我实测下来,把系统提示词的公共前缀利用起来,TTFT能降三到五成,这是投入产出比最高的一项优化。
5. RAG 与统一 API 层:让中台"可被调用"而不是"能跑"
模型能出字只是及格线。中台真正的价值在于"别人愿意接"——接口稳定、文档清楚、配额可控。这一节讲两件事:知识层怎么选,接入层怎么搭。
5.1 向量库选型:Milvus、Qdrant、pgvector 的真实差异
| 方案 | 优势 | 代价 | 适用场景 |
|---|---|---|---|
| pgvector | 复用现有PostgreSQL,运维零增量 | 千万级向量以上性能下降明显 | 切片量百万以内、已有PG |
| Qdrant | 单二进制部署,资源占用低,过滤检索强 | 生态相对小 | 中等规模、快速起步 |
| Milvus | 分布式、可水平扩展、索引类型全 | 组件多(etcd、对象存储、消息队列) | 千万级以上、需要弹性扩容 |
我踩过最大的坑在"切片策略"上而不是数据库本身。同一批文档,按固定长度切和按语义段落切,召回率能差出十几个百分点。我的做法是:先用固定长度(比如512 token、重叠64)快速跑一版基线,再针对召回差的问答对去调切片规则,不要一开始就追求完美切片。
5.2 统一网关:多模型、多租户、配额与计费
网关这块,开源方案里有轻量代理型的,也有全功能型的。我选择时的硬性指标是四条:OpenAI协议兼容、多密钥管理、用量落库、可插拔路由。前两条决定接入成本,后两条决定你能不能说清成本。
路由策略我一般配三档:按模型名路由(最常用)、按租户路由(不同部门走不同后端)、按降级路由(主模型超时自动切备用)。降级路由一定要有,我见过主力模型所在节点故障导致整个业务方停摆的情况,如果有自动降级,至少能保住可用性。
注意:网关的用量记录一定要写异步队列再落库,同步写库在高峰会把网关自己拖死。我之前图省事直接同步写,QPS一上50就开始出现超时。
5.3 应用侧对接:OpenAI 兼容协议的价值与坑
协议兼容最大的价值是"应用代码不用改",但兼容层也有几个坑:不同厂商对temperature、top_p、stop的实现细节不一致;流式返回的结束标志有的用[DONE]有的不用;usage字段有的不返回。我在网关里统一做了字段规整,把差异吃在中间层,这样上层业务同学完全无感。
另外一个现实问题:上下文长度超限的报错必须是明确的4xx,不能是空响应或者500。业务方拿到一个模糊错误,只能来问你,你的支持成本会指数级上升。
6. 可观测性、权限与审计:中台能不能被别人放心用
这一层不产出功能,但决定了中台能不能从"几个人的玩具"变成"全公司的基础设施"。我见过不少中台项目技术做得不错,最后死在没有监控和权限上——出了故障没人知道,用了多少没人清楚。
6.1 三个必看的监控面板
监控别一上来就搞几十个大屏,先做三个:
第一个是GPU面板:利用率、显存占用、温度、功耗,靠 DCGM 采集。这块数据能直接告诉你"是算力不够还是调度没用好"。我见过显存占了90%但利用率只有20%的情况,说明KV Cache占着不放、实际计算很闲,这时候加卡是浪费钱。
第二个是推理面板:TTFT、TPOT(每token输出耗时)、请求排队时长、KV Cache使用率。这几个指标比单纯的QPS有用得多,能直接指认瓶颈在排队还是在计算。
第三个是业务面板:按租户/应用维度的调用量、token消耗、错误率。这是给管理层看的,也是你做容量规划的依据。
# vLLM 自带 Prometheus 指标,压测时先确认这个端点能出数 curl -s http://localhost:8000/metrics | grep -E "vllm:(num_requests|gpu_cache_usage)"6.2 租户隔离与密钥管理
隔离分三个层次:密钥隔离(每个业务方独立密钥,便于吊销和统计)、配额隔离(按分钟/日限额,防止一个业务拖垮全局)、资源隔离(大客户独占推理副本,小客户共享)。
密钥千万别硬编码在配置里。我的做法是密钥存在数据库里存哈希、明文只在创建时返回一次,容器里的凭据通过 Secrets 注入。这样即使仓库泄露也不会连带泄露密钥。
6.3 审计日志与配额熔断
审计日志要记的东西其实不多:谁(租户/密钥ID)、什么时候、调了哪个模型、输入输出长度、耗时、状态码。不要把完整的输入输出内容落盘,一是隐私风险,二是存储成本高得离谱。真需要留存的,做脱敏+采样。
配额熔断一定要在网关层做实时的,不能靠事后统计。做法是网关内存里维护一个滑动窗口计数,超限直接返回429并带重试建议,同时异步落库。阈值我一般设成"日常峰值的1.5倍",既能挡住异常流量,又不会误伤正常业务。
7. 上线后的排查链路实录:五个高频故障的定位过程
这部分是我想写得最实在的一节。下面五个故障都是我在真实环境里遇到过的,我把排查过程写完整,你可以照着走。
7.1 镜像拉取卡住与节点磁盘打满
现象:Pod 一直 Pending 或 ImagePullBackOff,节点日志里出现 no space left on device。
排查链路:先kubectl describe pod看事件,如果是拉取失败,再df -h看节点盘。绝大多数情况是/var/lib/containerd或/var/lib/docker把根分区撑满了——镜像层累积、日志文件膨胀、旧镜像没清理都是原因。
处理:配置镜像仓库的内网私有地址减少拉取耗时,加imagePullPolicy和imagePullSecrets检查,部署定期清理旧镜像和日志轮转。另外把容器日志目录单独挂盘,这个改动一次到位,后面省很多事。
7.2 CUDA/NCCL 版本不匹配导致的多卡挂起
现象:单卡正常,多卡启动时卡在初始化,或者跑几分钟后hang住。
排查链路:打开NCCL_DEBUG=INFO,看日志卡在哪一步。如果是集合通信超时,通常是网卡选择不对或P2P不可用;如果是invalid device function之类的报错,多半是CUDA版本和编译时的版本不一致。
处理:统一基础镜像,宿主机驱动版本要高于容器内CUDA所需的最低驱动,多机时明确指定NCCL_SOCKET_IFNAME。这类问题我建议一开始就在CI里加一个"双卡小规模通信测试",几分钟就能跑完,提前暴露问题。
7.3 推理服务"活着但不出token"
现象:健康检查通过,接口返回200,但流式响应迟迟不出内容,最后超时。
排查链路:先看是不是所有请求都这样,还是只有特定长输入的请求。如果是长输入,看是不是max-num-batched-tokens太小导致长序列被无限推迟(饥饿)。再看KV Cache使用率是不是接近100%,如果是,说明没有空间调度新请求。
处理:调整批处理参数、开prefix caching、必要时降低单请求最大长度。另外健康检查不能用默认的HTTP 200,要做一层"真的能出一小段token"的探针,否则会出现"服务假活着"的情况。
7.4 向量检索召回率低的排查顺序
按这个顺序查,基本能定位:先看切片本身(用文本编辑器打开几条切片,看内容是否完整)→再看Embedding模型是否与查询同源(索引和查询用了不同模型,效果会断崖式下降)→再看距离度量与归一化(余弦距离和点积在归一化与否的情况下结果不同)→最后看检索数量(top-k设太小,正确答案根本没进来)。我遇到最多的其实是第一条,切片把一句话拦腰截断,模型再强也救不回来。
7.5 网关 502/504 的真实原因
502通常是后端连不上——推理服务刚重启还没就绪,或者网关到服务的连接池打满。504是后端响应超时——都在排队,或者单请求生成太长。处理上,网关对推理服务的超时应该区分"连接超时"和"读取超时",读取超时给到几分钟,因为长文本生成本来就慢;同时要区分流式和非流式的超时策略,流式请求只要首字节按时返回就不该被判定超时。
8. 压测与容量规划:把"能跑"变成"扛得住"
上线前不做压测,等于把风险留给线上。但压测AI服务有个特殊性:不能用固定并发去压,因为请求长度分布对结果影响极大。一个全是短问题的压测结果,和一个全是长文档问答的结果,可能相差三倍以上的资源需求。
8.1 用真实请求分布压测
我的做法是从审计日志里抽样出真实请求,按输入长度和输出长度做聚类,然后按比例回放。这样压出来的数据才有参考价值。压测指标至少要记录并发数、QPS、TTFT P50/P99、TPOT、显存峰值。
| 指标 | 含义 | 我的关注点 |
|---|---|---|
| TTFT P99 | 首token延迟的尾部值 | 决定用户"卡不卡"的体感 |
| TPOT | 每token生成耗时 | 决定长回答的流畅度 |
| 显存峰值 | 压力下最大占用 | 决定安全水位,留20%余量 |
| 排队时长 | 请求等待调度的时间 | 超过1秒基本就该扩容了 |
8.2 并发、TTFT、TPOT 三个指标怎么读
记住一个规律:并发上升时,吞吐先升后降,TTFT持续上升,TPOT相对稳定。拐点出现在批处理已经吃满显存、新请求只能排队的时候。找到这个拐点,就是你的单副本容量上限。
实际配置时,我会取拐点对应并发的70%作为单副本工作区间,超过就触发扩容。这个比例不是拍脑袋——预留30%是为了应对请求长度波动的方差,我曾经在80%水位上跑得很稳,结果来了一批超长文档请求,TTFT直接飙到十几秒。
8.3 扩容策略与成本控制
扩容策略上,GPU服务冷启动慢(模型加载要几分钟),所以不能纯靠HPA按CPU/内存扩容。我的方案是:常驻一个副本兜底,再按排队时长和GPU显存使用率做指标触发扩容,扩容的initialDelaySeconds要给足模型加载时间,否则新Pod还没就绪就被判定失败,反复重启。
成本控制有几招我一直在用:一是按业务高峰时段做定时伸缩,夜间低峰缩到最小副本;二是把非核心业务(比如内部测试、离线摘要)调度到共享的低优先级队列,用抢占式的方式填空闲算力;三是模型分级,高频短问答用小模型,复杂任务才路由到大模型。这三招组合下来,算力账单能降三到四成。
顺带说个容易忽略的点:GPU节点的电费和散热也是成本。我在一个项目里发现机房温度过高导致GPU降频,同样的负载延迟涨了20%,后来调整了机柜风道才恢复。硬件层面的东西,出问题时往往被当成软件问题排查很久。
最后分享一个我在部署运行开源AI中台时反复验证过的习惯:把每一次能跑通的完整配置(镜像Tag、启动参数、环境变量、依赖版本)原样提交到仓库,并配一份"变更记录"说明这次改了什么、为什么改。听起来很笨,但每次线上出问题,能快速回到上一个已知可用的配置,比从头推理快得多。我见过太多团队在深夜对着一个"昨天还好好的"的服务束手无策,就是因为没人记得昨天改了什么。这个习惯不花什么时间,但它救过的场,比任何一个参数调优都多。