Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
# FPGA into the server's RDMA ring (needs a ConnectX NIC
# cabled to the FPGA). CPU decoders ride the cpu_roce
# wire on host dispatch; nv-qldpc defaults to the
# hololink wire on device_graph dispatch (the GPU
# gpu_roce wire on device_graph dispatch (the GPU
# device-call scheduler). There is NO emulator here --
# emulator testing lives in the unittests
# hsb_fpga_decoding_server_test.sh.
Expand Down Expand Up @@ -117,7 +117,7 @@ SPACING="10"
# DISPATCH which engine consumes each decoder's ring: host (a CPU dispatcher
# thread) or device_graph (the GPU device-call scheduler)
# The combination gate below rejects pairings this example has not wired up.
WIRE="" # udp | cpu_roce | hololink
WIRE="" # udp | cpu_roce | gpu_roce
DISPATCH="" # host | device_graph
GPU_ID=0

Expand Down Expand Up @@ -187,9 +187,9 @@ Common:
--num-shots N Shots (default: 85 fpga / 200 qpu-kernel)
--gpu N GPU id for nv-qldpc (default 0)
--wire W Bridge provider carrying syndromes into the server:
udp | cpu_roce | hololink (default: derived --
udp | cpu_roce | gpu_roce (default: derived --
qpu-kernel -> udp; fpga -> cpu_roce for host
dispatch, hololink for device_graph)
dispatch, gpu_roce for device_graph)
--dispatch D Per-decoder ring consumer: host | device_graph
(default: derived -- fpga + nv-qldpc-decoder ->
device_graph, everything else -> host)
Expand Down Expand Up @@ -270,7 +270,7 @@ fi
# The bridge NIC and the FPGA must share a /24: there is no gateway on the
# FPGA link, and --setup-network flushes the NIC and assigns BRIDGE_IP/24, so
# a mismatched pair leaves the FPGA unreachable -- the run then dies minutes
# later as an opaque hololink read_timeout_error. Reject it up front.
# later as an opaque HSB read timeout. Reject it up front.
if [[ "$SOURCE" == "fpga" && "${BRIDGE_IP%.*}" != "${FPGA_IP%.*}" ]]; then
echo "ERROR: --bridge-ip $BRIDGE_IP and --fpga-ip $FPGA_IP are on different" >&2
echo " /24 subnets; the FPGA data path is same-subnet routed." >&2
Expand Down Expand Up @@ -332,7 +332,7 @@ fi
# qpu-kernel : udp wire, host dispatch (any decoder incl. trt_decoder)
# qpu-kernel : cpu_roce wire, host dispatch (any decoder incl. trt_decoder)
# fpga : cpu_roce wire, host dispatch (any decoder incl. trt_decoder)
# fpga : hololink wire, device_graph dispatch (nv-qldpc-decoder)
# fpga : gpu_roce wire, device_graph dispatch (nv-qldpc-decoder)
# Everything else is a real configuration of the decoding server that this
# example does not (yet) exercise, so it is rejected with the reason.
# ---------------------------------------------------------------------------
Expand All @@ -359,18 +359,19 @@ if [[ "$SOURCE" == "fpga" && "$DECODER" == "nv-qldpc-decoder" ]] && \
fi

[[ "$WIRE" == "cpu-roce" ]] && WIRE="cpu_roce" # accept both spellings
[[ "$WIRE" == "gpu-roce" ]] && WIRE="gpu_roce"
if [[ -z "$WIRE" ]]; then
if [[ "$SOURCE" == "qpu-kernel" ]]; then WIRE="udp"
elif [[ "$DISPATCH" == "device_graph" ]]; then WIRE="hololink"
elif [[ "$DISPATCH" == "device_graph" ]]; then WIRE="gpu_roce"
else WIRE="cpu_roce"; fi
fi
case "$WIRE" in udp|cpu_roce|hololink) ;;
*) echo "ERROR: --wire must be udp, cpu_roce, or hololink (got '$WIRE')" >&2; exit 1 ;;
case "$WIRE" in udp|cpu_roce|gpu_roce) ;;
*) echo "ERROR: --wire must be udp, cpu_roce, or gpu_roce (got '$WIRE')" >&2; exit 1 ;;
esac

if [[ "$SOURCE" == "qpu-kernel" ]]; then
if [[ "$WIRE" == "hololink" ]]; then
echo "ERROR: the qpu-kernel source is not wired to the hololink wire in" >&2
if [[ "$WIRE" == "gpu_roce" ]]; then
echo "ERROR: the qpu-kernel source is not wired to the gpu_roce wire in" >&2
echo " this example (use --wire udp or --wire cpu_roce)." >&2; exit 1
fi
if [[ "$DISPATCH" != "host" ]]; then
Expand All @@ -383,8 +384,8 @@ else
echo "ERROR: device_graph dispatch serves only nv-qldpc-decoder" >&2
echo " (got '$DECODER'); CPU decoders use --dispatch host." >&2; exit 1
fi
if [[ "$WIRE" != "hololink" ]]; then
echo "ERROR: device_graph dispatch on the FPGA requires the hololink" >&2
if [[ "$WIRE" != "gpu_roce" ]]; then
echo "ERROR: device_graph dispatch on the FPGA requires the gpu_roce" >&2
echo " wire (got '$WIRE')." >&2; exit 1
fi
else
Expand All @@ -395,11 +396,9 @@ else
fi
fi

# The provider library soname is hyphenated (libcudaq-realtime-bridge-cpu-roce.so)
# while the conventional token is cpu_roce; the server's resolver composes the
# soname literally from the token, so pass the hyphenated form on the wire.
# Provider names use the YAML spelling; the server maps underscores to the
# installed providers' hyphenated sonames.
WIRE_TOKEN="$WIRE"
[[ "$WIRE_TOKEN" == "cpu_roce" ]] && WIRE_TOKEN="cpu-roce"

