Skip to content

Commit 75dc517

Browse files
[Runtime] Use GIL lock in the runtime instead of custom mutexes. (#624)
**Context:** We have a custom mutex that denotes current use of the Python interpreter (a shared resource among threads). This custom mutex may be replaced by the lock found in pybind11. **Description of the Change:** Use the scope acquire lock. **Benefits:** No custom mutex, better DX with third party libraries. (No need to determine location of the mutex library). **Possible Drawbacks:** **Related GitHub Issues:** TODO: - [ ] ~~add a python file and outline all strings that are input to exec~~ - [x] OQC device - [x] release gil when executing the memory transfer - [x] write functions and classes for dlopen dlclose --------- Co-authored-by: Joey Carter <[email protected]>
1 parent 317f3a0 commit 75dc517

27 files changed

+459
-464
lines changed

.github/workflows/check-catalyst.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,10 +638,11 @@ jobs:
638638
- name: Build Runtime test suite for OpenQasm device
639639
if: ${{ matrix.backend == 'openqasm' }}
640640
run: |
641+
# Asan prevents dlopen from working?
641642
C_COMPILER=$(which ${{ needs.constants.outputs[format('c_compiler.{0}', matrix.compiler)] }}) \
642643
CXX_COMPILER=$(which ${{ needs.constants.outputs[format('cxx_compiler.{0}', matrix.compiler)] }}) \
643644
COMPILER_LAUNCHER="" \
644-
ENABLE_ASAN=ON \
645+
ENABLE_ASAN=OFF \
645646
make test-runtime
646647
647648
- name: Build examples

.github/workflows/scripts/linux_arm64/rh8/build_catalyst.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ cmake --build runtime-build --target rt_capi rtd_lightning rtd_openqasm rtd_null
5656
# Build OQC
5757
export OQC_BUILD_DIR="/catalyst/oqc-build"
5858
export RT_BUILD_DIR="/catalyst/runtime-build"
59-
export USE_ALTERNATIVE_CATALYST_PYTHON_INTERPRETER=ON
6059
make oqc
6160

6261
# Build Catalyst dialects
@@ -80,6 +79,7 @@ cmake --build quantum-build --target check-dialects compiler_driver catalyst-cli
8079
# Copy files needed for the wheel where they are expected
8180
cp /catalyst/runtime-build/lib/*/*/*/*/librtd* /catalyst/runtime-build/lib
8281
cp /catalyst/runtime-build/lib/registry/runtime-build/lib/catalyst_callback_registry.cpython-${PYTHON_ALTERNATIVE_VERSION}-aarch64-linux-gnu.so /catalyst/runtime-build/lib
82+
cp /catalyst/runtime-build/lib/*/*/*/*/openqasm_python_module.cpython-${PYTHON_ALTERNATIVE_VERSION}-aarch64-linux-gnu.so /catalyst/runtime-build/lib
8383
cp /catalyst/runtime-build/lib/capi/runtime-build/lib/librt_capi.so /catalyst/runtime-build/lib/
8484

8585
# Build wheels

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ wheel:
171171
mkdir -p $(MK_DIR)/frontend/catalyst/lib/backend
172172
cp $(RT_BUILD_DIR)/lib/librtd* $(MK_DIR)/frontend/catalyst/lib
173173
cp $(RT_BUILD_DIR)/lib/catalyst_callback_registry*.* $(MK_DIR)/frontend/catalyst/lib
174+
cp $(RT_BUILD_DIR)/lib/openqasm_python_module*.* $(MK_DIR)/frontend/catalyst/lib
174175
cp $(RT_BUILD_DIR)/lib/librt_capi.* $(MK_DIR)/frontend/catalyst/lib
175176
cp $(RT_BUILD_DIR)/lib/backend/*.toml $(MK_DIR)/frontend/catalyst/lib/backend
176177
cp $(OQC_BUILD_DIR)/librtd_oqc* $(MK_DIR)/frontend/catalyst/lib

frontend/catalyst/third_party/oqc/src/CMakeLists.txt

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,10 @@ cmake_minimum_required(VERSION 3.20)
22

33
project(catalyst_oqc)
44

5-
option(USE_ALTERNATIVE_CATALYST_PYTHON_INTERPRETER_PATH OFF)
6-
75
set(runtime_includes "${PROJECT_SOURCE_DIR}/../../../../../runtime/include")
86
set(backend_includes "${PROJECT_SOURCE_DIR}/../../../../../runtime/lib/backend/common")
9-
set(util_includes "${PROJECT_SOURCE_DIR}/../../../../../runtime/utils")
107
set(runtime_lib "${RUNTIME_BUILD_DIR}/lib")
118
set(oqc_backend_dir "${OQC_BUILD_DIR}/backend")
12-
set(catalyst_python_interpreter_path "${RUNTIME_BUILD_DIR}/utils/runtime-build/lib")
139

1410
set(CMAKE_CXX_STANDARD 20)
1511
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -47,24 +43,29 @@ add_library(rtd_oqc SHARED OQCDevice.cpp)
4743
target_include_directories(rtd_oqc PUBLIC .
4844
${runtime_includes}
4945
${backend_includes}
50-
${util_includes}
5146
)
5247

5348
set(OQC_LIBRARIES
5449
rtd_oqc
55-
catalyst_python_interpreter
5650
)
5751

5852
set_target_properties(rtd_oqc PROPERTIES BUILD_RPATH "$ORIGIN/../utils")
5953
target_link_directories(rtd_oqc PRIVATE ${runtime_lib})
60-
target_link_libraries(rtd_oqc PRIVATE pybind11::module catalyst_python_interpreter)
6154

62-
if(USE_ALTERNATIVE_CATALYST_PYTHON_INTERPRETER_PATH)
63-
target_link_directories(rtd_oqc PRIVATE ${catalyst_python_interpreter_path})
64-
endif()
65-
unset(USE_ALTERNATIVE_CATALYST_PYTHON_INTERPRETER_PATH CACHE)
55+
pybind11_add_module(oqc_python_module SHARED oqc_python_module.cpp)
56+
target_include_directories(oqc_python_module PRIVATE ${runtime_includes})
57+
58+
add_dependencies(rtd_oqc oqc_python_module)
59+
target_compile_definitions(rtd_oqc PUBLIC -DOQC_PY=\"$<TARGET_FILE_NAME:oqc_python_module>\")
6660

6761
set_property(TARGET rtd_oqc PROPERTY POSITION_INDEPENDENT_CODE ON)
62+
set_property(TARGET rtd_oqc APPEND PROPERTY BUILD_RPATH "$<TARGET_FILE_DIR:oqc_python_module>")
63+
if(NOT APPLE)
64+
set_property(TARGET rtd_oqc APPEND PROPERTY BUILD_RPATH $ORIGIN)
65+
else()
66+
set_property(TARGET rtd_oqc APPEND PROPERTY BUILD_RPATH @loader_path)
67+
endif()
68+
6869
file(COPY ${PROJECT_SOURCE_DIR}/oqc.toml DESTINATION ./backend)
6970

7071
add_subdirectory(tests)

frontend/catalyst/third_party/oqc/src/Makefile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ MK_ABSPATH := $(abspath $(lastword $(MAKEFILE_LIST)))
77
MK_DIR := $(dir $(MK_ABSPATH))
88
OQC_BUILD_DIR?=$(MK_DIR)/build
99
RT_BUILD_DIR?=$(MK_DIR)/../../../../../runtime/build
10-
USE_ALTERNATIVE_CATALYST_PYTHON_INTERPRETER?=OFF
1110

1211
.PHONY: configure
1312
configure:
@@ -17,7 +16,6 @@ configure:
1716
-DCMAKE_C_COMPILER=$(C_COMPILER) \
1817
-DCMAKE_CXX_COMPILER=$(CXX_COMPILER) \
1918
-DRUNTIME_BUILD_DIR=$(RT_BUILD_DIR) \
20-
-DUSE_ALTERNATIVE_CATALYST_PYTHON_INTERPRETER_PATH=$(USE_ALTERNATIVE_CATALYST_PYTHON_INTERPRETER) \
2119
-DPYTHON_EXECUTABLE=$(PYTHON) \
2220
-Dpybind11_DIR=$(shell $(PYTHON) -c "import pybind11; print(pybind11.get_cmake_dir())")
2321

frontend/catalyst/third_party/oqc/src/OQCDevice.hpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@
3131
#include "QubitManager.hpp"
3232
#include "Utils.hpp"
3333

34-
#include <pybind11/embed.h>
35-
36-
#include "OQCRunner.hpp" // <pybind11/embed.h>
34+
#include "OQCRunner.hpp"
3735
#include "OpenQASM2Builder.hpp"
3836

3937
using namespace Catalyst::Runtime::OpenQASM2;

frontend/catalyst/third_party/oqc/src/OQCRunner.hpp

Lines changed: 15 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,15 @@
1616
#pragma once
1717

1818
#include <complex>
19-
#include <mutex>
20-
#include <optional>
2119
#include <string>
22-
#include <unordered_map>
23-
#include <utility>
2420
#include <vector>
2521

26-
#include <iostream>
27-
22+
#include "DynamicLibraryLoader.hpp"
2823
#include "Exception.hpp"
29-
#include "Python.hpp"
3024

25+
#ifdef INITIALIZE_PYTHON
3126
#include <pybind11/embed.h>
27+
#endif
3228

3329
namespace Catalyst::Runtime::Device {
3430

@@ -119,54 +115,19 @@ struct OQCRunner : public OQCRunnerBase {
119115
size_t num_qubits, const std::string &kwargs = "") const
120116
-> std::vector<size_t>
121117
{
122-
std::lock_guard<std::mutex> lock(getPythonMutex());
123-
namespace py = pybind11;
124-
using namespace py::literals;
125-
126-
RT_FAIL_IF(!Py_IsInitialized(), "The Python interpreter is not initialized");
127-
128-
auto locals = py::dict("circuit"_a = circuit, "device"_a = device, "kwargs"_a = kwargs,
129-
"shots"_a = shots, "msg"_a = "");
130-
131-
py::exec(
132-
R"(
133-
import os
134-
from qcaas_client.client import OQCClient, QPUTask, CompilerConfig
135-
from qcaas_client.config import QuantumResultsFormat, Tket, TketOptimizations
136-
optimisations = Tket()
137-
optimisations.tket_optimizations = TketOptimizations.DefaultMappingPass
138-
139-
RES_FORMAT = QuantumResultsFormat().binary_count()
140-
141-
try:
142-
email = os.environ.get("OQC_EMAIL")
143-
password = os.environ.get("OQC_PASSWORD")
144-
url = os.environ.get("OQC_URL")
145-
client = OQCClient(url=url, email=email, password=password)
146-
client.authenticate()
147-
oqc_config = CompilerConfig(repeats=shots, results_format=RES_FORMAT, optimizations=optimisations)
148-
oqc_task = QPUTask(circuit, oqc_config)
149-
res = client.execute_tasks(oqc_task)
150-
counts = res[0].result["cbits"]
151-
152-
except Exception as e:
153-
print(f"circuit: {circuit}")
154-
msg = str(e)
155-
)",
156-
py::globals(), locals);
157-
158-
auto &&msg = locals["msg"].cast<std::string>();
159-
RT_FAIL_IF(!msg.empty(), msg.c_str());
160-
161-
py::dict results = locals["counts"];
162-
163-
std::vector<size_t> counts_value;
164-
for (auto item : results) {
165-
auto key = item.first;
166-
auto value = item.second;
167-
counts_value.push_back(value.cast<size_t>());
118+
#ifdef INITIALIZE_PYTHON
119+
if (!Py_IsInitialized()) {
120+
pybind11::initialize_interpreter();
168121
}
169-
return counts_value;
122+
#endif
123+
124+
DynamicLibraryLoader libLoader(OQC_PY);
125+
126+
using countsImpl_t =
127+
std::vector<size_t> (*)(const char *, const char *, size_t, const char *);
128+
auto countsImpl = libLoader.getSymbol<countsImpl_t>("counts");
129+
130+
return countsImpl(circuit.c_str(), device.c_str(), shots, kwargs.c_str());
170131
}
171132
};
172133

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2024 Xanadu Quantum Technologies Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <pybind11/eval.h>
16+
#include <pybind11/pybind11.h>
17+
#include <string>
18+
19+
#include "Exception.hpp"
20+
21+
std::string program = R"(
22+
import os
23+
from qcaas_client.client import OQCClient, QPUTask, CompilerConfig
24+
from qcaas_client.config import QuantumResultsFormat, Tket, TketOptimizations
25+
optimisations = Tket()
26+
optimisations.tket_optimizations = TketOptimizations.DefaultMappingPass
27+
28+
RES_FORMAT = QuantumResultsFormat().binary_count()
29+
30+
try:
31+
email = os.environ.get("OQC_EMAIL")
32+
password = os.environ.get("OQC_PASSWORD")
33+
url = os.environ.get("OQC_URL")
34+
client = OQCClient(url=url, email=email, password=password)
35+
client.authenticate()
36+
oqc_config = CompilerConfig(repeats=shots, results_format=RES_FORMAT, optimizations=optimisations)
37+
oqc_task = QPUTask(circuit, oqc_config)
38+
res = client.execute_tasks(oqc_task)
39+
counts = res[0].result["cbits"]
40+
41+
except Exception as e:
42+
print(f"circuit: {circuit}")
43+
msg = str(e)
44+
)";
45+
46+
[[gnu::visibility("default")]] void counts(const char *_circuit, const char *_device, size_t shots,
47+
size_t num_qubits, const char *_kwargs, void *_vector)
48+
{
49+
namespace py = pybind11;
50+
using namespace py::literals;
51+
52+
py::gil_scoped_acquire lock;
53+
54+
auto locals = py::dict("circuit"_a = _circuit, "device"_a = _device, "kwargs"_a = _kwargs,
55+
"shots"_a = shots, "msg"_a = "");
56+
57+
py::exec(program, py::globals(), locals);
58+
59+
auto &&msg = locals["msg"].cast<std::string>();
60+
RT_FAIL_IF(!msg.empty(), msg.c_str());
61+
62+
py::dict results = locals["counts"];
63+
64+
std::vector<size_t> *counts_value = reinterpret_cast<std::vector<size_t> *>(_vector);
65+
for (auto item : results) {
66+
auto key = item.first;
67+
auto value = item.second;
68+
counts_value->push_back(value.cast<size_t>());
69+
}
70+
return;
71+
}
72+
73+
PYBIND11_MODULE(oqc_python_module, m) { m.doc() = "oqc"; }

frontend/catalyst/third_party/oqc/src/tests/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,23 @@ include(CTest)
2424
include(Catch)
2525

2626
add_executable(runner_tests_oqc runner_main.cpp)
27+
add_dependencies(runner_tests_oqc oqc_python_module)
28+
set_property(TARGET runner_tests_oqc APPEND PROPERTY BUILD_RPATH "$<TARGET_FILE_DIR:oqc_python_module>")
29+
if(NOT APPLE)
30+
set_property(TARGET runner_tests_oqc APPEND PROPERTY BUILD_RPATH $ORIGIN)
31+
set_property(TARGET runner_tests_oqc APPEND PROPERTY BUILD_RPATH $ORIGIN/../lib)
32+
else()
33+
set_property(TARGET runner_tests_oqc APPEND PROPERTY BUILD_RPATH @loader_path)
34+
set_property(TARGET runner_tests_oqc APPEND PROPERTY BUILD_RPATH @loader_path/../lib)
35+
endif()
2736

2837
target_include_directories(runner_tests_oqc PRIVATE
2938
${OQC_LIBRARIES}
3039
)
3140

3241
target_link_directories(runner_tests_oqc PRIVATE ${runtime_lib})
3342
# To avoid link to libpython, we use pybind11::module interface library.
43+
target_compile_definitions(runner_tests_oqc PUBLIC INITIALIZE_PYTHON)
3444
target_link_libraries(runner_tests_oqc PRIVATE
3545
Catch2::Catch2
3646
pybind11::embed

frontend/catalyst/third_party/oqc/src/tests/Test_OQCDevice.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,8 @@
1414

1515
#include "OQCDevice.cpp"
1616
#include "OQCRunner.hpp"
17-
#include "Python.hpp"
1817
#include "RuntimeCAPI.h"
1918

20-
PythonInterpreterGuard guard{};
21-
2219
#include <catch2/catch.hpp>
2320

2421
using namespace Catalyst::Runtime::Device;
@@ -108,4 +105,4 @@ TEST_CASE("Test the bell pair circuit", "[openqasm]")
108105
"h qubits[2];\n";
109106

110107
CHECK(device->Circuit() == toqasmempty);
111-
}
108+
}

frontend/catalyst/utils/wrapper.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,10 @@ py::list wrap(py::object func, py::tuple py_args, py::object result_desc, py::ob
214214
auto value1 = py_args.attr("__getitem__")(1);
215215
void *value1_ptr = *reinterpret_cast<void **>(ctypes.attr("addressof")(value1).cast<size_t>());
216216

217-
f_ptr(value0_ptr, value1_ptr);
217+
{
218+
py::gil_scoped_release lock;
219+
f_ptr(value0_ptr, value1_ptr);
220+
}
218221
returns = move_returns(value0_ptr, result_desc, transfer, numpy_arrays);
219222

220223
return returns;

runtime/CMakeLists.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
2525
set(runtime_includes "${PROJECT_SOURCE_DIR}/include")
2626
set(capi_utils_includes "${PROJECT_SOURCE_DIR}/lib/capi")
2727
set(backend_includes "${PROJECT_SOURCE_DIR}/lib/backend/common")
28-
set(util_includes "${PROJECT_SOURCE_DIR}/utils")
2928

3029

3130
# Get LLVM hash to target from source tree.
@@ -130,4 +129,3 @@ endif()
130129

131130
add_subdirectory(lib)
132131
add_subdirectory(tests)
133-
add_subdirectory(utils)

0 commit comments

Comments
 (0)