Skip to content

Commit

Permalink
Switch jaxlib to use nanobind instead of pybind11.
Browse files Browse the repository at this point in the history
nanobind has a number of advantages (https://nanobind.readthedocs.io/en/latest/why.html), notably speed of compilation and dispatch, but the main reason to do this for these bindings is because nanobind can target the Python Stable ABI starting with Python 3.12. This means that we will not need to ship per-Python version CUDA plugins starting with Python 3.12.

PiperOrigin-RevId: 534535677
  • Loading branch information
hawkinsp authored and jax authors committed Aug 24, 2023
1 parent 55079a3 commit 9965dd4
Show file tree
Hide file tree
Showing 23 changed files with 620 additions and 251 deletions.
8 changes: 7 additions & 1 deletion WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ xla_workspace1()
load("@xla//:workspace0.bzl", "xla_workspace0")
xla_workspace0()


load("//third_party/flatbuffers:workspace.bzl", flatbuffers = "repo")
flatbuffers()

load("//third_party/robin_map:workspace.bzl", robin_map = "repo")
robin_map()

load("//third_party/nanobind:workspace.bzl", nanobind = "repo")
nanobind()

4 changes: 2 additions & 2 deletions examples/jax_cpp/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ cc_binary(
"@xla//xla/pjrt:tfrt_cpu_pjrt_client",
"@xla//xla/service:hlo_proto_cc",
"@xla//xla/tools:hlo_module_loader",
"@tsl///platform:logging",
"@tsl///platform:platform_port",
"@tsl//tsl/platform:logging",
"@tsl//tsl/platform:platform_port",
],
)
23 changes: 19 additions & 4 deletions jaxlib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,32 @@ exports_files([
])

cc_library(
name = "kernel_pybind11_helpers",
hdrs = ["kernel_pybind11_helpers.h"],
name = "absl_status_casters",
hdrs = ["absl_status_casters.h"],
copts = [
"-fexceptions",
"-fno-strict-aliasing",
],
features = ["-use_header_modules"],
deps = [
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
],
)

cc_library(
name = "kernel_nanobind_helpers",
hdrs = ["kernel_nanobind_helpers.h"],
copts = [
"-fexceptions",
"-fno-strict-aliasing",
],
features = ["-use_header_modules"],
deps = [
":kernel_helpers",
"@tsl//tsl/python/lib/core:numpy",
"@com_google_absl//absl/base",
"@pybind11",
"@nanobind",
],
)

Expand Down Expand Up @@ -163,7 +178,7 @@ pybind_extension(
"@xla//third_party/python_runtime:headers",
"@com_google_absl//absl/cleanup",
"@com_google_absl//absl/container:inlined_vector",
"@pybind11",
"@nanobind",
],
)