# ---------------------------------------------------------------------------
# qpu-kernel over cpu_roce: RDMA topology. Same four-env-var convention as
Expand Down Expand Up @@ -474,7 +473,7 @@ resolve_paths() {
_err "lowered kernel not found: $KERNEL_BIN (build the example, or --kernel PATH)"; exit 1
fi
if [[ "$SOURCE" == "fpga" && ! -x "$PLAYBACK_BIN" ]]; then
_err "playback tool not found: $PLAYBACK_BIN (a hololink-enabled deliverable)"; exit 1
_err "playback tool not found: $PLAYBACK_BIN (an HSB-enabled deliverable)"; exit 1
fi

# Load path: deliverable libs + plugins, plus the CUDA-Q runtime/realtime.
Expand Down Expand Up @@ -939,20 +938,61 @@ setup_network_cpu_roce() {
fi
}

prepare_gpu_roce_config() {
local peer_ip="$1" remote_qp="$2" num_pages="$3" page_size="$4"
local remote_qp_decimal=$((remote_qp))
local payload_size=$((PAGE_SIZE - 24))
if (( payload_size <= 0 )); then
_err "gpu_roce page size ($PAGE_SIZE) must exceed the 24-byte RPC header"
return 1
fi
if grep -Eq '^[[:space:]]*transport:' "$CONFIG_FILE"; then
_err "generated config already contains a transport section: $CONFIG_FILE"
return 1
fi

# CONFIG_FILE already lives in this run's temporary directory. Strip the
# generator's terminal YAML document marker, then serialize the dynamic
# endpoint once so the server sees one configuration source.
awk '
{ lines[NR] = $0 }
END {
last = NR
while (last > 0 && lines[last] ~ /^[[:space:]]*$/)
--last
if (last > 0 && lines[last] ~ /^[[:space:]]*\.\.\.[[:space:]]*$/)
--last
for (line = 1; line <= last; ++line)
print lines[line]
}
' "$CONFIG_FILE" > "$CONFIG_FILE.tmp" && mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
cat >> "$CONFIG_FILE" <<EOF
transport:
provider: gpu_roce
args:
- --device=$BRIDGE_DEVICE
- --peer-ip=$peer_ip
- --remote-qp=$remote_qp_decimal
- --page-size=$page_size
- --num-pages=$num_pages
- --payload-size=$payload_size
...
EOF
_info "GPU RoCE launch settings written to YAML: $CONFIG_FILE"
}

start_roce_server() {
local peer_ip="$1" remote_qp="$2" server_log="$3"
local device_graph_num_pages="$4" device_graph_page_size="$5"
_log "Starting decoding_server (wire=$WIRE, dispatch=$DISPATCH, remote-qp=$remote_qp)"
local ready
if [[ "$DISPATCH" == "device_graph" ]]; then
# All-device_graph config: the standalone device-graph transceiver
# brings up the wire itself (defaulting to the hololink provider) from
# the QEC_DEVICE_GRAPH_* env; no --transport flag is passed.
# brings up the GPU RoCE wire from the generated YAML; no transport
# environment variables, CLI selector, or provider arguments are used.
prepare_gpu_roce_config "$peer_ip" "$remote_qp" \
"$device_graph_num_pages" "$device_graph_page_size" || return 1
CUDA_MODULE_LOADING=EAGER \
QEC_DEVICE_GRAPH_DEVICE="$BRIDGE_DEVICE" \
QEC_DEVICE_GRAPH_PEER_IP="$peer_ip" \
QEC_DEVICE_GRAPH_REMOTE_QP="$((remote_qp))" \
QEC_DEVICE_GRAPH_FRAME_SIZE="$PAGE_SIZE" \
QEC_DEVICE_GRAPH_NUM_PAGES="$DEVICE_GRAPH_NUM_PAGES" \
"$SERVER_BIN" --config="$CONFIG_FILE" --timeout="$TIMEOUT" \
> >(tee "$server_log") 2>&1 &
ready="QEC_DECODING_SERVER_READY device_graph"
Expand Down Expand Up @@ -991,6 +1031,9 @@ run_fpga() {
# clamps), device_graph dispatch via DEVICE_GRAPH_NUM_PAGES here.
local HSB_WQE_DEPTH=64
local DEVICE_GRAPH_NUM_PAGES="$HSB_WQE_DEPTH"
# GPU RoCE ring slots are 128-byte granular. Keep PAGE_SIZE as the frame
# budget, but give the provider and playback the same aligned slot stride.
local DEVICE_GRAPH_PAGE_SIZE=$(( ((PAGE_SIZE + 127) / 128) * 128 ))
if (( NUM_SLOTS > HSB_WQE_DEPTH )); then
_warn "NUM_SLOTS=$NUM_SLOTS exceeds the HSB WQE depth ($HSB_WQE_DEPTH); clamping"
NUM_SLOTS="$HSB_WQE_DEPTH"
Expand All @@ -1002,8 +1045,8 @@ run_fpga() {
# configs) the server would reject the ring at startup, so fail fast
# with the constraint spelled out.
local host_page; host_page=$(getconf PAGESIZE)
if (( (DEVICE_GRAPH_NUM_PAGES * PAGE_SIZE) % host_page != 0 )); then
_err "device_graph ring ($DEVICE_GRAPH_NUM_PAGES slots x $PAGE_SIZE B) is not a multiple of this host's page size ($host_page B)."
if (( (DEVICE_GRAPH_NUM_PAGES * DEVICE_GRAPH_PAGE_SIZE) % host_page != 0 )); then
_err "device_graph ring ($DEVICE_GRAPH_NUM_PAGES slots x $DEVICE_GRAPH_PAGE_SIZE B) is not a multiple of this host's page size ($host_page B)."
_err "The HSB frame stride must be a multiple of $(( host_page / HSB_WQE_DEPTH )) B on this host; see the unittests"
_err "hsb_fpga_decoding_server_test.sh (--page-size) for a tunable-geometry run."
exit 1
Expand All @@ -1012,7 +1055,8 @@ run_fpga() {
: "${BRIDGE_DEVICE:=${IB_DEVICE:-rocep1s0f0}}"

local server_log="$GEN_DIR/server.log"
start_roce_server "$FPGA_IP" "0x2" "$server_log" || return 1
start_roce_server "$FPGA_IP" "0x2" "$server_log" \
"$DEVICE_GRAPH_NUM_PAGES" "$DEVICE_GRAPH_PAGE_SIZE" || return 1

_log "Streaming syndromes from the FPGA via playback (spacing=${SPACING}us)"
# The FPGA writes syndrome frame rid to RDMA slot (rid % num-pages), so the
Expand All @@ -1022,10 +1066,14 @@ run_fpga() {
# (num_slots). The cpu_roce wire's server ring is NUM_SLOTS; the
# device_graph ring is DEVICE_GRAPH_NUM_PAGES.
local pb_pages="$NUM_SLOTS"
if [[ "$DISPATCH" == "device_graph" ]]; then pb_pages="$DEVICE_GRAPH_NUM_PAGES"; fi
local pb_page_size="$PAGE_SIZE"
if [[ "$DISPATCH" == "device_graph" ]]; then
pb_pages="$DEVICE_GRAPH_NUM_PAGES"
pb_page_size="$DEVICE_GRAPH_PAGE_SIZE"
fi
local args=( --hsb-ip "$FPGA_IP" --per-round --config "$CONFIG_FILE"
--syndromes "$SYNDROMES_FILE" --qp-number "$SERVER_QP" --rkey "$SERVER_RKEY"
--buffer-addr "$SERVER_ADDR" --page-size "$PAGE_SIZE" --num-pages "$pb_pages" )
--buffer-addr "$SERVER_ADDR" --page-size "$pb_page_size" --num-pages "$pb_pages" )
$VERIFY && args+=(--verify)
[[ -n "$NUM_SHOTS" ]] && args+=(--num-shots "$NUM_SHOTS")
[[ -n "$SPACING" ]] && args+=(--spacing "$SPACING")
Expand Down
31 changes: 23 additions & 8 deletions libs/qec/include/cudaq/qec/realtime/decoding_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ struct transport_shape_override {
/// soname is "libcudaq-realtime-bridge-" + name + ".so", with '_' in the
/// name mapping to '-' to match the shipped hyphenated sonames (so
/// gpu_roce loads libcudaq-realtime-bridge-gpu-roce.so).
/// Empty = inherit the section/CLI default.
/// Empty = inherit the section default.
std::string provider;
/// Extra provider arguments appended for this shape's rings.
std::vector<std::string> args;
Expand All @@ -152,24 +152,39 @@ struct transport_shape_override {
/// provider: udp # "gpu_roce" on an HSB rig
/// args: [--pinned-rings]
///
/// Resolution per ring: shape override (device_graph rings) > this
/// section's provider/args > the server's --transport CLI fallback. The
/// CLI flag only applies when this section names no provider; a config
/// that names one plus an explicit --transport is rejected at startup
/// (the deployment file is the source of truth for the wire).
/// Resolution per ring: shape override (device_graph rings) > this section's
/// provider/args. The standalone server permits a CLI fallback only for
/// non-GPU-RoCE, host-only configurations whose YAML names no provider. A
/// configuration containing device_graph dispatch or using GPU RoCE must
/// define its provider and arguments in YAML (the deployment file is the
/// source of truth for GPU-visible rings).
struct transport_config {
std::string provider;
std::vector<std::string> args;
transport_shape_override device_graph;

/// Resolve the provider and ordered arguments for a device-graph ring.
/// The shape-specific provider overrides the section provider, while its
/// arguments are appended to the section arguments.
transport_shape_override resolve_device_graph() const {
transport_shape_override resolved;
resolved.provider =
device_graph.provider.empty() ? provider : device_graph.provider;
resolved.args = args;
resolved.args.insert(resolved.args.end(), device_graph.args.begin(),
device_graph.args.end());
return resolved;
}

bool operator==(const transport_config &) const = default;
};

class multi_decoder_config {
public:
std::vector<decoder_config> decoders;
/// Optional server-level transport section (empty provider/args = not
/// specified; the server's CLI defaults apply).
/// Optional server-level transport section. The standalone server's CLI
/// defaults may fill an empty section only for non-GPU-RoCE, host-only
/// configurations.
transport_config transport;

bool operator==(const multi_decoder_config &) const = default;
Expand Down
11 changes: 6 additions & 5 deletions libs/qec/lib/realtime/decoding-server-cqr/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,9 @@ set_target_properties(cudaq-qec-decoding-server PROPERTIES
# (DeviceGraphFactory.cpp) of the factory that DecodingServer.cpp references
# weakly. Kept out of the core library so core consumers stay loadable on
# machines without the CUDA runtime. The transport itself is a runtime-
# loaded bridge provider (libcudaq-realtime-bridge-gpu-roce.so or a
# CUDAQ_REALTIME_BRIDGE_LIB drop-in), so this component links only the
# CUDA-Q realtime bridge C API and the CUDA runtime -- no GpuRoceTransceiver / DOCA /
# HSB link-time dependencies.
# loaded bridge provider selected by the YAML transport section, so this
# component links only the CUDA-Q realtime bridge C API and the CUDA runtime --
# no GpuRoceTransceiver / DOCA / HSB link-time dependencies.
#
# Consumers must link this WHOLE_ARCHIVE: the only reference to the factory
# is weak, which does not pull archive members on its own.
Expand All @@ -160,7 +159,9 @@ if(CUDAQ_QEC_DEVICE_GRAPH_AVAILABLE)
)

target_compile_definitions(cudaq-qec-decoding-server-device-graph
PRIVATE CUDAQ_QEC_DEVICE_GRAPH_AVAILABLE)
PRIVATE
CUDAQ_QEC_DEVICE_GRAPH_AVAILABLE
QEC_BRIDGE_PROVIDER_DIR="${_cudaq_rt_lib_dir}")

target_include_directories(cudaq-qec-decoding-server-device-graph PRIVATE
"${CUDAQ_REALTIME_INCLUDE_DIR}"
Expand Down
15 changes: 10 additions & 5 deletions libs/qec/lib/realtime/decoding-server-cqr/DecodingServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@
// provides the strong definition of this factory; anywhere else the weak
// reference is null and make_transport throws.
extern "C" __attribute__((weak)) cudaq::qec::decoding_server::ITransceiver *
cudaqx_qec_make_device_graph_transceiver(int pinned_cuda_device);
cudaqx_qec_make_device_graph_transceiver(
int pinned_cuda_device,
const cudaq::qec::decoding::config::transport_shape_override *transport);

namespace cudaq::qec::decoding_server {

using cudaq::qec::decoding::config::DecoderDispatch;
using cudaq::qec::decoding::config::transport_shape_override;

/// Resolve the CUDA device a decode pipeline runs on from the decoder's
/// cuda_device_id pin; an unpinned decoder (-1) defaults to device 0. The
Expand All @@ -43,8 +46,8 @@ int resolve_decode_device(int decoder_pin) {
}

std::unique_ptr<ITransceiver>
DecodingServer::make_transport(DecoderDispatch dispatch,
int pinned_cuda_device) {
DecodingServer::make_transport(DecoderDispatch dispatch, int pinned_cuda_device,
const transport_shape_override &transport) {
switch (dispatch) {
case DecoderDispatch::device_graph:
// device_graph lives in the cudaq-qec-decoding-server-device-graph
Expand All @@ -53,7 +56,8 @@ DecodingServer::make_transport(DecoderDispatch dispatch,
// transceiver config lives; we just thread the pin to it.
if (cudaqx_qec_make_device_graph_transceiver)
return std::unique_ptr<ITransceiver>(
cudaqx_qec_make_device_graph_transceiver(pinned_cuda_device));
cudaqx_qec_make_device_graph_transceiver(pinned_cuda_device,
&transport));
throw std::runtime_error(
"device_graph dispatch requested but the device-graph component is "
"not linked into this binary. Link "
Expand Down Expand Up @@ -95,7 +99,8 @@ DecodingServer::DecodingServer(const std::string &config_yaml) {
boot_sessions.size() == 1
? boot_sessions.begin()->second->dec->get_cuda_device_id()
: -1;
auto t = make_transport(dispatch, pinned_cuda_device);
auto t = make_transport(dispatch, pinned_cuda_device,
config.transport.resolve_device_graph());
ITransceiver *raw = t.get();
owned_transports_.push_back(std::move(t));

Expand Down
12 changes: 7 additions & 5 deletions libs/qec/lib/realtime/decoding-server-cqr/DecodingServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,13 @@ class DecodingServer {
void print_session_stats() const;

private:
/// Create a transceiver for \p dispatch. Throws for host dispatch (served
/// by the CQR plugin) and when the device-graph component is not linked.
static std::unique_ptr<ITransceiver>
make_transport(cudaq::qec::decoding::config::DecoderDispatch dispatch,
int pinned_cuda_device);
/// Create a transceiver for \p dispatch and its resolved YAML transport.
/// Throws for host dispatch (served by the CQR plugin) and when the
/// device-graph component is not linked.
static std::unique_ptr<ITransceiver> make_transport(
cudaq::qec::decoding::config::DecoderDispatch dispatch,
int pinned_cuda_device,
const cudaq::qec::decoding::config::transport_shape_override &transport);

// Destruction order matters: the device-graph scheduler (inside
// owned_transports_) holds a cudaGraphExec_t captured from a session's
Expand Down
14 changes: 12 additions & 2 deletions libs/qec/lib/realtime/decoding-server-cqr/DeviceGraphFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,22 @@
#include "DecodingServer.h" // resolve_decode_device (core symbol)
#include "DeviceGraphTransceiver.h"

#include <stdexcept>

extern "C" cudaq::qec::decoding_server::ITransceiver *
cudaqx_qec_make_device_graph_transceiver(int pinned_cuda_device) {
cudaqx_qec_make_device_graph_transceiver(
int pinned_cuda_device,
const cudaq::qec::decoding::config::transport_shape_override *transport) {
using namespace cudaq::qec::decoding_server;
if (!transport)
throw std::invalid_argument(
"device-graph transport configuration is missing");

// The device-graph GPU is the decoder's cuda_device_id pin; resolve it
// here, inside the component, where DeviceGraphConfig is visible.
auto cfg = DeviceGraphConfig::from_env();
DeviceGraphConfig cfg;
cfg.provider = transport->provider;
cfg.provider_args = transport->args;
cfg.gpu_id = resolve_decode_device(pinned_cuda_device);
return new DeviceGraphTransceiver(cfg);
}
Loading
Loading