Migrate DeepGEMM from pybind11 to TORCH_LIBRARY op registration - #2
cleonard530 wants to merge 29 commits into
Conversation
| py::arg("max_context_len"), py::arg("clean_logits") = false, | ||
| py::arg("indices") = std::nullopt); | ||
| static void fp8_gemm_nt_skip_head_mid( | ||
| const torch::Tensor& a, const torch::Tensor& sfa, |
There was a problem hiding this comment.
sfa and sfb are here because TORCH_LIBRARY schemas can’t express pybind-style (tensor, scale) pairs. They need flat tensor arguments.
This shows up a lot in this migration.
| mma_type, activation, | ||
| num_ring_tokens, | ||
| ) | ||
| slice_input_buffers = lambda buffer: _C.slice_symm_buffer_for_mega_moe( |
There was a problem hiding this comment.
TORCH_LIBRARY cannot return a callback like pybind11 can, so a lamda function on the python side is created.
| #if DG_FP8_COMPATIBLE and DG_TENSORMAP_COMPATIBLE | ||
| static void tf32_hc_prenorm_gemm(const torch::Tensor& a, const torch::Tensor& b, | ||
| const torch::Tensor& d, const torch::Tensor& sqr_sum, | ||
| const c10::optional<int64_t>& num_splits) { |
There was a problem hiding this comment.
Wrapper is needed since TORCH_LIBRARY int? maps to optional<int64_t>, not optional<int>
| // re-export ATen types. DeepGEMM csrc uses torch::Tensor throughout; under | ||
| // Py_LIMITED_API we cannot include torch/python.h or torch/types.h (autograd | ||
| // pulls the full Python C-API). Re-export at:: into torch:: instead. | ||
| namespace torch { |
There was a problem hiding this comment.
Allows us to use torch::... instead of at::...
There was a problem hiding this comment.
Look into another header (e.g torch/all.h) instead of this wrapper
|
|
||
| } // namespace deep_gemm::torch_registration | ||
|
|
||
| #define DEEP_GEMM_IMPL(fn) TORCH_FN(deep_gemm::torch_registration::fn) |
There was a problem hiding this comment.
Defines a shorthand for binding adapter functions in deep_gemm::torch_registration
| @@ -0,0 +1,64 @@ | |||
| #pragma once | |||
There was a problem hiding this comment.
Helper functions to turn c10::List<int64_t> (TORCH_LIBRARY allowed type) to std::tuples<int,...> (existing kernel APIs) are defined in this helper file.
| @@ -1,6 +1,5 @@ | |||
| import os | |||
| import subprocess | |||
| import torch | |||
| @@ -0,0 +1,353 @@ | |||
| import torch | |||
There was a problem hiding this comment.
This file loads the compiled _C_extension*.so via torch.ops.load_library, wraps torch.ops.deep_gemm with Python shims that preserve the old API (tuple unpacking, guarded exports), and re-exports those ops as the deep_gemm._C module.
This keeps import _C consistency with legacy code
| from pathlib import Path | ||
|
|
||
|
|
||
| def build_cpp_function_index(root_path): |
There was a problem hiding this comment.
This file used to build the .pyi file by parsing the C++ functions. Now it used the TORCH_LIBRARY schema and _C.py (for default values) to build the .pyi file with type hints.
There was a problem hiding this comment.
The differemce in the generated file can be seen here: 02ed6bc
| extensions = {'.hpp', '.cpp', '.h', '.cc'} | ||
|
|
||
| # Regex: match m.def( ... ), supports multi-line | ||
| pattern = re.compile(r'm\.def\s*\(') |
| line = lines[i] | ||
| if 'm.def(' in line: | ||
| # Found a potential starting line | ||
| start_i = i |
| break | ||
| j += 1 | ||
| else: | ||
| pass |
There was a problem hiding this comment.
Doesn't do anything in this while loop
| cpp_results.append(cpp_item) | ||
|
|
||
| pyi_content = generate_pyi_file_content(cpp_results, module_name=name) | ||
| wrapper_defaults = {} |
There was a problem hiding this comment.
Get default arguments from _C.py
| const auto num_ranks = static_cast<int>(sym_buffer_ptrs.size()); | ||
| const auto num_experts_ = num_experts_per_rank * num_ranks; | ||
| const auto [num_required_bytes, slice] = get_symm_buffer_size_for_mega_moe( | ||
| const auto layout_info = build_symm_buffer_layout( |
There was a problem hiding this comment.
fp8_fp4_mega_moe and bf16_mega_moe now use the internal build_symm_buffer_layout to get the num_required_bytes and slice (through slice_symm_buffer_from_layout) instead of the registered torch ops get_symm_buffer_size_for_mega_moe, which used to return a callback but is unable to with TORCH_LIBRARY (it was allowed on pybind). This op is still registered and used in deep_gemm/mega/__init__.py as well as slice_symm_buffer_for_mega_moe (to replace the callback) which is a small wrapper around slice_symm_buffer_from_layout.
| import torch | ||
| from pathlib import Path | ||
|
|
||
| _SCALAR_TYPE = { |
There was a problem hiding this comment.
Need to remove this and use dtype
|
|
||
| #include "utils/exception.hpp" | ||
|
|
||
| namespace deep_gemm::torch_library_utils { |
There was a problem hiding this comment.
make shorter (e.g torch_utils)
441c417 to
eb4f3c9
Compare
61073b9 to
90d0764
Compare
Add SM90 FP8 MegaMoE support Co-authored-by: Jinyan Chen <jinyanc@nvidia.com>
Signed-off-by: Chris Leonard <chleonar@redhat.com>
… match what was in the legacy code Signed-off-by: Chris Leonard <chleonar@redhat.com>
…helper code from generate_pyi.py Signed-off-by: Chris Leonard <chleonar@redhat.com>
…updates so I updated it to inlcude the type hints Signed-off-by: Chris Leonard <chleonar@redhat.com>
Signed-off-by: Chris Leonard <chleonar@redhat.com>
…ybind default arguments are no longer there Signed-off-by: Chris Leonard <chleonar@redhat.com>
Signed-off-by: Chris Leonard <chleonar@redhat.com>
…ther minor formatting issues. Also added comments with examples to help describe each step. These will be removed (as well as main()) in a followup commit, but wanted them here for reference Signed-off-by: Chris Leonard <chleonar@redhat.com>
Signed-off-by: Chris Leonard <chleonar@redhat.com>
Signed-off-by: Chris Leonard <chleonar@redhat.com>
Signed-off-by: Chris Leonard <chleonar@redhat.com>
…TORCH_LIBRARY; deep_gemm/_C.py can keep using torch.ops.load_library. Signed-off-by: Chris Leonard <chleonar@redhat.com>
… function that was leftover from migration, replaces torch_compat with torch/all.h, and updated the sm120 files to use torch/all.h instead of torch/python.h
…_library_macros - Drop torch_library_macros.hpp; register ops via TORCH_FN directly and rename torch_library_utils namespace to torch_utils. - Use at::ScalarType/torch.dtype for logits_dtype instead of int + _SCALAR_TYPE dict. - Align schema/wrapper param names with C++ impl (fused_kv_cache, activation_clamp_opt, *_tuple/*_tuple_opt), propagated to _C.py, mega/__init__.py, and tests. - Prefix slice_symm_buffer_for_mega_moe with _ to mark it private. Signed-off-by: Chris Leonard <chleonar@redhat.com>
Signed-off-by: Chris Leonard <chleonar@redhat.com>
…o align it with the C++ names Signed-off-by: Chris Leonard <chleonar@redhat.com>
fp8_fp4_mega_moe and bf16_mega_moe both write into this tensor via a device-side red.add reduction (accumulating per-expert recv counts across calls), but the TORCH_LIBRARY schema declared it as immutable (Tensor?). Fix by annotating it Tensor(cumulative_local_expert_recv_stats!)?. Signed-off-by: Chris Leonard <chleonar@redhat.com>
…filenames on release uploads. Bump the floor to cp310, which every torch version in the matrix (2.4-2.8) already supports. Signed-off-by: Chris Leonard <chleonar@redhat.com>
…as. These were declared optional but the C++ impl always called unconditionally, so would have crashed anyway; the Python wrappers already default to a concrete tuple. Signed-off-by: Chris Leonard <chleonar@redhat.com>
…n by having get_symm_buffer_size_for_mega_moe return the computed layout as an int[] alongside num_bytes, which _slice_symm_buffer_for_mega_moe now reuses instead of rederiving it (and its num_sms-dependent ring sizing) from the original args a second time. Signed-off-by: Chris Leonard <chleonar@redhat.com>
…params like recipe/head_splits, so the schema itself is the source of truth for stub generation instead of a hand-maintained per-name promotion table in generate_pyi.py. Add an _as_int_list wrapper helper so int[N]'s scalar-broadcasting behavior can't silently convert a bad scalar argument into a repeated list. Signed-off-by: Chris Leonard <chleonar@redhat.com>
…le-factor pairs like q/q_sf, replacing the special-cased _apply_q_qsf_merge, and sort the csrc/ file scan so op ordering in the generated stub no longer shuffles across machines/checkouts. Also drop sanitize_param_name, since it could only keep the .pyi syntactically valid and not guarantee the stub's keyword name actually matches the real wrapper at runtime. Signed-off-by: Chris Leonard <chleonar@redhat.com>
…atter for this migration but it makes the migration to torch abi stable easier Signed-off-by: Chris Leonard <chleonar@redhat.com>
…of silently dropping the whole group, so a future C++ #if drift between grouped ops fails loudly at import with the exact op names involved rather than quietly vanishing from the API. Also document which file each op in the einsum/tf32_hc_prenorm_gemm/get_paged_mqa_logits_metadata group comes from, since they're only bundled by coincidence of a shared guard today. Signed-off-by: Chris Leonard <chleonar@redhat.com>
- serialize NVFP4-specific symmetric buffer layout fields - register fp4_fp4_mega_moe and preserve SiTU parameters - add matching Python operator wrappers Signed-off-by: Chris Leonard <chleonar@redhat.com>
…jit_kernels/impls/sm100_fp4_fp4_mega_moe.hpp file Signed-off-by: Chris Leonard <chleonar@redhat.com>
Signed-off-by: Chris Leonard <chleonar@redhat.com>
90d0764 to
343826c
Compare
… type is explicit so the make_tuple was unnessesary, and this keeps it consistent. with sm90_mega Signed-off-by: Chris Leonard <chleonar@redhat.com>
Summary
Migrates DeepGEMM's C++ extension from pybind11 (
PYBIND11_MODULE+ per-headerregister_apis) toTORCH_LIBRARY/TORCH_LIBRARY_IMPLregistration. Ops are now registered astorch.ops.deep_gemm.*.To preserve the existing Python API (
import deep_gemm; deep_gemm._C.fp8_gemm_nt(...)), a newdeep_gemm/_C.pyshim loads the extension and re-exports the legacy surface. The compiled module is renamed todeep_gemm._C_extensionand built as a limited-API (abi3) extension.Also updates
.pyistub generation to readTORCH_LIBRARYschemas fromcsrc/and overlay defaults/types from_C.py, and splits mega MoE symm-buffer sizing/slicing into two separate ops (replacing the old pybind callback return value).WARNING:
Before,
get_symm_buffer_size_for_mega_moereturned anintand afunction(std::tuple<int64_t, std::function...>) butTORCH_LIBRARYdoesn't allow returning function objects/closures. Now, it just returns anint, and a separate function,_slice_symm_buffer_for_mega_moe, is used instead of the closure. This breaks backwards compatibility forget_symm_buffer_size_for_mega_moe, but this method is not re-exported fromdeep_gemm/__init__.py(only used internally bySymmBuffer.__init__), and a quick look on GitHub shows no other public repo calls this function directly either.New files
deep_gemm/_C.py
Python compatibility shim for the old
deep_gemm._Cmodule. Responsibilities:torch.ops.load_library(...)on_C_extension*.soat import timeRe-export
torch.ops.deep_gemm.*under the legacy_CnamesRestore pybind-era conveniences that are not expressible in
TORCH_LIBRARYschemas alone: tuple unpacking (q/kv/(weight, scale)pairs), default arguments, and guarded op binding for#if DG_*_COMPATIBLEbuild variantsPreviously
_Cwas the compiled.soitself; now_Cis pure Python and the.sois_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>forrecipe/head_splits, and optional list →std::vector<int>. Without this, everyTORCH_LIBRARYwrapper would duplicate the same list/tuple parsing logic.Significantly changed files
csrc/apis/mega.hpp
build_symm_buffer_layout, which computes the fullSymmBufferLayoutInfo(every sub-buffer's offset/size) once.get_symm_buffer_size_for_mega_moe,slice_symm_buffer_for_mega_moe, andfp8_fp4_mega_moe/bf16_mega_moeall call it instead of duplicating layout math — the kernel entry points build the layout and slice the buffer directly rather than going through a second registered op.get_symm_buffer_size_for_mega_moeused to return (int64_t,std::function<...>) under pybind;TORCH_LIBRARYcan't return closures. It now returns just the int, and slicing moved to a separate op,_slice_symm_buffer_for_mega_moe(leading underscore since it's an internal implementation detail — onlydeep_gemm/mega/__init__.py'sSymmBuffercalls it, it isn't part of the public API).csrc/python_api.cpp
Replaces the old
PYBIND11_MODULE(...)entry point. Ops now self-register viaTORCH_LIBRARY_FRAGMENT/STABLE_TORCH_LIBRARY_IMPLstatic initializers inside eachcsrc/apis/*.hpp, which run automatically when the.sois loaded — so this file no longer needs to call per-headerregister_apis(m)functions. It just#includeseveryapis/*.hpp(to trigger their static registration) and defines an emptyPyInit_symbol via theREGISTER_EXTENSIONmacro, purely so the compiled.sois still a valid, importable Python extension module — no actual bindings go through the Python C API anymore.scripts/generate_pyi.py
Previously generated
.pyitype hints by regex-parsing the C++ function declarations incsrc/— but under pybind, the real Python-callable keyword names come frompy::arg("...")strings inPYBIND11_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 namedfused_kv_cache, but it's bound aspy::arg("kv_cache"), so the old generated stub advertised a keyword (fused_kv_cache) that would actually raiseTypeErrorif used — the true kwarg iskv_cache. The script now parses TORCH_LIBRARY schema strings fromcsrc/and overlays defaults from an AST parse ofdeep_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
smoke_test_all_ops.py, that performs a minimal call to every op and verifies that any tensor inputs expected to be immutable remain bit-for-bit identical before and after the call.Checkout commit 4cb88be to see the
smoke_test_all_ops.pyscript that exercises every TORCH_LIBRARY op. I added the results in the comment at the bottom of the commit (they all passed or were skipped because I didn't have the right SM).I also compared the generated .pyi file before (using C++ signatures directly) and after (using TORCH_LIBRARY schemas) to make sure types are mapped appropriately. The differences can be found here Gen pyi test #5 .
All test pass!