Reverb commited on
Commit
851bece
·
1 Parent(s): aead907

Fix nvdiffrast SM mismatch: add +PTX arch + GL context fallback

Browse files

cudaErrorNoKernelImageForDevice (209) means the compiled kernels don't
match the actual GPU (likely Blackwell SM 10.0 on ZeroGPU's CUDA 13.0
upgrade). Fix:
- TORCH_CUDA_ARCH_LIST="8.0;8.9;9.0+PTX": +PTX lets CUDA 13.0 runtime
JIT-compile for any newer arch (SM 10.0) at first use
- Marker file (/tmp/nvdiffrast_arch.txt) avoids redundant rebuilds on
warm container restarts; force-reinstalls when arch string changes
- All four bake stages fall back to RasterizeGLContext if CUDA context
creation fails

app.py CHANGED
@@ -53,78 +53,87 @@ import gradio as gr
53
  import spaces # HF ZeroGPU
54
 
55
 
 
 
 
 
 
 
 
56
  def _install_nvdiffrast() -> None:
57
  """Install nvdiffrast from source at startup.
58
 
59
  Must run here (not requirements.txt) because pip's build isolation hides
60
  torch from nvdiffrast's build system.
61
 
62
- Problem: torch 2.9.1 is compiled with CUDA 12.8, but the ZeroGPU machine
63
- has CUDA 13.0 toolkit. PyTorch's cpp_extension._check_cuda_version compares
64
- `nvcc --version` against `torch.version.cuda` and raises RuntimeError on
65
- any mismatch.
66
 
67
- Fix: put a shim `nvcc` earlier on PATH that reports torch's CUDA version
68
- for `--version` but delegates all real compilation to the actual nvcc 13.0.
69
- CUDA 13.0 is backward-compatible and compiles CUDA 12.x code without issues.
70
- """
71
- try:
72
- import nvdiffrast # noqa: F401
73
- return
74
- except (ImportError, OSError):
75
- pass
76
 
 
 
 
77
  import os
78
  import shutil
79
  import tempfile
80
  import torch
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  torch_cuda = torch.version.cuda or "12.8"
83
  print(f"nvdiffrast: torch.version.cuda={torch_cuda}", flush=True)
84
 
85
- # Locate real nvcc — check CUDA_HOME first, then common ZeroGPU paths.
86
  real_nvcc: str | None = None
87
- cuda_candidates = [
 
88
  os.environ.get("CUDA_HOME", ""),
89
  "/cuda-image/usr/local/cuda-13.0",
90
  "/cuda-image/usr/local/cuda-12.8",
91
  "/cuda-image/usr/local/cuda",
92
  "/usr/local/cuda-13.0",
93
- "/usr/local/cuda-12.8",
94
  "/usr/local/cuda",
95
- ]
96
- real_cuda_home: str | None = None
97
- for candidate in cuda_candidates:
98
  if candidate and os.path.isfile(os.path.join(candidate, "bin", "nvcc")):
99
  real_cuda_home = candidate
100
  real_nvcc = os.path.join(candidate, "bin", "nvcc")
101
  break
102
  if real_nvcc is None:
103
  real_nvcc = shutil.which("nvcc")
104
-
105
  print(f"nvdiffrast: real_nvcc={real_nvcc}", flush=True)
106
 
107
  env = os.environ.copy()
108
 
109
  if real_nvcc:
110
- # Build a fake CUDA home: a shim nvcc that reports torch's CUDA version
111
- # on --version (to pass PyTorch's _check_cuda_version) but delegates all
112
- # real compilation to the actual nvcc. CUDA 13.0 is backward-compatible
113
- # and compiles CUDA 12.x code without issues.
114
  fake_cuda = Path(tempfile.mkdtemp(prefix="cuda_shim_"))
115
- fake_bin = fake_cuda / "bin"
116
- fake_bin.mkdir()
117
  if real_cuda_home:
118
  for sub in ("include", "lib", "lib64"):
119
  src = Path(real_cuda_home) / sub
120
  if src.exists():
121
  (fake_cuda / sub).symlink_to(src)
