Models are 30% of the engineering; data pipelines are the other 70%. Keras 3 has a powerful preprocessing-layer system (image augmentation, text vectorization, normalization) that runs *inside the model graph* — meaning it ships with your saved model. Plus how to plug in tf.data, torch.DataLoader, and grain cleanly.
Preprocessing is a layer, not a script
The thing that breaks most production ML systems isn't the model — it's the gap between how you preprocessed data at training time and how you preprocess it at serving time. Keras 3 closes that gap by making preprocessing a layer: Rescaling, Normalization, RandomFlip, TextVectorization, IntegerLookup and the rest are real layers you stack like any other. Put them inside the model and the saved .keras file carries the exact same transform to production for free.
Adapt before you train
The stateful layers — Normalization and TextVectorization — need to see your data before they're useful. You call .adapt(data) once, and the layer learns its statistics (mean/variance, or the vocabulary) and freezes them as weights. After that the layer is deterministic: it applies the same learned transform forever, including at inference. Stateless layers like Rescaling skip this step — there's nothing to learn from a fixed 1/255 divide. The Code block below shows both kinds and then bakes a rescaler into a model.
Two places to put it
You have a choice: put preprocessing in the model (portable, deploy-safe — the transform travels with the weights) or in the tf.data pipeline (faster training, because the work runs on CPU in parallel with the GPU step). The rule of thumb: keep deploy-critical, learned transforms (normalization, vocabulary) in the model; push cheap, throwaway, training-only work (random augmentation) into the data pipeline where the speed matters and the skew risk is zero.