Skip to content

fix https://github.com/NVIDIA/TensorRT-Edge-LLM/issues/204: pass the CUDA include path to the XQA NVRTC JIT - #205

Open
filipemartinsubrobotics wants to merge 2 commits into
NVIDIA:mainfrom
filipemartinsubrobotics:fix/xqa-nvrtc-cuda-include-path
Open

filipemartinsubrobotics wants to merge 2 commits into
NVIDIA:mainfrom
filipemartinsubrobotics:fix/xqa-nvrtc-cuda-include-path

Conversation

@filipemartinsubrobotics

@filipemartinsubrobotics filipemartinsubrobotics commented Sep 11, 2026

Copy link
Copy Markdown

Addresses the first of the four findings in #204. The other three, and the
support-matrix question, are untouched by this PR and should outlive it — hence a
plain reference rather than a closing keyword.

Why

buildNvrtcOptions() passes no -I to NVRTC, on the stated grounds that every header reaches
nvrtcCreateProgram as a virtual include. That holds for the kernel's own headers, but mha.cu
includes cuda_fp16.h, which includes vector_types.h from the CUDA toolkit. NVRTC on CUDA 12.x
for x86 does not carry that as a builtin, so the XQA kernel never compiles:

cuda_fp16.h(129): catastrophic error: cannot open source file "vector_types.h"
1 catastrophic error detected in the compilation of "mha.cu".

Every model whose decoder takes the XQA JIT path then fails to load, and it surfaces during ONNX
parsing as Could not create the plugin, which points away from the cause. The option builder is
shared by every SM, so this is not architecture specific.

What

One include path, taken from CUDA_DIR, which CMakeLists.txt already resolves for the build.
It reaches the source as a PRIVATE compile definition on edgellmPluginJit, the target that
compiles decoderXQAJitCompiler.cpp. EDGELLM_NVRTC_INCLUDE overrides it at runtime for installs
relocated away from the toolkit they were built against, and the conventional install path is the
fallback when the definition is absent, so the translation unit still compiles in any build that
does not set it.

Impact and risk

No behaviour change for builds that already work: the option is additive, and an include path that
resolves to the toolkit the build was configured against cannot shadow a virtual include, since
NVRTC resolves those first. No API or ABI change. No new dependency. The only build-system change
is one target_compile_definitions call.

If you would rather the path came from somewhere else, CUDAToolkit_INCLUDE_DIRS or a cache
variable, say so and I will reshape it.

Verification

x86-64 Ubuntu 22.04 under WSL2, CUDA 12.8, TensorRT 11.2.1.2, GeForce RTX 4060 Ti (SM89),
configured natively with both CuTe DSL groups built for sm_89.

  • Before: llm_inference fails at plugin creation with the error above.
  • After, with EDGELLM_NVRTC_INCLUDE unset, so the compile definition is the only source of
    the path: the SM89 XQA kernel compiles, and a Cosmos3-Edge INT4-AWQ checkpoint runs end to end
    against examples/multimodal/pics/red_panda.jpeg with a correct description, 94.4 tokens/s
    generating.

git-clang-format and cmake-format both report clean on the change.

Related

Issue #204 also lists three other things found on the same build, which are not touched here: the
CUDA 12.8 floor, ENABLE_CUTE_DSL=OFF failing well downstream rather than at configure time, and
ENABLE_CUTE_DSL silently rejecting a comma-separated list. Happy to send separate PRs for any of
them.


Found during the NVIDIA / OpenHackathons / Oracle Open Models Codefest 2026,
while bringing up an INT4-AWQ Cosmos3-Edge reasoner on a DGX B300 for an
offline-first search-and-rescue robotics entry (Team UBR Stack). The production
target is a Jetson Orin Nano; the B300 is a bench machine used for evaluation.

buildNvrtcOptions() passed no -I, on the grounds that every header reaches
nvrtcCreateProgram as a virtual include. That holds for the kernel's own
headers, but mha.cu includes cuda_fp16.h, which includes vector_types.h from
the CUDA toolkit. NVRTC on CUDA 12.x for x86 does not carry that as a builtin,
so every model whose decoder takes the XQA JIT path fails to load:

  cuda_fp16.h(129): catastrophic error: cannot open source file "vector_types.h"

It surfaces during ONNX parsing as "Could not create the plugin", which points
away from the cause.

Pass one include path, taken from CUDA_DIR, which CMake already resolves for
this build. EDGELLM_NVRTC_INCLUDE overrides it at runtime for installs
relocated away from the toolkit they were built against, and the conventional
install path is the fallback when the definition is absent.

The option builder is shared by every SM, so this is not architecture
specific. Verified on x86-64 with CUDA 12.8 against an SM89 GPU: the XQA
kernel compiles in about a second, and a Cosmos3-Edge INT4-AWQ checkpoint then
runs end to end with the environment variable unset, so the build-system path
is the only source of the include directory.

