From e4a440ef3407b4a4eb390c191457b1967af8ba61 Mon Sep 17 00:00:00 2001 From: mbolding3 Date: Wed, 15 Jun 2022 11:08:46 -0400 Subject: [PATCH 01/17] draft directory organization --- python/cusignal/pytorch/polyphase.py | 201 ++++++++++++++++++ .../cusignal/test/test_pytorch_polyphase.py | 2 + python/cusignal/test/test_radartools.py | 1 - 3 files changed, 203 insertions(+), 1 deletion(-) create mode 100755 python/cusignal/pytorch/polyphase.py create mode 100644 python/cusignal/test/test_pytorch_polyphase.py diff --git a/python/cusignal/pytorch/polyphase.py b/python/cusignal/pytorch/polyphase.py new file mode 100755 index 00000000..9dd6e459 --- /dev/null +++ b/python/cusignal/pytorch/polyphase.py @@ -0,0 +1,201 @@ +import torch +import numpy as np +import cupy as cp +from torch import flip +from torch.autograd import Function +from torch.nn.modules.module import Module +from torch.nn.parameter import Parameter +from torch.nn.init import uniform_ +from torch.autograd.gradcheck import gradcheck +from math import gcd +from cusignal import resample_poly +from cusignal import choose_conv_method +from cusignal import correlate + + +class FuncCusignalResample(Function): + @staticmethod + def get_start_index(length): + if length <= 2: + return 0 + return (length - 1) // 2 + + @staticmethod + def best_corr(sig1, sig2, mode): + method = choose_conv_method(sig1, sig2, mode = mode) + out = correlate(sig1, sig2, mode = mode, method = method) + return out + + @staticmethod + def forward(ctx, x, filter_coeffs, up, down): + device = x.device.type + x = x.detach() + filter_coeffs = filter_coeffs.detach() + up = up.detach() + down = down.detach() + + x_size = x.shape[0] + filt_size = filter_coeffs.shape[0] + up = int(up[0]) + down = int(down[0]) + ud_gcd = gcd(up, down) + up = up // ud_gcd + down = down // ud_gcd + + if (up == 1 and down == 1): + x_out = x + inverse_size = torch.Tensor([x.shape[0]]) + out_len = torch.Tensor([0]) + x_up = None + else: + if 'cuda' in device: + gpupath = True + window = cp.array(filter_coeffs) + else: + gpupath = False + window = filter_coeffs.numpy() + x_out = resample_poly(x, up, down, window = window, + gpupath = gpupath) + inverse_size = up * x_size + filt_size - 1 + x_up = torch.zeros(up * x_size, device = device, dtype = x.dtype) + x_up[::up] = up * x + + ctx.save_for_backward(torch.Tensor([x_size]), filter_coeffs, + torch.Tensor([up]), + torch.Tensor([down]), + torch.Tensor([inverse_size]), + torch.Tensor([len(x_out)]), x_up) + return(torch.Tensor(cp.asnumpy(x_out))) + + @staticmethod + def backward(ctx, gradient): + gradient = gradient.detach() + x_size, filter_coeffs, up, down, inverse_size, out_len, x_up \ + = ctx.saved_tensors + + device = gradient.device.type + x_size = int(x_size[0]) + gradient_size = gradient.shape[0] + filt_size = filter_coeffs.shape[0] + up = int(up[0]) + down = int(down[0]) + start = FuncCusignalResample.get_start_index(filt_size) + inverse_size = int(inverse_size) + out_x_len = int(out_len) + filter_coeffs = filter_coeffs.type(gradient.dtype) + + if (up == 1 and down == 1): + # J_x up \times J_x conv + out_x = gradient + # J_f conv + out_f = torch.zeros(filter_coeffs.shape[0], + device = device, + dtype = filter_coeffs.dtype) + else: + tmp = torch.zeros(out_x_len, device = device, + dtype = gradient.dtype) + tmp[:gradient.shape[0]] = gradient + gradient = tmp + gradient_up = torch.zeros(inverse_size, + device = device, + dtype = gradient.dtype) + extra = bool((inverse_size - start) % down) + tmp = torch.zeros((inverse_size - start) // down + extra, + device = device, + dtype = filter_coeffs.dtype) + tmp[:gradient.shape[0]] = gradient + gradient_up[start :: down] = torch.clone(tmp) + + out_x = FuncCusignalResample.best_corr(gradient_up, + filter_coeffs, + mode = 'valid') + out_x = up * out_x[::up] + out_f = FuncCusignalResample.best_corr(gradient_up, x_up, + mode = 'valid') + out_x = torch.as_tensor(out_x[:x_size], + device = device) + out_f = torch.as_tensor(out_f[:filter_coeffs.shape[0]], + device = device) + return(out_x, out_f, None, None) + + +class Resample(Module): + def __init__(self, up, down, filter_coeffs): + super(Resample, self).__init__() + self.up = up + self.down = down + self.filter_coeffs = filter_coeffs + + def forward(self, x): + return FuncCusignalResample.apply(x, self.filter_coeffs, self.up, + self.down) + + +def accept_reps(f): + def wrapper(repetitions = 1, **kwargs): + for i in range(repetitions): + f(**kwargs) + return wrapper + + +@accept_reps +def gradcheck_main(eps=1e-6, atol=1e-3, rtol=-1, device = 'cpu'): + ''' + Verifies that our backward method works. + ''' + up = torch.randint(1, 20, (1,), requires_grad = False) + down = torch.randint(1, 20, (1,), requires_grad = False) + filter_size = np.random.randint(10,30) + filter_coeffs = torch.randn(filter_size, requires_grad = True, + dtype = torch.double, + device = device) + inputs = torch.randn(100, dtype = torch.double, requires_grad = True, + device = device) + module = Resample(up, down, filter_coeffs) + kwargs = {"eps": eps} + if rtol > 0: + kwargs["rtol"] = rtol + else: + kwargs["atol"] = atol + gradcheck(module, inputs, **kwargs, raise_exception = True) + + +@accept_reps +def forward_main(gpupath = True): + ''' + Verifies that our module agress with scipy's implementation + on randomly generated examples. + + gpupath = True accepts cupy typed windows. + gpupath = False accepts numpy types windows. + ''' + if gpupath: + device = torch.device('cuda') + else: + device = torch.device('cpu') + x_size = np.random.randint(30, 100) + filter_size = np.random.randint(5, 20) + x = torch.randn(x_size, device = device) + up = torch.randint(1, 20, (1,), device = device) + down = torch.randint(1, 20, (1,), device = device) + window = torch.randn(filter_size, device = device) + # The module requires a torch tensor window + module = Resample(up, down, window) + # resample_poly requires a cupy or numpy array window + window = window.cpu().numpy() + if gpupath: + window = cp.array(window) + bench_resample = resample_poly(x, up, down, window = window, + gpupath = gpupath) + our_resample = module.forward(x) + if not np.allclose(bench_resample, our_resample, atol=1e-4): + print(f"up: {up}, down: {down}") + print(f"scipy result: {scipy_resample[:10]}") + print(f"our result: {our_resample[:10]}") + raise Exception("Forward main failure") + + +if __name__ == '__main__': + #forward_main(100) + gradcheck_main(100, eps = 1e-3, atol = 1e-1) + print("tests complete") diff --git a/python/cusignal/test/test_pytorch_polyphase.py b/python/cusignal/test/test_pytorch_polyphase.py new file mode 100644 index 00000000..f323d115 --- /dev/null +++ b/python/cusignal/test/test_pytorch_polyphase.py @@ -0,0 +1,2 @@ +import pytorch +import diff --git a/python/cusignal/test/test_radartools.py b/python/cusignal/test/test_radartools.py index f690e96a..6c272faf 100644 --- a/python/cusignal/test/test_radartools.py +++ b/python/cusignal/test/test_radartools.py @@ -1,7 +1,6 @@ import cupy as cp import pytest from numpy import vectorize - from cusignal.radartools import ca_cfar, cfar_alpha from cusignal.testing.utils import array_equal From 4936da60812682c9b5be75bd5b6db197567084d7 Mon Sep 17 00:00:00 2001 From: sn1572 Date: Thu, 23 Jun 2022 11:12:09 -0400 Subject: [PATCH 02/17] Minor refactoring of diff/polyphase --- .../cusignal/{pytorch => diff}/polyphase.py | 20 +++++++++---------- ...test_pytorch_polyphase.py => test_diff.py} | 1 - 2 files changed, 10 insertions(+), 11 deletions(-) rename python/cusignal/{pytorch => diff}/polyphase.py (91%) mode change 100755 => 100644 rename python/cusignal/test/{test_pytorch_polyphase.py => test_diff.py} (65%) diff --git a/python/cusignal/pytorch/polyphase.py b/python/cusignal/diff/polyphase.py old mode 100755 new mode 100644 similarity index 91% rename from python/cusignal/pytorch/polyphase.py rename to python/cusignal/diff/polyphase.py index 9dd6e459..df1ab52d --- a/python/cusignal/pytorch/polyphase.py +++ b/python/cusignal/diff/polyphase.py @@ -13,7 +13,7 @@ from cusignal import correlate -class FuncCusignalResample(Function): +class FuncPolyphase(Function): @staticmethod def get_start_index(length): if length <= 2: @@ -79,7 +79,7 @@ def backward(ctx, gradient): filt_size = filter_coeffs.shape[0] up = int(up[0]) down = int(down[0]) - start = FuncCusignalResample.get_start_index(filt_size) + start = FuncPolyphase.get_start_index(filt_size) inverse_size = int(inverse_size) out_x_len = int(out_len) filter_coeffs = filter_coeffs.type(gradient.dtype) @@ -106,12 +106,12 @@ def backward(ctx, gradient): tmp[:gradient.shape[0]] = gradient gradient_up[start :: down] = torch.clone(tmp) - out_x = FuncCusignalResample.best_corr(gradient_up, - filter_coeffs, - mode = 'valid') + out_x = FuncPolyphase.best_corr(gradient_up, + filter_coeffs, + mode = 'valid') out_x = up * out_x[::up] - out_f = FuncCusignalResample.best_corr(gradient_up, x_up, - mode = 'valid') + out_f = FuncPolyphase.best_corr(gradient_up, x_up, + mode = 'valid') out_x = torch.as_tensor(out_x[:x_size], device = device) out_f = torch.as_tensor(out_f[:filter_coeffs.shape[0]], @@ -119,7 +119,7 @@ def backward(ctx, gradient): return(out_x, out_f, None, None) -class Resample(Module): +class PolyphaseDiff(Module): def __init__(self, up, down, filter_coeffs): super(Resample, self).__init__() self.up = up @@ -127,8 +127,8 @@ def __init__(self, up, down, filter_coeffs): self.filter_coeffs = filter_coeffs def forward(self, x): - return FuncCusignalResample.apply(x, self.filter_coeffs, self.up, - self.down) + return FuncPolyphase.apply(x, self.filter_coeffs, self.up, + self.down) def accept_reps(f): diff --git a/python/cusignal/test/test_pytorch_polyphase.py b/python/cusignal/test/test_diff.py similarity index 65% rename from python/cusignal/test/test_pytorch_polyphase.py rename to python/cusignal/test/test_diff.py index f323d115..3105dc9f 100644 --- a/python/cusignal/test/test_pytorch_polyphase.py +++ b/python/cusignal/test/test_diff.py @@ -1,2 +1 @@ import pytorch -import From ac52cd9f7d24296a490f058fdaca079979961d5f Mon Sep 17 00:00:00 2001 From: sn1572 Date: Thu, 23 Jun 2022 11:30:10 -0400 Subject: [PATCH 03/17] Moving test stubs to their own file --- python/cusignal/diff/polyphase.py | 72 +------------------------------ python/cusignal/test/test_diff.py | 64 ++++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 72 deletions(-) diff --git a/python/cusignal/diff/polyphase.py b/python/cusignal/diff/polyphase.py index df1ab52d..d758ae1f 100644 --- a/python/cusignal/diff/polyphase.py +++ b/python/cusignal/diff/polyphase.py @@ -121,7 +121,7 @@ def backward(ctx, gradient): class PolyphaseDiff(Module): def __init__(self, up, down, filter_coeffs): - super(Resample, self).__init__() + super(PolyphaseDiff, self).__init__() self.up = up self.down = down self.filter_coeffs = filter_coeffs @@ -129,73 +129,3 @@ def __init__(self, up, down, filter_coeffs): def forward(self, x): return FuncPolyphase.apply(x, self.filter_coeffs, self.up, self.down) - - -def accept_reps(f): - def wrapper(repetitions = 1, **kwargs): - for i in range(repetitions): - f(**kwargs) - return wrapper - - -@accept_reps -def gradcheck_main(eps=1e-6, atol=1e-3, rtol=-1, device = 'cpu'): - ''' - Verifies that our backward method works. - ''' - up = torch.randint(1, 20, (1,), requires_grad = False) - down = torch.randint(1, 20, (1,), requires_grad = False) - filter_size = np.random.randint(10,30) - filter_coeffs = torch.randn(filter_size, requires_grad = True, - dtype = torch.double, - device = device) - inputs = torch.randn(100, dtype = torch.double, requires_grad = True, - device = device) - module = Resample(up, down, filter_coeffs) - kwargs = {"eps": eps} - if rtol > 0: - kwargs["rtol"] = rtol - else: - kwargs["atol"] = atol - gradcheck(module, inputs, **kwargs, raise_exception = True) - - -@accept_reps -def forward_main(gpupath = True): - ''' - Verifies that our module agress with scipy's implementation - on randomly generated examples. - - gpupath = True accepts cupy typed windows. - gpupath = False accepts numpy types windows. - ''' - if gpupath: - device = torch.device('cuda') - else: - device = torch.device('cpu') - x_size = np.random.randint(30, 100) - filter_size = np.random.randint(5, 20) - x = torch.randn(x_size, device = device) - up = torch.randint(1, 20, (1,), device = device) - down = torch.randint(1, 20, (1,), device = device) - window = torch.randn(filter_size, device = device) - # The module requires a torch tensor window - module = Resample(up, down, window) - # resample_poly requires a cupy or numpy array window - window = window.cpu().numpy() - if gpupath: - window = cp.array(window) - bench_resample = resample_poly(x, up, down, window = window, - gpupath = gpupath) - our_resample = module.forward(x) - if not np.allclose(bench_resample, our_resample, atol=1e-4): - print(f"up: {up}, down: {down}") - print(f"scipy result: {scipy_resample[:10]}") - print(f"our result: {our_resample[:10]}") - raise Exception("Forward main failure") - - -if __name__ == '__main__': - #forward_main(100) - gradcheck_main(100, eps = 1e-3, atol = 1e-1) - print("tests complete") diff --git a/python/cusignal/test/test_diff.py b/python/cusignal/test/test_diff.py index 3105dc9f..964feaab 100644 --- a/python/cusignal/test/test_diff.py +++ b/python/cusignal/test/test_diff.py @@ -1 +1,63 @@ -import pytorch +import torch +from cusignal.diff import + + +def gradcheck_main(eps=1e-6, atol=1e-3, rtol=-1, device = 'cpu'): + ''' + Verifies that our backward method works. + ''' + up = torch.randint(1, 20, (1,), requires_grad = False) + down = torch.randint(1, 20, (1,), requires_grad = False) + filter_size = np.random.randint(10,30) + filter_coeffs = torch.randn(filter_size, requires_grad = True, + dtype = torch.double, + device = device) + inputs = torch.randn(100, dtype = torch.double, requires_grad = True, + device = device) + module = PolyphaseDiff(up, down, filter_coeffs) + kwargs = {"eps": eps} + if rtol > 0: + kwargs["rtol"] = rtol + else: + kwargs["atol"] = atol + gradcheck(module, inputs, **kwargs, raise_exception = True) + + +def forward_main(gpupath = True): + ''' + Verifies that our module agress with scipy's implementation + on randomly generated examples. + + gpupath = True accepts cupy typed windows. + gpupath = False accepts numpy types windows. + ''' + if gpupath: + device = torch.device('cuda') + else: + device = torch.device('cpu') + x_size = np.random.randint(30, 100) + filter_size = np.random.randint(5, 20) + x = torch.randn(x_size, device = device) + up = torch.randint(1, 20, (1,), device = device) + down = torch.randint(1, 20, (1,), device = device) + window = torch.randn(filter_size, device = device) + # The module requires a torch tensor window + module = PolyphaseDiff(up, down, window) + # resample_poly requires a cupy or numpy array window + window = window.cpu().numpy() + if gpupath: + window = cp.array(window) + bench_resample = resample_poly(x, up, down, window = window, + gpupath = gpupath) + our_resample = module.forward(x) + if not np.allclose(bench_resample, our_resample, atol=1e-4): + print(f"up: {up}, down: {down}") + print(f"scipy result: {scipy_resample[:10]}") + print(f"our result: {our_resample[:10]}") + raise Exception("Forward main failure") + + +if __name__ == '__main__': + #forward_main(100) + gradcheck_main(100, eps = 1e-3, atol = 1e-1) + print("tests complete") From 756ad4227025553aaaac6a2d57b4cc53b3d5824c Mon Sep 17 00:00:00 2001 From: sn1572 Date: Mon, 27 Jun 2022 16:36:33 -0400 Subject: [PATCH 04/17] polyphase module passes basic unit tests --- python/cusignal/__init__.py | 1 + python/cusignal/diff/polyphase.py | 23 ++++++------- python/cusignal/test/test_diff.py | 54 +++++++++++++++++-------------- 3 files changed, 43 insertions(+), 35 deletions(-) diff --git a/python/cusignal/__init__.py b/python/cusignal/__init__.py index 8f1eab1c..9ae3d394 100644 --- a/python/cusignal/__init__.py +++ b/python/cusignal/__init__.py @@ -108,6 +108,7 @@ triang, tukey, ) +from cusignal.diff import PolyphaseDiff # Versioneer from ._version import get_versions diff --git a/python/cusignal/diff/polyphase.py b/python/cusignal/diff/polyphase.py index d758ae1f..169b85db 100644 --- a/python/cusignal/diff/polyphase.py +++ b/python/cusignal/diff/polyphase.py @@ -1,12 +1,8 @@ import torch import numpy as np import cupy as cp -from torch import flip from torch.autograd import Function from torch.nn.modules.module import Module -from torch.nn.parameter import Parameter -from torch.nn.init import uniform_ -from torch.autograd.gradcheck import gradcheck from math import gcd from cusignal import resample_poly from cusignal import choose_conv_method @@ -42,17 +38,21 @@ def forward(ctx, x, filter_coeffs, up, down): up = up // ud_gcd down = down // ud_gcd + if 'cuda' in device: + gpupath = True + else: + gpupath = False + if (up == 1 and down == 1): x_out = x - inverse_size = torch.Tensor([x.shape[0]]) - out_len = torch.Tensor([0]) + # These need device typing + inverse_size = torch.as_tensor([x.shape[0]], device=device) + out_len = torch.as_tensor([0], device=device) x_up = None else: - if 'cuda' in device: - gpupath = True + if gpupath: window = cp.array(filter_coeffs) else: - gpupath = False window = filter_coeffs.numpy() x_out = resample_poly(x, up, down, window = window, gpupath = gpupath) @@ -65,7 +65,8 @@ def forward(ctx, x, filter_coeffs, up, down): torch.Tensor([down]), torch.Tensor([inverse_size]), torch.Tensor([len(x_out)]), x_up) - return(torch.Tensor(cp.asnumpy(x_out))) + out = torch.as_tensor(cp.asnumpy(x_out), device=device) + return(out) @staticmethod def backward(ctx, gradient): @@ -73,7 +74,7 @@ def backward(ctx, gradient): x_size, filter_coeffs, up, down, inverse_size, out_len, x_up \ = ctx.saved_tensors - device = gradient.device.type + device = gradient.device x_size = int(x_size[0]) gradient_size = gradient.shape[0] filt_size = filter_coeffs.shape[0] diff --git a/python/cusignal/test/test_diff.py b/python/cusignal/test/test_diff.py index 964feaab..993ea541 100644 --- a/python/cusignal/test/test_diff.py +++ b/python/cusignal/test/test_diff.py @@ -1,14 +1,26 @@ import torch -from cusignal.diff import +import pytest +import numpy as np +#from cusignal.diff import PolyphaseDiff +import sys +sys.path.insert(0, '..') +from diff import PolyphaseDiff +from cusignal import resample_poly +from torch.autograd.gradcheck import gradcheck -def gradcheck_main(eps=1e-6, atol=1e-3, rtol=-1, device = 'cpu'): + +@pytest.mark.parametrize("device", ['cpu', 'cuda']) +@pytest.mark.parametrize("up", [1, 10, 20]) +@pytest.mark.parametrize("down", [1, 7, 15]) +@pytest.mark.parametrize("filter_size", [1, 10]) +def test_gradcheck(device, up, down, filter_size, + eps=1e-3, atol=1e-1, rtol=-1): ''' Verifies that our backward method works. ''' - up = torch.randint(1, 20, (1,), requires_grad = False) - down = torch.randint(1, 20, (1,), requires_grad = False) - filter_size = np.random.randint(10,30) + up = torch.Tensor([up]) + down = torch.Tensor([down]) filter_coeffs = torch.randn(filter_size, requires_grad = True, dtype = torch.double, device = device) @@ -23,7 +35,12 @@ def gradcheck_main(eps=1e-6, atol=1e-3, rtol=-1, device = 'cpu'): gradcheck(module, inputs, **kwargs, raise_exception = True) -def forward_main(gpupath = True): +@pytest.mark.parametrize("device", ['cpu', 'cuda']) +@pytest.mark.parametrize("x_size", [30, 100]) +@pytest.mark.parametrize("filter_size", [5, 20]) +@pytest.mark.parametrize("up", [1, 10, 20]) +@pytest.mark.parametrize("down", [1, 7, 15]) +def test_forward(device, x_size, up, down, filter_size): ''' Verifies that our module agress with scipy's implementation on randomly generated examples. @@ -31,15 +48,13 @@ def forward_main(gpupath = True): gpupath = True accepts cupy typed windows. gpupath = False accepts numpy types windows. ''' - if gpupath: - device = torch.device('cuda') - else: - device = torch.device('cpu') - x_size = np.random.randint(30, 100) - filter_size = np.random.randint(5, 20) + device = torch.device(device) + gpupath = True + if device != 'cuda': + gpupath = False x = torch.randn(x_size, device = device) - up = torch.randint(1, 20, (1,), device = device) - down = torch.randint(1, 20, (1,), device = device) + up = torch.Tensor([up]) + down = torch.Tensor([down]) window = torch.randn(filter_size, device = device) # The module requires a torch tensor window module = PolyphaseDiff(up, down, window) @@ -51,13 +66,4 @@ def forward_main(gpupath = True): gpupath = gpupath) our_resample = module.forward(x) if not np.allclose(bench_resample, our_resample, atol=1e-4): - print(f"up: {up}, down: {down}") - print(f"scipy result: {scipy_resample[:10]}") - print(f"our result: {our_resample[:10]}") - raise Exception("Forward main failure") - - -if __name__ == '__main__': - #forward_main(100) - gradcheck_main(100, eps = 1e-3, atol = 1e-1) - print("tests complete") + raise Exception("Module does not agree with resample") From 5637224cc96e2c0376f490d549885f2db183a20f Mon Sep 17 00:00:00 2001 From: sn1572 Date: Mon, 27 Jun 2022 16:38:53 -0400 Subject: [PATCH 05/17] Forgot to add submodule __init__ --- python/cusignal/diff/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 python/cusignal/diff/__init__.py diff --git a/python/cusignal/diff/__init__.py b/python/cusignal/diff/__init__.py new file mode 100644 index 00000000..48d62e20 --- /dev/null +++ b/python/cusignal/diff/__init__.py @@ -0,0 +1 @@ +from cusignal.diff.polyphase import PolyphaseDiff From 354faa2cf43948bb3635ed7dd4db18951593fe30 Mon Sep 17 00:00:00 2001 From: sn1572 Date: Mon, 27 Jun 2022 16:42:17 -0400 Subject: [PATCH 06/17] Removed cusignal-level import in case someone doesn't have pytorch installed --- python/cusignal/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/cusignal/__init__.py b/python/cusignal/__init__.py index 9ae3d394..8f1eab1c 100644 --- a/python/cusignal/__init__.py +++ b/python/cusignal/__init__.py @@ -108,7 +108,6 @@ triang, tukey, ) -from cusignal.diff import PolyphaseDiff # Versioneer from ._version import get_versions From 685d9e99a06c82048e100c824278e513e7ec1d0d Mon Sep 17 00:00:00 2001 From: sn1572 Date: Tue, 28 Jun 2022 09:46:12 -0400 Subject: [PATCH 07/17] Streamlining the backward method - goal is to implement with another polyphase_resample call --- python/cusignal/diff/__init__.py | 2 +- python/cusignal/diff/polyphase.py | 34 ++++++++++++------------------- python/cusignal/test/test_diff.py | 22 ++++++++------------ 3 files changed, 23 insertions(+), 35 deletions(-) diff --git a/python/cusignal/diff/__init__.py b/python/cusignal/diff/__init__.py index 48d62e20..7520b93d 100644 --- a/python/cusignal/diff/__init__.py +++ b/python/cusignal/diff/__init__.py @@ -1 +1 @@ -from cusignal.diff.polyphase import PolyphaseDiff +from cusignal.diff.polyphase import ResamplePoly diff --git a/python/cusignal/diff/polyphase.py b/python/cusignal/diff/polyphase.py index 169b85db..d6d82482 100644 --- a/python/cusignal/diff/polyphase.py +++ b/python/cusignal/diff/polyphase.py @@ -9,7 +9,7 @@ from cusignal import correlate -class FuncPolyphase(Function): +class FuncResamplePoly(Function): @staticmethod def get_start_index(length): if length <= 2: @@ -80,7 +80,7 @@ def backward(ctx, gradient): filt_size = filter_coeffs.shape[0] up = int(up[0]) down = int(down[0]) - start = FuncPolyphase.get_start_index(filt_size) + start = FuncResamplePoly.get_start_index(filt_size) inverse_size = int(inverse_size) out_x_len = int(out_len) filter_coeffs = filter_coeffs.type(gradient.dtype) @@ -93,26 +93,18 @@ def backward(ctx, gradient): device = device, dtype = filter_coeffs.dtype) else: - tmp = torch.zeros(out_x_len, device = device, - dtype = gradient.dtype) - tmp[:gradient.shape[0]] = gradient - gradient = tmp gradient_up = torch.zeros(inverse_size, device = device, dtype = gradient.dtype) - extra = bool((inverse_size - start) % down) - tmp = torch.zeros((inverse_size - start) // down + extra, - device = device, - dtype = filter_coeffs.dtype) - tmp[:gradient.shape[0]] = gradient - gradient_up[start :: down] = torch.clone(tmp) + gradient_up[start : start + down * gradient.shape[0] : down] =\ + gradient - out_x = FuncPolyphase.best_corr(gradient_up, - filter_coeffs, - mode = 'valid') + out_x = FuncResamplePoly.best_corr(gradient_up, + filter_coeffs, + mode = 'valid') out_x = up * out_x[::up] - out_f = FuncPolyphase.best_corr(gradient_up, x_up, - mode = 'valid') + out_f = FuncResamplePoly.best_corr(gradient_up, x_up, + mode = 'valid') out_x = torch.as_tensor(out_x[:x_size], device = device) out_f = torch.as_tensor(out_f[:filter_coeffs.shape[0]], @@ -120,13 +112,13 @@ def backward(ctx, gradient): return(out_x, out_f, None, None) -class PolyphaseDiff(Module): +class ResamplePoly(Module): def __init__(self, up, down, filter_coeffs): - super(PolyphaseDiff, self).__init__() + super(ResamplePoly, self).__init__() self.up = up self.down = down self.filter_coeffs = filter_coeffs def forward(self, x): - return FuncPolyphase.apply(x, self.filter_coeffs, self.up, - self.down) + return FuncResamplePoly.apply(x, self.filter_coeffs, self.up, + self.down) diff --git a/python/cusignal/test/test_diff.py b/python/cusignal/test/test_diff.py index 993ea541..10236c8f 100644 --- a/python/cusignal/test/test_diff.py +++ b/python/cusignal/test/test_diff.py @@ -1,18 +1,14 @@ import torch import pytest -import numpy as np -#from cusignal.diff import PolyphaseDiff -import sys -sys.path.insert(0, '..') -from diff import PolyphaseDiff - +from numpy import allclose +from cusignal.diff import ResamplePoly from cusignal import resample_poly from torch.autograd.gradcheck import gradcheck @pytest.mark.parametrize("device", ['cpu', 'cuda']) -@pytest.mark.parametrize("up", [1, 10, 20]) -@pytest.mark.parametrize("down", [1, 7, 15]) +@pytest.mark.parametrize("up", [1, 10]) +@pytest.mark.parametrize("down", [1, 7, 10, 13]) @pytest.mark.parametrize("filter_size", [1, 10]) def test_gradcheck(device, up, down, filter_size, eps=1e-3, atol=1e-1, rtol=-1): @@ -26,7 +22,7 @@ def test_gradcheck(device, up, down, filter_size, device = device) inputs = torch.randn(100, dtype = torch.double, requires_grad = True, device = device) - module = PolyphaseDiff(up, down, filter_coeffs) + module = ResamplePoly(up, down, filter_coeffs) kwargs = {"eps": eps} if rtol > 0: kwargs["rtol"] = rtol @@ -38,8 +34,8 @@ def test_gradcheck(device, up, down, filter_size, @pytest.mark.parametrize("device", ['cpu', 'cuda']) @pytest.mark.parametrize("x_size", [30, 100]) @pytest.mark.parametrize("filter_size", [5, 20]) -@pytest.mark.parametrize("up", [1, 10, 20]) -@pytest.mark.parametrize("down", [1, 7, 15]) +@pytest.mark.parametrize("up", [1, 10]) +@pytest.mark.parametrize("down", [1, 7, 10]) def test_forward(device, x_size, up, down, filter_size): ''' Verifies that our module agress with scipy's implementation @@ -57,7 +53,7 @@ def test_forward(device, x_size, up, down, filter_size): down = torch.Tensor([down]) window = torch.randn(filter_size, device = device) # The module requires a torch tensor window - module = PolyphaseDiff(up, down, window) + module = ResamplePoly(up, down, window) # resample_poly requires a cupy or numpy array window window = window.cpu().numpy() if gpupath: @@ -65,5 +61,5 @@ def test_forward(device, x_size, up, down, filter_size): bench_resample = resample_poly(x, up, down, window = window, gpupath = gpupath) our_resample = module.forward(x) - if not np.allclose(bench_resample, our_resample, atol=1e-4): + if not allclose(bench_resample, our_resample, atol=1e-4): raise Exception("Module does not agree with resample") From 790b98d744a5813ace8a029cce88968dcdf0ab26 Mon Sep 17 00:00:00 2001 From: sn1572 Date: Tue, 28 Jun 2022 11:08:31 -0400 Subject: [PATCH 08/17] Fixed a redundancy --- python/cusignal/diff/polyphase.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/python/cusignal/diff/polyphase.py b/python/cusignal/diff/polyphase.py index d6d82482..5bee38ab 100644 --- a/python/cusignal/diff/polyphase.py +++ b/python/cusignal/diff/polyphase.py @@ -44,9 +44,9 @@ def forward(ctx, x, filter_coeffs, up, down): gpupath = False if (up == 1 and down == 1): - x_out = x + out_x = x # These need device typing - inverse_size = torch.as_tensor([x.shape[0]], device=device) + inverse_size = x.shape[0] out_len = torch.as_tensor([0], device=device) x_up = None else: @@ -54,7 +54,7 @@ def forward(ctx, x, filter_coeffs, up, down): window = cp.array(filter_coeffs) else: window = filter_coeffs.numpy() - x_out = resample_poly(x, up, down, window = window, + out_x = resample_poly(x, up, down, window = window, gpupath = gpupath) inverse_size = up * x_size + filt_size - 1 x_up = torch.zeros(up * x_size, device = device, dtype = x.dtype) @@ -64,8 +64,8 @@ def forward(ctx, x, filter_coeffs, up, down): torch.Tensor([up]), torch.Tensor([down]), torch.Tensor([inverse_size]), - torch.Tensor([len(x_out)]), x_up) - out = torch.as_tensor(cp.asnumpy(x_out), device=device) + torch.Tensor([len(out_x)]), x_up) + out = torch.as_tensor(cp.asnumpy(out_x), device=device) return(out) @staticmethod @@ -74,7 +74,7 @@ def backward(ctx, gradient): x_size, filter_coeffs, up, down, inverse_size, out_len, x_up \ = ctx.saved_tensors - device = gradient.device + device = gradient.device.type x_size = int(x_size[0]) gradient_size = gradient.shape[0] filt_size = filter_coeffs.shape[0] @@ -105,6 +105,7 @@ def backward(ctx, gradient): out_x = up * out_x[::up] out_f = FuncResamplePoly.best_corr(gradient_up, x_up, mode = 'valid') + out_x = torch.as_tensor(out_x[:x_size], device = device) out_f = torch.as_tensor(out_f[:filter_coeffs.shape[0]], From ee4b59ef370b84a1f35058519e029aaef6fb6211 Mon Sep 17 00:00:00 2001 From: sn1572 Date: Tue, 28 Jun 2022 11:15:31 -0400 Subject: [PATCH 09/17] Continuing to remove code cruft --- python/cusignal/diff/polyphase.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/python/cusignal/diff/polyphase.py b/python/cusignal/diff/polyphase.py index 5bee38ab..978afb89 100644 --- a/python/cusignal/diff/polyphase.py +++ b/python/cusignal/diff/polyphase.py @@ -45,9 +45,7 @@ def forward(ctx, x, filter_coeffs, up, down): if (up == 1 and down == 1): out_x = x - # These need device typing inverse_size = x.shape[0] - out_len = torch.as_tensor([0], device=device) x_up = None else: if gpupath: From c57373feb1e2ff5a1230c316cb670016850d2a28 Mon Sep 17 00:00:00 2001 From: sn1572 Date: Tue, 28 Jun 2022 11:47:26 -0400 Subject: [PATCH 10/17] Skip diff tests if pytorch unavailable --- python/cusignal/diff/__init__.py | 2 +- python/cusignal/diff/resample.py | 123 ++++++++++++++++++++++++++++++ python/cusignal/test/test_diff.py | 16 ++-- 3 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 python/cusignal/diff/resample.py diff --git a/python/cusignal/diff/__init__.py b/python/cusignal/diff/__init__.py index 7520b93d..162157d7 100644 --- a/python/cusignal/diff/__init__.py +++ b/python/cusignal/diff/__init__.py @@ -1 +1 @@ -from cusignal.diff.polyphase import ResamplePoly +from cusignal.diff.resample import ResamplePoly diff --git a/python/cusignal/diff/resample.py b/python/cusignal/diff/resample.py new file mode 100644 index 00000000..978afb89 --- /dev/null +++ b/python/cusignal/diff/resample.py @@ -0,0 +1,123 @@ +import torch +import numpy as np +import cupy as cp +from torch.autograd import Function +from torch.nn.modules.module import Module +from math import gcd +from cusignal import resample_poly +from cusignal import choose_conv_method +from cusignal import correlate + + +class FuncResamplePoly(Function): + @staticmethod + def get_start_index(length): + if length <= 2: + return 0 + return (length - 1) // 2 + + @staticmethod + def best_corr(sig1, sig2, mode): + method = choose_conv_method(sig1, sig2, mode = mode) + out = correlate(sig1, sig2, mode = mode, method = method) + return out + + @staticmethod + def forward(ctx, x, filter_coeffs, up, down): + device = x.device.type + x = x.detach() + filter_coeffs = filter_coeffs.detach() + up = up.detach() + down = down.detach() + + x_size = x.shape[0] + filt_size = filter_coeffs.shape[0] + up = int(up[0]) + down = int(down[0]) + ud_gcd = gcd(up, down) + up = up // ud_gcd + down = down // ud_gcd + + if 'cuda' in device: + gpupath = True + else: + gpupath = False + + if (up == 1 and down == 1): + out_x = x + inverse_size = x.shape[0] + x_up = None + else: + if gpupath: + window = cp.array(filter_coeffs) + else: + window = filter_coeffs.numpy() + out_x = resample_poly(x, up, down, window = window, + gpupath = gpupath) + inverse_size = up * x_size + filt_size - 1 + x_up = torch.zeros(up * x_size, device = device, dtype = x.dtype) + x_up[::up] = up * x + + ctx.save_for_backward(torch.Tensor([x_size]), filter_coeffs, + torch.Tensor([up]), + torch.Tensor([down]), + torch.Tensor([inverse_size]), + torch.Tensor([len(out_x)]), x_up) + out = torch.as_tensor(cp.asnumpy(out_x), device=device) + return(out) + + @staticmethod + def backward(ctx, gradient): + gradient = gradient.detach() + x_size, filter_coeffs, up, down, inverse_size, out_len, x_up \ + = ctx.saved_tensors + + device = gradient.device.type + x_size = int(x_size[0]) + gradient_size = gradient.shape[0] + filt_size = filter_coeffs.shape[0] + up = int(up[0]) + down = int(down[0]) + start = FuncResamplePoly.get_start_index(filt_size) + inverse_size = int(inverse_size) + out_x_len = int(out_len) + filter_coeffs = filter_coeffs.type(gradient.dtype) + + if (up == 1 and down == 1): + # J_x up \times J_x conv + out_x = gradient + # J_f conv + out_f = torch.zeros(filter_coeffs.shape[0], + device = device, + dtype = filter_coeffs.dtype) + else: + gradient_up = torch.zeros(inverse_size, + device = device, + dtype = gradient.dtype) + gradient_up[start : start + down * gradient.shape[0] : down] =\ + gradient + + out_x = FuncResamplePoly.best_corr(gradient_up, + filter_coeffs, + mode = 'valid') + out_x = up * out_x[::up] + out_f = FuncResamplePoly.best_corr(gradient_up, x_up, + mode = 'valid') + + out_x = torch.as_tensor(out_x[:x_size], + device = device) + out_f = torch.as_tensor(out_f[:filter_coeffs.shape[0]], + device = device) + return(out_x, out_f, None, None) + + +class ResamplePoly(Module): + def __init__(self, up, down, filter_coeffs): + super(ResamplePoly, self).__init__() + self.up = up + self.down = down + self.filter_coeffs = filter_coeffs + + def forward(self, x): + return FuncResamplePoly.apply(x, self.filter_coeffs, self.up, + self.down) diff --git a/python/cusignal/test/test_diff.py b/python/cusignal/test/test_diff.py index 10236c8f..ed7bf47c 100644 --- a/python/cusignal/test/test_diff.py +++ b/python/cusignal/test/test_diff.py @@ -1,14 +1,20 @@ -import torch import pytest from numpy import allclose -from cusignal.diff import ResamplePoly from cusignal import resample_poly -from torch.autograd.gradcheck import gradcheck + + +try: + import torch + from cusignal.diff import ResamplePoly + from torch.autograd.gradcheck import gradcheck +except ImportError: + pytest.skip(f"skipping pytorch dependant tests in {__file__}", + allow_module_level = True) @pytest.mark.parametrize("device", ['cpu', 'cuda']) -@pytest.mark.parametrize("up", [1, 10]) -@pytest.mark.parametrize("down", [1, 7, 10, 13]) +@pytest.mark.parametrize("up", [1, 3, 10]) +@pytest.mark.parametrize("down", [1, 7, 10]) @pytest.mark.parametrize("filter_size", [1, 10]) def test_gradcheck(device, up, down, filter_size, eps=1e-3, atol=1e-1, rtol=-1): From 5b99d03fbac8c9454e2ac10a2967b5d003d8dcdc Mon Sep 17 00:00:00 2001 From: sn1572 Date: Tue, 28 Jun 2022 12:35:45 -0400 Subject: [PATCH 11/17] resample.py passes pycodestyle checks --- python/cusignal/diff/polyphase.py | 123 ------------------------------ python/cusignal/diff/resample.py | 72 ++++++++--------- 2 files changed, 36 insertions(+), 159 deletions(-) delete mode 100644 python/cusignal/diff/polyphase.py diff --git a/python/cusignal/diff/polyphase.py b/python/cusignal/diff/polyphase.py deleted file mode 100644 index 978afb89..00000000 --- a/python/cusignal/diff/polyphase.py +++ /dev/null @@ -1,123 +0,0 @@ -import torch -import numpy as np -import cupy as cp -from torch.autograd import Function -from torch.nn.modules.module import Module -from math import gcd -from cusignal import resample_poly -from cusignal import choose_conv_method -from cusignal import correlate - - -class FuncResamplePoly(Function): - @staticmethod - def get_start_index(length): - if length <= 2: - return 0 - return (length - 1) // 2 - - @staticmethod - def best_corr(sig1, sig2, mode): - method = choose_conv_method(sig1, sig2, mode = mode) - out = correlate(sig1, sig2, mode = mode, method = method) - return out - - @staticmethod - def forward(ctx, x, filter_coeffs, up, down): - device = x.device.type - x = x.detach() - filter_coeffs = filter_coeffs.detach() - up = up.detach() - down = down.detach() - - x_size = x.shape[0] - filt_size = filter_coeffs.shape[0] - up = int(up[0]) - down = int(down[0]) - ud_gcd = gcd(up, down) - up = up // ud_gcd - down = down // ud_gcd - - if 'cuda' in device: - gpupath = True - else: - gpupath = False - - if (up == 1 and down == 1): - out_x = x - inverse_size = x.shape[0] - x_up = None - else: - if gpupath: - window = cp.array(filter_coeffs) - else: - window = filter_coeffs.numpy() - out_x = resample_poly(x, up, down, window = window, - gpupath = gpupath) - inverse_size = up * x_size + filt_size - 1 - x_up = torch.zeros(up * x_size, device = device, dtype = x.dtype) - x_up[::up] = up * x - - ctx.save_for_backward(torch.Tensor([x_size]), filter_coeffs, - torch.Tensor([up]), - torch.Tensor([down]), - torch.Tensor([inverse_size]), - torch.Tensor([len(out_x)]), x_up) - out = torch.as_tensor(cp.asnumpy(out_x), device=device) - return(out) - - @staticmethod - def backward(ctx, gradient): - gradient = gradient.detach() - x_size, filter_coeffs, up, down, inverse_size, out_len, x_up \ - = ctx.saved_tensors - - device = gradient.device.type - x_size = int(x_size[0]) - gradient_size = gradient.shape[0] - filt_size = filter_coeffs.shape[0] - up = int(up[0]) - down = int(down[0]) - start = FuncResamplePoly.get_start_index(filt_size) - inverse_size = int(inverse_size) - out_x_len = int(out_len) - filter_coeffs = filter_coeffs.type(gradient.dtype) - - if (up == 1 and down == 1): - # J_x up \times J_x conv - out_x = gradient - # J_f conv - out_f = torch.zeros(filter_coeffs.shape[0], - device = device, - dtype = filter_coeffs.dtype) - else: - gradient_up = torch.zeros(inverse_size, - device = device, - dtype = gradient.dtype) - gradient_up[start : start + down * gradient.shape[0] : down] =\ - gradient - - out_x = FuncResamplePoly.best_corr(gradient_up, - filter_coeffs, - mode = 'valid') - out_x = up * out_x[::up] - out_f = FuncResamplePoly.best_corr(gradient_up, x_up, - mode = 'valid') - - out_x = torch.as_tensor(out_x[:x_size], - device = device) - out_f = torch.as_tensor(out_f[:filter_coeffs.shape[0]], - device = device) - return(out_x, out_f, None, None) - - -class ResamplePoly(Module): - def __init__(self, up, down, filter_coeffs): - super(ResamplePoly, self).__init__() - self.up = up - self.down = down - self.filter_coeffs = filter_coeffs - - def forward(self, x): - return FuncResamplePoly.apply(x, self.filter_coeffs, self.up, - self.down) diff --git a/python/cusignal/diff/resample.py b/python/cusignal/diff/resample.py index 978afb89..8e800a8b 100644 --- a/python/cusignal/diff/resample.py +++ b/python/cusignal/diff/resample.py @@ -15,28 +15,28 @@ def get_start_index(length): if length <= 2: return 0 return (length - 1) // 2 - + @staticmethod def best_corr(sig1, sig2, mode): - method = choose_conv_method(sig1, sig2, mode = mode) - out = correlate(sig1, sig2, mode = mode, method = method) + method = choose_conv_method(sig1, sig2, mode=mode) + out = correlate(sig1, sig2, mode=mode, method=method) return out @staticmethod def forward(ctx, x, filter_coeffs, up, down): - device = x.device.type - x = x.detach() + device = x.device.type + x = x.detach() filter_coeffs = filter_coeffs.detach() - up = up.detach() - down = down.detach() + up = up.detach() + down = down.detach() - x_size = x.shape[0] + x_size = x.shape[0] filt_size = filter_coeffs.shape[0] - up = int(up[0]) - down = int(down[0]) - ud_gcd = gcd(up, down) - up = up // ud_gcd - down = down // ud_gcd + up = int(up[0]) + down = int(down[0]) + ud_gcd = gcd(up, down) + up = up // ud_gcd + down = down // ud_gcd if 'cuda' in device: gpupath = True @@ -49,13 +49,13 @@ def forward(ctx, x, filter_coeffs, up, down): x_up = None else: if gpupath: - window = cp.array(filter_coeffs) + window = cp.array(filter_coeffs) else: - window = filter_coeffs.numpy() - out_x = resample_poly(x, up, down, window = window, - gpupath = gpupath) + window = filter_coeffs.numpy() + out_x = resample_poly(x, up, down, window=window, + gpupath=gpupath) inverse_size = up * x_size + filt_size - 1 - x_up = torch.zeros(up * x_size, device = device, dtype = x.dtype) + x_up = torch.zeros(up * x_size, device=device, dtype=x.dtype) x_up[::up] = up * x ctx.save_for_backward(torch.Tensor([x_size]), filter_coeffs, @@ -70,17 +70,17 @@ def forward(ctx, x, filter_coeffs, up, down): def backward(ctx, gradient): gradient = gradient.detach() x_size, filter_coeffs, up, down, inverse_size, out_len, x_up \ - = ctx.saved_tensors + = ctx.saved_tensors - device = gradient.device.type - x_size = int(x_size[0]) + device = gradient.device.type + x_size = int(x_size[0]) gradient_size = gradient.shape[0] - filt_size = filter_coeffs.shape[0] - up = int(up[0]) - down = int(down[0]) - start = FuncResamplePoly.get_start_index(filt_size) - inverse_size = int(inverse_size) - out_x_len = int(out_len) + filt_size = filter_coeffs.shape[0] + up = int(up[0]) + down = int(down[0]) + start = FuncResamplePoly.get_start_index(filt_size) + inverse_size = int(inverse_size) + out_x_len = int(out_len) filter_coeffs = filter_coeffs.type(gradient.dtype) if (up == 1 and down == 1): @@ -88,26 +88,26 @@ def backward(ctx, gradient): out_x = gradient # J_f conv out_f = torch.zeros(filter_coeffs.shape[0], - device = device, - dtype = filter_coeffs.dtype) + device=device, + dtype=filter_coeffs.dtype) else: gradient_up = torch.zeros(inverse_size, - device = device, - dtype = gradient.dtype) - gradient_up[start : start + down * gradient.shape[0] : down] =\ + device=device, + dtype=gradient.dtype) + gradient_up[start: start + down * gradient.shape[0]: down] =\ gradient out_x = FuncResamplePoly.best_corr(gradient_up, filter_coeffs, - mode = 'valid') + mode='valid') out_x = up * out_x[::up] out_f = FuncResamplePoly.best_corr(gradient_up, x_up, - mode = 'valid') + mode='valid') out_x = torch.as_tensor(out_x[:x_size], - device = device) + device=device) out_f = torch.as_tensor(out_f[:filter_coeffs.shape[0]], - device = device) + device=device) return(out_x, out_f, None, None) From 5af824d01f128ff0615a39b771bf2fd8fd63d9bf Mon Sep 17 00:00:00 2001 From: sn1572 Date: Tue, 28 Jun 2022 14:35:26 -0400 Subject: [PATCH 12/17] Added backprop example to tests and class method for computing output size --- python/cusignal/diff/resample.py | 13 ++++++++++--- python/cusignal/test/test_diff.py | 26 +++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/python/cusignal/diff/resample.py b/python/cusignal/diff/resample.py index 8e800a8b..e9163bfb 100644 --- a/python/cusignal/diff/resample.py +++ b/python/cusignal/diff/resample.py @@ -3,6 +3,7 @@ import cupy as cp from torch.autograd import Function from torch.nn.modules.module import Module +from torch.nn.parameter import Parameter from math import gcd from cusignal import resample_poly from cusignal import choose_conv_method @@ -114,10 +115,16 @@ def backward(ctx, gradient): class ResamplePoly(Module): def __init__(self, up, down, filter_coeffs): super(ResamplePoly, self).__init__() - self.up = up - self.down = down - self.filter_coeffs = filter_coeffs + self.up = torch.Tensor([up]) + self.down = torch.Tensor([down]) + self.filter_coeffs = Parameter(filter_coeffs) def forward(self, x): return FuncResamplePoly.apply(x, self.filter_coeffs, self.up, self.down) + + @classmethod + def output_size(self, input_size, up, down): + out_size = input_size * up + out_size = out_size // down + bool(out_size % down) + return out_size diff --git a/python/cusignal/test/test_diff.py b/python/cusignal/test/test_diff.py index ed7bf47c..af2b0d8e 100644 --- a/python/cusignal/test/test_diff.py +++ b/python/cusignal/test/test_diff.py @@ -1,4 +1,5 @@ import pytest +from numpy import sqrt from numpy import allclose from cusignal import resample_poly @@ -21,8 +22,10 @@ def test_gradcheck(device, up, down, filter_size, ''' Verifies that our backward method works. ''' + ''' up = torch.Tensor([up]) down = torch.Tensor([down]) + ''' filter_coeffs = torch.randn(filter_size, requires_grad = True, dtype = torch.double, device = device) @@ -55,8 +58,10 @@ def test_forward(device, x_size, up, down, filter_size): if device != 'cuda': gpupath = False x = torch.randn(x_size, device = device) + ''' up = torch.Tensor([up]) down = torch.Tensor([down]) + ''' window = torch.randn(filter_size, device = device) # The module requires a torch tensor window module = ResamplePoly(up, down, window) @@ -67,5 +72,24 @@ def test_forward(device, x_size, up, down, filter_size): bench_resample = resample_poly(x, up, down, window = window, gpupath = gpupath) our_resample = module.forward(x) - if not allclose(bench_resample, our_resample, atol=1e-4): + if not allclose(bench_resample, our_resample.detach().cpu(), atol=1e-4): raise Exception("Module does not agree with resample") + + +def test_backprop(device='cuda', iters=100, filter_size=10, up=7, + down=3, x_size=1000): + ''' + Demonstration of how to use ResamplePoly with back prop + ''' + x = torch.linspace(-sqrt(10), sqrt(10), x_size, device='cuda') + y = torch.sin(x) + window = torch.randn(filter_size, device=device) + model = torch.nn.Sequential( + ResamplePoly(up, down, window), + torch.nn.Linear(in_features=ResamplePoly.output_size(x_size, up, down), + out_features=1000).to(device) + ) + loss_fn = torch.nn.MSELoss(reduction='sum') + y_pred = model(x) + loss = loss_fn(y_pred, y) + loss.backward() From 7fb06890a48b19a865760906e637b9e607291606 Mon Sep 17 00:00:00 2001 From: sn1572 Date: Tue, 28 Jun 2022 14:43:33 -0400 Subject: [PATCH 13/17] enforced pycodestyle on test_diff --- python/cusignal/test/test_diff.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/python/cusignal/test/test_diff.py b/python/cusignal/test/test_diff.py index af2b0d8e..01751410 100644 --- a/python/cusignal/test/test_diff.py +++ b/python/cusignal/test/test_diff.py @@ -7,10 +7,10 @@ try: import torch from cusignal.diff import ResamplePoly - from torch.autograd.gradcheck import gradcheck + from torch.autograd.gradcheck import gradcheck except ImportError: pytest.skip(f"skipping pytorch dependant tests in {__file__}", - allow_module_level = True) + allow_module_level=True) @pytest.mark.parametrize("device", ['cpu', 'cuda']) @@ -26,18 +26,18 @@ def test_gradcheck(device, up, down, filter_size, up = torch.Tensor([up]) down = torch.Tensor([down]) ''' - filter_coeffs = torch.randn(filter_size, requires_grad = True, - dtype = torch.double, - device = device) - inputs = torch.randn(100, dtype = torch.double, requires_grad = True, - device = device) + filter_coeffs = torch.randn(filter_size, requires_grad=True, + dtype=torch.double, + device=device) + inputs = torch.randn(100, dtype=torch.double, requires_grad=True, + device=device) module = ResamplePoly(up, down, filter_coeffs) kwargs = {"eps": eps} if rtol > 0: kwargs["rtol"] = rtol else: kwargs["atol"] = atol - gradcheck(module, inputs, **kwargs, raise_exception = True) + gradcheck(module, inputs, **kwargs, raise_exception=True) @pytest.mark.parametrize("device", ['cpu', 'cuda']) @@ -57,20 +57,20 @@ def test_forward(device, x_size, up, down, filter_size): gpupath = True if device != 'cuda': gpupath = False - x = torch.randn(x_size, device = device) + x = torch.randn(x_size, device=device) ''' up = torch.Tensor([up]) down = torch.Tensor([down]) ''' - window = torch.randn(filter_size, device = device) + window = torch.randn(filter_size, device=device) # The module requires a torch tensor window module = ResamplePoly(up, down, window) # resample_poly requires a cupy or numpy array window window = window.cpu().numpy() if gpupath: window = cp.array(window) - bench_resample = resample_poly(x, up, down, window = window, - gpupath = gpupath) + bench_resample = resample_poly(x, up, down, window=window, + gpupath=gpupath) our_resample = module.forward(x) if not allclose(bench_resample, our_resample.detach().cpu(), atol=1e-4): raise Exception("Module does not agree with resample") From e7eccc3ac9ed2c53b1f96c624cd8dcaa14d17913 Mon Sep 17 00:00:00 2001 From: sn1572 Date: Tue, 28 Jun 2022 15:00:19 -0400 Subject: [PATCH 14/17] Removed commented lines --- python/cusignal/test/test_diff.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/python/cusignal/test/test_diff.py b/python/cusignal/test/test_diff.py index 01751410..2af749e0 100644 --- a/python/cusignal/test/test_diff.py +++ b/python/cusignal/test/test_diff.py @@ -22,10 +22,6 @@ def test_gradcheck(device, up, down, filter_size, ''' Verifies that our backward method works. ''' - ''' - up = torch.Tensor([up]) - down = torch.Tensor([down]) - ''' filter_coeffs = torch.randn(filter_size, requires_grad=True, dtype=torch.double, device=device) @@ -58,10 +54,6 @@ def test_forward(device, x_size, up, down, filter_size): if device != 'cuda': gpupath = False x = torch.randn(x_size, device=device) - ''' - up = torch.Tensor([up]) - down = torch.Tensor([down]) - ''' window = torch.randn(filter_size, device=device) # The module requires a torch tensor window module = ResamplePoly(up, down, window) From e1cf1418655389133f15fcb5e1be17063beabf0a Mon Sep 17 00:00:00 2001 From: sn1572 Date: Wed, 29 Jun 2022 09:24:46 -0400 Subject: [PATCH 15/17] Enforced flake8 style checks --- python/cusignal/diff/resample.py | 3 --- python/cusignal/test/test_diff.py | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/python/cusignal/diff/resample.py b/python/cusignal/diff/resample.py index e9163bfb..5b3f5e8d 100644 --- a/python/cusignal/diff/resample.py +++ b/python/cusignal/diff/resample.py @@ -1,5 +1,4 @@ import torch -import numpy as np import cupy as cp from torch.autograd import Function from torch.nn.modules.module import Module @@ -75,13 +74,11 @@ def backward(ctx, gradient): device = gradient.device.type x_size = int(x_size[0]) - gradient_size = gradient.shape[0] filt_size = filter_coeffs.shape[0] up = int(up[0]) down = int(down[0]) start = FuncResamplePoly.get_start_index(filt_size) inverse_size = int(inverse_size) - out_x_len = int(out_len) filter_coeffs = filter_coeffs.type(gradient.dtype) if (up == 1 and down == 1): diff --git a/python/cusignal/test/test_diff.py b/python/cusignal/test/test_diff.py index 2af749e0..8b682dfc 100644 --- a/python/cusignal/test/test_diff.py +++ b/python/cusignal/test/test_diff.py @@ -1,4 +1,5 @@ import pytest +import cupy as cp from numpy import sqrt from numpy import allclose from cusignal import resample_poly @@ -49,9 +50,8 @@ def test_forward(device, x_size, up, down, filter_size): gpupath = True accepts cupy typed windows. gpupath = False accepts numpy types windows. ''' - device = torch.device(device) gpupath = True - if device != 'cuda': + if 'cuda' not in device: gpupath = False x = torch.randn(x_size, device=device) window = torch.randn(filter_size, device=device) From f36631d93f5cc01cc6a527598f4e0fb0bd3dc33c Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Wed, 29 Jun 2022 13:43:57 -0400 Subject: [PATCH 16/17] Adding diff function from top-level cusignal module -- at least I think -- and throw warning if PyTorch isn't installed on cusignal import --- python/cusignal/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/python/cusignal/__init__.py b/python/cusignal/__init__.py index 8f1eab1c..2be26531 100644 --- a/python/cusignal/__init__.py +++ b/python/cusignal/__init__.py @@ -28,6 +28,14 @@ ) from cusignal.convolution.correlate import correlate, correlate2d from cusignal.demod.demod import fm_demod +try: + from cusignal import diff +except: + msg = """ + Warning - Could not find PyTorch. Please install to use + differentiable functions in cuSignal. + """ + print(msg) from cusignal.estimation.filters import KalmanFilter from cusignal.filter_design.fir_filter_design import ( cmplx_sort, From 63f6f26bc6a7bfb936250b2bfed4eba29c7dc86c Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Wed, 29 Jun 2022 14:51:49 -0400 Subject: [PATCH 17/17] moving where we catch pytorch import --- python/cusignal/__init__.py | 8 -------- python/cusignal/diff/__init__.py | 22 +++++++++++++++++++++- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/python/cusignal/__init__.py b/python/cusignal/__init__.py index 2be26531..8f1eab1c 100644 --- a/python/cusignal/__init__.py +++ b/python/cusignal/__init__.py @@ -28,14 +28,6 @@ ) from cusignal.convolution.correlate import correlate, correlate2d from cusignal.demod.demod import fm_demod -try: - from cusignal import diff -except: - msg = """ - Warning - Could not find PyTorch. Please install to use - differentiable functions in cuSignal. - """ - print(msg) from cusignal.estimation.filters import KalmanFilter from cusignal.filter_design.fir_filter_design import ( cmplx_sort, diff --git a/python/cusignal/diff/__init__.py b/python/cusignal/diff/__init__.py index 162157d7..77fe1cf8 100644 --- a/python/cusignal/diff/__init__.py +++ b/python/cusignal/diff/__init__.py @@ -1 +1,21 @@ -from cusignal.diff.resample import ResamplePoly +# Copyright (c) 2019-2022, NVIDIA CORPORATION. +# 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. + +try: + from cusignal.diff.resample import ResamplePoly +except: + msg = """ + Warning - Could not find PyTorch. Please install to use + differentiable functions in cuSignal. + """ + print(msg)