Skip to content

统一MC2 V300中group_token,moe_combine, moe_dispatch,mega_moe在gfrun默认4线程下的测试方式 - #159

Closed
KeepTryingTo wants to merge 1 commit into
PTO-ISA:mainfrom
KeepTryingTo:main
Closed

KeepTryingTo wants to merge 1 commit into
PTO-ISA:mainfrom
KeepTryingTo:main

Conversation

@KeepTryingTo

Copy link
Copy Markdown

group_token 单 PE ELF 在 gfrun 默认 4 线程下 R2=3 精度失败:根因分析与解决方案

  • 日期:2026-09-17
  • 范围:benchmark/one-level-archkernels/solution/group_token_oldkernels/solution/group_token_vec(以及对照 moe_combinemoe_dispatch
  • 结论先行:gfrun 的 softcore.multiThreadNum 默认值就是 4,且 4 线程时 4 个 PE 会从同一 ELF 入口 SPMD 并发执行同一份程序、共享同一份 .data/.bss 内存。group_token 的"单 PE"测试程序在共享静态缓冲上做了大量非幂等的读-改-写(直方图 cnt[id]++、分组游标 cnt[min]++ + scatter 写),4 个 PE 无同步并发执行时这些 RMW 互相竞争,kernel 输出与参考结果被破坏且破坏方式不同,Phase 2 分组 ID 校验失败,main 返回 3,gfrun 打印 R2=3。 moe_combine / moe_dispatch 的单 PE 测试全程是"纯写"(每个 PE 写入完全相同的值,RMW 只发生在 PE 私有栈上),4 PE 冗余执行后内存终态与单 PE 完全一致,所以默认 4 线程"碰巧"不出问题。
  • 正确用法澄清:单 PE ELF 必须用 -s softcore.multiThreadNum=1 跑;_mt(4-PE)ELF 用 -s softcore.multiThreadNum=4(即默认值)跑

1. 问题现象

现象 说明
编译产物 group_token_old / group_token_vec 各产出单 PE ELF 与 _mt(4-PE)ELF 两份
gfrun 默认 仿真时默认 4 线程(softcore.multiThreadNum 默认 4),可用 -s softcore.multiThreadNum=N 覆盖(仅支持 1 或 4)
失败表现 单 PE 的 group_token ELF 用 gfrun 默认参数跑 → R2 = 3,精度不过
绕过方式 单 PE ELF 显式指定 -s softcore.multiThreadNum=1 即可通过;_mt ELF 配 4 线程通过
对照 moe_combine / moe_dispatch单 PE ELF 用默认 4 线程却没有问题

疑问点:为什么同样是"单 PE ELF",group_token 过不了默认配置,moe 系列却能过?

2. 复现与对照实验(本机实测,2026-09-17)

https://github.com/LinxISA/SuperScalarModel/blob/main/docs/gfrun-usage.md
gfrun:/mnt/workspace/projects/SuperScalarModel/bin/gfrun(源码基线含 configs/softcore_config.h 默认 multiThreadNum = 4)。
ELF:benchmark/one-level-arch/output/solution/... 下现编译产物(moe 两例为本次现场编译)。

# ELF gfrun 线程参数 R2 总指令数 结果
1 group_token_old(单 PE) 默认(=4) 3 151,650,872 失败(复现)
2 group_token_old(单 PE) multiThreadNum=1 0 3,037,904 通过
3 group_token_vec(单 PE) 默认(=4) 3 150,791,508 失败(复现)
4 group_token_vec(单 PE) multiThreadNum=1 0 2,823,059 通过
5 group_token_old_mt(4-PE) 默认(=4) 0 3,387,752 通过
6 group_token_old_mt(4-PE) multiThreadNum=1 挂起 在 PE 间 barrier 上无限空转
7 moe_combine_v2(单 PE) 默认(=4) 0 20,096 通过
8 moe_dispatch_v2(单 PE) 默认(=4) 0 270,052 通过
9 group_token_vec(单 PE)+ PE0 门控补丁 默认(=4) 0 2,823,145 修复有效(PE0 2,823,058 条指令干活,PE1–3 各 29 条指令即退出)
10 group_token_vec(单 PE)+ PE0 门控补丁 multiThreadNum=1 0 2,823,058 修复后两种线程数均通过

3. 根因详解

3.1 gfrun 默认就是 4 线程,"单 PE ELF"并不会只跑在一个 PE 上

  • SuperScalarModel/configs/softcore_config.h:11uint64_t multiThreadNum = 4; —— 不加 -s 时的默认值。
  • SuperScalarModel/emulator/SoftCore.cpp:417-418:只接受 1 或 4(kCorePeCount)。
  • 关键语义(SuperScalarModel/emulator/main.cpp:127-140 + SoftCore.cpp:469-478):multiThreadNum=4 时,gfrun 把 PE0~PE3 全部 reset 到同一个 ELF 入口_startmain),每个 PE 只拥有独立栈(InitializeThreadStacksSoftCore.cpp:438-453);ELF 只加载一次,.text/.data/.bss 对 4 个 PE 完全共享。调度上 SoftCore::Step()SoftCore.cpp:513-543)按 block 粒度轮转推进 4 个 PE —— 即 SPMD:4 个 PE 并发执行同一份程序、读写同一份全局内存

