Skip to content

Out of bound access in the test_graph_update test #843

Closed

Description

@leofang

CI log: https://github.com/NVIDIA/cuda-python/actions/runs/16994945071/job/48183959433

tests/test_graph.py::test_graph_update ========= Invalid __global__ read of size 4 bytes
=========     at add_one(int *)+0x40
=========     by thread (0,0,0) in block (0,0,0)
=========     Access at 0x7c547b000000 is out of bounds
=========     and is inside the nearest allocation at 0x7c547b000000 of size 12 bytes
=========     Saved host backtrace up to driver entry point at kernel launch time
=========         Host Frame:  [0x3f2bd] in cydriver.cpython-312-x86_64-linux-gnu.so
=========         Host Frame:  [0xf4e9] in cydriver.cpython-312-x86_64-linux-gnu.so
=========         Host Frame:  [0x3fe25d] in driver.cpython-312-x86_64-linux-gnu.so
=========         Host Frame: PyObject_Vectorcall in call.c:325 [0x210c94] in libpython3.12.so.1.0
=========         Host Frame: _PyEval_EvalFrameDefault in bytecodes.c:2715 [0x2606fd] in libpython3.12.so.1.0
=========         Host Frame: _PyObject_Call_Prepend in call.c:508 [0x2114b6] in libpython3.12.so.1.0
=========         Host Frame: slot_tp_call in typeobject.c:8791 [0x2d3572] in libpython3.12.so.1.0
=========         Host Frame: _PyObject_MakeTpCall in call.c:240 [0x210962] in libpython3.12.so.1.0
=========         Host Frame: _PyEval_EvalFrameDefault in bytecodes.c:2715 [0x2606fd] in libpython3.12.so.1.0
=========         Host Frame: _PyObject_Call_Prepend in call.c:508 [0x2114b6] in libpython3.12.so.1.0
=========         Host Frame: slot_tp_call in typeobject.c:8791 [0x2d3572] in libpython3.12.so.1.0
=========         Host Frame: PyObject_Call [0x211b9e] in libpython3.12.so.1.0
=========         Host Frame: _PyEval_EvalFrameDefault in bytecodes.c:3263 [0x264f1b] in libpython3.12.so.1.0
=========         Host Frame: _PyObject_Call_Prepend in call.c:508 [0x2114b6] in libpython3.12.so.1.0
=========         Host Frame: slot_tp_call in typeobject.c:8791 [0x2d3572] in libpython3.12.so.1.0
=========         Host Frame: _PyObject_MakeTpCall in call.c:240 [0x210962] in libpython3.12.so.1.0
=========         Host Frame: _PyEval_EvalFrameDefault in bytecodes.c:2715 [0x2606fd] in libpython3.12.so.1.0
=========         Host Frame: _PyObject_Call_Prepend in call.c:508 [0x2114b6] in libpython3.12.so.1.0
=========         Host Frame: slot_tp_call in typeobject.c:8791 [0x2d3572] in libpython3.12.so.1.0
=========         Host Frame: _PyObject_MakeTpCall in call.c:240 [0x210962] in libpython3.12.so.1.0
=========         Host Frame: _PyEval_EvalFrameDefault in bytecodes.c:2715 [0x2606fd] in libpython3.12.so.1.0
=========         Host Frame: _PyObject_Call_Prepend in call.c:508 [0x2114b6] in libpython3.12.so.1.0
=========         Host Frame: slot_tp_call in typeobject.c:8791 [0x2d3572] in libpython3.12.so.1.0
=========         Host Frame: _PyObject_MakeTpCall in call.c:240 [0x210962] in libpython3.12.so.1.0
=========         Host Frame: _PyEval_EvalFrameDefault in bytecodes.c:2715 [0x2606fd] in libpython3.12.so.1.0
=========         Host Frame: PyEval_EvalCode in ceval.c:580 [0x2fa2f5] in libpython3.12.so.1.0
=========         Host Frame: run_eval_code_obj in pythonrun.c:1757 [0x31b427] in libpython3.12.so.1.0
=========         Host Frame: run_mod in pythonrun.c:1778 [0x31af6a] in libpython3.12.so.1.0
=========         Host Frame: pyrun_file in pythonrun.c:1674 [0x31aeb0] in libpython3.12.so.1.0
=========         Host Frame: _PyRun_SimpleFileObject in pythonrun.c:459 [0x31ace8] in libpython3.12.so.1.0
=========         Host Frame: _PyRun_AnyFileObject in pythonrun.c:78 [0x31ab06] in libpython3.12.so.1.0
=========         Host Frame: Py_RunMain in main.c:714 [0x324274] in libpython3.12.so.1.0
=========         Host Frame: Py_BytesMain in main.c:768 [0x323d3c] in libpython3.12.so.1.0
=========         Host Frame:  [0x29d8f] in libc.so.6
=========         Host Frame: __libc_start_main [0x29e3f] in libc.so.6
=========         Host Frame: _start [0x1094] in python
=========         Host Frame: launch in _graph.py:782
=========         Host Frame: test_graph_update in test_graph.py:612
=========         Host Frame: pytest_pyfunc_call in python.py:157
=========         Host Frame: _multicall in _callers.py:121
=========         Host Frame: _hookexec in _manager.py:120
=========         Host Frame: __call__ in _hooks.py:512
=========         Host Frame: runtest in python.py:1671
=========         Host Frame: pytest_runtest_call in runner.py:178
=========         Host Frame: _multicall in _callers.py:121
=========         Host Frame: _hookexec in _manager.py:120
=========         Host Frame: __call__ in _hooks.py:512
=========         Host Frame: <lambda> in runner.py:246
=========         Host Frame: from_call in runner.py:344
=========         Host Frame: call_and_report in runner.py:245
=========         Host Frame: runtestprotocol in runner.py:136
=========         Host Frame: pytest_runtest_protocol in runner.py:117
=========         Host Frame: _multicall in _callers.py:121
=========         Host Frame: _hookexec in _manager.py:120
=========         Host Frame: __call__ in _hooks.py:512
=========         Host Frame: pytest_runtestloop in main.py:367
=========         Host Frame: _multicall in _callers.py:121
=========         Host Frame: _hookexec in _manager.py:120
=========         Host Frame: __call__ in _hooks.py:512
=========         Host Frame: _main in main.py:343
=========         Host Frame: wrap_session in main.py:289
=========         Host Frame: pytest_cmdline_main in main.py:336
=========         Host Frame: _multicall in _callers.py:121
=========         Host Frame: _hookexec in _manager.py:120
=========         Host Frame: __call__ in _hooks.py:512
=========         Host Frame: main in __init__.py:175
========= 
FAILED                            [ 38%]

