Fix #27-#30: dtype感知算力(Cube/AIV速率表) / GM首读下限与整芯片字节列口径+设计文档 / Fixpipe输出落点R4(整case驻留L2否则直写GM) / MergeBatch Cube公式复核注释

- docs/05_L2驻留GM读写与dtype算力口径_设计分析.md: R1-R6公理、S_A/S_B/S_C场景、输出落点R4、dtype速率表(白皮书出处+待标定假设)、逐分支GM/L2归属表 (issue#29/#30 先文档后代码)
- hardware: CUBE_DTYPE_FACTOR(f16/bf16=1, fp8=2x, fp4=4x, fp32=1/2假设) + AIV_DTYPE_FACTOR + q_cube/aiv_elem_rate (issue#28)
- 全分支 t_mmad/t_comp/drain/尾轮主导项/θ_c/R16 语义按输入dtype取算力; 混精度取慢侧; StreamK归约保持fp32(AIV fp32部分和)
- Fixpipe输出落点R4: to_l2 <=> V_in+V_out(+workspace)<=L2; 否则直写GM计入共享总线; ASW场景改S_A整case全驻留(原单batch驻留判定漏计整case输出累积逐出)
- 字节列统一整芯片口径(gm/l2/fix/cube_flops), dma_cmd_count注明单核; GM>=V_in不变量入测试; MergeBatch每步flops=2(b0M)(b0N)K公式注释显式化(#27复核与CSV一致无数值改动)
- tests 39->49 全过; 双压力seed7/6000+seed2024/4000: 0违规/0占位/0NaN/0GM<输入; examples三件套重生成且可复现0diff
This commit is contained in:
2026-09-04 16:26:10 +08:00
parent 4843053ad3
commit b9e07edc1d
15 changed files with 793 additions and 239 deletions

View File

@@ -80,22 +80,14 @@ class AswBasicBranch(Branch):
# Step 4: swizzle 窗口 W = max{d | d|C, d <= floor(sqrt(C))}
swizzle_w = self._swizzle_w()
# Step 5: L2 分组判断 (issue#24: 按单 batch 工作集判定, 同一时刻单 batch 激活)
a_b = m * k * dt
bb_b = k * n * dt
out_batch = m * n * case.dtype_out_bytes
if a_b + bb_b + out_batch <= s.l2_bytes:
l2_scene = "A_单batch全驻留(输入+输出)"
l2_out = "resident(输出驻留L2异步回写)"
r_in = 1.0
elif a_b + bb_b <= s.l2_bytes:
l2_scene = "B_单batch输入驻留输出直写GM"
l2_out = "direct_gm(输出直写GM不占L2)"
r_in = 1.0
else:
l2_scene = "C_单batch输入超L2分组执行"
l2_out = "direct_gm(输出直写GM不占L2)"
r_in = self._l2_group_r_gm(case, single_m, single_n)
# Step 5: L2 场景判定 (issue#24/#30, 设计文档 docs/05 §4, 芯片 L2 128MB)
# S_A 整case全驻留: V_in+V_out <= L2 -> 输出驻留 L2 (R4, GM 写=0);
# S_B 整case超L2但单batch输入可驻留: batch 内共享块重复读命中 L2, 输出直写 GM;
# S_C 单batch输入超L2: 分组执行 (工作集), 组间共享块落空回 GM (r_gm).
scene = self._l2_scene(case, single_m, single_n)
l2_out = ("resident(整case全驻留S_A: 输出驻留L2异步回写, GM写=0)"
if scene["to_l2"] else
"direct_gm(输出直写GM, 输入优先驻留L2)")
# Step 6: k_l1 (GM->L1 K 向粒度, 须 >= dValue 下限; K 小于下限时整 K 一次搬入不切)
dv_min_elems = max(s.dvalue_hw_min // dt, 1)
@@ -125,7 +117,9 @@ class AswBasicBranch(Branch):
tail_block_cnt=tail["r"], tail_wave_num=tail["n_wave"],
fixpipe_unitflag=True,
out_dtype_bytes=case.dtype_out_bytes,
note=f"L2场景{l2_scene}, GM首读倍率={r_in:.2f}; 尾轮: {tail['reason']}",
note=(f"L2场景: {scene['label']} (V_in={case.input_bytes/1048576:.1f}MB, "
f"V_out={case.output_bytes/1048576:.1f}MB, L2={s.l2_bytes/1048576:.0f}MB), "
f"GM首读倍率={scene['r_gm']:.2f}; 尾轮: {tail['reason']}"),
)
# ------------------------------------------------------------------
@@ -143,6 +137,7 @@ class AswBasicBranch(Branch):
single_m, single_n = clamp_base_mn_l0c(single_m, single_n, False, s)
# base_k 由 L0A/L0B 容量按 dtype 反推 (issue#5: 原硬编码 min(K,64) 在 fp32 下溢出)
base_k = clamp_base_k(single_m, single_n, dt, case.k, s)
out_l2 = case.input_bytes + case.output_bytes <= s.l2_bytes # R4 (issue#30)
return ImplPlan(
case_id=case.case_id, branch=self.name + "_降核",
used_core_num=used,
@@ -155,7 +150,9 @@ class AswBasicBranch(Branch):
l1_form="标准核内流水",
base_m=single_m, base_n=single_n,
base_k=base_k,
l2_policy_in="allocate", l2_policy_out="direct_gm",
l2_policy_in="allocate",
l2_policy_out=("resident(整case全驻留S_A)"
if out_l2 else "direct_gm(输出直写GM)"),
swizzle_w=0, workspace_bytes=0,
tail_strategy="不涉及(每核一块无尾轮)",
fixpipe_unitflag=True, out_dtype_bytes=case.dtype_out_bytes,
@@ -190,6 +187,31 @@ class AswBasicBranch(Branch):
w = d
return w
def _l2_scene(self, case: BmmCase, sm: int, sn: int) -> dict:
"""L2 场景判定 (make_plan 与 evaluate 共用, 设计文档 docs/05 §4.1).
判定顺序 S_A -> S_B -> S_C (L2 为整芯片 128MB):
S_A 整case全驻留: V_in + V_out <= L2 -> 输出驻留 L2 (R4);
S_B 单batch输入驻留: a_b + bb_b <= L2 -> 输入共享块重复读命中 L2;
S_C 单batch输入超L2: 分组执行, 组间落空回 GM (r_gm).
"""
s = self.spec
m, n, k = case.m, case.n, case.k
dt = case.dtype_in_bytes
a_b = m * k * dt
bb_b = k * n * dt
if case.input_bytes + case.output_bytes <= s.l2_bytes:
return {"code": "S_A",
"label": "A_整case全驻留(输入+输出<=L2)",
"to_l2": True, "r_gm": 1.0}
if a_b + bb_b <= s.l2_bytes:
return {"code": "S_B",
"label": "B_单batch输入驻留(整case超L2, 输出直写GM)",
"to_l2": False, "r_gm": 1.0}
return {"code": "S_C",
"label": "C_单batch输入超L2分组执行(组间落空回GM)",
"to_l2": False, "r_gm": self._l2_group_r_gm(case, sm, sn)}
def _l2_group_r_gm(self, case, sm, sn) -> float:
"""场景 C (单 batch 输入仍超 L2): 分组执行的 GM 重复读倍率.
@@ -227,7 +249,8 @@ class AswBasicBranch(Branch):
# 主导项判定
bw_eff = s.bw_l2_pc # L2 命中
t_mmad = 2 * sm * sn * case.k / s.q16
qc = s.q_cube(case.dtype_a, case.dtype_b) # issue#28
t_mmad = 2 * sm * sn * case.k / qc
t_mte2 = case.k * (sm + sn) * dt / bw_eff
t_fix = sm * sn * out_b / s.bw_pc
t_block = max(t_mmad, t_mte2, t_fix)
@@ -268,15 +291,17 @@ class AswBasicBranch(Branch):
# ------------------------------------------------------------------
def evaluate(self, case: BmmCase, plan: ImplPlan) -> HardwareTiming:
"""MTE2 两段块级模型 (issue#24, 用户澄清):
"""MTE2 两段块级模型 (issue#24 用户口径 + #28/#29/#30):
- 首读走 GM (按 GM 带宽, 不叠加 L2); 共享块 (A 行块被 n_cnt 个 tile 读、
B 列块被 m_cnt 个 tile 读) 驻留 L2 后其余 (n_cnt-1)/(m_cnt-1) 次读走
L2 读口 (5.2TB/s 独享) —— 场景 A/B (单 batch 工作集可驻留);
- 单 batch 输入超 L2 -> 场景 C 分组执行, GM 重复按组间落空计 (r_gm), 窗口内
复用未另计 (保守);
- 输出: 场景 A 驻留 L2 (5.2 写口) / 场景 B,C 直写 GM —— GM 读写共享总线
累加由 assemble 的 MTE2 链处理 (issue#23).
L2 读口 (5.2TB/s 独享) —— 场景 S_A/S_B (单 batch 工作集可驻留);
- 单 batch 输入超 L2 -> 场景 S_C 分组执行, GM 重复按组间落空计 (r_gm),
窗口内复用未另计 (保守);
- 输出落点 R4 (issue#30): 仅 S_A (整 case 输入+输出 <= L2) 驻留 L2
(5.2 写口, GM 写 = 0); S_B/S_C 直写 GM —— GM 读写共享总线累加由
assemble 的 MTE2 链处理 (issue#23);
- 字节列整芯片口径 (issue#29); Cube 算力按输入 dtype (issue#28).
"""
s = self.spec
b = case.batch_c
@@ -284,37 +309,33 @@ class AswBasicBranch(Branch):
dt = case.dtype_in_bytes
out_b = case.dtype_out_bytes
used = max(plan.used_core_num, 1)
qc = s.q_cube(case.dtype_a, case.dtype_b)
flops = 2.0 * b * m * n * k
t_mmad = flops / (used * s.q16)
t_mmad = flops / (used * qc)
# ---- 字节量 (每 batch 口径) ----
a_b = m * k * dt # batch A 字节
bb_b = k * n * dt # batch B 字节
# ---- 字节量 (整芯片口径) ----
a_b = m * k * dt # batch A 字节
bb_b = k * n * dt # batch B 字节
out_all = b * m * n * out_b # 输出总字节
m_cnt = max(plan.m_cnt, 1)
n_cnt = max(plan.n_cnt, 1)
# ---- 场景判定: 按单 batch 工作集 (同一时刻单 batch 激活) ----
if a_b + bb_b + m * n * out_b <= s.l2_bytes:
scene, to_l2, r_gm = "A_单batch全驻留(输入+输出)", True, 1.0
elif a_b + bb_b <= s.l2_bytes:
scene, to_l2, r_gm = "B_单batch输入驻留,输出直写GM", False, 1.0
else:
scene, to_l2, r_gm = "C_单batch输入超L2分组执行", False, \
self._l2_group_r_gm(case, plan.single_core_m, plan.single_core_n)
# ---- L2 场景 (S_A/S_B/S_C, 与 make_plan 同源) ----
scene = self._l2_scene(case, plan.single_core_m, plan.single_core_n)
to_l2, r_gm = scene["to_l2"], scene["r_gm"]
# ---- MTE2: GM 段 (首读, 每字节一次) + L2 段 (共享块重复读) ----
gm_read = r_gm * b * (a_b + bb_b)
if scene.startswith(("A_", "B_")):
if scene["code"] in ("S_A", "S_B"):
# 驻留命中: A 行块被 n_cnt 个 tile 复用 -> 其余 (n_cnt-1) 次走 L2 读口
l2_read = b * ((n_cnt - 1) * a_b + (m_cnt - 1) * bb_b)
else:
l2_read = 0.0 # 场景 C: 组间复用落空(已计 GM), 窗口内复用保守不计
l2_read = 0.0 # 场景 S_C: 组间复用落空(已计 GM), 窗口内复用保守不计
t_gm = gm_read / (used * s.bw_pc)
t_l2 = l2_read / (used * s.bw_l2_pc)
# ---- Fixpipe ----
# ---- Fixpipe (R4) ----
t_fix = out_all / (used * (s.bw_l2_pc if to_l2 else s.bw_pc))
# drain: 尾轮暴露 (方案 B 已均匀重切, drain 小; A1b 尾轮凑满, drain 小; A0 尾轮 r 核空转)

View File

@@ -13,7 +13,7 @@ from __future__ import annotations
from ..hardware import NpuSpec, ASCEND950PR
from ..models import BmmCase, ImplPlan, HardwareTiming, align_down
from ..timing import assemble_timing
from ..timing import assemble_timing, output_to_l2
from .base import Branch, BranchResult, ConditionCheck
@@ -171,6 +171,12 @@ class IterBatchBranch(Branch):
form_name = {"a": "a_单batch全驻留", "b": "b_双batch乒乓",
"c": "c_一侧驻留+对侧切K", "d": "d_两侧都切K"}[form]
# 输出落点 (issue#30, R4): 整 case 输入+输出 <= L2 才驻留 L2
out_l2 = output_to_l2(case, s)
l2_out = ("resident(整case输入+输出<=L2: 输出驻留L2异步回写, GM写=0)"
if out_l2 else
"direct_gm(整case超L2: 输入优先驻留L2, 输出直写GM不占L2)")
return ImplPlan(
case_id=case.case_id, branch=self.name,
used_core_num=s.aic_num,
@@ -181,54 +187,63 @@ class IterBatchBranch(Branch):
k_l1=k_l1, b_l1=2 if form == "b" else 1, l1_form=form_name,
base_m=base_m, base_n=base_n, base_k=base_k,
l2_policy_in="allocate(GM->L1随路驻留L2)",
l2_policy_out="direct_gm(输出仅写一次,直写GM不占L2)",
l2_policy_out=l2_out,
swizzle_w=0, workspace_bytes=0,
tail_strategy="不涉及(核内不切M/N)",
fixpipe_unitflag=True,
out_dtype_bytes=case.dtype_out_bytes,
note=form_desc,
note=form_desc + f"; 输出落点: {'L2驻留' if out_l2 else '直写GM'}",
)
# ------------------------------------------------------------------
# 时延评估 (v1.1 §4 端到端模型, IterBatch 侧)
# 口径: dtype 感知算力 (issue#28); 字节列整芯片 (issue#29);
# 输出落点 R4 (issue#30).
# ------------------------------------------------------------------
def evaluate(self, case: BmmCase, plan: ImplPlan) -> HardwareTiming:
s = self.spec
m, n, k = case.m, case.n, case.k
dt = case.dtype_in_bytes
out_b = case.dtype_out_bytes
b = case.batch_c
b_core, k_l1 = plan.b_core, plan.k_l1
qc = s.q_cube(case.dtype_a, case.dtype_b)
out_l2 = output_to_l2(case, s)
w_fix = s.bw_l2_pc if out_l2 else s.bw_pc
k_truncated = k_l1 >= k
n_k = 1 if k_truncated else -(-k // k_l1)
t_load = min(k_l1, k) * (m + n) * dt / s.bw_pc
t_comp_chunk = 2.0 * m * n * min(k_l1, k) / s.q16
t_write = m * n * out_b / s.bw_pc
t_comp_chunk = 2.0 * m * n * min(k_l1, k) / qc
t_write = m * n * out_b / w_fix
# 搬移: 每 batch n_K 次 GM->L1, 每次含 T_cmd
# 搬移: 每 batch n_K 次 GM->L1 (各 (batch,K段) 数据互不重叠, GM 每字节一次),
# 每次含 T_cmd; dma_cmds 为单核命令数 (各核并行, issue#29 口径)
dma_cmds = b_core * n_k
t_mte2_data = dma_cmds * t_load
t_dma_cmd = dma_cmds * s.t_cmd
t_mte2 = t_mte2_data + t_dma_cmd
# Cube: 无冗余
# Cube: 无冗余; 每核 flops = b_core*2MNK (整芯片列 = b*2MNK)
flops_pc = b_core * 2.0 * m * n * k
t_mmad = flops_pc / s.q16
flops_chip = b * 2.0 * m * n * k
t_mmad = flops_pc / qc
# Fixpipe: b_core 个 batch 输出, 按 C dtype
# Fixpipe (R4): b_core 个 batch 输出, 按 C dtype
fix_bytes_pc = b_core * m * n * out_b
t_fix = fix_bytes_pc / s.bw_pc
fix_bytes_chip = b * m * n * out_b
t_fix = fix_bytes_pc / w_fix
# drain: 末 batch 排空 = T_comp + T_write
t_drain = t_comp_chunk + t_write
gm_bytes_pc = b_core * (m * k + k * n) * dt
return assemble_timing(
t_mte2_gm=t_mte2_data, t_mte2_l2=0.0, t_dma_cmd=t_dma_cmd,
t_mmad=t_mmad, t_fixpipe=t_fix, t_reduce=0.0, t_drain=t_drain,
gm_read_bytes=gm_bytes_pc, l2_read_bytes=0.0,
dma_cmd_count=dma_cmds, cube_flops=flops_pc,
fixpipe_bytes=fix_bytes_pc,
gm_read_bytes=b * n_k * min(k_l1, k) * (m + n) * dt,
l2_read_bytes=0.0,
dma_cmd_count=dma_cmds, cube_flops=flops_chip,
fixpipe_bytes=fix_bytes_chip,
fixpipe_to_gm=(not out_l2),
)

View File

@@ -18,7 +18,7 @@ import math
from ..hardware import NpuSpec, ASCEND950PR
from ..models import BmmCase, ImplPlan, HardwareTiming, align_down
from ..timing import MoveInPlan, assemble_timing
from ..timing import assemble_timing, output_to_l2
from .base import Branch, BranchResult, ConditionCheck
MIN_B0 = 2 # b0: 合并搬移有收益的最小合并数
@@ -142,9 +142,17 @@ class MergeBatchBranch(Branch):
))
b_l1 = max(b_l1, b0)
# Step 5: 输出落点 (issue#30): 整 case 输入+输出可驻留 L2 才写 L2 (R4)
out_l2 = output_to_l2(case, s)
l2_out = ("resident(整case输入+输出<=L2: 输出驻留L2异步回写, GM写=0)"
if out_l2 else
"direct_gm(整case超L2: 输入优先驻留L2, 输出直写GM不占L2)")
note = (f"b0={b0} (L0C上限{b0_l0c:.1f}/算存比上限{b0_ai:.1f}/b_core={b_core}); "
f"{'K截断' if k_truncated else 'L1绑定'}; "
f"合并后单次DMA搬入 A'[{b0*m},{k_l1}]+B'[{k_l1},{b0*n}]")
f"合并后单次DMA搬入 A'[{b0*m},{k_l1}]+B'[{k_l1},{b0*n}]; "
f"输出落点: {'L2驻留' if out_l2 else '直写GM'} (整case V_in+V_out"
f"={case.input_bytes/1048576:.1f}MB vs L2={s.l2_bytes/1048576:.0f}MB)")
return ImplPlan(
case_id=case.case_id, branch=self.name,
@@ -157,7 +165,7 @@ class MergeBatchBranch(Branch):
# L0C 双缓冲约束已由进入条件 2 保证 (2*(b0*M)*(b0*N)*4B <= L0C)
base_m=b0 * m, base_n=b0 * n, base_k=max(min(k_l0, k_l1, k), s.fractal),
l2_policy_in="allocate(GM->L1随路驻留L2)",
l2_policy_out="direct_gm(输出仅写一次,直写GM不占L2)" if not case.out_nd else "direct_gm",
l2_policy_out=l2_out,
swizzle_w=0, workspace_bytes=0,
tail_strategy="不涉及(核内不切M/N)",
fixpipe_unitflag=True,
@@ -166,55 +174,71 @@ class MergeBatchBranch(Branch):
)
# ------------------------------------------------------------------
# 时延评估 (v1.1 §4 端到端模型)
# 时延评估 (v1.1 §4 端到端模型; issue#27/#28/#29/#30 口径)
# - Cube flops (issue#27 复核): 每合并步计算全网格 (b0*M)x(b0*N)xK
# 含交叉项冗余, flops_step = 2*(b0M)*(b0N)*K, 每核步数 = b_core/b0
# -> 每核 flops = b_core*b0*2MNK (与 CSV 及文档公式一致, 无修改);
# - 算力按输入 dtype (issue#28): q_cube(dtype_a, dtype_b);
# - 字节列 = 整芯片口径 (issue#29); GM 首读 = V_in 一次 (合并按组搬入
# 各 (batch,K段) 数据互不重叠, 无 L2 重复读);
# - 输出落点按整 case 驻留判定 (issue#30, R4).
# ------------------------------------------------------------------
def evaluate(self, case: BmmCase, plan: ImplPlan) -> HardwareTiming:
s = self.spec
m, n, k = case.m, case.n, case.k
dt = case.dtype_in_bytes
out_b = case.dtype_out_bytes
b = case.batch_c
b_core, b0, k_l1 = plan.b_core, plan.merge_b0, plan.k_l1
qc = s.q_cube(case.dtype_a, case.dtype_b)
out_l2 = output_to_l2(case, s, 0.0)
w_fix = s.bw_l2_pc if out_l2 else s.bw_pc
k_truncated = k_l1 >= k
# 每 K 分块搬移/计算时延 (未合并基准, v1.1 §4.1 符号)
t_load = k_l1 * (m + n) * dt / s.bw_pc
t_comp_chunk = 2.0 * m * n * k_l1 / s.q16
t_write = m * n * out_b / s.bw_pc # 单 batch 输出写回 (单核带宽份额)
t_comp_chunk = 2.0 * m * n * k_l1 / qc
t_write = m * n * out_b / w_fix # 单 batch 输出写回 (R4 落点带宽)
if k_truncated:
# K 截断: n_K=1, 合并后单次搬移量 b0 倍
# K 截断: n_K=1, 每核 b_core/b0 次合并搬入, 每次搬 b0 个 batch 全 K
n_move = b_core / b0
t_mte2_data = n_move * b0 * t_load
dma_cmds = n_move
gm_chip = b * (m + n) * k * dt # = V_in, GM 每字节一次
else:
# L1 绑定: k_L1^m = k_L1/b0, n_K^m = b0*n_K, 搬移次数与 IterBatch 相同
# (每 (合并组, K段) 数据互不重叠, GM 仍每字节一次, 末段含 padding 上取)
n_k = -(-k // k_l1)
n_move = b_core * n_k
t_mte2_data = n_move * t_load
dma_cmds = n_move
gm_chip = b * (m + n) * n_k * k_l1 * dt # >= V_in (padding 上取)
t_dma_cmd = dma_cmds * s.t_cmd
t_mte2 = t_mte2_data + t_dma_cmd
# Cube: 合并计算含冗余 (b0^2 输出, 有效 b0) -> 计算量 = b_core*b0*2MNK
# Cube (每核口径): 合并计算含冗余 (b0^2 输出, 有效 b0), 每核 b_core/b0 步,
# 每步 flops = 2*(b0*M)*(b0*N)*K -> 每核 = b_core*b0*2MNK (issue#27 复核一致)
flops_pc = b_core * b0 * 2.0 * m * n * k
t_mmad = flops_pc / s.q16
flops_chip = b * b0 * 2.0 * m * n * k # 整芯片口径列
t_mmad = flops_pc / qc
# Fixpipe: 只写对角块, 写出量 = b_core*MN*outB (C 矩阵 dtype, 随路转换)
# Fixpipe (R4): 只写对角块, 写出量 = b_core*MN*outB (C 矩阵 dtype, 随路转换)
fix_bytes_pc = b_core * m * n * out_b
t_fix = fix_bytes_pc / s.bw_pc
fix_bytes_chip = b * m * n * out_b
t_fix = fix_bytes_pc / w_fix
# drain: 末合并 batch 排空 = b0*(T_comp + T_write)
t_drain = b0 * (t_comp_chunk + t_write)
gm_bytes_pc = b_core * (m * k + k * n) * dt
return assemble_timing(
t_mte2_gm=t_mte2_data, t_mte2_l2=0.0, t_dma_cmd=t_dma_cmd,
t_mmad=t_mmad, t_fixpipe=t_fix, t_reduce=0.0, t_drain=t_drain,
gm_read_bytes=gm_bytes_pc, l2_read_bytes=0.0,
dma_cmd_count=dma_cmds, cube_flops=flops_pc,
fixpipe_bytes=fix_bytes_pc,
gm_read_bytes=gm_chip, l2_read_bytes=0.0,
dma_cmd_count=dma_cmds, cube_flops=flops_chip,
fixpipe_bytes=fix_bytes_chip,
fixpipe_to_gm=(not out_l2),
)
# ------------------------------------------------------------------
@@ -243,7 +267,9 @@ class MergeBatchBranch(Branch):
plan = self.make_plan(case)
b0 = plan.merge_b0
t_comp = 2.0 * m * n * min(k_l1_iter, k) / s.q16
# T_comp 按输入 dtype 算力 (issue#28); T_write 保持 v1.1 直写 GM 语义
qc = s.q_cube(case.dtype_a, case.dtype_b)
t_comp = 2.0 * m * n * min(k_l1_iter, k) / qc
t_write = m * n * out_b / s.bw_pc
drain_pen = (b0 - 1) * (t_comp + t_write)

View File

@@ -10,7 +10,7 @@ from __future__ import annotations
from ..hardware import NpuSpec, ASCEND950PR
from ..models import BmmCase, ImplPlan, HardwareTiming
from ..timing import assemble_timing
from ..timing import assemble_timing, output_to_l2
from .base import Branch, ConditionCheck
@@ -68,32 +68,42 @@ class SpecialBranch(Branch):
# ------------------------------------------------------------------
def evaluate(self, case: BmmCase, plan: ImplPlan) -> HardwareTiming:
"""AIV 通路时延: 瓶颈在搬移 (AIV 算力远剩)."""
"""AIV 通路时延: 瓶颈在搬移 (AIV 算力远剩).
口径: AIV 逐元素通量按输入 dtype (issue#28); 输出落点 R4 (issue#30):
整 case 输入+输出 <= L2 -> 输出写 L2 (异步回写不占算子时延), 否则直写
GM 与读共享总线 (assemble 的 MTE2 链累加, issue#23).
"""
s = self.spec
b = case.batch_c
m, n, k = case.m, case.n, case.k
dt = case.dtype_in_bytes
out_b = case.dtype_out_bytes
out_l2 = output_to_l2(case, s)
w_fix = s.bw_l2 if out_l2 else s.bw_gm
cube_flops = 0.0
t_compute = 0.0
if k == 0:
# 纯写值: 仅写出
t_in, t_compute, t_out = 0.0, 0.0, b * m * n * out_b / s.bw_gm
in_bytes, cube_flops = 0.0, 0.0
# 纯写值: 仅写出 (R1 下 GM 读 = 0, 输入本身为空)
t_in = 0.0
t_out = b * m * n * out_b / w_fix
in_bytes = 0.0
else:
# 逐元素乘: 搬入 A+B, 搬出 C, AIV 算力远剩
# 逐元素乘: 搬入 A+B (GM 每字节一次), 搬出 C
in_bytes = b * (m * k + k * n) * dt
out_bytes = b * m * n * out_b
t_in = in_bytes / s.bw_gm
t_out = out_bytes / s.bw_gm
# AIV 求积吞吐 (近似按 Q_AIV)
t_compute = b * m * n / s.q_aiv
t_out = out_bytes / w_fix
# AIV 逐元素吞吐按输入 dtype 取通量 (issue#28: bf16/fp16 x2, int8 x4...)
t_compute = b * m * n / s.aiv_elem_rate(case.dtype_in)
cube_flops = float(b * m * n)
t_total = max(t_in, t_out, t_compute)
return assemble_timing(
t_mte2_gm=t_in, t_mte2_l2=0.0, t_dma_cmd=0.0,
t_mmad=t_compute, t_fixpipe=t_out, t_reduce=0.0, t_drain=0.0,
gm_read_bytes=in_bytes, l2_read_bytes=0.0, dma_cmd_count=0.0,
cube_flops=cube_flops,
fixpipe_bytes=b * m * n * out_b,
fixpipe_to_gm=(not out_l2),
)

View File

@@ -12,7 +12,7 @@ import math
from ..hardware import NpuSpec, ASCEND950PR
from ..models import BmmCase, ImplPlan, HardwareTiming, ceil_div
from ..timing import assemble_timing, eval_streamk_reduce
from ..timing import assemble_timing, eval_streamk_reduce, output_to_l2
from .base import Branch, ConditionCheck
@@ -32,10 +32,14 @@ class StreamKBranch(Branch):
p = self._p_value(case)
return int(self.spec.aic_num // max(1, math.ceil(p)))
def _theta_c(self) -> float:
"""归约代价系数 theta_c = Q16/2 * (8B/W_L2 + 1/Q_AIV) ≈ 12."""
def _theta_c(self, case: BmmCase) -> float:
"""归约代价系数 theta_c = Q_cube(dtype)/2 * (8B/W_L2 + 1/Q_AIV_fp32).
Q_AIV 用 fp32 档 (部分和为 fp32, issue#28); Cube 侧按输入 dtype.
"""
s = self.spec
return s.q16 / 2 * (8 / s.bw_l2 + 1 / s.q_aiv)
qc = s.q_cube(case.dtype_a, case.dtype_b)
return qc / 2 * (8 / s.bw_l2 + 1 / s.q_aiv)
# ------------------------------------------------------------------
def check_conditions(self, case: BmmCase) -> list:
@@ -61,7 +65,7 @@ class StreamKBranch(Branch):
# 条件 3: K > grid_K^2/(grid_K-1) * theta_c (归约代价可接受)
if grid_k >= 2:
theta_c = self._theta_c()
theta_c = self._theta_c(case)
k_thresh = grid_k * grid_k / (grid_k - 1) * theta_c
c3 = case.k > k_thresh
checks.append(ConditionCheck(
@@ -139,7 +143,9 @@ class StreamKBranch(Branch):
(工作集超 L2, 保守同 ASW 场景 C 公式);
- MMAD: 芯片总量 2*b*m*n*k / (C核) —— 全核忙稳态;
- 归约 (部分和 4B 写 L2/AIV 读回求和/最终写回): 每 tile-组一次、组间串行
追加为 drain: t_drain = waves * eval_streamk_reduce(tile, grid_k, out).
追加为 drain: t_drain = waves * eval_streamk_reduce(tile, grid_k, out);
最终输出写回落点按 R4 (issue#30): 整 case 输入+输出+workspace <= L2
时写 L2, 否则直写 GM (drain 内按 GM 带宽).
"""
s = self.spec
b = case.batch_c
@@ -152,6 +158,8 @@ class StreamKBranch(Branch):
tile_m = max(plan.single_core_m, 1)
tile_n = max(plan.single_core_n, 1)
tile = tile_m * tile_n # 每核实际 tile 元素数
qc = s.q_cube(case.dtype_a, case.dtype_b)
out_l2 = output_to_l2(case, s, float(plan.workspace_bytes))
# ---- MTE2: GM 段 + L2 段 (同 ASW 块级规则) ----
a_b = m * k * dt
@@ -166,13 +174,14 @@ class StreamKBranch(Branch):
t_l2 = l2_read / s.bw_l2
t_mte2 = t_gm + t_l2
# ---- MMAD: 芯片总量 ----
# ---- MMAD: 芯片总量, dtype 感知算力 (issue#28) ----
flops = 2.0 * b * m * n * k
t_mmad = flops / (s.aic_num * s.q16)
t_mmad = flops / (s.aic_num * qc)
# ---- 归约: 每 tile-组一次, 组间分波串行追加 ----
waves = ceil_div(b * m_cnt * n_cnt * grid_k, s.aic_num)
t_reduce_group = eval_streamk_reduce(tile, grid_k, out_b, s)
t_reduce_group = eval_streamk_reduce(tile, grid_k, out_b, s,
out_to_gm=not out_l2)
t_reduce = waves * t_reduce_group
fix_bytes = 0.0
t_fix = 0.0

View File

@@ -12,7 +12,7 @@ from __future__ import annotations
from ..hardware import NpuSpec, ASCEND950PR
from ..models import BmmCase, ImplPlan, HardwareTiming
from ..timing import assemble_timing
from ..timing import assemble_timing, output_to_l2
from .base import Branch, ConditionCheck
@@ -71,7 +71,12 @@ class ToMatmulBranch(Branch):
# ------------------------------------------------------------------
def evaluate(self, case: BmmCase, plan: ImplPlan) -> HardwareTiming:
"""折叠后按 Matmul 粗估 (详细切分待 MM 理论体系打通后接入)."""
"""折叠后按 Matmul 粗估 (详细切分待 MM 理论体系打通后接入).
口径: Cube 算力按输入 dtype (issue#28); GM 首读 = V_in 一次 (R1);
输出落点 R4 (issue#30): 整 case 输入+输出 <= L2 时写 L2 写口, 否则
直写 GM 与读共享总线 (assemble 累加).
"""
s = self.spec
if case.batch_b == 1:
fold_m, fold_n, kk = case.batch_a * case.m, case.n, case.k
@@ -79,13 +84,15 @@ class ToMatmulBranch(Branch):
fold_m, fold_n, kk = case.m, case.batch_b * case.n, case.k
dt = case.dtype_in_bytes
out_b = case.dtype_out_bytes
qc = s.q_cube(case.dtype_a, case.dtype_b)
out_l2 = output_to_l2(case, s)
flops = 2.0 * fold_m * fold_n * kk
t_mmad = flops / (s.aic_num * s.q16)
t_mmad = flops / (s.aic_num * qc)
in_bytes = (fold_m * kk + kk * fold_n) * dt
out_bytes = fold_m * fold_n * out_b
t_mte2 = in_bytes / s.bw_gm
t_fix = out_bytes / s.bw_gm
t_fix = out_bytes / (s.bw_l2 if out_l2 else s.bw_gm)
return assemble_timing(
t_mte2_gm=t_mte2, t_mte2_l2=0.0, t_dma_cmd=0.0,
@@ -93,4 +100,5 @@ class ToMatmulBranch(Branch):
t_drain=0.0,
gm_read_bytes=in_bytes, l2_read_bytes=0.0, dma_cmd_count=0.0,
cube_flops=flops, fixpipe_bytes=out_bytes,
fixpipe_to_gm=(not out_l2),
)

View File

@@ -4,12 +4,36 @@
换芯片时逻辑结构不变, 只需新增一份同结构参数表.
单位约定: 算力 FLOP/s, 带宽 Byte/s, 容量 Byte, 时延 秒.
dtype 感知算力 (issue#28, 设计文档 docs/05 §2):
Cube 算力按输入 dtype 分档, 基准 = BF16 (fp16 同速); 白皮书: FP8/MXFP8/HiF8
提供 2x FP16 张量 TFLOPS, MXFP4 提供 4x FP16; FP32/TF32 同代比值为假设值
(按 DaVinci 惯例 = 1/2, 白皮书未给同代比值), int8 假设同 FP8, 均待实测标定.
AIV 逐元素通量按 lane 位宽等比假设 (16bit x2 / 8bit x4 / 4bit x8, 待标定).
"""
from __future__ import annotations
from dataclasses import dataclass
# Cube 精度因子 (相对 BF16/FP16 基准档; A/B 不一致时取较慢一侧 = min 因子)
CUBE_DTYPE_FACTOR = {
"fp32": 0.5, "f32": 0.5, "tf32": 0.5, # 假设 = 1/2, 待实测标定
"fp16": 1.0, "f16": 1.0, "bf16": 1.0,
"fp8": 2.0, "fp8_e4m3": 2.0, "fp8_e5m2": 2.0, "int8": 2.0, # 白皮书 FP8=2xFP16; int8 假设同 FP8
"fp4": 4.0, "fp4_e2m1": 4.0, # 白皮书 MXFP4=4xFP16; 普通 fp4 假设同 MXFP4
}
# AIV 逐元素通量因子 (相对 fp32 128 lane/拍/核; 位宽等比假设, 待实测标定)
AIV_DTYPE_FACTOR = {
"fp32": 1.0, "f32": 1.0, "tf32": 1.0,
"fp16": 2.0, "f16": 2.0, "bf16": 2.0,
"fp8": 4.0, "fp8_e4m3": 4.0, "fp8_e5m2": 4.0, "int8": 4.0,
"fp4": 8.0, "fp4_e2m1": 8.0,
}
_RATE_FALLBACK = 1.0 # 未知 dtype 按基准档 (models.dtype_bytes 已先行校验, 正常不会到达)
@dataclass(frozen=True)
class NpuSpec:
@@ -55,13 +79,50 @@ class NpuSpec:
# ---- 派生量 (属性) ----
@property
def q16(self) -> float:
"""单核 Cube BF16 峰值算力 (FLOP/s)."""
"""单核 Cube BF16/FP16 峰值算力 (FLOP/s) = 15.1875T."""
return self.cube_peak_tflops * 1e12 / self.aic_num
@staticmethod
def _dtype_key(dtype) -> str:
return str(dtype).strip().lower()
def cube_factor(self, dtype_a, dtype_b=None) -> float:
"""按输入 dtype 取 Cube 精度因子 (issue#28).
A/B 不一致时取**较慢一侧** (因子较小者, 等价字节较大者);
Cube 乘法两侧的实际吞吐受较慢精度限制.
"""
keys = [self._dtype_key(dtype_a)]
if dtype_b is not None:
keys.append(self._dtype_key(dtype_b))
factors = [CUBE_DTYPE_FACTOR.get(k, _RATE_FALLBACK) for k in keys]
return min(factors)
def q_cube(self, dtype_a, dtype_b=None) -> float:
"""单核 Cube 峰值算力 (FLOP/s), 按输入 dtype 分档 (issue#28)."""
return self.q16 * self.cube_factor(dtype_a, dtype_b)
def aiv_elem_factor(self, dtype) -> float:
"""按 dtype 取 AIV 逐元素通量因子 (相对 fp32 基准)."""
return AIV_DTYPE_FACTOR.get(self._dtype_key(dtype), _RATE_FALLBACK)
@property
def aiv_elem_rate_fp32(self) -> float:
"""AIV fp32 逐元素吞吐 (元素/s), 64 核合计."""
return self.aiv_num * self.aiv_fp32_per_cycle * self.aiv_freq_ghz * 1e9
def aiv_elem_rate(self, dtype) -> float:
"""AIV 逐元素吞吐 (元素/s, 64 核合计), 按 dtype 分档 (issue#28)."""
return self.aiv_elem_rate_fp32 * self.aiv_elem_factor(dtype)
@property
def q_aiv(self) -> float:
"""AIV 向量求和吞吐 (元素/s), 64 核合计."""
return self.aiv_num * self.aiv_fp32_per_cycle * self.aiv_freq_ghz * 1e9
"""AIV fp32 逐元素吞吐 (元素/s, 64 核合计) = aiv_elem_rate_fp32.
注意: 该值只适用于 fp32 数据 (如 StreamK 对 fp32 部分和求和);
逐元素运算按输入 dtype 用 aiv_elem_rate(dtype) (issue#28).
"""
return self.aiv_elem_rate_fp32
@property
def bw_pc(self) -> float:
@@ -75,7 +136,11 @@ class NpuSpec:
@property
def r16(self) -> float:
"""16bit 位宽平衡点算存比 R_16 = Cube峰值 / (GM带宽/2B) ≈ 607.5 FLOP/元素."""
"""16bit 位宽平衡点算存比 R_16 = Cube峰值 / (GM带宽/2B) ≈ 607.5 FLOP/元素.
按 issue#28 速率表该比值对全 dtype 不变 (Cube 因子与元素字节数互成反比),
故入口条件沿用单一 R_16.
"""
return self.cube_peak_tflops * 1e12 / (self.bw_gm / 2)
@property

View File

@@ -133,6 +133,12 @@ class BmmCase:
# 返回 float 以兼容 fp4 (0.5B)
return max(dtype_bytes(self.dtype_a), dtype_bytes(self.dtype_b))
@property
def dtype_in(self) -> str:
"""输入侧"较慢"dtype (元素字节较大者) — Cube/AIV 速率取慢侧 (issue#28)."""
return (self.dtype_a if dtype_bytes(self.dtype_a) >= dtype_bytes(self.dtype_b)
else self.dtype_b)
@property
def dtype_out_bytes(self) -> float:
return dtype_bytes(self.dtype_c)
@@ -261,23 +267,30 @@ class ImplPlan:
@dataclass
class HardwareTiming:
"""各硬件流水级时延 (秒) 与数据量明细."""
"""各硬件流水级时延 (秒) 与数据量明细.
数据量列口径 (issue#29, 设计文档 docs/05 §4.4):
gm_read_bytes / l2_read_bytes / fixpipe_bytes / cube_flops = **整芯片**总量
(跨分支可比, GM 首读下限断言: gm_read_bytes >= case.input_bytes);
dma_cmd_count = **单核** GM->L1 DMA 命令数 (各核引擎并行, 墙钟 T_cmd =
单核命令数 x T_cmd), 与字节列口径不同属.
"""
# 搬入 (MTE2)
gm_read_bytes: float = 0.0 # GM->L1 直读数据量 (不驻留/未命中 L2 的部分)
l2_read_bytes: float = 0.0 # L2->L1 数据量 (驻留 L2 后重复读命中部分)
gm_read_bytes: float = 0.0 # GM->L1 直读数据量 (整芯片, 首读/落空重读)
l2_read_bytes: float = 0.0 # L2->L1 数据量 (整芯片, 驻留 L2 后重复读命中部分)
t_mte2_gm: float = 0.0 # GM->L1 时延 (按 GM 带宽, 不累加 L2->L1)
t_mte2_l2: float = 0.0 # L2->L1 时延 (按 L2 带宽)
t_mte2: float = 0.0 # 搬入合计 = t_mte2_gm + t_mte2_l2 (两者发生在不同数据上)
dma_cmd_count: float = 0.0 # GM->L1 DMA 命令次数 (T_cmd 分析用)
t_dma_cmd: float = 0.0 # DMA 命令固定开销合计
dma_cmd_count: float = 0.0 # 单核 GM->L1 DMA 命令次数 (T_cmd 分析用)
t_dma_cmd: float = 0.0 # DMA 命令固定开销合计 (墙钟)
# 计算 (Cube MMAD)
cube_flops: float = 0.0 # Cube 实际计算量 (MergeBatch 含冗余)
cube_flops: float = 0.0 # Cube 实际计算量 (整芯片, MergeBatch 含冗余)
t_mmad: float = 0.0
# 搬出 (Fixpipe)
fixpipe_bytes: float = 0.0 # 写出数据量 (按 C 矩阵 dtype / StreamK 临时矩阵按 4B)
fixpipe_bytes: float = 0.0 # 写出数据量 (整芯片, 按 C 矩阵 dtype / StreamK 临时矩阵按 4B)
t_fixpipe: float = 0.0
# 归约 (StreamK 专用)

View File

@@ -24,7 +24,19 @@ from __future__ import annotations
from dataclasses import dataclass
from .hardware import NpuSpec, ASCEND950PR
from .models import HardwareTiming
from .models import BmmCase, HardwareTiming
def output_to_l2(case: BmmCase, spec: NpuSpec = ASCEND950PR,
workspace_bytes: float = 0.0) -> bool:
"""Fixpipe 输出落点决策 (issue#30, 设计文档 docs/05 §4.2 R4).
to_l2 (输出写 L2 写口 5.2TB/s, GM 写流量 = 0, 异步回写不占算子时延)
⟺ 整 case 输入 V_in + 输出 V_out [+ StreamK workspace] ≤ L2
否则输出**直写 GM**: 输入优先驻留 L2 (输入存在重复读), 输出计入 GM
读写共享总线 (与读累加, issue#23).
"""
return (case.input_bytes + case.output_bytes + workspace_bytes) <= spec.l2_bytes
@dataclass
@@ -56,9 +68,11 @@ def eval_mte2(move: MoveInPlan, spec: NpuSpec = ASCEND950PR,
return t_gm, t_l2, t_gm + t_l2 + t_cmd, t_cmd
def eval_mmad(flops_per_core: float, spec: NpuSpec = ASCEND950PR) -> float:
"""Cube 计算时延: 单核计算量 / 单核算力."""
return flops_per_core / spec.q16 if flops_per_core > 0 else 0.0
def eval_mmad(flops_per_core: float, spec: NpuSpec = ASCEND950PR,
dtype_a=None, dtype_b=None) -> float:
"""Cube 计算时延: 单核计算量 / 单核 dtype 感知算力 (issue#28)."""
rate = spec.q_cube(dtype_a, dtype_b)
return flops_per_core / rate if flops_per_core > 0 else 0.0
def eval_fixpipe(bytes_per_core: float, to_l2: bool,
@@ -75,21 +89,24 @@ def eval_fixpipe(bytes_per_core: float, to_l2: bool,
def eval_streamk_reduce(tile_elems: float, grid_k: int, out_dtype_bytes: int,
spec: NpuSpec = ASCEND950PR) -> float:
spec: NpuSpec = ASCEND950PR, out_to_gm: bool = False) -> float:
"""StreamK 单 tile 归约时延 (v0.98 §七).
部分和 dtype = L0C dtype (4B, 防精度丢失), 驻留 L2, AIV 归约:
部分和 dtype = L0C dtype (4B, 防精度丢失), 驻留 L2, AIV 归约 (fp32 求和,
AIV 按 fp32 通量, 不随输入 dtype 变 — issue#28):
AIC 写部分和 grid_k x tile x 4B / W_L2
AIV 读回 grid_k x tile x 4B / W_L2
AIV 求和 grid_k x tile / Q_AIV
写回 tile x outB / W_L2
AIV 求和 grid_k x tile / Q_AIV(fp32)
写回 tile x outB / W (最终输出落点, issue#30):
整 case 可驻留 (S_A) 时 W = W_L2; 否则直写 GM (W = W_GM)
"""
b4 = 4
w_l2 = spec.bw_l2
w_out = spec.bw_gm if out_to_gm else w_l2
t_write_partial = grid_k * tile_elems * b4 / w_l2
t_read_back = grid_k * tile_elems * b4 / w_l2
t_sum = grid_k * tile_elems / spec.q_aiv
t_write_out = tile_elems * out_dtype_bytes / w_l2
t_write_out = tile_elems * out_dtype_bytes / w_out
return t_write_partial + t_read_back + t_sum + t_write_out