Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 115 additions & 19 deletions .github/workflows/publish-witwin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,147 +83,242 @@
PY

build_cuda_wheels:
name: Build CUDA wheel / ${{ matrix.os }} / py${{ matrix.python_version }}
name: Build stable CUDA wheel / ${{ matrix.os }}
needs: validate_release
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, windows-2022]
python_version: ["3.10", "3.11", "3.12"]

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python_version }}
python-version: "3.10"

- name: Free disk space (Linux)
if: runner.os == 'Linux'
shell: bash
run: |
echo "Disk before cleanup:"; df -h /
sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android \
/usr/local/share/boost /opt/hostedtoolcache/CodeQL \
/usr/local/share/powershell /usr/share/swift || true
sudo docker image prune --all --force || true
echo "Disk after cleanup:"; df -h /

- name: Install CUDA Toolkit
uses: Jimver/cuda-toolkit@v0.2.29
uses: Jimver/cuda-toolkit@v0.2.35
with:
cuda: "12.5.0"
cuda: "12.8.1"
method: local
log-file-suffix: "${{ runner.os }}-py${{ matrix.python_version }}"
log-file-suffix: "${{ runner.os }}-stable-abi"

- name: Set up MSVC
if: runner.os == 'Windows'
uses: ilammy/msvc-dev-cmd@v1
with:
arch: x64

- name: Export Windows CUDA_HOME
if: runner.os == 'Windows'
shell: pwsh
run: |
$cudaRoot = $env:CUDA_PATH
if (-not $cudaRoot) {
$nvcc = (Get-Command nvcc -ErrorAction Stop).Source
$cudaRoot = Split-Path (Split-Path $nvcc -Parent) -Parent
}
"CUDA_HOME=$cudaRoot" >> $env:GITHUB_ENV
"CUDA_PATH=$cudaRoot" >> $env:GITHUB_ENV
"CUDA_ROOT=$cudaRoot" >> $env:GITHUB_ENV
"CUDA_BIN_PATH=$cudaRoot" >> $env:GITHUB_ENV

- name: Show toolchain
shell: bash
run: |
python --version
nvcc --version
command -v cl || true

- name: Install CUDA extension build dependencies
run: |
python -m pip install --upgrade pip setuptools wheel ninja numpy
python -m pip install torch==2.6.0 --index-url https://download.pytorch.org/whl/cu124
python -m pip install torch==2.10.0 --index-url https://download.pytorch.org/whl/cu128
python -m pip install build

- name: Build packaged mesh SDF CUDA extension
- name: Build packaged stable mesh SDF CUDA extension
shell: bash
env:
MAX_JOBS: "2"
TORCH_CUDA_ARCH_LIST: "7.0;8.0;8.6;8.9;9.0+PTX"
WITWIN_CORE_MESH_SDF_CUDA_BUILD_DIR: ${{ runner.temp }}/witwin_core_mesh_sdf_cuda
WITWIN_CUDA_GENCODE_ARCHES: "7.0;7.5;8.0;8.6;8.9;9.0;10.0;10.1;12.0+PTX"
PACKAGE_DIR: ${{ needs.validate_release.outputs.package_dir }}
WITWIN_CORE_MESH_SDF_CUDA_BUILD_DIR: ${{ runner.temp }}/witwin_core_mesh_sdf_cuda/stable_abi_v1
run: |
python "$PACKAGE_DIR"/scripts/build_mesh_sdf_cuda_prebuilt.py --verbose

- name: Verify compiled CUDA architectures
shell: bash
env:
PACKAGE_DIR: ${{ needs.validate_release.outputs.package_dir }}
run: |
python "$PACKAGE_DIR"/scripts/verify_cuda_binary_arches.py \
--stem witwin_core_mesh_sdf_cuda \
"$PACKAGE_DIR"/witwin/core/geometry/cuda/prebuilt

- name: Build platform wheel
shell: bash
env:
PACKAGE_DIR: ${{ needs.validate_release.outputs.package_dir }}
run: |
python -m build --wheel "$PACKAGE_DIR"

