99 IREE stream Dialect
99.1 Beginner Summary
The IREE stream dialect is the compiler stage where tensor programs become explicitly scheduled asynchronous programs. It sits between IREE flow and the lower-level hardware abstraction layer, usually called HAL:
flow.* -> stream.* -> hal.*
For a beginner, the central idea is that stream explains how tensor values turn into byte-addressed resources, how those resources move between host and device memory, how work is dispatched, and how asynchronous dependencies are tracked. Earlier dialects can pretend that tensors are immutable SSA values. The Stream dialect makes the hard execution questions explicit: where data lives, how large it is, what lifetime it has, what device or affinity should own it, what operations may run concurrently, and what timepoint says a result is ready.
99.2 Why This Dialect Exists
IREE compiles machine-learning programs to many runtime targets. A tensor program may start as a clean mathematical dataflow graph, but a runtime cannot execute abstract tensors directly. It needs buffers, command streams, device queues, dispatches, synchronization objects, parameter loads, file reads, and memory transfers.
The stream dialect exists to bridge that gap without immediately committing to one device API. It is lower level than tensor IR, but still higher level than a particular HAL backend. It gives IREE a place to decide scheduling and resource management before the program is converted to HAL Inline or HAL Loader forms.
99.3 When This Dialect Matters
Stream matters when you are reading the middle and late IREE compiler pipeline. If you see stream.tensor.*, the compiler is still carrying logical tensor information while preparing resource lowering. If you see stream.async.*, the program has mostly moved to asynchronous resource operations. If you see stream.cmd.*, the compiler is issuing more explicit command-style operations that are close to HAL lowering.
This dialect is important for understanding memory use, transfer behavior, dispatch boundaries, asynchronous ordering, concurrency, and target-specific layout/encoding decisions. Many performance questions in IREE eventually pass through Stream: unnecessary copies, poor resource lifetimes, over-serialization, too many dispatch bindings, or tensors encoded in layouts that do not match the target.
99.4 When To Use It
Most users do not write Stream IR by hand. You use it when inspecting compiler output, debugging IREE lowering, authoring IREE compiler passes, or adding a new backend path.
Use Stream concepts when you need to answer questions such as:
- Has this tensor become a
!stream.resourceyet? - Is this operation still in the tensor phase, async phase, or command phase?
- What timepoint protects this resource?
- Is the resource external, transient, variable, constant, staging, or unknown?
- Which dispatch executable receives this binding?
- Which pass is responsible for removing a copy or assigning a lifetime?
99.5 Core Types
!stream.timepoint represents a point in the execution timeline. If a resource is produced with a timepoint, it is not safe to use the resource until that timepoint has been reached. Timepoints make ordering explicit while still allowing the compiler to move waits, join dependencies, and preserve concurrency.
!stream.resource<...> represents managed storage. The lifetime parameter is critical:
| Lifetime | Meaning |
|---|---|
!stream.resource<*> |
Lifetime has not been analyzed yet. |
!stream.resource<external> |
Storage is externally managed, often at an ABI boundary. |
!stream.resource<staging> |
Staging storage used for upload/download paths. |
!stream.resource<transient> |
Short-lived storage used across stream operations. |
!stream.resource<variable> |
Long-lived mutable storage. |
!stream.resource<constant> |
Immutable long-lived storage, often program constants. |
!stream.channel represents a participant in a collective communication group. !stream.file represents a file handle for async file I/O into or out of resources. !stream.binding represents a resource binding visible inside an executable dispatch function. !stream.test.fence is test-only infrastructure for Stream timeline tests.
99.6 Core Attributes
Important attributes include #stream.collective, which describes collective operations such as all-gather, all-reduce, broadcast, send, and receive; #stream.partitioning_config, which guides partitioning tradeoffs such as debuggability, peak memory, or concurrency; #stream.resource_config, which captures resource constraints such as allocation limits, alignment, range limits, index bit width, aliasing policy, and memory model; and #stream.parameter.named, which names externally defined parameter storage.
These attributes are not decorative. They guide how Stream chooses memory layouts, dispatch boundaries, transfer behavior, and target-specific lowering.
99.7 The Phase Model
The easiest way to understand Stream is as a sequence of lowering phases.
stream.tensor.* operations still talk about tensors, shapes, encodings, and tensor imports/exports. This phase preserves high-level information needed to choose storage sizes and layouts.
stream.async.* operations work on !stream.resource values and represent asynchronous work. They model allocation, copies, fills, updates, dispatches, collectives, function calls, and timepoint-producing execution regions.
stream.cmd.* operations are later command-phase operations. They are closer to what HAL command buffers and runtime issue paths need: flushes, invalidates, copies, fills, dispatches, serial/concurrent command regions, and command-side parameter I/O.
Timepoint operations cut across the phases. They let Stream represent when work is complete without forcing every operation to become a blocking host wait.
99.8 Operation Inventory: Resources, Files, And Tensors
| Operation | Meaning |
|---|---|
stream.context.resolve |
Resolves low-level context resources for an affinity. |
stream.resource.alloc |
Allocates persistent resource storage. |
stream.resource.alloca |
Allocates transient asynchronous resource storage. |
stream.resource.dealloca |
Frees transient storage after a timepoint. |
stream.resource.retain |
Retains ownership of a resource. |
stream.resource.release |
Releases an ownership claim and reports terminal ownership. |
stream.resource.is_terminal |
Checks whether the current owner is the last owner. |
stream.resource.size |
Returns resource storage size in bytes. |
stream.resource.try_map |
Tries to map host memory into a resource. |
stream.resource.load |
Loads a value from a resource byte range. |
stream.resource.store |
Stores a value into a resource byte range. |
stream.resource.pack |
Packs multiple resource slices into a larger resource layout. |
stream.resource.constants |
Represents constant resource storage. |
stream.resource.subview |
Creates a resource subview. |
stream.resource.transients |
Represents user-provided transient storage. |
stream.file.constant |
Creates or references a file handle constant. |
stream.file.read |
Reads file data into a resource. |
stream.file.write |
Writes resource data to a file. |
stream.tensor.import |
Imports an external tensor-like value into Stream. |
stream.tensor.export |
Exports a Stream tensor/resource value outward. |
stream.tensor.sizeof |
Computes the encoded storage size of a tensor. |
stream.tensor.empty |
Creates an uninitialized tensor value. |
stream.tensor.constant |
Creates a tensor from constant or parameter data. |
stream.tensor.splat |
Creates a tensor filled with one scalar value. |
stream.tensor.clone |
Clones a tensor value. |
stream.tensor.encode |
Applies an encoding transformation to a tensor. |
stream.tensor.slice |
Produces a tensor slice. |
stream.tensor.fill |
Fills a tensor range. |
stream.tensor.update |
Updates part of a tensor with another tensor. |
stream.tensor.load |
Loads a scalar or vector from a tensor. |
stream.tensor.store |
Stores a scalar or vector into a tensor. |
stream.tensor.trace |
Emits tensor tracing/debug information. |
stream.tensor.dispatch |
Dispatches executable work over tensor operands. |
stream.tensor.parameter.load |
Loads tensor data from a parameter scope. |
stream.tensor.parameter.write |
Writes tensor data to a parameter scope. |
99.9 Operation Inventory: Async Operations
| Operation | Meaning |
|---|---|
stream.async.alloca |
Allocates an async resource. |
stream.async.constant |
Creates an async constant resource. |
stream.async.splat |
Creates an async resource filled with one value. |
stream.async.clone |
Clones an async resource. |
stream.async.slice |
Creates an async resource slice. |
stream.async.fill |
Fills an async resource range. |
stream.async.update |
Updates a target resource with another resource. |
stream.async.copy |
Copies bytes between async resources. |
stream.async.collective |
Performs a collective operation over resources. |
stream.async.barrier |
Creates a dependency barrier for async resources. |
stream.async.transfer |
Transfers a resource between compatible lifetimes or affinities. |
stream.async.cast |
Casts a resource lifetime/type view. |
stream.async.load |
Loads a value from an async resource. |
stream.async.store |
Stores a value into an async resource. |
stream.async.dispatch |
Dispatches executable work over async resources. |
stream.async.func |
Declares an async streamable function. |
stream.async.call |
Calls an async streamable function. |
stream.async.execute |
Groups async work into a timepoint-producing execution region. |
stream.async.concurrent |
Groups async work that may run concurrently. |
stream.async.parameter.load |
Loads async resources from parameters. |
stream.async.parameter.read |
Reads a resource from a parameter scope. |
stream.async.parameter.write |
Writes a resource to a parameter scope. |
stream.async.parameter.gather |
Gathers multiple parameter resources. |
stream.async.parameter.scatter |
Scatters multiple resources to parameters. |
99.10 Operation Inventory: Command Operations
| Operation | Meaning |
|---|---|
stream.cmd.flush |
Flushes resource writes for command execution. |
stream.cmd.invalidate |
Invalidates resource ranges before reads. |
stream.cmd.discard |
Discards resource contents before overwrite. |
stream.cmd.fill |
Fills a resource range. |
stream.cmd.copy |
Copies a resource range. |
stream.cmd.collective |
Issues a command-phase collective. |
stream.cmd.dispatch |
Issues a command-phase executable dispatch. |
stream.cmd.func |
Declares a command-phase streamable function. |
stream.cmd.call |
Calls a command-phase streamable function. |
stream.cmd.execute |
Executes a dependency-aware command sequence. |
stream.cmd.serial |
Executes nested command ops serially. |
stream.cmd.concurrent |
Executes nested command ops concurrently. |
stream.cmd.parameter.load |
Loads command-phase resources from parameters. |
stream.cmd.parameter.read |
Reads a parameter resource. |
stream.cmd.parameter.write |
Writes a parameter resource. |
stream.cmd.parameter.gather |
Gathers parameter resources. |
stream.cmd.parameter.scatter |
Scatters resources to parameters. |
99.11 Operation Inventory: Synchronization, Dispatch, And Utilities
| Operation | Meaning |
|---|---|
stream.timepoint.immediate |
Produces an already-reached timepoint. |
stream.timepoint.import |
Imports an external synchronization object as a timepoint. |
stream.timepoint.export |
Exports a timepoint to an external type. |
stream.timepoint.chain_external |
Chains a timepoint through an external synchronization value. |
stream.timepoint.join |
Joins multiple timepoints. |
stream.timepoint.barrier |
Produces a timepoint for resource availability. |
stream.timepoint.await |
Waits for a timepoint before exposing resources. |
stream.channel.create |
Creates a collective communication channel. |
stream.channel.split |
Splits a channel by color and key. |
stream.channel.rank |
Returns the local rank in a channel. |
stream.channel.count |
Returns the participant count in a channel. |
stream.executable |
Defines a generic executable module. |
stream.executable.end |
Terminates a stream.executable. |
stream.executable.export |
Defines a dispatch entry point. |
stream.binding.subspan |
Creates an alias to subspan data in an executable binding. |
stream.dispatch.workgroup.id |
Returns the current workgroup id dimension. |
stream.dispatch.workgroup.count |
Returns the workgroup count dimension. |
stream.dispatch.workgroup.size |
Returns the workgroup size dimension. |
stream.return |
Returns from Stream regions/functions. |
stream.yield |
Yields from Stream execution regions. |
stream.test.timeline_op |
Test-only timeline interface operation. |
stream.test.timeline_aware |
Test-only timeline-aware operation. |
99.12 Transformation Inventory
| Pass | Role |
|---|---|
iree-stream-conversion |
Converts supported Flow, tensor, util, HAL ABI, and control-flow inputs into Stream. |
iree-stream-split-parameter-encoder |
Splits compatible parameter encoding work into a separate module. |
iree-stream-encode-host-tensors |
Lowers host-side tensor encodings to async resource forms. |
iree-stream-encode-device-tensors |
Encodes executable binding tensors for device-side expectations. |
iree-stream-materialize-builtins |
Replaces unsupported operations with builtin dispatches. |
iree-stream-materialize-copy-on-write |
Expands implicit immutable-resource semantics into explicit clones or rematerialization. |
iree-stream-materialize-encodings |
Turns stream.tensor.encode operations into dispatches and executables. |
iree-stream-clone-to-consumers |
Clones eligible operations per consumer affinity. |
iree-stream-elide-async-copies |
Removes async clones, transfers, or slices that do no useful work. |
iree-stream-emplace-allocations |
Places results directly into existing resources when safe. |
iree-stream-refine-usage |
Assigns fixed resource lifetimes and inserts transfers when needed. |
iree-stream-schedule-execution |
Groups async operations into executable regions. |
iree-stream-schedule-concurrency |
Groups operations inside execution regions into concurrent streams. |
iree-stream-sync-initializers |
Converts initializer-produced timepoints into synchronous waits. |
iree-stream-propagate-timepoints |
Moves timepoints through calls, globals, and control flow to avoid unnecessary waits. |
iree-stream-elide-timepoints |
Removes waits covered by dependent timepoints. |
iree-stream-schedule-allocation |
Converts implicit async resource management into explicit command-style allocation and deallocation. |
iree-stream-automatic-reference-counting |
Inserts retains, releases, and deallocations for async resources. |
iree-stream-pack-constants |
Packs constant resources and materializes initialization operations. |
iree-stream-layout-slices |
Lays out packed resource slices with target-aware alignment and offsets. |
iree-stream-reuse-allocations |
Reuses compatible transient allocations when lifetime ordering permits. |
iree-stream-emplace-transients |
Places transient allocations into user-provided storage buffers. |
iree-stream-materialize-transient-size-queries |
Generates query functions for transient storage size requirements. |
iree-stream-annotate-constant-transient-size |
Adds reflection metadata for constant transient sizes. |
iree-stream-fold-uniform-operands |
Folds uniformly passed dispatch operands. |
iree-stream-fuse-dispatch-bindings |
Fuses dispatch bindings that share underlying storage. |
iree-stream-specialize-dispatches |
Specializes executables based on dispatch-site operand patterns. |
iree-stream-unify-encoding-for-globals |
Chooses one encoding for shared immutable global data when possible. |
iree-stream-specialize-encodings |
Duplicates and specializes executables for resolved encoding layouts. |
iree-stream-annotate-dispatch-arguments |
Annotates dispatch operands and bindings with value/alignment facts. |
iree-stream-annotate-dispatch-assumptions |
Inserts executable-local assumptions derived from dispatch sites. |
iree-stream-pack-dispatch-operands |
Packs dispatch operands into i32 push constants. |
iree-stream-annotate-affinities |
Adds affinity annotations for debugging. |
iree-stream-dump-statistics |
Dumps Stream dialect statistics. |
iree-stream-verify-input |
Verifies that input dialects are supported by Stream lowering. |
iree-stream-verify-affinities |
Checks that operations have affinities assigned directly or indirectly. |
iree-stream-verify-lowering-to-tensors |
Checks that inputs have reached stream.tensor.* form. |
iree-stream-verify-lowering-to-async-resources |
Checks that tensor ops/types have lowered to async resource ops. |
iree-stream-verify-lowering-to-async |
Checks that tensor ops are gone and resource lifetimes are assigned. |
iree-stream-verify-async-access-ranges |
Checks async resource ranges are in bounds where possible. |
iree-stream-verify-lowering-to-cmd |
Checks that async ops/types have lowered to command-phase ops. |
99.13 Conversion Paths
The entry conversion is iree-stream-conversion. It uses conversion patterns from Flow-to-Stream, Util-to-Stream, Standard-to-Stream, and HAL-to-Stream paths. Its job is not just to rename operations. It changes the representation from implicit tensor dataflow to resource-aware Stream IR with symbolic storage sizes.
The outbound conversions are usually owned by HAL module passes. The iree-hal-inline-conversion pass uses Stream-to-HAL-Inline patterns, converting Stream resource, file, tensor boundary, command, and timepoint operations into HAL Inline or simpler surrounding IR. The iree-hal-loader-conversion pass combines Stream-to-HAL-Inline patterns with Stream-to-HAL-Loader patterns, especially for executable loading and dispatch through the loader module.
In other words, Stream is the middle layer: it receives high-level tensor work, turns it into scheduled resource work, and then hands that work to HAL lowering.
99.14 How To Read Stream IR
A typical Stream resource type includes both lifetime and size information:
%r = stream.async.constant ... : !stream.resource<constant>{%size}
Read this as: %r is immutable storage with a known or symbolic byte size.
A timepoint-producing operation means the resource is not immediately safe to use:
%r, %t = stream.resource.alloca uninitialized : !stream.resource<transient>{%n}
=> !stream.timepoint
Read this as: the allocation has been scheduled, and %t tells later work when the resource is available.
A dispatch connects host-side resource state to executable code:
%r2 = stream.async.dispatch @executable::@entry(%r[%c0 to %n for %n])
: (!stream.resource<*>{%n}) -> %r{%n}
Read this as: Stream is scheduling executable work over a resource slice and tracking the resulting resource.
Inside executable code, bindings are made visible through stream.binding.subspan, and workgroup metadata comes from stream.dispatch.workgroup.id, stream.dispatch.workgroup.count, and stream.dispatch.workgroup.size.
99.15 What It Implies
Seeing Stream IR means the compiler is no longer only reasoning about tensor values. It is reasoning about execution. A stream.tensor.clone may still look tensor-like, but it is on a path toward resource copies. A stream.async.copy has concurrency and lifetime implications. A stream.timepoint.await may force a wait unless later propagation or elision can move it. A stream.resource<*> means lifetime inference is still pending, while stream.resource<transient> or stream.resource<constant> means the compiler has made a resource classification decision.
The dialect also implies that target details are beginning to matter. Affinity, resource constraints, encodings, memory model, and dispatch binding layout all influence the final runtime behavior.
99.16 Common Pitfalls
Do not treat stream.tensor.*, stream.async.*, and stream.cmd.* as interchangeable spellings. They are different lowering phases.
Do not ignore timepoints. Using a resource before its timepoint is reached is undefined in the Stream model.
Do not assume !stream.resource<*> is final. It means the lifetime is unknown and a later pass such as iree-stream-refine-usage must resolve it.
Do not assume every copy is waste. Some copies are needed for correctness, lifetime isolation, memory compatibility, or concurrency. The purpose of passes such as iree-stream-materialize-copy-on-write and iree-stream-elide-async-copies is to make required copies explicit and remove the redundant ones.
Do not debug backend performance without checking Stream. Many performance problems are visible here before HAL lowering: excessive timepoint waits, unfused dispatch bindings, poor resource reuse, unnecessary transfers, or encoding choices that force extra dispatches.