PEFT documentation

OSF (Orthogonal Subspace Fine-tuning)

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v0.21.0).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

OSF (Orthogonal Subspace Fine-tuning)

Fine-tuning trong không gian con trực giao (OSF) là một phương pháp PEFT được thiết kế cho học liên tục, trong đó các cập nhật tham số bị ràng buộc để trực giao với các hướng quan trọng trước đó. Cách tiếp cận này cho phép fine-tuning toàn bộ mô hình mà không gây ra sự quên thảm khốc, đồng thời không yêu cầu thêm tham số hay lưu trữ các gradient trước đó.

Tóm tắt từ bài báo là:

Học liên tục trong các mô hình ngôn ngữ lớn (LLM) dễ dẫn đến hiện tượng quên thảm khốc, trong đó việc thích ứng với các tác vụ mới làm suy giảm đáng kể hiệu suất trên các tác vụ đã học trước đó. Các phương pháp hiện có thường dựa vào các bản cập nhật tham số hiệu quả bậc thấp, điều này hạn chế khả năng diễn đạt của mô hình và đưa thêm các tham số cho mỗi tác vụ, dẫn đến các vấn đề về khả năng mở rộng. Để giải quyết những hạn chế này, chúng tôi đề xuất một phương pháp tinh chỉnh đầy đủ liên tục mới, tận dụng phân rã giá trị riêng thích ứng (SVD). Phương pháp của chúng tôi xác định động các không gian con tham số bậc thấp đặc thù cho từng tác vụ và ràng buộc các bản cập nhật phải trực giao với các hướng quan trọng liên quan đến các tác vụ trước đó, từ đó giảm thiểu hiệu quả sự can thiệp mà không cần thêm chi phí tham số hay lưu trữ gradient của các tác vụ trước. Chúng tôi đánh giá phương pháp của mình một cách toàn diện trên các bộ chuẩn đánh giá học liên tục tiêu chuẩn, sử dụng cả mô hình encoder-decoder (T5-Large) và mô hình chỉ có decoder (LLaMA-2 7B), trải dài qua nhiều tác vụ đa dạng bao gồm phân loại, sinh văn bản và suy luận. Về mặt thực nghiệm, phương pháp của chúng tôi đạt được kết quả tiên tiến nhất, với độ chính xác trung bình cao hơn tới 7% so với các đường cơ sở gần đây như O-LoRA, và đặc biệt duy trì được các khả năng ngôn ngữ tổng quát, độ chính xác tuân theo chỉ thị và tính an toàn của mô hình trong suốt quá trình học liên tục bằng cách giảm mức độ quên xuống gần như không đáng kể. Khung SVD thích ứng của chúng tôi cân bằng hiệu quả giữa tính dẻo dai của mô hình và khả năng giữ kiến thức, cung cấp một giải pháp thực tế, có cơ sở lý thuyết và có khả năng mở rộng về mặt tính toán cho các kịch bản học liên tục trong các mô hình ngôn ngữ lớn.

Cách OSF Hoạt Động

OSF phân rã mỗi ma trận trọng số thành các thành phần bậc cao (đóng băng) và bậc thấp (có thể huấn luyện) bằng SVD:

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

Trong đó:

  • U_high, S_high, V_high: Giữ lại các hướng quan trọng từ các tác vụ trước (đóng băng)
  • U_low, S_low, V_low: Cho phép thích ứng với các tác vụ mới (có thể huấn luyện)

Trong quá trình huấn luyện, các gradient được chiếu để trực giao với không gian con bậc cao, đảm bảo các bản cập nhật không gây nhiễu với kiến thức đã học trước đó.

Cách sử dụng cơ bản

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()

Tùy chọn cấu hình

Các module đích

Bạn có thể chỉ định các module đích theo một số cách:

# 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

Cấu hình Bậc Hiệu quả

Kiểm soát các không gian con được bảo tồn/đào tạo được:

# 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

Lưu ý: effective_rank của OSF là bậc (rank) được giữ lại (đóng băng), không phải bậc có thể huấn luyện. Bậc có thể huấn luyện bằng min(weight.shape) - effective_rank. Điều này khác với r của LoRA, vốn chỉ định trực tiếp bậc có thể huấn luyện.

Lời khuyên về huấn luyện cho Học liên tục

Học nhiệm vụ tuần tự

OSF được thiết kế đặc biệt cho các tác vụ học tuần tự. Giữa các tác vụ, hãy tính lại SVD để không gian con được bảo toàn phản ánh trọng số mới nhất. Một cách đơn giản là bọc lại mô hình cơ sở đã cập nhật bằng OSF một lần nữa:

# 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)

Phân bổ ngân sách cho các chuỗi tác vụ

