How it works#
TracedNpArray subclasses np.ndarray, so it holds real array data and
behaves like an array everywhere LEAPP is not involved. It implements NumPy’s
two dispatch hooks, __array_ufunc__ and __array_function__, which lets
it see every NumPy call made on it before NumPy executes it.
Each intercepted call does two things:
Runs the original NumPy operation eagerly on the underlying buffer, so the value you get back is exactly what NumPy would have produced.
Looks the NumPy function up in a translation table and records the equivalent torch operation into the FX graph.
So np.clip on a traced array returns a clipped NumPy array and records
torch.clamp. Given this node:
state, velocity = annotate.input_tensors("preprocess", {
"state": frame["observation.state"],
"velocity": frame["observation.velocity"],
})
state_norm = np.clip((state - STATE_MEAN) / STATE_STD, -5.0, 5.0)
obs = np.concatenate([state_norm, velocity])
LEAPP records a graph that mentions no NumPy at all:
%state = placeholder[target=state]
%velocity = placeholder[target=velocity]
%_tensor_constant0 = get_attr[target=_tensor_constant0]
%sub = call_function[target=torch.sub](args = (%state, %_tensor_constant0))
%_tensor_constant1 = get_attr[target=_tensor_constant1]
%div = call_function[target=torch.div](args = (%sub, %_tensor_constant1))
%clamp = call_function[target=torch.clamp](args = (%div, -5.0, 5.0))
%cat = call_function[target=torch.cat](args = ([%clamp, %velocity],))
Two details in that graph matter for Limitations. Plain NumPy arrays
used as operands, here STATE_MEAN and STATE_STD, become frozen
get_attr constants. And each recorded node comes from a lookup, so a
NumPy call with no entry in the table records nothing.
What gets traced#
Tracing is driven by an explicit NumPy-to-torch table, so support is a fixed list rather than a general rule.
Category |
Covered |
|---|---|
Arithmetic and math |
|
Comparison and logic |
|
Reductions |
|
Shape and layout |
|
Selection |
|
Linear algebra |
|
Array creation |
|
Conversion |
|
Anything outside the table runs normally and is skipped by the tracer, with a warning naming the call:
No torch equivalent for numpy function <name>. Operation will not be traced.
Treat that warning as an error. See Limitations. After tracing,
run compile_graph(validate=True) so a later sample can catch an
untraced result that was frozen as a constant. The workflow is on
Debugging.