Trace-Anything / ta_layers.py
bigshanedogg's picture
Upload Trace-Anything (transformers port, original code)
653a5eb verified
Raw History Blame Contribute Delete
29 kB
# ruff: noqa
# fmt: off
"""Trace Anything, vendored verbatim from ByteDance-Seed/TraceAnything @ 54677b5e7bf11510c2e8c917a509988ad379f8eb (trace_anything/): the files below
concatenated, package-internal imports replaced by relative imports of the other vendored files (generated by scripts/model_ports/common/vendor_flat.py;
do not edit by hand). Code license: Apache-2.0."""
# ==============================================================================================================
# trace_anything/layers/blocks.py
# ==============================================================================================================
# Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
# This source code is licensed under the license found in the
# LICENSE file in the root directory of this source tree.
# Copyright (C) 2022-present Naver Corporation. All rights reserved.
# Licensed under CC BY-NC-SA 4.0 (non-commercial use only).
# --------------------------------------------------------
# Main encoder/decoder blocks
# --------------------------------------------------------
# References:
# timm
# https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/vision_transformer.py
# https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/layers/helpers.py
# https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/layers/drop.py
# https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/layers/mlp.py
# https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/layers/patch_embed.py
import collections.abc
from itertools import repeat
import math
import torch
import torch.nn as nn
from torch.nn.functional import scaled_dot_product_attention
from torch.nn.attention import SDPBackend
def _ntuple(n):
def parse(x):
if isinstance(x, collections.abc.Iterable) and not isinstance(x, str):
return x
return tuple(repeat(x, n))
return parse
to_2tuple = _ntuple(2)
def drop_path(
x, drop_prob: float = 0.0, training: bool = False, scale_by_keep: bool = True
):
"""Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks)."""
if drop_prob == 0.0 or not training:
return x
keep_prob = 1 - drop_prob
shape = (x.shape[0],) + (1,) * (
x.ndim - 1
) # work with diff dim tensors, not just 2D ConvNets
random_tensor = x.new_empty(shape).bernoulli_(keep_prob)
if keep_prob > 0.0 and scale_by_keep:
random_tensor.div_(keep_prob)
return x * random_tensor
class DropPath(nn.Module):
"""Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks)."""
def __init__(self, drop_prob: float = 0.0, scale_by_keep: bool = True):
super(DropPath, self).__init__()
self.drop_prob = drop_prob
self.scale_by_keep = scale_by_keep
def forward(self, x):
return drop_path(x, self.drop_prob, self.training, self.scale_by_keep)
def extra_repr(self):
return f"drop_prob={round(self.drop_prob,3):0.3f}"
class Mlp(nn.Module):
"""MLP as used in Vision Transformer, MLP-Mixer and related networks"""
def __init__(
self,
in_features,
hidden_features=None,
out_features=None,
act_layer=nn.GELU,
bias=True,
drop=0.0,
):
super().__init__()
out_features = out_features or in_features
hidden_features = hidden_features or in_features
bias = to_2tuple(bias)
drop_probs = to_2tuple(drop)
self.fc1 = nn.Linear(in_features, hidden_features, bias=bias[0])
self.act = act_layer()
self.drop1 = nn.Dropout(drop_probs[0])
self.fc2 = nn.Linear(hidden_features, out_features, bias=bias[1])
self.drop2 = nn.Dropout(drop_probs[1])
def forward(self, x):
x = self.fc1(x)
x = self.act(x)
x = self.drop1(x)
x = self.fc2(x)
x = self.drop2(x)
return x
class Attention(nn.Module):
def __init__(
self, dim, rope=None, num_heads=8, qkv_bias=False, attn_drop=0.0, proj_drop=0.0,
attn_mask=None, is_causal=False, attn_implementation="pytorch_naive",
attn_bias_for_inference_enabled=False,
):
super().__init__()
self.num_heads = num_heads
head_dim = dim // num_heads
self.scale = head_dim**-0.5
# use attention biasing to accommodate for longer sequences than during training
self.attn_bias_for_inference_enabled = attn_bias_for_inference_enabled
gamma = 1.0
train_seqlen = 20
inference_seqlen = 137
self.attn_bias_scale = head_dim**-0.5 * (gamma * math.log(inference_seqlen) / math.log(train_seqlen))**0.5
self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
self.dropout_p = attn_drop
self.attn_drop = nn.Dropout(attn_drop)
self.proj = nn.Linear(dim, dim)
self.proj_drop = nn.Dropout(proj_drop)
self.rope = rope
self.attn_mask = attn_mask
self.is_causal = is_causal
self.attn_implementation = attn_implementation
def forward(self, x, xpos):
B, N, C = x.shape
qkv = (
self.qkv(x)
.reshape(B, N, 3, self.num_heads, C // self.num_heads)
.transpose(1, 3)
)
q, k, v = [qkv[:, :, i] for i in range(3)]
# q,k,v = qkv.unbind(2) # make torchscript happy (cannot use tensor as tuple)
if self.rope is not None:
with torch.autocast(device_type=next(self.parameters()).device.type, dtype=torch.float32): # FIXME: for some reason Lightning didn't pick up torch.cuda.amp.custom_fwd when using bf16-true
q = self.rope(q, xpos) if xpos is not None else q
k = self.rope(k, xpos) if xpos is not None else k
if not self.training and self.attn_bias_for_inference_enabled:
scale = self.attn_bias_scale
else:
scale = self.scale
# Important: For the fusion Transformer, we forward through the attention with bfloat16 precision
# If you are not using this block for the fusion Transformer, you should double check the precision of the input and output
if self.attn_implementation == "pytorch_naive":
assert self.attn_mask is None, "attn_mask not supported for pytorch_naive implementation of scaled dot product attention"
assert self.is_causal is False, "is_causal not supported for pytorch_naive implementation of scaled dot product attention"
dtype = k.dtype
with torch.autocast("cuda", dtype=torch.bfloat16):
x = (q @ k.transpose(-2, -1)) * scale
x = x.softmax(dim=-1)
x = self.attn_drop(x)
if dtype == torch.float32: # if input was FP32, cast back to FP32
x = x.to(torch.float32)
x = (x @ v).transpose(1, 2).reshape(B, N, C)
x = self.proj(x)
x = self.proj_drop(x)
elif self.attn_implementation == "flash_attention":
with torch.nn.attention.sdpa_kernel(SDPBackend.FLASH_ATTENTION):
dtype = k.dtype
with torch.autocast("cuda", dtype=torch.bfloat16):
x = scaled_dot_product_attention(q, k, v, attn_mask=self.attn_mask, dropout_p=self.dropout_p, is_causal=self.is_causal, scale=scale)
if dtype == torch.float32: # if input was FP32, cast back to FP32
x = x.to(torch.float32)
x = x.transpose(1, 2).reshape(B, N, C)
x = self.proj(x)
x = self.proj_drop(x)
elif self.attn_implementation == "pytorch_auto":
with torch.nn.attention.sdpa_kernel([SDPBackend.EFFICIENT_ATTENTION,]):
dtype = k.dtype
with torch.autocast("cuda", dtype=torch.bfloat16):
x = scaled_dot_product_attention(q, k, v, attn_mask=self.attn_mask, dropout_p=self.dropout_p, is_causal=self.is_causal, scale=scale)
if dtype == torch.float32: # if input was FP32, cast back to FP32
x = x.to(torch.float32)
x = x.transpose(1, 2).reshape(B, N, C)
x = self.proj(x)
x = self.proj_drop(x)
else:
raise ValueError(f"Unknown attn_implementation: {self.attn_implementation}")
return x
class Block(nn.Module):
def __init__(
self,
dim,
num_heads,
mlp_ratio=4.0,
qkv_bias=False,
drop=0.0,
attn_drop=0.0,
drop_path=0.0,
act_layer=nn.GELU,
norm_layer=nn.LayerNorm,
rope=None,
attn_implementation="pytorch_naive",
attn_bias_for_inference_enabled=False,
):
super().__init__()
self.norm1 = norm_layer(dim)
self.attn = Attention(
dim,
rope=rope,
num_heads=num_heads,
qkv_bias=qkv_bias,
attn_drop=attn_drop,
proj_drop=drop,
attn_implementation=attn_implementation,
attn_bias_for_inference_enabled=attn_bias_for_inference_enabled,
)
# NOTE: drop path for stochastic depth, we shall see if this is better than dropout here
self.drop_path = DropPath(drop_path) if drop_path > 0.0 else nn.Identity()
self.norm2 = norm_layer(dim)
mlp_hidden_dim = int(dim * mlp_ratio)
self.mlp = Mlp(
in_features=dim,
hidden_features=mlp_hidden_dim,
act_layer=act_layer,
drop=drop,
)
def forward(self, x, xpos):
x = x + self.drop_path(self.attn(self.norm1(x), xpos))
x = x + self.drop_path(self.mlp(self.norm2(x)))
return x
class CrossAttention(nn.Module):
def __init__(
self, dim, rope=None, num_heads=8, qkv_bias=False, attn_drop=0.0, proj_drop=0.0, attn_mask=None, is_causal=False, attn_implementation="pytorch_naive"
):
super().__init__()
self.num_heads = num_heads
head_dim = dim // num_heads
self.scale = head_dim**-0.5
self.projq = nn.Linear(dim, dim, bias=qkv_bias)
self.projk = nn.Linear(dim, dim, bias=qkv_bias)
self.projv = nn.Linear(dim, dim, bias=qkv_bias)
self.dropout_p = attn_drop
self.attn_drop = nn.Dropout(attn_drop)
self.proj = nn.Linear(dim, dim)
self.proj_drop = nn.Dropout(proj_drop)
self.rope = rope
self.attn_mask = attn_mask
self.is_causal = is_causal
self.attn_implementation = attn_implementation
def forward(self, query, key, value, qpos, kpos):
B, Nq, C = query.shape
Nk = key.shape[1]
Nv = value.shape[1]
q = (
self.projq(query)
.reshape(B, Nq, self.num_heads, C // self.num_heads)
.permute(0, 2, 1, 3)
)
k = (
self.projk(key)
.reshape(B, Nk, self.num_heads, C // self.num_heads)
.permute(0, 2, 1, 3)
)
v = (
self.projv(value)
.reshape(B, Nv, self.num_heads, C // self.num_heads)
.permute(0, 2, 1, 3)
)
if self.rope is not None:
with torch.autocast(device_type=next(self.parameters()).device.type, dtype=torch.float32): # FIXME: for some reason Lightning didn't pick up torch.cuda.amp.custom_fwd when using bf16-true
q = self.rope(q, qpos) if qpos is not None else q
k = self.rope(k, kpos) if kpos is not None else k
if self.attn_implementation == "pytorch_naive":
assert self.attn_mask is None, "attn_mask not supported for pytorch_naive implementation of scaled dot product attention"
assert self.is_causal is False, "is_causal not supported for pytorch_naive implementation of scaled dot product attention"
attn = (q @ k.transpose(-2, -1)) * self.scale
attn = attn.softmax(dim=-1)
attn = self.attn_drop(attn)
x = (attn @ v).transpose(1, 2).reshape(B, Nq, C)
x = self.proj(x)
x = self.proj_drop(x)
elif self.attn_implementation == "flash_attention":
with torch.nn.attention.sdpa_kernel(SDPBackend.FLASH_ATTENTION):
# cast to BF16 to use flash_attention
q = q.to(torch.bfloat16)
k = k.to(torch.bfloat16)
v = v.to(torch.bfloat16)
x = scaled_dot_product_attention(q, k, v, attn_mask=self.attn_mask, dropout_p=self.dropout_p, is_causal=self.is_causal, scale=self.scale)
# cast back to FP32
x = x.to(torch.float32)
x = x.transpose(1, 2).reshape(B, Nq, C)
x = self.proj(x)
x = self.proj_drop(x)
else:
raise ValueError(f"Unknown attn_implementation: {self.attn_implementation}")
return x
class DecoderBlock(nn.Module):
def __init__(
self,
dim,
num_heads,
mlp_ratio=4.0,
qkv_bias=False,
drop=0.0,
attn_drop=0.0,
drop_path=0.0,
act_layer=nn.GELU,
norm_layer=nn.LayerNorm,
norm_mem=True,
rope=None,
attn_implementation="pytorch_naive",
):
super().__init__()
self.norm1 = norm_layer(dim)
self.attn = Attention(
dim,
rope=rope,
num_heads=num_heads,
qkv_bias=qkv_bias,
attn_drop=attn_drop,
proj_drop=drop,
attn_implementation=attn_implementation,
)
self.cross_attn = CrossAttention(
dim,
rope=rope,
num_heads=num_heads,
qkv_bias=qkv_bias,
attn_drop=attn_drop,
proj_drop=drop,
attn_implementation=attn_implementation,
)
self.drop_path = DropPath(drop_path) if drop_path > 0.0 else nn.Identity()
self.norm2 = norm_layer(dim)
self.norm3 = norm_layer(dim)
mlp_hidden_dim = int(dim * mlp_ratio)
self.mlp = Mlp(
in_features=dim,
hidden_features=mlp_hidden_dim,
act_layer=act_layer,
drop=drop,
)
self.norm_y = norm_layer(dim) if norm_mem else nn.Identity()
def forward(self, x, y, xpos, ypos):
x = x + self.drop_path(self.attn(self.norm1(x), xpos))
y_ = self.norm_y(y)
x = x + self.drop_path(self.cross_attn(self.norm2(x), y_, y_, xpos, ypos))
x = x + self.drop_path(self.mlp(self.norm3(x)))
return x, y
# patch embedding
class PositionGetter(object):
"""return positions of patches"""
def __init__(self):
self.cache_positions = {}
def __call__(self, b, h, w, device):
if not (h, w) in self.cache_positions:
x = torch.arange(w, device=device)
y = torch.arange(h, device=device)
self.cache_positions[h, w] = torch.cartesian_prod(y, x) # (h, w, 2)
pos = self.cache_positions[h, w].view(1, h * w, 2).expand(b, -1, 2).clone()
return pos
class PatchEmbed(nn.Module):
"""just adding _init_weights + position getter compared to timm.models.layers.patch_embed.PatchEmbed"""
def __init__(
self,
img_size=224,
patch_size=16,
in_chans=3,
embed_dim=768,
norm_layer=None,
flatten=True,
):
super().__init__()
img_size = to_2tuple(img_size)
patch_size = to_2tuple(patch_size)
self.img_size = img_size
self.patch_size = patch_size
self.grid_size = (img_size[0] // patch_size[0], img_size[1] // patch_size[1])
self.num_patches = self.grid_size[0] * self.grid_size[1]
self.flatten = flatten
self.proj = nn.Conv2d(
in_chans, embed_dim, kernel_size=patch_size, stride=patch_size
)
self.norm = norm_layer(embed_dim) if norm_layer else nn.Identity()
self.position_getter = PositionGetter()
def forward(self, x):
B, C, H, W = x.shape
torch._assert(
H == self.img_size[0],
f"Input image height ({H}) doesn't match model ({self.img_size[0]}).",
)
torch._assert(
W == self.img_size[1],
f"Input image width ({W}) doesn't match model ({self.img_size[1]}).",
)
x = self.proj(x)
pos = self.position_getter(B, x.size(2), x.size(3), x.device)
if self.flatten:
x = x.flatten(2).transpose(1, 2).contiguous() # BCHW -> BNC
x = self.norm(x)
return x, pos
def _init_weights(self):
w = self.proj.weight.data
torch.nn.init.xavier_uniform_(w.view([w.shape[0], -1]))
# ==============================================================================================================
# trace_anything/layers/pos_embed.py
# ==============================================================================================================
# Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
# This source code is licensed under the license found in the
# LICENSE file in the root directory of this source tree.
# Copyright (C) 2022-present Naver Corporation. All rights reserved.
# Licensed under CC BY-NC-SA 4.0 (non-commercial use only).
# --------------------------------------------------------
# Position embedding utils
# --------------------------------------------------------
import numpy as np
import torch
# --------------------------------------------------------
# 2D sine-cosine position embedding
# References:
# MAE: https://github.com/facebookresearch/mae/blob/main/util/pos_embed.py
# Transformer: https://github.com/tensorflow/models/blob/master/official/nlp/transformer/model_utils.py
# MoCo v3: https://github.com/facebookresearch/moco-v3
# --------------------------------------------------------
def get_2d_sincos_pos_embed(embed_dim, grid_size, n_cls_token=0):
"""
grid_size: int of the grid height and width
return:
pos_embed: [grid_size*grid_size, embed_dim] or [n_cls_token+grid_size*grid_size, embed_dim] (w/ or w/o cls_token)
"""
grid_h = np.arange(grid_size, dtype=np.float32)
grid_w = np.arange(grid_size, dtype=np.float32)
grid = np.meshgrid(grid_w, grid_h) # here w goes first
grid = np.stack(grid, axis=0)
grid = grid.reshape([2, 1, grid_size, grid_size])
pos_embed = get_2d_sincos_pos_embed_from_grid(embed_dim, grid)
if n_cls_token > 0:
pos_embed = np.concatenate(
[np.zeros([n_cls_token, embed_dim]), pos_embed], axis=0
)
return pos_embed
def get_2d_sincos_pos_embed_from_grid(embed_dim, grid):
assert embed_dim % 2 == 0
# use half of dimensions to encode grid_h
emb_h = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[0]) # (H*W, D/2)
emb_w = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[1]) # (H*W, D/2)
emb = np.concatenate([emb_h, emb_w], axis=1) # (H*W, D)
return emb
def get_1d_sincos_pos_embed_from_grid(embed_dim, pos):
"""
embed_dim: output dimension for each position
pos: a list of positions to be encoded: size (M,)
out: (M, D)
"""
assert embed_dim % 2 == 0
omega = np.arange(embed_dim // 2, dtype=float)
omega /= embed_dim / 2.0
omega = 1.0 / 10000**omega # (D/2,)
pos = pos.reshape(-1) # (M,)
out = np.einsum("m,d->md", pos, omega) # (M, D/2), outer product
emb_sin = np.sin(out) # (M, D/2)
emb_cos = np.cos(out) # (M, D/2)
emb = np.concatenate([emb_sin, emb_cos], axis=1) # (M, D)
return emb
# --------------------------------------------------------
# Interpolate position embeddings for high-resolution
# References:
# MAE: https://github.com/facebookresearch/mae/blob/main/util/pos_embed.py
# DeiT: https://github.com/facebookresearch/deit
# --------------------------------------------------------
def interpolate_pos_embed(model, checkpoint_model):
if "pos_embed" in checkpoint_model:
pos_embed_checkpoint = checkpoint_model["pos_embed"]
embedding_size = pos_embed_checkpoint.shape[-1]
num_patches = model.patch_embed.num_patches
num_extra_tokens = model.pos_embed.shape[-2] - num_patches
# height (== width) for the checkpoint position embedding
orig_size = int((pos_embed_checkpoint.shape[-2] - num_extra_tokens) ** 0.5)
# height (== width) for the new position embedding
new_size = int(num_patches**0.5)
# class_token and dist_token are kept unchanged
if orig_size != new_size:
print(
"Position interpolate from %dx%d to %dx%d"
% (orig_size, orig_size, new_size, new_size)
)
extra_tokens = pos_embed_checkpoint[:, :num_extra_tokens]
# only the position tokens are interpolated
pos_tokens = pos_embed_checkpoint[:, num_extra_tokens:]
pos_tokens = pos_tokens.reshape(
-1, orig_size, orig_size, embedding_size
).permute(0, 3, 1, 2)
pos_tokens = torch.nn.functional.interpolate(
pos_tokens,
size=(new_size, new_size),
mode="bicubic",
align_corners=False,
)
pos_tokens = pos_tokens.permute(0, 2, 3, 1).flatten(1, 2)
new_pos_embed = torch.cat((extra_tokens, pos_tokens), dim=1)
checkpoint_model["pos_embed"] = new_pos_embed
# ----------------------------------------------------------
# RoPE2D: RoPE implementation in 2D
# ----------------------------------------------------------
try:
from fast3r.croco.models.curope import cuRoPE2D
RoPE2D = cuRoPE2D
except ImportError:
print(
"Warning, cannot find cuda-compiled version of RoPE2D, using a slow pytorch version instead"
)
class RoPE2D(torch.nn.Module):
def __init__(self, freq=100.0, F0=1.0):
super().__init__()
self.base = freq
self.F0 = F0
self.cache = {}
def get_cos_sin(self, D, seq_len, device, dtype):
if (D, seq_len, device, dtype) not in self.cache:
inv_freq = 1.0 / (
self.base ** (torch.arange(0, D, 2).float().to(device) / D)
)
t = torch.arange(seq_len, device=device, dtype=inv_freq.dtype)
freqs = torch.einsum("i,j->ij", t, inv_freq).to(dtype)
freqs = torch.cat((freqs, freqs), dim=-1)
cos = freqs.cos() # (Seq, Dim)
sin = freqs.sin()
self.cache[D, seq_len, device, dtype] = (cos, sin)
return self.cache[D, seq_len, device, dtype]
@staticmethod
def rotate_half(x):
x1, x2 = x[..., : x.shape[-1] // 2], x[..., x.shape[-1] // 2 :]
return torch.cat((-x2, x1), dim=-1)
def apply_rope1d(self, tokens, pos1d, cos, sin):
assert pos1d.ndim == 2
cos = torch.nn.functional.embedding(pos1d, cos)[:, None, :, :]
sin = torch.nn.functional.embedding(pos1d, sin)[:, None, :, :]
return (tokens * cos) + (self.rotate_half(tokens) * sin)
def forward(self, tokens, positions):
"""
input:
* tokens: batch_size x nheads x ntokens x dim
* positions: batch_size x ntokens x 2 (y and x position of each token)
output:
* tokens after appplying RoPE2D (batch_size x nheads x ntokens x dim)
"""
assert (
tokens.size(3) % 2 == 0
), "number of dimensions should be a multiple of two"
D = tokens.size(3) // 2
assert positions.ndim == 3 and positions.shape[-1] == 2 # Batch, Seq, 2
cos, sin = self.get_cos_sin(
D, int(positions.max()) + 1, tokens.device, tokens.dtype
)
# split features into two along the feature dimension, and apply rope1d on each half
y, x = tokens.chunk(2, dim=-1)
y = self.apply_rope1d(y, positions[:, :, 0], cos, sin)
x = self.apply_rope1d(x, positions[:, :, 1], cos, sin)
tokens = torch.cat((y, x), dim=-1)
return tokens
# ==============================================================================================================
# trace_anything/layers/patch_embed.py
# ==============================================================================================================
# Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
# This source code is licensed under the license found in the
# LICENSE file in the root directory of this source tree.
# Copyright (C) 2024-present Naver Corporation. All rights reserved.
# Licensed under CC BY-NC-SA 4.0 (non-commercial use only).
#
# --------------------------------------------------------
# PatchEmbed implementation for DUST3R,
# in particular ManyAR_PatchEmbed that Handle images with non-square aspect ratio
# --------------------------------------------------------
import torch
def get_patch_embed(patch_embed_cls, img_size, patch_size, enc_embed_dim):
assert patch_embed_cls in ["PatchEmbedDust3R", "ManyAR_PatchEmbed"]
patch_embed = eval(patch_embed_cls)(img_size, patch_size, 3, enc_embed_dim)
return patch_embed
class PatchEmbedDust3R(PatchEmbed):
def forward(self, x, **kw):
B, C, H, W = x.shape
assert (
H % self.patch_size[0] == 0
), f"Input image height ({H}) is not a multiple of patch size ({self.patch_size[0]})."
assert (
W % self.patch_size[1] == 0
), f"Input image width ({W}) is not a multiple of patch size ({self.patch_size[1]})."
x = self.proj(x)
pos = self.position_getter(B, x.size(2), x.size(3), x.device)
if self.flatten:
x = x.flatten(2).transpose(1, 2) # BCHW -> BNC
x = self.norm(x)
return x, pos
class ManyAR_PatchEmbed(PatchEmbed):
"""Handle images with non-square aspect ratio.
All images in the same batch have the same aspect ratio.
true_shape = [(height, width) ...] indicates the actual shape of each image.
"""
def __init__(
self,
img_size=224,
patch_size=16,
in_chans=3,
embed_dim=768,
norm_layer=None,
flatten=True,
):
self.embed_dim = embed_dim
super().__init__(img_size, patch_size, in_chans, embed_dim, norm_layer, flatten)
def forward(self, img, true_shape):
B, C, H, W = img.shape
assert W >= H, f"img should be in landscape mode, but got {W=} {H=}"
assert (
H % self.patch_size[0] == 0
), f"Input image height ({H}) is not a multiple of patch size ({self.patch_size[0]})."
assert (
W % self.patch_size[1] == 0
), f"Input image width ({W}) is not a multiple of patch size ({self.patch_size[1]})."
assert true_shape.shape == (
B,
2,
), f"true_shape has the wrong shape={true_shape.shape}"
# size expressed in tokens
W //= self.patch_size[0]
H //= self.patch_size[1]
n_tokens = H * W
height, width = true_shape.T
is_landscape = width >= height
is_portrait = ~is_landscape
# linear projection, transposed if necessary
if is_landscape.any():
new_landscape_content = self.proj(img[is_landscape])
new_landscape_content = new_landscape_content.permute(0, 2, 3, 1).flatten(1, 2)
if is_portrait.any():
new_protrait_content = self.proj(img[is_portrait].swapaxes(-1, -2))
new_protrait_content = new_protrait_content.permute(0, 2, 3, 1).flatten(1, 2)
# allocate space for result and set the content
x = img.new_empty((B, n_tokens, self.embed_dim), dtype=next(self.named_parameters())[1].dtype) # dynamically set dtype based on the current precision
if is_landscape.any():
x[is_landscape] = new_landscape_content
if is_portrait.any():
x[is_portrait] = new_protrait_content
# allocate space for result and set the content
pos = img.new_empty((B, n_tokens, 2), dtype=torch.int64)
if is_landscape.any():
pos[is_landscape] = self.position_getter(1, H, W, pos.device).expand(is_landscape.sum(), -1, -1)
if is_portrait.any():
pos[is_portrait] = self.position_getter(1, W, H, pos.device).expand(is_portrait.sum(), -1, -1)
x = self.norm(x)
return x, pos