第2030篇:LLM成本核算——量化每次推理调用的真实成本
2026/4/30大约 6 分钟
第2030篇:LLM成本核算——量化每次推理调用的真实成本
适读人群:需要控制LLM成本的工程师和技术负责人 | 阅读时长:约17分钟 | 核心价值:建立完整的LLM成本核算体系,做到成本可见、可控、可优化
季度末财务对账的时候,我发现一个奇怪的数字:我们的AI功能月均成本比预算高了45%,但调用量只增加了20%。
成本增长是调用量增长的两倍多。这意味着有些地方出了问题,但我不知道具体在哪里。
把每条调用的token数和成本都记录下来之后,答案出来了:有一个报告生成功能,它的平均输入token数在悄悄增长——因为有人把上下文窗口越用越满,最后变成了动辄5000 tokens的prompt。
这就是成本可见性的价值。
成本的组成
在私有化部署场景,成本不是按token付费,而是GPU的固定成本分摊。但核算方式类似:
私有化部署的成本结构:
- 硬件折旧:GPU服务器按使用年限折旧(通常3年)
- 电费:GPU满负荷运行的功耗(A100大约400W/GPU)
- 运维人力:服务器维护、更新、监控
- 其他:机房、网络、存储
换算为"每token成本":
每token成本 = 总月成本 / 月总token数
当你的总月成本是10万,月处理token数是1亿,
每token成本就是0.001元。云API的成本更直接,按token计费,但也需要跟踪:
GPT-4o(2024年价格参考):
- 输入:$5/1M tokens
- 输出:$15/1M tokens
Claude 3.5 Sonnet:
- 输入:$3/1M tokens
- 输出:$15/1M tokens
一次包含1000 token输入、500 token输出的请求:
成本 = 1000/1M × $5 + 500/1M × $15 = $0.005 + $0.0075 = $0.0125成本追踪系统
/**
* LLM成本追踪拦截器
* 对每次LLM调用记录token数和估算成本
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class LlmCostTracker {
private final LlmCostRepository costRepository;
private final LlmPricingConfig pricingConfig;
/**
* 追踪一次LLM调用的成本
* 在每次调用LLM后调用此方法
*/
public void track(LlmCallContext context) {
int inputTokens = context.getInputTokens();
int outputTokens = context.getOutputTokens();
// 计算成本
double inputCost = inputTokens * pricingConfig.getInputPricePerToken(context.getModelName());
double outputCost = outputTokens * pricingConfig.getOutputPricePerToken(context.getModelName());
double totalCost = inputCost + outputCost;
LlmCostRecord record = LlmCostRecord.builder()
.requestId(context.getRequestId())
.tenantId(context.getTenantId())
.featureName(context.getFeatureName()) // 哪个功能触发的
.userId(context.getUserId())
.modelName(context.getModelName())
.inputTokens(inputTokens)
.outputTokens(outputTokens)
.totalCost(totalCost)
.latencyMs(context.getLatencyMs())
.timestamp(LocalDateTime.now())
.build();
costRepository.save(record);
// 对于异常大的token数,实时告警
if (inputTokens > 5000) {
log.warn("异常大的输入token数: feature={}, inputTokens={}, cost={}",
context.getFeatureName(), inputTokens, totalCost);
}
}
}
/**
* 成本配置(支持多模型、多环境的不同定价)
*/
@Configuration
@ConfigurationProperties(prefix = "llm.pricing")
@Data
public class LlmPricingConfig {
// 单位:元/token
private Map<String, ModelPricing> models = new HashMap<>();
@Data
public static class ModelPricing {
private double inputPricePerToken;
private double outputPricePerToken;
}
public double getInputPricePerToken(String modelName) {
return models.getOrDefault(modelName,
models.get("default")).getInputPricePerToken();
}
public double getOutputPricePerToken(String modelName) {
return models.getOrDefault(modelName,
models.get("default")).getOutputPricePerToken();
}
}# application.yml 成本配置
llm:
pricing:
models:
gpt-4o:
input-price-per-token: 0.000035 # $5/1M tokens,约0.000035元/token
output-price-per-token: 0.000107 # $15/1M tokens
qwen2-7b-local: # 私有化部署,按摊销成本算
input-price-per-token: 0.000001
output-price-per-token: 0.000001
default:
input-price-per-token: 0.000035
output-price-per-token: 0.000107成本异常检测
光记录不够,还需要自动发现异常:
@Service
@RequiredArgsConstructor
@Slf4j
public class LlmCostAnomalyDetector {
private final LlmCostRepository costRepository;
private final AlertService alertService;
/**
* 每小时检查成本异常
*/
@Scheduled(cron = "0 0 * * * *")
public void detectAnomalies() {
LocalDateTime now = LocalDateTime.now();
LocalDateTime oneHourAgo = now.minusHours(1);
LocalDateTime oneDayAgo = now.minusDays(1);
// 1. 检查本小时成本是否异常(与过去7天同时段均值对比)
double currentHourCost = costRepository.sumCostByTimeRange(oneHourAgo, now);
double avgHourlyCost = costRepository.avgHourlyCostForPastWeek(now.getHour());
if (currentHourCost > avgHourlyCost * 3) {
alertService.warn(String.format(
"LLM成本异常:本小时成本%.2f元,是过去7天同时段均值的%.1f倍",
currentHourCost, currentHourCost / avgHourlyCost));
}
// 2. 检查Token使用率超高的功能
List<FeatureCostStat> topFeatures = costRepository.getTopCostFeatures(oneDayAgo, now, 5);
for (FeatureCostStat stat : topFeatures) {
if (stat.getAvgInputTokens() > 3000) {
alertService.warn(String.format(
"功能[%s]平均输入Token数偏高: %.0f tokens/次,建议优化Prompt",
stat.getFeatureName(), stat.getAvgInputTokens()));
}
}
// 3. 检查失败请求造成的成本浪费(调用了LLM但结果被丢弃)
long failedCount = costRepository.countFailedRequests(oneHourAgo, now);
long totalCount = costRepository.countRequests(oneHourAgo, now);
if (totalCount > 100) {
double failRate = (double) failedCount / totalCount;
if (failRate > 0.1) {
double wastedCost = costRepository.sumCostByStatusAndTimeRange(
"FAILED", oneHourAgo, now);
alertService.warn(String.format(
"LLM请求失败率%.1f%%,本小时浪费成本约%.2f元",
failRate * 100, wastedCost));
}
}
}
/**
* 每日成本报告
*/
@Scheduled(cron = "0 8 * * *") // 每天早上8点
public void dailyCostReport() {
LocalDate yesterday = LocalDate.now().minusDays(1);
LocalDateTime start = yesterday.atStartOfDay();
LocalDateTime end = yesterday.atTime(LocalTime.MAX);
double totalCost = costRepository.sumCostByTimeRange(start, end);
long totalRequests = costRepository.countRequests(start, end);
long totalInputTokens = costRepository.sumInputTokensByTimeRange(start, end);
long totalOutputTokens = costRepository.sumOutputTokensByTimeRange(start, end);
String report = String.format("""
昨日LLM成本报告(%s)
总成本:%.2f 元
总请求数:%d 次
总输入Token:%,d
总输出Token:%,d
平均每次请求成本:%.4f 元
平均输入Token/次:%.0f
""",
yesterday,
totalCost,
totalRequests,
totalInputTokens,
totalOutputTokens,
totalCost / totalRequests,
(double) totalInputTokens / totalRequests
);
log.info(report);
alertService.info(report);
}
}成本优化发现
建立了成本追踪体系之后,通过数据可以找到很多优化机会:
@Service
@RequiredArgsConstructor
public class CostOptimizationAnalyzer {
private final LlmCostRepository costRepository;
/**
* 分析成本优化机会
*/
public List<OptimizationOpportunity> analyze(LocalDate startDate, LocalDate endDate) {
List<OptimizationOpportunity> opportunities = new ArrayList<>();
// 机会1:找出输入token数异常大的功能
List<FeatureCostStat> highInputFeatures = costRepository
.getFeaturesBrAvgInputAbove(startDate, endDate, 2000);
for (FeatureCostStat stat : highInputFeatures) {
double potentialSaving = stat.getTotalCost() * 0.5; // 优化50%的token
opportunities.add(OptimizationOpportunity.builder()
.type("PROMPT_OPTIMIZATION")
.featureName(stat.getFeatureName())
.description(String.format(
"平均输入%.0f tokens,通过精简Prompt可节省约50%%成本",
stat.getAvgInputTokens()))
.estimatedMonthlySaving(potentialSaving * 30)
.priority(potentialSaving > 1000 ? "HIGH" : "MEDIUM")
.build());
}
// 机会2:找出可以用小模型替代的功能(高频但任务简单)
List<FeatureCostStat> highVolumeFeatures = costRepository
.getTopCostFeatures(startDate, endDate, 10);
for (FeatureCostStat stat : highVolumeFeatures) {
if (stat.getModelName().contains("gpt-4") &&
stat.getAvgOutputTokens() < 200) {
// 输出很短的任务可能不需要4o
double savingRate = 0.7; // 换成本地模型节省约70%
opportunities.add(OptimizationOpportunity.builder()
.type("MODEL_DOWNGRADE")
.featureName(stat.getFeatureName())
.description("短输出任务可以考虑使用本地7B模型替代,节省约70%成本")
.estimatedMonthlySaving(stat.getTotalCost() * savingRate * 30)
.priority("HIGH")
.build());
}
}
// 机会3:找出可以缓存的高重复率请求
List<HighRepeatQuery> repeatQueries = costRepository
.findHighRepeatQueries(startDate, endDate, 0.5); // 50%以上相似的请求
if (!repeatQueries.isEmpty()) {
double totalRepeatCost = repeatQueries.stream()
.mapToDouble(HighRepeatQuery::getRepeatCost).sum();
opportunities.add(OptimizationOpportunity.builder()
.type("CACHING")
.description(String.format("发现%d类高重复请求,引入缓存可节省约%.1f%%成本",
repeatQueries.size(), totalRepeatCost / getTotalCost(startDate, endDate) * 100))
.estimatedMonthlySaving(totalRepeatCost * 30)
.priority("HIGH")
.build());
}
// 按预期节省金额排序
opportunities.sort(Comparator.comparingDouble(
OptimizationOpportunity::getEstimatedMonthlySaving).reversed());
return opportunities;
}
private double getTotalCost(LocalDate start, LocalDate end) {
return costRepository.sumCostByTimeRange(
start.atStartOfDay(), end.atTime(LocalTime.MAX));
}
}成本可见性仪表板
把这些数据汇聚成一个管理层可以看的仪表板:
/**
* 成本看板API
* 供前端展示用
*/
@RestController
@RequestMapping("/api/admin/llm-cost")
@RequiredArgsConstructor
public class LlmCostDashboardController {
private final LlmCostRepository costRepository;
private final CostOptimizationAnalyzer optimizer;
@GetMapping("/summary")
public CostSummaryDTO getSummary(
@RequestParam(defaultValue = "7") int days) {
LocalDateTime endTime = LocalDateTime.now();
LocalDateTime startTime = endTime.minusDays(days);
return CostSummaryDTO.builder()
.totalCost(costRepository.sumCostByTimeRange(startTime, endTime))
.totalRequests(costRepository.countRequests(startTime, endTime))
.avgCostPerRequest(costRepository.avgCostPerRequest(startTime, endTime))
.avgInputTokens(costRepository.avgInputTokens(startTime, endTime))
.topFeaturesByCost(costRepository.getTopCostFeatures(
startTime.toLocalDate(), endTime.toLocalDate(), 5))
.costTrend(costRepository.getDailyCostTrend(
startTime.toLocalDate(), endTime.toLocalDate()))
.optimizationOpportunities(optimizer.analyze(
startTime.toLocalDate(), endTime.toLocalDate()))
.build();
}
}成本管理的本质是可见性驱动优化。很多成本浪费不是因为工程师不在乎,而是因为看不见——不知道哪个功能最贵、哪个请求最浪费、哪里可以优化。
把每次LLM调用的成本记录下来,问题自然就浮出水面了。
