| """ |
| ProtoVAR Smoke Test |
| |
| A minimal test to verify the ProtoVAR implementation works correctly. |
| This test uses a small model and synthetic data for quick verification. |
| """ |
|
|
| import os |
| import sys |
| import time |
| import json |
| from pathlib import Path |
|
|
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
|
|
| |
| sys.path.insert(0, '/tmp/VAR') |
|
|
|
|
| class MiniVAR(nn.Module): |
| """ |
| Miniature VAR model for smoke testing. |
| |
| This is a simplified version of VAR that can run on CPU |
| without requiring pre-trained weights. |
| """ |
| def __init__( |
| self, |
| num_classes: int = 10, |
| embed_dim: int = 64, |
| num_heads: int = 4, |
| depth: int = 4, |
| vocab_size: int = 256, |
| patch_nums: tuple = (1, 2, 4, 8), |
| ): |
| super().__init__() |
| self.num_classes = num_classes |
| self.embed_dim = embed_dim |
| self.patch_nums = patch_nums |
| self.vocab_size = vocab_size |
| |
| |
| self.class_emb = nn.Embedding(num_classes + 1, embed_dim) |
| self.word_emb = nn.Embedding(vocab_size, embed_dim) |
| |
| |
| total_tokens = sum(pn**2 for pn in patch_nums) |
| self.pos_emb = nn.Embedding(total_tokens, embed_dim) |
| |
| |
| self.blocks = nn.ModuleList([ |
| nn.TransformerEncoderLayer( |
| d_model=embed_dim, |
| nhead=num_heads, |
| dim_feedforward=embed_dim * 4, |
| batch_first=True, |
| ) |
| for _ in range(depth) |
| ]) |
| |
| |
| self.head = nn.Linear(embed_dim, vocab_size) |
| |
| def forward(self, labels: torch.Tensor, tokens: torch.Tensor) -> torch.Tensor: |
| """Forward pass for training.""" |
| B = labels.shape[0] |
| |
| |
| cls_emb = self.class_emb(labels) |
| |
| |
| token_emb = self.word_emb(tokens) |
| |
| |
| x = torch.cat([cls_emb.unsqueeze(1), token_emb], dim=1) |
| |
| |
| pos = self.pos_emb(torch.arange(x.shape[1], device=x.device)) |
| x = x + pos.unsqueeze(0) |
| |
| |
| for block in self.blocks: |
| x = block(x) |
| |
| return self.head(x) |
| |
| @torch.no_grad() |
| def generate(self, class_label: int, num_samples: int = 1) -> torch.Tensor: |
| """Generate samples for a class.""" |
| self.eval() |
| |
| batch_size = num_samples |
| labels = torch.full((batch_size,), class_label, dtype=torch.long) |
| |
| |
| x = self.class_emb(labels).unsqueeze(1) |
| |
| generated_tokens = [] |
| |
| for scale_idx, pn in enumerate(self.patch_nums): |
| |
| for i in range(pn * pn): |
| |
| pos_idx = sum(p**2 for p in self.patch_nums[:scale_idx]) + i |
| pos = self.pos_emb(torch.tensor([pos_idx], device=x.device)) |
| |
| |
| out = self.head(x[:, -1:, :] + pos) |
| |
| |
| token = torch.argmax(out, dim=-1) |
| generated_tokens.append(token) |
| |
| |
| token_emb = self.word_emb(token) |
| x = torch.cat([x, token_emb], dim=1) |
| |
| return torch.cat(generated_tokens, dim=1) |
|
|
|
|
| class MiniPrototypeBank(nn.Module): |
| """Miniature prototype bank for smoke testing.""" |
| def __init__(self, num_classes: int = 10, embed_dim: int = 64, num_scales: int = 4, input_dim: int = 256): |
| super().__init__() |
| self.num_classes = num_classes |
| self.num_scales = num_scales |
| |
| self.prototypes = nn.Parameter(torch.randn(num_classes, num_scales, embed_dim) * 0.02) |
| self.projections = nn.ModuleList([nn.Linear(input_dim, embed_dim) for _ in range(num_scales)]) |
| |
| def get_guidance(self, class_idx: int, scale_idx: int) -> torch.Tensor: |
| return self.prototypes[class_idx, scale_idx] |
|
|
|
|
| def smoke_test(): |
| """Run smoke test.""" |
| print("=" * 60) |
| print("ProtoVAR Smoke Test") |
| print("=" * 60) |
| |
| device = 'cpu' |
| num_classes = 10 |
| |
| |
| print("\n[1/3] Creating mini VAR model...") |
| model = MiniVAR( |
| num_classes=num_classes, |
| embed_dim=64, |
| num_heads=4, |
| depth=4, |
| vocab_size=256, |
| patch_nums=(1, 2, 4, 8), |
| ) |
| print(f" Model parameters: {sum(p.numel() for p in model.parameters()) / 1e3:.1f}K") |
| |
| |
| print("\n[2/3] Creating prototype bank...") |
| proto_bank = MiniPrototypeBank( |
| num_classes=num_classes, |
| embed_dim=64, |
| num_scales=4, |
| input_dim=256, |
| ) |
| print(f" Prototype parameters: {sum(p.numel() for p in proto_bank.parameters()) / 1e3:.1f}K") |
| |
| |
| print("\n[3/3] Testing generation...") |
| model.eval() |
| |
| start_time = time.time() |
| all_images = [] |
| all_labels = [] |
| |
| for class_idx in range(num_classes): |
| |
| tokens = model.generate(class_label=class_idx, num_samples=5) |
| all_images.append(tokens) |
| all_labels.append(torch.full((5,), class_idx, dtype=torch.long)) |
| |
| |
| guidance = proto_bank.get_guidance(class_idx, scale_idx=0) |
| print(f" Class {class_idx}: generated {tokens.shape[0]} samples, guidance shape: {guidance.shape}") |
| |
| elapsed = time.time() - start_time |
| |
| |
| all_images = torch.cat(all_images, dim=0) |
| all_labels = torch.cat(all_labels, dim=0) |
| |
| print(f"\n Total generation time: {elapsed:.2f}s") |
| print(f" Generated dataset shape: {all_images.shape}") |
| print(f" Labels shape: {all_labels.shape}") |
| |
| |
| print("\n[4/4] Testing prototype matching...") |
| model.train() |
| |
| |
| dummy_labels = torch.randint(0, num_classes, (4,)) |
| dummy_tokens = torch.randint(0, 256, (4, 12)) |
| logits = model(dummy_labels, dummy_tokens) |
| |
| |
| total_loss = 0.0 |
| for scale_idx in range(4): |
| |
| projected = proto_bank.projections[scale_idx](logits.mean(dim=1)) |
| proto = proto_bank.prototypes[dummy_labels, scale_idx] |
| similarity = F.cosine_similarity(projected, proto, dim=-1) |
| loss = (1 - similarity).mean() |
| total_loss += loss |
| |
| print(f" Prototype matching loss: {total_loss.item():.4f}") |
| |
| |
| output_dir = './smoke_test_outputs' |
| os.makedirs(output_dir, exist_ok=True) |
| |
| results = { |
| 'model_params': sum(p.numel() for p in model.parameters()), |
| 'proto_params': sum(p.numel() for p in proto_bank.parameters()), |
| 'generation_time': elapsed, |
| 'dataset_shape': list(all_images.shape), |
| 'prototype_loss': total_loss.item(), |
| } |
| |
| with open(os.path.join(output_dir, 'smoke_test_results.json'), 'w') as f: |
| json.dump(results, f, indent=2) |
| |
| print("\n" + "=" * 60) |
| print("Smoke Test PASSED") |
| print("=" * 60) |
| print(f"Results saved to {output_dir}/smoke_test_results.json") |
| |
| return results |
|
|
|
|
| if __name__ == '__main__': |
| smoke_test() |
|
|