Skip to content

Add full SYCL support for Windows builds#7513

Open
ssheorey with Copilot wants to merge 72 commits into
mainfrom
copilot/add-full-sycl-support-windows
Open

Add full SYCL support for Windows builds#7513
ssheorey with Copilot wants to merge 72 commits into
mainfrom
copilot/add-full-sycl-support-windows

Conversation

Copilot AI commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Type

  • New feature (non-breaking change which adds functionality)

Motivation and Context

Adds full Windows SYCL support to align with PyTorch 2025.3.1 (oneAPI 2025.3.1). Migrates the Windows CI matrix from CUDA ON/OFF to target cpu, cuda, and xpu (SYCL), enabling open3d_xpu Python wheels and C++ binary distributions with SYCL acceleration on Windows.

Checklist:

  • I have run python util/check_style.py --apply to apply Open3D code style to my code.
  • This PR changes Open3D behavior or adds new functionality.
    • Both C++ (Doxygen) and Python (Sphinx / Google style) documentation is updated accordingly.
    • I have added or updated C++ and / or Python unit tests OR included test results here.
  • I will follow up and update the code if CI fails.

Description

CMake & Build Infrastructure

  • Relaxed Platform Checks: Lifted the Linux-only restriction on BUILD_SYCL_MODULE in CMakeLists.txt to support Windows builds.
  • Packaging Names: Appended -sycl to the packaged distribution name in cmake/Open3DPackaging.cmake when built with SYCL.
  • XPU Python Pathing: Routed compiled SYCL bindings to open3d/xpu in cpp/pybind/CMakeLists.txt and updated cpp/pybind/make_python_package.cmake to bundle the xpu directory structure.

Python Runtime & DLL Loading

  • Dynamic DLL Resolution: Configured Windows Python initialization in python/open3d/__init__.py to locate and load Intel compiler, oneMKL, and oneTBB redistributable DLL directories. Resolves the runtime directories dynamically using ONEAPI_ROOT (or default paths) with os.add_dll_directory to prevent DLL loading errors on Python 3.8+.
  • Subpackage Routing: Updated visualization, ML, and ML contrib initializers to import symbols correctly from the xpu submodule when running on a SYCL-enabled (xpu) binary.

GHA Windows CI / CD Matrix

  • Matrix Migration: Changed the GHA strategy matrix in .github/workflows/windows.yml from BUILD_CUDA_MODULE: [ON, OFF] to device: [cpu, cuda, xpu].
  • oneAPI Silent Provisioning: Added a setup step to fetch, silently install, and verify the exit codes for oneMKL 2025.3.1.10 and the Intel Compiler 2025.3.1.15 on Windows GHA runners when targeting xpu.
  • Toolchain & Linker Config: Configured CMake to invoke MSBuild with -T "Intel(R) oneAPI DPC++ Compiler" and search for OpenCL dynamically.
  • Unified Wheel Tests: Configured wheel building and testing to support both CPU and XPU variants, using PowerShell pattern-matching to automatically discover and verify the built wheel.
import open3d as o3d

# Check built configuration
print("SYCL Module Compiled:", o3d._build_config["BUILD_SYCL_MODULE"])

# Verify device availability
if o3d._build_config["BUILD_SYCL_MODULE"]:
    print("SYCL Device Available:", o3d.core.sycl.is_available())

@update-docs

update-docs Bot commented Jun 12, 2026

Copy link
Copy Markdown

Thanks for submitting this pull request! The maintainers of this repository would appreciate if you could update the CHANGELOG.md based on your changes.

@ssheorey ssheorey marked this pull request as ready for review June 16, 2026 06:37
@ssheorey ssheorey added this to the v0.20 milestone Jul 2, 2026
Copilot AI added 2 commits July 2, 2026 18:50
… compiler

In Intel oneAPI 2025.3.0, icpx.exe identifies as "GNU-like command-line"
on Windows. CMake's Ninja generator on Windows generates MSVC-style flags
when the C compiler (icx) is MSVC-like. This causes icpx to reject flags
like /nologo, /EHsc, /Zi and the CXX compiler check fails.

