Skip to content

MC2 v300验证算子 - #74

Open
Cell-Cell wants to merge 15 commits into
PTO-ISA:mainfrom
Cell-Cell:mc2-v300-ops
Open

MC2 v300验证算子#74
Cell-Cell wants to merge 15 commits into
PTO-ISA:mainfrom
Cell-Cell:mc2-v300-ops

Conversation

@Cell-Cell

@Cell-Cell Cell-Cell commented Aug 24, 2026

Copy link
Copy Markdown

变更内容

新增 MC2 v300 验证算子(PTO one-level-arch),共 31 个文件 / ~6200 行:

Kernels

  • group_token_old — MoE Token 分组三阶段实现(直方图 + scatter + SIMD counting sort)
  • group_token_vec / group_token_vec_mt — vector 版 token 分组算子及多线程变体
  • mega_moe — MegaMoe A8W8 wave 自回环完整迁移(mega_moe_sim,对照源算子全逻辑迁移)
  • moe_dispatch / moe_combine — MoE dispatch v2 / combine v2 算子

Tests

  • 各算子对应 test/kernel/ 测试工程(Makefile + compile.all + src)
  • test/kernel/multi_thread/ 多线程测试

验证

  • 编译通过(compile.all)
  • 仿真验证(BUILD_AND_SIM)

关联 Issue:#81

Add MoE dispatch pipeline operators for v300 (PTO one-level-arch):
- group_token_old: three-stage MoE token grouping (histogram + scatter + counting sort)
- group_token_vec / group_token_vec_mt: vectorized token grouping and multi-thread variant
- mega_moe: full A8W8 wave self-loop MoE migration (mega_moe_sim)
- moe_dispatch / moe_combine: dispatch (v2) and combine (v2) operators
- kernel tests + multi-thread tests for all operators

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fb4d109091

ℹ️ 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".

#endif

static uint32_t topkIndex[kTopKEleNum + 2 * 4096];
uint32_t *topkIndexAligned = (uint32_t *)(((uint64_t)topkIndex & ~0xFFFu) + 0x1000);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve upper pointer bits when aligning input

On a 64-bit CPU build, ~0xFFFu is promoted as 0x00000000fffff000, so this expression discards the upper 32 bits of topkIndex and genTopkIndex writes through an invalid low address. The same alignment expression appears in the single- and multi-thread vector tests, so host simulation commonly crashes before exercising any kernel; use a uintptr_t-sized mask or an aligned declaration.

Useful? React with 👍 / 👎.

Comment on lines +639 to +640
gm_fl gmFlag(reinterpret_cast<float*>(wAddr + blk * SPLIT_BLOCK_SIZE + SPLIT_BLOCK_DATA_SIZE));
TSTORE(gmFlag, flagTile);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restrict the dispatch flag store to its 32-byte field

When dispatching any token, tile_fl contains 32 floats (128 bytes), but the store starts at byte 480 of a 512-byte block. It consequently writes 96 bytes into the next token record, corrupting its payload, and the final record writes beyond the window allocation. The flag field is only 32 bytes (8 floats), so the stored tile or write method must be sized accordingly.

Useful? React with 👍 / 👎.

Comment on lines +261 to +263
uint64_t winDataSizeOffsetEp_ = (uint64_t)dataState_ * (totalWinSizeEp / 2UL);
uint64_t winStatusOffset_ = COMBINE_STATE_OFFSET + dataState_ * WIN_STATE_OFFSET;
uint8_t* epWindowGM_ = windowBuf + winDataSizeOffsetEp_;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allocate a full window after selecting the second half

On the first invocation the zeroed status toggles dataState_ to 1, placing epWindowGM_ halfway into windowBuf, while subsequent epOffset addressing still spans all BS * (K + SharedExpertNum) * hAlignWinSize bytes. The included combine test allocates only exactly that many bytes, so dispatching its final half of tokens writes past windowBuf; either each state half must have the full calculated size or the buffer must be doubled.

Useful? React with 👍 / 👎.

