Fix TypeError in cached ElasticBuffer dispatch with default num_sms#685
Fix TypeError in cached ElasticBuffer dispatch with default num_sms#685yurekami wants to merge 1 commit into
Conversation
| @@ -924,8 +924,10 @@ def dispatch(self, | |||
| check_torch_deterministic() | |||
There was a problem hiding this comment.
🟡 warning: The public dispatch docstring still says num_sms: ... 0 for automatic via get_theoretical_num_sms, which is now inaccurate/inconsistent with combine (0 to reuse the SM count from the dispatch handle). It should state that a cached handle reuses handle.num_sms. Pure documentation; non-blocking.
🤖 v4
| # was laid out under that value; `num_experts` is also not inferred from the handle until below | ||
| num_topk = (handle.topk_idx if topk_idx is None else topk_idx).shape[1] | ||
| num_sms = self.get_theoretical_num_sms(num_experts, num_topk) if num_sms == 0 else num_sms | ||
| num_sms = (handle.num_sms if handle is not None else self.get_theoretical_num_sms(num_experts, num_topk)) if num_sms == 0 else num_sms |
There was a problem hiding this comment.
🔵 suggestion: The nested ternary (handle.num_sms if handle is not None else get_theoretical_num_sms(num_experts, num_topk)) if num_sms == 0 else num_sms is hard to read and duplicates logic. Consider splitting into a clear few lines. Behaviour already correct; non-blocking.
🤖 v4
🤖 ds-review-bot Code Reviewv4The candidate PR fixes the cached-dispatch v3The change fixes a TypeError in ElasticBuffer.dispatch where num_experts was read at line 928 (get_theoretical_num_sms) before being inferred from the handle 14 lines later. In cached-dispatch mode with the default num_sms=0 and num_experts=None, this raised 'TypeError: unsupported operand type(s) for %: NoneType and int'. The fix (line 930) reuses handle.num_sms when a handle is provided (as combine already does at line 1088 for the same value), instead of recomputing a theoretical SM count that could disagree with the channel geometry the handle was built under. All three behavioral branches are preserved correctly via the nested ternary: cached+num_sms=0 -> handle.num_sms; fresh dispatch (handle is None) -> get_theoretical_num_sms(num_experts, num_topk); explicit num_sms -> respected in both modes via the outer 'if num_sms == 0' guard. The fix is minimal (one line + comment), well-reasoned, and consistent with the existing combine() path. The analysis in the description is accurate and the design tradeoff (reuse handle.num_sms vs. hoisting the unpack block) is sound. Minor, non-blocking: the single-line nested ternary is fairly dense and could be split into an if/else block for readability. No correctness issues found. Files reviewed: 1 |
0258874 to
3caa7ed
Compare
Summary
ElasticBuffer.dispatchreadsnum_expertsatdeep_ep/buffers/elastic.py:928butonly infers it from the handle 14 lines later, at line 942:
Both defaults make this reachable:
num_experts: Optional[int] = None(L860) andnum_sms: int = 0(L863). The docstring documents exactly this call —"
num_experts: ... Inferred fromhandleif provided" (L893) and "num_sms: ...0 for automatic" (L897) — so the documented cached call
enters L928 with
num_experts=None, giving:Callers that pass
num_smsexplicitly are unaffected, which is why the test suitenever hits it:
tests/elastic/test_ep.py:65resolvesnum_smsitself up front andpasses it into every dispatch call (L146, L165, L212, L223), so
num_smsis never 0on entry.
Reproduction
I do not have an NVIDIA device, so this is reproduced host-side — the failure occurs
in pure Python at L928, before any native/NVSHMEM call. Loading the unmodified
elastic.py(onlydeep_ep._Cstubbed) and calling the realget_theoretical_num_smswith thenum_expertsthat L928 passes it in cached mode:On a GPU box the two-line snippet above reproduces it directly.
Fix
Take the SM count from the handle when one is provided, which is what
combinealready does for the same value at
elastic.py:1086:EPHandlestoresnum_sms(L63, L82) precisely so the cached path can reuse it.Design context
The alternative fix is to hoist the handle-unpacking block (L937-948) above the SM/QP
computation so
num_expertsis populated before L928. I did not take it: the handle'schannel metadata (
channel_linked_list,dst_buffer_slot_idx, the psum tensors) waslaid out under the SM count the original dispatch actually ran with, and recomputing a
theoretical value here could return a different count than the handle was built under.
Reusing
handle.num_smskeeps cached dispatch consistent with both the handle and withcombine, which must agree on channel geometry for the same handle.Scope
One line in
dispatch, plus a comment. Behavior changes only on the branch thatcurrently raises:
num_sms=0-> now useshandle.num_sms(was:TypeError)handle is None) -> unchanged, stillget_theoretical_num_sms(num_experts, ...)num_smsexplicitly -> unchangedTest plan
num_smsno longer raises; resolves tohandle.num_smsnum_smsstill resolves viaget_theoretical_num_smsnum_smsstill respected in both modestests/elastic/test_ep.pypassesnum_smsexplicitly, so it does not cover this branch either wayNote on
format-checkCI is red here on pre-existing lint in this file, not on this change.
format.shlints only files that differ from
origin/main, somainnever lintselastic.pyand these never surface there. Running the pinned tooling againstunmodified
origin/main(60d4403):This PR adds zero new violations — ruff reports the same 3 errors and yapf the
same 439 diff lines with and without it, and my added lines are all <=115 chars.
I deliberately did not fix the pre-existing lint here: it would either be a
1-of-3 fix that still leaves CI red, or a ~439-line reformat unrelated to a
4-line bugfix. Happy to send that as a separate PR if you want the file made
lint-clean.