OpFromGraph#
This page describes pytensor.compile.builders.OpFromGraph, an Op constructor that allows one to
encapsulate an PyTensor graph in a single Op.
This can be used to encapsulate some functionality in one block. It is useful to scale PyTensor compilation for regular bigger graphs when we reuse that encapsulated functionality with different inputs many times. Due to this encapsulation, it can make PyTensor’s compilation phase faster for graphs with many nodes.
Using this for small graphs is not recommended as it disables rewrites between what is inside the encapsulation and outside of it.
- class pytensor.compile.builders.OpFromGraph(inputs, outputs, *, inline=False, pullback=None, pushforward=None, lop_overrides=None, rop_overrides=None, connection_pattern=None, strict=False, name=None, destroy_map=None, **kwargs)[source]#
Create an Op from inputs and outputs lists of variables.
The signature is similar to
pytensor.function()and the resulting Op’s perform will do the same operation aspytensor.function(inputs, outputs, **kwargs).Does not support
updatesorgivens.Todo
Add support for NullType and DisconnectedType when R_op supports them
Add optimization to removing unused inputs/outputs
Add optimization to work inplace on inputs when not inline
Notes
Shared variables in the inner graph are supported. They are detected automatically and added as implicit inputs.
Unused inputs are supported (needed for gradient overrides).
Nested OpFromGraph is supported.
inline=Truecauses the Op’s inner graph to be inlined during compilation, which gives better runtime optimization at the cost of compilation time. Currently only works withfast_compileorfast_runmode.Override callables should be pure functions (no side effects). They are called once at the first call to L_op/R_op and converted to OpFromGraph instances. They are also called once at construction time with dummy inputs to build a frozen representation for equality comparison.
Two OpFromGraph instances with the same inner graph, overrides, shared variables, and settings are considered equal. This allows the MergeOptimizer to deduplicate identical OpFromGraph nodes.
Examples
Basic usage:
from pytensor import function, tensor as pt from pytensor.compile.builders import OpFromGraph x, y, z = pt.scalars("xyz") e = x + y * z op = OpFromGraph([x, y, z], [e]) # op behaves like a normal pytensor op e2 = op(x, y, z) + op(z, y, x) fn = function([x, y, z], [e2])
With a shared variable:
import numpy as np import pytensor from pytensor import config, function, tensor as pt from pytensor.compile.builders import OpFromGraph x, y, z = pt.scalars("xyz") s = pytensor.shared(np.random.random((2, 2)).astype(config.floatX)) e = x + y * z + s op = OpFromGraph([x, y, z], [e]) e2 = op(x, y, z) + op(z, y, x) fn = function([x, y, z], [e2])
Per-input L_op override:
from pytensor import function, tensor as pt, grad from pytensor.compile.builders import OpFromGraph x, y, z = pt.scalars("xyz") e = x + y * z def rescale_dy(inps, outputs, out_grads): x, y, z = inps (g,) = out_grads return z * 2 op = OpFromGraph( [x, y, z], [e], pullback=[None, rescale_dy, None], ) e2 = op(x, y, z) dx, dy, dz = grad(e2, [x, y, z]) fn = function([x, y, z], [dx, dy, dz]) # the gradient wrt y is now doubled fn(2.0, 3.0, 4.0) # [1., 8., 3.]