大模型多租户公平计费方案:Token 不再均摊的成本分摊模型

发布时间:2026/7/16 18:46:38
大模型多租户公平计费方案:Token 不再均摊的成本分摊模型 大模型多租户公平计费方案Token 不再均摊的成本分摊模型一、一个月烧了 8 万的 API 费用内部 6 个团队都说不关我事公司部署了一个共享的 GPT-4 调用网关6 个业务团队共用。财务对账单上只有一个数本月 OpenAI API 费用 8.3 万。CTO 拉会讨论客服团队说我们一天才几百次调用营销团队说我们用的是免费模型AI 团队说我们只做测试没有线上流量。没有使用数据就没有办法分账。大模型 API 的成本分摊比云主机更复杂云主机的计费维度是时间EC2 按小时或调用次数Lambda 按次数但大模型的计费维度是 Token。Token 的特殊性在于同样的请求不同模型的价格不同GPT-4 vs GPT-3.5 差距可达 20 倍同样的 prompt 不同长度消耗也不同。二、三层计费模型设计三层计费模型Token 计数层记录每次调用的 prompt tokens、completion tokens 和使用的模型名称。这是最底层的数据精度决定分摊的公平性。成本计算层根据模型定价表可以定期同步各厂商的最新价格将 Token 转换为金额。不同模型的单价差异巨大必须分模型计算。费用分摊层将成本分配到团队或个人。根据场景选择分摊策略。三、分摊策略的核心矛盾准确 vs 公平按实际用量分摊是最简单的——记好每个 API Key 的 Token 消耗按比例分摊。但这会导致先到先得的问题如果一个团队发了 10 个高消耗请求把本月预算假设公司预存了 5 万全用完了其他团队就没得用了。按加权共享分摊解决这个问题所有共享模型的调用成本先按团队权重系数分配再用实际用量核销。权重可以根据团队业务优先级动态调整。例如AI 研发团队权重 1.0按比例分摊客服团队权重 0.3只承担 30% 的共享成本营销团队权重 2.0预算更高承担 2 倍但如果某个团队用量低于权重应摊的比例多缴的部分应该退还——这就是先验分摊 后验结算。配额先行模式每个团队在月初分配固定预算额度如一万元当消耗达到 80% 时告警达到 100% 时截断。这种模式简单粗暴适合成本敏感的团队。四、Go 实现多租户 Token 分摊引擎package costallocation import ( context fmt log sync time ) // 模型定价 type ModelPricing struct { Model string PromptPricePer1K float64 CompletionPricePer1K float64 } var DefaultPricing map[string]ModelPricing{ gpt-4: {Model: gpt-4, PromptPricePer1K: 0.03, CompletionPricePer1K: 0.06}, gpt-4-turbo: {Model: gpt-4-turbo, PromptPricePer1K: 0.01, CompletionPricePer1K: 0.03}, gpt-3.5-turbo: {Model: gpt-3.5-turbo, PromptPricePer1K: 0.0005, CompletionPricePer1K: 0.0015}, claude-3-opus: {Model: claude-3-opus, PromptPricePer1K: 0.015, CompletionPricePer1K: 0.075}, } // 使用记录 type UsageRecord struct { ID string TenantID string Model string PromptTokens int CompletionTokens int Timestamp time.Time Cost float64 } // 分摊配置 type AllocationConfig struct { Strategy string // usage-based / weighted-shared / quota-first // 加权分摊的权重系数 TenantWeights map[string]float64 // 配额先行模式的预算 TenantQuotas map[string]float64 // 结算周期 SettlementCycle time.Duration // 默认按月 } // 分摊引擎 type CostAllocator struct { config AllocationConfig pricing map[string]ModelPricing mu sync.RWMutex // 当前结算周期的使用量 usageByTenant map[string]*TenantUsage cycleStart time.Time } type TenantUsage struct { TenantID string TotalCost float64 PromptTokens int CompletionTokens int Records []UsageRecord } func NewCostAllocator(config AllocationConfig) *CostAllocator { return CostAllocator{ config: config, pricing: DefaultPricing, usageByTenant: make(map[string]*TenantUsage), cycleStart: time.Now(), } } // RecordUsage 记录一次 API 调用 func (ca *CostAllocator) RecordUsage(ctx context.Context, record UsageRecord) error { ca.mu.Lock() defer ca.mu.Unlock() // 1. 计算本次调用成本 pricing, ok : ca.pricing[record.Model] if !ok { return fmt.Errorf(未知模型: %s, record.Model) } promptCost : float64(record.PromptTokens) / 1000.0 * pricing.PromptPricePer1K completionCost : float64(record.CompletionTokens) / 1000.0 * pricing.CompletionPricePer1K record.Cost promptCost completionCost // 2. 累加到租户 tu, ok : ca.usageByTenant[record.TenantID] if !ok { tu TenantUsage{TenantID: record.TenantID} ca.usageByTenant[record.TenantID] tu } tu.TotalCost record.Cost tu.PromptTokens record.PromptTokens tu.CompletionTokens record.CompletionTokens tu.Records append(tu.Records, record) // 3. 配额检查quota-first 模式 if ca.config.Strategy quota-first { quota, ok : ca.config.TenantQuotas[record.TenantID] if ok tu.TotalCost quota*0.8 { log.Printf([Cost] 租户 %s 已消耗 %.1f%% 配额 (%.4f/%.4f), record.TenantID, tu.TotalCost/quota*100, tu.TotalCost, quota) } } return nil } // AllocateCosts 执行分摊结算 func (ca *CostAllocator) AllocateCosts(ctx context.Context) map[string]float64 { ca.mu.RLock() defer ca.mu.RUnlock() result : make(map[string]float64) switch ca.config.Strategy { case usage-based: result ca.usageBasedAllocation() case weighted-shared: result ca.weightedSharedAllocation() case quota-first: result ca.quotaFirstAllocation() default: result ca.usageBasedAllocation() } return result } // 策略1: 按实际用量分摊 func (ca *CostAllocator) usageBasedAllocation() map[string]float64 { result : make(map[string]float64) var totalCost float64 for _, tu : range ca.usageByTenant { totalCost tu.TotalCost } for _, tu : range ca.usageByTenant { if totalCost 0 { result[tu.TenantID] tu.TotalCost } } return result } // 策略2: 加权分摊 func (ca *CostAllocator) weightedSharedAllocation() map[string]float64 { result : make(map[string]float64) // 1. 计算总成本和总权重 var totalCost float64 var totalWeight float64 for _, tu : range ca.usageByTenant { totalCost tu.TotalCost } for _, w : range ca.config.TenantWeights { totalWeight w } if totalCost 0 || totalWeight 0 { return result } // 2. 按权重预先分摊 preAllocated : make(map[string]float64) for tenantID, weight : range ca.config.TenantWeights { preAllocated[tenantID] totalCost * weight / totalWeight } // 3. 后验结算多用多补少用退款 for _, tu : range ca.usageByTenant { pre : preAllocated[tu.TenantID] actual : tu.TotalCost result[tu.TenantID] max(pre, actual) } return result } // 策略3: 配额先行 func (ca *CostAllocator) quotaFirstAllocation() map[string]float64 { result : make(map[string]float64) for _, tu : range ca.usageByTenant { quota, ok : ca.config.TenantQuotas[tu.TenantID] if !ok { result[tu.TenantID] tu.TotalCost continue } if tu.TotalCost quota { // 未超配额按实际计费 result[tu.TenantID] tu.TotalCost } else { // 超配额配额内按正常费率超额部分加收 50% overage : tu.TotalCost - quota result[tu.TenantID] quota overage*1.5 } } return result } // 结算并重置 func (ca *CostAllocator) Settle(ctx context.Context) (map[string]float64, error) { ca.mu.Lock() defer ca.mu.Unlock() // 1. 执行分摊计算 result : make(map[string]float64) for tenantID, tu : range ca.usageByTenant { result[tenantID] tu.TotalCost } // 2. 生成结算报告此处省略持久化到数据库逻辑 log.Printf([Cost] 结算周期结束, 参与租户: %d, len(result)) for tenantID, cost : range result { log.Printf( %s: $%.4f, tenantID, cost) } // 3. 重置 ca.usageByTenant make(map[string]*TenantUsage) ca.cycleStart time.Now() return result, nil } // 查询当前使用量 func (ca *CostAllocator) GetTenantUsage(tenantID string) (*TenantUsage, error) { ca.mu.RLock() defer ca.mu.RUnlock() tu, ok : ca.usageByTenant[tenantID] if !ok { return nil, fmt.Errorf(租户 %s 无使用记录, tenantID) } return tu, nil } // 使用示例 func ExampleAllocation() { config : AllocationConfig{ Strategy: weighted-shared, TenantWeights: map[string]float64{ team-ai: 1.0, team-service: 0.3, team-marketing: 2.0, }, } allocator : NewCostAllocator(config) // 模拟记录使用 _ allocator.RecordUsage(context.Background(), UsageRecord{ TenantID: team-ai, Model: gpt-4, PromptTokens: 2000, CompletionTokens: 500, }) _ allocator.RecordUsage(context.Background(), UsageRecord{ TenantID: team-marketing, Model: gpt-4-turbo, PromptTokens: 5000, CompletionTokens: 1500, }) // 月底结算 result, _ : allocator.Settle(context.Background()) for tenant, cost : range result { fmt.Printf(%s 本月费用: $%.4f\n, tenant, cost) } }五、总结大模型成本分摊的核心矛盾共享模型的共用性和每个团队的独立核算需求之间的张力。三层计费模型Token 计数 → 成本计算 → 费用分摊提供了一条可操作的路径。三种策略的适用场景按实际用量适合团队间模型使用模式差异大的情况客服团队用便宜模型AI 团队用贵模型加权分摊适合共享同一个模型实例、难以精确区分每次调用归属的场景配额先行适合成本预算严格、需要硬约束的场景工程落地时最容易被忽略的细节模型价格是变化的厂商调价、新模型上线定价表需要支持热更新。另外prompt tokens 和 completion tokens 的分开计费很关键——有些团队 prompt 很长但期望短回复另一些团队反之。混在一起计费会让我觉得不公平的团队无法追溯具体原因。