Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scipy.fft: Add a plan argument #10302

Closed
peterbell10 opened this issue Jun 14, 2019 · 32 comments · Fixed by #11408
Closed

scipy.fft: Add a plan argument #10302

peterbell10 opened this issue Jun 14, 2019 · 32 comments · Fixed by #11408
Labels
enhancement A new feature or improvement scipy.fft
Milestone

Comments

@peterbell10
Copy link
Member

peterbell10 commented Jun 14, 2019

See #10238 (comment)

scipy.fft currently lacks any plan caching. For repeated transforms, this does a significant amount of duplicate work and makes scipy.fft slower than scipy.fftpack for repeated regular sized ffts. (For one off ffts, pocketfft is still much faster)

Things to look into/discuss:

  • How much faster is pocketfft after adding a cache?
  • Can we have users pass in the plans instead of caching?
  • Should the user have control over the cache itself?
@peterbell10
Copy link
Member Author

I've implemented a simple cache with a FIFO eviction policy.

get_plan function
template<typename T, size_t size=4> shared_ptr<T> get_plan(size_t len)
	{
	static shared_ptr<T> cache[size];
	static size_t keys[size] = {0};
	static int head = 0;
	static mutex mut;
	{
	std::lock_guard<mutex> lock(mut);
	for (int i = 0; i < size; ++i)
		{
		if (keys[i] == len)
			{
			if (cache[i] != nullptr)
				return cache[i];

			break;
			}
		}
	}

	auto plan = make_shared<T>(len);
	{
	std::lock_guard<mutex> lock(mut);

	// If some else beat us to it, use theirs
	for (int i = 0; i < size; ++i)
		{
		if (keys[i] == len)
			{
			if (cache[i] != nullptr)
				return cache[i];

			break;
			}
		}

	keys[head] = len;
	cache[head] = plan;
	head++;

	if (head >= size)
		head = 0;
	}
	return plan;
	}

Benchmarks

Without cache

· `wheel_cache_size` has been renamed to `build_cache_size`. Update your `asv.conf.json` accordingly.
· Discovering benchmarks
· Running 5 total benchmarks (1 commits * 1 environments * 5 benchmarks)
[  0.00%] ·· Benchmarking existing-py_usr_bin_py
[ 10.00%] ··· Running (fft_basic.Fft.time_fft--)...
[ 40.00%] ··· Running (fft_basic.RFft.time_irfft--)..
[ 60.00%] ··· fft_basic.Fft.time_fft                                          ok
[ 60.00%] ··· ====== ======= =============== ============= ============
              --                               module                  
              -------------- ------------------------------------------
               size    type   scipy.fftpack    scipy.fft    numpy.fft  
              ====== ======= =============== ============= ============
               100     real     2.60±0.1μs    4.56±0.06μs   8.52±0.2μs 
               100    cmplx    2.82±0.04μs    4.34±0.07μs   8.29±0.2μs 
               256     real    3.14±0.05μs     5.53±0.1μs   10.2±0.2μs 
               256    cmplx     4.33±0.7μs     5.93±0.4μs   10.0±0.1μs 
               313     real      70.3±1μs      28.5±0.5μs    117±3μs   
               313    cmplx      120±3μs       26.9±0.5μs    116±1μs   
               512     real     4.74±0.1μs     7.47±0.2μs   14.2±0.7μs 
               512    cmplx     6.44±0.5μs     8.73±0.4μs   13.4±0.3μs 
               1000    real     8.83±0.5μs     11.2±0.2μs    24.5±2μs  
               1000   cmplx     10.7±0.1μs     14.8±0.5μs    21.2±2μs  
               1024    real     7.93±0.2μs      10.5±3μs    20.6±0.3μs 
               1024   cmplx     10.7±0.4μs     14.5±0.3μs   20.6±0.6μs 
               2048    real     14.1±0.2μs     17.9±0.4μs   37.5±0.3μs 
               2048   cmplx     22.5±0.8μs     28.8±0.3μs   35.6±0.2μs 
               4096    real      38.4±4μs      35.4±0.3μs    69.2±1μs  
               4096   cmplx      48.6±6μs      55.7±0.6μs    68.4±2μs  
               8192    real      68.1±2μs       75.0±1μs     233±30μs  
               8192   cmplx      113±2μs        128±1μs      149±2μs   
              ====== ======= =============== ============= ============