Expand Down
218 changes: 218 additions & 0 deletions jaxlib/absl_status_casters.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/* Copyright 2023 The JAX Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

#ifndef JAXLIB_ABSL_STATUS_CASTERS_H_
#define JAXLIB_ABSL_STATUS_CASTERS_H_

#include <stdexcept>

#include "absl/status/status.h"
#include "absl/status/statusor.h"

namespace jax {

// C++ -> Python caster helpers.
//
// Failing statuses become Python exceptions; OK Status() becomes None.
//
// Given there can be only a single global pybind11 type_caster for the
// `absl::Status` type, and given XLA wants a custom exception being raised,
// we use a dedicated helper to implement this feature without relying on a
// global `type_caster`.
//
// For example:
//
// - Functions without arguments:
// m.def("my_func", []() { ThrowIfError(MyFunc()); }
// - Classes with a single argument:
// py_class.def("delete", [](Buffer& self) {
// ThrowIfError(self.Delete());
// }
//
// For functions with more arguments, you can either inline the arguments,
// or use the `ThrowIfErrorWrapper` wrapper defined below:
//
// m.def("my_func", ThrowIfErrorWrapper(MyFunc));
//
// Nonstatic member functions can be wrapped by passing a
// pointer-to-member-function:
// ThrowIfErrorWrapper(&MyClass::MyMethod)

inline void ThrowIfError(absl::Status src) {
if (!src.ok()) {
throw std::runtime_error(src.ToString());
}
}

// If one does not want to have to define a lambda specifying the inputs
// arguments, on can use the `ThrowIfErrorWrapper` wrapper.
//
// There are three specializations:
// - For free functions, `Sig` is the function type and `F` is `Sig&`.
// - For callable types, `Sig` is the pointer to member function type
// and `F` is the type of the callable.
// - For a nonstatic member function of a class `C`, `Sig` is the function type
// and `F` is Sig C::*.
//
// In the first two cases, the wrapper returns a callable with signature `Sig`;
// in the third case, the wrapper returns callable with a modified signature
// that takes a C instance as the first argument.
template <typename Sig, typename F>
struct ThrowIfErrorWrapper;

// C++17 "deduction guide" that guides class template argument deduction (CTAD)
// For free functions.
template <typename F>
ThrowIfErrorWrapper(F) -> ThrowIfErrorWrapper<decltype(&F::operator()), F>;

// For callable types (with operator()).
template <typename... Args>
ThrowIfErrorWrapper(absl::Status (&)(Args...))
-> ThrowIfErrorWrapper<absl::Status(Args...), absl::Status (&)(Args...)>;

// For unbound nonstatic member functions.
template <typename C, typename... Args>
ThrowIfErrorWrapper(absl::Status (C::*)(Args...))
-> ThrowIfErrorWrapper<absl::Status(Args...), C>;

// Template specializations.

// For free functions.
template <typename... Args>
struct ThrowIfErrorWrapper<absl::Status(Args...), absl::Status (&)(Args...)> {
explicit ThrowIfErrorWrapper(absl::Status (&f)(Args...)) : func(f) {}
void operator()(Args... args) {
ThrowIfError(func(std::forward<Args>(args)...));
}
absl::Status (&func)(Args...);
};

// For callable types (with operator()), non-const and const versions.
template <typename C, typename... Args, typename F>
struct ThrowIfErrorWrapper<absl::Status (C::*)(Args...), F> {
explicit ThrowIfErrorWrapper(F&& f) : func(std::move(f)) {}
void operator()(Args... args) {
ThrowIfError(func(std::forward<Args>(args)...));
}
F func;
};
template <typename C, typename... Args, typename F>
struct ThrowIfErrorWrapper<absl::Status (C::*)(Args...) const, F> {
explicit ThrowIfErrorWrapper(F&& f) : func(std::move(f)) {}
void operator()(Args... args) const {
ThrowIfError(func(std::forward<Args>(args)...));
}
F func;
};

// For unbound nonstatic member functions, non-const and const versions.
// `ptmf` stands for "pointer to member function".
template <typename C, typename... Args>
struct ThrowIfErrorWrapper<absl::Status(Args...), C> {
explicit ThrowIfErrorWrapper(absl::Status (C::*ptmf)(Args...)) : ptmf(ptmf) {}
void operator()(C& instance, Args... args) {
ThrowIfError((instance.*ptmf)(std::forward<Args>(args)...));
}
absl::Status (C::*ptmf)(Args...);
};
template <typename C, typename... Args>
struct ThrowIfErrorWrapper<absl::Status(Args...) const, C> {
explicit ThrowIfErrorWrapper(absl::Status (C::*ptmf)(Args...) const)
: ptmf(ptmf) {}
void operator()(const C& instance, Args... args) const {
ThrowIfError((instance.*ptmf)(std::forward<Args>(args)...));
}
absl::Status (C::*ptmf)(Args...) const;
};

// Utilities for `StatusOr`.
template <typename T>
T ValueOrThrow(absl::StatusOr<T> v) {
if (!v.ok()) {
throw std::runtime_error(v.status().ToString());
}
return std::move(v).value();
}

template <typename Sig, typename F>
struct ValueOrThrowWrapper;

template <typename F>
ValueOrThrowWrapper(F) -> ValueOrThrowWrapper<decltype(&F::operator()), F>;

template <typename R, typename... Args>
ValueOrThrowWrapper(absl::StatusOr<R> (&)(Args...))
-> ValueOrThrowWrapper<absl::StatusOr<R>(Args...),
absl::StatusOr<R> (&)(Args...)>;

template <typename C, typename R, typename... Args>
ValueOrThrowWrapper(absl::StatusOr<R> (C::*)(Args...))
-> ValueOrThrowWrapper<absl::StatusOr<R>(Args...), C>;

// Deduction guide for const methods.
template <typename C, typename R, typename... Args>
ValueOrThrowWrapper(absl::StatusOr<R> (C::*)(Args...) const)
-> ValueOrThrowWrapper<absl::StatusOr<R>(Args...) const, C>;

template <typename R, typename... Args>
struct ValueOrThrowWrapper<absl::StatusOr<R>(Args...),
absl::StatusOr<R> (&)(Args...)> {
explicit ValueOrThrowWrapper(absl::StatusOr<R> (&f)(Args...)) : func(f) {}
R operator()(Args... args) const {
return ValueOrThrow(func(std::forward<Args>(args)...));
}
absl::StatusOr<R> (&func)(Args...);
};
template <typename R, typename C, typename... Args, typename F>
struct ValueOrThrowWrapper<absl::StatusOr<R> (C::*)(Args...), F> {
explicit ValueOrThrowWrapper(F&& f) : func(std::move(f)) {}
R operator()(Args... args) const {
return ValueOrThrow(func(std::forward<Args>(args)...));
}
F func;
};
template <typename R, typename C, typename... Args, typename F>
struct ValueOrThrowWrapper<absl::StatusOr<R> (C::*)(Args...) const, F> {
explicit ValueOrThrowWrapper(F&& f) : func(std::move(f)) {}
R operator()(Args... args) const {
return ValueOrThrow(func(std::forward<Args>(args)...));
}
F func;
};

// For unbound nonstatic member functions, non-const and const versions.
// `ptmf` stands for "pointer to member function".
template <typename R, typename C, typename... Args>
struct ValueOrThrowWrapper<absl::StatusOr<R>(Args...), C> {
explicit ValueOrThrowWrapper(absl::StatusOr<R> (C::*ptmf)(Args...))
: ptmf(ptmf) {}
R operator()(C& instance, Args... args) {
return ValueOrThrow((instance.*ptmf)(std::forward<Args>(args)...));
}
absl::StatusOr<R> (C::*ptmf)(Args...);
};
template <typename R, typename C, typename... Args>
struct ValueOrThrowWrapper<absl::StatusOr<R>(Args...) const, C> {
explicit ValueOrThrowWrapper(absl::StatusOr<R> (C::*ptmf)(Args...) const)
: ptmf(ptmf) {}
R operator()(const C& instance, Args... args) const {
return ValueOrThrow((instance.*ptmf)(std::forward<Args>(args)...));
}
absl::StatusOr<R> (C::*ptmf)(Args...) const;
};

} // namespace jax

#endif // JAXLIB_ABSL_STATUS_CASTERS_H_
8 changes: 4 additions & 4 deletions jaxlib/cpu/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ pybind_extension(
module_name = "_lapack",
deps = [
":lapack_kernels",
"//jaxlib:kernel_pybind11_helpers",
"@pybind11",
"//jaxlib:kernel_nanobind_helpers",
"@nanobind",
],
)

Expand Down Expand Up @@ -95,9 +95,9 @@ pybind_extension(
deps = [
":ducc_fft_flatbuffers_cc",
":ducc_fft_kernels",
"//jaxlib:kernel_pybind11_helpers",
"//jaxlib:kernel_nanobind_helpers",
"@flatbuffers//:runtime_cc",
"@pybind11",
"@nanobind",
],
)

Expand Down
22 changes: 11 additions & 11 deletions jaxlib/cpu/ducc_fft.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ limitations under the License.
#include <complex>
#include <vector>

#include "pybind11/pybind11.h"
#include "pybind11/stl.h"
#include "nanobind/nanobind.h"
#include "nanobind/stl/vector.h"
#include "jaxlib/cpu/ducc_fft_generated.h"
#include "jaxlib/cpu/ducc_fft_kernels.h"
#include "jaxlib/kernel_pybind11_helpers.h"
#include "jaxlib/kernel_nanobind_helpers.h"

namespace py = pybind11;
namespace nb = nanobind;

namespace jax {
namespace {


py::bytes BuildDynamicDuccFftDescriptor(
nb::bytes BuildDynamicDuccFftDescriptor(
const uint32_t ndims,
bool is_double, int fft_type,
const std::vector<uint32_t> &axes,
Expand All @@ -42,24 +42,24 @@ py::bytes BuildDynamicDuccFftDescriptor(
descriptor.forward = forward;
flatbuffers::FlatBufferBuilder fbb;
fbb.Finish(DynamicDuccFftDescriptor::Pack(fbb, &descriptor));
return py::bytes(reinterpret_cast<char *>(fbb.GetBufferPointer()),
return nb::bytes(reinterpret_cast<char *>(fbb.GetBufferPointer()),
fbb.GetSize());
}

py::dict Registrations() {
pybind11::dict dict;
nb::dict Registrations() {
nb::dict dict;
// TODO(b/287702203): this must be kept until EOY 2023 for backwards
// of serialized functions using fft.
dict["ducc_fft"] = EncapsulateFunction(DuccFft);
dict["dynamic_ducc_fft"] = EncapsulateFunction(DynamicDuccFft);
return dict;
}

PYBIND11_MODULE(_ducc_fft, m) {
NB_MODULE(_ducc_fft, m) {
m.def("registrations", &Registrations);
m.def("dynamic_ducc_fft_descriptor", &BuildDynamicDuccFftDescriptor,
py::arg("ndims"), py::arg("is_double"), py::arg("fft_type"),
py::arg("axes"), py::arg("forward"));
nb::arg("ndims"), nb::arg("is_double"), nb::arg("fft_type"),
nb::arg("axes"), nb::arg("forward"));
}

} // namespace
Expand Down
Loading

0 comments on commit 9965dd4

Please sign in to comment.