Skip to content

ctx.threads, ctx.warps, ctx.lanes

A CTA regrouping into warps as @launch changes its thread countSchematic. The CTA's threads drawn as rows of 32 cells, one row per warp. The thread count runs from 32 to 1024 in steps of 32, and the number of warps follows as the count divided by 32. ctx.threads, ctx.lanes and ctx.warps are marked as compile-time constants. With @launch(48) the second warp is half empty, drawn dashed, and the compiler's error is shown.FIG / CTX / COUNTSschematicctx.lanes = 32warp 0warp 4warp 7ctx.warps@launch(256)ctx.threads = 256ctx.lanes = 32ctx.warps = 256 / 32 = 8compile-time constantcompile-time constantcompile-time constant

@launch(256): 256 threads in 8 warps of 32 lanes. All three counts are compile-time constants.

  • not a whole warp
Schematic. Each row is one warp of ctx.lanes threads; ctx.warps is ctx.threads / ctx.lanes. The error is the compiler's own, from the example below.

A CTA is a fixed number of threads, and the hardware runs them in warps of a fixed number of lanes. Three ctx fields give those numbers. The figure shows a CTA regrouping into warps as its thread count changes.

Field Value Known
ctx.threads The CTA’s thread count, from @launch. 256 when the kernel has no @launch. At compile time
ctx.lanes Lanes in a warp on the target: 32 on NVIDIA. At compile time
ctx.warps ctx.threads / ctx.lanes At compile time

All three are uniform: every thread of every CTA sees the same value. They are also compile-time constants. The compiler folds them into the generated code as literals, so a loop bound or a stride written with them costs nothing at run time.

The normative definitions are in the spec under Indexing and synchronization.

@launch sets ctx.threads. Its first argument is the CTA size:

@launch(128)
kernel counts(O: tensor<i32>[1, 4]) {
O[0, 0] = i32(ctx.threads) // 128
O[0, 1] = i32(ctx.warps) // 4
O[0, 2] = i32(ctx.lanes) // 32
O[0, 3] = i32(ctx.blocks.y) // a runtime value, read from the launch
}

Every thread runs those four stores and every thread writes the same values, so the result is well defined. ctx.blocks.y is there for contrast: it is the grid’s extent, which is only known when the kernel is launched (see ctx.block and ctx.blocks).

ctx.warps requires ctx.threads to be a whole number of warps. On NVIDIA that means a multiple of 32, and @launch enforces it directly: a thread count that is not a positive multiple of 32 is rejected before any code is generated.

@launch(48)
kernel half_a_warp(O: tensor<i32>[1, 1]) {
O[0, 0] = i32(ctx.warps)
}

Since the check happens at @launch, a kernel can always rely on ctx.threads == ctx.warps * ctx.lanes.

The counts are what a kernel uses to split work over its threads without hard-coding the launch size:

Pattern Written as
One warp per output column let col = ctx.block.x * ctx.warps + ctx.warp
A loop whose lanes split a row for k in range(ctx.lane, K, ctx.lanes)
A loop whose threads split a row for k in range(ctx.thread, K, ctx.threads)
A thread’s run of four, CTA-strided let e = 4 * ctx.thread + i * 4 * ctx.threads

The indices in those patterns, ctx.thread, ctx.warp and ctx.lane, are the subject of the next page.