Skip to content

GPU 0.2.0: @Gpu path, GpuArena, workgroup barriers, docs, and cthreads[gpu] wheels - #28

Merged
K-T0BIAS merged 19 commits into
mainfrom
4-native-gpu-support-cthreads-for-vulkanspir-v
Sep 18, 2026
Merged

K-T0BIAS merged 19 commits into
mainfrom
4-native-gpu-support-cthreads-for-vulkanspir-v

Conversation

@K-T0BIAS

Copy link
Copy Markdown
Owner

Summary

Merges the public Vulkan compute path into main for the 0.2.0 line: @Gpu /
gpu() / GpuJob, list writeback, GpuArena residency, workgroup barriers
(__sync_threads / Barrier.arrive_and_wait()), full user docs, and optional
GPU wheels via pip install "cthreads[gpu]" (cthreads-gpu on PyPI).

CPU @Thread is unchanged. GPU is a separate backend in the same process.
Shared memory is not in this merge (planned 0.2.1).

How it works (short)

  1. Mark a void kernel with @Gpu (scalars + list of scalars only).
  2. Index work with GlobalIdx.x (dispatch is rounded up to workgroup size 64;
    always if i >= n: return).
  3. gpu(fn, *args) compiles registered kernels to SPIR-V (cached), uploads
    buffers, dispatches on Vulkan, returns a started GpuJob.
  4. join() waits on the fence and, by default, downloads list args into the
    same Python list objects. Scalars are inputs only; result() is always
    None.
  5. For many launches over the same lists: GpuArena.bind(...) once, then
    join(download=False) and arena.sync() when Python must read results.
  6. Workgroup barriers wait only inside one workgroup (not the whole grid).
    Global phases use multiple gpu() launches with join between them.

There is no mid-run Python observe on GPU (unlike CPU __sync_state).

Product rules and reading order: docs/guide/gpu/README.md.

Features

Kernels and launch

  • @Gpu / @Gpu(log=True): validate, register, attach launch meta
  • prepare / compile / gpu(fn, *args) / GpuJob.join(download=...)
  • Index builtins: GlobalIdx, ThreadIdx, BlockIdx, BlockDim, GridDim
  • Math CallPlugins: sqrt / floor / int(...) (and math.* forms)
  • Soft probe: gpu.available(), device_name(), typed GPU errors

Residency

  • GpuArena: bind Python lists into process GpuState
  • join(download=False) skips host writeback; arena.sync() downloads on demand

Sync

  • from cthreads.sync import Barrier, __sync_threads
  • Inside @Gpu: __sync_threads() and Barrier.arrive_and_wait() lower to the
    same GLSL workgroup barrier; Barrier(...) construction is rejected on GPU
  • CPU Barrier(parties) unchanged

Docs

User guides (no prior GPU experience assumed):

Doc Topic
guide/gpu/concepts.md Host/device, workgroups, writeback
guide/gpu/quickstart.md First saxpy
guide/gpu/kernels.md Types and language subset
guide/gpu/indexes.md Dispatch / GlobalIdx
guide/gpu/launch.md gpu() / GpuJob
guide/gpu/arena.md Residency
guide/gpu/sync.md Workgroup barriers
guide/gpu/best_practices.md Correctness and perf
guide/gpu/examples.md Worked samples
guide/gpu/errors.md Troubleshooting
guide/gpu/api.md Compact API

Also wired from docs/index.md, install.md,
API.md, and the package README.

Packaging / CI

  • cthreads wheels: CPU (CTHREADS_GPU=OFF)
  • cthreads-gpu wheels: GPU ON (retarget via scripts/retarget_gpu_wheel.py)
  • Extra: gpu = ["cthreads-gpu==<version>"] (must match project.version)
  • CI test-gpu compile smoke; release builds both wheel sets
  • Details: docs/release.md

Example: element-wise kernel

from cthreads.gpu import Gpu, GlobalIdx, gpu

@Gpu
def saxpy(n: int, a: float, x: list[float], y: list[float]) -> None:
    i: int = GlobalIdx.x
    if i >= n:
        return
    y[i] = a * x[i] + y[i]

x = [1.0, 2.0, 3.0, 4.0]
y = [10.0, 20.0, 30.0, 40.0]
gpu(saxpy, len(x), 2.0, x, y).join()
# y == [12.0, 24.0, 36.0, 48.0]

Example: resident multi-pass loop

from cthreads.gpu import Gpu, GlobalIdx, GpuArena, gpu

@Gpu
def damp(n: int, factor: float, y: list[float]) -> None:
    i: int = GlobalIdx.x
    if i >= n:
        return
    y[i] = y[i] * factor

y = [1.0] * 100_000
n = len(y)
with GpuArena() as arena:
    arena.bind(y=y)
    for _ in range(50):
        gpu(damp, n, 0.99, y).join(download=False)
    arena.sync("y")

Example: workgroup barrier call shape

from cthreads.sync import Barrier, __sync_threads
from cthreads.gpu import Gpu, GlobalIdx

@Gpu
def after_local_step(n: int, data: list[float]) -> None:
    i: int = GlobalIdx.x
    if i >= n:
        return
    data[i] = data[i] + 1.0
    __sync_threads()                 # CUDA-style
    # Barrier.arrive_and_wait()      # same lowering

Barriers are workgroup-local. For grid-wide phases, launch twice and join
between kernels (guide/gpu/sync.md).

Install (after release)

pip install cthreads              # CPU wheel
pip install "cthreads[gpu]"       # pulls cthreads-gpu (GPU _ext)
from cthreads import gpu
assert gpu.available()
print(gpu.device_name())

Editable GPU build (contributors): CMAKE_ARGS=-DCTHREADS_GPU=ON pip install -e ".[test]".

Out of scope (follow-ups)

  • Workgroup shared memory (0.2.1)
  • Device atomics / GPU sort-scan in the dialect
  • Grid-wide barrier inside one kernel
  • @Thread launching @Gpu (gpu_future_cpu_to_gpu.md)
  • macOS / MoltenVK

T-Karu-smaecs and others added 16 commits September 5, 2026 15:18
Dynamic loader init, _ext.gpu bindings, cthreads.gpu API/errors, and
pytest coverage that skips when no GPU is available.
Add Vulkan GPU context probe (Issue GPU-01 #9) behind CTHREADS_GPU.
Device-local SSBOs with staging via a Context-owned transfer engine,
option-5 GpuPack, typed GPU errors, and test-only _ext.gpu.testing round-trips.
…-substrate

20 gou 02 memory gpupack marshal substrate
Shader cache and descriptors, launch_gpu_kernel submit with fence,
join list writeback into kept Python args, and saxpy smoke under
_ext.gpu.testing.
Add GPU launch path and SpawnedGpuKernel join (#22).
Process-lifetime launch command pool with per-job CB and fence
checkout/return under mutex; stop creating a command pool per launch.
Complete the Vulkan GPU user path on top of LaunchEngine: vendored glslang
SPIR-V, Signature/codegen, ShaderCache register, resident-aware prepare after
shutdown, lists-only bindings, list[bool] marshal, and broad unit/pipeline
coverage.
Introduce process-wide GpuState and Python GpuArena so bound lists stay on
device across launches. Support join(download=False),
borrow resident buffers in launch, and skip GLSL compile tests on GitHub Actions.
…unch-pool-emit-entry_

24 gpu 04 gpu gpu public path launch pool emit entry
…ait.

Lower both call forms to the same GLSL barrier and memoryBarrierShared through
a CallPlugin, wire Call expr-stmts on the GPU path, and export a sync stub.
Add GPU workgroup barrier via __sync_threads and Barrier.arrive_and_w…
@K-T0BIAS K-T0BIAS self-assigned this Sep 18, 2026
@K-T0BIAS K-T0BIAS added enhancement New feature or request performance Speed this up labels Sep 18, 2026
@K-T0BIAS K-T0BIAS linked an issue Sep 18, 2026 that may be closed by this pull request
@K-T0BIAS
K-T0BIAS merged commit 8ac6029 into main Sep 18, 2026
4 checks passed
@K-T0BIAS
K-T0BIAS deleted the 4-native-gpu-support-cthreads-for-vulkanspir-v branch September 19, 2026 15:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request performance Speed this up

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Native GPU support (cthreads for vulkan/SPIR-V)

2 participants