Repository navigation
deep-ep: add DeepEP 2.5.0 (expert-parallel dispatch/combine) - #1231
IlyasMoutawwakil wants to merge 9 commits into
Conversation
|
This PR changes source code for the following kernel(s): If you have the kernel-bot build permissions, then trigger |
|
/kernel-bot build-and-stage deep-ep |
|
Build request processed. Command: Dispatched (1):
Hub uploads: |
| topk_idx_t, | ||
| ) | ||
|
|
||
| __version__ = '2.5.0' |
There was a problem hiding this comment.
I think we can remove the figures?
There was a problem hiding this comment.
is it common practice to remove the version ? i think it's useful since the hub versioning starts at 0/1
sayakpaul
left a comment
There was a problem hiding this comment.
Some thoughts:
- Possible to use Stable ABI here?
- Possible to follow the strategy of #999 to speed up the builds?
- Possible to use
kernel-porttool to see how far we can get with it? Cc: @drbh
I will also let @danieldk comment here.
|
|
hmm so yeah i had this feeling that stable-abi-ing might cause perf regression due to the stable abi apis being meant for versatility and not necessarily dispatching to the maximum perf route. the initial prototypes my agent came up with pass all tests but don't hold perf parity. will investigate more but i'd be wary of any stable-abi rewrite we did in the past without comparing performance. @sayakpaul @vasqu DeepEP stable ABI performance experimentDecision: retain the original bindings on the PR. Neither stable ABI prototype is ready to ship under the requirement to preserve performance. The functional minimum reached is Torch 2.10. That does not establish performance parity. The 2.10 prototype passes the stable ABI checker with Python limited ABI 3.9, using the host manylinux_2_39 check; the host toolchain still prevents establishing manylinux_2_28 compatibility. Its source passed eight CI tests and 36 two-GPU deferred EP cases on actual Torch 2.10, plus an external-storage/view ownership probe. Measurements used Torch 2.14 + CUDA 13.0 and NCCL 2.32.3 on two B200 GPUs (physical GPUs 4 and 5), with CUDA 13.2 runtime JIT. Each paired run loads the original and one candidate in both rank processes, uses the same inputs, group, GPU placement and CPU affinity, warms both implementations, and alternates measurement order across ten repetitions of 100 calls. The stable builds use -O3. Inference mode is enabled. Measurements include current_stream_wait; CUDA event elapsed time therefore includes host launch gaps and rank coordination. Reported values are the maximum across ranks of each rank’s median per-call elapsed time. Representative workload: 4096 tokens/rank, hidden size 7168, 256 experts, top-k 6. These are local two-GPU measurements, not a multi-node or production-scale characterization.
The 2.10 prototype regresses about 20–30% on this workload; the 2.14 prototype also regresses. Raising the minimum to 2.14 does not solve the issue in these implementations. The host-side paths show additional time, while separately profiled GPU epilogues remain close. Communication kernel durations can include rank waiting and must not be read as pure GPU compute time. These results do not prove stable ABI itself necessarily entails a regression; they reject these prototypes as production conversions. |
|
/kernel-bot build-and-stage deep-ep |
|
Build request processed. Command: Dispatched (1):
Hub uploads: |
|
kernel-port used ! |
There was a problem hiding this comment.
I need to take a deeper look at the overall kernel implementation but I'm not sure this is using the kernel-porting too in its intended way...
for example kernels that are ported with the porting tool should roughly have the shape
└── kernel (deep-ep)
├── port
│ ├── overlay
│ ├── ...
│ └── port.kdl
└── src
├── build.toml
├── ...
└── torch-ext/deep-ep
where the port directory contains the inputs, and the src contains the output (completely generated from the inputs and not edited)
**the einops kernel is a good example of this
based on the structure of this kernel its doesnt seem like the output is generated from the porting tool.
since the kernel seems pretty complex and likely needs non trivial changes to be built with the kernel builder - is probably not the best candidate for the porting tool, so unless this can be restructured to align with the tooling i'd recommend that we remove the port from this pr
|
i see, thanks @drbh, i only pointed the agent at the tool without an example, the tool probably doesn't explain how the codebase should be structured and that's why it treated it as just another folder within the kernel codebase rather than outside of it. will restructure it and see if nccl can be used fom torch as @danieldk said on slack🙏 |
|
checked: deepep 2.5's host code uses GIN symbols that only exist from nccl 2.31/2.32 (custom-stride/resource-sharing connections, gin-only windows, VA signal ops, EFA-GDA transport), and kernel-builder's torch pins nccl 2.30.4, so we can't build against it yet. if kernel-builder bumps nccl to 2.32 we can drop the vendored headers (runtime already uses torch's nccl) @danieldk |
7bf891d to
d4ee0ba
Compare
The inputs of the port, and what to review: port.kdl pins DeepEP def8651 (2.5.0), its DeepJIT dependency and the NCCL 2.32.3 headers and applies guarded edits to them; overlay/ holds the handwritten Torch-op bindings, the Python adapters, the CI tests and the Hub card; regenerate.py and README.md reproduce and check the output. The build flake at the kernel root builds ./src. Only the small host extension is built; DeepEP's GPU kernels are JIT-compiled at runtime by DeepJIT against PyTorch's NCCL. Co-Authored-By: Claude Opus 5.5 <[email protected]>
deep-ep/src as kernel-port generates it from deep-ep/port, never edited by hand; CI regenerates and compares it, including provenance. Co-Authored-By: Claude Opus 5.5 <[email protected]>
check_ports passes the vendor checkouts a recipe pins (vendor name/repo/commit) to kernel-port. Co-Authored-By: Claude Opus 5.5 <[email protected]>
d4ee0ba to
2d2fd54
Compare
|
Any updates here? Wasnt there some plan to use an alternative to nccl in general, i vaguely remember something |
|
there was a pr that simplifies nccl handling huggingface/kernels#951 using it |
kernel-builder now gives every CUDA kernel the NCCL of its Torch build (huggingface/kernels#951), with `mkNccl` and flake overlays to pick another version. DeepEP 2.5 needs NCCL 2.32 APIs, newer than the 2.30.7 of kernel-builder's Torch, so the flake overrides Torch's NCCL with 2.32.3 built from the nvidia-nccl-cu13 wheels. Their headers are byte-identical to the 69 headers the recipe assembled from NCCL's Git sources, so the recipe no longer vendors NCCL: the vendor, moves, version substitutions, include paths and the header README go, and regenerate.py drops its --nccl input. Bumps kernel-builder to 88e9dd07, which includes the NCCL support. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
/kernel-bot build deep-ep |
|
/kernel-bot build-and-stage deep-ep |
|
Build request processed. Command: Dispatched (1):
|
|
Build request processed. Command: Dispatched (1):
|
deep-ep has no Git submodules (DeepJIT is inlined by the recipe), but the flag makes Nix fetch every submodule of the repository, including deep-gemm's uninitialized cutlass checkout, which fails the build's matrix generation. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
/kernel-bot build-and-stage deep-ep |
|
Build request processed. Command: Dispatched (1):
Hub uploads: |
| @@ -0,0 +1,58 @@ | |||
| # DeepEP port recipe | |||
There was a problem hiding this comment.
we can probably remove the readme since it should be generated from the the CARD.md
There was a problem hiding this comment.
removed, the useful bits (regenerate/check commands, what's pruned) are in the deep-ep section of AGENTS.md now
| // Guard every change to upstream code; do not hide vendored edits in overlays. | ||
|
|
||
| // csrc/buffers/base.hpp | ||
| replace in="csrc/buffers/base.hpp" count=1 \ |
There was a problem hiding this comment.
nice to see the port being used!
long term we want to avoid explicit string replacement as much as possible, this is because the replace ops are a bit brittle and will require a bunch of changes if the upstream moves.
however, in the short term if you are open to maintaining these replace ops, I think they are acceptable and move us in the right direction.
**in general its already a benefit to use the port with replace over manually porting the files since all of the string changes are contained within this single file and are reproducible (instead of being spread over many files)
note: the porting tool is going to be updated (in this pr huggingface/kernels#946 and some upcoming ones) which should help make it easier to write ports without large replace ops. once those land, maybe we can revisit these ops and make them a bit more robust to upstream changes.
There was a problem hiding this comment.
yeah happy to maintain them for now, will revisit once kernels#946 and the follow-ups land
| @@ -0,0 +1,28 @@ | |||
| name: Code Format Check | |||
There was a problem hiding this comment.
we can probably prune this file in port.kdl since it comes from upstream but doesnt make sense to copy into the kernel here
There was a problem hiding this comment.
pruned, see below
|
|
||
| // DeepEP 2.5.0 and its exact DeepJIT dependency. | ||
| source repo="https://github.com/deepseek-ai/DeepEP.git" commit="def865146e95aa9ea223e277c0a8a4c6b81e5ac7" | ||
| prune keep=".clang-format,.editorconfig,.gitignore,.gitmodules,.github/workflows/format.yml,third-party/tilelang_ops/**,LICENSE,README.md,format.sh,requirements-lint.txt,csrc/**,deep_ep/**,tests/**,figures/**" |
There was a problem hiding this comment.
maybe we can prune upstream files the kernel doesn't use: lint/CI config (.clang-format, .editorconfig, .github/workflows/format.yml, format.sh, requirements-lint.txt), the stale .gitmodules (DeepJIT is inlined), unreferenced figures/ (1.2 MB), and the top-level deep_ep/*.py, which duplicate torch-ext/deep_ep (only deep_ep/include is used by build.toml).
| prune keep=".clang-format,.editorconfig,.gitignore,.gitmodules,.github/workflows/format.yml,third-party/tilelang_ops/**,LICENSE,README.md,format.sh,requirements-lint.txt,csrc/**,deep_ep/**,tests/**,figures/**" | |
| prune keep=".gitignore,LICENSE,README.md,csrc/**,deep_ep/include/**,tests/**,third-party/tilelang_ops/**" |
There was a problem hiding this comment.
applied both prunes and regenerated src/, only deletions, check_ports passes byte for byte
| vendor name="python" repo="https://github.com/deepseek-ai/DeepEP.git" commit="def865146e95aa9ea223e277c0a8a4c6b81e5ac7" path="deep_ep" to="torch-ext/deep_ep" | ||
| vendor name="deepjit" repo="https://github.com/deepseek-ai/DeepJIT.git" commit="cba802df081985141eeee36803685775c183a047" path="." to="third-party/deep_jit" | ||
|
|
||
| prune keep=".clang-format,.editorconfig,.gitignore,.gitmodules,.github/workflows/format.yml,third-party/tilelang_ops/**,LICENSE,README.md,format.sh,requirements-lint.txt,csrc/**,deep_ep/**,tests/**,figures/**,torch-ext/**,third-party/deep_jit/**" |
There was a problem hiding this comment.
same set as above, plus only the DeepJIT headers are built, so CMakeLists.txt, csrc/python_api.cpp, deep_jit/ and tests/ under third-party/deep_jit can go. src/ needs regenerating after this (port/regenerate.py).
| prune keep=".clang-format,.editorconfig,.gitignore,.gitmodules,.github/workflows/format.yml,third-party/tilelang_ops/**,LICENSE,README.md,format.sh,requirements-lint.txt,csrc/**,deep_ep/**,tests/**,figures/**,torch-ext/**,third-party/deep_jit/**" | |
| prune keep=".gitignore,LICENSE,README.md,csrc/**,deep_ep/include/**,tests/**,third-party/tilelang_ops/**,torch-ext/**,third-party/deep_jit/include/**,third-party/deep_jit/README.md" |
note: we need this second prune after the vendoring since it prunes the third party stuff
| @@ -0,0 +1,60 @@ | |||
| #!/usr/bin/env python3 | |||
There was a problem hiding this comment.
i think we can drop regenerate.py. it does the same thing as scripts/check_ports.py (run kernel-port on port.kdl and byte compare against src/ with the same ignore rules), and python3 scripts/check_ports.py deep-ep already checks just this kernel the same way ci does
the bigger issue is that it hard codes --vendor python=... --vendor deepjit=... and takes the checkouts as args, while check_ports.py reads source/vendor from port.kdl and clones the pinned commits itself. so if a sync adds or renames a vendor, or someone passes a checkout at the wrong commit, it can pass locally and fail in ci (or the other way around). einops/port doesnt have one either, so this would be the only per kernel script
the only extra thing it gives us is --output to write the generated tree for copying into src/, and that isnt deep-ep specific. would be nicer as a --write flag on check_ports.py that replaces <kernel>/src instead of comparing, then every port gets the same regenerate command
pushed --write in #1299, its one commit (b90e73c) on top of this branch so you can either cherry pick it or rebase onto it
to drop it:
- delete
deep-ep/port/regenerate.py - point
deep-ep/port/README.md(~L39) atpython3 scripts/check_ports.py --write deep-ep - update the deep-ep section in
AGENTS.md(~L662) which says to regenerate withregenerate.py
note: kernel-port drops the exec bit (format.sh comes out 644) and compare() only checks contents, so ci doesnt catch it. doesnt matter here once the prune suggestion lands since those files go away
There was a problem hiding this comment.
dropped, merged your --write branch in and pointed AGENTS.md at check_ports.py --write deep-ep. thanks!
`python3 scripts/check_ports.py --write <kernel>` regenerates a port from the pins in its recipe and replaces the committed tree, so ports no longer need their own regenerate scripts. A failed kernel-port run leaves src/ untouched.
…rite The recipe now keeps only what the kernel builds: drops upstream lint/CI config, figures, the stale .gitmodules, the top-level deep_ep Python package (the `python` vendor provides it as torch-ext/deep_ep), and everything in DeepJIT except its headers. regenerate.py and the port README go away: `python3 scripts/check_ports.py --write deep-ep` regenerates src/ from the recipe's own pins. Co-Authored-By: Claude Opus 5.5 <[email protected]>
e218a10 to
c8b9a0d
Compare
… overlay Co-Authored-By: Claude Opus 5.5 <[email protected]>
Related issue
None. This supports the upcoming
deepepexpert-parallel strategy in transformers, discussed internally.What does this PR do?
Adds DeepEP 2.5.0 as the Hub kernel
kernels-community/deep-ep, preserving its expert-parallel dispatch/combine API. Only the small host extension is prebuilt; DeepJIT compiles communication kernels at runtime.Review guide
The port is now reproduced by kernel-port, laid out like the other ported kernels (e.g. einops):
deep-ep/port/holds the inputs (recipe and overlays) anddeep-ep/src/the output, generated from them and never edited; the build flake sits at the kernel root and builds./src. Start with the recipe and handwritten overlays: CI verifies that they reproducesrc/byte-for-byte, including provenance. Imported files still need attention when assessing upstream behavior, but the local porting changes are collected in these review surfaces:def865146e95aa9ea223e277c0a8a4c6b81e5ac7and its DeepJIT dependencycba802df081985141eeee36803685775c183a047. NCCL is not vendored (see below).torch_binding.cpp,deep_ep_holders.hpp, andcompat/replace pybind11 registration with Torch ops/custom classes and smallc10::IValueshims. Focus on signatures, object/tensor lifetimes, deferred hooks, stream behavior, and generated operator namespaces. The recipe guards the corresponding small edits to upstream C++._C.pypreserves the upstream Python API;_runtime.pydefers dependency checks/JIT initialization until buffer construction;_nccl_symbols.pyexposes PyTorch's already-loaded NCCL symbols before loading the extension. The extension neither links nor ships a separate NCCL library. Runtime JIT uses headers matching the runtime NCCL installation.kernels.get_kernel. The shared checker now supplies pinned vendor checkouts to the runner; its regression tests cover vendor checkouts, checkout reuse, and stale/extra generated files.The remaining overlays hold the Hub card.
NCCL comes from kernel-builder (huggingface/kernels#951): every CUDA kernel builds against the NCCL of its Torch. DeepEP 2.5's host code uses GIN and EFA-GDA APIs from NCCL 2.31/2.32, while kernel-builder's Torch ships 2.30.7, so
flake.nixpasses an overlay that builds NCCL 2.32.3 withmkNccl(from thenvidia-nccl-cu13wheels) and gives it to Torch. Those wheel headers are byte-identical to the 69 headers the recipe used to assemble from NCCL's Git sources, so the extension compiles the same code. The overlay can go once kernel-builder's Torch ships NCCL >= 2.32.Regeneration instructions pin the experimental kernel-port runner and explain how to reproduce/check the tree. Future port changes should update the recipe/overlays and regenerate the output together. Normal builds use the checked-in generated files.
This PR retains version-specific Torch builds and the original Torch ops/custom-class bindings. There is no Torch stable-ABI conversion or DLPack adapter in the recipe. Adding the recipe leaves implementation sources byte-for-byte unchanged and preserves the existing build settings; the generated manifest's formatting/order changes. Stable ABI remains separate follow-up work requiring performance validation. DeepEP's runtime JIT also means the ahead-of-time GPU build optimization discussed in #999 does not apply here.
Runtime requirements
CARD.md.nvcc) and a C++20 host compiler at runtime. The prebuilt host extension has a CUDA 13.0 build minimum.Requirements are checked at first buffer construction with actionable errors. Compiled GPU kernels are cached by DeepJIT.
Validation
build.toml, deleted headers,port-provenance.jsonrecomputed with kernel-port's hashing) were made by hand, without nix; the 125 CI-script tests pass. The build and the reproduction check run in CI on this commit; the GPU results below predate it.Checklist