Khi huấn luyện trên một chuỗi n tác vụ đã biết, một chiến lược hiệu quả là phân bổ dần sức mạnh của mô hình để cân bằng giữa việc học các tác vụ mới và bảo toàn kiến thức trước đó:

  • Nhiệm vụ 1: Sử dụng toàn bộ sức mạnh (huấn luyện mọi thứ)
  • Nhiệm vụ 2: Đóng băng 1/n dung lượng mô hình, huấn luyện phần dung lượng còn lại (n-1)/n
  • Nhiệm vụ 3: Đóng băng 2/n dung lượng mô hình, huấn luyện phần dung lượng còn lại (n-2)/n
  • Nhiệm vụ n: Đóng băng (n-1)/n dung lượng mô hình, sử dụng 1/n dung lượng cho nhiệm vụ cuối cùng

Cách tiếp cận này đảm bảo mỗi tác vụ có đủ năng lực học trong khi dần dần bảo tồn nhiều hơn kiến thức từ các tác vụ trước đó.

# 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])

Thực hành tốt nhất

  1. Chọn hiệu quả Rank: Bắt đầu với effective_rank=None (tự động đặt rank bằng 50% của chiều trọng số nhỏ hơn cho mỗi module đích) và điều chỉnh dựa trên độ phức tạp của tác vụ
  2. Tốc độ học: Sử dụng tốc độ học nhỏ hơn (1e-5 đến 1e-4) so với tinh chỉnh tiêu chuẩn
  3. Độ quan trọng của tác vụ: Sử dụng rank_pattern để phân bổ nhiều dung lượng hơn cho các module quan trọng
  4. Kiến trúc mô hình: OSF hoạt động tốt nhất với các kiến trúc transformer có sự tách biệt rõ ràng giữa attention và MLP
  5. Hoạch định năng lực: Đối với các chuỗi tác vụ đã biết, hãy sử dụng phân bổ ngân sách tiến bộ (đóng băng 1/n, 2/n, …, (n-1)/n) để cân bằng tính dẻo dai và tính ổn định

Xem xét bộ nhớ

OSF sửa đổi trọng số tại chỗ và không thêm tham số, giúp tiết kiệm bộ nhớ:

# 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

Cách sử dụng nâng cao

Các Module Mục Tiêu Tùy Chỉnh

Đối với các mô hình có kiến trúc không chuẩn:

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

Tích hợp với các phương pháp khác

OSF có thể được kết hợp với các kỹ thuật khác:

# Use with gradient checkpointing for memory efficiency
model.gradient_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)

Tổng quan về benchmark

API

OSFConfig

lớp peft.OSFConfig

< >

( task_type: Optional[Union[str, TaskType]] = Nonepeft_type: Optional[Union[str, PeftType]] = Noneauto_mapping: Optional[dict] = Nonepeft_version: Optional[str] = Nonebase_model_name_or_path: Optional[str] = Nonerevision: Optional[str] = Noneinference_mode: bool = Falseeffective_rank: Optional[Union[int, float]] = Nonetarget_modules: Optional[Union[list[str], str]] = Nonerank_pattern: Optional[dict[str, Union[int, float]]] = Noneinit_weights: Optional[bool] = Truemodules_to_save: Optional[list[str]] = Nonetarget_svd_config: Optional[dict[str, int]] = None )

Tham số

  • effective_rank (int hoặc float, tùy chọn) — Bậc SVD được giữ lại (không gian con “cao”). Các hướng riêng lẻ hàng đầu có bậc effective_rank sẽ được đóng băng và giữ nguyên qua các tác vụ; các chiều còn lại tạo thành không gian con bậc thấp có thể huấn luyện. Nếu là None, mặc định là 50% của chiều trọng số nhỏ hơn cho mỗi module đích. Lưu ý: Điều này khác với r của LoRA (bậc có thể huấn luyện). Trong OSF, bậc có thể huấn luyện là min(weight.shape) - effective_rank.
  • target_modules (Union[list[str], str], tuỳ chọn) — Tên của các module cần áp dụng OSF. Có thể là một danh sách tên module hoặc “all-linear”.
  • rank_pattern (dict[str, int|float], optional) — Một từ điển chứa các mẫu regex để ghi đè effective_rank cho các module cụ thể. Giá trị có thể là số nguyên tuyệt đối hoặc phân số trong (0, 1], được hiểu là một phân số của chiều ma trận nhỏ hơn cho mỗi mục tiêu.

Cấu hình cho Fine-tuning Không gian con trực giao (OSF).

OSFModel

lớp peft.OSFModel

< >

( modelconfigadapter_namelow_cpu_mem_usage: bool = Falsestate_dict: dict[str, torch.Tensor] | None = None )

Bộ điều chỉnh tối thiểu triển khai Fine-tuning Subspace Trực giao.

Hàm tiện ích

Phân rã trọng số

peft.tuners.osf.utils.decompose_weight_matrix

< >

( weight: torch.Tensortop_k: int )

Thực hiện phân rã SVD của weight và tách nó thành các phần đóng băng và phần có thể huấn luyện.

Chiếu Gradient

peft.tuners.osf.utils.project_gradient_to_orthogonal_space

< >

( svd_dict: dict[str, Any] )

Chiếu gradient của U_low và V_low để chúng trực giao với không gian hạng cao.

Update on GitHub