Skip to content

Manage the lifetime of oneMKL FFT descriptors - #759

Merged
maleadt merged 3 commits into
JuliaGPU:mainfrom
wilkieolin:fft-descriptor-main
Nov 6, 2026
Merged

maleadt merged 3 commits into
JuliaGPU:mainfrom
wilkieolin:fft-descriptor-main

Conversation

@wilkieolin

@wilkieolin wilkieolin commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #758.

Every FFT plan commits a oneMKL DFT descriptor, and nothing destroyed it, so each fft, bfft or ifft call leaked one, along with the device memory that committing allocates for the descriptor's workspace: 31 MiB for a 64×32000×8 ComplexF32 array transformed along dimension 2. On an Arc A770 (16 GB), the loop from the issue (fft!(x, 2); ifft!(x, 2)) fails after a few hundred plans with FFT failed (-1).

  • Destroy descriptors of collected plans. A Descriptor object owns the oneMKL handle, and is shared by a plan and the inverse plans made from it. Its finalizer queues it, like sparse handles, and queued descriptors are destroyed on a regular task when creating a descriptor and when memory is reclaimed. This doesn't need to synchronize: compute calls wait for their work and keep the descriptor alive while they run.
  • Count descriptor workspaces towards the proactive GC. Neither garbage collector sees the workspace, so a loop of in-place transforms would still fill the device before any plan was collected. The workspace size that oneMKL reports after committing is now added to _allocated_bytes, so the pool's existing proactive GC collects plans as the device fills up.
  • Keep the SYCL queue of plans alive. A plan that outlived the task that created it crashed when used, because its queue lived only in that task's local storage. Descriptors now keep their queue rooted until they're destroyed.

On the A770, the issue's loop now completes 3000 plans in 33 s in place and 34 s out of place. Reusing a plan (p * x) remains the fast path; reusing committed descriptors across plans, as CUDA.jl's cuFFT does with a handle cache, is left for a follow-up.

@codecov

codecov Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.84615% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.99%. Comparing base (c5846c8) to head (94705c9).

Files with missing lines Patch % Lines
lib/mkl/fft.jl 93.84% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #759      +/-   ##
==========================================
- Coverage   86.83%   85.99%   -0.85%     
==========================================
  Files          55       55              
  Lines        5136     5112      -24     
==========================================
- Hits         4460     4396      -64     
- Misses        676      716      +40     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

wilkieolin and others added 3 commits November 5, 2026 12:21
Every plan commits a oneMKL descriptor and nothing destroyed it, so each `fft`, `bfft`
or `ifft` call leaked one, together with the device memory committing allocates for
its workspace (31 MiB for a 64×32000×8 ComplexF32 array transformed along dimension
2). A loop that transforms repeatedly fills the device after a few hundred calls.

Descriptors are now owned by a `Descriptor` object that plans and the inverse plans
made from them share. Its finalizer queues the descriptor, like sparse handles, and
queued descriptors are destroyed on a regular task when creating a descriptor and
when memory is reclaimed. That doesn't require synchronizing: compute calls wait for
their work and keep the descriptor alive while they run.

Co-authored-by: Tim Besard <tim.besard@gmail.com>
Neither garbage collector sees the workspace that committing a descriptor allocates,
so a loop that makes plans and allocates little else, e.g. one calling `fft!`, filled
the device before any plan was collected.

Account for the workspace in `_allocated_bytes`, which only exists to drive the pool's
proactive GC, so that the GC collects plans as the device fills up. oneMKL reports the
exact size after committing; before committing it only gives an estimate, which is
used to decide whether to collect first.

Co-authored-by: Wilkie Olin-Ammentorp <wolinamm@gmail.com>
A descriptor executes on the SYCL queue it was committed to, but plans only kept its
raw handle, while the queue object, and the SYCL context and Level Zero queue under it,
live in the creating task's local storage. Once that task was collected, using a plan
it created crashed.

Descriptors now keep their queue rooted in a global registry until they are destroyed.
Referencing it from the descriptor would only cover executing plans: once a task and
its plans are collected together, the queue can be finalized before the deferred
destruction of their descriptors.
@maleadt
maleadt force-pushed the fft-descriptor-main branch from 4092913 to 94705c9 Compare November 5, 2026 11:30
@maleadt maleadt changed the title Destroy oneMKL DFT descriptors of collected FFT plans Manage the lifetime of oneMKL FFT descriptors Nov 5, 2026
@maleadt

maleadt commented Nov 5, 2026

Copy link
Copy Markdown
Member

Thanks. Rebased and slightly reworked.

@maleadt
maleadt merged commit 2b518d3 into JuliaGPU:main Nov 6, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FFT descriptors are not destroyed (main/2.9.3)

2 participants