Files
goodguy1963-ComfyUI-Thinkin…/sageattention_patch.py
T
Marcel Hofer e0985b4f7b feat: improve video prompting and refactor QwenVL
Refactor the monolithic QwenVL implementation into focused core modules for model access, Hugging Face runtime and configuration, prompt state, prompt contracts, and media helpers while preserving the public compatibility facade.

Add duration-aware LTX 2.3 and MiniMax H3 prompting, including effective-duration cache identity, MiniMax frame-grid normalization, dedicated T2VA/I2VA/FL2VA/L2VA/reference contracts, and best-effort output normalization.

Improve streamed output handling by separating reasoning from final content, rejecting unfinished thinking traces, sanitizing terminal-only punctuation for legacy Windows code pages, and retaining original Unicode in responses and traces.

Expand preset recommendations, ComfyUI tooltips, system prompts, and documentation for video token budgets, thinking settings, reference roles, dialogue fidelity, and duration behavior.

Keep HF and GGUF consumers on canonical shared imports, preserve mutable export identities across reloads, and extend regression coverage for compatibility, downloads, caching, prompting, streaming, and node registration.
2026-08-10 22:04:05 +02:00

145 lines
5.6 KiB
Python

"""
SageAttention patching functions for QwenVL models.
This module contains the safe SageAttention implementation with proper error handling.
"""
import torch
def set_sage_attention(model):
"""
Apply SageAttention patching to the model.
Patches Qwen2Attention and Qwen3VLTextAttention modules to use SageAttention kernels.
"""
from thinkingllm_core.hf_models import get_sage_attention_config, sage_attn_available
if not sage_attn_available():
raise ImportError("SageAttention library is not installed or GPU doesn't support it.")
SAGE_ATTN_FUNC, QK_QUANT_GRAN, PV_ACCUM_DTYPE = get_sage_attention_config()
if SAGE_ATTN_FUNC is None:
raise RuntimeError("No compatible SageAttention kernel found for this GPU.")
# Try to import different attention classes for different Qwen models
attention_classes = []
# Qwen2 models
try:
from transformers.models.qwen2.modeling_qwen2 import Qwen2Attention, apply_rotary_pos_emb as qwen2_apply_rotary
attention_classes.append((Qwen2Attention, qwen2_apply_rotary))
except ImportError:
pass
# Qwen3 models (Qwen3-VL, etc.)
try:
from transformers.models.qwen3.modeling_qwen3 import Qwen3Attention, apply_rotary_pos_emb as qwen3_apply_rotary
attention_classes.append((Qwen3Attention, qwen3_apply_rotary))
except ImportError:
pass
# Qwen3-VL specific
try:
from transformers.models.qwen3_vl.modeling_qwen3_vl import Qwen3VLTextAttention, apply_rotary_pos_emb as qwen3vl_apply_rotary
attention_classes.append((Qwen3VLTextAttention, qwen3vl_apply_rotary))
except ImportError:
pass
if not attention_classes:
print("[QwenVL] Could not import any attention classes for SageAttention patching")
return
def make_sage_forward(AttentionClass, apply_rotary_pos_emb_func):
def sage_attention_forward(
self,
hidden_states: torch.Tensor,
position_embeddings: tuple = None,
attention_mask: torch.Tensor = None,
past_key_values=None,
cache_position: torch.LongTensor = None,
position_ids: torch.LongTensor = None,
**kwargs,
):
original_dtype = hidden_states.dtype
# Determine target dtype
is_4bit = hasattr(self.q_proj, 'quant_state')
if is_4bit:
target_dtype = torch.bfloat16
else:
target_dtype = self.q_proj.weight.dtype
if hidden_states.dtype != target_dtype:
hidden_states = hidden_states.to(target_dtype)
input_shape = hidden_states.shape[:-1]
hidden_shape = (*input_shape, -1, self.head_dim)
bsz, q_len = input_shape[0], input_shape[1] if len(input_shape) > 1 else hidden_states.size(1)
# Handle q_norm and k_norm for Qwen3-VL
query_states = self.q_proj(hidden_states)
key_states = self.k_proj(hidden_states)
value_states = self.v_proj(hidden_states)
# Apply normalization if available (Qwen3-VL specific)
if hasattr(self, 'q_norm'):
query_states = self.q_norm(query_states.view(hidden_shape)).transpose(1, 2)
else:
query_states = query_states.view(hidden_shape).transpose(1, 2)
if hasattr(self, 'k_norm'):
key_states = self.k_norm(key_states.view(hidden_shape)).transpose(1, 2)
else:
key_states = key_states.view(hidden_shape).transpose(1, 2)
value_states = value_states.view(hidden_shape).transpose(1, 2)
# Apply rotary embeddings
if position_embeddings is not None:
cos, sin = position_embeddings
query_states, key_states = apply_rotary_pos_emb_func(query_states, key_states, cos, sin)
if past_key_values is not None:
cache_kwargs = {"sin": sin if position_embeddings else None, "cos": cos if position_embeddings else None, "cache_position": cache_position}
key_states, value_states = past_key_values.update(key_states, value_states, self.layer_idx, cache_kwargs)
is_causal = attention_mask is None and q_len > 1
attn_output = SAGE_ATTN_FUNC(
query_states.to(target_dtype),
key_states.to(target_dtype),
value_states.to(target_dtype),
tensor_layout="HND",
is_causal=is_causal,
qk_quant_gran=QK_QUANT_GRAN,
pv_accum_dtype=PV_ACCUM_DTYPE,
)
if isinstance(attn_output, tuple):
attn_output = attn_output[0]
attn_output = attn_output.transpose(1, 2).contiguous()
attn_output = attn_output.reshape(*input_shape, -1)
attn_output = self.o_proj(attn_output)
if attn_output.dtype != original_dtype:
attn_output = attn_output.to(original_dtype)
return attn_output, None
return sage_attention_forward
# Apply patching to all supported attention modules
patched_count = 0
for AttentionClass, apply_rotary_func in attention_classes:
sage_forward = make_sage_forward(AttentionClass, apply_rotary_func)
for module in model.modules():
if isinstance(module, AttentionClass):
module.forward = sage_forward.__get__(module, AttentionClass)
patched_count += 1
if patched_count > 0:
print(f"[QwenVL] SageAttention: Patched {patched_count} attention layers")
else:
print("[QwenVL] SageAttention: No compatible attention layers found to patch")