统一MC2 V300中group_token,moe_combine, moe_dispatch,mega_moe在gfrun默认4线程下的测试方式 - #159
Closed
KeepTryingTo wants to merge 1 commit into
Closed
KeepTryingTo wants to merge 1 commit into
KeepTryingTo wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
group_token 单 PE ELF 在 gfrun 默认 4 线程下 R2=3 精度失败:根因分析与解决方案
benchmark/one-level-arch下kernels/solution/group_token_old、kernels/solution/group_token_vec(以及对照moe_combine、moe_dispatch)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 线程"碰巧"不出问题。-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 两份softcore.multiThreadNum默认 4),可用-s softcore.multiThreadNum=N覆盖(仅支持 1 或 4)R2 = 3,精度不过-s softcore.multiThreadNum=1即可通过;_mtELF 配 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 两例为本次现场编译)。multiThreadNum=1multiThreadNum=1multiThreadNum=1multiThreadNum=13. 根因详解
3.1 gfrun 默认就是 4 线程,"单 PE ELF"并不会只跑在一个 PE 上
SuperScalarModel/configs/softcore_config.h:11:uint64_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 入口(_start→main),每个 PE 只拥有独立栈(InitializeThreadStacks,SoftCore.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() 的返回值
R2取自 PE0 的 A0 寄存器,即 PE0main()的返回值(SuperScalarModel/emulator/main.cpp:233)。test/solution/group_token_vec/src/group_token_vec.cpp:181-186、group_token_old.cpp:201-207):1= Phase 1 专家计数不符;2= Phase 2 段计数不符;3= Phase 2 分组后的 token id 集合不符(idMatch != idTotal);4/5(6)= 段边界 / 排序结果不符。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):tokenPerExpertCnt[expertId]++(group_token_vec.hpp:63;group_token_old.hpp:75)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-153、group_token_old.cpp:166-167)3.4 为什么 moe_combine / moe_dispatch 的单 PE ELF 默认 4 线程没问题:全程 PE-幂等
对比V300的测试代码:
moe_combine_v2.cpp:26-32、moe_dispatch_v2.cpp:35-44只做确定性赋值,每个 PE 写入的字节完全相同。++/+=;grepmoe_combine_v2.hpp无任何自增/原子操作。moe_dispatch_v2.hpp:213的int32_t expertCounts[MoeExpertNum] = {0};(局部数组)与:247的writePos都是栈变量,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 变体显式指定线程数
规则:gfrun 的线程数必须与 ELF 编译时的 PE 语义配对。
compile.all头注释中已记录每个算子的正确跑法,测试时应照注释执行,不要依赖 gfrun 默认值。方案二(代码修复,为了统一代码的执行模式,选择对代码进行修复):单 PE 测试 main() 加 PE0 门控
在单 PE 测试的
main()开头加:get_thread_idx()来自工具链自带pto_tileop.hpp(读只读 PEID SSR 0x0802,返回 0..3),_mt测试已在用(group_token_vec_mt.cpp:81),无需新增依赖。return 0是安全的:gfrun 中每个 PE 独立走_start.s的退出路径,互不阻塞(_mtELF 的 PE1–3 在最终 barrier 后也是提前返回的)。test/solution/group_token_old/src/group_token_old.cpp、test/solution/group_token_vec/src/group_token_vec.cpp(以及其它含共享 RMW 的单 PE 用例)。5 结果复现
Uploading group_token_elf.zip…
Group token vec 修改后
Group token vec修改前
Group_token_old修复后
Group_token_old修复前
6. 结论
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 一致,故默认配置不报错。-s softcore.multiThreadNum=1,_mtELF 一律-s softcore.multiThreadNum=4(方案一);main()加if (get_thread_idx() != 0) return 0;PE0 门控(方案二,已实测两种线程数下均 R2=0);_mtELF 反向不配对(1 线程)会在 PE 间 barrier 上挂死,同样必须配对线程数。