Asymmetric encoders + MONA all 24 blocks + 1024-dim + hard negatives
Architecture changes: - Asymmetric DINOv3: WEB (drone) + SAT (satellite) with separate MONA - MONA on all 24 blocks per encoder (was last 12) - Remove projection, native 1024-dim retrieval space (was 512) - Total: 748M params, 17.6M trainable (2.35%) Hard negative memory bank: - MoCo-style FIFO queue of 4096 detached gallery embeddings - Each batch: B in-batch + Q queue negatives in InfoNCE - Queue updated after each forward pass Training config: - batch_size=8, grad_accum=8, effective_batch=64 - eval_every=1 (eval + train recall every epoch) - Max bs=24 with grad checkpointing on RTX 4090 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -297,14 +297,14 @@ class AsymmetricEncoder(nn.Module):
|
||||
lrsclip_path: str = "nn_models/LRSCLIP/DGTRS-CLIP-ViT-L-14.pt",
|
||||
init_gate: float = 0.7,
|
||||
baseline_mode: bool = False,
|
||||
shared_encoder: bool = True,
|
||||
embed_dim: int = 512,
|
||||
shared_encoder: bool = False,
|
||||
mona_bottleneck: int = 64,
|
||||
mona_last_n_blocks: int = 24,
|
||||
lora_rank: int = 4,
|
||||
device: str = "cuda",
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.embed_dim = embed_dim
|
||||
self.embed_dim = self.DINO_DIM # native 1024, no projection
|
||||
self.baseline_mode = baseline_mode
|
||||
self.shared_encoder = shared_encoder
|
||||
self.device = device
|
||||
@@ -313,20 +313,17 @@ class AsymmetricEncoder(nn.Module):
|
||||
if shared_encoder:
|
||||
self.image_encoder = DINOv3ViT.from_pretrained(dino_web_path)
|
||||
self._freeze(self.image_encoder)
|
||||
inject_mona_into_dinov3(self.image_encoder, bottleneck=mona_bottleneck)
|
||||
inject_mona_into_dinov3(self.image_encoder, bottleneck=mona_bottleneck, last_n_blocks=mona_last_n_blocks)
|
||||
LOGGER.info("Shared encoder mode: single DINOv3 WEB for drone + satellite")
|
||||
else:
|
||||
self.drone_encoder = DINOv3ViT.from_pretrained(dino_web_path)
|
||||
self.sat_encoder = DINOv3ViT.from_pretrained(dino_sat_path)
|
||||
self._freeze(self.drone_encoder)
|
||||
self._freeze(self.sat_encoder)
|
||||
inject_mona_into_dinov3(self.drone_encoder, bottleneck=mona_bottleneck)
|
||||
inject_mona_into_dinov3(self.sat_encoder, bottleneck=mona_bottleneck)
|
||||
inject_mona_into_dinov3(self.drone_encoder, bottleneck=mona_bottleneck, last_n_blocks=mona_last_n_blocks)
|
||||
inject_mona_into_dinov3(self.sat_encoder, bottleneck=mona_bottleneck, last_n_blocks=mona_last_n_blocks)
|
||||
LOGGER.info("Asymmetric encoder mode: DINOv3 WEB (drone) + DINOv3 SAT (satellite)")
|
||||
|
||||
# Projection: DINOv3 1024-dim -> embed_dim (512).
|
||||
self.image_projection = nn.Linear(self.DINO_DIM, embed_dim)
|
||||
|
||||
# Text encoder — official DGTRS architecture (frozen + LoRA).
|
||||
if not baseline_mode:
|
||||
self.text_encoder = load_dgtrs_text_encoder(lrsclip_path)
|
||||
@@ -335,11 +332,11 @@ class AsymmetricEncoder(nn.Module):
|
||||
else:
|
||||
self.text_encoder = None
|
||||
|
||||
# Shared text fusion MLP: 3×768 -> embed_dim (512).
|
||||
# Shared text fusion MLP: 3×768 -> 1024 (native DINOv3 dim).
|
||||
if not baseline_mode:
|
||||
self.text_fusion = TextFusionMLP(
|
||||
text_dim=self.TEXT_DIM,
|
||||
out_dim=embed_dim,
|
||||
out_dim=self.DINO_DIM,
|
||||
)
|
||||
|
||||
# Separate gated fusion for query and gallery branches.
|
||||
@@ -353,20 +350,16 @@ class AsymmetricEncoder(nn.Module):
|
||||
module.eval()
|
||||
|
||||
def encode_drone(self, images: torch.Tensor) -> torch.Tensor:
|
||||
"""Encode drone images with MONA adapters + projection. Returns [B, embed_dim]."""
|
||||
"""Encode drone images with MONA adapters. Returns [B, 1024]."""
|
||||
if self.shared_encoder:
|
||||
x = self.image_encoder(images)
|
||||
else:
|
||||
x = self.drone_encoder(images)
|
||||
return self.image_projection(x)
|
||||
return self.image_encoder(images)
|
||||
return self.drone_encoder(images)
|
||||
|
||||
def encode_satellite(self, images: torch.Tensor) -> torch.Tensor:
|
||||
"""Encode satellite images with MONA adapters + projection. Returns [B, embed_dim]."""
|
||||
"""Encode satellite images with MONA adapters. Returns [B, 1024]."""
|
||||
if self.shared_encoder:
|
||||
x = self.image_encoder(images)
|
||||
else:
|
||||
x = self.sat_encoder(images)
|
||||
return self.image_projection(x)
|
||||
return self.image_encoder(images)
|
||||
return self.sat_encoder(images)
|
||||
|
||||
def encode_text_levels(
|
||||
self,
|
||||
@@ -459,7 +452,6 @@ class AsymmetricEncoder(nn.Module):
|
||||
"model_state": self.state_dict(),
|
||||
"baseline_mode": self.baseline_mode,
|
||||
"shared_encoder": self.shared_encoder,
|
||||
"embed_dim": self.embed_dim,
|
||||
**extra,
|
||||
}
|
||||
tmp = path.with_suffix(path.suffix + ".tmp")
|
||||
@@ -494,8 +486,7 @@ class AsymmetricEncoder(nn.Module):
|
||||
dino_sat_path=dino_sat_path,
|
||||
lrsclip_path=lrsclip_path,
|
||||
baseline_mode=ckpt.get("baseline_mode", False),
|
||||
shared_encoder=ckpt.get("shared_encoder", True),
|
||||
embed_dim=ckpt.get("embed_dim", 512),
|
||||
shared_encoder=ckpt.get("shared_encoder", False),
|
||||
device=device,
|
||||
)
|
||||
model.load_state_dict(ckpt["model_state"], strict=False)
|
||||
|
||||
Reference in New Issue
Block a user