Comment on lines +786 to +788
if (hasExpertScalesFlag_) {
float scaleVal = expertScalesGM_[expertScaleBeginIdx_ * axisK_];
TMULS(accTile, accTile, scaleVal);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Apply each expert scale before accumulating

For inputs where expert scales differ by slot or token, this multiplies the already-summed expert outputs by the first processed token's first scale. The required result is sum(scale[token, expert] * value[token, expert]); a single post-accumulation multiply is only equivalent for the all-0.25 test fixture and silently produces incorrect MoE outputs for normal nonuniform routing weights.

Useful? React with 👍 / 👎.

Comment on lines +81 to +83
int32_t ip = (int32_t)p;
for (int32_t i = 0; i < ip; ++i) r *= 2.0;
return r;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle negative exponents in the MegaMoE golden decoder

The generated E4M3 weights deliberately use exponents 4 through 8, so ref_fp8_e4m3 calls this helper with negative values for exponents 4–6. Casting them to a negative ip makes the loop execute zero times and returns 1 instead of 2^ip, while the kernel decoder correctly multiplies by 0.5 repeatedly; therefore the newly added golden comparison disagrees with the kernel for most generated weights and cannot validate a correct result.

Useful? React with 👍 / 👎.

Comment on lines +727 to +729
float* combine = reinterpret_cast<float*>(g_mmWorkspace + combineOffset);
for (uint32_t i = 0; i < tilingData.bs * tilingData.h; ++i) {
combine[i] = 0.0f;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Synchronize MegaMoE initialization and compute phases

In the documented four-thread execution, every PE clears the entire shared combine buffer and then immediately starts computing its own token partition without a barrier. A slower PE can therefore zero entries another PE has already produced; the later full-buffer unpermute and shared token-count reset/increment are also performed concurrently. Partition initialization/output work to one PE or add cross-PE barriers so the default multi-thread run is deterministic.

Useful? React with 👍 / 👎.

Comment on lines +181 to +186
int ret = 0;
if (cntMatch != (int)kExpertNum) ret = 1;
else if (secMatch != (int)kExpertPerRank) ret = 2;
else if (idMatch != idTotal) ret = 3;
else if (boundMatch != (int)(kExpertPerRank + 1)) ret = 4;
else if (sortMatch != (int)kBS) ret = 5;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate the vector kernel's pod-info output

This test initializes and passes tokenSuperPodInfo, but its reference construction and final return checks cover only counts, grouped IDs, boundaries, and sorting. Consequently the vector kernel can return entirely incorrect destination-pod metadata while this validation still reports PASS; compute the expected pod bits as the old-variant test does and include them in ret.

Useful? React with 👍 / 👎.

Comment on lines +6 to +8

make TESTCASE=mega_moe_sim BS=256 H=128 HIDDEN=256 diss
make TESTCASE=mega_moe_sim BS=64 H=32 HIDDEN=64 diss No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Register the new kernels with the full compilation driver

The repository's benchmark/one-level-arch/compile_all.sh uses an explicit operator list and none of the newly added group-token, dispatch, combine, or MegaMoE directories are present in it. Thus the standard full-compilation workflow never invokes these new compile.all scripts, allowing their build and integration failures to go unnoticed; add the new operator paths to that driver.

Useful? React with 👍 / 👎.

Comment on lines +367 to +377
gm_fl gmFlag(reinterpret_cast<float*>(rankGM + SPLIT_BLOCK_DATA_SIZE - 24 * 4));
TSTORE(gmFlag, flagTile);

// 2) Load expandX data and store to window data area
for (int t = 0; t < HTiles; t++) {
auto gin = ex_iter(tkIndex, t);
tile_h dataTile;
TLOAD(dataTile, gin);
// Store to window (data at block start, skip flag area)
gm_w gmDst(reinterpret_cast<ExpandXType*>(rankGM) + t * TileW);
TSTORE(gmDst, dataTile);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve payload bytes when packing combine flags

For H * sizeof(ExpandXType) > 384 (for example H=256 with __half), the payload tiles are stored contiguously through byte 511, then this 128-byte flag tile is written over bytes 384–511 of the same record. The code also does not place data around each 32-byte gap when blockCntPerToken_ > 1, so common larger hidden dimensions are read back with their tail replaced by flag values; pack the payload in 480-byte chunks and write only each block's 32-byte flag.

Useful? React with 👍 / 👎.

Comment on lines +55 to +59
float a = static_cast<float>(output[i]);
float b = static_cast<float>(refOutput[i]);
float diff = a - b;
if (diff < 0.0f) diff = -diff;
if (diff > 0.01f) { ret = 1; break; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject non-finite values in numeric comparisons

If either the kernel output or reference is NaN, both diff < 0.0f and diff > 0.01f are false, so this validation reports PASS for that element. The MegaMoE error loops use the same comparison pattern and likewise leave their maxima unchanged for NaNs; explicitly check std::isfinite before applying tolerances so numerical failures cannot pass validation.

Useful? React with 👍 / 👎.

…LinxISA/SuperScalarModel#348)

gfrun R2=1 root cause is in the PTO port of the operator (not the
functional model), three defects fixed in moe_dispatch_v2.hpp:

1. SplitToCore uint32 underflow: with single-thread aivId_=0 <
   moeUsedAivNum_, newAivId = 0 - moeUsedAivNum_ = 0xFFFFFFFF ->
   startStatusIndex_=0xFFFFFFFC > endStatusIndex_ -> GetCumSum loop
   zero iterations -> sendCountsOut never written (stale per-expert
   counts / zeros instead of prefix sum [8,16,24,32]). Clamp to 0.

2. TSTORE flag-area overrun: 32B flag area per 512B block is smaller
   than the minimum 128B TSTORE tile; tile_fl stores wrote
   [blk*512+480, blk*512+608), clobbering the next block's data area
   (triple [512,524) / scale [524,528)). Replace with scalar writes
   strictly inside the 32B area (TokenToExpert + ClearLocalWindowDataFlags).

3. Missing block packing: original Ascend C packs the token record
   (x + triple + scale) into 480B data areas of 512B blocks via
   Copy stride {1,1,16,15}. The PTO port wrote the record contiguously,
   so x ([0,512)) overlapped block 0's flag area [480,512) and the flag
   write corrupted the last 8 floats of x. Add in-place repack
   (TokenToExpert, reverse block order) and unpack (CopyInAndOut,
   forward order) matching srcTokenCopyParams {blockCnt, 480, align, 0}.

Verified: compile OK; gfrun R2=0 (sendCountsOut=[8,16,24,32],
expertTokenNumsOut=[8,8,8,8], expandX/Idx/Scales all match ref,
117,027 insts).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 92c2efbe10

ℹ️ 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".

Comment on lines +512 to +515
bool maskValid = (bitIdx < (uint32_t)((slotCount + 31) / 32)) &&
(gatherMaskTensor_[bitIdx] & (1u << bitPos));
if (!maskValid) {
validExpertIds_[i] = -1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Populate the expert-mask bitmap before filtering

When IsExpertMask is enabled and at least one slot is active, gatherMaskTensor_ is still entirely zero because the preceding mask loop only counts active entries. This test consequently marks every validExpertIds_ entry as -1, so no active expert is dispatched and the reported counts and expanded outputs are empty; set the corresponding bitmap bits from xActiveMask before applying this selection.

Useful? React with 👍 / 👎.

// --- SendToMoeExpert (A:1092-1197) ---
// Unified token-iteration path (A5 style for single-thread)
auto SendToMoeExpert = [&]() {
uint32_t validTokenNum = isTokenMaskFlag_ ? (uint32_t)(activeMaskBsCnt_ * axisK_) : (uint32_t)expertIdsCnt_;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Filter token-mask entries instead of truncating the input

When IsTokenMask is enabled for a partially active batch, this treats the first activeMaskBsCnt_ * K slots as valid rather than selecting slots belonging to tokens whose mask is true. An inactive token before an active one is therefore dispatched while the later active token is omitted, and the subsequent full-array count also includes untouched inactive slots; iterate the original token range with the mask or compact both token indices and expert IDs.

Useful? React with 👍 / 👎.

Comment on lines +232 to +235
if constexpr (HasAddRmsNorm) {
armAvgFactor_ = 0.0f; // from tiling
epsilon_ = 0.0f; // from tiling
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Initialize RMSNorm with a nonzero averaging factor

Whenever HasAddRmsNorm is instantiated as true, these hard-coded zeros make every squared term multiplied by armAvgFactor_ vanish and leave denom equal to zero. The later reciprocal square root is therefore infinite, producing infinite or NaN yOut and rstdOut for every token; obtain the configured epsilon and use the RMS factor (normally 1/H) instead of zero.

Useful? React with 👍 / 👎.

Comment on lines +68 to +69
for (uint32_t t = 0; t < kThreadsPerBlock; t++) {
sum += cntLocal[t * expertNum + globalExpert];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Synchronize the old four-PE reductions

In the checked four-PE multi_thread/group_token_old driver, a PE begins summing every private histogram immediately after completing only its own histogram, so it can read another PE's cntLocal before that PE has initialized or finished it. The later per-PE scatter merge and PE-0 counting sort have the same producer/consumer ordering problem, making the advertised multi-thread result race-dependent; add cross-PE barriers before each reduction or merge phase.

Useful? React with 👍 / 👎.

Comment on lines +258 to +262
int32_t elasticInfoTensor_[64] = {0};
if (hasElasticInfoFlag_) {
uint32_t elasticInfoSize = (ELASTIC_INFO_OFFSET + RANK_LIST_NUM * epWorldSizeOriginal_) * sizeof(int32_t);
for (uint32_t i = 0; i < elasticInfoSize / sizeof(int32_t); i++) {
elasticInfoTensor_[i] = elasticInfo[i];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Size the elastic-info buffer for the configured world

When HasElasticInfo is enabled with EpWorldSize >= 31, elasticInfoSize exceeds this fixed 64-element array (4 + 2 * EpWorldSize entries), so the following copy writes past the stack buffer before any elastic routing is performed. Allocate the local storage from the compile-time world size or reject unsupported world sizes before copying.

Useful? React with 👍 / 👎.

Comment on lines +272 to +274
volatile uint32_t* finisher = reinterpret_cast<volatile uint32_t*>(0x10009000ULL);
if (tokOk && maxAbsErr < 1e-2 && maxRelErr < 1e-2) {
*finisher = 0x5555;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Guard the simulator finisher on CPU builds

When the MegaMoE target is built through its documented PLAT=cpu path and run with the common sim target, this unconditional write dereferences the simulator-only MMIO address 0x10009000 in a normal host process. Both passing and failing validations therefore terminate with a segmentation fault instead of returning their diagnostic code; restrict finisher writes to the Linx/gfsim build.

Useful? React with 👍 / 👎.

Comment on lines +625 to +626
if constexpr (QuantMode > UNQUANT) {
// quantInst_.QuantProcess — compiled out under UNQUANT

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject or implement dispatch quantization modes

Whenever QuantMode is instantiated as STATIC_QUANT, a dynamic quantization mode, or MX quantization, this selected branch performs no payload write at all. The function still fills the record metadata and arrival flags afterward, so the receive path treats the record as ready and copies stale window bytes into expandXOut; either implement the advertised quantization path or reject non-UNQUANT instantiations.

Useful? React with 👍 / 👎.

Comment on lines +361 to +363
if constexpr (QuantMode > UNQUANT) {
// Quantized pack path — compiled out under UNQUANT
} else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject or implement combine quantization modes

Whenever QuantMode is greater than UNQUANT, this dispatch branch writes neither the quantized payload nor its arrival flag, and the corresponding dequantization branch later in the function is also empty. The combine loop therefore skips tokens whose flags remain unset or accumulates no expert data, leaving the result missing; implement both pack and dequantization handling or make unsupported modes fail at compile time.

Useful? React with 👍 / 👎.

The previous fix (92c2efb) repacked window blocks with scalar loops while
the token copy still used TLOAD/TSTORE tiles, so a 512B window block was
touched by both a tile access and scalar accesses in the same program
region. That violates A3 (spec 1.4b:252-258): a block holds at most one
tile access and a tile access never shares a block with a scalar one.
gfsim reports 3x a3_tile_violation (tile_access_shares_block) and the
TLSU pipeline deadlocks at cycle 18010 (TLSU_AS.md:265 — software that
mixes the two on one address without a barrier owns the consequence).

Replace the tile copies with scalar loops in both directions:
- TokenToExpert: token data GM -> window (was TLOAD+TSTORE)
- CopyInAndOut: window -> expandXOut (was TLOAD+TSTORE)

All window-slot traffic is now scalar (data copy, triple, scale, block
pack/unpack, flags), so every window block only ever sees scalar
accesses. gfsim a3_tile_violation count drops 3 -> 0 and the deadlock
point moves forward from blocks=3330 to blocks=12487 (cycle 40186,
thread 0 B199 STD flushed) — that residual stall is a separate
TimingSim flush/replay issue tracked on the model side.

Verified: compile OK; gfrun R2=0 (164,205 insts); gfsim violation=0.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0471150243

ℹ️ 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".

Comment on lines +195 to +196
bool isInputTokenMaskFlag_ = false;
bool isInputExpertMaskFlag_ = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Honor the combine active-mask input

When a caller supplies xActiveMask for a partially active batch, both mask-mode flags remain hard-coded to false, so the mask-counting and index-gathering branches never run and the combine path processes all BS tokens and expert slots. This silently writes outputs for masked tokens and includes masked expert contributions; expose the intended token/expert mask mode and initialize these flags from it instead of disabling both modes.

Useful? React with 👍 / 👎.

uint32_t moeExpertPerRankNum_ = MoeExpertNum;
bool isInputTokenMaskFlag_ = false;
bool isInputExpertMaskFlag_ = false;
bool hasSharedExpertX_ = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the supplied shared-expert residual

Whenever sharedExpertX is non-null, this hard-coded false flag prevents the later AddSharedExpertX branch from executing, so every output omits the advertised [BS, H] shared-expert contribution. Initialize the feature flag from the corresponding configuration or pointer so supplying this input changes the result.

Useful? React with 👍 / 👎.

Comment on lines +231 to +233
uint32_t sharedExpertNum_ = SharedExpertNum;
uint32_t sharedExpertRankNum_ = 0;
uint32_t moeExpertRankNum_ = epWorldSize_ - sharedExpertRankNum_;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Configure shared-expert ranks before dispatching

For a non-elastic instantiation with SharedExpertNum > 0, sharedExpertRankNum_ remains zero even though this is the only value used to divide shared experts among ranks. Consequently the execution condition at the dispatch call site never selects SendToSharedExpert, leaving every shared-expert window slot and arrival flag unwritten; initialize the rank count from configuration or reject this otherwise-advertised instantiation.

Useful? React with 👍 / 👎.

uint32_t aivNum_ = 1;
uint32_t ubSize_ = 192U * 1024U;
uint32_t globalBS_ = BS;
bool hasElasticInfoFlag_ = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Apply elastic routing information during combine

When elasticInfo describes a scale-down/remapped EP topology, hasElasticInfoFlag_ is still fixed to false and elasticInfoGM_ is never read, leaving isScalingDownFlag_, the world size, and rank mappings at their compile-time values. The dispatch side can apply this remap, so combine then reads the pre-remap window slots and can omit or misattribute expert results; derive the elastic flag and topology from the supplied information.

Useful? React with 👍 / 👎.

Comment on lines +294 to +296
uint32_t scaleInBytes_ = 0;
uint32_t scaleOutBytes_ = 0;
uint32_t scalesCount_ = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve smooth-scale metadata in unquantized dispatch

When instantiated with QuantMode == UNQUANT and IsSmoothScaleExist == true, all scale sizes and counts remain zero, while the supplied scales input and dynamicScalesOut output are never accessed anywhere in the function. The record therefore contains only the token payload and ordinary expert scale, silently dropping the requested smooth-scale metadata; compute the scale layout and copy the scale values or reject this mode.

Useful? React with 👍 / 👎.

Comment on lines +906 to +914
// performanceTimeStart = GetSystemCycle() → 0
uint64_t performanceTimeStart = 0;

// RecordRankCommDuration called inside polling loop (A:1863-1864)
// PTO: data already arrived → single iteration
if (isPerformanceFlag_ && performanceInfo != nullptr) {
int32_t* perfInfoTensor = performanceInfo;
int32_t perfFlags[MoeExpertNum * 4 > 0 ? MoeExpertNum * 4 : 1] = {0};
int32_t duration = 0; // (0 - 0) / CYCLES_PER_US = 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Record real dispatch performance durations

Whenever IsPerformance is enabled, both the start cycle and computed duration are fixed to zero, so performanceInfo can never receive a positive communication duration regardless of how long dispatch takes. This makes the advertised performance output unusable for profiling experiments; use an available cycle source or reject performance mode rather than returning fabricated zero timings.

Useful? React with 👍 / 👎.

Adjacent i64 writes (e.g. tokRef[0]=0; tokRef[1]=0) get merged by the
backend into a 16B tile store (BLK_TSTORE v2i64 BUILD_VECTOR), which the
linxv5 ISel cannot select: "fatal error: error in backend: Cannot select:
v2i64 = BUILD_VECTOR". Add volatile at the three trigger sites to keep
scalar stores. Verified: BS256_H128_HD256 and BS64_H32_HD64 both build.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 096ce173f1

ℹ️ 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".

Comment on lines +185 to +186
for (uint32_t i = 0; i < kExpertPerRank * kBS * kSuperPodNum; i++) {
if (tokenSuperPodInfo[i] == refPodInfo[i]) podMatch++;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match pod metadata by token rather than scatter position

On the non-__linx/non-CPU hardware path, GroupToken_Vec_Impl assigns section positions using atomic increments, so tokens within a section need not appear in the scalar reference's sequential order. The grouped-ID check correctly treats each section as a set, but this positional comparison still compares pod metadata against the scalar ordering and can report FAIL for a correct hardware result. Validate each pod record together with its corresponding groupedTokenIds token instead.

Useful? React with 👍 / 👎.

const int64_t* expertTokenNumsOut,
uint32_t bs, uint32_t h, uint32_t k, uint32_t moeExpertNum) {
const uint32_t slotCnt = bs * k;
int32_t counts[1024] = {0};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Size dispatch reference counts from the expert count

When refDispatchCheck is used with moeExpertNum > 1024, which the templated dispatch kernel does not otherwise reject, the expert-ID counting loop writes beyond this fixed stack array; the subsequent cum[1024] has the same limitation. Larger-expert validation can therefore corrupt the stack or produce arbitrary results instead of checking the kernel, so allocate these buffers from moeExpertNum or enforce the limit explicitly.

Useful? React with 👍 / 👎.

…arModel#371 BS64 R2=9)

Kernel side (per maintainer protocol in LinxISA/SuperScalarModel#371):
- Wire kernel formal params (yOut/xIn/tokOut) — previously unused globals,
  ABI path for pointer-arg GM writeback now actually covered
- Decouple 16 pseudo-core loop from thread count: every PE covers all
  cores idempotently (old tid*4+lc sharding under-covered when threads!=4)
- Combine: direct per-token assignment instead of shared-region clear+"+=";
  removes cross-PE RMW races on the shared combine workspace (topK==1)
- Stats export: per-slot register count + single idempotent store; removes
  clear+accumulate executed N times by N PEs (token counts were xN)

Golden/host side:
- exp2_approx: add missing negative-exponent branch (e4m3 exp field e<7
  gave p<0; golden returned 1.0, mismatching kernel — 1t R2 failed too)
- Golden tokRef counting and yRef combine made idempotent (multi-PE shared
  stack/GM: array-slot RMW "+=" executed by all 4 PEs -> x4; probe showed
  scalar loop=64 vs array-slot=256 at 4t)
- BS16 build: volatile writes for topkIds/tilingData i64 zero fields —
  backend merges adjacent scalar zero stores into 16B tile store
  (BLK_TSTORE v2i64 BUILD_VECTOR, Cannot select); llvm-reduce minimal IR

Verified with gfrun (SuperScalarModel main incl. PR #380):
- BS64_H32_HD64:  R2=0 @ threads 1 and 4 (was R2=9 @ 4t)
- BS16_H128_HD256: R2=0 @ threads 1 and 4 (was fail-closed non-exec fetch)
- BS256_H128_HD256: regression in progress

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c1465cd904

ℹ️ 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".

Comment on lines +182 to +184
else if (idMatch != idTotal) ret = 3;
else if (boundMatch != (int)(kExpertPerRank + 1)) ret = 4;
else if (sortMatch != (int)kBS) ret = 5;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate pod metadata in the multi-thread vector test

The checked multi-thread test produces both perPePodInfo and tokenSuperPodInfo, but its reference data and return conditions validate only counts, grouped IDs, section boundaries, and sorting. Any regression in the per-PE pod merge can therefore return PASS while emitting incorrect destination-pod metadata; construct the expected pod bits for each grouped token and include that comparison in ret.

Useful? React with 👍 / 👎.

Comment on lines +210 to +213
MM_INLINE inline float fp8_e8m0_scale(uint8_t raw)
{
const int32_t e = static_cast<int32_t>(static_cast<int8_t>(raw));
float s = 1.0f;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Apply the E8M0 exponent bias when decoding scales

For standards-compliant E8M0 scale tensors, the byte is an unsigned exponent with bias 127, but casting it to int8_t interprets it as a signed unbiased exponent. For example, the encoding 0x7f for scale 1 becomes 2^127, while 0x80 for scale 2 becomes 2^-128, so normal MegaMoE weights are decoded with catastrophic multipliers. The all-0x00 fixture and its reference decoder repeat this nonstandard interpretation and therefore mask the defect; decode using raw - 127 and handle the reserved encoding separately.

Useful? React with 👍 / 👎.

Comment on lines +38 to +40
expandIdx[i * 3] = 0;
expandIdx[i * 3 + 1] = i / kK;
expandIdx[i * 3 + 2] = i % kK;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exercise non-identity unpermute mappings

This fixture assigns every expandIdx record its current token-major position, so the test never exercises the combine operator's unpermute behavior. Production dispatch output is expert-grouped and relies on these triplets to map each incoming row back to its original token and top-k slot; an implementation that ignores or misindexes expandIdx can therefore pass this validation. Populate a non-identity permutation and reorder expandX consistently before comparing with the token-major reference.

Useful? React with 👍 / 👎.

@Cell-Cell

Copy link
Copy Markdown
Author

更新:mega_moe 用例侧多 PE 修复(096ce17c1465cd,对应 LinxISA/SuperScalarModel#371)

kernel(mega_moe_sim.hpp):

  • 接入 kernel 形参 yOut/xIn/tokOut(原直接访问全局,未覆盖指针形参写 GM 的 ABI 路径)
  • 16 伪核循环与线程数解耦:每线程幂等覆盖全部伪核(原 tid*4+lc 分片在线程数 ≠ 4 时覆盖不足)
  • Combine 改按 token 直接赋值(topK==1 等价),消除共享 combine 区清零+累加跨 PE 竞态
  • 统计导出改为逐槽寄存器计数 + 一次性写回(原清零+累加被执行 N 次 → ×N 计数)
  • tilingData 64 位零字段 volatile 写:规避后端把相邻标量零 store 合并为 16B tile store(v2i64 BUILD_VECTOR Cannot select 崩溃,BS16 编译失败)

golden/host(mega_moe_sim.cpp):

  • exp2_approx 补负指数分支(e4m3 指数域 e<7 时 p<0,原恒返回 1.0,单线程也不一致)
  • tokRef/yRef 幂等写:gfrun 多 PE 共享栈/GM,数组槽 RMW 会被 N 个 PE 重复执行(探针实测 4t 标量累加=64 正常、数组槽=256 ×4)
  • g_mmTopkIds volatile 写(同为 v2i64 合并规避)

验证(gfrun = SuperScalarModel main 含 PR #380):BS16_H128_HD256 / BS64_H32_HD64 / BS256_H128_HD256 × threads 1/4 共 6 项全部 R2=0(BS64 原 R2=9、BS16 原编译崩溃/挂死)。

…omparison

- moe_combine_full.hpp: full combine with 4/5 A5 infrastructures
  (512B block packing, pipeline sync via TMOV, batch flag check via
  TCMP, window state management), BS=8 H=128 K=4 aligned with A5
- reduce_fused_aligned.cpp: pure compute variant, BS=8 H=128 K=4
- reduce_fused_full.cpp: full comm+compute variant with pack/poll/clear

gfrun PASS, gfsim: 5022 cycles (vs A5 cannsim 31506 cycles)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2395edf920

ℹ️ 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".

Comment on lines +48 to +49
auto gf = flag_iter(slot, t);
TSTORE(gf, flagTile);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allocate flags for every hidden tile

When H > TileW, t reaches 1 or more, but gm_flag and gm_pred are declared with only one TileW-wide tile per slot. Consequently flag_iter(slot, t) and the analogous predicate access alias subsequent slots and eventually run past the supplied buffers (for example, H=256, TileW=128); either allocate/index these tensors across H columns or store a single readiness tile without the t offset.

Useful? React with 👍 / 👎.

Comment on lines +101 to +104
for (int k = 0; k < K; k++) {
int slot = n * K + k;
if (predBuf[slot * TileW] < 0.5f) break;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Stop combining when a slot is not ready

When any slot's flag is unset, this break only exits a loop whose result is discarded; execution immediately proceeds to load and accumulate all K window rows. A missing or late expanded slot therefore contributes stale/uninitialized window data despite the readiness check, so the failed predicate must gate or defer the subsequent accumulation.

Useful? React with 👍 / 👎.

void combine_phase(float *expertScales, DTypeIn *windowData, float *windowFlag,
float *predBuf, DTypeOut *out)
{
constexpr int kTiles = H / TileW;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle a partial hidden tile

For any permitted instantiation where H is not divisible by TileW (for example, H=192, TileW=128), integer division drops the final partial tile. Neither packing nor combining touches those hidden elements, leaving the output tail stale; process a remainder tile or add an H % TileW == 0 constraint.

Useful? React with 👍 / 👎.


// 16 伪核全量循环: 与线程数解耦 (单线程/多线程均覆盖全部伪核, 结果幂等;
// 修正原 "tid*4+lc" 分片在线程数 != 4 时覆盖不足导致的 R2 失败)
const uint32_t perCore = tilingData.bs / kBlockAivNum;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Process remainder tokens after the 16-core split

When the configurable batch size is not divisible by kBlockAivNum (for example, BS=17), perCore is truncated and the following 16-core loop processes only 16 * floor(BS / 16) tokens. The remaining tokens are never sent through either GMM, retain stale outputs, and are omitted from expert counts; distribute the remainder or reject unsupported batch sizes explicitly.

Useful? React with 👍 / 👎.

#endif

static uint32_t topkIndex[kTopKEleNum + 2 * 4096];
uint32_t *topkIndexAligned = (uint32_t *)(((uint64_t)topkIndex & ~0xFFFu) + 0x1000);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the upper pointer bits when aligning the input

On a 64-bit host where the static array is mapped above 4 GiB, ~0xFFFu is a 32-bit mask that is zero-extended during the uint64_t bitwise operation, clearing the pointer's upper 32 bits and producing an invalid topkIndexAligned. The documented PLAT=cpu validation can therefore segfault before running the kernel; use a 64-bit mask such as ~uint64_t{0xFFF}. The same expression is repeated in the new group-token vector and multi-thread vector drivers.

Useful? React with 👍 / 👎.

Comment on lines +62 to +64
using gm_win = global_tensor<DTypeIn, RowMajor<NumExpanded, H>>;
using gm_flag = global_tensor<float, RowMajor<NumExpanded, TileW>>;
using gm_pred = global_tensor<float, RowMajor<NumExpanded, TileW>>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require the window to contain all BS-by-K slots

When NumExpanded < BS * K, which the template currently permits, these window, flag, and predicate tensors have only NumExpanded rows while the loop below unconditionally addresses slots 0 through BS * K - 1. Masked or compacted expansions therefore make the combine phase read and write beyond all three buffers; either size the window tensors by BS * K or enforce NumExpanded == BS * K.

Useful? React with 👍 / 👎.

Comment on lines +660 to +663
const uint32_t base = (round * kBlockAivNum + coreIdx) * kChunkElems;
tileShape t;
auto srcGT = xIter(0, base);
TLOAD(t, srcGT);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pass a tile index to the MegaMoE fake-path iterator

When MEGA_MOE_SIM_FAKE is enabled, base is an element offset, but the second argument to global_iterator::operator() is a tile coordinate. After the first chunk, passing values such as 256 advances by 256 whole 256-element tiles rather than by 256 elements, so the fake path reads and writes far beyond xIn and yOut; use base / kChunkElems with a tensor covering the full input or offset the pointer and select tile zero.

Useful? React with 👍 / 👎.

Comment on lines +490 to +491
uint32_t curDstPod = topkIndex[j] >> 6;
dstPodLocal[curDstPod] = 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bounds-check the scalar destination-pod index

On the __linx and CPU scalar path, any out-of-range or sentinel expert ID whose value is at least kExpertPerPod * kSuperPodNum produces curDstPod >= kSuperPodNum and writes past the two-element dstPodLocal array. The SIMT implementation and the scalar reference already guard this index, so the scalar fallback should apply the same curDstPod < superPodNum check rather than corrupting its stack.

Useful? React with 👍 / 👎.

- moe_dispatch_v2_aligned.hpp: full dispatch with 7/12 mechanisms aligned
  (512B stride, TMOV sync, check_flag, check_cumsum_flag, clear_flag)
  bf16 + TileW=128, windowData 512B stride matching A5 hCommuSize_
- moe_dispatch_v2_aligned.cpp: test main, BS=8 H=128 K=4 bf16
- moe_dispatch_v2.hpp: revert experimental tile window access in original
  (keep scalar A3-compliant version as-is)

gfrun PASS (R2=0), gfsim: 11968 cycles (vs A5 cannsim 31177 cycles)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc3f5cd519

ℹ️ 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".

Comment on lines +786 to +788
if (hasExpertScalesFlag_) {
float scaleVal = expertScalesGM_[expertScaleBeginIdx_ * axisK_];
TMULS(accTile, accTile, scaleVal);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Apply each slot's scale before accumulating

When top-k routing weights differ, this multiplies the completed sum by expertScales[beginIndex * K], producing scale[0] * sum(x_j) instead of sum(scale[j] * x_j) for every token on the single core. The earlier per-slot scaleVal is never applied, and the fixture's uniform 0.25f scales conceal the error; multiply each expert contribution by its corresponding scale before adding it.

Useful? React with 👍 / 👎.

Comment on lines +242 to +244
using gm_st = global_tensor<float, RowMajor<1, TileW>>;
auto gs = reinterpret_cast<gm_st*>(windowState + 4);
TSTORE(*gs, cumsumFlag);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the cumsum flag inside the state allocation

With the default TileW=128, this stores 128 floats (512 bytes) beginning at windowState + 4, but the supplied aligned-dispatch driver allocates only uint32_t windowState[16] (64 bytes total). A tightly allocated caller therefore receives a 464-byte out-of-bounds write during every dispatch; use separate tile-sized flag storage or enlarge and document the state buffer.

Useful? React with 👍 / 👎.

Comment on lines +84 to +87
uint32_t sum = 0;
for (uint32_t t = 0; t < kThreadsPerBlock; t++) {
sum += cntLocal[t * expertNum + globalExpert];
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Synchronize vector PEs before shared reductions

In the checked four-PE multi_thread/group_token_vec execution, a PE starts reading all four cntLocal histograms as soon as its own histogram is ready, with no cross-PE barrier. It can therefore reduce uninitialized or incomplete peer data; the subsequent merge similarly consumes peer section buffers early, and PE 0 can sort before peers finish minLocalExpIds. Add barriers at each producer/consumer boundary and make the merge single-owner.

Useful? React with 👍 / 👎.

Comment on lines +695 to +697
const uint32_t scaleStride = (tilingData.h / 32U) * 2U * 2U * 2U;
w1[flat] = fp8_e4m3_to_f32(g_mmWeight1[flat]) *
fp8_e8m0_scale(g_mmWeightScales1[scaleIdx % scaleStride]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve per-expert MegaMoE scale indices

For the default H=128 and hiddenDim=256, each expert has 1024 weight-scale groups, but scaleStride is only 32 and this modulo maps every expert's first group back to index 0 while repeating just the first 32 entries across all weights. Thus distinct scales for expert 1 or later groups are ignored in both GMMs; the all-zero scale fixture and matching reference repeat the same addressing and mask the incorrect A8W8 result.

Useful? React with 👍 / 👎.

Comment on lines +260 to +263
// ---------- Init: window half-region selection ----------
uint64_t winDataSizeOffsetEp_ = (uint64_t)dataState_ * (totalWinSizeEp / 2UL);
uint64_t winStatusOffset_ = COMBINE_STATE_OFFSET + dataState_ * WIN_STATE_OFFSET;
uint8_t* epWindowGM_ = windowBuf + winDataSizeOffsetEp_;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allocate both combine window halves

On the first invocation the zero-initialized state selects dataState == 1, so this advances epWindowGM_ by half of totalWinSizeEp and then indexes a full BS * (K + SharedExpertNum) set of records from that point. The supplied combine driver allocates exactly totalWinSizeEp bytes, so its final half of record writes extends another half-buffer beyond windowBuf and into subsequent storage; either allocate two full record regions and offset by one full region, or treat the existing allocation as a single region without this half offset.

Useful? React with 👍 / 👎.

…omparison

- mega_moe_full_sim.cpp: self-contained full GMM1→SwiGLU→GMM2→Combine
  pipeline using Cube TMATMUL (16 Cube tileops) + Vector tile ops.
  No mega_moe_sim.hpp include (avoids v2i64 BUILD_VECTOR crash).
  BS=8, H=128, HiddenDim=256, float, TileW=128.
- mega_moe_gmm.hpp/cpp: independent translation unit for GMM tile
  functions (split to avoid compiler backend crash)
- Makefile: add SIM_FAKE mode and mega_moe_full_sim target
- mega_moe_sim.cpp: add SIM_FAKE static_assert relaxation for BS<16

gfrun PASS (R2=0), gfsim: 17516 cycles (Cube+Vector full pipeline)
vs A5 cannsim: 18930 cycles (Vector-only, cannsim cannot simulate Cube)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4935b063f3

ℹ️ 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".

// --- SendToMoeExpert (A:1092-1197) ---
// Unified token-iteration path (A5 style for single-thread)
auto SendToMoeExpert = [&]() {
uint32_t validTokenNum = isTokenMaskFlag_ ? (uint32_t)(activeMaskBsCnt_ * axisK_) : (uint32_t)expertIdsCnt_;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve sparse token-mask indices during dispatch

When IsTokenMask is enabled and inactive tokens are not a suffix, this truncates processing to the first activeMaskBsCnt_ * K slots rather than gathering the slots whose mask is true. For example, a mask [false, true, ...] dispatches token 0 and omits token 1; the later expert-count loop still counts all validExpertIds_, so it can also advertise records that were never written. Build and use an active-token index list instead of treating the active count as a prefix length.

Useful? React with 👍 / 👎.

Comment on lines +448 to +450
sendToMoeExpTokenCnt_ = 0;
for (int32_t i = 0; i < expertIdsCnt_; i++) {
if (xActiveMask[i]) sendToMoeExpTokenCnt_++;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Populate the expert-mask selection bitmap

Whenever IsExpertMask is true and at least one slot is active, this loop only increments sendToMoeExpTokenCnt_ and never sets the corresponding bits in gatherMaskTensor_. The selection at lines 503-515 therefore converts every validExpertIds_ entry to -1, producing zero expert counts and returning without any expanded output. Set a bitmap bit for each true mask entry (or compact the valid indices directly).

Useful? React with 👍 / 👎.

Comment on lines +376 to +377
gm_w gmDst(reinterpret_cast<ExpandXType*>(rankGM) + t * TileW);
TSTORE(gmDst, dataTile);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pack combine records across every 512-byte block

When H * sizeof(ExpandXType) > 480 (for example, ExpandXType=float, H=128), these stores lay the payload contiguously across the first block instead of skipping its 32-byte flag area and initializing subsequent block flags. The payload then overwrites the readiness values at offsets 480-511, so the check at lines 523-529 treats every affected token as unready and leaves its output untouched. Apply the same 480-byte-data/32-byte-flag packing for every block that hAlignWinSize_ accounts for.

Useful? React with 👍 / 👎.

Comment on lines +315 to +316
uint32_t axisMaxBS_ = (epWorldSizeOriginal_ > 0) ? (globalBS_ / epWorldSizeOriginal_) : globalBS_;
uint64_t expertPerSizeOnWin_ = (uint64_t)axisMaxBS_ * hCommuSize_;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reserve a full local batch per expert window partition

Whenever EpWorldSize > 1, each source rank still processes all BS input rows, but this limits every source/expert window partition to floor(BS / EpWorldSize) records. If more than that many local tokens route to one expert, dstTokenIdx advances into the next expert partition; routing any records to that next expert then overwrites the excess records, and the formatted expansion contains duplicated or missing tokens. Size each partition for the maximum locally dispatched count (at least BS for distinct top-k routes), or make BS represent and enforce the per-rank batch consistently.

Useful? React with 👍 / 👎.

DType* windowData, float* windowFlag)
{
constexpr int slotCount = BS * K;
constexpr int hTiles = H / TileW;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle remainder columns in aligned dispatch

For any permitted instantiation where H is not divisible by TileW, integer division drops the final partial hidden tile. Both dispatch_pack here and dispatch_copy_out at line 169 use the truncated tile count, so the tail is never copied into the window or expandXOut and retains stale data. Process a masked remainder tile or add an H % TileW == 0 compile-time constraint.

Useful? React with 👍 / 👎.

Comment on lines +234 to +235
uint32_t moeExpertNumPerRank_ = (moeExpertRankNum_ > 0) ? (moeExpertNum_ / moeExpertRankNum_) : moeExpertNum_;
if (moeExpertNumPerRank_ == 0) moeExpertNumPerRank_ = moeExpertNum_;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require an even expert partition across EP ranks

When MoeExpertNum is not divisible by EpWorldSize (for example, five experts over two ranks), this floor division assigns only four experts to the rank partitions. The remaining expert computes toRankId == EpWorldSize, while rscvStatusNum_ covers only the four partitioned experts; its status aliases another entry and sendCountsOut/expertTokenNumsOut never report it correctly. Either distribute the remainder experts explicitly or reject non-divisible configurations.

Useful? React with 👍 / 👎.

using namespace pto;

using gm_x = global_tensor<DType, RowMajor<BS, H>>;
using gm_w = global_tensor<DType, RowMajor<slotCount, WindowStride>>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep aligned window rows large enough for the hidden width

Whenever H > WindowStride, the tile stores for one slot continue past that slot's RowMajor<slotCount, WindowStride> row and overwrite the following slot's window data. The copy-out path uses the same undersized row shape, so overlapping records produce corrupted expanded tokens even when H is tile-aligned. Enforce WindowStride >= H or derive the stride from the hidden width and required padding.

Useful? React with 👍 / 👎.

…e_dispatch_v2

- Delete original moe_dispatch_v2.hpp (1287 lines, scalar window access)
- Delete original moe_dispatch_v2.cpp
- Rename moe_dispatch_v2_aligned.hpp → moe_dispatch_v2.hpp
- Rename moe_dispatch_v2_aligned.cpp → moe_dispatch_v2.cpp
- Update include guard, function name, compile.all
- Remove test files (test_pack_copy.cpp, test_tile_copy.cpp)

Only the aligned version (tile window access, TMOV sync, check_flag,
512B stride) is retained for A5 performance comparison.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1b6663533e

ℹ️ 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".

Comment on lines +232 to +234
if constexpr (HasAddRmsNorm) {
armAvgFactor_ = 0.0f; // from tiling
epsilon_ = 0.0f; // from tiling

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Initialize RMSNorm scaling before normalization

Whenever HasAddRmsNorm is enabled, these constants remain zero, so the squared-value reduction is multiplied by zero and denom later becomes 0 + 0; 1 / sqrt(denom) therefore produces infinity and contaminates rstdOut and yOut with non-finite values for ordinary nonzero inputs. Initialize the averaging factor to 1/H and obtain a positive epsilon from the operator configuration.

Useful? React with 👍 / 👎.

Comment on lines +68 to +69
for (uint32_t t = 0; t < kThreadsPerBlock; t++) {
sum += cntLocal[t * expertNum + globalExpert];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Synchronize PEs before reading peer histograms

In the documented four-PE multi_thread/group_token_old execution, each PE starts summing all four cntLocal rows immediately after finishing only its own row, with no cross-PE barrier. A faster PE can therefore read peer counters while they are still zero or partially populated and publish incorrect tokenPerExpertCnt values, making the experiment nondeterministically fail or report bad grouping statistics; synchronize after the local counting loop before this reduction.

Useful? React with 👍 / 👎.

Comment on lines +195 to +196
bool isInputTokenMaskFlag_ = false;
bool isInputExpertMaskFlag_ = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Honor the supplied active mask during combine

Whenever a caller supplies xActiveMask, both mask-mode flags remain hard-coded to false, so neither the one-dimensional token-mask count nor the two-dimensional expert-mask filtering below can execute. The combine consequently processes every token and top-k slot, allowing inactive expert contributions into the output; initialize the appropriate mask mode from the operator configuration instead of unconditionally disabling both paths.

Useful? React with 👍 / 👎.

Comment on lines +361 to +363
if constexpr (QuantMode > UNQUANT) {
// Quantized pack path — compiled out under UNQUANT
} else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Implement or reject quantized combine modes

For every supported-looking instantiation with QuantMode > UNQUANT, this branch writes neither the token payload nor its readiness flag into the window. The later arrival check therefore skips the token and leaves its output stale; even with a preexisting flag, the matching dequantization branch is also empty and contributes zero. Implement the quantized pack/dequantize path or reject these modes at compile time.

Useful? React with 👍 / 👎.

const float* w1Row = w1fp32 + e * h * 256 + nt * kVecW;
gmFlat gW(const_cast<float*>(w1Row));
TLOAD(b, gW);
TMUL(acc, a, b);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reduce across K in the vector GMM helper

When gmm_pipeline_for_token is called with its documented h=128 configuration, kTiles is one and this elementwise multiply is stored directly as the GMM1 output. Thus output lane n receives one product such as x[n] * w1[0][n] rather than sum_k(x[k] * w1[k][n]); the analogous GMM2 helper repeats the same pattern, so the exported pipeline cannot produce a matrix-multiplication result even at its default dimensions. Use a matrix operation or perform the required reduction over K.

Useful? React with 👍 / 👎.

Comment on lines +57 to +59
float diff = a - b;
if (diff < 0.0f) diff = -diff;
if (diff > 0.01f) { ret = 1; break; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject non-finite combine outputs in the test

If the combine kernel produces NaN, then diff is also NaN and the comparison diff > 0.01f evaluates false, so an output containing non-finite values can still make this validation return success. This is especially relevant to the newly exposed normalization and quantized paths, where invalid arithmetic can generate non-finite results; explicitly fail when either actual or reference output is not finite.

Useful? React with 👍 / 👎.

Comment on lines +74 to +76
std::memcpy(&act_raw, &expandXOut[q * kH + j], 2);
uint16_t diff = exp_raw ^ act_raw;
if (diff != 0 && diff != 1) return 6;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require exact bits for dispatch copies

The dispatch path only copies BF16 payloads and performs no arithmetic conversion, but this condition accepts an XOR difference of one. Consequently a regression that flips the least-significant bit of every copied BF16 element still passes the validation even though expandXOut is not a faithful dispatch of x; require act_raw == exp_raw for this copy test.

Useful? React with 👍 / 👎.

…mbine_v2

- Delete original moe_combine_v2.hpp (892 lines, scalar window access)
- Rename moe_combine_full.hpp → moe_combine_v2.hpp (aligned: tile ops, TMOV sync,
  batch flag check, window state management)
- Update include guard and function name: moe_combine_full → moe_combine_v2
- Rewrite test main (moe_combine_v2.cpp) to include new path
- Delete reduce_fused_aligned.cpp and reduce_fused_full.cpp (test variants)
- Fix mega_moe_full_sim.cpp constant naming conflict with Makefile -D macros

gfrun PASS (R2=0, 977 blocks), gfsim: 5088 cycles (vs original 66942, -92%)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b1b9bd8bb

ℹ️ 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".

Comment on lines +357 to +360
mergeGroupTokenResults(
perPegroupedIds, perPeSectionCnt, perPePodInfo,
groupedTokenIds, tokenSuperPodInfo, expertSectionTokenCnt,
kExpertPerRank, kSuperPodNum);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Synchronize before merging per-PE scatter buffers

In the checked four-PE driver, each PE enters mergeGroupTokenResults immediately after producing only its own perPeSectionCnt, perPegroupedIds, and perPePodInfo, with no cross-PE barrier. A faster PE can therefore merge zero or incomplete peer sections and publish truncated grouping results; the merge should run only after all scatter producers finish, preferably on a single PE.

Useful? React with 👍 / 👎.

TLOAD(y3, gY3);
TMULS(y3, y3, weight);
gmV gY(y + token * kMoeH);
TSTORE(gY, y3);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Compute every output column in the full simulation

With the fixed H=128 configuration, GMM2 computes only one 16-column tile in y3Padded; loading 128 contiguous values from that 16-by-16 buffer then writes flattened matrix rows as the token's 128 hidden values. Only the first 16 values belong to row 0, while columns 16–127 were never computed, so the mega_moe_full_sim target cannot represent the claimed full pipeline despite returning success.

Useful? React with 👍 / 👎.

// gfsim 判读通道: test-finisher (0x10009000, 低 16 位 0x5555 = PASS)
volatile uint32_t* finisher = reinterpret_cast<volatile uint32_t*>(0x10009000ULL);
if (tokOk && maxAbsErr < 1e-2 && maxRelErr < 1e-2) {
*finisher = 0x5555;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid device-MMIO writes during CPU simulation

In the PLAT=cpu path explicitly supported by this test's Makefile, the common sim rule executes this ELF directly as a host process, where 0x10009000 is not a mapped gfsim finisher device. A successful validation therefore reaches this store and segfaults instead of returning zero; guard finisher writes to the simulator/bare-metal environment.

Useful? React with 👍 / 👎.

// ---- 完整参考 golden 对比 ----
double* yRef = g_goldenY;
int64* tokRef = g_goldenTok;
compute_golden(yRef, tokRef);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Compare fake-mode output against the sigmoid reference

Whenever SIM_FAKE=1, the selected kernel path computes sigmoid(x), but the test still calls the full quantized MoE compute_golden routine here. Correct fake-path output will consequently be compared with an unrelated GMM/SwiGLU result and reported as a failure; select a sigmoid reference under MEGA_MOE_SIM_FAKE.

Useful? React with 👍 / 👎.

for (uint32 i = 0; i < kTotalElems; ++i) {
const double err = (double)y[i] - yRef[i];
const double absErr = err < 0 ? -err : err;
if (absErr > maxAbsErr) maxAbsErr = absErr;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject non-finite MegaMoE results

If either the kernel or reference produces NaN, absErr is also NaN and this comparison is false, leaving maxAbsErr at its previous value; the relative-error loop behaves the same way. Thus non-finite outputs can satisfy both final tolerances and report PASS, so explicitly fail on non-finite actual, reference, or error values.

Useful? React with 👍 / 👎.

uint32_t cur = topkIndex[i * topk + j] % expertPerRank;
if (cur < minLocal) minLocal = cur;
}
blkv_get_tile_ptr(dst)[i] = minLocal;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Size the FloorFunc tile for the full batch

On the real-hardware path, the default batchSize is 512, but tile_shape_out is TileU32, whose 16-by-16 storage contains only 256 elements. The loop writes blkv_get_tile_ptr(dst)[i] through index 511, so the second half of the default batch runs beyond the output tile before the counting sort consumes it; use a tile covering all 512 entries or process the batch in multiple tiles.

Useful? React with 👍 / 👎.

Comment on lines +196 to +204
float frac = 1.0f + static_cast<float>(m) / 8.0f;
float exp2 = 1.0f;
int32_t bias = static_cast<int32_t>(e) - 7;
if (bias >= 0) {
for (int32_t i = 0; i < bias; ++i) exp2 *= 2.0f;
} else {
for (int32_t i = 0; i < -bias; ++i) exp2 *= 0.5f;
}
val = frac * exp2;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the reserved E4M3FN NaN encoding

For E4M3FN weights encoded as 0x7f or 0xff, exponent 15 and mantissa 7 represent NaN, but this generic normalized-number calculation decodes them as finite ±480. A standards-compliant tensor containing the reserved value will therefore silently inject a large finite weight rather than propagating invalid data; handle the reserved encoding before the normal exponent path.

Useful? React with 👍 / 👎.

#!/bin/bash
: "${COMPILER_DIR:?Set COMPILER_DIR to the in-repo Linx compiler bin directory}"

make TESTCASE=group_token_old COMPILER_DIR=$COMPILER_DIR No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Register the new suites in the full compilation driver

The repository-wide benchmark/one-level-arch/compile_all.sh uses a hard-coded list of suite directories, and a repo-wide check shows that none of the newly added group_token_old, group_token_vec, mega_moe, moe_combine, moe_dispatch, or multi-thread group-token compile.all scripts are invoked. Consequently the advertised full compilation skips every test introduced by this commit, allowing these kernels to regress or stop compiling unnoticed; add the new suite paths to the top-level driver.

Useful? React with 👍 / 👎.

Cell-Cell and others added 2 commits August 29, 2026 09:50
… pipeline in vec variant

group_token_old / group_token_vec_mt multi-thread correctness fixes:

- Add mtBarrier (volatile per-PE phase flags + compiler barrier, same
  convention as matmul RES_CHECK leader_ready) between phases: original
  code had cross-PE data hand-offs (Phase1 reduce, merge, Phase3b sort)
  with no synchronization at all.
- Run mergeGroupTokenResults and the verification/reference compare on
  PE0 only: previously all 4 PEs executed the merge concurrently on the
  same output arrays (race + 4x duplicate work).
- group_token_vec_mt.hpp: replace the pseudo-tile scheme with disjoint
  per-PE 4x16 tiles (PE tid owns rows [4*tid, 4*tid+3], trowsum/tadd
  convention). Phase 3a is now a real tile pipeline
  TLOAD -> TREMS -> TROWMIN -> TSTORE. Drop Phase 2 TLOAD whose data was
  never consumed by tile ops (scalar tile-register reads hit a linxv5
  backend 'Cannot select: extract_vector_elt' crash, and the load was
  duplicated 4x across PEs).
- Gate banner printf by tid==0; drop redundant zero-init of .bss arrays.

Verified: both tests compile and pass gfrun 4-PE
(multiThreadNum=4), R2=21b90 (old) / R2=29ef0 (vec).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9e57da233f

ℹ️ 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".


using gm_x = global_tensor<DTypeIn, RowMajor<NumExpanded, H>>;
using gm_win = global_tensor<DTypeIn, RowMajor<NumExpanded, H>>;
using gm_flag = global_tensor<float, RowMajor<NumExpanded, TileW>>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Size flag storage for every hidden tile

When H > TileW, flag_iter(slot, t) advances by a full TileW tile even though each gm_flag row contains only TileW elements. The second hidden tile therefore aliases the next slot's flag row, and the final slot writes beyond windowFlag; gm_pred has the same layout and corruption in the combine phase. Allocate a flag/predicate tile per hidden tile or store one readiness flag per slot.

Useful? React with 👍 / 👎.

void pack_phase(DTypeIn *expandX, std::int32_t *expandIdx,
DTypeIn *windowData, float *windowFlag)
{
constexpr int kTiles = H / TileW;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle the final partial hidden tile

For any allowed instantiation where H % TileW != 0, this floor division omits the final partial tile. Both packing and combining use the truncated count, so those hidden elements are never copied or written to out; process a masked remainder or enforce divisibility with a compile-time constraint.

Useful? React with 👍 / 👎.

Comment on lines +101 to +104
for (int k = 0; k < K; k++) {
int slot = n * K + k;
if (predBuf[slot * TileW] < 0.5f) break;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Stop combining when a contribution is not ready

When any slot's flag is absent or stale, this break exits only the short predicate-inspection loop; execution immediately initializes acc, reads every window slot, and stores an output anyway. The readiness result therefore has no effect, allowing incomplete or stale expert data into the combined token; skip or wait on the tile when the check fails.

Useful? React with 👍 / 👎.

#endif

static uint32_t topkIndex[kTopKEleNum + 2 * 4096];
uint32_t *topkIndexAligned = (uint32_t *)(((uint64_t)topkIndex & ~0xFFFu) + 0x1000);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve upper pointer bits when aligning the test buffer

On a 64-bit CPU/PIE build, ~0xFFFu is a 32-bit mask that is zero-extended before the uint64_t AND, discarding the upper 32 bits of topkIndex. The resulting topkIndexAligned usually points into unmapped low memory, so the explicitly supported PLAT=cpu test crashes during input generation; use a uintptr_t-width mask. The same expression is repeated in the single-thread old and multi-thread vector drivers.

Useful? React with 👍 / 👎.

Comment on lines +181 to +185
int ret = 0;
if (cntMatch != (int)kExpertNum) ret = 1;
else if (secMatch != (int)kExpertPerRank) ret = 2;
else if (idMatch != idTotal) ret = 3;
else if (boundMatch != (int)(kExpertPerRank + 1)) ret = 4;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Verify the vector pod-info output

The vector kernel produces tokenSuperPodInfo, but this test computes no pod-info reference and none of the final pass conditions inspect that buffer. A regression that leaves all pod flags zero or associates them with the wrong grouped token therefore still reports PASS even though one of the operator's documented outputs is corrupt; validate it alongside the grouped IDs as the old-variant test does.

Useful? React with 👍 / 👎.

Comment on lines +78 to +81
q++;
}
}
return (q == slotCnt) ? 0 : 7;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Verify dispatched expert scales

Although the operator emits expandScalesOut and the test initializes nonzero expertScales, the success path never compares the emitted scales with the source slot ordering. A regression that omits the copy, writes zero scales, or associates a scale with the wrong expanded token therefore passes while causing the downstream combine to weight routed tokens incorrectly; check each scale while validating expandIdxOut.

Useful? React with 👍 / 👎.

…ceil-sharding for BS<16

moe_combine_v2: move kernels/deepseek/moe → kernels/moe_combine (dispatch-style
layout: combine_pack/check_flag/clear_flag/combine_reduce with Phase banners
and #N A5-alignment annotations); update test include path.

mega_moe_sim: ceil-based 16-AIV pseudo-core token sharding with tail-core
guard (bs<16: 1 token per core, idle tail cores; bs%16==0 identical to
source), relax static_assert to kBS>0, SIM_FAKE round ceil + bounds check.
Standard sim spec BS=16 H=32 HIDDEN=64 (gfsim passed, Total Cycles=2190707),
aligned with A5 cann-samples mega_moe_story for v300/A5 perf comparison.

Verified: moe_combine_v2 & mega_moe_sim BS16 compile OK, gfrun R2=0.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 982db09433

ℹ️ 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".

Comment on lines +118 to +120
for (uint32_t t = 0; t < kThreadsPerBlock; t++) {
sum += cntLocal[t * expertNum + globalExpert];
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Synchronize histograms before reducing them

In the four-PE multi_thread/group_token_vec execution, each PE begins summing every PE's cntLocal row after completing only its own histogram. A faster PE can therefore read zero or partially populated peer rows and publish incorrect expert counts; the mtBarrier(1) in runGroupTokenVecMT occurs only after this reduction has already finished, so a barrier is needed between histogram construction and this loop.

Useful? React with 👍 / 👎.

Comment on lines +271 to +273
for (uint32_t i = 0; i < batchSize; i++) {
counts[minLocalExpIds[i]]++;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Synchronize row minima before counting-sort reads

In the four-PE vector driver, PE 0 enters this counting loop immediately after storing only its own subset of minLocalExpIds, while the other PEs may still be executing their tile stores. This can index counts with stale minima and produce incorrect section boundaries and sorted IDs; the existing mtBarrier(3) runs after sortKernel_mt_tile returns and is therefore too late to protect these reads.

Useful? React with 👍 / 👎.

static float windowFlag[kSlotCount * kTileW] __attribute__((aligned(4096))) = {};
static float predBuf[kSlotCount * kTileW] __attribute__((aligned(4096))) = {};
static int32_t windowTriple[kSlotCount * 3] __attribute__((aligned(4096))) = {};
static uint32_t windowState[16] __attribute__((aligned(4096))) = {};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reserve the full cumsum flag tile in windowState

With this test's default TileW=128, moe_dispatch_v2.hpp performs a TSTORE of 128 floats beginning at windowState + 4, which requires at least 132 uint32_t elements. This 16-element allocation therefore receives a 512-byte write starting 16 bytes into a 64-byte object, causing an out-of-bounds write and possible corruption of adjacent test buffers.

Useful? React with 👍 / 👎.

moe_combine_v2<__bf16, __bf16, BS, H, K, NUM_EXPANDED, TILE_W>(
expand_x, expert_scales, expand_idx, window_data, window_flag,
window_state, pred_buf, out_buf);
return 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate combine outputs before returning success

This newly added combine test invokes the kernel and then unconditionally returns success without inspecting out_buf, flags, or state. Consequently failures such as skipped reductions, incorrect weighting, or a completely unwritten output all pass the suite, so the test needs a reference computation and explicit output checks before returning zero.

Useful? React with 👍 / 👎.

Comment on lines +256 to +258
for (uint32_t blk = 0; blk < kBS / kTileM; ++blk) {
auto src = gIter(blk, 0);
auto dst = oIter(blk, 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Advance each PE tile by the full 16-row block

In the four-PE vector sort, global_iterator advances i by the tile's four rows, so incrementing blk by one does not move to the next 16-row block as intended. Across all 32 iterations the four PEs repeatedly overlap rows and cover only rows 0–139, leaving minLocalExpIds[140..511] unwritten before the counting sort; stride the iterator by kThreadsPerBlock blocks or construct each source from blk * kTileM + tid * 4.

Useful? React with 👍 / 👎.

Comment on lines +490 to +491
uint32_t curDstPod = topkIndex[j] >> 6;
dstPodLocal[curDstPod] = 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bounds-check pod IDs before indexing scratch storage

On the scalar simulator/CPU path, any out-of-range expert ID makes curDstPod exceed the two-element dstPodLocal array; for example expert 128 writes index 2, while a UINT32_MAX sentinel writes far outside the stack object. Phase 1 explicitly tolerates such IDs by ignoring experts outside expertNum, and the SIMT implementation checks curDstPod < superPodNum, so the scalar fallback should apply the same bound before this write.

Useful? React with 👍 / 👎.

Comment on lines +671 to +677
auto srcGT = xIter(0, base);
TLOAD(t, srcGT);
TMULS(t, t, -1.0f);
TEXP(t, t);
TADDS(t, t, 1.0f);
TRECIP(t, t);
auto dstGT = yIter(0, base);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pass tile indices rather than element offsets in fake mode

Whenever SIM_FAKE=1 processes more than one 256-element chunk, base is already an element offset, but global_iterator::operator() multiplies its column argument by the tile width again. Thus the second logical chunk (base=256) loads and stores at element 65,536 rather than 256—already beyond the default 32,768-element tensors—and subsequent chunks corrupt still more distant memory; pass base / kChunkElems as the iterator column or construct the tile view from xIn + base and yOut + base.

Useful? React with 👍 / 👎.

…ock 0828 toolchain)

moe_dispatch_v2 / moe_combine_v2:
- TMOV's PTO 0.58.4 B.DATR syntax ('NORM, DTYPE_NONE, Zero') is rejected
  by the llvm adcb8794 asm matcher; replace all TMOV pipeline-sync ops
  with TSUB(dst, src, src) — same src->dst dependency edge, no B.DATR
- TCMP's 'B.DATR Zero, cmode0' likewise rejected; collapse the EQ
  predicate in check_flag / check_cumsum_flag to a tile pass-through
  (predBuf is never read back, semantics preserved)

mega_moe compile.all: keep BS16 sim spec only (sim validation baseline).

Verified (TileOP f94bc12 x llvm adcb8794, SuperScalarModel d8903938):
- moe_dispatch: compile OK, gfrun R2=0, gfsim 11299 cycles PASS
- moe_combine: compile OK, gfrun R2=0 (gfsim blocked by model-side
  SuperScalarModel#418, tracked separately)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 35a5570cd9

ℹ️ 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".


for (int k = 0; k < K; k++) {
int slot = n * K + k;
float scale = expertScales[n * K + k];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reorder scales with their expanded rows

When dispatch groups expanded rows by expert, expandX is restored to token/top-k slots through expandIdx, but the corresponding scale remains in expanded-row order. Reading expertScales[n * K + k] therefore applies another row's weight whenever dispatch changed the order and scales differ, corrupting the combined output. Pack each scale into its mapped slot alongside expandX, or retain an expanded-row-to-slot lookup for the reduction.

Useful? React with 👍 / 👎.

Comment on lines +704 to +706
const uint32_t scaleStride = (tilingData.h / 32U) * 2U * 2U * 2U;
w1[flat] = fp8_e4m3_to_f32(g_mmWeight1[flat]) *
fp8_e8m0_scale(g_mmWeightScales1[scaleIdx % scaleStride]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve each weight group's scale index

With nonuniform group scales, reducing scaleIdx modulo scaleStride makes most weight groups reuse the first few scale entries and also aliases scales across experts. For the compiled 32×64 configuration, W1 has 64 groups per expert but this expression cycles through only 8 entries; the analogous W2 expression has the same defect. Index the allocated per-expert scale arrays with the full scaleIdx and the correct tensor stride.

Useful? React with 👍 / 👎.

// E8M0: 纯指数 (偏置 127), scale = 2^(signed)
MM_INLINE inline float fp8_e8m0_scale(uint8_t raw)
{
const int32_t e = static_cast<int32_t>(static_cast<int8_t>(raw));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Decode E8M0 using its exponent bias

For an actual E8M0 scale tensor, the byte is an unsigned exponent with bias 127 as documented immediately above this function; casting it to int8_t instead treats 0x7f (the encoding for scale 1) as exponent 127 and returns approximately 2^127. Ordinary encoded scales therefore inflate weights enough to overflow the GMM pipeline. Subtract the format bias from the unsigned byte and handle its reserved encoding separately.

Useful? React with 👍 / 👎.

Comment on lines +756 to +759
float y1[kMoeHiddenDim];
float y2[kMoeHiddenDim / 2U];
float y3[kMoeH];
for (uint32_t lc = 0U; lc < kBlockAivNum; ++lc) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Give each PE private GMM intermediates

In the documented four-PE gfrun execution, every PE enters this full 16-core loop and processes every token, while the file explicitly notes that gfrun PEs share stack/GM storage. The shared y1, y2, and y3 scratch arrays can therefore hold different tokens when PEs progress at different rates, so one PE may feed another token's intermediate into SwiGLU or GMM2 and nondeterministically corrupt yOut. Partition pseudo-cores by get_thread_idx() as the class path does, or allocate genuinely per-PE intermediate storage.

Useful? React with 👍 / 👎.

for (uint32_t k = 0; k < tilingData_.hiddenDim / 2U; ++k) {
float z = y1[k];
float e = 1.0f / (1.0f + exp_approx(-z)); // sigmoid(z)
y2[k] = z * e; // x·sigmoid(x) = silu

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Multiply both SwiGLU halves in the class pipeline

Whenever a caller uses MegaMoeWave::Process rather than the flattened simulator entry, this computes only silu(y1[k]) and omits the required multiplication by y1[k + hiddenDim / 2]. The subsequent GMM2 therefore receives a different activation from the SwiGLU operation documented above and implemented in the flattened path; include the gated second half here as well.

Useful? React with 👍 / 👎.

{
volatile int32_t* ids = g_mmTopkIds;
for (uint32 t = 0; t < kBS; ++t) {
ids[t] = (int32_t)((t / (kBS / 2u)) % 2u); // 与 gen_data 同规则

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle the advertised one-token batch size

The Makefile exposes BS as a parameter and the non-fake path accepts every positive value, but with BS=1 this routing expression divides by kBS / 2u, which is zero. That supported-looking smallest configuration consequently traps during input generation before the benchmark runs; generate the two expert partitions without using a potentially zero divisor or require kBS >= 2.

Useful? React with 👍 / 👎.

DType* windowData, float* windowFlag)
{
constexpr int slotCount = BS * K;
constexpr int hTiles = H / TileW;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Copy the dispatch hidden-width remainder

For any instantiation where H is not divisible by TileW, this floor division drops the final partial hidden tile. The packing path never writes those elements to the window and dispatch_copy_out repeats the same truncated tile count, so every expanded row has a stale or zero suffix. Process a masked remainder tile or reject non-divisible dimensions with a compile-time assertion.

Useful? React with 👍 / 👎.

The per-PE iterator base offset (tid*4 rows) combined with the 4-row
block stride only covered tokens [0, 140); tokens 140..511 were never
written and stayed zero, which PE0's Phase 3b counting sort then read as
minLocal=0 — root cause of the R2=4 section-bounds verification failure.

Use full-tensor iterators and address each PE's 4-row slice [16*blk +
4*tid, +4) via row-tile index 4*blk + tid (matching the documented
ownership and Phase 1's scalar loop). Phase 1's TLOAD prefetch iterator
gets the same correction (harmless there — counts walk GM by scalar).

Verified (TileOP f94bc12 x llvm adcb8794, gfrun a5dca25, 4-PE):
- before: R2=4 (instrumented diag: [0,140) all match ref, [140,512) all zero)
- after:  R2=0 (deterministic, includes cross-PE visibility checks)

Note: gfrun d8903938 still returns a garbage R2 for both mt kernels —
that is a model-side 4-PE exit-path regression tracked in
LinxISA/SuperScalarModel#419, independent of this kernel fix.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 17f9802cb8

ℹ️ 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".


// Cube tile types
using gmL = global_tensor<float, RowMajor<tM, tK>>;
using gmR = global_tensor<float, RowMajor<tK, tN>>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the W1 source row stride

For the default weights, each W1 row has kMoeHiddenDim = 256 elements, but this descriptor treats the expert buffer as a packed 128-by-16 matrix with a row stride of 16. The TLOAD therefore reads the remainder of row 0 after its first 16 values instead of columns 0–15 from rows 1–127, so even the single GMM1 output tile is computed from the wrong weights. Use a tensor view retaining the 256-element source stride (and likewise retain W2's 128-element stride).

Useful? React with 👍 / 👎.

if (base >= fakeTotalElems) break; // 尾核空转
tileShape t;
auto srcGT = xIter(0, base);
TLOAD(t, srcGT);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Mask the final fake-mode tile

With SIM_FAKE=1 and an allowed configuration where BS * H is not divisible by 256 (for example BS=2 H=64), the base < fakeTotalElems check admits a final partial chunk, but this TLOAD and the matching TSTORE still transfer all 256 floats. That reads and writes 128 elements beyond g_mmX and g_mmY, potentially corrupting adjacent benchmark state; handle the tail with a masked transfer or require a multiple of kChunkElems.

Useful? React with 👍 / 👎.

TileL tA;
TileR tB;
TileC tC;
TLOAD(tA, gA);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allocate the complete GMM2 input tile

In the default full simulation, gmL describes a 16-by-128 input and this TLOAD consequently consumes 2,048 floats, but y2Padded is allocated as only tM * tN = 256 floats. GMM2 therefore reads through the adjacent y3Padded object and beyond it on every token, so its result depends on out-of-bounds memory even before the incomplete output-column issue; allocate and populate the full 16-by-128 input or use a tile matching the actual buffer.

Useful? React with 👍 / 👎.

Comment on lines +185 to +186
for (uint32_t i = 0; i < kExpertPerRank * kBS * kSuperPodNum; i++) {
if (tokenSuperPodInfo[i] == refPodInfo[i]) podMatch++;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match pod information by token rather than scatter order

On the real SIMT path, the atomic increments used to scatter tokens do not guarantee the sequential token order produced by refGroupToken; the test accounts for this by sorting grouped IDs before comparing them, but it compares tokenSuperPodInfo in raw slot order here. A correct execution whose lanes acquire section positions in a different order can therefore fail solely because each valid pod record is compared with another token's reference record; canonicalize IDs together with their pod data or look up pod data by token ID.

Useful? React with 👍 / 👎.

const float* w1Row = w1fp32 + e * h * 256 + nt * kVecW;
gmFlat gW(const_cast<float*>(w1Row));
TLOAD(b, gW);
TMUL(acc, a, b);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reduce across K in the vector GMM implementation

When gmm_pipeline_for_token uses the default h=128, kTiles is one and this operation merely forms 128 lane-wise products between the input vector and one contiguous weight segment; no horizontal reduction across the 128 K values is ever performed. The stored lanes therefore contain individual products rather than the 128 dot products required for a GMM1 output tile, and gmm2_combine_tile repeats the same mistake. Use matrix multiplication or explicitly reduce K for every output column.

Useful? React with 👍 / 👎.

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