WAN2.2 i2v 14B FP16 GGUF with lightning LoRA 4steps or lightx2v 6steps & Distorch Memory Manager
On October 8, 2025, I updated Ver II 4steps, which applies the lightning LoRA for WAN2.2.
It is quite good. The concept above was a form that was quite difficult to achieve with WAN2.1.
About WAN2.2
i2v with WAN2.1 lightx2v LoRA 6steps
i2v II with WAN2.2 lightning LoRA 4steps
It is the usual setup. Even when using the full-size FP16 GGUF, the VRAM consumption is kept under 12GB thanks to Distorch, a modified MultiGPU node... It is becoming my standard every time a new video generation model is released.
It is faster than I thought. Distorch is familiar, but surprisingly, the single-task performance is faster than WAN2.1.
Also, as shown below, it is known that reducing the number of steps using lightx2v is effective. However, currently, using the LoRA for WAN2.1 outputs a large number of errors, so that part is not very smart. This error itself is unavoidable, but it is possible to "handle" the display itself, which I will describe later.
Regarding the Distorch modification, please refer to the following.
At 480x480, the processing looks like this. It seems that VRAM consumption stays under 12GB up to around 720x720 with a length of 121. The JSON above is set to 640x640 with a length of 121.

fp16 GGUF
I have released the unquantized full-size GGUF below.
Ollama-Generate V2
Although it makes the operation heavier, I use Ollama to provide an automatic prompt generation function. It also supports vision-based LLMs, but honestly, the operational accuracy is not good. A standard text-generation LLM is safer.

In return, prompt input supports Japanese. It is designed to perform automatic translation with Ollama Generate V2.