因此,"单 PE 的 ELF"用默认 gfrun 跑,实际是 4 个 PE 同时把整个测试程序(数据生成 + kernel + 参考实现 + 校验)各自完整执行一遍,且互相踩共享内存。只有显式 multiThreadNum=1 才是真正的单 PE 运行。

3.2 R2=3 的含义:PE0 上 main() 的返回值

  • gfrun 结束时打印的 R2 取自 PE0 的 A0 寄存器,即 PE0 main() 的返回值(SuperScalarModel/emulator/main.cpp:233)。
  • group_token 测试的返回码定义(test/solution/group_token_vec/src/group_token_vec.cpp:181-186group_token_old.cpp:201-207):
    • 1 = Phase 1 专家计数不符;
    • 2 = Phase 2 段计数不符;
    • 3 = Phase 2 分组后的 token id 集合不符(idMatch != idTotal
    • 4/5(6) = 段边界 / 排序结果不符。
  • 即 R2=3 精确对应 Phase 2 groupedTokenIds(以及/或参考 refGroupedIds)被竞争破坏

3.3 group_token 单 PE 测试里到处是"共享内存上的读-改-写"(非 PE-幂等)

单 PE 版测试(group_token_vec.cpp / group_token_old.cpp)没有任何 PE 区分、没有任何同步,所有缓冲都是跨 PE 共享的静态数组,且关键路径是 RMW:

kernel 侧(kernels/solution/group_token_vec/group_token_vec.hpp):

  • Phase 1 直方图:tokenPerExpertCnt[expertId]++group_token_vec.hpp:63group_token_old.hpp:75
  • Phase 2 分组游标 + scatter:
    idxInSection = expertSectionTokenCnt[minLocalExpId]++;              // group_token_vec.hpp:129
    groupedTokenIds[minLocalExpId * batchSize + idxInSection] = tokenId; // group_token_vec.hpp:133
    group_token_old.hpp:495 的 linx 标量路径同样是 expertSectionTokenCnt[minLocalExpId]++;SIMT 路径 asc_atomic_add 在 __linx 下不编译。)

