2996 字
约 9 分钟
18
S23-X06 · 部署形态:自建、托管与混合

选部署形态时最容易被忽略的一点:它不是技术选型,是成本结构选型。同一套模型,自建付出的是固定成本和人的时间,托管付出的是单价乘 token 数,混合付出的是两套系统的复杂度。

先算清这三种成本曲线在什么流量下交叉,再谈运维能力。凭"我们有 GPU"或"云上更省事"拍板,通常会在半年后以一次账单或一次事故的形式还回来。


1. 三种形态的成本结构

维度 自建 托管 混合
成本性质 固定(GPU 租金/折旧 + 人力) 变动(按 token 或按调用) 固定底载 + 变动峰值
边际成本 接近零(未饱和时) 恒定单价 分段
前期投入 高(采购、部署、调优) 近零 中
运维负担 驱动、CUDA、显存、扩容全要人 供应商承担 两套都要会
弹性上限 受限于已有卡数 受限于供应商配额 高
数据流向 完全自控 出域到供应商 按分流决定
出问题时的锅 自己的 供应商 + 自己的调用层 定位最难
月成本(自建) ≈ GPU 卡数 × 单卡月租(或折旧+电) + 人力(人月 × 人月成本) + 带宽/存储
月成本(托管) ≈ 输入 token × 输入单价 + 输出 token × 输出单价 + 平台费
交叉点:使两式相等的月 token 量,即"自建更划算"的流量门槛

交叉点必须用自己的单价与人力成本算,别人的基准值只能当量级参考。有一点几乎总成立:流量低于某个门槛时,人力成本那一项就足以让自建不划算——招一个能处理驱动、OOM、调度、扩容的人,成本常常高于托管的账单。


2. 自建的适用条件

条件 判断标准 不满足时的后果
流量稳定且高 底载长期高于交叉点,GPU 常年饱和 卡空转,单位成本比托管贵
数据不能出域 合规或合同明确要求 走托管即违规
需要定制模型 自训权重、私有 tokenizer、特殊采样 托管多数不让你换 kernel
需要极低延迟 同机房、无跨网、可 pin 住版本 公网托管多一层不可控
有 GPU 运维能力 有人能接 OOM、驱动升级、故障 事故恢复时间远长于托管

"数据不能出域"是硬约束,"延迟更低"是软约束——后者常常被低估:托管服务如果就在同一 region,网络延迟可能只差几毫秒,而这层差距在端到端体验里往往被前端渲染淹没。


3. 托管的适用条件

条件 说明
流量波动大 白天峰值是夜间数倍,按量计费天然贴合
团队没有 GPU 运维 没人值夜班处理 GPU 掉卡
试错期 模型换代快,不想为每个模型买卡
需要多模型并存 按请求选模型,供应商侧一起解决
长尾需求 偶尔的超大上下文请求,托管按次付费更合理

托管要提前确认三件事:配额上限(并发与 RPM/TPM)、限流行为(429 还是排队)、版本稳定性(模型是否会被静默更新)。这三件都直接影响客户端设计。


4. 混合的分流判据

判据 分流规则 优点 风险
敏感级别 含隐私字段 → 自建;公开内容 → 托管 合规清晰 分类错了就是事故
延迟要求 交互式 → 自建;离线批处理 → 托管 体验与成本兼顾 两套链路都要维护
成本阈值 月累计 token 超阈值后切自建 直接省钱 切换抖动、缓存不共享
可用性兜底 自建为主,托管做溢出与故障接管 抗峰值 契约不一致导致行为差异
模型能力 简单任务自建小模型,复杂任务托管大模型 单位成本最优 路由判定本身要准
def route(req):
    if req.contains_pii or req.tenant.region == "cn":
        return "self_hosted"
    if req.latency_budget_ms < 800 and req.stream:
        return "self_hosted"
    if req.prompt_tokens > 32000 or req.task in HEAVY_TASKS:
        return "managed"           # 长上下文不占本地 KV
    return "self_hosted" if local_pool.has_capacity() else "managed"

路由要落日志:每次分流都记录判据与结果,否则"为什么这个请求走了托管"永远查不清。


5. 自建的关键运维项

运维项 具体内容 常见失手
驱动与 CUDA 驱动版本、CUDA runtime、框架 wheel 三者匹配 升驱动导致 kernel 不兼容
显存碎片 长跑进程的缓存分配器碎片 重启才恢复,需定期滚动
模型热更新 新权重加载、双缓冲、灰度 直接替换文件导致半加载
多副本负载均衡 会话粘性或共享 KV 前缀 轮询把长请求分到已饱和副本
健康检查 测真实推理,不测端口 端口通但队列排到超时
日志与指标 TTFT、TPOT、队列长度、抢占计数 只看 GPU 利用率

健康检查这一项值得单独说:/health 返回 200 只证明进程活着。真实推理健康检查要发一个小请求并限超时:

# 真实推理探针:小 prompt + 严格超时,失败即摘流量
curl -s -m 3 http://127.0.0.1:8000/v1/completions \
  -H 'Content-Type: application/json' \
  -d '{"model":"MODEL","prompt":"ping","max_tokens":1,"temperature":0}' \
  | grep -q '"choices"' || exit 1
