-
Notifications
You must be signed in to change notification settings - Fork 82
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
Spreadinterponly (CPU and GPU) #602
base: master
Are you sure you want to change the base?
Conversation
…U type changes to keep
…ge-upsampfac warning (it legit could be Inf)
@chaithyagr If you look at the example, tester, and docs/opts.rst you will see my proposal for the cpu spreadinterponly=1 implementation. See what you think. If you are not unhappy - let me know - I will apply this to GPU too - the difference being you would not have to set upsampfac=1 to use it, rather would set it as with a NUFFT. This is really the only way I can see it working, without breaking the API. I still would be curious how you guys access the actual kernel function used, in your DCW for MRI application... |
@chaithyagr Still hoping for some feedback on GPU interface tweak, so I can go ahead (early February at this rate) with: Revisit gpu_spreadinterponly=1 and edit logic so upsampfac is respected rather than forced to be 1.0. |
Hey @ahbarnett , I am so sorry for my delay. I think I responded your question regarding
To be clearer: Same applies for adjoint operation where we provide a memory location for output image dimensions.
Finally, please do let me know when you are ready and I would like to make sure my tests pass and I can make fixes right on my side. Thank you so much for handeling this and please do let me know when you are ready. I will do my changes on finufft side at mri-nufft today and give you update on whether it works good. |
Assuming that we need to provide (512, 512), note that current state of codes lead to following error: finufft/python/finufft/finufft/_interfaces.py Lines 467 to 474 in d13dade
Basically the validity checks are right, except when we are pushing out a larger array when we have spreadinterp_only is true. |
@ahbarnett did you get a chance to look at my comments? The main issue of setting |
@chaithyagr Sorry about the delay. But the answer to your question is no: the output array for a type 1 always has the requested size. Recall that when spreadinterponly=1, upsampfac only controls the kernel choice and width in gridpoints; there is no actual upsampling, just spreading to the user-requested grid. It wouldn't make sense to do much else. The spreading kernel must be controlled somehow, and this is the most elegant way to do it (via tol and upsampfac, as kernel control parameters only). Note that for type 2 it would also be meaningless to "upsample" the user's regular input array: again, plain interpolation from their grid is done. See examples/spreadinterponly1d.cpp Let me know if unclear. I will finish up the PR now on the GPU side. Best, Alex |
@chaithyagr I am done and would like to bring it in, if you can let me know. From your end (GPU user) there is only one change: upsampfac cannot be set to 1.0; it must be a valid setting in order to know what kernel shape to use. Recall these are 0.0 (auto-choose), one of the valid settings 1.25 or 2.0 (for fast kerevalmeth=0), or any number in (1,+Inf], for slower kerevalmeth=1. Recall that upsampfac is only a kernel shape control parameter, and does not scale the I/O f grid which is always the requested size (N1*N2 etc). It is amusing that upsampfac=Inf is a valid setting for kerevalmeth=1, even though it could never be used for a NUFFT. I have removed your error code 23, as there is no need. I improved the comments. I fixed the docs to match, and to improve the explanation for you about the precise grid that is spread/interp to. I added a matlab CPU demo that includes plotting the spreading kernel: Best, Alex |
Hey @ahbarnett , thats awesome. Thank you for your updates. I will surely get back to you early next week! My understanding based on how it used to work in gpuNUFFT is that :
Am I missing something? If above is true, wouldnt removing FFT imply exposing a larger grid image as input (for forward) and as output (for adjoint). RIght? i.e. isnt the output the spread grid with a different size? I will go through code to get more clarity. Btw, the CPU one works when we give the right shapes as expected, but I think the output was cropped. Hopwfully I can get some example images to give clarity in my message above |
… clang-format make compat w/ 14.0.0
if (opts.spreadinterponly) { // (unusual case of no NUFFT, just report) | ||
|
||
// spreadinterp grid will simply be the user's "mode" grid... | ||
for (int idim = 0; idim < dim; ++idim) nfdim[idim] = mstu[idim]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chaithyagr this is the CPU version where you see upsampling is switched off. Indeed, it needs to write to the user sizes of array, so it cannot be any other way.
@chaithyagr Sorry, I forgot to set nf1=ms (ie, N1), and the equivalents in other dims. I fixed that now. The problem is we don't have a tester for your gpu_spreadinterponly=1 mode, so CI doesn't catch such a bug. For CPU I know the behavior is correct. I didn't understand your comment about output being cropped: that is just for plotting purposes! The I/O grid sizes are (500,1000) and indeed the result matches that. Re code, see l. 665 of src/finufft_core.cpp. I can't make github make a link to that since it's in a PR. I tagged you in a comment. But, in terms of behavior, I feel that the docs are clear: there is no upsampling, and the "upsampfac" merely controls the kernel parameters. I'm not sure what's unclear about that. Please let me know. PS I have no idea what gpuNUFFT does! (recall that none of these other codes provide actual mathematical formulae for what they do... I could do that for spreadinterponly=1 mode, but I want to know it's used first....) Thanks, Alex |
|
||
if (d_plan->opts.gpu_spreadinterponly) { | ||
// spread/interp grid is precisely the user "mode" sizes, no upsampling | ||
nf1 = d_plan->ms; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chaithyagr ...and here is the corresponding GPU code. At the risk of repetition, since the user allocates an N1*N2 output array, spreading could not write to any other size without segfault. Agreed?
Indeed, I was able to hack my local test/cuda/cufinufft1d_test to set debug=1 and gpu_spreadinterponly=1,
Note the error is garbage, as it should be since it thinks it's a NUFFT :) We need to add a GPU version of the basic SI-only math test that I created for the CPU. Anyway, I'll leave this now. Let me know if you are happy. Thanks, Alex |
Hey @ahbarnett , I finally tested your codes on our plugin and it works great! Thank you so much for your updates and cleaning up of my hacky implementation. Please let us know if you need anything else from us. Currently we have some basic python tests in our repository. Sadly we dont have any C++ version of tests. I can help with it if needed, but I cant work on it urgently, please let us know. Thank you again for this work! |
Could you send a link to the tests? |
The ongoing PR is at mind-inria/mri-nufft#224 In particular, you can see https://github.com/chaithyagr/mri-nufft/blob/7187491268f19ded65627cbed8d0a4a7acfbb9b6/tests/operators/test_density_for_op.py#L28-L52 This test checks that the density compensation weights is approximately the inverse of the radial distance for radial trajectories. Its not a very specific mathematical test, but when the trajectory is Nyquist, we expect the density to match what is theoretical. However, for MR recon approximations are okay. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good.
Just minor documentation issues.
**spreadinterponly**: [only has effect for type 1 or 2.] For experts only! | ||
If ``0`` do | ||
the NUFFT as intended. If ``1``, omit the FFT and deconvolution | ||
(diagonal division by kernel Fourier transform) steps, thus returning | ||
*garbage answers as a NUFFT*, but allowing experts to perform solely | ||
spreading (if type 1) or solely interpolation (if type 2) by hijacking | ||
the usual FINUFFT API. The spreading is onto the grid of the | ||
user-given size (``N1`` in x, ``N2`` in y, etc), with grid points | ||
located at coordinates $\{-\pi, -\pi+h, \dots, \pi-h\}$ in each | ||
dimension, where $h = 2\pi/N$ is the spacing for that dimension ($N$ | ||
here meaning ``N1``, etc). Interpolation is from that same grid. The | ||
kernel (width and shape parameter) is determined by ``tol`` and | ||
``opts.upsampfac``, just as it would be in an actual NUFFT. Note that | ||
the upsampling factor here only controls the kernel; the grid size | ||
never differs from ``N1``, etc. The kernel is not directly | ||
accessible, leaving the user to figure out how to make use of this | ||
interface to extract the actual kernel function. This provides a | ||
convenient (if hacky) interface to our ``spreadinterp`` module | ||
(including looping over multiple vectors, if ``ntransf>1``). The | ||
known use-case here is estimating so-called density compensation, | ||
conventionally used in MRI (see `MRI-NUFFT | ||
<https://mind-inria.github.io/mri-nufft/nufft.html>`_), although it | ||
might also be useful in spectral Ewald. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the rendered docs here: https://github.com/flatironinstitute/finufft/blob/spreadinterponly/docs/opts.rst
modeord has a nice itemize. I think is worth doing the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe: if 1
: It does not perform a NUFFT!
hijacking -> adapting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if hacky -> unconventional | unorthodox
x[0] = 0.0; | ||
c[0] = 1.0; | ||
int unused = 1; | ||
int ier = finufft1d1(1, &x[0], &c[0], unused, tol, N, &F[0], &opts); // warm-up |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
x.data(), c.data(), F.data()
&x[0] is hacky
we should not encourage users to do so.
// 1 FFT-style mode order | ||
int modeord; // (type 1,2 only): 0 CMCL-style increasing mode order | ||
// 1 FFT-style mode order | ||
int spreadinterponly; // 0 do actual NUFFT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as per comment for modeord (type1, 2 only) should be specified here too
upsampfac); | ||
return FINUFFT_ERR_UPSAMPFAC_TOO_SMALL; | ||
} | ||
// calling routine must abort on above errors, since opts is garbage! | ||
if (showwarn && upsampfac > 4.0) | ||
// calling routine must abort on above errors, since (spread)opts is garbage! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should update the comment to match the code. This is garbage only for a NUFFT
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good.
Just minor documentation issues.
This is a proposal for how a convenient access to the spread/interp task is possible via the FINUFFT/cuFINUFFT interface. (since spreadinterp module is not part of our API). It is based on @chaithyagr PR #599 for the new CPU opts field. And the GPU s/i-only is already part of 2.3.1 and master.
Design discussion:
I feel that the GPU spreadinterponly interface has to be tweaked so that upsampfac controls the kernel shape. This would also apply to CPU. Currently GPU forces upsampfac=1.0, but under the hood uses the kernel for the default upsampfac=2, which makes no sense.
Eg, I would like to be able to set upsampfac=infinity, which gives a certain kernel shape needed in Ewald methods.
I will hash this out here. Comments welcome.
Tasks: