tensorflow
dataset-api
cache
machine-learning
data-processing

Tensorflow Dataset API Cache

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

tf.data.Dataset.cache() is a performance tool for input pipelines. It stores the elements produced by a dataset pipeline so that later iterations can reuse them instead of recomputing the same preprocessing work every epoch.

Used well, caching can make training dramatically faster. Used in the wrong place, it can waste memory, pin unwanted randomness into the pipeline, or cache incomplete results that do not match your intent.

What cache() Actually Does

cache() saves the output of the dataset pipeline at the point where the transformation is inserted. On the first full pass, TensorFlow materializes the data. On later passes, it reads from the cache instead of recomputing upstream transformations.

Basic example:

python
1import tensorflow as tf
2
3
4dataset = tf.data.Dataset.range(5)
5dataset = dataset.map(lambda x: x * 10)
6dataset = dataset.cache()
7
8for epoch in range(2):
9    print(f"epoch {epoch}")
10    for item in dataset:
11        print(item.numpy())

The multiplication pipeline is computed on the first pass and reused on the second pass.

In-Memory Cache Versus File Cache

You can cache in memory by calling cache() with no argument:

python
dataset = dataset.cache()

You can also cache to a file path:

python
dataset = dataset.cache("/tmp/train_cache")

In-memory cache is simple and fast, but only works when the cached dataset fits comfortably in memory. File-based cache is useful for larger datasets or when memory is limited, though it may be slower than RAM.

Where to Put cache() in the Pipeline

Placement matters more than many first-time users expect. In general, cache after expensive deterministic preprocessing and before operations that should stay fresh each epoch.

A common pattern is:

python
1import tensorflow as tf
2
3
4dataset = tf.data.Dataset.from_tensor_slices([1, 2, 3, 4])
5dataset = dataset.map(lambda x: x * x, num_parallel_calls=tf.data.AUTOTUNE)
6dataset = dataset.cache()
7dataset = dataset.shuffle(4)
8dataset = dataset.batch(2)
9dataset = dataset.prefetch(tf.data.AUTOTUNE)

This works well because the deterministic square computation is cached, while shuffle still happens downstream and can vary between iterations.

Do Not Cache Random Augmentation Unless You Mean To

If you cache after a random transformation, you freeze that randomness into the cached output.

python
dataset = dataset.map(random_augmentation)
dataset = dataset.cache()

That means the augmented examples become identical on later epochs, which may not be what you want.

If the augmentation should change each epoch, cache before the random step instead:

python
dataset = dataset.cache()
dataset = dataset.map(random_augmentation)

This is one of the most important placement decisions in real training pipelines.

Caching with Training Data

A practical training pipeline often looks like this:

python
1import tensorflow as tf
2
3
4def preprocess(x):
5    x = tf.cast(x, tf.float32)
6    return x / 255.0
7
8
9images = tf.random.uniform((100, 28, 28, 1), maxval=255, dtype=tf.int32)
10labels = tf.random.uniform((100,), maxval=10, dtype=tf.int32)
11
12dataset = tf.data.Dataset.from_tensor_slices((images, labels))
13dataset = dataset.map(lambda x, y: (preprocess(x), y), num_parallel_calls=tf.data.AUTOTUNE)
14dataset = dataset.cache()
15dataset = dataset.shuffle(100)
16dataset = dataset.batch(16)
17dataset = dataset.prefetch(tf.data.AUTOTUNE)

Here, normalization is cached, but shuffling and batching remain dynamic where appropriate.

Full Iteration Matters

Caching only becomes complete after the upstream dataset has been fully consumed. If you stop early during the first pass, the cache may not represent the full dataset you expected.

That means partial iteration during debugging can produce confusing results, especially when you later assume the cache already contains everything.

In training code, this usually resolves itself because full epochs consume the dataset, but it is worth keeping in mind during experiments.

Common Pitfalls

One common mistake is placing cache() after random augmentation, which unintentionally removes augmentation diversity from later epochs.

Another issue is caching data that is too large for available memory. In that case, file caching or a different pipeline design is safer than forcing an in-memory cache.

It is also easy to assume cache placement is purely a performance choice. It is not. Placement changes the semantics of what gets reused.

Finally, avoid judging cache behavior after only a partial first pass through the dataset. The cache is most meaningful after the pipeline has been fully materialized.

Summary

  • 'cache() stores dataset elements at the point where it appears in the pipeline.'
  • Use in-memory caching for smaller datasets and file caching when memory is constrained.
  • Cache after expensive deterministic preprocessing, not after randomness you want to vary each epoch.
  • Placement affects both speed and training behavior.
  • A cache only reflects what has actually been iterated and materialized.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

All Rights Reserved.