Tibron commited on
Commit
9e50ce3
·
verified ·
1 Parent(s): fff65be

feat: Add initial model files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +3 -0
  2. __init__.py +18 -0
  3. autoencoder_kl_3d.py +1081 -0
  4. cache_utils.py +226 -0
  5. config.json +285 -0
  6. configuration_hunyuan_image_3.py +310 -0
  7. generation_config.json +21 -0
  8. hunyuan_image_3_pipeline.py +913 -0
  9. image_processor.py +465 -0
  10. model-0001-of-0032.safetensors +3 -0
  11. model-0002-of-0032.safetensors +3 -0
  12. model-0003-of-0032.safetensors +3 -0
  13. model-0004-of-0032.safetensors +3 -0
  14. model-0005-of-0032.safetensors +3 -0
  15. model-0006-of-0032.safetensors +3 -0
  16. model-0007-of-0032.safetensors +3 -0
  17. model-0008-of-0032.safetensors +3 -0
  18. model-0009-of-0032.safetensors +3 -0
  19. model-0010-of-0032.safetensors +3 -0
  20. model-0011-of-0032.safetensors +3 -0
  21. model-0012-of-0032.safetensors +3 -0
  22. model-0013-of-0032.safetensors +3 -0
  23. model-0014-of-0032.safetensors +3 -0
  24. model-0015-of-0032.safetensors +3 -0
  25. model-0016-of-0032.safetensors +3 -0
  26. model-0017-of-0032.safetensors +3 -0
  27. model-0018-of-0032.safetensors +3 -0
  28. model-0019-of-0032.safetensors +3 -0
  29. model-0020-of-0032.safetensors +3 -0
  30. model-0021-of-0032.safetensors +3 -0
  31. model-0022-of-0032.safetensors +3 -0
  32. model-0023-of-0032.safetensors +3 -0
  33. model-0024-of-0032.safetensors +3 -0
  34. model-0025-of-0032.safetensors +3 -0
  35. model-0026-of-0032.safetensors +3 -0
  36. model-0027-of-0032.safetensors +3 -0
  37. model-0028-of-0032.safetensors +3 -0
  38. model-0029-of-0032.safetensors +3 -0
  39. model-0030-of-0032.safetensors +3 -0
  40. model-0031-of-0032.safetensors +3 -0
  41. model-0032-of-0032.safetensors +3 -0
  42. model.safetensors.index.json +0 -0
  43. modeling_hunyuan_image_3.py +0 -0
  44. siglip2.py +570 -0
  45. system_prompt.py +206 -0
  46. tokenization_hunyuan_image_3.py +1773 -0
  47. tokenizer.json +3 -0
  48. tokenizer_config.json +8 -0
  49. utils/__init__.py +4 -0
  50. utils/__pycache__/__init__.cpython-311.pyc +0 -0
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
37
+ utils/__pycache__/import_utils.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
38
+ utils/__pycache__/import_utils.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
__init__.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import TYPE_CHECKING
2
+
3
+ from utils import _LazyModule
4
+ from utils.import_utils import define_import_structure
5
+
6
+
7
+ if TYPE_CHECKING:
8
+ from .configuration_hunyuan_image_3 import *
9
+ from .modeling_hunyuan_image_3 import *
10
+ from .autoencoder_kl_3d import *
11
+ from .image_processor import *
12
+ from .siglip2 import *
13
+ from .tokenization_hunyuan_image_3 import *
14
+ else:
15
+ import sys
16
+
17
+ _file = globals()["__file__"]
18
+ sys.modules[__name__] = _LazyModule(__name__, _file, define_import_structure(_file), module_spec=__spec__)
autoencoder_kl_3d.py ADDED
@@ -0,0 +1,1081 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Reference code
3
+ [FLUX] https://github.com/black-forest-labs/flux/blob/main/src/flux/modules/autoencoder.py
4
+ [DCAE] https://github.com/mit-han-lab/efficientvit/blob/master/efficientvit/models/efficientvit/dc_ae.py
5
+ """
6
+ import os
7
+ from dataclasses import dataclass
8
+ from typing import Tuple, Optional
9
+ import math
10
+ import random
11
+ import numpy as np
12
+ from einops import rearrange
13
+ import torch
14
+ from torch import Tensor, nn
15
+ import torch.nn.functional as F
16
+ import torch.distributed as dist
17
+ import torch.multiprocessing as mp
18
+
19
+ from safetensors import safe_open
20
+ import os
21
+ from collections import OrderedDict
22
+ from collections.abc import Iterable
23
+ from diffusers.configuration_utils import ConfigMixin, register_to_config
24
+ from diffusers.models.modeling_outputs import AutoencoderKLOutput
25
+ from diffusers.models.modeling_utils import ModelMixin
26
+ from diffusers.utils.torch_utils import randn_tensor
27
+ from diffusers.utils import BaseOutput
28
+
29
+
30
+
31
+ class DiagonalGaussianDistribution(object):
32
+ def __init__(self, parameters: torch.Tensor, deterministic: bool = False):
33
+ if parameters.ndim == 3:
34
+ dim = 2 # (B, L, C)
35
+ elif parameters.ndim == 5 or parameters.ndim == 4:
36
+ dim = 1 # (B, C, T, H ,W) / (B, C, H, W)
37
+ else:
38
+ raise NotImplementedError
39
+ self.parameters = parameters
40
+ self.mean, self.logvar = torch.chunk(parameters, 2, dim=dim)
41
+ self.logvar = torch.clamp(self.logvar, -30.0, 20.0)
42
+ self.deterministic = deterministic
43
+ self.std = torch.exp(0.5 * self.logvar)
44
+ self.var = torch.exp(self.logvar)
45
+ if self.deterministic:
46
+ self.var = self.std = torch.zeros_like(
47
+ self.mean, device=self.parameters.device, dtype=self.parameters.dtype
48
+ )
49
+
50
+ def sample(self, generator: Optional[torch.Generator] = None) -> torch.FloatTensor:
51
+ # make sure sample is on the same device as the parameters and has same dtype
52
+ sample = randn_tensor(
53
+ self.mean.shape,
54
+ generator=generator,
55
+ device=self.parameters.device,
56
+ dtype=self.parameters.dtype,
57
+ )
58
+ x = self.mean + self.std * sample
59
+ return x
60
+
61
+ def kl(self, other: "DiagonalGaussianDistribution" = None) -> torch.Tensor:
62
+ if self.deterministic:
63
+ return torch.Tensor([0.0])
64
+ else:
65
+ reduce_dim = list(range(1, self.mean.ndim))
66
+ if other is None:
67
+ return 0.5 * torch.sum(
68
+ torch.pow(self.mean, 2) + self.var - 1.0 - self.logvar,
69
+ dim=reduce_dim,
70
+ )
71
+ else:
72
+ return 0.5 * torch.sum(
73
+ torch.pow(self.mean - other.mean, 2) / other.var +
74
+ self.var / other.var -
75
+ 1.0 -
76
+ self.logvar +
77
+ other.logvar,
78
+ dim=reduce_dim,
79
+ )
80
+
81
+ def nll(self, sample: torch.Tensor, dims: Tuple[int, ...] = [1, 2, 3]) -> torch.Tensor:
82
+ if self.deterministic:
83
+ return torch.Tensor([0.0])
84
+ logtwopi = np.log(2.0 * np.pi)
85
+ return 0.5 * torch.sum(
86
+ logtwopi + self.logvar + torch.pow(sample - self.mean, 2) / self.var,
87
+ dim=dims,
88
+ )
89
+
90
+ def mode(self) -> torch.Tensor:
91
+ return self.mean
92
+
93
+ @dataclass
94
+ class DecoderOutput(BaseOutput):
95
+ sample: torch.FloatTensor
96
+ posterior: Optional[DiagonalGaussianDistribution] = None
97
+
98
+ def swish(x: Tensor) -> Tensor:
99
+ return x * torch.sigmoid(x)
100
+
101
+ def forward_with_checkpointing(module, *inputs, use_checkpointing=False):
102
+ def create_custom_forward(module):
103
+ def custom_forward(*inputs):
104
+ return module(*inputs)
105
+ return custom_forward
106
+
107
+ if use_checkpointing:
108
+ return torch.utils.checkpoint.checkpoint(create_custom_forward(module), *inputs, use_reentrant=False)
109
+ else:
110
+ return module(*inputs)
111
+
112
+
113
+ class Conv3d(nn.Conv3d):
114
+ """Perform Conv3d on patches with numerical differences from nn.Conv3d within 1e-5. Only symmetric padding is supported."""
115
+
116
+ def forward(self, input):
117
+ B, C, T, H, W = input.shape
118
+ memory_count = (C * T * H * W) * 2 / 1024**3
119
+ if memory_count > 2:
120
+ n_split = math.ceil(memory_count / 2)
121
+ assert n_split >= 2
122
+ chunks = torch.chunk(input, chunks=n_split, dim=-3)
123
+ padded_chunks = []
124
+ for i in range(len(chunks)):
125
+ if self.padding[0] > 0:
126
+ padded_chunk = F.pad(
127
+ chunks[i],
128
+ (0, 0, 0, 0, self.padding[0], self.padding[0]),
129
+ mode="constant" if self.padding_mode == "zeros" else self.padding_mode,
130
+ value=0,
131
+ )
132
+ if i > 0:
133
+ padded_chunk[:, :, :self.padding[0]] = chunks[i - 1][:, :, -self.padding[0]:]
134
+ if i < len(chunks) - 1:
135
+ padded_chunk[:, :, -self.padding[0]:] = chunks[i + 1][:, :, :self.padding[0]]
136
+ else:
137
+ padded_chunk = chunks[i]
138
+ padded_chunks.append(padded_chunk)
139
+ padding_bak = self.padding
140
+ self.padding = (0, self.padding[1], self.padding[2])
141
+ outputs = []
142
+ for i in range(len(padded_chunks)):
143
+ outputs.append(super().forward(padded_chunks[i]))
144
+ self.padding = padding_bak
145
+ return torch.cat(outputs, dim=-3)
146
+ else:
147
+ return super().forward(input)
148
+
149
+
150
+ class AttnBlock(nn.Module):
151
+ def __init__(self, in_channels: int):
152
+ super().__init__()
153
+ self.in_channels = in_channels
154
+
155
+ self.norm = nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True)
156
+
157
+ self.q = Conv3d(in_channels, in_channels, kernel_size=1)
158
+ self.k = Conv3d(in_channels, in_channels, kernel_size=1)
159
+ self.v = Conv3d(in_channels, in_channels, kernel_size=1)
160
+ self.proj_out = Conv3d(in_channels, in_channels, kernel_size=1)
161
+
162
+ def attention(self, h_: Tensor) -> Tensor:
163
+ h_ = self.norm(h_)
164
+ q = self.q(h_)
165
+ k = self.k(h_)
166
+ v = self.v(h_)
167
+
168
+ b, c, f, h, w = q.shape
169
+ q = rearrange(q, "b c f h w -> b 1 (f h w) c").contiguous()
170
+ k = rearrange(k, "b c f h w -> b 1 (f h w) c").contiguous()
171
+ v = rearrange(v, "b c f h w -> b 1 (f h w) c").contiguous()
172
+ h_ = nn.functional.scaled_dot_product_attention(q, k, v)
173
+
174
+ return rearrange(h_, "b 1 (f h w) c -> b c f h w", f=f, h=h, w=w, c=c, b=b)
175
+
176
+ def forward(self, x: Tensor) -> Tensor:
177
+ return x + self.proj_out(self.attention(x))
178
+
179
+
180
+ class ResnetBlock(nn.Module):
181
+ def __init__(self, in_channels: int, out_channels: int):
182
+ super().__init__()
183
+ self.in_channels = in_channels
184
+ out_channels = in_channels if out_channels is None else out_channels
185
+ self.out_channels = out_channels
186
+
187
+ self.norm1 = nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True)
188
+ self.conv1 = Conv3d(in_channels, out_channels, kernel_size=3, stride=1, padding=1)
189
+ self.norm2 = nn.GroupNorm(num_groups=32, num_channels=out_channels, eps=1e-6, affine=True)
190
+ self.conv2 = Conv3d(out_channels, out_channels, kernel_size=3, stride=1, padding=1)
191
+ if self.in_channels != self.out_channels:
192
+ self.nin_shortcut = Conv3d(in_channels, out_channels, kernel_size=1, stride=1, padding=0)
193
+
194
+ def forward(self, x):
195
+ h = x
196
+ h = self.norm1(h)
197
+ h = swish(h)
198
+ h = self.conv1(h)
199
+
200
+ h = self.norm2(h)
201
+ h = swish(h)
202
+ h = self.conv2(h)
203
+
204
+ if self.in_channels != self.out_channels:
205
+ x = self.nin_shortcut(x)
206
+ return x + h
207
+
208
+
209
+ class Downsample(nn.Module):
210
+ def __init__(self, in_channels: int, add_temporal_downsample: bool = True):
211
+ super().__init__()
212
+ self.add_temporal_downsample = add_temporal_downsample
213
+ stride = (2, 2, 2) if add_temporal_downsample else (1, 2, 2) # THW
214
+ # no asymmetric padding in torch conv, must do it ourselves
215
+ self.conv = Conv3d(in_channels, in_channels, kernel_size=3, stride=stride, padding=0)
216
+
217
+ def forward(self, x: Tensor):
218
+ spatial_pad = (0, 1, 0, 1, 0, 0) # WHT
219
+ x = nn.functional.pad(x, spatial_pad, mode="constant", value=0)
220
+
221
+ temporal_pad = (0, 0, 0, 0, 0, 1) if self.add_temporal_downsample else (0, 0, 0, 0, 1, 1)
222
+ x = nn.functional.pad(x, temporal_pad, mode="replicate")
223
+
224
+ x = self.conv(x)
225
+ return x
226
+
227
+
228
+ class DownsampleDCAE(nn.Module):
229
+ def __init__(self, in_channels: int, out_channels: int, add_temporal_downsample: bool = True):
230
+ super().__init__()
231
+ factor = 2 * 2 * 2 if add_temporal_downsample else 1 * 2 * 2
232
+ assert out_channels % factor == 0
233
+ self.conv = Conv3d(in_channels, out_channels // factor, kernel_size=3, stride=1, padding=1)
234
+
235
+ self.add_temporal_downsample = add_temporal_downsample
236
+ self.group_size = factor * in_channels // out_channels
237
+
238
+ def forward(self, x: Tensor):
239
+ r1 = 2 if self.add_temporal_downsample else 1
240
+ h = self.conv(x)
241
+ h = rearrange(h, "b c (f r1) (h r2) (w r3) -> b (r1 r2 r3 c) f h w", r1=r1, r2=2, r3=2)
242
+ shortcut = rearrange(x, "b c (f r1) (h r2) (w r3) -> b (r1 r2 r3 c) f h w", r1=r1, r2=2, r3=2)
243
+
244
+ B, C, T, H, W = shortcut.shape
245
+ shortcut = shortcut.view(B, h.shape[1], self.group_size, T, H, W).mean(dim=2)
246
+ return h + shortcut
247
+
248
+
249
+ class Upsample(nn.Module):
250
+ def __init__(self, in_channels: int, add_temporal_upsample: bool = True):
251
+ super().__init__()
252
+ self.add_temporal_upsample = add_temporal_upsample
253
+ self.scale_factor = (2, 2, 2) if add_temporal_upsample else (1, 2, 2) # THW
254
+ self.conv = Conv3d(in_channels, in_channels, kernel_size=3, stride=1, padding=1)
255
+
256
+ def forward(self, x: Tensor):
257
+ x = nn.functional.interpolate(x, scale_factor=self.scale_factor, mode="nearest")
258
+ x = self.conv(x)
259
+ return x
260
+
261
+
262
+ class UpsampleDCAE(nn.Module):
263
+ def __init__(self, in_channels: int, out_channels: int, add_temporal_upsample: bool = True):
264
+ super().__init__()
265
+ factor = 2 * 2 * 2 if add_temporal_upsample else 1 * 2 * 2
266
+ self.conv = Conv3d(in_channels, out_channels * factor, kernel_size=3, stride=1, padding=1)
267
+
268
+ self.add_temporal_upsample = add_temporal_upsample
269
+ self.repeats = factor * out_channels // in_channels
270
+
271
+ def forward(self, x: Tensor):
272
+ r1 = 2 if self.add_temporal_upsample else 1
273
+ h = self.conv(x)
274
+ h = rearrange(h, "b (r1 r2 r3 c) f h w -> b c (f r1) (h r2) (w r3)", r1=r1, r2=2, r3=2)
275
+ shortcut = x.repeat_interleave(repeats=self.repeats, dim=1)
276
+ shortcut = rearrange(shortcut, "b (r1 r2 r3 c) f h w -> b c (f r1) (h r2) (w r3)", r1=r1, r2=2, r3=2)
277
+ return h + shortcut
278
+
279
+
280
+ class Encoder(nn.Module):
281
+ def __init__(
282
+ self,
283
+ in_channels: int,
284
+ z_channels: int,
285
+ block_out_channels: Tuple[int, ...],
286
+ num_res_blocks: int,
287
+ ffactor_spatial: int,
288
+ ffactor_temporal: int,
289
+ downsample_match_channel: bool = True,
290
+ ):
291
+ super().__init__()
292
+ assert block_out_channels[-1] % (2 * z_channels) == 0
293
+
294
+ self.z_channels = z_channels
295
+ self.block_out_channels = block_out_channels
296
+ self.num_res_blocks = num_res_blocks
297
+
298
+ # downsampling
299
+ self.conv_in = Conv3d(in_channels, block_out_channels[0], kernel_size=3, stride=1, padding=1)
300
+
301
+ self.down = nn.ModuleList()
302
+ block_in = block_out_channels[0]
303
+ for i_level, ch in enumerate(block_out_channels):
304
+ block = nn.ModuleList()
305
+ block_out = ch
306
+ for _ in range(self.num_res_blocks):
307
+ block.append(ResnetBlock(in_channels=block_in, out_channels=block_out))
308
+ block_in = block_out
309
+ down = nn.Module()
310
+ down.block = block
311
+
312
+ add_spatial_downsample = bool(i_level < np.log2(ffactor_spatial))
313
+ add_temporal_downsample = add_spatial_downsample and bool(i_level >= np.log2(ffactor_spatial // ffactor_temporal))
314
+ if add_spatial_downsample or add_temporal_downsample:
315
+ assert i_level < len(block_out_channels) - 1
316
+ block_out = block_out_channels[i_level + 1] if downsample_match_channel else block_in
317
+ down.downsample = DownsampleDCAE(block_in, block_out, add_temporal_downsample)
318
+ block_in = block_out
319
+ self.down.append(down)
320
+
321
+ # middle
322
+ self.mid = nn.Module()
323
+ self.mid.block_1 = ResnetBlock(in_channels=block_in, out_channels=block_in)
324
+ self.mid.attn_1 = AttnBlock(block_in)
325
+ self.mid.block_2 = ResnetBlock(in_channels=block_in, out_channels=block_in)
326
+
327
+ # end
328
+ self.norm_out = nn.GroupNorm(num_groups=32, num_channels=block_in, eps=1e-6, affine=True)
329
+ self.conv_out = Conv3d(block_in, 2 * z_channels, kernel_size=3, stride=1, padding=1)
330
+
331
+ self.gradient_checkpointing = False
332
+
333
+ def forward(self, x: Tensor) -> Tensor:
334
+ with torch.no_grad():
335
+ use_checkpointing = bool(self.training and self.gradient_checkpointing)
336
+
337
+ # downsampling
338
+ h = self.conv_in(x)
339
+ for i_level in range(len(self.block_out_channels)):
340
+ for i_block in range(self.num_res_blocks):
341
+ h = forward_with_checkpointing(self.down[i_level].block[i_block], h, use_checkpointing=use_checkpointing)
342
+ if hasattr(self.down[i_level], "downsample"):
343
+ h = forward_with_checkpointing(self.down[i_level].downsample, h, use_checkpointing=use_checkpointing)
344
+
345
+ # middle
346
+ h = forward_with_checkpointing(self.mid.block_1, h, use_checkpointing=use_checkpointing)
347
+ h = forward_with_checkpointing(self.mid.attn_1, h, use_checkpointing=use_checkpointing)
348
+ h = forward_with_checkpointing(self.mid.block_2, h, use_checkpointing=use_checkpointing)
349
+
350
+ # end
351
+ group_size = self.block_out_channels[-1] // (2 * self.z_channels)
352
+ shortcut = rearrange(h, "b (c r) f h w -> b c r f h w", r=group_size).mean(dim=2)
353
+ h = self.norm_out(h)
354
+ h = swish(h)
355
+ h = self.conv_out(h)
356
+ h += shortcut
357
+ return h
358
+
359
+
360
+ class Decoder(nn.Module):
361
+ def __init__(
362
+ self,
363
+ z_channels: int,
364
+ out_channels: int,
365
+ block_out_channels: Tuple[int, ...],
366
+ num_res_blocks: int,
367
+ ffactor_spatial: int,
368
+ ffactor_temporal: int,
369
+ upsample_match_channel: bool = True,
370
+ ):
371
+ super().__init__()
372
+ assert block_out_channels[0] % z_channels == 0
373
+
374
+ self.z_channels = z_channels
375
+ self.block_out_channels = block_out_channels
376
+ self.num_res_blocks = num_res_blocks
377
+
378
+ # z to block_in
379
+ block_in = block_out_channels[0]
380
+ self.conv_in = Conv3d(z_channels, block_in, kernel_size=3, stride=1, padding=1)
381
+
382
+ # middle
383
+ self.mid = nn.Module()
384
+ self.mid.block_1 = ResnetBlock(in_channels=block_in, out_channels=block_in)
385
+ self.mid.attn_1 = AttnBlock(block_in)
386
+ self.mid.block_2 = ResnetBlock(in_channels=block_in, out_channels=block_in)
387
+
388
+ # upsampling
389
+ self.up = nn.ModuleList()
390
+ for i_level, ch in enumerate(block_out_channels):
391
+ block = nn.ModuleList()
392
+ block_out = ch
393
+ for _ in range(self.num_res_blocks + 1):
394
+ block.append(ResnetBlock(in_channels=block_in, out_channels=block_out))
395
+ block_in = block_out
396
+ up = nn.Module()
397
+ up.block = block
398
+
399
+ add_spatial_upsample = bool(i_level < np.log2(ffactor_spatial))
400
+ add_temporal_upsample = bool(i_level < np.log2(ffactor_temporal))
401
+ if add_spatial_upsample or add_temporal_upsample:
402
+ assert i_level < len(block_out_channels) - 1
403
+ block_out = block_out_channels[i_level + 1] if upsample_match_channel else block_in
404
+ up.upsample = UpsampleDCAE(block_in, block_out, add_temporal_upsample)
405
+ block_in = block_out
406
+ self.up.append(up)
407
+
408
+ # end
409
+ self.norm_out = nn.GroupNorm(num_groups=32, num_channels=block_in, eps=1e-6, affine=True)
410
+ self.conv_out = Conv3d(block_in, out_channels, kernel_size=3, stride=1, padding=1)
411
+
412
+ self.gradient_checkpointing = False
413
+
414
+
415
+ def forward(self, z: Tensor) -> Tensor:
416
+ with torch.no_grad():
417
+ use_checkpointing = bool(self.training and self.gradient_checkpointing)
418
+ # z to block_in
419
+ repeats = self.block_out_channels[0] // (self.z_channels)
420
+ h = self.conv_in(z) + z.repeat_interleave(repeats=repeats, dim=1)
421
+ # middle
422
+ h = forward_with_checkpointing(self.mid.block_1, h, use_checkpointing=use_checkpointing)
423
+ h = forward_with_checkpointing(self.mid.attn_1, h, use_checkpointing=use_checkpointing)
424
+ h = forward_with_checkpointing(self.mid.block_2, h, use_checkpointing=use_checkpointing)
425
+ # upsampling
426
+ for i_level in range(len(self.block_out_channels)):
427
+ for i_block in range(self.num_res_blocks + 1):
428
+ h = forward_with_checkpointing(self.up[i_level].block[i_block], h, use_checkpointing=use_checkpointing)
429
+ if hasattr(self.up[i_level], "upsample"):
430
+ h = forward_with_checkpointing(self.up[i_level].upsample, h, use_checkpointing=use_checkpointing)
431
+ # end
432
+ h = self.norm_out(h)
433
+ h = swish(h)
434
+ h = self.conv_out(h)
435
+ return h
436
+
437
+
438
+ class AutoencoderKLConv3D(ModelMixin, ConfigMixin):
439
+ _supports_gradient_checkpointing = True
440
+
441
+ @register_to_config
442
+ def __init__(
443
+ self,
444
+ in_channels: int,
445
+ out_channels: int,
446
+ latent_channels: int,
447
+ block_out_channels: Tuple[int, ...],
448
+ layers_per_block: int,
449
+ ffactor_spatial: int,
450
+ ffactor_temporal: int,
451
+ sample_size: int,
452
+ sample_tsize: int,
453
+ scaling_factor: float = None,
454
+ shift_factor: Optional[float] = None,
455
+ downsample_match_channel: bool = True,
456
+ upsample_match_channel: bool = True,
457
+ only_encoder: bool = False,
458
+ only_decoder: bool = False,
459
+ ):
460
+ super().__init__()
461
+ self.ffactor_spatial = ffactor_spatial
462
+ self.ffactor_temporal = ffactor_temporal
463
+ self.scaling_factor = scaling_factor
464
+ self.shift_factor = shift_factor
465
+
466
+ if not only_decoder:
467
+ self.encoder = Encoder(
468
+ in_channels=in_channels,
469
+ z_channels=latent_channels,
470
+ block_out_channels=block_out_channels,
471
+ num_res_blocks=layers_per_block,
472
+ ffactor_spatial=ffactor_spatial,
473
+ ffactor_temporal=ffactor_temporal,
474
+ downsample_match_channel=downsample_match_channel,
475
+ )
476
+ if not only_encoder:
477
+ self.decoder = Decoder(
478
+ z_channels=latent_channels,
479
+ out_channels=out_channels,
480
+ block_out_channels=list(reversed(block_out_channels)),
481
+ num_res_blocks=layers_per_block,
482
+ ffactor_spatial=ffactor_spatial,
483
+ ffactor_temporal=ffactor_temporal,
484
+ upsample_match_channel=upsample_match_channel,
485
+ )
486
+
487
+ self.use_slicing = False
488
+ self.slicing_bsz = 1
489
+ self.use_spatial_tiling = False
490
+ self.use_temporal_tiling = False
491
+ self.use_tiling_during_training = False
492
+
493
+ # only relevant if vae tiling is enabled
494
+ self.tile_sample_min_size = sample_size
495
+ self.tile_latent_min_size = sample_size // ffactor_spatial
496
+ self.tile_sample_min_tsize = sample_tsize
497
+ self.tile_latent_min_tsize = sample_tsize // ffactor_temporal
498
+ self.tile_overlap_factor = 0.125
499
+
500
+ self.use_compile = False
501
+
502
+ self.empty_cache = torch.empty(0, device="cuda")
503
+
504
+ def _set_gradient_checkpointing(self, module, value=False):
505
+ if isinstance(module, (Encoder, Decoder)):
506
+ module.gradient_checkpointing = value
507
+
508
+ def enable_tiling_during_training(self, use_tiling: bool = True):
509
+ self.use_tiling_during_training = use_tiling
510
+
511
+ def disable_tiling_during_training(self):
512
+ self.enable_tiling_during_training(False)
513
+
514
+ def enable_temporal_tiling(self, use_tiling: bool = True):
515
+ self.use_temporal_tiling = use_tiling
516
+
517
+ def disable_temporal_tiling(self):
518
+ self.enable_temporal_tiling(False)
519
+
520
+ def enable_spatial_tiling(self, use_tiling: bool = True):
521
+ self.use_spatial_tiling = use_tiling
522
+
523
+ def disable_spatial_tiling(self):
524
+ self.enable_spatial_tiling(False)
525
+
526
+ def enable_tiling(self, use_tiling: bool = True):
527
+ self.enable_spatial_tiling(use_tiling)
528
+
529
+ def disable_tiling(self):
530
+ self.disable_spatial_tiling()
531
+
532
+ def enable_slicing(self):
533
+ self.use_slicing = True
534
+
535
+ def disable_slicing(self):
536
+ self.use_slicing = False
537
+
538
+ def blend_h(self, a: torch.Tensor, b: torch.Tensor, blend_extent: int):
539
+ blend_extent = min(a.shape[-1], b.shape[-1], blend_extent)
540
+ for x in range(blend_extent):
541
+ b[:, :, :, :, x] = a[:, :, :, :, -blend_extent + x] * (1 - x / blend_extent) + b[:, :, :, :, x] * (x / blend_extent)
542
+ return b
543
+
544
+ def blend_v(self, a: torch.Tensor, b: torch.Tensor, blend_extent: int):
545
+ blend_extent = min(a.shape[-2], b.shape[-2], blend_extent)
546
+ for y in range(blend_extent):
547
+ b[:, :, :, y, :] = a[:, :, :, -blend_extent + y, :] * (1 - y / blend_extent) + b[:, :, :, y, :] * (y / blend_extent)
548
+ return b
549
+
550
+ def blend_t(self, a: torch.Tensor, b: torch.Tensor, blend_extent: int):
551
+ blend_extent = min(a.shape[-3], b.shape[-3], blend_extent)
552
+ for x in range(blend_extent):
553
+ b[:, :, x, :, :] = a[:, :, -blend_extent + x, :, :] * (1 - x / blend_extent) + b[:, :, x, :, :] * (x / blend_extent)
554
+ return b
555
+
556
+ def spatial_tiled_encode(self, x: torch.Tensor):
557
+ B, C, T, H, W = x.shape
558
+ overlap_size = int(self.tile_sample_min_size * (1 - self.tile_overlap_factor)) # 256 * (1 - 0.25) = 192
559
+ blend_extent = int(self.tile_latent_min_size * self.tile_overlap_factor) # 8 * 0.25 = 2
560
+ row_limit = self.tile_latent_min_size - blend_extent # 8 - 2 = 6
561
+
562
+ rows = []
563
+ for i in range(0, H, overlap_size):
564
+ row = []
565
+ for j in range(0, W, overlap_size):
566
+ tile = x[:, :, :, i: i + self.tile_sample_min_size, j: j + self.tile_sample_min_size]
567
+ tile = self.encoder(tile)
568
+ row.append(tile)
569
+ rows.append(row)
570
+ result_rows = []
571
+ for i, row in enumerate(rows):
572
+ result_row = []
573
+ for j, tile in enumerate(row):
574
+ if i > 0:
575
+ tile = self.blend_v(rows[i - 1][j], tile, blend_extent)
576
+ if j > 0:
577
+ tile = self.blend_h(row[j - 1], tile, blend_extent)
578
+ result_row.append(tile[:, :, :, :row_limit, :row_limit])
579
+ result_rows.append(torch.cat(result_row, dim=-1))
580
+ moments = torch.cat(result_rows, dim=-2)
581
+ return moments
582
+
583
+ def temporal_tiled_encode(self, x: torch.Tensor):
584
+ B, C, T, H, W = x.shape
585
+ overlap_size = int(self.tile_sample_min_tsize * (1 - self.tile_overlap_factor)) # 64 * (1 - 0.25) = 48
586
+ blend_extent = int(self.tile_latent_min_tsize * self.tile_overlap_factor) # 8 * 0.25 = 2
587
+ t_limit = self.tile_latent_min_tsize - blend_extent # 8 - 2 = 6
588
+
589
+ row = []
590
+ for i in range(0, T, overlap_size):
591
+ tile = x[:, :, i: i + self.tile_sample_min_tsize, :, :]
592
+ if self.use_spatial_tiling and (tile.shape[-1] > self.tile_sample_min_size or tile.shape[-2] > self.tile_sample_min_size):
593
+ tile = self.spatial_tiled_encode(tile)
594
+ else:
595
+ tile = self.encoder(tile)
596
+ row.append(tile)
597
+ result_row = []
598
+ for i, tile in enumerate(row):
599
+ if i > 0:
600
+ tile = self.blend_t(row[i - 1], tile, blend_extent)
601
+ result_row.append(tile[:, :, :t_limit, :, :])
602
+ moments = torch.cat(result_row, dim=-3)
603
+ return moments
604
+
605
+ def spatial_tiled_decode(self, z: torch.Tensor):
606
+ B, C, T, H, W = z.shape
607
+ overlap_size = int(self.tile_latent_min_size * (1 - self.tile_overlap_factor)) # 24 * (1 - 0.125) = 21
608
+ blend_extent = int(self.tile_sample_min_size * self.tile_overlap_factor) # 384 * 0.125 = 48
609
+ row_limit = self.tile_sample_min_size - blend_extent # 384 - 48 = 336
610
+
611
+ # 分布式/多卡:输入不做 padding -> 每 rank 对解码输出做右/下 padding -> GPU all_gather -> rank0重组/融合/裁剪
612
+ if dist.is_available() and dist.is_initialized() and dist.get_world_size() > 1:
613
+ rank = dist.get_rank()
614
+ world_size = dist.get_world_size()
615
+
616
+ # 统计tile
617
+ num_rows = math.ceil(H / overlap_size)
618
+ num_cols = math.ceil(W / overlap_size)
619
+ total_tiles = num_rows * num_cols
620
+ tiles_per_rank = math.ceil(total_tiles / world_size)
621
+
622
+ print(f"==={torch.distributed.get_rank()}, {total_tiles=}, {tiles_per_rank=}, {world_size=}")
623
+
624
+ # 本 rank 的 tile 索引(循环分配):rank, rank+world_size,
625
+ my_linear_indices = list(range(rank, total_tiles, world_size))
626
+ if my_linear_indices == []:
627
+ my_linear_indices = [0]
628
+ print(f"==={torch.distributed.get_rank()}, {my_linear_indices=}")
629
+ decoded_tiles = [] # tiles
630
+ decoded_metas = [] # (ri, rj, pad_w, pad_h)
631
+ H_out_std = self.tile_sample_min_size
632
+ W_out_std = self.tile_sample_min_size
633
+ for lin_idx in my_linear_indices:
634
+ ri = lin_idx // num_cols
635
+ rj = lin_idx % num_cols
636
+ i = ri * overlap_size
637
+ j = rj * overlap_size
638
+ tile = z[
639
+ :,
640
+ :,
641
+ :,
642
+ i : i + self.tile_latent_min_size,
643
+ j : j + self.tile_latent_min_size,
644
+ ]
645
+ dec = self.decoder(tile)
646
+ # 对边界 tile 的输出做右/下方向 padding 到标准尺寸
647
+ pad_h = max(0, H_out_std - dec.shape[-2])
648
+ pad_w = max(0, W_out_std - dec.shape[-1])
649
+ if pad_h > 0 or pad_w > 0:
650
+ dec = F.pad(dec, (0, pad_w, 0, pad_h, 0, 0), "constant", 0)
651
+ decoded_tiles.append(dec)
652
+ decoded_metas.append(torch.tensor([ri, rj, pad_w, pad_h], device=z.device, dtype=torch.int64))
653
+
654
+ # 各rank数量不一定相同,进行padding到相同长度
655
+ T_out = decoded_tiles[0].shape[2] if len(decoded_tiles) > 0 else (T-1)*self.ffactor_temporal+1
656
+ while len(decoded_tiles) < tiles_per_rank:
657
+ decoded_tiles.append(torch.zeros([1, 3, T_out, self.tile_sample_min_size, self.tile_sample_min_size], device=z.device, dtype=dec.dtype))
658
+ decoded_metas.append(torch.tensor([-1, -1, self.tile_sample_min_size, self.tile_sample_min_size], device=z.device, dtype=torch.int64))
659
+
660
+ # 进行gpu的all_gather
661
+ decoded_tiles = torch.stack(decoded_tiles, dim=0)
662
+ decoded_metas = torch.stack(decoded_metas, dim=0)
663
+
664
+ tiles_gather_list = [torch.empty_like(decoded_tiles) for _ in range(world_size)]
665
+ metas_gather_list = [torch.empty_like(decoded_metas) for _ in range(world_size)]
666
+
667
+ dist.all_gather(tiles_gather_list, decoded_tiles)
668
+ dist.all_gather(metas_gather_list, decoded_metas)
669
+
670
+ if rank != 0:
671
+ # 非0号rank返回空占位,结果只在rank0上有效
672
+ return torch.empty(0, device=z.device)
673
+
674
+ # rank0:根据 (ri, rj) 元信息重建 tile 网格;跳过占位项 (ri, rj) == (-1, -1)
675
+ rows = [[None for _ in range(num_cols)] for _ in range(num_rows)]
676
+ for r in range(world_size):
677
+ gathered_tiles_r = tiles_gather_list[r] # [tiles_per_rank, B, C, T, H, W]
678
+ gathered_metas_r = metas_gather_list[r] # [tiles_per_rank, 4],元素: (ri, rj, pad_w, pad_h)
679
+ for k in range(gathered_tiles_r.shape[0]):
680
+ ri = int(gathered_metas_r[k][0])
681
+ rj = int(gathered_metas_r[k][1])
682
+ if ri < 0 or rj < 0:
683
+ continue
684
+ if ri < num_rows and rj < num_cols:
685
+ # 去除padding
686
+ pad_w = int(gathered_metas_r[k][2])
687
+ pad_h = int(gathered_metas_r[k][3])
688
+ h_end = None if pad_h == 0 else -pad_h
689
+ w_end = None if pad_w == 0 else -pad_w
690
+ rows[ri][rj] = gathered_tiles_r[k][:, :, :, :h_end, :w_end]
691
+
692
+ result_rows = []
693
+ for i, row in enumerate(rows):
694
+ result_row = []
695
+ for j, tile in enumerate(row):
696
+ if tile is None:
697
+ continue
698
+ if i > 0:
699
+ tile = self.blend_v(rows[i - 1][j], tile, blend_extent)
700
+ if j > 0:
701
+ tile = self.blend_h(row[j - 1], tile, blend_extent)
702
+ result_row.append(tile[:, :, :, :row_limit, :row_limit])
703
+ result_rows.append(torch.cat(result_row, dim=-1))
704
+
705
+ dec = torch.cat(result_rows, dim=-2)
706
+ return dec
707
+
708
+ # 单卡:原有串行逻辑
709
+ rows = []
710
+ for i in range(0, H, overlap_size):
711
+ row = []
712
+ for j in range(0, W, overlap_size):
713
+ tile = z[
714
+ :,
715
+ :,
716
+ :,
717
+ i : i + self.tile_latent_min_size,
718
+ j : j + self.tile_latent_min_size,
719
+ ]
720
+ decoded = self.decoder(tile)
721
+ row.append(decoded)
722
+ rows.append(row)
723
+
724
+ result_rows = []
725
+ for i, row in enumerate(rows):
726
+ result_row = []
727
+ for j, tile in enumerate(row):
728
+ if i > 0:
729
+ tile = self.blend_v(rows[i - 1][j], tile, blend_extent)
730
+ if j > 0:
731
+ tile = self.blend_h(row[j - 1], tile, blend_extent)
732
+ result_row.append(tile[:, :, :, :row_limit, :row_limit])
733
+ result_rows.append(torch.cat(result_row, dim=-1))
734
+ dec = torch.cat(result_rows, dim=-2)
735
+ return dec
736
+
737
+ def temporal_tiled_decode(self, z: torch.Tensor):
738
+ B, C, T, H, W = z.shape
739
+ overlap_size = int(self.tile_latent_min_tsize * (1 - self.tile_overlap_factor)) # 8 * (1 - 0.25) = 6
740
+ blend_extent = int(self.tile_sample_min_tsize * self.tile_overlap_factor) # 64 * 0.25 = 16
741
+ t_limit = self.tile_sample_min_tsize - blend_extent # 64 - 16 = 48
742
+ assert 0 < overlap_size < self.tile_latent_min_tsize
743
+
744
+ row = []
745
+ for i in range(0, T, overlap_size):
746
+ tile = z[:, :, i: i + self.tile_latent_min_tsize, :, :]
747
+ if self.use_spatial_tiling and (tile.shape[-1] > self.tile_latent_min_size or tile.shape[-2] > self.tile_latent_min_size):
748
+ decoded = self.spatial_tiled_decode(tile)
749
+ else:
750
+ decoded = self.decoder(tile)
751
+ row.append(decoded)
752
+
753
+ result_row = []
754
+ for i, tile in enumerate(row):
755
+ if i > 0:
756
+ tile = self.blend_t(row[i - 1], tile, blend_extent)
757
+ result_row.append(tile[:, :, :t_limit, :, :])
758
+ dec = torch.cat(result_row, dim=-3)
759
+ return dec
760
+
761
+ def encode(self, x: Tensor, return_dict: bool = True):
762
+
763
+ def _encode(x):
764
+ if self.use_temporal_tiling and x.shape[-3] > self.tile_sample_min_tsize:
765
+ return self.temporal_tiled_encode(x)
766
+ if self.use_spatial_tiling and (x.shape[-1] > self.tile_sample_min_size or x.shape[-2] > self.tile_sample_min_size):
767
+ return self.spatial_tiled_encode(x)
768
+
769
+ if self.use_compile:
770
+ @torch.compile
771
+ def encoder(x):
772
+ return self.encoder(x)
773
+ return encoder(x)
774
+ return self.encoder(x)
775
+
776
+ if len(x.shape) != 5: # (B, C, T, H, W)
777
+ x = x[:, :, None]
778
+ assert len(x.shape) == 5 # (B, C, T, H, W)
779
+ if x.shape[2] == 1:
780
+ x = x.expand(-1, -1, self.ffactor_temporal, -1, -1)
781
+ else:
782
+ assert x.shape[2] != self.ffactor_temporal and x.shape[2] % self.ffactor_temporal == 0
783
+
784
+ if self.use_slicing and x.shape[0] > 1:
785
+ if self.slicing_bsz == 1:
786
+ encoded_slices = [_encode(x_slice) for x_slice in x.split(1)]
787
+ else:
788
+ sections = [self.slicing_bsz] * (x.shape[0] // self.slicing_bsz)
789
+ if x.shape[0] % self.slicing_bsz != 0:
790
+ sections.append(x.shape[0] % self.slicing_bsz)
791
+ encoded_slices = [_encode(x_slice) for x_slice in x.split(sections)]
792
+ h = torch.cat(encoded_slices)
793
+ else:
794
+ h = _encode(x)
795
+ posterior = DiagonalGaussianDistribution(h)
796
+
797
+ if not return_dict:
798
+ return (posterior,)
799
+
800
+ return AutoencoderKLOutput(latent_dist=posterior)
801
+
802
+ def decode(self, z: Tensor, return_dict: bool = True, generator=None):
803
+
804
+ def _decode(z):
805
+ if self.use_temporal_tiling and z.shape[-3] > self.tile_latent_min_tsize:
806
+ return self.temporal_tiled_decode(z)
807
+ if self.use_spatial_tiling and (z.shape[-1] > self.tile_latent_min_size or z.shape[-2] > self.tile_latent_min_size):
808
+ return self.spatial_tiled_decode(z)
809
+ return self.decoder(z)
810
+
811
+ if self.use_slicing and z.shape[0] > 1:
812
+ decoded_slices = [_decode(z_slice) for z_slice in z.split(1)]
813
+ decoded = torch.cat(decoded_slices)
814
+ else:
815
+ decoded = _decode(z)
816
+ if torch.distributed.is_initialized():
817
+ if torch.distributed.get_rank() != 0:
818
+ return self.empty_cache
819
+
820
+ if z.shape[-3] == 1:
821
+ decoded = decoded[:, :, -1:]
822
+ if not return_dict:
823
+ return (decoded,)
824
+
825
+ return DecoderOutput(sample=decoded)
826
+
827
+ def decode_dist(self, z: Tensor, return_dict: bool = True, generator=None):
828
+ z = z.cuda()
829
+ self.use_spatial_tiling = True
830
+ decoded = self.decode(z)
831
+ self.use_spatial_tiling = False
832
+ return decoded
833
+
834
+ def forward(
835
+ self,
836
+ sample: torch.Tensor,
837
+ sample_posterior: bool = False,
838
+ return_posterior: bool = True,
839
+ return_dict: bool = True
840
+ ):
841
+ posterior = self.encode(sample).latent_dist
842
+ z = posterior.sample() if sample_posterior else posterior.mode()
843
+ dec = self.decode(z).sample
844
+ return DecoderOutput(sample=dec, posterior=posterior) if return_dict else (dec, posterior)
845
+
846
+ def random_reset_tiling(self, x: torch.Tensor):
847
+ if x.shape[-3] == 1:
848
+ self.disable_spatial_tiling()
849
+ self.disable_temporal_tiling()
850
+ return
851
+
852
+ # tiling在input_shape和sample_size上限制很多,任意的input_shape和sample_size很可能不满足条件,因此这里使用固定值
853
+ min_sample_size = int(1 / self.tile_overlap_factor) * self.ffactor_spatial
854
+ min_sample_tsize = int(1 / self.tile_overlap_factor) * self.ffactor_temporal
855
+ sample_size = random.choice([None, 1 * min_sample_size, 2 * min_sample_size, 3 * min_sample_size])
856
+ if sample_size is None:
857
+ self.disable_spatial_tiling()
858
+ else:
859
+ self.tile_sample_min_size = sample_size
860
+ self.tile_latent_min_size = sample_size // self.ffactor_spatial
861
+ self.enable_spatial_tiling()
862
+
863
+ sample_tsize = random.choice([None, 1 * min_sample_tsize, 2 * min_sample_tsize, 3 * min_sample_tsize])
864
+ if sample_tsize is None:
865
+ self.disable_temporal_tiling()
866
+ else:
867
+ self.tile_sample_min_tsize = sample_tsize
868
+ self.tile_latent_min_tsize = sample_tsize // self.ffactor_temporal
869
+ self.enable_temporal_tiling()
870
+
871
+ def load_sharded_safetensors(model_dir):
872
+ """
873
+ 手动加载分片的 safetensors 文件
874
+
875
+ Args:
876
+ model_dir: 包含分片文件的目录路径
877
+
878
+ Returns:
879
+ 合并后的完整权重字典
880
+ """
881
+ # 获取所有分片文件并按编号排序
882
+ shard_files = []
883
+ for file in os.listdir(model_dir):
884
+ if file.endswith(".safetensors"):
885
+ shard_files.append(file)
886
+
887
+ # 按分片编号排序
888
+ shard_files.sort(key=lambda x: int(x.split("-")[1]))
889
+
890
+ print(f"找到 {len(shard_files)} 个分片文件")
891
+
892
+ # 合并所有权重
893
+ merged_state_dict = dict()
894
+
895
+ for shard_file in shard_files:
896
+ shard_path = os.path.join(model_dir, shard_file)
897
+ print(f"加载分片: {shard_file}")
898
+
899
+ # 使用 safetensors 加载当前分片
900
+ with safe_open(shard_path, framework="pt", device="cpu") as f:
901
+ for key in f.keys():
902
+ tensor = f.get_tensor(key)
903
+ merged_state_dict[key] = tensor
904
+
905
+ print(f"合并完成,总键数量: {len(merged_state_dict)}")
906
+ return merged_state_dict
907
+
908
+ def load_weights(model, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
909
+ def update_state_dict(state_dict: dict[str, torch.Tensor], name, weight):
910
+ if name not in state_dict:
911
+ raise ValueError(f"Unexpected weight {name}")
912
+
913
+ model_tensor = state_dict[name]
914
+ if model_tensor.shape != weight.shape:
915
+ raise ValueError(
916
+ f"Shape mismatch for weight {name}: "
917
+ f"model tensor shape {model_tensor.shape} vs. "
918
+ f"loaded tensor shape {weight.shape}"
919
+ )
920
+ if isinstance(weight, torch.Tensor):
921
+ model_tensor.data.copy_(weight.data)
922
+ else:
923
+ raise ValueError(
924
+ f"Unsupported tensor type in load_weights "
925
+ f"for {name}: {type(weight)}"
926
+ )
927
+
928
+ loaded_params = set()
929
+ for name, load_tensor in weights.items():
930
+ updated = True
931
+ name = name.replace('vae.', '')
932
+ if name in model.state_dict():
933
+ update_state_dict(model.state_dict(), name, load_tensor)
934
+ else:
935
+ updated = False
936
+
937
+ if updated:
938
+ loaded_params.add(name)
939
+
940
+ return loaded_params
941
+
942
+ def _worker(path, config,
943
+ rank=None, world_size=None, port=None, req_queue=None, rsp_queue=None):
944
+ """
945
+ each rank's worker:
946
+ - idle: block on req_queue.get() (CPU blocking, no GPU)
947
+ - receive request: run runner.predict(), all ranks forward
948
+ - only rank0 put result to rsp_queue
949
+ """
950
+ # _tame_cpu_threads_and_comm()
951
+ # basic env
952
+ os.environ["MASTER_ADDR"] = "127.0.0.1"
953
+ os.environ["MASTER_PORT"] = str(port)
954
+ os.environ["WORLD_SIZE"] = str(world_size)
955
+ os.environ["RANK"] = str(rank)
956
+ os.environ["LOCAL_RANK"] = str(rank)
957
+
958
+ # device binding should be early than all CUDA operations
959
+ visible = torch.cuda.device_count()
960
+ assert visible >= world_size, f"可见卡数 {visible} < world_size {world_size}"
961
+ local_rank = int(os.environ["LOCAL_RANK"])
962
+
963
+ print(f"[worker {rank}] bind to cuda:{local_rank} (visible={visible})", flush=True)
964
+ if not torch.distributed.is_initialized():
965
+ dist.init_process_group("nccl")
966
+ torch.cuda.set_device(local_rank)
967
+ #from .. import load_vae
968
+
969
+ #vae = load_vae(vae_type, vae_precision, device, logger, args, weights_only, only_encoder, only_decoder, sample_size, skip_create_dist=True)
970
+ #vae = vae.cuda()
971
+ vae = AutoencoderKLConv3D.from_config(config)
972
+ merged_state_dict = load_sharded_safetensors(path)
973
+ loaded_params = load_weights(vae, merged_state_dict)
974
+ vae = vae.cuda()
975
+ vae.eval() # 关闭 Dropout、BatchNorm 训练行为
976
+ for param in vae.parameters():
977
+ param.requires_grad = False #
978
+
979
+ while True:
980
+ req = req_queue.get() # blocking
981
+ if req == "__STOP__":
982
+ break
983
+ out = vae.decode_dist(req, return_dict=False)
984
+ if rank == 0:
985
+ rsp_queue.put(out)
986
+
987
+ #try:
988
+ # while True:
989
+ # # blocking on CPU queue
990
+ # req = req_queue.get() # blocking
991
+ # if req == "__STOP__":
992
+ # break
993
+ # out = vae.decode_dist(req, return_dict=False)
994
+ # if rank == 0:
995
+ # rsp_queue.put(out)
996
+ #finally:
997
+ # # destroy process group before exit
998
+ # try:
999
+ # dist.destroy_process_group()
1000
+ # except Exception:
1001
+ # pass
1002
+
1003
+ #def _find_free_port():
1004
+ # import socket
1005
+ # with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
1006
+ # s.bind(("127.0.0.1", 0))
1007
+ # return s.getsockname()[1]
1008
+
1009
+ # 避免端口冲突的常见做法
1010
+ def _find_free_port(start_port=8100, max_attempts=900):
1011
+ import socket
1012
+ """获取一个可用的端口"""
1013
+ for port in range(start_port, start_port + max_attempts):
1014
+ try:
1015
+ with socket.socket() as s:
1016
+ s.bind(('localhost', port))
1017
+ return s.getsockname()[1] # 返回实际绑定的端口
1018
+ except OSError:
1019
+ continue
1020
+ raise RuntimeError("找不到可用端口")
1021
+
1022
+ class AutoencoderKLConv3D_Dist(AutoencoderKLConv3D):
1023
+ def __init__(
1024
+ self,
1025
+ in_channels: int,
1026
+ out_channels: int,
1027
+ latent_channels: int,
1028
+ block_out_channels: Tuple[int, ...],
1029
+ layers_per_block: int,
1030
+ ffactor_spatial: int,
1031
+ ffactor_temporal: int,
1032
+ sample_size: int,
1033
+ sample_tsize: int,
1034
+ scaling_factor: float = None,
1035
+ shift_factor: Optional[float] = None,
1036
+ downsample_match_channel: bool = True,
1037
+ upsample_match_channel: bool = True,
1038
+ only_encoder: bool = False,
1039
+ only_decoder: bool = False,
1040
+ ):
1041
+ super().__init__(in_channels, out_channels, latent_channels, block_out_channels, layers_per_block, ffactor_spatial, ffactor_temporal, sample_size, sample_tsize, scaling_factor, shift_factor, downsample_match_channel, upsample_match_channel, only_encoder, only_decoder)
1042
+
1043
+ def create_dist(self, path, config,
1044
+ ):
1045
+ self.world_size = 8
1046
+ self.port = _find_free_port()
1047
+ ctx = mp.get_context("spawn")
1048
+ # 每个 rank 一个请求队列(纯 CPU),再加一个公共响应队列
1049
+ self.req_queues = [ctx.Queue() for _ in range(self.world_size)]
1050
+ self.rsp_queue = ctx.Queue()
1051
+
1052
+ self.procs = []
1053
+ for rank in range(self.world_size):
1054
+ p = ctx.Process(
1055
+ target=_worker,
1056
+ args=(
1057
+ path, config,
1058
+ rank, self.world_size, self.port,
1059
+ self.req_queues[rank], self.rsp_queue,
1060
+ ),
1061
+ daemon=True,
1062
+ )
1063
+ p.start()
1064
+ self.procs.append(p)
1065
+
1066
+ def decode(self, z: Tensor, return_dict: bool = True, generator=None):
1067
+ """
1068
+ synchronous inference: put the same request to all ranks' queues.
1069
+ return rank0's result.
1070
+ """
1071
+ # check alive
1072
+ for p in self.procs:
1073
+ if not p.is_alive():
1074
+ raise RuntimeError("One of the processes is not alive")
1075
+
1076
+ # put to each rank's queue
1077
+ for q in self.req_queues:
1078
+ q.put(z)
1079
+
1080
+ # wait for rank0's result
1081
+ return self.rsp_queue.get(timeout=None)
cache_utils.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import math
4
+ from typing import Tuple
5
+
6
+ def cache_init(cache_interval, max_order, num_steps=None,
7
+ enable_first_enhance=False, first_enhance_steps=3,
8
+ enable_tailing_enhance=False, tailing_enhance_steps=1,
9
+ low_freqs_order=0, high_freqs_order=2):
10
+ cache_dic = {}
11
+ cache_dic['counter']= 0
12
+ cache_dic['current_step'] = 0
13
+ cache_dic['cache_interval']= cache_interval
14
+ cache_dic['max_order'] = max_order
15
+ cache_dic['num_steps'] = num_steps
16
+
17
+ # enhance related utils
18
+
19
+ # first enhance: fully compute first some steps, enhancing contour infos
20
+ cache_dic['enable_first_enhance'] = enable_first_enhance
21
+ cache_dic['first_enhance_steps'] = first_enhance_steps
22
+
23
+ # tailing enhance: fully compute the last 1 steps, enhancing details
24
+ cache_dic['enable_tailing_enhance'] = enable_tailing_enhance
25
+ cache_dic['tailing_enhance_steps'] = tailing_enhance_steps
26
+
27
+ # freqs related utils
28
+ cache_dic['low_freqs_order'] = low_freqs_order
29
+ cache_dic['high_freqs_order'] = high_freqs_order
30
+
31
+ # features for training-aware cache, here we don't use these
32
+ cache_dic['enable_force_control']= False
33
+ cache_dic['force_compute']=False
34
+ return cache_dic
35
+
36
+ class TaylorCacheContainer(nn.Module):
37
+ def __init__(self, max_order):
38
+ super().__init__()
39
+ self.max_order = max_order
40
+ # 逐个注册buffer
41
+ for i in range(max_order + 1):
42
+ self.register_buffer(f"derivative_{i}", None, persistent=False)
43
+ self.register_buffer(f"temp_derivative_{i}", None, persistent=False)
44
+
45
+ def get_derivative(self, order):
46
+ return getattr(self, f"derivative_{order}")
47
+
48
+ def set_derivative(self, order, tensor):
49
+ setattr(self, f"derivative_{order}", tensor)
50
+
51
+ def set_temp_derivative(self, order, tensor):
52
+ setattr(self, f"temp_derivative_{order}", tensor)
53
+
54
+ def get_temp_derivative(self, order):
55
+ return getattr(self, f"temp_derivative_{order}")
56
+
57
+ def clear_temp_derivative(self):
58
+ for i in range(self.max_order + 1):
59
+ setattr(self, f"temp_derivative_{i}", None)
60
+
61
+ def move_temp_to_derivative(self):
62
+ for i in range(self.max_order + 1):
63
+ if self.get_temp_derivative(i) is not None:
64
+ setattr(self, f"derivative_{i}", self.get_temp_derivative(i))
65
+ else:
66
+ break
67
+ self.clear_temp_derivative()
68
+
69
+ def get_all_derivatives(self):
70
+ return [getattr(self, f"derivative_{i}") for i in range(self.max_order + 1)]
71
+
72
+ def get_all_filled_derivatives(self):
73
+ return [self.get_derivative(i) for i in range(self.max_order + 1) if self.get_derivative(i) is not None]
74
+
75
+ def taylor_formula(self, distance):
76
+ output = 0
77
+ for i in range(len(self.get_all_filled_derivatives())):
78
+ output += (1 / math.factorial(i)) * self.get_derivative(i) * (distance ** i)
79
+ return output
80
+
81
+ def derivatives_computation(self, x, distance):
82
+ '''
83
+ x: tensor, the new x_0
84
+ distance: int, the distance between the current step and the last full computation step
85
+ '''
86
+ self.set_temp_derivative(0, x)
87
+ for i in range(self.max_order):
88
+ if self.get_derivative(i) is not None:
89
+ self.set_temp_derivative(i+1, (self.get_temp_derivative(i) - self.get_derivative(i)) / distance)
90
+ else:
91
+ break
92
+ self.move_temp_to_derivative()
93
+
94
+ def clear_derivatives(self):
95
+ for i in range(self.max_order + 1):
96
+ setattr(self, f"derivative_{i}", None)
97
+ setattr(self, f"temp_derivative_{i}", None)
98
+
99
+
100
+ @torch.compile
101
+ def decomposition_FFT(x: torch.Tensor, cutoff_ratio: float = 0.1) -> Tuple[torch.Tensor, torch.Tensor]:
102
+ """
103
+ Fast Fourier Transform frequency domain decomposition
104
+
105
+ Args:
106
+ x: Input tensor [B, H*W, D]
107
+ cutoff_ratio: Cutoff frequency ratio (0~0.5)
108
+
109
+ Returns:
110
+ Tuple of (low_freq, high_freq) tensors with same dtype as input
111
+ """
112
+ orig_dtype = x.dtype
113
+ device = x.device
114
+
115
+ x_fp32 = x.to(torch.float32) # Convert to fp32 for FFT compatibility
116
+
117
+ B, HW, D = x_fp32.shape
118
+ freq = torch.fft.fft(x_fp32, dim=1) # FFT on spatial dimension
119
+
120
+ freqs = torch.fft.fftfreq(HW, d=1.0, device=device)
121
+ cutoff = cutoff_ratio * freqs.abs().max()
122
+
123
+ # Create frequency masks
124
+ low_mask = freqs.abs() <= cutoff
125
+ high_mask = ~low_mask
126
+
127
+ low_mask = low_mask[None, :, None] # Broadcast to (B, HW, D)
128
+ high_mask = high_mask[None, :, None]
129
+
130
+ low_freq_complex = freq * low_mask
131
+ high_freq_complex = freq * high_mask
132
+
133
+ # IFFT and take real part
134
+ low_fp32 = torch.fft.ifft(low_freq_complex, dim=1).real
135
+ high_fp32 = torch.fft.ifft(high_freq_complex, dim=1).real
136
+
137
+ low = low_fp32.to(device=device, dtype=orig_dtype)
138
+ high = high_fp32.to(device=device, dtype=orig_dtype)
139
+
140
+ return low, high
141
+
142
+ @torch.compile
143
+ def reconstruction(low_freq: torch.Tensor, high_freq: torch.Tensor) -> torch.Tensor:
144
+ return low_freq + high_freq
145
+
146
+ class CacheWithFreqsContainer(nn.Module):
147
+ def __init__(self, max_order):
148
+ super().__init__()
149
+ self.max_order = max_order
150
+ # 逐个注册buffer
151
+ for i in range(max_order + 1):
152
+ self.register_buffer(f"derivative_{i}_low_freqs", None, persistent=False)
153
+ self.register_buffer(f"derivative_{i}_high_freqs", None, persistent=False)
154
+ self.register_buffer(f"temp_derivative_{i}_low_freqs", None, persistent=False)
155
+ self.register_buffer(f"temp_derivative_{i}_high_freqs", None, persistent=False)
156
+
157
+ def get_derivative(self, order, freqs):
158
+ return getattr(self, f"derivative_{order}_{freqs}")
159
+
160
+ def set_derivative(self, order, freqs, tensor):
161
+ setattr(self, f"derivative_{order}_{freqs}", tensor)
162
+
163
+ def set_temp_derivative(self, order, freqs, tensor):
164
+ setattr(self, f"temp_derivative_{order}_{freqs}", tensor)
165
+
166
+ def get_temp_derivative(self, order, freqs):
167
+ return getattr(self, f"temp_derivative_{order}_{freqs}")
168
+
169
+ def move_temp_to_derivative(self):
170
+ for i in range(self.max_order + 1):
171
+ if self.get_temp_derivative(i, "low_freqs") is not None:
172
+ setattr(self, f"derivative_{i}_low_freqs", self.get_temp_derivative(i, "low_freqs"))
173
+ if self.get_temp_derivative(i, "high_freqs") is not None:
174
+ setattr(self, f"derivative_{i}_high_freqs", self.get_temp_derivative(i, "high_freqs"))
175
+ else:
176
+ break
177
+ self.clear_temp_derivative()
178
+
179
+ def get_all_filled_derivatives(self, freqs):
180
+ return [self.get_derivative(i, freqs) for i in range(self.max_order + 1) if self.get_derivative(i, freqs) is not None]
181
+
182
+ def taylor_formula(self, distance):
183
+ low_freqs_output = 0
184
+ high_freqs_output = 0
185
+ for i in range(len(self.get_all_filled_derivatives("low_freqs"))):
186
+ low_freqs_output += (1 / math.factorial(i)) * self.get_derivative(i, "low_freqs") * (distance ** i)
187
+ for i in range(len(self.get_all_filled_derivatives("high_freqs"))):
188
+ high_freqs_output += (1 / math.factorial(i)) * self.get_derivative(i, "high_freqs") * (distance ** i)
189
+ return reconstruction(low_freqs_output, high_freqs_output)
190
+
191
+ def hermite_formula(self, distance):
192
+ low_freqs_output = 0
193
+ high_freqs_output = 0
194
+ for i in range(len(self.get_all_filled_derivatives("low_freqs"))):
195
+ low_freqs_output += (1 / math.factorial(i)) * self.get_derivative(i, "low_freqs") * (distance ** i)
196
+ for i in range(len(self.get_all_filled_derivatives("high_freqs"))):
197
+ high_freqs_output += (1 / math.factorial(i)) * self.get_derivative(i, "high_freqs") * (distance ** i)
198
+ return reconstruction(low_freqs_output, high_freqs_output)
199
+
200
+ def derivatives_computation(self, x, distance, low_freqs_order, high_freqs_order):
201
+ '''
202
+ x: tensor, the new x_0
203
+ distance: int, the distance between the current step and the last full computation step
204
+ '''
205
+ x_low, x_high = decomposition_FFT(x, cutoff_ratio=0.1)
206
+ self.set_temp_derivative(0, "low_freqs", x_low)
207
+ self.set_temp_derivative(0, "high_freqs", x_high)
208
+ for i in range(low_freqs_order):
209
+ if self.get_derivative(i, "low_freqs") is not None:
210
+ self.set_temp_derivative(i+1, "low_freqs", (self.get_temp_derivative(i, "low_freqs") - self.get_derivative(i, "low_freqs")) / distance)
211
+ for i in range(high_freqs_order):
212
+ if self.get_derivative(i, "high_freqs") is not None:
213
+ self.set_temp_derivative(i+1, "high_freqs", (self.get_temp_derivative(i, "high_freqs") - self.get_derivative(i, "high_freqs")) / distance)
214
+ self.move_temp_to_derivative()
215
+
216
+ def clear_temp_derivative(self):
217
+ for i in range(self.max_order + 1):
218
+ setattr(self, f"temp_derivative_{i}_low_freqs", None)
219
+ setattr(self, f"temp_derivative_{i}_high_freqs", None)
220
+
221
+ def clear_derivatives(self):
222
+ for i in range(self.max_order + 1):
223
+ setattr(self, f"derivative_{i}_low_freqs", None)
224
+ setattr(self, f"derivative_{i}_high_freqs", None)
225
+ setattr(self, f"temp_derivative_{i}_low_freqs", None)
226
+ setattr(self, f"temp_derivative_{i}_high_freqs", None)
config.json ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cfg_distilled": true,
3
+ "use_meanflow": true,
4
+ "add_classification_head": false,
5
+ "anyres_pooling_size": 2,
6
+ "anyres_vit_max_image_size": null,
7
+ "anyres_vit_two_views": false,
8
+ "architectures": [
9
+ "HunyuanImage3ForCausalMM"
10
+ ],
11
+ "auto_map": {
12
+ "AutoConfig": "configuration_hunyuan_image_3.HunyuanImage3Config",
13
+ "AutoModel": "modeling_hunyuan_image_3.HunyuanImage3Model",
14
+ "AutoModelForCausalLM": "modeling_hunyuan_image_3.HunyuanImage3ForCausalMM"
15
+ },
16
+ "attention_bias": false,
17
+ "attention_dropout": 0.0,
18
+ "attention_head_dim": 128,
19
+ "bos_token_id": 127958,
20
+ "cla_share_factor": 2,
21
+ "class_num": 0,
22
+ "dense_list": [
23
+ 4096,
24
+ 0
25
+ ],
26
+ "eod_token_id": 3,
27
+ "eos_token_id": 127957,
28
+ "group_limited_greedy": false,
29
+ "hidden_act": "silu",
30
+ "hidden_size": 4096,
31
+ "im_end_id": 128001,
32
+ "im_newline_id": 11,
33
+ "im_start_id": 128000,
34
+ "image_token_id": 128006,
35
+ "initializer_range": 0.02,
36
+ "intermediate_size": 3072,
37
+ "kv_lora_rank": null,
38
+ "mask_init_id": 12,
39
+ "max_position_embeddings": 22800,
40
+ "mlp_bias": false,
41
+ "model_type": "hunyuan_image_3_moe",
42
+ "moe_drop_tokens": false,
43
+ "moe_intermediate_size": [
44
+ 3072,
45
+ 3072,
46
+ 3072,
47
+ 3072,
48
+ 3072,
49
+ 3072,
50
+ 3072,
51
+ 3072,
52
+ 3072,
53
+ 3072,
54
+ 3072,
55
+ 3072,
56
+ 3072,
57
+ 3072,
58
+ 3072,
59
+ 3072,
60
+ 3072,
61
+ 3072,
62
+ 3072,
63
+ 3072,
64
+ 3072,
65
+ 3072,
66
+ 3072,
67
+ 3072,
68
+ 3072,
69
+ 3072,
70
+ 3072,
71
+ 3072,
72
+ 3072,
73
+ 3072,
74
+ 3072,
75
+ 3072
76
+ ],
77
+ "moe_layer_num_skipped": 0,
78
+ "moe_random_routing_dropped_token": false,
79
+ "moe_topk": [
80
+ 8,
81
+ 8,
82
+ 8,
83
+ 8,
84
+ 8,
85
+ 8,
86
+ 8,
87
+ 8,
88
+ 8,
89
+ 8,
90
+ 8,
91
+ 8,
92
+ 8,
93
+ 8,
94
+ 8,
95
+ 8,
96
+ 8,
97
+ 8,
98
+ 8,
99
+ 8,
100
+ 8,
101
+ 8,
102
+ 8,
103
+ 8,
104
+ 8,
105
+ 8,
106
+ 8,
107
+ 8,
108
+ 8,
109
+ 8,
110
+ 8,
111
+ 8
112
+ ],
113
+ "n_group": false,
114
+ "norm_topk_prob": true,
115
+ "norm_type": "rms",
116
+ "num_attention_heads": 32,
117
+ "num_experts": 64,
118
+ "num_hidden_layers": 32,
119
+ "num_key_value_heads": 8,
120
+ "num_media_embeds": 257,
121
+ "num_shared_expert": [
122
+ 1,
123
+ 1,
124
+ 1,
125
+ 1,
126
+ 1,
127
+ 1,
128
+ 1,
129
+ 1,
130
+ 1,
131
+ 1,
132
+ 1,
133
+ 1,
134
+ 1,
135
+ 1,
136
+ 1,
137
+ 1,
138
+ 1,
139
+ 1,
140
+ 1,
141
+ 1,
142
+ 1,
143
+ 1,
144
+ 1,
145
+ 1,
146
+ 1,
147
+ 1,
148
+ 1,
149
+ 1,
150
+ 1,
151
+ 1,
152
+ 1,
153
+ 1
154
+ ],
155
+ "pad_id": 128009,
156
+ "pad_token_id": 128009,
157
+ "pool_type": "last",
158
+ "position_embedding_xdrope": false,
159
+ "pretraining_tp": 1,
160
+ "q_lora_rank": null,
161
+ "qk_nope_head_dim": null,
162
+ "qk_rope_head_dim": null,
163
+ "rms_norm_eps": 1e-05,
164
+ "rope_scaling": {
165
+ "alpha": 1.0,
166
+ "beta_fast": 32,
167
+ "beta_slow": 1,
168
+ "factor": 1.0,
169
+ "mscale": 1.0,
170
+ "mscale_all_dim": 1.0,
171
+ "type": "custom"
172
+ },
173
+ "rope_theta": 10000.0,
174
+ "routed_scaling_factor": false,
175
+ "skip_cls_token": false,
176
+ "text_end_id": 7,
177
+ "text_start_id": 6,
178
+ "tie_word_embeddings": false,
179
+ "topk_group": false,
180
+ "torch_dtype": "bfloat16",
181
+ "transformers_version": "4.50.0",
182
+ "use_cache": true,
183
+ "use_cla": false,
184
+ "use_mixed_mlp_moe": true,
185
+ "use_mla": false,
186
+ "use_qk_norm": true,
187
+ "use_rotary_pos_emb": true,
188
+ "v_head_dim": null,
189
+ "video_end_id": 10,
190
+ "video_start_id": 9,
191
+ "vit_add_patchemb_bias": false,
192
+ "vit_input_resolution": 224,
193
+ "vit_mapping_type": "resampler",
194
+ "vit_norm_type": "fused",
195
+ "vit_patch": 1,
196
+ "vit_path": null,
197
+ "vit_remove_prenorm": false,
198
+ "vit_token": 64,
199
+ "vit_type": "siglip2-so400m-patch16-naflex",
200
+ "vit_used_rms_norm": false,
201
+ "vocab_size": 133120,
202
+ "xdrope_section": null,
203
+ "head_dim": 128,
204
+ "rope_type": "2d",
205
+ "vae_downsample_factor": [
206
+ 16,
207
+ 16
208
+ ],
209
+ "vit_downsample_factor": [
210
+ 16,
211
+ 16
212
+ ],
213
+ "cond_token_attn_type": "joint_full",
214
+ "cond_image_type": "vae_vit",
215
+ "vae_type": "hunyuan-image-vae-v1",
216
+ "vae_dtype": "float32",
217
+ "vae_autocast_dtype": "float16",
218
+ "vae": {
219
+ "_class_name": "AutoencoderKLConv3D",
220
+ "block_out_channels": [
221
+ 128,
222
+ 256,
223
+ 512,
224
+ 1024,
225
+ 1024
226
+ ],
227
+ "in_channels": 3,
228
+ "out_channels": 3,
229
+ "latent_channels": 32,
230
+ "layers_per_block": 2,
231
+ "ffactor_spatial": 16,
232
+ "ffactor_temporal": 4,
233
+ "sample_size": 384,
234
+ "sample_tsize": 96,
235
+ "downsample_match_channel": true,
236
+ "upsample_match_channel": true,
237
+ "scaling_factor": 0.562679178327931
238
+ },
239
+ "vit": {
240
+ "_attn_implementation": "sdpa",
241
+ "attention_dropout": 0.0,
242
+ "hidden_act": "gelu_pytorch_tanh",
243
+ "hidden_size": 1152,
244
+ "intermediate_size": 4304,
245
+ "layer_norm_eps": 1e-06,
246
+ "num_attention_heads": 16,
247
+ "num_channels": 3,
248
+ "num_hidden_layers": 27,
249
+ "num_patches": 256,
250
+ "patch_size": 16,
251
+ "torch_dtype": "float32",
252
+ "output_attentions": false,
253
+ "output_hidden_states": false,
254
+ "use_return_dict": true
255
+ },
256
+ "vit_processor": {
257
+ "do_convert_rgb": null,
258
+ "do_normalize": true,
259
+ "do_rescale": true,
260
+ "do_resize": true,
261
+ "image_mean": [
262
+ 0.5,
263
+ 0.5,
264
+ 0.5
265
+ ],
266
+ "image_processor_type": "Siglip2ImageProcessorFast",
267
+ "image_std": [
268
+ 0.5,
269
+ 0.5,
270
+ 0.5
271
+ ],
272
+ "max_num_patches": 1024,
273
+ "patch_size": 16,
274
+ "processor_class": "Siglip2Processor",
275
+ "resample": 2,
276
+ "rescale_factor": 0.00392156862745098
277
+ },
278
+ "vit_aligner": {
279
+ "projector_type": "mlp_gelu",
280
+ "input_dim": 1152,
281
+ "n_embed": 4096,
282
+ "depth": 2,
283
+ "torch_dtype": "float32"
284
+ }
285
+ }
configuration_hunyuan_image_3.py ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Licensed under the TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # https://github.com/Tencent-Hunyuan/HunyuanImage-3.0/blob/main/LICENSE
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ # ==============================================================================
13
+
14
+ from transformers.configuration_utils import PretrainedConfig
15
+ from transformers.utils import logging
16
+ from typing import List, Union, Optional
17
+
18
+
19
+ logger = logging.get_logger(__name__)
20
+
21
+
22
+ class HunyuanImage3Config(PretrainedConfig):
23
+ r"""
24
+ This is the configuration class to store the configuration of a [`HunyuanImage3Model`]. It is used to instantiate
25
+ an Hunyuan model according to the specified arguments, defining the model architecture. Instantiating a
26
+ configuration with the defaults will yield a similar configuration to that of the Hunyuan-7B.
27
+
28
+ Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
29
+ documentation from [`PretrainedConfig`] for more information.
30
+
31
+
32
+ Args:
33
+ vocab_size (`int`, *optional*, defaults to 32000):
34
+ Vocabulary size of the Hunyuan Image 3 model. Defines the number of different tokens that can be
35
+ represented by the `inputs_ids` passed when calling [`HunyuanImage3Model`]
36
+ hidden_size (`int`, *optional*, defaults to 4096):
37
+ Dimension of the hidden representations.
38
+ intermediate_size (`int`, *optional*, defaults to 11008):
39
+ Dimension of the MLP representations or shared MLP representations.
40
+ moe_intermediate_size (`int` or `List`, *optional*, defaults to 11008):
41
+ Dimension of the MLP representations in MoE. Use a list if you want a different size per layer.
42
+ num_hidden_layers (`int`, *optional*, defaults to 32):
43
+ Number of hidden layers in the Transformer decoder.
44
+ num_attention_heads (`int`, *optional*, defaults to 32):
45
+ Number of attention heads for each attention layer in the Transformer decoder.
46
+ num_key_value_heads (`int`, *optional*):
47
+ This is the number of key_value heads that should be used to implement Grouped Query Attention. If
48
+ `num_key_value_heads=num_attention_heads`, the model will use Multi Head Attention (MHA), if
49
+ `num_key_value_heads=1 the model will use Multi Query Attention (MQA) otherwise GQA is used. When
50
+ converting a multi-head checkpoint to a GQA checkpoint, each group key and value head should be constructed
51
+ by meanpooling all the original heads within that group. For more details checkout [this
52
+ paper](https://arxiv.org/pdf/2305.13245.pdf). If it is not specified, will default to
53
+ `num_attention_heads`.
54
+ hidden_act (`str` or `function`, *optional*, defaults to `"silu"`):
55
+ The non-linear activation function (function or string) in the decoder.
56
+ max_position_embeddings (`int`, *optional*, defaults to 2048):
57
+ The maximum sequence length that this model might ever be used with.
58
+ initializer_range (`float`, *optional*, defaults to 0.02):
59
+ The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
60
+ rms_norm_eps (`float`, *optional*, defaults to 1e-06):
61
+ The epsilon used by the rms normalization layers.
62
+ use_cache (`bool`, *optional*, defaults to `True`):
63
+ Whether or not the model should return the last key/values attentions (not used by all models). Only
64
+ relevant if `config.is_decoder=True`.
65
+ pad_token_id (`int`, *optional*):
66
+ Padding token id.
67
+ bos_token_id (`int`, *optional*, defaults to 1):
68
+ Beginning of stream token id.
69
+ eos_token_id (`int`, *optional*, defaults to 2):
70
+ End of stream token id.
71
+ pretraining_tp (`int`, *optional*, defaults to 1):
72
+ Experimental feature. Tensor parallelism rank used during pretraining. Please refer to [this
73
+ document](https://huggingface.co/docs/transformers/parallelism) to understand more about it. This value is
74
+ necessary to ensure exact reproducibility of the pretraining results. Please refer to [this
75
+ issue](https://github.com/pytorch/pytorch/issues/76232).
76
+ tie_word_embeddings (`bool`, *optional*, defaults to `False`):
77
+ Whether to tie weight embeddings
78
+ rope_theta (`float`, *optional*, defaults to 10000.0):
79
+ The base period of the RoPE embeddings.
80
+ rope_scaling (`Dict`, *optional*):
81
+ Dictionary containing the scaling configuration for the RoPE embeddings. Currently supports two scaling
82
+ strategies: linear and dynamic. Their scaling factor must be a float greater than 1. The expected format is
83
+ `{"type": strategy name, "factor": scaling factor}`. When using this flag, don't update
84
+ `max_position_embeddings` to the expected new maximum. See the following thread for more information on how
85
+ these scaling strategies behave:
86
+ https://www.reddit.com/r/LocalLLaMA/comments/14mrgpr/dynamically_scaled_rope_further_increases/. This is an
87
+ experimental feature, subject to breaking API changes in future versions.
88
+ attention_bias (`bool`, defaults to `False`, *optional*, defaults to `False`):
89
+ Whether to use a bias in the query, key, value and output projection layers during self-attention.
90
+ attention_dropout (`float`, *optional*, defaults to 0.0):
91
+ The dropout ratio for the attention probabilities.
92
+ use_qk_norm (`bool`, *optional*, defaults to `False`):
93
+ Whether query and key in attention use norm
94
+ use_cla (`bool`, *optional*, defaults to `False`):
95
+ Whether to use CLA in attention
96
+ cla_share_factor (`int`, *optional*, defaults to 1):
97
+ The share factor of CLA
98
+ num_experts (`int` or `List`, *optional*, defaults to 1):
99
+ The number of experts for moe. If it is a list, it will be used as the number of experts for each layer.
100
+ num_shared_expert (`int` or `List`, *optional*, defaults to 1):
101
+ The number of shared experts for moe. If it is a list, it will be used as the number of shared experts
102
+ for each layer.
103
+ moe_topk (`int` or `List`, *optional*, defaults to 1):
104
+ The topk value for moe. If it is a list, it will be used as the topk value for each layer.
105
+ capacity_factor (Not used) (`float` or `List`, *optional*, defaults to 1.0):
106
+ The capacity factor for moe. If it is a list, it will be used as the capacity factor for each layer.
107
+ moe_layer_num_skipped (`int`, *optional*, defaults to 0):
108
+ First moe_layer_num_skipped layers do not use MoE.
109
+ """
110
+
111
+ model_type = "Hunyuan"
112
+ keys_to_ignore_at_inference = ["past_key_values"]
113
+
114
+ def __init__(
115
+ self,
116
+ vocab_size: int = 290943,
117
+ hidden_size: int = 4096,
118
+ intermediate_size: int = 11008,
119
+ moe_intermediate_size: Union[int, List] = None,
120
+ num_hidden_layers: int = 32,
121
+ num_attention_heads: int = 32,
122
+ num_key_value_heads: Optional[int] = None,
123
+ attention_head_dim: Optional[int] = None,
124
+ hidden_act="silu",
125
+ max_position_embeddings=2048,
126
+ initializer_range=0.02,
127
+ rms_norm_eps=1e-5,
128
+ use_cache=True,
129
+ pad_token_id=0,
130
+ bos_token_id=1,
131
+ eos_token_id=2,
132
+ eod_token_id=3,
133
+ im_start_id=4,
134
+ im_end_id=5,
135
+ text_start_id=6,
136
+ text_end_id=7,
137
+ image_token_id=8,
138
+ video_start_id=9,
139
+ video_end_id=10,
140
+ im_newline_id=11,
141
+ mask_init_id=12,
142
+ pretraining_tp=1,
143
+ tie_word_embeddings=False,
144
+ rope_theta=10000.0,
145
+ rope_scaling=None,
146
+ attention_bias=False,
147
+ mlp_bias=False,
148
+ attention_dropout=0.0,
149
+ use_qk_norm=False,
150
+ use_rotary_pos_emb=True,
151
+ use_cla=False,
152
+ cla_share_factor=1,
153
+ norm_type="hf_rms",
154
+ num_experts: Union[int, List] = 1,
155
+ use_mixed_mlp_moe=False,
156
+ num_shared_expert: Union[int, List] = 1,
157
+ moe_topk: Union[int, List] = 1,
158
+ capacity_factor: int = 1.0,
159
+ moe_drop_tokens=False,
160
+ moe_random_routing_dropped_token=False,
161
+ use_mla=False,
162
+ kv_lora_rank=512,
163
+ q_lora_rank=1536,
164
+ qk_rope_head_dim=64,
165
+ v_head_dim=128,
166
+ qk_nope_head_dim=128,
167
+ moe_layer_num_skipped=0,
168
+ norm_topk_prob=True,
169
+ routed_scaling_factor=1.0,
170
+ group_limited_greedy=False,
171
+ n_group=None,
172
+ topk_group=None,
173
+ add_classification_head=False,
174
+ class_num=0,
175
+ pool_type="last",
176
+ pad_id=-1,
177
+ # Added
178
+ moe_impl="eager",
179
+ vae_downsample_factor=(16, 16), # (h, w)
180
+ img_proj_type="unet",
181
+ patch_size=1,
182
+ patch_embed_hidden_dim=1024,
183
+ image_base_size=1024,
184
+ rope_type="2d",
185
+ cond_token_attn_type="full",
186
+ cond_image_type="vae_vit",
187
+ vae_type=None,
188
+ vae_dtype="float32",
189
+ vae_autocast_dtype="float16",
190
+ vae=None,
191
+ vit_type=None,
192
+ vit=None,
193
+ vit_processor=None,
194
+ vit_aligner=None,
195
+ cfg_distilled=False,
196
+ use_meanflow=False,
197
+ **kwargs,
198
+ ):
199
+ self.vocab_size = vocab_size
200
+ self.max_position_embeddings = max_position_embeddings
201
+ self.hidden_size = hidden_size
202
+ self.intermediate_size = intermediate_size
203
+ self.moe_intermediate_size = moe_intermediate_size
204
+ self.num_hidden_layers = num_hidden_layers
205
+ self.num_attention_heads = num_attention_heads
206
+ self.moe_impl = moe_impl
207
+ self.num_experts = num_experts
208
+ self.use_mixed_mlp_moe = use_mixed_mlp_moe
209
+ self.num_shared_expert = num_shared_expert
210
+ self.moe_topk = moe_topk
211
+ self.capacity_factor = capacity_factor
212
+ self.moe_drop_tokens = moe_drop_tokens
213
+ self.moe_random_routing_dropped_token = moe_random_routing_dropped_token
214
+
215
+ if attention_head_dim is not None:
216
+ self.attention_head_dim = attention_head_dim
217
+ else:
218
+ self.attention_head_dim = self.hidden_size // num_attention_heads
219
+
220
+ # for backward compatibility
221
+ if num_key_value_heads is None:
222
+ num_key_value_heads = num_attention_heads
223
+
224
+ self.num_key_value_heads = num_key_value_heads
225
+ self.hidden_act = hidden_act
226
+ self.initializer_range = initializer_range
227
+ self.rms_norm_eps = rms_norm_eps
228
+ self.pretraining_tp = pretraining_tp
229
+ self.use_cache = use_cache
230
+ self.rope_theta = rope_theta
231
+ self.rope_scaling = rope_scaling
232
+ self.attention_bias = attention_bias
233
+ self.mlp_bias = mlp_bias
234
+ self.attention_dropout = attention_dropout
235
+ self.use_qk_norm = use_qk_norm
236
+ self.use_rotary_pos_emb = use_rotary_pos_emb
237
+ self.use_cla = use_cla
238
+ self.cla_share_factor = cla_share_factor
239
+ self.norm_type = norm_type
240
+ # MLA args
241
+ self.use_mla = use_mla
242
+ self.kv_lora_rank = kv_lora_rank
243
+ self.q_lora_rank = q_lora_rank
244
+ self.qk_rope_head_dim = qk_rope_head_dim
245
+ self.qk_nope_head_dim = qk_nope_head_dim
246
+ self.v_head_dim = v_head_dim
247
+
248
+ # DeepSeek related args
249
+ self.moe_layer_num_skipped = moe_layer_num_skipped
250
+ self.norm_topk_prob = norm_topk_prob
251
+ self.routed_scaling_factor = routed_scaling_factor
252
+ self.group_limited_greedy = group_limited_greedy
253
+ self.n_group = n_group
254
+ self.topk_group = topk_group
255
+ self.add_classification_head = add_classification_head
256
+ self.class_num = class_num
257
+ self.pool_type = pool_type
258
+ self.pad_id = pad_id
259
+
260
+ if self.class_num is not None:
261
+ self.dense_list = [self.hidden_size, self.class_num]
262
+
263
+ # Conditioning image configs
264
+ self.cond_token_attn_type = cond_token_attn_type
265
+ self.cond_image_type = cond_image_type
266
+
267
+ # ViT args
268
+ self.vit_type = vit_type
269
+ self.vit = vit
270
+ self.vit_processor = vit_processor
271
+ self.vit_aligner = vit_aligner
272
+
273
+ # Image Gen args
274
+ self.vae_type = vae_type
275
+ self.vae_dtype = vae_dtype
276
+ self.vae_autocast_dtype = vae_autocast_dtype
277
+ self.vae = vae
278
+ self.vae_downsample_factor = vae_downsample_factor
279
+ self.img_proj_type = img_proj_type
280
+ self.patch_size = patch_size
281
+ self.patch_embed_hidden_dim = patch_embed_hidden_dim
282
+ self.image_base_size = image_base_size
283
+ self.rope_type = rope_type
284
+
285
+ # token id
286
+ self.eod_token_id = eod_token_id
287
+ self.im_start_id = im_start_id
288
+ self.im_end_id = im_end_id
289
+ self.text_start_id = text_start_id
290
+ self.text_end_id = text_end_id
291
+ self.image_token_id = image_token_id
292
+ self.video_start_id = video_start_id
293
+ self.video_end_id = video_end_id
294
+ self.im_newline_id = im_newline_id
295
+ self.mask_init_id = mask_init_id
296
+
297
+ # flag of cfg distilled model
298
+ self.cfg_distilled = cfg_distilled
299
+ # flag of meanflow distilled model
300
+ self.use_meanflow = use_meanflow
301
+ super().__init__(
302
+ pad_token_id=pad_token_id,
303
+ bos_token_id=bos_token_id,
304
+ eos_token_id=eos_token_id,
305
+ tie_word_embeddings=tie_word_embeddings,
306
+ **kwargs,
307
+ )
308
+
309
+
310
+ __all__ = ["HunyuanImage3Config"]
generation_config.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "disable_compile": true,
3
+ "eos_token_id": [
4
+ 127957
5
+ ],
6
+ "pad_token_id": 128009,
7
+ "do_sample": true,
8
+ "top_k": 1024,
9
+ "top_p": 0.95,
10
+ "temperature": 0.6,
11
+ "max_length": 22800,
12
+ "sequence_template": "instruct",
13
+ "diff_infer_steps": 8,
14
+ "diff_guidance_scale": 2.5,
15
+ "flow_shift": 3.0,
16
+ "use_system_prompt": "en_unified",
17
+ "drop_think": false,
18
+ "bot_task": "think_recaption",
19
+ "max_new_tokens": 2048,
20
+ "transformers_version": "4.50.0"
21
+ }
hunyuan_image_3_pipeline.py ADDED
@@ -0,0 +1,913 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Licensed under the TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # https://github.com/Tencent-Hunyuan/HunyuanImage-3.0/blob/main/LICENSE
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ # ==============================================================================
13
+ #
14
+ # Copyright 2024 The HuggingFace Team. All rights reserved.
15
+ #
16
+ # Licensed under the Apache License, Version 2.0 (the "License");
17
+ # you may not use this file except in compliance with the License.
18
+ # You may obtain a copy of the License at
19
+ #
20
+ # http://www.apache.org/licenses/LICENSE-2.0
21
+ #
22
+ # Unless required by applicable law or agreed to in writing, software
23
+ # distributed under the License is distributed on an "AS IS" BASIS,
24
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25
+ # See the License for the specific language governing permissions and
26
+ # limitations under the License.
27
+ # ==============================================================================================
28
+
29
+ import inspect
30
+ import math
31
+ from dataclasses import dataclass
32
+ from typing import Any, Callable, Dict, List
33
+ from typing import Optional, Tuple, Union
34
+
35
+ import numpy as np
36
+ import torch
37
+ from PIL import Image
38
+ from diffusers.callbacks import MultiPipelineCallbacks, PipelineCallback
39
+ from diffusers.configuration_utils import ConfigMixin, register_to_config
40
+ from diffusers.image_processor import VaeImageProcessor
41
+ from diffusers.pipelines.pipeline_utils import DiffusionPipeline
42
+ from diffusers.schedulers.scheduling_utils import SchedulerMixin
43
+ from diffusers.utils import BaseOutput, logging
44
+ from diffusers.utils.torch_utils import randn_tensor
45
+ from .cache_utils import cache_init
46
+ logger = logging.get_logger(__name__) # pylint: disable=invalid-name
47
+
48
+
49
+ def retrieve_timesteps(
50
+ scheduler,
51
+ num_inference_steps: Optional[int] = None,
52
+ device: Optional[Union[str, torch.device]] = None,
53
+ timesteps: Optional[List[int]] = None,
54
+ sigmas: Optional[List[float]] = None,
55
+ **kwargs,
56
+ ):
57
+ """
58
+ Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles
59
+ custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`.
60
+
61
+ Args:
62
+ scheduler (`SchedulerMixin`):
63
+ The scheduler to get timesteps from.
64
+ num_inference_steps (`int`):
65
+ The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps`
66
+ must be `None`.
67
+ device (`str` or `torch.device`, *optional*):
68
+ The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
69
+ timesteps (`List[int]`, *optional*):
70
+ Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed,
71
+ `num_inference_steps` and `sigmas` must be `None`.
72
+ sigmas (`List[float]`, *optional*):
73
+ Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed,
74
+ `num_inference_steps` and `timesteps` must be `None`.
75
+
76
+ Returns:
77
+ `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the
78
+ second element is the number of inference steps.
79
+ """
80
+ if timesteps is not None and sigmas is not None:
81
+ raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values")
82
+ if timesteps is not None:
83
+ accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
84
+ if not accepts_timesteps:
85
+ raise ValueError(
86
+ f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
87
+ f" timestep schedules. Please check whether you are using the correct scheduler."
88
+ )
89
+ scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
90
+ timesteps = scheduler.timesteps
91
+ num_inference_steps = len(timesteps)
92
+ elif sigmas is not None:
93
+ accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
94
+ if not accept_sigmas:
95
+ raise ValueError(
96
+ f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
97
+ f" sigmas schedules. Please check whether you are using the correct scheduler."
98
+ )
99
+ scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
100
+ timesteps = scheduler.timesteps
101
+ num_inference_steps = len(timesteps)
102
+ else:
103
+ scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
104
+ timesteps = scheduler.timesteps
105
+ return timesteps, num_inference_steps
106
+
107
+
108
+ def rescale_noise_cfg(noise_cfg, noise_pred_text, guidance_rescale=0.0):
109
+ r"""
110
+ Rescales `noise_cfg` tensor based on `guidance_rescale` to improve image quality and fix overexposure. Based on
111
+ Section 3.4 from [Common Diffusion Noise Schedules and Sample Steps are
112
+ Flawed](https://arxiv.org/pdf/2305.08891.pdf).
113
+
114
+ Args:
115
+ noise_cfg (`torch.Tensor`):
116
+ The predicted noise tensor for the guided diffusion process.
117
+ noise_pred_text (`torch.Tensor`):
118
+ The predicted noise tensor for the text-guided diffusion process.
119
+ guidance_rescale (`float`, *optional*, defaults to 0.0):
120
+ A rescale factor applied to the noise predictions.
121
+ Returns:
122
+ noise_cfg (`torch.Tensor`): The rescaled noise prediction tensor.
123
+ """
124
+ std_text = noise_pred_text.std(dim=list(range(1, noise_pred_text.ndim)), keepdim=True)
125
+ std_cfg = noise_cfg.std(dim=list(range(1, noise_cfg.ndim)), keepdim=True)
126
+ # rescale the results from guidance (fixes overexposure)
127
+ noise_pred_rescaled = noise_cfg * (std_text / std_cfg)
128
+ # mix with the original results from guidance by factor guidance_rescale to avoid "plain looking" images
129
+ noise_cfg = guidance_rescale * noise_pred_rescaled + (1 - guidance_rescale) * noise_cfg
130
+ return noise_cfg
131
+
132
+
133
+ @dataclass
134
+ class HunyuanImage3Text2ImagePipelineOutput(BaseOutput):
135
+ samples: Union[List[Any], np.ndarray]
136
+
137
+
138
+ @dataclass
139
+ class FlowMatchDiscreteSchedulerOutput(BaseOutput):
140
+ """
141
+ Output class for the scheduler's `step` function output.
142
+
143
+ Args:
144
+ prev_sample (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)` for images):
145
+ Computed sample `(x_{t-1})` of previous timestep. `prev_sample` should be used as next model input in the
146
+ denoising loop.
147
+ """
148
+
149
+ prev_sample: torch.FloatTensor
150
+
151
+
152
+ class FlowMatchDiscreteScheduler(SchedulerMixin, ConfigMixin):
153
+ """
154
+ Euler scheduler.
155
+
156
+ This model inherits from [`SchedulerMixin`] and [`ConfigMixin`]. Check the superclass documentation for the generic
157
+ methods the library implements for all schedulers such as loading and saving.
158
+
159
+ Args:
160
+ num_train_timesteps (`int`, defaults to 1000):
161
+ The number of diffusion steps to train the model.
162
+ timestep_spacing (`str`, defaults to `"linspace"`):
163
+ The way the timesteps should be scaled. Refer to Table 2 of the [Common Diffusion Noise Schedules and
164
+ Sample Steps are Flawed](https://huggingface.co/papers/2305.08891) for more information.
165
+ shift (`float`, defaults to 1.0):
166
+ The shift value for the timestep schedule.
167
+ reverse (`bool`, defaults to `True`):
168
+ Whether to reverse the timestep schedule.
169
+ """
170
+
171
+ _compatibles = []
172
+ order = 1
173
+
174
+ @register_to_config
175
+ def __init__(
176
+ self,
177
+ num_train_timesteps: int = 1000,
178
+ shift: float = 1.0,
179
+ reverse: bool = True,
180
+ solver: str = "euler",
181
+ use_flux_shift: bool = False,
182
+ flux_base_shift: float = 0.5,
183
+ flux_max_shift: float = 1.15,
184
+ n_tokens: Optional[int] = None,
185
+ ):
186
+ sigmas = torch.linspace(1, 0, num_train_timesteps + 1)
187
+
188
+ if not reverse:
189
+ sigmas = sigmas.flip(0)
190
+
191
+ self.sigmas = sigmas
192
+ # the value fed to model
193
+ self.timesteps = (sigmas[:-1] * num_train_timesteps).to(dtype=torch.float32)
194
+ self.timesteps_full = (sigmas * num_train_timesteps).to(dtype=torch.float32)
195
+
196
+ self._step_index = None
197
+ self._begin_index = None
198
+
199
+ self.supported_solver = [
200
+ "euler",
201
+ "heun-2", "midpoint-2",
202
+ "kutta-4",
203
+ ]
204
+ if solver not in self.supported_solver:
205
+ raise ValueError(f"Solver {solver} not supported. Supported solvers: {self.supported_solver}")
206
+
207
+ # empty dt and derivative (for heun)
208
+ self.derivative_1 = None
209
+ self.derivative_2 = None
210
+ self.derivative_3 = None
211
+ self.dt = None
212
+
213
+ @property
214
+ def step_index(self):
215
+ """
216
+ The index counter for current timestep. It will increase 1 after each scheduler step.
217
+ """
218
+ return self._step_index
219
+
220
+ @property
221
+ def begin_index(self):
222
+ """
223
+ The index for the first timestep. It should be set from pipeline with `set_begin_index` method.
224
+ """
225
+ return self._begin_index
226
+
227
+ # Copied from diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler.set_begin_index
228
+ def set_begin_index(self, begin_index: int = 0):
229
+ """
230
+ Sets the begin index for the scheduler. This function should be run from pipeline before the inference.
231
+
232
+ Args:
233
+ begin_index (`int`):
234
+ The begin index for the scheduler.
235
+ """
236
+ self._begin_index = begin_index
237
+
238
+ def _sigma_to_t(self, sigma):
239
+ return sigma * self.config.num_train_timesteps
240
+
241
+ @property
242
+ def state_in_first_order(self):
243
+ return self.derivative_1 is None
244
+
245
+ @property
246
+ def state_in_second_order(self):
247
+ return self.derivative_2 is None
248
+
249
+ @property
250
+ def state_in_third_order(self):
251
+ return self.derivative_3 is None
252
+
253
+ def get_timestep_r(self, timestep: Union[float, torch.FloatTensor]):
254
+ if self.step_index is None:
255
+ self._init_step_index(timestep)
256
+ return self.timesteps_full[self.step_index + 1]
257
+
258
+ def set_timesteps(self, num_inference_steps: int, device: Union[str, torch.device] = None,
259
+ n_tokens: int = None):
260
+ """
261
+ Sets the discrete timesteps used for the diffusion chain (to be run before inference).
262
+
263
+ Args:
264
+ num_inference_steps (`int`):
265
+ The number of diffusion steps used when generating samples with a pre-trained model.
266
+ device (`str` or `torch.device`, *optional*):
267
+ The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
268
+ n_tokens (`int`, *optional*):
269
+ Number of tokens in the input sequence.
270
+ """
271
+ self.num_inference_steps = num_inference_steps
272
+
273
+ sigmas = torch.linspace(1, 0, num_inference_steps + 1)
274
+
275
+ # Apply timestep shift
276
+ if self.config.use_flux_shift:
277
+ assert isinstance(n_tokens, int), "n_tokens should be provided for flux shift"
278
+ mu = self.get_lin_function(y1=self.config.flux_base_shift, y2=self.config.flux_max_shift)(n_tokens)
279
+ sigmas = self.flux_time_shift(mu, 1.0, sigmas)
280
+ elif self.config.shift != 1.:
281
+ sigmas = self.sd3_time_shift(sigmas)
282
+
283
+ if not self.config.reverse:
284
+ sigmas = 1 - sigmas
285
+
286
+ self.sigmas = sigmas
287
+ self.timesteps = (sigmas[:-1] * self.config.num_train_timesteps).to(dtype=torch.float32, device=device)
288
+ self.timesteps_full = (sigmas * self.config.num_train_timesteps).to(dtype=torch.float32, device=device)
289
+
290
+ # empty dt and derivative (for kutta)
291
+ self.derivative_1 = None
292
+ self.derivative_2 = None
293
+ self.derivative_3 = None
294
+ self.dt = None
295
+
296
+ # Reset step index
297
+ self._step_index = None
298
+
299
+ def index_for_timestep(self, timestep, schedule_timesteps=None):
300
+ if schedule_timesteps is None:
301
+ schedule_timesteps = self.timesteps
302
+
303
+ indices = (schedule_timesteps == timestep).nonzero()
304
+
305
+ # The sigma index that is taken for the **very** first `step`
306
+ # is always the second index (or the last index if there is only 1)
307
+ # This way we can ensure we don't accidentally skip a sigma in
308
+ # case we start in the middle of the denoising schedule (e.g. for image-to-image)
309
+ pos = 1 if len(indices) > 1 else 0
310
+
311
+ return indices[pos].item()
312
+
313
+ def _init_step_index(self, timestep):
314
+ if self.begin_index is None:
315
+ if isinstance(timestep, torch.Tensor):
316
+ timestep = timestep.to(self.timesteps.device)
317
+ self._step_index = self.index_for_timestep(timestep)
318
+ else:
319
+ self._step_index = self._begin_index
320
+
321
+ def scale_model_input(self, sample: torch.Tensor, timestep: Optional[int] = None) -> torch.Tensor:
322
+ return sample
323
+
324
+ @staticmethod
325
+ def get_lin_function(x1: float = 256, y1: float = 0.5, x2: float = 4096, y2: float = 1.15):
326
+ m = (y2 - y1) / (x2 - x1)
327
+ b = y1 - m * x1
328
+ return lambda x: m * x + b
329
+
330
+ @staticmethod
331
+ def flux_time_shift(mu: float, sigma: float, t: torch.Tensor):
332
+ return math.exp(mu) / (math.exp(mu) + (1 / t - 1) ** sigma)
333
+
334
+ def sd3_time_shift(self, t: torch.Tensor):
335
+ return (self.config.shift * t) / (1 + (self.config.shift - 1) * t)
336
+
337
+ def step(
338
+ self,
339
+ model_output: torch.FloatTensor,
340
+ timestep: Union[float, torch.FloatTensor],
341
+ sample: torch.FloatTensor,
342
+ pred_uncond: torch.FloatTensor = None,
343
+ generator: Optional[torch.Generator] = None,
344
+ n_tokens: Optional[int] = None,
345
+ return_dict: bool = True,
346
+ ) -> Union[FlowMatchDiscreteSchedulerOutput, Tuple]:
347
+ """
348
+ Predict the sample from the previous timestep by reversing the SDE. This function propagates the diffusion
349
+ process from the learned model outputs (most often the predicted noise).
350
+
351
+ Args:
352
+ model_output (`torch.FloatTensor`):
353
+ The direct output from learned diffusion model.
354
+ timestep (`float`):
355
+ The current discrete timestep in the diffusion chain.
356
+ sample (`torch.FloatTensor`):
357
+ A current instance of a sample created by the diffusion process.
358
+ generator (`torch.Generator`, *optional*):
359
+ A random number generator.
360
+ n_tokens (`int`, *optional*):
361
+ Number of tokens in the input sequence.
362
+ return_dict (`bool`):
363
+ Whether or not to return a [`~schedulers.scheduling_euler_discrete.EulerDiscreteSchedulerOutput`] or
364
+ tuple.
365
+
366
+ Returns:
367
+ [`~schedulers.scheduling_euler_discrete.EulerDiscreteSchedulerOutput`] or `tuple`:
368
+ If return_dict is `True`, [`~schedulers.scheduling_euler_discrete.EulerDiscreteSchedulerOutput`] is
369
+ returned, otherwise a tuple is returned where the first element is the sample tensor.
370
+ """
371
+
372
+ if (
373
+ isinstance(timestep, int)
374
+ or isinstance(timestep, torch.IntTensor)
375
+ or isinstance(timestep, torch.LongTensor)
376
+ ):
377
+ raise ValueError(
378
+ (
379
+ "Passing integer indices (e.g. from `enumerate(timesteps)`) as timesteps to"
380
+ " `EulerDiscreteScheduler.step()` is not supported. Make sure to pass"
381
+ " one of the `scheduler.timesteps` as a timestep."
382
+ ),
383
+ )
384
+
385
+ if self.step_index is None:
386
+ self._init_step_index(timestep)
387
+
388
+ # Upcast to avoid precision issues when computing prev_sample
389
+ sample = sample.to(torch.float32)
390
+ model_output = model_output.to(torch.float32)
391
+ pred_uncond = pred_uncond.to(torch.float32) if pred_uncond is not None else None
392
+
393
+ # dt = self.sigmas[self.step_index + 1] - self.sigmas[self.step_index]
394
+ sigma = self.sigmas[self.step_index]
395
+ sigma_next = self.sigmas[self.step_index + 1]
396
+
397
+ last_inner_step = True
398
+ if self.config.solver == "euler":
399
+ derivative, dt, sample, last_inner_step = self.first_order_method(model_output, sigma, sigma_next, sample)
400
+ elif self.config.solver in ["heun-2", "midpoint-2"]:
401
+ derivative, dt, sample, last_inner_step = self.second_order_method(model_output, sigma, sigma_next, sample)
402
+ elif self.config.solver == "kutta-4":
403
+ derivative, dt, sample, last_inner_step = self.fourth_order_method(model_output, sigma, sigma_next, sample)
404
+ else:
405
+ raise ValueError(f"Solver {self.config.solver} not supported. Supported solvers: {self.supported_solver}")
406
+
407
+ prev_sample = sample + derivative * dt
408
+
409
+ # Cast sample back to model compatible dtype
410
+ # prev_sample = prev_sample.to(model_output.dtype)
411
+
412
+ # upon completion increase step index by one
413
+ if last_inner_step:
414
+ self._step_index += 1
415
+
416
+ if not return_dict:
417
+ return (prev_sample,)
418
+
419
+ return FlowMatchDiscreteSchedulerOutput(prev_sample=prev_sample)
420
+
421
+ def first_order_method(self, model_output, sigma, sigma_next, sample):
422
+ derivative = model_output
423
+ dt = sigma_next - sigma
424
+ return derivative, dt, sample, True
425
+
426
+ def second_order_method(self, model_output, sigma, sigma_next, sample):
427
+ if self.state_in_first_order:
428
+ # store for 2nd order step
429
+ self.derivative_1 = model_output
430
+ self.dt = sigma_next - sigma
431
+ self.sample = sample
432
+
433
+ derivative = model_output
434
+ if self.config.solver == 'heun-2':
435
+ dt = self.dt
436
+ elif self.config.solver == 'midpoint-2':
437
+ dt = self.dt / 2
438
+ else:
439
+ raise NotImplementedError(f"Solver {self.config.solver} not supported.")
440
+ last_inner_step = False
441
+
442
+ else:
443
+ if self.config.solver == 'heun-2':
444
+ derivative = 0.5 * (self.derivative_1 + model_output)
445
+ elif self.config.solver == 'midpoint-2':
446
+ derivative = model_output
447
+ else:
448
+ raise NotImplementedError(f"Solver {self.config.solver} not supported.")
449
+
450
+ # 3. take prev timestep & sample
451
+ dt = self.dt
452
+ sample = self.sample
453
+ last_inner_step = True
454
+
455
+ # free dt and derivative
456
+ # Note, this puts the scheduler in "first order mode"
457
+ self.derivative_1 = None
458
+ self.dt = None
459
+ self.sample = None
460
+
461
+ return derivative, dt, sample, last_inner_step
462
+
463
+ def fourth_order_method(self, model_output, sigma, sigma_next, sample):
464
+ if self.state_in_first_order:
465
+ self.derivative_1 = model_output
466
+ self.dt = sigma_next - sigma
467
+ self.sample = sample
468
+ derivative = model_output
469
+ dt = self.dt / 2
470
+ last_inner_step = False
471
+
472
+ elif self.state_in_second_order:
473
+ self.derivative_2 = model_output
474
+ derivative = model_output
475
+ dt = self.dt / 2
476
+ last_inner_step = False
477
+
478
+ elif self.state_in_third_order:
479
+ self.derivative_3 = model_output
480
+ derivative = model_output
481
+ dt = self.dt
482
+ last_inner_step = False
483
+
484
+ else:
485
+ derivative = (1/6 * self.derivative_1 + 1/3 * self.derivative_2 + 1/3 * self.derivative_3 +
486
+ 1/6 * model_output)
487
+
488
+ # 3. take prev timestep & sample
489
+ dt = self.dt
490
+ sample = self.sample
491
+ last_inner_step = True
492
+
493
+ # free dt and derivative
494
+ # Note, this puts the scheduler in "first order mode"
495
+ self.derivative_1 = None
496
+ self.derivative_2 = None
497
+ self.derivative_3 = None
498
+ self.dt = None
499
+ self.sample = None
500
+
501
+ return derivative, dt, sample, last_inner_step
502
+
503
+ def __len__(self):
504
+ return self.config.num_train_timesteps
505
+
506
+
507
+ class ClassifierFreeGuidance:
508
+ def __init__(
509
+ self,
510
+ use_original_formulation: bool = False,
511
+ start: float = 0.0,
512
+ stop: float = 1.0,
513
+ ):
514
+ super().__init__()
515
+ self.use_original_formulation = use_original_formulation
516
+
517
+ def __call__(
518
+ self,
519
+ pred_cond: torch.Tensor,
520
+ pred_uncond: Optional[torch.Tensor],
521
+ guidance_scale: float,
522
+ step: int,
523
+ ) -> torch.Tensor:
524
+
525
+ shift = pred_cond - pred_uncond
526
+ pred = pred_cond if self.use_original_formulation else pred_uncond
527
+ pred = pred + guidance_scale * shift
528
+
529
+ return pred
530
+
531
+
532
+ class HunyuanImage3Text2ImagePipeline(DiffusionPipeline):
533
+ r"""
534
+ Pipeline for condition-to-sample generation using Stable Diffusion.
535
+
536
+ This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods
537
+ implemented for all pipelines (downloading, saving, running on a particular device, etc.).
538
+
539
+ Args:
540
+ model ([`ModelMixin`]):
541
+ A model to denoise the diffused latents.
542
+ scheduler ([`SchedulerMixin`]):
543
+ A scheduler to be used in combination with `diffusion_model` to denoise the diffused latents. Can be one of
544
+ [`DDIMScheduler`], [`LMSDiscreteScheduler`], or [`PNDMScheduler`].
545
+ """
546
+
547
+ model_cpu_offload_seq = ""
548
+ _optional_components = []
549
+ _exclude_from_cpu_offload = []
550
+ _callback_tensor_inputs = ["latents"]
551
+
552
+ def __init__(
553
+ self,
554
+ model,
555
+ scheduler: SchedulerMixin,
556
+ vae,
557
+ progress_bar_config: Dict[str, Any] = None,
558
+ ):
559
+ super().__init__()
560
+
561
+ # ==========================================================================================
562
+ if progress_bar_config is None:
563
+ progress_bar_config = {}
564
+ if not hasattr(self, '_progress_bar_config'):
565
+ self._progress_bar_config = {}
566
+ self._progress_bar_config.update(progress_bar_config)
567
+ # ==========================================================================================
568
+
569
+ self.register_modules(
570
+ model=model,
571
+ scheduler=scheduler,
572
+ vae=vae,
573
+ )
574
+
575
+ # should be a tuple or a list corresponding to the size of latents (batch_size, channel, *size)
576
+ # if None, will be treated as a tuple of 1
577
+ self.latent_scale_factor = self.model.config.vae_downsample_factor
578
+ self.image_processor = VaeImageProcessor(vae_scale_factor=self.latent_scale_factor)
579
+
580
+ # Must start with APG_mode_
581
+ self.cfg_operator = ClassifierFreeGuidance()
582
+
583
+ @staticmethod
584
+ def denormalize(images: Union[np.ndarray, torch.Tensor]) -> Union[np.ndarray, torch.Tensor]:
585
+ """
586
+ Denormalize an image array to [0,1].
587
+ """
588
+ return (images / 2 + 0.5).clamp(0, 1)
589
+
590
+ @staticmethod
591
+ def pt_to_numpy(images: torch.Tensor) -> np.ndarray:
592
+ """
593
+ Convert a PyTorch tensor to a NumPy image.
594
+ """
595
+ images = images.cpu().permute(0, 2, 3, 1).float().numpy()
596
+ return images
597
+
598
+ @staticmethod
599
+ def numpy_to_pil(images: np.ndarray):
600
+ """
601
+ Convert a numpy image or a batch of images to a PIL image.
602
+ """
603
+ if images.ndim == 3:
604
+ images = images[None, ...]
605
+ images = (images * 255).round().astype("uint8")
606
+ if images.shape[-1] == 1:
607
+ # special case for grayscale (single channel) images
608
+ pil_images = [Image.fromarray(image.squeeze(), mode="L") for image in images]
609
+ else:
610
+ pil_images = [Image.fromarray(image) for image in images]
611
+
612
+ return pil_images
613
+
614
+ def prepare_extra_func_kwargs(self, func, kwargs):
615
+ # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature
616
+ # eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers.
617
+ # eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502
618
+ # and should be between [0, 1]
619
+ extra_kwargs = {}
620
+
621
+ for k, v in kwargs.items():
622
+ accepts = k in set(inspect.signature(func).parameters.keys())
623
+ if accepts:
624
+ extra_kwargs[k] = v
625
+ return extra_kwargs
626
+
627
+ def prepare_latents(self, batch_size, latent_channel, image_size, dtype, device, generator, latents=None):
628
+ if self.latent_scale_factor is None:
629
+ latent_scale_factor = (1,) * len(image_size)
630
+ elif isinstance(self.latent_scale_factor, int):
631
+ latent_scale_factor = (self.latent_scale_factor,) * len(image_size)
632
+ elif isinstance(self.latent_scale_factor, tuple) or isinstance(self.latent_scale_factor, list):
633
+ assert len(self.latent_scale_factor) == len(image_size), \
634
+ "len(latent_scale_factor) shoudl be the same as len(image_size)"
635
+ latent_scale_factor = self.latent_scale_factor
636
+ else:
637
+ raise ValueError(
638
+ f"latent_scale_factor should be either None, int, tuple of int, or list of int, "
639
+ f"but got {self.latent_scale_factor}"
640
+ )
641
+
642
+ latents_shape = (
643
+ batch_size,
644
+ latent_channel,
645
+ *[int(s) // f for s, f in zip(image_size, latent_scale_factor)],
646
+ )
647
+ if isinstance(generator, list) and len(generator) != batch_size:
648
+ raise ValueError(
649
+ f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
650
+ f" size of {batch_size}. Make sure the batch size matches the length of the generators."
651
+ )
652
+
653
+ if latents is None:
654
+ latents = randn_tensor(latents_shape, generator=generator, device=device, dtype=dtype)
655
+ else:
656
+ latents = latents.to(device)
657
+
658
+ # Check existence to make it compatible with FlowMatchEulerDiscreteScheduler
659
+ if hasattr(self.scheduler, "init_noise_sigma"):
660
+ # scale the initial noise by the standard deviation required by the scheduler
661
+ latents = latents * self.scheduler.init_noise_sigma
662
+
663
+ return latents
664
+
665
+ @property
666
+ def guidance_scale(self):
667
+ return self._guidance_scale
668
+
669
+ @property
670
+ def guidance_rescale(self):
671
+ return self._guidance_rescale
672
+
673
+ # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)
674
+ # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`
675
+ # corresponds to doing no classifier free guidance.
676
+ @property
677
+ def do_classifier_free_guidance(self):
678
+ return self._guidance_scale > 1.0
679
+
680
+ @property
681
+ def num_timesteps(self):
682
+ return self._num_timesteps
683
+
684
+ def set_scheduler(self, new_scheduler):
685
+ self.register_modules(scheduler=new_scheduler)
686
+
687
+ @torch.no_grad()
688
+ def __call__(
689
+ self,
690
+ batch_size: int,
691
+ image_size: List[int],
692
+ num_inference_steps: int = 50,
693
+ timesteps: List[int] = None,
694
+ sigmas: List[float] = None,
695
+ guidance_scale: float = 7.5,
696
+ meanflow: bool = False,
697
+ generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
698
+ latents: Optional[torch.Tensor] = None,
699
+ output_type: Optional[str] = "pil",
700
+ return_dict: bool = True,
701
+ guidance_rescale: float = 0.0,
702
+ callback_on_step_end: Optional[
703
+ Union[Callable[[int, int, Dict], None], PipelineCallback, MultiPipelineCallbacks]
704
+ ] = None,
705
+ callback_on_step_end_tensor_inputs: List[str] = ["latents"],
706
+ model_kwargs: Dict[str, Any] = None,
707
+ **kwargs,
708
+ ):
709
+ r"""
710
+ The call function to the pipeline for generation.
711
+
712
+ Args:
713
+ prompt (`str` or `List[str]`):
714
+ The text to guide image generation.
715
+ image_size (`Tuple[int]` or `List[int]`):
716
+ The size (height, width) of the generated image.
717
+ num_inference_steps (`int`, *optional*, defaults to 50):
718
+ The number of denoising steps. More denoising steps usually lead to a higher quality image at the
719
+ expense of slower inference.
720
+ timesteps (`List[int]`, *optional*):
721
+ Custom timesteps to use for the denoising process with schedulers which support a `timesteps` argument
722
+ in their `set_timesteps` method. If not defined, the default behavior when `num_inference_steps` is
723
+ passed will be used. Must be in descending order.
724
+ sigmas (`List[float]`, *optional*):
725
+ Custom sigmas to use for the denoising process with schedulers which support a `sigmas` argument in
726
+ their `set_timesteps` method. If not defined, the default behavior when `num_inference_steps` is passed
727
+ will be used.
728
+ guidance_scale (`float`, *optional*, defaults to 7.5):
729
+ A higher guidance scale value encourages the model to generate samples closely linked to the
730
+ `condition` at the expense of lower sample quality. Guidance scale is enabled when `guidance_scale > 1`.
731
+ generator (`torch.Generator` or `List[torch.Generator]`, *optional*):
732
+ A [`torch.Generator`](https://pytorch.org/docs/stable/generated/torch.Generator.html) to make
733
+ generation deterministic.
734
+ latents (`torch.Tensor`, *optional*):
735
+ Pre-generated noisy latents sampled from a Gaussian distribution, to be used as inputs for sample
736
+ generation. Can be used to tweak the same generation with different conditions. If not provided,
737
+ a latents tensor is generated by sampling using the supplied random `generator`.
738
+ output_type (`str`, *optional*, defaults to `"pil"`):
739
+ The output format of the generated sample.
740
+ return_dict (`bool`, *optional*, defaults to `True`):
741
+ Whether or not to return a [`~DiffusionPipelineOutput`] instead of a
742
+ plain tuple.
743
+ guidance_rescale (`float`, *optional*, defaults to 0.0):
744
+ Guidance rescale factor from [Common Diffusion Noise Schedules and Sample Steps are
745
+ Flawed](https://arxiv.org/pdf/2305.08891.pdf). Guidance rescale factor should fix overexposure when
746
+ using zero terminal SNR.
747
+ callback_on_step_end (`Callable`, `PipelineCallback`, `MultiPipelineCallbacks`, *optional*):
748
+ A function or a subclass of `PipelineCallback` or `MultiPipelineCallbacks` that is called at the end of
749
+ each denoising step during the inference. with the following arguments: `callback_on_step_end(self:
750
+ DiffusionPipeline, step: int, timestep: int, callback_kwargs: Dict)`. `callback_kwargs` will include a
751
+ list of all tensors as specified by `callback_on_step_end_tensor_inputs`.
752
+ callback_on_step_end_tensor_inputs (`List`, *optional*):
753
+ The list of tensor inputs for the `callback_on_step_end` function. The tensors specified in the list
754
+ will be passed as `callback_kwargs` argument. You will only be able to include variables listed in the
755
+ `._callback_tensor_inputs` attribute of your pipeline class.
756
+
757
+ Examples:
758
+
759
+ Returns:
760
+ [`~DiffusionPipelineOutput`] or `tuple`:
761
+ If `return_dict` is `True`, [`~DiffusionPipelineOutput`] is returned,
762
+ otherwise a `tuple` is returned where the first element is a list with the generated samples.
763
+ """
764
+
765
+ callback_steps = kwargs.pop("callback_steps", None)
766
+ pbar_steps = kwargs.pop("pbar_steps", None)
767
+
768
+ if isinstance(callback_on_step_end, (PipelineCallback, MultiPipelineCallbacks)):
769
+ callback_on_step_end_tensor_inputs = callback_on_step_end.tensor_inputs
770
+
771
+ self._guidance_scale = guidance_scale
772
+ self._guidance_rescale = guidance_rescale
773
+
774
+
775
+ if not kwargs.get('cfg_distilled', False):
776
+ cfg_factor = 1 + self.do_classifier_free_guidance
777
+ else:
778
+ cfg_factor = 1
779
+ # Define call parameters
780
+ device = self._execution_device
781
+
782
+ # Prepare timesteps
783
+ timesteps, num_inference_steps = retrieve_timesteps(
784
+ self.scheduler, num_inference_steps, device, timesteps, sigmas,
785
+ )
786
+
787
+ # Prepare latent variables
788
+ latents = self.prepare_latents(
789
+ batch_size=batch_size,
790
+ latent_channel=self.model.config.vae["latent_channels"],
791
+ image_size=image_size,
792
+ dtype=torch.bfloat16,
793
+ device=device,
794
+ generator=generator,
795
+ latents=latents,
796
+ )
797
+
798
+ # Prepare extra step kwargs.
799
+ _scheduler_step_extra_kwargs = self.prepare_extra_func_kwargs(
800
+ self.scheduler.step, {"generator": generator}
801
+ )
802
+
803
+ # Prepare model kwargs
804
+ input_ids = model_kwargs.pop("input_ids")
805
+ attention_mask = self.model._prepare_attention_mask_for_generation( # noqa
806
+ input_ids, self.model.generation_config, model_kwargs=model_kwargs,
807
+ )
808
+ model_kwargs["attention_mask"] = attention_mask.to(latents.device)
809
+
810
+ # Sampling loop
811
+ num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order
812
+ self._num_timesteps = len(timesteps)
813
+
814
+ # Taylor cache
815
+ cache_dic = None
816
+ if self.model.use_taylor_cache:
817
+ cache_dic = cache_init(cache_interval=self.model.taylor_cache_interval, max_order=self.model.taylor_cache_order, num_steps=len(timesteps),
818
+ enable_first_enhance=self.model.taylor_cache_enable_first_enhance, first_enhance_steps=self.model.taylor_cache_first_enhance_steps,
819
+ enable_tailing_enhance=self.model.taylor_cache_enable_tailing_enhance,
820
+ tailing_enhance_steps=self.model.taylor_cache_tailing_enhance_steps,
821
+ low_freqs_order=self.model.taylor_cache_low_freqs_order,
822
+ high_freqs_order=self.model.taylor_cache_high_freqs_order)
823
+ print(f"***use_taylor_cache: {self.model.use_taylor_cache}, cache_dic: {cache_dic}")
824
+
825
+ with self.progress_bar(total=num_inference_steps) as progress_bar:
826
+ for i, t in enumerate(timesteps):
827
+ # expand the latents if we are doing classifier free guidance
828
+ latent_model_input = torch.cat([latents] * cfg_factor)
829
+ latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
830
+
831
+ if meanflow:
832
+ r = self.scheduler.get_timestep_r(t)
833
+ r_expand = r.repeat(latent_model_input.shape[0])
834
+ else:
835
+ r_expand = None
836
+ model_kwargs["timesteps_r"] = r_expand
837
+
838
+ t_expand = t.repeat(latent_model_input.shape[0])
839
+
840
+ if self.model.use_taylor_cache:
841
+ cache_dic['current_step'] = i
842
+ model_kwargs['cache_dic'] = cache_dic
843
+ if kwargs.get('cfg_distilled', False):
844
+ model_kwargs["guidance"] = torch.tensor(
845
+ [1000.0*self._guidance_scale], device=self.device, dtype=torch.bfloat16
846
+ )
847
+ model_inputs = self.model.prepare_inputs_for_generation(
848
+ input_ids,
849
+ images=latent_model_input,
850
+ timesteps=t_expand,
851
+ **model_kwargs,
852
+ )
853
+ with torch.autocast(device_type="cuda", dtype=torch.bfloat16, enabled=True):
854
+ model_output = self.model(**model_inputs, first_step=(i == 0))
855
+ pred = model_output["diffusion_prediction"]
856
+ pred = pred.to(dtype=torch.float32)
857
+ # perform guidance
858
+ if self.do_classifier_free_guidance:
859
+ if not kwargs.get('cfg_distilled', False):
860
+ pred_cond, pred_uncond = pred.chunk(2)
861
+ pred = self.cfg_operator(pred_cond, pred_uncond, self.guidance_scale, step=i)
862
+
863
+ if self.do_classifier_free_guidance and self.guidance_rescale > 0.0:
864
+ # Based on 3.4. in https://arxiv.org/pdf/2305.08891.pdf
865
+ pred = rescale_noise_cfg(pred, pred_cond, guidance_rescale=self.guidance_rescale)
866
+
867
+ # compute the previous noisy sample x_t -> x_t-1
868
+ latents = self.scheduler.step(pred, t, latents, **_scheduler_step_extra_kwargs, return_dict=False)[0]
869
+
870
+ if i != len(timesteps) - 1:
871
+ model_kwargs = self.model._update_model_kwargs_for_generation( # noqa
872
+ model_output,
873
+ model_kwargs,
874
+ )
875
+ input_ids = None
876
+ # if input_ids.shape[1] != model_kwargs["position_ids"].shape[1]:
877
+ # input_ids = torch.gather(input_ids, 1, index=model_kwargs["position_ids"])
878
+
879
+ if callback_on_step_end is not None:
880
+ callback_kwargs = {}
881
+ for k in callback_on_step_end_tensor_inputs:
882
+ callback_kwargs[k] = locals()[k]
883
+ callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)
884
+
885
+ latents = callback_outputs.pop("latents", latents)
886
+
887
+ # call the callback, if provided
888
+ if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
889
+ progress_bar.update()
890
+
891
+ if hasattr(self.vae.config, 'scaling_factor') and self.vae.config.scaling_factor:
892
+ latents = latents / self.vae.config.scaling_factor
893
+ if hasattr(self.vae.config, 'shift_factor') and self.vae.config.shift_factor:
894
+ latents = latents + self.vae.config.shift_factor
895
+
896
+ if hasattr(self.vae, "ffactor_temporal"):
897
+ latents = latents.unsqueeze(2)
898
+
899
+ with torch.autocast(device_type="cuda", dtype=torch.float16, enabled=True):
900
+ image = self.vae.decode(latents, return_dict=False, generator=generator)[0]
901
+
902
+ # b c t h w
903
+ if hasattr(self.vae, "ffactor_temporal"):
904
+ assert image.shape[2] == 1, "image should have shape [B, C, T, H, W] and T should be 1"
905
+ image = image.squeeze(2)
906
+
907
+ do_denormalize = [True] * image.shape[0]
908
+ image = self.image_processor.postprocess(image, output_type=output_type, do_denormalize=do_denormalize)
909
+
910
+ if not return_dict:
911
+ return (image,)
912
+
913
+ return HunyuanImage3Text2ImagePipelineOutput(samples=image)
image_processor.py ADDED
@@ -0,0 +1,465 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Licensed under the TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # https://github.com/Tencent-Hunyuan/HunyuanImage-3.0/blob/main/LICENSE
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ # ==============================================================================
13
+
14
+ from dataclasses import dataclass, field, asdict
15
+ from typing import Tuple, Optional, Callable, Union, Any
16
+ import random
17
+ import math
18
+
19
+ import torch
20
+ from PIL import Image
21
+ from torchvision import transforms
22
+ from transformers.image_processing_utils import BaseImageProcessor
23
+ from transformers.image_utils import load_image
24
+ from transformers.models.siglip2.image_processing_siglip2_fast import Siglip2ImageProcessorFast
25
+ from transformers.generation.logits_process import LogitsProcessor, LogitsProcessorList
26
+
27
+ from .tokenization_hunyuan_image_3 import ImageInfo, ImageTensor, CondImage, Resolution, ResolutionGroup
28
+
29
+ InputImage = Union[Image.Image, str]
30
+
31
+
32
+ class SliceVocabLogitsProcessor(LogitsProcessor):
33
+ """
34
+ [`LogitsProcessor`] that performs vocab slicing, i.e. restricting probabilities with in some range. This processor
35
+ is often used in multimodal discrete LLMs, which ensure that we only sample within one modality
36
+
37
+ Args:
38
+ vocab_start (`int`): start of slice, default None meaning from 0
39
+ vocab_end (`int`): end of slice, default None meaning to the end of list
40
+ when start and end are all None, this processor does noting
41
+
42
+ """
43
+
44
+ def __init__(self, vocab_start: int = None, vocab_end: int = None, **kwargs):
45
+ if vocab_start is not None and vocab_end is not None:
46
+ assert vocab_start < vocab_end, f"Ensure vocab_start {vocab_start} < vocab_end {vocab_end}"
47
+ self.vocab_start = vocab_start
48
+ self.vocab_end = vocab_end
49
+ self.other_slices = kwargs.get("other_slices", [])
50
+
51
+ def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor) -> torch.FloatTensor:
52
+ scores_processed = scores[:, self.vocab_start: self.vocab_end]
53
+ for other_slice in self.other_slices:
54
+ scores_processed = torch.cat([scores_processed, scores[:, other_slice[0]: other_slice[1]]], dim=-1)
55
+ return scores_processed
56
+
57
+ def __repr__(self):
58
+ return f"SliceVocabLogitsWarper(vocab_start={self.vocab_start}, vocab_end={self.vocab_end}, other_slices={self.other_slices})"
59
+
60
+
61
+ def resize_and_crop(image: Image.Image, target_size: Tuple[int, int], resample=Image.Resampling.LANCZOS, crop_type='center', crop_coords=None) -> Image.Image:
62
+ tw, th = target_size
63
+ w, h = image.size
64
+
65
+ tr = th / tw
66
+ r = h / w
67
+
68
+ if crop_type == "resize":
69
+ resize_width = tw
70
+ resize_height = th
71
+ crop_top = 0
72
+ crop_left = 0
73
+ image = image.resize((resize_width, resize_height), resample=resample)
74
+ else:
75
+ # maintain the aspect ratio
76
+ if r < tr:
77
+ resize_height = th
78
+ resize_width = int(round(th / h * w))
79
+ else:
80
+ resize_width = tw
81
+ resize_height = int(round(tw / w * h))
82
+
83
+ if crop_type == 'center':
84
+ crop_top = int(round((resize_height - th) / 2.0))
85
+ crop_left = int(round((resize_width - tw) / 2.0))
86
+ elif crop_type == 'random':
87
+ crop_top = random.randint(0, resize_height - th)
88
+ crop_left = random.randint(0, resize_width - tw)
89
+ elif crop_type == 'fixed':
90
+ assert crop_coords is not None, 'crop_coords should be provided when crop_type is fixed.'
91
+ crop_left, crop_top = crop_coords
92
+ else:
93
+ raise ValueError(f'crop_type must be center, random or fixed, but got {crop_type}')
94
+
95
+ image = image.resize((resize_width, resize_height), resample=resample)
96
+ image = image.crop((crop_left, crop_top, crop_left + tw, crop_top + th))
97
+
98
+ return image
99
+
100
+
101
+ @dataclass
102
+ class ResolutionGroupConfig:
103
+ base_size: int = None
104
+ step: Optional[int] = None
105
+ align: int = 16
106
+
107
+ def to_dict(self):
108
+ return asdict(self)
109
+
110
+
111
+ @dataclass
112
+ class VAEInfo:
113
+ encoder_type: str
114
+ down_h_factor: int = -1
115
+ down_w_factor: int = -1
116
+ patch_size: int = 1
117
+ h_factor: int = -1
118
+ w_factor: int = -1
119
+ image_type: str = None
120
+
121
+ def __post_init__(self):
122
+ self.h_factor = self.down_h_factor * self.patch_size
123
+ self.w_factor = self.down_w_factor * self.patch_size
124
+ if self.image_type is None:
125
+ self.image_type = "vae"
126
+
127
+
128
+ @dataclass
129
+ class ViTInfo:
130
+ encoder_type: str
131
+ h_factor: int = -1
132
+ w_factor: int = -1
133
+ max_token_length: int = 0 # pad to max_token_length
134
+ processor: Callable = field(default_factory=BaseImageProcessor)
135
+ image_type: str = None
136
+
137
+ def __post_init__(self):
138
+ if self.image_type is None:
139
+ self.image_type = self.encoder_type.split("-")[0]
140
+
141
+
142
+ class HunyuanImage3ImageProcessor(object):
143
+ def __init__(self, config):
144
+ self.config = config
145
+
146
+ self.reso_group_config = ResolutionGroupConfig(base_size=config.image_base_size)
147
+ self.vae_reso_group = ResolutionGroup(
148
+ **self.reso_group_config.to_dict(),
149
+ extra_resolutions=[
150
+ Resolution("1024x768"),
151
+ Resolution("1280x720"),
152
+ Resolution("768x1024"),
153
+ Resolution("720x1280"),
154
+ ]
155
+ )
156
+ self.img_ratio_slice_logits_processor = None
157
+ self.pil_image_to_tensor = transforms.Compose([
158
+ transforms.ToTensor(),
159
+ transforms.Normalize([0.5], [0.5]), # transform to [-1, 1]
160
+ ])
161
+ self.vae_info = VAEInfo(
162
+ encoder_type=config.vae_type,
163
+ down_h_factor=config.vae_downsample_factor[0], down_w_factor=config.vae_downsample_factor[0],
164
+ patch_size=config.patch_size,
165
+ )
166
+
167
+ if config.vit_type == "siglip2-so400m-patch16-naflex":
168
+ self.vit_processor = Siglip2ImageProcessorFast.from_dict(config.vit_processor)
169
+ else:
170
+ raise ValueError(f"Unsupported vit_type: {config.vit_type}")
171
+ self.vit_info = ViTInfo(
172
+ encoder_type=config.vit_type,
173
+ h_factor=self.vit_processor.patch_size,
174
+ w_factor=self.vit_processor.patch_size,
175
+ max_token_length=self.vit_processor.max_num_patches,
176
+ processor=self.vit_processor,
177
+ )
178
+ self.cond_token_attn_type = config.cond_token_attn_type
179
+ self.cond_image_type = config.cond_image_type
180
+
181
+ def build_gen_image_info(self, image_size, add_guidance_token=False, add_timestep_r_token=False) -> ImageInfo:
182
+ # parse image size (HxW, H:W, or <img_ratio_i>)
183
+ if isinstance(image_size, str):
184
+ if image_size.startswith("<img_ratio_"):
185
+ ratio_index = int(image_size.split("_")[-1].rstrip(">"))
186
+ reso = self.vae_reso_group[ratio_index]
187
+ image_size = reso.height, reso.width
188
+ elif 'x' in image_size:
189
+ image_size = [int(s) for s in image_size.split('x')]
190
+ elif ':' in image_size:
191
+ image_size = [int(s) for s in image_size.split(':')]
192
+ assert len(image_size) == 2, f"`image_size` should be in the format of 'W:H', got {image_size}."
193
+ # Note that ratio is width:height
194
+ image_size = [image_size[1], image_size[0]]
195
+ else:
196
+ raise ValueError(
197
+ f"`image_size` should be in the format of 'HxW', 'W:H' or <img_ratio_i>, got {image_size}.")
198
+ assert len(image_size) == 2, f"`image_size` should be in the format of 'HxW', got {image_size}."
199
+ elif isinstance(image_size, (list, tuple)):
200
+ assert len(image_size) == 2 and all(isinstance(s, int) for s in image_size), \
201
+ f"`image_size` should be a tuple of two integers or a string in the format of 'HxW', got {image_size}."
202
+ else:
203
+ raise ValueError(f"`image_size` should be a tuple of two integers or a string in the format of 'WxH', "
204
+ f"got {image_size}.")
205
+ image_width, image_height = self.vae_reso_group.get_target_size(image_size[1], image_size[0])
206
+ token_height = image_height // self.vae_info.h_factor
207
+ token_width = image_width // self.vae_info.w_factor
208
+ base_size, ratio_idx = self.vae_reso_group.get_base_size_and_ratio_index(image_size[1], image_size[0])
209
+ image_info = ImageInfo(
210
+ image_type="gen_image", image_width=image_width, image_height=image_height,
211
+ token_width=token_width, token_height=token_height, base_size=base_size, ratio_index=ratio_idx,
212
+ add_guidance_token=add_guidance_token, add_timestep_r_token=add_timestep_r_token,
213
+ )
214
+ return image_info
215
+
216
+ def as_image_tensor(self, image, image_type, **kwargs) -> ImageTensor:
217
+ if isinstance(image, Image.Image):
218
+ tensor = self.pil_image_to_tensor(image)
219
+ else:
220
+ tensor = image
221
+
222
+ origin_size = kwargs["origin_size"]
223
+ ori_image_width = origin_size[0]
224
+ ori_image_height = origin_size[1]
225
+
226
+ if image_type == "vae":
227
+ assert tensor.ndim == 3 or tensor.ndim == 4
228
+ h, w = tensor.shape[-2], tensor.shape[-1]
229
+ assert (h % self.vae_info.h_factor == 0 and w % self.vae_info.w_factor == 0), \
230
+ (f"Image size should be divisible by ({self.vae_info.h_factor}, {self.vae_info.w_factor}), "
231
+ f"but got ({h} x {w}).")
232
+ tk_height = h // self.vae_info.h_factor
233
+ tk_width = w // self.vae_info.w_factor
234
+ base_size, ratio_idx = self.vae_reso_group.get_base_size_and_ratio_index(w, h)
235
+ tensor.i = ImageInfo(
236
+ image_type=image_type,
237
+ image_width=w, image_height=h, token_width=tk_width, token_height=tk_height,
238
+ base_size=base_size, ratio_index=ratio_idx,
239
+ ori_image_width=ori_image_width,
240
+ ori_image_height=ori_image_height,
241
+ )
242
+ tensor.section_type = "cond_vae_image"
243
+ elif image_type == "siglip2":
244
+ spatial_shapes = kwargs["spatial_shapes"] # 2 (h, w)
245
+ pixel_attention_mask = kwargs["pixel_attention_mask"] # seq_len
246
+ tensor.i = ImageInfo(
247
+ image_type=image_type,
248
+ image_width=spatial_shapes[1].item() * self.vit_info.w_factor,
249
+ image_height=spatial_shapes[0].item() * self.vit_info.h_factor,
250
+ token_width=spatial_shapes[1].item(),
251
+ token_height=spatial_shapes[0].item(),
252
+ image_token_length=self.vit_info.max_token_length,
253
+ ori_image_width=ori_image_width,
254
+ ori_image_height=ori_image_height,
255
+ )
256
+ tensor.section_type = "cond_vit_image"
257
+ tensor.vision_encoder_kwargs = {
258
+ "spatial_shapes": spatial_shapes,
259
+ "pixel_attention_mask": pixel_attention_mask,
260
+ }
261
+ elif image_type == "anyres":
262
+ token_width = kwargs["resized_image_width"] // self.vit_info.w_factor
263
+ token_height = kwargs["resized_image_height"] // self.vit_info.h_factor
264
+ tensor.i = ImageInfo(
265
+ image_type=image_type,
266
+ image_width=kwargs["resized_image_width"],
267
+ image_height=kwargs["resized_image_height"],
268
+ token_width=token_width,
269
+ token_height=token_height,
270
+ image_token_length=token_height * (token_width + 1) + 2,
271
+ )
272
+ tensor.section_type = "cond_vit_image"
273
+ else:
274
+ raise ValueError(f"Unknown image type: {image_type}")
275
+ return tensor
276
+
277
+ def vae_process_image(self, image, target_size, random_crop: bool | str = False) -> ImageTensor:
278
+ origin_size = image.size
279
+ crop_type = random_crop if isinstance(random_crop, str) else ("random" if random_crop else "center")
280
+ resized_image = resize_and_crop(image, target_size, crop_type=crop_type)
281
+ return self.as_image_tensor(resized_image, image_type=self.vae_info.image_type, origin_size=origin_size)
282
+
283
+ def vit_process_image(self, image) -> ImageTensor:
284
+ origin_size = image.size
285
+ inputs = self.vit_info.processor(image)
286
+ image = inputs["pixel_values"].squeeze(0) # (seq_len, dim)
287
+
288
+ remain_keys = set(inputs.keys()) - {"pixel_values"}
289
+ remain_kwargs = {}
290
+ for key in remain_keys:
291
+ if isinstance(inputs[key], torch.Tensor):
292
+ remain_kwargs[key] = inputs[key].squeeze(0)
293
+ else:
294
+ remain_kwargs[key] = inputs[key]
295
+
296
+ return self.as_image_tensor(image, image_type=self.vit_info.image_type, origin_size=origin_size, **remain_kwargs)
297
+
298
+ def get_image_with_size(
299
+ self,
300
+ src: InputImage,
301
+ random_crop: bool | str = False,
302
+ return_type: str = "vae",
303
+ ) -> tuple[ImageTensor | CondImage, bool]:
304
+ """ For various image generation tasks, dynamic image sizes """
305
+ image = load_image(src)
306
+ image_flag = "normal"
307
+ img_success = image_flag != "gray"
308
+ origin_size = image.size # (w_ori, h_ori)
309
+
310
+ if "vae" in return_type:
311
+ target_size = self.vae_reso_group.get_target_size(*origin_size)
312
+ vae_image_tensor = self.vae_process_image(image, target_size, random_crop=random_crop)
313
+ else:
314
+ vae_image_tensor = None
315
+
316
+ if "vit" in return_type:
317
+ vit_image_tensor = self.vit_process_image(image)
318
+ else:
319
+ vit_image_tensor = None
320
+
321
+ if return_type == "vae":
322
+ image_tensor = vae_image_tensor
323
+ elif return_type == "vit":
324
+ image_tensor = vit_image_tensor
325
+ elif return_type == "vae_vit":
326
+ image_tensor = CondImage(image_type=return_type, vae_image=vae_image_tensor, vit_image=vit_image_tensor)
327
+ else:
328
+ raise ValueError(f"Unknown return_type: {return_type}")
329
+
330
+ return image_tensor, img_success
331
+
332
+ def build_cond_images(
333
+ self,
334
+ image_list: Optional[list[InputImage]] = None,
335
+ message_list: Optional[list[dict[str, Any]]] = None,
336
+ infer_align_image_size: bool = False,
337
+ ) -> Optional[list[CondImage]]:
338
+ if image_list is not None and message_list is not None:
339
+ raise ValueError("`image_list` and `message_list` cannot be provided at the same time.")
340
+ if message_list is not None:
341
+ image_list = []
342
+ for message in message_list:
343
+ visuals = [
344
+ content
345
+ for content in message["content"]
346
+ if isinstance(content, dict) and content["type"] in ["image"]
347
+ ]
348
+ image_list.extend([
349
+ vision_info[key]
350
+ for vision_info in visuals
351
+ for key in ["image", "url", "path", "base64"]
352
+ if key in vision_info and vision_info["type"] == "image"
353
+ ])
354
+
355
+ if infer_align_image_size:
356
+ random_crop = "resize"
357
+ else:
358
+ random_crop = "center"
359
+
360
+ return [
361
+ self.get_image_with_size(src, return_type=self.cond_image_type, random_crop=random_crop)[0]
362
+ for src in image_list
363
+ ]
364
+
365
+ def prepare_full_attn_slices(self, output, batch_idx=None, with_gen=True):
366
+ """ Determine full attention image slices according to strategies. """
367
+ if self.cond_image_type == "vae":
368
+ cond_choices = dict(
369
+ causal=[],
370
+ full=output.vae_image_slices[batch_idx] if batch_idx is not None else output.vae_image_slices
371
+ )
372
+
373
+ elif self.cond_image_type == "vit":
374
+ cond_choices = dict(
375
+ causal=[],
376
+ full=output.vit_image_slices[batch_idx] if batch_idx is not None else output.vit_image_slices
377
+ )
378
+
379
+ elif self.cond_image_type == "vae_vit":
380
+ cond_choices = {
381
+ "causal": [],
382
+ "full": (
383
+ output.vae_image_slices[batch_idx] + output.vit_image_slices[batch_idx]
384
+ if batch_idx is not None
385
+ else output.vae_image_slices + output.vit_image_slices
386
+ ),
387
+ "joint_full": (
388
+ output.joint_image_slices[batch_idx]
389
+ if batch_idx is not None
390
+ else output.joint_image_slices
391
+ ),
392
+ "full_causal": (
393
+ output.vae_image_slices[batch_idx]
394
+ if batch_idx is not None
395
+ else output.vae_image_slices
396
+ ),
397
+ }
398
+
399
+ else:
400
+ raise ValueError(f"Unknown cond_image_type: {self.cond_image_type}")
401
+ slices = cond_choices[self.cond_token_attn_type]
402
+
403
+ if with_gen:
404
+ gen_image_slices = (
405
+ output.gen_image_slices[batch_idx]
406
+ if batch_idx is not None
407
+ else output.gen_image_slices
408
+ )
409
+ slices = slices + gen_image_slices
410
+ return slices
411
+
412
+ def build_img_ratio_slice_logits_processor(self, tokenizer):
413
+ if self.img_ratio_slice_logits_processor is None:
414
+ self.img_ratio_slice_logits_processor = LogitsProcessorList()
415
+ self.img_ratio_slice_logits_processor.append(
416
+ SliceVocabLogitsProcessor(
417
+ vocab_start=tokenizer.start_ratio_token_id,
418
+ vocab_end=tokenizer.end_ratio_token_id + 1,
419
+ other_slices=getattr(tokenizer, "ratio_token_other_slices", []),
420
+ )
421
+ )
422
+
423
+ def postprocess_outputs(self, outputs: list[Image.Image], batch_cond_images, infer_align_image_size: bool = False):
424
+ if infer_align_image_size:
425
+ target_area = self.vae_reso_group.base_size ** 2
426
+
427
+ for batch_index, (output_image, cond_images) in enumerate(zip(outputs, batch_cond_images)):
428
+ output_image_ratio_index = self.vae_reso_group.get_base_size_and_ratio_index(width=output_image.width, height=output_image.height)[1]
429
+ cond_images_ratio_index_list = []
430
+ cond_images_ori_width_list = []
431
+ cond_images_ori_height_list = []
432
+ for cond_image in cond_images:
433
+ if isinstance(cond_image, ImageTensor):
434
+ cond_images_ratio_index_list.append(cond_image.i.ratio_index)
435
+ cond_images_ori_width_list.append(cond_image.i.ori_image_width)
436
+ cond_images_ori_height_list.append(cond_image.i.ori_image_height)
437
+ else: # CondImage
438
+ cond_images_ratio_index_list.append(cond_image.vae_image.i.ratio_index)
439
+ cond_images_ori_width_list.append(cond_image.vae_image.i.ori_image_width)
440
+ cond_images_ori_height_list.append(cond_image.vae_image.i.ori_image_height)
441
+
442
+ if len(cond_images) == 0:
443
+ continue
444
+ elif len(cond_images) == 1:
445
+ if output_image_ratio_index == cond_images_ratio_index_list[0]:
446
+ if abs(cond_images_ori_height_list[0] / cond_images_ori_width_list[0] - self.vae_reso_group[output_image_ratio_index].ratio) >= 0.01:
447
+ scale = math.sqrt(target_area / (cond_images_ori_width_list[0] * cond_images_ori_height_list[0]))
448
+ new_w = round(cond_images_ori_width_list[0] * scale)
449
+ new_h = round(cond_images_ori_height_list[0] * scale)
450
+ outputs[batch_index] = output_image.resize((new_w, new_h), resample=Image.Resampling.LANCZOS)
451
+ else:
452
+ for cond_image_ratio_index, cond_image_ori_width, cond_image_ori_height in zip(cond_images_ratio_index_list, cond_images_ori_width_list, cond_images_ori_height_list):
453
+ if output_image_ratio_index == cond_image_ratio_index:
454
+ if abs(cond_image_ori_height / cond_image_ori_width - self.vae_reso_group[output_image_ratio_index].ratio) >= 0.01:
455
+ scale = math.sqrt(target_area / (cond_image_ori_width * cond_image_ori_height))
456
+ new_w = round(cond_image_ori_width * scale)
457
+ new_h = round(cond_image_ori_height * scale)
458
+ outputs[batch_index] = output_image.resize((new_w, new_h), resample=Image.Resampling.LANCZOS)
459
+ break
460
+
461
+ return outputs
462
+
463
+ __all__ = [
464
+ "HunyuanImage3ImageProcessor"
465
+ ]
model-0001-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f636cb156e0d38d0739bd8b879da5bcce568f6ff3121314bf54a744d70a3303e
3
+ size 5348403192
model-0002-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4d596c456ffb1c0369b296815dbf5aecae344658399f9dbd44b1d0a383cdb43f
3
+ size 5344103080
model-0003-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:873864acf1cb6412ea2379cdf9a3517a64f64fbd584945ac225a5a5155a6b145
3
+ size 5318937248
model-0004-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6e1315b5aa43a77285047e916f67add4d8c63f08d894c723595edb8c7e3b3f09
3
+ size 5353033424
model-0005-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0e8a4e3175a906e0eb9b1e6b0ac45c9d26f4aad7a2f37f7d35c562f2a0154345
3
+ size 5318937248
model-0006-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:72ee1387a20f18cecced4fcd1dd19e6a5fe47473259f9901dc52d60a7b06a0cf
3
+ size 5344103080
model-0007-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:735b9cc3f26729f3ac058d40dbab4dd6c4fc80fcef87814ecf979dde5b4809a7
3
+ size 5318937256
model-0008-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:38ddb09d7f113bfd2b9b77d604251a27c1f5252fe161ef16138ddd3092c4a7b3
3
+ size 5344103088
model-0009-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4a670adad4ffc1c084887a144f40d83f2b737fb39d51b99e0a7ac719c0cd0e58
3
+ size 5318937256
model-0010-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1bc6d5bd8e956687d7f1ecdc2dcf61731828f16758c6a0e61c6904a8e874f1ef
3
+ size 5344103136
model-0011-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9bc082641d9f77d84c8b29a67cd4bdade9c754485741a4785fc550ecf17042a8
3
+ size 5318937400
model-0012-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:493b7acccb94a8808c3851ab0fe585af0c39df1c1a36fd66305d6aea030f4292
3
+ size 5344103232
model-0013-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1a0ed8a1eadb8a750d238d490db84fd7546d31649991666b867811e5e2d59853
3
+ size 5318937400
model-0014-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c6023be614b51bbc4a987930cf1a40d5beb192b6cb33b916601813dcca8e0d98
3
+ size 5344103232
model-0015-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bf87b76628ed4c85a0d8d6259a224dfea5464eb2d59a7adea00a02e733574f72
3
+ size 5318937400
model-0016-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c4f83f99e3ba5038cb05e05a6419ff5bbc72f341f453d3be87cf975fdb798e8c
3
+ size 5344103232
model-0017-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:791f22de90a61a7eab1bd8d00827920fac4b3591ff5f8c7b5ba3addcb23e564b
3
+ size 5318937392
model-0018-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6239f3e75845c6fc21ce4b865b4d2bdcaece44363c549da1ca2209d3d49ef4c0
3
+ size 5344636312
model-0019-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3eeffa236a37d693e6d2bc111d97a53f844a3d6f4adf99576841a2df8bc425d2
3
+ size 5327334656
model-0020-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:03a578aed706c5a11abeb8698a1eeb7aa1b05efac904087c0373c2218cc25a70
3
+ size 5344103224
model-0021-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:443c86dd32419ef1b1891d876f0e5260dbf8a8758519bb70a41047394efdc53f
3
+ size 5318937400
model-0022-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:99fef1fe82cdde878b6b7e022644571aba9710535be9fbbc9860f71eeb0d3efc
3
+ size 5344103232
model-0023-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:65c68d803db0691f317ef0a91a0937c84926be1dd9f5d7b622b4910b58ae9324
3
+ size 5318937400
model-0024-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c6323ef5306a06cae635a72aea64e2d34677cfd69beff18ffc90b248b2098257
3
+ size 5344103232
model-0025-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a10239c412419c8cce8cc62bd6196775a037bc5c607b13018e55b0b13f1ecdef
3
+ size 5318937400
model-0026-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:94a48c3477b95f036ed5564e59e244400abd167da53d3b48af59870cb38d4c57
3
+ size 5344103232
model-0027-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:77c33d36df1de8cac089fb38e56674d8824f20dae0609c18b0184a2b590b9881
3
+ size 5318937400
model-0028-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:403c781276c72030dbe4b378cfb65bcbce7c40fb145bc42ba1295fe24c0ca59f
3
+ size 5344103232
model-0029-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2c4a8f2224fd91b6a2c1ba83c88e751d4a1cced6996c9c1e25bf92da64788c52
3
+ size 5318937400
model-0030-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2223dd71e2a2826259094bf91c22cc5b70cdea5f26890fe332a7e9362cba3508
3
+ size 5344103232
model-0031-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:459d8bbc4c0f52b2ebfeb79d74886a271216157c689748d1e6bbd97a8393a38b
3
+ size 5302504652
model-0032-of-0032.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:65b72ec507661c7a146b3174fd0baaa2689a5c7e85a8736aa9778b1d146a4ec6
3
+ size 3316299512
model.safetensors.index.json ADDED
The diff for this file is too large to render. See raw diff
 
