fix(spaces): Hdri torch.load after ConvNeXt; default off CPU preload with spaces
Browse files- hdri_encoder: torch.load(..., weights_only=False, map_location=cpu) so Stateless
GPU WeightsUnpickler does not run after torchvision may have touched CUDA
- app: default NEAR_MODEL_CPU_PRELOAD_AT_START=0 when spaces is installed
- test: assert Hdri load_weights uses full unpickle on CPU
Made-with: Cursor
app.py
CHANGED
|
@@ -253,10 +253,18 @@ def _truthy_env(name: str, default: str) -> bool:
|
|
| 253 |
return v in ("1", "true", "yes", "on")
|
| 254 |
|
| 255 |
|
| 256 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
print(
|
| 258 |
f"[NeAR] NEAR_MODEL_CPU_PRELOAD_AT_START={'1' if _CPU_PRELOAD_AT_START else '0'} "
|
| 259 |
-
"(
|
| 260 |
flush=True,
|
| 261 |
)
|
| 262 |
|
|
|
|
| 253 |
return v in ("1", "true", "yes", "on")
|
| 254 |
|
| 255 |
|
| 256 |
+
# Background CPU preload runs in the Gradio host process. HF Stateless / ZeroGPU forbids
|
| 257 |
+
# CUDA init there; torchvision + torch.load (weights_only) interactions can still fail
|
| 258 |
+
# during NeAR build. Default to off when `spaces` is present (typical Space deploy); use
|
| 259 |
+
# NEAR_MODEL_CPU_PRELOAD_AT_START=1 to force preload locally or on dedicated GPU VMs.
|
| 260 |
+
_CPU_PRELOAD_DEFAULT = "0" if spaces is not None else "1"
|
| 261 |
+
_CPU_PRELOAD_AT_START = _truthy_env(
|
| 262 |
+
"NEAR_MODEL_CPU_PRELOAD_AT_START",
|
| 263 |
+
_CPU_PRELOAD_DEFAULT,
|
| 264 |
+
)
|
| 265 |
print(
|
| 266 |
f"[NeAR] NEAR_MODEL_CPU_PRELOAD_AT_START={'1' if _CPU_PRELOAD_AT_START else '0'} "
|
| 267 |
+
f"(default {_CPU_PRELOAD_DEFAULT!r} when spaces={'set' if spaces is not None else 'absent'}).",
|
| 268 |
flush=True,
|
| 269 |
)
|
| 270 |
|
tests/test_hdri_encoder_torch_load.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import unittest
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
HDRI_PATH = Path(__file__).resolve().parents[1] / "trellis" / "models" / "structured_latent_vae" / "hdri_encoder.py"
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class HdriEncoderTorchLoadTests(unittest.TestCase):
|
| 11 |
+
def test_load_weights_uses_full_unpickle_on_cpu(self) -> None:
|
| 12 |
+
text = HDRI_PATH.read_text(encoding="utf-8")
|
| 13 |
+
self.assertIn("weights_only=False", text)
|
| 14 |
+
self.assertIn("torch.device(\"cpu\")", text)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
if __name__ == "__main__":
|
| 18 |
+
unittest.main()
|
trellis/models/structured_latent_vae/hdri_encoder.py
CHANGED
|
@@ -374,7 +374,15 @@ class Hdri_Encoder(nn.Module):
|
|
| 374 |
|
| 375 |
def load_weights(self, pretrained_path):
|
| 376 |
if pretrained_path is not None:
|
| 377 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 378 |
self.load_state_dict(checkpoint)
|
| 379 |
|
| 380 |
def forward(self, context):
|
|
|
|
| 374 |
|
| 375 |
def load_weights(self, pretrained_path):
|
| 376 |
if pretrained_path is not None:
|
| 377 |
+
# ConvNeXt ImageNet weights are loaded above; that can initialize CUDA in the
|
| 378 |
+
# process. On Hugging Face Stateless / ZeroGPU, the next torch.load with
|
| 379 |
+
# weights_only=True (PyTorch 2.6+ default) then fails inside WeightsUnpickler.
|
| 380 |
+
# This checkpoint is from the NeAR bundle (trusted); full unpickle on CPU only.
|
| 381 |
+
checkpoint = torch.load(
|
| 382 |
+
pretrained_path,
|
| 383 |
+
map_location=torch.device("cpu"),
|
| 384 |
+
weights_only=False,
|
| 385 |
+
)
|
| 386 |
self.load_state_dict(checkpoint)
|
| 387 |
|
| 388 |
def forward(self, context):
|