Skip to content

Fix "AttributeError" thrown when processing multiple subgraphs induced by custom gradient definitions - #268

Open
nothingstopsme wants to merge 1 commit into
google-deepmind:mainfrom
nothingstopsme:main
Open

Fix "AttributeError" thrown when processing multiple subgraphs induced by custom gradient definitions#268
nothingstopsme wants to merge 1 commit into
google-deepmind:mainfrom
nothingstopsme:main

Conversation

@nothingstopsme

Copy link
Copy Markdown

This tiny patch is intended to fix the problem described in the title, which is probably best explained with an example as follows.

Considering this simple case:

import tensorflow as tf
import tf2jax

@tf.custom_gradient
def my_op(x):
  y = x**2
  def grad(dy):
    return dy * x * 2

  return y, grad

@tf.function
def func(z):
  return my_op(my_op(z))

with tf2jax.override_config("convert_custom_gradient", True):
  func_jaxed = tf2jax.convert_functional(func, np.zeros((2, 2), np.float32))

The conversion starts with generating a full graph depicted by func() and all op nodes involved:

nodes = [
  _OpNode(name='z'), _OpNode(name='pow/y'), _OpNode(name='pow'), _OpNode(name='IdentityN'),
  _OpNode(name='pow_1/y'), _OpNode(name='pow_1'), _OpNode(name='IdentityN_1'), _OpNode(name='Identity_2')
]

Since there are 2 calls to my_op(), which defines its own custom gradient, 2 subgraphs grouping associated nodes are extracted from the full graph:

# nodes belonging to _Subgraph(name='IdentityN')
[_OpNode(name='pow/y'), _OpNode(name='pow'), _OpNode(name='IdentityN')]

# nodes belonging to _Subgraph(name='IdentityN_1')
[_OpNode(name='pow_1/y'), _OpNode(name='pow_1'), _OpNode(name='IdentityN_1')]

Then the process goes on to update nodes to reflect the subgraph structures, supposedly resulting in:

nodes = [_OpNode(name='z'), _Subgraph(name='IdentityN'), _Subgraph(name='IdentityN_1'), _OpNode(name='Identity_2')]

Surprisingly, it turns out that tf2jax fails to convert this example and raises AttributeError at the converting stage above. This is because _Subgraph.rewrite() (defined in "tf2jax/_src/tf2jax.py"), which is the function responsible for manipulating nodes and subgraph substitution, treats every item in nodes as just a op node of the type _OpNode; however, this is not true after the addition of the 1st subgraph _Subgraph(name='IdentityN') to nodes, when the content of nodes becomes

# after "_Subgraph(name='IdentityN')" is substituted
nodes =  [
  _OpNode(name='z'), _Subgraph(name='IdentityN'), _OpNode(name='pow_1/y'),
  _OpNode(name='pow_1'), _OpNode(name='IdentityN_1'), _OpNode(name='Identity_2')
]

Therefore, as the subsequent call to _Subgraph.rewrite() on the 2nd subgraph _Subgraph(name='IdentityN_1') attempts to modify an attribute that is editable on _OpNode but read-only on _Subgraph while iterating nodes, an AttributeError exception occurs.

This fix takes into account the fact that nodes could be a mix of op nodes and subgraphs, and adds checks to avoid invalid attribute/property write attempts.

@google-cla

google-cla Bot commented Apr 21, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

…d by custom gradient definitions

When converting a graph containing custom gradient definitions, there is a stage
where the op node sequence is updated to reflect extracted subgraph structures
(nodes belonging to subgraphs are removed while subgraphs are added). However,
since such updates are conducted sequentially (i.e. one subgraph after another),
if there are multiple subgraphs, the op node sequence will start to contain a
mix of op nodes and subgraphs after the addition of the 1st subgraph, which the
update procedure can not handle properly as it treats every item in the op node
sequence as just a op node.

Specifically, when the update procedure modifies the values of "control_inputs"
attributes on all items in the op node sequence that do not belong to the
curently targeted subgraph, and encounters an item of the type "_Subgraph"
instead of "_OpNode", "AttributeError" will be raised due to the fact that
"control_inputs" in "_Subgraph" is defined as a read-only property

This fix introduces a check to see whether "control_inputs" is a read-only
property or not before a write to it, and skipping the update attempt if True.
Note that this skip in effect does not alter the existing behaviour, though, as
the property "control_inputs" of "_Subgraph" is set to always return an empty
tuple without backing variables (i.e. it remains unchanged no matter what)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant