Fix #31/#32: 切B类GM每字节恰一次=V_in(去K切分整段上取) / ASW场景升级(单侧全驻留+对侧滑窗->S_B, S_C最小替换2D分组+窗口L2计账) / 06文档§3+Step5与docs/05同步
#31 IterBatch/MergeBatch: K切分各(batch,K段)互不重叠+驻留侧每batch一次+末段按实际剩余 -> GM读取量=V_in(与L2容量无关), GM数据时延=V_in/W_GM; n_K仅决定DMA命令数(T_cmd) b64_m16_n256_k512 形态c: GM 25.07MB->17.83MB=V_in; 回归: 形态c/d非整除+L1绑定三类断言 #32 ASW: (1)S_B扩展单侧全驻留+对侧滑窗(a_b/b_b+2*对侧单块<=L2) -> GM=V_in, 6个场景C行回落S_B; (2)S_C在整L2容量约束下搜索最小GM=ceil(n_cnt/n_grp)a_b+ceil(m_cnt/m_grp)b_b(取代L2/2对半预算), 并计组内窗口L2流量((n_cnt-ceil)a_b+(m_cnt-ceil)b_b), 与S_B'驻留命中走L2'口径一致; b8_m131072_n8192_k8192 GM倍率4.76x->3.88x(物理下界~3.9x, 双侧均超L2) 大方形K行(如b128_m8192_n8192_k7168)由MMAD 253ms->MTE2(L2口)295ms: 共享块重复读1.38TB 经L2读口5.2TB/s, 如实计账(原C窗口流量零计低估) - docs/06 §3与Step5重写为S_A/S_B/S_C+两段链口径(旧r_in单段/除B/对半预算口径废弃) - docs/05 R5/R6/§3.1/§4.1/§4.3/§5与docs/01、02(01_MergeBatch/02_IterBatch GM口径附注)同步 - tests 54/54; 压力seed7/6000+seed2024/4000: 0违规/0占位/0NaN/0GM<V_in; examples重生成0diff
This commit is contained in:
@@ -80,11 +80,12 @@ 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/#30, 设计文档 docs/05 §4, 芯片 L2 128MB)
|
||||
# Step 5: L2 场景判定 (issue#24/#30/#32, 设计文档 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)
|
||||
# S_B 单batch可驻留 (全驻留或单侧全驻留+对侧滑窗): GM 每字节一次,
|
||||
# 共享块重复读全部命中 L2 读口, 输出直写 GM;
|
||||
# S_C 双侧超L2: 最小替换 2D 分组 (组间落空 GM, 窗口 L2).
|
||||
scene = self._l2_scene(case, single_m, single_n, m_cnt, n_cnt)
|
||||
l2_out = ("resident(整case全驻留S_A: 输出驻留L2异步回写, GM写=0)"
|
||||
if scene["to_l2"] else
|
||||
"direct_gm(输出直写GM, 输入优先驻留L2)")
|
||||
@@ -118,8 +119,10 @@ class AswBasicBranch(Branch):
|
||||
fixpipe_unitflag=True,
|
||||
out_dtype_bytes=case.dtype_out_bytes,
|
||||
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']}"),
|
||||
f"V_out={case.output_bytes/1048576:.1f}MB, "
|
||||
f"L2={s.l2_bytes/1048576:.0f}MB); "
|
||||
+ (scene["note_extra"] + "; " if scene["note_extra"] else "")
|
||||
+ f"尾轮: {tail['reason']}"),
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
@@ -187,48 +190,88 @@ 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).
|
||||
def _l2_scene(self, case: BmmCase, sm: int, sn: int,
|
||||
m_cnt: int | None = None, n_cnt: int | None = None) -> dict:
|
||||
"""L2 场景判定 (make_plan 与 evaluate 共用, 设计文档 docs/05 §4.1, issue#32).
|
||||
|
||||
判定顺序 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_A -> S_B -> S_C (L2 为整芯片 128MB; 返回"每 batch"流量):
|
||||
S_A 整case全驻留: V_in + V_out <= L2 -> 输出驻留 L2 (R4, GM 写=0);
|
||||
S_B 单batch可驻留: 单 batch 输入可全驻留 (a_b+bb_b<=L2) 或**单侧全驻留 +
|
||||
对侧滑窗** (驻留侧 <= L2 且 + 2x对侧单块 <= L2) -> GM 每字节恰读一次
|
||||
(V_in), 共享块重复读全部命中 L2 读口 (n_cnt-1)/(m_cnt-1) 次;
|
||||
S_C 双侧均不可全驻留: 整 L2 容量约束下搜索**最小 GM** 的 2D 分组
|
||||
(力求最小 L2 替换), 组间共享块落空计 GM、组内窗口复用计 L2.
|
||||
"""
|
||||
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 m_cnt is None:
|
||||
m_cnt = max(ceil_div(m, max(sm, 1)), 1)
|
||||
if n_cnt is None:
|
||||
n_cnt = max(ceil_div(n, max(sn, 1)), 1)
|
||||
blk_a = a_b / max(m_cnt, 1) # A 行块字节 (tile 行宽 x K)
|
||||
blk_b = bb_b / max(n_cnt, 1) # B 列块字节 (K x tile 列宽)
|
||||
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:
|
||||
"to_l2": True,
|
||||
"gm_batch": a_b + bb_b,
|
||||
"l2_batch": (n_cnt - 1) * a_b + (m_cnt - 1) * bb_b,
|
||||
"note_extra": ""}
|
||||
if (a_b + bb_b <= s.l2_bytes or # 双侧全驻留
|
||||
bb_b + 2 * blk_a <= s.l2_bytes or # B 驻留 + A 行块滑窗
|
||||
a_b + 2 * blk_b <= s.l2_bytes): # A 驻留 + B 列块滑窗
|
||||
return {"code": "S_B",
|
||||
"label": "B_单batch输入驻留(整case超L2, 输出直写GM)",
|
||||
"to_l2": False, "r_gm": 1.0}
|
||||
"label": "B_单batch可驻留(全驻留或单侧驻留+对侧滑窗)",
|
||||
"to_l2": False,
|
||||
"gm_batch": a_b + bb_b,
|
||||
"l2_batch": (n_cnt - 1) * a_b + (m_cnt - 1) * bb_b,
|
||||
"note_extra": ""}
|
||||
g = self._l2_group_min_gm(case, m_cnt, n_cnt, blk_a, blk_b)
|
||||
return {"code": "S_C",
|
||||
"label": "C_单batch输入超L2分组执行(组间落空回GM)",
|
||||
"to_l2": False, "r_gm": self._l2_group_r_gm(case, sm, sn)}
|
||||
"label": "C_双侧超L2: 最小替换2D分组(组间落空GM, 窗口L2)",
|
||||
"to_l2": False,
|
||||
"gm_batch": g["gm_batch"],
|
||||
"l2_batch": g["l2_batch"],
|
||||
"note_extra": g["note"]}
|
||||
|
||||
def _l2_group_r_gm(self, case, sm, sn) -> float:
|
||||
"""场景 C (单 batch 输入仍超 L2): 分组执行的 GM 重复读倍率.
|
||||
def _l2_group_min_gm(self, case: BmmCase, m_cnt: int, n_cnt: int,
|
||||
blk_a: float, blk_b: float) -> dict:
|
||||
"""场景 C: 整 L2 容量约束下的最小 GM 2D 分组 (issue#32, docs/05 §4.3b).
|
||||
|
||||
每组 (m_grp x n_grp 个 tile) 输入工作集 <= L2; 组间共享块复用落空回 GM.
|
||||
预算 D 按**单 batch** 计 (issue#24: 线性映射同一时刻只激活 1 个 batch,
|
||||
不再除以总 batch 数).
|
||||
r_gm = (ceil(n_cnt/n_grp)*M + ceil(m_cnt/m_grp)*N)/(M+N).
|
||||
容量约束: m_grp*blk_a + n_grp*blk_b <= L2 (组工作集占满整 L2, 不再对半);
|
||||
目标: GM = ceil(n_cnt/n_grp)*a_b + ceil(m_cnt/m_grp)*bb_b 最小
|
||||
(A 行块每列组一次 GM 首读; B 列块每行组一次 GM 首读);
|
||||
窗口 L2: (n_cnt - ceil(n_cnt/n_grp))*a_b + (m_cnt - ceil(m_cnt/m_grp))*bb_b
|
||||
(组内共享块其余次复用走 L2 读口, 与 S_B 口径一致, issue#32 失真b);
|
||||
无可行分组 (单块对都超 L2) 时保守回落: 每共享块独立落 GM, 窗口不计.
|
||||
"""
|
||||
s = self.spec
|
||||
m, n, k = case.m, case.n, case.k
|
||||
dt = case.dtype_in_bytes
|
||||
d = s.l2_bytes / (k * dt)
|
||||
m_grp = max(1, int(d / (2 * sm)))
|
||||
n_grp = max(1, int(d / (2 * sn)))
|
||||
m_cnt = ceil_div(m, sm)
|
||||
n_cnt = ceil_div(n, sn)
|
||||
return (ceil_div(n_cnt, n_grp) * m + ceil_div(m_cnt, m_grp) * n) / (m + n)
|
||||
a_b = m * k * dt
|
||||
bb_b = k * n * dt
|
||||
best = None
|
||||
for mg in range(1, m_cnt + 1):
|
||||
for ng in range(1, n_cnt + 1):
|
||||
if mg * blk_a + ng * blk_b > s.l2_bytes:
|
||||
continue
|
||||
ga = ceil_div(n_cnt, ng)
|
||||
gb = ceil_div(m_cnt, mg)
|
||||
gm = ga * a_b + gb * bb_b
|
||||
if best is None or gm < best[0]:
|
||||
best = (gm, mg, ng, ga, gb)
|
||||
if best is None:
|
||||
return {"gm_batch": n_cnt * a_b + m_cnt * bb_b, "l2_batch": 0.0,
|
||||
"note": "分组无可行解(单块对超L2): 保守每共享块独立落GM"}
|
||||
gm, mg, ng, ga, gb = best
|
||||
l2 = (n_cnt - ga) * a_b + (m_cnt - gb) * bb_b
|
||||
r = gm / (a_b + bb_b)
|
||||
return {"gm_batch": gm, "l2_batch": l2,
|
||||
"note": f"最小替换分组 m_grp={mg}x n_grp={ng} (GM倍率{r:.2f}, "
|
||||
f"窗口L2/batch={l2/1048576:.1f}MB)"}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
def _decide_tail(self, case, sm, sn, n_blk, k_l1) -> dict:
|
||||
@@ -291,13 +334,15 @@ class AswBasicBranch(Branch):
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
def evaluate(self, case: BmmCase, plan: ImplPlan) -> HardwareTiming:
|
||||
"""MTE2 两段块级模型 (issue#24 用户口径 + #28/#29/#30):
|
||||
"""MTE2 两段块级模型 (issue#24 用户口径 + #28/#29/#30/#32):
|
||||
|
||||
- 首读走 GM (按 GM 带宽, 不叠加 L2); 共享块 (A 行块被 n_cnt 个 tile 读、
|
||||
B 列块被 m_cnt 个 tile 读) 驻留 L2 后其余 (n_cnt-1)/(m_cnt-1) 次读走
|
||||
L2 读口 (5.2TB/s 独享) —— 场景 S_A/S_B (单 batch 工作集可驻留);
|
||||
- 单 batch 输入超 L2 -> 场景 S_C 分组执行, GM 重复按组间落空计 (r_gm),
|
||||
窗口内复用未另计 (保守);
|
||||
L2 读口 (5.2TB/s 独享);
|
||||
- 场景 (与 make_plan 同源 _l2_scene, 返回每 batch GM/L2 流量):
|
||||
S_A/S_B: GM = V_in 一次 (S_B 含单侧全驻留+对侧滑窗, issue#32);
|
||||
S_C: 最小替换 2D 分组 (整 L2 容量约束下最小 GM), 组间落空计 GM、
|
||||
组内窗口复用计 L2;
|
||||
- 输出落点 R4 (issue#30): 仅 S_A (整 case 输入+输出 <= L2) 驻留 L2
|
||||
(5.2 写口, GM 写 = 0); S_B/S_C 直写 GM —— GM 读写共享总线累加由
|
||||
assemble 的 MTE2 链处理 (issue#23);
|
||||
@@ -306,7 +351,6 @@ class AswBasicBranch(Branch):
|
||||
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
|
||||
used = max(plan.used_core_num, 1)
|
||||
qc = s.q_cube(case.dtype_a, case.dtype_b)
|
||||
@@ -315,23 +359,16 @@ class AswBasicBranch(Branch):
|
||||
t_mmad = flops / (used * qc)
|
||||
|
||||
# ---- 字节量 (整芯片口径) ----
|
||||
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)
|
||||
|
||||
# ---- 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["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 # 场景 S_C: 组间复用落空(已计 GM), 窗口内复用保守不计
|
||||
# ---- L2 场景 (S_A/S_B/S_C, 与 make_plan 同源; 每 batch 流量 x B) ----
|
||||
scene = self._l2_scene(case, plan.single_core_m, plan.single_core_n,
|
||||
m_cnt, n_cnt)
|
||||
to_l2 = scene["to_l2"]
|
||||
gm_read = b * scene["gm_batch"]
|
||||
l2_read = b * scene["l2_batch"]
|
||||
t_gm = gm_read / (used * s.bw_pc)
|
||||
t_l2 = l2_read / (used * s.bw_l2_pc)
|
||||
|
||||
|
||||
@@ -197,8 +197,8 @@ class IterBatchBranch(Branch):
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 时延评估 (v1.1 §4 端到端模型, IterBatch 侧)
|
||||
# 口径: dtype 感知算力 (issue#28); 字节列整芯片 (issue#29);
|
||||
# 输出落点 R4 (issue#30).
|
||||
# 口径: dtype 感知算力 (issue#28); GM 每字节恰读一次 = V_in (issue#31);
|
||||
# 字节列整芯片 (issue#29); 输出落点 R4 (issue#30).
|
||||
# ------------------------------------------------------------------
|
||||
def evaluate(self, case: BmmCase, plan: ImplPlan) -> HardwareTiming:
|
||||
s = self.spec
|
||||
@@ -214,14 +214,16 @@ class IterBatchBranch(Branch):
|
||||
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) / qc
|
||||
t_write = m * n * out_b / w_fix
|
||||
|
||||
# 搬移: 每 batch n_K 次 GM->L1 (各 (batch,K段) 数据互不重叠, GM 每字节一次),
|
||||
# 每次含 T_cmd; dma_cmds 为单核命令数 (各核并行, issue#29 口径)
|
||||
# 搬移 (issue#31): 每 batch 的 K 段/驻留侧数据互不重叠, 每个输入字节恰好从
|
||||
# GM 读一次 (形态 c 驻留侧每 batch 只搬一次; 切 K 末段按实际剩余计) ->
|
||||
# GM 数据量 = V_in, 数据时延 = V_in/芯片带宽 (全核并发);
|
||||
# n_K/k_L1 只决定 DMA 命令次数 (T_cmd) 与双缓冲调度, 不放大数据量.
|
||||
# dma_cmds 为单核命令数 (各核并行, issue#29 口径)
|
||||
dma_cmds = b_core * n_k
|
||||
t_mte2_data = dma_cmds * t_load
|
||||
t_mte2_data = case.input_bytes / s.bw_gm
|
||||
t_dma_cmd = dma_cmds * s.t_cmd
|
||||
t_mte2 = t_mte2_data + t_dma_cmd
|
||||
|
||||
@@ -241,8 +243,7 @@ class IterBatchBranch(Branch):
|
||||
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=b * n_k * min(k_l1, k) * (m + n) * dt,
|
||||
l2_read_bytes=0.0,
|
||||
gm_read_bytes=case.input_bytes, 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),
|
||||
|
||||
@@ -195,27 +195,23 @@ class MergeBatchBranch(Branch):
|
||||
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
|
||||
# 每 K 分块计算时延 (未合并基准, v1.1 §4.1 符号) / 单 batch 输出写回 (R4)
|
||||
t_comp_chunk = 2.0 * m * n * k_l1 / qc
|
||||
t_write = m * n * out_b / w_fix # 单 batch 输出写回 (R4 落点带宽)
|
||||
t_write = m * n * out_b / w_fix
|
||||
|
||||
# 搬移 (issue#31): 合并组/切 K 各 (组, K段) 数据互不重叠, 每个输入字节恰好
|
||||
# 从 GM 读一次 (K截断与 L1 绑定均如此; 切 K 末段按实际剩余计, 不再整段上取)
|
||||
# -> GM 数据量 = V_in, 数据时延 = V_in/芯片带宽 (全核并发);
|
||||
# 搬移命令数只决定 T_cmd (与 IterBatch 的同/少 b0 倍关系不变):
|
||||
# K截断: 每核 b_core/b0 次合并搬入 (每次 b0 个 batch 全 K)
|
||||
# L1绑定: k_L1^m = k_L1/b0, n_K^m = b0*n_K, 命令数与 IterBatch 相同
|
||||
if k_truncated:
|
||||
# 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 每字节一次
|
||||
dma_cmds = b_core / b0
|
||||
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 上取)
|
||||
|
||||
dma_cmds = b_core * n_k
|
||||
t_dma_cmd = dma_cmds * s.t_cmd
|
||||
t_mte2_data = case.input_bytes / s.bw_gm
|
||||
t_mte2 = t_mte2_data + t_dma_cmd
|
||||
|
||||
# Cube (每核口径): 合并计算含冗余 (b0^2 输出, 有效 b0), 每核 b_core/b0 步,
|
||||
@@ -235,7 +231,7 @@ class MergeBatchBranch(Branch):
|
||||
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_chip, l2_read_bytes=0.0,
|
||||
gm_read_bytes=case.input_bytes, 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),
|
||||
|
||||
Reference in New Issue
Block a user