Skip to content

Fix encoder CPU dwte bucket width - #3

Draft
SaiSandeepKantareddy wants to merge 1 commit into
ulmentflam:mainfrom
SaiSandeepKantareddy:fix-encoder-cpu-dwte-bucket-width
Draft

Fix encoder CPU dwte bucket width#3
SaiSandeepKantareddy wants to merge 1 commit into
ulmentflam:mainfrom
SaiSandeepKantareddy:fix-encoder-cpu-dwte-bucket-width

Conversation

@SaiSandeepKantareddy

Copy link
Copy Markdown

Why

While setting up the repo to look into possible benchmark contributions, I hit a failing encoder backward equivalence test on Apple Silicon/macOS.

make test-python was failing in tests/test_encoder_equivalence.py because the CPU custom-op path for encoder backward left token embedding gradients (dwte) as zero for token rows that appeared in the batch.

The issue was in wte_backward_cpu: it derived each bucket's channel span from WARP_SIZE * width, but the bucket metadata is produced using the module-level WTE_C_PER_WARP contract. On the CPU custom-op path this made the channel span empty, so the accumulation loop never wrote dwte.

What changed

Use WTE_C_PER_WARP directly when mapping a bucket channel_group to its channel range in the CPU wte_backward_cpu path.

Also added a short docs/ai/ bug note describing the symptom, root cause, fix, verification, and AI-use disclosure.

Verification

  • tests/test_encoder_equivalence.py: 6 passed
  • dataloader/tokenizer/encoder subset: 12 passed
  • make test-python: 233 passed, 15 skipped
  • make build: passed
  • pre-commit make lint: passed
  • pre-push make check: passed

Note: Mojo printed Crashpad warnings under sandboxed macOS, but the relevant commands exited successfully. One pre-push attempt hit a transient Mojo/Metal build-profile compiler failure; running make build-profile directly passed, and the subsequent pre-push make check passed.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 30c3618f-0f08-46b8-b398-02a7bf556568

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ulmentflam

Copy link
Copy Markdown
Owner

Hi @SaiSandeepKantareddy! Thanks for reporting this issue via draft PR. I will have a fix that includes a regression tests up for this shortly.

ulmentflam added a commit that referenced this pull request Aug 13, 2026
Reported in #3 against the CPU
custom-op path on Apple Silicon / macOS, where encoder backward left dwte at
zero for token rows present in the batch.

Bucket metadata is emitted against the module-level contract
`WTE_C_PER_WARP = 32 * WTE_BWD_SIMD_WIDTH` (128), and the equivalence test's
Python reference builder hardcodes the same 128, but the CPU consumer
re-derived the span as `WARP_SIZE * simd_width_of[dtype]()`. The GPU kernel
uses that identical expression and is right there only because on a GPU target
it evaluates to exactly 32 * 4 == 128; the CPU path inherited the expression
without the property that makes it true, and WARP_SIZE is meaningless on a CPU
target anyway.

The span is wrong on every platform, but only wrong in RESULT when it comes out
smaller than 128. On this x86-64 box it is 32 * 16 = 512: group 0 covers
[0, 512), every higher group computes c_len <= 0 and is skipped, and the union
is still exactly [0, channels) covered once, so the answer is correct and merely
leaves one bucket doing all the work. At a stride of 0, which is what the
reporter's sandboxed CPU compile produces, nothing is written at all. Worth
noting the mechanism in the report does not fire on Apple Silicon by itself:
NEON fp32 gives width == 4 and 32 * 4 == 128 would have matched, so the
operative fact is WARP_SIZE resolving to 0 there, not the SIMD width.

Two independent gaps hid this. `make test` on a GPU box auto-detects the GPU and
runs test-python-cuda, so the CPU custom-op path is never executed. And every
encoder case had channels <= 128, so ceildiv(channels, 128) == 1, channel_group
was always 0, and any wrong stride cancelled at c_base == 0.

Adds a channels=256 case (fp32_multi_channel_group) so the multi-group path is
covered at all. Be clear about what it buys: on x86-64 it passes both before and
after this fix, because a too-large stride still tiles correctly. It is real
coverage for a previously untested path and it catches this bug where the bug is
live, but it would not have failed here. Catching a too-small stride on x86
needs an assertion on the per-bucket span itself rather than end-to-end
gradients.

Verified: encoder equivalence 8 passed on the CPU custom-op path and 8 passed on
the GPU path; full gate green (21 mojo test files, 245 passed / 5 skipped, zero
warnings). `make test-python` on this box is separately broken by the upstream
MAX CPU custom-op SIGSEGV (docs/ai/max_cpu_custom_op_crash_2026-07-24.md);
confirmed pre-existing by reproducing the adamw crash at fa74e79 with this
change stashed. Analysis: docs/ai/wte_backward_cpu_channel_span_bug.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

2 participants