How to author device functions: types, language subset, math helpers, and how decoration differs from launch.
- Decorating
- Allowed types
- Return type and outputs
- Locals and annotations
- Language subset
- Math and casts
- Imports inside kernels
- What is rejected
- Decorate vs launch vs call-as-Python
from cthreads.gpu import Gpu, GlobalIdx
@Gpu
def fill(n: int, value: float, out: list[float]) -> None:
i: int = GlobalIdx.x
if i >= n:
return
out[i] = value(GlobalIdx is documented in indexes.md.)
Optional logging of the assigned device name:
@Gpu(log=True)
def fill_logged(n: int, value: float, out: list[float]) -> None:
...Decoration:
- Checks that the GPU path is available (
gpu.available()). - Validates annotations against the GPU allowlist.
- Builds launch metadata on the function (
__gpu_kernel_meta__). - Registers the function for later SPIR-V compile.
Decoration does not upload data or run the kernel.
| Annotation | Role | Notes |
|---|---|---|
int |
Scalar input | Packed into the scalar storage buffer |
float |
Scalar input | Device float (32-bit). CPU @Thread uses double; GPU follows GLSL float |
bool |
Scalar input | Stored as 32-bit on the device packing path |
list[int] |
Buffer (writeback) | Same Python list updated on join |
list[float] |
Buffer (writeback) | Same |
list[bool] |
Buffer (writeback) | Same |
Not supported on the GPU path (raise at meta build / decorate):
str,dict[...]@Threadableclassescthreads.synclock / event / TBuffer annotations as kernel parameters- Nested lists (
list[list[float]]) set, optional types,Any
Structure-of-arrays is the intended style: parallel lists of scalars
(pos_x, pos_y, vel_x, ...) rather than a list of particle objects.
Every @Gpu function must be annotated -> None.
Outputs are in-place list mutations. After join(), inspect those lists on
the host. Scalar parameters are not written back.
@Gpu
def add_scaled(
n: int,
alpha: float,
src: list[float],
dst: list[float],
) -> None:
i: int = GlobalIdx.x
if i >= n:
return
dst[i] = dst[i] + alpha * src[i]Locals must use annotated assignment, same discipline as @Thread:
i: int = GlobalIdx.x
tmp: float = x[i] * a
ok: bool = i < nBare i = GlobalIdx.x is not part of the supported subset.
Supported control flow (compiled to GLSL):
| Construct | Support |
|---|---|
if / else |
Yes |
while |
Yes (no while/else) |
for i in range(...) |
Yes (range with 1-3 args; no keywords) |
return |
Early exit only (return; / bare return) |
break / continue |
Via the shared flow helpers (same spirit as CPU) |
| Expression statements that are calls | Yes (math, barriers) |
Not supported:
for x in some_list:(no list iteration)for/else,while/else- Rebinding an existing name as a
fortarget - Arbitrary Python calls, comprehensions, generators,
try/except, classes, nesteddef
Index expressions on list parameters (y[i] = ...) are the primary memory API.
The GPU CallPlugin path currently lowers:
| Python form | Device |
|---|---|
sqrt(x) or math.sqrt(x) |
GLSL sqrt |
floor(x) or math.floor(x) |
GLSL floor (toward -inf) |
int(x) |
GLSL int (truncate toward zero) |
Example:
import math
from cthreads.gpu import Gpu, GlobalIdx
@Gpu
def cell_of(n: int, inv_h: float, x: list[float], cell: list[int]) -> None:
i: int = GlobalIdx.x
if i >= n:
return
c: float = math.floor(x[i] * inv_h)
cell[i] = int(c)Richer math will grow over time; do not assume the full math module is available
inside @Gpu. Prefer the helpers above or express formulas with + - * / and
comparisons.
You may import names that the translator recognizes (math for math.sqrt /
math.floor, sync barrier names, index builtins). The compiled kernel does not
run Python imports on the device; matching is by AST shape at compile time.
Typical host-side imports for authoring:
from cthreads.gpu import Gpu, GlobalIdx, BlockDim, ThreadIdx
from cthreads.sync import Barrier, __sync_threads| Pattern | Why |
|---|---|
Barrier(4) inside @Gpu |
Host-style construction; use Barrier.arrive_and_wait() |
Valued return x |
Kernels are void; use list writeback |
Keyword args to gpu(fn, x=...) |
Not supported yet; positional only |
| Mid-run Python reads of lists | No GPU sync_state; join or arena.sync |
Assuming float is IEEE double |
Device float is 32-bit |
| Action | Effect |
|---|---|
@Gpu on a function |
Validate + register |
Direct fn(*args) in Python |
Runs the Python source as ordinary Python (useful for tiny checks; not the device path) |
gpu(fn, *args) |
Compile if needed, upload, dispatch, return GpuJob |
job.join() |
Wait (+ download lists by default) |
For production device work, always go through gpu(...).