ACE is an automatic differentiation compiler engine with zero external runtime dependencies. Composite scalar formulas written with ordinary Python operators are implicitly intercepted and woven into a directed acyclic graph, and analytic gradients for all input variables are then computed through backpropagation.
- Support automatic differentiation for arbitrary composite scalar formulas
- Support multivariable formulas, deep nesting, and repeated variable reuse
- Support
+ - * /, constant powers,exp,log,sin,cos, andtanh - Support extended operators
sinh,cosh,sigmoid, andarctan - Use analytic automatic differentiation for the actual gradient engine
- Use Richardson Extrapolation for numerical gradient checking
Autograd-Compiler-Engine/
├── ace/
├── docs/
├── examples/
│ ├── evaluate_formula.py
│ ├── export_graph.py
│ ├── export_mega_graph.py
│ ├── export_deep_graph.py
│ ├── export_wide_graph.py
│ ├── export_ultimate_graph.py
│ ├── ace_graph
│ ├── ace_graph.svg
│ ├── ace_graph.png
│ ├── ace_mega_graph
│ ├── ace_mega_graph.svg
│ ├── ace_mega_graph.png
│ ├── ace_deep_graph
│ ├── ace_deep_graph.svg
│ ├── ace_deep_graph.png
│ ├── ace_wide_graph
│ ├── ace_wide_graph.svg
│ ├── ace_wide_graph.png
│ ├── ace_ultimate_graph
│ ├── ace_ultimate_graph.svg
│ └── ace_ultimate_graph.png
├── tests/
├── requirements.txt
└── README.md
ACE depends on the following ideas:
- Operator overloading: turn ordinary mathematical expressions into node operations
- DAG construction: every result node records its direct predecessors
- Reverse-mode automatic differentiation: propagate gradients from output nodes back to input nodes
- DFS-based topological scheduling: ensure dependencies are visited in the correct order during backpropagation
- Analytic derivative rules: every atomic operator provides its own local derivative formula
- Richardson Extrapolation: acts as a high-confidence numerical referee for gradient checking
- Graphviz graph export: visualize the intermediate IR structure
ACE does not use numerical differentiation as the main engine, but the tests use Richardson Extrapolation to build a high-confidence numerical reference and verify partial derivatives term by term for complex formulas at multiple points.
Related documents:
docs/PRD.mddocs/Richardson_Extrapolation.mddocs/Algorithms.mddocs/Graph_Visualization.md
PYTHONPATH=. python3 examples/evaluate_formula.pyInstall dependencies:
pip install -r requirements.txtExport a regular complex formula:
PYTHONPATH=. python3 examples/export_graph.pyExport a mega-complex formula:
PYTHONPATH=. python3 examples/export_mega_graph.pyExport a deeply nested formula:
PYTHONPATH=. python3 examples/export_deep_graph.pyExport a wide multi-branch formula:
PYTHONPATH=. python3 examples/export_wide_graph.pyExport the ultimate large formula:
PYTHONPATH=. python3 examples/export_ultimate_graph.pyIf you want bitmap output directly:
dot -Tpng examples/ace_graph -o examples/ace_graph.png
dot -Tpng examples/ace_mega_graph -o examples/ace_mega_graph.png
dot -Tpng examples/ace_deep_graph -o examples/ace_deep_graph.png
dot -Tpng examples/ace_wide_graph -o examples/ace_wide_graph.png
dot -Tpng examples/ace_ultimate_graph -o examples/ace_ultimate_graph.pngThese commands generate Graphviz source files together with svg and png outputs under examples/.
The formula used by examples/export_graph.py is:
f(x, y) =
tanh(x * y)
+ log(x^2 + 1)
+ exp(sin(x + y))
+ sigmoid(arctan(y) + cosh(x - y))
Characteristics:
- It mixes basic algebraic operations with transcendental functions
- It combines trigonometric functions, hyperbolic functions, logarithms, exponentials, and sigmoid
xandyare reused across multiple paths- The final graph has both depth and clear branching / merging structure
Exported files:
examples/ace_graphexamples/ace_graph.svgexamples/ace_graph.png
Embedded preview:
The formula used by examples/export_mega_graph.py is composed of five large blocks:
block_one =
tanh(
exp(sin(x * y + arctan(z)) + log(x^2 + y^2 + z^2 + 5))
/ (sigmoid(x - y + z) + 1.5)
)
block_two =
sinh(cosh(x - y) * sigmoid(z + x / (y + 3)))
block_three =
log((x * x + sin(y + z) + exp(arctan(x - z)))^2 + 2)
block_four =
(x * y * z) / (cosh(z - y) + 2)
block_five =
tanh(sigmoid(sinh(x + z) - cosh(y - z)) + arctan(x * y))
mega_result =
block_one + block_two + block_three + block_four + block_five
Characteristics:
- Three variables
x,y, andzparticipate simultaneously exp/log/sin/tanh/sinh/cosh/sigmoid/arctanall appear together- It has both deep nesting and many wide branches
- The same variable is reused across multiple blocks
- The result node receives gradients flowing back from many paths
Exported files:
examples/ace_mega_graphexamples/ace_mega_graph.svgexamples/ace_mega_graph.png
Embedded preview:
The formula used by examples/export_deep_graph.py is:
deep_result =
tanh(
exp(
log(
sigmoid(
sinh(
cosh(
arctan(sin(x + y) + x / (y + 2.5))
)
)
)
+ x^2 + y^2 + 3
)
)
)
Characteristics:
- It does not have the widest branching, but the chain itself is very deep
- Nearly every layer wraps the expression in another nonlinear function
- It is well suited for observing long-chain rule propagation through the graph
Exported files:
examples/ace_deep_graphexamples/ace_deep_graph.svgexamples/ace_deep_graph.png
Embedded preview:
The formula used by examples/export_wide_graph.py consists of ten parallel branches:
wide_result =
sin(x + y)
+ cosh(x - z)
+ exp(arctan(y + z))
+ log(x^2 + z^2 + 4)
+ tanh(x * y)
+ sigmoid(sinh(z + x))
+ (x * y * z) / (y + 2.5)
+ arctan(x - y + z)
+ sinh(y - z)
+ tanh(sigmoid(x + y + z) + log((x - z)^2 + 2))
Characteristics:
- A single variable enters many parallel branches at the same time
- The graph visibly converges in the middle and near the end
- It is useful for checking whether gradients are truly accumulated across multiple paths
Exported files:
examples/ace_wide_graphexamples/ace_wide_graph.svgexamples/ace_wide_graph.png
Embedded preview:
The formula used by examples/export_ultimate_graph.py combines two major classes of structures:
deep_a =
tanh(
exp(
log(
sigmoid(
sinh(
cosh(
arctan(
sin(x + y - z)
+ x / (y + 2.8)
+ z / (w + 2.2)
)
)
)
)
+ x^2 + y^2 + z^2 + w^2 + 4
)
)
)
deep_b =
tanh(
sigmoid(
exp(
sin(
log((x - z)^2 + (y + w)^2 + 3)
+ arctan(x * y - z * w)
)
)
)
)
wide_1 =
sin(x + y) + cosh(x - z) + exp(arctan(y + z))
wide_2 =
log(x^2 + z^2 + w^2 + 5) + tanh(x * y) + sigmoid(sinh(z + x - w))
wide_3 =
(x * y * z) / (y + 2.7) + arctan(x - y + z - w) + sinh(y - z + w)
wide_4 =
tanh(sigmoid(x + y + z + w) + log((x - z + w)^2 + 2.5))
wide_5 =
exp(sin(x * w) + cosh(y - w)) / (sigmoid(z - y + x) + 1.7)
wide_6 =
log((exp(arctan(x + w)) + sin(y - z) + x^2)^2 + 2)
fusion =
sigmoid(deep_a + deep_b + wide_1 + wide_2 + wide_3 + wide_4 + wide_5 + wide_6)
tail =
tanh(
log(
fusion^2
+ exp(arctan(x * z))
+ sigmoid(sinh(y + w) - cosh(z - x))
+ 3
)
)
ultimate_result =
deep_a + deep_b + wide_1 + wide_2 + wide_3 + wide_4 + wide_5 + wide_6 + fusion + tail
Characteristics:
- It increases both depth and width at the same time
- The variable count grows from 3 to 4
- Deep nonlinear nesting and many parallel branches coexist
- The same variable flows back repeatedly across distant blocks
- It is very suitable for observing the shape of a large automatic differentiation DAG
Exported files:
examples/ace_ultimate_graphexamples/ace_ultimate_graph.svgexamples/ace_ultimate_graph.png
Embedded preview:
The ACE graph export process can be summarized like this:
- Wrap input variables as
Valuenodes first, for examplex = Value(0.9, label="x") - Compose expressions as if writing normal mathematics; operator overloading intercepts every step
- Each operation returns a new
Valuenode and stores predecessor nodes, operator labels, and a local backward closure - After the expression is complete, the backend has already woven an implicit DAG
- When
result.backward()is called, the system runs DFS topological sorting and then triggers_backward()in reverse order - When
draw_dot(result)is called, the system backtracks from the root node and collects all nodes and edges - Graphviz then renders the nodes, operator labels, edge directions,
data, andgradvalues
The following example uses the complex formula from examples/export_graph.py:
f(x, y) =
tanh(x * y)
+ log(x^2 + 1)
+ exp(sin(x + y))
+ sigmoid(arctan(y) + cosh(x - y))
You can think of the internal decomposition in this order:
t1 = x * y
t2 = tanh(t1)
t3 = x^2
t4 = t3 + 1
t5 = log(t4)
t6 = x + y
t7 = sin(t6)
t8 = exp(t7)
t9 = arctan(y)
t10 = x - y
t11 = cosh(t10)
t12 = t9 + t11
t13 = sigmoid(t12)
result = t2 + t5 + t8 + t13
Each t_i corresponds to a new Value node inside ACE.
When these intermediate nodes are created, ACE records:
- the current
dataof the node - which operator produced it, via
_op - which predecessor nodes it depends on, via
_prev - the node's local backward closure
_backward
For example:
t1 = x * ycreates a*node whose predecessors arexandyt5 = log(t4)creates alognode whose predecessor ist4t13 = sigmoid(t12)creates asigmoidnode whose predecessor ist12
So the formula is not just a sequence of numeric operations. It is woven into a DAG.
When executing:
result.backward()ACE performs these steps:
- Traverse the whole graph from
resultusing DFS - Build a topological order where predecessors come before dependents
- Reset all node gradients to zero
- Set
result.grad = 1.0 - Call each node's
_backward()in reverse topological order
Examples:
t2 = tanh(t1)multiplies the incoming gradient by1 - tanh(t1)^2and sends it back tot1t1 = x * ymultiplies the gradient byyandxrespectively and sends it back toxandyt5 = log(t4)multiplies the gradient by1 / t4and sends it back tot4t13 = sigmoid(t12)multiplies the gradient bysigmoid(t12) * (1 - sigmoid(t12))and sends it back tot12
In this formula, both x and y appear in more than one place:
xappears inx * y,x^2,x + y, andx - yyappears inx * y,x + y,arctan(y), andx - y
So the final x.grad and y.grad are sums of contributions from multiple paths, not the result of a single path. ACE must use:
grad += contribution
If it overwrote the value instead of accumulating, the final gradient would be wrong.
The exported graph is not only for aesthetics. It is also a debugging entry point. When reading the graph, check:
- whether input leaf nodes appear correctly
- whether each intermediate expression is broken out into its own node
- whether
_oplabels match the formula structure - whether a reused variable really appears across multiple branches
- whether the final result node receives multiple merged paths
- whether the
dataandgradvalues of nodes stay in a reasonable range
Regular complex graph:
examples/ace_graph.svgexamples/ace_graph.png
Mega-complex graph:
examples/ace_mega_graph.svgexamples/ace_mega_graph.png
Deeply nested graph:
examples/ace_deep_graph.svgexamples/ace_deep_graph.png
Wide multi-branch graph:
examples/ace_wide_graph.svgexamples/ace_wide_graph.png
Ultimate graph:
examples/ace_ultimate_graph.svgexamples/ace_ultimate_graph.png
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -v



