Skip to content

Migrate DeepGEMM off of pybind11 and on to TORCH_LIBRARY - #15

Merged
Harry-Chen merged 13 commits into
vllm-project:devfrom
cleonard530:migrate_pybind_to_torch_library_dev
Oct 6, 2026
Merged

Harry-Chen merged 13 commits into
vllm-project:devfrom
cleonard530:migrate_pybind_to_torch_library_dev

Conversation

@cleonard530

@cleonard530 cleonard530 commented Sep 17, 2026 •

Copy link
Copy Markdown

Summary

Migrates DeepGEMM's C++ extension from pybind11 (PYBIND11_MODULE + per-header register_apis) to TORCH_LIBRARY / TORCH_LIBRARY_IMPL registration. Ops are now registered as torch.ops.deep_gemm.*.

To preserve the existing Python API (import deep_gemm; deep_gemm._C.fp8_gemm_nt(...)), a new deep_gemm/_C.py shim loads the extension and re-exports the legacy surface. The compiled module is renamed to deep_gemm._C_extension.

Updates .pyi stub generation to read TORCH_LIBRARY schemas from csrc/ and overlay defaults/types from _C.py.

JitRuntimeHandle in csrc/python_api.cpp preserves the existing _C.get_jit() API through TORCH_LIBRARY. It wraps the existing DeepJIT runtime’s shared pointer in a torch::CustomClassHolder, allowing the dispatcher to return it as a registered custom class. It does not create a new runtime or change kernel execution.

ABI3 note: This migration does not yet enable the stable CPython ABI (abi3) because DeepJIT still uses pybind11 to release the GIL. Since calls through torch.ops already release the GIL, DeepJIT’s GIL handling can be disabled for this integration, removing the remaining pybind11 dependency and allowing abi3 packaging to be restored.

working around the non ABI3 stable CPython was becomming difficult upstream on vllm, so I updated the build to use CPython stable abi. This added a preprocessor macro defined DJ_DISABLE_GIL which is used on my DeepJIT branch deepseek-ai/DeepJIT#11 to not pull in pybind11 and disables the gil release.

WARNING:

Before, get_symm_buffer_size_for_mega_moe returned an int and a function (std::tuple<int64_t, std::function...>) but TORCH_LIBRARY doesn't allow returning function objects/closures. Now, it returns an int and the computed buffer layout as an int[] (std::tuple<int64_t, c10::List<int64_t>>), and a separate function, _slice_symm_buffer_for_mega_moe, consumes that int[] instead of the closure. The _C.py wrapper reconstructs the callback, preserving the existing get_symm_buffer_size_for_mega_moe API and backwards compatibility.

New files

deep_gemm/_C.py

Python compatibility shim for the old deep_gemm._C module. Responsibilities:

torch.ops.load_library(...) on _C_extension*.so at import time
Re-export torch.ops.deep_gemm.* under the legacy _C names
Restore pybind-era conveniences that are not expressible in TORCH_LIBRARY schemas alone: tuple unpacking (q/kv/(weight, scale) pairs), default arguments.
Previously _C was the compiled .so itself; now _C is pure Python and the .so is _C_extension.

csrc/torch_library_utils.hpp

(namespace deep_gemm::torch_utils) Conversion helpers for bridging PyTorch schema types to the C++ types the existing kernel implementations expect — e.g. c10::List<int64_t> → std::tuple<int,int,int> for recipe/head_splits, and optional list → std::vector<int>. Without this, every TORCH_LIBRARY wrapper would duplicate the same list/tuple parsing logic.

csrc/utils/registration.h

Defines the REGISTER_EXTENSION(NAME) macro used by csrc/python_api.cpp. Under TORCH_LIBRARY, ops self-register via static initializers when the .so loads, so there's no real pybind11 module to build — this macro just emits an empty PyInit_ function so the compiled .so still satisfies Python's import machinery (which requires that symbol to exist).

Significantly changed files