Fix: use icx for CXX on Windows in all 3 cmake invocations in the
windows.yml workflow, and in 3rdparty/embree/embree.cmake (ExternalProject
for embree SYCL build). icx.exe is MSVC-compatible, handles C++ and
supports SYCL via -fsycl, making it consistent with the C compiler.

On Linux, icpx remains correct (it is the standard SYCL C++ compiler).
ssheorey and others added 4 commits July 2, 2026 14:44
…ce strings

Cherry-picks the shared-library-by-default and pybind-simplification work
from copilot/separate-pybind-and-libopen3d without the host/device
(libOpen3D vs libOpen3D_cuda/xpu) library split.

- BUILD_SHARED_LIBS now defaults to ON.
- Single open3d.pybind module; CUDA/SYCL detected at runtime via
  core.cuda.is_available()/core.sycl.is_available() instead of shipping
  separate cpu/cuda package variants.
- Device strings accept a bare type name (e.g. "cuda"), defaulting id to 0.
- New utility::filesystem::GetSelfBinaryDirectory() helper; Logging.cpp
  print function is now a function-local static.
- Renamed pybind/core/tensor_type_caster -> type_caster and added an
  implicit Device<->str caster.
- Flattened ML custom-op output layout (no cpu/cuda subfolder) and fixed
  the torch/tf ops loaders to match.
- Fixed a static-link ordering bug (curl/BoringSSL) exposed by building
  Open3D as a shared library, using CMake's LINK_GROUP:RESCAN genex.
- Ported unrelated CI/Docker/WebRTC improvements from the source branch.
- Updated docs/compilation.rst to describe the single combined library.

Verified: BUILD_SHARED_LIBS=ON by default, cpp tests (Device, MemoryManager)
and python/test/core/test_core.py device tests pass, and python import/
smoke test shows no cpu/cuda subpackages.

Co-authored-by: Cursor <cursoragent@cursor.com>
Several bugs slipped through when cherry-picking CI/Docker changes and the
simplified single-pybind-module python packaging from
copilot/separate-pybind-and-libopen3d:

- python/open3d/__init__.py: _insert_pybind_names() stripped the "open3d."
  prefix (not just "pybind."), so e.g. "open3d.pybind.t" was registered as
  bare "t" instead of "open3d.t", breaking `import open3d.t` and any
  `from open3d.t... import ...`. This failed pytest collection on the
  Ubuntu and OpenBLAS CI jobs.
- .github/workflows/windows.yml: BUILD_WEBRTC was hardcoded to 'OFF', but
  main enables it for the STATIC_RUNTIME=ON + BUILD_SHARED_LIBS=OFF matrix
  cells, since our own prebuilt BoringSSL is built with the dynamic CRT and
  is only ABI-compatible when WebRTC (built with matching static CRT)
  supplies crypto/ssl symbols instead. Restored the conditional; this fixes
  LNK2001 "unresolved external symbol __imp_bsearch" failures in the three
  static-runtime build-lib jobs.
- .github/workflows/ubuntu-cuda.yml: GCE image family was bumped to
  "ubuntu-os-docker-gpu-2204-lts", which does not exist in the GCP project
  ("was not found" at instance-create time). Reverted to main's working
  "ubuntu-os-docker-gpu-2004-lts" (the GCE host image is independent of the
  Ubuntu version used inside the CI Docker container).
- .github/workflows/ubuntu-wheel.yml: the new split-out "build-lib" job
  calls docker_build.sh, which requires BUILD_TENSORFLOW_OPS/
  BUILD_PYTORCH_OPS to be set (bash `set -u`); these were set on the
  build-wheel job but missing on build-lib, causing "unbound variable".

Not fixed (pre-existing/flaky, reproduced identically on unrelated
branches, unrelated to this port): macOS gfortran symlink lookup failing
on macos-14 runners, and transient pyenv-installer network fetch failures
on ARM64/py313+ wheel builds.

Verified: reproduced the "open3d.t" ModuleNotFoundError from the CI logs
using a local minimal CPU-only build (BUILD_PYTHON_MODULE=ON, no GUI/tests/
ISPC/WebRTC) and confirmed `import open3d.t`, `from open3d.t.geometry
import Image`, and pytest collection of python/test/t/geometry/test_image.py
all work after the fix; python/test/core/test_core.py (279 tests) still
passes.

