mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
Fix #155588: Cycles Metal state allocation OOM issues
This fixes #155588 - an issue introduced by PR #154916. That PR included a 10% safety margin for the working set calculations to account for certain hidden overheads when shrinking the working set size. This allowed for certain scenes like Junkshop to render on the new RAM-constrained Neo machines. However the same safety margin was also used for the state growth heuristic. This PR applies a much more aggressive threshold when growing the state size. It will only double if doing so leaves us with >= 50% of the max recommended work set available. Pull Request: https://projects.blender.org/blender/blender/pulls/155635
This commit is contained in:
parent
d5b08282b9
commit
81a25bf337
1 changed files with 56 additions and 18 deletions
|
|
@ -265,30 +265,68 @@ MetalDeviceQueue::~MetalDeviceQueue()
|
|||
|
||||
int MetalDeviceQueue::num_concurrent_states(const size_t state_size) const
|
||||
{
|
||||
static int result = 0;
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = 4194304;
|
||||
size_t state_count = 4194304;
|
||||
|
||||
/* Increasing the state count doesn't notably benefit M1-family systems. */
|
||||
if (MetalInfo::get_apple_gpu_architecture(metal_device_->mtlDevice) != APPLE_M1) {
|
||||
size_t system_ram = system_physical_ram();
|
||||
size_t allocated_so_far = [metal_device_->mtlDevice currentAllocatedSize];
|
||||
size_t max_recommended_working_set = [metal_device_->mtlDevice recommendedMaxWorkingSetSize];
|
||||
const size_t max_recommended_working_set =
|
||||
[metal_device_->mtlDevice recommendedMaxWorkingSetSize];
|
||||
|
||||
/* Determine whether we can double the state count, and leave enough GPU-available memory
|
||||
* (1/8 the system RAM or 1GB - whichever is largest). Enlarging the state size allows us to
|
||||
* keep dispatch sizes high and minimize work submission overheads. */
|
||||
size_t min_headroom = std::max(system_ram / 8, size_t(1024 * 1024 * 1024));
|
||||
size_t total_state_size = result * state_size;
|
||||
if (max_recommended_working_set - allocated_so_far - total_state_size * 2 >= min_headroom) {
|
||||
result *= 2;
|
||||
metal_printf("Doubling state count to exploit available RAM (new size = %d)", result);
|
||||
/* Only use 90% of available working set for safety. */
|
||||
size_t percent = 90;
|
||||
if (auto str = getenv("CYCLES_METAL_WORKING_SET_PERCENT")) {
|
||||
percent = atoi(str);
|
||||
}
|
||||
|
||||
const size_t max_working_set = (max_recommended_working_set * percent) / 100;
|
||||
size_t max_safe_state_count = 0;
|
||||
|
||||
if (stats_.mem_used < max_working_set) {
|
||||
const size_t headroom = max_working_set - stats_.mem_used;
|
||||
max_safe_state_count = headroom / state_size;
|
||||
}
|
||||
|
||||
/* Require a bare minimum of states to avoid pathological performance. */
|
||||
if (max_safe_state_count >= 65536) {
|
||||
/* If RAM is limited, we can still render with reduced state count. */
|
||||
if (max_safe_state_count < state_count) {
|
||||
metal_printf(
|
||||
"Reducing state count to fit within available RAM. %zu -> %zu (%.1f%% of original "
|
||||
"size)",
|
||||
state_count,
|
||||
max_safe_state_count,
|
||||
double(max_safe_state_count) / double(state_count) * 100.0);
|
||||
state_count = max_safe_state_count;
|
||||
}
|
||||
else {
|
||||
/* Aggressive safety margin: only grow if it leaves us at < 50% max working set
|
||||
* utilisation. */
|
||||
size_t grow_percent = 50;
|
||||
if (auto str = getenv("CYCLES_METAL_GROW_PERCENT")) {
|
||||
grow_percent = atoi(str);
|
||||
}
|
||||
|
||||
max_safe_state_count = (max_safe_state_count * grow_percent) / 100;
|
||||
|
||||
/* Limit to two "doublings" - we see diminishing returns after that. */
|
||||
for (int i = 0; i < 2; i++) {
|
||||
/* Determine whether we can double the state count, and leave enough GPU-available
|
||||
* memory. Enlarging the state size allows us to keep dispatch sizes high and minimize
|
||||
* work submission overheads. */
|
||||
if (max_safe_state_count > state_count * 2) {
|
||||
state_count *= 2;
|
||||
metal_printf("Doubling state count to exploit available RAM (new size = %zu)",
|
||||
state_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
metal_device_->set_error("Out of memory - couldn't allocate integrator state");
|
||||
state_count = 0;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return state_count;
|
||||
}
|
||||
|
||||
int MetalDeviceQueue::num_concurrent_busy_states(const size_t state_size) const
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue