xizaoqu commited on
Commit
4c1c078
·
1 Parent(s): 0153f2c

add main.py

Browse files
Files changed (1) hide show
  1. main.py +193 -0
main.py ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This repo is forked from [Boyuan Chen](https://boyuan.space/)'s research
3
+ template [repo](https://github.com/buoyancy99/research-template).
4
+ By its MIT license, you must keep the above sentence in `README.md`
5
+ and the `LICENSE` file to credit the author.
6
+
7
+ Main file for the project. This will create and run new experiments and load checkpoints from wandb.
8
+ Borrowed part of the code from David Charatan and wandb.
9
+ """
10
+
11
+ import sys
12
+ import subprocess
13
+ import time
14
+ from pathlib import Path
15
+
16
+ import hydra
17
+ from omegaconf import DictConfig, OmegaConf
18
+ from omegaconf.omegaconf import open_dict
19
+
20
+ from utils.print_utils import cyan
21
+ from utils.ckpt_utils import download_latest_checkpoint, is_run_id
22
+ from utils.cluster_utils import submit_slurm_job
23
+ from utils.distributed_utils import is_rank_zero
24
+
25
+ def get_latest_checkpoint(checkpoint_folder: Path, pattern: str = '*.ckpt'):
26
+ checkpoint_files = list(checkpoint_folder.glob(pattern))
27
+ if not checkpoint_files:
28
+ return None
29
+ latest_checkpoint = max(checkpoint_files, key=lambda f: f.stat().st_mtime)
30
+ return latest_checkpoint
31
+
32
+ def run_local(cfg: DictConfig):
33
+ # delay some imports in case they are not needed in non-local envs for submission
34
+ from experiments import build_experiment
35
+ from utils.wandb_utils import OfflineWandbLogger, SpaceEfficientWandbLogger
36
+
37
+ # Get yaml names
38
+ hydra_cfg = hydra.core.hydra_config.HydraConfig.get()
39
+ cfg_choice = OmegaConf.to_container(hydra_cfg.runtime.choices)
40
+
41
+ with open_dict(cfg):
42
+ if cfg_choice["experiment"] is not None:
43
+ cfg.experiment._name = cfg_choice["experiment"]
44
+ if cfg_choice["dataset"] is not None:
45
+ cfg.dataset._name = cfg_choice["dataset"]
46
+ if cfg_choice["algorithm"] is not None:
47
+ cfg.algorithm._name = cfg_choice["algorithm"]
48
+
49
+ # Set up the output directory.
50
+ output_dir = getattr(cfg, "output_dir", None)
51
+ if output_dir is not None:
52
+ OmegaConf.set_readonly(hydra_cfg, False)
53
+ hydra_cfg.runtime.output_dir = output_dir
54
+ OmegaConf.set_readonly(hydra_cfg, True)
55
+
56
+ output_dir = Path(hydra_cfg.runtime.output_dir)
57
+
58
+ if is_rank_zero:
59
+ print(cyan(f"Outputs will be saved to:"), output_dir)
60
+ (output_dir.parents[1] / "latest-run").unlink(missing_ok=True)
61
+ (output_dir.parents[1] / "latest-run").symlink_to(output_dir, target_is_directory=True)
62
+
63
+ # Set up logging with wandb.
64
+ if cfg.wandb.mode != "disabled":
65
+ # If resuming, merge into the existing run on wandb.
66
+ resume = cfg.get("resume", None)
67
+ name = f"{cfg.name} ({output_dir.parent.name}/{output_dir.name})" if resume is None else None
68
+
69
+ if "_on_compute_node" in cfg and cfg.cluster.is_compute_node_offline:
70
+ logger_cls = OfflineWandbLogger
71
+ else:
72
+ logger_cls = SpaceEfficientWandbLogger
73
+
74
+ offline = cfg.wandb.mode != "online"
75
+ logger = logger_cls(
76
+ name=name,
77
+ save_dir=str(output_dir),
78
+ offline=offline,
79
+ entity=cfg.wandb.entity,
80
+ project=cfg.wandb.project,
81
+ log_model=False,
82
+ config=OmegaConf.to_container(cfg),
83
+ id=resume,
84
+ resume="auto"
85
+ )
86
+
87
+ else:
88
+ logger = None
89
+
90
+ # Load ckpt
91
+ resume = cfg.get("resume", None)
92
+ load = cfg.get("load", None)
93
+ checkpoint_path = None
94
+ load_id = None
95
+ if load and not is_run_id(load):
96
+ checkpoint_path = load
97
+ if resume:
98
+ load_id = resume
99
+ elif load and is_run_id(load):
100
+ load_id = load
101
+ else:
102
+ load_id = None
103
+
104
+ if load_id:
105
+ checkpoint_path = get_latest_checkpoint(output_dir / "checkpoints")
106
+
107
+ if checkpoint_path and is_rank_zero:
108
+ print(f"Will load checkpoint from {checkpoint_path}")
109
+
110
+ # launch experiment
111
+ experiment = build_experiment(cfg, logger, checkpoint_path)
112
+ for task in cfg.experiment.tasks:
113
+ experiment.exec_task(task)
114
+
115
+
116
+ def run_slurm(cfg: DictConfig):
117
+ python_args = " ".join(sys.argv[1:]) + " +_on_compute_node=True"
118
+ project_root = Path.cwd()
119
+ while not (project_root / ".git").exists():
120
+ project_root = project_root.parent
121
+ if project_root == Path("/"):
122
+ raise Exception("Could not find repo directory!")
123
+
124
+ slurm_log_dir = submit_slurm_job(
125
+ cfg,
126
+ python_args,
127
+ project_root,
128
+ )
129
+
130
+ if "cluster" in cfg and cfg.cluster.is_compute_node_offline and cfg.wandb.mode == "online":
131
+ print("Job submitted to a compute node without internet. This requires manual syncing on login node.")
132
+ osh_command_dir = project_root / ".wandb_osh_command_dir"
133
+
134
+ osh_proc = None
135
+ # if click.confirm("Do you want us to run the sync loop for you?", default=True):
136
+ osh_proc = subprocess.Popen(["wandb-osh", "--command-dir", osh_command_dir])
137
+ print(f"Running wandb-osh in background... PID: {osh_proc.pid}")
138
+ print(f"To kill the sync process, run 'kill {osh_proc.pid}' in the terminal.")
139
+ print(
140
+ f"You can manually start a sync loop later by running the following:",
141
+ cyan(f"wandb-osh --command-dir {osh_command_dir}"),
142
+ )
143
+
144
+ print(
145
+ "Once the job gets allocated and starts running, we will print a command below "
146
+ "for you to trace the errors and outputs: (Ctrl + C to exit without waiting)"
147
+ )
148
+ msg = f"tail -f {slurm_log_dir}/* \n"
149
+ try:
150
+ while not list(slurm_log_dir.glob("*.out")) and not list(slurm_log_dir.glob("*.err")):
151
+ time.sleep(1)
152
+ print(cyan("To trace the outputs and errors, run the following command:"), msg)
153
+ except KeyboardInterrupt:
154
+ print("Keyboard interrupt detected. Exiting...")
155
+ print(
156
+ cyan("To trace the outputs and errors, manually wait for the job to start and run the following command:"),
157
+ msg,
158
+ )
159
+
160
+
161
+ @hydra.main(
162
+ version_base=None,
163
+ config_path="configurations",
164
+ config_name="training",
165
+ )
166
+ def run(cfg: DictConfig):
167
+ if "_on_compute_node" in cfg and cfg.cluster.is_compute_node_offline:
168
+ with open_dict(cfg):
169
+ if cfg.cluster.is_compute_node_offline and cfg.wandb.mode == "online":
170
+ cfg.wandb.mode = "offline"
171
+
172
+ if "name" not in cfg:
173
+ raise ValueError("must specify a name for the run with command line argument '+name=[name]'")
174
+
175
+ if not cfg.wandb.get("entity", None):
176
+ raise ValueError(
177
+ "must specify wandb entity in 'configurations/config.yaml' or with command line"
178
+ " argument 'wandb.entity=[entity]' \n An entity is your wandb user name or group"
179
+ " name. This is used for logging. If you don't have an wandb account, please signup at https://wandb.ai/"
180
+ )
181
+
182
+ if cfg.wandb.project is None:
183
+ cfg.wandb.project = str(Path(__file__).parent.name)
184
+
185
+ if "cluster" in cfg and not "_on_compute_node" in cfg:
186
+ print(cyan("Slurm detected, submitting to compute node instead of running locally..."))
187
+ run_slurm(cfg)
188
+ else:
189
+ run_local(cfg)
190
+
191
+
192
+ if __name__ == "__main__":
193
+ run() # pylint: disable=no-value-for-parameter