EdgeCape / runai.py
orhir's picture
Upload 114 files
184241a verified
raw
history blame
17.3 kB
# Description: Script to run multiple experiments on runai
import re
import subprocess
import os
import argparse
import time
from prettytable import PrettyTable
class Bcolors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKCYAN = '\033[96m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
def pretty_table(dct):
table = PrettyTable(['Job', 'Status'])
for c in sorted(dct.keys()):
table.add_row([c, dct[c]])
print(table)
def init_parser():
parser = argparse.ArgumentParser(prog="RUNAI SCRIPT")
parser.add_argument('action', type=str, default=None, help='Train or Test', choices=['train', 'test', 'run'])
parser.add_argument('--config_folder', type=str, default=None, help='Run all configs in folder')
parser.add_argument('--config', type=str, default=None, help='Run all configs in folder')
parser.add_argument('--name', type=str, default=None, help='prefix')
parser.add_argument('--delete', action='store_true', help='Delete job')
parser.add_argument('--delete_fail', action='store_true', help='Delete job')
parser.add_argument('--delete_pending', action='store_true', help='Delete job')
parser.add_argument('--log', action='store_true', help='Show logs')
parser.add_argument('--delete_folder', action='store_true', help='Delete workdir folder')
parser.add_argument('--permute_keypoints', action='store_true', help='Delete workdir folder')
parser.add_argument('--dist', action='store_true', help='Distributed Training')
parser.add_argument('--find_best', action='store_true', help='Find best according to val')
parser.add_argument('--results', action='store_true', help='Show Results')
parser.add_argument('--no_base', action='store_true', help='Skip base models')
parser.add_argument('--show_cmd', action='store_true', help='Show command')
parser.add_argument('--large', action='store_true', help='Use large node')
parser.add_argument('--eval_three', action='store_true', help='Evaluate on 3 ckpts')
parser.add_argument('--pck', type=float, default=0.2, help='PCK threshold')
parser.add_argument('--auc', action='store_true', help='Evaluate AUC')
parser.add_argument('--mpck', action='store_true', help='Evaluate mPCK')
parser.add_argument('--check_logs', action='store_true', help='check runai logs instead of workdir')
parser.add_argument('--stat', action='store_true', help='check runai status')
parser.add_argument('--CVPR24', action='store_true', help='run on CVPR24 legacy folder')
parser.add_argument('--run_best_ckpt', action='store_true', help='run on CVPR24 legacy folder')
parser.add_argument('--num_samples', type=int, default=32, help='PCK threshold')
parser.add_argument('--ft_epochs', type=int, default=None, help='Num of FT epochs')
parser.add_argument('--masking', type=float, default=None, help='Num of FT epochs')
parser.add_argument('--masking_lamda', type=float, default=None, help='Num of FT epochs')
return parser.parse_args()
def check_status(job_name):
status = None
status_command = f'runai describe job {job_name}'
log = subprocess.run(status_command, shell=True, capture_output=True)
log = log.stdout.decode('utf-8')
pattern = r"Status:\s+(\w+)"
match = re.search(pattern, log)
if match:
status = match.group(1)
return status
def train_is_running(job_name, status=['Running', 'Pending', 'Failed']):
run_status = check_status(job_name)
for stat in status:
if run_status == stat:
print(f'{Bcolors.FAIL}{job_name} is {stat}{Bcolors.ENDC}')
return True
return False
def get_best_run(workdir_path, config, find_best):
file_name = None
ckpt_path = f'{workdir_path}/latest.pth'
if find_best == 'best':
local_path = f'work_dir_runai/{config.split(".")[0]}'
if os.path.exists(local_path):
file_names = [filename for filename in os.listdir(local_path) if filename.startswith("best_")]
if len(file_names) > 0:
file_name = file_names[0]
ckpt_path = f'{workdir_path}/{file_name}'
elif find_best == 'epoch_100':
local_path = f'work_dir_runai/{config.split(".")[0]}'
if os.path.exists(local_path):
file_name = 'epoch_100.pth'
if len(file_name) > 0:
ckpt_path = f'{workdir_path}/{file_name}'
return ckpt_path, file_name
def check_runai_logs(job_name):
os_command = f'runai logs {job_name}'
# status = subprocess.run(os_command, shell=True, capture_output=True)
# status = status.decode('utf-8')
status = subprocess.run(os_command, shell=True, capture_output=True, text=True)
status = status.stdout
return status
def get_run_name(config, args, run):
run = run.replace('_', '-')
lwr_config = config.lower()
train_job_name = f'or-{lwr_config.split(".")[0].replace("_", "-")}'
if len(train_job_name) > 60:
renamed_config = name_abriviator(lwr_config)
train_job_name = f'or-{renamed_config.split(".")[0].replace("_", "-")}'[:60]
test_job_name = f'ev-{run}-{lwr_config.split(".")[0].replace("_", "-")}'
if len(test_job_name) > 40:
renamed_config = name_abriviator(lwr_config)
test_job_name = f'ev-{run}-{renamed_config.split(".")[0].replace("_", "-")}'[:58]
job_names = [train_job_name, test_job_name]
for i in range(len(job_names)):
if job_names[i].endswith('-'):
job_names[i] = job_names[i][:-1]
if args.name is not None:
job_names[i] = f'{args.name}-{job_names[i]}'
return job_names
def name_abriviator(name):
replace_dict = {
'encoder': 'enc',
'decoder': 'dec',
'look_twice': 'lt',
'cross_category': 'cc',
'max_hops': 'hops',
'lamda': 'l',
'symmetric': 'sym',
'auxiliary': 'aux',
'batch_size': 'bs',
}
for key, value in replace_dict.items():
name = name.replace(key, value)
return name
def check_skip(lwr_config, args):
if args.no_base and 'base' in lwr_config:
print(f'Skipping {Bcolors.OKCYAN}{lwr_config}{Bcolors.ENDC} - base model')
return True
# if not args.action == "train" and ('cross_category' in lwr_config or 'cross_cat' in lwr_config):
# print(
# f'Skipping {Bcolors.OKCYAN}{lwr_config}{Bcolors.ENDC} - test on cross_caregory, validation is the same as test')
# return True
return False
def print_results(results):
print(f'\n\n\n{Bcolors.OKGREEN}Scores{Bcolors.ENDC}')
config_length = max(15, max(len(key) for key in results.keys()))
config_column_width = config_length + 2
print(f'| {"Config":<{config_column_width}} | {"Max Value":<11} | {"Latest Value":<13} | {"Best Value":<10} | {"Best Epoch":<10} |')
print(f'|{"-" * (config_column_width + 2)}|{"-" * 13}|{"-" * 15}|{"-" * 13}|{"-" * 11}|')
for config, val_dict in sorted(results.items()):
config_print = config.split('/')[-1].replace('.py', '')
other_results = val_dict.copy()
del other_results['latest']
best_key = max(other_results, key=other_results.get)
latest_val = parse_result(val_dict['latest'], Bcolors.OKBLUE)
best_val = parse_result(val_dict[best_key], Bcolors.HEADER)
if val_dict['latest'] is None and val_dict[best_key] is None:
max_val = f'{Bcolors.WARNING}No results{Bcolors.ENDC}'
elif val_dict['latest'] is None:
max_val = best_val
elif val_dict[best_key] is None:
max_val = latest_val
else:
max_val = latest_val if val_dict['latest'] > val_dict[best_key] else best_val
# print as a table: config, max_val, latest_val, best_val
print(f'| {config_print:<{config_column_width}} | {max_val:<20} | {latest_val:<22} | {best_val:<20} |{best_key:<10} |')
# print(f'{config_print}: {round(max_val * 100, 2)} '
# f'Latest: {latest_val} {best_key}: {best_val}')
def parse_result(value, color):
if value is None:
return f'{Bcolors.WARNING}No results{Bcolors.ENDC}'
else:
return f'{color}{round(value * 100, 2)}{Bcolors.ENDC}'
def main():
delay = 1
args = init_parser()
scores = {}
stat = {}
best_run = None
if args.config_folder:
configs = []
# list all py files in folder and subfolders
if '*' in args.config_folder:
config_folder = args.config_folder.strip("'")
parent_folder = os.path.relpath(os.path.join(config_folder, os.pardir))
configs = [os.path.join(parent_folder, f) for f in os.listdir(parent_folder) if config_folder.split('*')[0] in os.path.join(parent_folder, f)]
else:
matched_folders = [args.config_folder]
for matched_folder in matched_folders:
for root, dirs, files in os.walk(matched_folder):
for file in files:
if file.endswith(".py"):
configs.append(os.path.join(root, file))
else:
configs = [args.config]
print(f"{Bcolors.OKGREEN}Running {args.action} on {len(configs)} configs{Bcolors.ENDC}")
if args.action == "test" and not args.eval_three and not args.find_best:
runs = ['latest', 'best']
elif args.eval_three:
runs = ['latest', 'best', 'epoch_100']
elif args.find_best:
runs = ['best']
else:
runs = ['latest']
for config_path in sorted(configs):
for run in runs:
config = config_path.split("/")[-2] + "_" + config_path.split("/")[-1].replace('_config', '')
if args.CVPR24:
workdir_path = f'/storage/orhir/capeformer_legacy/{config.split(".")[0]}'
else:
workdir_path = f'/storage/orhir/capeformer/{config.split(".")[0]}'
local_workdir_path = f'work_dir_runai/{config.split(".")[0]}'
lwr_config = config.lower()
if check_skip(lwr_config, args):
continue
if args.action == "train" or args.action == "run":
gpu = 4 if args.dist else 1
resource = f' -g {gpu}'
else:
# resource = f' --gpu-memory 4G --cpu 2 --memory 4G'
resource = f' -g 0.3'
if args.large:
resource += f' --node-pools blaufer'
if args.stat:
train_job_name, job_name = get_run_name(config, args, run)
if args.action == "train" or args.action == "run":
job_name = train_job_name
print(f'{"-" * 30 + Bcolors.OKCYAN + job_name + Bcolors.ENDC + "-" * 30}')
status = check_status(job_name)
stat[job_name] = status
continue
# else:
# resource += f' --node-pools faculty'
if args.action == "train":
job_name, _ = get_run_name(config, args, run)
if args.dist:
py_command = (f'python -m torch.distributed.launch '
f'--nproc_per_node={gpu} --master_port=29500 '
f'train.py --gpus {gpu} --config {config_path} '
f'--work-dir {workdir_path} --autoscale-lr '
f'--launcher pytorch')
else:
py_command = (f'python train.py '
f' --config {config_path}'
f' --work-dir {workdir_path}')
elif args.action == "run":
job_name, _ = get_run_name(config, args, run)
if args.masking is not None:
masking_precent = int(args.masking * 100)
workdir_path = f'/storage/orhir/capeformer/CVPR25_ablation_mask_{masking_precent}'
job_name += f'-{masking_precent}'
if args.masking_lamda:
workdir_path = f'/storage/orhir/capeformer/CVPR25_ablation_mask_lamda_{int(args.masking_lamda)}'
job_name += f'-lamda-{int(args.masking_lamda)}'
py_command = (f'python run.py '
f' --config {config_path}'
f' --work_dir {workdir_path}')
if args.run_best_ckpt:
py_command += ' --best'
job_name += '-best'
if args.ft_epochs:
py_command += f' --ft_epochs {args.ft_epochs}'
if args.masking:
py_command += f' --masking_ratio {args.masking}'
if args.masking_lamda:
py_command += f' --lamda_masking {args.masking_lamda}'
else:
train_job_name, job_name = get_run_name(config, args, run)
ckpt_path, best_run = get_best_run(workdir_path, config, run)
py_command = f'python test.py {config_path} {ckpt_path} --num_samples {args.num_samples}'
if args.permute_keypoints:
py_command += ' --permute_keypoints'
job_name = (job_name + '-permute-keypoints')[:60]
print(f'{"-" * 30 + Bcolors.OKCYAN + job_name + Bcolors.ENDC + "-" * 30}')
if args.log:
os_command = f'runai logs {job_name}'
elif args.delete_fail:
if not train_is_running(job_name, ['Failed', 'Error']):
print("Job not failed, skipping...")
continue
os_command = f'runai delete job {job_name}'
elif args.delete_pending:
if not train_is_running(job_name, ['Pending']):
continue
os_command = f'runai delete job {job_name}'
elif args.delete:
os_command = f'runai delete job {job_name}'
elif args.results:
if args.check_logs:
# First check if the job is completed
status = check_runai_logs(job_name)
else:
if args.action == 'run':
log_file = os.path.join(f'work_dir_runai/{config.split(".")[0]}',
'base_skeleton_bias',
'testing_log.txt')
else:
log_file = os.path.join(f'work_dir_runai/{config.split(".")[0]}',
'testing_log.txt')
if os.path.exists(log_file):
with open(log_file, 'r') as f:
status = f.read()
# Parse config:
match = re.search(f'\*\*[\s\S]*?checkpoint:\s*.*?{run}[\s\S]*?(AUC:[\s\S]*?mPCK:\s*[\d.]+)', status)
if match:
status = match.group(1)
else:
status = ''
delay = 0
else:
status = check_runai_logs(job_name)
if args.auc and 'AUC' in status:
score = float(status.split('AUC: ')[1].split('\n')[0])
elif args.mpck and 'mPCK' in status:
score = float(status.split('mPCK: ')[1].split('\n')[0])
elif f'PCK@{args.pck}:' in status:
score = float(status.split(f'PCK@{args.pck}: ')[1].split('\n')[0])
else:
score = None
best_run = best_run.replace('best_PCK_', '').strip('.pth') if best_run else "No Best"
key = 'latest' if run == 'latest' else best_run
if config in scores:
scores[config][key] = score
else:
scores[config] = {key: score}
continue
else:
if args.action == 'test':
if not train_is_running(train_job_name, ['Completed', 'Succeeded']):
print('Train not completed')
continue
os_command = (f'runai submit --pvc=storage:/storage -i orhir/capeformer '
f' --name {job_name} {resource} --large-shm '
f' --command -- {py_command}')
# print(os_command)
if args.show_cmd:
print(f'{Bcolors.OKGREEN}{os_command}{Bcolors.ENDC}')
subprocess.run(os_command, shell=True)
if args.delete_folder:
if os.path.exists(local_workdir_path):
subprocess.run(f'rm -rf {local_workdir_path}', shell=True)
else:
subprocess.run(f'echo {Bcolors.WARNING}No workdir folder to delete{Bcolors.ENDC}', shell=True)
# print(f'\n{"-" * 150}')
time.sleep(delay)
if args.results:
print_results(scores)
if args.stat:
pretty_table(stat)
if __name__ == "__main__":
main()