Files

171 lines
5.6 KiB
Python

"""LoadSharpModel node for ComfyUI-Sharp."""
import os
import logging
import torch
from huggingface_hub import hf_hub_download
log = logging.getLogger("sharp")
# Try to get ComfyUI models directory
try:
import folder_paths
MODELS_DIR = os.path.join(folder_paths.models_dir, "sharp")
except ImportError:
folder_paths = None
MODELS_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "models", "sharp")
SHARP_REPO_ID = "apple/Sharp"
SHARP_FILENAME = "sharp_2572gikvuh.pt"
WARM_READ_CHUNK_SIZE = 32 * 1024 * 1024
def _sharp_model_dirs():
if folder_paths is not None:
try:
return folder_paths.get_folder_paths("sharp")
except KeyError:
pass
return [MODELS_DIR]
def _resolve_sharp_checkpoint():
for model_dir in _sharp_model_dirs():
model_path = os.path.join(model_dir, SHARP_FILENAME)
if os.path.exists(model_path):
return model_path
return None
def _is_external_model_path(model_path):
model_drive = os.path.splitdrive(os.path.abspath(model_path))[0].upper()
local_drive = os.path.splitdrive(os.path.abspath(MODELS_DIR))[0].upper()
return bool(model_drive and model_drive != local_drive)
def _warm_file_cache(model_path):
if not _is_external_model_path(model_path):
return
file_size = os.path.getsize(model_path)
log.info(f"Warming SHARP checkpoint cache ({file_size / (1024 ** 3):.2f} GiB)")
with open(model_path, "rb") as checkpoint:
while checkpoint.read(WARM_READ_CHUNK_SIZE):
pass
class LoadSharpModel:
"""Load SHARP model and wrap with ModelPatcher for ComfyUI-native VRAM management.
The model is built once, cached by ComfyUI's execution cache, and stays in
VRAM between runs (no repeated GPU transfers).
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {},
"optional": {
"precision": (["auto", "bf16", "fp16", "fp32"], {
"default": "auto",
"tooltip": "Model precision. auto: best for your GPU (bf16 on Ampere+, fp16 on Volta/Turing, fp32 on older)."
}),
"checkpoint_path": ("STRING", {
"default": "",
"tooltip": "Path to .pt checkpoint. Leave empty to auto-download from Hugging Face."
}),
}
}
RETURN_TYPES = ("SHARP_MODEL",)
RETURN_NAMES = ("model",)
FUNCTION = "load_model"
CATEGORY = "Gaussian Splat Shot"
DESCRIPTION = "Load the SHARP model for monocular 3D Gaussian Splatting prediction."
def load_model(self, precision: str = "auto", checkpoint_path: str = ""):
"""Build model, load weights, wrap with ModelPatcher."""
import comfy.model_management
import comfy.model_patcher
import comfy.ops
import comfy.utils
from .sharp import PredictorParams, create_predictor
load_device = comfy.model_management.get_torch_device()
offload_device = comfy.model_management.unet_offload_device()
# Resolve dtype
if precision == "auto":
if comfy.model_management.should_use_bf16(load_device):
dtype = torch.bfloat16
elif comfy.model_management.should_use_fp16(load_device):
dtype = torch.float16
else:
dtype = torch.float32
elif precision == "bf16":
dtype = torch.bfloat16
elif precision == "fp16":
dtype = torch.float16
else:
dtype = torch.float32
# Select optimal operations class (enables fp8, nvfp4, CublasOps, etc.)
manual_cast_dtype = comfy.model_management.unet_manual_cast(dtype, load_device)
operations = comfy.ops.pick_operations(dtype, manual_cast_dtype)
# Resolve / download checkpoint
if checkpoint_path and os.path.exists(checkpoint_path):
model_path = checkpoint_path
else:
model_path = _resolve_sharp_checkpoint()
if model_path is None:
download_dir = _sharp_model_dirs()[0]
os.makedirs(download_dir, exist_ok=True)
model_path = hf_hub_download(
repo_id=SHARP_REPO_ID,
filename=SHARP_FILENAME,
local_dir=download_dir,
)
_warm_file_cache(model_path)
# Load state dict
log.info(f"Loading checkpoint from {model_path}")
state_dict = comfy.utils.load_torch_file(model_path)
# Build model with native operations (disable_weight_init skips random weight init)
log.info("Initializing model...")
predictor = create_predictor(
PredictorParams(),
dtype=dtype,
device=None,
operations=operations,
)
# Load weights (assign=True efficiently replaces nn.Parameter objects)
# Operations handle dtype casting at forward time — no .to(dtype) needed
predictor.load_state_dict(state_dict, assign=True)
predictor.eval()
if comfy.model_management.force_channels_last():
predictor.to(memory_format=torch.channels_last)
comfy.model_management.archive_model_dtypes(predictor)
log.info(f"Model ready ({dtype})")
# Wrap with ModelPatcher — ComfyUI manages VRAM from here
patcher = comfy.model_patcher.ModelPatcher(
predictor,
load_device=load_device,
offload_device=offload_device,
)
return (patcher,)
NODE_CLASS_MAPPINGS = {
"LoadSharpModel": LoadSharpModel,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"LoadSharpModel": "Load SHARP Model",
}