Skip to main content
Spec-only preview. The endpoints on this page describe what we are building. They are not live yet. Reach out to shape the surface or join the closed beta.
Classical recommenders give every item a random integer ID and learn an embedding row for it. That works, and it has three costs that get worse with scale: the embedding table grows with the catalog, a new item is meaningless until it has interactions, and nothing learned about one catalog transfers to another. Generative recommenders replace the random ID with a semantic ID: a short sequence of discrete codes derived from what the item actually is. Recommendation then becomes next-token prediction over a vocabulary where the tokens mean something.
The model is no longer retrieving from an index. It is generating an identifier, and every prefix of that identifier is a real region of item space.

Why the tokenizer is the bottleneck

Most of the interesting variance in generative recommendation sits in how you build the codes, not in the model that consumes them. A tokenizer that collapses distinct items into the same code caps your ceiling before training starts. One that ignores behavior produces codes that are semantically tidy and commercially useless. This is the part of the stack we think is underbuilt, and it is where this product sits.

How the codes are built

Encode content

Each item’s text and images go through frozen encoders, one per modality, concatenated into a single content vector. Nothing about interactions yet.

Quantize residually

An RQ-VAE assigns a code at level 1, takes the residual, assigns a code at level 2, and so on. Level 1 lands the item in a coarse neighborhood, later levels refine within it.

Fold in collaborative signal

Co-occurrence in real user sequences pulls on the quantizer, so items that get consumed together land near each other in code space. See below.

Resolve collisions

Items that quantize identically get a disambiguating suffix, so a semantic ID always maps to exactly one item.

Collaborative signal, and why not to fuse it directly

The obvious move is to concatenate a collaborative filtering embedding onto the content embedding before quantizing. It does not hold up. CF embeddings drift with popularity, so an item’s semantic ID would change as it trends, and a code that means something different this month is not an identifier. PLUM makes the better argument: use behavior as a training objective rather than an input feature. A co-occurrence contrastive loss on the quantizer pushes items that appear together in user sequences toward nearby codes, while the code itself stays a function of stable content. You get the collaborative structure without inheriting the drift. UTGRec arrives at a similar place from the transfer direction, using co-occurrence alignment and reconstruction so a single tokenizer generalizes across domains rather than being refit per catalog.

Multi-resolution codebooks

Uniform codebooks give every level the same cardinality, which wastes capacity: by level 4 there is very little residual entropy left to encode, and a wide codebook there mostly sits empty. PLUM sizes the codebook as a function of depth, 2048 / 2^(level-1): Paired with progressive masking, where training randomly truncates to the first r levels, this forces an actual hierarchy: the level-1 code has to be meaningful on its own, not just meaningful in combination with the levels below it. That property is what makes constrained beam search work well at decode time, because pruning on a prefix prunes a coherent region.

The API

Four endpoints. Fit a tokenizer, check on it, assign IDs, generate.
A generation response returns real items, because beam search is constrained to prefixes that exist in the codebook:
Full reference: Fit tokenizer, Get tokenizer, Assign semantic IDs, Generate.

Cold start

This is the property that tends to matter most in practice. A semantic ID is a function of content, so an item that went live sixty seconds ago and has zero interactions still gets a code in the right neighborhood, and the model can recommend it immediately. Pass a brand-new item to POST /semantic-ids and it is assigned against the existing codebooks without refitting. The response flags it:
Refit when the catalog’s distribution shifts, not when it grows.

What we are still deciding

Writing this page is partly how we are working out the product. Open questions, and we would rather hear from you than guess:
UTGRec argues for one tokenizer transferring across domains. A per-tenant fit is likely better on your catalog in isolation. The tradeoff is cold start quality on day one versus ceiling at maturity.
Some teams want POST /generate and nothing else. Others want to export semantic IDs and feed them into a ranker they already own. These imply fairly different products.
The whole approach assumes items carry real text or images. Catalogs where half the items are a bare SKU string change the design.

References