2次元FFTの実装は、各行・各列のスライス(配列)を独立して処理することになります。
もちろん前の項目で紹介した1次元FFTカーネルを各行、各列にそのまま適用することも可能です。ただこの項目では、OpenCLのカーネルのインデックス空間を2次元に拡大して、1つのカーネルで全行、全列を処理する方法を紹介させていただきます。
まずワークサイズについては以下のように2次元として設定します。
private static long[] global_work_size = new long[]{width/2,height,1}; private static long[] local_work_size = new long[]{1,1,1};
一つ目の次元のワークサイズがwidth/2に設定していますが、FFTでは処理点が全データの要素の半分となるため、ワークサイズ(インデックス空間)についても半分としています。
__kernel void fft2d( __global float2* F, int N, int sign) { int x = get_global_id(0); int row_size = get_global_size(0)*2; int y = get_global_id(1); int stride = N/2; float floor_adjust = x/stride; int index = ceil(floor_adjust)*stride + (x%row_size); float2 in0, in1; in0 = F[y*row_size + index]; in1 = F[y*row_size + index+stride]; float angle = -2*M_PI_F*(index)/N; float c,s; float2 v; float2 tmp0; c = native_cos(angle); s = sign*native_sin(angle); v.x = c * (in1.x) - s * in1.y; v.y = c * (in1.y) + s * in1.x; tmp0 = in0; in0 = tmp0 + v; in1 = tmp0 - v; F[y*row_size + index] = in0; F[y*row_size + index + stride] = in1; }
1次元からの大きな変更点としては各行を一つ一つ処理するために、yとrow_sizeの2つの変数が追加されたことです。
in0 = F[y*row_size + index]; in1 = F[y*row_size + index+stride];
この記述では、Fは2次元行列でindexは行の中の要素に対応します。またy*row_sizeは、y行を指定します。
擬似フロー(Figure 20.1, “図:2次元FFTの手順”)にある通り、2D-FFTの手順には、4回のビット反転、2回の行列転置を行う必要があります。
実装例では以下のような流れで処理をしています。
bit_reverse(data_mem) #(1) fft_2d(width, data_mem,processed_mem,1,true) #(2) transpose(processed_mem, transpose_mem) #(3) bit_reverse(transpose_mem) #(4) fft_2d(height,null,transpose_mem,1,false) #(5) bit_reverse(transpose_mem) #(6) fft_2d(height,null,transpose_mem,-1,false) #(7) fft_inverse2d(height,transpose_mem) #(8) transpose(transpose_mem, processed_mem) #(9) bit_reverse(processed_mem) #(10) fft_2d(width,null,processed_mem,-1,false) #(11) fft_inverse2d(width,processed_mem) #(12)
元データ(data_mem)のビット反転。 | |
元データを変換して、処理済みデータ(processed_mem)に書き込み。 | |
処理済みデータ(processed_mem)を転置して、転置データ(transpose_mem)に書き込み。 | |
転置データ(transpose_mem)のビット反転。 | |
転置データ(transpose_mem)のFFTをして転置データ(transpose_mem)を更新。 | |
転置データ(transpose_mem)のビット反転。 | |
転置データ(transpose_mem)の逆FFTをして転置データ(transpose_mem)を更新。 | |
転置データ(transpose_mem)にスケールファクターをかけて値を調整します。 | |
転置データ(transpose_mem)を転置して、処理済みデータ(processed_mem)に書き込み。 | |
処理済みデータ(processed_mem)のビット反転。 | |
処理済みデータ(processed_mem)のFFTをしてデータを更新。 | |
処理済みデータ(processed_mem)にスケールファクターをかけて値を調整します。 |
bit_reverse関数がビット反転、transpose関数が転置、fft_2dが高速フーリエ変換(第4引数を-1にすると逆変換)、fft_inverse2dが逆フーリエ変換のスケーリングに対応します。
fft_inverse2dは以下のカーネル関数に対応します。
__kernel void fft_inverse2d(int N, __global float2* F) { size_t x = get_global_id(0); size_t row_size = get_global_size(0); size_t y = get_global_id(1); F[y*row_size + x] /= N; }
この関数は単純に全ての要素に処理するデータサイズ(N)で割り算をしています。
FFTGPU2D.py.
import pyopencl as cl import numpy as np from numpy.random import * # private static final String KERNEL_PATH = "fft2d.cl"; KERNEL_INIT = "fft_init" KERNEL_BIT_REVERSAL = "bit_reversal" KERNEL_FFT = "fft2d" KERNEL_FFT_INVERSE = "fft_inverse2d" KERNEL_FFT_TRANSPOSE = "async_transpose" KERNEL_TRANSPOSE = "transpose" width = 8 height = 8 DATA_SIZE = width * height LOCAL_SIZE = 4 TILE_SIZE = LOCAL_SIZE data = np.arange(DATA_SIZE << 1).astype(np.float32) print(data) processed_data = np.zeros(DATA_SIZE << 1).astype(np.float32) transpose_data = np.zeros(DATA_SIZE << 1).astype(np.float32) global_work_size = (np.uint32(width / 2), height, 1) local_work_size = (1, 1, 1) global_work_size_rect = (width, height, 1) local_work_size_rect = (LOCAL_SIZE, LOCAL_SIZE, 1) devices = [cl.get_platforms()[0].get_devices(cl.device_type.GPU)[0]] ctx = cl.Context(devices) queue = cl.CommandQueue(ctx) mf = cl.mem_flags data_mem = cl.Buffer(ctx, mf.USE_HOST_PTR, hostbuf=data) processed_mem = cl.Buffer(ctx, mf.USE_HOST_PTR, hostbuf=processed_data) transpose_mem = cl.Buffer(ctx, mf.USE_HOST_PTR, hostbuf=transpose_data) opt_string = "-Dsize="+str(TILE_SIZE) options = [opt_string] program = cl.Program(ctx, """ inline int reverseBit(int x, int stage) { int b = 0; int bits = stage; while (bits != 0){ b <<=1; b |=( x &1 ); x >>=1; bits>>=1; } return b; } __kernel void bit_reversal(__global float2* data, uint N) { size_t x = get_global_id(0); size_t row_size = get_global_size(0); size_t y = get_global_id(1); uint rev = reverseBit(x, row_size-1); float2 in1; float2 in2; if(x < rev) { in1 = data[y*row_size + x]; in2 = data[y*row_size + rev]; //printf("x=%d, y=%d, pair: %d - %d, rs = %d, di=%d\\n", x, y, x, rev, row_size,y*row_size + x); data[y*row_size + rev] = in1; data[y*row_size + x] = in2; } } __kernel void fft_init( __global float2* data, __global float2* F, int N) { int x = get_global_id(0); int row_size = get_global_size(0)*2; int y = get_global_id(1); int stride = N/2; float floor_adjust = x/stride; int index = ceil(floor_adjust)*stride + (x%row_size); float2 in0, in1; in0 = data[y*row_size + index]; in1 = data[y*row_size + index+stride]; float2 v0; v0 = in0; in0 = v0 + in1; in1 = v0 - in1; F[y*row_size + index] = in0; F[y*row_size + index + stride] = in1; //printf("x=%d, y=%d, rs=%d, di=%d, pair: %d - %d, N = %d, s = %d, in0:in1 = %f:%f\\n", x,y,row_size,y*row_size + index, index, index+stride, N, stride, F[y*row_size + index].x, F[y*row_size + index+stride].x); } __kernel void fft2d( __global float2* F, int N, int sign) { int x = get_global_id(0); int row_size = get_global_size(0)*2; int y = get_global_id(1); int stride = N/2; float floor_adjushgt = x/stride; int index = ceil(floor_adjust)*stride + (x%row_size); float2 in0, in1; in0 = F[y*row_size + index]; in1 = F[y*row_size + index+stride]; float angle = -2*M_PI_F*(index)/N; float c,s; float2 v; float2 tmp0; c = native_cos(angle); s = sign*native_sin(angle); v.x = c * (in1.x) - s * in1.y; v.y = c * (in1.y) + s * in1.x; tmp0 = in0; in0 = tmp0 + v; in1 = tmp0 - v; F[y*row_size + index] = in0; F[y*row_size + index + stride] = in1; //printf("x=%d, y=%d, pair: %d - %d, N = %d, s = %d, sign = %d c:s = %f:%f\\n in0:in1 = %f:%f\\n", x,y, y*row_size + index, y*row_size + index+stride, N, stride, sign, c, s, F[y*row_size + index].x, F[y*row_size + index+stride].x); } __kernel void fft_inverse2d(int N, __global float2* F) { size_t x = get_global_id(0); size_t row_size = get_global_size(0); size_t y = get_global_id(1); F[y*row_size + x] /= N; //printf("di=%d, F[]=%f\\n",y*row_size + x,F[y*row_size + x].x); } __kernel void transpose( __global float2* input, __global float2* output, size_t width, size_t height) { __local float2 tile[size * (size+1)]; size_t x = get_global_id(0); size_t y = get_global_id(1); size_t lx = get_local_id(0); size_t ly = get_local_id(1); size_t gx = get_group_id(0); size_t gy = get_group_id(1); size_t index_input = y * width + x; size_t index_tile = ly * (size+1) + lx; tile[index_tile]= input[index_input]; barrier(CLK_LOCAL_MEM_FENCE); size_t ox = gy * size + lx; size_t oy = gx * size + ly; size_t index_output = oy * height + ox; index_tile = lx * (size+1) + ly; output[index_output] = tile[index_tile]; //printf("output[%d %d %d %d]=%f, index_tile=%d, index_out=%d\\n",x,y,gx,gy,output[index_output].x, index_tile, index_output); } """).build(options=options) kernel_init = cl.Kernel(program, name=KERNEL_INIT) kernel_bit_reversal = cl.Kernel(program, name=KERNEL_BIT_REVERSAL) kernel_fft = cl.Kernel(program, name=KERNEL_FFT) kernel_fft_inverse = cl.Kernel(program, name=KERNEL_FFT_INVERSE) kernel_transpose = cl.Kernel(program, name=KERNEL_TRANSPOSE) def bit_reverse(input_mem): N = DATA_SIZE kernel_bit_reversal.set_arg(0, input_mem) kernel_bit_reversal.set_arg(1, np.uint32(N)) cl.enqueue_nd_range_kernel(queue, kernel_bit_reversal, global_work_size_rect, local_work_size) def fft_2d(N, input_mem, output_mem, sign, is_init): fftSize = 1 ns = np.uint32(np.log2(N)) for i in range(ns): fftSize <<= 1 if is_init is False or fftSize != 2: kernel_fft.set_arg(0, output_mem) kernel_fft.set_arg(1, np.int32(fftSize)) kernel_fft.set_arg(2, np.int32(sign)) cl.enqueue_nd_range_kernel(queue, kernel_fft, global_work_size, local_work_size) else: kernel_init.set_arg(0, input_mem) kernel_init.set_arg(1, output_mem) kernel_init.set_arg(2, np.int32(fftSize)) cl.enqueue_nd_range_kernel(queue, kernel_init, global_work_size, local_work_size) def transpose(input_mem, output_mem): kernel_transpose.set_arg(0, input_mem) kernel_transpose.set_arg(1, output_mem) kernel_transpose.set_arg(2, np.int32(width)) kernel_transpose.set_arg(3, np.int32(height)) cl.enqueue_nd_range_kernel(queue, kernel_transpose, global_work_size_rect, local_work_size_rect) def fft_inverse2d(N, inverse_mem): kernel_fft_inverse.set_arg(0, np.int32(N)) kernel_fft_inverse.set_arg(1, inverse_mem) cl.enqueue_nd_range_kernel(queue, kernel_fft_inverse, global_work_size_rect, local_work_size) bit_reverse(data_mem) fft_2d(width, data_mem, processed_mem, 1, True) transpose(processed_mem, transpose_mem) bit_reverse(transpose_mem) fft_2d(height, None, transpose_mem, 1, False) bit_reverse(transpose_mem) fft_2d(height, None, transpose_mem, -1, False) fft_inverse2d(height, transpose_mem) transpose(transpose_mem, processed_mem) bit_reverse(processed_mem) fft_2d(width, None, processed_mem, -1, False) fft_inverse2d(width, processed_mem) cl.enqueue_read_buffer(queue, mem=processed_mem, hostbuf=processed_data) queue.finish() print(processed_data)
Copyright 2018-2019, by Masaki Komatsu