Qwen3-4B QLoRA -- Generative Recommender over Semantic IDs (v2)

QLoRA fine-tune of Qwen/Qwen3-4B to reason over semantic IDs -- short discrete codes produced by an RQ-VAE trained on game-catalog item embeddings -- instead of ever seeing raw item IDs or embeddings directly. Trained on the paired dataset pblrvo/steam-games-semanticIds-instructions.

This is a second iteration over pblrvo/Qwen3-4B-Game-semantic-IDs (v1), kept as a separate model rather than replacing it -- the two make different tradeoffs (see Evaluation below) and v1 is still the better choice for some use cases.

What changed from v1

  • Grounding train/val split fixed. v1 held out entire items from grounding's val set, so grounding was tested on items whose name<->SID mapping was structurally never in training -- not a fair test. v2 splits within each item's repeated examples instead, so every item is trained on and val tests recall under an unseen phrasing.
  • Two new task types: nl_preference (open-ended natural-language queries, e.g. "I want a racing game") and nl_similar_item (natural-language "recommend something like X" queries), both grounded in the same catalog/co-occurrence data as the existing tasks.
  • Cross-task exposure cap. An item could independently hit the ceiling in several recommendation tasks at once (sequential + asy + similar_item + nl_similar_item), compounding to ~140 total exposures for popular items vs. a dozen for a typical one -- this is what was driving the v1 model's tendency to over-recommend a handful of popular titles regardless of input. v2 caps combined exposure at 40, deliberately excluding grounding (which stays exactly uniform per item).
  • Stage 1 (embedding warmup) redesigned per STAR (arXiv, "Semantic-ID Token-Embedding Alignment for Generative Recommenders"): gradient-masked so only the new SID tokens update (the pretrained vocabulary stays frozen, previously it was inadvertently trainable too), and restricted to grounding-only text<->SID pairs instead of a mix of all task types.
  • grounding_id2name enriched with a short "About the game" snippet alongside name+genres, so the model grounds SIDs against real content, not just a name/genre tag.

Pipeline

Game catalog items -> embedded (Qwen3-0.6B) -> compressed into 4-level semantic IDs via RQ-VAE -> those ID tokens become new vocabulary the LLM is fine-tuned to reason over. Two-stage fine-tuning:

  1. Embedding warmup: only the new semantic-ID token embeddings are trained (codebook-grounded initialization + gradient-masked, grounding-only high-LR run) so the new tokens carry meaningful, linguistically grounded structure before task-specific training begins.
  2. QLoRA (this checkpoint): a real LoRA adapter (rank 8) across all attention/MLP projections, trained on top of stage 1's warmed-up embeddings, via Unsloth.

Both stages run through 4-bit quantization -- full-parameter fine-tuning of a ~4B model doesn't fit a single 12GB consumer GPU alongside gradients/optimizer state.

Tasks

  • Sequential recommendation: predict the next item's semantic ID from a user's play history
  • Grounding: map a semantic ID <-> item name/genres(+short description), both directions
  • Similar item: given an item, suggest another one real users also engaged with
  • NL preference: open-ended natural-language preference queries ("I want a racing game") -> a matching item's semantic ID
  • NL similar item: natural-language "recommend something like X" queries, same ground truth as Similar item
  • ASY (asymmetric item prediction, LC-Rec, arXiv 2311.09049): same history/target pairs as sequential, rendered as the target's name instead of its semantic ID

Evaluation

Recall@K / NDCG@K via constrained beam search (candidates restricted to real catalog items through a trie over valid semantic IDs / descriptions), the same methodology TIGER (Rajput et al. 2023) and LC-Rec use for semantic-ID generative recommenders. n=100 per task, temperature=0.8, beam=10; catalog size ~8,563 items, so random-chance Recall@10 is ~0.12%.

Task v1 Recall@5 v2 Recall@5 v1 Recall@10 v2 Recall@10 v1 NDCG@10 v2 NDCG@10
grounding_name2id 2% 29% 3% 38% 0.012 0.263
grounding_id2name 0% 0% 0% 0% 0.000 0.000
sequential 12% 3% 13% 3% 0.094 0.025
similar_item 10% 4% 13% 6% 0.082 0.032
nl_similar_item -- (new) 3% -- 6% -- 0.033
nl_preference -- (new) 80% -- 85% -- 0.661

This is a real tradeoff, not a clean win. Grounding (name<->SID) improved dramatically. But sequential/similar_item regressed substantially under strict exact-match Recall@K -- most likely because the cross-task exposure cap cut raw training volume for those tasks by ~40%, trading some ability to memorize the specific recorded co-occurrence pairing for eliminating the popularity- collapse shortcut (v1's tendency to recommend the same few popular items regardless of input).

Qualitative spot-checks (10 examples each, decoded to game names) support that read: v2's predictions for sequential/similar_item are consistently genre/franchise-coherent and fully diverse (no repeated defaults) even when they miss the specific recorded target -- e.g. a racing-game seed predicts another racing game, a Saints Row 2 history predicts Saints Row: The Third. Recall@K can't credit a plausible-but-different answer, so the metric likely understates v2's real recommendation quality on these tasks. Checkpoint-by-checkpoint Recall@K was also flat across the entire back half of training (no late-training improvement), consistent with a data-volume ceiling rather than needing more steps.

grounding_id2name stayed at 0% in both versions, but for different reasons: v1 essentially never got the exact answer despite valid-format output; v2's target now includes a short free-text description snippet, making exact string match a much harder bar to clear even for a model that has correctly grounded the SID's meaning.

Known limitations

  • Choosing between v1 and v2 depends on the task you care about. v1 is meaningfully better at sequential/similar_item's strict exact-match recall; v2 is dramatically better at grounding and adds two new natural-language query tasks.
  • grounding_id2name is weak in both versions, likely for different reasons in each -- see above.
  • Compute-constrained training. QLoRA rank 8, ~0.9 epochs of the fine-tuning stage (wall-clock capped on a single 12GB GPU, not run to convergence).
  • No classical-recommender baseline (e.g. SASRec) has been run against the same data -- these numbers show the model beats random chance substantially, not that the semantic-ID + LLM approach outperforms a much simpler sequential recommender.
  • Single training run, single seed, throughout.

Usage

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import PeftModel

ADAPTER = "pblrvo/Qwen3-4B-Game-semantic-IDs-v2"
BASE_MODEL = "Qwen/Qwen3-4B"

tokenizer = AutoTokenizer.from_pretrained(ADAPTER)
quantization_config = BitsAndBytesConfig(
    load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True, llm_int8_skip_modules=["lm_head"],
)
base_model = AutoModelForCausalLM.from_pretrained(BASE_MODEL, dtype=torch.bfloat16, quantization_config=quantization_config)
base_model.resize_token_embeddings(len(tokenizer))
model = PeftModel.from_pretrained(base_model, ADAPTER)

The semantic-ID vocabulary (<|sid_start|>, <|sid_L{level}_{code}|>, <|sid_end|>) is only meaningful relative to the specific RQ-VAE codebook trained in the source project -- this model isn't usable standalone without that catalog/codebook context.

Downloads last month
34
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for pblrvo/Qwen3-4B-Game-semantic-IDs-v2

Finetuned
Qwen/Qwen3-4B
Adapter
(1105)
this model

Dataset used to train pblrvo/Qwen3-4B-Game-semantic-IDs-v2