UE 5.8.2 Vulkan memory leak, consumed 114GB RAM and killed by OOM

Summary

Hi!

We found that on Linux Vulkan, UE 5.8.2 leaks RAM on every parallel render pass.

It was confirmed on 2 separate machines with different GPUs. Occurs in both 5.8.0 and 5.8.2.

RHIBeginParallelRenderPass() does new FVulkanDynamicRenderingInfo(). RHIEndParallelRenderPass() deletes CurrentParallelRenderPassInfo and leaves DynamicRenderingInfo behind. FVulkanParallelRenderPassInfo has no destructor. File: Engine/Source/Runtime/VulkanRHI/Private/VulkanContext.cpp.

We saw it on two Linux machines running a packaged offscreen project, with several SceneCaptures and hardware ray-traced LiDAR:

  • machine1: process grew to ~114 GB and the kernel OOM-killed it.
  • machine2: anonymous RSS grew ~1.5 GB/h. Insights + addr2line on matching debug symbols pointed at FVulkanCommandListContext::RHIBeginParallelRenderPass - operator new.

Workaround:
we tested r.Vulkan.AllowDynamicRendering=0, which reduced memory leak from about 1.5GB/h to 0.27 GB/h

What Type of Bug are you experiencing?

Simulation

Steps to Reproduce

  1. Package a Linux Vulkan build of a project that uses parallel render passes (SceneCapture / hardware ray tracing is enough).
  2. Run it offscreen for 20+ minutes (-RenderOffscreen).
  3. Watch process RSS / anonymous memory.

Optional: Insights memory trace, then addr2line on RHIBeginParallelRenderPass.

Optional: r.Vulkan.AllowDynamicRendering=0 at startup and repeat.

Expected Result

No memory leak

Observed Result

RSS climbs about 1.5 GiB/h (on 2 separate machines). Same binary on sw-perc-1 grew to ~114 GB and was OOM-killed. Trace shows RHIBeginParallelRenderPass → operator new. r.Vulkan.AllowDynamicRendering=0 drops growth to ~0.27 GiB/h. Still present in 5.8.0 and 5.8.2 source.

Affects Versions

5.8

Platform(s)

Linux

Additional Notes

Proposed fix:

From: Filip Kubicz <filip@kubicz.engineer>
Date: Fri, 11 Sep 2026 12:37:00 +0200
Subject: [PATCH] VulkanRHI: free FVulkanDynamicRenderingInfo on parallel pass end
RHIBeginParallelRenderPass heap-allocates DynamicRenderingInfo.
RHIEndParallelRenderPass only deletes CurrentParallelRenderPassInfo,
so the inner object leaks every pass.
---
 Engine/Source/Runtime/VulkanRHI/Private/VulkanContext.h | 5 +++++
 1 file changed, 5 insertions(+)
diff --git a/Engine/Source/Runtime/VulkanRHI/Private/VulkanContext.h b/Engine/Source/Runtime/VulkanRHI/Private/VulkanContext.h
index 0000000..1111111 100644
--- a/Engine/Source/Runtime/VulkanRHI/Private/VulkanContext.h
+++ b/Engine/Source/Runtime/VulkanRHI/Private/VulkanContext.h
@@ -49,6 +49,11 @@
 struct FVulkanParallelRenderPassInfo
 {
 	VkRenderPass RenderPassHandle = VK_NULL_HANDLE;
 	FVulkanDynamicRenderingInfo* DynamicRenderingInfo = nullptr;
 	TArray<FVulkanPayload*> SecondaryPayloads;
 	std::atomic<int32> NumParallelContexts = 0;
+
+	~FVulkanParallelRenderPassInfo()
+	{
+		delete DynamicRenderingInfo;
+	}
 };
3 Likes

Fix proposed above confirmed to fix the memory leak. Please package it into Unreal Engine release.

1 Like

Independent reproduction, different workload: no SceneCapture, no ray tracing, empty level

Confirming this on 5.8.2 CL 56702186, Linux (Ubuntu 22.04, kernel 6.8), Vulkan, NVIDIA 580.x, in a packaged Development build running windowed at 1280x720 on a near-empty template map. No SceneCaptures, no hardware ray tracing, no LiDAR. Worth noting because the repro steps above make those look relevant - they aren’t. This fires on any parallel raster pass.

Measured with RssAnon from /proc//status rather than VmRSS, so page cache and mapped pages are excluded. Sampled every 10 s over 12 minutes, steady state after a 60 s warm-up:

  • Default: RssAnon +44.5 MB/min, LLM tag SceneRender +36.4 MB/min
  • With r.Vulkan.AllowDynamicRendering=0: RssAnon +1.6 MB/min, LLM tag SceneRender +0.0 MB/min

