multimodalart HF Staff commited on
Commit
7dfaaee
·
verified ·
1 Parent(s): a5c56a7

Use prebuilt diff-gaussian-rasterization wheel (splatting_cuda decoder); drop gsplat JIT

Browse files
Files changed (5) hide show
  1. README.md +7 -3
  2. app.py +1 -69
  3. codecsplat_pipeline.py +12 -10
  4. requirements.txt +1 -2
  5. src/model/decoder/__init__.py +16 -9
README.md CHANGED
@@ -30,13 +30,17 @@ How the Space works:
30
  runs the **complete bitstream path** from `ModelWrapper.test_step`: encode context →
31
  compress to a real bitstream at the chosen λ → decompress → predict Gaussians, using bf16
32
  autocast like `trainer.precision=bf16-mixed`.
33
- 4. The authors' gsplat decoder (antialiased, eps2d=0.1) renders held-out views (PSNR is reported
 
34
  against the real photos) and a fly-through video. The decoded Gaussians are shown in an
35
  interactive `gr.Model3D` splat viewer, and the `.bin` bitstream is available to download.
36
 
37
  Notes:
38
- * gsplat's CUDA kernels are JIT-compiled at startup against the CUDA toolkit in the ZeroGPU
39
- image, following `hugging-apps/resplat-recurrent-gs`.
 
 
 
40
  * The viewer gets a compact copy of the Gaussians: DC colour only, near-transparent splats
41
  pruned, re-centred so the scene is framed.
42
 
 
30
  runs the **complete bitstream path** from `ModelWrapper.test_step`: encode context →
31
  compress to a real bitstream at the chosen λ → decompress → predict Gaussians, using bf16
32
  autocast like `trainer.precision=bf16-mixed`.
33
+ 4. The authors' `splatting_cuda` decoder (Inria `diff-gaussian-rasterization`, prebuilt sm_120
34
+ wheel) renders held-out views (PSNR is reported
35
  against the real photos) and a fly-through video. The decoded Gaussians are shown in an
36
  interactive `gr.Model3D` splat viewer, and the `.bin` bitstream is available to download.
37
 
38
  Notes:
39
+ * The authors' evaluation scripts render with gsplat (antialiased). This Space uses the
40
+ repo's other supported decoder, `model/decoder=splatting_cuda`, because a prebuilt Blackwell
41
+ wheel exists for it. Renders can differ very slightly from the paper's numbers.
42
+ * COLMAP runs in a separate process with 16 threads. On the ~192-CPU ZeroGPU hosts COLMAP's
43
+ default thread count makes the mapper fail to register images.
44
  * The viewer gets a compact copy of the Gaussians: DC colour only, near-transparent splats
45
  pruned, re-centred so the scene is framed.
46
 
app.py CHANGED
@@ -3,15 +3,13 @@
3
  Upload overlapping photos of a static scene. The Space runs COLMAP (pycolmap, CPU) to get
