mirror of
https://github.com/wassname/peft.git
synced 2026-09-24 13:40:15 +08:00
288 lines
11 KiB
Python
288 lines
11 KiB
Python
import gc
|
|
import os
|
|
import sys
|
|
import threading
|
|
|
|
import numpy as np
|
|
import torch
|
|
from accelerate import Accelerator
|
|
from torch.utils.data import DataLoader
|
|
from transformers import AutoModelForSeq2SeqLM, AutoTokenizer, get_linear_schedule_with_warmup, set_seed
|
|
|
|
import psutil
|
|
from datasets import load_dataset
|
|
from pet import LoRAConfig, TaskType, get_pet_model, get_pet_model_state_dict
|
|
from tqdm import tqdm
|
|
|
|
|
|
def levenshtein_distance(str1, str2):
|
|
# TC: O(N^2)
|
|
# SC: O(N^2)
|
|
if str1 == str2:
|
|
return 0
|
|
num_rows = len(str1) + 1
|
|
num_cols = len(str2) + 1
|
|
dp_matrix = np.empty((num_rows, num_cols))
|
|
dp_matrix[0, :] = range(num_cols)
|
|
dp_matrix[:, 0] = range(num_rows)
|
|
|
|
for i in range(1, num_rows):
|
|
for j in range(1, num_cols):
|
|
if str1[i - 1] == str2[j - 1]:
|
|
dp_matrix[i, j] = dp_matrix[i - 1, j - 1]
|
|
else:
|
|
dp_matrix[i, j] = min(dp_matrix[i - 1, j - 1], dp_matrix[i - 1, j], dp_matrix[i, j - 1]) + 1
|
|
|
|
return dp_matrix[num_rows - 1, num_cols - 1]
|
|
|
|
|
|
def get_closest_label(eval_pred, classes):
|
|
min_id = sys.maxsize
|
|
min_edit_distance = sys.maxsize
|
|
for i, class_label in enumerate(classes):
|
|
edit_distance = levenshtein_distance(eval_pred.strip(), class_label)
|
|
if edit_distance < min_edit_distance:
|
|
min_id = i
|
|
min_edit_distance = edit_distance
|
|
return classes[min_id]
|
|
|
|
|
|
# Converting Bytes to Megabytes
|
|
def b2mb(x):
|
|
return int(x / 2**20)
|
|
|
|
|
|
# This context manager is used to track the peak memory usage of the process
|
|
class TorchTracemalloc:
|
|
def __enter__(self):
|
|
gc.collect()
|
|
torch.cuda.empty_cache()
|
|
torch.cuda.reset_max_memory_allocated() # reset the peak gauge to zero
|
|
self.begin = torch.cuda.memory_allocated()
|
|
self.process = psutil.Process()
|
|
|
|
self.cpu_begin = self.cpu_mem_used()
|
|
self.peak_monitoring = True
|
|
peak_monitor_thread = threading.Thread(target=self.peak_monitor_func)
|
|
peak_monitor_thread.daemon = True
|
|
peak_monitor_thread.start()
|
|
return self
|
|
|
|
def cpu_mem_used(self):
|
|
"""get resident set size memory for the current process"""
|
|
return self.process.memory_info().rss
|
|
|
|
def peak_monitor_func(self):
|
|
self.cpu_peak = -1
|
|
|
|
while True:
|
|
self.cpu_peak = max(self.cpu_mem_used(), self.cpu_peak)
|
|
|
|
# can't sleep or will not catch the peak right (this comment is here on purpose)
|
|
# time.sleep(0.001) # 1msec
|
|
|
|
if not self.peak_monitoring:
|
|
break
|
|
|
|
def __exit__(self, *exc):
|
|
self.peak_monitoring = False
|
|
|
|
gc.collect()
|
|
torch.cuda.empty_cache()
|
|
self.end = torch.cuda.memory_allocated()
|
|
self.peak = torch.cuda.max_memory_allocated()
|
|
self.used = b2mb(self.end - self.begin)
|
|
self.peaked = b2mb(self.peak - self.begin)
|
|
|
|
self.cpu_end = self.cpu_mem_used()
|
|
self.cpu_used = b2mb(self.cpu_end - self.cpu_begin)
|
|
self.cpu_peaked = b2mb(self.cpu_peak - self.cpu_begin)
|
|
# print(f"delta used/peak {self.used:4d}/{self.peaked:4d}")
|
|
|
|
|
|
def main():
|
|
accelerator = Accelerator()
|
|
model_name_or_path = "bigscience/T0_3B"
|
|
dataset_name = "twitter_complaints"
|
|
pet_config = pet_config = LoRAConfig(
|
|
task_type=TaskType.TOKEN_CLS, inference_mode=False, r=8, lora_alpha=32, lora_dropout=0.1, bias="all"
|
|
)
|
|
checkpoint_name = f"{dataset_name}_{pet_config.pet_type}_{pet_config.task_type}_v1.pt".replace("/", "_")
|
|
text_column = "Tweet text"
|
|
label_column = "text_label"
|
|
lr = 3e-3
|
|
num_epochs = 20
|
|
batch_size = 8
|
|
seed = 42
|
|
set_seed(seed)
|
|
|
|
dataset = load_dataset("ought/raft", dataset_name)
|
|
classes = [k.replace("_", " ") for k in dataset["train"].features["Label"].names]
|
|
dataset = dataset.map(
|
|
lambda x: {"text_label": [classes[label] for label in x["Label"]]},
|
|
batched=True,
|
|
num_proc=1,
|
|
)
|
|
|
|
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path)
|
|
target_max_length = max([len(tokenizer(class_label)["input_ids"]) for class_label in classes])
|
|
|
|
def preprocess_function(examples):
|
|
inputs = examples[text_column]
|
|
targets = examples[label_column]
|
|
model_inputs = tokenizer(inputs, truncation=True)
|
|
labels = tokenizer(
|
|
targets, max_length=target_max_length, padding="max_length", truncation=True, return_tensors="pt"
|
|
)
|
|
labels = labels["input_ids"]
|
|
labels[labels == tokenizer.pad_token_id] = -100
|
|
model_inputs["labels"] = labels
|
|
return model_inputs
|
|
|
|
with accelerator.main_process_first():
|
|
processed_datasets = dataset.map(
|
|
preprocess_function,
|
|
batched=True,
|
|
num_proc=1,
|
|
remove_columns=dataset["train"].column_names,
|
|
load_from_cache_file=True,
|
|
desc="Running tokenizer on dataset",
|
|
)
|
|
accelerator.wait_for_everyone()
|
|
|
|
train_dataset = processed_datasets["train"]
|
|
eval_dataset = processed_datasets["train"]
|
|
test_dataset = processed_datasets["test"]
|
|
|
|
def collate_fn(examples):
|
|
return tokenizer.pad(examples, padding="longest", return_tensors="pt")
|
|
|
|
train_dataloader = DataLoader(
|
|
train_dataset, shuffle=True, collate_fn=collate_fn, batch_size=batch_size, pin_memory=True
|
|
)
|
|
eval_dataloader = DataLoader(eval_dataset, collate_fn=collate_fn, batch_size=batch_size, pin_memory=True)
|
|
test_dataloader = DataLoader(test_dataset, collate_fn=collate_fn, batch_size=batch_size, pin_memory=True)
|
|
|
|
# creating model
|
|
model = AutoModelForSeq2SeqLM.from_pretrained(model_name_or_path)
|
|
model = get_pet_model(model, pet_config)
|
|
model.print_trainable_parameters()
|
|
|
|
# optimizer
|
|
optimizer = torch.optim.AdamW(model.parameters(), lr=lr)
|
|
|
|
# lr scheduler
|
|
lr_scheduler = get_linear_schedule_with_warmup(
|
|
optimizer=optimizer,
|
|
num_warmup_steps=0,
|
|
num_training_steps=(len(train_dataloader) * num_epochs),
|
|
)
|
|
|
|
model, train_dataloader, eval_dataloader, optimizer, lr_scheduler = accelerator.prepare(
|
|
model, train_dataloader, eval_dataloader, optimizer, lr_scheduler
|
|
)
|
|
accelerator.print(model)
|
|
|
|
for epoch in range(num_epochs):
|
|
with TorchTracemalloc() as tracemalloc:
|
|
model.train()
|
|
total_loss = 0
|
|
for step, batch in enumerate(tqdm(train_dataloader)):
|
|
outputs = model(**batch)
|
|
loss = outputs.loss
|
|
total_loss += loss.detach().float()
|
|
accelerator.backward(loss)
|
|
optimizer.step()
|
|
lr_scheduler.step()
|
|
optimizer.zero_grad()
|
|
# Printing the GPU memory usage details such as allocated memory, peak memory, and total memory usage
|
|
accelerator.print("GPU Memory before entering the train : {}".format(b2mb(tracemalloc.begin)))
|
|
accelerator.print("GPU Memory consumed at the end of the train (end-begin): {}".format(tracemalloc.used))
|
|
accelerator.print("GPU Peak Memory consumed during the train (max-begin): {}".format(tracemalloc.peaked))
|
|
accelerator.print(
|
|
"GPU Total Peak Memory consumed during the train (max): {}".format(
|
|
tracemalloc.peaked + b2mb(tracemalloc.begin)
|
|
)
|
|
)
|
|
|
|
accelerator.print("CPU Memory before entering the train : {}".format(b2mb(tracemalloc.cpu_begin)))
|
|
accelerator.print("CPU Memory consumed at the end of the train (end-begin): {}".format(tracemalloc.cpu_used))
|
|
accelerator.print("CPU Peak Memory consumed during the train (max-begin): {}".format(tracemalloc.cpu_peaked))
|
|
accelerator.print(
|
|
"CPU Total Peak Memory consumed during the train (max): {}".format(
|
|
tracemalloc.cpu_peaked + b2mb(tracemalloc.cpu_begin)
|
|
)
|
|
)
|
|
|
|
model.eval()
|
|
eval_preds = []
|
|
with TorchTracemalloc() as tracemalloc:
|
|
for _, batch in enumerate(tqdm(eval_dataloader)):
|
|
batch = {k: v for k, v in batch.items() if k != "labels"}
|
|
with torch.no_grad():
|
|
outputs = model.generate(**batch, synced_gpus=True) # synced_gpus=True for DS-stage 3
|
|
preds = outputs.detach().cpu().numpy()
|
|
eval_preds.extend(tokenizer.batch_decode(preds, skip_special_tokens=True))
|
|
train_epoch_loss = total_loss / len(eval_dataloader)
|
|
train_ppl = torch.exp(train_epoch_loss)
|
|
accelerator.print(f"{epoch=}: {train_ppl=} {train_epoch_loss=}")
|
|
|
|
# Printing the GPU memory usage details such as allocated memory, peak memory, and total memory usage
|
|
accelerator.print("GPU Memory before entering the eval : {}".format(b2mb(tracemalloc.begin)))
|
|
accelerator.print("GPU Memory consumed at the end of the eval (end-begin): {}".format(tracemalloc.used))
|
|
accelerator.print("GPU Peak Memory consumed during the eval (max-begin): {}".format(tracemalloc.peaked))
|
|
accelerator.print(
|
|
"GPU Total Peak Memory consumed during the eval (max): {}".format(
|
|
tracemalloc.peaked + b2mb(tracemalloc.begin)
|
|
)
|
|
)
|
|
|
|
accelerator.print("CPU Memory before entering the eval : {}".format(b2mb(tracemalloc.cpu_begin)))
|
|
accelerator.print("CPU Memory consumed at the end of the eval (end-begin): {}".format(tracemalloc.cpu_used))
|
|
accelerator.print("CPU Peak Memory consumed during the eval (max-begin): {}".format(tracemalloc.cpu_peaked))
|
|
accelerator.print(
|
|
"CPU Total Peak Memory consumed during the eval (max): {}".format(
|
|
tracemalloc.cpu_peaked + b2mb(tracemalloc.cpu_begin)
|
|
)
|
|
)
|
|
|
|
correct = 0
|
|
total = 0
|
|
for pred, true in zip(eval_preds, dataset["validation"][label_column]):
|
|
if pred.strip() == true.strip():
|
|
correct += 1
|
|
total += 1
|
|
accuracy = correct / total * 100
|
|
accelerator.print(f"{accuracy=}")
|
|
accelerator.print(f"{eval_preds[:10]=}")
|
|
accelerator.print(f"{dataset['validation'][label_column][:10]=}")
|
|
accelerator.wait_for_everyone()
|
|
accelerator.save(get_pet_model_state_dict(model, state_dict=accelerator.get_state_dict(model)), checkpoint_name)
|
|
accelerator.wait_for_everyone()
|
|
|
|
model.eval()
|
|
test_preds = []
|
|
for _, batch in enumerate(tqdm(test_dataloader)):
|
|
batch = {k: v for k, v in batch.items() if k != "labels"}
|
|
outputs = model.generate(**batch, synced_gpus=True) # synced_gpus=True for DS-stage 3
|
|
test_preds.extend(tokenizer.batch_decode(outputs.detach().cpu().numpy(), skip_special_tokens=True))
|
|
|
|
test_preds_cleaned = []
|
|
for _, pred in enumerate(test_preds):
|
|
test_preds_cleaned.append(get_closest_label(pred, classes))
|
|
|
|
test_df = dataset["test"].to_pandas()
|
|
test_df["text_labels"] = test_preds_cleaned
|
|
test_df["text_labels_orig"] = test_preds
|
|
accelerator.print(test_df.sample(20))
|
|
|
|
pred_df = test_df[["ID", "text_labels"]]
|
|
pred_df.columns = ["ID", "Label"]
|
|
|
|
os.makedirs(f"data/{dataset_name}", exist_ok=True)
|
|
pred_df.to_csv(f"data/{dataset_name}/predictions.csv", index=False)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|