csrc/apis/mega_moe.hpp

  • Added build_symm_buffer_layout, which centralizes the layout calculation and returns the offsets and dimensions needed to construct every symmetric-buffer view. get_symm_buffer_size_for_mega_moe serializes this layout through to_int_list, while the Mega MoE kernel entry points deserialize it and slice the buffer through the shared C++ helper without dispatching through another registered operator. This preserves the original centralized layout calculation, including the num_sms-dependent ring sizing.
  • Under pybind, get_symm_buffer_size_for_mega_moe returned an int64_t and a C++ closure. Because TORCH_LIBRARY schemas cannot return closures, the registered operator now returns the byte count and serialized layout as an int[]. _slice_symm_buffer_for_mega_moe(Tensor buffer, int[] layout_info) reconstructs the views. The compatibility wrapper in _C.py captures that layout in a Python closure, preserving the original public API. The slicing operator remains an internal implementation detail and is excluded from _C.__all__.

csrc/python_api.cpp

Replaces the old PYBIND11_MODULE(...) entry point. Each API header declares its schemas with TORCH_LIBRARY_FRAGMENT and registers implementations with TORCH_LIBRARY_IMPL; including those headers in this file instantiates their static registrations when the shared library loads. This file also owns the primary TORCH_LIBRARY(deep_gemm, m) block, which registers the Runtime custom class and get_jit (see comment below about python binding difference). Finally, REGISTER_EXTENSION(_C_extension) defines the minimal PyInit__C_extension function needed to import the shared library as a Python extension. The Python C API is used only to create that empty module; operators and classes are registered through PyTorch’s library API.

scripts/generate_pyi.py

Previously generated .pyi type hints by regex-parsing the C++ function declarations in csrc/ — but under pybind, the real Python-callable keyword names come from py::arg("...") strings in PYBIND11_MODULE, which pybind doesn't require to match the C++ parameter names. That divergence caused real bugs: e.g. fp8_fp4_paged_mqa_logits's C++ parameter is named fused_kv_cache, but it's bound as py::arg("kv_cache"), so the old generated stub advertised a keyword (fused_kv_cache) that would actually raise TypeError if used — the true kwarg is kv_cache. The script now parses TORCH_LIBRARY schema strings from csrc/ and overlays defaults from an AST parse of deep_gemm/_C.py, so the stub is generated from the real registration contract and entry point instead of an incidental C++ signature.

Test plan/Results

  pytest tests/test_bf16.py
  pytest tests/test_fp8_fp4.py
  pytest tests/test_layout.py
  pytest tests/test_einsum.py
  pytest tests/test_attention.py
  pytest tests/test_hyperconnection.py
  pytest tests/test_legacy.py
  pytest test_torch_library.py
  pytest tests/test_coverage_gaps.py  # (local test that fills in the kernel gaps)

All test pass

Test that require Blackwell GPUs have not been tested yet. These include

test_mega_gate.py
test_mega_mhc.py
test_mega_moe.py
All six test_sm120_*.py files

and a few test in the other files.

test_layout.py::test_k_grouped_sf_layout_kernels
test_layout.py::test_k_grouped_psum_sf_layout_kernels
test_einsum.py::test_fp8_bhd_hdr_bhr
test_einsum.py::test_fp8_bhd_bhr_hdr
test_attention.py::test_sparse_mqa_logits

Migration progress using the Audit Python extension torch-abi-audit:

-- extensions --
    [UNSTABLE] [abi3-ok               ] deep_gemm/_C_extension.abi3.so  (stable_shim=0, unstable=97)

This shows that the library is now on the CPython Stable ABI (ABI3)

Performance Benchmarks

Link to performance benchmarks: cleonard530/Benchmarks#1
The tldr; most gemm kernels stayed the same except the fp8_fp4_gemm_{nn,nt,tn,tt} kernels which had a 10-20% runtime increase (2-6 µs/call). I ran another workload with them in a vLLM-like workload. In eager mode the regression was about the same, but it disappeared with the CUDA-graph run. the runtime increase in fp8_fp4_gemm_{nn,nt,tt} went away when the data element_size was cached. fp8_fp4_gemm_tn still has a slight runtime regression.

Updated DeepJIT to use their current commit on main (see deepseek-ai/DeepJIT@3732a3b) so that we can get on CPython stable ABI.

… branch. Will need to figure out how to handle DeepJIT.

Signed-off-by: Chris Leonard <chleonar@redhat.com>

Co-authored-by: codex
…nd for GIL release. We will need to come back and address this later

Signed-off-by: Chris Leonard <chleonar@redhat.com>

Co-authored-by: codex
…eplacing _C with _C_extension

