Events: connect streams without blocking the entire device
Trace record and wait placement, event timing boundaries, and safe buffer reuse.
On this page
The question for today
What you’ll understand
- Place an event after its producer and a wait before its consumer.
- Distinguish a device-side stream wait from host event synchronization.
- Interpret event timing as a defined interval rather than host submission latency.
An event can mark progress through a stream and let another stream depend on that progress. The useful relationship comes from where record and wait operations are placed, not from the event’s variable name. Events can also support timing when created and used appropriately, but timing a chosen stream interval is different from measuring CPU launch overhead or proving all device work completed. Keep dependency events and timing experiments conceptually separate.
Record the producer boundary, then wait at the consumer
Submit the producer in S1, record event E in S1 after it, and submit a wait for that recorded event in S2 before the consumer. Work in S2 after the wait cannot proceed past the required event completion until the captured producer boundary is reached. The event does not automatically include later work added after its record. Place the record too early and the consumer can race with the actual producer; place the wait after the consumer and it cannot retroactively fix the read.
cudaStreamWaitEvent creates a stream dependency without making the CPU wait for all device work. cudaEventSynchronize instead blocks the host until the event’s captured work is complete, and cudaEventQuery lets the host ask whether it is complete. A not-ready result is not a failure of the kernel by itself. Check API return values and create an acyclic dependency graph. An event that has never been recorded represents no captured work, so waiting on it does not wait for a future record. Do not use an unrecorded placeholder as a barrier.
Event reuse and buffer lifetime need a generation story
An event object can be recorded again, so a reusable pipeline must ensure each wait refers to the intended captured record according to the API’s most-recent-record semantics at submission. Give each buffer slot an explicit lifecycle: input available, producer complete, consumer complete, then reuse permitted. Reusing one event carelessly across multiple in-flight iterations can associate a wait with the wrong boundary. Separate event objects per pipeline slot or another proven protocol can make that lifecycle easier to reason about.
The completion that protects a buffer depends on its last relevant user. A producer-ready event allows a consumer to begin; it does not allow the host to overwrite the buffer while the consumer still reads it. Record or observe a consumer-complete boundary before reuse. The same principle applies to asynchronous copies and deallocation: launch order is not a lifetime proof unless the deallocation or overwrite is correctly ordered after every use under the actual API semantics.
Timing includes exactly the interval you bracket
For a simple timing experiment, record a start event and an end event around the intended work in a suitable stream and wait for completion before reading elapsed time. Timing-enabled events are required for elapsed-time queries. Other work, contention, and placement can influence what is measured, so do not equate an event interval with an isolated instruction cost. A CPU timer around cudaEventRecord calls measures a different interval unless it also accounts for device completion.
The network lab shows a fixed data-dependency graph, not asynchronous execution. Reset, set x=2 and w=1, predict sigmoid(1.5)≈0.818, then Step. The output mathematically depends on the earlier multiplication, but the graph has no streams, records, waits, or event generations. Use that limited graph to name the producer and consumer, then add the actual event edge in a separate schedule. Changing a number in the lab cannot validate a real buffer-lifetime protocol.
Work it through
Overlap independent setup before a consumer
Producer P runs in S1 from 0 to 4 ms, followed by event E. S2 has independent setup from 0 to 2 ms, then waits for E before a 3 ms consumer C. Assume no resource contention or overhead.
S2’s setup finishes at 2 ms, but P has not completed. The event wait holds C until the producer boundary at 4 ms, so the earliest C start is max(2,4)=4 ms.
C then finishes at 4+3=7 ms. The host need not block during the two-millisecond wait interval if it submitted a stream wait rather than performing a broad host synchronization.
Reusing P’s output buffer at 4 ms would be too early because C uses it until 7 ms. A separate completion boundary after C protects that reuse; E only established producer readiness.
Correct placement permits useful overlap while preserving the consumer’s dependency. Ready-for-consumption and safe-for-reuse are different boundaries.
The living notebook
Explore the model
Interactive lab · network
Name dependencies before scheduling them
Reset, set x=2 and w=1, predict σ(1.5)≈0.818, then Step. The fixed sigmoid graph shows a mathematical dependency only; it does not record events, wait on streams, time kernels, or validate buffer reuse.
- Product · wx
- 1.2
- Logit · wx − 0.5
- 0.7
- Sigmoid output
- 0.6682
One inspectable sigmoid unit, σ(wx − 0.5). Playback traces the computation graph. There is no training, attention mechanism or hidden deep network. 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 scheduling edge must be attached to the actual producer and last consumer, not just to a shared variable name.
Pause & reason
Check your understanding
Your turn
Try it yourself
A producer finishes at 6 ms. Earlier consumer-stream work finishes at 8 ms, and the dependent consumer takes 2 ms. Under the same ideal assumptions, when can it start, finish, and permit reuse of its input?
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
Apply both the producer dependency and the consumer stream’s own earlier work.
Compare with the solution
It starts no earlier than max(6,8)=8 ms and finishes at 10 ms. Reuse protected by this consumer is safe only after the corresponding completion at or after 10 ms, assuming there are no other users and the required host/device visibility conditions are met.
Keep the idea
Take it with you
- Record and wait placement defines the actual captured dependency.
- Host waits, device stream waits, queries, and timing serve different purposes.
- Producer readiness does not imply a buffer’s last consumer has finished.
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.