评分方法论
本文档定义 Tier6+Model 平台的部署方案评分体系,用于对 LLM 推理部署方案进行多维度量化评估。
行业参考与定位
现有行业基准
MLPerf Inference
MLPerf 是目前 LLM 推理领域最权威的基准测试。其核心思路是:在 P99 延迟约束(SLO)下最大化 QPS(Queries Per Second)。评测分为 Offline(纯吞吐)和 Server(延迟约束下吞吐)两个场景,不产生单一综合评分。
Goodput (有效吞吐)
学术界(如 Distserve、Splitwise 等论文)提出 Goodput 概念:只计入满足 SLO 的请求吞吐。一个方案即使总 QPS 很高,但大量请求超时,则 Goodput 很低。这体现了"延迟约束优先"的思想。
Roofline 模型
Roofline 模型将工作负载按算术强度(FLOPs/Byte)分为计算密集型和访存密集型。对于 LLM 推理:
- Prefill 阶段:大矩阵乘法,算术强度高,属于计算密集型 -> MFU 是关键指标
- Decode 阶段:逐 token 生成,每次读取大量 KV Cache,属于访存密集型 -> MBU 是关键指标
这是理解效率评分设计的基础。
为什么没有通用标准
LLM 推理部署没有单一的"最优评分标准",原因如下:
- 场景差异大:实时对话要求低延迟(TTFT < 200ms),批量处理只关心吞吐,成本敏感场景优先看 $/TPS
- 指标之间存在本质矛盾:增大 batch size 提升吞吐但牺牲延迟,增加 TP 降低延迟但降低芯片效率
- 硬件差异:不同芯片的计算/访存/通信能力比例不同,"好方案"的标准因硬件而异
本系统定位
Tier6+Model 是一个部署方案探索与对比工具,评分体系的目标是:
- 提供 0-100 的归一化分数,便于方案之间快速比较
- 多维度评估,避免单一指标掩盖短板
- 使用几何平均惩罚极端短板,引导用户关注瓶颈
- SLO 参数可配置,适配不同业务场景
推荐评分体系
六维度评分
删除原有的 balance_score(原因见第 3 节),新增 cost_score(基于 DFOP)。六个维度,每个输出 0-100 分。
维度 1: 延迟 (Latency Score)
输入: TTFT (ms), TPOT (ms), SLO 配置
设计思想: 采用 SLO 约束式评分。满足 SLO 目标即可获得高分,超过 SLO 后分数快速衰减。这与 MLPerf Server 场景和 Goodput 的思想一致。
公式:
ttft_score = 100 * min(1, SLO_TTFT / actual_TTFT)
tpot_score = 100 * min(1, SLO_TPOT / actual_TPOT)
latency_score = ttft_score * 0.5 + tpot_score * 0.5
默认 SLO 参数(可配置):
| 场景 | SLO_TTFT (ms) | SLO_TPOT (ms) |
|---|---|---|
| 实时对话 | 200 | 50 |
| 在线服务 | 500 | 100 |
| 批量处理 | 2000 | 200 |
得分示例(实时对话场景):
- TTFT=100ms, TPOT=30ms ->
min(1, 200/100)*50 + min(1, 50/30)*50 = 50 + 50 = 100 - TTFT=400ms, TPOT=30ms ->
min(1, 200/400)*50 + min(1, 50/30)*50 = 25 + 50 = 75 - TTFT=100ms, TPOT=100ms ->
min(1, 200/100)*50 + min(1, 50/100)*50 = 50 + 25 = 75
边界处理: 当 actual_latency <= 0 时,使用 epsilon (1e-6) 替代,避免除零。
维度 2: 吞吐 (Throughput Score)
输入: TPS_per_chip, 模型参数量 (B)
设计思想: 使用 TPS_per_chip 而非总 TPS,因为它反映了单芯片的成本效益。归一化基准根据模型大小调整,因为大模型天然单芯片吞吐低。
公式:
ratio = tps_per_chip / baseline_tps
throughput_score = min(100, ratio * 100)
基准 TPS/Chip(按模型大小):
| 模型大小 | baseline_tps | 依据 |
|---|---|---|
| < 10B | 100 | 小模型单芯片可承载,吞吐应较高 |
| 10B - 100B | 30 | 中等模型,典型 TP=2-8 |
| 100B - 500B | 10 | 大模型,典型 TP=8-16 |
| > 500B | 3 | 超大模型 (MoE),TP+EP 分布 |
边界处理: 得分下限为 0,上限为 100。tps_per_chip <= 0 时得 0 分。
维度 3: 效率 (Efficiency Score)
输入: MFU (0-1), MBU (0-1)
设计思想: 基于 Roofline 模型,Prefill 是计算密集型看 MFU,Decode 是访存密集型看 MBU。一个好的部署方案应该在各自的瓶颈侧达到高利用率。因此取两者的最大值,反映"瓶颈资源是否被充分利用"。
数据来源: MFU 取自 Prefill 阶段(计算密集),MBU 取自 Decode 阶段(访存密集)。两者已分别对应各自阶段的瓶颈资源,因此 max 的含义是"两个阶段中更高效的那个"。若某阶段效率低,其影响会通过延迟维度(TTFT/TPOT)间接体现,无需效率维度重复惩罚。
公式:
efficiency_score = max(MFU, MBU) * 100
为什么用 max 而不是加权平均:
- 加权平均
MFU*0.6 + MBU*0.4物理上没有意义:Prefill 的 MBU 低和 Decode 的 MFU 低都是正常的,加权只会拉低分数 max(MFU, MBU)的含义是:至少有一个阶段的瓶颈资源利用率达到了该水平- 如果 MFU 和 MBU 都低,说明计算和访存都没打满,方案确实低效
得分示例:
- MFU=0.7, MBU=0.3 (典型 Prefill 密集) ->
max(0.7, 0.3) * 100 = 70 - MFU=0.2, MBU=0.8 (典型 Decode 密集) ->
max(0.2, 0.8) * 100 = 80 - MFU=0.1, MBU=0.1 (低效方案) ->
max(0.1, 0.1) * 100 = 10
维度 4: 内存 (Memory Score)
输入: memory_used_gb, memory_capacity_gb
设计思想: 内存利用率存在一个"甜区"。太低说明芯片容量浪费(可以用更小/更少的芯片),太高则接近 OOM 风险,缺乏弹性(无法增大 batch size 或增加 KV Cache 长度)。
公式(分段函数):
utilization = memory_used_gb / memory_capacity_gb
if utilization <= 0.6:
score = 60 + utilization / 0.6 * 30 # [60, 90] 利用不足但可接受
elif utilization <= 0.85:
score = 90 + (utilization - 0.6) / 0.25 * 10 # [90, 100] 最优区间
elif utilization <= 0.95:
score = 100 - (utilization - 0.85) / 0.10 * 60 # [100, 40] 开始紧张,快速下降
else:
score = max(5, 40 - (utilization - 0.95) / 0.05 * 35) # [40, 5] 危险区域
设计要点:
- 最优区间 60%-85%:留有足够余量用于 KV Cache 增长和 batch size 调整
- 超过 85% 快速衰减:接近 OOM 是严重风险
- 低于 60% 也给出较高分(60+):内存富余不是严重问题,只是不够经济
- 最低分保底 5 分:避免几何平均中出现接近 0 的值
边界处理: memory_capacity_gb <= 0 时返回 0 分。
维度 5: 通信 (Communication Score)
输入: 通信时间, 计算时间(Prefill + Decode 阶段)
设计思想: 通信开销是并行扩展的核心代价。通信占比越低,说明并行策略越高效。
公式:
comm_ratio = total_comm_time / (total_comm_time + total_compute_time)
communication_score = max(10, (1 - comm_ratio) * 100)
其中:
total_comm_time = prefill_comm_time + decode_comm_time
total_compute_time = prefill_compute_time + decode_compute_time
得分示例:
- 通信占比 5% -> 95 分(优秀,通信几乎可忽略)
- 通信占比 20% -> 80 分(良好,TP=8 典型值)
- 通信占比 50% -> 50 分(较差,通信成为瓶颈)
- 通信占比 80% -> 20 分(极差,芯片大量空闲等待通信)
数据来源说明: comm_time 和 compute_time 来自后端仿真器的累积值。默认情况下两者是独立累加的(无 overlap)。若启用了 TBO 或 Ring Attention overlap,则 comm_time 已是 overlap 后的残留值。因此通信评分始终反映"实际暴露在外的通信开销占比"。
边界处理: 总时间 <= 0 时返回 0 分(无数据)。最低分 10 分。
维度 6: 成本效率 (Cost Score) — DFOP
输入: DFOP ($/TPS), 模型参数量 (B)
设计思想: DFOP (Dollar per TPS) 是唯一将经济成本纳入评分的维度。它衡量"产生 1 TPS 吞吐能力需要多少美元投入",越低越好。DFOP 已在后端 cost_analysis.py 中计算:dfop = total_cost / tps。
与 Throughput 维度的区别:Throughput 是纯技术视角(TPS/chip -> 芯片利用得好不好),DFOP 是经济视角($/TPS -> 花的钱值不值)。同样的 TPS/chip,用 $2,500 的 SG2262 和 $6,300 的 B200 达成,DFOP 差距很大。
公式:
dfop_score = 100 * min(1, baseline_dfop / actual_dfop)
基准 DFOP(按模型大小,基于典型部署成本和期望吞吐推导):
| 模型大小 | baseline_dfop ($/TPS) | 推导依据 |
|---|---|---|
| < 10B | 50 | 单芯片 ~$3,000, 期望 TPS ~60 |
| 10B - 100B | 500 | 4-8 芯片 ~$15k, 期望 TPS ~30 |
| 100B - 500B | 5,000 | 16-32 芯片 ~$60k, 期望 TPS ~12 |
| > 500B | 20,000 | 64+ 芯片 ~$160k+, 期望 TPS ~8 |
得分示例(100-500B 模型,baseline = $5,000/TPS):
- DFOP=$3,000 ->
min(1, 5000/3000)*100 = 100(优于基准,满分) - DFOP=$5,000 ->
min(1, 5000/5000)*100 = 100(达到基准) - DFOP=$10,000 ->
min(1, 5000/10000)*100 = 50(成本偏高) - DFOP=$25,000 ->
min(1, 5000/25000)*100 = 20(成本过高)
与 Throughput 的相关性说明: DFOP 和 Throughput 都包含 TPS,存在部分相关性。在几何平均中,一个 TPS 极低的方案会在两个维度都受惩罚。这是合理的 -- 既低效又不划算,应该双重体现。若用户认为惩罚过重,可降低其中一个维度的权重。
边界处理: actual_dfop <= 0 时返回 0 分(TPS 为 0 导致 DFOP 无法计算)。最低保底 5 分。
总分计算: 加权几何平均
公式:
overall = (latency^w1 * throughput^w2 * efficiency^w3 * memory^w4 * communication^w5 * cost^w6) ^ (1 / (w1+w2+w3+w4+w5+w6))
默认权重:
| 维度 | 权重 | 说明 |
|---|---|---|
| latency | 0.20 | 用户体验直接感知 |
| throughput | 0.20 | 单芯片吞吐能力 |
| efficiency | 0.20 | 硬件利用率 |
| memory | 0.15 | 可行性约束 |
| communication | 0.10 | 扩展效率 |
| cost (DFOP) | 0.15 | 部署成本效率 |
为什么用几何平均替代加权算术平均:
加权算术平均的问题:一个维度极差可以被其他维度"补偿"。例如 latency=10, throughput=90, efficiency=90, memory=90, communication=90,算术平均约 74 分,看起来"还行"。
几何平均的优势:
- 短板效应: 任何一个维度的极低分都会显著拉低总分
- 乘法性质: 各维度之间是"与"的关系 -- 一个好的方案需要各方面都合格
- 物理直觉: 类似于木桶效应,部署方案的整体质量受限于最差的维度
计算示例:
方案 A: latency=80, throughput=80, efficiency=75, memory=85, communication=90, cost=80
算术: 80*0.20 + 80*0.20 + 75*0.20 + 85*0.15 + 90*0.10 + 80*0.15 = 80.75
几何: exp(0.20*ln80 + 0.20*ln80 + 0.20*ln75 + 0.15*ln85 + 0.10*ln90 + 0.15*ln80) = 80.5
-> 均衡方案,两种方法差异不大
方案 B: latency=10, throughput=95, efficiency=90, memory=90, communication=95, cost=85
算术: 10*0.20 + 95*0.20 + 90*0.20 + 90*0.15 + 95*0.10 + 85*0.15 = 71.75
几何: exp(0.20*ln10 + 0.20*ln95 + 0.20*ln90 + 0.15*ln90 + 0.10*ln95 + 0.15*ln85) = 55.5
-> 延迟极差的方案,几何平均严厉惩罚到 56 分
避免 log(0) 问题: 几何平均等价于 exp(sum(wi * ln(si)) / sum(wi))。当任意 si = 0 时 ln(0) 无定义。实现时对所有维度分数取下限 epsilon:
safe_score = max(score, EPSILON) # EPSILON = 1e-3
这保证了即使某维度接近 0 分,总分也会被拉到极低值(而非产生数学错误)。
用 cost_score (DFOP) 替代 balance_score 的理由
balance_score 当前实现的问题
现有的 balance_score 惩罚 MFU 和 MBU 之间的差距:
// 现有代码 (scoreCalculator.ts)
const diff = Math.abs(mfu - mbu)
const score = 100 - diff * 100
为什么这在 LLM 推理中没有意义
Prefill 和 Decode 有本质不同的计算特征:
- Prefill 阶段: 处理整个 prompt,是大规模矩阵乘法,算术强度高。此时 MFU 高、MBU 低是正常且理想的状态
- Decode 阶段: 逐 token 生成,每次只做一个小矩阵运算但需读取大量 KV Cache。此时 MBU 高、MFU 低是正常且理想的状态
Roofline 模型告诉我们:一个工作负载要么是计算瓶颈,要么是访存瓶颈,两者同时打满只发生在 Roofline 的拐点处,而大多数实际负载不在拐点上。
惩罚 MFU-MBU 差距的后果: 一个 Prefill MFU=0.8、Decode MBU=0.85 的方案,在各自的瓶颈维度上都表现优秀。但如果混合来看 |MFU-MBU| 很大(因为 Prefill 的 MBU 低、Decode 的 MFU 低),balance_score 会给出低分。这与实际情况相矛盾。
替代方案: 用 DFOP 成本维度填补
效率维度已经用 max(MFU, MBU) 来捕获"瓶颈侧利用率"。如果 MFU 和 MBU 都低,效率分自然就低 -- 不需要额外的"均衡性"维度。
删除 balance_score 后空出的位置由 cost_score (DFOP) 填入,理由:
- 填补成本盲区: 原五维度全是技术指标,没有经济性考量。对于部署方案探索工具,成本效率是核心关切
- DFOP 数据已就绪: 后端
cost_analysis.py已计算 DFOP,前端已展示,只需接入评分 - 维持六维雷达图: 替换而非删除,保持雷达图的六角形对称性
实现注意事项
分数范围保证
所有维度分数必须在 [0, 100] 范围内。实现时每个计算函数结果都做 clamp:
function clamp(value: number, min: number = 0, max: number = 100): number {
return Math.max(min, Math.min(max, value))
}
避免除零和 log(0)
| 场景 | 处理方式 |
|---|---|
| actual_latency <= 0 | 使用 epsilon = 1e-6 替代 |
| memory_capacity <= 0 | 返回 0 分 |
| total_time <= 0 | 返回 0 分(无数据) |
| actual_dfop <= 0 | 返回 0 分(TPS 为 0 导致 DFOP 无法计算) |
| 几何平均中 score = 0 | 使用 epsilon = 1e-3 替代 |
SLO 参数可配置
SLO 参数应作为评分函数的可选参数传入,而非硬编码:
interface SLOConfig {
ttft_ms: number // TTFT SLO 目标值 (默认 200ms)
tpot_ms: number // TPOT SLO 目标值 (默认 50ms)
}
interface ThroughputBaseline {
small: number // <10B 模型基准 TPS/Chip (默认 100)
medium: number // 10-100B 基准 (默认 30)
large: number // 100-500B 基准 (默认 10)
xlarge: number // >500B 基准 (默认 3)
}
interface DfopBaseline {
small: number // <10B 模型基准 $/TPS (默认 50)
medium: number // 10-100B 基准 (默认 500)
large: number // 100-500B 基准 (默认 5000)
xlarge: number // >500B 基准 (默认 20000)
}
interface ScoringWeights {
latency: number // 默认 0.20
throughput: number // 默认 0.20
efficiency: number // 默认 0.20
memory: number // 默认 0.15
communication: number // 默认 0.10
cost: number // 默认 0.15
}
后端评分同步
当前后端 score = tps(直接使用 TPS 作为得分),应改为调用统一的评分逻辑,使前后端评分一致。两种实现路径:
- 方案 A: 后端用 Python 实现相同的评分算法,存入数据库
- 方案 B: 后端只存原始指标,前端负责所有评分计算(当前架构更接近此方案)
推荐方案 B,理由是:评分权重和 SLO 配置是用户可调的前端参数,前端统一计算更灵活。后端数据库中的 score 字段可保留用于默认排序(使用 TPS 或前端回写的综合分)。
涉及的代码文件
| 文件 | 当前状态 | 改动方向 |
|---|---|---|
frontend/src/utils/llmDeployment/scoreCalculator.ts | 六维评分 + 加权算术平均 | 替换 balance -> cost(DFOP) + 几何平均 |
backend/perf_model/L5_reporting/cost_analysis.py | 已计算 DFOP | 无需改动,数据已就绪 |
backend/perf_model/L0_entry/api/_helpers.py | score = tps | 保持或改为前端回写 |
评分体系总结
+------------------+------------------------------------------+--------+
| 维度 | 核心公式 | 权重 |
+------------------+------------------------------------------+--------+
| Latency | 100 * min(1, SLO / actual), TTFT+TPOT | 0.20 |
| Throughput | min(100, tps_per_chip / baseline * 100) | 0.20 |
| Efficiency | max(MFU, MBU) * 100 | 0.20 |
| Memory | 分段函数, 60-85% 最优 | 0.15 |
| Communication | max(10, (1 - comm_ratio) * 100) | 0.10 |
| Cost (DFOP) | 100 * min(1, baseline_dfop / actual_dfop)| 0.15 |
+------------------+------------------------------------------+--------+
| Overall | 加权几何平均 (短板惩罚) | -- |
+------------------+------------------------------------------+--------+