perf(dsv4/moe): remove redundant dependency edges - #803
Conversation
📝 WalkthroughWalkthroughDeepSeek-V4 MoE decode now buffers dispatch metadata locally, updates gather/combine ordering, extracts shared-routed accumulation into a submitted helper, and applies explicit manual dependencies to intermediate and expert quantization tensors. ChangesDeepSeek-V4 decode updates
Sequence Diagram(s)sequenceDiagram
participant moe
participant dispatch
participant recv_meta_local
participant dispatch_gather
participant combine
moe->>dispatch: pass recv_meta_local and dispatch buffers
dispatch->>recv_meta_local: store per-source expert counts
dispatch->>dispatch_gather: signal metadata and local push completion
dispatch_gather->>recv_meta_local: read local counts
dispatch_gather->>combine: provide gathered expert outputs
combine->>recv_meta_local: read local counts
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request optimizes the DeepSeek v4 MoE implementation by introducing a local metadata tensor recv_meta_local to reduce distributed reads, refactoring the shared_routed logic into an incore JIT function submitted via pl.spmd_submit, and marking several intermediate tensors with manual_dep=True to avoid automatic dependency tracking overhead. The reviewer feedback suggests further performance optimizations by also marking comb_ffn and recv_count_out with manual_dep=True, as their ordering is already transitively guaranteed.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
d365c8a to
c6ca8a0
Compare
#816) ## Summary Splits `models/deepseek/v4` into two variants and gives the A5 (Ascend 950) variant its own daily on-device CI. Built on top of latest `main` (includes #800's `setup-ci-job` restructure and #803's `dsv4/moe` dep-edge change — the rename carries both into `v4-flash` cleanly). ## Changes - **Rename `models/deepseek/v4` → `v4-flash`** (`git mv`, history preserved) and **add `models/deepseek/v4-pro`** as a byte-identical copy — the A5-targeted variant. Internal imports are bare module names resolved per-directory, so the rename is import-safe; the generic `detect_changes.py` auto-discovers both dirs. - **Path references** updated to `v4-flash` (README, docs, test-with-golden skill). `ci.yml` serving-test arming regex → `^models/deepseek/v4-(flash|pro)/` so a PR touching either variant arms the e2e serving test. Logical names (`model-name deepseek-v4`, `tests/test_deepseek_v4_accuracy.py`, `build_deepseek_v4_rope_tables`) are unchanged. - **`daily_ci.yml` `model-tests-a5`**: a real Ascend 950 device job on the `npu-a5` self-hosted runner that runs `-p a5` over only `models/deepseek/v4-pro`, built on the shared `setup-ci-job` (`needs-device: true`, no hardcoded paths — the A5 host's `.env` supplies `CANN_ROOT`/cache roots). `v4-pro` is excluded from the a2a3/a2a3sim/a5sim sweeps to avoid running the A5 variant on 910B. The summary gains a second **"DeepSeek V4-Pro (A5)"** table; it is best-effort — the summary job does **not** depend on `model-tests-a5`, so daily CI keeps producing and the table fills in once the runner exists.
Method: removing dependency edgesThe code-level mechanism depends on where the target edge comes from. In PyPTO, the effective fanin is: Therefore, if the same structural edge is produced by both mechanisms, both sources must be removed. 1. Remove an explicit TaskId edgeFor edges introduced by Before: with pl.spmd(
N_LOCAL,
name_hint="dispatch_gather",
deps=[_meta_tid, _wait_tid, _push_tid],
) as _gather_tid:
...After: with pl.spmd(
N_LOCAL,
name_hint="dispatch_gather",
deps=[_wait_tid, _push_tid],
) as _gather_tid:
...This removes the direct The same rule applies to 2. Disable automatic tracking for a tensor lifetimeFor automatic edges created by reads/writes of a tensor, mark the allocation with recv_scale_out = pl.create_tensor(
[N_LOCAL, RECV_MAX],
dtype=pl.FP32,
manual_dep=True,
)Every task that reads or writes this tensor skips OverlapMap lookup and producer insertion for its entire lifetime. The original allocation creator/owner retention still applies. Examples in this PR include:
Use this only when every automatic edge carried by that tensor is redundant or is covered by another data path or an explicit dependency. 3. Disable tracking for one call argumentWhen only one consumer should ignore a tensor dependency, use the narrower call-site form: ffn_out, _reduce_tid = pl.spmd_submit(
self.shared_routed,
sh,
pl.no_dep(routed_y_buf),
ffn_out,
num_tokens,
core_num=T,
deps=[_cwait_tid],
)
Here the local automatic For an outlined 4. Change the data path before disabling trackingIf the original tensor still has other consumers that need automatic tracking, introduce a dedicated local snapshot rather than marking the original tensor manual. This PR snapshots distributed metadata once: recv_meta_local = pl.create_tensor(
[N_RANKS, N_LOCAL],
dtype=pl.INT32,
manual_dep=True,
)
count = pl.read(recv_meta, [src, e])
pl.write(recv_meta_local, [src, e], count)Gather and combine then read 5. Preallocate expression results that need manual trackingIf a value is created as an expression result inside a task, first create explicit storage with Before: h_tile_scale_dq = pl.reshape(pl.recip(eh_sq_row), [RECV_TILE, 1])After: h_tile_scale_dq = pl.create_tensor(
[RECV_TILE, 1],
dtype=pl.FP32,
manual_dep=True,
)
with pl.at(level=pl.Level.CORE_GROUP, name_hint="exp_h_q"):
...
h_tile_scale_dq[:, :] = pl.reshape(
pl.recip(eh_sq_row),
[RECV_TILE, 1],
)This allows the scale path to opt out while the main Practical ruleFor each target structural edge
A structural edge disappears only after all of its sources have been removed. For example, removing Host-read control tensors and cross-rank synchronization require extra care. In this PR, |
Summary
Testing
python -m ruff check models/deepseek/v4/moe.py models/deepseek/v4/expert_shared.py models/deepseek/v4/expert_routed.pypython models/deepseek/v4/moe.py -p a2a3 --ep 2 -d 0,1 --compile-only