Problema e Manifestação
Ao integrar o modelo CrossFormer (transformador visual multiescala do ICLR 2022) ao sistema Backbone do Hugging Face Transformers, surgem erros de forma em tarefas de detecção ou segmentação: ```
from transformers import CrossFormerBackbone
backbone = CrossFormerBackbone.from_pretrained("crossformer-base") outputs = backbone(pixel_values) # pixel_values: (B, 3, 512, 512) feature_maps = outputs.feature_maps print([f.shape for f in feature_maps])
Resultados inesperados: - `ValueError: Esperado 4 níveis de feature map, recebido apenas 2`- `RuntimeError: Resolução esperada na camada 2 é (32, 32), mas foi (16, 16)`Ou, mesmo sem erro explícito, os resultados apresentam deslocamentos nos bounding boxes e perda total de objetos pequenos — indicando que as características multiescala não foram geradas conforme o esperado pelo "neck" da rede. O mais confuso é que a inferência individual funciona, o gradiente propaga-se normalmente, mas os métricas permanecem baixas. Esse tipo de falha estrutural — onde os dados estão numericamente corretos, mas semanticamente desalinhados — é difícil de depurar. Contexto Técnico
----------------
A inovação central do CrossFormer é o mecanismo de **atenção entre escalas (Cross-Scale Attention, CSA)**: ele gera múltiplas resoluções de tokens por meio de camadas convolucionais com diferentes passos (`ConvEmbed`) e, durante a atuação de atenção, combina informações locais (alta resolução) com globais (baixa resolução), otimizando custo computacional sem perder o campo de visão. Ao adaptá-lo como `Backbone`, diferentemente de ViT padrão, existem três diferenças críticas: 1. \*\*Embedding multiescala\*\*: Enquanto ViT usa um único tamanho de patch, CrossFormer produz quatro níveis de característica com resoluções distintas. 2. \*\*Ramificações Q/K em múltiplas escalas\*\*: Cada bloco de atenção possui duas pares de Q/K/V — uma para atenção local e outra para cruzamento entre escalas. Usar a atuação padrão de ViT ignora completamente o ramo de atenção entre escalas. 3. \*\*Contrato de saída\*\*: Modelos downstream (como MaskFormer, DETR, RetinaNet) dependem de `out\_features` ou `out\_indices` para definir quais níveis de saída usar. Se esses índices não mapearem corretamente aos quatro estágios do CrossFormer, o resultado será uma pirâmide de características desalinhada. A falha mais comum ocorre aqui: o mapeamento entre `out\_indices` e os estágios internos do modelo está incorreto, levando a número ou resolução erradas. Causa Raiz
----------
A raiz do problema é: \*\*a falta de alinhamento entre os níveis de saída definidos pelo usuário (`out\_features`) e os estágios reais do CrossFormer\*\*, combinada com a ausência de suporte para a atuação cruzada entre escalas quando usamos a implementação padrão de `ViTAttention`. Detalhes: 1. \*\*Mapeamento incorreto de índices\*\*: Os valores fornecidos em `out\_features` não são traduzidos corretamente para os quatro estágios internos. 2. \*\*Perda do ramo de atenção entre escalas\*\*: A substituição do mecanismo CSA por `ViTAttention` elimina a capacidade de capturar relações globais, reduzindo a expressividade sem causar falhas imediatas. 3. \*\*Cálculo errado de resolução\*\*: A taxa de downsampling em cada estágio do CrossFormer não segue a fórmula simples de `patch\_size`. Usar cálculos baseados em ViT resulta em resoluções falsas. Não é um problema de arquitetura, mas sim de contrato de interface entre backbone e downstream. Reprodução Mínima
-----------------
Simulação do erro causado por `out\_indices` mal configurado: ```
from dataclasses import dataclass
from typing import List
@dataclass
class FakeCrossFormer:
stage_resolutions: List[tuple] = None
def __post_init__(self):
self.stage_resolutions = [(128, 128), (64, 64), (32, 32), (16, 16)]
def backbone_forward(self, out_indices=(1, 2, 3)):
return [self.stage_resolutions[i] for i in out_indices]
model = FakeCrossFormer()
downstream_expects = 4
got = model.backbone_forward(out_indices=(1, 2, 3))
print("Esperado:", downstream_expects)
print("Obtido:", len(got))
print("Alinhado?", len(got) == downstream_expects) # False → erro de contagem
Saída: o modelo retorna apenas 3 níveis, mas o downstream espera 4 → falha no alinhamento da pirâmide. Solução (Nível 1: Correção Direta)
Garantir que o Backbone mapeie corretamente out\_features para os quatro estágios e calcule as resoluções com base nos fatores reais de downsampling do CrossFormer: ```
import torch
from transformers import PreTrainedModel
class CrossFormerBackbone(PreTrainedModel): def init(self, config): super().init(config) self.stage_indices = [0, 1, 2, 3] self.downsample_rates = [4, 8, 16, 32] # valores reais do CrossFormer
def forward(self, pixel_values, output_hidden_states=False, out_features=None):
out_features = out_features or ["stage1", "stage2", "stage3", "stage4"]
wanted = [int(f.replace("stage", "")) - 1 for f in out_features]
all_stages = self.encoder(pixel_values) # lista de 4 tensores
feature_maps = []
for idx in wanted:
feat = all_stages[idx] # (B, H, W, C)
feat = feat.permute(0, 3, 1, 2) # -> (B, C, H, W)
feature_maps.append(feat)
return {"feature_maps": feature_maps, "hidden_states": all_stages}
\*\*Importante:\*\* - Use `out\_features=\["stage1", "stage2", "stage3", "stage4"\]` para garantir todos os níveis. - As resoluções esperadas são `input\_size // 4, //8, //16, //32`. - Sempre faça o `permute` para `(B, C, H, W)`. Solução (Nível 2: Estruturação Centralizada)
--------------------------------------------
Para evitar futuras divergências, encapsule todas as regras do backbone em uma única fonte confiável: ```
from dataclasses import dataclass, field
from typing import Dict, List, Tuple
@dataclass
class CrossFormerBackbonePolicy:
stage_downsample: Dict[str, int] = field(default_factory=lambda: {
"stage1": 4, "stage2": 8, "stage3": 16, "stage4": 32
})
default_out_features: List[str] = field(
default_factory=lambda: ["stage1", "stage2", "stage3", "stage4"]
)
enable_cross_scale_attn: bool = True
def resolve_out_features(self, requested) -> List[str]:
feats = requested or self.default_out_features
for f in feats:
if f not in self.stage_downsample:
raise ValueError(f"Estágio inválido: {f}. Disponíveis: {list(self.stage_downsample)}")
return feats
def expected_resolution(self, stage: str, img_size: int) -> Tuple[int, int]:
r = self.stage_downsample[stage]
s = img_size // r
return (s, s)
def feature_map_spec(self, img_size: int) -> List[Tuple[str, Tuple[int, int]]]:
return [(f, self.expected_resolution(f, img_size)) for f in self.default_out_features]
Exemplo de uso: ``` policy = CrossFormerBackbonePolicy() spec = policy.feature_map_spec(img_size=512) for name, res in spec: print(name, res) # stage1 (128,128), ..., stage4 (16,16)
Benefícios: - Fonte única de verdade sobre os estágios. - Validação automática de entradas inválidas. - Facilidade de verificação em CI/CD. Solução (Nível 3: Testes Automatizados)
---------------------------------------
Adicione testes para validar o contrato de saída: ```
import pytest
from your_lib import CrossFormerBackbonePolicy
@pytest.fixture
def policy():
return CrossFormerBackbonePolicy()
def test_four_stages(policy):
assert len(policy.resolve_out_features(None)) == 4
def test_correct_resolutions(policy):
spec = policy.feature_map_spec(512)
expected = [
("stage1", (128, 128)),
("stage2", (64, 64)),
("stage3", (32, 32)),
("stage4", (16, 16))
]
assert spec == expected
def test_unknown_stage_raises(policy):
with pytest.raises(ValueError):
policy.resolve_out_features(["stage9"])
def test_feature_count_matches_request():
policy = CrossFormerBackbonePolicy()
wanted = policy.resolve_out_features(["stage2", "stage3"])
fake_outputs = [None] * 4
actual = [fake_outputs[int(w[-1]) - 1] for w in wanted]
assert len(actual) == len(wanted)
Esses testes devem rodar em CI para detectar regressões antes mesmo do merge. Checklist de Depuração
Quando o CrossFormer falhar após integração: 1. Verifique o número e shapes de feature\_maps. 2. Confirme que out\_features aponta para todos os 4 estágios. 3. Use fatores de downsampling reais (4/8/16/32), não estimativas ViT. 4. Garanta o permute para (B, C, H, W). 5. Certifique-se de que o ramo de atenção cruzada esteja implementado. 6. Revise os strides dos ConvEmbed — devem corresponder aos fatores de downsampling. 7. Verifique se o out\_features usado pelos modelos downstream (MaskFormer, DETR etc.) coincide com a política definida. Conclusão
O erro em conectar o CrossFormer como Backbone surge de um desalinhamento entre o contrato de saída esperado e a realidade multiescala do modelo. A solução envolve três camadas: 1. Mapeamento explícito de out\_features para estágios com resoluções corretas. 2. Centralização das regras em CrossFormerBackbonePolicy. 3. Validadores automáticos via testes. Frase final: qualquer backbone multiescala (Swin, PVT, Twins, CrossFormer) deve ter seu contrato de saída definido explicitamente, validado e mantido como documento único. Nunca use fórmulas genéricas baseadas em patch\_size — isso é a armadilha mais comum e silenciosa em integrações de modelos visionais modernos.