- name: Normalize Linux wheel platform tag
- name: Repair Linux wheel platform tag
if: runner.os == 'Linux'
shell: bash
env:
PACKAGE_DIR: ${{ needs.validate_release.outputs.package_dir }}
run: |
# PyPI rejects the raw linux_x86_64 tag; the extension is built on the
# ubuntu-22.04 runner (glibc 2.35), so retag to the matching manylinux
# tag. torch/CUDA runtime libraries are runtime dependencies and stay
# unbundled, so no auditwheel repair is required.
shopt -s nullglob
python -m pip install auditwheel patchelf
mkdir -p "$PACKAGE_DIR"/wheelhouse
for whl in "$PACKAGE_DIR"/dist/*-linux_x86_64.whl; do
echo "Retagging $whl"
python -m wheel tags --platform-tag manylinux_2_35_x86_64 --remove "$whl"
python -m auditwheel repair "$whl" \
--plat manylinux_2_35_x86_64 \
--wheel-dir "$PACKAGE_DIR"/wheelhouse \
--exclude libc10.so --exclude libc10_cuda.so \
--exclude libtorch.so --exclude libtorch_cpu.so \
--exclude libtorch_cuda.so --exclude libtorch_python.so \
--exclude libcudart.so.12 --exclude libcuda.so.1
done
rm -f "$PACKAGE_DIR"/dist/*-linux_x86_64.whl
mv "$PACKAGE_DIR"/wheelhouse/*.whl "$PACKAGE_DIR"/dist/
ls -l "$PACKAGE_DIR"/dist

- name: Smoke install prebuilt wheel
shell: bash
env:
PACKAGE_DIR: ${{ needs.validate_release.outputs.package_dir }}
WITWIN_CORE_MESH_SDF_CUDA_PREBUILT: "1"
WITWIN_CORE_MESH_SDF_CUDA_BUILD_DIR: ${{ runner.temp }}/missing_prebuilt_build_dir
run: |
python -m pip uninstall -y witwin || true
python -m pip install --no-deps "$PACKAGE_DIR"/dist/*.whl
python - <<'PY'
import importlib.util
import os
import site
import zipfile
from pathlib import Path

wheel = next((Path(os.environ["PACKAGE_DIR"]) / "dist").glob("*.whl"))
names = zipfile.ZipFile(wheel).namelist()
native = [name for name in names if "witwin_core_mesh_sdf_cuda" in name and name.endswith((".so", ".pyd"))]
assert len(native) == 1, native
assert "/prebuilt/" in f"/{native[0]}"
assert "torch_" not in native[0]

candidates = []
for root in site.getsitepackages() + [site.getusersitepackages()]:
candidates.append(Path(root) / "witwin" / "core" / "geometry" / "cuda" / "build.py")
build_path = next((path for path in candidates if path.exists()), None)
assert build_path is not None, f"installed build.py not found in {candidates}"

spec = importlib.util.spec_from_file_location("witwin_core_mesh_sdf_cuda_build", build_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)

path = module.prebuilt_extension_path()
assert path.exists(), f"missing packaged prebuilt extension: {path}"
assert path.parent.name == "prebuilt"
extension = module.build_extension()
print(f"Loaded packaged mesh SDF CUDA extension from {extension.__file__}")
PY

- name: Upload wheel artifact
uses: actions/upload-artifact@v4
with:
name: wheel-${{ matrix.os }}-py${{ matrix.python_version }}
name: wheel-${{ matrix.os }}
path: ${{ needs.validate_release.outputs.package_dir }}/dist/*.whl
if-no-files-found: error

test_torch_compatibility:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Comment on lines 86 to +248
name: Stable ABI / ${{ matrix.os }} / py${{ matrix.compatibility.python_version }} / torch${{ matrix.compatibility.torch_version }}
needs:
- validate_release
- build_cuda_wheels
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, windows-2022]
compatibility:
- {python_version: "3.10", torch_version: "2.10.0", cuda_index: "cu128"}
- {python_version: "3.11", torch_version: "2.10.0", cuda_index: "cu128"}
- {python_version: "3.12", torch_version: "2.10.0", cuda_index: "cu128"}
- {python_version: "3.13", torch_version: "2.10.0", cuda_index: "cu128"}
- {python_version: "3.14", torch_version: "2.10.0", cuda_index: "cu128"}
- {python_version: "3.14", torch_version: "2.11.0", cuda_index: "cu128"}
- {python_version: "3.14", torch_version: "2.12.0", cuda_index: "cu126"}

steps:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.compatibility.python_version }}

- name: Download stable wheel
uses: actions/download-artifact@v4
with:
name: wheel-${{ matrix.os }}
path: dist

- name: Install compatibility environment
shell: bash
run: |
python -m pip install --upgrade pip
python -m pip install --no-cache-dir "torch==${{ matrix.compatibility.torch_version }}" \
--index-url "https://download.pytorch.org/whl/${{ matrix.compatibility.cuda_index }}"
python -m pip install --no-deps dist/*.whl

- name: Load the same stable binary
shell: bash
env:
EXPECTED_TORCH: ${{ matrix.compatibility.torch_version }}
WITWIN_CORE_MESH_SDF_CUDA_PREBUILT: "1"
WITWIN_CORE_MESH_SDF_CUDA_BUILD_DIR: ${{ runner.temp }}/must-not-jit-build
run: |
python - <<'PY'
import importlib.util
import os
import site
from pathlib import Path

import torch

assert torch.__version__.split("+", 1)[0] == os.environ["EXPECTED_TORCH"]
candidates = [
Path(root) / "witwin" / "core" / "geometry" / "cuda" / "build.py"
for root in site.getsitepackages() + [site.getusersitepackages()]
]
build_path = next((path for path in candidates if path.exists()), None)
assert build_path is not None, candidates
spec = importlib.util.spec_from_file_location("witwin_core_mesh_sdf_cuda_build", build_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)

native_path = module.prebuilt_extension_path()
assert native_path.exists(), native_path
extension = module.build_extension()
assert Path(extension.__file__).resolve() == native_path.resolve()
assert extension.query_mesh_unsigned_distance is not None
print(f"Loaded {native_path.name} with torch {torch.__version__} on Python {os.sys.version.split()[0]}")
PY

build_sdist:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
name: Build source distribution
needs: validate_release
runs-on: ubuntu-latest
Expand Down Expand Up @@ -257,6 +352,7 @@
if: github.event_name == 'release'
needs:
- build_cuda_wheels
- test_torch_compatibility
- build_sdist
runs-on: ubuntu-latest
permissions:
Expand Down
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,6 @@ venv.bak/

# mkdocs documentation
/site
.slangtorch_cache/
.fdtd_runtime_*.slang
.adjoint_kernels_runtime_*.slang
*.png
*.npy
*.npz
Expand Down
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
# WiTwin Core

WiTwin Core is the core package of the WiTwin stack. It provides the shared foundations used across WiTwin projects, with a focus on common scene representation, geometry primitives, mesh utilities, and state management that can be reused by different simulation modules.
WiTwin Core is the shared data and geometry package of the WiTwin stack. It defines the geometry and material contracts consumed by solver packages without imposing a common solver-specific scene lifecycle.

## Get Started

Python 3.10+ and an NVIDIA GPU are required for the main WiTwin simulation workflows.
CPython 3.10-3.14, PyTorch 2.10 or newer, and an NVIDIA GPU are supported for the main WiTwin simulation workflows.

```bash
pip install witwin
```

## Prebuilt CUDA Support

Release wheels are built for Linux x86_64 and Windows x86_64 with CUDA 12.8. Each platform wheel carries one PyTorch Stable ABI native library built against PyTorch 2.10 and reused across supported Python and PyTorch versions; CI currently verifies PyTorch 2.10-2.12. The fat binaries contain native code for compute capabilities 7.0, 7.5, 8.0, 8.6, 8.9, 9.0, 10.0, 10.1, and 12.0, plus compute 12.0 PTX for forward-compatible Blackwell execution. This includes native coverage for RTX 2080-class Turing GPUs and current data-center and RTX/RTX PRO Blackwell families.

Linux wheels target `manylinux_2_35_x86_64`. The installed NVIDIA driver must support the CUDA 12.x runtime supplied by PyTorch; the CUDA toolkit is only needed for source/JIT builds.

For full CUDA 12.8 and Blackwell support, use at least driver 570.26 on Linux or 570.65 on Windows. Pre-Blackwell systems can use NVIDIA's CUDA 12.x minor-version compatibility floor (525.60.13 on Linux or 528.33 on Windows), subject to NVIDIA's compatibility-mode feature limits.

## What It Provides

- Shared scene and geometry primitives in `witwin.core`
- Common state management for simulation-facing scene objects
- Reusable mesh, structure, and material building blocks for WiTwin solvers
- Runtime-checkable `GeometrySpec` and `MaterialSpec` contracts
- Shared analytic geometry primitives and differentiable mesh/SDF utilities
- Reusable `Material` and `Structure` values for solver-owned scenes

Each solver owns its public `Scene` implementation. Radar-specific SMPL geometry lives in `witwin.radar.geometry`; Maxwell-specific `PolySlab` geometry lives in `witwin.maxwell.geometry`.

## Related Solvers

Expand Down
4 changes: 3 additions & 1 deletion hatch_build.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import sysconfig
from pathlib import Path

from hatchling.builders.hooks.plugin.interface import BuildHookInterface
Expand All @@ -13,5 +14,6 @@ def initialize(self, version: str, build_data: dict) -> None:
prebuilt_dir = Path(self.root) / "witwin" / "core" / "geometry" / "cuda" / "prebuilt"
has_prebuilt_extension = any(prebuilt_dir.glob("witwin_core_mesh_sdf_cuda.*"))
if has_prebuilt_extension:
build_data["infer_tag"] = True
platform_tag = sysconfig.get_platform().replace("-", "_").replace(".", "_")
build_data["tag"] = f"py3-none-{platform_tag}"
build_data["pure_python"] = False
28 changes: 14 additions & 14 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,27 @@ build-backend = "hatchling.build"

[project]
name = "witwin"
version = "0.0.2"
description = "Witwin - Multi-physics simulation platform"
version = "0.3.0"
description = "Witwin shared geometry, material, and structure contracts"
readme = "README.md"
requires-python = ">=3.10"
requires-python = ">=3.10,<3.15"
license = "MIT"

dependencies = [
"numpy>=1.24",
"torch>=2.0",
"torch>=2.10",
]

[project.optional-dependencies]
maxwell = ["witwin-maxwell>=0.0.1"]
radar = ["witwin-radar>=0.0.1"]
channel = ["witwin-channel>=0.0.1"]
genesis = ["witwin-genesis>=0.0.1"]
maxwell = ["witwin-maxwell>=0.2,<0.3"]
radar = ["witwin-radar>=0.3,<0.4"]
channel = ["witwin-channel-native>=0.1,<0.2"]
genesis = ["witwin-genesis>=0.1,<0.2"]
all = [
"witwin-maxwell>=0.0.1",
"witwin-radar>=0.0.1",
"witwin-channel>=0.0.1",
"witwin-genesis>=0.0.1",
"witwin-maxwell>=0.2,<0.3",
"witwin-radar>=0.3,<0.4",
"witwin-channel-native>=0.1,<0.2",
"witwin-genesis>=0.1,<0.2",
]

[tool.hatch.build.targets.wheel]
Expand All @@ -34,8 +34,8 @@ artifacts = [
"witwin/core/geometry/cuda/**/*.cu",
"witwin/core/geometry/cuda/**/*.cuh",
"witwin/core/geometry/cuda/**/*.h",
"witwin/core/geometry/cuda/prebuilt/*.pyd",
"witwin/core/geometry/cuda/prebuilt/*.so",
"witwin/core/geometry/cuda/prebuilt/**/*.pyd",
"witwin/core/geometry/cuda/prebuilt/**/*.so",
]