4
  camera poses, then CodecSplat (authors' DL3DV 8-view 256x448 checkpoint):
5
  context views -> 2D latent features -> learned entropy coder -> real bitstream
6
- -> decoder -> 3D Gaussians -> gsplat rendering.
7
  You get an interactive splat viewer, a fly-through video, held-out novel views, the
8
  size of the compressed scene bitstream (and the bitstream itself).
9
  """
10
 
11
  from __future__ import annotations
12
 
13
- import ctypes
14
- import glob
15
  import os
16
  import sys
17
  import tempfile
@@ -25,8 +23,6 @@ sys.path.insert(0, str(APP_DIR))
25
  # threads each spin up a multi-threaded BLAS; keep BLAS single-threaded.
26
  os.environ["OPENBLAS_NUM_THREADS"] = "1"
27
  os.environ.setdefault("OMP_NUM_THREADS", "4")
28
- os.environ.setdefault("TORCH_CUDA_ARCH_LIST", "12.0")
29
- os.environ.setdefault("MAX_JOBS", str(min(32, (os.cpu_count() or 8))))
30
 
31
  import spaces # noqa: E402 (must precede any CUDA-touching import)
32
  import torch # noqa: E402
@@ -34,67 +30,6 @@ import torch # noqa: E402
34
  torch.set_float32_matmul_precision("high")
35
 
36
 
37
- # ---------------------------------------------------------------------------
38
- # 1. gsplat CUDA backend: JIT-compile once at startup against the CUDA toolkit
39
- # shipped in the ZeroGPU image (same approach as hugging-apps/resplat-recurrent-gs).
40
- # ---------------------------------------------------------------------------
41
- def _pick_cuda_home() -> str | None:
42
- torch_cuda = (torch.version.cuda or "").split(".")[0]
43
- candidates: list[str] = []
44
- for pattern in ("/cuda-image/usr/local/cuda-*", "/usr/local/cuda-*"):
45
- candidates += sorted(glob.glob(pattern), reverse=True)
46
- candidates += ["/cuda-image/usr/local/cuda", "/usr/local/cuda"]
47
- usable = [c for c in candidates if os.path.isfile(os.path.join(c, "bin", "nvcc"))]
48
- if not usable:
49
- return None
50
- for c in usable:
51
- if torch_cuda and f"cuda-{torch_cuda}" in c:
52
- return c
53
- return usable[0]
54
-
55
-
56
- os.environ.setdefault("_ORIG_LD_LIBRARY_PATH", os.environ.get("LD_LIBRARY_PATH", ""))
57
- CUDA_HOME = _pick_cuda_home()
58
- print(f"[setup] torch {torch.__version__} (cuda {torch.version.cuda}) | CUDA_HOME={CUDA_HOME}", flush=True)
59
- if CUDA_HOME:
60
- os.environ["CUDA_HOME"] = CUDA_HOME
61
- os.environ["CUDA_PATH"] = CUDA_HOME
62
- os.environ["PATH"] = os.path.join(CUDA_HOME, "bin") + os.pathsep + os.environ.get("PATH", "")
63
- _libdir = os.path.join(CUDA_HOME, "lib64")
64
- os.environ["LD_LIBRARY_PATH"] = _libdir + os.pathsep + os.environ.get("LD_LIBRARY_PATH", "")
65
- for _so in sorted(glob.glob(os.path.join(_libdir, "libcudart.so*")), reverse=True):
66
- try:
67
- ctypes.CDLL(_so, mode=ctypes.RTLD_GLOBAL)
68
- break
69
- except OSError:
70
- continue
71
-
72
- def _cuda_diag(stage: str) -> None:
73
- """Log whether the CUDA driver got loaded into the main (GPU-less) process."""
74
- try:
75
- maps = open("/proc/self/maps").read()
76
- libcuda = "libcuda.so" in maps
77
- except Exception:
78
- libcuda = "?"
79
- print(f"[diag] {stage}: libcuda mapped={libcuda} "
80
- f"torch.cuda.is_initialized={torch.cuda.is_initialized()}", flush=True)
81
-
82
-
83
- _cuda_diag("after torch import")
84
- import torch.utils.cpp_extension as _cppext # noqa: E402
85
-
86
- _cppext._check_cuda_version = lambda *a, **k: None
87
-
88
- print("[setup] compiling gsplat CUDA kernels (one-off, several minutes)...", flush=True)
89
- _t0 = time.time()
90
- from gsplat.cuda._backend import _C as _GSPLAT_C # noqa: E402
91
-
92
- if _GSPLAT_C is None:
93
- raise RuntimeError(f"gsplat CUDA backend unavailable (CUDA_HOME={CUDA_HOME}).")
94
- print(f"[setup] gsplat backend ready in {time.time() - _t0:.1f}s", flush=True)
95
- _cuda_diag("after gsplat build")
96
-
97
-
98
  # ---------------------------------------------------------------------------
99
  # 2. Model
100
  # ---------------------------------------------------------------------------
@@ -106,7 +41,6 @@ from PIL import Image # noqa: E402
106
 
107
  import codecsplat_pipeline as cp # noqa: E402
108
 
109
- _cuda_diag("after pipeline import")
110
  from colmap_sfm import IMAGE_EXT, run_sfm_subprocess # noqa: E402
111
 
112
  MODEL_REPO = "pengpeng-yu/CodecSplat"
@@ -121,7 +55,6 @@ print("[setup] building CodecSplat...", flush=True)
121
  ENCODER = cp.build_encoder(ckpt_path).to("cuda")
122
  DECODER = cp.build_decoder().to("cuda")
123
  print("[setup] model ready", flush=True)
124
- _cuda_diag("after model build")
125
 
126
 
127
  def _u8(t: torch.Tensor) -> np.ndarray:
@@ -237,7 +170,6 @@ def reconstruct(images, lmb: float = 1024.0, matcher: str = "exhaustive",
237
  raise gr.Error(str(exc)) from exc
238
  t_sfm = time.time() - t_start
239
  img_paths, c2w, K = _scene_arrays(sfm["frames"])
240
- _cuda_diag("before GPU call")
241
 
242
  progress(0.55, desc="CodecSplat: encode -> bitstream -> decode -> render")
243
  r = codecsplat_gpu(img_paths, c2w, K, float(lmb))
 
3
  Upload overlapping photos of a static scene. The Space runs COLMAP (pycolmap, CPU) to get
4
  camera poses, then CodecSplat (authors' DL3DV 8-view 256x448 checkpoint):
5
  context views -> 2D latent features -> learned entropy coder -> real bitstream
6
+ -> decoder -> 3D Gaussians -> Gaussian-splatting rasterizer.
7
  You get an interactive splat viewer, a fly-through video, held-out novel views, the
8
  size of the compressed scene bitstream (and the bitstream itself).
9
  """
10
 
11
  from __future__ import annotations
12
 
 
 
13
  import os
14
  import sys
15
  import tempfile
 
23
  # threads each spin up a multi-threaded BLAS; keep BLAS single-threaded.
24
  os.environ["OPENBLAS_NUM_THREADS"] = "1"
25
  os.environ.setdefault("OMP_NUM_THREADS", "4")
 
 
26
 
27
  import spaces # noqa: E402 (must precede any CUDA-touching import)
28
  import torch # noqa: E402
 
30
  torch.set_float32_matmul_precision("high")
31
 
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  # ---------------------------------------------------------------------------
34
  # 2. Model
35
  # ---------------------------------------------------------------------------
 
41
 
42
  import codecsplat_pipeline as cp # noqa: E402
43
 
 
44
  from colmap_sfm import IMAGE_EXT, run_sfm_subprocess # noqa: E402
45
 
46
  MODEL_REPO = "pengpeng-yu/CodecSplat"
 
55
  ENCODER = cp.build_encoder(ckpt_path).to("cuda")
56
  DECODER = cp.build_decoder().to("cuda")
57
  print("[setup] model ready", flush=True)
 
58
 
59
 
60
  def _u8(t: torch.Tensor) -> np.ndarray:
 
170
  raise gr.Error(str(exc)) from exc
171
  t_sfm = time.time() - t_start
172
  img_paths, c2w, K = _scene_arrays(sfm["frames"])
 
173
 
174
  progress(0.55, desc="CodecSplat: encode -> bitstream -> decode -> render")
175
  r = codecsplat_gpu(img_paths, c2w, K, float(lmb))
codecsplat_pipeline.py CHANGED
@@ -76,19 +76,21 @@ def build_encoder(checkpoint_path: str):
76
 
77
 
78
  def build_decoder():
79
- from src.model.decoder.decoder_splatting_gsplat import (
80
- DecoderSplattingGsplat,
81
- DecoderSplattingGsplatCfg,
 
 
 
 
 
 
82
  )
83
 
84
- dec_cfg = OmegaConf.load(APP_DIR / "config/model/decoder/splatting_gsplat.yaml")
85
- dec_cfg = OmegaConf.merge(dec_cfg, DECODER_OVERRIDES)
86
- dec_cfg = from_dict(
87
- DecoderSplattingGsplatCfg,
88
- OmegaConf.to_container(dec_cfg, resolve=True),
89
- config=DaciteConfig(strict=False),
90
  )
91
- decoder = DecoderSplattingGsplat(dec_cfg, SimpleNamespace(background_color=[0.0, 0.0, 0.0]))
92
  return decoder.eval()
93
 
94
 
 
76
 
77
 
78
  def build_decoder():
79
+ """The authors' `model/decoder=splatting_cuda` decoder (Inria diff-gaussian-rasterization).
80
+
81
+ The evaluation scripts use the gsplat decoder; on ZeroGPU we use the repo's other
82
+ supported decoder because a prebuilt sm_120 wheel exists for it (gsplat would have to
83
+ be JIT-compiled against a CUDA toolkit at startup).
84
+ """
85
+ from src.model.decoder.decoder_splatting_cuda import (
86
+ DecoderSplattingCUDA,
87
+ DecoderSplattingCUDACfg,
88
  )
89
 
90
+ decoder = DecoderSplattingCUDA(
91
+ DecoderSplattingCUDACfg(name="splatting_cuda"),
92
+ SimpleNamespace(background_color=[0.0, 0.0, 0.0]),
 
 
 
93
  )
 
94
  return decoder.eval()
95
 
96
 
requirements.txt CHANGED
@@ -1,7 +1,6 @@
1
  torch==2.11.0
2
  torchvision==0.26.0
3
- gsplat==1.5.3
4
- ninja
5
  compressai==1.2.8
6
  numpy<2
7
  pycolmap==4.1.1
 
1
  torch==2.11.0
2
  torchvision==0.26.0
3
+ https://huggingface.co/datasets/multimodalart/zerogpu-blackwell-wheels/resolve/main/wheels/pt211-cu130-cp312/diff_gaussian_rasterization-0.0.0-cp312-cp312-linux_x86_64.whl
 
4
  compressai==1.2.8
5
  numpy<2
6
  pycolmap==4.1.1
src/model/decoder/__init__.py CHANGED
@@ -16,15 +16,22 @@ except ImportError as exc:
16
  else:
17
  CUDA_SPLATTING_IMPORT_ERROR = None
18
 
19
- from .decoder_splatting_gsplat import (
20
- DecoderSplattingGsplat,
21
- DecoderSplattingGsplatCfg,
22
- )
23
-
24
- DECODERS = {
25
- "splatting_gsplat": DecoderSplattingGsplat,
26
- "gsplat": DecoderSplattingGsplat,
27
- }
 
 
 
 
 
 
 
28
 
29
  DecoderCfg = DecoderSplattingGsplatCfg | DecoderSplattingCUDACfg
30
 
 
16
  else:
17
  CUDA_SPLATTING_IMPORT_ERROR = None
18
 
19
+ try: # gsplat is optional in this Space (we render with diff-gaussian-rasterization)
20
+ from .decoder_splatting_gsplat import (
21
+ DecoderSplattingGsplat,
22
+ DecoderSplattingGsplatCfg,
23
+ )
24
+ except ImportError:
25
+ DecoderSplattingGsplat = None
26
+
27
+ @dataclass
28
+ class DecoderSplattingGsplatCfg:
29
+ name: Literal["splatting_gsplat"]
30
+
31
+ DECODERS = {}
32
+ if DecoderSplattingGsplat is not None:
33
+ DECODERS["splatting_gsplat"] = DecoderSplattingGsplat
34
+ DECODERS["gsplat"] = DecoderSplattingGsplat
35
 
36
  DecoderCfg = DecoderSplattingGsplatCfg | DecoderSplattingCUDACfg
37