HCGRec is a research codebase for semantic-ID generative recommendation. The repository covers the full workflow used in the project: raw Amazon-style filtering, semantic ID construction, supervised fine-tuning with LLaMA-Factory, hint-conditioned GRPO training, and constrained-decoding evaluation.
HCGRec focuses on a training failure mode in recommendation-oriented RL. In many rollout groups, none of the sampled continuations reaches the target item branch, so every sample receives the same reward and the update carries little or no useful learning signal. This repository adds hint-aware training on top of a standard SID-based generative recommendation stack.
- Reachability diagnosis checks whether the current model can reach the target SID branch under the rollout budget.
- Training-only hints expose a short target prefix for hard samples during RL.
- Hint-aware optimization separates hinted prefix tokens from sampled suffix tokens during the policy update.
- Constrained decoding keeps generation inside the valid SID set during evaluation.
| Path | Description |
|---|---|
src/hcgrec/ |
HCGRec training, hint analysis, constrained evaluation, and utility modules |
src/index/ |
embedding extraction, RQ-VAE index training, SID export, and index evaluation |
src/rewards/ |
reward implementations for RL training |
scripts/experiments/<Domain>/ |
paper-facing wrappers for data prep, SFT, and RL on Instruments, Games, and Arts |
scripts/index/base/ |
base SID pipeline: text2emb, train, generate, and evaluate |
scripts/eval/ |
single-checkpoint and multi-checkpoint evaluation helpers |
scripts/data/ |
generic dataset conversion helpers |
examples/train_full/ |
LLaMA-Factory YAML configs used by SFT launchers |
config/ |
DeepSpeed configs from zero0 to zero3_offload |
The supported install path is uv. This repo is a normal src/ layout package, and the installable Python package name is hcgrec.
uv venv --python 3.12
source .venv/bin/activate
uv sync.env.example is only a template. Nothing in the current repo auto-loads it. If you want reusable local defaults, copy it to something like .env and source it yourself before launching the scripts.
If you already have raw category files and an index under data/Instruments/, the shortest path is the per-domain wrapper:
bash scripts/experiments/Instruments/prepare.sh check
bash scripts/experiments/Instruments/prepare.sh build-dataBy default this writes data/Instruments_grec_index/ with:
sft/train.json,sft/valid.json,sft/test.jsonrl/train.json,rl/valid.json,rl/test.jsonid2sid.jsonnew_tokens.json
Use the corresponding wrappers under scripts/experiments/Games/ and scripts/experiments/Arts/ for those domains.
bash scripts/experiments/Instruments/sft.sh --dry-run
bash scripts/experiments/Instruments/sft.shThe default Instruments launcher points at examples/train_full/Instruments/instruments_rec_full_sft_3b_dsz3_qwen4b_4_256_grec_genrec_aligned_8gpu.yaml. To switch configs, override YAML_PATH.
Run the preflight first:
python scripts/check_rl_env.py \
--model-path /path/to/sft_checkpoint \
--data-dir data/Instruments_grec_index/rl \
--index-path data/Instruments_grec_index/id2sid.json \
--output-dir rl_outputs/instruments_hcgrec_smokeThen launch RL:
MODEL_PATH=/path/to/sft_checkpoint \
bash scripts/experiments/Instruments/rl_rule.sh --dry-run
MODEL_PATH=/path/to/sft_checkpoint \
bash scripts/experiments/Instruments/rl_rule.shOther RL variants live beside it; see the variant table below for what each script does.
bash scripts/eval/evaluate_checkpoint.sh \
--checkpoint-path /path/to/checkpoint \
--test-data-path data/Instruments_grec_index/sft/test.json \
--index-path data/Instruments_grec_index/id2sid.jsonFor direct Python evaluation:
python -m hcgrec.evaluate \
--model_name_or_path /path/to/checkpoint \
--test_data_path data/Instruments_grec_index/sft/test.json \
--index_path data/Instruments_grec_index/id2sid.json \
--result_json_path temp/eval/instruments_result.json- Python
3.11to3.13 - One or more CUDA GPUs for training
- A causal LLM checkpoint for SFT and RL
- An embedding model checkpoint for SID construction
Use uv for the full workspace setup:
uv venv --python 3.12
source .venv/bin/activate
uv syncUseful environment variables:
HF_ENDPOINTif you use a Hugging Face mirrorWANDB_API_KEY,WANDB_PROJECT,WANDB_MODEfor experiment trackingHF_HUB_OFFLINE=1,TRANSFORMERS_OFFLINE=1,HF_DATASETS_OFFLINE=1for offline runsCUDA_VISIBLE_DEVICES,CUDA_LIST,RESULTS_ROOT, andTEMP_ROOTfor launcher control
If you only want the external training CLI outside this repo, the equivalent standalone install is:
uv pip install "llamafactory==0.9.5"The raw Amazon18-style preprocessing entrypoint is data/amazon18_data_process.py.
If your raw files follow the script defaults, run it inside data/:
cd data
python amazon18_data_process.py \
--dataset Instruments \
--user_k 5 \
--item_k 5 \
--st_year 1996 \
--st_month 10 \
--ed_year 2018 \
--ed_month 10 \
--output_path .This mode expects files such as meta_Instruments.json and Instruments_5.json in the current directory.
If your raw filenames or locations differ, call the Python entrypoint from the repo root and pass them explicitly:
python data/amazon18_data_process.py \
--dataset Instruments \
--metadata_file /path/to/meta_Instruments.json \
--reviews_file /path/to/Instruments_5.json \
--output_path dataThe script writes files such as:
data/Instruments/Instruments.train.interdata/Instruments/Instruments.valid.interdata/Instruments/Instruments.test.interdata/Instruments/Instruments.item.jsondata/Instruments/Instruments.review.jsondata/Instruments/Instruments.inter.json
The base SID pipeline under scripts/index/base/ is:
text2emb.shtrain.shgenerate.shevaluate.sh
Extract item embeddings:
DATASETS="Instruments" \
PLM_NAME="qwen3-embedding-4B" \
MODEL_PATH="/path/to/embedding-model" \
NUM_PROCESSES=1 \
BATCH_SIZE=256 \
bash scripts/index/base/text2emb.shTrain the index model:
USE_MULTI_DATASETS=false \
DATASET=Instruments \
MODEL_NAME=qwen3-embedding-4B \
NPROC_PER_NODE=1 \
BATCH_SIZE=2048 \
bash scripts/index/base/train.shGenerate SID files from a trained checkpoint:
USE_MULTI_DATASETS=false \
DATASET=Instruments \
MODEL_NAME=qwen3-embedding-4B \
CKPT_PATH=/path/to/best_collision_model.pth \
bash scripts/index/base/generate.shEvaluate the index checkpoint:
CKPT_PATH=/path/to/best_collision_model.pth \
DEVICE=cuda:0 \
BATCH_SIZE=2048 \
bash scripts/index/base/evaluate.shFor the current paper workflow, prefer the domain wrappers:
bash scripts/experiments/Instruments/prepare.sh check
bash scripts/experiments/Instruments/prepare.sh build-dataEquivalent wrappers exist for:
scripts/experiments/Games/prepare.shscripts/experiments/Arts/prepare.sh
These wrappers validate the raw category directory, pick the resolved index file, build the SFT and RL JSON files, and update data/dataset_info.json for LLaMA-Factory.
The SFT wrappers are thin shells around scripts/experiments/_canonical_sft_launcher.sh.
bash scripts/experiments/Instruments/sft.sh --dry-run
bash scripts/experiments/Instruments/sft.shTo switch domains:
bash scripts/experiments/Games/sft.shbash scripts/experiments/Arts/sft.sh
To switch the exact YAML, override YAML_PATH with a file from examples/train_full/.
The RL wrappers are thin shells around scripts/experiments/_canonical_rl_launcher.sh.
Start with a preflight:
python scripts/check_rl_env.py \
--model-path /path/to/sft_checkpoint \
--data-dir data/Instruments_grec_index/rl \
--index-path data/Instruments_grec_index/id2sid.json \
--output-dir rl_outputs/instruments_hcgrec_smokeThen launch the paper-style rule-only run:
MODEL_PATH=/path/to/sft_checkpoint \
bash scripts/experiments/Instruments/rl_rule.sh --dry-run
MODEL_PATH=/path/to/sft_checkpoint \
bash scripts/experiments/Instruments/rl_rule.shEach domain wrapper directory ships a subset of these RL launchers. They differ only in the flags they pass to scripts/experiments/_canonical_rl_launcher.sh:
| Script | Hint mode | Reward mode | Extra terms | Trainer class | Domains |
|---|---|---|---|---|---|
rl_rule.sh |
none | rule_only |
— | trl.GRPOTrainer (vanilla GRPO) |
all |
rl_fixed.sh |
fixed prefix, per-sample depth map | rule_only |
— | FixedHintRuleOnlyGRPOTrainer |
all |
rl_fixed_ce.sh |
fixed prefix, per-sample depth map | rule_only |
prefix CE loss on hinted tokens (HINT_CE_LOSS_COEF, 0.005) |
FixedHintRuleOnlyGRPOTrainer |
all |
rl_fixed_ce_dual.sh |
fixed prefix, per-sample depth map | rule_only |
prefix CE loss; trains on task1_sid_sft + task5_title_desc2sid, evaluates on task1_sid_sft |
FixedHintRuleOnlyGRPOTrainer |
Instruments only |
rl_fixed_full_sequence_sft.sh |
fixed prefix, per-sample depth map | rule_only |
full-sequence SFT regularization (FULL_SEQUENCE_SFT_LOSS_COEF, 0.001) |
FixedHintRuleOnlyGRPOTrainer |
Instruments, Arts |
rl_dynamic.sh |
dynamic per-rollout hints (DYNAMIC_HINT_MAX_DEPTH, 3) |
rule_only |
— | DynamicHintRuleOnlyGRPOTrainer |
Instruments, Arts |
rl_ndcg.sh |
none | ranking |
— | trl.GRPOTrainer |
all |
Notes on how to read this:
- Fixed hint means an offline reachability diagnosis runs before training:
hcgrec.analyze_rl_beam_hintbeam-searches the SFT model and exports a per-sample hint-depth map, which the trainer then uses to reveal a target-SID prefix on hard samples. Diagnosis artifacts are cached undertemp/rl_beam_hint/artifacts/; setFORCE_REANALYZE=trueto rebuild them. - Dynamic hint skips the offline pass and escalates the hint depth during rollout itself, up to
DYNAMIC_HINT_MAX_DEPTH. hint_ce_loss_coefandfull_sequence_sft_loss_coefare mutually exclusive (enforced inhcgrec.trl_trainer).
Use the Python module for direct constrained decoding:
python -m hcgrec.evaluate \
--model_name_or_path /path/to/checkpoint \
--test_data_path data/Instruments_grec_index/sft/test.json \
--index_path data/Instruments_grec_index/id2sid.json \
--result_json_path temp/eval/instruments_result.jsonUse the shell wrapper for checkpoint-oriented evaluation:
bash scripts/eval/evaluate_checkpoint.sh \
--checkpoint-path /path/to/checkpoint \
--test-data-path data/Instruments_grec_index/sft/test.json \
--index-path data/Instruments_grec_index/id2sid.jsonFor multi-checkpoint or watcher workflows, see:
scripts/eval/evaluate_all_checkpoints.shpython -m hcgrec.evaluate_all_checkpoints_sidecar
scripts/ops/ contains repo-local helpers for syncing results, uploader state, and evaluation maintenance. They are optional and not required for the main training pipeline.
@article{zhang2026hcgrec,
title = {Learning from Unreachable Rewards: Hint-Conditioned Reinforcement Learning for Generative Recommendation},
author = {Zhang, Kangning and Fang, Haotian and Luo, Xukun and Yin, Hao and Gao, Yang and Yan, Peng and Liu, Weiwen and Zhang, Weinan and Yu, Yong},
journal = {arXiv preprint arXiv:2608.11980},
year = {2026}
}- LLaMA-Factory for the SFT training stack
- TRL for GRPO and reward-model training utilities
- Open-source generative recommendation work that helped shape the SID-based training and evaluation workflow