Skip to content

Commit

Permalink
Merge branch 'main' into fix_issue_67
Browse files Browse the repository at this point in the history
  • Loading branch information
bmhowe23 committed Feb 12, 2025
2 parents eb5c08c + 1d77713 commit ccaa66a
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 8 deletions.
32 changes: 30 additions & 2 deletions .github/workflows/build_dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,41 @@ jobs:
echo "IMAGE_EXISTS=false" >> $GITHUB_OUTPUT
fi
- name: Build and push
- name: Fetch CUDA-Q
uses: actions/checkout@v4
with:
repository: ${{ steps.get-cudaq-version.outputs.repo }}
ref: ${{ steps.get-cudaq-version.outputs.ref }}
path: cudaq
set-safe-directory: true

- name: Build CUDA-Q wheels
id: wheel_build
uses: docker/build-push-action@v5
with:
context: cudaq
file: cudaq/docker/release/cudaq.wheel.Dockerfile
build-args: |
base_image=ghcr.io/nvidia/cuda-quantum-devdeps:manylinux-${{ matrix.platform }}-cu12.0-gcc11-main
python_version=${{ matrix.python }}
outputs: type=local,dest=/tmp/wheels

- name: Build and push dev image with prebuilt CUDA-Q wheels
if: ${{ inputs.force_rebuild || steps.check-image.outputs.IMAGE_EXISTS == 'false' }}
run: |
# Bring wheels into upcoming Docker context
mkdir cudaq-wheels
cp /tmp/wheels/*.whl cudaq-wheels/
# Perform build
TAGS="-t ghcr.io/nvidia/cudaqx-dev:${{ steps.get-cudaq-version-short.outputs.commit_date }}-${{ steps.get-cudaq-version-short.outputs.shortref }}-py${{ matrix.python }}-${{ matrix.platform }}"
TAGS+=" -t ghcr.io/nvidia/cudaqx-dev:${{ steps.get-cudaq-version-short.outputs.shortref }}-py${{ matrix.python }}-${{ matrix.platform }}"
TAGS+=" -t ghcr.io/nvidia/cudaqx-dev:latest-py${{ matrix.python }}-${{ matrix.platform }}"
docker build $TAGS -f docker/build_env/cudaqx.dev.Dockerfile .
BUILDARGS="--build-arg base_image=ghcr.io/nvidia/cuda-quantum-devdeps:manylinux-${{ matrix.platform }}-cu12.0-gcc11-main"
BUILDARGS+=" --build-arg python_version=${{ matrix.python }}"
# For some reason, this fails on amd64 unless DOCKER_BUILDKIT=0 is set.
# The exact error is: too many open files.
DOCKER_BUILDKIT=0 docker build $TAGS $BUILDARGS -f docker/build_env/cudaqx.wheel.Dockerfile .
docker push -a ghcr.io/nvidia/cudaqx-dev
shell: bash --noprofile --norc -euo pipefail {0}

Expand Down
11 changes: 9 additions & 2 deletions .github/workflows/pr_sanity_checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,15 @@ jobs:
- '.github/workflows/pr_sanity_checks.yaml'
- '.clang-format'
check-cpp:
- '**/*.cpp'
- '**/*.c'
- '**/*.h'
- '**/*.cpp'
- '**/*.hpp'
- '**/*.cc'
- '**/*.hh'
- '**/*.cxx'
- '**/*.cu'
- '**/*.cuh'
check-all-python:
- '.github/workflows/pr_sanity_checks.yaml'
- '.style.yapf'
Expand Down Expand Up @@ -74,7 +81,7 @@ jobs:
- name: clang-format all things
if: needs.check-changes.outputs.check-all-cpp == 'true'
run: |
git ls-files '*.cpp' '*.h' | xargs clang-format-${LLVM_VERSION} -i
git ls-files '*.c' '*.h' '*.cpp' '*.hpp' '*.cc' '*.hh' '*.cxx' '*.hxx' '*.cu' '*.cuh' | xargs clang-format-${LLVM_VERSION} -i
if ! git diff --exit-code; then
git diff --ignore-submodules > clang-format.patch
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-cudaq-dep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:

- name: Fetch the latest commit
run: |
SHA=$(curl -s "https://api.github.com/repos/NVIDIA/cuda-quantum/commits/${{ inputs.cudaq_branch_name }}" | jq -r '.sha')
SHA=$(curl -s "https://api.github.com/repos/NVIDIA/cuda-quantum/commits/${{ inputs.cudaq_branch_name || 'main' }}" | jq -r '.sha')
echo "Latest SHA: $SHA"
echo "sha=$SHA" >> $GITHUB_ENV
Expand Down
7 changes: 5 additions & 2 deletions docker/build_env/cudaqx.wheel.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ RUN dnf install -y jq
RUN mkdir -p /workspaces/cudaqx
COPY .cudaq_version /workspaces/cudaqx
COPY .github/workflows/scripts/build_cudaq.sh /workspaces/cudaqx
RUN mkdir /cudaq-wheels
COPY cudaq-wheels/ /cudaq-wheels/

RUN mkdir -p /workspaces/cudaqx/cudaq && cd /workspaces/cudaqx/cudaq \
&& git init \
Expand All @@ -28,5 +30,6 @@ RUN mkdir -p /workspaces/cudaqx/cudaq && cd /workspaces/cudaqx/cudaq \
&& git remote add origin https://github.com/${CUDAQ_REPO} \
&& git fetch -q --depth=1 origin ${CUDAQ_COMMIT} \
&& git reset --hard FETCH_HEAD \
&& bash ../build_cudaq.sh --python-version ${python_version} \
&& rm -rf build
&& cd .. \
&& bash build_cudaq.sh --python-version ${python_version} \
&& rm -rf cudaq
34 changes: 34 additions & 0 deletions libs/qec/include/cudaq/qec/decoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,40 @@ struct decoder_result {
}
};

/// @brief Return type for asynchronous decoding results
class async_decoder_result {
public:
std::future<cudaq::qec::decoder_result> fut;

/// @brief Construct an async_decoder_result from a std::future.
/// @param f A rvalue reference to a std::future containing a decoder_result.
async_decoder_result(std::future<cudaq::qec::decoder_result> &&f)
: fut(std::move(f)) {}

async_decoder_result(async_decoder_result &&other) noexcept
: fut(std::move(other.fut)) {}

async_decoder_result &operator=(async_decoder_result &&other) noexcept {
if (this != &other) {
fut = std::move(other.fut);
}

return *this;
}

/// @brief Block until the decoder result is ready and retrieve it.
/// Wait until the underlying future is ready and then
/// return the stored decoder result.
/// @return The decoder_result obtained from the asynchronous operation.
cudaq::qec::decoder_result get() { return fut.get(); }

/// @brief Check if the asynchronous result is ready.
/// @return `true` if the future is ready, `false` otherwise.
bool ready() {
return fut.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
}
};

/// @brief The `decoder` base class should be subclassed by specific decoder
/// implementations. The `heterogeneous_map` provides a placeholder for
/// arbitrary constructor parameters that can be unique to each specific
Expand Down
3 changes: 2 additions & 1 deletion libs/qec/lib/decoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ decoder::decode_multi(const std::vector<std::vector<float_t>> &syndrome) {

std::future<decoder_result>
decoder::decode_async(const std::vector<float_t> &syndrome) {
return std::async(std::launch::async, [&] { return this->decode(syndrome); });
return std::async(std::launch::async,
[this, syndrome] { return this->decode(syndrome); });
}

std::unique_ptr<decoder>
Expand Down
31 changes: 31 additions & 0 deletions libs/qec/python/bindings/py_decoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ void bindDecoder(py::module &mod) {
auto qecmod = py::hasattr(mod, "qecrt")
? mod.attr("qecrt").cast<py::module_>()
: mod.def_submodule("qecrt");

py::class_<decoder_result>(qecmod, "DecoderResult", R"pbdoc(
A class representing the results of a quantum error correction decoding operation.
Expand Down Expand Up @@ -123,6 +124,19 @@ void bindDecoder(py::module &mod) {
return py::iter(py::make_tuple(r.converged, r.result));
});

py::class_<async_decoder_result>(qecmod, "AsyncDecoderResult",
R"pbdoc(
A future-like object that holds the result of an asynchronous decoder call.
Call get() to block until the result is available.
)pbdoc")
.def("get", &async_decoder_result::get,
py::call_guard<py::gil_scoped_release>(),
"Return the decoder result (blocking until ready)")
.def("ready", &async_decoder_result::ready,
py::call_guard<py::gil_scoped_release>(),
"Return True if the asynchronous decoder result is ready, False "
"otherwise");

py::class_<decoder, PyDecoder>(
qecmod, "Decoder", "Represents a decoder for quantum error correction")
.def(py::init_alias<const py::array_t<uint8_t> &>())
Expand All @@ -133,6 +147,23 @@ void bindDecoder(py::module &mod) {
},
"Decode the given syndrome to determine the error correction",
py::arg("syndrome"))
.def(
"decode_async",
[](decoder &dec,
const std::vector<float_t> &syndrome) -> async_decoder_result {
// Release the GIL while launching asynchronous work.
py::gil_scoped_release release;
return async_decoder_result(dec.decode_async(syndrome));
},
"Asynchronously decode the given syndrome", py::arg("syndrome"))
.def(
"decode_multi",
[](decoder &decoder,
const std::vector<std::vector<float_t>> &syndrome) {
return decoder.decode_multi(syndrome);
},
"Decode multiple syndromes and return the results",
py::arg("syndrome"))
.def("get_block_size", &decoder::get_block_size,
"Get the size of the code block")
.def("get_syndrome_size", &decoder::get_syndrome_size,
Expand Down
27 changes: 27 additions & 0 deletions libs/qec/python/tests/test_decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,33 @@ def test_decoder_initialization():
assert hasattr(decoder, 'decode')


def test_decoder_api():
# Test decode_multi
decoder = qec.get_decoder('example_byod', H)
result = decoder.decode_multi(
[create_test_syndrome(), create_test_syndrome()])
assert len(result) == 2
for r in result:
assert hasattr(r, 'converged')
assert hasattr(r, 'result')
assert isinstance(r.converged, bool)
assert isinstance(r.result, list)
assert len(r.result) == 10

# Test decode_async
decoder = qec.get_decoder('example_byod', H)
result_async = decoder.decode_async(create_test_syndrome())
assert hasattr(result_async, 'get')
assert hasattr(result_async, 'ready')

result = result_async.get()
assert hasattr(result, 'converged')
assert hasattr(result, 'result')
assert isinstance(result.converged, bool)
assert isinstance(result.result, list)
assert len(result.result) == 10


def test_decoder_result_structure():
decoder = qec.get_decoder('example_byod', H)
result = decoder.decode(create_test_syndrome())
Expand Down
26 changes: 26 additions & 0 deletions libs/qec/unittests/test_decoders.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "cudaq/qec/decoder.h"
#include <cmath>
#include <future>
#include <gtest/gtest.h>

TEST(DecoderUtils, CovertHardToSoft) {
Expand Down Expand Up @@ -83,6 +84,8 @@ TEST(SampleDecoder, checkAPI) {
for (auto x : dec_result.result)
ASSERT_EQ(x, 0.0f);

// Test the move constructor and move assignment operator

// Multi test
auto dec_results = d->decode_multi({syndromes, syndromes});
ASSERT_EQ(dec_results.size(), 2);
Expand Down Expand Up @@ -147,3 +150,26 @@ TEST(SteaneLutDecoder, checkAPI) {
ASSERT_TRUE(convergeTrueFound);
ASSERT_FALSE(convergeFalseFound);
}

TEST(AsyncDecoderResultTest, MoveConstructorTransfersFuture) {
std::promise<cudaq::qec::decoder_result> promise;
std::future<cudaq::qec::decoder_result> future = promise.get_future();

cudaq::qec::async_decoder_result original(std::move(future));
EXPECT_TRUE(original.fut.valid());

cudaq::qec::async_decoder_result moved(std::move(original));
EXPECT_TRUE(moved.fut.valid());
EXPECT_FALSE(original.fut.valid());
}

TEST(AsyncDecoderResultTest, MoveAssignmentTransfersFuture) {
std::promise<cudaq::qec::decoder_result> promise;
std::future<cudaq::qec::decoder_result> future = promise.get_future();

cudaq::qec::async_decoder_result first(std::move(future));
cudaq::qec::async_decoder_result second = std::move(first);

EXPECT_TRUE(second.fut.valid());
EXPECT_FALSE(first.fut.valid());
}

0 comments on commit ccaa66a

Please sign in to comment.