birth
This commit is contained in:
61
DCNv4_op/scripts/find_best.py
Normal file
61
DCNv4_op/scripts/find_best.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import json
|
||||
import argparse
|
||||
class LineParser:
|
||||
def __init__(self) -> None:
|
||||
self.data = {}
|
||||
|
||||
def parse(self, line):
|
||||
def startswith(line, lst):
|
||||
for ele in lst:
|
||||
if line.startswith(ele):
|
||||
return True
|
||||
return False
|
||||
|
||||
if not startswith(line, ['1', '2', '3', '4', '5', '6', '7', '8', '9']):
|
||||
return
|
||||
|
||||
eles = line.strip().split()
|
||||
key = eles[0]
|
||||
if key not in self.data:
|
||||
self.data[key] = []
|
||||
|
||||
self.data[key].append([eles[1], float(eles[2])])
|
||||
|
||||
def sort(self):
|
||||
for k, v in self.data.items():
|
||||
nv = sorted(v, key=lambda x: x[1])
|
||||
self.data[k] = nv
|
||||
|
||||
def display_best(self):
|
||||
for k, v in self.data.items():
|
||||
print(f'{k} \t {v[0][0]} \t {v[0][1]:.4f} \t {v[1][0]} \t {v[1][1]:.4f}')
|
||||
|
||||
def display_best_python(self, output):
|
||||
res = {}
|
||||
def parse(spec):
|
||||
d_stride = int(spec.split('/')[0])
|
||||
thread = int(spec.split('/')[1].split('(')[0])
|
||||
m = int(spec.split('(')[1].split(')')[0])
|
||||
return d_stride, thread, m
|
||||
|
||||
for k, v in self.data.items():
|
||||
res[k] = parse(v[0][0])
|
||||
|
||||
with open(output, "w") as f:
|
||||
json.dump(res, f, indent=4)
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--input', type=str)
|
||||
parser.add_argument('--output', type=str)
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.input) as f:
|
||||
lines = f.readlines()
|
||||
|
||||
lineparser = LineParser()
|
||||
for line in lines:
|
||||
lineparser.parse(line)
|
||||
lineparser.sort()
|
||||
lineparser.display_best()
|
||||
lineparser.display_best_python(args.output)
|
||||
2
DCNv4_op/scripts/search_bwd.sh
Normal file
2
DCNv4_op/scripts/search_bwd.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
python search_dcnv4_bwd_engine.py > res_bwd.txt
|
||||
python find_best.py --input res_bwd.txt --output table_bwd.py
|
||||
131
DCNv4_op/scripts/search_dcnv4.py
Normal file
131
DCNv4_op/scripts/search_dcnv4.py
Normal file
@@ -0,0 +1,131 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
|
||||
import time
|
||||
import math
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import math
|
||||
from torch.autograd import gradcheck
|
||||
import pandas as pd
|
||||
from easydict import EasyDict as edict
|
||||
import argparse
|
||||
|
||||
from torch.cuda import Event
|
||||
|
||||
from functions.dcnv3_func import DCNv3Function, dcnv3_core_pytorch
|
||||
from functions.dcnv4_func import DCNv4Function
|
||||
torch.set_printoptions(threshold=10000)
|
||||
|
||||
torch.manual_seed(3)
|
||||
|
||||
|
||||
#@torch.no_grad()
|
||||
def speed_test(func, args, inputs, name='Unknown'):
|
||||
|
||||
tic = Event(enable_timing=True)
|
||||
toc = Event(enable_timing=True)
|
||||
# warmup
|
||||
for i in range(args.warmup_num):
|
||||
func(*inputs)
|
||||
|
||||
total_time = 0
|
||||
tic.record()
|
||||
for i in range(args.test_num):
|
||||
o = func(*inputs)
|
||||
torch.cuda.synchronize()
|
||||
toc.record()
|
||||
|
||||
avg_time = tic.elapsed_time(toc) / args.test_num
|
||||
# print(
|
||||
# f'>>> {name: <10} finished {args.test_num} running, avg_time: {avg_time:.6f} ms')
|
||||
return avg_time
|
||||
|
||||
@torch.no_grad()
|
||||
def test(N, H_in, W_in, M, D, spec=None):
|
||||
Kh, Kw = 3, 3
|
||||
remove_center = False
|
||||
P = Kh * Kw - remove_center
|
||||
offset_scale = 2.0
|
||||
pad = 1
|
||||
dilation = 1
|
||||
stride = 1
|
||||
H_out = (H_in + 2 * pad - (dilation * (Kh - 1) + 1)) // stride + 1
|
||||
W_out = (W_in + 2 * pad - (dilation * (Kw - 1) + 1)) // stride + 1
|
||||
|
||||
input = torch.rand(N, H_in, W_in, M*D).cuda()
|
||||
# print(input.shape)
|
||||
offset = (torch.rand(N, H_out, W_out, M*P*2).cuda() * 2 - 1)*2
|
||||
# offset = (torch.rand(N, H_out, W_out, M*P*2).cuda() * 2 - 1)*0
|
||||
mask_origin = torch.rand(N, H_out, W_out, M, P).cuda() + 1e-5
|
||||
mask_origin = mask_origin.half()
|
||||
mask = mask_origin
|
||||
# mask = torch.nn.functional.softmax(mask_origin, dim=-1)
|
||||
offset_mask = torch.cat([offset.unflatten(-1, (M, P * 2)), mask_origin.detach()], dim=-1).flatten(-2)
|
||||
|
||||
im2col_step = 128
|
||||
|
||||
input = input.half()
|
||||
offset = offset.half()
|
||||
mask = mask.half()
|
||||
offset_mask = offset_mask.half()
|
||||
|
||||
dcnv3_args = [
|
||||
input,
|
||||
offset,
|
||||
mask,
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale,
|
||||
im2col_step, remove_center,
|
||||
]
|
||||
output_pytorch = DCNv3Function.apply(*dcnv3_args)
|
||||
|
||||
input1 = input.detach()
|
||||
|
||||
def pad(om):
|
||||
padded_zero = int(math.ceil(om.shape[3]/8)*8) - om.shape[3]
|
||||
padded = torch.zeros(om.shape[0], om.shape[1], om.shape[2], padded_zero).to(om)
|
||||
return torch.cat([om, padded], dim=-1)
|
||||
|
||||
dcnv4_args = [
|
||||
input1, pad(offset_mask),
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale,
|
||||
im2col_step, remove_center,
|
||||
spec[0], spec[1], 2, None
|
||||
# 8, 512, 2, 256
|
||||
]
|
||||
output_flash_cuda = DCNv4Function.apply(*dcnv4_args)
|
||||
|
||||
fwdok = torch.allclose(output_flash_cuda, output_pytorch, rtol=1e-2, atol=1e-3)
|
||||
max_abs_err = (output_flash_cuda - output_pytorch).abs().max()
|
||||
max_rel_err = ((output_flash_cuda - output_pytorch).abs() /
|
||||
(output_pytorch.abs()+ 1e-3)).max()
|
||||
# print('>>> forward half')
|
||||
# print(f'* {fwdok} check_forward_equal_with_pytorch_float: max_abs_err {max_abs_err:.2e} max_rel_err {max_rel_err:.2e}')
|
||||
if not fwdok:
|
||||
print(f"Wrong: {N}x{H_in}x{W_in}x{M}x{D} \t {spec[0]}/{spec[1]}({spec[2]})")
|
||||
return
|
||||
# assert(fwdok)
|
||||
|
||||
test_args = edict({'warmup_num': 10000, 'test_num': 10000})
|
||||
|
||||
exp_time_dcnv4 = speed_test(DCNv4Function.apply, test_args, dcnv4_args, name='exp')
|
||||
torch.cuda.synchronize()
|
||||
print(f"{N}x{H_in}x{W_in}x{M}x{D} \t {spec[0]}/{spec[1]}({spec[2]}): {exp_time_dcnv4}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--n", type=int)
|
||||
parser.add_argument("--h", type=int)
|
||||
parser.add_argument("--w", type=int)
|
||||
parser.add_argument("--g", type=int)
|
||||
parser.add_argument("--c", type=int)
|
||||
parser.add_argument("--dstride", type=int)
|
||||
parser.add_argument("--blockthread", type=int)
|
||||
parser.add_argument("--multiplier", type=int)
|
||||
args = parser.parse_args()
|
||||
test(args.n, args.h, args.w, args.g, args.c, (args.dstride, args.blockthread, args.multiplier))
|
||||
|
||||
|
||||
200
DCNv4_op/scripts/search_dcnv4_bwd.py
Normal file
200
DCNv4_op/scripts/search_dcnv4_bwd.py
Normal file
@@ -0,0 +1,200 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
|
||||
import time
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import math
|
||||
from torch.autograd import gradcheck
|
||||
import pandas as pd
|
||||
from easydict import EasyDict as edict
|
||||
import argparse
|
||||
|
||||
from torch.cuda import Event
|
||||
|
||||
from functions import DCNv4Function, DCNv3Function
|
||||
torch.set_printoptions(threshold=10000)
|
||||
|
||||
|
||||
|
||||
torch.manual_seed(3)
|
||||
|
||||
def speed_test_backward(func, args, inputs, name='Unknown'):
|
||||
# warmup
|
||||
# for i in range(args.warmup_num):
|
||||
# o = func(*inputs)
|
||||
# o.sum().backward()
|
||||
|
||||
total_time = 0
|
||||
len_input = len(inputs)
|
||||
for i in range(args.warmup_num + args.test_num):
|
||||
tic = Event(enable_timing=True)
|
||||
toc = Event(enable_timing=True)
|
||||
inputs[0] = inputs[0].detach()
|
||||
inputs[0].requires_grad = True
|
||||
if len_input > 1 and isinstance(inputs[1], torch.Tensor):
|
||||
inputs[1] = inputs[1].detach()
|
||||
inputs[1].requires_grad = True
|
||||
if len_input > 2 and isinstance(inputs[2], torch.Tensor):
|
||||
inputs[2] = inputs[2].detach()
|
||||
inputs[2].requires_grad = True
|
||||
|
||||
o = func(*inputs)
|
||||
torch.cuda.synchronize()
|
||||
tic.record()
|
||||
o.sum().backward()
|
||||
toc.record()
|
||||
torch.cuda.synchronize()
|
||||
_time = tic.elapsed_time(toc)
|
||||
if i >= args.warmup_num:
|
||||
total_time += _time
|
||||
o = o.detach()
|
||||
|
||||
# toc.record()
|
||||
# torch.cuda.synchronize()
|
||||
|
||||
avg_time = total_time / args.test_num
|
||||
#print(
|
||||
# f'>>> {name: <10} finished {args.test_num} running, avg_time: {avg_time:.6f} ms')
|
||||
return avg_time
|
||||
|
||||
# @torch.no_grad()
|
||||
def test(N=64, H_in=32, W_in=32, M=4, D=16, spec=None):
|
||||
"""
|
||||
64x56x56x128(G=4)
|
||||
2 64: 3.66
|
||||
- offset_mask collection write 3.4022
|
||||
- offset_mask collection 3.1968
|
||||
|
||||
"""
|
||||
Kh, Kw = 3, 3
|
||||
remove_center = False
|
||||
P = Kh * Kw - remove_center
|
||||
offset_scale = 2.0
|
||||
pad = 1
|
||||
dilation = 1
|
||||
stride = 1
|
||||
H_out = (H_in + 2 * pad - (dilation * (Kh - 1) + 1)) // stride + 1
|
||||
W_out = (W_in + 2 * pad - (dilation * (Kw - 1) + 1)) // stride + 1
|
||||
|
||||
additions = [None, None, spec[0], spec[1], False]
|
||||
input = torch.rand(N, H_in, W_in, M*D).cuda() * 10
|
||||
#offset = torch.rand(N, H_out, W_out, M*P*2).cuda() * 0
|
||||
offset = (torch.rand(N, H_out, W_out, M*P*2).cuda() * 2 - 1)*2
|
||||
mask_origin = torch.rand(N, H_out, W_out, M, P).cuda() + 1e-5
|
||||
mask_origin = mask_origin.half()
|
||||
mask_origin.requires_grad = True
|
||||
# offset_mask = torch.cat([offset.unflatten(-1, (M, P, 2)), mask_origin.detach().unsqueeze(-1)], dim=-1).flatten(-3)
|
||||
# mask /= mask.sum(-1, keepdim=True)
|
||||
# mask = torch.nn.functional.softmax(mask_origin, dim=-1, dtype=torch.float32)
|
||||
mask = mask_origin
|
||||
# mask = mask.reshape(N, H_out, W_out, M*P)
|
||||
# offset_mask = torch.cat([offset.unflatten(-1, (M, P, 2)), mask.detach().unsqueeze(-1)], dim=-1).flatten(-3)
|
||||
offset_mask = torch.cat([offset.detach().unflatten(-1, (M, P * 2)), mask_origin.detach()], dim=-1).flatten(-2)
|
||||
|
||||
im2col_step = 128
|
||||
|
||||
input = input.half()
|
||||
offset = offset.half()
|
||||
mask = mask.half()
|
||||
input.requires_grad = True
|
||||
offset.requires_grad = True
|
||||
# mask.requires_grad = True
|
||||
output_pytorch = DCNv3Function.apply(
|
||||
input,
|
||||
offset,
|
||||
mask,
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale,
|
||||
im2col_step, remove_center)#.detach().cpu()
|
||||
(output_pytorch.sum()/10).backward()
|
||||
|
||||
def pad(om):
|
||||
padded_zero = int(math.ceil(om.shape[3]/8)*8) - om.shape[3]
|
||||
padded = torch.zeros(om.shape[0], om.shape[1], om.shape[2], padded_zero).to(om)
|
||||
return torch.cat([om, padded], dim=-1)
|
||||
|
||||
# value_offset_mask = input.detach()
|
||||
input1 = input.detach()
|
||||
input1.requires_grad = True
|
||||
offset_mask = offset_mask.half()
|
||||
offset_mask.requires_grad = True
|
||||
# offset_mask1.requires_grad = True
|
||||
torch.cuda.profiler.cudart().cudaProfilerStart()
|
||||
output_flash_cuda = DCNv4Function.apply(
|
||||
input1, offset_mask,
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale,
|
||||
im2col_step, remove_center, *additions)#.detach().cpu()
|
||||
(output_flash_cuda.sum()/10).backward()
|
||||
torch.cuda.profiler.cudart().cudaProfilerStop()
|
||||
|
||||
input_grad = input.grad
|
||||
input2_grad = input1.grad
|
||||
bwdok = torch.allclose(input_grad.float(), input2_grad.float(), rtol=1e-2, atol=1e-3)
|
||||
rel_err = (input_grad.abs() - input2_grad.abs())/(input_grad.abs()+1e-3)
|
||||
|
||||
offset_grad1 = offset.grad
|
||||
offset_grad2 = offset_mask.grad.reshape(N, H_out, W_out, M, P*3)[..., :P*2].reshape(N, H_out, W_out, M*P*2)
|
||||
|
||||
bwdok2 = torch.allclose(offset_grad1.float(), offset_grad2.float(), rtol=1e-2, atol=1e-3)
|
||||
rel_err = (offset_grad1 - offset_grad2).abs() / (offset_grad1.abs()+1e-3)
|
||||
|
||||
mask_grad1 = mask_origin.grad
|
||||
mask_grad2 = offset_mask.grad.reshape(N, H_out, W_out, M, P*3)[..., P*2:].reshape(N, H_out, W_out, M, P)
|
||||
|
||||
bwdok3 = torch.allclose(mask_grad1, mask_grad2, rtol=1e-2, atol=1e-3)
|
||||
rel_err = (mask_grad1 - mask_grad2).abs() / (mask_grad1.abs()+1e-3)
|
||||
|
||||
fwdok = torch.allclose(output_flash_cuda, output_pytorch, rtol=1e-2, atol=1e-3)
|
||||
max_abs_err = (output_flash_cuda - output_pytorch).abs().max()
|
||||
max_rel_err = ((output_flash_cuda - output_pytorch).abs() /
|
||||
(output_pytorch.abs()+ 1e-3)).max()
|
||||
if not (bwdok and bwdok2 and bwdok3):
|
||||
print(f"Wrong: {N}x{H_in}x{W_in}x{M}x{D} \t {spec[0]}/{spec[1]}({spec[2]})")
|
||||
return
|
||||
# fn_args = [
|
||||
# input,
|
||||
# offset,
|
||||
# mask,
|
||||
# Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale,
|
||||
# im2col_step, remove_center
|
||||
# ]
|
||||
|
||||
flash_dcn_fn_args = [
|
||||
input1,
|
||||
offset_mask,
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale,
|
||||
im2col_step, remove_center, *additions
|
||||
]
|
||||
|
||||
test_args = edict({'warmup_num': 1000, 'test_num': 1000})
|
||||
try:
|
||||
exp_time = speed_test_backward(DCNv4Function.apply, test_args, flash_dcn_fn_args, name='exp')
|
||||
except:
|
||||
print(f"Wrong: {N}x{H_in}x{W_in}x{M}x{D} \t {spec[0]}/{spec[1]}({spec[2]})")
|
||||
return
|
||||
|
||||
torch.cuda.synchronize()
|
||||
print(f"{N}x{H_in}x{W_in}x{M}x{D} \t {spec[0]}/{spec[1]}({spec[2]}): {exp_time}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--n", type=int)
|
||||
parser.add_argument("--h", type=int)
|
||||
parser.add_argument("--w", type=int)
|
||||
parser.add_argument("--g", type=int)
|
||||
parser.add_argument("--c", type=int)
|
||||
parser.add_argument("--dstride", type=int)
|
||||
parser.add_argument("--blockthread", type=int)
|
||||
parser.add_argument("--multiplier", type=int)
|
||||
args = parser.parse_args()
|
||||
test(args.n, args.h, args.w, args.g, args.c, (args.dstride, args.blockthread, args.multiplier))
|
||||
|
||||
|
||||
24
DCNv4_op/scripts/search_dcnv4_bwd_engine.py
Normal file
24
DCNv4_op/scripts/search_dcnv4_bwd_engine.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import os
|
||||
|
||||
def factors(N):
|
||||
res = []
|
||||
for i in range(1, N+1):
|
||||
if N % i == 0:
|
||||
res.append(i)
|
||||
return res
|
||||
|
||||
if __name__ == '__main__':
|
||||
BATCH=64
|
||||
for N, Hin, Win in [(BATCH, 56, 56), (BATCH, 28, 28), (BATCH, 14, 14), (BATCH, 7, 7),
|
||||
(1, 200, 320), (1, 100, 160), (1, 50, 80), (1, 25, 40), (1, 64, 64)]:
|
||||
for group_channel in [16, 32, 64]:
|
||||
for group in [4, 5, 6, 7, 8]:
|
||||
for d_stride in [1, 2, 4]:
|
||||
for m in factors(N*Hin*Win):
|
||||
if m > 64:
|
||||
break
|
||||
block_thread = group * (group_channel//d_stride) * m
|
||||
if block_thread > 1024:
|
||||
break
|
||||
cmd = f"python search_dcnv4_bwd.py --n {N} --h {Hin} --w {Win} --g {group} --c {group_channel} --dstride {d_stride} --blockthread {block_thread} --multiplier {m}"
|
||||
os.system(cmd)
|
||||
24
DCNv4_op/scripts/search_dcnv4_engine.py
Normal file
24
DCNv4_op/scripts/search_dcnv4_engine.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import os
|
||||
|
||||
def factors(N):
|
||||
res = []
|
||||
for i in range(1, N+1):
|
||||
if N % i == 0:
|
||||
res.append(i)
|
||||
return res
|
||||
|
||||
if __name__ == '__main__':
|
||||
BATCH=64
|
||||
for group_channel in [16, 32, 64]:
|
||||
for group in [4, 5, 6, 7, 8]:
|
||||
for N, Hin, Win in [(BATCH, 56, 56), (BATCH, 28, 28), (BATCH, 14, 14), (BATCH, 7, 7),
|
||||
(1, 200, 320), (1, 100, 160), (1, 50, 80), (1, 25, 40), (1, 64, 64)]:
|
||||
for d_stride in [2, 4, 8, 16]:
|
||||
for m in factors(N*Hin*Win):
|
||||
if m > 64:
|
||||
break
|
||||
block_thread = group * (group_channel//d_stride) * m
|
||||
if block_thread > 1024:
|
||||
break
|
||||
cmd = f"python search_dcnv4.py --n {N} --h {Hin} --w {Win} --g {group} --c {group_channel} --dstride {d_stride} --blockthread {block_thread} --multiplier {m}"
|
||||
os.system(cmd)
|
||||
2
DCNv4_op/scripts/search_fwd.sh
Normal file
2
DCNv4_op/scripts/search_fwd.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
python search_dcnv4_engine.py > res.txt
|
||||
python find_best.py --input res.txt --output table.py
|
||||
144
DCNv4_op/scripts/test_dcnv4.py
Normal file
144
DCNv4_op/scripts/test_dcnv4.py
Normal file
@@ -0,0 +1,144 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
|
||||
import time
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import math
|
||||
from torch.autograd import gradcheck
|
||||
import pandas as pd
|
||||
from easydict import EasyDict as edict
|
||||
|
||||
from torch.cuda import Event
|
||||
|
||||
from functions.dcnv3_func import DCNv3Function, dcnv3_core_pytorch
|
||||
from functions.dcnv4_func import DCNv4Function
|
||||
torch.set_printoptions(threshold=10000)
|
||||
|
||||
H_in, W_in = 56, 56
|
||||
N, M, D = 64, 4, 32
|
||||
|
||||
# H_in, W_in = 28, 28
|
||||
# N, M, D = 64, 8, 32
|
||||
|
||||
# H_in, W_in = 14, 14
|
||||
# N, M, D = 64, 16, 32
|
||||
|
||||
# H_in, W_in = 7, 7
|
||||
# N, M, D = 64, 32, 32
|
||||
|
||||
# H_in, W_in = 8, 8
|
||||
# N, M, D = 128, 4, 16
|
||||
|
||||
|
||||
Kh, Kw = 3, 3
|
||||
remove_center = False
|
||||
P = Kh * Kw - remove_center
|
||||
offset_scale = 2.0
|
||||
pad = 1
|
||||
dilation = 1
|
||||
stride = 1
|
||||
H_out = (H_in + 2 * pad - (dilation * (Kh - 1) + 1)) // stride + 1
|
||||
W_out = (W_in + 2 * pad - (dilation * (Kw - 1) + 1)) // stride + 1
|
||||
|
||||
torch.manual_seed(3)
|
||||
|
||||
#@torch.no_grad()
|
||||
def speed_test(func, args, inputs, name='Unknown'):
|
||||
|
||||
tic = Event(enable_timing=True)
|
||||
toc = Event(enable_timing=True)
|
||||
# warmup
|
||||
for i in range(args.warmup_num):
|
||||
func(*inputs)
|
||||
|
||||
total_time = 0
|
||||
tic.record()
|
||||
for i in range(args.test_num):
|
||||
o = func(*inputs)
|
||||
torch.cuda.synchronize()
|
||||
toc.record()
|
||||
|
||||
avg_time = tic.elapsed_time(toc) / args.test_num
|
||||
print(
|
||||
f'>>> {name: <10} finished {args.test_num} running, avg_time: {avg_time:.6f} ms')
|
||||
return avg_time
|
||||
|
||||
@torch.no_grad()
|
||||
def check_forward_equal_with_pytorch_half():
|
||||
input = torch.rand(N, H_in, W_in, M*D).cuda()
|
||||
print(input.shape)
|
||||
offset = (torch.rand(N, H_out, W_out, M*P*2).cuda() * 2 - 1)*10
|
||||
# offset = (torch.rand(N, H_out, W_out, M*P*2).cuda() * 2 - 1)*0
|
||||
mask_origin = torch.rand(N, H_out, W_out, M, P).cuda() + 1e-5
|
||||
mask_origin = mask_origin.half()
|
||||
mask = mask_origin
|
||||
# mask = torch.nn.functional.softmax(mask_origin, dim=-1)
|
||||
offset_mask = torch.cat([offset.unflatten(-1, (M, P * 2)), mask_origin.detach()], dim=-1).flatten(-2)
|
||||
|
||||
im2col_step = 128
|
||||
|
||||
input = input.half()
|
||||
offset = offset.half()
|
||||
mask = mask.half()
|
||||
offset_mask = offset_mask.half()
|
||||
|
||||
dcnv3_args = [
|
||||
input,
|
||||
offset,
|
||||
mask,
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale,
|
||||
im2col_step, remove_center,
|
||||
]
|
||||
output_pytorch = DCNv3Function.apply(*dcnv3_args)
|
||||
|
||||
input1 = input.detach()
|
||||
|
||||
def pad(om):
|
||||
padded_zero = int(math.ceil(om.shape[3]/8)*8) - om.shape[3]
|
||||
padded = torch.zeros(om.shape[0], om.shape[1], om.shape[2], padded_zero).to(om)
|
||||
return torch.cat([om, padded], dim=-1)
|
||||
|
||||
dcnv4_args = [
|
||||
input1, pad(offset_mask),
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale,
|
||||
im2col_step, remove_center, 8, 512, 2, 256, True, True,
|
||||
]
|
||||
output_flash_cuda = DCNv4Function.apply(*dcnv4_args)
|
||||
|
||||
fwdok = torch.allclose(output_flash_cuda, output_pytorch, rtol=1e-2, atol=1e-3)
|
||||
max_abs_err = (output_flash_cuda - output_pytorch).abs().max()
|
||||
max_rel_err = ((output_flash_cuda - output_pytorch).abs() /
|
||||
(output_pytorch.abs()+ 1e-3)).max()
|
||||
print('>>> forward half')
|
||||
print(f'* {fwdok} check_forward_equal_with_pytorch_float: max_abs_err {max_abs_err:.2e} max_rel_err {max_rel_err:.2e}')
|
||||
assert(fwdok)
|
||||
|
||||
test_args = edict({'warmup_num': 1000, 'test_num': 1000})
|
||||
|
||||
exp_time_dcnv4 = speed_test(DCNv4Function.apply, test_args, dcnv4_args, name='exp')
|
||||
exp_time_dcnv3 = speed_test(DCNv3Function.apply, test_args, dcnv3_args, name='exp')
|
||||
torch.cuda.synchronize()
|
||||
|
||||
results = [{}]
|
||||
results[0]['dcnv3_time'] = exp_time_dcnv3
|
||||
results[0]['dcnv4_time'] = exp_time_dcnv4
|
||||
columns = list(results[0].keys())
|
||||
|
||||
outputs = pd.DataFrame(results, columns=columns)
|
||||
with pd.option_context(
|
||||
'display.max_rows', None, 'display.max_columns', None,
|
||||
'display.max_colwidth', None, 'display.width', None,
|
||||
'display.precision', 4, ):
|
||||
print(outputs)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
check_forward_equal_with_pytorch_half()
|
||||
221
DCNv4_op/scripts/test_dcnv4_bwd.py
Normal file
221
DCNv4_op/scripts/test_dcnv4_bwd.py
Normal file
@@ -0,0 +1,221 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
|
||||
import time
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import math
|
||||
from torch.autograd import gradcheck
|
||||
import pandas as pd
|
||||
from easydict import EasyDict as edict
|
||||
|
||||
from torch.cuda import Event
|
||||
|
||||
from functions import DCNv4Function, DCNv3Function
|
||||
torch.set_printoptions(threshold=10000)
|
||||
|
||||
H_in, W_in = 56, 56
|
||||
N, M, D = 64, 4, 32
|
||||
|
||||
# H_in, W_in = 28, 28
|
||||
# N, M, D = 64, 16, 16
|
||||
|
||||
# H_in, W_in = 14, 14
|
||||
# N, M, D = 64, 32, 16
|
||||
|
||||
|
||||
# H_in, W_in = 7, 7
|
||||
# N, M, D = 64, 64, 16
|
||||
|
||||
# H_in, W_in = 8, 8
|
||||
# N, M, D = 128, 4, 16
|
||||
|
||||
|
||||
Kh, Kw = 3, 3
|
||||
remove_center = False
|
||||
P = Kh * Kw - remove_center
|
||||
offset_scale = 2.0
|
||||
pad = 1
|
||||
dilation = 1
|
||||
stride = 1
|
||||
H_out = (H_in + 2 * pad - (dilation * (Kh - 1) + 1)) // stride + 1
|
||||
W_out = (W_in + 2 * pad - (dilation * (Kw - 1) + 1)) // stride + 1
|
||||
|
||||
torch.manual_seed(3)
|
||||
|
||||
def speed_test_backward(func, args, inputs, name='Unknown'):
|
||||
# warmup
|
||||
# for i in range(args.warmup_num):
|
||||
# o = func(*inputs)
|
||||
# o.sum().backward()
|
||||
|
||||
total_time = 0
|
||||
len_input = len(inputs)
|
||||
for i in range(args.warmup_num + args.test_num):
|
||||
tic = Event(enable_timing=True)
|
||||
toc = Event(enable_timing=True)
|
||||
inputs[0] = inputs[0].detach()
|
||||
inputs[0].requires_grad = True
|
||||
if len_input > 1 and isinstance(inputs[1], torch.Tensor):
|
||||
inputs[1] = inputs[1].detach()
|
||||
inputs[1].requires_grad = True
|
||||
if len_input > 2 and isinstance(inputs[2], torch.Tensor):
|
||||
inputs[2] = inputs[2].detach()
|
||||
inputs[2].requires_grad = True
|
||||
|
||||
o = func(*inputs)
|
||||
torch.cuda.synchronize()
|
||||
tic.record()
|
||||
o.sum().backward()
|
||||
toc.record()
|
||||
torch.cuda.synchronize()
|
||||
_time = tic.elapsed_time(toc)
|
||||
if i >= args.warmup_num:
|
||||
total_time += _time
|
||||
o = o.detach()
|
||||
|
||||
# toc.record()
|
||||
# torch.cuda.synchronize()
|
||||
|
||||
avg_time = total_time / args.test_num
|
||||
#print(
|
||||
# f'>>> {name: <10} finished {args.test_num} running, avg_time: {avg_time:.6f} ms')
|
||||
return avg_time
|
||||
|
||||
# @torch.no_grad()
|
||||
def check_forward_equal_with_pytorch_half():
|
||||
"""
|
||||
64x56x56x128(G=4)
|
||||
2 64: 3.66
|
||||
- offset_mask collection write 3.4022
|
||||
- offset_mask collection 3.1968
|
||||
|
||||
"""
|
||||
additions = [8, 128, 2, 256, False]
|
||||
input = torch.rand(N, H_in, W_in, M*D).cuda() * 10
|
||||
print(input.shape)
|
||||
#offset = torch.rand(N, H_out, W_out, M*P*2).cuda() * 0
|
||||
offset = (torch.rand(N, H_out, W_out, M*P*2).cuda() * 2 - 1)*2
|
||||
mask_origin = torch.rand(N, H_out, W_out, M, P).cuda() + 1e-5
|
||||
mask_origin = mask_origin.half()
|
||||
mask_origin.requires_grad = True
|
||||
# offset_mask = torch.cat([offset.unflatten(-1, (M, P, 2)), mask_origin.detach().unsqueeze(-1)], dim=-1).flatten(-3)
|
||||
# mask /= mask.sum(-1, keepdim=True)
|
||||
# mask = torch.nn.functional.softmax(mask_origin, dim=-1, dtype=torch.float32)
|
||||
mask = mask_origin
|
||||
# mask = mask.reshape(N, H_out, W_out, M*P)
|
||||
# offset_mask = torch.cat([offset.unflatten(-1, (M, P, 2)), mask.detach().unsqueeze(-1)], dim=-1).flatten(-3)
|
||||
offset_mask = torch.cat([offset.detach().unflatten(-1, (M, P * 2)), mask_origin.detach()], dim=-1).flatten(-2)
|
||||
|
||||
im2col_step = 128
|
||||
|
||||
input = input.half()
|
||||
offset = offset.half()
|
||||
mask = mask.half()
|
||||
input.requires_grad = True
|
||||
offset.requires_grad = True
|
||||
# mask.requires_grad = True
|
||||
output_pytorch = DCNv3Function.apply(
|
||||
input,
|
||||
offset,
|
||||
mask,
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale,
|
||||
im2col_step, remove_center)#.detach().cpu()
|
||||
(output_pytorch.sum()/10).backward()
|
||||
|
||||
def pad(om):
|
||||
padded_zero = int(math.ceil(om.shape[3]/8)*8) - om.shape[3]
|
||||
padded = torch.zeros(om.shape[0], om.shape[1], om.shape[2], padded_zero).to(om)
|
||||
return torch.cat([om, padded], dim=-1)
|
||||
|
||||
# value_offset_mask = input.detach()
|
||||
input1 = input.detach()
|
||||
input1.requires_grad = True
|
||||
offset_mask = offset_mask.half()
|
||||
offset_mask.requires_grad = True
|
||||
# offset_mask1.requires_grad = True
|
||||
torch.cuda.profiler.cudart().cudaProfilerStart()
|
||||
output_flash_cuda = DCNv4Function.apply(
|
||||
input1, offset_mask,
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale,
|
||||
im2col_step, remove_center, *additions)#.detach().cpu()
|
||||
(output_flash_cuda.sum()/10).backward()
|
||||
torch.cuda.profiler.cudart().cudaProfilerStop()
|
||||
|
||||
input_grad = input.grad
|
||||
input2_grad = input1.grad
|
||||
bwdok = torch.allclose(input_grad.float(), input2_grad.float(), rtol=1e-2, atol=1e-3)
|
||||
print("bwdok")
|
||||
print(bwdok)
|
||||
rel_err = (input_grad.abs() - input2_grad.abs())/(input_grad.abs()+1e-3)
|
||||
print(rel_err.max())
|
||||
|
||||
offset_grad1 = offset.grad
|
||||
offset_grad2 = offset_mask.grad.reshape(N, H_out, W_out, M, P*3)[..., :P*2].reshape(N, H_out, W_out, M*P*2)
|
||||
# print(offset_grad1)
|
||||
# print("====================")
|
||||
# print(offset_grad2)
|
||||
bwdok2 = torch.allclose(offset_grad1.float(), offset_grad2.float(), rtol=1e-2, atol=1e-3)
|
||||
print("bwdok2")
|
||||
print(bwdok2)
|
||||
rel_err = (offset_grad1 - offset_grad2).abs() / (offset_grad1.abs()+1e-3)
|
||||
print(rel_err.max())
|
||||
|
||||
mask_grad1 = mask_origin.grad
|
||||
mask_grad2 = offset_mask.grad.reshape(N, H_out, W_out, M, P*3)[..., P*2:].reshape(N, H_out, W_out, M, P)
|
||||
|
||||
bwdok3 = torch.allclose(mask_grad1, mask_grad2, rtol=1e-2, atol=1e-3)
|
||||
print("bwdok3")
|
||||
print(bwdok3)
|
||||
rel_err = (mask_grad1 - mask_grad2).abs() / (mask_grad1.abs()+1e-3)
|
||||
print(rel_err.max())
|
||||
|
||||
fwdok = torch.allclose(output_flash_cuda, output_pytorch, rtol=1e-2, atol=1e-3)
|
||||
max_abs_err = (output_flash_cuda - output_pytorch).abs().max()
|
||||
max_rel_err = ((output_flash_cuda - output_pytorch).abs() /
|
||||
(output_pytorch.abs()+ 1e-3)).max()
|
||||
print('>>> forward half')
|
||||
print(f'* {fwdok} check_forward_equal_with_pytorch_float: max_abs_err {max_abs_err:.2e} max_rel_err {max_rel_err:.2e}')
|
||||
fn_args = [
|
||||
input,
|
||||
offset,
|
||||
mask,
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale,
|
||||
im2col_step, remove_center
|
||||
]
|
||||
|
||||
flash_dcn_fn_args = [
|
||||
input1,
|
||||
offset_mask,
|
||||
Kh, Kw, stride, stride, Kh // 2, Kw // 2, dilation, dilation, M, D, offset_scale,
|
||||
im2col_step, remove_center, *additions
|
||||
]
|
||||
|
||||
test_args = edict({'warmup_num': 1000, 'test_num': 1000})
|
||||
exp_time = speed_test_backward(
|
||||
DCNv4Function.apply, test_args, flash_dcn_fn_args, name='exp')
|
||||
exp_time_base = speed_test_backward(
|
||||
DCNv3Function.apply, test_args, fn_args, name='exp')
|
||||
|
||||
results = [{}]
|
||||
results[0]['time'] = exp_time
|
||||
results[0]['time_base'] = exp_time_base
|
||||
columns = list(results[0].keys())
|
||||
|
||||
outputs = pd.DataFrame(results, columns=columns)
|
||||
with pd.option_context(
|
||||
'display.max_rows', None, 'display.max_columns', None,
|
||||
'display.max_colwidth', None, 'display.width', None,
|
||||
'display.precision', 4, ):
|
||||
print(outputs)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
check_forward_equal_with_pytorch_half()
|
||||
174
DCNv4_op/scripts/test_flash_deform_attn.py
Normal file
174
DCNv4_op/scripts/test_flash_deform_attn.py
Normal file
@@ -0,0 +1,174 @@
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
# 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
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
from easydict import EasyDict as edict
|
||||
from torch.cuda import Event
|
||||
import pandas as pd
|
||||
|
||||
import time
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch.autograd import gradcheck
|
||||
|
||||
from functions import MSDeformAttnFunction, FlashDeformAttnFunction, ms_deform_attn_core_pytorch
|
||||
|
||||
|
||||
# N, M, D = 1, 4, 8
|
||||
# # Lq, L, P = 2, 2, 2
|
||||
# # shapes = torch.as_tensor([(6, 4), (3, 2)], dtype=torch.long).cuda()
|
||||
# Lq, L, P = 1, 2, 8
|
||||
# shapes = torch.as_tensor([(8, 16), (4, 8)], dtype=torch.long).cuda()
|
||||
|
||||
# N, M, D = 1, 8, 32
|
||||
# # Lq, L, P = 2, 2, 2
|
||||
# # shapes = torch.as_tensor([(6, 4), (3, 2)], dtype=torch.long).cuda()
|
||||
# Lq, L, P = 300, 4, 4
|
||||
# # shapes = torch.as_tensor([(134, 151), (67, 76), (34, 38), (17, 19)], dtype=torch.long).cuda()
|
||||
# # shapes = torch.as_tensor([(134, 151), (67, 76), (34, 38), (16, 16)], dtype=torch.long).cuda()
|
||||
# # shapes = torch.as_tensor([(134, 151), (67, 76), (34, 38), (17, 19)], dtype=torch.long).cuda()
|
||||
# # shapes = torch.as_tensor([(17, 19), (4, 4)], dtype=torch.long).cuda()
|
||||
# shapes = torch.as_tensor([(100, 151), (50, 76), (25, 38), (13, 19)], dtype=torch.long).cuda()
|
||||
# # shapes = torch.as_tensor([(110, 151)], dtype=torch.long).cuda()
|
||||
|
||||
# B:6
|
||||
# H:232
|
||||
# W:400
|
||||
# G:5
|
||||
# D: 16
|
||||
# channels: 80
|
||||
# kernel: 3 points = 3 * 3
|
||||
# num_split = 45 = kernel *kernel * G
|
||||
|
||||
H = 256
|
||||
W = 256
|
||||
N, M, D = 1, 8, 32
|
||||
Lq, L, P = 100*152, 4, 8
|
||||
|
||||
shapes = torch.Tensor([[100, 152], [ 50, 76], [ 25, 38], [ 13, 19]]).long().cuda()
|
||||
|
||||
# x = x.reshape([B, H*W, G, D + self.num_split * 3])
|
||||
# shapes = torch.as_tensor([(H, W)], dtype=torch.long).cuda()
|
||||
# shapes = torch.as_tensor([(H, W), (H // 2, W // 2)], dtype=torch.long).cuda()
|
||||
# shapes = torch.as_tensor([(H, W), (H // 2, W // 2), (H // 4, W // 4), (H // 8, W // 8)], dtype=torch.long).cuda()
|
||||
|
||||
level_start_index = torch.cat((shapes.new_zeros((1,)), shapes.prod(1).cumsum(0)[:-1]))
|
||||
S = sum([(H * W).item() for H, W in shapes])
|
||||
print(S)
|
||||
|
||||
def get_reference_points(spatial_shapes, 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] / (H_)
|
||||
ref_x = ref_x.reshape(-1)[None] / (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
|
||||
|
||||
|
||||
torch.manual_seed(3)
|
||||
|
||||
@torch.no_grad()
|
||||
def speed_test(func, args, inputs, name='Unknown'):
|
||||
|
||||
tic = Event(enable_timing=True)
|
||||
toc = Event(enable_timing=True)
|
||||
# warmup
|
||||
for i in range(args.warmup_num):
|
||||
func(*inputs)
|
||||
|
||||
tic.record()
|
||||
for i in range(args.test_num):
|
||||
func(*inputs)
|
||||
toc.record()
|
||||
torch.cuda.synchronize()
|
||||
|
||||
avg_time = tic.elapsed_time(toc) / args.test_num
|
||||
print(
|
||||
f'>>> {name: <10} finished {args.test_num} running, avg_time: {avg_time:.6f} ms')
|
||||
return avg_time
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def check_forward_equal_with_pytorch_half():
|
||||
value = torch.rand(N, S, M, D).cuda() * 0.01
|
||||
# offset = (torch.rand(N, Lq, M, L, P, 2).cuda() * 2 - 1) / 10
|
||||
sampling_locations = torch.rand(N, Lq, M, L, P, 2).cuda()
|
||||
attention_weights = torch.rand(N, Lq, M, L, P).cuda() + 1e-5
|
||||
sampling_loc_attn = torch.cat([sampling_locations.reshape(N, Lq, M, L*P*2), attention_weights.reshape(N, Lq, M, L*P)], dim=-1)
|
||||
attention_weights = torch.nn.functional.softmax(attention_weights.flatten(-2, -1), dim=-1).unflatten(-1, (L, P))
|
||||
|
||||
|
||||
im2col_step = 128
|
||||
|
||||
flash_fn_args = (
|
||||
value.half(),
|
||||
shapes,
|
||||
level_start_index,
|
||||
sampling_loc_attn.half(),
|
||||
im2col_step,
|
||||
P, 16
|
||||
)
|
||||
output_cuda = (
|
||||
FlashDeformAttnFunction.apply(*flash_fn_args)
|
||||
.detach()
|
||||
.cpu()
|
||||
).double()
|
||||
|
||||
fn_args = (
|
||||
value,
|
||||
shapes,
|
||||
level_start_index,
|
||||
sampling_locations,
|
||||
attention_weights,
|
||||
im2col_step,
|
||||
)
|
||||
|
||||
output_pytorch = (
|
||||
MSDeformAttnFunction.apply(*fn_args)
|
||||
.detach().double()
|
||||
.cpu()
|
||||
)
|
||||
|
||||
max_abs_err = (output_cuda - output_pytorch).abs().max()
|
||||
max_rel_err = ((output_cuda - output_pytorch).abs() / output_pytorch.abs()).max()
|
||||
fwdok = torch.allclose(output_cuda, output_pytorch, rtol=1e-2, atol=1e-3)
|
||||
|
||||
print(
|
||||
f"* {fwdok} check_forward_equal_with_pytorch_float: max_abs_err {max_abs_err:.2e} max_rel_err {max_rel_err:.2e}"
|
||||
)
|
||||
|
||||
test_args = edict({'warmup_num': 1000, 'test_num': 1000})
|
||||
exp_time_base = speed_test(
|
||||
MSDeformAttnFunction.apply, test_args, fn_args, name='exp')
|
||||
exp_time = speed_test(
|
||||
FlashDeformAttnFunction.apply, test_args, flash_fn_args, name='exp')
|
||||
|
||||
results = [{}]
|
||||
results[0]['time'] = exp_time
|
||||
results[0]['time_base'] = exp_time_base
|
||||
columns = list(results[0].keys())
|
||||
|
||||
outputs = pd.DataFrame(results, columns=columns)
|
||||
with pd.option_context(
|
||||
'display.max_rows', None, 'display.max_columns', None,
|
||||
'display.max_colwidth', None, 'display.width', None,
|
||||
'display.precision', 4, ):
|
||||
print(outputs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_forward_equal_with_pytorch_half()
|
||||
|
||||
194
DCNv4_op/scripts/test_flash_deform_attn_backward.py
Normal file
194
DCNv4_op/scripts/test_flash_deform_attn_backward.py
Normal file
@@ -0,0 +1,194 @@
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
# 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
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
from easydict import EasyDict as edict
|
||||
from torch.cuda import Event
|
||||
import pandas as pd
|
||||
|
||||
import time
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch.autograd import gradcheck
|
||||
|
||||
from functions import MSDeformAttnFunction, ms_deform_attn_core_pytorch, FlashDeformAttnFunction
|
||||
|
||||
|
||||
H = 256
|
||||
W = 256
|
||||
N, M, D = 1, 8, 16
|
||||
Lq, L, P = H * W, 1, 8
|
||||
|
||||
# x = x.reshape([B, H*W, G, D + self.num_split * 3])
|
||||
shapes = torch.as_tensor([(H, W)], dtype=torch.long).cuda()
|
||||
# shapes = torch.as_tensor([(H, W), (H // 2, W // 2)], dtype=torch.long).cuda()
|
||||
# shapes = torch.as_tensor([(H, W), (H // 2, W // 2), (H // 4, W // 4), (H // 8, W // 8)], dtype=torch.long).cuda()
|
||||
|
||||
H = 256
|
||||
W = 256
|
||||
N, M, D = 1, 8, 32
|
||||
Lq, L, P = 100*152, 4, 8
|
||||
|
||||
shapes = torch.Tensor([[100, 152], [ 50, 76], [ 25, 38], [ 13, 19]]).long().cuda()
|
||||
|
||||
level_start_index = torch.cat((shapes.new_zeros((1,)), shapes.prod(1).cumsum(0)[:-1]))
|
||||
S = sum([(H * W).item() for H, W in shapes])
|
||||
|
||||
def get_reference_points(spatial_shapes, 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] / (H_)
|
||||
ref_x = ref_x.reshape(-1)[None] / (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
|
||||
|
||||
|
||||
torch.manual_seed(3)
|
||||
|
||||
@torch.no_grad()
|
||||
def speed_test(func, args, inputs, name='Unknown'):
|
||||
|
||||
tic = Event(enable_timing=True)
|
||||
toc = Event(enable_timing=True)
|
||||
# warmup
|
||||
for i in range(args.warmup_num):
|
||||
func(*inputs)
|
||||
|
||||
tic.record()
|
||||
for i in range(args.test_num):
|
||||
func(*inputs)
|
||||
toc.record()
|
||||
torch.cuda.synchronize()
|
||||
|
||||
avg_time = tic.elapsed_time(toc) / args.test_num
|
||||
print(
|
||||
f'>>> {name: <10} finished {args.test_num} running, avg_time: {avg_time:.6f} ms')
|
||||
return avg_time
|
||||
|
||||
|
||||
def check_forward_equal_with_pytorch_half():
|
||||
value = torch.rand(N, S, M, D).cuda() * 0.01
|
||||
offset = (torch.rand(N, Lq, M, L, P, 2).cuda() * 2 - 1) / 10
|
||||
sampling_locations = torch.rand(N, Lq, M, L, P, 2).cuda()
|
||||
attention_weights_origin = torch.rand(N, Lq, M, L, P).cuda() + 1e-5
|
||||
attention_weights_origin.requires_grad = True
|
||||
sampling_loc_attn = torch.cat([sampling_locations.detach().reshape(N, Lq, M, L*P*2), attention_weights_origin.detach().reshape(N, Lq, M, L*P)], dim=-1)
|
||||
|
||||
attention_weights = torch.nn.functional.softmax(attention_weights_origin.flatten(-2, -1), dim=-1).unflatten(-1, (L, P))
|
||||
|
||||
|
||||
im2col_step = 128
|
||||
|
||||
value.requires_grad = True
|
||||
sampling_loc_attn.requires_grad = True
|
||||
output_cuda = (
|
||||
FlashDeformAttnFunction.apply(
|
||||
value.float(),
|
||||
shapes,
|
||||
level_start_index,
|
||||
sampling_loc_attn.float(),
|
||||
im2col_step,
|
||||
)
|
||||
)
|
||||
(output_cuda.float().sum()/10).backward()
|
||||
|
||||
|
||||
value1 = value.detach()
|
||||
value1.requires_grad = True
|
||||
sampling_locations.requires_grad = True
|
||||
#attention_weights.requires_grad = True
|
||||
output_pytorch = (
|
||||
ms_deform_attn_core_pytorch(value1, shapes, sampling_locations, attention_weights)
|
||||
)
|
||||
(output_pytorch.sum()/10).backward()
|
||||
|
||||
max_abs_err = (output_cuda.float() - output_pytorch).abs().max()
|
||||
max_rel_err = ((output_cuda.float() - output_pytorch).abs() / output_pytorch.abs()).max()
|
||||
fwdok = torch.allclose(output_cuda.float(), output_pytorch, rtol=1e-2, atol=1e-3)
|
||||
print(fwdok)
|
||||
print(max_abs_err, max_rel_err)
|
||||
#exit()
|
||||
|
||||
bwdok1 = torch.allclose(value.grad, value1.grad, rtol=1e-2, atol=1e-3)
|
||||
print(bwdok1)
|
||||
# rel_err = (sampling_locations.grad - sampling_loc_attn.grad[..., :L*P*2].reshape(*sampling_locations.shape)).abs()/(sampling_locations.grad.abs()+1e-3)
|
||||
# print(rel_err.max())
|
||||
|
||||
locgrad1 = sampling_locations.grad
|
||||
locgrad2 = sampling_loc_attn.grad[..., :L*P*2].reshape(*sampling_locations.shape)
|
||||
bwdok2 = torch.allclose(locgrad1, locgrad2, rtol=1e-2, atol=1e-3)
|
||||
print(bwdok2)
|
||||
rel_err = (locgrad1 - locgrad2).abs()/(locgrad1.abs()+1e-3)
|
||||
print(rel_err.max())
|
||||
|
||||
attngrad1 = attention_weights_origin.grad
|
||||
attngrad2 = sampling_loc_attn.grad[..., L*P*2:].reshape(*attention_weights_origin.shape)
|
||||
bwdok3 = torch.allclose(locgrad1, locgrad2, rtol=1e-2, atol=1e-3)
|
||||
print(bwdok3)
|
||||
rel_err = (attngrad1 - attngrad2).abs()/(attngrad1.abs()+1e-3)
|
||||
print(rel_err.max())
|
||||
exit()
|
||||
#exit()
|
||||
|
||||
# pdb.set_trace()
|
||||
max_abs_err = (output_cuda - output_pytorch).abs().max()
|
||||
max_rel_err = ((output_cuda - output_pytorch).abs() / output_pytorch.abs()).max()
|
||||
fwdok = torch.allclose(output_cuda, output_pytorch, rtol=1e-2, atol=1e-3)
|
||||
|
||||
print(
|
||||
f"* {fwdok} check_forward_equal_with_pytorch_float: max_abs_err {max_abs_err:.2e} max_rel_err {max_rel_err:.2e}"
|
||||
)
|
||||
|
||||
|
||||
fn_args = (
|
||||
value,
|
||||
shapes,
|
||||
level_start_index,
|
||||
sampling_locations,
|
||||
attention_weights,
|
||||
im2col_step,
|
||||
)
|
||||
|
||||
flash_dcn_fn_args = (
|
||||
value.half(),
|
||||
shapes,
|
||||
level_start_index,
|
||||
sampling_loc_attn.half(),
|
||||
im2col_step,
|
||||
)
|
||||
|
||||
|
||||
test_args = edict({'warmup_num': 50, 'test_num': 100})
|
||||
exp_time = speed_test(
|
||||
FlashMSDeformAttnFunction.apply, test_args, flash_dcn_fn_args, name='exp')
|
||||
exp_time_base = speed_test(
|
||||
MSDeformAttnFunction.apply, test_args, fn_args, name='exp')
|
||||
|
||||
results = [{}]
|
||||
results[0]['time'] = exp_time
|
||||
results[0]['time_base'] = exp_time_base
|
||||
columns = list(results[0].keys())
|
||||
|
||||
outputs = pd.DataFrame(results, columns=columns)
|
||||
with pd.option_context(
|
||||
'display.max_rows', None, 'display.max_columns', None,
|
||||
'display.max_colwidth', None, 'display.width', None,
|
||||
'display.precision', 4, ):
|
||||
print(outputs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_forward_equal_with_pytorch_half()
|
||||
Reference in New Issue
Block a user