test(tlsu): 端到端 memory op 压测用例(S/C/R 三组) - #59
liujiang833 wants to merge 10 commits into
Conversation
The minimal testable unit for TLOAD is copy = TLOAD + TSTORE: TLOAD's destination is a tile register, which never appears in a memory dump, so with architectural memory as the criterion TLOAD alone produces nothing observable. Case names use `copy` rather than `tload` to avoid implying otherwise. copy_128b moves 4x8 fp32 = 128 B, exactly one BEAT_SIZE of the Streaming L2 backend, aligned and not crossing a cacheline. The harness pieces this needed: - cross_model_result lives in .bss. The ELF loader creates a bank for every SHF_ALLOC section, and only SHF_EXECINSTR sections are write protected, so .bss is both mapped and writable. - cross_model_result_size must be an absolute symbol: the dump tooling reads the symbol's value, not its contents, which C cannot express. TLSU_STR needs two-level expansion -- stringifying the macro parameter directly emits `.set cross_model_result_size, RESULT_SIZE`, which silently leaves the symbol out of the table with no diagnostic. - The finisher is a 32-bit store of exactly 0x5555 to 0x10009000. gfsim takes the low 16 bits, gfrun requires width==4 and the full value to match, so only that form satisfies both. - A drain window before the finisher. The finisher terminates the simulation as soon as it retires, and writes not yet globally visible disappear from the dump: a scalar store placed immediately before it vanishes, while the same store moved ahead of the tile ops survives. Do not use a degenerate M=1 shape -- the compiler emits a TLOAD whose dstTile is empty and both models abort on it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
以架构内存为唯一判据、gfrun 为黄金参考的 TLSU 端到端用例。 tlsu_bench.hpp 收拢三条硬约束(源数据烘进 ELF、收尾留排空窗口、不用 M=1 的退化形状)和自识别编码 0xT_rrr_cccc —— 每个元素编码来源图样与自己的行列 号,于是错位、行距、beat 次序、RAW/WAR 违例在 hex dump 里各有各的签名,且 全零恒等于"从未被写"。源对象按物理容量分配、有效区之外填 0xF,让过读可见 而不是未定义行为。 S 组(数据通路): s1_copy_i32_32x32 行宽 128 B,多行挤一条 cacheline s2_strided_i32_8x128 从 8x512 抠 8x128,行距 2048 B,真正用上 B.IOR src1 s3_two_dst_i32 两条独立 copy 写两个目的区(C 组失败后的归因对照) C 组(定序): c1_store_load_i32 RAW c2_load_store_i32 WAR c3_load_store_load_i32 load -> store -> load c1_store_gap_load_i32 c1 插延时的归因变体,-DTLSU_GAP 可扫 C 组的 witness 区在序列开始前先预填图样 D:没有它,"那条 store 没落地"和 "落地了但搬的是零"在 dump 里都是全零,两个不同的故障分不开。 结果缓冲补上 alignas(256),此前靠链接器碰巧对齐。 S 组全过,C 组全挂 —— 详见 SuperScalarModel docs/tlsu_e2e_findings_2026-08-13.md。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PTO 把 GM 行距定义为逻辑元素数(PTO-TILE-MODEL-MEMORY-STRIDE), Linx-TileOP-API@f35d3aa 已把绑定处的 `* sizeof(DType)` 去掉。用例源码本来就 是按元素声明的(RowMajor 第三个模板参数),只是注释还在描述旧的字节编码, 一并更新;tlsu_bench.hpp 把这条单位契约写成第 4 条硬约束。 新增 s2b_stride_elem_i32_8x128。s2_strided 用 512 元素行距,int32 是 4 字节, 而 128 列的行长恰好也是 512 字节 —— "把 512 当字节"这个错误正好退化成一次稠密 搬运,虽然 PAD 标记仍能让它显形,但失败签名长得像"行距被忽略"而不是"单位搞 错了"。新用例取 160 元素行距(640 字节 vs 行长 512 字节),刻意与 sizeof 无 倍数关系:按字节误读则行距成了 40 个元素,比行长 128 还小,每行都会退回上一 行内部,dump 里出现大量重复的低列号元素,两种错误一眼可分。 删掉 c1_store_gap_load。它是归因探针不是用例:延时够长就过,PASS 只证明"等得 够久",不证明定序正确 —— 定序再坏一次它照样绿。它要区分的"定序错 vs 可见性 断"已经定论,任务结束了。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
S 组测数据通路,C 组测定序,但两组都是三五条指令的小序列,队列压力浅:c1 全程
只有 8 个 tile op,LIQ/STQ/SCB 根本填不满。R 组补的是这一段 —— 一次 200 个块,
让交错深度和在飞条目数都上到真实量级。
随机的只有序列本身:块数、load/store 的交错、每一步用哪个 tile 寄存器、读写哪
一块内存。形状、dtype、行距一律固定(8x128 int32,稠密行距),基址一律 256 字
节对齐(区域按 4096 B 紧排,是 256 的整数倍)。这样一旦失败,唯一的变量就是定
序与交错,不必再去排除形状或寻址的嫌疑。
序列里天然长出 RAW / WAR / WAW 和长距离 in-flight 交错:生成器有一半概率把写过
的区域读回来,这正是 RAW 的来源。
生成器按种子出确定结果,生成的 .cpp 一并提交 —— 失败可复现(记下种子即可)、
构建不依赖 Python、生成的源码人能读也能手改来缩例。
首轮(seed=20260814, 200 块 = 91 TLOAD + 109 TSTORE):
gfrun 非零 49152 16 个区域全部写到(16 x 3072)
gfsim_new 非零 49152 与 gfrun 逐字节一致
gfsim_legacy 非零 29952 差 19520 字节,首处 byte 4097
legacy 错得比 C 组彻底得多,说明这组用例的鉴别力确实来自序列深度,不是
换个写法重测一遍 C 组。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
viz_random_case.py 两件事:
1. 把生成的 r1_random_seq_i32.cpp 画成可逐步推进的 GM 演进图(本地 html):
每一条指令的数据从哪到哪、该步之后每个 GM 区域应该装什么(首字/末字、
转手次数、上一跳来源)、TLOAD 明确标"GM 不变"。期望值由源码与自识别编码
闭式推出,不依赖任何模型。
2. --check 把这套推导用作独立判据:
python3 viz_random_case.py src/r1_random_seq_i32.cpp --check dump.bin
三方比对以 gfrun 为黄金参考,而 gfrun 与 gfsim 共用 isa/ 解码器 —— 共用层
错了两边一起错,比对照样 IDENTICAL。2026-08-14 的 ADDTPC 回归正是这样把
整套用例伪装成全绿的。独立判据只依赖用例源码,因此能独立判死:
gfrun PASS 65536 字节逐字节一致
gfsim_new PASS
gfsim_legacy FAIL 首处不一致 @字节 4096(R1 第 0 行第 0 列)
还能直接指出机制:legacy 在块 2/3 没拦住 RAW,读到零,零被搬进了 R1。
生成的 html 不进仓库(.gitignore)。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 484104cc4b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // (ELF.cpp 对 SHT_PROGBITS 段逐字节 fn_load),完全不经过标量写路径。 | ||
| // constinit 保证它是静态初始化而非运行期动态初始化。 | ||
| struct SrcTile { | ||
| int32_t v[M * N]; |
There was a problem hiding this comment.
Pad the overridable copy source to the load footprint
When this case is built with the documented -DTLSU_M/-DTLSU_N override smaller than the physical tile load footprint, for example -DTLSU_M=4 -DTLSU_N=128, SrcTile allocates only M*N elements even though the comments above note that TLOAD may read rows inferred from the physical size. The load then consumes bytes past gSrc instead of deterministic embedded pattern/PAD data, so the gfrun/gfsim comparison can depend on adjacent ELF contents rather than isolating TLSU behavior.
Useful? React with 👍 / 👎.
TLSU 端到端用例做的是搬运语义的正确性验证(dump + 独立判据 + 跨模型差分), 不测 cycle,不应出现在 benchmark 的用例集里。原先放在 microbenchmark/tlsu/, 与 microbenchmark/memory/(25 个 TLSU 性能微基准)同名同级,极易被误认为是 第五个 benchmark family。 - 目录移到 microbenchmark/verification/tlsu/ - 本地 Makefile 补 -I$(CURDIR):Makefile.common 由路径推导 CATEGORY,嵌套一层后 -I$(MICROBENCH_ROOT)/$(CATEGORY) 只到 verification/,否则找不到 tlsu_bench.hpp - 新增 verification/tlsu/README.md,两处 README 说明它不参与 compile_all.sh - 产物路径变为 output/microbenchmark/verification/elf/verification/ 9 个用例在 linx_blockisa_llvm_musl 下清空 output 后全部重编通过。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
原 README 只提了一句"生成/可视化/校验",没说清参数。补两章: - gen_random_case.py:CLI 三个参数(--seed / --blocks / -o)的含义与默认值; 仓库内 r1 用例实际用的是 --seed 20260814 --blocks 200,展开为 91 TLOAD + 109 TSTORE(同参数复跑与仓库文件逐字节一致,已实测);形状/区域数/tile 数 等写死在脚本顶部的常量单列一表;序列的随机策略与提交约定。 - viz_random_case.py:-o 出可视化、--check 出判定两种模式;统计行三个数的 含义;html 页面的控件与键盘操作;--check 要求 dump 正好 65536 字节, 一致退 0、不一致或长度不符退 1,可直接接 CI。 顺带补上自识别编码 (tag<<28)|((row+1)<<16)|(col+1) 的说明,以及本地 Makefile 为何需要 -I$(CURDIR)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
原来随机体有一半概率去读只读图样,那一半 TLOAD 读的是永远不会被写的地址, 天然不与任何 store 冲突。现在改成两段:先用 32 块(16 TLOAD + 16 TSTORE)把 16 份图样各装进一个区,之后 --blocks 块随机体只在区与区之间搬运 —— 每次 TLOAD 读的都是可能刚被写过的区。 实测同为 200 块随机体:读只读图样的 TLOAD 46→16,近距 RAW(上次写该区后 ≤16 块就读回)28→42、占 TLOAD 31%→35%,区域→区域搬运 49→86。 代价是"错读了别的区"要靠内容区分,所以 16 份初始图样两两不同:区号编进元素值 空闲的 [27:24](有效行 r+1<=8,pad 行也只到 32,占到 [21:16] 为止,不相碰)。 0x15010001 = 图样 W、区 5、第 1 行、第 1 列,hex dump 按 nibble 直读。这让失败 可读 —— legacy 路径的 r1 dump 首字是 0x14010001,一眼看出串进了区 4 的内容。 配套: - viz_random_case.py 的 oracle 从跟 tag 改成跟 pattern id;区数不再写死, 从用例源码的 RESULT_SIZE 读出 - RAW 口径改为"近距":初始化后"读回写过的区"恒真、没有区分度,改成统计上次写 该区之后 ≤RAW_WINDOW(16) 块就读回。仅供看图,不参与判据 - README 补新方案说明、改前后密度对比,以及一条已知代价:图样多样性随序列长度 塌缩(16 → 9@75块 → 6@100块 → 3@末尾),判据只看终态,越靠后的错读越易被 同内容掩盖;要分辨力就缩短 --blocks 多跑种子,别拉长单条序列 验证:SuperScalarModel dev_tlsu(PR#210)自建 gfrun/gfsim 跑全套 —— 9/9 用例 gfsim_new 与 gfrun 逐字节 IDENTICAL,r1 独立判据 PASS;legacy 在 c1/c2/c3/r1 四个定序用例 FAIL,与该 PR 描述的阴性对照一致。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
保留原来的混读策略(TLOAD 一半从只读图样装载、一半读回写过的区),只把只读图样 从 4 份增到 16 份,编号编进元素值空闲的 [27:24]。 16 份的作用有两层: 1. 可分辨。图样只有 4 份时,"搬错了源"有 1/4 概率被同内容掩盖,判据看不见。 2. 不塌缩。TSTORE 只复制不创造,区与区之间搬得越久、存活的图样种类越少(遗传 漂变);src 装载持续把新编号注回工作区,序列再长也不收敛成少数几种内容。 实测同为 200 块、只统计已写过的区:末态不同图样数 4→6 种,错读被掩盖的概率 28.3%→12.5%,中途也不再出现 4 份源那种 46% 的尖峰。 这一版取代了上一个 commit 的 workspace-only 方案:那版冲突密度更高(近距 RAW 42 vs 27),但随机体不读 src 就没有新图样注入,多样性从 16 一路塌到 3、末态掩盖概率 31%。判据只看终态,可分辨性优先。 改名:TlsuWsTag/MakeTlsuWsPattern → TlsuIdTag/MakeTlsuIdPattern,语义从"区号"变 成"图样编号"。编码位段不变,S/C 组的基础编码不受影响。 验证:SuperScalarModel dev_tlsu(PR#210)自建 gfrun/gfsim 跑全套 —— 9/9 用例 gfsim_new 与 gfrun 逐字节 IDENTICAL,r1 独立判据 PASS(65536 字节),legacy 在 c1/c2/c3/r1 四个定序用例 FAIL,与该 PR 的阴性对照一致。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
run_tlsu_suite.sh 硬编码了本目录路径四处(ELFDIR / CASEDIR / 编译时 cd / VIZ)。 改了目录名却不同步那边,失败是静默的:脚本不报错,只在表格里整列打印"无 ELF", 看起来像用例没编出来。2026-08-18 的目录迁移踩过一次,所以把这条契约写进 README, 连同"目录名 / NUM_REGIONS / 用例文件名都是跨仓契约"这句提醒。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
466151e to
b59d91d
Compare
内容
TLSU 端到端 memory op 用例,判据是架构内存 dump,gfrun 为黄金参考,gfsim 新旧两条 TLSU 路径三方比对。驱动脚本在 SuperScalarModel 侧(
scripts/run_tlsu_suite.sh)。三组共 9 个用例,全部新增于
microbenchmark/tlsu/:copy_i32_8x128、s1_copy_i32_32x32(多行挤一条 cacheline)、s2_strided_i32_8x128(512 元素行距)、s2b_stride_elem_i32_8x128(160 元素行距,element/byte 判别)、s3_two_dst_i32(双目的区)c1_store_load_i32(RAW)、c2_load_store_i32(WAR)、c3_load_store_load_i32r1_random_seq_i32,200 块随机 TLOAD/TSTORE,由gen_random_case.py按固定种子生成并提交配套:
tlsu_bench.hpp—— 公共骨架,三条硬约束(数据烘进 ELF、收尾排空窗口、禁用退化形状)+ 自识别编码0xT_rrr_cccc(每个元素自带来源标记与行列号,dump 里直接可读)viz_random_case.py—— R 组用例的可视化 + 独立判据(--check):期望值由源码与自识别编码闭式推出,不依赖任何模型。已实测与 gfrun 的 65536 字节 dump 逐字节一致背景与结果
这些用例驱动了 SuperScalarModel 侧的一轮修复(见那边的 PR):
注意
用例需要 SuperScalarModel 侧的
run_tlsu_suite.sh来驱动,且需要 linx 工具链(COMPILER_DIR)编译。单独拉这个 PR 不能直接跑。🤖 Generated with Claude Code