Fixed lora.py
\ComfyUI\comfy\lora.py"
It does not affect operation, but applying the lightx2v LoRA originally intended for WAN2.1 to WAN2.2 results in a large number of
lora key not loaded: diffusion_model.blocks.0.attn1.to_q.weight
lora key not loaded: diffusion_model.blocks.0.attn1.to_k.weighttype errors. It is not a pleasant feeling, so I have applied a fix to convert this as follows, but only "when applying a WAN2.1 LoRA to WAN2.2," and I have released it above.
Suppressed 290 WAN2.1 LoRA key warnings (WAN2.1 LoRA on WAN2.2 model - expected behavior)Explanation
Implementation note for aggregating and suppressing unloaded key warnings only when applying WAN2.1 LoRA to WAN2.2 models (ComfyUI `lora.py`)
Objective
Aggregate the large volume of "expected unloaded key warnings" into a summary log only when applying WAN2.1 LoRA to WAN2.2.
In all other cases (WAN2.1 models or other models), output warnings as usual without suppression.
Include all newly error-prone series (`double_blocks`/`single_blocks` img/text heads) in the suppression scope.
Target file
`ComfyUI/comfy/lora.py`
Before change (initial state)
There is no suppression logic; all unloaded keys are output as warnings.
if log_missing:
for x in lora.keys():
if x not in loaded_keys:
logging.warning("lora key not loaded: {}".format(x))Problem:
When applying WAN2.1 LoRA to WAN2.2, a large number of unloaded keys derived from structural differences continue to appear, turning the logs into noise.
After change (final form)
Change location: `if log_missing:` block within `def load_lora(...):`
-
Core of implementation:
If the LoRA side contains even one "key fragment" specific to WAN2.1, it is identified as a "WAN2.1 LoRA".
If the model side (`to_load.values()`) contains no WAN2.1-specific fragments, it is presumed to be "WAN2.2".
Only when both are true (WAN2.1 LoRA × WAN2.2 model) and the corresponding key matches the indicator, suppress it and output the count in the summary log.
Otherwise, output individual warnings as usual.
Actual code (relevant section only):
if log_missing:
# WAN2.1 on WAN2.2 warning suppression (expected missing keys)
# Detect WAN2.1-style LoRA by presence of characteristic key fragments in the LoRA file
wan21_lora_indicators = [
# classic image-attn/text-attn specific
"k_img.diff_b", "k_img.lora_down.weight", "k_img.lora_up.weight",
"v_img.diff_b", "v_img.lora_down.weight", "v_img.lora_up.weight",
"norm_k_img.diff",
# img_emb variants (WAN2.1)
"img_emb.proj.",
# cross_attn variants (WAN2.1)
".cross_attn.k_img.", ".cross_attn.v_img.",
# trainers: double_blocks/single_blocks heads (img/text)
".double_blocks.", ".single_blocks.",
".img_attn_proj.", ".img_attn_qkv.",
".img_mlp.fc1.", ".img_mlp.fc2.",
".img_mod.linear.",
".txt_attn_proj.", ".txt_attn_qkv.",
".txt_mlp.fc1.", ".txt_mlp.fc2.",
".txt_mod.linear.",
# common single block linear heads
".linear1.lora_", ".linear2.lora_", ".modulation.linear."
]
wan21_keys_found = [key for key in lora.keys() if any(indicator in key for indicator in wan21_lora_indicators)]
is_wan21_lora = len(wan21_keys_found) > 0
# Apply suppression ONLY when applying WAN2.1 LoRA onto WAN2.2 model.
# Heuristic for model: if model (to_load values) contains WAN2.1-only keys, it's not WAN2.2.
try:
model_key_samples = [str(v) for v in to_load.values()]
except Exception:
model_key_samples = []
model_has_wan21_like_keys = any(
(".cross_attn.k_img." in mk) or (".cross_attn.v_img." in mk) or
("img_emb.proj" in mk) or ("norm_k_img" in mk)
for mk in model_key_samples
)
is_wan22_model_target = not model_has_wan21_like_keys
suppressed_count = 0
actual_warnings = []
for x in lora.keys():
if x not in loaded_keys:
# Suppress only expected WAN2.1-on-WAN2.2 missing keys
if (is_wan21_lora and is_wan22_model_target and
any(indicator in x for indicator in wan21_lora_indicators)):
suppressed_count += 1
continue
actual_warnings.append(x)
for x in actual_warnings:
logging.warning("lora key not loaded: {}".format(x))
if suppressed_count > 0:
logging.info(
"Suppressed {} WAN2.1 LoRA key warnings (WAN2.1 LoRA on WAN2.2 model - expected behavior)".format(
suppressed_count
)
)What was targeted for suppression (partial match indicator)
-
k_img/v_img series:
"k_img.diff_b", "k_img.lora_down.weight", "k_img.lora_up.weight"
"v_img.diff_b", "v_img.lora_down.weight", "v_img.lora_up.weight"
"norm_k_img.diff"
-
img_emb series:
"img_emb.proj."
-
cross_attn series:
".cross_attn.k_img.", ".cross_attn.v_img."
-
Also covering the newly presented sequences:
".double_blocks.", ".single_blocks."
".img_attn_proj.", ".img_attn_qkv."
".img_mlp.fc1.", ".img_mlp.fc2."
".img_mod.linear."
".txt_attn_proj.", ".txt_attn_qkv."
".txt_mlp.fc1.", ".txt_mlp.fc2."
".txt_mod.linear."
".linear1.lora_", ".linear2.lora_", ".modulation.linear."
Note:
Do not use an exact match up to the trailing `.weight` (as the ends may vary due to differences in trainers). Partial matching is sufficient and safe.
Reproduction steps
Target file: `ComfyUI/comfy/lora.py`
Replace the `if log_missing:` block in `load_lora` with the completed version above.
Restart ComfyUI (or reload the relevant module).
Set the log level to info or higher (aggregated logs are info).
Points for operational verification
-
WAN2.2 × WAN2.1 LoRA:
Individual un-loaded warnings are suppressed, and only an aggregated log is displayed at the end
Example: `Suppressed 290 WAN2.1 LoRA key warnings (WAN2.1 LoRA on WAN2.2 model - expected behavior)`
-
WAN2.2 × WAN2.2 LoRA:
Suppression is not triggered (normally, un-loaded warnings themselves are not expected to appear)
-
WAN2.1 × WAN2.1 LoRA (or other models):
Suppression is not triggered (individual warnings appear)
Troubleshooting
-
If there are keys that are not suppressed:
Add a part of the key name to `wan21_lora_indicators` on a new line (partial match is fine)
-
If suppression is too aggressive (it disappears in environments where it should appear):
Make the conditions for model-side determination (`model_has_wan21_like_keys`) more strict
-
No logs appearing:
Set log level to info or higher
Impact of changes
Significantly reduced log noise when applying WAN2.2 × WAN2.1
Maintained conventional warning displays for combinations other than WAN2.2 (for safety)
With the contents of this notebook, a third party can reproduce the same implementation and behavior later. If needed, it can be extended by simply adding one line of fragment to `wan21_lora_indicators`.
About WAN2.1 LoRA
While the application of LoRA for WAN2.1 cannot be summarized in a single statement, the following is an example where it worked as expected with strength 3.0 on the High side and 1.5 on the Low side. I am using a 360-degree rotation motion LoRA.
WAN2.2 has extremely high prompt reproduction capability, so actually, this level of task can be done with just prompts.
Fixed GGUF Loader for WAN2.2
Regardless of what happens later, at least at the current moment, the essential GGUF loader does not support WAN2.2.
Therefore, I have modified it. The following is not particularly difficult. Strictly speaking, it is structured such that “by modifying the GGUF loader description, the MultiGPU GGUF loader recognizes WAN2.2”.
## Modification points
**File**: `ComfyUI/custom_nodes/ComfyUI-GGUF/loader.py`
### Code before modification
IMG_ARCH_LIST = {"flux", "sd1", "sdxl", "sd3", "aura", "hidream", "cosmos", "ltxv", "hyvid", "wan"}### Code after modification
IMG_ARCH_LIST = {"flux", "sd1", "sdxl", "sd3", "aura", "hidream", "cosmos", "ltxv", "hyvid", "wan", "wan2.2"}```
Distorch Memory Manager
About a node
I had a feeling that the process of applying distorch processing to two 30GB-class base models would have some impact, and as expected, it caused an issue where x4 Upscale would not complete due to OOM on RTX4070 12GB-class hardware.
However, I have already solved this through my own modifications. First, as shown in the figure below, I created an original node to unload memory usage by Distorch.

independently Distorch Memory Cleaner
The nodes are published in the article below. Simply download, unzip, and place them in the custom_nodes folder to use them.
custom_nodes\ComfyUI-MultiGPU_init_.py
I do not recommend this as it increases the difficulty, but it is possible to integrate the same functionality into MultiGPU as shown below. However, this method will be difficult for users to maintain unless they understand the file structure and the content of the code. The reasons are described in the article above.
I will explain the details of the DisTorch Memory Manager modifications, including the code.
## 📋 DisTorch Memory Manager Modification Summary
### 1. **DisTorchMemoryCleaner** - Basic memory clearing
class DisTorchMemoryCleaner:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"image": ("IMAGE",),
}}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "clean_memory"
CATEGORY = "DisTorch"
def clean_memory(self, image):
import torch
import gc
# GPUメモリのクリア
if torch.cuda.is_available():
torch.cuda.empty_cache()
torch.cuda.synchronize()
# Pythonのガベージコレクション
gc.collect()
# DisTorchの仮想メモリを解放
try:
import comfy.model_management
# 仮想メモリの割り当てをリセット
if hasattr(comfy.model_management, 'free_memory'):
comfy.model_management.free_memory(0, 'cuda:0')
comfy.model_management.free_memory(0, 'cpu')
except:
pass
print("DisTorch memory cleaned")
return (image,)**Functionality**:
- GPU memory clearing (`torch.cuda.empty_cache()`)
- Python garbage collection (`gc.collect()`)
- Resetting DisTorch virtual memory
- Simple and safe memory management
### 2. **DisTorchMemoryManager** - Comprehensive memory management (with UI corruption prevention)
class DisTorchMemoryManager:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"image": ("IMAGE",),
"clean_gpu": ("BOOLEAN", {"default": True}),
"clean_cpu": ("BOOLEAN", {"default": False, "tooltip": "CPU memory cleanup (use with caution)"}),
"force_gc": ("BOOLEAN", {"default": True}),
"reset_virtual_memory": ("BOOLEAN", {"default": True}),
"restore_original_functions": ("BOOLEAN", {"default": False, "tooltip": "Restore original model_management functions"}),
}}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "manage_memory"
CATEGORY = "DisTorch"
def manage_memory(self, image, clean_gpu, clean_cpu, force_gc, reset_virtual_memory, restore_original_functions):
import torch
import gc
import psutil
import pydantic
print("=== DisTorch Memory Management ===")
# メモリ使用量の表示(前)
if torch.cuda.is_available():
gpu_memory_before = torch.cuda.memory_allocated() / 1024**3
print(f"GPU Memory before: {gpu_memory_before:.2f} GB")
cpu_memory_before = psutil.virtual_memory().used / 1024**3
print(f"CPU Memory before: {cpu_memory_before:.2f} GB")
# GPUメモリのクリア
if clean_gpu and torch.cuda.is_available():
try:
torch.cuda.empty_cache()
torch.cuda.synchronize()
print("GPU cache cleared")
except Exception as e:
print(f"GPU cache clear failed: {e}")
# CPUメモリのクリア(UI破損対策済み)
if clean_cpu:
# 基本的なガベージコレクションのみ実行
# オブジェクトの詳細検査は行わない(UIに影響する可能性があるため)
try:
# 明示的にガベージコレクションを実行
collected = gc.collect()
print(f"CPU memory cleanup: {collected} objects collected")
except Exception as e:
print(f"CPU memory cleanup failed: {e}")
# ガベージコレクション
if force_gc:
try:
collected = gc.collect()
print(f"Garbage collected: {collected} objects")
except Exception as e:
print(f"Garbage collection failed: {e}")
# DisTorchの仮想メモリをリセット
if reset_virtual_memory:
try:
import comfy.model_management
if hasattr(comfy.model_management, 'free_memory'):
comfy.model_management.free_memory(0, 'cuda:0')
comfy.model_management.free_memory(0, 'cpu')
print("Virtual memory reset")
except Exception as e:
print(f"Virtual memory reset failed: {e}")
# メモリ使用量の表示(後)
if torch.cuda.is_available():
try:
gpu_memory_after = torch.cuda.memory_allocated() / 1024**3
gpu_freed = gpu_memory_before - gpu_memory_after
print(f"GPU Memory after: {gpu_memory_after:.2f} GB (freed: {gpu_freed:.2f} GB)")
except Exception as e:
print(f"GPU memory measurement failed: {e}")
try:
cpu_memory_after = psutil.virtual_memory().used / 1024**3
cpu_freed = cpu_memory_before - cpu_memory_after
print(f"CPU Memory after: {cpu_memory_after:.2f} GB (freed: {cpu_freed:.2f} GB)")
except Exception as e:
print(f"CPU memory measurement failed: {e}")
# 元の関数を復元(オプション)
if restore_original_functions:
try:
mm.get_torch_device = original_get_torch_device
mm.text_encoder_device = original_text_encoder_device
print("Original model_management functions restored")
except Exception as e:
print(f"Failed to restore original functions: {e}")
print("=== Memory Management Complete ===")
return (image,)**Key improvements**:
1. **UI corruption prevention**:
# CPUメモリクリアを安全に実行
if clean_cpu:
# オブジェクトの詳細検査は行わない
try:
collected = gc.collect()
print(f"CPU memory cleanup: {collected} objects collected")
except Exception as e:
print(f"CPU memory cleanup failed: {e}")2. **Detailed memory monitoring**:
# メモリ使用量の前後比較
gpu_memory_before = torch.cuda.memory_allocated() / 1024**3
cpu_memory_before = psutil.virtual_memory().used / 1024**3
# 処理後
gpu_freed = gpu_memory_before - gpu_memory_after
cpu_freed = cpu_memory_before - cpu_memory_after3. **Error handling**:
try:
torch.cuda.empty_cache()
torch.cuda.synchronize()
print("GPU cache cleared")
except Exception as e:
print(f"GPU cache clear failed: {e}")### 3. **DisTorchSafeMemoryManager** - Safe memory management (recommended)
class DisTorchSafeMemoryManager:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"image": ("IMAGE",),
"clean_gpu": ("BOOLEAN", {"default": True}),
"force_gc": ("BOOLEAN", {"default": True}),
"reset_virtual_memory": ("BOOLEAN", {"default": True}),
}}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "safe_manage_memory"
CATEGORY = "DisTorch"
def safe_manage_memory(self, image, clean_gpu, force_gc, reset_virtual_memory):
import torch
import gc
print("=== DisTorch Safe Memory Management ===")
# メモリ使用量の表示
if torch.cuda.is_available():
gpu_memory_before = torch.cuda.memory_allocated() / 1024**3
print(f"GPU Memory before: {gpu_memory_before:.2f} GB")
# GPUメモリのクリア(UIに影響しない)
if clean_gpu and torch.cuda.is_available():
try:
torch.cuda.empty_cache()
torch.cuda.synchronize()
print("GPU cache cleared")
except Exception as e:
print(f"GPU cache clear failed: {e}")
# ガベージコレクション(UIに影響しない)
if force_gc:
try:
collected = gc.collect()
print(f"Garbage collected: {collected} objects")
except Exception as e:
print(f"Garbage collection failed: {e}")
# DisTorchの仮想メモリをリセット
if reset_virtual_memory:
try:
import comfy.model_management
if hasattr(comfy.model_management, 'free_memory'):
comfy.model_management.free_memory(0, 'cuda:0')
comfy.model_management.free_memory(0, 'cpu')
print("Virtual memory reset")
except Exception as e:
print(f"Virtual memory reset failed: {e}")
# メモリ使用量の表示(後)
if torch.cuda.is_available():
try:
gpu_memory_after = torch.cuda.memory_allocated() / 1024**3
gpu_freed = gpu_memory_before - gpu_memory_after
print(f"GPU Memory after: {gpu_memory_after:.2f} GB (freed: {gpu_freed:.2f} GB)")
except Exception as e:
print(f"GPU memory measurement failed: {e}")
print("=== Safe Memory Management Complete ===")
return (image,)**Features**:
- **Completely excludes CPU memory clearing functionality** (prevents UI corruption)
- **Execute GPU memory and garbage collection only**
- **The safest and most stable** memory management
## 🛠 **Core points of the modification**
### 1. **Resolution of UI corruption issues**
# 問題のあったコード(削除済み)
# for obj in gc.get_objects():
# if isinstance(obj, pydantic.BaseModel):
# # この部分がUI破損の原因
# 解決策
if clean_cpu:
# 基本的なガベージコレクションのみ実行
collected = gc.collect()
print(f"CPU memory cleanup: {collected} objects collected")### 2. **Visualization of memory usage**
# メモリ解放量の詳細表示
gpu_freed = gpu_memory_before - gpu_memory_after
cpu_freed = cpu_memory_before - cpu_memory_after
print(f"GPU Memory freed: {gpu_freed:.2f} GB")
print(f"CPU Memory freed: {cpu_freed:.2f} GB")### 3. **Enhanced error handling**
try:
torch.cuda.empty_cache()
torch.cuda.synchronize()
print("GPU cache cleared")
except Exception as e:
print(f"GPU cache clear failed: {e}")## 🚀 **How to use**
# DisTorchSafeMemoryManager(最も安全)
clean_gpu = True
force_gc = True
reset_virtual_memory = True
# DisTorchMemoryManager(上級者向け)
clean_gpu = True
clean_cpu = False # UI破損を防ぐためFalse推奨
force_gc = True
reset_virtual_memory = True### **Effects**:
- **GPU memory**: Frees up several GBs
- **CPU memory**: Frees up several hundred MBs to several GBs
- **Stability**: No UI corruption
- **Performance**: Stabilizes upscaling and video processing
These modifications significantly improve memory management in ComfyUI, greatly reducing memory errors during upscaling and video processing.
Paging file
If you still encounter OOM errors (in this case, crashing due to system memory shortage rather than VRAM) during upscaling even after using this node, please expand your paging file size to address it.

Regarding acceleration node combinations and tensor mismatch errors
The default combination is Skip Layer + Torch Compile, but this combination seems to be the cause of tensor mismatch errors when generating at 720x720. It rarely occurs at 640x640 as well, but the conditions are unclear.

Bypassing Skip Layer and using Apply First Block Cache instead often resolves the issue. In any case, I have made it possible to select the combinations.
Without Torch.compile, generation speed drops significantly, especially at 640x640 or higher, so I would like to use it as much as possible.