[ 70.00%] ··· fft_basic.Fft.time_ifft                                         ok
[ 70.00%] ··· ====== ======= =============== ============ ============
              --                               module                 
              -------------- -----------------------------------------
               size    type   scipy.fftpack   scipy.fft    numpy.fft  
              ====== ======= =============== ============ ============
               100     real    2.75±0.06μs    4.99±0.3μs   10.9±0.3μs 
               100    cmplx     3.49±0.1μs    4.55±0.4μs   10.8±0.2μs 
               256     real     3.57±0.4μs    6.13±0.5μs   12.4±0.2μs 
               256    cmplx     4.92±0.5μs    6.27±0.3μs   12.3±0.2μs 
               313     real      76.2±3μs     28.9±0.6μs    124±20μs  
               313    cmplx      125±3μs       35.0±4μs     118±6μs   
               512     real    5.05±0.08μs     8.49±1μs    17.0±0.3μs 
               512    cmplx     8.16±0.3μs    8.79±0.2μs    17.7±1μs  
               1000    real      15.0±9μs     12.3±0.7μs   25.7±0.5μs 
               1000   cmplx     18.0±0.5μs     16.4±2μs    25.7±0.5μs 
               1024    real    8.33±0.03μs    11.2±0.2μs    25.9±1μs  
               1024   cmplx     15.0±0.3μs    15.3±0.3μs   26.3±0.5μs 
               2048    real     16.9±0.4μs     20.2±4μs    42.3±0.6μs 
               2048   cmplx     30.7±0.6μs    29.7±0.9μs   43.6±0.5μs 
               4096    real     35.0±0.5μs    40.1±0.6μs    76.1±2μs  
               4096   cmplx     62.1±0.6μs     58.8±1μs     80.4±7μs  
               8192    real      79.5±2μs      79.1±1μs     172±4μs   
               8192   cmplx      136±2μs       129±2μs      172±2μs   
              ====== ======= =============== ============ ============

[ 80.00%] ··· fft_basic.Fftn.time_fftn                                        ok
[ 80.00%] ··· ========== ======= =============== =============
                 size      type       module                  
              ---------- ------- --------------- -------------
               100x100     real   scipy.fftpack     219±6μs   
               100x100     real     scipy.fft       136±3μs   
               100x100     real     numpy.fft       203±5μs   
               100x100    cmplx   scipy.fftpack     223±7μs   
               100x100    cmplx     scipy.fft       163±5μs   
               100x100    cmplx     numpy.fft       198±7μs   
               313x100     real   scipy.fftpack    12.3±0.2ms 
               313x100     real     scipy.fft       615±9μs   
               313x100     real     numpy.fft      10.8±0.1ms 
               313x100    cmplx   scipy.fftpack   12.0±0.05ms 
               313x100    cmplx     scipy.fft       942±10μs  
               313x100    cmplx     numpy.fft      10.8±0.3ms 
               1000x100    real   scipy.fftpack   2.14±0.09ms 
               1000x100    real     scipy.fft       947±40μs  
               1000x100    real     numpy.fft      4.03±0.7ms 
               1000x100   cmplx   scipy.fftpack    2.20±0.1ms 
               1000x100   cmplx     scipy.fft     1.30±0.03ms 
               1000x100   cmplx     numpy.fft     2.23±0.09ms 
               256x256     real   scipy.fftpack   1.32±0.01ms 
               256x256     real     scipy.fft       604±10μs  
               256x256     real     numpy.fft      2.11±0.7ms 
               256x256    cmplx   scipy.fftpack    1.42±0.1ms 
               256x256    cmplx     scipy.fft       848±20μs  
               256x256    cmplx     numpy.fft      2.69±0.8ms 
               512x512     real   scipy.fftpack    7.86±0.9ms 
               512x512     real     scipy.fft      2.64±0.3ms 
               512x512     real     numpy.fft       10.8±2ms  
               512x512    cmplx   scipy.fftpack    7.87±0.9ms 
               512x512    cmplx     scipy.fft      4.91±0.1ms 
               512x512    cmplx     numpy.fft       10.4±3ms  
              ========== ======= =============== =============

