Skip to content

Commit

Permalink
Add an example of loading a decoder .so (#42)
Browse files Browse the repository at this point in the history
Add an example of auto loading decoder plugins

Signed-off-by: Melody Ren <melodyr@nvidia.com>
  • Loading branch information
melody-ren authored Jan 8, 2025
1 parent 15cad65 commit 4f96465
Show file tree
Hide file tree
Showing 9 changed files with 418 additions and 0 deletions.
42 changes: 42 additions & 0 deletions libs/qec/include/cudaq/qec/plugin_loader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*******************************************************************************
* 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 <memory>
#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 {
std::shared_ptr<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
)
112 changes: 112 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,112 @@
/*******************************************************************************
* 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();
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

0 comments on commit 4f96465

Please sign in to comment.