From cc02608066e347900a176d0346f98a359faec1af Mon Sep 17 00:00:00 2001 From: SlongLiu Date: Tue, 21 Mar 2023 11:38:41 +0800 Subject: [PATCH] init codes --- .asset/cats.png | Bin 0 -> 381659 bytes demo/inference_on_a_image.py | 170 +++ groundingdino/__init__.py | 0 .../config/GroundingDINO_SwinT_OGC.py | 43 + groundingdino/datasets/transforms.py | 311 ++++ .../models/GroundingDINO/__init__.py | 10 + .../models/GroundingDINO/backbone/__init__.py | 1 + .../models/GroundingDINO/backbone/backbone.py | 216 +++ .../backbone/position_encoding.py | 181 +++ .../backbone/swin_transformer.py | 797 ++++++++++ .../models/GroundingDINO/bertwarper.py | 266 ++++ .../csrc/MsDeformAttn/ms_deform_attn.h | 64 + .../csrc/MsDeformAttn/ms_deform_attn_cpu.cpp | 43 + .../csrc/MsDeformAttn/ms_deform_attn_cpu.h | 35 + .../csrc/MsDeformAttn/ms_deform_attn_cuda.cu | 156 ++ .../csrc/MsDeformAttn/ms_deform_attn_cuda.h | 33 + .../MsDeformAttn/ms_deform_im2col_cuda.cuh | 1327 +++++++++++++++++ .../models/GroundingDINO/csrc/cuda_version.cu | 7 + .../models/GroundingDINO/csrc/vision.cpp | 58 + .../models/GroundingDINO/fuse_modules.py | 290 ++++ .../models/GroundingDINO/groundingdino.py | 394 +++++ .../models/GroundingDINO/ms_deform_attn.py | 419 ++++++ .../models/GroundingDINO/transformer.py | 942 ++++++++++++ .../GroundingDINO/transformer_vanilla.py | 117 ++ groundingdino/models/GroundingDINO/utils.py | 267 ++++ groundingdino/models/__init__.py | 17 + groundingdino/models/registry.py | 60 + groundingdino/util/__init__.py | 1 + groundingdino/util/box_ops.py | 140 ++ groundingdino/util/get_tokenlizer.py | 26 + groundingdino/util/logger.py | 93 ++ groundingdino/util/misc.py | 717 +++++++++ groundingdino/util/slconfig.py | 424 ++++++ groundingdino/util/slio.py | 177 +++ groundingdino/util/time_counter.py | 62 + groundingdino/util/utils.py | 621 ++++++++ groundingdino/util/visualizer.py | 318 ++++ groundingdino/util/vl_utils.py | 100 ++ groundingdino/version.py | 1 + 39 files changed, 8904 insertions(+) create mode 100644 .asset/cats.png create mode 100644 demo/inference_on_a_image.py create mode 100644 groundingdino/__init__.py create mode 100644 groundingdino/config/GroundingDINO_SwinT_OGC.py create mode 100644 groundingdino/datasets/transforms.py create mode 100644 groundingdino/models/GroundingDINO/__init__.py create mode 100644 groundingdino/models/GroundingDINO/backbone/__init__.py create mode 100644 groundingdino/models/GroundingDINO/backbone/backbone.py create mode 100644 groundingdino/models/GroundingDINO/backbone/position_encoding.py create mode 100644 groundingdino/models/GroundingDINO/backbone/swin_transformer.py create mode 100644 groundingdino/models/GroundingDINO/bertwarper.py create mode 100644 groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn.h create mode 100644 groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn_cpu.cpp create mode 100644 groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn_cpu.h create mode 100644 groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn_cuda.cu create mode 100644 groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn_cuda.h create mode 100644 groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_im2col_cuda.cuh create mode 100644 groundingdino/models/GroundingDINO/csrc/cuda_version.cu create mode 100644 groundingdino/models/GroundingDINO/csrc/vision.cpp create mode 100644 groundingdino/models/GroundingDINO/fuse_modules.py create mode 100644 groundingdino/models/GroundingDINO/groundingdino.py create mode 100644 groundingdino/models/GroundingDINO/ms_deform_attn.py create mode 100644 groundingdino/models/GroundingDINO/transformer.py create mode 100644 groundingdino/models/GroundingDINO/transformer_vanilla.py create mode 100644 groundingdino/models/GroundingDINO/utils.py create mode 100644 groundingdino/models/__init__.py create mode 100644 groundingdino/models/registry.py create mode 100644 groundingdino/util/__init__.py create mode 100644 groundingdino/util/box_ops.py create mode 100644 groundingdino/util/get_tokenlizer.py create mode 100644 groundingdino/util/logger.py create mode 100644 groundingdino/util/misc.py create mode 100644 groundingdino/util/slconfig.py create mode 100644 groundingdino/util/slio.py create mode 100644 groundingdino/util/time_counter.py create mode 100644 groundingdino/util/utils.py create mode 100644 groundingdino/util/visualizer.py create mode 100644 groundingdino/util/vl_utils.py create mode 100644 groundingdino/version.py diff --git a/.asset/cats.png b/.asset/cats.png new file mode 100644 index 0000000000000000000000000000000000000000..c9b851eec668af5bc5c6467e9ef45c4be5381ead GIT binary patch literal 381659 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Ð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 z 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
XvM&8=?^ukuC$jvspa-c3lX#PQ(*d%h2T3p5<6 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 YQ0ck{yW$8W0glR5iyP);rikeO0HufNr2qf` literal 0 HcmV?d00001 diff --git a/demo/inference_on_a_image.py b/demo/inference_on_a_image.py new file mode 100644 index 0000000..6802451 --- /dev/null +++ b/demo/inference_on_a_image.py @@ -0,0 +1,170 @@ +import argparse +import os +import sys + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + +import numpy as np +import torch +from PIL import Image, ImageDraw, ImageFont + +import groundingdino.datasets.transforms as T +from groundingdino.models import build_model +from groundingdino.util import box_ops +from groundingdino.util.slconfig import SLConfig +from groundingdino.util.utils import clean_state_dict, get_phrases_from_posmap + + +def plot_boxes_to_image(image_pil, tgt): + H, W = tgt["size"] + boxes = tgt["boxes"] + labels = tgt["labels"] + assert len(boxes) == len(labels), "boxes and labels must have same length" + + draw = ImageDraw.Draw(image_pil) + mask = Image.new("L", image_pil.size, 0) + mask_draw = ImageDraw.Draw(mask) + + # draw boxes and masks + for box, label in zip(boxes, labels): + # from 0..1 to 0..W, 0..H + box = box * torch.Tensor([W, H, W, H]) + # from xywh to xyxy + box[:2] -= box[2:] / 2 + box[2:] += box[:2] + # random color + color = tuple(np.random.randint(0, 255, size=3).tolist()) + # draw + x0, y0, x1, y1 = box + x0, y0, x1, y1 = int(x0), int(y0), int(x1), int(y1) + + draw.rectangle([x0, y0, x1, y1], outline=color, width=6) + # draw.text((x0, y0), str(label), fill=color) + + bbox = draw.textbbox((x0, y0), str(label)) + draw.rectangle(bbox, fill=color) + draw.text((x0, y0), str(label), fill="white") + + mask_draw.rectangle([x0, y0, x1, y1], fill=255, width=6) + + return image_pil, mask + + +def load_image(image_path): + # load image + image_pil = Image.open(image_path).convert("RGB") # load image + + transform = T.Compose( + [ + T.RandomResize([800], max_size=1333), + T.ToTensor(), + T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), + ] + ) + image, _ = transform(image_pil, None) # 3, h, w + return image_pil, image + + +def load_model(model_config_path, model_checkpoint_path): + args = SLConfig.fromfile(model_config_path) + args.device = "cuda" + model = build_model(args) + checkpoint = torch.load(model_checkpoint_path, map_location="cpu") + load_res = model.load_state_dict(clean_state_dict(checkpoint["model"]), strict=False) + print(load_res) + _ = model.eval() + return model + + +def get_grounding_output(model, image, caption, box_threshold, text_threshold, with_logits=True): + caption = caption.lower() + caption = caption.strip() + if not caption.endswith("."): + caption = caption + "." + model = model.cuda() + image = image.cuda() + with torch.no_grad(): + outputs = model(image[None], captions=[caption]) + logits = outputs["pred_logits"].cpu().sigmoid()[0] # (nq, 256) + boxes = outputs["pred_boxes"].cpu()[0] # (nq, 4) + logits.shape[0] + + + # filter output + logits_filt = logits.clone() + boxes_filt = boxes.clone() + filt_mask = logits_filt.max(dim=1)[0] > box_threshold + logits_filt = logits_filt[filt_mask] # num_filt, 256 + boxes_filt = boxes_filt[filt_mask] # num_filt, 4 + logits_filt.shape[0] + + # get phrase + tokenlizer = model.tokenizer + tokenized = tokenlizer(caption) + # build pred + pred_phrases = [] + for logit, box in zip(logits_filt, boxes_filt): + pred_phrase = get_phrases_from_posmap(logit > text_threshold, tokenized, caption) + if with_logits: + pred_phrases.append(pred_phrase + f"({str(logit.max().item())[:4]})") + else: + pred_phrases.append(pred_phrase) + + return boxes_filt, pred_phrases + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser("Grounding DINO example", add_help=True) + parser.add_argument("--config_file", "-c", type=str, required=True, help="path to config file") + parser.add_argument( + "--checkpoint_path", "-p", type=str, required=True, help="path to checkpoint file" + ) + parser.add_argument("--image_path", "-i", type=str, required=True, help="path to image file") + parser.add_argument("--text_prompt", "-t", type=str, required=True, help="text prompt") + parser.add_argument( + "--output_dir", "-o", type=str, default="outputs", required=True, help="output directory" + ) + + parser.add_argument( + "--box_threshold", type=float, default=0.3, help="box threshold" + ) + parser.add_argument( + "--text_threshold", type=float, default=0.25, help="text threshold" + ) + args = parser.parse_args() + + # cfg + config_file = args.config_file # change the path of the model config file + checkpoint_path = args.checkpoint_path # change the path of the model + image_path = args.image_path + text_prompt = args.text_prompt + output_dir = args.output_dir + box_threshold = args.box_threshold + text_threshold = args.box_threshold + + # make dir + os.makedirs(output_dir, exist_ok=True) + # load image + image_pil, image = load_image(image_path) + # load model + model = load_model(config_file, checkpoint_path) + + # visualize raw image + image_pil.save(os.path.join(output_dir, "raw_image.jpg")) + + # run model + boxes_filt, pred_phrases = get_grounding_output( + model, image, text_prompt, box_threshold, text_threshold + ) + + # visualize pred + size = image_pil.size + pred_dict = { + "boxes": boxes_filt, + "size": [size[1], size[0]], # H,W + "labels": pred_phrases, + } + # import ipdb; ipdb.set_trace() + image_with_box = plot_boxes_to_image(image_pil, pred_dict)[0] + image_with_box.save(os.path.join(output_dir, "pred.jpg")) diff --git a/groundingdino/__init__.py b/groundingdino/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/groundingdino/config/GroundingDINO_SwinT_OGC.py b/groundingdino/config/GroundingDINO_SwinT_OGC.py new file mode 100644 index 0000000..9158d5f --- /dev/null +++ b/groundingdino/config/GroundingDINO_SwinT_OGC.py @@ -0,0 +1,43 @@ +batch_size = 1 +modelname = "groundingdino" +backbone = "swin_T_224_1k" +position_embedding = "sine" +pe_temperatureH = 20 +pe_temperatureW = 20 +return_interm_indices = [1, 2, 3] +backbone_freeze_keywords = None +enc_layers = 6 +dec_layers = 6 +pre_norm = False +dim_feedforward = 2048 +hidden_dim = 256 +dropout = 0.0 +nheads = 8 +num_queries = 900 +query_dim = 4 +num_patterns = 0 +num_feature_levels = 4 +enc_n_points = 4 +dec_n_points = 4 +two_stage_type = "standard" +two_stage_bbox_embed_share = False +two_stage_class_embed_share = False +transformer_activation = "relu" +dec_pred_bbox_embed_share = True +dn_box_noise_scale = 1.0 +dn_label_noise_ratio = 0.5 +dn_label_coef = 1.0 +dn_bbox_coef = 1.0 +embed_init_tgt = True +dn_labelbook_size = 2000 +max_text_len = 256 +text_encoder_type = "bert-base-uncased" +use_text_enhancer = True +use_fusion_layer = True +use_checkpoint = True +use_transformer_ckpt = True +use_text_cross_attention = True +text_dropout = 0.0 +fusion_dropout = 0.0 +fusion_droppath = 0.1 +sub_sentence_present = True diff --git a/groundingdino/datasets/transforms.py b/groundingdino/datasets/transforms.py new file mode 100644 index 0000000..91cf926 --- /dev/null +++ b/groundingdino/datasets/transforms.py @@ -0,0 +1,311 @@ +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved +""" +Transforms and data augmentation for both image + bbox. +""" +import os +import random + +import PIL +import torch +import torchvision.transforms as T +import torchvision.transforms.functional as F + +from groundingdino.util.box_ops import box_xyxy_to_cxcywh +from groundingdino.util.misc import interpolate + + +def crop(image, target, region): + cropped_image = F.crop(image, *region) + + target = target.copy() + i, j, h, w = region + + # should we do something wrt the original size? + target["size"] = torch.tensor([h, w]) + + fields = ["labels", "area", "iscrowd", "positive_map"] + + if "boxes" in target: + boxes = target["boxes"] + max_size = torch.as_tensor([w, h], dtype=torch.float32) + cropped_boxes = boxes - torch.as_tensor([j, i, j, i]) + cropped_boxes = torch.min(cropped_boxes.reshape(-1, 2, 2), max_size) + cropped_boxes = cropped_boxes.clamp(min=0) + area = (cropped_boxes[:, 1, :] - cropped_boxes[:, 0, :]).prod(dim=1) + target["boxes"] = cropped_boxes.reshape(-1, 4) + target["area"] = area + fields.append("boxes") + + if "masks" in target: + # FIXME should we update the area here if there are no boxes? + target["masks"] = target["masks"][:, i : i + h, j : j + w] + fields.append("masks") + + # remove elements for which the boxes or masks that have zero area + if "boxes" in target or "masks" in target: + # favor boxes selection when defining which elements to keep + # this is compatible with previous implementation + if "boxes" in target: + cropped_boxes = target["boxes"].reshape(-1, 2, 2) + keep = torch.all(cropped_boxes[:, 1, :] > cropped_boxes[:, 0, :], dim=1) + else: + keep = target["masks"].flatten(1).any(1) + + for field in fields: + if field in target: + target[field] = target[field][keep] + + if os.environ.get("IPDB_SHILONG_DEBUG", None) == "INFO": + # for debug and visualization only. + if "strings_positive" in target: + target["strings_positive"] = [ + _i for _i, _j in zip(target["strings_positive"], keep) if _j + ] + + return cropped_image, target + + +def hflip(image, target): + flipped_image = F.hflip(image) + + w, h = image.size + + target = target.copy() + if "boxes" in target: + boxes = target["boxes"] + boxes = boxes[:, [2, 1, 0, 3]] * torch.as_tensor([-1, 1, -1, 1]) + torch.as_tensor( + [w, 0, w, 0] + ) + target["boxes"] = boxes + + if "masks" in target: + target["masks"] = target["masks"].flip(-1) + + return flipped_image, target + + +def resize(image, target, size, max_size=None): + # size can be min_size (scalar) or (w, h) tuple + + def get_size_with_aspect_ratio(image_size, size, max_size=None): + w, h = image_size + if max_size is not None: + min_original_size = float(min((w, h))) + max_original_size = float(max((w, h))) + if max_original_size / min_original_size * size > max_size: + size = int(round(max_size * min_original_size / max_original_size)) + + if (w <= h and w == size) or (h <= w and h == size): + return (h, w) + + if w < h: + ow = size + oh = int(size * h / w) + else: + oh = size + ow = int(size * w / h) + + return (oh, ow) + + def get_size(image_size, size, max_size=None): + if isinstance(size, (list, tuple)): + return size[::-1] + else: + return get_size_with_aspect_ratio(image_size, size, max_size) + + size = get_size(image.size, size, max_size) + rescaled_image = F.resize(image, size) + + if target is None: + return rescaled_image, None + + ratios = tuple(float(s) / float(s_orig) for s, s_orig in zip(rescaled_image.size, image.size)) + ratio_width, ratio_height = ratios + + target = target.copy() + if "boxes" in target: + boxes = target["boxes"] + scaled_boxes = boxes * torch.as_tensor( + [ratio_width, ratio_height, ratio_width, ratio_height] + ) + target["boxes"] = scaled_boxes + + if "area" in target: + area = target["area"] + scaled_area = area * (ratio_width * ratio_height) + target["area"] = scaled_area + + h, w = size + target["size"] = torch.tensor([h, w]) + + if "masks" in target: + target["masks"] = ( + interpolate(target["masks"][:, None].float(), size, mode="nearest")[:, 0] > 0.5 + ) + + return rescaled_image, target + + +def pad(image, target, padding): + # assumes that we only pad on the bottom right corners + padded_image = F.pad(image, (0, 0, padding[0], padding[1])) + if target is None: + return padded_image, None + target = target.copy() + # should we do something wrt the original size? + target["size"] = torch.tensor(padded_image.size[::-1]) + if "masks" in target: + target["masks"] = torch.nn.functional.pad(target["masks"], (0, padding[0], 0, padding[1])) + return padded_image, target + + +class ResizeDebug(object): + def __init__(self, size): + self.size = size + + def __call__(self, img, target): + return resize(img, target, self.size) + + +class RandomCrop(object): + def __init__(self, size): + self.size = size + + def __call__(self, img, target): + region = T.RandomCrop.get_params(img, self.size) + return crop(img, target, region) + + +class RandomSizeCrop(object): + def __init__(self, min_size: int, max_size: int, respect_boxes: bool = False): + # respect_boxes: True to keep all boxes + # False to tolerence box filter + self.min_size = min_size + self.max_size = max_size + self.respect_boxes = respect_boxes + + def __call__(self, img: PIL.Image.Image, target: dict): + init_boxes = len(target["boxes"]) + max_patience = 10 + for i in range(max_patience): + w = random.randint(self.min_size, min(img.width, self.max_size)) + h = random.randint(self.min_size, min(img.height, self.max_size)) + region = T.RandomCrop.get_params(img, [h, w]) + result_img, result_target = crop(img, target, region) + if ( + not self.respect_boxes + or len(result_target["boxes"]) == init_boxes + or i == max_patience - 1 + ): + return result_img, result_target + return result_img, result_target + + +class CenterCrop(object): + def __init__(self, size): + self.size = size + + def __call__(self, img, target): + image_width, image_height = img.size + crop_height, crop_width = self.size + crop_top = int(round((image_height - crop_height) / 2.0)) + crop_left = int(round((image_width - crop_width) / 2.0)) + return crop(img, target, (crop_top, crop_left, crop_height, crop_width)) + + +class RandomHorizontalFlip(object): + def __init__(self, p=0.5): + self.p = p + + def __call__(self, img, target): + if random.random() < self.p: + return hflip(img, target) + return img, target + + +class RandomResize(object): + def __init__(self, sizes, max_size=None): + assert isinstance(sizes, (list, tuple)) + self.sizes = sizes + self.max_size = max_size + + def __call__(self, img, target=None): + size = random.choice(self.sizes) + return resize(img, target, size, self.max_size) + + +class RandomPad(object): + def __init__(self, max_pad): + self.max_pad = max_pad + + def __call__(self, img, target): + pad_x = random.randint(0, self.max_pad) + pad_y = random.randint(0, self.max_pad) + return pad(img, target, (pad_x, pad_y)) + + +class RandomSelect(object): + """ + Randomly selects between transforms1 and transforms2, + with probability p for transforms1 and (1 - p) for transforms2 + """ + + def __init__(self, transforms1, transforms2, p=0.5): + self.transforms1 = transforms1 + self.transforms2 = transforms2 + self.p = p + + def __call__(self, img, target): + if random.random() < self.p: + return self.transforms1(img, target) + return self.transforms2(img, target) + + +class ToTensor(object): + def __call__(self, img, target): + return F.to_tensor(img), target + + +class RandomErasing(object): + def __init__(self, *args, **kwargs): + self.eraser = T.RandomErasing(*args, **kwargs) + + def __call__(self, img, target): + return self.eraser(img), target + + +class Normalize(object): + def __init__(self, mean, std): + self.mean = mean + self.std = std + + def __call__(self, image, target=None): + image = F.normalize(image, mean=self.mean, std=self.std) + if target is None: + return image, None + target = target.copy() + h, w = image.shape[-2:] + if "boxes" in target: + boxes = target["boxes"] + boxes = box_xyxy_to_cxcywh(boxes) + boxes = boxes / torch.tensor([w, h, w, h], dtype=torch.float32) + target["boxes"] = boxes + return image, target + + +class Compose(object): + def __init__(self, transforms): + self.transforms = transforms + + def __call__(self, image, target): + for t in self.transforms: + image, target = t(image, target) + return image, target + + def __repr__(self): + format_string = self.__class__.__name__ + "(" + for t in self.transforms: + format_string += "\n" + format_string += " {0}".format(t) + format_string += "\n)" + return format_string diff --git a/groundingdino/models/GroundingDINO/__init__.py b/groundingdino/models/GroundingDINO/__init__.py new file mode 100644 index 0000000..4ca1bd2 --- /dev/null +++ b/groundingdino/models/GroundingDINO/__init__.py @@ -0,0 +1,10 @@ +# ------------------------------------------------------------------------ +# Conditional DETR +# Copyright (c) 2021 Microsoft. All Rights Reserved. +# Licensed under the Apache License, Version 2.0 [see LICENSE for details] +# ------------------------------------------------------------------------ +# Copied from DETR (https://github.com/facebookresearch/detr) +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +# ------------------------------------------------------------------------ + +from .groundingdino import build_groundingdino diff --git a/groundingdino/models/GroundingDINO/backbone/__init__.py b/groundingdino/models/GroundingDINO/backbone/__init__.py new file mode 100644 index 0000000..76e4b27 --- /dev/null +++ b/groundingdino/models/GroundingDINO/backbone/__init__.py @@ -0,0 +1 @@ +from .backbone import build_backbone diff --git a/groundingdino/models/GroundingDINO/backbone/backbone.py b/groundingdino/models/GroundingDINO/backbone/backbone.py new file mode 100644 index 0000000..b4c8642 --- /dev/null +++ b/groundingdino/models/GroundingDINO/backbone/backbone.py @@ -0,0 +1,216 @@ +# ------------------------------------------------------------------------ +# Conditional DETR +# Copyright (c) 2021 Microsoft. All Rights Reserved. +# Licensed under the Apache License, Version 2.0 [see LICENSE for details] +# ------------------------------------------------------------------------ +# Copied from DETR (https://github.com/facebookresearch/detr) +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +# ------------------------------------------------------------------------ + +""" +Backbone modules. +""" + +from typing import Dict, List + +import torch +import torch.nn.functional as F +import torchvision +from torch import nn +from torchvision.models._utils import IntermediateLayerGetter + +from groundingdino.util.misc import NestedTensor, clean_state_dict, is_main_process + +from .position_encoding import build_position_encoding +from .swin_transformer import build_swin_transformer + + +class FrozenBatchNorm2d(torch.nn.Module): + """ + BatchNorm2d where the batch statistics and the affine parameters are fixed. + + Copy-paste from torchvision.misc.ops with added eps before rqsrt, + without which any other models than torchvision.models.resnet[18,34,50,101] + produce nans. + """ + + def __init__(self, n): + super(FrozenBatchNorm2d, self).__init__() + self.register_buffer("weight", torch.ones(n)) + self.register_buffer("bias", torch.zeros(n)) + self.register_buffer("running_mean", torch.zeros(n)) + self.register_buffer("running_var", torch.ones(n)) + + def _load_from_state_dict( + self, state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs + ): + num_batches_tracked_key = prefix + "num_batches_tracked" + if num_batches_tracked_key in state_dict: + del state_dict[num_batches_tracked_key] + + super(FrozenBatchNorm2d, self)._load_from_state_dict( + state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs + ) + + def forward(self, x): + # move reshapes to the beginning + # to make it fuser-friendly + w = self.weight.reshape(1, -1, 1, 1) + b = self.bias.reshape(1, -1, 1, 1) + rv = self.running_var.reshape(1, -1, 1, 1) + rm = self.running_mean.reshape(1, -1, 1, 1) + eps = 1e-5 + scale = w * (rv + eps).rsqrt() + bias = b - rm * scale + return x * scale + bias + + +class BackboneBase(nn.Module): + def __init__( + self, + backbone: nn.Module, + train_backbone: bool, + num_channels: int, + return_interm_indices: list, + ): + super().__init__() + for name, parameter in backbone.named_parameters(): + if ( + not train_backbone + or "layer2" not in name + and "layer3" not in name + and "layer4" not in name + ): + parameter.requires_grad_(False) + + return_layers = {} + for idx, layer_index in enumerate(return_interm_indices): + return_layers.update( + {"layer{}".format(5 - len(return_interm_indices) + idx): "{}".format(layer_index)} + ) + + # if len: + # if use_stage1_feature: + # return_layers = {"layer1": "0", "layer2": "1", "layer3": "2", "layer4": "3"} + # else: + # return_layers = {"layer2": "0", "layer3": "1", "layer4": "2"} + # else: + # return_layers = {'layer4': "0"} + self.body = IntermediateLayerGetter(backbone, return_layers=return_layers) + self.num_channels = num_channels + + def forward(self, tensor_list: NestedTensor): + xs = self.body(tensor_list.tensors) + out: Dict[str, NestedTensor] = {} + for name, x in xs.items(): + m = tensor_list.mask + assert m is not None + mask = F.interpolate(m[None].float(), size=x.shape[-2:]).to(torch.bool)[0] + out[name] = NestedTensor(x, mask) + # import ipdb; ipdb.set_trace() + return out + + +class Backbone(BackboneBase): + """ResNet backbone with frozen BatchNorm.""" + + def __init__( + self, + name: str, + train_backbone: bool, + dilation: bool, + return_interm_indices: list, + batch_norm=FrozenBatchNorm2d, + ): + if name in ["resnet18", "resnet34", "resnet50", "resnet101"]: + backbone = getattr(torchvision.models, name)( + replace_stride_with_dilation=[False, False, dilation], + pretrained=is_main_process(), + norm_layer=batch_norm, + ) + else: + raise NotImplementedError("Why you can get here with name {}".format(name)) + # num_channels = 512 if name in ('resnet18', 'resnet34') else 2048 + assert name not in ("resnet18", "resnet34"), "Only resnet50 and resnet101 are available." + assert return_interm_indices in [[0, 1, 2, 3], [1, 2, 3], [3]] + num_channels_all = [256, 512, 1024, 2048] + num_channels = num_channels_all[4 - len(return_interm_indices) :] + super().__init__(backbone, train_backbone, num_channels, return_interm_indices) + + +class Joiner(nn.Sequential): + def __init__(self, backbone, position_embedding): + super().__init__(backbone, position_embedding) + + def forward(self, tensor_list: NestedTensor): + xs = self[0](tensor_list) + out: List[NestedTensor] = [] + pos = [] + for name, x in xs.items(): + out.append(x) + # position encoding + pos.append(self[1](x).to(x.tensors.dtype)) + + return out, pos + + +def build_backbone(args): + """ + Useful args: + - backbone: backbone name + - lr_backbone: + - dilation + - return_interm_indices: available: [0,1,2,3], [1,2,3], [3] + - backbone_freeze_keywords: + - use_checkpoint: for swin only for now + + """ + position_embedding = build_position_encoding(args) + train_backbone = True + if not train_backbone: + raise ValueError("Please set lr_backbone > 0") + return_interm_indices = args.return_interm_indices + assert return_interm_indices in [[0, 1, 2, 3], [1, 2, 3], [3]] + args.backbone_freeze_keywords + use_checkpoint = getattr(args, "use_checkpoint", False) + + if args.backbone in ["resnet50", "resnet101"]: + backbone = Backbone( + args.backbone, + train_backbone, + args.dilation, + return_interm_indices, + batch_norm=FrozenBatchNorm2d, + ) + bb_num_channels = backbone.num_channels + elif args.backbone in [ + "swin_T_224_1k", + "swin_B_224_22k", + "swin_B_384_22k", + "swin_L_224_22k", + "swin_L_384_22k", + ]: + pretrain_img_size = int(args.backbone.split("_")[-2]) + backbone = build_swin_transformer( + args.backbone, + pretrain_img_size=pretrain_img_size, + out_indices=tuple(return_interm_indices), + dilation=False, + use_checkpoint=use_checkpoint, + ) + + bb_num_channels = backbone.num_features[4 - len(return_interm_indices) :] + else: + raise NotImplementedError("Unknown backbone {}".format(args.backbone)) + + assert len(bb_num_channels) == len( + return_interm_indices + ), f"len(bb_num_channels) {len(bb_num_channels)} != len(return_interm_indices) {len(return_interm_indices)}" + + model = Joiner(backbone, position_embedding) + model.num_channels = bb_num_channels + assert isinstance( + bb_num_channels, List + ), "bb_num_channels is expected to be a List but {}".format(type(bb_num_channels)) + # import ipdb; ipdb.set_trace() + return model diff --git a/groundingdino/models/GroundingDINO/backbone/position_encoding.py b/groundingdino/models/GroundingDINO/backbone/position_encoding.py new file mode 100644 index 0000000..2131aee --- /dev/null +++ b/groundingdino/models/GroundingDINO/backbone/position_encoding.py @@ -0,0 +1,181 @@ +# ------------------------------------------------------------------------ +# DINO +# Copyright (c) 2022 IDEA. All Rights Reserved. +# Licensed under the Apache License, Version 2.0 [see LICENSE for details] +# ------------------------------------------------------------------------ +# Conditional DETR +# Copyright (c) 2021 Microsoft. All Rights Reserved. +# Licensed under the Apache License, Version 2.0 [see LICENSE for details] +# ------------------------------------------------------------------------ +# Copied from DETR (https://github.com/facebookresearch/detr) +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +# ------------------------------------------------------------------------ + +""" +Various positional encodings for the transformer. +""" +import math + +import torch +from torch import nn + +from groundingdino.util.misc import NestedTensor + + +class PositionEmbeddingSine(nn.Module): + """ + This is a more standard version of the position embedding, very similar to the one + used by the Attention is all you need paper, generalized to work on images. + """ + + def __init__(self, num_pos_feats=64, temperature=10000, normalize=False, scale=None): + super().__init__() + self.num_pos_feats = num_pos_feats + self.temperature = temperature + self.normalize = normalize + if scale is not None and normalize is False: + raise ValueError("normalize should be True if scale is passed") + if scale is None: + scale = 2 * math.pi + self.scale = scale + + def forward(self, tensor_list: NestedTensor): + x = tensor_list.tensors + mask = tensor_list.mask + assert mask is not None + not_mask = ~mask + y_embed = not_mask.cumsum(1, dtype=torch.float32) + x_embed = not_mask.cumsum(2, dtype=torch.float32) + if self.normalize: + eps = 1e-6 + # if os.environ.get("SHILONG_AMP", None) == '1': + # eps = 1e-4 + # else: + # eps = 1e-6 + y_embed = y_embed / (y_embed[:, -1:, :] + eps) * self.scale + x_embed = x_embed / (x_embed[:, :, -1:] + eps) * self.scale + + dim_t = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device) + dim_t = self.temperature ** (2 * (dim_t // 2) / self.num_pos_feats) + + pos_x = x_embed[:, :, :, None] / dim_t + pos_y = y_embed[:, :, :, None] / dim_t + pos_x = torch.stack( + (pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4 + ).flatten(3) + pos_y = torch.stack( + (pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4 + ).flatten(3) + pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2) + return pos + + +class PositionEmbeddingSineHW(nn.Module): + """ + This is a more standard version of the position embedding, very similar to the one + used by the Attention is all you need paper, generalized to work on images. + """ + + def __init__( + self, num_pos_feats=64, temperatureH=10000, temperatureW=10000, normalize=False, scale=None + ): + super().__init__() + self.num_pos_feats = num_pos_feats + self.temperatureH = temperatureH + self.temperatureW = temperatureW + self.normalize = normalize + if scale is not None and normalize is False: + raise ValueError("normalize should be True if scale is passed") + if scale is None: + scale = 2 * math.pi + self.scale = scale + + def forward(self, tensor_list: NestedTensor): + x = tensor_list.tensors + mask = tensor_list.mask + assert mask is not None + not_mask = ~mask + y_embed = not_mask.cumsum(1, dtype=torch.float32) + x_embed = not_mask.cumsum(2, dtype=torch.float32) + + # import ipdb; ipdb.set_trace() + + if self.normalize: + eps = 1e-6 + y_embed = y_embed / (y_embed[:, -1:, :] + eps) * self.scale + x_embed = x_embed / (x_embed[:, :, -1:] + eps) * self.scale + + dim_tx = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device) + dim_tx = self.temperatureW ** (2 * (dim_tx // 2) / self.num_pos_feats) + pos_x = x_embed[:, :, :, None] / dim_tx + + dim_ty = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device) + dim_ty = self.temperatureH ** (2 * (dim_ty // 2) / self.num_pos_feats) + pos_y = y_embed[:, :, :, None] / dim_ty + + pos_x = torch.stack( + (pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4 + ).flatten(3) + pos_y = torch.stack( + (pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4 + ).flatten(3) + pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2) + + # import ipdb; ipdb.set_trace() + + return pos + + +class PositionEmbeddingLearned(nn.Module): + """ + Absolute pos embedding, learned. + """ + + def __init__(self, num_pos_feats=256): + super().__init__() + self.row_embed = nn.Embedding(50, num_pos_feats) + self.col_embed = nn.Embedding(50, num_pos_feats) + self.reset_parameters() + + def reset_parameters(self): + nn.init.uniform_(self.row_embed.weight) + nn.init.uniform_(self.col_embed.weight) + + def forward(self, tensor_list: NestedTensor): + x = tensor_list.tensors + h, w = x.shape[-2:] + i = torch.arange(w, device=x.device) + j = torch.arange(h, device=x.device) + x_emb = self.col_embed(i) + y_emb = self.row_embed(j) + pos = ( + torch.cat( + [ + x_emb.unsqueeze(0).repeat(h, 1, 1), + y_emb.unsqueeze(1).repeat(1, w, 1), + ], + dim=-1, + ) + .permute(2, 0, 1) + .unsqueeze(0) + .repeat(x.shape[0], 1, 1, 1) + ) + return pos + + +def build_position_encoding(args): + N_steps = args.hidden_dim // 2 + if args.position_embedding in ("v2", "sine"): + # TODO find a better way of exposing other arguments + position_embedding = PositionEmbeddingSineHW( + N_steps, + temperatureH=args.pe_temperatureH, + temperatureW=args.pe_temperatureW, + normalize=True, + ) + elif args.position_embedding in ("v3", "learned"): + position_embedding = PositionEmbeddingLearned(N_steps) + else: + raise ValueError(f"not supported {args.position_embedding}") + + return position_embedding diff --git a/groundingdino/models/GroundingDINO/backbone/swin_transformer.py b/groundingdino/models/GroundingDINO/backbone/swin_transformer.py new file mode 100644 index 0000000..767868d --- /dev/null +++ b/groundingdino/models/GroundingDINO/backbone/swin_transformer.py @@ -0,0 +1,797 @@ +# ------------------------------------------------------------------------ +# DINO +# Copyright (c) 2022 IDEA. All Rights Reserved. +# Licensed under the Apache License, Version 2.0 [see LICENSE for details] +# -------------------------------------------------------- +# modified from https://github.com/SwinTransformer/Swin-Transformer-Object-Detection/blob/master/mmdet/models/backbones/swin_transformer.py +# -------------------------------------------------------- + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.utils.checkpoint as checkpoint +from timm.models.layers import DropPath, to_2tuple, trunc_normal_ + +from groundingdino.util.misc import NestedTensor + + +class Mlp(nn.Module): + """Multilayer perceptron.""" + + def __init__( + self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.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 = 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 + + +def window_partition(x, window_size): + """ + Args: + x: (B, H, W, C) + window_size (int): window size + Returns: + windows: (num_windows*B, window_size, window_size, C) + """ + B, H, W, C = x.shape + x = x.view(B, H // window_size, window_size, W // window_size, window_size, C) + windows = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, window_size, window_size, C) + return windows + + +def window_reverse(windows, window_size, H, W): + """ + Args: + windows: (num_windows*B, window_size, window_size, C) + window_size (int): Window size + H (int): Height of image + W (int): Width of image + Returns: + x: (B, H, W, C) + """ + B = int(windows.shape[0] / (H * W / window_size / window_size)) + x = windows.view(B, H // window_size, W // window_size, window_size, window_size, -1) + x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, H, W, -1) + return x + + +class WindowAttention(nn.Module): + """Window based multi-head self attention (W-MSA) module with relative position bias. + It supports both of shifted and non-shifted window. + Args: + dim (int): Number of input channels. + window_size (tuple[int]): The height and width of the window. + num_heads (int): Number of attention heads. + qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True + qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set + attn_drop (float, optional): Dropout ratio of attention weight. Default: 0.0 + proj_drop (float, optional): Dropout ratio of output. Default: 0.0 + """ + + def __init__( + self, + dim, + window_size, + num_heads, + qkv_bias=True, + qk_scale=None, + attn_drop=0.0, + proj_drop=0.0, + ): + + super().__init__() + self.dim = dim + self.window_size = window_size # Wh, Ww + self.num_heads = num_heads + head_dim = dim // num_heads + self.scale = qk_scale or head_dim**-0.5 + + # define a parameter table of relative position bias + self.relative_position_bias_table = nn.Parameter( + torch.zeros((2 * window_size[0] - 1) * (2 * window_size[1] - 1), num_heads) + ) # 2*Wh-1 * 2*Ww-1, nH + + # get pair-wise relative position index for each token inside the window + coords_h = torch.arange(self.window_size[0]) + coords_w = torch.arange(self.window_size[1]) + coords = torch.stack(torch.meshgrid([coords_h, coords_w])) # 2, Wh, Ww + coords_flatten = torch.flatten(coords, 1) # 2, Wh*Ww + relative_coords = coords_flatten[:, :, None] - coords_flatten[:, None, :] # 2, Wh*Ww, Wh*Ww + relative_coords = relative_coords.permute(1, 2, 0).contiguous() # Wh*Ww, Wh*Ww, 2 + relative_coords[:, :, 0] += self.window_size[0] - 1 # shift to start from 0 + relative_coords[:, :, 1] += self.window_size[1] - 1 + relative_coords[:, :, 0] *= 2 * self.window_size[1] - 1 + relative_position_index = relative_coords.sum(-1) # Wh*Ww, Wh*Ww + self.register_buffer("relative_position_index", relative_position_index) + + self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias) + self.attn_drop = nn.Dropout(attn_drop) + self.proj = nn.Linear(dim, dim) + self.proj_drop = nn.Dropout(proj_drop) + + trunc_normal_(self.relative_position_bias_table, std=0.02) + self.softmax = nn.Softmax(dim=-1) + + def forward(self, x, mask=None): + """Forward function. + Args: + x: input features with shape of (num_windows*B, N, C) + mask: (0/-inf) mask with shape of (num_windows, Wh*Ww, Wh*Ww) or None + """ + B_, N, C = x.shape + qkv = ( + self.qkv(x) + .reshape(B_, N, 3, self.num_heads, C // self.num_heads) + .permute(2, 0, 3, 1, 4) + ) + q, k, v = qkv[0], qkv[1], qkv[2] # make torchscript happy (cannot use tensor as tuple) + + q = q * self.scale + attn = q @ k.transpose(-2, -1) + + relative_position_bias = self.relative_position_bias_table[ + self.relative_position_index.view(-1) + ].view( + self.window_size[0] * self.window_size[1], self.window_size[0] * self.window_size[1], -1 + ) # Wh*Ww,Wh*Ww,nH + relative_position_bias = relative_position_bias.permute( + 2, 0, 1 + ).contiguous() # nH, Wh*Ww, Wh*Ww + attn = attn + relative_position_bias.unsqueeze(0) + + if mask is not None: + nW = mask.shape[0] + attn = attn.view(B_ // nW, nW, self.num_heads, N, N) + mask.unsqueeze(1).unsqueeze(0) + attn = attn.view(-1, self.num_heads, N, N) + attn = self.softmax(attn) + else: + attn = self.softmax(attn) + + attn = self.attn_drop(attn) + + x = (attn @ v).transpose(1, 2).reshape(B_, N, C) + x = self.proj(x) + x = self.proj_drop(x) + return x + + +class SwinTransformerBlock(nn.Module): + """Swin Transformer Block. + Args: + dim (int): Number of input channels. + num_heads (int): Number of attention heads. + window_size (int): Window size. + shift_size (int): Shift size for SW-MSA. + mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. + qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True + qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set. + drop (float, optional): Dropout rate. Default: 0.0 + attn_drop (float, optional): Attention dropout rate. Default: 0.0 + drop_path (float, optional): Stochastic depth rate. Default: 0.0 + act_layer (nn.Module, optional): Activation layer. Default: nn.GELU + norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm + """ + + def __init__( + self, + dim, + num_heads, + window_size=7, + shift_size=0, + mlp_ratio=4.0, + qkv_bias=True, + qk_scale=None, + drop=0.0, + attn_drop=0.0, + drop_path=0.0, + act_layer=nn.GELU, + norm_layer=nn.LayerNorm, + ): + super().__init__() + self.dim = dim + self.num_heads = num_heads + self.window_size = window_size + self.shift_size = shift_size + self.mlp_ratio = mlp_ratio + assert 0 <= self.shift_size < self.window_size, "shift_size must in 0-window_size" + + self.norm1 = norm_layer(dim) + self.attn = WindowAttention( + dim, + window_size=to_2tuple(self.window_size), + num_heads=num_heads, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + attn_drop=attn_drop, + proj_drop=drop, + ) + + self.drop_path = DropPath(drop_path) if drop_path > 0.0 else nn.Identity() + self.norm2 = norm_layer(dim) + mlp_hidden_dim = int(dim * mlp_ratio) + self.mlp = Mlp( + in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop + ) + + self.H = None + self.W = None + + def forward(self, x, mask_matrix): + """Forward function. + Args: + x: Input feature, tensor size (B, H*W, C). + H, W: Spatial resolution of the input feature. + mask_matrix: Attention mask for cyclic shift. + """ + B, L, C = x.shape + H, W = self.H, self.W + assert L == H * W, "input feature has wrong size" + + shortcut = x + x = self.norm1(x) + x = x.view(B, H, W, C) + + # pad feature maps to multiples of window size + pad_l = pad_t = 0 + pad_r = (self.window_size - W % self.window_size) % self.window_size + pad_b = (self.window_size - H % self.window_size) % self.window_size + x = F.pad(x, (0, 0, pad_l, pad_r, pad_t, pad_b)) + _, Hp, Wp, _ = x.shape + + # cyclic shift + if self.shift_size > 0: + shifted_x = torch.roll(x, shifts=(-self.shift_size, -self.shift_size), dims=(1, 2)) + attn_mask = mask_matrix + else: + shifted_x = x + attn_mask = None + + # partition windows + x_windows = window_partition( + shifted_x, self.window_size + ) # nW*B, window_size, window_size, C + x_windows = x_windows.view( + -1, self.window_size * self.window_size, C + ) # nW*B, window_size*window_size, C + + # W-MSA/SW-MSA + attn_windows = self.attn(x_windows, mask=attn_mask) # nW*B, window_size*window_size, C + + # merge windows + attn_windows = attn_windows.view(-1, self.window_size, self.window_size, C) + shifted_x = window_reverse(attn_windows, self.window_size, Hp, Wp) # B H' W' C + + # reverse cyclic shift + if self.shift_size > 0: + x = torch.roll(shifted_x, shifts=(self.shift_size, self.shift_size), dims=(1, 2)) + else: + x = shifted_x + + if pad_r > 0 or pad_b > 0: + x = x[:, :H, :W, :].contiguous() + + x = x.view(B, H * W, C) + + # FFN + x = shortcut + self.drop_path(x) + x = x + self.drop_path(self.mlp(self.norm2(x))) + + return x + + +class PatchMerging(nn.Module): + """Patch Merging Layer + Args: + dim (int): Number of input channels. + norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm + """ + + def __init__(self, dim, norm_layer=nn.LayerNorm): + super().__init__() + self.dim = dim + self.reduction = nn.Linear(4 * dim, 2 * dim, bias=False) + self.norm = norm_layer(4 * dim) + + def forward(self, x, H, W): + """Forward function. + Args: + x: Input feature, tensor size (B, H*W, C). + H, W: Spatial resolution of the input feature. + """ + B, L, C = x.shape + assert L == H * W, "input feature has wrong size" + + x = x.view(B, H, W, C) + + # padding + pad_input = (H % 2 == 1) or (W % 2 == 1) + if pad_input: + x = F.pad(x, (0, 0, 0, W % 2, 0, H % 2)) + + x0 = x[:, 0::2, 0::2, :] # B H/2 W/2 C + x1 = x[:, 1::2, 0::2, :] # B H/2 W/2 C + x2 = x[:, 0::2, 1::2, :] # B H/2 W/2 C + x3 = x[:, 1::2, 1::2, :] # B H/2 W/2 C + x = torch.cat([x0, x1, x2, x3], -1) # B H/2 W/2 4*C + x = x.view(B, -1, 4 * C) # B H/2*W/2 4*C + + x = self.norm(x) + x = self.reduction(x) + + return x + + +class BasicLayer(nn.Module): + """A basic Swin Transformer layer for one stage. + Args: + dim (int): Number of feature channels + depth (int): Depths of this stage. + num_heads (int): Number of attention head. + window_size (int): Local window size. Default: 7. + mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. Default: 4. + qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True + qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set. + 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 + downsample (nn.Module | None, optional): Downsample layer at the end of the layer. Default: None + use_checkpoint (bool): Whether to use checkpointing to save memory. Default: False. + """ + + def __init__( + self, + dim, + depth, + num_heads, + window_size=7, + mlp_ratio=4.0, + qkv_bias=True, + qk_scale=None, + drop=0.0, + attn_drop=0.0, + drop_path=0.0, + norm_layer=nn.LayerNorm, + downsample=None, + use_checkpoint=False, + ): + super().__init__() + self.window_size = window_size + self.shift_size = window_size // 2 + self.depth = depth + self.use_checkpoint = use_checkpoint + + # build blocks + self.blocks = nn.ModuleList( + [ + SwinTransformerBlock( + dim=dim, + num_heads=num_heads, + window_size=window_size, + shift_size=0 if (i % 2 == 0) else window_size // 2, + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop=drop, + attn_drop=attn_drop, + drop_path=drop_path[i] if isinstance(drop_path, list) else drop_path, + norm_layer=norm_layer, + ) + for i in range(depth) + ] + ) + + # patch merging layer + if downsample is not None: + self.downsample = downsample(dim=dim, norm_layer=norm_layer) + else: + self.downsample = None + + def forward(self, x, H, W): + """Forward function. + Args: + x: Input feature, tensor size (B, H*W, C). + H, W: Spatial resolution of the input feature. + """ + + # calculate attention mask for SW-MSA + Hp = int(np.ceil(H / self.window_size)) * self.window_size + Wp = int(np.ceil(W / self.window_size)) * self.window_size + img_mask = torch.zeros((1, Hp, Wp, 1), device=x.device) # 1 Hp Wp 1 + h_slices = ( + slice(0, -self.window_size), + slice(-self.window_size, -self.shift_size), + slice(-self.shift_size, None), + ) + w_slices = ( + slice(0, -self.window_size), + slice(-self.window_size, -self.shift_size), + slice(-self.shift_size, None), + ) + cnt = 0 + for h in h_slices: + for w in w_slices: + img_mask[:, h, w, :] = cnt + cnt += 1 + + mask_windows = window_partition( + img_mask, self.window_size + ) # nW, window_size, window_size, 1 + mask_windows = mask_windows.view(-1, self.window_size * self.window_size) + attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2) + attn_mask = attn_mask.masked_fill(attn_mask != 0, float(-100.0)).masked_fill( + attn_mask == 0, float(0.0) + ) + + for blk in self.blocks: + blk.H, blk.W = H, W + if self.use_checkpoint: + x = checkpoint.checkpoint(blk, x, attn_mask) + else: + x = blk(x, attn_mask) + if self.downsample is not None: + x_down = self.downsample(x, H, W) + Wh, Ww = (H + 1) // 2, (W + 1) // 2 + return x, H, W, x_down, Wh, Ww + else: + return x, H, W, x, H, W + + +class PatchEmbed(nn.Module): + """Image to Patch Embedding + Args: + patch_size (int): Patch token size. Default: 4. + in_chans (int): Number of input image channels. Default: 3. + embed_dim (int): Number of linear projection output channels. Default: 96. + norm_layer (nn.Module, optional): Normalization layer. Default: None + """ + + def __init__(self, patch_size=4, in_chans=3, embed_dim=96, norm_layer=None): + super().__init__() + patch_size = to_2tuple(patch_size) + self.patch_size = patch_size + + self.in_chans = in_chans + self.embed_dim = embed_dim + + self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size) + if norm_layer is not None: + self.norm = norm_layer(embed_dim) + else: + self.norm = None + + def forward(self, x): + """Forward function.""" + # padding + _, _, H, W = x.size() + if W % self.patch_size[1] != 0: + x = F.pad(x, (0, self.patch_size[1] - W % self.patch_size[1])) + if H % self.patch_size[0] != 0: + x = F.pad(x, (0, 0, 0, self.patch_size[0] - H % self.patch_size[0])) + + x = self.proj(x) # B C Wh Ww + if self.norm is not None: + Wh, Ww = x.size(2), x.size(3) + x = x.flatten(2).transpose(1, 2) + x = self.norm(x) + x = x.transpose(1, 2).view(-1, self.embed_dim, Wh, Ww) + + return x + + +class SwinTransformer(nn.Module): + """Swin Transformer backbone. + A PyTorch impl of : `Swin Transformer: Hierarchical Vision Transformer using Shifted Windows` - + https://arxiv.org/pdf/2103.14030 + Args: + pretrain_img_size (int): Input image size for training the pretrained model, + used in absolute postion embedding. Default 224. + patch_size (int | tuple(int)): Patch size. Default: 4. + in_chans (int): Number of input image channels. Default: 3. + embed_dim (int): Number of linear projection output channels. Default: 96. + depths (tuple[int]): Depths of each Swin Transformer stage. + num_heads (tuple[int]): Number of attention head of each stage. + window_size (int): Window size. Default: 7. + mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. Default: 4. + qkv_bias (bool): If True, add a learnable bias to query, key, value. Default: True + qk_scale (float): Override default qk scale of head_dim ** -0.5 if set. + drop_rate (float): Dropout rate. + attn_drop_rate (float): Attention dropout rate. Default: 0. + drop_path_rate (float): Stochastic depth rate. Default: 0.2. + norm_layer (nn.Module): Normalization layer. Default: nn.LayerNorm. + ape (bool): If True, add absolute position embedding to the patch embedding. Default: False. + patch_norm (bool): If True, add normalization after patch embedding. Default: True. + out_indices (Sequence[int]): Output from which stages. + frozen_stages (int): Stages to be frozen (stop grad and set eval mode). + -1 means not freezing any parameters. + use_checkpoint (bool): Whether to use checkpointing to save memory. Default: False. + dilation (bool): if True, the output size if 16x downsample, ow 32x downsample. + """ + + def __init__( + self, + pretrain_img_size=224, + patch_size=4, + in_chans=3, + embed_dim=96, + depths=[2, 2, 6, 2], + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4.0, + qkv_bias=True, + qk_scale=None, + drop_rate=0.0, + attn_drop_rate=0.0, + drop_path_rate=0.2, + norm_layer=nn.LayerNorm, + ape=False, + patch_norm=True, + out_indices=(0, 1, 2, 3), + frozen_stages=-1, + dilation=False, + use_checkpoint=False, + ): + super().__init__() + + self.pretrain_img_size = pretrain_img_size + self.num_layers = len(depths) + self.embed_dim = embed_dim + self.ape = ape + self.patch_norm = patch_norm + self.out_indices = out_indices + self.frozen_stages = frozen_stages + self.dilation = dilation + + # if use_checkpoint: + # print("use_checkpoint!!!!!!!!!!!!!!!!!!!!!!!!") + + # split image into non-overlapping patches + self.patch_embed = PatchEmbed( + patch_size=patch_size, + in_chans=in_chans, + embed_dim=embed_dim, + norm_layer=norm_layer if self.patch_norm else None, + ) + + # absolute position embedding + if self.ape: + pretrain_img_size = to_2tuple(pretrain_img_size) + patch_size = to_2tuple(patch_size) + patches_resolution = [ + pretrain_img_size[0] // patch_size[0], + pretrain_img_size[1] // patch_size[1], + ] + + self.absolute_pos_embed = nn.Parameter( + torch.zeros(1, embed_dim, patches_resolution[0], patches_resolution[1]) + ) + trunc_normal_(self.absolute_pos_embed, std=0.02) + + self.pos_drop = nn.Dropout(p=drop_rate) + + # stochastic depth + dpr = [ + x.item() for x in torch.linspace(0, drop_path_rate, sum(depths)) + ] # stochastic depth decay rule + + # build layers + self.layers = nn.ModuleList() + # prepare downsample list + downsamplelist = [PatchMerging for i in range(self.num_layers)] + downsamplelist[-1] = None + num_features = [int(embed_dim * 2**i) for i in range(self.num_layers)] + if self.dilation: + downsamplelist[-2] = None + num_features[-1] = int(embed_dim * 2 ** (self.num_layers - 1)) // 2 + for i_layer in range(self.num_layers): + layer = BasicLayer( + # dim=int(embed_dim * 2 ** i_layer), + dim=num_features[i_layer], + depth=depths[i_layer], + num_heads=num_heads[i_layer], + window_size=window_size, + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop=drop_rate, + attn_drop=attn_drop_rate, + drop_path=dpr[sum(depths[:i_layer]) : sum(depths[: i_layer + 1])], + norm_layer=norm_layer, + # downsample=PatchMerging if (i_layer < self.num_layers - 1) else None, + downsample=downsamplelist[i_layer], + use_checkpoint=use_checkpoint, + ) + self.layers.append(layer) + + # num_features = [int(embed_dim * 2 ** i) for i in range(self.num_layers)] + self.num_features = num_features + + # add a norm layer for each output + for i_layer in out_indices: + layer = norm_layer(num_features[i_layer]) + layer_name = f"norm{i_layer}" + self.add_module(layer_name, layer) + + self._freeze_stages() + + def _freeze_stages(self): + if self.frozen_stages >= 0: + self.patch_embed.eval() + for param in self.patch_embed.parameters(): + param.requires_grad = False + + if self.frozen_stages >= 1 and self.ape: + self.absolute_pos_embed.requires_grad = False + + if self.frozen_stages >= 2: + self.pos_drop.eval() + for i in range(0, self.frozen_stages - 1): + m = self.layers[i] + m.eval() + for param in m.parameters(): + param.requires_grad = False + + # def init_weights(self, pretrained=None): + # """Initialize the weights in backbone. + # Args: + # pretrained (str, optional): Path to pre-trained weights. + # Defaults to None. + # """ + + # def _init_weights(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) + + # if isinstance(pretrained, str): + # self.apply(_init_weights) + # logger = get_root_logger() + # load_checkpoint(self, pretrained, strict=False, logger=logger) + # elif pretrained is None: + # self.apply(_init_weights) + # else: + # raise TypeError('pretrained must be a str or None') + + def forward_raw(self, x): + """Forward function.""" + x = self.patch_embed(x) + + Wh, Ww = x.size(2), x.size(3) + if self.ape: + # interpolate the position embedding to the corresponding size + absolute_pos_embed = F.interpolate( + self.absolute_pos_embed, size=(Wh, Ww), mode="bicubic" + ) + x = (x + absolute_pos_embed).flatten(2).transpose(1, 2) # B Wh*Ww C + else: + x = x.flatten(2).transpose(1, 2) + x = self.pos_drop(x) + + outs = [] + for i in range(self.num_layers): + layer = self.layers[i] + x_out, H, W, x, Wh, Ww = layer(x, Wh, Ww) + # import ipdb; ipdb.set_trace() + + if i in self.out_indices: + norm_layer = getattr(self, f"norm{i}") + x_out = norm_layer(x_out) + + out = x_out.view(-1, H, W, self.num_features[i]).permute(0, 3, 1, 2).contiguous() + outs.append(out) + # in: + # torch.Size([2, 3, 1024, 1024]) + # outs: + # [torch.Size([2, 192, 256, 256]), torch.Size([2, 384, 128, 128]), \ + # torch.Size([2, 768, 64, 64]), torch.Size([2, 1536, 32, 32])] + return tuple(outs) + + def forward(self, tensor_list: NestedTensor): + x = tensor_list.tensors + + """Forward function.""" + x = self.patch_embed(x) + + Wh, Ww = x.size(2), x.size(3) + if self.ape: + # interpolate the position embedding to the corresponding size + absolute_pos_embed = F.interpolate( + self.absolute_pos_embed, size=(Wh, Ww), mode="bicubic" + ) + x = (x + absolute_pos_embed).flatten(2).transpose(1, 2) # B Wh*Ww C + else: + x = x.flatten(2).transpose(1, 2) + x = self.pos_drop(x) + + outs = [] + for i in range(self.num_layers): + layer = self.layers[i] + x_out, H, W, x, Wh, Ww = layer(x, Wh, Ww) + + if i in self.out_indices: + norm_layer = getattr(self, f"norm{i}") + x_out = norm_layer(x_out) + + out = x_out.view(-1, H, W, self.num_features[i]).permute(0, 3, 1, 2).contiguous() + outs.append(out) + # in: + # torch.Size([2, 3, 1024, 1024]) + # out: + # [torch.Size([2, 192, 256, 256]), torch.Size([2, 384, 128, 128]), \ + # torch.Size([2, 768, 64, 64]), torch.Size([2, 1536, 32, 32])] + + # collect for nesttensors + outs_dict = {} + for idx, out_i in enumerate(outs): + m = tensor_list.mask + assert m is not None + mask = F.interpolate(m[None].float(), size=out_i.shape[-2:]).to(torch.bool)[0] + outs_dict[idx] = NestedTensor(out_i, mask) + + return outs_dict + + def train(self, mode=True): + """Convert the model into training mode while keep layers freezed.""" + super(SwinTransformer, self).train(mode) + self._freeze_stages() + + +def build_swin_transformer(modelname, pretrain_img_size, **kw): + assert modelname in [ + "swin_T_224_1k", + "swin_B_224_22k", + "swin_B_384_22k", + "swin_L_224_22k", + "swin_L_384_22k", + ] + + model_para_dict = { + "swin_T_224_1k": dict( + embed_dim=96, depths=[2, 2, 6, 2], num_heads=[3, 6, 12, 24], window_size=7 + ), + "swin_B_224_22k": dict( + embed_dim=128, depths=[2, 2, 18, 2], num_heads=[4, 8, 16, 32], window_size=7 + ), + "swin_B_384_22k": dict( + embed_dim=128, depths=[2, 2, 18, 2], num_heads=[4, 8, 16, 32], window_size=12 + ), + "swin_L_224_22k": dict( + embed_dim=192, depths=[2, 2, 18, 2], num_heads=[6, 12, 24, 48], window_size=7 + ), + "swin_L_384_22k": dict( + embed_dim=192, depths=[2, 2, 18, 2], num_heads=[6, 12, 24, 48], window_size=12 + ), + } + kw_cgf = model_para_dict[modelname] + kw_cgf.update(kw) + model = SwinTransformer(pretrain_img_size=pretrain_img_size, **kw_cgf) + return model + + +if __name__ == "__main__": + model = build_swin_transformer("swin_L_384_22k", 384, dilation=True) + x = torch.rand(2, 3, 1024, 1024) + y = model.forward_raw(x) + import ipdb + + ipdb.set_trace() + x = torch.rand(2, 3, 384, 384) + y = model.forward_raw(x) diff --git a/groundingdino/models/GroundingDINO/bertwarper.py b/groundingdino/models/GroundingDINO/bertwarper.py new file mode 100644 index 0000000..8474c1c --- /dev/null +++ b/groundingdino/models/GroundingDINO/bertwarper.py @@ -0,0 +1,266 @@ +import torch +import torch.nn.functional as F +import torch.utils.checkpoint as checkpoint +from torch import Tensor, nn +from torchvision.ops.boxes import nms +from transformers import BertConfig, BertModel, BertPreTrainedModel +from transformers.modeling_outputs import BaseModelOutputWithPoolingAndCrossAttentions + + +class BertModelWarper(nn.Module): + def __init__(self, bert_model): + super().__init__() + # self.bert = bert_modelc + + self.config = bert_model.config + self.embeddings = bert_model.embeddings + self.encoder = bert_model.encoder + self.pooler = bert_model.pooler + + self.get_extended_attention_mask = bert_model.get_extended_attention_mask + self.invert_attention_mask = bert_model.invert_attention_mask + self.get_head_mask = bert_model.get_head_mask + + def forward( + self, + input_ids=None, + attention_mask=None, + token_type_ids=None, + position_ids=None, + head_mask=None, + inputs_embeds=None, + encoder_hidden_states=None, + encoder_attention_mask=None, + past_key_values=None, + use_cache=None, + output_attentions=None, + output_hidden_states=None, + return_dict=None, + ): + r""" + encoder_hidden_states (:obj:`torch.FloatTensor` of shape :obj:`(batch_size, sequence_length, hidden_size)`, `optional`): + Sequence of hidden-states at the output of the last layer of the encoder. Used in the cross-attention if + the model is configured as a decoder. + encoder_attention_mask (:obj:`torch.FloatTensor` of shape :obj:`(batch_size, sequence_length)`, `optional`): + Mask to avoid performing attention on the padding token indices of the encoder input. This mask is used in + the cross-attention if the model is configured as a decoder. Mask values selected in ``[0, 1]``: + + - 1 for tokens that are **not masked**, + - 0 for tokens that are **masked**. + past_key_values (:obj:`tuple(tuple(torch.FloatTensor))` of length :obj:`config.n_layers` with each tuple having 4 tensors of shape :obj:`(batch_size, num_heads, sequence_length - 1, embed_size_per_head)`): + Contains precomputed key and value hidden states of the attention blocks. Can be used to speed up decoding. + + If :obj:`past_key_values` are used, the user can optionally input only the last :obj:`decoder_input_ids` + (those that don't have their past key value states given to this model) of shape :obj:`(batch_size, 1)` + instead of all :obj:`decoder_input_ids` of shape :obj:`(batch_size, sequence_length)`. + use_cache (:obj:`bool`, `optional`): + If set to :obj:`True`, :obj:`past_key_values` key value states are returned and can be used to speed up + decoding (see :obj:`past_key_values`). + """ + output_attentions = ( + output_attentions if output_attentions is not None else self.config.output_attentions + ) + output_hidden_states = ( + output_hidden_states + if output_hidden_states is not None + else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + if self.config.is_decoder: + use_cache = use_cache if use_cache is not None else self.config.use_cache + else: + use_cache = False + + if input_ids is not None and inputs_embeds is not None: + raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time") + elif input_ids is not None: + input_shape = input_ids.size() + batch_size, seq_length = input_shape + elif inputs_embeds is not None: + input_shape = inputs_embeds.size()[:-1] + batch_size, seq_length = input_shape + else: + raise ValueError("You have to specify either input_ids or inputs_embeds") + + device = input_ids.device if input_ids is not None else inputs_embeds.device + + # past_key_values_length + past_key_values_length = ( + past_key_values[0][0].shape[2] if past_key_values is not None else 0 + ) + + if attention_mask is None: + attention_mask = torch.ones( + ((batch_size, seq_length + past_key_values_length)), device=device + ) + if token_type_ids is None: + token_type_ids = torch.zeros(input_shape, dtype=torch.long, device=device) + + # We can provide a self-attention mask of dimensions [batch_size, from_seq_length, to_seq_length] + # ourselves in which case we just need to make it broadcastable to all heads. + extended_attention_mask: torch.Tensor = self.get_extended_attention_mask( + attention_mask, input_shape, device + ) + + # If a 2D or 3D attention mask is provided for the cross-attention + # we need to make broadcastable to [batch_size, num_heads, seq_length, seq_length] + if self.config.is_decoder and encoder_hidden_states is not None: + encoder_batch_size, encoder_sequence_length, _ = encoder_hidden_states.size() + encoder_hidden_shape = (encoder_batch_size, encoder_sequence_length) + if encoder_attention_mask is None: + encoder_attention_mask = torch.ones(encoder_hidden_shape, device=device) + encoder_extended_attention_mask = self.invert_attention_mask(encoder_attention_mask) + else: + encoder_extended_attention_mask = None + # if os.environ.get('IPDB_SHILONG_DEBUG', None) == 'INFO': + # import ipdb; ipdb.set_trace() + + # Prepare head mask if needed + # 1.0 in head_mask indicate we keep the head + # attention_probs has shape bsz x n_heads x N x N + # input head_mask has shape [num_heads] or [num_hidden_layers x num_heads] + # and head_mask is converted to shape [num_hidden_layers x batch x num_heads x seq_length x seq_length] + head_mask = self.get_head_mask(head_mask, self.config.num_hidden_layers) + + embedding_output = self.embeddings( + input_ids=input_ids, + position_ids=position_ids, + token_type_ids=token_type_ids, + inputs_embeds=inputs_embeds, + past_key_values_length=past_key_values_length, + ) + + encoder_outputs = self.encoder( + embedding_output, + attention_mask=extended_attention_mask, + head_mask=head_mask, + encoder_hidden_states=encoder_hidden_states, + encoder_attention_mask=encoder_extended_attention_mask, + past_key_values=past_key_values, + use_cache=use_cache, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + sequence_output = encoder_outputs[0] + pooled_output = self.pooler(sequence_output) if self.pooler is not None else None + + if not return_dict: + return (sequence_output, pooled_output) + encoder_outputs[1:] + + return BaseModelOutputWithPoolingAndCrossAttentions( + last_hidden_state=sequence_output, + pooler_output=pooled_output, + past_key_values=encoder_outputs.past_key_values, + hidden_states=encoder_outputs.hidden_states, + attentions=encoder_outputs.attentions, + cross_attentions=encoder_outputs.cross_attentions, + ) + + +class TextEncoderShell(nn.Module): + def __init__(self, text_encoder): + super().__init__() + self.text_encoder = text_encoder + self.config = self.text_encoder.config + + def forward(self, **kw): + # feed into text encoder + return self.text_encoder(**kw) + + +def generate_masks_with_special_tokens(tokenized, special_tokens_list, tokenizer): + """Generate attention mask between each pair of special tokens + Args: + input_ids (torch.Tensor): input ids. Shape: [bs, num_token] + special_tokens_mask (list): special tokens mask. + Returns: + torch.Tensor: attention mask between each special tokens. + """ + input_ids = tokenized["input_ids"] + bs, num_token = input_ids.shape + # special_tokens_mask: bs, num_token. 1 for special tokens. 0 for normal tokens + special_tokens_mask = torch.zeros((bs, num_token), device=input_ids.device).bool() + for special_token in special_tokens_list: + special_tokens_mask |= input_ids == special_token + + # idxs: each row is a list of indices of special tokens + idxs = torch.nonzero(special_tokens_mask) + + # generate attention mask and positional ids + attention_mask = ( + torch.eye(num_token, device=input_ids.device).bool().unsqueeze(0).repeat(bs, 1, 1) + ) + position_ids = torch.zeros((bs, num_token), device=input_ids.device) + previous_col = 0 + for i in range(idxs.shape[0]): + row, col = idxs[i] + if (col == 0) or (col == num_token - 1): + attention_mask[row, col, col] = True + position_ids[row, col] = 0 + else: + attention_mask[row, previous_col + 1 : col + 1, previous_col + 1 : col + 1] = True + position_ids[row, previous_col + 1 : col + 1] = torch.arange( + 0, col - previous_col, device=input_ids.device + ) + + previous_col = col + + # # padding mask + # padding_mask = tokenized['attention_mask'] + # attention_mask = attention_mask & padding_mask.unsqueeze(1).bool() & padding_mask.unsqueeze(2).bool() + + return attention_mask, position_ids.to(torch.long) + + +def generate_masks_with_special_tokens_and_transfer_map(tokenized, special_tokens_list, tokenizer): + """Generate attention mask between each pair of special tokens + Args: + input_ids (torch.Tensor): input ids. Shape: [bs, num_token] + special_tokens_mask (list): special tokens mask. + Returns: + torch.Tensor: attention mask between each special tokens. + """ + input_ids = tokenized["input_ids"] + bs, num_token = input_ids.shape + # special_tokens_mask: bs, num_token. 1 for special tokens. 0 for normal tokens + special_tokens_mask = torch.zeros((bs, num_token), device=input_ids.device).bool() + for special_token in special_tokens_list: + special_tokens_mask |= input_ids == special_token + + # idxs: each row is a list of indices of special tokens + idxs = torch.nonzero(special_tokens_mask) + + # generate attention mask and positional ids + attention_mask = ( + torch.eye(num_token, device=input_ids.device).bool().unsqueeze(0).repeat(bs, 1, 1) + ) + position_ids = torch.zeros((bs, num_token), device=input_ids.device) + cate_to_token_mask_list = [[] for _ in range(bs)] + previous_col = 0 + for i in range(idxs.shape[0]): + row, col = idxs[i] + if (col == 0) or (col == num_token - 1): + attention_mask[row, col, col] = True + position_ids[row, col] = 0 + else: + attention_mask[row, previous_col + 1 : col + 1, previous_col + 1 : col + 1] = True + position_ids[row, previous_col + 1 : col + 1] = torch.arange( + 0, col - previous_col, device=input_ids.device + ) + c2t_maski = torch.zeros((num_token), device=input_ids.device).bool() + c2t_maski[previous_col + 1 : col] = True + cate_to_token_mask_list[row].append(c2t_maski) + previous_col = col + + cate_to_token_mask_list = [ + torch.stack(cate_to_token_mask_listi, dim=0) + for cate_to_token_mask_listi in cate_to_token_mask_list + ] + + # # padding mask + # padding_mask = tokenized['attention_mask'] + # attention_mask = attention_mask & padding_mask.unsqueeze(1).bool() & padding_mask.unsqueeze(2).bool() + + return attention_mask, position_ids.to(torch.long), cate_to_token_mask_list diff --git a/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn.h b/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn.h new file mode 100644 index 0000000..c7408eb --- /dev/null +++ b/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn.h @@ -0,0 +1,64 @@ +/*! +************************************************************************************************** +* Deformable DETR +* Copyright (c) 2020 SenseTime. All Rights Reserved. +* Licensed under the Apache License, Version 2.0 [see LICENSE for details] +************************************************************************************************** +* Modified from https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch/tree/pytorch_1.0.0 +************************************************************************************************** +*/ + +#pragma once + +#include "ms_deform_attn_cpu.h" + +#ifdef WITH_CUDA +#include "ms_deform_attn_cuda.h" +#endif + +namespace groundingdino { + +at::Tensor +ms_deform_attn_forward( + const at::Tensor &value, + const at::Tensor &spatial_shapes, + const at::Tensor &level_start_index, + const at::Tensor &sampling_loc, + const at::Tensor &attn_weight, + const int im2col_step) +{ + if (value.type().is_cuda()) + { +#ifdef WITH_CUDA + return ms_deform_attn_cuda_forward( + value, spatial_shapes, level_start_index, sampling_loc, attn_weight, im2col_step); +#else + AT_ERROR("Not compiled with GPU support"); +#endif + } + AT_ERROR("Not implemented on the CPU"); +} + +std::vector +ms_deform_attn_backward( + const at::Tensor &value, + const at::Tensor &spatial_shapes, + const at::Tensor &level_start_index, + const at::Tensor &sampling_loc, + const at::Tensor &attn_weight, + const at::Tensor &grad_output, + const int im2col_step) +{ + if (value.type().is_cuda()) + { +#ifdef WITH_CUDA + return ms_deform_attn_cuda_backward( + value, spatial_shapes, level_start_index, sampling_loc, attn_weight, grad_output, im2col_step); +#else + AT_ERROR("Not compiled with GPU support"); +#endif + } + AT_ERROR("Not implemented on the CPU"); +} + +} // namespace groundingdino \ No newline at end of file diff --git a/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn_cpu.cpp b/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn_cpu.cpp new file mode 100644 index 0000000..551243f --- /dev/null +++ b/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn_cpu.cpp @@ -0,0 +1,43 @@ +/*! +************************************************************************************************** +* Deformable DETR +* Copyright (c) 2020 SenseTime. All Rights Reserved. +* Licensed under the Apache License, Version 2.0 [see LICENSE for details] +************************************************************************************************** +* Modified from https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch/tree/pytorch_1.0.0 +************************************************************************************************** +*/ + +#include + +#include +#include + +namespace groundingdino { + +at::Tensor +ms_deform_attn_cpu_forward( + const at::Tensor &value, + const at::Tensor &spatial_shapes, + const at::Tensor &level_start_index, + const at::Tensor &sampling_loc, + const at::Tensor &attn_weight, + const int im2col_step) +{ + AT_ERROR("Not implement on cpu"); +} + +std::vector +ms_deform_attn_cpu_backward( + const at::Tensor &value, + const at::Tensor &spatial_shapes, + const at::Tensor &level_start_index, + const at::Tensor &sampling_loc, + const at::Tensor &attn_weight, + const at::Tensor &grad_output, + const int im2col_step) +{ + AT_ERROR("Not implement on cpu"); +} + +} // namespace groundingdino diff --git a/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn_cpu.h b/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn_cpu.h new file mode 100644 index 0000000..b2b88e8 --- /dev/null +++ b/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn_cpu.h @@ -0,0 +1,35 @@ +/*! +************************************************************************************************** +* Deformable DETR +* Copyright (c) 2020 SenseTime. All Rights Reserved. +* Licensed under the Apache License, Version 2.0 [see LICENSE for details] +************************************************************************************************** +* Modified from https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch/tree/pytorch_1.0.0 +************************************************************************************************** +*/ + +#pragma once +#include + +namespace groundingdino { + +at::Tensor +ms_deform_attn_cpu_forward( + const at::Tensor &value, + const at::Tensor &spatial_shapes, + const at::Tensor &level_start_index, + const at::Tensor &sampling_loc, + const at::Tensor &attn_weight, + const int im2col_step); + +std::vector +ms_deform_attn_cpu_backward( + const at::Tensor &value, + const at::Tensor &spatial_shapes, + const at::Tensor &level_start_index, + const at::Tensor &sampling_loc, + const at::Tensor &attn_weight, + const at::Tensor &grad_output, + const int im2col_step); + +} // namespace groundingdino diff --git a/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn_cuda.cu b/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn_cuda.cu new file mode 100644 index 0000000..d04fae8 --- /dev/null +++ b/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn_cuda.cu @@ -0,0 +1,156 @@ +/*! +************************************************************************************************** +* Deformable DETR +* Copyright (c) 2020 SenseTime. All Rights Reserved. +* Licensed under the Apache License, Version 2.0 [see LICENSE for details] +************************************************************************************************** +* Modified from https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch/tree/pytorch_1.0.0 +************************************************************************************************** +*/ + +#include +#include "ms_deform_im2col_cuda.cuh" + +#include +#include +#include +#include + +namespace groundingdino { + +at::Tensor ms_deform_attn_cuda_forward( + const at::Tensor &value, + const at::Tensor &spatial_shapes, + const at::Tensor &level_start_index, + const at::Tensor &sampling_loc, + const at::Tensor &attn_weight, + const int im2col_step) +{ + AT_ASSERTM(value.is_contiguous(), "value tensor has to be contiguous"); + AT_ASSERTM(spatial_shapes.is_contiguous(), "spatial_shapes tensor has to be contiguous"); + AT_ASSERTM(level_start_index.is_contiguous(), "level_start_index tensor has to be contiguous"); + AT_ASSERTM(sampling_loc.is_contiguous(), "sampling_loc tensor has to be contiguous"); + AT_ASSERTM(attn_weight.is_contiguous(), "attn_weight tensor has to be contiguous"); + + AT_ASSERTM(value.type().is_cuda(), "value must be a CUDA tensor"); + AT_ASSERTM(spatial_shapes.type().is_cuda(), "spatial_shapes must be a CUDA tensor"); + AT_ASSERTM(level_start_index.type().is_cuda(), "level_start_index must be a CUDA tensor"); + AT_ASSERTM(sampling_loc.type().is_cuda(), "sampling_loc must be a CUDA tensor"); + AT_ASSERTM(attn_weight.type().is_cuda(), "attn_weight must be a CUDA tensor"); + + const int batch = value.size(0); + const int spatial_size = value.size(1); + const int num_heads = value.size(2); + const int channels = value.size(3); + + const int num_levels = spatial_shapes.size(0); + + const int num_query = sampling_loc.size(1); + const int num_point = sampling_loc.size(4); + + 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_); + + auto output = at::zeros({batch, num_query, num_heads, channels}, value.options()); + + const int batch_n = im2col_step_; + auto output_n = output.view({batch/im2col_step_, batch_n, num_query, num_heads, channels}); + auto per_value_size = spatial_size * num_heads * channels; + auto per_sample_loc_size = num_query * num_heads * num_levels * num_point * 2; + auto per_attn_weight_size = num_query * num_heads * num_levels * num_point; + for (int n = 0; n < batch/im2col_step_; ++n) + { + auto columns = output_n.select(0, n); + AT_DISPATCH_FLOATING_TYPES(value.type(), "ms_deform_attn_forward_cuda", ([&] { + ms_deformable_im2col_cuda(at::cuda::getCurrentCUDAStream(), + value.data() + n * im2col_step_ * per_value_size, + spatial_shapes.data(), + level_start_index.data(), + sampling_loc.data() + n * im2col_step_ * per_sample_loc_size, + attn_weight.data() + n * im2col_step_ * per_attn_weight_size, + batch_n, spatial_size, num_heads, channels, num_levels, num_query, num_point, + columns.data()); + + })); + } + + output = output.view({batch, num_query, num_heads*channels}); + + return output; +} + + +std::vector ms_deform_attn_cuda_backward( + const at::Tensor &value, + const at::Tensor &spatial_shapes, + const at::Tensor &level_start_index, + const at::Tensor &sampling_loc, + const at::Tensor &attn_weight, + const at::Tensor &grad_output, + const int im2col_step) +{ + + AT_ASSERTM(value.is_contiguous(), "value tensor has to be contiguous"); + AT_ASSERTM(spatial_shapes.is_contiguous(), "spatial_shapes tensor has to be contiguous"); + AT_ASSERTM(level_start_index.is_contiguous(), "level_start_index tensor has to be contiguous"); + AT_ASSERTM(sampling_loc.is_contiguous(), "sampling_loc tensor has to be contiguous"); + AT_ASSERTM(attn_weight.is_contiguous(), "attn_weight tensor has to be contiguous"); + AT_ASSERTM(grad_output.is_contiguous(), "grad_output tensor has to be contiguous"); + + AT_ASSERTM(value.type().is_cuda(), "value must be a CUDA tensor"); + AT_ASSERTM(spatial_shapes.type().is_cuda(), "spatial_shapes must be a CUDA tensor"); + AT_ASSERTM(level_start_index.type().is_cuda(), "level_start_index must be a CUDA tensor"); + AT_ASSERTM(sampling_loc.type().is_cuda(), "sampling_loc must be a CUDA tensor"); + AT_ASSERTM(attn_weight.type().is_cuda(), "attn_weight must be a CUDA tensor"); + AT_ASSERTM(grad_output.type().is_cuda(), "grad_output must be a CUDA tensor"); + + const int batch = value.size(0); + const int spatial_size = value.size(1); + const int num_heads = value.size(2); + const int channels = value.size(3); + + const int num_levels = spatial_shapes.size(0); + + const int num_query = sampling_loc.size(1); + const int num_point = sampling_loc.size(4); + + 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_); + + auto grad_value = at::zeros_like(value); + auto grad_sampling_loc = at::zeros_like(sampling_loc); + auto grad_attn_weight = at::zeros_like(attn_weight); + + const int batch_n = im2col_step_; + auto per_value_size = spatial_size * num_heads * channels; + auto per_sample_loc_size = num_query * num_heads * num_levels * num_point * 2; + auto per_attn_weight_size = num_query * num_heads * num_levels * num_point; + auto grad_output_n = grad_output.view({batch/im2col_step_, batch_n, num_query, num_heads, channels}); + + for (int n = 0; n < batch/im2col_step_; ++n) + { + auto grad_output_g = grad_output_n.select(0, n); + AT_DISPATCH_FLOATING_TYPES(value.type(), "ms_deform_attn_backward_cuda", ([&] { + ms_deformable_col2im_cuda(at::cuda::getCurrentCUDAStream(), + grad_output_g.data(), + value.data() + n * im2col_step_ * per_value_size, + spatial_shapes.data(), + level_start_index.data(), + sampling_loc.data() + n * im2col_step_ * per_sample_loc_size, + attn_weight.data() + n * im2col_step_ * per_attn_weight_size, + batch_n, spatial_size, num_heads, channels, num_levels, num_query, num_point, + grad_value.data() + n * im2col_step_ * per_value_size, + grad_sampling_loc.data() + n * im2col_step_ * per_sample_loc_size, + grad_attn_weight.data() + n * im2col_step_ * per_attn_weight_size); + + })); + } + + return { + grad_value, grad_sampling_loc, grad_attn_weight + }; +} + +} // namespace groundingdino \ No newline at end of file diff --git a/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn_cuda.h b/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn_cuda.h new file mode 100644 index 0000000..ad1311a --- /dev/null +++ b/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_attn_cuda.h @@ -0,0 +1,33 @@ +/*! +************************************************************************************************** +* Deformable DETR +* Copyright (c) 2020 SenseTime. All Rights Reserved. +* Licensed under the Apache License, Version 2.0 [see LICENSE for details] +************************************************************************************************** +* Modified from https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch/tree/pytorch_1.0.0 +************************************************************************************************** +*/ + +#pragma once +#include + +namespace groundingdino { + +at::Tensor ms_deform_attn_cuda_forward( + const at::Tensor &value, + const at::Tensor &spatial_shapes, + const at::Tensor &level_start_index, + const at::Tensor &sampling_loc, + const at::Tensor &attn_weight, + const int im2col_step); + +std::vector ms_deform_attn_cuda_backward( + const at::Tensor &value, + const at::Tensor &spatial_shapes, + const at::Tensor &level_start_index, + const at::Tensor &sampling_loc, + const at::Tensor &attn_weight, + const at::Tensor &grad_output, + const int im2col_step); + +} // namespace groundingdino \ No newline at end of file diff --git a/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_im2col_cuda.cuh b/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_im2col_cuda.cuh new file mode 100644 index 0000000..6bc2acb --- /dev/null +++ b/groundingdino/models/GroundingDINO/csrc/MsDeformAttn/ms_deform_im2col_cuda.cuh @@ -0,0 +1,1327 @@ +/*! +************************************************************************** +* Deformable DETR +* Copyright (c) 2020 SenseTime. All Rights Reserved. +* Licensed under the Apache License, Version 2.0 [see LICENSE for details] +************************************************************************** +* Modified from DCN (https://github.com/msracver/Deformable-ConvNets) +* Copyright (c) 2018 Microsoft +************************************************************************** +*/ + +#include +#include +#include + +#include +#include + +#include + +#define CUDA_KERNEL_LOOP(i, n) \ + for (int i = blockIdx.x * blockDim.x + threadIdx.x; \ + i < (n); \ + i += blockDim.x * gridDim.x) + +const int CUDA_NUM_THREADS = 1024; +inline int GET_BLOCKS(const int N, const int num_threads) +{ + return (N + num_threads - 1) / num_threads; +} + + +template +__device__ scalar_t ms_deform_attn_im2col_bilinear(const scalar_t* &bottom_data, + const int &height, const int &width, const int &nheads, const int &channels, + const scalar_t &h, const scalar_t &w, const int &m, const int &c) +{ + const int h_low = floor(h); + const int w_low = floor(w); + const int h_high = h_low + 1; + const int w_high = w_low + 1; + + const scalar_t lh = h - h_low; + const scalar_t lw = w - w_low; + const scalar_t hh = 1 - lh, hw = 1 - lw; + + const int w_stride = nheads * channels; + const int h_stride = width * w_stride; + const int h_low_ptr_offset = h_low * h_stride; + const int h_high_ptr_offset = h_low_ptr_offset + h_stride; + const int w_low_ptr_offset = w_low * w_stride; + const int w_high_ptr_offset = w_low_ptr_offset + w_stride; + const int base_ptr = m * channels + c; + + scalar_t v1 = 0; + if (h_low >= 0 && w_low >= 0) + { + const int ptr1 = h_low_ptr_offset + w_low_ptr_offset + base_ptr; + v1 = bottom_data[ptr1]; + } + scalar_t v2 = 0; + if (h_low >= 0 && w_high <= width - 1) + { + const int ptr2 = h_low_ptr_offset + w_high_ptr_offset + base_ptr; + v2 = bottom_data[ptr2]; + } + scalar_t v3 = 0; + if (h_high <= height - 1 && w_low >= 0) + { + const int ptr3 = h_high_ptr_offset + w_low_ptr_offset + base_ptr; + v3 = bottom_data[ptr3]; + } + scalar_t v4 = 0; + if (h_high <= height - 1 && w_high <= width - 1) + { + const int ptr4 = h_high_ptr_offset + w_high_ptr_offset + base_ptr; + v4 = bottom_data[ptr4]; + } + + const scalar_t w1 = hh * hw, w2 = hh * lw, w3 = lh * hw, w4 = lh * lw; + + const scalar_t val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4); + return val; +} + + +template +__device__ void ms_deform_attn_col2im_bilinear(const scalar_t* &bottom_data, + const int &height, const int &width, const int &nheads, const int &channels, + const scalar_t &h, const scalar_t &w, const int &m, const int &c, + const scalar_t &top_grad, + const scalar_t &attn_weight, + scalar_t* &grad_value, + scalar_t* grad_sampling_loc, + scalar_t* grad_attn_weight) +{ + const int h_low = floor(h); + const int w_low = floor(w); + const int h_high = h_low + 1; + const int w_high = w_low + 1; + + const scalar_t lh = h - h_low; + const scalar_t lw = w - w_low; + const scalar_t hh = 1 - lh, hw = 1 - lw; + + const int w_stride = nheads * channels; + const int h_stride = width * w_stride; + const int h_low_ptr_offset = h_low * h_stride; + const int h_high_ptr_offset = h_low_ptr_offset + h_stride; + const int w_low_ptr_offset = w_low * w_stride; + const int w_high_ptr_offset = w_low_ptr_offset + w_stride; + const int base_ptr = m * channels + c; + + const scalar_t w1 = hh * hw, w2 = hh * lw, w3 = lh * hw, w4 = lh * lw; + const scalar_t top_grad_value = top_grad * attn_weight; + scalar_t grad_h_weight = 0, grad_w_weight = 0; + + scalar_t v1 = 0; + if (h_low >= 0 && w_low >= 0) + { + const int ptr1 = h_low_ptr_offset + w_low_ptr_offset + base_ptr; + v1 = bottom_data[ptr1]; + grad_h_weight -= hw * v1; + grad_w_weight -= hh * v1; + atomicAdd(grad_value+ptr1, w1*top_grad_value); + } + scalar_t v2 = 0; + if (h_low >= 0 && w_high <= width - 1) + { + const int ptr2 = h_low_ptr_offset + w_high_ptr_offset + base_ptr; + v2 = bottom_data[ptr2]; + grad_h_weight -= lw * v2; + grad_w_weight += hh * v2; + atomicAdd(grad_value+ptr2, w2*top_grad_value); + } + scalar_t v3 = 0; + if (h_high <= height - 1 && w_low >= 0) + { + const int ptr3 = h_high_ptr_offset + w_low_ptr_offset + base_ptr; + v3 = bottom_data[ptr3]; + grad_h_weight += hw * v3; + grad_w_weight -= lh * v3; + atomicAdd(grad_value+ptr3, w3*top_grad_value); + } + scalar_t v4 = 0; + if (h_high <= height - 1 && w_high <= width - 1) + { + const int ptr4 = h_high_ptr_offset + w_high_ptr_offset + base_ptr; + v4 = bottom_data[ptr4]; + grad_h_weight += lw * v4; + grad_w_weight += lh * v4; + atomicAdd(grad_value+ptr4, w4*top_grad_value); + } + + const scalar_t val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4); + *grad_attn_weight = top_grad * val; + *grad_sampling_loc = width * grad_w_weight * top_grad_value; + *(grad_sampling_loc + 1) = height * grad_h_weight * top_grad_value; +} + + +template +__device__ void ms_deform_attn_col2im_bilinear_gm(const scalar_t* &bottom_data, + const int &height, const int &width, const int &nheads, const int &channels, + const scalar_t &h, const scalar_t &w, const int &m, const int &c, + const scalar_t &top_grad, + const scalar_t &attn_weight, + scalar_t* &grad_value, + scalar_t* grad_sampling_loc, + scalar_t* grad_attn_weight) +{ + const int h_low = floor(h); + const int w_low = floor(w); + const int h_high = h_low + 1; + const int w_high = w_low + 1; + + const scalar_t lh = h - h_low; + const scalar_t lw = w - w_low; + const scalar_t hh = 1 - lh, hw = 1 - lw; + + const int w_stride = nheads * channels; + const int h_stride = width * w_stride; + const int h_low_ptr_offset = h_low * h_stride; + const int h_high_ptr_offset = h_low_ptr_offset + h_stride; + const int w_low_ptr_offset = w_low * w_stride; + const int w_high_ptr_offset = w_low_ptr_offset + w_stride; + const int base_ptr = m * channels + c; + + const scalar_t w1 = hh * hw, w2 = hh * lw, w3 = lh * hw, w4 = lh * lw; + const scalar_t top_grad_value = top_grad * attn_weight; + scalar_t grad_h_weight = 0, grad_w_weight = 0; + + scalar_t v1 = 0; + if (h_low >= 0 && w_low >= 0) + { + const int ptr1 = h_low_ptr_offset + w_low_ptr_offset + base_ptr; + v1 = bottom_data[ptr1]; + grad_h_weight -= hw * v1; + grad_w_weight -= hh * v1; + atomicAdd(grad_value+ptr1, w1*top_grad_value); + } + scalar_t v2 = 0; + if (h_low >= 0 && w_high <= width - 1) + { + const int ptr2 = h_low_ptr_offset + w_high_ptr_offset + base_ptr; + v2 = bottom_data[ptr2]; + grad_h_weight -= lw * v2; + grad_w_weight += hh * v2; + atomicAdd(grad_value+ptr2, w2*top_grad_value); + } + scalar_t v3 = 0; + if (h_high <= height - 1 && w_low >= 0) + { + const int ptr3 = h_high_ptr_offset + w_low_ptr_offset + base_ptr; + v3 = bottom_data[ptr3]; + grad_h_weight += hw * v3; + grad_w_weight -= lh * v3; + atomicAdd(grad_value+ptr3, w3*top_grad_value); + } + scalar_t v4 = 0; + if (h_high <= height - 1 && w_high <= width - 1) + { + const int ptr4 = h_high_ptr_offset + w_high_ptr_offset + base_ptr; + v4 = bottom_data[ptr4]; + grad_h_weight += lw * v4; + grad_w_weight += lh * v4; + atomicAdd(grad_value+ptr4, w4*top_grad_value); + } + + const scalar_t val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4); + atomicAdd(grad_attn_weight, top_grad * val); + atomicAdd(grad_sampling_loc, width * grad_w_weight * top_grad_value); + atomicAdd(grad_sampling_loc + 1, height * grad_h_weight * top_grad_value); +} + + +template +__global__ void ms_deformable_im2col_gpu_kernel(const int n, + const scalar_t *data_value, + const int64_t *data_spatial_shapes, + const int64_t *data_level_start_index, + const scalar_t *data_sampling_loc, + const scalar_t *data_attn_weight, + const int batch_size, + const int spatial_size, + const int num_heads, + const int channels, + const int num_levels, + const int num_query, + const int num_point, + scalar_t *data_col) +{ + CUDA_KERNEL_LOOP(index, n) + { + int _temp = index; + const int c_col = _temp % channels; + _temp /= channels; + const int sampling_index = _temp; + const int m_col = _temp % num_heads; + _temp /= num_heads; + const int q_col = _temp % num_query; + _temp /= num_query; + const int b_col = _temp; + + scalar_t *data_col_ptr = data_col + index; + int data_weight_ptr = sampling_index * num_levels * num_point; + int data_loc_w_ptr = data_weight_ptr << 1; + const int qid_stride = num_heads * channels; + const int data_value_ptr_init_offset = b_col * spatial_size * qid_stride; + scalar_t col = 0; + + for (int l_col=0; l_col < num_levels; ++l_col) + { + const int level_start_id = data_level_start_index[l_col]; + const int spatial_h_ptr = l_col << 1; + const int spatial_h = data_spatial_shapes[spatial_h_ptr]; + const int spatial_w = data_spatial_shapes[spatial_h_ptr + 1]; + const scalar_t *data_value_ptr = data_value + (data_value_ptr_init_offset + level_start_id * qid_stride); + for (int p_col=0; p_col < num_point; ++p_col) + { + const scalar_t loc_w = data_sampling_loc[data_loc_w_ptr]; + const scalar_t loc_h = data_sampling_loc[data_loc_w_ptr + 1]; + const scalar_t weight = data_attn_weight[data_weight_ptr]; + + const scalar_t h_im = loc_h * spatial_h - 0.5; + const scalar_t w_im = loc_w * spatial_w - 0.5; + + if (h_im > -1 && w_im > -1 && h_im < spatial_h && w_im < spatial_w) + { + col += ms_deform_attn_im2col_bilinear(data_value_ptr, spatial_h, spatial_w, num_heads, channels, h_im, w_im, m_col, c_col) * weight; + } + + data_weight_ptr += 1; + data_loc_w_ptr += 2; + } + } + *data_col_ptr = col; + } +} + +template +__global__ void ms_deformable_col2im_gpu_kernel_shm_blocksize_aware_reduce_v1(const int n, + const scalar_t *grad_col, + const scalar_t *data_value, + const int64_t *data_spatial_shapes, + const int64_t *data_level_start_index, + const scalar_t *data_sampling_loc, + const scalar_t *data_attn_weight, + const int batch_size, + const int spatial_size, + const int num_heads, + const int channels, + const int num_levels, + const int num_query, + const int num_point, + scalar_t *grad_value, + scalar_t *grad_sampling_loc, + scalar_t *grad_attn_weight) +{ + CUDA_KERNEL_LOOP(index, n) + { + __shared__ scalar_t cache_grad_sampling_loc[blockSize * 2]; + __shared__ scalar_t cache_grad_attn_weight[blockSize]; + unsigned int tid = threadIdx.x; + int _temp = index; + const int c_col = _temp % channels; + _temp /= channels; + const int sampling_index = _temp; + const int m_col = _temp % num_heads; + _temp /= num_heads; + const int q_col = _temp % num_query; + _temp /= num_query; + const int b_col = _temp; + + const scalar_t top_grad = grad_col[index]; + + int data_weight_ptr = sampling_index * num_levels * num_point; + int data_loc_w_ptr = data_weight_ptr << 1; + const int grad_sampling_ptr = data_weight_ptr; + grad_sampling_loc += grad_sampling_ptr << 1; + grad_attn_weight += grad_sampling_ptr; + const int grad_weight_stride = 1; + const int grad_loc_stride = 2; + const int qid_stride = num_heads * channels; + const int data_value_ptr_init_offset = b_col * spatial_size * qid_stride; + + for (int l_col=0; l_col < num_levels; ++l_col) + { + const int level_start_id = data_level_start_index[l_col]; + const int spatial_h_ptr = l_col << 1; + const int spatial_h = data_spatial_shapes[spatial_h_ptr]; + const int spatial_w = data_spatial_shapes[spatial_h_ptr + 1]; + const int value_ptr_offset = data_value_ptr_init_offset + level_start_id * qid_stride; + const scalar_t *data_value_ptr = data_value + value_ptr_offset; + scalar_t *grad_value_ptr = grad_value + value_ptr_offset; + + for (int p_col=0; p_col < num_point; ++p_col) + { + const scalar_t loc_w = data_sampling_loc[data_loc_w_ptr]; + const scalar_t loc_h = data_sampling_loc[data_loc_w_ptr + 1]; + const scalar_t weight = data_attn_weight[data_weight_ptr]; + + const scalar_t h_im = loc_h * spatial_h - 0.5; + const scalar_t w_im = loc_w * spatial_w - 0.5; + *(cache_grad_sampling_loc+(threadIdx.x << 1)) = 0; + *(cache_grad_sampling_loc+((threadIdx.x << 1) + 1)) = 0; + *(cache_grad_attn_weight+threadIdx.x)=0; + if (h_im > -1 && w_im > -1 && h_im < spatial_h && w_im < spatial_w) + { + ms_deform_attn_col2im_bilinear( + data_value_ptr, spatial_h, spatial_w, num_heads, channels, h_im, w_im, m_col, c_col, + top_grad, weight, grad_value_ptr, + cache_grad_sampling_loc+(threadIdx.x << 1), cache_grad_attn_weight+threadIdx.x); + } + + __syncthreads(); + if (tid == 0) + { + scalar_t _grad_w=cache_grad_sampling_loc[0], _grad_h=cache_grad_sampling_loc[1], _grad_a=cache_grad_attn_weight[0]; + int sid=2; + for (unsigned int tid = 1; tid < blockSize; ++tid) + { + _grad_w += cache_grad_sampling_loc[sid]; + _grad_h += cache_grad_sampling_loc[sid + 1]; + _grad_a += cache_grad_attn_weight[tid]; + sid += 2; + } + + + *grad_sampling_loc = _grad_w; + *(grad_sampling_loc + 1) = _grad_h; + *grad_attn_weight = _grad_a; + } + __syncthreads(); + + data_weight_ptr += 1; + data_loc_w_ptr += 2; + grad_attn_weight += grad_weight_stride; + grad_sampling_loc += grad_loc_stride; + } + } + } +} + + +template +__global__ void ms_deformable_col2im_gpu_kernel_shm_blocksize_aware_reduce_v2(const int n, + const scalar_t *grad_col, + const scalar_t *data_value, + const int64_t *data_spatial_shapes, + const int64_t *data_level_start_index, + const scalar_t *data_sampling_loc, + const scalar_t *data_attn_weight, + const int batch_size, + const int spatial_size, + const int num_heads, + const int channels, + const int num_levels, + const int num_query, + const int num_point, + scalar_t *grad_value, + scalar_t *grad_sampling_loc, + scalar_t *grad_attn_weight) +{ + CUDA_KERNEL_LOOP(index, n) + { + __shared__ scalar_t cache_grad_sampling_loc[blockSize * 2]; + __shared__ scalar_t cache_grad_attn_weight[blockSize]; + unsigned int tid = threadIdx.x; + int _temp = index; + const int c_col = _temp % channels; + _temp /= channels; + const int sampling_index = _temp; + const int m_col = _temp % num_heads; + _temp /= num_heads; + const int q_col = _temp % num_query; + _temp /= num_query; + const int b_col = _temp; + + const scalar_t top_grad = grad_col[index]; + + int data_weight_ptr = sampling_index * num_levels * num_point; + int data_loc_w_ptr = data_weight_ptr << 1; + const int grad_sampling_ptr = data_weight_ptr; + grad_sampling_loc += grad_sampling_ptr << 1; + grad_attn_weight += grad_sampling_ptr; + const int grad_weight_stride = 1; + const int grad_loc_stride = 2; + const int qid_stride = num_heads * channels; + const int data_value_ptr_init_offset = b_col * spatial_size * qid_stride; + + for (int l_col=0; l_col < num_levels; ++l_col) + { + const int level_start_id = data_level_start_index[l_col]; + const int spatial_h_ptr = l_col << 1; + const int spatial_h = data_spatial_shapes[spatial_h_ptr]; + const int spatial_w = data_spatial_shapes[spatial_h_ptr + 1]; + const int value_ptr_offset = data_value_ptr_init_offset + level_start_id * qid_stride; + const scalar_t *data_value_ptr = data_value + value_ptr_offset; + scalar_t *grad_value_ptr = grad_value + value_ptr_offset; + + for (int p_col=0; p_col < num_point; ++p_col) + { + const scalar_t loc_w = data_sampling_loc[data_loc_w_ptr]; + const scalar_t loc_h = data_sampling_loc[data_loc_w_ptr + 1]; + const scalar_t weight = data_attn_weight[data_weight_ptr]; + + const scalar_t h_im = loc_h * spatial_h - 0.5; + const scalar_t w_im = loc_w * spatial_w - 0.5; + *(cache_grad_sampling_loc+(threadIdx.x << 1)) = 0; + *(cache_grad_sampling_loc+((threadIdx.x << 1) + 1)) = 0; + *(cache_grad_attn_weight+threadIdx.x)=0; + if (h_im > -1 && w_im > -1 && h_im < spatial_h && w_im < spatial_w) + { + ms_deform_attn_col2im_bilinear( + data_value_ptr, spatial_h, spatial_w, num_heads, channels, h_im, w_im, m_col, c_col, + top_grad, weight, grad_value_ptr, + cache_grad_sampling_loc+(threadIdx.x << 1), cache_grad_attn_weight+threadIdx.x); + } + + __syncthreads(); + + for (unsigned int s=blockSize/2; s>0; s>>=1) + { + if (tid < s) { + const unsigned int xid1 = tid << 1; + const unsigned int xid2 = (tid + s) << 1; + cache_grad_attn_weight[tid] += cache_grad_attn_weight[tid + s]; + cache_grad_sampling_loc[xid1] += cache_grad_sampling_loc[xid2]; + cache_grad_sampling_loc[xid1 + 1] += cache_grad_sampling_loc[xid2 + 1]; + } + __syncthreads(); + } + + if (tid == 0) + { + *grad_sampling_loc = cache_grad_sampling_loc[0]; + *(grad_sampling_loc + 1) = cache_grad_sampling_loc[1]; + *grad_attn_weight = cache_grad_attn_weight[0]; + } + __syncthreads(); + + data_weight_ptr += 1; + data_loc_w_ptr += 2; + grad_attn_weight += grad_weight_stride; + grad_sampling_loc += grad_loc_stride; + } + } + } +} + + +template +__global__ void ms_deformable_col2im_gpu_kernel_shm_reduce_v1(const int n, + const scalar_t *grad_col, + const scalar_t *data_value, + const int64_t *data_spatial_shapes, + const int64_t *data_level_start_index, + const scalar_t *data_sampling_loc, + const scalar_t *data_attn_weight, + const int batch_size, + const int spatial_size, + const int num_heads, + const int channels, + const int num_levels, + const int num_query, + const int num_point, + scalar_t *grad_value, + scalar_t *grad_sampling_loc, + scalar_t *grad_attn_weight) +{ + CUDA_KERNEL_LOOP(index, n) + { + extern __shared__ int _s[]; + scalar_t* cache_grad_sampling_loc = (scalar_t*)_s; + scalar_t* cache_grad_attn_weight = cache_grad_sampling_loc + 2 * blockDim.x; + unsigned int tid = threadIdx.x; + int _temp = index; + const int c_col = _temp % channels; + _temp /= channels; + const int sampling_index = _temp; + const int m_col = _temp % num_heads; + _temp /= num_heads; + const int q_col = _temp % num_query; + _temp /= num_query; + const int b_col = _temp; + + const scalar_t top_grad = grad_col[index]; + + int data_weight_ptr = sampling_index * num_levels * num_point; + int data_loc_w_ptr = data_weight_ptr << 1; + const int grad_sampling_ptr = data_weight_ptr; + grad_sampling_loc += grad_sampling_ptr << 1; + grad_attn_weight += grad_sampling_ptr; + const int grad_weight_stride = 1; + const int grad_loc_stride = 2; + const int qid_stride = num_heads * channels; + const int data_value_ptr_init_offset = b_col * spatial_size * qid_stride; + + for (int l_col=0; l_col < num_levels; ++l_col) + { + const int level_start_id = data_level_start_index[l_col]; + const int spatial_h_ptr = l_col << 1; + const int spatial_h = data_spatial_shapes[spatial_h_ptr]; + const int spatial_w = data_spatial_shapes[spatial_h_ptr + 1]; + const int value_ptr_offset = data_value_ptr_init_offset + level_start_id * qid_stride; + const scalar_t *data_value_ptr = data_value + value_ptr_offset; + scalar_t *grad_value_ptr = grad_value + value_ptr_offset; + + for (int p_col=0; p_col < num_point; ++p_col) + { + const scalar_t loc_w = data_sampling_loc[data_loc_w_ptr]; + const scalar_t loc_h = data_sampling_loc[data_loc_w_ptr + 1]; + const scalar_t weight = data_attn_weight[data_weight_ptr]; + + const scalar_t h_im = loc_h * spatial_h - 0.5; + const scalar_t w_im = loc_w * spatial_w - 0.5; + *(cache_grad_sampling_loc+(threadIdx.x << 1)) = 0; + *(cache_grad_sampling_loc+((threadIdx.x << 1) + 1)) = 0; + *(cache_grad_attn_weight+threadIdx.x)=0; + if (h_im > -1 && w_im > -1 && h_im < spatial_h && w_im < spatial_w) + { + ms_deform_attn_col2im_bilinear( + data_value_ptr, spatial_h, spatial_w, num_heads, channels, h_im, w_im, m_col, c_col, + top_grad, weight, grad_value_ptr, + cache_grad_sampling_loc+(threadIdx.x << 1), cache_grad_attn_weight+threadIdx.x); + } + + __syncthreads(); + if (tid == 0) + { + scalar_t _grad_w=cache_grad_sampling_loc[0], _grad_h=cache_grad_sampling_loc[1], _grad_a=cache_grad_attn_weight[0]; + int sid=2; + for (unsigned int tid = 1; tid < blockDim.x; ++tid) + { + _grad_w += cache_grad_sampling_loc[sid]; + _grad_h += cache_grad_sampling_loc[sid + 1]; + _grad_a += cache_grad_attn_weight[tid]; + sid += 2; + } + + + *grad_sampling_loc = _grad_w; + *(grad_sampling_loc + 1) = _grad_h; + *grad_attn_weight = _grad_a; + } + __syncthreads(); + + data_weight_ptr += 1; + data_loc_w_ptr += 2; + grad_attn_weight += grad_weight_stride; + grad_sampling_loc += grad_loc_stride; + } + } + } +} + +template +__global__ void ms_deformable_col2im_gpu_kernel_shm_reduce_v2(const int n, + const scalar_t *grad_col, + const scalar_t *data_value, + const int64_t *data_spatial_shapes, + const int64_t *data_level_start_index, + const scalar_t *data_sampling_loc, + const scalar_t *data_attn_weight, + const int batch_size, + const int spatial_size, + const int num_heads, + const int channels, + const int num_levels, + const int num_query, + const int num_point, + scalar_t *grad_value, + scalar_t *grad_sampling_loc, + scalar_t *grad_attn_weight) +{ + CUDA_KERNEL_LOOP(index, n) + { + extern __shared__ int _s[]; + scalar_t* cache_grad_sampling_loc = (scalar_t*)_s; + scalar_t* cache_grad_attn_weight = cache_grad_sampling_loc + 2 * blockDim.x; + unsigned int tid = threadIdx.x; + int _temp = index; + const int c_col = _temp % channels; + _temp /= channels; + const int sampling_index = _temp; + const int m_col = _temp % num_heads; + _temp /= num_heads; + const int q_col = _temp % num_query; + _temp /= num_query; + const int b_col = _temp; + + const scalar_t top_grad = grad_col[index]; + + int data_weight_ptr = sampling_index * num_levels * num_point; + int data_loc_w_ptr = data_weight_ptr << 1; + const int grad_sampling_ptr = data_weight_ptr; + grad_sampling_loc += grad_sampling_ptr << 1; + grad_attn_weight += grad_sampling_ptr; + const int grad_weight_stride = 1; + const int grad_loc_stride = 2; + const int qid_stride = num_heads * channels; + const int data_value_ptr_init_offset = b_col * spatial_size * qid_stride; + + for (int l_col=0; l_col < num_levels; ++l_col) + { + const int level_start_id = data_level_start_index[l_col]; + const int spatial_h_ptr = l_col << 1; + const int spatial_h = data_spatial_shapes[spatial_h_ptr]; + const int spatial_w = data_spatial_shapes[spatial_h_ptr + 1]; + const int value_ptr_offset = data_value_ptr_init_offset + level_start_id * qid_stride; + const scalar_t *data_value_ptr = data_value + value_ptr_offset; + scalar_t *grad_value_ptr = grad_value + value_ptr_offset; + + for (int p_col=0; p_col < num_point; ++p_col) + { + const scalar_t loc_w = data_sampling_loc[data_loc_w_ptr]; + const scalar_t loc_h = data_sampling_loc[data_loc_w_ptr + 1]; + const scalar_t weight = data_attn_weight[data_weight_ptr]; + + const scalar_t h_im = loc_h * spatial_h - 0.5; + const scalar_t w_im = loc_w * spatial_w - 0.5; + *(cache_grad_sampling_loc+(threadIdx.x << 1)) = 0; + *(cache_grad_sampling_loc+((threadIdx.x << 1) + 1)) = 0; + *(cache_grad_attn_weight+threadIdx.x)=0; + if (h_im > -1 && w_im > -1 && h_im < spatial_h && w_im < spatial_w) + { + ms_deform_attn_col2im_bilinear( + data_value_ptr, spatial_h, spatial_w, num_heads, channels, h_im, w_im, m_col, c_col, + top_grad, weight, grad_value_ptr, + cache_grad_sampling_loc+(threadIdx.x << 1), cache_grad_attn_weight+threadIdx.x); + } + + __syncthreads(); + + for (unsigned int s=blockDim.x/2, spre=blockDim.x; s>0; s>>=1, spre>>=1) + { + if (tid < s) { + const unsigned int xid1 = tid << 1; + const unsigned int xid2 = (tid + s) << 1; + cache_grad_attn_weight[tid] += cache_grad_attn_weight[tid + s]; + cache_grad_sampling_loc[xid1] += cache_grad_sampling_loc[xid2]; + cache_grad_sampling_loc[xid1 + 1] += cache_grad_sampling_loc[xid2 + 1]; + if (tid + (s << 1) < spre) + { + cache_grad_attn_weight[tid] += cache_grad_attn_weight[tid + (s << 1)]; + cache_grad_sampling_loc[xid1] += cache_grad_sampling_loc[xid2 + (s << 1)]; + cache_grad_sampling_loc[xid1 + 1] += cache_grad_sampling_loc[xid2 + 1 + (s << 1)]; + } + } + __syncthreads(); + } + + if (tid == 0) + { + *grad_sampling_loc = cache_grad_sampling_loc[0]; + *(grad_sampling_loc + 1) = cache_grad_sampling_loc[1]; + *grad_attn_weight = cache_grad_attn_weight[0]; + } + __syncthreads(); + + data_weight_ptr += 1; + data_loc_w_ptr += 2; + grad_attn_weight += grad_weight_stride; + grad_sampling_loc += grad_loc_stride; + } + } + } +} + +template +__global__ void ms_deformable_col2im_gpu_kernel_shm_reduce_v2_multi_blocks(const int n, + const scalar_t *grad_col, + const scalar_t *data_value, + const int64_t *data_spatial_shapes, + const int64_t *data_level_start_index, + const scalar_t *data_sampling_loc, + const scalar_t *data_attn_weight, + const int batch_size, + const int spatial_size, + const int num_heads, + const int channels, + const int num_levels, + const int num_query, + const int num_point, + scalar_t *grad_value, + scalar_t *grad_sampling_loc, + scalar_t *grad_attn_weight) +{ + CUDA_KERNEL_LOOP(index, n) + { + extern __shared__ int _s[]; + scalar_t* cache_grad_sampling_loc = (scalar_t*)_s; + scalar_t* cache_grad_attn_weight = cache_grad_sampling_loc + 2 * blockDim.x; + unsigned int tid = threadIdx.x; + int _temp = index; + const int c_col = _temp % channels; + _temp /= channels; + const int sampling_index = _temp; + const int m_col = _temp % num_heads; + _temp /= num_heads; + const int q_col = _temp % num_query; + _temp /= num_query; + const int b_col = _temp; + + const scalar_t top_grad = grad_col[index]; + + int data_weight_ptr = sampling_index * num_levels * num_point; + int data_loc_w_ptr = data_weight_ptr << 1; + const int grad_sampling_ptr = data_weight_ptr; + grad_sampling_loc += grad_sampling_ptr << 1; + grad_attn_weight += grad_sampling_ptr; + const int grad_weight_stride = 1; + const int grad_loc_stride = 2; + const int qid_stride = num_heads * channels; + const int data_value_ptr_init_offset = b_col * spatial_size * qid_stride; + + for (int l_col=0; l_col < num_levels; ++l_col) + { + const int level_start_id = data_level_start_index[l_col]; + const int spatial_h_ptr = l_col << 1; + const int spatial_h = data_spatial_shapes[spatial_h_ptr]; + const int spatial_w = data_spatial_shapes[spatial_h_ptr + 1]; + const int value_ptr_offset = data_value_ptr_init_offset + level_start_id * qid_stride; + const scalar_t *data_value_ptr = data_value + value_ptr_offset; + scalar_t *grad_value_ptr = grad_value + value_ptr_offset; + + for (int p_col=0; p_col < num_point; ++p_col) + { + const scalar_t loc_w = data_sampling_loc[data_loc_w_ptr]; + const scalar_t loc_h = data_sampling_loc[data_loc_w_ptr + 1]; + const scalar_t weight = data_attn_weight[data_weight_ptr]; + + const scalar_t h_im = loc_h * spatial_h - 0.5; + const scalar_t w_im = loc_w * spatial_w - 0.5; + *(cache_grad_sampling_loc+(threadIdx.x << 1)) = 0; + *(cache_grad_sampling_loc+((threadIdx.x << 1) + 1)) = 0; + *(cache_grad_attn_weight+threadIdx.x)=0; + if (h_im > -1 && w_im > -1 && h_im < spatial_h && w_im < spatial_w) + { + ms_deform_attn_col2im_bilinear( + data_value_ptr, spatial_h, spatial_w, num_heads, channels, h_im, w_im, m_col, c_col, + top_grad, weight, grad_value_ptr, + cache_grad_sampling_loc+(threadIdx.x << 1), cache_grad_attn_weight+threadIdx.x); + } + + __syncthreads(); + + for (unsigned int s=blockDim.x/2, spre=blockDim.x; s>0; s>>=1, spre>>=1) + { + if (tid < s) { + const unsigned int xid1 = tid << 1; + const unsigned int xid2 = (tid + s) << 1; + cache_grad_attn_weight[tid] += cache_grad_attn_weight[tid + s]; + cache_grad_sampling_loc[xid1] += cache_grad_sampling_loc[xid2]; + cache_grad_sampling_loc[xid1 + 1] += cache_grad_sampling_loc[xid2 + 1]; + if (tid + (s << 1) < spre) + { + cache_grad_attn_weight[tid] += cache_grad_attn_weight[tid + (s << 1)]; + cache_grad_sampling_loc[xid1] += cache_grad_sampling_loc[xid2 + (s << 1)]; + cache_grad_sampling_loc[xid1 + 1] += cache_grad_sampling_loc[xid2 + 1 + (s << 1)]; + } + } + __syncthreads(); + } + + if (tid == 0) + { + atomicAdd(grad_sampling_loc, cache_grad_sampling_loc[0]); + atomicAdd(grad_sampling_loc + 1, cache_grad_sampling_loc[1]); + atomicAdd(grad_attn_weight, cache_grad_attn_weight[0]); + } + __syncthreads(); + + data_weight_ptr += 1; + data_loc_w_ptr += 2; + grad_attn_weight += grad_weight_stride; + grad_sampling_loc += grad_loc_stride; + } + } + } +} + + +template +__global__ void ms_deformable_col2im_gpu_kernel_gm(const int n, + const scalar_t *grad_col, + const scalar_t *data_value, + const int64_t *data_spatial_shapes, + const int64_t *data_level_start_index, + const scalar_t *data_sampling_loc, + const scalar_t *data_attn_weight, + const int batch_size, + const int spatial_size, + const int num_heads, + const int channels, + const int num_levels, + const int num_query, + const int num_point, + scalar_t *grad_value, + scalar_t *grad_sampling_loc, + scalar_t *grad_attn_weight) +{ + CUDA_KERNEL_LOOP(index, n) + { + int _temp = index; + const int c_col = _temp % channels; + _temp /= channels; + const int sampling_index = _temp; + const int m_col = _temp % num_heads; + _temp /= num_heads; + const int q_col = _temp % num_query; + _temp /= num_query; + const int b_col = _temp; + + const scalar_t top_grad = grad_col[index]; + + int data_weight_ptr = sampling_index * num_levels * num_point; + int data_loc_w_ptr = data_weight_ptr << 1; + const int grad_sampling_ptr = data_weight_ptr; + grad_sampling_loc += grad_sampling_ptr << 1; + grad_attn_weight += grad_sampling_ptr; + const int grad_weight_stride = 1; + const int grad_loc_stride = 2; + const int qid_stride = num_heads * channels; + const int data_value_ptr_init_offset = b_col * spatial_size * qid_stride; + + for (int l_col=0; l_col < num_levels; ++l_col) + { + const int level_start_id = data_level_start_index[l_col]; + const int spatial_h_ptr = l_col << 1; + const int spatial_h = data_spatial_shapes[spatial_h_ptr]; + const int spatial_w = data_spatial_shapes[spatial_h_ptr + 1]; + const int value_ptr_offset = data_value_ptr_init_offset + level_start_id * qid_stride; + const scalar_t *data_value_ptr = data_value + value_ptr_offset; + scalar_t *grad_value_ptr = grad_value + value_ptr_offset; + + for (int p_col=0; p_col < num_point; ++p_col) + { + const scalar_t loc_w = data_sampling_loc[data_loc_w_ptr]; + const scalar_t loc_h = data_sampling_loc[data_loc_w_ptr + 1]; + const scalar_t weight = data_attn_weight[data_weight_ptr]; + + const scalar_t h_im = loc_h * spatial_h - 0.5; + const scalar_t w_im = loc_w * spatial_w - 0.5; + if (h_im > -1 && w_im > -1 && h_im < spatial_h && w_im < spatial_w) + { + ms_deform_attn_col2im_bilinear_gm( + data_value_ptr, spatial_h, spatial_w, num_heads, channels, h_im, w_im, m_col, c_col, + top_grad, weight, grad_value_ptr, + grad_sampling_loc, grad_attn_weight); + } + data_weight_ptr += 1; + data_loc_w_ptr += 2; + grad_attn_weight += grad_weight_stride; + grad_sampling_loc += grad_loc_stride; + } + } + } +} + + +template +void ms_deformable_im2col_cuda(cudaStream_t stream, + const scalar_t* data_value, + const int64_t* data_spatial_shapes, + const int64_t* data_level_start_index, + const scalar_t* data_sampling_loc, + const scalar_t* data_attn_weight, + const int batch_size, + const int spatial_size, + const int num_heads, + const int channels, + const int num_levels, + const int num_query, + const int num_point, + scalar_t* data_col) +{ + const int num_kernels = batch_size * num_query * num_heads * channels; + const int num_actual_kernels = batch_size * num_query * num_heads * channels; + const int num_threads = CUDA_NUM_THREADS; + ms_deformable_im2col_gpu_kernel + <<>>( + num_kernels, data_value, data_spatial_shapes, data_level_start_index, data_sampling_loc, data_attn_weight, + batch_size, spatial_size, num_heads, channels, num_levels, num_query, num_point, data_col); + + cudaError_t err = cudaGetLastError(); + if (err != cudaSuccess) + { + printf("error in ms_deformable_im2col_cuda: %s\n", cudaGetErrorString(err)); + } + +} + +template +void ms_deformable_col2im_cuda(cudaStream_t stream, + const scalar_t* grad_col, + const scalar_t* data_value, + const int64_t * data_spatial_shapes, + const int64_t * data_level_start_index, + const scalar_t * data_sampling_loc, + const scalar_t * data_attn_weight, + const int batch_size, + const int spatial_size, + const int num_heads, + const int channels, + const int num_levels, + const int num_query, + const int num_point, + scalar_t* grad_value, + scalar_t* grad_sampling_loc, + scalar_t* grad_attn_weight) +{ + const int num_threads = (channels > CUDA_NUM_THREADS)?CUDA_NUM_THREADS:channels; + const int num_kernels = batch_size * num_query * num_heads * channels; + const int num_actual_kernels = batch_size * num_query * num_heads * channels; + if (channels > 1024) + { + if ((channels & 1023) == 0) + { + ms_deformable_col2im_gpu_kernel_shm_reduce_v2_multi_blocks + <<>>( + num_kernels, + grad_col, + data_value, + data_spatial_shapes, + data_level_start_index, + data_sampling_loc, + data_attn_weight, + batch_size, + spatial_size, + num_heads, + channels, + num_levels, + num_query, + num_point, + grad_value, + grad_sampling_loc, + grad_attn_weight); + } + else + { + ms_deformable_col2im_gpu_kernel_gm + <<>>( + num_kernels, + grad_col, + data_value, + data_spatial_shapes, + data_level_start_index, + data_sampling_loc, + data_attn_weight, + batch_size, + spatial_size, + num_heads, + channels, + num_levels, + num_query, + num_point, + grad_value, + grad_sampling_loc, + grad_attn_weight); + } + } + else{ + switch(channels) + { + case 1: + ms_deformable_col2im_gpu_kernel_shm_blocksize_aware_reduce_v1 + <<>>( + num_kernels, + grad_col, + data_value, + data_spatial_shapes, + data_level_start_index, + data_sampling_loc, + data_attn_weight, + batch_size, + spatial_size, + num_heads, + channels, + num_levels, + num_query, + num_point, + grad_value, + grad_sampling_loc, + grad_attn_weight); + break; + case 2: + ms_deformable_col2im_gpu_kernel_shm_blocksize_aware_reduce_v1 + <<>>( + num_kernels, + grad_col, + data_value, + data_spatial_shapes, + data_level_start_index, + data_sampling_loc, + data_attn_weight, + batch_size, + spatial_size, + num_heads, + channels, + num_levels, + num_query, + num_point, + grad_value, + grad_sampling_loc, + grad_attn_weight); + break; + case 4: + ms_deformable_col2im_gpu_kernel_shm_blocksize_aware_reduce_v1 + <<>>( + num_kernels, + grad_col, + data_value, + data_spatial_shapes, + data_level_start_index, + data_sampling_loc, + data_attn_weight, + batch_size, + spatial_size, + num_heads, + channels, + num_levels, + num_query, + num_point, + grad_value, + grad_sampling_loc, + grad_attn_weight); + break; + case 8: + ms_deformable_col2im_gpu_kernel_shm_blocksize_aware_reduce_v1 + <<>>( + num_kernels, + grad_col, + data_value, + data_spatial_shapes, + data_level_start_index, + data_sampling_loc, + data_attn_weight, + batch_size, + spatial_size, + num_heads, + channels, + num_levels, + num_query, + num_point, + grad_value, + grad_sampling_loc, + grad_attn_weight); + break; + case 16: + ms_deformable_col2im_gpu_kernel_shm_blocksize_aware_reduce_v1 + <<>>( + num_kernels, + grad_col, + data_value, + data_spatial_shapes, + data_level_start_index, + data_sampling_loc, + data_attn_weight, + batch_size, + spatial_size, + num_heads, + channels, + num_levels, + num_query, + num_point, + grad_value, + grad_sampling_loc, + grad_attn_weight); + break; + case 32: + ms_deformable_col2im_gpu_kernel_shm_blocksize_aware_reduce_v1 + <<>>( + num_kernels, + grad_col, + data_value, + data_spatial_shapes, + data_level_start_index, + data_sampling_loc, + data_attn_weight, + batch_size, + spatial_size, + num_heads, + channels, + num_levels, + num_query, + num_point, + grad_value, + grad_sampling_loc, + grad_attn_weight); + break; + case 64: + ms_deformable_col2im_gpu_kernel_shm_blocksize_aware_reduce_v2 + <<>>( + num_kernels, + grad_col, + data_value, + data_spatial_shapes, + data_level_start_index, + data_sampling_loc, + data_attn_weight, + batch_size, + spatial_size, + num_heads, + channels, + num_levels, + num_query, + num_point, + grad_value, + grad_sampling_loc, + grad_attn_weight); + break; + case 128: + ms_deformable_col2im_gpu_kernel_shm_blocksize_aware_reduce_v2 + <<>>( + num_kernels, + grad_col, + data_value, + data_spatial_shapes, + data_level_start_index, + data_sampling_loc, + data_attn_weight, + batch_size, + spatial_size, + num_heads, + channels, + num_levels, + num_query, + num_point, + grad_value, + grad_sampling_loc, + grad_attn_weight); + break; + case 256: + ms_deformable_col2im_gpu_kernel_shm_blocksize_aware_reduce_v2 + <<>>( + num_kernels, + grad_col, + data_value, + data_spatial_shapes, + data_level_start_index, + data_sampling_loc, + data_attn_weight, + batch_size, + spatial_size, + num_heads, + channels, + num_levels, + num_query, + num_point, + grad_value, + grad_sampling_loc, + grad_attn_weight); + break; + case 512: + ms_deformable_col2im_gpu_kernel_shm_blocksize_aware_reduce_v2 + <<>>( + num_kernels, + grad_col, + data_value, + data_spatial_shapes, + data_level_start_index, + data_sampling_loc, + data_attn_weight, + batch_size, + spatial_size, + num_heads, + channels, + num_levels, + num_query, + num_point, + grad_value, + grad_sampling_loc, + grad_attn_weight); + break; + case 1024: + ms_deformable_col2im_gpu_kernel_shm_blocksize_aware_reduce_v2 + <<>>( + num_kernels, + grad_col, + data_value, + data_spatial_shapes, + data_level_start_index, + data_sampling_loc, + data_attn_weight, + batch_size, + spatial_size, + num_heads, + channels, + num_levels, + num_query, + num_point, + grad_value, + grad_sampling_loc, + grad_attn_weight); + break; + default: + if (channels < 64) + { + ms_deformable_col2im_gpu_kernel_shm_reduce_v1 + <<>>( + num_kernels, + grad_col, + data_value, + data_spatial_shapes, + data_level_start_index, + data_sampling_loc, + data_attn_weight, + batch_size, + spatial_size, + num_heads, + channels, + num_levels, + num_query, + num_point, + grad_value, + grad_sampling_loc, + grad_attn_weight); + } + else + { + ms_deformable_col2im_gpu_kernel_shm_reduce_v2 + <<>>( + num_kernels, + grad_col, + data_value, + data_spatial_shapes, + data_level_start_index, + data_sampling_loc, + data_attn_weight, + batch_size, + spatial_size, + num_heads, + channels, + num_levels, + num_query, + num_point, + grad_value, + grad_sampling_loc, + grad_attn_weight); + } + } + } + cudaError_t err = cudaGetLastError(); + if (err != cudaSuccess) + { + printf("error in ms_deformable_col2im_cuda: %s\n", cudaGetErrorString(err)); + } + +} \ No newline at end of file diff --git a/groundingdino/models/GroundingDINO/csrc/cuda_version.cu b/groundingdino/models/GroundingDINO/csrc/cuda_version.cu new file mode 100644 index 0000000..64569e3 --- /dev/null +++ b/groundingdino/models/GroundingDINO/csrc/cuda_version.cu @@ -0,0 +1,7 @@ +#include + +namespace groundingdino { +int get_cudart_version() { + return CUDART_VERSION; +} +} // namespace groundingdino diff --git a/groundingdino/models/GroundingDINO/csrc/vision.cpp b/groundingdino/models/GroundingDINO/csrc/vision.cpp new file mode 100644 index 0000000..c1f2c50 --- /dev/null +++ b/groundingdino/models/GroundingDINO/csrc/vision.cpp @@ -0,0 +1,58 @@ +// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved + +#include "MsDeformAttn/ms_deform_attn.h" + +namespace groundingdino { + +#ifdef WITH_CUDA +extern int get_cudart_version(); +#endif + +std::string get_cuda_version() { +#ifdef WITH_CUDA + std::ostringstream oss; + + // copied from + // https://github.com/pytorch/pytorch/blob/master/aten/src/ATen/cuda/detail/CUDAHooks.cpp#L231 + auto printCudaStyleVersion = [&](int v) { + oss << (v / 1000) << "." << (v / 10 % 100); + if (v % 10 != 0) { + oss << "." << (v % 10); + } + }; + printCudaStyleVersion(get_cudart_version()); + return oss.str(); +#else + return std::string("not available"); +#endif +} + +// similar to +// https://github.com/pytorch/pytorch/blob/master/aten/src/ATen/Version.cpp +std::string get_compiler_version() { + std::ostringstream ss; +#if defined(__GNUC__) +#ifndef __clang__ + { ss << "GCC " << __GNUC__ << "." << __GNUC_MINOR__; } +#endif +#endif + +#if defined(__clang_major__) + { + ss << "clang " << __clang_major__ << "." << __clang_minor__ << "." + << __clang_patchlevel__; + } +#endif + +#if defined(_MSC_VER) + { ss << "MSVC " << _MSC_FULL_VER; } +#endif + return ss.str(); +} + +PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { + m.def("ms_deform_attn_forward", &ms_deform_attn_forward, "ms_deform_attn_forward"); + m.def("ms_deform_attn_backward", &ms_deform_attn_backward, "ms_deform_attn_backward"); +} + +} // namespace groundingdino \ No newline at end of file diff --git a/groundingdino/models/GroundingDINO/fuse_modules.py b/groundingdino/models/GroundingDINO/fuse_modules.py new file mode 100644 index 0000000..67aa5ac --- /dev/null +++ b/groundingdino/models/GroundingDINO/fuse_modules.py @@ -0,0 +1,290 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from timm.models.layers import DropPath + + +class FeatureResizer(nn.Module): + """ + This class takes as input a set of embeddings of dimension C1 and outputs a set of + embedding of dimension C2, after a linear transformation, dropout and normalization (LN). + """ + + def __init__(self, input_feat_size, output_feat_size, dropout, do_ln=True): + super().__init__() + self.do_ln = do_ln + # Object feature encoding + self.fc = nn.Linear(input_feat_size, output_feat_size, bias=True) + self.layer_norm = nn.LayerNorm(output_feat_size, eps=1e-12) + self.dropout = nn.Dropout(dropout) + + def forward(self, encoder_features): + x = self.fc(encoder_features) + if self.do_ln: + x = self.layer_norm(x) + output = self.dropout(x) + return output + + +def l1norm(X, dim, eps=1e-8): + """L1-normalize columns of X""" + norm = torch.abs(X).sum(dim=dim, keepdim=True) + eps + X = torch.div(X, norm) + return X + + +def l2norm(X, dim, eps=1e-8): + """L2-normalize columns of X""" + norm = torch.pow(X, 2).sum(dim=dim, keepdim=True).sqrt() + eps + X = torch.div(X, norm) + return X + + +def func_attention(query, context, smooth=1, raw_feature_norm="softmax", eps=1e-8): + """ + query: (n_context, queryL, d) + context: (n_context, sourceL, d) + """ + batch_size_q, queryL = query.size(0), query.size(1) + batch_size, sourceL = context.size(0), context.size(1) + + # Get attention + # --> (batch, d, queryL) + queryT = torch.transpose(query, 1, 2) + + # (batch, sourceL, d)(batch, d, queryL) + # --> (batch, sourceL, queryL) + attn = torch.bmm(context, queryT) + if raw_feature_norm == "softmax": + # --> (batch*sourceL, queryL) + attn = attn.view(batch_size * sourceL, queryL) + attn = nn.Softmax()(attn) + # --> (batch, sourceL, queryL) + attn = attn.view(batch_size, sourceL, queryL) + elif raw_feature_norm == "l2norm": + attn = l2norm(attn, 2) + elif raw_feature_norm == "clipped_l2norm": + attn = nn.LeakyReLU(0.1)(attn) + attn = l2norm(attn, 2) + else: + raise ValueError("unknown first norm type:", raw_feature_norm) + # --> (batch, queryL, sourceL) + attn = torch.transpose(attn, 1, 2).contiguous() + # --> (batch*queryL, sourceL) + attn = attn.view(batch_size * queryL, sourceL) + attn = nn.Softmax()(attn * smooth) + # --> (batch, queryL, sourceL) + attn = attn.view(batch_size, queryL, sourceL) + # --> (batch, sourceL, queryL) + attnT = torch.transpose(attn, 1, 2).contiguous() + + # --> (batch, d, sourceL) + contextT = torch.transpose(context, 1, 2) + # (batch x d x sourceL)(batch x sourceL x queryL) + # --> (batch, d, queryL) + weightedContext = torch.bmm(contextT, attnT) + # --> (batch, queryL, d) + weightedContext = torch.transpose(weightedContext, 1, 2) + + return weightedContext, attnT + + +class BiMultiHeadAttention(nn.Module): + def __init__(self, v_dim, l_dim, embed_dim, num_heads, dropout=0.1, cfg=None): + super(BiMultiHeadAttention, self).__init__() + + self.embed_dim = embed_dim + self.num_heads = num_heads + self.head_dim = embed_dim // num_heads + self.v_dim = v_dim + self.l_dim = l_dim + + assert ( + self.head_dim * self.num_heads == self.embed_dim + ), f"embed_dim must be divisible by num_heads (got `embed_dim`: {self.embed_dim} and `num_heads`: {self.num_heads})." + self.scale = self.head_dim ** (-0.5) + self.dropout = dropout + + self.v_proj = nn.Linear(self.v_dim, self.embed_dim) + self.l_proj = nn.Linear(self.l_dim, self.embed_dim) + self.values_v_proj = nn.Linear(self.v_dim, self.embed_dim) + self.values_l_proj = nn.Linear(self.l_dim, self.embed_dim) + + self.out_v_proj = nn.Linear(self.embed_dim, self.v_dim) + self.out_l_proj = nn.Linear(self.embed_dim, self.l_dim) + + self.stable_softmax_2d = True + self.clamp_min_for_underflow = True + self.clamp_max_for_overflow = True + + self._reset_parameters() + + def _shape(self, tensor: torch.Tensor, seq_len: int, bsz: int): + return tensor.view(bsz, seq_len, self.num_heads, self.head_dim).transpose(1, 2).contiguous() + + def _reset_parameters(self): + nn.init.xavier_uniform_(self.v_proj.weight) + self.v_proj.bias.data.fill_(0) + nn.init.xavier_uniform_(self.l_proj.weight) + self.l_proj.bias.data.fill_(0) + nn.init.xavier_uniform_(self.values_v_proj.weight) + self.values_v_proj.bias.data.fill_(0) + nn.init.xavier_uniform_(self.values_l_proj.weight) + self.values_l_proj.bias.data.fill_(0) + nn.init.xavier_uniform_(self.out_v_proj.weight) + self.out_v_proj.bias.data.fill_(0) + nn.init.xavier_uniform_(self.out_l_proj.weight) + self.out_l_proj.bias.data.fill_(0) + + def forward(self, v, l, attention_mask_v=None, attention_mask_l=None): + """_summary_ + + Args: + v (_type_): bs, n_img, dim + l (_type_): bs, n_text, dim + attention_mask_v (_type_, optional): _description_. bs, n_img + attention_mask_l (_type_, optional): _description_. bs, n_text + + Returns: + _type_: _description_ + """ + # if os.environ.get('IPDB_SHILONG_DEBUG', None) == 'INFO': + # import ipdb; ipdb.set_trace() + bsz, tgt_len, _ = v.size() + + query_states = self.v_proj(v) * self.scale + key_states = self._shape(self.l_proj(l), -1, bsz) + value_v_states = self._shape(self.values_v_proj(v), -1, bsz) + value_l_states = self._shape(self.values_l_proj(l), -1, bsz) + + proj_shape = (bsz * self.num_heads, -1, self.head_dim) + query_states = self._shape(query_states, tgt_len, bsz).view(*proj_shape) + key_states = key_states.view(*proj_shape) + value_v_states = value_v_states.view(*proj_shape) + value_l_states = value_l_states.view(*proj_shape) + + src_len = key_states.size(1) + attn_weights = torch.bmm(query_states, key_states.transpose(1, 2)) # bs*nhead, nimg, ntxt + + if attn_weights.size() != (bsz * self.num_heads, tgt_len, src_len): + raise ValueError( + f"Attention weights should be of size {(bsz * self.num_heads, tgt_len, src_len)}, but is {attn_weights.size()}" + ) + + if self.stable_softmax_2d: + attn_weights = attn_weights - attn_weights.max() + + if self.clamp_min_for_underflow: + attn_weights = torch.clamp( + attn_weights, min=-50000 + ) # Do not increase -50000, data type half has quite limited range + if self.clamp_max_for_overflow: + attn_weights = torch.clamp( + attn_weights, max=50000 + ) # Do not increase 50000, data type half has quite limited range + + attn_weights_T = attn_weights.transpose(1, 2) + attn_weights_l = attn_weights_T - torch.max(attn_weights_T, dim=-1, keepdim=True)[0] + if self.clamp_min_for_underflow: + attn_weights_l = torch.clamp( + attn_weights_l, min=-50000 + ) # Do not increase -50000, data type half has quite limited range + if self.clamp_max_for_overflow: + attn_weights_l = torch.clamp( + attn_weights_l, max=50000 + ) # Do not increase 50000, data type half has quite limited range + + # mask vison for language + if attention_mask_v is not None: + attention_mask_v = ( + attention_mask_v[:, None, None, :].repeat(1, self.num_heads, 1, 1).flatten(0, 1) + ) + attn_weights_l.masked_fill_(attention_mask_v, float("-inf")) + + attn_weights_l = attn_weights_l.softmax(dim=-1) + + # mask language for vision + if attention_mask_l is not None: + attention_mask_l = ( + attention_mask_l[:, None, None, :].repeat(1, self.num_heads, 1, 1).flatten(0, 1) + ) + attn_weights.masked_fill_(attention_mask_l, float("-inf")) + attn_weights_v = attn_weights.softmax(dim=-1) + + attn_probs_v = F.dropout(attn_weights_v, p=self.dropout, training=self.training) + attn_probs_l = F.dropout(attn_weights_l, p=self.dropout, training=self.training) + + attn_output_v = torch.bmm(attn_probs_v, value_l_states) + attn_output_l = torch.bmm(attn_probs_l, value_v_states) + + if attn_output_v.size() != (bsz * self.num_heads, tgt_len, self.head_dim): + raise ValueError( + f"`attn_output_v` should be of size {(bsz, self.num_heads, tgt_len, self.head_dim)}, but is {attn_output_v.size()}" + ) + + if attn_output_l.size() != (bsz * self.num_heads, src_len, self.head_dim): + raise ValueError( + f"`attn_output_l` should be of size {(bsz, self.num_heads, src_len, self.head_dim)}, but is {attn_output_l.size()}" + ) + + attn_output_v = attn_output_v.view(bsz, self.num_heads, tgt_len, self.head_dim) + attn_output_v = attn_output_v.transpose(1, 2) + attn_output_v = attn_output_v.reshape(bsz, tgt_len, self.embed_dim) + + attn_output_l = attn_output_l.view(bsz, self.num_heads, src_len, self.head_dim) + attn_output_l = attn_output_l.transpose(1, 2) + attn_output_l = attn_output_l.reshape(bsz, src_len, self.embed_dim) + + attn_output_v = self.out_v_proj(attn_output_v) + attn_output_l = self.out_l_proj(attn_output_l) + + return attn_output_v, attn_output_l + + +# Bi-Direction MHA (text->image, image->text) +class BiAttentionBlock(nn.Module): + def __init__( + self, + v_dim, + l_dim, + embed_dim, + num_heads, + dropout=0.1, + drop_path=0.0, + init_values=1e-4, + cfg=None, + ): + """ + Inputs: + embed_dim - Dimensionality of input and attention feature vectors + hidden_dim - Dimensionality of hidden layer in feed-forward network + (usually 2-4x larger than embed_dim) + num_heads - Number of heads to use in the Multi-Head Attention block + dropout - Amount of dropout to apply in the feed-forward network + """ + super(BiAttentionBlock, self).__init__() + + # pre layer norm + self.layer_norm_v = nn.LayerNorm(v_dim) + self.layer_norm_l = nn.LayerNorm(l_dim) + self.attn = BiMultiHeadAttention( + v_dim=v_dim, l_dim=l_dim, embed_dim=embed_dim, num_heads=num_heads, dropout=dropout + ) + + # add layer scale for training stability + self.drop_path = DropPath(drop_path) if drop_path > 0.0 else nn.Identity() + self.gamma_v = nn.Parameter(init_values * torch.ones((v_dim)), requires_grad=True) + self.gamma_l = nn.Parameter(init_values * torch.ones((l_dim)), requires_grad=True) + + def forward(self, v, l, attention_mask_v=None, attention_mask_l=None): + v = self.layer_norm_v(v) + l = self.layer_norm_l(l) + delta_v, delta_l = self.attn( + v, l, attention_mask_v=attention_mask_v, attention_mask_l=attention_mask_l + ) + # v, l = v + delta_v, l + delta_l + v = v + self.drop_path(self.gamma_v * delta_v) + l = l + self.drop_path(self.gamma_l * delta_l) + return v, l + + # def forward(self, v:List[torch.Tensor], l, attention_mask_v=None, attention_mask_l=None) diff --git a/groundingdino/models/GroundingDINO/groundingdino.py b/groundingdino/models/GroundingDINO/groundingdino.py new file mode 100644 index 0000000..29385cf --- /dev/null +++ b/groundingdino/models/GroundingDINO/groundingdino.py @@ -0,0 +1,394 @@ +# ------------------------------------------------------------------------ +# DINO +# Copyright (c) 2022 IDEA. All Rights Reserved. +# Licensed under the Apache License, Version 2.0 [see LICENSE for details] +# ------------------------------------------------------------------------ +# Conditional DETR model and criterion classes. +# Copyright (c) 2021 Microsoft. All Rights Reserved. +# Licensed under the Apache License, Version 2.0 [see LICENSE for details] +# ------------------------------------------------------------------------ +# Modified from DETR (https://github.com/facebookresearch/detr) +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +# ------------------------------------------------------------------------ +# Modified from Deformable DETR (https://github.com/fundamentalvision/Deformable-DETR) +# Copyright (c) 2020 SenseTime. All Rights Reserved. +# ------------------------------------------------------------------------ +import copy +from typing import List + +import torch +import torch.nn.functional as F +from torch import nn +from torchvision.ops.boxes import nms +from transformers import AutoTokenizer, BertModel, BertTokenizer, RobertaModel, RobertaTokenizerFast + +from groundingdino.util import box_ops, get_tokenlizer +from groundingdino.util.misc import ( + NestedTensor, + accuracy, + get_world_size, + interpolate, + inverse_sigmoid, + is_dist_avail_and_initialized, + nested_tensor_from_tensor_list, +) +from groundingdino.util.utils import get_phrases_from_posmap +from groundingdino.util.visualizer import COCOVisualizer +from groundingdino.util.vl_utils import create_positive_map_from_span + +from ..registry import MODULE_BUILD_FUNCS +from .backbone import build_backbone +from .bertwarper import ( + BertModelWarper, + generate_masks_with_special_tokens, + generate_masks_with_special_tokens_and_transfer_map, +) +from .transformer import build_transformer +from .utils import MLP, ContrastiveEmbed, sigmoid_focal_loss + + +class GroundingDINO(nn.Module): + """This is the Cross-Attention Detector module that performs object detection""" + + def __init__( + self, + backbone, + transformer, + num_queries, + aux_loss=False, + iter_update=False, + query_dim=2, + num_feature_levels=1, + nheads=8, + # two stage + two_stage_type="no", # ['no', 'standard'] + dec_pred_bbox_embed_share=True, + two_stage_class_embed_share=True, + two_stage_bbox_embed_share=True, + num_patterns=0, + dn_number=100, + dn_box_noise_scale=0.4, + dn_label_noise_ratio=0.5, + dn_labelbook_size=100, + text_encoder_type="bert-base-uncased", + sub_sentence_present=True, + max_text_len=256, + ): + """Initializes the model. + Parameters: + backbone: torch module of the backbone to be used. See backbone.py + transformer: torch module of the transformer architecture. See transformer.py + num_queries: number of object queries, ie detection slot. This is the maximal number of objects + Conditional DETR can detect in a single image. For COCO, we recommend 100 queries. + aux_loss: True if auxiliary decoding losses (loss at each decoder layer) are to be used. + """ + super().__init__() + self.num_queries = num_queries + self.transformer = transformer + self.hidden_dim = hidden_dim = transformer.d_model + self.num_feature_levels = num_feature_levels + self.nheads = nheads + self.max_text_len = 256 + self.sub_sentence_present = sub_sentence_present + + # setting query dim + self.query_dim = query_dim + assert query_dim == 4 + + # for dn training + self.num_patterns = num_patterns + self.dn_number = dn_number + self.dn_box_noise_scale = dn_box_noise_scale + self.dn_label_noise_ratio = dn_label_noise_ratio + self.dn_labelbook_size = dn_labelbook_size + + # bert + self.tokenizer = get_tokenlizer.get_tokenlizer(text_encoder_type) + self.bert = get_tokenlizer.get_pretrained_language_model(text_encoder_type) + self.bert.pooler.dense.weight.requires_grad_(False) + self.bert.pooler.dense.bias.requires_grad_(False) + self.bert = BertModelWarper(bert_model=self.bert) + + self.feat_map = nn.Linear(self.bert.config.hidden_size, self.hidden_dim, bias=True) + nn.init.constant_(self.feat_map.bias.data, 0) + nn.init.xavier_uniform_(self.feat_map.weight.data) + # freeze + + # special tokens + self.specical_tokens = self.tokenizer.convert_tokens_to_ids(["[CLS]", "[SEP]", ".", "?"]) + + # prepare input projection layers + if num_feature_levels > 1: + num_backbone_outs = len(backbone.num_channels) + input_proj_list = [] + for _ in range(num_backbone_outs): + in_channels = backbone.num_channels[_] + input_proj_list.append( + nn.Sequential( + nn.Conv2d(in_channels, hidden_dim, kernel_size=1), + nn.GroupNorm(32, hidden_dim), + ) + ) + for _ in range(num_feature_levels - num_backbone_outs): + input_proj_list.append( + nn.Sequential( + nn.Conv2d(in_channels, hidden_dim, kernel_size=3, stride=2, padding=1), + nn.GroupNorm(32, hidden_dim), + ) + ) + in_channels = hidden_dim + self.input_proj = nn.ModuleList(input_proj_list) + else: + assert two_stage_type == "no", "two_stage_type should be no if num_feature_levels=1 !!!" + self.input_proj = nn.ModuleList( + [ + nn.Sequential( + nn.Conv2d(backbone.num_channels[-1], hidden_dim, kernel_size=1), + nn.GroupNorm(32, hidden_dim), + ) + ] + ) + + self.backbone = backbone + self.aux_loss = aux_loss + self.box_pred_damping = box_pred_damping = None + + self.iter_update = iter_update + assert iter_update, "Why not iter_update?" + + # prepare pred layers + self.dec_pred_bbox_embed_share = dec_pred_bbox_embed_share + # prepare class & box embed + _class_embed = ContrastiveEmbed() + + _bbox_embed = MLP(hidden_dim, hidden_dim, 4, 3) + nn.init.constant_(_bbox_embed.layers[-1].weight.data, 0) + nn.init.constant_(_bbox_embed.layers[-1].bias.data, 0) + + if dec_pred_bbox_embed_share: + box_embed_layerlist = [_bbox_embed for i in range(transformer.num_decoder_layers)] + else: + box_embed_layerlist = [ + copy.deepcopy(_bbox_embed) for i in range(transformer.num_decoder_layers) + ] + class_embed_layerlist = [_class_embed for i in range(transformer.num_decoder_layers)] + self.bbox_embed = nn.ModuleList(box_embed_layerlist) + self.class_embed = nn.ModuleList(class_embed_layerlist) + self.transformer.decoder.bbox_embed = self.bbox_embed + self.transformer.decoder.class_embed = self.class_embed + + # two stage + self.two_stage_type = two_stage_type + assert two_stage_type in ["no", "standard"], "unknown param {} of two_stage_type".format( + two_stage_type + ) + if two_stage_type != "no": + if two_stage_bbox_embed_share: + assert dec_pred_bbox_embed_share + self.transformer.enc_out_bbox_embed = _bbox_embed + else: + self.transformer.enc_out_bbox_embed = copy.deepcopy(_bbox_embed) + + if two_stage_class_embed_share: + assert dec_pred_bbox_embed_share + self.transformer.enc_out_class_embed = _class_embed + else: + self.transformer.enc_out_class_embed = copy.deepcopy(_class_embed) + + self.refpoint_embed = None + + self._reset_parameters() + + def _reset_parameters(self): + # init input_proj + for proj in self.input_proj: + nn.init.xavier_uniform_(proj[0].weight, gain=1) + nn.init.constant_(proj[0].bias, 0) + + def init_ref_points(self, use_num_queries): + self.refpoint_embed = nn.Embedding(use_num_queries, self.query_dim) + + def forward(self, samples: NestedTensor, targets: List = None, **kw): + """The forward expects a NestedTensor, which consists of: + - samples.tensor: batched images, of shape [batch_size x 3 x H x W] + - samples.mask: a binary mask of shape [batch_size x H x W], containing 1 on padded pixels + + It returns a dict with the following elements: + - "pred_logits": the classification logits (including no-object) for all queries. + Shape= [batch_size x num_queries x num_classes] + - "pred_boxes": The normalized boxes coordinates for all queries, represented as + (center_x, center_y, width, height). These values are normalized in [0, 1], + relative to the size of each individual image (disregarding possible padding). + See PostProcess for information on how to retrieve the unnormalized bounding box. + - "aux_outputs": Optional, only returned when auxilary losses are activated. It is a list of + dictionnaries containing the two above keys for each decoder layer. + """ + if targets is None: + captions = kw["captions"] + else: + captions = [t["caption"] for t in targets] + len(captions) + + # encoder texts + tokenized = self.tokenizer(captions, padding="longest", return_tensors="pt").to( + samples.device + ) + ( + text_self_attention_masks, + position_ids, + cate_to_token_mask_list, + ) = generate_masks_with_special_tokens_and_transfer_map( + tokenized, self.specical_tokens, self.tokenizer + ) + + if text_self_attention_masks.shape[1] > self.max_text_len: + text_self_attention_masks = text_self_attention_masks[ + :, : self.max_text_len, : self.max_text_len + ] + position_ids = position_ids[:, : self.max_text_len] + tokenized["input_ids"] = tokenized["input_ids"][:, : self.max_text_len] + tokenized["attention_mask"] = tokenized["attention_mask"][:, : self.max_text_len] + tokenized["token_type_ids"] = tokenized["token_type_ids"][:, : self.max_text_len] + + # extract text embeddings + if self.sub_sentence_present: + tokenized_for_encoder = {k: v for k, v in tokenized.items() if k != "attention_mask"} + tokenized_for_encoder["attention_mask"] = text_self_attention_masks + tokenized_for_encoder["position_ids"] = position_ids + else: + # import ipdb; ipdb.set_trace() + tokenized_for_encoder = tokenized + + bert_output = self.bert(**tokenized_for_encoder) # bs, 195, 768 + + encoded_text = self.feat_map(bert_output["last_hidden_state"]) # bs, 195, d_model + text_token_mask = tokenized.attention_mask.bool() # bs, 195 + # text_token_mask: True for nomask, False for mask + # text_self_attention_masks: True for nomask, False for mask + + if encoded_text.shape[1] > self.max_text_len: + encoded_text = encoded_text[:, : self.max_text_len, :] + text_token_mask = text_token_mask[:, : self.max_text_len] + position_ids = position_ids[:, : self.max_text_len] + text_self_attention_masks = text_self_attention_masks[ + :, : self.max_text_len, : self.max_text_len + ] + + text_dict = { + "encoded_text": encoded_text, # bs, 195, d_model + "text_token_mask": text_token_mask, # bs, 195 + "position_ids": position_ids, # bs, 195 + "text_self_attention_masks": text_self_attention_masks, # bs, 195,195 + } + + # import ipdb; ipdb.set_trace() + + if isinstance(samples, (list, torch.Tensor)): + samples = nested_tensor_from_tensor_list(samples) + features, poss = self.backbone(samples) + + srcs = [] + masks = [] + for l, feat in enumerate(features): + src, mask = feat.decompose() + srcs.append(self.input_proj[l](src)) + masks.append(mask) + assert mask is not None + if self.num_feature_levels > len(srcs): + _len_srcs = len(srcs) + for l in range(_len_srcs, self.num_feature_levels): + if l == _len_srcs: + src = self.input_proj[l](features[-1].tensors) + else: + src = self.input_proj[l](srcs[-1]) + m = samples.mask + mask = F.interpolate(m[None].float(), size=src.shape[-2:]).to(torch.bool)[0] + pos_l = self.backbone[1](NestedTensor(src, mask)).to(src.dtype) + srcs.append(src) + masks.append(mask) + poss.append(pos_l) + + input_query_bbox = input_query_label = attn_mask = dn_meta = None + hs, reference, hs_enc, ref_enc, init_box_proposal = self.transformer( + srcs, masks, input_query_bbox, poss, input_query_label, attn_mask, text_dict + ) + + # deformable-detr-like anchor update + outputs_coord_list = [] + for dec_lid, (layer_ref_sig, layer_bbox_embed, layer_hs) in enumerate( + zip(reference[:-1], self.bbox_embed, hs) + ): + layer_delta_unsig = layer_bbox_embed(layer_hs) + layer_outputs_unsig = layer_delta_unsig + inverse_sigmoid(layer_ref_sig) + layer_outputs_unsig = layer_outputs_unsig.sigmoid() + outputs_coord_list.append(layer_outputs_unsig) + outputs_coord_list = torch.stack(outputs_coord_list) + + # output + outputs_class = torch.stack( + [ + layer_cls_embed(layer_hs, text_dict) + for layer_cls_embed, layer_hs in zip(self.class_embed, hs) + ] + ) + out = {"pred_logits": outputs_class[-1], "pred_boxes": outputs_coord_list[-1]} + + # # for intermediate outputs + # if self.aux_loss: + # out['aux_outputs'] = self._set_aux_loss(outputs_class, outputs_coord_list) + + # # for encoder output + # if hs_enc is not None: + # # prepare intermediate outputs + # interm_coord = ref_enc[-1] + # interm_class = self.transformer.enc_out_class_embed(hs_enc[-1], text_dict) + # out['interm_outputs'] = {'pred_logits': interm_class, 'pred_boxes': interm_coord} + # out['interm_outputs_for_matching_pre'] = {'pred_logits': interm_class, 'pred_boxes': init_box_proposal} + + return out + + @torch.jit.unused + def _set_aux_loss(self, outputs_class, outputs_coord): + # this is a workaround to make torchscript happy, as torchscript + # doesn't support dictionary with non-homogeneous values, such + # as a dict having both a Tensor and a list. + return [ + {"pred_logits": a, "pred_boxes": b} + for a, b in zip(outputs_class[:-1], outputs_coord[:-1]) + ] + + +@MODULE_BUILD_FUNCS.registe_with_name(module_name="groundingdino") +def build_groundingdino(args): + + backbone = build_backbone(args) + transformer = build_transformer(args) + + dn_labelbook_size = args.dn_labelbook_size + dec_pred_bbox_embed_share = args.dec_pred_bbox_embed_share + sub_sentence_present = args.sub_sentence_present + + model = GroundingDINO( + backbone, + transformer, + num_queries=args.num_queries, + aux_loss=True, + iter_update=True, + query_dim=4, + num_feature_levels=args.num_feature_levels, + nheads=args.nheads, + dec_pred_bbox_embed_share=dec_pred_bbox_embed_share, + two_stage_type=args.two_stage_type, + two_stage_bbox_embed_share=args.two_stage_bbox_embed_share, + two_stage_class_embed_share=args.two_stage_class_embed_share, + num_patterns=args.num_patterns, + dn_number=0, + dn_box_noise_scale=args.dn_box_noise_scale, + dn_label_noise_ratio=args.dn_label_noise_ratio, + dn_labelbook_size=dn_labelbook_size, + text_encoder_type=args.text_encoder_type, + sub_sentence_present=sub_sentence_present, + max_text_len=args.max_text_len, + ) + + return model diff --git a/groundingdino/models/GroundingDINO/ms_deform_attn.py b/groundingdino/models/GroundingDINO/ms_deform_attn.py new file mode 100644 index 0000000..938bfaa --- /dev/null +++ b/groundingdino/models/GroundingDINO/ms_deform_attn.py @@ -0,0 +1,419 @@ +# coding=utf-8 +# Copyright 2022 The IDEA Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ------------------------------------------------------------------------------------------------ +# Deformable DETR +# Copyright (c) 2020 SenseTime. All Rights Reserved. +# Licensed under the Apache License, Version 2.0 [see LICENSE for details] +# ------------------------------------------------------------------------------------------------ +# Modified from: +# https://github.com/fundamentalvision/Deformable-DETR/blob/main/models/ops/functions/ms_deform_attn_func.py +# https://github.com/fundamentalvision/Deformable-DETR/blob/main/models/ops/modules/ms_deform_attn.py +# https://github.com/open-mmlab/mmcv/blob/master/mmcv/ops/multi_scale_deform_attn.py +# ------------------------------------------------------------------------------------------------ + +import math +import warnings +from typing import Optional +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.autograd import Function +from torch.autograd.function import once_differentiable +from torch.nn.init import constant_, xavier_uniform_ +from groundingdino import _C + + +# helpers +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 MultiScaleDeformableAttnFunction(Function): + @staticmethod + def forward( + ctx, + value, + value_spatial_shapes, + value_level_start_index, + sampling_locations, + attention_weights, + im2col_step, + ): + ctx.im2col_step = im2col_step + output = _C.ms_deform_attn_forward( + value, + value_spatial_shapes, + value_level_start_index, + sampling_locations, + attention_weights, + ctx.im2col_step, + ) + ctx.save_for_backward( + value, + value_spatial_shapes, + value_level_start_index, + sampling_locations, + attention_weights, + ) + return output + + @staticmethod + @once_differentiable + def backward(ctx, grad_output): + ( + value, + value_spatial_shapes, + value_level_start_index, + sampling_locations, + attention_weights, + ) = ctx.saved_tensors + grad_value, grad_sampling_loc, grad_attn_weight = _C.ms_deform_attn_backward( + value, + value_spatial_shapes, + value_level_start_index, + sampling_locations, + attention_weights, + grad_output, + ctx.im2col_step, + ) + + return grad_value, None, None, grad_sampling_loc, grad_attn_weight, None + + +def multi_scale_deformable_attn_pytorch( + value: torch.Tensor, + value_spatial_shapes: torch.Tensor, + sampling_locations: torch.Tensor, + attention_weights: torch.Tensor, +) -> torch.Tensor: + + bs, _, num_heads, embed_dims = value.shape + _, num_queries, num_heads, num_levels, num_points, _ = sampling_locations.shape + value_list = value.split([H_ * W_ for H_, W_ in value_spatial_shapes], dim=1) + sampling_grids = 2 * sampling_locations - 1 + sampling_value_list = [] + for level, (H_, W_) in enumerate(value_spatial_shapes): + # bs, H_*W_, num_heads, embed_dims -> + # bs, H_*W_, num_heads*embed_dims -> + # bs, num_heads*embed_dims, H_*W_ -> + # bs*num_heads, embed_dims, H_, W_ + value_l_ = ( + value_list[level].flatten(2).transpose(1, 2).reshape(bs * num_heads, embed_dims, H_, W_) + ) + # bs, num_queries, num_heads, num_points, 2 -> + # bs, num_heads, num_queries, num_points, 2 -> + # bs*num_heads, num_queries, num_points, 2 + sampling_grid_l_ = sampling_grids[:, :, :, level].transpose(1, 2).flatten(0, 1) + # bs*num_heads, embed_dims, num_queries, num_points + sampling_value_l_ = F.grid_sample( + value_l_, sampling_grid_l_, mode="bilinear", padding_mode="zeros", align_corners=False + ) + sampling_value_list.append(sampling_value_l_) + # (bs, num_queries, num_heads, num_levels, num_points) -> + # (bs, num_heads, num_queries, num_levels, num_points) -> + # (bs, num_heads, 1, num_queries, num_levels*num_points) + attention_weights = attention_weights.transpose(1, 2).reshape( + bs * num_heads, 1, num_queries, num_levels * num_points + ) + output = ( + (torch.stack(sampling_value_list, dim=-2).flatten(-2) * attention_weights) + .sum(-1) + .view(bs, num_heads * embed_dims, num_queries) + ) + return output.transpose(1, 2).contiguous() + + +class MultiScaleDeformableAttention(nn.Module): + """Multi-Scale Deformable Attention Module used in Deformable-DETR + + `Deformable DETR: Deformable Transformers for End-to-End Object Detection. + `_. + + Args: + embed_dim (int): The embedding dimension of Attention. Default: 256. + num_heads (int): The number of attention heads. Default: 8. + num_levels (int): The number of feature map used in Attention. Default: 4. + num_points (int): The number of sampling points for each query + in each head. Default: 4. + img2col_steps (int): The step used in image_to_column. Defualt: 64. + dropout (float): Dropout layer used in output. Default: 0.1. + batch_first (bool): if ``True``, then the input and output tensor will be + provided as `(bs, n, embed_dim)`. Default: False. `(n, bs, embed_dim)` + """ + + def __init__( + self, + embed_dim: int = 256, + num_heads: int = 8, + num_levels: int = 4, + num_points: int = 4, + img2col_step: int = 64, + batch_first: bool = False, + ): + super().__init__() + if embed_dim % num_heads != 0: + raise ValueError( + "embed_dim must be divisible by num_heads, but got {} and {}".format( + embed_dim, num_heads + ) + ) + head_dim = embed_dim // num_heads + + self.batch_first = batch_first + + if not _is_power_of_2(head_dim): + warnings.warn( + """ + You'd better set d_model in MSDeformAttn to make sure that + each dim of the attention head a power of 2, which is more efficient. + """ + ) + + self.im2col_step = img2col_step + self.embed_dim = embed_dim + self.num_heads = num_heads + self.num_levels = num_levels + self.num_points = num_points + self.sampling_offsets = nn.Linear(embed_dim, num_heads * num_levels * num_points * 2) + self.attention_weights = nn.Linear(embed_dim, num_heads * num_levels * num_points) + self.value_proj = nn.Linear(embed_dim, embed_dim) + self.output_proj = nn.Linear(embed_dim, embed_dim) + + self.init_weights() + + def _reset_parameters(self): + return self.init_weights() + + def init_weights(self): + """ + Default initialization for Parameters of Module. + """ + constant_(self.sampling_offsets.weight.data, 0.0) + thetas = torch.arange(self.num_heads, dtype=torch.float32) * ( + 2.0 * math.pi / self.num_heads + ) + grid_init = torch.stack([thetas.cos(), thetas.sin()], -1) + grid_init = ( + (grid_init / grid_init.abs().max(-1, keepdim=True)[0]) + .view(self.num_heads, 1, 1, 2) + .repeat(1, self.num_levels, self.num_points, 1) + ) + for i in range(self.num_points): + grid_init[:, :, i, :] *= i + 1 + with torch.no_grad(): + self.sampling_offsets.bias = nn.Parameter(grid_init.view(-1)) + constant_(self.attention_weights.weight.data, 0.0) + constant_(self.attention_weights.bias.data, 0.0) + xavier_uniform_(self.value_proj.weight.data) + constant_(self.value_proj.bias.data, 0.0) + xavier_uniform_(self.output_proj.weight.data) + constant_(self.output_proj.bias.data, 0.0) + + def freeze_sampling_offsets(self): + print("Freeze sampling offsets") + self.sampling_offsets.weight.requires_grad = False + self.sampling_offsets.bias.requires_grad = False + + def freeze_attention_weights(self): + print("Freeze attention weights") + self.attention_weights.weight.requires_grad = False + self.attention_weights.bias.requires_grad = False + + def forward( + self, + query: torch.Tensor, + key: Optional[torch.Tensor] = None, + value: Optional[torch.Tensor] = None, + query_pos: Optional[torch.Tensor] = None, + key_padding_mask: Optional[torch.Tensor] = None, + reference_points: Optional[torch.Tensor] = None, + spatial_shapes: Optional[torch.Tensor] = None, + level_start_index: Optional[torch.Tensor] = None, + **kwargs + ) -> torch.Tensor: + + """Forward Function of MultiScaleDeformableAttention + + Args: + query (torch.Tensor): Query embeddings with shape + `(num_query, bs, embed_dim)` + key (torch.Tensor): Key embeddings with shape + `(num_key, bs, embed_dim)` + value (torch.Tensor): Value embeddings with shape + `(num_key, bs, embed_dim)` + query_pos (torch.Tensor): The position embedding for `query`. Default: None. + key_padding_mask (torch.Tensor): ByteTensor for `query`, with shape `(bs, num_key)`, + indicating which elements within `key` to be ignored in attention. + reference_points (torch.Tensor): The normalized reference points + with shape `(bs, num_query, num_levels, 2)`, + all elements is range in [0, 1], top-left (0, 0), + bottom-right (1, 1), including padding are. + or `(N, Length_{query}, num_levels, 4)`, add additional + two dimensions `(h, w)` to form reference boxes. + spatial_shapes (torch.Tensor): Spatial shape of features in different levels. + With shape `(num_levels, 2)`, last dimension represents `(h, w)`. + level_start_index (torch.Tensor): The start index of each level. A tensor with + shape `(num_levels, )` which can be represented as + `[0, h_0 * w_0, h_0 * w_0 + h_1 * w_1, ...]`. + + Returns: + torch.Tensor: forward results with shape `(num_query, bs, embed_dim)` + """ + + if value is None: + value = query + + if query_pos is not None: + query = query + query_pos + + if not self.batch_first: + # change to (bs, num_query ,embed_dims) + query = query.permute(1, 0, 2) + value = value.permute(1, 0, 2) + + bs, num_query, _ = query.shape + bs, num_value, _ = value.shape + + assert (spatial_shapes[:, 0] * spatial_shapes[:, 1]).sum() == num_value + + + value = self.value_proj(value) + if key_padding_mask is not None: + value = value.masked_fill(key_padding_mask[..., None], float(0)) + value = value.view(bs, num_value, self.num_heads, -1) + sampling_offsets = self.sampling_offsets(query).view( + bs, num_query, self.num_heads, self.num_levels, self.num_points, 2 + ) + attention_weights = self.attention_weights(query).view( + bs, num_query, self.num_heads, self.num_levels * self.num_points + ) + attention_weights = attention_weights.softmax(-1) + attention_weights = attention_weights.view( + bs, + num_query, + self.num_heads, + self.num_levels, + self.num_points, + ) + + # bs, num_query, num_heads, num_levels, num_points, 2 + if reference_points.shape[-1] == 2: + offset_normalizer = torch.stack([spatial_shapes[..., 1], spatial_shapes[..., 0]], -1) + sampling_locations = ( + reference_points[:, :, None, :, None, :] + + sampling_offsets / offset_normalizer[None, None, None, :, None, :] + ) + elif reference_points.shape[-1] == 4: + sampling_locations = ( + reference_points[:, :, None, :, None, :2] + + sampling_offsets + / self.num_points + * reference_points[:, :, None, :, None, 2:] + * 0.5 + ) + else: + raise ValueError( + "Last dim of reference_points must be 2 or 4, but get {} instead.".format( + reference_points.shape[-1] + ) + ) + if torch.cuda.is_available() and value.is_cuda: + halffloat = False + if value.dtype == torch.float16: + halffloat = True + value = value.float() + sampling_locations = sampling_locations.float() + attention_weights = attention_weights.float() + + + output = MultiScaleDeformableAttnFunction.apply( + value, + spatial_shapes, + level_start_index, + sampling_locations, + attention_weights, + self.im2col_step, + ) + + if halffloat: + output = output.half() + else: + output = multi_scale_deformable_attn_pytorch( + value, spatial_shapes, sampling_locations, attention_weights + ) + + output = self.output_proj(output) + + if not self.batch_first: + output = output.permute(1, 0, 2) + + return output + + +def create_dummy_class(klass, dependency, message=""): + """ + When a dependency of a class is not available, create a dummy class which throws ImportError + when used. + + Args: + klass (str): name of the class. + dependency (str): name of the dependency. + message: extra message to print + Returns: + class: a class object + """ + err = "Cannot import '{}', therefore '{}' is not available.".format(dependency, klass) + if message: + err = err + " " + message + + class _DummyMetaClass(type): + # throw error on class attribute access + def __getattr__(_, __): # noqa: B902 + raise ImportError(err) + + class _Dummy(object, metaclass=_DummyMetaClass): + # throw error on constructor + def __init__(self, *args, **kwargs): + raise ImportError(err) + + return _Dummy + + +def create_dummy_func(func, dependency, message=""): + """ + When a dependency of a function is not available, create a dummy function which throws + ImportError when used. + + Args: + func (str): name of the function. + dependency (str or list[str]): name(s) of the dependency. + message: extra message to print + Returns: + function: a function object + """ + err = "Cannot import '{}', therefore '{}' is not available.".format(dependency, func) + if message: + err = err + " " + message + + if isinstance(dependency, (list, tuple)): + dependency = ",".join(dependency) + + def _dummy(*args, **kwargs): + raise ImportError(err) + + return _dummy + diff --git a/groundingdino/models/GroundingDINO/transformer.py b/groundingdino/models/GroundingDINO/transformer.py new file mode 100644 index 0000000..44cd2b2 --- /dev/null +++ b/groundingdino/models/GroundingDINO/transformer.py @@ -0,0 +1,942 @@ +# ------------------------------------------------------------------------ +# DINO +# Copyright (c) 2022 IDEA. All Rights Reserved. +# Licensed under the Apache License, Version 2.0 [see LICENSE for details] +# ------------------------------------------------------------------------ +# Conditional DETR Transformer class. +# Copyright (c) 2021 Microsoft. All Rights Reserved. +# Licensed under the Apache License, Version 2.0 [see LICENSE for details] +# ------------------------------------------------------------------------ +# Modified from DETR (https://github.com/facebookresearch/detr) +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +# ------------------------------------------------------------------------ + +from typing import Optional + +import torch +import torch.utils.checkpoint as checkpoint +from torch import Tensor, nn + +from groundingdino.util.misc import inverse_sigmoid + +from .fuse_modules import BiAttentionBlock +from .ms_deform_attn import MultiScaleDeformableAttention as MSDeformAttn +from .transformer_vanilla import TransformerEncoderLayer +from .utils import ( + MLP, + _get_activation_fn, + _get_clones, + gen_encoder_output_proposals, + gen_sineembed_for_position, + get_sine_pos_embed, +) + + +class Transformer(nn.Module): + def __init__( + self, + d_model=256, + nhead=8, + num_queries=300, + num_encoder_layers=6, + num_unicoder_layers=0, + num_decoder_layers=6, + dim_feedforward=2048, + dropout=0.0, + activation="relu", + normalize_before=False, + return_intermediate_dec=False, + query_dim=4, + num_patterns=0, + # for deformable encoder + num_feature_levels=1, + enc_n_points=4, + dec_n_points=4, + # init query + learnable_tgt_init=False, + # two stage + two_stage_type="no", # ['no', 'standard', 'early', 'combine', 'enceachlayer', 'enclayer1'] + embed_init_tgt=False, + # for text + use_text_enhancer=False, + use_fusion_layer=False, + use_checkpoint=False, + use_transformer_ckpt=False, + use_text_cross_attention=False, + text_dropout=0.1, + fusion_dropout=0.1, + fusion_droppath=0.0, + ): + super().__init__() + self.num_feature_levels = num_feature_levels + self.num_encoder_layers = num_encoder_layers + self.num_unicoder_layers = num_unicoder_layers + self.num_decoder_layers = num_decoder_layers + self.num_queries = num_queries + assert query_dim == 4 + + # choose encoder layer type + encoder_layer = DeformableTransformerEncoderLayer( + d_model, dim_feedforward, dropout, activation, num_feature_levels, nhead, enc_n_points + ) + + if use_text_enhancer: + text_enhance_layer = TransformerEncoderLayer( + d_model=d_model, + nhead=nhead // 2, + dim_feedforward=dim_feedforward // 2, + dropout=text_dropout, + ) + else: + text_enhance_layer = None + + if use_fusion_layer: + feature_fusion_layer = BiAttentionBlock( + v_dim=d_model, + l_dim=d_model, + embed_dim=dim_feedforward // 2, + num_heads=nhead // 2, + dropout=fusion_dropout, + drop_path=fusion_droppath, + ) + else: + feature_fusion_layer = None + + encoder_norm = nn.LayerNorm(d_model) if normalize_before else None + assert encoder_norm is None + self.encoder = TransformerEncoder( + encoder_layer, + num_encoder_layers, + d_model=d_model, + num_queries=num_queries, + text_enhance_layer=text_enhance_layer, + feature_fusion_layer=feature_fusion_layer, + use_checkpoint=use_checkpoint, + use_transformer_ckpt=use_transformer_ckpt, + ) + + # choose decoder layer type + decoder_layer = DeformableTransformerDecoderLayer( + d_model, + dim_feedforward, + dropout, + activation, + num_feature_levels, + nhead, + dec_n_points, + use_text_cross_attention=use_text_cross_attention, + ) + + decoder_norm = nn.LayerNorm(d_model) + self.decoder = TransformerDecoder( + decoder_layer, + num_decoder_layers, + decoder_norm, + return_intermediate=return_intermediate_dec, + d_model=d_model, + query_dim=query_dim, + num_feature_levels=num_feature_levels, + ) + + self.d_model = d_model + self.nhead = nhead + self.dec_layers = num_decoder_layers + self.num_queries = num_queries # useful for single stage model only + self.num_patterns = num_patterns + if not isinstance(num_patterns, int): + Warning("num_patterns should be int but {}".format(type(num_patterns))) + self.num_patterns = 0 + + if num_feature_levels > 1: + if self.num_encoder_layers > 0: + self.level_embed = nn.Parameter(torch.Tensor(num_feature_levels, d_model)) + else: + self.level_embed = None + + self.learnable_tgt_init = learnable_tgt_init + assert learnable_tgt_init, "why not learnable_tgt_init" + self.embed_init_tgt = embed_init_tgt + if (two_stage_type != "no" and embed_init_tgt) or (two_stage_type == "no"): + self.tgt_embed = nn.Embedding(self.num_queries, d_model) + nn.init.normal_(self.tgt_embed.weight.data) + else: + self.tgt_embed = None + + # for two stage + self.two_stage_type = two_stage_type + assert two_stage_type in ["no", "standard"], "unknown param {} of two_stage_type".format( + two_stage_type + ) + if two_stage_type == "standard": + # anchor selection at the output of encoder + self.enc_output = nn.Linear(d_model, d_model) + self.enc_output_norm = nn.LayerNorm(d_model) + self.two_stage_wh_embedding = None + + if two_stage_type == "no": + self.init_ref_points(num_queries) # init self.refpoint_embed + + self.enc_out_class_embed = None + self.enc_out_bbox_embed = None + + self._reset_parameters() + + def _reset_parameters(self): + for p in self.parameters(): + if p.dim() > 1: + nn.init.xavier_uniform_(p) + for m in self.modules(): + if isinstance(m, MSDeformAttn): + m._reset_parameters() + if self.num_feature_levels > 1 and self.level_embed is not None: + nn.init.normal_(self.level_embed) + + def get_valid_ratio(self, mask): + _, H, W = mask.shape + valid_H = torch.sum(~mask[:, :, 0], 1) + valid_W = torch.sum(~mask[:, 0, :], 1) + valid_ratio_h = valid_H.float() / H + valid_ratio_w = valid_W.float() / W + valid_ratio = torch.stack([valid_ratio_w, valid_ratio_h], -1) + return valid_ratio + + def init_ref_points(self, use_num_queries): + self.refpoint_embed = nn.Embedding(use_num_queries, 4) + + def forward(self, srcs, masks, refpoint_embed, pos_embeds, tgt, attn_mask=None, text_dict=None): + """ + Input: + - srcs: List of multi features [bs, ci, hi, wi] + - masks: List of multi masks [bs, hi, wi] + - refpoint_embed: [bs, num_dn, 4]. None in infer + - pos_embeds: List of multi pos embeds [bs, ci, hi, wi] + - tgt: [bs, num_dn, d_model]. None in infer + + """ + # prepare input for encoder + src_flatten = [] + mask_flatten = [] + lvl_pos_embed_flatten = [] + spatial_shapes = [] + for lvl, (src, mask, pos_embed) in enumerate(zip(srcs, masks, pos_embeds)): + bs, c, h, w = src.shape + spatial_shape = (h, w) + spatial_shapes.append(spatial_shape) + + src = src.flatten(2).transpose(1, 2) # bs, hw, c + mask = mask.flatten(1) # bs, hw + pos_embed = pos_embed.flatten(2).transpose(1, 2) # bs, hw, c + if self.num_feature_levels > 1 and self.level_embed is not None: + lvl_pos_embed = pos_embed + self.level_embed[lvl].view(1, 1, -1) + else: + lvl_pos_embed = pos_embed + lvl_pos_embed_flatten.append(lvl_pos_embed) + src_flatten.append(src) + mask_flatten.append(mask) + src_flatten = torch.cat(src_flatten, 1) # bs, \sum{hxw}, c + mask_flatten = torch.cat(mask_flatten, 1) # bs, \sum{hxw} + lvl_pos_embed_flatten = torch.cat(lvl_pos_embed_flatten, 1) # bs, \sum{hxw}, c + spatial_shapes = torch.as_tensor( + spatial_shapes, dtype=torch.long, device=src_flatten.device + ) + level_start_index = torch.cat( + (spatial_shapes.new_zeros((1,)), spatial_shapes.prod(1).cumsum(0)[:-1]) + ) + valid_ratios = torch.stack([self.get_valid_ratio(m) for m in masks], 1) + + # two stage + enc_topk_proposals = enc_refpoint_embed = None + + ######################################################### + # Begin Encoder + ######################################################### + memory, memory_text = self.encoder( + src_flatten, + pos=lvl_pos_embed_flatten, + level_start_index=level_start_index, + spatial_shapes=spatial_shapes, + valid_ratios=valid_ratios, + key_padding_mask=mask_flatten, + memory_text=text_dict["encoded_text"], + text_attention_mask=~text_dict["text_token_mask"], + # we ~ the mask . False means use the token; True means pad the token + position_ids=text_dict["position_ids"], + text_self_attention_masks=text_dict["text_self_attention_masks"], + ) + ######################################################### + # End Encoder + # - memory: bs, \sum{hw}, c + # - mask_flatten: bs, \sum{hw} + # - lvl_pos_embed_flatten: bs, \sum{hw}, c + # - enc_intermediate_output: None or (nenc+1, bs, nq, c) or (nenc, bs, nq, c) + # - enc_intermediate_refpoints: None or (nenc+1, bs, nq, c) or (nenc, bs, nq, c) + ######################################################### + text_dict["encoded_text"] = memory_text + # if os.environ.get("SHILONG_AMP_INFNAN_DEBUG") == '1': + # if memory.isnan().any() | memory.isinf().any(): + # import ipdb; ipdb.set_trace() + + if self.two_stage_type == "standard": + output_memory, output_proposals = gen_encoder_output_proposals( + memory, mask_flatten, spatial_shapes + ) + output_memory = self.enc_output_norm(self.enc_output(output_memory)) + + if text_dict is not None: + enc_outputs_class_unselected = self.enc_out_class_embed(output_memory, text_dict) + else: + enc_outputs_class_unselected = self.enc_out_class_embed(output_memory) + + topk_logits = enc_outputs_class_unselected.max(-1)[0] + enc_outputs_coord_unselected = ( + self.enc_out_bbox_embed(output_memory) + output_proposals + ) # (bs, \sum{hw}, 4) unsigmoid + topk = self.num_queries + + topk_proposals = torch.topk(topk_logits, topk, dim=1)[1] # bs, nq + + # gather boxes + refpoint_embed_undetach = torch.gather( + enc_outputs_coord_unselected, 1, topk_proposals.unsqueeze(-1).repeat(1, 1, 4) + ) # unsigmoid + refpoint_embed_ = refpoint_embed_undetach.detach() + init_box_proposal = torch.gather( + output_proposals, 1, topk_proposals.unsqueeze(-1).repeat(1, 1, 4) + ).sigmoid() # sigmoid + + # gather tgt + tgt_undetach = torch.gather( + output_memory, 1, topk_proposals.unsqueeze(-1).repeat(1, 1, self.d_model) + ) + if self.embed_init_tgt: + tgt_ = ( + self.tgt_embed.weight[:, None, :].repeat(1, bs, 1).transpose(0, 1) + ) # nq, bs, d_model + else: + tgt_ = tgt_undetach.detach() + + if refpoint_embed is not None: + refpoint_embed = torch.cat([refpoint_embed, refpoint_embed_], dim=1) + tgt = torch.cat([tgt, tgt_], dim=1) + else: + refpoint_embed, tgt = refpoint_embed_, tgt_ + + elif self.two_stage_type == "no": + tgt_ = ( + self.tgt_embed.weight[:, None, :].repeat(1, bs, 1).transpose(0, 1) + ) # nq, bs, d_model + refpoint_embed_ = ( + self.refpoint_embed.weight[:, None, :].repeat(1, bs, 1).transpose(0, 1) + ) # nq, bs, 4 + + if refpoint_embed is not None: + refpoint_embed = torch.cat([refpoint_embed, refpoint_embed_], dim=1) + tgt = torch.cat([tgt, tgt_], dim=1) + else: + refpoint_embed, tgt = refpoint_embed_, tgt_ + + if self.num_patterns > 0: + tgt_embed = tgt.repeat(1, self.num_patterns, 1) + refpoint_embed = refpoint_embed.repeat(1, self.num_patterns, 1) + tgt_pat = self.patterns.weight[None, :, :].repeat_interleave( + self.num_queries, 1 + ) # 1, n_q*n_pat, d_model + tgt = tgt_embed + tgt_pat + + init_box_proposal = refpoint_embed_.sigmoid() + + else: + raise NotImplementedError("unknown two_stage_type {}".format(self.two_stage_type)) + ######################################################### + # End preparing tgt + # - tgt: bs, NQ, d_model + # - refpoint_embed(unsigmoid): bs, NQ, d_model + ######################################################### + + ######################################################### + # Begin Decoder + ######################################################### + hs, references = self.decoder( + tgt=tgt.transpose(0, 1), + memory=memory.transpose(0, 1), + memory_key_padding_mask=mask_flatten, + pos=lvl_pos_embed_flatten.transpose(0, 1), + refpoints_unsigmoid=refpoint_embed.transpose(0, 1), + level_start_index=level_start_index, + spatial_shapes=spatial_shapes, + valid_ratios=valid_ratios, + tgt_mask=attn_mask, + memory_text=text_dict["encoded_text"], + text_attention_mask=~text_dict["text_token_mask"], + # we ~ the mask . False means use the token; True means pad the token + ) + ######################################################### + # End Decoder + # hs: n_dec, bs, nq, d_model + # references: n_dec+1, bs, nq, query_dim + ######################################################### + + ######################################################### + # Begin postprocess + ######################################################### + if self.two_stage_type == "standard": + hs_enc = tgt_undetach.unsqueeze(0) + ref_enc = refpoint_embed_undetach.sigmoid().unsqueeze(0) + else: + hs_enc = ref_enc = None + ######################################################### + # End postprocess + # hs_enc: (n_enc+1, bs, nq, d_model) or (1, bs, nq, d_model) or (n_enc, bs, nq, d_model) or None + # ref_enc: (n_enc+1, bs, nq, query_dim) or (1, bs, nq, query_dim) or (n_enc, bs, nq, d_model) or None + ######################################################### + + return hs, references, hs_enc, ref_enc, init_box_proposal + # hs: (n_dec, bs, nq, d_model) + # references: sigmoid coordinates. (n_dec+1, bs, bq, 4) + # hs_enc: (n_enc+1, bs, nq, d_model) or (1, bs, nq, d_model) or None + # ref_enc: sigmoid coordinates. \ + # (n_enc+1, bs, nq, query_dim) or (1, bs, nq, query_dim) or None + + +class TransformerEncoder(nn.Module): + def __init__( + self, + encoder_layer, + num_layers, + d_model=256, + num_queries=300, + enc_layer_share=False, + text_enhance_layer=None, + feature_fusion_layer=None, + use_checkpoint=False, + use_transformer_ckpt=False, + ): + """_summary_ + + Args: + encoder_layer (_type_): _description_ + num_layers (_type_): _description_ + norm (_type_, optional): _description_. Defaults to None. + d_model (int, optional): _description_. Defaults to 256. + num_queries (int, optional): _description_. Defaults to 300. + enc_layer_share (bool, optional): _description_. Defaults to False. + + """ + super().__init__() + # prepare layers + self.layers = [] + self.text_layers = [] + self.fusion_layers = [] + if num_layers > 0: + self.layers = _get_clones(encoder_layer, num_layers, layer_share=enc_layer_share) + + if text_enhance_layer is not None: + self.text_layers = _get_clones( + text_enhance_layer, num_layers, layer_share=enc_layer_share + ) + if feature_fusion_layer is not None: + self.fusion_layers = _get_clones( + feature_fusion_layer, num_layers, layer_share=enc_layer_share + ) + else: + self.layers = [] + del encoder_layer + + if text_enhance_layer is not None: + self.text_layers = [] + del text_enhance_layer + if feature_fusion_layer is not None: + self.fusion_layers = [] + del feature_fusion_layer + + self.query_scale = None + self.num_queries = num_queries + self.num_layers = num_layers + self.d_model = d_model + + self.use_checkpoint = use_checkpoint + self.use_transformer_ckpt = use_transformer_ckpt + + @staticmethod + def get_reference_points(spatial_shapes, valid_ratios, device): + reference_points_list = [] + for lvl, (H_, W_) in enumerate(spatial_shapes): + + ref_y, ref_x = torch.meshgrid( + torch.linspace(0.5, H_ - 0.5, H_, dtype=torch.float32, device=device), + torch.linspace(0.5, W_ - 0.5, W_, dtype=torch.float32, device=device), + ) + ref_y = ref_y.reshape(-1)[None] / (valid_ratios[:, None, lvl, 1] * H_) + ref_x = ref_x.reshape(-1)[None] / (valid_ratios[:, None, lvl, 0] * W_) + ref = torch.stack((ref_x, ref_y), -1) + reference_points_list.append(ref) + reference_points = torch.cat(reference_points_list, 1) + reference_points = reference_points[:, :, None] * valid_ratios[:, None] + return reference_points + + def forward( + self, + # for images + src: Tensor, + pos: Tensor, + spatial_shapes: Tensor, + level_start_index: Tensor, + valid_ratios: Tensor, + key_padding_mask: Tensor, + # for texts + memory_text: Tensor = None, + text_attention_mask: Tensor = None, + pos_text: Tensor = None, + text_self_attention_masks: Tensor = None, + position_ids: Tensor = None, + ): + """ + Input: + - src: [bs, sum(hi*wi), 256] + - pos: pos embed for src. [bs, sum(hi*wi), 256] + - spatial_shapes: h,w of each level [num_level, 2] + - level_start_index: [num_level] start point of level in sum(hi*wi). + - valid_ratios: [bs, num_level, 2] + - key_padding_mask: [bs, sum(hi*wi)] + + - memory_text: bs, n_text, 256 + - text_attention_mask: bs, n_text + False for no padding; True for padding + - pos_text: bs, n_text, 256 + + - position_ids: bs, n_text + Intermedia: + - reference_points: [bs, sum(hi*wi), num_level, 2] + Outpus: + - output: [bs, sum(hi*wi), 256] + """ + + output = src + + # preparation and reshape + if self.num_layers > 0: + reference_points = self.get_reference_points( + spatial_shapes, valid_ratios, device=src.device + ) + + if self.text_layers: + # generate pos_text + bs, n_text, text_dim = memory_text.shape + if pos_text is None and position_ids is None: + pos_text = ( + torch.arange(n_text, device=memory_text.device) + .float() + .unsqueeze(0) + .unsqueeze(-1) + .repeat(bs, 1, 1) + ) + pos_text = get_sine_pos_embed(pos_text, num_pos_feats=256, exchange_xy=False) + if position_ids is not None: + pos_text = get_sine_pos_embed( + position_ids[..., None], num_pos_feats=256, exchange_xy=False + ) + + # main process + for layer_id, layer in enumerate(self.layers): + # if output.isnan().any() or memory_text.isnan().any(): + # if os.environ.get('IPDB_SHILONG_DEBUG', None) == 'INFO': + # import ipdb; ipdb.set_trace() + if self.fusion_layers: + if self.use_checkpoint: + output, memory_text = checkpoint.checkpoint( + self.fusion_layers[layer_id], + output, + memory_text, + key_padding_mask, + text_attention_mask, + ) + else: + output, memory_text = self.fusion_layers[layer_id]( + v=output, + l=memory_text, + attention_mask_v=key_padding_mask, + attention_mask_l=text_attention_mask, + ) + + if self.text_layers: + memory_text = self.text_layers[layer_id]( + src=memory_text.transpose(0, 1), + src_mask=~text_self_attention_masks, # note we use ~ for mask here + src_key_padding_mask=text_attention_mask, + pos=(pos_text.transpose(0, 1) if pos_text is not None else None), + ).transpose(0, 1) + + # main process + if self.use_transformer_ckpt: + output = checkpoint.checkpoint( + layer, + output, + pos, + reference_points, + spatial_shapes, + level_start_index, + key_padding_mask, + ) + else: + output = layer( + src=output, + pos=pos, + reference_points=reference_points, + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + key_padding_mask=key_padding_mask, + ) + + return output, memory_text + + +class TransformerDecoder(nn.Module): + def __init__( + self, + decoder_layer, + num_layers, + norm=None, + return_intermediate=False, + d_model=256, + query_dim=4, + num_feature_levels=1, + ): + super().__init__() + if num_layers > 0: + self.layers = _get_clones(decoder_layer, num_layers) + else: + self.layers = [] + self.num_layers = num_layers + self.norm = norm + self.return_intermediate = return_intermediate + assert return_intermediate, "support return_intermediate only" + self.query_dim = query_dim + assert query_dim in [2, 4], "query_dim should be 2/4 but {}".format(query_dim) + self.num_feature_levels = num_feature_levels + + self.ref_point_head = MLP(query_dim // 2 * d_model, d_model, d_model, 2) + self.query_pos_sine_scale = None + + self.query_scale = None + self.bbox_embed = None + self.class_embed = None + + self.d_model = d_model + + self.ref_anchor_head = None + + def forward( + self, + tgt, + memory, + tgt_mask: Optional[Tensor] = None, + memory_mask: Optional[Tensor] = None, + tgt_key_padding_mask: Optional[Tensor] = None, + memory_key_padding_mask: Optional[Tensor] = None, + pos: Optional[Tensor] = None, + refpoints_unsigmoid: Optional[Tensor] = None, # num_queries, bs, 2 + # for memory + level_start_index: Optional[Tensor] = None, # num_levels + spatial_shapes: Optional[Tensor] = None, # bs, num_levels, 2 + valid_ratios: Optional[Tensor] = None, + # for text + memory_text: Optional[Tensor] = None, + text_attention_mask: Optional[Tensor] = None, + ): + """ + Input: + - tgt: nq, bs, d_model + - memory: hw, bs, d_model + - pos: hw, bs, d_model + - refpoints_unsigmoid: nq, bs, 2/4 + - valid_ratios/spatial_shapes: bs, nlevel, 2 + """ + output = tgt + + intermediate = [] + reference_points = refpoints_unsigmoid.sigmoid() + ref_points = [reference_points] + + for layer_id, layer in enumerate(self.layers): + + if reference_points.shape[-1] == 4: + reference_points_input = ( + reference_points[:, :, None] + * torch.cat([valid_ratios, valid_ratios], -1)[None, :] + ) # nq, bs, nlevel, 4 + else: + assert reference_points.shape[-1] == 2 + reference_points_input = reference_points[:, :, None] * valid_ratios[None, :] + query_sine_embed = gen_sineembed_for_position( + reference_points_input[:, :, 0, :] + ) # nq, bs, 256*2 + + # conditional query + raw_query_pos = self.ref_point_head(query_sine_embed) # nq, bs, 256 + pos_scale = self.query_scale(output) if self.query_scale is not None else 1 + query_pos = pos_scale * raw_query_pos + # if os.environ.get("SHILONG_AMP_INFNAN_DEBUG") == '1': + # if query_pos.isnan().any() | query_pos.isinf().any(): + # import ipdb; ipdb.set_trace() + + # main process + output = layer( + tgt=output, + tgt_query_pos=query_pos, + tgt_query_sine_embed=query_sine_embed, + tgt_key_padding_mask=tgt_key_padding_mask, + tgt_reference_points=reference_points_input, + memory_text=memory_text, + text_attention_mask=text_attention_mask, + memory=memory, + memory_key_padding_mask=memory_key_padding_mask, + memory_level_start_index=level_start_index, + memory_spatial_shapes=spatial_shapes, + memory_pos=pos, + self_attn_mask=tgt_mask, + cross_attn_mask=memory_mask, + ) + if output.isnan().any() | output.isinf().any(): + print(f"output layer_id {layer_id} is nan") + try: + num_nan = output.isnan().sum().item() + num_inf = output.isinf().sum().item() + print(f"num_nan {num_nan}, num_inf {num_inf}") + except Exception as e: + print(e) + # if os.environ.get("SHILONG_AMP_INFNAN_DEBUG") == '1': + # import ipdb; ipdb.set_trace() + + # iter update + if self.bbox_embed is not None: + # box_holder = self.bbox_embed(output) + # box_holder[..., :self.query_dim] += inverse_sigmoid(reference_points) + # new_reference_points = box_holder[..., :self.query_dim].sigmoid() + + reference_before_sigmoid = inverse_sigmoid(reference_points) + delta_unsig = self.bbox_embed[layer_id](output) + outputs_unsig = delta_unsig + reference_before_sigmoid + new_reference_points = outputs_unsig.sigmoid() + + reference_points = new_reference_points.detach() + # if layer_id != self.num_layers - 1: + ref_points.append(new_reference_points) + + intermediate.append(self.norm(output)) + + return [ + [itm_out.transpose(0, 1) for itm_out in intermediate], + [itm_refpoint.transpose(0, 1) for itm_refpoint in ref_points], + ] + + +class DeformableTransformerEncoderLayer(nn.Module): + def __init__( + self, + d_model=256, + d_ffn=1024, + dropout=0.1, + activation="relu", + n_levels=4, + n_heads=8, + n_points=4, + ): + super().__init__() + + # self attention + self.self_attn = MSDeformAttn(embed_dim=d_model, num_levels=n_levels, num_heads=n_heads, num_points=n_points, batch_first=True) + self.dropout1 = nn.Dropout(dropout) + self.norm1 = nn.LayerNorm(d_model) + + # ffn + self.linear1 = nn.Linear(d_model, d_ffn) + self.activation = _get_activation_fn(activation, d_model=d_ffn) + self.dropout2 = nn.Dropout(dropout) + self.linear2 = nn.Linear(d_ffn, d_model) + self.dropout3 = nn.Dropout(dropout) + self.norm2 = nn.LayerNorm(d_model) + + @staticmethod + def with_pos_embed(tensor, pos): + return tensor if pos is None else tensor + pos + + def forward_ffn(self, src): + src2 = self.linear2(self.dropout2(self.activation(self.linear1(src)))) + src = src + self.dropout3(src2) + src = self.norm2(src) + return src + + def forward( + self, src, pos, reference_points, spatial_shapes, level_start_index, key_padding_mask=None + ): + # self attention + # import ipdb; ipdb.set_trace() + src2 = self.self_attn( + query=self.with_pos_embed(src, pos), + reference_points=reference_points, + value=src, + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + key_padding_mask=key_padding_mask, + ) + src = src + self.dropout1(src2) + src = self.norm1(src) + + # ffn + src = self.forward_ffn(src) + + return src + + +class DeformableTransformerDecoderLayer(nn.Module): + def __init__( + self, + d_model=256, + d_ffn=1024, + dropout=0.1, + activation="relu", + n_levels=4, + n_heads=8, + n_points=4, + use_text_feat_guide=False, + use_text_cross_attention=False, + ): + super().__init__() + + # cross attention + self.cross_attn = MSDeformAttn(embed_dim=d_model, num_levels=n_levels, num_heads=n_heads, num_points=n_points, batch_first=True) + self.dropout1 = nn.Dropout(dropout) if dropout > 0 else nn.Identity() + self.norm1 = nn.LayerNorm(d_model) + + # cross attention text + if use_text_cross_attention: + self.ca_text = nn.MultiheadAttention(d_model, n_heads, dropout=dropout) + self.catext_dropout = nn.Dropout(dropout) if dropout > 0 else nn.Identity() + self.catext_norm = nn.LayerNorm(d_model) + + # self attention + self.self_attn = nn.MultiheadAttention(d_model, n_heads, dropout=dropout) + self.dropout2 = nn.Dropout(dropout) if dropout > 0 else nn.Identity() + self.norm2 = nn.LayerNorm(d_model) + + # ffn + self.linear1 = nn.Linear(d_model, d_ffn) + self.activation = _get_activation_fn(activation, d_model=d_ffn, batch_dim=1) + self.dropout3 = nn.Dropout(dropout) if dropout > 0 else nn.Identity() + self.linear2 = nn.Linear(d_ffn, d_model) + self.dropout4 = nn.Dropout(dropout) if dropout > 0 else nn.Identity() + self.norm3 = nn.LayerNorm(d_model) + + self.key_aware_proj = None + self.use_text_feat_guide = use_text_feat_guide + assert not use_text_feat_guide + self.use_text_cross_attention = use_text_cross_attention + + def rm_self_attn_modules(self): + self.self_attn = None + self.dropout2 = None + self.norm2 = None + + @staticmethod + def with_pos_embed(tensor, pos): + return tensor if pos is None else tensor + pos + + def forward_ffn(self, tgt): + with torch.cuda.amp.autocast(enabled=False): + tgt2 = self.linear2(self.dropout3(self.activation(self.linear1(tgt)))) + tgt = tgt + self.dropout4(tgt2) + tgt = self.norm3(tgt) + return tgt + + def forward( + self, + # for tgt + tgt: Optional[Tensor], # nq, bs, d_model + tgt_query_pos: Optional[Tensor] = None, # pos for query. MLP(Sine(pos)) + tgt_query_sine_embed: Optional[Tensor] = None, # pos for query. Sine(pos) + tgt_key_padding_mask: Optional[Tensor] = None, + tgt_reference_points: Optional[Tensor] = None, # nq, bs, 4 + memory_text: Optional[Tensor] = None, # bs, num_token, d_model + text_attention_mask: Optional[Tensor] = None, # bs, num_token + # for memory + memory: Optional[Tensor] = None, # hw, bs, d_model + memory_key_padding_mask: Optional[Tensor] = None, + memory_level_start_index: Optional[Tensor] = None, # num_levels + memory_spatial_shapes: Optional[Tensor] = None, # bs, num_levels, 2 + memory_pos: Optional[Tensor] = None, # pos for memory + # sa + self_attn_mask: Optional[Tensor] = None, # mask used for self-attention + cross_attn_mask: Optional[Tensor] = None, # mask used for cross-attention + ): + """ + Input: + - tgt/tgt_query_pos: nq, bs, d_model + - + """ + assert cross_attn_mask is None + + # self attention + if self.self_attn is not None: + # import ipdb; ipdb.set_trace() + q = k = self.with_pos_embed(tgt, tgt_query_pos) + tgt2 = self.self_attn(q, k, tgt, attn_mask=self_attn_mask)[0] + tgt = tgt + self.dropout2(tgt2) + tgt = self.norm2(tgt) + + if self.use_text_cross_attention: + tgt2 = self.ca_text( + self.with_pos_embed(tgt, tgt_query_pos), + memory_text.transpose(0, 1), + memory_text.transpose(0, 1), + key_padding_mask=text_attention_mask, + )[0] + tgt = tgt + self.catext_dropout(tgt2) + tgt = self.catext_norm(tgt) + + tgt2 = self.cross_attn( + query=self.with_pos_embed(tgt, tgt_query_pos).transpose(0, 1), + reference_points=tgt_reference_points.transpose(0, 1).contiguous(), + value=memory.transpose(0, 1), + spatial_shapes=memory_spatial_shapes, + level_start_index=memory_level_start_index, + key_padding_mask=memory_key_padding_mask, + ).transpose(0, 1) + tgt = tgt + self.dropout1(tgt2) + tgt = self.norm1(tgt) + + # ffn + tgt = self.forward_ffn(tgt) + + return tgt + + +def build_transformer(args): + return Transformer( + d_model=args.hidden_dim, + dropout=args.dropout, + nhead=args.nheads, + num_queries=args.num_queries, + dim_feedforward=args.dim_feedforward, + num_encoder_layers=args.enc_layers, + num_decoder_layers=args.dec_layers, + normalize_before=args.pre_norm, + return_intermediate_dec=True, + query_dim=args.query_dim, + activation=args.transformer_activation, + num_patterns=args.num_patterns, + num_feature_levels=args.num_feature_levels, + enc_n_points=args.enc_n_points, + dec_n_points=args.dec_n_points, + learnable_tgt_init=True, + # two stage + two_stage_type=args.two_stage_type, # ['no', 'standard', 'early'] + embed_init_tgt=args.embed_init_tgt, + use_text_enhancer=args.use_text_enhancer, + use_fusion_layer=args.use_fusion_layer, + use_checkpoint=args.use_checkpoint, + use_transformer_ckpt=args.use_transformer_ckpt, + use_text_cross_attention=args.use_text_cross_attention, + text_dropout=args.text_dropout, + fusion_dropout=args.fusion_dropout, + fusion_droppath=args.fusion_droppath, + ) diff --git a/groundingdino/models/GroundingDINO/transformer_vanilla.py b/groundingdino/models/GroundingDINO/transformer_vanilla.py new file mode 100644 index 0000000..1c982f4 --- /dev/null +++ b/groundingdino/models/GroundingDINO/transformer_vanilla.py @@ -0,0 +1,117 @@ +# Copyright (c) Aishwarya Kamath & Nicolas Carion. Licensed under the Apache License 2.0. All Rights Reserved +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved +""" +DETR Transformer class. + +Copy-paste from torch.nn.Transformer with modifications: + * positional encodings are passed in MHattention + * extra LN at the end of encoder is removed + * decoder returns a stack of activations from all decoding layers +""" +from typing import Optional + +import torch +import torch.nn.functional as F +from torch import Tensor, nn + +from .utils import ( + MLP, + _get_activation_fn, + _get_clones, + gen_encoder_output_proposals, + gen_sineembed_for_position, + sigmoid_focal_loss, +) + + +class TextTransformer(nn.Module): + def __init__(self, num_layers, d_model=256, nheads=8, dim_feedforward=2048, dropout=0.1): + super().__init__() + self.num_layers = num_layers + self.d_model = d_model + self.nheads = nheads + self.dim_feedforward = dim_feedforward + self.norm = None + + single_encoder_layer = TransformerEncoderLayer( + d_model=d_model, nhead=nheads, dim_feedforward=dim_feedforward, dropout=dropout + ) + self.layers = _get_clones(single_encoder_layer, num_layers) + + def forward(self, memory_text: torch.Tensor, text_attention_mask: torch.Tensor): + """ + + Args: + text_attention_mask: bs, num_token + memory_text: bs, num_token, d_model + + Raises: + RuntimeError: _description_ + + Returns: + output: bs, num_token, d_model + """ + + output = memory_text.transpose(0, 1) + + for layer in self.layers: + output = layer(output, src_key_padding_mask=text_attention_mask) + + if self.norm is not None: + output = self.norm(output) + + return output.transpose(0, 1) + + +class TransformerEncoderLayer(nn.Module): + def __init__( + self, + d_model, + nhead, + dim_feedforward=2048, + dropout=0.1, + activation="relu", + normalize_before=False, + ): + super().__init__() + self.self_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout) + # Implementation of Feedforward model + self.linear1 = nn.Linear(d_model, dim_feedforward) + self.dropout = nn.Dropout(dropout) + self.linear2 = nn.Linear(dim_feedforward, d_model) + + self.norm1 = nn.LayerNorm(d_model) + self.norm2 = nn.LayerNorm(d_model) + self.dropout1 = nn.Dropout(dropout) + self.dropout2 = nn.Dropout(dropout) + + self.activation = _get_activation_fn(activation) + self.normalize_before = normalize_before + self.nhead = nhead + + def with_pos_embed(self, tensor, pos: Optional[Tensor]): + return tensor if pos is None else tensor + pos + + def forward( + self, + src, + src_mask: Optional[Tensor] = None, + src_key_padding_mask: Optional[Tensor] = None, + pos: Optional[Tensor] = None, + ): + # repeat attn mask + if src_mask.dim() == 3 and src_mask.shape[0] == src.shape[1]: + # bs, num_q, num_k + src_mask = src_mask.repeat(self.nhead, 1, 1) + + q = k = self.with_pos_embed(src, pos) + + src2 = self.self_attn(q, k, value=src, attn_mask=src_mask)[0] + + # src2 = self.self_attn(q, k, value=src, attn_mask=src_mask, key_padding_mask=src_key_padding_mask)[0] + src = src + self.dropout1(src2) + src = self.norm1(src) + src2 = self.linear2(self.dropout(self.activation(self.linear1(src)))) + src = src + self.dropout2(src2) + src = self.norm2(src) + return src diff --git a/groundingdino/models/GroundingDINO/utils.py b/groundingdino/models/GroundingDINO/utils.py new file mode 100644 index 0000000..1f04705 --- /dev/null +++ b/groundingdino/models/GroundingDINO/utils.py @@ -0,0 +1,267 @@ +# ------------------------------------------------------------------------ +# DINO +# Copyright (c) 2022 IDEA. All Rights Reserved. +# Licensed under the Apache License, Version 2.0 [see LICENSE for details] +# ------------------------------------------------------------------------ + +import copy +import math + +import torch +import torch.nn.functional as F +from torch import Tensor, nn + + +def _get_clones(module, N, layer_share=False): + # import ipdb; ipdb.set_trace() + if layer_share: + return nn.ModuleList([module for i in range(N)]) + else: + return nn.ModuleList([copy.deepcopy(module) for i in range(N)]) + + +def get_sine_pos_embed( + pos_tensor: torch.Tensor, + num_pos_feats: int = 128, + temperature: int = 10000, + exchange_xy: bool = True, +): + """generate sine position embedding from a position tensor + Args: + pos_tensor (torch.Tensor): shape: [..., n]. + num_pos_feats (int): projected shape for each float in the tensor. + temperature (int): temperature in the sine/cosine function. + exchange_xy (bool, optional): exchange pos x and pos y. \ + For example, input tensor is [x,y], the results will be [pos(y), pos(x)]. Defaults to True. + Returns: + pos_embed (torch.Tensor): shape: [..., n*num_pos_feats]. + """ + scale = 2 * math.pi + dim_t = torch.arange(num_pos_feats, dtype=torch.float32, device=pos_tensor.device) + dim_t = temperature ** (2 * torch.div(dim_t, 2, rounding_mode="floor") / num_pos_feats) + + def sine_func(x: torch.Tensor): + sin_x = x * scale / dim_t + sin_x = torch.stack((sin_x[..., 0::2].sin(), sin_x[..., 1::2].cos()), dim=3).flatten(2) + return sin_x + + pos_res = [sine_func(x) for x in pos_tensor.split([1] * pos_tensor.shape[-1], dim=-1)] + if exchange_xy: + pos_res[0], pos_res[1] = pos_res[1], pos_res[0] + pos_res = torch.cat(pos_res, dim=-1) + return pos_res + + +def gen_encoder_output_proposals( + memory: Tensor, memory_padding_mask: Tensor, spatial_shapes: Tensor, learnedwh=None +): + """ + Input: + - memory: bs, \sum{hw}, d_model + - memory_padding_mask: bs, \sum{hw} + - spatial_shapes: nlevel, 2 + - learnedwh: 2 + Output: + - output_memory: bs, \sum{hw}, d_model + - output_proposals: bs, \sum{hw}, 4 + """ + N_, S_, C_ = memory.shape + proposals = [] + _cur = 0 + for lvl, (H_, W_) in enumerate(spatial_shapes): + mask_flatten_ = memory_padding_mask[:, _cur : (_cur + H_ * W_)].view(N_, H_, W_, 1) + valid_H = torch.sum(~mask_flatten_[:, :, 0, 0], 1) + valid_W = torch.sum(~mask_flatten_[:, 0, :, 0], 1) + + # import ipdb; ipdb.set_trace() + + grid_y, grid_x = torch.meshgrid( + torch.linspace(0, H_ - 1, H_, dtype=torch.float32, device=memory.device), + torch.linspace(0, W_ - 1, W_, dtype=torch.float32, device=memory.device), + ) + grid = torch.cat([grid_x.unsqueeze(-1), grid_y.unsqueeze(-1)], -1) # H_, W_, 2 + + scale = torch.cat([valid_W.unsqueeze(-1), valid_H.unsqueeze(-1)], 1).view(N_, 1, 1, 2) + grid = (grid.unsqueeze(0).expand(N_, -1, -1, -1) + 0.5) / scale + + if learnedwh is not None: + # import ipdb; ipdb.set_trace() + wh = torch.ones_like(grid) * learnedwh.sigmoid() * (2.0**lvl) + else: + wh = torch.ones_like(grid) * 0.05 * (2.0**lvl) + + # scale = torch.cat([W_[None].unsqueeze(-1), H_[None].unsqueeze(-1)], 1).view(1, 1, 1, 2).repeat(N_, 1, 1, 1) + # grid = (grid.unsqueeze(0).expand(N_, -1, -1, -1) + 0.5) / scale + # wh = torch.ones_like(grid) / scale + proposal = torch.cat((grid, wh), -1).view(N_, -1, 4) + proposals.append(proposal) + _cur += H_ * W_ + # import ipdb; ipdb.set_trace() + output_proposals = torch.cat(proposals, 1) + output_proposals_valid = ((output_proposals > 0.01) & (output_proposals < 0.99)).all( + -1, keepdim=True + ) + output_proposals = torch.log(output_proposals / (1 - output_proposals)) # unsigmoid + output_proposals = output_proposals.masked_fill(memory_padding_mask.unsqueeze(-1), float("inf")) + output_proposals = output_proposals.masked_fill(~output_proposals_valid, float("inf")) + + output_memory = memory + output_memory = output_memory.masked_fill(memory_padding_mask.unsqueeze(-1), float(0)) + output_memory = output_memory.masked_fill(~output_proposals_valid, float(0)) + + # output_memory = output_memory.masked_fill(memory_padding_mask.unsqueeze(-1), float('inf')) + # output_memory = output_memory.masked_fill(~output_proposals_valid, float('inf')) + + return output_memory, output_proposals + + +class RandomBoxPerturber: + def __init__( + self, x_noise_scale=0.2, y_noise_scale=0.2, w_noise_scale=0.2, h_noise_scale=0.2 + ) -> None: + self.noise_scale = torch.Tensor( + [x_noise_scale, y_noise_scale, w_noise_scale, h_noise_scale] + ) + + def __call__(self, refanchors: Tensor) -> Tensor: + nq, bs, query_dim = refanchors.shape + device = refanchors.device + + noise_raw = torch.rand_like(refanchors) + noise_scale = self.noise_scale.to(device)[:query_dim] + + new_refanchors = refanchors * (1 + (noise_raw - 0.5) * noise_scale) + return new_refanchors.clamp_(0, 1) + + +def sigmoid_focal_loss( + inputs, targets, num_boxes, alpha: float = 0.25, gamma: float = 2, no_reduction=False +): + """ + Loss used in RetinaNet for dense detection: https://arxiv.org/abs/1708.02002. + Args: + inputs: A float tensor of arbitrary shape. + The predictions for each example. + targets: A float tensor with the same shape as inputs. Stores the binary + classification label for each element in inputs + (0 for the negative class and 1 for the positive class). + alpha: (optional) Weighting factor in range (0,1) to balance + positive vs negative examples. Default = -1 (no weighting). + gamma: Exponent of the modulating factor (1 - p_t) to + balance easy vs hard examples. + Returns: + Loss tensor + """ + prob = inputs.sigmoid() + ce_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduction="none") + p_t = prob * targets + (1 - prob) * (1 - targets) + loss = ce_loss * ((1 - p_t) ** gamma) + + if alpha >= 0: + alpha_t = alpha * targets + (1 - alpha) * (1 - targets) + loss = alpha_t * loss + + if no_reduction: + return loss + + return loss.mean(1).sum() / num_boxes + + +class MLP(nn.Module): + """Very simple multi-layer perceptron (also called FFN)""" + + def __init__(self, input_dim, hidden_dim, output_dim, num_layers): + super().__init__() + self.num_layers = num_layers + h = [hidden_dim] * (num_layers - 1) + self.layers = nn.ModuleList( + nn.Linear(n, k) for n, k in zip([input_dim] + h, h + [output_dim]) + ) + + def forward(self, x): + for i, layer in enumerate(self.layers): + x = F.relu(layer(x)) if i < self.num_layers - 1 else layer(x) + return x + + +def _get_activation_fn(activation, d_model=256, batch_dim=0): + """Return an activation function given a string""" + if activation == "relu": + return F.relu + if activation == "gelu": + return F.gelu + if activation == "glu": + return F.glu + if activation == "prelu": + return nn.PReLU() + if activation == "selu": + return F.selu + + raise RuntimeError(f"activation should be relu/gelu, not {activation}.") + + +def gen_sineembed_for_position(pos_tensor): + # n_query, bs, _ = pos_tensor.size() + # sineembed_tensor = torch.zeros(n_query, bs, 256) + scale = 2 * math.pi + dim_t = torch.arange(128, dtype=torch.float32, device=pos_tensor.device) + dim_t = 10000 ** (2 * (dim_t // 2) / 128) + x_embed = pos_tensor[:, :, 0] * scale + y_embed = pos_tensor[:, :, 1] * scale + pos_x = x_embed[:, :, None] / dim_t + pos_y = y_embed[:, :, None] / dim_t + pos_x = torch.stack((pos_x[:, :, 0::2].sin(), pos_x[:, :, 1::2].cos()), dim=3).flatten(2) + pos_y = torch.stack((pos_y[:, :, 0::2].sin(), pos_y[:, :, 1::2].cos()), dim=3).flatten(2) + if pos_tensor.size(-1) == 2: + pos = torch.cat((pos_y, pos_x), dim=2) + elif pos_tensor.size(-1) == 4: + w_embed = pos_tensor[:, :, 2] * scale + pos_w = w_embed[:, :, None] / dim_t + pos_w = torch.stack((pos_w[:, :, 0::2].sin(), pos_w[:, :, 1::2].cos()), dim=3).flatten(2) + + h_embed = pos_tensor[:, :, 3] * scale + pos_h = h_embed[:, :, None] / dim_t + pos_h = torch.stack((pos_h[:, :, 0::2].sin(), pos_h[:, :, 1::2].cos()), dim=3).flatten(2) + + pos = torch.cat((pos_y, pos_x, pos_w, pos_h), dim=2) + else: + raise ValueError("Unknown pos_tensor shape(-1):{}".format(pos_tensor.size(-1))) + return pos + + +class ContrastiveEmbed(nn.Module): + def __init__(self, max_text_len=256): + """ + Args: + max_text_len: max length of text. + """ + super().__init__() + self.max_text_len = max_text_len + + def forward(self, x, text_dict): + """_summary_ + + Args: + x (_type_): _description_ + text_dict (_type_): _description_ + { + 'encoded_text': encoded_text, # bs, 195, d_model + 'text_token_mask': text_token_mask, # bs, 195 + # True for used tokens. False for padding tokens + } + Returns: + _type_: _description_ + """ + assert isinstance(text_dict, dict) + + y = text_dict["encoded_text"] + text_token_mask = text_dict["text_token_mask"] + + res = x @ y.transpose(-1, -2) + res.masked_fill_(~text_token_mask[:, None, :], float("-inf")) + + # padding to max_text_len + new_res = torch.full((*res.shape[:-1], self.max_text_len), float("-inf"), device=res.device) + new_res[..., : res.shape[-1]] = res + + return new_res diff --git a/groundingdino/models/__init__.py b/groundingdino/models/__init__.py new file mode 100644 index 0000000..96f2b36 --- /dev/null +++ b/groundingdino/models/__init__.py @@ -0,0 +1,17 @@ +# ------------------------------------------------------------------------ +# DINO +# Copyright (c) 2022 IDEA. All Rights Reserved. +# Licensed under the Apache License, Version 2.0 [see LICENSE for details] +# ------------------------------------------------------------------------ +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved +from .GroundingDINO import build_groundingdino + + +def build_model(args): + # we use register to maintain models from catdet6 on. + from .registry import MODULE_BUILD_FUNCS + + assert args.modelname in MODULE_BUILD_FUNCS._module_dict + build_func = MODULE_BUILD_FUNCS.get(args.modelname) + model = build_func(args) + return model diff --git a/groundingdino/models/registry.py b/groundingdino/models/registry.py new file mode 100644 index 0000000..cfd4144 --- /dev/null +++ b/groundingdino/models/registry.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# @Author: Yihao Chen +# @Date: 2021-08-16 16:03:17 +# @Last Modified by: Shilong Liu +# @Last Modified time: 2022-01-23 15:26 +# modified from mmcv + +import inspect +from functools import partial + + +class Registry(object): + def __init__(self, name): + self._name = name + self._module_dict = dict() + + def __repr__(self): + format_str = self.__class__.__name__ + "(name={}, items={})".format( + self._name, list(self._module_dict.keys()) + ) + return format_str + + def __len__(self): + return len(self._module_dict) + + @property + def name(self): + return self._name + + @property + def module_dict(self): + return self._module_dict + + def get(self, key): + return self._module_dict.get(key, None) + + def registe_with_name(self, module_name=None, force=False): + return partial(self.register, module_name=module_name, force=force) + + def register(self, module_build_function, module_name=None, force=False): + """Register a module build function. + Args: + module (:obj:`nn.Module`): Module to be registered. + """ + if not inspect.isfunction(module_build_function): + raise TypeError( + "module_build_function must be a function, but got {}".format( + type(module_build_function) + ) + ) + if module_name is None: + module_name = module_build_function.__name__ + if not force and module_name in self._module_dict: + raise KeyError("{} is already registered in {}".format(module_name, self.name)) + self._module_dict[module_name] = module_build_function + + return module_build_function + + +MODULE_BUILD_FUNCS = Registry("model build functions") diff --git a/groundingdino/util/__init__.py b/groundingdino/util/__init__.py new file mode 100644 index 0000000..168f997 --- /dev/null +++ b/groundingdino/util/__init__.py @@ -0,0 +1 @@ +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved diff --git a/groundingdino/util/box_ops.py b/groundingdino/util/box_ops.py new file mode 100644 index 0000000..781068d --- /dev/null +++ b/groundingdino/util/box_ops.py @@ -0,0 +1,140 @@ +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved +""" +Utilities for bounding box manipulation and GIoU. +""" +import torch +from torchvision.ops.boxes import box_area + + +def box_cxcywh_to_xyxy(x): + x_c, y_c, w, h = x.unbind(-1) + b = [(x_c - 0.5 * w), (y_c - 0.5 * h), (x_c + 0.5 * w), (y_c + 0.5 * h)] + return torch.stack(b, dim=-1) + + +def box_xyxy_to_cxcywh(x): + x0, y0, x1, y1 = x.unbind(-1) + b = [(x0 + x1) / 2, (y0 + y1) / 2, (x1 - x0), (y1 - y0)] + return torch.stack(b, dim=-1) + + +# modified from torchvision to also return the union +def box_iou(boxes1, boxes2): + area1 = box_area(boxes1) + area2 = box_area(boxes2) + + # import ipdb; ipdb.set_trace() + lt = torch.max(boxes1[:, None, :2], boxes2[:, :2]) # [N,M,2] + rb = torch.min(boxes1[:, None, 2:], boxes2[:, 2:]) # [N,M,2] + + wh = (rb - lt).clamp(min=0) # [N,M,2] + inter = wh[:, :, 0] * wh[:, :, 1] # [N,M] + + union = area1[:, None] + area2 - inter + + iou = inter / (union + 1e-6) + return iou, union + + +def generalized_box_iou(boxes1, boxes2): + """ + Generalized IoU from https://giou.stanford.edu/ + + The boxes should be in [x0, y0, x1, y1] format + + Returns a [N, M] pairwise matrix, where N = len(boxes1) + and M = len(boxes2) + """ + # degenerate boxes gives inf / nan results + # so do an early check + assert (boxes1[:, 2:] >= boxes1[:, :2]).all() + assert (boxes2[:, 2:] >= boxes2[:, :2]).all() + # except: + # import ipdb; ipdb.set_trace() + iou, union = box_iou(boxes1, boxes2) + + lt = torch.min(boxes1[:, None, :2], boxes2[:, :2]) + rb = torch.max(boxes1[:, None, 2:], boxes2[:, 2:]) + + wh = (rb - lt).clamp(min=0) # [N,M,2] + area = wh[:, :, 0] * wh[:, :, 1] + + return iou - (area - union) / (area + 1e-6) + + +# modified from torchvision to also return the union +def box_iou_pairwise(boxes1, boxes2): + area1 = box_area(boxes1) + area2 = box_area(boxes2) + + lt = torch.max(boxes1[:, :2], boxes2[:, :2]) # [N,2] + rb = torch.min(boxes1[:, 2:], boxes2[:, 2:]) # [N,2] + + wh = (rb - lt).clamp(min=0) # [N,2] + inter = wh[:, 0] * wh[:, 1] # [N] + + union = area1 + area2 - inter + + iou = inter / union + return iou, union + + +def generalized_box_iou_pairwise(boxes1, boxes2): + """ + Generalized IoU from https://giou.stanford.edu/ + + Input: + - boxes1, boxes2: N,4 + Output: + - giou: N, 4 + """ + # degenerate boxes gives inf / nan results + # so do an early check + assert (boxes1[:, 2:] >= boxes1[:, :2]).all() + assert (boxes2[:, 2:] >= boxes2[:, :2]).all() + assert boxes1.shape == boxes2.shape + iou, union = box_iou_pairwise(boxes1, boxes2) # N, 4 + + lt = torch.min(boxes1[:, :2], boxes2[:, :2]) + rb = torch.max(boxes1[:, 2:], boxes2[:, 2:]) + + wh = (rb - lt).clamp(min=0) # [N,2] + area = wh[:, 0] * wh[:, 1] + + return iou - (area - union) / area + + +def masks_to_boxes(masks): + """Compute the bounding boxes around the provided masks + + The masks should be in format [N, H, W] where N is the number of masks, (H, W) are the spatial dimensions. + + Returns a [N, 4] tensors, with the boxes in xyxy format + """ + if masks.numel() == 0: + return torch.zeros((0, 4), device=masks.device) + + h, w = masks.shape[-2:] + + y = torch.arange(0, h, dtype=torch.float) + x = torch.arange(0, w, dtype=torch.float) + y, x = torch.meshgrid(y, x) + + x_mask = masks * x.unsqueeze(0) + x_max = x_mask.flatten(1).max(-1)[0] + x_min = x_mask.masked_fill(~(masks.bool()), 1e8).flatten(1).min(-1)[0] + + y_mask = masks * y.unsqueeze(0) + y_max = y_mask.flatten(1).max(-1)[0] + y_min = y_mask.masked_fill(~(masks.bool()), 1e8).flatten(1).min(-1)[0] + + return torch.stack([x_min, y_min, x_max, y_max], 1) + + +if __name__ == "__main__": + x = torch.rand(5, 4) + y = torch.rand(3, 4) + iou, union = box_iou(x, y) + import ipdb + + ipdb.set_trace() diff --git a/groundingdino/util/get_tokenlizer.py b/groundingdino/util/get_tokenlizer.py new file mode 100644 index 0000000..f7dcf7e --- /dev/null +++ b/groundingdino/util/get_tokenlizer.py @@ -0,0 +1,26 @@ +from transformers import AutoTokenizer, BertModel, BertTokenizer, RobertaModel, RobertaTokenizerFast + + +def get_tokenlizer(text_encoder_type): + if not isinstance(text_encoder_type, str): + # print("text_encoder_type is not a str") + if hasattr(text_encoder_type, "text_encoder_type"): + text_encoder_type = text_encoder_type.text_encoder_type + elif text_encoder_type.get("text_encoder_type", False): + text_encoder_type = text_encoder_type.get("text_encoder_type") + else: + raise ValueError( + "Unknown type of text_encoder_type: {}".format(type(text_encoder_type)) + ) + print("final text_encoder_type: {}".format(text_encoder_type)) + + tokenizer = AutoTokenizer.from_pretrained(text_encoder_type) + return tokenizer + + +def get_pretrained_language_model(text_encoder_type): + if text_encoder_type == "bert-base-uncased": + return BertModel.from_pretrained(text_encoder_type) + if text_encoder_type == "roberta-base": + return RobertaModel.from_pretrained(text_encoder_type) + raise ValueError("Unknown text_encoder_type {}".format(text_encoder_type)) diff --git a/groundingdino/util/logger.py b/groundingdino/util/logger.py new file mode 100644 index 0000000..18145f5 --- /dev/null +++ b/groundingdino/util/logger.py @@ -0,0 +1,93 @@ +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved +import functools +import logging +import os +import sys + +from termcolor import colored + + +class _ColorfulFormatter(logging.Formatter): + def __init__(self, *args, **kwargs): + self._root_name = kwargs.pop("root_name") + "." + self._abbrev_name = kwargs.pop("abbrev_name", "") + if len(self._abbrev_name): + self._abbrev_name = self._abbrev_name + "." + super(_ColorfulFormatter, self).__init__(*args, **kwargs) + + def formatMessage(self, record): + record.name = record.name.replace(self._root_name, self._abbrev_name) + log = super(_ColorfulFormatter, self).formatMessage(record) + if record.levelno == logging.WARNING: + prefix = colored("WARNING", "red", attrs=["blink"]) + elif record.levelno == logging.ERROR or record.levelno == logging.CRITICAL: + prefix = colored("ERROR", "red", attrs=["blink", "underline"]) + else: + return log + return prefix + " " + log + + +# so that calling setup_logger multiple times won't add many handlers +@functools.lru_cache() +def setup_logger(output=None, distributed_rank=0, *, color=True, name="imagenet", abbrev_name=None): + """ + Initialize the detectron2 logger and set its verbosity level to "INFO". + + Args: + output (str): a file name or a directory to save log. If None, will not save log file. + If ends with ".txt" or ".log", assumed to be a file name. + Otherwise, logs will be saved to `output/log.txt`. + name (str): the root module name of this logger + + Returns: + logging.Logger: a logger + """ + logger = logging.getLogger(name) + logger.setLevel(logging.DEBUG) + logger.propagate = False + + if abbrev_name is None: + abbrev_name = name + + plain_formatter = logging.Formatter( + "[%(asctime)s.%(msecs)03d]: %(message)s", datefmt="%m/%d %H:%M:%S" + ) + # stdout logging: master only + if distributed_rank == 0: + ch = logging.StreamHandler(stream=sys.stdout) + ch.setLevel(logging.DEBUG) + if color: + formatter = _ColorfulFormatter( + colored("[%(asctime)s.%(msecs)03d]: ", "green") + "%(message)s", + datefmt="%m/%d %H:%M:%S", + root_name=name, + abbrev_name=str(abbrev_name), + ) + else: + formatter = plain_formatter + ch.setFormatter(formatter) + logger.addHandler(ch) + + # file logging: all workers + if output is not None: + if output.endswith(".txt") or output.endswith(".log"): + filename = output + else: + filename = os.path.join(output, "log.txt") + if distributed_rank > 0: + filename = filename + f".rank{distributed_rank}" + os.makedirs(os.path.dirname(filename), exist_ok=True) + + fh = logging.StreamHandler(_cached_log_stream(filename)) + fh.setLevel(logging.DEBUG) + fh.setFormatter(plain_formatter) + logger.addHandler(fh) + + return logger + + +# cache the opened file object, so that different calls to `setup_logger` +# with the same file name can safely write to the same file. +@functools.lru_cache(maxsize=None) +def _cached_log_stream(filename): + return open(filename, "a") diff --git a/groundingdino/util/misc.py b/groundingdino/util/misc.py new file mode 100644 index 0000000..d64b84e --- /dev/null +++ b/groundingdino/util/misc.py @@ -0,0 +1,717 @@ +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved +""" +Misc functions, including distributed helpers. + +Mostly copy-paste from torchvision references. +""" +import colorsys +import datetime +import functools +import io +import json +import os +import pickle +import subprocess +import time +from collections import OrderedDict, defaultdict, deque +from typing import List, Optional + +import numpy as np +import torch +import torch.distributed as dist + +# needed due to empty tensor bug in pytorch and torchvision 0.5 +import torchvision +from torch import Tensor + +__torchvision_need_compat_flag = float(torchvision.__version__.split(".")[1]) < 7 +if __torchvision_need_compat_flag: + from torchvision.ops import _new_empty_tensor + from torchvision.ops.misc import _output_size + + +class SmoothedValue(object): + """Track a series of values and provide access to smoothed values over a + window or the global series average. + """ + + def __init__(self, window_size=20, fmt=None): + if fmt is None: + fmt = "{median:.4f} ({global_avg:.4f})" + self.deque = deque(maxlen=window_size) + self.total = 0.0 + self.count = 0 + self.fmt = fmt + + def update(self, value, n=1): + self.deque.append(value) + self.count += n + self.total += value * n + + def synchronize_between_processes(self): + """ + Warning: does not synchronize the deque! + """ + if not is_dist_avail_and_initialized(): + return + t = torch.tensor([self.count, self.total], dtype=torch.float64, device="cuda") + dist.barrier() + dist.all_reduce(t) + t = t.tolist() + self.count = int(t[0]) + self.total = t[1] + + @property + def median(self): + d = torch.tensor(list(self.deque)) + if d.shape[0] == 0: + return 0 + return d.median().item() + + @property + def avg(self): + d = torch.tensor(list(self.deque), dtype=torch.float32) + return d.mean().item() + + @property + def global_avg(self): + if os.environ.get("SHILONG_AMP", None) == "1": + eps = 1e-4 + else: + eps = 1e-6 + return self.total / (self.count + eps) + + @property + def max(self): + return max(self.deque) + + @property + def value(self): + return self.deque[-1] + + def __str__(self): + return self.fmt.format( + median=self.median, + avg=self.avg, + global_avg=self.global_avg, + max=self.max, + value=self.value, + ) + + +@functools.lru_cache() +def _get_global_gloo_group(): + """ + Return a process group based on gloo backend, containing all the ranks + The result is cached. + """ + + if dist.get_backend() == "nccl": + return dist.new_group(backend="gloo") + + return dist.group.WORLD + + +def all_gather_cpu(data): + """ + Run all_gather on arbitrary picklable data (not necessarily tensors) + Args: + data: any picklable object + Returns: + list[data]: list of data gathered from each rank + """ + + world_size = get_world_size() + if world_size == 1: + return [data] + + cpu_group = _get_global_gloo_group() + + buffer = io.BytesIO() + torch.save(data, buffer) + data_view = buffer.getbuffer() + device = "cuda" if cpu_group is None else "cpu" + tensor = torch.ByteTensor(data_view).to(device) + + # obtain Tensor size of each rank + local_size = torch.tensor([tensor.numel()], device=device, dtype=torch.long) + size_list = [torch.tensor([0], device=device, dtype=torch.long) for _ in range(world_size)] + if cpu_group is None: + dist.all_gather(size_list, local_size) + else: + print("gathering on cpu") + dist.all_gather(size_list, local_size, group=cpu_group) + size_list = [int(size.item()) for size in size_list] + max_size = max(size_list) + assert isinstance(local_size.item(), int) + local_size = int(local_size.item()) + + # receiving Tensor from all ranks + # we pad the tensor because torch all_gather does not support + # gathering tensors of different shapes + tensor_list = [] + for _ in size_list: + tensor_list.append(torch.empty((max_size,), dtype=torch.uint8, device=device)) + if local_size != max_size: + padding = torch.empty(size=(max_size - local_size,), dtype=torch.uint8, device=device) + tensor = torch.cat((tensor, padding), dim=0) + if cpu_group is None: + dist.all_gather(tensor_list, tensor) + else: + dist.all_gather(tensor_list, tensor, group=cpu_group) + + data_list = [] + for size, tensor in zip(size_list, tensor_list): + tensor = torch.split(tensor, [size, max_size - size], dim=0)[0] + buffer = io.BytesIO(tensor.cpu().numpy()) + obj = torch.load(buffer) + data_list.append(obj) + + return data_list + + +def all_gather(data): + """ + Run all_gather on arbitrary picklable data (not necessarily tensors) + Args: + data: any picklable object + Returns: + list[data]: list of data gathered from each rank + """ + + if os.getenv("CPU_REDUCE") == "1": + return all_gather_cpu(data) + + world_size = get_world_size() + if world_size == 1: + return [data] + + # serialized to a Tensor + buffer = pickle.dumps(data) + storage = torch.ByteStorage.from_buffer(buffer) + tensor = torch.ByteTensor(storage).to("cuda") + + # obtain Tensor size of each rank + local_size = torch.tensor([tensor.numel()], device="cuda") + size_list = [torch.tensor([0], device="cuda") for _ in range(world_size)] + dist.all_gather(size_list, local_size) + size_list = [int(size.item()) for size in size_list] + max_size = max(size_list) + + # receiving Tensor from all ranks + # we pad the tensor because torch all_gather does not support + # gathering tensors of different shapes + tensor_list = [] + for _ in size_list: + tensor_list.append(torch.empty((max_size,), dtype=torch.uint8, device="cuda")) + if local_size != max_size: + padding = torch.empty(size=(max_size - local_size,), dtype=torch.uint8, device="cuda") + tensor = torch.cat((tensor, padding), dim=0) + dist.all_gather(tensor_list, tensor) + + data_list = [] + for size, tensor in zip(size_list, tensor_list): + buffer = tensor.cpu().numpy().tobytes()[:size] + data_list.append(pickle.loads(buffer)) + + return data_list + + +def reduce_dict(input_dict, average=True): + """ + Args: + input_dict (dict): all the values will be reduced + average (bool): whether to do average or sum + Reduce the values in the dictionary from all processes so that all processes + have the averaged results. Returns a dict with the same fields as + input_dict, after reduction. + """ + world_size = get_world_size() + if world_size < 2: + return input_dict + with torch.no_grad(): + names = [] + values = [] + # sort the keys so that they are consistent across processes + for k in sorted(input_dict.keys()): + names.append(k) + values.append(input_dict[k]) + values = torch.stack(values, dim=0) + dist.all_reduce(values) + if average: + values /= world_size + reduced_dict = {k: v for k, v in zip(names, values)} + return reduced_dict + + +class MetricLogger(object): + def __init__(self, delimiter="\t"): + self.meters = defaultdict(SmoothedValue) + self.delimiter = delimiter + + def update(self, **kwargs): + for k, v in kwargs.items(): + if isinstance(v, torch.Tensor): + v = v.item() + assert isinstance(v, (float, int)) + self.meters[k].update(v) + + def __getattr__(self, attr): + if attr in self.meters: + return self.meters[attr] + if attr in self.__dict__: + return self.__dict__[attr] + raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, attr)) + + def __str__(self): + loss_str = [] + for name, meter in self.meters.items(): + # print(name, str(meter)) + # import ipdb;ipdb.set_trace() + if meter.count > 0: + loss_str.append("{}: {}".format(name, str(meter))) + return self.delimiter.join(loss_str) + + def synchronize_between_processes(self): + for meter in self.meters.values(): + meter.synchronize_between_processes() + + def add_meter(self, name, meter): + self.meters[name] = meter + + def log_every(self, iterable, print_freq, header=None, logger=None): + if logger is None: + print_func = print + else: + print_func = logger.info + + i = 0 + if not header: + header = "" + start_time = time.time() + end = time.time() + iter_time = SmoothedValue(fmt="{avg:.4f}") + data_time = SmoothedValue(fmt="{avg:.4f}") + space_fmt = ":" + str(len(str(len(iterable)))) + "d" + if torch.cuda.is_available(): + log_msg = self.delimiter.join( + [ + header, + "[{0" + space_fmt + "}/{1}]", + "eta: {eta}", + "{meters}", + "time: {time}", + "data: {data}", + "max mem: {memory:.0f}", + ] + ) + else: + log_msg = self.delimiter.join( + [ + header, + "[{0" + space_fmt + "}/{1}]", + "eta: {eta}", + "{meters}", + "time: {time}", + "data: {data}", + ] + ) + MB = 1024.0 * 1024.0 + for obj in iterable: + data_time.update(time.time() - end) + yield obj + # import ipdb; ipdb.set_trace() + iter_time.update(time.time() - end) + if i % print_freq == 0 or i == len(iterable) - 1: + eta_seconds = iter_time.global_avg * (len(iterable) - i) + eta_string = str(datetime.timedelta(seconds=int(eta_seconds))) + if torch.cuda.is_available(): + print_func( + log_msg.format( + i, + len(iterable), + eta=eta_string, + meters=str(self), + time=str(iter_time), + data=str(data_time), + memory=torch.cuda.max_memory_allocated() / MB, + ) + ) + else: + print_func( + log_msg.format( + i, + len(iterable), + eta=eta_string, + meters=str(self), + time=str(iter_time), + data=str(data_time), + ) + ) + i += 1 + end = time.time() + total_time = time.time() - start_time + total_time_str = str(datetime.timedelta(seconds=int(total_time))) + print_func( + "{} Total time: {} ({:.4f} s / it)".format( + header, total_time_str, total_time / len(iterable) + ) + ) + + +def get_sha(): + cwd = os.path.dirname(os.path.abspath(__file__)) + + def _run(command): + return subprocess.check_output(command, cwd=cwd).decode("ascii").strip() + + sha = "N/A" + diff = "clean" + branch = "N/A" + try: + sha = _run(["git", "rev-parse", "HEAD"]) + subprocess.check_output(["git", "diff"], cwd=cwd) + diff = _run(["git", "diff-index", "HEAD"]) + diff = "has uncommited changes" if diff else "clean" + branch = _run(["git", "rev-parse", "--abbrev-ref", "HEAD"]) + except Exception: + pass + message = f"sha: {sha}, status: {diff}, branch: {branch}" + return message + + +def collate_fn(batch): + # import ipdb; ipdb.set_trace() + batch = list(zip(*batch)) + batch[0] = nested_tensor_from_tensor_list(batch[0]) + return tuple(batch) + + +def _max_by_axis(the_list): + # type: (List[List[int]]) -> List[int] + maxes = the_list[0] + for sublist in the_list[1:]: + for index, item in enumerate(sublist): + maxes[index] = max(maxes[index], item) + return maxes + + +class NestedTensor(object): + def __init__(self, tensors, mask: Optional[Tensor]): + self.tensors = tensors + self.mask = mask + if mask == "auto": + self.mask = torch.zeros_like(tensors).to(tensors.device) + if self.mask.dim() == 3: + self.mask = self.mask.sum(0).to(bool) + elif self.mask.dim() == 4: + self.mask = self.mask.sum(1).to(bool) + else: + raise ValueError( + "tensors dim must be 3 or 4 but {}({})".format( + self.tensors.dim(), self.tensors.shape + ) + ) + + def imgsize(self): + res = [] + for i in range(self.tensors.shape[0]): + mask = self.mask[i] + maxH = (~mask).sum(0).max() + maxW = (~mask).sum(1).max() + res.append(torch.Tensor([maxH, maxW])) + return res + + def to(self, device): + # type: (Device) -> NestedTensor # noqa + cast_tensor = self.tensors.to(device) + mask = self.mask + if mask is not None: + assert mask is not None + cast_mask = mask.to(device) + else: + cast_mask = None + return NestedTensor(cast_tensor, cast_mask) + + def to_img_list_single(self, tensor, mask): + assert tensor.dim() == 3, "dim of tensor should be 3 but {}".format(tensor.dim()) + maxH = (~mask).sum(0).max() + maxW = (~mask).sum(1).max() + img = tensor[:, :maxH, :maxW] + return img + + def to_img_list(self): + """remove the padding and convert to img list + + Returns: + [type]: [description] + """ + if self.tensors.dim() == 3: + return self.to_img_list_single(self.tensors, self.mask) + else: + res = [] + for i in range(self.tensors.shape[0]): + tensor_i = self.tensors[i] + mask_i = self.mask[i] + res.append(self.to_img_list_single(tensor_i, mask_i)) + return res + + @property + def device(self): + return self.tensors.device + + def decompose(self): + return self.tensors, self.mask + + def __repr__(self): + return str(self.tensors) + + @property + def shape(self): + return {"tensors.shape": self.tensors.shape, "mask.shape": self.mask.shape} + + +def nested_tensor_from_tensor_list(tensor_list: List[Tensor]): + # TODO make this more general + if tensor_list[0].ndim == 3: + if torchvision._is_tracing(): + # nested_tensor_from_tensor_list() does not export well to ONNX + # call _onnx_nested_tensor_from_tensor_list() instead + return _onnx_nested_tensor_from_tensor_list(tensor_list) + + # TODO make it support different-sized images + max_size = _max_by_axis([list(img.shape) for img in tensor_list]) + # min_size = tuple(min(s) for s in zip(*[img.shape for img in tensor_list])) + batch_shape = [len(tensor_list)] + max_size + b, c, h, w = batch_shape + dtype = tensor_list[0].dtype + device = tensor_list[0].device + tensor = torch.zeros(batch_shape, dtype=dtype, device=device) + mask = torch.ones((b, h, w), dtype=torch.bool, device=device) + for img, pad_img, m in zip(tensor_list, tensor, mask): + pad_img[: img.shape[0], : img.shape[1], : img.shape[2]].copy_(img) + m[: img.shape[1], : img.shape[2]] = False + else: + raise ValueError("not supported") + return NestedTensor(tensor, mask) + + +# _onnx_nested_tensor_from_tensor_list() is an implementation of +# nested_tensor_from_tensor_list() that is supported by ONNX tracing. +@torch.jit.unused +def _onnx_nested_tensor_from_tensor_list(tensor_list: List[Tensor]) -> NestedTensor: + max_size = [] + for i in range(tensor_list[0].dim()): + max_size_i = torch.max( + torch.stack([img.shape[i] for img in tensor_list]).to(torch.float32) + ).to(torch.int64) + max_size.append(max_size_i) + max_size = tuple(max_size) + + # work around for + # pad_img[: img.shape[0], : img.shape[1], : img.shape[2]].copy_(img) + # m[: img.shape[1], :img.shape[2]] = False + # which is not yet supported in onnx + padded_imgs = [] + padded_masks = [] + for img in tensor_list: + padding = [(s1 - s2) for s1, s2 in zip(max_size, tuple(img.shape))] + padded_img = torch.nn.functional.pad(img, (0, padding[2], 0, padding[1], 0, padding[0])) + padded_imgs.append(padded_img) + + m = torch.zeros_like(img[0], dtype=torch.int, device=img.device) + padded_mask = torch.nn.functional.pad(m, (0, padding[2], 0, padding[1]), "constant", 1) + padded_masks.append(padded_mask.to(torch.bool)) + + tensor = torch.stack(padded_imgs) + mask = torch.stack(padded_masks) + + return NestedTensor(tensor, mask=mask) + + +def setup_for_distributed(is_master): + """ + This function disables printing when not in master process + """ + import builtins as __builtin__ + + builtin_print = __builtin__.print + + def print(*args, **kwargs): + force = kwargs.pop("force", False) + if is_master or force: + builtin_print(*args, **kwargs) + + __builtin__.print = print + + +def is_dist_avail_and_initialized(): + if not dist.is_available(): + return False + if not dist.is_initialized(): + return False + return True + + +def get_world_size(): + if not is_dist_avail_and_initialized(): + return 1 + return dist.get_world_size() + + +def get_rank(): + if not is_dist_avail_and_initialized(): + return 0 + return dist.get_rank() + + +def is_main_process(): + return get_rank() == 0 + + +def save_on_master(*args, **kwargs): + if is_main_process(): + torch.save(*args, **kwargs) + + +def init_distributed_mode(args): + if "WORLD_SIZE" in os.environ and os.environ["WORLD_SIZE"] != "": # 'RANK' in os.environ and + args.rank = int(os.environ["RANK"]) + args.world_size = int(os.environ["WORLD_SIZE"]) + args.gpu = args.local_rank = int(os.environ["LOCAL_RANK"]) + + # launch by torch.distributed.launch + # Single node + # python -m torch.distributed.launch --nproc_per_node=8 main.py --world-size 1 --rank 0 ... + # Multi nodes + # python -m torch.distributed.launch --nproc_per_node=8 main.py --world-size 2 --rank 0 --dist-url 'tcp://IP_OF_NODE0:FREEPORT' ... + # python -m torch.distributed.launch --nproc_per_node=8 main.py --world-size 2 --rank 1 --dist-url 'tcp://IP_OF_NODE0:FREEPORT' ... + # args.rank = int(os.environ.get('OMPI_COMM_WORLD_RANK')) + # local_world_size = int(os.environ['GPU_PER_NODE_COUNT']) + # args.world_size = args.world_size * local_world_size + # args.gpu = args.local_rank = int(os.environ['LOCAL_RANK']) + # args.rank = args.rank * local_world_size + args.local_rank + print( + "world size: {}, rank: {}, local rank: {}".format( + args.world_size, args.rank, args.local_rank + ) + ) + print(json.dumps(dict(os.environ), indent=2)) + elif "SLURM_PROCID" in os.environ: + args.rank = int(os.environ["SLURM_PROCID"]) + args.gpu = args.local_rank = int(os.environ["SLURM_LOCALID"]) + args.world_size = int(os.environ["SLURM_NPROCS"]) + + print( + "world size: {}, world rank: {}, local rank: {}, device_count: {}".format( + args.world_size, args.rank, args.local_rank, torch.cuda.device_count() + ) + ) + else: + print("Not using distributed mode") + args.distributed = False + args.world_size = 1 + args.rank = 0 + args.local_rank = 0 + return + + print("world_size:{} rank:{} local_rank:{}".format(args.world_size, args.rank, args.local_rank)) + args.distributed = True + torch.cuda.set_device(args.local_rank) + args.dist_backend = "nccl" + print("| distributed init (rank {}): {}".format(args.rank, args.dist_url), flush=True) + + torch.distributed.init_process_group( + backend=args.dist_backend, + world_size=args.world_size, + rank=args.rank, + init_method=args.dist_url, + ) + + print("Before torch.distributed.barrier()") + torch.distributed.barrier() + print("End torch.distributed.barrier()") + setup_for_distributed(args.rank == 0) + + +@torch.no_grad() +def accuracy(output, target, topk=(1,)): + """Computes the precision@k for the specified values of k""" + if target.numel() == 0: + return [torch.zeros([], device=output.device)] + maxk = max(topk) + batch_size = target.size(0) + + _, pred = output.topk(maxk, 1, True, True) + pred = pred.t() + correct = pred.eq(target.view(1, -1).expand_as(pred)) + + res = [] + for k in topk: + correct_k = correct[:k].view(-1).float().sum(0) + res.append(correct_k.mul_(100.0 / batch_size)) + return res + + +@torch.no_grad() +def accuracy_onehot(pred, gt): + """_summary_ + + Args: + pred (_type_): n, c + gt (_type_): n, c + """ + tp = ((pred - gt).abs().sum(-1) < 1e-4).float().sum() + acc = tp / gt.shape[0] * 100 + return acc + + +def interpolate(input, size=None, scale_factor=None, mode="nearest", align_corners=None): + # type: (Tensor, Optional[List[int]], Optional[float], str, Optional[bool]) -> Tensor + """ + Equivalent to nn.functional.interpolate, but with support for empty batch sizes. + This will eventually be supported natively by PyTorch, and this + class can go away. + """ + if __torchvision_need_compat_flag < 0.7: + if input.numel() > 0: + return torch.nn.functional.interpolate(input, size, scale_factor, mode, align_corners) + + output_shape = _output_size(2, input, size, scale_factor) + output_shape = list(input.shape[:-2]) + list(output_shape) + return _new_empty_tensor(input, output_shape) + else: + return torchvision.ops.misc.interpolate(input, size, scale_factor, mode, align_corners) + + +class color_sys: + def __init__(self, num_colors) -> None: + self.num_colors = num_colors + colors = [] + for i in np.arange(0.0, 360.0, 360.0 / num_colors): + hue = i / 360.0 + lightness = (50 + np.random.rand() * 10) / 100.0 + saturation = (90 + np.random.rand() * 10) / 100.0 + colors.append( + tuple([int(j * 255) for j in colorsys.hls_to_rgb(hue, lightness, saturation)]) + ) + self.colors = colors + + def __call__(self, idx): + return self.colors[idx] + + +def inverse_sigmoid(x, eps=1e-3): + x = x.clamp(min=0, max=1) + x1 = x.clamp(min=eps) + x2 = (1 - x).clamp(min=eps) + return torch.log(x1 / x2) + + +def clean_state_dict(state_dict): + new_state_dict = OrderedDict() + for k, v in state_dict.items(): + if k[:7] == "module.": + k = k[7:] # remove `module.` + new_state_dict[k] = v + return new_state_dict diff --git a/groundingdino/util/slconfig.py b/groundingdino/util/slconfig.py new file mode 100644 index 0000000..0d84a4c --- /dev/null +++ b/groundingdino/util/slconfig.py @@ -0,0 +1,424 @@ +# ========================================================== +# Modified from mmcv +# ========================================================== +import ast +import os.path as osp +import shutil +import sys +import tempfile +from argparse import Action +from importlib import import_module + +from addict import Dict +from yapf.yapflib.yapf_api import FormatCode + +BASE_KEY = "_base_" +DELETE_KEY = "_delete_" +RESERVED_KEYS = ["filename", "text", "pretty_text", "get", "dump", "merge_from_dict"] + + +def check_file_exist(filename, msg_tmpl='file "{}" does not exist'): + if not osp.isfile(filename): + raise FileNotFoundError(msg_tmpl.format(filename)) + + +class ConfigDict(Dict): + def __missing__(self, name): + raise KeyError(name) + + def __getattr__(self, name): + try: + value = super(ConfigDict, self).__getattr__(name) + except KeyError: + ex = AttributeError(f"'{self.__class__.__name__}' object has no " f"attribute '{name}'") + except Exception as e: + ex = e + else: + return value + raise ex + + +class SLConfig(object): + """ + config files. + only support .py file as config now. + + ref: mmcv.utils.config + + Example: + >>> cfg = Config(dict(a=1, b=dict(b1=[0, 1]))) + >>> cfg.a + 1 + >>> cfg.b + {'b1': [0, 1]} + >>> cfg.b.b1 + [0, 1] + >>> cfg = Config.fromfile('tests/data/config/a.py') + >>> cfg.filename + "/home/kchen/projects/mmcv/tests/data/config/a.py" + >>> cfg.item4 + 'test' + >>> cfg + "Config [path: /home/kchen/projects/mmcv/tests/data/config/a.py]: " + "{'item1': [1, 2], 'item2': {'a': 0}, 'item3': True, 'item4': 'test'}" + """ + + @staticmethod + def _validate_py_syntax(filename): + with open(filename) as f: + content = f.read() + try: + ast.parse(content) + except SyntaxError: + raise SyntaxError("There are syntax errors in config " f"file {filename}") + + @staticmethod + def _file2dict(filename): + filename = osp.abspath(osp.expanduser(filename)) + check_file_exist(filename) + if filename.lower().endswith(".py"): + with tempfile.TemporaryDirectory() as temp_config_dir: + temp_config_file = tempfile.NamedTemporaryFile(dir=temp_config_dir, suffix=".py") + temp_config_name = osp.basename(temp_config_file.name) + shutil.copyfile(filename, osp.join(temp_config_dir, temp_config_name)) + temp_module_name = osp.splitext(temp_config_name)[0] + sys.path.insert(0, temp_config_dir) + SLConfig._validate_py_syntax(filename) + mod = import_module(temp_module_name) + sys.path.pop(0) + cfg_dict = { + name: value for name, value in mod.__dict__.items() if not name.startswith("__") + } + # delete imported module + del sys.modules[temp_module_name] + # close temp file + temp_config_file.close() + elif filename.lower().endswith((".yml", ".yaml", ".json")): + from .slio import slload + + cfg_dict = slload(filename) + else: + raise IOError("Only py/yml/yaml/json type are supported now!") + + cfg_text = filename + "\n" + with open(filename, "r") as f: + cfg_text += f.read() + + # parse the base file + if BASE_KEY in cfg_dict: + cfg_dir = osp.dirname(filename) + base_filename = cfg_dict.pop(BASE_KEY) + base_filename = base_filename if isinstance(base_filename, list) else [base_filename] + + cfg_dict_list = list() + cfg_text_list = list() + for f in base_filename: + _cfg_dict, _cfg_text = SLConfig._file2dict(osp.join(cfg_dir, f)) + cfg_dict_list.append(_cfg_dict) + cfg_text_list.append(_cfg_text) + + base_cfg_dict = dict() + for c in cfg_dict_list: + if len(base_cfg_dict.keys() & c.keys()) > 0: + raise KeyError("Duplicate key is not allowed among bases") + # TODO Allow the duplicate key while warnning user + base_cfg_dict.update(c) + + base_cfg_dict = SLConfig._merge_a_into_b(cfg_dict, base_cfg_dict) + cfg_dict = base_cfg_dict + + # merge cfg_text + cfg_text_list.append(cfg_text) + cfg_text = "\n".join(cfg_text_list) + + return cfg_dict, cfg_text + + @staticmethod + def _merge_a_into_b(a, b): + """merge dict `a` into dict `b` (non-inplace). + values in `a` will overwrite `b`. + copy first to avoid inplace modification + + Args: + a ([type]): [description] + b ([type]): [description] + + Returns: + [dict]: [description] + """ + # import ipdb; ipdb.set_trace() + if not isinstance(a, dict): + return a + + b = b.copy() + for k, v in a.items(): + if isinstance(v, dict) and k in b and not v.pop(DELETE_KEY, False): + + if not isinstance(b[k], dict) and not isinstance(b[k], list): + # if : + # import ipdb; ipdb.set_trace() + raise TypeError( + f"{k}={v} in child config cannot inherit from base " + f"because {k} is a dict in the child config but is of " + f"type {type(b[k])} in base config. You may set " + f"`{DELETE_KEY}=True` to ignore the base config" + ) + b[k] = SLConfig._merge_a_into_b(v, b[k]) + elif isinstance(b, list): + try: + _ = int(k) + except: + raise TypeError( + f"b is a list, " f"index {k} should be an int when input but {type(k)}" + ) + b[int(k)] = SLConfig._merge_a_into_b(v, b[int(k)]) + else: + b[k] = v + + return b + + @staticmethod + def fromfile(filename): + cfg_dict, cfg_text = SLConfig._file2dict(filename) + return SLConfig(cfg_dict, cfg_text=cfg_text, filename=filename) + + def __init__(self, cfg_dict=None, cfg_text=None, filename=None): + if cfg_dict is None: + cfg_dict = dict() + elif not isinstance(cfg_dict, dict): + raise TypeError("cfg_dict must be a dict, but " f"got {type(cfg_dict)}") + for key in cfg_dict: + if key in RESERVED_KEYS: + raise KeyError(f"{key} is reserved for config file") + + super(SLConfig, self).__setattr__("_cfg_dict", ConfigDict(cfg_dict)) + super(SLConfig, self).__setattr__("_filename", filename) + if cfg_text: + text = cfg_text + elif filename: + with open(filename, "r") as f: + text = f.read() + else: + text = "" + super(SLConfig, self).__setattr__("_text", text) + + @property + def filename(self): + return self._filename + + @property + def text(self): + return self._text + + @property + def pretty_text(self): + + indent = 4 + + def _indent(s_, num_spaces): + s = s_.split("\n") + if len(s) == 1: + return s_ + first = s.pop(0) + s = [(num_spaces * " ") + line for line in s] + s = "\n".join(s) + s = first + "\n" + s + return s + + def _format_basic_types(k, v, use_mapping=False): + if isinstance(v, str): + v_str = f"'{v}'" + else: + v_str = str(v) + + if use_mapping: + k_str = f"'{k}'" if isinstance(k, str) else str(k) + attr_str = f"{k_str}: {v_str}" + else: + attr_str = f"{str(k)}={v_str}" + attr_str = _indent(attr_str, indent) + + return attr_str + + def _format_list(k, v, use_mapping=False): + # check if all items in the list are dict + if all(isinstance(_, dict) for _ in v): + v_str = "[\n" + v_str += "\n".join( + f"dict({_indent(_format_dict(v_), indent)})," for v_ in v + ).rstrip(",") + if use_mapping: + k_str = f"'{k}'" if isinstance(k, str) else str(k) + attr_str = f"{k_str}: {v_str}" + else: + attr_str = f"{str(k)}={v_str}" + attr_str = _indent(attr_str, indent) + "]" + else: + attr_str = _format_basic_types(k, v, use_mapping) + return attr_str + + def _contain_invalid_identifier(dict_str): + contain_invalid_identifier = False + for key_name in dict_str: + contain_invalid_identifier |= not str(key_name).isidentifier() + return contain_invalid_identifier + + def _format_dict(input_dict, outest_level=False): + r = "" + s = [] + + use_mapping = _contain_invalid_identifier(input_dict) + if use_mapping: + r += "{" + for idx, (k, v) in enumerate(input_dict.items()): + is_last = idx >= len(input_dict) - 1 + end = "" if outest_level or is_last else "," + if isinstance(v, dict): + v_str = "\n" + _format_dict(v) + if use_mapping: + k_str = f"'{k}'" if isinstance(k, str) else str(k) + attr_str = f"{k_str}: dict({v_str}" + else: + attr_str = f"{str(k)}=dict({v_str}" + attr_str = _indent(attr_str, indent) + ")" + end + elif isinstance(v, list): + attr_str = _format_list(k, v, use_mapping) + end + else: + attr_str = _format_basic_types(k, v, use_mapping) + end + + s.append(attr_str) + r += "\n".join(s) + if use_mapping: + r += "}" + return r + + cfg_dict = self._cfg_dict.to_dict() + text = _format_dict(cfg_dict, outest_level=True) + # copied from setup.cfg + yapf_style = dict( + based_on_style="pep8", + blank_line_before_nested_class_or_def=True, + split_before_expression_after_opening_paren=True, + ) + text, _ = FormatCode(text, style_config=yapf_style, verify=True) + + return text + + def __repr__(self): + return f"Config (path: {self.filename}): {self._cfg_dict.__repr__()}" + + def __len__(self): + return len(self._cfg_dict) + + def __getattr__(self, name): + # # debug + # print('+'*15) + # print('name=%s' % name) + # print("addr:", id(self)) + # # print('type(self):', type(self)) + # print(self.__dict__) + # print('+'*15) + # if self.__dict__ == {}: + # raise ValueError + + return getattr(self._cfg_dict, name) + + def __getitem__(self, name): + return self._cfg_dict.__getitem__(name) + + def __setattr__(self, name, value): + if isinstance(value, dict): + value = ConfigDict(value) + self._cfg_dict.__setattr__(name, value) + + def __setitem__(self, name, value): + if isinstance(value, dict): + value = ConfigDict(value) + self._cfg_dict.__setitem__(name, value) + + def __iter__(self): + return iter(self._cfg_dict) + + def dump(self, file=None): + # import ipdb; ipdb.set_trace() + if file is None: + return self.pretty_text + else: + with open(file, "w") as f: + f.write(self.pretty_text) + + def merge_from_dict(self, options): + """Merge list into cfg_dict + + Merge the dict parsed by MultipleKVAction into this cfg. + + Examples: + >>> options = {'model.backbone.depth': 50, + ... 'model.backbone.with_cp':True} + >>> cfg = Config(dict(model=dict(backbone=dict(type='ResNet')))) + >>> cfg.merge_from_dict(options) + >>> cfg_dict = super(Config, self).__getattribute__('_cfg_dict') + >>> assert cfg_dict == dict( + ... model=dict(backbone=dict(depth=50, with_cp=True))) + + Args: + options (dict): dict of configs to merge from. + """ + option_cfg_dict = {} + for full_key, v in options.items(): + d = option_cfg_dict + key_list = full_key.split(".") + for subkey in key_list[:-1]: + d.setdefault(subkey, ConfigDict()) + d = d[subkey] + subkey = key_list[-1] + d[subkey] = v + + cfg_dict = super(SLConfig, self).__getattribute__("_cfg_dict") + super(SLConfig, self).__setattr__( + "_cfg_dict", SLConfig._merge_a_into_b(option_cfg_dict, cfg_dict) + ) + + # for multiprocess + def __setstate__(self, state): + self.__init__(state) + + def copy(self): + return SLConfig(self._cfg_dict.copy()) + + def deepcopy(self): + return SLConfig(self._cfg_dict.deepcopy()) + + +class DictAction(Action): + """ + argparse action to split an argument into KEY=VALUE form + on the first = and append to a dictionary. List options should + be passed as comma separated values, i.e KEY=V1,V2,V3 + """ + + @staticmethod + def _parse_int_float_bool(val): + try: + return int(val) + except ValueError: + pass + try: + return float(val) + except ValueError: + pass + if val.lower() in ["true", "false"]: + return True if val.lower() == "true" else False + if val.lower() in ["none", "null"]: + return None + return val + + def __call__(self, parser, namespace, values, option_string=None): + options = {} + for kv in values: + key, val = kv.split("=", maxsplit=1) + val = [self._parse_int_float_bool(v) for v in val.split(",")] + if len(val) == 1: + val = val[0] + options[key] = val + setattr(namespace, self.dest, options) diff --git a/groundingdino/util/slio.py b/groundingdino/util/slio.py new file mode 100644 index 0000000..72c1f0f --- /dev/null +++ b/groundingdino/util/slio.py @@ -0,0 +1,177 @@ +# ========================================================== +# Modified from mmcv +# ========================================================== + +import json +import pickle +from abc import ABCMeta, abstractmethod +from pathlib import Path + +import yaml + +try: + from yaml import CLoader as Loader, CDumper as Dumper +except ImportError: + from yaml import Loader, Dumper + + +# =========================== +# Rigister handler +# =========================== + + +class BaseFileHandler(metaclass=ABCMeta): + @abstractmethod + def load_from_fileobj(self, file, **kwargs): + pass + + @abstractmethod + def dump_to_fileobj(self, obj, file, **kwargs): + pass + + @abstractmethod + def dump_to_str(self, obj, **kwargs): + pass + + def load_from_path(self, filepath, mode="r", **kwargs): + with open(filepath, mode) as f: + return self.load_from_fileobj(f, **kwargs) + + def dump_to_path(self, obj, filepath, mode="w", **kwargs): + with open(filepath, mode) as f: + self.dump_to_fileobj(obj, f, **kwargs) + + +class JsonHandler(BaseFileHandler): + def load_from_fileobj(self, file): + return json.load(file) + + def dump_to_fileobj(self, obj, file, **kwargs): + json.dump(obj, file, **kwargs) + + def dump_to_str(self, obj, **kwargs): + return json.dumps(obj, **kwargs) + + +class PickleHandler(BaseFileHandler): + def load_from_fileobj(self, file, **kwargs): + return pickle.load(file, **kwargs) + + def load_from_path(self, filepath, **kwargs): + return super(PickleHandler, self).load_from_path(filepath, mode="rb", **kwargs) + + def dump_to_str(self, obj, **kwargs): + kwargs.setdefault("protocol", 2) + return pickle.dumps(obj, **kwargs) + + def dump_to_fileobj(self, obj, file, **kwargs): + kwargs.setdefault("protocol", 2) + pickle.dump(obj, file, **kwargs) + + def dump_to_path(self, obj, filepath, **kwargs): + super(PickleHandler, self).dump_to_path(obj, filepath, mode="wb", **kwargs) + + +class YamlHandler(BaseFileHandler): + def load_from_fileobj(self, file, **kwargs): + kwargs.setdefault("Loader", Loader) + return yaml.load(file, **kwargs) + + def dump_to_fileobj(self, obj, file, **kwargs): + kwargs.setdefault("Dumper", Dumper) + yaml.dump(obj, file, **kwargs) + + def dump_to_str(self, obj, **kwargs): + kwargs.setdefault("Dumper", Dumper) + return yaml.dump(obj, **kwargs) + + +file_handlers = { + "json": JsonHandler(), + "yaml": YamlHandler(), + "yml": YamlHandler(), + "pickle": PickleHandler(), + "pkl": PickleHandler(), +} + +# =========================== +# load and dump +# =========================== + + +def is_str(x): + """Whether the input is an string instance. + + Note: This method is deprecated since python 2 is no longer supported. + """ + return isinstance(x, str) + + +def slload(file, file_format=None, **kwargs): + """Load data from json/yaml/pickle files. + + This method provides a unified api for loading data from serialized files. + + Args: + file (str or :obj:`Path` or file-like object): Filename or a file-like + object. + file_format (str, optional): If not specified, the file format will be + inferred from the file extension, otherwise use the specified one. + Currently supported formats include "json", "yaml/yml" and + "pickle/pkl". + + Returns: + The content from the file. + """ + if isinstance(file, Path): + file = str(file) + if file_format is None and is_str(file): + file_format = file.split(".")[-1] + if file_format not in file_handlers: + raise TypeError(f"Unsupported format: {file_format}") + + handler = file_handlers[file_format] + if is_str(file): + obj = handler.load_from_path(file, **kwargs) + elif hasattr(file, "read"): + obj = handler.load_from_fileobj(file, **kwargs) + else: + raise TypeError('"file" must be a filepath str or a file-object') + return obj + + +def sldump(obj, file=None, file_format=None, **kwargs): + """Dump data to json/yaml/pickle strings or files. + + This method provides a unified api for dumping data as strings or to files, + and also supports custom arguments for each file format. + + Args: + obj (any): The python object to be dumped. + file (str or :obj:`Path` or file-like object, optional): If not + specified, then the object is dump to a str, otherwise to a file + specified by the filename or file-like object. + file_format (str, optional): Same as :func:`load`. + + Returns: + bool: True for success, False otherwise. + """ + if isinstance(file, Path): + file = str(file) + if file_format is None: + if is_str(file): + file_format = file.split(".")[-1] + elif file is None: + raise ValueError("file_format must be specified since file is None") + if file_format not in file_handlers: + raise TypeError(f"Unsupported format: {file_format}") + + handler = file_handlers[file_format] + if file is None: + return handler.dump_to_str(obj, **kwargs) + elif is_str(file): + handler.dump_to_path(obj, file, **kwargs) + elif hasattr(file, "write"): + handler.dump_to_fileobj(obj, file, **kwargs) + else: + raise TypeError('"file" must be a filename str or a file-object') diff --git a/groundingdino/util/time_counter.py b/groundingdino/util/time_counter.py new file mode 100644 index 0000000..0aedb2e --- /dev/null +++ b/groundingdino/util/time_counter.py @@ -0,0 +1,62 @@ +import json +import time + + +class TimeCounter: + def __init__(self) -> None: + pass + + def clear(self): + self.timedict = {} + self.basetime = time.perf_counter() + + def timeit(self, name): + nowtime = time.perf_counter() - self.basetime + self.timedict[name] = nowtime + self.basetime = time.perf_counter() + + +class TimeHolder: + def __init__(self) -> None: + self.timedict = {} + + def update(self, _timedict: dict): + for k, v in _timedict.items(): + if k not in self.timedict: + self.timedict[k] = AverageMeter(name=k, val_only=True) + self.timedict[k].update(val=v) + + def final_res(self): + return {k: v.avg for k, v in self.timedict.items()} + + def __str__(self): + return json.dumps(self.final_res(), indent=2) + + +class AverageMeter(object): + """Computes and stores the average and current value""" + + def __init__(self, name, fmt=":f", val_only=False): + self.name = name + self.fmt = fmt + self.val_only = val_only + self.reset() + + def reset(self): + self.val = 0 + self.avg = 0 + self.sum = 0 + self.count = 0 + + def update(self, val, n=1): + self.val = val + self.sum += val * n + self.count += n + self.avg = self.sum / self.count + + def __str__(self): + if self.val_only: + fmtstr = "{name} {val" + self.fmt + "}" + else: + fmtstr = "{name} {val" + self.fmt + "} ({avg" + self.fmt + "})" + return fmtstr.format(**self.__dict__) diff --git a/groundingdino/util/utils.py b/groundingdino/util/utils.py new file mode 100644 index 0000000..c0d4268 --- /dev/null +++ b/groundingdino/util/utils.py @@ -0,0 +1,621 @@ +import argparse +import json +import warnings +from collections import OrderedDict +from copy import deepcopy +from typing import Any, Dict, List + +import numpy as np +import torch + +from groundingdino.util.slconfig import SLConfig + + +def slprint(x, name="x"): + if isinstance(x, (torch.Tensor, np.ndarray)): + print(f"{name}.shape:", x.shape) + elif isinstance(x, (tuple, list)): + print("type x:", type(x)) + for i in range(min(10, len(x))): + slprint(x[i], f"{name}[{i}]") + elif isinstance(x, dict): + for k, v in x.items(): + slprint(v, f"{name}[{k}]") + else: + print(f"{name}.type:", type(x)) + + +def clean_state_dict(state_dict): + new_state_dict = OrderedDict() + for k, v in state_dict.items(): + if k[:7] == "module.": + k = k[7:] # remove `module.` + new_state_dict[k] = v + return new_state_dict + + +def renorm( + img: torch.FloatTensor, mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] +) -> torch.FloatTensor: + # img: tensor(3,H,W) or tensor(B,3,H,W) + # return: same as img + assert img.dim() == 3 or img.dim() == 4, "img.dim() should be 3 or 4 but %d" % img.dim() + if img.dim() == 3: + assert img.size(0) == 3, 'img.size(0) shoule be 3 but "%d". (%s)' % ( + img.size(0), + str(img.size()), + ) + img_perm = img.permute(1, 2, 0) + mean = torch.Tensor(mean) + std = torch.Tensor(std) + img_res = img_perm * std + mean + return img_res.permute(2, 0, 1) + else: # img.dim() == 4 + assert img.size(1) == 3, 'img.size(1) shoule be 3 but "%d". (%s)' % ( + img.size(1), + str(img.size()), + ) + img_perm = img.permute(0, 2, 3, 1) + mean = torch.Tensor(mean) + std = torch.Tensor(std) + img_res = img_perm * std + mean + return img_res.permute(0, 3, 1, 2) + + +class CocoClassMapper: + def __init__(self) -> None: + self.category_map_str = { + "1": 1, + "2": 2, + "3": 3, + "4": 4, + "5": 5, + "6": 6, + "7": 7, + "8": 8, + "9": 9, + "10": 10, + "11": 11, + "13": 12, + "14": 13, + "15": 14, + "16": 15, + "17": 16, + "18": 17, + "19": 18, + "20": 19, + "21": 20, + "22": 21, + "23": 22, + "24": 23, + "25": 24, + "27": 25, + "28": 26, + "31": 27, + "32": 28, + "33": 29, + "34": 30, + "35": 31, + "36": 32, + "37": 33, + "38": 34, + "39": 35, + "40": 36, + "41": 37, + "42": 38, + "43": 39, + "44": 40, + "46": 41, + "47": 42, + "48": 43, + "49": 44, + "50": 45, + "51": 46, + "52": 47, + "53": 48, + "54": 49, + "55": 50, + "56": 51, + "57": 52, + "58": 53, + "59": 54, + "60": 55, + "61": 56, + "62": 57, + "63": 58, + "64": 59, + "65": 60, + "67": 61, + "70": 62, + "72": 63, + "73": 64, + "74": 65, + "75": 66, + "76": 67, + "77": 68, + "78": 69, + "79": 70, + "80": 71, + "81": 72, + "82": 73, + "84": 74, + "85": 75, + "86": 76, + "87": 77, + "88": 78, + "89": 79, + "90": 80, + } + self.origin2compact_mapper = {int(k): v - 1 for k, v in self.category_map_str.items()} + self.compact2origin_mapper = {int(v - 1): int(k) for k, v in self.category_map_str.items()} + + def origin2compact(self, idx): + return self.origin2compact_mapper[int(idx)] + + def compact2origin(self, idx): + return self.compact2origin_mapper[int(idx)] + + +def to_device(item, device): + if isinstance(item, torch.Tensor): + return item.to(device) + elif isinstance(item, list): + return [to_device(i, device) for i in item] + elif isinstance(item, dict): + return {k: to_device(v, device) for k, v in item.items()} + else: + raise NotImplementedError( + "Call Shilong if you use other containers! type: {}".format(type(item)) + ) + + +# +def get_gaussian_mean(x, axis, other_axis, softmax=True): + """ + + Args: + x (float): Input images(BxCxHxW) + axis (int): The index for weighted mean + other_axis (int): The other index + + Returns: weighted index for axis, BxC + + """ + mat2line = torch.sum(x, axis=other_axis) + # mat2line = mat2line / mat2line.mean() * 10 + if softmax: + u = torch.softmax(mat2line, axis=2) + else: + u = mat2line / (mat2line.sum(2, keepdim=True) + 1e-6) + size = x.shape[axis] + ind = torch.linspace(0, 1, size).to(x.device) + batch = x.shape[0] + channel = x.shape[1] + index = ind.repeat([batch, channel, 1]) + mean_position = torch.sum(index * u, dim=2) + return mean_position + + +def get_expected_points_from_map(hm, softmax=True): + """get_gaussian_map_from_points + B,C,H,W -> B,N,2 float(0, 1) float(0, 1) + softargmax function + + Args: + hm (float): Input images(BxCxHxW) + + Returns: + weighted index for axis, BxCx2. float between 0 and 1. + + """ + # hm = 10*hm + B, C, H, W = hm.shape + y_mean = get_gaussian_mean(hm, 2, 3, softmax=softmax) # B,C + x_mean = get_gaussian_mean(hm, 3, 2, softmax=softmax) # B,C + # return torch.cat((x_mean.unsqueeze(-1), y_mean.unsqueeze(-1)), 2) + return torch.stack([x_mean, y_mean], dim=2) + + +# Positional encoding (section 5.1) +# borrow from nerf +class Embedder: + def __init__(self, **kwargs): + self.kwargs = kwargs + self.create_embedding_fn() + + def create_embedding_fn(self): + embed_fns = [] + d = self.kwargs["input_dims"] + out_dim = 0 + if self.kwargs["include_input"]: + embed_fns.append(lambda x: x) + out_dim += d + + max_freq = self.kwargs["max_freq_log2"] + N_freqs = self.kwargs["num_freqs"] + + if self.kwargs["log_sampling"]: + freq_bands = 2.0 ** torch.linspace(0.0, max_freq, steps=N_freqs) + else: + freq_bands = torch.linspace(2.0**0.0, 2.0**max_freq, steps=N_freqs) + + for freq in freq_bands: + for p_fn in self.kwargs["periodic_fns"]: + embed_fns.append(lambda x, p_fn=p_fn, freq=freq: p_fn(x * freq)) + out_dim += d + + self.embed_fns = embed_fns + self.out_dim = out_dim + + def embed(self, inputs): + return torch.cat([fn(inputs) for fn in self.embed_fns], -1) + + +def get_embedder(multires, i=0): + import torch.nn as nn + + if i == -1: + return nn.Identity(), 3 + + embed_kwargs = { + "include_input": True, + "input_dims": 3, + "max_freq_log2": multires - 1, + "num_freqs": multires, + "log_sampling": True, + "periodic_fns": [torch.sin, torch.cos], + } + + embedder_obj = Embedder(**embed_kwargs) + embed = lambda x, eo=embedder_obj: eo.embed(x) + return embed, embedder_obj.out_dim + + +class APOPMeter: + def __init__(self) -> None: + self.tp = 0 + self.fp = 0 + self.tn = 0 + self.fn = 0 + + def update(self, pred, gt): + """ + Input: + pred, gt: Tensor() + """ + assert pred.shape == gt.shape + self.tp += torch.logical_and(pred == 1, gt == 1).sum().item() + self.fp += torch.logical_and(pred == 1, gt == 0).sum().item() + self.tn += torch.logical_and(pred == 0, gt == 0).sum().item() + self.tn += torch.logical_and(pred == 1, gt == 0).sum().item() + + def update_cm(self, tp, fp, tn, fn): + self.tp += tp + self.fp += fp + self.tn += tn + self.tn += fn + + +def inverse_sigmoid(x, eps=1e-5): + x = x.clamp(min=0, max=1) + x1 = x.clamp(min=eps) + x2 = (1 - x).clamp(min=eps) + return torch.log(x1 / x2) + + +def get_raw_dict(args): + """ + return the dicf contained in args. + + e.g: + >>> with open(path, 'w') as f: + json.dump(get_raw_dict(args), f, indent=2) + """ + if isinstance(args, argparse.Namespace): + return vars(args) + elif isinstance(args, dict): + return args + elif isinstance(args, SLConfig): + return args._cfg_dict + else: + raise NotImplementedError("Unknown type {}".format(type(args))) + + +def stat_tensors(tensor): + assert tensor.dim() == 1 + tensor_sm = tensor.softmax(0) + entropy = (tensor_sm * torch.log(tensor_sm + 1e-9)).sum() + + return { + "max": tensor.max(), + "min": tensor.min(), + "mean": tensor.mean(), + "var": tensor.var(), + "std": tensor.var() ** 0.5, + "entropy": entropy, + } + + +class NiceRepr: + """Inherit from this class and define ``__nice__`` to "nicely" print your + objects. + + Defines ``__str__`` and ``__repr__`` in terms of ``__nice__`` function + Classes that inherit from :class:`NiceRepr` should redefine ``__nice__``. + If the inheriting class has a ``__len__``, method then the default + ``__nice__`` method will return its length. + + Example: + >>> class Foo(NiceRepr): + ... def __nice__(self): + ... return 'info' + >>> foo = Foo() + >>> assert str(foo) == '' + >>> assert repr(foo).startswith('>> class Bar(NiceRepr): + ... pass + >>> bar = Bar() + >>> import pytest + >>> with pytest.warns(None) as record: + >>> assert 'object at' in str(bar) + >>> assert 'object at' in repr(bar) + + Example: + >>> class Baz(NiceRepr): + ... def __len__(self): + ... return 5 + >>> baz = Baz() + >>> assert str(baz) == '' + """ + + def __nice__(self): + """str: a "nice" summary string describing this module""" + if hasattr(self, "__len__"): + # It is a common pattern for objects to use __len__ in __nice__ + # As a convenience we define a default __nice__ for these objects + return str(len(self)) + else: + # In all other cases force the subclass to overload __nice__ + raise NotImplementedError(f"Define the __nice__ method for {self.__class__!r}") + + def __repr__(self): + """str: the string of the module""" + try: + nice = self.__nice__() + classname = self.__class__.__name__ + return f"<{classname}({nice}) at {hex(id(self))}>" + except NotImplementedError as ex: + warnings.warn(str(ex), category=RuntimeWarning) + return object.__repr__(self) + + def __str__(self): + """str: the string of the module""" + try: + classname = self.__class__.__name__ + nice = self.__nice__() + return f"<{classname}({nice})>" + except NotImplementedError as ex: + warnings.warn(str(ex), category=RuntimeWarning) + return object.__repr__(self) + + +def ensure_rng(rng=None): + """Coerces input into a random number generator. + + If the input is None, then a global random state is returned. + + If the input is a numeric value, then that is used as a seed to construct a + random state. Otherwise the input is returned as-is. + + Adapted from [1]_. + + Args: + rng (int | numpy.random.RandomState | None): + if None, then defaults to the global rng. Otherwise this can be an + integer or a RandomState class + Returns: + (numpy.random.RandomState) : rng - + a numpy random number generator + + References: + .. [1] https://gitlab.kitware.com/computer-vision/kwarray/blob/master/kwarray/util_random.py#L270 # noqa: E501 + """ + + if rng is None: + rng = np.random.mtrand._rand + elif isinstance(rng, int): + rng = np.random.RandomState(rng) + else: + rng = rng + return rng + + +def random_boxes(num=1, scale=1, rng=None): + """Simple version of ``kwimage.Boxes.random`` + + Returns: + Tensor: shape (n, 4) in x1, y1, x2, y2 format. + + References: + https://gitlab.kitware.com/computer-vision/kwimage/blob/master/kwimage/structs/boxes.py#L1390 + + Example: + >>> num = 3 + >>> scale = 512 + >>> rng = 0 + >>> boxes = random_boxes(num, scale, rng) + >>> print(boxes) + tensor([[280.9925, 278.9802, 308.6148, 366.1769], + [216.9113, 330.6978, 224.0446, 456.5878], + [405.3632, 196.3221, 493.3953, 270.7942]]) + """ + rng = ensure_rng(rng) + + tlbr = rng.rand(num, 4).astype(np.float32) + + tl_x = np.minimum(tlbr[:, 0], tlbr[:, 2]) + tl_y = np.minimum(tlbr[:, 1], tlbr[:, 3]) + br_x = np.maximum(tlbr[:, 0], tlbr[:, 2]) + br_y = np.maximum(tlbr[:, 1], tlbr[:, 3]) + + tlbr[:, 0] = tl_x * scale + tlbr[:, 1] = tl_y * scale + tlbr[:, 2] = br_x * scale + tlbr[:, 3] = br_y * scale + + boxes = torch.from_numpy(tlbr) + return boxes + + +class ModelEma(torch.nn.Module): + def __init__(self, model, decay=0.9997, device=None): + super(ModelEma, self).__init__() + # make a copy of the model for accumulating moving average of weights + self.module = deepcopy(model) + self.module.eval() + + # import ipdb; ipdb.set_trace() + + self.decay = decay + self.device = device # perform ema on different device from model if set + if self.device is not None: + self.module.to(device=device) + + def _update(self, model, update_fn): + with torch.no_grad(): + for ema_v, model_v in zip( + self.module.state_dict().values(), model.state_dict().values() + ): + if self.device is not None: + model_v = model_v.to(device=self.device) + ema_v.copy_(update_fn(ema_v, model_v)) + + def update(self, model): + self._update(model, update_fn=lambda e, m: self.decay * e + (1.0 - self.decay) * m) + + def set(self, model): + self._update(model, update_fn=lambda e, m: m) + + +class BestMetricSingle: + def __init__(self, init_res=0.0, better="large") -> None: + self.init_res = init_res + self.best_res = init_res + self.best_ep = -1 + + self.better = better + assert better in ["large", "small"] + + def isbetter(self, new_res, old_res): + if self.better == "large": + return new_res > old_res + if self.better == "small": + return new_res < old_res + + def update(self, new_res, ep): + if self.isbetter(new_res, self.best_res): + self.best_res = new_res + self.best_ep = ep + return True + return False + + def __str__(self) -> str: + return "best_res: {}\t best_ep: {}".format(self.best_res, self.best_ep) + + def __repr__(self) -> str: + return self.__str__() + + def summary(self) -> dict: + return { + "best_res": self.best_res, + "best_ep": self.best_ep, + } + + +class BestMetricHolder: + def __init__(self, init_res=0.0, better="large", use_ema=False) -> None: + self.best_all = BestMetricSingle(init_res, better) + self.use_ema = use_ema + if use_ema: + self.best_ema = BestMetricSingle(init_res, better) + self.best_regular = BestMetricSingle(init_res, better) + + def update(self, new_res, epoch, is_ema=False): + """ + return if the results is the best. + """ + if not self.use_ema: + return self.best_all.update(new_res, epoch) + else: + if is_ema: + self.best_ema.update(new_res, epoch) + return self.best_all.update(new_res, epoch) + else: + self.best_regular.update(new_res, epoch) + return self.best_all.update(new_res, epoch) + + def summary(self): + if not self.use_ema: + return self.best_all.summary() + + res = {} + res.update({f"all_{k}": v for k, v in self.best_all.summary().items()}) + res.update({f"regular_{k}": v for k, v in self.best_regular.summary().items()}) + res.update({f"ema_{k}": v for k, v in self.best_ema.summary().items()}) + return res + + def __repr__(self) -> str: + return json.dumps(self.summary(), indent=2) + + def __str__(self) -> str: + return self.__repr__() + + +def targets_to(targets: List[Dict[str, Any]], device): + """Moves the target dicts to the given device.""" + excluded_keys = [ + "questionId", + "tokens_positive", + "strings_positive", + "tokens", + "dataset_name", + "sentence_id", + "original_img_id", + "nb_eval", + "task_id", + "original_id", + "token_span", + "caption", + "dataset_type", + ] + return [ + {k: v.to(device) if k not in excluded_keys else v for k, v in t.items()} for t in targets + ] + + +def get_phrases_from_posmap(posmap: torch.BoolTensor, tokenlized, caption: str): + assert isinstance(posmap, torch.Tensor), "posmap must be torch.Tensor" + if posmap.dim() == 1: + non_zero_idx = posmap.nonzero(as_tuple=True)[0].tolist() + words_list = caption.split() + + # build word idx list + words_idx_used_list = [] + for idx in non_zero_idx: + word_idx = tokenlized.token_to_word(idx) + if word_idx is not None: + words_idx_used_list.append(word_idx) + words_idx_used_list = set(words_idx_used_list) + + # build phrase + words_used_list = [] + for idx, word in enumerate(words_list): + if idx in words_idx_used_list: + words_used_list.append(word) + + sentence_res = " ".join(words_used_list) + return sentence_res + else: + raise NotImplementedError("posmap must be 1-dim") diff --git a/groundingdino/util/visualizer.py b/groundingdino/util/visualizer.py new file mode 100644 index 0000000..7a1b7b1 --- /dev/null +++ b/groundingdino/util/visualizer.py @@ -0,0 +1,318 @@ +# -*- coding: utf-8 -*- +""" +@File : visualizer.py +@Time : 2022/04/05 11:39:33 +@Author : Shilong Liu +@Contact : slongliu86@gmail.com +""" + +import datetime +import os + +import cv2 +import matplotlib.pyplot as plt +import numpy as np +import torch +from matplotlib import transforms +from matplotlib.collections import PatchCollection +from matplotlib.patches import Polygon +from pycocotools import mask as maskUtils + + +def renorm( + img: torch.FloatTensor, mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] +) -> torch.FloatTensor: + # img: tensor(3,H,W) or tensor(B,3,H,W) + # return: same as img + assert img.dim() == 3 or img.dim() == 4, "img.dim() should be 3 or 4 but %d" % img.dim() + if img.dim() == 3: + assert img.size(0) == 3, 'img.size(0) shoule be 3 but "%d". (%s)' % ( + img.size(0), + str(img.size()), + ) + img_perm = img.permute(1, 2, 0) + mean = torch.Tensor(mean) + std = torch.Tensor(std) + img_res = img_perm * std + mean + return img_res.permute(2, 0, 1) + else: # img.dim() == 4 + assert img.size(1) == 3, 'img.size(1) shoule be 3 but "%d". (%s)' % ( + img.size(1), + str(img.size()), + ) + img_perm = img.permute(0, 2, 3, 1) + mean = torch.Tensor(mean) + std = torch.Tensor(std) + img_res = img_perm * std + mean + return img_res.permute(0, 3, 1, 2) + + +class ColorMap: + def __init__(self, basergb=[255, 255, 0]): + self.basergb = np.array(basergb) + + def __call__(self, attnmap): + # attnmap: h, w. np.uint8. + # return: h, w, 4. np.uint8. + assert attnmap.dtype == np.uint8 + h, w = attnmap.shape + res = self.basergb.copy() + res = res[None][None].repeat(h, 0).repeat(w, 1) # h, w, 3 + attn1 = attnmap.copy()[..., None] # h, w, 1 + res = np.concatenate((res, attn1), axis=-1).astype(np.uint8) + return res + + +def rainbow_text(x, y, ls, lc, **kw): + """ + Take a list of strings ``ls`` and colors ``lc`` and place them next to each + other, with text ls[i] being shown in color lc[i]. + + This example shows how to do both vertical and horizontal text, and will + pass all keyword arguments to plt.text, so you can set the font size, + family, etc. + """ + t = plt.gca().transData + fig = plt.gcf() + plt.show() + + # horizontal version + for s, c in zip(ls, lc): + text = plt.text(x, y, " " + s + " ", color=c, transform=t, **kw) + text.draw(fig.canvas.get_renderer()) + ex = text.get_window_extent() + t = transforms.offset_copy(text._transform, x=ex.width, units="dots") + + # #vertical version + # for s,c in zip(ls,lc): + # text = plt.text(x,y," "+s+" ",color=c, transform=t, + # rotation=90,va='bottom',ha='center',**kw) + # text.draw(fig.canvas.get_renderer()) + # ex = text.get_window_extent() + # t = transforms.offset_copy(text._transform, y=ex.height, units='dots') + + +class COCOVisualizer: + def __init__(self, coco=None, tokenlizer=None) -> None: + self.coco = coco + + def visualize(self, img, tgt, caption=None, dpi=180, savedir="vis"): + """ + img: tensor(3, H, W) + tgt: make sure they are all on cpu. + must have items: 'image_id', 'boxes', 'size' + """ + plt.figure(dpi=dpi) + plt.rcParams["font.size"] = "5" + ax = plt.gca() + img = renorm(img).permute(1, 2, 0) + # if os.environ.get('IPDB_SHILONG_DEBUG', None) == 'INFO': + # import ipdb; ipdb.set_trace() + ax.imshow(img) + + self.addtgt(tgt) + + if tgt is None: + image_id = 0 + elif "image_id" not in tgt: + image_id = 0 + else: + image_id = tgt["image_id"] + + if caption is None: + savename = "{}/{}-{}.png".format( + savedir, int(image_id), str(datetime.datetime.now()).replace(" ", "-") + ) + else: + savename = "{}/{}-{}-{}.png".format( + savedir, caption, int(image_id), str(datetime.datetime.now()).replace(" ", "-") + ) + print("savename: {}".format(savename)) + os.makedirs(os.path.dirname(savename), exist_ok=True) + plt.savefig(savename) + plt.close() + + def addtgt(self, tgt): + """ """ + if tgt is None or not "boxes" in tgt: + ax = plt.gca() + + if "caption" in tgt: + ax.set_title(tgt["caption"], wrap=True) + + ax.set_axis_off() + return + + ax = plt.gca() + H, W = tgt["size"] + numbox = tgt["boxes"].shape[0] + + color = [] + polygons = [] + boxes = [] + for box in tgt["boxes"].cpu(): + unnormbbox = box * torch.Tensor([W, H, W, H]) + unnormbbox[:2] -= unnormbbox[2:] / 2 + [bbox_x, bbox_y, bbox_w, bbox_h] = unnormbbox.tolist() + boxes.append([bbox_x, bbox_y, bbox_w, bbox_h]) + poly = [ + [bbox_x, bbox_y], + [bbox_x, bbox_y + bbox_h], + [bbox_x + bbox_w, bbox_y + bbox_h], + [bbox_x + bbox_w, bbox_y], + ] + np_poly = np.array(poly).reshape((4, 2)) + polygons.append(Polygon(np_poly)) + c = (np.random.random((1, 3)) * 0.6 + 0.4).tolist()[0] + color.append(c) + + p = PatchCollection(polygons, facecolor=color, linewidths=0, alpha=0.1) + ax.add_collection(p) + p = PatchCollection(polygons, facecolor="none", edgecolors=color, linewidths=2) + ax.add_collection(p) + + if "strings_positive" in tgt and len(tgt["strings_positive"]) > 0: + assert ( + len(tgt["strings_positive"]) == numbox + ), f"{len(tgt['strings_positive'])} = {numbox}, " + for idx, strlist in enumerate(tgt["strings_positive"]): + cate_id = int(tgt["labels"][idx]) + _string = str(cate_id) + ":" + " ".join(strlist) + bbox_x, bbox_y, bbox_w, bbox_h = boxes[idx] + # ax.text(bbox_x, bbox_y, _string, color='black', bbox={'facecolor': 'yellow', 'alpha': 1.0, 'pad': 1}) + ax.text( + bbox_x, + bbox_y, + _string, + color="black", + bbox={"facecolor": color[idx], "alpha": 0.6, "pad": 1}, + ) + + if "box_label" in tgt: + assert len(tgt["box_label"]) == numbox, f"{len(tgt['box_label'])} = {numbox}, " + for idx, bl in enumerate(tgt["box_label"]): + _string = str(bl) + bbox_x, bbox_y, bbox_w, bbox_h = boxes[idx] + # ax.text(bbox_x, bbox_y, _string, color='black', bbox={'facecolor': 'yellow', 'alpha': 1.0, 'pad': 1}) + ax.text( + bbox_x, + bbox_y, + _string, + color="black", + bbox={"facecolor": color[idx], "alpha": 0.6, "pad": 1}, + ) + + if "caption" in tgt: + ax.set_title(tgt["caption"], wrap=True) + # plt.figure() + # rainbow_text(0.0,0.0,"all unicorns poop rainbows ! ! !".split(), + # ['red', 'orange', 'brown', 'green', 'blue', 'purple', 'black']) + + if "attn" in tgt: + # if os.environ.get('IPDB_SHILONG_DEBUG', None) == 'INFO': + # import ipdb; ipdb.set_trace() + if isinstance(tgt["attn"], tuple): + tgt["attn"] = [tgt["attn"]] + for item in tgt["attn"]: + attn_map, basergb = item + attn_map = (attn_map - attn_map.min()) / (attn_map.max() - attn_map.min() + 1e-3) + attn_map = (attn_map * 255).astype(np.uint8) + cm = ColorMap(basergb) + heatmap = cm(attn_map) + ax.imshow(heatmap) + ax.set_axis_off() + + def showAnns(self, anns, draw_bbox=False): + """ + Display the specified annotations. + :param anns (array of object): annotations to display + :return: None + """ + if len(anns) == 0: + return 0 + if "segmentation" in anns[0] or "keypoints" in anns[0]: + datasetType = "instances" + elif "caption" in anns[0]: + datasetType = "captions" + else: + raise Exception("datasetType not supported") + if datasetType == "instances": + ax = plt.gca() + ax.set_autoscale_on(False) + polygons = [] + color = [] + for ann in anns: + c = (np.random.random((1, 3)) * 0.6 + 0.4).tolist()[0] + if "segmentation" in ann: + if type(ann["segmentation"]) == list: + # polygon + for seg in ann["segmentation"]: + poly = np.array(seg).reshape((int(len(seg) / 2), 2)) + polygons.append(Polygon(poly)) + color.append(c) + else: + # mask + t = self.imgs[ann["image_id"]] + if type(ann["segmentation"]["counts"]) == list: + rle = maskUtils.frPyObjects( + [ann["segmentation"]], t["height"], t["width"] + ) + else: + rle = [ann["segmentation"]] + m = maskUtils.decode(rle) + img = np.ones((m.shape[0], m.shape[1], 3)) + if ann["iscrowd"] == 1: + color_mask = np.array([2.0, 166.0, 101.0]) / 255 + if ann["iscrowd"] == 0: + color_mask = np.random.random((1, 3)).tolist()[0] + for i in range(3): + img[:, :, i] = color_mask[i] + ax.imshow(np.dstack((img, m * 0.5))) + if "keypoints" in ann and type(ann["keypoints"]) == list: + # turn skeleton into zero-based index + sks = np.array(self.loadCats(ann["category_id"])[0]["skeleton"]) - 1 + kp = np.array(ann["keypoints"]) + x = kp[0::3] + y = kp[1::3] + v = kp[2::3] + for sk in sks: + if np.all(v[sk] > 0): + plt.plot(x[sk], y[sk], linewidth=3, color=c) + plt.plot( + x[v > 0], + y[v > 0], + "o", + markersize=8, + markerfacecolor=c, + markeredgecolor="k", + markeredgewidth=2, + ) + plt.plot( + x[v > 1], + y[v > 1], + "o", + markersize=8, + markerfacecolor=c, + markeredgecolor=c, + markeredgewidth=2, + ) + + if draw_bbox: + [bbox_x, bbox_y, bbox_w, bbox_h] = ann["bbox"] + poly = [ + [bbox_x, bbox_y], + [bbox_x, bbox_y + bbox_h], + [bbox_x + bbox_w, bbox_y + bbox_h], + [bbox_x + bbox_w, bbox_y], + ] + np_poly = np.array(poly).reshape((4, 2)) + polygons.append(Polygon(np_poly)) + color.append(c) + + # p = PatchCollection(polygons, facecolor=color, linewidths=0, alpha=0.4) + # ax.add_collection(p) + p = PatchCollection(polygons, facecolor="none", edgecolors=color, linewidths=2) + ax.add_collection(p) + elif datasetType == "captions": + for ann in anns: + print(ann["caption"]) diff --git a/groundingdino/util/vl_utils.py b/groundingdino/util/vl_utils.py new file mode 100644 index 0000000..c91bb02 --- /dev/null +++ b/groundingdino/util/vl_utils.py @@ -0,0 +1,100 @@ +import os +import random +from typing import List + +import torch + + +def create_positive_map_from_span(tokenized, token_span, max_text_len=256): + """construct a map such that positive_map[i,j] = True iff box i is associated to token j + Input: + - tokenized: + - input_ids: Tensor[1, ntokens] + - attention_mask: Tensor[1, ntokens] + - token_span: list with length num_boxes. + - each item: [start_idx, end_idx] + """ + positive_map = torch.zeros((len(token_span), max_text_len), dtype=torch.float) + for j, tok_list in enumerate(token_span): + for (beg, end) in tok_list: + beg_pos = tokenized.char_to_token(beg) + end_pos = tokenized.char_to_token(end - 1) + if beg_pos is None: + try: + beg_pos = tokenized.char_to_token(beg + 1) + if beg_pos is None: + beg_pos = tokenized.char_to_token(beg + 2) + except: + beg_pos = None + if end_pos is None: + try: + end_pos = tokenized.char_to_token(end - 2) + if end_pos is None: + end_pos = tokenized.char_to_token(end - 3) + except: + end_pos = None + if beg_pos is None or end_pos is None: + continue + + assert beg_pos is not None and end_pos is not None + if os.environ.get("SHILONG_DEBUG_ONLY_ONE_POS", None) == "TRUE": + positive_map[j, beg_pos] = 1 + break + else: + positive_map[j, beg_pos : end_pos + 1].fill_(1) + + return positive_map / (positive_map.sum(-1)[:, None] + 1e-6) + + +def build_captions_and_token_span(cat_list, force_lowercase): + """ + Return: + captions: str + cat2tokenspan: dict + { + 'dog': [[0, 2]], + ... + } + """ + + cat2tokenspan = {} + captions = "" + for catname in cat_list: + class_name = catname + if force_lowercase: + class_name = class_name.lower() + if "/" in class_name: + class_name_list: List = class_name.strip().split("/") + class_name_list.append(class_name) + class_name: str = random.choice(class_name_list) + + tokens_positive_i = [] + subnamelist = [i.strip() for i in class_name.strip().split(" ")] + for subname in subnamelist: + if len(subname) == 0: + continue + if len(captions) > 0: + captions = captions + " " + strat_idx = len(captions) + end_idx = strat_idx + len(subname) + tokens_positive_i.append([strat_idx, end_idx]) + captions = captions + subname + + if len(tokens_positive_i) > 0: + captions = captions + " ." + cat2tokenspan[class_name] = tokens_positive_i + + return captions, cat2tokenspan + + +def build_id2posspan_and_caption(category_dict: dict): + """Build id2pos_span and caption from category_dict + + Args: + category_dict (dict): category_dict + """ + cat_list = [item["name"].lower() for item in category_dict] + id2catname = {item["id"]: item["name"].lower() for item in category_dict} + caption, cat2posspan = build_captions_and_token_span(cat_list, force_lowercase=True) + id2posspan = {catid: cat2posspan[catname] for catid, catname in id2catname.items()} + return id2posspan, caption diff --git a/groundingdino/version.py b/groundingdino/version.py new file mode 100644 index 0000000..b794fd4 --- /dev/null +++ b/groundingdino/version.py @@ -0,0 +1 @@ +__version__ = '0.1.0'