Repository navigation
moe: top-k selects exactly k experts on exact fp16 ties - #298
Merged
Merged
Conversation
The amax peel in _topk_gate marked every expert tied at the row maximum, so an exact fp16 tie activated more than k experts. On Qwen1.5-MoE-A2.7B (E=60, k=4) this happened in 36 of 1848 layer-steps of one 48-token decode (5 or 6 experts active). Each round now selects the lowest tied index and removes only that expert. Expert indices are fp16 integers, exact through 2048, so the exactly-k guarantee holds for E <= 2049; larger counts and k outside 1..E are rejected. Tests: an on-device tie test (three tie patterns and a no-tie row; it fails on the previous code, which selects 6 experts on a 3-way tie with k=2) and an off-device range test.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
aneforge/moe.py:_topk_gatebuilds the top-k mask by peeling the row maximum k times. Each round marked every expert tied at the maximum, so an exact fp16 probability tie activated more than k experts. On Qwen1.5-MoE-A2.7B (E=60, k=4) this happened in 36 of 1848 layer-steps of one 48-token decode, activating 5 or 6 experts.Change
_topk_gatenow rejects counts outside1 <= k <= E <= 2049with a clear error instead of silently selecting more than k.Tests
tests/test_moe.py::test_topk_gate_exact_ties_select_exactly_k(on-device): three tie patterns (a 3-way tie at the max, a tie at the k-th slot, a tie among the top two) and a no-tie row; checks the selected indices and that gate values are unchanged. On the previous code it fails (a 3-way tie with k=2 selects 6 experts).tests/test_moe_topk_range.py(off-device): rejected counts and the largest supported count.Checks run (Apple M5 Pro, H17s, macOS 27.0.1)
ruff check, the 2-space pylint gate,python -m compileall -q aneforge: clean.pyright aneforge: 0 errors withaneforge/_version.pypresent (generated at install). In this un-installed checkout it reports 2 errors for the missing generated file, identical onmain.pytest -m "not requires_ane" -q: 525 passed, 1 skipped.tests/run_corpus.py: GREEN, 90/90.pytest tests/ -q(on-device, full): 27 failed, 1275 passed, 1 skipped. All 27 failures reproduce on unmodifiedmain(4114158) on this machine: the same 27 tests fail on both trees when rerun serially, and the set did not change across runs. None involve MoE code. Most are ANE compile failures (ane_e5rt_program_compile failed; the native SDPA bridge reportsverifyBundleAtPath: invalid model), which looks like a macOS 27.0.1 compatibility issue worth its own investigation:tests/test_autograd.py::test_transformer_block_trainstests/test_autograd.py::test_charlm_trains_and_predictstests/test_autograd.py::test_charlm_generalizes_on_corpustests/test_autograd.py::test_prenorm_transformer_block_trainstests/test_autograd.py::test_llama_block_trainstests/test_autograd.py::test_resident_transformer_block_trainstests/test_decoder_block.py::test_causal_llama_block_matches_numpy[6-16-2-32]tests/test_decoder_block.py::test_causal_llama_block_matches_numpy[8-32-4-64]tests/test_decoder_block.py::test_generate_fixed_cache_matches_per_steptests/test_linalg.py::test_lu_pivotedtests/test_linalg.py::test_det_matches_numpy[10.0]tests/test_linalg.py::test_det_matches_numpy[100.0]tests/test_linalg.py::test_slogdet_matches_numpytests/test_linalg.py::test_solve_matches_numpy[4]tests/test_linalg.py::test_solve_matches_numpy[6]tests/test_linalg.py::test_solve_matches_numpy[8]tests/test_linalg.py::test_solve_matrix_rhstests/test_linalg.py::test_solve_residual_is_smalltests/test_linalg.py::test_inv_matches_numpy[4]tests/test_linalg.py::test_inv_matches_numpy[6]tests/test_linalg.py::test_inv_matches_numpy[8]tests/test_linalg.py::test_inv_times_a_is_identitytests/test_onnx.py::test_fuse_attention_causal_uses_native_sdpatests/test_onnx.py::test_attention_matches_onnxruntimetests/test_sdpa_causal.py::test_af_sdpa_is_causal_native_end_to_endtests/test_sdpa_causal.py::test_native_sdpa_additive_mask_bridgetests/test_streaming.py::test_streamed_grads_match_monolith