birth
This commit is contained in:
190
classification/README.md
Normal file
190
classification/README.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# FlashInternImage for Image Classification
|
||||
|
||||
This folder contains the implementation of the FlashInternImage for image classification.
|
||||
|
||||
## Usage
|
||||
|
||||
### Install
|
||||
|
||||
- Clone this repo:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/OpenGVLab/DCNv4.git
|
||||
cd DCNv4
|
||||
```
|
||||
|
||||
- Create a conda virtual environment and activate it:
|
||||
|
||||
```bash
|
||||
conda create -n internimage python=3.7 -y
|
||||
conda activate internimage
|
||||
```
|
||||
|
||||
- Install `CUDA>=10.2` with `cudnn>=7` following
|
||||
the [official installation instructions](https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html)
|
||||
- Install `PyTorch>=1.10.0` and `torchvision>=0.9.0` with `CUDA>=10.2`:
|
||||
|
||||
For examples, to install torch==1.11 with CUDA==11.3:
|
||||
```bash
|
||||
pip install torch==1.11.0+cu113 torchvision==0.12.0+cu113 -f https://download.pytorch.org/whl/torch_stable.html
|
||||
```
|
||||
|
||||
- Install `timm==0.6.11` and `mmcv-full==1.5.0`:
|
||||
|
||||
```bash
|
||||
pip install -U openmim
|
||||
mim install mmcv-full==1.5.0
|
||||
pip install timm==0.6.11 mmdet==2.28.1
|
||||
```
|
||||
|
||||
- Install other requirements:
|
||||
|
||||
```bash
|
||||
pip install opencv-python termcolor yacs pyyaml scipy
|
||||
```
|
||||
|
||||
- Install DCNv4
|
||||
```bash
|
||||
pip install DCNv4==latest
|
||||
```
|
||||
|
||||
### Data Preparation
|
||||
|
||||
We use standard ImageNet dataset, you can download it from http://image-net.org/. We provide the following two ways to
|
||||
load data:
|
||||
|
||||
- For standard folder dataset, move validation images to labeled sub-folders. The file structure should look like:
|
||||
```bash
|
||||
$ tree data
|
||||
imagenet
|
||||
├── train
|
||||
│ ├── class1
|
||||
│ │ ├── img1.jpeg
|
||||
│ │ ├── img2.jpeg
|
||||
│ │ └── ...
|
||||
│ ├── class2
|
||||
│ │ ├── img3.jpeg
|
||||
│ │ └── ...
|
||||
│ └── ...
|
||||
└── val
|
||||
├── class1
|
||||
│ ├── img4.jpeg
|
||||
│ ├── img5.jpeg
|
||||
│ └── ...
|
||||
├── class2
|
||||
│ ├── img6.jpeg
|
||||
│ └── ...
|
||||
└── ...
|
||||
|
||||
```
|
||||
- To boost the slow speed when reading images from massive small files, we also support zipped ImageNet, which includes
|
||||
four files:
|
||||
- `train.zip`, `val.zip`: which store the zipped folder for train and validate splits.
|
||||
- `train.txt`, `val.txt`: which store the relative path in the corresponding zip file and ground truth
|
||||
label. Make sure the data folder looks like this:
|
||||
|
||||
```bash
|
||||
$ tree data
|
||||
data
|
||||
└── ImageNet-Zip
|
||||
├── train_map.txt
|
||||
├── train.zip
|
||||
├── val_map.txt
|
||||
└── val.zip
|
||||
|
||||
$ head -n 5 meta_data/val.txt
|
||||
ILSVRC2012_val_00000001.JPEG 65
|
||||
ILSVRC2012_val_00000002.JPEG 970
|
||||
ILSVRC2012_val_00000003.JPEG 230
|
||||
ILSVRC2012_val_00000004.JPEG 809
|
||||
ILSVRC2012_val_00000005.JPEG 516
|
||||
|
||||
$ head -n 5 meta_data/train.txt
|
||||
n01440764/n01440764_10026.JPEG 0
|
||||
n01440764/n01440764_10027.JPEG 0
|
||||
n01440764/n01440764_10029.JPEG 0
|
||||
n01440764/n01440764_10040.JPEG 0
|
||||
n01440764/n01440764_10042.JPEG 0
|
||||
```
|
||||
- For ImageNet-22K dataset, make a folder named `fall11_whole` and move all images to labeled sub-folders in this
|
||||
folder. Then download the train-val split
|
||||
file ([ILSVRC2011fall_whole_map_train.txt](https://github.com/SwinTransformer/storage/releases/download/v2.0.1/ILSVRC2011fall_whole_map_train.txt)
|
||||
& [ILSVRC2011fall_whole_map_val.txt](https://github.com/SwinTransformer/storage/releases/download/v2.0.1/ILSVRC2011fall_whole_map_val.txt))
|
||||
, and put them in the parent directory of `fall11_whole`. The file structure should look like:
|
||||
|
||||
```bash
|
||||
$ tree imagenet22k/
|
||||
imagenet22k/
|
||||
└── fall11_whole
|
||||
├── n00004475
|
||||
├── n00005787
|
||||
├── n00006024
|
||||
├── n00006484
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Evaluation
|
||||
|
||||
To evaluate a pretrained `FlashInternImage` on ImageNet val, run:
|
||||
|
||||
```bash
|
||||
python -m torch.distributed.launch --nproc_per_node <num-of-gpus-to-use> --master_port 12345 main.py --eval \
|
||||
--cfg <config-file> --resume <checkpoint> --data-path <imagenet-path>
|
||||
```
|
||||
|
||||
For example, to evaluate the `FlashInternImage-B` with a single GPU:
|
||||
|
||||
```bash
|
||||
python -m torch.distributed.launch --nproc_per_node 1 --master_port 12345 main.py --eval \
|
||||
--cfg configs/flash_intern_image_b_1k_224.yaml --resume flash_intern_image_b_1k_224.pth --data-path <imagenet-path>
|
||||
```
|
||||
|
||||
### Training from Scratch on ImageNet-1K
|
||||
|
||||
> The paper results were obtained from models trained with configs in `configs/without_lr_decay`.
|
||||
|
||||
To train an `InternImage` on ImageNet from scratch, run:
|
||||
|
||||
```bash
|
||||
python -m torch.distributed.launch --nproc_per_node <num-of-gpus-to-use> --master_port 12345 main.py \
|
||||
--cfg <config-file> --data-path <imagenet-path> [--batch-size <batch-size-per-gpu> --output <output-directory> --tag <job-tag>]
|
||||
```
|
||||
|
||||
### Manage Jobs with Slurm.
|
||||
|
||||
For example, to train `FlashInternImage` with 8 GPU on a single node for 300 epochs, run:
|
||||
|
||||
`FlashInternImage-T`:
|
||||
|
||||
```bash
|
||||
GPUS=8 sh train_in1k.sh <partition> <job-name> configs/flash_intern_image_t_1k_224.yaml --resume flash_intern_image_t_1k_224.pth --eval
|
||||
```
|
||||
|
||||
`FlashInternImage-S`:
|
||||
|
||||
```bash
|
||||
GPUS=8 sh train_in1k.sh <partition> <job-name> configs/flash_intern_image_s_1k_224.yaml --resume flash_intern_image_s_1k_224.pth --eval
|
||||
```
|
||||
|
||||
|
||||
<!--
|
||||
### Test pretrained model on ImageNet-22K
|
||||
|
||||
For example, to evaluate the `InternImage-L-22k`:
|
||||
|
||||
```bash
|
||||
python -m torch.distributed.launch --nproc_per_node <num-of-gpus-to-use> --master_port 12345 main.py \
|
||||
--cfg configs/internimage_xl_22k_192to384.yaml --data-path <imagenet-path> [--batch-size <batch-size-per-gpu> --output <output-directory>] \
|
||||
--resume internimage_xl_22k_192to384.pth --eval
|
||||
``` -->
|
||||
|
||||
<!-- ### Fine-tuning from a ImageNet-22K pretrained model
|
||||
|
||||
For example, to fine-tune a `InternImage-XL-22k` model pretrained on ImageNet-22K:
|
||||
|
||||
```bashs
|
||||
GPUS=8 sh train_in1k.sh <partition> <job-name> configs/intern_image_.yaml --pretrained intern_image_b.pth --eval
|
||||
python -m torch.distributed.launch --nproc_per_node 8 --master_port 12345 main.py \
|
||||
--cfg configs/.yaml --pretrained swin_base_patch4_window7_224_22k.pth \
|
||||
--data-path <imagenet-path> --batch-size 64 --accumulation-steps 2 [--use-checkpoint]
|
||||
``` -->
|
||||
302
classification/config.py
Normal file
302
classification/config.py
Normal file
@@ -0,0 +1,302 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
import os
|
||||
import yaml
|
||||
from yacs.config import CfgNode as CN
|
||||
|
||||
_C = CN()
|
||||
|
||||
# Base config files
|
||||
_C.BASE = ['']
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Data settings
|
||||
# -----------------------------------------------------------------------------
|
||||
_C.DATA = CN()
|
||||
# Batch size for a single GPU, could be overwritten by command line argument
|
||||
_C.DATA.BATCH_SIZE = 128
|
||||
# Path to dataset, could be overwritten by command line argument
|
||||
_C.DATA.DATA_PATH = ''
|
||||
# Dataset name
|
||||
_C.DATA.DATASET = 'imagenet'
|
||||
# Input image size
|
||||
_C.DATA.IMG_SIZE = 224
|
||||
# Interpolation to resize image (random, bilinear, bicubic)
|
||||
_C.DATA.INTERPOLATION = 'bicubic'
|
||||
# Use zipped dataset instead of folder dataset
|
||||
# could be overwritten by command line argument
|
||||
_C.DATA.ZIP_MODE = False
|
||||
# Cache Data in Memory, could be overwritten by command line argument
|
||||
_C.DATA.CACHE_MODE = 'part'
|
||||
# Pin CPU memory in DataLoader for more efficient (sometimes) transfer to GPU.
|
||||
_C.DATA.PIN_MEMORY = True
|
||||
# Number of data loading threads
|
||||
_C.DATA.NUM_WORKERS = 8
|
||||
# Load data to memory
|
||||
_C.DATA.IMG_ON_MEMORY = False
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Model settings
|
||||
# -----------------------------------------------------------------------------
|
||||
_C.MODEL = CN()
|
||||
# Model type
|
||||
_C.MODEL.TYPE = 'INTERN_IMAGE'
|
||||
# Model name
|
||||
_C.MODEL.NAME = 'intern_image'
|
||||
# Pretrained weight from checkpoint, could be imagenet22k pretrained weight
|
||||
# could be overwritten by command line argument
|
||||
_C.MODEL.PRETRAINED = ''
|
||||
# Checkpoint to resume, could be overwritten by command line argument
|
||||
_C.MODEL.RESUME = ''
|
||||
# Number of classes, overwritten in data preparation
|
||||
_C.MODEL.NUM_CLASSES = 1000
|
||||
# Dropout rate
|
||||
_C.MODEL.DROP_RATE = 0.0
|
||||
# Drop path rate
|
||||
_C.MODEL.DROP_PATH_RATE = 0.1
|
||||
# Drop path type
|
||||
_C.MODEL.DROP_PATH_TYPE = 'linear' # linear, uniform
|
||||
# Label Smoothing
|
||||
_C.MODEL.LABEL_SMOOTHING = 0.1
|
||||
|
||||
# INTERN_IMAGE parameters
|
||||
_C.MODEL.INTERN_IMAGE = CN()
|
||||
_C.MODEL.INTERN_IMAGE.DEPTHS = [4, 4, 18, 4]
|
||||
_C.MODEL.INTERN_IMAGE.GROUPS = [4, 8, 16, 32]
|
||||
_C.MODEL.INTERN_IMAGE.CHANNELS = 64
|
||||
_C.MODEL.INTERN_IMAGE.LAYER_SCALE = None
|
||||
_C.MODEL.INTERN_IMAGE.OFFSET_SCALE = 1.0
|
||||
_C.MODEL.INTERN_IMAGE.MLP_RATIO = 4.0
|
||||
_C.MODEL.INTERN_IMAGE.CORE_OP = 'DCNv3'
|
||||
_C.MODEL.INTERN_IMAGE.POST_NORM = False
|
||||
_C.MODEL.INTERN_IMAGE.RES_POST_NORM = False
|
||||
_C.MODEL.INTERN_IMAGE.DW_KERNEL_SIZE = None
|
||||
_C.MODEL.INTERN_IMAGE.USE_CLIP_PROJECTOR = False
|
||||
_C.MODEL.INTERN_IMAGE.LEVEL2_POST_NORM = False
|
||||
_C.MODEL.INTERN_IMAGE.LEVEL2_POST_NORM_BLOCK_IDS = None
|
||||
_C.MODEL.INTERN_IMAGE.CENTER_FEATURE_SCALE = False
|
||||
|
||||
# FLASH_INTERN_IMAGE parameters
|
||||
_C.MODEL.FLASH_INTERN_IMAGE = CN()
|
||||
_C.MODEL.FLASH_INTERN_IMAGE.DEPTHS = [4, 4, 18, 4]
|
||||
_C.MODEL.FLASH_INTERN_IMAGE.GROUPS = [4, 8, 16, 32]
|
||||
_C.MODEL.FLASH_INTERN_IMAGE.CHANNELS = 64
|
||||
_C.MODEL.FLASH_INTERN_IMAGE.LAYER_SCALE = None
|
||||
_C.MODEL.FLASH_INTERN_IMAGE.OFFSET_SCALE = 1.0
|
||||
_C.MODEL.FLASH_INTERN_IMAGE.MLP_RATIO = 4.0
|
||||
_C.MODEL.FLASH_INTERN_IMAGE.CORE_OP = 'DCNv4'
|
||||
_C.MODEL.FLASH_INTERN_IMAGE.POST_NORM = False
|
||||
_C.MODEL.FLASH_INTERN_IMAGE.RES_POST_NORM = False
|
||||
_C.MODEL.FLASH_INTERN_IMAGE.DW_KERNEL_SIZE = None
|
||||
_C.MODEL.FLASH_INTERN_IMAGE.USE_CLIP_PROJECTOR = False
|
||||
_C.MODEL.FLASH_INTERN_IMAGE.LEVEL2_POST_NORM = False
|
||||
_C.MODEL.FLASH_INTERN_IMAGE.LEVEL2_POST_NORM_BLOCK_IDS = None
|
||||
_C.MODEL.FLASH_INTERN_IMAGE.CENTER_FEATURE_SCALE = False
|
||||
_C.MODEL.FLASH_INTERN_IMAGE.MLP_FC2_BIAS = False
|
||||
_C.MODEL.FLASH_INTERN_IMAGE.DCN_OUTPUT_BIAS = False
|
||||
# -----------------------------------------------------------------------------
|
||||
# Training settings
|
||||
# -----------------------------------------------------------------------------
|
||||
_C.TRAIN = CN()
|
||||
_C.TRAIN.START_EPOCH = 0
|
||||
_C.TRAIN.EPOCHS = 300
|
||||
_C.TRAIN.WARMUP_EPOCHS = 20
|
||||
_C.TRAIN.WEIGHT_DECAY = 0.05
|
||||
_C.TRAIN.BASE_LR = 5e-4
|
||||
_C.TRAIN.WARMUP_LR = 5e-7
|
||||
_C.TRAIN.MIN_LR = 5e-6
|
||||
# Clip gradient norm
|
||||
_C.TRAIN.CLIP_GRAD = 5.0
|
||||
# Auto resume from latest checkpoint
|
||||
_C.TRAIN.AUTO_RESUME = True
|
||||
# Gradient accumulation steps
|
||||
# could be overwritten by command line argument
|
||||
_C.TRAIN.ACCUMULATION_STEPS = 0
|
||||
# Whether to use gradient checkpointing to save memory
|
||||
# could be overwritten by command line argument
|
||||
_C.TRAIN.USE_CHECKPOINT = False
|
||||
|
||||
# LR scheduler
|
||||
_C.TRAIN.LR_SCHEDULER = CN()
|
||||
_C.TRAIN.LR_SCHEDULER.NAME = 'cosine'
|
||||
# Epoch interval to decay LR, used in StepLRScheduler
|
||||
_C.TRAIN.LR_SCHEDULER.DECAY_EPOCHS = 30
|
||||
# LR decay rate, used in StepLRScheduler
|
||||
_C.TRAIN.LR_SCHEDULER.DECAY_RATE = 0.1
|
||||
|
||||
# Optimizer
|
||||
_C.TRAIN.OPTIMIZER = CN()
|
||||
_C.TRAIN.OPTIMIZER.NAME = 'adamw'
|
||||
# Optimizer Epsilon
|
||||
_C.TRAIN.OPTIMIZER.EPS = 1e-8
|
||||
# Optimizer Betas
|
||||
_C.TRAIN.OPTIMIZER.BETAS = (0.9, 0.999)
|
||||
# SGD momentum
|
||||
_C.TRAIN.OPTIMIZER.MOMENTUM = 0.9
|
||||
# ZeRO
|
||||
_C.TRAIN.OPTIMIZER.USE_ZERO = False
|
||||
# freeze backbone
|
||||
_C.TRAIN.OPTIMIZER.FREEZE_BACKBONE = None
|
||||
# dcn lr
|
||||
_C.TRAIN.OPTIMIZER.DCN_LR_MUL = None
|
||||
|
||||
# EMA
|
||||
_C.TRAIN.EMA = CN()
|
||||
_C.TRAIN.EMA.ENABLE = False
|
||||
_C.TRAIN.EMA.DECAY = 0.9998
|
||||
|
||||
# LR_LAYER_DECAY
|
||||
_C.TRAIN.LR_LAYER_DECAY = False
|
||||
_C.TRAIN.LR_LAYER_DECAY_RATIO = 0.875
|
||||
|
||||
# FT head init weights
|
||||
_C.TRAIN.RAND_INIT_FT_HEAD = False
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Augmentation settings
|
||||
# -----------------------------------------------------------------------------
|
||||
_C.AUG = CN()
|
||||
# Color jitter factor
|
||||
_C.AUG.COLOR_JITTER = 0.4
|
||||
# Use AutoAugment policy. "v0" or "original"
|
||||
_C.AUG.AUTO_AUGMENT = 'rand-m9-mstd0.5-inc1'
|
||||
# Random erase prob
|
||||
_C.AUG.REPROB = 0.25
|
||||
# Random erase mode
|
||||
_C.AUG.REMODE = 'pixel'
|
||||
# Random erase count
|
||||
_C.AUG.RECOUNT = 1
|
||||
# Mixup alpha, mixup enabled if > 0
|
||||
_C.AUG.MIXUP = 0.8
|
||||
# Cutmix alpha, cutmix enabled if > 0
|
||||
_C.AUG.CUTMIX = 1.0
|
||||
# Cutmix min/max ratio, overrides alpha and enables cutmix if set
|
||||
_C.AUG.CUTMIX_MINMAX = None
|
||||
# Probability of performing mixup or cutmix when either/both is enabled
|
||||
_C.AUG.MIXUP_PROB = 1.0
|
||||
# Probability of switching to cutmix when both mixup and cutmix enabled
|
||||
_C.AUG.MIXUP_SWITCH_PROB = 0.5
|
||||
# How to apply mixup/cutmix params. Per "batch", "pair", or "elem"
|
||||
_C.AUG.MIXUP_MODE = 'batch'
|
||||
# RandomResizedCrop
|
||||
_C.AUG.RANDOM_RESIZED_CROP = False
|
||||
_C.AUG.MEAN = (0.485, 0.456, 0.406)
|
||||
_C.AUG.STD = (0.229, 0.224, 0.225)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Testing settings
|
||||
# -----------------------------------------------------------------------------
|
||||
_C.TEST = CN()
|
||||
# Whether to use center crop when testing
|
||||
_C.TEST.CROP = True
|
||||
|
||||
# Whether to use SequentialSampler as validation sampler
|
||||
_C.TEST.SEQUENTIAL = False
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Misc
|
||||
# -----------------------------------------------------------------------------
|
||||
# Mixed precision opt level, if O0, no amp is used ('O0', 'O1', 'O2')
|
||||
# overwritten by command line argument
|
||||
_C.AMP_OPT_LEVEL = ''
|
||||
# Path to output folder, overwritten by command line argument
|
||||
_C.OUTPUT = ''
|
||||
# Tag of experiment, overwritten by command line argument
|
||||
_C.TAG = 'default'
|
||||
# Frequency to save checkpoint
|
||||
_C.SAVE_FREQ = 1
|
||||
# Frequency to logging info
|
||||
_C.PRINT_FREQ = 10
|
||||
# eval freq
|
||||
_C.EVAL_FREQ = 1
|
||||
# Fixed random seed
|
||||
_C.SEED = 0
|
||||
# Perform evaluation only, overwritten by command line argument
|
||||
_C.EVAL_MODE = False
|
||||
# Test throughput only, overwritten by command line argument
|
||||
_C.THROUGHPUT_MODE = False
|
||||
# local rank for DistributedDataParallel, given by command line argument
|
||||
_C.LOCAL_RANK = 0
|
||||
_C.EVAL_22K_TO_1K = False
|
||||
|
||||
_C.AMP_TYPE = 'float16'
|
||||
|
||||
|
||||
def _update_config_from_file(config, cfg_file):
|
||||
config.defrost()
|
||||
with open(cfg_file, 'r') as f:
|
||||
yaml_cfg = yaml.load(f, Loader=yaml.FullLoader)
|
||||
|
||||
for cfg in yaml_cfg.setdefault('BASE', ['']):
|
||||
if cfg:
|
||||
_update_config_from_file(
|
||||
config, os.path.join(os.path.dirname(cfg_file), cfg))
|
||||
print('=> merge config from {}'.format(cfg_file))
|
||||
config.merge_from_file(cfg_file)
|
||||
config.freeze()
|
||||
|
||||
|
||||
def update_config(config, args):
|
||||
_update_config_from_file(config, args.cfg)
|
||||
|
||||
config.defrost()
|
||||
if hasattr(args, 'opts') and args.opts:
|
||||
config.merge_from_list(args.opts)
|
||||
|
||||
# merge from specific arguments
|
||||
if hasattr(args, 'batch_size') and args.batch_size:
|
||||
config.DATA.BATCH_SIZE = args.batch_size
|
||||
if hasattr(args, 'dataset') and args.dataset:
|
||||
config.DATA.DATASET = args.dataset
|
||||
if hasattr(args, 'data_path') and args.data_path:
|
||||
config.DATA.DATA_PATH = args.data_path
|
||||
if hasattr(args, 'zip') and args.zip:
|
||||
config.DATA.ZIP_MODE = True
|
||||
if hasattr(args, 'cache_mode') and args.cache_mode:
|
||||
config.DATA.CACHE_MODE = args.cache_mode
|
||||
if hasattr(args, 'pretrained') and args.pretrained:
|
||||
config.MODEL.PRETRAINED = args.pretrained
|
||||
if hasattr(args, 'resume') and args.resume:
|
||||
config.MODEL.RESUME = args.resume
|
||||
if hasattr(args, 'accumulation_steps') and args.accumulation_steps:
|
||||
config.TRAIN.ACCUMULATION_STEPS = args.accumulation_steps
|
||||
if hasattr(args, 'use_checkpoint') and args.use_checkpoint:
|
||||
config.TRAIN.USE_CHECKPOINT = True
|
||||
if hasattr(args, 'amp_opt_level') and args.amp_opt_level:
|
||||
config.AMP_OPT_LEVEL = args.amp_opt_level
|
||||
if hasattr(args, 'output') and args.output:
|
||||
config.OUTPUT = args.output
|
||||
if hasattr(args, 'tag') and args.tag:
|
||||
config.TAG = args.tag
|
||||
if hasattr(args, 'eval') and args.eval:
|
||||
config.EVAL_MODE = True
|
||||
if hasattr(args, 'throughput') and args.throughput:
|
||||
config.THROUGHPUT_MODE = True
|
||||
if hasattr(args, 'save_ckpt_num') and args.save_ckpt_num:
|
||||
config.SAVE_CKPT_NUM = args.save_ckpt_num
|
||||
if hasattr(args, 'use_zero') and args.use_zero:
|
||||
config.TRAIN.OPTIMIZER.USE_ZERO = True
|
||||
# set local rank for distributed training
|
||||
if hasattr(args, 'local_rank') and args.local_rank:
|
||||
config.LOCAL_RANK = args.local_rank
|
||||
|
||||
# output folder
|
||||
config.MODEL.NAME = args.cfg.split('/')[-1].replace('.yaml', '')
|
||||
config.OUTPUT = os.path.join(config.OUTPUT, config.MODEL.NAME)
|
||||
# config.OUTPUT = os.path.join(config.OUTPUT, config.MODEL.NAME, config.TAG)
|
||||
|
||||
config.freeze()
|
||||
|
||||
|
||||
def get_config(args):
|
||||
"""Get a yacs CfgNode object with default values."""
|
||||
# Return a clone so that the defaults will not be altered
|
||||
# This is for the "local variable" use pattern
|
||||
config = _C.clone()
|
||||
update_config(config, args)
|
||||
|
||||
return config
|
||||
20
classification/configs/flash_intern_image_b_1k_224.yaml
Executable file
20
classification/configs/flash_intern_image_b_1k_224.yaml
Executable file
@@ -0,0 +1,20 @@
|
||||
DATA:
|
||||
IMG_ON_MEMORY: False
|
||||
MODEL:
|
||||
TYPE: flash_intern_image
|
||||
DROP_PATH_RATE: 0.5
|
||||
FLASH_INTERN_IMAGE:
|
||||
CORE_OP: 'DCNv4'
|
||||
DEPTHS: [4, 4, 21, 4]
|
||||
GROUPS: [7, 14, 28, 56]
|
||||
CHANNELS: 112
|
||||
LAYER_SCALE: 1e-5
|
||||
OFFSET_SCALE: 0.5
|
||||
MLP_RATIO: 4.0
|
||||
POST_NORM: True
|
||||
DW_KERNEL_SIZE: 3
|
||||
TRAIN:
|
||||
EMA:
|
||||
ENABLE: True
|
||||
DECAY: 0.9999
|
||||
BASE_LR: 5e-4
|
||||
40
classification/configs/flash_intern_image_l_22kto1k_384.yaml
Executable file
40
classification/configs/flash_intern_image_l_22kto1k_384.yaml
Executable file
@@ -0,0 +1,40 @@
|
||||
DATA:
|
||||
IMG_SIZE: 384
|
||||
IMG_ON_MEMORY: False
|
||||
AUG:
|
||||
MIXUP: 0.0
|
||||
CUTMIX: 0.0
|
||||
REPROB: 0.0
|
||||
MODEL:
|
||||
TYPE: flash_intern_image
|
||||
DROP_PATH_RATE: 0.1
|
||||
LABEL_SMOOTHING: 0.3
|
||||
FLASH_INTERN_IMAGE:
|
||||
CORE_OP: 'DCNv4'
|
||||
DEPTHS: [5, 5, 22, 5]
|
||||
GROUPS: [10, 20, 40, 80]
|
||||
CHANNELS: 160
|
||||
LAYER_SCALE: 1e-5
|
||||
OFFSET_SCALE: 2.0
|
||||
MLP_RATIO: 4.0
|
||||
POST_NORM: True
|
||||
DW_KERNEL_SIZE: 3
|
||||
DCN_OUTPUT_BIAS: True
|
||||
MLP_FC2_BIAS: True
|
||||
TRAIN:
|
||||
EMA:
|
||||
ENABLE: true
|
||||
DECAY: 0.9999
|
||||
EPOCHS: 20
|
||||
WARMUP_EPOCHS: 2
|
||||
WEIGHT_DECAY: 0.05
|
||||
BASE_LR: 2e-05 # 512
|
||||
WARMUP_LR: .0
|
||||
MIN_LR: .0
|
||||
LR_LAYER_DECAY: true
|
||||
LR_LAYER_DECAY_RATIO: 0.9
|
||||
USE_CHECKPOINT: true
|
||||
OPTIMIZER:
|
||||
DCN_LR_MUL: 0.1
|
||||
AMP_OPT_LEVEL: O0
|
||||
EVAL_FREQ: 1
|
||||
20
classification/configs/flash_intern_image_s_1k_224.yaml
Executable file
20
classification/configs/flash_intern_image_s_1k_224.yaml
Executable file
@@ -0,0 +1,20 @@
|
||||
DATA:
|
||||
IMG_ON_MEMORY: False
|
||||
MODEL:
|
||||
TYPE: flash_intern_image
|
||||
DROP_PATH_RATE: 0.4
|
||||
FLASH_INTERN_IMAGE:
|
||||
CORE_OP: 'DCNv4'
|
||||
DEPTHS: [4, 4, 21, 4]
|
||||
GROUPS: [5, 10, 20, 40]
|
||||
CHANNELS: 80
|
||||
LAYER_SCALE: 1e-5
|
||||
OFFSET_SCALE: 1.0
|
||||
MLP_RATIO: 4.0
|
||||
POST_NORM: True
|
||||
DW_KERNEL_SIZE: 3
|
||||
TRAIN:
|
||||
EMA:
|
||||
ENABLE: True
|
||||
DECAY: 0.9999
|
||||
BASE_LR: 5e-4
|
||||
17
classification/configs/flash_intern_image_t_1k_224.yaml
Executable file
17
classification/configs/flash_intern_image_t_1k_224.yaml
Executable file
@@ -0,0 +1,17 @@
|
||||
DATA:
|
||||
IMG_ON_MEMORY: False
|
||||
MODEL:
|
||||
TYPE: flash_intern_image
|
||||
DROP_PATH_RATE: 0.1
|
||||
FLASH_INTERN_IMAGE:
|
||||
CORE_OP: 'DCNv4'
|
||||
DEPTHS: [4, 4, 18, 4]
|
||||
GROUPS: [4, 8, 16, 32]
|
||||
CHANNELS: 64
|
||||
OFFSET_SCALE: 1.0
|
||||
MLP_RATIO: 4.0
|
||||
TRAIN:
|
||||
EMA:
|
||||
ENABLE: True
|
||||
DECAY: 0.9999
|
||||
BASE_LR: 5e-4
|
||||
7
classification/dataset/__init__.py
Normal file
7
classification/dataset/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from .build import build_loader, build_loader2
|
||||
286
classification/dataset/build.py
Normal file
286
classification/dataset/build.py
Normal file
@@ -0,0 +1,286 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
import os
|
||||
import torch
|
||||
import numpy as np
|
||||
import torch.distributed as dist
|
||||
from torchvision import transforms
|
||||
from timm.data import Mixup
|
||||
from timm.data import create_transform
|
||||
from .cached_image_folder import ImageCephDataset
|
||||
from .samplers import SubsetRandomSampler, NodeDistributedSampler
|
||||
|
||||
try:
|
||||
from torchvision.transforms import InterpolationMode
|
||||
|
||||
def _pil_interp(method):
|
||||
if method == 'bicubic':
|
||||
return InterpolationMode.BICUBIC
|
||||
elif method == 'lanczos':
|
||||
return InterpolationMode.LANCZOS
|
||||
elif method == 'hamming':
|
||||
return InterpolationMode.HAMMING
|
||||
else:
|
||||
return InterpolationMode.BILINEAR
|
||||
except:
|
||||
from timm.data.transforms import _pil_interp
|
||||
|
||||
|
||||
class TTA(torch.nn.Module):
|
||||
|
||||
def __init__(self, size, scales=[1.0, 1.05, 1.1]):
|
||||
super().__init__()
|
||||
self.size = size
|
||||
self.scales = scales
|
||||
|
||||
def forward(self, img):
|
||||
out = []
|
||||
cc = transforms.CenterCrop(self.size)
|
||||
for scale in self.scales:
|
||||
size_ = int(scale * self.size)
|
||||
rs = transforms.Resize(size_, interpolation=_pil_interp('bicubic'))
|
||||
img_ = rs(img)
|
||||
img_ = cc(img_)
|
||||
out.append(img_)
|
||||
|
||||
return out
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}(size={self.size}, scale={self.scales})"
|
||||
|
||||
|
||||
def build_loader(config):
|
||||
config.defrost()
|
||||
dataset_train, config.MODEL.NUM_CLASSES = build_dataset('train',
|
||||
config=config)
|
||||
config.freeze()
|
||||
print(f"local rank {config.LOCAL_RANK} / global rank {dist.get_rank()}"
|
||||
"successfully build train dataset")
|
||||
|
||||
dataset_val, _ = build_dataset('val', config=config)
|
||||
print(f"local rank {config.LOCAL_RANK} / global rank {dist.get_rank()}"
|
||||
"successfully build val dataset")
|
||||
|
||||
dataset_test, _ = build_dataset('test', config=config)
|
||||
print(f"local rank {config.LOCAL_RANK} / global rank {dist.get_rank()}"
|
||||
"successfully build test dataset")
|
||||
|
||||
num_tasks = dist.get_world_size()
|
||||
global_rank = dist.get_rank()
|
||||
|
||||
if dataset_train is not None:
|
||||
if config.DATA.IMG_ON_MEMORY:
|
||||
sampler_train = NodeDistributedSampler(dataset_train)
|
||||
else:
|
||||
if config.DATA.ZIP_MODE and config.DATA.CACHE_MODE == 'part':
|
||||
indices = np.arange(dist.get_rank(), len(dataset_train),
|
||||
dist.get_world_size())
|
||||
sampler_train = SubsetRandomSampler(indices)
|
||||
else:
|
||||
sampler_train = torch.utils.data.DistributedSampler(
|
||||
dataset_train,
|
||||
num_replicas=num_tasks,
|
||||
rank=global_rank,
|
||||
shuffle=True)
|
||||
|
||||
if dataset_val is not None:
|
||||
if config.TEST.SEQUENTIAL:
|
||||
sampler_val = torch.utils.data.SequentialSampler(dataset_val)
|
||||
else:
|
||||
sampler_val = torch.utils.data.distributed.DistributedSampler(
|
||||
dataset_val, shuffle=False)
|
||||
|
||||
if dataset_test is not None:
|
||||
if config.TEST.SEQUENTIAL:
|
||||
sampler_test = torch.utils.data.SequentialSampler(dataset_test)
|
||||
else:
|
||||
sampler_test = torch.utils.data.distributed.DistributedSampler(
|
||||
dataset_test, shuffle=False)
|
||||
|
||||
data_loader_train = torch.utils.data.DataLoader(
|
||||
dataset_train,
|
||||
sampler=sampler_train,
|
||||
batch_size=config.DATA.BATCH_SIZE,
|
||||
num_workers=config.DATA.NUM_WORKERS,
|
||||
pin_memory=config.DATA.PIN_MEMORY,
|
||||
drop_last=True,
|
||||
persistent_workers=True) if dataset_train is not None else None
|
||||
|
||||
data_loader_val = torch.utils.data.DataLoader(
|
||||
dataset_val,
|
||||
sampler=sampler_val,
|
||||
batch_size=config.DATA.BATCH_SIZE,
|
||||
shuffle=False,
|
||||
num_workers=config.DATA.NUM_WORKERS,
|
||||
pin_memory=config.DATA.PIN_MEMORY,
|
||||
drop_last=False,
|
||||
persistent_workers=True) if dataset_val is not None else None
|
||||
|
||||
data_loader_test = torch.utils.data.DataLoader(
|
||||
dataset_test,
|
||||
sampler=sampler_test,
|
||||
batch_size=config.DATA.BATCH_SIZE,
|
||||
shuffle=False,
|
||||
num_workers=config.DATA.NUM_WORKERS,
|
||||
pin_memory=config.DATA.PIN_MEMORY,
|
||||
drop_last=False,
|
||||
persistent_workers=True) if dataset_test is not None else None
|
||||
|
||||
# setup mixup / cutmix
|
||||
mixup_fn = None
|
||||
mixup_active = config.AUG.MIXUP > 0 or config.AUG.CUTMIX > 0. or config.AUG.CUTMIX_MINMAX is not None
|
||||
if mixup_active:
|
||||
mixup_fn = Mixup(mixup_alpha=config.AUG.MIXUP,
|
||||
cutmix_alpha=config.AUG.CUTMIX,
|
||||
cutmix_minmax=config.AUG.CUTMIX_MINMAX,
|
||||
prob=config.AUG.MIXUP_PROB,
|
||||
switch_prob=config.AUG.MIXUP_SWITCH_PROB,
|
||||
mode=config.AUG.MIXUP_MODE,
|
||||
label_smoothing=config.MODEL.LABEL_SMOOTHING,
|
||||
num_classes=config.MODEL.NUM_CLASSES)
|
||||
|
||||
return dataset_train, dataset_val, dataset_test, data_loader_train, \
|
||||
data_loader_val, data_loader_test, mixup_fn
|
||||
|
||||
|
||||
def build_loader2(config):
|
||||
config.defrost()
|
||||
dataset_train, config.MODEL.NUM_CLASSES = build_dataset('train',
|
||||
config=config)
|
||||
config.freeze()
|
||||
dataset_val, _ = build_dataset('val', config=config)
|
||||
dataset_test, _ = build_dataset('test', config=config)
|
||||
|
||||
data_loader_train = torch.utils.data.DataLoader(
|
||||
dataset_train,
|
||||
shuffle=True,
|
||||
batch_size=config.DATA.BATCH_SIZE,
|
||||
num_workers=config.DATA.NUM_WORKERS,
|
||||
pin_memory=config.DATA.PIN_MEMORY,
|
||||
drop_last=True,
|
||||
persistent_workers=True) if dataset_train is not None else None
|
||||
|
||||
data_loader_val = torch.utils.data.DataLoader(
|
||||
dataset_val,
|
||||
batch_size=config.DATA.BATCH_SIZE,
|
||||
shuffle=False,
|
||||
num_workers=config.DATA.NUM_WORKERS,
|
||||
pin_memory=config.DATA.PIN_MEMORY,
|
||||
drop_last=False,
|
||||
persistent_workers=True) if dataset_val is not None else None
|
||||
|
||||
data_loader_test = torch.utils.data.DataLoader(
|
||||
dataset_test,
|
||||
batch_size=config.DATA.BATCH_SIZE,
|
||||
shuffle=False,
|
||||
num_workers=config.DATA.NUM_WORKERS,
|
||||
pin_memory=config.DATA.PIN_MEMORY,
|
||||
drop_last=False,
|
||||
persistent_workers=True) if dataset_test is not None else None
|
||||
|
||||
# setup mixup / cutmix
|
||||
mixup_fn = None
|
||||
mixup_active = config.AUG.MIXUP > 0 or config.AUG.CUTMIX > 0. or config.AUG.CUTMIX_MINMAX is not None
|
||||
if mixup_active:
|
||||
mixup_fn = Mixup(mixup_alpha=config.AUG.MIXUP,
|
||||
cutmix_alpha=config.AUG.CUTMIX,
|
||||
cutmix_minmax=config.AUG.CUTMIX_MINMAX,
|
||||
prob=config.AUG.MIXUP_PROB,
|
||||
switch_prob=config.AUG.MIXUP_SWITCH_PROB,
|
||||
mode=config.AUG.MIXUP_MODE,
|
||||
label_smoothing=config.MODEL.LABEL_SMOOTHING,
|
||||
num_classes=config.MODEL.NUM_CLASSES)
|
||||
|
||||
return dataset_train, dataset_val, dataset_test, data_loader_train, \
|
||||
data_loader_val, data_loader_test, mixup_fn
|
||||
|
||||
|
||||
def build_dataset(split, config):
|
||||
transform = build_transform(split == 'train', config)
|
||||
dataset = None
|
||||
nb_classes = None
|
||||
prefix = split
|
||||
if config.DATA.DATASET == 'imagenet':
|
||||
if prefix == 'train' and not config.EVAL_MODE:
|
||||
root = os.path.join(config.DATA.DATA_PATH, 'train')
|
||||
dataset = ImageCephDataset(root,
|
||||
'train',
|
||||
transform=transform,
|
||||
on_memory=config.DATA.IMG_ON_MEMORY)
|
||||
elif prefix == 'val':
|
||||
root = os.path.join(config.DATA.DATA_PATH, 'val')
|
||||
dataset = ImageCephDataset(root, 'val', transform=transform)
|
||||
nb_classes = 1000
|
||||
elif config.DATA.DATASET == 'imagenet22K':
|
||||
if prefix == 'train':
|
||||
if not config.EVAL_MODE:
|
||||
root = config.DATA.DATA_PATH
|
||||
dataset = ImageCephDataset(root,
|
||||
'train',
|
||||
transform=transform,
|
||||
on_memory=config.DATA.IMG_ON_MEMORY)
|
||||
nb_classes = 21841
|
||||
elif prefix == 'val':
|
||||
root = os.path.join(config.DATA.DATA_PATH, 'val')
|
||||
dataset = ImageCephDataset(root, 'val', transform=transform)
|
||||
nb_classes = 1000
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f'build_dataset does support {config.DATA.DATASET}')
|
||||
|
||||
return dataset, nb_classes
|
||||
|
||||
|
||||
def build_transform(is_train, config):
|
||||
resize_im = config.DATA.IMG_SIZE > 32
|
||||
if is_train:
|
||||
# this should always dispatch to transforms_imagenet_train
|
||||
transform = create_transform(
|
||||
input_size=config.DATA.IMG_SIZE,
|
||||
is_training=True,
|
||||
color_jitter=config.AUG.COLOR_JITTER
|
||||
if config.AUG.COLOR_JITTER > 0 else None,
|
||||
auto_augment=config.AUG.AUTO_AUGMENT
|
||||
if config.AUG.AUTO_AUGMENT != 'none' else None,
|
||||
re_prob=config.AUG.REPROB,
|
||||
re_mode=config.AUG.REMODE,
|
||||
re_count=config.AUG.RECOUNT,
|
||||
interpolation=config.DATA.INTERPOLATION,
|
||||
)
|
||||
if not resize_im:
|
||||
# replace RandomResizedCropAndInterpolation with
|
||||
# RandomCrop
|
||||
transform.transforms[0] = transforms.RandomCrop(
|
||||
config.DATA.IMG_SIZE, padding=4)
|
||||
|
||||
return transform
|
||||
|
||||
t = []
|
||||
if resize_im:
|
||||
if config.TEST.CROP:
|
||||
size = int(1.0 * config.DATA.IMG_SIZE)
|
||||
t.append(
|
||||
transforms.Resize(size,
|
||||
interpolation=_pil_interp(
|
||||
config.DATA.INTERPOLATION)),
|
||||
# to maintain same ratio w.r.t. 224 images
|
||||
)
|
||||
t.append(transforms.CenterCrop(config.DATA.IMG_SIZE))
|
||||
elif config.AUG.RANDOM_RESIZED_CROP:
|
||||
t.append(
|
||||
transforms.RandomResizedCrop(
|
||||
(config.DATA.IMG_SIZE, config.DATA.IMG_SIZE),
|
||||
interpolation=_pil_interp(config.DATA.INTERPOLATION)))
|
||||
else:
|
||||
t.append(
|
||||
transforms.Resize(
|
||||
(config.DATA.IMG_SIZE, config.DATA.IMG_SIZE),
|
||||
interpolation=_pil_interp(config.DATA.INTERPOLATION)))
|
||||
t.append(transforms.ToTensor())
|
||||
t.append(transforms.Normalize(config.AUG.MEAN, config.AUG.STD))
|
||||
|
||||
return transforms.Compose(t)
|
||||
538
classification/dataset/cached_image_folder.py
Normal file
538
classification/dataset/cached_image_folder.py
Normal file
@@ -0,0 +1,538 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import json
|
||||
import math
|
||||
import mmcv
|
||||
import torch
|
||||
import logging
|
||||
import os.path as osp
|
||||
from PIL import Image
|
||||
from tqdm import tqdm, trange
|
||||
from abc import abstractmethod
|
||||
import torch.utils.data as data
|
||||
import torch.distributed as dist
|
||||
from mmcv.fileio import FileClient
|
||||
from .zipreader import is_zip_path, ZipReader
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
_ERROR_RETRY = 50
|
||||
|
||||
|
||||
def has_file_allowed_extension(filename, extensions):
|
||||
"""Checks if a file is an allowed extension.
|
||||
Args:
|
||||
filename (string): path to a file
|
||||
Returns:
|
||||
bool: True if the filename ends with a known image extension
|
||||
"""
|
||||
filename_lower = filename.lower()
|
||||
return any(filename_lower.endswith(ext) for ext in extensions)
|
||||
|
||||
|
||||
def find_classes(dir):
|
||||
classes = [
|
||||
d for d in os.listdir(dir) if os.path.isdir(os.path.join(dir, d))
|
||||
]
|
||||
classes.sort()
|
||||
class_to_idx = {classes[i]: i for i in range(len(classes))}
|
||||
return classes, class_to_idx
|
||||
|
||||
|
||||
def make_dataset(dir, class_to_idx, extensions):
|
||||
images = []
|
||||
dir = os.path.expanduser(dir)
|
||||
for target in sorted(os.listdir(dir)):
|
||||
d = os.path.join(dir, target)
|
||||
if not os.path.isdir(d):
|
||||
continue
|
||||
for root, _, fnames in sorted(os.walk(d)):
|
||||
for fname in sorted(fnames):
|
||||
if has_file_allowed_extension(fname, extensions):
|
||||
path = os.path.join(root, fname)
|
||||
item = (path, class_to_idx[target])
|
||||
images.append(item)
|
||||
|
||||
return images
|
||||
|
||||
|
||||
def make_dataset_with_ann(ann_file, img_prefix, extensions):
|
||||
images = []
|
||||
with open(ann_file, "r") as f:
|
||||
contents = f.readlines()
|
||||
for line_str in contents:
|
||||
path_contents = [c for c in line_str.split('\t')]
|
||||
im_file_name = path_contents[0]
|
||||
class_index = int(path_contents[1])
|
||||
assert str.lower(os.path.splitext(im_file_name)[-1]) in extensions
|
||||
item = (os.path.join(img_prefix, im_file_name), class_index)
|
||||
images.append(item)
|
||||
|
||||
return images
|
||||
|
||||
|
||||
class DatasetFolder(data.Dataset):
|
||||
"""A generic data loader where the samples are arranged in this way: ::
|
||||
root/class_x/xxx.ext
|
||||
root/class_x/xxy.ext
|
||||
root/class_x/xxz.ext
|
||||
root/class_y/123.ext
|
||||
root/class_y/nsdf3.ext
|
||||
root/class_y/asd932_.ext
|
||||
Args:
|
||||
root (string): Root directory path.
|
||||
loader (callable): A function to load a sample given its path.
|
||||
extensions (list[string]): A list of allowed extensions.
|
||||
transform (callable, optional): A function/transform that takes in
|
||||
a sample and returns a transformed version.
|
||||
E.g, ``transforms.RandomCrop`` for images.
|
||||
target_transform (callable, optional): A function/transform that takes
|
||||
in the target and transforms it.
|
||||
Attributes:
|
||||
samples (list): List of (sample path, class_index) tuples
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
root,
|
||||
loader,
|
||||
extensions,
|
||||
ann_file='',
|
||||
img_prefix='',
|
||||
transform=None,
|
||||
target_transform=None,
|
||||
cache_mode="no"):
|
||||
# image folder mode
|
||||
if ann_file == '':
|
||||
_, class_to_idx = find_classes(root)
|
||||
samples = make_dataset(root, class_to_idx, extensions)
|
||||
# zip mode
|
||||
else:
|
||||
samples = make_dataset_with_ann(os.path.join(root, ann_file),
|
||||
os.path.join(root, img_prefix),
|
||||
extensions)
|
||||
|
||||
if len(samples) == 0:
|
||||
raise (RuntimeError("Found 0 files in subfolders of: " + root +
|
||||
"\n" + "Supported extensions are: " +
|
||||
",".join(extensions)))
|
||||
|
||||
self.root = root
|
||||
self.loader = loader
|
||||
self.extensions = extensions
|
||||
|
||||
self.samples = samples
|
||||
self.labels = [y_1k for _, y_1k in samples]
|
||||
self.classes = list(set(self.labels))
|
||||
|
||||
self.transform = transform
|
||||
self.target_transform = target_transform
|
||||
|
||||
self.cache_mode = cache_mode
|
||||
if self.cache_mode != "no":
|
||||
self.init_cache()
|
||||
|
||||
def init_cache(self):
|
||||
assert self.cache_mode in ["part", "full"]
|
||||
n_sample = len(self.samples)
|
||||
global_rank = dist.get_rank()
|
||||
world_size = dist.get_world_size()
|
||||
|
||||
samples_bytes = [None for _ in range(n_sample)]
|
||||
start_time = time.time()
|
||||
for index in range(n_sample):
|
||||
if index % (n_sample // 10) == 0:
|
||||
t = time.time() - start_time
|
||||
print(
|
||||
f'global_rank {dist.get_rank()} cached {index}/{n_sample} takes {t:.2f}s per block'
|
||||
)
|
||||
start_time = time.time()
|
||||
path, target = self.samples[index]
|
||||
if self.cache_mode == "full":
|
||||
samples_bytes[index] = (ZipReader.read(path), target)
|
||||
elif self.cache_mode == "part" and index % world_size == global_rank:
|
||||
samples_bytes[index] = (ZipReader.read(path), target)
|
||||
else:
|
||||
samples_bytes[index] = (path, target)
|
||||
self.samples = samples_bytes
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""
|
||||
Args:
|
||||
index (int): Index
|
||||
Returns:
|
||||
tuple: (sample, target) where target is class_index of the target class.
|
||||
"""
|
||||
path, target = self.samples[index]
|
||||
sample = self.loader(path)
|
||||
if self.transform is not None:
|
||||
sample = self.transform(sample)
|
||||
if self.target_transform is not None:
|
||||
target = self.target_transform(target)
|
||||
|
||||
return sample, target
|
||||
|
||||
def __len__(self):
|
||||
return len(self.samples)
|
||||
|
||||
def __repr__(self):
|
||||
fmt_str = 'Dataset ' + self.__class__.__name__ + '\n'
|
||||
fmt_str += ' Number of datapoints: {}\n'.format(self.__len__())
|
||||
fmt_str += ' Root Location: {}\n'.format(self.root)
|
||||
tmp = ' Transforms (if any): '
|
||||
fmt_str += '{0}{1}\n'.format(
|
||||
tmp,
|
||||
self.transform.__repr__().replace('\n', '\n' + ' ' * len(tmp)))
|
||||
tmp = ' Target Transforms (if any): '
|
||||
fmt_str += '{0}{1}'.format(
|
||||
tmp,
|
||||
self.target_transform.__repr__().replace('\n',
|
||||
'\n' + ' ' * len(tmp)))
|
||||
|
||||
return fmt_str
|
||||
|
||||
|
||||
IMG_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.ppm', '.bmp', '.pgm', '.tif']
|
||||
|
||||
|
||||
def pil_loader(path):
|
||||
# open path as file to avoid ResourceWarning (https://github.com/python-pillow/Pillow/issues/835)
|
||||
if isinstance(path, bytes):
|
||||
img = Image.open(io.BytesIO(path))
|
||||
elif is_zip_path(path):
|
||||
data = ZipReader.read(path)
|
||||
img = Image.open(io.BytesIO(data))
|
||||
else:
|
||||
with open(path, 'rb') as f:
|
||||
img = Image.open(f)
|
||||
return img.convert('RGB')
|
||||
|
||||
return img.convert('RGB')
|
||||
|
||||
|
||||
def accimage_loader(path):
|
||||
import accimage
|
||||
try:
|
||||
return accimage.Image(path)
|
||||
except IOError:
|
||||
# Potentially a decoding problem, fall back to PIL.Image
|
||||
return pil_loader(path)
|
||||
|
||||
|
||||
def default_img_loader(path):
|
||||
from torchvision import get_image_backend
|
||||
if get_image_backend() == 'accimage':
|
||||
return accimage_loader(path)
|
||||
else:
|
||||
return pil_loader(path)
|
||||
|
||||
|
||||
class CachedImageFolder(DatasetFolder):
|
||||
"""A generic data loader where the images are arranged in this way: ::
|
||||
root/dog/xxx.png
|
||||
root/dog/xxy.png
|
||||
root/dog/xxz.png
|
||||
root/cat/123.png
|
||||
root/cat/nsdf3.png
|
||||
root/cat/asd932_.png
|
||||
Args:
|
||||
root (string): Root directory path.
|
||||
transform (callable, optional): A function/transform that takes in an PIL image
|
||||
and returns a transformed version. E.g, ``transforms.RandomCrop``
|
||||
target_transform (callable, optional): A function/transform that takes in the
|
||||
target and transforms it.
|
||||
loader (callable, optional): A function to load an image given its path.
|
||||
Attributes:
|
||||
imgs (list): List of (image path, class_index) tuples
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
root,
|
||||
ann_file='',
|
||||
img_prefix='',
|
||||
transform=None,
|
||||
target_transform=None,
|
||||
loader=default_img_loader,
|
||||
cache_mode="no"):
|
||||
super(CachedImageFolder,
|
||||
self).__init__(root,
|
||||
loader,
|
||||
IMG_EXTENSIONS,
|
||||
ann_file=ann_file,
|
||||
img_prefix=img_prefix,
|
||||
transform=transform,
|
||||
target_transform=target_transform,
|
||||
cache_mode=cache_mode)
|
||||
self.imgs = self.samples
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""
|
||||
Args:
|
||||
index (int): Index
|
||||
Returns:
|
||||
tuple: (image, target) where target is class_index of the target class.
|
||||
"""
|
||||
path, target = self.samples[index]
|
||||
image = self.loader(path)
|
||||
if self.transform is not None:
|
||||
img = self.transform(image)
|
||||
else:
|
||||
img = image
|
||||
if self.target_transform is not None:
|
||||
target = self.target_transform(target)
|
||||
|
||||
return img, target
|
||||
|
||||
|
||||
class ImageCephDataset(data.Dataset):
|
||||
|
||||
def __init__(self,
|
||||
root,
|
||||
split,
|
||||
parser=None,
|
||||
transform=None,
|
||||
target_transform=None,
|
||||
on_memory=False):
|
||||
if '22k' in root:
|
||||
# Imagenet 22k
|
||||
annotation_root = 'meta/'
|
||||
else:
|
||||
# Imagenet
|
||||
annotation_root = 'meta/'
|
||||
if parser is None or isinstance(parser, str):
|
||||
parser = ParserCephImage(root=root,
|
||||
split=split,
|
||||
annotation_root=annotation_root,
|
||||
on_memory=on_memory)
|
||||
self.parser = parser
|
||||
self.transform = transform
|
||||
self.target_transform = target_transform
|
||||
self._consecutive_errors = 0
|
||||
|
||||
def __getitem__(self, index):
|
||||
img, target = self.parser[index]
|
||||
self._consecutive_errors = 0
|
||||
if self.transform is not None:
|
||||
img = self.transform(img)
|
||||
if target is None:
|
||||
target = -1
|
||||
elif self.target_transform is not None:
|
||||
target = self.target_transform(target)
|
||||
return img, target
|
||||
|
||||
def __len__(self):
|
||||
return len(self.parser)
|
||||
|
||||
def filename(self, index, basename=False, absolute=False):
|
||||
return self.parser.filename(index, basename, absolute)
|
||||
|
||||
def filenames(self, basename=False, absolute=False):
|
||||
return self.parser.filenames(basename, absolute)
|
||||
|
||||
|
||||
class Parser:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _filename(self, index, basename=False, absolute=False):
|
||||
pass
|
||||
|
||||
def filename(self, index, basename=False, absolute=False):
|
||||
return self._filename(index, basename=basename, absolute=absolute)
|
||||
|
||||
def filenames(self, basename=False, absolute=False):
|
||||
return [
|
||||
self._filename(index, basename=basename, absolute=absolute)
|
||||
for index in range(len(self))
|
||||
]
|
||||
|
||||
|
||||
class ParserCephImage(Parser):
|
||||
|
||||
def __init__(self,
|
||||
root,
|
||||
split,
|
||||
annotation_root,
|
||||
on_memory=False,
|
||||
**kwargs):
|
||||
super().__init__()
|
||||
|
||||
self.file_client = None
|
||||
self.kwargs = kwargs
|
||||
|
||||
self.root = root # dataset:s3://imagenet22k
|
||||
if '22k' in root:
|
||||
self.io_backend = 'petrel'
|
||||
with open(osp.join(annotation_root, '22k_class_to_idx.json'),
|
||||
'r') as f:
|
||||
self.class_to_idx = json.loads(f.read())
|
||||
with open(osp.join(annotation_root, '22k_label.txt'), 'r') as f:
|
||||
self.samples = f.read().splitlines()
|
||||
else:
|
||||
self.io_backend = 'disk'
|
||||
self.class_to_idx = None
|
||||
with open(osp.join(annotation_root, f'{split}.txt'), 'r') as f:
|
||||
self.samples = f.read().splitlines()
|
||||
local_rank = None
|
||||
local_size = None
|
||||
self._consecutive_errors = 0
|
||||
self.on_memory = on_memory
|
||||
if on_memory:
|
||||
self.holder = {}
|
||||
if local_rank is None:
|
||||
local_rank = int(os.environ.get('LOCAL_RANK', 0))
|
||||
if local_size is None:
|
||||
local_size = int(os.environ.get('LOCAL_SIZE', 1))
|
||||
self.local_rank = local_rank
|
||||
self.local_size = local_size
|
||||
self.rank = int(os.environ["RANK"])
|
||||
self.world_size = int(os.environ['WORLD_SIZE'])
|
||||
self.num_replicas = int(os.environ['WORLD_SIZE'])
|
||||
self.num_parts = local_size
|
||||
self.num_samples = int(
|
||||
math.ceil(len(self.samples) * 1.0 / self.num_replicas))
|
||||
self.total_size = self.num_samples * self.num_replicas
|
||||
self.total_size_parts = self.num_samples * self.num_replicas // self.num_parts
|
||||
self.load_onto_memory_v2()
|
||||
|
||||
def load_onto_memory(self):
|
||||
print("Loading images onto memory...", self.local_rank,
|
||||
self.local_size)
|
||||
if self.file_client is None:
|
||||
self.file_client = FileClient(self.io_backend, **self.kwargs)
|
||||
for index in trange(len(self.samples)):
|
||||
if index % self.local_size != self.local_rank:
|
||||
continue
|
||||
path, _ = self.samples[index].split(' ')
|
||||
path = osp.join(self.root, path)
|
||||
img_bytes = self.file_client.get(path)
|
||||
self.holder[path] = img_bytes
|
||||
|
||||
print("Loading complete!")
|
||||
|
||||
def load_onto_memory_v2(self):
|
||||
# print("Loading images onto memory...", self.local_rank, self.local_size)
|
||||
t = torch.Generator()
|
||||
t.manual_seed(0)
|
||||
indices = torch.randperm(len(self.samples), generator=t).tolist()
|
||||
# indices = range(len(self.samples))
|
||||
indices = [i for i in indices if i % self.num_parts == self.local_rank]
|
||||
# add extra samples to make it evenly divisible
|
||||
indices += indices[:(self.total_size_parts - len(indices))]
|
||||
assert len(indices) == self.total_size_parts
|
||||
|
||||
# subsample
|
||||
indices = indices[self.rank // self.num_parts:self.
|
||||
total_size_parts:self.num_replicas // self.num_parts]
|
||||
assert len(indices) == self.num_samples
|
||||
|
||||
if self.file_client is None:
|
||||
self.file_client = FileClient(self.io_backend, **self.kwargs)
|
||||
for index in tqdm(indices):
|
||||
if index % self.local_size != self.local_rank:
|
||||
continue
|
||||
path, _ = self.samples[index].split(' ')
|
||||
path = osp.join(self.root, path)
|
||||
img_bytes = self.file_client.get(path)
|
||||
|
||||
self.holder[path] = img_bytes
|
||||
|
||||
print("Loading complete!")
|
||||
|
||||
def __getitem__(self, index):
|
||||
if self.file_client is None:
|
||||
self.file_client = FileClient(self.io_backend, **self.kwargs)
|
||||
|
||||
filepath, target = self.samples[index].split(' ')
|
||||
filepath = osp.join(self.root, filepath)
|
||||
|
||||
try:
|
||||
if self.on_memory:
|
||||
img_bytes = self.holder[filepath]
|
||||
else:
|
||||
# pass
|
||||
img_bytes = self.file_client.get(filepath)
|
||||
img = mmcv.imfrombytes(img_bytes)[:, :, ::-1]
|
||||
except Exception as e:
|
||||
_logger.warning(
|
||||
f'Skipped sample (index {index}, file {filepath}). {str(e)}')
|
||||
self._consecutive_errors += 1
|
||||
if self._consecutive_errors < _ERROR_RETRY:
|
||||
return self.__getitem__((index + 1) % len(self))
|
||||
else:
|
||||
raise e
|
||||
self._consecutive_errors = 0
|
||||
|
||||
img = Image.fromarray(img)
|
||||
try:
|
||||
if self.class_to_idx is not None:
|
||||
target = self.class_to_idx[target]
|
||||
else:
|
||||
target = int(target)
|
||||
except:
|
||||
print('aaaaaaaaaaaa', filepath, target)
|
||||
exit()
|
||||
|
||||
return img, target
|
||||
|
||||
def __len__(self):
|
||||
return len(self.samples)
|
||||
|
||||
def _filename(self, index, basename=False, absolute=False):
|
||||
filename, _ = self.samples[index].split(' ')
|
||||
filename = osp.join(self.root, filename)
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def get_temporal_info(date, miss_hour=False):
|
||||
try:
|
||||
if date:
|
||||
if miss_hour:
|
||||
pattern = re.compile(r'(\d*)-(\d*)-(\d*)', re.I)
|
||||
else:
|
||||
pattern = re.compile(r'(\d*)-(\d*)-(\d*) (\d*):(\d*):(\d*)',
|
||||
re.I)
|
||||
m = pattern.match(date.strip())
|
||||
|
||||
if m:
|
||||
year = int(m.group(1))
|
||||
month = int(m.group(2))
|
||||
day = int(m.group(3))
|
||||
x_month = math.sin(2 * math.pi * month / 12)
|
||||
y_month = math.cos(2 * math.pi * month / 12)
|
||||
if miss_hour:
|
||||
x_hour = 0
|
||||
y_hour = 0
|
||||
else:
|
||||
hour = int(m.group(4))
|
||||
x_hour = math.sin(2 * math.pi * hour / 24)
|
||||
y_hour = math.cos(2 * math.pi * hour / 24)
|
||||
return [x_month, y_month, x_hour, y_hour]
|
||||
else:
|
||||
return [0, 0, 0, 0]
|
||||
else:
|
||||
return [0, 0, 0, 0]
|
||||
except:
|
||||
return [0, 0, 0, 0]
|
||||
|
||||
|
||||
def get_spatial_info(latitude, longitude):
|
||||
if latitude and longitude:
|
||||
latitude = math.radians(latitude)
|
||||
longitude = math.radians(longitude)
|
||||
x = math.cos(latitude) * math.cos(longitude)
|
||||
y = math.cos(latitude) * math.sin(longitude)
|
||||
z = math.sin(latitude)
|
||||
return [x, y, z]
|
||||
else:
|
||||
return [0, 0, 0]
|
||||
114
classification/dataset/samplers.py
Normal file
114
classification/dataset/samplers.py
Normal file
@@ -0,0 +1,114 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
import torch
|
||||
import os
|
||||
import math
|
||||
from torch.utils.data.sampler import Sampler
|
||||
import torch.distributed as dist
|
||||
import numpy as np
|
||||
|
||||
|
||||
class SubsetRandomSampler(torch.utils.data.Sampler):
|
||||
"""Samples elements randomly from a given list of indices, without replacement.
|
||||
|
||||
Arguments:
|
||||
indices (sequence): a sequence of indices
|
||||
"""
|
||||
|
||||
def __init__(self, indices):
|
||||
self.epoch = 0
|
||||
self.indices = indices
|
||||
|
||||
def __iter__(self):
|
||||
return (self.indices[i] for i in torch.randperm(len(self.indices)))
|
||||
|
||||
def __len__(self):
|
||||
return len(self.indices)
|
||||
|
||||
def set_epoch(self, epoch):
|
||||
self.epoch = epoch
|
||||
|
||||
|
||||
class NodeDistributedSampler(Sampler):
|
||||
"""Sampler that restricts data loading to a subset of the dataset.
|
||||
It is especially useful in conjunction with
|
||||
:class:`torch.nn.parallel.DistributedDataParallel`. In such case, each
|
||||
process can pass a DistributedSampler instance as a DataLoader sampler,
|
||||
and load a subset of the original dataset that is exclusive to it.
|
||||
.. note::
|
||||
Dataset is assumed to be of constant size.
|
||||
Arguments:
|
||||
dataset: Dataset used for sampling.
|
||||
num_replicas (optional): Number of processes participating in
|
||||
distributed training.
|
||||
rank (optional): Rank of the current process within num_replicas.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
dataset,
|
||||
num_replicas=None,
|
||||
rank=None,
|
||||
local_rank=None,
|
||||
local_size=None):
|
||||
if num_replicas is None:
|
||||
if not dist.is_available():
|
||||
raise RuntimeError(
|
||||
"Requires distributed package to be available")
|
||||
num_replicas = dist.get_world_size()
|
||||
if rank is None:
|
||||
if not dist.is_available():
|
||||
raise RuntimeError(
|
||||
"Requires distributed package to be available")
|
||||
rank = dist.get_rank()
|
||||
if local_rank is None:
|
||||
local_rank = int(os.environ.get('LOCAL_RANK', 0))
|
||||
if local_size is None:
|
||||
local_size = int(os.environ.get('LOCAL_SIZE', 1))
|
||||
self.dataset = dataset
|
||||
self.num_replicas = num_replicas
|
||||
self.num_parts = local_size
|
||||
self.rank = rank
|
||||
self.local_rank = local_rank
|
||||
self.epoch = 0
|
||||
self.num_samples = int(
|
||||
math.ceil(len(self.dataset) * 1.0 / self.num_replicas))
|
||||
self.total_size = self.num_samples * self.num_replicas
|
||||
|
||||
self.total_size_parts = self.num_samples * self.num_replicas // self.num_parts
|
||||
|
||||
def __iter__(self):
|
||||
# deterministically shuffle based on epoch
|
||||
g = torch.Generator()
|
||||
g.manual_seed(self.epoch)
|
||||
|
||||
t = torch.Generator()
|
||||
t.manual_seed(0)
|
||||
|
||||
indices = torch.randperm(len(self.dataset), generator=t).tolist()
|
||||
# indices = range(len(self.dataset))
|
||||
indices = [i for i in indices if i % self.num_parts == self.local_rank]
|
||||
|
||||
# add extra samples to make it evenly divisible
|
||||
indices += indices[:(self.total_size_parts - len(indices))]
|
||||
assert len(indices) == self.total_size_parts
|
||||
|
||||
# subsample
|
||||
indices = indices[self.rank // self.num_parts:self.
|
||||
total_size_parts:self.num_replicas // self.num_parts]
|
||||
|
||||
index = torch.randperm(len(indices), generator=g).tolist()
|
||||
indices = list(np.array(indices)[index])
|
||||
|
||||
assert len(indices) == self.num_samples
|
||||
|
||||
return iter(indices)
|
||||
|
||||
def __len__(self):
|
||||
return self.num_samples
|
||||
|
||||
def set_epoch(self, epoch):
|
||||
self.epoch = epoch
|
||||
102
classification/dataset/zipreader.py
Normal file
102
classification/dataset/zipreader.py
Normal file
@@ -0,0 +1,102 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
import os
|
||||
import zipfile
|
||||
import io
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from PIL import ImageFile
|
||||
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||
|
||||
|
||||
def is_zip_path(img_or_path):
|
||||
"""judge if this is a zip path"""
|
||||
return '.zip@' in img_or_path
|
||||
|
||||
|
||||
class ZipReader(object):
|
||||
"""A class to read zipped files"""
|
||||
zip_bank = dict()
|
||||
|
||||
def __init__(self):
|
||||
super(ZipReader, self).__init__()
|
||||
|
||||
@staticmethod
|
||||
def get_zipfile(path):
|
||||
zip_bank = ZipReader.zip_bank
|
||||
if path not in zip_bank:
|
||||
zfile = zipfile.ZipFile(path, 'r')
|
||||
zip_bank[path] = zfile
|
||||
return zip_bank[path]
|
||||
|
||||
@staticmethod
|
||||
def split_zip_style_path(path):
|
||||
pos_at = path.index('@')
|
||||
assert pos_at != -1, "character '@' is not found from the given path '%s'" % path
|
||||
|
||||
zip_path = path[0:pos_at]
|
||||
folder_path = path[pos_at + 1:]
|
||||
folder_path = str.strip(folder_path, '/')
|
||||
return zip_path, folder_path
|
||||
|
||||
@staticmethod
|
||||
def list_folder(path):
|
||||
zip_path, folder_path = ZipReader.split_zip_style_path(path)
|
||||
|
||||
zfile = ZipReader.get_zipfile(zip_path)
|
||||
folder_list = []
|
||||
for file_foler_name in zfile.namelist():
|
||||
file_foler_name = str.strip(file_foler_name, '/')
|
||||
if file_foler_name.startswith(folder_path) and \
|
||||
len(os.path.splitext(file_foler_name)[-1]) == 0 and \
|
||||
file_foler_name != folder_path:
|
||||
if len(folder_path) == 0:
|
||||
folder_list.append(file_foler_name)
|
||||
else:
|
||||
folder_list.append(file_foler_name[len(folder_path) + 1:])
|
||||
|
||||
return folder_list
|
||||
|
||||
@staticmethod
|
||||
def list_files(path, extension=None):
|
||||
if extension is None:
|
||||
extension = ['.*']
|
||||
zip_path, folder_path = ZipReader.split_zip_style_path(path)
|
||||
|
||||
zfile = ZipReader.get_zipfile(zip_path)
|
||||
file_lists = []
|
||||
for file_foler_name in zfile.namelist():
|
||||
file_foler_name = str.strip(file_foler_name, '/')
|
||||
if file_foler_name.startswith(folder_path) and \
|
||||
str.lower(os.path.splitext(file_foler_name)[-1]) in extension:
|
||||
if len(folder_path) == 0:
|
||||
file_lists.append(file_foler_name)
|
||||
else:
|
||||
file_lists.append(file_foler_name[len(folder_path) + 1:])
|
||||
|
||||
return file_lists
|
||||
|
||||
@staticmethod
|
||||
def read(path):
|
||||
zip_path, path_img = ZipReader.split_zip_style_path(path)
|
||||
zfile = ZipReader.get_zipfile(zip_path)
|
||||
data = zfile.read(path_img)
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def imread(path):
|
||||
zip_path, path_img = ZipReader.split_zip_style_path(path)
|
||||
zfile = ZipReader.get_zipfile(zip_path)
|
||||
data = zfile.read(path_img)
|
||||
try:
|
||||
im = Image.open(io.BytesIO(data))
|
||||
except:
|
||||
print("ERROR IMG LOADED: ", path_img)
|
||||
random_img = np.random.rand(224, 224, 3) * 255
|
||||
im = Image.fromarray(np.uint8(random_img))
|
||||
return im
|
||||
183
classification/ddp_hooks.py
Normal file
183
classification/ddp_hooks.py
Normal file
@@ -0,0 +1,183 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from typing import Any, Callable
|
||||
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
|
||||
|
||||
def _allreduce_fut(process_group: dist.ProcessGroup,
|
||||
tensor: torch.Tensor) -> torch.futures.Future[torch.Tensor]:
|
||||
"Averages the input gradient tensor by allreduce and returns a future."
|
||||
group_to_use = process_group if process_group is not None else dist.group.WORLD
|
||||
|
||||
# Apply the division first to avoid overflow, especially for FP16.
|
||||
tensor.div_(group_to_use.size())
|
||||
|
||||
return (dist.all_reduce(
|
||||
tensor, group=group_to_use,
|
||||
async_op=True).get_future().then(lambda fut: fut.value()[0]))
|
||||
|
||||
|
||||
def allreduce_hook(
|
||||
process_group: dist.ProcessGroup,
|
||||
bucket: dist.GradBucket) -> torch.futures.Future[torch.Tensor]:
|
||||
"""
|
||||
This DDP communication hook just calls ``allreduce`` using ``GradBucket``
|
||||
tensors. Once gradient tensors are aggregated across all workers, its ``then``
|
||||
callback takes the mean and returns the result. If user registers this hook,
|
||||
DDP results is expected to be same as the case where no hook was registered.
|
||||
Hence, this won't change behavior of DDP and user can use this as a reference
|
||||
or modify this hook to log useful information or any other purposes while
|
||||
unaffecting DDP behavior.
|
||||
|
||||
Example::
|
||||
>>> ddp_model.register_comm_hook(process_group, allreduce_hook)
|
||||
"""
|
||||
return _allreduce_fut(process_group, bucket.buffer())
|
||||
|
||||
|
||||
def fp16_compress_hook(
|
||||
process_group: dist.ProcessGroup,
|
||||
bucket: dist.GradBucket) -> torch.futures.Future[torch.Tensor]:
|
||||
"""
|
||||
This DDP communication hook implements a simple gradient compression
|
||||
approach that casts ``GradBucket`` tensor to half-precision floating-point format (``torch.float16``)
|
||||
and then divides it by the process group size.
|
||||
It allreduces those ``float16`` gradient tensors. Once compressed gradient
|
||||
tensors are allreduced, the chained callback ``decompress`` casts it back to the input data type (such as ``float32``).
|
||||
|
||||
Example::
|
||||
>>> ddp_model.register_comm_hook(process_group, fp16_compress_hook)
|
||||
"""
|
||||
group_to_use = process_group if process_group is not None else dist.group.WORLD
|
||||
world_size = group_to_use.size()
|
||||
|
||||
compressed_tensor = bucket.buffer().to(torch.float16).div_(world_size)
|
||||
|
||||
fut = dist.all_reduce(compressed_tensor, group=group_to_use,
|
||||
async_op=True).get_future()
|
||||
|
||||
def decompress(fut):
|
||||
decompressed_tensor = bucket.buffer()
|
||||
# Decompress in place to reduce the peak memory.
|
||||
# See: https://github.com/pytorch/pytorch/issues/45968
|
||||
decompressed_tensor.copy_(fut.value()[0])
|
||||
return decompressed_tensor
|
||||
|
||||
return fut.then(decompress)
|
||||
|
||||
|
||||
# TODO: create an internal helper function and extract the duplicate code in FP16_compress and BF16_compress.
|
||||
|
||||
|
||||
def bf16_compress_hook(
|
||||
process_group: dist.ProcessGroup,
|
||||
bucket: dist.GradBucket) -> torch.futures.Future[torch.Tensor]:
|
||||
"""
|
||||
Warning: This API is experimental, and it requires NCCL version later than 2.9.6.
|
||||
|
||||
This DDP communication hook implements a simple gradient compression
|
||||
approach that casts ``GradBucket`` tensor to half-precision
|
||||
`Brain floating point format <https://en.wikipedia.org/wiki/Bfloat16_floating-point_format>`_ (``torch.bfloat16``)
|
||||
and then divides it by the process group size.
|
||||
It allreduces those ``bfloat16`` gradient tensors. Once compressed gradient
|
||||
tensors are allreduced, the chained callback ``decompress`` casts it back to the input data type (such as ``float32``).
|
||||
|
||||
Example::
|
||||
>>> ddp_model.register_comm_hook(process_group, bf16_compress_hook)
|
||||
"""
|
||||
group_to_use = process_group if process_group is not None else dist.group.WORLD
|
||||
world_size = group_to_use.size()
|
||||
|
||||
compressed_tensor = bucket.buffer().to(torch.bfloat16).div_(world_size)
|
||||
|
||||
fut = dist.all_reduce(compressed_tensor, group=group_to_use,
|
||||
async_op=True).get_future()
|
||||
|
||||
def decompress(fut):
|
||||
decompressed_tensor = bucket.buffer()
|
||||
# Decompress in place to reduce the peak memory.
|
||||
# See: https://github.com/pytorch/pytorch/issues/45968
|
||||
decompressed_tensor.copy_(fut.value()[0])
|
||||
return decompressed_tensor
|
||||
|
||||
return fut.then(decompress)
|
||||
|
||||
|
||||
def fp16_compress_wrapper(
|
||||
hook: Callable[[Any, dist.GradBucket], torch.futures.Future[torch.Tensor]]
|
||||
) -> Callable[[Any, dist.GradBucket], torch.futures.Future[torch.Tensor]]:
|
||||
"""
|
||||
This wrapper casts the input gradient tensor of a given DDP communication hook to half-precision
|
||||
floating point format (``torch.float16``), and casts the resulting tensor of the given hook back to
|
||||
the input data type, such as ``float32``.
|
||||
|
||||
Therefore, ``fp16_compress_hook`` is equivalent to ``fp16_compress_wrapper(allreduce_hook)``.
|
||||
|
||||
Example::
|
||||
>>> state = PowerSGDState(process_group=process_group, matrix_approximation_rank=1, start_powerSGD_iter=10)
|
||||
>>> ddp_model.register_comm_hook(state, fp16_compress_wrapper(powerSGD_hook))
|
||||
"""
|
||||
|
||||
def fp16_compress_wrapper_hook(
|
||||
hook_state,
|
||||
bucket: dist.GradBucket) -> torch.futures.Future[torch.Tensor]:
|
||||
# Cast bucket tensor to FP16.
|
||||
bucket.set_buffer(bucket.buffer().to(torch.float16))
|
||||
|
||||
fut = hook(hook_state, bucket)
|
||||
|
||||
def decompress(fut):
|
||||
decompressed_tensor = bucket.buffer()
|
||||
# Decompress in place to reduce the peak memory.
|
||||
# See: https://github.com/pytorch/pytorch/issues/45968
|
||||
decompressed_tensor.copy_(fut.value())
|
||||
return decompressed_tensor
|
||||
|
||||
# Decompress after hook has run.
|
||||
return fut.then(decompress)
|
||||
|
||||
return fp16_compress_wrapper_hook
|
||||
|
||||
|
||||
def bf16_compress_wrapper(
|
||||
hook: Callable[[Any, dist.GradBucket], torch.futures.Future[torch.Tensor]]
|
||||
) -> Callable[[Any, dist.GradBucket], torch.futures.Future[torch.Tensor]]:
|
||||
"""
|
||||
Warning: This API is experimental, and it requires NCCL version later than 2.9.6.
|
||||
|
||||
This wrapper casts the input gradient tensor of a given DDP communication hook to half-precision
|
||||
`Brain floating point format <https://en.wikipedia.org/wiki/Bfloat16_floating-point_format> `_ (``torch.bfloat16``),
|
||||
and casts the resulting tensor of the given hook back to the input data type, such as ``float32``.
|
||||
|
||||
Therefore, ``bf16_compress_hook`` is equivalent to ``bf16_compress_wrapper(allreduce_hook)``.
|
||||
|
||||
Example::
|
||||
>>> state = PowerSGDState(process_group=process_group, matrix_approximation_rank=1, start_powerSGD_iter=10)
|
||||
>>> ddp_model.register_comm_hook(state, bf16_compress_wrapper(powerSGD_hook))
|
||||
"""
|
||||
|
||||
def bf16_compress_wrapper_hook(
|
||||
hook_state,
|
||||
bucket: dist.GradBucket) -> torch.futures.Future[torch.Tensor]:
|
||||
# Cast bucket tensor to BF16.
|
||||
bucket.set_buffer(bucket.buffer().to(torch.bfloat16))
|
||||
|
||||
fut = hook(hook_state, bucket)
|
||||
|
||||
def decompress(fut):
|
||||
decompressed_tensor = bucket.buffer()
|
||||
# Decompress in place to reduce the peak memory.
|
||||
# See: https://github.com/pytorch/pytorch/issues/45968
|
||||
decompressed_tensor.copy_(fut.value())
|
||||
return decompressed_tensor
|
||||
|
||||
# Decompress after hook has run.
|
||||
return fut.then(decompress)
|
||||
|
||||
return bf16_compress_wrapper_hook
|
||||
99
classification/ema_deepspeed.py
Normal file
99
classification/ema_deepspeed.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import deepspeed
|
||||
from deepspeed.runtime.zero import GatheredParameters
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
class EMADeepspeed(nn.Module):
|
||||
""" migrated from https://github.com/microsoft/DeepSpeed/issues/2056
|
||||
"""
|
||||
|
||||
def __init__(self, model, decay=0.9999, use_num_updates=True):
|
||||
super().__init__()
|
||||
if decay < 0.0 or decay > 1.0:
|
||||
raise ValueError('Decay must be between 0 and 1')
|
||||
|
||||
self.m_name2s_name = {}
|
||||
self.decay = decay
|
||||
self.num_updates = 0 if use_num_updates else -1
|
||||
|
||||
with GatheredParameters(model.parameters(), fwd_module=self):
|
||||
for name, p in model.named_parameters():
|
||||
if p.requires_grad:
|
||||
# remove as '.'-character is not allowed in buffers
|
||||
s_name = name.replace('.', '')
|
||||
self.m_name2s_name.update({name: s_name})
|
||||
self.register_buffer(s_name, p.clone().detach().data)
|
||||
# remove as '.'-character is not allowed in buffers
|
||||
self.collected_params = []
|
||||
|
||||
def forward(self, model):
|
||||
decay = self.decay
|
||||
|
||||
if self.num_updates >= 0:
|
||||
self.num_updates += 1
|
||||
decay = min(self.decay, (1 + self.num_updates) / (10 + self.num_updates))
|
||||
|
||||
one_minus_decay = 1.0 - decay
|
||||
shadow_params = dict(self.named_buffers())
|
||||
|
||||
with torch.no_grad():
|
||||
with GatheredParameters(model.parameters()):
|
||||
if deepspeed.comm.get_rank() == 0:
|
||||
m_param = dict(model.named_parameters())
|
||||
|
||||
for key in m_param:
|
||||
if m_param[key].requires_grad:
|
||||
sname = self.m_name2s_name[key]
|
||||
shadow_params[sname] = shadow_params[sname].type_as(m_param[key])
|
||||
shadow_params[sname].sub_(one_minus_decay * (shadow_params[sname] - m_param[key]))
|
||||
else:
|
||||
assert not key in self.m_name2s_name
|
||||
|
||||
def copy_to(self, model):
|
||||
shadow_params = dict(self.named_buffers())
|
||||
with GatheredParameters(model.parameters(), modifier_rank=0):
|
||||
if deepspeed.comm.get_rank() == 0:
|
||||
m_param = dict(model.named_parameters())
|
||||
for key in m_param:
|
||||
if m_param[key].requires_grad:
|
||||
m_param[key].data.copy_(shadow_params[self.m_name2s_name[key]].data)
|
||||
else:
|
||||
assert not key in self.m_name2s_name
|
||||
|
||||
def store(self, model):
|
||||
"""
|
||||
Save the current parameters for restoring later.
|
||||
Args:
|
||||
model: A model that parameters will be stored
|
||||
"""
|
||||
with GatheredParameters(model.parameters()):
|
||||
if deepspeed.comm.get_rank() == 0:
|
||||
parameters = model.parameters()
|
||||
self.collected_params = [param.clone() for param in parameters]
|
||||
|
||||
def restore(self, model):
|
||||
"""
|
||||
Restore the parameters stored with the `store` method.
|
||||
Useful to validate the model with EMA parameters without affecting the
|
||||
original optimization process. Store the parameters before the
|
||||
`copy_to` method. After validation (or model saving), use this to
|
||||
restore the former parameters.
|
||||
Args:
|
||||
model: A model that to restore its parameters.
|
||||
"""
|
||||
with GatheredParameters(model.parameters(), modifier_rank=0):
|
||||
if deepspeed.comm.get_rank() == 0:
|
||||
parameters = model.parameters()
|
||||
for c_param, param in zip(self.collected_params, parameters):
|
||||
param.data.copy_(c_param.data)
|
||||
|
||||
@contextmanager
|
||||
def activate(self, model):
|
||||
try:
|
||||
self.store(model)
|
||||
self.copy_to(model)
|
||||
yield
|
||||
finally:
|
||||
self.restore(model)
|
||||
2
classification/eval.sh
Normal file
2
classification/eval.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
python -m torch.distributed.launch --nproc_per_node 1 --master_port 12345 main.py --eval \
|
||||
--cfg configs/flash_intern_image_l_22k_384.yaml --data-path /path/to/imagenet1k
|
||||
122
classification/export.py
Normal file
122
classification/export.py
Normal file
@@ -0,0 +1,122 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
|
||||
import os
|
||||
import time
|
||||
import argparse
|
||||
|
||||
import torch
|
||||
from tqdm import tqdm
|
||||
|
||||
from config import get_config
|
||||
from models import build_model
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--model_name', type=str,
|
||||
default='internimage_t_1k_224')
|
||||
parser.add_argument('--ckpt_dir', type=str,
|
||||
default='/mnt/petrelfs/share_data/huangzhenhang/code/internimage/checkpoint_dir/new/cls')
|
||||
parser.add_argument('--onnx', default=False, action='store_true')
|
||||
parser.add_argument('--trt', default=False, action='store_true')
|
||||
|
||||
args = parser.parse_args()
|
||||
args.cfg = os.path.join('./configs', f'{args.model_name}.yaml')
|
||||
args.ckpt = os.path.join(args.ckpt_dir, f'{args.model_name}.pth')
|
||||
args.size = int(args.model_name.split('.')[0].split('_')[-1])
|
||||
|
||||
cfg = get_config(args)
|
||||
return args, cfg
|
||||
|
||||
def get_model(args, cfg):
|
||||
model = build_model(cfg)
|
||||
ckpt = torch.load(args.ckpt, map_location='cpu')['model']
|
||||
|
||||
model.load_state_dict(ckpt)
|
||||
return model
|
||||
|
||||
def speed_test(model, input):
|
||||
# warmup
|
||||
for _ in tqdm(range(100)):
|
||||
_ = model(input)
|
||||
|
||||
# speed test
|
||||
torch.cuda.synchronize()
|
||||
start = time.time()
|
||||
for _ in tqdm(range(100)):
|
||||
_ = model(input)
|
||||
end = time.time()
|
||||
th = 100 / (end - start)
|
||||
print(f"using time: {end - start}, throughput {th}")
|
||||
|
||||
def torch2onnx(args, cfg):
|
||||
model = get_model(args, cfg).cuda()
|
||||
|
||||
# speed_test(model)
|
||||
|
||||
onnx_name = f'{args.model_name}.onnx'
|
||||
torch.onnx.export(model,
|
||||
torch.rand(1, 3, args.size, args.size).cuda(),
|
||||
onnx_name,
|
||||
input_names=['input'],
|
||||
output_names=['output'])
|
||||
|
||||
return model
|
||||
|
||||
def onnx2trt(args):
|
||||
from mmdeploy.backend.tensorrt import from_onnx
|
||||
|
||||
onnx_name = f'{args.model_name}.onnx'
|
||||
from_onnx(
|
||||
onnx_name,
|
||||
args.model_name,
|
||||
dict(
|
||||
input=dict(
|
||||
min_shape=[1, 3, args.size, args.size],
|
||||
opt_shape=[1, 3, args.size, args.size],
|
||||
max_shape=[1, 3, args.size, args.size],
|
||||
)
|
||||
),
|
||||
max_workspace_size=2**30,
|
||||
)
|
||||
|
||||
def check(args, cfg):
|
||||
from mmdeploy.backend.tensorrt.wrapper import TRTWrapper
|
||||
|
||||
model = get_model(args, cfg).cuda()
|
||||
model.eval()
|
||||
trt_model = TRTWrapper(f'{args.model_name}.engine',
|
||||
['output'])
|
||||
|
||||
x = torch.randn(1, 3, args.size, args.size).cuda()
|
||||
|
||||
torch_out = model(x)
|
||||
trt_out = trt_model(dict(input=x))['output']
|
||||
|
||||
print('torch out shape:', torch_out.shape)
|
||||
print('trt out shape:', trt_out.shape)
|
||||
|
||||
print('max delta:', (torch_out - trt_out).abs().max())
|
||||
print('mean delta:', (torch_out - trt_out).abs().mean())
|
||||
|
||||
speed_test(model, x)
|
||||
speed_test(trt_model, dict(input=x))
|
||||
|
||||
def main():
|
||||
args, cfg = get_args()
|
||||
|
||||
if args.onnx or args.trt:
|
||||
torch2onnx(args, cfg)
|
||||
print('torch -> onnx: succeess')
|
||||
|
||||
if args.trt:
|
||||
onnx2trt(args)
|
||||
print('onnx -> trt: success')
|
||||
check(args, cfg)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
128
classification/extract_feature.py
Normal file
128
classification/extract_feature.py
Normal file
@@ -0,0 +1,128 @@
|
||||
import functools
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
# using wonder's beautiful simplification:
|
||||
# https://stackoverflow.com/questions/31174295/getattr-and-setattr-on-nested-objects/31174427?noredirect=1#comment86638618_31174427
|
||||
def rgetattr(obj, attr, *args):
|
||||
def _getattr(obj, attr):
|
||||
return getattr(obj, attr, *args)
|
||||
|
||||
return functools.reduce(_getattr, [obj] + attr.split('.'))
|
||||
|
||||
|
||||
class IntermediateLayerGetter:
|
||||
def __init__(self, model, return_layers, keep_output=True):
|
||||
"""Wraps a Pytorch module to get intermediate values
|
||||
|
||||
Arguments:
|
||||
model {nn.module} -- The Pytorch module to call
|
||||
return_layers {dict} -- Dictionary with the selected submodules
|
||||
to return the output (format: {[current_module_name]: [desired_output_name]},
|
||||
current_module_name can be a nested submodule, e.g. submodule1.submodule2.submodule3)
|
||||
|
||||
Keyword Arguments:
|
||||
keep_output {bool} -- If True model_output contains the final model's output
|
||||
in the other case model_output is None (default: {True})
|
||||
|
||||
Returns:
|
||||
(mid_outputs {OrderedDict}, model_output {any}) -- mid_outputs keys are
|
||||
your desired_output_name (s) and their values are the returned tensors
|
||||
of those submodules (OrderedDict([(desired_output_name,tensor(...)), ...).
|
||||
See keep_output argument for model_output description.
|
||||
In case a submodule is called more than one time, all it's outputs are
|
||||
stored in a list.
|
||||
"""
|
||||
self._model = model
|
||||
self.return_layers = return_layers
|
||||
self.keep_output = keep_output
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
ret = OrderedDict()
|
||||
handles = []
|
||||
for name, new_name in self.return_layers.items():
|
||||
layer = rgetattr(self._model, name)
|
||||
|
||||
def hook(module, input, output, new_name=new_name):
|
||||
if new_name in ret:
|
||||
if type(ret[new_name]) is list:
|
||||
ret[new_name].append(output)
|
||||
else:
|
||||
ret[new_name] = [ret[new_name], output]
|
||||
else:
|
||||
ret[new_name] = output
|
||||
|
||||
try:
|
||||
h = layer.register_forward_hook(hook)
|
||||
except AttributeError as e:
|
||||
raise AttributeError(f'Module {name} not found')
|
||||
handles.append(h)
|
||||
|
||||
if self.keep_output:
|
||||
output = self._model(*args, **kwargs)
|
||||
else:
|
||||
self._model(*args, **kwargs)
|
||||
output = None
|
||||
|
||||
for h in handles:
|
||||
h.remove()
|
||||
|
||||
return ret, output
|
||||
|
||||
|
||||
def main(args, config):
|
||||
from models import build_model
|
||||
import torchvision.transforms as T
|
||||
from PIL import Image
|
||||
|
||||
model = build_model(config)
|
||||
checkpoint = torch.load(config.MODEL.RESUME, map_location='cpu')
|
||||
model.load_state_dict(checkpoint['model'], strict=False)
|
||||
model.cuda()
|
||||
|
||||
# examples:
|
||||
# return_layers = {
|
||||
# 'patch_embed': 'patch_embed',
|
||||
# 'levels.0.downsample': 'levels.0.downsample',
|
||||
# 'levels.0.blocks.0.dcn': 'levels.0.blocks.0.dcn',
|
||||
# }
|
||||
return_layers = {k: k for k in args.keys}
|
||||
mid_getter = IntermediateLayerGetter(model, return_layers=return_layers, keep_output=True)
|
||||
|
||||
image = Image.open(args.img)
|
||||
|
||||
transforms = T.Compose([
|
||||
T.Resize(config.DATA.IMG_SIZE),
|
||||
T.ToTensor(),
|
||||
T.Normalize(config.AUG.MEAN, config.AUG.STD)
|
||||
])
|
||||
image = transforms(image)
|
||||
image = image.unsqueeze(0)
|
||||
image = image.cuda()
|
||||
|
||||
mid_outputs, model_output = mid_getter(image)
|
||||
|
||||
for k, v in mid_outputs.items():
|
||||
print(k, v.shape)
|
||||
|
||||
return mid_outputs, model_output
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
import torch
|
||||
from config import get_config
|
||||
|
||||
parser = argparse.ArgumentParser('Get Intermediate Layer Output')
|
||||
parser.add_argument('--cfg', type=str, required=True, metavar="FILE", help='Path to config file')
|
||||
parser.add_argument('--img', type=str, required=True, metavar="FILE", help='Path to img file')
|
||||
parser.add_argument("--keys", default=None, nargs='+', help="The intermediate layer's keys you want to save.")
|
||||
parser.add_argument('--resume', help='resume from checkpoint')
|
||||
parser.add_argument('--save', action='store_true', help='Save the results.')
|
||||
args = parser.parse_args()
|
||||
config = get_config(args)
|
||||
|
||||
mid_outputs, model_output = main(args, config)
|
||||
|
||||
if args.save:
|
||||
torch.save(mid_outputs, args.img[:-3] + '.pth')
|
||||
44
classification/logger.py
Normal file
44
classification/logger.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import functools
|
||||
from termcolor import colored
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
def create_logger(output_dir, dist_rank=0, name=''):
|
||||
# create logger
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.propagate = False
|
||||
|
||||
# create formatter
|
||||
fmt = '[%(asctime)s %(name)s] (%(filename)s %(lineno)d): %(levelname)s %(message)s'
|
||||
color_fmt = colored('[%(asctime)s %(name)s]', 'green') + \
|
||||
colored('(%(filename)s %(lineno)d)', 'yellow') + \
|
||||
': %(levelname)s %(message)s'
|
||||
|
||||
# create console handlers for master process
|
||||
if dist_rank == 0:
|
||||
console_handler = logging.StreamHandler(sys.stdout)
|
||||
console_handler.setLevel(logging.DEBUG)
|
||||
console_handler.setFormatter(
|
||||
logging.Formatter(fmt=color_fmt, datefmt='%Y-%m-%d %H:%M:%S'))
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
# create file handlers
|
||||
file_handler = logging.FileHandler(os.path.join(
|
||||
output_dir, f'log_rank{dist_rank}.txt'),
|
||||
mode='a')
|
||||
file_handler.setLevel(logging.DEBUG)
|
||||
file_handler.setFormatter(
|
||||
logging.Formatter(fmt=fmt, datefmt='%Y-%m-%d %H:%M:%S'))
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
return logger
|
||||
112
classification/lr_scheduler.py
Normal file
112
classification/lr_scheduler.py
Normal file
@@ -0,0 +1,112 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
|
||||
import torch
|
||||
from timm.scheduler.cosine_lr import CosineLRScheduler
|
||||
from timm.scheduler.step_lr import StepLRScheduler
|
||||
from timm.scheduler.scheduler import Scheduler
|
||||
|
||||
|
||||
def build_scheduler(config, optimizer, n_iter_per_epoch):
|
||||
num_steps = int(config.TRAIN.EPOCHS * n_iter_per_epoch)
|
||||
warmup_steps = int(config.TRAIN.WARMUP_EPOCHS * n_iter_per_epoch)
|
||||
decay_steps = int(config.TRAIN.LR_SCHEDULER.DECAY_EPOCHS *
|
||||
n_iter_per_epoch)
|
||||
|
||||
lr_scheduler = None
|
||||
if config.TRAIN.LR_SCHEDULER.NAME == 'cosine':
|
||||
lr_scheduler = CosineLRScheduler(
|
||||
optimizer,
|
||||
t_initial=num_steps,
|
||||
# t_mul=1.,
|
||||
lr_min=config.TRAIN.MIN_LR,
|
||||
warmup_lr_init=config.TRAIN.WARMUP_LR,
|
||||
warmup_t=warmup_steps,
|
||||
cycle_limit=1,
|
||||
t_in_epochs=False,
|
||||
)
|
||||
elif config.TRAIN.LR_SCHEDULER.NAME == 'linear':
|
||||
lr_scheduler = LinearLRScheduler(
|
||||
optimizer,
|
||||
t_initial=num_steps,
|
||||
lr_min_rate=0.01,
|
||||
warmup_lr_init=config.TRAIN.WARMUP_LR,
|
||||
warmup_t=warmup_steps,
|
||||
t_in_epochs=False,
|
||||
)
|
||||
elif config.TRAIN.LR_SCHEDULER.NAME == 'step':
|
||||
lr_scheduler = StepLRScheduler(
|
||||
optimizer,
|
||||
decay_t=decay_steps,
|
||||
decay_rate=config.TRAIN.LR_SCHEDULER.DECAY_RATE,
|
||||
warmup_lr_init=config.TRAIN.WARMUP_LR,
|
||||
warmup_t=warmup_steps,
|
||||
t_in_epochs=False,
|
||||
)
|
||||
|
||||
return lr_scheduler
|
||||
|
||||
|
||||
class LinearLRScheduler(Scheduler):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
optimizer: torch.optim.Optimizer,
|
||||
t_initial: int,
|
||||
lr_min_rate: float,
|
||||
warmup_t=0,
|
||||
warmup_lr_init=0.,
|
||||
t_in_epochs=True,
|
||||
noise_range_t=None,
|
||||
noise_pct=0.67,
|
||||
noise_std=1.0,
|
||||
noise_seed=42,
|
||||
initialize=True,
|
||||
) -> None:
|
||||
super().__init__(optimizer,
|
||||
param_group_field="lr",
|
||||
noise_range_t=noise_range_t,
|
||||
noise_pct=noise_pct,
|
||||
noise_std=noise_std,
|
||||
noise_seed=noise_seed,
|
||||
initialize=initialize)
|
||||
|
||||
self.t_initial = t_initial
|
||||
self.lr_min_rate = lr_min_rate
|
||||
self.warmup_t = warmup_t
|
||||
self.warmup_lr_init = warmup_lr_init
|
||||
self.t_in_epochs = t_in_epochs
|
||||
if self.warmup_t:
|
||||
self.warmup_steps = [(v - warmup_lr_init) / self.warmup_t
|
||||
for v in self.base_values]
|
||||
super().update_groups(self.warmup_lr_init)
|
||||
else:
|
||||
self.warmup_steps = [1 for _ in self.base_values]
|
||||
|
||||
def _get_lr(self, t):
|
||||
if t < self.warmup_t:
|
||||
lrs = [self.warmup_lr_init + t * s for s in self.warmup_steps]
|
||||
else:
|
||||
t = t - self.warmup_t
|
||||
total_t = self.t_initial - self.warmup_t
|
||||
lrs = [
|
||||
v - ((v - v * self.lr_min_rate) * (t / total_t))
|
||||
for v in self.base_values
|
||||
]
|
||||
return lrs
|
||||
|
||||
def get_epoch_values(self, epoch: int):
|
||||
if self.t_in_epochs:
|
||||
return self._get_lr(epoch)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_update_values(self, num_updates: int):
|
||||
if not self.t_in_epochs:
|
||||
return self._get_lr(num_updates)
|
||||
else:
|
||||
return None
|
||||
671
classification/main.py
Normal file
671
classification/main.py
Normal file
@@ -0,0 +1,671 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
import argparse
|
||||
import datetime
|
||||
import numpy as np
|
||||
import subprocess
|
||||
|
||||
import torch
|
||||
import torch.backends.cudnn as cudnn
|
||||
import torch.distributed as dist
|
||||
from timm.utils import ModelEma, ApexScaler
|
||||
from timm.loss import LabelSmoothingCrossEntropy, SoftTargetCrossEntropy
|
||||
from timm.utils import accuracy, AverageMeter
|
||||
|
||||
from config import get_config
|
||||
from models import build_model
|
||||
from dataset import build_loader
|
||||
from lr_scheduler import build_scheduler
|
||||
from optimizer import build_optimizer
|
||||
from logger import create_logger
|
||||
from utils import NativeScalerWithGradNormCount as NativeScaler
|
||||
from utils import (load_checkpoint, load_pretrained, save_checkpoint,
|
||||
get_grad_norm, auto_resume_helper, reduce_tensor,
|
||||
load_ema_checkpoint, MyAverageMeter)
|
||||
|
||||
from contextlib import suppress
|
||||
from ddp_hooks import fp16_compress_hook
|
||||
|
||||
try:
|
||||
from apex import amp
|
||||
has_apex = True
|
||||
except ImportError:
|
||||
has_apex = False
|
||||
# assert not has_apex, "The code is modified based on native amp"
|
||||
|
||||
has_native_amp = False
|
||||
try:
|
||||
if getattr(torch.cuda.amp, 'autocast') is not None:
|
||||
has_native_amp = True
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
TORCH_VERSION = tuple(int(x) for x in torch.__version__.split('.')[:2])
|
||||
|
||||
|
||||
def obsolete_torch_version(torch_version, version_threshold):
|
||||
return torch_version == 'parrots' or torch_version <= version_threshold
|
||||
|
||||
|
||||
def parse_option():
|
||||
parser = argparse.ArgumentParser(
|
||||
'InternImage training and evaluation script', add_help=False)
|
||||
parser.add_argument('--cfg',
|
||||
type=str,
|
||||
required=True,
|
||||
metavar="FILE",
|
||||
help='path to config file')
|
||||
parser.add_argument(
|
||||
"--opts",
|
||||
help="Modify config options by adding 'KEY VALUE' pairs. ",
|
||||
default=None,
|
||||
nargs='+')
|
||||
|
||||
# easy config modification
|
||||
parser.add_argument('--batch-size',
|
||||
type=int,
|
||||
help="batch size for single GPU")
|
||||
parser.add_argument('--dataset',
|
||||
type=str,
|
||||
help='dataset name',
|
||||
default=None)
|
||||
parser.add_argument('--data-path', type=str, help='path to dataset')
|
||||
parser.add_argument('--zip',
|
||||
action='store_true',
|
||||
help='use zipped dataset instead of folder dataset')
|
||||
parser.add_argument(
|
||||
'--cache-mode',
|
||||
type=str,
|
||||
default='part',
|
||||
choices=['no', 'full', 'part'],
|
||||
help='no: no cache, '
|
||||
'full: cache all data, '
|
||||
'part: sharding the dataset into nonoverlapping pieces and only cache one piece'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--pretrained',
|
||||
help=
|
||||
'pretrained weight from checkpoint, could be imagenet22k pretrained weight'
|
||||
)
|
||||
parser.add_argument('--resume', help='resume from checkpoint')
|
||||
parser.add_argument('--accumulation-steps',
|
||||
type=int,
|
||||
default=1,
|
||||
help="gradient accumulation steps")
|
||||
parser.add_argument(
|
||||
'--use-checkpoint',
|
||||
action='store_true',
|
||||
help="whether to use gradient checkpointing to save memory")
|
||||
parser.add_argument(
|
||||
'--amp-opt-level',
|
||||
type=str,
|
||||
default='O1',
|
||||
choices=['O0', 'O1', 'O2'],
|
||||
help='mixed precision opt level, if O0, no amp is used')
|
||||
parser.add_argument(
|
||||
'--output',
|
||||
default='output',
|
||||
type=str,
|
||||
metavar='PATH',
|
||||
help=
|
||||
'root of output folder, the full path is <output>/<model_name>/<tag> (default: output)'
|
||||
)
|
||||
parser.add_argument('--tag', help='tag of experiment')
|
||||
parser.add_argument('--eval',
|
||||
action='store_true',
|
||||
help='Perform evaluation only')
|
||||
parser.add_argument('--throughput',
|
||||
action='store_true',
|
||||
help='Test throughput only')
|
||||
parser.add_argument('--save-ckpt-num', default=1, type=int)
|
||||
parser.add_argument(
|
||||
'--use-zero',
|
||||
action='store_true',
|
||||
help="whether to use ZeroRedundancyOptimizer (ZeRO) to save memory")
|
||||
|
||||
# distributed training
|
||||
parser.add_argument("--local-rank",
|
||||
type=int,
|
||||
default=0,
|
||||
help='local rank for DistributedDataParallel')
|
||||
|
||||
args, unparsed = parser.parse_known_args()
|
||||
|
||||
if 'LOCAL_RANK' not in os.environ:
|
||||
os.environ['LOCAL_RANK'] = str(args.local_rank)
|
||||
|
||||
|
||||
config = get_config(args)
|
||||
|
||||
config.defrost()
|
||||
config.LOCAL_RANK = int(os.environ['LOCAL_RANK'])
|
||||
config.freeze()
|
||||
return args, config
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def throughput(data_loader, model, logger):
|
||||
model.eval()
|
||||
|
||||
for idx, (images, _) in enumerate(data_loader):
|
||||
images = images.cuda(non_blocking=True)
|
||||
batch_size = images.shape[0]
|
||||
for i in range(50):
|
||||
model(images)
|
||||
torch.cuda.synchronize()
|
||||
logger.info(f"throughput averaged with 30 times")
|
||||
tic1 = time.time()
|
||||
for i in range(30):
|
||||
model(images)
|
||||
torch.cuda.synchronize()
|
||||
tic2 = time.time()
|
||||
logger.info(
|
||||
f"batch_size {batch_size} throughput {30 * batch_size / (tic2 - tic1)}"
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
def main(config):
|
||||
# prepare data loaders
|
||||
dataset_train, dataset_val, dataset_test, data_loader_train, \
|
||||
data_loader_val, data_loader_test, mixup_fn = build_loader(config)
|
||||
|
||||
# build runner
|
||||
logger.info(f"Creating model:{config.MODEL.TYPE}/{config.MODEL.NAME}")
|
||||
model = build_model(config)
|
||||
model.cuda()
|
||||
logger.info(str(model))
|
||||
|
||||
# build optimizer
|
||||
optimizer = build_optimizer(config, model)
|
||||
|
||||
if config.AMP_OPT_LEVEL != "O0":
|
||||
config.defrost()
|
||||
if has_native_amp:
|
||||
config.native_amp = True
|
||||
use_amp = 'native'
|
||||
elif has_apex:
|
||||
config.apex_amp = True
|
||||
use_amp = 'apex'
|
||||
else:
|
||||
use_amp = None
|
||||
logger.warning(
|
||||
"Neither APEX or native Torch AMP is available, using float32. "
|
||||
"Install NVIDA apex or upgrade to PyTorch 1.6")
|
||||
config.freeze()
|
||||
|
||||
# setup automatic mixed-precision (AMP) loss scaling and op casting
|
||||
amp_autocast = suppress # do nothing
|
||||
loss_scaler = None
|
||||
if config.AMP_OPT_LEVEL != "O0":
|
||||
if use_amp == 'apex':
|
||||
model, optimizer = amp.initialize(model,
|
||||
optimizer,
|
||||
opt_level=config.AMP_OPT_LEVEL)
|
||||
loss_scaler = ApexScaler()
|
||||
if config.LOCAL_RANK == 0:
|
||||
logger.info(
|
||||
'Using NVIDIA APEX AMP. Training in mixed precision.')
|
||||
if use_amp == 'native':
|
||||
amp_autocast = torch.cuda.amp.autocast
|
||||
loss_scaler = NativeScaler()
|
||||
if config.LOCAL_RANK == 0:
|
||||
logger.info(
|
||||
'Using native Torch AMP. Training in mixed precision.')
|
||||
else:
|
||||
if config.LOCAL_RANK == 0:
|
||||
logger.info('AMP not enabled. Training in float32.')
|
||||
|
||||
# put model on gpus
|
||||
model = torch.nn.parallel.DistributedDataParallel(
|
||||
model, device_ids=[config.LOCAL_RANK], broadcast_buffers=False)
|
||||
|
||||
try:
|
||||
model.register_comm_hook(state=None, hook=fp16_compress_hook)
|
||||
logger.info('using fp16_compress_hook!')
|
||||
except:
|
||||
logger.info("cannot register fp16_compress_hook!")
|
||||
|
||||
model_without_ddp = model.module
|
||||
|
||||
n_parameters = sum(p.numel() for p in model.parameters()
|
||||
if p.requires_grad)
|
||||
logger.info(f"number of params: {n_parameters}")
|
||||
if hasattr(model_without_ddp, 'flops'):
|
||||
flops = model_without_ddp.flops()
|
||||
logger.info(f"number of GFLOPs: {flops / 1e9}")
|
||||
|
||||
# build learning rate scheduler
|
||||
lr_scheduler = build_scheduler(config, optimizer, len(data_loader_train)) \
|
||||
if not config.EVAL_MODE else None
|
||||
|
||||
# build criterion
|
||||
if config.AUG.MIXUP > 0.:
|
||||
# smoothing is handled with mixup label transform
|
||||
criterion = SoftTargetCrossEntropy()
|
||||
elif config.MODEL.LABEL_SMOOTHING > 0.:
|
||||
criterion = LabelSmoothingCrossEntropy(
|
||||
smoothing=config.MODEL.LABEL_SMOOTHING)
|
||||
else:
|
||||
criterion = torch.nn.CrossEntropyLoss()
|
||||
|
||||
max_accuracy = 0.0
|
||||
max_ema_accuracy = 0.0
|
||||
# set auto resume
|
||||
if config.MODEL.RESUME == '' and config.TRAIN.AUTO_RESUME:
|
||||
resume_file = auto_resume_helper(config.OUTPUT)
|
||||
if resume_file:
|
||||
if config.MODEL.RESUME:
|
||||
logger.warning(
|
||||
f"auto-resume changing resume file from {config.MODEL.RESUME} to {resume_file}"
|
||||
)
|
||||
config.defrost()
|
||||
config.MODEL.RESUME = resume_file
|
||||
config.freeze()
|
||||
logger.info(f'auto resuming from {resume_file}')
|
||||
else:
|
||||
logger.info(
|
||||
f'no checkpoint found in {config.OUTPUT}, ignoring auto resume'
|
||||
)
|
||||
|
||||
# set resume and pretrain
|
||||
if config.MODEL.RESUME:
|
||||
max_accuracy = load_checkpoint(config, model_without_ddp, optimizer,
|
||||
lr_scheduler, loss_scaler, logger)
|
||||
if data_loader_val is not None:
|
||||
acc1, acc5, loss = validate(config, data_loader_val, model, amp_autocast=amp_autocast)
|
||||
logger.info(
|
||||
f"Accuracy of the network on the {len(dataset_val)} test images: {acc1:.1f}%"
|
||||
)
|
||||
elif config.MODEL.PRETRAINED:
|
||||
load_pretrained(config, model_without_ddp, logger)
|
||||
if data_loader_val is not None:
|
||||
acc1, acc5, loss = validate(config, data_loader_val, model, amp_autocast=amp_autocast)
|
||||
logger.info(
|
||||
f"Accuracy of the network on the {len(dataset_val)} test images: {acc1:.1f}%"
|
||||
)
|
||||
|
||||
# evaluate EMA
|
||||
model_ema = None
|
||||
if config.TRAIN.EMA.ENABLE:
|
||||
# Important to create EMA model after cuda(), DP wrapper, and AMP but before SyncBN and DDP wrapper
|
||||
model_ema = ModelEma(model, decay=config.TRAIN.EMA.DECAY)
|
||||
print("Using EMA with decay = %.8f" % config.TRAIN.EMA.DECAY)
|
||||
if config.MODEL.RESUME:
|
||||
load_ema_checkpoint(config, model_ema, logger)
|
||||
acc1, acc5, loss = validate(config, data_loader_val, model_ema.ema, amp_autocast=amp_autocast)
|
||||
logger.info(
|
||||
f"Accuracy of the ema network on the {len(dataset_val)} test images: {acc1:.1f}%"
|
||||
)
|
||||
|
||||
if config.THROUGHPUT_MODE:
|
||||
throughput(data_loader_val, model, logger)
|
||||
|
||||
if config.EVAL_MODE:
|
||||
return
|
||||
|
||||
# train
|
||||
logger.info("Start training")
|
||||
start_time = time.time()
|
||||
for epoch in range(config.TRAIN.START_EPOCH, config.TRAIN.EPOCHS):
|
||||
data_loader_train.sampler.set_epoch(epoch)
|
||||
|
||||
train_one_epoch(config,
|
||||
model,
|
||||
criterion,
|
||||
data_loader_train,
|
||||
optimizer,
|
||||
epoch,
|
||||
mixup_fn,
|
||||
lr_scheduler,
|
||||
amp_autocast,
|
||||
loss_scaler,
|
||||
model_ema=model_ema)
|
||||
if (epoch % config.SAVE_FREQ == 0 or epoch == (config.TRAIN.EPOCHS - 1)) and \
|
||||
config.TRAIN.OPTIMIZER.USE_ZERO:
|
||||
optimizer.consolidate_state_dict(to=0)
|
||||
if dist.get_rank() == 0 and (epoch % config.SAVE_FREQ == 0
|
||||
or epoch == (config.TRAIN.EPOCHS - 1)):
|
||||
save_checkpoint(config,
|
||||
epoch,
|
||||
model_without_ddp,
|
||||
max_accuracy,
|
||||
optimizer,
|
||||
lr_scheduler,
|
||||
loss_scaler,
|
||||
logger,
|
||||
model_ema=model_ema)
|
||||
if data_loader_val is not None and epoch % config.EVAL_FREQ == 0:
|
||||
acc1, acc5, loss = validate(config, data_loader_val, model, epoch, amp_autocast)
|
||||
logger.info(
|
||||
f"Accuracy of the network on the {len(dataset_val)} test images: {acc1:.1f}%"
|
||||
)
|
||||
if dist.get_rank() == 0 and acc1 > max_accuracy:
|
||||
save_checkpoint(config,
|
||||
epoch,
|
||||
model_without_ddp,
|
||||
max_accuracy,
|
||||
optimizer,
|
||||
lr_scheduler,
|
||||
loss_scaler,
|
||||
logger,
|
||||
model_ema=model_ema,
|
||||
best='best')
|
||||
max_accuracy = max(max_accuracy, acc1)
|
||||
logger.info(f'Max accuracy: {max_accuracy:.2f}%')
|
||||
|
||||
if config.TRAIN.EMA.ENABLE:
|
||||
acc1, acc5, loss = validate(config, data_loader_val,
|
||||
model_ema.ema, epoch, amp_autocast)
|
||||
logger.info(
|
||||
f"Accuracy of the ema network on the {len(dataset_val)} test images: {acc1:.1f}%"
|
||||
)
|
||||
if dist.get_rank() == 0 and acc1 > max_ema_accuracy:
|
||||
save_checkpoint(config,
|
||||
epoch,
|
||||
model_without_ddp,
|
||||
max_accuracy,
|
||||
optimizer,
|
||||
lr_scheduler,
|
||||
loss_scaler,
|
||||
logger,
|
||||
model_ema=model_ema,
|
||||
best='ema_best')
|
||||
max_ema_accuracy = max(max_ema_accuracy, acc1)
|
||||
logger.info(f'Max ema accuracy: {max_ema_accuracy:.2f}%')
|
||||
|
||||
total_time = time.time() - start_time
|
||||
total_time_str = str(datetime.timedelta(seconds=int(total_time)))
|
||||
logger.info('Training time {}'.format(total_time_str))
|
||||
|
||||
|
||||
def train_one_epoch(config,
|
||||
model,
|
||||
criterion,
|
||||
data_loader,
|
||||
optimizer,
|
||||
epoch,
|
||||
mixup_fn,
|
||||
lr_scheduler,
|
||||
amp_autocast=suppress,
|
||||
loss_scaler=None,
|
||||
model_ema=None):
|
||||
model.train()
|
||||
optimizer.zero_grad()
|
||||
|
||||
num_steps = len(data_loader)
|
||||
batch_time = AverageMeter()
|
||||
model_time = AverageMeter()
|
||||
loss_meter = AverageMeter()
|
||||
norm_meter = MyAverageMeter(300)
|
||||
|
||||
start = time.time()
|
||||
end = time.time()
|
||||
|
||||
amp_type = torch.float16 if config.AMP_TYPE == 'float16' else torch.bfloat16
|
||||
for idx, (samples, targets) in enumerate(data_loader):
|
||||
iter_begin_time = time.time()
|
||||
samples = samples.cuda(non_blocking=True)
|
||||
targets = targets.cuda(non_blocking=True)
|
||||
|
||||
if mixup_fn is not None:
|
||||
samples, targets = mixup_fn(samples, targets)
|
||||
|
||||
if not obsolete_torch_version(TORCH_VERSION,
|
||||
(1, 9)) and config.AMP_OPT_LEVEL != "O0":
|
||||
with amp_autocast(dtype=amp_type):
|
||||
outputs = model(samples)
|
||||
else:
|
||||
with amp_autocast():
|
||||
outputs = model(samples)
|
||||
|
||||
if config.TRAIN.ACCUMULATION_STEPS > 1:
|
||||
if not obsolete_torch_version(
|
||||
TORCH_VERSION, (1, 9)) and config.AMP_OPT_LEVEL != "O0":
|
||||
with amp_autocast(dtype=amp_type):
|
||||
loss = criterion(outputs, targets)
|
||||
loss = loss / config.TRAIN.ACCUMULATION_STEPS
|
||||
else:
|
||||
with amp_autocast():
|
||||
loss = criterion(outputs, targets)
|
||||
loss = loss / config.TRAIN.ACCUMULATION_STEPS
|
||||
if config.AMP_OPT_LEVEL != "O0":
|
||||
is_second_order = hasattr(optimizer, 'is_second_order') and \
|
||||
optimizer.is_second_order
|
||||
grad_norm = loss_scaler(loss,
|
||||
optimizer,
|
||||
clip_grad=config.TRAIN.CLIP_GRAD,
|
||||
parameters=model.parameters(),
|
||||
create_graph=is_second_order,
|
||||
update_grad=(idx + 1) %
|
||||
config.TRAIN.ACCUMULATION_STEPS == 0)
|
||||
if (idx + 1) % config.TRAIN.ACCUMULATION_STEPS == 0:
|
||||
optimizer.zero_grad()
|
||||
if model_ema is not None:
|
||||
model_ema.update(model)
|
||||
else:
|
||||
loss.backward()
|
||||
if config.TRAIN.CLIP_GRAD:
|
||||
grad_norm = torch.nn.utils.clip_grad_norm_(
|
||||
model.parameters(), config.TRAIN.CLIP_GRAD)
|
||||
else:
|
||||
grad_norm = get_grad_norm(model.parameters())
|
||||
if (idx + 1) % config.TRAIN.ACCUMULATION_STEPS == 0:
|
||||
optimizer.step()
|
||||
optimizer.zero_grad()
|
||||
if model_ema is not None:
|
||||
model_ema.update(model)
|
||||
if (idx + 1) % config.TRAIN.ACCUMULATION_STEPS == 0:
|
||||
lr_scheduler.step_update(epoch * num_steps + idx)
|
||||
else:
|
||||
if not obsolete_torch_version(
|
||||
TORCH_VERSION, (1, 9)) and config.AMP_OPT_LEVEL != "O0":
|
||||
with amp_autocast(dtype=amp_type):
|
||||
loss = criterion(outputs, targets)
|
||||
else:
|
||||
with amp_autocast():
|
||||
loss = criterion(outputs, targets)
|
||||
optimizer.zero_grad()
|
||||
if config.AMP_OPT_LEVEL != "O0":
|
||||
is_second_order = hasattr(optimizer, 'is_second_order') and \
|
||||
optimizer.is_second_order
|
||||
grad_norm = loss_scaler(loss,
|
||||
optimizer,
|
||||
clip_grad=config.TRAIN.CLIP_GRAD,
|
||||
parameters=model.parameters(),
|
||||
create_graph=is_second_order,
|
||||
update_grad=(idx + 1) %
|
||||
config.TRAIN.ACCUMULATION_STEPS == 0)
|
||||
if model_ema is not None:
|
||||
model_ema.update(model)
|
||||
else:
|
||||
loss.backward()
|
||||
if config.TRAIN.CLIP_GRAD:
|
||||
grad_norm = torch.nn.utils.clip_grad_norm_(
|
||||
model.parameters(), config.TRAIN.CLIP_GRAD)
|
||||
else:
|
||||
grad_norm = get_grad_norm(model.parameters())
|
||||
optimizer.step()
|
||||
if model_ema is not None:
|
||||
model_ema.update(model)
|
||||
|
||||
lr_scheduler.step_update(epoch * num_steps + idx)
|
||||
|
||||
torch.cuda.synchronize()
|
||||
|
||||
loss_meter.update(loss.item(), targets.size(0))
|
||||
if grad_norm is not None:
|
||||
norm_meter.update(grad_norm.item())
|
||||
batch_time.update(time.time() - end)
|
||||
model_time.update(time.time() - iter_begin_time)
|
||||
end = time.time()
|
||||
|
||||
if idx % config.PRINT_FREQ == 0:
|
||||
lr = optimizer.param_groups[0]['lr']
|
||||
memory_used = torch.cuda.max_memory_allocated() / (1024.0 * 1024.0)
|
||||
etas = batch_time.avg * (num_steps - idx)
|
||||
logger.info(
|
||||
f'Train: [{epoch}/{config.TRAIN.EPOCHS}][{idx}/{num_steps}]\t'
|
||||
f'eta {datetime.timedelta(seconds=int(etas))} lr {lr:.6f}\t'
|
||||
f'time {batch_time.val:.4f} ({batch_time.avg:.4f})\t'
|
||||
f'model_time {model_time.val:.4f} ({model_time.avg:.4f})\t'
|
||||
f'loss {loss_meter.val:.4f} ({loss_meter.avg:.4f})\t'
|
||||
f'grad_norm {norm_meter.val:.4f} ({norm_meter.avg:.4f}/{norm_meter.var:.4f})\t'
|
||||
f'mem {memory_used:.0f}MB')
|
||||
epoch_time = time.time() - start
|
||||
logger.info(
|
||||
f"EPOCH {epoch} training takes {datetime.timedelta(seconds=int(epoch_time))}"
|
||||
)
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def validate(config, data_loader, model, epoch=None, amp_autocast=None):
|
||||
criterion = torch.nn.CrossEntropyLoss()
|
||||
model.eval()
|
||||
|
||||
batch_time = AverageMeter()
|
||||
loss_meter = AverageMeter()
|
||||
acc1_meter = AverageMeter()
|
||||
acc5_meter = AverageMeter()
|
||||
|
||||
end = time.time()
|
||||
for idx, (images, target) in enumerate(data_loader):
|
||||
images = images.cuda(non_blocking=True)
|
||||
target = target.cuda(non_blocking=True)
|
||||
with amp_autocast():
|
||||
output = model(images)
|
||||
|
||||
# convert 22k to 1k to evaluate
|
||||
if output.size(-1) == 21841:
|
||||
convert_file = './meta_data/map22kto1k.txt'
|
||||
with open(convert_file, 'r') as f:
|
||||
convert_list = [int(line) for line in f.readlines()]
|
||||
output = output[:, convert_list]
|
||||
|
||||
# measure accuracy and record loss
|
||||
loss = criterion(output, target)
|
||||
acc1, acc5 = accuracy(output, target, topk=(1, 5))
|
||||
|
||||
acc1 = reduce_tensor(acc1)
|
||||
acc5 = reduce_tensor(acc5)
|
||||
loss = reduce_tensor(loss)
|
||||
|
||||
loss_meter.update(loss.item(), target.size(0))
|
||||
acc1_meter.update(acc1.item(), target.size(0))
|
||||
acc5_meter.update(acc5.item(), target.size(0))
|
||||
|
||||
# measure elapsed time
|
||||
batch_time.update(time.time() - end)
|
||||
end = time.time()
|
||||
|
||||
if idx % config.PRINT_FREQ == 0:
|
||||
memory_used = torch.cuda.max_memory_allocated() / (1024.0 * 1024.0)
|
||||
logger.info(f'Test: [{idx}/{len(data_loader)}]\t'
|
||||
f'Time {batch_time.val:.3f} ({batch_time.avg:.3f})\t'
|
||||
f'Loss {loss_meter.val:.4f} ({loss_meter.avg:.4f})\t'
|
||||
f'Acc@1 {acc1_meter.val:.3f} ({acc1_meter.avg:.3f})\t'
|
||||
f'Acc@5 {acc5_meter.val:.3f} ({acc5_meter.avg:.3f})\t'
|
||||
f'Mem {memory_used:.0f}MB')
|
||||
if epoch is not None:
|
||||
logger.info(
|
||||
f'[Epoch:{epoch}] * Acc@1 {acc1_meter.avg:.3f} Acc@5 {acc5_meter.avg:.3f}'
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
f' * Acc@1 {acc1_meter.avg:.3f} Acc@5 {acc5_meter.avg:.3f}')
|
||||
|
||||
return acc1_meter.avg, acc5_meter.avg, loss_meter.avg
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
_, config = parse_option()
|
||||
|
||||
if config.AMP_OPT_LEVEL != "O0":
|
||||
assert has_native_amp, "Please update pytorch(1.6+) to support amp!"
|
||||
|
||||
# init distributed env
|
||||
if 'SLURM_PROCID' in os.environ and int(os.environ['SLURM_NNODES']) != 1:
|
||||
print("\nDist init: SLURM")
|
||||
rank = int(os.environ['SLURM_PROCID'])
|
||||
gpu = rank % torch.cuda.device_count()
|
||||
config.defrost()
|
||||
config.LOCAL_RANK = gpu
|
||||
config.freeze()
|
||||
|
||||
world_size = int(os.environ["SLURM_NTASKS"])
|
||||
if "MASTER_PORT" not in os.environ:
|
||||
os.environ["MASTER_PORT"] = "29501"
|
||||
node_list = os.environ["SLURM_NODELIST"]
|
||||
addr = subprocess.getoutput(
|
||||
f"scontrol show hostname {node_list} | head -n1")
|
||||
if "MASTER_ADDR" not in os.environ:
|
||||
os.environ["MASTER_ADDR"] = addr
|
||||
|
||||
os.environ['RANK'] = str(rank)
|
||||
os.environ['LOCAL_RANK'] = str(gpu)
|
||||
os.environ['LOCAL_SIZE'] = str(torch.cuda.device_count())
|
||||
os.environ['WORLD_SIZE'] = str(world_size)
|
||||
if 'RANK' in os.environ and 'WORLD_SIZE' in os.environ:
|
||||
rank = int(os.environ["RANK"])
|
||||
world_size = int(os.environ['WORLD_SIZE'])
|
||||
print(f"RANK and WORLD_SIZE in environ: {rank}/{world_size}")
|
||||
else:
|
||||
rank = -1
|
||||
world_size = -1
|
||||
torch.cuda.set_device(config.LOCAL_RANK)
|
||||
torch.distributed.init_process_group(backend='nccl',
|
||||
init_method='env://',
|
||||
world_size=world_size,
|
||||
rank=rank)
|
||||
torch.distributed.barrier()
|
||||
|
||||
seed = config.SEED + dist.get_rank()
|
||||
torch.manual_seed(seed)
|
||||
torch.cuda.manual_seed(seed)
|
||||
np.random.seed(seed)
|
||||
random.seed(seed)
|
||||
cudnn.benchmark = True
|
||||
|
||||
# linear scale the learning rate according to total batch size, may not be optimal
|
||||
linear_scaled_lr = config.TRAIN.BASE_LR * \
|
||||
config.DATA.BATCH_SIZE * dist.get_world_size() / 512.0
|
||||
linear_scaled_warmup_lr = config.TRAIN.WARMUP_LR * \
|
||||
config.DATA.BATCH_SIZE * dist.get_world_size() / 512.0
|
||||
linear_scaled_min_lr = config.TRAIN.MIN_LR * \
|
||||
config.DATA.BATCH_SIZE * dist.get_world_size() / 512.0
|
||||
# gradient accumulation also need to scale the learning rate
|
||||
if config.TRAIN.ACCUMULATION_STEPS > 1:
|
||||
linear_scaled_lr = linear_scaled_lr * config.TRAIN.ACCUMULATION_STEPS
|
||||
linear_scaled_warmup_lr = linear_scaled_warmup_lr * config.TRAIN.ACCUMULATION_STEPS
|
||||
linear_scaled_min_lr = linear_scaled_min_lr * config.TRAIN.ACCUMULATION_STEPS
|
||||
config.defrost()
|
||||
config.TRAIN.BASE_LR = linear_scaled_lr
|
||||
config.TRAIN.WARMUP_LR = linear_scaled_warmup_lr
|
||||
config.TRAIN.MIN_LR = linear_scaled_min_lr
|
||||
print(config.AMP_OPT_LEVEL, _.amp_opt_level)
|
||||
|
||||
config.freeze()
|
||||
|
||||
os.makedirs(config.OUTPUT, exist_ok=True)
|
||||
logger = create_logger(output_dir=config.OUTPUT,
|
||||
dist_rank=dist.get_rank(),
|
||||
name=f"{config.MODEL.NAME}")
|
||||
|
||||
if dist.get_rank() == 0:
|
||||
path = os.path.join(config.OUTPUT, "config.json")
|
||||
with open(path, "w") as f:
|
||||
f.write(config.dump())
|
||||
logger.info(f"Full config saved to {path}")
|
||||
|
||||
# print config
|
||||
logger.info(config.dump())
|
||||
|
||||
main(config)
|
||||
380
classification/main_accelerate.py
Normal file
380
classification/main_accelerate.py
Normal file
@@ -0,0 +1,380 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
|
||||
import datetime
|
||||
import argparse
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import random
|
||||
|
||||
import torch
|
||||
import torch.backends.cudnn as cudnn
|
||||
import numpy as np
|
||||
from accelerate import Accelerator
|
||||
from accelerate import GradScalerKwargs
|
||||
from accelerate.logging import get_logger
|
||||
from timm.loss import LabelSmoothingCrossEntropy, SoftTargetCrossEntropy
|
||||
from timm.utils import AverageMeter, accuracy, ModelEma
|
||||
from tqdm import tqdm
|
||||
import warnings
|
||||
|
||||
from config import get_config
|
||||
from models import build_model
|
||||
from dataset import build_loader2
|
||||
from lr_scheduler import build_scheduler
|
||||
from optimizer import build_optimizer
|
||||
from utils import load_pretrained, load_ema_checkpoint
|
||||
from ddp_hooks import fp16_compress_hook
|
||||
|
||||
logger = get_logger(__name__)
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
|
||||
def parse_option():
|
||||
parser = argparse.ArgumentParser(
|
||||
'InternImage training and evaluation script', add_help=False)
|
||||
parser.add_argument('--cfg', type=str, required=True, metavar="FILE", help='path to config file')
|
||||
parser.add_argument("--opts", help="Modify config options by adding 'KEY VALUE' pairs. ", default=None, nargs='+')
|
||||
|
||||
# easy config modification
|
||||
parser.add_argument('--batch-size', type=int, help="batch size for single GPU")
|
||||
parser.add_argument('--dataset', type=str, help='dataset name', default=None)
|
||||
parser.add_argument('--data-path', type=str, help='path to dataset')
|
||||
parser.add_argument('--zip', action='store_true', help='use zipped dataset instead of folder dataset')
|
||||
parser.add_argument('--cache-mode', type=str, default='part', choices=['no', 'full', 'part'],
|
||||
help='no: no cache, '
|
||||
'full: cache all data, '
|
||||
'part: sharding the dataset into nonoverlapping pieces and only cache one piece'
|
||||
)
|
||||
parser.add_argument('--pretrained', help='pretrained weight from checkpoint, could be imagenet22k pretrained weight')
|
||||
parser.add_argument('--resume', help='resume from checkpoint')
|
||||
parser.add_argument('--output', default='output', type=str, metavar='PATH',
|
||||
help='root of output folder, the full path is <output>/<model_name>/<tag> (default: output)'
|
||||
)
|
||||
parser.add_argument('--eval', action='store_true', help='Perform evaluation only')
|
||||
parser.add_argument('--throughput', action='store_true', help='Test throughput only')
|
||||
parser.add_argument('--save-ckpt-num', default=1, type=int)
|
||||
parser.add_argument('--accumulation-steps', type=int, default=1, help="gradient accumulation steps")
|
||||
parser.add_argument('--disable-grad-scalar', action='store_true', help='disable Grad Scalar')
|
||||
parser.add_argument(
|
||||
"--logger",
|
||||
type=str,
|
||||
default="tensorboard",
|
||||
choices=["tensorboard", "wandb"],
|
||||
help=(
|
||||
"Whether to use [tensorboard](https://www.tensorflow.org/tensorboard) or [wandb](https://www.wandb.ai)"
|
||||
" for experiment tracking and logging of model metrics and model checkpoints"
|
||||
),
|
||||
)
|
||||
|
||||
args, unparsed = parser.parse_known_args()
|
||||
config = get_config(args)
|
||||
config.defrost()
|
||||
config.TRAIN.OPTIMIZER.USE_ZERO = False
|
||||
config.OUTPUT += '_deepspeed'
|
||||
config.DATA.IMG_ON_MEMORY = False
|
||||
config.freeze()
|
||||
return args, config
|
||||
|
||||
|
||||
def seed_everything(seed, rank):
|
||||
seed = seed + rank
|
||||
torch.manual_seed(seed)
|
||||
torch.cuda.manual_seed(seed)
|
||||
np.random.seed(seed)
|
||||
random.seed(seed)
|
||||
cudnn.benchmark = True
|
||||
|
||||
|
||||
def save_config(config):
|
||||
path = os.path.join(config.OUTPUT, "config.json")
|
||||
with open(path, "w") as f:
|
||||
f.write(config.dump())
|
||||
logger.info(f"Full config saved to {path}")
|
||||
|
||||
|
||||
def build_criterion(config):
|
||||
if config.AUG.MIXUP > 0.:
|
||||
# smoothing is handled with mixup label transform
|
||||
criterion = SoftTargetCrossEntropy()
|
||||
elif config.MODEL.LABEL_SMOOTHING > 0.:
|
||||
criterion = LabelSmoothingCrossEntropy(
|
||||
smoothing=config.MODEL.LABEL_SMOOTHING)
|
||||
else:
|
||||
criterion = torch.nn.CrossEntropyLoss()
|
||||
return criterion
|
||||
|
||||
|
||||
def scale_learning_rate(config, num_processes):
|
||||
# linear scale the learning rate according to total batch size, may not be optimal
|
||||
linear_scaled_lr = config.TRAIN.BASE_LR * \
|
||||
config.DATA.BATCH_SIZE * num_processes / 512.0
|
||||
linear_scaled_warmup_lr = config.TRAIN.WARMUP_LR * \
|
||||
config.DATA.BATCH_SIZE * num_processes / 512.0
|
||||
linear_scaled_min_lr = config.TRAIN.MIN_LR * \
|
||||
config.DATA.BATCH_SIZE * num_processes / 512.0
|
||||
# gradient accumulation also need to scale the learning rate
|
||||
if config.TRAIN.ACCUMULATION_STEPS > 1:
|
||||
linear_scaled_lr = linear_scaled_lr * config.TRAIN.ACCUMULATION_STEPS
|
||||
linear_scaled_warmup_lr = linear_scaled_warmup_lr * config.TRAIN.ACCUMULATION_STEPS
|
||||
linear_scaled_min_lr = linear_scaled_min_lr * config.TRAIN.ACCUMULATION_STEPS
|
||||
config.defrost()
|
||||
config.TRAIN.BASE_LR = linear_scaled_lr
|
||||
config.TRAIN.WARMUP_LR = linear_scaled_warmup_lr
|
||||
config.TRAIN.MIN_LR = linear_scaled_min_lr
|
||||
config.freeze()
|
||||
|
||||
logger.info('BASE_LR={}'.format(config.TRAIN.BASE_LR))
|
||||
logger.info('WARMUP_LR={}'.format(config.TRAIN.WARMUP_LR))
|
||||
logger.info('MIN_LR={}'.format(config.TRAIN.MIN_LR))
|
||||
|
||||
|
||||
def setup_autoresume(config):
|
||||
if config.MODEL.RESUME == '' and config.TRAIN.AUTO_RESUME:
|
||||
last_checkpoint = os.path.join(config.OUTPUT, 'last')
|
||||
resume_file = last_checkpoint if os.path.exists(last_checkpoint) else None
|
||||
|
||||
if resume_file:
|
||||
if config.MODEL.RESUME:
|
||||
logger.warning(f"auto-resume changing resume file from {config.MODEL.RESUME} to {resume_file}")
|
||||
config.defrost()
|
||||
config.MODEL.RESUME = resume_file
|
||||
config.freeze()
|
||||
logger.info(f'auto resuming from {resume_file}')
|
||||
else:
|
||||
logger.info(f'no checkpoint found in {config.OUTPUT}, ignoring auto resume')
|
||||
|
||||
|
||||
def load_model_checkpoint(config, model, accelerator):
|
||||
if config.MODEL.RESUME:
|
||||
try:
|
||||
checkpoint = torch.load(config.MODEL.RESUME)['model']
|
||||
checkpoint = {k.replace('module.', ''): v for k, v in checkpoint.items()}
|
||||
model.load_state_dict(checkpoint)
|
||||
except:
|
||||
accelerator.load_state(config.MODEL.RESUME)
|
||||
elif config.MODEL.PRETRAINED:
|
||||
try:
|
||||
load_pretrained(config, model, logger)
|
||||
except:
|
||||
accelerator.load_state(config.MODEL.PRETRAINED)
|
||||
return model
|
||||
|
||||
|
||||
def save_checkpoint(save_dir, accelerator, epoch, max_acc, config, lr_scheduler=None):
|
||||
# let accelerator handle the model and optimizer state for ddp and deepspeed.
|
||||
accelerator.save_state(save_dir)
|
||||
|
||||
if accelerator.is_main_process:
|
||||
save_state = {
|
||||
'lr_scheduler': lr_scheduler.state_dict(),
|
||||
'max_acc': max_acc,
|
||||
'epoch': epoch,
|
||||
'config': config
|
||||
}
|
||||
torch.save(save_state, os.path.join(save_dir, 'additional_state.pth'))
|
||||
|
||||
|
||||
def load_checkpoint_if_needed(accelerator, config, lr_scheduler=None):
|
||||
setup_autoresume(config)
|
||||
save_dir = config.MODEL.RESUME
|
||||
if not save_dir:
|
||||
return 0.0
|
||||
accelerator.load_state(save_dir)
|
||||
checkpoint = torch.load(os.path.join(save_dir, 'additional_state.pth'), map_location='cpu')
|
||||
if lr_scheduler is not None:
|
||||
logger.info('resuming lr_scheduler')
|
||||
lr_scheduler.load_state_dict(checkpoint['lr_scheduler'])
|
||||
config.defrost()
|
||||
config.TRAIN.START_EPOCH = checkpoint['epoch'] + 1
|
||||
config.freeze()
|
||||
max_acc = checkpoint.get('max_acc', 0.0)
|
||||
logger.info(f"=> loaded successfully {config.MODEL.RESUME} (epoch {checkpoint['epoch']})")
|
||||
return max_acc
|
||||
|
||||
|
||||
def log_model_statistic(model_wo_ddp):
|
||||
n_parameters = sum(p.numel() for p in model_wo_ddp.parameters()
|
||||
if p.requires_grad)
|
||||
logger.info(f"number of params: {n_parameters}")
|
||||
if hasattr(model_wo_ddp, 'flops'):
|
||||
flops = model_wo_ddp.flops()
|
||||
logger.info(f"number of GFLOPs: {flops / 1e9}")
|
||||
|
||||
|
||||
def train_epoch(*, model, optimizer, data_loader, scheduler, criterion, mixup_fn,
|
||||
accelerator: Accelerator, epoch, config):
|
||||
model.train()
|
||||
|
||||
num_steps = len(data_loader)
|
||||
batch_time = AverageMeter()
|
||||
model_time = AverageMeter()
|
||||
loss_meter = AverageMeter()
|
||||
|
||||
end = time.time()
|
||||
|
||||
gradient_accumulation_steps = config.TRAIN.ACCUMULATION_STEPS
|
||||
|
||||
for step, (samples, targets) in enumerate(data_loader):
|
||||
iter_begin_time = time.time()
|
||||
|
||||
if mixup_fn is not None:
|
||||
samples, targets = mixup_fn(samples, targets)
|
||||
|
||||
with accelerator.accumulate(model):
|
||||
outputs = model(samples)
|
||||
loss = criterion(outputs, targets)
|
||||
accelerator.backward(loss)
|
||||
if accelerator.sync_gradients:
|
||||
accelerator.clip_grad_norm_(model.parameters(), config.TRAIN.CLIP_GRAD)
|
||||
optimizer.step()
|
||||
optimizer.zero_grad()
|
||||
|
||||
accelerator.wait_for_everyone()
|
||||
|
||||
if (step + 1) % gradient_accumulation_steps == 0:
|
||||
if scheduler is not None:
|
||||
scheduler.step_update((epoch * num_steps + step) // gradient_accumulation_steps)
|
||||
|
||||
batch_time.update(time.time() - end)
|
||||
model_time.update(time.time() - iter_begin_time)
|
||||
loss_meter.update(loss.item())
|
||||
end = time.time()
|
||||
|
||||
if accelerator.is_main_process and step % config.PRINT_FREQ == 0:
|
||||
lr = optimizer.param_groups[0]['lr']
|
||||
memory_used = torch.cuda.max_memory_allocated() / (1024.0 * 1024.0)
|
||||
etas = batch_time.avg * (num_steps - step)
|
||||
|
||||
logger.info(
|
||||
f'Train: [{epoch}/{config.TRAIN.EPOCHS}][{step}/{num_steps}]\t'
|
||||
f'eta {datetime.timedelta(seconds=int(etas))} lr {lr:.10f}\t'
|
||||
f'time {batch_time.val:.4f} ({batch_time.avg:.4f})\t'
|
||||
f'model_time {model_time.val:.4f} ({model_time.avg:.4f})\t'
|
||||
f'loss {loss_meter.val:.8f} ({loss_meter.avg:.4f})\t'
|
||||
f'mem {memory_used:.0f}MB')
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def eval_epoch(*, config, data_loader, model, accelerator: Accelerator):
|
||||
model.eval()
|
||||
|
||||
acc1_meter = AverageMeter()
|
||||
acc5_meter = AverageMeter()
|
||||
|
||||
for idx, (images, target) in enumerate(tqdm(data_loader, disable=accelerator.is_main_process)):
|
||||
output = model(images)
|
||||
|
||||
# convert 22k to 1k to evaluate
|
||||
if output.size(-1) == 21841:
|
||||
convert_file = './meta_data/map22kto1k.txt'
|
||||
with open(convert_file, 'r') as f:
|
||||
convert_list = [int(line) for line in f.readlines()]
|
||||
output = output[:, convert_list]
|
||||
|
||||
acc1, acc5 = accuracy(output, target, topk=(1, 5))
|
||||
acc1 = accelerator.gather(acc1).mean(0)
|
||||
acc5 = accelerator.gather(acc5).mean(0)
|
||||
|
||||
acc1_meter.update(acc1.item(), target.size(0))
|
||||
acc5_meter.update(acc5.item(), target.size(0))
|
||||
|
||||
if (idx + 1) % config.PRINT_FREQ == 0 or idx + 1 == len(data_loader):
|
||||
logger.info(f'Test: [{idx+1}/{len(data_loader)}]\t'
|
||||
f'Acc@1 {acc1_meter.val:.3f} ({acc1_meter.avg:.3f})\t'
|
||||
f'Acc@5 {acc5_meter.val:.3f} ({acc5_meter.avg:.3f})\t'
|
||||
)
|
||||
return acc1_meter.avg
|
||||
|
||||
|
||||
def eval(config, accelerator: Accelerator):
|
||||
_, _, _, _, validate_dataloader, _, _ = build_loader2(config)
|
||||
model = build_model(config)
|
||||
model, validate_dataloader = accelerator.prepare(model, validate_dataloader)
|
||||
model = load_model_checkpoint(config, model, accelerator)
|
||||
log_model_statistic(accelerator.unwrap_model(model))
|
||||
eval_epoch(config=config, data_loader=validate_dataloader, model=model, accelerator=accelerator)
|
||||
|
||||
|
||||
def train(config, accelerator: Accelerator):
|
||||
_, _, _, training_dataloader, validate_dataloader, _, mixup_fn = build_loader2(config)
|
||||
model = build_model(config)
|
||||
optimizer = build_optimizer(config, model)
|
||||
criterion = build_criterion(config)
|
||||
|
||||
model, optimizer, training_dataloader, validate_dataloader = accelerator.prepare(
|
||||
model, optimizer, training_dataloader, validate_dataloader)
|
||||
|
||||
effective_update_steps_per_epoch = len(training_dataloader) // config.TRAIN.ACCUMULATION_STEPS
|
||||
lr_scheduler = build_scheduler(config, optimizer, effective_update_steps_per_epoch)
|
||||
|
||||
try:
|
||||
model.register_comm_hook(state=None, hook=fp16_compress_hook)
|
||||
logger.info('using fp16_compress_hook!')
|
||||
except:
|
||||
logger.info("cannot register fp16_compress_hook!")
|
||||
|
||||
max_acc = load_checkpoint_if_needed(accelerator, config, lr_scheduler)
|
||||
|
||||
logger.info(f"Created model:{config.MODEL.TYPE}/{config.MODEL.NAME}")
|
||||
logger.info(str(model))
|
||||
logger.info("Effective Optimizer Steps: {}".format(effective_update_steps_per_epoch))
|
||||
logger.info("Start training")
|
||||
logger.info("Max accuracy: {}".format(max_acc))
|
||||
log_model_statistic(accelerator.unwrap_model(model))
|
||||
|
||||
for epoch in range(config.TRAIN.START_EPOCH, config.TRAIN.EPOCHS):
|
||||
train_epoch(model=model, optimizer=optimizer, data_loader=training_dataloader,
|
||||
scheduler=lr_scheduler, criterion=criterion, mixup_fn=mixup_fn,
|
||||
accelerator=accelerator, epoch=epoch, config=config)
|
||||
acc = eval_epoch(config=config, data_loader=validate_dataloader, model=model,
|
||||
accelerator=accelerator)
|
||||
|
||||
accelerator.wait_for_everyone()
|
||||
if acc > max_acc:
|
||||
max_acc = acc
|
||||
save_checkpoint(os.path.join(config.OUTPUT, 'best'), accelerator, epoch, max_acc, config, lr_scheduler)
|
||||
logger.info(f'Max Acc@1 {max_acc:.3f}')
|
||||
save_checkpoint(os.path.join(config.OUTPUT, 'last'), accelerator, epoch, max_acc, config, lr_scheduler)
|
||||
|
||||
|
||||
def main():
|
||||
args, config = parse_option()
|
||||
os.makedirs(config.OUTPUT, exist_ok=True)
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
|
||||
datefmt="%m/%d/%Y %H:%M:%S",
|
||||
filename=os.path.join(config.OUTPUT, 'run.log'),
|
||||
level=logging.INFO,
|
||||
)
|
||||
|
||||
loggers = ['tensorboard']
|
||||
accelerator = Accelerator(
|
||||
log_with=loggers,
|
||||
project_dir=config.OUTPUT,
|
||||
gradient_accumulation_steps=config.TRAIN.ACCUMULATION_STEPS,
|
||||
# When use deepspeed, you could not comment this out
|
||||
# even if you set loss scale to 1.0 in deepspeed config.
|
||||
kwargs_handlers=[GradScalerKwargs(enabled=not args.disable_grad_scalar)],
|
||||
)
|
||||
logger.info(accelerator.state, main_process_only=False)
|
||||
|
||||
scale_learning_rate(config, accelerator.num_processes)
|
||||
seed_everything(config.SEED, accelerator.process_index)
|
||||
save_config(config)
|
||||
|
||||
logger.info(config.dump())
|
||||
|
||||
if config.EVAL_MODE:
|
||||
eval(config, accelerator)
|
||||
else:
|
||||
train(config, accelerator)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
531
classification/main_deepspeed.py
Normal file
531
classification/main_deepspeed.py
Normal file
@@ -0,0 +1,531 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
import argparse
|
||||
import datetime
|
||||
import numpy as np
|
||||
import subprocess
|
||||
|
||||
import torch
|
||||
import torch.backends.cudnn as cudnn
|
||||
import torch.distributed as dist
|
||||
import deepspeed
|
||||
from timm.loss import LabelSmoothingCrossEntropy, SoftTargetCrossEntropy
|
||||
from timm.utils import accuracy, AverageMeter
|
||||
|
||||
from config import get_config
|
||||
from models import build_model
|
||||
from dataset import build_loader
|
||||
from lr_scheduler import build_scheduler
|
||||
from optimizer import set_weight_decay_and_lr
|
||||
from logger import create_logger
|
||||
from utils import load_pretrained, reduce_tensor, MyAverageMeter
|
||||
from ddp_hooks import fp16_compress_hook
|
||||
from ema_deepspeed import EMADeepspeed
|
||||
|
||||
def parse_option():
|
||||
parser = argparse.ArgumentParser(
|
||||
'InternImage training and evaluation script', add_help=False)
|
||||
parser.add_argument('--cfg', type=str, required=True, metavar="FILE", help='path to config file')
|
||||
parser.add_argument("--opts", help="Modify config options by adding 'KEY VALUE' pairs. ", default=None, nargs='+')
|
||||
|
||||
# easy config modification
|
||||
parser.add_argument('--batch-size', type=int, help="batch size for single GPU")
|
||||
parser.add_argument('--dataset', type=str, help='dataset name', default=None)
|
||||
parser.add_argument('--data-path', type=str, help='path to dataset')
|
||||
parser.add_argument('--zip', action='store_true', help='use zipped dataset instead of folder dataset')
|
||||
parser.add_argument('--cache-mode', type=str, default='part', choices=['no', 'full', 'part'],
|
||||
help='no: no cache, '
|
||||
'full: cache all data, '
|
||||
'part: sharding the dataset into nonoverlapping pieces and only cache one piece'
|
||||
)
|
||||
parser.add_argument('--pretrained', help='pretrained weight from checkpoint, could be imagenet22k pretrained weight')
|
||||
parser.add_argument('--resume', help='resume from checkpoint')
|
||||
parser.add_argument('--output', default='output', type=str, metavar='PATH',
|
||||
help='root of output folder, the full path is <output>/<model_name>/<tag> (default: output)'
|
||||
)
|
||||
|
||||
parser.add_argument('--eval', action='store_true', help='Perform evaluation only')
|
||||
parser.add_argument('--throughput', action='store_true', help='Test throughput only')
|
||||
parser.add_argument('--save-ckpt-num', default=1, type=int)
|
||||
parser.add_argument('--accumulation-steps', type=int, default=1, help="gradient accumulation steps")
|
||||
|
||||
# distributed training
|
||||
parser.add_argument("--local-rank", type=int, required=True, help='local rank for DistributedDataParallel')
|
||||
parser.add_argument('--disable-grad-scalar', action='store_true', help='disable Grad Scalar')
|
||||
|
||||
args, unparsed = parser.parse_known_args()
|
||||
config = get_config(args)
|
||||
|
||||
return args, config
|
||||
|
||||
|
||||
def seed_everything(seed, rank):
|
||||
seed = seed + rank
|
||||
torch.manual_seed(seed)
|
||||
torch.cuda.manual_seed(seed)
|
||||
np.random.seed(seed)
|
||||
random.seed(seed)
|
||||
cudnn.benchmark = True
|
||||
|
||||
|
||||
def save_config(config):
|
||||
path = os.path.join(config.OUTPUT, "config.json")
|
||||
with open(path, "w") as f:
|
||||
f.write(config.dump())
|
||||
logger.info(f"Full config saved to {path}")
|
||||
|
||||
|
||||
def build_criterion(config):
|
||||
if config.AUG.MIXUP > 0.:
|
||||
# smoothing is handled with mixup label transform
|
||||
criterion = SoftTargetCrossEntropy()
|
||||
elif config.MODEL.LABEL_SMOOTHING > 0.:
|
||||
criterion = LabelSmoothingCrossEntropy(
|
||||
smoothing=config.MODEL.LABEL_SMOOTHING)
|
||||
else:
|
||||
criterion = torch.nn.CrossEntropyLoss()
|
||||
return criterion
|
||||
|
||||
|
||||
def scale_learning_rate(config, num_processes):
|
||||
# linear scale the learning rate according to total batch size, may not be optimal
|
||||
linear_scaled_lr = config.TRAIN.BASE_LR * \
|
||||
config.DATA.BATCH_SIZE * num_processes / 512.0
|
||||
linear_scaled_warmup_lr = config.TRAIN.WARMUP_LR * \
|
||||
config.DATA.BATCH_SIZE * num_processes / 512.0
|
||||
linear_scaled_min_lr = config.TRAIN.MIN_LR * \
|
||||
config.DATA.BATCH_SIZE * num_processes / 512.0
|
||||
# gradient accumulation also need to scale the learning rate
|
||||
if config.TRAIN.ACCUMULATION_STEPS > 1:
|
||||
linear_scaled_lr = linear_scaled_lr * config.TRAIN.ACCUMULATION_STEPS
|
||||
linear_scaled_warmup_lr = linear_scaled_warmup_lr * config.TRAIN.ACCUMULATION_STEPS
|
||||
linear_scaled_min_lr = linear_scaled_min_lr * config.TRAIN.ACCUMULATION_STEPS
|
||||
config.defrost()
|
||||
config.TRAIN.BASE_LR = linear_scaled_lr
|
||||
config.TRAIN.WARMUP_LR = linear_scaled_warmup_lr
|
||||
config.TRAIN.MIN_LR = linear_scaled_min_lr
|
||||
config.freeze()
|
||||
|
||||
logger.info('BASE_LR={}'.format(config.TRAIN.BASE_LR))
|
||||
logger.info('WARMUP_LR={}'.format(config.TRAIN.WARMUP_LR))
|
||||
logger.info('MIN_LR={}'.format(config.TRAIN.MIN_LR))
|
||||
|
||||
|
||||
def log_model_statistic(model_wo_ddp):
|
||||
n_parameters = sum(p.numel() for p in model_wo_ddp.parameters()
|
||||
if p.requires_grad)
|
||||
logger.info(f"number of params: {n_parameters/1e6} M")
|
||||
if hasattr(model_wo_ddp, 'flops'):
|
||||
flops = model_wo_ddp.flops()
|
||||
logger.info(f"number of GFLOPs: {flops / 1e9}")
|
||||
|
||||
|
||||
def get_parameter_groups(model, config):
|
||||
skip = {}
|
||||
skip_keywords = {}
|
||||
if hasattr(model, 'no_weight_decay'):
|
||||
skip = model.no_weight_decay()
|
||||
if hasattr(model, 'no_weight_decay_keywords'):
|
||||
skip_keywords = model.no_weight_decay_keywords()
|
||||
|
||||
parameters = set_weight_decay_and_lr(
|
||||
model,
|
||||
config.TRAIN.WEIGHT_DECAY,
|
||||
config.TRAIN.BASE_LR,
|
||||
skip,
|
||||
skip_keywords,
|
||||
lr_layer_decay=config.TRAIN.LR_LAYER_DECAY,
|
||||
lr_layer_decay_ratio=config.TRAIN.LR_LAYER_DECAY_RATIO,
|
||||
freeze_backbone=config.TRAIN.OPTIMIZER.FREEZE_BACKBONE,
|
||||
dcn_lr_mul=config.TRAIN.OPTIMIZER.DCN_LR_MUL,
|
||||
)
|
||||
return parameters
|
||||
|
||||
|
||||
def get_optimizer_state_str(optimizer):
|
||||
states = []
|
||||
for param_group in optimizer.param_groups:
|
||||
states.append(f'name={param_group["name"]} lr={param_group["lr"]} weight_decay={param_group["weight_decay"]}')
|
||||
return '\n'.join(states)
|
||||
|
||||
|
||||
def build_ds_config(config, args):
|
||||
opt_lower = config.TRAIN.OPTIMIZER.NAME.lower()
|
||||
if opt_lower == 'adamw':
|
||||
optimizer = {
|
||||
"type": "AdamW",
|
||||
"params": {
|
||||
"lr": config.TRAIN.BASE_LR,
|
||||
"eps": config.TRAIN.OPTIMIZER.EPS,
|
||||
"betas": config.TRAIN.OPTIMIZER.BETAS,
|
||||
"weight_decay": config.TRAIN.WEIGHT_DECAY
|
||||
}
|
||||
}
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
ds_config = {
|
||||
"train_micro_batch_size_per_gpu": config.DATA.BATCH_SIZE,
|
||||
"optimizer": optimizer,
|
||||
"fp16": {
|
||||
"enabled": True,
|
||||
"auto_cast": True,
|
||||
"loss_scale": 1 if args.disable_grad_scalar else 0
|
||||
},
|
||||
"zero_optimization": {
|
||||
"stage": 1,
|
||||
},
|
||||
"steps_per_print": 1e10,
|
||||
"gradient_accumulation_steps": config.TRAIN.ACCUMULATION_STEPS,
|
||||
"gradient_clipping": config.TRAIN.CLIP_GRAD,
|
||||
}
|
||||
return ds_config
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def throughput(data_loader, model, logger):
|
||||
model.eval()
|
||||
|
||||
for idx, (images, _) in enumerate(data_loader):
|
||||
images = images.cuda(non_blocking=True)
|
||||
batch_size = images.shape[0]
|
||||
for i in range(50):
|
||||
model(images)
|
||||
torch.cuda.synchronize()
|
||||
logger.info(f"throughput averaged with 30 times")
|
||||
tic1 = time.time()
|
||||
for i in range(30):
|
||||
model(images)
|
||||
torch.cuda.synchronize()
|
||||
tic2 = time.time()
|
||||
logger.info(
|
||||
f"batch_size {batch_size} throughput {30 * batch_size / (tic2 - tic1)}"
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
def train_epoch(config, model, criterion, data_loader, optimizer, epoch, mixup_fn, lr_scheduler, model_ema=None):
|
||||
model.train()
|
||||
|
||||
num_steps = len(data_loader)
|
||||
batch_time = AverageMeter()
|
||||
model_time = AverageMeter()
|
||||
loss_meter = AverageMeter()
|
||||
norm_meter = MyAverageMeter(300)
|
||||
|
||||
start = time.time()
|
||||
end = time.time()
|
||||
|
||||
for idx, (samples, targets) in enumerate(data_loader):
|
||||
iter_begin_time = time.time()
|
||||
samples = samples.cuda(non_blocking=True)
|
||||
targets = targets.cuda(non_blocking=True)
|
||||
|
||||
if mixup_fn is not None:
|
||||
samples, targets = mixup_fn(samples, targets)
|
||||
|
||||
outputs = model(samples)
|
||||
loss = criterion(outputs, targets)
|
||||
|
||||
model.backward(loss)
|
||||
model.step()
|
||||
|
||||
if model_ema is not None:
|
||||
model_ema(model)
|
||||
|
||||
if (idx + 1) % config.TRAIN.ACCUMULATION_STEPS == 0:
|
||||
lr_scheduler.step_update(epoch * num_steps + idx)
|
||||
|
||||
torch.cuda.synchronize()
|
||||
loss_meter.update(loss.item(), targets.size(0))
|
||||
norm_meter.update(optimizer._global_grad_norm)
|
||||
batch_time.update(time.time() - end)
|
||||
model_time.update(time.time() - iter_begin_time)
|
||||
end = time.time()
|
||||
|
||||
if idx % config.PRINT_FREQ == 0:
|
||||
lr = optimizer.param_groups[0]['lr']
|
||||
memory_used = torch.cuda.max_memory_allocated() / (1024.0 * 1024.0)
|
||||
etas = batch_time.avg * (num_steps - idx)
|
||||
logger.info(
|
||||
f'Train: [{epoch}/{config.TRAIN.EPOCHS}][{idx}/{num_steps}]\t'
|
||||
f'eta {datetime.timedelta(seconds=int(etas))} lr {lr:.6f}\t'
|
||||
f'time {batch_time.val:.4f} ({batch_time.avg:.4f})\t'
|
||||
f'model_time {model_time.val:.4f} ({model_time.avg:.4f})\t'
|
||||
f'loss {loss_meter.val:.4f} ({loss_meter.avg:.4f})\t'
|
||||
f'grad_norm {norm_meter.val:.4f} ({norm_meter.avg:.4f}/{norm_meter.var:.4f})\t'
|
||||
f'mem {memory_used:.0f}MB')
|
||||
|
||||
epoch_time = time.time() - start
|
||||
logger.info(f"EPOCH {epoch} training takes {datetime.timedelta(seconds=int(epoch_time))}")
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def eval_epoch(config, data_loader, model, epoch=None):
|
||||
criterion = torch.nn.CrossEntropyLoss()
|
||||
model.eval()
|
||||
|
||||
batch_time = AverageMeter()
|
||||
loss_meter = AverageMeter()
|
||||
acc1_meter = AverageMeter()
|
||||
acc5_meter = AverageMeter()
|
||||
|
||||
end = time.time()
|
||||
for idx, (images, target) in enumerate(data_loader):
|
||||
images = images.cuda(non_blocking=True)
|
||||
target = target.cuda(non_blocking=True)
|
||||
output = model(images)
|
||||
|
||||
# convert 22k to 1k to evaluate
|
||||
if output.size(-1) == 21841:
|
||||
convert_file = './meta_data/map22kto1k.txt'
|
||||
with open(convert_file, 'r') as f:
|
||||
convert_list = [int(line) for line in f.readlines()]
|
||||
output = output[:, convert_list]
|
||||
|
||||
# measure accuracy and record loss
|
||||
loss = criterion(output, target)
|
||||
acc1, acc5 = accuracy(output, target, topk=(1, 5))
|
||||
|
||||
acc1 = reduce_tensor(acc1)
|
||||
acc5 = reduce_tensor(acc5)
|
||||
loss = reduce_tensor(loss)
|
||||
|
||||
loss_meter.update(loss.item(), target.size(0))
|
||||
acc1_meter.update(acc1.item(), target.size(0))
|
||||
acc5_meter.update(acc5.item(), target.size(0))
|
||||
|
||||
# measure elapsed time
|
||||
batch_time.update(time.time() - end)
|
||||
end = time.time()
|
||||
|
||||
if idx % config.PRINT_FREQ == 0:
|
||||
memory_used = torch.cuda.max_memory_allocated() / (1024.0 * 1024.0)
|
||||
logger.info(f'Test: [{idx}/{len(data_loader)}]\t'
|
||||
f'Time {batch_time.val:.3f} ({batch_time.avg:.3f})\t'
|
||||
f'Loss {loss_meter.val:.4f} ({loss_meter.avg:.4f})\t'
|
||||
f'Acc@1 {acc1_meter.val:.3f} ({acc1_meter.avg:.3f})\t'
|
||||
f'Acc@5 {acc5_meter.val:.3f} ({acc5_meter.avg:.3f})\t'
|
||||
f'Mem {memory_used:.0f}MB')
|
||||
if epoch is not None:
|
||||
logger.info(f'[Epoch:{epoch}] * Acc@1 {acc1_meter.avg:.3f} Acc@5 {acc5_meter.avg:.3f}')
|
||||
else:
|
||||
logger.info(f' * Acc@1 {acc1_meter.avg:.3f} Acc@5 {acc5_meter.avg:.3f}')
|
||||
|
||||
return acc1_meter.avg, acc5_meter.avg, loss_meter.avg
|
||||
|
||||
|
||||
def train(config, ds_config):
|
||||
# -------------- build ---------------- #
|
||||
|
||||
_, dataset_val, _, data_loader_train, data_loader_val, _, mixup_fn = build_loader(config)
|
||||
model = build_model(config)
|
||||
model.cuda()
|
||||
|
||||
if config.MODEL.PRETRAINED:
|
||||
load_pretrained(config, model, logger)
|
||||
|
||||
logger.info(ds_config)
|
||||
model, optimizer, _, _ = deepspeed.initialize(
|
||||
config=ds_config,
|
||||
model=model,
|
||||
model_parameters=get_parameter_groups(model, config),
|
||||
dist_init_required=False,
|
||||
)
|
||||
|
||||
try:
|
||||
model.register_comm_hook(state=None, hook=fp16_compress_hook)
|
||||
logger.info('using fp16_compress_hook!')
|
||||
except:
|
||||
logger.info("cannot register fp16_compress_hook!")
|
||||
|
||||
model_without_ddp = model.module
|
||||
|
||||
lr_scheduler = build_scheduler(config, optimizer, len(data_loader_train))
|
||||
criterion = build_criterion(config)
|
||||
|
||||
model_ema = None
|
||||
if config.TRAIN.EMA.ENABLE:
|
||||
model_ema = EMADeepspeed(model, config.TRAIN.EMA.DECAY)
|
||||
|
||||
# -------------- resume ---------------- #
|
||||
|
||||
max_accuracy = 0.0
|
||||
max_accuracy_ema = 0.0
|
||||
client_state = {}
|
||||
if config.MODEL.RESUME == '' and config.TRAIN.AUTO_RESUME:
|
||||
if os.path.exists(os.path.join(config.OUTPUT, 'latest')):
|
||||
config.defrost()
|
||||
config.MODEL.RESUME = config.OUTPUT
|
||||
config.freeze()
|
||||
tag = None
|
||||
elif config.MODEL.RESUME:
|
||||
config.MODEL.RESUME = os.path.dirname(config.MODEL.RESUME)
|
||||
tag = os.path.basename(config.MODEL.RESUME)
|
||||
if config.MODEL.RESUME:
|
||||
logger.info('loading checkpoint from {}'.format(config.MODEL.RESUME))
|
||||
_, client_state = model.load_checkpoint(load_dir=config.MODEL.RESUME, tag=tag)
|
||||
logger.info(f'client_state={client_state.keys()}')
|
||||
lr_scheduler.load_state_dict(client_state['custom_lr_scheduler'])
|
||||
max_accuracy = client_state['max_accuracy']
|
||||
|
||||
if model_ema is not None:
|
||||
max_accuracy_ema = client_state.get('max_accuracy_ema', 0.0)
|
||||
model_ema.load_state_dict((client_state['model_ema']))
|
||||
|
||||
# -------------- training ---------------- #
|
||||
|
||||
logger.info(f"Creating model:{config.MODEL.TYPE}/{config.MODEL.NAME}")
|
||||
logger.info(str(model))
|
||||
logger.info(get_optimizer_state_str(optimizer))
|
||||
logger.info("Start training")
|
||||
logger.info('max_accuracy: {}'.format(max_accuracy))
|
||||
log_model_statistic(model_without_ddp)
|
||||
|
||||
start_time = time.time()
|
||||
start_epoch = client_state['epoch'] + 1 if 'epoch' in client_state else config.TRAIN.START_EPOCH
|
||||
for epoch in range(start_epoch, config.TRAIN.EPOCHS):
|
||||
data_loader_train.sampler.set_epoch(epoch)
|
||||
train_epoch(config, model, criterion, data_loader_train, optimizer, epoch, mixup_fn, lr_scheduler,
|
||||
model_ema=model_ema)
|
||||
|
||||
if epoch % config.SAVE_FREQ == 0 or epoch == config.TRAIN.EPOCHS - 1:
|
||||
model.save_checkpoint(
|
||||
save_dir=config.OUTPUT,
|
||||
tag=f'epoch{epoch}',
|
||||
client_state={
|
||||
'custom_lr_scheduler': lr_scheduler.state_dict(),
|
||||
'max_accuracy': max_accuracy,
|
||||
'epoch': epoch,
|
||||
'config': config,
|
||||
'max_accuracy_ema': max_accuracy_ema if model_ema is not None else 0.0,
|
||||
'model_ema': model_ema.state_dict() if model_ema is not None else None,
|
||||
}
|
||||
)
|
||||
|
||||
if epoch % config.EVAL_FREQ == 0:
|
||||
acc1, _, _ = eval_epoch(config, data_loader_val, model, epoch)
|
||||
logger.info(f"Accuracy of the network on the {len(dataset_val)} test images: {acc1:.1f}%")
|
||||
|
||||
if acc1 > max_accuracy:
|
||||
model.save_checkpoint(
|
||||
save_dir=config.OUTPUT,
|
||||
tag='best',
|
||||
client_state={
|
||||
'custom_lr_scheduler': lr_scheduler.state_dict(),
|
||||
'max_accuracy': max_accuracy,
|
||||
'epoch': epoch,
|
||||
'config': config,
|
||||
'max_accuracy_ema': max_accuracy_ema if model_ema is not None else 0.0,
|
||||
'model_ema': model_ema.state_dict() if model_ema is not None else None,
|
||||
}
|
||||
)
|
||||
|
||||
max_accuracy = max(max_accuracy, acc1)
|
||||
logger.info(f'Max accuracy: {max_accuracy:.2f}%')
|
||||
|
||||
if model_ema is not None:
|
||||
with model_ema.activate(model):
|
||||
acc1_ema, _, _ = eval_epoch(config, data_loader_val, model, epoch)
|
||||
logger.info(f"[EMA] Accuracy of the network on the {len(dataset_val)} test images: {acc1_ema:.1f}%")
|
||||
max_accuracy_ema = max(max_accuracy_ema, acc1_ema)
|
||||
logger.info(f'[EMA] Max accuracy: {max_accuracy_ema:.2f}%')
|
||||
|
||||
total_time = time.time() - start_time
|
||||
total_time_str = str(datetime.timedelta(seconds=int(total_time)))
|
||||
logger.info('Training time {}'.format(total_time_str))
|
||||
|
||||
|
||||
def eval(config):
|
||||
_, _, _, _, data_loader_val, _, _ = build_loader(config)
|
||||
model = build_model(config)
|
||||
model.cuda()
|
||||
model = torch.nn.parallel.DistributedDataParallel(
|
||||
model, device_ids=[config.LOCAL_RANK], broadcast_buffers=False)
|
||||
|
||||
model_wo_ddp = model.module
|
||||
if config.MODEL.RESUME:
|
||||
try:
|
||||
checkpoint = torch.load(config.MODEL.RESUME, map_location='cpu')
|
||||
msg = model_wo_ddp.load_state_dict(checkpoint['model'], strict=False)
|
||||
logger.info(msg)
|
||||
except:
|
||||
try:
|
||||
from deepspeed.utils.zero_to_fp32 import get_fp32_state_dict_from_zero_checkpoint
|
||||
ckpt_dir = os.path.dirname(config.MODEL.RESUME)
|
||||
tag = os.path.basename(config.MODEL.RESUME)
|
||||
state_dict = get_fp32_state_dict_from_zero_checkpoint(checkpoint_dir=ckpt_dir, tag=tag)
|
||||
model_wo_ddp.load_state_dict(state_dict)
|
||||
except:
|
||||
checkpoint = torch.load(os.path.join(config.MODEL.RESUME, 'mp_rank_00_model_states.pt'), map_location='cpu')
|
||||
model_wo_ddp.load_state_dict(checkpoint['module'])
|
||||
elif config.MODEL.PRETRAINED:
|
||||
load_pretrained(config, model_wo_ddp, logger)
|
||||
|
||||
if config.THROUGHPUT_MODE:
|
||||
throughput(data_loader_val, model, logger)
|
||||
|
||||
eval_epoch(config, data_loader_val, model)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args, config = parse_option()
|
||||
|
||||
# init distributed env
|
||||
if 'SLURM_PROCID' in os.environ and int(os.environ['SLURM_TASKS_PER_NODE']) != 1:
|
||||
print("\nDist init: SLURM")
|
||||
rank = int(os.environ['SLURM_PROCID'])
|
||||
gpu = rank % torch.cuda.device_count()
|
||||
config.defrost()
|
||||
config.LOCAL_RANK = gpu
|
||||
config.freeze()
|
||||
|
||||
world_size = int(os.environ["SLURM_NTASKS"])
|
||||
if "MASTER_PORT" not in os.environ:
|
||||
os.environ["MASTER_PORT"] = "29501"
|
||||
node_list = os.environ["SLURM_NODELIST"]
|
||||
addr = subprocess.getoutput(
|
||||
f"scontrol show hostname {node_list} | head -n1")
|
||||
if "MASTER_ADDR" not in os.environ:
|
||||
os.environ["MASTER_ADDR"] = addr
|
||||
|
||||
os.environ['RANK'] = str(rank)
|
||||
os.environ['LOCAL_RANK'] = str(gpu)
|
||||
os.environ['LOCAL_SIZE'] = str(torch.cuda.device_count())
|
||||
os.environ['WORLD_SIZE'] = str(world_size)
|
||||
if 'RANK' in os.environ and 'WORLD_SIZE' in os.environ:
|
||||
rank = int(os.environ["RANK"])
|
||||
world_size = int(os.environ['WORLD_SIZE'])
|
||||
print(f"RANK and WORLD_SIZE in environ: {rank}/{world_size}")
|
||||
else:
|
||||
rank = -1
|
||||
world_size = -1
|
||||
torch.cuda.set_device(config.LOCAL_RANK)
|
||||
torch.distributed.init_process_group(backend='nccl',
|
||||
init_method='env://',
|
||||
world_size=world_size,
|
||||
rank=rank)
|
||||
torch.distributed.barrier()
|
||||
|
||||
os.makedirs(config.OUTPUT, exist_ok=True)
|
||||
logger = create_logger(output_dir=config.OUTPUT,
|
||||
dist_rank=dist.get_rank(),
|
||||
name=f"{config.MODEL.NAME}")
|
||||
logger.info(config.dump())
|
||||
|
||||
if dist.get_rank() == 0: save_config(config)
|
||||
scale_learning_rate(config, dist.get_world_size())
|
||||
seed_everything(config.SEED, dist.get_rank())
|
||||
|
||||
if config.EVAL_MODE:
|
||||
eval(config)
|
||||
else:
|
||||
train(config, build_ds_config(config, args))
|
||||
1
classification/meta_data/22k_class_to_idx.json
Normal file
1
classification/meta_data/22k_class_to_idx.json
Normal file
File diff suppressed because one or more lines are too long
1000
classification/meta_data/map22kto1k.txt
Normal file
1000
classification/meta_data/map22kto1k.txt
Normal file
File diff suppressed because it is too large
Load Diff
1
classification/meta_data/meta
Symbolic link
1
classification/meta_data/meta
Symbolic link
@@ -0,0 +1 @@
|
||||
/mnt/petrelfs/share/images/meta/
|
||||
7
classification/models/__init__.py
Normal file
7
classification/models/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from .build import build_model
|
||||
58
classification/models/build.py
Normal file
58
classification/models/build.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
|
||||
from .intern_image import InternImage
|
||||
from .flash_intern_image import FlashInternImage
|
||||
|
||||
def build_model(config):
|
||||
model_type = config.MODEL.TYPE
|
||||
if model_type == 'intern_image':
|
||||
model = InternImage(
|
||||
core_op=config.MODEL.INTERN_IMAGE.CORE_OP,
|
||||
num_classes=config.MODEL.NUM_CLASSES,
|
||||
channels=config.MODEL.INTERN_IMAGE.CHANNELS,
|
||||
depths=config.MODEL.INTERN_IMAGE.DEPTHS,
|
||||
groups=config.MODEL.INTERN_IMAGE.GROUPS,
|
||||
layer_scale=config.MODEL.INTERN_IMAGE.LAYER_SCALE,
|
||||
offset_scale=config.MODEL.INTERN_IMAGE.OFFSET_SCALE,
|
||||
post_norm=config.MODEL.INTERN_IMAGE.POST_NORM,
|
||||
mlp_ratio=config.MODEL.INTERN_IMAGE.MLP_RATIO,
|
||||
with_cp=config.TRAIN.USE_CHECKPOINT,
|
||||
drop_path_rate=config.MODEL.DROP_PATH_RATE,
|
||||
res_post_norm=config.MODEL.INTERN_IMAGE.RES_POST_NORM, # for InternImage-H/G
|
||||
dw_kernel_size=config.MODEL.INTERN_IMAGE.DW_KERNEL_SIZE, # for InternImage-H/G
|
||||
use_clip_projector=config.MODEL.INTERN_IMAGE.USE_CLIP_PROJECTOR, # for InternImage-H/G
|
||||
level2_post_norm=config.MODEL.INTERN_IMAGE.LEVEL2_POST_NORM, # for InternImage-H/G
|
||||
level2_post_norm_block_ids=config.MODEL.INTERN_IMAGE.LEVEL2_POST_NORM_BLOCK_IDS, # for InternImage-H/G
|
||||
center_feature_scale=config.MODEL.INTERN_IMAGE.CENTER_FEATURE_SCALE # for InternImage-H/G
|
||||
)
|
||||
elif model_type == 'flash_intern_image':
|
||||
model = FlashInternImage(
|
||||
core_op=config.MODEL.FLASH_INTERN_IMAGE.CORE_OP,
|
||||
num_classes=config.MODEL.NUM_CLASSES,
|
||||
channels=config.MODEL.FLASH_INTERN_IMAGE.CHANNELS,
|
||||
depths=config.MODEL.FLASH_INTERN_IMAGE.DEPTHS,
|
||||
groups=config.MODEL.FLASH_INTERN_IMAGE.GROUPS,
|
||||
layer_scale=config.MODEL.FLASH_INTERN_IMAGE.LAYER_SCALE,
|
||||
offset_scale=config.MODEL.FLASH_INTERN_IMAGE.OFFSET_SCALE,
|
||||
post_norm=config.MODEL.FLASH_INTERN_IMAGE.POST_NORM,
|
||||
mlp_ratio=config.MODEL.FLASH_INTERN_IMAGE.MLP_RATIO,
|
||||
with_cp=config.TRAIN.USE_CHECKPOINT,
|
||||
drop_path_rate=config.MODEL.DROP_PATH_RATE,
|
||||
mlp_fc2_bias=config.MODEL.FLASH_INTERN_IMAGE.MLP_FC2_BIAS,
|
||||
dcn_output_bias=config.MODEL.FLASH_INTERN_IMAGE.DCN_OUTPUT_BIAS,
|
||||
res_post_norm=config.MODEL.FLASH_INTERN_IMAGE.RES_POST_NORM, # for InternImage-H/G
|
||||
dw_kernel_size=config.MODEL.FLASH_INTERN_IMAGE.DW_KERNEL_SIZE,
|
||||
use_clip_projector=config.MODEL.FLASH_INTERN_IMAGE.USE_CLIP_PROJECTOR, # for InternImage-H/G
|
||||
level2_post_norm=config.MODEL.FLASH_INTERN_IMAGE.LEVEL2_POST_NORM, # for InternImage-H/G
|
||||
level2_post_norm_block_ids=config.MODEL.FLASH_INTERN_IMAGE.LEVEL2_POST_NORM_BLOCK_IDS, # for InternImage-H/G
|
||||
center_feature_scale=config.MODEL.FLASH_INTERN_IMAGE.CENTER_FEATURE_SCALE # for InternImage-H/G
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(f"Unkown model: {model_type}")
|
||||
|
||||
return model
|
||||
795
classification/models/flash_intern_image.py
Normal file
795
classification/models/flash_intern_image.py
Normal file
@@ -0,0 +1,795 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from collections import OrderedDict
|
||||
import torch.utils.checkpoint as checkpoint
|
||||
from timm.models.layers import trunc_normal_, DropPath
|
||||
import torch.nn.functional as F
|
||||
import DCNv4
|
||||
|
||||
|
||||
class to_channels_first(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def forward(self, x):
|
||||
return x.permute(0, 3, 1, 2)
|
||||
|
||||
|
||||
class to_channels_last(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def forward(self, x):
|
||||
return x.permute(0, 2, 3, 1)
|
||||
|
||||
|
||||
def build_norm_layer(dim,
|
||||
norm_layer,
|
||||
in_format='channels_last',
|
||||
out_format='channels_last',
|
||||
eps=1e-6):
|
||||
layers = []
|
||||
if norm_layer == 'BN':
|
||||
if in_format == 'channels_last':
|
||||
layers.append(to_channels_first())
|
||||
layers.append(nn.BatchNorm2d(dim))
|
||||
if out_format == 'channels_last':
|
||||
layers.append(to_channels_last())
|
||||
elif norm_layer == 'LN':
|
||||
if in_format == 'channels_first':
|
||||
layers.append(to_channels_last())
|
||||
layers.append(nn.LayerNorm(dim, eps=eps))
|
||||
if out_format == 'channels_first':
|
||||
layers.append(to_channels_first())
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f'build_norm_layer does not support {norm_layer}')
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
|
||||
def build_act_layer(act_layer):
|
||||
if act_layer == 'ReLU':
|
||||
return nn.ReLU(inplace=True)
|
||||
elif act_layer == 'SiLU':
|
||||
return nn.SiLU(inplace=True)
|
||||
elif act_layer == 'GELU':
|
||||
return nn.GELU()
|
||||
|
||||
raise NotImplementedError(f'build_act_layer does not support {act_layer}')
|
||||
|
||||
|
||||
class CrossAttention(nn.Module):
|
||||
r""" Cross Attention Module
|
||||
Args:
|
||||
dim (int): Number of input channels.
|
||||
num_heads (int): Number of attention heads. Default: 8
|
||||
qkv_bias (bool, optional): If True, add a learnable bias to q, k, v.
|
||||
Default: False.
|
||||
qk_scale (float | None, optional): Override default qk scale of
|
||||
head_dim ** -0.5 if set. Default: None.
|
||||
attn_drop (float, optional): Dropout ratio of attention weight.
|
||||
Default: 0.0
|
||||
proj_drop (float, optional): Dropout ratio of output. Default: 0.0
|
||||
attn_head_dim (int, optional): Dimension of attention head.
|
||||
out_dim (int, optional): Dimension of output.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
dim,
|
||||
num_heads=8,
|
||||
qkv_bias=False,
|
||||
qk_scale=None,
|
||||
attn_drop=0.,
|
||||
proj_drop=0.,
|
||||
attn_head_dim=None,
|
||||
out_dim=None):
|
||||
super().__init__()
|
||||
if out_dim is None:
|
||||
out_dim = dim
|
||||
self.num_heads = num_heads
|
||||
head_dim = dim // num_heads
|
||||
if attn_head_dim is not None:
|
||||
head_dim = attn_head_dim
|
||||
all_head_dim = head_dim * self.num_heads
|
||||
self.scale = qk_scale or head_dim ** -0.5
|
||||
assert all_head_dim == dim
|
||||
|
||||
self.q = nn.Linear(dim, all_head_dim, bias=False)
|
||||
self.k = nn.Linear(dim, all_head_dim, bias=False)
|
||||
self.v = nn.Linear(dim, all_head_dim, bias=False)
|
||||
|
||||
if qkv_bias:
|
||||
self.q_bias = nn.Parameter(torch.zeros(all_head_dim))
|
||||
self.k_bias = nn.Parameter(torch.zeros(all_head_dim))
|
||||
self.v_bias = nn.Parameter(torch.zeros(all_head_dim))
|
||||
else:
|
||||
self.q_bias = None
|
||||
self.k_bias = None
|
||||
self.v_bias = None
|
||||
|
||||
self.attn_drop = nn.Dropout(attn_drop)
|
||||
self.proj = nn.Linear(all_head_dim, out_dim)
|
||||
self.proj_drop = nn.Dropout(proj_drop)
|
||||
|
||||
def forward(self, x, k=None, v=None):
|
||||
B, N, C = x.shape
|
||||
N_k = k.shape[1]
|
||||
N_v = v.shape[1]
|
||||
|
||||
q_bias, k_bias, v_bias = None, None, None
|
||||
if self.q_bias is not None:
|
||||
q_bias = self.q_bias
|
||||
k_bias = self.k_bias
|
||||
v_bias = self.v_bias
|
||||
|
||||
q = F.linear(input=x, weight=self.q.weight, bias=q_bias)
|
||||
q = q.reshape(B, N, 1, self.num_heads,
|
||||
-1).permute(2, 0, 3, 1,
|
||||
4).squeeze(0) # (B, N_head, N_q, dim)
|
||||
|
||||
k = F.linear(input=k, weight=self.k.weight, bias=k_bias)
|
||||
k = k.reshape(B, N_k, 1, self.num_heads, -1).permute(2, 0, 3, 1,
|
||||
4).squeeze(0)
|
||||
|
||||
v = F.linear(input=v, weight=self.v.weight, bias=v_bias)
|
||||
v = v.reshape(B, N_v, 1, self.num_heads, -1).permute(2, 0, 3, 1,
|
||||
4).squeeze(0)
|
||||
|
||||
q = q * self.scale
|
||||
attn = (q @ k.transpose(-2, -1)) # (B, N_head, N_q, N_k)
|
||||
|
||||
attn = attn.softmax(dim=-1)
|
||||
attn = self.attn_drop(attn)
|
||||
|
||||
x = (attn @ v).transpose(1, 2).reshape(B, N, -1)
|
||||
x = self.proj(x)
|
||||
x = self.proj_drop(x)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class AttentiveBlock(nn.Module):
|
||||
r"""Attentive Block
|
||||
Args:
|
||||
dim (int): Number of input channels.
|
||||
num_heads (int): Number of attention heads. Default: 8
|
||||
qkv_bias (bool, optional): If True, add a learnable bias to q, k, v.
|
||||
Default: False.
|
||||
qk_scale (float | None, optional): Override default qk scale of
|
||||
head_dim ** -0.5 if set. Default: None.
|
||||
drop (float, optional): Dropout rate. Default: 0.0.
|
||||
attn_drop (float, optional): Attention dropout rate. Default: 0.0.
|
||||
drop_path (float | tuple[float], optional): Stochastic depth rate.
|
||||
Default: 0.0.
|
||||
norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm.
|
||||
attn_head_dim (int, optional): Dimension of attention head. Default: None.
|
||||
out_dim (int, optional): Dimension of output. Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
dim,
|
||||
num_heads,
|
||||
qkv_bias=False,
|
||||
qk_scale=None,
|
||||
drop=0.,
|
||||
attn_drop=0.,
|
||||
drop_path=0.,
|
||||
norm_layer="LN",
|
||||
attn_head_dim=None,
|
||||
out_dim=None):
|
||||
super().__init__()
|
||||
|
||||
self.norm1_q = build_norm_layer(dim, norm_layer, eps=1e-6)
|
||||
self.norm1_k = build_norm_layer(dim, norm_layer, eps=1e-6)
|
||||
self.norm1_v = build_norm_layer(dim, norm_layer, eps=1e-6)
|
||||
self.cross_dcn = CrossAttention(dim,
|
||||
num_heads=num_heads,
|
||||
qkv_bias=qkv_bias,
|
||||
qk_scale=qk_scale,
|
||||
attn_drop=attn_drop,
|
||||
proj_drop=drop,
|
||||
attn_head_dim=attn_head_dim,
|
||||
out_dim=out_dim)
|
||||
|
||||
self.drop_path = DropPath(
|
||||
drop_path) if drop_path > 0. else nn.Identity()
|
||||
|
||||
def forward(self,
|
||||
x_q,
|
||||
x_kv,
|
||||
pos_q,
|
||||
pos_k,
|
||||
bool_masked_pos,
|
||||
rel_pos_bias=None):
|
||||
x_q = self.norm1_q(x_q + pos_q)
|
||||
x_k = self.norm1_k(x_kv + pos_k)
|
||||
x_v = self.norm1_v(x_kv)
|
||||
|
||||
x = self.cross_dcn(x_q, k=x_k, v=x_v)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class AttentionPoolingBlock(AttentiveBlock):
|
||||
|
||||
def forward(self, x):
|
||||
x_q = x.mean(1, keepdim=True)
|
||||
x_kv = x
|
||||
pos_q, pos_k = 0, 0
|
||||
x = super().forward(x_q, x_kv, pos_q, pos_k,
|
||||
bool_masked_pos=None,
|
||||
rel_pos_bias=None)
|
||||
x = x.squeeze(1)
|
||||
return x
|
||||
|
||||
|
||||
class StemLayer(nn.Module):
|
||||
r""" Stem layer of InternImage
|
||||
Args:
|
||||
in_chans (int): number of input channels
|
||||
out_chans (int): number of output channels
|
||||
act_layer (str): activation layer
|
||||
norm_layer (str): normalization layer
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_chans=3,
|
||||
out_chans=96,
|
||||
act_layer='GELU',
|
||||
norm_layer='BN'):
|
||||
super().__init__()
|
||||
self.conv1 = nn.Conv2d(in_chans,
|
||||
out_chans // 2,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1)
|
||||
self.norm1 = build_norm_layer(out_chans // 2, norm_layer,
|
||||
'channels_first', 'channels_first')
|
||||
self.act = build_act_layer(act_layer)
|
||||
self.conv2 = nn.Conv2d(out_chans // 2,
|
||||
out_chans,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1)
|
||||
self.norm2 = build_norm_layer(out_chans, norm_layer, 'channels_first',
|
||||
'channels_last')
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv1(x)
|
||||
x = self.norm1(x)
|
||||
x = self.act(x)
|
||||
x = self.conv2(x)
|
||||
x = self.norm2(x)
|
||||
return x
|
||||
|
||||
|
||||
|
||||
class DownsampleLayer(nn.Module):
|
||||
r""" Downsample layer of InternImage
|
||||
Args:
|
||||
channels (int): number of input channels
|
||||
norm_layer (str): normalization layer
|
||||
"""
|
||||
|
||||
def __init__(self, channels, norm_layer='LN'):
|
||||
super().__init__()
|
||||
self.conv = nn.Conv2d(channels,
|
||||
2 * channels,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
bias=False)
|
||||
self.norm = build_norm_layer(2 * channels, norm_layer,
|
||||
'channels_first', 'channels_first')
|
||||
|
||||
|
||||
def forward(self, x, shape=None):
|
||||
H, W = shape
|
||||
N, HW, C = x.shape
|
||||
x = x.view(N, H, W, C)
|
||||
x = self.conv(x.permute(0, 3, 1, 2))
|
||||
x = self.norm(x) # B C H W
|
||||
H, W = x.size(2), x.size(3)
|
||||
x = x.flatten(2).permute(0, 2, 1)
|
||||
|
||||
return x, (H, W)
|
||||
|
||||
|
||||
|
||||
class MLPLayer(nn.Module):
|
||||
r""" MLP layer of InternImage
|
||||
Args:
|
||||
in_features (int): number of input features
|
||||
hidden_features (int): number of hidden features
|
||||
out_features (int): number of output features
|
||||
act_layer (str): activation layer
|
||||
drop (float): dropout rate
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_features,
|
||||
hidden_features=None,
|
||||
out_features=None,
|
||||
act_layer='GELU',
|
||||
mlp_fc2_bias=False,
|
||||
drop=0.):
|
||||
super().__init__()
|
||||
out_features = out_features or in_features
|
||||
hidden_features = hidden_features or in_features
|
||||
self.fc1 = nn.Linear(in_features, hidden_features, bias=True)
|
||||
self.act = build_act_layer(act_layer)
|
||||
self.fc2 = nn.Linear(hidden_features, out_features, bias=mlp_fc2_bias)
|
||||
self.drop = nn.Dropout(drop)
|
||||
|
||||
|
||||
def forward(self, x, shape):
|
||||
x = self.fc1(x)
|
||||
x = self.act(x)
|
||||
x = self.drop(x)
|
||||
x = self.fc2(x)
|
||||
x = self.drop(x)
|
||||
return x
|
||||
|
||||
|
||||
class InternImageLayer(nn.Module):
|
||||
r""" Basic layer of InternImage
|
||||
Args:
|
||||
core_op (nn.Module): core operation of InternImage
|
||||
channels (int): number of input channels
|
||||
groups (list): Groups of each block.
|
||||
mlp_ratio (float): ratio of mlp hidden features to input channels
|
||||
drop (float): dropout rate
|
||||
drop_path (float): drop path rate
|
||||
act_layer (str): activation layer
|
||||
norm_layer (str): normalization layer
|
||||
post_norm (bool): whether to use post normalization
|
||||
layer_scale (float): layer scale
|
||||
offset_scale (float): offset scale
|
||||
with_cp (bool): whether to use checkpoint
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
core_op,
|
||||
channels,
|
||||
groups,
|
||||
mlp_ratio=4.,
|
||||
drop=0.,
|
||||
drop_path=0.,
|
||||
act_layer='GELU',
|
||||
norm_layer='LN',
|
||||
post_norm=False,
|
||||
layer_scale=None,
|
||||
offset_scale=1.0,
|
||||
with_cp=False,
|
||||
dcn_output_bias=False,
|
||||
mlp_fc2_bias=False,
|
||||
dw_kernel_size=None, # for InternImage-H/G
|
||||
res_post_norm=False, # for InternImage-H/G
|
||||
center_feature_scale=False): # for InternImage-H/G
|
||||
super().__init__()
|
||||
self.channels = channels
|
||||
self.groups = groups
|
||||
self.mlp_ratio = mlp_ratio
|
||||
self.with_cp = with_cp
|
||||
|
||||
self.norm1 = build_norm_layer(channels, 'LN')
|
||||
self.post_norm = post_norm
|
||||
self.dcn = core_op(
|
||||
channels=channels,
|
||||
group=groups,
|
||||
offset_scale=offset_scale,
|
||||
dw_kernel_size=dw_kernel_size,
|
||||
output_bias=dcn_output_bias,
|
||||
)
|
||||
self.drop_path = DropPath(drop_path) if drop_path > 0. \
|
||||
else nn.Identity()
|
||||
self.norm2 = build_norm_layer(channels, 'LN')
|
||||
self.mlp = MLPLayer(in_features=channels,
|
||||
hidden_features=int(channels * mlp_ratio),
|
||||
act_layer=act_layer,
|
||||
drop=drop,
|
||||
mlp_fc2_bias=mlp_fc2_bias
|
||||
)
|
||||
self.layer_scale = layer_scale is not None
|
||||
if self.layer_scale:
|
||||
self.gamma1 = nn.Parameter(layer_scale * torch.ones(channels),
|
||||
requires_grad=True)
|
||||
self.gamma2 = nn.Parameter(layer_scale * torch.ones(channels),
|
||||
requires_grad=True)
|
||||
self.res_post_norm = res_post_norm
|
||||
if res_post_norm:
|
||||
self.res_post_norm1 = build_norm_layer(channels, 'LN')
|
||||
self.res_post_norm2 = build_norm_layer(channels, 'LN')
|
||||
def forward(self, x, shape):
|
||||
|
||||
def _inner_forward(x, shape):
|
||||
if not self.layer_scale:
|
||||
if self.post_norm:
|
||||
x = x + self.drop_path(self.norm1(self.dcn(x, shape)))
|
||||
x = x + self.drop_path(self.norm2(self.mlp(x, shape)))
|
||||
elif self.res_post_norm: # for InternImage-H/G
|
||||
x = x + self.drop_path(self.res_post_norm1(self.dcn(self.norm1(x), shape)))
|
||||
x = x + self.drop_path(self.res_post_norm2(self.mlp(self.norm2(x), shape)))
|
||||
|
||||
else:
|
||||
x = x + self.drop_path(self.dcn(self.norm1(x), shape,))
|
||||
x = x + self.drop_path(self.mlp(self.norm2(x), shape))
|
||||
return x
|
||||
if self.post_norm:
|
||||
x = x + self.drop_path(self.gamma1 * self.norm1(self.dcn(x, shape)))
|
||||
x = x + self.drop_path(self.gamma2 * self.norm2(self.mlp(x, shape)))
|
||||
else:
|
||||
x = x + self.drop_path(self.gamma1 * self.dcn(self.norm1(x), shape))
|
||||
x = x + self.drop_path(self.gamma2 * self.mlp(self.norm2(x), shape))
|
||||
return x
|
||||
|
||||
if self.with_cp and x.requires_grad:
|
||||
x = checkpoint.checkpoint(_inner_forward, x, shape)
|
||||
else:
|
||||
x = _inner_forward(x, shape)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class InternImageBlock(nn.Module):
|
||||
r""" Block of InternImage
|
||||
Args:
|
||||
core_op (nn.Module): core operation of InternImage
|
||||
channels (int): number of input channels
|
||||
depths (list): Depth of each block.
|
||||
groups (list): Groups of each block.
|
||||
mlp_ratio (float): ratio of mlp hidden features to input channels
|
||||
drop (float): dropout rate
|
||||
drop_path (float): drop path rate
|
||||
act_layer (str): activation layer
|
||||
norm_layer (str): normalization layer
|
||||
post_norm (bool): whether to use post normalization
|
||||
layer_scale (float): layer scale
|
||||
offset_scale (float): offset scale
|
||||
with_cp (bool): whether to use checkpoint
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
core_op,
|
||||
channels,
|
||||
depth,
|
||||
groups,
|
||||
downsample=True,
|
||||
downsample_layer=DownsampleLayer,
|
||||
mlp_ratio=4.,
|
||||
drop=0.,
|
||||
drop_path=0.,
|
||||
act_layer='GELU',
|
||||
norm_layer='LN',
|
||||
post_norm=False,
|
||||
offset_scale=0.5,
|
||||
layer_scale=None,
|
||||
with_cp=False,
|
||||
dcn_output_bias=False,
|
||||
mlp_fc2_bias=False,
|
||||
dw_kernel_size=None, # for InternImage-H/G
|
||||
post_norm_block_ids=None, # for InternImage-H/G
|
||||
res_post_norm=False, # for InternImage-H/G
|
||||
center_feature_scale=False): # for InternImage-H/G
|
||||
super().__init__()
|
||||
self.channels = channels
|
||||
self.depth = depth
|
||||
self.post_norm = post_norm
|
||||
self.center_feature_scale = center_feature_scale
|
||||
|
||||
self.blocks = nn.ModuleList([
|
||||
InternImageLayer(
|
||||
core_op=core_op,
|
||||
channels=channels,
|
||||
groups=groups,
|
||||
mlp_ratio=mlp_ratio,
|
||||
drop=drop,
|
||||
drop_path=drop_path[i] if isinstance(
|
||||
drop_path, list) else drop_path,
|
||||
act_layer=act_layer,
|
||||
norm_layer=norm_layer,
|
||||
post_norm=post_norm,
|
||||
layer_scale=layer_scale,
|
||||
offset_scale=offset_scale,
|
||||
with_cp=with_cp,
|
||||
dcn_output_bias=dcn_output_bias,
|
||||
mlp_fc2_bias=mlp_fc2_bias,
|
||||
dw_kernel_size=dw_kernel_size, # for InternImage-H/G
|
||||
res_post_norm=res_post_norm, # for InternImage-H/G
|
||||
center_feature_scale=center_feature_scale # for InternImage-H/G
|
||||
) for i in range(depth)
|
||||
])
|
||||
if not self.post_norm or center_feature_scale:
|
||||
self.norm = build_norm_layer(channels, 'LN')
|
||||
self.post_norm_block_ids = post_norm_block_ids
|
||||
if post_norm_block_ids is not None: # for InternImage-H/G
|
||||
self.post_norms = nn.ModuleList(
|
||||
[build_norm_layer(channels, 'LN', eps=1e-6) for _ in post_norm_block_ids]
|
||||
)
|
||||
self.downsample = downsample_layer(
|
||||
channels=channels, norm_layer=norm_layer) if downsample else None
|
||||
|
||||
|
||||
def forward(self, x, return_wo_downsample=False, shape=None):
|
||||
for i, blk in enumerate(self.blocks):
|
||||
x = blk(x, shape=shape)
|
||||
if (self.post_norm_block_ids is not None) and (i in self.post_norm_block_ids):
|
||||
index = self.post_norm_block_ids.index(i)
|
||||
x = self.post_norms[index](x) # for InternImage-H/G
|
||||
if not self.post_norm or self.center_feature_scale:
|
||||
x = self.norm(x)
|
||||
if return_wo_downsample:
|
||||
x_ = x.clone()
|
||||
if self.downsample is not None:
|
||||
x, shape = self.downsample(x, shape=shape)
|
||||
|
||||
if return_wo_downsample:
|
||||
return x, x_, shape
|
||||
return x, shape
|
||||
|
||||
|
||||
class FlashInternImage(nn.Module):
|
||||
r""" FlashInternImage
|
||||
A PyTorch impl based on :
|
||||
`InternImage: Exploring Large-Scale Vision Foundation Models with Deformable Convolutions` -
|
||||
https://arxiv.org/pdf/2103.14030
|
||||
'DCNv4': TODO: add arxiv
|
||||
Args:
|
||||
core_op (str): Core operator. Default: 'DCNv4'
|
||||
channels (int): Number of the first stage. Default: 64
|
||||
depths (list): Depth of each block. Default: [3, 4, 18, 5]
|
||||
groups (list): Groups of each block. Default: [3, 6, 12, 24]
|
||||
num_classes (int): Number of classes. Default: 1000
|
||||
mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. Default: 4.
|
||||
drop_rate (float): Probability of an element to be zeroed. Default: 0.
|
||||
drop_path_rate (float): Stochastic depth rate. Default: 0.2
|
||||
act_layer (str): Activation layer. Default: 'GELU'
|
||||
norm_layer (str): Normalization layer. Default: 'LN'
|
||||
layer_scale (bool): Whether to use layer scale. Default: False
|
||||
cls_scale (bool): Whether to use class scale. Default: False
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
dw_kernel_size (int): Size of the dwconv. Default: None
|
||||
use_clip_projector (bool): Whether to use clip projector. Default: False
|
||||
level2_post_norm (bool): Whether to use level2 post norm. Default: False
|
||||
level2_post_norm_block_ids (list): Indexes of post norm blocks. Default: None
|
||||
res_post_norm (bool): Whether to use res post norm. Default: False
|
||||
center_feature_scale (bool): Whether to use center feature scale. Default: False
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
core_op='DCNv4',
|
||||
channels=64,
|
||||
depths=[3, 4, 18, 5],
|
||||
groups=[3, 6, 12, 24],
|
||||
num_classes=1000,
|
||||
mlp_ratio=4.,
|
||||
drop_rate=0.,
|
||||
drop_path_rate=0.2,
|
||||
drop_path_type='linear',
|
||||
act_layer='GELU',
|
||||
norm_layer='LN',
|
||||
layer_scale=None,
|
||||
offset_scale=0.5,
|
||||
post_norm=False,
|
||||
cls_scale=1.5,
|
||||
with_cp=False,
|
||||
mlp_fc2_bias=False,
|
||||
dcn_output_bias=False,
|
||||
dw_kernel_size=None,
|
||||
use_clip_projector=False, # for InternImage-H/G
|
||||
level2_post_norm=False, # for InternImage-H/G
|
||||
level2_post_norm_block_ids=None, # for InternImage-H/G
|
||||
res_post_norm=False, # for InternImage-H/G
|
||||
center_feature_scale=False, # for InternImage-H/G
|
||||
**kwargs):
|
||||
super().__init__()
|
||||
self.core_op = core_op
|
||||
self.num_classes = num_classes
|
||||
self.num_levels = len(depths)
|
||||
self.depths = depths
|
||||
self.channels = channels
|
||||
self.num_features = int(channels * 2**(self.num_levels - 1))
|
||||
self.post_norm = post_norm
|
||||
self.mlp_ratio = mlp_ratio
|
||||
self.use_clip_projector = use_clip_projector
|
||||
|
||||
self.level2_post_norm_block_ids = level2_post_norm_block_ids
|
||||
print(f'using core type: {core_op}')
|
||||
print(f'using activation layer: {act_layer}')
|
||||
print(f'using main norm layer: {norm_layer}')
|
||||
print(f'using dpr: {drop_path_type}, {drop_path_rate}')
|
||||
print(f"level2_post_norm: {level2_post_norm}")
|
||||
print(f"level2_post_norm_block_ids: {level2_post_norm_block_ids}")
|
||||
print(f"res_post_norm: {res_post_norm}")
|
||||
|
||||
in_chans = 3
|
||||
self.patch_embed = StemLayer(in_chans=in_chans,
|
||||
out_chans=channels,
|
||||
act_layer=act_layer,
|
||||
norm_layer=norm_layer)
|
||||
self.pos_drop = nn.Dropout(p=drop_rate)
|
||||
|
||||
dpr = [
|
||||
x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))
|
||||
]
|
||||
if drop_path_type == 'uniform':
|
||||
for i in range(len(dpr)):
|
||||
dpr[i] = drop_path_rate
|
||||
|
||||
self.levels = nn.ModuleList()
|
||||
for i in range(self.num_levels):
|
||||
post_norm_block_ids = level2_post_norm_block_ids if level2_post_norm and (
|
||||
i == 2) else None # for InternImage-H/G
|
||||
|
||||
level = InternImageBlock(
|
||||
core_op=getattr(DCNv4, core_op),
|
||||
channels=int(channels * 2**i),
|
||||
depth=depths[i],
|
||||
groups=groups[i],
|
||||
mlp_ratio=self.mlp_ratio,
|
||||
drop=drop_rate,
|
||||
drop_path=dpr[sum(depths[:i]):sum(depths[:i + 1])],
|
||||
act_layer=act_layer,
|
||||
norm_layer=norm_layer,
|
||||
post_norm=post_norm,
|
||||
downsample=(i < self.num_levels - 1),
|
||||
downsample_layer = DownsampleLayer,
|
||||
layer_scale=layer_scale,
|
||||
offset_scale=offset_scale,
|
||||
with_cp=with_cp,
|
||||
mlp_fc2_bias=mlp_fc2_bias,
|
||||
dcn_output_bias=dcn_output_bias,
|
||||
dw_kernel_size=dw_kernel_size, # for InternImage-H/G
|
||||
post_norm_block_ids=post_norm_block_ids, # for InternImage-H/G
|
||||
res_post_norm=res_post_norm, # for InternImage-H/G
|
||||
center_feature_scale=center_feature_scale # for InternImage-H/G
|
||||
)
|
||||
self.levels.append(level)
|
||||
|
||||
if not use_clip_projector: # for InternImage-T/S/B/L/XL
|
||||
self.conv_head = nn.Sequential(
|
||||
nn.Conv2d(self.num_features,
|
||||
int(self.num_features * cls_scale),
|
||||
kernel_size=1,
|
||||
bias=False),
|
||||
build_norm_layer(int(self.num_features * cls_scale), 'BN',
|
||||
'channels_first', 'channels_first'),
|
||||
build_act_layer(act_layer))
|
||||
self.head = nn.Linear(int(self.num_features * cls_scale), num_classes) \
|
||||
if num_classes > 0 else nn.Identity()
|
||||
else: # for InternImage-H/G
|
||||
pretrain_embed_dim, _stride, attnpool_num_heads, clip_embed_dim = 1024, 2, 16, 768
|
||||
self.dcnv3_head_x4 = nn.Sequential(
|
||||
nn.Conv2d(in_channels=self.num_features,
|
||||
out_channels=pretrain_embed_dim * (_stride ** 2),
|
||||
kernel_size=1), nn.PixelShuffle(_stride))
|
||||
self.dcnv3_head_x3 = nn.Conv2d(in_channels=self.num_features // 2,
|
||||
out_channels=pretrain_embed_dim,
|
||||
kernel_size=1)
|
||||
self.clip_projector = AttentionPoolingBlock(
|
||||
dim=pretrain_embed_dim,
|
||||
num_heads=attnpool_num_heads,
|
||||
qkv_bias=True,
|
||||
qk_scale=None,
|
||||
drop=0.,
|
||||
attn_drop=0.,
|
||||
norm_layer=norm_layer,
|
||||
out_dim=clip_embed_dim)
|
||||
self.fc_norm = build_norm_layer(clip_embed_dim, norm_layer, eps=1e-6)
|
||||
self.head = nn.Linear(
|
||||
clip_embed_dim, num_classes) if num_classes > 0 else nn.Identity()
|
||||
|
||||
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
|
||||
self.num_layers = len(depths)
|
||||
self.apply(self._init_weights)
|
||||
self.apply(self._init_deform_weights)
|
||||
|
||||
def _init_weights(self, m):
|
||||
if isinstance(m, nn.Linear):
|
||||
trunc_normal_(m.weight, std=.02)
|
||||
if isinstance(m, nn.Linear) and m.bias is not None:
|
||||
nn.init.constant_(m.bias, 0)
|
||||
elif isinstance(m, nn.LayerNorm):
|
||||
nn.init.constant_(m.bias, 0)
|
||||
nn.init.constant_(m.weight, 1.0)
|
||||
|
||||
def _init_deform_weights(self, m):
|
||||
if isinstance(m, getattr(DCNv4, self.core_op)):
|
||||
m._reset_parameters()
|
||||
|
||||
@torch.jit.ignore
|
||||
def lr_decay_keywards(self, decay_ratio=0.87):
|
||||
lr_ratios = {}
|
||||
|
||||
# blocks
|
||||
idx = 0
|
||||
for i in range(4):
|
||||
layer_num = 3 - i # 3 2 1 0
|
||||
for j in range(self.depths[layer_num]):
|
||||
block_num = self.depths[layer_num] - j - 1
|
||||
tag = 'levels.{}.blocks.{}.'.format(layer_num, block_num)
|
||||
decay = 1.0 * (decay_ratio**idx)
|
||||
lr_ratios[tag] = decay
|
||||
idx += 1
|
||||
# patch_embed (before stage-1)
|
||||
lr_ratios["patch_embed"] = lr_ratios['levels.0.blocks.0.']
|
||||
# levels.0.downsample (between stage-1 and stage-2)
|
||||
lr_ratios["levels.0.downsample"] = lr_ratios['levels.1.blocks.0.']
|
||||
lr_ratios["levels.0.norm"] = lr_ratios['levels.1.blocks.0.']
|
||||
# levels.1.downsample (between stage-2 and stage-3)
|
||||
lr_ratios["levels.1.downsample"] = lr_ratios['levels.2.blocks.0.']
|
||||
lr_ratios["levels.1.norm"] = lr_ratios['levels.2.blocks.0.']
|
||||
# levels.2.downsample (between stage-3 and stage-4)
|
||||
lr_ratios["levels.2.downsample"] = lr_ratios['levels.3.blocks.0.']
|
||||
lr_ratios["levels.2.norm"] = lr_ratios['levels.3.blocks.0.']
|
||||
return lr_ratios
|
||||
|
||||
def forward_features(self, x):
|
||||
x = self.patch_embed(x)
|
||||
N, H, W, C = x.shape
|
||||
x = x.view(N, H*W, C)
|
||||
|
||||
shape=(H, W)
|
||||
seq_out = []
|
||||
for level_idx, level in enumerate(self.levels):
|
||||
old_shape = shape
|
||||
x, shape = level(x, shape=shape)
|
||||
h, w = shape
|
||||
x = x.view(N, h, w, -1)
|
||||
x = self.conv_head(x.permute(0, 3, 1, 2))
|
||||
x = self.avgpool(x)
|
||||
x = torch.flatten(x, 1)
|
||||
return x
|
||||
|
||||
def forward_features_seq_out(self, x):
|
||||
x = self.patch_embed(x)
|
||||
N, H, W, C = x.shape
|
||||
x = x.view(N, H*W, C)
|
||||
shape=(H, W)
|
||||
seq_out = []
|
||||
for level_idx, level in enumerate(self.levels):
|
||||
old_shape = shape
|
||||
x, x_ , shape = level(x, return_wo_downsample=True, shape=shape)
|
||||
h, w= old_shape
|
||||
seq_out.append(x_.reshape(N, h, w, -1).permute(0, 3, 1, 2))
|
||||
return seq_out
|
||||
|
||||
def forward_clip_projector(self, x): # for InternImage-H/G
|
||||
xs = self.forward_features_seq_out(x)
|
||||
x1, x2, x3, x4 = xs
|
||||
|
||||
x1 = x1.permute(0, 3, 1, 2) # NHWC -> NCHW
|
||||
x2 = x2.permute(0, 3, 1, 2) # NHWC -> NCHW
|
||||
x3 = x3.permute(0, 3, 1, 2) # NHWC -> NCHW
|
||||
x4 = x4.permute(0, 3, 1, 2) # NHWC -> NCHW
|
||||
|
||||
x4 = self.dcnv3_head_x4(x4)
|
||||
x = x4
|
||||
x3 = self.dcnv3_head_x3(x3)
|
||||
x = x + x3
|
||||
|
||||
x = x.flatten(-2).transpose(1, 2).contiguous()
|
||||
x = self.clip_projector(x)
|
||||
x = self.fc_norm(x)
|
||||
|
||||
return x
|
||||
|
||||
def forward(self, x):
|
||||
if self.use_clip_projector: # for InternImage-H/G
|
||||
x = self.forward_clip_projector(x)
|
||||
else: # for InternImage-T/S/B/L/XL
|
||||
x = self.forward_features(x)
|
||||
x = self.head(x)
|
||||
return x
|
||||
|
||||
|
||||
|
||||
759
classification/models/intern_image.py
Normal file
759
classification/models/intern_image.py
Normal file
@@ -0,0 +1,759 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.utils.checkpoint as checkpoint
|
||||
from timm.models.layers import trunc_normal_, DropPath
|
||||
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
class to_channels_first(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def forward(self, x):
|
||||
return x.permute(0, 3, 1, 2)
|
||||
|
||||
|
||||
class to_channels_last(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def forward(self, x):
|
||||
return x.permute(0, 2, 3, 1)
|
||||
|
||||
|
||||
def build_norm_layer(dim,
|
||||
norm_layer,
|
||||
in_format='channels_last',
|
||||
out_format='channels_last',
|
||||
eps=1e-6):
|
||||
layers = []
|
||||
if norm_layer == 'BN':
|
||||
if in_format == 'channels_last':
|
||||
layers.append(to_channels_first())
|
||||
layers.append(nn.BatchNorm2d(dim))
|
||||
if out_format == 'channels_last':
|
||||
layers.append(to_channels_last())
|
||||
elif norm_layer == 'LN':
|
||||
if in_format == 'channels_first':
|
||||
layers.append(to_channels_last())
|
||||
layers.append(nn.LayerNorm(dim, eps=eps))
|
||||
if out_format == 'channels_first':
|
||||
layers.append(to_channels_first())
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f'build_norm_layer does not support {norm_layer}')
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
|
||||
def build_act_layer(act_layer):
|
||||
if act_layer == 'ReLU':
|
||||
return nn.ReLU(inplace=True)
|
||||
elif act_layer == 'SiLU':
|
||||
return nn.SiLU(inplace=True)
|
||||
elif act_layer == 'GELU':
|
||||
return nn.GELU()
|
||||
|
||||
raise NotImplementedError(f'build_act_layer does not support {act_layer}')
|
||||
|
||||
|
||||
class CrossAttention(nn.Module):
|
||||
r""" Cross Attention Module
|
||||
Args:
|
||||
dim (int): Number of input channels.
|
||||
num_heads (int): Number of attention heads. Default: 8
|
||||
qkv_bias (bool, optional): If True, add a learnable bias to q, k, v.
|
||||
Default: False.
|
||||
qk_scale (float | None, optional): Override default qk scale of
|
||||
head_dim ** -0.5 if set. Default: None.
|
||||
attn_drop (float, optional): Dropout ratio of attention weight.
|
||||
Default: 0.0
|
||||
proj_drop (float, optional): Dropout ratio of output. Default: 0.0
|
||||
attn_head_dim (int, optional): Dimension of attention head.
|
||||
out_dim (int, optional): Dimension of output.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
dim,
|
||||
num_heads=8,
|
||||
qkv_bias=False,
|
||||
qk_scale=None,
|
||||
attn_drop=0.,
|
||||
proj_drop=0.,
|
||||
attn_head_dim=None,
|
||||
out_dim=None):
|
||||
super().__init__()
|
||||
if out_dim is None:
|
||||
out_dim = dim
|
||||
self.num_heads = num_heads
|
||||
head_dim = dim // num_heads
|
||||
if attn_head_dim is not None:
|
||||
head_dim = attn_head_dim
|
||||
all_head_dim = head_dim * self.num_heads
|
||||
self.scale = qk_scale or head_dim ** -0.5
|
||||
assert all_head_dim == dim
|
||||
|
||||
self.q = nn.Linear(dim, all_head_dim, bias=False)
|
||||
self.k = nn.Linear(dim, all_head_dim, bias=False)
|
||||
self.v = nn.Linear(dim, all_head_dim, bias=False)
|
||||
|
||||
if qkv_bias:
|
||||
self.q_bias = nn.Parameter(torch.zeros(all_head_dim))
|
||||
self.k_bias = nn.Parameter(torch.zeros(all_head_dim))
|
||||
self.v_bias = nn.Parameter(torch.zeros(all_head_dim))
|
||||
else:
|
||||
self.q_bias = None
|
||||
self.k_bias = None
|
||||
self.v_bias = None
|
||||
|
||||
self.attn_drop = nn.Dropout(attn_drop)
|
||||
self.proj = nn.Linear(all_head_dim, out_dim)
|
||||
self.proj_drop = nn.Dropout(proj_drop)
|
||||
|
||||
def forward(self, x, k=None, v=None):
|
||||
B, N, C = x.shape
|
||||
N_k = k.shape[1]
|
||||
N_v = v.shape[1]
|
||||
|
||||
q_bias, k_bias, v_bias = None, None, None
|
||||
if self.q_bias is not None:
|
||||
q_bias = self.q_bias
|
||||
k_bias = self.k_bias
|
||||
v_bias = self.v_bias
|
||||
|
||||
q = F.linear(input=x, weight=self.q.weight, bias=q_bias)
|
||||
q = q.reshape(B, N, 1, self.num_heads,
|
||||
-1).permute(2, 0, 3, 1,
|
||||
4).squeeze(0) # (B, N_head, N_q, dim)
|
||||
|
||||
k = F.linear(input=k, weight=self.k.weight, bias=k_bias)
|
||||
k = k.reshape(B, N_k, 1, self.num_heads, -1).permute(2, 0, 3, 1,
|
||||
4).squeeze(0)
|
||||
|
||||
v = F.linear(input=v, weight=self.v.weight, bias=v_bias)
|
||||
v = v.reshape(B, N_v, 1, self.num_heads, -1).permute(2, 0, 3, 1,
|
||||
4).squeeze(0)
|
||||
|
||||
q = q * self.scale
|
||||
attn = (q @ k.transpose(-2, -1)) # (B, N_head, N_q, N_k)
|
||||
|
||||
attn = attn.softmax(dim=-1)
|
||||
attn = self.attn_drop(attn)
|
||||
|
||||
x = (attn @ v).transpose(1, 2).reshape(B, N, -1)
|
||||
x = self.proj(x)
|
||||
x = self.proj_drop(x)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class AttentiveBlock(nn.Module):
|
||||
r"""Attentive Block
|
||||
Args:
|
||||
dim (int): Number of input channels.
|
||||
num_heads (int): Number of attention heads. Default: 8
|
||||
qkv_bias (bool, optional): If True, add a learnable bias to q, k, v.
|
||||
Default: False.
|
||||
qk_scale (float | None, optional): Override default qk scale of
|
||||
head_dim ** -0.5 if set. Default: None.
|
||||
drop (float, optional): Dropout rate. Default: 0.0.
|
||||
attn_drop (float, optional): Attention dropout rate. Default: 0.0.
|
||||
drop_path (float | tuple[float], optional): Stochastic depth rate.
|
||||
Default: 0.0.
|
||||
norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm.
|
||||
attn_head_dim (int, optional): Dimension of attention head. Default: None.
|
||||
out_dim (int, optional): Dimension of output. Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
dim,
|
||||
num_heads,
|
||||
qkv_bias=False,
|
||||
qk_scale=None,
|
||||
drop=0.,
|
||||
attn_drop=0.,
|
||||
drop_path=0.,
|
||||
norm_layer="LN",
|
||||
attn_head_dim=None,
|
||||
out_dim=None):
|
||||
super().__init__()
|
||||
|
||||
self.norm1_q = build_norm_layer(dim, norm_layer, eps=1e-6)
|
||||
self.norm1_k = build_norm_layer(dim, norm_layer, eps=1e-6)
|
||||
self.norm1_v = build_norm_layer(dim, norm_layer, eps=1e-6)
|
||||
self.cross_dcn = CrossAttention(dim,
|
||||
num_heads=num_heads,
|
||||
qkv_bias=qkv_bias,
|
||||
qk_scale=qk_scale,
|
||||
attn_drop=attn_drop,
|
||||
proj_drop=drop,
|
||||
attn_head_dim=attn_head_dim,
|
||||
out_dim=out_dim)
|
||||
|
||||
self.drop_path = DropPath(
|
||||
drop_path) if drop_path > 0. else nn.Identity()
|
||||
|
||||
def forward(self,
|
||||
x_q,
|
||||
x_kv,
|
||||
pos_q,
|
||||
pos_k,
|
||||
bool_masked_pos,
|
||||
rel_pos_bias=None):
|
||||
x_q = self.norm1_q(x_q + pos_q)
|
||||
x_k = self.norm1_k(x_kv + pos_k)
|
||||
x_v = self.norm1_v(x_kv)
|
||||
|
||||
x = self.cross_dcn(x_q, k=x_k, v=x_v)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class AttentionPoolingBlock(AttentiveBlock):
|
||||
|
||||
def forward(self, x):
|
||||
x_q = x.mean(1, keepdim=True)
|
||||
x_kv = x
|
||||
pos_q, pos_k = 0, 0
|
||||
x = super().forward(x_q, x_kv, pos_q, pos_k,
|
||||
bool_masked_pos=None,
|
||||
rel_pos_bias=None)
|
||||
x = x.squeeze(1)
|
||||
return x
|
||||
|
||||
|
||||
class StemLayer(nn.Module):
|
||||
r""" Stem layer of InternImage
|
||||
Args:
|
||||
in_chans (int): number of input channels
|
||||
out_chans (int): number of output channels
|
||||
act_layer (str): activation layer
|
||||
norm_layer (str): normalization layer
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_chans=3,
|
||||
out_chans=96,
|
||||
act_layer='GELU',
|
||||
norm_layer='BN'):
|
||||
super().__init__()
|
||||
self.conv1 = nn.Conv2d(in_chans,
|
||||
out_chans // 2,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1)
|
||||
self.norm1 = build_norm_layer(out_chans // 2, norm_layer,
|
||||
'channels_first', 'channels_first')
|
||||
self.act = build_act_layer(act_layer)
|
||||
self.conv2 = nn.Conv2d(out_chans // 2,
|
||||
out_chans,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1)
|
||||
self.norm2 = build_norm_layer(out_chans, norm_layer, 'channels_first',
|
||||
'channels_last')
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv1(x)
|
||||
x = self.norm1(x)
|
||||
x = self.act(x)
|
||||
x = self.conv2(x)
|
||||
x = self.norm2(x)
|
||||
return x
|
||||
|
||||
|
||||
class DownsampleLayer(nn.Module):
|
||||
r""" Downsample layer of InternImage
|
||||
Args:
|
||||
channels (int): number of input channels
|
||||
norm_layer (str): normalization layer
|
||||
"""
|
||||
|
||||
def __init__(self, channels, norm_layer='LN'):
|
||||
super().__init__()
|
||||
self.conv = nn.Conv2d(channels,
|
||||
2 * channels,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
bias=False)
|
||||
self.norm = build_norm_layer(2 * channels, norm_layer,
|
||||
'channels_first', 'channels_last')
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv(x.permute(0, 3, 1, 2))
|
||||
x = self.norm(x)
|
||||
return x
|
||||
|
||||
|
||||
class MLPLayer(nn.Module):
|
||||
r""" MLP layer of InternImage
|
||||
Args:
|
||||
in_features (int): number of input features
|
||||
hidden_features (int): number of hidden features
|
||||
out_features (int): number of output features
|
||||
act_layer (str): activation layer
|
||||
drop (float): dropout rate
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_features,
|
||||
hidden_features=None,
|
||||
out_features=None,
|
||||
act_layer='GELU',
|
||||
drop=0.):
|
||||
super().__init__()
|
||||
out_features = out_features or in_features
|
||||
hidden_features = hidden_features or in_features
|
||||
self.fc1 = nn.Linear(in_features, hidden_features)
|
||||
self.act = build_act_layer(act_layer)
|
||||
self.fc2 = nn.Linear(hidden_features, out_features)
|
||||
self.drop = nn.Dropout(drop)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.fc1(x)
|
||||
x = self.act(x)
|
||||
x = self.drop(x)
|
||||
x = self.fc2(x)
|
||||
x = self.drop(x)
|
||||
return x
|
||||
|
||||
|
||||
class InternImageLayer(nn.Module):
|
||||
r""" Basic layer of InternImage
|
||||
Args:
|
||||
core_op (nn.Module): core operation of InternImage
|
||||
channels (int): number of input channels
|
||||
groups (list): Groups of each block.
|
||||
mlp_ratio (float): ratio of mlp hidden features to input channels
|
||||
drop (float): dropout rate
|
||||
drop_path (float): drop path rate
|
||||
act_layer (str): activation layer
|
||||
norm_layer (str): normalization layer
|
||||
post_norm (bool): whether to use post normalization
|
||||
layer_scale (float): layer scale
|
||||
offset_scale (float): offset scale
|
||||
with_cp (bool): whether to use checkpoint
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
core_op,
|
||||
channels,
|
||||
groups,
|
||||
mlp_ratio=4.,
|
||||
drop=0.,
|
||||
drop_path=0.,
|
||||
act_layer='GELU',
|
||||
norm_layer='LN',
|
||||
post_norm=False,
|
||||
layer_scale=None,
|
||||
offset_scale=1.0,
|
||||
with_cp=False,
|
||||
dw_kernel_size=None, # for InternImage-H/G
|
||||
res_post_norm=False, # for InternImage-H/G
|
||||
center_feature_scale=False): # for InternImage-H/G
|
||||
super().__init__()
|
||||
self.channels = channels
|
||||
self.groups = groups
|
||||
self.mlp_ratio = mlp_ratio
|
||||
self.with_cp = with_cp
|
||||
|
||||
self.norm1 = build_norm_layer(channels, 'LN')
|
||||
self.post_norm = post_norm
|
||||
self.dcn = core_op(
|
||||
channels=channels,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
pad=1,
|
||||
dilation=1,
|
||||
group=groups,
|
||||
offset_scale=offset_scale,
|
||||
act_layer=act_layer,
|
||||
norm_layer=norm_layer,
|
||||
dw_kernel_size=dw_kernel_size, # for InternImage-H/G
|
||||
center_feature_scale=center_feature_scale) # for InternImage-H/G
|
||||
self.drop_path = DropPath(drop_path) if drop_path > 0. \
|
||||
else nn.Identity()
|
||||
self.norm2 = build_norm_layer(channels, 'LN')
|
||||
self.mlp = MLPLayer(in_features=channels,
|
||||
hidden_features=int(channels * mlp_ratio),
|
||||
act_layer=act_layer,
|
||||
drop=drop)
|
||||
self.layer_scale = layer_scale is not None
|
||||
if self.layer_scale:
|
||||
self.gamma1 = nn.Parameter(layer_scale * torch.ones(channels),
|
||||
requires_grad=True)
|
||||
self.gamma2 = nn.Parameter(layer_scale * torch.ones(channels),
|
||||
requires_grad=True)
|
||||
self.res_post_norm = res_post_norm
|
||||
if res_post_norm:
|
||||
self.res_post_norm1 = build_norm_layer(channels, 'LN')
|
||||
self.res_post_norm2 = build_norm_layer(channels, 'LN')
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
def _inner_forward(x):
|
||||
if not self.layer_scale:
|
||||
if self.post_norm:
|
||||
x = x + self.drop_path(self.norm1(self.dcn(x)))
|
||||
x = x + self.drop_path(self.norm2(self.mlp(x)))
|
||||
elif self.res_post_norm: # for InternImage-H/G
|
||||
x = x + self.drop_path(self.res_post_norm1(self.dcn(self.norm1(x))))
|
||||
x = x + self.drop_path(self.res_post_norm2(self.mlp(self.norm2(x))))
|
||||
else:
|
||||
x = x + self.drop_path(self.dcn(self.norm1(x)))
|
||||
x = x + self.drop_path(self.mlp(self.norm2(x)))
|
||||
return x
|
||||
if self.post_norm:
|
||||
x = x + self.drop_path(self.gamma1 * self.norm1(self.dcn(x)))
|
||||
x = x + self.drop_path(self.gamma2 * self.norm2(self.mlp(x)))
|
||||
else:
|
||||
x = x + self.drop_path(self.gamma1 * self.dcn(self.norm1(x)))
|
||||
x = x + self.drop_path(self.gamma2 * self.mlp(self.norm2(x)))
|
||||
return x
|
||||
|
||||
if self.with_cp and x.requires_grad:
|
||||
x = checkpoint.checkpoint(_inner_forward, x)
|
||||
else:
|
||||
x = _inner_forward(x)
|
||||
return x
|
||||
|
||||
|
||||
class InternImageBlock(nn.Module):
|
||||
r""" Block of InternImage
|
||||
Args:
|
||||
core_op (nn.Module): core operation of InternImage
|
||||
channels (int): number of input channels
|
||||
depths (list): Depth of each block.
|
||||
groups (list): Groups of each block.
|
||||
mlp_ratio (float): ratio of mlp hidden features to input channels
|
||||
drop (float): dropout rate
|
||||
drop_path (float): drop path rate
|
||||
act_layer (str): activation layer
|
||||
norm_layer (str): normalization layer
|
||||
post_norm (bool): whether to use post normalization
|
||||
layer_scale (float): layer scale
|
||||
offset_scale (float): offset scale
|
||||
with_cp (bool): whether to use checkpoint
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
core_op,
|
||||
channels,
|
||||
depth,
|
||||
groups,
|
||||
downsample=True,
|
||||
mlp_ratio=4.,
|
||||
drop=0.,
|
||||
drop_path=0.,
|
||||
act_layer='GELU',
|
||||
norm_layer='LN',
|
||||
post_norm=False,
|
||||
offset_scale=1.0,
|
||||
layer_scale=None,
|
||||
with_cp=False,
|
||||
dw_kernel_size=None, # for InternImage-H/G
|
||||
post_norm_block_ids=None, # for InternImage-H/G
|
||||
res_post_norm=False, # for InternImage-H/G
|
||||
center_feature_scale=False): # for InternImage-H/G
|
||||
super().__init__()
|
||||
self.channels = channels
|
||||
self.depth = depth
|
||||
self.post_norm = post_norm
|
||||
self.center_feature_scale = center_feature_scale
|
||||
|
||||
self.blocks = nn.ModuleList([
|
||||
InternImageLayer(
|
||||
core_op=core_op,
|
||||
channels=channels,
|
||||
groups=groups,
|
||||
mlp_ratio=mlp_ratio,
|
||||
drop=drop,
|
||||
drop_path=drop_path[i] if isinstance(
|
||||
drop_path, list) else drop_path,
|
||||
act_layer=act_layer,
|
||||
norm_layer=norm_layer,
|
||||
post_norm=post_norm,
|
||||
layer_scale=layer_scale,
|
||||
offset_scale=offset_scale,
|
||||
with_cp=with_cp,
|
||||
dw_kernel_size=dw_kernel_size, # for InternImage-H/G
|
||||
res_post_norm=res_post_norm, # for InternImage-H/G
|
||||
center_feature_scale=center_feature_scale # for InternImage-H/G
|
||||
) for i in range(depth)
|
||||
])
|
||||
if not self.post_norm or center_feature_scale:
|
||||
self.norm = build_norm_layer(channels, 'LN')
|
||||
self.post_norm_block_ids = post_norm_block_ids
|
||||
if post_norm_block_ids is not None: # for InternImage-H/G
|
||||
self.post_norms = nn.ModuleList(
|
||||
[build_norm_layer(channels, 'LN', eps=1e-6) for _ in post_norm_block_ids]
|
||||
)
|
||||
self.downsample = DownsampleLayer(
|
||||
channels=channels, norm_layer=norm_layer) if downsample else None
|
||||
|
||||
def forward(self, x, return_wo_downsample=False):
|
||||
for i, blk in enumerate(self.blocks):
|
||||
x = blk(x)
|
||||
if (self.post_norm_block_ids is not None) and (i in self.post_norm_block_ids):
|
||||
index = self.post_norm_block_ids.index(i)
|
||||
x = self.post_norms[index](x) # for InternImage-H/G
|
||||
if not self.post_norm or self.center_feature_scale:
|
||||
x = self.norm(x)
|
||||
if return_wo_downsample:
|
||||
x_ = x
|
||||
if self.downsample is not None:
|
||||
x = self.downsample(x)
|
||||
|
||||
if return_wo_downsample:
|
||||
return x, x_
|
||||
return x
|
||||
|
||||
|
||||
class InternImage(nn.Module):
|
||||
r""" InternImage
|
||||
A PyTorch impl of : `InternImage: Exploring Large-Scale Vision Foundation Models with Deformable Convolutions` -
|
||||
https://arxiv.org/pdf/2103.14030
|
||||
Args:
|
||||
core_op (str): Core operator. Default: 'DCNv3'
|
||||
channels (int): Number of the first stage. Default: 64
|
||||
depths (list): Depth of each block. Default: [3, 4, 18, 5]
|
||||
groups (list): Groups of each block. Default: [3, 6, 12, 24]
|
||||
num_classes (int): Number of classes. Default: 1000
|
||||
mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. Default: 4.
|
||||
drop_rate (float): Probability of an element to be zeroed. Default: 0.
|
||||
drop_path_rate (float): Stochastic depth rate. Default: 0.
|
||||
act_layer (str): Activation layer. Default: 'GELU'
|
||||
norm_layer (str): Normalization layer. Default: 'LN'
|
||||
layer_scale (bool): Whether to use layer scale. Default: False
|
||||
cls_scale (bool): Whether to use class scale. Default: False
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
dw_kernel_size (int): Size of the dwconv. Default: None
|
||||
use_clip_projector (bool): Whether to use clip projector. Default: False
|
||||
level2_post_norm (bool): Whether to use level2 post norm. Default: False
|
||||
level2_post_norm_block_ids (list): Indexes of post norm blocks. Default: None
|
||||
res_post_norm (bool): Whether to use res post norm. Default: False
|
||||
center_feature_scale (bool): Whether to use center feature scale. Default: False
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
core_op='DCNv3',
|
||||
channels=64,
|
||||
depths=[3, 4, 18, 5],
|
||||
groups=[3, 6, 12, 24],
|
||||
num_classes=1000,
|
||||
mlp_ratio=4.,
|
||||
drop_rate=0.,
|
||||
drop_path_rate=0.2,
|
||||
drop_path_type='linear',
|
||||
act_layer='GELU',
|
||||
norm_layer='LN',
|
||||
layer_scale=None,
|
||||
offset_scale=1.0,
|
||||
post_norm=False,
|
||||
cls_scale=1.5,
|
||||
with_cp=False,
|
||||
dw_kernel_size=None, # for InternImage-H/G
|
||||
use_clip_projector=False, # for InternImage-H/G
|
||||
level2_post_norm=False, # for InternImage-H/G
|
||||
level2_post_norm_block_ids=None, # for InternImage-H/G
|
||||
res_post_norm=False, # for InternImage-H/G
|
||||
center_feature_scale=False, # for InternImage-H/G
|
||||
**kwargs):
|
||||
super().__init__()
|
||||
self.core_op = core_op
|
||||
self.num_classes = num_classes
|
||||
self.num_levels = len(depths)
|
||||
self.depths = depths
|
||||
self.channels = channels
|
||||
self.num_features = int(channels * 2**(self.num_levels - 1))
|
||||
self.post_norm = post_norm
|
||||
self.mlp_ratio = mlp_ratio
|
||||
self.use_clip_projector = use_clip_projector
|
||||
self.level2_post_norm_block_ids = level2_post_norm_block_ids
|
||||
print(f'using core type: {core_op}')
|
||||
print(f'using activation layer: {act_layer}')
|
||||
print(f'using main norm layer: {norm_layer}')
|
||||
print(f'using dpr: {drop_path_type}, {drop_path_rate}')
|
||||
print(f"level2_post_norm: {level2_post_norm}")
|
||||
print(f"level2_post_norm_block_ids: {level2_post_norm_block_ids}")
|
||||
print(f"res_post_norm: {res_post_norm}")
|
||||
|
||||
in_chans = 3
|
||||
self.patch_embed = StemLayer(in_chans=in_chans,
|
||||
out_chans=channels,
|
||||
act_layer=act_layer,
|
||||
norm_layer=norm_layer)
|
||||
self.pos_drop = nn.Dropout(p=drop_rate)
|
||||
|
||||
dpr = [
|
||||
x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))
|
||||
]
|
||||
if drop_path_type == 'uniform':
|
||||
for i in range(len(dpr)):
|
||||
dpr[i] = drop_path_rate
|
||||
|
||||
self.levels = nn.ModuleList()
|
||||
for i in range(self.num_levels):
|
||||
post_norm_block_ids = level2_post_norm_block_ids if level2_post_norm and (
|
||||
i == 2) else None # for InternImage-H/G
|
||||
from ops_offset import modules as opsm
|
||||
level = InternImageBlock(
|
||||
core_op=getattr(opsm, core_op),
|
||||
channels=int(channels * 2**i),
|
||||
depth=depths[i],
|
||||
groups=groups[i],
|
||||
mlp_ratio=self.mlp_ratio,
|
||||
drop=drop_rate,
|
||||
drop_path=dpr[sum(depths[:i]):sum(depths[:i + 1])],
|
||||
act_layer=act_layer,
|
||||
norm_layer=norm_layer,
|
||||
post_norm=post_norm,
|
||||
downsample=(i < self.num_levels - 1),
|
||||
layer_scale=layer_scale,
|
||||
offset_scale=offset_scale,
|
||||
with_cp=with_cp,
|
||||
dw_kernel_size=dw_kernel_size, # for InternImage-H/G
|
||||
post_norm_block_ids=post_norm_block_ids, # for InternImage-H/G
|
||||
res_post_norm=res_post_norm, # for InternImage-H/G
|
||||
center_feature_scale=center_feature_scale # for InternImage-H/G
|
||||
)
|
||||
self.levels.append(level)
|
||||
|
||||
if not use_clip_projector: # for InternImage-T/S/B/L/XL
|
||||
self.conv_head = nn.Sequential(
|
||||
nn.Conv2d(self.num_features,
|
||||
int(self.num_features * cls_scale),
|
||||
kernel_size=1,
|
||||
bias=False),
|
||||
build_norm_layer(int(self.num_features * cls_scale), 'BN',
|
||||
'channels_first', 'channels_first'),
|
||||
build_act_layer(act_layer))
|
||||
self.head = nn.Linear(int(self.num_features * cls_scale), num_classes) \
|
||||
if num_classes > 0 else nn.Identity()
|
||||
else: # for InternImage-H/G
|
||||
pretrain_embed_dim, _stride, attnpool_num_heads, clip_embed_dim = 1024, 2, 16, 768
|
||||
self.dcnv3_head_x4 = nn.Sequential(
|
||||
nn.Conv2d(in_channels=self.num_features,
|
||||
out_channels=pretrain_embed_dim * (_stride ** 2),
|
||||
kernel_size=1), nn.PixelShuffle(_stride))
|
||||
self.dcnv3_head_x3 = nn.Conv2d(in_channels=self.num_features // 2,
|
||||
out_channels=pretrain_embed_dim,
|
||||
kernel_size=1)
|
||||
self.clip_projector = AttentionPoolingBlock(
|
||||
dim=pretrain_embed_dim,
|
||||
num_heads=attnpool_num_heads,
|
||||
qkv_bias=True,
|
||||
qk_scale=None,
|
||||
drop=0.,
|
||||
attn_drop=0.,
|
||||
norm_layer=norm_layer,
|
||||
out_dim=clip_embed_dim)
|
||||
self.fc_norm = build_norm_layer(clip_embed_dim, norm_layer, eps=1e-6)
|
||||
self.head = nn.Linear(
|
||||
clip_embed_dim, num_classes) if num_classes > 0 else nn.Identity()
|
||||
|
||||
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
|
||||
self.num_layers = len(depths)
|
||||
self.apply(self._init_weights)
|
||||
self.apply(self._init_deform_weights)
|
||||
|
||||
def _init_weights(self, m):
|
||||
if isinstance(m, nn.Linear):
|
||||
trunc_normal_(m.weight, std=.02)
|
||||
if isinstance(m, nn.Linear) and m.bias is not None:
|
||||
nn.init.constant_(m.bias, 0)
|
||||
elif isinstance(m, nn.LayerNorm):
|
||||
nn.init.constant_(m.bias, 0)
|
||||
nn.init.constant_(m.weight, 1.0)
|
||||
|
||||
def _init_deform_weights(self, m):
|
||||
from ops_offset import modules as opsm
|
||||
if isinstance(m, getattr(opsm, self.core_op)):
|
||||
m._reset_parameters()
|
||||
|
||||
@torch.jit.ignore
|
||||
def lr_decay_keywards(self, decay_ratio=0.87):
|
||||
lr_ratios = {}
|
||||
|
||||
# blocks
|
||||
idx = 0
|
||||
for i in range(4):
|
||||
layer_num = 3 - i # 3 2 1 0
|
||||
for j in range(self.depths[layer_num]):
|
||||
block_num = self.depths[layer_num] - j - 1
|
||||
tag = 'levels.{}.blocks.{}.'.format(layer_num, block_num)
|
||||
decay = 1.0 * (decay_ratio**idx)
|
||||
lr_ratios[tag] = decay
|
||||
idx += 1
|
||||
# patch_embed (before stage-1)
|
||||
lr_ratios["patch_embed"] = lr_ratios['levels.0.blocks.0.']
|
||||
# levels.0.downsample (between stage-1 and stage-2)
|
||||
lr_ratios["levels.0.downsample"] = lr_ratios['levels.1.blocks.0.']
|
||||
lr_ratios["levels.0.norm"] = lr_ratios['levels.1.blocks.0.']
|
||||
# levels.1.downsample (between stage-2 and stage-3)
|
||||
lr_ratios["levels.1.downsample"] = lr_ratios['levels.2.blocks.0.']
|
||||
lr_ratios["levels.1.norm"] = lr_ratios['levels.2.blocks.0.']
|
||||
# levels.2.downsample (between stage-3 and stage-4)
|
||||
lr_ratios["levels.2.downsample"] = lr_ratios['levels.3.blocks.0.']
|
||||
lr_ratios["levels.2.norm"] = lr_ratios['levels.3.blocks.0.']
|
||||
return lr_ratios
|
||||
|
||||
def forward_features(self, x):
|
||||
x = self.patch_embed(x)
|
||||
x = self.pos_drop(x)
|
||||
|
||||
for level in self.levels:
|
||||
x = level(x)
|
||||
|
||||
x = self.conv_head(x.permute(0, 3, 1, 2))
|
||||
x = self.avgpool(x)
|
||||
x = torch.flatten(x, 1)
|
||||
return x
|
||||
|
||||
def forward_features_seq_out(self, x):
|
||||
x = self.patch_embed(x)
|
||||
x = self.pos_drop(x)
|
||||
|
||||
seq_out = []
|
||||
for level in self.levels:
|
||||
x, x_ = level(x, return_wo_downsample=True)
|
||||
seq_out.append(x_)
|
||||
return seq_out
|
||||
|
||||
def forward_clip_projector(self, x): # for InternImage-H/G
|
||||
xs = self.forward_features_seq_out(x)
|
||||
x1, x2, x3, x4 = xs
|
||||
|
||||
x1 = x1.permute(0, 3, 1, 2) # NHWC -> NCHW
|
||||
x2 = x2.permute(0, 3, 1, 2) # NHWC -> NCHW
|
||||
x3 = x3.permute(0, 3, 1, 2) # NHWC -> NCHW
|
||||
x4 = x4.permute(0, 3, 1, 2) # NHWC -> NCHW
|
||||
|
||||
x4 = self.dcnv3_head_x4(x4)
|
||||
x = x4
|
||||
x3 = self.dcnv3_head_x3(x3)
|
||||
x = x + x3
|
||||
|
||||
x = x.flatten(-2).transpose(1, 2).contiguous()
|
||||
x = self.clip_projector(x)
|
||||
x = self.fc_norm(x)
|
||||
|
||||
return x
|
||||
|
||||
def forward(self, x):
|
||||
if self.use_clip_projector: # for InternImage-H/G
|
||||
x = self.forward_clip_projector(x)
|
||||
else: # for InternImage-T/S/B/L/XL
|
||||
x = self.forward_features(x)
|
||||
x = self.head(x)
|
||||
return x
|
||||
7
classification/ops_dcnv3/functions/__init__.py
Normal file
7
classification/ops_dcnv3/functions/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from .dcnv3_func import DCNv3Function, dcnv3_core_pytorch
|
||||
220
classification/ops_dcnv3/functions/dcnv3_func.py
Normal file
220
classification/ops_dcnv3/functions/dcnv3_func.py
Normal file
@@ -0,0 +1,220 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch.autograd import Function
|
||||
from torch.autograd.function import once_differentiable
|
||||
from torch.cuda.amp import custom_bwd, custom_fwd
|
||||
import DCNv3
|
||||
|
||||
import pkg_resources
|
||||
dcn_version = float(pkg_resources.get_distribution('DCNv3').version)
|
||||
|
||||
|
||||
class DCNv3Function(Function):
|
||||
@staticmethod
|
||||
@custom_fwd
|
||||
def forward(
|
||||
ctx, input, offset, mask,
|
||||
kernel_h, kernel_w, stride_h, stride_w,
|
||||
pad_h, pad_w, dilation_h, dilation_w,
|
||||
group, group_channels, offset_scale, im2col_step, remove_center):
|
||||
ctx.kernel_h = kernel_h
|
||||
ctx.kernel_w = kernel_w
|
||||
ctx.stride_h = stride_h
|
||||
ctx.stride_w = stride_w
|
||||
ctx.pad_h = pad_h
|
||||
ctx.pad_w = pad_w
|
||||
ctx.dilation_h = dilation_h
|
||||
ctx.dilation_w = dilation_w
|
||||
ctx.group = group
|
||||
ctx.group_channels = group_channels
|
||||
ctx.offset_scale = offset_scale
|
||||
ctx.im2col_step = im2col_step
|
||||
ctx.remove_center = remove_center
|
||||
|
||||
args = [
|
||||
input, offset, mask, kernel_h,
|
||||
kernel_w, stride_h, stride_w, pad_h,
|
||||
pad_w, dilation_h, dilation_w, group,
|
||||
group_channels, offset_scale, ctx.im2col_step
|
||||
]
|
||||
if remove_center or dcn_version > 1.0:
|
||||
args.append(remove_center)
|
||||
|
||||
output = DCNv3.dcnv3_forward(*args)
|
||||
ctx.save_for_backward(input, offset, mask)
|
||||
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
@once_differentiable
|
||||
@custom_bwd
|
||||
def backward(ctx, grad_output):
|
||||
input, offset, mask = ctx.saved_tensors
|
||||
|
||||
args = [
|
||||
input, offset, mask, ctx.kernel_h,
|
||||
ctx.kernel_w, ctx.stride_h, ctx.stride_w, ctx.pad_h,
|
||||
ctx.pad_w, ctx.dilation_h, ctx.dilation_w, ctx.group,
|
||||
ctx.group_channels, ctx.offset_scale, grad_output.contiguous(), ctx.im2col_step
|
||||
]
|
||||
if ctx.remove_center or dcn_version > 1.0:
|
||||
args.append(ctx.remove_center)
|
||||
|
||||
grad_input, grad_offset, grad_mask = \
|
||||
DCNv3.dcnv3_backward(*args)
|
||||
|
||||
return grad_input, grad_offset, grad_mask, \
|
||||
None, None, None, None, None, None, None, None, None, None, None, None, None
|
||||
|
||||
@staticmethod
|
||||
def symbolic(g, input, offset, mask, kernel_h, kernel_w, stride_h,
|
||||
stride_w, pad_h, pad_w, dilation_h, dilation_w, group,
|
||||
group_channels, offset_scale, im2col_step, remove_center):
|
||||
"""Symbolic function for mmdeploy::DCNv3.
|
||||
|
||||
Returns:
|
||||
DCNv3 op for onnx.
|
||||
"""
|
||||
return g.op(
|
||||
'mmdeploy::TRTDCNv3',
|
||||
input,
|
||||
offset,
|
||||
mask,
|
||||
kernel_h_i=int(kernel_h),
|
||||
kernel_w_i=int(kernel_w),
|
||||
stride_h_i=int(stride_h),
|
||||
stride_w_i=int(stride_w),
|
||||
pad_h_i=int(pad_h),
|
||||
pad_w_i=int(pad_w),
|
||||
dilation_h_i=int(dilation_h),
|
||||
dilation_w_i=int(dilation_w),
|
||||
group_i=int(group),
|
||||
group_channels_i=int(group_channels),
|
||||
offset_scale_f=float(offset_scale),
|
||||
im2col_step_i=int(im2col_step),
|
||||
remove_center=int(remove_center),
|
||||
)
|
||||
|
||||
|
||||
def _get_reference_points(spatial_shapes, device, kernel_h, kernel_w, dilation_h, dilation_w, pad_h=0, pad_w=0, stride_h=1, stride_w=1):
|
||||
_, H_, W_, _ = spatial_shapes
|
||||
H_out = (H_ - (dilation_h * (kernel_h - 1) + 1)) // stride_h + 1
|
||||
W_out = (W_ - (dilation_w * (kernel_w - 1) + 1)) // stride_w + 1
|
||||
|
||||
ref_y, ref_x = torch.meshgrid(
|
||||
torch.linspace(
|
||||
# pad_h + 0.5,
|
||||
# H_ - pad_h - 0.5,
|
||||
(dilation_h * (kernel_h - 1)) // 2 + 0.5,
|
||||
(dilation_h * (kernel_h - 1)) // 2 + 0.5 + (H_out - 1) * stride_h,
|
||||
H_out,
|
||||
dtype=torch.float32,
|
||||
device=device),
|
||||
torch.linspace(
|
||||
# pad_w + 0.5,
|
||||
# W_ - pad_w - 0.5,
|
||||
(dilation_w * (kernel_w - 1)) // 2 + 0.5,
|
||||
(dilation_w * (kernel_w - 1)) // 2 + 0.5 + (W_out - 1) * stride_w,
|
||||
W_out,
|
||||
dtype=torch.float32,
|
||||
device=device))
|
||||
ref_y = ref_y.reshape(-1)[None] / H_
|
||||
ref_x = ref_x.reshape(-1)[None] / W_
|
||||
|
||||
ref = torch.stack((ref_x, ref_y), -1).reshape(
|
||||
1, H_out, W_out, 1, 2)
|
||||
|
||||
return ref
|
||||
|
||||
|
||||
def _generate_dilation_grids(spatial_shapes, kernel_h, kernel_w, dilation_h, dilation_w, group, device):
|
||||
_, H_, W_, _ = spatial_shapes
|
||||
points_list = []
|
||||
x, y = torch.meshgrid(
|
||||
torch.linspace(
|
||||
-((dilation_w * (kernel_w - 1)) // 2),
|
||||
-((dilation_w * (kernel_w - 1)) // 2) + (kernel_w - 1) * dilation_w,
|
||||
kernel_w,
|
||||
dtype=torch.float32,
|
||||
device=device),
|
||||
torch.linspace(
|
||||
-((dilation_h * (kernel_h - 1)) // 2),
|
||||
-((dilation_h * (kernel_h - 1)) // 2) + (kernel_h - 1) * dilation_h,
|
||||
kernel_h,
|
||||
dtype=torch.float32,
|
||||
device=device))
|
||||
|
||||
points_list.extend([x / W_, y / H_])
|
||||
grid = torch.stack(points_list, -1).reshape(-1, 1, 2).\
|
||||
repeat(1, group, 1).permute(1, 0, 2)
|
||||
grid = grid.reshape(1, 1, 1, group * kernel_h * kernel_w, 2)
|
||||
|
||||
return grid
|
||||
|
||||
|
||||
def remove_center_sampling_locations(sampling_locations, kernel_w, kernel_h):
|
||||
idx = list(range(sampling_locations.shape[-2]))
|
||||
C = (kernel_w * kernel_h - 1)//2
|
||||
idx = [i for i in idx if i != C and (i-C) % (C*2+1) != 0]
|
||||
sampling_locations = sampling_locations[:,:,:,idx, :]
|
||||
return sampling_locations
|
||||
|
||||
def dcnv3_core_pytorch(
|
||||
input, offset, mask, kernel_h,
|
||||
kernel_w, stride_h, stride_w, pad_h,
|
||||
pad_w, dilation_h, dilation_w, group,
|
||||
group_channels, offset_scale, remove_center):
|
||||
# for debug and test only,
|
||||
# need to use cuda version instead
|
||||
|
||||
if remove_center and (kernel_h % 2 == 0 or kernel_w % 2 == 0 or kernel_w != kernel_h):
|
||||
raise ValueError('remove_center is only compatible with square odd kernel size.')
|
||||
|
||||
input = F.pad(
|
||||
input,
|
||||
[0, 0, pad_h, pad_h, pad_w, pad_w])
|
||||
N_, H_in, W_in, _ = input.shape
|
||||
_, H_out, W_out, _ = offset.shape
|
||||
|
||||
ref = _get_reference_points(
|
||||
input.shape, input.device, kernel_h, kernel_w, dilation_h, dilation_w, pad_h, pad_w, stride_h, stride_w)
|
||||
grid = _generate_dilation_grids(
|
||||
input.shape, kernel_h, kernel_w, dilation_h, dilation_w, group, input.device)
|
||||
spatial_norm = torch.tensor([W_in, H_in]).reshape(1, 1, 1, 2).\
|
||||
repeat(1, 1, 1, group*(kernel_h*kernel_w-remove_center)).to(input.device)
|
||||
|
||||
sampling_locations = (ref + grid * offset_scale).repeat(N_, 1, 1, 1, 1)
|
||||
if remove_center:
|
||||
sampling_locations = remove_center_sampling_locations(sampling_locations, kernel_w=kernel_w, kernel_h=kernel_h)
|
||||
sampling_locations = sampling_locations.flatten(3, 4)
|
||||
sampling_locations = sampling_locations + offset * offset_scale / spatial_norm
|
||||
|
||||
P_ = kernel_h * kernel_w - remove_center
|
||||
sampling_grids = 2 * sampling_locations - 1
|
||||
# N_, H_in, W_in, group*group_channels -> N_, H_in*W_in, group*group_channels -> N_, group*group_channels, H_in*W_in -> N_*group, group_channels, H_in, W_in
|
||||
input_ = input.view(N_, H_in*W_in, group*group_channels).transpose(1, 2).\
|
||||
reshape(N_*group, group_channels, H_in, W_in)
|
||||
# N_, H_out, W_out, group*P_*2 -> N_, H_out*W_out, group, P_, 2 -> N_, group, H_out*W_out, P_, 2 -> N_*group, H_out*W_out, P_, 2
|
||||
sampling_grid_ = sampling_grids.view(N_, H_out*W_out, group, P_, 2).transpose(1, 2).\
|
||||
flatten(0, 1)
|
||||
# N_*group, group_channels, H_out*W_out, P_
|
||||
sampling_input_ = F.grid_sample(
|
||||
input_, sampling_grid_, mode='bilinear', padding_mode='zeros', align_corners=False)
|
||||
|
||||
# (N_, H_out, W_out, group*P_) -> N_, H_out*W_out, group, P_ -> (N_, group, H_out*W_out, P_) -> (N_*group, 1, H_out*W_out, P_)
|
||||
mask = mask.view(N_, H_out*W_out, group, P_).transpose(1, 2).\
|
||||
reshape(N_*group, 1, H_out*W_out, P_)
|
||||
output = (sampling_input_ * mask).sum(-1).view(N_,
|
||||
group*group_channels, H_out*W_out)
|
||||
|
||||
return output.transpose(1, 2).reshape(N_, H_out, W_out, -1).contiguous()
|
||||
8
classification/ops_dcnv3/make.sh
Executable file
8
classification/ops_dcnv3/make.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
python setup.py build install
|
||||
7
classification/ops_dcnv3/modules/__init__.py
Normal file
7
classification/ops_dcnv3/modules/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from .dcnv3 import DCNv3, DCNv3_pytorch
|
||||
357
classification/ops_dcnv3/modules/dcnv3.py
Normal file
357
classification/ops_dcnv3/modules/dcnv3.py
Normal file
@@ -0,0 +1,357 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
|
||||
import warnings
|
||||
import torch
|
||||
from torch import nn
|
||||
import torch.nn.functional as F
|
||||
from torch.nn.init import xavier_uniform_, constant_
|
||||
from ..functions import DCNv3Function, dcnv3_core_pytorch
|
||||
|
||||
|
||||
class to_channels_first(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def forward(self, x):
|
||||
return x.permute(0, 3, 1, 2)
|
||||
|
||||
|
||||
class to_channels_last(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def forward(self, x):
|
||||
return x.permute(0, 2, 3, 1)
|
||||
|
||||
|
||||
def build_norm_layer(dim,
|
||||
norm_layer,
|
||||
in_format='channels_last',
|
||||
out_format='channels_last',
|
||||
eps=1e-6):
|
||||
layers = []
|
||||
if norm_layer == 'BN':
|
||||
if in_format == 'channels_last':
|
||||
layers.append(to_channels_first())
|
||||
layers.append(nn.BatchNorm2d(dim))
|
||||
if out_format == 'channels_last':
|
||||
layers.append(to_channels_last())
|
||||
elif norm_layer == 'LN':
|
||||
if in_format == 'channels_first':
|
||||
layers.append(to_channels_last())
|
||||
layers.append(nn.LayerNorm(dim, eps=eps))
|
||||
if out_format == 'channels_first':
|
||||
layers.append(to_channels_first())
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f'build_norm_layer does not support {norm_layer}')
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
|
||||
def build_act_layer(act_layer):
|
||||
if act_layer == 'ReLU':
|
||||
return nn.ReLU(inplace=True)
|
||||
elif act_layer == 'SiLU':
|
||||
return nn.SiLU(inplace=True)
|
||||
elif act_layer == 'GELU':
|
||||
return nn.GELU()
|
||||
|
||||
raise NotImplementedError(f'build_act_layer does not support {act_layer}')
|
||||
|
||||
|
||||
def _is_power_of_2(n):
|
||||
if (not isinstance(n, int)) or (n < 0):
|
||||
raise ValueError(
|
||||
"invalid input for _is_power_of_2: {} (type: {})".format(n, type(n)))
|
||||
|
||||
return (n & (n - 1) == 0) and n != 0
|
||||
|
||||
|
||||
class CenterFeatureScaleModule(nn.Module):
|
||||
def forward(self,
|
||||
query,
|
||||
center_feature_scale_proj_weight,
|
||||
center_feature_scale_proj_bias):
|
||||
center_feature_scale = F.linear(query,
|
||||
weight=center_feature_scale_proj_weight,
|
||||
bias=center_feature_scale_proj_bias).sigmoid()
|
||||
return center_feature_scale
|
||||
|
||||
|
||||
class DCNv3_pytorch(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
channels=64,
|
||||
kernel_size=3,
|
||||
dw_kernel_size=None,
|
||||
stride=1,
|
||||
pad=1,
|
||||
dilation=1,
|
||||
group=4,
|
||||
offset_scale=1.0,
|
||||
act_layer='GELU',
|
||||
norm_layer='LN',
|
||||
center_feature_scale=False,
|
||||
remove_center=False,
|
||||
):
|
||||
"""
|
||||
DCNv3 Module
|
||||
:param channels
|
||||
:param kernel_size
|
||||
:param stride
|
||||
:param pad
|
||||
:param dilation
|
||||
:param group
|
||||
:param offset_scale
|
||||
:param act_layer
|
||||
:param norm_layer
|
||||
"""
|
||||
super().__init__()
|
||||
if channels % group != 0:
|
||||
raise ValueError(
|
||||
f'channels must be divisible by group, but got {channels} and {group}')
|
||||
_d_per_group = channels // group
|
||||
dw_kernel_size = dw_kernel_size if dw_kernel_size is not None else kernel_size
|
||||
# you'd better set _d_per_group to a power of 2 which is more efficient in our CUDA implementation
|
||||
if not _is_power_of_2(_d_per_group):
|
||||
warnings.warn(
|
||||
"You'd better set channels in DCNv3 to make the dimension of each attention head a power of 2 "
|
||||
"which is more efficient in our CUDA implementation.")
|
||||
|
||||
self.offset_scale = offset_scale
|
||||
self.channels = channels
|
||||
self.kernel_size = kernel_size
|
||||
self.dw_kernel_size = dw_kernel_size
|
||||
self.stride = stride
|
||||
self.dilation = dilation
|
||||
self.pad = pad
|
||||
self.group = group
|
||||
self.group_channels = channels // group
|
||||
self.offset_scale = offset_scale
|
||||
self.center_feature_scale = center_feature_scale
|
||||
self.remove_center = int(remove_center)
|
||||
|
||||
self.dw_conv = nn.Sequential(
|
||||
nn.Conv2d(
|
||||
channels,
|
||||
channels,
|
||||
kernel_size=dw_kernel_size,
|
||||
stride=1,
|
||||
padding=(dw_kernel_size - 1) // 2,
|
||||
groups=channels),
|
||||
build_norm_layer(
|
||||
channels,
|
||||
norm_layer,
|
||||
'channels_first',
|
||||
'channels_last'),
|
||||
build_act_layer(act_layer))
|
||||
self.offset = nn.Linear(
|
||||
channels,
|
||||
group * (kernel_size * kernel_size - remove_center) * 2)
|
||||
self.mask = nn.Linear(
|
||||
channels,
|
||||
group * (kernel_size * kernel_size - remove_center))
|
||||
self.input_proj = nn.Linear(channels, channels)
|
||||
self.output_proj = nn.Linear(channels, channels)
|
||||
self._reset_parameters()
|
||||
|
||||
if center_feature_scale:
|
||||
self.center_feature_scale_proj_weight = nn.Parameter(
|
||||
torch.zeros((group, channels), dtype=torch.float))
|
||||
self.center_feature_scale_proj_bias = nn.Parameter(
|
||||
torch.tensor(0.0, dtype=torch.float).view((1,)).repeat(group, ))
|
||||
self.center_feature_scale_module = CenterFeatureScaleModule()
|
||||
|
||||
def _reset_parameters(self):
|
||||
constant_(self.offset.weight.data, 0.)
|
||||
constant_(self.offset.bias.data, 0.)
|
||||
constant_(self.mask.weight.data, 0.)
|
||||
constant_(self.mask.bias.data, 0.)
|
||||
xavier_uniform_(self.input_proj.weight.data)
|
||||
constant_(self.input_proj.bias.data, 0.)
|
||||
xavier_uniform_(self.output_proj.weight.data)
|
||||
constant_(self.output_proj.bias.data, 0.)
|
||||
|
||||
def forward(self, input):
|
||||
"""
|
||||
:param query (N, H, W, C)
|
||||
:return output (N, H, W, C)
|
||||
"""
|
||||
N, H, W, _ = input.shape
|
||||
|
||||
x = self.input_proj(input)
|
||||
x_proj = x
|
||||
|
||||
x1 = input.permute(0, 3, 1, 2)
|
||||
x1 = self.dw_conv(x1)
|
||||
offset = self.offset(x1)
|
||||
mask = self.mask(x1).reshape(N, H, W, self.group, -1)
|
||||
mask = F.softmax(mask, -1).reshape(N, H, W, -1)
|
||||
|
||||
x = dcnv3_core_pytorch(
|
||||
x, offset, mask,
|
||||
self.kernel_size, self.kernel_size,
|
||||
self.stride, self.stride,
|
||||
self.pad, self.pad,
|
||||
self.dilation, self.dilation,
|
||||
self.group, self.group_channels,
|
||||
self.offset_scale, self.remove_center)
|
||||
if self.center_feature_scale:
|
||||
center_feature_scale = self.center_feature_scale_module(
|
||||
x1, self.center_feature_scale_proj_weight, self.center_feature_scale_proj_bias)
|
||||
# N, H, W, groups -> N, H, W, groups, 1 -> N, H, W, groups, _d_per_group -> N, H, W, channels
|
||||
center_feature_scale = center_feature_scale[..., None].repeat(
|
||||
1, 1, 1, 1, self.channels // self.group).flatten(-2)
|
||||
x = x * (1 - center_feature_scale) + x_proj * center_feature_scale
|
||||
x = self.output_proj(x)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class DCNv3(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
channels=64,
|
||||
kernel_size=3,
|
||||
dw_kernel_size=None,
|
||||
stride=1,
|
||||
pad=1,
|
||||
dilation=1,
|
||||
group=4,
|
||||
offset_scale=1.0,
|
||||
act_layer='GELU',
|
||||
norm_layer='LN',
|
||||
center_feature_scale=False,
|
||||
remove_center=False,
|
||||
):
|
||||
"""
|
||||
DCNv3 Module
|
||||
:param channels
|
||||
:param kernel_size
|
||||
:param stride
|
||||
:param pad
|
||||
:param dilation
|
||||
:param group
|
||||
:param offset_scale
|
||||
:param act_layer
|
||||
:param norm_layer
|
||||
"""
|
||||
super().__init__()
|
||||
if channels % group != 0:
|
||||
raise ValueError(
|
||||
f'channels must be divisible by group, but got {channels} and {group}')
|
||||
_d_per_group = channels // group
|
||||
dw_kernel_size = dw_kernel_size if dw_kernel_size is not None else kernel_size
|
||||
# you'd better set _d_per_group to a power of 2 which is more efficient in our CUDA implementation
|
||||
if not _is_power_of_2(_d_per_group):
|
||||
warnings.warn(
|
||||
"You'd better set channels in DCNv3 to make the dimension of each attention head a power of 2 "
|
||||
"which is more efficient in our CUDA implementation.")
|
||||
|
||||
self.offset_scale = offset_scale
|
||||
self.channels = channels
|
||||
self.kernel_size = kernel_size
|
||||
self.dw_kernel_size = dw_kernel_size
|
||||
self.stride = stride
|
||||
self.dilation = dilation
|
||||
self.pad = pad
|
||||
self.group = group
|
||||
self.group_channels = channels // group
|
||||
self.offset_scale = offset_scale
|
||||
self.center_feature_scale = center_feature_scale
|
||||
self.remove_center = int(remove_center)
|
||||
|
||||
if self.remove_center and self.kernel_size % 2 == 0:
|
||||
raise ValueError('remove_center is only compatible with odd kernel size.')
|
||||
|
||||
self.dw_conv = nn.Sequential(
|
||||
nn.Conv2d(
|
||||
channels,
|
||||
channels,
|
||||
kernel_size=dw_kernel_size,
|
||||
stride=1,
|
||||
padding=(dw_kernel_size - 1) // 2,
|
||||
groups=channels),
|
||||
build_norm_layer(
|
||||
channels,
|
||||
norm_layer,
|
||||
'channels_first',
|
||||
'channels_last'),
|
||||
build_act_layer(act_layer))
|
||||
self.offset = nn.Linear(
|
||||
channels,
|
||||
group * (kernel_size * kernel_size - remove_center) * 2)
|
||||
self.mask = nn.Linear(
|
||||
channels,
|
||||
group * (kernel_size * kernel_size - remove_center))
|
||||
self.input_proj = nn.Linear(channels, channels)
|
||||
self.output_proj = nn.Linear(channels, channels)
|
||||
self._reset_parameters()
|
||||
|
||||
if center_feature_scale:
|
||||
self.center_feature_scale_proj_weight = nn.Parameter(
|
||||
torch.zeros((group, channels), dtype=torch.float))
|
||||
self.center_feature_scale_proj_bias = nn.Parameter(
|
||||
torch.tensor(0.0, dtype=torch.float).view((1,)).repeat(group, ))
|
||||
self.center_feature_scale_module = CenterFeatureScaleModule()
|
||||
|
||||
def _reset_parameters(self):
|
||||
constant_(self.offset.weight.data, 0.)
|
||||
constant_(self.offset.bias.data, 0.)
|
||||
constant_(self.mask.weight.data, 0.)
|
||||
constant_(self.mask.bias.data, 0.)
|
||||
xavier_uniform_(self.input_proj.weight.data)
|
||||
constant_(self.input_proj.bias.data, 0.)
|
||||
xavier_uniform_(self.output_proj.weight.data)
|
||||
constant_(self.output_proj.bias.data, 0.)
|
||||
|
||||
def forward(self, input):
|
||||
"""
|
||||
:param query (N, H, W, C)
|
||||
:return output (N, H, W, C)
|
||||
"""
|
||||
N, H, W, _ = input.shape
|
||||
|
||||
x = self.input_proj(input)
|
||||
x_proj = x
|
||||
dtype = x.dtype
|
||||
|
||||
x1 = input.permute(0, 3, 1, 2)
|
||||
x1 = self.dw_conv(x1)
|
||||
offset = self.offset(x1)
|
||||
mask = self.mask(x1).reshape(N, H, W, self.group, -1)
|
||||
mask = F.softmax(mask, -1)
|
||||
mask = mask.reshape(N, H, W, -1).type(dtype)
|
||||
|
||||
x = DCNv3Function.apply(
|
||||
x, offset, mask,
|
||||
self.kernel_size, self.kernel_size,
|
||||
self.stride, self.stride,
|
||||
self.pad, self.pad,
|
||||
self.dilation, self.dilation,
|
||||
self.group, self.group_channels,
|
||||
self.offset_scale,
|
||||
256,
|
||||
self.remove_center)
|
||||
|
||||
if self.center_feature_scale:
|
||||
center_feature_scale = self.center_feature_scale_module(
|
||||
x1, self.center_feature_scale_proj_weight, self.center_feature_scale_proj_bias)
|
||||
# N, H, W, groups -> N, H, W, groups, 1 -> N, H, W, groups, _d_per_group -> N, H, W, channels
|
||||
center_feature_scale = center_feature_scale[..., None].repeat(
|
||||
1, 1, 1, 1, self.channels // self.group).flatten(-2)
|
||||
x = x * (1 - center_feature_scale) + x_proj * center_feature_scale
|
||||
x = self.output_proj(x)
|
||||
|
||||
return x
|
||||
75
classification/ops_dcnv3/setup.py
Normal file
75
classification/ops_dcnv3/setup.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
import os
|
||||
import glob
|
||||
|
||||
import torch
|
||||
|
||||
from torch.utils.cpp_extension import CUDA_HOME
|
||||
from torch.utils.cpp_extension import CppExtension
|
||||
from torch.utils.cpp_extension import CUDAExtension
|
||||
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
requirements = ["torch", "torchvision"]
|
||||
|
||||
|
||||
def get_extensions():
|
||||
this_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
extensions_dir = os.path.join(this_dir, "src")
|
||||
|
||||
main_file = glob.glob(os.path.join(extensions_dir, "*.cpp"))
|
||||
source_cpu = glob.glob(os.path.join(extensions_dir, "cpu", "*.cpp"))
|
||||
source_cuda = glob.glob(os.path.join(extensions_dir, "cuda", "*.cu"))
|
||||
|
||||
sources = main_file + source_cpu
|
||||
extension = CppExtension
|
||||
extra_compile_args = {"cxx": []}
|
||||
define_macros = []
|
||||
|
||||
if torch.cuda.is_available() and CUDA_HOME is not None:
|
||||
extension = CUDAExtension
|
||||
sources += source_cuda
|
||||
define_macros += [("WITH_CUDA", None)]
|
||||
extra_compile_args["nvcc"] = [
|
||||
# "-DCUDA_HAS_FP16=1",
|
||||
# "-D__CUDA_NO_HALF_OPERATORS__",
|
||||
# "-D__CUDA_NO_HALF_CONVERSIONS__",
|
||||
# "-D__CUDA_NO_HALF2_OPERATORS__",
|
||||
]
|
||||
else:
|
||||
raise NotImplementedError('Cuda is not availabel')
|
||||
|
||||
sources = [os.path.join(extensions_dir, s) for s in sources]
|
||||
include_dirs = [extensions_dir]
|
||||
ext_modules = [
|
||||
extension(
|
||||
"DCNv3",
|
||||
sources,
|
||||
include_dirs=include_dirs,
|
||||
define_macros=define_macros,
|
||||
extra_compile_args=extra_compile_args,
|
||||
)
|
||||
]
|
||||
return ext_modules
|
||||
|
||||
|
||||
setup(
|
||||
name="DCNv3",
|
||||
version="1.1",
|
||||
author="InternImage",
|
||||
url="https://github.com/OpenGVLab/InternImage",
|
||||
description=
|
||||
"PyTorch Wrapper for CUDA Functions of DCNv3",
|
||||
packages=find_packages(exclude=(
|
||||
"configs",
|
||||
"tests",
|
||||
)),
|
||||
ext_modules=get_extensions(),
|
||||
cmdclass={"build_ext": torch.utils.cpp_extension.BuildExtension},
|
||||
)
|
||||
37
classification/ops_dcnv3/src/cpu/dcnv3_cpu.cpp
Normal file
37
classification/ops_dcnv3/src/cpu/dcnv3_cpu.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
/*!
|
||||
**************************************************************************************************
|
||||
* InternImage
|
||||
* Copyright (c) 2022 OpenGVLab
|
||||
* Licensed under The MIT License [see LICENSE for details]
|
||||
**************************************************************************************************
|
||||
* Modified from
|
||||
*https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch/tree/pytorch_1.0.0
|
||||
**************************************************************************************************
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <ATen/ATen.h>
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
|
||||
at::Tensor dcnv3_cpu_forward(const at::Tensor &input, const at::Tensor &offset,
|
||||
const at::Tensor &mask, const int kernel_h,
|
||||
const int kernel_w, const int stride_h,
|
||||
const int stride_w, const int pad_h,
|
||||
const int pad_w, const int dilation_h,
|
||||
const int dilation_w, const int group,
|
||||
const int group_channels, const float offset_scale,
|
||||
const int im2col_step) {
|
||||
AT_ERROR("Not implement on cpu");
|
||||
}
|
||||
|
||||
std::vector<at::Tensor>
|
||||
dcnv3_cpu_backward(const at::Tensor &input, const at::Tensor &offset,
|
||||
const at::Tensor &mask, const int kernel_h,
|
||||
const int kernel_w, const int stride_h, const int stride_w,
|
||||
const int pad_h, const int pad_w, const int dilation_h,
|
||||
const int dilation_w, const int group,
|
||||
const int group_channels, const float offset_scale,
|
||||
const at::Tensor &grad_output, const int im2col_step) {
|
||||
AT_ERROR("Not implement on cpu");
|
||||
}
|
||||
31
classification/ops_dcnv3/src/cpu/dcnv3_cpu.h
Normal file
31
classification/ops_dcnv3/src/cpu/dcnv3_cpu.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*!
|
||||
**************************************************************************************************
|
||||
* InternImage
|
||||
* Copyright (c) 2022 OpenGVLab
|
||||
* Licensed under The MIT License [see LICENSE for details]
|
||||
**************************************************************************************************
|
||||
* Modified from
|
||||
*https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch/tree/pytorch_1.0.0
|
||||
**************************************************************************************************
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <torch/extension.h>
|
||||
|
||||
at::Tensor dcnv3_cpu_forward(const at::Tensor &input, const at::Tensor &offset,
|
||||
const at::Tensor &mask, const int kernel_h,
|
||||
const int kernel_w, const int stride_h,
|
||||
const int stride_w, const int pad_h,
|
||||
const int pad_w, const int dilation_h,
|
||||
const int dilation_w, const int group,
|
||||
const int group_channels, const float offset_scale,
|
||||
const int im2col_step);
|
||||
|
||||
std::vector<at::Tensor>
|
||||
dcnv3_cpu_backward(const at::Tensor &input, const at::Tensor &offset,
|
||||
const at::Tensor &mask, const int kernel_h,
|
||||
const int kernel_w, const int stride_h, const int stride_w,
|
||||
const int pad_h, const int pad_w, const int dilation_h,
|
||||
const int dilation_w, const int group,
|
||||
const int group_channels, const float offset_scale,
|
||||
const at::Tensor &grad_output, const int im2col_step);
|
||||
174
classification/ops_dcnv3/src/cuda/dcnv3_cuda.cu
Normal file
174
classification/ops_dcnv3/src/cuda/dcnv3_cuda.cu
Normal file
@@ -0,0 +1,174 @@
|
||||
/*!
|
||||
**************************************************************************************************
|
||||
* InternImage
|
||||
* Copyright (c) 2022 OpenGVLab
|
||||
* Licensed under The MIT License [see LICENSE for details]
|
||||
**************************************************************************************************
|
||||
* Modified from
|
||||
*https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch/tree/pytorch_1.0.0
|
||||
**************************************************************************************************
|
||||
*/
|
||||
|
||||
#include "cuda/dcnv3_im2col_cuda.cuh"
|
||||
#include <vector>
|
||||
|
||||
#include <ATen/ATen.h>
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <cuda.h>
|
||||
#include <cuda_runtime.h>
|
||||
#include <torch/torch.h>
|
||||
|
||||
at::Tensor dcnv3_cuda_forward(const at::Tensor &input, const at::Tensor &offset,
|
||||
const at::Tensor &mask, const int kernel_h,
|
||||
const int kernel_w, const int stride_h,
|
||||
const int stride_w, const int pad_h,
|
||||
const int pad_w, const int dilation_h,
|
||||
const int dilation_w, const int group,
|
||||
const int group_channels,
|
||||
const float offset_scale, const int im2col_step, const int remove_center) {
|
||||
AT_ASSERTM(input.is_contiguous(), "input tensor has to be contiguous");
|
||||
AT_ASSERTM(offset.is_contiguous(), "offset tensor has to be contiguous");
|
||||
AT_ASSERTM(mask.is_contiguous(), "mask tensor has to be contiguous");
|
||||
AT_ASSERTM(input.type().is_cuda(), "input must be a CUDA tensor");
|
||||
AT_ASSERTM(offset.type().is_cuda(), "offset must be a CUDA tensor");
|
||||
AT_ASSERTM(mask.type().is_cuda(), "mask must be a CUDA tensor");
|
||||
|
||||
const int batch = input.size(0);
|
||||
const int height_in = input.size(1);
|
||||
const int width_in = input.size(2);
|
||||
const int channels = input.size(3);
|
||||
const int height_out =
|
||||
(height_in + 2 * pad_h - (dilation_h * (kernel_h - 1) + 1)) / stride_h +
|
||||
1;
|
||||
const int width_out =
|
||||
(width_in + 2 * pad_w - (dilation_w * (kernel_w - 1) + 1)) / stride_w +
|
||||
1;
|
||||
const int im2col_step_ = std::min(batch, im2col_step);
|
||||
|
||||
AT_ASSERTM(batch % im2col_step_ == 0,
|
||||
"batch(%d) must divide im2col_step(%d)", batch, im2col_step_);
|
||||
AT_ASSERTM(
|
||||
channels == (group * group_channels),
|
||||
"Input channels and group times group channels wont match: (%d vs %d).",
|
||||
channels, group * group_channels);
|
||||
|
||||
auto output =
|
||||
at::zeros({batch, height_out, width_out, group * group_channels},
|
||||
input.options());
|
||||
|
||||
const int batch_n = im2col_step_;
|
||||
auto output_n = output.view({batch / batch_n, batch_n, height_out,
|
||||
width_out, group * group_channels});
|
||||
auto per_input_size = height_in * width_in * group * group_channels;
|
||||
auto per_offset_size =
|
||||
height_out * width_out * group * (kernel_h * kernel_w - remove_center) * 2;
|
||||
auto per_mask_size = height_out * width_out * group * (kernel_h * kernel_w - remove_center);
|
||||
for (int n = 0; n < batch / im2col_step_; ++n) {
|
||||
auto columns = output_n.select(0, n);
|
||||
// AT_DISPATCH_FLOATING_TYPES(
|
||||
AT_DISPATCH_FLOATING_TYPES_AND_HALF(
|
||||
input.type(), "ms_deform_attn_forward_cuda", ([&] {
|
||||
dcnv3_im2col_cuda(
|
||||
at::cuda::getCurrentCUDAStream(),
|
||||
input.data<scalar_t>() + n * im2col_step_ * per_input_size,
|
||||
offset.data<scalar_t>() +
|
||||
n * im2col_step_ * per_offset_size,
|
||||
mask.data<scalar_t>() + n * im2col_step_ * per_mask_size,
|
||||
columns.data<scalar_t>(), kernel_h, kernel_w, stride_h,
|
||||
stride_w, pad_h, pad_w, dilation_h, dilation_w, group,
|
||||
group_channels, batch_n, height_in, width_in, height_out,
|
||||
width_out, offset_scale, remove_center);
|
||||
}));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
std::vector<at::Tensor>
|
||||
dcnv3_cuda_backward(const at::Tensor &input, const at::Tensor &offset,
|
||||
const at::Tensor &mask, const int kernel_h,
|
||||
const int kernel_w, const int stride_h, const int stride_w,
|
||||
const int pad_h, const int pad_w, const int dilation_h,
|
||||
const int dilation_w, const int group,
|
||||
const int group_channels, const float offset_scale,
|
||||
const at::Tensor &grad_output, const int im2col_step, const int remove_center) {
|
||||
|
||||
AT_ASSERTM(input.is_contiguous(), "input tensor has to be contiguous");
|
||||
AT_ASSERTM(offset.is_contiguous(), "offset tensor has to be contiguous");
|
||||
AT_ASSERTM(mask.is_contiguous(), "mask tensor has to be contiguous");
|
||||
AT_ASSERTM(grad_output.is_contiguous(),
|
||||
"grad_output tensor has to be contiguous");
|
||||
AT_ASSERTM(input.type().is_cuda(), "input must be a CUDA tensor");
|
||||
AT_ASSERTM(offset.type().is_cuda(), "offset must be a CUDA tensor");
|
||||
AT_ASSERTM(mask.type().is_cuda(), "mask must be a CUDA tensor");
|
||||
AT_ASSERTM(grad_output.type().is_cuda(),
|
||||
"grad_output must be a CUDA tensor");
|
||||
|
||||
const int batch = input.size(0);
|
||||
const int height_in = input.size(1);
|
||||
const int width_in = input.size(2);
|
||||
const int channels = input.size(3);
|
||||
const int height_out =
|
||||
(height_in + 2 * pad_h - (dilation_h * (kernel_h - 1) + 1)) / stride_h +
|
||||
1;
|
||||
const int width_out =
|
||||
(width_in + 2 * pad_w - (dilation_w * (kernel_w - 1) + 1)) / stride_w +
|
||||
1;
|
||||
const int im2col_step_ = std::min(batch, im2col_step);
|
||||
|
||||
AT_ASSERTM(batch % im2col_step_ == 0,
|
||||
"batch(%d) must divide im2col_step(%d)", batch, im2col_step_);
|
||||
AT_ASSERTM(
|
||||
channels == (group * group_channels),
|
||||
"Input channels and group times group channels wont match: (%d vs %d).",
|
||||
channels, group * group_channels);
|
||||
|
||||
auto dtype = input.dtype();
|
||||
if (dtype == at::kHalf) {
|
||||
dtype = at::kFloat;
|
||||
}
|
||||
|
||||
auto grad_input = at::zeros_like(input, dtype);
|
||||
auto grad_offset = at::zeros_like(offset, dtype);
|
||||
auto grad_mask = at::zeros_like(mask, dtype);
|
||||
|
||||
const int batch_n = im2col_step_;
|
||||
auto per_input_size = height_in * width_in * group * group_channels;
|
||||
auto per_offset_size =
|
||||
height_out * width_out * group * (kernel_h * kernel_w - remove_center) * 2;
|
||||
auto per_mask_size = height_out * width_out * group * (kernel_h * kernel_w - remove_center);
|
||||
auto grad_output_n =
|
||||
grad_output.view({batch / im2col_step_, batch_n, height_out * width_out,
|
||||
group, group_channels});
|
||||
|
||||
for (int n = 0; n < batch / im2col_step_; ++n) {
|
||||
auto grad_output_g = grad_output_n.select(0, n);
|
||||
// AT_DISPATCH_FLOATING_TYPES(
|
||||
AT_DISPATCH_FLOATING_TYPES_AND_HALF(
|
||||
input.type(), "ms_deform_attn_backward_cuda", ([&] {
|
||||
dcnv3_col2im_cuda(
|
||||
at::cuda::getCurrentCUDAStream(),
|
||||
grad_output_g.data<scalar_t>(),
|
||||
input.data<scalar_t>() + n * im2col_step_ * per_input_size,
|
||||
offset.data<scalar_t>() +
|
||||
n * im2col_step_ * per_offset_size,
|
||||
mask.data<scalar_t>() + n * im2col_step_ * per_mask_size,
|
||||
kernel_h, kernel_w, stride_h, stride_w, pad_h, pad_w,
|
||||
dilation_h, dilation_w, group, group_channels, batch_n,
|
||||
height_in, width_in, height_out, width_out, offset_scale, remove_center,
|
||||
grad_input.data<opmath_t>() +
|
||||
n * im2col_step_ * per_input_size,
|
||||
grad_offset.data<opmath_t>() +
|
||||
n * im2col_step_ * per_offset_size,
|
||||
grad_mask.data<opmath_t>() +
|
||||
n * im2col_step_ * per_mask_size);
|
||||
}));
|
||||
}
|
||||
|
||||
if (input.dtype() == torch::kHalf) {
|
||||
return {grad_input.to(torch::kHalf), grad_offset.to(torch::kHalf),
|
||||
grad_mask.to(torch::kHalf)};
|
||||
} else {
|
||||
return {grad_input, grad_offset, grad_mask};
|
||||
}
|
||||
}
|
||||
31
classification/ops_dcnv3/src/cuda/dcnv3_cuda.h
Normal file
31
classification/ops_dcnv3/src/cuda/dcnv3_cuda.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*!
|
||||
**************************************************************************************************
|
||||
* InternImage
|
||||
* Copyright (c) 2022 OpenGVLab
|
||||
* Licensed under The MIT License [see LICENSE for details]
|
||||
**************************************************************************************************
|
||||
* Modified from
|
||||
*https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch/tree/pytorch_1.0.0
|
||||
**************************************************************************************************
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <torch/extension.h>
|
||||
|
||||
at::Tensor dcnv3_cuda_forward(const at::Tensor &input, const at::Tensor &offset,
|
||||
const at::Tensor &mask, const int kernel_h,
|
||||
const int kernel_w, const int stride_h,
|
||||
const int stride_w, const int pad_h,
|
||||
const int pad_w, const int dilation_h,
|
||||
const int dilation_w, const int group,
|
||||
const int group_channels,
|
||||
const float offset_scale, const int im2col_step, const int remove_center);
|
||||
|
||||
std::vector<at::Tensor>
|
||||
dcnv3_cuda_backward(const at::Tensor &input, const at::Tensor &offset,
|
||||
const at::Tensor &mask, const int kernel_h,
|
||||
const int kernel_w, const int stride_h, const int stride_w,
|
||||
const int pad_h, const int pad_w, const int dilation_h,
|
||||
const int dilation_w, const int group,
|
||||
const int group_channels, const float offset_scale,
|
||||
const at::Tensor &grad_output, const int im2col_step, const int remove_center);
|
||||
1094
classification/ops_dcnv3/src/cuda/dcnv3_im2col_cuda.cuh
Normal file
1094
classification/ops_dcnv3/src/cuda/dcnv3_im2col_cuda.cuh
Normal file
File diff suppressed because it is too large
Load Diff
59
classification/ops_dcnv3/src/dcnv3.h
Normal file
59
classification/ops_dcnv3/src/dcnv3.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*!
|
||||
**************************************************************************************************
|
||||
* InternImage
|
||||
* Copyright (c) 2022 OpenGVLab
|
||||
* Licensed under The MIT License [see LICENSE for details]
|
||||
**************************************************************************************************
|
||||
* Modified from
|
||||
*https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch/tree/pytorch_1.0.0
|
||||
**************************************************************************************************
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpu/dcnv3_cpu.h"
|
||||
|
||||
#ifdef WITH_CUDA
|
||||
#include "cuda/dcnv3_cuda.h"
|
||||
#endif
|
||||
|
||||
at::Tensor dcnv3_forward(const at::Tensor &input, const at::Tensor &offset,
|
||||
const at::Tensor &mask, const int kernel_h,
|
||||
const int kernel_w, const int stride_h,
|
||||
const int stride_w, const int pad_h, const int pad_w,
|
||||
const int dilation_h, const int dilation_w,
|
||||
const int group, const int group_channels,
|
||||
const float offset_scale, const int im2col_step, const int remove_center) {
|
||||
if (input.type().is_cuda()) {
|
||||
#ifdef WITH_CUDA
|
||||
return dcnv3_cuda_forward(input, offset, mask, kernel_h, kernel_w,
|
||||
stride_h, stride_w, pad_h, pad_w, dilation_h,
|
||||
dilation_w, group, group_channels,
|
||||
offset_scale, im2col_step, remove_center);
|
||||
#else
|
||||
AT_ERROR("Not compiled with GPU support");
|
||||
#endif
|
||||
}
|
||||
AT_ERROR("Not implemented on the CPU");
|
||||
}
|
||||
|
||||
std::vector<at::Tensor>
|
||||
dcnv3_backward(const at::Tensor &input, const at::Tensor &offset,
|
||||
const at::Tensor &mask, const int kernel_h, const int kernel_w,
|
||||
const int stride_h, const int stride_w, const int pad_h,
|
||||
const int pad_w, const int dilation_h, const int dilation_w,
|
||||
const int group, const int group_channels,
|
||||
const float offset_scale, const at::Tensor &grad_output,
|
||||
const int im2col_step, const int remove_center) {
|
||||
if (input.type().is_cuda()) {
|
||||
#ifdef WITH_CUDA
|
||||
return dcnv3_cuda_backward(input, offset, mask, kernel_h, kernel_w,
|
||||
stride_h, stride_w, pad_h, pad_w, dilation_h,
|
||||
dilation_w, group, group_channels,
|
||||
offset_scale, grad_output, im2col_step, remove_center);
|
||||
#else
|
||||
AT_ERROR("Not compiled with GPU support");
|
||||
#endif
|
||||
}
|
||||
AT_ERROR("Not implemented on the CPU");
|
||||
}
|
||||
17
classification/ops_dcnv3/src/vision.cpp
Normal file
17
classification/ops_dcnv3/src/vision.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
/*!
|
||||
**************************************************************************************************
|
||||
* InternImage
|
||||
* Copyright (c) 2022 OpenGVLab
|
||||
* Licensed under The MIT License [see LICENSE for details]
|
||||
**************************************************************************************************
|
||||
* Modified from
|
||||
*https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch/tree/pytorch_1.0.0
|
||||
**************************************************************************************************
|
||||
*/
|
||||
|
||||
#include "dcnv3.h"
|
||||
|
||||
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
|
||||
m.def("dcnv3_forward", &dcnv3_forward, "dcnv3_forward");
|
||||
m.def("dcnv3_backward", &dcnv3_backward, "dcnv3_backward");
|
||||
}
|
||||
264
classification/ops_dcnv3/test.py
Normal file
264
classification/ops_dcnv3/test.py
Normal file
@@ -0,0 +1,264 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
|
||||
import time
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import math
|
||||
from torch.autograd import gradcheck
|
||||
|
||||
from functions.dcnv3_func import DCNv3Function, dcnv3_core_pytorch
|
||||
|
||||
H_in, W_in = 8, 8
|
||||
N, M, D = 2, 4, 16
|
||||
Kh, Kw = 3, 3
|
||||
remove_center = False
|
||||
P = Kh * Kw - remove_center
|
||||
offset_scale = 2.0
|
||||
pad = 1
|
||||
dilation = 1
|
||||
stride = 1
|
||||
H_out = (H_in + 2 * pad - (dilation * (Kh - 1) + 1)) // stride + 1
|
||||
W_out = (W_in + 2 * pad - (dilation * (Kw - 1) + 1)) // stride + 1
|
||||
|
||||
torch.manual_seed(3)
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def check_forward_equal_with_pytorch_double():
|
||||
input = torch.rand(N, H_in, W_in, M*D).cuda() * 0.01
|
||||
offset = torch.rand(N, H_out, W_out, M*P*2).cuda() * 10
|
||||
mask = torch.rand(N, H_out, W_out, M, P).cuda() + 1e-5
|
||||
mask /= mask.sum(-1, keepdim=True)
|
||||
mask = mask.reshape(N, H_out, W_out, M*P)
|
||||
|
||||
output_pytorch = dcnv3_core_pytorch(
|
||||
input.double(),
|
||||
offset.double(),
|
||||
mask.double(),
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale, remove_center).detach().cpu()
|
||||
|
||||
im2col_step = 2
|
||||
output_cuda = DCNv3Function.apply(
|
||||
input.double(),
|
||||
offset.double(),
|
||||
mask.double(),
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale,
|
||||
im2col_step, remove_center).detach().cpu()
|
||||
|
||||
fwdok = torch.allclose(output_cuda, output_pytorch)
|
||||
max_abs_err = (output_cuda - output_pytorch).abs().max()
|
||||
max_rel_err = ((output_cuda - output_pytorch).abs() /
|
||||
output_pytorch.abs()).max()
|
||||
print('>>> forward double')
|
||||
print(f'* {fwdok} check_forward_equal_with_pytorch_double: max_abs_err {max_abs_err:.2e} max_rel_err {max_rel_err:.2e}')
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def check_forward_equal_with_pytorch_float():
|
||||
input = torch.rand(N, H_in, W_in, M*D).cuda() * 0.01
|
||||
offset = torch.rand(N, H_out, W_out, M*P*2).cuda() * 10
|
||||
mask = torch.rand(N, H_out, W_out, M, P).cuda() + 1e-5
|
||||
mask /= mask.sum(-1, keepdim=True)
|
||||
mask = mask.reshape(N, H_out, W_out, M*P)
|
||||
|
||||
output_pytorch = dcnv3_core_pytorch(
|
||||
input,
|
||||
offset,
|
||||
mask,
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale, remove_center).detach().cpu()
|
||||
|
||||
im2col_step = 2
|
||||
output_cuda = DCNv3Function.apply(
|
||||
input,
|
||||
offset,
|
||||
mask,
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale,
|
||||
im2col_step, remove_center).detach().cpu()
|
||||
|
||||
fwdok = torch.allclose(output_cuda, output_pytorch, rtol=1e-2, atol=1e-3)
|
||||
max_abs_err = (output_cuda - output_pytorch).abs().max()
|
||||
max_rel_err = ((output_cuda - output_pytorch).abs() /
|
||||
output_pytorch.abs()).max()
|
||||
print('>>> forward float')
|
||||
print(f'* {fwdok} check_forward_equal_with_pytorch_float: max_abs_err {max_abs_err:.2e} max_rel_err {max_rel_err:.2e}')
|
||||
|
||||
|
||||
def check_backward_equal_with_pytorch_double(channels=4, grad_input=True, grad_offset=True, grad_mask=True):
|
||||
# H_in, W_in = 4, 4
|
||||
N = 2
|
||||
M = 2
|
||||
H_out = (H_in + 2 * pad - (dilation * (Kh - 1) + 1)) // stride + 1
|
||||
W_out = (W_in + 2 * pad - (dilation * (Kw - 1) + 1)) // stride + 1
|
||||
|
||||
D = channels
|
||||
input0 = torch.rand(N, H_in, W_in, M*D).cuda() * 0.01
|
||||
offset0 = torch.rand(N, H_out, W_out, M*P*2).cuda() * 10
|
||||
mask0 = torch.rand(N, H_out, W_out, M, P).cuda() + 1e-5
|
||||
mask0 /= mask0.sum(-1, keepdim=True)
|
||||
mask0 = mask0.reshape(N, H_out, W_out, M*P)
|
||||
input0.requires_grad = grad_input
|
||||
offset0.requires_grad = grad_offset
|
||||
mask0.requires_grad = grad_mask
|
||||
|
||||
output_pytorch = dcnv3_core_pytorch(
|
||||
input0.double(),
|
||||
offset0.double(),
|
||||
mask0.double(),
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale, remove_center)
|
||||
output_pytorch.sum().backward()
|
||||
|
||||
input1 = input0.detach()
|
||||
offset1 = offset0.detach()
|
||||
mask1 = mask0.detach()
|
||||
input1.requires_grad = grad_input
|
||||
offset1.requires_grad = grad_offset
|
||||
mask1.requires_grad = grad_mask
|
||||
|
||||
im2col_step = 2
|
||||
output_cuda = DCNv3Function.apply(
|
||||
input1.double(),
|
||||
offset1.double(),
|
||||
mask1.double(),
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale,
|
||||
im2col_step, remove_center)
|
||||
output_cuda.sum().backward()
|
||||
|
||||
print(f'>>> backward double: channels {D}')
|
||||
bwdok = torch.allclose(input0.grad, input1.grad, rtol=1e-2, atol=1e-3)
|
||||
max_abs_err = (input0.grad - input1.grad).abs().max()
|
||||
max_rel_err = ((input0.grad - input1.grad).abs() /
|
||||
input0.grad.abs()).max()
|
||||
print(
|
||||
f'* {bwdok} input_grad check_backward_equal_with_pytorch_double: max_abs_err {max_abs_err:.2e} max_rel_err {max_rel_err:.2e}')
|
||||
|
||||
bwdok = torch.allclose(offset0.grad, offset1.grad, rtol=1e-2, atol=1e-3)
|
||||
max_abs_err = (offset0.grad - offset1.grad).abs().max()
|
||||
max_rel_err = ((offset0.grad - offset1.grad).abs() /
|
||||
offset0.grad.abs()).max()
|
||||
print(
|
||||
f'* {bwdok} offset_grad check_backward_equal_with_pytorch_double: max_abs_err {max_abs_err:.2e} max_rel_err {max_rel_err:.2e}')
|
||||
|
||||
bwdok = torch.allclose(mask0.grad, mask1.grad, rtol=1e-2, atol=1e-3)
|
||||
max_abs_err = (mask0.grad - mask1.grad).abs().max()
|
||||
max_rel_err = ((mask0.grad - mask1.grad).abs() /
|
||||
mask0.grad.abs()).max()
|
||||
print(
|
||||
f'* {bwdok} mask_grad check_backward_equal_with_pytorch_double: max_abs_err {max_abs_err:.2e} max_rel_err {max_rel_err:.2e}')
|
||||
|
||||
|
||||
def check_backward_equal_with_pytorch_float(channels=4, grad_input=True, grad_offset=True, grad_mask=True):
|
||||
# H_in, W_in = 4, 4
|
||||
N = 2
|
||||
M = 2
|
||||
H_out = (H_in + 2 * pad - (dilation * (Kh - 1) + 1)) // stride + 1
|
||||
W_out = (W_in + 2 * pad - (dilation * (Kw - 1) + 1)) // stride + 1
|
||||
|
||||
D = channels
|
||||
input0 = torch.rand(N, H_in, W_in, M*D).cuda() * 0.01
|
||||
offset0 = torch.rand(N, H_out, W_out, M*P*2).cuda() * 10
|
||||
mask0 = torch.rand(N, H_out, W_out, M, P).cuda() + 1e-5
|
||||
mask0 /= mask0.sum(-1, keepdim=True)
|
||||
mask0 = mask0.reshape(N, H_out, W_out, M*P)
|
||||
input0.requires_grad = grad_input
|
||||
offset0.requires_grad = grad_offset
|
||||
mask0.requires_grad = grad_mask
|
||||
|
||||
output_pytorch = dcnv3_core_pytorch(
|
||||
input0,
|
||||
offset0,
|
||||
mask0,
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale, remove_center)
|
||||
output_pytorch.sum().backward()
|
||||
|
||||
input1 = input0.detach()
|
||||
offset1 = offset0.detach()
|
||||
mask1 = mask0.detach()
|
||||
input1.requires_grad = grad_input
|
||||
offset1.requires_grad = grad_offset
|
||||
mask1.requires_grad = grad_mask
|
||||
|
||||
im2col_step = 2
|
||||
output_cuda = DCNv3Function.apply(
|
||||
input1,
|
||||
offset1,
|
||||
mask1,
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale,
|
||||
im2col_step, remove_center)
|
||||
output_cuda.sum().backward()
|
||||
|
||||
print(f'>>> backward float: channels {D}')
|
||||
bwdok = torch.allclose(input0.grad, input1.grad, rtol=1e-2, atol=1e-3)
|
||||
max_abs_err = (input0.grad - input1.grad).abs().max()
|
||||
max_rel_err = ((input0.grad - input1.grad).abs() /
|
||||
input0.grad.abs()).max()
|
||||
print(
|
||||
f'* {bwdok} input_grad check_backward_equal_with_pytorch_float: max_abs_err {max_abs_err:.2e} max_rel_err {max_rel_err:.2e}')
|
||||
|
||||
bwdok = torch.allclose(offset0.grad, offset1.grad, rtol=1e-2, atol=1e-3)
|
||||
max_abs_err = (offset0.grad - offset1.grad).abs().max()
|
||||
max_rel_err = ((offset0.grad - offset1.grad).abs() /
|
||||
offset0.grad.abs()).max()
|
||||
print(
|
||||
f'* {bwdok} offset_grad check_backward_equal_with_pytorch_float: max_abs_err {max_abs_err:.2e} max_rel_err {max_rel_err:.2e}')
|
||||
|
||||
bwdok = torch.allclose(mask0.grad, mask1.grad, rtol=1e-2, atol=1e-3)
|
||||
max_abs_err = (mask0.grad - mask1.grad).abs().max()
|
||||
max_rel_err = ((mask0.grad - mask1.grad).abs() /
|
||||
mask0.grad.abs()).max()
|
||||
print(
|
||||
f'* {bwdok} mask_grad check_backward_equal_with_pytorch_float: max_abs_err {max_abs_err:.2e} max_rel_err {max_rel_err:.2e}')
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def check_time_cost(im2col_step=128):
|
||||
N = 512
|
||||
H_in, W_in = 64, 64
|
||||
H_out = (H_in + 2 * pad - (dilation * (Kh - 1) + 1)) // stride + 1
|
||||
W_out = (W_in + 2 * pad - (dilation * (Kw - 1) + 1)) // stride + 1
|
||||
|
||||
input = torch.rand(N, H_in, W_in, M*D).cuda() * 0.01
|
||||
offset = torch.rand(N, H_out, W_out, M*P*2).cuda() * 10
|
||||
mask = torch.rand(N, H_out, W_out, M, P).cuda() + 1e-5
|
||||
mask /= mask.sum(-1, keepdim=True)
|
||||
mask = mask.reshape(N, H_out, W_out, M*P)
|
||||
print(
|
||||
f'>>> time cost: im2col_step {im2col_step}; input {input.shape}; points {P} ')
|
||||
repeat = 100
|
||||
for i in range(repeat):
|
||||
output_cuda = DCNv3Function.apply(
|
||||
input,
|
||||
offset,
|
||||
mask,
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, 1.0,
|
||||
im2col_step, remove_center)
|
||||
torch.cuda.synchronize()
|
||||
start = time.time()
|
||||
for i in range(repeat):
|
||||
output_cuda = DCNv3Function.apply(
|
||||
input,
|
||||
offset,
|
||||
mask,
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, 1.0,
|
||||
im2col_step, remove_center)
|
||||
torch.cuda.synchronize()
|
||||
print(f'foward time cost: {(time.time() - start) / repeat}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
check_forward_equal_with_pytorch_double()
|
||||
check_forward_equal_with_pytorch_float()
|
||||
for channels in [1, 16, 30, 32, 64, 71, 1025]:
|
||||
check_backward_equal_with_pytorch_double(channels, True, True, True)
|
||||
for channels in [1, 16, 30, 32, 64, 71, 1025]:
|
||||
check_backward_equal_with_pytorch_float(channels, True, True, True)
|
||||
for i in range(3):
|
||||
im2col_step = 128 * (2 ** i)
|
||||
check_time_cost(im2col_step)
|
||||
159
classification/optimizer.py
Normal file
159
classification/optimizer.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
|
||||
from torch import optim as optim
|
||||
from torch.distributed.optim import ZeroRedundancyOptimizer
|
||||
|
||||
|
||||
def build_optimizer(config, model):
|
||||
"""
|
||||
Build optimizer, set weight decay of normalization to 0 by default.
|
||||
"""
|
||||
skip = {}
|
||||
skip_keywords = {}
|
||||
if hasattr(model, 'no_weight_decay'):
|
||||
skip = model.no_weight_decay()
|
||||
if hasattr(model, 'no_weight_decay_keywords'):
|
||||
skip_keywords = model.no_weight_decay_keywords()
|
||||
|
||||
parameters = set_weight_decay_and_lr(
|
||||
model,
|
||||
config.TRAIN.WEIGHT_DECAY,
|
||||
config.TRAIN.BASE_LR,
|
||||
skip,
|
||||
skip_keywords,
|
||||
lr_layer_decay=config.TRAIN.LR_LAYER_DECAY,
|
||||
lr_layer_decay_ratio=config.TRAIN.LR_LAYER_DECAY_RATIO,
|
||||
freeze_backbone=config.TRAIN.OPTIMIZER.FREEZE_BACKBONE,
|
||||
dcn_lr_mul=config.TRAIN.OPTIMIZER.DCN_LR_MUL,
|
||||
)
|
||||
|
||||
opt_lower = config.TRAIN.OPTIMIZER.NAME.lower()
|
||||
optimizer = None
|
||||
use_zero = config.TRAIN.OPTIMIZER.USE_ZERO
|
||||
if use_zero:
|
||||
print(f"\nUse Zero!")
|
||||
if opt_lower == 'sgd':
|
||||
# an ugly implementation
|
||||
# this problem is fixed after torch 1.12
|
||||
# https://github.com/pytorch/pytorch/issues/71347
|
||||
|
||||
# before 1.12, we could only pass list to zero optimizer, so we first pass parameters[0] with its lr and weight decay,
|
||||
# then we add other parameter via parameter group.
|
||||
|
||||
optimizer = ZeroRedundancyOptimizer(
|
||||
parameters[0]['params'],
|
||||
optimizer_class=optim.SGD,
|
||||
momentum=config.TRAIN.OPTIMIZER.MOMENTUM, nesterov=True,
|
||||
lr=parameters[0]['lr'], weight_decay=parameters[0]['weight_decay']
|
||||
)
|
||||
if len(parameters) > 1:
|
||||
for param_group in parameters[1:]:
|
||||
optimizer.add_param_group(param_group)
|
||||
elif opt_lower == 'adamw':
|
||||
optimizer = ZeroRedundancyOptimizer(
|
||||
parameters[0]['params'],
|
||||
optimizer_class=optim.AdamW,
|
||||
eps=config.TRAIN.OPTIMIZER.EPS, betas=config.TRAIN.OPTIMIZER.BETAS,
|
||||
lr=parameters[0]['lr'], weight_decay=parameters[0]['weight_decay']
|
||||
)
|
||||
if len(parameters) > 1:
|
||||
for param_group in parameters[1:]:
|
||||
optimizer.add_param_group(param_group)
|
||||
else:
|
||||
if opt_lower == 'sgd':
|
||||
optimizer = optim.SGD(parameters,
|
||||
momentum=config.TRAIN.OPTIMIZER.MOMENTUM,
|
||||
nesterov=True,
|
||||
lr=config.TRAIN.BASE_LR,
|
||||
weight_decay=config.TRAIN.WEIGHT_DECAY)
|
||||
elif opt_lower == 'adamw':
|
||||
optimizer = optim.AdamW(parameters,
|
||||
eps=config.TRAIN.OPTIMIZER.EPS,
|
||||
betas=config.TRAIN.OPTIMIZER.BETAS,
|
||||
lr=config.TRAIN.BASE_LR,
|
||||
weight_decay=config.TRAIN.WEIGHT_DECAY)
|
||||
|
||||
return optimizer
|
||||
|
||||
|
||||
def check_keywords_in_name(name, keywords=()):
|
||||
isin = False
|
||||
for keyword in keywords:
|
||||
if keyword in name:
|
||||
isin = True
|
||||
return isin
|
||||
|
||||
|
||||
def check_keywords_in_dict(name, keywords_dict):
|
||||
for k, v in keywords_dict.items():
|
||||
if k in name:
|
||||
return v
|
||||
return None
|
||||
|
||||
|
||||
def set_weight_decay_and_lr(
|
||||
model,
|
||||
weight_decay,
|
||||
base_lr,
|
||||
skip_list=(),
|
||||
skip_keywords=(),
|
||||
lr_layer_decay=None,
|
||||
lr_layer_decay_ratio=None,
|
||||
freeze_backbone=None,
|
||||
dcn_lr_mul=None,
|
||||
layerwise_lr=True,
|
||||
):
|
||||
parameters = []
|
||||
no_decay_name = []
|
||||
lr_ratio_log = {}
|
||||
|
||||
for name, param in model.named_parameters():
|
||||
if not param.requires_grad:
|
||||
continue # frozen weights
|
||||
if freeze_backbone:
|
||||
for i in freeze_backbone:
|
||||
if f'levels.{i}' in name:
|
||||
param.requires_grad = False
|
||||
# 1. check wd
|
||||
if len(param.shape) == 1 or name.endswith(".bias") or (
|
||||
name in skip_list) or check_keywords_in_name(
|
||||
name, skip_keywords):
|
||||
wd = 0.
|
||||
no_decay_name.append(name)
|
||||
else:
|
||||
wd = weight_decay
|
||||
|
||||
if lr_layer_decay:
|
||||
print('layer-wise lr decay is used !')
|
||||
assert hasattr(model, 'lr_decay_keywards')
|
||||
lr_ratio_keywards = model.lr_decay_keywards(lr_layer_decay_ratio)
|
||||
|
||||
# 2. check lr
|
||||
ratio = check_keywords_in_dict(name, lr_ratio_keywards)
|
||||
if ratio is not None:
|
||||
lr = ratio * base_lr
|
||||
else:
|
||||
lr = base_lr
|
||||
|
||||
# dcn lr
|
||||
if dcn_lr_mul is not None:
|
||||
if 'offset' in name or 'attention_weights' in name or 'center_feature_scale_proj' in name or 'alpha_beta' in name:
|
||||
lr = dcn_lr_mul * lr
|
||||
|
||||
lr_ratio_log[name] = (base_lr, ratio, wd, param.requires_grad)
|
||||
else:
|
||||
lr = base_lr
|
||||
parameters.append({'params': [param], 'weight_decay': wd, 'lr': lr, 'name': name})
|
||||
|
||||
print('no decay params: {no_decay_name}')
|
||||
if layerwise_lr:
|
||||
print('lr_ratio_params:')
|
||||
for k, v in lr_ratio_log.items():
|
||||
print(k, v)
|
||||
|
||||
return parameters
|
||||
31
classification/train_in1k.sh
Executable file
31
classification/train_in1k.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
|
||||
PARTITION=$1
|
||||
JOB_NAME=$2
|
||||
CONFIG=$3
|
||||
WORK_DIR=$4
|
||||
GPUS=${GPUS:-1}
|
||||
GPUS_PER_NODE=${GPUS_PER_NODE:-1}
|
||||
CPUS_PER_TASK=${CPUS_PER_TASK:-10}
|
||||
SRUN_ARGS=${SRUN_ARGS:-""}
|
||||
PY_ARGS=${@:5}
|
||||
|
||||
PYTHONPATH="$(dirname $0)/..":$PYTHONPATH \
|
||||
srun -p ${PARTITION} \
|
||||
--job-name=${JOB_NAME} \
|
||||
--gres=gpu:${GPUS_PER_NODE} \
|
||||
--ntasks=${GPUS} \
|
||||
--ntasks-per-node=${GPUS_PER_NODE} \
|
||||
--cpus-per-task=${CPUS_PER_TASK} \
|
||||
--kill-on-bad-exit=1 \
|
||||
--quotatype=reserved \
|
||||
${SRUN_ARGS} \
|
||||
python -u main.py \
|
||||
--cfg ${CONFIG} \
|
||||
--accumulation-steps 1 \
|
||||
--local-rank 0 \
|
||||
--batch-size 128 \
|
||||
--data-path /mnt/petrelfs/share/images \
|
||||
--output work_dirs ${@:4} --launcher="slurm"
|
||||
27
classification/train_in1k_deepspeed.sh
Normal file
27
classification/train_in1k_deepspeed.sh
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
|
||||
PARTITION=$1
|
||||
JOB_NAME=$2
|
||||
CONFIG=$3
|
||||
GPUS=${GPUS:-8}
|
||||
GPUS_PER_NODE=${GPUS_PER_NODE:-8}
|
||||
CPUS_PER_TASK=${CPUS_PER_TASK:-12}
|
||||
SRUN_ARGS=${SRUN_ARGS:-""}
|
||||
|
||||
PYTHONPATH="$(dirname $0)/..":$PYTHONPATH \
|
||||
srun -p ${PARTITION} \
|
||||
--job-name=${JOB_NAME} \
|
||||
--gres=gpu:${GPUS_PER_NODE} \
|
||||
--ntasks=${GPUS} \
|
||||
--ntasks-per-node=${GPUS_PER_NODE} \
|
||||
--cpus-per-task=${CPUS_PER_TASK} \
|
||||
--kill-on-bad-exit=1 \
|
||||
--quotatype=spot \
|
||||
${SRUN_ARGS} \
|
||||
python -u main_deepspeed.py \
|
||||
--cfg ${CONFIG} \
|
||||
--local-rank 0 \
|
||||
--data-path /mnt/lustre/share/images \
|
||||
--output work_dirs_deepspeed ${@:4}
|
||||
423
classification/utils.py
Normal file
423
classification/utils.py
Normal file
@@ -0,0 +1,423 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
import os
|
||||
import math
|
||||
import torch
|
||||
import numpy as np
|
||||
import torch.distributed as dist
|
||||
from collections import OrderedDict
|
||||
from timm.utils import get_state_dict
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from apex import amp
|
||||
except ImportError:
|
||||
amp = None
|
||||
|
||||
|
||||
def load_ema_checkpoint(config, model_ema, logger):
|
||||
logger.info(
|
||||
f'==============> Resuming form {config.MODEL.RESUME}....................'
|
||||
)
|
||||
if config.MODEL.RESUME.startswith('https'):
|
||||
checkpoint = torch.hub.load_state_dict_from_url(config.MODEL.RESUME,
|
||||
map_location='cpu',
|
||||
check_hash=True)
|
||||
else:
|
||||
checkpoint = torch.load(config.MODEL.RESUME, map_location='cpu')
|
||||
|
||||
assert isinstance(checkpoint, dict)
|
||||
if 'model_ema' in checkpoint:
|
||||
new_state_dict = OrderedDict()
|
||||
for k, v in checkpoint['model_ema'].items():
|
||||
if model_ema.ema_has_module:
|
||||
name = 'module.' + k if not k.startswith('module') else k
|
||||
else:
|
||||
name = k
|
||||
new_state_dict[name] = v
|
||||
msg = model_ema.ema.load_state_dict(new_state_dict, strict=False)
|
||||
logger.info(msg)
|
||||
logger.info('Loaded state_dict_ema')
|
||||
else:
|
||||
logger.warning(
|
||||
'Failed to find state_dict_ema, starting from loaded model weights'
|
||||
)
|
||||
|
||||
max_accuracy_ema = 0
|
||||
if 'max_accuracy_ema' in checkpoint:
|
||||
max_accuracy_ema = checkpoint['max_accuracy_ema']
|
||||
if 'ema_decay' in checkpoint:
|
||||
model_ema.decay = checkpoint['ema_decay']
|
||||
return max_accuracy_ema
|
||||
|
||||
|
||||
def load_checkpoint(config, model, optimizer, lr_scheduler, scaler, logger):
|
||||
logger.info(
|
||||
f'==============> Resuming form {config.MODEL.RESUME}....................'
|
||||
)
|
||||
if config.MODEL.RESUME.startswith('https'):
|
||||
checkpoint = torch.hub.load_state_dict_from_url(config.MODEL.RESUME,
|
||||
map_location='cpu',
|
||||
check_hash=True)
|
||||
else:
|
||||
checkpoint = torch.load(config.MODEL.RESUME, map_location='cpu')
|
||||
|
||||
print('resuming model')
|
||||
msg = model.load_state_dict(checkpoint['model'], strict=False)
|
||||
logger.info(msg)
|
||||
max_accuracy = 0.0
|
||||
if not config.EVAL_MODE and 'optimizer' in checkpoint and 'lr_scheduler' in checkpoint and 'epoch' in checkpoint:
|
||||
if optimizer is not None:
|
||||
print('resuming optimizer')
|
||||
try:
|
||||
optimizer.load_state_dict(checkpoint['optimizer'])
|
||||
except:
|
||||
print('resume optimizer failed')
|
||||
if lr_scheduler is not None:
|
||||
print('resuming lr_scheduler')
|
||||
lr_scheduler.load_state_dict(checkpoint['lr_scheduler'])
|
||||
config.defrost()
|
||||
config.TRAIN.START_EPOCH = checkpoint['epoch'] + 1
|
||||
config.freeze()
|
||||
if 'amp' in checkpoint and config.AMP_OPT_LEVEL != 'O0' and checkpoint[
|
||||
'config'].AMP_OPT_LEVEL != 'O0':
|
||||
scaler.load_state_dict(checkpoint['amp'])
|
||||
logger.info(
|
||||
f"=> loaded successfully {config.MODEL.RESUME} (epoch {checkpoint['epoch']})"
|
||||
)
|
||||
if 'max_accuracy' in checkpoint:
|
||||
max_accuracy = checkpoint['max_accuracy']
|
||||
|
||||
del checkpoint
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
return max_accuracy
|
||||
|
||||
|
||||
def load_pretrained(config, model, logger):
|
||||
logger.info(
|
||||
f'==============> Loading weight {config.MODEL.PRETRAINED} for fine-tuning......'
|
||||
)
|
||||
checkpoint = torch.load(config.MODEL.PRETRAINED, map_location='cpu')
|
||||
|
||||
state_dict = checkpoint
|
||||
if 'model' in checkpoint:
|
||||
state_dict = checkpoint['model']
|
||||
elif 'module' in checkpoint:
|
||||
state_dict = checkpoint['module']
|
||||
|
||||
first_key = list(state_dict.keys())[0]
|
||||
# delete teacher weights
|
||||
if 'student' in first_key or 'teacher' in first_key:
|
||||
new_state_dict = OrderedDict()
|
||||
for k, v in state_dict.items():
|
||||
if 'student_proj' in k:
|
||||
continue
|
||||
if 'student' in k:
|
||||
new_k = k.replace('student.', '')
|
||||
new_state_dict[new_k] = v
|
||||
state_dict = new_state_dict
|
||||
|
||||
# weights from sim
|
||||
if 'mask_token' in first_key:
|
||||
new_state_dict = OrderedDict()
|
||||
for k, v in state_dict.items():
|
||||
if 'mm_dcnv3' in k:
|
||||
continue
|
||||
if 'dcnv3' not in k and 'clip_projector' not in k:
|
||||
continue
|
||||
new_k = k.replace('dcnv3.', '')
|
||||
new_state_dict[new_k] = v
|
||||
new_state_dict['fc_norm.weight'] = state_dict[
|
||||
'clip.classifier_ln.weight']
|
||||
new_state_dict['fc_norm.bias'] = state_dict['clip.classifier_ln.bias']
|
||||
new_state_dict['head.weight'] = state_dict['clip.classifier.weight']
|
||||
new_state_dict['head.bias'] = state_dict['clip.classifier.bias']
|
||||
state_dict = new_state_dict
|
||||
|
||||
# delete relative_position_index since we always re-init it
|
||||
relative_position_index_keys = [
|
||||
k for k in state_dict.keys() if 'relative_position_index' in k
|
||||
]
|
||||
for k in relative_position_index_keys:
|
||||
del state_dict[k]
|
||||
|
||||
# delete relative_coords_table since we always re-init it
|
||||
relative_position_index_keys = [
|
||||
k for k in state_dict.keys() if 'relative_coords_table' in k
|
||||
]
|
||||
for k in relative_position_index_keys:
|
||||
del state_dict[k]
|
||||
|
||||
# delete attn_mask since we always re-init it
|
||||
attn_mask_keys = [k for k in state_dict.keys() if 'attn_mask' in k]
|
||||
for k in attn_mask_keys:
|
||||
del state_dict[k]
|
||||
|
||||
# bicubic interpolate relative_position_bias_table if not match
|
||||
relative_position_bias_table_keys = [
|
||||
k for k in state_dict.keys() if 'relative_position_bias_table' in k
|
||||
]
|
||||
for k in relative_position_bias_table_keys:
|
||||
relative_position_bias_table_pretrained = state_dict[k]
|
||||
relative_position_bias_table_current = model.state_dict()[k]
|
||||
L1, nH1 = relative_position_bias_table_pretrained.size()
|
||||
L2, nH2 = relative_position_bias_table_current.size()
|
||||
if nH1 != nH2:
|
||||
logger.warning(f'Error in loading {k}, passing......')
|
||||
else:
|
||||
if L1 != L2:
|
||||
# bicubic interpolate relative_position_bias_table if not match
|
||||
S1 = int(L1**0.5)
|
||||
S2 = int(L2**0.5)
|
||||
relative_position_bias_table_pretrained_resized = torch.nn.functional.interpolate(
|
||||
relative_position_bias_table_pretrained.permute(1, 0).view(
|
||||
1, nH1, S1, S1),
|
||||
size=(S2, S2),
|
||||
mode='bicubic')
|
||||
state_dict[
|
||||
k] = relative_position_bias_table_pretrained_resized.view(
|
||||
nH2, L2).permute(1, 0)
|
||||
|
||||
# bicubic interpolate absolute_pos_embed if not match
|
||||
absolute_pos_embed_keys = [
|
||||
k for k in state_dict.keys() if 'absolute_pos_embed' in k
|
||||
]
|
||||
for k in absolute_pos_embed_keys:
|
||||
# dpe
|
||||
absolute_pos_embed_pretrained = state_dict[k]
|
||||
absolute_pos_embed_current = model.state_dict()[k]
|
||||
_, L1, C1 = absolute_pos_embed_pretrained.size()
|
||||
_, L2, C2 = absolute_pos_embed_current.size()
|
||||
if C1 != C1:
|
||||
logger.warning(f'Error in loading {k}, passing......')
|
||||
else:
|
||||
if L1 != L2:
|
||||
S1 = int(L1**0.5)
|
||||
S2 = int(L2**0.5)
|
||||
absolute_pos_embed_pretrained = absolute_pos_embed_pretrained.reshape(
|
||||
-1, S1, S1, C1)
|
||||
absolute_pos_embed_pretrained = absolute_pos_embed_pretrained.permute(
|
||||
0, 3, 1, 2)
|
||||
absolute_pos_embed_pretrained_resized = torch.nn.functional.interpolate(
|
||||
absolute_pos_embed_pretrained,
|
||||
size=(S2, S2),
|
||||
mode='bicubic')
|
||||
absolute_pos_embed_pretrained_resized = absolute_pos_embed_pretrained_resized.permute(
|
||||
0, 2, 3, 1)
|
||||
absolute_pos_embed_pretrained_resized = absolute_pos_embed_pretrained_resized.flatten(
|
||||
1, 2)
|
||||
state_dict[k] = absolute_pos_embed_pretrained_resized
|
||||
|
||||
# check classifier, if not match, then re-init classifier to zero
|
||||
if 'head.bias' in state_dict:
|
||||
head_bias_pretrained = state_dict['head.bias']
|
||||
Nc1 = head_bias_pretrained.shape[0]
|
||||
Nc2 = model.head.bias.shape[0]
|
||||
|
||||
if (Nc1 != Nc2):
|
||||
if config.TRAIN.RAND_INIT_FT_HEAD:
|
||||
model.head.weight.data = model.head.weight.data * 0.001
|
||||
model.head.bias.data = model.head.bias.data * 0.001
|
||||
del state_dict['head.weight']
|
||||
del state_dict['head.bias']
|
||||
logger.warning(
|
||||
f'Error in loading classifier head, re-init classifier head to 0'
|
||||
)
|
||||
elif Nc1 == 21841 and Nc2 == 1000:
|
||||
logger.info(
|
||||
'loading ImageNet-22K weight to ImageNet-1K ......')
|
||||
map22kto1k_path = 'meta_data/map22kto1k.txt'
|
||||
logger.info(map22kto1k_path)
|
||||
with open(map22kto1k_path) as f:
|
||||
map22kto1k = f.readlines()
|
||||
map22kto1k = [int(id22k.strip()) for id22k in map22kto1k]
|
||||
state_dict['head.weight'] = state_dict['head.weight'][
|
||||
map22kto1k, :]
|
||||
state_dict['head.bias'] = state_dict['head.bias'][map22kto1k]
|
||||
|
||||
msg = model.load_state_dict(state_dict, strict=False)
|
||||
logger.warning(msg)
|
||||
|
||||
# from IPython import embed
|
||||
# embed()
|
||||
|
||||
logger.info(f'=> loaded successfully {config.MODEL.PRETRAINED}')
|
||||
|
||||
del checkpoint
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
|
||||
def convert_22k_head_to_1k(model, logger):
|
||||
head_weight = model.module.head.weight
|
||||
head_bias = model.module.head.bias
|
||||
Nc1 = head_bias.shape[0]
|
||||
|
||||
if Nc1 == 21841:
|
||||
logger.info('converting ImageNet-22K head to ImageNet-1K ......')
|
||||
map22kto1k_path = 'meta_data/map22kto1k.txt'
|
||||
logger.info(map22kto1k_path)
|
||||
with open(map22kto1k_path) as f:
|
||||
map22kto1k = f.readlines()
|
||||
map22kto1k = [int(id22k.strip()) for id22k in map22kto1k]
|
||||
model.module.head.weight = torch.nn.Parameter(
|
||||
head_weight[map22kto1k, :])
|
||||
model.module.head.bias = torch.nn.Parameter(head_bias[map22kto1k])
|
||||
else:
|
||||
logger.warning(f'Error in converting classifier head')
|
||||
|
||||
return model
|
||||
|
||||
|
||||
def save_checkpoint(config,
|
||||
epoch,
|
||||
model,
|
||||
max_accuracy,
|
||||
optimizer,
|
||||
lr_scheduler,
|
||||
scaler,
|
||||
logger,
|
||||
model_ema=None,
|
||||
max_accuracy_ema=None,
|
||||
ema_decay=None,
|
||||
model_ems=None,
|
||||
max_accuracy_ems=None,
|
||||
ems_model_num=None,
|
||||
best=None):
|
||||
|
||||
save_state = {
|
||||
'model': model.state_dict(),
|
||||
'optimizer': optimizer.state_dict(),
|
||||
'lr_scheduler': lr_scheduler.state_dict(),
|
||||
'max_accuracy': max_accuracy,
|
||||
'epoch': epoch,
|
||||
'config': config
|
||||
}
|
||||
if model_ema is not None:
|
||||
save_state['model_ema'] = get_state_dict(model_ema)
|
||||
if max_accuracy_ema is not None:
|
||||
save_state['max_accuracy_ema'] = max_accuracy_ema
|
||||
if ema_decay is not None:
|
||||
save_state['ema_decay'] = ema_decay
|
||||
if model_ems is not None:
|
||||
save_state['model_ems'] = get_state_dict(model_ems)
|
||||
if max_accuracy_ems is not None:
|
||||
save_state['max_accuracy_ems'] = max_accuracy_ems
|
||||
if ems_model_num is not None:
|
||||
save_state['ems_model_num'] = ems_model_num
|
||||
if config.AMP_OPT_LEVEL != 'O0':
|
||||
# save_state['amp'] = amp.state_dict()
|
||||
save_state['amp'] = scaler.state_dict()
|
||||
if best is None:
|
||||
save_path = os.path.join(config.OUTPUT, f'ckpt_epoch_{epoch}.pth')
|
||||
else:
|
||||
save_path = os.path.join(config.OUTPUT, f'ckpt_epoch_{best}.pth')
|
||||
logger.info(f'{save_path} saving......')
|
||||
torch.save(save_state, save_path)
|
||||
logger.info(f'{save_path} saved !!!')
|
||||
|
||||
if dist.get_rank() == 0 and isinstance(epoch, int):
|
||||
to_del = epoch - config.SAVE_CKPT_NUM * config.SAVE_FREQ
|
||||
old_ckpt = os.path.join(config.OUTPUT, f'ckpt_epoch_{to_del}.pth')
|
||||
if os.path.exists(old_ckpt):
|
||||
os.remove(old_ckpt)
|
||||
|
||||
|
||||
def get_grad_norm(parameters, norm_type=2):
|
||||
if isinstance(parameters, torch.Tensor):
|
||||
parameters = [parameters]
|
||||
parameters = list(filter(lambda p: p.grad is not None, parameters))
|
||||
norm_type = float(norm_type)
|
||||
total_norm = 0
|
||||
for p in parameters:
|
||||
param_norm = p.grad.data.norm(norm_type)
|
||||
total_norm += param_norm.item()**norm_type
|
||||
total_norm = total_norm**(1. / norm_type)
|
||||
return total_norm
|
||||
|
||||
|
||||
def auto_resume_helper(output_dir):
|
||||
checkpoints = os.listdir(output_dir)
|
||||
checkpoints = [ckpt for ckpt in checkpoints if ckpt.endswith('pth')]
|
||||
print(f'All checkpoints founded in {output_dir}: {checkpoints}')
|
||||
if len(checkpoints) > 0:
|
||||
latest_checkpoint = max(
|
||||
[os.path.join(output_dir, d) for d in checkpoints],
|
||||
key=os.path.getmtime)
|
||||
print(f'The latest checkpoint founded: {latest_checkpoint}')
|
||||
resume_file = latest_checkpoint
|
||||
else:
|
||||
resume_file = None
|
||||
return resume_file
|
||||
|
||||
|
||||
def reduce_tensor(tensor):
|
||||
rt = tensor.clone()
|
||||
dist.all_reduce(rt, op=dist.ReduceOp.SUM)
|
||||
rt /= dist.get_world_size()
|
||||
return rt
|
||||
|
||||
|
||||
# https://github.com/facebookresearch/ConvNeXt/blob/main/utils.py
|
||||
class NativeScalerWithGradNormCount:
|
||||
state_dict_key = 'amp_scaler'
|
||||
|
||||
def __init__(self):
|
||||
self._scaler = torch.cuda.amp.GradScaler()
|
||||
|
||||
def __call__(self,
|
||||
loss,
|
||||
optimizer,
|
||||
clip_grad=None,
|
||||
parameters=None,
|
||||
create_graph=False,
|
||||
update_grad=True):
|
||||
self._scaler.scale(loss).backward(create_graph=create_graph)
|
||||
if update_grad:
|
||||
if clip_grad is not None:
|
||||
assert parameters is not None
|
||||
self._scaler.unscale_(
|
||||
optimizer
|
||||
) # unscale the gradients of optimizer's assigned params in-place
|
||||
norm = torch.nn.utils.clip_grad_norm_(parameters, clip_grad)
|
||||
else:
|
||||
self._scaler.unscale_(optimizer)
|
||||
norm = get_grad_norm(parameters)
|
||||
self._scaler.step(optimizer)
|
||||
self._scaler.update()
|
||||
else:
|
||||
norm = None
|
||||
return norm
|
||||
|
||||
def state_dict(self):
|
||||
return self._scaler.state_dict()
|
||||
|
||||
def load_state_dict(self, state_dict):
|
||||
self._scaler.load_state_dict(state_dict)
|
||||
|
||||
|
||||
class MyAverageMeter(object):
|
||||
"""Computes and stores the average and current value."""
|
||||
|
||||
def __init__(self, max_len=-1):
|
||||
self.val_list = []
|
||||
self.count = []
|
||||
self.max_len = max_len
|
||||
self.val = 0
|
||||
self.avg = 0
|
||||
self.var = 0
|
||||
|
||||
def update(self, val):
|
||||
self.val = val
|
||||
self.avg = 0
|
||||
self.var = 0
|
||||
if not math.isnan(val) and not math.isinf(val):
|
||||
self.val_list.append(val)
|
||||
if self.max_len > 0 and len(self.val_list) > self.max_len:
|
||||
self.val_list = self.val_list[-self.max_len:]
|
||||
if len(self.val_list) > 0:
|
||||
self.avg = np.mean(np.array(self.val_list))
|
||||
self.var = np.std(np.array(self.val_list))
|
||||
Reference in New Issue
Block a user