Training Keras Models#
This page is for a reader who has a Keras training script and wants to
run that script on a cloud TPU or GPU. Kinetic runs your
model.compile() and model.fit() code on a remote accelerator after
one decorator change. You do not restructure the training loop. This
page shows the first run, explains what the function sees on the pod,
and covers the Keras backend, multi-host slices, data, and outputs.
Before you start#
Run
kinetic initone time. The active profile supplies the project, the zone, and the cluster to every job. See Getting Started.Make sure that the cluster has a node pool for the accelerator in the example.
kinetic pool listshows the pools. Add one withkinetic pool add --accelerator tpu-v5litepod-4, or changeaccelerator=to an accelerator that the cluster has.
A first run#
import kinetic
@kinetic.run(accelerator="tpu-v5litepod-4")
def train_model():
import keras
import numpy as np
model = keras.Sequential(
[
keras.layers.Dense(64, activation="relu", input_shape=(10,)),
keras.layers.Dense(1),
]
)
model.compile(optimizer="adam", loss="mse")
x_train = np.random.randn(1000, 10)
y_train = np.random.randn(1000, 1)
history = model.fit(x_train, y_train, epochs=5, verbose=0)
return history.history["loss"][-1]
final_loss = train_model()
print(f"Final loss: {final_loss}")
Three points about this script:
Put the imports for
kerasandnumpyinside the function. Your machine then does not need those packages. The pod imports them from the image, and the image contains JAX with the runtime for the accelerator.The return value goes back to your local process. Keep the value small: a final metric, a dict of numbers, or a path under
KINETIC_OUTPUT_DIR. Do not return the model object.accelerator="tpu-v5litepod-4"selects a 4-chip TPU v5e slice on one host. Useaccelerator="cpu"while you develop the code. Change the accelerator when the code works. See Accelerators.
The first run takes 5 to 10 minutes, because Kinetic builds the container image with your dependencies. Later runs with the same dependencies start in less than 1 minute while a node still runs. See How Kinetic Works.
For an end-to-end example with a real dataset, see
fashion_mnist.py.
What the function sees#
Your decorated function runs in a new Python process, inside a
container, on a node of the cluster. Kinetic serializes the function
with cloudpickle, together with the objects that the function
references. Two consequences follow:
The payload carries every referenced object. A value from the enclosing scope or a module-level global goes into the payload. A small value, such as a config dict, is not a problem. A large object, such as a dataset that you load at module level, makes the payload large on every submit. Kinetic logs a warning when the payload is larger than 50 MB. Load large data inside the function, or pass the data as
kinetic.Data. See What Ships to the Pod.The image supplies the packages. The image contains Keras, JAX, and the packages from your
requirements.txtorpyproject.toml. Apip installin your local shell does not carry over. See Dependencies.
The Keras backend#
The image that Kinetic builds sets KERAS_BACKEND=jax. Keras therefore
uses the JAX backend on the pod, regardless of the backend on your
machine. JAX is also the only accelerator runtime in the image:
jax[tpu] for a TPU and jax[cuda12] for a GPU.
If your script needs another backend, do three things:
Add the framework, for example
torch, to your dependency file.Set
KERAS_BACKENDin your shell, for exampleexport KERAS_BACKEND=torch.Forward the variable with
capture_env_vars.
@kinetic.run(accelerator="gpu-l4", capture_env_vars=["KERAS_BACKEND"])
def train(): ...
Name the variable exactly. A wildcard such as "KERAS_*" never
captures KERAS_BACKEND, because that variable is on the wildcard
blocklist. The pod applies the captured value before it calls your
function, so the import keras inside the function sees the new
backend. See
Forward Environment Variables.
Scale to more than one host#
A single-host slice such as tpu-v5litepod-8 has up to 8 chips on one
VM. If the model or the batch does not fit on one host, select a
multi-host slice, for example tpu-v5litepod-16 or tpu-v6e-16. Each
of those slices has four 4-chip VMs. Kinetic reads the host count from
the accelerator name and selects the Pathways backend for you. You do
not set backend="pathways".
The Keras distribution API sees every chip on every host. Set a
DataParallel distribution before you build the model:
@kinetic.run(accelerator="tpu-v5litepod-16")
def train_distributed():
import keras
devices = keras.distribution.list_devices()
mesh = keras.distribution.DeviceMesh(
shape=(len(devices),), axis_names=["batch"], devices=devices
)
keras.distribution.set_distribution(
keras.distribution.DataParallel(device_mesh=mesh)
)
model = keras.Sequential([...])
model.compile(...)
model.fit(...)
The cluster needs a node pool for the multi-host accelerator. See Distributed Training for the node pool, the log that you see, and the return value of a multi-host job. See Fine-tuning LLMs for a Gemma example.
Data#
Random NumPy arrays inside the function are sufficient for a test. Real
data must reach the pod. Construct a kinetic.Data(...) object at the
call site in your local script. Pass the object as an argument.
Kinetic uploads a local path one time and downloads the data to the
pod, or mounts a Cloud Storage location. Your function receives a plain
filesystem path (str):
import kinetic
from kinetic import Data
@kinetic.run(accelerator="tpu-v5litepod-8")
def train(data_dir):
# `data_dir` is a local filesystem path on the pod.
import keras
...
# A local directory:
train(Data("./my_dataset/"))
# A directory in Cloud Storage (the trailing slash marks a directory):
train(Data("gs://my-bucket/dataset/"))
# A large Cloud Storage dataset, read on demand through a FUSE mount:
train(Data("gs://my-bucket/large/", fuse=True))
# A Hugging Face dataset:
train(Data("hf://imdb?split=train"))
Data accepts a local path, a gs:// URI, or an hf:// Hugging Face
dataset URI. An hf:// URI needs the datasets package in your
dependency file. See Working with Data for the
choice between a download, a FUSE mount, and direct gs:// access, and
for the limits.
Save the model#
Kubernetes deletes the pod filesystem, including /tmp, when the pod
ends. Write the files that you want to keep, for example the model
weights and the checkpoints, under KINETIC_OUTPUT_DIR. Kinetic sets
that variable on the pod to a per-job Cloud Storage location. Return the
path, not the model:
import os
import kinetic
@kinetic.run(accelerator="tpu-v5litepod-4")
def train():
import keras
output_dir = os.environ.get("KINETIC_OUTPUT_DIR", "/tmp/local_run")
# ... build, compile, and fit the model ...
# ... write the weights and the metrics under output_dir ...
return output_dir
See Outputs and Checkpoints for the output
directory, retention, and a resumable Keras run with Orbax
(example_keras_checkpoint.py).