Fixed Flash Attention-3/2 on ComfyUI without xformers
14.02.2026, updated for newest KJ nodes v1.21.0.
Finally, we come to the main event: ComfyUI.
Since this update coincides with a major version upgrade on the CUDA side, xformers support is particularly delayed this time... well, since the development and updates of xformers aren't managed by a single individual or a small organization... I don't think it will disappear anytime soon... but still, I want to improve the mechanism that relies on xformers to load FA-3/2.
Also, in its initial state, ComfyUI has mutually exclusive settings for FA and SA options, so I have modified that as well.
I will state this clearly here: it is absolutely not the case that FA and SA cannot coexist.I have dealt with attention-related code in both A1111-based WebUIs and ComfyUI enough times in the past to be certain of this.
Of course, that doesn't mean both kernels can be used simultaneously for the same generation. That's not what I mean; I am saying it is possible to enable both kernels and switch between them during operation.
ComfyUI-DistorchMemoryManager
Initially, I was modifying the ComfyUI core code, but currently, this functionality is achieved solely through custom nodes without requiring those modifications.
By using the Patch Sage Attention DM node and selecting Disabled, the system is set to automatically load FA2.
This allows for loading the FA2 kernel directly without xformers.

Due to the above, the following modifications are no longer necessary, but I am keeping them for the sake of preserving technical information.
...
With that premise, this modification is based on the following two articles.
Now, ComfyUI has a feature called Initial Attention, which loads what it considers to be the optimal kernel from among various Attention types excluding SA... but in reality, it is not visible to the user what this is loading.
If it crashes without question when installing xformers on Blackwell... it is likely that FA-3 is being applied; by the way, Forge-based systems use this format. Since ComfyUI also crashes without question when xformers is applied in an unmodified state, the user can tell, "Ah, it's prioritizing the use of FA-3".
The handling of this is explained below.
And so... the introduction has been long, but this is a modification to load FA-3/2 without going through xformers, while also allowing it to coexist with SA and enabling on/off switching via KJ's SA node... and visualizing the used kernel by outputting it to the log.
Specifically, it looks like the following, but the reason FA-2 and SA-2 appear alternately is that, as a specification of ComfyUI, immediately after using an Advanced Kernel like SA, the system is designed to momentarily reset to Initial Attention, even if SA is re-enabled in the subsequent process.

In this case, since the initial state itself is being visualized, you can tell that it is FA-2. If xformers is installed, it is often reset to Cutlass.
This time, since it is an environment where xformers has been excluded, the initial state remains FA-2.
Explanation of ComfyUI Flash-Attention direct loading feature implementation
Overview
Implemented a feature to directly load FA-3/FA-2 using the `--use-flash-attention` option even without xformers. Added a new direct FA path while fully preserving existing SageAttention and xformers functionality.
List of modified files
1. comfy/cli_args.py
10.12.2025 onward
Purpose: Enable simultaneous specification of `--use-sage-attention` and `--use-flash-attention`
Modified lines: lines 109-118
Before change:
attn_group = parser.add_mutually_exclusive_group()
attn_group.add_argument("--use-split-cross-attention", action="store_true", help="Use the split cross attention optimization. Ignored when xformers is used.")
attn_group.add_argument("--use-quad-cross-attention", action="store_true", help="Use the sub-quadratic cross attention optimization . Ignored when xformers is used.")
attn_group.add_argument("--use-pytorch-cross-attention", action="store_true", help="Use the new pytorch 2.0 cross attention function.")
attn_group.add_argument("--use-sage-attention", action="store_true", help="Use sage attention.")
attn_group.add_argument("--use-flash-attention", action="store_true", help="Use FlashAttention.")
parser.add_argument("--disable-xformers", action="store_true", help="Disable xformers.")After change:
attn_group = parser.add_mutually_exclusive_group()
attn_group.add_argument("--use-split-cross-attention", action="store_true", help="Use the split cross attention optimization. Ignored when xformers is used.")
attn_group.add_argument("--use-quad-cross-attention", action="store_true", help="Use the sub-quadratic cross attention optimization . Ignored when xformers is used.")
attn_group.add_argument("--use-pytorch-cross-attention", action="store_true", help="Use the new pytorch 2.0 cross attention function.")
# SageAttention and FlashAttention can be used together (SageAttention has priority)
parser.add_argument("--use-sage-attention", action="store_true", help="Use sage attention.")
parser.add_argument("--use-flash-attention", action="store_true", help="Use FlashAttention.")
parser.add_argument("--disable-xformers", action="store_true", help="Disable xformers.")Explanation:
Exclude `--use-sage-attention` and `--use-flash-attention` from the mutually exclusive group (`attn_group`)
This allows both options to be specified simultaneously
Necessary for dynamically switching SA/FA in KJ nodes
2. comfy/model_management.py
Before
07.01.2026 onward
Purpose: Addition of direct loading feature for SageAttention and Flash-Attention
A. SageAttention loading feature (added lines 279-300)
# SageAttention support
SAGE_IS_AVAILABLE = False
SAGE_ATTN_VERSION = None
if args.use_sage_attention:
try:
import sageattention
from sageattention import sageattn
SAGE_IS_AVAILABLE = True
# Try to get version
try:
SAGE_ATTN_VERSION = sageattention.__version__
except AttributeError:
try:
import importlib.metadata
SAGE_ATTN_VERSION = importlib.metadata.version("sageattention")
except Exception:
SAGE_ATTN_VERSION = "unknown"
if SAGE_ATTN_VERSION and SAGE_ATTN_VERSION != "unknown":
logging.info(f"SageAttention {SAGE_ATTN_VERSION} successfully loaded")
else:
logging.info("SageAttention successfully loaded (version unknown)")
except ImportError as e:
logging.warning(f"--use-sage-attention specified but sageattention import failed: {e}")
logging.warning("SageAttention will not be available.")Explanation:
Import SageAttention only if the `--use-sage-attention` flag is specified
-
Attempt to retrieve the version in three stages:
Directly retrieve the `sageattention.version` attribute
Retrieve from package metadata using `importlib.metadata.version("sageattention")`
Treat as "unknown" if it fails
If import fails, output a warning and continue with `SAGE_IS_AVAILABLE = False`
B. Flash-Attention direct loading feature (added lines 302-336)
# Flash-Attention direct support (without xformers)
FLASH_IS_AVAILABLE = False
FLASH_ATTN_VERSION = None
FLASH_ATTN_TYPE = None
if args.use_flash_attention:
try:
import flash_attn
from flash_attn import flash_attn_func
FLASH_IS_AVAILABLE = True
# Detect Flash-Attention version
try:
FLASH_ATTN_VERSION = flash_attn.__version__
# Determine FA-3 or FA-2 based on version
version_parts = FLASH_ATTN_VERSION.split('.')
major_version = int(version_parts[0])
if major_version >= 3:
FLASH_ATTN_TYPE = "FA-3"
else:
FLASH_ATTN_TYPE = "FA-2"
logging.info(f"Flash-Attention {FLASH_ATTN_VERSION} ({FLASH_ATTN_TYPE}) successfully loaded")
except Exception:
FLASH_ATTN_VERSION = "unknown"
FLASH_ATTN_TYPE = "FA"
logging.info("Flash-Attention successfully loaded (version unknown)")
except ImportError as e:
logging.warning(f"--use-flash-attention specified but flash-attn import failed: {e}")
logging.warning("Flash-Attention will not be available.")Explanation:
Import Flash-Attention only if the `--use-flash-attention` flag is specified
Directly import `flash_attn_func` (xformers not required)
-
Automatically distinguish between FA-3/FA-2 based on version number:
Major version 3 or higher → `"FA-3"`
Major version 2 or lower → `"FA-2"`
Version retrieval failure → `"FA"` (generic notation)
On success, display something like `Flash-Attention 2.8.2 (FA-2) successfully loaded`
C. Added helper functions (added lines 1443-1461)
def xformers_enabled():
"""Check if xformers is available and enabled"""
return XFORMERS_IS_AVAILABLE
def xformers_enabled_vae():
"""Check if xformers is available and enabled for VAE"""
return XFORMERS_IS_AVAILABLE and XFORMERS_ENABLED_VAE
def sage_attention_enabled():
"""Check if SageAttention is available and enabled"""
return SAGE_IS_AVAILABLE
def flash_attention_enabled():
"""Check if Flash-Attention is available and enabled"""
return FLASH_IS_AVAILABLE
def pytorch_attention_enabled():
"""Check if PyTorch attention is enabled"""
return ENABLE_PYTORCH_ATTENTIONExplanation:
Unified interface to check the enabled status of attention features
Called from `attention.py` and used to select the appropriate attention function
Maintain consistency by including the existing `xformers_enabled()` and `xformers_enabled_vae()`
Code added on November 8, 2025
Wonderful! ✅ **This time, the FA-2 log appeared!**
```
Restoring initial comfy attention
[ComfyUI] Using FA-2 (Flash-Attention 2.8.3) direct```
### **Detailed explanation of why it succeeded this time:**
#### **1. Backup code (which was failing):**
if orig_attn == comfy_attention.attention_flash:```
The problem with this code:
- **Comparison of references (memory addresses)**
- It succeeded in the backup environment (the references happened to be the same)
- However, it failed in the current environment (the references were different)
#### **2. Code fixed by the user (which succeeded):**
if mm.flash_attention_enabled():The reason this code succeeded:
**What is `mm.flash_attention_enabled()`?**
def flash_attention_enabled():
return FLASH_IS_AVAILABLE- `FLASH_IS_AVAILABLE` is a **boolean value** (True/False)
- It is set during initialization in lines 311-337 of `model_management.py`
**Flow:**
# model_management.py 311行目
FLASH_IS_AVAILABLE = False
if args.use_flash_attention: # <-- --use-flash-attention フラグが渡された場合
try:
import flash_attn
FLASH_IS_AVAILABLE = True # <-- Trueに設定される
# バージョン検出
FLASH_ATTN_VERSION = flash_attn.__version__ # "2.8.3"
FLASH_ATTN_TYPE = "FA-2" # または "FA-3"
except:
pass#### **3. Execution flow in KJNode (this time):**
# KJNode: model_optimization_nodes.py 202行目
if mm.flash_attention_enabled(): # <-- FLASH_IS_AVAILABLE == True ?
print("Restoring initial comfy attention")
# 205-206行目
if hasattr(mm, 'FLASH_ATTN_VERSION') and mm.FLASH_ATTN_VERSION and mm.FLASH_ATTN_VERSION != "unknown":
print(f"[ComfyUI] Using {mm.FLASH_ATTN_TYPE} (Flash-Attention {mm.FLASH_ATTN_VERSION}) direct")
# --> "[ComfyUI] Using FA-2 (Flash-Attention 2.8.3) direct" が出力される!```
### **Difference between Reference and Flag:**|