modeling_hunyuan_image_3.py ADDED
The diff for this file is too large to render. See raw diff
 
siglip2.py ADDED
@@ -0,0 +1,570 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Licensed under the TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # https://github.com/Tencent-Hunyuan/HunyuanImage-3.0/blob/main/LICENSE
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ # ==============================================================================
13
+ #
14
+ # Copyright 2025 The HuggingFace Inc. team.
15
+ #
16
+ # Licensed under the Apache License, Version 2.0 (the "License");
17
+ # you may not use this file except in compliance with the License.
18
+ # You may obtain a copy of the License at
19
+ #
20
+ # http://www.apache.org/licenses/LICENSE-2.0
21
+ #
22
+ # Unless required by applicable law or agreed to in writing, software
23
+ # distributed under the License is distributed on an "AS IS" BASIS,
24
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25
+ # See the License for the specific language governing permissions and
26
+ # limitations under the License.
27
+ # ==============================================================================
28
+
29
+ from typing import Optional, Tuple, Union
30
+ import warnings
31
+
32
+ import torch
33
+ import torch.nn as nn
34
+ import torch.nn.functional as F
35
+
36
+ from transformers.activations import ACT2FN
37
+ from transformers.modeling_outputs import BaseModelOutput, BaseModelOutputWithPooling
38
+ from transformers.modeling_attn_mask_utils import _prepare_4d_attention_mask
39
+
40
+
41
+ class Config(object):
42
+ def __init__(self, config):
43
+ if config is not None:
44
+ for key, value in config.items():
45
+ setattr(self, key, value)
46
+
47
+ def __getitem__(self, key):
48
+ return getattr(self, key, None)
49
+
50
+ def __setitem__(self, key, value):
51
+ return setattr(self, key, value)
52
+
53
+
54
+ class Siglip2VisionEmbeddings(nn.Module):
55
+ def __init__(self, config):
56
+ super().__init__()
57
+ self.config = config
58
+ self.embed_dim = config.hidden_size
59
+ self.patch_size = config.patch_size
60
+
61
+ self.patch_embedding = nn.Linear(
62
+ in_features=config.num_channels * self.patch_size * self.patch_size,
63
+ out_features=self.embed_dim,
64
+ )
65
+
66
+ self.num_patches = config.num_patches
67
+ self.position_embedding_size = int(self.num_patches**0.5)
68
+ self.position_embedding = nn.Embedding(self.num_patches, self.embed_dim)
69
+
70
+ @staticmethod
71
+ def resize_positional_embeddings(
72
+ positional_embeddings: torch.Tensor,
73
+ spatial_shapes: torch.LongTensor,
74
+ max_length: int,
75
+ ) -> torch.Tensor:
76
+ """
77
+ Resize positional embeddings to image-specific size and pad to a fixed size.
78
+
79
+ Args:
80
+ positional_embeddings (`torch.Tensor`):
81
+ Position embeddings of shape (height, width, embed_dim)
82
+ spatial_shapes (`torch.LongTensor`):
83
+ Spatial shapes of shape (batch_size, 2) to resize the positional embeddings to
84
+ max_length (`int`):
85
+ Maximum length of the positional embeddings to pad resized positional embeddings to
86
+
87
+ Returns:
88
+ `torch.Tensor`: Embeddings of shape (batch_size, max_length, embed_dim)
89
+ """
90
+ batch_size = spatial_shapes.shape[0]
91
+ embed_dim = positional_embeddings.shape[-1]
92
+ source_dtype = positional_embeddings.dtype
93
+
94
+ resulted_positional_embeddings = torch.empty(
95
+ (batch_size, max_length, embed_dim),
96
+ device=positional_embeddings.device,
97
+ dtype=source_dtype,
98
+ )
99
+
100
+ # (height, width, embed_dim) -> (1, embed_dim, height, width) for interpolation
101
+ positional_embeddings = positional_embeddings.permute(2, 0, 1).unsqueeze(0)
102
+
103
+ # Upcast to float32 on CPU because antialias is not supported for bfloat16/float16 on CPU
104
+ if positional_embeddings.device.type == "cpu":
105
+ positional_embeddings = positional_embeddings.to(torch.float32)
106
+
107
+ for i in range(batch_size):
108
+ # (1, dim, height, width) -> (1, dim, target_height, target_width)
109
+ height, width = spatial_shapes[i]
110
+ resized_embeddings = F.interpolate(
111
+ positional_embeddings,
112
+ size=(height, width),
113
+ mode="bilinear",
114
+ align_corners=False,
115
+ antialias=True,
116
+ )
117
+
118
+ # (1, dim, target_height, target_width) -> (target_height * target_width, dim)
119
+ resized_embeddings = resized_embeddings.reshape(embed_dim, height * width).transpose(0, 1)
120
+
121
+ # Cast to original dtype
122
+ resized_embeddings = resized_embeddings.to(source_dtype)
123
+
124
+ resulted_positional_embeddings[i, : height * width] = resized_embeddings
125
+ resulted_positional_embeddings[i, height * width :] = resized_embeddings[0]
126
+
127
+ return resulted_positional_embeddings
128
+
129
+ def forward(self, pixel_values: torch.FloatTensor, spatial_shapes: torch.LongTensor) -> torch.Tensor:
130
+ """
131
+ Args:
132
+ pixel_values (`torch.FloatTensor`):
133
+ Pixel values of shape (batch_size, max_num_patches, num_channels * patch_size * patch_size)
134
+ spatial_shapes (`List[Tuple[int, int]]`):
135
+ Spatial shapes of shape (batch_size, 2) to resize the positional embeddings to
136
+ """
137
+
138
+ # Apply patch embeddings to already patchified pixel values
139
+ target_dtype = self.patch_embedding.weight.dtype
140
+ patch_embeds = self.patch_embedding(pixel_values.to(dtype=target_dtype))
141
+
142
+ # Get positional resized and padded positional embeddings
143
+ positional_embeddings = self.position_embedding.weight.reshape(
144
+ self.position_embedding_size, self.position_embedding_size, -1
145
+ )
146
+ resized_positional_embeddings = self.resize_positional_embeddings(
147
+ positional_embeddings, spatial_shapes, max_length=pixel_values.shape[1]
148
+ )
149
+
150
+ # Add positional embeddings to patch embeddings
151
+ embeddings = patch_embeds + resized_positional_embeddings
152
+ return embeddings
153
+
154
+
155
+ class Siglip2Attention(nn.Module):
156
+ """Multi-headed attention from 'Attention Is All You Need' paper"""
157
+
158
+ def __init__(self, config):
159
+ super().__init__()
160
+ self.config = config
161
+ self.embed_dim = config.hidden_size
162
+ self.num_heads = config.num_attention_heads
163
+ self.head_dim = self.embed_dim // self.num_heads
164
+ if self.head_dim * self.num_heads != self.embed_dim:
165
+ raise ValueError(
166
+ f"embed_dim must be divisible by num_heads (got `embed_dim`: {self.embed_dim} and `num_heads`:"
167
+ f" {self.num_heads})."
168
+ )
169
+ self.scale = self.head_dim**-0.5
170
+ self.dropout = config.attention_dropout
171
+
172
+ self.k_proj = nn.Linear(self.embed_dim, self.embed_dim)
173
+ self.v_proj = nn.Linear(self.embed_dim, self.embed_dim)
174
+ self.q_proj = nn.Linear(self.embed_dim, self.embed_dim)
175
+ self.out_proj = nn.Linear(self.embed_dim, self.embed_dim)
176
+
177
+ def forward(
178
+ self,
179
+ hidden_states: torch.Tensor,
180
+ attention_mask: Optional[torch.Tensor] = None,
181
+ output_attentions: Optional[bool] = False,
182
+ ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
183
+ """Input shape: Batch x Time x Channel"""
184
+
185
+ batch_size, q_len, _ = hidden_states.size()
186
+
187
+ query_states = self.q_proj(hidden_states)
188
+ key_states = self.k_proj(hidden_states)
189
+ value_states = self.v_proj(hidden_states)
190
+
191
+ query_states = query_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2)
192
+ key_states = key_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2)
193
+ value_states = value_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2)
194
+
195
+ k_v_seq_len = key_states.shape[-2]
196
+ attn_weights = torch.matmul(query_states, key_states.transpose(2, 3)) * self.scale
197
+
198
+ if attn_weights.size() != (batch_size, self.num_heads, q_len, k_v_seq_len):
199
+ raise ValueError(
200
+ f"Attention weights should be of size {(batch_size, self.num_heads, q_len, k_v_seq_len)}, but is"
201
+ f" {attn_weights.size()}"
202
+ )
203
+
204
+ if attention_mask is not None:
205
+ if attention_mask.size() != (batch_size, 1, q_len, k_v_seq_len):
206
+ raise ValueError(
207
+ f"Attention mask should be of size {(batch_size, 1, q_len, k_v_seq_len)}, "
208
+ f"but is {attention_mask.size()}"
209
+ )
210
+ attn_weights = attn_weights + attention_mask
211
+
212
+ # upcast attention to fp32
213
+ attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query_states.dtype)
214
+ attn_weights = nn.functional.dropout(attn_weights, p=self.dropout, training=self.training)
215
+ attn_output = torch.matmul(attn_weights, value_states)
216
+
217
+ if attn_output.size() != (batch_size, self.num_heads, q_len, self.head_dim):
218
+ raise ValueError(
219
+ f"`attn_output` should be of size {(batch_size, self.num_heads, q_len, self.head_dim)}, but is"
220
+ f" {attn_output.size()}"
221
+ )
222
+
223
+ attn_output = attn_output.transpose(1, 2).contiguous()
224
+ attn_output = attn_output.reshape(batch_size, q_len, self.embed_dim)
225
+
226
+ attn_output = self.out_proj(attn_output)
227
+
228
+ return attn_output, attn_weights
229
+
230
+ class Siglip2SdpaAttention(Siglip2Attention):
231
+ """
232
+ Siglip2 attention module using torch.nn.functional.scaled_dot_product_attention. This module inherits from
233
+ `Siglip2Attention` as the weights of the module stays untouched. The only changes are on the forward pass to adapt
234
+ to SDPA API.
235
+ """
236
+
237
+ is_causal = False
238
+
239
+ # Adapted from Siglip2Attention.forward and transformers.models.llama.modeling_llama.LlamaSdpaAttention.forward
240
+ def forward(
241
+ self,
242
+ hidden_states: torch.Tensor,
243
+ attention_mask: Optional[torch.Tensor] = None,
244
+ output_attentions: Optional[bool] = False,
245
+ ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
246
+ if output_attentions:
247
+ # TODO: Improve this warning with e.g. `model.config.attn_implementation = "manual"`
248
+ # once this is implemented.
249
+ warnings.warn(
250
+ "Siglip2Model is using Siglip2SdpaAttention, but `torch.nn.functional.scaled_dot_product_attention` "
251
+ "does not support `output_attentions=True`. Falling back to the manual attention implementation, "
252
+ 'but specifying the manual implementation will be required from Transformers version v5.0.0 onwards. '
253
+ 'This warning can be removed using the argument `attn_implementation="eager"` when loading the model.'
254
+ )
255
+ return super().forward(
256
+ hidden_states=hidden_states,
257
+ attention_mask=attention_mask,
258
+ output_attentions=output_attentions,
259
+ )
260
+
261
+ batch_size, q_len, _ = hidden_states.size()
262
+
263
+ query_states = self.q_proj(hidden_states)
264
+ key_states = self.k_proj(hidden_states)
265
+ value_states = self.v_proj(hidden_states)
266
+
267
+ query_states = query_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2)
268
+ key_states = key_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2)
269
+ value_states = value_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2)
270
+
271
+ # SDPA with memory-efficient backend is currently (torch==2.1.2) bugged with non-contiguous inputs with
272
+ # custom attn_mask,
273
+ # Reference: https://github.com/pytorch/pytorch/issues/112577.
274
+ if query_states.device.type == "cuda" and attention_mask is not None:
275
+ query_states = query_states.contiguous()
276
+ key_states = key_states.contiguous()
277
+ value_states = value_states.contiguous()
278
+
279
+ # We dispatch to SDPA's Flash Attention or Efficient kernels via this `is_causal` if statement instead of an
280
+ # inline conditional assignment in SDPA to support both torch.compile's dynamic shapes and full graph options.
281
+ # An inline conditional prevents dynamic shapes from compiling.
282
+ is_causal = True if self.is_causal and q_len > 1 else False
283
+
284
+ attn_output = torch.nn.functional.scaled_dot_product_attention(
285
+ query_states,
286
+ key_states,
287
+ value_states,
288
+ attn_mask=attention_mask,
289
+ dropout_p=self.dropout if self.training else 0.0,
290
+ is_causal=is_causal,
291
+ )
292
+
293
+ attn_output = attn_output.transpose(1, 2).contiguous()
294
+ attn_output = attn_output.view(batch_size, q_len, self.embed_dim)
295
+
296
+ attn_output = self.out_proj(attn_output)
297
+
298
+ return attn_output, None
299
+
300
+
301
+ class Siglip2MLP(nn.Module):
302
+ def __init__(self, config):
303
+ super().__init__()
304
+ self.config = config
305
+ self.activation_fn = ACT2FN[config.hidden_act]
306
+ self.fc1 = nn.Linear(config.hidden_size, config.intermediate_size)
307
+ self.fc2 = nn.Linear(config.intermediate_size, config.hidden_size)
308
+
309
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
310
+ hidden_states = self.fc1(hidden_states)
311
+ hidden_states = self.activation_fn(hidden_states)
312
+ hidden_states = self.fc2(hidden_states)
313
+ return hidden_states
314
+
315
+
316
+ class Siglip2EncoderLayer(nn.Module):
317
+ def __init__(self, config):
318
+ super().__init__()
319
+ self.embed_dim = config.hidden_size
320
+ self.self_attn = Siglip2Attention(config=config)
321
+ self.layer_norm1 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps)
322
+ self.mlp = Siglip2MLP(config)
323
+ self.layer_norm2 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps)
324
+
325
+ # Ignore copy
326
+ def forward(
327
+ self,
328
+ hidden_states: torch.Tensor,
329
+ attention_mask: torch.Tensor,
330
+ output_attentions: Optional[bool] = False,
331
+ ) -> Tuple[torch.FloatTensor]:
332
+ """
333
+ Args:
334
+ hidden_states (`torch.FloatTensor`):
335
+ Input to the layer of shape `(batch, seq_len, embed_dim)`.
336
+ attention_mask (`torch.FloatTensor`):
337
+ Attention mask of shape `(batch, 1, q_len, k_v_seq_len)` where padding elements are indicated by very
338
+ large negative values.
339
+ output_attentions (`bool`, *optional*, defaults to `False`):
340
+ Whether or not to return the attentions tensors of all attention layers. See `attentions` under
341
+ returned tensors for more detail.
342
+ """
343
+ residual = hidden_states
344
+
345
+ hidden_states = self.layer_norm1(hidden_states)
346
+ hidden_states, attn_weights = self.self_attn(
347
+ hidden_states=hidden_states,
348
+ attention_mask=attention_mask,
349
+ output_attentions=output_attentions,
350
+ )
351
+ hidden_states = residual + hidden_states
352
+
353
+ residual = hidden_states
354
+ hidden_states = self.layer_norm2(hidden_states)
355
+ hidden_states = self.mlp(hidden_states)
356
+ hidden_states = residual + hidden_states
357
+
358
+ outputs = (hidden_states,)
359
+
360
+ if output_attentions:
361
+ outputs += (attn_weights,)
362
+
363
+ return outputs
364
+
365
+
366
+ class Siglip2Encoder(nn.Module):
367
+ """
368
+ Transformer encoder consisting of `config.num_hidden_layers` self attention layers. Each layer is a
369
+ [`Siglip2EncoderLayer`].
370
+
371
+ Args:
372
+ config: Siglip2Config
373
+ """
374
+
375
+ def __init__(self, config):
376
+ super().__init__()
377
+ self.config = config
378
+ self.layers = nn.ModuleList([Siglip2EncoderLayer(config) for _ in range(config.num_hidden_layers)])
379
+ self.gradient_checkpointing = True
380
+
381
+ # Ignore copy
382
+ def forward(
383
+ self,
384
+ inputs_embeds,
385
+ attention_mask: Optional[torch.Tensor] = None,
386
+ output_attentions: Optional[bool] = None,
387
+ output_hidden_states: Optional[bool] = None,
388
+ return_dict: Optional[bool] = None,
389
+ ) -> Union[Tuple, BaseModelOutput]:
390
+ r"""
391
+ Args:
392
+ inputs_embeds (`torch.FloatTensor` of shape `(batch_size, sequence_length, hidden_size)`):
393
+ Optionally, instead of passing `input_ids` you can choose to directly pass an embedded representation.
394
+ This is useful if you want more control over how to convert `input_ids` indices into associated vectors
395
+ than the model's internal embedding lookup matrix.
396
+ attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*):
397
+ Mask to avoid performing attention on padding token indices. Mask values selected in `[0, 1]`:
398
+
399
+ - 1 for tokens that are **not masked**,
400
+ - 0 for tokens that are **masked**.
401
+
402
+ [What are attention masks?](../glossary#attention-mask)
403
+ output_attentions (`bool`, *optional*):
404
+ Whether or not to return the attentions tensors of all attention layers. See `attentions` under
405
+ returned tensors for more detail.
406
+ output_hidden_states (`bool`, *optional*):
407
+ Whether or not to return the hidden states of all layers. See `hidden_states` under returned tensors
408
+ for more detail.
409
+ return_dict (`bool`, *optional*):
410
+ Whether or not to return a [`~utils.ModelOutput`] instead of a plain tuple.
411
+ """
412
+ output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
413
+ output_hidden_states = (
414
+ output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
415
+ )
416
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
417
+
418
+ encoder_states = () if output_hidden_states else None
419
+ all_attentions = () if output_attentions else None
420
+
421
+ hidden_states = inputs_embeds
422
+ for layer_index, encoder_layer in enumerate(self.layers): # len(self.layers): 27
423
+ if output_hidden_states:
424
+ encoder_states = encoder_states + (hidden_states,)
425
+
426
+ layer_outputs = encoder_layer(
427
+ hidden_states,
428
+ attention_mask,
429
+ output_attentions=output_attentions,
430
+ )
431
+
432
+ hidden_states = layer_outputs[0]
433
+
434
+ if output_attentions:
435
+ all_attentions = all_attentions + (layer_outputs[1],)
436
+
437
+ if output_hidden_states:
438
+ encoder_states = encoder_states + (hidden_states,)
439
+
440
+ if not return_dict:
441
+ return tuple(v for v in [hidden_states, encoder_states, all_attentions] if v is not None)
442
+ return BaseModelOutput(
443
+ last_hidden_state=hidden_states, hidden_states=encoder_states, attentions=all_attentions
444
+ )
445
+
446
+
447
+ class Siglip2MultiheadAttentionPoolingHead(nn.Module):
448
+ """Multihead Attention Pooling."""
449
+
450
+ def __init__(self, config):
451
+ super().__init__()
452
+
453
+ self.probe = nn.Parameter(torch.randn(1, 1, config.hidden_size))
454
+ self.attention = torch.nn.MultiheadAttention(config.hidden_size, config.num_attention_heads, batch_first=True)
455
+ self.layernorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
456
+ self.mlp = Siglip2MLP(config)
457
+ self.num_heads = config.num_attention_heads
458
+
459
+ def forward(self, hidden_state: torch.Tensor, attention_mask: Optional[torch.Tensor] = None):
460
+ batch_size = hidden_state.shape[0]
461
+ probe = self.probe.repeat(batch_size, 1, 1)
462
+
463
+ if attention_mask is not None:
464
+ target_len, source_len = probe.shape[1], hidden_state.shape[1]
465
+ attention_mask = _prepare_4d_attention_mask(attention_mask, hidden_state.dtype, target_len)
466
+ attention_mask = attention_mask.repeat(1, self.num_heads, target_len, 1)
467
+ attention_mask = attention_mask.reshape(-1, target_len, source_len)
468
+
469
+ hidden_state = self.attention(probe, hidden_state, hidden_state, attn_mask=attention_mask)[0]
470
+
471
+ residual = hidden_state
472
+ hidden_state = self.layernorm(hidden_state)
473
+ hidden_state = residual + self.mlp(hidden_state)
474
+
475
+ return hidden_state[:, 0]
476
+
477
+
478
+ class Siglip2VisionTransformer(nn.Module):
479
+ def __init__(self, config):
480
+ super().__init__()
481
+ config = Config(config)
482
+ self.config = config
483
+ embed_dim = config.hidden_size
484
+
485
+ self.embeddings = Siglip2VisionEmbeddings(config)
486
+ self.encoder = Siglip2Encoder(config)
487
+ self.post_layernorm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps)
488
+ self.use_head = True if not hasattr(config, "vision_use_head") else config.vision_use_head
489
+ if self.use_head:
490
+ self.head = Siglip2MultiheadAttentionPoolingHead(config)
491
+ self._use_flash_attention_2 = config._attn_implementation == "flash_attention_2"
492
+
493
+ def forward(
494
+ self,
495
+ pixel_values: torch.FloatTensor,
496
+ attention_mask: torch.Tensor,
497
+ spatial_shapes: torch.LongTensor,
498
+ output_attentions: Optional[bool] = None,
499
+ output_hidden_states: Optional[bool] = None,
500
+ return_dict: Optional[bool] = None,
501
+ ) -> Union[Tuple, BaseModelOutputWithPooling]:
502
+ r"""
503
+ Returns:
504
+
505
+ """
506
+ output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
507
+ output_hidden_states = (
508
+ output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
509
+ )
510
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
511
+
512
+ hidden_states = self.embeddings(pixel_values, spatial_shapes)
513
+
514
+ if attention_mask is not None and not self._use_flash_attention_2:
515
+ # [batch_size, seq_len] -> [batch_size, 1, tgt_seq_len, src_seq_len]
516
+ encoder_attention_mask = _prepare_4d_attention_mask(attention_mask, hidden_states.dtype)
517
+ else:
518
+ encoder_attention_mask = attention_mask
519
+
520
+ encoder_outputs = self.encoder(
521
+ inputs_embeds=hidden_states,
522
+ attention_mask=encoder_attention_mask,
523
+ output_attentions=output_attentions,
524
+ output_hidden_states=output_hidden_states,
525
+ return_dict=return_dict,
526
+ )
527
+
528
+ last_hidden_state = encoder_outputs[0]
529
+ last_hidden_state = self.post_layernorm(last_hidden_state)
530
+
531
+ pooler_output = self.head(last_hidden_state, attention_mask) if self.use_head else None
532
+ if not return_dict:
533
+ return (last_hidden_state, pooler_output) + encoder_outputs[1:]
534
+
535
+ return BaseModelOutputWithPooling(
536
+ last_hidden_state=last_hidden_state,
537
+ pooler_output=pooler_output,
538
+ hidden_states=encoder_outputs.hidden_states,
539
+ attentions=encoder_outputs.attentions,
540
+ )
541
+
542
+
543
+ class LightProjector(nn.Module):
544
+ def __init__(self, config):
545
+ config = Config(config)
546
+ super().__init__()
547
+
548
+ if config.projector_type == "linear":
549
+ modules = nn.Linear(config.input_dim, config.n_embed)
550
+
551
+ elif config.projector_type == "mlp_gelu":
552
+ modules = [nn.Linear(config.input_dim, config.n_embed)]
553
+ for _ in range(1, config.depth):
554
+ modules.append(nn.GELU())
555
+ modules.append(nn.Linear(config.n_embed, config.n_embed))
556
+ modules = nn.Sequential(*modules)
557
+
558
+ else:
559
+ raise ValueError(f"Unknown projector type: {config.projector_type}")
560
+
561
+ self.layers = modules
562
+
563
+ def forward(self, x):
564
+ return self.layers(x)
565
+
566
+
567
+ __all__ = [
568
+ "Siglip2VisionTransformer",
569
+ "LightProjector",
570
+ ]
system_prompt.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Licensed under the TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # https://github.com/Tencent-Hunyuan/HunyuanImage-3.0/blob/main/LICENSE
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ # ==============================================================================
13
+
14
+ t2i_system_prompt_en_vanilla = """
15
+ You are an advanced AI text-to-image generation system. Given a detailed text prompt, your task is to create a high-quality, visually compelling image that accurately represents the described scene, characters, or objects. Pay careful attention to style, color, lighting, perspective, and any specific instructions provided.
16
+ """
17
+
18
+ # 775
19
+ t2i_system_prompt_en_recaption = """
20
+ You are a world-class image generation prompt expert. Your task is to rewrite a user's simple description into a **structured, objective, and detail-rich** professional-level prompt.
21
+
22
+ The final output must be wrapped in `<recaption>` tags.
23
+
24
+ ### **Universal Core Principles**
25
+
26
+ When rewriting the prompt (inside the `<recaption>` tags), you must adhere to the following principles:
27
+
28
+ 1. **Absolute Objectivity**: Describe only what is visually present. Avoid subjective words like "beautiful" or "sad". Convey aesthetic qualities through specific descriptions of color, light, shadow, and composition.
29
+ 2. **Physical and Logical Consistency**: All scene elements (e.g., gravity, light, shadows, reflections, spatial relationships, object proportions) must strictly adhere to real-world physics and common sense. For example, tennis players must be on opposite sides of the net; objects cannot float without a cause.
30
+ 3. **Structured Description**: Strictly follow a logical order: from general to specific, background to foreground, and primary to secondary elements. Use directional terms like "foreground," "mid-ground," "background," and "left side of the frame" to clearly define the spatial layout.
31
+ 4. **Use Present Tense**: Describe the scene from an observer's perspective using the present tense, such as "A man stands..." or "Light shines on..."
32
+ 5. **Use Rich and Specific Descriptive Language**: Use precise adjectives to describe the quantity, size, shape, color, and other attributes of objects, subjects, and text. Vague expressions are strictly prohibited.
33
+
34
+ If the user specifies a style (e.g., oil painting, anime, UI design, text rendering), strictly adhere to that style. Otherwise, first infer a suitable style from the user's input. If there is no clear stylistic preference, default to an **ultra-realistic photographic style**. Then, generate the detailed rewritten prompt according to the **Style-Specific Creation Guide** below:
35
+
36
+ ### **Style-Specific Creation Guide**
37
+
38
+ Based on the determined artistic style, apply the corresponding professional knowledge.
39
+
40
+ **1. Photography and Realism Style**
41
+ * Utilize professional photography terms (e.g., lighting, lens, composition) and meticulously detail material textures, physical attributes of subjects, and environmental details.
42
+
43
+ **2. Illustration and Painting Style**
44
+ * Clearly specify the artistic school (e.g., Japanese Cel Shading, Impasto Oil Painting) and focus on describing its unique medium characteristics, such as line quality, brushstroke texture, or paint properties.
45
+
46
+ **3. Graphic/UI/APP Design Style**
47
+ * Objectively describe the final product, clearly defining the layout, elements, and color palette. All text on the interface must be enclosed in double quotes `""` to specify its exact content (e.g., "Login"). Vague descriptions are strictly forbidden.
48
+
49
+ **4. Typographic Art**
50
+ * The text must be described as a complete physical object. The description must begin with the text itself. Use a straightforward front-on or top-down perspective to ensure the entire text is visible without cropping.
51
+
52
+ ### **Final Output Requirements**
53
+
54
+ 1. **Output the Final Prompt Only**: Do not show any thought process, Markdown formatting, or line breaks.
55
+ 2. **Adhere to the Input**: You must retain the core concepts, attributes, and any specified text from the user's input.
56
+ 3. **Style Reinforcement**: Mention the core style 3-5 times within the prompt and conclude with a style declaration sentence.
57
+ 4. **Avoid Self-Reference**: Describe the image content directly. Remove redundant phrases like "This image shows..." or "The scene depicts..."
58
+ 5. **The final output must be wrapped in `<recaption>xxxx</recaption>` tags.**
59
+
60
+ The user will now provide an input prompt. You will provide the expanded prompt.
61
+ """
62
+
63
+ # 890
64
+ t2i_system_prompt_en_think_recaption = """
65
+ You will act as a top-tier Text-to-Image AI. Your core task is to deeply analyze the user's text input and transform it into a detailed, artistic, and fully user-intent-compliant image.
66
+
67
+ Your workflow is divided into two phases:
68
+
69
+ 1. Thinking Phase (<think>): In the <think> tag, you need to conduct a structured thinking process, progressively breaking down and enriching the constituent elements of the image. This process must include, but is not limited to, the following dimensions:
70
+
71
+ Subject: Clearly define the core character(s) or object(s) in the scene, including their appearance, posture, expression, and emotion.
72
+ Composition: Set the camera angle and layout, such as close-up, long shot, bird's-eye view, golden ratio composition, etc.
73
+ Environment/Background: Describe the scene where the subject is located, including the location, time of day, weather, and other elements in the background.
74
+ Lighting: Define the type, direction, and quality of the light source, such as soft afternoon sunlight, cool tones of neon lights, dramatic Rembrandt lighting, etc., to create a specific atmosphere.
75
+ Color Palette: Set the main color tone and color scheme of the image, such as vibrant and saturated, low-saturation Morandi colors, black and white, etc.
76
+ Quality/Style: Determine the artistic style and technical details of the image. This includes user-specified styles (e.g., anime, oil painting) or the default realistic style, as well as camera parameters (e.g., focal length, aperture, depth of field).
77
+ Details: Add minute elements that enhance the realism and narrative quality of the image, such as a character's accessories, the texture of a surface, dust particles in the air, etc.
78
+
79
+
80
+ 2. Recaption Phase (<recaption>): In the <recaption> tag, merge all the key details from the thinking process into a coherent, precise, and visually evocative final description. This description is the direct instruction for generating the image, so it must be clear, unambiguous, and organized in a way that is most suitable for an image generation engine to understand.
81
+
82
+ Absolutely Objective: Describe only what is visually present. Avoid subjective words like "beautiful" or "sad." Convey aesthetic sense through concrete descriptions of colors, light, shadow, and composition.
83
+
84
+ Physical and Logical Consistency: All scene elements (e.g., gravity, light and shadow, reflections, spatial relationships, object proportions) must strictly adhere to the physical laws of the real world and common sense. For example, in a tennis match, players must be on opposite sides of the net; objects cannot float without reason.
85
+
86
+ Structured Description: Strictly follow a logical order: from whole to part, background to foreground, and primary to secondary. Use directional words like "foreground," "mid-ground," "background," "left side of the frame" to clearly define the spatial layout.
87
+
88
+ Use Present Tense: Describe from an observer's perspective using the present tense, such as "a man stands," "light shines on..."
89
+ Use Rich and Specific Descriptive Language: Use precise adjectives to describe the quantity, size, shape, color, and other attributes of objects/characters/text. Absolutely avoid any vague expressions.
90
+
91
+
92
+ Output Format:
93
+ <think>Thinking process</think><recaption>Refined image description</recaption>Generate Image
94
+
95
+
96
+ You must strictly adhere to the following rules:
97
+
98
+ 1. Faithful to Intent, Reasonable Expansion: You can creatively add details to the user's description to enhance the image's realism and artistic quality. However, all additions must be highly consistent with the user's core intent and never introduce irrelevant or conflicting elements.
99
+ 2. Style Handling: When the user does not specify a style, you must default to an "Ultra-realistic, Photorealistic" style. If the user explicitly specifies a style (e.g., anime, watercolor, oil painting, cyberpunk, etc.), both your thinking process and final description must strictly follow and reflect that specified style.
100
+ 3. Text Rendering: If specific text needs to appear in the image (such as words on a sign, a book title), you must enclose this text in English double quotes (""). Descriptive text must not use double quotes.
101
+ 4. Design-related Images: You need to specify all text and graphical elements that appear in the image and clearly describe their design details, including font, color, size, position, arrangement, visual effects, etc.
102
+ """
103
+
104
+ t2i_system_prompts = {
105
+ "en_vanilla": [t2i_system_prompt_en_vanilla],
106
+ "en_recaption": [t2i_system_prompt_en_recaption],
107
+ "en_think_recaption": [t2i_system_prompt_en_think_recaption]
108
+ }
109
+
110
+
111
+ unified_system_prompt_en = """You are an advanced multimodal model whose core mission is to analyze user intent and generate high-quality text and images.
112
+
113
+ #### Four Core Capabilities
114
+ 1. **Text-to-Text (T2T):** Generate coherent text responses from text prompts.
115
+ 2. **Text-to-Image (T2I):** Generate high-quality images from text prompts.
116
+ 3. **Text & Image to Text (TI2T):** Generate accurate text responses based on a combination of images and text.
117
+ 4. **Text & Image to Image (TI2I):** Generate modified images based on a reference image and editing instructions.
118
+
119
+ ---
120
+ ### Image Generation Protocol (for T2I & TI2I)
121
+ You will operate in one of two modes, determined by the user's starting tag:
122
+ #### **<recaption> Mode (Prompt Rewriting)**:
123
+ * **Trigger:** Input begins with `<recaption>`.
124
+ * **Task:** Immediately rewrite the user's text into a structured, objective, and detail-rich professional-grade prompt.
125
+ * **Output:** Output only the rewritten prompt within `<recaption>` tags: `<recaption>Rewritten professional-grade prompt</recaption>`
126
+
127
+ #### **<think> Mode (Think + Rewrite)**:
128
+ * **Trigger:** Input begins with `<think>`.
129
+ * **Task:** First, conduct a structured analysis of the request within `<think>` tags. Then, output the professional prompt, rewritten based on the analysis, within `<recaption>` tags.
130
+ * **Output:** Strictly adhere to the format: `<think>Analysis process</think><recaption>Rewritten prompt</recaption>`
131
+
132
+ ---
133
+ ### Execution Standards and Guidelines
134
+ #### **`<think>` Phase: Analysis Guidelines**
135
+ **For T2I (New Image Generation):**
136
+ Deconstruct the user's request into the following core visual components:
137
+ * **Subject:** Key features of the main character/object, including appearance, pose, expression, and emotion.
138
+ * **Composition:** Camera angle, lens type, and layout.
139
+ * **Environment/Background:** The setting, time of day, weather, and background elements.
140
+ * **Lighting:** Technical details such as light source type, direction, and quality.
141
+ * **Color Palette:** The dominant hues and overall color scheme.
142
+ * **Style/Quality:** The artistic style, clarity, depth of field, and other technical details.
143
+ * **Text:** Identify any text to be rendered in the image, including its content, style, and position.
144
+ * **Details:** Small elements that add narrative depth and realism.
145
+
146
+ **For TI2I (Image Editing):**
147
+ Adopt a task-diagnostic approach:
148
+ 1. **Diagnose Task:** Identify the edit type and analyze key requirements.
149
+ 2. **Prioritize Analysis:**
150
+ * **Adding:** Analyze the new element's position and appearance, ensuring seamless integration with the original image's lighting, shadows, and style.
151
+ * **Removing:** Identify the target for removal and determine how to logically fill the resulting space using surrounding textures and lighting.
152
+ * **Modifying:** Analyze what to change and what it should become, while emphasizing which elements must remain unchanged.
153
+ * **Style Transfer:** Deconstruct the target style into specific features (e.g., brushstrokes, color palette) and apply them to the original image.
154
+ * **Text Editing:** Ensure correct content and format. Consider the text's visual style (e.g., font, color, material) and how it adapts to the surface's perspective, curvature, and lighting.
155
+ * **Reference Editing:** Extract specific visual elements (e.g., appearance, posture, composition, lines, depth) from the reference image to generate an image that aligns with the text description while also incorporating the referenced content.
156
+ * **Inferential Editing:** Identify vague requests (e.g., "make it more professional") and translate them into concrete visual descriptions.
157
+
158
+ #### `<recaption>` Phase: Professional-Grade Prompt Generation Rules
159
+ **General Rewriting Principles (for T2I & TI2I):**
160
+ 1. **Structure & Logic:** Start with a global description. Use positional words (e.g., "foreground", "background") to define the layout.
161
+ 2. **Absolute Objectivity:** Avoid subjective terms. Convey aesthetics through precise descriptions of color, light, shadow, and materials.
162
+ 3. **Physical & Logical Consistency:** Ensure all descriptions adhere to the laws of physics and common sense.
163
+ 4. **Fidelity to User Intent:** Preserve the user's core concepts, subjects, and attributes. Text to be rendered in the image **must be enclosed in double quotes ("")**.
164
+ 5. **Camera & Resolution:** Translate camera parameters into descriptions of visual effects. Convert resolution information into natural language.
165
+
166
+ **T2I-Specific Guidelines:**
167
+ * **Style Adherence & Inference:** Strictly follow the specified style. If none is given, infer the most appropriate style and detail it using professional terminology.
168
+ * **Style Detailing:**
169
+ * **Photography/Realism:** Use professional photography terms to describe lighting, lens effects, and material textures.
170
+ * **Painting/Illustration:** Specify the art movement or medium's characteristics.
171
+ * **UI/Design:** Objectively describe the final product. Define layout, elements, and typography. Text content must be specific and unambiguous.
172
+
173
+ **TI2I-Specific Guidelines:**
174
+ * **Preserve Unchanged Elements:** Emphasize elements that **remain unchanged**. Unless explicitly instructed, never alter a character's identity/appearance, the core background, camera angle, or overall style.
175
+ * **Clear Editing Instructions:**
176
+ * **Replacement:** Use the logic "**replace B with A**," and provide a detailed description of A.
177
+ * **Addition:** Clearly state what to add, where, and what it looks like.
178
+ * **Unambiguous Referencing:** Avoid vague references (e.g., "that person"). Use specific descriptions of appearance.
179
+ """
180
+
181
+
182
+ def get_system_prompt(sys_type, bot_task, system_prompt=None):
183
+ if sys_type == 'None':
184
+ return None
185
+ elif sys_type == "en_unified":
186
+ return unified_system_prompt_en
187
+ elif sys_type in ['en_vanilla', 'en_recaption', 'en_think_recaption']:
188
+ return t2i_system_prompts[sys_type][0]
189
+ elif sys_type == "dynamic":
190
+ if bot_task == "think":
191
+ return t2i_system_prompts["en_think_recaption"][0]
192
+ elif bot_task == "recaption":
193
+ return t2i_system_prompts["en_recaption"][0]
194
+ elif bot_task == "image":
195
+ return t2i_system_prompts["en_vanilla"][0].strip("\n")
196
+ else:
197
+ return system_prompt
198
+ elif sys_type == 'custom':
199
+ return system_prompt
200
+ else:
201
+ raise NotImplementedError(f"Unsupported system prompt type: {sys_type}")
202
+
203
+
204
+ __all__ = [
205
+ "get_system_prompt"
206
+ ]
tokenization_hunyuan_image_3.py ADDED
@@ -0,0 +1,1773 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Licensed under the TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # https://github.com/Tencent-Hunyuan/HunyuanImage-3.0/blob/main/LICENSE
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ # ==============================================================================
13
+
14
+ import random
15
+ from collections import defaultdict
16
+ from copy import deepcopy
17
+ from dataclasses import dataclass
18
+ from enum import IntEnum, auto
19
+ from typing import List, Tuple, Dict
20
+ from typing import Optional, Union, Any
21
+
22
+ import numpy as np
23
+ import torch
24
+ import torch.nn.functional as F
25
+ from diffusers.utils import BaseOutput
26
+
27
+ from transformers.tokenization_utils_fast import PreTrainedTokenizerFast
28
+
29
+
30
+ def default(value, default_value):
31
+ return value if value is not None else default_value
32
+
33
+
34
+ def ensure_list(value):
35
+ if value is None:
36
+ return []
37
+ if isinstance(value, (list, tuple)):
38
+ return list(value)
39
+ return [value]
40
+
41
+
42
+ class Resolution(object):
43
+ def __init__(self, size, *args):
44
+ if isinstance(size, str):
45
+ if 'x' in size:
46
+ size = size.split('x')
47
+ size = (int(size[0]), int(size[1]))
48
+ else:
49
+ size = int(size)
50
+ if len(args) > 0:
51
+ size = (size, args[0])
52
+ if isinstance(size, int):
53
+ size = (size, size)
54
+
55
+ self.h = self.height = size[0]
56
+ self.w = self.width = size[1]
57
+ self.r = self.ratio = self.height / self.width
58
+
59
+ def __getitem__(self, idx):
60
+ if idx == 0:
61
+ return self.h
62
+ elif idx == 1:
63
+ return self.w
64
+ else:
65
+ raise IndexError(f'Index {idx} out of range')
66
+
67
+ def __str__(self):
68
+ return f'{self.h}x{self.w}'
69
+
70
+
71
+ class ResolutionGroup(object):
72
+ def __init__(self, base_size=None, step=None, align=1, extra_resolutions=None):
73
+ self.align = align
74
+ self.base_size = base_size
75
+ assert base_size % align == 0, f'base_size {base_size} is not divisible by align {align}'
76
+ if base_size is not None and not isinstance(base_size, int):
77
+ raise ValueError(f'base_size must be None or int, but got {type(base_size)}')
78
+ if step is None:
79
+ step = base_size // 16
80
+ if step is not None and step > base_size // 2:
81
+ raise ValueError(f'step must be smaller than base_size // 2, but got {step} > {base_size // 2}')
82
+
83
+ self.step = step
84
+ self.data = self._calc_by_step()
85
+
86
+ if extra_resolutions is not None:
87
+ for extra_resolution in extra_resolutions:
88
+ height, width = extra_resolution.height, extra_resolution.width
89
+ ratio = height / width
90
+ flag = True
91
+ for resolution in self.data:
92
+ if resolution.ratio == ratio:
93
+ flag = False
94
+ break
95
+ if flag:
96
+ self.data.append(extra_resolution)
97
+
98
+ self.ratio = np.array([x.ratio for x in self.data])
99
+ self.attr = ['' for _ in range(len(self.data))]
100
+ self.prefix_space = 0
101
+
102
+ def __len__(self):
103
+ return len(self.data)
104
+
105
+ def __getitem__(self, idx):
106
+ return self.data[idx]
107
+
108
+ def __repr__(self):
109
+ prefix = self.prefix_space * ' '
110
+ prefix_close = (self.prefix_space - 4) * ' '
111
+ res_str = f'ResolutionGroup(base_size={self.base_size}, step={self.step}, data='
112
+ attr_maxlen = max([len(x) for x in self.attr] + [5])
113
+ res_str += \
114
+ f'\n{prefix}ID: height width ratio {" " * max(0, attr_maxlen - 4)}count h/16 w/16 tokens\n{prefix}'
115
+ res_str += \
116
+ ('\n' + prefix).join([f'{i:2d}: ({x.h:4d}, {x.w:4d}) {self.ratio[i]:.4f} {self.attr[i]:>{attr_maxlen}s} '
117
+ f'({x.h // 16:3d}, {x.w // 16:3d}) {x.h // 16 * x.w // 16:6d}'
118
+ for i, x in enumerate(self.data)])
119
+ res_str += f'\n{prefix_close})'
120
+ return res_str
121
+
122
+ def _calc_by_step(self):
123
+ assert self.align <= self.step, f'align {self.align} must be smaller than step {self.step}'
124
+
125
+ min_height = self.base_size // 2
126
+ min_width = self.base_size // 2
127
+ max_height = self.base_size * 2
128
+ max_width = self.base_size * 2
129
+
130
+ resolutions = [Resolution(self.base_size, self.base_size)]
131
+
132
+ cur_height, cur_width = self.base_size, self.base_size
133
+ while True:
134
+ if cur_height >= max_height and cur_width <= min_width:
135
+ break
136
+
137
+ cur_height = min(cur_height + self.step, max_height)
138
+ cur_width = max(cur_width - self.step, min_width)
139
+ resolutions.append(Resolution(cur_height // self.align * self.align, cur_width // self.align * self.align))
140
+
141
+ cur_height, cur_width = self.base_size, self.base_size
142
+ while True:
143
+ if cur_height <= min_height and cur_width >= max_width:
144
+ break
145
+
146
+ cur_height = max(cur_height - self.step, min_height)
147
+ cur_width = min(cur_width + self.step, max_width)
148
+ resolutions.append(Resolution(cur_height // self.align * self.align, cur_width // self.align * self.align))
149
+
150
+ resolutions = sorted(resolutions, key=lambda x: x.ratio)
151
+
152
+ return resolutions
153
+
154
+ def get_target_size(self, width, height):
155
+ ratio = height / width
156
+ idx = np.argmin(np.abs(self.ratio - ratio))
157
+ reso = self.data[idx]
158
+ return reso.w, reso.h
159
+
160
+ def get_base_size_and_ratio_index(self, width, height):
161
+ ratio = height / width
162
+ idx = np.argmin(np.abs(self.ratio - ratio))
163
+ return self.base_size, idx
164
+
165
+
166
+ class ImageInfo:
167
+ """ Class to store image information for processing and generation. """
168
+
169
+ def __init__(
170
+ self,
171
+ image_type: str = None,
172
+ image_tensor: torch.Tensor = None,
173
+ image_width: int = None,
174
+ image_height: int = None,
175
+ token_width: int = None,
176
+ token_height: int = None,
177
+ image_token_length: int = None,
178
+ base_size: int = None,
179
+ ratio_index: int = None,
180
+ ori_image_width: int = None,
181
+ ori_image_height: int = None,
182
+ **kwargs,
183
+ ):
184
+ self.image_type = image_type
185
+ self.image_tensor = image_tensor
186
+ self.ori_image_width = ori_image_width
187
+ self.image_width = image_width
188
+ self.w = image_width
189
+ self.ori_image_height = ori_image_height
190
+ self.image_height = image_height
191
+ self.h = image_height
192
+ self.token_width = token_width
193
+ self.tk_w = token_width
194
+ self.token_height = token_height
195
+ self.tk_h = token_height
196
+ self.image_token_length = default(
197
+ image_token_length,
198
+ token_width * token_height if token_width is not None and token_height is not None else None
199
+ )
200
+ self.base_size = base_size
201
+ self.ratio_index = ratio_index
202
+
203
+ self.add_timestep_token = kwargs.get("add_timestep_token", True)
204
+ self.add_guidance_token = kwargs.get("add_guidance_token", False)
205
+ self.use_front_boi_token = kwargs.get("use_front_boi_token", True)
206
+ self.add_image_shape_token = kwargs.get("add_image_shape_token", True)
207
+ self.add_timestep_r_token = kwargs.get("add_timestep_r_token", False)
208
+
209
+ def __getitem__(self, key: str) -> Any:
210
+ """Allow dictionary-like access to attributes."""
211
+ if hasattr(self, key):
212
+ return getattr(self, key)
213
+ raise KeyError(f"Key '{key}' not found in ImageInfo")
214
+
215
+ def __setitem__(self, key: str, value: Any) -> None:
216
+ """Allow dictionary-like assignment to attributes."""
217
+ if hasattr(self, key):
218
+ setattr(self, key, value)
219
+ else:
220
+ raise KeyError(f"Key '{key}' not found in ImageInfo")
221
+
222
+ def __contains__(self, key: str) -> bool:
223
+ """Check if the key exists in the ImageInfo object."""
224
+ return hasattr(self, key)
225
+
226
+ def __repr__(self):
227
+ return (f"ImageInfo(image_type={self.image_type}, image_tensor={self.image_tensor}, "
228
+ f"ori_image_width={self.ori_image_width}, ori_image_height={self.ori_image_height}, "
229
+ f"image_width={self.image_width}, image_height={self.image_height}, "
230
+ f"token_width={self.token_width}, token_height={self.token_height}, "
231
+ f"image_token_length={self.image_token_length}, "
232
+ f"base_size={self.base_size}, ratio_index={self.ratio_index}")
233
+
234
+ @property
235
+ def meta_info(self):
236
+ # Used for image sections of tkwrapper.encode_general()
237
+ if self.image_type in ["vae", "gen_image"]:
238
+ return dict(
239
+ token_length=self.image_token_length,
240
+ add_timestep_token=self.add_timestep_token,
241
+ add_guidance_token=self.add_guidance_token,
242
+ add_timestep_r_token=self.add_timestep_r_token,
243
+ use_front_boi_token=self.use_front_boi_token,
244
+ add_image_shape_token=self.add_image_shape_token,
245
+ base_size=self.base_size,
246
+ ratio_idx=self.ratio_index,
247
+ # for rope 2d
248
+ token_height=self.token_height,
249
+ token_width=self.token_width,
250
+ # for bc
251
+ image_height=self.image_height,
252
+ image_width=self.image_width,
253
+ ori_image_width=self.ori_image_width,
254
+ ori_image_height=self.ori_image_height,
255
+ )
256
+ elif self.image_type in ["vit", "siglip2"]:
257
+ return dict(
258
+ token_length=self.image_token_length,
259
+ use_front_boi_token=self.use_front_boi_token,
260
+ add_image_shape_token=self.add_image_shape_token,
261
+ # for rope 2d
262
+ token_height=self.token_height,
263
+ token_width=self.token_width,
264
+ # for bc
265
+ image_height=self.image_height,
266
+ image_width=self.image_width,
267
+ ori_image_width=self.ori_image_width,
268
+ ori_image_height=self.ori_image_height,
269
+ )
270
+ else:
271
+ raise ValueError(f"Unknown image type '{self.image_type}'")
272
+
273
+ @property
274
+ def num_special_tokens(self):
275
+ if self.args is None:
276
+ raise ValueError("meta_info requires `args` attribute to be set.")
277
+ if self.image_type in ["vae", "src_image", "gen_image"]:
278
+ count = (
279
+ 2 + # <boi> + <eoi> or <src_boi> + <src_eoi>
280
+ (1 if self.add_timestep_token else 0) +
281
+ (1 if self.add_guidance_token else 0) +
282
+ (1 if self.add_timestep_r_token else 0) +
283
+ (2 if self.add_image_shape_token else 0)
284
+ )
285
+ else:
286
+ raise ValueError(f"Unknown image_type: {self.image_type}")
287
+ return count
288
+
289
+ def copy(self, copy_image_tensor=True):
290
+ if copy_image_tensor and self.image_tensor is None:
291
+ raise ValueError("image_tensor is None, cannot copy")
292
+ return ImageInfo(
293
+ image_type=self.image_type,
294
+ image_tensor=self.image_tensor.clone() if copy_image_tensor else None,
295
+ image_width=self.image_width,
296
+ image_height=self.image_height,
297
+ ori_image_width=self.ori_image_width,
298
+ ori_image_height=self.ori_image_height,
299
+ token_width=self.token_width,
300
+ token_height=self.token_height,
301
+ image_token_length=self.image_token_length,
302
+ base_size=self.base_size,
303
+ ratio_index=self.ratio_index,
304
+ )
305
+
306
+ def zeros_(self):
307
+ self.image_tensor = torch.zeros_like(self.image_tensor)
308
+
309
+
310
+ class ImageTensor(torch.Tensor):
311
+ # This class is just for type hinting purposes. Attribute `i` should be defined
312
+ # as an instance attribute of the torch.Tensor instance, like: tensor.i = ImageInfo(...)
313
+ i: ImageInfo
314
+ vision_encoder_kwargs: dict
315
+
316
+
317
+ class JointImageInfo(object):
318
+ def __init__(self, vae_image_info: ImageInfo, vision_image_info: ImageInfo, vision_encoder_kwargs: dict = None):
319
+ self.vae_image_info = vae_image_info
320
+ self.vision_image_info = vision_image_info
321
+ self.vision_encoder_kwargs = vision_encoder_kwargs
322
+
323
+ # Define key attributes to align with ImageInfo for uniformity
324
+ self.image_type = "joint_image"
325
+ self.image_token_length = vae_image_info.image_token_length + vision_image_info.image_token_length
326
+
327
+ self.add_timestep_token = vae_image_info.add_timestep_token
328
+ self.use_front_boi_token = vae_image_info.use_front_boi_token
329
+ self.add_image_shape_token = vae_image_info.add_image_shape_token
330
+
331
+ def __repr__(self):
332
+ return f"JointImageInfo(vae_image={self.vae_image_info}, vision_image={self.vision_image_info})"
333
+
334
+ @property
335
+ def meta_info(self):
336
+ # Used for image sections of tkwrapper.encode_general()
337
+ return dict(
338
+ token_length=[self.vae_image_info.image_token_length, self.vision_image_info.image_token_length],
339
+ add_timestep_token=self.add_timestep_token,
340
+ use_front_boi_token=self.use_front_boi_token,
341
+ add_image_shape_token=self.add_image_shape_token,
342
+ base_size=self.vae_image_info.base_size,
343
+ ratio_idx=self.vae_image_info.ratio_index,
344
+ # for rope 2d
345
+ token_height=[self.vae_image_info.token_height, self.vision_image_info.token_height],
346
+ token_width=[self.vae_image_info.token_width, self.vision_image_info.token_width],
347
+ # for bc
348
+ image_height=[self.vae_image_info.image_height, self.vision_image_info.image_height],
349
+ image_width=[self.vae_image_info.image_width, self.vision_image_info.image_width],
350
+ )
351
+
352
+ @property
353
+ def num_special_tokens(self):
354
+ return (
355
+ 2 + # <boi> + <eoi>
356
+ (1 if self.add_timestep_token else 0) +
357
+ (2 if self.add_image_shape_token else 0) +
358
+ 1 # <joint_image_sep>
359
+ )
360
+
361
+ def copy(self, copy_image_tensor=True):
362
+ if copy_image_tensor and (
363
+ self.vae_image_info.image_tensor is None or self.vision_image_info.image_tensor is None):
364
+ raise ValueError("image_tensor is None, cannot copy")
365
+ return JointImageInfo(
366
+ self.vae_image_info.copy(copy_image_tensor),
367
+ self.vision_image_info.copy(copy_image_tensor),
368
+ self.vision_encoder_kwargs,
369
+ )
370
+
371
+ def zeros_(self):
372
+ self.vae_image_info.zeros_()
373
+ self.vision_image_info.zeros_()
374
+
375
+
376
+ class CondImage(object):
377
+ def __init__(self, image_type: str, vae_image: ImageTensor, vit_image: ImageTensor):
378
+ self.image_type = image_type
379
+ self.vae_image = vae_image
380
+ self.vit_image = vit_image
381
+
382
+ if image_type == "vae":
383
+ self.i = vae_image.i
384
+ self.section_type = "cond_vae_image"
385
+
386
+ elif image_type == "vit":
387
+ self.i = vit_image.i
388
+ self.section_type = "cond_vit_image"
389
+
390
+ elif image_type == "vae_vit":
391
+ self.i = JointImageInfo(vae_image.i, vit_image.i)
392
+ self.section_type = "cond_joint_image"
393
+
394
+ else:
395
+ raise ValueError(f"Unknown image_type: {image_type}")
396
+
397
+
398
+ class TokenizerEncodeOutput(BaseOutput):
399
+ tokens: torch.Tensor = None
400
+ text_slices: Optional[list[slice]] = None
401
+ vae_image_slices: Optional[list[slice]] = None
402
+ gen_image_slices: Optional[list[slice]] = None
403
+ vit_image_slices: Optional[list[slice]] = None
404
+ joint_image_slices: Optional[list[slice]] = None
405
+ all_image_slices: Optional[list[slice]] = None
406
+ text_mask: Optional[torch.Tensor] = None
407
+ vae_image_mask: Optional[torch.Tensor] = None
408
+ gen_image_mask: Optional[torch.Tensor] = None
409
+ vit_image_mask: Optional[torch.Tensor] = None
410
+ real_pos: Optional[torch.Tensor] = None
411
+ guidance_scatter_index: Optional[torch.Tensor] = None
412
+ cond_timestep_scatter_index: Optional[torch.Tensor] = None
413
+ gen_timestep_scatter_index: Optional[torch.Tensor] = None
414
+ gen_timestep_r_scatter_index: Optional[torch.Tensor] = None
415
+
416
+
417
+ class SeparatorStyle(IntEnum):
418
+ ADD_COLON_SPACE_SINGLE = auto()
419
+ NONE = auto()
420
+
421
+
422
+ @dataclass
423
+ class Conversation(object):
424
+ name: str
425
+ system_template: str = "{system_message}"
426
+ system_message: str = ""
427
+ roles: Tuple[str, str] = ("User", "Assistant")
428
+ messages: List[List[str]] = ()
429
+ sep_style: SeparatorStyle = SeparatorStyle.ADD_COLON_SPACE_SINGLE
430
+ sep: str = "\n"
431
+ sep2: str = None
432
+ sep_sp: str = None
433
+ stop_token_ids: list[int] = None
434
+
435
+ def get_prompt(self, return_type="str", add_system=True):
436
+ system_prompt = self.system_template.format(system_message=self.system_message)
437
+ prompt_list = []
438
+
439
+ if self.sep_style == SeparatorStyle.ADD_COLON_SPACE_SINGLE:
440
+ seps = [self.sep, self.sep2]
441
+ if add_system:
442
+ prompt_list.append(("System", system_prompt + self.sep_sp if system_prompt else ""))
443
+ for i, (role, message) in enumerate(self.messages):
444
+ if message:
445
+ prompt_list.append((role, f"{role}: {message}{seps[i % 2]}"))
446
+ else:
447
+ prompt_list.append((role, f"{role}: "))
448
+
449
+ elif self.sep_style == SeparatorStyle.NONE:
450
+ seps = [self.sep, self.sep2]
451
+ if add_system:
452
+ prompt_list.append(("System", system_prompt + self.sep_sp if system_prompt else ""))
453
+ for i, (role, message) in enumerate(self.messages):
454
+ if message:
455
+ prompt_list.append((role, f"{role}{message}{seps[i % 2]}"))
456
+ else:
457
+ prompt_list.append((role, f"{role}"))
458
+ else:
459
+ raise NotImplementedError(f"Unsupported sep_style: {self.sep_style}")
460
+
461
+ if return_type == "str":
462
+ prompt = "".join([msg for _, msg in prompt_list])
463
+ else:
464
+ prompt = prompt_list
465
+
466
+ return prompt
467
+
468
+ def get_role_prefix(self, role):
469
+ if self.sep_style == SeparatorStyle.ADD_COLON_SPACE_SINGLE:
470
+ return f"{role}: "
471
+ elif self.sep_style == SeparatorStyle.NONE:
472
+ return f"{role}"
473
+ else:
474
+ raise NotImplementedError(f"Unsupported sep_style: {self.sep_style}")
475
+
476
+ def set_system_message(self, system_message: str):
477
+ """Set the system message."""
478
+ self.system_message = system_message
479
+
480
+ def add_message(self, role: str, message: str):
481
+ """Append a new message."""
482
+ self.messages.append([role, message])
483
+
484
+ def copy(self):
485
+ return deepcopy(self)
486
+
487
+ def empty(self, name=None):
488
+ """Return an empty conversation with the same template."""
489
+ return Conversation(
490
+ name=name or self.name,
491
+ system_template=self.system_template,
492
+ system_message="",
493
+ roles=self.roles,
494
+ messages=[],
495
+ sep_style=self.sep_style,
496
+ sep=self.sep,
497
+ sep2=self.sep2,
498
+ sep_sp=self.sep_sp,
499
+ stop_token_ids=self.stop_token_ids,
500
+ )
501
+
502
+
503
+ # A global registry for all conversation templates
504
+ conv_templates: Dict[str, Conversation] = {}
505
+
506
+
507
+ def register_conv_template(template: Conversation, override: bool = False):
508
+ """Register a new conversation template."""
509
+ if not override:
510
+ assert (
511
+ template.name not in conv_templates
512
+ ), f"{template.name} has been registered."
513
+
514
+ conv_templates[template.name] = template
515
+
516
+
517
+ register_conv_template(
518
+ Conversation(
519
+ name="hunyuan-image-3",
520
+ system_template="{system_message}",
521
+ system_message="",
522
+ roles=("User", "Assistant"),
523
+ messages=[],
524
+ sep_style=SeparatorStyle.ADD_COLON_SPACE_SINGLE,
525
+ sep="\n\n",
526
+ sep2="<|endoftext|>",
527
+ sep_sp="\n\n",
528
+ stop_token_ids=[127957],
529
+ )
530
+ )
531
+
532
+
533
+ def get_conversation_template(name: str) -> Conversation:
534
+ """Get a conversation template."""
535
+ return conv_templates[name].copy()
536
+
537
+
538
+ class HunyuanImage3TokenizerFast(PreTrainedTokenizerFast):
539
+ """
540
+ Tokenizer for Hunyuan Multimodal models, utilizing a fast tokenizer backend.
541
+ This tokenizer extends the PreTrainedTokenizerFast from Hugging Face Transformers
542
+ for multimodal tasks.
543
+ """
544
+ def __init__(self, *args, **kwargs):
545
+ super().__init__(*args, **kwargs)
546
+ # A convenience mapping for special tokens
547
+ special_tokens = self.special_tokens_map.get('additional_special_tokens', [])
548
+ if len(special_tokens) > 0:
549
+ special_token_ids = self.convert_tokens_to_ids(special_tokens)
550
+ self._sp_dict = dict(zip(special_tokens, special_token_ids))
551
+ else:
552
+ self._sp_dict = dict()
553
+
554
+ # Assign commonly used special tokens to attributes for easy access.
555
+ self.setup_special_tokens()
556
+
557
+ # Define decorator section
558
+ self.conversation_template = kwargs.get("conversation_template", "hunyuan-image-3")
559
+ self.conversation = get_conversation_template(self.conversation_template)
560
+ self.sequence_template = kwargs.get("sequence_template", "instruct")
561
+ self.decorator_section = DecoratorSections(
562
+ self,
563
+ conv=self.conversation,
564
+ sequence_template=self.sequence_template,
565
+ )
566
+
567
+ def setup_special_tokens(self):
568
+ # Define names for commonly used special tokens
569
+ predefined_name_mapping = dict(
570
+ boi="<boi>",
571
+ eoi="<eoi>",
572
+ boa="<boa>",
573
+ eoa="<eoa>",
574
+ bov="<bov>",
575
+ eov="<eov>",
576
+ img="<img>",
577
+ audio="<audio>",
578
+ video="<video>",
579
+ cfg="<cfg>",
580
+ timestep="<timestep>",
581
+ timestep_r="<timestep_r>",
582
+ guidance="<guidance>",
583
+ joint_img_sep="<joint_img_sep>",
584
+ answer="<answer>",
585
+ end_of_answer="</answer>",
586
+ # for extended cot types
587
+ think="<think>",
588
+ end_of_think="</think>",
589
+ recaption="<recaption>",
590
+ end_of_recaption="</recaption>",
591
+ # for grounding
592
+ ref="<ref>",
593
+ end_of_ref="</ref>",
594
+ quad="<quad>",
595
+ end_of_quad="</quad>",
596
+ )
597
+ for name, mapping in predefined_name_mapping.items():
598
+ setattr(self, f"{name}_token", mapping)
599
+ setattr(self, f"{name}_token_id", self.convert_tokens_to_ids(mapping))
600
+
601
+ if len(self._sp_dict) > 0:
602
+ name_mapping = dict()
603
+ for name, token in name_mapping.items():
604
+ setattr(self, name, token)
605
+ setattr(self, f"{name}_id", self._sp_dict[token])
606
+
607
+
608
+ self.start_ratio_token_id = self.convert_tokens_to_ids("<img_ratio_0>")
609
+ self.end_ratio_token_id = self.convert_tokens_to_ids("<img_ratio_32>")
610
+ self.ratio_token_other_slices = [
611
+ (self.convert_tokens_to_ids("<img_ratio_33>"), self.convert_tokens_to_ids("<img_ratio_36>") + 1)
612
+ ]
613
+
614
+ @property
615
+ def max_token_id(self):
616
+ return self.vocab_size
617
+
618
+ def size_token(self, size: int):
619
+ return f"<img_size_{size}>"
620
+
621
+ def size_token_id(self, size: int):
622
+ return self.convert_tokens_to_ids(f"<img_size_{size}>")
623
+
624
+ def ratio_token(self, ratio_idx: int):
625
+ return f"<img_ratio_{ratio_idx}>"
626
+
627
+ def ratio_token_id(self, ratio_idx: int):
628
+ return self.convert_tokens_to_ids(f"<img_ratio_{ratio_idx}>")
629
+
630
+ def get_all_ratio_token_ids(self):
631
+ return [self.ratio_token_id(i) for i in range(37)]
632
+
633
+ def relation_token(self, relation_idx: int, end: bool = False):
634
+ if end:
635
+ return f"</relation_{relation_idx}>"
636
+ return f"<relation_{relation_idx}>"
637
+
638
+ def relation_token_id(self, relation_idx: int, end: bool = False):
639
+ if end:
640
+ return self.convert_tokens_to_ids(f"</relation_{relation_idx}>")
641
+ return self.convert_tokens_to_ids(f"<relation_{relation_idx}>")
642
+
643
+ def x_token(self, pos: int):
644
+ return f"<pos_x_{pos}>"
645
+
646
+ def x_token_id(self, pos: int):
647
+ return self.convert_tokens_to_ids(f"<pos_x_{pos}>")
648
+
649
+ def y_token(self, pos: int):
650
+ return f"<pos_y_{pos}>"
651
+
652
+ def y_token_id(self, pos: int):
653
+ return self.convert_tokens_to_ids(f"<pos_y_{pos}>")
654
+
655
+ def z_token(self, pos: int):
656
+ return f"<pos_z_{pos}>"
657
+
658
+ def z_token_id(self, pos: int):
659
+ return self.convert_tokens_to_ids(f"<pos_z_{pos}>")
660
+
661
+ def get_img_token(self):
662
+ if hasattr(self, "img_token"):
663
+ return self.img_token
664
+ else:
665
+ return self.convert_ids_to_tokens(len(self) - 1)
666
+
667
+ def encode_text(
668
+ self,
669
+ *texts,
670
+ uncond_enabled: Optional[bool | list[bool]] = None,
671
+ uncond_p: Optional[float] = None,
672
+ max_length: Optional[int] = None,
673
+ pad: Optional[str] = None,
674
+ return_lengths: bool = False,
675
+ ):
676
+ """
677
+ Encode text and image for AR-like model training of the text-to-image/instruction tuning tasks.
678
+ Support encode multiple texts at once. Each text can be separately conditioned or unconditioned
679
+ based on the uncond_flags and a uniform uncond_p.
680
+ **<bos> token is always prepended to the text tokens.**
681
+
682
+ Parameters
683
+ ----------
684
+ texts: str or List[str]
685
+ List of texts to be encoded.
686
+ uncond_enabled: bool or List[bool]
687
+ List of flags to indicate whether the text should be unconditioned.
688
+ If False, the text will never be unconditioned.
689
+ If True, the text will be unconditioned with uncond_p.
690
+ uncond_p: float
691
+ Probability to the unconditional text. Only works when uncond_enabled is True.
692
+ max_length: int
693
+ Maximum length of the encoded text.
694
+ pad: Optional[str]
695
+ Padding method. Can be 'left' or 'right'.
696
+ return_lengths: bool
697
+ Whether to return the length of each encoded text.
698
+ """
699
+ if pad is not None:
700
+ assert max_length is not None, "max_length should be provided when pad is not None."
701
+
702
+ if uncond_enabled is None:
703
+ uncond_enabled = [True] * len(texts)
704
+ elif isinstance(uncond_enabled, bool):
705
+ uncond_enabled = [uncond_enabled] * len(texts)
706
+ if len(uncond_enabled) != len(texts):
707
+ print(uncond_enabled, texts)
708
+ assert len(uncond_enabled) == len(texts), (
709
+ f"Length of uncond_flags should be equal to the number of texts, "
710
+ f"but got {len(uncond_enabled)} and {len(texts)}."
711
+ )
712
+
713
+ # Prepare text/uncond tokens
714
+ # TODO: If len(texts) > 1, such as instruction + prompt in inpainting, we need to determine how to do uncond.
715
+ # Now all texts will be cond or uncond at the same time.
716
+ do_uncond_drop = (uncond_p is not None) and (random.random() < uncond_p)
717
+ text_tokens, lengths = [], []
718
+ cum_length = 0
719
+ for text, uncond_flag in zip(texts, uncond_enabled):
720
+ # If reach the max_length and there still have unencoded texts, give a warning message and break the loop.
721
+ if max_length is not None and cum_length >= max_length:
722
+ self.logger.warning(
723
+ f"Text length exceeds the max_length({max_length}). The remaining texts will be ignored: "
724
+ f"{text[:80]}..."
725
+ )
726
+ break
727
+ # Set add_special_tokens=False to avoid adding <bos> token in some LLMs.
728
+ if isinstance(text, str):
729
+ text_token = self.encode(text, add_special_tokens=False)
730
+ else:
731
+ text_token = text
732
+ if uncond_flag and do_uncond_drop:
733
+ text_token = [self.cfg_token_id] * len(text_token)
734
+ # Cutoff the text by max_length if necessary
735
+ if max_length is not None and (cum_length + len(text_token)) > max_length:
736
+ text_token = text_token[:max_length - cum_length]
737
+ text_tokens.extend(text_token)
738
+ lengths.append(len(text_token))
739
+ cum_length += len(text_token)
740
+
741
+ # Prepend/Append <pad> tokens if applicable
742
+ if pad is not None and (pad_length := max_length - len(text_tokens)) > 0:
743
+ if pad == 'left':
744
+ text_tokens = [self.pad_token_id] * pad_length + text_tokens
745
+ elif pad == 'right':
746
+ text_tokens = text_tokens + [self.pad_token_id] * pad_length
747
+ else:
748
+ raise ValueError(f"Unsupported padding method: {pad}.")
749
+
750
+ if return_lengths:
751
+ return text_tokens, lengths
752
+ return text_tokens
753
+
754
+ @staticmethod
755
+ def _check_key_number_matched(keys, data):
756
+ # Assert keys and token_source are matched
757
+ assert set(keys) == set(data.keys()), (
758
+ f"Keys in the template and token source should be matched, but got {keys} and {list(data.keys())}."
759
+ )
760
+ key_counts = {k: 0 for k in keys}
761
+ for key in keys:
762
+ key_counts[key] += 1
763
+ for key, count in key_counts.items():
764
+ assert len(data[key]) == count, (
765
+ f"Number of `{key}` in the token source should be matched with the template, but got "
766
+ f"{data[key]}({len(data[key])}) and {count}."
767
+ )
768
+
769
+ def _add_image_meta_info_token(
770
+ self,
771
+ token_seq,
772
+ token_count,
773
+ extra_token_pos,
774
+ add_timestep_token: bool = False,
775
+ add_timestep_r_token: bool = False,
776
+ add_image_shape_token: bool = False,
777
+ add_guidance_token: bool = False,
778
+ base_size=None,
779
+ ratio_idx=None,
780
+ image_type=None,
781
+ ):
782
+ if add_image_shape_token:
783
+ token_seq.extend([self.size_token_id(base_size), self.ratio_token_id(ratio_idx)])
784
+ token_count += 2
785
+ if add_timestep_token:
786
+ token_seq.extend([self.timestep_token_id])
787
+ extra_token_pos['timestep'].append(token_count)
788
+ if image_type is not None:
789
+ if image_type == "gen_image":
790
+ extra_token_pos['gen_timestep'].append(token_count)
791
+ elif image_type in ["cond_joint_image", "cond_vae_image"]:
792
+ extra_token_pos['cond_timestep'].append(token_count)
793
+ else:
794
+ raise ValueError(f"Unsupported image type: {image_type}.")
795
+ token_count += 1
796
+ if add_guidance_token:
797
+ token_seq.extend([self.guidance_token_id])
798
+ extra_token_pos['guidance'].append(token_count)
799
+ token_count += 1
800
+ if add_timestep_r_token:
801
+ token_seq.extend([self.timestep_r_token_id])
802
+ extra_token_pos['gen_timestep_r'].append(token_count)
803
+ token_count += 1
804
+ return token_count
805
+
806
+ def encode_sequence(
807
+ self,
808
+ template: str,
809
+ token_source: dict[str, list[list[int] | dict[str, Any]]],
810
+ total_length=None,
811
+ add_timestep_token=False,
812
+ add_timestep_r_token=False,
813
+ add_guidance_token=False,
814
+ add_eos=True,
815
+ add_pad=True,
816
+ add_bos=True,
817
+ drop_last: str | bool = 'auto',
818
+ add_image_shape_token=False,
819
+ ):
820
+ """
821
+ Encode a sequence of tokens based on the provided token source.
822
+
823
+ Args:
824
+ template: str
825
+ Template of the sequence. E.g., "text-image" means the sequence is composed of text and an image.
826
+ "text-image-image" means the sequence is composed of text and two images.
827
+ token_source (dict[str, list[list[int] | dict[str, Any]]]): Token source for each key in the template, in order.
828
+ - text: List[List[int]]. Each List[int] is a sequence of tokenized text tokens.
829
+ - gen_image: ImageInfo
830
+ - cond_joint_image: JointImageInfo
831
+ - cond_vae_image: ImageInfo
832
+ - cond_vit_image: ImageInfo
833
+ total_length: int
834
+ Total length of the encoded sequence, include padding tokens.
835
+ add_timestep_token: bool
836
+ Whether to add timestep token before the image tokens.
837
+ (Right after the <iw><ih> token or <img_ratio_*><img_size_*> tokens)
838
+ add_guidance_token: bool
839
+ Whether to add guidance token before the image tokens.
840
+ add_eos: bool or 'auto'
841
+ Whether to add eos token at the end of the sequence. If True, always add eos token. If 'auto',
842
+ add eos token only when the total_length is not reached and the last token is not <eos>.
843
+ use_front_boi_token: bool:
844
+ Whether to put the <boi> token at the front of iw, ih and timestep tokens.
845
+ add_pad: bool or 'auto'
846
+ Whether to add padding tokens to the sequence. If True and total_length is not reached, add padding tokens.
847
+ add_bos: bool
848
+ Whether to add bos token at the beginning of the sequence.
849
+ drop_last: bool or 'auto'
850
+ - If auto, drop last tokens exceeding the total_length if the total_length is provided. If cut point is
851
+ in the middle of the image tokens, an error will raised.
852
+ - If True, drop last tokens exceeding the total_length. If cut point is in the middle of the image tokens,
853
+ all the successive image tokens will be dropped.
854
+ - If False, keep the last tokens exceeding the total_length, even if the total_length is reached.
855
+ add_image_shape_token: bool
856
+ Whether to add image shape token before the image tokens. (Right before the <timestep> token)
857
+
858
+ Returns
859
+ -------
860
+ token_seq: list
861
+ Encoded token sequence.
862
+ extra_token_pos: dict
863
+ Positions of extra tokens. E.g., iw_ih, timestep.
864
+ """
865
+ if drop_last is True and total_length is None:
866
+ raise ValueError("total_length should be provided when drop_last is True.")
867
+
868
+ keys = template.split('-')
869
+ modal_length = len(keys)
870
+ index_indicator = {k: 0 for k in token_source}
871
+ for k, v in token_source.items():
872
+ assert isinstance(v, (list, tuple)), (
873
+ f"Value of `{k}` in the token source should be a list or tuple, but got {type(v)}."
874
+ )
875
+ self._check_key_number_matched(keys, token_source)
876
+
877
+ token_seq = []
878
+ token_count = 0
879
+ extra_token_pos = defaultdict(list)
880
+ if add_bos:
881
+ token_seq.append(self.bos_token_id)
882
+ token_count += 1
883
+ # If drop_last is True, we check the token_count on the fly and exit the loop if the total_length is reached.
884
+ # This check is only applied to the block tokens. Block tokens mean the tokens that are unsplittable, like
885
+ # image tokens, face tokens. Text tokens are splittable, so we don't need to check the token_count for text.
886
+ # If the loop is broken by drop_last, we don't add the eos token at the end because the sequence is not complete.
887
+ drop_last_break = False
888
+ for i, key in enumerate(keys):
889
+ source = token_source[key][index_indicator[key]]
890
+ if key == "text":
891
+ token_seq.extend(source) # text token sequence
892
+ extra_token_pos["<text>_start"].append(token_count)
893
+ token_count += len(source)
894
+ extra_token_pos["<text>_end"].append(token_count - 1)
895
+
896
+ elif key == "gen_image":
897
+ # 2 means boi and eoi
898
+ extra_count = \
899
+ 2 \
900
+ + (1 if source.get('timestep', add_timestep_token) else 0) \
901
+ + (1 if source.get('timestep_r', add_timestep_r_token) else 0) \
902
+ + (1 if source.get('guidance', add_guidance_token) else 0) \
903
+ + (2 if source.get('image_shape', add_image_shape_token) else 0)
904
+ if drop_last is True and token_count + extra_count + source['length'] > total_length:
905
+ drop_last_break = True
906
+ break
907
+ token_seq.append(self.boi_token_id) # Use patched boi for Janus, otherwise using default <boi>
908
+ extra_token_pos["boi"].append(token_count)
909
+ token_count += 1
910
+ token_count = self._add_image_meta_info_token(
911
+ token_seq=token_seq,
912
+ token_count=token_count,
913
+ extra_token_pos=extra_token_pos,
914
+ add_timestep_token=source.get('timestep', add_timestep_token),
915
+ add_timestep_r_token=source.get('timestep_r', add_timestep_r_token),
916
+ add_guidance_token=source.get('guidance', add_guidance_token),
917
+ add_image_shape_token=source.get('image_shape', add_image_shape_token),
918
+ base_size=source.get('base_size'),
919
+ ratio_idx=source.get('ratio_idx'),
920
+ image_type=key,
921
+ )
922
+ token_seq.extend(
923
+ [self.img_token_id] * source['length'] + # token number
924
+ [self.eoi_token_id]
925
+ )
926
+ extra_token_pos["<img>_start"].append(token_count)
927
+ extra_token_pos["<all_img>_start"].append(token_count)
928
+ token_count += source['length']
929
+ extra_token_pos["<img>_end"].append(token_count - 1)
930
+ extra_token_pos["<all_img>_end"].append(token_count - 1)
931
+ extra_token_pos["eoi"].append(token_count)
932
+ token_count += 1 # <eoi>
933
+
934
+ elif key == "cond_joint_image":
935
+ assert isinstance(source['length'], list) and len(
936
+ source['length']) == 2, "cond_joint_image length should be a list of two integers"
937
+ # 2 + 1 means: boi, eoi, joint_img_sep
938
+ extra_count = \
939
+ 2 + 1 \
940
+ + (1 if source.get('timestep', add_timestep_token) else 0) \
941
+ + (2 if source.get('image_shape', add_image_shape_token) else 0)
942
+ if drop_last is True and token_count + extra_count + sum(source['length']) > total_length:
943
+ drop_last_break = True
944
+ break
945
+ token_seq.append(self.boi_token_id) # Use patched boi for Janus, otherwise using default <boi>
946
+ extra_token_pos["boi"].append(token_count)
947
+ token_count += 1
948
+ token_count = self._add_image_meta_info_token(
949
+ token_seq=token_seq,
950
+ token_count=token_count,
951
+ extra_token_pos=extra_token_pos,
952
+ add_timestep_token=source.get('timestep', add_timestep_token),
953
+ add_image_shape_token=source.get('image_shape', add_image_shape_token),
954
+ base_size=source.get('base_size'),
955
+ ratio_idx=source.get('ratio_idx'),
956
+ image_type=key,
957
+ )
958
+ token_seq.extend(
959
+ [self.img_token_id] * source['length'][0]
960
+ )
961
+ extra_token_pos["<vae_img>_start"].append(token_count)
962
+ extra_token_pos["<joint_img>_start"].append(token_count)
963
+ extra_token_pos["<all_img>_start"].append(token_count)
964
+ token_count += source['length'][0]
965
+ extra_token_pos["<vae_img>_end"].append(token_count - 1)
966
+ extra_token_pos["<all_img>_end"].append(token_count - 1)
967
+
968
+ token_seq.extend([self.joint_img_sep_token_id])
969
+ extra_token_pos["joint_img_sep"].append(token_count)
970
+ token_count += 1
971
+
972
+ token_seq.extend(
973
+ [self.img_token_id] * source['length'][1]
974
+ )
975
+ extra_token_pos["<vit_img>_start"].append(token_count)
976
+ extra_token_pos["<all_img>_start"].append(token_count)
977
+ token_count += source['length'][1]
978
+ extra_token_pos["<vit_img>_end"].append(token_count - 1)
979
+ extra_token_pos["<joint_img>_end"].append(token_count - 1)
980
+ extra_token_pos["<all_img>_end"].append(token_count - 1)
981
+
982
+ token_seq.extend(
983
+ [self.eoi_token_id]
984
+ )
985
+ extra_token_pos["eoi"].append(token_count)
986
+ token_count += 1 # <eoi>
987
+
988
+ elif key == "cond_vae_image":
989
+ # 2 means: boi, eoi
990
+ extra_count = \
991
+ 2 \
992
+ + (1 if source.get('timestep', add_timestep_token) else 0) \
993
+ + (2 if source.get('image_shape', add_image_shape_token) else 0)
994
+ if drop_last is True and token_count + extra_count + source['length'] > total_length:
995
+ drop_last_break = True
996
+ break
997
+ token_seq.append(self.boi_token_id) # Use patched boi for Janus, otherwise using default <boi>
998
+ extra_token_pos["boi"].append(token_count)
999
+ token_count += 1
1000
+ token_count = self._add_image_meta_info_token(
1001
+ token_seq=token_seq,
1002
+ token_count=token_count,
1003
+ extra_token_pos=extra_token_pos,
1004
+ add_timestep_token=source.get('timestep', add_timestep_token),
1005
+ add_image_shape_token=source.get('image_shape', add_image_shape_token),
1006
+ base_size=source.get('base_size'),
1007
+ ratio_idx=source.get('ratio_idx'),
1008
+ image_type=key,
1009
+ )
1010
+ token_seq.extend(
1011
+ [self.img_token_id] * source['length']
1012
+ )
1013
+ extra_token_pos["<vae_img>_start"].append(token_count)
1014
+ extra_token_pos["<all_img>_start"].append(token_count)
1015
+ token_count += source['length']
1016
+ extra_token_pos["<vae_img>_end"].append(token_count - 1)
1017
+ extra_token_pos["<all_img>_end"].append(token_count - 1)
1018
+ token_seq.extend(
1019
+ [self.eoi_token_id]
1020
+ )
1021
+ extra_token_pos["eoi"].append(token_count)
1022
+ token_count += 1 # <eoi>
1023
+
1024
+ elif key == "cond_vit_image":
1025
+ # 2 means: boi, eoi
1026
+ extra_count = 2
1027
+ if drop_last is True and token_count + extra_count + source['length'] > total_length:
1028
+ drop_last_break = True
1029
+ break
1030
+
1031
+ if hasattr(self, "boi_token_id"):
1032
+ token_seq.append(self.boi_token_id)
1033
+ extra_token_pos["boi"].append(token_count)
1034
+ token_count += 1
1035
+
1036
+ if hasattr(self, "img_token_id"):
1037
+ token_seq.extend([self.img_token_id] * source['length'])
1038
+ else:
1039
+ # If not img_token_id defined, but we still need to fill the image tokens,
1040
+ # we use the last token id representing the image token.
1041
+ token_seq.extend([len(self) - 1] * source['length'])
1042
+ extra_token_pos["<vit_img>_start"].append(token_count)
1043
+ extra_token_pos["<all_img>_start"].append(token_count)
1044
+ token_count += source['length']
1045
+ extra_token_pos["<vit_img>_end"].append(token_count - 1)
1046
+ extra_token_pos["<all_img>_end"].append(token_count - 1)
1047
+
1048
+ if hasattr(self, "eoi_token_id"):
1049
+ token_seq.append(self.eoi_token_id)
1050
+ extra_token_pos["eoi"].append(token_count)
1051
+ token_count += 1
1052
+
1053
+ else:
1054
+ raise ValueError(f"Not supported key: {key}")
1055
+ index_indicator[key] += 1
1056
+
1057
+ if add_eos is True and not drop_last_break:
1058
+ # Typically used for t2i task.
1059
+ token_seq.append(self.eos_token_id)
1060
+ extra_token_pos["eos"].append(token_count)
1061
+ token_count += 1
1062
+ elif add_eos == 'auto' and not drop_last_break:
1063
+ # Typically used for lm and mmu task.
1064
+ if token_seq[-1] != self.eos_token_id and (total_length is None or token_count < total_length):
1065
+ token_seq.append(self.eos_token_id)
1066
+ extra_token_pos["eos"].append(token_count)
1067
+ token_count += 1
1068
+
1069
+ if total_length:
1070
+ # Check token count and clip sequence if necessary
1071
+ if token_count > total_length and drop_last:
1072
+ # Assert clip position is not in the middle of the block-wise tokens (gen_image,
1073
+ # src_image, und_image, face)
1074
+ for start_key, end_key in [
1075
+ ("<img>_start", "<img>_end"), ("<vae_img>_start", "<vae_img>_end"),
1076
+ ("<vit_img>_start", "<vit_img>_end"), ("<joint>_start", "<joint>_end")
1077
+ ]:
1078
+ if start_key in extra_token_pos and end_key in extra_token_pos:
1079
+ assert all(
1080
+ (start > total_length or end + 1 < total_length)
1081
+ for start, end in zip(extra_token_pos[start_key], extra_token_pos[end_key])
1082
+ ), ("Clip position should not be in the middle of the image tokens.\n"
1083
+ f"Below is the text:\n{self._shorten_text(self.decode(token_seq))}")
1084
+ token_seq = token_seq[:total_length]
1085
+
1086
+ # Pad the sequence if necessary
1087
+ pad_num = max(0, total_length - len(token_seq))
1088
+ if add_pad and pad_num:
1089
+ token_seq.extend([self.pad_token_id] * pad_num)
1090
+ extra_token_pos["first_pad"].append(token_count)
1091
+
1092
+ return token_seq, extra_token_pos
1093
+
1094
+ @staticmethod
1095
+ def parse_extra_token_pos(extra_token_pos, prefix, tokens, rng=None):
1096
+ if rng is None:
1097
+ rng = slice(None)
1098
+ image_slices = [
1099
+ slice(start, end + 1)
1100
+ for start, end in zip(extra_token_pos[f'<{prefix}>_start'][rng], extra_token_pos[f'<{prefix}>_end'][rng])
1101
+ ] if f'<{prefix}>_start' in extra_token_pos and f'<{prefix}>_end' in extra_token_pos else []
1102
+ if image_slices:
1103
+ image_mask = torch.zeros_like(tokens, dtype=torch.bool)
1104
+ for image_slice in image_slices:
1105
+ image_mask[image_slice] = True
1106
+ else:
1107
+ image_mask = None
1108
+ return image_slices, image_mask
1109
+
1110
+ def encode_general(
1111
+ self,
1112
+ sections: Optional[list[dict[str, Any]]] = None,
1113
+ max_token_length: Optional[int] = None,
1114
+ add_eos: bool | str = 'auto',
1115
+ use_text_mask: bool = True,
1116
+ add_pad: bool | str = 'auto',
1117
+ add_bos: bool = True,
1118
+ drop_last: bool | str = 'auto',
1119
+ ):
1120
+ if sections is None:
1121
+ raise ValueError("sections must be provided.")
1122
+ template = '-'.join([section['type'] for section in sections])
1123
+
1124
+ sections = deepcopy(sections)
1125
+ token_source = defaultdict(list)
1126
+ text_mask_specs = []
1127
+ for section in sections:
1128
+ if section['type'] == 'text':
1129
+ text = self.encode_text(
1130
+ section['text'] if 'text' in section else section['tokens'],
1131
+ uncond_enabled=section.get('uncond_enabled'),
1132
+ uncond_p=section.get('uncond_p'),
1133
+ max_length=section.get('max_length'),
1134
+ )
1135
+ token_source['text'].append(text)
1136
+ text_mask_specs.append(dict(
1137
+ ignore=section.get('ignore', False),
1138
+ start_offset=section.get('start_offset', 0),
1139
+ end_offset=section.get('end_offset', 0),
1140
+ ))
1141
+ elif section['type'] == 'gen_image':
1142
+ token_source['gen_image'].append(dict(
1143
+ length=section['token_length'],
1144
+ timestep=section.get('add_timestep_token', False),
1145
+ timestep_r=section.get('add_timestep_r_token', False),
1146
+ guidance=section.get('add_guidance_token', False),
1147
+ front_boi=section.get('use_front_boi_token', False),
1148
+ image_shape=section.get('add_image_shape_token', False),
1149
+ base_size=section.get('base_size'),
1150
+ ratio_idx=section.get('ratio_idx'),
1151
+ ))
1152
+ elif section['type'] == 'cond_joint_image':
1153
+ token_source['cond_joint_image'].append(dict(
1154
+ length=section['token_length'],
1155
+ timestep=section.get('add_timestep_token', False),
1156
+ front_boi=section.get('use_front_boi_token', False),
1157
+ image_shape=section.get('add_image_shape_token', False),
1158
+ base_size=section.get('base_size'),
1159
+ ratio_idx=section.get('ratio_idx'),
1160
+ ))
1161
+ elif section['type'] == 'cond_vae_image':
1162
+ token_source['cond_vae_image'].append(dict(
1163
+ length=section['token_length'],
1164
+ timestep=section.get('add_timestep_token', False),
1165
+ front_boi=section.get('use_front_boi_token', False),
1166
+ image_shape=section.get('add_image_shape_token', False),
1167
+ base_size=section.get('base_size'),
1168
+ ratio_idx=section.get('ratio_idx'),
1169
+ ))
1170
+ elif section['type'] == 'cond_vit_image':
1171
+ token_source['cond_vit_image'].append(dict(
1172
+ length=section['token_length'],
1173
+ timestep=section.get('add_timestep_token', False),
1174
+ front_boi=section.get('use_front_boi_token', False),
1175
+ image_shape=section.get('add_image_shape_token', False),
1176
+ base_size=section.get('base_size'),
1177
+ ratio_idx=section.get('ratio_idx'),
1178
+ ))
1179
+ else:
1180
+ raise ValueError(f"Invalid section type: {section['type']}")
1181
+
1182
+ # Combine text and image tokens
1183
+ full_token_seq, extra_token_pos = self.encode_sequence(
1184
+ template=template,
1185
+ token_source=dict(token_source),
1186
+ total_length=max_token_length,
1187
+ add_eos=add_eos,
1188
+ add_pad=add_pad,
1189
+ add_bos=add_bos,
1190
+ drop_last=drop_last,
1191
+ )
1192
+ full_seq_token_tensor = torch.tensor(full_token_seq, dtype=torch.long)
1193
+
1194
+ guidance_scatter_index = torch.tensor(extra_token_pos['guidance'], dtype=torch.long) \
1195
+ if 'guidance' in extra_token_pos else None
1196
+ cond_timestep_scatter_index = torch.tensor(extra_token_pos['cond_timestep'], dtype=torch.long) \
1197
+ if 'cond_timestep' in extra_token_pos else None
1198
+ gen_timestep_scatter_index = torch.tensor(extra_token_pos['gen_timestep'], dtype=torch.long) \
1199
+ if 'gen_timestep' in extra_token_pos else None
1200
+ gen_timestep_r_scatter_index = torch.tensor(extra_token_pos['gen_timestep_r'], dtype=torch.long) \
1201
+ if 'gen_timestep_r' in extra_token_pos else None
1202
+ gen_image_slices, gen_image_mask = self.parse_extra_token_pos(
1203
+ extra_token_pos, 'img', full_seq_token_tensor)
1204
+ vae_image_slices, vae_image_mask = self.parse_extra_token_pos(
1205
+ extra_token_pos, 'vae_img', full_seq_token_tensor)
1206
+ vit_image_slices, vit_image_mask = self.parse_extra_token_pos(
1207
+ extra_token_pos, 'vit_img', full_seq_token_tensor)
1208
+ joint_image_slices, _ = self.parse_extra_token_pos(
1209
+ extra_token_pos, 'joint_img', full_seq_token_tensor)
1210
+ # All image slices (src_image, gen_image, und_image)
1211
+ all_image_slices = [
1212
+ slice(start, end + 1)
1213
+ for start, end in zip(extra_token_pos['<all_img>_start'], extra_token_pos['<all_img>_end'])
1214
+ ] if '<all_img>_start' in extra_token_pos and '<all_img>_end' in extra_token_pos else []
1215
+
1216
+ # Text mask
1217
+ text_slices = [
1218
+ slice(start, end + 1)
1219
+ for start, end in zip(extra_token_pos['<text>_start'], extra_token_pos['<text>_end'])
1220
+ ] if '<text>_start' in extra_token_pos and '<text>_end' in extra_token_pos else []
1221
+ assert len(text_slices) <= len(text_mask_specs), \
1222
+ (f"Number of text slices ({len(text_slices)}) should be less than or equal to "
1223
+ f"number of text mask specs ({len(text_mask_specs)})")
1224
+ if use_text_mask:
1225
+ text_mask = torch.zeros_like(full_seq_token_tensor, dtype=torch.float32)
1226
+ for text_slice, mask_spec in zip(text_slices, text_mask_specs):
1227
+ if not mask_spec['ignore']:
1228
+ real_slice = slice(
1229
+ text_slice.start + mask_spec['start_offset'],
1230
+ text_slice.stop + mask_spec['end_offset']
1231
+ )
1232
+ text_mask[real_slice] = 1.0
1233
+ else:
1234
+ text_mask = None
1235
+
1236
+ # real_pos is the first position of the <pad> token
1237
+ real_pos = torch.tensor(extra_token_pos.get('first_pad', [full_seq_token_tensor.shape[0]]), dtype=torch.long)
1238
+
1239
+ return TokenizerEncodeOutput(
1240
+ tokens=full_seq_token_tensor,
1241
+ text_slices=text_slices,
1242
+ gen_image_slices=gen_image_slices,
1243
+ vae_image_slices=vae_image_slices,
1244
+ vit_image_slices=vit_image_slices,
1245
+ joint_image_slices=joint_image_slices,
1246
+ all_image_slices=all_image_slices,
1247
+ text_mask=text_mask,
1248
+ gen_image_mask=gen_image_mask,
1249
+ vae_image_mask=vae_image_mask,
1250
+ vit_image_mask=vit_image_mask,
1251
+ real_pos=real_pos,
1252
+ guidance_scatter_index=guidance_scatter_index,
1253
+ cond_timestep_scatter_index=cond_timestep_scatter_index,
1254
+ gen_timestep_scatter_index=gen_timestep_scatter_index,
1255
+ gen_timestep_r_scatter_index=gen_timestep_r_scatter_index,
1256
+ )
1257
+
1258
+ def get_cot_sections(self, cot_text, uncond_kwargs, cot_max_length=None, drop_think=False):
1259
+ if not cot_text: # None or empty
1260
+ return []
1261
+ deco = self.decorator_section
1262
+
1263
+ if self.think_token in cot_text and self.end_of_think_token in cot_text:
1264
+ before_think_sec = cot_text.split(self.think_token)[0]
1265
+ after_think_sec = cot_text.split(self.end_of_think_token)[1]
1266
+ think_sec = cot_text.split(self.think_token)[1].split(self.end_of_think_token)[0]
1267
+ return self.get_cot_sections(before_think_sec, uncond_kwargs, drop_think=drop_think) + \
1268
+ (deco.think(dict(type="text", text=think_sec, max_length=cot_max_length, **uncond_kwargs))
1269
+ if not drop_think else []) + \
1270
+ self.get_cot_sections(after_think_sec, uncond_kwargs, drop_think=drop_think)
1271
+
1272
+ if self.recaption_token in cot_text and self.end_of_recaption_token in cot_text:
1273
+ before_recaption_sec = cot_text.split(self.recaption_token)[0]
1274
+ after_recaption_sec = cot_text.split(self.end_of_recaption_token)[1]
1275
+ recaption_sec = cot_text.split(self.recaption_token)[1].split(self.end_of_recaption_token)[0]
1276
+ return self.get_cot_sections(before_recaption_sec, uncond_kwargs, drop_think=drop_think) + \
1277
+ (deco.recaption(dict(type="text", text=recaption_sec, max_length=cot_max_length, **uncond_kwargs))) + \
1278
+ self.get_cot_sections(after_recaption_sec, uncond_kwargs, drop_think=drop_think)
1279
+
1280
+ return [
1281
+ dict(type="text", text=cot_text, **uncond_kwargs),
1282
+ ]
1283
+
1284
+ def apply_general_template(
1285
+ self,
1286
+ message_list,
1287
+ max_length=None,
1288
+ add_assistant_prefix=False,
1289
+ answer="auto",
1290
+ bot_task="auto",
1291
+ sequence_template=None,
1292
+ uncond_p=0.0,
1293
+ cfg_factor=1,
1294
+ batchify=False,
1295
+ image_base_size=None,
1296
+ drop_think=False,
1297
+ ):
1298
+ if bot_task == "img_ratio":
1299
+ assert image_base_size is not None, "image_base_size should be provided for img_ratio task."
1300
+
1301
+ # If cfg_factor > 1, we need to repeat the unconditioned part
1302
+ if batchify:
1303
+ assert isinstance(message_list[0], list), \
1304
+ f"When batchify is True, message_list should be a list of list, but got [{type(message_list[0])}, ...]."
1305
+ return self.batch_gen_infer(
1306
+ infer_fn=self.apply_general_template,
1307
+ prompt_list=[[]],
1308
+ infer_fn_kwargs_list=[dict(
1309
+ message_list=message_list_i,
1310
+ max_length=max_length,
1311
+ add_assistant_prefix=add_assistant_prefix,
1312
+ answer=answer,
1313
+ bot_task=bot_task,
1314
+ sequence_template=sequence_template,
1315
+ image_base_size=image_base_size,
1316
+ drop_think=drop_think,
1317
+ ) for message_list_i in message_list],
1318
+ do_classifier_free_guidance=cfg_factor > 1,
1319
+ condition_repeat_times=1,
1320
+ uncondition_repeat_times=cfg_factor - 1,
1321
+ )
1322
+
1323
+ sequence_template = sequence_template or self.sequence_template
1324
+ uncond_kwargs = dict(uncond_enabled=uncond_p == 1.0, uncond_p=uncond_p)
1325
+
1326
+ def process_successive_message(_message_list, _cur_message_idx, role, prefix, suffix,
1327
+ answer_prefix="", answer_suffix=""):
1328
+ _sub_sections = []
1329
+ while _cur_message_idx < len(message_list) and _message_list[_cur_message_idx]['role'] == role:
1330
+ message = _message_list[_cur_message_idx]
1331
+ if message['type'] == 'text':
1332
+ text = message['content']
1333
+ if role == "system":
1334
+ _sub_sections.append(dict(type="text", text=text))
1335
+ elif role == "assistant":
1336
+ if (self.recaption_token in text and self.end_of_recaption_token in text) or (
1337
+ self.think_token in text and self.end_of_think_token in text):
1338
+ _sub_sections.extend(self.get_cot_sections(text, uncond_kwargs, drop_think=drop_think))
1339
+ else:
1340
+ _sub_sections.append(dict(
1341
+ type="text", text=f"{answer_prefix}{text}{answer_suffix}", **uncond_kwargs))
1342
+ else:
1343
+ _sub_sections.append(dict(type="text", text=text, **uncond_kwargs))
1344
+ elif message['type'] == 'gen_image':
1345
+ info = message['content']
1346
+ assert isinstance(info, ImageInfo), f"Expected ImageInfo, but got {type(info)}"
1347
+ if role == "assistant":
1348
+ _sub_sections.append(dict(type="text", text=answer_prefix))
1349
+ _sub_sections.append(dict(type=message['type'], **info.meta_info))
1350
+ if role == "assistant":
1351
+ _sub_sections.append(dict(type="text", text=answer_suffix))
1352
+ elif message['type'] in ['cond_joint_image', 'cond_vae_image', 'cond_vit_image']:
1353
+ info = message['content']
1354
+ assert isinstance(info, (ImageInfo, JointImageInfo)), \
1355
+ f"Expected ImageInfo or JointImageInfo, but got {type(info)}"
1356
+ _sub_sections.append(dict(type=message['type'], **info.meta_info))
1357
+ else:
1358
+ raise ValueError(f"Unknown message type: {message['type']}")
1359
+ _cur_message_idx += 1
1360
+ if len(_sub_sections) > 0:
1361
+ # Add role prefix and suffix
1362
+ _sub_sections.insert(0, dict(type='text', text=prefix))
1363
+ _sub_sections.append(dict(type='text', text=suffix))
1364
+ return _sub_sections, _cur_message_idx
1365
+
1366
+ # Define assistant prefix and suffix
1367
+ if (answer == "auto" and sequence_template == "instruct") or answer is True:
1368
+ answer_prefix, answer_suffix = self.answer_token, self.end_of_answer_token
1369
+ else:
1370
+ answer_prefix, answer_suffix = "", ""
1371
+ if sequence_template == "pretrain":
1372
+ system_suffix = ""
1373
+ user_prefix = ""
1374
+ user_suffix = ""
1375
+ bot_prefix = ""
1376
+ bot_suffix = ""
1377
+ else:
1378
+ conv = self.conversation
1379
+ system_suffix = f"{conv.sep}"
1380
+ user_prefix = conv.get_role_prefix(conv.roles[0])
1381
+ user_suffix = f"{conv.sep}"
1382
+ bot_prefix = conv.get_role_prefix(conv.roles[1])
1383
+ bot_suffix = f"{conv.sep}"
1384
+
1385
+ # Process successive user and assistant messages
1386
+ sections = []
1387
+ cur_message_idx = 0
1388
+ final_role = None
1389
+ while cur_message_idx < len(message_list):
1390
+ # Process successive system messages
1391
+ sub_sections, cur_message_idx = process_successive_message(
1392
+ message_list, cur_message_idx, role="system", prefix="", suffix=system_suffix)
1393
+ # Add to the template and sections
1394
+ sections.extend(sub_sections)
1395
+ if len(sub_sections) > 0:
1396
+ final_role = "system"
1397
+
1398
+ # Process successive user messages
1399
+ sub_sections, cur_message_idx = process_successive_message(
1400
+ message_list, cur_message_idx, role="user", prefix=user_prefix, suffix=user_suffix)
1401
+ # Add to the template and sections
1402
+ sections.extend(sub_sections)
1403
+ if len(sub_sections) > 0:
1404
+ final_role = "user"
1405
+
1406
+ # Process successive assistant messages
1407
+ sub_sections, cur_message_idx = process_successive_message(
1408
+ message_list, cur_message_idx, role="assistant", prefix=bot_prefix, suffix=bot_suffix,
1409
+ answer_prefix=answer_prefix, answer_suffix=answer_suffix,
1410
+ )
1411
+ # Add to the template and sections
1412
+ sections.extend(sub_sections)
1413
+ if len(sub_sections) > 0:
1414
+ final_role = "assistant"
1415
+
1416
+ if add_assistant_prefix:
1417
+ if final_role == "assistant":
1418
+ # Avoid adding prefix twice
1419
+ _bot_prefix = ""
1420
+ # Remove the final bot_suffix
1421
+ if len(sections) > 0 and sections[-1]['type'] == 'text' and sections[-1]['text'] == bot_suffix:
1422
+ sections = sections[:-1]
1423
+ else:
1424
+ _bot_prefix = bot_prefix
1425
+ # We can add special tokens for the bot lastest message according to different tasks
1426
+ bot_response_prefix = dict(
1427
+ auto=_bot_prefix,
1428
+ image="",
1429
+ think=f"{_bot_prefix}{self.think_token}",
1430
+ recaption=f"{_bot_prefix}{self.recaption_token}",
1431
+ img_ratio=f"{_bot_prefix}{answer_prefix}{self.boi_token}{self.size_token(image_base_size)}",
1432
+ )[bot_task]
1433
+ sections.append(dict(type='text', text=bot_response_prefix))
1434
+
1435
+ output = self.encode_general(
1436
+ sections=sections,
1437
+ use_text_mask=False,
1438
+ add_eos=False,
1439
+ add_pad=False,
1440
+ )
1441
+
1442
+ if max_length is not None:
1443
+ if output.tokens.shape[-1] > max_length:
1444
+ raise ValueError(
1445
+ f"Encoded token length {output.tokens.shape[-1]} exceeds max_length {max_length}.\n"
1446
+ f"Please set a larger max_length or check the input messages:\n{message_list}"
1447
+ )
1448
+
1449
+ return output, sections
1450
+
1451
+ def apply_chat_template(
1452
+ self,
1453
+ batch_prompt: Optional[list[str]] = None,
1454
+ batch_message_list: Optional[list[list[dict[str, Any]]]] = None,
1455
+ mode: str = "gen_text",
1456
+ batch_gen_image_info: Optional[list[ImageInfo]] = None,
1457
+ batch_cond_images: Optional[Union[list[CondImage], list[list[CondImage]]]] = None,
1458
+ batch_system_prompt: Optional[list[str]] = None,
1459
+ batch_cot_text: Optional[list[str]] = None,
1460
+ max_length: Optional[int] = None,
1461
+ bot_task: str = "auto", # auto/image/think/recaption/img_ratio
1462
+ image_base_size: Optional[int] = None,
1463
+ sequence_template: str = "pretrain",
1464
+ cfg_factor: int = 1,
1465
+ add_assistant_prefix: Optional[bool] = None,
1466
+ drop_think: bool = False,
1467
+ ) -> dict[str, Any]:
1468
+ assert bot_task in ["image", "auto", "think", "recaption", "img_ratio"], \
1469
+ f"bot_task should be one of ['image', 'auto', 'think', 'recaption', 'img_ratio'], but got {bot_task}."
1470
+
1471
+ if batch_message_list is None:
1472
+ # Simple text-to-image or text-cot-to-image task
1473
+ batch_size = len(batch_prompt)
1474
+
1475
+ # Batchify inputs
1476
+ if not isinstance(batch_system_prompt, list):
1477
+ batch_system_prompt = [batch_system_prompt] * batch_size
1478
+ if not isinstance(batch_gen_image_info, list):
1479
+ batch_gen_image_info = [batch_gen_image_info] * batch_size
1480
+ if batch_cot_text is not None:
1481
+ assert len(batch_cot_text) == batch_size, \
1482
+ (f"batch_cot_text should have the same length as batch_size ({batch_size}), "
1483
+ f"but got {len(batch_cot_text)}.")
1484
+ else:
1485
+ batch_cot_text = [None] * batch_size
1486
+ if batch_cond_images is not None:
1487
+ assert len(batch_cond_images) == batch_size, \
1488
+ (f"batch_cond_image_info should have the same length as batch_size ({batch_size}), "
1489
+ f"but got {len(batch_cond_images)}.")
1490
+ batch_cond_images = [
1491
+ cond_images if isinstance(cond_images, list) else [cond_images]
1492
+ for cond_images in batch_cond_images
1493
+ ]
1494
+ else:
1495
+ batch_cond_images = [[] for _ in range(batch_size)]
1496
+
1497
+ # Convert single round materials into standard message list
1498
+ batch_message_list = []
1499
+ for prompt, system_prompt, cot_text, gen_image_info, cond_images in zip(
1500
+ batch_prompt, batch_system_prompt, batch_cot_text, batch_gen_image_info,
1501
+ batch_cond_images,
1502
+ ):
1503
+ message_list = []
1504
+ # 1. system prompt section
1505
+ if system_prompt:
1506
+ message_list.append(dict(role="system", type="text", content=system_prompt))
1507
+ # 2. user inputs sections
1508
+ # 2.1 image inputs
1509
+ if len(cond_images) > 0:
1510
+ message_list.extend([
1511
+ dict(role="user", type=cond_image.section_type, content=cond_image.i)
1512
+ for cond_image in cond_images
1513
+ ])
1514
+ # 2.2 text inputs
1515
+ message_list.append(dict(role="user", type="text", content=prompt))
1516
+ # 3. assistant answer sections
1517
+ if cot_text is not None:
1518
+ message_list.append(dict(role="assistant", type="text", content=cot_text))
1519
+ if mode == "gen_image":
1520
+ message_list.append(dict(
1521
+ role="assistant", type="gen_image", content=gen_image_info))
1522
+ # ---
1523
+ batch_message_list.append(message_list)
1524
+
1525
+ output, sections = self.apply_general_template(
1526
+ message_list=batch_message_list,
1527
+ max_length=max_length,
1528
+ add_assistant_prefix=default(add_assistant_prefix, mode != "gen_image"),
1529
+ bot_task=bot_task,
1530
+ sequence_template=sequence_template,
1531
+ cfg_factor=cfg_factor,
1532
+ batchify=True,
1533
+ image_base_size=image_base_size,
1534
+ drop_think=drop_think,
1535
+ )
1536
+ return dict(output=output, sections=sections)
1537
+
1538
+ def pad(self, tensor_list, dim=0, pad_val=None):
1539
+ if pad_val is None:
1540
+ pad_val = self.pad_token_id
1541
+ max_len = max([t.shape[dim] for t in tensor_list])
1542
+ padded_tensor_list = []
1543
+ for t in tensor_list:
1544
+ if t.shape[dim] < max_len:
1545
+ assert pad_val is not False, "Not allowed pad."
1546
+ t = F.pad(t, (0, max_len - t.shape[dim]), value=pad_val)
1547
+ padded_tensor_list.append(t)
1548
+ return padded_tensor_list
1549
+
1550
+ def batch_gen_infer(
1551
+ self,
1552
+ infer_fn,
1553
+ prompt_list: list,
1554
+ negative_prompt_list: list = None,
1555
+ infer_fn_kwargs_list: list[dict[str, int]] = None,
1556
+ do_classifier_free_guidance=False,
1557
+ condition_repeat_times: int = 1,
1558
+ uncondition_repeat_times: int = 1,
1559
+ ):
1560
+ """
1561
+ Batch inference for the AR-like model training of the text-to-image/instruction tuning tasks.
1562
+
1563
+ Parameters
1564
+ ----------
1565
+ infer_fn: callable
1566
+ Inference function to encode the prompt.
1567
+ prompt_list: list
1568
+ List of prompts. Each element can be a single prompt or a list of prompts passed to the infer_fn.
1569
+ negative_prompt_list: list
1570
+ List of negative prompts. Only used when do_classifier_free_guidance is True. If None, will use <cfg> token sequence as negative prompt.
1571
+ infer_fn_kwargs_list: List[Dict[str, int]]
1572
+ List of keyword arguments for the infer_fn.
1573
+ do_classifier_free_guidance: bool
1574
+ Whether to do classifier-free guidance.
1575
+ condition_repeat_times: int
1576
+ uncondition_repeat_times: int
1577
+ Support multi-condition and multi-uncondition. e.g, [pred_cond, pred_uncond_text, pred_uncond_text_uncond_src]
1578
+ """
1579
+ if infer_fn_kwargs_list is None:
1580
+ infer_fn_kwargs_list = [{} for _ in prompt_list]
1581
+
1582
+ # [n_output, bsz]
1583
+ cond_results_list = None
1584
+ uncond_results_list = None
1585
+ output_type_list = []
1586
+
1587
+ for prompt_idx, (prompt, infer_fn_kwargs) in enumerate(zip(prompt_list, infer_fn_kwargs_list)):
1588
+ if not isinstance(prompt, (list, tuple)):
1589
+ prompt = [prompt]
1590
+ cond_kwargs = {"uncond_p": 0.0} if do_classifier_free_guidance else {}
1591
+ results = infer_fn(
1592
+ *prompt,
1593
+ **infer_fn_kwargs,
1594
+ **cond_kwargs,
1595
+ )
1596
+ output_type_list.append((type(results), len(results) if isinstance(results, (list, tuple)) else 1))
1597
+ if isinstance(results, dict):
1598
+ raise ValueError("Make batch on dict is not supported. Please return list or tuple for infer_fn.")
1599
+ if not isinstance(results, (list, tuple)):
1600
+ results = (results,)
1601
+ if cond_results_list is None:
1602
+ cond_results_list = [[] for _ in results]
1603
+ uncond_results_list = [[] for _ in results]
1604
+ for i, result in enumerate(results):
1605
+ cond_results_list[i].append(result)
1606
+
1607
+ if do_classifier_free_guidance:
1608
+ if negative_prompt_list is None:
1609
+ uncond_kwargs = {"uncond_p": 1.0}
1610
+ uncond_results = infer_fn(
1611
+ *prompt,
1612
+ **infer_fn_kwargs,
1613
+ **uncond_kwargs,
1614
+ )
1615
+ else:
1616
+ negative_prompt = negative_prompt_list[prompt_idx]
1617
+ if not isinstance(negative_prompt, (list, tuple)):
1618
+ negative_prompt = [negative_prompt]
1619
+ uncond_results = infer_fn(
1620
+ *negative_prompt,
1621
+ **infer_fn_kwargs,
1622
+ )
1623
+ if isinstance(uncond_results, TokenizerEncodeOutput):
1624
+ uncond_results_list.append(uncond_results)
1625
+ else:
1626
+ for i, result in enumerate(uncond_results):
1627
+ uncond_results_list[i].append(result)
1628
+
1629
+ assert all(output_type_list[0] == n for n in output_type_list), \
1630
+ f"Number of outputs should be equal for all samples, but got {output_type_list}."
1631
+ output_type, output_num = output_type_list[0]
1632
+
1633
+ def make_batch(batch_cond_item, batch_uncond_item):
1634
+ # Process each output item to make batch
1635
+ first = batch_cond_item[0] # The first element in the batch
1636
+ if isinstance(first, torch.Tensor):
1637
+ stacked_item = torch.stack(self.pad(
1638
+ batch_cond_item * condition_repeat_times + batch_uncond_item * uncondition_repeat_times,
1639
+ ))
1640
+
1641
+ elif first is None:
1642
+ assert all(item is None for item in batch_cond_item + batch_uncond_item), \
1643
+ (f"The first cond item is None, but some items are not None:\n\n"
1644
+ f"condition: {batch_cond_item}\n\n"
1645
+ f"uncondition: {batch_uncond_item}")
1646
+ stacked_item = None
1647
+
1648
+ elif isinstance(first, (list, tuple)):
1649
+ # If the output item is a list or tuple, we treat it as a whole, and won't make nested batch any more.
1650
+ stacked_item = batch_cond_item * condition_repeat_times + batch_uncond_item * uncondition_repeat_times
1651
+
1652
+ elif isinstance(first, TokenizerEncodeOutput):
1653
+ stacked_item = {}
1654
+ # Traverse not-None attributes
1655
+ for key in list(first.keys()):
1656
+ merged_list = [cond_item[key] for cond_item in batch_cond_item] * condition_repeat_times + \
1657
+ [uncond_item[key] for uncond_item in batch_uncond_item] * uncondition_repeat_times
1658
+ if isinstance(first[key], torch.Tensor):
1659
+ if 'mask' in key:
1660
+ pad_val = 0.0
1661
+ elif key == 'tokens':
1662
+ pad_val = self.pad_token_id
1663
+ else:
1664
+ pad_val = False # Should not pad for other tensors
1665
+ stacked_item[key] = torch.stack(self.pad(merged_list, pad_val=pad_val), dim=0)
1666
+ elif isinstance(first[key], list):
1667
+ stacked_item[key] = merged_list
1668
+ elif first[key] is None:
1669
+ pass
1670
+ else:
1671
+ raise ValueError(f"Unsupported type of {key}: {type(first[key])}.")
1672
+ stacked_item = TokenizerEncodeOutput(stacked_item)
1673
+
1674
+ else:
1675
+ raise TypeError(f"Making batch on type {type(first)} is not supported.")
1676
+
1677
+ return stacked_item
1678
+
1679
+ stacked_outputs = []
1680
+ for cond_results, uncond_results in zip(cond_results_list, uncond_results_list):
1681
+ stacked_outputs.append(make_batch(cond_results, uncond_results))
1682
+
1683
+ if output_type == list:
1684
+ return stacked_outputs
1685
+ elif output_type == tuple:
1686
+ return tuple(stacked_outputs)
1687
+ elif output_num == 1:
1688
+ return stacked_outputs[0]
1689
+ else:
1690
+ raise ValueError(f"Unsupported output type: {output_type}.")
1691
+
1692
+
1693
+ class DecoratorSections(object):
1694
+ """ Define predefined sections in a multimodal template. """
1695
+
1696
+ def __init__(
1697
+ self,
1698
+ tokenizer: HunyuanImage3TokenizerFast,
1699
+ conv: Conversation,
1700
+ sequence_template: str,
1701
+ ignore_start_tokens: Optional[set] = None,
1702
+ ):
1703
+ self.tokenizer = tokenizer
1704
+ self.conv = conv
1705
+ self.sequence_template = sequence_template
1706
+ self.ignore_start_tokens = ignore_start_tokens or set()
1707
+ self.roles = self.conv.roles
1708
+
1709
+ # Define sections based on the sequence template
1710
+ if self.sequence_template == "pretrain":
1711
+ self.user = []
1712
+ self.user_sep = []
1713
+ self.bot = []
1714
+ self.bot_sep = []
1715
+ self.answer_ = []
1716
+ self._answer = []
1717
+
1718
+ elif self.sequence_template == "instruct":
1719
+ self.user = [dict(type="text", text=self.conv.get_role_prefix(self.roles[0]), ignore=True)]
1720
+ self.user_sep = [dict(type="text", text=self.conv.sep)]
1721
+ self.bot = [dict(type="text", text=self.conv.get_role_prefix(self.roles[1]), ignore=True)]
1722
+ self.bot_sep = [dict(type="text", text=self.conv.sep2)]
1723
+ self.answer_ = [dict(type="text", text=self.tokenizer.answer_token,
1724
+ ignore=(self.tokenizer.answer_token in self.ignore_start_tokens))]
1725
+ self._answer = [dict(type="text", text=self.tokenizer.end_of_answer_token)]
1726
+
1727
+ else:
1728
+ raise NotImplementedError(f"Unsupported sequence_template: {self.sequence_template}")
1729
+
1730
+ # Define eos token
1731
+ eos_token = self.tokenizer.eos_token
1732
+ if isinstance(eos_token, int):
1733
+ eos_token = self.tokenizer.convert_ids_to_tokens(eos_token)
1734
+ assert isinstance(eos_token, str), f"eos_token should be a string, got {type(eos_token)}."
1735
+ self.eos = [dict(type="text", text=eos_token)]
1736
+
1737
+ # Define think sections
1738
+ self.think_ = [dict(type="text", text=self.tokenizer.think_token,
1739
+ ignore=(self.tokenizer.think_token in self.ignore_start_tokens))]
1740
+ self._think = [dict(type="text", text=self.tokenizer.end_of_think_token)]
1741
+
1742
+ # Define recaption sections
1743
+ if hasattr(self.tokenizer, "recaption_token"):
1744
+ self.recaption_ = [dict(type="text", text=self.tokenizer.recaption_token,
1745
+ ignore=(self.tokenizer.recaption_token in self.ignore_start_tokens))]
1746
+ self._recaption = [dict(type="text", text=self.tokenizer.end_of_recaption_token)]
1747
+
1748
+ def answer(self, section):
1749
+ if isinstance(section, dict):
1750
+ section = [section]
1751
+ return self.answer_ + section + self._answer
1752
+
1753
+ def think(self, section):
1754
+ if isinstance(section, dict):
1755
+ section = [section]
1756
+ return self.think_ + section + self._think
1757
+
1758
+ def recaption(self, section):
1759
+ if not hasattr(self, "recaption_"):
1760
+ raise AttributeError("This tokenizer does not support recaption sections.")
1761
+ if isinstance(section, dict):
1762
+ section = [section]
1763
+ return self.recaption_ + section + self._recaption
1764
+
1765
+
1766
+ __all__ = [
1767
+ "ResolutionGroup",
1768
+ "ImageInfo",
1769
+ "ImageTensor",
1770
+ "JointImageInfo",
1771
+ "CondImage",
1772
+ "HunyuanImage3TokenizerFast",
1773
+ ]
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c2439418b9be76a54d8b5ff1ed89b37e36ec1735730f7da99b5aabb83a73db64
3
+ size 25028750
tokenizer_config.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token": "<|startoftext|>",
3
+ "clean_up_tokenization_spaces": true,
4
+ "eos_token": "<|endoftext|>",
5
+ "model_max_length": 1048576,
6
+ "pad_token": "<pad>",
7
+ "tokenizer_class": "PreTrainedTokenizerFast"
8
+ }
utils/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ # Minimal utils module for hunyuan_image_3
2
+ from .import_utils import _LazyModule, define_import_structure
3
+
4
+ __all__ = ["_LazyModule", "define_import_structure"]
utils/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (333 Bytes). View file