Files
JasonHoku-ComfyUI-Ultimate-…/batch_encoding.py
T

153 lines
6.2 KiB
Python

"""
Batch CLIP Encoding Module
Handles efficient batch encoding of prompts with caching
"""
import torch
import gc
import comfy.model_management
def batch_encode_with_cache(clip_model, prompts, cond_cache, prompt_type="positive", batch_size=64, clip_skip=0):
"""
Batch encode prompts while checking persistent cache first.
Only encodes prompts that aren't already cached.
Args:
clip_model: CLIP model to use for encoding
prompts: Set or list of prompt strings to encode
cond_cache: ConditioningCache instance
prompt_type: "positive" or "negative"
batch_size: Number of prompts to encode at once
clip_skip: Number of CLIP layers to skip from the end (0 = use last layer, -1 = skip 1 layer, -2 = skip 2 layers)
Returns:
dict: Mapping of prompt text to conditioning tensors
"""
results = {}
prompts_to_encode = []
# Check cache first
print(f"[GridTester] 🔍 Checking cache for {len(prompts)} {prompt_type} prompts...")
for prompt in prompts:
cached = cond_cache.get(prompt, prompt_type)
if cached is not None:
results[prompt] = cached
else:
prompts_to_encode.append(prompt)
cache_hits = len(results)
cache_misses = len(prompts_to_encode)
print(f"[GridTester] 📊 Cache: {cache_hits} hits, {cache_misses} misses")
# Batch encode uncached prompts
if prompts_to_encode:
clip_skip_msg = f" (clip_skip={clip_skip})" if clip_skip != 0 else ""
print(f"[GridTester] 🚀 Batch encoding {len(prompts_to_encode)} {prompt_type} prompts{clip_skip_msg} (batch_size={batch_size})")
# Force model to stay in VRAM
comfy.model_management.load_models_gpu([clip_model.patcher])
prompts_list = list(prompts_to_encode)
total_batches = (len(prompts_list) + batch_size - 1) // batch_size
with torch.no_grad():
for batch_idx in range(0, len(prompts_list), batch_size):
batch_prompts = prompts_list[batch_idx:batch_idx + batch_size]
current_batch = (batch_idx // batch_size) + 1
# Encode batch
for prompt in batch_prompts:
try:
tokens = clip_model.tokenize(prompt)
# ComfyUI's CLIP encode_from_tokens doesn't have a layer parameter
# We need to use the CLIP model's layer setter before encoding
# Store original layer setting
original_layer = None
if clip_skip != 0 and hasattr(clip_model.cond_stage_model, 'clip_layer'):
original_layer = clip_model.cond_stage_model.clip_layer
clip_model.cond_stage_model.set_clip_options({"layer": clip_skip})
cond, pooled = clip_model.encode_from_tokens(tokens, return_pooled=True)
conditioning = [[cond, {"pooled_output": pooled}]]
results[prompt] = conditioning
cond_cache.set(prompt, conditioning, prompt_type)
# Restore original layer
if original_layer is not None:
clip_model.cond_stage_model.set_clip_options({"layer": original_layer})
except Exception as e:
print(f"[GridTester] ⚠️ Failed to encode: {e}")
results[prompt] = None
# Progress
encoded_count = min(batch_idx + batch_size, len(prompts_list))
if current_batch % 5 == 0 or current_batch == total_batches:
print(f"[GridTester] Batch {current_batch}/{total_batches} | Encoded {encoded_count}/{len(prompts_list)}")
print(f"[GridTester] ✅ Batch encoding complete!")
return results
def batch_encode_prompts(patched_clip, unique_positives, unique_negatives, cond_cache, clip_skip=0, enable_disk_cache=True):
"""
Batch encode both positive and negative prompts.
Args:
patched_clip: Patched CLIP model
unique_positives: Set of unique positive prompts
unique_negatives: Set of unique negative prompts
cond_cache: ConditioningCache instance
clip_skip: Number of CLIP layers to skip from the end
enable_disk_cache: Whether to save cache to disk (passed through to cache)
Returns:
dict: conditioning_cache with "positive" and "negative" keys
"""
conditioning_cache = {"positive": {}, "negative": {}}
print(f"[GridTester] 🧠 Found {len(unique_positives)} unique positive prompts")
print(f"[GridTester] 🧠 Found {len(unique_negatives)} unique negative prompts")
# Use ComfyUI's model management to prevent memory leak warnings during batch encoding
# The model needs to stay loaded for the entire encoding loop
with torch.no_grad():
# Encode all positive prompts
print(f"[GridTester] 🧠 Encoding {len(unique_positives)} unique positive prompts...")
conditioning_cache["positive"] = batch_encode_with_cache(
patched_clip,
unique_positives,
cond_cache,
prompt_type="positive",
batch_size=16,
clip_skip=clip_skip
)
conditioning_cache["negative"] = batch_encode_with_cache(
patched_clip,
unique_negatives,
cond_cache,
prompt_type="negative",
batch_size=16,
clip_skip=clip_skip
)
# Save and print cache stats (only saves if disk cache is enabled)
if cond_cache is not None:
cond_cache.save()
cond_cache.print_stats()
# Final cleanup after all encoding is complete
gc.collect()
if torch.cuda.is_available():
torch.cuda.empty_cache()
print(f"[GridTester] ✅ All prompts pre-encoded!")
print(f"[GridTester] 💾 Cache size: {len(conditioning_cache['positive'])} positive, {len(conditioning_cache['negative'])} negative")
return conditioning_cache