-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathpurge_vram.py
More file actions
4003 lines (3746 loc) · 264 KB
/
Copy pathpurge_vram.py
File metadata and controls
4003 lines (3746 loc) · 264 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
Purge VRAM V2 node for DistorchMemoryManager
"""
import torch
import gc
import sys
import os
import logging
import importlib
# AnyType mirrors the behavior of the original Purge VRAM node
class AnyType(str):
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
def __eq__(self, __value: object) -> bool:
return True
def __ne__(self, __value: object) -> bool:
return False
def __repr__(self):
return str(self)
any = AnyType("*")
class DisTorchPurgeVRAMV2:
"""
Compatibility clone of the original LayerUtility Purge VRAM V2 node
maintained within the Distortch Memory Manager package.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"anything": (any, {}),
"purge_cache": ("BOOLEAN", {"default": True}),
"purge_models": ("BOOLEAN", {"default": True}),
"purge_seedvr2_models": ("BOOLEAN", {"default": False, "tooltip": "Clear SeedVR2 DiT (base) and VAE models from cache"}),
"purge_qwen3vl_models": ("BOOLEAN", {"default": False, "tooltip": "Clear Qwen3-VL models from GPU memory"}),
"purge_nunchaku_models": ("BOOLEAN", {"default": False, "tooltip": "Clear Nunchaku models (FLUX/Z-Image/Qwen-Image) from GPU memory"}),
"HSWQ": ("BOOLEAN", {"default": False, "tooltip": "Purge HSWQ residual VRAM (whole HSWQ path: models, PinCache, Detailer caches)"}),
"Ollama": ("BOOLEAN", {"default": False, "tooltip": "Full purge of Ollama VRAM used by comfyui-ollama and comfyui-ollama-describer: unload every loaded model until /api/ps is empty (generate+chat keep_alive=0, ollama stop), clear CHAT_SESSIONS/saved_context, wipe saved_context files"}),
}
}
RETURN_TYPES = (any,)
RETURN_NAMES = ("any",)
FUNCTION = "purge_vram"
CATEGORY = "Distorch/Memory"
def purge_vram(self, anything, purge_cache, purge_models, purge_seedvr2_models, purge_qwen3vl_models, purge_nunchaku_models, **kwargs):
# Toggle label is "HSWQ"; accept legacy "HSWQ INT8" for old workflows.
purge_hswq_int8 = bool(kwargs.get("HSWQ", kwargs.get("HSWQ INT8", False)))
purge_ollama = bool(kwargs.get("Ollama", False))
global torch
if purge_cache:
gc.collect()
if torch.cuda.is_available():
current_device = torch.cuda.current_device()
try:
for idx in range(torch.cuda.device_count()):
torch.cuda.set_device(idx)
torch.cuda.empty_cache()
try:
torch.cuda.ipc_collect()
except Exception:
pass
finally:
torch.cuda.set_device(current_device)
if purge_models:
try:
import comfy.model_management
# Pre-cleanup: Remove models with None or non-callable real_model before calling cleanup_models()
# This prevents 'NoneType' object is not callable errors
if hasattr(comfy.model_management, "current_loaded_models"):
current_loaded_models = comfy.model_management.current_loaded_models
pre_cleaned = 0
for i in range(len(current_loaded_models) - 1, -1, -1):
loaded_model = current_loaded_models[i]
if loaded_model is not None:
try:
# Check if real_model is None or not callable
if hasattr(loaded_model, "real_model"):
real_model = loaded_model.real_model
if real_model is None:
# Remove model with None real_model
current_loaded_models.pop(i)
pre_cleaned += 1
elif not callable(real_model):
# Remove model with non-callable real_model
current_loaded_models.pop(i)
pre_cleaned += 1
else:
# Check if calling real_model() would fail
try:
if real_model() is None:
current_loaded_models.pop(i)
pre_cleaned += 1
except (TypeError, AttributeError):
# real_model is not callable or has issues
current_loaded_models.pop(i)
pre_cleaned += 1
except Exception:
# Skip problematic models
pass
if pre_cleaned > 0:
print(f"Pre-cleaned {pre_cleaned} problematic model(s) before cleanup_models()")
# Cleanup dead models
if hasattr(comfy.model_management, "cleanup_models") and callable(comfy.model_management.cleanup_models):
try:
comfy.model_management.cleanup_models()
except Exception as e:
print(f"Error in cleanup_models: {e}")
# Cleanup models GC
if hasattr(comfy.model_management, "cleanup_models_gc") and callable(comfy.model_management.cleanup_models_gc):
try:
comfy.model_management.cleanup_models_gc()
except Exception as e:
print(f"Error in cleanup_models_gc: {e}")
# Aggressive unload: MultiGPU Dynamic / NVFP4 (Krea2) often leave
# ~9GB CUDA after model_unload()==True. Soft path alone is not enough.
if hasattr(comfy.model_management, "current_loaded_models"):
current_loaded_models = comfy.model_management.current_loaded_models
unloaded_count = 0
bytes_force_killed = 0
def _force_empty_cuda_storage(t) -> int:
# NVFP4 / MultiGPU Dynamic: free leftover CUDA only.
# Never wipe CPU tensors — after model_unload() they are
# ComfyUI's reload source. Wiping to empty(0) made CLIP
# Embedding.weight non-2D (Ollama purge → CLIPTextEncode
# RuntimeError: 'weight' must be 2-D; reload logged 0.00 MB).
if t is None:
return 0
freed = 0
try:
data = getattr(t, "data", t)
if data is None:
return 0
nbytes = int(getattr(data, "nbytes", 0) or 0)
is_cuda = False
try:
is_cuda = bool(getattr(data, "is_cuda", False))
if not is_cuda:
dev = getattr(data, "device", None)
is_cuda = getattr(dev, "type", None) == "cuda"
except Exception:
pass
if not is_cuda:
return 0
dtype = getattr(data, "dtype", torch.float32)
empty = torch.empty(0, dtype=dtype, device="cpu")
if hasattr(t, "data"):
t.data = empty
freed = nbytes
except Exception:
pass
return freed
def _force_kill_nn_cuda(module) -> int:
if module is None:
return 0
freed = 0
try:
if hasattr(module, "to") and callable(module.to):
try:
module.to("cpu")
except Exception:
pass
except Exception:
pass
try:
for _n, p in list(module.named_parameters()):
freed += _force_empty_cuda_storage(p)
except Exception:
pass
try:
for _n, b in list(module.named_buffers()):
freed += _force_empty_cuda_storage(b)
except Exception:
pass
return freed
def _unwrap_nn_soft(obj):
cur = obj
for _ in range(8):
if cur is None:
return None
try:
if isinstance(cur, torch.nn.Module):
return cur
except Exception:
pass
nxt = getattr(cur, "model", None)
if nxt is None or nxt is cur:
nxt = getattr(cur, "diffusion_model", None)
if nxt is None or nxt is cur:
try:
return cur if isinstance(cur, torch.nn.Module) else None
except Exception:
return None
cur = nxt
try:
return cur if isinstance(cur, torch.nn.Module) else None
except Exception:
return None
# Mark unused, unload, kill CUDA storage, then remove from registry
for i in range(len(current_loaded_models) - 1, -1, -1):
loaded_model = current_loaded_models[i]
if loaded_model is None:
try:
current_loaded_models.pop(i)
except Exception:
pass
continue
try:
try:
loaded_model.currently_used = False
except Exception:
pass
try:
if hasattr(loaded_model, "partially_unload") and callable(loaded_model.partially_unload):
try:
loaded_model.partially_unload(None, 1e30)
except Exception:
loaded_model.partially_unload(torch.device("cpu"), 1e30)
except Exception:
pass
try:
if hasattr(loaded_model, "model_unload") and callable(loaded_model.model_unload):
loaded_model.model_unload()
unloaded_count += 1
except Exception as e:
print(f"Error unloading model: {e}")
try:
inner = getattr(loaded_model, "model", None)
nn = _unwrap_nn_soft(inner)
if nn is not None:
bytes_force_killed += _force_kill_nn_cuda(nn)
elif inner is not None:
bytes_force_killed += _force_kill_nn_cuda(_unwrap_nn_soft(inner))
except Exception:
pass
try:
current_loaded_models.pop(i)
except Exception:
pass
except Exception as e:
print(f"Error force-unloading model[{i}]: {e}")
try:
current_loaded_models.pop(i)
except Exception:
pass
if unloaded_count > 0:
print(f"Unloaded {unloaded_count} model(s)")
if bytes_force_killed > 0:
print(
f"Force-killed ~{bytes_force_killed / (1024 ** 3):.2f} GB CUDA storage "
f"from loaded models (MultiGPU/NVFP4 soft-unload residue)"
)
# Pre-cleanup again before second cleanup_models() call
if hasattr(comfy.model_management, "current_loaded_models"):
current_loaded_models = comfy.model_management.current_loaded_models
pre_cleaned_2 = 0
for i in range(len(current_loaded_models) - 1, -1, -1):
loaded_model = current_loaded_models[i]
if loaded_model is not None:
try:
if hasattr(loaded_model, "real_model"):
real_model = loaded_model.real_model
if real_model is None or not callable(real_model):
current_loaded_models.pop(i)
pre_cleaned_2 += 1
else:
try:
if real_model() is None:
current_loaded_models.pop(i)
pre_cleaned_2 += 1
except (TypeError, AttributeError):
current_loaded_models.pop(i)
pre_cleaned_2 += 1
except Exception:
pass
if pre_cleaned_2 > 0:
print(f"Pre-cleaned {pre_cleaned_2} problematic model(s) before second cleanup_models()")
# Cleanup again after unloading
if hasattr(comfy.model_management, "cleanup_models"):
try:
comfy.model_management.cleanup_models()
except Exception as e:
print(f"Error in cleanup_models: {e}")
# Hard free: unload_all + free_memory(1e30). free_memory(0) does nothing.
try:
mm = comfy.model_management
if hasattr(mm, "unload_all_models") and callable(mm.unload_all_models):
mm.unload_all_models()
print("unload_all_models() issued")
if torch.cuda.is_available() and hasattr(mm, "free_memory") and callable(mm.free_memory):
for di in range(torch.cuda.device_count()):
try:
mm.free_memory(1e30, torch.device(f"cuda:{di}"))
except Exception as e:
print(f"free_memory(cuda:{di}) warning: {e}")
print("free_memory(1e30) issued for all CUDA devices")
except Exception as e:
print(f"Hard free after purge_models warning: {e}")
# Soft empty cache (if available)
if hasattr(comfy.model_management, "soft_empty_cache") and callable(comfy.model_management.soft_empty_cache):
try:
comfy.model_management.soft_empty_cache()
except Exception as e:
print(f"Error in soft_empty_cache: {e}")
except Exception as e:
print(f"Error purging models: {e}")
# Purge SeedVR2 models if requested
if purge_seedvr2_models:
try:
# Try to import SeedVR2's GlobalModelCache
# Note: sys and os are already imported at module level
# Try multiple possible paths for SeedVR2 custom node
# Note: Paths are relative to avoid hardcoding user-specific directories
seedvr2_path = None
# Method 1: Try to import from already loaded modules (most reliable)
try:
import seedvr2_videoupscaler
if hasattr(seedvr2_videoupscaler, '__file__'):
seedvr2_path = os.path.dirname(os.path.abspath(seedvr2_videoupscaler.__file__))
except (ImportError, AttributeError):
pass
# Method 2: Relative to current file (same custom_nodes directory)
# Current file is in: ComfyUI/custom_nodes/ComfyUI-DistorchMemoryManager/__init__.py
# Target is: ComfyUI/custom_nodes/seedvr2_videoupscaler
if not seedvr2_path:
current_dir = os.path.dirname(os.path.abspath(__file__))
# Go up one level to custom_nodes directory
custom_nodes_dir = os.path.dirname(current_dir)
seedvr2_candidate = os.path.join(custom_nodes_dir, 'seedvr2_videoupscaler')
if os.path.exists(seedvr2_candidate) and os.path.isdir(seedvr2_candidate):
seedvr2_path = seedvr2_candidate
# Method 3: Search in sys.path for seedvr2_videoupscaler
if not seedvr2_path:
for path in sys.path:
# Check if path contains seedvr2_videoupscaler
if 'seedvr2_videoupscaler' in path:
# Extract the directory containing seedvr2_videoupscaler
parts = path.split(os.sep)
if 'seedvr2_videoupscaler' in parts:
idx = parts.index('seedvr2_videoupscaler')
candidate = os.sep.join(parts[:idx+1])
if os.path.exists(candidate) and os.path.isdir(candidate):
seedvr2_path = candidate
break
else:
# Check if seedvr2_videoupscaler exists as subdirectory
seedvr2_candidate = os.path.join(path, 'seedvr2_videoupscaler')
if os.path.exists(seedvr2_candidate) and os.path.isdir(seedvr2_candidate):
seedvr2_path = seedvr2_candidate
break
# Method 4: Find custom_nodes directory from current file path structure
if not seedvr2_path:
current_file = os.path.abspath(__file__)
parts = current_file.split(os.sep)
# Look for 'custom_nodes' in the path
if 'custom_nodes' in parts:
idx = parts.index('custom_nodes')
# Reconstruct path up to custom_nodes
custom_nodes_base = os.sep.join(parts[:idx+1])
seedvr2_candidate = os.path.join(custom_nodes_base, 'seedvr2_videoupscaler')
if os.path.exists(seedvr2_candidate) and os.path.isdir(seedvr2_candidate):
seedvr2_path = seedvr2_candidate
if seedvr2_path:
# Add seedvr2_path to sys.path temporarily
original_path = sys.path[:]
try:
if seedvr2_path not in sys.path:
sys.path.insert(0, seedvr2_path)
# Try importing with different methods
cache = None
import_method = None
try:
# Method 1: Direct import
from src.core.model_cache import get_global_cache
cache = get_global_cache()
import_method = "Method 1 (direct import)"
except (ImportError, ModuleNotFoundError) as e1:
try:
# Method 2: Import seedvr2_videoupscaler first
import seedvr2_videoupscaler
from seedvr2_videoupscaler.src.core.model_cache import get_global_cache
cache = get_global_cache()
import_method = "Method 2 (via seedvr2_videoupscaler)"
except (ImportError, ModuleNotFoundError, AttributeError) as e2:
# Method 3: Try to access via already loaded module
if 'seedvr2_videoupscaler' in sys.modules:
seedvr2_module = sys.modules['seedvr2_videoupscaler']
if hasattr(seedvr2_module, 'src'):
from seedvr2_videoupscaler.src.core.model_cache import get_global_cache
cache = get_global_cache()
import_method = "Method 3 (via sys.modules)"
if cache is not None:
if import_method:
print(f"SeedVR2: Cache accessed via {import_method}")
dit_cleared = 0
vae_cleared = 0
# Debug: Check cache state before clearing
dit_count_before = len(cache._dit_models) if hasattr(cache, '_dit_models') else 0
vae_count_before = len(cache._vae_models) if hasattr(cache, '_vae_models') else 0
runner_count_before = len(cache._runner_templates) if hasattr(cache, '_runner_templates') else 0
# Log SeedVR2 cache access with detailed info
print(f"SeedVR2: Checking cache (DiT: {dit_count_before}, VAE: {vae_count_before}, Runners: {runner_count_before})")
# Debug: Check if cache attributes exist and show details
if hasattr(cache, '_dit_models'):
dit_keys = list(cache._dit_models.keys()) if cache._dit_models else []
if dit_keys:
print(f"SeedVR2: DiT model node IDs: {dit_keys}")
else:
print("SeedVR2: DiT models dictionary exists but is empty")
else:
print("SeedVR2: _dit_models attribute not found in cache")
if hasattr(cache, '_vae_models'):
vae_keys = list(cache._vae_models.keys()) if cache._vae_models else []
if vae_keys:
print(f"SeedVR2: VAE model node IDs: {vae_keys}")
else:
print("SeedVR2: VAE models dictionary exists but is empty")
else:
print("SeedVR2: _vae_models attribute not found in cache")
# Clear all DiT models
if hasattr(cache, '_dit_models') and cache._dit_models:
dit_models_copy = dict(cache._dit_models)
for node_id, (model, config) in dit_models_copy.items():
try:
# Ensure config has node_id for remove_dit
if not isinstance(config, dict):
config = {}
if 'node_id' not in config:
config['node_id'] = node_id
# Use remove_dit to properly clean up
if cache.remove_dit(config, debug=None):
dit_cleared += 1
except Exception as e:
print(f"Error removing SeedVR2 DiT model {node_id}: {e}")
# Clear all VAE models
if hasattr(cache, '_vae_models') and cache._vae_models:
vae_models_copy = dict(cache._vae_models)
for node_id, (model, config) in vae_models_copy.items():
try:
# Ensure config has node_id for remove_vae
if not isinstance(config, dict):
config = {}
if 'node_id' not in config:
config['node_id'] = node_id
# Use remove_vae to properly clean up
if cache.remove_vae(config, debug=None):
vae_cleared += 1
except Exception as e:
print(f"Error removing SeedVR2 VAE model {node_id}: {e}")
# Clear runner templates
if hasattr(cache, '_runner_templates') and cache._runner_templates:
runner_count = len(cache._runner_templates)
cache._runner_templates.clear()
if runner_count > 0:
print(f"Cleared {runner_count} SeedVR2 runner template(s)")
# Report results
if dit_cleared > 0 or vae_cleared > 0:
print(f"Cleared {dit_cleared} SeedVR2 DiT model(s) and {vae_cleared} VAE model(s)")
elif dit_count_before == 0 and vae_count_before == 0 and runner_count_before == 0:
# Cache is completely empty - SeedVR2 may not have cached models yet
# This is normal if SeedVR2 is used but models aren't cached (cache_model=False)
# Or models were already cleared by SeedVR2 after processing completed
try:
import comfy.model_management
if hasattr(comfy.model_management, "current_loaded_models"):
# Check if any loaded models might be SeedVR2 models
seedvr2_model_count = 0
for loaded_model in comfy.model_management.current_loaded_models:
if loaded_model is not None and hasattr(loaded_model, "model"):
model = loaded_model.model
# Check if model name or type suggests SeedVR2
model_str = str(type(model)).lower()
if __builtins__['any'](keyword in model_str for keyword in ['seedvr', 'dit', 'video_vae']):
seedvr2_model_count += 1
if seedvr2_model_count > 0:
print(f"SeedVR2: Cache is empty, but found {seedvr2_model_count} potential SeedVR2 model(s) in ComfyUI's model management (not cached in GlobalModelCache)")
else:
# cache_model=False (default): Models are never cached in GlobalModelCache and are automatically deleted from memory after processing
# cache_model=True: Models are cached in GlobalModelCache and remain in memory after processing
print("SeedVR2: Cache is empty - cache_model option is disabled (False by default). Enable cache_model=True in SeedVR2 nodes to cache models in GlobalModelCache.")
except Exception:
print("SeedVR2: Cache is empty - cache_model option is disabled (False by default). Enable cache_model=True in SeedVR2 nodes to cache models in GlobalModelCache.")
else:
# Models exist in cache but weren't cleared (shouldn't happen normally)
print(f"SeedVR2 cache state: {dit_count_before} DiT, {vae_count_before} VAE, {runner_count_before} runner template(s) (models may not be cached)")
else:
print("SeedVR2: Could not access GlobalModelCache")
except ImportError as e:
print(f"SeedVR2 not available or incompatible version: {e}")
except Exception as e:
print(f"Error purging SeedVR2 models: {e}")
finally:
# Restore original sys.path
sys.path[:] = original_path
else:
# SeedVR2 path not found - this is normal if SeedVR2 is not installed
pass
except Exception as e:
print(f"Error accessing SeedVR2 models: {e}")
# Purge Qwen3-VL models if requested
if purge_qwen3vl_models:
try:
print("Qwen3-VL: Starting purge process...")
# Try to import Qwen3VLForConditionalGeneration to check model type
qwen3vl_model_type = None
try:
from transformers import Qwen3VLForConditionalGeneration
qwen3vl_model_type = Qwen3VLForConditionalGeneration
print("Qwen3-VL: Successfully imported Qwen3VLForConditionalGeneration")
except ImportError as e:
print(f"Qwen3-VL: Failed to import Qwen3VLForConditionalGeneration: {e}")
if qwen3vl_model_type is not None:
qwen3vl_cleared = 0
# Method 1: Search for Qwen3VL models in sys.modules and other places
# Check if models are stored in any module attributes
print("Qwen3-VL: Method 1 - Searching sys.modules for models...")
modules_checked = 0
# Create a copy of sys.modules.items() to avoid RuntimeError if dictionary changes during iteration
modules_items = list(sys.modules.items())
for module_name, module in modules_items:
if module is None:
continue
modules_checked += 1
try:
# Check module attributes for Qwen3VL models
for attr_name in dir(module):
try:
attr = getattr(module, attr_name, None)
if attr is None:
continue
# Check if it's a Qwen3VL model instance
if isinstance(attr, qwen3vl_model_type):
try:
print(f"Qwen3-VL: Found model instance at {module_name}.{attr_name}")
# Handle device_map="auto" case - move all modules from GPU to CPU
try:
if hasattr(attr, 'hf_device_map'):
hf_device_map = attr.hf_device_map
print(f"Qwen3-VL: Model has hf_device_map with {len(hf_device_map)} entries")
modules_moved = 0
for param_name, device in hf_device_map.items():
# Handle different device formats: str ('cuda:0'), int (device index), or torch.device
device_str = str(device) if device is not None else ''
if device_str.startswith('cuda') or (isinstance(device, int) and device >= 0):
print(f"Qwen3-VL: Moving module {param_name} from {device} to CPU")
submodule = attr
# Skip empty param_name (root module)
if param_name:
try:
for part in param_name.split('.'):
submodule = getattr(submodule, part)
except AttributeError:
print(f"Qwen3-VL: Warning: Could not find module path {param_name}, skipping")
continue
if hasattr(submodule, 'to'):
submodule.to('cpu')
modules_moved += 1
print(f"Qwen3-VL: Moved {modules_moved} modules from GPU to CPU")
except Exception as e:
print(f"Qwen3-VL: Error handling hf_device_map: {e}")
# Move model to CPU and clear GPU memory
try:
print(f"Qwen3-VL: Attempting to move model to CPU...")
if hasattr(attr, 'to'):
attr.to('cpu')
print(f"Qwen3-VL: Model moved to CPU using .to('cpu')")
elif hasattr(attr, 'cpu'):
attr.cpu()
print(f"Qwen3-VL: Model moved to CPU using .cpu()")
except Exception as e:
print(f"Qwen3-VL: Direct move to CPU failed: {e}, trying parameter-by-parameter move...")
# If direct move fails, try moving parameters individually
try:
params_moved = 0
for param in attr.parameters():
if param.is_cuda:
param.data = param.data.cpu()
params_moved += 1
buffers_moved = 0
for buffer in attr.buffers():
if buffer.is_cuda:
buffer.data = buffer.data.cpu()
buffers_moved += 1
print(f"Qwen3-VL: Moved {params_moved} parameters and {buffers_moved} buffers to CPU")
except Exception as e2:
print(f"Qwen3-VL: Parameter-by-parameter move also failed: {e2}")
# Delete the model reference and force memory release
try:
# First, try to delete the attribute
if hasattr(module, attr_name):
delattr(module, attr_name)
print(f"Qwen3-VL: Deleted model reference from {module_name}.{attr_name}")
except Exception as e:
print(f"Qwen3-VL: Failed to delete model reference: {e}")
# Force delete the model object itself
# Clear all parameters and buffers to release memory
try:
# Try to clear model's internal state more aggressively (delete, not move to CPU)
if hasattr(attr, 'named_parameters'):
for name, param in list(attr.named_parameters(recurse=False)):
if param is not None and hasattr(param, 'data'):
try:
if param.data is not None:
# Delete data instead of moving to CPU
del param.data
except Exception:
pass
if hasattr(attr, 'named_buffers'):
for name, buffer in list(attr.named_buffers(recurse=False)):
if buffer is not None and hasattr(buffer, 'data'):
try:
if buffer.data is not None:
# Delete data instead of moving to CPU
del buffer.data
except Exception:
pass
# Clear model's modules dict if available
if hasattr(attr, '_modules'):
attr._modules.clear()
print(f"Qwen3-VL: Cleared model internal state")
except Exception as e:
print(f"Qwen3-VL: Warning: Failed to clear model internal state: {e}")
try:
del attr
print(f"Qwen3-VL: Deleted model object")
except Exception as e:
print(f"Qwen3-VL: Failed to delete model object: {e}")
qwen3vl_cleared += 1
print(f"Qwen3-VL: Successfully cleared model from {module_name}.{attr_name}")
except Exception as e:
print(f"Qwen3-VL: Error clearing model from {module_name}.{attr_name}: {e}")
import traceback
print(f"Qwen3-VL: Traceback: {traceback.format_exc()}")
# Check if it's a dict containing a model (like {"model": model_object, "model_path": path})
elif isinstance(attr, dict) and 'model' in attr:
model_obj = attr.get('model')
if isinstance(model_obj, qwen3vl_model_type):
try:
print(f"Qwen3-VL: Found model in dict at {module_name}.{attr_name}")
# Handle device_map="auto" case - move all modules from GPU to CPU
try:
if hasattr(model_obj, 'hf_device_map'):
hf_device_map = model_obj.hf_device_map
print(f"Qwen3-VL: Model in dict has hf_device_map with {len(hf_device_map)} entries")
modules_moved = 0
for param_name, device in hf_device_map.items():
# Handle different device formats: str ('cuda:0'), int (device index), or torch.device
device_str = str(device) if device is not None else ''
if device_str.startswith('cuda') or (isinstance(device, int) and device >= 0):
print(f"Qwen3-VL: Moving module {param_name} from {device} to CPU")
submodule = model_obj
# Skip empty param_name (root module)
if param_name:
try:
for part in param_name.split('.'):
submodule = getattr(submodule, part)
except AttributeError:
print(f"Qwen3-VL: Warning: Could not find module path {param_name}, skipping")
continue
if hasattr(submodule, 'to'):
submodule.to('cpu')
modules_moved += 1
print(f"Qwen3-VL: Moved {modules_moved} modules from GPU to CPU")
except Exception as e:
print(f"Qwen3-VL: Error handling hf_device_map in dict: {e}")
# Move model to CPU
try:
print(f"Qwen3-VL: Attempting to move model in dict to CPU...")
if hasattr(model_obj, 'to'):
model_obj.to('cpu')
print(f"Qwen3-VL: Model in dict moved to CPU using .to('cpu')")
elif hasattr(model_obj, 'cpu'):
model_obj.cpu()
print(f"Qwen3-VL: Model in dict moved to CPU using .cpu()")
except Exception as e:
print(f"Qwen3-VL: Direct move to CPU failed: {e}, trying parameter-by-parameter move...")
# If direct move fails, try moving parameters individually
try:
params_moved = 0
for param in model_obj.parameters():
if param.is_cuda:
param.data = param.data.cpu()
params_moved += 1
buffers_moved = 0
for buffer in model_obj.buffers():
if buffer.is_cuda:
buffer.data = buffer.data.cpu()
buffers_moved += 1
print(f"Qwen3-VL: Moved {params_moved} parameters and {buffers_moved} buffers to CPU")
except Exception as e2:
print(f"Qwen3-VL: Parameter-by-parameter move also failed: {e2}")
# Clear the model from dict and force memory release
try:
# Clear model's internal state before deletion (delete, not move to CPU)
if hasattr(model_obj, 'named_parameters'):
for name, param in list(model_obj.named_parameters(recurse=False)):
if param is not None and hasattr(param, 'data'):
try:
if param.data is not None:
# Delete data instead of moving to CPU
del param.data
except Exception:
pass
if hasattr(model_obj, 'named_buffers'):
for name, buffer in list(model_obj.named_buffers(recurse=False)):
if buffer is not None and hasattr(buffer, 'data'):
try:
if buffer.data is not None:
# Delete data instead of moving to CPU
del buffer.data
except Exception:
pass
if hasattr(model_obj, '_modules'):
model_obj._modules.clear()
print(f"Qwen3-VL: Cleared model internal state from dict")
except Exception as e:
print(f"Qwen3-VL: Warning: Failed to clear model internal state from dict: {e}")
try:
# Delete the model object first
del model_obj
print(f"Qwen3-VL: Deleted model object from dict")
except Exception as e:
print(f"Qwen3-VL: Failed to delete model object from dict: {e}")
# Clear the dict entry
attr['model'] = None
qwen3vl_cleared += 1
print(f"Qwen3-VL: Successfully cleared model from dict in {module_name}.{attr_name}")
except Exception as e:
print(f"Qwen3-VL: Error clearing model from dict in {module_name}.{attr_name}: {e}")
import traceback
print(f"Qwen3-VL: Traceback: {traceback.format_exc()}")
except Exception:
pass
except Exception as e:
print(f"Qwen3-VL: Error checking module {module_name}: {e}")
print(f"Qwen3-VL: Method 1 complete - checked {modules_checked} modules")
# Method 2: Force clear GPU memory for any remaining transformers models
print("Qwen3-VL: Method 2 - Searching gc.get_objects() for models...")
if torch.cuda.is_available():
try:
objects_checked = 0
models_found_in_gc = 0
# Get all objects in memory that might be transformers models
for obj in gc.get_objects():
objects_checked += 1
try:
if isinstance(obj, qwen3vl_model_type):
print(
f"Qwen3-VL: Found model instance in gc.get_objects() (type: {type(obj).__name__}, id: {id(obj)})"
)
models_found_in_gc += 1
# For transformers models with device_map="auto", need to handle multiple devices
try:
# Try to get device info from model
if hasattr(obj, 'hf_device_map'):
hf_device_map = obj.hf_device_map
print(f"Qwen3-VL: Model has hf_device_map with {len(hf_device_map)} entries")
modules_moved = 0
# Model is distributed across multiple devices
# Move all modules to CPU
for param_name, device in hf_device_map.items():
# Handle different device formats: str ('cuda:0'), int (device index), or torch.device
device_str = str(device) if device is not None else ''
if device_str.startswith('cuda') or (isinstance(device, int) and device >= 0):
print(f"Qwen3-VL: Moving module {param_name} from {device} to CPU")
submodule = obj
# Skip empty param_name (root module)
if param_name:
try:
for part in param_name.split('.'):
submodule = getattr(submodule, part)
except AttributeError:
print(f"Qwen3-VL: Warning: Could not find module path {param_name}, skipping")
continue
if hasattr(submodule, 'to'):
submodule.to('cpu')
modules_moved += 1
print(f"Qwen3-VL: Moved {modules_moved} modules from GPU to CPU")
except Exception as e:
print(f"Qwen3-VL: Error handling hf_device_map in gc objects: {e}")
# Move entire model to CPU
try:
print(f"Qwen3-VL: Attempting to move model to CPU...")
if hasattr(obj, 'to'):
obj.to('cpu')
print(f"Qwen3-VL: Model moved to CPU using .to('cpu')")
elif hasattr(obj, 'cpu'):
obj.cpu()
print(f"Qwen3-VL: Model moved to CPU using .cpu()")
except Exception as e:
print(f"Qwen3-VL: Direct move to CPU failed: {e}, trying parameter-by-parameter move...")
# If model is quantized or has special structure, try moving parameters
try:
params_moved = 0
for param in obj.parameters():
if param.is_cuda:
param.data = param.data.cpu()
params_moved += 1
buffers_moved = 0
for buffer in obj.buffers():
if buffer.is_cuda:
buffer.data = buffer.data.cpu()
buffers_moved += 1
print(f"Qwen3-VL: Moved {params_moved} parameters and {buffers_moved} buffers to CPU")
except Exception as e2:
print(f"Qwen3-VL: Parameter-by-parameter move also failed: {e2}")
# Force delete the model object and clear all references
try:
# Clear model's internal state more aggressively (delete, not move to CPU)
if hasattr(obj, 'named_parameters'):
for name, param in list(obj.named_parameters(recurse=False)):
if param is not None and hasattr(param, 'data'):
try:
if param.data is not None:
# Delete data instead of moving to CPU
del param.data
except Exception:
pass
if hasattr(obj, 'named_buffers'):
for name, buffer in list(obj.named_buffers(recurse=False)):
if buffer is not None and hasattr(buffer, 'data'):
try:
if buffer.data is not None:
# Delete data instead of moving to CPU
del buffer.data
except Exception:
pass
# Clear model's modules dict if available
if hasattr(obj, '_modules'):
obj._modules.clear()
print(f"Qwen3-VL: Cleared model internal state from gc.get_objects()")
except Exception as e:
print(f"Qwen3-VL: Warning: Failed to clear model internal state from gc.get_objects(): {e}")
try:
del obj
print(f"Qwen3-VL: Deleted model object from gc.get_objects()")
except Exception as e:
print(f"Qwen3-VL: Failed to delete model object: {e}")
qwen3vl_cleared += 1
print(f"Qwen3-VL: Successfully cleared model from gc.get_objects()")
except Exception as e:
pass
print(
f"Qwen3-VL: Method 2 complete - checked {objects_checked} objects, found {models_found_in_gc} models"
)
except Exception as e:
print(f"Qwen3-VL: Error in GPU memory cleanup: {e}")
import traceback
print(f"Qwen3-VL: Traceback: {traceback.format_exc()}")
# Force garbage collection and clear GPU cache
print("Qwen3-VL: Running garbage collection...")
gc.collect()
gc.collect() # Run twice to ensure cleanup
if torch.cuda.is_available():
print("Qwen3-VL: Clearing CUDA cache...")
# Clear cache for all devices
for device_idx in range(torch.cuda.device_count()):
with torch.cuda.device(device_idx):
torch.cuda.empty_cache()
torch.cuda.ipc_collect()
torch.cuda.synchronize()
print("Qwen3-VL: CUDA cache cleared for all devices")
if qwen3vl_cleared > 0:
print(f"Qwen3-VL: Successfully cleared {qwen3vl_cleared} model(s)")
else:
print("Qwen3-VL: No models found in memory (models may not be cached or already cleared)")
else:
print("Qwen3-VL: transformers library with Qwen3VLForConditionalGeneration not available")
except Exception as e:
print(f"Qwen3-VL: Error purging models: {e}")
import traceback
print(f"Qwen3-VL: Traceback: {traceback.format_exc()}")
# Purge Nunchaku models if requested
if purge_nunchaku_models:
try:
print("Nunchaku: Starting purge process...")
# Try to import Nunchaku transformer model types
nunchaku_model_types = []
# Try to import NunchakuFluxTransformer2dModel
try:
from nunchaku import NunchakuFluxTransformer2dModel
nunchaku_model_types.append(NunchakuFluxTransformer2dModel)
print("Nunchaku: Successfully imported NunchakuFluxTransformer2dModel")
except ImportError as e:
print(f"Nunchaku: Failed to import NunchakuFluxTransformer2dModel: {e}")
# Try to import NunchakuZImageTransformer2DModel
try:
from nunchaku.models.transformers.transformer_zimage import NunchakuZImageTransformer2DModel
nunchaku_model_types.append(NunchakuZImageTransformer2DModel)
print("Nunchaku: Successfully imported NunchakuZImageTransformer2DModel")
except ImportError as e:
print(f"Nunchaku: Failed to import NunchakuZImageTransformer2DModel: {e}")
# Try to import NunchakuT5EncoderModel (text encoder)
try:
from nunchaku.models.transformers.transformer_t5 import NunchakuT5EncoderModel
nunchaku_model_types.append(NunchakuT5EncoderModel)
print("Nunchaku: Successfully imported NunchakuT5EncoderModel")
except ImportError as e:
print(f"Nunchaku: Failed to import NunchakuT5EncoderModel: {e}")
# Try to import NunchakuQwenImageTransformer2DModel (Qwen-Image)
try:
from comfyui_nunchaku.models.qwenimage import NunchakuQwenImageTransformer2DModel
nunchaku_model_types.append(NunchakuQwenImageTransformer2DModel)
print("Nunchaku: Successfully imported NunchakuQwenImageTransformer2DModel")
except ImportError as e:
print(f"Nunchaku: Failed to import NunchakuQwenImageTransformer2DModel from comfyui_nunchaku: {e}")
# Try to import NunchakuSDXLUNet2DConditionModel (SDXL)
try:
from nunchaku.models.unets.unet_sdxl import NunchakuSDXLUNet2DConditionModel
nunchaku_model_types.append(NunchakuSDXLUNet2DConditionModel)
print("Nunchaku: Successfully imported NunchakuSDXLUNet2DConditionModel")
except ImportError as e:
print(f"Nunchaku: Failed to import NunchakuSDXLUNet2DConditionModel: {e}")
# Try to import NunchakuSDXL class (SDXL model wrapper)
nunchaku_sdxl_class = None
try:
# Try to import from model_base
try:
from model_base.sdxl import NunchakuSDXL
nunchaku_sdxl_class = NunchakuSDXL
print("Nunchaku: Successfully imported NunchakuSDXL from model_base.sdxl")
except ImportError:
# Try alternative import paths
try:
for module_name in list(sys.modules.keys()):
if 'nunchaku' in module_name.lower() and 'sdxl' in module_name.lower():
module = sys.modules[module_name]
if hasattr(module, 'NunchakuSDXL'):
nunchaku_sdxl_class = getattr(module, 'NunchakuSDXL')
print(f"Nunchaku: Found NunchakuSDXL in {module_name}")
break