Online Serving at Scale#

CoreRec is not only an offline trainer: corerec.serving.OnlineRecommender turns a trained model (or raw interactions) into an online recommendation tier that is sub-millisecond, fresh, and cold-start-graceful at million-item scale.

It addresses the three things a production feed needs that a plain model.recommend does not:

  • Sub-linear retrieval — ANN over the item catalogue (FAISS HNSW/IVF via corerec.retrieval.VectorIndex) instead of an O(catalogue) full scan.

  • Freshness without retraining — add new items and fold in new/returning users incrementally.

  • Graceful cold-start — unknown users fall back to a popularity ranking instead of raising.

Quick start: train and serve in one call#

import pandas as pd
from corerec.serving import OnlineRecommender

# interactions: any DataFrame with user/item columns
df = pd.DataFrame({"user_id": users, "item_id": items})

# trains native embeddings (LightGCN by default) AND builds the ANN index
rec = OnlineRecommender.from_interactions(
    df, model="lightgcn", dim=64, epochs=200, device="cuda",
)

rec.recommend(user_id=42, top_k=10)        # sub-ms ANN retrieval
rec.recommend_batch([1, 2, 3], top_k=10)   # batched

model="lightgcn" (default) is the strongest option on large graph datasets; model="bpr" is a lighter matrix-factorization alternative. Both train on GPU when available. For a ~7x faster fit at a small accuracy cost, pass a large batch (e.g. from_interactions(..., ) with the trainer’s batch=1_000_000).

Serving an existing model’s embeddings#

If you already trained an embedding model, serve its factors directly:

rec = OnlineRecommender.from_embeddings(
    item_ids=item_ids, item_emb=item_vectors,
    user_ids=user_ids, user_emb=user_vectors,
    index_type="hnsw", metric="ip",   # use "ip" for dot-product models (MF/ALS)
)

Note

Choose metric="ip" for dot-product models (matrix factorization, ALS, LightGCN) and metric="cosine" for normalized/embedding models. CoreRec’s VectorIndex supports inner-product HNSW, so dot-product rankings are preserved rather than distorted by cosine normalization.

OnlineRecommender.from_model(model) serves a trained ALS, Item2Vec, LightGCN or TwoTower model, and returns the same ranking as model.recommend(). Other models don’t rank by a user-item dot product, so from_model raises NotImplementedError for them; keep those behind ModelServer instead.

Freshness: updating without retraining#

A live feed changes constantly. Update the served index in place:

# new content arrives -> add it to the live index (no retrain)
rec.add_items(item_ids=["post_991", "post_992"], item_emb=new_item_vectors)

# a new or returning user, described by their latest interactions ->
# fold them in (ridge solve over item factors, no retrain)
rec.fold_in_user("user_777", item_ids=["post_12", "post_44", "post_991"])
rec.recommend("user_777", top_k=10)

Cold-start#

Unknown users do not raise; they receive a popularity-based fallback ranking:

rec.recommend("brand_new_user", top_k=10)   # -> popular items, not an error

Monitoring#

rec.stats()
# {'n_items': ..., 'n_users': ..., 'index_type': 'hnsw', 'faiss': True,
#  'queries_served': ..., 'latency_ms_p50': ..., 'latency_ms_p99': ...}

Scale and performance#

On Gowalla (29,858 users, 40,981 items, ~1.0M interactions) the engine builds in seconds, serves at ~0.09 ms/user (p50), ~10,000 req/s single-thread, and holds ~1.5 GB of memory — versus tens of GB for dense co-occurrence models. FAISS HNSW scales to a 1M-item index searched in ~0.02 ms/query, and the approximate ANN preserves 88–99% of exact-search accuracy.

Installing the ANN backend#

pip install faiss-cpu   # or faiss-gpu

Without FAISS, VectorIndex falls back to brute-force NumPy (correct but O(catalogue)); install FAISS for sub-linear serving.

What still belongs to infrastructure#

OnlineRecommender is the retrieval/ranking engine. A full production feed still wraps it with a hardened RPC server, a feature store, and a streaming source that calls add_items/fold_in_user — those are operational layers, not modelling.