[ 90.00%] ··· fft_basic.RFft.time_irfft                                       ok
[ 90.00%] ··· ====== =============== ============ ============
              --                       module                 
              ------ -----------------------------------------
               size   scipy.fftpack   scipy.fft    numpy.fft  
              ====== =============== ============ ============
               100     2.71±0.08μs    4.99±0.4μs   11.7±0.3μs 
               256      3.59±0.4μs    5.57±0.2μs   13.0±0.2μs 
               313      70.5±0.9μs    6.73±0.3μs   19.1±0.3μs 
               512      4.61±0.3μs    7.29±0.2μs   15.4±0.1μs 
               1000     7.59±0.1μs    11.8±0.3μs   20.5±0.5μs 
               1024     6.79±0.1μs    10.1±0.3μs   19.8±0.5μs 
               2048     13.2±0.7μs    18.5±0.5μs   29.2±0.4μs 
               4096     29.6±0.5μs     38.0±1μs     54.5±4μs  
               8192     67.3±0.8μs     80.7±1μs     101±2μs   
              ====== =============== ============ ============

[100.00%] ··· fft_basic.RFft.time_rfft                                        ok
[100.00%] ··· ====== =============== ============= ============
              --                       module                  
              ------ ------------------------------------------
               size   scipy.fftpack    scipy.fft    numpy.fft  
              ====== =============== ============= ============
               100      2.60±0.2μs     4.23±0.1μs   7.76±0.2μs 
               256      3.44±0.4μs     6.06±0.4μs   9.07±0.4μs 
               313       73.1±3μs       30.4±3μs     83.4±6μs  
               512       5.62±2μs      7.75±0.3μs   11.1±0.9μs 
               1000     7.03±0.3μs     10.5±0.3μs   15.0±0.7μs 
               1024      8.02±4μs     9.72±0.07μs   13.9±0.4μs 
               2048      13.0±2μs       15.8±3μs    20.7±0.3μs 
               4096     23.7±0.6μs     31.6±0.3μs    38.2±1μs  
               8192     53.0±0.7μs      66.9±2μs     78.0±3μs  
              ====== =============== ============= ============

Running benchmarks for Scipy version 1.4.0.dev0+c120e22 at /mnt/d/git/scipy/build/testenv/lib/python3.7/site-packages/scipy/__init__.py

With cache

