返回 JoyAI-Echo
text_encoder_wrapper.py
根目录 / ltx-distillation / src / ltx_distillation / models / text_encoder_wrapper.py
1 """
2 Gemma Text Encoder Wrapper for DMD distillation.
3
4 Provides a simple interface for text encoding without prompt enhancement.
5 Just pure text -> context embedding conversion.
6 """
7
8 from typing import List, Dict, Any, Optional
9 import torch
10 import torch.nn as nn
11
12 from ltx_core.loader.registry import Registry
13
14
15 class GemmaTextEncoderWrapper(nn.Module):
16 """
17 Wrapper for Gemma text encoder to provide DMD-compatible interface.
18
19 This wrapper:
20 - Takes raw text prompts (no enhancement needed)
21 - Returns conditional_dict with video_context and audio_context
22 - Handles batched encoding
23 """
24
25 def __init__(
26 self,
27 text_encoder,
28 embeddings_processor,
29 device: torch.device = None,
30 dtype: torch.dtype = torch.bfloat16,
31 ):
32 """
33 Args:
34 text_encoder: GemmaTextEncoder instance
35 embeddings_processor: EmbeddingsProcessor instance
36 device: Target device
37 dtype: Model dtype
38 """
39 super().__init__()
40 self.text_encoder = text_encoder
41 self.embeddings_processor = embeddings_processor
42 self.device = device
43 self.dtype = dtype
44
45 @torch.no_grad()
46 def forward(
47 self,
48 text_prompts: List[str],
49 padding_side: str = "left",
50 ) -> Dict[str, Optional[torch.Tensor]]:
51 """
52 Encode text prompts to conditioning embeddings.
53
54 Args:
55 text_prompts: List of text prompts (already processed, no enhancement)
56 padding_side: Padding side for tokenizer
57
58 Returns:
59 Dictionary containing:
60 - video_context: [B, seq_len, dim] video conditioning
61 - audio_context: [B, seq_len, dim] audio conditioning
62 - attention_mask: [B, seq_len] attention mask
63 """
64 batch_size = len(text_prompts)
65
66 # Encode each prompt
67 video_contexts = []
68 audio_contexts = []
69 attention_masks = []
70
71 for prompt in text_prompts:
72 # 1) Run Gemma LLM to get raw hidden states + attention mask
73 hidden_states, attn_mask = self.text_encoder.encode(prompt, padding_side=padding_side)
74 # 2) Process hidden states to obtain final embeddings
75 output = self.embeddings_processor.process_hidden_states(
76 hidden_states, attn_mask, padding_side=padding_side
77 )
78
79 video_contexts.append(output.video_encoding)
80 audio_contexts.append(output.audio_encoding)
81 attention_masks.append(output.attention_mask)
82
83 # Stack batch
84 video_context = torch.cat(video_contexts, dim=0) if len(video_contexts) > 0 else None
85 # Handle optional audio connector (may be None depending on config)
86 if any(ac is None for ac in audio_contexts):
87 audio_context = None
88 else:
89 audio_context = torch.cat(audio_contexts, dim=0)
90 attention_mask = torch.cat(attention_masks, dim=0) if len(attention_masks) > 0 else None
91
92 return {
93 "video_context": video_context,
94 "audio_context": audio_context,
95 "attention_mask": attention_mask,
96 }
97
98 def encode_batch(
99 self,
100 text_prompts: List[str],
101 ) -> Dict[str, torch.Tensor]:
102 """Alias for forward() with default padding."""
103 return self.forward(text_prompts)
104
105
106 def create_text_encoder_wrapper(
107 checkpoint_path: str,
108 gemma_path: str,
109 device: torch.device,
110 dtype: torch.dtype = torch.bfloat16,
111 registry: Registry | None = None,
112 ) -> GemmaTextEncoderWrapper:
113 """
114 Factory function to create GemmaTextEncoderWrapper from checkpoint.
115
116 Args:
117 checkpoint_path: Path to LTX-2 checkpoint
118 gemma_path: Path to Gemma text encoder
119 device: Target device
120 dtype: Model dtype
121
122 Returns:
123 Configured GemmaTextEncoderWrapper
124 """
125 from ltx_pipelines.utils.model_ledger import ModelLedger
126
127 # Load to CPU first to avoid safetensors device issues
128 ledger = ModelLedger(
129 dtype=dtype,
130 device=torch.device("cpu"),
131 checkpoint_path=checkpoint_path,
132 gemma_root_path=gemma_path,
133 registry=registry,
134 )
135
136 text_encoder = ledger.text_encoder().to(device=device, dtype=dtype)
137 embeddings_processor = ledger.gemma_embeddings_processor().to(device=device, dtype=dtype)
138
139 wrapper = GemmaTextEncoderWrapper(
140 text_encoder=text_encoder,
141 embeddings_processor=embeddings_processor,
142 device=device,
143 dtype=dtype,
144 )
145
146 return wrapper
147
147 lines PYTHON