You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Metal has a limit, the error looks like this (see forum):
/Library/Caches/com.apple.xbs/Sources/Metal/Metal-56.6.1/ToolsLayers/Debug/MTLDebugDevice.mm:89: failed assertion `newBufferWith*:length 0x20000000 must not exceed 256 MB.'
The limit appears to be exposed on MTLDevice as maxBufferLength property.
I really thought we'd be able to hide max buffer sizes behind "out-of-memory" errors. I think that would work if max buffer sizes were in the same ballpark with system memory sizes. With values as low as 256MiB, that might not be so great. Though do we really target iPhone 6? If we're just talking about numbers close to 1GiB, I think it might be okay to continue hiding the limits behind "out-of-memory".
Yeah, I guess it could be fine. I wish we had some sort of guarantee about the values returned from maxBufferLength.
I know it was 1Gb on some macs before, and now it reports 2Gb on the 4 year old MBP.
256MB is very limiting and would block some of my plans to port progressive rendering to WebGPU and also block our future plans on processing and rendering large point clouds directly in browsers using WebGPU. Essentially, it would mean I won't be able to use WebGPU for our recently started 2 year research project. Is it not possible to just report the max buffer length and/or throw an exception if one tries to allocate more than that? My systems are able to handle 2GB buffers, and for the paper above I'd need to generate 8 x 2GB buffers and bind all of them to a single compute shader. With 256 I'd need 64x256MB buffers which, as far as I've seen, isn't even possible to bind to a shader, and would also make it potentially expensive to chose the right one out of 64. With 8, I can just do some ifs.
Per #1069, the iPhone 6 is out of scope, as it is A8 and its last major OS release was iOS 12.
The next iPhone is probably in scope: the iPhone 6S is A9 and currently has iOS 14.
256MB is very limiting and would block some of my plans to port progressive rendering to WebGPU and also block our future plans on processing and rendering large point clouds directly in browsers using WebGPU. Essentially, it would mean I won't be able to use WebGPU for our recently started 2 year research project. Is it not possible to just report the max buffer length and/or throw an exception if one tries to allocate more than that?
If there are limits that low then I think we are likely to expose the limit. But if the limits are in the ballpark of 1GiB then I'd like to try to get away without an explicit limit.
I guess one result of this should be that it's acceptable for an implementation to fail to create a single buffer of size X but able to create 2 buffers of size X / 2 (or even X - 1). This is already true in the general case because of fragmentation.
Apple A9 (the oldest iPhone that supports the newest iOS) reports half a gigabyte for this value. Relatedly, apps can't actually allocate much more than half a gigabyte (even unrelated to the GPU) without hitting the jetsam limit.
It's very rare for Metal's allocations-which-are-smaller-than-maxBufferLength to actually fail - they will only fail when you've exhausted all of the GPU's virtual (!!) memory. So, if you go looking for allocation failures, you'll almost never see them.
This field is intended to be a much smaller than the amount of virtual memory on the device, and is intended to be closer to be what is actually usable in any specific draw/dispatch call.
The design makes sense because it's totally reasonable for an app to allocate way more than can physically fit on the GPU at a time, throughout the entire lifetime of the app, as long as each individual allocation fits and the app doesn't use all the allocations at the same time. The VM system will page resources to/from the GPU as necessary.
Thinking about this more, I think the relevant question is what the failure mode for buffer allocation looks like in WebGPU. The description in the spec of createBuffer() doesn't indicate what should happen here.
If a buffer allocation fails (for whatever reason, maybe the size was > maxBufferLength on Metal or maybe the device is out of local memory and you're running on Vulkan which doesn't do automatic eviction (and the browser hasn't implemented their own automatic eviction)), how should a WebGPU application detect this and react to it?
Are browsers going to be required to post a GPUOutOfMemoryError to the current scope for every allocation failure?
If the answer is "yes," and applications are expected to be listening for OOM errors during resource creation to detect if their allocation fails, then that is a bit disingenuous on Metal. On Metal, the allocation can fail even if the device isn't actually out of memory. E.g. my Radeon Pro Vega 56 has 8GB of memory but maxBufferLength is 3.75GB. If they want to allocate 4GB on this card, that's totally fine, they just have to use 2 buffers to do it. If they try to allocate one 4GB buffer, and it fails, it isn't because the device is out of memory - the device has plenty of memory available. The reaction the app should have in this situation isn't "use less memory" but instead should be "use the same amount of memory but broken up into more allocations."
On the other hand, if the answer is "no," then how can an application deterministically know whether or not their buffer failed to allocate? Surely they shouldn't have to submit a dummy command buffer that just uses a single resource just to determine if the resource was allocated successfully.
(Aside: Metal doesn't have buffer binding regions, but instead just has pointers. When encoding a pass, you don't attach a region of a buffer to a shader, but just attach a buffer and an offset. This is a surfaced in the shading language as just a pointer, so the pointer has to be able to index all the way to the end of the resource. So, limiting the "binding size" is meaningful in other APIs, but isn't meaningful in Metal. Instead, Metal has to expose this concept in the form of limiting the size of the resource itself.)
So, we probably should either have a limit for this value (ugh fingerprinting booo) or we should have a new GPUAllocationTooBigError sibling to GPUOutOfMemoryError.
I guess another option would be to have a single GPUBuffer be backed by multiple MTLBuffers. I think we shouldn't do this (now), however, because:
The group already rejected the general idea of having a GPUBuffer corresponding to multiple platform buffers in our discussions about uploads/downloads. (The idea back then was to have the implementation automatically copy between one backing storageModeShared buffer and another backing storageModePrivate buffer under the hood if it thought doing so would improve performance - doing so would allow us to relax the "you can't have both STORAGE and MAP_WRITE usages on the same buffer" requirement.)
It would naturally lead to divergence and might actually lead to catastrophic performance in the common case. Consider this line of WGSL:
but it's actually worse than this, because indexing into a resource array needs to have a dynamically uniform index, so this would actually end up being executed like this on many devices:
And this kind of blowup seems unacceptable from such an innocuous line of source code - people read/write to buffers all the time.
I don't want to close the door on this forever, though - if an application really needs a buffer that's > maxBufferLength, they can emulate it themselves (accepting the potential performance problems), and if people actually do end up emulating it themselves, that would be a reasonable signal that we should make their lives easier and just do it for them with an extension. But it shouldn't be in the unextended core API.
I guess another option would be to have a single GPUBuffer be backed by multiple MTLBuffers. I think we shouldn't do this (now)
+1, it would also have massive consequences for the binding model because all of a sudden each storage buffer bindings takes more than 1 slot so we would quickly run out of slots on Metal.
The reaction the app should have in this situation isn't "use less memory" but instead should be "use the same amount of memory but broken up into more allocations."
I agree that GPUOutOfMemoryError doesn't faithfully represent Metal's behavior (and other drivers too, I'd expect some Vulkan driver to fail to allocate more than 4GB even if there is enough memory because of pointer width), and think GPUAllocationTooBigError would work. However how many application do we expect will be able to react in a useful way to this signal to switch to a code path that uses multiple buffers? It seems that it would be a massive change to their shaders for a situation that would happen extremely rarely?
For implementers' information: the maxMemoryAllocationSize on Vulkan, IIUC, requires Vulkan 1.1 or VK_KHR_maintenance3. On remaining Vulkan platforms, we'll have to make up a number, like 2GiB or 4GiB perhaps. Here's the result of running gpuinfo-vulkan-query with that requirement, showing the devices where we'd have to conjure a number.
Requirement "Vulkan 1.1 or "VK_KHR_maintenance3" for maxBufferSize" loses 44 (and partially loses 199) further deviceNames:
In ALL reports (44 deviceNames):
x AMD FirePro M4150: 1 (2289)
x AMD FirePro W4100: 1 (3675)
x AMD Radeon (TM) 520: 1 (3902)
x AMD Radeon (TM) 535DX: 1 (2914)
x AMD Radeon (TM) R5 M335: 1 (2068)
x AMD Radeon (TM) R5 M430: 1 (4406)
x AMD Radeon (TM) R7 M360: 1 (2355)
x AMD Radeon (TM) R7 M460: 1 (12637)
x AMD Radeon (TM) RX 570: 1 (3934)
x AMD Radeon HD 7770 GHz Edition: 1 (8041)
x AMD Radeon HD 8400 / R3 Series: 1 (6936)
x AMD Radeon HD 8500M Series: 1 (2433)
x AMD Radeon HD 8600/8700M: 2 (1518 2139)
x AMD Radeon R5 M445 Series: 1 (2808)
x AMD Radeon R7 350 Series: 1 (2333)
x AMD Radeon R7 Graphics + R7 200 Dual Graphics: 1 (1544)
x AMD Radeon R7 M260 Series: 1 (2290)
x AMD Radeon(TM) R6 Graphics: 2 (1866 3089)
x AMD Radeon(TM) R7 350X: 15 (1875 2974 3513 3763 3824 3918 3975 3985 4108 4189 4283 4291 4533 4641 4868)
x ASUS R7 265 Series: 1 (2112)
x ASUS R9 280 Series: 2 (1664 2144)
x GeForce 840A: 1 (9603)
x GeForce 845M: 1 (2786)
x GeForce GPU: 2 (2586 4538)
x GeForce GT 640M: 3 (1624 1685 1747)
x GeForce GT 745M: 1 (3261)
x GeForce GTX 675MX: 1 (2496)
x GeForce GTX 690: 1 (2901)
x GeForce GTX 780M: 2 (1469 12209)
x Intel(R) HD Graphics 5300 (Broadwell GT2): 1 (1497)
x Intel(R) HD Graphics P530: 1 (6154)
x Intel(R) Iris Graphics 540 (Skylake GT3e): 4 (1545 1673 2231 2332)
x NVIDIA Tegra K1: 4 (2235 2665 5341 5506)
x NVIDIA Tegra X1: 7 (1550 1977 2115 2952 5451 9235 10177)
x Quadro GP100: 1 (2765)
x Quadro K2000M: 4 (1599 3386 3628 3718)
x Quadro K420: 1 (3949)
x Quadro K5100M: 1 (2506)
x Quadro M6000: 2 (1962 1963)
x Quadro P6000: 2 (2668 7106)
x Radeon Pro 575: 1 (3371)
x Radeon Pro WX 5100: 1 (6510)
x Radeon(TM) 520: 1 (3353)
x Radeon(TM) RX580: 1 (3388)
Activity
kainino0x commentedon Jan 26, 2021
I really thought we'd be able to hide max buffer sizes behind
"out-of-memory"errors. I think that would work if max buffer sizes were in the same ballpark with system memory sizes. With values as low as 256MiB, that might not be so great. Though do we really target iPhone 6? If we're just talking about numbers close to 1GiB, I think it might be okay to continue hiding the limits behind"out-of-memory".kvark commentedon Jan 26, 2021
Yeah, I guess it could be fine. I wish we had some sort of guarantee about the values returned from
maxBufferLength.I know it was 1Gb on some macs before, and now it reports 2Gb on the 4 year old MBP.
potree commentedon Jan 26, 2021
256MB is very limiting and would block some of my plans to port progressive rendering to WebGPU and also block our future plans on processing and rendering large point clouds directly in browsers using WebGPU. Essentially, it would mean I won't be able to use WebGPU for our recently started 2 year research project. Is it not possible to just report the max buffer length and/or throw an exception if one tries to allocate more than that? My systems are able to handle 2GB buffers, and for the paper above I'd need to generate 8 x 2GB buffers and bind all of them to a single compute shader. With 256 I'd need 64x256MB buffers which, as far as I've seen, isn't even possible to bind to a shader, and would also make it potentially expensive to chose the right one out of 64. With 8, I can just do some ifs.
kainino0x commentedon Jan 26, 2021
Per #1069, the iPhone 6 is out of scope, as it is A8 and its last major OS release was iOS 12.
The next iPhone is probably in scope: the iPhone 6S is A9 and currently has iOS 14.
kainino0x commentedon Jan 27, 2021
If there are limits that low then I think we are likely to expose the limit. But if the limits are in the ballpark of 1GiB then I'd like to try to get away without an explicit limit.
litherum commentedon Dec 4, 2021
This comes from https://developer.apple.com/documentation/metal/mtldevice/2966563-maxbufferlength?language=objc.
litherum commentedon Dec 4, 2021
I guess one result of this should be that it's acceptable for an implementation to fail to create a single buffer of size X but able to create 2 buffers of size X / 2 (or even X - 1). This is already true in the general case because of fragmentation.
litherum commentedon Dec 4, 2021
Apple A9 (the oldest iPhone that supports the newest iOS) reports half a gigabyte for this value. Relatedly, apps can't actually allocate much more than half a gigabyte (even unrelated to the GPU) without hitting the jetsam limit.
litherum commentedon Dec 4, 2021
It's very rare for Metal's allocations-which-are-smaller-than-maxBufferLength to actually fail - they will only fail when you've exhausted all of the GPU's virtual (!!) memory. So, if you go looking for allocation failures, you'll almost never see them.
This field is intended to be a much smaller than the amount of virtual memory on the device, and is intended to be closer to be what is actually usable in any specific draw/dispatch call.
The design makes sense because it's totally reasonable for an app to allocate way more than can physically fit on the GPU at a time, throughout the entire lifetime of the app, as long as each individual allocation fits and the app doesn't use all the allocations at the same time. The VM system will page resources to/from the GPU as necessary.
litherum commentedon Dec 5, 2021
Thinking about this more, I think the relevant question is what the failure mode for buffer allocation looks like in WebGPU. The description in the spec of
createBuffer()doesn't indicate what should happen here.If a buffer allocation fails (for whatever reason, maybe the size was >
maxBufferLengthon Metal or maybe the device is out of local memory and you're running on Vulkan which doesn't do automatic eviction (and the browser hasn't implemented their own automatic eviction)), how should a WebGPU application detect this and react to it?Are browsers going to be required to post a
GPUOutOfMemoryErrorto the current scope for every allocation failure?If the answer is "yes," and applications are expected to be listening for OOM errors during resource creation to detect if their allocation fails, then that is a bit disingenuous on Metal. On Metal, the allocation can fail even if the device isn't actually out of memory. E.g. my Radeon Pro Vega 56 has 8GB of memory but
maxBufferLengthis 3.75GB. If they want to allocate 4GB on this card, that's totally fine, they just have to use 2 buffers to do it. If they try to allocate one 4GB buffer, and it fails, it isn't because the device is out of memory - the device has plenty of memory available. The reaction the app should have in this situation isn't "use less memory" but instead should be "use the same amount of memory but broken up into more allocations."On the other hand, if the answer is "no," then how can an application deterministically know whether or not their buffer failed to allocate? Surely they shouldn't have to submit a dummy command buffer that just uses a single resource just to determine if the resource was allocated successfully.
(Aside: Metal doesn't have buffer binding regions, but instead just has pointers. When encoding a pass, you don't attach a region of a buffer to a shader, but just attach a buffer and an offset. This is a surfaced in the shading language as just a pointer, so the pointer has to be able to index all the way to the end of the resource. So, limiting the "binding size" is meaningful in other APIs, but isn't meaningful in Metal. Instead, Metal has to expose this concept in the form of limiting the size of the resource itself.)
So, we probably should either have a limit for this value (ugh fingerprinting booo) or we should have a new
GPUAllocationTooBigErrorsibling toGPUOutOfMemoryError.litherum commentedon Dec 5, 2021
I guess another option would be to have a single
GPUBufferbe backed by multipleMTLBuffers. I think we shouldn't do this (now), however, because:GPUBuffercorresponding to multiple platform buffers in our discussions about uploads/downloads. (The idea back then was to have the implementation automatically copy between one backingstorageModeSharedbuffer and another backingstorageModePrivatebuffer under the hood if it thought doing so would improve performance - doing so would allow us to relax the "you can't have both STORAGE and MAP_WRITE usages on the same buffer" requirement.)would be translated to this:
but it's actually worse than this, because indexing into a resource array needs to have a dynamically uniform index, so this would actually end up being executed like this on many devices:
And this kind of blowup seems unacceptable from such an innocuous line of source code - people read/write to buffers all the time.
I don't want to close the door on this forever, though - if an application really needs a buffer that's >
maxBufferLength, they can emulate it themselves (accepting the potential performance problems), and if people actually do end up emulating it themselves, that would be a reasonable signal that we should make their lives easier and just do it for them with an extension. But it shouldn't be in the unextended core API.Kangz commentedon Dec 6, 2021
+1, it would also have massive consequences for the binding model because all of a sudden each storage buffer bindings takes more than 1 slot so we would quickly run out of slots on Metal.
I agree that
GPUOutOfMemoryErrordoesn't faithfully represent Metal's behavior (and other drivers too, I'd expect some Vulkan driver to fail to allocate more than 4GB even if there is enough memory because of pointer width), and thinkGPUAllocationTooBigErrorwould work. However how many application do we expect will be able to react in a useful way to this signal to switch to a code path that uses multiple buffers? It seems that it would be a massive change to their shaders for a situation that would happen extremely rarely?19 remaining items
kainino0x commentedon Aug 17, 2022
back to candidate until we know what the number is going to be
litherum commentedon Aug 24, 2022
The number should be 256MB.
Add maxBufferSize limit
Add maxBufferSize limit
Add maxBufferSize limit
kainino0x commentedon Aug 24, 2022
swapping this issue with the PR in the tacit resolution queue
Add maxBufferSize limit (#3366)
[wgsl] reserve binding_array. (gpuweb#1371)
kainino0x commentedon Sep 27, 2022
For implementers' information: the maxMemoryAllocationSize on Vulkan, IIUC, requires Vulkan 1.1 or VK_KHR_maintenance3. On remaining Vulkan platforms, we'll have to make up a number, like 2GiB or 4GiB perhaps. Here's the result of running gpuinfo-vulkan-query with that requirement, showing the devices where we'd have to conjure a number.
In SOME reports (199 deviceNames):wangkuiyi commentedon Mar 10, 2023
When I am running a Metal program in the iOS Simulator (iPhone 14 Pro), I got a 256MB limit.