已合并
add inference script in benchmarks #34570
HandsoemLemon创建于 4月28日
add inference script in benchmarks #34570
已合并
HandsoemLemon创建于 4月28日
共 7 个文件变更+1587-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,92 @@
1+# 推理说明
2+
3+本 README 说明如何使用 **DCNv2**,**DIN**,**MMOE**和**ETA**模型进行推理(含 eager / torch.compile 两种模式)。
4+在运行完成后输出
5+---
6+ 
7+## 目录
8+ 
9+- [1. 数据集处理](#1-数据集处理)
10+ - [1.1 DIN & DCNv2](#11-DIN-&-DCNv2)
11+ - [1.2 MMOE & ETA](#12-MMOE-&-ETA)
12+- [2. 模型推理](#2-模型推理)
13+ - [2.1 eager mode(默认)](#21-eager-mode默认)
14+ - [2.2 启用 torch.compile(可选)](#22-启用-torch.compile可选)
15+ - [2.3 采集 profile文件(可选)](#23-采集profile文件可选)
16+---
17+ 
18+## 1. 数据集处理
19+ 
20+### 1.1 DIN & DCNv2
21+ 
22+**DIN** 和 **DCNv2** 的推理数据集来自 CTR_Algorithm 仓库示例数据,该数据集无需手动下载
23+ 
24+https://github.com/Prayforhanluo/CTR_Algorithm/tree/main/data
25+ 
26+在DIN或DCNv2目录下执行如下命令:
27+```bash
28+git clone https://github.com/Prayforhanluo/CTR_Algorithm.git
29+```
30+- 数据集目录:`CTR_Algorithm/data/data.csv`
31+ 
32+ 
33+### 1.2 MMOE & ETA
34+ 
35+进入**MMOE**或**ETA**文件夹下,进行如下数据预处理
36+ 
37+推理数据集来自 https://tianchi.aliyun.com/dataset/408 Ali-CPP数据集,需做如下处理:
38+1. 下载数据集,存放至当前目录下alicpp文件夹中
39+2. 后续流程参考:https://gitee.com/ascend/RecSDK/blob/develop_torch_benchmark/torch_examples_benchmark/model_zoo/README.md
40+ 
41+执行完成后数据集会默认生成至aliccp_out目录下。
42+ 
43+网络依赖已整理到 `requirements.txt`,可直接安装:
44+ 
45+```bash
46+pip install -r ./requirements.txt
47+```
48+ 
49+在模型执行时,默认加载数据集路径为: `./aliccp/aliccp_out/`;模型脚本同时支持运行时使用如下命令动态指定数据集目录:
50+ 
51+```bash
52+python eta.py --data_dir path/to/your/data/
53+```
54+ 
55+ 
56+## 2. 模型推理(DCNv2网络为例)
57+ 
58+进入**DCNv2**文件夹,直接执行推理脚本`dcnv2.py`即可
59+ 
60+- 注:运行该网络前需要先将patch文件加上,具体方式如下:
61+ ```bash
62+ cd DCNv2
63+ unix2dos dcnv2.patch #如果在arm机器上需要执行
64+ git apply dcnv2.patch
65+ ```
66+ 
67+### 2.1 eager mode(默认)
68+ 
69+```bash
70+python dcnv2.py
71+```
72+ 
73+### 2.2 启用 `torch.compile`(可选)
74+ 
75+脚本已支持 `--enable_compile` 这样的开关,开启方式为:
76+ 
77+```bash
78+python dcnv2.py \
79+--enable_compile
80+```
81+ 
82+### 2.3 采集profile文件(可选)
83+ 
84+脚本已支持 `--enable_profiler` 这样的开关,开启方式为:
85+ 
86+```bash
87+python dcnv2.py \
88+ --enable_profiler \
89+ --profiler_start_step 5 \
90+ --profiler_end_step 6 \
91+```
92+可以通过 `--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