diffrhythm-1.2-p100a / code /models /tt_dit /tests /unit /test_embeddings.py
sunandbean's picture
Add files using upload-large-folder tool
33d23e1 verified
Raw History Blame Contribute Delete
22.1 kB
# SPDX-FileCopyrightText: © 2025 Tenstorrent USA, Inc.
# SPDX-License-Identifier: Apache-2.0
import math
import pytest
import torch
from diffusers import FlowMatchEulerDiscreteScheduler
from diffusers.models.embeddings import (
CombinedTimestepGuidanceTextProjEmbeddings as TorchCombinedTimestepGuidanceTextProjEmbeddings,
)
from diffusers.models.embeddings import Timesteps as TorchTimesteps
from diffusers.models.transformers.transformer_wan import WanTimeTextImageEmbedding as TorchWanTimeTextImageEmbedding
from loguru import logger
import ttnn
from ...layers.embeddings import (
CombinedTimestepGuidanceTextProjEmbeddings,
Embedding,
PixArtAlphaTextProjection,
SD35CombinedTimestepTextProjEmbeddings,
TimestepEmbedding,
Timesteps,
WanPatchEmbed,
WanTimeTextImageEmbedding,
)
from ...parallel.manager import CCLManager
from ...utils import tensor
from ...utils.check import assert_quality
from ...utils.substate import substate
from ...utils.tensor import bf16_tensor, float32_tensor, unflatten
class TorchCombinedTimestepTextProjEmbeddings(torch.nn.Module):
def __init__(self, embedding_dim: int, pooled_projection_dim: int) -> None:
super().__init__()
self.timestep_embedder = TorchTimestepEmbedding(in_channels=256, time_embed_dim=embedding_dim)
self.text_embedder = TorchPixArtAlphaTextProjection(
in_features=pooled_projection_dim, hidden_size=embedding_dim
)
def forward(self, timestep: torch.Tensor, pooled_projection: torch.Tensor) -> torch.Tensor:
timesteps_proj = _time_proj(num_channels=256, timesteps=timestep)
timesteps_emb = self.timestep_embedder(timesteps_proj.to(dtype=pooled_projection.dtype))
return timesteps_emb + self.text_embedder(pooled_projection)
class TorchTimestepEmbedding(torch.nn.Module):
def __init__(self, in_channels: int, time_embed_dim: int) -> None:
super().__init__()
self.linear_1 = torch.nn.Linear(in_channels, time_embed_dim)
self.act = torch.nn.SiLU()
self.linear_2 = torch.nn.Linear(time_embed_dim, time_embed_dim)
def forward(self, sample: torch.Tensor) -> torch.Tensor:
sample = self.linear_1(sample)
sample = self.act(sample)
return self.linear_2(sample)
def _time_proj(num_channels: int, timesteps: torch.Tensor) -> torch.Tensor:
assert num_channels % 2 == 0
half_dim = num_channels // 2
max_period = 10000
exponent = -math.log(max_period) * torch.arange(start=0, end=half_dim, dtype=torch.float32, device=timesteps.device)
exponent = exponent / half_dim
emb = torch.exp(exponent)
emb = timesteps[:, None].float() * emb[None, :]
return torch.cat([torch.cos(emb), torch.sin(emb)], dim=-1)
class TorchPixArtAlphaTextProjection(torch.nn.Module):
def __init__(self, in_features: int, hidden_size: int) -> None:
super().__init__()
self.linear_1 = torch.nn.Linear(in_features=in_features, out_features=hidden_size)
self.linear_2 = torch.nn.Linear(in_features=hidden_size, out_features=hidden_size)
self.act_1 = torch.nn.SiLU()
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = self.linear_1(x)
x = self.act_1(x)
return self.linear_2(x)
class TorchPatchEmbed(torch.nn.Module):
def __init__(
self,
*,
patch_size: int,
in_channels: int,
embed_dim: int,
pos_embed_max_size: int,
) -> None:
super().__init__()
self.pos_embed_max_size = pos_embed_max_size
self.patch_size = patch_size
self.proj = torch.nn.Conv2d(
in_channels,
embed_dim,
kernel_size=(patch_size, patch_size),
stride=patch_size,
)
self.register_buffer("pos_embed", torch.zeros((1, pos_embed_max_size**2, embed_dim)))
def forward(self, latent: torch.Tensor) -> torch.Tensor:
height, width = latent.shape[-2:]
latent = self.proj(latent)
latent = latent.flatten(2).transpose(1, 2) # BCHW -> BNC
pos_embed = self._cropped_pos_embed(height, width)
return (latent + pos_embed).to(latent.dtype)
def _cropped_pos_embed(self, height: int, width: int) -> torch.Tensor:
height = height // self.patch_size
width = width // self.patch_size
top = (self.pos_embed_max_size - height) // 2
left = (self.pos_embed_max_size - width) // 2
spatial_pos_embed = self.pos_embed.reshape(1, self.pos_embed_max_size, self.pos_embed_max_size, -1)
spatial_pos_embed = spatial_pos_embed[:, top : top + height, left : left + width, :]
return spatial_pos_embed.reshape(1, -1, spatial_pos_embed.shape[-1])
class TorchWanPatchEmbed(torch.nn.Module):
def __init__(
self,
patch_size: tuple,
in_channels: int,
embed_dim: int,
) -> None:
super().__init__()
self.patch_size = patch_size
self.proj = torch.nn.Conv3d(
in_channels,
embed_dim,
kernel_size=patch_size,
stride=patch_size,
)
def forward(self, latent: torch.Tensor) -> torch.Tensor:
# latent: (B, C, T, H, W)
latent = self.proj(latent)
# latent: (B, embed_dim, T', H', W')
B, embed_dim, T_out, H_out, W_out = latent.shape
# Flatten spatial dimensions and permute: BCTHW -> B(THW)C
latent = latent.permute(0, 2, 3, 4, 1) # (B, T', H', W', embed_dim)
latent = latent.reshape(B, T_out * H_out * W_out, embed_dim)
return latent
@pytest.mark.parametrize(
"mesh_device",
[(1, 1)],
indirect=True,
)
@pytest.mark.parametrize(
("num_channels, num_inference_steps"),
[
(256, 40), # SD3.5 timestep embedding. Add wan
],
)
# @pytest.mark.parametrize("device_params", [{"fabric_config": ttnn.FabricConfig.FABRIC_1D}], indirect=True)
def test_timestep_encoding(
mesh_device: ttnn.MeshDevice,
num_channels: int,
num_inference_steps: int,
) -> None:
torch_dtype = torch.bfloat16
# Create Torch model
torch_model = TorchTimesteps(num_channels=num_channels, flip_sin_to_cos=True, downscale_freq_shift=0, scale=1).to(
torch_dtype
)
torch_model.eval()
# Create TT model
tt_model = Timesteps(num_channels=num_channels, mesh_device=mesh_device, dtype=ttnn.float32)
tt_model_bf16 = Timesteps(num_channels=num_channels, mesh_device=mesh_device, dtype=ttnn.bfloat16)
# Create input tensors
torch.manual_seed(0)
scheduler = FlowMatchEulerDiscreteScheduler()
scheduler.set_timesteps(num_inference_steps)
input_tensor = scheduler.timesteps # Assume batched based on num_inference_steps to test all.
# Run torch model
torch_output = torch_model(input_tensor)
# Convert to TT tensor
tt_input = float32_tensor(input_tensor.unsqueeze(1).unsqueeze(1).unsqueeze(1), device=mesh_device)
# Run TT model
tt_output = tt_model(tt_input)
tt_output_bf16 = tt_model_bf16(ttnn.typecast(tt_input, dtype=ttnn.bfloat16))
# Convert back to torch and compare
tt_output_torch = ttnn.to_torch(tt_output).squeeze(1).squeeze(1) # remove wh dimensions
tt_output_torch_bf16 = ttnn.to_torch(tt_output_bf16).squeeze(1).squeeze(1) # remove wh dimensions
assert_quality(torch_output, tt_output_torch, pcc=0.99, relative_rmse=0.02) # FP32. Expected RMSE ~1%
assert_quality(torch_output, tt_output_torch_bf16, pcc=0.95, relative_rmse=0.35) # BF16. Expected RMSE ~30%
@pytest.mark.parametrize(
"mesh_device",
[(1, 1)],
indirect=True,
)
@pytest.mark.parametrize(
("B, in_channels, time_embed_dim"),
[
(1, 256, 2432), # SD3.5 timestep embedding
(2, 256, 1536), # SD3.5 medium
(1, 128, 1024), # Smaller test case
],
)
# @pytest.mark.parametrize("device_params", [{"fabric_config": ttnn.FabricConfig.FABRIC_1D}], indirect=True)
def test_timestep_embedding(
mesh_device: ttnn.MeshDevice,
B: int,
in_channels: int,
time_embed_dim: int,
) -> None:
torch_dtype = torch.bfloat16
# Create Torch model
torch_model = TorchTimestepEmbedding(in_channels, time_embed_dim).to(torch_dtype)
torch_model.eval()
# Create TT model
tt_model = TimestepEmbedding(
in_channels=in_channels,
time_embed_dim=time_embed_dim,
mesh_device=mesh_device,
)
tt_model.load_torch_state_dict(torch_model.state_dict())
# Create input tensors
torch.manual_seed(0)
input_tensor = torch.randn((B, in_channels), dtype=torch_dtype)
# Run torch model
torch_output = torch_model(input_tensor)
# Convert to TT tensor
input_4d = input_tensor.unsqueeze(0).unsqueeze(0)
tt_input = bf16_tensor(input_4d, device=mesh_device)
# Run TT model
tt_output = tt_model(tt_input)
# Convert back to torch and compare
tt_output_torch = ttnn.to_torch(tt_output).squeeze(0).squeeze(0)
assert_quality(torch_output, tt_output_torch, pcc=0.9998, relative_rmse=0.017)
@pytest.mark.parametrize(
"mesh_device",
[(1, 1)],
indirect=True,
)
@pytest.mark.parametrize(
("B, in_features, hidden_size"),
[
(1, 2048, 2432), # SD3.5 text projection
(2, 1152, 1536), # SD3.5 medium text projection
(1, 512, 1024), # Smaller test case
],
)
# @pytest.mark.parametrize("device_params", [{"fabric_config": ttnn.FabricConfig.FABRIC_1D}], indirect=True)
def test_pixart_alpha_text_projection(
mesh_device: ttnn.MeshDevice,
B: int,
in_features: int,
hidden_size: int,
) -> None:
torch_dtype = torch.bfloat16
# Create Torch model
torch_model = TorchPixArtAlphaTextProjection(in_features, hidden_size).to(torch_dtype)
torch_model.eval()
# Create TT model
tt_model = PixArtAlphaTextProjection(
in_features=in_features,
hidden_size=hidden_size,
mesh_device=mesh_device,
)
tt_model.load_torch_state_dict(torch_model.state_dict())
# Create input tensors
torch.manual_seed(0)
input_tensor = torch.randn((B, in_features), dtype=torch_dtype)
# Run torch model
torch_output = torch_model(input_tensor)
# Convert to TT tensor
input_4d = input_tensor.unsqueeze(0).unsqueeze(0)
tt_input = bf16_tensor(input_4d, device=mesh_device)
# Run TT model
tt_output = tt_model(tt_input)
# Convert back to torch and compare
tt_output_torch = ttnn.to_torch(tt_output).squeeze(0).squeeze(0)
assert_quality(torch_output, tt_output_torch, pcc=0.99984, relative_rmse=0.019)
@pytest.mark.parametrize(
"mesh_device",
[(1, 1)],
indirect=True,
)
@pytest.mark.parametrize(
("B, embedding_dim, pooled_projection_dim"),
[
(1, 2432, 2048), # SD3.5 large config
(2, 1536, 1152), # SD3.5 medium config
(1, 1024, 768), # Smaller test case
],
)
# @pytest.mark.parametrize("device_params", [{"fabric_config": ttnn.FabricConfig.FABRIC_1D}], indirect=True)
def test_combined_timestep_text_proj_embeddings(
mesh_device: ttnn.MeshDevice,
B: int,
embedding_dim: int,
pooled_projection_dim: int,
) -> None:
torch_dtype = torch.bfloat16
# Create Torch model
torch_model = TorchCombinedTimestepTextProjEmbeddings(embedding_dim, pooled_projection_dim).to(torch_dtype)
torch_model.eval()
# Create TT model
tt_model = SD35CombinedTimestepTextProjEmbeddings(
embedding_dim=embedding_dim,
pooled_projection_dim=pooled_projection_dim,
mesh_device=mesh_device,
)
tt_model.load_torch_state_dict(torch_model.state_dict())
# Create input tensors
torch.manual_seed(0)
timestep_tensor = torch.randn((B,), dtype=torch_dtype)
pooled_projection_tensor = torch.randn((B, pooled_projection_dim), dtype=torch_dtype)
# Run torch model
torch_output = torch_model(timestep_tensor, pooled_projection_tensor)
# Convert to TT tensors
timestep_4d = timestep_tensor.unsqueeze(0).unsqueeze(0).unsqueeze(-1)
pooled_4d = pooled_projection_tensor.unsqueeze(0).unsqueeze(0)
tt_timestep = bf16_tensor(timestep_4d, device=mesh_device)
tt_pooled = bf16_tensor(pooled_4d, device=mesh_device)
# Run TT model
tt_output = tt_model(tt_timestep, tt_pooled)
# Convert back to torch and compare
tt_output_torch = ttnn.to_torch(tt_output).squeeze(0).squeeze(0)
assert_quality(
torch_output, tt_output_torch, pcc=0.99984, relative_rmse=0.019
) # Slightly lower PCC due to sinusoidal ops
@pytest.mark.parametrize("mesh_device", [(1, 1)], indirect=True)
@pytest.mark.parametrize(
("batch_size", "embedding_dim", "pooled_projection_dim"),
[
(10, 3072, 768), # Flux.1 [schnell]
],
)
# @pytest.mark.parametrize("device_params", [{"fabric_config": ttnn.FabricConfig.FABRIC_1D}], indirect=True)
def test_combined_timestep_guidance_text_proj_embeddings(
mesh_device: ttnn.MeshDevice,
batch_size: int,
embedding_dim: int,
pooled_projection_dim: int,
) -> None:
torch_model = TorchCombinedTimestepGuidanceTextProjEmbeddings(embedding_dim, pooled_projection_dim)
torch_model.eval()
tt_model = CombinedTimestepGuidanceTextProjEmbeddings(
embedding_dim=embedding_dim,
pooled_projection_dim=pooled_projection_dim,
mesh_device=mesh_device,
)
tt_model.load_torch_state_dict(torch_model.state_dict())
torch.manual_seed(0)
timestep = torch.full([batch_size], fill_value=500)
guidance = torch.full([batch_size], fill_value=3)
pooled = torch.randn([batch_size, pooled_projection_dim])
torch_output = torch_model.forward(timestep, guidance, pooled)
tt_timestep = ttnn.from_torch(
timestep.unsqueeze(-1), dtype=ttnn.float32, layout=ttnn.TILE_LAYOUT, device=mesh_device
)
tt_guidance = bf16_tensor(guidance.unsqueeze(-1), device=mesh_device)
tt_pooled = bf16_tensor(pooled, device=mesh_device)
tt_output = tt_model(
timestep=tt_timestep,
guidance=tt_guidance,
pooled_projection=tt_pooled,
)
tt_output_torch = ttnn.to_torch(tt_output)
assert_quality(torch_output, tt_output_torch, pcc=0.9971, relative_rmse=0.076)
@pytest.mark.parametrize(
"mesh_device",
[(1, 1)],
indirect=True,
)
@pytest.mark.parametrize(
("B, T, H, W, patch_size, in_channels, embed_dim"),
[
(1, 21, 60, 104, (1, 2, 2), 16, 5120), # WAN config - similar to test_transformer_wan
(1, 31, 40, 80, (1, 2, 2), 16, 5120), # Alternative WAN config
(1, 16, 32, 32, (2, 2, 2), 12, 3072), # Smaller test case
],
)
# @pytest.mark.parametrize("device_params", [{"fabric_config": ttnn.FabricConfig.FABRIC_1D}], indirect=True)
def test_wan_patch_embed(
mesh_device: ttnn.MeshDevice,
B: int,
T: int,
H: int,
W: int,
patch_size: tuple,
in_channels: int,
embed_dim: int,
) -> None:
torch_dtype = torch.bfloat16
# Create Torch model
torch_model = TorchWanPatchEmbed(
patch_size=patch_size,
in_channels=in_channels,
embed_dim=embed_dim,
).to(torch_dtype)
torch_model.eval()
# Create TT model
tt_model = WanPatchEmbed(
patch_size=patch_size,
in_channels=in_channels,
embed_dim=embed_dim,
mesh_device=mesh_device,
)
tt_model.load_torch_state_dict(substate(torch_model.state_dict(), "proj"))
# Create input tensors
torch.manual_seed(0)
input_tensor_bcthw = torch.randn((B, in_channels, T, H, W), dtype=torch_dtype)
# Calculate output dimensions
pt, ph, pw = patch_size
patches_t = T // pt
patches_h = H // ph
patches_w = W // pw
# Create the patched input tensor for TT model (1, B, patches_total, pt*ph*pw*in_channels)
# The expected format is (1, batch, num_patches, patch_size_flat * in_channels)
input_tensor_patched = input_tensor_bcthw.clone().reshape(
B, in_channels, patches_t, pt, patches_h, ph, patches_w, pw
)
input_tensor_patched = input_tensor_patched.permute(
0, 2, 4, 6, 3, 5, 7, 1
) # (B, patches_t, patches_h, patches_w, pt, ph, pw, in_channels)
input_tensor_patched = input_tensor_patched.reshape(
1, B, patches_t * patches_h * patches_w, pt * ph * pw * in_channels
)
# Run torch model
torch_output = torch_model(input_tensor_bcthw)
# Convert to TT tensor
tt_input = bf16_tensor(input_tensor_patched, device=mesh_device)
# Run TT model
tt_output = tt_model(tt_input)
# Convert back to torch and compare
tt_output_torch = ttnn.to_torch(tt_output)
# Extract the batch output (remove the leading 1 dimension)
tt_output_batch = tt_output_torch[0]
assert_quality(
torch_output, tt_output_batch, pcc=0.999_991, relative_rmse=0.006
) # Lower PCC due to conv3d approximation
@pytest.mark.parametrize(
("B, seq_len, embed_dim"),
[
(1, 512, 4096), # Wan2.2
],
)
@pytest.mark.parametrize(
"mesh_device, num_links",
[[(2, 4), 1], [(4, 8), 4], [(4, 8), 2]],
ids=["t3k", "wh_glx", "bh_glx"],
indirect=["mesh_device"],
)
@pytest.mark.parametrize(
"device_params",
[{"fabric_config": ttnn.FabricConfig.FABRIC_1D}],
indirect=True,
)
def test_wan_time_text_image_embedding(
mesh_device: ttnn.MeshDevice,
B: int,
seq_len: int,
embed_dim: int,
num_links: int,
) -> None:
torch_dtype = torch.float32
torch.manual_seed(12334)
# Create Torch model
inner_dim = 5120
time_freq_dim = 256
time_proj_dim = inner_dim * 6
text_embed_dim = 4096
torch_model = TorchWanTimeTextImageEmbedding(
dim=inner_dim,
time_freq_dim=time_freq_dim,
time_proj_dim=time_proj_dim,
text_embed_dim=text_embed_dim,
).to(torch_dtype)
torch_model.eval()
mesh_axis = 1
ccl_manager = CCLManager(
mesh_device=mesh_device,
num_links=num_links,
topology=ttnn.Topology.Linear,
)
# Create TT model
tt_model = WanTimeTextImageEmbedding(
dim=inner_dim,
time_freq_dim=time_freq_dim,
time_proj_dim=time_proj_dim,
text_embed_dim=text_embed_dim,
mesh_device=mesh_device,
tp_mesh_axis=mesh_axis,
ccl_manager=ccl_manager,
)
tt_model.load_torch_state_dict(torch_model.state_dict())
# Create input tensors
timestep_tensor = torch.randn((B,), dtype=torch_dtype) * 1000
encoder_hidden_states_tensor = torch.randn((B, seq_len, embed_dim), dtype=torch_dtype)
# Run torch model
temb_torch, timestep_proj_torch, encoder_hidden_states_torch, _ = torch_model(
timestep_tensor, encoder_hidden_states_tensor
)
# Convert to TT tensors
timestep_4d = timestep_tensor.unsqueeze(0).unsqueeze(0).unsqueeze(0)
encoder_hidden_states_4d = encoder_hidden_states_tensor.unsqueeze(0)
tt_timestep = float32_tensor(timestep_4d, device=mesh_device)
tt_encoder_hidden_states = bf16_tensor(encoder_hidden_states_4d, device=mesh_device)
# Run TT model
temb, timestep_proj, encoder_hidden_states = tt_model(tt_timestep, tt_encoder_hidden_states)
# prepare data for comparison
timestep_proj_torch = timestep_proj_torch.unflatten(1, (6, -1))
timestep_proj = unflatten(timestep_proj, -1, (6, -1))
temb = ccl_manager.all_gather(temb, dim=-1, mesh_axis=mesh_axis, use_hyperparams=True)
timestep_proj = ccl_manager.all_gather(timestep_proj, dim=-1, mesh_axis=mesh_axis, use_hyperparams=True)
# Convert back to torch and compare
tt_temb_torch = ttnn.to_torch(ttnn.get_device_tensors(temb)[0], dtype=torch_dtype).squeeze(0).squeeze(0)
tt_timestep_proj_torch = (
ttnn.to_torch(ttnn.get_device_tensors(timestep_proj)[0], dtype=torch_dtype).squeeze(0).squeeze(0)
)
tt_encoder_hidden_states_torch = ttnn.to_torch(
ttnn.get_device_tensors(encoder_hidden_states)[0], dtype=torch_dtype
).squeeze(0)
logger.info(
f"torch shape: {timestep_proj_torch.shape}, mean: {torch.mean(timestep_proj_torch)}, std: {torch.std(timestep_proj_torch)}, min: {torch.min(timestep_proj_torch)}, max: {torch.max(timestep_proj_torch)}"
)
logger.info(
f"tt shape: {tt_timestep_proj_torch.shape}, mean: {torch.mean(tt_timestep_proj_torch)}, std: {torch.std(tt_timestep_proj_torch)}, min: {torch.min(tt_timestep_proj_torch)}, max: {torch.max(tt_timestep_proj_torch)}"
)
assert_quality(temb_torch, tt_temb_torch, pcc=0.9999, relative_rmse=0.005) # expected RMSE =0.3%
assert_quality(timestep_proj_torch, tt_timestep_proj_torch, pcc=0.9999, relative_rmse=0.005) # expected RMSE =0.4%
# Investigate slightly lower PCC and higher RMSE when compared to using Wantransformer.context_embedder
assert_quality(
encoder_hidden_states_torch, tt_encoder_hidden_states_torch, pcc=0.9999, relative_rmse=0.01
) # expected RMSE =0.8%
@pytest.mark.parametrize("mesh_device", [(1, 1), (1, 2)], indirect=["mesh_device"])
@pytest.mark.parametrize(
("dictionary_size", "embedding_size", "batch_size", "sequence_length"),
[
(152064, 3584, 2, 512), # Qwen-2.5-VL
],
)
# @pytest.mark.parametrize("device_params", [{"fabric_config": ttnn.FabricConfig.FABRIC_1D}], indirect=True)
def test_embedding(
mesh_device: ttnn.MeshDevice, dictionary_size: int, embedding_size: int, batch_size: int, sequence_length: int
) -> None:
torch.manual_seed(0)
torch_model = torch.nn.Embedding(dictionary_size, embedding_size)
torch_model.eval()
tt_model = Embedding(dictionary_size, embedding_size, device=mesh_device, mesh_axis=1)
tt_model.load_torch_state_dict(torch_model.state_dict())
inp = torch.randint(0, dictionary_size, [batch_size, sequence_length])
tt_inp = tensor.from_torch(inp, device=mesh_device, dtype=ttnn.uint32)
out = torch_model.forward(inp)
tt_out = tt_model.forward(tt_inp)
tt_output_torch = tensor.to_torch(tt_out, mesh_axes=[None, None, 1])
assert_quality(out, tt_output_torch, pcc=0.999998, relative_rmse=0.003)