Несколько вещей, которые следует учитывать.
Интерпретатор Python очень медленный, и эти библиотеки векторизации переносят рабочую нагрузку на нативную реализацию. Чтобы иметь какое-либо значение, вам нужно иметь возможность выполнять множество операций в одной инструкции Python. Оценка вещей на графическом процессоре следует тому же принципу, хотя у графического процессора больше вычислительная мощность, он медленнее копирует данные в/из графического процессора.
Я адаптировал ваш пример для одновременной обработки нескольких сигналов.
import numpy as np
def numpy_xcorr(BATCH=1, signal_length=36000):
# make the signals
signal_1 = np.random.uniform(-1,1, (BATCH, signal_length))
signal_2 = np.random.uniform(-1,1, (BATCH, signal_length))
# output target length of crosscorrelation
x_cor_sig_length = signal_length*2 - 1
# get optimized array length for fft computation
fast_length = next_fast_len(x_cor_sig_length)
# move data into the frequency domain. axis=-1 to perform
# along last dimension
fft_1 = np.fft.rfft(signal_1, fast_length, axis=-1)
fft_2 = np.fft.rfft(signal_2 + 0.1 * signal_1, fast_length, axis=-1)
# take the complex conjugate of one of the spectrums.
fft_1 = np.conj(fft_1)
fft_multiplied = fft_1 * fft_2
# back to time domain.
prelim_correlation = np.fft.irfft(fft_multiplied, fast_length, axis=-1)
# shift the signal to make it look like a proper crosscorrelation,
# and transform the output to be purely real
final_result = np.fft.fftshift(np.real(prelim_correlation), axes=-1)
return final_result, np.sum(final_result)
Начиная с torch 1.7 у нас есть torch.fft, который предоставляет интерфейс, аналогичный numpy.fft, fftshift отсутствует, но тот же результат можно получить с помощью torch.roll. Еще один момент заключается в том, что numpy по умолчанию использует 64-битную точность, а torch будет использовать 32-битную точность.
Быстрая длина состоит в выборе гладких чисел (те, которые разложены на маленькие простые числа, и я полагаю, вы знакомы с этой темой).
def next_fast_len(n, factors=[2, 3, 5, 7]):
'''
Returns the minimum integer not smaller than n that can
be written as a product (possibly with repettitions) of
the given factors.
'''
best = float('inf')
stack = [1]
while len(stack):
a = stack.pop()
if a >= n:
if a < best:
best = a;
if best == n:
break; # no reason to keep searching
else:
for p in factors:
b = a * p;
if b < best:
stack.append(b)
return best;
Затем идет реализация факела
import torch;
import torch.fft
def torch_xcorr(BATCH=1, signal_length=36000, device='cpu', factors=[2,3,5], dtype=torch.float):
signal_length=36000
# torch.rand is random in the range (0, 1)
signal_1 = 1 - 2*torch.rand((BATCH, signal_length), device=device, dtype=dtype)
signal_2 = 1 - 2*torch.rand((BATCH, signal_length), device=device, dtype=dtype)
# just make the cross correlation more interesting
signal_2 += 0.1 * signal_1;
# output target length of crosscorrelation
x_cor_sig_length = signal_length*2 - 1
# get optimized array length for fft computation
fast_length = next_fast_len(x_cor_sig_length, [2, 3])
# the last signal_ndim axes (1,2 or 3) will be transformed
fft_1 = torch.fft.rfft(signal_1, fast_length, dim=-1)
fft_2 = torch.fft.rfft(signal_2, fast_length, dim=-1)
# take the complex conjugate of one of the spectrums. Which one you choose depends on domain specific conventions
fft_multiplied = torch.conj(fft_1) * fft_2
# back to time domain.
prelim_correlation = torch.fft.irfft(fft_multiplied, dim=-1)
# shift the signal to make it look like a proper crosscorrelation,
# and transform the output to be purely real
final_result = torch.roll(prelim_correlation, (fast_length//2,))
return final_result, torch.sum(final_result);
А вот код для проверки результатов
import time
funcs = {'numpy-f64': lambda b: numpy_xcorr(b, factors=[2,3,5], dtype=np.float64),
'numpy-f32': lambda b: numpy_xcorr(b, factors=[2,3,5], dtype=np.float32),
'torch-cpu-f64': lambda b: torch_xcorr(b, device='cpu', factors=[2,3,5], dtype=torch.float64),
'torch-cpu': lambda b: torch_xcorr(b, device='cpu', factors=[2,3,5], dtype=torch.float32),
'torch-gpu-f64': lambda b: torch_xcorr(b, device='cuda', factors=[2,3,5], dtype=torch.float64),
'torch-gpu': lambda b: torch_xcorr(b, device='cuda', factors=[2,3,5], dtype=torch.float32),
}
times ={}
for batch in [1, 10, 100]:
times[batch] = {}
for l, f in funcs.items():
t0 = time.time()
t1, t2 = f(batch)
tf = time.time()
del t1
del t2
times[batch][l] = 1000 * (tf - t0) / batch;
Я получил следующие результаты
![Время выполнения для 32-битной и 64-битной точности, разного размера пакета, с использованием numpy, pytorch-cpu и pytorch-gpu, с длиной плавного БПФ 5](https://i.stack.imgur.com/7j72c.png)
![Время выполнения для 32-битной и 64-битной точности, разного размера пакета, с использованием numpy, pytorch-cpu и pytorch-gpu, с длиной плавного БПФ 5](https://i.stack.imgur.com/B1mHG.png)
И что меня удивило, так это результат, когда числа не такие гладкие, например. при использовании 17-кратной гладкой длины реализация факела настолько лучше, что я использовал здесь логарифмическую шкалу (с размером пакета 100 GPU факела был в 10000 раз быстрее, чем numpy с размером пакета 1).
![Время выполнения для 32-битной и 64-битной точности, разного размера пакета, с использованием numpy, pytorch-cpu и pytorch-gpu, с длиной плавного БПФ 5](https://i.stack.imgur.com/b07Sg.png)
Помните, что эти функции генерируют данные в графическом процессоре, в общем, мы хотим скопировать окончательные результаты в ЦП, если учесть время, затрачиваемое на копирование конечного результата в ЦП, я наблюдал время до 10 раз выше, чем вычисление кросс-корреляции (случайное генерация данных + три БПФ).
person
Bob
schedule
09.02.2021