· `wheel_cache_size` has been renamed to `build_cache_size`. Update your `asv.conf.json` accordingly.
· Discovering benchmarks
· Running 5 total benchmarks (1 commits * 1 environments * 5 benchmarks)
[  0.00%] ·· Benchmarking existing-py_usr_bin_py
[ 10.00%] ··· Running (fft_basic.Fft.time_fft--)...
[ 40.00%] ··· Running (fft_basic.RFft.time_irfft--)..
[ 60.00%] ··· fft_basic.Fft.time_fft                                          ok
[ 60.00%] ··· ====== ======= =============== ============= ============
              --                               module                  
              -------------- ------------------------------------------
               size    type   scipy.fftpack    scipy.fft    numpy.fft  
              ====== ======= =============== ============= ============
               100     real    2.64±0.05μs    3.08±0.08μs   8.63±0.1μs 
               100    cmplx    2.75±0.07μs     3.20±0.1μs   8.85±0.3μs 
               256     real     3.36±0.1μs     4.85±0.7μs   10.3±0.3μs 
               256    cmplx     4.40±0.5μs     4.33±0.1μs   9.81±0.3μs 
               313     real      72.8±2μs      15.6±0.3μs    116±3μs   
               313    cmplx      124±1μs       14.8±0.2μs    117±3μs   
               512     real     5.25±0.7μs     5.41±0.1μs   14.3±0.2μs 
               512    cmplx     6.01±0.1μs     6.59±0.1μs   13.4±0.3μs 
               1000    real     7.86±0.7μs     9.20±0.3μs    21.9±1μs  
               1000   cmplx    10.7±0.07μs     11.7±0.2μs   21.1±0.3μs 
               1024    real     7.43±0.5μs     8.42±0.2μs   21.8±0.9μs 
               1024   cmplx    10.4±0.07μs     11.4±0.3μs   20.6±0.4μs 
               2048    real     14.1±0.7μs     14.9±0.8μs    36.9±1μs  
               2048   cmplx     22.6±0.4μs     23.8±0.4μs   35.9±0.6μs 
               4096    real     28.2±0.4μs     30.0±0.6μs   68.9±0.8μs 
               4096   cmplx     46.2±0.5μs     46.9±0.6μs    65.4±1μs  
               8192    real     62.0±0.8μs     63.7±0.7μs    154±4μs   
               8192   cmplx      105±9μs        111±3μs      148±2μs   
              ====== ======= =============== ============= ============

[ 70.00%] ··· fft_basic.Fft.time_ifft                                         ok
[ 70.00%] ··· ====== ======= =============== ============= ============
              --                               module                  
              -------------- ------------------------------------------
               size    type   scipy.fftpack    scipy.fft    numpy.fft  
              ====== ======= =============== ============= ============
               100     real    2.58±0.04μs     3.16±0.2μs   10.9±0.3μs 
               100    cmplx     3.38±0.1μs     3.28±0.3μs   10.9±0.2μs 
               256     real    3.55±0.08μs    3.99±0.04μs   12.8±0.3μs 
               256    cmplx     4.70±0.3μs     4.75±0.2μs   12.7±0.3μs 
               313     real      74.6±2μs      15.5±0.1μs    118±6μs   
               313    cmplx      126±2μs       14.8±0.2μs    117±4μs   
               512     real     4.82±0.1μs     6.04±0.4μs   17.6±0.2μs 
               512    cmplx     8.10±0.2μs     7.23±0.5μs   18.1±0.3μs 
               1000    real     8.36±0.1μs     9.68±0.3μs   25.5±0.4μs 
               1000   cmplx     19.2±0.9μs     11.9±0.3μs   25.7±0.5μs 
               1024    real     8.07±0.3μs     9.54±0.3μs   24.6±0.5μs 
               1024   cmplx     14.7±0.2μs     12.3±0.2μs   25.8±0.4μs 
               2048    real     15.3±0.4μs     16.1±0.3μs    44.5±1μs  
               2048   cmplx     31.6±0.3μs     25.1±0.5μs   44.2±0.9μs 
               4096    real      33.8±1μs      39.9±10μs     78.1±1μs  
               4096   cmplx      62.1±1μs       58.2±7μs     80.9±1μs  
               8192    real     70.7±0.9μs     69.3±0.6μs    173±4μs   
               8192   cmplx      137±2μs        112±4μs      178±5μs   
              ====== ======= =============== ============= ============

