Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an example of loading a decoder .so #42

Merged
merged 22 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ add_custom_target(cudaqx-pymodules)

if (CUDAQX_INCLUDE_TESTS)
include(CTest)
# Create the directory for decoder plugins before GoogleTest Discovery is run
file(MAKE_DIRECTORY ${CUDAQ_INSTALL_DIR}/lib/decoder-plugins)
add_custom_target(CUDAQXUnitTests)

add_custom_target(run_tests
COMMAND ${CMAKE_COMMAND} -E env
PYTHONPATH="${CUDAQ_INSTALL_DIR}:${CMAKE_BINARY_DIR}/python"
Expand Down
41 changes: 41 additions & 0 deletions libs/qec/include/cudaq/qec/plugin_loader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*******************************************************************************
* Copyright (c) 2024 NVIDIA Corporation & Affiliates. *
* All rights reserved. *
* *
* This source code and the accompanying materials are made available under *
* the terms of the Apache License 2.0 which accompanies this distribution. *
******************************************************************************/

#ifndef PLUGIN_LOADER_H
#define PLUGIN_LOADER_H

#include <dlfcn.h>
#include <map>
#include <string>

/// @brief Enum to define different types of plugins
enum class PluginType {
DECODER, // Decoder plugins
CODE // QEC codes plugins
// Add other plugin types here as needed
};

/// @brief A struct to store plugin handle with its type
struct PluginHandle {
void *handle; // Pointer to the shared library handle. This is the result of
// dlopen() function.
PluginType type; // Type of the plugin (e.g., decoder, code, etc)
};

/// @brief Function to load plugins from a directory based on type
/// @param plugin_dir The directory where the plugins are located
/// @param type The type of plugins to load. Only plugins of this type will be
/// loaded.
void load_plugins(const std::string &plugin_dir, PluginType type);

/// @brief Function to clean up loaded plugins of a specific type
/// @param type The type of plugins to clean up. Only plugins of this type will
/// be cleaned up.
void cleanup_plugins(PluginType type);

#endif // PLUGIN_LOADER_H
3 changes: 3 additions & 0 deletions libs/qec/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
set(LIBRARY_NAME cudaq-qec)

add_compile_options(-Wno-attributes)
add_compile_definitions(DECODER_PLUGIN_DIR="${CMAKE_INSTALL_PREFIX}/lib/decoder-plugins")

# FIXME?: This must be a shared library. Trying to build a static one will fail.
add_library(${LIBRARY_NAME} SHARED
Expand All @@ -17,8 +18,10 @@ add_library(${LIBRARY_NAME} SHARED
decoder.cpp
experiments.cpp
decoders/single_error_lut.cpp
plugin_loader.cpp
)

add_subdirectory(decoders/plugins/example)
add_subdirectory(codes)
add_subdirectory(device)

Expand Down
15 changes: 15 additions & 0 deletions libs/qec/lib/decoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
******************************************************************************/

#include "cudaq/qec/decoder.h"
#include "cudaq/qec/plugin_loader.h"
#include <cassert>
#include <dlfcn.h>
#include <filesystem>
#include <vector>

INSTANTIATE_REGISTRY(cudaq::qec::decoder, const cudaqx::tensor<uint8_t> &)
Expand Down Expand Up @@ -71,3 +74,15 @@ std::unique_ptr<decoder> get_decoder(const std::string &name,
return decoder::get(name, H, options);
}
} // namespace cudaq::qec

// Constructor function for auto-loading plugins
__attribute__((constructor)) void load_decoder_plugins() {
// Load plugins from the decoder-specific plugin directory
load_plugins(DECODER_PLUGIN_DIR, PluginType::DECODER);
}

// Destructor function to clean up only decoder plugins
__attribute__((destructor)) void cleanup_decoder_plugins() {
// Clean up decoder-specific plugins
cleanup_plugins(PluginType::DECODER);
}
72 changes: 72 additions & 0 deletions libs/qec/lib/decoders/plugins/example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# ============================================================================ #
# Copyright (c) 2024 NVIDIA Corporation & Affiliates. #
# All rights reserved. #
# #
# This source code and the accompanying materials are made available under #
# the terms of the Apache License 2.0 which accompanies this distribution. #
# ============================================================================ #

cmake_minimum_required(VERSION 3.28 FATAL_ERROR)

set(MODULE_NAME "cudaq-qec-example")

project(${MODULE_NAME})

# Specify the source file for the plugin
set(PLUGIN_SRC
single_error_lut_example.cpp
# single_error_lut_example2.cpp // add other decoder source files here
)

# Create the shared library
add_library(${MODULE_NAME} SHARED ${PLUGIN_SRC})

# Set the include directories for dependencies
target_include_directories(${MODULE_NAME}
PUBLIC
${CMAKE_SOURCE_DIR}/libs/qec/include
${CMAKE_SOURCE_DIR}/libs/core/include
)

# Link with required libraries
target_link_libraries(${MODULE_NAME}
PUBLIC
cudaqx-core
cudaq::cudaq
cudaq::cudaq-spin
PRIVATE
cudaq::cudaq-common
cudaq-qec
)

set_target_properties(${MODULE_NAME} PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/decoder-plugins
)

# RPATH configuration
# ==============================================================================

if (NOT SKBUILD)
set_target_properties(${LIBRARY_NAME} PROPERTIES
BUILD_RPATH "$ORIGIN"
INSTALL_RPATH "$ORIGIN:$ORIGIN/.."
)

# Let CMake automatically add paths of linked libraries to the RPATH:
set_target_properties(${LIBRARY_NAME} PROPERTIES
INSTALL_RPATH_USE_LINK_PATH TRUE)
else()
# CUDA-Q install its libraries in site-packages/lib (or dist-packages/lib)
# Thus, we need the $ORIGIN/../lib
set_target_properties(${LIBRARY_NAME} PROPERTIES
INSTALL_RPATH "$ORIGIN/../../lib"
)
endif()

# Install
# ==============================================================================

install(TARGETS ${MODULE_NAME}
COMPONENT qec-lib-plugins
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/decoder-plugins
)
113 changes: 113 additions & 0 deletions libs/qec/lib/decoders/plugins/example/decoder_plugins_demo.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*******************************************************************************
* Copyright (c) 2024 NVIDIA Corporation & Affiliates. *
* All rights reserved. *
* *
* This source code and the accompanying materials are made available under *
* the terms of the Apache License 2.0 which accompanies this distribution. *
******************************************************************************/

// This example shows how to use decoders from decoder plugins
//
// Compile and run with
// nvq++ --enable-mlir -lcudaq-qec decoder_plugins_demo.cpp -o
// decoder_plugins_demo
// ./decoder_plugins_demo

#include <dlfcn.h>
#include <filesystem>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

#include "cudaq.h"
#include "cudaq/qec/decoder.h"
#include "cudaq/qec/experiments.h"

int main() {
auto steane = cudaq::qec::get_code("steane");
auto Hz = steane->get_parity_z();
cudaqx::heterogeneous_map params;
std::vector<size_t> t_shape = Hz.shape();

std::cout << "Hz.shape():\n";
for (size_t elem : t_shape)
std::cout << elem << " ";
std::cout << "\n";

std::cout << "Hz:\n";
Hz.dump();

auto Lz = steane->get_observables_x();
std::cout << "Lz:\n";
Lz.dump();

double p = 0.2;
size_t nShots = 5;

// Check for available decoders
for (auto &name : cudaq::qec::decoder::get_registered())
printf("Decoder: %s\n", name.c_str());
// create a decoder from the plugins
auto lut_decoder = cudaq::qec::get_decoder("single_error_lut_example", Hz);

std::cout << "nShots: " << nShots << "\n";

// May want a order-2 tensor of syndromes
// access tensor by stride to write in an entire syndrome
cudaqx::tensor<uint8_t> syndrome({Hz.shape()[0]});

int nErrors = 0;
for (size_t shot = 0; shot < nShots; ++shot) {
std::cout << "shot: " << shot << "\n";
auto shot_data = cudaq::qec::generate_random_bit_flips(Hz.shape()[1], p);
std::cout << "shot data\n";
shot_data.dump();

auto observable_z_data = Lz.dot(shot_data);
observable_z_data = observable_z_data % 2;
std::cout << "Data Lz state:\n";
observable_z_data.dump();

auto syndrome = Hz.dot(shot_data);
syndrome = syndrome % 2;
std::cout << "syndrome:\n";
syndrome.dump();

auto [converged, v_result] = lut_decoder->decode(syndrome);
cudaqx::tensor<uint8_t> result_tensor;
// v_result is a std::vector<float_t>, of soft information. We'll convert
// this to hard information and store as a tensor<uint8_t>.
cudaq::qec::convert_vec_soft_to_tensor_hard(v_result, result_tensor);
std::cout << "decode result:\n";
result_tensor.dump();

// check observable result
auto decoded_observable_z = Lz.dot(result_tensor);
std::cout << "decoded observable:\n";
decoded_observable_z.dump();

// check how many observable operators were decoded correctly
// observable_z_data == decoded_observable_z This maps onto element wise
// addition (mod 2)
auto observable_flips = decoded_observable_z + observable_z_data;
observable_flips = observable_flips % 2;
std::cout << "Logical errors:\n";
observable_flips.dump();
std::cout << "\n";

// shot counts as a observable error unless all observables are correct
if (observable_flips.any()) {
nErrors++;
}
}
std::cout << "Total logical errors: " << nErrors << "\n";

// Full data gen in function call
auto [syn, data] = cudaq::qec::sample_code_capacity(Hz, nShots, p);
std::cout << "Numerical experiment:\n";
std::cout << "Data:\n";
data.dump();
std::cout << "Syn:\n";
syn.dump();
}
91 changes: 91 additions & 0 deletions libs/qec/lib/decoders/plugins/example/single_error_lut_example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*******************************************************************************
* Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. *
* All rights reserved. *
* *
* This source code and the accompanying materials are made available under *
* the terms of the Apache License 2.0 which accompanies this distribution. *
******************************************************************************/

#include "cudaq/qec/decoder.h"
#include <cassert>
#include <map>
#include <vector>

namespace cudaq::qec {

/// @brief This is a simple LUT (LookUp Table) decoder that demonstrates how to
/// build a simple decoder that can correctly decode errors during a single bit
/// flip in the block.
class single_error_lut_example : public decoder {
private:
std::map<std::string, std::size_t> single_qubit_err_signatures;

public:
single_error_lut_example(const cudaqx::tensor<uint8_t> &H,
const cudaqx::heterogeneous_map &params)
: decoder(H) {
// Decoder-specific constructor arguments can be placed in `params`.

// Build a lookup table for an error on each possible qubit

// For each qubit with a possible error, calculate an error signature.
for (std::size_t qErr = 0; qErr < block_size; qErr++) {
std::string err_sig(syndrome_size, '0');
for (std::size_t r = 0; r < syndrome_size; r++) {
bool syndrome = 0;
// Toggle syndrome on every "1" entry in the row.
// Except if there is an error on this qubit (c == qErr).
for (std::size_t c = 0; c < block_size; c++)
syndrome ^= (c != qErr) && H.at({r, c});
err_sig[r] = syndrome ? '1' : '0';
}
// printf("Adding err_sig=%s for qErr=%lu\n", err_sig.c_str(), qErr);
single_qubit_err_signatures.insert({err_sig, qErr});
}
}

virtual decoder_result decode(const std::vector<float_t> &syndrome) {
// This is a simple decoder that simply results
decoder_result result{false, std::vector<float_t>(block_size, 0.0)};

// Convert syndrome to a string
std::string syndrome_str(syndrome.size(), '0');
assert(syndrome_str.length() == syndrome_size);
bool anyErrors = false;
for (std::size_t i = 0; i < syndrome_size; i++) {
if (syndrome[i] >= 0.5) {
syndrome_str[i] = '1';
anyErrors = true;
}
}

if (!anyErrors) {
result.converged = true;
return result;
}

auto it = single_qubit_err_signatures.find(syndrome_str);
if (it != single_qubit_err_signatures.end()) {
assert(it->second < block_size);
result.converged = true;
result.result[it->second] = 1.0;
} else {
// Leave result.converged set to false.
}

return result;
}

virtual ~single_error_lut_example() {}

CUDAQ_EXTENSION_CUSTOM_CREATOR_FUNCTION(
single_error_lut_example, static std::unique_ptr<decoder> create(
const cudaqx::tensor<uint8_t> &H,
const cudaqx::heterogeneous_map &params) {
return std::make_unique<single_error_lut_example>(H, params);
})
};

CUDAQ_REGISTER_TYPE(single_error_lut_example)

} // namespace cudaq::qec
Loading
Loading