Public release of PerFit: Exploring Personalization Shifts in Representation Space of LLMs (accepted at ICLR 2026) for parameter-efficient personalization on LaMP tasks.
💡 Hypothesis (Observation)
- Personalized preference information induces structured, low-rank shifts in the LLM representation space.
- These shifts can be decomposed into a shared global direction plus user-specific deviations, enabling parameter-efficient personalization.
🛠️ Methodology
PerFit is a representation-space personalization method for LLMs. Instead of tuning full model parameters, it learns low-rank interventions on hidden states to steer outputs toward user preferences with much lower parameter cost.
PerFit is trained in two stages:
- Stage 1 (collective shift learning): learn a shared task-level shift from all users.
- Stage 2 (personalized shift learning): adapt user-specific local shifts on top of the shared shift.
- Python:
3.12 - OS: Linux recommended
- GPU: CUDA-capable GPU recommended for training/inference
Create environment and install dependencies:
conda create -n perfit python=3.12 -y
conda activate perfit
pip install -r requirements.txtSet --model_path to your local base model directory (required for local/offline usage).
Run a task:
bash run.sh --task_name=movie_tagging --cuda_device=0 --model_path /path/to/base-model
bash run.sh --task_name=news_categorize --cuda_device=0 --model_path /path/to/base-model
bash run.sh --task_name=news_headline --cuda_device=0 --model_path /path/to/base-modelIf the stage-1 checkpoint already exists, run.sh reuses it automatically.
.
├── run.sh
├── train/
│ ├── stage1_train.py
│ └── stage2_train.py
├── lib/
│ ├── reft_module.py
│ └── utils.py
├── Config
│ ├── requirements.txt
│ ├── configs/presets.json
│ └── configs/prompt.json
├── Evaluation
│ └── eval/
│ ├── eval_task.py
│ ├── evaluation.py
│ └── metrics/
│ ├── accuracy/
│ ├── f1/
│ └── rouge/
└── Data
└── data/
├── movie_tagging/
├── news_categorize/
└── news_headline/
| File | Role |
|---|---|
run.sh |
Runs the full pipeline |
train/stage1_train.py |
Stage-1 training |
train/stage2_train.py |
Stage-2 training and prediction |
lib/reft_module.py |
Core intervention modules |
eval/eval_task.py |
Final metric computation |
configs/presets.json |
Default task presets |
Each task directory under data/<task>/ should contain:
user_others.jsonuser_top_100_history.jsonuser_top_100_history_label.json
| Task | LRank | ULRank | IL | PL | EP |
|---|---|---|---|---|---|
movie_tagging |
32 | 4 | 14;15 |
f7+l7 |
4 |
news_categorize |
16 | 32 | 14;15;16 |
f7+l7 |
6 |
news_headline |
32 | 4 | 14;15;16 |
l7 |
4 |
You can override the main preset values in run.sh with:
--task_name--cuda_device--run_dir_name--seed--batch_size--model_name--model_path--low_rank_dimension--user_low_rank_dimension--intervene_layer--positions--max_epoch
In configs/presets.json, each task includes a model_path placeholder ("") for user-side configuration.
A completed run writes:
output/<dataset>/lr=..._ulr=..._il=..._pl=..._ul=..._ua=.../task_reft/ckpt/...
output/<dataset>/lr=..._ulr=..._il=..._pl=..._ul=..._ua=.../perfit_data_type=.../predictions.json
output/<dataset>/lr=..._ulr=..._il=..._pl=..._ul=..._ua=.../result_data_type=.../user_top_100_history_all.json
@inproceedings{liuperfit,
title={PerFit: Exploring Personalization Shifts in Representation Space of LLMs},
author={Liu, Jiahong and Yu, Wenhao and Dai, Quanyu and Li, Zhongyang and Zhu, Jieming and Yang, Menglin and Chua, Tat-Seng and King, Irwin},
booktitle={The Fourteenth International Conference on Learning Representations},
year={2026}
}

