diff --git a/hindsight-api-slim/hindsight_api/engine/local_device.py b/hindsight-api-slim/hindsight_api/engine/local_device.py index ab9704d5a..78bc609b0 100644 --- a/hindsight-api-slim/hindsight_api/engine/local_device.py +++ b/hindsight-api-slim/hindsight_api/engine/local_device.py @@ -163,10 +163,14 @@ def _empty_gpu_cache(device_type: str | None) -> None: def release_local_inference_memory(device_type: str | None = None) -> None: """Release transient heap (and GPU allocator) memory after a local inference batch. - Frees Python objects, returns freed native pages to the OS, and empties the GPU - allocator pool when the model ran on a GPU. Safe to call on every platform and - device; the pieces that don't apply are cheap no-ops. + Returns freed native pages to the OS, and empties the GPU allocator pool when + the model ran on a GPU. Python's normal reference counting already releases + the short-lived CPU inference buffers; a full cyclic-GC scan on every batch is + needlessly expensive on that hot path, so CPU callers leave cyclic GC to its + normal threshold-based schedule. GPU callers retain the existing full cleanup + because allocator release is part of the opt-in accelerator memory policy. """ - gc.collect() + if device_type != "cpu": + gc.collect() _heap_trim() _empty_gpu_cache(device_type) diff --git a/hindsight-api-slim/tests/test_local_device.py b/hindsight-api-slim/tests/test_local_device.py index 874e02069..5ee536c5d 100644 --- a/hindsight-api-slim/tests/test_local_device.py +++ b/hindsight-api-slim/tests/test_local_device.py @@ -161,8 +161,14 @@ class TestReleaseLocalInferenceMemory: assert calls == ["gc", "trim"] assert log == ["cuda"] - def test_release_cpu_skips_empty_cache(self): + def test_release_cpu_skips_gc_and_empty_cache_but_trims_heap(self): + calls = [] log = [] - with patch.dict(sys.modules, {"torch": _fake_torch(empty_cache_log=log)}): + with ( + patch.dict(sys.modules, {"torch": _fake_torch(empty_cache_log=log)}), + patch.object(local_device.gc, "collect", lambda: calls.append("gc")), + patch.object(local_device, "_heap_trim", lambda: calls.append("trim")), + ): release_local_inference_memory("cpu") + assert calls == ["trim"] assert log == []