RssFile and thread count are flat throughout, so it is purely anonymous heap. A /proc//smaps diff shows the growth arriving as a stream of 8256 kB MallocBinned2 pool chunks (FMallocBinned2::MallocExternalSmall → FPooledVirtualMemoryAllocator::CreatePool), i.e. a steady drip of small allocations rather than anything large.

The leak is per frame, not per unit time. Capping with t.MaxFPS 10 dropped SceneRender growth to +3.9 MB/min, which works out to roughly 6.5 KB retained per rendered frame. Measured frame rate at default was 102.6 fps, and 36.4 MB/min at that rate gives 5.9 KB/frame - the two agree within 10%. That is consistent with ~1 KB per leaked FVulkanDynamicRenderingInfo times the number of parallel render passes per frame (about 6 on this map).

Two things that may help triage:

Disabling dynamic rendering eliminated it completely here - SceneRender went from +36.4 to +0.0 MB/min - rather than the ~82% reduction reported above. The residual +1.6 MB/min in my case is LLM tag RHIMisc and is present identically with the workaround on or off, so it looks like a separate and much smaller issue.

The growth is attributed by the low level memory tracker to ELLMTag::SceneRender, not RHIMisc. That surprised me, since RHIBeginParallelRenderPass is reached through an EnqueueLambda at command list execution rather than inside the render thread’s SceneRender scope, but the A/B is unambiguous: turning the CVar off takes that tag to exactly zero.

Also ruled out by A/B on the same build, each identical to control: r.PSOPrecaching=0 gave +36.4 MB/min, r.Shadow.Virtual.Enable=0 gave +36.6 MB/min, and Lumen’s own LLM tag never moved off 2.1 MB for the whole run.

Confirming the mechanism is present in the stock 5.8.2 binary release as shipped: FVulkanParallelRenderPassInfo in VulkanContext.h declares no destructor, RHIBeginParallelRenderPass allocates DynamicRenderingInfo at VulkanContext.cpp:585 under HasKHRDynamicRendering, and RHIEndParallelRenderPass ends at VulkanContext.cpp:667 with delete CurrentParallelRenderPassInfo only.

One note for anyone else applying the workaround: r.Vulkan.AllowDynamicRendering is ECVF_ReadOnly, so it has to come from an ini or -dpcvars on the command line. Setting it via -ExecCmds or the in-game console is too late to have any effect. A quick way to confirm it took: with the CVar at 0 the base VK_KHR_dynamic_rendering extension is still loaded (Maintenance5 depends on it) but VK_KHR_dynamic_rendering_local_read and VK_EXT_dynamic_rendering_unused_attachments drop out of the enabled extension list in the log.

Thank you, I’ll get this to the correct team for review.

UE-396792 has been ‘Closed’ as a duplicate of an existing known issue. Origin Issue: UE-392081

UE-392081’s status has changed to ‘Ready for QA’. A member of the QA department is investigating the issue.

I can independently confirm this issue on UE 5.8 with a different project/workload.

I originally investigated a long-running packaged Unreal application where performance gradually degraded over time. Using Unreal Memory Insights, I traced continuous SceneRender memory growth to repeated allocations through the Vulkan parallel render-pass path, including:

FVulkanCommandListContext::RHIBeginParallelRenderPass
→ FVulkanDynamicRenderingInfo
→ repeated ~900-byte allocations

Reviewing the UE 5.8 VulkanRHI source also led me to the same ownership/lifetime path described in this report, where DynamicRenderingInfo is allocated separately inside FVulkanParallelRenderPassInfo.

I then performed an A/B test using the same packaged application:

r.Vulkan.RHIThread=2
→ SceneRender growth approximately 21 MB/min

r.Vulkan.RHIThread=1
→ SceneRender growth approximately 0 MB/min

As a longer confirmation with r.Vulkan.RHIThread=1, SceneRender remained effectively flat during a 5-minute run:

12.85 MB → 12.83 MB

This was repeatable and strongly suggests that the retained allocations are associated with the Vulkan parallel/RHI execution path rather than application-level state accumulation.

This appears consistent with the FVulkanDynamicRenderingInfo lifetime issue already identified in this thread and may provide an additional reproduction/configuration for UE-392081.

I can provide Memory Insights captures, allocation-stack screenshots, hardware/OS details, and additional longer-duration A/B results if useful for QA.