GPU 账单正在上涨,模型服务每天处理数十亿个 Token,但很多平台团队仍然只能回答“这组 GPU 花了多少钱”,却无法回答“每个模型、每个团队,甚至每个 Token 到底花了多少钱”。OpenCost 1.121.0 将 Kubernetes 成本分析推进到推理工作负载,重点解决的正是这类归因问题。
这件事的难点不在于把 GPU 小时价格乘起来,而在于把基础设施成本和模型服务产生的业务指标连接起来:Pod 使用了哪些 GPU,属于哪个模型和团队,处理了多少输入与输出 Token,以及这些数据能否稳定地进入成本报表。
从 GPU 成本到推理单位经济学
传统 Kubernetes 成本分析通常围绕命名空间、Deployment、Pod 或节点展开。这个粒度适合回答资源管理问题,例如某个团队使用了多少 CPU、内存和 GPU。但对于推理平台来说,资源账单只是起点。
同一张 GPU 卡可能承载不同模型,也可能同时服务多个租户。模型的上下文长度、批处理策略、量化方式和请求分布都会影响吞吐量。于是,“每 GPU 小时多少钱”并不能直接等价于“每个 Token 多少钱”。
一个更接近业务的计算链路是:
GPU、CPU、内存和集群固定成本
↓
Pod / 工作负载归属
↓
模型服务处理的 Token 数
↓
每请求、每百万 Token 的成本
OpenCost 1.121.0 的意义,在于把成本观测从基础设施账单进一步带到 Kubernetes 上的推理工作负载。实际落地时,平台团队仍需要准备可靠的标签、资源价格和 Token 指标,OpenCost 才能进行有价值的归因。
关键数据链路怎么设计
要得到可用的 Token 成本,至少需要对齐三类数据。
1. 成本数据
成本数据包括 GPU、CPU、内存、网络和持久化存储等资源的价格。GPU 通常是推理服务中最醒目的成本项,但不能忽略节点闲置、跨可用区流量和平台公共组件带来的摊销成本。
2. 归属数据
建议给推理工作负载建立稳定的元数据约定,例如:
app.kubernetes.io/name:服务名称inference.platform/model:模型标识inference.platform/team:负责团队inference.platform/tenant:租户或业务线inference.platform/version:模型或服务版本
标签的价值不只是让 Kubernetes 资源更容易搜索,也决定了成本是否能够按模型、团队和租户聚合。标签一旦频繁变化,历史成本报表就会出现难以解释的断层。
3. Token 指标
模型服务需要暴露至少两类累计计数器:输入 Token 和输出 Token。例如可以使用以下 Prometheus 指标名:
inference_prompt_tokens_total{model="llama-3-8b",team="search"}
inference_completion_tokens_total{model="llama-3-8b",team="search"}
这里的指标名是可以这样实践的约定,并不意味着所有推理框架都会默认使用同名指标。接入时应根据 vLLM、TGI、NVIDIA Triton 或自研服务的实际指标进行映射。
用 PromQL 计算最近一小时的 Token 速率,可以写成:
sum by (model, team) (
rate(inference_prompt_tokens_total[5m])
+ rate(inference_completion_tokens_total[5m])
)
如果成本系统按小时聚合,而 Token 指标来自另一个监控系统,就需要保证时间窗口、标签值和时区一致,否则会出现 GPU 成本已经产生但 Token 尚未统计完成的情况。
一个可改造的 Kubernetes 示例
下面的 Deployment 示例展示了一种标签约定。镜像、端口和启动参数需要替换为实际的模型服务配置。
apiVersion: apps/v1
kind: Deployment
metadata:
name: llama-3-8b
namespace: inference
labels:
app.kubernetes.io/name: llama-3-8b
app.kubernetes.io/component: model-server
inference.platform/model: llama-3-8b
inference.platform/team: search
inference.platform/tenant: consumer-search
inference.platform/version: v2025-01
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: llama-3-8b
template:
metadata:
labels:
app.kubernetes.io/name: llama-3-8b
app.kubernetes.io/component: model-server
inference.platform/model: llama-3-8b
inference.platform/team: search
inference.platform/tenant: consumer-search
inference.platform/version: v2025-01
spec:
containers:
- name: server
image: example.com/inference-server:2025-01
args:
- "--model"
- "/models/llama-3-8b"
ports:
- name: http
containerPort: 8000
resources:
requests:
cpu: "4"
memory: 16Gi
nvidia.com/gpu: "1"
limits:
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "1"
应用前可以检查标签是否出现在 Pod 上:
kubectl apply -f llama-3-8b.yaml
kubectl -n inference get pods \
-l app.kubernetes.io/name=llama-3-8b \
-L inference.platform/model,inference.platform/team,inference.platform/tenant
这段 YAML 只负责让工作负载具备清晰的成本归属。Token 计数器仍需由模型服务暴露,并由 Prometheus 或兼容的指标系统抓取。OpenCost 的具体采集和归因配置应结合当前部署方式、云厂商价格模型以及 1.121.0 的发布说明进行调整。
用一个小脚本验证成本公式
在接入完整成本系统前,可以先用一段独立脚本验证团队对成本口径的理解。下面的脚本假设 GPU 按小时计费,并将 GPU 成本按输入和输出 Token 总量平均分摊。
把 gpu_hours、gpu_hourly_price 和 Token 数替换成一组真实观测值后即可运行:
#!/usr/bin/env python3
from decimal import Decimal, ROUND_HALF_UP
def money(value: Decimal) -> Decimal:
return value.quantize(Decimal("0.000001"), rounding=ROUND_HALF_UP)
gpu_hours = Decimal("12.5")
gpu_hourly_price = Decimal("2.40")
prompt_tokens = Decimal("18500000")
completion_tokens = Decimal("4200000")
infrastructure_cost = gpu_hours * gpu_hourly_price
total_tokens = prompt_tokens + completion_tokens
if total_tokens == 0:
raise SystemExit("Token total is zero; cannot calculate unit cost")
cost_per_token = infrastructure_cost / total_tokens
cost_per_million_tokens = cost_per_token * Decimal("1000000")
print(f"infrastructure_cost=${money(infrastructure_cost)}")
print(f"total_tokens={int(total_tokens)}")
print(f"cost_per_token=${cost_per_token:.12f}")
print(f"cost_per_million_tokens=${money(cost_per_million_tokens)}")
这个公式是成本核算的起点,不是完整财务口径。生产环境还应决定是否纳入 CPU、内存、网络、节点空闲、平台控制面和高可用副本成本,并明确输入 Token 与输出 Token 是否采用不同价格。输出 Token 通常更消耗计算资源,简单平均分摊可能会低估长输出请求的真实成本。
落地时最容易忽略的边界
Token 指标必须是可靠的累计计数器。 使用瞬时 Gauge 或在 Pod 重启后无法正确处理计数器重置,都会让 rate() 结果失真。
模型标签和成本标签要保持同一套命名。 如果 Kubernetes 标签写的是 model_name,服务指标写的是 model,聚合时就必须做额外映射。建议在接入阶段固定字典,而不是让每个团队自由命名。
副本数量会直接影响单位成本。 为了延迟和可用性保留的空闲副本也在消耗 GPU。报表需要区分“已处理请求分摊的成本”和“为了容量而预留的成本”,否则团队可能误以为模型吞吐率越低,单位成本越高只是监控误差。
不要把估算结果当成请求级精确账单。 Token 成本通常是时间窗口内的聚合值,适合容量规划、模型选型和团队成本分摊。若要做到请求级核算,还需要请求 ID、租户、路由和重试等更细粒度的链路数据。
采用清单
可以按下面的顺序推进:
- 为所有推理 Deployment 统一模型、团队、租户和版本标签。
- 确认 GPU、节点和公共基础设施的价格来源与更新时间。
- 从模型服务暴露输入和输出 Token 累计指标。
- 用 PromQL 或离线脚本验证 Token 速率与成本聚合结果。
- 在 OpenCost 1.121.0 中按工作负载、模型和团队检查归因结果。
- 把每百万 Token 成本接入容量规划和模型版本比较,而不是只展示月度 GPU 总账单。
OpenCost 1.121.0 让 Kubernetes 推理成本具备了更贴近业务的观察角度,但工具本身不能替代指标治理和成本口径设计。真正有用的结果,来自三件事同时成立:资源价格可信、工作负载归属稳定、Token 计数完整。只有这样,“GPU 花了多少钱”才会进一步变成“每个模型的服务效率如何,以及每个 Token 是否值得这笔成本”。