This is an automated email from the ASF dual-hosted git repository.

tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git


The following commit(s) were added to refs/heads/main by this push:
     new a59b4266 [FIX] Build CUDA sources through tvm_ffi.cpp on Windows (#829)
a59b4266 is described below

commit a59b4266c0b692de311edb63ff7fd12ca7785d24
Author: YevheniiKotyrlo <[email protected]>
AuthorDate: Tue Sep 29 21:28:55 2026 +0300

    [FIX] Build CUDA sources through tvm_ffi.cpp on Windows (#829)
    
    ## Problem
    
    `_generate_ninja_build` has never built a CUDA source on Windows (#822).
    Each failure is reached only once the one before it is fixed:
    
    1. `default_cuda_cflags = ["-Xcompiler", "/std:c++17", "/O2"]`:
    `-Xcompiler` forwards one argument, so nvcc reads `/O2` as a second
    input file — `nvcc fatal : A single input file is required for a
    non-link phase when an outputfile is specified`.
    2. That `-Xcompiler /std:c++17` reaches cl after nvcc's own `-std`, so
    it overrides a caller's `-std=c++20`.
    3. No arch target is passed and `cudart` is never linked — `LNK2019:
    unresolved external symbol __cudaRegisterFatBinary`.
    4. `/LIBPATH:` is unquoted, and the CUDA toolkit installs under
    `C:\Program Files` — `LNK1181: cannot open input file
    'Files\NVIDIA.obj'`. The torch C-DLPack addon links Python's `libs` the
    same way, and a Python installed for all users lives there too —
    `LNK1181: cannot open input file 'Files\Python313\libs.obj'`.
    5. **With 1-4 fixed, a module mixing C++ and CUDA sources fails to
    link**: nvcc compiles host code against cl's default static runtime
    while the C++ objects use `/MD` — `LNK2038: mismatch detected for
    'RuntimeLibrary': value 'MD_DynamicRelease' doesn't match value
    'MT_StaticRelease'`.
    
    Separately, `default_cxxflags` on Windows has no `/O2`, so C++ sources
    build unoptimized, although the `build` and `load` docstrings list `/O2`
    and the Linux branch passes `-O2`.
    
    ## Solution
    
    The Windows nvcc defaults become Linux's, `-std=c++17 -O2`, which nvcc
    forwards to cl for the host pass (`nvcc --dryrun` shows `cl.exe
    /std:c++17 ... -O2`), plus `-Xcompiler /MD`. A caller's `-std=c++20`
    then applies the way it does on Linux: nvcc keeps the last value of a
    repeated option. With CUDA, the arch target from `_get_cuda_target()`
    and `cudart.lib` from `CUDA_HOME/lib/x64` are added. Every `/LIBPATH:`
    goes through `subprocess.list2cmdline`, which quotes it only when it
    holds a space, so a path without one is emitted as before. C++ gets
    `/O2`.
    
    ## Why it isn't caught today
    
    The Windows CI job has no GPU, so the `load_inline` CUDA tests skip, and
    the Python it installs does not live under a path with a space.
    
    ## Tests
    
    No new test: the existing CUDA tests cover this build line. On Windows,
    `test_load_inline_cuda`, `test_load_inline_both` and
    `test_cuda_memory_alloc_noleak` fail on `main` and pass with this
    change, and so do `test_build_torch_c_dlpack_extension` and
    `test_parallel_build` with Python under a path with a space.
    
    ## Verification
    
    Windows 11, MSVC 19.51, CUDA 13.3, RTX 3090 Ti, Python 3.13 under
    `C:\Program Files`, `pytest tests/python` inside `VsDevCmd` as CI runs
    it: on `main` 5 failed (`test_load_inline_cuda`,
    `test_load_inline_both`, `test_cuda_memory_alloc_noleak`,
    `test_build_torch_c_dlpack_extension`, `test_parallel_build`) and 2586
    passed · on this branch all of them pass · `ruff check`, `ruff format
    --check` and `ty check --error-on-warning` clean. With #830, FreeToken's
    82 JIT kernels compile, link and load on Windows through this build
    line.
    
    ## Known limits
    
    A space elsewhere in a generated build still splits it: an include path,
    or a build directory under a home directory whose name has one. Those
    sit on build statements too, which need ninja's `$ ` escaping as well as
    quoting, on every platform, so I kept them out of this PR.
    
    ## Base
    
    `c96f9e3`, and it merges cleanly with `main`.
    
    Fixes #822
---
 python/tvm_ffi/cpp/extension.py                    | 26 +++++++++++++++++-----
 .../utils/_build_optional_torch_c_dlpack.py        |  7 ++++--
 2 files changed, 26 insertions(+), 7 deletions(-)

diff --git a/python/tvm_ffi/cpp/extension.py b/python/tvm_ffi/cpp/extension.py
index f11b3284..a575b767 100644
--- a/python/tvm_ffi/cpp/extension.py
+++ b/python/tvm_ffi/cpp/extension.py
@@ -364,6 +364,14 @@ def _run_command_in_dev_prompt(
         ) from e
 
 
+def _windows_argument(argument: str) -> str:
+    """Quote one argument of a Windows command line if it would otherwise 
split on a space.
+
+    The CUDA toolkit installs under ``Program Files`` by default.
+    """
+    return subprocess.list2cmdline([argument])
+
+
 def _generate_ninja_build(  # noqa: PLR0915, PLR0912
     name: str,
     extra_cflags: Sequence[str],
@@ -400,7 +408,7 @@ def _generate_ninja_build(  # noqa: PLR0915, PLR0912
     tvm_ffi_lib_name = tvm_ffi_lib.stem
     if IS_WINDOWS:
         default_cflags = ["/O2", "/MD"]
-        default_cxxflags = ["/std:c++17", "/MD", "/EHsc"]
+        default_cxxflags = ["/std:c++17", "/O2", "/MD", "/EHsc"]
         _win_warnings = [
             "/wd4819",
             "/wd4251",
@@ -415,12 +423,20 @@ def _generate_ninja_build(  # noqa: PLR0915, PLR0912
         ]
         default_cflags += _win_warnings
         default_cxxflags += _win_warnings
-        default_cuda_cflags = ["-Xcompiler", "/std:c++17", "/O2"]
+        # nvcc forwards its own -std and -O to cl for the host pass, as it 
does to gcc.
+        # /MD matches the C++ objects: cl's default /MT fails a mixed link 
(LNK2038).
+        default_cuda_cflags = ["-Xcompiler", "/MD", "-std=c++17", "-O2"]
         default_ldflags = [
             "/DLL",
-            f"/LIBPATH:{tvm_ffi_lib_path}",
+            _windows_argument(f"/LIBPATH:{tvm_ffi_lib_path}"),
             f"{tvm_ffi_lib_name}.lib",
         ]
+        if with_cuda:
+            default_cuda_cflags += [_get_cuda_target()]
+            default_ldflags += [
+                _windows_argument(f"/LIBPATH:{Path(_find_cuda_home()) / 'lib' 
/ 'x64'}"),
+                "cudart.lib",  # cuda runtime library
+            ]
     else:
         default_cflags = ["-fPIC", "-O2"]
         default_cxxflags = ["-std=c++17", "-fPIC", "-O2"]
@@ -1266,7 +1282,7 @@ def build(  # noqa: PLR0913
         The default flags are:
 
         - ['-Xcompiler', '-fPIC', '-std=c++17', '-O2'] (Linux/macOS)
-        - ['-Xcompiler', '/std:c++17', '/O2'] (Windows)
+        - ['-Xcompiler', '/MD', '-std=c++17', '-O2'] (Windows)
 
     extra_ldflags
         The extra linker flags.
@@ -1422,7 +1438,7 @@ def load(  # noqa: PLR0913
         The default flags are:
 
         - ['-Xcompiler', '-fPIC', '-std=c++17', '-O2'] (Linux/macOS)
-        - ['-Xcompiler', '/std:c++17', '/O2'] (Windows)
+        - ['-Xcompiler', '/MD', '-std=c++17', '-O2'] (Windows)
 
     extra_ldflags
         The extra linker flags.
diff --git a/python/tvm_ffi/utils/_build_optional_torch_c_dlpack.py 
b/python/tvm_ffi/utils/_build_optional_torch_c_dlpack.py
index 9e4f9a16..e8466d93 100644
--- a/python/tvm_ffi/utils/_build_optional_torch_c_dlpack.py
+++ b/python/tvm_ffi/utils/_build_optional_torch_c_dlpack.py
@@ -847,7 +847,7 @@ def main() -> None:  # noqa: PLR0912, PLR0915
 
         for lib_dir in torch.utils.cpp_extension.library_paths():
             if IS_WINDOWS:
-                ldflags.append(f"/LIBPATH:{lib_dir}")
+                
ldflags.append(subprocess.list2cmdline([f"/LIBPATH:{lib_dir}"]))
             else:
                 ldflags.extend(["-L", str(lib_dir)])
 
@@ -879,7 +879,10 @@ def main() -> None:  # noqa: PLR0912, PLR0915
                 )
             for python_libdir in python_libdir_list:
                 if python_libdir and (Path(python_libdir) / 
python_lib).exists():
-                    ldflags.append(f"/LIBPATH:{python_libdir.replace(':', 
'$:')}")
+                    # A Python installed for all users lives under C:\Program 
Files.
+                    ldflags.append(
+                        
subprocess.list2cmdline([f"/LIBPATH:{python_libdir.replace(':', '$:')}"])
+                    )
                     break
 
         if IS_DARWIN:

Reply via email to