Skip to content

Commit

Permalink
Replace pybind11 with nanobind in the OQC module (#1391)
Browse files Browse the repository at this point in the history
**Context:** OQC was still using pybind11, which made it incompatible
with the stable ABI.

**Description of the Change:** Replace pybind11 with nanobind in the OQC
module.

**Benefits:** Conforming with the stable ABI.
  • Loading branch information
rauletorresc authored Dec 19, 2024
1 parent 6ebfd70 commit 11e4e4f
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 21 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ wheel:
cp $(RT_BUILD_DIR)/lib/librt_capi.* $(MK_DIR)/frontend/catalyst/lib
cp $(RT_BUILD_DIR)/lib/backend/*.toml $(MK_DIR)/frontend/catalyst/lib/backend
cp $(OQC_BUILD_DIR)/librtd_oqc* $(MK_DIR)/frontend/catalyst/lib
cp $(OQC_BUILD_DIR)/oqc_python_module.so $(MK_DIR)/frontend/catalyst/lib
cp $(OQC_BUILD_DIR)/backend/*.toml $(MK_DIR)/frontend/catalyst/lib/backend
cp $(OQD_BUILD_DIR)/librtd_oqd* $(MK_DIR)/frontend/catalyst/lib
cp $(OQD_BUILD_DIR)/backend/*.toml $(MK_DIR)/frontend/catalyst/lib/backend
Expand Down
1 change: 1 addition & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* Replace pybind11 with nanobind for C++/Python bindings in the frontend and in the runtime.
[(#1173)](https://github.com/PennyLaneAI/catalyst/pull/1173)
[(#1293)](https://github.com/PennyLaneAI/catalyst/pull/1293)
[(#1391)](https://github.com/PennyLaneAI/catalyst/pull/1391)

Nanobind has been developed as a natural successor to the pybind11 library and offers a number of
[advantages](https://nanobind.readthedocs.io/en/latest/why.html#major-additions), in particular,
Expand Down
34 changes: 27 additions & 7 deletions frontend/catalyst/third_party/oqc/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,39 @@ set(oqc_backend_dir "${OQC_BUILD_DIR}/backend")
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Locate Python & PyBind11
# The optional component is only used for the C++ test suite (to spin up their its interpreter),
# nanobind suggests including these lines to configure CMake to perform an optimized release build
# by default unless another build type is specified. Without this addition, binding code may run
# slowly and produce large binaries.
# See https://nanobind.readthedocs.io/en/latest/building.html#preliminaries
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()

# Locate Python
# The optional component is only used for the C++ test suite (to spin up its own interpreter),
# and requires libpython.so to be available on the system.
find_package(Python REQUIRED
COMPONENTS Interpreter Development.Module
OPTIONAL_COMPONENTS Development.Embed
OPTIONAL_COMPONENTS Development.Embed Development.SABIModule
)

# Locate nanobind
execute_process(
COMMAND "${Python_EXECUTABLE}" -c "import pybind11; print(pybind11.get_cmake_dir())"
OUTPUT_VARIABLE pybind11_DIR OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND "${Python_EXECUTABLE}" -c "import nanobind; print(nanobind.cmake_dir())"
OUTPUT_VARIABLE nanobind_DIR OUTPUT_STRIP_TRAILING_WHITESPACE
)
find_package(pybind11 CONFIG REQUIRED)
find_package(nanobind CONFIG REQUIRED)

# Create the Python `oqc_python_module` module
# Target the stable ABI for Python 3.12+, which reduces the number of binary wheels that must be
# built (`STABLE_ABI` does nothing on older Python versions).
nanobind_add_module(oqc_python_module STABLE_ABI oqc_python_module.cpp)

# Use a consistant suffix ".so" rather than, e.g. ".abi3.so" (when using the Stable ABI) or
# ".cpython-3xx-darwin.so". Doing so simplifies the process to locate it when calling
# `dlopen(OQC_PY)` in frontend/catalyst/third_party/oqc/src/OQCRunner.hpp.
set_target_properties(oqc_python_module PROPERTIES SUFFIX ".so")

message(STATUS "Building the OQC device.")

Expand All @@ -39,7 +60,6 @@ set(OQC_LIBRARIES
set_target_properties(rtd_oqc PROPERTIES BUILD_RPATH "$ORIGIN/../utils")
target_link_directories(rtd_oqc PRIVATE ${runtime_lib})

pybind11_add_module(oqc_python_module oqc_python_module.cpp)
target_include_directories(oqc_python_module PRIVATE ${runtime_includes})

add_dependencies(rtd_oqc oqc_python_module)
Expand Down
33 changes: 21 additions & 12 deletions frontend/catalyst/third_party/oqc/src/oqc_python_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#include <pybind11/eval.h>
#include <pybind11/pybind11.h>
#include <string>
#include <vector>

#include <nanobind/eval.h>
#include <nanobind/nanobind.h>
#include <nanobind/stl/string.h>

#include "Exception.hpp"

Expand Down Expand Up @@ -46,28 +49,34 @@ except Exception as e:
[[gnu::visibility("default")]] void counts(const char *_circuit, const char *_device, size_t shots,
size_t num_qubits, const char *_kwargs, void *_vector)
{
namespace py = pybind11;
using namespace py::literals;
namespace nb = nanobind;
using namespace nb::literals;

py::gil_scoped_acquire lock;
nb::gil_scoped_acquire lock;

auto locals = py::dict("circuit"_a = _circuit, "device"_a = _device, "kwargs"_a = _kwargs,
"shots"_a = shots, "msg"_a = "");
nb::dict locals;
locals["circuit"] = _circuit;
locals["device"] = _device;
locals["kwargs"] = _kwargs;
locals["shots"] = shots;
locals["msg"] = "";

py::exec(program, py::globals(), locals);
// Evaluate in scope of main module
nb::object scope = nb::module_::import_("__main__").attr("__dict__");
nb::exec(nb::str(program.c_str()), scope, locals);

auto &&msg = locals["msg"].cast<std::string>();
auto msg = nb::cast<std::string>(locals["msg"]);
RT_FAIL_IF(!msg.empty(), msg.c_str());

py::dict results = locals["counts"];
nb::dict results = locals["counts"];

std::vector<size_t> *counts_value = reinterpret_cast<std::vector<size_t> *>(_vector);
for (auto item : results) {
auto key = item.first;
auto value = item.second;
counts_value->push_back(value.cast<size_t>());
counts_value->push_back(nb::cast<size_t>(value));
}
return;
}

PYBIND11_MODULE(oqc_python_module, m) { m.doc() = "oqc"; }
NB_MODULE(oqc_python_module, m) { m.doc() = "oqc"; }
7 changes: 7 additions & 0 deletions frontend/catalyst/third_party/oqc/src/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ target_include_directories(runner_tests_oqc PRIVATE

target_link_directories(runner_tests_oqc PRIVATE ${runtime_lib})

# Locate PyBind11
execute_process(
COMMAND "${Python_EXECUTABLE}" -c "import pybind11; print(pybind11.get_cmake_dir())"
OUTPUT_VARIABLE pybind11_DIR OUTPUT_STRIP_TRAILING_WHITESPACE
)
find_package(pybind11 CONFIG REQUIRED)

target_link_libraries(runner_tests_oqc PRIVATE
Catch2::Catch2
pybind11::embed
Expand Down
1 change: 1 addition & 0 deletions frontend/catalyst/utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ endif()
# Locate Python & nanobind
find_package(Python REQUIRED
COMPONENTS Interpreter Development.Module NumPy
OPTIONAL_COMPONENTS Development.SABIModule
)
execute_process(
COMMAND "${Python_EXECUTABLE}" -c "import nanobind; print(nanobind.cmake_dir())"
Expand Down
2 changes: 1 addition & 1 deletion runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ FetchContent_Declare(
# and requires libpython.so to be available on the system.
find_package(Python REQUIRED
COMPONENTS Interpreter Development.Module
OPTIONAL_COMPONENTS Development.Embed
OPTIONAL_COMPONENTS Development.Embed Development.SABIModule
)

if(RUNTIME_ENABLE_WARNINGS)
Expand Down
2 changes: 1 addition & 1 deletion runtime/lib/backend/openqasm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ execute_process(
)
find_package(nanobind CONFIG REQUIRED)

# Create the Python `catalyst_callback_registry` module
# Create the Python `openqasm_python_module` module
# Target the stable ABI for Python 3.12+, which reduces the number of binary wheels that must be
# built (`STABLE_ABI` does nothing on older Python versions).
nanobind_add_module(openqasm_python_module STABLE_ABI openqasm_python_module.cpp)
Expand Down

0 comments on commit 11e4e4f

Please sign in to comment.