Request description
Overview
In multi-device settings that include a heterogenous device setup including e.g. the CPU and some kind of accelerator, memory is often shared between the CPU and the accelerator. While the memory may be shared, explicit flush of written memory regions may be required in case non-coherent shared memory.
Currently there exist multiple gaps in the compiler to allow a true zero-copy lowering path for this use case.
This ticket proposes steps to close these gaps and aims to describe an implementation that achieves a true zero-copy lowering path:
The zero-copy path must preserve a synchronization operation representing the handoff between devices, even when no data copy is required. Allocation planning must make the transfer result alias the source storage. Transfer canonicalization and elision must preserve required cache maintenance and producer/consumer dependencies.
The current lowering for flow.tensor.transfer
Currently handling device assignment happens through the insertion of flow transfer operations on tensors:
%1 = flow.tensor.transfer %0 : tensor<16x16xi8> to #hal.device.affinity<@device_b>
these sub-sequently lower to async transfer in the stream dialect:
stream.async.transfer %src : ... -> to(#hal.device.affinity<@device_b>) ...
In the lowering from stream async to stream cmd this then lowers outgoing transfers to (
|
applyAsyncTransferOp(IREE::Stream::AffinityAttr executionAffinityAttr, |
)
stream.cmd.copy %src[...], %dst[...], %length : ...
stream.cmd.flush to(#hal.device.affinity<@device_b>) %dst[...] : ...
incoming transfers on the other hand lower to: stream.cmd.invalidate and stream.cmd.copy
Missing lowering for stream.cmd.flush and stream.cmd.invalidate
The first gap in the lowering is that lowering of stream.cmd.flush is simply not implemented. The operations is simply erased in the current lowering. See
|
struct CmdFlushOpPattern : StreamConversionPattern<IREE::Stream::CmdFlushOp> { |
Not only is the lowering of the operation missing also its implementation in the HAL command buffer is not present. The lowering to be implemented should follow what is implemented for the IREE::Stream::CmdCopyOp
|
struct CmdCopyOpPattern : StreamConversionPattern<IREE::Stream::CmdCopyOp> { |
. This lowers to
IREE::HAL::CommandBufferCopyBufferOp which is sub-sequently lowered to the VM call here
|
class CommandBufferCopyBufferOpConversion |
It also requires the extension of the HAL with the correct command buffer handler. See handling for hal.command_buffer.copy_buffer it should be analogous.
Similar for stream.cmd.invalidate
Note that the HAL implementation already provides functions for buffer flush and invalidation:
iree_hal_buffer_mapping_flush_range
iree_hal_buffer_mapping_invalidate_range
Copy operation when lowering
The current lowering can produce a copy of the buffer which breaks a true zero-copy path:
stream.cmd.copy %src[...], %dst[...], %length : ...
stream.cmd.flush to(#hal.device.affinity<@device_b>) %dst[...] : ...
Transfers can be eliminated when resource lifetimes match and the affinity/topology checks permit it. This replaces the transfer result with its source.
|
IREE::Stream::AsyncTransferOp transferOp, ElisionAnalysis &analysis, |
For non-coherent memory this elision is not valid.
For true zero-copy we need to avoid the introduction of stream.cmd.copy.
To achieve this we should allow to communicate that no explicit buffer allocation and copy is required for flow.tensor.transfer and stream.async.transfer. Either derived from the device affinity or from an explicit attribute set on the operation. Zero-copy requires that the backing allocation is accessible to both devices and satisfies their usage requirements. Device affinity or an operation attribute must convey or establish this compatibility, shared physical memory alone is insufficient.
This should control the lowering of stream.async.transfer to avoid the insertion of the stream.cmd.copy
Note that simply leaving out the copy is not possible, there also has to be change in allocation so that the consumers read from the correct buffer. This needs to be handled in
|
allocateExecutionRegion(IREE::Stream::AsyncExecuteOp executeOp, |
Flush and invalidate operations must have execution-time ordering semantics. A flush must execute after the writes it makes available, and completion must be included in the handoff’s signaled timepoint/fence. An invalidate must execute after the corresponding producer completion and before dependent reads. Implementations must not perform cache maintenance merely during command-buffer recording. Backends may satisfy these requirements through queued commands, ordered host work, or equivalent synchronization guarantees.
Invalidate/flush is ignored for single copy/fill
The current matching for transfer operations is ignoring flush/invalidate
This optimization must preserve required flush/invalidate operations or decline the optimization. A region containing only cache maintenance after copy removal must retain its execution and synchronization semantics.
Cases to be handled
- Coherent shared memory: shared backing allocation, no copy, preserved ordering.
- Non-coherent shared memory: shared backing allocation, no copy, correctly ordered flush/invalidate as required in each direction.
- Incompatible or discrete memory: retain allocation and copy.
- Multiple consumers and in-place updates: preserve tensor semantics and allocation lifetime.
What component(s) does this issue relate to?
No response
Additional context
No response
Request description
Overview
In multi-device settings that include a heterogenous device setup including e.g. the CPU and some kind of accelerator, memory is often shared between the CPU and the accelerator. While the memory may be shared, explicit flush of written memory regions may be required in case non-coherent shared memory.
Currently there exist multiple gaps in the compiler to allow a true zero-copy lowering path for this use case.
This ticket proposes steps to close these gaps and aims to describe an implementation that achieves a true zero-copy lowering path:
The zero-copy path must preserve a synchronization operation representing the handoff between devices, even when no data copy is required. Allocation planning must make the transfer result alias the source storage. Transfer canonicalization and elision must preserve required cache maintenance and producer/consumer dependencies.
The current lowering for flow.tensor.transfer
Currently handling device assignment happens through the insertion of flow transfer operations on tensors:
these sub-sequently lower to async transfer in the stream dialect:
In the lowering from stream async to stream cmd this then lowers outgoing transfers to (
iree/compiler/src/iree/compiler/Dialect/Stream/Transforms/ScheduleAllocation.cpp
Line 677 in 9acea6a
incoming transfers on the other hand lower to:
stream.cmd.invalidateandstream.cmd.copyMissing lowering for stream.cmd.flush and stream.cmd.invalidate
The first gap in the lowering is that lowering of
stream.cmd.flushis simply not implemented. The operations is simply erased in the current lowering. Seeiree/compiler/src/iree/compiler/Dialect/HAL/Conversion/StreamToHAL/Patterns.cpp
Line 622 in 9acea6a
Not only is the lowering of the operation missing also its implementation in the HAL command buffer is not present. The lowering to be implemented should follow what is implemented for the
IREE::Stream::CmdCopyOpiree/compiler/src/iree/compiler/Dialect/HAL/Conversion/StreamToHAL/Patterns.cpp
Line 674 in 9acea6a
IREE::HAL::CommandBufferCopyBufferOpwhich is sub-sequently lowered to the VM call hereiree/compiler/src/iree/compiler/Dialect/HAL/Conversion/HALToVM/ConvertCommandBufferOps.cpp
Line 206 in 9acea6a
It also requires the extension of the HAL with the correct command buffer handler. See handling for
hal.command_buffer.copy_bufferit should be analogous.Similar for
stream.cmd.invalidateNote that the HAL implementation already provides functions for buffer flush and invalidation:
iree_hal_buffer_mapping_flush_rangeiree_hal_buffer_mapping_invalidate_rangeCopy operation when lowering
The current lowering can produce a copy of the buffer which breaks a true zero-copy path:
Transfers can be eliminated when resource lifetimes match and the affinity/topology checks permit it. This replaces the transfer result with its source.
iree/compiler/src/iree/compiler/Dialect/Stream/Transforms/ElideAsyncCopies.cpp
Line 994 in 9acea6a
For true zero-copy we need to avoid the introduction of
stream.cmd.copy.To achieve this we should allow to communicate that no explicit buffer allocation and copy is required for
flow.tensor.transferandstream.async.transfer. Either derived from the device affinity or from an explicit attribute set on the operation. Zero-copy requires that the backing allocation is accessible to both devices and satisfies their usage requirements. Device affinity or an operation attribute must convey or establish this compatibility, shared physical memory alone is insufficient.This should control the lowering of
stream.async.transferto avoid the insertion of thestream.cmd.copyNote that simply leaving out the copy is not possible, there also has to be change in allocation so that the consumers read from the correct buffer. This needs to be handled in
iree/compiler/src/iree/compiler/Dialect/Stream/Transforms/ScheduleAllocation.cpp
Line 1642 in 9acea6a
Flush and invalidate operations must have execution-time ordering semantics. A flush must execute after the writes it makes available, and completion must be included in the handoff’s signaled timepoint/fence. An invalidate must execute after the corresponding producer completion and before dependent reads. Implementations must not perform cache maintenance merely during command-buffer recording. Backends may satisfy these requirements through queued commands, ordered host work, or equivalent synchronization guarantees.
Invalidate/flush is ignored for single copy/fill
The current matching for transfer operations is ignoring flush/invalidate
iree/compiler/src/iree/compiler/Dialect/HAL/Conversion/StreamToHAL/Patterns.cpp
Line 1232 in 9acea6a
This optimization must preserve required flush/invalidate operations or decline the optimization. A region containing only cache maintenance after copy removal must retain its execution and synchronization semantics.
Cases to be handled
What component(s) does this issue relate to?
No response
Additional context
No response