[tool.hatch.build.hooks.custom]
Expand Down
6 changes: 3 additions & 3 deletions scripts/build_mesh_sdf_cuda_prebuilt.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,24 @@ def main() -> None:
parser.add_argument("--verbose", action="store_true")
args = parser.parse_args()

cuda_build = _load_cuda_build_module()
build_dir = Path(
os.environ.get(
"WITWIN_CORE_MESH_SDF_CUDA_BUILD_DIR",
Path(tempfile.gettempdir()) / "witwin_core_mesh_sdf_cuda_wheel",
Path(tempfile.gettempdir()) / "witwin_core_mesh_sdf_cuda_wheel" / "stable_abi_v1",
)
)
os.environ["WITWIN_CORE_MESH_SDF_CUDA_BUILD_DIR"] = str(build_dir)
os.environ["WITWIN_CORE_MESH_SDF_CUDA_SKIP_PREBUILT"] = "1"
_ensure_current_device_arch()

cuda_build = _load_cuda_build_module()
module = cuda_build.build_extension(verbose=args.verbose)
module_file = Path(module.__file__).resolve()

target_dir = cuda_build.prebuilt_root()
target_dir.mkdir(parents=True, exist_ok=True)
for suffix in (".pyd", ".so"):
existing = target_dir / f"witwin_core_mesh_sdf_cuda{suffix}"
existing = target_dir / f"{cuda_build.EXTENSION_NAME}{suffix}"
if existing.exists():
existing.unlink()
target = cuda_build.prebuilt_extension_path()
Expand Down
Loading
Loading