# Kubernetes 探针示例:readiness 用真实推理,liveness 只查进程
readinessProbe:
  exec:
    command: ["/bin/sh", "-c", "curl -s -m 3 localhost:8000/health && curl -s -m 3 -X POST localhost:8000/v1/completions -d '{\"model\":\"MODEL\",\"prompt\":\"ping\",\"max_tokens\":1}' | grep -q choices"]
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3
startupProbe:
  tcpSocket: {port: 8000}
  failureThreshold: 60
  periodSeconds: 10

6. 弹性与冷启动

扩容的最小粒度不是"一个 Pod",而是一次模型加载所需的时间。7B 从磁盘到可服务通常是几十秒到几分钟量级,70B 或网络存储上更久——具体按你的存储带宽、模型大小、是否启用权重预取核对:

加载时间 ≈ 权重体积 / 存储带宽 + 引擎初始化(编译 kernel、建 CUDA 图)
扩容有效时刻 = 触发时刻 + 加载时间 + 预热请求时间

如果这个时刻晚于客户端超时,扩容等于没扩。三种缓解:

做法 效果 代价
预热池(常驻备用副本) 秒级接管 常驻卡成本
权重放本地 NVMe / 内存盘 缩短加载 存储成本
提前扩容(按历史曲线定时) 峰值前就位 闲时浪费

7. 成本可观测:三个口径

口径 计算方式 用在哪
按请求 请求数 × 平均单价 对外报价、按租户分摊
按 token 输入/输出 token 分别统计 与托管账单对账
按 GPU 小时 卡数 × 时长 × 单价 自建真实成本、容量决策
# 每次调用落一条成本记录,字段与账单口径对齐
rec = {"request_id": rid, "model": m, "in_tok": it, "out_tok": ot,
       "gpu_hours": gpu_seconds / 3600, "route": route_name,
       "cost_est": it * PIN + ot * POUT if route_name == "managed" else gpu_seconds / 3600 * GPU_HOUR}

三个口径必须能相互对上,否则"按 token 算很便宜、按 GPU 小时算很贵"的争论永远没有结论。句柄里最小可用的指标是每千 token 的 GPU 秒数,它同时反映调度效率与模型大小。


8. 容量规划

副本数 ≈ ceil(峰值并发 × 单请求显存 / (单卡可用显存 × 安全系数)) × 冗余系数
import math
def replicas(peak_concurrency, kv_per_req_gib, weights_gib, card_gib,
             util=0.9, overhead=1.5, redundancy=1.3):
    per_replica = int((card_gib * util - weights_gib - overhead) // kv_per_req_gib)
    per_replica = max(1, per_replica)
    return math.ceil(peak_concurrency / per_replica * redundancy), per_replica

print(replicas(200, 1.0, 3.5, 24))   # 峰值并发 200、单请求 KV 1 GiB、INT4 7B、24G 卡

冗余系数 不是拍脑袋:单副本故障、滚动升级、以及"峰值比预估高 20%"都要靠它吸收。冗余 1.3 意味着常态利用率约 77%,这个水平对 GPU 服务是可接受的。


9. 合规与数据流向

落点 可能留下什么 处理
应用日志 完整 prompt 与输出 脱敏、截断、按级别采样
引擎日志 请求 ID、token 数 一般不含内容,核对确认
前缀缓存 提示词前缀的 KV 与哈希 跨租户共享要隔离
监控与追踪 span 属性里常带 prompt 属性白名单
托管侧 供应商的日志与留存策略 按合同与开关配置

前缀缓存这一项最容易被忘:混合部署时若多个租户共用一份前缀缓存,缓存键里必须带租户维度,否则等于把别人的上下文块挂在自己名下。


10. 排错:五个高频坑

现象 根因 处理
自建单位成本反而更高 只算 GPU 租金,没算人力与闲置 用第 1 节公式重算,含人力
扩容了还是超时 冷启动时间 > 客户端超时 预热池或提前扩容
健康检查全绿但请求排队 探针只测端口//health 改真实推理探针 + 队列长度指标
托管 429 后大面积失败 客户端无退避、无重试预算 指数退避 + 抖动 + 降级路由
灰度新模型后指标突变 参数、模板、量化版本未同步 发布单元带完整配置快照

11. 边界:形态之外的变量

变量 影响 说明
网络位置 端到端延迟 同 region 与跨地域差一个量级
模型许可证 能否商用/托管 先看许可,再谈成本
供应商锁定 迁移成本 接口抽象层能缓解,但采样行为难对齐
峰值倍率 弹性需求 峰值/均值比越高,越偏向托管
团队规模 隐性成本 一人团队的自建风险被严重低估

12. 自测

  1. 自建与托管的成本交叉点由哪几项决定?漏掉哪一项最常导致误判?
  2. 混合部署里,"数据敏感级别"这条判据为什么优先级高于成本判据?
  3. 健康检查通过但用户仍在等待,问题最可能出在哪个环节?
  4. 为什么说扩容的最小粒度是"一次模型加载时间",而不是"一个副本"?
  5. 成本核算的三种口径如何互相对账?哪个指标最能反映调度效率?
  6. 多个租户共用前缀缓存时,缓存键为什么必须带租户维度?
S23-X06 · 部署形态:自建、托管与混合
https://www.clxhxhhr.top/posts/5911/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。