Repository navigation
Let a Core ML model read and write bound pixel buffers in place (#24261) - #24261
sivapatibandla wants to merge 2 commits into
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24261
Note: Links to docs will display an error until the docs builds have been completed. ✅ You can merge normally! (1 Unrelated Failure)As of commit 32837f6 with merge base 337aa81 ( BROKEN TRUNK - The following job failed but were present on the merge base:👉 Rebase onto the `viable/strict` branch to avoid these failures
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@sivapatibandla has exported this pull request. If you are a Meta employee, you can view the originating Diff in D127640931. |
This PR needs a
|
…rch#24261) Summary: Pull Request resolved: pytorch#24261 Lets a Core ML model take inputs and outputs in IOSurface-backed fp16 pixel buffers bound to it, so Core ML reads and writes them in place instead of copying them into and out of its own buffers. The saving grows with tensor size, so it matters most for large fp16 tensors such as video frames. `-[ETCoreMLModel bindPixelBuffer:error:]` binds a `kCVPixelFormatType_OneComponent16Half` IOSurface-backed buffer to one model instance, and refuses it on OSes older than macOS 12 / iOS 16, which lack the pixel-buffer `MLMultiArray` API, or if the buffer can't be locked or has no base address. There is no global registry, and two models can bind the same buffer, e.g. to chain one model's output into another's input. Callers don't have the delegate's headers, so the next diff lets them bind buffers through load-time options. Bound buffers are matched by base address: when an input's or output's data pointer is a bound buffer's base address, `prepareArgs` hands Core ML an array wrapping that buffer (`-[MLMultiArray initWithPixelBuffer:shape:]`) as the input or the output backing. The model's input or output has to be fp16 too: against a feature of another type, a bound buffer fails the arg instead of reaching Core ML, while unbound args still convert as before. The model keeps one array per bound buffer, made on first use and remade only when the arg's shape changes, since wrapping costs Core ML a setup per array. After the prediction, the output step recognizes the in-place write by Core ML returning that same array (it hands back the output backing itself) and doesn't touch the buffer. When Core ML doesn't write a backing in place, the fallback copy writes through the pixel buffer's row stride (rows are padded to 64 bytes, e.g. 720 -> 768 bytes for 360 fp16 values) instead of treating the buffer as dense, and a buffer whose shape or dtype doesn't match the arg fails the prediction. Only models with bound buffers see any of this: with nothing bound, the per-arg lookup walks an empty list and returns, and the copy paths are unchanged. This diff was authored with Claude Code. Reviewed By: metascroy Differential Revision: D127640931
f629990 to
d25c875
Compare
…rch#24261) Summary: Lets a Core ML model take inputs and outputs in IOSurface-backed fp16 pixel buffers bound to it, so Core ML reads and writes them in place instead of copying them into and out of its own buffers. The saving grows with tensor size, so it matters most for large fp16 tensors such as video frames. `-[ETCoreMLModel bindPixelBuffer:error:]` binds a `kCVPixelFormatType_OneComponent16Half` IOSurface-backed buffer to one model instance, and refuses it on OSes older than macOS 12 / iOS 16, which lack the pixel-buffer `MLMultiArray` API, or if the buffer can't be locked or has no base address. There is no global registry, and two models can bind the same buffer, e.g. to chain one model's output into another's input. Callers don't have the delegate's headers, so the next diff lets them bind buffers through load-time options. Bound buffers are matched by base address: when an input's or output's data pointer is a bound buffer's base address, `prepareArgs` hands Core ML an array wrapping that buffer (`-[MLMultiArray initWithPixelBuffer:shape:]`) as the input or the output backing. The model's input or output has to be fp16 too: against a feature of another type, a bound buffer fails the arg instead of reaching Core ML, while unbound args still convert as before. The model keeps one array per bound buffer, made on first use and remade only when the arg's shape changes, since wrapping costs Core ML a setup per array. After the prediction, the output step recognizes the in-place write by Core ML returning that same array (it hands back the output backing itself) and doesn't touch the buffer. When Core ML doesn't write a backing in place, the fallback copy writes through the pixel buffer's row stride (rows are padded to 64 bytes, e.g. 720 -> 768 bytes for 360 fp16 values) instead of treating the buffer as dense, and a buffer whose shape or dtype doesn't match the arg fails the prediction. Only models with bound buffers see any of this: with nothing bound, the per-arg lookup walks an empty list and returns, and the copy paths are unchanged. This diff was authored with Claude Code. Differential Revision: D127640931
d25c875 to
b9c9e56
Compare
…rch#24261) Summary: Lets a Core ML model take inputs and outputs in IOSurface-backed fp16 pixel buffers bound to it, so Core ML reads and writes them in place instead of copying them into and out of its own buffers. The saving grows with tensor size, so it matters most for large fp16 tensors such as video frames. `-[ETCoreMLModel bindPixelBuffer:error:]` binds a `kCVPixelFormatType_OneComponent16Half` IOSurface-backed buffer to one model instance, and refuses it on OSes older than macOS 12 / iOS 16, which lack the pixel-buffer `MLMultiArray` API, or if the buffer can't be locked or has no base address. There is no global registry, and two models can bind the same buffer, e.g. to chain one model's output into another's input. Callers don't have the delegate's headers, so the next diff lets them bind buffers through load-time options. Bound buffers are matched by base address: when an input's or output's data pointer is a bound buffer's base address, `prepareArgs` hands Core ML an array wrapping that buffer (`-[MLMultiArray initWithPixelBuffer:shape:]`) as the input or the output backing. The model's input or output has to be fp16 too: against a feature of another type, a bound buffer fails the arg instead of reaching Core ML, while unbound args still convert as before. The model keeps one array per bound buffer, made on first use and remade only when the arg's shape changes, since wrapping costs Core ML a setup per array. After the prediction, the output step recognizes the in-place write by Core ML returning that same array (it hands back the output backing itself) and doesn't touch the buffer. When Core ML doesn't write a backing in place, the fallback copy writes through the pixel buffer's row stride (rows are padded to 64 bytes, e.g. 720 -> 768 bytes for 360 fp16 values) instead of treating the buffer as dense, and a buffer whose shape or dtype doesn't match the arg fails the prediction. Only models with bound buffers see any of this: with nothing bound, the per-arg lookup walks an empty list and returns, and the copy paths are unchanged. This diff was authored with Claude Code. Differential Revision: D127640931
Summary: `BackendInitContext::method_meta()` gives a delegate's `init` the `MethodMeta` of the method being loaded, or null when the context is built without one. `Method::init` passes its own; the view is borrowed and valid only during `init`. With it, a delegate can check the method around it, such as its delegates, its instruction count and whether its inputs and outputs are memory planned, before it accepts options that depend on them. The Core ML delegate uses this later in the stack to accept IOSurface load options only for a method that is just its own call on unplanned inputs and outputs. This diff was authored with Claude Code. Reviewed By: JacobSzwejbka Differential Revision: D127716377
…rch#24261) Summary: Lets a Core ML model take inputs and outputs in IOSurface-backed fp16 pixel buffers bound to it, so Core ML reads and writes them in place instead of copying them into and out of its own buffers. The saving grows with tensor size, so it matters most for large fp16 tensors such as video frames. `-[ETCoreMLModel bindPixelBuffer:error:]` binds a `kCVPixelFormatType_OneComponent16Half` IOSurface-backed buffer to one model instance, and refuses it on OSes older than macOS 12 / iOS 16, which lack the pixel-buffer `MLMultiArray` API, or if the buffer can't be locked or has no base address. There is no global registry, and two models can bind the same buffer, e.g. to chain one model's output into another's input. Callers don't have the delegate's headers, so the next diff lets them bind buffers through load-time options. Bound buffers are matched by base address: when an input's or output's data pointer is a bound buffer's base address, `prepareArgs` hands Core ML an array wrapping that buffer (`-[MLMultiArray initWithPixelBuffer:shape:]`) as the input or the output backing. The model's input or output has to be fp16 too: against a feature of another type, a bound buffer fails the arg instead of reaching Core ML, while unbound args still convert as before. The model keeps one array per bound buffer, made on first use and remade only when the arg's shape changes, since wrapping costs Core ML a setup per array. After the prediction, the output step recognizes the in-place write by Core ML returning that same array (it hands back the output backing itself) and doesn't touch the buffer. When Core ML doesn't write a backing in place, the fallback copy writes through the pixel buffer's row stride (rows are padded to 64 bytes, e.g. 720 -> 768 bytes for 360 fp16 values) instead of treating the buffer as dense, and a buffer whose shape or dtype doesn't match the arg fails the prediction. Only models with bound buffers see any of this: with nothing bound, the per-arg lookup walks an empty list and returns, and the copy paths are unchanged. This diff was authored with Claude Code. Differential Revision: D127640931
b9c9e56 to
32837f6
Compare
Summary:
Lets a Core ML model take inputs and outputs in IOSurface-backed fp16 pixel buffers bound to it, so Core ML reads and writes them in place instead of copying them into and out of its own buffers. The saving grows with tensor size, so it matters most for large fp16 tensors such as video frames.
-[ETCoreMLModel bindPixelBuffer:error:]binds akCVPixelFormatType_OneComponent16HalfIOSurface-backed buffer to one model instance, and refuses it on OSes older than macOS 12 / iOS 16, which lack the pixel-bufferMLMultiArrayAPI, or if the buffer can't be locked or has no base address. There is no global registry, and two models can bind the same buffer, e.g. to chain one model's output into another's input. Callers don't have the delegate's headers, so the next diff lets them bind buffers through load-time options.Bound buffers are matched by base address: when an input's or output's data pointer is a bound buffer's base address,
prepareArgshands Core ML an array wrapping that buffer (-[MLMultiArray initWithPixelBuffer:shape:]) as the input or the output backing. The model's input or output has to be fp16 too: against a feature of another type, a bound buffer fails the arg instead of reaching Core ML, while unbound args still convert as before. The model keeps one array per bound buffer, made on first use and remade only when the arg's shape changes, since wrapping costs Core ML a setup per array. After the prediction, the output step recognizes the in-place write by Core ML returning that same array (it hands back the output backing itself) and doesn't touch the buffer. When Core ML doesn't write a backing in place, the fallback copy writes through the pixel buffer's row stride (rows are padded to 64 bytes, e.g. 720 -> 768 bytes for 360 fp16 values) instead of treating the buffer as dense, and a buffer whose shape or dtype doesn't match the arg fails the prediction.Only models with bound buffers see any of this: with nothing bound, the per-arg lookup walks an empty list and returns, and the copy paths are unchanged.
This diff was authored with Claude Code.
Differential Revision: D127640931