Skip to content

Commit d4e07b0

Browse files
bmhowe23claude
andcommitted
Fall back to the v0.6.0 HOST_LOOP bridge without the dispatch-graph API
The QEC CI build fails compiling hololink_qldpc_graph_decoder_bridge.cpp against the old cuda-quantum pinned by this branch: the per-round device-graph scheduler implementation (#631) uses the 0.16-era dispatch graph API (cudaq_create_dispatch_graph_regular, routing_key, ...) that the pinned realtime does not provide. The releases/v0.6.0 version of this bridge worked against that era of cuda-quantum: a HOST_LOOP full-window implementation built on BridgeConfig/bridge_run and capture_decode_graph, all of which the pinned realtime and this branch's QEC headers still provide (graph_resources is unchanged since v0.6.0). Restore that implementation behind the existing CUDAQ_REALTIME_FOR_0_16 gate: the CMake target now passes the define when the dispatch-graph API is detected (same genex pattern as cudaq-qec-realtime-decoding), the scheduler implementation compiles when it is set, and the v0.6.0 HOST_LOOP implementation compiles otherwise. The modern path is byte-for-byte untouched. Both branches were syntax-checked against their respective realtime headers (the exact pinned cudaq revision for the fallback). The companion playback tool already compiles against the old realtime, so the bridge was the only remaining consumer of the missing API in this directory. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Ben Howe <bhowe@nvidia.com>
1 parent 8046de7 commit d4e07b0

2 files changed

Lines changed: 203 additions & 0 deletions

File tree

libs/qec/unittests/utils/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,12 @@ if (GPU_ROCE_TRANSCEIVER_LIB AND CUDAQ_REALTIME_INCLUDE_DIR AND
234234
cuda_link_stub.cu
235235
)
236236

237+
# Without the 0.16-era device-graph scheduler API (CUDAQ_REALTIME_FOR_0_16,
238+
# see libs/qec/lib/realtime/CMakeLists.txt) the bridge compiles its earlier
239+
# HOST_LOOP full-window implementation instead of the per-round scheduler.
240+
target_compile_definitions(hololink_qldpc_graph_decoder_bridge PRIVATE
241+
$<$<BOOL:${CUDAQ_REALTIME_FOR_0_16}>:CUDAQ_REALTIME_FOR_0_16>)
242+
237243
set_target_properties(hololink_qldpc_graph_decoder_bridge PROPERTIES
238244
LINKER_LANGUAGE CUDA
239245
CUDA_SEPARABLE_COMPILATION ON

libs/qec/unittests/utils/hololink_qldpc_graph_decoder_bridge.cpp

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@
66
* the terms of the Apache License 2.0 which accompanies this distribution. *
77
******************************************************************************/
88

9+
// The per-round device-graph scheduler implementation below requires the
10+
// 0.16-era cuda-quantum realtime dispatch-graph API (see
11+
// CUDAQ_REALTIME_FOR_0_16 in libs/qec/lib/realtime/CMakeLists.txt). Against
12+
// an older cuda-quantum realtime, the #else branch compiles the previous
13+
// (v0.6.0) HOST_LOOP full-window implementation of this bridge instead.
14+
// TEMPORARY: remove the #else branch (and this guard) once the minimum
15+
// supported CUDA-Q provides the dispatch-graph API.
16+
#if defined(CUDAQ_REALTIME_FOR_0_16)
17+
918
/// @file hololink_qldpc_graph_decoder_bridge.cpp
1019
/// @brief QLDPC Relay-BP decoder bridge: Hololink GPU-RoCE ring <-> the
1120
/// self-relaunching device-graph scheduler.
@@ -473,3 +482,191 @@ int main(int argc, char *argv[]) {
473482
std::cout << "=== Bridge exited cleanly ===" << std::endl;
474483
return 0;
475484
}
485+
486+
#else // CUDAQ_REALTIME_FOR_0_16
487+
488+
/// @file hololink_qldpc_graph_decoder_bridge.cpp
489+
/// @brief QLDPC BP decoder bridge adapter using CPU-launched CUDA graph
490+
/// dispatch (HOST_LOOP) with the generic Hololink bridge skeleton.
491+
///
492+
/// This thin adapter:
493+
/// 1. Parses --config argument (Relay BP config YAML)
494+
/// 2. Loads the decoder config, builds the H tensor, creates the decoder
495+
/// 3. Calls capture_decode_graph() to get a CUDA graph + mailbox
496+
/// 4. Builds a cudaq_function_entry_t with the graph_exec
497+
/// 5. Configures BridgeConfig for HOST_LOOP backend
498+
/// 6. Delegates all Hololink / dispatcher plumbing to bridge_run()
499+
///
500+
/// The HOST_LOOP dispatcher (CPU thread) polls Hololink ring flags, then
501+
/// launches the CUDA graph for each incoming RPC request. This avoids
502+
/// the 120-outstanding-graph limit of device-side cudaGraphLaunch.
503+
///
504+
/// Requires a Grace-based system (DGX Spark / GB200) where GPU memory
505+
/// is CPU-accessible via NVLink-C2C.
506+
507+
#include <algorithm>
508+
#include <cstdint>
509+
#include <fstream>
510+
#include <iostream>
511+
#include <memory>
512+
#include <string>
513+
#include <vector>
514+
515+
#include <cuda_runtime.h>
516+
517+
#include "cudaq/realtime/hololink_bridge_common.h"
518+
519+
#include "cudaq/qec/decoder.h"
520+
#include "cudaq/qec/realtime/decoding_config.h"
521+
#include "cudaq/qec/realtime/graph_resources.h"
522+
#include "cudaq/qec/realtime/sparse_to_csr.h"
523+
524+
static std::string read_file(const std::string &path) {
525+
std::ifstream f(path);
526+
if (!f.is_open()) {
527+
std::cerr << "ERROR: Cannot open file: " << path << std::endl;
528+
return {};
529+
}
530+
return {std::istreambuf_iterator<char>(f), std::istreambuf_iterator<char>()};
531+
}
532+
533+
int main(int argc, char *argv[]) {
534+
std::string config_path;
535+
536+
for (int i = 1; i < argc; i++) {
537+
std::string arg = argv[i];
538+
if (arg.find("--config=") == 0)
539+
config_path = arg.substr(9);
540+
else if (arg == "--help" || arg == "-h") {
541+
std::cout
542+
<< "Usage: " << argv[0] << " [options]\n\n"
543+
<< "QLDPC BP decoder bridge: Hololink GPU-RoCE <-> HOST_LOOP "
544+
"graph dispatch.\n\n"
545+
<< "Decoder options:\n"
546+
<< " --config=PATH Path to Relay BP config YAML "
547+
"(required)\n\n"
548+
<< "Bridge options (passed to generic skeleton):\n"
549+
<< " --device=NAME IB device (default: rocep1s0f0)\n"
550+
<< " --peer-ip=ADDR FPGA/emulator IP (default: 10.0.0.2)\n"
551+
<< " --remote-qp=N Remote QP number (default: 0x2)\n"
552+
<< " --gpu=N GPU device ID (default: 0)\n"
553+
<< " --timeout=N Timeout in seconds (default: 60)\n"
554+
<< " --page-size=N Ring buffer slot size (default: 384)\n"
555+
<< " --num-pages=N Ring buffer slots (default: 64)\n"
556+
<< " --exchange-qp Enable QP exchange (emulator mode)\n"
557+
<< " --exchange-port=N QP exchange TCP port (default: "
558+
"12345)\n";
559+
return 0;
560+
}
561+
}
562+
563+
if (config_path.empty()) {
564+
std::cerr << "ERROR: --config=PATH is required" << std::endl;
565+
return 1;
566+
}
567+
568+
std::cout << "=== Hololink QLDPC BP Decoder Bridge (Graph Launch) ==="
569+
<< std::endl;
570+
571+
// ---- Load decoder config ----
572+
std::string yaml_str = read_file(config_path);
573+
if (yaml_str.empty())
574+
return 1;
575+
576+
auto mdc = cudaq::qec::decoding::config::multi_decoder_config::from_yaml_str(
577+
yaml_str);
578+
if (mdc.decoders.empty()) {
579+
std::cerr << "ERROR: No decoders found in config" << std::endl;
580+
return 1;
581+
}
582+
auto &dec = mdc.decoders[0];
583+
584+
// ---- Build H tensor from sparse representation ----
585+
std::vector<uint32_t> h_row_ptr, h_col_idx;
586+
std::size_t h_rows = cudaq::qec::realtime::sparse_vec_to_csr(
587+
dec.H_sparse, h_row_ptr, h_col_idx);
588+
std::size_t bs = dec.block_size;
589+
std::size_t ss = dec.syndrome_size;
590+
591+
cudaqx::tensor<uint8_t> H_tensor({ss, bs});
592+
for (std::size_t r = 0; r < ss; ++r)
593+
for (uint32_t j = h_row_ptr[r]; j < h_row_ptr[r + 1]; ++j)
594+
H_tensor.at({r, static_cast<std::size_t>(h_col_idx[j])}) = 1;
595+
596+
// ---- Create decoder ----
597+
auto params = dec.decoder_custom_args_to_heterogeneous_map();
598+
auto decoder = cudaq::qec::decoder::get("nv-qldpc-decoder", H_tensor, params);
599+
if (!decoder) {
600+
std::cerr << "ERROR: Failed to create nv-qldpc-decoder" << std::endl;
601+
return 1;
602+
}
603+
604+
decoder->set_D_sparse(dec.D_sparse);
605+
decoder->set_O_sparse(dec.O_sparse);
606+
607+
// Derive num_measurements and num_observables for frame size calculation
608+
std::vector<uint32_t> d_rp, d_ci;
609+
cudaq::qec::realtime::sparse_vec_to_csr(dec.D_sparse, d_rp, d_ci);
610+
std::size_t num_measurements = 0;
611+
for (auto c : d_ci)
612+
num_measurements =
613+
std::max(num_measurements, static_cast<std::size_t>(c + 1));
614+
std::vector<uint32_t> o_rp, o_ci;
615+
std::size_t num_observables =
616+
cudaq::qec::realtime::sparse_vec_to_csr(dec.O_sparse, o_rp, o_ci);
617+
618+
std::cout << " block_size: " << bs << std::endl;
619+
std::cout << " syndrome_size: " << ss << std::endl;
620+
std::cout << " num_measurements: " << num_measurements << std::endl;
621+
std::cout << " num_observables: " << num_observables << std::endl;
622+
(void)h_rows;
623+
624+
// ---- Capture CUDA graph ----
625+
if (!decoder->supports_graph_dispatch()) {
626+
std::cerr << "ERROR: nv-qldpc-decoder does not support graph dispatch"
627+
<< std::endl;
628+
return 1;
629+
}
630+
631+
cudaq::realtime::BridgeConfig config;
632+
cudaq::realtime::parse_bridge_args(argc, argv, config);
633+
634+
BRIDGE_CUDA_CHECK(cudaSetDevice(config.gpu_id));
635+
636+
void *raw_res = decoder->capture_decode_graph(/*reserved_sms=*/2);
637+
if (!raw_res) {
638+
std::cerr << "ERROR: capture_decode_graph() returned null" << std::endl;
639+
return 1;
640+
}
641+
auto *graph_res =
642+
static_cast<cudaq::qec::realtime::graph_resources *>(raw_res);
643+
644+
std::cout << " Graph captured: function_id=0x" << std::hex
645+
<< graph_res->function_id << std::dec << std::endl;
646+
647+
// ---- Configure HOST_LOOP bridge ----
648+
cudaq_function_entry_t entry{};
649+
entry.handler.graph_exec = graph_res->graph_exec;
650+
entry.function_id = graph_res->function_id;
651+
entry.dispatch_mode = CUDAQ_DISPATCH_GRAPH_LAUNCH;
652+
653+
config.dispatch_path = CUDAQ_DISPATCH_PATH_HOST;
654+
config.h_function_entries = &entry;
655+
config.h_func_count = 1;
656+
config.h_mailbox = graph_res->h_mailbox;
657+
config.d_mailbox = graph_res->d_mailbox;
658+
659+
config.frame_size = sizeof(cudaq::realtime::RPCHeader) +
660+
std::max(num_measurements, num_observables);
661+
if (config.page_size < config.frame_size)
662+
config.page_size = config.frame_size;
663+
664+
auto *decoder_ptr = decoder.get();
665+
config.cleanup_fn = [decoder_ptr, raw_res]() {
666+
decoder_ptr->release_decode_graph(raw_res);
667+
};
668+
669+
return cudaq::realtime::bridge_run(config);
670+
}
671+
672+
#endif // CUDAQ_REALTIME_FOR_0_16

0 commit comments

Comments
 (0)