Feature Pipelines

How do we transform raw data into model inputs consistently across training and production?

The Model Did Not Change. Accuracy Collapsed.

Training accuracy

97%

Production accuracy

61%

The cause was one line: training lowercased every comment; production did not. “IDIOT” became an unknown or different token from “idiot.” The classifier received a representation it had never learned to interpret.

Why can one missing preprocessing step destroy an excellent model?

Because the model never sees raw comments. It sees the output of a feature pipeline. Change that pipeline and you change the model’s effective input space.

Retrieval Warm-Up

  1. Why must a feature exist at prediction time?
  2. Which lesson artifact tells us what “toxicity” means?
  3. Why can a balanced annotation set distort deployment estimates?
Check answers after retrieving

Future information leaks; the versioned label policy; and balanced sampling changes class prevalence. If these were slow, revisit Data Collection and Labels before continuing.

Learning objectives

  • Separate raw observations from model features
  • Eliminate training-serving skew
  • Design offline, online, batch, and streaming features
  • Compute point-in-time-correct stateful features
  • Understand feature stores and lineage
  • Validate schemas, values, and distributions

Feature Pipeline Failure Lab

Inspect every representation

Output

"YOU ARE SUCH AN IDIOT!!! 😂😂"

Schema

str

Python

event.text

Break production on purpose

Training remains fixed at 97%.

Production accuracy

97%

Feature validation dashboard

Shift production comment length away from training.

+18%

Schema

pass

Missing language

0.7%

Length drift

pass

Unknown tokens

3.2%

One Comment, Seven Representations

Start with "YOU ARE SUCH AN IDIOT!!! 😂😂". Unicode normalisation makes equivalent characters canonical. Whitespace cleanup removes accidental layout variation. Case folding produces "you are such an idiot!!! 😂😂". Language detection yields en. Tokenisation maps text to stable token IDs. An encoder maps those IDs to a fixed-dimensional vector.

Every intermediate value needs a schema and version. Emoji removal would discard intensity. Unversioned language detection could route the same comment differently after an upgrade. A tokenizer vocabulary change can silently reinterpret every integer.

raw event v3 → normaliser v2 → language model v5 → tokenizer toxic-2026-04 → encoder emb-17 → float32[384]

The Model Sees Features, Not Reality

For raw observation xx, a versioned pipeline computes:

z=fv(x),p^=P(y=toxicz)z=f_v(x),\qquad \hat p=P(y=\text{toxic}\mid z)

The model only receives zz. Offline training produces feature distribution P(z)P(z); online serving produces Q(z)Q(z). If implementations differ, then:

P(z)Q(z)  EQ[(p^,y)]>EP[(p^,y)]P(z)\ne Q(z)\ \Longrightarrow\ \mathbb E_Q[\ell(\hat p,y)]>\mathbb E_P[\ell(\hat p,y)]

This is training-serving skew: a distribution shift created by the system itself.

Offline And Online Must Share A Definition

Offline training

historical events → batch computation → feature table → training

Optimised for throughput, backfills, and point-in-time joins.

Online serving

live event → feature service → feature vector → prediction

Optimised for low latency, freshness, and availability.

The storage and execution engines may differ. The feature definition, schema, defaults, tokenizer, windows, and versions must not. Prefer one shared transformation library or declarative feature definition over copied notebook and service code.

Point-In-Time Correctness Prevents Leakage

12:00:00 comment posted ← prediction happens here
12:00:06 first user report
12:03:20 moderator review
14:40:00 appeal filed

At 12:00:00, report count, moderator action, and appeal outcome do not exist. Joining them into the training row teaches from the future. Offline generation must ask: what value would the online system have returned at that exact timestamp?

A legal feature might be the author’s prior violations before 12:00:00. The same author’s moderation decision at 12:03:20 is illegal for this prediction.

Stateful Features Need Window Semantics

“Toxic comments in the last 30 days” requires an entity key, event-time clock, window boundaries, lateness policy, update frequency, and default. Define it precisely:

