已合并
migrate-benchmarks-to-v2.12.0 #40569
migrate-benchmarks-to-v2.12.0 #40569
已合并
Lu_G创建于 7月9日
59 个文件变更+12457-0
@@ -0,0 +1,22 @@
1+--- CTR_Algorithm/pytorch/model/DCNv2.py 2026-03-31 11:09:49.207077300 +0800
2++++ dcn/CTR_Algorithm/pytorch/model/DCNv2.py 2026-03-31 11:12:56.799077300 +0800
3+@@ -120,7 +120,8 @@
4+ """
5+ super(DeepCrossNetv2, self).__init__()
6+ self.feature_fields = feature_fields
7+- self.offsets = np.array((0, *np.cumsum(feature_fields)[:-1]), dtype = np.long)
8++ self.offsets = np.array((0, *np.cumsum(feature_fields)[:-1]), dtype = np.int64)
9++ self.register_buffer("offsets_tensor", torch.as_tensor(self.offsets, dtype=torch.int64))
10+ self.model_method = model_method
11+
12+ # Embedding layer
13+@@ -157,7 +158,7 @@
14+ raise NotImplementedError
15+
16+ def forward(self, x):
17+- tmp = x + x.new_tensor(self.offsets).unsqueeze(0)
18++ tmp = x + self.offsets_tensor.unsqueeze(0)
19+
20+ # embeded dense vector
21+ embeded_x = self.embedding(tmp).view(-1, self.embedding_out_dim)
22+
@@ -0,0 +1,205 @@
1+# -*- coding: utf-8 -*-
2+import os
3+import pandas as pd
4+import numpy as np
5+import torch
6+import torch.nn as nn
7+import torch.optim as optim
8+import torch.utils.data as Data
9+import torch.nn.functional as F
10+from sklearn.preprocessing import LabelEncoder
11+from sklearn.model_selection import train_test_split
12+import sys
13+import time
14+import argparse
15+ 
16+from CTR_Algorithm.data.AmazonDataPreprocess import AmazonBookPreprocess
17+ 
18+MODEL_NAME='DCNV2'
19+ 
20+data = pd.read_csv('CTR_Algorithm/data/data.csv')
21+data_X = data.iloc[:,2:]
22+data_y = data.click.values
23+ 
24+data_X = data_X.apply(LabelEncoder().fit_transform)
25+ 
26+fields = data_X.max().values + 1
27+ 
28+# data_X = data.iloc[:,:-1]
29+# data_y = data.label.values
30+ 
31+tmp_X, test_X, tmp_y, test_y = train_test_split(data_X, data_y, test_size = 0.2, random_state=42, stratify=data_y)
32+train_X, val_X, train_y, val_y = train_test_split(tmp_X, tmp_y, test_size = 0.25, random_state=42, stratify=tmp_y)
33+ 
34+train_X = torch.from_numpy(train_X.values).long()
35+val_X = torch.from_numpy(val_X.values).long()
36+test_X = torch.from_numpy(test_X.values).long()
37+ 
38+train_y = torch.from_numpy(train_y).long()
39+val_y = torch.from_numpy(val_y).long()
40+test_y = torch.from_numpy(test_y).long()
41+ 
42+train_set = Data.TensorDataset(train_X, train_y)
43+val_set = Data.TensorDataset(val_X, val_y)
44+train_loader = Data.DataLoader(dataset=train_set,
45+ batch_size=32,
46+ shuffle=True)
47+val_loader = Data.DataLoader(dataset=val_set,
48+ batch_size=32,
49+ shuffle=False)
50+ 
51+ 
52+def detect_device_type():
53+ try:
54+ import torch
55+ if torch.cuda.is_available():
56+ return "cuda"
57+ try:
58+ import torch_npu
59+ import os
60+ os.environ['TORCHINDUCTOR_NPU_BACKEND'] = 'mlir'
61+ if torch.npu.is_available():
62+ return "npu"
63+ except ImportError:
64+ pass
65+ except ImportError:
66+ pass
67+ return "cpu"
68+ 
69+ 
70+def get_profile(profiler_start_step: int, profiler_end_step: int, profiling_save_path: str):
71+ warm_step = profiler_start_step
72+ active_step = profiler_end_step - warm_step +1
73+ print(f"[Profile INFO]: warm_step: {warm_step}, active_step: {active_step}, profiling_save_path: {profiling_save_path}")
74+ device = detect_device_type()
75+ if device == 'npu':
76+ import torch_npu
77+ g_prof_config = torch_npu.profiler._ExperimentalConfig(
78+ export_type=[
79+ torch_npu.profiler.ExportType.Text,
80+ torch_npu.profiler.ExportType.Db
81+ ],
82+ profiler_level=torch_npu.profiler.ProfilerLevel.Level2,
83+ msprof_tx=False,
84+ aic_metrics=torch_npu.profiler.AiCMetrics.AiCoreNone,
85+ l2_cache=False,
86+ op_attr=False,
87+ data_simplification=False,
88+ record_op_args=False,
89+ gc_detect_threshold=None)
90+ 
91+ return torch_npu.profiler.profile(
92+ activities=[
93+ torch_npu.profiler.ProfilerActivity.CPU,
94+ torch_npu.profiler.ProfilerActivity.NPU],
95+ schedule=torch_npu.profiler.schedule(wait=0, warmup=warm_step, active=active_step, repeat=1),
96+ on_trace_ready=torch_npu.profiler.tensorboard_trace_handler(profiling_save_path),
97+ record_shapes=False,
98+ profile_memory=False,
99+ with_stack=False,
100+ with_modules=False,
101+ with_flops=False,
102+ experimental_config=g_prof_config)
103+ elif device == 'cuda':
104+ from torch.profiler import profile, schedule, tensorboard_trace_handler, ProfilerActivity
105+ return profile(
106+ activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
107+ schedule=schedule(wait=0, warmup=warm_step, active=active_step, repeat=1),
108+ on_trace_ready=tensorboard_trace_handler(profiling_save_path),
109+ record_shapes=False,
110+ profile_memory=False,
111+ )
112+ else:
113+ print(f"[Warning]: No supported acceleration device (CUDA/NPU) detected, profiler will be disabled")
114+ return None
115+ 
116+def detect_device_type():
117+ try:
118+ import torch
119+ if torch.cuda.is_available():
120+ return "cuda"
121+ try:
122+ import torch_npu
123+ import os
124+ os.environ['TORCHINDUCTOR_NPU_BACKEND'] = 'mlir'
125+ if torch.npu.is_available():
126+ return "npu"
127+ except ImportError:
128+ pass
129+ except ImportError:
130+ pass
131+ return "cpu"
132+ 
133+def device_synchronize():
134+ if torch.cuda.is_available():
135+ torch.cuda.synchronize()
136+ elif torch.npu.is_available():
137+ torch.npu.synchronize()
138+ 
139+def eval_op_prof(model: nn.Module, device, mod, args):
140+ dl = iter(val_loader)
141+ x, _ = next(dl)
142+ x = x.to(device)
143+ pt_path= MODEL_NAME +'_'+ mod + '_result.pt'
144+ 
145+ model.eval()
146+ with torch.no_grad():
147+ res = model(x)
148+ torch.save(res, pt_path)
149+ device_synchronize()
150+ 
151+ prof = None
152+ if args.enable_profiler:
153+ profiling_save_path = args.profiler_save_path + '/' + MODEL_NAME + '/' + mod
154+ prof = get_profile(args.profiler_start_step, args.profiler_end_step, profiling_save_path)
155+ prof.start()
156+ 
157+ execution_times = []
158+ with torch.no_grad():
159+ for i in range(args.max_steps):
160+ start_time = time.time()
161+ model(x)
162+ device_synchronize()
163+ end_time = time.time()
164+ step_time_ms = (end_time - start_time) * 1000
165+ print(f"[{mod}]: Step {i}: {step_time_ms:.4f} ms")
166+ 
167+ if i >= 10:
168+ execution_times.append(step_time_ms)
169+ 
170+ if args.enable_profiler:
171+ prof.step()
172+ 
173+ if args.enable_profiler:
174+ prof.stop()
175+ if execution_times:
176+ avg_ms = sum(execution_times) / len(execution_times)
177+ print(f"[{mod}]:: Avg over {len(execution_times)} steps: {avg_ms:.4f} ms")
178+ 
179+parser = argparse.ArgumentParser(description=MODEL_NAME + " infernece")
180+parser.add_argument("--max_steps", type=int, default=200,
181+ help="Total training steps")
182+parser.add_argument("--enable_compile", action="store_true",
183+ help="Enable torch.compile and Inductor backend")
184+parser.add_argument("--enable_profiler", action="store_true",
185+ help="Enable profiler for performance analysis")
186+parser.add_argument("--profiler_start_step", type=int, default=5,
187+ help="Output directory for trained model")
188+parser.add_argument("--profiler_end_step", type=int, default=8,
189+ help="Output directory for trained model")
190+parser.add_argument("--profiler_save_path", type=str, default="./profile",
191+ help="Output directory for trained model")
192+args = parser.parse_args()
193+ 
194+from CTR_Algorithm.pytorch.model.DCNv2 import DeepCrossNetv2
195+model = DeepCrossNetv2(feature_fields = fields, embed_dim = 16, layer_num = 2, mlp_dims = (32, 16), dropout = 0.1, cross_method='Matrix')
196+ 
197+device_type = detect_device_type()
198+device = torch.device(device_type)
199+model.to(device)
200+mod = 'eager'
201+if args.enable_compile:
202+ model = torch.compile(model, dynamic=False)
203+ mod = 'compile'
204+print(f"{MODEL_NAME} inference begin, mode is {mod}")
205+eval_op_prof(model, device, mod, args)
@@ -0,0 +1,176 @@
1+# -*- coding: utf-8 -*-
2+import os
3+import time
4+import argparse
5+import sys
6+import pandas as pd
7+import numpy as np
8+import torch
9+import torch.nn as nn
10+import torch.optim as optim
11+import torch.utils.data as Data
12+import torch.nn.functional as F
13+from sklearn.model_selection import train_test_split
14+ 
15+from CTR_Algorithm.pytorch.model import DIN
16+from CTR_Algorithm.data.AmazonDataPreprocess import AmazonBookPreprocess
17+ 
18+MODEL_NAME = "DIN"
19+ 
20+data = pd.read_csv('CTR_Algorithm/data/amazon-books-100k.txt')
21+data = AmazonBookPreprocess(data)
22+fields = data.max().max()
23+ 
24+data_X = data.iloc[:,:-1]
25+data_y = data.label.values
26+ 
27+tmp_X, test_X, tmp_y, test_y = train_test_split(data_X, data_y, test_size = 0.2, random_state=42, stratify=data_y)
28+train_X, val_X, train_y, val_y = train_test_split(tmp_X, tmp_y, test_size = 0.25, random_state=42, stratify=tmp_y)
29+ 
30+train_X = torch.from_numpy(train_X.values).long()
31+val_X = torch.from_numpy(val_X.values).long()
32+test_X = torch.from_numpy(test_X.values).long()
33+ 
34+train_y = torch.from_numpy(train_y).long()
35+val_y = torch.from_numpy(val_y).long()
36+test_y = torch.from_numpy(test_y).long()
37+ 
38+train_set = Data.TensorDataset(train_X, train_y)
39+val_set = Data.TensorDataset(val_X, val_y)
40+train_loader = Data.DataLoader(dataset=train_set, batch_size=32, shuffle=True)
41+val_loader = Data.DataLoader(dataset=val_set, batch_size=32, shuffle=False)
42+ 
43+def get_profile(profiler_start_step: int, profiler_end_step: int, profiling_save_path: str):
44+ warm_step = profiler_start_step
45+ active_step = profiler_end_step - warm_step +1
46+ print(f"[Profile INFO]: warm_step: {warm_step}, active_step: {active_step}, profiling_save_path: {profiling_save_path}")
47+ device = detect_device_type()
48+ if device == 'npu':
49+ import torch_npu
50+ g_prof_config = torch_npu.profiler._ExperimentalConfig(
51+ export_type=[
52+ torch_npu.profiler.ExportType.Text,
53+ torch_npu.profiler.ExportType.Db
54+ ],
55+ profiler_level=torch_npu.profiler.ProfilerLevel.Level2,
56+ msprof_tx=False,
57+ aic_metrics=torch_npu.profiler.AiCMetrics.AiCoreNone,
58+ l2_cache=False,
59+ op_attr=False,
60+ data_simplification=False,
61+ record_op_args=False,
62+ gc_detect_threshold=None)
63+ 
64+ return torch_npu.profiler.profile(
65+ activities=[
66+ torch_npu.profiler.ProfilerActivity.CPU,
67+ torch_npu.profiler.ProfilerActivity.NPU],
68+ schedule=torch_npu.profiler.schedule(wait=0, warmup=warm_step, active=active_step, repeat=1),
69+ on_trace_ready=torch_npu.profiler.tensorboard_trace_handler(profiling_save_path),
70+ record_shapes=False,
71+ profile_memory=False,
72+ with_stack=False,
73+ with_modules=False,
74+ with_flops=False,
75+ experimental_config=g_prof_config)
76+ elif device == 'cuda':
77+ from torch.profiler import profile, schedule, tensorboard_trace_handler, ProfilerActivity
78+ return profile(
79+ activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
80+ schedule=schedule(wait=0, warmup=warm_step, active=active_step, repeat=1),
81+ on_trace_ready=tensorboard_trace_handler(profiling_save_path),
82+ record_shapes=False,
83+ profile_memory=False,
84+ )
85+ else:
86+ print(f"[Warning]: No supported acceleration device (CUDA/NPU) detected, profiler will be disabled")
87+ return None
88+ 
89+def detect_device_type():
90+ try:
91+ import torch
92+ if torch.cuda.is_available():
93+ return "cuda"
94+ try:
95+ import torch_npu
96+ import os
97+ os.environ['TORCHINDUCTOR_NPU_BACKEND'] = 'mlir'
98+ if torch.npu.is_available():
99+ return "npu"
100+ except ImportError:
101+ pass
102+ except ImportError:
103+ pass
104+ return "cpu"
105+ 
106+def device_synchronize():
107+ if torch.cuda.is_available():
108+ torch.cuda.synchronize()
109+ elif torch.npu.is_available():
110+ torch.npu.synchronize()
111+ 
112+def eval_op_prof(model: nn.Module, device, mod, args):
113+ dl = iter(val_loader)
114+ x, _ = next(dl)
115+ x = x.to(device)
116+ pt_path= MODEL_NAME +'_'+ mod + '_result.pt'
117+ 
118+ model.eval()
119+ with torch.no_grad():
120+ res = model(x)
121+ torch.save(res, pt_path)
122+ device_synchronize()
123+ 
124+ prof = None
125+ if args.enable_profiler:
126+ profiling_save_path = args.profiler_save_path + '/' + MODEL_NAME + '/' + mod
127+ prof = get_profile(args.profiler_start_step, args.profiler_end_step, profiling_save_path)
128+ prof.start()
129+ 
130+ execution_times = []
131+ with torch.no_grad():
132+ for i in range(args.max_steps):
133+ start_time = time.time()
134+ model(x)
135+ device_synchronize()
136+ end_time = time.time()
137+ step_time_ms = (end_time - start_time) * 1000
138+ print(f"[{mod}]: Step {i}: {step_time_ms:.4f} ms")
139+ 
140+ if i >= 10:
141+ execution_times.append(step_time_ms)
142+ 
143+ if args.enable_profiler:
144+ prof.step()
145+ 
146+ if args.enable_profiler:
147+ prof.stop()
148+ if execution_times:
149+ avg_ms = sum(execution_times) / len(execution_times)
150+ print(f"[{mod}]:: Avg over {len(execution_times)} steps: {avg_ms:.4f} ms")
151+ 
152+parser = argparse.ArgumentParser(description=MODEL_NAME + " infernece")
153+parser.add_argument("--max_steps", type=int, default=200,
154+ help="Total training steps")
155+parser.add_argument("--enable_compile", action="store_true",
156+ help="Enable torch.compile and Inductor backend")
157+parser.add_argument("--enable_profiler", action="store_true",
158+ help="Enable profiler for performance analysis")
159+parser.add_argument("--profiler_start_step", type=int, default=5,
160+ help="Output directory for trained model")
161+parser.add_argument("--profiler_end_step", type=int, default=8,
162+ help="Output directory for trained model")
163+parser.add_argument("--profiler_save_path", type=str, default="./profile",
164+ help="Output directory for trained model")
165+args = parser.parse_args()
166+ 
167+model = DIN.DeepInterestNet(feature_dim=fields, embed_dim=8, mlp_dims=[64,32], dropout=float(0.2))
168+device_type = detect_device_type()
169+device = torch.device(device_type)
170+model.to(device)
171+mod = 'eager'
172+if args.enable_compile:
173+ model = torch.compile(model, dynamic=False)
174+ mod = 'compile'
175+print(f"{MODEL_NAME} inference begin, mode is {mod}")
176+eval_op_prof(model, device, mod, args)
@@ -0,0 +1,510 @@
1+import argparse
2+import glob
3+import json
4+import os
5+import random
6+import stat
7+import time
8+from datetime import datetime
9+ 
10+import h5py
11+import numpy as np
12+ 
13+import torch
14+import torch.nn as nn
15+import torch.optim as optim
16+import torch.nn.functional as F
17+from torch.nn.utils.rnn import pad_sequence
18+from torch.utils.data import Dataset, DataLoader, ConcatDataset
19+from sklearn.metrics import roc_auc_score
20+ 
21+torch.manual_seed(2024)
22+random.seed(2024)
23+ 
24+MODEL_NAME = "ETA"
25+STD_DEV = (2 / 512) ** 0.5
26+EMBEDDING_FEATURE_NUM = 27
27+TARGET_FIELDS = ["206", "207", "216", "210"]
28+ 
29+def detect_device_type():
30+ try:
31+ import torch
32+ if torch.cuda.is_available():
33+ return "cuda"
34+ try:
35+ import torch_npu
36+ import os
37+ os.environ['TORCHINDUCTOR_NPU_BACKEND'] = 'mlir'
38+ if torch.npu.is_available():
39+ return "npu"
40+ except ImportError:
41+ pass
42+ except ImportError:
43+ pass
44+ return "cpu"
45+ 
46+def json_file_load(json_name: str, json_path: str) -> dict:
47+ """
48+ Load a JSON file from the specified path.
49+ """
50+ flags = os.O_RDONLY
51+ modes = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
52+ try:
53+ with os.fdopen(os.open(json_path, flags, modes), "r") as fp:
54+ json_re = json.load(fp)
55+ except FileNotFoundError as e:
56+ raise FileNotFoundError(f"{json_name} file not found: {e}") from e
57+ except Exception as e:
58+ raise RuntimeError(f"Error loading {json_name} file: {e}") from e
59+ 
60+ return json_re
61+ 
62+def collate_fn(batch):
63+ input_dicts = [item[0] for item in batch]
64+ target_dicts = [item[1] for item in batch]
65+ input_tensors = {}
66+ for key in input_dicts[0].keys():
67+ tensors = [d[key] for d in input_dicts if key in d and d[key] is not None]
68+ if not tensors:
69+ continue
70+ if tensors[0].dim() == 0:
71+ tensors = [t.unsqueeze(0) for t in tensors]
72+ input_tensors[key] = pad_sequence(tensors, batch_first=True)
73+ 
74+ target_tensors = {}
75+ for key in target_dicts[0].keys():
76+ tensors = [d[key] for d in target_dicts if key in d and d[key] is not None]
77+ if not tensors:
78+ continue
79+ target_tensors[key] = torch.stack(tensors)
80+ 
81+ return input_tensors, target_tensors
82+ 
83+def parse_arguments():
84+ parser = argparse.ArgumentParser()
85+ parser.add_argument("--embedding_size", type=int, default=16, help="Embedding size")
86+ parser.add_argument("--attention_dim", type=int, default=64, help="")
87+ parser.add_argument("--enable_compile", action="store_true", help="")
88+ parser.add_argument("--num_heads", type=int, default=4, help="")
89+ parser.add_argument("--short_output_dim", type=int, default=16, help="short attention output dimension")
90+ parser.add_argument("--max_seq_len", type=int, default=50, help="")
91+ parser.add_argument("--topk", type=int, default=16, help="")
92+ parser.add_argument("--deep_layers", type=str, default="512,256,128,64", help="deep layers")
93+ parser.add_argument("--hash_bits", type=int, default=32, help="")
94+ parser.add_argument("--reuse_hash", type=bool, default=True, help="")
95+ parser.add_argument("--batch_size", type=int, default=4096, help="Number of batch size")
96+ parser.add_argument("--learning_rate", type=float, default=0.001, help="learning rate")
97+ parser.add_argument("--optimizer", type=str, default="Adam",
98+ choices=["Adam", "Adagrad", "GD", "Momentum"], help="")
99+ parser.add_argument('--early_stop_patience', type=int, default=5, help="")
100+ parser.add_argument("--data_dir", type=str, default="./aliccp/aliccp_out/", help="data dir")
101+ parser.add_argument("--dt_dir", type=str, default='', help="data dt partition")
102+ parser.add_argument("--model_dir", type=str, default=f"./", help="code check point dir")
103+ parser.add_argument("--clear_existing_model", action="store_true", help="")
104+ parser.add_argument("--task_type", type=str, default="train",
105+ choices=["train", "eval", "predict"], help="task type")
106+ parser.add_argument("--max_steps", type=int, default=100,
107+ help="Total training steps (优先级高于num_epochs,-1表示使用num_epochs控制)")
108+ parser.add_argument('--epoch_num', type=int, default=1, help="Number of epochs")
109+ parser.add_argument('--train_batch_num', type=int, default=2000, help="Number of train batchs")
110+ parser.add_argument('--test_batch_num', type=int, default=10, help="Number of test batchs")
111+ parser.add_argument("--enable_profiler", action="store_true",
112+ help="Enable profiler for performance analysis")
113+ parser.add_argument("--profiler_start_step", type=int, default=5,
114+ help="Output directory for trained model")
115+ parser.add_argument("--profiler_end_step", type=int, default=8,
116+ help="Output directory for trained model")
117+ parser.add_argument("--profiler_save_path", type=str, default="./profile",
118+ help="Output directory for trained model")
119+ return parser.parse_args()
120+ 
121+ 
122+class HDF5Dataset(Dataset):
123+ def __init__(self, hdf5_path):
124+ self.hdf5_path = hdf5_path
125+ self._load_hdf5()
126+ 
127+ def _load_hdf5(self):
128+ with h5py.File(self.hdf5_path, 'r') as f:
129+ self.input_sample = {}
130+ self.target_sample = {}
131+ y = np.array(f["y"])
132+ z = np.array(f["z"])
133+ fields = [key for key in f.keys() if key not in ["y", "z"]]
134+ self.target_sample.update({"y": torch.tensor(y, dtype=torch.float32)})
135+ self.target_sample.update({"z": torch.tensor(z, dtype=torch.float32)})
136+ for multi_field in fields:
137+ self.input_sample.update({multi_field: torch.tensor(np.array(f[multi_field]), dtype=torch.int64)})
138+ self._length = len(y)
139+ 
140+ def __len__(self):
141+ return self._length
142+ 
143+ def __getitem__(self, idx):
144+ input_dict = {k: v[idx] for k, v in self.input_sample.items()}
145+ target_dict = {k: v[idx] for k, v in self.target_sample.items()}
146+ return input_dict, target_dict
147+ 
148+ 
149+class TorchDataSet(ConcatDataset):
150+ def __init__(self, files):
151+ datasets = [HDF5Dataset(fp) for fp in files]
152+ super().__init__(datasets)
153+ 
154+ 
155+class ETA(nn.Module):
156+ def __init__(self, spec, params):
157+ super(ETA, self).__init__()
158+ self.spec = spec
159+ self.params = params
160+ 
161+ # embedding layers
162+ self.emb_weights = nn.ModuleDict()
163+ for key, vocab_len in spec["vocab_length"].items():
164+ self.emb_weights[key] = nn.Embedding(vocab_len + 1, params.embedding_size)
165+ nn.init.normal_(self.emb_weights[key].weight, std=STD_DEV)
166+ 
167+ self.hash_weights = nn.Parameter(
168+ torch.randn(params.embedding_size, params.hash_bits),
169+ requires_grad=False
170+ )
171+ 
172+ # attention paramters
173+ self.short_attentions = nn.ModuleList()
174+ self.long_attentions = nn.ModuleList()
175+ for _ in range(len(TARGET_FIELDS)): # 4 target fields
176+ self.short_attentions.append(ShortAttention(params))
177+ self.long_attentions.append(LongAttention(self.hash_weights, params))
178+ 
179+ # mlp layer
180+ self.mlp = nn.Sequential()
181+ deep_layers = list(map(int, params.deep_layers.strip().split(",")))
182+ input_dim = EMBEDDING_FEATURE_NUM * params.embedding_size
183+ for i, dim in enumerate(deep_layers):
184+ self.mlp.add_module(f'mlp{i}', nn.Linear(input_dim, dim))
185+ self.mlp.add_module(f'relu{i}', nn.ReLU())
186+ input_dim = dim
187+ 
188+ # output layer
189+ self.output = nn.Linear(input_dim, 1)
190+ 
191+ def embedding_lookup_sparse(self, params: nn.Embedding, ids: torch.Tensor, combiner: str):
192+ mask = (ids >= 0).float().unsqueeze(-1)
193+ ids = ids.clone()
194+ ids[ids == -1] = 0
195+ embedding = params(ids) * mask
196+ if combiner == "sum":
197+ return embedding.sum(dim=1)
198+ elif combiner == "mean":
199+ return embedding.sum(dim=1) / mask.sum(dim=1).clamp(min=1e-7)
200+ else:
201+ raise ValueError("combiner only supoort 'sum', 'mean'")
202+ 
203+ def forward(self, features):
204+ # embedding
205+ embeddings = {}
206+ masks = {}
207+ for key in self.spec["one_hot_fields"]:
208+ embeddings[key] = self.emb_weights[key](features[key]).unsqueeze(1)
209+ for key in self.spec["multi_hot_fields"]:
210+ feat = features[key]
211+ masks[key] = (feat >= 0).bool().unsqueeze(1)
212+ feat = feat.clone()
213+ feat[feat == -1] = 0
214+ embeddings[key] = self.emb_weights[key](feat)
215+ for key in self.spec["special_fields"]:
216+ embeddings[key] = self.embedding_lookup_sparse(
217+ self.emb_weights[key], features[key], combiner="sum"
218+ ).unsqueeze(1)
219+ 
220+ def long_emb_cat(field_name):
221+ dense_embedding = embeddings.get(field_name)
222+ dense_mask = masks.get(field_name)
223+ if dense_embedding is None or dense_mask is None:
224+ raise ValueError(f"Field {field_name} not found in embeddings or masks")
225+ padded_embedding = F.pad(dense_embedding, (0, 0, 0, self.params.max_seq_len), "constant", 0)
226+ padded_mask = F.pad(dense_mask, (0, self.params.max_seq_len), "constant", False)
227+ return (
228+ padded_embedding[:, :self.params.topk, :],
229+ padded_embedding[:, :self.params.max_seq_len, :],
230+ padded_mask[:, :, :self.params.topk],
231+ padded_mask[:, :, :self.params.max_seq_len]
232+ )
233+ emb_cats = [long_emb_cat(field) for field in self.spec["multi_hot_fields"]]
234+ target_fields = TARGET_FIELDS
235+ 
236+ short_attns = []
237+ long_attns = []
238+ 
239+ for i, (emb_cat, target) in enumerate(zip(emb_cats, target_fields)):
240+ emb_target = embeddings[target]
241+ 
242+ emb_short = emb_cat[0]
243+ mask_short = emb_cat[2]
244+ short_attns.append(self.short_attentions[i](emb_target, emb_short, mask_short))
245+ 
246+ emb_long = emb_cat[1]
247+ mask_long = emb_cat[3]
248+ long_attns.append(self.long_attentions[i](emb_target, emb_long, mask_long))
249+ 
250+ # concat all embeddings
251+ all_embs = []
252+ for field in self.spec["one_hot_fields"] + self.spec["special_fields"]:
253+ if embeddings[field].dim() > 3:
254+ all_embs.append(embeddings[field].squeeze(1))
255+ else:
256+ all_embs.append(embeddings[field])
257+ all_embs += short_attns + long_attns
258+ all_embs = torch.cat(all_embs, dim=1)
259+ if all_embs.dim() >= 2:
260+ concat_emb = all_embs.squeeze(1)
261+ else:
262+ concat_emb = all_embs
263+ 
264+ concat_emb = concat_emb.reshape(concat_emb.size(0), -1)
265+ # mlp and outputs
266+ mlp_out = self.mlp(concat_emb)
267+ logits = self.output(mlp_out).squeeze()
268+ pred = torch.sigmoid(logits)
269+ return pred, logits
270+ 
271+ def build_loss(self, pred, labels, click_weight=0.14, epsilon=1e-7):
272+ if pred.shape != labels.shape:
273+ raise ValueError(f"pred and labels must be the same shape. "
274+ f"pred shape: {pred.shape}, labels shape: {labels.shape}")
275+ pred = torch.clamp(pred, min=epsilon, max=1 - epsilon)
276+ loss = - (1 - click_weight) / click_weight * labels * torch.log(pred) - (1 - labels) * torch.log(1 - pred)
277+ return loss.mean()
278+ 
279+ def build_optimizer(self):
280+ if self.params.optimizer == "Adam":
281+ optimizer = optim.Adam(
282+ params=self.parameters(),
283+ lr=self.params.learning_rate,
284+ betas=[0.9, 0.999], eps=1e-8
285+ )
286+ elif self.params.optimizer == "Adagrad":
287+ optimizer = optim.Adagrad(
288+ params=self.parameters(),
289+ lr=self.params.learning_rate,
290+ initial_accumulator_value=1e-6
291+ )
292+ elif self.params.optimizer == "Momentum":
293+ optimizer = optim.SGD(
294+ params=self.parameters(),
295+ lr=self.params.learning_rate,
296+ momentum=0.95
297+ )
298+ elif self.params.optimizer == "SGD":
299+ optimizer = optim.SGD(
300+ params=self.parameters(),
301+ lr=self.params.learning_rate,
302+ )
303+ else:
304+ raise ValueError("Unsupported optimizer type: {}".format(args.optimizer))
305+ return optimizer
306+ 
307+ 
308+class ShortAttention(nn.Module):
309+ def __init__(self, params):
310+ super().__init__()
311+ self.attention_dim = params.attention_dim
312+ self.num_heads = params.num_heads
313+ self.key_dim = self.attention_dim // self.num_heads
314+ 
315+ self.q_fc = nn.Linear(params.embedding_size, self.attention_dim)
316+ self.k_fc = nn.Linear(params.embedding_size, self.attention_dim)
317+ self.v_fc = nn.Linear(params.embedding_size, self.attention_dim)
318+ self.o_fc = nn.Linear(self.attention_dim, params.short_output_dim)
319+ 
320+ def forward(self, target_input, seq_input, mask):
321+ # target_input:[B, 1, E], seq_input:[B, S, E], mask:[B, 1, S] B = target_input.size(0)
322+ # logger.info(f"tgt shape: {target_input.shape}, seq shape: {seq_input.shape}, mask shape: {mask.shape}")
323+ b, tgt_len = target_input.shape[:2]
324+ b, seq_len = seq_input.shape[:2]
325+ query = self.q_fc(target_input) # [B, 1, A]
326+ key = self.k_fc(seq_input) # [B, S, A]
327+ value = self.v_fc(seq_input) # [B, S, A]
328+ 
329+ # split heads
330+ query = query.view(b, tgt_len, self.num_heads, self.key_dim).permute(0, 2, 1, 3) # [B, Heads, 1, key_dim]
331+ key = key.view(b, seq_len, self.num_heads, self.key_dim).permute(0, 2, 3, 1) # [B, Heads, key_dim, S]
332+ value = value.view(b, seq_len, self.num_heads, self.key_dim).permute(0, 2, 1, 3) # [B, Heads, S, key_dim]
333+ 
334+ # scaled dot-product attention
335+ scores = torch.matmul(query, key) / (self.key_dim ** 0.5) # [B, Heads, 1, S]
336+ scores = scores.masked_fill(~mask.unsqueeze(1), float('-inf'))
337+ 
338+ attn = F.softmax(scores, dim=-1)
339+ 
340+ output = torch.matmul(attn, value) # [B, Heads, 1, key_dim]
341+ output = output.permute(0, 2, 1, 3)
342+ b, lens, heads, dims = output.shape
343+ output = output.reshape(b * lens, 1, heads * dims)
344+ output = self.o_fc(output)
345+ return output
346+ 
347+ 
348+class LongAttention(nn.Module):
349+ def __init__(self, hash_weights, params):
350+ super().__init__()
351+ self.hash_weights = hash_weights
352+ self.reuse_hash = params.reuse_hash
353+ self.topk = params.topk
354+ self.short_attn = ShortAttention(params)
355+ 
356+ def lsh_hash(self, vecs, hash_weights):
357+ rotated_vecs = torch.matmul(vecs, hash_weights)
358+ return (rotated_vecs > 0).float()
359+ 
360+ def forward(self, target_input, seq_input, mask):
361+ # target_input:[B, 1, E], seq_input:[B, S, E], mask:[B, 1, S]
362+ b, s, e = seq_input.shape
363+ hash_weights = self.hash_weights if self.reuse_hash else nn.Parameter(
364+ torch.randn(e, self.hash_weights.size(-1)),
365+ requires_grad=False
366+ ) # [E, hash_bits]
367+ 
368+ target_hash = self.lsh_hash(target_input.squeeze(), hash_weights) # [B, hash_bits]
369+ seq_hash = self.lsh_hash(seq_input, hash_weights) # [B, S, hash_bits]
370+ # calculate similarity(hamming distance)
371+ hash_sim = - torch.sum(torch.abs(seq_hash - target_hash.unsqueeze(1)), dim=-1) # [B, S]
372+ hash_sim = hash_sim.masked_fill(~mask.squeeze(1), float('-inf'))
373+ 
374+ # select topk
375+ _, topk_idx = torch.topk(hash_sim, self.topk, dim=-1) # [B, topk]
376+ 
377+ topk_seq = torch.gather(
378+ seq_input,
379+ 1,
380+ topk_idx.unsqueeze(-1).expand(-1, -1, e)
381+ )
382+ 
383+ topk_idx = topk_idx.unsqueeze(1)
384+ topk_mask = torch.gather(mask, 2, topk_idx)
385+ 
386+ # apply short attention to topk
387+ output = self.short_attn(target_input, topk_seq, topk_mask)
388+ return output
389+ 
390+def get_profile(profiler_start_step: int, profiler_end_step: int, profiling_save_path: str):
391+ warm_step = profiler_start_step
392+ active_step = profiler_end_step - warm_step +1
393+ print(f"[Profile INFO]: warm_step: {warm_step}, active_step: {active_step}, profiling_save_path: {profiling_save_path}")
394+ device = detect_device_type()
395+ if device == 'npu':
396+ import torch_npu
397+ g_prof_config = torch_npu.profiler._ExperimentalConfig(
398+ export_type=[
399+ torch_npu.profiler.ExportType.Text,
400+ torch_npu.profiler.ExportType.Db
401+ ],
402+ profiler_level=torch_npu.profiler.ProfilerLevel.Level2,
403+ msprof_tx=False,
404+ aic_metrics=torch_npu.profiler.AiCMetrics.AiCoreNone,
405+ l2_cache=False,
406+ op_attr=False,
407+ data_simplification=False,
408+ record_op_args=False,
409+ gc_detect_threshold=None)
410+ 
411+ return torch_npu.profiler.profile(
412+ activities=[
413+ torch_npu.profiler.ProfilerActivity.CPU,
414+ torch_npu.profiler.ProfilerActivity.NPU],
415+ schedule=torch_npu.profiler.schedule(wait=0, warmup=warm_step, active=active_step, repeat=1),
416+ on_trace_ready=torch_npu.profiler.tensorboard_trace_handler(profiling_save_path),
417+ record_shapes=False,
418+ profile_memory=False,
419+ with_stack=False,
420+ with_modules=False,
421+ with_flops=False,
422+ experimental_config=g_prof_config)
423+ elif device == 'cuda':
424+ from torch.profiler import profile, schedule, tensorboard_trace_handler, ProfilerActivity
425+ return profile(
426+ activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
427+ schedule=schedule(wait=0, warmup=warm_step, active=active_step, repeat=1),
428+ on_trace_ready=tensorboard_trace_handler(profiling_save_path),
429+ record_shapes=False,
430+ profile_memory=False,
431+ )
432+ else:
433+ print(f"[Warning]: No supported acceleration device (CUDA/NPU) detected, profiler will be disabled")
434+ return None
435+ 
436+ 
437+def device_synchronize():
438+ if torch.cuda.is_available():
439+ torch.cuda.synchronize()
440+ elif torch.npu.is_available():
441+ torch.npu.synchronize()
442+ 
443+ 
444+def evaluate_op(model: ETA, dataloader, device, args):
445+ mod = 'compile' if args.enable_compile else 'eager'
446+ dl = iter(dataloader)
447+ features, _ = next(dl)
448+ features = {k: v.to(device) for k, v in features.items()}
449+ 
450+ for key, value in features.items():
451+ if isinstance(value, torch.Tensor) and value.dtype == torch.int64:
452+ features[key] = value.to(torch.int32)
453+ 
454+ if mod == 'compile':
455+ model = torch.compile(model, dynamic=False)
456+ model.eval()
457+ with torch.no_grad():
458+ model(features)
459+ device_synchronize()
460+ 
461+ prof = None
462+ if args.enable_profiler:
463+ profiling_save_path = args.profiler_save_path + '/' + MODEL_NAME + '/' + mod
464+ prof = get_profile(args.profiler_start_step, args.profiler_end_step, profiling_save_path)
465+ prof.start()
466+ 
467+ exec_times = []
468+ with torch.no_grad():
469+ for i in range(args.max_steps):
470+ e2etime1 = time.perf_counter()
471+ model(features)
472+ device_synchronize()
473+ e2etime2 = time.perf_counter()
474+ elapsed_ms = (e2etime2 - e2etime1) * 1000
475+ print(f"iterations {i} / {args.max_steps}: [{mod}] e2e time: {elapsed_ms} ms.")
476+ 
477+ if i >= 10:
478+ exec_times.append(elapsed_ms)
479+ 
480+ if args.enable_profiler:
481+ prof.step()
482+ if args.enable_profiler:
483+ prof.stop()
484+ if exec_times:
485+ avg_time = sum(exec_times) / len(exec_times)
486+ print("Step time consumption statistics (excluding the first 10 steps)")
487+ print(f"\n>>> [{mod}] avg e2e: {avg_time:.3f} ms <<<")
488+ 
489+def main(args):
490+ test_files = glob.glob("%stest/data_test.csv.hd5.*" % args.data_dir)
491+ spec = json_file_load("spec", os.path.join(args.data_dir, "spec.json"))
492+ model = ETA(spec, args)
493+ device_type = detect_device_type()
494+ print(f"Using device type {device_type}")
495+ device = torch.device(device_type)
496+ model.to(device)
497+ test_dataset = TorchDataSet(test_files[1:2])
498+ test_loader = DataLoader(dataset=test_dataset,
499+ batch_size=args.batch_size,
500+ shuffle=True,
501+ collate_fn=collate_fn,
502+ prefetch_factor=100,
503+ num_workers=10)
504+ evaluate_op(model, test_loader, device, args)
505+ 
506+ 
507+if __name__ == "__main__":
508+ print(f"{MODEL_NAME} inference begin !!!")
509+ args = parse_arguments()
510+ main(args)
@@ -0,0 +1,577 @@
1+import os
2+import stat
3+import glob
4+import json
5+import random
6+import shutil
7+import logging
8+import time
9+from datetime import datetime
10+import argparse
11+ 
12+import pytz
13+import h5py
14+import numpy as np
15+import torch
16+import torch.nn as nn
17+import torch.optim as optim
18+from torch.nn.utils.rnn import pad_sequence
19+from torch.utils.data import DataLoader, Dataset, ConcatDataset
20+from sklearn.metrics import roc_auc_score
21+ 
22+torch.manual_seed(2024)
23+random.seed(2024)
24+ 
25+MODEL_NAME = "MMOE"
26+EMBEDDING_FEATURE_NUM = 23
27+STD_DEV = (2 / 512) ** 0.5
28+ 
29+def detect_device_type():
30+ try:
31+ import torch
32+ if torch.cuda.is_available():
33+ return "cuda"
34+ try:
35+ import torch_npu
36+ import os
37+ os.environ['TORCHINDUCTOR_NPU_BACKEND'] = 'mlir'
38+ if torch.npu.is_available():
39+ return "npu"
40+ except ImportError:
41+ pass
42+ except ImportError:
43+ pass
44+ return "cpu"
45+ 
46+def parse_arguments():
47+ parser = argparse.ArgumentParser(description='PyTorch Example with Command Line Arguments')
48+ parser.add_argument('--embedding_size', type=int, default=16, help="Embedding size")
49+ parser.add_argument('--batch_size', type=int, default=4096, help="Batch size for training")
50+ parser.add_argument('--learning_rate', type=float, default=0.001, help="Learning rate")
51+ parser.add_argument('--optimizer', type=str, default="Adam", choices=["Adam", "Adagrad", "GD", "Momentum"],
52+ help="Optimizer type")
53+ parser.add_argument('--expert_layers', type=str, default="512,256", help="Expert layers")
54+ parser.add_argument('--tower_layers', type=str, default="128,64", help="tower layers")
55+ parser.add_argument('--ctr_task_wgt', type=float, default=0.5, help="loss weight of ctr task")
56+ parser.add_argument('--data_dir', type=str, default="./aliccp/aliccp_out/", help="Data directory")
57+ parser.add_argument('--dt_dir', type=str, default="", help="Data dt partition")
58+ parser.add_argument('--model_dir', type=str, default=f"./",
59+ help="Model checkpoint directory")
60+ parser.add_argument('--servable_model_dir', type=str, default=f"./",
61+ help="Export servable model for pytorch Serving")
62+ parser.add_argument('--clear_existing_model', action="store_true", help="Clear existing model or not")
63+ parser.add_argument('--max_seq_len', type=int, default=50, help="Max length of sequence")
64+ parser.add_argument('--task_num', type=int, default=2, help="Task number")
65+ parser.add_argument('--experts_num', type=int, default=8, help="Number of experts")
66+ parser.add_argument('--log_level', type=str, default="DEBUG",
67+ choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
68+ help="Log level")
69+ parser.add_argument('--epoch_num', type=int, default=10, help="Number of epochs")
70+ parser.add_argument('--train_batch_num', type=int, default=2000, help="Number of train batchs")
71+ parser.add_argument('--eval_batch_num', type=int, default=20, help="Number of eval batchs")
72+ parser.add_argument('--test_batch_num', type=int, default=20, help="Number of test batchs")
73+ parser.add_argument("--enable_compile", action="store_true", help="")
74+ parser.add_argument("--max_steps", type=int, default=100, help="Total training steps")
75+ parser.add_argument("--enable_profiler", action="store_true",
76+ help="Enable profiler for performance analysis")
77+ parser.add_argument("--profiler_start_step", type=int, default=5,
78+ help="Output directory for trained model")
79+ parser.add_argument("--profiler_end_step", type=int, default=8,
80+ help="Output directory for trained model")
81+ parser.add_argument("--profiler_save_path", type=str, default="./profile",
82+ help="Output directory for trained model")
83+ return parser.parse_args()
84+ 
85+ 
86+def json_file_load(json_name: str, json_path: str) -> dict:
87+ """
88+ Load a JSON file from the specified path.
89+ """
90+ flags = os.O_RDONLY
91+ modes = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
92+ try:
93+ with os.fdopen(os.open(json_path, flags, modes), "r") as fp:
94+ json_re = json.load(fp)
95+ except FileNotFoundError as e:
96+ raise FileNotFoundError(f"{json_name} file not found: {e}") from e
97+ except Exception as e:
98+ raise RuntimeError(f"Error loading {json_name} file: {e}") from e
99+ 
100+ return json_re
101+ 
102+ 
103+def pre_deal_dataset(dataset):
104+ num_pos = 0
105+ num_neg = 0
106+ num_pos_y = 0
107+ num_neg_y = 0
108+ dataset_index = 0
109+ for sub_ds in dataset.datasets:
110+ z = sub_ds.target_sample["z"]
111+ num_pos += (z == 1).sum().item()
112+ num_neg += (z == 0).sum().item()
113+ 
114+ y = sub_ds.target_sample["y"]
115+ num_pos_y += (y == 1).sum().item()
116+ num_neg_y += (y == 0).sum().item()
117+ 
118+ # logger.info("find pos index %s", dataset_index)
119+ dataset_index += 1
120+ return np.log(num_neg_y / num_pos_y), np.log(num_neg / num_pos)
121+ 
122+ 
123+def collate_fn(batch):
124+ input_dicts = [item[0] for item in batch]
125+ target_dicts = [item[1] for item in batch]
126+ input_tensors = {}
127+ 
128+ for key in input_dicts[0].keys():
129+ tensors = [d[key] for d in input_dicts if key in d and d[key] is not None]
130+ if not tensors:
131+ continue
132+ 
133+ if tensors[0].dim() == 0:
134+ tensors = [t.unsqueeze(0) for t in tensors]
135+ input_tensors[key] = pad_sequence(tensors, batch_first=True)
136+ target_tensors = {}
137+ for key in target_dicts[0].keys():
138+ tensors = [d[key] for d in target_dicts if key in d and d[key] is not None]
139+ if not tensors:
140+ continue
141+ target_tensors[key] = torch.stack(tensors)
142+ return input_tensors, target_tensors
143+ 
144+ 
145+class HDF5Dataset(Dataset):
146+ def __init__(self, hdf5_path):
147+ self.hdf5_path = hdf5_path
148+ self._load_hdf5()
149+ 
150+ def _load_hdf5(self):
151+ with h5py.File(self.hdf5_path, 'r') as f:
152+ self.input_sample = {}
153+ self.target_sample = {}
154+ y = np.array(f["y"])
155+ z = np.array(f["z"])
156+ fields = [key for key in f.keys() if key not in ["y", "z"]]
157+ self.target_sample.update({"y": torch.tensor(y, dtype=torch.float32)})
158+ self.target_sample.update({"z": torch.tensor(z, dtype=torch.float32)})
159+ for multi_field in fields:
160+ self.input_sample.update({multi_field: torch.tensor(np.array(f[multi_field]), dtype=torch.int64)})
161+ self._length = len(y)
162+ self.positive_indices = np.where(y == 1)[0]
163+ self.negative_indices = np.where(y == 0)[0]
164+ # logger.info(f"load file {self.hdf5_path} finished")
165+ 
166+ def __len__(self):
167+ return self._length
168+ 
169+ def __getitem__(self, idx):
170+ input_dict = {k: v[idx] for k, v in self.input_sample.items()}
171+ target_dict = {k: v[idx] for k, v in self.target_sample.items()}
172+ return input_dict, target_dict
173+ 
174+ 
175+class TorchDataSet(ConcatDataset):
176+ def __init__(self, files):
177+ print(files)
178+ datasets = [HDF5Dataset(fp) for fp in files]
179+ super().__init__(datasets)
180+ 
181+ 
182+class TorchMmoeModel(nn.Module):
183+ def __init__(self, params) -> None:
184+ super().__init__()
185+ self.params = params
186+ self.spec = json_file_load("spec", os.path.join(self.params.data_dir, "spec.json"))
187+ self.embedding_layers = self.build_embedding_layers()
188+ self.experts = self.build_experts()
189+ self.gates = self.build_gate_networks()
190+ self.task_output_layers = self.build_task_output_layers()
191+ 
192+ self.tower_names = ['ctr', 'cvr']
193+ self.towers = nn.ModuleDict()
194+ self.towers_output_layers = nn.ModuleDict()
195+ tower_units = list(map(int, self.params.tower_layers.strip().split(',')))
196+ input_dim = self.params.experts_num * list(map(int, self.params.expert_layers.strip().split(',')))[-1]
197+ for name in self.tower_names:
198+ tower_layers = []
199+ in_dim = input_dim
200+ for out_dim in tower_units:
201+ tower_layers.append(nn.Linear(in_dim, out_dim))
202+ tower_layers.append(nn.BatchNorm1d(out_dim))
203+ tower_layers.append(nn.ReLU())
204+ tower_layers.append(nn.Dropout(p=0.2))
205+ in_dim = out_dim
206+ self.towers[name] = nn.Sequential(*tower_layers)
207+ self.towers_output_layers[name] = nn.Linear(in_dim, 1)
208+ 
209+ def forward(self, features: dict):
210+ # Build the embedding layer
211+ x_deep = self.get_embedding(features)
212+ experts_out = [expert(x_deep) for expert in self.experts]
213+ experts_out = torch.stack(experts_out, dim=1)
214+ 
215+ gate_outs = [gate(x_deep) for gate in self.gates]
216+ 
217+ task_outputs = []
218+ for gate_network in gate_outs:
219+ gate_network = gate_network.unsqueeze(-1)
220+ task_out = torch.multiply(experts_out, gate_network)
221+ task_out_shape = list(task_out.shape)
222+ task_outputs.append(torch.reshape(task_out, shape=[-1, task_out_shape[1] * task_out_shape[2]]))
223+ return task_outputs
224+ 
225+ def build_embedding_layers(self):
226+ embeddings = nn.ModuleDict()
227+ for key, vocab_len in self.spec["vocab_length"].items():
228+ embeddings[key] = nn.Embedding(vocab_len + 1, self.params.embedding_size)
229+ nn.init.normal_(embeddings[key].weight, std=STD_DEV)
230+ return embeddings
231+ 
232+ def build_experts(self):
233+ 
234+ experts = nn.ModuleList()
235+ expert_units = list(map(int, self.params.expert_layers.strip().split(',')))
236+ input_dim = self.params.embedding_size * EMBEDDING_FEATURE_NUM
237+ 
238+ for _ in range(self.params.experts_num):
239+ expert_layers = []
240+ in_features = input_dim
241+ 
242+ for out_features in expert_units:
243+ expert_layers.append(nn.Linear(in_features, out_features))
244+ expert_layers.append(nn.BatchNorm1d(out_features))
245+ expert_layers.append(nn.ReLU())
246+ expert_layers.append(nn.Dropout(0.2))
247+ in_features = out_features
248+ experts.append(nn.Sequential(*expert_layers))
249+ return experts
250+ 
251+ def build_gate_networks(self):
252+ gates = nn.ModuleList()
253+ input_dim = self.params.embedding_size * EMBEDDING_FEATURE_NUM
254+ 
255+ for _ in range(self.params.task_num):
256+ gate = nn.Sequential(
257+ nn.Linear(input_dim, self.params.experts_num),
258+ nn.Softmax(dim=1)
259+ )
260+ gates.append(gate)
261+ return gates
262+ 
263+ def build_task_output_layers(self):
264+ task_output_layers = nn.ModuleList()
265+ expert_units = list(map(int, self.params.expert_layers.strip().split(',')))
266+ input_dim = self.params.experts_num * expert_units[-1]
267+ tower_units = list(map(int, self.params.tower_layers.strip().split(',')))
268+ for _ in range(self.params.task_num):
269+ tower = []
270+ in_features = input_dim
271+ for out_features in tower_units:
272+ tower.append(nn.Linear(in_features, out_features))
273+ tower.append(nn.ReLU())
274+ in_features = out_features
275+ task_output_layers.append(nn.Sequential(*tower))
276+ return task_output_layers
277+ 
278+ def embedding_lookup_sparse_fake(self, key,
279+ ids: torch.Tensor,
280+ combiner: str = None,
281+ name: str = None) -> torch.Tensor:
282+ dense_mask = torch.unsqueeze(torch.where(ids >= 0,
283+ torch.ones_like(ids, dtype=torch.float32),
284+ torch.zeros_like(ids)
285+ ),
286+ dim=-1
287+ )
288+ 
289+ # Replace invalid IDs (-1) with zeros
290+ ids = torch.where(ids == -1, torch.zeros_like(ids), ids)
291+ embedding_layer = self.embedding_layers[key]
292+ embedding_output = embedding_layer(ids)
293+ embedding = embedding_output * dense_mask
294+ summed_embedding = torch.sum(embedding, axis=1)
295+ if combiner == "sum":
296+ return summed_embedding
297+ elif combiner == "mean":
298+ return summed_embedding / torch.sum(dense_mask, axis=1)
299+ else:
300+ raise ValueError("combiner only supoort 'sum', 'mean'")
301+ 
302+ def get_embedding(self, features: dict) -> torch.Tensor:
303+ """
304+ Build the embedding layer for the model.
305+ 
306+ Args:
307+ features (dict): The input features.
308+ Returns:
309+ torch.Tensor: The concatenated and reshaped embedding tensor.
310+ """
311+ on_hot_field_lst = self.spec.get("one_hot_fields")
312+ other_field_lst = self.spec.get("multi_hot_fields") + self.spec.get("special_fields")
313+ 
314+ embeddings = {}
315+ for key in on_hot_field_lst:
316+ embedding_layer = self.embedding_layers[key]
317+ embeddings[key] = embedding_layer(features[key])
318+ embeddings[key] = torch.reshape(embeddings[key], [-1, 1, self.params.embedding_size])
319+ for key in other_field_lst:
320+ embeddings[key] = torch.unsqueeze(
321+ self.embedding_lookup_sparse_fake(key=key, ids=features[key], combiner="sum"),
322+ dim=1
323+ )
324+ embedding = torch.concat(
325+ [embeddings.get(field_name) for field_name in self.spec.get("one_hot_fields")] +
326+ [embeddings.get(field_name) for field_name in self.spec.get("multi_hot_fields")] +
327+ [embeddings.get(field_name) for field_name in self.spec.get("special_fields")],
328+ dim=2,
329+ )
330+ 
331+ return torch.reshape(embedding, [-1, EMBEDDING_FEATURE_NUM * self.params.embedding_size])
332+ 
333+ def build_tower(self, tower_input: torch.Tensor, name: str) -> torch.Tensor:
334+ """
335+ Build the tower network for a specific task.
336+ 
337+ Args:
338+ tower_input (torch.Tensor): The input tensor for the tower.
339+ name (str): The name of the tower.
340+ Returns:
341+ torch.Tensor: The output tensor of the tower network.
342+ """
343+ tower_units = list(map(int, self.params.tower_layers.strip().split(',')))
344+ y_tower = tower_input
345+ for tower_i, _ in enumerate(tower_units):
346+ tower_linear = nn.Linear(in_features=y_tower.shape[-1], out_features=tower_units[tower_i])
347+ tower_linear_out = tower_linear(y_tower)
348+ relu = nn.ReLU()
349+ y_tower = relu(tower_linear_out)
350+ return y_tower
351+ 
352+ def build_predictions(self, task_outputs: list) -> dict:
353+ """
354+ Build the predictions for the model.
355+ 
356+ Args:
357+ task_outputs (list): A list of task output tensors.
358+ 
359+ Returns:
360+ dict: A dictionary containing the predictions for ctr, cvr, and ctcvr.
361+ """
362+ preds = {}
363+ tower_outputs = {}
364+ for i, name in enumerate(self.tower_names):
365+ y = self.towers[name](task_outputs[i])
366+ y = self.towers_output_layers[name](y)
367+ y = torch.reshape(y, [-1, ])
368+ preds[name + '_logit'] = y
369+ preds[name] = torch.sigmoid(y)
370+ tower_outputs[name] = y
371+ 
372+ ctr_pred = preds.get('ctr')
373+ cvr_pred = preds.get('cvr')
374+ if ctr_pred is not None and cvr_pred is not None:
375+ if 'ctr_logit' not in preds or 'cvr_logit' not in preds:
376+ raise ValueError("Missing required keys in preds dictionary")
377+ else:
378+ preds['ctcvr'] = ctr_pred * cvr_pred
379+ preds['ctcvr_logit'] = preds['ctr_logit'] + preds['cvr_logit']
380+ return preds
381+ 
382+ def build_loss(self,
383+ labels: dict,
384+ y_ctr_logit: torch.Tensor,
385+ y_ctcvr_logit: torch.Tensor,
386+ ctr_weight,
387+ ctcvr_weight) -> torch.Tensor:
388+ """
389+ Build the loss function for the model.
390+ 
391+ Args:
392+ labels (dict): A dictionary containing the true labels for ctr and ctcvr.
393+ y_ctr_logit (torch.Tensor): The predicted ctr values.
394+ y_ctcvr_logit (torch.Tensor): The predicted ctcvr values.
395+ Returns:
396+ torch.Tensor: The combined loss tensor.
397+ """
398+ y_ctr_logit = y_ctr_logit.view(-1)
399+ y_ctcvr_logit = y_ctcvr_logit.view(-1)
400+ y = labels['y'].view(-1)
401+ z = labels['z'].view(-1)
402+ 
403+ bce_loss = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([ctr_weight],
404+ dtype=torch.float32,
405+ device=y.device)
406+ )
407+ ctcvr_bce_loss = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([ctcvr_weight],
408+ dtype=torch.float32,
409+ device=z.device)
410+ )
411+ 
412+ ctr_loss = bce_loss(y_ctr_logit, y)
413+ ctcvr_loss = ctcvr_bce_loss(y_ctcvr_logit, z)
414+ ctr_task_wgt = self.params.ctr_task_wgt
415+ 
416+ return ctr_task_wgt * ctr_loss + (1 - ctr_task_wgt) * ctcvr_loss
417+ 
418+ def build_optimizer(self):
419+ """
420+ Build the optimizer for training.
421+ 
422+ Args:
423+ loss (torch.Tensor): The loss tensor to minimize.
424+ 
425+ Returns:
426+ optim: The operation for applying gradients.
427+ 
428+ Raises:
429+ ValueError: If the optimizer type is not supported.
430+ """
431+ if self.params.optimizer == "Adam":
432+ optimizer = optim.Adam(
433+ params=self.parameters(),
434+ lr=self.params.learning_rate,
435+ betas=[0.9, 0.99], eps=1e-8
436+ )
437+ elif self.params.optimizer == "Adagrad":
438+ optimizer = optim.Adagrad(
439+ params=self.parameters(),
440+ lr=self.params.learning_rate,
441+ initial_accumulator_value=1e-6
442+ )
443+ elif self.params.optimizer == "Momentum":
444+ optimizer = optim.SGD(
445+ params=self.parameters(),
446+ lr=self.params.learning_rate,
447+ momentum=0.95
448+ )
449+ elif self.params.optimizer == "SGD":
450+ optimizer = optim.SGD(
451+ params=self.parameters(),
452+ lr=self.params.learning_rate,
453+ )
454+ else:
455+ raise ValueError("Unsupported optimizer type: {}".format(args.optimizer))
456+ return optimizer
457+ 
458+ 
459+def get_profile(profiler_start_step: int, profiler_end_step: int, profiling_save_path: str):
460+ warm_step = profiler_start_step
461+ active_step = profiler_end_step - warm_step +1
462+ print(f"[Profile INFO]: warm_step: {warm_step}, active_step: {active_step}, profiling_save_path: {profiling_save_path}")
463+ device = detect_device_type()
464+ if device == 'npu':
465+ import torch_npu
466+ g_prof_config = torch_npu.profiler._ExperimentalConfig(
467+ export_type=[
468+ torch_npu.profiler.ExportType.Text,
469+ torch_npu.profiler.ExportType.Db
470+ ],
471+ profiler_level=torch_npu.profiler.ProfilerLevel.Level2,
472+ msprof_tx=False,
473+ aic_metrics=torch_npu.profiler.AiCMetrics.AiCoreNone,
474+ l2_cache=False,
475+ op_attr=False,
476+ data_simplification=False,
477+ record_op_args=False,
478+ gc_detect_threshold=None)
479+ 
480+ return torch_npu.profiler.profile(
481+ activities=[
482+ torch_npu.profiler.ProfilerActivity.CPU,
483+ torch_npu.profiler.ProfilerActivity.NPU],
484+ schedule=torch_npu.profiler.schedule(wait=0, warmup=warm_step, active=active_step, repeat=1),
485+ on_trace_ready=torch_npu.profiler.tensorboard_trace_handler(profiling_save_path),
486+ record_shapes=False,
487+ profile_memory=False,
488+ with_stack=False,
489+ with_modules=False,
490+ with_flops=False,
491+ experimental_config=g_prof_config)
492+ elif device == 'cuda':
493+ from torch.profiler import profile, schedule, tensorboard_trace_handler, ProfilerActivity
494+ return profile(
495+ activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
496+ schedule=schedule(wait=0, warmup=warm_step, active=active_step, repeat=1),
497+ on_trace_ready=tensorboard_trace_handler(profiling_save_path),
498+ record_shapes=False,
499+ profile_memory=False,
500+ )
501+ else:
502+ print(f"[Warning]: No supported acceleration device (CUDA/NPU) detected, profiler will be disabled")
503+ return None
504+ 
505+ 
506+def device_synchronize():
507+ if torch.cuda.is_available():
508+ torch.cuda.synchronize()
509+ elif torch.npu.is_available():
510+ torch.npu.synchronize()
511+ 
512+ 
513+def evaluate_op(model: TorchMmoeModel, te_files, device, args):
514+ te_dataset = TorchDataSet(te_files)
515+ te_ctr, te_ctcvr = pre_deal_dataset(te_dataset)
516+ test_dataloader = DataLoader(dataset=te_dataset,
517+ batch_size=args.batch_size,
518+ shuffle=True,
519+ collate_fn=collate_fn,
520+ prefetch_factor=100,
521+ num_workers=10)
522+ dl = iter(test_dataloader)
523+ input_sample, target_sample = next(dl)
524+ input_sample = {k: v.to(device) for k, v in input_sample.items()}
525+ 
526+ mod = 'compile' if args.enable_compile else 'eager'
527+ if mod == 'compile':
528+ model = torch.compile(model, mode='reduce-overhead', dynamic=False)
529+ 
530+ model.eval()
531+ with torch.no_grad():
532+ model(input_sample)
533+ device_synchronize()
534+ 
535+ prof = None
536+ if args.enable_profiler:
537+ profiling_save_path = args.profiler_save_path + '/' + MODEL_NAME + '/' + mod
538+ prof = get_profile(args.profiler_start_step, args.profiler_end_step, profiling_save_path)
539+ prof.start()
540+ 
541+ exec_times = []
542+ with torch.no_grad():
543+ for i in range(args.max_steps):
544+ e2etime1 = time.perf_counter()
545+ model(input_sample)
546+ device_synchronize()
547+ e2etime2 = time.perf_counter()
548+ elapsed_ms = (e2etime2 - e2etime1) * 1000
549+ print(f"iterations {i} / {args.max_steps}: [{mod}] e2e time: {elapsed_ms} ms.")
550+ 
551+ if i >= 10:
552+ exec_times.append(elapsed_ms)
553+ 
554+ if args.enable_profiler:
555+ prof.step()
556+ if args.enable_profiler:
557+ prof.stop()
558+ if exec_times:
559+ avg_time = sum(exec_times) / len(exec_times)
560+ print("Step time consumption statistics (excluding the first 10 steps)")
561+ print(f"\n>>> [{mod}] avg e2e: {avg_time:.3f} ms <<<")
562+ 
563+ 
564+def main(args):
565+ model = TorchMmoeModel(args)
566+ te_files = glob.glob("%stest/data_test.csv.hd5.*" % args.data_dir)
567+ device_type = detect_device_type()
568+ print(f"Using device type {device_type}")
569+ device = torch.device(device_type)
570+ model.to(device)
571+ evaluate_op(model, te_files, device, args)
572+ 
573+ 
574+if __name__ == "__main__":
575+ print(f"{MODEL_NAME} inference begin !!!")
576+ args = parse_arguments()
577+ main(args)
@@ -0,0 +1,96 @@
1+# 推理说明
2+ 
3+本 README 说明如何使用 **DCNv2****DIN****MMOE****ETA**模型进行推理(含 eager / torch.compile 两种模式)。在运行完成后输出。
4+ 
5+---
6+ 
7+## 目录
8+ 
9+- [1. 数据集处理](#1-数据集处理)
10+ - [1.1 DIN and DCNv2](#11-din-and-dcnv2)
11+ - [1.2 MMOE and ETA](#12-mmoe-and-eta)
12+- [2. 模型推理](#2-模型推理)
13+ - [2.1 eager mode(默认)](#21-eager-mode默认)
14+ - [2.2 启用 torch.compile(可选)](#22-启用-torchcompile可选)
15+ - [2.3 采集 profile文件(可选)](#23-采集profile文件可选)
16+ 
17+---
18+ 
19+## 1. 数据集处理
20+ 
21+### 1.1 DIN and DCNv2
22+ 
23+**DIN****DCNv2** 的推理数据集来自 CTR_Algorithm 仓库示例数据,该数据集无需手动下载
24+ 
25+https://github.com/Prayforhanluo/CTR_Algorithm/tree/main/data
26+ 
27+在DIN或DCNv2目录下执行如下命令:
28+ 
29+```bash
30+git clone https://github.com/Prayforhanluo/CTR_Algorithm.git
31+```
32+ 
33+- 数据集目录:`CTR_Algorithm/data/data.csv`
34+ 
35+### 1.2 MMOE and ETA
36+ 
37+进入**MMOE****ETA**文件夹下,进行如下数据预处理
38+ 
39+推理数据集来自 https://tianchi.aliyun.com/dataset/408 Ali-CPP数据集,需做如下处理:
40+ 
41+1. 下载数据集,存放至当前目录下alicpp文件夹中
42+2. 后续流程参考:https://gitee.com/ascend/RecSDK/blob/develop_torch_benchmark/torch_examples_benchmark/model_zoo/README.md
43+ 
44+执行完成后数据集会默认生成至aliccp_out目录下。
45+ 
46+网络依赖已整理到 `requirements.txt`,可直接安装:
47+ 
48+```bash
49+pip install -r ./requirements.txt
50+```
51+ 
52+在模型执行时,默认加载数据集路径为: `./aliccp/aliccp_out/`;模型脚本同时支持运行时使用如下命令动态指定数据集目录:
53+ 
54+```bash
55+python eta.py --data_dir path/to/your/data/
56+```
57+ 
58+## 2. 模型推理
59+ 
60+进入**DCNv2**文件夹,直接执行推理脚本`dcnv2.py`即可
61+ 
62+- 注:运行该网络前需要先将patch文件加上,具体方式如下:
63+ 
64+ ```bash
65+ cd DCNv2
66+ unix2dos dcnv2.patch #如果在arm机器上需要执行
67+ git apply dcnv2.patch
68+ ```
69+ 
70+### 2.1 eager mode(默认)
71+ 
72+```bash
73+python dcnv2.py
74+```
75+ 
76+### 2.2 启用 `torch.compile`(可选)
77+ 
78+脚本已支持 `--enable_compile` 这样的开关,开启方式为:
79+ 
80+```bash
81+python dcnv2.py \
82+--enable_compile
83+```
84+ 
85+### 2.3 采集profile文件(可选)
86+ 
87+脚本已支持 `--enable_profiler` 这样的开关,开启方式为:
88+ 
89+```bash
90+python dcnv2.py \
91+ --enable_profiler \
92+ --profiler_start_step 5 \
93+ --profiler_end_step 6 \
94+```
95+ 
96+可以通过 `--profiler_start_step``--profiler_end_step` 分别设置profile开始和结束步数。
@@ -0,0 +1,5 @@
1+h5py==3.13.0
2+numpy==1.26.4
3+pandas
4+pytz
5+scikit-learn
@@ -0,0 +1,124 @@
1+# Baichuan2-7B-Chat 微调训练
2+ 
3+本 README 说明如何使用 **Baichuan2-7B-Chat** 模型权重,结合 **LlamaFactory** 提供的 `alpaca_zh_demo.json` 示例数据,完成数据下载、预处理与训练启动(含 eager / torch.compile 两种模式)。
4+ 
5+---
6+ 
7+## 目录
8+ 
9+- [1. 模型权重](#1-模型权重)
10+- [2. 数据获取](#2-数据获取)
11+- [3. 数据预处理](#3-数据预处理)
12+- [4. 模型训练](#4-模型训练)
13+ - [4.1 eager mode(默认)](#41-eager-mode默认)
14+ - [4.2 启用 torchcompile(可选)](#42-启用-torchcompile可选)
15+ 
16+---
17+ 
18+### 环境配置
19+ 
20+本项目依赖已整理到 `requirements.txt`,可直接安装:
21+ 
22+```bash
23+pip install -r ../utils/requirements.txt
24+```
25+ 
26+## 1. 模型权重
27+ 
28+- Hugging Face 模型:**baichuan-inc/Baichuan2-7B-Chat**
29+ https://huggingface.co/baichuan-inc/Baichuan2-7B-Chat
30+ 
31+- 可使用如下的自定义脚本下载
32+ 
33+```bash
34+python ../utils/download_hf.py --model baichuan-inc/Baichuan2-7B-Chat --save_path ./Baichuan2-7B-Chat
35+```
36+ 
37+## 2. 数据获取
38+ 
39+训练数据集来自 LlamaFactory 仓库示例数据:
40+ 
41+- `alpaca_zh_demo.json`
42+ https://github.com/hiyouga/LlamaFactory/blob/main/data/alpaca_zh_demo.json
43+ 
44+建议直接下载 raw 文件到本地:
45+ 
46+```bash
47+wget -O alpaca_zh_demo.json \
48+https://raw.githubusercontent.com/hiyouga/LlamaFactory/main/data/alpaca_zh_demo.json
49+```
50+ 
51+## 3. 数据预处理
52+ 
53+本项目提供 `preprocess.py`,用于把 Alpaca 格式(`instruction`/`input`/`output`)转换为 Baichuan 常用的 `conversations` 格式。
54+ 
55+### 命令
56+ 
57+```bash
58+python preprocess.py -i alpaca_zh_demo.json -o train.json
59+```
60+ 
61+## 4. 模型训练
62+ 
63+训练脚本:`run_baichuan2.sh`,脚本支持在 GPU 和 NPU 训练
64+ 
65+开始训练前,请修改脚本中的路径参数:
66+ 
67+- 模型权重路径(本地目录)
68+- 训练数据路径(预处理后的 `train.json`
69+ 
70+### 4.1 eager mode(默认)
71+ 
72+```bash
73+bash run_baichuan2.sh
74+```
75+ 
76+### 4.2 启用 `torch.compile`(可选)
77+ 
78+可通过添加 `--enable_compile` 选项运行图模式
79+当在 GPU 上训练时,默认后端使用triton;当在 NPU 上训练时,可进一步指定后端为 mlir 或 dvm,默认使用 mlir。
80+ 
81+**默认后端(mlir,可不写 --npu-backend):**
82+ 
83+```bash
84+bash run_baichuan2.sh \
85+ --enable_compile
86+```
87+ 
88+**显式指定后端为 mlir:**
89+ 
90+```bash
91+bash run_baichuan2.sh \
92+ --enable_compile \
93+ --npu-backend mlir
94+```
95+ 
96+**切换后端为 dvm:**
97+ 
98+```bash
99+bash run_baichuan2.sh \
100+ --enable_compile \
101+ --npu-backend dvm
102+```
103+ 
104+当在 NPU 上训练时,可通过 `--mfusion` 参数开启 MFusion 图算融合优化功能, 配合不同的NPU图模式后端, 进一步提升模型的性能,使用示例如下
105+ 
106+```bash
107+bash run_baichuan2.sh \
108+ --enable_compile \
109+ --npu-backend dvm \
110+ --mfusion
111+```
112+ 
113+### 4.3 采集profile文件(可选)
114+ 
115+脚本已支持 `--enable_profiler` 这样的开关,开启方式为:
116+ 
117+```bash
118+bash run_baichuan2.sh \
119+ --enable_profiler \
120+ --profiler_start_step 5 \
121+ --profiler_end_step 6 \
122+```
123+ 
124+可以通过 `--profiler_start_step``--profiler_end_step` 分别设置profile开始和结束步数。
@@ -0,0 +1,50 @@
1+import json
2+import os
3+import argparse
4+ 
5+def convert_alpaca_to_baichuan(input_path, output_path):
6+ with open(input_path, "r", encoding="utf-8") as f:
7+ data = json.load(f)
8+ 
9+ converted_data = []
10+ 
11+ for item in data:
12+ instruction = item.get("instruction", "").strip()
13+ input_text = item.get("input", "").strip()
14+ output_text = item.get("output", "").strip()
15+ 
16+ if input_text:
17+ human_text = instruction + "\n" + input_text
18+ else:
19+ human_text = instruction
20+ 
21+ new_item = {
22+ "conversations": [
23+ {
24+ "from": "human",
25+ "value": human_text
26+ },
27+ {
28+ "from": "assistant",
29+ "value": output_text
30+ }
31+ ]
32+ }
33+ 
34+ converted_data.append(new_item)
35+ 
36+ with open(output_path, "w", encoding="utf-8") as f:
37+ json.dump(converted_data, f, ensure_ascii=False, indent=2)
38+ 
39+ print(f"Conversion completed, total {len(converted_data)} records")
40+ print(f"Output file: {output_path}")
41+ 
42+def build_argparser():
43+ p = argparse.ArgumentParser(description="Convert Alpaca JSON to Baichuan conversations JSON")
44+ p.add_argument("--input", "-i", required=True, help="Path to alpaca json file")
45+ p.add_argument("--output", "-o", required=True, help="Path to output json file")
46+ return p
47+ 
48+if __name__ == "__main__":
49+ args = build_argparser().parse_args()
50+ convert_alpaca_to_baichuan(args.input, args.output)
@@ -0,0 +1,23 @@
1+#!/bin/bash
2+export TORCHINDUCTOR_CACHE_DIR="./cache"
3+export ASCEND_RT_VISIBLE_DEVICES=0
4+export CUDA_VISIBLE_DEVICES=0
5+export TORCH_COMPILE_DEBUG=1
6+export TORCH_NPU_USE_COMPATIBLE_IMPL=1
7+ 
8+rm -rf ./cache/*
9+mkdir -p ./cache logs
10+ 
11+python train_baichuan2_7B.py \
12+ --model_path $MODEL_PATH \
13+ --data_path $DATA_PATH \
14+ --output_dir "./baichuan2-finetuned" \
15+ --num_epochs 3 \
16+ --max_steps 200 \
17+ --batch_size 1 \
18+ --learning_rate 2e-5 \
19+ --max_length 1024 \
20+ --use_lora \
21+ --use_bf16 \
22+ --gradient_checkpointing \
23+ > logs/train_baichuan.log 2>&1
@@ -0,0 +1,308 @@
1+import os
2+import json
3+import time
4+import sys
5+from pathlib import Path
6+import argparse
7+import logging
8+from typing import Dict, List, Optional
9+ 
10+import torch
11+from datasets import Dataset, load_dataset
12+ 
13+from transformers import (
14+ AutoTokenizer,
15+ AutoModelForCausalLM,
16+ TrainingArguments,
17+ Trainer,
18+ DataCollatorForSeq2Seq,
19+ TrainerCallback,
20+ set_seed,
21+)
22+from peft import LoraConfig, get_peft_model, TaskType
23+sys.path.append(str(Path(__file__).parent.parent))
24+from utils.utils import TimingCallback, get_profile, detect_device_type
25+ 
26+ 
27+logging.basicConfig(level=logging.INFO)
28+logger = logging.getLogger(__name__)
29+ 
30+MODEL_NAME = "Baichuan2-7B-Chat"
31+ 
32+class BaichuanTrainer(Trainer):
33+ def compute_loss(self, model, inputs, return_outputs=False, num_items_in_batch=None):
34+ return super().compute_loss(model, inputs, return_outputs=return_outputs)
35+ 
36+ 
37+class Baichuan2Trainer:
38+ def __init__(self, args):
39+ self.args = args
40+ self.model = None
41+ self.tokenizer = None
42+ self.setup_training()
43+ 
44+ def setup_training(self):
45+ logger.info(f"Loading model/tokenizer from {self.args.model_path}")
46+ 
47+ self.tokenizer = AutoTokenizer.from_pretrained(
48+ self.args.model_path,
49+ trust_remote_code=True,
50+ padding_side="right",
51+ model_max_length=self.args.max_length,
52+ )
53+ 
54+ if self.tokenizer.pad_token is None:
55+ self.tokenizer.pad_token = self.tokenizer.eos_token
56+ 
57+ self.model = AutoModelForCausalLM.from_pretrained(
58+ self.args.model_path,
59+ trust_remote_code=True,
60+ torch_dtype=torch.bfloat16 if self.args.use_bf16 else torch.float16,
61+ use_cache=not self.args.gradient_checkpointing,
62+ )
63+ 
64+ if self.args.gradient_checkpointing:
65+ self.model.gradient_checkpointing_enable()
66+ self.model.config.use_cache = False
67+ 
68+ def apply_lora(self):
69+ if not self.args.use_lora:
70+ return
71+ 
72+ logger.info("Applying LoRA...")
73+ 
74+ target_modules = self.args.lora_target_modules.split(",") if self.args.lora_target_modules else ["W_pack"]
75+ 
76+ lora_config = LoraConfig(
77+ task_type=TaskType.CAUSAL_LM,
78+ r=self.args.lora_r,
79+ lora_alpha=self.args.lora_alpha,
80+ lora_dropout=self.args.lora_dropout,
81+ target_modules=target_modules,
82+ bias="none",
83+ )
84+ self.model = get_peft_model(self.model, lora_config)
85+ self.model.print_trainable_parameters()
86+ 
87+ def format_conversation(self, conversations: List[Dict]) -> str:
88+ text = ""
89+ for turn in conversations:
90+ role = turn.get("from", "")
91+ content = turn.get("value", "")
92+ if role == "human":
93+ text += f"<reserved_106>{content}"
94+ elif role == "assistant":
95+ text += f"<reserved_107>{content}"
96+ elif role == "system":
97+ text += f"<reserved_106>{content}"
98+ text += self.tokenizer.eos_token
99+ return text
100+ 
101+ def load_and_preprocess_data(self) -> Dataset:
102+ logger.info(f"Loading dataset from {self.args.data_path}")
103+ 
104+ if self.args.data_path.endswith(".json") or self.args.data_path.endswith(".jsonl"):
105+ with open(self.args.data_path, "r", encoding="utf-8") as f:
106+ if self.args.data_path.endswith(".jsonl"):
107+ raw = [json.loads(line) for line in f]
108+ else:
109+ raw = json.load(f)
110+ 
111+ formatted = []
112+ for item in raw:
113+ if "conversations" in item:
114+ text = self.format_conversation(item["conversations"])
115+ else:
116+ text = item.get("text", "")
117+ formatted.append({"text": text})
118+ 
119+ dataset = Dataset.from_list(formatted)
120+ else:
121+ dataset = load_dataset(self.args.data_path, split=self.args.split)
122+ 
123+ def preprocess_function(examples):
124+ tokenized = self.tokenizer(
125+ examples["text"],
126+ truncation=True,
127+ padding="max_length",
128+ max_length=self.args.max_length,
129+ return_attention_mask=True,
130+ )
131+ tokenized["labels"] = tokenized["input_ids"].copy()
132+ return tokenized
133+ 
134+ num_proc = self.args.num_proc if self.args.num_proc > 0 else None
135+ processed = dataset.map(
136+ preprocess_function,
137+ batched=True,
138+ remove_columns=dataset.column_names,
139+ num_proc=num_proc,
140+ load_from_cache_file=not self.args.overwrite_cache,
141+ )
142+ logger.info(f"Dataset size: {len(processed)}")
143+ return processed
144+ 
145+ def create_trainer(self, train_dataset, eval_dataset=None):
146+ has_eval = eval_dataset is not None
147+ eval_strategy = "steps" if has_eval else "no"
148+ 
149+ training_args = TrainingArguments(
150+ output_dir=self.args.output_dir,
151+ overwrite_output_dir=True,
152+ num_train_epochs=self.args.num_epochs,
153+ max_steps=self.args.max_steps,
154+ per_device_train_batch_size=self.args.batch_size,
155+ per_device_eval_batch_size=self.args.batch_size,
156+ gradient_accumulation_steps=self.args.gradient_accumulation_steps,
157+ learning_rate=self.args.learning_rate,
158+ weight_decay=self.args.weight_decay,
159+ warmup_ratio=self.args.warmup_ratio,
160+ lr_scheduler_type=self.args.lr_scheduler_type,
161+ logging_dir=os.path.join(self.args.output_dir, "logs"),
162+ logging_steps=self.args.logging_steps,
163+ save_steps=self.args.save_steps,
164+ save_total_limit=self.args.save_total_limit,
165+ eval_strategy=eval_strategy,
166+ eval_steps=self.args.eval_steps if has_eval else None,
167+ bf16=self.args.use_bf16,
168+ fp16=self.args.use_fp16,
169+ gradient_checkpointing=self.args.gradient_checkpointing,
170+ remove_unused_columns=False,
171+ report_to="none",
172+ dataloader_pin_memory=False,
173+ optim="adamw_torch",
174+ )
175+ 
176+ data_collator = DataCollatorForSeq2Seq(
177+ tokenizer=self.tokenizer,
178+ model=self.model,
179+ padding="max_length",
180+ max_length=self.args.max_length,
181+ label_pad_token_id=-100,
182+ )
183+ 
184+ mode = "compile" if self.args.enable_compile else "eager"
185+ prof=None
186+ 
187+ if self.args.enable_profiler:
188+ profiling_save_path = self.args.profiler_save_path + '/' + MODEL_NAME + '/' + mode
189+ prof = get_profile(self.args.profiler_start_step, self.args.profiler_end_step, profiling_save_path)
190+ 
191+ timing_callback = TimingCallback(prof, mode)
192+ 
193+ trainer = BaichuanTrainer(
194+ model=self.model,
195+ args=training_args,
196+ train_dataset=train_dataset,
197+ eval_dataset=eval_dataset,
198+ tokenizer=self.tokenizer,
199+ data_collator=data_collator,
200+ callbacks=[timing_callback],
201+ )
202+ return trainer
203+ 
204+ def train(self):
205+ logger.info("Starting training...")
206+ 
207+ if self.args.use_lora:
208+ self.apply_lora()
209+ 
210+ if self.args.enable_compile:
211+ logger.warning("Enable torch.compile (experimental on NPU).")
212+ try:
213+ self.model = torch.compile(self.model, dynamic=False)
214+ except Exception as e:
215+ logger.error(f"torch.compile failed: {e}. Continue without compile.")
216+ 
217+ dataset = self.load_and_preprocess_data()
218+ 
219+ trainer = self.create_trainer(dataset)
220+ train_result = trainer.train()
221+ 
222+ trainer.save_state()
223+ trainer.save_model(self.args.output_dir)
224+ self.tokenizer.save_pretrained(self.args.output_dir)
225+ 
226+ metrics = train_result.metrics
227+ trainer.log_metrics("train", metrics)
228+ trainer.save_metrics("train", metrics)
229+ 
230+ logger.info(f"Training completed! Model saved to {self.args.output_dir}")
231+ return metrics
232+ 
233+ 
234+def build_argparser():
235+ parser = argparse.ArgumentParser(description="Train Baichuan2-7B-Chat (LoRA) on NPU")
236+ 
237+ parser.add_argument("--npu-backend", type=str, default="mlir")
238+ parser.add_argument("--mfusion", action="store_true",
239+ help="Enable MFusion for graph fusion optimization")
240+ parser.add_argument("--model_path", type=str, required=True)
241+ parser.add_argument("--data_path", type=str, required=True)
242+ parser.add_argument("--output_dir", type=str, default="./baichuan2-finetuned")
243+ parser.add_argument("--num_epochs", type=int, default=3)
244+ parser.add_argument("--max_steps", type=int, default=-1)
245+ parser.add_argument("--batch_size", type=int, default=1)
246+ parser.add_argument("--gradient_accumulation_steps", type=int, default=1)
247+ parser.add_argument("--learning_rate", type=float, default=2e-5)
248+ parser.add_argument("--weight_decay", type=float, default=0.1)
249+ parser.add_argument("--warmup_ratio", type=float, default=0.03)
250+ parser.add_argument("--lr_scheduler_type", type=str, default="cosine")
251+ parser.add_argument("--max_length", type=int, default=1024)
252+ parser.add_argument("--gradient_checkpointing", action="store_true")
253+ parser.add_argument("--seed", type=int, default=42)
254+ parser.add_argument("--use_bf16", action="store_true")
255+ parser.add_argument("--use_fp16", action="store_true")
256+ parser.add_argument("--use_lora", action="store_true")
257+ parser.add_argument("--lora_r", type=int, default=8)
258+ parser.add_argument("--lora_alpha", type=int, default=32)
259+ parser.add_argument("--lora_dropout", type=float, default=0.1)
260+ parser.add_argument("--lora_target_modules", type=str, default="W_pack")
261+ parser.add_argument("--validation_split", type=float, default=0.0)
262+ parser.add_argument("--split", type=str, default="train")
263+ parser.add_argument("--num_proc", type=int, default=0)
264+ parser.add_argument("--overwrite_cache", action="store_true")
265+ parser.add_argument("--logging_steps", type=int, default=1)
266+ parser.add_argument("--save_steps", type=int, default=500)
267+ parser.add_argument("--save_total_limit", type=int, default=3)
268+ parser.add_argument("--eval_steps", type=int, default=500)
269+ parser.add_argument("--enable_compile", action="store_true")
270+ parser.add_argument("--enable_profiler", action="store_true")
271+ parser.add_argument("--profiler_save_path", type=str, default="./profile")
272+ parser.add_argument("--profiler_start_step", type=int, default=5,
273+ help="Output directory for trained model")
274+ parser.add_argument("--profiler_end_step", type=int, default=7,
275+ help="Output directory for trained model")
276+ 
277+ return parser
278+ 
279+ 
280+def main():
281+ args = build_argparser().parse_args()
282+ detect_device_type()
283+ os.environ['TORCHINDUCTOR_NPU_BACKEND']=args.npu_backend
284+ if args.npu_backend == "akg":
285+ os.environ['TORCHINDUCTOR_NPU_BACKEND'] = 'mlir'
286+ os.environ['TORCHINDUCTOR_USE_AKG'] = '1'
287+ if args.mfusion:
288+ os.environ['TORCHINDUCTOR_ENABLE_MFUSION']='1'
289+ os.makedirs(args.output_dir, exist_ok=True)
290+ os.makedirs(os.path.join(args.output_dir, "logs"), exist_ok=True)
291+ 
292+ set_seed(args.seed)
293+ torch.manual_seed(args.seed)
294+ 
295+ logger.info(f"npu train {MODEL_NAME}")
296+ trainer = Baichuan2Trainer(args)
297+ metrics = trainer.train()
298+ 
299+ if args.enable_compile:
300+ headers, values = torch._dynamo.utils.compile_times("csv")
301+ for header, value in zip(headers, values):
302+ if header == "PyCodeCache.load_by_key_path":
303+ numbers = [float(num.strip()) for num in value.split(',') if num.strip()]
304+ op_compile_time = sum(numbers)
305+ print(f"op_compile_time:{op_compile_time * 1e3} ms", )
306+ 
307+if __name__ == "__main__":
308+ main()
@@ -0,0 +1,106 @@
1+# glm4-9B-chat 微调训练
2+ 
3+本 README 说明如何使用 **glm4-9B-chat** 模型权重,结合 **LlamaFactory** 提供的 `c4_demo.jsonl` 示例数据,完成数据下载、预处理与训练启动(含 eager / torch.compile 两种模式)。
4+ 
5+---
6+ 
7+## 目录
8+ 
9+- [1. 模型权重](#1-模型权重)
10+- [2. 数据获取](#2-数据获取)
11+- [3. 数据预处理](#3-数据预处理)
12+- [4. 模型训练](#4-模型训练)
13+ - [4.1 eager mode(默认)](#41-eager-mode默认)
14+ - [4.2 启用 torchcompile(可选)](#42-启用-torchcompile可选)
15+ - [4.3 采集profile文件(可选)](#43-采集profile文件可选)
16+ 
17+---
18+ 
19+## 1. 模型权重
20+ 
21+- Hugging Face 模型:**glm4-9B-chat**
22+ https://huggingface.co/zai-org/glm-4-9b-chat
23+ 
24+可在链接页面中 `Files and versions` 一栏直接下载。
25+ 
26+### 环境提示
27+ 
28+本项目依赖已整理到 `requirements.txt`,可直接安装:
29+ 
30+```bash
31+pip install -r ../utils/requirements.txt
32+```
33+ 
34+## 2. 数据获取
35+ 
36+训练数据集来自 LlamaFactory 仓库示例数据,可直接在如下连接获取:
37+ 
38+- `c4_demo.json`
39+ https://github.com/hiyouga/LlamaFactory/blob/main/data/c4_demo.jsonl
40+ 
41+或直接下载 raw 文件到本地::
42+ 
43+```bash
44+wget -O c4_demo.json \
45+https://github.com/hiyouga/LlamaFactory/blob/main/data/c4_demo.jsonl
46+```
47+ 
48+## 3. 数据预处理
49+ 
50+`train_glm4_9B.py`中已经对数据进行了处理
51+ 
52+## 4. 模型训练
53+ 
54+训练脚本:`train_glm4_9B.sh`,脚本支持在 GPU 和 NPU 训练
55+ 
56+开始训练前,请修改脚本中的路径参数:
57+ 
58+- 模型权重路径(本地目录)
59+- 训练数据路径(本地`c4_demo.json`
60+ 
61+### 4.1 eager mode(默认)
62+ 
63+```bash
64+bash train_glm4_9B.sh
65+```
66+ 
67+### 4.2 启用 `torch.compile`(可选)
68+ 
69+可通过添加 `--enable_compile` 选项运行图模式
70+当在 GPU 上训练时,默认后端使用triton;当在 NPU 上训练时,可进一步指定后端为 mlir 或 dvm,默认使用 mlir。
71+ 
72+**默认后端(mlir,可不写 --npu-backend):**
73+ 
74+```bash
75+bash train_glm4_9B.sh \
76+ --enable_compile
77+```
78+ 
79+**显式指定后端为 mlir:**
80+ 
81+```bash
82+bash train_glm4_9B.sh \
83+ --enable_compile \
84+ --npu-backend mlir
85+```
86+ 
87+**切换后端为 dvm::**
88+ 
89+```bash
90+bash train_glm4_9B.sh \
91+ --enable_compile \
92+ --npu-backend dvm
93+```
94+ 
95+### 4.3 采集profile文件(可选)
96+ 
97+脚本已支持 `--enable_profiler` 这样的开关,开启方式为:
98+ 
99+```bash
100+bash train_glm4_9B.sh \
101+ --enable_profiler \
102+ --profiler_start_step 5 \
103+ --profiler_end_step 6 \
104+```
105+ 
106+可以通过 `--profiler_start_step``--profiler_end_step` 分别设置profile开始和结束步数。结束步数。
@@ -0,0 +1,20 @@
1+export TORCHINDUCTOR_CACHE_DIR='./cache'
2+export ASCEND_RT_VISIBLE_DEVICES=0
3+export TORCH_COMPILE_DEBUG=1
4+export TORCH_NPU_USE_COMPATIBLE_IMPL=1
5+ 
6+rm -rf ./cache/*
7+mkdir -p ./cache logs
8+ 
9+python train_glm4_9B.py \
10+ --model_path "your model path" \
11+ --data_path "dataset path" \
12+ --output_dir "./glm4-finetuned" \
13+ --num_epochs 3 \
14+ --max_steps 200 \
15+ --batch_size 1 \
16+ --learning_rate 2e-5 \
17+ --max_length 512 \
18+ --use_lora \
19+ --use_bf16 \
20+ --pad_to_max_length > logs/train_glm4.log 2>&1
@@ -0,0 +1,424 @@
1+import torch
2+import os
3+import sys
4+from pathlib import Path
5+import torch.nn as nn
6+from datasets import Dataset, load_dataset
7+from transformers import (
8+ AutoModelForCausalLM,
9+ AutoTokenizer,
10+ TrainingArguments,
11+ Trainer,
12+ DataCollatorForLanguageModeling,
13+ BitsAndBytesConfig,
14+)
15+from peft import (
16+ LoraConfig,
17+ get_peft_model,
18+ prepare_model_for_kbit_training,
19+ TaskType
20+)
21+import json
22+from typing import Dict, Union, Any, Optional
23+import argparse
24+import logging
25+ 
26+ 
27+sys.path.append(str(Path(__file__).parent.parent))
28+from utils.utils import (
29+ TimingCallback,
30+ get_profile,
31+ detect_device_type
32+)
33+ 
34+logging.basicConfig(level=logging.INFO)
35+logger = logging.getLogger(__name__)
36+ 
37+model_name = 'GLM4-9B'
38+ 
39+class CustomTrainer(Trainer):
40+ def compute_loss(
41+ self,
42+ model: nn.Module,
43+ inputs: Dict[str, Union[torch.Tensor, Any]],
44+ return_outputs: bool = False,
45+ num_items_in_batch: Optional[torch.Tensor] = None,
46+ ):
47+ return super().compute_loss(
48+ model=model,
49+ inputs=inputs,
50+ return_outputs=return_outputs,
51+ num_items_in_batch=None
52+ )
53+ 
54+class GLM4Trainer:
55+ def __init__(self, args):
56+ self.args = args
57+ self.setup_training()
58+ 
59+ def setup_training(self):
60+ logger.info(f"Loading GLM-4 model from {self.args.model_path}")
61+ 
62+ bnb_config = None
63+ if self.args.use_4bit:
64+ bnb_config = BitsAndBytesConfig(
65+ load_in_4bit=True,
66+ bnb_4bit_quant_type="nf4",
67+ bnb_4bit_compute_dtype=torch.float16,
68+ bnb_4bit_use_double_quant=True
69+ )
70+ 
71+ self.model = AutoModelForCausalLM.from_pretrained(
72+ self.args.model_path,
73+ quantization_config=bnb_config if self.args.use_4bit else None,
74+ torch_dtype=torch.bfloat16 if self.args.use_bf16 else torch.float16,
75+ trust_remote_code=True,
76+ )
77+ 
78+ logger.info(f"Moving model to {self.args.device_type}...")
79+ self.model = self.model.to(self.args.device_type)
80+ 
81+ self.tokenizer = AutoTokenizer.from_pretrained(
82+ self.args.model_path,
83+ trust_remote_code=True
84+ )
85+ 
86+ if self.tokenizer.pad_token is None:
87+ self.tokenizer.pad_token = self.tokenizer.eos_token
88+ self.model.config.pad_token_id = self.tokenizer.eos_token_id
89+ 
90+ if self.args.gradient_checkpointing:
91+ self.model.gradient_checkpointing_enable()
92+ self.model.config.use_cache = False
93+ 
94+ 
95+ def apply_lora(self):
96+ if not self.args.use_lora:
97+ return
98+ 
99+ logger.info("Applying LoRA configuration...")
100+ 
101+ if self.args.use_4bit:
102+ self.model = prepare_model_for_kbit_training(self.model)
103+ 
104+ lora_config = LoraConfig(
105+ task_type=TaskType.CAUSAL_LM,
106+ r=self.args.lora_r,
107+ lora_alpha=self.args.lora_alpha,
108+ lora_dropout=self.args.lora_dropout,
109+ target_modules=self.get_lora_target_modules(),
110+ bias="none",
111+ )
112+ 
113+ self.model = get_peft_model(self.model, lora_config)
114+ self.model.print_trainable_parameters()
115+ 
116+ if self.args.enable_compile:
117+ self.model = torch.compile(self.model, dynamic=False)
118+ 
119+ 
120+ def get_lora_target_modules(self):
121+ target_modules = [
122+ "query_key_value",
123+ "dense",
124+ "dense_h_to_4h",
125+ "dense_4h_to_h",
126+ ]
127+ 
128+ model_modules = sorted([name for name, _ in self.model.named_modules()])
129+ available_modules = []
130+ 
131+ for target in target_modules:
132+ found = False
133+ for name in model_modules:
134+ if target in name:
135+ available_modules.append(name)
136+ found = True
137+ break
138+ if not found:
139+ logger.warning(f"Target module {target} not found in model")
140+ 
141+ available_modules = list(dict.fromkeys(available_modules))
142+ 
143+ if not available_modules:
144+ available_modules = ["qkv", "proj", "fc1", "fc2"]
145+ logger.warning(f"No avaliable, GLM-4 using LoRA target modules: {available_modules}")
146+ else:
147+ logger.info(f"GLM-4 LoRA target modules: {available_modules}")
148+ 
149+ return available_modules
150+ 
151+ 
152+ def load_and_preprocess_data(self) -> Dataset:
153+ logger.info(f"Loading dataset from {self.args.data_path}")
154+ 
155+ if self.args.data_path.endswith('.json') or self.args.data_path.endswith('.jsonl'):
156+ with open(self.args.data_path, 'r', encoding='utf-8') as f:
157+ if self.args.data_path.endswith('.jsonl'):
158+ data = [json.loads(line) for line in f]
159+ else:
160+ data = json.load(f)
161+ 
162+ formatted_data = []
163+ for item in data:
164+ if "conversations" in item or "messages" in item:
165+ messages = item.get("messages", item.get("conversations", []))
166+ text = self.tokenizer.apply_chat_template(
167+ messages,
168+ tokenize=False,
169+ add_generation_prompt=False
170+ )
171+ else:
172+ text = item.get("text", "")
173+ 
174+ formatted_data.append({"text": text})
175+ 
176+ dataset = Dataset.from_list(formatted_data)
177+ else:
178+ try:
179+ dataset = load_dataset(
180+ self.args.data_path,
181+ split=self.args.split
182+ )
183+ except:
184+ dataset = load_dataset(
185+ "json",
186+ data_files=self.args.data_path,
187+ split="train"
188+ )
189+ 
190+ tokenizer_ref = self.tokenizer
191+ max_length_ref = self.args.max_length
192+ 
193+ def preprocess_function(examples):
194+ tokenized = tokenizer_ref(
195+ examples["text"],
196+ truncation=True,
197+ padding="max_length" if self.args.pad_to_max_length else False,
198+ max_length=max_length_ref,
199+ return_tensors=None,
200+ return_attention_mask=True,
201+ )
202+ import copy
203+ tokenized["labels"] = copy.deepcopy(tokenized["input_ids"])
204+ return tokenized
205+ 
206+ 
207+ num_proc = self.args.num_proc if self.args.num_proc > 0 else None
208+ processed_dataset = dataset.map(
209+ preprocess_function,
210+ batched=True,
211+ remove_columns=dataset.column_names,
212+ num_proc=num_proc,
213+ load_from_cache_file=not self.args.overwrite_cache
214+ )
215+ logger.info(f"Dataset size: {len(processed_dataset)}")
216+ return processed_dataset
217+ 
218+ 
219+ def create_trainer(self, train_dataset, eval_dataset=None):
220+ has_eval = eval_dataset is not None
221+ eval_strategy = "steps" if has_eval else "no"
222+ save_strategy = "steps"
223+ 
224+ training_args = TrainingArguments(
225+ output_dir=self.args.output_dir,
226+ overwrite_output_dir=True,
227+ num_train_epochs=self.args.num_epochs,
228+ max_steps=self.args.max_steps if self.args.max_steps > 0 else 30,
229+ per_device_train_batch_size=self.args.batch_size,
230+ per_device_eval_batch_size=self.args.batch_size,
231+ gradient_accumulation_steps=self.args.gradient_accumulation_steps,
232+ weight_decay=self.args.weight_decay,
233+ logging_dir=f"{self.args.output_dir}/logs",
234+ logging_steps=self.args.logging_steps,
235+ save_steps=self.args.save_steps,
236+ save_total_limit=self.args.save_total_limit,
237+ eval_strategy=eval_strategy,
238+ eval_steps=self.args.eval_steps if has_eval else None,
239+ save_strategy=save_strategy,
240+ load_best_model_at_end=has_eval,
241+ metric_for_best_model="eval_loss",
242+ greater_is_better=False,
243+ learning_rate=self.args.learning_rate,
244+ lr_scheduler_type=self.args.lr_scheduler_type,
245+ fp16=self.args.use_fp16,
246+ bf16=self.args.use_bf16,
247+ gradient_checkpointing=self.args.gradient_checkpointing,
248+ report_to="none",
249+ ddp_find_unused_parameters=False if torch.cuda.device_count() > 1 else None,
250+ remove_unused_columns=False,
251+ dataloader_num_workers=self.args.dataloader_num_workers,
252+ group_by_length=self.args.group_by_length,
253+ length_column_name="length",
254+ prediction_loss_only=True,
255+ )
256+ 
257+ data_collator = DataCollatorForLanguageModeling(
258+ tokenizer=self.tokenizer,
259+ mlm=False,
260+ )
261+ 
262+ mod='compile' if self.args.enable_compile else 'eager'
263+ prof=None
264+ 
265+ if self.args.enable_profiler:
266+ profiling_save_path = self.args.profiler_save_path + '/' + model_name + '/' + mod
267+ prof = get_profile(self.args.profiler_start_step, self.args.profiler_end_step, profiling_save_path)
268+ 
269+ timing_callback = TimingCallback(prof, mod)
270+ 
271+ trainer = CustomTrainer(
272+ model=self.model,
273+ args=training_args,
274+ train_dataset=train_dataset,
275+ eval_dataset=eval_dataset,
276+ tokenizer=self.tokenizer,
277+ data_collator=data_collator,
278+ callbacks=[timing_callback],
279+ )
280+ return trainer
281+ 
282+ 
283+ def train(self):
284+ logger.info("Starting GLM-4 training...")
285+ 
286+ if self.args.use_lora:
287+ self.apply_lora()
288+ 
289+ dataset = self.load_and_preprocess_data()
290+ 
291+ if self.args.validation_split > 0:
292+ split_dataset = dataset.train_test_split(
293+ test_size=self.args.validation_split,
294+ seed=self.args.seed
295+ )
296+ train_dataset = split_dataset["train"]
297+ eval_dataset = split_dataset["test"]
298+ else:
299+ train_dataset = dataset
300+ eval_dataset = None
301+ 
302+ trainer = self.create_trainer(train_dataset, eval_dataset)
303+ train_result = trainer.train()
304+ if self.args.enable_compile:
305+ headers, values = torch._dynamo.utils.compile_times("csv")
306+ for header, value in zip(headers, values):
307+ if header == "PyCodeCache.load_by_key_path":
308+ numbers = [float(num.strip()) for num in value.split(',') if num.strip()]
309+ op_compile_time = sum(numbers)
310+ print(f"op_compile_time:{op_compile_time * 1e3} ms", )
311+ 
312+ trainer.save_model()
313+ self.tokenizer.save_pretrained(self.args.output_dir)
314+ metrics = train_result.metrics
315+ trainer.log_metrics("train", metrics)
316+ trainer.save_metrics("train", metrics)
317+ trainer.save_state()
318+ logger.info(f"Training completed! Model saved to {self.args.output_dir}")
319+ return metrics
320+ 
321+ 
322+def main():
323+ parser = argparse.ArgumentParser(description="Train GLM-4 model")
324+ parser.add_argument("--model_path", type=str, default="ZhipuAI/glm-4-9b-chat",
325+ help="Path to the pretrained GLM-4 model")
326+ parser.add_argument("--data_path", type=str, required=True,
327+ help="Path to training data (json/jsonl file or dataset name)")
328+ parser.add_argument("--output_dir", type=str, default="./glm4-finetuned",
329+ help="Output directory for trained model")
330+ parser.add_argument("--num_epochs", type=int, default=3,
331+ help="Number of training epochs")
332+ parser.add_argument("--batch_size", type=int, default=2,
333+ help="Batch size per device")
334+ parser.add_argument("--gradient_accumulation_steps", type=int, default=1,
335+ help="Gradient accumulation steps")
336+ parser.add_argument("--learning_rate", type=float, default=1e-4,
337+ help="Learning rate")
338+ parser.add_argument("--warmup_steps", type=int, default=50,
339+ help="Warmup steps")
340+ parser.add_argument("--max_steps", type=int, default=-1,
341+ help="Total training steps ")
342+ parser.add_argument("--weight_decay", type=float, default=0.01,
343+ help="Weight decay")
344+ parser.add_argument("--max_length", type=int, default=2048,
345+ help="Maximum sequence length")
346+ parser.add_argument("--lr_scheduler_type", type=str, default="cosine",
347+ choices=["linear", "cosine", "cosine_with_restarts", "constant"],
348+ help="Learning rate scheduler type")
349+ parser.add_argument("--use_lora", action="store_true",
350+ help="Use LoRA for parameter-efficient fine-tuning")
351+ parser.add_argument("--lora_r", type=int, default=8,
352+ help="LoRA rank")
353+ parser.add_argument("--lora_alpha", type=int, default=32,
354+ help="LoRA alpha")
355+ parser.add_argument("--lora_dropout", type=float, default=0.1,
356+ help="LoRA dropout")
357+ parser.add_argument("--use_4bit", action="store_true",
358+ help="Use 4-bit quantization")
359+ parser.add_argument("--use_fp16", action="store_true",
360+ help="Use FP16 precision")
361+ parser.add_argument("--use_bf16", action="store_true",
362+ help="Use BF16 precision")
363+ parser.add_argument("--gradient_checkpointing", action="store_true",
364+ help="Enable gradient checkpointing")
365+ parser.add_argument("--validation_split", type=float, default=0.1,
366+ help="Validation split ratio")
367+ parser.add_argument("--split", type=str, default="train",
368+ help="Dataset split to use")
369+ parser.add_argument("--num_proc", type=int, default=0,
370+ help="Number of processes for data preprocessing (0 = single process)")
371+ parser.add_argument("--pad_to_max_length", action="store_true",
372+ help="Pad sequences to max_length")
373+ parser.add_argument("--overwrite_cache", action="store_true",
374+ help="Overwrite cached features")
375+ parser.add_argument("--group_by_length", action="store_true",
376+ help="Group sequences by length for efficient training")
377+ parser.add_argument("--dataloader_num_workers", type=int, default=4,
378+ help="Number of workers for data loading")
379+ parser.add_argument("--seed", type=int, default=42,
380+ help="Random seed")
381+ parser.add_argument("--logging_steps", type=int, default=1,
382+ help="Log every X updates steps")
383+ parser.add_argument("--save_steps", type=int, default=500,
384+ help="Save checkpoint every X updates steps")
385+ parser.add_argument("--eval_steps", type=int, default=500,
386+ help="Evaluate every X updates steps")
387+ parser.add_argument("--save_total_limit", type=int, default=3,
388+ help="Limit the total amount of checkpoints")
389+ parser.add_argument("--report_to_tensorboard", action="store_true",
390+ help="Report metrics to TensorBoard")
391+ parser.add_argument("--enable_compile", action="store_true",
392+ help="Enable torch.compile and Inductor backend")
393+ parser.add_argument("--enable_profiler", action="store_true",
394+ help="Enable profiler for performance analysis")
395+ parser.add_argument("--profiler_start_step", type=int, default=5,
396+ help="Output directory for trained model")
397+ parser.add_argument("--profiler_end_step", type=int, default=8,
398+ help="Output directory for trained model")
399+ parser.add_argument("--profiler_save_path", type=str, default="./profile",
400+ help="Output directory for trained model")
401+ parser.add_argument("--npu-backend", type=str, default="mlir")
402+ parser.add_argument("--mfusion", action="store_true", help="Enable MFusion for graph fusion optimization")
403+ args = parser.parse_args()
404+ torch.manual_seed(args.seed)
405+ args.device_type = detect_device_type()
406+ os.environ['TORCHINDUCTOR_NPU_BACKEND']=args.npu_backend
407+ if args.npu_backend == "akg":
408+ os.environ['TORCHINDUCTOR_NPU_BACKEND'] = 'mlir'
409+ os.environ['TORCHINDUCTOR_USE_AKG'] = '1'
410+ if args.mfusion:
411+ os.environ['TORCHINDUCTOR_ENABLE_MFUSION']='1'
412+ print(f"{args.device_type} train {model_name}")
413+ 
414+ trainer = GLM4Trainer(args)
415+ metrics = trainer.train()
416+ 
417+ print("\n" + "="*50)
418+ print("GLM-4 Training completed successfully!")
419+ print(f"Model saved to: {args.output_dir}")
420+ print(f"Final training loss: {metrics.get('train_loss', 'N/A')}")
421+ print("="*50)
422+ 
423+if __name__ == "__main__":
424+ main()
@@ -0,0 +1,119 @@
1+# gpt-oss-20B 微调训练
2+ 
3+本 README 说明如何使用 **gpt-oss-20B** 模型权重,结合 **LlamaFactory** 提供的 `c4_demo.jsonl` 示例数据,完成数据下载、预处理与训练启动(含 eager / torch.compile 两种模式)。
4+ 
5+---
6+ 
7+## 目录
8+ 
9+- [1. 模型权重](#1-模型权重)
10+- [2. 数据获取](#2-数据获取)
11+- [3. 数据预处理](#3-数据预处理)
12+- [4. 模型训练](#4-模型训练)
13+ - [4.1 eager mode(默认)](#41-eager-mode默认)
14+ - [4.2 启用 torchcompile(可选)](#42-启用-torchcompile可选)
15+ - [4.3 采集profile文件(可选)](#43-采集profile文件可选)
16+ 
17+---
18+ 
19+### 环境配置
20+ 
21+本项目依赖已整理到 `requirements.txt`,可直接安装:
22+ 
23+```bash
24+pip install -r ../utils/requirements.txt
25+```
26+ 
27+## 1. 模型权重
28+ 
29+- Hugging Face 模型地址:**gpt-oss-20B**
30+ https://huggingface.co/openai/gpt-oss-20b
31+ 
32+- 可使用如下的自定义脚本下载
33+ 
34+```bash
35+python ../utils/download_hf.py --model openai/gpt-oss-20b --save_path ./gpt-oss-20b
36+```
37+ 
38+## 2. 数据获取
39+ 
40+训练数据集来自 LlamaFactory 仓库示例数据,可直接在如下连接获取:
41+ 
42+- `c4_demo.json`
43+ https://github.com/hiyouga/LlamaFactory/blob/main/data/c4_demo.jsonl
44+ 
45+或直接下载 raw 文件到本地:
46+ 
47+```bash
48+wget -O c4_demo.json \
49+https://github.com/hiyouga/LlamaFactory/blob/main/data/c4_demo.jsonl
50+```
51+ 
52+## 3. 数据预处理
53+ 
54+`train_gpt-oss_4B.py`中已经对数据进行了处理
55+ 
56+## 4. 模型训练
57+ 
58+训练脚本:`run_gpt-oss.sh`,脚本支持在 GPU 和 NPU 训练
59+ 
60+开始训练前,请修改脚本中的路径参数:
61+ 
62+- 模型权重路径
63+- 训练数据路径
64+ 
65+### 4.1 eager mode(默认)
66+ 
67+```bash
68+bash run_gpt-oss.sh
69+```
70+ 
71+### 4.2 启用 `torch.compile`(可选)
72+ 
73+可通过添加 `--enable_compile` 选项运行图模式
74+当在 GPU 上训练时,默认后端使用triton;当在 NPU 上训练时,可进一步指定后端为 mlir 或 dvm,默认使用 mlir。
75+ 
76+**默认后端(mlir,可不写 --npu-backend):**
77+ 
78+```bash
79+bash run_gpt-oss.sh \
80+ --enable_compile
81+```
82+ 
83+**显式指定后端为 mlir:**
84+ 
85+```bash
86+bash run_gpt-oss.sh \
87+ --enable_compile \
88+ --npu-backend mlir
89+```
90+ 
91+**切换后端为 dvm:**
92+ 
93+```bash
94+bash run_gpt-oss.sh \
95+ --enable_compile \
96+ --npu-backend dvm
97+```
98+ 
99+当在 NPU 上训练时,可通过 `--mfusion` 参数开启 MFusion 图算融合优化功能, 配合不同的NPU图模式后端, 进一步提升模型的性能,使用示例如下
100+ 
101+```bash
102+bash run_gpt-oss.sh \
103+ --enable_compile \
104+ --npu-backend dvm \
105+ --mfusion
106+```
107+ 
108+### 4.3 采集profile文件(可选)
109+ 
110+脚本已支持 `--enable_profiler` 这样的开关,开启方式为:
111+ 
112+```bash
113+bash run_gpt-oss.sh \
114+ --enable_profiler \
115+ --profiler_start_step 5 \
116+ --profiler_end_step 6 \
117+```
118+ 
119+可以通过 `--profiler_start_step``--profiler_end_step` 分别设置profile开始和结束步数。
@@ -0,0 +1,23 @@
1+#!/bin/bash
2+export TORCHINDUCTOR_CACHE_DIR="./cache"
3+export ASCEND_RT_VISIBLE_DEVICES=0
4+export CUDA_VISIBLE_DEVICES=0
5+export TORCH_COMPILE_DEBUG=1
6+export TORCH_NPU_USE_COMPATIBLE_IMPL=1
7+ 
8+rm -rf ./cache/*
9+mkdir -p ./cache logs
10+ 
11+python train_gpt_oss_20B.py \
12+ --model_path $MODEL_PATH \
13+ --data_path $DATA_PATH \
14+ --output_dir "./gpt-oss-finetuned" \
15+ --num_epochs 3 \
16+ --max_steps 200\
17+ --batch_size 1 \
18+ --learning_rate 2e-5 \
19+ --max_length 512 \
20+ --use_lora \
21+ --use_bf16 \
22+ --gradient_checkpointing \
23+ --pad_to_max_length > logs/train_gpt-oss.log 2>&1