Quick reference¶
Use this page while writing a kernel. For a first complete program, start with Getting Started.
Host code and device code¶
| Task | In Python, outside a kernel | In a kernel or @tack.func |
|---|---|---|
| Allocate storage | tack.empty, tack.zeros, tack.full, tack.array |
Fixed-size scratch with tack.local_array |
| Choose a device | tack.set_default_device("cuda"), with tack.device("cuda"):, device= on a constructor, data.to_device(d) |
Runs on its arrays' device |
| Access values | data.numpy() or np.asarray(data) (copies), or data[i] for one element |
data[i], grid[row, column] |
| Write values | tack.copyto(data, host_values) or tack.copyto(data, other_array), data.fill(value) |
data[i] = value |
| Repeat work | Python timestep loop calling kernels | One parallel for in a kernel; sequential nested loops |
| Reuse an operation | Ordinary Python function | Helper decorated with @tack.func |
| Change a parameter | Pass a scalar argument | Read that argument |
| Fix a model parameter | Declare DT = tack.constant(0.01) |
Read DT |
| Sum a whole array | data.sum() or tack.algorithms statistics |
Write contributions, or use an atomic where appropriate |
| Wait for the GPU | tack.sync(); host array reads also wait |
tack.barrier() waits only within a workgroup |
Device code supports a numerical subset of Python. NumPy calls, file operations
and arbitrary Python objects belong on the host. Save decorated functions in a
.py file so Tack can read their source. See the execution model.
Choose storage¶
| Need | Allocation | NumPy result |
|---|---|---|
| Uninitialized scalar array | tack.empty((h, w)) (f32) |
numpy() → (h, w) |
| Zero-filled integer array | tack.zeros((h, w), tack.i32) |
numpy() → (h, w) |
| Allocate without copying | tack.empty_like(prototype) |
Same array shape and element type; unspecified values |
| Copy an existing NumPy array | tack.array(a) |
Same shape and dtype (float64 stays f64) |
| One RGB vector per pixel | tack.empty((h, w), tack.vec3()) |
numpy() → (h, w, 3) |
| One 2×2 matrix per particle | tack.empty((n,), tack.mat2()) |
numpy() → (n, 2, 2) |
Initialize every value before reading it. Shape comes first, then dtype, as in
NumPy; the dtype defaults to f32. Array shape counts elements; the
dtype describes one scalar, vector or matrix element. numpy() appends
component dimensions, and tack.copyto expects them. reshape preserves the element type and shares storage;
copy allocates independent storage. See Arrays and Types.
Choose an execution pattern¶
| Calculation | Pattern | Example |
|---|---|---|
| Independent element update | One iteration owns each output | First kernel |
| Neighbourhood stencil | Read an old array, write a new array, swap | Heat diffusion |
| Many inputs contribute to one output | Initialize an accumulator, then use atomics | Particle-to-grid simulation |
| Unordered append with known capacity | Claim slots with the old value from atomic_add |
Kernel results |
| Ordered or exactly sized output | Count, exclusive scan, allocate, write | Marching squares |
| Reuse data within a GPU workgroup | Shared memory and barriers, with complete groups | Tiled N-body |
Separate globally dependent stages into separate kernel calls. The device runs them in order. A workgroup barrier cannot synchronize the whole grid. See Memory and Parallel Correctness.
Choose a dataset operation¶
| Need | Start with |
|---|---|
| Build discontinuous nodal coefficients | Construct an L2 field |
| Initialize quadratic nodal values | Construct a quadratic H1 field |
| Continuous, per-cell or discontinuous values | Fields and Spaces |
| Select whole cells or cut a region | Filter and Analyze |
| Work per cell or face without hard-coding connectivity | Write Mesh Algorithms |
| Evaluate a field at physical positions | Probing |
| Trace a steady velocity field or render extracted geometry | Pipelines |
| Filter and reduce a dataset in pieces, with ghost cells | Work with Data in Pieces |
tack.Array describes storage. tack.dataset.Field associates values with a
topology through a space. A field's association must be correct before a filter
can interpolate or propagate it.
Keep these numerical choices explicit¶
- Use
f32for a portable starting point. Metal has nof64; on Level Zero, checkget_backend().supports_f64after importingget_backendfromtack.runtime.dispatch. int(x)truncates toward zero. Useint(floor(x))for the cell containing a possibly negative coordinate.- Vector
*is component-wise; vectordotand matrix@form products. - Integer
/producesf32. Cast both operands totack.f64for a double-precision quotient on a backend that supports it. - Floating parallel sums can differ in their low bits. Compare against an independent result with a tolerance chosen for the calculation.
See Control Flow and Math, numerical details, and reductions.
Check, then time¶
Use tack.set_debug(True) to find invalid indices. Compare a small
case with a NumPy reference before increasing the input size. For timing, disable
debug mode, warm the kernel, reuse allocations, and call tack.sync() before
stopping the clock. Measure transfers separately when studying kernel execution.
Debugging and Timing has complete examples and a symptom table. Backends explains capabilities and setup.