| # MAVT — Memory-Augmented Vision Tokenizer |
|
|
| A unified visual tokenizer for images, videos, and 3D assets operating in a shared 4D latent space. MAVT combines two architectural decisions: |
|
|
| - **4D Relational Graph Attention (RGAT)** — injects typed geometric relationships (spatial, temporal, cross-plane) directly into Transformer attention, replacing the implicit all-pairs weighting of standard self-attention with explicit structural priors. |
| - **Content-Detail Split** — decompresses the token stream into a semantic content channel and a high-frequency detail channel via learned slot attention, achieving ~2.9× token reduction without discarding information. |
|
|
| ~276M parameters. Designed for 8× A100/H100 DDP training across three modalities in a progressive curriculum. |
|
|
| --- |
|
|
| ## Architecture |
|
|
| ``` |
| Input (image / video / 3D triplane) |
| | |
| v |
| Stage 1: Patchify |
| Unified Conv3d (kernel 2x16x16, stride 2x16x16) |
| Image: causal zero-pad → equivalent to Conv2d |
| Video: direct application, bidirectional |
| 3D: per-plane patchification, plane_id tags |
| Output: tokens (B, N, 1152) + 4D positions (N, 4) |
| | |
| v |
| Stage 2: Hybrid Transformer-RGAT Backbone (12 blocks) |
| Blocks 0-3: StandardTransformer [SigLIP2 init] |
| Block 4: RGAT4D [zero-init output] |
| Blocks 5-7: StandardTransformer [SigLIP2 init] |
| Block 8: RGAT4D [zero-init output] |
| Blocks 9-11: StandardTransformer [SigLIP2 init] |
| |
| RGAT4D edge types (dense masked attention, no torch_geometric): |
| Type 0 SPATIAL same (t,z), |dx|<=2, |dy|<=2 |
| Type 1 TEMPORAL same (x,y,z), |dt|<=1 [video only] |
| Type 2 DEPTH reserved |
| Type 3 CROSS-PLANE different plane_id, shares>=1 coord [3D only] |
| | |
| v |
| Stage 3: Content-Detail Split |
| ContentExtractor (slot cross-attn, 2 layers) -> C tokens (N_c = 0.25*N) |
| Residual R = X - broadcast(C) |
| DynamicsPooler (slot cross-attn, 2 layers) -> D tokens (N_d = 0.10*N) |
| Output: [C ; D] (B, N_c+N_d, 1152) ~2.9x compression |
| | |
| v |
| Stage 4: Dual Latent Projection |
| VAE head: [C;D] -> mu, logvar -> z in R^32 (per token) |
| Semantic head: [C;D] -> attention pooling -> s in R^768 |
| | |
| v |
| Stage 5: Modality-Specific Decoder |
| UnifiedDetailExpander: cross-attn from target positions into z (2 layers) |
| 4x self-attention blocks (d=768) |
| 4-stage PixelShuffle CNN: 16x spatial upsample |
| | |
| v |
| Outputs: reconstructed pixel tensor + semantic embedding |
| ``` |
|
|
| ### Token counts (full resolution) |
|
|
| | Modality | Input N | After C-D Split | Compression | |
| |----------|---------|-----------------|-------------| |
| | Image 256px | 256 | 89 | 2.9x | |
| | Video 8f x 128px | 512 | 179 | 2.9x | |
| | 3D triplane S=64 | 48 | 24 | 2.0x | |
|
|
| --- |
|
|
| ## Installation |
|
|
| **Using uv (recommended):** |
| ```bash |
| bash setup_env.sh |
| source .venv/bin/activate |
| ``` |
|
|
| **Using pip directly:** |
| ```bash |
| pip install -e ".[dev]" |
| ``` |
|
|
| **Requirements:** Python >= 3.8, PyTorch >= 2.2, CUDA 12.x recommended. |
|
|
| --- |
|
|
| ## Quick Start |
|
|
| ### Smoke test (CPU, synthetic data, ~30 seconds) |
| ```bash |
| python3 smoke_test.py |
| ``` |
|
|
| Runs 20 unit tests covering RGAT zero-init, adjacency mask edge counts, C-D Split residual ratio, and full forward passes for all three modalities. |
|
|
| ### Training — Stage 1 (image only, synthetic data) |
| ```bash |
| python3 train.py fit \ |
| --config configs/train/stage1_image.yaml \ |
| --trainer.max_steps 500 \ |
| --trainer.accelerator gpu |
| ``` |
|
|
| ### Training — Stage 1 (real data, single GPU) |
| ```bash |
| python3 train.py fit \ |
| --config configs/train/stage1_image.yaml \ |
| --data.image_root /path/to/open-images |
| ``` |
|
|
| ### Training — Stage 1 (DDP, 8 GPUs) |
| ```bash |
| python3 train.py fit \ |
| --config configs/train/stage1_image.yaml \ |
| --trainer.devices 8 \ |
| --trainer.strategy ddp \ |
| --data.image_root /path/to/open-images |
| ``` |
|
|
| ### Training — Stage 2 (resume from stage 1 checkpoint) |
| ```bash |
| python3 train.py fit \ |
| --config configs/train/stage2_video.yaml \ |
| --ckpt_path checkpoints/stage1/mavt-stage1-best.ckpt \ |
| --data.image_root /path/to/open-images \ |
| --data.video_root /path/to/webvid |
| ``` |
|
|
| ### Training — Stage 3 (all modalities) |
| ```bash |
| python3 train.py fit \ |
| --config configs/train/stage3_3d.yaml \ |
| --ckpt_path checkpoints/stage2/mavt-stage2-best.ckpt \ |
| --data.image_root /path/to/open-images \ |
| --data.video_root /path/to/webvid \ |
| --data.threed_root /path/to/cap3d |
| ``` |
|
|
| ### Evaluation |
| ```bash |
| python3 evaluate.py \ |
| --ckpt checkpoints/stage1/mavt-stage1-best.ckpt \ |
| --modality image \ |
| --data_root /path/to/images \ |
| --resolution 256 |
| ``` |
|
|
| ### WandB logging |
| Append to any training command: |
| ```bash |
| --trainer.logger.class_path lightning.pytorch.loggers.WandbLogger \ |
| --trainer.logger.init_args.project mavt \ |
| --trainer.logger.init_args.name stage1-image |
| ``` |
|
|
| --- |
|
|
| ## Training Curriculum |
|
|
| Three progressive stages following the spec: |
|
|
| | Stage | Modalities | Steps | LR | SigLIP2 | Notes | |
| |-------|-----------|-------|----|---------|-------| |
| | 1 | Image | 200K | 1e-4 | Frozen | Establish spatial features | |
| | 2 | + Video | 200K | 5e-5 | Last 4 blocks unfrozen | Add temporal structure | |
| | 3 | + 3D | 50K | 2e-5 | Fully unfrozen | Cross-plane edges | |
|
|
| Resume across stages by passing `--ckpt_path` to the next stage's config. |
|
|
| --- |
|
|
| ## Configuration |
|
|
| All model and training hyperparameters are YAML-configurable via Lightning CLI. Override any field on the command line: |
|
|
| ```bash |
| # Change patch size |
| python3 train.py fit --config configs/train/stage1_image.yaml \ |
| --model.patch_size 8 |
| |
| # Change RGAT spatial radius |
| python3 train.py fit --config configs/train/stage1_image.yaml \ |
| --model.r_s 1 |
| |
| # Disable LPIPS (faster, less GPU memory) |
| python3 train.py fit --config configs/train/stage1_image.yaml \ |
| --model.use_lpips false |
| ``` |
|
|
| ### Key model parameters (`configs/model/mavt_base.yaml`) |
| |
| | Parameter | Default | Description | |
| |-----------|---------|-------------| |
| | `embed_dim` | 1152 | Token embedding dimension | |
| | `num_heads` | 16 | Attention heads | |
| | `num_blocks` | 12 | Backbone depth (RGAT at positions 4, 8) | |
| | `patch_size` | 16 | Spatial patch size in pixels | |
| | `latent_dim` | 32 | VAE latent dimension per token | |
| | `r_s` | 2 | RGAT spatial radius (5x5 window) | |
| | `r_t` | 1 | RGAT temporal radius (+-1 frame) | |
| | `use_gradient_checkpointing` | true | Saves ~30% GPU memory, +15% compute | |
|
|
| --- |
|
|
| ## Loss Function |
|
|
| ``` |
| L_total = w_mod * (w_l1 * L1 + w_lpips * LPIPS) |
| + w_kl * KL |
| + w_clip * InfoNCE(visual, text) [optional] |
| + w_aux * SlotDiversity |
| ``` |
|
|
| Default weights: `w_l1=1.0, w_lpips=0.1, w_kl=1e-4, w_clip=0.1, w_aux=0.01`. |
|
|
| `w_mod` is a per-modality inverse-EMA scale so harder modalities receive proportionally more gradient. |
|
|
| --- |
|
|
| ## C-D Split Monitoring |
|
|
| Three signals logged during training to detect failure modes: |
|
|
| | Metric | Target | Failure mode | |
| |--------|--------|-------------| |
| | `cd_slot_diversity` | <= 0.5 | > 0.9 → slot collapse | |
| | `cd_residual_ratio` | 0.3 – 0.5 | < 0.1 → content over-fits; > 0.7 → content fails | |
| | `detail_contribution` | >= 15% | Detail branch inactive | |
|
|
| --- |
|
|
| ## SigLIP2 Weight Initialization |
|
|
| To load pretrained SigLIP2 weights into the 10 Transformer backbone blocks: |
|
|
| ```bash |
| python3 train.py fit \ |
| --config configs/train/stage1_image.yaml \ |
| --model.init_siglip2 true \ |
| --model.siglip2_model_name google/siglip2-base-patch16-224 |
| ``` |
|
|
| This requires `pip install transformers` and HuggingFace Hub access. The two RGAT4D blocks are always randomly initialized with zero-init output projections (identity at step 0). |
|
|
| --- |
|
|
| ## Hardware Requirements |
|
|
| **Minimum (smoke test / development):** |
| - Any CPU, 4 GB RAM |
|
|
| **Recommended (full-scale training):** |
| - 8x A100 80GB or H100 80GB |
| - bf16 mixed precision |
| - Batch size: 32 image / 16 video / 32 3D per GPU |
| - Estimated wall-clock: ~10 days for 450K total steps |
|
|
| **Memory notes:** |
| - RGAT4D blocks operate on the full (B, N, N) attention matrix |
| - For video with N=2048, enable `use_gradient_checkpointing=true` and reduce batch size if OOM |
| - Adjacency masks are precomputed once per (modality, resolution) and cached on device |
|
|
| --- |
|
|
| ## Project Structure |
|
|
| ``` |
| Antoken/ |
| ├── pyproject.toml |
| ├── setup_env.sh |
| ├── train.py # LightningCLI entry point |
| ├── evaluate.py # Evaluation script |
| ├── smoke_test.py # 20 unit tests (no GPU required) |
| ├── quick_train_test.py # End-to-end training loop check |
| ├── configs/ |
| │ ├── model/mavt_base.yaml |
| │ └── train/ |
| │ ├── stage1_image.yaml |
| │ ├── stage2_video.yaml |
| │ └── stage3_3d.yaml |
| └── src/mavt/ |
| ├── model/ |
| │ ├── patchify.py # Conv3d patchification, 4D position grids |
| │ ├── rgat.py # RGAT4DBlock, build_adjacency |
| │ ├── transformer.py # StandardTransformerBlock (SigLIP2-compatible) |
| │ ├── backbone.py # 12-block hybrid backbone, mask caching |
| │ ├── content_detail_split.py # SlotPooler, ContentDetailSplit |
| │ ├── latent_heads.py # VAEHead, SemanticHead |
| │ ├── decoder.py # UnifiedDetailExpander, PixelShuffleCNNDecoder |
| │ └── mavt.py # Full MAVT model |
| ├── losses/losses.py # MAVTLoss, ModalityEMAWeighter, infonce_loss |
| ├── data/ |
| │ ├── datasets.py # Synthetic, ImageFolder, Video, ThreeD datasets |
| │ └── datamodule.py # MAVTDataModule (3-stage curriculum) |
| ├── training/ |
| │ └── lightning_module.py # MAVTLightningModule, LR schedule, visualization |
| └── evaluation/ |
| └── metrics.py # PSNR, SSIM, temporal-PSNR, FIDTracker |
| ``` |
|
|
| --- |
|
|
| ## Data Formats |
|
|
| | Modality | Dataset tensor shape | Source | |
| |----------|---------------------|--------| |
| | Image | `(3, H, W)` float32, normalized [-1, 1] | Open Images V7, any image folder | |
| | Video | `(3, T, H, W)` float32, normalized | WebVid, Panda-70M, HMDB51, MSVD | |
| | 3D triplane | `(3, 3, S, S)` float32 | Cap3D, TRELLIS-SLAT preprocessing | |
|
|
| Video reconstruction target is temporally downsampled to `(3, T//t_patch, H, W)` to match the patch-grid temporal resolution of the encoder output. |
|
|
| --- |
|
|
| ## Ablation Variants |
|
|
| As specified in section 10.2 of the design doc: |
|
|
| | Variant | RGAT | C-D Split | Run | |
| |---------|------|-----------|-----| |
| | V0 (baseline) | No | No | `--model.num_blocks 12` with standard Transformer only | |
| | V1 | No | Yes | Remove RGAT blocks from backbone | |
| | V2 | Yes | No | Set `content_ratio=1.0` to disable split | |
| | V3 (target) | Yes | Yes | Default config | |
|
|
| --- |
|
|
| ## 📌 MAVT Path — Step-by-Step Trace (rgat-demo branch) |
|
|
| > Detailed 3D-path documentation with code references, comparison vs AToken/LRM, and slide outline. |
| > See [`3D_pipeline_and_review.md`](3D_pipeline_and_review.md) for the full document. |
|
|
| ### 3D path code trace |
|
|
| ``` |
| GLB mesh → offline render → 3 plane PNG (oxoy/oxoz/oyoz, 256×256 RGB) |
| ↓ |
| UniversalThreeDDataset # src/mavt/data/datasets.py:174-228 |
| # → (B, 3, 3, 256, 256) in [-1, 1] |
| ↓ |
| PatchifyEncoder.forward_threed # src/mavt/model/patchify.py:134-175 |
| # → (B, 768, 768) tokens + positions (768, 4) + plane_ids (768,) |
| # Plane XY: pos = (0, x, y, 0) plane_id = 0 |
| # Plane XZ: pos = (0, x, 0, z) plane_id = 1 |
| # Plane YZ: pos = (0, 0, y, z) plane_id = 2 |
| ↓ |
| Hybrid Transformer + RGAT4D backbone (12 blocks) |
| # src/mavt/model/backbone.py + rgat.py |
| # 4 edge types: spatial (5×5) / temporal (|dt|≤1) / depth (reserved) / cross-plane (share ≥1 coord) |
| ↓ |
| ContentDetailSplit # src/mavt/model/content_detail_split.py:160-220 |
| # → 268 content slots + 192 detail tokens = 460 tokens |
| ↓ |
| VAEHead # src/mavt/model/latent_heads.py |
| # → 460 × 32 latent |
| ↓ |
| AsymmetricDecoder (3 plane loop, share weights) # src/mavt/model/decoder.py:170-405 |
| # Distance bias: implicit plane-aware reconstruction |
| # → (B, 3, 3, 256, 256) reconstruction |
| ``` |
|
|
| ### 3D vs other modalities |
|
|
| | Modality | Raw tokens | Compressed | Compression | |
| |---|---:|---:|---:| |
| | Image 256² | 256 | 128 | 48× | |
| | Video 16×256² | 2 048 | 1 024 | 96× | |
| | **3D 256² ×3 planes** | **768** | **460** | **13.4×** | |
|
|
| ### Comparison with related work |
|
|
| | Model | 3D repr | Params | Stage 3 3D | |
| |---|---|---:|---| |
| | AToken (Lu et al., arXiv:2509.14476) | triplane | 224 M | not reported | |
| | LRM (Hong et al., ICLR 2024) | triplane | 7 000 M | not reported | |
| | EG3D (Chan et al., CVPR 2022) | triplane | 80 M | n/a | |
| | **MAVT (rgat-demo)** | triplane | 224 M | **val/3d = 0.11** | |
|
|
| ### Known limitations |
|
|
| - 3D tested only on triplane renders (not raw GLB end-to-end). |
| - Cross-plane RGAT edge density high (~32 edges/token) — cap radius L1 ≤ 1 recommended. |
| - Video data is ~51% corrupt (loader handles). |
| - 30k 3D objects (vs AToken 1M+). |
| - 3D compression ratio (13.4×) lower than image (48×) and video (96×). |
| - No 3D mesh metrics (Chamfer, Volume IoU) yet — only per-plane PSNR/SSIM. |
|
|
| --- |
|
|
| ## 🎯 Stage 3 Training (rgat-demo branch, live) |
|
|
| | | | |
| |---|---| |
| | Job | Slurm 11841 on dgx01 (2× A100 DDP) | |
| | Status | RUNNING, 22k/50k steps | |
| | W&B | [banalaxis93/mavt/zbq1iqma](https://wandb.ai/banalaxis93/mavt/runs/zbq1iqma) | |
| | Init | Stage 1.5 step 120k (val/loss=0.124) | |
| | 3D data | 30,519 Objaverse triplanes + LVIS captions | |
|
|
| Latest metrics @ step 22k: |
|
|
| | Metric | image | video | threed | |
| |---|---:|---:|---:| |
| | val/loss | 0.081 | 0.197 | 0.110 | |
| | train/L1 | 0.044 | 0.110 | 0.032 | |
| | Semantic (1-cos) | 0.119 | — | — | |
|
|
| → Image already beats Stage 1.5 baseline (0.124) at 22k steps. |
| → 3D pooler converges 64% in 14k steps (0.30 → 0.11). |
|
|
|
|