# OSF (Orthogonal Subspace Fine-tuning)

Fine-tuning Subruang Ortogonal ([OSF](https://huggingface.co/papers/2504.07097)) adalah metode PEFT yang dirancang untuk pembelajaran berkelanjutan, yang membatasi pembaruan parameter agar ortogonal terhadap arah penting sebelumnya. Pendekatan ini memungkinkan fine-tuning penuh sambil mencegah catastrophic forgetting tanpa memerlukan parameter tambahan atau penyimpanan gradien sebelumnya.

Abstrak dari makalah tersebut adalah:

*Pembelajaran berkelanjutan pada model bahasa besar (LLM) rentan terhadap pelupaan katastrofik, di mana adaptasi terhadap tugas baru secara signifikan menurunkan kinerja pada tugas-tugas yang telah dipelajari sebelumnya. Metode yang ada umumnya mengandalkan pembaruan ber-rank rendah yang efisien parameter, yang membatasi ekspresivitas model dan memperkenalkan parameter tambahan per tugas, sehingga menimbulkan masalah skalabilitas. Untuk mengatasi keterbatasan ini, kami mengusulkan pendekatan fine-tuning penuh berkelanjutan yang baru dengan memanfaatkan dekomposisi nilai singular adaptif (SVD). Metode kami secara dinamis mengidentifikasi subruang parameter ber-rank rendah yang spesifik tugas dan membatasi pembaruan agar ortogonal terhadap arah-arah kritis yang terkait dengan tugas-tugas sebelumnya, sehingga secara efektif meminimalkan interferensi tanpa overhead parameter tambahan atau penyimpanan gradien tugas sebelumnya. Kami mengevaluasi pendekatan kami secara luas pada benchmark pembelajaran berkelanjutan standar menggunakan model encoder-decoder (T5-Large) dan decoder-only (LLaMA-2 7B), mencakup berbagai tugas termasuk klasifikasi, generasi, dan penalaran. Secara empiris, metode kami mencapai hasil state-of-the-art, dengan akurasi rata-rata hingga 7% lebih tinggi dibandingkan baseline terbaru seperti O-LoRA, dan secara signifikan mempertahankan kemampuan linguistik umum model, akurasi mengikuti instruksi, dan keselamatannya selama proses pembelajaran berkelanjutan dengan mengurangi pelupaan ke tingkat yang hampir dapat diabaikan. Kerangka SVD adaptif kami secara efektif menyeimbangkan plastisitas model dan retensi pengetahuan, menyediakan solusi yang praktis, berlandaskan teori, dan skalabel secara komputasi untuk skenario pembelajaran berkelanjutan pada model bahasa besar.*

## Cara Kerja OSF

OSF menguraikan setiap matriks bobot menjadi komponen ber-rank tinggi (beku) dan ber-rank rendah (dapat dilatih) menggunakan SVD:

```
W = U_high * S_high * V_high^T + U_low * S_low * V_low^T
```

Di mana:
- `U_high, S_high, V_high`: Mempertahankan arah penting dari tugas sebelumnya (dibekukan)
- `U_low, S_low, V_low`: Memungkinkan adaptasi ke tugas baru (dapat dilatih)

Selama pelatihan, gradien diproyeksikan agar ortogonal terhadap subruang ber-rank tinggi, sehingga memastikan pembaruan tidak mengganggu pengetahuan yang telah dipelajari sebelumnya.

## Penggunaan Dasar

```python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import OSFConfig, get_peft_model

# Load base model
model = AutoModelForCausalLM.from_pretrained("facebook/opt-125m")

# Configure OSF
config = OSFConfig(
    target_modules=["q_proj", "v_proj"],  # Target attention layers
    effective_rank=8,                     # Default rank for decomposition
    rank_pattern={"q_proj": 16}           # Override rank for specific modules
)

# Apply OSF
model = get_peft_model(model, config)

# Train as usual
optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4)

tokenizer = AutoTokenizer.from_pretrained("facebook/opt-125m")

inputs = tokenizer("Hello world", return_tensors="pt", padding=True)
loss = model(**inputs, labels=inputs.input_ids).loss
loss.backward()
optimizer.step()
optimizer.zero_grad()
```

## Opsi Konfigurasi

### Modul Target

Anda dapat menentukan modul target dengan beberapa cara:

```python
# Specific module names
config = OSFConfig(target_modules=["q_proj", "k_proj", "v_proj", "o_proj"])

# All linear layers
config = OSFConfig(target_modules="all-linear")

# Model-specific defaults (automatically detected)
config = OSFConfig()  # Uses model-appropriate defaults
```

### Konfigurasi Rank Efektif

Kendalikan subspace yang dipertahankan/dapat dilatih:

```python
# Global preserved rank (applies to all target modules)
config = OSFConfig(effective_rank=16)  # preserves top-16 singular directions; trains the rest

# Automatic preserved rank (50% of the smaller matrix dimension per target)
config = OSFConfig(effective_rank=None)

# Per-module preserved-rank overrides
config = OSFConfig(
    effective_rank=8,
    rank_pattern={
        "q_proj": 16,      # Higher rank for query projection
        "gate_proj": 4     # Lower rank for gate projection
    }
)

# Fractional preserved rank is supported (interpreted per-target as fraction * min_dim)
config = OSFConfig(effective_rank=0.8)  # preserve 80% of min_dim; train remaining 20%
config = OSFConfig(rank_pattern={"q_proj": 0.5})  # preserve 50% on q_proj, others use global/default
```

Catatan: `effective_rank` OSF adalah rank yang dipertahankan (dibekukan), bukan rank yang dapat dilatih. Rank yang dapat dilatih sama dengan `min(weight.shape) - effective_rank`. Ini berbeda dari `r` LoRA, yang secara langsung menentukan rank yang dapat dilatih.

## Saran Pelatihan untuk Pembelajaran Berkelanjutan

### Pembelajaran Tugas Berurutan

OSF dirancang secara khusus untuk tugas pembelajaran secara berurutan. Di antara tugas-tugas, hitung ulang SVD agar subruang yang dipertahankan mencerminkan bobot terbaru. Salah satu cara sederhana adalah membungkus ulang model dasar yang telah diperbarui dengan OSF lagi:

```python
# Task 1: train on domain A with initial preserved subspace
r = 8  # initial effective rank to preserve
model = get_peft_model(base_model, OSFConfig(effective_rank=r))
train_task(model, task_1_data)

# Task 2: recompute SVD on updated weights and increase preserved subspace
base_model = model.unload()  # unwrap base model without assuming internals
r += 4  # grow preserved subspace to include Task 1 knowledge
model = get_peft_model(base_model, OSFConfig(effective_rank=r))
train_task(model, task_2_data)

# Task 3: recompute again and expand preserved subspace further
base_model = model.unload()
r += 4
model = get_peft_model(base_model, OSFConfig(effective_rank=r))
train_task(model, task_3_data)
```

### Alokasi Anggaran untuk Urutan Tugas

Saat melatih pada urutan n tugas yang diketahui, satu strategi yang efektif adalah mengalokasikan kapasitas model secara bertahap untuk menyeimbangkan pembelajaran tugas baru sambil mempertahankan pengetahuan sebelumnya:

- **Tugas 1**: Gunakan kapasitas penuh (latih semuanya)
- **Tugas 2**: Bekukan 1/n dari kapasitas model, latih sisa (n-1)/n kapasitas
- **Tugas 3**: Bekukan 2/n dari kapasitas model, latih sisa (n-2)/n kapasitas
- **Tugas n**: Bekukan (n-1)/n kapasitas model, gunakan 1/n kapasitas untuk tugas akhir

Pendekatan ini memastikan setiap tugas mendapatkan kapasitas pembelajaran yang memadai sambil secara progresif mempertahankan lebih banyak pengetahuan dari tugas-tugas sebelumnya.

```python
# Example: 4-task sequence with progressive budget allocation
n_tasks = 4
max_preserved_rank = 512  # Upper bound for preserved rank per target (heuristic)

for task_id in range(n_tasks):
    # Freeze increases over time; trainable capacity shrinks
    preserved_fraction = (task_id + 1) / n_tasks
    preserved_rank = int(max_preserved_rank * preserved_fraction)

    config = OSFConfig(
        target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
        effective_rank=preserved_rank,
    )

    print(
        f"Task {task_id + 1}: Preserving rank {preserved_rank} "
        f"({preserved_fraction:.1%} of max_preserved_rank - {max_preserved_rank} frozen); trainable rank = min_dim - preserved_rank"
    )

    model = get_peft_model(base_model, config)
    train_task(model, task_data[task_id])
```

### Praktik Terbaik

1. **Seleksi Rank Efektif**: Mulai dengan `effective_rank=None` (otomatis mengatur rank menjadi 50% dari dimensi bobot yang lebih kecil per modul target) dan sesuaikan berdasarkan kompleksitas tugas
2. **Learning Rate**: Gunakan learning rate yang lebih kecil (1e-5 hingga 1e-4) dibandingkan fine-tuning standar
3. **Pentingnya Tugas**: Gunakan `rank_pattern` untuk mengalokasikan lebih banyak kapasitas ke modul-modul kritis
4. **Arsitektur Model**: OSF bekerja paling baik dengan arsitektur transformer yang memiliki pemisahan yang jelas antara attention dan MLP
5. **Perencanaan Kapasitas**: Untuk urutan tugas yang diketahui, gunakan alokasi anggaran progresif (pembekuan 1/n, 2/n, ..., (n-1)/n) untuk menyeimbangkan plastisitas dan stabilitas

### Pertimbangan Memori

OSF memodifikasi bobot secara in-place dan tidak menambahkan parameter, sehingga hemat memori:

```python
# Memory usage remains close to base model
print(f"Base model parameters: {base_model.num_parameters():,}")
print(f"OSF model parameters: {osf_model.num_parameters():,}")  # Similar count
```

## Penggunaan Lanjutan

### Modul Target Kustom

Untuk model dengan arsitektur non-standar:

```python
config = OSFConfig(
    target_modules=["dense", "intermediate.dense"],  # Custom layer names
    effective_rank=12,
    rank_pattern={"dense": 8, "intermediate.dense": 16}
)
```

### Integrasi dengan Metode Lain

OSF dapat dikombinasikan dengan teknik lain:

```python
# Use with gradient checkpointing for memory efficiency
# note: use `model.gradient_checkpointing_enable()` for older Transformers versions
model.activation_checkpointing_enable()

# Apply weight decay selectively (regularizes low-rank factors to limit drift/overfitting in continual updates; keep small)
optimizer = torch.optim.AdamW([
    {"params": [p for n, p in model.named_parameters() if "U_low" in n], "weight_decay": 0.01},
    {"params": [p for n, p in model.named_parameters() if "S_low" in n], "weight_decay": 0.001},
    {"params": [p for n, p in model.named_parameters() if "V_low" in n], "weight_decay": 0.01},
], lr=1e-4)
```

## Ikhtisar benchmark

<iframe src="https://peft-internal-testing-peft-method-comparison-embed.hf.space/?highlight[type]=OSF" frameborder="0" width="850" height="1000"
>

# API

## OSFConfig[[peft.OSFConfig]]

#### peft.OSFConfig[[peft.OSFConfig]]

```python
peft.OSFConfig(task_type: Optional[Union[str, TaskType]] = None, peft_type: Optional[Union[str, PeftType]] = None, auto_mapping: Optional[dict] = None, peft_version: Optional[str] = None, base_model_name_or_path: Optional[str] = None, revision: Optional[str] = None, inference_mode: bool = False, effective_rank: Optional[Union[int, float]] = None, target_modules: Optional[Union[list[str], str]] = None, rank_pattern: Optional[dict[str, Union[int, float]]] = None, init_weights: Optional[bool] = True, modules_to_save: Optional[list[str]] = None, target_svd_config: Optional[dict[str, int]] = None)
```

[Sumber](https://github.com/huggingface/peft/blob/main/src/peft/tuners/osf/config.py#L11)

**Parameter:**

effective_rank (*int* atau *float*, *opsional*) : Rank SVD yang dipertahankan (subruang "tinggi"). Arah singular teratas-`effective_rank` dibekukan dan dipertahankan di antara tugas; dimensi yang tersisa membentuk subruang rank-rendah yang dapat dilatih. Jika *None*, defaultnya adalah 50% dari dimensi bobot yang lebih kecil per modul target. Catatan: Ini berbeda dari *r* LoRA (rank yang dapat dilatih). Di OSF, rank yang dapat dilatih adalah *min(weight.shape) - effective_rank*.

target_modules (*Union[list[str], str]*, *opsional*) : Nama-nama modul untuk menerapkan OSF. Bisa berupa daftar nama modul atau *"all-linear"*.

rank_pattern (*dict[str, int|float]*, *opsional*) : Kamus pola regex untuk menimpa *effective_rank* pada modul tertentu. Nilainya dapat berupa bilangan bulat absolut atau pecahan dalam (0, 1], yang ditafsirkan sebagai pecahan dari dimensi matriks yang lebih kecil per target.

Konfigurasi untuk Orthogonal Subspace Fine-tuning (OSF).

## OSFModel[[peft.OSFModel]]

#### peft.OSFModel[[peft.OSFModel]]

```python
peft.OSFModel(model, config, adapter_name, low_cpu_mem_usage: bool = False, state_dict: dict[str, torch.Tensor] | None = None)
```

[Sumber](https://github.com/huggingface/peft/blob/main/src/peft/tuners/osf/model.py#L14)

Penala minimal yang menerapkan Orthogonal Subspace Fine-tuning.

## Fungsi Utilitas

### Dekomposisi Bobot[[peft.tuners.osf.utils.decompose_weight_matrix]]

#### peft.tuners.osf.utils.decompose_weight_matrix[[peft.tuners.osf.utils.decompose_weight_matrix]]

```python
peft.tuners.osf.utils.decompose_weight_matrix(weight: torch.Tensor, top_k: int)
```

[Sumber](https://github.com/huggingface/peft/blob/main/src/peft/tuners/osf/utils.py#L41)

Lakukan SVD pada `weight` dan bagi menjadi bagian beku dan bagian yang dapat dilatih.

### Proyeksi Gradien[[peft.tuners.osf.utils.project_gradient_to_orthogonal_space]]

#### peft.tuners.osf.utils.project_gradient_to_orthogonal_space[[peft.tuners.osf.utils.project_gradient_to_orthogonal_space]]

```python
peft.tuners.osf.utils.project_gradient_to_orthogonal_space(svd_dict: dict[str, Any])
```

[Sumber](https://github.com/huggingface/peft/blob/main/src/peft/tuners/osf/utils.py#L61)

Proyeksikan gradien dari `U_low` dan `V_low` agar ortogonal terhadap ruang ber-rank tinggi.

