Streams: order work without assuming overlap
Trace same-stream dependencies, independent streams, host buffer lifetime, and the configured default-stream mode.
On this page
The question for today
What you’ll understand
- Build an ordered copy-kernel-copy chain within one stream.
- Explain why different streams do not automatically order shared-data accesses.
- Distinguish legacy and per-thread default-stream behavior.
A CUDA stream is an ordered sequence of submitted operations, not a dedicated hardware lane or a guarantee of simultaneous execution. Operations in the same stream provide useful ordering; separate streams can expose independence but need explicit dependencies when they share data. Host return behavior is another dimension: a launch can return before its device work finishes. Keep these contracts separate so a program remains correct whether the runtime overlaps work or serializes it.
Same-stream order gives a dependency chain
A host-to-device copy, kernel, and device-to-host copy submitted to one stream can express that the kernel uses the copied input and that the output copy follows the kernel. The host still needs the appropriate completion observation before reading the destination or reusing buffers whose transfers may be active. A launch returning successfully does not mean its output exists on the host. Record which stream owns each operation and which completion point protects each buffer’s lifetime.
An Async suffix does not mean every argument combination behaves identically with respect to the host. CUDA12.6 documents copy synchronization behavior depending on transfer direction and memory type; pageable host memory can involve staging or blocking. Proper pinned memory can support intended asynchronous transfer behavior, but hardware support, resources, dependencies, and workload still determine overlap. Do not build correctness around a particular amount of host blocking that an implementation happened to exhibit during one test.
Different streams need an explicit shared-data relation
If producer P writes a buffer in stream S1 and consumer C reads it in S2, host submission order alone is not a sufficient cross-stream dependency in the general case. Record an event after P and make S2 wait on the appropriate event before C, or use another documented ordering mechanism. If no data dependency exists, separate streams permit overlap but do not promise it. Resource pressure, copy-engine limits, kernel characteristics, and other contexts can serialize operations without violating correctness.
Default-stream mode matters. The legacy default stream has implicit synchronization with blocking streams in the same context, while explicitly nonblocking streams avoid that legacy synchronization. A per-thread default stream is local to the host thread and context and behaves like an ordinary stream with respect to other ordinary streams; it is not the same as a legacy global ordering shortcut. It is also not a nonblocking stream relative to a legacy default stream. Configure and document the mode, especially when separately compiled libraries participate.
Design for legal serialization as well as concurrency
A correct multi-stream program should not deadlock merely because independent work is scheduled serially. Avoid cross-stream waits whose graph contains a cycle, and keep buffer ownership explicit across reuse and deallocation. Benchmark device work with suitable events or completion-aware timers; a CPU timer around enqueue calls measures submission plus any host blocking, not necessarily kernel execution. Include required output completion when reporting application latency, and distinguish it from the steady-state throughput of a pipelined transfer schedule.
The queue lab illustrates submission outrunning service at a much simpler level. Reset, set λ=4 and μ=3, predict backlog 4+t, then Play or Step. Reset and set λ=2, μ=3 to predict a drain at t=4. This one fluid queue has no CUDA streams, DMA engines, events, pinned buffers, or default-stream mode. It explains why fast host submission need not mean completed device work; actual stream order and transfer lifetime must be verified with the documented API.
Work it through
Two independent ordered chains
Each of two independent streams has a 2 ms input copy, a 5 ms kernel, and a 1 ms output copy. Assume these fixed durations and ignore host submission overhead. No buffers are shared between chains.
Within each stream the chain is ordered, so its declared duration is 2+5+1=8 ms. A host consumer must still observe that stream’s output completion before reading its result.
If the two chains can overlap perfectly under the hypothetical resource assumptions, completing both needs at least max(8,8)=8 ms. Fully serial execution takes 16 ms.
Two streams alone do not establish the eight-millisecond case. Copy engines, kernel resources, and contention may prevent that overlap. Correctness should hold under either schedule because each chain’s buffers and dependencies are independent.
Ordering is a correctness contract; overlap is a resource-dependent performance opportunity that must be measured.
The living notebook
Explore the model
Interactive lab · queue
Submission is not completion
Reset, set λ=4 and μ=3, predict backlog 4+t, then Play or Step. Reset and set λ=2 and μ=3 for drainage. The fluid queue does not represent CUDA streams, transfer engines, buffer lifetime, or default-stream rules.
- Fluid backlog
- 4
- Net rate · λ − μ
- −1 /s
- M/M/1 mean residence
- 1 s
Fluid backlog starts at 4 and follows max(0, 4 + (λ − μ)t). The separate M/M/1 residence-time formula assumes Poisson arrivals, exponential service and λ < μ. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.
Look forA producer can submit work faster than it completes, even when every operation has correct dependencies.
Pause & reason
Check your understanding
Your turn
Try it yourself
Two independent chains take 6 ms and 9 ms. Give the perfect-overlap lower bound and serial sum. Does using pinned buffers prove the lower bound is achieved?
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
Compare maximum and sum, then separate enabling conditions from measured concurrency.
Compare with the solution
The lower bound is max(6,9)=9 ms, and serial time is 15 ms before extra overhead. Pinned buffers can support asynchronous transfers under the documented API, but do not guarantee available copy engines, kernel overlap, or the absence of resource contention.
Keep the idea
Take it with you
- Same-stream order does not mean host-visible completion.
- Shared data across streams needs an explicit supported dependency.
- Default-stream mode and host memory type change relevant behavior.
Read further
Original sources and deeper explanations to follow at your own pace.
Optional progress, saved in this browser when available. Revisit any lesson at any time.
Lesson not marked complete.