### **Why it worked in the backup:**
In the backup environment, it was likely that:
1. The module loading order was different
2. The timing of decorator processing was different
3. Or both references happened to point to the same memory address
### **Why the current implementation is superior:**
✅ **Flag-based determination** → 100% reliable
✅ **Does not depend on references** → Not affected by the environment
✅ **High maintainability** → The reason is clear
**In other words, the code the user fixed is a better implementation than the backup!** 🎉
3. comfy/ldm/modules/attention.py
Before 25.10.2025
08.11.2025 onward
Purpose: Direct Flash-Attention implementation and priority logic
A. attention_flash function implementation (added lines 588-629)
def attention_flash(q, k, v, heads, mask=None, attn_precision=None, skip_reshape=False, skip_output_reshape=False, transformer_options=None):
"""Direct Flash-Attention implementation (FA-2/FA-3) without xformers"""
# Log Flash-Attention usage (once per step)
if not hasattr(attention_flash, "_logged"):
if model_management.FLASH_ATTN_VERSION and model_management.FLASH_ATTN_VERSION != "unknown":
print(f"[ComfyUI] Using {model_management.FLASH_ATTN_TYPE} (Flash-Attention {model_management.FLASH_ATTN_VERSION}) direct")
else:
print(f"[ComfyUI] Using Flash-Attention direct (version unknown)")
attention_flash._logged = True
if skip_reshape:
b, _, _, dim_head = q.shape
else:
b, _, dim_head = q.shape
dim_head //= heads
q, k, v = map(
lambda t: t.view(b, -1, heads, dim_head).transpose(1, 2),
(q, k, v),
)
if mask is not None:
# add a batch dimension if there isn't already one
if mask.ndim == 2:
mask = mask.unsqueeze(0)
# add a heads dimension if there isn't already one
if mask.ndim == 3:
mask = mask.unsqueeze(1)
try:
# Flash-Attention expects (batch, seqlen, nheads, headdim)
# Current shape after transpose(1,2): (batch, nheads, seqlen, headdim)
# Need to transpose back to (batch, seqlen, nheads, headdim)
out = flash_attn_func(
q.transpose(1, 2),
k.transpose(1, 2),
v.transpose(1, 2),
dropout_p=0.0,
causal=False,
)
# flash_attn_func returns (batch, seqlen, nheads, headdim)
# Transpose to (batch, nheads, seqlen, headdim)
out = out.transpose(1, 2)
except Exception as e:
logging.warning(f"Flash Attention failed, using default SDPA: {e}")
out = torch.nn.functional.scaled_dot_product_attention(q, k, v, attn_mask=mask, dropout_p=0.0, is_causal=False)
if not skip_output_reshape:
out = (
out.transpose(1, 2).reshape(b, -1, heads * dim_head)
)
return outExplanation:
Log output: Output logs with version only on the first execution (using `_logged` attribute)
-
Tensor shape conversion:
Input: `(batch, seq, heads*dim_head)` or `(batch, heads, seq, dim_head)`
Convert for Flash-Attention: `(batch, seq, heads, dim_head)`
Output: `(batch, seq, heads*dim_head)`
-
transpose operation:
`(batch, seq, heads*dim_head)` → `view()` → `(batch, seq, heads, dim_head)`
`transpose(1, 2)` → `(batch, heads, seq, dim_head)`
`transpose(1, 2)` for `flash_attn_func` → `(batch, seq, heads, dim_head)`
output `transpose(1, 2)` → `(batch, heads, seq, dim_head)`
`reshape()` → `(batch, seq, heads*dim_head)`
error handling: fallback to PyTorch SDPA if Flash-Attention fails
mask processing: properly add batch and head dimensions
B. Priority logic (lines 650-674, existing code, no changes)
optimized_attention = attention_basic
# デフォルトでxformers(FA-3)を使用、SageAttentionはKJノードが動的に制御
print("[DEBUG] attention.py: Starting attention selection process")
print(f"[DEBUG] attention.py: model_management.xformers_enabled() = {model_management.xformers_enabled()}")
print(f"[DEBUG] attention.py: model_management.sage_attention_enabled() = {model_management.sage_attention_enabled()}")
print(f"[DEBUG] attention.py: model_management.flash_attention_enabled() = {model_management.flash_attention_enabled()}")
print(f"[DEBUG] attention.py: model_management.pytorch_attention_enabled() = {model_management.pytorch_attention_enabled()}")
if model_management.flash_attention_enabled():
logging.info("Using Flash Attention 2 (FA2)")
print("[DEBUG] attention.py: Selected Flash Attention 2 (FA2)")
optimized_attention = attention_flash
elif model_management.xformers_enabled():
logging.info("Using xformers attention (FA2/FA3)")
print("[DEBUG] attention.py: Selected xformers attention (FA2/FA3)")
optimized_attention = attention_xformers
elif model_management.sage_attention_enabled():
logging.info("Using sage attention")
print("[DEBUG] attention.py: Selected sage attention")
optimized_attention = attention_sage
elif model_management.pytorch_attention_enabled():
logging.info("Using pytorch attention")
print("[DEBUG] attention.py: Selected pytorch attention")
optimized_attention = attention_pytorch
else:
if args.use_split_cross_attention:
logging.info("Using split optimization for attention")
print("[DEBUG] attention.py: Selected split optimization for attention")
optimized_attention = attention_split
else:
logging.info("Using sub quadratic optimization for attention, if you have memory or speed issues try using: --use-split-cross-attention")
print("[DEBUG] attention.py: Selected sub quadratic optimization for attention")
optimized_attention = attention_sub_quad
print(f"[DEBUG] attention.py: Final optimized_attention = {optimized_attention.__name__}")priority:
Flash-Attention (direct) - when `--use-flash-attention` is specified, `model_management.flash_attention_enabled()` returns True
xformers - automatically select FA-3 → FA-2 via xformers, fully protecting existing functionality
SageAttention - when `--use-sage-attention` is specified, dynamic control via KJ nodes
PyTorch SDPA - When `--use-pytorch-cross-attention` is specified
Split/Sub-quad - Otherwise
Key Point:
This priority logic has not been changed at all
The xformers functionality is completely preserved
I only added the new `flash_attention_enabled()` check at the top level
C. attention_xformers function (lines 362-472, existing code, no changes)
def attention_xformers(q, k, v, heads, mask=None, attn_precision=None, skip_reshape=False, skip_output_reshape=False, transformer_options=None):
if skip_reshape:
b, _, _, dim_head = q.shape
else:
b, _, dim_head = q.shape
dim_head //= heads
q, k, v = map(
lambda t: t.view(b, -1, heads, dim_head),
(q, k, v),
)
if mask is not None:
pad = 8 - q.shape[1] % 8
mask_out = torch.empty([q.shape[0], q.shape[1], q.shape[1] + pad], dtype=q.dtype, device=q.device)
mask_out[:, :, :mask.shape[-1]] = mask
mask = mask_out[:, :, :mask.shape[-1]]
out = xformers.ops.memory_efficient_attention(q, k, v, attn_bias=mask)
if not skip_output_reshape:
out = (
out.view(b, -1, heads * dim_head)
)
return outExplanation:
Attention implementation via xformers
This function has not been changed at all
The FA-3 to FA-2 priority logic remains as is (lines 425-454, described below)
D. xformers FA-3 to FA-2 priority logic (lines 425-454, existing code, no changes)
# FA-3/FA-2 kernel prioritization logic in xformers
if XFORMERS_IS_AVAILABLE:
try:
import xformers.ops as xops
from xformers.ops.fmha.attn_bias import BlockDiagonalMask
# Check if FA kernels are available
fa2_available = any("flash" in str(k).lower() and "2" in str(k) for k in xops.fmha.common.all_kernels)
fa3_available = any("flash" in str(k).lower() and "3" in str(k) for k in xops.fmha.common.all_kernels)
if fa3_available:
# Prioritize FA-3 if available
fa3_kernels = [k for k in xops.fmha.common.all_kernels if "flash" in str(k).lower() and "3" in str(k)]
for kernel in fa3_kernels:
if kernel in xops.fmha.common.all_kernels:
xops.fmha.common.all_kernels.remove(kernel)
xops.fmha.common.all_kernels.insert(0, kernel)
logging.info(f"[xformers] FA3 kernels prioritized: {len(fa3_kernels)} kernels moved to front")
elif fa2_available:
# Prioritize FA-2 if FA-3 is not available
fa2_kernels = [k for k in xops.fmha.common.all_kernels if "flash" in str(k).lower() and "2" in str(k)]
for kernel in fa2_kernels:
if kernel in xops.fmha.common.all_kernels:
xops.fmha.common.all_kernels.remove(kernel)
xops.fmha.common.all_kernels.insert(0, kernel)
logging.info(f"[xformers] FA2 kernels prioritized: {len(fa2_kernels)} kernels moved to front")
except Exception as e:
logging.warning(f"[xformers] Could not prioritize FA kernels: {e}")Explanation:
FA-3 to FA-2 priority logic when using xformers
This functionality has not been changed at all
Automatic selection of FA-3/FA-2 via xformers is completely preserved
1.11.2025, Uploaded a file fixed some errors onSD1.5.
Background and Cause of the Problem
ComfyUI uses multiple attention functions. Flux uses attention_flash, while SD1.5 uses attention_basic and other functions. The wrap_attn decorator that wraps these functions adds an _inside_attn_wrapper keyword argument to prevent infinite loops.
However, the attention_flash function from xformers cannot accept the _inside_attn_wrapper keyword argument that ComfyUI adds. This keyword argument is not defined in the attention_flash function signature.
Initial Error Content
TypeError: attention_flash() got an unexpected keyword argument '_inside_attn_wrapper'The cause was that the wrap_attn decorator added _inside_attn_wrapper = True to kwargs and then called the attention_flash function. attention_flash is a function from the xformers package and does not define this keyword argument. The fix location is in the wrap_attn function at line 120-142 of attention.py.
Code Before Fix
def wrapper(*args, **kwargs):
remove_attn_wrapper_key = False
try:
if "_inside_attn_wrapper" not in kwargs:
transformer_options = kwargs.get("transformer_options", None)
remove_attn_wrapper_key = True
kwargs["_inside_attn_wrapper"] = True
if transformer_options is not None:
if "optimized_attention_override" in transformer_options:
return transformer_options["optimized_attention_override"](func, *args, **kwargs)
return func(*args, **kwargs)
finally:
if remove_attn_wrapper_key:
del kwargs["_inside_attn_wrapper"]The problem is that the _inside_attn_wrapper key is passed to attention_flash while still included in kwargs.
Code After Fix
def wrapper(*args, **kwargs):
remove_attn_wrapper_key = False
try:
if "_inside_attn_wrapper" not in kwargs:
transformer_options = kwargs.get("transformer_options", None)
remove_attn_wrapper_key = True
kwargs["_inside_attn_wrapper"] = True
if transformer_options is not None:
if "optimized_attention_override" in transformer_options:
if func.__name__ == "attention_flash":
kwargs.pop("_inside_attn_wrapper", None)
remove_attn_wrapper_key = False
return transformer_options["optimized_attention_override"](func, *args, **kwargs)
if func.__name__ == "attention_flash":
kwargs.pop("_inside_attn_wrapper", None)
return func(*args, **kwargs)
finally:
if remove_attn_wrapper_key and "_inside_attn_wrapper" in kwargs:
del kwargs["_inside_attn_wrapper"]
return wrapperFix Layer 1: Deletion Before Override Processing
Fix location: line 129-132
if func.__name__ == "attention_flash":
kwargs.pop("_inside_attn_wrapper", None)
remove_attn_wrapper_key = FalseExplanation: When Flux is executed, optimized_attention_override is set in transformer_options. This is a handler for custom attention implementations (FA-2, FA-3). Before calling the override handler, the _inside_attn_wrapper key is deleted.
When func.name detects "attention_flash", this layer is executed. kwargs.pop("_inside_attn_wrapper", None) returns and deletes the value if the key exists, or returns None if the key does not exist. No KeyError occurs.
The important point is setting remove_attn_wrapper_key = False. This causes the cleanup in the finally block to be skipped. This prevents errors from attempting to delete a key that has already been deleted.
Fix Layer 2: Deletion on Direct Call
Fix location: line 135-136
if func.__name__ == "attention_flash":
kwargs.pop("_inside_attn_wrapper", None)Explanation: When there is no override handler, the attention_flash function is called directly. The _inside_attn_wrapper key is also deleted in this case.
When func.name detects "attention_flash", kwargs.pop("_inside_attn_wrapper", None) is executed. If layer 1 has already deleted the key, layer 2 simply returns None. The second deletion attempt does nothing.
Fix Layer 3: Cleanup in Finally Block
Fix location: line 140-141
if remove_attn_wrapper_key and "_inside_attn_wrapper" in kwargs:
del kwargs["_inside_attn_wrapper"]Explanation: If remove_attn_wrapper_key is True, cleanup is executed in the finally block. However, only if the _inside_attn_wrapper key still exists in kwargs.
By using the in operator to check for existence, KeyError when the key does not exist is completely eliminated. With this check, cases where the key is already deleted (Flux) are skipped, and only cases where it is not deleted (SD1.5 normal attention) are executed.
Fix Layer 4: Overall Logic Structure
if "_inside_attn_wrapper" not in kwargs:
remove_attn_wrapper_key = True
kwargs["_inside_attn_wrapper"] = True
if transformer_options is not None:
if "optimized_attention_override" in transformer_options:
Layer 1: Delete before override (For Flux, change remove_attn_wrapper_key to False)
Layer 2: Delete on direct call (Does not reach here if Flux with override)
finally:
Layer 3: Cleanup in finally (Skip if remove_attn_wrapper_key is False)Structurally, different paths are executed for Flux and SD1.5.
Complete Flow During Flux Execution
User executes Flux prompt
diffusion_model calls attention
wrapper function of wrap_attn decorator is executed
Check func.name and confirm "attention_flash"
"_inside_attn_wrapper" not in kwargs is True at line 123
Set remove_attn_wrapper_key = True
Add kwargs["_inside_attn_wrapper"] = True
Retrieve transformer_options
Confirm optimized_attention_override exists
Layer 1: func.name == "attention_flash" is True
Delete with kwargs.pop("_inside_attn_wrapper", None)
Change remove_attn_wrapper_key = False (Important)
Call override handler and return
Reach finally
remove_attn_wrapper_key is False, so layer 3 is not executed
Complete without cleanup
Complete Flow During SD1.5 Execution
User executes SD1.5 prompt
diffusion_model calls attention
wrapper function of wrap_attn decorator is executed
Check func.name and confirm "attention_basic"
"_inside_attn_wrapper" not in kwargs is True at line 123
Set remove_attn_wrapper_key = True
Add kwargs["_inside_attn_wrapper"] = True
Retrieve transformer_options
optimized_attention_override does not exist or is None
Layer 1 is not executed (skipped by conditional branch)
Layer 2: func.name == "attention_basic" is False
Pass to attention_basic function while still including _inside_attn_wrapper key
attention_basic executes normally (accepts this key)
Reach finally
remove_attn_wrapper_key is True and "_inside_attn_wrapper" in kwargs is True
Layer 3: Execute del kwargs["_inside_attn_wrapper"]
Cleanup complete
Overall Sequence for Multiple Model Continuous Execution
Flux execution: Load FA-2, complete processing, skip finally with remove_attn_wrapper_key = False
SD1.5 execution: Use normal attention, provide _inside_attn_wrapper, delete in finally
Next Flux execution: Load FA-2 again, complete processing, skip finally with remove_attn_wrapper_key = False
Each model executes its independent path and does not interfere with others.
Summary of Fix Key Points
Identify function with func.name: "attention_flash" for Flux, "attention_basic" for SD1.5 etc
Delete only for attention_flash: Delete safely with pop(), no KeyError
Control with remove_attn_wrapper_key flag: False on override, True normally
Check existence in finally: Safely check with in operator
Each model is independent: Can execute Flux and SD1.5 continuously
Result
Flux and SD1.5 can now be executed continuously. The appropriate attention implementation is used for each model. FA-2 is loaded and executed normally for Flux, and normal attention operates for SD1.5.
4. custom_nodes/ComfyUI-KJNodes/nodes/model_optimization_nodes.py
Writing out all related code.
[2 tools called]
Note on adding SA/FA logging functionality in the latest version of ComfyUI-KJNodes
Background and Purpose
The processing method for SageAttention was changed in the latest version (ComfyUI-KJNodes-main). The previous version used a global patching method (direct assignment to `comfy_attention.optimized_attention`), but the latest version has switched to a per-model override method (`optimized_attention_override`).
However, the SA and FA log output processing was removed from `_patch_modules` in the latest version. Since the previous version output both SA and FA logs within `_patch_modules`, it was necessary to maintain this functionality in the latest version as well.
Why the new logging functionality was created
The latest version's `_patch_modules` only handles `patch_cublaslinear` and did not include SA and FA log output processing. To maintain the behavior of the previous version, SA and FA log output processing was added to `_patch_modules`.
This was done to maintain the same behavior (log output) even if the function changes (from global patching to `optimized_attention_override`).
Added code (complete version)
File: `ComfyUI/custom_nodes/comfyui-kjnodes/nodes/model_optimization_nodes.py`
1. Necessary imports and initialization section (lines 1-39)
import os
from comfy.ldm.modules import attention as comfy_attention
import logging
import torch
import importlib
import math
import datetime
import folder_paths
import comfy.model_management as mm
from comfy.cli_args import args
from comfy.ldm.modules.attention import wrap_attn, optimized_attention
import comfy.model_patcher
import comfy.utils
import comfy.sd
try:
from comfy_api.latest import io
v3_available = True
except ImportError:
v3_available = False
logging.warning("ComfyUI v3 node API not available, please update ComfyUI to access latest v3 nodes.")
sageattn_modes = ["disabled", "auto", "sageattn_qk_int8_pv_fp16_cuda", "sageattn_qk_int8_pv_fp16_triton", "sageattn_qk_int8_pv_fp8_cuda", "sageattn_qk_int8_pv_fp8_cuda++", "sageattn3", "sageattn3_per_block_mean"]
_initialized = False
_original_functions = {}
_sage_attention_active = False
if not _initialized:
_original_functions["orig_attention"] = comfy_attention.optimized_attention
_original_functions["original_patch_model"] = comfy.model_patcher.ModelPatcher.patch_model
_original_functions["original_load_lora_for_models"] = comfy.sd.load_lora_for_models
try:
_original_functions["original_qwen_forward"] = comfy.ldm.qwen_image.model.Attention.forward
except:
pass
_initialized = True2. Entire get_sage_func function (lines 41-110)
def get_sage_func(sage_attention, allow_compile=False):
logging.info(f"Using sage attention mode: {sage_attention}")
from sageattention import sageattn
if sage_attention == "auto":
def sage_func(q, k, v, is_causal=False, attn_mask=None, tensor_layout="NHD"):
return sageattn(q, k, v, is_causal=is_causal, attn_mask=attn_mask, tensor_layout=tensor_layout)
elif sage_attention == "sageattn_qk_int8_pv_fp16_cuda":
from sageattention import sageattn_qk_int8_pv_fp16_cuda
def sage_func(q, k, v, is_causal=False, attn_mask=None, tensor_layout="NHD"):
return sageattn_qk_int8_pv_fp16_cuda(q, k, v, is_causal=is_causal, attn_mask=attn_mask, pv_accum_dtype="fp32", tensor_layout=tensor_layout)
elif sage_attention == "sageattn_qk_int8_pv_fp16_triton":
from sageattention import sageattn_qk_int8_pv_fp16_triton
def sage_func(q, k, v, is_causal=False, attn_mask=None, tensor_layout="NHD"):
return sageattn_qk_int8_pv_fp16_triton(q, k, v, is_causal=is_causal, attn_mask=attn_mask, tensor_layout=tensor_layout)
elif sage_attention == "sageattn_qk_int8_pv_fp8_cuda":
from sageattention import sageattn_qk_int8_pv_fp8_cuda
def sage_func(q, k, v, is_causal=False, attn_mask=None, tensor_layout="NHD"):
return sageattn_qk_int8_pv_fp8_cuda(q, k, v, is_causal=is_causal, attn_mask=attn_mask, pv_accum_dtype="fp32+fp32", tensor_layout=tensor_layout)
elif sage_attention == "sageattn_qk_int8_pv_fp8_cuda++":
from sageattention import sageattn_qk_int8_pv_fp8_cuda
def sage_func(q, k, v, is_causal=False, attn_mask=None, tensor_layout="NHD"):
return sageattn_qk_int8_pv_fp8_cuda(q, k, v, is_causal=is_causal, attn_mask=attn_mask, pv_accum_dtype="fp32+fp16", tensor_layout=tensor_layout)
elif "sageattn3" in sage_attention:
from sageattn3 import sageattn3_blackwell
if sage_attention == "sageattn3_per_block_mean":
def sage_func(q, k, v, is_causal=False, attn_mask=None, **kwargs):
return sageattn3_blackwell(q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2), is_causal=is_causal, attn_mask=attn_mask, per_block_mean=True).transpose(1, 2)
else:
def sage_func(q, k, v, is_causal=False, attn_mask=None, **kwargs):
return sageattn3_blackwell(q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2), is_causal=is_causal, attn_mask=attn_mask, per_block_mean=False).transpose(1, 2)
if not allow_compile:
sage_func = torch.compiler.disable()(sage_func)
@wrap_attn
def attention_sage(q, k, v, heads, mask=None, attn_precision=None, skip_reshape=False, skip_output_reshape=False, **kwargs):
in_dtype = v.dtype
if q.dtype == torch.float32 or k.dtype == torch.float32 or v.dtype == torch.float32:
q, k, v = q.to(torch.float16), k.to(torch.float16), v.to(torch.float16)
if skip_reshape:
b, _, _, dim_head = q.shape
tensor_layout="HND"
else:
b, _, dim_head = q.shape
dim_head //= heads
q, k, v = map(
lambda t: t.view(b, -1, heads, dim_head),
(q, k, v),
)
tensor_layout="NHD"
if mask is not None:
# add a batch dimension if there isn't already one
if mask.ndim == 2:
mask = mask.unsqueeze(0)
# add a heads dimension if there isn't already one
if mask.ndim == 3:
mask = mask.unsqueeze(1)
out = sage_func(q, k, v, attn_mask=mask, is_causal=False, tensor_layout=tensor_layout).to(in_dtype)
if tensor_layout == "HND":
if not skip_output_reshape:
out = (
out.transpose(1, 2).reshape(b, -1, heads * dim_head)
)
else:
if skip_output_reshape:
out = out.transpose(1, 2)
else:
out = out.reshape(b, -1, heads * dim_head)
return out
return attention_sage3. Entire _patch_modules method of the BaseLoaderKJ class (lines 117-191)
class BaseLoaderKJ:
original_linear = None
cublas_patched = False
@torch.compiler.disable()
def _patch_modules(self, patch_cublaslinear, sage_attention):
from comfy.ops import disable_weight_init, CastWeightBiasOp, cast_bias_weight
if sage_attention != "disabled":
# SAログ機能
try:
import sageattention
sage_version = None
try:
sage_version = sageattention.__version__
except AttributeError:
try:
import importlib.metadata
sage_version = importlib.metadata.version("sageattention")
except Exception:
sage_version = None
if sage_version and sage_version != "unknown":
print(f"Patching comfy attention to use SageAttention {sage_version}")
else:
print("Patching comfy attention to use sageattn")
except:
print("Patching comfy attention to use sageattn")
else:
# FAログ機能(SAがdisabledの場合のみ)
# Detect if Flash-Attention is being restored (only if SA is disabled)
if mm.flash_attention_enabled():
# Check Flash-Attention version and determine FA-3 or FA-2
if hasattr(mm, 'FLASH_ATTN_VERSION') and mm.FLASH_ATTN_VERSION and mm.FLASH_ATTN_VERSION != "unknown":
try:
version_parts = mm.FLASH_ATTN_VERSION.split('.')
major_version = int(version_parts[0])
if major_version >= 3:
flash_attn_type = "FA-3"
else:
flash_attn_type = "FA-2"
print(f"[ComfyUI] Using {flash_attn_type} (Flash-Attention {mm.FLASH_ATTN_VERSION}) direct")
except Exception:
if hasattr(mm, 'FLASH_ATTN_TYPE') and mm.FLASH_ATTN_TYPE:
print(f"[ComfyUI] Using {mm.FLASH_ATTN_TYPE} (Flash-Attention {mm.FLASH_ATTN_VERSION}) direct")
else:
print(f"[ComfyUI] Using Flash-Attention {mm.FLASH_ATTN_VERSION} direct")
else:
print(f"[ComfyUI] Using Flash-Attention direct")
if patch_cublaslinear:
if not BaseLoaderKJ.cublas_patched:
BaseLoaderKJ.original_linear = disable_weight_init.Linear
try:
from cublas_ops import CublasLinear
except ImportError:
raise Exception("Can't import 'torch-cublas-hgemm', install it from here https://github.com/aredden/torch-cublas-hgemm")
class PatchedLinear(CublasLinear, CastWeightBiasOp):
def reset_parameters(self):
pass
def forward_comfy_cast_weights(self, input):
weight, bias = cast_bias_weight(self, input)
return torch.nn.functional.linear(input, weight, bias)
def forward(self, *args, **kwargs):
if self.comfy_cast_weights:
return self.forward_comfy_cast_weights(*args, **kwargs)
else:
return super().forward(*args, **kwargs)
disable_weight_init.Linear = PatchedLinear
BaseLoaderKJ.cublas_patched = True
else:
if BaseLoaderKJ.cublas_patched:
disable_weight_init.Linear = BaseLoaderKJ.original_linear
BaseLoaderKJ.cublas_patched = False4. Entire PathchSageAttentionKJ class (lines 193-234)
from comfy.patcher_extension import CallbacksMP
class PathchSageAttentionKJ(BaseLoaderKJ):
@classmethod
def INPUT_TYPES(s):
return {"required": {
"model": ("MODEL",),
"sage_attention": (sageattn_modes, {"default": False, "tooltip": "Global patch comfy attention to use sageattn, once patched to revert back to normal you would need to run this node again with disabled option."}),
},
"optional": {
"allow_compile": ("BOOLEAN", {"default": False, "tooltip": "Allow the use of torch.compile for the sage attention function, requires latest sageattn 2.2.0 or higher."})
}
}
RETURN_TYPES = ("MODEL", )
FUNCTION = "patch"
DESCRIPTION = "Experimental node for patching attention mode. This doesn't use the model patching system and thus can't be disabled without running the node again with 'disabled' option."
EXPERIMENTAL = True
CATEGORY = "KJNodes/experimental"
def patch(self, model, sage_attention, allow_compile=False):
model_clone = model.clone()
# _patch_modulesを呼んでSA/FAログを出力(前版と同じ動作)
@torch.compiler.disable()
def patch_attention_enable(model):
self._patch_modules(False, sage_attention)
@torch.compiler.disable()
def patch_attention_disable(model):
self._patch_modules(False, "disabled")
model_clone.add_callback(CallbacksMP.ON_PRE_RUN, patch_attention_enable)
model_clone.add_callback(CallbacksMP.ON_CLEANUP, patch_attention_disable)
if sage_attention != "disabled":
# SAが有効な場合はoptimized_attention_overrideを設定
new_attention = get_sage_func(sage_attention, allow_compile=allow_compile)
def attention_override_sage(func, *args, **kwargs):
return new_attention.__wrapped__(*args, **kwargs)
# attention override
model_clone.model_options["transformer_options"]["optimized_attention_override"] = attention_override_sage
return model_clone,Its meaning
Meaning of the SA logging function
If `sage_attention` is not `"disabled"`, it detects the SageAttention version and outputs a log. If the version is successfully retrieved, it outputs "Patching comfy attention to use SageAttention {version}"; if it cannot be retrieved, it outputs "Patching comfy attention to use sageattn".
Meaning of the FA logging function
If `sage_attention` is `"disabled"`, it checks whether Flash-Attention is enabled, and if so, detects the version and outputs a log. Versions 3 and above are displayed as "FA-3", and lower versions as "FA-2". If the version cannot be retrieved, it outputs "Flash-Attention direct".
Meaning of calling `_patch_modules` via a callback
In the `PathchSageAttentionKJ` class, by setting `ON_PRE_RUN` and `ON_CLEANUP` callbacks to call `_patch_modules`, logs are output before model execution and during cleanup. This allows maintaining the same behavior as the previous version (logs are output at startup and during model execution).
Overall design philosophy
Even if the function changes (e.g., from a global patch to `optimized_attention_override`), the same behavior (log output) is maintained. By outputting logs within `_patch_modules`, the logging functionality remains unaffected even if the SA processing method changes.
Important points
By outputting logs within `_patch_modules`, the logging functionality is maintained even if the SA processing method changes
The `PathchSageAttentionKJ` class inherits from `BaseLoaderKJ` and calls `self._patch_modules`, maintaining the same structure as the previous version
When SA is enabled, `optimized_attention_override` is also set, but log output is performed within `_patch_modules`
To maintain the same behavior as the previous version, the design of outputting logs within `_patch_modules` is preserved
Reproduction steps
Check the latest version of ComfyUI-KJNodes-main
Add the code for the SA logging and FA logging functions mentioned above into the `_patch_modules` method of `model_optimization_nodes.py`
Implement the `PatchSageAttentionKJ` class to inherit from `BaseLoaderKJ` and call `self._patch_modules` as a callback
When SA is enabled, also set `optimized_attention_override`, but perform log output within `_patch_modules`
Verify that it behaves the same as the previous version (logs are output at startup and during model execution)
Save the following as a record of the code creation at the initial stage.
Purpose: Improve logging during dynamic switching in KJ nodes
A. Add global variables (add 24 lines)
_sage_attention_active = False # Track if SageAttention is currently activeExplanation:
Flag to track whether SageAttention is currently active
Used to determine whether to output FA logs during restoration
B. Version display when patching SageAttention (change lines 94-117)
Before change:
if sage_attention != "disabled":
print("Patching comfy attention to use sageattn")
from sageattention import sageattn
def set_sage_func(sage_attention):
if sage_attention == "auto":
def func(q, k, v, is_causal=False, attn_mask=None, tensor_layout="NHD"):
return sageattn(q, k, v, is_causal=is_causal, attn_mask=attn_mask, tensor_layout=tensor_layout)
return funcAfter change:
if sage_attention != "disabled":
global _sage_attention_active
_sage_attention_active = True
# Detect SageAttention version
try:
import sageattention
sage_version = None
try:
sage_version = sageattention.__version__
except AttributeError:
try:
import importlib.metadata
sage_version = importlib.metadata.version("sageattention")
except Exception:
sage_version = None
if sage_version and sage_version != "unknown":
print(f"Patching comfy attention to use SageAttention {sage_version}")
else:
print("Patching comfy attention to use sageattn")
except:
print("Patching comfy attention to use sageattn")
from sageattention import sageattn
def set_sage_func(sage_attention):
if sage_attention == "auto":
def func(q, k, v, is_causal=False, attn_mask=None, tensor_layout="NHD"):
return sageattn(q, k, v, is_causal=is_causal, attn_mask=attn_mask, tensor_layout=tensor_layout)
return funcExplanation:
Record that SA has become active with `_sage_attention_active = True`
-
Attempt to retrieve the SA version in 3 steps:
Directly retrieve the `sageattention.version` attribute
Retrieve from package metadata using `importlib.metadata.version("sageattention")`
Display a generic message if it fails
On success, display something like `Patching comfy attention to use SageAttention 2.2.0+cu128torch2.8.0.post3`
C. Added Flash-Attention logging during Restoring (lines 180-204 changed)
Before change:
else:
print("Restoring initial comfy attention")
comfy_attention.optimized_attention = _original_functions.get("orig_attention")
comfy.ldm.hunyuan_video.model.optimized_attention = _original_functions.get("orig_attention")
comfy.ldm.flux.math.optimized_attention = _original_functions.get("orig_attention")
comfy.ldm.genmo.joint_model.asymm_models_joint.optimized_attention = _original_functions.get("orig_attention")
comfy.ldm.cosmos.blocks.optimized_attention = _original_functions.get("orig_attention")
comfy.ldm.wan.model.optimized_attention = _original_functions.get("orig_attention")
try:
comfy.ldm.qwen_image.model.Attention.forward = _original_functions.get("original_qwen_forward")
except:
passAfter change:
else:
global _sage_attention_active
print("Restoring initial comfy attention")
# Only log Flash-Attention if SA was previously active (not just switching between generations)
orig_attn = _original_functions.get("orig_attention")
if _sage_attention_active and orig_attn == comfy_attention.attention_flash:
# Check Flash-Attention version
if hasattr(mm, 'FLASH_ATTN_VERSION') and mm.FLASH_ATTN_VERSION and mm.FLASH_ATTN_VERSION != "unknown":
print(f"[ComfyUI] Using {mm.FLASH_ATTN_TYPE} (Flash-Attention {mm.FLASH_ATTN_VERSION}) direct")
else:
print(f"[ComfyUI] Using Flash-Attention direct")
_sage_attention_active = False # Reset the flag after restoring
comfy_attention.optimized_attention = orig_attn
comfy.ldm.hunyuan_video.model.optimized_attention = orig_attn
comfy.ldm.flux.math.optimized_attention = orig_attn
comfy.ldm.genmo.joint_model.asymm_models_joint.optimized_attention = orig_attn
comfy.ldm.cosmos.blocks.optimized_attention = orig_attn
comfy.ldm.wan.model.optimized_attention = orig_attn
try:
comfy.ldm.qwen_image.model.Attention.forward = _original_functions.get("original_qwen_forward")
except:
passExplanation:
Check `_sage_attention_active` flag: Output FA log only if SA was previously active
Check `orig_attn == comfy_attention.attention_flash`: Only if initial attention is FA
Retrieve and display FA version information from `model_management`
Reset flag with `_sage_attention_active = False`
This ensures logs are displayed correctly for each tile during Tiled generation
D. set_sage_func function (lines 118-171, existing code, no changes)
def set_sage_func(sage_attention):
if sage_attention == "auto":
def func(q, k, v, is_causal=False, attn_mask=None, tensor_layout="NHD"):
return sageattn(q, k, v, is_causal=is_causal, attn_mask=attn_mask, tensor_layout=tensor_layout)
return func
elif sage_attention == "sageattn_qk_int8_pv_fp16_cuda":
def func(q, k, v, is_causal=False, attn_mask=None, tensor_layout="NHD"):
return sageattn(q, k, v, tensor_layout=tensor_layout, is_causal=is_causal, attn_mask=attn_mask, qk_dtype="int8")
return func
elif sage_attention == "sageattn_qk_int8_pv_fp8_cuda":
def func(q, k, v, is_causal=False, attn_mask=None, tensor_layout="NHD"):
return sageattn(q, k, v, tensor_layout=tensor_layout, is_causal=is_causal, attn_mask=attn_mask, qk_dtype="int8", pv_dtype="fp8")
return func
else:
raise ValueError(f"Unknown sage_attention mode: {sage_attention}")Explanation:
Generate functions corresponding to each SageAttention mode
`auto`: Default mode
`sageattn_qk_int8_pv_fp16_cuda`: QK int8 quantization, PV fp16
`sageattn_qk_int8_pv_fp8_cuda`: QK int8 quantization, PV fp8 quantization
This function has not been changed at all
SA/FA logging feature for KJ Nodes added on December 12, 2025 - Additional code and complete explanation
[1. Imports at the beginning of the file (around line 10)]
import comfy.model_management as mmLocation: Beginning of the file (along with other import statements)
Note: No need to add if already exists
[2. SA/FA logging feature within _patch_modules method]
Insertion point: Inside the `_patch_modules` method, immediately after `from comfy.ops import`, and before `if patch_cublaslinear:`
if sage_attention != "disabled":
# SAログ機能
try:
import sageattention
sage_version = None
try:
sage_version = sageattention.__version__
except AttributeError:
try:
import importlib.metadata
sage_version = importlib.metadata.version("sageattention")
except Exception:
sage_version = None
if sage_version and sage_version != "unknown":
print(f"Patching comfy attention to use SageAttention {sage_version}")
else:
print("Patching comfy attention to use sageattn")
except:
print("Patching comfy attention to use sageattn")
else:
# FAログ機能(SAがdisabledの場合のみ)
# Detect if Flash-Attention is being restored (only if SA is disabled)
if mm.flash_attention_enabled():
# Check Flash-Attention version and determine FA-3 or FA-2
if hasattr(mm, 'FLASH_ATTN_VERSION') and mm.FLASH_ATTN_VERSION and mm.FLASH_ATTN_VERSION != "unknown":
try:
version_parts = mm.FLASH_ATTN_VERSION.split('.')
major_version = int(version_parts[0])
if major_version >= 3:
flash_attn_type = "FA-3"
else:
flash_attn_type = "FA-2"
print(f"[ComfyUI] Using {flash_attn_type} (Flash-Attention {mm.FLASH_ATTN_VERSION}) direct")
except Exception:
if hasattr(mm, 'FLASH_ATTN_TYPE') and mm.FLASH_ATTN_TYPE:
print(f"[ComfyUI] Using {mm.FLASH_ATTN_TYPE} (Flash-Attention {mm.FLASH_ATTN_VERSION}) direct")
else:
print(f"[ComfyUI] Using Flash-Attention {mm.FLASH_ATTN_VERSION} direct")
else:
print(f"[ComfyUI] Using Flash-Attention direct")Complete addition guide
Step 1: Open the file
D:\USERFILES\ComfyUI\ComfyUI\custom_nodes\comfyui-kjnodes\nodes\model_optimization_nodes.pyStep 2: Verify and add import statements (around line 10)
Verification: Check the following at the beginning of the file
import comfy.model_management as mmHow to add:
Add only if it does not exist
Place at the same level as other import statements (e.g., `import os`, `import torch`)
Recommended position: Around line 10 (e.g., after `import folder_paths`)
Example:
import folder_paths
import comfy.model_management as mm # ← ここに追加
from comfy.cli_args import argsStep 3: Locate the _patch_modules method
Search: Look for the following line
def _patch_modules(self, patch_cublaslinear, sage_attention):Verification: Check the structure within the method
def _patch_modules(self, patch_cublaslinear, sage_attention):
from comfy.ops import disable_weight_init, CastWeightBiasOp, cast_bias_weight
# ← ここにSA/FAログ機能を挿入
if patch_cublaslinear:
# ... 既存のコードStep 4: Insert the SA/FA logging function code
Insertion point:
Immediately after `from comfy.ops import disable_weight_init, CastWeightBiasOp, cast_bias_weight`
Immediately before `if patch_cublaslinear:`
Indentation:
Since it is inside the method, use 8 spaces (2 levels) of indentation
`if sage_attention != "disabled":` should be 8 spaces
The code within it has 12 spaces (3 levels).
Complete insertion example:
def _patch_modules(self, patch_cublaslinear, sage_attention):
from comfy.ops import disable_weight_init, CastWeightBiasOp, cast_bias_weight
# ← ここから追加開始
if sage_attention != "disabled":
# SAログ機能
try:
import sageattention
sage_version = None
try:
sage_version = sageattention.__version__
except AttributeError:
try:
import importlib.metadata
sage_version = importlib.metadata.version("sageattention")
except Exception:
sage_version = None
if sage_version and sage_version != "unknown":
print(f"Patching comfy attention to use SageAttention {sage_version}")
else:
print("Patching comfy attention to use sageattn")
except:
print("Patching comfy attention to use sageattn")
else:
# FAログ機能(SAがdisabledの場合のみ)
# Detect if Flash-Attention is being restored (only if SA is disabled)
if mm.flash_attention_enabled():
# Check Flash-Attention version and determine FA-3 or FA-2
if hasattr(mm, 'FLASH_ATTN_VERSION') and mm.FLASH_ATTN_VERSION and mm.FLASH_ATTN_VERSION != "unknown":
try:
version_parts = mm.FLASH_ATTN_VERSION.split('.')
major_version = int(version_parts[0])
if major_version >= 3:
flash_attn_type = "FA-3"
else:
flash_attn_type = "FA-2"
print(f"[ComfyUI] Using {flash_attn_type} (Flash-Attention {mm.FLASH_ATTN_VERSION}) direct")
except Exception:
if hasattr(mm, 'FLASH_ATTN_TYPE') and mm.FLASH_ATTN_TYPE:
print(f"[ComfyUI] Using {mm.FLASH_ATTN_TYPE} (Flash-Attention {mm.FLASH_ATTN_VERSION}) direct")
else:
print(f"[ComfyUI] Using Flash-Attention {mm.FLASH_ATTN_VERSION} direct")
else:
print(f"[ComfyUI] Using Flash-Attention direct")
# ← ここまで追加終了
if patch_cublaslinear:
# ... 既存のコードが続くExplanation of operation
SA logging function (when SageAttention is enabled)
Executed when `sage_attention != "disabled"`
Import `sageattention`
-
Attempt to retrieve version:
`sageattention.version`
If it fails, use `importlib.metadata.version("sageattention")`
-
Log output:
Version retrieval successful: `"Patching comfy attention to use SageAttention {version}"`
Failure: `"Patching comfy attention to use sageattn"`
FA logging function (when SageAttention is disabled)
Executed when `sage_attention == "disabled"`
Verify FA is enabled with `mm.flash_attention_enabled()`
-
Version determination:
Retrieve from `mm.FLASH_ATTN_VERSION`
Determine FA-3/FA-2 based on major version
-
Log output:
FA-3/FA-2 detection successful: `"[ComfyUI] Using FA-3 (Flash-Attention {version}) direct"`
Detection failed: `"[ComfyUI] Using Flash-Attention {version} direct"`
Unknown version: `"[ComfyUI] Using Flash-Attention direct"`
Notes
Indentation: Indentation is important in Python. Maintain 8 spaces (within the method)
Importing `mm`: `import comfy.model_management as mm` is required at the top of the file
Insertion point: Immediately before `if patch_cublaslinear:`. Do not break existing code
Conditional branching: Maintain the structure of `if sage_attention != "disabled":` and `else:`
Verification method
After adding, check the following:
Are there any syntax errors? (Check in the editor)
Is the indentation correct?
Is `mm` imported?
Is the code inserted before `if patch_cublaslinear:`?
That is all.
Operation flow details
Case 1: No xformers + only `--use-flash-attention`
Startup log:
Flash-Attention 2.8.2 (FA-2) successfully loaded
[DEBUG] attention.py: Starting attention selection process
[DEBUG] attention.py: model_management.xformers_enabled() = False
[DEBUG] attention.py: model_management.sage_attention_enabled() = False
[DEBUG] attention.py: model_management.flash_attention_enabled() = True
[DEBUG] attention.py: model_management.pytorch_attention_enabled() = False
Using Flash Attention 2 (FA2)
[DEBUG] attention.py: Selected Flash Attention 2 (FA2)
[DEBUG] attention.py: Final optimized_attention = attention_flashGeneration log:
[ComfyUI] Using FA-2 (Flash-Attention 2.8.2) direct
0%| | 0/20 [00:00<?, ?it/s]
10%|████████████▌ | 2/20 [00:01<00:15, 1.14it/s]
20%|█████████████████████████ | 4/20 [00:03<00:14, 1.13it/s]
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 20/20 [00:17<00:00, 1.14it/s]Explanation:
FA-2 is loaded directly (xformers not required)
Versioned log is displayed only on the first execution
Logs are not displayed for subsequent generations (controlled by the `_logged` flag)
Case 2: With xformers (as before)
Startup logs:
xformers version: 0.0.32.post2
[DEBUG] attention.py: Starting attention selection process
[DEBUG] attention.py: model_management.xformers_enabled() = True
[DEBUG] attention.py: model_management.sage_attention_enabled() = False
[DEBUG] attention.py: model_management.flash_attention_enabled() = False
[DEBUG] attention.py: model_management.pytorch_attention_enabled() = False
Using xformers attention (FA2/FA3)
[DEBUG] attention.py: Selected xformers attention (FA2/FA3)
[DEBUG] attention.py: Final optimized_attention = attention_xformers
[xformers] FA2 kernels prioritized: 2 kernels moved to frontGeneration logs:
[xformers] memory_efficient_attention: selected kernel = fa2F@2.8.2.post1
0%| | 0/20 [00:00<?, ?it/s]
10%|████████████▌ | 2/20 [00:01<00:15, 1.16it/s]
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 20/20 [00:17<00:00, 1.15it/s]Explanation:
FA-2 is used via xformers
FA-3 is prioritized if available
Existing functionality is fully protected
Case 3: `--use-flash-attention` + `--use-sage-attention` + KJ nodes
Startup logs:
SageAttention 2.2.0+cu128torch2.8.0.post3 successfully loaded
Flash-Attention 2.8.2 (FA-2) successfully loaded
[DEBUG] attention.py: Starting attention selection process
[DEBUG] attention.py: model_management.xformers_enabled() = False
[DEBUG] attention.py: model_management.sage_attention_enabled() = True
[DEBUG] attention.py: model_management.flash_attention_enabled() = True
[DEBUG] attention.py: model_management.pytorch_attention_enabled() = False
Using Flash Attention 2 (FA2)
[DEBUG] attention.py: Selected Flash Attention 2 (FA2)
[DEBUG] attention.py: Final optimized_attention = attention_flashExplanation:
Both SA and FA are loaded
Initial attention is FA-2 (due to priority logic)
Dynamically switchable via KJ nodes
Logs during generation (SA on):
Patching comfy attention to use SageAttention 2.2.0+cu128torch2.8.0.post3
Patching torch settings: lowvram_model_memory = 0.00 GB, total_vram = 22.77 GB, torch.cuda.max_memory_reserved = 5.39 GB
0%| | 0/20 [00:00<?, ?it/s]
10%|████████████▌ | 2/20 [00:01<00:14, 1.21it/s]
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 20/20 [00:16<00:00, 1.20it/s]
Restoring initial comfy attention
[ComfyUI] Using FA-2 (Flash-Attention 2.8.2) direct
Patching torch settings: lowvram_model_memory = 0.00 GB, total_vram = 22.77 GB, torch.cuda.max_memory_reserved = 5.39 GBExplanation:
KJ nodes patch to SA (versioned logs)
Generation process with SA
Restored to initial attention (FA-2) after generation completes
FA logs are displayed due to the `_sage_attention_active` flag
Log of the next tile generation:
Patching comfy attention to use SageAttention 2.2.0+cu128torch2.8.0.post3
Patching torch settings: lowvram_model_memory = 0.00 GB, total_vram = 22.77 GB, torch.cuda.max_memory_reserved = 5.39 GB
0%| | 0/20 [00:00<?, ?it/s]
10%|████████████▌ | 2/20 [00:01<00:14, 1.21it/s]
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 20/20 [00:16<00:00, 1.20it/s]
Restoring initial comfy attention
[ComfyUI] Using FA-2 (Flash-Attention 2.8.2) direct
Patching torch settings: lowvram_model_memory = 0.00 GB, total_vram = 22.77 GB, torch.cuda.max_memory_reserved = 5.39 GBExplanation:
The same log is repeated for each tile in Tiled generation
The switching of SA→FA→SA→FA is clearly visualized
Details of technical features
A. Full protection of xformers functionality
Protected elements:
`attention_xformers` function (lines 362-472)
FA-3 to FA-2 prioritization logic (lines 425-454)
xformers kernel logging functionality
`XFORMERS_IS_AVAILABLE` and `XFORMERS_ENABLED_VAE` flags
`xformers_enabled()` and `xformers_enabled_vae()` helper functions
Verification method:
When starting in an environment with xformers installed, it displays `Using xformers attention (FA2/FA3)` as usual
The `[xformers] FA2 kernels prioritized` log is displayed correctly
Automatic selection of FA-2/FA-3 via xformers works
B. Mechanism for automatic version detection
Flash-Attention version detection:
FLASH_ATTN_VERSION = flash_attn.__version__ # 例: "2.8.2" または "3.0.1"
version_parts = FLASH_ATTN_VERSION.split('.')
major_version = int(version_parts[0])
if major_version >= 3:
FLASH_ATTN_TYPE = "FA-3"
else:
FLASH_ATTN_TYPE = "FA-2"SageAttention version detection:
# 方法1: __version__属性
sage_version = sageattention.__version__
# 方法2: importlib.metadata(方法1失敗時)
import importlib.metadata
sage_version = importlib.metadata.version("sageattention")
# 方法3: unknown(方法1・2失敗時)
sage_version = "unknown"Output example:
FA-2: `Flash-Attention 2.8.2 (FA-2) successfully loaded`
FA-3: `Flash-Attention 3.0.1 (FA-3) successfully loaded`
SA: `SageAttention 2.2.0+cu128torch2.8.0.post3 successfully loaded`
C. Dynamic switching mechanism
KJ node callback mechanism:
# _patch_modules関数内
if sage_attention != "disabled":
_sage_attention_active = True
# SAにパッチ
comfy_attention.optimized_attention = set_sage_func(sage_attention)
else:
# イニシャルアテンションに復元
orig_attn = _original_functions.get("orig_attention")
if _sage_attention_active and orig_attn == comfy_attention.attention_flash:
# FAログを表示
print(f"[ComfyUI] Using {mm.FLASH_ATTN_TYPE} (Flash-Attention {mm.FLASH_ATTN_VERSION}) direct")
_sage_attention_active = False
comfy_attention.optimized_attention = orig_attnState transition:
At startup: `optimized_attention = attention_flash` (initial)
SA on: `optimized_attention = set_sage_func("auto")` (patch)
SA off: `optimized_attention = attention_flash` (restore + log)
D. Error handling and fallback
Error handling during Flash-Attention execution:
try:
out = flash_attn_func(
q.transpose(1, 2),
k.transpose(1, 2),
v.transpose(1, 2),
dropout_p=0.0,
causal=False,
)
out = out.transpose(1, 2)
except Exception as e:
logging.warning(f"Flash Attention failed, using default SDPA: {e}")
out = torch.nn.functional.scaled_dot_product_attention(q, k, v, attn_mask=mask, dropout_p=0.0, is_causal=False)Error handling during import:
try:
import flash_attn
from flash_attn import flash_attn_func
FLASH_IS_AVAILABLE = True
except ImportError as e:
logging.warning(f"--use-flash-attention specified but flash-attn import failed: {e}")
logging.warning("Flash-Attention will not be available.")
# FLASH_IS_AVAILABLE = False のまま継続E. Considerations for OOM prevention
Memory-efficient attention selection:
Flash-Attention: Highest memory efficiency (O(N) memory usage)
xformers: High memory efficiency (auto-selects FA-3/FA-2)
SageAttention: Reduced memory usage via quantization
PyTorch SDPA: Standard memory efficiency
Split/Sub-quad: Low memory efficiency but stable
Memory management by KJ node:
Patching torch settings: lowvram_model_memory = 0.00 GB, total_vram = 22.77 GB, torch.cuda.max_memory_reserved = 5.39 GBUsage details
Command line options
Flash-Attention only:
cd D:\USERFILES\ComfyUI
$env:PYTHONPATH="D:\USERFILES\ComfyUI\ComfyUI"
.\python_embeded\python.exe -s ComfyUI\main.py --use-flash-attentionSageAttention only:
cd D:\USERFILES\ComfyUI
$env:PYTHONPATH="D:\USERFILES\ComfyUI\ComfyUI"
.\python_embeded\python.exe -s ComfyUI\main.py --use-sage-attentionSpecify both (dynamic switching with KJ nodes):
cd D:\USERFILES\ComfyUI
$env:PYTHONPATH="D:\USERFILES\ComfyUI\ComfyUI"
.\python_embeded\python.exe -s ComfyUI\main.py --use-flash-attention --use-sage-attentionxformers (as usual, no options required):
cd D:\USERFILES\ComfyUI
$env:PYTHONPATH="D:\USERFILES\ComfyUI\ComfyUI"
.\python_embeded\python.exe -s ComfyUI\main.pyDisable xformers + Flash-Attention:
cd D:\USERFILES\ComfyUI
$env:PYTHONPATH="D:\USERFILES\ComfyUI\ComfyUI"
.\python_embeded\python.exe -s ComfyUI\main.py --disable-xformers --use-flash-attentionHow to use with KJ nodes
Node configuration example:
[KJNodes Model Optimization]
├─ sage_attention: auto / disabled
├─ pab_settings: auto / disabled
└─ lowvram: 0.00 GBWhen SageAttention is enabled:
`sage_attention = "auto"`: Use SA
Log: `Patching comfy attention to use SageAttention 2.2.0+cu128torch2.8.0.post3`
When SageAttention is disabled:
`sage_attention = "disabled"`: Use initial attention (FA-2)
Log: `Restoring initial comfy attention` + `[ComfyUI] Using FA-2 (Flash-Attention 2.8.2) direct`
Advantages of the implementation
1. Full visibility
Users can always check which kernel is being used
Version information is also clearly displayed (FA-2/FA-3, SA version)
Switching logs are displayed for each tile during tiled generation
2. xformers compatibility
Existing xformers functionality remains completely unchanged
Works as before in environments where xformers is installed
FA-3 to FA-2 priority logic is also fully protected
3. Flexibility
SA/FA/xformers can be combined freely
Dynamic switching possible via KJ nodes
Selectable at startup via command line options
4. Stability
Appropriate fallback functionality in case of errors
Can continue even if import fails
Operation continues even if version retrieval fails
5. OOM Prevention
Memory-efficient attention selection
Compatibility with custom nodes
Integration with memory management features via KJ nodes
Summary
With this implementation, the following has been achieved in ComfyUI:
FA-3/FA-2 can be used directly even without xformers
Existing xformers functionality is fully protected
Supports dynamic switching with SageAttention
Clear version information and log display
Error handling and fallback functionality
Considerations for OOM prevention
All fixes are implemented in a way that adds new features without breaking existing ones.
...
With this, I thought I could finally migrate ComfyUI to a Cuda13 environment... but it crashed. There's still something holding it back. It's still stuck on 2.8.0.+cu129... and it's a big one, too.
Yes, it's Nunchaku...
Can't be helped... building it myself again, huh... It's complicated, so I really don't want to touch it... It would be great if the official team released it, but honestly, they are slow to respond to various issues.
I've managed to handle quite a lot on my own up to this point.
