t5gemma
T5-Gemma encoder wrapper for daVinci-MagiHuman.
MagiHuman uses transformers.models.t5gemma.T5GemmaEncoderModel on
google/t5gemma-9b-9b-ul2 (a gated Google repo). This wrapper follows the
same lazy-loading pattern as fastvideo/models/encoders/gemma.py: we keep
the HF module under self._t5gemma_model and exclude it from
named_parameters so FastVideo's weight loader does not try to load
encoder shards from the converted repo directory.
For the base MagiHuman T2V port there are no additional connector layers
on top — the pipeline prompt-preprocessing stage handles pad-or-trim to
text_len and exposes both the padded embedding and the original length.
fastvideo.models.encoders.t5gemma.BaseEncoderOutput
BaseEncoderOutput(last_hidden_state: torch.FloatTensor | None = None, pooler_output: torch.FloatTensor | None = None, hidden_states: tuple[torch.FloatTensor, ...] | None = None, attentions: tuple[torch.FloatTensor, ...] | None = None, attention_mask: torch.Tensor | None = None)
fastvideo.models.encoders.t5gemma.TextEncoderConfig
TextEncoderConfig(arch_config: ArchConfig = TextEncoderArchConfig(), prefix: str = '', quant_config: QuantizationConfig | None = None, lora_config: Any | None = None, is_chat_model: bool = False, treat_empty_as_dot: bool = False, *, _resolved_attention_backend: AttentionBackendEnum | None = None, chat_template_enable_thinking: bool = False)
fastvideo.models.encoders.t5gemma.TextEncoder
TextEncoder(config: TextEncoderConfig)
Base for native encoders with a model-specific forward output contract.
fastvideo.models.encoders.t5gemma.TextEncoder.forward
forward(*args: Any, **kwargs: Any) -> TextEncoderOutputT
fastvideo.models.encoders.t5gemma.effective_attention_backend
effective_attention_backend(config: object) -> AttentionBackendEnum | None
Return the attention backend that a component built from config follows.
Some components choose part of their own structure by backend: Wan picks
its VSA transformer block, LTX-2 its distributed-attention path, and the
Gemma and T5-Gemma text encoders switch Hugging Face attention to SDPA.
That choice must match the attention layers inside the component, and
those layers resolve through :func:get_attn_backend. This function
applies the same order:
- The decision a loader recorded on
configwith :func:record_resolved_attention_backend. Every component built by a loader has one when a backend was requested. - While a loader is building a component (a
_component_attention_backend_scopeis active), that scope's rule: its backend, and the environment variable only when the scope consults it. A scope that deliberately ignores the environment variable, such as the dense DMD teacher and critic, keeps ignoring it here. - With no loader involved, the
FASTVIDEO_ATTENTION_BACKENDenvironment variable. This covers components constructed directly, such as MagiHuman's lazily built T5-Gemma encoder or a script that builds a transformer itself.
Reading only the recorded decision is not enough: a directly constructed
component has no recorded decision (None), so it would ignore the
environment variable while its attention layers still follow it, and the
component's structure would not match its layers.
fastvideo.models.encoders.t5gemma.AttentionBackendEnum
AttentionBackendEnum()
fastvideo.models.encoders.t5gemma.T5GemmaEncoderModel
T5GemmaEncoderModel(config: TextEncoderConfig)
Thin wrapper over HuggingFace's T5GemmaEncoderModel.
On first forward, the wrapper lazily instantiates the upstream encoder
from t5gemma_model_path (defaulting to google/t5gemma-9b-9b-ul2).
Afterwards, forward returns a BaseEncoderOutput with
last_hidden_state = [B, L, 3584] matching MagiHuman's
context.half() output.
Source
class T5GemmaEncoderModel(TextEncoder):
"""Thin wrapper over HuggingFace's `T5GemmaEncoderModel`.
On first `forward`, the wrapper lazily instantiates the upstream encoder
from `t5gemma_model_path` (defaulting to `google/t5gemma-9b-9b-ul2`).
Afterwards, forward returns a `BaseEncoderOutput` with
`last_hidden_state = [B, L, 3584]` matching MagiHuman's
`context.half()` output.
"""
_supported_attention_backends = (
AttentionBackendEnum.FLASH_ATTN,
AttentionBackendEnum.TORCH_SDPA,
)
def __init__(self, config: TextEncoderConfig) -> None:
super().__init__(config)
arch = config.arch_config
self.t5gemma_model_path: str = arch.t5gemma_model_path
self.t5gemma_dtype: str = arch.t5gemma_dtype
self._t5gemma_model = None
def named_parameters(self, prefix: str = "", recurse: bool = True):
# The upstream encoder is loaded lazily and its parameters are
# managed by HF, not FastVideo's loader. Hide them from the parent
# module-tree traversal so Diffusers-repo weight loading does not
# try to match them.
for name, param in super().named_parameters(prefix=prefix, recurse=recurse):
if name.startswith("_t5gemma_model.") or name == "_t5gemma_model":
continue
yield name, param
def _build_t5gemma_model(self, device: torch.device | None = None):
from transformers.models.t5gemma import T5GemmaEncoderModel as HFEncoder
path = self.t5gemma_model_path
if not path:
raise ValueError("t5gemma_model_path must be set. Expected "
"`google/t5gemma-9b-9b-ul2` or a local path to an "
"equivalent T5-Gemma encoder.")
dtype = getattr(torch, self.t5gemma_dtype, torch.bfloat16)
model = HFEncoder.from_pretrained(
path,
is_encoder_decoder=False,
dtype=dtype,
)
if effective_attention_backend(self.config) == AttentionBackendEnum.TORCH_SDPA:
if hasattr(model.config, "attn_implementation"):
model.config.attn_implementation = "sdpa"
if hasattr(model.config, "_attn_implementation"):
model.config._attn_implementation = "sdpa"
if device is not None:
model = model.to(device=device)
model.eval()
return model
@property
def t5gemma_model(self):
if self._t5gemma_model is None:
# Lazy-load on CPU if no device is known yet; `forward` will
# move the model to the input's device on first call.
self._t5gemma_model = self._build_t5gemma_model()
return self._t5gemma_model
def forward(
self,
input_ids: torch.Tensor | None = None,
position_ids: torch.Tensor | None = None,
attention_mask: torch.Tensor | None = None,
inputs_embeds: torch.Tensor | None = None,
output_hidden_states: bool | None = None,
**kwargs,
) -> BaseEncoderOutput:
# Ensure the lazy-loaded encoder lives on the same device as the
# input; lazy-loading leaves it on CPU until the first forward.
ref = input_ids if input_ids is not None else inputs_embeds
target_device = ref.device if ref is not None else None
model = self.t5gemma_model
if target_device is not None:
first_param = next(model.parameters(), None)
if first_param is not None and first_param.device != target_device:
model = model.to(device=target_device)
self._t5gemma_model = model
outputs = model(
input_ids=input_ids,
position_ids=position_ids,
attention_mask=attention_mask,
inputs_embeds=inputs_embeds,
output_hidden_states=output_hidden_states,
)
# MagiHuman casts to fp16 at this point; keep the raw dtype here and
# leave precision management to the pipeline's postprocess stage.
return BaseEncoderOutput(
last_hidden_state=outputs["last_hidden_state"],
hidden_states=getattr(outputs, "hidden_states", None),
attention_mask=attention_mask,
)
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
# The HF T5-Gemma encoder is lazy-loaded from `t5gemma_model_path`
# (see `_build_t5gemma_model`), so this wrapper owns zero
# FastVideo-native parameters and `named_parameters()` is filtered
# to hide the HF submodule. `TextEncoderLoader.load_model()` calls
# `model.load_weights(...)` unconditionally, so we must define it
# here. Returning an empty set matches the empty `weights_to_load`
# set the loader computes from `named_parameters()`, satisfying
# its strict-load check.
for _ in weights:
pass
return set()</code></pre></details>
fastvideo.models.encoders.t5gemma.T5GemmaEncoderModel.named_parameters
named_parameters(prefix: str = '', recurse: bool = True)
Source
def named_parameters(self, prefix: str = "", recurse: bool = True):
# The upstream encoder is loaded lazily and its parameters are
# managed by HF, not FastVideo's loader. Hide them from the parent
# module-tree traversal so Diffusers-repo weight loading does not
# try to match them.
for name, param in super().named_parameters(prefix=prefix, recurse=recurse):
if name.startswith("_t5gemma_model.") or name == "_t5gemma_model":
continue
yield name, param
fastvideo.models.encoders.t5gemma.T5GemmaEncoderModel.forward
forward(input_ids: torch.Tensor | None = None, position_ids: torch.Tensor | None = None, attention_mask: torch.Tensor | None = None, inputs_embeds: torch.Tensor | None = None, output_hidden_states: bool | None = None, **kwargs) -> BaseEncoderOutput
Source
def forward(
self,
input_ids: torch.Tensor | None = None,
position_ids: torch.Tensor | None = None,
attention_mask: torch.Tensor | None = None,
inputs_embeds: torch.Tensor | None = None,
output_hidden_states: bool | None = None,
**kwargs,
) -> BaseEncoderOutput:
# Ensure the lazy-loaded encoder lives on the same device as the
# input; lazy-loading leaves it on CPU until the first forward.
ref = input_ids if input_ids is not None else inputs_embeds
target_device = ref.device if ref is not None else None
model = self.t5gemma_model
if target_device is not None:
first_param = next(model.parameters(), None)
if first_param is not None and first_param.device != target_device:
model = model.to(device=target_device)
self._t5gemma_model = model
outputs = model(
input_ids=input_ids,
position_ids=position_ids,
attention_mask=attention_mask,
inputs_embeds=inputs_embeds,
output_hidden_states=output_hidden_states,
)
# MagiHuman casts to fp16 at this point; keep the raw dtype here and
# leave precision management to the pipeline's postprocess stage.
return BaseEncoderOutput(
last_hidden_state=outputs["last_hidden_state"],
hidden_states=getattr(outputs, "hidden_states", None),
attention_mask=attention_mask,
)
fastvideo.models.encoders.t5gemma.T5GemmaEncoderModel.load_weights
load_weights(weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]
Source
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
# The HF T5-Gemma encoder is lazy-loaded from `t5gemma_model_path`
# (see `_build_t5gemma_model`), so this wrapper owns zero
# FastVideo-native parameters and `named_parameters()` is filtered
# to hide the HF submodule. `TextEncoderLoader.load_model()` calls
# `model.load_weights(...)` unconditionally, so we must define it
# here. Returning an empty set matches the empty `weights_to_load`
# set the loader computes from `named_parameters()`, satisfying
# its strict-load check.
for _ in weights:
pass
return set()