Upload 162 files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- DeepForestVisionV2.pth +3 -0
- LICENSE.md +66 -0
- dinov3/.DS_Store +0 -0
- dinov3/__init__.py +6 -0
- dinov3/checkpointer/__init__.py +18 -0
- dinov3/checkpointer/checkpointer.py +352 -0
- dinov3/configs/__init__.py +16 -0
- dinov3/configs/config.py +222 -0
- dinov3/configs/ssl_default_config.yaml +205 -0
- dinov3/configs/train/dinov3_vit7b16_gram_anchor.yaml +203 -0
- dinov3/configs/train/dinov3_vit7b16_high_res_adapt.yaml +224 -0
- dinov3/configs/train/dinov3_vit7b16_pretrain.yaml +172 -0
- dinov3/configs/train/dinov3_vitl16_lvd1689m_distilled.yaml +251 -0
- dinov3/configs/train/multi_distillation_test.yaml +27 -0
- dinov3/configs/train/multidist_tests/vitb_p16.yaml +7 -0
- dinov3/configs/train/multidist_tests/vits_p16.yaml +6 -0
- dinov3/configs/train/vitl_im1k_lin834.yaml +143 -0
- dinov3/data/__init__.py +12 -0
- dinov3/data/adapters.py +68 -0
- dinov3/data/augmentations.py +227 -0
- dinov3/data/collate.py +125 -0
- dinov3/data/datasets/__init__.py +9 -0
- dinov3/data/datasets/ade20k.py +88 -0
- dinov3/data/datasets/coco_captions.py +88 -0
- dinov3/data/datasets/decoders.py +40 -0
- dinov3/data/datasets/extended.py +46 -0
- dinov3/data/datasets/image_net.py +297 -0
- dinov3/data/datasets/image_net_22k.py +301 -0
- dinov3/data/loaders.py +237 -0
- dinov3/data/masking.py +94 -0
- dinov3/data/meta_loaders.py +78 -0
- dinov3/data/samplers.py +229 -0
- dinov3/data/transforms.py +165 -0
- dinov3/distributed/__init__.py +24 -0
- dinov3/distributed/torch_distributed_primitives.py +100 -0
- dinov3/distributed/torch_distributed_wrapper.py +351 -0
- dinov3/env/__init__.py +33 -0
- dinov3/eval/.DS_Store +0 -0
- dinov3/eval/accumulators.py +77 -0
- dinov3/eval/data.py +256 -0
- dinov3/eval/dense/depth/models/__init__.py +186 -0
- dinov3/eval/dense/depth/models/dpt_head.py +532 -0
- dinov3/eval/dense/depth/models/embed.py +52 -0
- dinov3/eval/dense/depth/models/encoder.py +124 -0
- dinov3/eval/dense/depth/utils.py +19 -0
- dinov3/eval/detection/config.py +65 -0
- dinov3/eval/detection/models/__init__.py +5 -0
- dinov3/eval/detection/models/backbone.py +117 -0
- dinov3/eval/detection/models/detr.py +462 -0
- dinov3/eval/detection/models/global_ape_decoder.py +321 -0
DeepForestVisionV2.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:21f12d6a6610efd8c69f04302b6dc7cdec3c8e75a8306e70348469af60c2c44e
|
| 3 |
+
size 343226287
|
LICENSE.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# DINOv3 License
|
| 2 |
+
|
| 3 |
+
*Last Updated: August 19, 2025*
|
| 4 |
+
|
| 5 |
+
**“Agreement”** means the terms and conditions for use, reproduction, distribution and modification of the DINO Materials set forth herein.
|
| 6 |
+
|
| 7 |
+
**“DINO Materials”** means, collectively, Documentation and the models, software and algorithms, including machine-learning model code, trained model weights, inference-enabling code, training-enabling code, fine-tuning enabling code, and other elements of the foregoing distributed by Meta and made available under this Agreement.
|
| 8 |
+
|
| 9 |
+
**“Documentation”** means the specifications, manuals and documentation accompanying
|
| 10 |
+
DINO Materials distributed by Meta.
|
| 11 |
+
|
| 12 |
+
**“Licensee”** or **“you”** means you, or your employer or any other person or entity (if you are entering into this Agreement on such person or entity’s behalf), of the age required under applicable laws, rules or regulations to provide legal consent and that has legal authority to bind your employer or such other person or entity if you are entering in this Agreement on their behalf.
|
| 13 |
+
|
| 14 |
+
**“Meta”** or **“we”** means Meta Platforms Ireland Limited (if you are located in or, if you are an entity, your principal place of business is in the EEA or Switzerland) or Meta Platforms, Inc. (if you are located outside of the EEA or Switzerland).
|
| 15 |
+
|
| 16 |
+
**“Sanctions”** means any economic or trade sanctions or restrictions administered or enforced by the United States (including the Office of Foreign Assets Control of the U.S. Department of the Treasury (“OFAC”), the U.S. Department of State and the U.S. Department of Commerce), the United Nations, the European Union, or the United Kingdom.
|
| 17 |
+
|
| 18 |
+
**“Trade Controls”** means any of the following: Sanctions and applicable export and import controls.
|
| 19 |
+
|
| 20 |
+
By clicking “I Accept” below or by using or distributing any portion or element of the DINO Materials, you agree to be bound by this Agreement.
|
| 21 |
+
|
| 22 |
+
## 1. License Rights and Redistribution.
|
| 23 |
+
|
| 24 |
+
a. <ins>Grant of Rights</ins>. You are granted a non-exclusive, worldwide, non-transferable and royalty-free limited license under Meta’s intellectual property or other rights owned by Meta embodied in the DINO Materials to use, reproduce, distribute, copy, create derivative works of, and make modifications to the DINO Materials.
|
| 25 |
+
|
| 26 |
+
b. <ins>Redistribution and Use</ins>.
|
| 27 |
+
|
| 28 |
+
i. Distribution of DINO Materials, and any derivative works thereof, are subject to the terms of this Agreement. If you distribute or make the DINO Materials, or any derivative works thereof, available to a third party, you may only do so under the terms of this Agreement and you shall provide a copy of this Agreement with any such DINO Materials.
|
| 29 |
+
|
| 30 |
+
ii. If you submit for publication the results of research you perform on, using, or otherwise in connection with DINO Materials, you must acknowledge the use of DINO Materials in your publication.
|
| 31 |
+
|
| 32 |
+
iii. Your use of the DINO Materials must comply with applicable laws and regulations, including Trade Control Laws and applicable privacy and data protection laws.
|
| 33 |
+
|
| 34 |
+
iv. Your use of the DINO Materials will not involve or encourage others to reverse engineer, decompile or discover the underlying components of the DINO Materials.
|
| 35 |
+
|
| 36 |
+
v. You are not the target of Trade Controls and your use of DINO Materials must comply with Trade Controls. You agree not to use, or permit others to use, DINO Materials for any activities subject to the International Traffic in Arms Regulations (ITAR) or end uses prohibited by Trade Controls, including those related to military or warfare purposes, nuclear industries or applications, espionage, or the development or use of guns or illegal weapons.
|
| 37 |
+
|
| 38 |
+
## 2. User Support.
|
| 39 |
+
|
| 40 |
+
Your use of the DINO Materials is done at your own discretion; Meta does not process any information nor provide any service in relation to such use. Meta is under no obligation to provide any support services for the DINO Materials. Any support provided is “as is”, “with all faults”, and without warranty of any kind.
|
| 41 |
+
|
| 42 |
+
## 3. Disclaimer of Warranty.
|
| 43 |
+
|
| 44 |
+
UNLESS REQUIRED BY APPLICABLE LAW, THE DINO MATERIALS AND ANY OUTPUT AND RESULTS THEREFROM ARE PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OF ANY KIND, AND META DISCLAIMS ALL WARRANTIES OF ANY KIND, BOTH EXPRESS AND IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE APPROPRIATENESS OF USING OR REDISTRIBUTING THE DINO MATERIALS AND ASSUME ANY RISKS ASSOCIATED WITH YOUR USE OF THE DINO MATERIALS AND ANY OUTPUT AND RESULTS.
|
| 45 |
+
|
| 46 |
+
## 4. Limitation of Liability.
|
| 47 |
+
|
| 48 |
+
IN NO EVENT WILL META OR ITS AFFILIATES BE LIABLE UNDER ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, TORT, NEGLIGENCE, PRODUCTS LIABILITY, OR OTHERWISE, ARISING OUT OF THIS AGREEMENT, FOR ANY LOST PROFITS OR ANY DIRECT OR INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL, EXEMPLARY OR PUNITIVE DAMAGES, EVEN IF META OR ITS AFFILIATES HAVE BEEN ADVISED OF THE POSSIBILITY OF ANY OF THE FOREGOING.
|
| 49 |
+
|
| 50 |
+
## 5. Intellectual Property.
|
| 51 |
+
|
| 52 |
+
a. Subject to Meta’s ownership of DINO Materials and derivatives made by or for Meta, with respect to any derivative works and modifications of the DINO Materials that are made by you, as between you and Meta, you are and will be the owner of such derivative works and modifications.
|
| 53 |
+
|
| 54 |
+
b. If you institute litigation or other proceedings against Meta or any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the DINO Materials, outputs or results, or any portion of any of the foregoing, constitutes infringement of intellectual property or other rights owned or licensable by you, then any licenses granted to you under this Agreement shall terminate as of the date such litigation or claim is filed or instituted. You will indemnify and hold harmless Meta from and against any claim by any third party arising out of or related to your use or distribution of the DINO Materials.
|
| 55 |
+
|
| 56 |
+
## 6. Term and Termination.
|
| 57 |
+
|
| 58 |
+
The term of this Agreement will commence upon your acceptance of this Agreement or access to the DINO Materials and will continue in full force and effect until terminated in accordance with the terms and conditions herein. Meta may terminate this Agreement if you are in breach of any term or condition of this Agreement. Upon termination of this Agreement, you shall delete and cease use of the DINO Materials. Sections 3, 4 and 7 shall survive the termination of this Agreement.
|
| 59 |
+
|
| 60 |
+
## 7. Governing Law and Jurisdiction.
|
| 61 |
+
|
| 62 |
+
This Agreement will be governed and construed under the laws of the State of California without regard to choice of law principles, and the UN Convention on Contracts for the International Sale of Goods does not apply to this Agreement. The courts of California shall have exclusive jurisdiction of any dispute arising out of this Agreement.
|
| 63 |
+
|
| 64 |
+
## 8. Modifications and Amendments.
|
| 65 |
+
|
| 66 |
+
Meta may modify this Agreement from time to time; provided that they are similar in spirit to the current version of the Agreement, but may differ in detail to address new problems or concerns. All such changes will be effective immediately. Your continued use of the DINO Materials after any modification to this Agreement constitutes your agreement to such modification. Except as provided in this Agreement, no modification or addition to any provision of this Agreement will be binding unless it is in writing and signed by an authorized representative of both you and Meta.
|
dinov3/.DS_Store
ADDED
|
Binary file (10.2 kB). View file
|
|
|
dinov3/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
__version__ = "0.0.1"
|
dinov3/checkpointer/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
from .checkpointer import (
|
| 7 |
+
CheckpointRetentionPolicy,
|
| 8 |
+
cleanup_checkpoint,
|
| 9 |
+
find_all_checkpoints,
|
| 10 |
+
find_latest_checkpoint,
|
| 11 |
+
init_fsdp_model_from_checkpoint,
|
| 12 |
+
init_model_from_checkpoint_for_evals,
|
| 13 |
+
keep_checkpoint_copy,
|
| 14 |
+
keep_last_n_checkpoints,
|
| 15 |
+
load_checkpoint,
|
| 16 |
+
register_dont_save_hooks,
|
| 17 |
+
save_checkpoint,
|
| 18 |
+
)
|
dinov3/checkpointer/checkpointer.py
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
"""
|
| 7 |
+
Suggested file structure:
|
| 8 |
+
|
| 9 |
+
output_dir/
|
| 10 |
+
|-- ckpt/
|
| 11 |
+
| |-- 0/
|
| 12 |
+
| |-- 99/
|
| 13 |
+
| |-- 199/
|
| 14 |
+
| |-- 199_keep/
|
| 15 |
+
| |-- 299/
|
| 16 |
+
| `-- ...
|
| 17 |
+
`-- eval/
|
| 18 |
+
`-- 0/
|
| 19 |
+
`-- 99/
|
| 20 |
+
`-- ckpt/
|
| 21 |
+
|
| 22 |
+
Distributed checkpointer docs:
|
| 23 |
+
- https://pytorch.org/tutorials/recipes/distributed_checkpoint_recipe.html
|
| 24 |
+
- https://pytorch.org/docs/stable/distributed.checkpoint.html
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
import logging
|
| 28 |
+
import shutil
|
| 29 |
+
import subprocess
|
| 30 |
+
import tempfile
|
| 31 |
+
from enum import Enum
|
| 32 |
+
from pathlib import Path
|
| 33 |
+
from typing import List, Sequence, Set
|
| 34 |
+
|
| 35 |
+
import torch
|
| 36 |
+
import torch.distributed as dist
|
| 37 |
+
import torch.distributed.checkpoint as dcp
|
| 38 |
+
import torch.distributed.checkpoint.filesystem as dcpfs
|
| 39 |
+
import torch.distributed.checkpoint.state_dict as dcpsd
|
| 40 |
+
from torch.distributed.checkpoint.stateful import Stateful
|
| 41 |
+
|
| 42 |
+
logger = logging.getLogger("dinov3")
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class CheckpointRetentionPolicy(Enum):
|
| 46 |
+
ALL = "all" # keep all checkpoints
|
| 47 |
+
BEST = "best"
|
| 48 |
+
LAST = "last"
|
| 49 |
+
LAST_AND_BEST = "last_and_best"
|
| 50 |
+
NONE = "none" # do not keep any checkpoints
|
| 51 |
+
|
| 52 |
+
@property
|
| 53 |
+
def keep_filters(self) -> Set[str]:
|
| 54 |
+
"""Files that match these patterns are not deleted by cleanup"""
|
| 55 |
+
if self == CheckpointRetentionPolicy.LAST:
|
| 56 |
+
return set(["final"])
|
| 57 |
+
if self == CheckpointRetentionPolicy.BEST:
|
| 58 |
+
return set(["best"])
|
| 59 |
+
if self == CheckpointRetentionPolicy.LAST_AND_BEST:
|
| 60 |
+
return set(["final", "best"])
|
| 61 |
+
if self == CheckpointRetentionPolicy.ALL:
|
| 62 |
+
return set()
|
| 63 |
+
return set()
|
| 64 |
+
|
| 65 |
+
@property
|
| 66 |
+
def max_to_keep(self) -> int | None:
|
| 67 |
+
"""
|
| 68 |
+
maximum "periodic" checkpoints to keep concurrently, ie. saved with `step` and not `save`. `None` for keep all
|
| 69 |
+
"""
|
| 70 |
+
if self == CheckpointRetentionPolicy.ALL:
|
| 71 |
+
return None
|
| 72 |
+
return 1
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def save_checkpoint(
|
| 76 |
+
ckpt_dir: str | Path, # output_dir/ckpt/199
|
| 77 |
+
*,
|
| 78 |
+
iteration: int | str,
|
| 79 |
+
model: torch.nn.Module,
|
| 80 |
+
optimizer: torch.optim.Optimizer | None = None,
|
| 81 |
+
overwrite: bool = True,
|
| 82 |
+
process_group: dist.ProcessGroup = None,
|
| 83 |
+
**others: Stateful,
|
| 84 |
+
):
|
| 85 |
+
"""Save a plain/DDP/FSDP/FSDP2 model, its optimizer, an integer iteration and other stateful objects."""
|
| 86 |
+
rank = torch.distributed.get_rank(group=process_group)
|
| 87 |
+
|
| 88 |
+
# Rank 0 checks if the checkpoint directory exists, but all ranks need to know if if exists,
|
| 89 |
+
# so they can raise an error when overwrite is False. If overwrite is True, rank 0 will delete it
|
| 90 |
+
# and other ranks wait for the deletion to finish.
|
| 91 |
+
ckpt_dir = Path(ckpt_dir)
|
| 92 |
+
ckpt_dir_exists = [ckpt_dir.exists() if rank == 0 else None]
|
| 93 |
+
src_rank = 0
|
| 94 |
+
if process_group is not None:
|
| 95 |
+
src_rank = torch.distributed.get_global_rank(group=process_group, group_rank=0)
|
| 96 |
+
torch.distributed.broadcast_object_list(ckpt_dir_exists, src=src_rank, group=process_group)
|
| 97 |
+
ckpt_dir_exists = ckpt_dir_exists[0]
|
| 98 |
+
if ckpt_dir_exists:
|
| 99 |
+
if overwrite:
|
| 100 |
+
if rank == 0:
|
| 101 |
+
if ckpt_dir.is_dir():
|
| 102 |
+
shutil.rmtree(ckpt_dir)
|
| 103 |
+
else:
|
| 104 |
+
ckpt_dir.unlink()
|
| 105 |
+
logger.info(f"Deleted: {ckpt_dir}")
|
| 106 |
+
torch.distributed.barrier(group=process_group)
|
| 107 |
+
else:
|
| 108 |
+
raise RuntimeError(f"Checkpoint already exists: {ckpt_dir}")
|
| 109 |
+
|
| 110 |
+
# Rank 0 creates a temporary directory for the checkpoint and broadcasts the name to all ranks.
|
| 111 |
+
ckpt_dir.parent.mkdir(parents=True, exist_ok=True)
|
| 112 |
+
ckpt_dir_tmp = [tempfile.mkdtemp(dir=ckpt_dir.parent, prefix=ckpt_dir.name) if rank == 0 else None]
|
| 113 |
+
torch.distributed.broadcast_object_list(ckpt_dir_tmp, src=src_rank, group=process_group)
|
| 114 |
+
ckpt_dir_tmp = Path(ckpt_dir_tmp[0])
|
| 115 |
+
|
| 116 |
+
to_save = {"iteration": iteration}
|
| 117 |
+
to_save["model"] = dcpsd.get_model_state_dict(model)
|
| 118 |
+
if optimizer is not None:
|
| 119 |
+
to_save["optimizer"] = dcpsd.get_optimizer_state_dict(model, optimizer)
|
| 120 |
+
to_save.update(others)
|
| 121 |
+
dcp.save(
|
| 122 |
+
to_save,
|
| 123 |
+
storage_writer=dcpfs.FileSystemWriter(ckpt_dir_tmp),
|
| 124 |
+
process_group=process_group,
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
# Rank 0 renames the temporary directory to the final checkpoint directory. All ranks wait for the rename.
|
| 128 |
+
if rank == 0:
|
| 129 |
+
ckpt_dir_tmp.rename(ckpt_dir)
|
| 130 |
+
torch.distributed.barrier()
|
| 131 |
+
|
| 132 |
+
logger.info(f"Saved: {ckpt_dir}")
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def load_checkpoint(
|
| 136 |
+
ckpt_dir: str | Path, # output_dir/ckpt/199
|
| 137 |
+
*,
|
| 138 |
+
model: torch.nn.Module,
|
| 139 |
+
optimizer: torch.optim.Optimizer | None = None,
|
| 140 |
+
strict_loading: bool = True,
|
| 141 |
+
process_group: dist.ProcessGroup = None,
|
| 142 |
+
**others: Stateful,
|
| 143 |
+
) -> int | None:
|
| 144 |
+
"""
|
| 145 |
+
Load a plain/DDP/FSDP/FSDP2 model, its optimizer, an integer iteration and other stateful objects.
|
| 146 |
+
Can you take a checkpoint saved on N ranks and load it on M ranks? Sure you can!
|
| 147 |
+
Activation checkpointing and torch-compile can also be different between save and load, no problem.
|
| 148 |
+
"""
|
| 149 |
+
ckpt_dir = Path(ckpt_dir)
|
| 150 |
+
to_load = {"iteration": None}
|
| 151 |
+
to_load["model"] = dcpsd.get_model_state_dict(model)
|
| 152 |
+
if optimizer is not None:
|
| 153 |
+
to_load["optimizer"] = dcpsd.get_optimizer_state_dict(model, optimizer)
|
| 154 |
+
to_load.update(others)
|
| 155 |
+
dcp.load(
|
| 156 |
+
to_load,
|
| 157 |
+
storage_reader=dcpfs.FileSystemReader(ckpt_dir),
|
| 158 |
+
planner=dcp.default_planner.DefaultLoadPlanner(allow_partial_load=not strict_loading),
|
| 159 |
+
process_group=process_group,
|
| 160 |
+
)
|
| 161 |
+
iteration = to_load["iteration"]
|
| 162 |
+
dcpsd.set_model_state_dict(model, to_load["model"])
|
| 163 |
+
if optimizer is not None:
|
| 164 |
+
dcpsd.set_optimizer_state_dict(model, optimizer, to_load["optimizer"])
|
| 165 |
+
logger.info(f"Loaded: {ckpt_dir}")
|
| 166 |
+
return iteration
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def register_dont_save_hooks(module: torch.nn.Module, dont_save: Sequence[str]):
|
| 170 |
+
"""
|
| 171 |
+
Registers save/load state dict hooks such that the weights in `dont_save` are not persisted in the checkpoint.
|
| 172 |
+
|
| 173 |
+
Typical use case: a classification model composed of a frozen backbone and a trainable head.
|
| 174 |
+
If the frozen backbone is loaded from torch hub, it does't make sense to save a copy of it in each checkpoint.
|
| 175 |
+
"""
|
| 176 |
+
|
| 177 |
+
def state_dict_post_hook(module, state_dict, prefix, local_metadata):
|
| 178 |
+
# Remove frozen weights so they won't get saved.
|
| 179 |
+
# If this module is not the top-level module, its weights will have a prefix in the state dict.
|
| 180 |
+
nonlocal _dont_save
|
| 181 |
+
for k in _dont_save:
|
| 182 |
+
del state_dict[prefix + k]
|
| 183 |
+
|
| 184 |
+
def load_state_dict_pre_hook(
|
| 185 |
+
module,
|
| 186 |
+
state_dict,
|
| 187 |
+
prefix,
|
| 188 |
+
local_metadata,
|
| 189 |
+
strict,
|
| 190 |
+
missing_keys,
|
| 191 |
+
unexpected_keys,
|
| 192 |
+
error_msgs,
|
| 193 |
+
):
|
| 194 |
+
# This pre hook exists only to pass the prefix to the post hook when loading the state dict.
|
| 195 |
+
nonlocal _prefix
|
| 196 |
+
assert _prefix is None
|
| 197 |
+
_prefix = prefix
|
| 198 |
+
|
| 199 |
+
def load_state_dict_post_hook(module, incompatible_keys):
|
| 200 |
+
# Remove the frozen weights from the missing keys so they don't raise an error.
|
| 201 |
+
nonlocal _prefix
|
| 202 |
+
assert _prefix is not None
|
| 203 |
+
to_remove = []
|
| 204 |
+
for missing_key in incompatible_keys.missing_keys:
|
| 205 |
+
k = missing_key.removeprefix(_prefix)
|
| 206 |
+
k = k.replace("_checkpoint_wrapped_module.", "") # Added by activation checkpointing
|
| 207 |
+
if k in _dont_save:
|
| 208 |
+
to_remove.append(missing_key)
|
| 209 |
+
for r in to_remove:
|
| 210 |
+
incompatible_keys.missing_keys.remove(r)
|
| 211 |
+
_prefix = None
|
| 212 |
+
|
| 213 |
+
_dont_save = set(name.replace("_checkpoint_wrapped_module.", "") for name in dont_save)
|
| 214 |
+
_prefix = None
|
| 215 |
+
module.register_state_dict_post_hook(state_dict_post_hook)
|
| 216 |
+
module.register_load_state_dict_pre_hook(load_state_dict_pre_hook)
|
| 217 |
+
module.register_load_state_dict_post_hook(load_state_dict_post_hook)
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def find_all_checkpoints(ckpt_dir: Path | str) -> list[Path]:
|
| 221 |
+
"""Find all checkpoints in a directory, i.e. subdirs with integer name. Sorted from first to last."""
|
| 222 |
+
ckpt_dir = Path(ckpt_dir)
|
| 223 |
+
if not ckpt_dir.is_dir():
|
| 224 |
+
return []
|
| 225 |
+
checkpoints = [p for p in ckpt_dir.iterdir() if p.is_dir() and _is_int(p.name)]
|
| 226 |
+
checkpoints.sort(key=lambda p: int(p.name))
|
| 227 |
+
return checkpoints
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
def find_latest_checkpoint(ckpt_dir: Path | str) -> Path | None:
|
| 231 |
+
"""Find the latest checkpoint in a directory, i.e. the subdir with the highest integer name."""
|
| 232 |
+
checkpoints = find_all_checkpoints(ckpt_dir)
|
| 233 |
+
if len(checkpoints) == 0:
|
| 234 |
+
return None
|
| 235 |
+
return checkpoints[-1]
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def keep_last_n_checkpoints(ckpt_dir: Path | str, n: int | None):
|
| 239 |
+
"""In a directory with integer-named subdirs, keep only the n subdirs with the highest number."""
|
| 240 |
+
if n is None:
|
| 241 |
+
return
|
| 242 |
+
checkpoints = find_all_checkpoints(ckpt_dir)
|
| 243 |
+
for ckpt_dir in checkpoints[:-n]:
|
| 244 |
+
try:
|
| 245 |
+
shutil.rmtree(ckpt_dir)
|
| 246 |
+
logger.info(f"Deleted: {ckpt_dir}")
|
| 247 |
+
except Exception:
|
| 248 |
+
logger.exception(f"Failed to delete: {ckpt_dir}")
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def keep_checkpoint_copy(src: Path | str):
|
| 252 |
+
"""Copy a file/directory next to itself with a _keep suffix. Files are hardlinked."""
|
| 253 |
+
src = Path(src)
|
| 254 |
+
dst = src.parent / f"{src.name}_keep"
|
| 255 |
+
subprocess.check_output(["cp", "--recursive", "--link", src, dst])
|
| 256 |
+
logger.info(f"Copied: {src} -> {dst}")
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
def _is_int(s: str) -> bool:
|
| 260 |
+
try:
|
| 261 |
+
int(s)
|
| 262 |
+
return True
|
| 263 |
+
except ValueError:
|
| 264 |
+
return False
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
# Initialize a FSDP2 model from DCP or PyTorch standard checkpoint
|
| 268 |
+
def init_fsdp_model_from_checkpoint(
|
| 269 |
+
model: torch.nn.Module,
|
| 270 |
+
checkpoint_path: str,
|
| 271 |
+
skip_load_keys: List[str] | None = None,
|
| 272 |
+
keys_not_sharded: List[str] | None = None,
|
| 273 |
+
process_group: dist.ProcessGroup = None,
|
| 274 |
+
):
|
| 275 |
+
if not Path(checkpoint_path).is_dir(): # PyTorch standard checkpoint
|
| 276 |
+
logger.info(f"Loading pretrained weights from {checkpoint_path}")
|
| 277 |
+
chkpt = torch.load(checkpoint_path, map_location="cpu")["teacher"]
|
| 278 |
+
from torch.distributed.device_mesh import DeviceMesh, init_device_mesh
|
| 279 |
+
|
| 280 |
+
if process_group is None:
|
| 281 |
+
world_mesh = init_device_mesh(
|
| 282 |
+
"cuda",
|
| 283 |
+
mesh_shape=(dist.get_world_size(),),
|
| 284 |
+
mesh_dim_names=("dp",),
|
| 285 |
+
)
|
| 286 |
+
else:
|
| 287 |
+
world_mesh = DeviceMesh.from_group(process_group, "cuda")
|
| 288 |
+
chkpt = {
|
| 289 |
+
key: (
|
| 290 |
+
torch.distributed.tensor.distribute_tensor(tensor, world_mesh, src_data_rank=None)
|
| 291 |
+
if not any(key_not_sharded in key for key_not_sharded in keys_not_sharded)
|
| 292 |
+
else tensor
|
| 293 |
+
)
|
| 294 |
+
for key, tensor in chkpt.items()
|
| 295 |
+
}
|
| 296 |
+
model.load_state_dict(
|
| 297 |
+
{
|
| 298 |
+
key: tensor
|
| 299 |
+
for key, tensor in chkpt.items()
|
| 300 |
+
if not any(skip_load_key in key for skip_load_key in skip_load_keys)
|
| 301 |
+
}
|
| 302 |
+
)
|
| 303 |
+
else: # DCP checkpoint
|
| 304 |
+
load_checkpoint(ckpt_dir=checkpoint_path, model=model, process_group=process_group)
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
# Initialize a standard non distributed PyTorch model from PyTorch standard checkpoint for evals
|
| 308 |
+
def init_model_from_checkpoint_for_evals(
|
| 309 |
+
model: torch.nn.Module, pretrained_weights: str | Path, checkpoint_key: str = None
|
| 310 |
+
):
|
| 311 |
+
state_dict = torch.load(pretrained_weights, map_location="cpu")
|
| 312 |
+
if checkpoint_key is not None and checkpoint_key in state_dict:
|
| 313 |
+
logger.info(f"Take key {checkpoint_key} in provided checkpoint dict")
|
| 314 |
+
state_dict = state_dict[checkpoint_key]
|
| 315 |
+
# remove `module.` prefix
|
| 316 |
+
state_dict = {k.replace("module.", ""): v for k, v in state_dict.items()}
|
| 317 |
+
# remove `backbone.` prefix induced by multicrop wrapper
|
| 318 |
+
state_dict = {k.replace("backbone.", ""): v for k, v in state_dict.items()}
|
| 319 |
+
msg = model.load_state_dict(state_dict, strict=False)
|
| 320 |
+
logger.info("Pretrained weights found at {} and loaded with msg: {}".format(pretrained_weights, msg))
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
def cleanup_checkpoint(ckpt_dir: str, checkpoint_retention_policy: CheckpointRetentionPolicy):
|
| 324 |
+
"""
|
| 325 |
+
ckpt_dir is the directory containing each individual checkpoint directories (either at iteration, best (validation performance) or final)
|
| 326 |
+
|-- ckpt_dir/
|
| 327 |
+
| |-- 0/
|
| 328 |
+
| |--checkpoint.pth or dcp_sharded_checkpoint_dir
|
| 329 |
+
| |-- 99/
|
| 330 |
+
|--checkpoint.pth or dcp_sharded_checkpoint_dir
|
| 331 |
+
| |-- 199/
|
| 332 |
+
|--checkpoint.pth or dcp_sharded_checkpoint_dir
|
| 333 |
+
| |-- best/
|
| 334 |
+
|--checkpoint.pth or dcp_sharded_checkpoint_dir
|
| 335 |
+
| |-- 299/
|
| 336 |
+
|--checkpoint.pth or dcp_sharded_checkpoint_dir
|
| 337 |
+
| |-- final/
|
| 338 |
+
|--checkpoint.pth or dcp_sharded_checkpoint_dir
|
| 339 |
+
"""
|
| 340 |
+
ckpt_dir = Path(ckpt_dir)
|
| 341 |
+
if not ckpt_dir.is_dir():
|
| 342 |
+
return []
|
| 343 |
+
checkpoint_filters = checkpoint_retention_policy.keep_filters
|
| 344 |
+
checkpoints = [p for p in ckpt_dir.iterdir() if p.is_dir()]
|
| 345 |
+
for checkpoint in checkpoints:
|
| 346 |
+
if checkpoint in checkpoint_filters:
|
| 347 |
+
continue
|
| 348 |
+
try:
|
| 349 |
+
shutil.rmtree(checkpoint)
|
| 350 |
+
logger.info(f"Deleted: {checkpoint}")
|
| 351 |
+
except Exception:
|
| 352 |
+
logger.exception(f"Failed to delete: {checkpoint}")
|
dinov3/configs/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
from .config import (
|
| 7 |
+
DinoV3SetupArgs,
|
| 8 |
+
apply_scaling_rules_to_cfg,
|
| 9 |
+
exit_job,
|
| 10 |
+
get_cfg_from_args,
|
| 11 |
+
get_default_config,
|
| 12 |
+
setup_config,
|
| 13 |
+
setup_job,
|
| 14 |
+
setup_multidistillation,
|
| 15 |
+
write_config,
|
| 16 |
+
)
|
dinov3/configs/config.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import logging
|
| 7 |
+
import math
|
| 8 |
+
import os
|
| 9 |
+
import pathlib
|
| 10 |
+
import sys
|
| 11 |
+
from dataclasses import dataclass, field
|
| 12 |
+
from datetime import timedelta
|
| 13 |
+
from typing import Any, List, Optional, Sequence, Tuple
|
| 14 |
+
|
| 15 |
+
from omegaconf import DictConfig, OmegaConf
|
| 16 |
+
|
| 17 |
+
import dinov3.distributed as distributed
|
| 18 |
+
from dinov3.logging import cleanup_logging, setup_logging
|
| 19 |
+
from dinov3.utils import fix_random_seeds, get_conda_env, get_sha
|
| 20 |
+
|
| 21 |
+
logger = logging.getLogger("dinov3")
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@dataclass
|
| 25 |
+
class DinoV3SetupArgs:
|
| 26 |
+
config_file: str
|
| 27 |
+
pretrained_weights: str | None = None
|
| 28 |
+
shard_unsharded_model: bool = False
|
| 29 |
+
output_dir: str = ""
|
| 30 |
+
opts: List[Any] = field(default_factory=lambda: [])
|
| 31 |
+
|
| 32 |
+
def __post_init__(self):
|
| 33 |
+
# When loaded from benchmark.yaml, self.opts is a frozen omegaconf.ListConfig,
|
| 34 |
+
# which works everywhere except when we want to modify it or when
|
| 35 |
+
# we try to json-serialize it. So we convert it to a regular list here.
|
| 36 |
+
if OmegaConf.is_config(self.opts):
|
| 37 |
+
self.opts = OmegaConf.to_object(self.opts)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def apply_scaling_rules_to_cfg(cfg): # to fix
|
| 41 |
+
assert distributed.is_enabled(), "Setup distributed to get global size !"
|
| 42 |
+
if "schedules" in cfg:
|
| 43 |
+
# For schedules v2, the scaling rules are applied when building the schedules, the config is not modified
|
| 44 |
+
return cfg
|
| 45 |
+
|
| 46 |
+
if cfg.optim.scaling_rule == "linear_wrt_256":
|
| 47 |
+
old_lr = cfg.optim.lr
|
| 48 |
+
cfg.optim.lr *= cfg.train.batch_size_per_gpu * distributed.get_world_size() / 256.0
|
| 49 |
+
logger.info(f"linear scaling learning rate; old: {old_lr}, new: {cfg.optim.lr}")
|
| 50 |
+
elif cfg.optim.scaling_rule == "sqrt_wrt_1024":
|
| 51 |
+
old_lr = cfg.optim.lr
|
| 52 |
+
cfg.optim.lr *= 4 * math.sqrt(cfg.train.batch_size_per_gpu * distributed.get_world_size() / 1024.0)
|
| 53 |
+
logger.info(f"sqrt scaling learning rate; old: {old_lr}, new: {cfg.optim.lr}")
|
| 54 |
+
return cfg
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def write_config(cfg, output_dir, name="config.yaml"):
|
| 58 |
+
logger.info(OmegaConf.to_yaml(cfg))
|
| 59 |
+
output_dir = os.path.abspath(output_dir)
|
| 60 |
+
saved_cfg_path = os.path.join(output_dir, name)
|
| 61 |
+
with open(saved_cfg_path, "w") as f:
|
| 62 |
+
OmegaConf.save(config=cfg, f=f)
|
| 63 |
+
return saved_cfg_path
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def get_default_config() -> DictConfig:
|
| 67 |
+
p = pathlib.Path(__file__).parent / "ssl_default_config.yaml"
|
| 68 |
+
return OmegaConf.load(p)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def get_cfg_from_args(args: DinoV3SetupArgs, multidistillation=False, strict=True):
|
| 72 |
+
overrides = [*args.opts]
|
| 73 |
+
if args.output_dir is not None:
|
| 74 |
+
overrides.append(f"train.output_dir={os.path.realpath(args.output_dir)}")
|
| 75 |
+
|
| 76 |
+
# Config file
|
| 77 |
+
cfg = OmegaConf.load(args.config_file)
|
| 78 |
+
|
| 79 |
+
# Command line overrides
|
| 80 |
+
opts_cfg = OmegaConf.from_cli(overrides)
|
| 81 |
+
|
| 82 |
+
if multidistillation:
|
| 83 |
+
cfg = OmegaConf.merge(cfg, opts_cfg)
|
| 84 |
+
else:
|
| 85 |
+
# Default config
|
| 86 |
+
default_cfg = get_default_config()
|
| 87 |
+
if strict:
|
| 88 |
+
OmegaConf.set_struct(default_cfg, True)
|
| 89 |
+
cfg = OmegaConf.merge(default_cfg, cfg, opts_cfg)
|
| 90 |
+
return cfg
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def setup_config(args: DinoV3SetupArgs, strict_cfg=True):
|
| 94 |
+
"""
|
| 95 |
+
Create configs and perform basic setups.
|
| 96 |
+
"""
|
| 97 |
+
# Create the cfg with OmegaConf
|
| 98 |
+
cfg = get_cfg_from_args(args, strict=strict_cfg)
|
| 99 |
+
# setup distributed, logging, and random seeds
|
| 100 |
+
logger.info("\n".join("%s: %s" % (k, str(v)) for k, v in sorted(dict(vars(args)).items())))
|
| 101 |
+
# dump config before modifying so it can be reloaded
|
| 102 |
+
if args.output_dir is not None:
|
| 103 |
+
write_config(cfg, args.output_dir)
|
| 104 |
+
# modify the config inplace by applying scaling rules
|
| 105 |
+
apply_scaling_rules_to_cfg(cfg)
|
| 106 |
+
return cfg
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def _enumerate_all_subgroup_ranks(all_subgroup_rank_spans: Sequence[Tuple[int, int]]):
|
| 110 |
+
"""Expands a specification of process subgroups from spans to enumerated ranks.
|
| 111 |
+
|
| 112 |
+
Args:
|
| 113 |
+
all_group_rank_spans: a sequence of rank spans (first rank, last rank),
|
| 114 |
+
one for each process group. Example: ((0, 1), (2, 3), (4, 7)).
|
| 115 |
+
"""
|
| 116 |
+
for first, last in all_subgroup_rank_spans:
|
| 117 |
+
assert first <= last
|
| 118 |
+
return tuple(tuple(range(first, last + 1)) for first, last in all_subgroup_rank_spans)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def setup_multidistillation(args: DinoV3SetupArgs):
|
| 122 |
+
base_output_dir = args.output_dir
|
| 123 |
+
os.makedirs(args.output_dir, exist_ok=True)
|
| 124 |
+
# get config file for this rank
|
| 125 |
+
base_cfg = OmegaConf.load(args.config_file)
|
| 126 |
+
assert base_cfg.multidistillation.enabled
|
| 127 |
+
|
| 128 |
+
global_batch_size = base_cfg.multidistillation.global_batch_size
|
| 129 |
+
|
| 130 |
+
distributed.enable(overwrite=True)
|
| 131 |
+
seed = getattr(args, "seed", 0)
|
| 132 |
+
rank = distributed.get_rank()
|
| 133 |
+
|
| 134 |
+
# build process subgroups
|
| 135 |
+
all_subgroup_rank_spans = tuple(
|
| 136 |
+
(student.ranks_range[0], student.ranks_range[1] - 1) for student in base_cfg.multidistillation.students
|
| 137 |
+
)
|
| 138 |
+
all_subgroup_ranks = _enumerate_all_subgroup_ranks(all_subgroup_rank_spans)
|
| 139 |
+
distributed.new_subgroups(all_subgroup_ranks)
|
| 140 |
+
|
| 141 |
+
found = False
|
| 142 |
+
for student in base_cfg.multidistillation.students:
|
| 143 |
+
if rank in range(*student.ranks_range):
|
| 144 |
+
found = True
|
| 145 |
+
break
|
| 146 |
+
assert found, "rank of worker not in defined range"
|
| 147 |
+
|
| 148 |
+
name = student.name
|
| 149 |
+
config_path = student.config_path
|
| 150 |
+
n_gpus = student.ranks_range[1] - student.ranks_range[0]
|
| 151 |
+
assert global_batch_size % n_gpus == 0
|
| 152 |
+
total_n_gpus = distributed.get_world_size()
|
| 153 |
+
|
| 154 |
+
args.output_dir = os.path.join(base_output_dir, name)
|
| 155 |
+
args.opts += [f"train.output_dir={args.output_dir}"]
|
| 156 |
+
args.opts += [f"train.batch_size_per_gpu={global_batch_size // total_n_gpus}"]
|
| 157 |
+
args.config_file = os.path.abspath(config_path)
|
| 158 |
+
default_cfg = get_default_config()
|
| 159 |
+
cfg = OmegaConf.load(args.config_file)
|
| 160 |
+
cfg = OmegaConf.merge(default_cfg, cfg, base_cfg, OmegaConf.from_cli(args.opts))
|
| 161 |
+
|
| 162 |
+
global logger
|
| 163 |
+
setup_logging(output=args.output_dir, level=logging.INFO)
|
| 164 |
+
|
| 165 |
+
fix_random_seeds(seed + rank)
|
| 166 |
+
|
| 167 |
+
write_config(cfg, args.output_dir)
|
| 168 |
+
apply_scaling_rules_to_cfg(cfg)
|
| 169 |
+
|
| 170 |
+
return cfg
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def setup_job(
|
| 174 |
+
output_dir: Optional[str] = None,
|
| 175 |
+
distributed_enabled: bool = True,
|
| 176 |
+
logging_enabled: bool = True,
|
| 177 |
+
seed: Optional[int] = 0,
|
| 178 |
+
restrict_print_to_main_process: bool = True,
|
| 179 |
+
distributed_timeout: timedelta | None = None,
|
| 180 |
+
):
|
| 181 |
+
"""
|
| 182 |
+
Setup methods that should be done in every fairvit job
|
| 183 |
+
Initializes logging, distributed, random seeds and other utilities.
|
| 184 |
+
"""
|
| 185 |
+
if output_dir is not None:
|
| 186 |
+
output_dir = os.path.realpath(output_dir)
|
| 187 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 188 |
+
|
| 189 |
+
if logging_enabled:
|
| 190 |
+
setup_logging(
|
| 191 |
+
output=output_dir,
|
| 192 |
+
level=logging.INFO,
|
| 193 |
+
log_to_stdout_only_in_main_process=restrict_print_to_main_process,
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
if distributed_enabled:
|
| 197 |
+
distributed.enable(
|
| 198 |
+
overwrite=True,
|
| 199 |
+
nccl_async_error_handling=True,
|
| 200 |
+
restrict_print_to_main_process=restrict_print_to_main_process,
|
| 201 |
+
timeout=distributed_timeout,
|
| 202 |
+
)
|
| 203 |
+
|
| 204 |
+
if seed is not None:
|
| 205 |
+
rank = distributed.get_rank()
|
| 206 |
+
fix_random_seeds(seed + rank)
|
| 207 |
+
|
| 208 |
+
logger = logging.getLogger("dinov3")
|
| 209 |
+
logger.info("git:\n {}\n".format(get_sha()))
|
| 210 |
+
|
| 211 |
+
# Log some python info
|
| 212 |
+
conda_env_name, conda_env_path = get_conda_env()
|
| 213 |
+
logger.info(f"conda env name: {conda_env_name}")
|
| 214 |
+
logger.info(f"conda env path: {conda_env_path}")
|
| 215 |
+
logger.info(f"python path: {sys.path}")
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def exit_job(distributed_enabled: bool = True, logging_enabled: bool = True):
|
| 219 |
+
if distributed_enabled:
|
| 220 |
+
distributed.disable()
|
| 221 |
+
if logging_enabled:
|
| 222 |
+
cleanup_logging()
|
dinov3/configs/ssl_default_config.yaml
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MODEL:
|
| 2 |
+
META_ARCHITECTURE: SSLMetaArch
|
| 3 |
+
DEVICE: cuda
|
| 4 |
+
WEIGHTS: ''
|
| 5 |
+
DTYPE: float32
|
| 6 |
+
compute_precision:
|
| 7 |
+
param_dtype: bf16
|
| 8 |
+
reduce_dtype: fp32
|
| 9 |
+
sharding_strategy: SHARD_GRAD_OP
|
| 10 |
+
dino:
|
| 11 |
+
loss_weight: 1.0
|
| 12 |
+
global_ignore_diagonal: true # Whether to ignore A-A and B-B global pairs, default as in DINOv2, ignored by SSLMetaArch
|
| 13 |
+
head_n_prototypes: 65536
|
| 14 |
+
head_bottleneck_dim: 256
|
| 15 |
+
head_norm_last_layer: false
|
| 16 |
+
head_nlayers: 3
|
| 17 |
+
head_hidden_dim: 2048
|
| 18 |
+
koleo_loss_weight: 0.1
|
| 19 |
+
koleo_loss_distributed: false
|
| 20 |
+
koleo_topk: 1
|
| 21 |
+
koleo_distributed_replicas: 0
|
| 22 |
+
koleo_distributed_loss_group_size: null # Size of the nearest neighbor set for distributed Koleo. If None, uses global batch size.
|
| 23 |
+
koleo_distributed_loss_group_data: true # group data from adjacent ranks to make sure koleo is applied on the same data distribution
|
| 24 |
+
force_weight_norm: false
|
| 25 |
+
reweight_dino_local_loss: false # If true, reweighting of DINO loss
|
| 26 |
+
local_loss_weight_schedule: # Schedule for local loss weight, enabled if reweight_dino_local_loss is true
|
| 27 |
+
start: 0.5
|
| 28 |
+
peak: 0.5
|
| 29 |
+
end: 0.5
|
| 30 |
+
warmup_epochs: 0
|
| 31 |
+
ibot:
|
| 32 |
+
loss_weight: 1.0
|
| 33 |
+
mask_sample_probability: 0.5
|
| 34 |
+
mask_ratio_min_max:
|
| 35 |
+
- 0.1
|
| 36 |
+
- 0.5
|
| 37 |
+
mask_random_circular_shift: false
|
| 38 |
+
force_masking_even_with_zero_weight: False
|
| 39 |
+
separate_head: true
|
| 40 |
+
head_n_prototypes: 65536
|
| 41 |
+
head_bottleneck_dim: 256
|
| 42 |
+
head_norm_last_layer: false
|
| 43 |
+
head_nlayers: 3
|
| 44 |
+
head_hidden_dim: 2048
|
| 45 |
+
gram:
|
| 46 |
+
use_loss: false # (bool) if true gram is used, else not
|
| 47 |
+
compute_stats: false # (bool): if true compute auxilliary stats
|
| 48 |
+
loss_weight: 1.0 # (float): weight of the loss
|
| 49 |
+
ema_teacher: false # (bool): using the EMA teacher as GRAM teacher
|
| 50 |
+
ckpt: null #(str): Checkpoint to the teacher
|
| 51 |
+
it_load_ema_teacher: -1 # (int): iteration at which the ema teacher is loaded into the gram teacher
|
| 52 |
+
rep_update: true # (bool): if true GRAM teacher updated every gram.update_frequency after iter gram.it_first_update steps
|
| 53 |
+
update_frequency: 50000 # (int): update frequency
|
| 54 |
+
it_first_update: 0 # (int): iteration of the first update
|
| 55 |
+
max_updates: null # (int): maximum number of updates to gram teacher. If None, it is unlimited
|
| 56 |
+
normalized: true # (bool): normalization of the features
|
| 57 |
+
img_level: false # (bool): if true GRAM computation at the image else, otherwise at the local batch level
|
| 58 |
+
remove_neg: false # (bool): if true remove the negative similarities before applying the loss
|
| 59 |
+
remove_only_teacher_neg: false # (bool): remove negative similarities of the teacher
|
| 60 |
+
tokens_used: all # (str): In [all, masked, unmasked]
|
| 61 |
+
global_teacher_resize_method: bicubic # Method for resizing the outputs of the gram teacher
|
| 62 |
+
global_teacher_resize_antialias: false # Whether to use antialiasing when resizing the outputs of the gram teacher
|
| 63 |
+
loss_weight_schedule: null # (dict): If not None, use a schedule for the loss weight instead of `loss_weight`
|
| 64 |
+
train:
|
| 65 |
+
batch_size_per_gpu: 64
|
| 66 |
+
dataset_path: ImageNet:split=TRAIN
|
| 67 |
+
data_config: null
|
| 68 |
+
output_dir: .
|
| 69 |
+
saveckp_freq: 20
|
| 70 |
+
seed: 0
|
| 71 |
+
num_workers: 10
|
| 72 |
+
OFFICIAL_EPOCH_LENGTH: 1250
|
| 73 |
+
monitor_gradient_norm: false
|
| 74 |
+
chunk_schedule: []
|
| 75 |
+
use_teacher_head: true
|
| 76 |
+
learn_from_teacher_tokens: false
|
| 77 |
+
centering: "sinkhorn_knopp" # or "sinkhorn_knopp"
|
| 78 |
+
checkpointing: false
|
| 79 |
+
checkpointing_full: false # aggressive checkpointing
|
| 80 |
+
compile: true
|
| 81 |
+
cudagraphs: false
|
| 82 |
+
sharded_eval_checkpoint: false
|
| 83 |
+
cache_dataset: false
|
| 84 |
+
student:
|
| 85 |
+
arch: vit_large
|
| 86 |
+
patch_size: 16
|
| 87 |
+
drop_path_rate: 0.3
|
| 88 |
+
layerscale: 1.0e-05
|
| 89 |
+
pretrained_weights: ''
|
| 90 |
+
ffn_layer: "mlp"
|
| 91 |
+
ffn_ratio: 4.0
|
| 92 |
+
resume_from_teacher_chkpt: ""
|
| 93 |
+
qkv_bias: true
|
| 94 |
+
proj_bias: true
|
| 95 |
+
ffn_bias: true
|
| 96 |
+
norm_layer: "layernorm"
|
| 97 |
+
n_storage_tokens: 0
|
| 98 |
+
mask_k_bias: false
|
| 99 |
+
untie_cls_and_patch_norms: false # If true, use separate norms for CLS/reg and patch/mask tokens
|
| 100 |
+
untie_global_and_local_cls_norm: false # If true, use separate norms for local and global crop CLS token during training
|
| 101 |
+
in_chans: 3
|
| 102 |
+
pos_embed_type: rope
|
| 103 |
+
pos_embed_rope_base: 100.0
|
| 104 |
+
pos_embed_rope_min_period: null
|
| 105 |
+
pos_embed_rope_max_period: null
|
| 106 |
+
pos_embed_rope_normalize_coords: separate # min, max, separate
|
| 107 |
+
pos_embed_rope_shift_coords: null
|
| 108 |
+
pos_embed_rope_jitter_coords: null
|
| 109 |
+
pos_embed_rope_rescale_coords: null
|
| 110 |
+
pos_embed_rope_dtype: bf16
|
| 111 |
+
fp8_enabled: False # Convert Linear layers to operate in fp8 precision
|
| 112 |
+
fp8_filter: "blocks" # Regex that must appear in module path; empty means everything
|
| 113 |
+
teacher:
|
| 114 |
+
momentum_teacher: 0.992
|
| 115 |
+
final_momentum_teacher: 1
|
| 116 |
+
warmup_teacher_temp: 0.04
|
| 117 |
+
teacher_temp: 0.07
|
| 118 |
+
warmup_teacher_temp_epochs: 30
|
| 119 |
+
in_chans: 3
|
| 120 |
+
distillation: # teacher
|
| 121 |
+
enabled: false
|
| 122 |
+
full_cfg_path: ""
|
| 123 |
+
checkpoint_path: ""
|
| 124 |
+
multidistillation:
|
| 125 |
+
enabled: false
|
| 126 |
+
hrft: # non-hrft'd student
|
| 127 |
+
enabled: false
|
| 128 |
+
checkpoint_path: "" # teacher_checkpoint path
|
| 129 |
+
optim:
|
| 130 |
+
epochs: 100
|
| 131 |
+
optimizer: adamw
|
| 132 |
+
weight_decay: 0.04
|
| 133 |
+
weight_decay_end: 0.4
|
| 134 |
+
lr: 0.001
|
| 135 |
+
warmup_epochs: 10
|
| 136 |
+
min_lr: 1.0e-06
|
| 137 |
+
schedule_trunc_extra: 0.0 # Compute the schedule for (1 + schedule_trunc_extra) steps and truncate, .25 is a good choice
|
| 138 |
+
clip_grad: 3.0
|
| 139 |
+
freeze_last_layer_epochs: 1
|
| 140 |
+
scaling_rule: sqrt_wrt_1024
|
| 141 |
+
patch_embed_lr_mult: 0.2
|
| 142 |
+
dino_head_wd_multiplier: 1.0
|
| 143 |
+
layerwise_decay: 0.9
|
| 144 |
+
multi_tensor_optim: true
|
| 145 |
+
dump_fsdp_weights_path: ""
|
| 146 |
+
adamw_beta1: 0.9
|
| 147 |
+
adamw_beta2: 0.999
|
| 148 |
+
crops:
|
| 149 |
+
global_crops_scale:
|
| 150 |
+
- 0.32
|
| 151 |
+
- 1.0
|
| 152 |
+
local_crops_number: 8
|
| 153 |
+
local_crops_scale:
|
| 154 |
+
- 0.05
|
| 155 |
+
- 0.32
|
| 156 |
+
global_crops_size: 224
|
| 157 |
+
local_crops_size: 96
|
| 158 |
+
global_local_crop_pairs_ratios: 1.0
|
| 159 |
+
gram_teacher_crops_size: null # If not None, return crops for gram teacher
|
| 160 |
+
localcrops_subset_of_globalcrops: false
|
| 161 |
+
share_color_jitter: false
|
| 162 |
+
horizontal_flips: true
|
| 163 |
+
gram_teacher_no_distortions: false # If True, no distortions are applied to gram teacher crops
|
| 164 |
+
rgb_mean:
|
| 165 |
+
- 0.485
|
| 166 |
+
- 0.456
|
| 167 |
+
- 0.406
|
| 168 |
+
rgb_std:
|
| 169 |
+
- 0.229
|
| 170 |
+
- 0.224
|
| 171 |
+
- 0.225
|
| 172 |
+
evaluation:
|
| 173 |
+
eval_period_iterations: 12500
|
| 174 |
+
low_freq_every: 5
|
| 175 |
+
config_files: # Must be in fairvit/eval/configs
|
| 176 |
+
high_freq: benchmark_high_frequency.yaml # More often
|
| 177 |
+
low_freq: benchmark_low_frequency.yaml # Less often
|
| 178 |
+
checkpointing:
|
| 179 |
+
period: 3750
|
| 180 |
+
max_to_keep: 3
|
| 181 |
+
keep_every: 99999999999999999 # Save a checkpoint every N iterations, regardless of max_to_keep and period
|
| 182 |
+
|
| 183 |
+
# Example of constant schedules with schedules v2
|
| 184 |
+
# # schedules:
|
| 185 |
+
# # lr:
|
| 186 |
+
# # start: 0.0
|
| 187 |
+
# # peak: 1e-3
|
| 188 |
+
# # end: 1e-6
|
| 189 |
+
# # warmup_epochs: 10
|
| 190 |
+
# # freeze_last_layer_epochs: 1
|
| 191 |
+
# # weight_decay:
|
| 192 |
+
# # start: 0.04
|
| 193 |
+
# # peak: 0.04
|
| 194 |
+
# # end: 0.04
|
| 195 |
+
# # warmup_epochs: 0
|
| 196 |
+
# # momentum:
|
| 197 |
+
# # start: 0.992
|
| 198 |
+
# # peak: 0.992
|
| 199 |
+
# # end: 0.992
|
| 200 |
+
# # warmup_epochs: 0
|
| 201 |
+
# # teacher_temp:
|
| 202 |
+
# # start: 0.04
|
| 203 |
+
# # peak: 0.07
|
| 204 |
+
# # end: 0.07
|
| 205 |
+
# # warmup_epochs: 30
|
dinov3/configs/train/dinov3_vit7b16_gram_anchor.yaml
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MODEL:
|
| 2 |
+
META_ARCHITECTURE: SSLMetaArch
|
| 3 |
+
DEVICE: cuda
|
| 4 |
+
WEIGHTS: ''
|
| 5 |
+
DTYPE: float32
|
| 6 |
+
compute_precision:
|
| 7 |
+
param_dtype: bf16
|
| 8 |
+
reduce_dtype: fp32
|
| 9 |
+
sharding_strategy: SHARD_GRAD_OP
|
| 10 |
+
dino:
|
| 11 |
+
loss_weight: 1.0
|
| 12 |
+
global_ignore_diagonal: true
|
| 13 |
+
head_n_prototypes: 262144
|
| 14 |
+
head_bottleneck_dim: 512
|
| 15 |
+
head_norm_last_layer: false
|
| 16 |
+
head_nlayers: 3
|
| 17 |
+
head_hidden_dim: 8192
|
| 18 |
+
koleo_loss_weight: 0.1
|
| 19 |
+
koleo_loss_distributed: false
|
| 20 |
+
koleo_topk: 1
|
| 21 |
+
koleo_distributed_replicas: 0
|
| 22 |
+
koleo_distributed_loss_group_size: null
|
| 23 |
+
koleo_distributed_loss_group_data: true
|
| 24 |
+
force_weight_norm: false
|
| 25 |
+
reweight_dino_local_loss: true
|
| 26 |
+
local_loss_weight_schedule:
|
| 27 |
+
start: 1
|
| 28 |
+
peak: 1
|
| 29 |
+
end: 0.5
|
| 30 |
+
warmup_epochs: 1000
|
| 31 |
+
cosine_epochs: 1
|
| 32 |
+
ibot:
|
| 33 |
+
loss_weight: 1.0
|
| 34 |
+
mask_sample_probability: 0.5
|
| 35 |
+
mask_ratio_min_max:
|
| 36 |
+
- 0.1
|
| 37 |
+
- 0.5
|
| 38 |
+
mask_random_circular_shift: false
|
| 39 |
+
force_masking_even_with_zero_weight: false
|
| 40 |
+
separate_head: true
|
| 41 |
+
head_n_prototypes: 98304
|
| 42 |
+
head_bottleneck_dim: 384
|
| 43 |
+
head_norm_last_layer: false
|
| 44 |
+
head_nlayers: 3
|
| 45 |
+
head_hidden_dim: 4096
|
| 46 |
+
gram:
|
| 47 |
+
use_loss: true
|
| 48 |
+
compute_stats: false
|
| 49 |
+
loss_weight: 1.0
|
| 50 |
+
ema_teacher: false
|
| 51 |
+
ckpt: ignore
|
| 52 |
+
it_load_ema_teacher: -1
|
| 53 |
+
rep_update: true
|
| 54 |
+
update_frequency: 10000
|
| 55 |
+
it_first_update: 1010000
|
| 56 |
+
max_updates: 3
|
| 57 |
+
normalized: true
|
| 58 |
+
img_level: true
|
| 59 |
+
remove_neg: false
|
| 60 |
+
remove_only_teacher_neg: false
|
| 61 |
+
tokens_used: all
|
| 62 |
+
global_teacher_resize_method: bicubic
|
| 63 |
+
global_teacher_resize_antialias: false
|
| 64 |
+
loss_weight_schedule:
|
| 65 |
+
start: 0
|
| 66 |
+
peak: 0
|
| 67 |
+
end: 2.0
|
| 68 |
+
warmup_epochs: 1000
|
| 69 |
+
cosine_epochs: 1
|
| 70 |
+
train:
|
| 71 |
+
batch_size_per_gpu: 16
|
| 72 |
+
dataset_path: null
|
| 73 |
+
saveckp_freq: 20
|
| 74 |
+
seed: 0
|
| 75 |
+
num_workers: 10
|
| 76 |
+
OFFICIAL_EPOCH_LENGTH: 1000
|
| 77 |
+
monitor_gradient_norm: false
|
| 78 |
+
chunk_schedule: []
|
| 79 |
+
cache_dataset: true
|
| 80 |
+
use_teacher_head: true
|
| 81 |
+
learn_from_teacher_tokens: false
|
| 82 |
+
centering: sinkhorn_knopp
|
| 83 |
+
checkpointing: true
|
| 84 |
+
checkpointing_full: true
|
| 85 |
+
compile: true
|
| 86 |
+
cudagraphs: false
|
| 87 |
+
cell_augmentation: false
|
| 88 |
+
cell_augmentation_type: hpa
|
| 89 |
+
sharded_eval_checkpoint: true
|
| 90 |
+
student:
|
| 91 |
+
arch: vit_7b
|
| 92 |
+
patch_size: 16
|
| 93 |
+
drop_path_rate: 0.4
|
| 94 |
+
layerscale: 1.0e-05
|
| 95 |
+
patch_drop: 0.0
|
| 96 |
+
pretrained_weights: ''
|
| 97 |
+
ffn_layer: swiglu64
|
| 98 |
+
ffn_ratio: 3
|
| 99 |
+
resume_from_teacher_chkpt: ''
|
| 100 |
+
qkv_bias: false
|
| 101 |
+
proj_bias: true
|
| 102 |
+
ffn_bias: true
|
| 103 |
+
norm_layer: layernormbf16
|
| 104 |
+
n_storage_tokens: 4
|
| 105 |
+
untie_cls_and_patch_norms: false
|
| 106 |
+
untie_global_and_local_cls_norm: true
|
| 107 |
+
mask_k_bias: true
|
| 108 |
+
in_chans: 3
|
| 109 |
+
pos_embed_type: rope
|
| 110 |
+
pos_embed_rope_base: 100
|
| 111 |
+
pos_embed_rope_min_period: null
|
| 112 |
+
pos_embed_rope_max_period: null
|
| 113 |
+
pos_embed_rope_normalize_coords: separate
|
| 114 |
+
pos_embed_rope_shift_coords: null
|
| 115 |
+
pos_embed_rope_jitter_coords: null
|
| 116 |
+
pos_embed_rope_rescale_coords: 2
|
| 117 |
+
pos_embed_rope_dtype: fp32
|
| 118 |
+
fp8_enabled: true
|
| 119 |
+
fp8_filter: blocks
|
| 120 |
+
teacher:
|
| 121 |
+
momentum_teacher: null
|
| 122 |
+
final_momentum_teacher: null
|
| 123 |
+
warmup_teacher_temp: null
|
| 124 |
+
teacher_temp: null
|
| 125 |
+
warmup_teacher_temp_epochs: null
|
| 126 |
+
in_chans: 3
|
| 127 |
+
distillation:
|
| 128 |
+
enabled: false
|
| 129 |
+
full_cfg_path: ''
|
| 130 |
+
checkpoint_path: ''
|
| 131 |
+
multidistillation:
|
| 132 |
+
enabled: false
|
| 133 |
+
hrft:
|
| 134 |
+
enabled: false
|
| 135 |
+
checkpoint_path: ''
|
| 136 |
+
optim:
|
| 137 |
+
epochs: 1200
|
| 138 |
+
optimizer: adamw
|
| 139 |
+
weight_decay: null
|
| 140 |
+
weight_decay_end: null
|
| 141 |
+
lr: null
|
| 142 |
+
warmup_epochs: null
|
| 143 |
+
min_lr: null
|
| 144 |
+
schedule_trunc_extra: null
|
| 145 |
+
clip_grad: 30.0
|
| 146 |
+
freeze_last_layer_epochs: null
|
| 147 |
+
scaling_rule: sqrt_wrt_1024
|
| 148 |
+
patch_embed_lr_mult: 0.2
|
| 149 |
+
dino_head_wd_multiplier: 1.0
|
| 150 |
+
layerwise_decay: 0.98
|
| 151 |
+
multi_tensor_optim: true
|
| 152 |
+
dump_fsdp_weights_path: ''
|
| 153 |
+
adamw_beta1: 0.9
|
| 154 |
+
adamw_beta2: 0.99
|
| 155 |
+
crops:
|
| 156 |
+
global_crops_scale:
|
| 157 |
+
- 0.32
|
| 158 |
+
- 1.0
|
| 159 |
+
local_crops_number: 8
|
| 160 |
+
local_crops_scale:
|
| 161 |
+
- 0.05
|
| 162 |
+
- 0.32
|
| 163 |
+
global_crops_size: 256
|
| 164 |
+
local_crops_size: 112
|
| 165 |
+
gram_teacher_crops_size: 512
|
| 166 |
+
localcrops_subset_of_globalcrops: false
|
| 167 |
+
share_color_jitter: false
|
| 168 |
+
horizontal_flips: false
|
| 169 |
+
gram_teacher_no_distortions: true
|
| 170 |
+
rgb_mean:
|
| 171 |
+
- 0.485
|
| 172 |
+
- 0.456
|
| 173 |
+
- 0.406
|
| 174 |
+
rgb_std:
|
| 175 |
+
- 0.229
|
| 176 |
+
- 0.224
|
| 177 |
+
- 0.225
|
| 178 |
+
checkpointing:
|
| 179 |
+
period: 1000
|
| 180 |
+
max_to_keep: 3
|
| 181 |
+
keep_every: 50000
|
| 182 |
+
schedules:
|
| 183 |
+
lr:
|
| 184 |
+
start: 0
|
| 185 |
+
peak: 3.0e-05
|
| 186 |
+
end: 3.0e-05
|
| 187 |
+
warmup_epochs: 100
|
| 188 |
+
freeze_last_layer_epochs: 5
|
| 189 |
+
weight_decay:
|
| 190 |
+
start: 0.04
|
| 191 |
+
peak: 0.04
|
| 192 |
+
end: 0.04
|
| 193 |
+
warmup_epochs: 0
|
| 194 |
+
teacher_temp:
|
| 195 |
+
start: 0.04
|
| 196 |
+
peak: 0.07
|
| 197 |
+
end: 0.07
|
| 198 |
+
warmup_epochs: 100
|
| 199 |
+
momentum:
|
| 200 |
+
start: 0.999
|
| 201 |
+
peak: 0.999
|
| 202 |
+
end: 0.999
|
| 203 |
+
warmup_epochs: 0
|
dinov3/configs/train/dinov3_vit7b16_high_res_adapt.yaml
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MODEL:
|
| 2 |
+
META_ARCHITECTURE: SSLMetaArch
|
| 3 |
+
DEVICE: cuda
|
| 4 |
+
WEIGHTS: ''
|
| 5 |
+
DTYPE: float32
|
| 6 |
+
compute_precision:
|
| 7 |
+
param_dtype: bf16
|
| 8 |
+
reduce_dtype: fp32
|
| 9 |
+
sharding_strategy: SHARD_GRAD_OP
|
| 10 |
+
dino:
|
| 11 |
+
loss_weight: 1.0
|
| 12 |
+
global_ignore_diagonal: true
|
| 13 |
+
head_n_prototypes: 262144
|
| 14 |
+
head_bottleneck_dim: 512
|
| 15 |
+
head_norm_last_layer: false
|
| 16 |
+
head_nlayers: 3
|
| 17 |
+
head_hidden_dim: 8192
|
| 18 |
+
koleo_loss_weight: 0.1
|
| 19 |
+
koleo_loss_distributed: true
|
| 20 |
+
koleo_topk: 1
|
| 21 |
+
koleo_distributed_replicas: 0
|
| 22 |
+
koleo_distributed_loss_group_size: 16
|
| 23 |
+
force_weight_norm: false
|
| 24 |
+
reweight_dino_local_loss: true
|
| 25 |
+
local_loss_weight_schedule:
|
| 26 |
+
start: 0.5
|
| 27 |
+
peak: 0.5
|
| 28 |
+
end: 0.5
|
| 29 |
+
warmup_epochs: 0
|
| 30 |
+
cosine_epochs: 0
|
| 31 |
+
koleo_distributed_loss_group_data: true
|
| 32 |
+
ibot:
|
| 33 |
+
loss_weight: 1.0
|
| 34 |
+
mask_sample_probability: 0.5
|
| 35 |
+
mask_ratio_min_max:
|
| 36 |
+
- 0.1
|
| 37 |
+
- 0.5
|
| 38 |
+
mask_random_circular_shift: false
|
| 39 |
+
force_masking_even_with_zero_weight: false
|
| 40 |
+
separate_head: true
|
| 41 |
+
head_n_prototypes: 98304
|
| 42 |
+
head_bottleneck_dim: 384
|
| 43 |
+
head_norm_last_layer: false
|
| 44 |
+
head_nlayers: 3
|
| 45 |
+
head_hidden_dim: 4096
|
| 46 |
+
gram:
|
| 47 |
+
use_loss: true
|
| 48 |
+
compute_stats: false
|
| 49 |
+
loss_weight: 1.0
|
| 50 |
+
ema_teacher: false
|
| 51 |
+
it_load_ema_teacher: -1
|
| 52 |
+
rep_update: false
|
| 53 |
+
update_frequency: 10000
|
| 54 |
+
it_first_update: 1010000
|
| 55 |
+
max_updates: 3
|
| 56 |
+
normalized: true
|
| 57 |
+
img_level: true
|
| 58 |
+
remove_neg: false
|
| 59 |
+
remove_only_teacher_neg: false
|
| 60 |
+
tokens_used: all
|
| 61 |
+
global_teacher_resize_method: bicubic
|
| 62 |
+
global_teacher_resize_antialias: false
|
| 63 |
+
loss_weight_schedule:
|
| 64 |
+
start: 1.5
|
| 65 |
+
peak: 1.5
|
| 66 |
+
end: 1.5
|
| 67 |
+
warmup_epochs: 0
|
| 68 |
+
cosine_epochs: 0
|
| 69 |
+
train:
|
| 70 |
+
batch_size_per_gpu: 8
|
| 71 |
+
dataset_path: null
|
| 72 |
+
saveckp_freq: 20
|
| 73 |
+
seed: 0
|
| 74 |
+
num_workers: 2
|
| 75 |
+
OFFICIAL_EPOCH_LENGTH: 1000
|
| 76 |
+
monitor_gradient_norm: false
|
| 77 |
+
chunk_schedule: []
|
| 78 |
+
cache_dataset: true
|
| 79 |
+
use_teacher_head: true
|
| 80 |
+
learn_from_teacher_tokens: false
|
| 81 |
+
centering: sinkhorn_knopp
|
| 82 |
+
checkpointing: true
|
| 83 |
+
checkpointing_full: true
|
| 84 |
+
compile: true
|
| 85 |
+
cudagraphs: false
|
| 86 |
+
cell_augmentation: false
|
| 87 |
+
cell_augmentation_type: hpa
|
| 88 |
+
sharded_eval_checkpoint: true
|
| 89 |
+
student:
|
| 90 |
+
arch: vit_7b
|
| 91 |
+
patch_size: 16
|
| 92 |
+
drop_path_rate: 0.4
|
| 93 |
+
layerscale: 1.0e-05
|
| 94 |
+
patch_drop: 0.0
|
| 95 |
+
pretrained_weights: ''
|
| 96 |
+
ffn_layer: swiglu64
|
| 97 |
+
ffn_ratio: 3
|
| 98 |
+
resume_from_teacher_chkpt: ''
|
| 99 |
+
qkv_bias: false
|
| 100 |
+
proj_bias: true
|
| 101 |
+
ffn_bias: true
|
| 102 |
+
norm_layer: layernormbf16
|
| 103 |
+
n_storage_tokens: 4
|
| 104 |
+
untie_cls_and_patch_norms: false
|
| 105 |
+
untie_global_and_local_cls_norm: true
|
| 106 |
+
mask_k_bias: true
|
| 107 |
+
in_chans: 3
|
| 108 |
+
pos_embed_type: rope
|
| 109 |
+
pos_embed_rope_base: 100
|
| 110 |
+
pos_embed_rope_min_period: null
|
| 111 |
+
pos_embed_rope_max_period: null
|
| 112 |
+
pos_embed_rope_normalize_coords: separate
|
| 113 |
+
pos_embed_rope_shift_coords: null
|
| 114 |
+
pos_embed_rope_jitter_coords: null
|
| 115 |
+
pos_embed_rope_rescale_coords: 2
|
| 116 |
+
pos_embed_rope_dtype: fp32
|
| 117 |
+
fp8_enabled: true
|
| 118 |
+
fp8_filter: blocks
|
| 119 |
+
teacher:
|
| 120 |
+
momentum_teacher: null
|
| 121 |
+
final_momentum_teacher: null
|
| 122 |
+
warmup_teacher_temp: null
|
| 123 |
+
teacher_temp: null
|
| 124 |
+
warmup_teacher_temp_epochs: null
|
| 125 |
+
in_chans: 3
|
| 126 |
+
distillation:
|
| 127 |
+
enabled: false
|
| 128 |
+
full_cfg_path: ''
|
| 129 |
+
checkpoint_path: ''
|
| 130 |
+
multidistillation:
|
| 131 |
+
enabled: false
|
| 132 |
+
hrft:
|
| 133 |
+
enabled: false
|
| 134 |
+
checkpoint_path: ''
|
| 135 |
+
optim:
|
| 136 |
+
epochs: 30
|
| 137 |
+
optimizer: adamw
|
| 138 |
+
weight_decay: null
|
| 139 |
+
weight_decay_end: null
|
| 140 |
+
lr: null
|
| 141 |
+
warmup_epochs: null
|
| 142 |
+
min_lr: null
|
| 143 |
+
schedule_trunc_extra: null
|
| 144 |
+
clip_grad: 30.0
|
| 145 |
+
freeze_last_layer_epochs: null
|
| 146 |
+
scaling_rule: sqrt_wrt_1024
|
| 147 |
+
patch_embed_lr_mult: 0.2
|
| 148 |
+
dino_head_wd_multiplier: 1.0
|
| 149 |
+
layerwise_decay: 0.98
|
| 150 |
+
multi_tensor_optim: true
|
| 151 |
+
dump_fsdp_weights_path: ''
|
| 152 |
+
adamw_beta1: 0.9
|
| 153 |
+
adamw_beta2: 0.99
|
| 154 |
+
crops:
|
| 155 |
+
global_crops_scale:
|
| 156 |
+
- 0.32
|
| 157 |
+
- 1.0
|
| 158 |
+
local_crops_number: 8
|
| 159 |
+
local_crops_scale:
|
| 160 |
+
- 0.05
|
| 161 |
+
- 0.32
|
| 162 |
+
global_crops_size:
|
| 163 |
+
- 512
|
| 164 |
+
- 768
|
| 165 |
+
- 768
|
| 166 |
+
- 768
|
| 167 |
+
- 768
|
| 168 |
+
local_crops_size:
|
| 169 |
+
- 112
|
| 170 |
+
- 112
|
| 171 |
+
- 168
|
| 172 |
+
- 224
|
| 173 |
+
- 336
|
| 174 |
+
global_local_crop_pairs_ratios:
|
| 175 |
+
- 0.3
|
| 176 |
+
- 0.3
|
| 177 |
+
- 0.3
|
| 178 |
+
- 0.05
|
| 179 |
+
- 0.05
|
| 180 |
+
gram_teacher_crops_size:
|
| 181 |
+
- 768
|
| 182 |
+
- 1152
|
| 183 |
+
- 1152
|
| 184 |
+
- 1152
|
| 185 |
+
- 1152
|
| 186 |
+
localcrops_subset_of_globalcrops: false
|
| 187 |
+
share_color_jitter: false
|
| 188 |
+
horizontal_flips: false
|
| 189 |
+
gram_teacher_no_distortions: true
|
| 190 |
+
rgb_mean:
|
| 191 |
+
- 0.485
|
| 192 |
+
- 0.456
|
| 193 |
+
- 0.406
|
| 194 |
+
rgb_std:
|
| 195 |
+
- 0.229
|
| 196 |
+
- 0.224
|
| 197 |
+
- 0.225
|
| 198 |
+
checkpointing:
|
| 199 |
+
period: 250
|
| 200 |
+
max_to_keep: 3
|
| 201 |
+
keep_every: 50000
|
| 202 |
+
schedules:
|
| 203 |
+
lr:
|
| 204 |
+
start: 0
|
| 205 |
+
peak: 0
|
| 206 |
+
end: 1.25e-05
|
| 207 |
+
warmup_epochs: 0
|
| 208 |
+
freeze_last_layer_epochs: 0
|
| 209 |
+
cosine_epochs: 10
|
| 210 |
+
weight_decay:
|
| 211 |
+
start: 0.04
|
| 212 |
+
peak: 0.04
|
| 213 |
+
end: 0.04
|
| 214 |
+
warmup_epochs: 0
|
| 215 |
+
teacher_temp:
|
| 216 |
+
start: 0.07
|
| 217 |
+
peak: 0.07
|
| 218 |
+
end: 0.07
|
| 219 |
+
warmup_epochs: 0
|
| 220 |
+
momentum:
|
| 221 |
+
start: 0.999
|
| 222 |
+
peak: 0.999
|
| 223 |
+
end: 0.999
|
| 224 |
+
warmup_epochs: 0
|
dinov3/configs/train/dinov3_vit7b16_pretrain.yaml
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MODEL:
|
| 2 |
+
META_ARCHITECTURE: SSLMetaArch
|
| 3 |
+
DEVICE: cuda
|
| 4 |
+
WEIGHTS: ''
|
| 5 |
+
DTYPE: float32
|
| 6 |
+
compute_precision:
|
| 7 |
+
param_dtype: bf16
|
| 8 |
+
reduce_dtype: fp32
|
| 9 |
+
sharding_strategy: SHARD_GRAD_OP
|
| 10 |
+
dino:
|
| 11 |
+
loss_weight: 1.0
|
| 12 |
+
global_ignore_diagonal: true
|
| 13 |
+
head_n_prototypes: 262144
|
| 14 |
+
head_bottleneck_dim: 512
|
| 15 |
+
head_norm_last_layer: false
|
| 16 |
+
head_nlayers: 3
|
| 17 |
+
head_hidden_dim: 8192
|
| 18 |
+
koleo_loss_weight: 0.1
|
| 19 |
+
koleo_loss_distributed: false
|
| 20 |
+
koleo_topk: 1
|
| 21 |
+
koleo_distributed_replicas: 0
|
| 22 |
+
koleo_distributed_loss_group_size: null
|
| 23 |
+
force_weight_norm: false
|
| 24 |
+
ibot:
|
| 25 |
+
loss_weight: 1.0
|
| 26 |
+
mask_sample_probability: 0.5
|
| 27 |
+
mask_ratio_min_max:
|
| 28 |
+
- 0.1
|
| 29 |
+
- 0.5
|
| 30 |
+
mask_random_circular_shift: false
|
| 31 |
+
force_masking_even_with_zero_weight: false
|
| 32 |
+
separate_head: true
|
| 33 |
+
head_n_prototypes: 98304
|
| 34 |
+
head_bottleneck_dim: 384
|
| 35 |
+
head_norm_last_layer: false
|
| 36 |
+
head_nlayers: 3
|
| 37 |
+
head_hidden_dim: 4096
|
| 38 |
+
gram:
|
| 39 |
+
use_loss: false
|
| 40 |
+
compute_stats: false
|
| 41 |
+
train:
|
| 42 |
+
batch_size_per_gpu: 16
|
| 43 |
+
dataset_path: null
|
| 44 |
+
saveckp_freq: 20
|
| 45 |
+
seed: 0
|
| 46 |
+
num_workers: 10
|
| 47 |
+
OFFICIAL_EPOCH_LENGTH: 1000
|
| 48 |
+
monitor_gradient_norm: false
|
| 49 |
+
chunk_schedule: []
|
| 50 |
+
cache_dataset: true
|
| 51 |
+
use_teacher_head: true
|
| 52 |
+
learn_from_teacher_tokens: false
|
| 53 |
+
centering: sinkhorn_knopp
|
| 54 |
+
checkpointing: true
|
| 55 |
+
checkpointing_full: false
|
| 56 |
+
compile: true
|
| 57 |
+
cudagraphs: false
|
| 58 |
+
cell_augmentation: false
|
| 59 |
+
cell_augmentation_type: hpa
|
| 60 |
+
sharded_eval_checkpoint: true
|
| 61 |
+
student:
|
| 62 |
+
arch: vit_7b
|
| 63 |
+
patch_size: 16
|
| 64 |
+
drop_path_rate: 0.4
|
| 65 |
+
layerscale: 1.0e-05
|
| 66 |
+
patch_drop: 0.0
|
| 67 |
+
pretrained_weights: ''
|
| 68 |
+
ffn_layer: swiglu64
|
| 69 |
+
ffn_ratio: 3
|
| 70 |
+
resume_from_teacher_chkpt: ''
|
| 71 |
+
qkv_bias: false
|
| 72 |
+
proj_bias: true
|
| 73 |
+
ffn_bias: true
|
| 74 |
+
norm_layer: layernormbf16
|
| 75 |
+
n_storage_tokens: 4
|
| 76 |
+
untie_cls_and_patch_norms: false
|
| 77 |
+
untie_global_and_local_cls_norm: true
|
| 78 |
+
mask_k_bias: true
|
| 79 |
+
in_chans: 3
|
| 80 |
+
pos_embed_type: rope
|
| 81 |
+
pos_embed_rope_base: 100
|
| 82 |
+
pos_embed_rope_min_period: null
|
| 83 |
+
pos_embed_rope_max_period: null
|
| 84 |
+
pos_embed_rope_normalize_coords: separate
|
| 85 |
+
pos_embed_rope_shift_coords: null
|
| 86 |
+
pos_embed_rope_jitter_coords: null
|
| 87 |
+
pos_embed_rope_rescale_coords: 2
|
| 88 |
+
pos_embed_rope_dtype: fp32
|
| 89 |
+
fp8_enabled: true
|
| 90 |
+
fp8_filter: blocks
|
| 91 |
+
teacher:
|
| 92 |
+
momentum_teacher: null
|
| 93 |
+
final_momentum_teacher: null
|
| 94 |
+
warmup_teacher_temp: null
|
| 95 |
+
teacher_temp: null
|
| 96 |
+
warmup_teacher_temp_epochs: null
|
| 97 |
+
in_chans: 3
|
| 98 |
+
distillation:
|
| 99 |
+
enabled: false
|
| 100 |
+
full_cfg_path: ''
|
| 101 |
+
checkpoint_path: ''
|
| 102 |
+
multidistillation:
|
| 103 |
+
enabled: false
|
| 104 |
+
hrft:
|
| 105 |
+
enabled: false
|
| 106 |
+
checkpoint_path: ''
|
| 107 |
+
optim:
|
| 108 |
+
epochs: 1000
|
| 109 |
+
optimizer: adamw
|
| 110 |
+
weight_decay: null
|
| 111 |
+
weight_decay_end: null
|
| 112 |
+
lr: null
|
| 113 |
+
warmup_epochs: null
|
| 114 |
+
min_lr: null
|
| 115 |
+
schedule_trunc_extra: null
|
| 116 |
+
clip_grad: 30.0
|
| 117 |
+
freeze_last_layer_epochs: null
|
| 118 |
+
scaling_rule: sqrt_wrt_1024
|
| 119 |
+
patch_embed_lr_mult: 0.2
|
| 120 |
+
dino_head_wd_multiplier: 1.0
|
| 121 |
+
layerwise_decay: 0.98
|
| 122 |
+
multi_tensor_optim: true
|
| 123 |
+
dump_fsdp_weights_path: ''
|
| 124 |
+
adamw_beta1: 0.9
|
| 125 |
+
adamw_beta2: 0.99
|
| 126 |
+
crops:
|
| 127 |
+
global_crops_scale:
|
| 128 |
+
- 0.32
|
| 129 |
+
- 1.0
|
| 130 |
+
local_crops_number: 8
|
| 131 |
+
local_crops_scale:
|
| 132 |
+
- 0.05
|
| 133 |
+
- 0.32
|
| 134 |
+
global_crops_size: 256
|
| 135 |
+
local_crops_size: 112
|
| 136 |
+
localcrops_subset_of_globalcrops: false
|
| 137 |
+
share_color_jitter: false
|
| 138 |
+
horizontal_flips: false
|
| 139 |
+
rgb_mean:
|
| 140 |
+
- 0.485
|
| 141 |
+
- 0.456
|
| 142 |
+
- 0.406
|
| 143 |
+
rgb_std:
|
| 144 |
+
- 0.229
|
| 145 |
+
- 0.224
|
| 146 |
+
- 0.225
|
| 147 |
+
checkpointing:
|
| 148 |
+
period: 1000
|
| 149 |
+
max_to_keep: 3
|
| 150 |
+
keep_every: 50000
|
| 151 |
+
schedules:
|
| 152 |
+
lr:
|
| 153 |
+
start: 0
|
| 154 |
+
peak: 5.0e-05
|
| 155 |
+
end: 5.0e-05
|
| 156 |
+
warmup_epochs: 100
|
| 157 |
+
freeze_last_layer_epochs: 5
|
| 158 |
+
weight_decay:
|
| 159 |
+
start: 0.04
|
| 160 |
+
peak: 0.04
|
| 161 |
+
end: 0.04
|
| 162 |
+
warmup_epochs: 0
|
| 163 |
+
teacher_temp:
|
| 164 |
+
start: 0.04
|
| 165 |
+
peak: 0.07
|
| 166 |
+
end: 0.07
|
| 167 |
+
warmup_epochs: 100
|
| 168 |
+
momentum:
|
| 169 |
+
start: 0.994
|
| 170 |
+
peak: 0.994
|
| 171 |
+
end: 0.994
|
| 172 |
+
warmup_epochs: 0
|
dinov3/configs/train/dinov3_vitl16_lvd1689m_distilled.yaml
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MODEL:
|
| 2 |
+
META_ARCHITECTURE: MultiDistillationMetaArch
|
| 3 |
+
DEVICE: cuda
|
| 4 |
+
WEIGHTS: ''
|
| 5 |
+
DTYPE: float32
|
| 6 |
+
compute_precision:
|
| 7 |
+
param_dtype: bf16
|
| 8 |
+
reduce_dtype: fp32
|
| 9 |
+
sharding_strategy: SHARD_GRAD_OP
|
| 10 |
+
dino:
|
| 11 |
+
loss_weight: 1.0
|
| 12 |
+
global_ignore_diagonal: true
|
| 13 |
+
head_n_prototypes: 262144
|
| 14 |
+
head_bottleneck_dim: 512
|
| 15 |
+
head_norm_last_layer: false
|
| 16 |
+
head_nlayers: 3
|
| 17 |
+
head_hidden_dim: 8192
|
| 18 |
+
koleo_loss_weight: 0.1
|
| 19 |
+
koleo_loss_distributed: false
|
| 20 |
+
koleo_topk: 1
|
| 21 |
+
koleo_distributed_replicas: 0
|
| 22 |
+
koleo_distributed_loss_group_size: null
|
| 23 |
+
koleo_distributed_loss_group_data: true
|
| 24 |
+
force_weight_norm: false
|
| 25 |
+
reweight_dino_local_loss: false
|
| 26 |
+
local_loss_weight_schedule:
|
| 27 |
+
start: 0.5
|
| 28 |
+
peak: 0.5
|
| 29 |
+
end: 0.5
|
| 30 |
+
warmup_epochs: 0
|
| 31 |
+
ibot:
|
| 32 |
+
loss_weight: 1.0
|
| 33 |
+
mask_sample_probability: 0.5
|
| 34 |
+
mask_ratio_min_max:
|
| 35 |
+
- 0.1
|
| 36 |
+
- 0.5
|
| 37 |
+
mask_random_circular_shift: false
|
| 38 |
+
force_masking_even_with_zero_weight: false
|
| 39 |
+
separate_head: true
|
| 40 |
+
head_n_prototypes: 98304
|
| 41 |
+
head_bottleneck_dim: 384
|
| 42 |
+
head_norm_last_layer: false
|
| 43 |
+
head_nlayers: 3
|
| 44 |
+
head_hidden_dim: 4096
|
| 45 |
+
coding_rate_loss:
|
| 46 |
+
use_cls_loss: false
|
| 47 |
+
cls_loss_weight: 0.2
|
| 48 |
+
use_masked_patches_loss: false
|
| 49 |
+
masked_patches_loss_weight: 0.1
|
| 50 |
+
epsilon: 8
|
| 51 |
+
gram:
|
| 52 |
+
use_loss: false
|
| 53 |
+
compute_stats: false
|
| 54 |
+
loss_weight: 1.0
|
| 55 |
+
ema_teacher: false
|
| 56 |
+
ckpt: null
|
| 57 |
+
it_load_ema_teacher: -1
|
| 58 |
+
rep_update: true
|
| 59 |
+
update_frequency: 50000
|
| 60 |
+
it_first_update: 0
|
| 61 |
+
max_updates: null
|
| 62 |
+
normalized: true
|
| 63 |
+
img_level: false
|
| 64 |
+
remove_neg: false
|
| 65 |
+
remove_only_teacher_neg: false
|
| 66 |
+
tokens_used: all
|
| 67 |
+
global_teacher_resize_method: bicubic
|
| 68 |
+
global_teacher_resize_antialias: false
|
| 69 |
+
loss_weight_schedule: null
|
| 70 |
+
train:
|
| 71 |
+
batch_size_per_gpu: 3
|
| 72 |
+
dataset_path: <TRAIN/DATASET>
|
| 73 |
+
output_dir: <OUTPUT/DIR>
|
| 74 |
+
saveckp_freq: 20
|
| 75 |
+
seed: 0
|
| 76 |
+
num_workers: 2
|
| 77 |
+
OFFICIAL_EPOCH_LENGTH: 1250
|
| 78 |
+
monitor_gradient_norm: false
|
| 79 |
+
chunk_schedule: []
|
| 80 |
+
cache_dataset: true
|
| 81 |
+
use_teacher_head: true
|
| 82 |
+
learn_from_teacher_tokens: false
|
| 83 |
+
centering: sinkhorn_knopp
|
| 84 |
+
checkpointing: true
|
| 85 |
+
checkpointing_full: true
|
| 86 |
+
compile: true
|
| 87 |
+
cudagraphs: false
|
| 88 |
+
cell_augmentation: false
|
| 89 |
+
cell_augmentation_type: hpa
|
| 90 |
+
sharded_eval_checkpoint: false
|
| 91 |
+
student:
|
| 92 |
+
arch: vit_large
|
| 93 |
+
patch_size: 16
|
| 94 |
+
drop_path_rate: 0.0
|
| 95 |
+
layerscale: 1.0e-05
|
| 96 |
+
drop_path_uniform: true
|
| 97 |
+
drop_path_shape: uniform
|
| 98 |
+
patch_drop: 0.0
|
| 99 |
+
pretrained_weights: ''
|
| 100 |
+
sin_cos_embeddings: false
|
| 101 |
+
fourier_embeddings: false
|
| 102 |
+
fourier_encoding_dim: 64
|
| 103 |
+
multiple_pos_embeddings: false
|
| 104 |
+
cls_pos_embedding: false
|
| 105 |
+
reg_pos_embedding: false
|
| 106 |
+
ffn_layer: mlp
|
| 107 |
+
ffn_ratio: 4.0
|
| 108 |
+
resume_from_teacher_chkpt: <PATH/TO/HRFT/TEACHER>
|
| 109 |
+
block_chunks: 0
|
| 110 |
+
qkv_bias: true
|
| 111 |
+
proj_bias: true
|
| 112 |
+
ffn_bias: true
|
| 113 |
+
norm_layer: layernormbf16
|
| 114 |
+
n_storage_tokens: 4
|
| 115 |
+
mask_attention: false
|
| 116 |
+
mask_register_attention: false
|
| 117 |
+
untie_cls_and_patch_norms: false
|
| 118 |
+
untie_global_and_local_cls_norm: false
|
| 119 |
+
interpolate_offset: 0.0
|
| 120 |
+
interpolate_antialias: true
|
| 121 |
+
mask_k_bias: true
|
| 122 |
+
init_std_cls: 0.02
|
| 123 |
+
init_std_reg: 0.02
|
| 124 |
+
rescale_weights_by_layer_id: false
|
| 125 |
+
in_chans: 3
|
| 126 |
+
pos_embed_grid_size: 48
|
| 127 |
+
pos_embed_type: ropenew
|
| 128 |
+
pos_embed_rope_gamma: 1.0
|
| 129 |
+
pos_embed_rope_init_multi_frequencies: false
|
| 130 |
+
pos_embed_rope_base: 100
|
| 131 |
+
pos_embed_rope_min_period: null
|
| 132 |
+
pos_embed_rope_max_period: null
|
| 133 |
+
pos_embed_rope_normalize_coords: separate
|
| 134 |
+
pos_embed_rope_shift_coords: null
|
| 135 |
+
pos_embed_rope_jitter_coords: null
|
| 136 |
+
pos_embed_rope_rescale_coords: 2
|
| 137 |
+
pos_embed_rope_dtype: bf16
|
| 138 |
+
sparse24_ranges: []
|
| 139 |
+
sparse24_filter:
|
| 140 |
+
- mlp
|
| 141 |
+
sparse24_default: false
|
| 142 |
+
fp8_enabled: false
|
| 143 |
+
fp8_filter: blocks
|
| 144 |
+
teacher:
|
| 145 |
+
momentum_teacher: 0.994
|
| 146 |
+
final_momentum_teacher: 1
|
| 147 |
+
warmup_teacher_temp: 0.04
|
| 148 |
+
teacher_temp: 0.07
|
| 149 |
+
warmup_teacher_temp_epochs: 120
|
| 150 |
+
in_chans: 3
|
| 151 |
+
distillation:
|
| 152 |
+
enabled: true
|
| 153 |
+
full_cfg_path: <PATH/TO/TEACHER/CONFIG/config.yaml>
|
| 154 |
+
checkpoint_path: <PATH/TO/TEACHER/checkpoint.pth>
|
| 155 |
+
multidistillation:
|
| 156 |
+
enabled: true
|
| 157 |
+
global_batch_size: 1920
|
| 158 |
+
students:
|
| 159 |
+
- name: vits_mlp4_4
|
| 160 |
+
config_path: <PATH/TO/STUDENT/CONFIG/vits_mlp4_4.yaml>
|
| 161 |
+
ranks_range:
|
| 162 |
+
- 0
|
| 163 |
+
- 48
|
| 164 |
+
- name: vitsp_swiglu6_1
|
| 165 |
+
config_path: <PATH/TO/STUDENT/CONFIG/vitsp_swiglu6_1.yaml>
|
| 166 |
+
ranks_range:
|
| 167 |
+
- 48
|
| 168 |
+
- 96
|
| 169 |
+
- name: vitb_mlp4_3
|
| 170 |
+
config_path: <PATH/TO/STUDENT/CONFIG/vitb_mlp4_3.yaml>
|
| 171 |
+
ranks_range:
|
| 172 |
+
- 96
|
| 173 |
+
- 176
|
| 174 |
+
- name: vitl_mlp4_1
|
| 175 |
+
config_path: <PATH/TO/STUDENT/CONFIG/vitl_mlp4_1.yaml>
|
| 176 |
+
ranks_range:
|
| 177 |
+
- 176
|
| 178 |
+
- 296
|
| 179 |
+
hrft:
|
| 180 |
+
enabled: false
|
| 181 |
+
checkpoint_path: ''
|
| 182 |
+
optim:
|
| 183 |
+
epochs: 20
|
| 184 |
+
optimizer: adamw
|
| 185 |
+
weight_decay: 0.04
|
| 186 |
+
weight_decay_end: 0.2
|
| 187 |
+
lr: 0.0002
|
| 188 |
+
warmup_epochs: 0
|
| 189 |
+
min_lr: 1.0e-06
|
| 190 |
+
schedule_trunc_extra: 0.0
|
| 191 |
+
clip_grad: 3.0
|
| 192 |
+
freeze_last_layer_epochs: 0
|
| 193 |
+
scaling_rule: sqrt_wrt_1024
|
| 194 |
+
patch_embed_lr_mult: 0.2
|
| 195 |
+
dino_head_wd_multiplier: 1.0
|
| 196 |
+
layerwise_decay: 0.99
|
| 197 |
+
multi_tensor_optim: true
|
| 198 |
+
dump_fsdp_weights_path: ''
|
| 199 |
+
adamw_beta1: 0.9
|
| 200 |
+
adamw_beta2: 0.999
|
| 201 |
+
crops:
|
| 202 |
+
global_crops_scale:
|
| 203 |
+
- 0.32
|
| 204 |
+
- 1.0
|
| 205 |
+
local_crops_number: 8
|
| 206 |
+
local_crops_scale:
|
| 207 |
+
- 0.05
|
| 208 |
+
- 0.32
|
| 209 |
+
global_crops_size: 256
|
| 210 |
+
local_crops_size: 112
|
| 211 |
+
global_local_crop_pairs_ratios: 1.0
|
| 212 |
+
gram_teacher_crops_size: 256
|
| 213 |
+
localcrops_subset_of_globalcrops: false
|
| 214 |
+
share_color_jitter: false
|
| 215 |
+
horizontal_flips: false
|
| 216 |
+
gram_teacher_no_distortions: false
|
| 217 |
+
rgb_mean:
|
| 218 |
+
- 0.485
|
| 219 |
+
- 0.456
|
| 220 |
+
- 0.406
|
| 221 |
+
rgb_std:
|
| 222 |
+
- 0.229
|
| 223 |
+
- 0.224
|
| 224 |
+
- 0.225
|
| 225 |
+
checkpointing:
|
| 226 |
+
period: 3750
|
| 227 |
+
max_to_keep: 3
|
| 228 |
+
keep_every: 99999999999999999
|
| 229 |
+
schedules:
|
| 230 |
+
weight_decay:
|
| 231 |
+
start: 0.04
|
| 232 |
+
peak: 0.04
|
| 233 |
+
end: 0.04
|
| 234 |
+
warmup_epochs: 0
|
| 235 |
+
teacher_temp:
|
| 236 |
+
start: 0.04
|
| 237 |
+
peak: 0.07
|
| 238 |
+
end: 0.07
|
| 239 |
+
warmup_epochs: 0
|
| 240 |
+
lr:
|
| 241 |
+
start: 0
|
| 242 |
+
peak: 0
|
| 243 |
+
end: 5.0e-05
|
| 244 |
+
warmup_epochs: 0
|
| 245 |
+
freeze_last_layer_epochs: 0
|
| 246 |
+
cosine_epochs: 10
|
| 247 |
+
momentum:
|
| 248 |
+
start: 0.994
|
| 249 |
+
peak: 0.994
|
| 250 |
+
end: 1.0
|
| 251 |
+
warmup_epochs: 0
|
dinov3/configs/train/multi_distillation_test.yaml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MODEL:
|
| 2 |
+
META_ARCHITECTURE: MultiDistillationMetaArch
|
| 3 |
+
multidistillation:
|
| 4 |
+
enabled: true
|
| 5 |
+
global_batch_size: 256
|
| 6 |
+
students:
|
| 7 |
+
- name: vits
|
| 8 |
+
config_path: dinov3/configs/train/multidist_tests/vits_p16.yaml
|
| 9 |
+
ranks_range:
|
| 10 |
+
- 0
|
| 11 |
+
- 4
|
| 12 |
+
- name: vitb
|
| 13 |
+
config_path: dinov3/configs/train/multidist_tests/vitb_p16.yaml
|
| 14 |
+
ranks_range:
|
| 15 |
+
- 4
|
| 16 |
+
- 8
|
| 17 |
+
distillation: # teacher
|
| 18 |
+
enabled: true
|
| 19 |
+
full_cfg_path: dinov3/configs/train/vitl_im1k_lin834.yaml
|
| 20 |
+
checkpoint_path: ignore
|
| 21 |
+
train:
|
| 22 |
+
dataset_path: ImageNet:split=TRAIN
|
| 23 |
+
cache_dataset: false
|
| 24 |
+
centering: "sinkhorn_knopp"
|
| 25 |
+
compile: true
|
| 26 |
+
ibot:
|
| 27 |
+
separate_head: true
|
dinov3/configs/train/multidist_tests/vitb_p16.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# this corresponds to the default config
|
| 2 |
+
train:
|
| 3 |
+
dataset_path: ImageNet:split=TRAIN
|
| 4 |
+
checkpointing: true
|
| 5 |
+
student:
|
| 6 |
+
drop_path_rate: 0.1
|
| 7 |
+
arch: vit_base
|
dinov3/configs/train/multidist_tests/vits_p16.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# this corresponds to the default config
|
| 2 |
+
train:
|
| 3 |
+
dataset_path: ImageNet:split=TRAIN
|
| 4 |
+
student:
|
| 5 |
+
drop_path_rate: 0.1
|
| 6 |
+
arch: vit_small
|
dinov3/configs/train/vitl_im1k_lin834.yaml
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# tested on RSC: /checkpoint/dino/qas/rope/vitl16_im1k/
|
| 2 |
+
# gives 82.2 im1k-knn, 83.3 im1k-linear
|
| 3 |
+
# runs with a total batch size of 2048 (64/gpu, 4 nodes here)
|
| 4 |
+
# runs at 0.57s/iter
|
| 5 |
+
MODEL:
|
| 6 |
+
META_ARCHITECTURE: SSLMetaArch
|
| 7 |
+
DEVICE: cuda
|
| 8 |
+
WEIGHTS: ''
|
| 9 |
+
DTYPE: float32
|
| 10 |
+
compute_precision:
|
| 11 |
+
param_dtype: bf16
|
| 12 |
+
reduce_dtype: fp32
|
| 13 |
+
sharding_strategy: SHARD_GRAD_OP
|
| 14 |
+
dino:
|
| 15 |
+
loss_weight: 1.0
|
| 16 |
+
global_ignore_diagonal: true
|
| 17 |
+
head_n_prototypes: 65536
|
| 18 |
+
head_bottleneck_dim: 256
|
| 19 |
+
head_norm_last_layer: false
|
| 20 |
+
head_nlayers: 3
|
| 21 |
+
head_hidden_dim: 2048
|
| 22 |
+
koleo_loss_weight: 0.1
|
| 23 |
+
koleo_loss_distributed: false
|
| 24 |
+
koleo_topk: 1
|
| 25 |
+
koleo_distributed_replicas: 0
|
| 26 |
+
force_weight_norm: false
|
| 27 |
+
ibot:
|
| 28 |
+
loss_weight: 1.0
|
| 29 |
+
mask_sample_probability: 0.5
|
| 30 |
+
mask_ratio_min_max:
|
| 31 |
+
- 0.1
|
| 32 |
+
- 0.5
|
| 33 |
+
mask_random_circular_shift: false
|
| 34 |
+
force_masking_even_with_zero_weight: false
|
| 35 |
+
separate_head: true
|
| 36 |
+
head_n_prototypes: 65536
|
| 37 |
+
head_bottleneck_dim: 256
|
| 38 |
+
head_norm_last_layer: false
|
| 39 |
+
head_nlayers: 3
|
| 40 |
+
head_hidden_dim: 2048
|
| 41 |
+
train:
|
| 42 |
+
batch_size_per_gpu: 64
|
| 43 |
+
dataset_path: ImageNet:split=TRAIN
|
| 44 |
+
output_dir: /checkpoint/dino/qas/rope/vitl16_im1k
|
| 45 |
+
saveckp_freq: 20
|
| 46 |
+
seed: 0
|
| 47 |
+
num_workers: 10
|
| 48 |
+
OFFICIAL_EPOCH_LENGTH: 1250
|
| 49 |
+
monitor_gradient_norm: false
|
| 50 |
+
chunk_schedule: []
|
| 51 |
+
cache_dataset: true
|
| 52 |
+
use_teacher_head: true
|
| 53 |
+
learn_from_teacher_tokens: false
|
| 54 |
+
centering: sinkhorn_knopp
|
| 55 |
+
checkpointing: false
|
| 56 |
+
compile: true
|
| 57 |
+
cudagraphs: false
|
| 58 |
+
cell_augmentation: false
|
| 59 |
+
cell_augmentation_type: hpa
|
| 60 |
+
student:
|
| 61 |
+
arch: vit_large
|
| 62 |
+
patch_size: 16
|
| 63 |
+
drop_path_rate: 0.3
|
| 64 |
+
layerscale: 1.0e-05
|
| 65 |
+
patch_drop: 0.0
|
| 66 |
+
pretrained_weights: ''
|
| 67 |
+
ffn_layer: mlp
|
| 68 |
+
ffn_ratio: 4.0
|
| 69 |
+
resume_from_teacher_chkpt: ''
|
| 70 |
+
qkv_bias: true
|
| 71 |
+
proj_bias: true
|
| 72 |
+
ffn_bias: true
|
| 73 |
+
norm_layer: layernorm
|
| 74 |
+
n_storage_tokens: 0
|
| 75 |
+
mask_k_bias: false
|
| 76 |
+
in_chans: 3
|
| 77 |
+
pos_embed_type: rope
|
| 78 |
+
pos_embed_rope_base: 100.0
|
| 79 |
+
pos_embed_rope_min_period: null
|
| 80 |
+
pos_embed_rope_max_period: null
|
| 81 |
+
pos_embed_rope_normalize_coords: separate # min, max, separate
|
| 82 |
+
pos_embed_rope_shift_coords: null
|
| 83 |
+
pos_embed_rope_jitter_coords: null
|
| 84 |
+
pos_embed_rope_rescale_coords: null
|
| 85 |
+
pos_embed_rope_dtype: bf16
|
| 86 |
+
fp8_enabled: False # Convert Linear layers to operate in fp8 precision
|
| 87 |
+
fp8_filter: "blocks" # Regex that must appear in module path; empty means everything
|
| 88 |
+
teacher:
|
| 89 |
+
momentum_teacher: 0.992
|
| 90 |
+
final_momentum_teacher: 1
|
| 91 |
+
warmup_teacher_temp: 0.04
|
| 92 |
+
teacher_temp: 0.07
|
| 93 |
+
warmup_teacher_temp_epochs: 30
|
| 94 |
+
in_chans: 3
|
| 95 |
+
distillation:
|
| 96 |
+
enabled: false
|
| 97 |
+
full_cfg_path: ''
|
| 98 |
+
checkpoint_path: ''
|
| 99 |
+
multidistillation:
|
| 100 |
+
enabled: false
|
| 101 |
+
hrft:
|
| 102 |
+
enabled: false
|
| 103 |
+
checkpoint_path: ''
|
| 104 |
+
optim:
|
| 105 |
+
epochs: 100
|
| 106 |
+
optimizer: adamw
|
| 107 |
+
weight_decay: 0.04
|
| 108 |
+
weight_decay_end: 0.4
|
| 109 |
+
lr: 0.001
|
| 110 |
+
warmup_epochs: 10
|
| 111 |
+
min_lr: 1.0e-06
|
| 112 |
+
clip_grad: 3.0
|
| 113 |
+
freeze_last_layer_epochs: 1
|
| 114 |
+
scaling_rule: sqrt_wrt_1024
|
| 115 |
+
patch_embed_lr_mult: 0.2
|
| 116 |
+
dino_head_wd_multiplier: 1.0
|
| 117 |
+
layerwise_decay: 0.9
|
| 118 |
+
multi_tensor_optim: true
|
| 119 |
+
dump_fsdp_weights_path: ''
|
| 120 |
+
adamw_beta1: 0.9
|
| 121 |
+
adamw_beta2: 0.999
|
| 122 |
+
crops:
|
| 123 |
+
global_crops_scale:
|
| 124 |
+
- 0.32
|
| 125 |
+
- 1.0
|
| 126 |
+
local_crops_number: 8
|
| 127 |
+
local_crops_scale:
|
| 128 |
+
- 0.05
|
| 129 |
+
- 0.32
|
| 130 |
+
global_crops_size: 224
|
| 131 |
+
local_crops_size: 96
|
| 132 |
+
localcrops_subset_of_globalcrops: false
|
| 133 |
+
share_color_jitter: false
|
| 134 |
+
horizontal_flips: true
|
| 135 |
+
evaluation:
|
| 136 |
+
eval_period_iterations: 12500
|
| 137 |
+
low_freq_every: 5
|
| 138 |
+
config_files:
|
| 139 |
+
high_freq: benchmark_high_frequency.yaml
|
| 140 |
+
low_freq: benchmark_low_frequency.yaml
|
| 141 |
+
checkpointing:
|
| 142 |
+
period: 3750
|
| 143 |
+
max_to_keep: 3
|
dinov3/data/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
from .adapters import DatasetWithEnumeratedTargets
|
| 7 |
+
from .augmentations import DataAugmentationDINO
|
| 8 |
+
from .collate import collate_data_and_cast
|
| 9 |
+
from .loaders import SamplerType, make_data_loader, make_dataset
|
| 10 |
+
from .meta_loaders import CombinedDataLoader
|
| 11 |
+
from .masking import MaskingGenerator
|
| 12 |
+
from .transforms import make_classification_eval_transform, make_classification_train_transform
|
dinov3/data/adapters.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
from typing import Any, Optional, Tuple
|
| 7 |
+
|
| 8 |
+
from torch.utils.data import Dataset
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def extend_samples_with_index(dataset_class):
|
| 12 |
+
class DatasetWithIndex(dataset_class):
|
| 13 |
+
def __init__(self, **kwargs) -> None:
|
| 14 |
+
root = dataset_class.get_root()
|
| 15 |
+
super().__init__(root=root, **kwargs)
|
| 16 |
+
|
| 17 |
+
def __getitem__(self, index: int):
|
| 18 |
+
image, target = super().__getitem__(index)
|
| 19 |
+
return image, target, index
|
| 20 |
+
|
| 21 |
+
return DatasetWithIndex
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class DatasetWithEnumeratedTargets(Dataset):
|
| 25 |
+
"""
|
| 26 |
+
If pad_dataset is set, pads based on torch's DistributedSampler implementation, which
|
| 27 |
+
with drop_last=False pads the last batch to be a multiple of the world size.
|
| 28 |
+
https://github.com/pytorch/pytorch/blob/main/torch/utils/data/distributed.py#L91
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
def __init__(self, dataset: Dataset, pad_dataset: bool = False, num_replicas: Optional[int] = None):
|
| 32 |
+
self._dataset = dataset
|
| 33 |
+
self._size = len(self._dataset)
|
| 34 |
+
self._padded_size = self._size
|
| 35 |
+
self._pad_dataset = pad_dataset
|
| 36 |
+
if self._pad_dataset:
|
| 37 |
+
assert num_replicas is not None, "num_replicas should be set if pad_dataset is True"
|
| 38 |
+
self._padded_size = num_replicas * ((len(dataset) + num_replicas - 1) // num_replicas)
|
| 39 |
+
|
| 40 |
+
def get_image_relpath(self, index: int) -> str:
|
| 41 |
+
assert self._pad_dataset or index < self._size
|
| 42 |
+
return self._dataset.get_image_relpath(index % self._size)
|
| 43 |
+
|
| 44 |
+
def get_image_data(self, index: int) -> bytes:
|
| 45 |
+
assert self._pad_dataset or index < self._size
|
| 46 |
+
return self._dataset.get_image_data(index % self._size)
|
| 47 |
+
|
| 48 |
+
def get_target(self, index: int) -> Tuple[Any, int]:
|
| 49 |
+
target = self._dataset.get_target(index % self._size)
|
| 50 |
+
if index >= self._size:
|
| 51 |
+
assert self._pad_dataset
|
| 52 |
+
return (-1, target)
|
| 53 |
+
return (index, target)
|
| 54 |
+
|
| 55 |
+
def get_sample_decoder(self, index: int) -> Any:
|
| 56 |
+
assert self._pad_dataset or index < self._size
|
| 57 |
+
return self._dataset.get_sample_decoder(index % self._size)
|
| 58 |
+
|
| 59 |
+
def __getitem__(self, index: int) -> Tuple[Any, Tuple[Any, int]]:
|
| 60 |
+
image, target = self._dataset[index % self._size]
|
| 61 |
+
if index >= self._size:
|
| 62 |
+
assert self._pad_dataset
|
| 63 |
+
return image, (-1, target)
|
| 64 |
+
target = index if target is None else target
|
| 65 |
+
return image, (index, target)
|
| 66 |
+
|
| 67 |
+
def __len__(self) -> int:
|
| 68 |
+
return self._padded_size
|
dinov3/data/augmentations.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import logging
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import torch
|
| 10 |
+
from torch import nn
|
| 11 |
+
from torchvision.transforms import v2
|
| 12 |
+
|
| 13 |
+
from dinov3.data.transforms import IMAGENET_DEFAULT_MEAN, IMAGENET_DEFAULT_STD, GaussianBlur, make_normalize_transform
|
| 14 |
+
|
| 15 |
+
logger = logging.getLogger("dinov3")
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class DataAugmentationDINO(object):
|
| 19 |
+
def __init__(
|
| 20 |
+
self,
|
| 21 |
+
global_crops_scale,
|
| 22 |
+
local_crops_scale,
|
| 23 |
+
local_crops_number,
|
| 24 |
+
global_crops_size=224,
|
| 25 |
+
local_crops_size=96,
|
| 26 |
+
gram_teacher_crops_size=None,
|
| 27 |
+
gram_teacher_no_distortions=False,
|
| 28 |
+
teacher_no_color_jitter=False,
|
| 29 |
+
local_crops_subset_of_global_crops=False,
|
| 30 |
+
patch_size=16,
|
| 31 |
+
share_color_jitter=False,
|
| 32 |
+
horizontal_flips=True,
|
| 33 |
+
mean=IMAGENET_DEFAULT_MEAN,
|
| 34 |
+
std=IMAGENET_DEFAULT_STD,
|
| 35 |
+
):
|
| 36 |
+
self.global_crops_scale = global_crops_scale
|
| 37 |
+
self.local_crops_scale = local_crops_scale
|
| 38 |
+
self.local_crops_number = local_crops_number
|
| 39 |
+
self.global_crops_size = global_crops_size
|
| 40 |
+
self.local_crops_size = local_crops_size
|
| 41 |
+
self.gram_teacher_crops_size = gram_teacher_crops_size
|
| 42 |
+
self.gram_teacher_no_distortions = gram_teacher_no_distortions
|
| 43 |
+
self.teacher_no_color_jitter = teacher_no_color_jitter
|
| 44 |
+
self.local_crops_subset_of_global_crops = local_crops_subset_of_global_crops
|
| 45 |
+
self.patch_size = patch_size
|
| 46 |
+
self.share_color_jitter = share_color_jitter
|
| 47 |
+
self.mean = mean
|
| 48 |
+
self.std = std
|
| 49 |
+
|
| 50 |
+
logger.info("###################################")
|
| 51 |
+
logger.info("Using data augmentation parameters:")
|
| 52 |
+
logger.info(f"global_crops_scale: {global_crops_scale}")
|
| 53 |
+
logger.info(f"local_crops_scale: {local_crops_scale}")
|
| 54 |
+
logger.info(f"local_crops_number: {local_crops_number}")
|
| 55 |
+
logger.info(f"global_crops_size: {global_crops_size}")
|
| 56 |
+
logger.info(f"local_crops_size: {local_crops_size}")
|
| 57 |
+
logger.info(f"gram_crops_size: {gram_teacher_crops_size}")
|
| 58 |
+
logger.info(f"gram_teacher_no_distortions: {gram_teacher_no_distortions}")
|
| 59 |
+
logger.info(f"teacher_no_color_jitter: {teacher_no_color_jitter}")
|
| 60 |
+
logger.info(f"local_crops_subset_of_global_crops: {local_crops_subset_of_global_crops}")
|
| 61 |
+
logger.info(f"patch_size if local_crops_subset_of_global_crops: {patch_size}")
|
| 62 |
+
logger.info(f"share_color_jitter: {share_color_jitter}")
|
| 63 |
+
logger.info(f"horizontal flips: {horizontal_flips}")
|
| 64 |
+
logger.info("###################################")
|
| 65 |
+
|
| 66 |
+
# Global crops and gram teacher crops can have different sizes. We first take a crop of the maximum size
|
| 67 |
+
# and then resize it to the desired size for global and gram teacher crops.
|
| 68 |
+
global_crop_max_size = max(global_crops_size, gram_teacher_crops_size if gram_teacher_crops_size else 0)
|
| 69 |
+
|
| 70 |
+
# random resized crop and flip
|
| 71 |
+
self.geometric_augmentation_global = v2.Compose(
|
| 72 |
+
[
|
| 73 |
+
v2.RandomResizedCrop(
|
| 74 |
+
global_crop_max_size,
|
| 75 |
+
scale=global_crops_scale,
|
| 76 |
+
interpolation=v2.InterpolationMode.BICUBIC,
|
| 77 |
+
),
|
| 78 |
+
v2.RandomHorizontalFlip(p=0.5 if horizontal_flips else 0.0),
|
| 79 |
+
]
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
resize_global = nn.Identity() # Resize transform applied to global crops after random crop
|
| 83 |
+
self.resize_global_post_transf = (
|
| 84 |
+
nn.Identity()
|
| 85 |
+
) # Resize transform applied to global crops after all other transforms
|
| 86 |
+
self.resize_gram_teacher = None # Resize transform applied to crops for gram teacher
|
| 87 |
+
if gram_teacher_crops_size is not None:
|
| 88 |
+
# All resize transforms will do nothing if the crop size is already the desired size.
|
| 89 |
+
if gram_teacher_no_distortions:
|
| 90 |
+
# When there a no distortions for the gram teacher crop, we can resize before the distortions.
|
| 91 |
+
# This is the preferred order, because it keeps the image size for the augmentations consistent,
|
| 92 |
+
# which matters e.g. for GaussianBlur.
|
| 93 |
+
resize_global = v2.Resize(
|
| 94 |
+
global_crops_size,
|
| 95 |
+
interpolation=v2.InterpolationMode.BICUBIC,
|
| 96 |
+
)
|
| 97 |
+
else:
|
| 98 |
+
# When there a no distortions for the gram teacher crop, we need to resize after the distortions,
|
| 99 |
+
# because the distortions are shared between global and gram teacher crops.
|
| 100 |
+
self.resize_global_post_transf = v2.Resize(
|
| 101 |
+
global_crops_size,
|
| 102 |
+
interpolation=v2.InterpolationMode.BICUBIC,
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
self.resize_gram_teacher = v2.Resize(
|
| 106 |
+
gram_teacher_crops_size,
|
| 107 |
+
interpolation=v2.InterpolationMode.BICUBIC,
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
self.geometric_augmentation_local = v2.Compose(
|
| 111 |
+
[
|
| 112 |
+
v2.RandomResizedCrop(
|
| 113 |
+
local_crops_size,
|
| 114 |
+
scale=local_crops_scale,
|
| 115 |
+
interpolation=v2.InterpolationMode.BICUBIC,
|
| 116 |
+
),
|
| 117 |
+
v2.RandomHorizontalFlip(p=0.5 if horizontal_flips else 0.0),
|
| 118 |
+
]
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
# color distortions / blurring
|
| 122 |
+
color_jittering = v2.Compose(
|
| 123 |
+
[
|
| 124 |
+
v2.RandomApply(
|
| 125 |
+
[v2.ColorJitter(brightness=0.4, contrast=0.4, saturation=0.2, hue=0.1)],
|
| 126 |
+
p=0.8,
|
| 127 |
+
),
|
| 128 |
+
v2.RandomGrayscale(p=0.2),
|
| 129 |
+
]
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
global_transfo1_extra = GaussianBlur(p=1.0)
|
| 133 |
+
|
| 134 |
+
global_transfo2_extra = v2.Compose(
|
| 135 |
+
[
|
| 136 |
+
GaussianBlur(p=0.1),
|
| 137 |
+
v2.RandomSolarize(threshold=128, p=0.2),
|
| 138 |
+
]
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
local_transfo_extra = GaussianBlur(p=0.5)
|
| 142 |
+
|
| 143 |
+
# normalization
|
| 144 |
+
self.normalize = v2.Compose(
|
| 145 |
+
[
|
| 146 |
+
v2.ToImage(),
|
| 147 |
+
v2.ToDtype(torch.float32, scale=True),
|
| 148 |
+
make_normalize_transform(mean=mean, std=std),
|
| 149 |
+
]
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
if self.share_color_jitter:
|
| 153 |
+
self.color_jittering = color_jittering
|
| 154 |
+
self.global_transfo1 = v2.Compose([resize_global, global_transfo1_extra, self.normalize])
|
| 155 |
+
self.global_transfo2 = v2.Compose([resize_global, global_transfo2_extra, self.normalize])
|
| 156 |
+
self.local_transfo = v2.Compose([local_transfo_extra, self.normalize])
|
| 157 |
+
else:
|
| 158 |
+
self.global_transfo1 = v2.Compose(
|
| 159 |
+
[resize_global, color_jittering, global_transfo1_extra, self.normalize]
|
| 160 |
+
)
|
| 161 |
+
self.global_transfo2 = v2.Compose(
|
| 162 |
+
[resize_global, color_jittering, global_transfo2_extra, self.normalize]
|
| 163 |
+
)
|
| 164 |
+
self.local_transfo = v2.Compose([color_jittering, local_transfo_extra, self.normalize])
|
| 165 |
+
|
| 166 |
+
def __call__(self, image):
|
| 167 |
+
output = {}
|
| 168 |
+
output["weak_flag"] = True # some residual from mugs
|
| 169 |
+
|
| 170 |
+
if self.share_color_jitter:
|
| 171 |
+
image = self.color_jittering(image)
|
| 172 |
+
|
| 173 |
+
# global crops:
|
| 174 |
+
im1_base = self.geometric_augmentation_global(image)
|
| 175 |
+
global_crop_1_transf = self.global_transfo1(im1_base)
|
| 176 |
+
global_crop_1 = self.resize_global_post_transf(global_crop_1_transf)
|
| 177 |
+
|
| 178 |
+
im2_base = self.geometric_augmentation_global(image)
|
| 179 |
+
global_crop_2_transf = self.global_transfo2(im2_base)
|
| 180 |
+
global_crop_2 = self.resize_global_post_transf(global_crop_2_transf)
|
| 181 |
+
|
| 182 |
+
output["global_crops"] = [global_crop_1, global_crop_2]
|
| 183 |
+
|
| 184 |
+
# global crops for teacher:
|
| 185 |
+
if self.teacher_no_color_jitter:
|
| 186 |
+
output["global_crops_teacher"] = [
|
| 187 |
+
self.normalize(im1_base),
|
| 188 |
+
self.normalize(im2_base),
|
| 189 |
+
]
|
| 190 |
+
else:
|
| 191 |
+
output["global_crops_teacher"] = [global_crop_1, global_crop_2]
|
| 192 |
+
|
| 193 |
+
if self.gram_teacher_crops_size is not None:
|
| 194 |
+
# crops for gram teacher:
|
| 195 |
+
if self.gram_teacher_no_distortions:
|
| 196 |
+
gram_crop_1 = self.normalize(self.resize_gram_teacher(im1_base))
|
| 197 |
+
gram_crop_2 = self.normalize(self.resize_gram_teacher(im2_base))
|
| 198 |
+
else:
|
| 199 |
+
gram_crop_1 = self.resize_gram_teacher(global_crop_1_transf)
|
| 200 |
+
gram_crop_2 = self.resize_gram_teacher(global_crop_2_transf)
|
| 201 |
+
output["gram_teacher_crops"] = [gram_crop_1, gram_crop_2]
|
| 202 |
+
|
| 203 |
+
# local crops:
|
| 204 |
+
if self.local_crops_subset_of_global_crops:
|
| 205 |
+
_local_crops = [self.local_transfo(im1_base) for _ in range(self.local_crops_number // 2)] + [
|
| 206 |
+
self.local_transfo(im2_base) for _ in range(self.local_crops_number // 2)
|
| 207 |
+
]
|
| 208 |
+
|
| 209 |
+
local_crops = []
|
| 210 |
+
offsets = []
|
| 211 |
+
gs = self.global_crops_size
|
| 212 |
+
ls = self.local_crops_size
|
| 213 |
+
for img in _local_crops:
|
| 214 |
+
rx, ry = np.random.randint(0, (gs - ls) // self.patch_size, 2) * self.patch_size
|
| 215 |
+
local_crops.append(img[:, rx : rx + ls, ry : ry + ls])
|
| 216 |
+
offsets.append((rx, ry))
|
| 217 |
+
|
| 218 |
+
output["local_crops"] = local_crops
|
| 219 |
+
output["offsets"] = offsets
|
| 220 |
+
else:
|
| 221 |
+
local_crops = [
|
| 222 |
+
self.local_transfo(self.geometric_augmentation_local(image)) for _ in range(self.local_crops_number)
|
| 223 |
+
]
|
| 224 |
+
output["local_crops"] = local_crops
|
| 225 |
+
output["offsets"] = ()
|
| 226 |
+
|
| 227 |
+
return output
|
dinov3/data/collate.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import random
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def collate_data_and_cast(
|
| 12 |
+
samples_list,
|
| 13 |
+
mask_ratio_tuple,
|
| 14 |
+
mask_probability,
|
| 15 |
+
dtype,
|
| 16 |
+
n_tokens=None,
|
| 17 |
+
mask_generator=None,
|
| 18 |
+
random_circular_shift=False,
|
| 19 |
+
local_batch_size=None,
|
| 20 |
+
):
|
| 21 |
+
n_global_crops = len(samples_list[0][0]["global_crops"])
|
| 22 |
+
n_local_crops = len(samples_list[0][0]["local_crops"])
|
| 23 |
+
|
| 24 |
+
collated_global_crops = torch.stack(
|
| 25 |
+
[s[0]["global_crops"][i] for i in range(n_global_crops) for s in samples_list]
|
| 26 |
+
) # [n_global_crops, B, ...]
|
| 27 |
+
collated_local_crops = torch.stack([s[0]["local_crops"][i] for i in range(n_local_crops) for s in samples_list])
|
| 28 |
+
if "gram_teacher_crops" in samples_list[0][0]:
|
| 29 |
+
collated_gram_teacher_crops = torch.stack(
|
| 30 |
+
[s[0]["gram_teacher_crops"][i] for i in range(n_global_crops) for s in samples_list]
|
| 31 |
+
) # [n_global_crops, B, ...]
|
| 32 |
+
else:
|
| 33 |
+
collated_gram_teacher_crops = None
|
| 34 |
+
|
| 35 |
+
if local_batch_size is not None:
|
| 36 |
+
# multi-distillation case, number of masks is different because the number of samples masked
|
| 37 |
+
# is different of the number of samples passed into the teacher initially
|
| 38 |
+
B = n_global_crops * local_batch_size
|
| 39 |
+
else:
|
| 40 |
+
B = len(collated_global_crops)
|
| 41 |
+
N = n_tokens
|
| 42 |
+
n_samples_masked = int(B * mask_probability)
|
| 43 |
+
probs = torch.linspace(*mask_ratio_tuple, n_samples_masked + 1)
|
| 44 |
+
upperbound = 0
|
| 45 |
+
masks_list = []
|
| 46 |
+
for i in range(0, n_samples_masked):
|
| 47 |
+
prob_max = probs[i + 1]
|
| 48 |
+
mask = torch.BoolTensor(mask_generator(int(N * prob_max)))
|
| 49 |
+
if random_circular_shift: # apply le random circular shift to
|
| 50 |
+
shift_x, shift_y = (
|
| 51 |
+
random.randint(0, mask.shape[0] - 1),
|
| 52 |
+
random.randint(0, mask.shape[1] - 1),
|
| 53 |
+
)
|
| 54 |
+
mask = torch.roll(mask, (shift_x, shift_y), (0, 1))
|
| 55 |
+
masks_list.append(mask)
|
| 56 |
+
upperbound += int(N * prob_max)
|
| 57 |
+
for _ in range(n_samples_masked, B):
|
| 58 |
+
masks_list.append(torch.BoolTensor(mask_generator(0)))
|
| 59 |
+
|
| 60 |
+
random.shuffle(masks_list)
|
| 61 |
+
|
| 62 |
+
collated_masks = torch.stack(masks_list).flatten(1)
|
| 63 |
+
mask_indices_list = collated_masks.flatten().nonzero().flatten()
|
| 64 |
+
|
| 65 |
+
masks_weight = (1 / collated_masks.sum(-1).clamp(min=1.0)).unsqueeze(-1).expand_as(collated_masks)[collated_masks]
|
| 66 |
+
|
| 67 |
+
out = {
|
| 68 |
+
"collated_global_crops": collated_global_crops.to(dtype),
|
| 69 |
+
"collated_local_crops": collated_local_crops.to(dtype),
|
| 70 |
+
"collated_masks": collated_masks,
|
| 71 |
+
"mask_indices_list": mask_indices_list,
|
| 72 |
+
"masks_weight": masks_weight,
|
| 73 |
+
"upperbound": upperbound,
|
| 74 |
+
"n_masked_patches": torch.full((1,), fill_value=mask_indices_list.shape[0], dtype=torch.long),
|
| 75 |
+
}
|
| 76 |
+
if collated_gram_teacher_crops is not None:
|
| 77 |
+
out["collated_gram_teacher_crops"] = collated_gram_teacher_crops.to(dtype)
|
| 78 |
+
return out
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
# def get_batch_subset(collated_data_batch, target_bs):
|
| 82 |
+
def get_batch_subset(collated_data_batch, divide_by):
|
| 83 |
+
old_bs = collated_data_batch["collated_global_crops"].shape[0] // 2
|
| 84 |
+
target_bs = (old_bs + divide_by - 1) // divide_by
|
| 85 |
+
collated_global_crops = (
|
| 86 |
+
collated_data_batch["collated_global_crops"].unflatten(0, (2, old_bs)).narrow(1, 0, target_bs).flatten(0, 1)
|
| 87 |
+
)
|
| 88 |
+
collated_local_crops = (
|
| 89 |
+
collated_data_batch["collated_local_crops"].unflatten(0, (-1, old_bs)).narrow(1, 0, target_bs).flatten(0, 1)
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
masks_old_bs = collated_data_batch["collated_masks"].shape[0] // 2
|
| 93 |
+
masks_target_bs = masks_old_bs // divide_by
|
| 94 |
+
collated_masks = (
|
| 95 |
+
collated_data_batch["collated_masks"]
|
| 96 |
+
.unflatten(0, (2, masks_old_bs))
|
| 97 |
+
.narrow(1, 0, masks_target_bs)
|
| 98 |
+
.flatten(0, 1)
|
| 99 |
+
)
|
| 100 |
+
mask_indices_list = collated_masks.flatten().nonzero().flatten()
|
| 101 |
+
|
| 102 |
+
while mask_indices_list.shape[0] == 0:
|
| 103 |
+
_unbind = list(collated_data_batch["collated_masks"].unbind(0))
|
| 104 |
+
random.shuffle(_unbind)
|
| 105 |
+
_bind = torch.stack(_unbind, dim=0)
|
| 106 |
+
collated_masks = _bind.unflatten(0, (2, masks_old_bs)).narrow(1, 0, masks_target_bs).flatten(0, 1)
|
| 107 |
+
mask_indices_list = collated_masks.flatten().nonzero().flatten()
|
| 108 |
+
|
| 109 |
+
masks_weight = (1 / collated_masks.sum(-1).clamp(min=1.0)).unsqueeze(-1).expand_as(collated_masks)[collated_masks]
|
| 110 |
+
upperbound = collated_data_batch["upperbound"]
|
| 111 |
+
|
| 112 |
+
new_batch = {
|
| 113 |
+
"collated_global_crops": collated_global_crops,
|
| 114 |
+
"collated_local_crops": collated_local_crops,
|
| 115 |
+
"collated_masks": collated_masks,
|
| 116 |
+
"mask_indices_list": mask_indices_list,
|
| 117 |
+
"masks_weight": masks_weight,
|
| 118 |
+
"upperbound": upperbound,
|
| 119 |
+
"n_masked_patches": torch.full((1,), fill_value=mask_indices_list.shape[0], dtype=torch.long),
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
if "global_batch_size" in collated_data_batch.keys():
|
| 123 |
+
new_batch["global_batch_size"] = collated_data_batch["global_batch_size"] // divide_by
|
| 124 |
+
|
| 125 |
+
return new_batch
|
dinov3/data/datasets/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
from .ade20k import ADE20K
|
| 7 |
+
from .coco_captions import CocoCaptions
|
| 8 |
+
from .image_net import ImageNet
|
| 9 |
+
from .image_net_22k import ImageNet22k
|
dinov3/data/datasets/ade20k.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
from enum import Enum
|
| 8 |
+
from typing import Any, Callable, List, Optional, Tuple, Union
|
| 9 |
+
|
| 10 |
+
from PIL import Image
|
| 11 |
+
|
| 12 |
+
from .decoders import Decoder, DenseTargetDecoder, ImageDataDecoder
|
| 13 |
+
from .extended import ExtendedVisionDataset
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class _Split(Enum):
|
| 17 |
+
TRAIN = "train"
|
| 18 |
+
VAL = "val"
|
| 19 |
+
|
| 20 |
+
@property
|
| 21 |
+
def dirname(self) -> str:
|
| 22 |
+
return {
|
| 23 |
+
_Split.TRAIN: "training",
|
| 24 |
+
_Split.VAL: "validation",
|
| 25 |
+
}[self]
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _file_to_segmentation_path(file_name: str, segm_base_path: str) -> str:
|
| 29 |
+
file_name_noext = os.path.splitext(file_name)[0]
|
| 30 |
+
return os.path.join(segm_base_path, file_name_noext + ".png")
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _load_segmentation(root: str, split_file_names: List[str]):
|
| 34 |
+
segm_base_path = "annotations"
|
| 35 |
+
segmentation_paths = [_file_to_segmentation_path(file_name, segm_base_path) for file_name in split_file_names]
|
| 36 |
+
return segmentation_paths
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _load_file_paths(root: str, split: _Split) -> Tuple[List[str], List[str]]:
|
| 40 |
+
with open(os.path.join(root, f"ADE20K_object150_{split.value}.txt")) as f:
|
| 41 |
+
split_file_names = sorted(f.read().strip().split("\n"))
|
| 42 |
+
|
| 43 |
+
all_segmentation_paths = _load_segmentation(root, split_file_names)
|
| 44 |
+
file_names = [os.path.join("images", el) for el in split_file_names]
|
| 45 |
+
return file_names, all_segmentation_paths
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class ADE20K(ExtendedVisionDataset):
|
| 49 |
+
Split = Union[_Split]
|
| 50 |
+
Labels = Union[Image.Image]
|
| 51 |
+
|
| 52 |
+
def __init__(
|
| 53 |
+
self,
|
| 54 |
+
split: "ADE20K.Split",
|
| 55 |
+
root: Optional[str] = None,
|
| 56 |
+
transforms: Optional[Callable] = None,
|
| 57 |
+
transform: Optional[Callable] = None,
|
| 58 |
+
target_transform: Optional[Callable] = None,
|
| 59 |
+
image_decoder: Decoder = ImageDataDecoder,
|
| 60 |
+
target_decoder: Decoder = DenseTargetDecoder,
|
| 61 |
+
) -> None:
|
| 62 |
+
super().__init__(
|
| 63 |
+
root=root,
|
| 64 |
+
transforms=transforms,
|
| 65 |
+
transform=transform,
|
| 66 |
+
target_transform=target_transform,
|
| 67 |
+
image_decoder=image_decoder,
|
| 68 |
+
target_decoder=target_decoder,
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
self.image_paths, self.target_paths = _load_file_paths(root, split)
|
| 72 |
+
|
| 73 |
+
def get_image_data(self, index: int) -> bytes:
|
| 74 |
+
image_relpath = self.image_paths[index]
|
| 75 |
+
image_full_path = os.path.join(self.root, image_relpath)
|
| 76 |
+
with open(image_full_path, mode="rb") as f:
|
| 77 |
+
image_data = f.read()
|
| 78 |
+
return image_data
|
| 79 |
+
|
| 80 |
+
def get_target(self, index: int) -> Any:
|
| 81 |
+
target_relpath = self.target_paths[index]
|
| 82 |
+
target_full_path = os.path.join(self.root, target_relpath)
|
| 83 |
+
with open(target_full_path, mode="rb") as f:
|
| 84 |
+
target_data = f.read()
|
| 85 |
+
return target_data
|
| 86 |
+
|
| 87 |
+
def __len__(self) -> int:
|
| 88 |
+
return len(self.image_paths)
|
dinov3/data/datasets/coco_captions.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import json
|
| 7 |
+
import os
|
| 8 |
+
import random
|
| 9 |
+
from enum import Enum
|
| 10 |
+
from typing import Callable, Dict, List, Optional, Union
|
| 11 |
+
|
| 12 |
+
from .decoders import ImageDataDecoder, TargetDecoder
|
| 13 |
+
from .extended import ExtendedVisionDataset
|
| 14 |
+
|
| 15 |
+
# Dataset: https://www.kaggle.com/datasets/nikhil7280/coco-image-caption
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class _Split(Enum):
|
| 19 |
+
TRAIN = "train"
|
| 20 |
+
VAL = "val"
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def read_images_and_captions(root: str, split: _Split) -> List[Dict]:
|
| 24 |
+
image_dir = None
|
| 25 |
+
if _Split(split) == _Split.TRAIN:
|
| 26 |
+
annotations_full_path = os.path.join(
|
| 27 |
+
root, "annotations_trainval2014/annotations/captions_train2014.json"
|
| 28 |
+
)
|
| 29 |
+
image_dir = os.path.join(root, "train2014/train2014")
|
| 30 |
+
else:
|
| 31 |
+
annotations_full_path = os.path.join(
|
| 32 |
+
root, "annotations_trainval2017/annotations/captions_train2017.json"
|
| 33 |
+
)
|
| 34 |
+
image_dir = os.path.join(root, "val2017/val2017")
|
| 35 |
+
with open(annotations_full_path) as f:
|
| 36 |
+
all_annotations = json.load(f)
|
| 37 |
+
data = {}
|
| 38 |
+
for item in all_annotations["images"]:
|
| 39 |
+
id = item["id"]
|
| 40 |
+
data[id] = {
|
| 41 |
+
"id": None,
|
| 42 |
+
"image": os.path.join(image_dir, item["file_name"]),
|
| 43 |
+
"captions": [],
|
| 44 |
+
}
|
| 45 |
+
for item in all_annotations["annotations"]:
|
| 46 |
+
data[item["image_id"]]["id"] = item["image_id"]
|
| 47 |
+
data[item["image_id"]]["captions"].append(item["caption"])
|
| 48 |
+
return list(data.values())
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class CocoCaptions(ExtendedVisionDataset):
|
| 52 |
+
Split = Union[_Split]
|
| 53 |
+
|
| 54 |
+
def __init__(
|
| 55 |
+
self,
|
| 56 |
+
*,
|
| 57 |
+
split: "CocoCaptions.Split",
|
| 58 |
+
root: Optional[str] = None,
|
| 59 |
+
transforms: Optional[Callable] = None,
|
| 60 |
+
transform: Optional[Callable] = None,
|
| 61 |
+
target_transform: Optional[Callable] = None,
|
| 62 |
+
) -> None:
|
| 63 |
+
super().__init__(
|
| 64 |
+
root=root,
|
| 65 |
+
transforms=transforms,
|
| 66 |
+
transform=transform,
|
| 67 |
+
target_transform=target_transform,
|
| 68 |
+
image_decoder=ImageDataDecoder,
|
| 69 |
+
target_decoder=TargetDecoder,
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
self.image_captions = read_images_and_captions(root, split)
|
| 73 |
+
|
| 74 |
+
def get_image_relpath(self, index: int) -> str:
|
| 75 |
+
image_path = self.image_captions[index]["image"]
|
| 76 |
+
return image_path
|
| 77 |
+
|
| 78 |
+
def get_image_data(self, index: int) -> bytes:
|
| 79 |
+
image_path = self.get_image_relpath(index)
|
| 80 |
+
with open(image_path, mode="rb") as f:
|
| 81 |
+
image_data = f.read()
|
| 82 |
+
return image_data
|
| 83 |
+
|
| 84 |
+
def get_target(self, index: int) -> str:
|
| 85 |
+
return random.choice(self.image_captions[index]["captions"])
|
| 86 |
+
|
| 87 |
+
def __len__(self) -> int:
|
| 88 |
+
return len(self.image_captions)
|
dinov3/data/datasets/decoders.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
from io import BytesIO
|
| 7 |
+
from typing import Any
|
| 8 |
+
|
| 9 |
+
from PIL import Image
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class Decoder:
|
| 13 |
+
def decode(self) -> Any:
|
| 14 |
+
raise NotImplementedError
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class ImageDataDecoder(Decoder):
|
| 18 |
+
def __init__(self, image_data: bytes) -> None:
|
| 19 |
+
self._image_data = image_data
|
| 20 |
+
|
| 21 |
+
def decode(self) -> Image:
|
| 22 |
+
f = BytesIO(self._image_data)
|
| 23 |
+
return Image.open(f).convert(mode="RGB")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class TargetDecoder(Decoder):
|
| 27 |
+
def __init__(self, target: Any):
|
| 28 |
+
self._target = target
|
| 29 |
+
|
| 30 |
+
def decode(self) -> Any:
|
| 31 |
+
return self._target
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class DenseTargetDecoder(Decoder):
|
| 35 |
+
def __init__(self, image_data: bytes) -> None:
|
| 36 |
+
self._image_data = image_data
|
| 37 |
+
|
| 38 |
+
def decode(self) -> Image:
|
| 39 |
+
f = BytesIO(self._image_data)
|
| 40 |
+
return Image.open(f)
|
dinov3/data/datasets/extended.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
from typing import Any, Tuple
|
| 7 |
+
|
| 8 |
+
from torchvision.datasets import VisionDataset
|
| 9 |
+
|
| 10 |
+
from .decoders import Decoder, ImageDataDecoder, TargetDecoder
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class ExtendedVisionDataset(VisionDataset):
|
| 14 |
+
def __init__(
|
| 15 |
+
self,
|
| 16 |
+
image_decoder: Decoder = ImageDataDecoder,
|
| 17 |
+
target_decoder: Decoder = TargetDecoder,
|
| 18 |
+
*args,
|
| 19 |
+
**kwargs,
|
| 20 |
+
) -> None:
|
| 21 |
+
super().__init__(*args, **kwargs) # type: ignore
|
| 22 |
+
self.image_decoder = image_decoder
|
| 23 |
+
self.target_decoder = target_decoder
|
| 24 |
+
|
| 25 |
+
def get_image_data(self, index: int) -> bytes:
|
| 26 |
+
raise NotImplementedError
|
| 27 |
+
|
| 28 |
+
def get_target(self, index: int) -> Any:
|
| 29 |
+
raise NotImplementedError
|
| 30 |
+
|
| 31 |
+
def __getitem__(self, index: int) -> Tuple[Any, Any]:
|
| 32 |
+
try:
|
| 33 |
+
image_data = self.get_image_data(index)
|
| 34 |
+
image = self.image_decoder(image_data).decode()
|
| 35 |
+
except Exception as e:
|
| 36 |
+
raise RuntimeError(f"can not read image for sample {index}") from e
|
| 37 |
+
target = self.get_target(index)
|
| 38 |
+
target = self.target_decoder(target).decode()
|
| 39 |
+
|
| 40 |
+
if self.transforms is not None:
|
| 41 |
+
image, target = self.transforms(image, target)
|
| 42 |
+
|
| 43 |
+
return image, target
|
| 44 |
+
|
| 45 |
+
def __len__(self) -> int:
|
| 46 |
+
raise NotImplementedError
|
dinov3/data/datasets/image_net.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import csv
|
| 7 |
+
import logging
|
| 8 |
+
import os
|
| 9 |
+
from enum import Enum
|
| 10 |
+
from typing import Callable, List, Optional, Tuple, Union
|
| 11 |
+
|
| 12 |
+
import numpy as np
|
| 13 |
+
|
| 14 |
+
from .decoders import ImageDataDecoder, TargetDecoder
|
| 15 |
+
from .extended import ExtendedVisionDataset
|
| 16 |
+
|
| 17 |
+
logger = logging.getLogger("dinov3")
|
| 18 |
+
_Target = int
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class _Split(Enum):
|
| 22 |
+
TRAIN = "train"
|
| 23 |
+
VAL = "val"
|
| 24 |
+
TEST = "test" # NOTE: torchvision does not support the test split
|
| 25 |
+
|
| 26 |
+
@property
|
| 27 |
+
def length(self) -> int:
|
| 28 |
+
split_lengths = {
|
| 29 |
+
_Split.TRAIN: 1_281_167,
|
| 30 |
+
_Split.VAL: 50_000,
|
| 31 |
+
_Split.TEST: 100_000,
|
| 32 |
+
}
|
| 33 |
+
return split_lengths[self]
|
| 34 |
+
|
| 35 |
+
def get_dirname(self, class_id: Optional[str] = None) -> str:
|
| 36 |
+
return self.value if class_id is None else os.path.join(self.value, class_id)
|
| 37 |
+
|
| 38 |
+
def get_image_relpath(self, actual_index: int, class_id: Optional[str] = None) -> str:
|
| 39 |
+
dirname = self.get_dirname(class_id)
|
| 40 |
+
if self == _Split.TRAIN:
|
| 41 |
+
basename = f"{class_id}_{actual_index}"
|
| 42 |
+
else: # self in (_Split.VAL, _Split.TEST):
|
| 43 |
+
basename = f"ILSVRC2012_{self.value}_{actual_index:08d}"
|
| 44 |
+
return os.path.join(dirname, basename + ".JPEG")
|
| 45 |
+
|
| 46 |
+
def parse_image_relpath(self, image_relpath: str) -> Tuple[str, int]:
|
| 47 |
+
assert self != _Split.TEST
|
| 48 |
+
dirname, filename = os.path.split(image_relpath)
|
| 49 |
+
class_id = os.path.split(dirname)[-1]
|
| 50 |
+
basename, _ = os.path.splitext(filename)
|
| 51 |
+
actual_index = int(basename.split("_")[-1])
|
| 52 |
+
return class_id, actual_index
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
class ImageNet(ExtendedVisionDataset):
|
| 56 |
+
Target = Union[_Target]
|
| 57 |
+
Split = Union[_Split]
|
| 58 |
+
|
| 59 |
+
def __init__(
|
| 60 |
+
self,
|
| 61 |
+
*,
|
| 62 |
+
split: "ImageNet.Split",
|
| 63 |
+
root: str,
|
| 64 |
+
extra: str,
|
| 65 |
+
transforms: Optional[Callable] = None,
|
| 66 |
+
transform: Optional[Callable] = None,
|
| 67 |
+
target_transform: Optional[Callable] = None,
|
| 68 |
+
) -> None:
|
| 69 |
+
super().__init__(
|
| 70 |
+
root=root,
|
| 71 |
+
transforms=transforms,
|
| 72 |
+
transform=transform,
|
| 73 |
+
target_transform=target_transform,
|
| 74 |
+
image_decoder=ImageDataDecoder,
|
| 75 |
+
target_decoder=TargetDecoder,
|
| 76 |
+
)
|
| 77 |
+
self._extra_root = extra
|
| 78 |
+
self._split = split
|
| 79 |
+
|
| 80 |
+
self._entries = None
|
| 81 |
+
self._class_ids = None
|
| 82 |
+
self._class_names = None
|
| 83 |
+
|
| 84 |
+
@property
|
| 85 |
+
def split(self) -> "ImageNet.Split":
|
| 86 |
+
return self._split
|
| 87 |
+
|
| 88 |
+
def _get_extra_full_path(self, extra_path: str) -> str:
|
| 89 |
+
return os.path.join(self._extra_root, extra_path)
|
| 90 |
+
|
| 91 |
+
def _load_extra(self, extra_path: str) -> np.ndarray:
|
| 92 |
+
extra_full_path = self._get_extra_full_path(extra_path)
|
| 93 |
+
return np.load(extra_full_path, mmap_mode="r")
|
| 94 |
+
|
| 95 |
+
def _save_extra(self, extra_array: np.ndarray, extra_path: str) -> None:
|
| 96 |
+
extra_full_path = self._get_extra_full_path(extra_path)
|
| 97 |
+
os.makedirs(self._extra_root, exist_ok=True)
|
| 98 |
+
np.save(extra_full_path, extra_array)
|
| 99 |
+
|
| 100 |
+
@property
|
| 101 |
+
def _entries_path(self) -> str:
|
| 102 |
+
return f"entries-{self._split.value.upper()}.npy"
|
| 103 |
+
|
| 104 |
+
@property
|
| 105 |
+
def _class_ids_path(self) -> str:
|
| 106 |
+
return f"class-ids-{self._split.value.upper()}.npy"
|
| 107 |
+
|
| 108 |
+
@property
|
| 109 |
+
def _class_names_path(self) -> str:
|
| 110 |
+
return f"class-names-{self._split.value.upper()}.npy"
|
| 111 |
+
|
| 112 |
+
def _get_entries(self) -> np.ndarray:
|
| 113 |
+
if self._entries is None:
|
| 114 |
+
self._entries = self._load_extra(self._entries_path)
|
| 115 |
+
assert self._entries is not None
|
| 116 |
+
return self._entries
|
| 117 |
+
|
| 118 |
+
def _get_class_ids(self) -> np.ndarray:
|
| 119 |
+
if self._split == _Split.TEST:
|
| 120 |
+
raise AssertionError("Class IDs are not available in TEST split")
|
| 121 |
+
if self._class_ids is None:
|
| 122 |
+
self._class_ids = self._load_extra(self._class_ids_path)
|
| 123 |
+
assert self._class_ids is not None
|
| 124 |
+
return self._class_ids
|
| 125 |
+
|
| 126 |
+
def _get_class_names(self) -> np.ndarray:
|
| 127 |
+
if self._split == _Split.TEST:
|
| 128 |
+
raise AssertionError("Class names are not available in TEST split")
|
| 129 |
+
if self._class_names is None:
|
| 130 |
+
self._class_names = self._load_extra(self._class_names_path)
|
| 131 |
+
assert self._class_names is not None
|
| 132 |
+
return self._class_names
|
| 133 |
+
|
| 134 |
+
def find_class_id(self, class_index: int) -> str:
|
| 135 |
+
class_ids = self._get_class_ids()
|
| 136 |
+
return str(class_ids[class_index])
|
| 137 |
+
|
| 138 |
+
def find_class_name(self, class_index: int) -> str:
|
| 139 |
+
class_names = self._get_class_names()
|
| 140 |
+
return str(class_names[class_index])
|
| 141 |
+
|
| 142 |
+
def get_image_data(self, index: int) -> bytes:
|
| 143 |
+
entries = self._get_entries()
|
| 144 |
+
actual_index = entries[index]["actual_index"]
|
| 145 |
+
|
| 146 |
+
class_id = self.get_class_id(index)
|
| 147 |
+
|
| 148 |
+
image_relpath = self.split.get_image_relpath(actual_index, class_id)
|
| 149 |
+
image_full_path = os.path.join(self.root, image_relpath)
|
| 150 |
+
with open(image_full_path, mode="rb") as f:
|
| 151 |
+
image_data = f.read()
|
| 152 |
+
return image_data
|
| 153 |
+
|
| 154 |
+
def get_target(self, index: int) -> Optional[Target]:
|
| 155 |
+
entries = self._get_entries()
|
| 156 |
+
class_index = entries[index]["class_index"]
|
| 157 |
+
return None if self.split == _Split.TEST else int(class_index)
|
| 158 |
+
|
| 159 |
+
def get_targets(self) -> Optional[np.ndarray]:
|
| 160 |
+
entries = self._get_entries()
|
| 161 |
+
return None if self.split == _Split.TEST else entries["class_index"]
|
| 162 |
+
|
| 163 |
+
def get_class_id(self, index: int) -> Optional[str]:
|
| 164 |
+
entries = self._get_entries()
|
| 165 |
+
class_id = entries[index]["class_id"]
|
| 166 |
+
return None if self.split == _Split.TEST else str(class_id)
|
| 167 |
+
|
| 168 |
+
def get_class_name(self, index: int) -> Optional[str]:
|
| 169 |
+
entries = self._get_entries()
|
| 170 |
+
class_name = entries[index]["class_name"]
|
| 171 |
+
return None if self.split == _Split.TEST else str(class_name)
|
| 172 |
+
|
| 173 |
+
def __len__(self) -> int:
|
| 174 |
+
entries = self._get_entries()
|
| 175 |
+
assert len(entries) == self.split.length
|
| 176 |
+
return len(entries)
|
| 177 |
+
|
| 178 |
+
def _load_labels(self, labels_path: str) -> List[Tuple[str, str]]:
|
| 179 |
+
labels_full_path = os.path.join(self.root, labels_path)
|
| 180 |
+
labels = []
|
| 181 |
+
|
| 182 |
+
try:
|
| 183 |
+
with open(labels_full_path, "r") as f:
|
| 184 |
+
reader = csv.reader(f)
|
| 185 |
+
for row in reader:
|
| 186 |
+
class_id, class_name = row
|
| 187 |
+
labels.append((class_id, class_name))
|
| 188 |
+
except OSError as e:
|
| 189 |
+
raise RuntimeError(f'can not read labels file "{labels_full_path}"') from e
|
| 190 |
+
|
| 191 |
+
return labels
|
| 192 |
+
|
| 193 |
+
def _dump_entries(self) -> None:
|
| 194 |
+
split = self.split
|
| 195 |
+
if split == ImageNet.Split.TEST:
|
| 196 |
+
dataset = None
|
| 197 |
+
sample_count = split.length
|
| 198 |
+
max_class_id_length, max_class_name_length = 0, 0
|
| 199 |
+
else:
|
| 200 |
+
labels_path = "labels.txt"
|
| 201 |
+
logger.info(f'loading labels from "{labels_path}"')
|
| 202 |
+
labels = self._load_labels(labels_path)
|
| 203 |
+
|
| 204 |
+
# NOTE: Using torchvision ImageFolder for consistency
|
| 205 |
+
from torchvision.datasets import ImageFolder
|
| 206 |
+
|
| 207 |
+
dataset_root = os.path.join(self.root, split.get_dirname())
|
| 208 |
+
dataset = ImageFolder(dataset_root)
|
| 209 |
+
sample_count = len(dataset)
|
| 210 |
+
max_class_id_length, max_class_name_length = -1, -1
|
| 211 |
+
for sample in dataset.samples:
|
| 212 |
+
_, class_index = sample
|
| 213 |
+
class_id, class_name = labels[class_index]
|
| 214 |
+
max_class_id_length = max(len(class_id), max_class_id_length)
|
| 215 |
+
max_class_name_length = max(len(class_name), max_class_name_length)
|
| 216 |
+
|
| 217 |
+
dtype = np.dtype(
|
| 218 |
+
[
|
| 219 |
+
("actual_index", "<u4"),
|
| 220 |
+
("class_index", "<u4"),
|
| 221 |
+
("class_id", f"U{max_class_id_length}"),
|
| 222 |
+
("class_name", f"U{max_class_name_length}"),
|
| 223 |
+
]
|
| 224 |
+
)
|
| 225 |
+
entries_array = np.empty(sample_count, dtype=dtype)
|
| 226 |
+
|
| 227 |
+
if split == ImageNet.Split.TEST:
|
| 228 |
+
old_percent = -1
|
| 229 |
+
for index in range(sample_count):
|
| 230 |
+
percent = 100 * (index + 1) // sample_count
|
| 231 |
+
if percent > old_percent:
|
| 232 |
+
logger.info(f"creating entries: {percent}%")
|
| 233 |
+
old_percent = percent
|
| 234 |
+
|
| 235 |
+
actual_index = index + 1
|
| 236 |
+
class_index = np.uint32(-1)
|
| 237 |
+
class_id, class_name = "", ""
|
| 238 |
+
entries_array[index] = (actual_index, class_index, class_id, class_name)
|
| 239 |
+
else:
|
| 240 |
+
class_names = {class_id: class_name for class_id, class_name in labels}
|
| 241 |
+
|
| 242 |
+
assert dataset
|
| 243 |
+
old_percent = -1
|
| 244 |
+
for index in range(sample_count):
|
| 245 |
+
percent = 100 * (index + 1) // sample_count
|
| 246 |
+
if percent > old_percent:
|
| 247 |
+
logger.info(f"creating entries: {percent}%")
|
| 248 |
+
old_percent = percent
|
| 249 |
+
|
| 250 |
+
image_full_path, class_index = dataset.samples[index]
|
| 251 |
+
image_relpath = os.path.relpath(image_full_path, self.root)
|
| 252 |
+
class_id, actual_index = split.parse_image_relpath(image_relpath)
|
| 253 |
+
class_name = class_names[class_id]
|
| 254 |
+
entries_array[index] = (actual_index, class_index, class_id, class_name)
|
| 255 |
+
|
| 256 |
+
logger.info(f'saving entries to "{self._entries_path}"')
|
| 257 |
+
self._save_extra(entries_array, self._entries_path)
|
| 258 |
+
|
| 259 |
+
def _dump_class_ids_and_names(self) -> None:
|
| 260 |
+
split = self.split
|
| 261 |
+
if split == ImageNet.Split.TEST:
|
| 262 |
+
return
|
| 263 |
+
|
| 264 |
+
entries_array = self._load_extra(self._entries_path)
|
| 265 |
+
|
| 266 |
+
max_class_id_length, max_class_name_length, max_class_index = -1, -1, -1
|
| 267 |
+
for entry in entries_array:
|
| 268 |
+
class_index, class_id, class_name = (
|
| 269 |
+
entry["class_index"],
|
| 270 |
+
entry["class_id"],
|
| 271 |
+
entry["class_name"],
|
| 272 |
+
)
|
| 273 |
+
max_class_index = max(int(class_index), max_class_index)
|
| 274 |
+
max_class_id_length = max(len(str(class_id)), max_class_id_length)
|
| 275 |
+
max_class_name_length = max(len(str(class_name)), max_class_name_length)
|
| 276 |
+
|
| 277 |
+
class_count = max_class_index + 1
|
| 278 |
+
class_ids_array = np.empty(class_count, dtype=f"U{max_class_id_length}")
|
| 279 |
+
class_names_array = np.empty(class_count, dtype=f"U{max_class_name_length}")
|
| 280 |
+
for entry in entries_array:
|
| 281 |
+
class_index, class_id, class_name = (
|
| 282 |
+
entry["class_index"],
|
| 283 |
+
entry["class_id"],
|
| 284 |
+
entry["class_name"],
|
| 285 |
+
)
|
| 286 |
+
class_ids_array[class_index] = class_id
|
| 287 |
+
class_names_array[class_index] = class_name
|
| 288 |
+
|
| 289 |
+
logger.info(f'saving class IDs to "{self._class_ids_path}"')
|
| 290 |
+
self._save_extra(class_ids_array, self._class_ids_path)
|
| 291 |
+
|
| 292 |
+
logger.info(f'saving class names to "{self._class_names_path}"')
|
| 293 |
+
self._save_extra(class_names_array, self._class_names_path)
|
| 294 |
+
|
| 295 |
+
def dump_extra(self) -> None:
|
| 296 |
+
self._dump_entries()
|
| 297 |
+
self._dump_class_ids_and_names()
|
dinov3/data/datasets/image_net_22k.py
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import warnings
|
| 8 |
+
from dataclasses import dataclass
|
| 9 |
+
from enum import Enum
|
| 10 |
+
from functools import lru_cache
|
| 11 |
+
from gzip import GzipFile
|
| 12 |
+
from io import BytesIO
|
| 13 |
+
from mmap import ACCESS_READ, mmap
|
| 14 |
+
from typing import Any, Callable, List, Optional, Set, Tuple
|
| 15 |
+
|
| 16 |
+
import numpy as np
|
| 17 |
+
|
| 18 |
+
from .extended import ExtendedVisionDataset
|
| 19 |
+
|
| 20 |
+
_Labels = int
|
| 21 |
+
|
| 22 |
+
_DEFAULT_MMAP_CACHE_SIZE = 16 # Warning: This can exhaust file descriptors
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@dataclass
|
| 26 |
+
class _ClassEntry:
|
| 27 |
+
block_offset: int
|
| 28 |
+
maybe_filename: Optional[str] = None
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
@dataclass
|
| 32 |
+
class _Entry:
|
| 33 |
+
class_index: int # noqa: E701
|
| 34 |
+
start_offset: int
|
| 35 |
+
end_offset: int
|
| 36 |
+
filename: str
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class _Split(Enum):
|
| 40 |
+
TRAIN = "train"
|
| 41 |
+
VAL = "val"
|
| 42 |
+
|
| 43 |
+
@property
|
| 44 |
+
def length(self) -> int:
|
| 45 |
+
return {
|
| 46 |
+
_Split.TRAIN: 11_797_647,
|
| 47 |
+
_Split.VAL: 561_050,
|
| 48 |
+
}[self]
|
| 49 |
+
|
| 50 |
+
def entries_path(self):
|
| 51 |
+
return f"imagenet21kp_{self.value}.txt"
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def _get_tarball_path(class_id: str) -> str:
|
| 55 |
+
return f"{class_id}.tar"
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def _make_mmap_tarball(tarballs_root: str, mmap_cache_size: int):
|
| 59 |
+
@lru_cache(maxsize=mmap_cache_size)
|
| 60 |
+
def _mmap_tarball(class_id: str) -> mmap:
|
| 61 |
+
tarball_path = _get_tarball_path(class_id)
|
| 62 |
+
tarball_full_path = os.path.join(tarballs_root, tarball_path)
|
| 63 |
+
with open(tarball_full_path) as f:
|
| 64 |
+
return mmap(fileno=f.fileno(), length=0, access=ACCESS_READ)
|
| 65 |
+
|
| 66 |
+
return _mmap_tarball
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class ImageNet22k(ExtendedVisionDataset):
|
| 70 |
+
_GZIPPED_INDICES: Set[int] = {
|
| 71 |
+
841_545,
|
| 72 |
+
1_304_131,
|
| 73 |
+
2_437_921,
|
| 74 |
+
2_672_079,
|
| 75 |
+
2_795_676,
|
| 76 |
+
2_969_786,
|
| 77 |
+
6_902_965,
|
| 78 |
+
6_903_550,
|
| 79 |
+
6_903_628,
|
| 80 |
+
7_432_557,
|
| 81 |
+
7_432_589,
|
| 82 |
+
7_813_809,
|
| 83 |
+
8_329_633,
|
| 84 |
+
10_296_990,
|
| 85 |
+
10_417_652,
|
| 86 |
+
10_492_265,
|
| 87 |
+
10_598_078,
|
| 88 |
+
10_782_398,
|
| 89 |
+
10_902_612,
|
| 90 |
+
11_203_736,
|
| 91 |
+
11_342_890,
|
| 92 |
+
11_397_596,
|
| 93 |
+
11_589_762,
|
| 94 |
+
11_705_103,
|
| 95 |
+
12_936_875,
|
| 96 |
+
13_289_782,
|
| 97 |
+
}
|
| 98 |
+
Labels = _Labels
|
| 99 |
+
|
| 100 |
+
def __init__(
|
| 101 |
+
self,
|
| 102 |
+
*,
|
| 103 |
+
root: str,
|
| 104 |
+
extra: str,
|
| 105 |
+
transforms: Optional[Callable] = None,
|
| 106 |
+
transform: Optional[Callable] = None,
|
| 107 |
+
target_transform: Optional[Callable] = None,
|
| 108 |
+
mmap_cache_size: int = _DEFAULT_MMAP_CACHE_SIZE,
|
| 109 |
+
) -> None:
|
| 110 |
+
super().__init__(root, transforms, transform, target_transform)
|
| 111 |
+
self._extra_root = extra
|
| 112 |
+
|
| 113 |
+
entries_path = self._get_entries_path(root)
|
| 114 |
+
self._entries = self._load_extra(entries_path)
|
| 115 |
+
|
| 116 |
+
class_ids_path = self._get_class_ids_path(root)
|
| 117 |
+
self._class_ids = self._load_extra(class_ids_path)
|
| 118 |
+
|
| 119 |
+
self._gzipped_indices = ImageNet22k._GZIPPED_INDICES
|
| 120 |
+
self._mmap_tarball = _make_mmap_tarball(self._tarballs_root, mmap_cache_size)
|
| 121 |
+
|
| 122 |
+
def _get_entries_path(self, root: Optional[str] = None) -> str:
|
| 123 |
+
return "entries.npy"
|
| 124 |
+
|
| 125 |
+
def _get_class_ids_path(self, root: Optional[str] = None) -> str:
|
| 126 |
+
return "class-ids.npy"
|
| 127 |
+
|
| 128 |
+
def _find_class_ids(self, path: str) -> List[str]:
|
| 129 |
+
class_ids = []
|
| 130 |
+
|
| 131 |
+
with os.scandir(path) as entries:
|
| 132 |
+
for entry in entries:
|
| 133 |
+
root, ext = os.path.splitext(entry.name)
|
| 134 |
+
if ext != ".tar":
|
| 135 |
+
continue
|
| 136 |
+
class_ids.append(root)
|
| 137 |
+
|
| 138 |
+
return sorted(class_ids)
|
| 139 |
+
|
| 140 |
+
def _load_entries_class_ids(self, root: Optional[str] = None) -> Tuple[List[_Entry], List[str]]:
|
| 141 |
+
root = self.get_root(root)
|
| 142 |
+
entries: List[_Entry] = []
|
| 143 |
+
class_ids = self._find_class_ids(root)
|
| 144 |
+
|
| 145 |
+
for class_index, class_id in enumerate(class_ids):
|
| 146 |
+
path = os.path.join(root, "blocks", f"{class_id}.log")
|
| 147 |
+
class_entries = []
|
| 148 |
+
|
| 149 |
+
try:
|
| 150 |
+
with open(path) as f:
|
| 151 |
+
for line in f:
|
| 152 |
+
line = line.rstrip()
|
| 153 |
+
block, filename = line.split(":")
|
| 154 |
+
block_offset = int(block[6:])
|
| 155 |
+
filename = filename[1:]
|
| 156 |
+
|
| 157 |
+
maybe_filename = None
|
| 158 |
+
if filename != "** Block of NULs **":
|
| 159 |
+
maybe_filename = filename
|
| 160 |
+
_, ext = os.path.splitext(filename)
|
| 161 |
+
# assert ext == ".JPEG"
|
| 162 |
+
|
| 163 |
+
class_entry = _ClassEntry(block_offset, maybe_filename)
|
| 164 |
+
class_entries.append(class_entry)
|
| 165 |
+
except OSError as e:
|
| 166 |
+
raise RuntimeError(f'can not read blocks file "{path}"') from e
|
| 167 |
+
|
| 168 |
+
assert class_entries[-1].maybe_filename is None
|
| 169 |
+
|
| 170 |
+
for class_entry1, class_entry2 in zip(class_entries, class_entries[1:]):
|
| 171 |
+
assert class_entry1.block_offset <= class_entry2.block_offset
|
| 172 |
+
start_offset = 512 * class_entry1.block_offset
|
| 173 |
+
end_offset = 512 * class_entry2.block_offset
|
| 174 |
+
assert class_entry1.maybe_filename is not None
|
| 175 |
+
filename = class_entry1.maybe_filename
|
| 176 |
+
entry = _Entry(class_index, start_offset, end_offset, filename)
|
| 177 |
+
# Skip invalid image files (PIL throws UnidentifiedImageError)
|
| 178 |
+
if filename == "n06470073_47249.JPEG":
|
| 179 |
+
continue
|
| 180 |
+
entries.append(entry)
|
| 181 |
+
|
| 182 |
+
return entries, class_ids
|
| 183 |
+
|
| 184 |
+
def _load_extra(self, extra_path: str) -> np.ndarray:
|
| 185 |
+
extra_root = self._extra_root
|
| 186 |
+
extra_full_path = os.path.join(extra_root, extra_path)
|
| 187 |
+
return np.load(extra_full_path, mmap_mode="r")
|
| 188 |
+
|
| 189 |
+
def _save_extra(self, extra_array: np.ndarray, extra_path: str) -> None:
|
| 190 |
+
extra_root = self._extra_root
|
| 191 |
+
extra_full_path = os.path.join(extra_root, extra_path)
|
| 192 |
+
os.makedirs(extra_root, exist_ok=True)
|
| 193 |
+
np.save(extra_full_path, extra_array)
|
| 194 |
+
|
| 195 |
+
@property
|
| 196 |
+
def _tarballs_root(self) -> str:
|
| 197 |
+
return self.root
|
| 198 |
+
|
| 199 |
+
def find_class_id(self, class_index: int) -> str:
|
| 200 |
+
return str(self._class_ids[class_index])
|
| 201 |
+
|
| 202 |
+
def get_image_data(self, index: int) -> bytes:
|
| 203 |
+
entry = self._entries[index]
|
| 204 |
+
class_id = entry["class_id"]
|
| 205 |
+
class_mmap = self._mmap_tarball(class_id)
|
| 206 |
+
|
| 207 |
+
start_offset, end_offset = entry["start_offset"], entry["end_offset"]
|
| 208 |
+
try:
|
| 209 |
+
mapped_data = class_mmap[start_offset:end_offset]
|
| 210 |
+
data = mapped_data[512:] # Skip entry header block
|
| 211 |
+
|
| 212 |
+
if len(data) >= 2 and tuple(data[:2]) == (0x1F, 0x8B):
|
| 213 |
+
assert index in self._gzipped_indices, f"unexpected gzip header for sample {index}"
|
| 214 |
+
with GzipFile(fileobj=BytesIO(data)) as g:
|
| 215 |
+
data = g.read()
|
| 216 |
+
except Exception as e:
|
| 217 |
+
raise RuntimeError(f"can not retrieve image data for sample {index} " f'from "{class_id}" tarball') from e
|
| 218 |
+
|
| 219 |
+
return data
|
| 220 |
+
|
| 221 |
+
def get_target(self, index: int) -> Any:
|
| 222 |
+
return int(self._entries[index]["class_index"])
|
| 223 |
+
|
| 224 |
+
def get_targets(self) -> np.ndarray:
|
| 225 |
+
return self._entries["class_index"]
|
| 226 |
+
|
| 227 |
+
def get_class_id(self, index: int) -> str:
|
| 228 |
+
return str(self._entries[index]["class_id"])
|
| 229 |
+
|
| 230 |
+
def get_class_ids(self) -> np.ndarray:
|
| 231 |
+
return self._entries["class_id"]
|
| 232 |
+
|
| 233 |
+
def __getitem__(self, index: int) -> Tuple[Any, Any]:
|
| 234 |
+
with warnings.catch_warnings():
|
| 235 |
+
warnings.simplefilter("ignore")
|
| 236 |
+
return super().__getitem__(index)
|
| 237 |
+
|
| 238 |
+
def __len__(self) -> int:
|
| 239 |
+
return len(self._entries)
|
| 240 |
+
|
| 241 |
+
def _dump_entries(self, *args, **kwargs) -> None:
|
| 242 |
+
entries, class_ids = self._load_entries_class_ids(*args, **kwargs)
|
| 243 |
+
|
| 244 |
+
max_class_id_length, max_filename_length, max_class_index = -1, -1, -1
|
| 245 |
+
for entry in entries:
|
| 246 |
+
class_id = class_ids[entry.class_index]
|
| 247 |
+
max_class_index = max(entry.class_index, max_class_index)
|
| 248 |
+
max_class_id_length = max(len(class_id), max_class_id_length)
|
| 249 |
+
max_filename_length = max(len(entry.filename), max_filename_length)
|
| 250 |
+
|
| 251 |
+
dtype = np.dtype(
|
| 252 |
+
[
|
| 253 |
+
("class_index", "<u4"),
|
| 254 |
+
("class_id", f"U{max_class_id_length}"),
|
| 255 |
+
("start_offset", "<u4"),
|
| 256 |
+
("end_offset", "<u4"),
|
| 257 |
+
("filename", f"U{max_filename_length}"),
|
| 258 |
+
]
|
| 259 |
+
)
|
| 260 |
+
sample_count = len(entries)
|
| 261 |
+
entries_array = np.empty(sample_count, dtype=dtype)
|
| 262 |
+
for i, entry in enumerate(entries):
|
| 263 |
+
class_index = entry.class_index
|
| 264 |
+
class_id = class_ids[class_index]
|
| 265 |
+
start_offset = entry.start_offset
|
| 266 |
+
end_offset = entry.end_offset
|
| 267 |
+
filename = entry.filename
|
| 268 |
+
entries_array[i] = (
|
| 269 |
+
class_index,
|
| 270 |
+
class_id,
|
| 271 |
+
start_offset,
|
| 272 |
+
end_offset,
|
| 273 |
+
filename,
|
| 274 |
+
)
|
| 275 |
+
|
| 276 |
+
entries_path = self._get_entries_path(*args, **kwargs)
|
| 277 |
+
self._save_extra(entries_array, entries_path)
|
| 278 |
+
|
| 279 |
+
def _dump_class_ids(self, *args, **kwargs) -> None:
|
| 280 |
+
entries_path = self._get_entries_path(*args, **kwargs)
|
| 281 |
+
entries_array = self._load_extra(entries_path)
|
| 282 |
+
|
| 283 |
+
max_class_id_length, max_class_index = -1, -1
|
| 284 |
+
for entry in entries_array:
|
| 285 |
+
class_index, class_id = entry["class_index"], entry["class_id"]
|
| 286 |
+
max_class_index = max(int(class_index), max_class_index)
|
| 287 |
+
max_class_id_length = max(len(str(class_id)), max_class_id_length)
|
| 288 |
+
|
| 289 |
+
class_ids_array = np.empty(max_class_index + 1, dtype=f"U{max_class_id_length}")
|
| 290 |
+
for entry in entries_array:
|
| 291 |
+
class_index, class_id = entry["class_index"], entry["class_id"]
|
| 292 |
+
class_ids_array[class_index] = class_id
|
| 293 |
+
class_ids_path = self._get_class_ids_path(*args, **kwargs)
|
| 294 |
+
self._save_extra(class_ids_array, class_ids_path)
|
| 295 |
+
|
| 296 |
+
def _dump_extra(self, *args, **kwargs) -> None:
|
| 297 |
+
self._dump_entries(*args, *kwargs)
|
| 298 |
+
self._dump_class_ids(*args, *kwargs)
|
| 299 |
+
|
| 300 |
+
def dump_extra(self, root: Optional[str] = None) -> None:
|
| 301 |
+
return self._dump_extra(root)
|
dinov3/data/loaders.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import logging
|
| 7 |
+
from enum import Enum
|
| 8 |
+
from typing import Any, Callable, List, Optional, TypeVar
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
from torch.utils.data import Sampler
|
| 12 |
+
|
| 13 |
+
from .datasets import ADE20K, CocoCaptions, ImageNet, ImageNet22k
|
| 14 |
+
from .samplers import EpochSampler, InfiniteSampler, ShardedInfiniteSampler
|
| 15 |
+
|
| 16 |
+
logger = logging.getLogger("dinov3")
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class SamplerType(Enum):
|
| 20 |
+
DISTRIBUTED = 0
|
| 21 |
+
EPOCH = 1
|
| 22 |
+
INFINITE = 2
|
| 23 |
+
SHARDED_INFINITE = 3
|
| 24 |
+
SHARDED_INFINITE_NEW = 4
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _make_bool_str(b: bool) -> str:
|
| 28 |
+
return "yes" if b else "no"
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _make_sample_transform(
|
| 32 |
+
image_transform: Optional[Callable] = None,
|
| 33 |
+
target_transform: Optional[Callable] = None,
|
| 34 |
+
):
|
| 35 |
+
def transform(sample):
|
| 36 |
+
image, target = sample
|
| 37 |
+
if image_transform is not None:
|
| 38 |
+
image = image_transform(image)
|
| 39 |
+
if target_transform is not None:
|
| 40 |
+
target = target_transform(target)
|
| 41 |
+
return image, target
|
| 42 |
+
|
| 43 |
+
return transform
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def _parse_dataset_str(dataset_str: str):
|
| 47 |
+
tokens = dataset_str.split(":")
|
| 48 |
+
|
| 49 |
+
name = tokens[0]
|
| 50 |
+
kwargs = {}
|
| 51 |
+
|
| 52 |
+
for token in tokens[1:]:
|
| 53 |
+
key, value = token.split("=")
|
| 54 |
+
assert key in ("root", "extra", "split")
|
| 55 |
+
kwargs[key] = value
|
| 56 |
+
|
| 57 |
+
if name == "ImageNet":
|
| 58 |
+
class_ = ImageNet
|
| 59 |
+
if "split" in kwargs:
|
| 60 |
+
kwargs["split"] = ImageNet.Split[kwargs["split"]]
|
| 61 |
+
elif name == "ImageNet22k":
|
| 62 |
+
class_ = ImageNet22k
|
| 63 |
+
elif name == "ADE20K":
|
| 64 |
+
class_ = ADE20K
|
| 65 |
+
if "split" in kwargs:
|
| 66 |
+
kwargs["split"] = ADE20K.Split[kwargs["split"]]
|
| 67 |
+
elif name == "CocoCaptions":
|
| 68 |
+
class_ = CocoCaptions
|
| 69 |
+
if "split" in kwargs:
|
| 70 |
+
kwargs["split"] = CocoCaptions.Split[kwargs["split"]]
|
| 71 |
+
else:
|
| 72 |
+
raise ValueError(f'Unsupported dataset "{name}"')
|
| 73 |
+
|
| 74 |
+
return class_, kwargs
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def make_dataset(
|
| 78 |
+
*,
|
| 79 |
+
dataset_str: str,
|
| 80 |
+
transform: Optional[Callable] = None,
|
| 81 |
+
target_transform: Optional[Callable] = None,
|
| 82 |
+
transforms: Optional[Callable] = None,
|
| 83 |
+
):
|
| 84 |
+
"""
|
| 85 |
+
Creates a dataset with the specified parameters.
|
| 86 |
+
|
| 87 |
+
Args:
|
| 88 |
+
dataset_str: A dataset string description (e.g. ImageNet:split=TRAIN).
|
| 89 |
+
transform: A transform to apply to images.
|
| 90 |
+
target_transform: A transform to apply to targets.
|
| 91 |
+
transforms: A transform to apply to both images and targets.
|
| 92 |
+
|
| 93 |
+
Returns:
|
| 94 |
+
The created dataset.
|
| 95 |
+
"""
|
| 96 |
+
logger.info(f'using dataset: "{dataset_str}"')
|
| 97 |
+
|
| 98 |
+
class_, kwargs = _parse_dataset_str(dataset_str)
|
| 99 |
+
dataset = class_(transform=transform, target_transform=target_transform, transforms=transforms, **kwargs)
|
| 100 |
+
|
| 101 |
+
logger.info(f"# of dataset samples: {len(dataset):,d}")
|
| 102 |
+
|
| 103 |
+
# Aggregated datasets do not expose (yet) these attributes, so add them.
|
| 104 |
+
if not hasattr(dataset, "transform"):
|
| 105 |
+
dataset.transform = transform
|
| 106 |
+
if not hasattr(dataset, "target_transform"):
|
| 107 |
+
dataset.target_transform = target_transform
|
| 108 |
+
if not hasattr(dataset, "transforms"):
|
| 109 |
+
dataset.transforms = transforms
|
| 110 |
+
|
| 111 |
+
return dataset
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def _make_sampler(
|
| 115 |
+
*,
|
| 116 |
+
dataset,
|
| 117 |
+
type: Optional[SamplerType] = None,
|
| 118 |
+
shuffle: bool = False,
|
| 119 |
+
seed: int = 0,
|
| 120 |
+
size: int = -1,
|
| 121 |
+
advance: int = 0,
|
| 122 |
+
) -> Optional[Sampler]:
|
| 123 |
+
sample_count = len(dataset)
|
| 124 |
+
|
| 125 |
+
if type == SamplerType.INFINITE:
|
| 126 |
+
logger.info("sampler: infinite")
|
| 127 |
+
if size > 0:
|
| 128 |
+
raise ValueError("sampler size > 0 is invalid")
|
| 129 |
+
return InfiniteSampler(
|
| 130 |
+
sample_count=sample_count,
|
| 131 |
+
shuffle=shuffle,
|
| 132 |
+
seed=seed,
|
| 133 |
+
advance=advance,
|
| 134 |
+
)
|
| 135 |
+
elif type in (SamplerType.SHARDED_INFINITE, SamplerType.SHARDED_INFINITE_NEW):
|
| 136 |
+
logger.info("sampler: sharded infinite")
|
| 137 |
+
if size > 0:
|
| 138 |
+
raise ValueError("sampler size > 0 is invalid")
|
| 139 |
+
use_new_shuffle_tensor_slice = type == SamplerType.SHARDED_INFINITE_NEW
|
| 140 |
+
return ShardedInfiniteSampler(
|
| 141 |
+
sample_count=sample_count,
|
| 142 |
+
shuffle=shuffle,
|
| 143 |
+
seed=seed,
|
| 144 |
+
advance=advance,
|
| 145 |
+
use_new_shuffle_tensor_slice=use_new_shuffle_tensor_slice,
|
| 146 |
+
)
|
| 147 |
+
elif type == SamplerType.EPOCH:
|
| 148 |
+
logger.info("sampler: epoch")
|
| 149 |
+
if advance > 0:
|
| 150 |
+
raise NotImplementedError("sampler advance > 0 is not supported")
|
| 151 |
+
size = size if size > 0 else sample_count
|
| 152 |
+
logger.info(f"# of samples / epoch: {size:,d}")
|
| 153 |
+
return EpochSampler(
|
| 154 |
+
size=size,
|
| 155 |
+
sample_count=sample_count,
|
| 156 |
+
shuffle=shuffle,
|
| 157 |
+
seed=seed,
|
| 158 |
+
)
|
| 159 |
+
elif type == SamplerType.DISTRIBUTED:
|
| 160 |
+
logger.info("sampler: distributed")
|
| 161 |
+
if size > 0:
|
| 162 |
+
raise ValueError("sampler size > 0 is invalid")
|
| 163 |
+
if advance > 0:
|
| 164 |
+
raise ValueError("sampler advance > 0 is invalid")
|
| 165 |
+
return torch.utils.data.DistributedSampler(
|
| 166 |
+
dataset=dataset,
|
| 167 |
+
shuffle=shuffle,
|
| 168 |
+
seed=seed,
|
| 169 |
+
drop_last=False,
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
logger.info("sampler: none")
|
| 173 |
+
return None
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
T = TypeVar("T")
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def make_data_loader(
|
| 180 |
+
*,
|
| 181 |
+
dataset,
|
| 182 |
+
batch_size: int,
|
| 183 |
+
num_workers: int,
|
| 184 |
+
shuffle: bool = True,
|
| 185 |
+
seed: int = 0,
|
| 186 |
+
sampler_type: Optional[SamplerType] = SamplerType.INFINITE,
|
| 187 |
+
sampler_size: int = -1,
|
| 188 |
+
sampler_advance: int = 0,
|
| 189 |
+
drop_last: bool = True,
|
| 190 |
+
persistent_workers: bool = False,
|
| 191 |
+
collate_fn: Optional[Callable[[List[T]], Any]] = None,
|
| 192 |
+
worker_init_fn: Optional[Callable[[List[T]], Any]] = None,
|
| 193 |
+
):
|
| 194 |
+
"""
|
| 195 |
+
Creates a data loader with the specified parameters.
|
| 196 |
+
|
| 197 |
+
Args:
|
| 198 |
+
dataset: A dataset (third party, LaViDa or WebDataset).
|
| 199 |
+
batch_size: The size of batches to generate.
|
| 200 |
+
num_workers: The number of workers to use.
|
| 201 |
+
shuffle: Whether to shuffle samples.
|
| 202 |
+
seed: The random seed to use.
|
| 203 |
+
sampler_type: Which sampler to use: EPOCH, INFINITE, SHARDED_INFINITE, SHARDED_INFINITE_NEW, DISTRIBUTED or None.
|
| 204 |
+
sampler_size: The number of images per epoch (when applicable) or -1 for the entire dataset.
|
| 205 |
+
sampler_advance: How many samples to skip (when applicable).
|
| 206 |
+
drop_last: Whether the last non-full batch of data should be dropped.
|
| 207 |
+
persistent_workers: maintain the workers Dataset instances alive after a dataset has been consumed once.
|
| 208 |
+
collate_fn: Function that performs batch collation
|
| 209 |
+
"""
|
| 210 |
+
|
| 211 |
+
sampler = _make_sampler(
|
| 212 |
+
dataset=dataset,
|
| 213 |
+
type=sampler_type,
|
| 214 |
+
shuffle=shuffle,
|
| 215 |
+
seed=seed,
|
| 216 |
+
size=sampler_size,
|
| 217 |
+
advance=sampler_advance,
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
logger.info("using PyTorch data loader")
|
| 221 |
+
data_loader = torch.utils.data.DataLoader(
|
| 222 |
+
dataset,
|
| 223 |
+
sampler=sampler,
|
| 224 |
+
batch_size=batch_size,
|
| 225 |
+
num_workers=num_workers,
|
| 226 |
+
pin_memory=True,
|
| 227 |
+
drop_last=drop_last,
|
| 228 |
+
persistent_workers=persistent_workers,
|
| 229 |
+
collate_fn=collate_fn,
|
| 230 |
+
worker_init_fn=worker_init_fn,
|
| 231 |
+
)
|
| 232 |
+
|
| 233 |
+
try:
|
| 234 |
+
logger.info(f"# of batches: {len(data_loader):,d}")
|
| 235 |
+
except TypeError: # data loader has no length
|
| 236 |
+
logger.info("infinite data loader")
|
| 237 |
+
return data_loader
|
dinov3/data/masking.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import math
|
| 7 |
+
import random
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class MaskingGenerator:
|
| 13 |
+
def __init__(
|
| 14 |
+
self,
|
| 15 |
+
input_size,
|
| 16 |
+
num_masking_patches=None,
|
| 17 |
+
min_num_patches=4,
|
| 18 |
+
max_num_patches=None,
|
| 19 |
+
min_aspect=0.3,
|
| 20 |
+
max_aspect=None,
|
| 21 |
+
):
|
| 22 |
+
if not isinstance(input_size, tuple):
|
| 23 |
+
input_size = (input_size,) * 2
|
| 24 |
+
self.height, self.width = input_size
|
| 25 |
+
|
| 26 |
+
self.num_patches = self.height * self.width
|
| 27 |
+
self.num_masking_patches = num_masking_patches
|
| 28 |
+
|
| 29 |
+
self.min_num_patches = min_num_patches
|
| 30 |
+
self.max_num_patches = num_masking_patches if max_num_patches is None else max_num_patches
|
| 31 |
+
|
| 32 |
+
max_aspect = max_aspect or 1 / min_aspect
|
| 33 |
+
self.log_aspect_ratio = (math.log(min_aspect), math.log(max_aspect))
|
| 34 |
+
|
| 35 |
+
def __repr__(self):
|
| 36 |
+
repr_str = "Generator(%d, %d -> [%d ~ %d], max = %d, %.3f ~ %.3f)" % (
|
| 37 |
+
self.height,
|
| 38 |
+
self.width,
|
| 39 |
+
self.min_num_patches,
|
| 40 |
+
self.max_num_patches,
|
| 41 |
+
self.num_masking_patches,
|
| 42 |
+
self.log_aspect_ratio[0],
|
| 43 |
+
self.log_aspect_ratio[1],
|
| 44 |
+
)
|
| 45 |
+
return repr_str
|
| 46 |
+
|
| 47 |
+
def get_shape(self):
|
| 48 |
+
return self.height, self.width
|
| 49 |
+
|
| 50 |
+
def _mask(self, mask, max_mask_patches):
|
| 51 |
+
delta = 0
|
| 52 |
+
for _ in range(10):
|
| 53 |
+
target_area = random.uniform(self.min_num_patches, max_mask_patches)
|
| 54 |
+
aspect_ratio = math.exp(random.uniform(*self.log_aspect_ratio))
|
| 55 |
+
h = int(round(math.sqrt(target_area * aspect_ratio)))
|
| 56 |
+
w = int(round(math.sqrt(target_area / aspect_ratio)))
|
| 57 |
+
if w < self.width and h < self.height:
|
| 58 |
+
top = random.randint(0, self.height - h)
|
| 59 |
+
left = random.randint(0, self.width - w)
|
| 60 |
+
|
| 61 |
+
num_masked = mask[top : top + h, left : left + w].sum()
|
| 62 |
+
# Overlap
|
| 63 |
+
if 0 < h * w - num_masked <= max_mask_patches:
|
| 64 |
+
for i in range(top, top + h):
|
| 65 |
+
for j in range(left, left + w):
|
| 66 |
+
if mask[i, j] == 0:
|
| 67 |
+
mask[i, j] = 1
|
| 68 |
+
delta += 1
|
| 69 |
+
|
| 70 |
+
if delta > 0:
|
| 71 |
+
break
|
| 72 |
+
return delta
|
| 73 |
+
|
| 74 |
+
def __call__(self, num_masking_patches=0):
|
| 75 |
+
mask = np.zeros(shape=self.get_shape(), dtype=bool)
|
| 76 |
+
mask_count = 0
|
| 77 |
+
while mask_count < num_masking_patches:
|
| 78 |
+
max_mask_patches = num_masking_patches - mask_count
|
| 79 |
+
max_mask_patches = min(max_mask_patches, self.max_num_patches)
|
| 80 |
+
|
| 81 |
+
delta = self._mask(mask, max_mask_patches)
|
| 82 |
+
if delta == 0:
|
| 83 |
+
break
|
| 84 |
+
else:
|
| 85 |
+
mask_count += delta
|
| 86 |
+
|
| 87 |
+
return self.complete_mask_randomly(mask, num_masking_patches)
|
| 88 |
+
|
| 89 |
+
def complete_mask_randomly(self, mask, num_masking_patches):
|
| 90 |
+
shape = mask.shape
|
| 91 |
+
m2 = mask.flatten()
|
| 92 |
+
to_add = np.random.choice(np.where(~m2)[0], size=num_masking_patches - m2.sum(), replace=False)
|
| 93 |
+
m2[to_add] = True
|
| 94 |
+
return m2.reshape(shape)
|
dinov3/data/meta_loaders.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import logging
|
| 7 |
+
from typing import Any, Iterable, Iterator, List, Tuple, TypeVar
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
|
| 11 |
+
logger = logging.getLogger("dinov3")
|
| 12 |
+
Loader = Iterable[List[Any]]
|
| 13 |
+
T = TypeVar("T")
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class CombinedDataLoader:
|
| 17 |
+
"""
|
| 18 |
+
Combines data loaders using the provided sampling ratios
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
GLOBAL_HOMOGENEOUS = 0
|
| 22 |
+
LOCAL_HOMOGENEOUS = 1
|
| 23 |
+
|
| 24 |
+
def __init__(
|
| 25 |
+
self,
|
| 26 |
+
loaders_with_ratios: Iterable[Tuple[Loader, float]],
|
| 27 |
+
batch_size: int,
|
| 28 |
+
combining_mode: int = 1,
|
| 29 |
+
seed: int = 65537,
|
| 30 |
+
name: str = None,
|
| 31 |
+
logging_period: int = 100,
|
| 32 |
+
):
|
| 33 |
+
if combining_mode not in [self.GLOBAL_HOMOGENEOUS, self.LOCAL_HOMOGENEOUS]:
|
| 34 |
+
raise ValueError(f"Unsupported value of combining_mode ({combining_mode})")
|
| 35 |
+
loaders, ratios = zip(*loaders_with_ratios)
|
| 36 |
+
assert np.all([loader.batch_size == batch_size for loader in loaders]), (
|
| 37 |
+
f"All individual loaders must have the same batch size to the combined data loader for combining_mode={combining_mode}"
|
| 38 |
+
)
|
| 39 |
+
self.loaders = loaders
|
| 40 |
+
self.ratios = ratios
|
| 41 |
+
self.batch_size = batch_size
|
| 42 |
+
self.combining_mode = combining_mode
|
| 43 |
+
self.initial_seed = seed
|
| 44 |
+
self.name = name if name is not None else ""
|
| 45 |
+
self.logging_period = logging_period
|
| 46 |
+
if combining_mode == self.GLOBAL_HOMOGENEOUS:
|
| 47 |
+
logger.info(f"Initialize CDL {self.name} with seed={seed}")
|
| 48 |
+
self.seed = seed
|
| 49 |
+
self.rng = np.random.default_rng(seed=seed)
|
| 50 |
+
else:
|
| 51 |
+
logger.info(f"Initialize CDL {self.name} with random seed")
|
| 52 |
+
self.seed = 0
|
| 53 |
+
self.rng = np.random.default_rng()
|
| 54 |
+
self.loader_count = np.zeros(len(self.loaders))
|
| 55 |
+
|
| 56 |
+
def homogeneous_iterator(self) -> Iterator[List[Any]]:
|
| 57 |
+
iteration = 0
|
| 58 |
+
iters = [iter(loader) for loader in self.loaders]
|
| 59 |
+
while True:
|
| 60 |
+
iteration += 1
|
| 61 |
+
try:
|
| 62 |
+
idx = self.rng.choice(len(self.loaders), p=self.ratios)
|
| 63 |
+
self.loader_count[idx] += 1
|
| 64 |
+
if iteration % self.logging_period == 0:
|
| 65 |
+
logger.info(f"Empirical ratios: CDL {self.name} {self.loader_count / self.loader_count.sum()}")
|
| 66 |
+
yield next(iters[idx])
|
| 67 |
+
except StopIteration:
|
| 68 |
+
break
|
| 69 |
+
|
| 70 |
+
def heterogeneous_iterator(self) -> Iterator[List[Any]]:
|
| 71 |
+
pass
|
| 72 |
+
|
| 73 |
+
def __iter__(self) -> Iterator[List[Any]]:
|
| 74 |
+
if self.combining_mode in [self.GLOBAL_HOMOGENEOUS, self.LOCAL_HOMOGENEOUS]:
|
| 75 |
+
logger.info(f"Using homogeneous iterator for CDL {self.name}")
|
| 76 |
+
return self.homogeneous_iterator()
|
| 77 |
+
else:
|
| 78 |
+
raise ValueError(f"Unsupported value of combining_mode ({self.combining_mode})")
|
dinov3/data/samplers.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import itertools
|
| 7 |
+
import warnings
|
| 8 |
+
from typing import Any, Optional
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
import torch
|
| 12 |
+
from torch.utils.data.sampler import Sampler
|
| 13 |
+
|
| 14 |
+
from dinov3.distributed import get_rank, get_world_size
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class EpochSampler(Sampler):
|
| 18 |
+
def __init__(
|
| 19 |
+
self,
|
| 20 |
+
*,
|
| 21 |
+
size: int,
|
| 22 |
+
sample_count: int,
|
| 23 |
+
shuffle: bool = False,
|
| 24 |
+
seed: int = 0,
|
| 25 |
+
start: Optional[int] = None,
|
| 26 |
+
step: Optional[int] = None,
|
| 27 |
+
):
|
| 28 |
+
self._size = size
|
| 29 |
+
self._sample_count = sample_count
|
| 30 |
+
self._shuffle = shuffle
|
| 31 |
+
self._seed = seed
|
| 32 |
+
self._start = get_rank() if start is None else start
|
| 33 |
+
self._step = get_world_size() if step is None else step
|
| 34 |
+
self._epoch = 0
|
| 35 |
+
|
| 36 |
+
def __iter__(self):
|
| 37 |
+
count = (self._size + self._sample_count - 1) // self._sample_count
|
| 38 |
+
tiled_indices = np.tile(np.arange(self._sample_count), count)
|
| 39 |
+
if self._shuffle:
|
| 40 |
+
seed = self._seed * self._epoch if self._seed != 0 else self._epoch
|
| 41 |
+
rng = np.random.default_rng(seed)
|
| 42 |
+
iterable = rng.choice(tiled_indices, self._size, replace=False)
|
| 43 |
+
else:
|
| 44 |
+
iterable = tiled_indices[: self._size]
|
| 45 |
+
|
| 46 |
+
yield from itertools.islice(iterable, self._start, None, self._step)
|
| 47 |
+
|
| 48 |
+
def __len__(self):
|
| 49 |
+
return (self._size - self._start + self._step - 1) // self._step
|
| 50 |
+
|
| 51 |
+
def set_epoch(self, epoch):
|
| 52 |
+
self._epoch = epoch
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def _get_numpy_dtype(size: int) -> Any:
|
| 56 |
+
return np.int32 if size <= 2**31 else np.int64
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def _get_torch_dtype(size: int) -> Any:
|
| 60 |
+
return torch.int32 if size <= 2**31 else torch.int64
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def _generate_randperm_indices(*, size: int, generator: torch.Generator):
|
| 64 |
+
"""Generate the indices of a random permutation."""
|
| 65 |
+
dtype = _get_torch_dtype(size)
|
| 66 |
+
# This is actually matching PyTorch's CPU implementation, see: https://github.com/pytorch/pytorch/blob/master/aten/src/ATen/native/TensorFactories.cpp#L900-L921
|
| 67 |
+
perm = torch.arange(size, dtype=dtype)
|
| 68 |
+
for i in range(size):
|
| 69 |
+
j = torch.randint(i, size, size=(1,), generator=generator).item()
|
| 70 |
+
|
| 71 |
+
# Always swap even if no-op
|
| 72 |
+
value = perm[j].item()
|
| 73 |
+
perm[j] = perm[i].item()
|
| 74 |
+
perm[i] = value
|
| 75 |
+
yield value
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class InfiniteSampler(Sampler):
|
| 79 |
+
def __init__(
|
| 80 |
+
self,
|
| 81 |
+
*,
|
| 82 |
+
sample_count: int,
|
| 83 |
+
shuffle: bool = False,
|
| 84 |
+
seed: int = 0,
|
| 85 |
+
start: Optional[int] = None,
|
| 86 |
+
step: Optional[int] = None,
|
| 87 |
+
advance: int = 0,
|
| 88 |
+
):
|
| 89 |
+
self._sample_count = sample_count
|
| 90 |
+
self._seed = seed
|
| 91 |
+
self._shuffle = shuffle
|
| 92 |
+
self._start = get_rank() if start is None else start
|
| 93 |
+
self._step = get_world_size() if step is None else step
|
| 94 |
+
self._advance = advance
|
| 95 |
+
|
| 96 |
+
def __iter__(self):
|
| 97 |
+
if self._shuffle:
|
| 98 |
+
iterator = self._shuffled_iterator()
|
| 99 |
+
else:
|
| 100 |
+
iterator = self._iterator()
|
| 101 |
+
|
| 102 |
+
yield from itertools.islice(iterator, self._advance, None)
|
| 103 |
+
|
| 104 |
+
def _iterator(self):
|
| 105 |
+
assert not self._shuffle
|
| 106 |
+
|
| 107 |
+
while True:
|
| 108 |
+
iterable = range(self._sample_count)
|
| 109 |
+
yield from itertools.islice(iterable, self._start, None, self._step)
|
| 110 |
+
|
| 111 |
+
def _shuffled_iterator(self):
|
| 112 |
+
assert self._shuffle
|
| 113 |
+
|
| 114 |
+
# Instantiate a generator here (rather than in the ctor) to keep the class
|
| 115 |
+
# picklable (requirement of mp.spawn)
|
| 116 |
+
generator = torch.Generator().manual_seed(self._seed)
|
| 117 |
+
|
| 118 |
+
while True:
|
| 119 |
+
iterable = _generate_randperm_indices(size=self._sample_count, generator=generator)
|
| 120 |
+
yield from itertools.islice(iterable, self._start, None, self._step)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
# The following function is somewhat equivalent to _new_shuffle_tensor_slice below,
|
| 124 |
+
# but avoids a full in-place random permutation generation.
|
| 125 |
+
def _shuffle_tensor_slice(
|
| 126 |
+
*, tensor: torch.Tensor, start: int = 0, step: int = 1, generator: torch.Generator
|
| 127 |
+
) -> np.ndarray:
|
| 128 |
+
stop = len(tensor)
|
| 129 |
+
count = stop // step
|
| 130 |
+
drop_count = stop - step * count
|
| 131 |
+
if drop_count:
|
| 132 |
+
warnings.warn(f"# of dropped samples: {drop_count}", stacklevel=1)
|
| 133 |
+
|
| 134 |
+
dtype = _get_numpy_dtype(stop)
|
| 135 |
+
result = np.empty(count, dtype=dtype)
|
| 136 |
+
|
| 137 |
+
for i in range(count):
|
| 138 |
+
j = torch.randint(0, i + 1, size=(1,), generator=generator).item() if i > 0 else 0
|
| 139 |
+
|
| 140 |
+
result[i] = result[j]
|
| 141 |
+
result[j] = tensor[start + i * step].item()
|
| 142 |
+
|
| 143 |
+
return result
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def _new_shuffle_tensor_slice(
|
| 147 |
+
*, tensor: torch.Tensor, start: int = 0, step: int = 1, generator: torch.Generator
|
| 148 |
+
) -> np.ndarray:
|
| 149 |
+
stop = len(tensor)
|
| 150 |
+
count = stop // step
|
| 151 |
+
dtype = torch.int64 # Needed for using randperm result as indices
|
| 152 |
+
count = stop // step
|
| 153 |
+
drop_count = stop - step * count
|
| 154 |
+
if drop_count:
|
| 155 |
+
warnings.warn(f"# of dropped samples: {drop_count}", stacklevel=1)
|
| 156 |
+
indices = torch.randperm(count, dtype=dtype, generator=generator)
|
| 157 |
+
return tensor[start::step][indices].numpy()
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def _make_seed(seed: int, start: int, iter_count: int) -> int:
|
| 161 |
+
# NOTE: Tried a few variants (including iter_count << 32), this one worked best.
|
| 162 |
+
return seed + start + (iter_count << 24)
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
class ShardedInfiniteSampler(Sampler):
|
| 166 |
+
def __init__(
|
| 167 |
+
self,
|
| 168 |
+
*,
|
| 169 |
+
sample_count: int,
|
| 170 |
+
shuffle: bool = False,
|
| 171 |
+
seed: int = 0,
|
| 172 |
+
start: Optional[int] = None,
|
| 173 |
+
step: Optional[int] = None,
|
| 174 |
+
advance: int = 0,
|
| 175 |
+
use_new_shuffle_tensor_slice: bool = False,
|
| 176 |
+
):
|
| 177 |
+
self._sample_count = sample_count
|
| 178 |
+
self._seed = seed
|
| 179 |
+
self._shuffle = shuffle
|
| 180 |
+
self._start = get_rank() if start is None else start
|
| 181 |
+
self._step = get_world_size() if step is None else step
|
| 182 |
+
self._advance = advance
|
| 183 |
+
self._iter_count = 0
|
| 184 |
+
self._shuffle_tensor_slice_fn = (
|
| 185 |
+
_new_shuffle_tensor_slice if use_new_shuffle_tensor_slice else _shuffle_tensor_slice
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
def __iter__(self):
|
| 189 |
+
iter_count = self._advance // self._sample_count
|
| 190 |
+
if iter_count > 0:
|
| 191 |
+
self._advance -= iter_count * self._sample_count
|
| 192 |
+
self._iter_count += iter_count
|
| 193 |
+
|
| 194 |
+
if self._shuffle:
|
| 195 |
+
iterator = self._shuffled_iterator()
|
| 196 |
+
else:
|
| 197 |
+
iterator = self._iterator()
|
| 198 |
+
|
| 199 |
+
yield from itertools.islice(iterator, self._advance, None)
|
| 200 |
+
|
| 201 |
+
def _iterator(self):
|
| 202 |
+
assert not self._shuffle
|
| 203 |
+
|
| 204 |
+
while True:
|
| 205 |
+
iterable = range(self._sample_count)
|
| 206 |
+
yield from itertools.islice(iterable, self._start, None, self._step)
|
| 207 |
+
|
| 208 |
+
def _shuffled_iterator(self):
|
| 209 |
+
assert self._shuffle
|
| 210 |
+
|
| 211 |
+
# Instantiate a generator here (rather than in the ctor) to be keep the class
|
| 212 |
+
# picklable (requirement of mp.spawn)
|
| 213 |
+
generator = torch.Generator()
|
| 214 |
+
|
| 215 |
+
# Always shuffle everything first
|
| 216 |
+
generator.manual_seed(self._seed)
|
| 217 |
+
dtype = _get_torch_dtype(self._sample_count)
|
| 218 |
+
perm = torch.randperm(self._sample_count, dtype=dtype, generator=generator)
|
| 219 |
+
|
| 220 |
+
while True:
|
| 221 |
+
# Re-seed on each iteration to allow skipping whole permutations
|
| 222 |
+
seed = _make_seed(self._seed, self._start, self._iter_count)
|
| 223 |
+
generator.manual_seed(seed)
|
| 224 |
+
|
| 225 |
+
iterable = self._shuffle_tensor_slice_fn(
|
| 226 |
+
tensor=perm, start=self._start, step=self._step, generator=generator
|
| 227 |
+
)
|
| 228 |
+
yield from iterable
|
| 229 |
+
self._iter_count += 1
|
dinov3/data/transforms.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import logging
|
| 7 |
+
from typing import Sequence
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
from torchvision.transforms import v2
|
| 11 |
+
|
| 12 |
+
logger = logging.getLogger("dinov3")
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def make_interpolation_mode(mode_str: str) -> v2.InterpolationMode:
|
| 16 |
+
return {mode.value: mode for mode in v2.InterpolationMode}[mode_str]
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class GaussianBlur(v2.RandomApply):
|
| 20 |
+
"""
|
| 21 |
+
Apply Gaussian Blur to the PIL image.
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
def __init__(self, *, p: float = 0.5, radius_min: float = 0.1, radius_max: float = 2.0):
|
| 25 |
+
# NOTE: torchvision is applying 1 - probability to return the original image
|
| 26 |
+
keep_p = 1 - p
|
| 27 |
+
transform = v2.GaussianBlur(kernel_size=9, sigma=(radius_min, radius_max))
|
| 28 |
+
super().__init__(transforms=[transform], p=keep_p)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
# Use timm's names
|
| 32 |
+
IMAGENET_DEFAULT_MEAN = (0.485, 0.456, 0.406)
|
| 33 |
+
IMAGENET_DEFAULT_STD = (0.229, 0.224, 0.225)
|
| 34 |
+
|
| 35 |
+
CROP_DEFAULT_SIZE = 224
|
| 36 |
+
RESIZE_DEFAULT_SIZE = int(256 * CROP_DEFAULT_SIZE / 224)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def make_normalize_transform(
|
| 40 |
+
mean: Sequence[float] = IMAGENET_DEFAULT_MEAN,
|
| 41 |
+
std: Sequence[float] = IMAGENET_DEFAULT_STD,
|
| 42 |
+
) -> v2.Normalize:
|
| 43 |
+
return v2.Normalize(mean=mean, std=std)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def make_base_transform(
|
| 47 |
+
mean: Sequence[float] = IMAGENET_DEFAULT_MEAN,
|
| 48 |
+
std: Sequence[float] = IMAGENET_DEFAULT_STD,
|
| 49 |
+
) -> v2.Normalize:
|
| 50 |
+
return v2.Compose(
|
| 51 |
+
[
|
| 52 |
+
v2.ToDtype(torch.float32, scale=True),
|
| 53 |
+
make_normalize_transform(mean=mean, std=std),
|
| 54 |
+
]
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
# This roughly matches torchvision's preset for classification training:
|
| 59 |
+
# https://github.com/pytorch/vision/blob/main/references/classification/presets.py#L6-L44
|
| 60 |
+
def make_classification_train_transform(
|
| 61 |
+
*,
|
| 62 |
+
crop_size: int = CROP_DEFAULT_SIZE,
|
| 63 |
+
interpolation=v2.InterpolationMode.BICUBIC,
|
| 64 |
+
hflip_prob: float = 0.5,
|
| 65 |
+
mean: Sequence[float] = IMAGENET_DEFAULT_MEAN,
|
| 66 |
+
std: Sequence[float] = IMAGENET_DEFAULT_STD,
|
| 67 |
+
):
|
| 68 |
+
transforms_list = [v2.ToImage(), v2.RandomResizedCrop(crop_size, interpolation=interpolation)]
|
| 69 |
+
if hflip_prob > 0.0:
|
| 70 |
+
transforms_list.append(v2.RandomHorizontalFlip(hflip_prob))
|
| 71 |
+
transforms_list.append(make_base_transform(mean, std))
|
| 72 |
+
transform = v2.Compose(transforms_list)
|
| 73 |
+
logger.info(f"Built classification train transform\n{transform}")
|
| 74 |
+
return transform
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def make_resize_transform(
|
| 78 |
+
*,
|
| 79 |
+
resize_size: int,
|
| 80 |
+
resize_square: bool = False,
|
| 81 |
+
resize_large_side: bool = False, # Set the larger side to resize_size instead of the smaller
|
| 82 |
+
interpolation: v2.InterpolationMode = v2.InterpolationMode.BICUBIC,
|
| 83 |
+
):
|
| 84 |
+
assert not (resize_square and resize_large_side), "These two options can not be set together"
|
| 85 |
+
if resize_square:
|
| 86 |
+
logger.info("resizing image as a square")
|
| 87 |
+
size = (resize_size, resize_size)
|
| 88 |
+
transform = v2.Resize(size=size, interpolation=interpolation)
|
| 89 |
+
return transform
|
| 90 |
+
elif resize_large_side:
|
| 91 |
+
logger.info("resizing based on large side")
|
| 92 |
+
transform = v2.Resize(size=None, max_size=resize_size, interpolation=interpolation)
|
| 93 |
+
return transform
|
| 94 |
+
else:
|
| 95 |
+
transform = v2.Resize(resize_size, interpolation=interpolation)
|
| 96 |
+
return transform
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
# Derived from make_classification_eval_transform() with more control over resize and crop
|
| 100 |
+
def make_eval_transform(
|
| 101 |
+
*,
|
| 102 |
+
resize_size: int = RESIZE_DEFAULT_SIZE,
|
| 103 |
+
crop_size: int = CROP_DEFAULT_SIZE,
|
| 104 |
+
resize_square: bool = False,
|
| 105 |
+
resize_large_side: bool = False, # Set the larger side to resize_size instead of the smaller
|
| 106 |
+
interpolation: v2.InterpolationMode = v2.InterpolationMode.BICUBIC,
|
| 107 |
+
mean: Sequence[float] = IMAGENET_DEFAULT_MEAN,
|
| 108 |
+
std: Sequence[float] = IMAGENET_DEFAULT_STD,
|
| 109 |
+
) -> v2.Compose:
|
| 110 |
+
transforms_list = [v2.ToImage()]
|
| 111 |
+
resize_transform = make_resize_transform(
|
| 112 |
+
resize_size=resize_size,
|
| 113 |
+
resize_square=resize_square,
|
| 114 |
+
resize_large_side=resize_large_side,
|
| 115 |
+
interpolation=interpolation,
|
| 116 |
+
)
|
| 117 |
+
transforms_list.append(resize_transform)
|
| 118 |
+
if crop_size:
|
| 119 |
+
transforms_list.append(v2.CenterCrop(crop_size))
|
| 120 |
+
transforms_list.append(make_base_transform(mean, std))
|
| 121 |
+
transform = v2.Compose(transforms_list)
|
| 122 |
+
logger.info(f"Built eval transform\n{transform}")
|
| 123 |
+
return transform
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
# This matches (roughly) torchvision's preset for classification evaluation:
|
| 127 |
+
# https://github.com/pytorch/vision/blob/main/references/classification/presets.py#L47-L69
|
| 128 |
+
def make_classification_eval_transform(
|
| 129 |
+
*,
|
| 130 |
+
resize_size: int = RESIZE_DEFAULT_SIZE,
|
| 131 |
+
crop_size: int = CROP_DEFAULT_SIZE,
|
| 132 |
+
interpolation=v2.InterpolationMode.BICUBIC,
|
| 133 |
+
mean: Sequence[float] = IMAGENET_DEFAULT_MEAN,
|
| 134 |
+
std: Sequence[float] = IMAGENET_DEFAULT_STD,
|
| 135 |
+
) -> v2.Compose:
|
| 136 |
+
return make_eval_transform(
|
| 137 |
+
resize_size=resize_size,
|
| 138 |
+
crop_size=crop_size,
|
| 139 |
+
interpolation=interpolation,
|
| 140 |
+
mean=mean,
|
| 141 |
+
std=std,
|
| 142 |
+
resize_square=False,
|
| 143 |
+
resize_large_side=False,
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def voc2007_classification_target_transform(label, n_categories=20):
|
| 148 |
+
one_hot = torch.zeros(n_categories, dtype=int)
|
| 149 |
+
for instance in label.instances:
|
| 150 |
+
one_hot[instance.category_id] = True
|
| 151 |
+
return one_hot
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def imaterialist_classification_target_transform(label, n_categories=294):
|
| 155 |
+
one_hot = torch.zeros(n_categories, dtype=int)
|
| 156 |
+
one_hot[label.attributes] = True
|
| 157 |
+
return one_hot
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def get_target_transform(dataset_str):
|
| 161 |
+
if "VOC2007" in dataset_str:
|
| 162 |
+
return voc2007_classification_target_transform
|
| 163 |
+
elif "IMaterialist" in dataset_str:
|
| 164 |
+
return imaterialist_classification_target_transform
|
| 165 |
+
return None
|
dinov3/distributed/__init__.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
# isort: skip_file
|
| 7 |
+
from .torch_distributed_wrapper import (
|
| 8 |
+
disable_distributed as disable,
|
| 9 |
+
enable_distributed as enable,
|
| 10 |
+
get_default_process_group,
|
| 11 |
+
get_process_subgroup,
|
| 12 |
+
get_rank,
|
| 13 |
+
get_subgroup_rank,
|
| 14 |
+
get_subgroup_size,
|
| 15 |
+
get_world_size,
|
| 16 |
+
is_distributed_enabled as is_enabled,
|
| 17 |
+
is_main_process,
|
| 18 |
+
is_subgroup_main_process,
|
| 19 |
+
new_subgroups,
|
| 20 |
+
save_in_main_process,
|
| 21 |
+
TorchDistributedEnvironment,
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
from .torch_distributed_primitives import gather_all_tensors, reduce_dict
|
dinov3/distributed/torch_distributed_primitives.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
from typing import Any, Dict, List, Optional
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
import torch.distributed as dist
|
| 10 |
+
from torch.nn import functional as F
|
| 11 |
+
|
| 12 |
+
from .torch_distributed_wrapper import get_default_process_group, get_world_size
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def reduce_dict(input_dict: Dict[Any, torch.Tensor], average: bool = True) -> Dict[Any, torch.Tensor]:
|
| 16 |
+
"""
|
| 17 |
+
Reduce the values in the dictionary from all processes so that all processes
|
| 18 |
+
have the averaged results. Returns a dictionary with the same fields as
|
| 19 |
+
the input dictionary, after reduction.
|
| 20 |
+
|
| 21 |
+
Args:
|
| 22 |
+
input_dict (dict): all the values will be reduced
|
| 23 |
+
average (bool): whether to do average or sum
|
| 24 |
+
"""
|
| 25 |
+
world_size = get_world_size()
|
| 26 |
+
if world_size <= 1:
|
| 27 |
+
return input_dict
|
| 28 |
+
with torch.no_grad():
|
| 29 |
+
names = []
|
| 30 |
+
values = []
|
| 31 |
+
# Sort the keys so that they are consistent across processes
|
| 32 |
+
for k in sorted(input_dict.keys()):
|
| 33 |
+
names.append(k)
|
| 34 |
+
values.append(input_dict[k])
|
| 35 |
+
stacked_values = torch.stack(values, dim=0)
|
| 36 |
+
dist.all_reduce(stacked_values)
|
| 37 |
+
if average:
|
| 38 |
+
stacked_values /= world_size
|
| 39 |
+
reduced_dict = {k: v for k, v in zip(names, stacked_values)}
|
| 40 |
+
return reduced_dict
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def _simple_gather_all_tensors(result: torch.Tensor, group: Any, world_size: int) -> List[torch.Tensor]:
|
| 44 |
+
gathered_result = [torch.zeros_like(result) for _ in range(world_size)]
|
| 45 |
+
dist.all_gather(gathered_result, result, group)
|
| 46 |
+
return gathered_result
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def gather_all_tensors(result: torch.Tensor, group: Optional[Any] = None) -> List[torch.Tensor]:
|
| 50 |
+
"""
|
| 51 |
+
Copied from https://github.com/Lightning-AI/torchmetrics/blob/master/src/torchmetrics/utilities/distributed.py
|
| 52 |
+
Gather all tensors from several ddp processes onto a list that is broadcasted to all processes.
|
| 53 |
+
|
| 54 |
+
Works on tensors that have the same number of dimensions, but where each dimension may differ. In this case
|
| 55 |
+
tensors are padded, gathered and then trimmed to secure equal workload for all processes.
|
| 56 |
+
|
| 57 |
+
Args:
|
| 58 |
+
result: the value to sync
|
| 59 |
+
group: the process group to gather results from. Defaults to all processes (world)
|
| 60 |
+
|
| 61 |
+
Return:
|
| 62 |
+
list with size equal to the process group where element i corresponds to result tensor from process i
|
| 63 |
+
"""
|
| 64 |
+
if group is None:
|
| 65 |
+
group = get_default_process_group()
|
| 66 |
+
|
| 67 |
+
# convert tensors to contiguous format
|
| 68 |
+
result = result.contiguous()
|
| 69 |
+
|
| 70 |
+
world_size = get_world_size()
|
| 71 |
+
dist.barrier(group=group)
|
| 72 |
+
|
| 73 |
+
# if the tensor is scalar, things are easy
|
| 74 |
+
if result.ndim == 0:
|
| 75 |
+
return _simple_gather_all_tensors(result, group, world_size)
|
| 76 |
+
|
| 77 |
+
# 1. Gather sizes of all tensors
|
| 78 |
+
local_size = torch.tensor(result.shape, device=result.device)
|
| 79 |
+
local_sizes = [torch.zeros_like(local_size) for _ in range(world_size)]
|
| 80 |
+
dist.all_gather(local_sizes, local_size, group=group)
|
| 81 |
+
max_size = torch.stack(local_sizes).max(dim=0).values
|
| 82 |
+
all_sizes_equal = all(all(ls == max_size) for ls in local_sizes)
|
| 83 |
+
|
| 84 |
+
# 2. If shapes are all the same, then do a simple gather:
|
| 85 |
+
if all_sizes_equal:
|
| 86 |
+
return _simple_gather_all_tensors(result, group, world_size)
|
| 87 |
+
|
| 88 |
+
# 3. If not, we need to pad each local tensor to maximum size, gather and then truncate
|
| 89 |
+
pad_dims = []
|
| 90 |
+
pad_by = (max_size - local_size).detach().cpu()
|
| 91 |
+
for val in reversed(pad_by):
|
| 92 |
+
pad_dims.append(0)
|
| 93 |
+
pad_dims.append(val.item())
|
| 94 |
+
result_padded = F.pad(result, pad_dims)
|
| 95 |
+
gathered_result = [torch.zeros_like(result_padded) for _ in range(world_size)]
|
| 96 |
+
dist.all_gather(gathered_result, result_padded, group)
|
| 97 |
+
for idx, item_size in enumerate(local_sizes):
|
| 98 |
+
slice_param = [slice(dim_size) for dim_size in item_size]
|
| 99 |
+
gathered_result[idx] = gathered_result[idx][slice_param]
|
| 100 |
+
return gathered_result
|
dinov3/distributed/torch_distributed_wrapper.py
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import logging
|
| 7 |
+
import os
|
| 8 |
+
import random
|
| 9 |
+
import socket
|
| 10 |
+
import subprocess
|
| 11 |
+
from datetime import timedelta
|
| 12 |
+
from enum import Enum
|
| 13 |
+
from typing import List, Sequence
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
import torch.distributed as dist
|
| 17 |
+
|
| 18 |
+
logger = logging.getLogger("dinov3")
|
| 19 |
+
|
| 20 |
+
_DEFAULT_PROCESS_GROUP = None
|
| 21 |
+
_PROCESS_SUBGROUP = None
|
| 22 |
+
_BUILTIN_PRINT = None
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def is_distributed_enabled() -> bool:
|
| 26 |
+
"""
|
| 27 |
+
Returns:
|
| 28 |
+
True if distributed training is enabled.
|
| 29 |
+
"""
|
| 30 |
+
return dist.is_available() and dist.is_initialized()
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def get_rank(group=None) -> int:
|
| 34 |
+
"""
|
| 35 |
+
Returns:
|
| 36 |
+
The rank of the current process within the specified process group.
|
| 37 |
+
"""
|
| 38 |
+
if not is_distributed_enabled():
|
| 39 |
+
return 0
|
| 40 |
+
return dist.get_rank(group=group)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def get_world_size(group=None) -> int:
|
| 44 |
+
"""
|
| 45 |
+
Returns:
|
| 46 |
+
The number of processes in the specified process group.
|
| 47 |
+
"""
|
| 48 |
+
if not is_distributed_enabled():
|
| 49 |
+
return 1
|
| 50 |
+
return dist.get_world_size(group=group)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def is_main_process(group=None) -> bool:
|
| 54 |
+
"""
|
| 55 |
+
Returns:
|
| 56 |
+
True if the current process is the main one in the specified process group.
|
| 57 |
+
"""
|
| 58 |
+
return get_rank(group) == 0
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def save_in_main_process(*args, **kwargs) -> None:
|
| 62 |
+
"""Utility function to save only from the main process."""
|
| 63 |
+
group = kwargs.pop("group", None)
|
| 64 |
+
if not is_main_process(group):
|
| 65 |
+
return
|
| 66 |
+
torch.save(*args, **kwargs)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def _restrict_print_to_main_process() -> None:
|
| 70 |
+
"""This function disables printing when not in the main process."""
|
| 71 |
+
import builtins as __builtin__
|
| 72 |
+
|
| 73 |
+
global _BUILTIN_PRINT
|
| 74 |
+
_BUILTIN_PRINT = __builtin__.print
|
| 75 |
+
|
| 76 |
+
def print(*args, **kwargs):
|
| 77 |
+
force = kwargs.pop("force", False)
|
| 78 |
+
if is_main_process() or force:
|
| 79 |
+
_BUILTIN_PRINT(*args, **kwargs)
|
| 80 |
+
|
| 81 |
+
__builtin__.print = print
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def _get_master_port(seed: int = 0) -> int:
|
| 85 |
+
MIN_MASTER_PORT, MAX_MASTER_PORT = (20_000, 60_000)
|
| 86 |
+
|
| 87 |
+
master_port_str = os.environ.get("MASTER_PORT")
|
| 88 |
+
if master_port_str is None:
|
| 89 |
+
rng = random.Random(seed)
|
| 90 |
+
return rng.randint(MIN_MASTER_PORT, MAX_MASTER_PORT)
|
| 91 |
+
|
| 92 |
+
return int(master_port_str)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def _get_available_port() -> int:
|
| 96 |
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
| 97 |
+
# A "" host address means INADDR_ANY i.e. binding to all interfaces.
|
| 98 |
+
# Note this is not compatible with IPv6.
|
| 99 |
+
s.bind(("", 0))
|
| 100 |
+
port = s.getsockname()[1]
|
| 101 |
+
return port
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def _parse_slurm_node_list(s: str) -> List[str]:
|
| 105 |
+
return subprocess.check_output(["scontrol", "show", "hostnames", s], text=True).splitlines()
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
class JobType(Enum):
|
| 109 |
+
TORCHELASTIC = "TorchElastic"
|
| 110 |
+
SLURM = "Slurm"
|
| 111 |
+
MANUAL = "manual"
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
class TorchDistributedEnvironment:
|
| 115 |
+
"""
|
| 116 |
+
Helper class to get (and set) distributed job information from the
|
| 117 |
+
environment. Identifies and supports (in this order):
|
| 118 |
+
- TorchElastic,
|
| 119 |
+
- Slurm,
|
| 120 |
+
- Manual launch (single-node).
|
| 121 |
+
"""
|
| 122 |
+
|
| 123 |
+
def __init__(self):
|
| 124 |
+
if "TORCHELASTIC_RUN_ID" in os.environ:
|
| 125 |
+
# TorchElastic job created with torchrun
|
| 126 |
+
self.job_id = os.environ["TORCHELASTIC_RUN_ID"]
|
| 127 |
+
self.job_type = JobType.TORCHELASTIC
|
| 128 |
+
|
| 129 |
+
self.master_addr = os.environ["MASTER_ADDR"]
|
| 130 |
+
self.master_port = int(os.environ["MASTER_PORT"])
|
| 131 |
+
self.rank = int(os.environ["RANK"])
|
| 132 |
+
self.world_size = int(os.environ["WORLD_SIZE"])
|
| 133 |
+
self.local_rank = int(os.environ["LOCAL_RANK"])
|
| 134 |
+
self.local_world_size = int(os.environ["LOCAL_WORLD_SIZE"])
|
| 135 |
+
elif "SLURM_JOB_ID" in os.environ:
|
| 136 |
+
# Slurm job created with sbatch, submitit, etc...
|
| 137 |
+
self.job_id = int(os.environ["SLURM_JOB_ID"])
|
| 138 |
+
self.job_type = JobType.SLURM
|
| 139 |
+
|
| 140 |
+
node_count = int(os.environ["SLURM_JOB_NUM_NODES"])
|
| 141 |
+
nodes = _parse_slurm_node_list(os.environ["SLURM_JOB_NODELIST"])
|
| 142 |
+
assert len(nodes) == node_count
|
| 143 |
+
|
| 144 |
+
self.master_addr = nodes[0]
|
| 145 |
+
self.master_port = _get_master_port(seed=self.job_id)
|
| 146 |
+
self.rank = int(os.environ["SLURM_PROCID"])
|
| 147 |
+
self.world_size = int(os.environ["SLURM_NTASKS"])
|
| 148 |
+
self.local_rank = int(os.environ["SLURM_LOCALID"])
|
| 149 |
+
self.local_world_size = self.world_size // node_count
|
| 150 |
+
else:
|
| 151 |
+
# Single node and single job launched manually
|
| 152 |
+
self.job_id = None
|
| 153 |
+
self.job_type = JobType.MANUAL
|
| 154 |
+
|
| 155 |
+
self.master_addr = "127.0.0.1"
|
| 156 |
+
self.master_port = _get_available_port()
|
| 157 |
+
self.rank = 0
|
| 158 |
+
self.world_size = 1
|
| 159 |
+
self.local_rank = 0
|
| 160 |
+
self.local_world_size = 1
|
| 161 |
+
|
| 162 |
+
assert self.rank < self.world_size
|
| 163 |
+
assert self.local_rank < self.local_world_size
|
| 164 |
+
|
| 165 |
+
def export(
|
| 166 |
+
self,
|
| 167 |
+
*,
|
| 168 |
+
overwrite: bool,
|
| 169 |
+
nccl_async_error_handling: bool = False,
|
| 170 |
+
) -> "TorchDistributedEnvironment":
|
| 171 |
+
# See the "Environment variable initialization" section from
|
| 172 |
+
# https://pytorch.org/docs/stable/distributed.html for the complete list of
|
| 173 |
+
# environment variables required for the env:// initialization method.
|
| 174 |
+
env_vars = {
|
| 175 |
+
"MASTER_ADDR": self.master_addr,
|
| 176 |
+
"MASTER_PORT": str(self.master_port),
|
| 177 |
+
"RANK": str(self.rank),
|
| 178 |
+
"WORLD_SIZE": str(self.world_size),
|
| 179 |
+
"LOCAL_RANK": str(self.local_rank),
|
| 180 |
+
"LOCAL_WORLD_SIZE": str(self.local_world_size),
|
| 181 |
+
}
|
| 182 |
+
if nccl_async_error_handling:
|
| 183 |
+
env_vars.update(
|
| 184 |
+
{
|
| 185 |
+
"TORCH_NCCL_ASYNC_ERROR_HANDLING": "1", # "TORCH_" prefix added in PyTorch 2.2
|
| 186 |
+
}
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
if not overwrite:
|
| 190 |
+
for k, v in env_vars.items():
|
| 191 |
+
# Only check for difference with preset environment variables
|
| 192 |
+
if k not in os.environ:
|
| 193 |
+
continue
|
| 194 |
+
if os.environ[k] == v:
|
| 195 |
+
continue
|
| 196 |
+
raise RuntimeError(f"Cannot export environment variables as {k} is already set")
|
| 197 |
+
|
| 198 |
+
os.environ.update(env_vars)
|
| 199 |
+
return self
|
| 200 |
+
|
| 201 |
+
@property
|
| 202 |
+
def is_main_process(self) -> bool:
|
| 203 |
+
return self.rank == 0
|
| 204 |
+
|
| 205 |
+
def __str__(self):
|
| 206 |
+
return (
|
| 207 |
+
f"{self.job_type.value} job "
|
| 208 |
+
+ (f"({self.job_id}) " if self.job_id else "")
|
| 209 |
+
+ f"using {self.master_addr}:{self.master_port} " # noqa: E231
|
| 210 |
+
f"(rank={self.rank}, world size={self.world_size})"
|
| 211 |
+
)
|
| 212 |
+
|
| 213 |
+
def __repr__(self):
|
| 214 |
+
return (
|
| 215 |
+
f"{self.__class__.__name__}("
|
| 216 |
+
f"master_addr={self.master_addr}," # noqa: E231
|
| 217 |
+
f"master_port={self.master_port}," # noqa: E231
|
| 218 |
+
f"rank={self.rank}," # noqa: E231
|
| 219 |
+
f"world_size={self.world_size}," # noqa: E231
|
| 220 |
+
f"local_rank={self.local_rank}," # noqa: E231
|
| 221 |
+
f"local_world_size={self.local_world_size}"
|
| 222 |
+
")"
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def enable_distributed(
|
| 227 |
+
*,
|
| 228 |
+
set_cuda_current_device: bool = True,
|
| 229 |
+
overwrite: bool = False,
|
| 230 |
+
nccl_async_error_handling: bool = False,
|
| 231 |
+
restrict_print_to_main_process: bool = True,
|
| 232 |
+
timeout: timedelta | None = None,
|
| 233 |
+
):
|
| 234 |
+
"""Enable distributed mode.
|
| 235 |
+
|
| 236 |
+
Args:
|
| 237 |
+
set_cuda_current_device: If True, call torch.cuda.set_device() to set the
|
| 238 |
+
current PyTorch CUDA device to the one matching the local rank.
|
| 239 |
+
overwrite: If True, overwrites already set variables. Else fails.
|
| 240 |
+
nccl_async_error_handling: Enables NCCL asynchronous error handling. As a
|
| 241 |
+
side effect, this enables timing out PyTorch distributed operations
|
| 242 |
+
after a default 30 minutes delay).
|
| 243 |
+
restrict_print_to_main_process: If True, the print function of non-main processes
|
| 244 |
+
(ie rank>0) is disabled. Use print(..., force=True) to print anyway.
|
| 245 |
+
If False, nothing is changed and all processes can print as usual.
|
| 246 |
+
timeout: Timeout for operations executed against the process group.
|
| 247 |
+
Default value is 10 minutes for NCCL and 30 minutes for other backends.
|
| 248 |
+
"""
|
| 249 |
+
global _DEFAULT_PROCESS_GROUP
|
| 250 |
+
|
| 251 |
+
if _DEFAULT_PROCESS_GROUP is not None:
|
| 252 |
+
raise RuntimeError("Distributed mode has already been enabled")
|
| 253 |
+
|
| 254 |
+
torch_env = TorchDistributedEnvironment()
|
| 255 |
+
logger.info(f"PyTorch distributed environment: {torch_env}")
|
| 256 |
+
torch_env.export(
|
| 257 |
+
overwrite=overwrite,
|
| 258 |
+
nccl_async_error_handling=nccl_async_error_handling,
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
if set_cuda_current_device:
|
| 262 |
+
torch.cuda.set_device(torch_env.local_rank)
|
| 263 |
+
|
| 264 |
+
dist.init_process_group(backend="nccl", timeout=timeout)
|
| 265 |
+
dist.barrier()
|
| 266 |
+
|
| 267 |
+
if restrict_print_to_main_process:
|
| 268 |
+
_restrict_print_to_main_process()
|
| 269 |
+
|
| 270 |
+
# Finalize setup
|
| 271 |
+
_DEFAULT_PROCESS_GROUP = torch.distributed.group.WORLD
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def get_default_process_group():
|
| 275 |
+
return _DEFAULT_PROCESS_GROUP
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
def disable_distributed() -> None:
|
| 279 |
+
global _BUILTIN_PRINT
|
| 280 |
+
if _BUILTIN_PRINT is not None:
|
| 281 |
+
import builtins as __builtin__
|
| 282 |
+
|
| 283 |
+
__builtin__.print = _BUILTIN_PRINT
|
| 284 |
+
|
| 285 |
+
global _PROCESS_SUBGROUP
|
| 286 |
+
# checking here because get_process_subgroup can return _DEFAULT_PROCESS_GROUP
|
| 287 |
+
if _PROCESS_SUBGROUP is not None:
|
| 288 |
+
torch.distributed.destroy_process_group(_PROCESS_SUBGROUP)
|
| 289 |
+
_PROCESS_SUBGROUP = None
|
| 290 |
+
|
| 291 |
+
global _DEFAULT_PROCESS_GROUP
|
| 292 |
+
if _DEFAULT_PROCESS_GROUP is not None: # not initialized
|
| 293 |
+
torch.distributed.destroy_process_group(_DEFAULT_PROCESS_GROUP)
|
| 294 |
+
_DEFAULT_PROCESS_GROUP = None
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
def new_subgroups(all_subgroup_ranks: Sequence[Sequence[int]]):
|
| 298 |
+
"""Create new process subgroups according to the provided specification.
|
| 299 |
+
|
| 300 |
+
Args:
|
| 301 |
+
all_subgroup_ranks: a sequence of rank sequences (first rank, ..., last rank),
|
| 302 |
+
one for each process subgroup. Example: ((0, 1), (2, 3), (4, 5, 6, 7)).
|
| 303 |
+
|
| 304 |
+
Note:
|
| 305 |
+
This is similar to the (non-documented) new_subgroups_by_enumeration().
|
| 306 |
+
This should be called once (and not sequentially) to create all subgroups.
|
| 307 |
+
"""
|
| 308 |
+
all_ranks = tuple(rank for subgroup_ranks in all_subgroup_ranks for rank in subgroup_ranks)
|
| 309 |
+
rank = get_rank()
|
| 310 |
+
assert len(all_ranks) == len(set(all_ranks))
|
| 311 |
+
assert rank in all_ranks
|
| 312 |
+
|
| 313 |
+
global _PROCESS_SUBGROUP
|
| 314 |
+
assert _PROCESS_SUBGROUP is None
|
| 315 |
+
|
| 316 |
+
for subgroup_ranks in all_subgroup_ranks:
|
| 317 |
+
subgroup = torch.distributed.new_group(subgroup_ranks)
|
| 318 |
+
if rank in subgroup_ranks:
|
| 319 |
+
_PROCESS_SUBGROUP = subgroup
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
def get_process_subgroup():
|
| 323 |
+
"""
|
| 324 |
+
Returns:
|
| 325 |
+
The process subgroup of this rank (or None).
|
| 326 |
+
"""
|
| 327 |
+
return _PROCESS_SUBGROUP or _DEFAULT_PROCESS_GROUP
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def get_subgroup_rank() -> int:
|
| 331 |
+
"""
|
| 332 |
+
Returns:
|
| 333 |
+
The rank of the current process within its process subgroup.
|
| 334 |
+
"""
|
| 335 |
+
return get_rank(group=get_process_subgroup())
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
def get_subgroup_size() -> int:
|
| 339 |
+
"""
|
| 340 |
+
Returns:
|
| 341 |
+
The number of processes in the process subgroup
|
| 342 |
+
"""
|
| 343 |
+
return get_world_size(group=get_process_subgroup())
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
def is_subgroup_main_process() -> bool:
|
| 347 |
+
"""
|
| 348 |
+
Returns:
|
| 349 |
+
True if the current process is the main one within its process subgroup.
|
| 350 |
+
"""
|
| 351 |
+
return get_rank(group=get_process_subgroup()) == 0
|
dinov3/env/__init__.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import contextlib
|
| 7 |
+
import logging
|
| 8 |
+
import os
|
| 9 |
+
import tempfile
|
| 10 |
+
from typing import Optional
|
| 11 |
+
|
| 12 |
+
import submitit.helpers
|
| 13 |
+
|
| 14 |
+
logger = logging.getLogger("dinov3")
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@contextlib.contextmanager
|
| 18 |
+
def clean_env():
|
| 19 |
+
try:
|
| 20 |
+
# Hide torch.compile() variables from the launched evals
|
| 21 |
+
extra_names = ("TRITON_CACHE_DIR", "TORCHINDUCTOR_CACHE_DIR")
|
| 22 |
+
ctx = submitit.helpers.clean_env(extra_names=extra_names)
|
| 23 |
+
except TypeError as e:
|
| 24 |
+
logger.warning("Update submitit to the latest main branch\n%s", e)
|
| 25 |
+
ctx = submitit.helpers.clean_env()
|
| 26 |
+
with ctx:
|
| 27 |
+
yield
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def set_triton_cache_dir(cache_dir: Optional[str] = None) -> None:
|
| 31 |
+
if cache_dir is None:
|
| 32 |
+
cache_dir = tempfile.mkdtemp()
|
| 33 |
+
os.environ["TRITON_CACHE_DIR"] = cache_dir
|
dinov3/eval/.DS_Store
ADDED
|
Binary file (6.15 kB). View file
|
|
|
dinov3/eval/accumulators.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import logging
|
| 7 |
+
from collections import defaultdict
|
| 8 |
+
from typing import Dict, List, Optional
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
from torch import Tensor
|
| 12 |
+
|
| 13 |
+
from dinov3.distributed import gather_all_tensors # Gathers tensors of different sizes
|
| 14 |
+
|
| 15 |
+
logger = logging.getLogger("dinov3")
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def _cat_and_gather_tensor_list(tensor_list: List[Tensor]) -> Tensor:
|
| 19 |
+
local_cat = torch.cat(tensor_list)
|
| 20 |
+
return torch.cat(gather_all_tensors(local_cat))
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class Accumulator:
|
| 24 |
+
def __init__(self) -> None:
|
| 25 |
+
pass
|
| 26 |
+
|
| 27 |
+
def update(self, preds: Tensor, target: Tensor, index: Tensor) -> None:
|
| 28 |
+
raise NotImplementedError
|
| 29 |
+
|
| 30 |
+
def accumulate(self) -> Optional[Dict[str, Tensor]]:
|
| 31 |
+
raise NotImplementedError
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class NoOpAccumulator(Accumulator):
|
| 35 |
+
def __init__(self) -> None:
|
| 36 |
+
pass
|
| 37 |
+
|
| 38 |
+
def update(self, preds: Tensor, target: Tensor, index: Tensor) -> None:
|
| 39 |
+
pass
|
| 40 |
+
|
| 41 |
+
def accumulate(self) -> None:
|
| 42 |
+
return None
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class ResultsAccumulator(Accumulator):
|
| 46 |
+
"""
|
| 47 |
+
Accumulate predictions and targets across processes
|
| 48 |
+
"""
|
| 49 |
+
|
| 50 |
+
def __init__(self) -> None:
|
| 51 |
+
self._local_values: Dict[str, List[Tensor]] = defaultdict(list)
|
| 52 |
+
self._gathered_values: Dict[str, Tensor] = {}
|
| 53 |
+
self._gathered = False
|
| 54 |
+
|
| 55 |
+
def update(self, preds: Tensor, target: Tensor, index: Tensor) -> None:
|
| 56 |
+
assert len(preds) == len(target) == len(index)
|
| 57 |
+
assert not self._gathered, "Tensors have already been gathered in this helper"
|
| 58 |
+
self._local_values["preds"].append(preds)
|
| 59 |
+
self._local_values["target"].append(target)
|
| 60 |
+
self._local_values["index"].append(index)
|
| 61 |
+
self._gathered = False
|
| 62 |
+
|
| 63 |
+
def _gather_tensors(self):
|
| 64 |
+
for k, tensor_list in self._local_values.items():
|
| 65 |
+
self._gathered_values[k] = _cat_and_gather_tensor_list(tensor_list)
|
| 66 |
+
self._gathered = True
|
| 67 |
+
|
| 68 |
+
def accumulate(self) -> Dict[str, Tensor]:
|
| 69 |
+
if not self._gathered:
|
| 70 |
+
self._gather_tensors()
|
| 71 |
+
preds, target, index = [self._gathered_values[k] for k in ["preds", "target", "index"]]
|
| 72 |
+
assert len(preds) == len(target) == len(index) and index.min() == 0
|
| 73 |
+
preds_ordered = torch.zeros((index.max() + 1, *preds.shape[1:]), dtype=preds.dtype, device=preds.device)
|
| 74 |
+
preds_ordered[index] = preds
|
| 75 |
+
target_ordered = torch.zeros((index.max() + 1, *target.shape[1:]), dtype=target.dtype, device=target.device)
|
| 76 |
+
target_ordered[index] = target
|
| 77 |
+
return {"preds": preds_ordered, "target": target_ordered}
|
dinov3/eval/data.py
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import logging
|
| 7 |
+
from functools import lru_cache
|
| 8 |
+
from typing import Any, Callable, Optional
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
import torch
|
| 12 |
+
from torch.utils.data import Subset
|
| 13 |
+
from torchvision.datasets.vision import StandardTransform
|
| 14 |
+
|
| 15 |
+
from dinov3.eval.utils import extract_features
|
| 16 |
+
|
| 17 |
+
logger = logging.getLogger("dinov3")
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class SubsetEx(Subset):
|
| 21 |
+
def _get_actual_index(self, index: int) -> int:
|
| 22 |
+
return self.indices[index]
|
| 23 |
+
|
| 24 |
+
def get_target(self, index: int) -> Any:
|
| 25 |
+
actual_index = self._get_actual_index(index)
|
| 26 |
+
return self.dataset.get_target(actual_index)
|
| 27 |
+
|
| 28 |
+
@property
|
| 29 |
+
def transforms(self):
|
| 30 |
+
return self.dataset.transforms
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def get_target_transform(dataset) -> Optional[Callable]:
|
| 34 |
+
if hasattr(dataset, "transforms"):
|
| 35 |
+
if isinstance(dataset.transforms, StandardTransform):
|
| 36 |
+
return dataset.transforms.target_transform
|
| 37 |
+
raise ValueError("Dataset has a non-standard .transforms property")
|
| 38 |
+
if hasattr(dataset, "target_transform"):
|
| 39 |
+
return dataset.target_transform
|
| 40 |
+
return None
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
@lru_cache(maxsize=1)
|
| 44 |
+
def get_labels(dataset) -> torch.Tensor:
|
| 45 |
+
"""
|
| 46 |
+
Get the labels of a classification dataset, as a Tensor, using the `get_targets` method
|
| 47 |
+
if it is present or loading the labels one by one with `get_target`, if it exists.
|
| 48 |
+
If the dataset has a target transform, iterate over the whole dataset to get the
|
| 49 |
+
transformed labels for each element, then stack them as a torch tensor.
|
| 50 |
+
"""
|
| 51 |
+
logger.info("Getting dataset labels ...")
|
| 52 |
+
if hasattr(dataset, "get_targets") or hasattr(dataset, "get_target"):
|
| 53 |
+
if hasattr(dataset, "get_targets"): # Returns a np.array
|
| 54 |
+
labels = dataset.get_targets()
|
| 55 |
+
elif hasattr(dataset, "get_target"):
|
| 56 |
+
labels = [dataset.get_target(i) for i in range(len(dataset))]
|
| 57 |
+
target_transform = get_target_transform(dataset)
|
| 58 |
+
if target_transform is not None:
|
| 59 |
+
labels = [target_transform(label) for label in labels]
|
| 60 |
+
else:
|
| 61 |
+
# Target transform is applied in this case
|
| 62 |
+
labels = [dataset[i][1] for i in range(len(dataset))]
|
| 63 |
+
return torch.stack([torch.tensor(label, dtype=int) for label in labels])
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def get_num_classes(dataset) -> int:
|
| 67 |
+
"""
|
| 68 |
+
Get the labels of a dataset and compute the number of classes
|
| 69 |
+
"""
|
| 70 |
+
labels = get_labels(dataset)
|
| 71 |
+
if len(labels.shape) > 1:
|
| 72 |
+
return int(labels.shape[1])
|
| 73 |
+
return int(labels.max() + 1)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def create_class_indices_mapping(labels: torch.Tensor) -> dict[int, torch.Tensor]:
|
| 77 |
+
"""
|
| 78 |
+
Efficiently creates a mapping between the labels and tensors containing
|
| 79 |
+
the indices of all the dataset elements that share this label.
|
| 80 |
+
In the case of multiple labels, it is not guaranteed that there
|
| 81 |
+
will be exactly the specified percentage of labels.
|
| 82 |
+
"""
|
| 83 |
+
if len(labels.shape) > 1: # labels are a one-hot encoding
|
| 84 |
+
assert len(labels.shape) == 2
|
| 85 |
+
sorted_labels, indices = torch.nonzero(labels.T, as_tuple=True)
|
| 86 |
+
else:
|
| 87 |
+
sorted_labels, indices = torch.sort(labels, stable=True)
|
| 88 |
+
unique_labels, counts = torch.unique_consecutive(sorted_labels, return_counts=True)
|
| 89 |
+
mapping = dict(zip(unique_labels.tolist(), torch.split(indices, counts.tolist())))
|
| 90 |
+
return mapping
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def _shuffle_dataset(dataset: torch.Tensor, seed: int = 0):
|
| 94 |
+
"""
|
| 95 |
+
Shuffling a dataset by subsetting it with a random permutation of its indices
|
| 96 |
+
"""
|
| 97 |
+
random_generator = torch.Generator()
|
| 98 |
+
random_generator.manual_seed(seed)
|
| 99 |
+
random_indices = torch.randperm(len(dataset), generator=random_generator)
|
| 100 |
+
return SubsetEx(dataset, random_indices)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def _subset_dataset_per_class(
|
| 104 |
+
class_indices_mapping: dict[int, torch.Tensor],
|
| 105 |
+
n_or_percent_per_class: int | float,
|
| 106 |
+
dataset_size: int,
|
| 107 |
+
seed: int = 0,
|
| 108 |
+
is_percent: bool = False,
|
| 109 |
+
) -> torch.Tensor:
|
| 110 |
+
"""
|
| 111 |
+
Helper function to select a percentage of a dataset, equally distributed across classes,
|
| 112 |
+
or to take the same number of elements from each class of the dataset.
|
| 113 |
+
Returns a boolean mask tensor being True at indices of selected elements
|
| 114 |
+
"""
|
| 115 |
+
|
| 116 |
+
random_generator = torch.Generator()
|
| 117 |
+
random_generator.manual_seed(seed)
|
| 118 |
+
|
| 119 |
+
final_indices_bool = torch.zeros(dataset_size, dtype=bool)
|
| 120 |
+
for class_indices in class_indices_mapping.values():
|
| 121 |
+
# Select at least one element
|
| 122 |
+
n_for_class = max(int(len(class_indices) * n_or_percent_per_class), 1) if is_percent else n_or_percent_per_class
|
| 123 |
+
assert isinstance(n_for_class, int)
|
| 124 |
+
filtered_index = torch.randperm(len(class_indices), generator=random_generator)[:n_for_class]
|
| 125 |
+
final_indices_bool[class_indices[filtered_index]] = True
|
| 126 |
+
return final_indices_bool
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def _multilabel_rebalance_subset(
|
| 130 |
+
class_indices_mapping: dict[int, torch.Tensor],
|
| 131 |
+
n_or_percent_per_class: int | float,
|
| 132 |
+
labels: torch.Tensor,
|
| 133 |
+
indices_bool: torch.Tensor,
|
| 134 |
+
dataset_size: int,
|
| 135 |
+
seed: int = 0,
|
| 136 |
+
) -> torch.Tensor:
|
| 137 |
+
"""
|
| 138 |
+
Helper function to refine a subset of a multi-label dataset (indices_bool)
|
| 139 |
+
to better match a target percentage of labels.
|
| 140 |
+
Returns a boolean mask tensor being True at indices of selected elements.
|
| 141 |
+
"""
|
| 142 |
+
|
| 143 |
+
# Compute the number of selected labels in indices_bool
|
| 144 |
+
num_total_labels = labels.sum()
|
| 145 |
+
num_wanted_labels = int(num_total_labels * n_or_percent_per_class)
|
| 146 |
+
num_selected_labels = (labels[indices_bool] > 0).sum()
|
| 147 |
+
logger.info(f" {num_selected_labels} labels instead of {num_wanted_labels}")
|
| 148 |
+
|
| 149 |
+
# Compute a new percentage and new set selecting less images, therefore less labels, to match approximatelly the exact percentage of labels selected
|
| 150 |
+
n_or_percent_per_class = n_or_percent_per_class / (num_selected_labels / num_wanted_labels)
|
| 151 |
+
final_indices_bool = _subset_dataset_per_class(
|
| 152 |
+
class_indices_mapping, n_or_percent_per_class, dataset_size, seed, True
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
# Compute the number of labels finally used
|
| 156 |
+
num_selected_labels = (labels[final_indices_bool] > 0).sum()
|
| 157 |
+
logger.info(f" {num_selected_labels} labels instead of {num_wanted_labels}")
|
| 158 |
+
|
| 159 |
+
return final_indices_bool
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def split_train_val_datasets(train_dataset, split_percentage: float = 0.1, shuffle_train: bool = True):
|
| 163 |
+
"""
|
| 164 |
+
Splitting a percent of the train dataset to choose hyperparameters, taking the same percentage for each class.
|
| 165 |
+
If `shuffle` is False, taking the first elements of each class as the validaton set.
|
| 166 |
+
"""
|
| 167 |
+
assert 0 < split_percentage < 1
|
| 168 |
+
logger.info(f"Selecting {int(split_percentage * 100)}% of the train dataset as the validation set")
|
| 169 |
+
if shuffle_train:
|
| 170 |
+
logger.info("Shuffling train dataset before splitting in train and validation sets")
|
| 171 |
+
train_dataset = _shuffle_dataset(train_dataset)
|
| 172 |
+
train_labels = get_labels(train_dataset)
|
| 173 |
+
class_indices_mapping = create_class_indices_mapping(train_labels)
|
| 174 |
+
val_mask = torch.zeros(len(train_labels), dtype=bool)
|
| 175 |
+
for class_indices in class_indices_mapping.values():
|
| 176 |
+
# If there is only one element, it goes in the train set
|
| 177 |
+
n_for_val = max(1, int(split_percentage * len(class_indices))) if len(class_indices) > 1 else 0
|
| 178 |
+
val_mask[class_indices[:n_for_val]] = True
|
| 179 |
+
|
| 180 |
+
val_dataset = SubsetEx(train_dataset, val_mask.nonzero().flatten())
|
| 181 |
+
train_dataset = SubsetEx(train_dataset, (~val_mask).nonzero().flatten())
|
| 182 |
+
return train_dataset, val_dataset
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def create_train_dataset_dict(
|
| 186 |
+
train_dataset,
|
| 187 |
+
few_shot_eval: bool = False,
|
| 188 |
+
few_shot_k_or_percent: float | None = None,
|
| 189 |
+
few_shot_n_tries: int = 1,
|
| 190 |
+
) -> dict[int, dict[int, Any]]:
|
| 191 |
+
"""
|
| 192 |
+
Randomly split a dataset for few-shot evaluation, with `few_shot_k_or_percent` being
|
| 193 |
+
n elements or x% of a class. Produces a dict, which keys are number of random "tries"
|
| 194 |
+
and values are the dataset subset for this "try".
|
| 195 |
+
|
| 196 |
+
Format is {"nth-try": dataset}
|
| 197 |
+
"""
|
| 198 |
+
if few_shot_eval is False:
|
| 199 |
+
assert few_shot_k_or_percent is None
|
| 200 |
+
assert few_shot_n_tries == 1
|
| 201 |
+
return {0: train_dataset}
|
| 202 |
+
|
| 203 |
+
assert few_shot_k_or_percent is not None
|
| 204 |
+
train_labels = get_labels(train_dataset)
|
| 205 |
+
class_indices_mapping = create_class_indices_mapping(train_labels)
|
| 206 |
+
train_dataset_dict: dict[int, Any] = {}
|
| 207 |
+
is_percent = few_shot_k_or_percent < 1
|
| 208 |
+
if not is_percent:
|
| 209 |
+
few_shot_k_or_percent = int(few_shot_k_or_percent)
|
| 210 |
+
|
| 211 |
+
for t in range(few_shot_n_tries):
|
| 212 |
+
t_subset_bool = _subset_dataset_per_class(
|
| 213 |
+
class_indices_mapping=class_indices_mapping,
|
| 214 |
+
n_or_percent_per_class=few_shot_k_or_percent,
|
| 215 |
+
dataset_size=len(train_labels),
|
| 216 |
+
is_percent=is_percent,
|
| 217 |
+
seed=t,
|
| 218 |
+
)
|
| 219 |
+
if len(train_labels.shape) > 1 and is_percent:
|
| 220 |
+
t_subset_bool = _multilabel_rebalance_subset(
|
| 221 |
+
class_indices_mapping=class_indices_mapping,
|
| 222 |
+
n_or_percent_per_class=few_shot_k_or_percent,
|
| 223 |
+
dataset_size=len(train_labels),
|
| 224 |
+
labels=train_labels,
|
| 225 |
+
indices_bool=t_subset_bool,
|
| 226 |
+
seed=t,
|
| 227 |
+
)
|
| 228 |
+
train_dataset_dict[t] = SubsetEx(train_dataset, t_subset_bool.nonzero().flatten())
|
| 229 |
+
return train_dataset_dict
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def extract_features_for_dataset_dict(
|
| 233 |
+
model, dataset_dict: dict[int, dict[int, Any]], batch_size: int, num_workers: int, gather_on_cpu=False
|
| 234 |
+
) -> dict[int, dict[str, torch.Tensor]]:
|
| 235 |
+
"""
|
| 236 |
+
Extract features for each subset of dataset in the context of few-shot evaluations
|
| 237 |
+
"""
|
| 238 |
+
few_shot_data_dict: dict[int, dict[str, torch.Tensor]] = {}
|
| 239 |
+
for try_n, dataset in dataset_dict.items():
|
| 240 |
+
features, labels = extract_features(model, dataset, batch_size, num_workers, gather_on_cpu=gather_on_cpu)
|
| 241 |
+
few_shot_data_dict[try_n] = {"train_features": features, "train_labels": labels}
|
| 242 |
+
return few_shot_data_dict
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def pad_multilabel_and_collate(batch, pad_value=-1):
|
| 246 |
+
"""
|
| 247 |
+
This method pads and collates a batch of (image, (index, target)) tuples, coming from
|
| 248 |
+
DatasetWithEnumeratedTargets, with targets that are list of potentially varying sizes.
|
| 249 |
+
The targets are padded to the length of the longest target list in the batch.
|
| 250 |
+
"""
|
| 251 |
+
maxlen = max(len(targets) for _, (_, targets) in batch)
|
| 252 |
+
padded_batch = [
|
| 253 |
+
(image, (index, np.pad(targets, (0, maxlen - len(targets)), constant_values=pad_value)))
|
| 254 |
+
for image, (index, targets) in batch
|
| 255 |
+
]
|
| 256 |
+
return torch.utils.data.default_collate(padded_batch)
|
dinov3/eval/dense/depth/models/__init__.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
from dinov3.eval.dense.depth.utils import cast_to
|
| 8 |
+
|
| 9 |
+
from .dpt_head import DPTHead
|
| 10 |
+
from .encoder import BackboneLayersSet, DinoVisionTransformerWrapper, PatchSizeAdaptationStrategy
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class FeaturesToDepth(torch.nn.Module):
|
| 14 |
+
def __init__(
|
| 15 |
+
self,
|
| 16 |
+
min_depth=0.001,
|
| 17 |
+
max_depth=80,
|
| 18 |
+
bins_strategy="linear",
|
| 19 |
+
norm_strategy="linear",
|
| 20 |
+
):
|
| 21 |
+
"""
|
| 22 |
+
Module which converts a feature maps into a depth map
|
| 23 |
+
|
| 24 |
+
Args:
|
| 25 |
+
min_depth (float): minimum depth, used to calibrate the depth range
|
| 26 |
+
max_depth (float): maximum depth, used to calibrate the depth range
|
| 27 |
+
bins_strategy (str): Choices are 'linear' or 'log', for Uniform or Scale Invariant distributions for depth bins.
|
| 28 |
+
See AdaBins [1] for more details.
|
| 29 |
+
norm_strategy (str): Choices are 'linear', 'softmax' or 'sigmoid', for the conversion of features to depth logits
|
| 30 |
+
scale_up (bool): If true, and only if regression by classification is not used, the result is multiplied by max_depth
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
Example:
|
| 34 |
+
x = depth_model(input_image) # N C H W
|
| 35 |
+
- If pure regression (C == 1), depth is obtained by scaling and/or shifting x
|
| 36 |
+
- If C > 1, bins are used:
|
| 37 |
+
Depth is obtained as a weighted sum of depth bins, where weights are predicted logits. (see AdaBins [1] for more details)
|
| 38 |
+
|
| 39 |
+
[1] AdaBins: https://github.com/shariqfarooq123/AdaBins
|
| 40 |
+
"""
|
| 41 |
+
super().__init__()
|
| 42 |
+
self.min_depth = min_depth
|
| 43 |
+
self.max_depth = max_depth
|
| 44 |
+
assert bins_strategy in ["linear", "log"], "Support bins_strategy: linear, log"
|
| 45 |
+
assert norm_strategy in ["linear", "softmax", "sigmoid"], "Support norm_strategy: linear, softmax, sigmoid"
|
| 46 |
+
|
| 47 |
+
self.bins_strategy = bins_strategy
|
| 48 |
+
self.norm_strategy = norm_strategy
|
| 49 |
+
|
| 50 |
+
def forward(self, x):
|
| 51 |
+
n_bins = x.shape[1] # N n_bins H W
|
| 52 |
+
if n_bins > 1:
|
| 53 |
+
if self.bins_strategy == "linear":
|
| 54 |
+
bins = torch.linspace(self.min_depth, self.max_depth, n_bins, device=x.device)
|
| 55 |
+
elif self.bins_strategy == "log":
|
| 56 |
+
bins = torch.linspace(
|
| 57 |
+
torch.log(torch.tensor(self.min_depth)),
|
| 58 |
+
torch.log(torch.tensor(self.max_depth)),
|
| 59 |
+
n_bins,
|
| 60 |
+
device=x.device,
|
| 61 |
+
)
|
| 62 |
+
bins = torch.exp(bins)
|
| 63 |
+
|
| 64 |
+
# following Adabins, default linear
|
| 65 |
+
if self.norm_strategy == "linear":
|
| 66 |
+
logit = torch.relu(x)
|
| 67 |
+
eps = 0.1
|
| 68 |
+
logit = logit + eps
|
| 69 |
+
logit = logit / logit.sum(dim=1, keepdim=True)
|
| 70 |
+
elif self.norm_strategy == "softmax":
|
| 71 |
+
logit = torch.softmax(x, dim=1)
|
| 72 |
+
elif self.norm_strategy == "sigmoid":
|
| 73 |
+
logit = torch.sigmoid(x)
|
| 74 |
+
logit = logit / logit.sum(dim=1, keepdim=True)
|
| 75 |
+
|
| 76 |
+
output = torch.einsum("ikmn,k->imn", [logit, bins]).unsqueeze(dim=1)
|
| 77 |
+
else:
|
| 78 |
+
# standard regression
|
| 79 |
+
output = torch.relu(x) + self.min_depth
|
| 80 |
+
return output
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def make_head(
|
| 84 |
+
embed_dims: int | list[int],
|
| 85 |
+
n_output_channels: int,
|
| 86 |
+
use_batchnorm: bool = False,
|
| 87 |
+
use_cls_token: bool = False,
|
| 88 |
+
# upsample: int = 4,
|
| 89 |
+
head_type: str = "linear",
|
| 90 |
+
**kwargs,
|
| 91 |
+
) -> torch.nn.Module:
|
| 92 |
+
|
| 93 |
+
if isinstance(embed_dims, int):
|
| 94 |
+
embed_dims = [embed_dims]
|
| 95 |
+
|
| 96 |
+
if head_type == "linear":
|
| 97 |
+
raise NotImplementedError
|
| 98 |
+
elif head_type == "dpt":
|
| 99 |
+
decoder = DPTHead(
|
| 100 |
+
in_channels=embed_dims,
|
| 101 |
+
n_output_channels=n_output_channels,
|
| 102 |
+
readout_type="project" if use_cls_token else "ignore",
|
| 103 |
+
use_batchnorm=use_batchnorm,
|
| 104 |
+
**kwargs, # TODO add here post_process_channels, n_hidden_channels
|
| 105 |
+
)
|
| 106 |
+
else:
|
| 107 |
+
raise NotImplementedError("only linear and DPT head supported")
|
| 108 |
+
return decoder
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
class EncoderDecoder(torch.nn.Module):
|
| 112 |
+
def __init__(
|
| 113 |
+
self,
|
| 114 |
+
encoder: torch.nn.Module,
|
| 115 |
+
decoder: torch.nn.Module,
|
| 116 |
+
encoder_dtype=torch.float,
|
| 117 |
+
decoder_dtype=torch.float,
|
| 118 |
+
):
|
| 119 |
+
super().__init__()
|
| 120 |
+
self.encoder = encoder
|
| 121 |
+
self.decoder = decoder
|
| 122 |
+
self.encoder_dtype = encoder_dtype
|
| 123 |
+
self.decoder_dtype = decoder_dtype
|
| 124 |
+
self.is_cuda = torch.cuda.is_available()
|
| 125 |
+
|
| 126 |
+
def forward(self, x):
|
| 127 |
+
with torch.autocast("cuda" if torch.cuda.is_available() else "cpu", enabled=False):
|
| 128 |
+
x = x.to(self.encoder_dtype)
|
| 129 |
+
x = self.encoder(x)
|
| 130 |
+
x = cast_to(x, self.decoder_dtype)
|
| 131 |
+
return self.decoder(x)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def build_depther(
|
| 135 |
+
backbone: torch.nn.Module,
|
| 136 |
+
backbone_out_layers: list[int] | BackboneLayersSet,
|
| 137 |
+
n_output_channels: int,
|
| 138 |
+
use_backbone_norm: bool = False,
|
| 139 |
+
use_batchnorm: bool = False,
|
| 140 |
+
use_cls_token: bool = False,
|
| 141 |
+
adapt_to_patch_size: PatchSizeAdaptationStrategy = PatchSizeAdaptationStrategy.CENTER_PADDING,
|
| 142 |
+
head_type: str = "dpt",
|
| 143 |
+
encoder_dtype: torch.dtype = torch.float,
|
| 144 |
+
decoder_dtype: torch.dtype = torch.float,
|
| 145 |
+
# depth args
|
| 146 |
+
min_depth: float = 0.001,
|
| 147 |
+
max_depth: float = 10.0,
|
| 148 |
+
bins_strategy: str = "linear",
|
| 149 |
+
norm_strategy: str = "linear",
|
| 150 |
+
**kwargs,
|
| 151 |
+
):
|
| 152 |
+
encoder = DinoVisionTransformerWrapper(
|
| 153 |
+
backbone_model=backbone,
|
| 154 |
+
backbone_out_layers=backbone_out_layers,
|
| 155 |
+
use_backbone_norm=use_backbone_norm,
|
| 156 |
+
adapt_to_patch_size=adapt_to_patch_size,
|
| 157 |
+
)
|
| 158 |
+
encoder = encoder.to(encoder_dtype)
|
| 159 |
+
encoder.eval()
|
| 160 |
+
|
| 161 |
+
decoder = make_head(
|
| 162 |
+
encoder.embed_dims,
|
| 163 |
+
n_output_channels=n_output_channels,
|
| 164 |
+
use_batchnorm=use_batchnorm,
|
| 165 |
+
use_cls_token=use_cls_token,
|
| 166 |
+
head_type=head_type,
|
| 167 |
+
**kwargs,
|
| 168 |
+
)
|
| 169 |
+
decoder.eval()
|
| 170 |
+
|
| 171 |
+
features_to_depth = FeaturesToDepth(
|
| 172 |
+
min_depth=min_depth,
|
| 173 |
+
max_depth=max_depth,
|
| 174 |
+
bins_strategy=bins_strategy,
|
| 175 |
+
norm_strategy=norm_strategy,
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
return torch.nn.Sequential(
|
| 179 |
+
EncoderDecoder(
|
| 180 |
+
encoder,
|
| 181 |
+
decoder,
|
| 182 |
+
encoder_dtype=encoder_dtype,
|
| 183 |
+
decoder_dtype=decoder_dtype,
|
| 184 |
+
),
|
| 185 |
+
features_to_depth,
|
| 186 |
+
)
|
dinov3/eval/dense/depth/models/dpt_head.py
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
from torch import nn
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def kaiming_init(
|
| 11 |
+
module: nn.Module,
|
| 12 |
+
a: float = 0,
|
| 13 |
+
mode: str = "fan_out",
|
| 14 |
+
nonlinearity: str = "relu",
|
| 15 |
+
bias: float = 0,
|
| 16 |
+
distribution: str = "normal",
|
| 17 |
+
) -> None:
|
| 18 |
+
assert distribution in ["uniform", "normal"]
|
| 19 |
+
if hasattr(module, "weight") and module.weight is not None:
|
| 20 |
+
if distribution == "uniform":
|
| 21 |
+
nn.init.kaiming_uniform_(module.weight, a=a, mode=mode, nonlinearity=nonlinearity)
|
| 22 |
+
else:
|
| 23 |
+
nn.init.kaiming_normal_(module.weight, a=a, mode=mode, nonlinearity=nonlinearity)
|
| 24 |
+
if hasattr(module, "bias") and module.bias is not None:
|
| 25 |
+
nn.init.constant_(module.bias, bias)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def constant_init(module, val, bias=0):
|
| 29 |
+
if hasattr(module, "weight") and module.weight is not None:
|
| 30 |
+
nn.init.constant_(module.weight, val)
|
| 31 |
+
if hasattr(module, "bias") and module.bias is not None:
|
| 32 |
+
nn.init.constant_(module.bias, bias)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class ConvModule(nn.Module):
|
| 36 |
+
"""A conv block that bundles conv/norm/activation layers.
|
| 37 |
+
This block simplifies the usage of convolution layers, which are commonly
|
| 38 |
+
used with a norm layer (e.g., BatchNorm) and activation layer (e.g., ReLU).
|
| 39 |
+
It is based upon three build methods: `build_conv_layer()`,
|
| 40 |
+
`build_norm_layer()` and `build_activation_layer()`.
|
| 41 |
+
Besides, we add some additional features in this module.
|
| 42 |
+
1. Automatically set `bias` of the conv layer.
|
| 43 |
+
2. Spectral norm is supported.
|
| 44 |
+
3. More padding modes are supported. Before PyTorch 1.5, nn.Conv2d only
|
| 45 |
+
supports zero and circular padding, and we add "reflect" padding mode.
|
| 46 |
+
Args:
|
| 47 |
+
in_channels (int): Number of channels in the input feature map.
|
| 48 |
+
Same as that in ``nn._ConvNd``.
|
| 49 |
+
out_channels (int): Number of channels produced by the convolution.
|
| 50 |
+
Same as that in ``nn._ConvNd``.
|
| 51 |
+
kernel_size (int | tuple[int]): Size of the convolving kernel.
|
| 52 |
+
Same as that in ``nn._ConvNd``.
|
| 53 |
+
stride (int | tuple[int]): Stride of the convolution.
|
| 54 |
+
Same as that in ``nn._ConvNd``.
|
| 55 |
+
padding (int | tuple[int]): Zero-padding added to both sides of
|
| 56 |
+
the input. Same as that in ``nn._ConvNd``.
|
| 57 |
+
dilation (int | tuple[int]): Spacing between kernel elements.
|
| 58 |
+
Same as that in ``nn._ConvNd``.
|
| 59 |
+
groups (int): Number of blocked connections from input channels to
|
| 60 |
+
output channels. Same as that in ``nn._ConvNd``.
|
| 61 |
+
bias (bool | str): If specified as `auto`, it will be decided by the
|
| 62 |
+
norm_cfg. Bias will be set as True if `norm_cfg` is None, otherwise
|
| 63 |
+
False. Default: "auto".
|
| 64 |
+
conv_cfg (dict): Config dict for convolution layer. Default: None,
|
| 65 |
+
which means using conv2d.
|
| 66 |
+
norm_cfg (dict): Config dict for normalization layer. Default: None.
|
| 67 |
+
act_cfg (dict): Config dict for activation layer.
|
| 68 |
+
Default: dict(type='ReLU').
|
| 69 |
+
inplace (bool): Whether to use inplace mode for activation.
|
| 70 |
+
Default: True.
|
| 71 |
+
with_spectral_norm (bool): Whether use spectral norm in conv module.
|
| 72 |
+
Default: False.
|
| 73 |
+
padding_mode (str): If the `padding_mode` has not been supported by
|
| 74 |
+
current `Conv2d` in PyTorch, we will use our own padding layer
|
| 75 |
+
instead. Currently, we support ['zeros', 'circular'] with official
|
| 76 |
+
implementation and ['reflect'] with our own implementation.
|
| 77 |
+
Default: 'zeros'.
|
| 78 |
+
order (tuple[str]): The order of conv/norm/activation layers. It is a
|
| 79 |
+
sequence of "conv", "norm" and "act". Common examples are
|
| 80 |
+
("conv", "norm", "act") and ("act", "conv", "norm").
|
| 81 |
+
Default: ('conv', 'norm', 'act').
|
| 82 |
+
"""
|
| 83 |
+
|
| 84 |
+
_abbr_ = "conv_block"
|
| 85 |
+
|
| 86 |
+
def __init__(
|
| 87 |
+
self,
|
| 88 |
+
in_channels,
|
| 89 |
+
out_channels,
|
| 90 |
+
kernel_size,
|
| 91 |
+
stride=1,
|
| 92 |
+
padding=0,
|
| 93 |
+
dilation=1,
|
| 94 |
+
groups=1,
|
| 95 |
+
bias="auto",
|
| 96 |
+
conv_cfg=None,
|
| 97 |
+
norm_cfg=None,
|
| 98 |
+
act_cfg=dict(type="ReLU"),
|
| 99 |
+
inplace=True,
|
| 100 |
+
with_spectral_norm=False,
|
| 101 |
+
padding_mode="zeros",
|
| 102 |
+
order=("conv", "norm", "act"),
|
| 103 |
+
):
|
| 104 |
+
super().__init__()
|
| 105 |
+
assert conv_cfg is None or isinstance(conv_cfg, dict)
|
| 106 |
+
assert norm_cfg is None or isinstance(norm_cfg, dict)
|
| 107 |
+
assert act_cfg is None or isinstance(act_cfg, dict)
|
| 108 |
+
official_padding_mode = ["zeros", "circular"]
|
| 109 |
+
self.conv_cfg = conv_cfg
|
| 110 |
+
self.norm_cfg = norm_cfg
|
| 111 |
+
self.act_cfg = act_cfg
|
| 112 |
+
self.inplace = inplace
|
| 113 |
+
self.with_spectral_norm = with_spectral_norm
|
| 114 |
+
self.with_explicit_padding = padding_mode not in official_padding_mode
|
| 115 |
+
self.order = order
|
| 116 |
+
assert isinstance(self.order, tuple) and len(self.order) == 3
|
| 117 |
+
assert set(order) == {"conv", "norm", "act"}
|
| 118 |
+
|
| 119 |
+
self.with_norm = norm_cfg is not None
|
| 120 |
+
self.with_activation = act_cfg is not None
|
| 121 |
+
# if the conv layer is before a norm layer, bias is unnecessary.
|
| 122 |
+
if bias == "auto":
|
| 123 |
+
bias = not self.with_norm
|
| 124 |
+
self.with_bias = bias
|
| 125 |
+
|
| 126 |
+
# if self.with_explicit_padding:
|
| 127 |
+
# pad_cfg = dict(type=padding_mode)
|
| 128 |
+
# self.padding_layer = build_padding_layer(pad_cfg, padding)
|
| 129 |
+
# to do Camille put back
|
| 130 |
+
|
| 131 |
+
# reset padding to 0 for conv module
|
| 132 |
+
conv_padding = 0 if self.with_explicit_padding else padding
|
| 133 |
+
# build convolution layer
|
| 134 |
+
self.conv = nn.Conv2d( # build_conv_layer(#conv_cfg,
|
| 135 |
+
in_channels,
|
| 136 |
+
out_channels,
|
| 137 |
+
kernel_size,
|
| 138 |
+
stride=stride,
|
| 139 |
+
padding=conv_padding,
|
| 140 |
+
dilation=dilation,
|
| 141 |
+
groups=groups,
|
| 142 |
+
bias=bias,
|
| 143 |
+
)
|
| 144 |
+
# export the attributes of self.conv to a higher level for convenience
|
| 145 |
+
self.in_channels = self.conv.in_channels
|
| 146 |
+
self.out_channels = self.conv.out_channels
|
| 147 |
+
self.kernel_size = self.conv.kernel_size
|
| 148 |
+
self.stride = self.conv.stride
|
| 149 |
+
self.padding = padding
|
| 150 |
+
self.dilation = self.conv.dilation
|
| 151 |
+
self.transposed = self.conv.transposed
|
| 152 |
+
self.output_padding = self.conv.output_padding
|
| 153 |
+
self.groups = self.conv.groups
|
| 154 |
+
|
| 155 |
+
if self.with_spectral_norm:
|
| 156 |
+
self.conv = nn.utils.spectral_norm(self.conv)
|
| 157 |
+
|
| 158 |
+
# # build normalization layers
|
| 159 |
+
if self.with_norm:
|
| 160 |
+
# norm layer is after conv layer
|
| 161 |
+
if order.index("norm") > order.index("conv"):
|
| 162 |
+
norm_channels = out_channels
|
| 163 |
+
else:
|
| 164 |
+
norm_channels = in_channels
|
| 165 |
+
# self.norm_name, norm = build_norm_layer(
|
| 166 |
+
# norm_cfg, norm_channels) # type: ignore
|
| 167 |
+
self.add_module("bn", torch.nn.SyncBatchNorm(norm_channels))
|
| 168 |
+
# if self.with_bias:
|
| 169 |
+
# if isinstance(norm, (_BatchNorm, _InstanceNorm)):
|
| 170 |
+
# warnings.warn(
|
| 171 |
+
# 'Unnecessary conv bias before batch/instance norm')
|
| 172 |
+
self.norm_name = "bn"
|
| 173 |
+
else:
|
| 174 |
+
self.norm_name = None # type: ignore
|
| 175 |
+
|
| 176 |
+
# build activation layer
|
| 177 |
+
if self.with_activation:
|
| 178 |
+
act_cfg_ = act_cfg.copy() # type: ignore
|
| 179 |
+
# nn.Tanh has no 'inplace' argument
|
| 180 |
+
if act_cfg_["type"] not in ["Tanh", "PReLU", "Sigmoid", "HSigmoid", "Swish", "GELU"]:
|
| 181 |
+
act_cfg_.setdefault("inplace", inplace)
|
| 182 |
+
self.activate = nn.ReLU() # build_activation_layer(act_cfg_)
|
| 183 |
+
|
| 184 |
+
# Use msra init by default
|
| 185 |
+
self.init_weights()
|
| 186 |
+
|
| 187 |
+
@property
|
| 188 |
+
def norm(self):
|
| 189 |
+
if self.norm_name:
|
| 190 |
+
return getattr(self, self.norm_name)
|
| 191 |
+
else:
|
| 192 |
+
return None
|
| 193 |
+
|
| 194 |
+
def init_weights(self):
|
| 195 |
+
# 1. It is mainly for customized conv layers with their own
|
| 196 |
+
# initialization manners by calling their own ``init_weights()``,
|
| 197 |
+
# and we do not want ConvModule to override the initialization.
|
| 198 |
+
# 2. For customized conv layers without their own initialization
|
| 199 |
+
# manners (that is, they don't have their own ``init_weights()``)
|
| 200 |
+
# and PyTorch's conv layers, they will be initialized by
|
| 201 |
+
# this method with default ``kaiming_init``.
|
| 202 |
+
# Note: For PyTorch's conv layers, they will be overwritten by our
|
| 203 |
+
# initialization implementation using default ``kaiming_init``.
|
| 204 |
+
if not hasattr(self.conv, "init_weights"):
|
| 205 |
+
if self.with_activation and self.act_cfg["type"] == "LeakyReLU":
|
| 206 |
+
nonlinearity = "leaky_relu"
|
| 207 |
+
a = self.act_cfg.get("negative_slope", 0.01)
|
| 208 |
+
else:
|
| 209 |
+
nonlinearity = "relu"
|
| 210 |
+
a = 0
|
| 211 |
+
kaiming_init(self.conv, a=a, nonlinearity=nonlinearity)
|
| 212 |
+
if self.with_norm:
|
| 213 |
+
constant_init(self.norm_name, 1, bias=0)
|
| 214 |
+
|
| 215 |
+
def forward(self, x: torch.Tensor, activate: bool = True, norm: bool = True, debug: bool = False) -> torch.Tensor:
|
| 216 |
+
for layer in self.order:
|
| 217 |
+
if debug:
|
| 218 |
+
breakpoint()
|
| 219 |
+
if layer == "conv":
|
| 220 |
+
if self.with_explicit_padding:
|
| 221 |
+
x = self.padding_layer(x)
|
| 222 |
+
x = self.conv(x)
|
| 223 |
+
elif layer == "norm" and norm and self.with_norm:
|
| 224 |
+
x = self.norm(x)
|
| 225 |
+
elif layer == "act" and activate and self.with_activation:
|
| 226 |
+
x = self.activate(x)
|
| 227 |
+
return x
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
class Interpolate(nn.Module):
|
| 231 |
+
def __init__(self, scale_factor, mode, align_corners=False):
|
| 232 |
+
super(Interpolate, self).__init__()
|
| 233 |
+
self.interp = nn.functional.interpolate
|
| 234 |
+
self.scale_factor = scale_factor
|
| 235 |
+
self.mode = mode
|
| 236 |
+
self.align_corners = align_corners
|
| 237 |
+
|
| 238 |
+
def forward(self, x):
|
| 239 |
+
x = self.interp(x, scale_factor=self.scale_factor, mode=self.mode, align_corners=self.align_corners)
|
| 240 |
+
return x
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
class UpConvHead(nn.Module):
|
| 244 |
+
"""
|
| 245 |
+
A 3 layer Convolutional head with intermediate upsampling
|
| 246 |
+
|
| 247 |
+
Args:
|
| 248 |
+
- features (int): number of input channels
|
| 249 |
+
- n_output_channels (int, default=256): number of output channels
|
| 250 |
+
- n_hidden_channels (int, default=32): number of channels in hidden layer
|
| 251 |
+
|
| 252 |
+
The operations are
|
| 253 |
+
[
|
| 254 |
+
Conv3x3(features, features // 2),
|
| 255 |
+
2x-Upsampling,
|
| 256 |
+
Conv3x3(features // 2, hidden_channels),
|
| 257 |
+
ReLU,
|
| 258 |
+
Conv1x1(hidden_channels, n_output_channels),
|
| 259 |
+
]
|
| 260 |
+
"""
|
| 261 |
+
|
| 262 |
+
def __init__(self, features, n_output_channels, n_hidden_channels=32):
|
| 263 |
+
super(UpConvHead, self).__init__()
|
| 264 |
+
self.n_output_channels = n_output_channels
|
| 265 |
+
self.head = nn.Sequential(
|
| 266 |
+
nn.Conv2d(features, features // 2, kernel_size=3, stride=1, padding=1),
|
| 267 |
+
Interpolate(scale_factor=2, mode="bilinear", align_corners=True),
|
| 268 |
+
nn.Conv2d(features // 2, n_hidden_channels, kernel_size=3, stride=1, padding=1),
|
| 269 |
+
nn.ReLU(),
|
| 270 |
+
nn.Conv2d(n_hidden_channels, n_output_channels, kernel_size=1, stride=1, padding=0),
|
| 271 |
+
)
|
| 272 |
+
|
| 273 |
+
def forward(self, x):
|
| 274 |
+
x = self.head(x)
|
| 275 |
+
return x
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
class ReassembleBlocks(nn.Module):
|
| 279 |
+
"""ViTPostProcessBlock, process cls_token in ViT backbone output and
|
| 280 |
+
rearrange the feature vector to feature map.
|
| 281 |
+
Args:
|
| 282 |
+
in_channels (List): ViT feature channels.
|
| 283 |
+
Default: [1024, 1024, 1024, 1024].
|
| 284 |
+
out_channels (List): output channels of each stage.
|
| 285 |
+
Default: [128, 256, 512, 1024].
|
| 286 |
+
readout_type (str): Type of readout operation. Default: 'ignore'.
|
| 287 |
+
init_cfg (dict, optional): Initialization config dict. Default: None.
|
| 288 |
+
"""
|
| 289 |
+
|
| 290 |
+
def __init__(
|
| 291 |
+
self,
|
| 292 |
+
in_channels=[1024, 1024, 1024, 1024],
|
| 293 |
+
out_channels=[128, 256, 512, 1024],
|
| 294 |
+
readout_type="project",
|
| 295 |
+
use_batchnorm=False,
|
| 296 |
+
):
|
| 297 |
+
super(ReassembleBlocks, self).__init__()
|
| 298 |
+
|
| 299 |
+
assert readout_type in ["ignore", "add", "project"]
|
| 300 |
+
self.readout_type = readout_type
|
| 301 |
+
|
| 302 |
+
self.projects = nn.ModuleList(
|
| 303 |
+
[
|
| 304 |
+
ConvModule(
|
| 305 |
+
in_channels=in_channels[channel_index],
|
| 306 |
+
out_channels=out_channel,
|
| 307 |
+
kernel_size=1,
|
| 308 |
+
act_cfg=None,
|
| 309 |
+
)
|
| 310 |
+
for channel_index, out_channel in enumerate(out_channels)
|
| 311 |
+
]
|
| 312 |
+
)
|
| 313 |
+
|
| 314 |
+
self.resize_layers = nn.ModuleList(
|
| 315 |
+
[
|
| 316 |
+
nn.ConvTranspose2d(
|
| 317 |
+
in_channels=out_channels[0], out_channels=out_channels[0], kernel_size=4, stride=4, padding=0
|
| 318 |
+
),
|
| 319 |
+
nn.ConvTranspose2d(
|
| 320 |
+
in_channels=out_channels[1], out_channels=out_channels[1], kernel_size=2, stride=2, padding=0
|
| 321 |
+
),
|
| 322 |
+
nn.Identity(),
|
| 323 |
+
nn.Conv2d(
|
| 324 |
+
in_channels=out_channels[3], out_channels=out_channels[3], kernel_size=3, stride=2, padding=1
|
| 325 |
+
),
|
| 326 |
+
]
|
| 327 |
+
)
|
| 328 |
+
if self.readout_type == "project":
|
| 329 |
+
self.readout_projects = nn.ModuleList()
|
| 330 |
+
for i in range(len(self.projects)):
|
| 331 |
+
self.readout_projects.append(nn.Sequential(nn.Linear(2 * in_channels[i], in_channels[i]), nn.GELU()))
|
| 332 |
+
|
| 333 |
+
self.batchnorm_layers = nn.ModuleList(
|
| 334 |
+
[nn.SyncBatchNorm(channel) if use_batchnorm else nn.Identity(channel) for channel in in_channels]
|
| 335 |
+
)
|
| 336 |
+
|
| 337 |
+
def forward(self, inputs):
|
| 338 |
+
assert isinstance(inputs, list)
|
| 339 |
+
out = []
|
| 340 |
+
for i, x in enumerate(inputs):
|
| 341 |
+
assert len(x) == 2
|
| 342 |
+
x, cls_token = x[0], x[1]
|
| 343 |
+
feature_shape = x.shape
|
| 344 |
+
if self.readout_type == "project":
|
| 345 |
+
x = x.flatten(2).permute((0, 2, 1))
|
| 346 |
+
readout = cls_token.unsqueeze(1).expand_as(x)
|
| 347 |
+
x = self.readout_projects[i](torch.cat((x, readout), -1))
|
| 348 |
+
x = x.permute(0, 2, 1).reshape(feature_shape)
|
| 349 |
+
elif self.readout_type == "add":
|
| 350 |
+
x = x.flatten(2) + cls_token.unsqueeze(-1)
|
| 351 |
+
x = x.reshape(feature_shape)
|
| 352 |
+
else:
|
| 353 |
+
pass
|
| 354 |
+
x = self.batchnorm_layers[i](x)
|
| 355 |
+
x = self.projects[i](x)
|
| 356 |
+
x = self.resize_layers[i](x)
|
| 357 |
+
out.append(x)
|
| 358 |
+
return out
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
class PreActResidualConvUnit(nn.Module):
|
| 362 |
+
"""ResidualConvUnit, pre-activate residual unit.
|
| 363 |
+
Args:
|
| 364 |
+
in_channels (int): number of channels in the input feature map.
|
| 365 |
+
act_cfg (dict): dictionary to construct and config activation layer.
|
| 366 |
+
norm_cfg (dict): dictionary to construct and config norm layer.
|
| 367 |
+
stride (int): stride of the first block. Default: 1
|
| 368 |
+
dilation (int): dilation rate for convs layers. Default: 1.
|
| 369 |
+
init_cfg (dict, optional): Initialization config dict. Default: None.
|
| 370 |
+
"""
|
| 371 |
+
|
| 372 |
+
def __init__(self, in_channels, act_cfg, norm_cfg, stride=1, dilation=1, init_cfg=None):
|
| 373 |
+
super(PreActResidualConvUnit, self).__init__() # init_cfg)
|
| 374 |
+
self.conv1 = ConvModule(
|
| 375 |
+
in_channels,
|
| 376 |
+
in_channels,
|
| 377 |
+
3,
|
| 378 |
+
stride=stride,
|
| 379 |
+
padding=dilation,
|
| 380 |
+
dilation=dilation,
|
| 381 |
+
norm_cfg=norm_cfg,
|
| 382 |
+
act_cfg=act_cfg,
|
| 383 |
+
bias=False,
|
| 384 |
+
order=("act", "conv", "norm"),
|
| 385 |
+
)
|
| 386 |
+
self.conv2 = ConvModule(
|
| 387 |
+
in_channels,
|
| 388 |
+
in_channels,
|
| 389 |
+
3,
|
| 390 |
+
padding=1,
|
| 391 |
+
norm_cfg=norm_cfg,
|
| 392 |
+
act_cfg=act_cfg,
|
| 393 |
+
bias=False,
|
| 394 |
+
order=("act", "conv", "norm"),
|
| 395 |
+
)
|
| 396 |
+
|
| 397 |
+
def forward(self, inputs):
|
| 398 |
+
inputs_ = inputs.clone()
|
| 399 |
+
x = self.conv1(inputs)
|
| 400 |
+
x = self.conv2(x)
|
| 401 |
+
return x + inputs_
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
class FeatureFusionBlock(nn.Module):
|
| 405 |
+
"""FeatureFusionBlock, merge feature map from different stages.
|
| 406 |
+
Args:
|
| 407 |
+
in_channels (int): Input channels.
|
| 408 |
+
act_cfg (dict): The activation config for ResidualConvUnit.
|
| 409 |
+
norm_cfg (dict): Config dict for normalization layer.
|
| 410 |
+
expand (bool): Whether expand the channels in post process block.
|
| 411 |
+
Default: False.
|
| 412 |
+
align_corners (bool): align_corner setting for bilinear upsample.
|
| 413 |
+
Default: True.
|
| 414 |
+
init_cfg (dict, optional): Initialization config dict. Default: None.
|
| 415 |
+
"""
|
| 416 |
+
|
| 417 |
+
def __init__(self, in_channels, act_cfg, norm_cfg, expand=False, align_corners=True, init_cfg=None):
|
| 418 |
+
super(FeatureFusionBlock, self).__init__() # init_cfg)
|
| 419 |
+
self.in_channels = in_channels
|
| 420 |
+
self.expand = expand
|
| 421 |
+
self.align_corners = align_corners
|
| 422 |
+
self.out_channels = in_channels
|
| 423 |
+
if self.expand:
|
| 424 |
+
self.out_channels = in_channels // 2
|
| 425 |
+
self.project = ConvModule(self.in_channels, self.out_channels, kernel_size=1, act_cfg=None, bias=True)
|
| 426 |
+
self.res_conv_unit1 = PreActResidualConvUnit(in_channels=self.in_channels, act_cfg=act_cfg, norm_cfg=norm_cfg)
|
| 427 |
+
self.res_conv_unit2 = PreActResidualConvUnit(in_channels=self.in_channels, act_cfg=act_cfg, norm_cfg=norm_cfg)
|
| 428 |
+
|
| 429 |
+
def forward(self, *inputs):
|
| 430 |
+
x = inputs[0]
|
| 431 |
+
|
| 432 |
+
if len(inputs) == 2:
|
| 433 |
+
if x.shape != inputs[1].shape:
|
| 434 |
+
res = torch.nn.functional.interpolate(
|
| 435 |
+
inputs[1],
|
| 436 |
+
size=(x.shape[2], x.shape[3]),
|
| 437 |
+
mode="bilinear",
|
| 438 |
+
align_corners=False,
|
| 439 |
+
)
|
| 440 |
+
else:
|
| 441 |
+
res = inputs[1]
|
| 442 |
+
x = x + self.res_conv_unit1(res)
|
| 443 |
+
x = self.res_conv_unit2(x) # ok
|
| 444 |
+
|
| 445 |
+
x = torch.nn.functional.interpolate(x, scale_factor=2, mode="bilinear", align_corners=self.align_corners)
|
| 446 |
+
# ok
|
| 447 |
+
|
| 448 |
+
x = self.project(x) # ok
|
| 449 |
+
return x
|
| 450 |
+
|
| 451 |
+
|
| 452 |
+
class DPTHead(nn.Module):
|
| 453 |
+
"""Vision Transformers for Dense Prediction.
|
| 454 |
+
This head is implemented of `DPT <https://arxiv.org/abs/2103.13413>`_.
|
| 455 |
+
Args:
|
| 456 |
+
in_channels (List): The input dimensions of the ViT backbone.
|
| 457 |
+
Default: [1024, 1024, 1024, 1024].
|
| 458 |
+
channels (int): Channels after modules, before the task-specific module
|
| 459 |
+
(`conv_depth`). Default: 256.
|
| 460 |
+
post_process_channels (List): Out channels of post process conv
|
| 461 |
+
layers. Default: [96, 192, 384, 768].
|
| 462 |
+
readout_type (str): Type of readout operation. Default: 'ignore'.
|
| 463 |
+
expand_channels (bool): Whether expand the channels in post process
|
| 464 |
+
block. Default: False.
|
| 465 |
+
"""
|
| 466 |
+
|
| 467 |
+
def __init__(
|
| 468 |
+
self,
|
| 469 |
+
in_channels=(1024, 1024, 1024, 1024),
|
| 470 |
+
channels=256,
|
| 471 |
+
post_process_channels=[128, 256, 512, 1024],
|
| 472 |
+
readout_type="project",
|
| 473 |
+
expand_channels=False,
|
| 474 |
+
n_output_channels=256,
|
| 475 |
+
use_batchnorm=False, # TODO
|
| 476 |
+
**kwargs,
|
| 477 |
+
):
|
| 478 |
+
super(DPTHead, self).__init__(**kwargs)
|
| 479 |
+
self.channels = channels
|
| 480 |
+
self.n_output_channels = n_output_channels
|
| 481 |
+
self.in_channels = in_channels
|
| 482 |
+
self.expand_channels = expand_channels
|
| 483 |
+
self.norm_cfg = None # TODO CHECK THIS
|
| 484 |
+
self.reassemble_blocks = ReassembleBlocks(
|
| 485 |
+
in_channels=in_channels,
|
| 486 |
+
out_channels=post_process_channels,
|
| 487 |
+
readout_type=readout_type,
|
| 488 |
+
use_batchnorm=use_batchnorm,
|
| 489 |
+
)
|
| 490 |
+
|
| 491 |
+
self.post_process_channels = [
|
| 492 |
+
channel * (2**i) if expand_channels else channel for i, channel in enumerate(post_process_channels)
|
| 493 |
+
]
|
| 494 |
+
self.convs = nn.ModuleList()
|
| 495 |
+
for channel in self.post_process_channels:
|
| 496 |
+
self.convs.append(ConvModule(channel, self.channels, kernel_size=3, padding=1, act_cfg=None, bias=False))
|
| 497 |
+
self.fusion_blocks = nn.ModuleList()
|
| 498 |
+
self.act_cfg = {"type": "ReLU"}
|
| 499 |
+
for _ in range(len(self.convs)):
|
| 500 |
+
self.fusion_blocks.append(FeatureFusionBlock(self.channels, self.act_cfg, self.norm_cfg))
|
| 501 |
+
self.fusion_blocks[0].res_conv_unit1 = None
|
| 502 |
+
self.project = ConvModule(self.channels, self.channels, kernel_size=3, padding=1, norm_cfg=self.norm_cfg)
|
| 503 |
+
self.num_fusion_blocks = len(self.fusion_blocks)
|
| 504 |
+
self.num_reassemble_blocks = len(self.reassemble_blocks.resize_layers)
|
| 505 |
+
self.num_post_process_channels = len(self.post_process_channels)
|
| 506 |
+
assert self.num_fusion_blocks == self.num_reassemble_blocks
|
| 507 |
+
assert self.num_reassemble_blocks == self.num_post_process_channels
|
| 508 |
+
self.conv_depth = UpConvHead(self.channels, self.n_output_channels)
|
| 509 |
+
|
| 510 |
+
def forward_features(self, inputs):
|
| 511 |
+
assert (
|
| 512 |
+
len(inputs) == self.num_reassemble_blocks
|
| 513 |
+
), f"Expected {self.num_reassemble_blocks} inputs, got {len(inputs)}."
|
| 514 |
+
x = [inp for inp in inputs]
|
| 515 |
+
|
| 516 |
+
x = self.reassemble_blocks(x)
|
| 517 |
+
x = [self.convs[i](feature) for i, feature in enumerate(x)]
|
| 518 |
+
out = self.fusion_blocks[0](x[-1])
|
| 519 |
+
|
| 520 |
+
for i in range(1, len(self.fusion_blocks)):
|
| 521 |
+
out = self.fusion_blocks[i](out, x[-(i + 1)])
|
| 522 |
+
|
| 523 |
+
out = self.project(out)
|
| 524 |
+
return out
|
| 525 |
+
|
| 526 |
+
def forward(self, inputs):
|
| 527 |
+
out = self.forward_features(inputs)
|
| 528 |
+
return self.conv_depth(out)
|
| 529 |
+
|
| 530 |
+
def predict(self, inputs, rescale_to=(512, 512)):
|
| 531 |
+
out = self.forward_features(inputs)
|
| 532 |
+
return self.conv_depth.predict(out, rescale_to=rescale_to)
|
dinov3/eval/dense/depth/models/embed.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import itertools
|
| 7 |
+
import math
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class CenterPadding(torch.nn.Module):
|
| 13 |
+
def __init__(self, multiple: int):
|
| 14 |
+
super().__init__()
|
| 15 |
+
self.multiple = multiple
|
| 16 |
+
|
| 17 |
+
def _get_pad(self, size):
|
| 18 |
+
new_size = math.ceil(size / self.multiple) * self.multiple
|
| 19 |
+
pad_size = new_size - size
|
| 20 |
+
pad_size_left = pad_size // 2
|
| 21 |
+
pad_size_right = pad_size - pad_size_left
|
| 22 |
+
return pad_size_left, pad_size_right
|
| 23 |
+
|
| 24 |
+
# @torch.inference_mode()
|
| 25 |
+
def forward(self, x):
|
| 26 |
+
# expected shapes are ... x H x W, usually B x C x H x W
|
| 27 |
+
pads = list(itertools.chain.from_iterable(self._get_pad(m) for m in x.shape[:-3:-1]))
|
| 28 |
+
output = torch.nn.functional.pad(x, pads)
|
| 29 |
+
return output
|
| 30 |
+
|
| 31 |
+
def __extra_repr__(self) -> str:
|
| 32 |
+
return f"multiple={self.multiple}"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class StretchToMultiple(torch.nn.Module):
|
| 36 |
+
def __init__(self, multiple: int):
|
| 37 |
+
super().__init__()
|
| 38 |
+
self.multiple = multiple
|
| 39 |
+
|
| 40 |
+
def forward(self, x):
|
| 41 |
+
# expected shapes are ... x H x W, usually B x C x H x W
|
| 42 |
+
*shape, C, H, W = x.shape
|
| 43 |
+
new_H = math.ceil(H / self.multiple) * self.multiple
|
| 44 |
+
new_W = math.ceil(W / self.multiple) * self.multiple
|
| 45 |
+
if new_H != H or new_W != W:
|
| 46 |
+
x = x.reshape(-1, C, H, W)
|
| 47 |
+
x = torch.nn.functional.interpolate(x, size=(new_H, new_W), mode="bilinear")
|
| 48 |
+
x = x.reshape(*shape, C, new_H, new_W)
|
| 49 |
+
return x
|
| 50 |
+
|
| 51 |
+
def __extra_repr__(self) -> str:
|
| 52 |
+
return f"multiple={self.multiple}"
|
dinov3/eval/dense/depth/models/encoder.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import logging
|
| 7 |
+
from enum import Enum
|
| 8 |
+
from typing import Sequence, Union
|
| 9 |
+
|
| 10 |
+
from dinov3.eval.dense.depth.models.embed import CenterPadding, StretchToMultiple
|
| 11 |
+
from torch import Tensor, nn
|
| 12 |
+
|
| 13 |
+
logger = logging.getLogger("dinov3")
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class BackboneLayersSet(Enum):
|
| 17 |
+
# Set of intermediate layers to take from the backbone
|
| 18 |
+
LAST = "LAST" # extracting only the last layer
|
| 19 |
+
FOUR_LAST = "FOUR_LAST" # extracting the last 4 layers
|
| 20 |
+
FOUR_EVEN_INTERVALS = "FOUR_EVEN_INTERVALS" # extracting outputs every 1/4 of the total number of blocks
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _get_backbone_out_indices(
|
| 24 |
+
model: nn.Module,
|
| 25 |
+
backbone_out_layers: Union[list[int], BackboneLayersSet] = BackboneLayersSet.FOUR_EVEN_INTERVALS,
|
| 26 |
+
):
|
| 27 |
+
"""
|
| 28 |
+
Get indices for output layers of the ViT backbone. For now there are 3 options available:
|
| 29 |
+
BackboneLayersSet.LAST : only extract the last layer, used in segmentation tasks with a bn head.
|
| 30 |
+
BackboneLayersSet.FOUR_LAST : extract the last 4 layers, used in segmentation (multiscale setting)
|
| 31 |
+
BackboneLayersSet.FOUR_EVEN_INTERVALS : extract outputs every 1/4 of the total number of blocks
|
| 32 |
+
Reference outputs in 'FOUR_EVEN_INTERVALS' mode :
|
| 33 |
+
ViT/S (12 blocks): [2, 5, 8, 11]
|
| 34 |
+
ViT/B (12 blocks): [2, 5, 8, 11]
|
| 35 |
+
ViT/L (24 blocks): [5, 11, 17, 23] (correct), [4, 11, 17, 23] (incorrect)
|
| 36 |
+
ViT/g (40 blocks): [9, 19, 29, 39]
|
| 37 |
+
"""
|
| 38 |
+
n_blocks = getattr(model, "n_blocks", 1)
|
| 39 |
+
if isinstance(backbone_out_layers, list):
|
| 40 |
+
out_indices = backbone_out_layers
|
| 41 |
+
|
| 42 |
+
if backbone_out_layers == BackboneLayersSet.LAST:
|
| 43 |
+
out_indices = [n_blocks - 1]
|
| 44 |
+
elif backbone_out_layers == BackboneLayersSet.FOUR_LAST:
|
| 45 |
+
out_indices = [i for i in range(n_blocks - 4, n_blocks)]
|
| 46 |
+
elif backbone_out_layers == BackboneLayersSet.FOUR_EVEN_INTERVALS:
|
| 47 |
+
# XXX: Force (incorrect) out indices for backward-compatibility (ViT/L only)
|
| 48 |
+
if n_blocks == 24:
|
| 49 |
+
out_indices = [4, 11, 17, 23]
|
| 50 |
+
else:
|
| 51 |
+
out_indices = [i * (n_blocks // 4) - 1 for i in range(1, 5)]
|
| 52 |
+
assert all([out_index < n_blocks for out_index in out_indices])
|
| 53 |
+
return out_indices
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class PatchSizeAdaptationStrategy(Enum):
|
| 57 |
+
CENTER_PADDING = "center_padding"
|
| 58 |
+
STRETCH = "stretch"
|
| 59 |
+
NO_ADAPTATION = "never"
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
class DinoVisionTransformerWrapper(nn.Module):
|
| 63 |
+
"""Vision Transformer."""
|
| 64 |
+
|
| 65 |
+
def __init__(
|
| 66 |
+
self,
|
| 67 |
+
backbone_model: nn.Module,
|
| 68 |
+
backbone_out_layers: Union[str, list[int]],
|
| 69 |
+
use_backbone_norm: bool = False,
|
| 70 |
+
adapt_to_patch_size: PatchSizeAdaptationStrategy = PatchSizeAdaptationStrategy.CENTER_PADDING,
|
| 71 |
+
):
|
| 72 |
+
super().__init__()
|
| 73 |
+
|
| 74 |
+
self.final_norm = use_backbone_norm
|
| 75 |
+
self.backbone = backbone_model
|
| 76 |
+
self.backbone_out_indices = _get_backbone_out_indices(
|
| 77 |
+
self.backbone,
|
| 78 |
+
backbone_out_layers=(
|
| 79 |
+
backbone_out_layers if isinstance(backbone_out_layers, list) else BackboneLayersSet(backbone_out_layers)
|
| 80 |
+
),
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
# If the backbone does not define embed_dims, use [embed_dim] * n_blocks
|
| 84 |
+
try:
|
| 85 |
+
embed_dims = self.backbone.embed_dims
|
| 86 |
+
except AttributeError:
|
| 87 |
+
embed_dim = self.backbone.embed_dim
|
| 88 |
+
n_blocks = self.backbone.n_blocks
|
| 89 |
+
logger.warning(f"Backbone does not define embed_dims, using {[embed_dim] * n_blocks=} instead")
|
| 90 |
+
embed_dims = [embed_dim] * n_blocks
|
| 91 |
+
self.embed_dims: Sequence[int] = [embed_dims[idx] for idx in self.backbone_out_indices]
|
| 92 |
+
|
| 93 |
+
# How to adapt input images to the patch size of the model?
|
| 94 |
+
try:
|
| 95 |
+
input_pad_size = self.backbone.input_pad_size
|
| 96 |
+
except AttributeError:
|
| 97 |
+
patch_size = self.backbone.patch_size
|
| 98 |
+
logger.warning(f"Backbone does not define input_pad_size, using {patch_size=} instead")
|
| 99 |
+
input_pad_size = patch_size
|
| 100 |
+
if adapt_to_patch_size is PatchSizeAdaptationStrategy.CENTER_PADDING:
|
| 101 |
+
self.patch_size_adapter = CenterPadding(input_pad_size)
|
| 102 |
+
elif adapt_to_patch_size is PatchSizeAdaptationStrategy.STRETCH:
|
| 103 |
+
self.patch_size_adapter = StretchToMultiple(input_pad_size)
|
| 104 |
+
elif adapt_to_patch_size is PatchSizeAdaptationStrategy.NO_ADAPTATION:
|
| 105 |
+
self.patch_size_adapter = nn.Identity()
|
| 106 |
+
else:
|
| 107 |
+
raise ValueError(f"Unknown value {adapt_to_patch_size=}")
|
| 108 |
+
|
| 109 |
+
# Freeze backbone
|
| 110 |
+
self.backbone.requires_grad_(False)
|
| 111 |
+
|
| 112 |
+
def forward(
|
| 113 |
+
self,
|
| 114 |
+
x: Tensor, # [B, rgb, H, W]
|
| 115 |
+
) -> list[tuple[Tensor, Tensor]]:
|
| 116 |
+
x = self.patch_size_adapter(x)
|
| 117 |
+
outputs = self.backbone.get_intermediate_layers(
|
| 118 |
+
x,
|
| 119 |
+
n=self.backbone_out_indices,
|
| 120 |
+
reshape=True,
|
| 121 |
+
return_class_token=True,
|
| 122 |
+
norm=self.final_norm,
|
| 123 |
+
) # List of (patch feats [B, C, h, w], class token [B, C])
|
| 124 |
+
return outputs
|
dinov3/eval/dense/depth/utils.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
def cast_to(o, dtype: torch.dtype):
|
| 9 |
+
if isinstance(o, torch.Tensor):
|
| 10 |
+
return o.to(dtype)
|
| 11 |
+
if isinstance(o, tuple):
|
| 12 |
+
return tuple(cast_to(u, dtype) for u in o)
|
| 13 |
+
if isinstance(o, list):
|
| 14 |
+
return list(cast_to(u, dtype) for u in o)
|
| 15 |
+
if isinstance(o, dict):
|
| 16 |
+
return {k: cast_to(u, dtype) for k, u in o.items()}
|
| 17 |
+
if isinstance(o, (bool, int, float, str, type(None))):
|
| 18 |
+
return o
|
| 19 |
+
raise NotImplementedError(f"Unsupported type: {type(o)}")
|
dinov3/eval/detection/config.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
|
| 8 |
+
from .models.position_encoding import PositionEncoding
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@dataclass(kw_only=True)
|
| 12 |
+
class DetectionHeadConfig:
|
| 13 |
+
num_classes: int = 91 # 91 classes in COCO
|
| 14 |
+
# Deformable DETR tricks
|
| 15 |
+
with_box_refine: bool = True
|
| 16 |
+
two_stage: bool = True
|
| 17 |
+
# DINO DETR tricks
|
| 18 |
+
mixed_selection: bool = True
|
| 19 |
+
look_forward_twice: bool = True # was default False
|
| 20 |
+
# Hybrid Matching tricks
|
| 21 |
+
k_one2many: int = 6 # was 5
|
| 22 |
+
lambda_one2many: float = 1.0
|
| 23 |
+
num_queries_one2one: int = 300 # number of query slots for one_to_one matching
|
| 24 |
+
num_queries_one2many: int = 1500 # was 0, number of query slots for one_to_many matching
|
| 25 |
+
"""
|
| 26 |
+
Absolute coordinates & box regression reparameterization.
|
| 27 |
+
If true, we use absolute coordindates & reparameterization for bounding boxes.
|
| 28 |
+
"""
|
| 29 |
+
reparam: bool = True
|
| 30 |
+
topk: int = 100
|
| 31 |
+
|
| 32 |
+
# * Backbone
|
| 33 |
+
# type of positional embedding to use on top of the image features
|
| 34 |
+
position_embedding: PositionEncoding = PositionEncoding.SINE
|
| 35 |
+
num_feature_levels: int = 1 # number of feature levels
|
| 36 |
+
|
| 37 |
+
# * Transformer
|
| 38 |
+
dec_layers: int = 6 # number of decoding layers in the transformer
|
| 39 |
+
dim_feedforward: int = 2048 # intermediate size of the feedforward layers in the transformer blocks
|
| 40 |
+
hidden_dim: int = 256 # size of the embeddings (dimension of the transformer)
|
| 41 |
+
dropout: float = 0.0 # dropout applied in the transformer, was 0.1
|
| 42 |
+
nheads: int = 8 # number of attention heads inside the transformer's attentions
|
| 43 |
+
norm_type: str = "pre_norm"
|
| 44 |
+
|
| 45 |
+
# Loss
|
| 46 |
+
aux_loss: bool = True # auxiliary decoding losses (loss at each layer)
|
| 47 |
+
|
| 48 |
+
# * dev: proposals
|
| 49 |
+
proposal_feature_levels: int = 4 # was 1
|
| 50 |
+
proposal_min_size: int = 50
|
| 51 |
+
# * dev decoder: global decoder
|
| 52 |
+
decoder_type: str = "global_rpe_decomp" # was deform
|
| 53 |
+
decoder_use_checkpoint: bool = False
|
| 54 |
+
decoder_rpe_hidden_dim: int = 512
|
| 55 |
+
decoder_rpe_type: str = "linear"
|
| 56 |
+
|
| 57 |
+
# Custom
|
| 58 |
+
add_transformer_encoder: bool = True
|
| 59 |
+
num_encoder_layers: int = 6
|
| 60 |
+
layers_to_use: list[int] | None = None
|
| 61 |
+
blocks_to_train: list[int] | None = None
|
| 62 |
+
n_windows_sqrt: int = 0
|
| 63 |
+
proposal_in_stride: int | None = None
|
| 64 |
+
proposal_tgt_strides: list[int] | None = None
|
| 65 |
+
backbone_use_layernorm: bool = False # whether to use layernorm on each layer of the backbone's features
|
dinov3/eval/detection/models/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
dinov3/eval/detection/models/backbone.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
# ------------------------------------------------------------------------
|
| 7 |
+
# Plain-DETR
|
| 8 |
+
# Copyright (c) 2023 Xi'an Jiaotong University & Microsoft Research Asia.
|
| 9 |
+
# Licensed under The MIT License [see LICENSE for details]
|
| 10 |
+
# ------------------------------------------------------------------------
|
| 11 |
+
# Deformable DETR
|
| 12 |
+
# Copyright (c) 2020 SenseTime. All Rights Reserved.
|
| 13 |
+
# Licensed under the Apache License, Version 2.0 [see LICENSE for details]
|
| 14 |
+
# ------------------------------------------------------------------------
|
| 15 |
+
# Modified from DETR (https://github.com/facebookresearch/detr)
|
| 16 |
+
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
|
| 17 |
+
# ------------------------------------------------------------------------
|
| 18 |
+
|
| 19 |
+
"""
|
| 20 |
+
Backbone modules.
|
| 21 |
+
"""
|
| 22 |
+
import logging
|
| 23 |
+
from typing import List, Optional, Union
|
| 24 |
+
|
| 25 |
+
import torch
|
| 26 |
+
import torch.nn.functional as F
|
| 27 |
+
from torch import nn
|
| 28 |
+
|
| 29 |
+
from ..util.misc import NestedTensor
|
| 30 |
+
from .position_encoding import build_position_encoding
|
| 31 |
+
from .utils import LayerNorm2D
|
| 32 |
+
from .windows import WindowsWrapper
|
| 33 |
+
|
| 34 |
+
logger = logging.getLogger("dinov3")
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class DINOBackbone(nn.Module):
|
| 38 |
+
def __init__(
|
| 39 |
+
self,
|
| 40 |
+
backbone_model: nn.Module,
|
| 41 |
+
train_backbone: bool,
|
| 42 |
+
blocks_to_train: Optional[List[str]] = None,
|
| 43 |
+
layers_to_use: Union[int, List] = 1,
|
| 44 |
+
use_layernorm: bool = True,
|
| 45 |
+
):
|
| 46 |
+
super().__init__()
|
| 47 |
+
self.backbone = backbone_model
|
| 48 |
+
self.blocks_to_train = blocks_to_train
|
| 49 |
+
self.patch_size = self.backbone.patch_size
|
| 50 |
+
self.use_layernorm = use_layernorm
|
| 51 |
+
|
| 52 |
+
for _, (name, parameter) in enumerate(self.backbone.named_parameters()):
|
| 53 |
+
train_condition = any(f".{b}." in name for b in self.blocks_to_train) if self.blocks_to_train else True
|
| 54 |
+
if (not train_backbone) or "mask_token" in name or (not train_condition):
|
| 55 |
+
parameter.requires_grad_(False)
|
| 56 |
+
|
| 57 |
+
self.strides = [self.backbone.patch_size]
|
| 58 |
+
|
| 59 |
+
# get embed_dim for each intermediate output
|
| 60 |
+
n_all_layers = self.backbone.n_blocks
|
| 61 |
+
blocks_to_take = (
|
| 62 |
+
range(n_all_layers - layers_to_use, n_all_layers) if isinstance(layers_to_use, int) else layers_to_use
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
# if models do not define embed_dims, repeat embed_dim n_blocks times
|
| 66 |
+
embed_dims = getattr(self.backbone, "embed_dims", [self.backbone.embed_dim] * self.backbone.n_blocks)
|
| 67 |
+
embed_dims = [embed_dims[i] for i in range(n_all_layers) if i in blocks_to_take]
|
| 68 |
+
|
| 69 |
+
if self.use_layernorm:
|
| 70 |
+
self.layer_norms = nn.ModuleList([LayerNorm2D(embed_dim) for embed_dim in embed_dims])
|
| 71 |
+
|
| 72 |
+
self.num_channels = [sum(embed_dims)]
|
| 73 |
+
self.layers_to_use = layers_to_use
|
| 74 |
+
|
| 75 |
+
def forward(self, tensor_list: NestedTensor):
|
| 76 |
+
xs = self.backbone.get_intermediate_layers(tensor_list.tensors, n=self.layers_to_use, reshape=True)
|
| 77 |
+
if self.use_layernorm:
|
| 78 |
+
xs = [ln(x).contiguous() for ln, x in zip(self.layer_norms, xs)]
|
| 79 |
+
|
| 80 |
+
xs = [torch.cat(xs, axis=1)]
|
| 81 |
+
|
| 82 |
+
out: list[NestedTensor] = []
|
| 83 |
+
for x in xs:
|
| 84 |
+
m = tensor_list.mask
|
| 85 |
+
assert m is not None
|
| 86 |
+
mask = F.interpolate(m[None].float(), size=x.shape[-2:]).to(torch.bool)[0]
|
| 87 |
+
out.append(NestedTensor(x, mask))
|
| 88 |
+
return out
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class BackboneWithPositionEncoding(nn.Sequential):
|
| 92 |
+
def __init__(self, backbone, position_embedding):
|
| 93 |
+
super().__init__(backbone, position_embedding)
|
| 94 |
+
self.strides = backbone.strides
|
| 95 |
+
self.num_channels = backbone.num_channels
|
| 96 |
+
|
| 97 |
+
def forward(self, tensor_list: NestedTensor):
|
| 98 |
+
out: List[NestedTensor] = list(self[0](tensor_list))
|
| 99 |
+
pos = [self[1][idx](x).to(x.tensors.dtype) for idx, x in enumerate(out)]
|
| 100 |
+
return out, pos
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def build_backbone(backbone_model, args):
|
| 104 |
+
position_embedding = build_position_encoding(args)
|
| 105 |
+
train_backbone = False
|
| 106 |
+
backbone = DINOBackbone(
|
| 107 |
+
backbone_model, train_backbone, args.blocks_to_train, args.layers_to_use, args.backbone_use_layernorm
|
| 108 |
+
)
|
| 109 |
+
if args.n_windows_sqrt > 0:
|
| 110 |
+
logger.info(f"Wrapping with {args.n_windows_sqrt} x {args.n_windows_sqrt} windows")
|
| 111 |
+
backbone = WindowsWrapper(
|
| 112 |
+
backbone, n_windows_w=args.n_windows_sqrt, n_windows_h=args.n_windows_sqrt, patch_size=backbone.patch_size
|
| 113 |
+
)
|
| 114 |
+
else:
|
| 115 |
+
logger.info("Not wrapping with windows")
|
| 116 |
+
|
| 117 |
+
return BackboneWithPositionEncoding(backbone, position_embedding)
|
dinov3/eval/detection/models/detr.py
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
# ------------------------------------------------------------------------
|
| 7 |
+
# Plain-DETR
|
| 8 |
+
# Copyright (c) 2023 Xi'an Jiaotong University & Microsoft Research Asia.
|
| 9 |
+
# Licensed under The MIT License [see LICENSE for details]
|
| 10 |
+
# ------------------------------------------------------------------------
|
| 11 |
+
# Deformable DETR
|
| 12 |
+
# Copyright (c) 2020 SenseTime. All Rights Reserved.
|
| 13 |
+
# Licensed under the Apache License, Version 2.0 [see LICENSE for details]
|
| 14 |
+
# ------------------------------------------------------------------------
|
| 15 |
+
# Modified from DETR (https://github.com/facebookresearch/detr)
|
| 16 |
+
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
|
| 17 |
+
# ------------------------------------------------------------------------
|
| 18 |
+
|
| 19 |
+
"""
|
| 20 |
+
Deformable DETR model and criterion classes.
|
| 21 |
+
"""
|
| 22 |
+
import math
|
| 23 |
+
|
| 24 |
+
import torch
|
| 25 |
+
import torch.nn.functional as F
|
| 26 |
+
from torch import nn
|
| 27 |
+
|
| 28 |
+
from ..util import box_ops
|
| 29 |
+
from ..util.misc import NestedTensor, _get_clones, inverse_sigmoid, nested_tensor_from_tensor_list
|
| 30 |
+
from .backbone import build_backbone
|
| 31 |
+
from .transformer import build_transformer
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class PlainDETR(nn.Module):
|
| 35 |
+
"""This is the Deformable DETR module that performs object detection"""
|
| 36 |
+
|
| 37 |
+
def __init__(
|
| 38 |
+
self,
|
| 39 |
+
backbone,
|
| 40 |
+
transformer,
|
| 41 |
+
num_classes,
|
| 42 |
+
num_feature_levels,
|
| 43 |
+
aux_loss=True,
|
| 44 |
+
with_box_refine=False,
|
| 45 |
+
two_stage=False,
|
| 46 |
+
num_queries_one2one=300,
|
| 47 |
+
num_queries_one2many=0,
|
| 48 |
+
mixed_selection=False,
|
| 49 |
+
):
|
| 50 |
+
"""Initializes the model.
|
| 51 |
+
Parameters:
|
| 52 |
+
backbone: torch module of the backbone to be used. See backbone.py
|
| 53 |
+
transformer: torch module of the transformer architecture. See transformer.py
|
| 54 |
+
num_classes: number of object classes
|
| 55 |
+
aux_loss: True if auxiliary decoding losses (loss at each decoder layer) are to be used.
|
| 56 |
+
with_box_refine: iterative bounding box refinement
|
| 57 |
+
two_stage: two-stage Deformable DETR
|
| 58 |
+
num_queries_one2one: number of object queries for one-to-one matching part
|
| 59 |
+
num_queries_one2many: number of object queries for one-to-many matching part
|
| 60 |
+
mixed_selection: a trick for Deformable DETR two stage
|
| 61 |
+
|
| 62 |
+
"""
|
| 63 |
+
super().__init__()
|
| 64 |
+
num_queries = num_queries_one2one + num_queries_one2many
|
| 65 |
+
self.num_queries = num_queries
|
| 66 |
+
self.transformer = transformer
|
| 67 |
+
hidden_dim = transformer.d_model
|
| 68 |
+
self.class_embed = nn.Linear(hidden_dim, num_classes)
|
| 69 |
+
self.bbox_embed = MLP(hidden_dim, hidden_dim, 4, 3)
|
| 70 |
+
self.num_feature_levels = num_feature_levels
|
| 71 |
+
if not two_stage:
|
| 72 |
+
self.query_embed = nn.Embedding(num_queries, hidden_dim * 2)
|
| 73 |
+
elif mixed_selection:
|
| 74 |
+
self.query_embed = nn.Embedding(num_queries, hidden_dim)
|
| 75 |
+
self.input_proj = nn.ModuleList(
|
| 76 |
+
[
|
| 77 |
+
nn.Sequential(
|
| 78 |
+
nn.Conv2d(backbone.num_channels[0], hidden_dim, kernel_size=1),
|
| 79 |
+
nn.GroupNorm(32, hidden_dim),
|
| 80 |
+
)
|
| 81 |
+
]
|
| 82 |
+
)
|
| 83 |
+
self.backbone = backbone
|
| 84 |
+
self.aux_loss = aux_loss
|
| 85 |
+
self.with_box_refine = with_box_refine
|
| 86 |
+
self.two_stage = two_stage
|
| 87 |
+
|
| 88 |
+
prior_prob = 0.01
|
| 89 |
+
bias_value = -math.log((1 - prior_prob) / prior_prob)
|
| 90 |
+
self.class_embed.bias.data = torch.ones(num_classes) * bias_value
|
| 91 |
+
nn.init.constant_(self.bbox_embed.layers[-1].weight.data, 0)
|
| 92 |
+
nn.init.constant_(self.bbox_embed.layers[-1].bias.data, 0)
|
| 93 |
+
for proj in self.input_proj:
|
| 94 |
+
nn.init.xavier_uniform_(proj[0].weight, gain=1)
|
| 95 |
+
nn.init.constant_(proj[0].bias, 0)
|
| 96 |
+
|
| 97 |
+
# if two-stage, the last class_embed and bbox_embed is for region proposal generation
|
| 98 |
+
num_pred = (transformer.decoder.num_layers + 1) if two_stage else transformer.decoder.num_layers
|
| 99 |
+
if with_box_refine:
|
| 100 |
+
self.class_embed = _get_clones(self.class_embed, num_pred)
|
| 101 |
+
self.bbox_embed = _get_clones(self.bbox_embed, num_pred)
|
| 102 |
+
nn.init.constant_(self.bbox_embed[0].layers[-1].bias.data[2:], -2.0)
|
| 103 |
+
# hack implementation for iterative bounding box refinement
|
| 104 |
+
self.transformer.decoder.bbox_embed = self.bbox_embed
|
| 105 |
+
else:
|
| 106 |
+
nn.init.constant_(self.bbox_embed.layers[-1].bias.data[2:], -2.0)
|
| 107 |
+
self.class_embed = nn.ModuleList([self.class_embed for _ in range(num_pred)])
|
| 108 |
+
self.bbox_embed = nn.ModuleList([self.bbox_embed for _ in range(num_pred)])
|
| 109 |
+
self.transformer.decoder.bbox_embed = None
|
| 110 |
+
if two_stage:
|
| 111 |
+
# hack implementation for two-stage
|
| 112 |
+
self.transformer.decoder.class_embed = self.class_embed
|
| 113 |
+
for box_embed in self.bbox_embed:
|
| 114 |
+
nn.init.constant_(box_embed.layers[-1].bias.data[2:], 0.0)
|
| 115 |
+
|
| 116 |
+
self.num_queries_one2one = num_queries_one2one
|
| 117 |
+
self.mixed_selection = mixed_selection
|
| 118 |
+
|
| 119 |
+
def forward(self, samples: NestedTensor):
|
| 120 |
+
"""The forward expects a NestedTensor, which consists of:
|
| 121 |
+
- samples.tensor: batched images, of shape [batch_size x 3 x H x W]
|
| 122 |
+
- samples.mask: a binary mask of shape [batch_size x H x W], containing 1 on padded pixels
|
| 123 |
+
|
| 124 |
+
It returns a dict with the following elements:
|
| 125 |
+
- "pred_logits": the classification logits (including no-object) for all queries.
|
| 126 |
+
Shape= [batch_size x num_queries x (num_classes + 1)]
|
| 127 |
+
- "pred_boxes": The normalized boxes coordinates for all queries, represented as
|
| 128 |
+
(center_x, center_y, height, width). These values are normalized in [0, 1],
|
| 129 |
+
relative to the size of each individual image (disregarding possible padding).
|
| 130 |
+
See PostProcess for information on how to retrieve the unnormalized bounding box.
|
| 131 |
+
- "aux_outputs": Optional, only returned when auxilary losses are activated. It is a list of
|
| 132 |
+
dictionnaries containing the two above keys for each decoder layer.
|
| 133 |
+
"""
|
| 134 |
+
if not isinstance(samples, NestedTensor):
|
| 135 |
+
samples = nested_tensor_from_tensor_list(samples)
|
| 136 |
+
features, pos = self.backbone(samples)
|
| 137 |
+
|
| 138 |
+
srcs = []
|
| 139 |
+
masks = []
|
| 140 |
+
for layer, feat in enumerate(features):
|
| 141 |
+
src, mask = feat.decompose()
|
| 142 |
+
srcs.append(self.input_proj[layer](src))
|
| 143 |
+
masks.append(mask)
|
| 144 |
+
assert mask is not None
|
| 145 |
+
|
| 146 |
+
query_embeds = None
|
| 147 |
+
if not self.two_stage or self.mixed_selection:
|
| 148 |
+
query_embeds = self.query_embed.weight[0 : self.num_queries, :]
|
| 149 |
+
|
| 150 |
+
# make attn mask
|
| 151 |
+
""" attention mask to prevent information leakage
|
| 152 |
+
"""
|
| 153 |
+
self_attn_mask = torch.zeros(
|
| 154 |
+
[
|
| 155 |
+
self.num_queries,
|
| 156 |
+
self.num_queries,
|
| 157 |
+
],
|
| 158 |
+
dtype=bool,
|
| 159 |
+
device=src.device,
|
| 160 |
+
)
|
| 161 |
+
self_attn_mask[
|
| 162 |
+
self.num_queries_one2one :,
|
| 163 |
+
0 : self.num_queries_one2one,
|
| 164 |
+
] = True
|
| 165 |
+
self_attn_mask[
|
| 166 |
+
0 : self.num_queries_one2one,
|
| 167 |
+
self.num_queries_one2one :,
|
| 168 |
+
] = True
|
| 169 |
+
|
| 170 |
+
(
|
| 171 |
+
hs,
|
| 172 |
+
init_reference,
|
| 173 |
+
inter_references,
|
| 174 |
+
enc_outputs_class,
|
| 175 |
+
enc_outputs_coord_unact,
|
| 176 |
+
enc_outputs_delta,
|
| 177 |
+
output_proposals,
|
| 178 |
+
max_shape,
|
| 179 |
+
) = self.transformer(srcs, masks, pos, query_embeds, self_attn_mask)
|
| 180 |
+
|
| 181 |
+
outputs_classes_one2one = []
|
| 182 |
+
outputs_coords_one2one = []
|
| 183 |
+
outputs_classes_one2many = []
|
| 184 |
+
outputs_coords_one2many = []
|
| 185 |
+
for lvl in range(hs.shape[0]):
|
| 186 |
+
if lvl == 0:
|
| 187 |
+
reference = init_reference
|
| 188 |
+
else:
|
| 189 |
+
reference = inter_references[lvl - 1]
|
| 190 |
+
reference = inverse_sigmoid(reference)
|
| 191 |
+
outputs_class = self.class_embed[lvl](hs[lvl])
|
| 192 |
+
tmp = self.bbox_embed[lvl](hs[lvl])
|
| 193 |
+
if reference.shape[-1] == 4:
|
| 194 |
+
tmp += reference
|
| 195 |
+
else:
|
| 196 |
+
assert reference.shape[-1] == 2
|
| 197 |
+
tmp[..., :2] += reference
|
| 198 |
+
outputs_coord = tmp.sigmoid()
|
| 199 |
+
|
| 200 |
+
outputs_classes_one2one.append(outputs_class[:, 0 : self.num_queries_one2one])
|
| 201 |
+
outputs_classes_one2many.append(outputs_class[:, self.num_queries_one2one :])
|
| 202 |
+
|
| 203 |
+
outputs_coords_one2one.append(outputs_coord[:, 0 : self.num_queries_one2one])
|
| 204 |
+
outputs_coords_one2many.append(outputs_coord[:, self.num_queries_one2one :])
|
| 205 |
+
|
| 206 |
+
outputs_classes_one2one = torch.stack(outputs_classes_one2one)
|
| 207 |
+
outputs_coords_one2one = torch.stack(outputs_coords_one2one)
|
| 208 |
+
|
| 209 |
+
outputs_classes_one2many = torch.stack(outputs_classes_one2many)
|
| 210 |
+
outputs_coords_one2many = torch.stack(outputs_coords_one2many)
|
| 211 |
+
|
| 212 |
+
out = {
|
| 213 |
+
"pred_logits": outputs_classes_one2one[-1],
|
| 214 |
+
"pred_boxes": outputs_coords_one2one[-1],
|
| 215 |
+
"pred_logits_one2many": outputs_classes_one2many[-1],
|
| 216 |
+
"pred_boxes_one2many": outputs_coords_one2many[-1],
|
| 217 |
+
}
|
| 218 |
+
if self.aux_loss:
|
| 219 |
+
out["aux_outputs"] = self._set_aux_loss(outputs_classes_one2one, outputs_coords_one2one)
|
| 220 |
+
out["aux_outputs_one2many"] = self._set_aux_loss(outputs_classes_one2many, outputs_coords_one2many)
|
| 221 |
+
|
| 222 |
+
if self.two_stage:
|
| 223 |
+
enc_outputs_coord = enc_outputs_coord_unact.sigmoid()
|
| 224 |
+
out["enc_outputs"] = {
|
| 225 |
+
"pred_logits": enc_outputs_class,
|
| 226 |
+
"pred_boxes": enc_outputs_coord,
|
| 227 |
+
}
|
| 228 |
+
return out
|
| 229 |
+
|
| 230 |
+
@torch.jit.unused
|
| 231 |
+
def _set_aux_loss(self, outputs_class, outputs_coord):
|
| 232 |
+
# this is a workaround to make torchscript happy, as torchscript
|
| 233 |
+
# doesn't support dictionary with non-homogeneous values, such
|
| 234 |
+
# as a dict having both a Tensor and a list.
|
| 235 |
+
return [{"pred_logits": a, "pred_boxes": b} for a, b in zip(outputs_class[:-1], outputs_coord[:-1])]
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
class PlainDETRReParam(PlainDETR):
|
| 239 |
+
def forward(self, samples: NestedTensor):
|
| 240 |
+
"""The forward expects a NestedTensor, which consists of:
|
| 241 |
+
- samples.tensor: batched images, of shape [batch_size x 3 x H x W]
|
| 242 |
+
- samples.mask: a binary mask of shape [batch_size x H x W], containing 1 on padded pixels
|
| 243 |
+
|
| 244 |
+
It returns a dict with the following elements:
|
| 245 |
+
- "pred_logits": the classification logits (including no-object) for all queries.
|
| 246 |
+
Shape= [batch_size x num_queries x (num_classes + 1)]
|
| 247 |
+
- "pred_boxes": The normalized boxes coordinates for all queries, represented as
|
| 248 |
+
(center_x, center_y, height, width). These values are normalized in [0, 1],
|
| 249 |
+
relative to the size of each individual image (disregarding possible padding).
|
| 250 |
+
See PostProcess for information on how to retrieve the unnormalized bounding box.
|
| 251 |
+
- "aux_outputs": Optional, only returned when auxilary losses are activated. It is a list of
|
| 252 |
+
dictionnaries containing the two above keys for each decoder layer.
|
| 253 |
+
"""
|
| 254 |
+
if not isinstance(samples, NestedTensor):
|
| 255 |
+
samples = nested_tensor_from_tensor_list(samples)
|
| 256 |
+
features, pos = self.backbone(samples)
|
| 257 |
+
|
| 258 |
+
srcs = []
|
| 259 |
+
masks = []
|
| 260 |
+
for layer, feat in enumerate(features):
|
| 261 |
+
src, mask = feat.decompose()
|
| 262 |
+
srcs.append(self.input_proj[layer](src))
|
| 263 |
+
masks.append(mask)
|
| 264 |
+
assert mask is not None
|
| 265 |
+
|
| 266 |
+
query_embeds = None
|
| 267 |
+
if not self.two_stage or self.mixed_selection:
|
| 268 |
+
query_embeds = self.query_embed.weight[0 : self.num_queries, :]
|
| 269 |
+
|
| 270 |
+
# make attn mask
|
| 271 |
+
""" attention mask to prevent information leakage
|
| 272 |
+
"""
|
| 273 |
+
self_attn_mask = torch.zeros(
|
| 274 |
+
[
|
| 275 |
+
self.num_queries,
|
| 276 |
+
self.num_queries,
|
| 277 |
+
],
|
| 278 |
+
dtype=bool,
|
| 279 |
+
device=src.device,
|
| 280 |
+
)
|
| 281 |
+
self_attn_mask[
|
| 282 |
+
self.num_queries_one2one :,
|
| 283 |
+
0 : self.num_queries_one2one,
|
| 284 |
+
] = True
|
| 285 |
+
self_attn_mask[
|
| 286 |
+
0 : self.num_queries_one2one,
|
| 287 |
+
self.num_queries_one2one :,
|
| 288 |
+
] = True
|
| 289 |
+
|
| 290 |
+
(
|
| 291 |
+
hs,
|
| 292 |
+
init_reference,
|
| 293 |
+
inter_references,
|
| 294 |
+
enc_outputs_class,
|
| 295 |
+
enc_outputs_coord_unact,
|
| 296 |
+
enc_outputs_delta,
|
| 297 |
+
output_proposals,
|
| 298 |
+
max_shape,
|
| 299 |
+
) = self.transformer(srcs, masks, pos, query_embeds, self_attn_mask)
|
| 300 |
+
|
| 301 |
+
outputs_classes_one2one = []
|
| 302 |
+
outputs_coords_one2one = []
|
| 303 |
+
outputs_classes_one2many = []
|
| 304 |
+
outputs_coords_one2many = []
|
| 305 |
+
|
| 306 |
+
outputs_coords_old_one2one = []
|
| 307 |
+
outputs_deltas_one2one = []
|
| 308 |
+
outputs_coords_old_one2many = []
|
| 309 |
+
outputs_deltas_one2many = []
|
| 310 |
+
|
| 311 |
+
for lvl in range(hs.shape[0]):
|
| 312 |
+
if lvl == 0:
|
| 313 |
+
reference = init_reference
|
| 314 |
+
else:
|
| 315 |
+
reference = inter_references[lvl - 1]
|
| 316 |
+
outputs_class = self.class_embed[lvl](hs[lvl])
|
| 317 |
+
tmp = self.bbox_embed[lvl](hs[lvl])
|
| 318 |
+
if reference.shape[-1] == 4:
|
| 319 |
+
outputs_coord = box_ops.box_xyxy_to_cxcywh(box_ops.delta2bbox(reference, tmp, max_shape))
|
| 320 |
+
else:
|
| 321 |
+
raise NotImplementedError
|
| 322 |
+
|
| 323 |
+
outputs_classes_one2one.append(outputs_class[:, 0 : self.num_queries_one2one])
|
| 324 |
+
outputs_classes_one2many.append(outputs_class[:, self.num_queries_one2one :])
|
| 325 |
+
|
| 326 |
+
outputs_coords_one2one.append(outputs_coord[:, 0 : self.num_queries_one2one])
|
| 327 |
+
outputs_coords_one2many.append(outputs_coord[:, self.num_queries_one2one :])
|
| 328 |
+
|
| 329 |
+
outputs_coords_old_one2one.append(reference[:, : self.num_queries_one2one])
|
| 330 |
+
outputs_coords_old_one2many.append(reference[:, self.num_queries_one2one :])
|
| 331 |
+
outputs_deltas_one2one.append(tmp[:, : self.num_queries_one2one])
|
| 332 |
+
outputs_deltas_one2many.append(tmp[:, self.num_queries_one2one :])
|
| 333 |
+
|
| 334 |
+
outputs_classes_one2one = torch.stack(outputs_classes_one2one)
|
| 335 |
+
outputs_coords_one2one = torch.stack(outputs_coords_one2one)
|
| 336 |
+
|
| 337 |
+
outputs_classes_one2many = torch.stack(outputs_classes_one2many)
|
| 338 |
+
outputs_coords_one2many = torch.stack(outputs_coords_one2many)
|
| 339 |
+
|
| 340 |
+
out = {
|
| 341 |
+
"pred_logits": outputs_classes_one2one[-1],
|
| 342 |
+
"pred_boxes": outputs_coords_one2one[-1],
|
| 343 |
+
"pred_logits_one2many": outputs_classes_one2many[-1],
|
| 344 |
+
"pred_boxes_one2many": outputs_coords_one2many[-1],
|
| 345 |
+
"pred_boxes_old": outputs_coords_old_one2one[-1],
|
| 346 |
+
"pred_deltas": outputs_deltas_one2one[-1],
|
| 347 |
+
"pred_boxes_old_one2many": outputs_coords_old_one2many[-1],
|
| 348 |
+
"pred_deltas_one2many": outputs_deltas_one2many[-1],
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
if self.aux_loss:
|
| 352 |
+
out["aux_outputs"] = self._set_aux_loss(
|
| 353 |
+
outputs_classes_one2one, outputs_coords_one2one, outputs_coords_old_one2one, outputs_deltas_one2one
|
| 354 |
+
)
|
| 355 |
+
out["aux_outputs_one2many"] = self._set_aux_loss(
|
| 356 |
+
outputs_classes_one2many, outputs_coords_one2many, outputs_coords_old_one2many, outputs_deltas_one2many
|
| 357 |
+
)
|
| 358 |
+
|
| 359 |
+
if self.two_stage:
|
| 360 |
+
out["enc_outputs"] = {
|
| 361 |
+
"pred_logits": enc_outputs_class,
|
| 362 |
+
"pred_boxes": enc_outputs_coord_unact,
|
| 363 |
+
"pred_boxes_old": output_proposals,
|
| 364 |
+
"pred_deltas": enc_outputs_delta,
|
| 365 |
+
}
|
| 366 |
+
return out
|
| 367 |
+
|
| 368 |
+
@torch.jit.unused
|
| 369 |
+
def _set_aux_loss(self, outputs_class, outputs_coord, outputs_coord_old, outputs_deltas):
|
| 370 |
+
# this is a workaround to make torchscript happy, as torchscript
|
| 371 |
+
# doesn't support dictionary with non-homogeneous values, such
|
| 372 |
+
# as a dict having both a Tensor and a list.
|
| 373 |
+
return [
|
| 374 |
+
{
|
| 375 |
+
"pred_logits": a,
|
| 376 |
+
"pred_boxes": b,
|
| 377 |
+
"pred_boxes_old": c,
|
| 378 |
+
"pred_deltas": d,
|
| 379 |
+
}
|
| 380 |
+
for a, b, c, d in zip(outputs_class[:-1], outputs_coord[:-1], outputs_coord_old[:-1], outputs_deltas[:-1])
|
| 381 |
+
]
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
class PostProcess(nn.Module):
|
| 385 |
+
"""This module converts the model's output into the format expected by the coco api"""
|
| 386 |
+
|
| 387 |
+
def __init__(self, topk=100, reparam=False):
|
| 388 |
+
super().__init__()
|
| 389 |
+
self.topk = topk
|
| 390 |
+
self.reparam = reparam
|
| 391 |
+
|
| 392 |
+
@torch.no_grad()
|
| 393 |
+
def forward(self, outputs, target_sizes, original_target_sizes=None):
|
| 394 |
+
"""Perform the computation
|
| 395 |
+
Parameters:
|
| 396 |
+
outputs: raw outputs of the model
|
| 397 |
+
target_sizes: tensor of dimension [batch_size x 2] containing the size of each images of the batch
|
| 398 |
+
For evaluation, this must be the original image size (before any data augmentation)
|
| 399 |
+
For visualization, this should be the image size after data augment, but before padding
|
| 400 |
+
"""
|
| 401 |
+
out_logits, out_bbox = outputs["pred_logits"], outputs["pred_boxes"]
|
| 402 |
+
|
| 403 |
+
assert len(out_logits) == len(target_sizes)
|
| 404 |
+
assert target_sizes.shape[1] == 2
|
| 405 |
+
assert not self.reparam or original_target_sizes.shape[1] == 2
|
| 406 |
+
|
| 407 |
+
prob = out_logits.sigmoid()
|
| 408 |
+
topk_values, topk_indexes = torch.topk(prob.view(out_logits.shape[0], -1), self.topk, dim=1)
|
| 409 |
+
scores = topk_values
|
| 410 |
+
topk_boxes = topk_indexes // out_logits.shape[2]
|
| 411 |
+
labels = topk_indexes % out_logits.shape[2]
|
| 412 |
+
boxes = box_ops.box_cxcywh_to_xyxy(out_bbox)
|
| 413 |
+
boxes = torch.gather(boxes, 1, topk_boxes.unsqueeze(-1).repeat(1, 1, 4))
|
| 414 |
+
|
| 415 |
+
# and from relative [0, 1] to absolute [0, height] coordinates
|
| 416 |
+
img_h, img_w = target_sizes.unbind(1)
|
| 417 |
+
if self.reparam:
|
| 418 |
+
img_h, img_w = img_h[:, None, None], img_w[:, None, None] # [BS, 1, 1]
|
| 419 |
+
boxes[..., 0::2].clamp_(min=torch.zeros_like(img_w), max=img_w)
|
| 420 |
+
boxes[..., 1::2].clamp_(min=torch.zeros_like(img_h), max=img_h)
|
| 421 |
+
scale_h, scale_w = (original_target_sizes / target_sizes).unbind(1)
|
| 422 |
+
scale_fct = torch.stack([scale_w, scale_h, scale_w, scale_h], dim=1)
|
| 423 |
+
else:
|
| 424 |
+
scale_fct = torch.stack([img_w, img_h, img_w, img_h], dim=1)
|
| 425 |
+
boxes = boxes * scale_fct[:, None, :]
|
| 426 |
+
|
| 427 |
+
results = [{"scores": s, "labels": l, "boxes": b} for s, l, b in zip(scores, labels, boxes)]
|
| 428 |
+
|
| 429 |
+
return results
|
| 430 |
+
|
| 431 |
+
|
| 432 |
+
class MLP(nn.Module):
|
| 433 |
+
"""Very simple multi-layer perceptron (also called FFN)"""
|
| 434 |
+
|
| 435 |
+
def __init__(self, input_dim, hidden_dim, output_dim, num_layers):
|
| 436 |
+
super().__init__()
|
| 437 |
+
self.num_layers = num_layers
|
| 438 |
+
h = [hidden_dim] * (num_layers - 1)
|
| 439 |
+
self.layers = nn.ModuleList(nn.Linear(n, k) for n, k in zip([input_dim] + h, h + [output_dim]))
|
| 440 |
+
|
| 441 |
+
def forward(self, x):
|
| 442 |
+
for i, layer in enumerate(self.layers):
|
| 443 |
+
x = F.relu(layer(x)) if i < self.num_layers - 1 else layer(x)
|
| 444 |
+
return x
|
| 445 |
+
|
| 446 |
+
|
| 447 |
+
def build_model(backbone_model, args):
|
| 448 |
+
backbone = build_backbone(backbone_model, args)
|
| 449 |
+
transformer = build_transformer(args)
|
| 450 |
+
model_class = PlainDETR if (not args.reparam) else PlainDETRReParam
|
| 451 |
+
return model_class(
|
| 452 |
+
backbone,
|
| 453 |
+
transformer,
|
| 454 |
+
num_classes=args.num_classes,
|
| 455 |
+
num_feature_levels=args.num_feature_levels,
|
| 456 |
+
aux_loss=args.aux_loss,
|
| 457 |
+
with_box_refine=args.with_box_refine,
|
| 458 |
+
two_stage=args.two_stage,
|
| 459 |
+
num_queries_one2one=args.num_queries_one2one,
|
| 460 |
+
num_queries_one2many=args.num_queries_one2many,
|
| 461 |
+
mixed_selection=args.mixed_selection,
|
| 462 |
+
)
|
dinov3/eval/detection/models/global_ape_decoder.py
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
# ------------------------------------------------------------------------
|
| 7 |
+
# Plain-DETR
|
| 8 |
+
# Copyright (c) 2023 Xi'an Jiaotong University & Microsoft Research Asia.
|
| 9 |
+
# Licensed under The MIT License [see LICENSE for details]
|
| 10 |
+
# ------------------------------------------------------------------------
|
| 11 |
+
# -*- coding: utf-8 -*-
|
| 12 |
+
import torch
|
| 13 |
+
import torch.nn as nn
|
| 14 |
+
import torch.utils.checkpoint as checkpoint
|
| 15 |
+
|
| 16 |
+
from ..util.misc import _get_activation_fn, _get_clones, inverse_sigmoid
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class GlobalCrossAttention(nn.Module):
|
| 20 |
+
def __init__(
|
| 21 |
+
self,
|
| 22 |
+
dim,
|
| 23 |
+
num_heads,
|
| 24 |
+
qkv_bias=True,
|
| 25 |
+
qk_scale=None,
|
| 26 |
+
attn_drop=0.0,
|
| 27 |
+
proj_drop=0.0,
|
| 28 |
+
):
|
| 29 |
+
super().__init__()
|
| 30 |
+
self.dim = dim
|
| 31 |
+
self.num_heads = num_heads
|
| 32 |
+
head_dim = dim // num_heads
|
| 33 |
+
self.scale = qk_scale or head_dim**-0.5
|
| 34 |
+
|
| 35 |
+
self.q = nn.Linear(dim, dim, bias=qkv_bias)
|
| 36 |
+
self.k = nn.Linear(dim, dim, bias=qkv_bias)
|
| 37 |
+
self.v = nn.Linear(dim, dim, bias=qkv_bias)
|
| 38 |
+
self.attn_drop = nn.Dropout(attn_drop)
|
| 39 |
+
self.proj = nn.Linear(dim, dim)
|
| 40 |
+
self.proj_drop = nn.Dropout(proj_drop)
|
| 41 |
+
self.softmax = nn.Softmax(dim=-1)
|
| 42 |
+
|
| 43 |
+
def forward(
|
| 44 |
+
self,
|
| 45 |
+
query,
|
| 46 |
+
k_input_flatten,
|
| 47 |
+
v_input_flatten,
|
| 48 |
+
input_padding_mask=None,
|
| 49 |
+
):
|
| 50 |
+
B_, N, C = k_input_flatten.shape
|
| 51 |
+
k = self.k(k_input_flatten).reshape(B_, N, self.num_heads, C // self.num_heads).permute(0, 2, 1, 3)
|
| 52 |
+
v = self.v(v_input_flatten).reshape(B_, N, self.num_heads, C // self.num_heads).permute(0, 2, 1, 3)
|
| 53 |
+
B_, N, C = query.shape
|
| 54 |
+
q = self.q(query).reshape(B_, N, self.num_heads, C // self.num_heads).permute(0, 2, 1, 3)
|
| 55 |
+
|
| 56 |
+
attn_mask = None
|
| 57 |
+
if input_padding_mask is not None:
|
| 58 |
+
attn_mask = input_padding_mask[:, None, None] * -100
|
| 59 |
+
attn_mask = attn_mask.contiguous() # to enable efficient attention
|
| 60 |
+
|
| 61 |
+
x = torch.nn.functional.scaled_dot_product_attention(
|
| 62 |
+
query=q,
|
| 63 |
+
key=k,
|
| 64 |
+
value=v,
|
| 65 |
+
attn_mask=attn_mask,
|
| 66 |
+
dropout_p=self.attn_drop.p if self.training else 0,
|
| 67 |
+
scale=self.scale,
|
| 68 |
+
)
|
| 69 |
+
x = x.transpose(1, 2).reshape(B_, N, C)
|
| 70 |
+
x = self.proj(x)
|
| 71 |
+
x = self.proj_drop(x)
|
| 72 |
+
return x
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class GlobalDecoderLayer(nn.Module):
|
| 76 |
+
def __init__(
|
| 77 |
+
self,
|
| 78 |
+
d_model=256,
|
| 79 |
+
d_ffn=1024,
|
| 80 |
+
dropout=0.1,
|
| 81 |
+
activation="relu",
|
| 82 |
+
n_heads=8,
|
| 83 |
+
norm_type="post_norm",
|
| 84 |
+
):
|
| 85 |
+
super().__init__()
|
| 86 |
+
|
| 87 |
+
self.norm_type = norm_type
|
| 88 |
+
|
| 89 |
+
# global cross attention
|
| 90 |
+
self.cross_attn = GlobalCrossAttention(d_model, n_heads)
|
| 91 |
+
self.dropout1 = nn.Dropout(dropout)
|
| 92 |
+
self.norm1 = nn.LayerNorm(d_model)
|
| 93 |
+
|
| 94 |
+
# self attention
|
| 95 |
+
self.self_attn = nn.MultiheadAttention(d_model, n_heads, dropout=dropout)
|
| 96 |
+
self.dropout2 = nn.Dropout(dropout)
|
| 97 |
+
self.norm2 = nn.LayerNorm(d_model)
|
| 98 |
+
|
| 99 |
+
# ffn
|
| 100 |
+
self.linear1 = nn.Linear(d_model, d_ffn)
|
| 101 |
+
self.activation = _get_activation_fn(activation)
|
| 102 |
+
self.dropout3 = nn.Dropout(dropout)
|
| 103 |
+
self.linear2 = nn.Linear(d_ffn, d_model)
|
| 104 |
+
self.dropout4 = nn.Dropout(dropout)
|
| 105 |
+
self.norm3 = nn.LayerNorm(d_model)
|
| 106 |
+
|
| 107 |
+
@staticmethod
|
| 108 |
+
def with_pos_embed(tensor, pos):
|
| 109 |
+
return tensor if pos is None else tensor + pos
|
| 110 |
+
|
| 111 |
+
def forward_pre(
|
| 112 |
+
self,
|
| 113 |
+
tgt,
|
| 114 |
+
query_pos,
|
| 115 |
+
src,
|
| 116 |
+
src_pos_embed,
|
| 117 |
+
src_padding_mask=None,
|
| 118 |
+
self_attn_mask=None,
|
| 119 |
+
):
|
| 120 |
+
# self attention
|
| 121 |
+
tgt2 = self.norm2(tgt)
|
| 122 |
+
q = k = self.with_pos_embed(tgt2, query_pos)
|
| 123 |
+
tgt2 = self.self_attn(
|
| 124 |
+
q.transpose(0, 1), k.transpose(0, 1), tgt2.transpose(0, 1), attn_mask=self_attn_mask, need_weights=False
|
| 125 |
+
)[0].transpose(0, 1)
|
| 126 |
+
tgt = tgt + self.dropout2(tgt2)
|
| 127 |
+
|
| 128 |
+
# global cross attention
|
| 129 |
+
tgt2 = self.norm1(tgt)
|
| 130 |
+
tgt2 = self.cross_attn(
|
| 131 |
+
self.with_pos_embed(tgt2, query_pos),
|
| 132 |
+
self.with_pos_embed(src, src_pos_embed),
|
| 133 |
+
src,
|
| 134 |
+
src_padding_mask,
|
| 135 |
+
)
|
| 136 |
+
tgt = tgt + self.dropout1(tgt2)
|
| 137 |
+
|
| 138 |
+
# ffn
|
| 139 |
+
tgt2 = self.norm3(tgt)
|
| 140 |
+
tgt2 = self.linear2(self.dropout3(self.activation(self.linear1(tgt2))))
|
| 141 |
+
tgt = tgt + self.dropout4(tgt2)
|
| 142 |
+
|
| 143 |
+
return tgt
|
| 144 |
+
|
| 145 |
+
def forward_post(
|
| 146 |
+
self,
|
| 147 |
+
tgt,
|
| 148 |
+
query_pos,
|
| 149 |
+
src,
|
| 150 |
+
src_pos_embed,
|
| 151 |
+
src_padding_mask=None,
|
| 152 |
+
self_attn_mask=None,
|
| 153 |
+
):
|
| 154 |
+
# self attention
|
| 155 |
+
q = k = self.with_pos_embed(tgt, query_pos)
|
| 156 |
+
tgt2 = self.self_attn(
|
| 157 |
+
q.transpose(0, 1), k.transpose(0, 1), tgt.transpose(0, 1), attn_mask=self_attn_mask, need_weights=False
|
| 158 |
+
)[0].transpose(0, 1)
|
| 159 |
+
tgt = tgt + self.dropout2(tgt2)
|
| 160 |
+
tgt = self.norm2(tgt)
|
| 161 |
+
|
| 162 |
+
# cross attention
|
| 163 |
+
tgt2 = self.cross_attn(
|
| 164 |
+
self.with_pos_embed(tgt, query_pos),
|
| 165 |
+
self.with_pos_embed(src, src_pos_embed),
|
| 166 |
+
src,
|
| 167 |
+
src_padding_mask,
|
| 168 |
+
)
|
| 169 |
+
tgt = tgt + self.dropout1(tgt2)
|
| 170 |
+
tgt = self.norm1(tgt)
|
| 171 |
+
|
| 172 |
+
# ffn
|
| 173 |
+
tgt2 = self.linear2(self.dropout3(self.activation(self.linear1(tgt))))
|
| 174 |
+
tgt = tgt + self.dropout4(tgt2)
|
| 175 |
+
tgt = self.norm3(tgt)
|
| 176 |
+
|
| 177 |
+
return tgt
|
| 178 |
+
|
| 179 |
+
def forward(
|
| 180 |
+
self,
|
| 181 |
+
tgt,
|
| 182 |
+
query_pos,
|
| 183 |
+
src,
|
| 184 |
+
src_pos_embed,
|
| 185 |
+
src_padding_mask=None,
|
| 186 |
+
self_attn_mask=None,
|
| 187 |
+
):
|
| 188 |
+
if self.norm_type == "pre_norm":
|
| 189 |
+
return self.forward_pre(tgt, query_pos, src, src_pos_embed, src_padding_mask, self_attn_mask)
|
| 190 |
+
if self.norm_type == "post_norm":
|
| 191 |
+
return self.forward_post(tgt, query_pos, src, src_pos_embed, src_padding_mask, self_attn_mask)
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
class GlobalDecoder(nn.Module):
|
| 195 |
+
def __init__(
|
| 196 |
+
self,
|
| 197 |
+
decoder_layer,
|
| 198 |
+
num_layers,
|
| 199 |
+
return_intermediate=False,
|
| 200 |
+
look_forward_twice=False,
|
| 201 |
+
use_checkpoint=False,
|
| 202 |
+
d_model=256,
|
| 203 |
+
norm_type="post_norm",
|
| 204 |
+
):
|
| 205 |
+
super().__init__()
|
| 206 |
+
self.layers = _get_clones(decoder_layer, num_layers)
|
| 207 |
+
self.num_layers = num_layers
|
| 208 |
+
self.return_intermediate = return_intermediate
|
| 209 |
+
self.look_forward_twice = look_forward_twice
|
| 210 |
+
self.use_checkpoint = use_checkpoint
|
| 211 |
+
# hack implementation for iterative bounding box refinement and two-stage Deformable DETR
|
| 212 |
+
self.bbox_embed = None
|
| 213 |
+
self.class_embed = None
|
| 214 |
+
|
| 215 |
+
self.norm_type = norm_type
|
| 216 |
+
if self.norm_type == "pre_norm":
|
| 217 |
+
self.final_layer_norm = nn.LayerNorm(d_model)
|
| 218 |
+
else:
|
| 219 |
+
self.final_layer_norm = None
|
| 220 |
+
|
| 221 |
+
def _reset_parameters(self):
|
| 222 |
+
# stolen from Swin Transformer
|
| 223 |
+
def _init_weights(m):
|
| 224 |
+
if isinstance(m, nn.Linear):
|
| 225 |
+
nn.init.trunc_normal_(m.weight, std=0.02)
|
| 226 |
+
if isinstance(m, nn.Linear) and m.bias is not None:
|
| 227 |
+
nn.init.constant_(m.bias, 0)
|
| 228 |
+
elif isinstance(m, nn.LayerNorm):
|
| 229 |
+
nn.init.constant_(m.bias, 0)
|
| 230 |
+
nn.init.constant_(m.weight, 1.0)
|
| 231 |
+
|
| 232 |
+
self.apply(_init_weights)
|
| 233 |
+
|
| 234 |
+
def forward(
|
| 235 |
+
self,
|
| 236 |
+
tgt,
|
| 237 |
+
reference_points,
|
| 238 |
+
src,
|
| 239 |
+
src_pos_embed,
|
| 240 |
+
src_spatial_shapes,
|
| 241 |
+
src_level_start_index,
|
| 242 |
+
src_valid_ratios,
|
| 243 |
+
query_pos=None,
|
| 244 |
+
src_padding_mask=None,
|
| 245 |
+
self_attn_mask=None,
|
| 246 |
+
max_shape=None,
|
| 247 |
+
):
|
| 248 |
+
output = tgt
|
| 249 |
+
|
| 250 |
+
intermediate = []
|
| 251 |
+
intermediate_reference_points = []
|
| 252 |
+
for lid, layer in enumerate(self.layers):
|
| 253 |
+
if self.use_checkpoint:
|
| 254 |
+
output = checkpoint.checkpoint(
|
| 255 |
+
layer,
|
| 256 |
+
output,
|
| 257 |
+
query_pos,
|
| 258 |
+
src,
|
| 259 |
+
src_pos_embed,
|
| 260 |
+
src_padding_mask,
|
| 261 |
+
self_attn_mask,
|
| 262 |
+
)
|
| 263 |
+
else:
|
| 264 |
+
output = layer(
|
| 265 |
+
output,
|
| 266 |
+
query_pos,
|
| 267 |
+
src,
|
| 268 |
+
src_pos_embed,
|
| 269 |
+
src_padding_mask,
|
| 270 |
+
self_attn_mask,
|
| 271 |
+
)
|
| 272 |
+
|
| 273 |
+
if self.final_layer_norm is not None:
|
| 274 |
+
output_after_norm = self.final_layer_norm(output)
|
| 275 |
+
else:
|
| 276 |
+
output_after_norm = output
|
| 277 |
+
|
| 278 |
+
# hack implementation for iterative bounding box refinement
|
| 279 |
+
if self.bbox_embed is not None:
|
| 280 |
+
tmp = self.bbox_embed[lid](output_after_norm)
|
| 281 |
+
if reference_points.shape[-1] == 4:
|
| 282 |
+
new_reference_points = tmp + inverse_sigmoid(reference_points)
|
| 283 |
+
new_reference_points = new_reference_points.sigmoid()
|
| 284 |
+
else:
|
| 285 |
+
assert reference_points.shape[-1] == 2
|
| 286 |
+
new_reference_points = tmp
|
| 287 |
+
new_reference_points[..., :2] = tmp[..., :2] + inverse_sigmoid(reference_points)
|
| 288 |
+
new_reference_points = new_reference_points.sigmoid()
|
| 289 |
+
reference_points = new_reference_points.detach()
|
| 290 |
+
|
| 291 |
+
if self.return_intermediate:
|
| 292 |
+
intermediate.append(output_after_norm)
|
| 293 |
+
intermediate_reference_points.append(
|
| 294 |
+
new_reference_points if self.look_forward_twice else reference_points
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
if self.return_intermediate:
|
| 298 |
+
return torch.stack(intermediate), torch.stack(intermediate_reference_points)
|
| 299 |
+
|
| 300 |
+
return output_after_norm, reference_points
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
def build_global_ape_decoder(args):
|
| 304 |
+
decoder_layer = GlobalDecoderLayer(
|
| 305 |
+
d_model=args.hidden_dim,
|
| 306 |
+
d_ffn=args.dim_feedforward,
|
| 307 |
+
dropout=args.dropout,
|
| 308 |
+
activation="relu",
|
| 309 |
+
n_heads=args.nheads,
|
| 310 |
+
norm_type=args.norm_type,
|
| 311 |
+
)
|
| 312 |
+
decoder = GlobalDecoder(
|
| 313 |
+
decoder_layer,
|
| 314 |
+
num_layers=args.dec_layers,
|
| 315 |
+
return_intermediate=True,
|
| 316 |
+
look_forward_twice=args.look_forward_twice,
|
| 317 |
+
use_checkpoint=args.decoder_use_checkpoint,
|
| 318 |
+
d_model=args.hidden_dim,
|
| 319 |
+
norm_type=args.norm_type,
|
| 320 |
+
)
|
| 321 |
+
return decoder
|