quax.experimental¤
Experimental APIs, which may change or be removed without notice.
quax.experimental.hijax¤
Plumbing for building a quax.Value around a
hijax type.
Quax and hijax covers when to reach for it,
the how-to covers how, and
quax.examples.hijax is a worked type built on this module.
Doubly experimental: jax.experimental.hijax is itself experimental and has
renamed things in most recent releases. This module resolves those names by
probing, so a rename upstream is absorbed here rather than in your code.
| Minimum JAX | 0.10.2 (quax.experimental.hijax.HIJAX_FLOOR) |
| Import failure | ImportError naming the missing hijax objects |
| Re-exported hijax names | HiPrim, HiType, MappingSpec, ShapedArray, instantiate_zeros, register_hitype |
HiPrim is VJPHiPrimitive up to JAX 0.11.1 and HiPrim after it; importing
it from here means never spelling either name.
quax.experimental.hijax.HiValue
¤
A quax.ArrayValue whose single pytree leaf is a hijax value.
Subclass this, then wire up primitives with
register_rules. The subclass
normally adds a friendlier __init__ and some read-only properties, and
nothing else:
class Unitful(HiValue):
def __init__(self, array, units=()):
self.leaf = array if isinstance(array, UnitfulArray) else wrap(array, units)
@property
def units(self):
return jax.typeof(self.leaf).units
Two constraints this class exists to enforce, both of which are easy to get wrong by hand:
aval()reports a plainShapedArraybuilt from the leaf's hijax type, never the hijax type itself.jax.Array'sisinstancecheck reads a tracer's aval and everyjnpfunction gates on it, so aHiTypeaval would makejnpreject your tracers -- defeating the point of using Quax.- The leaf must not be re-wrapped. A hijax tracer forwards
.shapeand.dtypefrom its type, so it passesequinox.is_array_like; an__init__that tests for array-likeness will wrap a traced value twice. Test for your hi value class instead.
__init__(leaf: typing.Any) -> None
¤
Arguments:
leaf: the hijax value to wrap.
aval() -> ShapedArray
¤
The shape and dtype of the leaf's hijax type, as a ShapedArray.
materialise() -> typing.Any
¤
Refuse: materialising would discard whatever the hi type carries.
A primitive with no registered rule is therefore an error rather than a silent downgrade to a plain array. Override if your type would rather fall back than fail.
quax.experimental.hijax.register_rules(cls: type[quax.experimental.hijax.HiValue], rules: collections.abc.Mapping[jax._src.core.Primitive, collections.abc.Callable[..., typing.Any]]) -> None
¤
Wire lax primitives to hijax functions for a HiValue subclass.
Each {primitive: function} entry generates the Quax dispatch rules for
that primitive: one per combination of cls and plain-array operands, since
a binary primitive can be reached with your type on either side or both.
Each generated rule unwraps the leaves, applies function, and wraps a
hijax result back up in cls.
The number of operands is read from function's positional parameters, and
the primitive's own parameters are forwarded to whichever keyword-only
arguments function declares. Where the two do not line up -- because the
hijax function takes them positionally, or names them differently -- adapt
with a small lambda.
Example
register_rules(
Unitful,
{
lax.mul_p: hi.mul, # (x, y), no params
lax.add_p: hi.add,
lax.integer_pow_p: lambda q, *, y, **kw: hi.int_pow(q, y),
lax.reduce_sum_p: lambda q, *, axes, **kw: hi.sum(q, axes),
},
)
Arguments:
cls: theHiValuesubclass to register against.rules: a mapping fromjax.extend.core.Primitiveto the hijax function implementing it.
Returns:
Nothing; the rules are registered as a side effect, exactly as
quax.register does.
Raises:
NotImplementedError for a primitive with multiple results, which this
helper cannot wrap. Write the rule by hand with quax.register.