Tensor Flow 📂 Graphs and Speed with tf.function · 1 of 5 29 min read

Eager vs Graph Mode in TensorFlow: How tf.function Speeds Up Code

Learn the difference between eager mode and graph mode in TensorFlow. See how tf.function turns Python into a graph of ops, why graphs run faster, and what you give up. Type your own maths to build a live graph, push values through it, and watch constant folding shrink it. Ends with a simple switch for debugging graphs.

Section 01

The Story — A Taxi and a Train

Turn-by-Turn Directions vs a Fixed Route
In a taxi, you tell the driver each turn: "left here… now right… stop". It is flexible. You can change your mind at any corner. But you spend a lot of time talking, and the driver waits for you at every junction.

A train runs on a fixed route that was planned in advance. Nobody gives directions on the way. It just goes, fast. You cannot change the route mid-journey, but for a trip you make every day, it is much faster.

Eager mode is the taxi: Python tells TensorFlow each op, one at a time. Graph mode is the train: TensorFlow plans the whole route once, then runs it again and again without asking Python.

In this module you will learn how TensorFlow turns Python code into a graph, why that makes it faster, what can go wrong, and how to measure the speed-up yourself. This first lesson explains the two modes side by side.


Section 02

Eager Mode — The Default

In TensorFlow 2, every op runs the moment Python reaches it. You get real numbers back at once.

import tensorflow as tf

def layer(x, w, b):
    return tf.nn.relu(x * w + b)

x, w, b = tf.constant(3.0), tf.constant(-2.0), tf.constant(4.0)
y = layer(x, w, b)
print("eager result :", y)
print("running eager:", tf.executing_eagerly())
OUTPUT
eager result : tf.Tensor(0.0, shape=(), dtype=float32) running eager: True

Behind the scenes, Python walks line by line. For each op it calls into TensorFlow's C++ core, waits for the answer, then moves to the next op.


Section 03

Graph Mode — Plan Once, Run Many Times

Wrap the same function with tf.function. The first call builds a graph: a data structure where nodes are ops and edges are the tensors flowing between them. Later calls run that graph directly in C++.

graph_layer = tf.function(layer)
print("graph result :", graph_layer(x, w, b))

concrete = graph_layer.get_concrete_function(x, w, b)
ops = [op.type for op in concrete.graph.get_operations()]
print("ops in graph :", ops)
OUTPUT
graph result : tf.Tensor(0.0, shape=(), dtype=float32) ops in graph : ['Placeholder', 'Placeholder', 'Placeholder', 'Mul', 'AddV2', 'Relu', 'Identity']
🔍
Reading the Op List

Placeholder nodes are the inputs (x, w, b). They have a shape and dtype, but no value until the graph runs. Mul, AddV2 and Relu are your maths. Identity marks the output. This graph is the "train route". It can run with no Python at all.

Animated Diagram — Who Does the Talking?
EAGER — one round trip per op Python TF runtimeMul, Add, Relu "do Mul""done, next?" GRAPH — one call for the whole plan Python TF runtime Mul Add Relu Eager: Python is busy for every single op. With thousands of tiny ops, this chatter costs more than the maths. Graph: Python sends one request. The runtime runs the whole plan in C++, and can reorder, fuse and parallelise ops. eagergraph

The progress bars show the same 1,000 small ops. The graph finishes first because it skips most of the Python chatter.


Section 04

Try It — Build and Run a Graph

🕸️ Type Maths, See the Graph Interactive

Type an expression using the inputs x, w, b, numbers, + - * / and relu sigmoid exp square. Press Build graph to trace it. Then press Run to push values through. Try x * (2 + 3) + b and press Optimise to see constant folding.


Section 05

Why Graphs Are Useful

⚡
Speed
No Python overhead per op. Big win for many small ops and custom training loops.
less chatter
✨
Optimisation
TensorFlow's Grappler folds constants, removes unused ops and fuses ops together before running.
Grappler
🔀
Parallelism
Ops that do not depend on each other can run at the same time.
independent branches
📦
Portability
A graph needs no Python. Save it as a SavedModel and run it on a server, phone or browser.
SavedModel, LiteRT, TF.js
🖥️
Devices
The whole graph can be placed on a GPU or TPU and run there without going back to the CPU.
GPU / TPU
🚧
The cost
Harder to debug. Python side effects run only once. You will study these traps in lesson 3.
trade-off

Section 06

Eager vs Graph — Side by Side

QuestionEager modeGraph mode (tf.function)
When does an op run?ImmediatelyWhen the whole graph is called
Can I print values?Yes, with print()Use tf.print()
Can I use .numpy()?YesNo — tensors are symbolic inside
Python overheadEvery opOnce per call
Graph optimisationsNoYes (Grappler, optional XLA)
Export without PythonNoYes
Best forLearning, debugging, one-off mathsTraining loops, inference, deployment

A Handy Switch for Debugging

If a tf.function misbehaves, you can force every tf.function to run eagerly, find the bug, then switch back.

@tf.function
def where_am_i():
    return tf.executing_eagerly()

print("normal     : eager inside?", bool(where_am_i()))
tf.config.run_functions_eagerly(True)       # debug: all tf.functions run eagerly
print("debug mode : eager inside?", bool(where_am_i()))
tf.config.run_functions_eagerly(False)      # back to fast graph mode
OUTPUT
normal : eager inside? False debug mode : eager inside? True

Section 07

Golden Rules

🚆 Eager vs Graph — Rules to Remember
1
Eager mode runs ops one by one. It is simple and great for learning and debugging.
2
Graph mode builds a plan once, then runs it fast in C++ with no Python per op.
3
Graphs can be optimised, parallelised, placed on GPUs and exported without Python.
4
Write and test in eager mode first. Add tf.function once the code works.
5
Use tf.config.run_functions_eagerly(True) to debug a graph, then turn it off.