Common tensors¶
ppy.Tensor[dtype, shape] describes a tensor independently of the library
that owns its storage. PPy carries its element type and dimensions through
type checking, function calls, and canonical IR.
import ppy
def identity(x: ppy.Tensor[ppy.bf16, (3840,)]) -> ppy.Tensor[ppy.bf16, (3840,)]:
return x
The annotation does not allocate memory, wrap an array, or import a GPU framework.
Element types and shapes¶
ppy.bf16 names bfloat16. It stays distinct from ppy.f16, which names IEEE
float16, even though both occupy 16 bits.
The shape is a tuple. The comma in (3840,) makes it a one-dimensional
shape. Dimensions can be nonnegative integers or symbolic names, and ()
describes a scalar tensor.
The existing Shape and DType refinements offer the same contract:
from typing import Annotated
import ppy
Matrix = ppy.Tensor[ppy.f32, ("N", 4)]
Scalar = ppy.Tensor[ppy.bf16, ()]
Vector = Annotated[ppy.Tensor, ppy.DType("bfloat16"), ppy.Shape(3840)]
A bare ppy.Tensor leaves dtype and shape unspecified. Canonical lowering
requires those facts to be known from the annotation or analysis.
Checking a value at run time¶
ppy.check[TensorType](value) validates the object's shape and dtype
metadata without importing its framework. Repeated symbolic names within one
shape must have the same size.
Ownership markers describe a relationship between caller and callee, so
ppy.check cannot establish Mut or Owned from one object's metadata.
Writing through an argument¶
ppy.Mut[ppy.Tensor[...]] permits writes for the duration of a call. A
plugin declares which arguments it reads, writes, or retains.
- A borrowed argument may not be kept after the call.
- A read-only borrow cannot be passed to an operation that writes through it.
For a device plugin implementing the common tensor and borrowing APIs, a broadcast into preallocated output can have this form:
import ppy
import ppy_furiosa as fx
def broadcast(
x: ppy.Tensor[ppy.bf16, (3840,)],
out: ppy.Mut[ppy.Tensor[ppy.bf16, (256, 3840)]],
) -> None:
value = fx.broadcast(x, copies=256)
fx.store(out, value)
The plugin must describe store as a write and declare its output argument
as a mutable borrow. PPy preserves the write even though the call returns no
value.
Note
An existing plugin needs to adopt these contracts before accepting this example. Installing PPy alone does not update a device plugin.
Storage and backends¶
Without a selected device backend, a common tensor uses the shared tensor IR type. A backend extension can choose a physical representation from the same analyzed type and facts. Address spaces, device memory placement, and device-specific execution settings belong to that extension.
Enabling several plugins does not let registration order choose the storage of a common tensor. The selected backend determines which extension can provide its representation. If multiple extensions claim it for that backend, compilation reports the ambiguity.
CPU paths¶
Common tensor annotations do not create a CPU native ABI. A CPU path that cannot represent a tensor keeps its Python fallback. An explicit external backend request reports a located lowering error when its representation or operation is unsupported.
See the plugin API for argument contracts and type lowering hooks.