122
- shim = fake_bin / "nvcc"
123
  shim.write_text(
124
  "#!/bin/sh\n"
125
  'case "$1" in\n'
126
  ' --version)\n'
127
- f' echo "nvcc: NVIDIA (R) Cuda compiler driver"\n'
128
  f' echo "Cuda compilation tools, release {torch_cuda}, V{torch_cuda}.0"\n'
129
  ' ;;\n'
130
  ' *)\n'
@@ -135,23 +144,17 @@ def _install_nvdiffrast() -> None:
135
  shim.chmod(0o755)
136
  env["CUDA_HOME"] = str(fake_cuda)
137
  env["CUDA_PATH"] = str(fake_cuda)
138
- print(f"nvdiffrast: shim active — CUDA_HOME={fake_cuda}", flush=True)
139
- else:
140
- print("nvdiffrast: nvcc not found — build will likely fail", flush=True)
141
 
142
- # At startup there is no GPU allocated, so PyTorch cannot auto-detect
143
- # compute capabilities — _get_cuda_arch_flags returns an empty list and
144
- # crashes with IndexError. Explicitly target A100 (8.0) and H100/H200 (9.0).
145
- env.setdefault("TORCH_CUDA_ARCH_LIST", "8.0;9.0")
146
 
147
- print("Building nvdiffrast from source (~2 min)...", flush=True)
148
  result = subprocess.run(
149
  [sys.executable, "-m", "pip", "install", "--no-build-isolation",
150
  "git+https://github.com/NVlabs/nvdiffrast.git"],
151
- env=env,
152
- check=False,
153
- capture_output=True,
154
- text=True,
155
  )
156
  print(result.stdout[-3000:] if result.stdout else "", flush=True)
157
  if result.returncode != 0:
@@ -159,6 +162,7 @@ def _install_nvdiffrast() -> None:
159
  print(result.stderr[-2000:] if result.stderr else "", flush=True)
160
  else:
161
  print("nvdiffrast build succeeded.", flush=True)
 
162
 
163
 
164
  _install_nvdiffrast()
 
53
  import spaces # HF ZeroGPU
54
 
55
 
56
+ # GPU arch list used to compile nvdiffrast.
57
+ # +PTX on the last entry generates portable PTX code that the CUDA runtime
58
+ # JIT-compiles for any newer GPU (e.g. Blackwell SM 10.0) at first use.
59
+ _NVDR_ARCH = "8.0;8.9;9.0+PTX"
60
+ _NVDR_MARKER = Path("/tmp/nvdiffrast_arch.txt")
61
+
62
+
63
  def _install_nvdiffrast() -> None:
64
  """Install nvdiffrast from source at startup.
65
 
66
  Must run here (not requirements.txt) because pip's build isolation hides
67
  torch from nvdiffrast's build system.
68
 
69
+ Shim strategy: ZeroGPU machine has CUDA 13.0 toolkit but torch 2.9.1 was
70
+ compiled with CUDA 12.8. PyTorch's _check_cuda_version raises on mismatch.
71
+ We put a shim nvcc on PATH that reports 12.8 for --version but delegates
72
+ real compilation to the actual CUDA 13.0 nvcc.
73
 
74
+ Arch strategy: compile native kernels for SM 8.0 (A100) and 8.9 (L4/L40),
75
+ plus PTX for SM 9.0 (H100/H200). The +PTX entry lets the CUDA 13.0 runtime
76
+ JIT-compile for any newer architecture (e.g. SM 10.0 Blackwell) at first use.
 
 
 
 
 
 
77
 
78
+ Marker file: /tmp/nvdiffrast_arch.txt stores the arch string used. If it
79
+ matches _NVDR_ARCH, skip rebuild on warm container restarts.
80
+ """
81
  import os
82
  import shutil
83
  import tempfile
84
  import torch
85
 
86
+ # Check if already installed with the current arch — skip rebuild if so.
87
+ try:
88
+ import nvdiffrast # noqa: F401
89
+ if _NVDR_MARKER.exists() and _NVDR_MARKER.read_text().strip() == _NVDR_ARCH:
90
+ print("nvdiffrast: already installed with correct arch.", flush=True)
91
+ return
92
+ print("nvdiffrast: arch mismatch or missing marker — forcing reinstall.", flush=True)
93
+ subprocess.run(
94
+ [sys.executable, "-m", "pip", "uninstall", "nvdiffrast", "-y"],
95
+ check=False, capture_output=True,
96
+ )
97
+ except (ImportError, OSError):
98
+ pass
99
+
100
  torch_cuda = torch.version.cuda or "12.8"
101
  print(f"nvdiffrast: torch.version.cuda={torch_cuda}", flush=True)
102
 
103
+ # Locate real nvcc.
104
  real_nvcc: str | None = None
105
+ real_cuda_home: str | None = None
106
+ for candidate in [
107
  os.environ.get("CUDA_HOME", ""),
108
  "/cuda-image/usr/local/cuda-13.0",
109
  "/cuda-image/usr/local/cuda-12.8",
110
  "/cuda-image/usr/local/cuda",
111
  "/usr/local/cuda-13.0",
 
112
  "/usr/local/cuda",
113
+ ]:
 
 
114
  if candidate and os.path.isfile(os.path.join(candidate, "bin", "nvcc")):
115
  real_cuda_home = candidate
116
  real_nvcc = os.path.join(candidate, "bin", "nvcc")
117
  break
118
  if real_nvcc is None:
119
  real_nvcc = shutil.which("nvcc")
 
120
  print(f"nvdiffrast: real_nvcc={real_nvcc}", flush=True)
121
 
122
  env = os.environ.copy()
123
 
124
  if real_nvcc:
 
 
 
 
125
  fake_cuda = Path(tempfile.mkdtemp(prefix="cuda_shim_"))
126
+ (fake_cuda / "bin").mkdir()
 
127
  if real_cuda_home:
128
  for sub in ("include", "lib", "lib64"):
129
  src = Path(real_cuda_home) / sub
130
  if src.exists():
131
  (fake_cuda / sub).symlink_to(src)
132
+ shim = fake_cuda / "bin" / "nvcc"
133
  shim.write_text(
134
  "#!/bin/sh\n"
135
  'case "$1" in\n'
136
  ' --version)\n'
 
137
  f' echo "Cuda compilation tools, release {torch_cuda}, V{torch_cuda}.0"\n'
138
  ' ;;\n'
139
  ' *)\n'
 
144
  shim.chmod(0o755)
145
  env["CUDA_HOME"] = str(fake_cuda)
146
  env["CUDA_PATH"] = str(fake_cuda)
147
+ print(f"nvdiffrast: shim CUDA_HOME={fake_cuda}", flush=True)
 
 
148
 
149
+ # PTX in the arch list lets the runtime JIT-compile for any newer GPU
150
+ # (e.g. Blackwell SM 10.0) that lacks a pre-compiled cubin.
151
+ env["TORCH_CUDA_ARCH_LIST"] = _NVDR_ARCH
 
152
 
153
+ print(f"Building nvdiffrast (arch={_NVDR_ARCH}, ~2 min)...", flush=True)
154
  result = subprocess.run(
155
  [sys.executable, "-m", "pip", "install", "--no-build-isolation",
156
  "git+https://github.com/NVlabs/nvdiffrast.git"],
157
+ env=env, check=False, capture_output=True, text=True,
 
 
 
158
  )
159
  print(result.stdout[-3000:] if result.stdout else "", flush=True)
160
  if result.returncode != 0:
 
162
  print(result.stderr[-2000:] if result.stderr else "", flush=True)
163
  else:
164
  print("nvdiffrast build succeeded.", flush=True)
165
+ _NVDR_MARKER.write_text(_NVDR_ARCH)
166
 
167
 
168
  _install_nvdiffrast()
src/stages/stage2_bake_albedo.py CHANGED
@@ -66,7 +66,10 @@ def bake_albedo(
66
  from PIL import Image
67
 
68
  device = torch.device("cuda")
69
- ctx = dr.RasterizeCudaContext()
 
 
 
70
 
71
  hi = trimesh.load(str(high_poly_path), force="mesh")
72
  lo = trimesh.load(str(unwrapped_glb_path), force="mesh")
@@ -143,7 +146,10 @@ def bake_material(
143
  from PIL import Image
144
 
145
  device = torch.device("cuda")
146
- ctx = dr.RasterizeCudaContext()
 
 
 
147
 
148
  hi = trimesh.load(str(high_poly_path), force="mesh")
149
  lo = trimesh.load(str(unwrapped_glb_path), force="mesh")
 
66
  from PIL import Image
67
 
68
  device = torch.device("cuda")
69
+ try:
70
+ ctx = dr.RasterizeCudaContext()
71
+ except Exception:
72
+ ctx = dr.RasterizeGLContext()
73
 
74
  hi = trimesh.load(str(high_poly_path), force="mesh")
75
  lo = trimesh.load(str(unwrapped_glb_path), force="mesh")
 
146
  from PIL import Image
147
 
148
  device = torch.device("cuda")
149
+ try:
150
+ ctx = dr.RasterizeCudaContext()
151
+ except Exception:
152
+ ctx = dr.RasterizeGLContext()
153
 
154
  hi = trimesh.load(str(high_poly_path), force="mesh")
155
  lo = trimesh.load(str(unwrapped_glb_path), force="mesh")
src/stages/stage2_bake_ao.py CHANGED
@@ -81,7 +81,10 @@ def bake_ao(
81
  max_dist = 0.5 # AO search radius (in mesh units)
82
 
83
  device = torch.device("cuda")
84
- ctx = dr.RasterizeCudaContext()
 
 
 
85
 
86
  # Use the unwrapped mesh for both source positions and AO scene
87
  scene_mesh = trimesh.load(str(mesh_path), force="mesh")
 
81
  max_dist = 0.5 # AO search radius (in mesh units)
82
 
83
  device = torch.device("cuda")
84
+ try:
85
+ ctx = dr.RasterizeCudaContext()
86
+ except Exception:
87
+ ctx = dr.RasterizeGLContext()
88
 
89
  # Use the unwrapped mesh for both source positions and AO scene
90
  scene_mesh = trimesh.load(str(mesh_path), force="mesh")
src/stages/stage2_bake_normal.py CHANGED
@@ -94,7 +94,10 @@ def bake_normal_map(
94
  from PIL import Image
95
 
96
  device = torch.device("cuda")
97
- ctx = dr.RasterizeCudaContext()
 
 
 
98
 
99
  # ---- Load meshes -------------------------------------------------------
100
  hi = trimesh.load(str(high_poly_path), force="mesh")
 
94
  from PIL import Image
95
 
96
  device = torch.device("cuda")
97
+ try:
98
+ ctx = dr.RasterizeCudaContext()
99
+ except Exception:
100
+ ctx = dr.RasterizeGLContext()
101
 
102
  # ---- Load meshes -------------------------------------------------------
103
  hi = trimesh.load(str(high_poly_path), force="mesh")