Fixes NVIDIA#204

Signed-off-by: Filipe Martins <293984334+filipemartinsubrobotics@users.noreply.github.com>

@sylvesterkaczmarek sylvesterkaczmarek left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The embedded include path comes from ${CUDA_DIR}/include, but FindNVRTC.cmake explicitly supports selecting NVRTC through NVRTC_ROOT_DIR or CUDA_TARGET_DIR and resolves the actual headers as NVRTC_INCLUDE_DIR. In those supported configurations this can link against one NVRTC toolkit while the runtime JIT searches another header tree. Could the definition use the resolved NVRTC_INCLUDE_DIR (or equivalent resolved CUDA include path) and cover a non-default NVRTC root?

…UDA_DIR

Review on NVIDIA#205: FindNVRTC.cmake supports selecting NVRTC through NVRTC_ROOT_DIR
or CUDA_TARGET_DIR, so baking ${CUDA_DIR}/include could link one toolkit's NVRTC
while the runtime JIT searched another's headers.

Resolving NVRTC_INCLUDE_DIR instead would not be right either: that is found by
locating nvrtc.h, and a standalone NVRTC package ships nvrtc.h alone. The
nvidia-cuda-nvrtc wheel's include/ holds exactly one header, so pointing
NVRTC_ROOT_DIR at it and baking that directory would hand the JIT a tree with
neither cuda_fp16.h nor vector_types.h — the failure this series fixes.

So resolve the header that is actually included, across the roots FindNVRTC
already honours and in the same precedence. A non-default NVRTC root wins when
it can serve the compile and is declined when it cannot, with a warning and the
CUDA_DIR fallback rather than a silent mismatch.

Checked against four real configurations on one box: an nvidia-cu13 wheel with a
full header set resolves to itself; NVRTC_ROOT_DIR=/usr with apt CUDA 11.5 there
resolves to /usr/include; CUDA_TARGET_DIR at a second toolkit resolves to that
toolkit; and the nvidia-cuda-nvrtc wheel falls back to CUDA_DIR with a warning.

A cross build still bakes a path correct on the build host, which may not be
where the toolkit sits on the target. EDGELLM_NVRTC_INCLUDE covers that, and a
comment now says so.

Signed-off-by: Filipe Martins <293984334+filipemartinsubrobotics@users.noreply.github.com>
@filipemartinsubrobotics

Copy link
Copy Markdown
Author

You are right that the two can diverge. I have pushed a change, though not quite the suggested one.

NVRTC_INCLUDE_DIR is resolved by locating nvrtc.h. What the JIT compile needs is the CUDA runtime headers, cuda_fp16.h and the vector_types.h it includes. In a full toolkit those sit beside nvrtc.h and the two coincide; in a standalone NVRTC package, which ships nvrtc.h and the libraries with no runtime headers, they do not, and baking NVRTC_INCLUDE_DIR there would reintroduce exactly the failure this PR fixes.

So the definition now resolves the header that is actually included, searching the roots FindNVRTC.cmake already honours, in the same precedence:

find_path(
  EDGELLM_JIT_CUDA_INCLUDE_DIR vector_types.h
  HINTS ${NVRTC_INCLUDE_DIR} ${NVRTC_ROOT_DIR}/include ${CUDA_TARGET_DIR}/include
        ${CUDA_DIR}/include
  NO_DEFAULT_PATH)

A non-default NVRTC root wins when it has the headers and is skipped when it does not, with a warning and the CUDA_DIR fallback rather than a silent mismatch. Resolution checked against each configuration:

configuration resolved
CUDA_DIR=12.8, NVRTC from the same toolkit /usr/local/cuda-12.8/include
CUDA_DIR=12.8, NVRTC_ROOT_DIR=/usr (11.5) /usr/include
CUDA_DIR=12.8, CUDA_TARGET_DIR=12.5 /usr/local/cuda-12.5/include
NVRTC root with nvrtc.h but no runtime headers CUDA_DIR fallback, with a warning

One case the definition cannot cover on its own is the case when a cross build bakes a path correct on the build host, and the toolkit may sit elsewhere on the target. That is what EDGELLM_NVRTC_INCLUDE is for, and there is now a comment saying so.

Rebuilt and re-verified on SM89 with the environment variable unset, so the baked path is the only source: the XQA kernel compiles and the Cosmos3-Edge INT4-AWQ checkpoint runs end to end. cmake-format and git-clang-format are clean.

@sylvesterkaczmarek sylvesterkaczmarek left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rechecked 4e5edfff. The JIT include path now resolves the runtime header tree through the same supported NVRTC/CUDA roots instead of assuming CUDA_DIR, and the fallback/override behavior covers standalone NVRTC and cross-target layouts. This resolves my earlier concern.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants