-
Notifications
You must be signed in to change notification settings - Fork 74
Add realtime_decoding_demo example: drive the delivered decoding server from an FPGA or a QPU kernel #703
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
Merged
cketcham2333
merged 59 commits into
NVIDIA:main
from
cketcham2333:decoding_server_examples
Aug 3, 2026
Merged
Add realtime_decoding_demo example: drive the delivered decoding server from an FPGA or a QPU kernel #703
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 0c426d0
Merge remote-tracking branch 'upstream/main' into add-relaybp-fpga
cketcham2333 b25778b
decoding-server-cqr: link CUDA::cuda_driver into the gpu_roce block
cketcham2333 d9cdf52
decoding-server-cqr: move GPU RoCE out of the core static lib
cketcham2333 5efaff8
Decoding server: load transports as CUDA-Q bridge providers
bmhowe23 332ef5d
GpuRoceTransceiver: consume the Hololink bridge provider
bmhowe23 d949a1c
Merge branch 'pr670-head' into bmh/decoding-server-bridge-670
bmhowe23 d637d66
Rename the device-dispatch path to what it is: device_graph, not gpu_…
bmhowe23 ac0cb40
Remove pre-release compatibility aliases from the dispatch/transport …
bmhowe23 21831c5
docs: transport-provider design (before/after, contracts, reproductio…
bmhowe23 af8db8e
One ring buffer (and one dispatcher) per decoder, device_id == decode…
bmhowe23 878eee3
Decoding server: one ring per decoder across two processes
bmhowe23 4ba17aa
Mixed-dispatch decoding server: device_graph + host rings in one process
bmhowe23 21d0836
Transport section is server-level and shape-keyed, not per-decoder
bmhowe23 2065319
sc4 caller: per-decoder ring endpoints; record device-ring validation…
bmhowe23 d97157f
Refine WSL2 device-graph diagnosis: cudaErrorNotSupported at kernel l…
bmhowe23 f3fe16b
Correct the device-graph wedge diagnosis: probe was invalid, platform…
bmhowe23 e0ed435
docs: re-scope the realtime design docs into contracts / topology / log
bmhowe23 58e40c1
docs: deployment cookbook (sample YAML + launch lines per scenario)
bmhowe23 ecec808
Fix decode-graph co-residency wedge; full mixed E2E now passes locally
bmhowe23 65f16d9
Adopt the string-keyed bridge loader; drop the one-provider-per-proce…
bmhowe23 f4b19d4
Address self-review findings in the decoding server and CQR components
bmhowe23 b94767a
--transport is a fallback, never an override; test the YAML transport…
bmhowe23 e64f56f
Apply clang-format
bmhowe23 19dd816
docs: refresh the architecture diagram
bmhowe23 2229b85
Pin to custom CUDA-Q version
bmhowe23 9014dd9
Merge origin/main (PR 670 landed + 15 commits) into decoding-server b…
bmhowe23 71d1e68
Validation notes: post-merge revalidation of the per-decoder-rings stack
bmhowe23 c8e3da3
clang-format
bmhowe23 49eb8e1
Generalize the standalone device-graph transceiver for non-HSB providers
bmhowe23 07ce040
Bump cudaq version
bmhowe23 44bec3b
Add realtime_decoding_demo: drive the delivered decoding server from …
cketcham2333 798c82d
clang format
cketcham2333 11108a4
Merge remote-tracking branch 'upstream/main' into decoding_server_exa…
cketcham2333 ae3716b
Rebase realtime_decoding_demo onto the refactored surface_code-1 app
cketcham2333 4d770db
Merge remote-tracking branch 'upstream/main' into decoding_server_exa…
cketcham2333 143fe9a
Address review comments
cketcham2333 bcb21a2
Merge branch 'main' into bmh/decoding-server-bridge-670
bmhowe23 2e68bf1
Addressed additional review comments
cketcham2333 379f3d2
Merge remote-tracking branch 'bmhowe23/bmh/decoding-server-bridge-670…
cketcham2333 51e5862
Fix swallowed --device in DeviceGraphTransceiver's provider argv
cketcham2333 3344a15
Adapt realtime_decoding_demo + hsb driver to the bridge-provider server
cketcham2333 18d0ff0
Merge branch 'main' into bmh/decoding-server-bridge-670
bmhowe23 0f92f55
Fix swallowed --device in DeviceGraphTransceiver's provider argv
cketcham2333 0d6b2d1
Merge branch 'main' into bmh/decoding-server-bridge-670
bmhowe23 4f2343e
Add qpu-kernel-over-cpu_roce runs to the realtime decoding example
cketcham2333 c2a1bf0
Merge remote-tracking branch 'bmhowe23/bmh/decoding-server-bridge-670…
cketcham2333 03217c7
Add the Ising decoder (trt_decoder) profile to the realtime decoding …
cketcham2333 e139c40
clang format
cketcham2333 65a6820
Fix two FPGA-run footguns: nv-qldpc auto-pacing + bridge-ip subnet guard
cketcham2333 4bdea49
Merge remote-tracking branch 'upstream/main' into decoding_server_exa…
cketcham2333 5cd9751
FPGA runs: fix the hsb endpoint scrape; auto-pace slow decoders at 100us
cketcham2333 dba6725
realtime_decoding_demo: adopt the prepare_ising_artifacts.py workflow
cketcham2333 9d846a5
Remove unneeded documents left over from early PR 682 merge
cketcham2333 a8ffb2e
realtime_decoding_demo: code-review fixes (precedence, subnet guard, …
cketcham2333 92de54b
Address more review comments
cketcham2333 ae1383a
Merge remote-tracking branch 'upstream/main' into decoding_server_exa…
cketcham2333 921e4bb
Merge remote-tracking branch 'upstream/main' into decoding_server_exa…
cketcham2333 5bb0ffc
Fix semantic merge leftovers from the hololink->hsb rename (PR 754)
cketcham2333 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
167
docs/sphinx/examples/qec/realtime_decoding_demo/CMakeLists.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
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) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.