cu(t)=i1[ui=u]1[t30dti<t]1[yi=toxic]c_u(t)=\sum_i \mathbb 1[u_i=u]\,\mathbb 1[t-30d\le t_i<t]\,\mathbb 1[y_i=\text{toxic}]

The strict ti<tt_i<t excludes the current and future event. Online state may update continuously; offline backfills must reproduce the same boundary and late-event rules. UTC avoids region-dependent windows.

What A Feature Store Solves

versioned feature definition
├── offline computation → historical store → training export
└── streaming computation → online store → low-latency lookup
                             └── lineage + freshness + schema

A feature store is a coordination layer, not magic. It centralises definitions, materialises historical and fresh values, supports point-in-time joins, and exposes online lookup. Tools such as Feast embody this pattern, but the design matters more than a particular tool.

It does not automatically prevent a logically leaky feature or guarantee the offline and online implementations are semantically identical; tests and validation still do that.

Validate Before The Model Receives Anything

Schema

Required names, types, shapes, and version.

Missingness

Null rate by feature and slice; explicit defaults.

Ranges

Comment length ≥ 0; probability in [0,1].

Categories

Known languages plus a deliberate unknown bucket.

Freshness

User-history timestamp is recent enough for the SLA.

Statistical drift

Training and production histograms remain comparable.

One Deterministic Pipeline For Both Paths

from dataclasses import dataclass
import unicodedata

@dataclass(frozen=True)
class FeatureConfig:
    version: str = "tox-features-v4"
    tokenizer_version: str = "toxic-2026-04"
    unknown_language: str = "und"

def normalise(text: str) -> str:
    text = unicodedata.normalize("NFKC", text)
    return " ".join(text.split()).casefold()

def transform(event, tokenizer, language_detector, config=FeatureConfig()):
    text = normalise(event["text"])
    language = language_detector(text) or config.unknown_language
    token_ids = tokenizer.encode(text)
    return {"token_ids": token_ids, "language": language,
            "char_count": len(text), "feature_version": config.version}

# Training and serving import this exact function.
assert transform(sample, tok, detect) == transform(sample, tok, detect)

Miniature timestamped store

class FeatureStore:
    def __init__(self): self.values = {}

    def write(self, entity, feature, event_time, value):
        self.values.setdefault((entity, feature), []).append((event_time, value))

    def lookup(self, entity, feature, as_of):
        valid = [(t, v) for t, v in self.values.get((entity, feature), []) if t <= as_of]
        return max(valid)[1] if valid else 0

    def offline_export(self, entities, feature, timestamps):
        return [self.lookup(e, feature, t) for e, t in zip(entities, timestamps)]

Failure Catalogue

Missing lowercasing

Capitalised words map to different tokens.

Tokenizer mismatch

Identical text becomes different token IDs.

Embedding version mismatch

Vector dimensions or semantic space change.

Schema drift

A renamed or retyped field breaks consumers.

Timezone bug

Thirty-day windows include different events.

Missing-value mismatch

Training imputes ‘unknown’; serving emits null.

Changing vocabulary

Category IDs move between runs.

Duplicate logic

Two copies drift after one is updated.

Interview Discussion

Can offline and online systems use different technologies?
Yes, if they share versioned semantics and parity tests. Identical infrastructure is not required; identical outputs are.

How would you detect skew?
Log online feature vectors, recompute them offline for the same event and timestamp, compare field-by-field, then monitor distribution and missingness.

Why is a feature store insufficient by itself?
It serves definitions and values; it cannot decide whether a definition leaks or encodes the wrong product concept.

Apply And Review

  1. For one comment, name every representation before prediction.
  2. Explain training-serving skew without using the word “different.”
  3. Decide whether “reports in the next hour” is legal and justify using the event timeline.
  4. Specify a 30-day user feature with entity, clock, boundary, and default.
  5. Design three parity tests and three production validations.
  6. In one day, retrieve skew, point-in-time correctness, and feature lineage; in three days, reproduce the stateful-feature formula without notes.

What Comes Next?

Models rarely fail because matrix multiplication changes. They fail because inputs quietly change. With reproducible inputs, the next systems question is how another engineer can recreate the entire trained model six months from now.