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

@@ -137,11 +137,13 @@ class TestTimingSanity(unittest.TestCase):
self.assertIn(mb.timing.bottleneck, ("MTE2", "MMAD"))
def test_fixpipe_dtype_conversion(self):
# C 指定 fp16 输出时, 写出量按 2B 而非 L0C 的 4B
# C 指定 fp16 输出时, 写出量按 2B 而非 L0C 的 4B;
# 字节列为整芯片口径 (issue#29): fixpipe_bytes = B*MN*outB
case = mkcase(128, 64, 64, 512, dtype_c="fp16")
ib = IterBatchBranch().analyze(case)
expect = ib.plan.b_core * 64 * 64 * 2
expect = case.batch_c * 64 * 64 * 2
self.assertAlmostEqual(ib.timing.fixpipe_bytes, expect)
self.assertAlmostEqual(ib.timing.fixpipe_bytes, case.output_bytes)
class TestIssueRegression(unittest.TestCase):
@@ -423,5 +425,172 @@ class TestZeroCmdHandling(unittest.TestCase):
self.assertEqual(t.t_total, t.t_total)
class TestIssue27to30(unittest.TestCase):
"""第四轮评审 (issue#27-#30, 设计文档 docs/05).
#27 MergeBatch Cube 公式复核 (每步 2*(b0M)(b0N)K x b_core/b0 步);
#28 dtype 感知算力 (Cube/AIV 速率表);
#29 GM 首读下限 GM>=V_in + 字节列整芯片口径;
#30 Fixpipe 输出落点 (整case驻留 -> L2, 否则直写 GM).
"""
def setUp(self):
from bmm_theory.hardware import ASCEND950PR
self.s = ASCEND950PR
self.router = BranchRouter()
# ---------------- #27 ----------------
def test_issue27_merge_cube_flops_matches_formula(self):
from bmm_theory.branches.merge_batch import MergeBatchBranch
case = mkcase(2048, 32, 32, 256)
mb = MergeBatchBranch().analyze(case)
self.assertTrue(mb.capable)
p, t = mb.plan, mb.timing
b0, bc = p.merge_b0, p.b_core
# 整芯片列 = B*b0*2MNK; 等价 (b_core/b0) 步 x 每步 2*(b0M)(b0N)K x C 核
step_flops = 2.0 * (b0 * 32) * (b0 * 32) * 256
self.assertAlmostEqual(t.cube_flops,
step_flops * (bc / b0) * self.s.aic_num)
self.assertAlmostEqual(t.cube_flops,
case.batch_c * b0 * 2.0 * 32 * 32 * 256)
# t_mmad = 每核 flops / 单核算力
self.assertAlmostEqual(t.t_mmad,
(bc * b0 * 2.0 * 32 * 32 * 256) / self.s.q16)
def test_issue27_merge_gm_chip_col_equals_input(self):
# K 截断合并: GM 每字节一次 -> gm 整芯片列 = V_in (issue#29 口径)
from bmm_theory.branches.merge_batch import MergeBatchBranch
case = mkcase(2048, 32, 32, 256)
t = MergeBatchBranch().analyze(case).timing
self.assertAlmostEqual(t.gm_read_bytes, case.input_bytes)
self.assertAlmostEqual(t.l2_read_bytes, 0.0)
# ---------------- #28 ----------------
def test_issue28_cube_dtype_rate(self):
from bmm_theory.branches.iter_batch import IterBatchBranch
base = mkcase(128, 64, 64, 512) # IterBatch 形态 b, 各 dtype 均可行
t16 = IterBatchBranch().analyze(base).timing
t32 = IterBatchBranch().analyze(
mkcase(128, 64, 64, 512, dtype_a="fp32", dtype_b="fp32")).timing
t8 = IterBatchBranch().analyze(
mkcase(128, 64, 64, 512, dtype_a="fp8", dtype_b="fp8")).timing
# 同计算量, 时延反比于 dtype 算力: fp32=1/2, fp8=2x (相对 bf16)
self.assertAlmostEqual(t32.t_mmad / t16.t_mmad, 2.0, places=6)
self.assertAlmostEqual(t8.t_mmad / t16.t_mmad, 0.5, places=6)
def test_issue28_aiv_dtype_rate_k1(self):
# K=1 AIV 逐元素: bf16 通量 2x fp32 -> fp32 时延为 bf16 的 2 倍
base = mkcase(64, 8192, 512, 1)
t_bf16 = BranchRouter().route(base)["timing"]
t_fp32 = BranchRouter().route(
mkcase(64, 8192, 512, 1, dtype_a="fp32", dtype_b="fp32"))["timing"]
self.assertAlmostEqual(t_fp32.t_mmad / t_bf16.t_mmad, 2.0, places=6)
# ---------------- #29 ----------------
def test_issue29_gm_floor_and_chip_columns(self):
# 各分支代表 case: GM 整芯片列 >= V_in (R1), 且迭代/合并/转matmul/special
# 等于 V_in (无重复读结构)
from bmm_theory.branches.iter_batch import IterBatchBranch
from bmm_theory.branches.merge_batch import MergeBatchBranch
from bmm_theory.branches.stream_k import StreamKBranch
from bmm_theory.branches.asw_basic import AswBasicBranch
checks = [
(IterBatchBranch().analyze(mkcase(128, 64, 64, 512)).timing,
mkcase(128, 64, 64, 512)),
(MergeBatchBranch().analyze(mkcase(2048, 32, 32, 256)).timing,
mkcase(2048, 32, 32, 256)),
(StreamKBranch().analyze(mkcase(4, 128, 128, 10240)).timing,
mkcase(4, 128, 128, 10240)),
(AswBasicBranch().analyze(mkcase(8, 4096, 4096, 1024)).timing,
mkcase(8, 4096, 4096, 1024)),
]
for t, case in checks:
self.assertGreaterEqual(t.gm_read_bytes + 1e-6, case.input_bytes,
f"{case.case_id} GM < V_in")
# 转Matmul / 特殊分支 (K=1) 亦等于 V_in
r = self.router.route(mkcase(1, 2048, 2048, 2048))
t = r["timing"]
self.assertGreaterEqual(t.gm_read_bytes + 1e-6,
BmmCase(case_id="x", batch_a=1, batch_b=1,
m=2048, n=2048, k=2048).input_bytes)
r = self.router.route(mkcase(128, 256, 256, 1))
self.assertAlmostEqual(r["timing"].gm_read_bytes, 128 * (256 + 256) * 2)
def test_issue29_gm_floor_random_smoke(self):
# 随机多 dtype 冒烟: 所有可行方案 GM 整芯片列 >= V_in, 无 NaN
import random
rng = random.Random(20260609)
dtypes = ["bf16", "fp16", "fp32", "int8", "fp8", "fp4"]
for _ in range(400):
dt = rng.choice(dtypes)
kw = dict(dtype_a=dt, dtype_b=dt, dtype_c=rng.choice(["bf16", "fp16"]))
b = rng.choice([1, 2, 4, 8, 16, 32, 64, 128, 256, 2048])
m = rng.choice([1, 8, 32, 64, 128, 256, 1024, 4096])
n = rng.choice([1, 8, 32, 64, 128, 256, 1024, 4096])
k = rng.choice([0, 1, 32, 64, 128, 256, 512, 1024, 4096])
c = BmmCase(case_id="gm", batch_a=b, batch_b=b, m=m, n=n, k=k, **kw)
r = self.router.route(c)
t = r["timing"]
self.assertIsNotNone(t, c.case_id)
self.assertEqual(t.t_total, t.t_total) # 非 NaN
self.assertGreaterEqual(t.gm_read_bytes + 1e-6, c.input_bytes,
f"{dt} b={b} m={m} n={n} k={k} gm={t.gm_read_bytes}")
# ---------------- #30 ----------------
def test_issue30_iter_output_l2_when_whole_case_fits(self):
from bmm_theory.branches.iter_batch import IterBatchBranch
# 整 case 输入+输出 16.8MB+1.0MB <= 128MB -> 输出写 L2 写口, GM 写=0
case = mkcase(128, 64, 64, 512)
self.assertLess(case.input_bytes + case.output_bytes, self.s.l2_bytes)
t = IterBatchBranch().analyze(case).timing
self.assertAlmostEqual(t.fixpipe_bytes, case.output_bytes)
self.assertAlmostEqual(t.t_fixpipe, case.output_bytes / self.s.bw_l2)
def test_issue30_iter_output_gm_when_case_overflows(self):
from bmm_theory.branches.iter_batch import IterBatchBranch
# 输出 268MB > L2 余量 -> 直写 GM (输入优先驻留)
case = mkcase(128, 1024, 1024, 64)
self.assertGreater(case.input_bytes + case.output_bytes, self.s.l2_bytes)
t = IterBatchBranch().analyze(case).timing
self.assertAlmostEqual(t.fixpipe_bytes, case.output_bytes)
self.assertAlmostEqual(t.t_fixpipe, case.output_bytes / self.s.bw_gm)
def test_issue30_asw_scene_whole_case_labels(self):
# S_A (整case全驻留): l2_policy_out=resident, t_fixpipe 走 L2 写口
case_sa = mkcase(2, 4096, 4096, 512)
self.assertLess(case_sa.input_bytes + case_sa.output_bytes, self.s.l2_bytes)
r = self.router.route(case_sa)
self.assertEqual(r["branch"], "ASW_Basic")
self.assertTrue(r["plan"].l2_policy_out.startswith("resident"), r["plan"].l2_policy_out)
self.assertIn("A_整case全驻留", r["plan"].note)
self.assertAlmostEqual(r["timing"].t_fixpipe,
case_sa.output_bytes / self.s.bw_l2)
# S_B (整case超L2, 单batch输入可驻留): 输出直写 GM
case_sb = mkcase(64, 4096, 4096, 1024)
self.assertGreater(case_sb.input_bytes + case_sb.output_bytes, self.s.l2_bytes)
self.assertLess(4096 * 1024 * 2 * 2, self.s.l2_bytes) # 单batch输入 16.8MB
r = self.router.route(case_sb)
self.assertIn("ASW", r["branch"])
self.assertTrue(r["plan"].l2_policy_out.startswith("direct_gm"),
r["plan"].l2_policy_out)
self.assertAlmostEqual(r["timing"].t_fixpipe,
case_sb.output_bytes / self.s.bw_gm)
def test_issue30_to_matmul_output_l2_toggle(self):
# 转Matmul 粗估: 整case可驻留 -> L2; 大 case -> GM
small = mkcase(1, 2048, 2048, 2048)
self.assertLess(small.input_bytes + small.output_bytes, self.s.l2_bytes)
r = self.router.route(small)
self.assertEqual(r["branch"], "转Matmul")
self.assertAlmostEqual(r["timing"].t_fixpipe,
small.output_bytes / self.s.bw_l2)
big = mkcase(1, 8192, 8192, 4096)
self.assertGreater(big.input_bytes + big.output_bytes, self.s.l2_bytes)
r = self.router.route(big)
self.assertEqual(r["branch"], "转Matmul")
self.assertAlmostEqual(r["timing"].t_fixpipe,
big.output_bytes / self.s.bw_gm)
if __name__ == "__main__":
unittest.main()