Co-authored-by: Cursor <cursoragent@cursor.com>
ssheorey and others added 30 commits July 9, 2026 16:37
Reusing a stripped Docker build tree caused overlay ENOSPC and forced
3rdparty recompiles in wheel jobs. Package CPU/CUDA devel (+ viewer) once,
run C++ tests in parallel with per-Python wheels on stock nvidia/cuda, and
compile Torch/TF ops only in the wheel stage where their ABI belongs.

Co-authored-by: Cursor <cursoragent@cursor.com>
Skip same-file cp when CI already places devel tars in open3d-artifacts/,
and pass BUILD_SHARED_LIBS=OFF into OpenBLAS Docker builds so ARM64 does
not hit unresolved OpenSSL from shared+static curl without WebRTC.

Co-authored-by: Cursor <cursoragent@cursor.com>
Store third-party downloads under the build directory so they ship in the
build-lib zip, and reconfigure the wheel job without cmake --fresh using the
same static-runtime flags as the artifact.

Co-authored-by: Cursor <cursoragent@cursor.com>
icx flush-to-zero was zeroing PCD packed-RGB denormals (~220 color error);
disable FTZ/FMA contraction and write packed color bits via memcpy. Also
match open3d_xpu wheels in test-wheel artifact download and relax VBG
extract count tolerance for cross-compiler variance.

Co-authored-by: Cursor <cursoragent@cursor.com>
build_pip_package_from_installed was installing CUDA torch first, so
open3d_torch_ops.so required libc10_cuda.so and failed Test wheel CPU
(CPU runner + CPU torch). Match main's effective ABI by keeping CPU torch.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ion.

The previous "build torch ops against CPU torch" fix broke the Ubuntu CUDA
wheel: with BUILD_CUDA_MODULE=ON the torch ops compile .cu kernels that need
CUDA-enabled torch headers (c10/cuda), but only CPU torch is installed. Build
the CUDA wheel with ML ops OFF (the shipped matrix is CPU wheel = ML ops, CUDA
wheel = no ML ops; CUDA torch ops are currently broken). The CPU companion
wheel keeps torch ops built against CPU torch.

Windows build-wheel reused build-lib's CMakeCache (BUILD_PYTHON_MODULE=OFF, no
torch), so FindPytorch queried a stale, torch-less interpreter. Clear cached
Python/Torch entries and pin this runner's Python.

Also cap the intermediate C++ tests bundle artifact to retention-days: 1.

Co-authored-by: Cursor <cursoragent@cursor.com>
Revert the CUDA-wheel-without-ML-ops shortcut. Instead mirror main's
build_pip_package(): build the CPU wheel first against the CPU torch shipped in
the wheel image, then install CUDA torch/tf and build the CUDA wheel so its ops
link against CUDA torch. Both wheels build torch/tf ops per
BUILD_{PYTORCH,TENSORFLOW}_OPS. This produces the same artifacts as main (open3d
CUDA wheel with ML ops, open3d_cpu wheel with ML ops) and keeps the CPU wheel's
ops CPU-linked so "Test wheel CPU" passes. Unset cached Python/Torch entries on
reconfigure since torch changes between the two wheels.

Co-authored-by: Cursor <cursoragent@cursor.com>
…eanup

- style-check: reflow std::memcpy in t/io/file_format/FilePCD.cpp (clang-format)
- Example open3d-cmake-find-package: copy runtime DLLs to
  $<TARGET_FILE_DIR:Draw> so it works with single-config Ninja (Windows
  SYCL/icx) as well as multi-config Visual Studio (build/Release).
