From 2489f5c25be9d92acdc84fd306f760c30a9d29ee Mon Sep 17 00:00:00 2001 From: Booyaka101 Date: Sun, 10 May 2026 14:34:06 +0800 Subject: [PATCH 1/2] fix(nodes): restore non-quantized backups locally and skip super's mmap walk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The override forwards unpatch_weights=True to comfy core's unpatch_model, which then calls self.model.to(device_to). Walking that .to() over the GGUF-quantized tensors that are still mmap-backed crashes with a Windows access violation (#444), surfacing whenever ComfyUI actually invokes unpatch_model — easiest repro is wrapping UnetLoaderGGUF in a node that returns multiple outputs, which flushes cache and triggers the unpatch path. The maintainer mitigated this in Sep 2024 (commit 6dbb4ba) by passing device_to=None to super, then reverted in 717a0e1 because that broke VRAM estimation. Reporter's proposed fix (passing unpatch_weights=False to super) avoids the crash but skips comfy core's non-quantized backup restoration — patch_weight_to_device's `if key not in self.backup` guard then never re-saves the original weight, and the next run's temp_weight = weight.to(...) reads the still-patched value, so LoRA deltas compound across runs. Restore the non-quantized backups in the override mirroring comfy core's loop (copy_to_param / set_attr_param + backup.clear() + comfy_patched_weights cleanup), then forward unpatch_weights=False so super skips its weight-restore + model.to(device_to) block. Keeps the crash fix without regressing LoRA correctness or VRAM accounting. Verified with a stub-based reproducer (verify_unpatch.py) covering three scenarios: pre-fix crashes on simulated mmap .to() — matches #444 reporter fix avoids crash, but non-quantized weight stays patched this fix avoids crash AND restores non-quantized backup correctly Closes #444 Co-Authored-By: Claude Opus 4.7 (1M context) --- nodes.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/nodes.py b/nodes.py index 4683514..33e0897 100644 --- a/nodes.py +++ b/nodes.py @@ -74,8 +74,29 @@ def unpatch_model(self, device_to=None, unpatch_weights=True): patches = getattr(p, "patches", []) if len(patches) > 0: p.patches = [] - # TODO: Find another way to not unload after patches - return super().unpatch_model(device_to=device_to, unpatch_weights=unpatch_weights) + # Restore non-quantized backups locally so we can short-circuit + # super()'s weight-unpatch block — its self.model.to(device_to) + # walk crashes with a Windows access violation on the still-mmap'd + # quantized tensors (#444). The maintainer's previous mitigation + # (Sep 2024 commit 6dbb4ba, reverted in 717a0e1) sidestepped the + # walk via device_to=None but skipped backup restoration, breaking + # LoRA correctness on subsequent runs (temp_weight in + # patch_weight_to_device reads the still-patched current weight). + # Doing the restore ourselves keeps non-quantized weights + # round-tripping correctly. + keys = list(self.backup.keys()) + for k in keys: + bk = self.backup[k] + if bk.inplace_update: + comfy.utils.copy_to_param(self.model, k, bk.weight) + else: + comfy.utils.set_attr_param(self.model, k, bk.weight) + self.model.current_weight_patches_uuid = None + self.backup.clear() + for m in self.model.modules(): + if hasattr(m, "comfy_patched_weights"): + del m.comfy_patched_weights + return super().unpatch_model(device_to=device_to, unpatch_weights=False) def pin_weight_to_device(self, key): From 32414a120ffb5c8256044e29ab33cbd007484845 Mon Sep 17 00:00:00 2001 From: Booyaka101 Date: Thu, 28 May 2026 01:24:23 +0800 Subject: [PATCH 2/2] nodes: also zero memory counters and run hook/pin cleanup in unpatch override The previous override forwarded unpatch_weights=False to super to dodge the self.model.to(device_to) walk that faults on mmap'd quantized tensors, but that also skipped the model_loaded_weight_memory / model_offload_buffer_memory reset super does in the same block. partially_load checks that counter immediately after calling unpatch_model and short-circuits self.load() if it is nonzero, so subsequent LoRA strength changes left .patches cleared without re-attaching anything (no LoRA applied at all on re-runs). Inline the full unpatch_weights body from base ModelPatcher minus that one device walk: hooks/pin/lowvram cleanup, backup restore, uuid reset, memory counters, comfy_patched_weights deletion. Upstream tracked at Comfy-Org/ComfyUI#14142. --- nodes.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/nodes.py b/nodes.py index 33e0897..225097a 100644 --- a/nodes.py +++ b/nodes.py @@ -67,25 +67,25 @@ def patch_weight_to_device(self, key, device_to=None, inplace_update=False): comfy.utils.set_attr_param(self.model, key, out_weight) def unpatch_model(self, device_to=None, unpatch_weights=True): + self.eject_model() if unpatch_weights: for p in self.model.parameters(): if is_torch_compatible(p): continue - patches = getattr(p, "patches", []) - if len(patches) > 0: + if len(getattr(p, "patches", [])) > 0: p.patches = [] - # Restore non-quantized backups locally so we can short-circuit - # super()'s weight-unpatch block — its self.model.to(device_to) - # walk crashes with a Windows access violation on the still-mmap'd - # quantized tensors (#444). The maintainer's previous mitigation - # (Sep 2024 commit 6dbb4ba, reverted in 717a0e1) sidestepped the - # walk via device_to=None but skipped backup restoration, breaking - # LoRA correctness on subsequent runs (temp_weight in - # patch_weight_to_device reads the still-patched current weight). - # Doing the restore ourselves keeps non-quantized weights - # round-tripping correctly. - keys = list(self.backup.keys()) - for k in keys: + # Mirror of base unpatch_model's unpatch_weights block, skipping the + # self.model.to(device_to) walk that faults on mmap'd quantized + # tensors (#444). Tracking upstream at Comfy-Org/ComfyUI#14142. + self.unpatch_hooks() + self.unpin_all_weights() + if self.model.model_lowvram: + for m in self.model.modules(): + comfy.model_patcher.move_weight_functions(m, device_to) + comfy.model_patcher.wipe_lowvram_weight(m) + self.model.model_lowvram = False + self.model.lowvram_patch_counter = 0 + for k in list(self.backup.keys()): bk = self.backup[k] if bk.inplace_update: comfy.utils.copy_to_param(self.model, k, bk.weight) @@ -93,6 +93,10 @@ def unpatch_model(self, device_to=None, unpatch_weights=True): comfy.utils.set_attr_param(self.model, k, bk.weight) self.model.current_weight_patches_uuid = None self.backup.clear() + if device_to is not None: + self.model.device = device_to + self.model.model_loaded_weight_memory = 0 + self.model.model_offload_buffer_memory = 0 for m in self.model.modules(): if hasattr(m, "comfy_patched_weights"): del m.comfy_patched_weights