Skip to content

Commit ccace6d

Browse files
committed
Update Huawei NPU supports and dependencies
1 parent 80bfd76 commit ccace6d

23 files changed

Lines changed: 2163 additions & 163 deletions

dnallm/configuration/configs.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,9 @@ class TaskConfig(BaseModel):
9898
"""Configuration for different fine-tuning tasks"""
9999

100100
task_type: str = Field(
101-
...,
101+
default="binary",
102102
pattern="^(embedding|mask|generation|binary|binary_classification|multiclass|multi_class_classification|multilabel|multi_label_classification|regression|token|token_classification)$",
103+
description="Task type"
103104
)
104105
num_labels: int | None = Field(default=2, description="Number of labels (default 2)")
105106
label_names: list[str] | None = None

dnallm/datahandling/data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
load_dataset,
1818
concatenate_datasets,
1919
)
20-
from transformers import PreTrainedTokenizerBase
20+
from transformers import PreTrainedTokenizerBase # type: ignore[attr-defined]
2121
from transformers.tokenization_utils_base import BatchEncoding
2222

2323
from ..utils.sequence import (

dnallm/finetune/megatron.py

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
import os
2+
from tqdm import tqdm
3+
import pandas as pd
4+
import torch
5+
import torch.nn as nn
6+
from torch.utils.data import Dataset, DataLoader
7+
from sklearn.metrics import (
8+
accuracy_score,
9+
f1_score,
10+
precision_score,
11+
recall_score,
12+
mean_squared_error,
13+
r2_score,
14+
hamming_loss,
15+
)
16+
from megatron.core import mpu
17+
from megatron.core.enums import ModelType
18+
from megatron.core.transformer.spec_utils import import_module
19+
from megatron.training import get_args, print_rank_0, get_tokenizer
20+
from megatron.training.arguments import core_transformer_config_from_args
21+
from megatron.training.initialize import initialize_megatron
22+
from megatron.training.training import setup_model_and_optimizer
23+
from megatron.training.checkpointing import save_checkpoint
24+
25+
from ..models.special.mamba_npu import Mamba2ForSequenceClassification
26+
27+
28+
class CustomCSVDataset(Dataset):
29+
def __init__(self, csv_path, tokenizer, max_length=512):
30+
self.df = pd.read_csv(csv_path)
31+
self.tokenizer = tokenizer
32+
self.max_length = max_length
33+
34+
def __len__(self):
35+
return len(self.df)
36+
37+
def __getitem__(self, idx):
38+
text = str(self.df.iloc[idx]["sequence"])
39+
label = self.df.iloc[idx]["label"]
40+
inputs = self.tokenizer(
41+
text,
42+
max_length=self.max_length,
43+
padding="max_length",
44+
truncation=True,
45+
return_tensors="pt",
46+
)
47+
return {
48+
"input_ids": inputs["input_ids"].squeeze(0),
49+
"attention_mask": inputs["attention_mask"].squeeze(0),
50+
"position_ids": torch.arange(inputs["input_ids"].size(1), dtype=torch.long),
51+
"labels": torch.tensor(label, dtype=torch.long),
52+
}
53+
54+
55+
def create_dl(path, tokenizer, max_seq_len, shuffle=True):
56+
args = get_args()
57+
ds = CustomCSVDataset(path, tokenizer, max_seq_len)
58+
sampler = torch.utils.data.distributed.DistributedSampler(
59+
ds,
60+
num_replicas=mpu.get_data_parallel_world_size(),
61+
rank=mpu.get_data_parallel_rank(),
62+
shuffle=shuffle,
63+
)
64+
return DataLoader(ds, batch_size=args.micro_batch_size, sampler=sampler), sampler
65+
66+
67+
def model_provider(pre_process=True, post_process=True):
68+
args = get_args()
69+
config = core_transformer_config_from_args(args)
70+
mamba_stack_spec = import_module(args.spec)
71+
model = Mamba2ForSequenceClassification(
72+
config=config,
73+
mamba_stack_spec=mamba_stack_spec,
74+
vocab_size=args.padded_vocab_size,
75+
max_sequence_length=args.max_position_embeddings,
76+
pre_process=pre_process,
77+
post_process=post_process,
78+
parallel_output=True,
79+
num_labels=args.num_labels,
80+
problem_type=args.problem_type,
81+
share_embeddings_and_output_weights=not args.untie_embeddings_and_output_weights,
82+
position_embedding_type=args.position_embedding_type,
83+
rotary_percent=args.rotary_percent,
84+
rotary_base=args.rotary_base,
85+
)
86+
return model
87+
88+
89+
def add_extra_args(parser):
90+
group = parser.add_argument_group(title="genomic_finetune")
91+
group.add_argument("--num_labels", type=int, default=2)
92+
group.add_argument("--problem_type", type=str, default="single_label_classification")
93+
group.add_argument("--train_csv", type=str, required=True)
94+
group.add_argument("--dev_csv", type=str, required=True)
95+
group.add_argument("--test_csv", type=str, required=True)
96+
group.add_argument("--tensorboard_dir", type=str, default="tensorboard_logs")
97+
group.add_argument("--epochs", type=int, default=3)
98+
group.add_argument("--log_interval", type=int, default=10)
99+
return parser
100+
101+
102+
@torch.no_grad()
103+
def evaluate(model, dataloader, writer, global_step, desc="Eval"):
104+
args = get_args()
105+
model.eval()
106+
all_preds, all_labels = [], []
107+
total_eval_loss = 0
108+
device = torch.npu.current_device()
109+
110+
for batch in dataloader:
111+
input_ids = batch["input_ids"].to(device)
112+
labels = batch["labels"].to(device)
113+
114+
output = model(
115+
input_ids=input_ids,
116+
labels=labels,
117+
attention_mask=batch["attention_mask"].to(device),
118+
position_ids=batch["position_ids"].to(device),
119+
)
120+
121+
loss = output["loss"] if isinstance(output, dict) else output
122+
logits = output["logits"] if isinstance(output, dict) else None
123+
total_eval_loss += loss.item()
124+
125+
if args.problem_type == "regression":
126+
preds = logits.squeeze(-1)
127+
elif args.problem_type == "multi_label_classification":
128+
probs = torch.sigmoid(logits)
129+
preds = (probs > 0.5).float()
130+
else:
131+
preds = torch.argmax(logits, dim=-1)
132+
133+
all_preds.append(preds.float())
134+
all_labels.append(labels.float())
135+
136+
all_preds = torch.cat(all_preds, dim=0)
137+
all_labels = torch.cat(all_labels, dim=0)
138+
139+
if torch.distributed.is_initialized():
140+
world_size = torch.distributed.get_world_size()
141+
gathered_preds = [torch.zeros_like(all_preds) for _ in range(world_size)]
142+
gathered_labels = [torch.zeros_like(all_labels) for _ in range(world_size)]
143+
torch.distributed.all_gather(gathered_preds, all_preds)
144+
torch.distributed.all_gather(gathered_labels, all_labels)
145+
full_preds = torch.cat(gathered_preds, dim=0).cpu().numpy()
146+
full_labels = torch.cat(gathered_labels, dim=0).cpu().numpy()
147+
else:
148+
full_preds = all_preds.cpu().numpy()
149+
full_labels = all_labels.cpu().numpy()
150+
151+
if mpu.get_data_parallel_rank() == 0:
152+
avg_loss = total_eval_loss / len(dataloader)
153+
metrics_results = {"Loss": avg_loss}
154+
155+
if args.problem_type == "regression":
156+
metrics_results.update({
157+
"MSE": mean_squared_error(full_labels, full_preds),
158+
"R2": r2_score(full_labels, full_preds),
159+
})
160+
elif args.problem_type == "multi_label_classification":
161+
h_loss = hamming_loss(full_labels, full_preds)
162+
f1 = f1_score(full_labels, full_preds, average="samples", zero_division=0)
163+
metrics_results.update({"HammingLoss": h_loss, "F1_Samples": f1})
164+
else:
165+
acc = accuracy_score(full_labels, full_preds)
166+
f1 = f1_score(full_labels, full_preds, average="weighted", zero_division=0)
167+
prec = precision_score(full_labels, full_preds, average="weighted", zero_division=0)
168+
rec = recall_score(full_labels, full_preds, average="weighted", zero_division=0)
169+
metrics_results.update({"Acc": acc, "F1": f1, "Precision": prec, "Recall": rec})
170+
171+
print_rank_0(f">>> {desc} | Loss: {avg_loss:.4f} | F1(weighted): {f1:.4f}")
172+
173+
if writer:
174+
for name, value in metrics_results.items():
175+
writer.add_scalar(f"{desc}/{name}", value, global_step)
176+
177+
model.train()
178+
return avg_loss
179+
180+
181+
def start_train():
182+
initialize_megatron(extra_args_provider=add_extra_args)
183+
args = get_args()
184+
185+
# Auto-processing discordance of classification head
186+
original_load = nn.Module.load_state_dict
187+
188+
def patched_load(self, state_dict, strict=True):
189+
if "output_layer.weight" in state_dict and hasattr(self, "output_layer"):
190+
if state_dict["output_layer.weight"].shape != self.output_layer.weight.shape:
191+
state_dict.pop("output_layer.weight", None)
192+
state_dict.pop("output_layer.bias", None)
193+
return original_load(self, state_dict, strict=False)
194+
195+
nn.Module.load_state_dict = patched_load
196+
197+
tokenizer = get_tokenizer().tokenizer
198+
device = torch.npu.current_device()
199+
200+
writer = None
201+
if mpu.get_data_parallel_rank() == 0:
202+
from torch.utils.tensorboard import SummaryWriter
203+
204+
os.makedirs(args.tensorboard_dir, exist_ok=True)
205+
writer = SummaryWriter(log_dir=args.tensorboard_dir)
206+
207+
model_list, optimizer, _ = setup_model_and_optimizer(
208+
model_provider, ModelType.encoder_or_decoder
209+
)
210+
model = model_list[0]
211+
212+
train_loader, train_sampler = create_dl(args.train_csv, tokenizer, args.seq_length)
213+
dev_loader, _ = create_dl(args.dev_csv, tokenizer, args.seq_length, shuffle=False)
214+
test_loader, _ = create_dl(args.test_csv, tokenizer, args.seq_length, shuffle=False)
215+
216+
print_rank_0(">>> Starting Fine-tuning with tqdm...")
217+
global_step = 0
218+
total_steps = len(train_loader) * args.epochs
219+
220+
# Initialize best metrics and path
221+
best_val_loss = float("inf")
222+
best_model_path = os.path.join(args.save, "best_model_weights.pt")
223+
os.makedirs(args.save, exist_ok=True)
224+
225+
pbar = None
226+
if mpu.get_data_parallel_rank() == 0:
227+
pbar = tqdm(total=total_steps, desc="Finetuning", unit="it", dynamic_ncols=True)
228+
229+
for epoch in range(args.epochs):
230+
train_sampler.set_epoch(epoch)
231+
model.train()
232+
233+
for batch_idx, batch in enumerate(train_loader):
234+
optimizer.zero_grad(set_to_none=True)
235+
input_ids = batch["input_ids"].to(device)
236+
labels = batch["labels"].to(device).long()
237+
238+
output = model(
239+
input_ids=input_ids,
240+
labels=labels,
241+
attention_mask=batch["attention_mask"].to(device),
242+
position_ids=batch["position_ids"].to(device),
243+
)
244+
245+
loss = output["loss"] if isinstance(output, dict) else output
246+
loss.backward()
247+
optimizer.step()
248+
global_step += 1
249+
250+
if writer and global_step % args.log_interval == 0:
251+
writer.add_scalar("Train/Loss", loss.item(), global_step)
252+
253+
if pbar:
254+
pbar.update(1)
255+
pbar.set_postfix({"loss": f"{loss.item():.4f}", "epoch": epoch})
256+
257+
if global_step % args.eval_interval == 0:
258+
if pbar:
259+
pbar.write(f">>> Interval Eval at Step {global_step}")
260+
# receive return values and judge the best
261+
val_loss = evaluate(model, dev_loader, writer, global_step, desc="Eval_Step")
262+
if val_loss < best_val_loss:
263+
best_val_loss = val_loss
264+
if mpu.get_data_parallel_rank() == 0:
265+
torch.save(model.state_dict(), best_model_path)
266+
if pbar:
267+
pbar.write(f"*** Best Model Saved (Loss: {val_loss:.4f}) ***")
268+
269+
# Checkpointing
270+
if global_step % args.save_interval == 0:
271+
if pbar:
272+
pbar.write(f">>> Saving Checkpoint at Step {global_step}")
273+
save_checkpoint(global_step, model_list, optimizer, None, 0)
274+
275+
# receive return values and judge the best
276+
val_loss = evaluate(model, dev_loader, writer, global_step, desc="Eval_Epoch")
277+
if val_loss < best_val_loss:
278+
best_val_loss = val_loss
279+
if mpu.get_data_parallel_rank() == 0:
280+
torch.save(model.state_dict(), best_model_path)
281+
print_rank_0(f"*** Best Model Saved at Epoch End (Loss: {val_loss:.4f}) ***")
282+
283+
if pbar:
284+
pbar.close()
285+
286+
# Load the best weight before testing
287+
if os.path.exists(best_model_path):
288+
print_rank_0(f">>> Loading best weights from {best_model_path} for testing...")
289+
# Use map_location to ensure the correct loading in distributed environment
290+
best_state = torch.load(best_model_path, map_location=f"npu:{device}")
291+
model.load_state_dict(best_state)
292+
293+
print_rank_0(">>> Starting Final Test Set Evaluation...")
294+
evaluate(model, test_loader, writer, global_step, desc="Final_Test")
295+
296+
if writer:
297+
writer.close()
298+
print_rank_0(">>> All Tasks Completed.")

0 commit comments

Comments
 (0)