Send T5 tokens to the T5 encoder's own device when caching text embeddings#935
Open
rajakshay wants to merge 1 commit into
Open
Send T5 tokens to the T5 encoder's own device when caching text embeddings#935rajakshay wants to merge 1 commit into
rajakshay wants to merge 1 commit into
Conversation
…dings
When caching text embeddings for dual text-encoder models (CLIP + T5),
the T5 branch sent its input ids to `device`, which is computed from the
first encoder (CLIP). If the large T5 encoder is offloaded to a different
device (e.g. CPU offload on low-VRAM / unified-memory setups) while CLIP
stays on the GPU, the token tensor lands on CLIP's device while T5's
weights are elsewhere, raising:
Expected all tensors to be on the same device, but found at least
two devices, cuda:0 and cpu
Send the tokens to the T5 encoder's own device instead. When both
encoders are co-located (the common single-GPU case) this is a no-op,
so existing setups are unaffected.
Author
Reproduction (no models or data)Tiny stand-in encoders — CLIP on GPU, T5 on CPU — which is exactly the state when the large T5 encoder is offloaded while CLIP stays on the GPU. Needs one CUDA device (to place the two encoders on different devices). Run from the repo root. repro.pyimport sys
import torch
import torch.nn as nn
if not torch.cuda.is_available():
print("SKIP: needs a CUDA device to place CLIP and T5 on different devices")
sys.exit(0)
from toolkit.train_tools import encode_prompts_flux
class Tok:
model_max_length = 77
def __call__(self, prompts, **kw):
o = type("O", (), {})()
o.input_ids = torch.zeros((len(prompts), 8), dtype=torch.long)
return o
class CLIP(nn.Module):
def __init__(self):
super().__init__()
self.lin = nn.Linear(8, 8)
@property
def device(self): return next(self.parameters()).device
@property
def dtype(self): return next(self.parameters()).dtype
def forward(self, ids, output_hidden_states=False):
o = type("O", (), {})()
o.pooler_output = self.lin(ids.float())
return o
class T5(nn.Module):
def __init__(self):
super().__init__()
self.emb = nn.Embedding(100, 16)
@property
def device(self): return next(self.parameters()).device
@property
def dtype(self): return next(self.parameters()).dtype
def forward(self, ids, output_hidden_states=False):
return (self.emb(ids),)
clip = CLIP().to("cuda") # CLIP on GPU
t5 = T5().to("cpu") # T5 offloaded to CPU (the real low-VRAM / device_map case)
try:
embeds, pooled = encode_prompts_flux([Tok(), Tok()], [clip, t5], ["a photo of a cat"])
print("PASSED: encode_prompts_flux returned embeds", tuple(embeds.shape),
"pooled", tuple(pooled.shape))
except RuntimeError as e:
print("REPRODUCED RuntimeError:", str(e).splitlines()[0])Result: |
Author
|
Hi @jaretburkett |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
encode_prompts_fluxintoolkit/train_tools.pycomputes a singledevicefrom the first text encoder (CLIP) and reuses it for the second encoder (T5). When caching text embeddings, it sends the T5 input ids todevice(CLIP's device) instead of to T5's own device.Why it breaks
On setups that place the large T5 encoder on a different device than CLIP (e.g. CPU offload /
device_mapon low-VRAM or unified-memory systems), the tokens land on CLIP's GPU while T5's weights are elsewhere, so the T5 forward raises:This surfaces during "Caching text embeddings to disk" — the model, VAE, network, and CLIP all load fine first, then it dies when T5 is invoked.
Fix
Send the tokens to
text_encoder[1].device(T5's own device).Regression safety
When both encoders are co-located (the default single-GPU case),
text_encoder[1].device == device, so this is a no-op — no behavior change for existing setups. It only corrects the split-device path.Validation
Refs #374 (the
cache_text_embeddingsdevice-mismatch report).