[ 80.00%] ··· fft_basic.Fftn.time_fftn                                        ok
[ 80.00%] ··· ========== ======= =============== =============
                 size      type       module                  
              ---------- ------- --------------- -------------
               100x100     real   scipy.fftpack     213±4μs   
               100x100     real     scipy.fft       141±7μs   
               100x100     real     numpy.fft       205±4μs   
               100x100    cmplx   scipy.fftpack     226±8μs   
               100x100    cmplx     scipy.fft       181±6μs   
               100x100    cmplx     numpy.fft       205±4μs   
               313x100     real   scipy.fftpack    13.1±0.7ms 
               313x100     real     scipy.fft       600±20μs  
               313x100     real     numpy.fft      11.2±0.3ms 
               313x100    cmplx   scipy.fftpack    12.5±0.2ms 
               313x100    cmplx     scipy.fft       937±30μs  
               313x100    cmplx     numpy.fft      11.1±0.4ms 
               1000x100    real   scipy.fftpack    2.18±0.1ms 
               1000x100    real     scipy.fft       955±30μs  
               1000x100    real     numpy.fft      4.04±0.8ms 
               1000x100   cmplx   scipy.fftpack    2.37±0.2ms 
               1000x100   cmplx     scipy.fft     1.44±0.05ms 
               1000x100   cmplx     numpy.fft     2.32±0.09ms 
               256x256     real   scipy.fftpack   1.36±0.05ms 
               256x256     real     scipy.fft       611±20μs  
               256x256     real     numpy.fft      1.55±0.7ms 
               256x256    cmplx   scipy.fftpack   1.39±0.09ms 
               256x256    cmplx     scipy.fft       844±20μs  
               256x256    cmplx     numpy.fft      2.66±0.7ms 
               512x512     real   scipy.fftpack    7.17±0.7ms 
               512x512     real     scipy.fft      2.75±0.3ms 
               512x512     real     numpy.fft      10.5±0.8ms 
               512x512    cmplx   scipy.fftpack    7.68±0.5ms 
               512x512    cmplx     scipy.fft      5.18±0.3ms 
               512x512    cmplx     numpy.fft       10.8±3ms  
              ========== ======= =============== =============

[ 90.00%] ··· fft_basic.RFft.time_irfft                                       ok
[ 90.00%] ··· ====== =============== ============= ============
              --                       module                  
              ------ ------------------------------------------
               size   scipy.fftpack    scipy.fft    numpy.fft  
              ====== =============== ============= ============
               100     2.71±0.05μs     3.35±0.3μs   12.5±0.3μs 
               256      3.35±0.2μs    3.91±0.04μs   13.7±0.3μs 
               313       75.2±5μs      5.04±0.2μs    19.8±1μs  
               512      4.67±0.1μs    5.37±0.08μs   16.7±0.5μs 
               1000    7.79±0.04μs     8.81±0.4μs   21.0±0.5μs 
               1024     7.37±0.7μs     7.94±0.2μs   20.2±0.5μs 
               2048     13.0±0.4μs     14.6±0.7μs    31.4±2μs  
               4096     30.8±0.4μs     32.5±0.5μs    51.5±2μs  
               8192      68.4±2μs       68.8±4μs     102±1μs   
              ====== =============== ============= ============

[100.00%] ··· fft_basic.RFft.time_rfft                                        ok
[100.00%] ··· ====== =============== ============= ============
              --                       module                  
              ------ ------------------------------------------
               size   scipy.fftpack    scipy.fft    numpy.fft  
              ====== =============== ============= ============
               100      2.72±0.5μs    3.83±0.06μs   7.66±0.1μs 
               256      3.80±0.6μs    3.70±0.08μs   8.62±0.2μs 
               313       72.3±2μs       17.7±1μs     73.4±1μs  
               512      5.53±0.9μs    4.92±0.07μs    10.8±2μs  
               1000    6.94±0.08μs     8.19±0.6μs   14.4±0.3μs 
               1024     6.93±0.1μs     8.32±0.7μs   14.0±0.3μs 
               2048     12.4±0.7μs     12.8±0.2μs   22.0±0.3μs 
               4096      26.8±2μs      26.4±0.5μs   39.0±0.9μs 
               8192      58.9±1μs       57.6±1μs     79.8±3μs  
              ====== =============== ============= ============

Running benchmarks for Scipy version 1.4.0.dev0+c120e22 at /mnt/d/git/scipy/build/testenv/lib/python3.7/site-packages/scipy/__init__.py

@larsoner
Copy link
Member

A flexible API would be:

def fft(x, axis=-1, norm=None, plan='auto')
    """
    ...
    
    Parameters
    ----------
    ...
    plan : {'auto', None, scipy.fft.PocketFFTPlan}
        If 'auto' (default), use an internally managed FIFO cache of FFT plans.
        If None, a plan will be computed and used (the internal cache will not be modified).
        If a PocketFFTPlan instance is provided, it will be used and the internal
        cache will not be modified.

    See Also
    --------
    <functions related to cache management>

@peterbell10 WDYT both in terms of usability, and in terms of compat with PyFFTW / PyCUDA APIs?

@peterbell10
Copy link
Member Author

I think having that level of customizability is good. An alternative API would be to have something like #4607 where the plan is an FFT callable object.

@peterbell10
Copy link
Member Author

As far as compatibility with pyFFTW, I can't see any problems. For CuPy, there doesn't seem to be any support for plan objects but it has some internal cache. I also believe the underlying CUDA library mimics the FFTW interface so it should be possible.

@peterbell10
Copy link
Member Author

peterbell10 commented Jun 18, 2019

Actually, I found that cupy already supports a plan argument but only in cupyx.scipy.fftpack module and not cupy.fft.

I also noticed that these are symmetrical plans (i.e. fft can use the same plan as ifft) which doesn't seem to be the case for pyfftw.

@grlee77
Copy link
Contributor

grlee77 commented Jun 19, 2019

Actually, I found that cupy already supports a plan argument but only in cupyx.scipy.fftpack module and not cupy.fft.

Yes, the CuPy core devs did not want to deviate from the NumPy API in the cupy.fft module, but were more open to additional optional arguments being added under the cupyx modules.

Also, see cupy/cupy#1936 which discusses adding R2C and C2R support to the n-dimensional planning.
(Originally, CuPy did not have any n-dimensional plans and would just apply 1D plans separably along each axis in turn. I added n-dimensional planning support for complex-to-complex transforms, but no one has gotten around to the R2C or C2R cases yet.)

@peterbell10
Copy link
Member Author

From my earlier benchmark data I've plotted percentage difference in FFT time for the 1D case. The speedup is ~20% or more.
fft_cacheing

@larsoner
Copy link
Member

@peterbell10 this does indeed seem worth adding. Maybe a LRU instead of FIFO, though, assuming there is not a big performance hit for adding it?

Does the API I proposed above make the most sense to you? Or do you prefer the callable approach from #4607 (I haven't looked closely at it)?

@peterbell10
Copy link
Member Author

@peterbell10 this does indeed seem worth adding. Maybe a LRU instead of FIFO, though, assuming there is not a big performance hit for adding it?

Sure, I only wrote it as a FIFO for the sake of quick prototyping. I wouldn't expect the LRU bookkeeping to add a significant performance overhead.

Does the API I proposed above make the most sense to you? Or do you prefer the callable approach from #4607 (I haven't looked closely at it)?

I think both are reasonable and I don't have any real preference. The callable approach is similar to pyfftw's FFTW objects and your functional API is quite similar to CuPy's approach. Perhaps @grlee77 will have some insight as it sound like he's been involved in both of those?

@grlee77
Copy link
Contributor

grlee77 commented Jun 24, 2019

I think the API Eric suggested is good. pyFFTW has "builders" which return a callable like in #4607, but it also has plan caching that can be used by the scipy and numpy interfaces and works basically like the "auto" case proposed in Eric's API.
I am not a big fan of the "keep-alive time" approach used by pyFFTW and would be fine with something simple like an LRU cache.

I started a branch with a CUFFT plan cache for CuPy (see https://github.com/grlee77/cupy/commits/cufft_cache), but haven't started a PR for it. It is using a key-based dictionary with tracking of the total amount of memory being used for the work arrays currently in the cache so that the user could potentially set an upper memory limit for the cache. If I recall correctly, pocketfft does not keep large work arrays allocated within the plans, so I don't think similar memory limit concerns would apply here.

@larsoner
Copy link
Member

If I recall correctly, pocketfft does not keep large work arrays allocated within the plans, so I don't think similar memory limit concerns would apply here.

This should help. Also for users who are really concerned with memory, they can always use None or their own plans. Eventually we could also add support for setting limits on the MRU cache, either in number of plans or in approximate memory consumption. But I'd say let's start with a simple PR that will work in the (vast) majority of cases, and if someone wants to later add cache configuration options we can do it.

So @peterbell10 I think if you are satisfied with the API I proposed above and think it's sufficiently future compatible with the backend system we want to build, it's probably worth opening a PR to add this feature.

@larsoner
Copy link
Member

@mreineck
Copy link
Contributor

If I recall correctly, pocketfft does not keep large work arrays allocated within the plans, so I don't think similar memory limit concerns would apply here.

It's correct that it doesn't store the work arrays, but it stores the full twiddle factors, which usually have almost the size of the array to be transformed, and in some cases (Bluestein!) several times the array size.

So the memory consumption is not inacceptably high, but you can still swamp your computer if you run a few very large 1D FFTs with different lengths.

@peterbell10
Copy link
Member Author

One aspect missing from the API is plan creation:

  • should we have different plan creation functions for each transform function? Or a single function with an extra argument e.g. get_plan(shape, dtype, transform_type='fft').
  • how specific should plans be: does the plan and fft function have to be given exactly the same parameters and array, or should there be some wiggle room?

@larsoner
Copy link
Member

should we have different plan creation functions for each transform function? Or a single function with an extra argument e.g. get_plan(shape, dtype, transform_type='fft')

I would say a single one with a transform type.

how specific should plans be: does the plan and fft function have to be given exactly the same parameters and array, or should there be some wiggle room?

I would say as specific as the args to get_plan, at least to start. So the same shape/dtype/transform will give you the same plan each time.

@larsoner
Copy link
Member

We cache now, so I'll close this

@peterbell10
Copy link
Member Author

What about pre-planned transforms?

@larsoner
Copy link
Member

Feel free to reopen and re-title, or open a separate issue for that. We can also wait to implement until someone wants it

@peterbell10 peterbell10 changed the title scipy.fft: Should we include plan caching scipy.fft: Add a plan argument Aug 1, 2019
@peterbell10
Copy link
Member Author

Reopening as cupy is interested in having pre-planned transforms in their scipy.fft backend.

@leofang
Copy link
Contributor

leofang commented Dec 27, 2019

Hello guys, while the discussion of supporting a plan argument in cupyx.scipy.fftpack is ongoing (see cupy/cupy#2743), I come up with some random thoughts and I'd like to learn what SciPy developers/maintainers would think.

What if we add a keyword-only argument plan=None to scipy.fftpack (or to scipy.fft if you think more appropriate) and raise a NotImplementedError when any FFT function is called with plan not being None? The purpose is to let downstream libraries like pyFFTW and CuPy to reuse their own precomputed FFT plans (generated from FFTW and cuFFT, respectively).

@larsoner
Copy link
Member

@leofang adding that kwarg and behavior to scipy.fft sounds reasonable to me, what do you think @peterbell10 ?

@peterbell10
Copy link
Member Author

For scipy.fftpack I don't think we should make any API changes after declaring it as legacy.

For scipy.fft, I am a bit hesitant about enabling features that aren't supported by scipy itself. Partly because it could be confusing to users looking at the scipy docs. But also because it means there is no prototype for the behaviour the third-party backends are expected to follow.

@leofang
Copy link
Contributor

leofang commented Dec 30, 2019

For scipy.fftpack I don't think we should make any API changes after declaring it as legacy.

I would think it is fine as long as it is not declared deprecated. For the proposed change, there is no new feature implemented or change of behavior, after all. I do not insist on touching this module though.

For scipy.fft, I am a bit hesitant about enabling features that aren't supported by scipy itself. Partly because it could be confusing to users looking at the scipy docs.

We could say something in the doc along the line that “this argument is reserved for FFT backend providers and is not used by SciPy itself.”

But also because it means there is no prototype for the behaviour the third-party backends are expected to follow.

I don’t understand this. I thought it is obvious in the FFT community that when a plan is given, we execute it? Could you elaborate @peterbell10?

Alternatively, we may also just close this issue so as to make a guarantee that SciPy will not add the argument plan to any of its FFT modules, so that 3rd party providers can freely add it to extend the functionalities without worrying interference with SciPy? I am fine with either move.

@leofang
Copy link
Contributor

leofang commented Jan 16, 2020

@peterbell10 @larsoner Any comments? Thanks.

@peterbell10
Copy link
Member Author

But also because it means there is no prototype for the behaviour the third-party backends are expected to follow.

I thought it is obvious in the FFT community that when a plan is given, we execute it? Could you elaborate @peterbell10?

If all goes well then, yes, you should just get the same FFT computation but hopefully faster. I'm more concerned about the edge-cases where the FFT can or cannot be computed. How should errors be reported? Are the edges the same on all implementations? etc. These types of details can be important if we are keeping the interface generic.

That being said, I suppose since you aren't proposing SciPy add a get_fft_plan yet then nobody could actually use the interface in that way anyway.

@leofang
Copy link
Contributor

leofang commented Jan 20, 2020

I'm more concerned about the edge-cases where the FFT can or cannot be computed. How should errors be reported? Are the edges the same on all implementations? etc. These types of details can be important if we are keeping the interface generic.

Thanks Peter! I agree these are legit concerns. But I also think if we're gonna raise NotImplementedError in SciPy, downstream vendors should have the freedom to determine the edge-case behaviors.

That being said, I suppose since you aren't proposing SciPy add a get_fft_plan yet then nobody could actually use the interface in that way anyway.

That's right. I am only proposing a (placeholder) consumer API. The producer APIs like pyfftw.builders.* or cupyx.scipy.fftpack.get_fft_plan should be determined by the vendors.

Alternatively, we may also just close this issue so as to make a guarantee that SciPy will not add the argument plan to any of its FFT modules, so that 3rd party providers can freely add it to extend the functionalities without worrying interference with SciPy?

I added this as an alternative solution in my earlier post. Could be the simplest & fastest way out?

@larsoner
Copy link
Member

I'm okay with adding a plan=None that raises NotImplementedError (for now) if not None in SciPy. Something like:

if plan is not None:
    raise NotImplementedError('Passing a precomputed plan is not yet supported by scipy.fft functions')

That both provides an API for other packages to follow, and leaves the door open for us to eventually allow passing plans via scipy.fft.get_fft_plan or so.

@leofang
Copy link
Contributor

leofang commented Jan 21, 2020

Thanks Eric! One last question to both of you before I start drafting a PR: Do we want to touch the scipy.fft module only and leave scipy.fftpack intact, or we wanna add plan to both?

@larsoner
Copy link
Member

fftpack is deprecated and we shouldn't change it anymore.

Just wait to hear from @peterbell10 that he's okay with this plan before making a PR.

@mreineck
Copy link
Contributor

fftpack is deprecated and we shouldn't change it anymore.

As far as I recall, it is not officially deprecated, because eventually removing it would break too many codes.
On the other hand I'd suggest to leave it as it is now, or people won't get the message that scipy.fft is the package they should be using, at least for new projects.

@peterbell10
Copy link
Member Author

Okay, just reserving a keyword with plan=None seems fairly harmless.

@mreineck I agree. I believe the phrasing used was "long term legacy", not deprecated. The intention being it won't be removed but will also not receive new features. With that in mind, it should be left as-is without a plan argument.

@leofang
Copy link
Contributor

leofang commented Jan 24, 2020

Thanks a lot to you all, @peterbell10 @larsoner @mreineck @grlee77, for your comments and help!🙏

@tylerjereddy tylerjereddy added the enhancement A new feature or improvement label Jan 24, 2020
@tylerjereddy tylerjereddy added this to the 1.5.0 milestone Jan 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement A new feature or improvement scipy.fft
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants