sunandbean's picture
Add files using upload-large-folder tool
629b123 verified
Raw History Blame Contribute Delete
4.55 kB
# SPDX-FileCopyrightText: © 2026 Tenstorrent USA, Inc.
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
from typing import TYPE_CHECKING
import torch
from loguru import logger
from transformers import T5EncoderModel, T5TokenizerFast
from models.tt_dit.pipelines.events import PipelineEventCallback, SectionEnd, SectionStart, null_callback
if TYPE_CHECKING:
from collections.abc import Sequence
class TextEncoder:
def __init__(
self,
*,
checkpoint_name: str,
force_zeros_for_empty_prompt: bool,
max_sequence_length: int = 256,
) -> None:
self.text_encoder = T5EncoderModel.from_pretrained(
checkpoint_name, subfolder="text_encoder", torch_dtype=torch.float32
)
self.tokenizer = T5TokenizerFast.from_pretrained(checkpoint_name, subfolder="tokenizer")
self._force_zeros_for_empty_prompt = force_zeros_for_empty_prompt
self._max_sequence_length = max_sequence_length
@torch.no_grad()
def encode_cfg(
self,
prompts: Sequence[str],
negative_prompts: Sequence[str],
*,
num_videos_per_prompt: int = 1,
disable_attention_mask: bool = False,
on_event: PipelineEventCallback = null_callback,
) -> tuple[tuple[torch.Tensor, torch.Tensor], tuple[torch.Tensor, torch.Tensor]]:
"""Encode both cfg branches and return them in (uncond, cond) order."""
on_event(SectionStart("t5_encoding"))
cond_embeds, cond_mask = self._get_t5_prompt_embeds(
prompt=list(prompts),
num_videos_per_prompt=num_videos_per_prompt,
disable_attention_mask=disable_attention_mask,
)
uncond_embeds, uncond_mask = self._get_t5_prompt_embeds(
prompt=list(negative_prompts),
num_videos_per_prompt=num_videos_per_prompt,
disable_attention_mask=disable_attention_mask,
)
on_event(SectionEnd("t5_encoding"))
return (uncond_embeds, cond_embeds), (uncond_mask, cond_mask)
def _get_t5_prompt_embeds(
self,
prompt: list[str],
*,
num_videos_per_prompt: int,
disable_attention_mask: bool,
) -> tuple[torch.Tensor, torch.Tensor]:
max_sequence_length = self._max_sequence_length
device = "cpu"
dtype = self.text_encoder.dtype
batch_size = len(prompt)
text_inputs = self.tokenizer(
prompt,
padding="max_length",
max_length=max_sequence_length,
truncation=True,
add_special_tokens=True,
return_tensors="pt",
)
text_input_ids = text_inputs.input_ids
prompt_attention_mask = text_inputs.attention_mask
prompt_attention_mask = prompt_attention_mask.bool().to(device)
if disable_attention_mask:
prompt_attention_mask.fill_(value=True)
# The original Mochi implementation zeros out empty negative prompts but this can lead to
# overflow when placing the entire pipeline under the autocast context, hence the option.
if self._force_zeros_for_empty_prompt and (prompt == "" or prompt[-1] == ""):
text_input_ids = torch.zeros_like(text_input_ids, device=device)
prompt_attention_mask = torch.zeros_like(prompt_attention_mask, dtype=torch.bool, device=device)
untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids
if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):
removed_text = self.tokenizer.batch_decode(untruncated_ids[:, max_sequence_length - 1 : -1])
logger.warning(
"The following part of your input was truncated because `max_sequence_length` is set to "
f" {max_sequence_length} tokens: {removed_text}"
)
prompt_embeds = self.text_encoder(text_input_ids.to(device), attention_mask=prompt_attention_mask)[0]
prompt_embeds = prompt_embeds.to(dtype=dtype, device=device)
_, seq_len, _ = prompt_embeds.shape
prompt_embeds = prompt_embeds.repeat(1, num_videos_per_prompt, 1)
prompt_embeds = prompt_embeds.view(batch_size * num_videos_per_prompt, seq_len, -1)
prompt_attention_mask = prompt_attention_mask.view(batch_size, -1)
prompt_attention_mask = prompt_attention_mask.repeat(num_videos_per_prompt, 1)
return prompt_embeds, prompt_attention_mask