PEFT documentation
Xử lý sự cố
Xử lý sự cố
Nếu bạn gặp bất kỳ vấn đề nào khi sử dụng PEFT, hãy kiểm tra danh sách các vấn đề phổ biến và giải pháp tương ứng bên dưới.
Các ví dụ không hoạt động
Các ví dụ thường dựa vào các phiên bản gói mới nhất, vì vậy vui lòng đảm bảo chúng được cập nhật. Cụ thể, hãy kiểm tra các phiên bản gói sau:
pefttransformersacceleratetorch
Nhìn chung, bạn có thể cập nhật phiên bản gói bằng cách chạy lệnh này trong môi trường Python của mình:
python -m pip install -U <package_name>
Việc cài đặt PEFT từ mã nguồn hữu ích để cập nhật các phát triển mới nhất:
python -m pip install git+https://github.com/huggingface/peft
Các vấn đề liên quan đến Dtype
ValueError: Đang cố gắng khử tỷ lệ (unscale) gradient FP16
Lỗi này có thể xảy ra vì mô hình được nạp với dtype=torch.float16 và sau đó được sử dụng trong ngữ cảnh độ chính xác hỗn hợp tự động (AMP), ví dụ bằng cách đặt fp16=True trong lớp Trainer của 🤗 Transformers. Lý do là khi sử dụng AMP, trọng số có thể huấn luyện không bao giờ nên dùng fp16. Để hoạt động mà không cần nạp toàn bộ mô hình ở fp32, hãy thêm các dòng sau vào mã của bạn:
peft_model = get_peft_model(...)
# add this:
for param in model.parameters():
if param.requires_grad:
param.data = param.data.float()
# proceed as usual
trainer = Trainer(model=peft_model, fp16=True, ...)
trainer.train()Ngoài ra, bạn có thể sử dụng hàm cast_mixed_precision_params() để chuyển đổi kiểu dữ liệu của trọng số một cách chính xác:
from peft import cast_mixed_precision_params
peft_model = get_peft_model(...)
cast_mixed_precision_params(peft_model, dtype=torch.float16)
# proceed as usual
trainer = Trainer(model=peft_model, fp16=True, ...)
trainer.train()Bắt đầu từ phiên bản PEFT v0.12.0, PEFT tự động nâng cấp dtype của trọng số adapter từ
torch.float16vàtorch.bfloat16lêntorch.float32khi phù hợp. Để ngăn chặn hành vi này, bạn có thể truyềnautocast_adapter_dtype=Falsevào ~get_peft_model(), vào from_pretrained() và vào load_adapter().
Chọn kiểu dữ liệu của adapter
Hầu hết các phương pháp PEFT, chẳng hạn như LoRA, hoạt động bằng cách thêm các trọng số adapter có thể huấn luyện. Mặc định, các trọng số này được lưu trữ ở dtype float32 (fp32), tức là ở độ chính xác tương đối cao. Do đó, ngay cả khi mô hình cơ sở được nạp ở float16 (fp16) hoặc bfloat16 (bf16), các trọng số adapter vẫn là float32. Khi kết quả của adapter được tính toán trong quá trình truyền thuận, đầu vào thường sẽ ở dtype của mô hình cơ sở, do đó nó sẽ được tăng độ chính xác lên float32 nếu cần, sau đó được chuyển đổi trở lại dtype ban đầu.
Nếu bạn muốn trọng số adapter ở độ chính xác thấp hơn của mô hình cơ sở, tức là float16 hoặc bfloat16, bạn có thể truyền autocast_adapter_dtype=False khi tạo mô hình (~get_peft_model()) hoặc tải mô hình (from_pretrained()). Cách này có một số ưu và nhược điểm:
Ưu điểm của adapter độ chính xác nửa:
- tính toán nhanh hơn một chút
- ít bộ nhớ hơn một chút
- kích thước tệp checkpoint nhỏ hơn (một nửa kích thước)
Nhược điểm của adapter độ chính xác nửa:
- loss hơi tệ hơn
- nguy cơ tràn hoặc dưới tràn cao hơn
Lưu ý rằng đối với hầu hết các trường hợp sử dụng, thời gian chạy tổng thể và chi phí bộ nhớ sẽ được xác định bởi kích thước của mô hình cơ sở và bộ dữ liệu, trong khi dtype của bộ thích ứng PEFT chỉ có tác động nhỏ.
Quá trình huấn luyện chạy nhưng mô hình không cải thiện
Nếu quá trình huấn luyện có vẻ ổn định (loss giảm, các checkpoint được lưu mà không có lỗi) nhưng mô hình kết quả không hoạt động tốt hơn mô hình cơ sở, hãy kiểm tra xem các tham số có thể huấn luyện có thực sự thay đổi trong quá trình huấn luyện hay không:
# before training
sample_param = next(p for p in model.parameters() if p.requires_grad)
before = sample_param.detach().clone()
# ... train for at least three optimizer steps ...
after = next(p for p in model.parameters() if p.requires_grad)
print("weights updated:", not torch.allclose(before, after.detach().cpu()))Thiếu hook forward hoặc backward
Nếu trọng số không thay đổi, nguyên nhân có thể là các tham chiếu tham số đã được ghi nhận trước khi các tham số được vật thể hóa trên thiết bị đích. Điều này có thể xảy ra khi mô hình cơ sở được bọc bằng get_peft_model() trong khi các tham số của nó chưa được chuyển sang thiết bị đích, và một thư viện bên thứ ba sau đó đăng ký các hook forward/backward (ví dụ: các hook gradient theo mẫu từ một khung differential privacy) hoặc một optimizer được tạo trước lần gọi forward đầu tiên. Khi các tham số được vật thể hóa sau đó, các hook và tham chiếu optimizer đó sẽ trỏ đến các tensor cũ và mọi cập nhật sẽ trở thành một thao tác không có tác dụng (no-op) một cách im lặng.
Để tránh điều này, hãy sử dụng thứ tự sau:
- Tải mô hình cơ sở và chuyển nó sang thiết bị đích (ví dụ:
model.to(device)). - Bọc nó bằng get_peft_model().
- Chỉ khi đó mới đăng ký các hook hoặc tạo optimizer.
Các module đích không khớp với mô hình
Nếu quá trình huấn luyện chạy không có lỗi nhưng loss hầu như không thay đổi — hoặc mô hình không cải thiện sau khi fine-tuning — thì target_modules đã cấu hình (hoặc target_modules mặc định nếu người dùng không chỉ định khác) có thể không khớp với tên module thực tế trong mô hình. Điều này thường gặp với các kiến trúc hybrid như Mamba, Jamba, hoặc NemotronH, vốn sử dụng tên layer khác so với các mô hình Transformer tiêu chuẩn.
PEFT chỉ báo lỗi khi không có module nào trong target_modules được cấu hình khớp với bất kỳ module nào trong mô hình. Nếu một số khớp và một số không khớp, các mục không khớp sẽ bị bỏ qua một cách im lặng — không có cảnh báo nào. Một phần lớn mô hình có thể vẫn bị đóng băng mà không có bất kỳ chỉ báo nào.
Chẩn đoán đầu tiên là kiểm tra số lượng tham số có thể huấn luyện:
from peft import LoraConfig, get_peft_model
config = LoraConfig(target_modules=["q_proj", "v_proj", "gate_proj"])
model = get_peft_model(base_model, config)
model.print_healthcheck()Nếu số lượng được báo cáo thấp hơn nhiều so với dự kiến, hãy kiểm tra các lớp adapter trong mô hình:
print(model.get_layer_status())Điều này trả về một danh sách các mục TunerLayerStatus hiển thị tên, loại module, trạng thái bật và các adapter đang hoạt động của từng lớp adapter. Ngoài ra, in ra biểu diễn của model (print(model)) sẽ cho thấy tổng quan về hệ thống phân cấp module đầy đủ, rất hữu ích để xác định tên lớp chính xác trên các kiến trúc không quen thuộc.
Bất kỳ mục nào trong target_modules không tương ứng với một module trong mô hình sẽ bị bỏ qua một cách im lặng. Hãy cập nhật target_modules để khớp với tên module thực tế.
Vấn đề này ảnh hưởng đến tất cả các phương pháp PEFT sử dụng
target_modules(LoRA, LoHa, IA³, v.v.), không chỉ riêng LoRA. Các bước chẩn đoán là giống nhau bất kể phương pháp nào. Nếu bạn không chắc kiến trúc cụ thể sử dụng tên module nào, hãy kiểm tra tài liệu của mô hình hoặc xem xétmodel.named_modules()trước khi cấu hình adapter.
Kết quả kém từ một mô hình PEFT đã được nạp
Có thể có nhiều lý do khiến kết quả từ một mô hình PEFT đã tải vào không tốt, được liệt kê bên dưới. Nếu bạn vẫn không thể khắc phục sự cố, hãy xem liệu có ai khác gặp vấn đề tương tự trên GitHub không, và nếu không tìm thấy, hãy mở một vấn đề mới.
Khi mở một issue, việc cung cấp một ví dụ mã tối giản tái hiện được vấn đề sẽ giúp ích rất nhiều. Ngoài ra, vui lòng báo cáo nếu mô hình đã tải hoạt động ở cùng mức độ như mô hình trước khi fine-tuning, nếu nó hoạt động ở mức ngẫu nhiên, hoặc nếu nó chỉ kém hơn một chút so với kỳ vọng. Thông tin này giúp chúng tôi xác định vấn đề nhanh hơn.
Các sai lệch ngẫu nhiên
Nếu đầu ra của mô hình của bạn không hoàn toàn giống với các lần chạy trước, có thể có vấn đề với các yếu tố ngẫu nhiên. Ví dụ:
- hãy đảm bảo nó ở chế độ
.eval(), điều này rất quan trọng, chẳng hạn nếu mô hình sử dụng dropout - nếu bạn sử dụng generate trên một mô hình ngôn ngữ, có thể có lấy mẫu ngẫu nhiên, do đó để đạt được cùng một kết quả thì cần đặt một hạt ngẫu nhiên
- nếu bạn đã sử dụng lượng tử hóa và gộp các trọng số, các sai lệch nhỏ là điều được dự kiến do lỗi làm tròn
Mô hình được nạp không chính xác
Hãy đảm bảo rằng bạn tải model đúng cách. Một lỗi phổ biến là cố gắng tải model đã được huấn luyện bằng get_peft_model(), điều này là không chính xác. Thay vào đó, mã tải nên có dạng như sau:
from peft import PeftModel, PeftConfig
base_model = ... # to load the base model, use the same code as when you trained it
config = PeftConfig.from_pretrained(peft_model_id)
peft_model = PeftModel.from_pretrained(base_model, peft_model_id)Các đường dẫn thực thi được tối ưu hóa, bỏ qua các adapter
Một số đường dẫn thực thi được tối ưu hóa đọc trọng số module trực tiếp thay vì gọi phương thức forward của chúng. Điều này có thể bỏ qua các thay đổi do các adapter chưa được hợp nhất tạo ra mà không có cảnh báo, ngay cả khi adapter đang hoạt động. Vấn đề này có thể ảnh hưởng đến các module và phương pháp PEFT khác nhau.
Ví dụ, torch.nn.TransformerEncoder bản địa của PyTorch có thể sử dụng đường dẫn đã hợp nhất trong quá trình đánh giá với torch.no_grad() hoặc torch.inference_mode(). Đường dẫn này có thể bỏ qua các adapter trong các submodule TransformerEncoderLayer, chẳng hạn như các adapter LoRA trên linear1 và linear2. Việc huấn luyện có thể hoạt động chính xác trong khi đánh giá lại bỏ qua các thay đổi của adapter. Xem issue #3976 để có một ví dụ tái hiện.
Nếu bạn nghi ngờ vấn đề này, trước tiên hãy kiểm tra xem adapter đã được bật và đang hoạt động bằng get_layer_status(). So sánh kết quả đầu ra trên cùng các đầu vào ở chế độ đánh giá, lần lượt với và không có adapter, sau đó với tối ưu hóa bị tắt. Hãy sử dụng một adapter đã được huấn luyện có khả năng thay đổi đầu ra, vì các adapter LoRA mới khởi tạo thường không có tác dụng. Những khác biệt nhỏ về mặt số học là bình thường.
Đối với một adapter chưa được hợp nhất trên một encoder native ở chế độ eager, hãy so sánh các đầu ra cho cùng một tensor đầu vào x:
import torch
model.eval()
previous = torch.backends.mha.get_fastpath_enabled()
try:
with torch.inference_mode():
torch.backends.mha.set_fastpath_enabled(True)
fast_output = model(x)
torch.backends.mha.set_fastpath_enabled(False)
reference_output = model(x)
finally:
torch.backends.mha.set_fastpath_enabled(previous)
print((fast_output - reference_output).abs().max().item())Việc bật cờ backend đơn thuần không có nghĩa là đường dẫn hợp nhất (fused path) sẽ được thực thi; việc sử dụng nó còn phụ thuộc vào mô hình, đầu vào và các điều kiện thời gian chạy. Hãy kiểm tra cài đặt forward của module bao quanh để tìm các truy cập trọng số trực tiếp hoặc các lời gọi toán tử hợp nhất. Hãy cẩn thận khi gỡ lỗi bằng forward hooks: việc đăng ký một hook có thể tự nó vô hiệu hóa một tối ưu hóa và làm thay đổi hành vi mà bạn đang điều tra.
Đối với bộ mã hóa native ở chế độ eager, torch.backends.mha.set_fastpath_enabled(False) là một giải pháp thay thế giúp giữ cho các adapter không bị gộp. Cài đặt này áp dụng cho toàn bộ tiến trình, có thể ảnh hưởng đến các mô hình khác và hiệu suất, và không nên được bật/tắt độc lập bởi các yêu cầu đồng thời. Đối với các đường dẫn tối ưu khác, hãy tắt tối ưu hóa bị ảnh hưởng hoặc sử dụng một triển khai gọi các module đã thích ứng. Nếu phương pháp PEFT và cấu hình lượng tử hóa cho phép, bạn cũng có thể gộp các adapter bằng merge_and_unload() và sử dụng mô hình được trả về để suy luận. Các thay đổi của adapter sau đó sẽ được bao gồm trong trọng số cơ sở. Việc gộp sẽ loại bỏ các wrapper adapter, vì vậy hãy giữ lại các checkpoint gốc của bạn nếu cần chuyển đổi adapter hoặc tiếp tục huấn luyện.
Các lớp được khởi tạo ngẫu nhiên
Đối với một số tác vụ, việc cấu hình chính xác modules_to_save trong config là quan trọng để tính đến các lớp được khởi tạo ngẫu nhiên.
Ví dụ, điều này là cần thiết nếu bạn sử dụng LoRA để tinh chỉnh một mô hình ngôn ngữ cho phân loại chuỗi, vì 🤗 Transformers thêm một đầu phân loại được khởi tạo ngẫu nhiên lên trên mô hình. Nếu bạn không thêm lớp này vào modules_to_save, đầu phân loại sẽ không được lưu lại. Lần sau khi bạn tải mô hình, bạn sẽ nhận được một đầu phân loại khác được khởi tạo ngẫu nhiên, dẫn đến kết quả hoàn toàn khác biệt.
PEFT sẽ cố gắng đoán chính xác modules_to_save nếu bạn cung cấp đối số task_type trong cấu hình. Điều này nên hoạt động với các mô hình transformers tuân theo quy tắc đặt tên tiêu chuẩn. Tuy nhiên, luôn là một ý tưởng tốt để kiểm tra lại vì chúng tôi không thể đảm bảo tất cả các mô hình đều tuân theo quy tắc đặt tên.
Khi bạn tải một mô hình transformers có các lớp được khởi tạo ngẫu nhiên, bạn sẽ thấy một cảnh báo tương tự như:
Some weights of <MODEL> were not initialized from the model checkpoint at <ID> and are newly initialized: [<LAYER_NAMES>].
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.Các lớp được đề cập nên được thêm vào modules_to_save trong cấu hình để tránh vấn đề đã mô tả.
healthcheck() báo cáo cảnh báo TASK_HEAD_NOT_SAVED cho dạng phổ biến nhất của sai sót này: một head phân loại chuỗi, phân loại token hoặc trả lời câu hỏi được khởi tạo mới khi tải mô hình cơ sở nhưng không có trong modules_to_save, ví dụ vì task_type không được đặt. Việc kiểm tra sẽ bị bỏ qua nếu mô hình cơ sở không liệt kê các kiến trúc của nó trong config.
Ví dụ, khi tải một mô hình sử dụng kiến trúc DeBERTa cho phân loại chuỗi, bạn sẽ thấy cảnh báo rằng các trọng số sau được khởi tạo mới:
['classifier.bias', 'classifier.weight', 'pooler.dense.bias', 'pooler.dense.weight']. Từ đó, suy ra rằng các lớpclassifiervàpoolercần được thêm vào:modules_to_save=["classifier", "pooler"].
Mở rộng bảng từ
Đối với nhiều tác vụ tinh chỉnh ngôn ngữ, việc mở rộng bảng từ vựng của mô hình là cần thiết do các token mới được đưa vào. Điều này đòi hỏi phải mở rộng lớp nhúng để tính đến các token mới và, tùy thuộc vào phương pháp tinh chỉnh, cũng phải lưu trữ lớp nhúng bên cạnh trọng số adapter khi lưu adapter. Có một số cách để đạt được điều này, được sắp xếp theo hiệu quả tham số:
- token có thể huấn luyện, chỉ huấn luyện các token được chỉ định, tùy chọn chỉ lưu trữ các giá trị đã cập nhật
- huấn luyện một adapter trên ma trận nhúng, tùy chọn chỉ lưu trữ các giá trị đã cập nhật
- full-finetuning của lớp embedding
Sử dụng token có thể huấn luyện
Hãy bắt đầu với các token có thể huấn luyện, trong trường hợp này là tích hợp LoRA. Nếu bạn chỉ quan tâm đến việc huấn luyện các embedding mới và không huấn luyện gì khác, hãy tham khảo tài liệu độc lập.
Để bật chế độ huấn luyện chọn lọc token của lớp nhúng, bạn cần cung cấp các id token của các token mới được thêm vào thông qua tham số trainable_token_indices. Tùy chọn, bạn có thể chỉ định lớp cần nhắm tới nếu có nhiều hơn một lớp nhúng. Đối với một mô hình Mistral, điều này có thể trông như sau:
new_tokens = ['<think>', '</think>']
tokenizer.add_tokens(new_tokens)
base_model.resize_token_embeddings(len(tokenizer))
lora_config = LoraConfig(
...,
trainable_token_indices={'embed_tokens': tokenizer.convert_tokens_to_ids(new_tokens)},
)Nếu mô hình của bạn sử dụng trọng số liên kết (tied weights) (chẳng hạn như lm_head), các token có thể huấn luyện sẽ cố gắng giải quyết chúng và đồng thời giữ cho chúng được cập nhật, vì vậy trong trường hợp đó sẽ không cần thêm modules_to_save=["lm_head"]. Điều này chỉ hoạt động nếu mô hình sử dụng quy ước của Transformers để liên kết trọng số.
Việc lưu mô hình bằng model.save_pretrained có thể lưu toàn bộ ma trận embedding thay vì chỉ lưu phần chênh lệch như một biện pháp phòng ngừa, vì ma trận embedding đã được thay đổi kích thước. Để tiết kiệm dung lượng, bạn có thể tắt hành vi này bằng cách đặt save_embedding_layers=False khi gọi save_pretrained. Việc này là an toàn miễn là bạn không thay đổi ma trận embedding bằng các phương tiện khác, vì những thay đổi như vậy sẽ không được theo dõi bởi các token có thể huấn luyện.
Sử dụng một adapter, ví dụ như LoRA
Chuẩn bị lớp nhúng bằng cách thêm nó vào target_modules của cấu hình adapter của bạn. Ví dụ, cấu hình Mistral có thể trông như sau:
config = LoraConfig(..., target_modules=["embed_tokens", "lm_head", "q_proj", "v_proj"])Một khi được thêm vào target_modules, PEFT sẽ tự động lưu trữ lớp embedding khi lưu adapter nếu model có get_input_embeddings và get_output_embeddings. Điều này thường đúng với các model Transformers.
Nếu lớp nhúng của mô hình không tuân theo quy tắc đặt tên của Transformer nhưng vẫn triển khai get_input_embeddings, bạn vẫn có thể lưu nó bằng cách truyền thủ công save_embedding_layers=True khi lưu adapter:
model = get_peft_model(...)
# train the model
model.save_pretrained("my_adapter", save_embedding_layers=True)Để suy luận, hãy tải mô hình cơ sở trước và điều chỉnh kích thước theo cách bạn đã làm trước khi huấn luyện mô hình. Sau khi đã điều chỉnh kích thước mô hình cơ sở, bạn có thể tải checkpoint PEFT.
Để xem một ví dụ hoàn chỉnh, vui lòng xem notebook này.
Tinh chỉnh đầy đủ
Fine-tuning toàn bộ tốn kém hơn về VRAM hoặc dung lượng lưu trữ, nhưng nếu mọi phương pháp khác đều thất bại, bạn có thể quay lại phương pháp này và xem liệu nó có hoạt động với bạn hay không. Để thực hiện, hãy thêm tên của lớp embedding vào modules_to_save. Lưu ý rằng bạn cũng cần thêm các lớp liên kết (tied layers), ví dụ lm_head. Ví dụ cho một mô hình Mistral với LoRA:
config = LoraConfig(..., modules_to_save=["embed_tokens", "lm_head"], target_modules=["q_proj", "v_proj"])Nhận cảnh báo về “trọng số không được khởi tạo từ checkpoint của mô hình”
Khi bạn tải mô hình PEFT đã được huấn luyện trên một tác vụ (ví dụ: phân loại), bạn có thể nhận được một cảnh báo như:
Một số trọng số của LlamaForSequenceClassification không được khởi tạo từ checkpoint mô hình tại meta-llama/Llama-3.2-1B và được khởi tạo mới: [‘score.weight’]. Bạn nên HUẤN LUYỆN mô hình này trên một tác vụ hạ nguồn để có thể sử dụng nó cho dự đoán và suy luận.
Mặc dù điều này trông có vẻ đáng lo, nhưng hầu như không có gì đáng bận tâm. Cảnh báo này đến từ Transformers và không phải là cảnh báo đặc thù của PEFT. Nó cho bạn biết rằng một đầu phân loại được khởi tạo ngẫu nhiên (score) đã được gắn vào mô hình cơ sở, và đầu này phải được huấn luyện để tạo ra các dự đoán hợp lý.
Khi bạn nhận được cảnh báo này trước khi huấn luyện mô hình, PEFT sẽ tự động đảm bảo rằng đầu phân loại có thể huấn luyện được nếu bạn đã truyền đúng đối số task_type vào cấu hình PEFT.
from peft import LoraConfig, TaskType
lora_config = LoraConfig(..., task_type=TaskType.SEQ_CLS)Nếu đầu phân loại của bạn không tuân theo các quy ước đặt tên thông thường từ Transformers (điều này hiếm gặp), bạn phải chỉ định rõ tên của đầu đó trong modules_to_save.
lora_config = LoraConfig(..., modules_to_save=["name-of-classification-head"])Để kiểm tra tên của đầu phân loại, hãy in ra mô hình và nó phải là module cuối cùng.
Nếu bạn nhận được cảnh báo này từ mã suy luận của mình, tức là sau khi huấn luyện mô hình, khi bạn tải mô hình PEFT, bạn luôn phải tải mô hình Transformers trước. Vì Transformers không biết rằng bạn sẽ tải trọng số PEFT sau đó, nên nó vẫn hiển thị cảnh báo.
Như mọi khi, cách tốt nhất là đảm bảo mô hình hoạt động chính xác cho suy luận bằng cách chạy một số kiểm tra xác thực trên nó.
Chạy kiểm tra sức khỏe huấn luyện
Trước khi huấn luyện, hãy sử dụng print_healthcheck() để nhận được bản tóm tắt ngắn gọn, dễ đọc cho con người về trạng thái của mô hình PEFT. Nó được suy ra từ các API trạng thái mô hình và lớp (xem mục tiếp theo), và chỉ ra các trạng thái đáng ngờ như adapter không nhất quán, adapter đã được hợp nhất, hoặc số lượng tham số có thể huấn luyện đáng ngờ.
peft_model.print_healthcheck()
Hãy đặc biệt chú ý đến sự hiện diện của mục “Findings” ở cuối báo cáo, mục này được bao gồm nếu phát hiện thấy các bất thường.
Nếu bạn ưu tiên đầu ra dạng từ điển, có thể chuyển đổi sang JSON, hãy dùng healthcheck() thay thế:
healthcheck = peft_model.healthcheck()
print(healthcheck.get("findings", "all good")Kiểm tra này chỉ xem xét mô hình và các adapter của nó. Nó không xác thực vòng lặp huấn luyện, optimizer hoặc tập dữ liệu của bạn.
Kiểm tra trạng thái lớp và mô hình
Đôi khi một mô hình PEFT có thể rơi vào trạng thái không ổn định, đặc biệt khi xử lý nhiều adapter. Có thể xảy ra nhầm lẫn về việc adapter nào tồn tại, adapter nào đang hoạt động, adapter nào đã được merge, v.v. Để hỗ trợ điều tra vấn đề này, hãy gọi các phương thức get_layer_status() và get_model_status().
Phương thức get_layer_status() cung cấp cho bạn cái nhìn chi tiết về các adapter đang hoạt động, đã hợp nhất và khả dụng của từng lớp được nhắm tới.
>>> from transformers import AutoModel
>>> from peft import get_peft_model, LoraConfig
>>> model_id = "google/flan-t5-small"
>>> model = AutoModel.from_pretrained(model_id)
>>> model = get_peft_model(model, LoraConfig())
>>> model.get_layer_status()
[TunerLayerStatus(name='model.encoder.block.0.layer.0.SelfAttention.q',
module_type='lora.Linear',
enabled=True,
active_adapters=['default'],
merged_adapters=[],
requires_grad={'default': True},
available_adapters=['default']),
TunerLayerStatus(name='model.encoder.block.0.layer.0.SelfAttention.v',
module_type='lora.Linear',
enabled=True,
active_adapters=['default'],
merged_adapters=[],
requires_grad={'default': True},
available_adapters=['default']),
...]
>>> model.get_model_status()
TunerModelStatus(
base_model_type='T5Model',
adapter_model_type='LoraModel',
peft_types={'default': 'LORA'},
trainable_params=344064,
total_params=60855680,
num_adapter_layers=48,
enabled=True,
active_adapters=['default'],
merged_adapters=[],
requires_grad={'default': True},
available_adapters=['default'],
)Trong đầu ra trạng thái mô hình, bạn cần chú ý đến các mục có nội dung "irregular". Điều này có nghĩa là PEFT đã phát hiện trạng thái không nhất quán trong mô hình. Ví dụ, nếu merged_adapters="irregular", điều đó có nghĩa là với ít nhất một adapter, nó đã được gộp vào một số module đích nhưng không phải tất cả. Kết quả suy luận rất có thể sẽ không chính xác do điều này.
Cách tốt nhất để giải quyết vấn đề này là tải lại toàn bộ model và checkpoint(s) của adapter. Đảm bảo rằng bạn không thực hiện bất kỳ thao tác sai nào trên model, ví dụ như tự động merge adapter trên một số module nhưng không merge trên các module khác.
Chuyển đổi trạng thái lớp thành DataFrame của pandas để dễ kiểm tra trực quan hơn.
from dataclasses import asdict
import pandas as pd
df = pd.DataFrame(asdict(layer) for layer in model.get_layer_status())Bạn có thể lấy thông tin này cho các model không phải PEFT nếu chúng đang sử dụng các lớp PEFT bên trong, nhưng một số thông tin như base_model_type hoặc peft_types sẽ không thể xác định được trong trường hợp đó. Ví dụ, bạn có thể gọi điều này trên một model diffusers như sau:
>>> import torch
>>> from diffusers import StableDiffusionPipeline
>>> from peft import get_model_status, get_layer_status
>>> path = "runwayml/stable-diffusion-v1-5"
>>> lora_id = "takuma104/lora-test-text-encoder-lora-target"
>>> pipe = StableDiffusionPipeline.from_pretrained(path, dtype=torch.float16)
>>> pipe.load_lora_weights(lora_id, adapter_name="adapter-1")
>>> pipe.load_lora_weights(lora_id, adapter_name="adapter-2")
>>> pipe.set_lora_device(["adapter-2"], "cuda")
>>> get_layer_status(pipe.text_encoder)
[TunerLayerStatus(name='text_model.encoder.layers.0.self_attn.k_proj',
module_type='lora.Linear',
enabled=True,
active_adapters=['adapter-2'],
merged_adapters=[],
requires_grad={'adapter-1': False, 'adapter-2': True},
available_adapters=['adapter-1', 'adapter-2'],
devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']}),
TunerLayerStatus(name='text_model.encoder.layers.0.self_attn.v_proj',
module_type='lora.Linear',
enabled=True,
active_adapters=['adapter-2'],
merged_adapters=[],
requires_grad={'adapter-1': False, 'adapter-2': True},
devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']}),
...]
>>> get_model_status(pipe.unet)
TunerModelStatus(
base_model_type='other',
adapter_model_type='None',
peft_types={},
trainable_params=797184,
total_params=861115332,
num_adapter_layers=128,
enabled=True,
active_adapters=['adapter-2'],
merged_adapters=[],
requires_grad={'adapter-1': False, 'adapter-2': True},
available_adapters=['adapter-1', 'adapter-2'],
devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']},
)Tốc độ
Việc tải trọng số adapter diễn ra chậm
Việc nạp các adapter như trọng số LoRA thường nhanh hơn đáng kể so với việc nạp mô hình cơ sở. Tuy nhiên, có thể có các trường hợp sử dụng mà trọng số adapter khá lớn hoặc người dùng cần nạp một số lượng lớn adapter — trong trường hợp này, thời gian nạp có thể tích lũy đáng kể. Nguyên nhân là do trọng số adapter trước tiên được khởi tạo rồi sau đó bị ghi đè bởi các trọng số đã nạp, điều này gây lãng phí. Để tăng tốc thời gian nạp, bạn có thể truyền đối số low_cpu_mem_usage=True vào from_pretrained() và load_adapter().
Nếu tùy chọn này hoạt động tốt trên các trường hợp sử dụng khác nhau, nó có thể trở thành mặc định cho việc nạp adapter trong tương lai.
Tính khả tái lập
Các mô hình sử dụng batch norm
Khi nạp một mô hình PEFT đã được huấn luyện mà mô hình cơ sở sử dụng batch norm (ví dụ torch.nn.BatchNorm1d hoặc torch.nn.BatchNorm2d), bạn có thể nhận thấy rằng không thể tái tạo chính xác cùng các đầu ra. Điều này là do các lớp batch norm theo dõi các thống kê chạy (running stats) trong quá trình huấn luyện, nhưng các thống kê này không phải là một phần của checkpoint PEFT. Do đó, khi bạn nạp mô hình PEFT, các thống kê chạy của mô hình cơ sở sẽ được sử dụng (tức là từ trước khi huấn luyện với PEFT).
Tùy thuộc vào trường hợp sử dụng của bạn, điều này có thể không quá quan trọng. Tuy nhiên, nếu bạn cần các đầu ra của mình phải tái lập được 100%, bạn có thể đạt được điều này bằng cách thêm các lớp batch norm vào modules_to_save. Dưới đây là một ví dụ về việc này sử dụng resnet và LoRA. Lưu ý rằng chúng tôi đặt modules_to_save=["classifier", "normalization"]. Chúng tôi cần đối số "classifier" vì tác vụ của chúng tôi là phân loại hình ảnh, và chúng tôi thêm đối số "normalization" để đảm bảo rằng các lớp batch norm được lưu trong checkpoint PEFT.
from transformers import AutoModelForImageClassification
from peft import LoraConfig, get_peft_model
model_id = "microsoft/resnet-18"
base_model = AutoModelForImageClassification.from_pretrained(self.model_id)
config = LoraConfig(
target_modules=["convolution"],
modules_to_save=["classifier", "normalization"],
),Tùy thuộc vào loại model bạn sử dụng, các lớp batch norm có thể có tên khác với "normalization", vì vậy hãy đảm bảo rằng tên khớp với kiến trúc model của bạn.
Lỗi không khớp phiên bản
Lỗi khi tải cấu hình do có một tham số từ khóa không mong đợi
Khi bạn gặp lỗi như được hiển thị bên dưới, điều đó có nghĩa là adapter bạn đang cố gắng tải đã được huấn luyện bằng một phiên bản PEFT mới hơn so với phiên bản bạn đã cài đặt trên hệ thống của mình.
TypeError: LoraConfig.__init__() got an unexpected keyword argument <argument-name>Cách tốt nhất để giải quyết vấn đề này là cài đặt phiên bản PEFT mới nhất:
python -m pip install -U PEFT
Nếu adapter được huấn luyện từ bản cài đặt nguồn của PEFT (một phiên bản PEFT chưa được phát hành), thì bạn cũng cần cài đặt PEFT từ nguồn.
python -m pip install -U git+https://github.com/huggingface/peft.git
Nếu bạn không thể nâng cấp PEFT, có một giải pháp thay thế bạn có thể thử.
Giả sử thông báo lỗi cho biết đối số từ khóa không xác định có tên là foobar. Tìm trong adapter_config.json của bộ thích ứng PEFT này mục foobar và xóa nó khỏi tệp. Sau đó lưu tệp và thử tải mô hình một lần nữa.
Giải pháp này hoạt động trong hầu hết các trường hợp. Miễn là đây là giá trị mặc định của foobar, bạn có thể bỏ qua. Tuy nhiên, khi nó được đặt thành một giá trị khác, bạn sẽ nhận được kết quả không chính xác. Nâng cấp PEFT là giải pháp được khuyến nghị.
Xử lý Adapter
Sử dụng nhiều adapter cùng một lúc
PEFT cho phép bạn tạo nhiều hơn một adapter trên cùng một model. Điều này có thể hữu ích trong nhiều tình huống. Ví dụ, đối với inference, bạn có thể muốn phục vụ hai model đã fine-tune từ cùng một base model thay vì nạp base model một lần cho mỗi model đã fine-tune, điều này sẽ tốn nhiều bộ nhớ hơn. Tuy nhiên, nhiều adapter có thể được kích hoạt cùng một lúc. Bằng cách này, model có thể tận dụng kiến thức từ tất cả các adapter đó đồng thời. Ví dụ, nếu bạn có một diffusion model, bạn có thể muốn sử dụng một adapter LoRA để thay đổi phong cách và một adapter khác để thay đổi chủ thể.
Việc kích hoạt nhiều adapter cùng lúc nhìn chung là khả thi với tất cả các phương pháp PEFT (LoRA, LoHa, IA³, v.v.) ngoại trừ các phương pháp prompt learning (p-tuning, prefix tuning, v.v.). Ví dụ sau minh họa cách thực hiện điều này:
from transformers import AutoModelForCausalLM
from peft import PeftModel
model_id = ...
base_model = AutoModelForCausalLM.from_pretrained(model_id)
model = PeftModel.from_pretrained(base_model, lora_path_0) # default adapter_name is 'default'
model.load_adapter(lora_path_1, adapter_name="other")
# the 'other' adapter was loaded but it's not active yet, so to activate both adapters:
model.base_model.set_adapter(["default", "other"])Trong ví dụ trên, bạn có thể thấy rằng chúng ta cần gọi
model.base_model.set_adapter(["default", "other"]). Tại sao chúng ta không thể gọimodel.set_adapter(["default", "other"])? Thật không may, điều này không thể thực hiện được vì, như đã giải thích ở trên, một số phương pháp PEFT không hỗ trợ kích hoạt nhiều hơn một adapter cùng một lúc.
Bạn cũng có thể huấn luyện hai adapter cùng lúc, nhưng cần cẩn thận để đảm bảo rằng trọng số của cả hai adapter đều được optimizer biết đến. Nếu không, chỉ một adapter sẽ nhận được các cập nhật.
from transformers import AutoModelForCausalLM
from peft import LoraConfig, get_peft_model
model_id = ...
base_model = AutoModelForCausalLM.from_pretrained(model_id)
lora_config_0 = LoraConfig(...)
lora_config_1 = LoraConfig(...)
model = get_peft_model(base_model, lora_config_0)
model.add_adapter(adapter_name="other", peft_config=lora_config_1)Nếu bây giờ chúng ta gọi:
from transformers import Trainer
trainer = Trainer(model=model, ...)
trainer.train()hoặc
optimizer = torch.optim.AdamW([param for param in model.parameters() if param.requires_grad], ...)thì adapter LoRA thứ hai ("other") sẽ không được huấn luyện. Điều này là do adapter đó đang ở trạng thái không hoạt động tại thời điểm này, nghĩa là thuộc tính requires_grad trên các tham số của nó được đặt thành False và optimizer sẽ bỏ qua nó. Do đó, hãy đảm bảo kích hoạt tất cả các adapter cần được huấn luyện trước khi khởi tạo optimizer:
# activate all adapters
model.base_model.set_adapter(["default", "other"])
trainer = Trainer(model=model, ...)
trainer.train()Phần này đề cập đến việc sử dụng nhiều adapter cùng loại trên cùng một mô hình, chẳng hạn như sử dụng nhiều adapter LoRA cùng lúc. Nội dung này không áp dụng cho việc sử dụng các loại khác nhau của adapter trên cùng một mô hình, chẳng hạn như một adapter LoRA và một adapter LoHa. Để làm điều này, vui lòng xem
PeftMixedModel.
Gộp một số adapter đang hoạt động
Khi trọng số adapter được gộp vào trọng số cơ sở, các lớp bị ảnh hưởng sẽ không còn áp dụng bất kỳ adapter nào trong quá trình truyền thẳng, mà chỉ sử dụng trọng số cơ sở đã được gộp. Do đó, nếu có nhiều adapter đang hoạt động nhưng chỉ một số trong số chúng được gộp, các adapter vẫn đang hoạt động nhưng chưa được gộp sẽ bị bỏ qua một cách im lặng:
model = get_peft_model(base_model, lora_config_0, adapter_name="default")
model.add_adapter("other", lora_config_1)
model.base_model.set_adapter(["default", "other"])
output_both = model(**inputs)
model.merge_adapter(adapter_names=["default"])
output_merged = model(**inputs) # this is *not* the same as output_bothỞ đây, output_merged là kết quả đầu ra của riêng adapter "default", mặc dù "other" vẫn được liệt kê là đang hoạt động:
>>> model.get_layer_status()[0].active_adapters
['default', 'other']
>>> model.get_layer_status()[0].merged_adapters
['default']Điều tương tự cũng áp dụng khi một adapter không đang hoạt động được hợp nhất: từ thời điểm đó, mô hình sẽ trả về đầu ra của adapter đã hợp nhất và adapter đang hoạt động sẽ bị bỏ qua. Không có lỗi hay cảnh báo nào được phát sinh trong cả hai trường hợp. Bản thân trọng số của adapter không bị ảnh hưởng, do đó việc gọi unmerge_adapter() sẽ khôi phục đầu ra kết hợp.
Để tránh điều này, hãy gộp tất cả các adapter đang hoạt động, tức là gọi model.merge_adapter() mà không có adapter_names, hoặc tách gộp trước khi thay đổi các adapter đang hoạt động.
Hành vi này nhất quán giữa các phương pháp PEFT hỗ trợ gộp (LoRA, LoHa, LoKr, IA³, OFT, v.v.), vì tất cả chúng đều chia sẻ cùng một logic trong bước truyền thẳng.
Update on GitHubHành vi này gây nhầm lẫn và dự kiến sẽ được thay đổi trong bản phát hành PEFT v1.0.