import os
import time
import json
import shutil
from abc import ABC, abstractmethod
import torch
from atk.configs.dataset_config import InputDataset
from atk.configs.results_config import TaskResult
from atk.tasks.dataset.base_dataset import OpsDataset
from atk.tasks import record_times
from atk.common.log import Logger
from atk.common.file_check import safe_file_open
from atk.case_generator.generator.data_types import DataTypeFactory
from atk.common.utils import get_input_path_by_case, get_file_md5
logging = Logger().get_logger()
class BaseDatasetExecutor(ABC):
def __init__(self, task_result: TaskResult):
super().__init__()
self.task_result = task_result
@abstractmethod
def create_dataset(self):
pass
@abstractmethod
def save_dataset(self):
pass
@abstractmethod
def clean_dataset(self):
pass
@abstractmethod
def load_dataset(self):
pass
class DatasetExecutor:
run_times = {}
def __init__(self, task_result: TaskResult):
super().__init__()
self.task_result = task_result
self.input_dataset = InputDataset()
self.api_type = self.task_result.case_config.api_type
self.case_config = self.task_result.case_config
if self.task_result.nodes:
self.group_nodes = self.task_result.nodes.group_node_by_same_device()
@staticmethod
def get_input_file_path(task_result, final_input: bool = False, custom_base: str = None):
if custom_base is not None:
input_dir = os.path.join(custom_base, str(task_result.case_config.id))
else:
input_dir = task_result.get_input_final_dir() if final_input else task_result.get_input_dir()
suffix = "final.bin" if final_input else ".bin"
file_path = os.path.join(input_dir, f"input{suffix}")
other_path = None
if "tensor" in task_result.case_config.api_type:
other_path = os.path.join(input_dir, f"tensor_input{suffix}")
elif "method" in task_result.case_config.api_type:
other_path = os.path.join(input_dir, f"method_input{suffix}")
return file_path, other_path
@staticmethod
def _load_file_and_backward(path, inputs):
"""
加载落盘的输入数据和输入的backward信息
"""
if not os.path.exists(path):
raise FileNotFoundError(f"input file {path} not found")
data = torch.load(path, map_location="cpu", weights_only=True)
logging.debug(f"load file success: {path}")
args, backward_args = [], []
kwargs, backward_kwargs = dict(), dict()
for i, input_info in enumerate(inputs):
if isinstance(input_info, list):
name = input_info[0].name
backward = input_info[0].backward
is_tuple = isinstance(data[i], tuple)
if is_tuple:
data[i] = list(data[i])
for index, input_config in enumerate(input_info):
data[i][index] = (DataTypeFactory.create_customer_datatype(input_config.dtype)
(input_config).get_data(data[i][index]))
if is_tuple:
data[i] = tuple(data[i])
else:
name = input_info.name
backward = input_info.backward
data[i] = (DataTypeFactory.create_customer_datatype(input_info.dtype)
(input_info).get_data(data[i]))
if not name:
args.append(data[i])
backward_args.append(backward)
else:
kwargs[name] = data[i]
backward_kwargs[name] = backward
return args, kwargs, backward_args, backward_kwargs
@record_times(run_times)
def create_dataset(self):
if self.task_result.use_input_data_dir:
return
ops = OpsDataset(
[self.task_result.case_config.model_dump()],
self.task_result.case_config.id,
)
self.input_dataset = ops.__next__()
@record_times(run_times)
def save_dataset(self):
file_path, other_path = self.get_input_file_path(self.task_result)
if self.task_result.use_input_data_dir:
return file_path, other_path
self.input_dataset.save_input_data(file_path, other_path, self.task_result)
return file_path, other_path
@record_times(run_times)
def load_dataset(self) -> InputDataset:
file_path, other_path = self.get_input_file_path(
self.task_result,
custom_base=self.task_result.use_input_data_dir
)
if not os.path.exists(file_path) and other_path is None:
raise FileNotFoundError(f"input file_path: {file_path} is not exists and other_path is none")
data = InputDataset()
if os.path.exists(file_path):
data.args, data.kwargs, data.backward_args, data.backward_kwargs = DatasetExecutor._load_file_and_backward(
file_path, self.case_config.inputs
)
logging.debug(f"load data from {file_path} success.")
else:
logging.warning(f"input file_path: {file_path} is not exists, please check")
if other_path is not None:
if os.path.exists(other_path):
if "method" in self.api_type and self.case_config.method_inputs:
data.method_args, data.method_kwargs, _, _ = DatasetExecutor._load_file_and_backward(
other_path, self.case_config.method_inputs
)
logging.debug(f"load other data from {other_path} success.")
elif "tensor" in self.api_type:
data.tensor_args = torch.load(other_path, map_location="cpu", weights_only=True)
data.backward_tensor_args = getattr(self.case_config.tensor_input, 'backward', None)
logging.debug(f"load other data from {other_path} success.")
else:
logging.warning("not set method or tensor in api_type, please check")
else:
raise FileNotFoundError(f"input other_path: {other_path} is not exists")
return data
def clean_dataset(self):
def _remove_by_path(file_path):
try:
shutil.rmtree(file_path)
logging.debug(f"delete file {file_path} success.")
except Exception as exc:
logging.warning(f"Delete file {file_path} exception: {str(exc)}")
file_path, _ = self.get_input_file_path(self.task_result)
input_path = os.path.dirname(file_path)
if self.task_result.save_data and "input" in self.task_result.save_data:
logging.info(f"save input data in {input_path}")
return
_remove_by_path(input_path)
logging.debug(f"clean input file success {input_path}")
@record_times(run_times)
def update_md5_and_wait_for_opp_task(self, timeout=1800):
"""
remote侧计算并记录单个case的所有输入的md5值, 主节点会通过http请求获取这些值
remote通过获取本地是否有'data_upload_completed.txt'来判断主节点的数据是否传输完成
"""
md5_value = {}
backend = self.task_result.backend
name = self.task_result.name
input_path = self.task_result.nodes.get_node(backend=backend, name=name).get_input_path()
dire = get_input_path_by_case(
input_path,
self.case_config,
is_create=True
)
for root, _, files in os.walk(dire):
for file in files:
if "input.bin" not in file:
continue
local_file = os.path.join(root, file)
local_md5 = get_file_md5(local_file)
md5_value[file] = local_md5
input_dir = self.task_result.get_input_dir()
input_path = os.path.join(input_dir, f"md5.json")
with safe_file_open(input_path, "w") as f:
json.dump(md5_value, f)
logging.debug("end update inputs md5 in remote node and waiting for 'data_upload_completed.txt'")
start_time = time.time()
while time.time() - start_time < timeout:
file_path = os.path.join(input_dir, 'data_upload_completed.txt')
if os.path.exists(file_path):
logging.debug("end waiting for 'data_upload_completed.txt'")
return True
time.sleep(0.5)
else:
raise TimeoutError("Timeout waiting for the main node upload data to remote side")
def check_in_main_node(self):
return not self.group_nodes or self.task_result.get_node() in self.group_nodes[0]