Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
c8331a2
qec/realtime: Relay BP (nv-qldpc) gpu_roce profile for the HSB decodi…
cketcham2333 Jul 10, 2026
0c426d0
Merge remote-tracking branch 'upstream/main' into add-relaybp-fpga
cketcham2333 Jul 10, 2026
b25778b
decoding-server-cqr: link CUDA::cuda_driver into the gpu_roce block
cketcham2333 Jul 10, 2026
d9cdf52
decoding-server-cqr: move GPU RoCE out of the core static lib
cketcham2333 Jul 10, 2026
5efaff8
Decoding server: load transports as CUDA-Q bridge providers
bmhowe23 Jul 11, 2026
332ef5d
GpuRoceTransceiver: consume the Hololink bridge provider
bmhowe23 Jul 11, 2026
d949a1c
Merge branch 'pr670-head' into bmh/decoding-server-bridge-670
bmhowe23 Jul 11, 2026
d637d66
Rename the device-dispatch path to what it is: device_graph, not gpu_…
bmhowe23 Jul 11, 2026
ac0cb40
Remove pre-release compatibility aliases from the dispatch/transport …
bmhowe23 Jul 11, 2026
21831c5
docs: transport-provider design (before/after, contracts, reproductio…
bmhowe23 Jul 12, 2026
af8db8e
One ring buffer (and one dispatcher) per decoder, device_id == decode…
bmhowe23 Jul 12, 2026
878eee3
Decoding server: one ring per decoder across two processes
bmhowe23 Jul 12, 2026
4ba17aa
Mixed-dispatch decoding server: device_graph + host rings in one process
bmhowe23 Jul 12, 2026
21d0836
Transport section is server-level and shape-keyed, not per-decoder
bmhowe23 Jul 12, 2026
2065319
sc4 caller: per-decoder ring endpoints; record device-ring validation…
bmhowe23 Jul 12, 2026
d97157f
Refine WSL2 device-graph diagnosis: cudaErrorNotSupported at kernel l…
bmhowe23 Jul 12, 2026
f3fe16b
Correct the device-graph wedge diagnosis: probe was invalid, platform…
bmhowe23 Jul 12, 2026
e0ed435
docs: re-scope the realtime design docs into contracts / topology / log
bmhowe23 Jul 12, 2026
58e40c1
docs: deployment cookbook (sample YAML + launch lines per scenario)
bmhowe23 Jul 12, 2026
ecec808
Fix decode-graph co-residency wedge; full mixed E2E now passes locally
bmhowe23 Jul 12, 2026
65f16d9
Adopt the string-keyed bridge loader; drop the one-provider-per-proce…
bmhowe23 Jul 12, 2026
f4b19d4
Address self-review findings in the decoding server and CQR components
bmhowe23 Jul 12, 2026
b94767a
--transport is a fallback, never an override; test the YAML transport…
bmhowe23 Jul 12, 2026
e64f56f
Apply clang-format
bmhowe23 Jul 12, 2026
19dd816
docs: refresh the architecture diagram
bmhowe23 Jul 12, 2026
2229b85
Pin to custom CUDA-Q version
bmhowe23 Jul 12, 2026
9014dd9
Merge origin/main (PR 670 landed + 15 commits) into decoding-server b…
bmhowe23 Jul 15, 2026
71d1e68
Validation notes: post-merge revalidation of the per-decoder-rings stack
bmhowe23 Jul 15, 2026
c8e3da3
clang-format
bmhowe23 Jul 15, 2026
49eb8e1
Generalize the standalone device-graph transceiver for non-HSB providers
bmhowe23 Jul 15, 2026
07ce040
Bump cudaq version
bmhowe23 Jul 15, 2026
44bec3b
Add realtime_decoding_demo: drive the delivered decoding server from …
cketcham2333 Jul 16, 2026
798c82d
clang format
cketcham2333 Jul 16, 2026
11108a4
Merge remote-tracking branch 'upstream/main' into decoding_server_exa…
cketcham2333 Jul 17, 2026
ae3716b
Rebase realtime_decoding_demo onto the refactored surface_code-1 app
cketcham2333 Jul 17, 2026
4d770db
Merge remote-tracking branch 'upstream/main' into decoding_server_exa…
cketcham2333 Jul 17, 2026
143fe9a
Address review comments
cketcham2333 Jul 17, 2026
bcb21a2
Merge branch 'main' into bmh/decoding-server-bridge-670
bmhowe23 Jul 17, 2026
2e68bf1
Addressed additional review comments
cketcham2333 Jul 17, 2026
379f3d2
Merge remote-tracking branch 'bmhowe23/bmh/decoding-server-bridge-670…
cketcham2333 Jul 17, 2026
51e5862
Fix swallowed --device in DeviceGraphTransceiver's provider argv
cketcham2333 Jul 17, 2026
3344a15
Adapt realtime_decoding_demo + hsb driver to the bridge-provider server
cketcham2333 Jul 17, 2026
18d0ff0
Merge branch 'main' into bmh/decoding-server-bridge-670
bmhowe23 Jul 17, 2026
0f92f55
Fix swallowed --device in DeviceGraphTransceiver's provider argv
cketcham2333 Jul 17, 2026
0d6b2d1
Merge branch 'main' into bmh/decoding-server-bridge-670
bmhowe23 Jul 17, 2026
4f2343e
Add qpu-kernel-over-cpu_roce runs to the realtime decoding example
cketcham2333 Jul 17, 2026
c2a1bf0
Merge remote-tracking branch 'bmhowe23/bmh/decoding-server-bridge-670…
cketcham2333 Jul 17, 2026
03217c7
Add the Ising decoder (trt_decoder) profile to the realtime decoding …
cketcham2333 Jul 21, 2026
e139c40
clang format
cketcham2333 Jul 21, 2026
65a6820
Fix two FPGA-run footguns: nv-qldpc auto-pacing + bridge-ip subnet guard
cketcham2333 Jul 22, 2026
4bdea49
Merge remote-tracking branch 'upstream/main' into decoding_server_exa…
cketcham2333 Jul 24, 2026
5cd9751
FPGA runs: fix the hsb endpoint scrape; auto-pace slow decoders at 100us
cketcham2333 Jul 24, 2026
dba6725
realtime_decoding_demo: adopt the prepare_ising_artifacts.py workflow
cketcham2333 Jul 24, 2026
9d846a5
Remove unneeded documents left over from early PR 682 merge
cketcham2333 Jul 24, 2026
a8ffb2e
realtime_decoding_demo: code-review fixes (precedence, subnet guard, …
cketcham2333 Jul 25, 2026
92de54b
Address more review comments
cketcham2333 Aug 3, 2026
ae1383a
Merge remote-tracking branch 'upstream/main' into decoding_server_exa…
cketcham2333 Aug 3, 2026
921e4bb
Merge remote-tracking branch 'upstream/main' into decoding_server_exa…
cketcham2333 Aug 3, 2026
5bb0ffc
Fix semantic merge leftovers from the hololink->hsb rename (PR 754)
cketcham2333 Aug 3, 2026
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
31 changes: 31 additions & 0 deletions .github/workflows/lib_qec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,37 @@ jobs:
- name: Run example tests
run: bash scripts/ci/test_examples.sh qec

# ========================================================================
# Realtime decoding demo example
#
# Build the two example binaries against the INSTALLED SDK -- this is what
# enforces the installed-headers-only rule: a build-tree-only include
# would fail to compile here. Then drive the delivered decoding_server
# over UDP loopback with a real pymatching decode (the qpu-kernel source).
# No hardware: the FPGA source needs a ConnectX NIC and is not run in CI.
# ========================================================================
- name: Build & run realtime_decoding_demo (qpu-kernel, UDP loopback)
shell: bash
run: |
export PATH=/cudaq-install/bin:$PATH
# Mirror build_qec.sh: it builds+installs cudaq-realtime to
# $CUDAQ_REALTIME_ROOT, defaulting to /tmp/cudaq-realtime when unset
# (the case for this workflow). Track the same value so this step
# stays correct if the job ever sets CUDAQ_REALTIME_ROOT.
RT="${CUDAQ_REALTIME_ROOT:-/tmp/cudaq-realtime}"
EX=docs/sphinx/examples/qec/realtime_decoding_demo
cmake -S "$EX" -B /tmp/rtdemo-build -G Ninja \
-DCUDAQ_INSTALL_DIR=/cudaq-install \
-DCUDAQX_INSTALL_DIR="$HOME/.cudaqx" \
-DCUDAQ_REALTIME_DIR="$RT"
cmake --build /tmp/rtdemo-build -j
bash "$EX/run_realtime_decoding.sh" \
--source qpu-kernel --decoder pymatching \
--install-prefix "$HOME/.cudaqx" \
--cudaq-prefix /cudaq-install \
--realtime-lib-dir "$RT" \
--example-build-dir /tmp/rtdemo-build

# ========================================================================
# Upload build artifacts for GPU tests
# ========================================================================
Expand Down
5 changes: 4 additions & 1 deletion docs/sphinx/conf.py.in
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ master_doc = 'index'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_templates']
# Example-directory READMEs are for the shipped example tree (rendered on
# GitHub), not standalone doc pages -- without this they are ingested via the
# '.md' source suffix and warn as toctree orphans.
exclude_patterns = ['_templates', 'examples/**/README.md']

# The reST default role (used for this markup: `text`) to use for all documents.
default_role = 'code' # NOTE: the following may be a better choice to error on the side of flagging anything that is referenced but but not declared
Expand Down
2 changes: 2 additions & 0 deletions docs/sphinx/examples/qec/realtime_decoding_demo/.gitignore
Comment thread
cketcham2333 marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Build output (the two example binaries + CMake cache)
build/
167 changes: 167 additions & 0 deletions docs/sphinx/examples/qec/realtime_decoding_demo/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# ============================================================================ #
# Copyright (c) 2026 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. #
# ============================================================================ #

# Realtime-decoding example.
#
# This builds ONE source (surface_code_realtime_decoding.cpp) two ways, against
# the *installed* CUDA-Q / CUDA-QX SDK -- nothing here reaches into a build tree:
#
# surface_code_realtime_decoding generator: --target stim; writes the
# decoder config + syndrome files.
# surface_code_realtime_decoding-cqr lowered kernel: adds -frealtime-lowering
# -DQEC_APP_EXTERNAL_DECODING_SERVER; the
Comment thread
cketcham2333 marked this conversation as resolved.
Outdated
# live QPU kernel that streams syndromes to
# the delivered decoding_server over UDP.
#
# The decoding_server and the FPGA playback tool are DELIVERABLES (installed,
# not built here); run_realtime_decoding.sh resolves them from --install-prefix.

cmake_minimum_required(VERSION 3.23)

# The lowered-kernel binary links the realtime dispatch archive (relocatable
# CUDA device code), so the project enables CUDA and needs an architecture for
# the device-link step. Default to 80 (A100) -- an architecture the shipped
# SDK's dispatch archive includes; override to match your GPU, e.g.
# -DCMAKE_CUDA_ARCHITECTURES=90 for Hopper / GB200.
if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
set(CMAKE_CUDA_ARCHITECTURES 80)
endif()

project(realtime_decoding_demo LANGUAGES CXX CUDA)

# ---------------------------------------------------------------------------- #
# Locate the installed SDK.
# ---------------------------------------------------------------------------- #
# CUDA-Q: provides nvq++, cudaq/*.h, cudaq/realtime.h and the core runtime libs.
if(NOT CUDAQ_INSTALL_DIR)
if(DEFINED ENV{CUDA_QUANTUM_PATH})
set(CUDAQ_INSTALL_DIR "$ENV{CUDA_QUANTUM_PATH}")
else()
set(CUDAQ_INSTALL_DIR "/usr/local/cudaq")
endif()
endif()
if(NOT EXISTS "${CUDAQ_INSTALL_DIR}/bin/nvq++")
message(FATAL_ERROR
"nvq++ not found under CUDAQ_INSTALL_DIR=${CUDAQ_INSTALL_DIR}. "
"Pass -DCUDAQ_INSTALL_DIR=<cuda-quantum install prefix>.")
endif()

# CUDA-QX: provides cudaq/qec/*.h and the QEC + realtime-decoding libs/plugins.
if(NOT CUDAQX_INSTALL_DIR)
if(DEFINED ENV{CUDAQX_INSTALL_DIR})
set(CUDAQX_INSTALL_DIR "$ENV{CUDAQX_INSTALL_DIR}")
else()
message(FATAL_ERROR
"Set -DCUDAQX_INSTALL_DIR=<cuda-qx install prefix> (the directory that "
"holds include/cudaq/qec and lib/libcudaq-qec.so).")
endif()
endif()
if(NOT EXISTS "${CUDAQX_INSTALL_DIR}/include/cudaq/qec/code.h")
message(FATAL_ERROR
"CUDAQX_INSTALL_DIR=${CUDAQX_INSTALL_DIR} does not look like a CUDA-QX "
"install (missing include/cudaq/qec/code.h).")
endif()

set(NVQPP "${CUDAQ_INSTALL_DIR}/bin/nvq++")
find_package(CUDAToolkit REQUIRED)

# The realtime dispatch archive carries relocatable (RDC) device code, so the
# -cqr executable must be linked with a CUDA device-link step (see below).
find_library(CUDAQ_REALTIME_DISPATCH_LIB cudaq-realtime-dispatch
PATHS "${CUDAQ_INSTALL_DIR}" "${CUDAQ_REALTIME_DIR}" PATH_SUFFIXES lib REQUIRED)

link_directories(
"${CUDAQX_INSTALL_DIR}/lib"
"${CUDAQX_INSTALL_DIR}/lib/decoder-plugins"
"${CUDAQ_INSTALL_DIR}/lib"
"${CUDAQ_INSTALL_DIR}/lib/plugins")

set(_src "${CMAKE_CURRENT_SOURCE_DIR}/surface_code_realtime_decoding.cpp")

# Only the CUDA-QX headers need an explicit -I; nvq++ already knows its own
# CUDA-Q include dir (cudaq/*.h, cudaq/realtime.h, common/*.h).
set(_nvqpp_includes "-I${CUDAQX_INSTALL_DIR}/include")

# CUDA-Q core libraries, listed AFTER the qec + simulation libs on purpose.
# The simulation backend's static initializer registers into a global map owned
# by libcudaq-common; ELF runs initializers in reverse DT_NEEDED (link) order,
# so the qec/simulation libs must be listed FIRST (they initialize last) and the
# cudaq core libs LAST (they initialize first) or the registration dereferences
# a not-yet-constructed map and segfaults at startup.
set(_cudaq_core_libs
cudaq-mlir-runtime nvqir nvqir-stim
cudaq-qec-realtime-decoding cudaq-qec-decoders
cudaq cudaq-platform-default cudaq-em-default
cudaq-common cudaq-operator cudaq-logger)

# ---------------------------------------------------------------------------- #
# Helper: compile the source with nvq++ into an object, in an isolated working
# directory (nvq++ drops intermediates named after the source, so two compiles
# of the same file must not share a cwd).
# ---------------------------------------------------------------------------- #
function(_add_nvqpp_object out_var obj_name)
set(_obj "${CMAKE_CURRENT_BINARY_DIR}/${obj_name}.o")
set(_wd "${CMAKE_CURRENT_BINARY_DIR}/${obj_name}.nvqpp")
file(MAKE_DIRECTORY "${_wd}")
add_custom_command(
OUTPUT "${_obj}"
COMMAND ${NVQPP} ${ARGN} -c -fPIC "${_src}" -o "${_obj}" ${_nvqpp_includes}
DEPENDS "${_src}"
WORKING_DIRECTORY "${_wd}"
COMMENT "Compiling surface_code_realtime_decoding.cpp with nvq++ (${obj_name})"
VERBATIM)
set_source_files_properties("${_obj}" PROPERTIES EXTERNAL_OBJECT TRUE GENERATED TRUE)
set(${out_var} "${_obj}" PARENT_SCOPE)
endfunction()

# ---------------------------------------------------------------------------- #
# Generator (plain): writes the decoder config + syndrome files.
# ---------------------------------------------------------------------------- #
_add_nvqpp_object(_gen_obj generator --target stim)
add_executable(surface_code_realtime_decoding "${_gen_obj}")
set_target_properties(surface_code_realtime_decoding PROPERTIES LINKER_LANGUAGE CXX)
# --no-as-needed: nvqir-stim (the --target stim simulator) and
# cudaq-platform-default are pulled in at runtime by registration, not by a
# direct symbol reference, so the default --as-needed would drop them from
# DT_NEEDED and the process would crash at startup with no platform.
target_link_libraries(surface_code_realtime_decoding PRIVATE
-Wl,--no-as-needed
cudaq-qec cudaq-qec-realtime-decoding
cudaq-qec-realtime-decoding-simulation ${_cudaq_core_libs}
-Wl,--as-needed
"${CUDAQ_REALTIME_DISPATCH_LIB}")
target_link_options(surface_code_realtime_decoding PRIVATE
LINKER:--allow-shlib-undefined LINKER:--export-dynamic)

# ---------------------------------------------------------------------------- #
# Lowered kernel (-cqr): the live syndrome source over UDP.
# LINKER_LANGUAGE CUDA + CUDA_RESOLVE_DEVICE_SYMBOLS runs the device-link
# (nvcc -dlink) that resolves the dispatch archive's RDC registration symbols.
# ---------------------------------------------------------------------------- #
_add_nvqpp_object(_cqr_obj generator-cqr
--target stim -frealtime-lowering -DQEC_APP_CQR)
add_executable(surface_code_realtime_decoding-cqr "${_cqr_obj}")
set_target_properties(surface_code_realtime_decoding-cqr PROPERTIES
CUDA_SEPARABLE_COMPILATION ON
CUDA_RESOLVE_DEVICE_SYMBOLS ON
CUDA_STANDARD 17
LINKER_LANGUAGE CUDA)
target_link_libraries(surface_code_realtime_decoding-cqr PRIVATE
-Wl,--no-as-needed
cudaq-qec cudaq-qec-realtime-decoding
cudaq-qec-realtime-decoding-simulation-cqr
# The in-process decoding service; also exports
# cudaqx_qec_device_call_dispatch_count, which the app prints as evidence
# that an external server (not the in-process service) did the decoding.
cudaq-qec-realtime-decoding-server-cqr
cudaq-device-call-runtime
${_cudaq_core_libs}
-Wl,--as-needed
"${CUDAQ_REALTIME_DISPATCH_LIB}" CUDA::cudart)
target_link_options(surface_code_realtime_decoding-cqr PRIVATE
LINKER:--allow-shlib-undefined LINKER:--export-dynamic)
95 changes: 95 additions & 0 deletions docs/sphinx/examples/qec/realtime_decoding_demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Realtime decoding demo

Drive the delivered `decoding_server` from two syndrome sources — a real FPGA,
or a lowered QPU kernel — both decoding through the **same prebuilt server**.

## What this example is

- **Deliverables** (installed, *not* built here): `decoding_server`, the FPGA
playback tool `hololink_fpga_syndrome_playback`, the QEC + realtime libraries,
and the decoder plugins.
- **The example** (the only thing you compile): one source,
`surface_code_realtime_decoding.cpp`, built two ways by `CMakeLists.txt`
against the **installed SDK** (installed headers/libs only):
- `surface_code_realtime_decoding` — the **generator** (`--target stim`):
writes the decoder config and, for the FPGA source, the syndrome file.
- `surface_code_realtime_decoding-cqr` — the **lowered kernel**
(`-frealtime-lowering`): the live syndrome source that streams to the server
over UDP.

## Build (once)

```bash
cmake -S . -B build \
-DCUDAQ_INSTALL_DIR=<cuda-quantum install prefix> \
-DCUDAQX_INSTALL_DIR=<cuda-qx install prefix>
cmake --build build
# -> build/surface_code_realtime_decoding (generator)
# -> build/surface_code_realtime_decoding-cqr (lowered kernel)
```

In a CUDA-QX container `CUDAQ_INSTALL_DIR` defaults to `/usr/local/cudaq` (or
`$CUDA_QUANTUM_PATH`); point `CUDAQX_INSTALL_DIR` at where the CUDA-QX SDK is
installed. If the realtime libraries live in a separate prefix, add
`-DCUDAQ_REALTIME_DIR=<realtime prefix>`. The lowered kernel links the realtime
dispatch archive (relocatable CUDA device code), so the build needs a CUDA
toolchain; override the device-link architecture with
`-DCMAKE_CUDA_ARCHITECTURES=90` (default `80`) to match your GPU.

## Run

`run_realtime_decoding.sh` resolves the deliverables from `--install-prefix`
(`$PREFIX/bin`, `$PREFIX/lib`) and the two example binaries from
`--example-build-dir` (default `./build`).

### QPU-kernel source (software, UDP, no NIC)

```bash
./run_realtime_decoding.sh --source qpu-kernel --decoder pymatching --install-prefix <prefix>
./run_realtime_decoding.sh --source qpu-kernel --decoder multi_error_lut --install-prefix <prefix>
./run_realtime_decoding.sh --source qpu-kernel --decoder nv-qldpc-decoder --gpu 0 --install-prefix <prefix>
```

The lowered kernel runs the surface-code memory experiment and streams each
shot's syndromes to the server over UDP; the server decodes and returns
corrections. No NIC, no FPGA, no network setup. Runs are seeded (`--seed`,
default 42), so the reported counts are reproducible.

PASS/FAIL uses the same criteria as the in-tree surface-code tests: the run
must complete without decoder errors, the residual logical-error count must
stay at or under `num_shots/50` (a decoder that is connected but decoding
wrong produces far more), the kernel's in-process dispatch count must be 0
(proof the decode stayed in the external server), and the server must have
dispatched at least `num_shots * (num_rounds + 3)` RPCs.

### FPGA source (real FPGA; needs a ConnectX NIC)

```bash
./run_realtime_decoding.sh --source fpga --decoder pymatching \
--setup-network --device <nic> --bridge-ip <host-ip> --fpga-ip <fpga-ip> \
--install-prefix <prefix>
# --decoder multi_error_lut / --decoder nv-qldpc-decoder --gpu 0 likewise
```

The delivered playback tool streams pre-generated syndromes over RoCE from the
FPGA into the server's RDMA RX ring. `--spacing` (default 10 µs) paces the
playback so it does not overrun the FPGA's fixed 64-slot ring. There is **no
emulator** in this example — `--source fpga` requires a real FPGA. (Emulator
testing lives in the unittests `hsb_fpga_decoding_server_test.sh`.)

## Decoders

| decoder | qpu-kernel (UDP) | fpga (real FPGA) | extra requirement |
|---|---|---|---|
| `pymatching` | CPU, no hardware | NIC | none |
| `multi_error_lut` | CPU, no hardware | NIC | none |
| `nv-qldpc-decoder` | GPU (host-call path) | NIC + GPU (device path) | plugin + `--gpu` |

- **`pymatching`** — CPU matching decoder; nothing extra.
- **`multi_error_lut`** — CPU lookup-table decoder; nothing extra.
- **`nv-qldpc-decoder`** — GPU relay-BP. Needs the prebuilt plugin
(auto-found in the install prefix, else pass `--nv-qldpc-plugin <path.so>`)
and a GPU selected with `--gpu <id>`. If the plugin is unavailable the script
exits `77` (skip).

See `./run_realtime_decoding.sh --help` for the full option list.
Loading
Loading