Signed-off-by: Chris Leonard <chleonar@redhat.com>

Co-authored-by: codex
…hon_api.cpp like it was before, added some checks to mega_moe.hpp to keep the new registration inline with the pybind registration.

Signed-off-by: Chris Leonard <chleonar@redhat.com>

Co-authored-by: codex
Signed-off-by: Chris Leonard <chleonar@redhat.com>

Co-authored-by: codex
Signed-off-by: Chris Leonard <chleonar@redhat.com>

Co-authored-by: codex
…hpp to disable to gil.hpp in DeepJIT, this is just temporary and will need to be changed upstream

Signed-off-by: Chris Leonard <chleonar@redhat.com>

Co-authored-by: codex
…of doing it itslef (requires changes to DeepJIT)

Signed-off-by: Chris Leonard <chleonar@redhat.com>

Co-authored-by: codex
Co-authored-by: codex
Signed-off-by: Chris Leonard <chleonar@redhat.com>
…mpatibility facade.

Co-authored-by: codex
Signed-off-by: Chris Leonard <chleonar@redhat.com>
@cleonard530
cleonard530 force-pushed the migrate_pybind_to_torch_library_dev branch from 27cc188 to 496fd0b Compare September 28, 2026 23:15
Signed-off-by: Chris Leonard <chleonar@redhat.com>

Co-authored-by: codex
Comment thread csrc/python_api.cpp
Comment on lines +23 to +27
// Preserve the opaque runtime handle exposed by DeepJIT's former Python binding.
struct JitRuntimeHandle : torch::CustomClassHolder {
std::shared_ptr<deep_jit::Runtime<deep_jit::CUDA>> value;
JitRuntimeHandle() : value(jit.get()) {}
};

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DeepJIT's Python binding exposed no Runtime methods, so this opaque handle preserves the old get_jit() API's observable behavior, but does not preserve custom-class identity.

Co-authored-by: codex
Signed-off-by: Chris Leonard <chleonar@redhat.com>
…setup.py

Co-authored-by: codex
Signed-off-by: Chris Leonard <chleonar@redhat.com>
@cleonard530
cleonard530 force-pushed the migrate_pybind_to_torch_library_dev branch from 9560d13 to 45d9a65 Compare September 30, 2026 12:27
@cleonard530
cleonard530 marked this pull request as ready for review September 30, 2026 12:30
@Harry-Chen
Harry-Chen merged commit 1e1842a into vllm-project:dev Oct 6, 2026
1 check passed
@wawerekxxx-debug

Copy link
Copy Markdown

zyongye pushed a commit that referenced this pull request Oct 10, 2026
Register every op through TORCH_LIBRARY instead of pybind11, and keep
the existing Python API through a deep_gemm._C facade that loads the
_C_extension library and forwards to torch.ops.deep_gemm. Parameter
names match the former pybind registrations, and the NVFP4 MegaMoE API
moves over too.

Build the extension against the CPython limited API (cp310-abi3, Python
>= 3.10), drop the pybind11 and torch_python dependencies and the
private cxx11 ABI query from CMake and setup.py, and disable DeepJIT's
own GIL release with DJ_DISABLE_GIL. DeepJIT is bumped to its stable ABI
integration.

Squashed from:
- 29597d6 migrating DeepGEMM off of pybind and onto TORCH_LIBRARY
- 2884a57 removed the stable CPython dependency because DeepJIT still uses pybind for GIL release
- b5dbde8 moved CMakeLists.txt to be back inline with the original file
- 3d47b20 cleaned up some of the migration
- 6aa3313 Added explicit methods to _C.py __all__ list
- ca99616 changed registered parameter name to match before when it was on pybind
- 2c8001e changed build to use CPython stable abi
- 3d4041f updated DeepGEMM to use flag to disable DeepJIT's gil release
- c7c9780 updated the newly added nvfp4 API to use torch_library instead of pybind
- fa21ec9 Avoid MegaMoE layout serialization round trips and document the _C compatibility facade
- 056a881 added min python version 3.10 to setup.py
- 364da78 Update DeepJIT stable ABI integration
- 45d9a65 removed pybind and unstable cxx_abi references in CMakeLists.txt and setup.py

Co-Authored-By: Claude Opus 5.5 <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.

3 participants