Skip to content

Commit 501360d

Browse files
committed
Update parallel.async<_stream> syntax.
1 parent 190a006 commit 501360d

3 files changed

Lines changed: 85 additions & 11 deletions

File tree

‎documentation/general-async.md‎

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,70 @@
11
### Synchronization and Asynchronous `parallel-by`
2-
Similar to `inthreads`, `parallel-by` can also be made asynchronous.
32

3+
Similar to `inthreads`, `parallel-by` can also be made asynchronous by appending `.async`:
4+
5+
```choreo
6+
parallel.async p by 6 : block {
7+
// asynchronous kernel launch (no host-side synchronization)
8+
}
9+
```
10+
11+
Without `.async`, the host waits for the kernel to finish before continuing:
12+
13+
```choreo
14+
parallel p by 6 : block {
15+
// synchronous kernel launch (host blocks until completion)
16+
}
17+
// host continues only after the kernel finishes
18+
```
19+
20+
With `.async`, the host code continues immediately after launching the kernel.
21+
This is essential for overlapping computation with data transfers or launching
22+
independent kernels concurrently.
23+
24+
### Stream Binding
25+
26+
CUDA streams allow multiple kernels and memory transfers to execute concurrently
27+
on the GPU. In Choreo, you can bind a `parallel-by` block to a specific stream
28+
using angle-bracket syntax:
29+
30+
```choreo
31+
__co__ void foo(stream s0, stream s1, f32[M] a, f32[N] b) {
32+
parallel.async<s0> p by M : block {
33+
a.at(p) = 1.0f;
34+
}
35+
parallel.async<s1> q by N : block {
36+
b.at(q) = 2.0f;
37+
}
38+
}
39+
```
40+
41+
In this example:
42+
43+
- **`stream s0, stream s1`**: Stream parameters passed from the host.
44+
- **`parallel.async<s0>`**: Launches the kernel asynchronously on stream `s0`.
45+
- **`parallel.async<s1>`**: Launches a second kernel on stream `s1`.
46+
47+
Since both kernels are launched on different streams with `.async`, they can
48+
execute concurrently on the GPU.
49+
50+
### Stream Binding without `.async`
51+
52+
Stream binding also works with synchronous `parallel-by`. In this case, the host
53+
launches the kernel on the specified stream and then waits for that stream to
54+
complete:
55+
56+
```choreo
57+
__co__ void bar(stream _s, f32[M] a) {
58+
parallel<_s> p by M : block {
59+
a.at(p) = 1.0f;
60+
}
61+
// host synchronizes on stream _s before continuing
62+
}
63+
```
64+
65+
### Rules
66+
67+
- Stream binding is only allowed on **block-level** parallel (`: block`).
68+
- The identifier inside `<...>` must be a `stream`-typed variable.
69+
- Without stream binding, synchronous `parallel-by` uses device-wide
70+
synchronization, and asynchronous `parallel-by` uses the default stream.

‎documentation/parallel-in-choreo.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,21 @@ For example, for *CUDA/Cute* targets, a *spanned* buffer must be annotated with
117117

118118
Additionally, *shared* and *local* memory types have a lifetime limited to the duration of the kernel launch. Therefore, referencing them outside the *parallel execution block* is not possible. This constraint ensures proper lifetime management of these memory types.
119119

120+
### Asynchronous Kernel Launch and Stream Binding
121+
122+
By default, the host waits for a `parallel-by` kernel to complete before continuing. To launch kernels asynchronously, use `parallel.async`. To bind a kernel to a specific CUDA stream, use angle-bracket syntax:
123+
124+
```choreo
125+
__co__ void foo(stream s, f32[M] a) {
126+
parallel.async<s> p by M : block {
127+
a.at(p) = 1.0f;
128+
}
129+
// host continues immediately (no synchronization)
130+
}
131+
```
132+
133+
This is covered in detail in the [Asynchronous parallel-by](general-async.md) section.
134+
120135
### Quick Summary
121136

122137
In this section, we explored Choreo's approach to parallelism in heterogeneous computing environments, focusing on the `parallel-by` block syntax and the management of multiple and sub-level parallelism. We also discussed how Choreo abstracts kernel launches and imposes memory management constraints, enabling us to leverage parallel hardware effectively.

‎tutorial/ch05-branch-control.md‎

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -254,18 +254,10 @@ parallel.async {px, py} by [grid_m, grid_n] : block {
254254
// kernel body
255255
}
256256
```
257-
<!--
258-
```choreo
259-
parallel.async {px, py} by [grid_m, grid_n] : block {
260-
stream s;
261-
// kernel body
262-
}
263-
```
264-
-->
265257

266258
**`parallel.async`** returns control to the host immediately — the kernel is enqueued but the host does not wait for completion. This is the Croqtile equivalent of `cudaLaunchKernel` with a non-default stream.
267259

268-
<!-- **`stream s`** pins the kernel to CUDA stream `s`. Multiple `parallel.async` blocks with different streams can overlap on the GPU if there are enough SMs. -->
260+
**`parallel.async<s>`** pins the kernel to CUDA stream `s`. Multiple `parallel.async` blocks with different streams can overlap on the GPU if there are enough SMs.
269261

270262
This is **host orchestration**, orthogonal to in-kernel control flow. It does not replace `inthreads.async` for thread partitioning or `if` for runtime predicates — it decides *when* and *where* a grid runs relative to other grids.
271263

@@ -299,7 +291,7 @@ parallel p by BLK_CNT: block {
299291
| `int total_tiles = expr` | Local integer variable |
300292
| `parallel.async ... : block` | Non-blocking kernel launch |
301293
| `yield` | Early return from `parallel` block |
302-
<!-- | `stream s` | Bind kernel to CUDA stream `s` | -->
294+
| `stream s` | Bind kernel to CUDA stream `s` |
303295

304296
## Chapter summary
305297

0 commit comments

Comments
 (0)