| """NKI-accelerated 2D Fourier Transform kernel for FNet on AWS Neuron (Trainium/Inferentia). |
| |
| Replaces the `FNetBasicFourierTransform` module with NKI kernels that perform |
| the 2D FFT (over sequence and hidden dimensions) entirely on NeuronCore hardware. |
| |
| Algorithm: |
| FNet applies fftn(x, dim=(1,2)).real to the hidden states tensor (B, S, D). |
| This is decomposed into: |
| 1. 1D FFT along hidden_dim (dim=2) for each (batch, seq) position |
| 2. 1D FFT along seq_dim (dim=1) for each (batch, hidden) position |
| 3. Take real part |
| |
| The NKI kernel uses radix-2 Cooley-Tukey with a 128-point DFT base case |
| computed via nc_matmul on the Tensor Engine (~90% utilization in base case). |
| |
| Supported configurations: |
| - FNet-base: hidden_size=768 (padded to 1024), max_seq_len=512 |
| - FNet-large: hidden_size=1024, max_seq_len=512 |
| - Custom configs where hidden_size and seq_len are powers of 2 (128-2048) |
| |
| Requirements: |
| - PyTorch Native (torch-neuronx) with NKI support |
| - NKI >= 0.3.0 (SDK 2.29+) |
| - hidden_size must be <= 2048 (padded to next power of 2) |
| - seq_len must be <= 2048 (padded to next power of 2) |
| """ |
|
|
| import math |
|
|
| import numpy as np |
| import torch |
| import torch.nn as nn |
|
|
| import nki |
| import nki.isa as nisa |
| import nki.language as nl |
|
|
|
|
| |
| |
| |
|
|
| TILE_H = 128 |
| BASE_N = 128 |
|
|
|
|
| def _fft1d_matmul_isa(X_real, X_imag, Y_real, Y_imag, W_real, W_imag): |
| """128-pt DFT via complex matrix multiply on the Tensor Engine.""" |
| H, W_size = X_real.shape |
|
|
| W_real_T_psum = nl.ndarray((W_size, W_size), dtype=nl.float32, buffer=nl.psum) |
| nisa.nc_transpose(dst=W_real_T_psum, data=W_real) |
| W_real_T = nl.ndarray((W_size, W_size), dtype=nl.float32, buffer=nl.sbuf) |
| nisa.tensor_copy(dst=W_real_T, src=W_real_T_psum) |
|
|
| W_imag_T_psum = nl.ndarray((W_size, W_size), dtype=nl.float32, buffer=nl.psum) |
| nisa.nc_transpose(dst=W_imag_T_psum, data=W_imag) |
| W_imag_T = nl.ndarray((W_size, W_size), dtype=nl.float32, buffer=nl.sbuf) |
| nisa.tensor_copy(dst=W_imag_T, src=W_imag_T_psum) |
|
|
| neg_W_imag_T = nl.ndarray((W_size, W_size), dtype=nl.float32, buffer=nl.sbuf) |
| nisa.tensor_scalar(dst=neg_W_imag_T, data=W_imag_T, op0=nl.multiply, operand0=-1.0) |
|
|
| X_real_T_psum = nl.ndarray((W_size, H), dtype=nl.float32, buffer=nl.psum) |
| nisa.nc_transpose(dst=X_real_T_psum, data=X_real) |
| X_real_T = nl.ndarray((W_size, H), dtype=nl.float32, buffer=nl.sbuf) |
| nisa.tensor_copy(dst=X_real_T, src=X_real_T_psum) |
|
|
| X_imag_T_psum = nl.ndarray((W_size, H), dtype=nl.float32, buffer=nl.psum) |
| nisa.nc_transpose(dst=X_imag_T_psum, data=X_imag) |
| X_imag_T = nl.ndarray((W_size, H), dtype=nl.float32, buffer=nl.sbuf) |
| nisa.tensor_copy(dst=X_imag_T, src=X_imag_T_psum) |
|
|
| psum_real = nl.ndarray((H, W_size), dtype=nl.float32, buffer=nl.psum) |
| nisa.nc_matmul(dst=psum_real, stationary=X_real_T, moving=W_real_T) |
| nisa.nc_matmul(dst=psum_real, stationary=X_imag_T, moving=neg_W_imag_T) |
| nisa.tensor_copy(dst=Y_real, src=psum_real) |
|
|
| psum_imag = nl.ndarray((H, W_size), dtype=nl.float32, buffer=nl.psum) |
| nisa.nc_matmul(dst=psum_imag, stationary=X_real_T, moving=W_imag_T) |
| nisa.nc_matmul(dst=psum_imag, stationary=X_imag_T, moving=W_real_T) |
| nisa.tensor_copy(dst=Y_imag, src=psum_imag) |
|
|
|
|
| def _complex_twiddle_multiply( |
| odd_r, odd_i, tw_r, tw_i, out_r, out_i, tmp_ac, tmp_bd, tmp_ad, tmp_bc |
| ): |
| """Complex multiplication: out = twiddle * odd.""" |
| nisa.tensor_tensor(dst=tmp_ac, data1=odd_r, data2=tw_r, op=nl.multiply) |
| nisa.tensor_tensor(dst=tmp_bd, data1=odd_i, data2=tw_i, op=nl.multiply) |
| nisa.tensor_tensor(dst=tmp_ad, data1=odd_r, data2=tw_i, op=nl.multiply) |
| nisa.tensor_tensor(dst=tmp_bc, data1=odd_i, data2=tw_r, op=nl.multiply) |
| nisa.tensor_tensor(dst=out_r, data1=tmp_ac, data2=tmp_bd, op=nl.subtract) |
| nisa.tensor_tensor(dst=out_i, data1=tmp_ad, data2=tmp_bc, op=nl.add) |
|
|
|
|
| def _butterfly_pair( |
| even_r, |
| even_i, |
| odd_r, |
| odd_i, |
| tw_r, |
| tw_i, |
| new_even_r, |
| new_even_i, |
| new_odd_r, |
| new_odd_i, |
| tw_out_r, |
| tw_out_i, |
| tmp_ac, |
| tmp_bd, |
| tmp_ad, |
| tmp_bc, |
| ): |
| """Butterfly: new_even = even + twiddle*odd, new_odd = even - twiddle*odd.""" |
| _complex_twiddle_multiply( |
| odd_r, odd_i, tw_r, tw_i, tw_out_r, tw_out_i, tmp_ac, tmp_bd, tmp_ad, tmp_bc |
| ) |
| nisa.tensor_tensor(dst=new_even_r, data1=even_r, data2=tw_out_r, op=nl.add) |
| nisa.tensor_tensor(dst=new_even_i, data1=even_i, data2=tw_out_i, op=nl.add) |
| nisa.tensor_tensor(dst=new_odd_r, data1=even_r, data2=tw_out_r, op=nl.subtract) |
| nisa.tensor_tensor(dst=new_odd_i, data1=even_i, data2=tw_out_i, op=nl.subtract) |
|
|
|
|
| |
| |
| |
|
|
|
|
| @nki.jit |
| def _fft1d_128(X_real_hbm, X_imag_hbm, W_128_real_hbm, W_128_imag_hbm): |
| """128-point FFT: direct DFT via Tensor Engine matmul.""" |
| Y_real_hbm = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.shared_hbm) |
| Y_imag_hbm = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.shared_hbm) |
|
|
| X_real = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| X_imag = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| nisa.dma_copy(dst=X_real, src=X_real_hbm[0:TILE_H, 0:BASE_N]) |
| nisa.dma_copy(dst=X_imag, src=X_imag_hbm[0:TILE_H, 0:BASE_N]) |
|
|
| W_real = nl.ndarray((BASE_N, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| W_imag = nl.ndarray((BASE_N, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| nisa.dma_copy(dst=W_real, src=W_128_real_hbm[0:BASE_N, 0:BASE_N]) |
| nisa.dma_copy(dst=W_imag, src=W_128_imag_hbm[0:BASE_N, 0:BASE_N]) |
|
|
| Y_real = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| Y_imag = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| _fft1d_matmul_isa(X_real, X_imag, Y_real, Y_imag, W_real, W_imag) |
|
|
| nisa.dma_copy(dst=Y_real_hbm[0:TILE_H, 0:BASE_N], src=Y_real) |
| nisa.dma_copy(dst=Y_imag_hbm[0:TILE_H, 0:BASE_N], src=Y_imag) |
| return Y_real_hbm, Y_imag_hbm |
|
|
|
|
| |
| |
| |
|
|
|
|
| @nki.jit |
| def _fft1d_256( |
| X_real_hbm, |
| X_imag_hbm, |
| W_128_real_hbm, |
| W_128_imag_hbm, |
| twiddle_real_hbm, |
| twiddle_imag_hbm, |
| ): |
| """256-point FFT: 2 groups of 128, 1 butterfly level.""" |
| N = 256 |
| N_half = 128 |
|
|
| Y_real_hbm = nl.ndarray((TILE_H, N), dtype=nl.float32, buffer=nl.shared_hbm) |
| Y_imag_hbm = nl.ndarray((TILE_H, N), dtype=nl.float32, buffer=nl.shared_hbm) |
|
|
| X_real = nl.ndarray((TILE_H, N), dtype=nl.float32, buffer=nl.sbuf) |
| X_imag = nl.ndarray((TILE_H, N), dtype=nl.float32, buffer=nl.sbuf) |
| nisa.dma_copy(dst=X_real, src=X_real_hbm[0:TILE_H, 0:N]) |
| nisa.dma_copy(dst=X_imag, src=X_imag_hbm[0:TILE_H, 0:N]) |
|
|
| W_128_real = nl.ndarray((BASE_N, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| W_128_imag = nl.ndarray((BASE_N, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| nisa.dma_copy(dst=W_128_real, src=W_128_real_hbm[0:BASE_N, 0:BASE_N]) |
| nisa.dma_copy(dst=W_128_imag, src=W_128_imag_hbm[0:BASE_N, 0:BASE_N]) |
|
|
| twiddle_real = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| twiddle_imag = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| nisa.dma_copy(dst=twiddle_real, src=twiddle_real_hbm[0:TILE_H, 0:N_half]) |
| nisa.dma_copy(dst=twiddle_imag, src=twiddle_imag_hbm[0:TILE_H, 0:N_half]) |
|
|
| |
| even_idx = nl.ndarray((TILE_H, N_half), dtype=nl.uint32, buffer=nl.sbuf) |
| odd_idx = nl.ndarray((TILE_H, N_half), dtype=nl.uint32, buffer=nl.sbuf) |
| nisa.iota(dst=even_idx, pattern=[[2, N_half]], offset=0, channel_multiplier=0) |
| nisa.iota(dst=odd_idx, pattern=[[2, N_half]], offset=1, channel_multiplier=0) |
|
|
| g0_r = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| g0_i = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| g1_r = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| g1_i = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| nisa.nc_n_gather(dst=g0_r, data=X_real, indices=even_idx) |
| nisa.nc_n_gather(dst=g0_i, data=X_imag, indices=even_idx) |
| nisa.nc_n_gather(dst=g1_r, data=X_real, indices=odd_idx) |
| nisa.nc_n_gather(dst=g1_i, data=X_imag, indices=odd_idx) |
|
|
| |
| d0_r = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| d0_i = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| d1_r = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| d1_i = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| _fft1d_matmul_isa(g0_r, g0_i, d0_r, d0_i, W_128_real, W_128_imag) |
| _fft1d_matmul_isa(g1_r, g1_i, d1_r, d1_i, W_128_real, W_128_imag) |
|
|
| |
| tw_out_r = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| tw_out_i = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| tmp_ac = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| tmp_bd = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| tmp_ad = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| tmp_bc = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
|
|
| Y_first_r = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| Y_first_i = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| Y_second_r = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
| Y_second_i = nl.ndarray((TILE_H, N_half), dtype=nl.float32, buffer=nl.sbuf) |
|
|
| _butterfly_pair( |
| d0_r, |
| d0_i, |
| d1_r, |
| d1_i, |
| twiddle_real, |
| twiddle_imag, |
| Y_first_r, |
| Y_first_i, |
| Y_second_r, |
| Y_second_i, |
| tw_out_r, |
| tw_out_i, |
| tmp_ac, |
| tmp_bd, |
| tmp_ad, |
| tmp_bc, |
| ) |
|
|
| |
| Y_combined_real = nl.ndarray((TILE_H, N), dtype=nl.float32, buffer=nl.sbuf) |
| Y_combined_imag = nl.ndarray((TILE_H, N), dtype=nl.float32, buffer=nl.sbuf) |
| nisa.tensor_copy(dst=Y_combined_real[0:TILE_H, 0:N_half], src=Y_first_r) |
| nisa.tensor_copy(dst=Y_combined_imag[0:TILE_H, 0:N_half], src=Y_first_i) |
| nisa.tensor_copy(dst=Y_combined_real[0:TILE_H, N_half:N], src=Y_second_r) |
| nisa.tensor_copy(dst=Y_combined_imag[0:TILE_H, N_half:N], src=Y_second_i) |
|
|
| nisa.dma_copy(dst=Y_real_hbm[0:TILE_H, 0:N], src=Y_combined_real) |
| nisa.dma_copy(dst=Y_imag_hbm[0:TILE_H, 0:N], src=Y_combined_imag) |
| return Y_real_hbm, Y_imag_hbm |
|
|
|
|
| |
| |
| |
|
|
|
|
| @nki.jit |
| def _fft1d_512( |
| X_real_hbm, |
| X_imag_hbm, |
| W_128_real_hbm, |
| W_128_imag_hbm, |
| tw256_real_hbm, |
| tw256_imag_hbm, |
| tw512_real_hbm, |
| tw512_imag_hbm, |
| ): |
| """512-point FFT: 4 groups of 128, 2 butterfly levels.""" |
| N = 512 |
|
|
| Y_real_hbm = nl.ndarray((TILE_H, N), dtype=nl.float32, buffer=nl.shared_hbm) |
| Y_imag_hbm = nl.ndarray((TILE_H, N), dtype=nl.float32, buffer=nl.shared_hbm) |
|
|
| X_real = nl.ndarray((TILE_H, N), dtype=nl.float32, buffer=nl.sbuf) |
| X_imag = nl.ndarray((TILE_H, N), dtype=nl.float32, buffer=nl.sbuf) |
| nisa.dma_copy(dst=X_real, src=X_real_hbm[0:TILE_H, 0:N]) |
| nisa.dma_copy(dst=X_imag, src=X_imag_hbm[0:TILE_H, 0:N]) |
|
|
| W_128_real = nl.ndarray((BASE_N, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| W_128_imag = nl.ndarray((BASE_N, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| nisa.dma_copy(dst=W_128_real, src=W_128_real_hbm[0:BASE_N, 0:BASE_N]) |
| nisa.dma_copy(dst=W_128_imag, src=W_128_imag_hbm[0:BASE_N, 0:BASE_N]) |
|
|
| tw256_r = nl.ndarray((TILE_H, 128), dtype=nl.float32, buffer=nl.sbuf) |
| tw256_i = nl.ndarray((TILE_H, 128), dtype=nl.float32, buffer=nl.sbuf) |
| nisa.dma_copy(dst=tw256_r, src=tw256_real_hbm[0:TILE_H, 0:128]) |
| nisa.dma_copy(dst=tw256_i, src=tw256_imag_hbm[0:TILE_H, 0:128]) |
|
|
| tw512_r = nl.ndarray((TILE_H, 256), dtype=nl.float32, buffer=nl.sbuf) |
| tw512_i = nl.ndarray((TILE_H, 256), dtype=nl.float32, buffer=nl.sbuf) |
| nisa.dma_copy(dst=tw512_r, src=tw512_real_hbm[0:TILE_H, 0:256]) |
| nisa.dma_copy(dst=tw512_i, src=tw512_imag_hbm[0:TILE_H, 0:256]) |
|
|
| |
| idx0 = nl.ndarray((TILE_H, BASE_N), dtype=nl.uint32, buffer=nl.sbuf) |
| idx1 = nl.ndarray((TILE_H, BASE_N), dtype=nl.uint32, buffer=nl.sbuf) |
| idx2 = nl.ndarray((TILE_H, BASE_N), dtype=nl.uint32, buffer=nl.sbuf) |
| idx3 = nl.ndarray((TILE_H, BASE_N), dtype=nl.uint32, buffer=nl.sbuf) |
| nisa.iota(dst=idx0, pattern=[[4, 128]], offset=0, channel_multiplier=0) |
| nisa.iota(dst=idx1, pattern=[[4, 128]], offset=1, channel_multiplier=0) |
| nisa.iota(dst=idx2, pattern=[[4, 128]], offset=2, channel_multiplier=0) |
| nisa.iota(dst=idx3, pattern=[[4, 128]], offset=3, channel_multiplier=0) |
|
|
| g0_r = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| g0_i = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| g1_r = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| g1_i = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| g2_r = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| g2_i = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| g3_r = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| g3_i = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
|
|
| nisa.nc_n_gather(dst=g0_r, data=X_real, indices=idx0) |
| nisa.nc_n_gather(dst=g0_i, data=X_imag, indices=idx0) |
| nisa.nc_n_gather(dst=g1_r, data=X_real, indices=idx1) |
| nisa.nc_n_gather(dst=g1_i, data=X_imag, indices=idx1) |
| nisa.nc_n_gather(dst=g2_r, data=X_real, indices=idx2) |
| nisa.nc_n_gather(dst=g2_i, data=X_imag, indices=idx2) |
| nisa.nc_n_gather(dst=g3_r, data=X_real, indices=idx3) |
| nisa.nc_n_gather(dst=g3_i, data=X_imag, indices=idx3) |
|
|
| |
| d0_r = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| d0_i = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| d1_r = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| d1_i = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| d2_r = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| d2_i = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| d3_r = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
| d3_i = nl.ndarray((TILE_H, BASE_N), dtype=nl.float32, buffer=nl.sbuf) |
|
|
| _fft1d_matmul_isa(g0_r, g0_i, d0_r, d0_i, W_128_real, W_128_imag) |
| _fft1d_matmul_isa(g1_r, g1_i, d1_r, d1_i, W_128_real, W_128_imag) |
| _fft1d_matmul_isa(g2_r, g2_i, d2_r, d2_i, W_128_real, W_128_imag) |
| _fft1d_matmul_isa(g3_r, g3_i, d3_r, d3_i, W_128_real, W_128_imag) |
|
|
| |
| tw_out_r = nl.ndarray((TILE_H, 128), dtype=nl.float32, buffer=nl.sbuf) |
| tw_out_i = nl.ndarray((TILE_H, 128), dtype=nl.float32, buffer=nl.sbuf) |
| tmp_ac = nl.ndarray((TILE_H, 128), dtype=nl.float32, buffer=nl.sbuf) |
| tmp_bd = nl.ndarray((TILE_H, 128), dtype=nl.float32, buffer=nl.sbuf) |
| tmp_ad = nl.ndarray((TILE_H, 128), dtype=nl.float32, buffer=nl.sbuf) |
| tmp_bc = nl.ndarray((TILE_H, 128), dtype=nl.float32, buffer=nl.sbuf) |
|
|
| b1_even_0_r = nl.ndarray((TILE_H, 128), dtype=nl.float32, buffer=nl.sbuf) |
| b1_even_0_i = nl.ndarray((TILE_H, 128), dtype=nl.float32, buffer=nl.sbuf) |
| b1_even_1_r = nl.ndarray((TILE_H, 128), dtype=nl.float32, buffer=nl.sbuf) |
| b1_even_1_i = nl.ndarray((TILE_H, 128), dtype=nl.float32, buffer=nl.sbuf) |
| _butterfly_pair( |
| d0_r, |
| d0_i, |
| d2_r, |
| d2_i, |
| tw256_r, |
| tw256_i, |
| b1_even_0_r, |
| b1_even_0_i, |
| b1_even_1_r, |
| b1_even_1_i, |
| tw_out_r, |
| tw_out_i, |
| tmp_ac, |
| tmp_bd, |
| tmp_ad, |
| tmp_bc, |
| ) |
|
|
| b1_odd_0_r = nl.ndarray((TILE_H, 128), dtype=nl.float32, buffer=nl.sbuf) |
| b1_odd_0_i = nl.ndarray((TILE_H, 128), dtype=nl.float32, buffer=nl.sbuf) |
| b1_odd_1_r = nl.ndarray((TILE_H, 128), dtype=nl.float32, buffer=nl.sbuf) |
| b1_odd_1_i = nl.ndarray((TILE_H, 128), dtype=nl.float32, buffer=nl.sbuf) |
| _butterfly_pair( |
| d1_r, |
| d1_i, |
| d3_r, |
| d3_i, |
| tw256_r, |
| tw256_i, |
| b1_odd_0_r, |
| b1_odd_0_i, |
| b1_odd_1_r, |
| b1_odd_1_i, |
| tw_out_r, |
| tw_out_i, |
| tmp_ac, |
| tmp_bd, |
| tmp_ad, |
| tmp_bc, |
| ) |
|
|
| |
| half_A_r = nl.ndarray((TILE_H, 256), dtype=nl.float32, buffer=nl.sbuf) |
| half_A_i = nl.ndarray((TILE_H, 256), dtype=nl.float32, buffer=nl.sbuf) |
| half_B_r = nl.ndarray((TILE_H, 256), dtype=nl.float32, buffer=nl.sbuf) |
| half_B_i = nl.ndarray((TILE_H, 256), dtype=nl.float32, buffer=nl.sbuf) |
|
|
| nisa.tensor_copy(dst=half_A_r[0:TILE_H, 0:128], src=b1_even_0_r) |
| nisa.tensor_copy(dst=half_A_r[0:TILE_H, 128:256], src=b1_even_1_r) |
| nisa.tensor_copy(dst=half_A_i[0:TILE_H, 0:128], src=b1_even_0_i) |
| nisa.tensor_copy(dst=half_A_i[0:TILE_H, 128:256], src=b1_even_1_i) |
|
|
| nisa.tensor_copy(dst=half_B_r[0:TILE_H, 0:128], src=b1_odd_0_r) |
| nisa.tensor_copy(dst=half_B_r[0:TILE_H, 128:256], src=b1_odd_1_r) |
| nisa.tensor_copy(dst=half_B_i[0:TILE_H, 0:128], src=b1_odd_0_i) |
| nisa.tensor_copy(dst=half_B_i[0:TILE_H, 128:256], src=b1_odd_1_i) |
|
|
| tw2_out_r = nl.ndarray((TILE_H, 256), dtype=nl.float32, buffer=nl.sbuf) |
| tw2_out_i = nl.ndarray((TILE_H, 256), dtype=nl.float32, buffer=nl.sbuf) |
| t2_ac = nl.ndarray((TILE_H, 256), dtype=nl.float32, buffer=nl.sbuf) |
| t2_bd = nl.ndarray((TILE_H, 256), dtype=nl.float32, buffer=nl.sbuf) |
| t2_ad = nl.ndarray((TILE_H, 256), dtype=nl.float32, buffer=nl.sbuf) |
| t2_bc = nl.ndarray((TILE_H, 256), dtype=nl.float32, buffer=nl.sbuf) |
|
|
| Y_first_r = nl.ndarray((TILE_H, 256), dtype=nl.float32, buffer=nl.sbuf) |
| Y_first_i = nl.ndarray((TILE_H, 256), dtype=nl.float32, buffer=nl.sbuf) |
| Y_second_r = nl.ndarray((TILE_H, 256), dtype=nl.float32, buffer=nl.sbuf) |
| Y_second_i = nl.ndarray((TILE_H, 256), dtype=nl.float32, buffer=nl.sbuf) |
|
|
| _butterfly_pair( |
| half_A_r, |
| half_A_i, |
| half_B_r, |
| half_B_i, |
| tw512_r, |
| tw512_i, |
| Y_first_r, |
| Y_first_i, |
| Y_second_r, |
| Y_second_i, |
| tw2_out_r, |
| tw2_out_i, |
| t2_ac, |
| t2_bd, |
| t2_ad, |
| t2_bc, |
| ) |
|
|
| |
| Y_combined_r = nl.ndarray((TILE_H, N), dtype=nl.float32, buffer=nl.sbuf) |
| Y_combined_i = nl.ndarray((TILE_H, N), dtype=nl.float32, buffer=nl.sbuf) |
| nisa.tensor_copy(dst=Y_combined_r[0:TILE_H, 0:256], src=Y_first_r) |
| nisa.tensor_copy(dst=Y_combined_i[0:TILE_H, 0:256], src=Y_first_i) |
| nisa.tensor_copy(dst=Y_combined_r[0:TILE_H, 256:N], src=Y_second_r) |
| nisa.tensor_copy(dst=Y_combined_i[0:TILE_H, 256:N], src=Y_second_i) |
|
|
| nisa.dma_copy(dst=Y_real_hbm[0:TILE_H, 0:N], src=Y_combined_r) |
| nisa.dma_copy(dst=Y_imag_hbm[0:TILE_H, 0:N], src=Y_combined_i) |
| return Y_real_hbm, Y_imag_hbm |
|
|
|
|
| |
| |
| |
|
|
|
|
| def _next_power_of_2(n): |
| """Return the smallest power of 2 >= n.""" |
| if n <= 1: |
| return 1 |
| return 1 << (n - 1).bit_length() |
|
|
|
|
| def _compute_dft_matrix(N=128): |
| """Compute N-point DFT matrix split into real/imag.""" |
| k = np.arange(N, dtype=np.float32).reshape(N, 1) |
| n = np.arange(N, dtype=np.float32).reshape(1, N) |
| angles = -2.0 * np.pi * k * n / N |
| return np.cos(angles), np.sin(angles) |
|
|
|
|
| def _compute_twiddle_factors(N, H=128): |
| """Compute twiddle factors W_N^k for k=0..N/2-1, tiled to [H, N/2].""" |
| k = np.arange(N // 2, dtype=np.float32) |
| angles = -2.0 * np.pi * k / N |
| return np.tile(np.cos(angles), (H, 1)), np.tile(np.sin(angles), (H, 1)) |
|
|
|
|
| def nki_fft1d(x_real, x_imag, fft_size, device): |
| """Perform 1D FFT using NKI kernels. Input shape: (H, W) where H <= 128. |
| |
| Pads W to fft_size (must be power of 2: 128, 256, or 512). |
| Returns (Y_real, Y_imag) of shape (H, fft_size). |
| """ |
| H, W = x_real.shape |
| assert H <= 128, f"Tile height must be <= 128, got {H}" |
| assert fft_size in (128, 256, 512), f"Only 128/256/512 supported, got {fft_size}" |
|
|
| |
| x_r = torch.zeros(128, fft_size, dtype=torch.float32, device=device) |
| x_i = torch.zeros(128, fft_size, dtype=torch.float32, device=device) |
| x_r[:H, :W] = x_real[:H, : min(W, fft_size)] |
| x_i[:H, :W] = x_imag[:H, : min(W, fft_size)] |
|
|
| |
| W_r_np, W_i_np = _compute_dft_matrix(128) |
| W_r = torch.tensor(W_r_np, dtype=torch.float32, device=device) |
| W_i = torch.tensor(W_i_np, dtype=torch.float32, device=device) |
|
|
| if fft_size == 128: |
| y_r, y_i = _fft1d_128(x_r, x_i, W_r, W_i) |
| elif fft_size == 256: |
| tw_r_np, tw_i_np = _compute_twiddle_factors(256, 128) |
| tw_r = torch.tensor(tw_r_np, dtype=torch.float32, device=device) |
| tw_i = torch.tensor(tw_i_np, dtype=torch.float32, device=device) |
| y_r, y_i = _fft1d_256(x_r, x_i, W_r, W_i, tw_r, tw_i) |
| elif fft_size == 512: |
| tw256_r_np, tw256_i_np = _compute_twiddle_factors(256, 128) |
| tw512_r_np, tw512_i_np = _compute_twiddle_factors(512, 128) |
| tw256_r = torch.tensor(tw256_r_np, dtype=torch.float32, device=device) |
| tw256_i = torch.tensor(tw256_i_np, dtype=torch.float32, device=device) |
| tw512_r = torch.tensor(tw512_r_np, dtype=torch.float32, device=device) |
| tw512_i = torch.tensor(tw512_i_np, dtype=torch.float32, device=device) |
| y_r, y_i = _fft1d_512(x_r, x_i, W_r, W_i, tw256_r, tw256_i, tw512_r, tw512_i) |
|
|
| return y_r[:H, :fft_size], y_i[:H, :fft_size] |
|
|
|
|
| def _dft_matrix_torch(N, device): |
| """Compute N-point DFT matrix as a torch tensor (for arbitrary N).""" |
| k = torch.arange(N, dtype=torch.float32, device=device).unsqueeze(1) |
| n = torch.arange(N, dtype=torch.float32, device=device).unsqueeze(0) |
| angles = -2.0 * math.pi * k * n / N |
| return torch.cos(angles), torch.sin(angles) |
|
|
|
|
| def _torch_fft1d(x_real, x_imag, device): |
| """Pure PyTorch 1D FFT via DFT matrix multiply. For sizes not supported by NKI. |
| |
| Input: (H, N) real and imag tensors. |
| Output: (H, N) real and imag tensors. |
| """ |
| H, N = x_real.shape |
| W_r, W_i = _dft_matrix_torch(N, device) |
| |
| |
| |
| y_real = x_real @ W_r.t() - x_imag @ W_i.t() |
| y_imag = x_real @ W_i.t() + x_imag @ W_r.t() |
| return y_real, y_imag |
|
|
|
|
| def _fft1d_dispatch(x_real, x_imag, N, device): |
| """Dispatch to NKI kernel for supported sizes, else use PyTorch DFT. |
| |
| Performs an N-point FFT on each row. Input must have exactly N columns. |
| """ |
| if N in (128, 256, 512) and x_real.shape[0] <= 128: |
| return nki_fft1d(x_real, x_imag, N, device) |
| else: |
| |
| return _torch_fft1d(x_real, x_imag, device) |
|
|
|
|
| def nki_fft2d_real(hidden_states, device): |
| """Perform 2D FFT on hidden_states (B, S, D) and return real part. |
| |
| Equivalent to torch.fft.fftn(hidden_states, dim=(1, 2)).real |
| |
| Decomposes into: |
| 1. 1D FFT along dim=2 (hidden_dim, size D) for each row |
| 2. 1D FFT along dim=1 (seq_dim, size S) for each column |
| 3. Take real part |
| |
| Uses NKI kernels for sizes 128/256/512; falls back to PyTorch DFT |
| matrix multiply for other sizes. |
| """ |
| B, S, D = hidden_states.shape |
|
|
| |
| results = [] |
| for b in range(B): |
| x = hidden_states[b].float() |
|
|
| |
| |
| pass1_real = torch.zeros(S, D, dtype=torch.float32, device=device) |
| pass1_imag = torch.zeros(S, D, dtype=torch.float32, device=device) |
|
|
| tile_size = 128 if D in (128, 256, 512) else S |
|
|
| for tile_start in range(0, S, 128): |
| tile_end = min(tile_start + 128, S) |
| tile_h = tile_end - tile_start |
| tile_data = x[tile_start:tile_end] |
| tile_imag = torch.zeros_like(tile_data) |
|
|
| yr, yi = _fft1d_dispatch(tile_data, tile_imag, D, device) |
| pass1_real[tile_start:tile_end] = yr[:tile_h, :D] |
| pass1_imag[tile_start:tile_end] = yi[:tile_h, :D] |
|
|
| |
| |
| p1_r_t = pass1_real.t().contiguous() |
| p1_i_t = pass1_imag.t().contiguous() |
|
|
| pass2_real = torch.zeros(D, S, dtype=torch.float32, device=device) |
| pass2_imag = torch.zeros(D, S, dtype=torch.float32, device=device) |
|
|
| for tile_start in range(0, D, 128): |
| tile_end = min(tile_start + 128, D) |
| tile_h = tile_end - tile_start |
|
|
| tile_r = p1_r_t[tile_start:tile_end] |
| tile_i = p1_i_t[tile_start:tile_end] |
|
|
| yr, yi = _fft1d_dispatch(tile_r, tile_i, S, device) |
| pass2_real[tile_start:tile_end] = yr[:tile_h, :S] |
| pass2_imag[tile_start:tile_end] = yi[:tile_h, :S] |
|
|
| |
| final_real = pass2_real.t() |
| results.append(final_real) |
|
|
| return torch.stack(results, dim=0) |
|
|
|
|
| |
| |
| |
|
|
|
|
| class NeuronFNetFourierLayout(nn.Module): |
| """Layout for FNetBasicFourierTransform. No learnable weights needed.""" |
|
|
| conversion_mapping = [] |
|
|
| def __init__(self, config, layer_idx: int): |
| super().__init__() |
| self.hidden_size = config.hidden_size |
| |
| self._use_tpu_fourier = getattr(config, "use_tpu_fourier_optimizations", False) |
|
|
| def forward(self, hidden_states): |
| pass |
|
|
|
|
| |
| |
| |
|
|
|
|
| class NeuronFNetFourierForward(nn.Module): |
| """NKI-accelerated 2D Fourier Transform for FNet. |
| |
| Replaces torch.fft.fftn(x, dim=(1,2)).real with NKI kernels that perform |
| the FFT entirely on NeuronCore hardware using the Tensor Engine for the |
| 128-point DFT base case. |
| """ |
|
|
| def forward(self, hidden_states): |
| device = hidden_states.device |
|
|
| |
| output = nki_fft2d_real(hidden_states, device) |
|
|
| |
| output = output.to(hidden_states.dtype) |
| return (output,) |
|
|
|
|
| |
| |
| |
|
|
|
|
| class layers: |
| NeuronFNetFourierForward = NeuronFNetFourierForward |
|
|