Activity

  1. added
    triageNeeds the team's attention
    cuda.coreEverything related to the cuda.core module
    on Aug 15, 2025
  2. leofang commented on Aug 15, 2025

    @leofang
    MemberAuthor

    This might explain the occasional CI crashes we've seen. @oleksandr-pavlyk would you have time next week to take a look?

  3. moved this from Todo to Needs Triage in CCCLon Aug 15, 2025
  4. moved this from Needs Triage to No status in CCCLon Aug 15, 2025
  5. oleksandr-pavlyk commented on Aug 18, 2025

    @oleksandr-pavlyk
    Contributor

    Can reproduce, sporadically.

    Creating environment
    # This file may be used to create an environment using:
    # $ conda create --name <env> --file <this file>
    # platform: linux-64
    # created-by: conda 24.11.3
    @EXPLICIT
    https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2
    https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda
    https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.9-h4f385c5_3.conda
    https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1423503_1.conda
    https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_4.conda
    https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda
    https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2
    https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.9.27-ha770c72_0.conda
    https://conda.anaconda.org/conda-forge/noarch/cuda-crt-dev_linux-64-12.9.86-ha770c72_2.conda
    https://conda.anaconda.org/conda-forge/linux-64/cuda-crt-tools-12.9.86-ha770c72_2.conda
    https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-static_linux-64-12.9.79-h3f2d84a_0.conda
    https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.9.79-h3f2d84a_0.conda
    https://conda.anaconda.org/conda-forge/noarch/cuda-nvvm-dev_linux-64-12.9.86-ha770c72_2.conda
    https://conda.anaconda.org/conda-forge/noarch/libnvptxcompiler-dev_linux-64-12.9.86-ha770c72_2.conda
    https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-dev_linux-64-12.9.79-h3f2d84a_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_4.conda
    https://conda.anaconda.org/conda-forge/linux-64/libnvptxcompiler-dev-12.9.86-ha770c72_2.conda
    https://conda.anaconda.org/conda-forge/noarch/cuda-nvcc-dev_linux-64-12.9.86-he91c749_2.conda
    https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-impl-12.9.86-h4bc722e_2.conda
    https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.9.86-h4bc722e_2.conda
    https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda
    https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_4.conda
    https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_4.conda
    https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda
    https://conda.anaconda.org/conda-forge/linux-64/libnl-3.11.0-hb9d3cd8_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda
    https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_4.conda
    https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda
    https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda
    https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2
    https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda
    https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-12.9.79-h5888daf_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-static-12.9.79-h5888daf_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/cuda-nvcc-tools-12.9.86-he02047a_2.conda
    https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.9.86-h5888daf_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_4.conda
    https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.55-h3f2d84a_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.9.86-h5888daf_1.conda
    https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda
    https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda
    https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda
    https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda
    https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda
    https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-dev-12.9.79-h5888daf_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/libcap-2.75-h39aace5_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-lib-1.11.1-hb9d3cd8_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_2.conda
    https://conda.anaconda.org/conda-forge/linux-64/python-3.12.11-h9e4cc4f_0_cpython.conda
    https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda
    https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda
    https://conda.anaconda.org/conda-forge/linux-64/cuda-nvcc-impl-12.9.86-h85509e4_2.conda
    https://conda.anaconda.org/conda-forge/noarch/cuda-pathfinder-1.1.0-pyhe01879c_0.conda
    https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda
    https://conda.anaconda.org/conda-forge/noarch/executing-2.2.0-pyhd8ed1ab_0.conda
    https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda
    https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-34_h59b9bed_openblas.conda
    https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-257.7-h4e0b6ca_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/libudev1-257.7-hbe16f8c_0.conda
    https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda
    https://conda.anaconda.org/conda-forge/noarch/parso-0.8.4-pyhd8ed1ab_1.conda
    https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda
    https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda
    https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda
    https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda
    https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda
    https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda
    https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda
    https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda
    https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.14.1-pyhe01879c_0.conda
    https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda
    https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda
    https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda
    https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda
    https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda
    https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-34_he106b2a_openblas.conda
    https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-34_h7ac8fdf_openblas.conda
    https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda
    https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda
    https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda
    https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/rdma-core-58.0-h5888daf_0.conda
    https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda
    https://conda.anaconda.org/conda-forge/noarch/ipython-9.4.0-pyhfa0c392_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/libcufile-1.14.1.1-ha8da6e3_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.2-py312h33ff503_0.conda
    https://conda.anaconda.org/conda-forge/noarch/pytest-8.4.1-pyhd8ed1ab_0.conda
    https://conda.anaconda.org/conda-forge/linux-64/cuda-bindings-12.9.1-py312hebe1815_1.conda
    https://conda.anaconda.org/conda-forge/linux-64/cuda-core-0.3.2-py312h38fbac0_0.conda
    

    Create and activate conda environment using the explicit file provided above.

    Then, using compute sanitizer from 12.9 CTK,

    /usr/local/cuda/bin/compute-sanitizer               \
         --target-processes=all                         \
         --launch-timeout=0                             \
         --tool=memcheck                                \
         --error-exitcode=1                             \
         --report-api-errors=no                         \
         --padding=32                                   \
         pytest repos/cuda-python/cuda_core/tests/

    It helps to remove/reinstate the padding CLI argument to expedite reproducing. The issue observed:

    repos/cuda-python/cuda_core/tests/test_graph.py .................
    ========= Invalid __global__ read of size 4 bytes
    =========     at add_one(int *)+0x40
    =========     by thread (0,0,0) in block (0,0,0)
    =========     Access at 0x7ab0ec200000 is out of bounds
    =========     and is inside the nearest allocation at 0x7ab0ec200000 of size 12 bytes
    =========     Saved host backtrace up to driver entry point at kernel launch time
    =========         Host Frame:  [0x3147e] in cydriver.cpython-312-x86_64-linux-gnu.so
    =========         Host Frame:  [0x14a39] in cydriver.cpython-312-x86_64-linux-gnu.so
    =========         Host Frame:  [0x32deae] in driver.cpython-312-x86_64-linux-gnu.so
    =========         Host Frame: PyObject_Vectorcall in call.c:325 [0x211ddd] in python3.12
    <<< elided >>>
    

    The traceback points to exception occurring while launching the first graph:

    =========         Host Frame: launch in _graph.py:782
    =========         Host Frame: test_graph_update in test_graph.py:612
    =========         Host Frame: pytest_pyfunc_call in python.py:157
    

    https://github.com/NVIDIA/cuda-python/blob/main/cuda_core/tests/test_graph.py#L607-L616

  6. oleksandr-pavlyk commented on Aug 18, 2025

    @oleksandr-pavlyk
    Contributor

    Incidentally, if test_graph.py is being tested stand-alone, the failure occurs in a different test:

    /usr/local/cuda/bin/compute-sanitizer \
       --target-processes=all             \
       --launch-timeout=0                 \
       --tool=memcheck                    \
       --error-exitcode=1                 \
       --report-api-errors=no             \
       --padding=32                       \
       pytest -v --maxfail=1 repos/cuda-python/cuda_core/tests/test_graph.py
    

    failing in test_graph.py::test_graph_conditional_switch[0] with cuda.core.experimental._utils.cuda_utils.CUDAError: CUDA_ERROR_ILLEGAL_ADDRESS.

    When --report-api-errors is set to all, even for green run of the test file, a lot of errors like

    ========= Program hit CUDA_ERROR_INVALID_CONTEXT (error 201) due to "invalid device context" on CUDA API call to cuCtxGetDevice.
    

    are reported by compute sanitizer.

  7. oleksandr-pavlyk commented on Aug 18, 2025

    @oleksandr-pavlyk
    Contributor

    The CUDA_ERROR_INVALID_CONTEXT arises from init_cuda fixture:

    :~$ /usr/local/cuda/bin/compute-sanitizer --target-processes=all --launch-timeout=0 --tool=memcheck --error-exitcode=1 --report-api-errors=all --padding=32 pytest -v repos/cuda-python/cuda_core/tests/test_graph.py::test_graph_update
    ========= COMPUTE-SANITIZER
    ================================================================================================================= test session starts ==================================================================================================================
    platform linux -- Python 3.12.11, pytest-8.4.1, pluggy-1.6.0 -- /home/opavlyk/miniforge/envs/test-graph-update-843/bin/python3.12
    cachedir: .pytest_cache
    rootdir: /home/opavlyk/repos/cuda-python/cuda_core/tests
    configfile: pytest.ini
    collected 1 item                                                                                                                                                                                                                                       
    
    repos/cuda-python/cuda_core/tests/test_graph.py::test_graph_update ========= Program hit CUDA_ERROR_INVALID_CONTEXT (error 201) due to "invalid device context" on CUDA API call to cuCtxGetDevice.
    =========     Saved host backtrace up to driver entry point at error
    =========         Host Frame:  [0x578d7] in cydriver.cpython-312-x86_64-linux-gnu.so
    =========         Host Frame:  [0xc119] in cydriver.cpython-312-x86_64-linux-gnu.so
    =========         Host Frame:  [0x402b40] in driver.cpython-312-x86_64-linux-gnu.so
    =========         Host Frame: PyObject_Vectorcall in call.c:325 [0x211ddd] in python3.12
    =========         Host Frame: _PyEval_EvalFrameDefault.cold in bytecodes.c:2715 [0x112b22] in python3.12
    =========         Host Frame: _PyObject_FastCallDictTstate in call.c:133 [0x201b6d] in python3.12
    =========         Host Frame: slot_tp_new in typeobject.c:9065 [0x248089] in python3.12
    =========         Host Frame: _PyObject_MakeTpCall in call.c:240 [0x1ff062] in python3.12
    =========         Host Frame: _PyEval_EvalFrameDefault.cold in bytecodes.c:2715 [0x112b22] in python3.12
    =========         Host Frame: builtin_next in bltinmodule.c:1494 [0x265d2d] in python3.12
    =========         Host Frame: cfunction_vectorcall_FASTCALL in methodobject.c:422 [0x21cf2a] in python3.12
    =========         Host Frame: PyObject_Vectorcall in call.c:325 [0x211ddd] in python3.12
    =========         Host Frame: _PyEval_EvalFrameDefault.cold in bytecodes.c:2715 [0x112b22] in python3.12
    =========         Host Frame: _PyObject_FastCallDictTstate in call.c:144 [0x201c11] in python3.12
    =========         Host Frame: _PyObject_Call_Prepend in call.c:508 [0x22a638] in python3.12
    =========         Host Frame: slot_tp_call in typeobject.c:8791 [0x2f70ca] in python3.12
    =========         Host Frame: _PyObject_MakeTpCall in call.c:240 [0x1ff17b] in python3.12
    =========         Host Frame: _PyEval_EvalFrameDefault.cold in bytecodes.c:2715 [0x112b22] in python3.12
    =========         Host Frame: _PyObject_FastCallDictTstate in call.c:144 [0x201c11] in python3.12
    =========         Host Frame: _PyObject_Call_Prepend in call.c:508 [0x22a638] in python3.12
    =========         Host Frame: slot_tp_call in typeobject.c:8791 [0x2f70ca] in python3.12
    =========         Host Frame: _PyObject_Call in call.c:367 [0x22d0a4] in python3.12
    =========         Host Frame: _PyEval_EvalFrameDefault.cold in bytecodes.c:3263 [0x1136ef] in python3.12
    =========         Host Frame: _PyObject_FastCallDictTstate in call.c:144 [0x201c11] in python3.12
    =========         Host Frame: _PyObject_Call_Prepend in call.c:508 [0x22a638] in python3.12
    =========         Host Frame: slot_tp_call in typeobject.c:8791 [0x2f70ca] in python3.12
    =========         Host Frame: _PyObject_MakeTpCall in call.c:240 [0x1ff17b] in python3.12
    =========         Host Frame: _PyEval_EvalFrameDefault.cold in bytecodes.c:2715 [0x112b22] in python3.12
    =========         Host Frame: _PyObject_FastCallDictTstate in call.c:144 [0x201c11] in python3.12
    =========         Host Frame: _PyObject_Call_Prepend in call.c:508 [0x22a638] in python3.12
    =========         Host Frame: slot_tp_call in typeobject.c:8791 [0x2f70ca] in python3.12
    =========         Host Frame: _PyObject_MakeTpCall in call.c:240 [0x1ff17b] in python3.12
    =========         Host Frame: _PyEval_EvalFrameDefault.cold in bytecodes.c:2715 [0x112b22] in python3.12
    =========         Host Frame: _PyObject_FastCallDictTstate in call.c:144 [0x201c11] in python3.12
    =========         Host Frame: _PyObject_Call_Prepend in call.c:508 [0x22a638] in python3.12
    =========         Host Frame: slot_tp_call in typeobject.c:8791 [0x2f70ca] in python3.12
    =========         Host Frame: _PyObject_MakeTpCall in call.c:240 [0x1ff17b] in python3.12
    =========         Host Frame: _PyEval_EvalFrameDefault.cold in bytecodes.c:2715 [0x112b22] in python3.12
    =========         Host Frame: PyEval_EvalCode in ceval.c:580 [0x2b01a0] in python3.12
    =========         Host Frame: run_eval_code_obj in pythonrun.c:1757 [0x2ea8c9] in python3.12
    =========         Host Frame: run_mod in pythonrun.c:1778 [0x2e5584] in python3.12
    =========         Host Frame: pyrun_file in pythonrun.c:1674 [0x2e261f] in python3.12
    =========         Host Frame: _PyRun_SimpleFileObject in pythonrun.c:459 [0x2e22bd] in python3.12
    =========         Host Frame: _PyRun_AnyFileObject in pythonrun.c:78 [0x2e1fe3] in python3.12
    =========         Host Frame: Py_RunMain in main.c:714 [0x2deeb1] in python3.12
    =========         Host Frame: Py_BytesMain in main.c:768 [0x29a246] in python3.12
    =========         Host Frame: __libc_start_call_main in libc_start_call_main.h:58 [0x2a1c9] in libc.so.6
    =========         Host Frame: __libc_start_main in libc-start.c:360 [0x2a28a] in libc.so.6
    =========         Host Frame:  [0x29a0ec] in python3.12
    =========         Host Frame: __new__ in _device.py:976
    =========         Host Frame: init_cuda in conftest.py:24
    =========         Host Frame: call_fixture_func in fixtures.py:922
    =========         Host Frame: pytest_fixture_setup in fixtures.py:1195
    =========         Host Frame: _multicall in _callers.py:121
    =========         Host Frame: _hookexec in _manager.py:120
    =========         Host Frame: __call__ in _hooks.py:512
    =========         Host Frame: execute in fixtures.py:1127
    =========         Host Frame: _get_active_fixturedef in fixtures.py:639
    =========         Host Frame: getfixturevalue in fixtures.py:548
    =========         Host Frame: _fillfixtures in fixtures.py:719
    =========         Host Frame: setup in python.py:1674
    =========         Host Frame: setup in runner.py:514
    =========         Host Frame: pytest_runtest_setup in runner.py:164
    =========         Host Frame: _multicall in _callers.py:121
    =========         Host Frame: _hookexec in _manager.py:120
    =========         Host Frame: __call__ in _hooks.py:512
    =========         Host Frame: <lambda> in runner.py:246
    =========         Host Frame: from_call in runner.py:344
    =========         Host Frame: call_and_report in runner.py:245
    =========         Host Frame: runtestprotocol in runner.py:130
    =========         Host Frame: pytest_runtest_protocol in runner.py:117
    =========         Host Frame: _multicall in _callers.py:121
    =========         Host Frame: _hookexec in _manager.py:120
    =========         Host Frame: __call__ in _hooks.py:512
    =========         Host Frame: pytest_runtestloop in main.py:367
    =========         Host Frame: _multicall in _callers.py:121
    =========         Host Frame: _hookexec in _manager.py:120
    =========         Host Frame: __call__ in _hooks.py:512
    =========         Host Frame: _main in main.py:343
    =========         Host Frame: wrap_session in main.py:289
    =========         Host Frame: pytest_cmdline_main in main.py:336
    =========         Host Frame: _multicall in _callers.py:121
    =========         Host Frame: _hookexec in _manager.py:120
    =========         Host Frame: __call__ in _hooks.py:512
    =========         Host Frame: main in __init__.py:175
    ========= 
    PASSED                                                                                                                                                                        [100%]
    
    ================================================================================================================== 1 passed in 0.59s ===================================================================================================================
    ========= ERROR SUMMARY: 1 error
    

    Note the following host frames:

    =========         Host Frame: __new__ in _device.py:976
    =========         Host Frame: init_cuda in conftest.py:24
    =========         Host Frame: call_fixture_func in fixtures.py:922
    

    These may be unrelated to the out-of-bound access issue though

  8. leofang commented on Aug 18, 2025

    @leofang
    MemberAuthor

    btw cuda.core currently requires --report-api-errors=no

  9. oleksandr-pavlyk commented on Aug 18, 2025

    @oleksandr-pavlyk
    Contributor

    No real progress, but the issue can be reproduced with

    Reproducing stand-alone script
    import numpy as np
    import cuda.core.experimental as core
    
    
    from cuda.bindings import nvrtc
    from cuda.core.experimental import (
        Device,
        GraphBuilder,
        GraphCompleteOptions,
        GraphDebugPrintOptions,
        LaunchConfig,
        LegacyPinnedMemoryResource,
        Program,
        ProgramOptions,
        launch,
    )   
    from cuda.core.experimental._utils.cuda_utils import NVRTCError, handle_return
    
    
    def _common_kernels():
        code = """
        __global__ void add_one(int *a) { *a += 1; }
        """
        arch = "".join(f"{i}" for i in Device().compute_capability)
        program_options = ProgramOptions(std="c++17", arch=f"sm_{arch}")
        prog = Program(code, code_type="c++", options=program_options)
        mod = prog.compile("cubin", name_expressions=("add_one",))
        return mod
    
    
    def main():
        mod = _common_kernels()
        add_one = mod.get_kernel("add_one")
    
        # Allocate memory
        launch_stream = Device().create_stream()
        mr = LegacyPinnedMemoryResource()
        b = mr.allocate(12)
        print(int(b.handle))
        arr = np.from_dlpack(b).view(np.int32)
        arr.fill(0)
    
        def add_case(gb_case, ptr):
            gb_case.begin_building()
            for _ in range(3):
                launch(gb_case, LaunchConfig(grid=1, block=1), add_one, ptr)
            gb_case.end_building()
    
        def build_graph(condition_value):
            # Begin capture
            gb = Device().create_graph_builder().begin_building()
        
            # Add Node A (sets condition)
            handle = gb.create_conditional_handle(default_value=condition_value)
    
            # Add Node B (while condition)
            gb_case = list(gb.switch(handle, 3))
            for case_id in range(3):
                ## Case 0
                add_case(gb_case[case_id], arr[case_id:].ctypes.data)
            return gb.end_building()
    
        try:
            graph_variants = [build_graph(0), build_graph(1), build_graph(2)]
        except Exception as e:
            b.close()
            exit(1)
    
        # Launch the first graph
        assert np.all(arr[:3] == [0, 0, 0])
        graph = graph_variants[0].complete()
        print(arr.ctypes.data)
        graph.launch(launch_stream)
        launch_stream.sync()
        assert np.all(arr[:3] == [3, 0, 0])
    
        print("====" * 20)
    
        # Update with second variant and launch again
        graph.update(graph_variants[1])
        graph.launch(launch_stream)
        launch_stream.sync()
        assert np.all(arr[:3] == [3, 3, 0])
        
        print("+=+=" * 20)
    
        # Update with third variant and launch again
        graph.update(graph_variants[2])
        graph.launch(launch_stream)
        launch_stream.sync() 
        assert np.all(arr[:3] == [3, 3, 3])
        print("++++" * 20)
        
        # Close the memory resource now because the garbage collected might
        # de-allocate it during the next graph builder process
        b.close()
    
    
    if __name__ == "__main__":
        Device().set_current()
        main()

    Stand-alone it runs fine:

    $ python run.py 
    136199391936512
    136199391936512
    ================================================================================
    +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    Launching the same with compute sanitizer /usr/local/cuda/bin/compute-sanitizer --report-api-errors=no python run.py trips CUDAError: CUDA_ERROR_ILLEGAL_ADDRESS.

  10. oleksandr-pavlyk commented on Aug 18, 2025

    @oleksandr-pavlyk
    Contributor

    I should add that my driver is 575.57.08:

    (test-graph-update-843) opavlyk@ee09c48-lcedt:~/tmp$ nvidia-smi
    Mon Aug 18 15:26:42 2025       
    +-----------------------------------------------------------------------------------------+
    | NVIDIA-SMI 575.57.08              Driver Version: 575.57.08      CUDA Version: 12.9     |
    |-----------------------------------------+------------------------+----------------------+
    
  11. oleksandr-pavlyk commented on Aug 18, 2025

    @oleksandr-pavlyk
    Contributor

    @leofang I find that changing the example away from using LegacyPinnedMemoryResource to using DeviceMemoryResource and injecting copies from device allocation back to host for validation resolves the crash.

    I can open a PR to see if this would fix the issue with CI. Pursuing triage with pinned memory separately.

  12. kkraus14 commented on Aug 19, 2025

    @kkraus14
    Collaborator

    It sounds like we're missing a synchronization somewhere then where running against pinned host memory would keep things asynchronous, whereas presumably the device to host copy you injected used pageable host memory which synchronized things?

  13. leofang commented on Aug 19, 2025

    @leofang
    MemberAuthor

    Sasha mentioned offline that the sanitizer team noted this might be a compute-sanitizer bug. He'll update this thread soon-ish after investigation.

  14. rwgk commented on Aug 20, 2025

    @rwgk
    Contributor

    JIC it's useful, here is another flake of this kind:

    tests/test_graph.py::test_graph_update ========= Invalid __global__ read of size 4 bytes
    

    The rerun succeeded.

  15. oleksandr-pavlyk commented on Aug 21, 2025

    @oleksandr-pavlyk
    Contributor

    I have finished building C++ stand-alone executable that builds same graphs as test_graph.py. It runs fine both stand-alone, and under CTK 12.9 compute sanitizer:

    $ /usr/local/cuda/bin/compute-sanitizer   \
         --target-processes=all               \
         --launch-timeout=0                   \
         --tool=memcheck                      \
         --error-exitcode=1                   \
         --report-api-errors=no               \
         --padding=32                         \
          ./a.out 
    ========= COMPUTE-SANITIZER
    Number of CUDA devices found: 2
    Using device: NVIDIA RTX A6000, CC: (8, 6)
    test_graph_is_building ...OK
    test_graph_straight ...OK
    test_graph_fork_join ...OK
    test_graph_is_join_required ...OK
    test_graph_repeat_capture ...OK
    test_graph_conditional_if ...OK
    test_graph_conditional_if_else ...OK
    test_graph_conditional_switch ...OK
    test_graph_conditional_while ...OK
    test_graph_child ...OK
    test_graph_update ...OK
    ========= ERROR SUMMARY: 0 errors
    

    I have attempted several dozen runs, without running into any problem.

    To execute ELF, two files are expected to be present in the same folder, "simple_kernels.cubin" and "conditional_kernels.cubin". These were constructed as follows:

    import test_graph
    common_mod = test_graph._common_kernels()
    
    with open('simple_kernels.cubin', 'wb') as fh:
        fh.write(common_mod.code)
    
    cond_mod = test_graph._common_kernels_conditional(
    
    with open('conditional_kernels.cubin', 'wb') as fh:
        fh.write(cond_mod.code)
    test_graph.cpp
    // g++ -std=c++20 test_graph.cpp -I /usr/local/cuda/include -L /usr/local/cuda/lib -lcuda
    
    #include <cuda.h>
    
    #include <cassert>
    #include <fstream>
    #include <sstream>
    #include <iostream>
    #include <stdexcept>
    #include <string>
    
    #include <string_view>
    #include <source_location>
    
    namespace {
    
    void
    check(CUresult status, const std::source_location location = std::source_location::current())
    {
       if (CUDA_SUCCESS != status) {
          std::stringstream ss;
          ss << "CUDA Driver API error [";
          const char *error_name;
          CUresult st1 = cuGetErrorName(status, &error_name);
          if (CUDA_SUCCESS == st1) {
              ss << error_name;
          } else {
              ss << "unknown";
          }
          ss << "] : ";
          const char *error_string;
          CUresult st = cuGetErrorString(status, &error_string);
          if (CUDA_SUCCESS == st) {
             ss << error_string;
          } else {
             ss << "code(" << std::to_string(status) << ")";
          }
          ss << ", file: " << location.file_name()
             << "(" << location.line() << ":" << location.column() << ") `"
             << location.function_name() << "`";
    
    
    
          throw std::runtime_error(ss.str());
      }
    }
    
    
    bool array_cmp_equal(int *actual, int *expected, size_t n)
    {
      bool cmp_equal = true;
      for(size_t i = 0; i < n; ++i) {
         cmp_equal = cmp_equal && (actual[i] == expected[i]);
      }
    
      return cmp_equal;
    }
    
    }
    
    
    void test_graph_is_building(CUstream);
    void test_graph_straight(CUstream, CUfunction);
    void test_graph_fork_join(CUstream, CUfunction);
    void test_graph_is_join_required(CUstream, CUfunction);
    void test_graph_repeat_capture(CUstream, CUfunction);
    
    void test_graph_conditional_if(CUstream, CUfunction, CUfunction, int);
    void test_graph_conditional_if_else(CUstream, CUfunction, CUfunction, int);
    void test_graph_conditional_switch(CUstream, CUfunction, CUfunction, int);
    void test_graph_conditional_while(CUstream, CUfunction, CUfunction, CUfunction, int condition_value);
    
    void test_graph_child(CUstream, CUfunction);
    void test_graph_update(CUstream, CUfunction);
    
    void test_graph_is_building(CUstream launch_stream) {
       CUdevice this_dev;
       check(cuStreamGetDevice(launch_stream, &this_dev));
    
       CUstream capt_stream;
       check(cuStreamCreate(&capt_stream, this_dev));
    
       check(cuStreamBeginCapture(capt_stream, CU_STREAM_CAPTURE_MODE_RELAXED));
    
       CUstreamCaptureStatus capt_status;
       check(cuStreamIsCapturing(capt_stream, &capt_status));
    
       if (capt_status != CU_STREAM_CAPTURE_STATUS_ACTIVE) {
          std::cerr << "Unexpected capturing status: " << capt_status << std::endl;
          std::terminate();
       }
    
       // build graph
       CUgraph graph;
       check(cuStreamEndCapture(capt_stream, &graph));
    
       // clean-up
       check(cuGraphDestroy(graph));
       check(cuStreamDestroy(capt_stream));
    }
    
    void create_node(CUstream launch_stream, CUfunction krn, int n = 1) {
       // launch kernel
       CUlaunchConfig config;
       config.blockDimX = 1; config.gridDimX = 1;
       config.blockDimY = 1; config.gridDimY = 1;
       config.blockDimZ = 1; config.gridDimZ = 1;
       config.attrs = nullptr; config.numAttrs = 0;
       config.hStream = launch_stream;
       config.sharedMemBytes = 0;
    
       // launch n times
       void **kernel_params = nullptr;
       void **extra = nullptr;
    
       for(int i = 0; i < n; ++i) {
          check(cuLaunchKernelEx(&config, krn, kernel_params, extra));
       }
    }
    
    void test_graph_straight(CUstream launch_stream, CUfunction krn) {
       CUdevice this_dev;
       check(cuStreamGetDevice(launch_stream, &this_dev));
    
       CUstream capt_stream;
       check(cuStreamCreate(&capt_stream, this_dev));
    
       // start capturing
       check(cuStreamBeginCapture(capt_stream, CU_STREAM_CAPTURE_MODE_RELAXED));
    
       CUstreamCaptureStatus capt_status;
       check(cuStreamIsCapturing(capt_stream, &capt_status));
    
       if (capt_status != CU_STREAM_CAPTURE_STATUS_ACTIVE) {
          std::cerr << "Unexpected capturing status: " << capt_status << std::endl;
          std::terminate();
       }
    
       // launch kernel 3 times
       create_node(capt_stream, krn, 3);
    
       // end capturing
       CUgraph graph;
       check(cuStreamEndCapture(capt_stream, &graph));
    
       CUgraphExec graph_exec;
       CUDA_GRAPH_INSTANTIATE_PARAMS params;
       params.flags = CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD;
       params.hUploadStream = launch_stream;
       check(cuGraphInstantiateWithParams(&graph_exec, graph, &params));
    
       if (params.result_out != CUDA_GRAPH_INSTANTIATE_SUCCESS) {
          throw std::runtime_error("CUDA_GRAPH_INSTANTIATE result is not a success");
       }
    
       check(cuGraphUpload(graph_exec, launch_stream));
       check(cuGraphLaunch(graph_exec, launch_stream));
    
       // synchronize
       check(cuStreamSynchronize(launch_stream));
    
       check(cuGraphExecDestroy(graph_exec));
    
       const char *dotFileName = "test_graph_straight.dot";
       check(cuGraphDebugDotPrint(graph, dotFileName, 0));
    
       check(cuGraphDestroy(graph));
       check(cuStreamDestroy(capt_stream));
    }
    
    void split(CUstream* out_streams, int n)
    {
       assert(n > 1);
    
       CUevent ev_before_fork;
       CUstream in_stream = out_streams[0];
    
       check(cuEventCreate(&ev_before_fork, CU_EVENT_WAIT_DEFAULT));
       check(cuEventRecord(ev_before_fork, in_stream));
    
       for(int i=1; i < n; ++i) {
          check(cuStreamWaitEvent(out_streams[i], ev_before_fork, CU_EVENT_WAIT_DEFAULT));
       }
    
       check(cuEventDestroy(ev_before_fork));
    }
    
    void join(CUstream *in_streams, int n)
    {
       assert(n > 1);
    
       for (int i = 1; i < n; ++i) {
         CUevent join_ev;
         check(cuEventCreate(&join_ev, CU_EVENT_WAIT_DEFAULT));
         check(cuEventRecord(join_ev, in_streams[i]));
    
         check(cuStreamWaitEvent(in_streams[0], join_ev, CU_EVENT_WAIT_DEFAULT));
         check(cuEventDestroy(join_ev));
       }
    }
    
    void test_graph_fork_join(CUstream launch_stream, CUfunction krn)
    {
       CUdevice this_dev;
       check(cuStreamGetDevice(launch_stream, &this_dev));
    
       CUstream capt_stream;
       check(cuStreamCreate(&capt_stream, this_dev));
    
       // start capturing
       check(cuStreamBeginCapture(capt_stream, CU_STREAM_CAPTURE_MODE_RELAXED));
    
       CUstreamCaptureStatus capt_status;
       check(cuStreamIsCapturing(capt_stream, &capt_status));
    
       if (capt_status != CU_STREAM_CAPTURE_STATUS_ACTIVE) {
          std::cerr << "Unexpected capturing status: " << capt_status << std::endl;
          std::terminate();
       }
    
       // launch kernel
       create_node(capt_stream, krn, 1);
    
       // split
       //  -- capt_stream --- \ ---- capt_stream----------
       //                      \                      /
       //                       ---- right_stream ---/
    
       CUstream right_stream;
       check(cuStreamCreate(&right_stream, this_dev));
    
       CUstream split_streams[2] = {capt_stream, right_stream};
       split(split_streams, 2);
    
       // launch 2 kernels on left stream
       create_node(split_streams[0], krn, 2);
    
       // launch kernel on right stream
       create_node(split_streams[1], krn, 2);
    
       // join graph
       join(split_streams, 2);
       check(cuStreamDestroy(right_stream));
    
       // launch krn on capt_stream
       create_node(capt_stream, krn, 1);
    
       // end capturing
       CUgraph graph;
       check(cuStreamEndCapture(capt_stream, &graph));
    
       /*  Final graph to be instantiated:
        *   --- K ---------- K ---------- K ------ K ---
        *           \                         /
        *            ---- K -------- K -------
        */
    
       CUgraphExec graph_exec;
       CUDA_GRAPH_INSTANTIATE_PARAMS params;
       params.flags = CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD;
       params.hUploadStream = launch_stream;
       check(cuGraphInstantiateWithParams(&graph_exec, graph, &params));
    
       if (params.result_out != CUDA_GRAPH_INSTANTIATE_SUCCESS) {
          throw std::runtime_error("CUDA_GRAPH_INSTANTIATE result is not a success");
       }
    
       check(cuGraphUpload(graph_exec, launch_stream));
       check(cuGraphLaunch(graph_exec, launch_stream));
    
       // synchronize
       check(cuStreamSynchronize(launch_stream));
    
       check(cuGraphExecDestroy(graph_exec));
    
       const char *dotFileName = "test_graph_fork_join.dot";
       check(cuGraphDebugDotPrint(graph, dotFileName, 0));
    
       check(cuGraphDestroy(graph));
    }
    
    void test_graph_is_join_required(CUstream launch_stream, CUfunction krn) {
       CUdevice this_dev;
       check(cuStreamGetDevice(launch_stream, &this_dev));
    
       CUstream capt_stream;
       check(cuStreamCreate(&capt_stream, this_dev));
    
       // start capturing
       CUgraph graph;
       check(cuStreamBeginCapture(capt_stream, CU_STREAM_CAPTURE_MODE_RELAXED));
    
       // create root node executing krn
       create_node(capt_stream, krn, 1);
    
       // split it 3-way
       CUstream first_split_streams[3];
       first_split_streams[0] = capt_stream;
    
       check(cuStreamCreate(&first_split_streams[1], this_dev));
       check(cuStreamCreate(&first_split_streams[2], this_dev));
    
       split(first_split_streams, 3);
    
       for(int i = 0; i < 3; ++i) {
          CUstreamCaptureStatus capt_status;
          check(cuStreamIsCapturing(first_split_streams[i], &capt_status));
    
          if (capt_status != CU_STREAM_CAPTURE_STATUS_ACTIVE) {
             std::cerr << "first_split_stream[" << i << "] is not capturing" << std::endl;
          }
       }
    
       // in each of edges create a node execute krn
       for(int i = 0; i < 3; ++i) {
          create_node(first_split_streams[i], krn, 1);
       }
    
       // perform 3-way split at the last edge
    
       CUstream second_split_streams[3];
       second_split_streams[0] = first_split_streams[2];
    
       check(cuStreamCreate(&second_split_streams[1], this_dev));
       check(cuStreamCreate(&second_split_streams[2], this_dev));
    
       split(second_split_streams, 3);
       for(int i = 0; i < 3; ++i) {
          CUstreamCaptureStatus capt_status;
          check(cuStreamIsCapturing(second_split_streams[i], &capt_status));
    
          if (capt_status != CU_STREAM_CAPTURE_STATUS_ACTIVE) {
             std::cerr << "first_split_stream[" << i << "] is not capturing" << std::endl;
          }
       }
    
       // in each of new edges create a node execute krn
       for(int i = 0; i < 3; ++i) {
          create_node(second_split_streams[i], krn, 1);
       }
    
       // join nodes of second split
       join(second_split_streams, 3);
    
       // join nodes of the first split
       join(first_split_streams, 3);
    
       check(cuStreamDestroy(second_split_streams[2]));
       check(cuStreamDestroy(second_split_streams[1]));
    
       check(cuStreamDestroy(first_split_streams[2]));
       check(cuStreamDestroy(first_split_streams[1]));
    
       // create tail node executing krn
       create_node(capt_stream, krn, 1);
    
       // end capturing
       check(cuStreamEndCapture(capt_stream, &graph));
    
       // instantiate, upload and launch the graph
       CUgraphExec graph_exec;
       CUDA_GRAPH_INSTANTIATE_PARAMS params;
       params.flags = CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD;
       params.hUploadStream = launch_stream;
       check(cuGraphInstantiateWithParams(&graph_exec, graph, &params));
    
       if (params.result_out != CUDA_GRAPH_INSTANTIATE_SUCCESS) {
          throw std::runtime_error("CUDA_GRAPH_INSTANTIATE result is not a success");
       }
    
       check(cuGraphUpload(graph_exec, launch_stream));
       check(cuGraphLaunch(graph_exec, launch_stream));
    
       // synchronize
       check(cuStreamSynchronize(launch_stream));
    
       check(cuGraphExecDestroy(graph_exec));
    
       const char *dotFileName = "test_graph_is_join_required.dot";
       check(cuGraphDebugDotPrint(graph, dotFileName, 0));
    
       check(cuGraphDestroy(graph));
       check(cuStreamDestroy(capt_stream));
    }
    
    void test_graph_repeat_capture(CUstream launch_stream, CUfunction krn)
    {
       CUdevice this_dev;
       check(cuStreamGetDevice(launch_stream, &this_dev));
    
       CUstream capt_stream;
       check(cuStreamCreate(&capt_stream, this_dev));
    
       check(cuStreamBeginCapture(capt_stream, CU_STREAM_CAPTURE_MODE_RELAXED));
    
       // allocate pinned host memory
       int *d_ptr;
       check(cuMemAllocHost((void **)&d_ptr, sizeof(int)));
       // initialize from the host
       d_ptr[0] = 0;
    
       // launch kernel
       void *kernel_args[] = {&d_ptr};
       void **extra = nullptr;
    
       check(cuLaunchKernel(krn, 1, 1, 1, 1, 1, 1, /* smem */0, /* stream */capt_stream, kernel_args, extra));
    
       // end capturing
       CUgraph graph;
       check(cuStreamEndCapture(capt_stream, &graph));
    
       // instantiate, upload and launch the graph
       CUgraphExec graph_exec;
       CUDA_GRAPH_INSTANTIATE_PARAMS params;
       params.flags = CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD;
       params.hUploadStream = launch_stream;
       check(cuGraphInstantiateWithParams(&graph_exec, graph, &params));
    
       if (params.result_out != CUDA_GRAPH_INSTANTIATE_SUCCESS) {
          throw std::runtime_error("CUDA_GRAPH_INSTANTIATE result is not a success");
       }
    
       check(cuGraphUpload(graph_exec, launch_stream));
    
       // launch
       check(cuGraphLaunch(graph_exec, launch_stream));
    
       // synchronize
       check(cuStreamSynchronize(launch_stream));
    
       if (d_ptr[0] != 1) {
          throw std::runtime_error("test_graph_repeat_capture: unexpected value after first launch!");
       }
    
       check(cuGraphLaunch(graph_exec, capt_stream));
       check(cuGraphLaunch(graph_exec, capt_stream));
       check(cuGraphLaunch(graph_exec, capt_stream));
       check(cuStreamSynchronize(capt_stream));
    
       if (d_ptr[0] != 4) {
          throw std::runtime_error("test_graph_repeat_capture: unexpected value after subsequent launches!");
       }
       check(cuMemFreeHost(d_ptr));
    
       check(cuGraphExecDestroy(graph_exec));
    
       const char *dotFileName = "test_graph_repeat_capture.dot";
       check(cuGraphDebugDotPrint(graph, dotFileName, 0));
    
       check(cuGraphDestroy(graph));
       check(cuStreamDestroy(capt_stream));
    }
    
    void test_graph_conditional_if(CUstream launch_stream, CUfunction action_krn, CUfunction service_krn, int conditional_value)
    {
       /* -- A -- B -- C --- D ---- F ---- G --
        *          \      \      /     /
        *           \      - E -      /
        *            \               /
        *              --------------
        *
        *    A - set-handle krn
        *    B - if node
        *    C, D, E, F - nodes add_one execute if conditional is set
        *    G - add_one node following the if
        *
        * If conditional_value is
        *     - set  : [C, D, E, F, G] are executed
        *              C, D, F, G work on d_ptr + 0
        *              E works on         d_ptr + 1
        *     - unset: [G] is executed
        *              G works on         d_ptr + 0
        * */
       CUdevice this_dev;
       check(cuStreamGetDevice(launch_stream, &this_dev));
    
       CUstream capt_stream;
       check(cuStreamCreate(&capt_stream, this_dev));
    
       check(cuStreamBeginCapture(capt_stream, CU_STREAM_CAPTURE_MODE_RELAXED));
    
       // allocate pinned host memory
       int *h_ptr;
       check(cuMemAllocHost((void **)&h_ptr, 2*sizeof(int)));
       // initialize from the host
       h_ptr[0] = 0;
       h_ptr[1] = 0;
    
       CUcontext ctx;
       check(cuStreamGetCtx(capt_stream, &ctx));
    
       // create conditional handle
       CUstreamCaptureStatus cp1_capt_status;
       CUgraph cp1_graph;
       const CUgraphNode* cp1_dependencies;
       size_t cp1_num_dependencies;
       check(cuStreamGetCaptureInfo(capt_stream, &cp1_capt_status, nullptr, &cp1_graph, &cp1_dependencies, &cp1_num_dependencies));
    
       if (cp1_capt_status != CU_STREAM_CAPTURE_STATUS_ACTIVE) {
          std::cerr << "Unexpected capturing status: " << cp1_capt_status << std::endl;
          std::terminate();
       }
    
       CUgraphConditionalHandle cond_h;
       check(cuGraphConditionalHandleCreate(&cond_h, cp1_graph, ctx, 0, 0));
    
       // add node A: sets condition
       int _conditional_value = conditional_value;
       void *service_krn_args[] = {&cond_h, &_conditional_value};
       void **extra = nullptr;
       check(cuLaunchKernel(service_krn, 1, 1, 1, 1, 1, 1, /* smem */0, capt_stream, service_krn_args, extra));
    
       // add node B: if condition
       CUgraphNodeParams node_B_params{};
       node_B_params.type = CU_GRAPH_NODE_TYPE_CONDITIONAL;
       node_B_params.conditional.handle = cond_h;
       node_B_params.conditional.type = CU_GRAPH_COND_TYPE_IF;
       node_B_params.conditional.size = 1;
       node_B_params.conditional.ctx = ctx;
    
       // get graph built by capturing stream `capt_stream`
       CUstreamCaptureStatus cp2_capt_status;
       CUgraph cp2_graph;
       const CUgraphNode* cp2_dependencies;
       size_t cp2_num_dependencies;
       check(cuStreamGetCaptureInfo(capt_stream, &cp2_capt_status, nullptr, &cp2_graph, &cp2_dependencies, &cp2_num_dependencies));
    
       if (cp2_capt_status != CU_STREAM_CAPTURE_STATUS_ACTIVE) {
          std::cerr << "Unexpected capturing status: " << cp2_capt_status << std::endl;
          std::terminate();
       }
    
       CUgraphNode node_B;
       check(cuGraphAddNode(&node_B, cp2_graph, cp2_dependencies, cp2_num_dependencies, &node_B_params));
    
       // insert nodeB into the stream
       check(cuStreamUpdateCaptureDependencies(capt_stream, &node_B, 1, CU_STREAM_SET_CAPTURE_DEPENDENCIES));
    
       CUstream if_stream;
       check(cuStreamCreate(&if_stream, this_dev));
    
       check(cuStreamBeginCaptureToGraph(if_stream, node_B_params.conditional.phGraph_out[0], nullptr, nullptr, 0, CU_STREAM_CAPTURE_MODE_RELAXED));
    
       // create node C
       void *first_elem_args[] = {&h_ptr};
       int *h_next_ptr = h_ptr + 1;
       void *second_elem_args[] = {&h_next_ptr};
       check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, if_stream, first_elem_args, extra));
    
       CUstream split_streams[2];
       split_streams[0] = if_stream;
       check(cuStreamCreate(&split_streams[1], this_dev));
    
       split(split_streams, 2);
    
       // node D
       check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, split_streams[0], first_elem_args, extra));
       // node E
       check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, split_streams[1], second_elem_args, extra));
    
       join(split_streams, 2);
    
       check(cuStreamDestroy(split_streams[1]));
    
       // node F
       check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, if_stream, first_elem_args, extra));
    
       CUgraph conditional_graph;
       check(cuStreamEndCapture(if_stream, &conditional_graph));
       check(cuStreamDestroy(if_stream));
    
       // node G
       check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, capt_stream, first_elem_args, extra));
    
       // end capturing
       CUgraph complete_graph;
       check(cuStreamEndCapture(capt_stream, &complete_graph));
    
       // these temporaries are deleted by the driver,
       // making these calls returns CUDA_ERROR_INVALID_VALUE
       /*
          check(cuGraphDestroy(cp2_graph));
          check(cuGraphDestroy(cp1_graph));
          check(cuGraphDestroy(conditional_graph));
       */
    
       // instantiate, upload and launch the graph
       CUgraphExec graph_exec;
       CUDA_GRAPH_INSTANTIATE_PARAMS params;
       params.flags = CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD;
       params.hUploadStream = launch_stream;
       check(cuGraphInstantiateWithParams(&graph_exec, complete_graph, &params));
    
       if (params.result_out != CUDA_GRAPH_INSTANTIATE_SUCCESS) {
          throw std::runtime_error("CUDA_GRAPH_INSTANTIATE result is not a success");
       }
    
       check(cuGraphUpload(graph_exec, launch_stream));
    
       // launch
       check(cuGraphLaunch(graph_exec, launch_stream));
    
       // synchronize
       check(cuStreamSynchronize(launch_stream));
       check(cuGraphExecDestroy(graph_exec));
    
       int expected_res[2];
    
       expected_res[0] = (conditional_value) ? 4 : 1;
       expected_res[1] = (conditional_value) ? 1 : 0;
    
       if (!array_cmp_equal(h_ptr, expected_res, 2)) {
         if (conditional_value) {
           std::cerr << "Actual results: " << h_ptr[0] << ", " << h_ptr[1] << std::endl;
           throw std::runtime_error("Unexpected result of test_graph_conditional_if(true)");
         } else {
           std::cerr << "Actual results: " << h_ptr[0] << ", " << h_ptr[1] << std::endl;
           throw std::runtime_error("Unexpected result of test_graph_conditional_if(false)");
         }
       }
    
       // deallocate host-pinned memory
       check(cuMemFreeHost(h_ptr));
       check(cuGraphDestroy(complete_graph));
       check(cuStreamDestroy(capt_stream));
    }
    
    void test_graph_conditional_if_else(CUstream launch_stream, CUfunction action_krn, CUfunction service_krn, int conditional_value)
    {
        /* -- A -- B -- C --- D ---- F ---- G --
        *          \      \      /     /
        *           \      - E -      /
        *            \               /
        *             - H -- I -- J -
        *
        *    A - set-handle krn
        *    B - if node
        *    C, D, E, F - nodes add_one execute if conditional is set
        *    G - add_one node following the if
        *
        * If conditional_value is
        *     - set  : [C, D, E, F, G] are executed
        *              C, D, F, G work on d_ptr + 0
        *              E works on         d_ptr + 1
        *     - unset: [H, I, J, G] is executed
        *              G works on         d_ptr + 0
        *              H, I, J work on    d_ptr + 1
        * */
       CUdevice this_dev;
       check(cuStreamGetDevice(launch_stream, &this_dev));
    
       CUstream capt_stream;
       check(cuStreamCreate(&capt_stream, this_dev));
    
       check(cuStreamBeginCapture(capt_stream, CU_STREAM_CAPTURE_MODE_RELAXED));
    
       // allocate pinned host memory
       int *h_ptr;
       check(cuMemAllocHost((void **)&h_ptr, 2*sizeof(int)));
       // initialize from the host
       h_ptr[0] = 0;
       h_ptr[1] = 0;
    
       CUcontext ctx;
       check(cuStreamGetCtx(capt_stream, &ctx));
    
       // create conditional handle
       CUstreamCaptureStatus cp1_capt_status;
       CUgraph cp1_graph;
       const CUgraphNode* cp1_dependencies;
       size_t cp1_num_dependencies;
       check(cuStreamGetCaptureInfo(capt_stream, &cp1_capt_status, nullptr, &cp1_graph, &cp1_dependencies, &cp1_num_dependencies));
    
       if (cp1_capt_status != CU_STREAM_CAPTURE_STATUS_ACTIVE) {
          std::cerr << "Unexpected capturing status: " << cp1_capt_status << std::endl;
          std::terminate();
       }
    
       CUgraphConditionalHandle cond_h;
       check(cuGraphConditionalHandleCreate(&cond_h, cp1_graph, ctx, 0, 0));
    
       // add node A: sets condition
       int _conditional_value = conditional_value;
       void *service_krn_args[] = {&cond_h, &_conditional_value};
       void **extra = nullptr;
       check(cuLaunchKernel(service_krn, 1, 1, 1, 1, 1, 1, /* smem */0, capt_stream, service_krn_args, extra));
    
       // add node B: if condition
       CUgraphNodeParams node_B_params{};
       node_B_params.type = CU_GRAPH_NODE_TYPE_CONDITIONAL;
       node_B_params.conditional.handle = cond_h;
       node_B_params.conditional.type = CU_GRAPH_COND_TYPE_IF;
       node_B_params.conditional.size = 2; // for if branch and else branch
       node_B_params.conditional.ctx = ctx;
    
       // get graph built by capturing stream `capt_stream`
       CUstreamCaptureStatus cp2_capt_status;
       CUgraph cp2_graph;
       const CUgraphNode* cp2_dependencies;
       size_t cp2_num_dependencies;
       check(cuStreamGetCaptureInfo(capt_stream, &cp2_capt_status, nullptr, &cp2_graph, &cp2_dependencies, &cp2_num_dependencies));
    
       if (cp2_capt_status != CU_STREAM_CAPTURE_STATUS_ACTIVE) {
          std::cerr << "Unexpected capturing status: " << cp2_capt_status << std::endl;
          std::terminate();
       }
    
       CUgraphNode node_B;
       check(cuGraphAddNode(&node_B, cp2_graph, cp2_dependencies, cp2_num_dependencies, &node_B_params));
    
       // insert nodeB into the stream
       check(cuStreamUpdateCaptureDependencies(capt_stream, &node_B, 1, CU_STREAM_SET_CAPTURE_DEPENDENCIES));
    
       CUstream then_stream;
       CUstream else_stream;
       check(cuStreamCreate(&then_stream, this_dev));
       check(cuStreamCreate(&else_stream, this_dev));
    
       check(cuStreamBeginCaptureToGraph(then_stream, node_B_params.conditional.phGraph_out[0], nullptr, nullptr, 0, CU_STREAM_CAPTURE_MODE_RELAXED));
       check(cuStreamBeginCaptureToGraph(else_stream, node_B_params.conditional.phGraph_out[1], nullptr, nullptr, 0, CU_STREAM_CAPTURE_MODE_RELAXED));
    
       // create node C
       void *first_elem_args[] = {&h_ptr};
       int *h_next_ptr = h_ptr + 1;
       void *second_elem_args[] = {&h_next_ptr};
       check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, then_stream, first_elem_args, extra));
    
       CUstream split_streams[2];
       split_streams[0] = then_stream;
       check(cuStreamCreate(&split_streams[1], this_dev));
    
       split(split_streams, 2);
    
       // node D
       check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, split_streams[0], first_elem_args, extra));
       // node E
       check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, split_streams[1], second_elem_args, extra));
    
       join(split_streams, 2);
    
       check(cuStreamDestroy(split_streams[1]));
    
       // node F
       check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, then_stream, first_elem_args, extra));
    
       CUgraph then_graph;
       check(cuStreamEndCapture(then_stream, &then_graph));
       check(cuStreamDestroy(then_stream));
    
       // node H
       check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, else_stream, second_elem_args, extra));
       // node I
       check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, else_stream, second_elem_args, extra));
       // node J
       check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, else_stream, second_elem_args, extra));
    
       CUgraph else_graph;
       check(cuStreamEndCapture(else_stream, &else_graph));
       check(cuStreamDestroy(else_stream));
    
       // node G
       check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, capt_stream, first_elem_args, extra));
    
       // end capturing
       CUgraph complete_graph;
       check(cuStreamEndCapture(capt_stream, &complete_graph));
    
       // these temporaries are deleted by the driver,
       // making these calls returns CUDA_ERROR_INVALID_VALUE
       /*
          check(cuGraphDestroy(cp2_graph));
          check(cuGraphDestroy(cp1_graph));
          check(cuGraphDestroy(then_graph));
          check(cuGraphDestroy(else_graph));
       */
    
       // instantiate, upload and launch the graph
       CUgraphExec graph_exec;
       CUDA_GRAPH_INSTANTIATE_PARAMS params;
       params.flags = CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD;
       params.hUploadStream = launch_stream;
       check(cuGraphInstantiateWithParams(&graph_exec, complete_graph, &params));
    
       if (params.result_out != CUDA_GRAPH_INSTANTIATE_SUCCESS) {
          throw std::runtime_error("CUDA_GRAPH_INSTANTIATE result is not a success");
       }
    
       check(cuGraphUpload(graph_exec, launch_stream));
    
       // launch
       check(cuGraphLaunch(graph_exec, launch_stream));
    
       // synchronize
       check(cuStreamSynchronize(launch_stream));
       check(cuGraphExecDestroy(graph_exec));
    
       int expected_res[2];
    
       expected_res[0] = (conditional_value) ? 4 : 1;
       expected_res[1] = (conditional_value) ? 1 : 3;
    
       if (!array_cmp_equal(h_ptr, expected_res, 2)) {
         if (conditional_value) {
           std::cerr << "Actual results: " << h_ptr[0] << ", " << h_ptr[1] << std::endl;
           throw std::runtime_error("Unexpected result of test_graph_conditional_if_else(true)");
         } else {
           std::cerr << "Actual results: " << h_ptr[0] << ", " << h_ptr[1] << std::endl;
           throw std::runtime_error("Unexpected result of test_graph_conditional_if_else(false)");
         }
       }
    
       // deallocate host-pinned memory
       check(cuMemFreeHost(h_ptr));
       check(cuGraphDestroy(complete_graph));
       check(cuStreamDestroy(capt_stream));
    }
    
    void test_graph_conditional_switch(CUstream launch_stream, CUfunction action_krn, CUfunction service_krn, int conditional_value)
    {
      /*
       *                -- C0 - C1 - C2 -
       *               /                \
       *              /       - E1 -     \
       *             /       /      \     \
       * -- A -- B -- -- E0 --- E2 ---------- G --
       *             \                    /
       *              \                  /
       *               -- D0 - D1 - D2 --
       *
       *    - node A: sets conditional value
       *    - node B: switch node
       *       case 0:
       *         - execute nodes C0, C1, C2
       *            they update h_ptr + 0
       *       case 1:
       *         - execute nodes E0, E1, E2
       *            E0, E2 update  h_ptr + 1
       *            E1     updates h_ptr + 2
       *       case 2:
       *         - execute nodes D0, D1, D2
       *            they all update h_ptr + 2
       *     - node G
       *            updates h_ptr + 0
       * */
      CUdevice this_dev;
      check(cuStreamGetDevice(launch_stream, &this_dev));
    
      CUstream capt_stream;
      check(cuStreamCreate(&capt_stream, this_dev));
    
      check(cuStreamBeginCapture(capt_stream, CU_STREAM_CAPTURE_MODE_RELAXED));
    
      // allocate pinned host memory
      int *h_ptr;
      check(cuMemAllocHost((void **)&h_ptr, 3*sizeof(int)));
      // initialize from the host
      h_ptr[0] = 0;
      h_ptr[1] = 0;
      h_ptr[2] = 0;
    
      CUcontext ctx;
      check(cuStreamGetCtx(capt_stream, &ctx));
    
      // create conditional handle
      CUstreamCaptureStatus cp1_capt_status;
      CUgraph cp1_graph;
      const CUgraphNode* cp1_dependencies;
      size_t cp1_num_dependencies;
      check(cuStreamGetCaptureInfo(capt_stream, &cp1_capt_status, nullptr, &cp1_graph, &cp1_dependencies, &cp1_num_dependencies));
    
      if (cp1_capt_status != CU_STREAM_CAPTURE_STATUS_ACTIVE) {
        std::cerr << "Unexpected capturing status: " << cp1_capt_status << std::endl;
        std::terminate();
      }
    
      CUgraphConditionalHandle cond_h;
      check(cuGraphConditionalHandleCreate(&cond_h, cp1_graph, ctx, 0, 0));
    
      // add node A: sets condition
      int _conditional_value = conditional_value;
      void *service_krn_args[] = {&cond_h, &_conditional_value};
      void **extra = nullptr;
      check(cuLaunchKernel(service_krn, 1, 1, 1, 1, 1, 1, /* smem */0, capt_stream, service_krn_args, extra));
    
      // add node B: if condition
      CUgraphNodeParams node_B_params{};
      node_B_params.type = CU_GRAPH_NODE_TYPE_CONDITIONAL;
      node_B_params.conditional.handle = cond_h;
      node_B_params.conditional.type = CU_GRAPH_COND_TYPE_SWITCH;
      node_B_params.conditional.size = 3; // for 3 branches of 3-way-switch
      node_B_params.conditional.ctx = ctx;
    
      // get graph built by capturing stream `capt_stream`
      CUstreamCaptureStatus cp2_capt_status;
      CUgraph cp2_graph;
      const CUgraphNode* cp2_dependencies;
      size_t cp2_num_dependencies;
      check(cuStreamGetCaptureInfo(capt_stream, &cp2_capt_status, nullptr, &cp2_graph, &cp2_dependencies, &cp2_num_dependencies));
    
      if (cp2_capt_status != CU_STREAM_CAPTURE_STATUS_ACTIVE) {
        std::cerr << "Unexpected capturing status: " << cp2_capt_status << std::endl;
        std::terminate();
      }
    
      CUgraphNode node_B;
      check(cuGraphAddNode(&node_B, cp2_graph, cp2_dependencies, cp2_num_dependencies, &node_B_params));
    
      // insert nodeB into the stream
      check(cuStreamUpdateCaptureDependencies(capt_stream, &node_B, 1, CU_STREAM_SET_CAPTURE_DEPENDENCIES));
    
      CUstream case0_stream;
      CUstream case1_stream;
      CUstream case2_stream;
      check(cuStreamCreate(&case0_stream, this_dev));
      check(cuStreamCreate(&case1_stream, this_dev));
      check(cuStreamCreate(&case2_stream, this_dev));
    
      check(cuStreamBeginCaptureToGraph(case0_stream, node_B_params.conditional.phGraph_out[0], nullptr, nullptr, 0, CU_STREAM_CAPTURE_MODE_RELAXED));
      check(cuStreamBeginCaptureToGraph(case1_stream, node_B_params.conditional.phGraph_out[1], nullptr, nullptr, 0, CU_STREAM_CAPTURE_MODE_RELAXED));
      check(cuStreamBeginCaptureToGraph(case2_stream, node_B_params.conditional.phGraph_out[2], nullptr, nullptr, 0, CU_STREAM_CAPTURE_MODE_RELAXED));
    
      void *first_elem_args[] = {&h_ptr};
      int *h_ptr1 = h_ptr + 1;
      int *h_ptr2 = h_ptr + 2;
      void *second_elem_args[] = {&h_ptr1};
      void *third_elem_args[] = {&h_ptr2};
    
      // nodes C0, C1, C2
      check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, case0_stream, first_elem_args, extra));
      check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, case0_stream, first_elem_args, extra));
      check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, case0_stream, first_elem_args, extra));
    
      CUgraph case0_graph;
      check(cuStreamEndCapture(case0_stream, &case0_graph));
      check(cuStreamDestroy(case0_stream));
    
      // nodes D0, D1, D2
      check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, case2_stream, third_elem_args, extra));
      check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, case2_stream, third_elem_args, extra));
      check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, case2_stream, third_elem_args, extra));
    
      CUgraph case2_graph;
      check(cuStreamEndCapture(case2_stream, &case2_graph));
      check(cuStreamDestroy(case2_stream));
    
      // nodes E0
      check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, case1_stream, second_elem_args, extra));
      CUstream split_streams[2];
      split_streams[0] = case1_stream;
      check(cuStreamCreate(&split_streams[1], this_dev));
    
      split(split_streams, 2);
      // node E1
      check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, split_streams[0], third_elem_args, extra));
      // node E2
      check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, split_streams[1], second_elem_args, extra));
    
      join(split_streams, 2);
      check(cuStreamDestroy(split_streams[1]));
    
      CUgraph case1_graph;
      check(cuStreamEndCapture(case1_stream, &case1_graph));
      check(cuStreamDestroy(case1_stream));
    
      // node G
      check(cuLaunchKernel(action_krn, 1, 1, 1, 1, 1, 1, 0, capt_stream, first_elem_args, extra));
    
      // end capturing
      CUgraph complete_graph;
      check(cuStreamEndCapture(capt_stream, &complete_graph));
    
      // instantiate, upload and launch the graph
      CUgraphExec graph_exec;
      CUDA_GRAPH_INSTANTIATE_PARAMS params;
      params.flags = CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD;
      params.hUploadStream = launch_stream;
      check(cuGraphInstantiateWithParams(&graph_exec, complete_graph, &params));
    
      if (params.result_out != CUDA_GRAPH_INSTANTIATE_SUCCESS) {
        throw std::runtime_error("CUDA_GRAPH_INSTANTIATE result is not a success");
      }
    
      check(cuGraphUpload(graph_exec, launch_stream));
    
      // launch
      check(cuGraphLaunch(graph_exec, launch_stream));
    
      // synchronize
      check(cuStreamSynchronize(launch_stream));
      check(cuGraphExecDestroy(graph_exec));
    
      int expected_res[3] = {0, 0, 0};
    
      switch(conditional_value) {
        case 0:
          expected_res[0] = 4;
          break;
        case 1:
          expected_res[0] = 1;
          expected_res[1] = 2;
          expected_res[2] = 1;
          break;
        case 2:
          expected_res[0] = 1;
          expected_res[2] = 3;
          break;
        default:
          expected_res[0] = 1;
      }
    
      if (!array_cmp_equal(h_ptr, expected_res, 3)) {
        std::cerr << "Conditional value: " << conditional_value;
        std::cerr << "Actual result: " << h_ptr[0] << ", " << h_ptr[1] << ", " << h_ptr[2] << std::endl;
        throw std::runtime_error("Unexpected result of test_graph_conditional_switch");
      }
    
      // deallocate host-pinned memory
      check(cuMemFreeHost(h_ptr));
      check(cuGraphDestroy(complete_graph));
      check(cuStreamDestroy(capt_stream));
    }
    
    void
    test_graph_conditional_while(
       CUstream launch_stream,
       CUfunction no_arg_krn,
       CUfunction ptr_arg_krn,
       CUfunction loop_krn,
       int conditional_value)
    {
      /*
       *           ----------------
       *           |              |
       *   ------- B -- C0 - C1 - L -- D --
       *
       *  node B: check loop condition
       *  nodes C0/C1: update memory
       *  node L:  loop back
       *  node D:  empty kernel
       *
       * */
    
      CUdevice this_dev;
      check(cuStreamGetDevice(launch_stream, &this_dev));
    
      CUstream capt_stream;
      check(cuStreamCreate(&capt_stream, this_dev));
    
      // start capturing
      check(cuStreamBeginCapture(capt_stream, CU_STREAM_CAPTURE_MODE_RELAXED));
    
      // allocate pinned host memory
      int *h_ptr;
      check(cuMemAllocHost((void **)&h_ptr, sizeof(int)));
      // initialize from the host
      h_ptr[0] = 0;
    
      CUcontext ctx;
      check(cuStreamGetCtx(capt_stream, &ctx));
    
      // create conditional handle
      CUstreamCaptureStatus cp1_capt_status;
      CUgraph cp1_graph;
      const CUgraphNode* cp1_dependencies;
      size_t cp1_num_dependencies;
      check(cuStreamGetCaptureInfo(capt_stream, &cp1_capt_status, nullptr, &cp1_graph, &cp1_dependencies, &cp1_num_dependencies));
    
      if (cp1_capt_status != CU_STREAM_CAPTURE_STATUS_ACTIVE) {
        std::cerr << "Unexpected capturing status: " << cp1_capt_status << std::endl;
        std::terminate();
      }
    
      CUgraphConditionalHandle cond_h;
      check(cuGraphConditionalHandleCreate(&cond_h, cp1_graph, ctx, conditional_value, CU_GRAPH_COND_ASSIGN_DEFAULT));
    
      // add node B: if condition
      CUgraphNodeParams node_B_params{};
      node_B_params.type = CU_GRAPH_NODE_TYPE_CONDITIONAL;
      node_B_params.conditional.handle = cond_h;
      node_B_params.conditional.type = CU_GRAPH_COND_TYPE_WHILE;
      node_B_params.conditional.size = 1;
      node_B_params.conditional.ctx = ctx;
    
      // get graph built by capturing stream `capt_stream`
      CUstreamCaptureStatus cp2_capt_status;
      CUgraph cp2_graph;
      const CUgraphNode* cp2_dependencies;
      size_t cp2_num_dependencies;
      check(cuStreamGetCaptureInfo(capt_stream, &cp2_capt_status, nullptr, &cp2_graph, &cp2_dependencies, &cp2_num_dependencies));
    
      if (cp2_capt_status != CU_STREAM_CAPTURE_STATUS_ACTIVE) {
        std::cerr << "Unexpected capturing status: " << cp2_capt_status << std::endl;
        std::terminate();
      }
    
      CUgraphNode node_B;
      check(cuGraphAddNode(&node_B, cp2_graph, cp2_dependencies, cp2_num_dependencies, &node_B_params));
    
      // insert nodeB into the stream
      check(cuStreamUpdateCaptureDependencies(capt_stream, &node_B, 1, CU_STREAM_SET_CAPTURE_DEPENDENCIES));
    
      CUstream while_body_stream;
      check(cuStreamCreate(&while_body_stream, this_dev));
    
      check(cuStreamBeginCaptureToGraph(while_body_stream, node_B_params.conditional.phGraph_out[0], nullptr, nullptr, 0, CU_STREAM_CAPTURE_MODE_RELAXED));
    
      void* ptr_krn_args[] = {&h_ptr};
    
      // nodes C0, C1
      void **extra = nullptr;
      check(cuLaunchKernel(ptr_arg_krn, 1, 1, 1, 1, 1, 1, 0, while_body_stream, ptr_krn_args, extra));
      check(cuLaunchKernel(ptr_arg_krn, 1, 1, 1, 1, 1, 1, 0, while_body_stream, ptr_krn_args, extra));
    
      // node L
      void *loop_krn_args[] = {&cond_h};
      check(cuLaunchKernel(loop_krn, 1, 1, 1, 1, 1, 1, 0, while_body_stream, loop_krn_args, extra));
    
      CUgraph while_body_graph;
      check(cuStreamEndCapture(while_body_stream, &while_body_graph));
      check(cuStreamDestroy(while_body_stream));
    
      // node G
      check(cuLaunchKernel(no_arg_krn, 1, 1, 1, 1, 1, 1, 0, capt_stream, extra, extra));
    
      // end capturing
      CUgraph complete_graph;
      check(cuStreamEndCapture(capt_stream, &complete_graph));
    
      // instantiate, upload and launch the graph
      CUgraphExec graph_exec;
      CUDA_GRAPH_INSTANTIATE_PARAMS params;
      params.flags = CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD;
      params.hUploadStream = launch_stream;
      check(cuGraphInstantiateWithParams(&graph_exec, complete_graph, &params));
    
      if (params.result_out != CUDA_GRAPH_INSTANTIATE_SUCCESS) {
        throw std::runtime_error("CUDA_GRAPH_INSTANTIATE result is not a success");
      }
    
      check(cuGraphUpload(graph_exec, launch_stream));
    
      // launch
      check(cuGraphLaunch(graph_exec, launch_stream));
    
      // synchronize
      check(cuStreamSynchronize(launch_stream));
    
      int expected_value = (conditional_value) ? 20 : 0;
    
      if (h_ptr[0] != expected_value) {
         std::cerr << "Conditional value: " << conditional_value;
         std::cerr << " Actual result: " << h_ptr[0] << std::endl;
    
         throw std::runtime_error("Unexpected result in test_graph_conditional_while");
      }
    
      // deallocate host-pinned memory
      check(cuMemFreeHost(h_ptr));
      check(cuGraphExecDestroy(graph_exec));
      check(cuGraphDestroy(complete_graph));
      check(cuStreamDestroy(capt_stream));
    }
    
    void test_graph_child(CUstream launch_stream, CUfunction krn)
    {
      CUdevice this_dev;
      check(cuStreamGetDevice(launch_stream, &this_dev));
    
      CUstream capt_stream;
      check(cuStreamCreate(&capt_stream, this_dev));
    
      check(cuStreamBeginCapture(capt_stream, CU_STREAM_CAPTURE_MODE_RELAXED));
    
      // allocate pinned host memory
      int *h_ptr;
      check(cuMemAllocHost((void **)&h_ptr, 2*sizeof(int)));
      // initialize from the host
      h_ptr[0] = 0;
      h_ptr[1] = 0;
    
      void **extra = nullptr;
      int *h_ptr1 = h_ptr + 1;
      void *krn_ptr1_args[] = {&h_ptr1};
    
      check(cuLaunchKernel(krn, 1, 1, 1, 1, 1, 1, 0, capt_stream, krn_ptr1_args, extra));
      check(cuLaunchKernel(krn, 1, 1, 1, 1, 1, 1, 0, capt_stream, krn_ptr1_args, extra));
      check(cuLaunchKernel(krn, 1, 1, 1, 1, 1, 1, 0, capt_stream, krn_ptr1_args, extra));
    
      CUgraph child_graph;
      check(cuStreamEndCapture(capt_stream, &child_graph));
    
      CUstream parent_stream;
      check(cuStreamCreate(&parent_stream, this_dev));
    
      check(cuStreamBeginCapture(parent_stream, CU_STREAM_CAPTURE_MODE_RELAXED));
    
      void *krn_ptr0_args[] = {&h_ptr};
      check(cuLaunchKernel(krn, 1, 1, 1, 1, 1, 1, 0, parent_stream, krn_ptr0_args, extra));
    
      // call add_child
    
      // get graph built by capturing stream `capt_stream`
      CUstreamCaptureStatus capt_status;
      CUgraph cp_graph;
      const CUgraphNode* cp_dependencies;
      size_t cp_num_dependencies;
      check(cuStreamGetCaptureInfo(parent_stream, &capt_status, nullptr, &cp_graph, &cp_dependencies, &cp_num_dependencies));
    
      if (capt_status != CU_STREAM_CAPTURE_STATUS_ACTIVE) {
        std::cerr << "Unexpected capture status: " << capt_status << std::endl;
        std::terminate();
      }
    
      // create child node graph representing child_graph
      CUgraphNode child_graph_node;
      check(cuGraphAddChildGraphNode(&child_graph_node, cp_graph, cp_dependencies, cp_num_dependencies, child_graph));
    
      // insert child node into parent stream
      check(cuStreamUpdateCaptureDependencies(parent_stream, &child_graph_node, 1, CU_STREAM_SET_CAPTURE_DEPENDENCIES));
    
      check(cuLaunchKernel(krn, 1, 1, 1, 1, 1, 1, 0, parent_stream, krn_ptr0_args, extra));
    
      // end capturing
      CUgraph complete_graph;
      check(cuStreamEndCapture(parent_stream, &complete_graph));
    
      // instantiate, upload and launch the graph
      CUgraphExec graph_exec;
      CUDA_GRAPH_INSTANTIATE_PARAMS params;
      params.flags = CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD;
      params.hUploadStream = launch_stream;
      check(cuGraphInstantiateWithParams(&graph_exec, complete_graph, &params));
    
      if (params.result_out != CUDA_GRAPH_INSTANTIATE_SUCCESS) {
        throw std::runtime_error("CUDA_GRAPH_INSTANTIATE result is not a success");
      }
    
      check(cuGraphUpload(graph_exec, launch_stream));
    
      // launch
      check(cuGraphLaunch(graph_exec, launch_stream));
    
      // synchronize
      check(cuStreamSynchronize(launch_stream));
    
      int expected[2] = {2, 3};
    
      if (!array_cmp_equal(h_ptr, expected, 2)) {
         std::cerr << " Actual result: " << h_ptr[0] << ", " << h_ptr[1] << std::endl;
    
         throw std::runtime_error("Unexpected result in test_graph_child");
      }
    
      // deallocate host-pinned memory
      check(cuMemFreeHost(h_ptr));
      check(cuGraphExecDestroy(graph_exec));
      check(cuGraphDestroy(complete_graph));
      check(cuStreamDestroy(capt_stream));
    }
    
    CUgraph util_build_graph(CUdevice this_dev, int condition_value, CUfunction krn, int *h_ptr) {
      CUstream capt_stream;
      check(cuStreamCreate(&capt_stream, this_dev));
    
      CUcontext ctx;
      check(cuStreamGetCtx(capt_stream, &ctx));
    
      // start building
      check(cuStreamBeginCapture(capt_stream, CU_STREAM_CAPTURE_MODE_RELAXED));
    
      // create conditional handle
      CUstreamCaptureStatus cp1_capt_status;
      CUgraph cp1_graph;
      const CUgraphNode* cp1_dependencies;
      size_t cp1_num_dependencies;
      check(cuStreamGetCaptureInfo(capt_stream, &cp1_capt_status, nullptr, &cp1_graph, &cp1_dependencies, &cp1_num_dependencies));
    
      if (cp1_capt_status != CU_STREAM_CAPTURE_STATUS_ACTIVE) {
        std::cerr << "Unexpected capturing status: " << cp1_capt_status << std::endl;
        std::terminate();
      }
    
      CUgraphConditionalHandle cond_h;
      check(cuGraphConditionalHandleCreate(&cond_h, cp1_graph, ctx, condition_value, CU_GRAPH_COND_ASSIGN_DEFAULT));
    
      // add node B: if condition
      CUgraphNodeParams node_B_params{};
      node_B_params.type = CU_GRAPH_NODE_TYPE_CONDITIONAL;
      node_B_params.conditional.handle = cond_h;
      node_B_params.conditional.type = CU_GRAPH_COND_TYPE_SWITCH;
      node_B_params.conditional.size = 3; // for 3 branches of 3-way-switch
      node_B_params.conditional.ctx = ctx;
    
      // get graph built by capturing stream `capt_stream`
      CUstreamCaptureStatus cp2_capt_status;
      CUgraph cp2_graph;
      const CUgraphNode* cp2_dependencies;
      size_t cp2_num_dependencies;
      check(cuStreamGetCaptureInfo(capt_stream, &cp2_capt_status, nullptr, &cp2_graph, &cp2_dependencies, &cp2_num_dependencies));
    
      if (cp2_capt_status != CU_STREAM_CAPTURE_STATUS_ACTIVE) {
        std::cerr << "Unexpected capturing status: " << cp2_capt_status << std::endl;
        std::terminate();
      }
    
      CUgraphNode node_B;
      check(cuGraphAddNode(&node_B, cp2_graph, cp2_dependencies, cp2_num_dependencies, &node_B_params));
    
      // insert nodeB into the stream
      check(cuStreamUpdateCaptureDependencies(capt_stream, &node_B, 1, CU_STREAM_SET_CAPTURE_DEPENDENCIES));
    
      CUstream case0_stream;
      CUstream case1_stream;
      CUstream case2_stream;
      check(cuStreamCreate(&case0_stream, this_dev));
      check(cuStreamCreate(&case1_stream, this_dev));
      check(cuStreamCreate(&case2_stream, this_dev));
    
      check(cuStreamBeginCaptureToGraph(case0_stream, node_B_params.conditional.phGraph_out[0], nullptr, nullptr, 0, CU_STREAM_CAPTURE_MODE_RELAXED));
      check(cuStreamBeginCaptureToGraph(case1_stream, node_B_params.conditional.phGraph_out[1], nullptr, nullptr, 0, CU_STREAM_CAPTURE_MODE_RELAXED));
      check(cuStreamBeginCaptureToGraph(case2_stream, node_B_params.conditional.phGraph_out[2], nullptr, nullptr, 0, CU_STREAM_CAPTURE_MODE_RELAXED));
    
      int *h_ptr1 = h_ptr + 1;
      int *h_ptr2 = h_ptr + 2;
    
      void **extra = nullptr;
    
      void *case0_krn_args[] = {&h_ptr};
      check(cuLaunchKernel(krn, 1, 1, 1, 1, 1, 1, 0, case0_stream, case0_krn_args, extra));
      check(cuLaunchKernel(krn, 1, 1, 1, 1, 1, 1, 0, case0_stream, case0_krn_args, extra));
      check(cuLaunchKernel(krn, 1, 1, 1, 1, 1, 1, 0, case0_stream, case0_krn_args, extra));
    
      CUgraph case0_graph;
      check(cuStreamEndCapture(case0_stream, &case0_graph));
      check(cuStreamDestroy(case0_stream));
    
      void *case1_krn_args[] = {&h_ptr1};
      check(cuLaunchKernel(krn, 1, 1, 1, 1, 1, 1, 0, case1_stream, case1_krn_args, extra));
      check(cuLaunchKernel(krn, 1, 1, 1, 1, 1, 1, 0, case1_stream, case1_krn_args, extra));
      check(cuLaunchKernel(krn, 1, 1, 1, 1, 1, 1, 0, case1_stream, case1_krn_args, extra));
    
      CUgraph case1_graph;
      check(cuStreamEndCapture(case1_stream, &case1_graph));
      check(cuStreamDestroy(case1_stream));
    
      void *case2_krn_args[] = {&h_ptr2};
      check(cuLaunchKernel(krn, 1, 1, 1, 1, 1, 1, 0, case2_stream, case2_krn_args, extra));
      check(cuLaunchKernel(krn, 1, 1, 1, 1, 1, 1, 0, case2_stream, case2_krn_args, extra));
      check(cuLaunchKernel(krn, 1, 1, 1, 1, 1, 1, 0, case2_stream, case2_krn_args, extra));
    
      CUgraph case2_graph;
      check(cuStreamEndCapture(case2_stream, &case2_graph));
      check(cuStreamDestroy(case2_stream));
    
      CUgraph complete_graph;
      check(cuStreamEndCapture(capt_stream, &complete_graph));
      check(cuStreamDestroy(capt_stream));
    
      return complete_graph;
    }
    
    void test_graph_update(CUstream launch_stream, CUfunction krn)
    {
      /*  */
      CUdevice this_dev;
      check(cuStreamGetDevice(launch_stream, &this_dev));
    
      CUstream capt_stream;
      check(cuStreamCreate(&capt_stream, this_dev));
    
      // allocate pinned host memory
      int *h_ptr;
      check(cuMemAllocHost((void **)&h_ptr, 3 * sizeof(int)));
      // initialize from the host
      h_ptr[0] = 0;
      h_ptr[1] = 0;
      h_ptr[2] = 0;
    
      CUgraph graph_var0 = util_build_graph(this_dev, 0, krn, h_ptr);
      CUgraph graph_var1 = util_build_graph(this_dev, 1, krn, h_ptr);
      CUgraph graph_var2 = util_build_graph(this_dev, 2, krn, h_ptr);
    
      // instantiate, upload and launch the graph
      CUgraphExec graph_exec;
      CUDA_GRAPH_INSTANTIATE_PARAMS params;
      params.flags = CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD;
      params.hUploadStream = launch_stream;
      check(cuGraphInstantiateWithParams(&graph_exec, graph_var0, &params));
    
      if (params.result_out != CUDA_GRAPH_INSTANTIATE_SUCCESS) {
        throw std::runtime_error("CUDA_GRAPH_INSTANTIATE result is not a success");
      }
    
      check(cuGraphUpload(graph_exec, launch_stream));
    
      // launch
      check(cuGraphLaunch(graph_exec, launch_stream));
    
      // synchronize
      check(cuStreamSynchronize(launch_stream));
    
      int step0_expected[3] = {3, 0, 0};
      if (!array_cmp_equal(h_ptr, step0_expected, 3)) {
         throw std::runtime_error("test_graph_update, step0 check failed");
      }
    
      // update second variant and launch again
      CUgraphExecUpdateResultInfo update1_res{};
      check(cuGraphExecUpdate(graph_exec, graph_var1, &update1_res));
    
      check(cuGraphUpload(graph_exec, launch_stream));
    
      // launch
      check(cuGraphLaunch(graph_exec, launch_stream));
    
      // synchronize
      check(cuStreamSynchronize(launch_stream));
    
      int step1_expected[3] = {3, 3, 0};
      if (!array_cmp_equal(h_ptr, step1_expected, 3)) {
         throw std::runtime_error("test_graph_update, step1 check failed");
      }
    
      // update last variant and launch again
      CUgraphExecUpdateResultInfo update2_res{};
      check(cuGraphExecUpdate(graph_exec, graph_var2, &update2_res));
    
      check(cuGraphUpload(graph_exec, launch_stream));
    
      // launch
      check(cuGraphLaunch(graph_exec, launch_stream));
    
      // synchronize
      check(cuStreamSynchronize(launch_stream));
    
      int step2_expected[3] = {3, 3, 3};
      if (!array_cmp_equal(h_ptr, step2_expected, 3)) {
         throw std::runtime_error("test_graph_update, step2 check failed");
      }
    
      check(cuMemFreeHost(h_ptr));
      check(cuGraphDestroy(graph_var0));
      check(cuGraphDestroy(graph_var1));
      check(cuGraphDestroy(graph_var2));
    }
    
    int main(void) {
        check(cuInit(0));
    
        int num_devices = -1;
        check(cuDeviceGetCount(&num_devices));
    
        std::cout << "Number of CUDA devices found: " << num_devices << std::endl;
        if (num_devices < 1) {
           std::cerr << "No CUDA devices found!" << std::endl;
           return 1;
        }
    
        CUdevice dev;
        check(cuDeviceGet(&dev, 0));
    
        char device_name[256];
        check(cuDeviceGetName(device_name, sizeof(device_name), dev));
    
        int cc_maj;
        int cc_min;
        check(cuDeviceGetAttribute(&cc_maj, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, dev));
        check(cuDeviceGetAttribute(&cc_min, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, dev));
    
        std::cout << "Using device: "
          << device_name << ", CC: (" << cc_maj << ", " << cc_min << ")"
          << std::endl;
    
        CUcontext pctx;
        check(cuDevicePrimaryCtxRetain(&pctx, dev));
        check(cuCtxSetCurrent(pctx));
    
        CUstream launch_stream;
        check(cuStreamCreate(&launch_stream, CU_STREAM_DEFAULT));
    
        static constexpr const char* simple_kernels_fatbin = "simple_kernels.cubin";
        static constexpr const char* conditional_kernels_fatbin = "conditional_kernels.cubin";
    
        // contains kernels: empty_kernel(void), add_one(int *)
        CUmodule simple_kernels_mod;
        {
          std::ostringstream simple_fatbin;
          {
            std::ifstream file(simple_kernels_fatbin, std::ios::binary);
            simple_fatbin << file.rdbuf();
            file.close();
          }
          check(cuModuleLoadData(&simple_kernels_mod, simple_fatbin.str().c_str()));
        }
    
        // contains kernels: empty_kernel, add_one, loop_kernel, set_handle
        CUmodule conditional_kernels_mod;
        {
          std::ostringstream conditional_fatbin;
          {
            std::ifstream file(conditional_kernels_fatbin, std::ios::binary);
            conditional_fatbin << file.rdbuf();
            file.close();
          }
          check(cuModuleLoadData(&conditional_kernels_mod, conditional_fatbin.str().c_str()));
        }
    
        // test_graph_is_building
        std::cout << "test_graph_is_building ...";
        test_graph_is_building(launch_stream);
        std::cout << "OK" << std::endl;
    
        // group of tests using simple kernels
        {
          CUfunction empty_krn;
          try {
            check(cuModuleGetFunction(&empty_krn, simple_kernels_mod, "_Z12empty_kernelv"));
          } catch (const std::runtime_error &e) {
            std::cerr << "Error processign simple_kernels_mod: could not find empty_kernel " << std::endl;
            throw e;
          }
    
          // test_graph_straight
          std::cout << "test_graph_straight ...";
          test_graph_straight(launch_stream, empty_krn);
          std::cout << "OK" << std::endl;
    
          // test_graph_fork_join
          std::cout << "test_graph_fork_join ...";
          test_graph_fork_join(launch_stream, empty_krn);
          std::cout << "OK" << std::endl;
    
          // test_graph_is_join_required
          std::cout << "test_graph_is_join_required ...";
          test_graph_is_join_required(launch_stream, empty_krn);
          std::cout << "OK" << std::endl;
    
          CUfunction add_one_krn;
          try {
            check(cuModuleGetFunction(&add_one_krn, simple_kernels_mod, "_Z7add_onePi"));
          } catch (const std::runtime_error &e) {
            std::cerr << "Error processing simple_kernels_mod: could not find add_one kernel" << std::endl;
            throw e;
          }
    
          // test_graph_repeat_capture
          std::cout << "test_graph_repeat_capture ...";
          test_graph_repeat_capture(launch_stream, add_one_krn);
          std::cout << "OK" << std::endl;
        }
    
        // test_graph_capture_errors  : NOT suitable for C++, as it tests Python class behavior
    
    
        {
    /*
     *  0x194    _Z12empty_kernelv   R_CUDA_64
     *  0x124    _Z7add_onePi        R_CUDA_64
     *   0xb4    _Z10set_handleyi    R_CUDA_64
     *   0x44    _Z11loop_kernely    R_CUDA_64
     */
          CUfunction add_one_krn;
          try {
            check(cuModuleGetFunction(&add_one_krn, conditional_kernels_mod, "_Z7add_onePi"));
          } catch (const std::runtime_error &e) {
            std::cerr << "Error processing conditional_kernels_mod: could not find add_one kernel" << std::endl;
            throw e;
          }
    
          CUfunction set_handle_krn;
          try {
            check(cuModuleGetFunction(&set_handle_krn, conditional_kernels_mod, "_Z10set_handleyi"));
          } catch (const std::runtime_error &e) {
            std::cerr << "Error processing conditional_kernels_mod: could not find set_handle kernel" << std::endl;
            throw e;
          }
    
          // test_graph_conditional_if
          std::cout << "test_graph_conditional_if ...";
          test_graph_conditional_if(launch_stream, add_one_krn, set_handle_krn, 0);
          test_graph_conditional_if(launch_stream, add_one_krn, set_handle_krn, 1);
          std::cout << "OK" << std::endl;
    
          // test_graph_conditional_if_else
          std::cout << "test_graph_conditional_if_else ...";
          test_graph_conditional_if_else(launch_stream, add_one_krn, set_handle_krn, 0);
          test_graph_conditional_if_else(launch_stream, add_one_krn, set_handle_krn, 1);
          std::cout << "OK" << std::endl;
    
          // test_graph_conditional_switch
          std::cout << "test_graph_conditional_switch ...";
          test_graph_conditional_switch(launch_stream, add_one_krn, set_handle_krn, 0);
          test_graph_conditional_switch(launch_stream, add_one_krn, set_handle_krn, 1);
          test_graph_conditional_switch(launch_stream, add_one_krn, set_handle_krn, 2);
          test_graph_conditional_switch(launch_stream, add_one_krn, set_handle_krn, 3);
          std::cout << "OK" << std::endl;
    
          CUfunction empty_krn;
          try {
            check(cuModuleGetFunction(&empty_krn, conditional_kernels_mod, "_Z12empty_kernelv"));
          } catch (const std::runtime_error &e) {
            std::cerr << "Error processing conditional_kernels_mod: could not find empty_kernel" << std::endl;
            throw e;
          }
    
          CUfunction loop_krn;
          try {
            check(cuModuleGetFunction(&loop_krn, conditional_kernels_mod, "_Z11loop_kernely"));
          } catch (const std::runtime_error &e) {
            std::cerr << "Error processing conditional_kernels_mod: could not find loop_kernel" << std::endl;
            throw e;
          }
    
          // test_graph_conditional_while
          std::cout << "test_graph_conditional_while ...";
          test_graph_conditional_while(launch_stream, empty_krn, add_one_krn, loop_krn, 0);
          test_graph_conditional_while(launch_stream, empty_krn, add_one_krn, loop_krn, 1);
          std::cout << "OK" << std::endl;
    
          // test_graph_child_graph
          std::cout << "test_graph_child ...";
          test_graph_child(launch_stream, add_one_krn);
          std::cout << "OK" << std::endl;
    
          // test_graph_update
          std::cout << "test_graph_update ...";
          test_graph_update(launch_stream, add_one_krn);
          std::cout << "OK" << std::endl;
        }
    
        // release resources
        check(cuModuleUnload(conditional_kernels_mod));
        check(cuModuleUnload(simple_kernels_mod));
        check(cuStreamDestroy(launch_stream));  // destroy stream
        check(cuCtxSetCurrent(nullptr));        // unbind context
        check(cuDevicePrimaryCtxRelease(dev));  // release primary context
    
        return 0;
    }

    Note that this source file won't compile for CTK 13.0, due to API changes in cuStreamGetCaptureInfo between 12.9 and 13.0 as well as related changes to other graph APIs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

cuda.coreEverything related to the cuda.core moduletriageNeeds the team's attention

Type

No type

Projects

  • Status
    Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions