Keras
machine learning
multithreading
prediction
neural networks

Running Keras model for prediction in multiple threads

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

Running Keras inference from multiple threads is possible, but it is easy to reach for the wrong kind of concurrency. The real question is not only "can threads call the model" but also "will that improve throughput on this hardware and runtime."

What Usually Works

For inference-only workloads, the safest baseline is:

  • load the model once at process startup
  • keep it read-only
  • call it from worker code using stable tensor shapes when possible

In modern TensorFlow and tf.keras, actual numerical kernels run in TensorFlow's own thread pools or on the GPU, so adding many Python threads does not guarantee better performance. In many cases, batching requests is more effective than increasing thread count.

Simple Shared-Model Pattern

Here is a small example using one shared model and a lock around prediction. The lock keeps the example conservative and easy to reason about.

python
1import threading
2from concurrent.futures import ThreadPoolExecutor
3import numpy as np
4import tensorflow as tf
5
6model = tf.keras.Sequential([
7    tf.keras.layers.Input(shape=(4,)),
8    tf.keras.layers.Dense(8, activation="relu"),
9    tf.keras.layers.Dense(1)
10])
11
12model.compile(optimizer="adam", loss="mse")
13model(np.zeros((1, 4), dtype="float32"))
14
15predict_lock = threading.Lock()
16
17def predict_batch(batch):
18    with predict_lock:
19        return model(batch, training=False).numpy()
20
21inputs = [np.random.rand(16, 4).astype("float32") for _ in range(4)]
22
23with ThreadPoolExecutor(max_workers=4) as pool:
24    outputs = list(pool.map(predict_batch, inputs))
25
26for out in outputs:
27    print(out.shape)

This demonstrates thread-driven request handling while keeping access to the shared model serialized.

When Multiple Threads Help

Threads help most when your application does other work around inference, such as:

  • network I/O
  • request parsing
  • feature lookup
  • result serialization

In that design, threads keep the service responsive, while the model call itself may still be the bottleneck.

If the goal is pure inference throughput, try batching first. A single model(batch) call is often much faster than many tiny calls from separate threads.

Alternative: One Model Per Worker

Another pattern is one model instance per worker thread or process. That reduces shared-state concerns, but it increases memory usage. On GPU systems, blindly loading multiple copies can be worse than helpful because all workers still contend for the same device.

For heavier workloads, many teams prefer process-based serving systems such as TensorFlow Serving, TorchServe, or a custom service with a request queue and dynamic batching. Those patterns scale more predictably than ad hoc Python threading.

Tune TensorFlow, Not Just Python

TensorFlow already has internal threading controls. If CPU inference is the focus, parameters in tf.config.threading can matter more than Python thread count.

That means performance tuning should be evidence-driven:

python
1import tensorflow as tf
2
3tf.config.threading.set_inter_op_parallelism_threads(2)
4tf.config.threading.set_intra_op_parallelism_threads(4)

Then benchmark realistic request sizes instead of assuming "more threads" equals "more speed."

Common Pitfalls

The biggest mistake is using threads to send many tiny prediction requests one by one. The Python overhead and runtime contention often cancel the benefit.

Another mistake is mutating model state during concurrent inference. Prediction code should avoid training-mode layers, weight updates, or shared mutable preprocessing objects unless the design explicitly synchronizes them.

A third issue is measuring only latency for one request and ignoring total throughput. Sometimes threads improve responsiveness under load; other times they just add contention.

Summary

  • Shared-model inference from multiple threads can work, but it is not automatically faster.
  • Batching is often a better first optimization than adding Python threads.
  • Keep the model read-only during inference and synchronize access if needed.
  • Consider TensorFlow's own threading settings when tuning CPU workloads.
  • For serious serving workloads, a queue-based or process-based inference service is usually more robust.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free 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.