phanerozoic commited on
Commit
5eaa5db
·
verified ·
1 Parent(s): 93e9f92

argus.py: factor padding_mode through DPT blocks; add depth crop_border kwarg; batched correspond. README: align with shipped 3M cofiber detection head, drop FCOS framing, fix file sizes/param counts, add real IN1k val comparison, document qkv-bias choice. eval JSON: strip personal paths.

Browse files
Files changed (3) hide show
  1. README.md +42 -45
  2. argus.py +79 -27
  3. rf100vl_zero_shot_cross_domain_eval.json +4 -3
README.md CHANGED
@@ -26,7 +26,7 @@ metrics:
26
 
27
  # Argus
28
 
29
- Argus is a multi-task perception system built on a single compact vision backbone. From one forward pass through the encoder, the model produces classification labels, semantic segmentation masks, metric depth maps, object detections with bounding boxes, and dense keypoint correspondences, thereby collapsing five domain-specific pipelines into a unified package of roughly 116 million parameters. The system is named after Argus Panoptes, the many-eyed giant of Greek mythology who was tasked by Hera with watching over everything at once.
30
 
31
  The underlying backbone is [EUPE-ViT-B](https://huggingface.co/facebook/EUPE-ViT-B) (86M parameters), which was introduced in *Efficient Universal Perception Encoder* (Zhu et al., Meta FAIR, [arXiv:2603.22387](https://arxiv.org/abs/2603.22387), March 2026). That paper demonstrates that a small vision encoder can be distilled from a collection of larger specialist teachers, yielding features that transfer well to image understanding, dense prediction, and vision–language tasks simultaneously. Argus takes the released EUPE-ViT-B backbone, leaves its weights frozen, and attaches five lightweight task heads that were trained or constructed independently for this project.
32
 
@@ -38,14 +38,14 @@ Image → EUPE-ViT-B (frozen, 86M parameters) → shared features
38
  ├── Classification — trained linear softmax, 1000 ImageNet classes
39
  ├── Segmentation — linear head, 150 ADE20K classes
40
  ├── Depth — DPT multi-scale decoder, metric depth in meters (NYU Depth V2)
41
- ├── Detection — FCOS head with simple feature pyramid, 80 COCO classes
42
  └── Correspondence — training-free dense feature matching
43
  ```
44
 
45
  - **Classification** — trained linear softmax, a single `Linear(768, 1000)` layer with bias applied to the L2-normalized CLS token. 85.53% top-1 and 97.69% top-5 on ImageNet-1k val.
46
  - **Segmentation** — BatchNorm layer followed by a single 1×1 convolution, trained with the backbone held frozen throughout.
47
  - **Depth** — DPT (Dense Prediction Transformer) decoder hooking into four intermediate ViT layers (blocks 2, 5, 8, 11), fusing their features at four spatial scales via residual conv fusion, producing metric depth in meters via a 256-bin weighted sum over the 0.001 to 10 meter range. Improves RMSE by 8% over a linear probe on the same backbone (0.480 vs 0.520 on NYUv2 test), with abs_rel improving by 28%. Attempts to extend the depth head to outdoor scenes via mixed indoor/outdoor training with scale-and-shift invariant loss degraded indoor accuracy without producing a model that worked reliably across both domains; outdoor depth remains a known limitation.
48
- - **Detection** — FCOS-style anchor-free detector on a ViTDet-style simple feature pyramid that synthesizes five spatial scales (strides 8 through 128) from the backbone's stride-16 patch features, with shared four-layer convolutional towers for classification and box regression across all pyramid levels. Runs at 640-pixel input with letterbox padding; returns per-image lists of bounding boxes with class labels, confidence scores, and COCO class names.
49
  - **Correspondence** — no trained parameters. Source and target features are extracted from two images, upsampled to pixel resolution, and matched by cosine similarity at each source keypoint.
50
 
51
  ## Benchmarks
@@ -63,29 +63,26 @@ All four of the paper's reported benchmarks were reproduced as part of building
63
 
64
  The classification evaluation used the full 1.28-million-image ImageNet-1k training set as the kNN reference and the 50,000-image validation set as the query. The segmentation and depth heads were trained using the same linear-probe configurations described in the EUPE repository. Correspondence was evaluated on the SPair-71k test split at 512-pixel resolution across all 12,234 test pairs, for a total of 88,328 keypoints, with no failures during the run.
65
 
66
- The classification head reaches 85.53% top-1 and 97.69% top-5 on ImageNet-1k val. The kNN protocol used during the paper reproduction phase served as the baseline; the trained linear head superseded it and is the only classification method shipped in the current checkpoint.
67
-
68
- | Classification method | Top-1 | Top-5 |
69
- |-----------------------|----------|----------|
70
- | kNN (k=10, retired) | 84.07 % | 93.99 % |
71
- | Linear softmax | 85.53 % | 97.69 % |
72
 
73
  ### Detection
74
 
75
- The EUPE paper evaluates its backbone exclusively through minimal decoders linear probes, kNN, and training-free matching so that downstream performance can be attributed to the learned features rather than to the capacity of the head. The paper's three evaluation domains are image understanding, dense prediction, and visionlanguage modeling; detection is not among them. The same frozen-backbone protocol applies: the FCOS detection head is trained on COCO 2017 train (117,266 images) at 640-pixel input while the backbone weights remain fixed.
76
 
77
  Evaluation on COCO val2017 (5,000 images) with the standard pycocotools protocol:
78
 
79
- | Metric | Value |
80
- |--------|-------|
81
- | mAP@[0.5:0.95] | **41.0** |
82
- | mAP@0.50 | 64.8 |
83
- | mAP@0.75 | 43.2 |
84
- | mAP (small objects) | 21.4 |
85
- | mAP (medium objects) | 44.9 |
86
- | mAP (large objects) | 62.1 |
 
 
87
 
88
- FCOS with a fully-trained ResNet-50-FPN backbone all backbone weights learned end-to-end on COCO achieves 39.1 mAP on the same benchmark. The frozen EUPE-ViT-B backbone reaches 41.0 mAP while sharing its features across all five task heads. The backbone was never exposed to detection data; these are the same features that produce the classification, segmentation, depth, and correspondence results above.
89
 
90
  ### Depth Decoder Comparison (Development Reference)
91
 
@@ -120,15 +117,15 @@ The Cityscapes probe reaches 63.76% mIoU, with road at 96.4%, car at 87.9%, sky
120
 
121
  ## Comparison with Standard Baselines
122
 
123
- As a sanity check, Argus was compared against several well-known models on the same 200-image COCO subset. The classification comparison uses a keyword cross-reference between each model's top-k ImageNet predictions and the COCO ground-truth detection labels on those images, which provides a consistent yardstick across differently-trained models despite the label-space mismatch. **These hit rates measure agreement with COCO detection labels via keyword matching on the 200-image subset; they are not raw ImageNet accuracy.** For reference, all three classifiers exceed 80% top-1 on the full ImageNet validation set.
124
 
125
- **Classification** (hit rate against COCO detection labels, 200 images):
 
 
 
 
126
 
127
- | Model | Parameters | Top-1 hit | Top-5 hit | Latency | Peak VRAM |
128
- |--------------------|------------|-----------|-----------|---------|-----------|
129
- | Argus (EUPE-ViT-B) | 86 M | 38.5% | 61.0% | 13.1 ms | 0.34 GB |
130
- | ConvNeXt-Base | 89 M | 25.5% | 48.5% | 10.4 ms | 0.35 GB |
131
- | ResNet50 | 26 M | 23.0% | 46.0% | 8.4 ms | 0.12 GB |
132
 
133
  **Segmentation**:
134
 
@@ -154,12 +151,12 @@ The models chosen for this comparison were selected to match the quality tier of
154
 
155
  | Pipeline | Parameters | Latency per image | Tasks |
156
  |----------|-----------|-------------------|-------|
157
- | Argus unified | 116 M | 56 ms | 5 (classify, segment, depth, detect, correspond) |
158
  | Four separate models | 260 M | 68 ms | 4 (classify, segment, depth, detect) |
159
 
160
- The per-model breakdown for the separate pipeline is ConvNeXt-Base at 6 ms, SegFormer-B3 at 19 ms, Depth-Anything-V2-Base at 31 ms, and YOLO26l at 12 ms, summing to 68 ms when the tasks are run sequentially on the same image. Argus completes five tasks the same four plus keypoint correspondence, which the separate pipeline does not attempt in 56 ms from a single model load. The total parameter count for the separate pipeline is 260M across four independent weight sets, while Argus carries 116M in a single file.
161
 
162
- The throughput advantage comes from the shared backbone. Each of the four separate models pays the cost of encoding the image through its own network before producing task-specific output. Argus encodes the image once through EUPE-ViT-B and then routes the resulting features to five lightweight heads, each of which adds only a few milliseconds on top of the shared representation. The backbone forward pass is the dominant cost in both pipelines, and running it once rather than four times is where the 1.2x throughput improvement and 2.2x parameter reduction originate. The practical consequence for deployment is that Argus requires a single model download (447 MB), a single checkpoint load into VRAM (0.53 GB), and a single Python import, where the equivalent separate-model pipeline requires four downloads totaling over a gigabyte, four loads consuming over a gigabyte of VRAM if held concurrently, and four separate dependency trees to manage.
163
 
164
  ## Usage
165
 
@@ -176,7 +173,7 @@ top5 = model.classify(image, top_k=5) # trained linear softma
176
  seg = model.segment(image) # returns [H, W] class indices
177
  depth = model.depth(image) # returns [H, W] metric depth in meters
178
 
179
- # Detection runs at 640px with letterbox padding:
180
  dets = model.detect(image, score_thresh=0.3)
181
  # returns list of {"box": [x1,y1,x2,y2], "score": float, "label": int, "class_name": str}
182
 
@@ -230,7 +227,7 @@ paths = model.export_onnx("/path/to/out_dir", backbone_resolution=224, verify=Tr
230
  # paths["verification"] — max abs diff per component
231
  ```
232
 
233
- Classification (kNN over class prototypes) and correspondence run as post-processing on top of the backbone output and need no separate graph.
234
 
235
  For reduced VRAM on memory-constrained hardware, INT8 weight-only quantization is available via torchao. This quantizes the Linear weight matrices to INT8 while keeping activations in BF16, avoiding the outlier-channel problems that break naive INT8 quantization of ViT models:
236
 
@@ -263,12 +260,11 @@ The backbone is frozen for every task. Only the task heads are trained, and the
263
  | EUPE-ViT-B backbone | LVD-1689M (approximately 1.7 billion web images) | Meta FAIR (used here frozen) |
264
  | Segmentation head | ADE20K (20,210 training images, 2,000 validation images) | This repository, 40,000 iterations of linear-probe training |
265
  | Depth head | NYU Depth V2 (24,231 training images) | This repository, 38,400 iterations of linear-probe training |
266
- | Class prototypes (kNN) | ImageNet-1k (1.28 million training images) | This repository, mean CLS feature per class |
267
  | Linear softmax classifier | ImageNet-1k (1.28 million training images) | This repository, SGD over cached frozen features |
268
- | Detection head | COCO 2017 (117,266 training images, 80 classes) | This repository, FCOS with simple FPN, 8 epochs at 640px |
269
  | Correspondence | None (training-free) | — |
270
 
271
- The trainable heads sum to approximately 30.5M parameters (seg 117K + depth DPT 13.45M + linear classifier 769K + detection 16.14M). The unified `model.safetensors` is 443 MB.
272
 
273
  ### Precision variants
274
 
@@ -276,22 +272,24 @@ Two safetensors files with the same weights at different on-disk precision. Infe
276
 
277
  | File | Size | Load |
278
  |---|---|---|
279
- | `model.safetensors` | 334 MB | `AutoModel.from_pretrained("phanerozoic/argus", trust_remote_code=True)` |
280
- | `model.bf16_backbone.safetensors` | 170 MB | `AutoModel.from_pretrained("phanerozoic/argus", trust_remote_code=True, variant="bf16_backbone")` |
281
 
282
  Both files load into the same FP32 model in memory; PyTorch automatically upcasts the bfloat16 stored weights at construction time. The smaller variant saves download bandwidth and disk space but does not reduce inference VRAM.
283
 
284
  ### Architecture details
285
 
286
- **Segmentation head** is `BatchNorm2d(768) Conv2d(768, 150, 1×1)` 116,886 parameters, 1.4 MB on disk. Trained at 512×512 with cross-entropy loss, AdamW (lr 1e-3, weight decay 1e-3), WarmupOneCycleLR with 1500-step warmup, batch size 16.
 
 
287
 
288
  **Depth head** is a DPT multi-scale decoder that hooks into backbone blocks [2, 5, 8, 11] via PyTorch forward hooks, capturing intermediate representations without modifying the backbone. A reassemble stage projects each block's output from 768 to 256 channels via LayerNorm + Linear, reshapes to spatial grids, and rescales to four target strides (4, 8, 16, 32) via bilinear interpolation. A bottom-up fusion path combines these four scales through residual conv blocks with skip connections, progressively doubling spatial resolution from stride 32 to stride 2. A final conv head produces 256 depth-bin logits, outputting metric depth in meters via a bin-weighted sum. 13,450,000 parameters, ~51 MB on disk. Trained at 416×416 with SILog loss, AdamW (lr 1e-4, weight decay 1e-3), cosine schedule with 3% warmup, batch size 16, 38,400 iterations.
289
 
290
- **Class prototypes (kNN path)** are produced by running the frozen backbone over the full ImageNet-1k training set at 224×224 resolution, computing the mean L2-normalized CLS feature per class, and saving the resulting 1000×768 matrix. No training, just feature extraction. At inference, the kNN path normalizes the query CLS token and computes cosine similarity against the prototype matrix.
291
 
292
- **Linear softmax classifier** is a single `Linear(768, 1000)` layer with bias 769,000 parameters, about 3 MB on disk. Trained as a two-pass job: first the frozen backbone is run over the ImageNet-1k training set to cache a per-image CLS feature tensor (1,281,167 × 768, stored once at ~3.9 GB), then the linear layer is trained on the cached features alone. The training pass uses SGD with momentum 0.9, weight decay 0, batch size 4096, cosine schedule, 100 epochs, no augmentation, and the best checkpoint by validation top-1 is restored at the end. A small learning-rate sweep over `{0.5, 1.0, 3.0, 10.0, 30.0}` selects the best configuration; the L2-normalized CLS features and zero-initialized weights demand an unusually large learning rate to grow the weight scale to the point where softmax distributions become sharp. The best run used lr = 30.0 and produced 85.53% top-1 / 97.69% top-5 on ImageNet-1k val, beating the kNN protocol on both metrics.
293
 
294
- **Detection head** is an FCOS-style anchor-free detector on a ViTDet-style simple feature pyramid. The FPN takes the backbone's stride-16 spatial features and synthesizes five levels: P3 (stride 8, via transposed convolution), P4 (stride 16, identity with channel reduction), P5 (stride 32), P6 (stride 64), and P7 (stride 128), each with 256 channels and GroupNorm normalization. Two shared four-layer convolutional towers (classification and regression) with GroupNorm and GELU process each level, followed by three prediction heads: 80 classification channels, 4 box regression channels (left/top/right/bottom distances, exponentiated with learned per-level scale), and 1 centerness channel. 16,138,074 parameters total, 61.6 MB on disk. Trained at 640×640 with letterbox padding, focal loss (alpha 0.25, gamma 2.0) for classification, GIoU loss for boxes, BCE for centerness, AdamW (lr 1e-3, weight decay 1e-4), cosine schedule with 3% warmup, batch size 64, 8 epochs.
295
 
296
  **Correspondence** has no learned parameters. At inference time, dense patch features are extracted from both images, upsampled to 512×512 pixel resolution, and matched by cosine similarity per source keypoint.
297
 
@@ -301,9 +299,8 @@ Both files load into the same FP32 model in memory; PyTorch automatically upcast
301
  |------------------------------|---------------------------|-------------|
302
  | Segmentation (ADE20K) | 40,000 | ~5 hours |
303
  | Depth (NYU Depth V2) | 38,400 | ~3 hours |
304
- | Class prototypes (IN1k) | 1.28M images, single pass | ~45 minutes |
305
- | Linear classifier (IN1k) | 100 epochs × 313 steps | ~25 seconds (on cached features, extraction amortized with the kNN prototype pass) |
306
- | Detection (COCO 2017) | 8 epochs × 1,832 batches | ~6 hours at batch 64, 640px, FP32, frozen backbone |
307
  | DPT depth decoder (NYUv2) | 38,400 iterations | ~5.3 hours at batch 16, 416px, SILog loss, frozen backbone |
308
  | Correspondence (SPair) | training-free | — |
309
 
@@ -311,11 +308,11 @@ Training was done on a single 48 GB workstation GPU. Peak VRAM was approximately
311
 
312
  ### Why minimal heads
313
 
314
- The segmentation and classification heads follow the EUPE paper's evaluation principle: a minimal decoder isolates the backbone's contribution from the head's capacity. A Mask2Former-style segmentation head would produce higher mIoU, but those numbers would reflect the decoder as much as the features. The depth and detection heads are heavier. The DPT decoder fuses features from four intermediate ViT layers at multiple spatial scales; the FCOS head synthesizes a five-level feature pyramid from the backbone's stride-16 output. Depth requires multi-scale fusion to capture spatial gradients across a scene, and detection requires a feature pyramid to resolve objects that range from a dozen pixels to the full image. In both cases the backbone remains frozen and only the head is trained.
315
 
316
  ## Notes
317
 
318
- The segmentation head was trained on ADE20K's 150-class indoor-and-urban label space. The depth head was trained on NYU Depth v2 and is indoor-biased; outdoor metric depth should be treated as approximate. The detection head was trained on COCO 2017's 80-class label space at 640-pixel input; small-object detection (mAP 21.4) is the expected weakness because the stride-8 P3 level can only resolve objects roughly 12 pixels and larger at that resolution. Classification uses a trained linear softmax classifier that produces calibrated probabilities and reaches 85.53% top-1 on ImageNet-1k val.
319
 
320
  ## License
321
 
 
26
 
27
  # Argus
28
 
29
+ Argus is a multi-task perception system built on a single compact vision backbone. From one forward pass through the encoder, the model produces classification labels, semantic segmentation masks, metric depth maps, object detections with bounding boxes, and dense keypoint correspondences, collapsing five domain-specific pipelines into a unified package of roughly 103 million parameters (85.6M frozen backbone + 17.4M trained heads + buffers). The system is named after Argus Panoptes, the many-eyed giant of Greek mythology who was tasked by Hera with watching over everything at once.
30
 
31
  The underlying backbone is [EUPE-ViT-B](https://huggingface.co/facebook/EUPE-ViT-B) (86M parameters), which was introduced in *Efficient Universal Perception Encoder* (Zhu et al., Meta FAIR, [arXiv:2603.22387](https://arxiv.org/abs/2603.22387), March 2026). That paper demonstrates that a small vision encoder can be distilled from a collection of larger specialist teachers, yielding features that transfer well to image understanding, dense prediction, and vision–language tasks simultaneously. Argus takes the released EUPE-ViT-B backbone, leaves its weights frozen, and attaches five lightweight task heads that were trained or constructed independently for this project.
32
 
 
38
  ├── Classification — trained linear softmax, 1000 ImageNet classes
39
  ├── Segmentation — linear head, 150 ADE20K classes
40
  ├── Depth — DPT multi-scale decoder, metric depth in meters (NYU Depth V2)
41
+ ├── Detection — cofiber pyramid + CLIP-text-aligned cosine head, 80 COCO classes
42
  └── Correspondence — training-free dense feature matching
43
  ```
44
 
45
  - **Classification** — trained linear softmax, a single `Linear(768, 1000)` layer with bias applied to the L2-normalized CLS token. 85.53% top-1 and 97.69% top-5 on ImageNet-1k val.
46
  - **Segmentation** — BatchNorm layer followed by a single 1×1 convolution, trained with the backbone held frozen throughout.
47
  - **Depth** — DPT (Dense Prediction Transformer) decoder hooking into four intermediate ViT layers (blocks 2, 5, 8, 11), fusing their features at four spatial scales via residual conv fusion, producing metric depth in meters via a 256-bin weighted sum over the 0.001 to 10 meter range. Improves RMSE by 8% over a linear probe on the same backbone (0.480 vs 0.520 on NYUv2 test), with abs_rel improving by 28%. Attempts to extend the depth head to outdoor scenes via mixed indoor/outdoor training with scale-and-shift invariant loss degraded indoor accuracy without producing a model that worked reliably across both domains; outdoor depth remains a known limitation.
48
+ - **Detection** — anchor-free detector built on a parameter-free cofiber decomposition of the backbone's stride-16 spatial features (iterated downsample-then-subtract residuals at four scales, concatenated with a 64-dim sinusoidal positional embedding to give an 832-channel input). A 1x1 stem reduces to a 160-channel hidden representation; shared 9-layer cls/reg towers (5 ConvGN blocks plus 4 depthwise-residual blocks) feed three predictions per pyramid level: a CLIP-text-aligned cosine classifier with 80 frozen COCO text embeddings (`text_embed`), a learned `logit_scale` and per-class `cls_bias`, an LTRB box regressor with per-level learned scale, and a centerness branch. Runs at 768-pixel input with letterbox padding; returns per-image lists of bounding boxes with class labels, confidence scores, and COCO class names. ~3M trainable parameters.
49
  - **Correspondence** — no trained parameters. Source and target features are extracted from two images, upsampled to pixel resolution, and matched by cosine similarity at each source keypoint.
50
 
51
  ## Benchmarks
 
63
 
64
  The classification evaluation used the full 1.28-million-image ImageNet-1k training set as the kNN reference and the 50,000-image validation set as the query. The segmentation and depth heads were trained using the same linear-probe configurations described in the EUPE repository. Correspondence was evaluated on the SPair-71k test split at 512-pixel resolution across all 12,234 test pairs, for a total of 88,328 keypoints, with no failures during the run.
65
 
66
+ The shipped classification head is a trained linear softmax that reaches 85.53% top-1 and 97.69% top-5 on ImageNet-1k val. The kNN protocol shown in the EUPE-reproduction table above was the development baseline and is no longer exposed at the API level.
 
 
 
 
 
67
 
68
  ### Detection
69
 
70
+ The EUPE paper evaluates its backbone exclusively through minimal decoders, linear probes, kNN, and training-free matching, so that downstream performance can be attributed to the learned features rather than to the capacity of the head. The paper's three evaluation domains are image understanding, dense prediction, and vision-language modeling; detection is not among them. The same frozen-backbone protocol applies: the detection head is trained on COCO 2017 train (117,266 images) at 768-pixel input while the backbone weights remain fixed.
71
 
72
  Evaluation on COCO val2017 (5,000 images) with the standard pycocotools protocol:
73
 
74
+ | Metric | Hard NMS | Soft NMS |
75
+ |--------|---------:|---------:|
76
+ | mAP@[0.5:0.95] | **42.64** | **42.71** |
77
+ | mAP@0.50 | 65.70 | 65.67 |
78
+ | mAP@0.75 | 45.10 | 45.29 |
79
+ | mAP (small objects) | 22.3 | 22.3 |
80
+ | mAP (medium) | 48.3 | 48.4 |
81
+ | mAP (large) | 62.9 | 63.1 |
82
+
83
+ The full lineage of the cofiber-based detection head, including the analytical closed-form, evolved-circuit, dim-sweep, and person-specialist variants and the recipe ablations that took the same architecture from 24.6 to 42.64 mAP, lives in [phanerozoic/cofiber-detection](https://huggingface.co/phanerozoic/cofiber-detection).
84
 
85
+ Cross-domain transfer behavior on a 20-domain Roboflow 100 VL subset is recorded in `rf100vl_zero_shot_cross_domain_eval.json`: class-agnostic AR@100 averages 0.289 across the 20 domains, with the per-domain breakdown stored in the same file.
86
 
87
  ### Depth Decoder Comparison (Development Reference)
88
 
 
117
 
118
  ## Comparison with Standard Baselines
119
 
120
+ **Classification** on ImageNet-1k val (top-1 / top-5):
121
 
122
+ | Model | Parameters | Top-1 | Top-5 |
123
+ |--------------------|------------|---------|---------|
124
+ | Argus (EUPE-ViT-B) | 86 M | 85.53% | 97.69% |
125
+ | ConvNeXt-Base | 89 M | 83.85% | 96.74% |
126
+ | ResNet50 | 26 M | 80.86% | 95.43% |
127
 
128
+ ConvNeXt-Base and ResNet50 numbers are from the [torchvision pretrained-model accuracy table](https://docs.pytorch.org/vision/main/models.html) (`ConvNeXt_Base_Weights.IMAGENET1K_V1`, `ResNet50_Weights.IMAGENET1K_V2`). Argus is evaluated on the same 50,000-image ImageNet-1k validation set with center-crop preprocessing.
 
 
 
 
129
 
130
  **Segmentation**:
131
 
 
151
 
152
  | Pipeline | Parameters | Latency per image | Tasks |
153
  |----------|-----------|-------------------|-------|
154
+ | Argus unified | 103 M | 56 ms | 5 (classify, segment, depth, detect, correspond) |
155
  | Four separate models | 260 M | 68 ms | 4 (classify, segment, depth, detect) |
156
 
157
+ The per-model breakdown for the separate pipeline is ConvNeXt-Base at 6 ms, SegFormer-B3 at 19 ms, Depth-Anything-V2-Base at 31 ms, and YOLO26l at 12 ms, summing to 68 ms when the tasks are run sequentially on the same image. Argus completes five tasks, the same four plus keypoint correspondence which the separate pipeline does not attempt, in 56 ms from a single model load. The total parameter count for the separate pipeline is 260M across four independent weight sets, while Argus carries 103M in a single file.
158
 
159
+ The throughput advantage comes from the shared backbone. Each of the four separate models pays the cost of encoding the image through its own network before producing task-specific output. Argus encodes the image once through EUPE-ViT-B and then routes the resulting features to five lightweight heads, each of which adds only a few milliseconds on top of the shared representation. The backbone forward pass is the dominant cost in both pipelines, and running it once rather than four times is where the 1.2x throughput improvement and 2.2x parameter reduction originate. The practical consequence for deployment is that Argus requires a single model download (334 MB), a single checkpoint load into VRAM (0.53 GB), and a single Python import, where the equivalent separate-model pipeline requires four downloads totaling over a gigabyte, four loads consuming over a gigabyte of VRAM if held concurrently, and four separate dependency trees to manage.
160
 
161
  ## Usage
162
 
 
173
  seg = model.segment(image) # returns [H, W] class indices
174
  depth = model.depth(image) # returns [H, W] metric depth in meters
175
 
176
+ # Detection runs at 768px with letterbox padding:
177
  dets = model.detect(image, score_thresh=0.3)
178
  # returns list of {"box": [x1,y1,x2,y2], "score": float, "label": int, "class_name": str}
179
 
 
227
  # paths["verification"] — max abs diff per component
228
  ```
229
 
230
+ Correspondence has no learned parameters and runs as cosine-max on the backbone's spatial output, so it needs no separate graph.
231
 
232
  For reduced VRAM on memory-constrained hardware, INT8 weight-only quantization is available via torchao. This quantizes the Linear weight matrices to INT8 while keeping activations in BF16, avoiding the outlier-channel problems that break naive INT8 quantization of ViT models:
233
 
 
260
  | EUPE-ViT-B backbone | LVD-1689M (approximately 1.7 billion web images) | Meta FAIR (used here frozen) |
261
  | Segmentation head | ADE20K (20,210 training images, 2,000 validation images) | This repository, 40,000 iterations of linear-probe training |
262
  | Depth head | NYU Depth V2 (24,231 training images) | This repository, 38,400 iterations of linear-probe training |
 
263
  | Linear softmax classifier | ImageNet-1k (1.28 million training images) | This repository, SGD over cached frozen features |
264
+ | Detection head | COCO 2017 (117,266 training images, 80 classes) | This repository, cofiber pyramid + CLIP-text-aligned cosine classifier, frozen text embeddings |
265
  | Correspondence | None (training-free) | — |
266
 
267
+ The trainable heads sum to approximately 17.4M parameters (seg 117K + depth DPT 13.45M + linear classifier 769K + detection 3.04M). The unified `model.safetensors` is 412 MB.
268
 
269
  ### Precision variants
270
 
 
272
 
273
  | File | Size | Load |
274
  |---|---|---|
275
+ | `model.safetensors` | 412 MB | `AutoModel.from_pretrained("phanerozoic/argus", trust_remote_code=True)` |
276
+ | `model.bf16_backbone.safetensors` | 241 MB | `AutoModel.from_pretrained("phanerozoic/argus", trust_remote_code=True, variant="bf16_backbone")` |
277
 
278
  Both files load into the same FP32 model in memory; PyTorch automatically upcasts the bfloat16 stored weights at construction time. The smaller variant saves download bandwidth and disk space but does not reduce inference VRAM.
279
 
280
  ### Architecture details
281
 
282
+ **Backbone** is the 86M-parameter EUPE-ViT-B with a deliberate simplification: `qkv_bias` is set to `False` and `mask_k_bias` is set to `False`. The upstream EUPE-ViT-B release shipped a `qkv.bias_mask` buffer that is identically zero across all attention blocks, which makes the effective qkv bias zero everywhere through `masked_bias = bias * 0 = 0`. Argus drops the bias parameter entirely so the computation is bit-equivalent in fp32. The bf16 output drift this introduces is sub-ULP and is absorbed by every head except the DPT depth decoder, where it surfaces as roughly 2 cm of noise against a 0.39 m RMSE (well below the depth head's own metric floor). The Argus-Lite (ViT-S) variant uses the same `qkv_bias=False` setting; the Argus-Edge (ViT-T) variant restores `qkv_bias=True` and `mask_k_bias=True` because the upstream EUPE-ViT-T release ships a non-trivial bias and bias-mask, neither of which can be folded away.
283
+
284
+ **Segmentation head** is `BatchNorm2d(768) → Conv2d(768, 150, 1×1)`, 116,886 parameters, 1.4 MB on disk. Trained at 512×512 with cross-entropy loss, AdamW (lr 1e-3, weight decay 1e-3), WarmupOneCycleLR with 1500-step warmup, batch size 16.
285
 
286
  **Depth head** is a DPT multi-scale decoder that hooks into backbone blocks [2, 5, 8, 11] via PyTorch forward hooks, capturing intermediate representations without modifying the backbone. A reassemble stage projects each block's output from 768 to 256 channels via LayerNorm + Linear, reshapes to spatial grids, and rescales to four target strides (4, 8, 16, 32) via bilinear interpolation. A bottom-up fusion path combines these four scales through residual conv blocks with skip connections, progressively doubling spatial resolution from stride 32 to stride 2. A final conv head produces 256 depth-bin logits, outputting metric depth in meters via a bin-weighted sum. 13,450,000 parameters, ~51 MB on disk. Trained at 416×416 with SILog loss, AdamW (lr 1e-4, weight decay 1e-3), cosine schedule with 3% warmup, batch size 16, 38,400 iterations.
287
 
288
+ **Linear softmax classifier** is a single `Linear(768, 1000)` layer with bias, 769,000 parameters, about 3 MB on disk. Trained as a two-pass job: first the frozen backbone is run over the ImageNet-1k training set to cache a per-image CLS feature tensor (1,281,167 × 768, stored once at ~3.9 GB), then the linear layer is trained on the cached features alone. The training pass uses SGD with momentum 0.9, weight decay 0, batch size 4096, cosine schedule, 100 epochs, no augmentation, and the best checkpoint by validation top-1 is restored at the end. A small learning-rate sweep over `{0.5, 1.0, 3.0, 10.0, 30.0}` selects the best configuration; the L2-normalized CLS features and zero-initialized weights demand an unusually large learning rate to grow the weight scale to the point where softmax distributions become sharp. The best run used lr = 30.0 and produced 85.53% top-1 / 97.69% top-5 on ImageNet-1k val.
289
 
290
+ **Detection head** is an anchor-free per-pixel detector built on a parameter-free cofiber decomposition of the backbone's stride-16 spatial features. The cofiber decomposition iterates an avg-pool then bilinear-upsample-and-subtract residual at four scales (a Rocq/HoTT machine-checked exact decomposition in a semi-additive category, see `phanerozoic/cofiber-detection/CofiberDecomposition.v`), giving frequency-separated bands at strides 16, 32, 64, and 128 with no learned parameters and no FPN. Each band is concatenated with a 64-dim sinusoidal positional embedding (832-channel input per scale), passed through a per-scale GroupNorm and a shared 1x1 stem to 160 hidden channels; top-down lateral fusion combines coarser bands into finer ones, and a single stride-2 transposed convolution synthesizes the stride-8 P3 level from the stride-16 band. Each of the five resulting pyramid levels (strides 8, 16, 32, 64, 128) goes through split classification and regression towers, each tower being five standard 3x3 conv blocks (Conv3x3 + GroupNorm + activation) followed by four depthwise-residual blocks. The classifier projects the 160-dim tower output to a 768-dim space via `cls_project` and scores via cosine similarity against a frozen 80x768 `text_embed` matrix of COCO class names encoded with a CLIP ViT-L/14 8-prompt average; a learned `logit_scale` and per-class `cls_bias` sharpen the scores. The regressor predicts LTRB distances exponentiated with a per-level learned scale, and a centerness branch gates final scores. 2,975,067 parameters, 11.4 MB on disk.
291
 
292
+ Trained on COCO 2017 at 768x768 with letterbox padding for 16 epochs at batch 64, AdamW (lr 1e-3, weight decay 1e-4) cosine schedule with 3% warmup, ATSS target assignment, horizontal-flip augmentation, EMA decay 0.9998, and a `cls_project` initialized with the principal components of the text embedding (first 80 columns from the SVD of `text_embed`, remaining 80 columns a random orthogonal complement). After the main run, a 3-epoch partial fine-tune updates only `cls_project`, `cls_bias`, and `logit_scale` at lr 1e-4 with the towers and the cofiber path frozen, picking up +0.15 mAP. The 24.6 to 42.64 mAP path was entirely recipe and resolution: hidden width actually went down (192 to 160), and the cofiber decomposition itself never changed.
293
 
294
  **Correspondence** has no learned parameters. At inference time, dense patch features are extracted from both images, upsampled to 512×512 pixel resolution, and matched by cosine similarity per source keypoint.
295
 
 
299
  |------------------------------|---------------------------|-------------|
300
  | Segmentation (ADE20K) | 40,000 | ~5 hours |
301
  | Depth (NYU Depth V2) | 38,400 | ~3 hours |
302
+ | Linear classifier (IN1k) | 100 epochs × 313 steps | ~25 seconds on cached features held on GPU; ~45 minutes for the one-shot feature extraction pass over the full 1.28M training set if the cache is not already on disk |
303
+ | Detection (COCO 2017) | 8 epochs × 1,832 batches | ~6 hours at batch 64, 768px, FP32, frozen backbone |
 
304
  | DPT depth decoder (NYUv2) | 38,400 iterations | ~5.3 hours at batch 16, 416px, SILog loss, frozen backbone |
305
  | Correspondence (SPair) | training-free | — |
306
 
 
308
 
309
  ### Why minimal heads
310
 
311
+ The segmentation and classification heads follow the EUPE paper's evaluation principle: a minimal decoder isolates the backbone's contribution from the head's capacity. A Mask2Former-style segmentation head would produce higher mIoU, but those numbers would reflect the decoder as much as the features. The depth and detection heads are heavier. The DPT decoder fuses features from four intermediate ViT layers at multiple spatial scales; the cofiber detection head builds a five-level pyramid (strides 8 through 128) from the backbone's stride-16 output via a parameter-free decomposition rather than a learned FPN. Depth requires multi-scale fusion to capture spatial gradients across a scene, and detection requires a feature pyramid to resolve objects that range from a dozen pixels to the full image. In both cases the backbone remains frozen and only the head is trained.
312
 
313
  ## Notes
314
 
315
+ The segmentation head was trained on ADE20K's 150-class indoor-and-urban label space. The depth head was trained on NYU Depth v2 and is indoor-biased; outdoor metric depth should be treated as approximate. The detection head was trained on COCO 2017's 80-class label space at 768-pixel input; small-object detection is the expected weakness because the stride-8 P3 level can only resolve objects roughly 14 pixels and larger at that resolution. Classification uses a trained linear softmax classifier that produces calibrated probabilities and reaches 85.53% top-1 on ImageNet-1k val.
316
 
317
  ## License
318
 
argus.py CHANGED
@@ -1427,11 +1427,19 @@ N_PREFIX_TOKENS = 5 # 1 CLS + 4 register/storage tokens
1427
 
1428
 
1429
  class _ResidualConvUnit(nn.Module):
1430
- def __init__(self, dim: int):
 
 
 
 
 
 
 
 
1431
  super().__init__()
1432
- self.conv1 = nn.Conv2d(dim, dim, 3, padding=1, padding_mode="reflect", bias=False)
1433
  self.bn1 = nn.BatchNorm2d(dim)
1434
- self.conv2 = nn.Conv2d(dim, dim, 3, padding=1, padding_mode="reflect", bias=False)
1435
  self.bn2 = nn.BatchNorm2d(dim)
1436
  self.act = nn.GELU()
1437
 
@@ -1440,10 +1448,10 @@ class _ResidualConvUnit(nn.Module):
1440
 
1441
 
1442
  class _FeatureFusionBlock(nn.Module):
1443
- def __init__(self, dim: int, has_skip: bool = True):
1444
  super().__init__()
1445
- self.rcu1 = _ResidualConvUnit(dim)
1446
- self.rcu2 = _ResidualConvUnit(dim)
1447
  self.skip_proj = nn.Conv2d(dim, dim, 1) if has_skip else None
1448
 
1449
  def forward(self, x: Tensor, skip: Optional[Tensor] = None) -> Tensor:
@@ -1757,7 +1765,18 @@ class Argus(PreTrainedModel):
1757
  return [seg_maps[i] for i in range(len(images))]
1758
 
1759
  @torch.inference_mode()
1760
- def depth(self, image_or_images, resolution: int = 416, return_confidence: bool = False):
 
 
 
 
 
 
 
 
 
 
 
1761
  single, images = _normalize_image_input(image_or_images)
1762
  transform = make_eupe_transform(resolution)
1763
  batch = torch.stack([transform(img) for img in images]).to(self.device)
@@ -1790,6 +1809,11 @@ class Argus(PreTrainedModel):
1790
  depth_b = self.depth_head(inter_list, H, W)
1791
  std_b = None
1792
 
 
 
 
 
 
1793
  depth_b = F.interpolate(depth_b, size=(resolution, resolution), mode="bilinear", align_corners=False)
1794
  if std_b is not None:
1795
  std_b = F.interpolate(std_b, size=(resolution, resolution), mode="bilinear", align_corners=False)
@@ -1809,34 +1833,62 @@ class Argus(PreTrainedModel):
1809
  @torch.inference_mode()
1810
  def correspond(
1811
  self,
1812
- src_image: Image.Image,
1813
- tgt_image: Image.Image,
1814
  resolution: int = 512,
1815
  ):
1816
  """Dense patch correspondence between two images.
1817
 
1818
- Returns a dict with keys `matches` (numpy array of length grid*grid mapping
1819
- each source patch to its argmax target patch), `scores` (cosine similarity
1820
- at the match), and `grid` (the patch-grid side length).
 
 
 
 
 
 
 
1821
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1822
  transform = make_eupe_transform(resolution)
1823
- src_t = transform(src_image).unsqueeze(0).to(self.device)
1824
- tgt_t = transform(tgt_image).unsqueeze(0).to(self.device)
1825
 
1826
  with torch.autocast(self.device.type, dtype=torch.bfloat16, enabled=self.device.type == "cuda"):
1827
- oa = self.backbone.forward_features(src_t)
1828
- ob = self.backbone.forward_features(tgt_t)
1829
- pa = F.normalize(oa['x_norm_patchtokens'].float().squeeze(0), dim=-1)
1830
- pb = F.normalize(ob['x_norm_patchtokens'].float().squeeze(0), dim=-1)
1831
- sim = pa @ pb.t()
1832
- m = sim.argmax(dim=-1)
1833
- s = sim.max(dim=-1).values
1834
- grid = int(np.sqrt(pa.shape[0]))
1835
- return {
1836
- "matches": m.cpu().numpy(),
1837
- "scores": s.cpu().numpy(),
1838
- "grid": grid,
1839
- }
 
 
 
 
1840
 
1841
  @torch.inference_mode()
1842
  def detect(
 
1427
 
1428
 
1429
  class _ResidualConvUnit(nn.Module):
1430
+ """Two 3x3 conv + BatchNorm blocks with a residual connection. Padding
1431
+ mode is configurable: the Argus-B DPT depth head trains with reflect
1432
+ padding to avoid edge artifacts; Argus-Lite ships weights that were
1433
+ trained with zero padding (the PyTorch default), and switching pad
1434
+ modes at inference would create a small distribution shift in the
1435
+ edge regions. Variants pass `padding_mode` to keep their inference
1436
+ aligned with their training."""
1437
+
1438
+ def __init__(self, dim: int, padding_mode: str = "reflect"):
1439
  super().__init__()
1440
+ self.conv1 = nn.Conv2d(dim, dim, 3, padding=1, padding_mode=padding_mode, bias=False)
1441
  self.bn1 = nn.BatchNorm2d(dim)
1442
+ self.conv2 = nn.Conv2d(dim, dim, 3, padding=1, padding_mode=padding_mode, bias=False)
1443
  self.bn2 = nn.BatchNorm2d(dim)
1444
  self.act = nn.GELU()
1445
 
 
1448
 
1449
 
1450
  class _FeatureFusionBlock(nn.Module):
1451
+ def __init__(self, dim: int, has_skip: bool = True, padding_mode: str = "reflect"):
1452
  super().__init__()
1453
+ self.rcu1 = _ResidualConvUnit(dim, padding_mode=padding_mode)
1454
+ self.rcu2 = _ResidualConvUnit(dim, padding_mode=padding_mode)
1455
  self.skip_proj = nn.Conv2d(dim, dim, 1) if has_skip else None
1456
 
1457
  def forward(self, x: Tensor, skip: Optional[Tensor] = None) -> Tensor:
 
1765
  return [seg_maps[i] for i in range(len(images))]
1766
 
1767
  @torch.inference_mode()
1768
+ def depth(self, image_or_images, resolution: int = 416, return_confidence: bool = False,
1769
+ crop_border: bool = False):
1770
+ """Run the DPT depth decoder. Returns metric depth in meters at the
1771
+ input resolution.
1772
+
1773
+ ``crop_border=True`` strips a small border (``max(4, H/13)`` pixels per
1774
+ side) from the raw decoder output before bilinear-upsampling to the
1775
+ input resolution. Useful when this model is loaded with a backbone
1776
+ whose DPT decoder was trained with zero padding (the unshipped
1777
+ dev-fork behaviour), which leaves a systematic edge artifact. The
1778
+ canonical checkpoint uses reflect padding inside every DPT conv and
1779
+ does not need this crop, so the option defaults to ``False``."""
1780
  single, images = _normalize_image_input(image_or_images)
1781
  transform = make_eupe_transform(resolution)
1782
  batch = torch.stack([transform(img) for img in images]).to(self.device)
 
1809
  depth_b = self.depth_head(inter_list, H, W)
1810
  std_b = None
1811
 
1812
+ if crop_border:
1813
+ crop = max(4, depth_b.shape[2] // 13)
1814
+ depth_b = depth_b[:, :, crop:-crop, crop:-crop]
1815
+ if std_b is not None:
1816
+ std_b = std_b[:, :, crop:-crop, crop:-crop]
1817
  depth_b = F.interpolate(depth_b, size=(resolution, resolution), mode="bilinear", align_corners=False)
1818
  if std_b is not None:
1819
  std_b = F.interpolate(std_b, size=(resolution, resolution), mode="bilinear", align_corners=False)
 
1833
  @torch.inference_mode()
1834
  def correspond(
1835
  self,
1836
+ src_image,
1837
+ tgt_image,
1838
  resolution: int = 512,
1839
  ):
1840
  """Dense patch correspondence between two images.
1841
 
1842
+ Single-pair form: pass two `PIL.Image` instances. Returns a dict with
1843
+ keys `matches` (numpy array of length grid*grid mapping each source
1844
+ patch to its argmax target patch), `scores` (cosine similarity at the
1845
+ match), and `grid` (the patch-grid side length).
1846
+
1847
+ Batched form: pass two equally-sized lists/iterables of images. Returns
1848
+ a list of per-pair dicts in the same shape that a single call would
1849
+ produce. Both lists are forwarded through the backbone in two
1850
+ contiguous batches, so cross-pair throughput on GPU is much higher
1851
+ than calling `correspond` in a loop.
1852
  """
1853
+ single = isinstance(src_image, Image.Image) and isinstance(tgt_image, Image.Image)
1854
+ if single:
1855
+ srcs = [src_image]
1856
+ tgts = [tgt_image]
1857
+ else:
1858
+ srcs = list(src_image)
1859
+ tgts = list(tgt_image)
1860
+ if len(srcs) != len(tgts):
1861
+ raise ValueError(
1862
+ f"src_image and tgt_image must have the same length; "
1863
+ f"got {len(srcs)} and {len(tgts)}")
1864
+ if not srcs:
1865
+ raise ValueError("empty image list")
1866
+ for i, (a, b) in enumerate(zip(srcs, tgts)):
1867
+ if not isinstance(a, Image.Image) or not isinstance(b, Image.Image):
1868
+ raise TypeError(f"pair {i} must contain two PIL.Image instances")
1869
+
1870
  transform = make_eupe_transform(resolution)
1871
+ src_batch = torch.stack([transform(img) for img in srcs]).to(self.device)
1872
+ tgt_batch = torch.stack([transform(img) for img in tgts]).to(self.device)
1873
 
1874
  with torch.autocast(self.device.type, dtype=torch.bfloat16, enabled=self.device.type == "cuda"):
1875
+ oa = self.backbone.forward_features(src_batch)
1876
+ ob = self.backbone.forward_features(tgt_batch)
1877
+ pa_batch = F.normalize(oa['x_norm_patchtokens'].float(), dim=-1)
1878
+ pb_batch = F.normalize(ob['x_norm_patchtokens'].float(), dim=-1)
1879
+
1880
+ results = []
1881
+ for pa, pb in zip(pa_batch, pb_batch):
1882
+ sim = pa @ pb.t()
1883
+ m = sim.argmax(dim=-1)
1884
+ s = sim.max(dim=-1).values
1885
+ grid = int(np.sqrt(pa.shape[0]))
1886
+ results.append({
1887
+ "matches": m.cpu().numpy(),
1888
+ "scores": s.cpu().numpy(),
1889
+ "grid": grid,
1890
+ })
1891
+ return results[0] if single else results
1892
 
1893
  @torch.inference_mode()
1894
  def detect(
rf100vl_zero_shot_cross_domain_eval.json CHANGED
@@ -1,14 +1,15 @@
1
  {
2
  "picker": {
3
- "path": "/mnt/d/detection-heads/heads/cofiber_threshold/split_tower_5scale_160h_5std_4dw_ema_l14_16ep_768_cls_calib/checkpoint_final.pth",
 
4
  "n_params": 2975067,
5
  "text_embed_dim": 768
6
  },
7
  "fcos": {
8
- "path": "/mnt/d/_tmp/argus_fcos_head.pth",
9
  "n_params": 16138074
10
  },
11
- "cache_dir": "/home/zootest/datasets/rf100vl_val_cache_768",
12
  "resolution": 768,
13
  "score_thresh": 0.05,
14
  "max_per_image": 100,
 
1
  {
2
  "picker": {
3
+ "head": "argus_text_aligned_detection_head",
4
+ "config": "split_tower_5scale_160h_5std_4dw_ema_l14_16ep_768_cls_calib",
5
  "n_params": 2975067,
6
  "text_embed_dim": 768
7
  },
8
  "fcos": {
9
+ "head": "argus_fcos_detection_head",
10
  "n_params": 16138074
11
  },
12
+ "cache_dir": "rf100vl_val_cache_768",
13
  "resolution": 768,
14
  "score_thresh": 0.05,
15
  "max_per_image": 100,