- xpu wheel import: add sys.prefix/Library/bin to the Windows DLL search
  path. Intel DPC++ runtime pip wheels install their DLLs there (not under
  site-packages/*.data), which caused "DLL load failed while importing
  pybind" for the open3d-xpu wheel.
- windows.yml: remove leftover "Debug oneAPI layout" step that referenced
  the deleted print_oneapi_windows_paths.ps1 (would fail the ML/wheel job).
- Remove print_oneapi_windows_paths.ps1 debug helper and its invocation in
  install_oneapi_windows.ps1; restore trailing newline.

Co-authored-by: Cursor <cursoragent@cursor.com>
TODO: simplify python package DLL paths (next PR).
Resolve workflow conflicts after ss/dll landed on main; keep Windows SYCL
build-lib/build-wheel split and artifact reuse for CPU wheels.

Co-authored-by: Cursor <cursoragent@cursor.com>
The IntelLLVM IEEE-FP target_compile_options() call was missing its
closing paren, causing "Parse error. Function missing ending )" during
configure on all platforms. Add the missing ")".

Co-authored-by: Cursor <cursoragent@cursor.com>
CMake has no 'elif' command; use 'elseif'. The invalid command caused
"Unknown CMake command elif" during Windows configure.

Co-authored-by: Cursor <cursoragent@cursor.com>
Decouple wheel building from C++ unit-test outcome so a (possibly flaky)
test failure no longer blocks producing/publishing wheels:

- windows.yml: upload the CPU build artifact and run the build-wheel job
  with !cancelled(), so the artifact is available and wheels build even
  when the "Run C++ unit tests" step failed in build-lib.
- ubuntu-openblas.yml: attest, upload and release the wheel built in the
  "Docker build" step with !cancelled(), independent of the "Docker test"
  (C++ tests) result.

Also fix two Windows attestation steps that gated on the non-existent
matrix.BUILD_CUDA_MODULE (always false -> attestation never ran); use the
device-based condition to match their sibling upload steps.

ubuntu-wheel.yml already runs C++ tests in a separate test-cpp job, and
macOS/SYCL workflows do not run C++ unit tests in build-lib, so no change
is needed there.

Co-authored-by: Cursor <cursoragent@cursor.com>
Static Windows SYCL builds export Open3D::3rdparty_mkl in
Open3DTargets.cmake, and its link interface carries the OpenCL::OpenCL
imported target. Downstream projects doing find_package(Open3D) failed at
CMake configure with:

  set_target_properties ... target "Open3D::3rdparty_mkl" contains
  OpenCL::OpenCL but the target was not found.

Register OpenCL in Open3D_3RDPARTY_EXTERNAL_MODULES (static SYCL/Windows
only) so the installed Open3DConfig.cmake runs find_dependency(OpenCL) and
recreates the imported target, mirroring the existing CUDAToolkit pattern.
Shared builds are unaffected (3rdparty_mkl is not exported there), matching
the observed CI: the shared xpu leg passed while the static xpu leg failed
at "Linking to Open3D".

Co-authored-by: Cursor <cursoragent@cursor.com>
Two failures inherited from the ss/dll merge (#7516) on main:

1) Windows "Build wheel" Config failed with "Could NOT find Python3
   (missing: Development ...)" for every Python version except 3.12.
   build-lib configures with Python 3.12 (BUILD_PYTHON_MODULE=OFF) and
   build-wheel reuses that CMakeCache. Re-running cmake with
   `-U Python3*` does not clear FindPython3's INTERNAL artifact cache
   (`_Python3_*`, leading underscore), so the stale 3.12 include/lib
   paths broke Development.Module detection for other Python versions.
   Add `-U _Python3_*` in windows.yml and in ci_utils.sh build_pip_package
   (macOS reuses build-lib's 3.12 build dir the same way).

2) Ubuntu "Test wheel CPU" failed loading open3d_torch_ops.so
   (libc10_cuda.so missing): the CUDA wheel's ops link against CUDA torch,
   but test_wheel installs CPU torch on the CPU-only runner. Skip the
   ML-ops load check when a CUDA-built wheel is tested with a CPU-only
   framework (the companion open3d-cpu wheel still exercises the load;
   GPU CI covers the CUDA ops).

Reproduced the Python cache issue locally with a minimal find_package
(Python3 COMPONENTS Interpreter Development) project: `-U Python3*` alone
fails to re-detect a different Python's Development component, while adding
`-U _Python3_*` fixes it.

Co-authored-by: Cursor <cursoragent@cursor.com>
Two pre-existing failures surfaced after earlier fixes let the jobs
proceed further:

- Windows "Build lib (OFF, ON, xpu, Release)" failed "Linking to Open3D"
  with LNK2038 (/MD vs /MT). SYCL Open3D is always built with the dynamic
  runtime (/MD) because icx -fsycl rejects /MT, but the downstream
  open3d-cmake-find-package example was still configured with
  STATIC_WINDOWS_RUNTIME=ON (matrix.STATIC_RUNTIME) and built /MT. Force
  the example to STATIC_WINDOWS_RUNTIME=OFF for xpu so it matches Open3D.

- Ubuntu "Test wheel CPU" failed "Run Python unit tests": pytest
  collection of python/test/ml_ops/ imports open3d.ml.torch whose CUDA
  built ops cannot load against CPU-only torch (libc10_cuda.so missing).
  run_python_tests now skips ml_ops when the compiled ops fail to import,
  mirroring the earlier test_wheel gate.

Co-authored-by: Cursor <cursoragent@cursor.com>
The open3d-cmake-find-package example intermittently failed "Linking to
Open3D" on Windows with LNK2038 (Draw.obj built /MT while Open3D and its
3rdparty libs are /MD). find_package(Open3D) transitively loads dependency
configs, some of which call cmake_minimum_required()/cmake_policy() with an
old version and can reset CMP0091 away from NEW in this scope. With CMP0091
not NEW, CMAKE_MSVC_RUNTIME_LIBRARY is silently ignored and MSVC defaults the
target to the static runtime (/MT), which then fails to link against a
dynamic-runtime (/MD) Open3D.

Re-assert CMP0091 NEW and set MSVC_RUNTIME_LIBRARY on the target right before
it is created so the intended runtime is always honored regardless of the
runtime Open3D was built with. This also hardens the user-facing template.

Co-authored-by: Cursor <cursoragent@cursor.com>
Restore the pre-#7516 behavior where a CUDA wheel ships both the CPU- and
CUDA-linked PyTorch/TensorFlow ops and the loader picks one by device at
runtime. The single-DLL simplification flattened the ops next to pybind, so a
CUDA wheel only carried CUDA-linked ops and failed for users with CPU-only
torch/TF.

- ops CMake: output open3d_{torch,tf}_ops into lib/<cfg>/{cpu,cuda} by
  BUILD_CUDA_MODULE.
- make_python_package: keep pybind/libOpen3D flat; copy every present ops arch
  subdir into open3d/{cpu,cuda}.
- loaders: try open3d/cuda then open3d/cpu (torch: only when torch has matching
  CUDA); add package root to the Windows DLL search so the flat Open3D.dll
  resolves from open3d/{cpu,cuda}.
- ci_utils.sh: build the CPU wheel first, then bundle its cpu ops alongside the
  CUDA ops in the CUDA wheel (both build_pip_package and
  build_pip_package_from_installed).
- Drop the temporary "skip ml_ops on CPU-only runner" workarounds; the CUDA
  wheel now loads its CPU ops with CPU torch/TF.

pybind and libOpen3D stay a single flat module. BUILD_CUDA_MODULE is a PRIVATE
compile definition and the public core value types (Tensor/Device/Dtype/Blob)
have no CUDA-conditional layout, so a CPU-view ops lib safely dlopens the CUDA
libOpen3D.

Co-authored-by: Cursor <cursoragent@cursor.com>
find_package(Open3D) pulls in dependency configs that can reset policy
CMP0091 away from NEW in the example's scope. When CMP0091 is not NEW,
CMAKE_MSVC_RUNTIME_LIBRARY / MSVC_RUNTIME_LIBRARY are silently ignored and
MSVC defaults the target to the static runtime (/MT), which then fails to
link (LNK2038) against a dynamic-runtime (/MD) Open3D.

Set CMAKE_POLICY_DEFAULT_CMP0091=NEW before project() so the policy stays
NEW in every nested scope (mirrors 3rdparty/find_dependencies.cmake), and
add explicit /M compile flags on the Draw target as a defensive fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>
Move the "Docker test" step (which runs the C++ unit tests) to after the
wheel attestation/upload/release steps so failing or flaky tests no longer
block the wheel artifacts from being uploaded.

Co-authored-by: Cursor <cursoragent@cursor.com>
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