@@ -97,6 +97,37 @@ function soft_memory_limit()
9797 SOFT_MEMORY_LIMIT[] = soft_limit
9898end
9999
100+
101+ # # allocation statistics
102+
103+ mutable struct AllocStats
104+ Base. @atomic alloc_count:: Int
105+ Base. @atomic alloc_bytes:: Int
106+
107+ Base. @atomic free_count:: Int
108+ Base. @atomic free_bytes:: Int
109+
110+ Base. @atomic total_time:: Float64
111+ end
112+
113+ AllocStats () = AllocStats (0 , 0 , 0 , 0 , 0.0 )
114+
115+ Base. copy (s:: AllocStats ) =
116+ AllocStats (s. alloc_count, s. alloc_bytes,
117+ s. free_count, s. free_bytes, s. total_time)
118+
119+ Base.:(- )(a:: AllocStats , b:: AllocStats ) = (;
120+ alloc_count = a. alloc_count - b. alloc_count,
121+ alloc_bytes = a. alloc_bytes - b. alloc_bytes,
122+ free_count = a. free_count - b. free_count,
123+ free_bytes = a. free_bytes - b. free_bytes,
124+ total_time = a. total_time - b. total_time)
125+
126+ const alloc_stats = AllocStats ()
127+
128+
129+ # # memory accounting
130+
100131mutable struct MemoryStats
101132 # Maximum size of the heap.
102133 # Estimated during `maybe_collect` stage.
@@ -134,7 +165,7 @@ function account!(stats::MemoryStats, bytes::Integer)
134165 Base. @atomic stats. live += bytes
135166end
136167
137- const EAGER_GC:: Ref{Bool} = Ref {Bool} (@load_preference (" eager_gc" , false ))
168+ const EAGER_GC:: Ref{Bool} = Ref {Bool} (@load_preference (" eager_gc" , true ))
138169
139170function eager_gc! (flag:: Bool )
140171 global EAGER_GC[] = flag
@@ -210,6 +241,169 @@ function maybe_collect(; blocking::Bool = false)
210241 return
211242end
212243
244+
245+ # # pool activity tracking
246+
247+ const POOL_STATUS = AMDGPU. LockedObject (Dict {Int, Ref{Bool}} ())
248+
249+ function pool_mark (dev:: HIPDevice )
250+ ps = POOL_STATUS. payload
251+ did = HIP. device_id (dev)
252+ status = get (ps, did, nothing )
253+ status === nothing && return nothing
254+ return status[]
255+ end
256+
257+ function pool_mark! (dev:: HIPDevice , val:: Bool )
258+ ps = POOL_STATUS. payload
259+ did = HIP. device_id (dev)
260+ box = get (ps, did, nothing )
261+ if box === nothing
262+ Base. @lock POOL_STATUS. lock begin
263+ box = get! (ps, did) do
264+ Ref {Bool} (val)
265+ end
266+ end
267+ end
268+ box[] = val
269+ return
270+ end
271+
272+
273+ # # reclaim hooks
274+
275+ """
276+ reclaim_hooks
277+
278+ A list of callables that are invoked when memory needs to be reclaimed.
279+ Downstream packages can push functions into this list to free cached resources
280+ (e.g., workspace buffers, FFT plans, etc.) when GPU memory is scarce.
281+ """
282+ const reclaim_hooks = Any[]
283+
284+
285+ # # pool cleanup
286+
287+ const _pool_cleanup_task = Ref {Task} ()
288+
289+ function pool_cleanup ()
290+ idle_counters = Dict {Int, Int} ()
291+ while true
292+ try
293+ sleep (60 )
294+ catch ex
295+ if ex isa EOFError
296+ break
297+ else
298+ rethrow ()
299+ end
300+ end
301+
302+ for dev in HIP. devices ()
303+ did = HIP. device_id (dev)
304+ status = pool_mark (dev)
305+ status === nothing && continue
306+
307+ if status
308+ idle_counters[did] = 0
309+ else
310+ idle_counters[did] = get (idle_counters, did, 0 ) + 1
311+ end
312+ pool_mark! (dev, false )
313+
314+ if get (idle_counters, did, 0 ) >= 5
315+ HIP. device! (dev) do
316+ reclaim ()
317+ end
318+ end
319+ end
320+ end
321+ end
322+
323+
324+ # # reclaim
325+
326+ """
327+ reclaim([sz=typemax(Int)])
328+
329+ Reclaims `sz` bytes of cached memory. Use this to free GPU memory before
330+ calling into functionality that does not use the memory pool. Returns the
331+ number of bytes actually reclaimed.
332+ """
333+ function reclaim (sz:: Int = typemax (Int))
334+ dev = AMDGPU. device ()
335+ for hook in reclaim_hooks
336+ hook ()
337+ end
338+ HIP. device_synchronize ()
339+ pool = Mem. pool_create (dev)
340+ before = HIP. reserved_memory (pool)
341+ HIP. trim (pool)
342+ after = HIP. reserved_memory (pool)
343+ return Int (before - after)
344+ end
345+
346+
347+ # # pool status & queries
348+
349+ """
350+ used_memory()
351+
352+ Returns the amount of memory from the HIP memory pool that is currently
353+ in use by the application.
354+ """
355+ function used_memory ()
356+ pool = Mem. pool_create (AMDGPU. device ())
357+ Int (HIP. used_memory (pool))
358+ end
359+
360+ """
361+ cached_memory()
362+
363+ Returns the amount of backing memory currently allocated (reserved) for the
364+ HIP memory pool.
365+ """
366+ function cached_memory ()
367+ pool = Mem. pool_create (AMDGPU. device ())
368+ Int (HIP. reserved_memory (pool))
369+ end
370+
371+ """
372+ pool_status([io=stdout])
373+
374+ Report to `io` on the memory status of the current GPU and the active memory pool.
375+ """
376+ function pool_status (io:: IO = stdout )
377+ free_bytes, total_bytes = info ()
378+ used_bytes = total_bytes - free_bytes
379+ used_ratio = used_bytes / total_bytes
380+ @printf (io, " Effective GPU memory usage: %.2f%% (%s/%s)\n " ,
381+ 100 * used_ratio, Base. format_bytes (used_bytes),
382+ Base. format_bytes (total_bytes))
383+
384+ pool = Mem. pool_create (AMDGPU. device ())
385+ pool_used = HIP. used_memory (pool)
386+ pool_reserved = HIP. reserved_memory (pool)
387+ @printf (io, " Memory pool usage: %s (%s reserved)\n " ,
388+ Base. format_bytes (pool_used),
389+ Base. format_bytes (pool_reserved))
390+
391+ hard_limit = hard_memory_limit ()
392+ soft_limit = soft_memory_limit ()
393+ if hard_limit != typemax (UInt64) || soft_limit != typemax (UInt64)
394+ print (io, " Memory limit: " )
395+ parts = String[]
396+ if soft_limit != typemax (UInt64)
397+ push! (parts, " soft = $(Base. format_bytes (soft_limit)) " )
398+ end
399+ if hard_limit != typemax (UInt64)
400+ push! (parts, " hard = $(Base. format_bytes (hard_limit)) " )
401+ end
402+ println (io, join (parts, " , " ))
403+ end
404+ end
405+
406+
213407# TODO handle stream capturing when we support HIP graphs
214408mutable struct Managed{M}
215409 const mem:: M
@@ -275,16 +469,41 @@ function Base.convert(::Type{Mem.AbstractAMDBuffer}, managed::Managed{M}) where
275469end
276470
277471function pool_alloc (:: Type{B} , bytesize) where B
278- s = AMDGPU. stream ()
279- # @info "[pool_alloc] $(Base.format_bytes(bytesize))"
280- # display(stacktrace()); println()
281- # println()
282- # println()
283- Managed (B (bytesize; stream= s); stream= s)
472+ maybe_collect ()
473+ time = Base. @elapsed begin
474+ s = AMDGPU. stream ()
475+ managed = Managed (B (bytesize; stream= s); stream= s)
476+ end
477+
478+ Base. @atomic alloc_stats. alloc_count += 1
479+ Base. @atomic alloc_stats. alloc_bytes += bytesize
480+ Base. @atomic alloc_stats. total_time += time
481+
482+ pool_mark! (AMDGPU. device (), true )
483+
484+ if isinteractive () && ! isassigned (_pool_cleanup_task)
485+ _pool_cleanup_task[] = errormonitor (Threads. @spawn pool_cleanup ())
486+ end
487+
488+ return managed
284489end
285490
286491function pool_free (managed:: Managed{M} ) where M
287- _pool_free (managed. mem, managed. stream)
492+ sz = Int (sizeof (managed. mem))
493+ sz == 0 && return
494+
495+ try
496+ time = Base. @elapsed _pool_free (managed. mem, managed. stream)
497+ Base. @atomic alloc_stats. free_count += 1
498+ Base. @atomic alloc_stats. free_bytes += sz
499+ Base. @atomic alloc_stats. total_time += time
500+ catch ex
501+ Base. showerror_nostdio (ex,
502+ " WARNING: Error while freeing $(Base. format_bytes (sz)) of GPU memory" )
503+ Base. show_backtrace (Core. stdout , catch_backtrace ())
504+ Core. println ()
505+ end
506+ return
288507end
289508
290509function _pool_free (buf, stream:: HIPStream )
0 commit comments