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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user