参考实现/校验侧(测试 cpp 内,同样跑在共享内存上):

  • refExpertCnt[topkIndex[i]]++group_token_vec.cpp:34
  • uint32_t idx = refSectionCnt[minLocal]++; refGroupedIds[minLocal * bs + idx] = i;group_token_vec.cpp:44-45
  • 校验用暂存 static uint32_t buf[kBS]; static uint32_t ref[kBS]; 也是共享静态区(group_token_vec.cpp:152-153group_token_old.cpp:166-167
#   group_token_vec:     gfrun -t 1 -s softcore.multiThreadNum=1 -f <elf>

#   group_token_vec_mt:  gfrun -t 1 -s softcore.multiThreadNum=4 -f <elf>

3.4 为什么 moe_combine / moe_dispatch 的单 PE ELF 默认 4 线程没问题:全程 PE-幂等

对比V300的测试代码:

  • 数据生成是纯写moe_combine_v2.cpp:26-32moe_dispatch_v2.cpp:35-44 只做确定性赋值,每个 PE 写入的字节完全相同。
  • kernel 对共享缓冲只有纯写(TLOAD/TSTORE 搬运 + 确定性计算),没有任何共享缓冲上的 ++/+=;grep moe_combine_v2.hpp 无任何自增/原子操作。
  • 唯一的自增都在 PE 私有栈上moe_dispatch_v2.hpp:213int32_t expertCounts[MoeExpertNum] = {0};(局部数组)与 :247writePos 都是栈变量,PE 间互不可见。因此,单PE的moe_mega, moe_combine和moe_dispatch在默认的4线程情况能正常执行并输出R2=0。
  • 校验只读共享、只写局部

于是 4 个 PE 冗余执行时,每个 PE 在每个地址写的值都相同,内存终态与单 PE 执行逐字节一致,每个 PE 的 main 都返回 0,R2=0。这不是 gfrun 对 moe 有什么特殊照顾,而是 moe 的单 PE 测试恰好符合"PE-幂等"(冗余执行无害)的设计;group_token 的单 PE 测试不符合。

4. 解决方案

方案一(零代码改动,立即可用):按 ELF 变体显式指定线程数

# 单 PE(group_token_old / group_token_vec 等)
gfrun -t 1 -s softcore.multiThreadNum=1 -f <单PE.elf>

# 4-PE mt 版(multiThreadNum=4 恰为默认值,仍建议显式写出,防歧义、防默认值变更)
gfrun -t 1 -s softcore.multiThreadNum=4 -f <xxx_mt.elf>

规则:gfrun 的线程数必须与 ELF 编译时的 PE 语义配对compile.all 头注释中已记录每个算子的正确跑法,测试时应照注释执行,不要依赖 gfrun 默认值。

方案二(代码修复,为了统一代码的执行模式,选择对代码进行修复):单 PE 测试 main() 加 PE0 门控

在单 PE 测试的 main() 开头加:

int main()
{
    // 单 PE 用例:仅 PE0 执行。
    // gfrun 默认 softcore.multiThreadNum=4 会令 4 个 PE 从同一入口 SPMD
    // 并发执行本程序;本测试在共享静态缓冲上含读-改-写(直方图/分组游标),
    // 多 PE 冗余执行会互相竞争破坏结果。非 0 号 PE 直接退出即可。
    if (get_thread_idx() != 0) {
           return 0;
     }
    ...  
}
  • get_thread_idx() 来自工具链自带 pto_tileop.hpp(读只读 PEID SSR 0x0802,返回 0..3),_mt 测试已在用(group_token_vec_mt.cpp:81),无需新增依赖。
  • 非 0 PE 提前 return 0 是安全的:gfrun 中每个 PE 独立走 _start.s 的退出路径,互不阻塞(_mt ELF 的 PE1–3 在最终 barrier 后也是提前返回的)。
  • 适用文件:test/solution/group_token_old/src/group_token_old.cpptest/solution/group_token_vec/src/group_token_vec.cpp(以及其它含共享 RMW 的单 PE 用例)。

5 结果复现
Uploading group_token_elf.zip…

bin/gfrun -f solution_group_token_vec_group_token_vec.elf 

Group token vec 修改后

Memory: 0x10200 - 0x10248 (Size=0KB) [.rodata]
Memory: 0x1024c - 0x1025f (Size=0KB) [.eh_frame_hdr]
Memory: 0x10260 - 0x102ab (Size=0KB) [.eh_frame]
Memory: 0x112ac - 0x11fc3 (Size=3KB) [.text]
Memory: 0x12fc8 - 0x12fd7 (Size=0KB) [.init_array]
Memory: 0x12fd8 - 0x12fef (Size=0KB) [.data.rel.ro]
Memory: 0x13ff0 - 0x2b46b (Size=93KB) [.bss]
Memory: 0x2b470 - 0x2b47f (Size=0KB) [.sbss]
Memory: 0x2b480 - 0x803b480 (Size=131136KB) [stack mem]
Memory: 0x4000802000 - 0x4008803000 (Size=131076KB) [map mem]
Starting PE0 from 0x112ac

Thread:0Total Block number = 440755
Thread:0Total Inst number = 2823058
Thread:1Total Block number = 7
Thread:1Total Inst number = 29
Thread:2Total Block number = 7
Thread:2Total Inst number = 29
Thread:3Total Block number = 7
Thread:3Total Inst number = 29

Total Block number = 440776
Total Inst number = 2823145
Suaccelss to Reach the End of Benchmark! R2 = 0

Group token vec修改前

bin/gfrun -f  solution_group_token_vec_group_token_vec.elf 
Memory: 0x10200 - 0x10248 (Size=0KB) [.rodata]
Memory: 0x1024c - 0x1025f (Size=0KB) [.eh_frame_hdr]
Memory: 0x10260 - 0x102ab (Size=0KB) [.eh_frame]
Memory: 0x112ac - 0x11fb7 (Size=3KB) [.text]
Memory: 0x12fb8 - 0x12fc7 (Size=0KB) [.init_array]
Memory: 0x12fc8 - 0x12fdf (Size=0KB) [.data.rel.ro]
Memory: 0x13fe0 - 0x2b45b (Size=93KB) [.bss]
Memory: 0x2b460 - 0x2b46f (Size=0KB) [.sbss]
Memory: 0x2b470 - 0x803b470 (Size=131136KB) [stack mem]
Memory: 0x4000802000 - 0x4008803000 (Size=131076KB) [map mem]
Starting PE0 from 0x112ac

Thread:0Total Block number = 6244532
Thread:0Total Inst number = 37697877
Thread:1Total Block number = 6244532
Thread:1Total Inst number = 37697877
Thread:2Total Block number = 6244532
Thread:2Total Inst number = 37697877
Thread:3Total Block number = 6244532
Thread:3Total Inst number = 37697877

Total Block number = 24978128
Total Inst number = 150791508
Suaccelss to Reach the End of Benchmark! R2 = 3
bin/gfrun -f solution_group_token_vec_group_token_vec.elf -s softcore.multiThreadNum=1 
Memory: 0x10200 - 0x10248 (Size=0KB) [.rodata]
Memory: 0x1024c - 0x1025f (Size=0KB) [.eh_frame_hdr]
Memory: 0x10260 - 0x102ab (Size=0KB) [.eh_frame]
Memory: 0x112ac - 0x11fb7 (Size=3KB) [.text]
Memory: 0x12fb8 - 0x12fc7 (Size=0KB) [.init_array]
Memory: 0x12fc8 - 0x12fdf (Size=0KB) [.data.rel.ro]
Memory: 0x13fe0 - 0x2b45b (Size=93KB) [.bss]
Memory: 0x2b460 - 0x2b46f (Size=0KB) [.sbss]
Memory: 0x2b470 - 0x803b470 (Size=131136KB) [stack mem]
Memory: 0x4000802000 - 0x4008803000 (Size=131076KB) [map mem]
Starting PE0 from 0x112ac

Thread:0Total Block number = 440754
Thread:0Total Inst number = 2823059

Total Block number = 440754
Total Inst number = 2823059
Suaccelss to Reach the End of Benchmark! R2 = 0

Group_token_old修复后

bin/gfrun -f solution_group_token_old_group_token_old.elf 
Memory: 0x10200 - 0x10248 (Size=0KB) [.rodata]
Memory: 0x1024c - 0x1025f (Size=0KB) [.eh_frame_hdr]
Memory: 0x10260 - 0x102ab (Size=0KB) [.eh_frame]
Memory: 0x112ac - 0x11f43 (Size=3KB) [.text]
Memory: 0x12f48 - 0x12f5f (Size=0KB) [.data.rel.ro]
Memory: 0x13f60 - 0x333af (Size=125KB) [.bss]
Memory: 0x333b0 - 0x80433b0 (Size=131136KB) [stack mem]
Memory: 0x4000802000 - 0x4008803000 (Size=131076KB) [map mem]
Starting PE0 from 0x112ac

Thread:0Total Block number = 470080
Thread:0Total Inst number = 3037909
Thread:1Total Block number = 7
Thread:1Total Inst number = 29
Thread:2Total Block number = 7
Thread:2Total Inst number = 29
Thread:3Total Block number = 7
Thread:3Total Inst number = 29

Total Block number = 470101
Total Inst number = 3037996
Suaccelss to Reach the End of Benchmark! R2 = 0

Group_token_old修复前

bin/gfrun -f solution_group_token_old_group_token_old.elf 
Memory: 0x10200 - 0x10248 (Size=0KB) [.rodata]
Memory: 0x1024c - 0x1025f (Size=0KB) [.eh_frame_hdr]
Memory: 0x10260 - 0x102ab (Size=0KB) [.eh_frame]
Memory: 0x112ac - 0x11f37 (Size=3KB) [.text]
Memory: 0x12f38 - 0x12f4f (Size=0KB) [.data.rel.ro]
Memory: 0x13f50 - 0x3339f (Size=125KB) [.bss]
Memory: 0x333a0 - 0x80433a0 (Size=131136KB) [stack mem]
Memory: 0x4000802000 - 0x4008803000 (Size=131076KB) [map mem]
Starting PE0 from 0x112ac

Thread:0Total Block number = 6273856
Thread:0Total Inst number = 37912718
Thread:1Total Block number = 6273856
Thread:1Total Inst number = 37912718
Thread:2Total Block number = 6273856
Thread:2Total Inst number = 37912718
Thread:3Total Block number = 6273856
Thread:3Total Inst number = 37912718

Total Block number = 25095424
Total Inst number = 151650872
Suaccelss to Reach the End of Benchmark! R2 = 3
bin/gfrun -f solution_group_token_old_group_token_old.elf -s softcore.multiThreadNum=1 
Memory: 0x10200 - 0x10248 (Size=0KB) [.rodata]
Memory: 0x1024c - 0x1025f (Size=0KB) [.eh_frame_hdr]
Memory: 0x10260 - 0x102ab (Size=0KB) [.eh_frame]
Memory: 0x112ac - 0x11f37 (Size=3KB) [.text]
Memory: 0x12f38 - 0x12f4f (Size=0KB) [.data.rel.ro]
Memory: 0x13f50 - 0x3339f (Size=125KB) [.bss]
Memory: 0x333a0 - 0x80433a0 (Size=131136KB) [stack mem]
Memory: 0x4000802000 - 0x4008803000 (Size=131076KB) [map mem]
Starting PE0 from 0x112ac

Thread:0Total Block number = 470079
Thread:0Total Inst number = 3037904

Total Block number = 470079
Total Inst number = 3037904
Suaccelss to Reach the End of Benchmark! R2 = 0

6. 结论

  1. 根本原因:gfrun 默认 softcore.multiThreadNum=4,此时 4 个 PE 从同一 ELF 入口 SPMD 并发执行同一程序并共享 .data/.bss;group_token 单 PE 测试在共享静态缓冲上含大量非幂等读-改-写(直方图 cnt[id]++、分组游标 cnt[min]++ + scatter),4 PE 无同步并发执行导致丢失更新/重复槽位/初始化与累加交错,kernel 输出与参考结果被破坏,Phase 2 分组 ID 校验失败 → main 返回 3 → R2=3。moe_combine/moe_dispatch 单 PE 测试全程纯写、RMW 仅在 PE 私有栈上(PE-幂等),4 PE 冗余执行结果与单 PE 一致,故默认配置不报错。
  2. 解决
    • 立即:单 PE ELF 一律 -s softcore.multiThreadNum=1_mt ELF 一律 -s softcore.multiThreadNum=4(方案一);
    • 推荐:单 PE 测试 main()if (get_thread_idx() != 0) return 0; PE0 门控(方案二,已实测两种线程数下均 R2=0);
    • 长期:runner 按 ELF 变体显式传线程数(方案三);新增单 PE 用例按 PE-幂等规范编写(方案四)。
  3. 注意_mt ELF 反向不配对(1 线程)会在 PE 间 barrier 上挂死,同样必须配对线程数。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant