API¶
Look up the functions you call from Python here. The signatures and detailed descriptions below are generated from the source. For a complete program, use the User's Guide; for a reminder of which operations belong on the host or in a kernel, use the quick reference.
| Task | Call | Result or effect |
|---|---|---|
| Choose the default device | tack.set_default_device("cuda") |
Arrays are allocated there; with nothing set it is the CPU |
| Name a device | tack.device("cuda"), with tack.device("cuda"): |
The same Device every time; with makes it the default in the block |
| Allocate on a device | tack.empty((n,), device="cuda") |
Every constructor takes device= |
| Move an array | a.to_device("cpu"), a.device |
a itself if its memory is usable there, else a copy |
| Check indices | tack.set_debug(True) |
Every kernel launched afterwards checks its indices |
| Allocate an output | tack.empty((n,)) |
New f32 array with unspecified contents |
| Allocate with another dtype | tack.empty((n,), tack.i32) |
Any Tack type or NumPy dtype (np.int32, "int32") |
| Allocate zeros, ones or a value | tack.zeros((n,)), tack.ones((n,)), tack.full((n,), 2.5) |
New array, filled on the device |
| Match storage without copying | tack.empty_like(prototype) |
Same shape and element dtype; no values read or initialized |
| Match and fill | tack.zeros_like(a), tack.ones_like(a), tack.full_like(a, v) |
Same shape and element dtype, filled |
| Upload NumPy data | tack.array(host) |
New array with an independent copy; a NumPy array keeps its dtype |
| Use an array as is | tack.asarray(obj) |
obj itself if it is already a Tack array (of dtype, if given), else tack.array(obj) |
| Overwrite values from the host | tack.copyto(a, host) |
Copies into a; host has a.numpy()'s shape or broadcasts to it |
| Copy between arrays | tack.copyto(dst, src) |
Device-side copy into an existing array of the same shape |
| Download data | a.numpy() or np.asarray(a) |
Independent NumPy array; waits for earlier writes |
| Allocate vector elements | tack.empty((n,), tack.vec3()) |
Array of n 3-vectors; NumPy download shape (n, 3) |
| Inspect shape and size | a.shape, a.ndim, a.size, a.nbytes |
Element counts (components excluded) and storage bytes |
| Reshape without copying | a.reshape(rows, -1) |
Shares storage; one dimension may be -1 |
| Wait for device work | tack.sync() |
Waits for queued launches on every device; returns None |
| Inspect a kernel | tack.inspect(kernel, *args, mode="source") |
Generated source as a string; does not execute the kernel |
Without a dtype, an allocation is f32; values from Python make f32 from
floats and i32 from ints (i64 if a value needs it), and a NumPy array keeps
its own dtype, so a float64 array becomes f64.
For ownership, errors and precise guarantees, see the Runtime API contract and the Kernel Language Contract. Compiler internals are in the Developer's Guide.
Devices¶
A kernel runs on its arrays' device: the default device when that can address them, otherwise theirs. See Backends: Devices.
tack.device ¶
device(spec='cpu', **options) -> Device
The device spec names: "cpu", "metal", "cuda", "cuda:0",
"hip", "level_zero"; a Device is returned as it is.
Without options it is made on first use and the same device afterwards,
so its compiled kernels and arrays are shared by everything that asks.
Options go to the backend and must be ones it declares in
Backend.device_options; they make a new device each call. The CPU
takes num_threads; Level Zero takes external_context, the
driver, device and context handles of a context owned by another
library, so both can address the same memory.
"cuda:N" is device N as the runtime numbers it (CUDA's ordinal,
HIP's device id, the index among the first Level Zero driver's
devices); device_count says how many there are. A bare "cuda" or
"hip" is the device the calling thread already uses: the one of a
CUDA context another library made current, HIP's current device, else
device 0. "level_zero" is device 0.
with tack.device("cuda"): makes it the default device in the block.
Source code in packages/tack-core/src/tack/runtime/dispatch.py
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | |
tack.Device ¶
Device(backend)
A device: where arrays live and kernels run. tack.device(...) makes one.
type is "cpu", "metal", "cuda", "hip" or
"level_zero"; index numbers the devices of a type. Two devices are
equal when they are the same backend. with device: makes it the
default device inside the block, on this thread.
Source code in packages/tack-core/src/tack/runtime/dispatch.py
103 104 | |
synchronize ¶
synchronize()
Wait for every kernel launched on this device so far.
Source code in packages/tack-core/src/tack/runtime/dispatch.py
114 115 116 | |
tack.device_count ¶
device_count(kind: str) -> int
How many devices of type kind this machine has: 0 where it cannot start.
Counting makes no device: tack.device(f"cuda:{n}") for n below
the count does.
Source code in packages/tack-core/src/tack/runtime/dispatch.py
208 209 210 211 212 213 214 215 216 217 218 219 220 | |
tack.set_default_device ¶
set_default_device(spec='cpu', **options) -> Device
Make spec's device the default: where arrays are allocated, and where
kernels without arrays run. Returns it.
The device is tack.device(spec, **options): without options the same
one as before, so arrays and compiled kernels survive another call.
Set TACK_NO_REINIT=1 to leave the default alone once one is set
(useful when embedded in an ANARI device that shares the process).
Source code in packages/tack-core/src/tack/runtime/dispatch.py
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 | |
tack.get_default_device ¶
get_default_device() -> Device
The default device: the innermost with tack.device(...) block's, else
the one set_default_device chose, else the CPU.
Source code in packages/tack-core/src/tack/runtime/dispatch.py
292 293 294 295 | |
tack.set_debug ¶
set_debug(enabled: bool = True)
Check every array index in every kernel launched from now on.
A launch that went out of bounds raises IndexError naming the access;
the access itself is skipped. Kernels compile separately in debug mode
and run slower; each launch waits to finish. TACK_DEBUG=1 turns it on
when Tack is imported.
Source code in packages/tack-core/src/tack/runtime/dispatch.py
298 299 300 301 302 303 304 305 306 307 | |
tack.runtime.dispatch.get_backend ¶
get_backend()
The default device's backend: the innermost with tack.device(...)
block's, else the one set_default_device chose, starting the CPU if
none was.
Source code in packages/tack-core/src/tack/runtime/dispatch.py
440 441 442 443 444 445 446 447 448 449 450 451 | |
tack.sync ¶
sync()
Wait until every kernel launched so far, on every device, has finished.
Reading or writing an array from the host (numpy, tack.copyto,
indexing, reductions, DLPack export) already waits for the kernels
before it, so most code never needs this. It is for timing kernels, and
for memory shared with another library that reads it without Tack.
Source code in packages/tack-core/src/tack/runtime/dispatch.py
409 410 411 412 413 414 415 416 417 418 | |
tack.sync_async
async
¶
sync_async()
sync for asyncio: await tack.sync_async() waits for every
kernel launched so far, on every device, without blocking the event
loop.
On CUDA, HIP, Metal and Level Zero the wait runs in a worker thread and
the loop goes on; launches made after the call are not waited for. A
launch's error is raised here. On the CPU, whose launches complete
before returning, it returns at once. See tack.runtime.aio.
Source code in packages/tack-core/src/tack/runtime/dispatch.py
426 427 428 429 430 431 432 433 434 435 436 437 | |
Arrays¶
tack.Array holds storage on one device (a.device), chosen at allocation. Its shape
counts elements; its dtype describes one scalar, vector or matrix element.
tack.dataset.Field instead couples coefficient values with a space on a topology.
See Arrays and Types for examples.
tack.Array ¶
Array(
dtype: ScalarType,
shape: tuple[int, ...],
buffer: DeviceBuffer,
writable: bool = True,
device=None,
)
An n-dimensional array of scalar, vector or matrix elements on a device.
shape counts elements and dtype describes one element, so an array
of shape (n,) and dtype vec3(f32) holds 3 * n scalars and
downloads as (n, 3).
layout says how a vector or matrix array stores its components:
"aos" (the default) interleaves them in one buffer, "soa" keeps
one scalar array per component (SoaArray). An array of scalars says
"aos".
Source code in packages/tack-core/src/tack/lang/array.py
235 236 237 238 239 240 241 242 243 | |
device
property
¶
device
The tack.Device this array's memory belongs to; kernels given the
array run there.
scalar_dtype
property
¶
scalar_dtype
The scalar component type used by backend storage and code generation.
to_device ¶
to_device(device) -> Array
This array on device (a tack.Device or a spec such as "cpu"):
itself if its memory is already usable there, else a copy.
Source code in packages/tack-core/src/tack/lang/array.py
259 260 261 262 263 264 265 266 | |
numpy ¶
numpy() -> np.ndarray
Copy the values into a new NumPy array.
Vector and matrix components become trailing NumPy dimensions: an
array of shape (n,) and dtype vec3(f32) gives (n, 3).
Source code in packages/tack-core/src/tack/lang/array.py
340 341 342 343 344 345 346 347 348 349 350 351 | |
__array__ ¶
__array__(dtype=None, copy=None)
np.asarray(a): the values as a NumPy array (always a copy).
Source code in packages/tack-core/src/tack/lang/array.py
353 354 355 356 357 358 | |
numpy_async
async
¶
numpy_async() -> np.ndarray
numpy for asyncio: await a.numpy_async().
Where reading waits for queued launches (CUDA, HIP, Metal, Level
Zero), the wait and the copy run in a worker thread and the event
loop goes on; elsewhere it is numpy. The result holds every write
queued before the call; one queued later by another task may or may
not be in it.
Source code in packages/tack-core/src/tack/lang/array.py
360 361 362 363 364 365 366 367 368 369 370 | |
fill ¶
fill(value)
Set every element to value.
A vector or matrix array also takes one element's value, a sequence
of its components: colors.fill([1.0, 1.0, 1.0]).
Source code in packages/tack-core/src/tack/lang/array.py
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 | |
sum ¶
sum()
Return a float sum; parallel floating addition order may vary.
Source code in packages/tack-core/src/tack/lang/array.py
409 410 411 | |
min ¶
min()
Return a float minimum; propagate NaNs and prefer negative zero.
Source code in packages/tack-core/src/tack/lang/array.py
413 414 415 | |
max ¶
max()
Return a float maximum; propagate NaNs and prefer positive zero.
Source code in packages/tack-core/src/tack/lang/array.py
417 418 419 | |
mean ¶
mean()
Return the mean of scalar components, or NaN for an empty array.
Source code in packages/tack-core/src/tack/lang/array.py
421 422 423 424 425 | |
export_memory ¶
export_memory() -> ExportedMemory
Export the array's GPU memory for cross-API sharing.
Returns an ExportedMemory with backend tag and a handle suitable for
importing into another API (e.g. Dawn, Vulkan, pycuda).
If the array was not allocated with exportable=True, a one-time
copy into exportable memory is performed automatically.
Source code in packages/tack-core/src/tack/lang/array.py
427 428 429 430 431 432 433 434 435 436 437 438 | |
__dlpack__ ¶
__dlpack__(
*,
stream=None,
max_version=None,
dl_device=None,
copy=None,
)
Export this array as a DLPack capsule for zero-copy interop.
Usage
torch_tensor = torch.from_dlpack(tack_array) cupy_array = cupy.from_dlpack(tack_array) np_array = np.from_dlpack(tack_array) # numpy 1.25+
A consumer passing max_version >= (1, 0) gets a versioned capsule, which is the only form that can carry the read-only flag. Without it the consumer must assume the memory is writable, so NumPy marks what it gets read-only and then cannot re-export it -- which breaks round trips through anything that hands the array back.
Source code in packages/tack-core/src/tack/lang/array.py
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 | |
__dlpack_device__ ¶
__dlpack_device__()
Return (device_type, device_id) for the DLPack protocol.
Source code in packages/tack-core/src/tack/lang/array.py
460 461 462 463 | |
__len__ ¶
__len__() -> int
Number of elements along the first dimension.
Source code in packages/tack-core/src/tack/lang/array.py
473 474 475 476 477 | |
__getitem__ ¶
__getitem__(index)
Read one element from the host: a[i], a[i, j], or a[()] for a
zero-dimensional array.
An array of scalars gives a Python number; a vector or matrix array
gives a NumPy array of the element. Negative indices count from
the end, as in Python. This is for inspection and for host-side
decisions: on backends without host-visible memory each read is a
transfer of that element, so many elements are read with
numpy(). Arrays are not written this way; see tack.copyto
and fill.
Source code in packages/tack-core/src/tack/lang/array.py
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 | |
copy ¶
copy() -> Array
Copy all values into independent storage on the same device.
Source code in packages/tack-core/src/tack/lang/array.py
535 536 537 538 539 540 | |
astype ¶
astype(dtype) -> Array
Copy with a new component type or a matching vector/matrix dtype.
A scalar dtype changes the component type while preserving the element dimensions. An element dtype must have those same dimensions.
Source code in packages/tack-core/src/tack/lang/array.py
542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 | |
reshape ¶
reshape(*shape) -> Array
Share storage under a new shape, preserving the element type.
Takes the shape as a tuple or as separate integers, as NumPy does;
one dimension may be -1, inferred from the size.
Source code in packages/tack-core/src/tack/lang/array.py
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 | |
tack.lang.array.DeviceBuffer ¶
Abstract interface for backend-specific array storage.
host_address
property
¶
host_address: int
The address a CPU kernel reads and writes through: only memory in the host's address space has one. A CPU kernel bound to another device's memory wrote device pointers from the host and segfaulted; this raises instead.
read_range ¶
read_range(start: int, count: int) -> np.ndarray
Elements [start, start + count) of the flat storage, as a copy.
Serves array[i] on the host. Every shipped buffer overrides it:
CPU and Metal slice their host-visible views, and CUDA, HIP and
Level Zero copy just the range. This default, for a buffer that
does not, copies the whole buffer, which is correct and slow.
Source code in packages/tack-core/src/tack/lang/array.py
69 70 71 72 73 74 75 76 77 | |
fill_bytes ¶
fill_bytes(pattern: bytes) -> bool
Fill the storage with pattern, one element's bytes, repeated.
Returns false when this buffer cannot, and the caller uploads the
array instead: right for host-visible storage, which this default
serves. CUDA, HIP and Level Zero override it to fill on the device
(runtime/device_fill.py).
Source code in packages/tack-core/src/tack/lang/array.py
82 83 84 85 86 87 88 89 90 | |
synchronize ¶
synchronize()
Wait for the launches queued on this storage's device, if any are: a backend that queues launches overrides it.
Source code in packages/tack-core/src/tack/lang/array.py
92 93 94 | |
thread_binding ¶
thread_binding()
A context manager that makes this storage usable from a thread
other than the one that made it -- CUDA's context made current --
for Array.numpy_async's worker. Nothing by default.
Source code in packages/tack-core/src/tack/lang/array.py
101 102 103 104 105 106 | |
share ¶
share()
Hand this storage outside Tack: wait for queued launches, and have later launches that use it complete before returning. A backend that queues launches overrides it.
Source code in packages/tack-core/src/tack/lang/array.py
108 109 110 111 112 | |
Creating arrays¶
tack.empty ¶
empty(
shape,
dtype=f32,
*,
device=None,
exportable=False,
layout="aos",
) -> Array
A new array of uninitialized scalar, vector or matrix elements.
shape counts elements; dtype describes one element. A vector
array of shape (n,) and dtype vec3(f32) downloads as (n, 3).
Initialize its values with a kernel, fill or tack.copyto before
reading. device is a tack.Device or a spec such as "cuda"; by
default the default device. layout="soa" stores a vector or matrix
array's components in one array each (see SoaArray).
Source code in packages/tack-core/src/tack/lang/array.py
839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 | |
tack.zeros ¶
zeros(
shape, dtype=f32, *, device=None, layout="aos"
) -> Array
A new array of zeros, filled on the device.
Source code in packages/tack-core/src/tack/lang/array.py
896 897 898 899 900 901 | |
tack.ones ¶
ones(
shape, dtype=f32, *, device=None, layout="aos"
) -> Array
A new array of ones.
Source code in packages/tack-core/src/tack/lang/array.py
904 905 906 907 908 | |
tack.full ¶
full(
shape,
fill_value,
dtype=None,
*,
device=None,
layout="aos",
) -> Array
A new array with every element fill_value.
Without dtype, a Python float gives f32 and an int i32 (i64 if it
needs it). A sequence fills vector or matrix elements; it needs an
element dtype.
Source code in packages/tack-core/src/tack/lang/array.py
911 912 913 914 915 916 917 918 919 920 921 922 | |
tack.empty_like ¶
empty_like(
prototype, dtype=None, *, device=None, layout=None
) -> Array
A new uninitialized array of prototype's shape and dtype.
prototype is a Tack or NumPy array; nothing is read from it. A scalar
dtype changes the component type of a vector or matrix prototype and
keeps its element dimensions. A Tack prototype's device and layout are
the defaults.
Source code in packages/tack-core/src/tack/lang/array.py
945 946 947 948 949 950 951 952 953 | |
tack.zeros_like ¶
zeros_like(
prototype, dtype=None, *, device=None, layout=None
) -> Array
A new array of zeros of prototype's shape and dtype.
Source code in packages/tack-core/src/tack/lang/array.py
956 957 958 959 960 961 | |
tack.ones_like ¶
ones_like(
prototype, dtype=None, *, device=None, layout=None
) -> Array
A new array of ones of prototype's shape and dtype.
Source code in packages/tack-core/src/tack/lang/array.py
964 965 966 967 968 | |
tack.full_like ¶
full_like(
prototype,
fill_value,
dtype=None,
*,
device=None,
layout=None,
) -> Array
A new array of prototype's shape and dtype, every element fill_value.
Source code in packages/tack-core/src/tack/lang/array.py
971 972 973 974 975 | |
tack.array ¶
array(
obj, dtype=None, *, device=None, layout=None
) -> Array
A new array holding a copy of obj: a NumPy array, a sequence or a Tack array.
Without dtype, all of obj's dimensions are array dimensions, a
NumPy array keeps its dtype, and Python floats and ints become f32 and
i32 (i64 if a value needs it). With an element dtype such as
vec3(f32), the trailing dimensions are the elements' components.
A Tack array is copied on its device unless device names another.
layout is "aos" or "soa" for a vector or matrix array (see
SoaArray); by default a Tack array keeps its own and other data is
interleaved.
Source code in packages/tack-core/src/tack/lang/array.py
978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 | |
tack.asarray ¶
asarray(
obj, dtype=None, *, device=None, layout=None
) -> Array
obj itself if it is a Tack array of dtype (or any, without one)
on device (or any, without one) in layout (or either, without one),
else array(obj, dtype, device=device, layout=layout).
Source code in packages/tack-core/src/tack/lang/array.py
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 | |
tack.arange ¶
arange(
start, stop=None, step=1, dtype=None, *, device=None
) -> Array
Evenly spaced values in [start, stop), as np.arange.
Without dtype, integer arguments give i32 (i64 if a value needs it)
and any float argument gives f32.
Source code in packages/tack-core/src/tack/lang/array.py
1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 | |
tack.concat ¶
concat(arrays) -> Array
Join a sequence of arrays end to end into a new one-dimensional array.
All arrays must have the same dtype and be on one device, where the copy runs.
Source code in packages/tack-core/src/tack/lang/array.py
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 | |
Structure of arrays¶
A vector or matrix array stored one scalar array per component
(layout="soa"); kernels index it as any vector array.
tack.soa ¶
soa(*components, dtype=None) -> SoaArray
A vector array whose components are the given arrays, without copying.
tack.soa(x, y, z) is an array of vec3 elements whose element
i is (x[i], y[i], z[i]): the structure-of-arrays layout, for
data that arrives that way, such as a simulation's coordinate arrays
or another library's tensors (from_dlpack, array_from_ptr).
Kernels read and write it as they do any vector array, and writes go
to the component arrays. The components are arrays of scalars of one
dtype and one shape, on one device. An element dtype of the same
component type makes a matrix array instead, components row-major:
tack.soa(a, b, c, d, dtype=tack.mat2()).
Source code in packages/tack-core/src/tack/lang/array.py
720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 | |
tack.SoaArray ¶
SoaArray(components, element_shape)
Bases: Array
A vector or matrix array stored as one scalar array per component.
Made by soa from arrays that exist already (from another library,
through from_dlpack or array_from_ptr), or by a constructor given
layout="soa". It is an Array like any other: a kernel reads
a[i] as a vector, stores, updates and atomically updates it, and
only the generated code differs, reading component c of element
i from a.components[c][i] rather than from i * n + c of one
buffer. A kernel compiled for one layout is not used for the other.
With no single buffer, an SOA array is not exported whole (DLPack,
export_memory); its components are.
Source code in packages/tack-core/src/tack/lang/array.py
611 612 613 614 615 616 617 618 619 | |
components
property
¶
components: tuple
The scalar arrays holding each component, in order (row-major for
a matrix): the arrays soa was given, or the ones a constructor made.
numpy ¶
numpy() -> np.ndarray
Copy the values into a new NumPy array, components as the trailing dimensions, exactly as for an interleaved array.
Source code in packages/tack-core/src/tack/lang/array.py
647 648 649 650 651 | |
Copying values¶
tack.copyto ¶
copyto(dst: Array, src)
Copy src into the existing array dst, as np.copyto does.
src is host data -- a NumPy array, a sequence or a scalar -- whose
shape is dst.numpy()'s or broadcasts to it, converted to dst's
dtype; or a Tack array of dst's shape on dst's device, copied
there (src.to_device(dst.device) brings one from another device).
It only writes into Tack arrays. numpy reads one into a new NumPy
array; np.copyto copies between NumPy arrays.
Source code in packages/tack-core/src/tack/lang/array.py
1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 | |
Implicit arrays¶
Arrays that compute each value from its index, in tack.arrays; a kernel
indexes any of them as it does a stored array (a[k], len(a)).
tack.arrays.CountingArray ¶
CountingArray(size, start=0, step=1, dtype=None)
Bases: _Implicit
start + step * index for index < size (VTK's affine array): integers are
dtype, by default i32 unless a value needs i64 (ids past 2**31 - 1),
else f32.
Source code in packages/tack-core/src/tack/arrays.py
252 253 254 255 256 257 258 259 | |
tack.arrays.ConstantArray ¶
ConstantArray(value, size, dtype=None, device=None)
Bases: _Implicit
size copies of value: a scalar, or a vector (a sequence of components),
as VTK's constant arrays of several components. dtype defaults to i32 for
integers and f32 otherwise; the value is kept in a one-element field of it,
so it is exactly that type in the kernel, on device (by default the
default device).
Source code in packages/tack-core/src/tack/arrays.py
233 234 235 236 237 238 239 | |
tack.arrays.CartesianProduct ¶
CartesianProduct(
x, y=(0.0,), z=(0.0,), dtype=tack.f32, device=None
)
Bases: _Implicit
The points of a grid whose lines run along the axes: (x[i], y[j], z[k]) at
index i + nx * (j + ny * k). Only the three axes are stored.
Source code in packages/tack-core/src/tack/arrays.py
276 277 278 279 280 281 282 | |
tack.arrays.UniformCoordinates ¶
UniformCoordinates(
dims,
origin=(0.0, 0.0, 0.0),
spacing=(1.0, 1.0, 1.0),
direction=None,
dtype=tack.f32,
device=None,
)
Bases: _Implicit
The points of an image: origin + direction @ ((i, j, k) * spacing) at index
i + nx * (j + ny * k), as VTK's image data (with its direction matrix) and
Viskores' uniform point coordinates. Nothing per point is stored.
dims has one to three sizes; origin and spacing as many values
or three, the rest taken as 0 and 1, and direction is 3 x 3 or, for a
2D image, 2 x 2.
Source code in packages/tack-core/src/tack/arrays.py
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 | |
tack.arrays.Permutation ¶
Permutation(ids, base)
Bases: _Implicit
base[ids[k]]: base's values in the order ids (integers, explicit or
not) gives, as VTK's indexed array.
Source code in packages/tack-core/src/tack/arrays.py
409 410 411 | |
tack.arrays.Concatenate ¶
Concatenate(*arrays)
The arrays end to end, as VTK's composite array: of one width and type.
Source code in packages/tack-core/src/tack/arrays.py
441 442 443 444 445 446 447 448 449 450 451 452 | |
tack.arrays.View ¶
View(base, start, size)
Bases: _Implicit
base[start + k] for k < size: a stretch of another array.
Source code in packages/tack-core/src/tack/arrays.py
482 483 484 485 | |
tack.arrays.Strided ¶
Strided(values, stride, offset=0, size=None)
Bases: _Implicit
values[offset + stride * k] of a scalar device array, as VTK's strided array:
one column of interleaved data, or every n-th value.
Source code in packages/tack-core/src/tack/arrays.py
460 461 462 463 464 465 466 467 | |
tack.arrays.ExtractComponent ¶
ExtractComponent(base, component)
Bases: _Implicit
Component c of a vector array's values.
Source code in packages/tack-core/src/tack/arrays.py
502 503 504 505 506 507 508 | |
tack.arrays.Components ¶
Components(*arrays)
Two to four scalar arrays as the components of a vector array, as Viskores' composite vector.
Source code in packages/tack-core/src/tack/arrays.py
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 | |
tack.arrays.Function ¶
Function(func, size, dtype=tack.f32, width=None)
Bases: _Implicit
func(k) for k < size: any @tack.func of the index, as VTK's
vtkStdFunctionArray -- but evaluated in the kernel, on every backend. Its
values are dtype (and vectors of width) as the function returns them.
Source code in packages/tack-core/src/tack/arrays.py
591 592 593 | |
tack.arrays.Transform ¶
Transform(func, base, dtype=None, width=None)
Bases: _Implicit
func(base[k]): another array's values through a @tack.func. dtype
and width are the function's results, by default base's.
Source code in packages/tack-core/src/tack/arrays.py
605 606 607 608 609 | |
tack.arrays.Cast ¶
Cast(base, dtype)
base's values converted to dtype, component by component.
Source code in packages/tack-core/src/tack/arrays.py
671 672 673 674 675 | |
tack.arrays.RandomUniform ¶
RandomUniform(size, seed=0, low=0.0, high=1.0)
Bases: _Implicit
size uniform draws in [low, high), value k a pure function of k
and seed (tack.random: the same on every backend, and from
tack.random.np_uniform on the host). Indices repeat past 2**32.
Source code in packages/tack-core/src/tack/arrays.py
684 685 686 687 | |
tack.arrays.RandomNormal ¶
RandomNormal(size, seed=0, mean=0.0, stddev=1.0)
Bases: _Implicit
size normal draws of mean and stddev, as RandomUniform.
Source code in packages/tack-core/src/tack/arrays.py
699 700 701 702 | |
tack.arrays.materialize ¶
materialize(values)
values as a device array: itself if it is one, else computed by a kernel.
Source code in packages/tack-core/src/tack/arrays.py
769 770 771 772 773 774 775 776 777 778 | |
tack.arrays.size_of ¶
size_of(values)
The number of values.
Source code in packages/tack-core/src/tack/arrays.py
732 733 734 | |
tack.arrays.width_of ¶
width_of(values)
Components per value: 3 for 3-vectors, None for scalars.
Source code in packages/tack-core/src/tack/arrays.py
737 738 739 740 741 | |
tack.arrays.dtype_of ¶
dtype_of(values)
The type of each component.
Source code in packages/tack-core/src/tack/arrays.py
744 745 746 | |
tack.arrays.to_host ¶
to_host(values)
The values as a host array: (size,) for scalars, (size, width) for vectors.
Source code in packages/tack-core/src/tack/arrays.py
758 759 760 | |
Element types¶
tack.ElementType
dataclass
¶
ElementType(
scalar_dtype: ScalarType, shape: tuple[int, ...]
)
A fixed-size vector or matrix element, independent of an array's shape.
Components are stored together, without padding, in row-major order.
scalar_dtype is the component type; shape describes one element.
tack.vec
cached
¶
vec(n: int, dtype: ScalarType = f32) -> ElementType
The type of an n-component vector; no storage is allocated.
Source code in packages/tack-core/src/tack/lang/types.py
143 144 145 146 | |
tack.vec2 ¶
vec2(dtype: ScalarType = f32) -> ElementType
A two-component vector element type; in a kernel, tack.vec2(...) builds one.
In kernels it takes 2 scalars, one scalar for every component, or one 2-vector.
Source code in packages/tack-core/src/tack/lang/types.py
158 159 160 161 162 163 164 | |
tack.vec3 ¶
vec3(dtype: ScalarType = f32) -> ElementType
A three-component vector element type; in a kernel, tack.vec3(...) builds one.
In kernels it takes 3 scalars, one scalar for every component, or one 3-vector.
Source code in packages/tack-core/src/tack/lang/types.py
167 168 169 170 171 172 173 | |
tack.vec4 ¶
vec4(dtype: ScalarType = f32) -> ElementType
A four-component vector element type; in a kernel, tack.vec4(...) builds one.
In kernels it takes 4 scalars, one scalar for every component, or one 4-vector.
Source code in packages/tack-core/src/tack/lang/types.py
176 177 178 179 180 181 182 | |
tack.mat
cached
¶
mat(
rows: int, columns: int, dtype: ScalarType = f32
) -> ElementType
The type of a row-major matrix element, up to 4x4.
In a kernel, tack.mat(row, ...) builds a matrix from its rows instead.
Source code in packages/tack-core/src/tack/lang/types.py
149 150 151 152 153 154 155 | |
tack.mat2 ¶
mat2(dtype: ScalarType = f32) -> ElementType
A 2x2 matrix element type; in a kernel, tack.mat2(...) builds one.
In kernels it takes 2 rows (lists of scalars or vectors) or 4 scalars in row-major order.
Source code in packages/tack-core/src/tack/lang/types.py
185 186 187 188 189 190 191 | |
tack.mat3 ¶
mat3(dtype: ScalarType = f32) -> ElementType
A 3x3 matrix element type; in a kernel, tack.mat3(...) builds one.
In kernels it takes 3 rows (lists of scalars or vectors) or 9 scalars in row-major order.
Source code in packages/tack-core/src/tack/lang/types.py
194 195 196 197 198 199 200 | |
tack.mat4 ¶
mat4(dtype: ScalarType = f32) -> ElementType
A 4x4 matrix element type; in a kernel, tack.mat4(...) builds one.
In kernels it takes 4 rows (lists of scalars or vectors) or 16 scalars in row-major order.
Source code in packages/tack-core/src/tack/lang/types.py
203 204 205 206 207 208 209 | |
Kernel values and textures¶
Inside a kernel the element-type names above build values instead:
tack.vec3(x, y, z), tack.vec3(s) (every component s) or
tack.vec3(v) (a 3-vector), and tack.mat2([a, b], [c, d]),
tack.mat2(a, b, c, d) (row-major) or tack.mat(row, ...) for any
shape up to 4×4. See Vectors and Matrices.
tack.identity ¶
identity(n)
The n-by-n identity matrix, as np.identity. Only usable inside a @tack.kernel.
Source code in packages/tack-core/src/tack/__init__.py
137 138 139 | |
tack.select ¶
select(mask, a, b)
a where mask is true, else b, component by component.
Only usable inside a kernel or device function. mask is a scalar
or a comparison vector such as v > 0.0; a and b are scalars
or vectors of one shape, a scalar standing for every component.
Source code in packages/tack-core/src/tack/__init__.py
96 97 98 99 100 101 102 103 | |
tack.texture3d ¶
texture3d(source_array, shape=None, interp='linear')
Create a 3D texture from an array for hardware-accelerated sampling.
The texture copies the array's data now. Later writes to the array --
kernel stores, from_numpy, external or DLPack writes -- are not
visible to it until tex.update() copies the array again. That holds
on every backend, including CPU.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_array
|
A Tack array with f32 dtype holding WHD elements, x varying fastest. |
required | |
shape
|
(W, H, D) tuple for the 3D dimensions. If the array's shape is already 3D, this can be omitted. |
None
|
|
interp
|
Interpolation mode. Only 'linear' (the default) is implemented. |
'linear'
|
Source code in packages/tack-core/src/tack/__init__.py
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | |
tack.lang.array.Texture3D ¶
Texture3D(
source_array: Array,
shape_3d: tuple,
interp: str = "linear",
)
A 3D texture over a snapshot of an array, sampled trilinearly.
Created via tack.texture3d(array, shape=(W, H, D)). In kernels,
tex.sample(u, v, w) samples at normalized [0,1] coordinates using
trilinear interpolation.
The texture copies the array's data when it is created, and again on
update(); writes to the array in between do not reach it. That
holds on every backend. Where the backend samples in hardware the copy
is a texture image (a CUDA or HIP array, an MTLTexture, a Level Zero
image); elsewhere it is a private array the generated code interpolates
in software. Either way the texture owns it, so it is released with the
texture and no other texture can be handed it.
Source code in packages/tack-core/src/tack/lang/array.py
1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 | |
update ¶
update()
Copy the array's current data into the texture.
Sampling after this sees the array as it is now, on every backend.
Source code in packages/tack-core/src/tack/lang/array.py
1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 | |
sample ¶
sample(u, v, w)
Sample at normalized coordinates. Only usable inside @tack.kernel.
Source code in packages/tack-core/src/tack/lang/array.py
1288 1289 1290 | |
Sharing memory¶
tack.array_from_ptr ¶
array_from_ptr(
ptr,
shape,
dtype,
*,
device=None,
writable: bool = False,
) -> Array
Wrap existing device memory as a Tack array without copying.
Use this for interop with external libraries (pycuda, cupy, Catalyst) or when receiving a device pointer from a simulation framework.
The array does NOT own the memory — Tack will not free it. Read-only by default; pass writable=True to enable writes.
Raises ValueError if the pointer's memory space does not match the active backend (e.g. a CPU pointer with the CUDA backend), and TypeError if a CUDA, HIP or Level Zero pointer is not an address.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ptr
|
device pointer (integer) or backend-specific buffer object. - CPU: integer address or numpy array - Metal: MTLBuffer object (from PyObjC) - CUDA/HIP: device pointer as integer - Level Zero: device pointer as integer |
required | |
shape
|
the array's dimensions, counting elements |
required | |
dtype
|
scalar or element type (tack.f32, tack.vec3(), np.int32, ...) |
required | |
device
|
the device whose memory this is; by default the default device |
None
|
|
writable
|
bool
|
if False (default), copyto() and fill() raise errors |
False
|
Returns:
| Type | Description |
|---|---|
Array
|
An Array wrapping the external memory. |
Source code in packages/tack-core/src/tack/lang/array.py
1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 | |
tack.memory_space ¶
memory_space(ptr, *, device=None) -> str
Query the memory space of a pointer.
Uses device (by default the default device) to determine where the
pointer resides.
Returns:
| Type | Description |
|---|---|
str
|
The memory space's name, one of:
|
Source code in packages/tack-core/src/tack/lang/array.py
1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 | |
tack.from_dlpack ¶
from_dlpack(x, *, device=None, copy: bool = False) -> Array
A Tack array from a DLPack capsule or any object with __dlpack__.
Zero-copy: the array shares memory with the source, and the source is
held alive for as long as the array lives. Works for device tensors as
well as host ones. device must be able to wrap the tensor; by
default the array goes on the default device when that can, else on the
tensor's own device, as the array API's from_dlpack does.
Wrap, not address: the Metal backend reads host memory perfectly well
but cannot turn a host pointer into the MTLBuffer an array needs, so
with Metal as the default a host tensor becomes a CPU array, and
device="metal" refuses it and says so. Pass copy=True for an
independent array instead.
Usage
a = tack.from_dlpack(torch_tensor) a = tack.from_dlpack(cupy_array) a = tack.from_dlpack(numpy_array) a = tack.from_dlpack(vtk_capsule)
Source code in packages/tack-core/src/tack/lang/array.py
1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 | |
tack.ExportedMemory
dataclass
¶
ExportedMemory(
backend: str,
size: int,
allocation_size: int,
handle: object,
handle_type: str | None = None,
device_uuid: bytes | None = None,
)
Handle for sharing GPU memory across APIs (e.g. Tack → Dawn/Vulkan).
Contains only plain Python values — no dependency on any GPU library.
The consumer dispatches on handle_type to choose the right import
path. backend alone does not settle it: CUDA exports a POSIX fd on
Linux and a Win32 KMT handle on Windows, and those are imported by
different calls.
tack.lang.dlpack.array_to_dlpack ¶
array_to_dlpack(array, versioned=False)
Create a DLPack capsule from a Tack array.
With versioned, produces a v1.0 DLManagedTensorVersioned in a
'dltensor_versioned' capsule; otherwise the legacy DLManagedTensor in
a 'dltensor' one. Only the versioned form can carry the read-only
flag, so a non-writable array exported the legacy way arrives as
writable -- the legacy protocol simply has no way to say otherwise.
Source code in packages/tack-core/src/tack/lang/dlpack.py
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 | |
tack.lang.dlpack.dlpack_to_array ¶
dlpack_to_array(source, writable=True, device=None)
Wrap a DLPack tensor as a Tack array, without copying.
source may be a capsule or anything implementing __dlpack__ -- a
CuPy array, a PyTorch tensor, a VTK array via
vtkmodules.util.dlpack_support.
The tensor is held for as long as the array lives, so the source may be dropped immediately. The array does not copy, so the two share memory and a write through either is visible to the other.
device is where the array goes. By default it is the default
device when that can wrap the tensor, else the tensor's own device, as
the array API's from_dlpack keeps an array on its device: CUDA
memory imported with the CPU as default is a CUDA array, not a copy.
Raises RuntimeError if device is given and cannot wrap the tensor --
CUDA memory imported onto the CPU device. Metal refuses host tensors for
a different reason, and says which: it can read that memory, it just
cannot make an MTLBuffer out of it.
Source code in packages/tack-core/src/tack/lang/dlpack.py
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 | |
tack.lang.dlpack.dlpack_device ¶
dlpack_device(array)
Return (device_type, device_id) tuple for DLPack protocol.
Source code in packages/tack-core/src/tack/lang/dlpack.py
434 435 436 437 | |
Kernels and device functions¶
tack.kernel ¶
kernel(func)
Decorator that marks a Python function as a GPU kernel.
Source code in packages/tack-core/src/tack/lang/kernel.py
291 292 293 | |
tack.lang.kernel.Kernel ¶
Kernel(func, funcdef=None)
A captured kernel function, ready for AST transformation and compilation.
Source code in packages/tack-core/src/tack/lang/kernel.py
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | |
get_ir ¶
get_ir(
vector_arrays=None,
template_args=None,
texture_arrays=None,
debug=False,
)
Get IR, re-transforming if vector/texture array or template metadata is provided.
debug lowers with bounds checks (see ir_bounds), cached apart.
Source code in packages/tack-core/src/tack/lang/kernel.py
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | |
__get__ ¶
__get__(instance, owner=None)
Bind a kernel defined in a class body to the instance it is called on.
model.step(dt) passes model as the kernel's first argument,
where a @tack.device_class object is a template like any
other. Reached through the class, the kernel is unbound.
Source code in packages/tack-core/src/tack/lang/kernel.py
228 229 230 231 232 233 234 235 236 237 | |
tack.ndindex ¶
ndindex(*args)
Multi-dimensional parallel iteration, as np.ndindex.
Used in kernels: for i, j in tack.ndindex(w, h); each argument is a
size or a (start, end) pair. Cannot be called from Python directly.
Source code in packages/tack-core/src/tack/__init__.py
128 129 130 131 132 133 134 | |
tack.func ¶
func(f)
Decorator that marks a Python function as a device-side inlineable function.
Source code in packages/tack-core/src/tack/lang/func.py
87 88 89 | |
tack.lang.func.Func ¶
Func(func)
A captured device-side function, ready for AST inlining.
Source code in packages/tack-core/src/tack/lang/func.py
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | |
tack.device_class ¶
device_class(cls)
Mark a class as a device class: its objects can be kernel arguments.
Methods decorated with @tack.func are collected and made available for inlining when instances are passed to kernels.
Source code in packages/tack-core/src/tack/lang/device_class.py
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
tack.constant ¶
constant(value, dtype=None)
Declare a named constant that kernels and device functions may read.
DT = tack.constant(0.01) at module (or enclosing function) scope
lets a kernel write DT as if it were the literal 0.01. Without
dtype the constant behaves exactly like that literal: an integer
takes the narrowest of i32, i64 and u64 that holds it, and a float is
weak, taking the precision of what it meets. With dtype it has that
type, as tack.u32(747796405) or tack.f64(0.1) written in the
kernel would: tack.constant(747796405, tack.u32) multiplies in
wrapping u32 arithmetic, and tack.constant(0.1, tack.f64) is the
exact double.
The value is fixed here. A kernel reads the constant its name is bound to when the kernel is first compiled; binding the name to another constant afterwards does not change a compiled kernel, the same rule as for device functions.
Source code in packages/tack-core/src/tack/lang/constant.py
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | |
tack.lang.constant.ArrayConstant ¶
ArrayConstant(components, shape, dtype)
A vector or matrix that kernels may read by name.
SUN = tack.constant((0.5, 0.5, 0.0)) is the vector
tack.vec3(0.5, 0.5, 0.0) in a kernel, and
tack.constant(((1, 0), (0, 1))) the matrix with those rows, each
component a constant with the rules of tack.constant. On the host
it indexes and iterates like a tuple of its rows and converts to a
NumPy array: np.array(SUN, np.float32), SUN[1], len(SUN).
It is not a tuple, so + does not concatenate it by mistake; do
host arithmetic on np.asarray(SUN).
Source code in packages/tack-core/src/tack/lang/constant.py
56 57 58 59 | |
Shader-style helpers¶
tack.math ¶
Shader-style helpers as device functions: fract, mix, clamp...
GLSL, MSL and Taichi's taichi.math share a small vocabulary that
graphics and simulation code reaches for constantly, and that Python's
math lacks. These are ordinary @tack.func device functions, so
they inline into kernels, and because Tack's arithmetic and builtins apply
to each component of a vector, each one works on scalars and on vectors
(and matrices) alike, as the GLSL functions do::
from tack import math as tm
@tack.kernel
def shade(uv, out, n):
for i in range(n):
st = tm.fract(uv[i] * 8.0) - 0.5 # a 2-vector
edge = tm.smoothstep(0.0, 0.05, st.x) # a scalar
out[i] = tm.mix(DARK, LIGHT, edge) # 3-vectors
The definitions are GLSL's, so mix(x, y, a) is x * (1 - a) + y * a,
smoothstep is the Hermite polynomial on the clamped ratio, and step
and sign select per component through tack.select. They are
only callable from kernels and device functions; a NumPy reference writes
the same one-liners with np.floor, np.clip and friends.
fract ¶
fract(x)
The fractional part, x - floor(x), in [0, 1).
Source code in packages/tack-core/src/tack/math.py
29 30 31 32 | |
mix ¶
mix(x, y, a)
The linear blend x * (1 - a) + y * a; a may be a scalar or match x.
Source code in packages/tack-core/src/tack/math.py
35 36 37 38 | |
clamp ¶
clamp(x, lo, hi)
x limited to [lo, hi], component by component.
Source code in packages/tack-core/src/tack/math.py
41 42 43 44 | |
saturate ¶
saturate(x)
x limited to [0, 1].
Source code in packages/tack-core/src/tack/math.py
47 48 49 50 | |
smoothstep ¶
smoothstep(edge0, edge1, x)
Hermite interpolation: 0 below edge0, 1 above edge1, smooth between.
edge0 greater than edge1 reverses the step, as in GLSL.
Source code in packages/tack-core/src/tack/math.py
53 54 55 56 57 58 59 60 | |
step ¶
step(edge, x)
0.0 where x < edge, else 1.0, component by component.
Source code in packages/tack-core/src/tack/math.py
63 64 65 66 | |
sign ¶
sign(x)
-1.0, 0.0 or 1.0 by the sign of x, component by component.
Source code in packages/tack-core/src/tack/math.py
69 70 71 72 | |
length ¶
length(v)
The Euclidean length of a vector.
Source code in packages/tack-core/src/tack/math.py
75 76 77 78 | |
distance ¶
distance(a, b)
The Euclidean distance between two points.
Source code in packages/tack-core/src/tack/math.py
81 82 83 84 | |
normalize ¶
normalize(v)
v scaled to unit length; a zero vector gives NaNs, as in GLSL.
Source code in packages/tack-core/src/tack/math.py
87 88 89 90 | |
Random numbers¶
tack.random ¶
Random numbers in kernels, from a counter-based generator with explicit state.
Nothing here is hidden: a kernel seeds a u32 state from the element it
works on and a stream number, and every draw returns the value and the next
state. The sequence is a pure function of those integers, so the same
program draws the same numbers on every backend and for any thread order,
and a NumPy reference that calls the np_ mirrors below draws exactly
the same ones::
from tack import random
@tack.kernel
def scatter(pos, n, frame):
for i in range(n):
state = random.seed(i, frame) # element i of this frame
u, state = random.uniform(state) # f32 in [0, 1)
z, state = random.normal(state) # standard normal
d, state = random.direction3(state) # a unit 3-vector
pos[i] = [u, z, d.x]
The generator is PCG's output permutation (RXS-M-XS on 32 bits) over a
linear congruential step, in wrapping u32 arithmetic. uniform is
the state's top 24 bits scaled, so it is exact and identical everywhere;
normal and the directions go through log, sqrt, cos and
sin, so they agree across backends to those functions' rounding.
Taichi's ti.random() keeps hidden per-thread state instead; its results
depend on the thread schedule and cannot be reproduced by a reference.
advance ¶
advance(state)
The state after state; also the 32 random bits it stands for.
Source code in packages/tack-core/src/tack/random.py
47 48 49 50 51 52 | |
seed ¶
seed(index, stream)
A state for element index of stream (a frame, a pass, a purpose).
Source code in packages/tack-core/src/tack/random.py
55 56 57 58 | |
uniform ¶
uniform(state)
An f32 in [0, 1) and the next state.
Source code in packages/tack-core/src/tack/random.py
61 62 63 64 | |
normal ¶
normal(state)
A standard normal draw (Box-Muller) and the next state; uses two states.
Source code in packages/tack-core/src/tack/random.py
67 68 69 70 71 72 73 | |
direction2 ¶
direction2(state)
A unit 2-vector with uniformly distributed direction, and the next state.
Source code in packages/tack-core/src/tack/random.py
76 77 78 79 80 | |
direction3 ¶
direction3(state)
A unit 3-vector uniformly distributed on the sphere, and the next state; two states.
Source code in packages/tack-core/src/tack/random.py
83 84 85 86 87 88 89 90 | |
tack.lang.source_validation.UnsupportedSyntaxError ¶
Bases: NotImplementedError
A source construct outside Tack's kernel language.
Inspection¶
tack.inspect ¶
inspect(kernel, *args, mode='source')
Return generated code for a kernel without executing it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kernel
|
A @tack.kernel decorated function. |
required | |
*args
|
The arguments the kernel would be called with. |
()
|
|
mode
|
What to return: "ir" — Tack intermediate representation "source" — backend-specific source code (MSL, CUDA C, LLVM IR, etc.) "optimized" — LLVM IR after O3; CPU only, since the GPU backends' vendor compilers optimize past anything Tack sees |
'source'
|
Returns:
| Type | Description |
|---|---|
|
The generated code as a string. |
Source code in packages/tack-core/src/tack/lang/inspect_kernel.py
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | |
Reductions, statistics and scans¶
How these behave on each backend — dtypes, accumulators, NaN and empty
inputs, repeatability — is in the User's Guide chapter
Reductions and Scans. Array
reductions are the methods Array.sum, min, max and mean, above.
Statistics¶
Import these from tack.algorithms; they are defined in
tack.algorithms.stats.
tack.algorithms.stats.var ¶
var(data, n=None)
Population variance of an array: Σ(x - mean)² / n.
Two passes over the first n elements: the mean by Array.sum(), then
the squared differences, formed in the data's precision (f64 for f64
data, otherwise f32) and summed by Array.sum(). Returns a Python
float; an empty range gives NaN, as Array.mean() does.
Source code in packages/tack-core/src/tack/algorithms/stats.py
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | |
tack.algorithms.stats.std ¶
std(data, n=None)
Population standard deviation of an array.
Source code in packages/tack-core/src/tack/algorithms/stats.py
159 160 161 162 163 | |
tack.algorithms.stats.norm ¶
norm(data, ord=2, n=None)
Vector norm of an array, in the data's precision (f64 for f64 data, otherwise f32), as a Python float.
ord=1: L1 norm (sum of absolute values) ord=2: L2 norm (Euclidean) ord=inf: L-infinity (max absolute value)
Any other ord raises ValueError. An empty range gives 0.0.
Source code in packages/tack-core/src/tack/algorithms/stats.py
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | |
tack.algorithms.stats.absmax ¶
absmax(data, n=None)
Maximum absolute value of an array, as a Python float; 0.0 when empty.
Source code in packages/tack-core/src/tack/algorithms/stats.py
197 198 199 200 | |
tack.algorithms.stats.count_nonzero ¶
count_nonzero(data, n=None)
The number of nonzero elements of an array, as a Python int: its 0/1 flags scanned into i64 on the device, so the count is exact past 2^31 and only the total comes back to the host.
Source code in packages/tack-core/src/tack/algorithms/stats.py
203 204 205 206 207 208 209 210 211 212 213 214 215 | |
tack.algorithms.stats.dot ¶
dot(a, b, n=None)
Dot product of two arrays, Σa[i]·b[i], in their precision (f64 if either is f64, otherwise f32), as a Python float; 0.0 when empty.
Source code in packages/tack-core/src/tack/algorithms/stats.py
218 219 220 221 222 223 224 225 | |
tack.algorithms.stats.histogram ¶
histogram(data, bins=10, range=None, n=None)
Compute a histogram of array values with atomic bin counts.
Values below the range are counted in the first bin and values above it in the last, unlike numpy.histogram, which drops them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
input array of any dtype |
required | |
bins
|
number of bins (at least 1) |
10
|
|
range
|
(min, max) tuple. If None, uses the minimum and maximum of the first n elements, and n must then be at least 1. |
None
|
|
n
|
number of elements (default: data.size) |
None
|
Returns:
| Type | Description |
|---|---|
|
(counts, bin_edges) where counts is a Tack array of i32, |
|
|
bin_edges is a numpy array of (bins + 1) float64 edges. |
Source code in packages/tack-core/src/tack/algorithms/stats.py
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 | |
Scans¶
Import these from tack.algorithms; they are defined in
tack.algorithms.scan.
tack.algorithms.scan.exclusive_scan ¶
exclusive_scan(
input_array, output_array, n, total=True, close=False
)
Compute exclusive prefix sum on the inputs' device.
output[i] = sum(input[0..i-1]), output[0] = 0.
The scan runs in the output array's dtype; input values convert to it on the copy, so any dtype the backend allocates works.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_array
|
array with input values. |
required | |
output_array
|
array for the output offsets. |
required | |
n
|
number of elements, at most either array's size. Zero writes nothing and returns 0. |
required | |
total
|
whether to return the total. Reading it waits for the
device (on Metal, for every launch queued so far); a caller
that needs only the offsets passes |
True
|
|
close
|
also write the total at |
False
|
Returns:
| Type | Description |
|---|---|
|
The total of all input elements in the output's dtype, as a Python |
|
|
int or float; |
Source code in packages/tack-core/src/tack/algorithms/scan.py
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | |
tack.algorithms.scan.inclusive_scan ¶
inclusive_scan(input_array, output_array, n, total=True)
Compute inclusive prefix sum on the inputs' device.
output[i] = sum(input[0..i]).
The scan runs in the output array's dtype; input values convert to it on the copy, so any dtype the backend allocates works.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_array
|
array with input values. |
required | |
output_array
|
array for the output sums. |
required | |
n
|
number of elements, at most either array's size. Zero writes nothing and returns 0. |
required | |
total
|
whether to return the total; see |
True
|
Returns:
| Type | Description |
|---|---|
|
The total of all input elements in the output's dtype, as a Python |
|
|
int or float; |
Source code in packages/tack-core/src/tack/algorithms/scan.py
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 | |
Sorting and segmented reductions¶
Import these from tack.algorithms; they are defined in
tack.algorithms.sort.
tack.algorithms.sort.argsort ¶
argsort(keys, n=None, index_dtype=None)
Stable permutation that sorts the first n keys ascending.
keys is an i32, u32, i64 or u64 array. Returns a
new array perm of n elements with keys[perm[0]] <=
keys[perm[1]] <= ...; equal keys keep their original order. n
defaults to the whole array and may be zero. perm is i32, or
i64 past 2**31 - 1 elements or when index_dtype asks.
Source code in packages/tack-core/src/tack/algorithms/sort.py
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 | |
tack.algorithms.sort.sort_by_key ¶
sort_by_key(keys, values=None, n=None)
Sort the first n keys ascending, carrying values along.
Returns (sorted_keys, sorted_values) as new arrays of n
elements; sorted_values is None when no values are given. The
sort is stable, so equal keys keep the order of their values. See
argsort for the key dtypes.
Source code in packages/tack-core/src/tack/algorithms/sort.py
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 | |
tack.algorithms.sort.gather ¶
gather(src, indices, n=None)
New array out with out[i] = src[indices[i]] for i < n.
indices is an integer array; n defaults to its size. The
output has src's dtype. Indices must lie in [0, src.size).
Source code in packages/tack-core/src/tack/algorithms/sort.py
529 530 531 532 533 534 535 536 537 538 539 540 | |
tack.algorithms.sort.unique ¶
unique(sorted_keys, n=None)
The distinct keys of a sorted array and how often each occurs.
sorted_keys must have equal keys adjacent (sorted, or grouped).
Returns (keys, counts): keys is a new array of the same dtype
holding each distinct key once, in order of first appearance, and
counts the run lengths, an index array (see argsort). Works for any
dtype that supports !=.
Source code in packages/tack-core/src/tack/algorithms/sort.py
578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 | |
tack.algorithms.sort.reduce_by_key ¶
reduce_by_key(sorted_keys, values, n=None, op='sum')
Reduce the values of each run of equal keys to one value.
sorted_keys must have equal keys adjacent, as for unique;
values is any array with at least n elements. op is
"sum", "min" or "max". Returns (keys, reduced): the
distinct keys and a new array of values' dtype with one entry per
key. Each run is reduced serially in element order, so the result is
exact for integers and reproducible for floats; integer sums wrap at
the values' width.
Source code in packages/tack-core/src/tack/algorithms/sort.py
598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 | |
Copy and fill¶
copy and fill_value are re-exported from tack.algorithms;
copy_with_offset is imported from tack.algorithms.copy.
tack.algorithms.copy.copy ¶
copy(src, dst, n)
Copy n elements from src array to dst array with a kernel.
Source code in packages/tack-core/src/tack/algorithms/copy.py
34 35 36 | |
tack.algorithms.copy.fill_value ¶
fill_value(dst, value, n)
Fill n elements of dst array with a scalar value with a kernel.
Source code in packages/tack-core/src/tack/algorithms/copy.py
44 45 46 | |
tack.algorithms.copy.copy_with_offset ¶
copy_with_offset(src, dst, dst_offset, n)
Copy n elements from src into dst starting at dst_offset with a kernel.
Source code in packages/tack-core/src/tack/algorithms/copy.py
39 40 41 | |
Block reductions¶
Usable only inside a @tack.kernel, on backends with workgroups (not CPU).
tack.block_sum ¶
block_sum(value)
Sum a value across all threads in the workgroup. Only usable inside a @tack.kernel.
Source code in packages/tack-core/src/tack/__init__.py
141 142 143 | |
tack.block_min ¶
block_min(value)
Min of a value across all threads in the workgroup. Only usable inside a @tack.kernel.
Source code in packages/tack-core/src/tack/__init__.py
149 150 151 | |
tack.block_max ¶
block_max(value)
Max of a value across all threads in the workgroup. Only usable inside a @tack.kernel.
Source code in packages/tack-core/src/tack/__init__.py
145 146 147 | |
Communication between blocks¶
tack.communication moves data between blocks -- units of work with global
ids, each on a device, such as the pieces of a partitioned dataset -- and
combines values over them. World() is this process; its blocks may be on any
of its devices. The Visualization Guide's
Communicate Between Blocks builds an
algorithm on it step by step, and the
Runtime API contract
says what it guarantees.
tack.communication.World ¶
World(comm=None, *, gpu_aware=False)
The processes blocks live in, and how messages move between them.
World() is this process alone (rank 0 of 1): its blocks may be on any
of its devices, and a message reaches its destination as copies of its
arrays on the destination block's device -- the arrays themselves when
that device can already address them.
World(comm) spans the ranks of comm, an mpi4py communicator (or
anything with its Dup, Get_rank, Get_size, allgather,
alltoall, Isend and Irecv). Tack communicates on a duplicate,
so its messages never meet the application's; make one World for a
run. Messages between ranks are staged through host memory, or with
gpu_aware=True passed to MPI as the arrays themselves (through DLPack),
for an MPI that reads and writes device memory. Where a backend says the
MPI may not take an array's memory as it is (the stream-ordered pools of
CUDA and HIP, which Cray MPICH cannot export and MVAPICH-Plus's UCX takes
for host memory), the array goes as a device
copy in memory the MPI does take, and arrives in such memory. Tack imports
no MPI module: the caller makes the communicator.
A GPU-aware MPI uses the device that is current when MPI starts. Make the
rank's device first, with tack.device("cuda:N"), and only then import
mpi4py.MPI, which starts MPI. A rank that moves to another device
afterwards loses Cray MPICH's CUDA IPC between ranks on a node (7 GB/s a
rank against 22 on Perlmutter's A100s), or fails across nodes. CUDA IPC
also needs every rank to see its node's GPUs (Slurm's --gpu-bind=none,
not --gpus-per-task=1), each rank taking its own by local rank.
Source code in packages/tack-core/src/tack/communication/_world.py
46 47 48 49 50 51 52 53 54 55 56 | |
tack.communication.Blocks ¶
Blocks(devices, world=None)
This rank's blocks, one per entry of devices (a tack.Device or a spec
such as "cuda:1" each), in a World (World(), this process alone, if
not given).
Global ids run over every rank's blocks, contiguous by rank: rank 0's first,
then rank 1's, as VTK numbers a partitioned dataset's pieces across ranks.
local is the range of this rank's.
Source code in packages/tack-core/src/tack/communication/_blocks.py
30 31 32 33 34 35 36 37 | |
rank_of ¶
rank_of(gid)
The rank holding block gid, any block's: global ids run over rank 0's
blocks, then rank 1's, and so on.
Source code in packages/tack-core/src/tack/communication/_blocks.py
42 43 44 45 46 | |
device ¶
device(gid)
The tack.Device of block gid, one of this rank's: where its arrays
are, sent and received. ValueError for another rank's block.
Source code in packages/tack-core/src/tack/communication/_blocks.py
48 49 50 51 | |
exchange ¶
exchange(outgoing)
Send messages between blocks and return what each block received.
outgoing is {source: {destination: message}}: for blocks on this
rank, a message, a dict of names to Tack arrays on the source block's
device, for any block. The result is {destination: {source: message}}
for every block on this rank, in order of global id, with an empty dict
where nothing came; each message's arrays are on the destination block's
device.
A message's arrays belong to the receiver afterwards: where the destination's device addresses the source's memory, they are the sent arrays themselves, so a sender must not write to what it sent.
Source code in packages/tack-core/src/tack/communication/_blocks.py
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | |
all_gather ¶
all_gather(values)
Every block's value, a list indexed by global id, on every rank.
values is {gid: value} for every block on this rank; the values
are small Python objects (over MPI they are pickled).
Source code in packages/tack-core/src/tack/communication/_blocks.py
104 105 106 107 108 109 110 111 112 113 | |
all_reduce ¶
all_reduce(values, op='sum')
values ({gid: number or NumPy array}, every block on this rank)
combined over every block by op ("sum", "min" or "max").
Values combine in order of global id, the same order whichever rank holds which block, so a floating-point sum has the same bits however the blocks are assigned.
Source code in packages/tack-core/src/tack/communication/_blocks.py
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | |
exclusive_scan ¶
exclusive_scan(counts)
Each block's offset, the sum of the counts of the blocks before it:
({gid: offset}, total) for the blocks on this rank, from counts
({gid: int}, every block on this rank) -- where each block's share of
a global numbering starts.
Source code in packages/tack-core/src/tack/communication/_blocks.py
135 136 137 138 139 140 141 142 143 144 145 146 | |
link_by_bounds ¶
link_by_bounds(boxes, margin=0.0)
Each block's neighbours: {gid: [other gids]} for the blocks on this
rank, every block whose box overlaps or touches its box grown by
margin on every side.
boxes is {gid: (lower, upper)} for every block on this rank, two
corners of equal length. Blocks that share a face touch, so a margin of
0 finds the neighbours of a decomposition into abutting pieces; a margin
of r finds every block within r.
Source code in packages/tack-core/src/tack/communication/_blocks.py
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | |
tack.communication.Plan ¶
Plan(blocks, sends, receives)
Which elements each block sends to which, and where the receiver puts them: an exchange kept for reuse, as ghost values are refreshed from their owners.
sends is {gid: {destination: indices}} and receives
{gid: {source: indices}}, for blocks on this rank of blocks: the
element indices a block reads for each destination, and those it writes from
each source, in matching order. Indices are integer Tack arrays on the
block's device, or anything numpy.asarray makes integers of. Every send
must have a receive of its length on the other block, and the other way
round; this is checked across ranks when the plan is made. A block may send
to itself, as a piece in a periodic box is its own neighbour: what it sends
is gathered before anything is written.
Source code in packages/tack-core/src/tack/communication/_plan.py
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | |
run ¶
run(values)
Copy each sent element of values into its place on the receiving block.
values is {gid: array} -- or {gid: {name: array}} to move
several arrays in one exchange -- with an entry for every block on this
rank that sends or receives; each array is on its block's device, and an
array and its counterparts on other blocks have one element type. Received
elements are written in place; the others are left as they were.
Source code in packages/tack-core/src/tack/communication/_plan.py
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | |
tack.communication.Routing ¶
Routing(blocks, positions, boxes, holds)
Where each point goes: the lowest block that holds it.
positions is {gid: points} for every block on this rank, an
(n, d) host array of coordinates each (n may be 0); boxes
{gid: (lower, upper)} for every block on this rank, None for a block
that holds nothing. A point is asked of every block whose box holds it (with
a relative slack of 1e-6), and holds(gid, points) -- called by the block
asked, with the points it was asked about as an (m, d) float64 host
array -- says how well it holds each: 0 not at all, or a rank (True is
1). The point goes to the block of the highest rank, the lowest such block
on a tie: a locator that holds a point beats one only within its tolerance. Coordinates travel as their f64 bits in u64 arrays, which
every device stores, so a block decides on exactly the points it was given.
destination is {gid: array}, each point's block, or -1 where no block
holds it. send moves values with the points to their destinations, and
reply returns values from there to where the points came from.
Source code in packages/tack-core/src/tack/communication/_route.py
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | |
send ¶
send(values=None)
Move values ({gid: {name: array}}, an element per point, on the
block's device; may be omitted) with the points to their destinations.
Returns {gid: (positions, {name: array})} for every block on this
rank: what arrived there, the points as an (m, d) host array and the
values on the block's device, in order of origin block and then of the
points there. reply answers in that order.
Source code in packages/tack-core/src/tack/communication/_route.py
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | |
reply ¶
reply(values, like)
Send values -- {gid: {name: array}}, an element for each point
that arrived at the block (send's order) -- back to where the points
came from. Returns {gid: {name: array}} for the blocks on this rank,
an element per point of the block's positions, zero where the point
had no destination. like is {name: element type}, for blocks
none of whose points were taken.
Source code in packages/tack-core/src/tack/communication/_route.py
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | |
tack.communication.sort_by_key ¶
sort_by_key(blocks, keys, values=None, samples=16)
Sort the keys of every block, carrying values along: block 0 ends with the
smallest keys, the last block with the largest, each block's ascending.
keys is {gid: array} and values {gid: {name: array}}, for every
block on this rank: one-dimensional i32, u32, i64 or u64 keys of
one type on every block, and values of the keys' length, each name of one
element type everywhere, all on the block's device. Returns (keys,
values) in the same form (values None if not given), on the same
devices.
Every copy of a key ends on one block, so equal keys can be grouped where
they land; heavily repeated keys can leave blocks uneven. The sort is
stable: equal keys keep the order of their blocks, then their order within
a block. A sample sort, as DIY's: each block sorts its own keys and sends
samples of them, weighted by its count, from which every block finds the
same count - 1 splitters; each block sends each range to its block, and
sorts what arrives.
Source code in packages/tack-core/src/tack/communication/_sort.py
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | |
VTK interop¶
tack.interop.vtk (in the tack-vis package) provides vtk_to_array,
array_to_vtk and init_level_zero, and vtk_to_dataset/dataset_to_vtk.
Their contracts are in Runtime API: VTK,
and their entries, with MFEM's and Warp's, in the Visualization Guide's
Data Model. The
tack.dataset algorithms are in
Algorithms.
The backend contract¶
Every backend subclasses this. It exists so callers can ask what a backend
supports instead of probing for methods — the distinction that
hasattr(backend, 'reduce_array') got wrong, since it answers "is this
defined" rather than "is this supported".
tack.runtime.backend.Backend ¶
Base class for Tack compute backends.
Subclasses declare their capabilities as class attributes (or set them
in __init__ when they are device-dependent, as Level Zero does for
f64) and implement the methods below.
supported_atomic_dtypes
property
¶
supported_atomic_dtypes: frozenset[ScalarType]
Global array types supporting add/min/max on this target.
supports_f64
property
¶
supports_f64: bool
Whether kernels can be dispatched with f64 arrays.
Derived, so it cannot disagree with supported_dtypes.
memory_domain
property
¶
memory_domain: tuple
What an array's memory is valid in: two backends with one domain can use each other's arrays (two CPU backends with different thread counts, say). CUDA and Level Zero add their context, since a pointer belongs to one.
device_count
classmethod
¶
device_count() -> int
How many devices of this type the machine has.
Source code in packages/tack-core/src/tack/runtime/backend.py
63 64 65 66 | |
current_ordinal
classmethod
¶
current_ordinal() -> int
The device a bare spec names ("cuda"): the one the calling
thread already uses, as another library may have chosen it.
Source code in packages/tack-core/src/tack/runtime/backend.py
68 69 70 71 72 | |
allocate_array ¶
allocate_array(
dtype: ScalarType,
shape: tuple[int, ...],
exportable: bool = False,
)
Allocate backend storage for an array, returning a DeviceBuffer.
Source code in packages/tack-core/src/tack/runtime/backend.py
145 146 147 148 | |
wrap_ptr ¶
wrap_ptr(ptr, dtype: ScalarType, shape: tuple[int, ...])
Wrap existing memory as a DeviceBuffer without copying.
Source code in packages/tack-core/src/tack/runtime/backend.py
150 151 152 | |
addresses_dlpack ¶
addresses_dlpack(device_type: int, device_id: int) -> bool
Whether this backend can wrap a DLPack tensor on (device_type,
device_id) without copying, so from_dlpack may put it here. By
default by the backend's name (lang/dlpack.py's
_DEVICE_BACKENDS) and, for a type with several devices, its ordinal.
The Warp backend answers for the one device it drives.
Source code in packages/tack-core/src/tack/runtime/backend.py
154 155 156 157 158 159 160 161 162 | |
mpi_direct ¶
mpi_direct(buffer) -> bool
Whether a GPU-aware MPI may read and write buffer's memory as it is
(tack.communication.World(comm, gpu_aware=True)). True by default;
a backend says no for memory such an MPI does not know -- the
stream-ordered pools of CUDA and HIP, which Cray MPICH cannot export
and MVAPICH-Plus's UCX takes for host memory -- and the exchange goes
through allocate_mpi_array's memory instead.
Source code in packages/tack-core/src/tack/runtime/backend.py
164 165 166 167 168 169 170 171 | |
allocate_mpi_array ¶
allocate_mpi_array(
dtype: ScalarType, shape: tuple[int, ...]
)
Storage a GPU-aware MPI reads and writes directly (mpi_direct): by
default what allocate_array gives.
Source code in packages/tack-core/src/tack/runtime/backend.py
173 174 175 176 | |
fill_array ¶
fill_array(array, value) -> bool
Fill an array with one scalar the backend's own way; false to let
Array.fill fill the buffer in place.
The CPU backend runs a large fill as a kernel through its dispatch,
so the threads first-touch the pages. GPU buffers fill on the
device through DeviceBuffer.fill and need nothing here.
Source code in packages/tack-core/src/tack/runtime/backend.py
178 179 180 181 182 183 184 185 186 | |
execute ¶
execute(kernel, args, kwargs)
Compile (or reuse) and run a kernel with these arguments.
Source code in packages/tack-core/src/tack/runtime/backend.py
188 189 190 | |
release_memory ¶
release_memory()
Free memory kept for reuse (a cache of dropped arrays' buffers), so an
allocation that ran out of memory can try again (lang.array._allocate).
Nothing by default.
Source code in packages/tack-core/src/tack/runtime/backend.py
194 195 196 197 | |
synchronize ¶
synchronize()
Wait until every kernel launched so far has finished.
Backends whose launches complete before execute returns need not
override it. Metal queues launches and waits only when the host
touches array memory, so tack.sync() is how a caller timing
kernels, or handing memory to a library Tack cannot see, waits.
Source code in packages/tack-core/src/tack/runtime/backend.py
199 200 201 202 203 204 205 206 | |
async_marker ¶
async_marker()
A mark after every launch queued so far that another thread can
wait on -- an object with a blocking wait(), which raises a
launch's error as synchronize would -- or None.
tack.sync_async waits on it in a worker thread so an asyncio
loop keeps running. None, the default, means there is no such mark,
and the coroutine calls synchronize inline: right for a backend
whose launches complete before returning, and still correct, if
blocking, for one that queues launches and has not added a mark.
Source code in packages/tack-core/src/tack/runtime/backend.py
208 209 210 211 212 213 214 215 216 217 218 219 | |
memory_space ¶
memory_space(ptr) -> str
Classify where an integer pointer lives.
The default says host memory, which is correct for backends whose
allocations are CPU-addressable. Backends with distinct device
memory override this and list the results in
device_memory_spaces.
Source code in packages/tack-core/src/tack/runtime/backend.py
221 222 223 224 225 226 227 228 229 | |
cache_target ¶
cache_target()
What compiled code for this backend depends on besides the kernel, as a
string (the device, the compiler's version), or None if this backend
does not store compiled variants on disk. See disk_cache.
Source code in packages/tack-core/src/tack/runtime/backend.py
241 242 243 244 | |
variant_artifact ¶
variant_artifact(payload)
What of a compiled variant's payload to store on disk: picklable data
from which load_variant rebuilds it in another process.
Source code in packages/tack-core/src/tack/runtime/backend.py
246 247 248 249 | |
load_variant ¶
load_variant(ir_func, artifact, effective_args)
The payload of a variant, from what variant_artifact stored.
effective_args are the call's arguments, for what the payload
holds per process rather than on disk: the GPU backends' scalar
pack buffers. CPU and Metal ignore them.
Source code in packages/tack-core/src/tack/runtime/backend.py
251 252 253 254 255 256 257 | |
reduce_array ¶
reduce_array(array, op: str) -> float
Reduce an array on the device. Only called when
supports_device_reductions is True.
Source code in packages/tack-core/src/tack/runtime/backend.py
259 260 261 262 263 | |
texture_in_hardware ¶
texture_in_hardware(shape_3d) -> bool
Whether a Texture3D of this extent is sampled by texture units.
False means the texture keeps a private array and the generated
code interpolates it in software, which is what CPU does. Texture3D
asks when choosing its storage, and backends whose answer varies by
device or extent ask again when preparing a variant, so a texture
always holds the storage its compiled kernel binds.
Source code in packages/tack-core/src/tack/runtime/backend.py
265 266 267 268 269 270 271 272 273 274 | |
create_texture_image ¶
create_texture_image(shape_3d)
Allocate a single-channel f32 3D texture image.
Only called when texture_in_hardware() is True. The result is
owned by one Texture3D: it exposes upload(array), which copies
the array into it, and frees the device objects when collected.
Source code in packages/tack-core/src/tack/runtime/backend.py
276 277 278 279 280 281 282 283 284 | |