DancingNow commited on
Commit
923fdff
·
verified ·
1 Parent(s): 23ee5ab

Add files using upload-large-folder tool

Browse files
README.md CHANGED
@@ -97,17 +97,22 @@ bash Train/run_download_prepare.sh
97
 
98
  ## Training
99
 
100
- The default runnable configuration uses a direct batch size of 256 on one GPU,
101
- no gradient accumulation, 15 epochs, and saves checkpoints every 5 epochs:
 
102
 
103
  ```bash
104
- bash Train/run_train_local.sh
105
  ```
106
 
 
 
 
 
107
  For a new machine, the complete download-to-training sequence can be started with:
108
 
109
  ```bash
110
- bash Train/run_all_local.sh
111
  ```
112
 
113
  The training entrypoint itself is independent of a scheduler:
@@ -117,8 +122,10 @@ python Train/training/train.py --config Train/configs/stead_empty8_local.yaml
117
  ```
118
 
119
  The transfer-learning configuration in `Train/Transfer_and_Test/configs/config.yaml`
120
- also uses a training batch size of 256. Generation uses a batch size of 256 in
121
- both local and transfer-learning launchers.
 
 
122
 
123
  If another machine cannot fit 256 waveforms in GPU memory, reduce the configured
124
  batch size before running. If distributed training is required, launch this same
@@ -133,5 +140,11 @@ After epoch 15 finishes:
133
  bash Train/run_generate_100.sh
134
  ```
135
 
 
 
 
 
 
 
136
  This performs ancestral DDPM sampling with 25 steps and seed 2026. The output
137
  is `Train/results/stead_empty8_local/generated_100/generated_ddpm25_seed2026.h5`.
 
97
 
98
  ## Training
99
 
100
+ The default runnable configuration uses 8 GPUs, a global batch size of 256
101
+ (32 samples per GPU), no gradient accumulation, 15 epochs, and saves
102
+ checkpoints every 5 epochs:
103
 
104
  ```bash
105
+ bash Train/run_train_local.sh --num-gpus 8
106
  ```
107
 
108
+ The GPU count is a launcher parameter. For example, use `--num-gpus 1`,
109
+ `--num-gpus 4`, or `--num-gpus 8`. It defaults to 8 and may also be set with
110
+ `NUM_GPUS`. The global batch size must be divisible by the GPU count.
111
+
112
  For a new machine, the complete download-to-training sequence can be started with:
113
 
114
  ```bash
115
+ bash Train/run_all_local.sh --num-gpus 8
116
  ```
117
 
118
  The training entrypoint itself is independent of a scheduler:
 
122
  ```
123
 
124
  The transfer-learning configuration in `Train/Transfer_and_Test/configs/config.yaml`
125
+ also uses a global training batch size of 256. Generation uses a global batch
126
+ size of 256 in both local and transfer-learning launchers. Training and
127
+ generation distribute their work across the requested GPUs and only the main
128
+ process writes the final checkpoint or merged HDF5 output.
129
 
130
  If another machine cannot fit 256 waveforms in GPU memory, reduce the configured
131
  batch size before running. If distributed training is required, launch this same
 
140
  bash Train/run_generate_100.sh
141
  ```
142
 
143
+ This also defaults to 8 GPUs. To select a different count:
144
+
145
+ ```bash
146
+ bash Train/run_generate_100.sh --num-gpus 4
147
+ ```
148
+
149
  This performs ancestral DDPM sampling with 25 steps and seed 2026. The output
150
  is `Train/results/stead_empty8_local/generated_100/generated_ddpm25_seed2026.h5`.
Transfer_and_Test/README.md CHANGED
@@ -15,10 +15,12 @@ Run from the `GenerationArticle` directory:
15
 
16
  ```bash
17
  conda activate DiT
18
- bash Train/Transfer_and_Test/run_transfer_and_test.sh
19
  ```
20
 
21
- The launcher uses paths inside this directory, trains with batch size 256,
22
- saves checkpoints every 50 epochs, generates 100 DDPM samples in a batch of
23
- 256 at each milestone, and evaluates them with
24
- the bundled EQT checkpoint using a 1.0-second tolerance.
 
 
 
15
 
16
  ```bash
17
  conda activate DiT
18
+ bash Train/Transfer_and_Test/run_transfer_and_test.sh --num-gpus 8
19
  ```
20
 
21
+ `--num-gpus` is configurable and defaults to 8. The launcher uses paths inside
22
+ this directory, trains with global batch size 256 (32 per GPU with 8 GPUs),
23
+ saves checkpoints every 50 epochs, and distributes each 256-sample generation
24
+ batch across the selected GPUs before merging the shards. At each milestone it
25
+ generates 100 DDPM samples and evaluates them with
26
+ the bundled EQT checkpoint using the selected GPUs and a 1.0-second tolerance.
Transfer_and_Test/run_transfer_and_test.sh CHANGED
@@ -17,6 +17,24 @@ EVAL_SCRIPT="${BUNDLE_ROOT}/src/eqt/evaluate_transfer_eqt_test_in_memory.py"
17
  EQT_CHECKPOINT="${BUNDLE_ROOT}/eqt/best_val_state_dict.pt"
18
  LOG_DIR="${RESULT_ROOT}/logs"
19
  MILESTONE_ROOT="${RESULT_ROOT}/milestones"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  mkdir -p "${LOG_DIR}" "${MILESTONE_ROOT}"
22
  cd "${BUNDLE_ROOT}"
@@ -34,6 +52,17 @@ else
34
  }
35
  fi
36
 
 
 
 
 
 
 
 
 
 
 
 
37
  for path in "${CONFIG}" "${TRAIN_SCRIPT}" "${GENERATE_SCRIPT}" \
38
  "${EVAL_SCRIPT}" "${EQT_CHECKPOINT}" \
39
  "${BUNDLE_ROOT}/data/processed/iquique/train_3000_two_conditions.h5" \
@@ -52,8 +81,13 @@ print(yaml.safe_load(open(sys.argv[1], encoding="utf-8"))["training"]["global_ba
52
  PY
53
  )"
54
  echo "[INFO] train batch=${TRAIN_BATCH}"
55
- echo "[INFO] generation samples=100 generation batch=256 sampler=ddpm steps=100 cfg=1"
56
- echo "[INFO] EQT tolerance=100 samples (1.0 s)"
 
 
 
 
 
57
  nvidia-smi || true
58
 
59
  FINAL_EPOCH="${TARGET_EPOCH:-2140}"
@@ -71,7 +105,7 @@ for epoch in "${MILESTONES[@]}"; do
71
  if [[ ! -f "${checkpoint}" ]]; then
72
  if [[ "${epoch}" -eq 50 ]]; then
73
  echo "[train] target_epoch=${epoch}"
74
- "${PYTHON_BIN}" -u "${TRAIN_SCRIPT}" --config "${CONFIG}" \
75
  --target-epoch "${epoch}" \
76
  2>&1 | tee "${LOG_DIR}/train_to_${epoch}.log"
77
  else
@@ -86,7 +120,7 @@ for epoch in "${MILESTONES[@]}"; do
86
  exit 1
87
  }
88
  echo "[train] resume=${previous_checkpoint} target_epoch=${epoch}"
89
- "${PYTHON_BIN}" -u "${TRAIN_SCRIPT}" --config "${CONFIG}" \
90
  --resume "${previous_checkpoint}" --target-epoch "${epoch}" \
91
  2>&1 | tee "${LOG_DIR}/train_to_${epoch}.log"
92
  fi
@@ -103,7 +137,7 @@ for epoch in "${MILESTONES[@]}"; do
103
 
104
  if [[ ! -f "${generated}" ]]; then
105
  echo "[generate] epoch=${epoch}"
106
- "${PYTHON_BIN}" -u "${GENERATE_SCRIPT}" \
107
  --checkpoint "${checkpoint}" \
108
  --source-h5 "${BUNDLE_ROOT}/data/processed/iquique/train_3000_two_conditions.h5" \
109
  --output-h5 "${generated}" \
@@ -128,6 +162,7 @@ for epoch in "${MILESTONES[@]}"; do
128
  --tasks milestone \
129
  --extra-checkpoints "milestone=${EQT_CHECKPOINT}" \
130
  --device cuda \
 
131
  --batch-size 100 \
132
  --workers 0 \
133
  --threshold 0.1 \
 
17
  EQT_CHECKPOINT="${BUNDLE_ROOT}/eqt/best_val_state_dict.pt"
18
  LOG_DIR="${RESULT_ROOT}/logs"
19
  MILESTONE_ROOT="${RESULT_ROOT}/milestones"
20
+ NUM_GPUS="${NUM_GPUS:-8}"
21
+
22
+ while [[ $# -gt 0 ]]; do
23
+ case "$1" in
24
+ --num-gpus)
25
+ [[ $# -ge 2 ]] || { echo "--num-gpus requires a value" >&2; exit 2; }
26
+ NUM_GPUS="$2"
27
+ shift 2
28
+ ;;
29
+ --num-gpus=*) NUM_GPUS="${1#*=}"; shift ;;
30
+ *)
31
+ echo "Unknown argument: $1" >&2
32
+ echo "Usage: bash Train/Transfer_and_Test/run_transfer_and_test.sh [--num-gpus N]" >&2
33
+ exit 2
34
+ ;;
35
+ esac
36
+ done
37
+ [[ "${NUM_GPUS}" =~ ^[1-9][0-9]*$ ]] || { echo "NUM_GPUS must be a positive integer" >&2; exit 2; }
38
 
39
  mkdir -p "${LOG_DIR}" "${MILESTONE_ROOT}"
40
  cd "${BUNDLE_ROOT}"
 
52
  }
53
  fi
54
 
55
+ run_gpu_job() {
56
+ if [[ "${NUM_GPUS}" -eq 1 ]]; then
57
+ "${PYTHON_BIN}" -u "$@"
58
+ else
59
+ "${PYTHON_BIN}" -u -m torch.distributed.run \
60
+ --standalone \
61
+ --nproc_per_node="${NUM_GPUS}" \
62
+ "$@"
63
+ fi
64
+ }
65
+
66
  for path in "${CONFIG}" "${TRAIN_SCRIPT}" "${GENERATE_SCRIPT}" \
67
  "${EVAL_SCRIPT}" "${EQT_CHECKPOINT}" \
68
  "${BUNDLE_ROOT}/data/processed/iquique/train_3000_two_conditions.h5" \
 
81
  PY
82
  )"
83
  echo "[INFO] train batch=${TRAIN_BATCH}"
84
+ if (( TRAIN_BATCH % NUM_GPUS != 0 )); then
85
+ echo "[FAIL] global train batch ${TRAIN_BATCH} is not divisible by NUM_GPUS=${NUM_GPUS}" >&2
86
+ exit 2
87
+ fi
88
+ echo "[INFO] num_gpus=${NUM_GPUS} train_global_batch=${TRAIN_BATCH} train_per_gpu_batch=$((TRAIN_BATCH / NUM_GPUS))"
89
+ echo "[INFO] generation samples=100 generation_global_batch=256 sampler=ddpm steps=100 cfg=1"
90
+ echo "[INFO] EQT num_gpus=${NUM_GPUS} tolerance=100 samples (1.0 s)"
91
  nvidia-smi || true
92
 
93
  FINAL_EPOCH="${TARGET_EPOCH:-2140}"
 
105
  if [[ ! -f "${checkpoint}" ]]; then
106
  if [[ "${epoch}" -eq 50 ]]; then
107
  echo "[train] target_epoch=${epoch}"
108
+ run_gpu_job "${TRAIN_SCRIPT}" --config "${CONFIG}" \
109
  --target-epoch "${epoch}" \
110
  2>&1 | tee "${LOG_DIR}/train_to_${epoch}.log"
111
  else
 
120
  exit 1
121
  }
122
  echo "[train] resume=${previous_checkpoint} target_epoch=${epoch}"
123
+ run_gpu_job "${TRAIN_SCRIPT}" --config "${CONFIG}" \
124
  --resume "${previous_checkpoint}" --target-epoch "${epoch}" \
125
  2>&1 | tee "${LOG_DIR}/train_to_${epoch}.log"
126
  fi
 
137
 
138
  if [[ ! -f "${generated}" ]]; then
139
  echo "[generate] epoch=${epoch}"
140
+ run_gpu_job "${GENERATE_SCRIPT}" \
141
  --checkpoint "${checkpoint}" \
142
  --source-h5 "${BUNDLE_ROOT}/data/processed/iquique/train_3000_two_conditions.h5" \
143
  --output-h5 "${generated}" \
 
162
  --tasks milestone \
163
  --extra-checkpoints "milestone=${EQT_CHECKPOINT}" \
164
  --device cuda \
165
+ --num-gpus "${NUM_GPUS}" \
166
  --batch-size 100 \
167
  --workers 0 \
168
  --threshold 0.1 \
Transfer_and_Test/src/eqt/evaluate_transfer_eqt_test_in_memory.py CHANGED
@@ -93,6 +93,7 @@ def evaluate_one(
93
  batch_size: int,
94
  workers: int,
95
  device: torch.device,
 
96
  threshold: float,
97
  peak_distance_samples: int,
98
  tolerance_samples: int,
@@ -123,6 +124,8 @@ def evaluate_one(
123
  model: EQTransformer = build_eqtransformer_for_state(waves.shape[-1], state).to(device)
124
  model.load_state_dict(state)
125
  model.eval()
 
 
126
 
127
  rows: list[dict[str, Any]] = []
128
  prob_chunks: list[np.ndarray] = []
@@ -227,6 +230,7 @@ def parse_args() -> argparse.Namespace:
227
  parser.add_argument("--tasks", nargs="*", default=DEFAULT_TASKS)
228
  parser.add_argument("--extra-checkpoints", nargs="*", default=[], help="Additional task=checkpoint entries, e.g. original=work/EQT/eqtransformer_original_state_dict.pt")
229
  parser.add_argument("--device", default="cuda")
 
230
  parser.add_argument("--batch-size", type=int, default=1280)
231
  parser.add_argument("--workers", type=int, default=8)
232
  parser.add_argument("--threshold", type=float, default=0.1)
@@ -261,6 +265,15 @@ def main() -> None:
261
  print(f"Tasks ({len(args.tasks)}): {' '.join(args.tasks)}")
262
 
263
  device = torch.device(str(args.device) if torch.cuda.is_available() else "cpu")
 
 
 
 
 
 
 
 
 
264
  rows = []
265
  for task in args.tasks:
266
  checkpoint = resolve_checkpoint(task, run_root, extra_checkpoints)
@@ -274,6 +287,7 @@ def main() -> None:
274
  batch_size=int(args.batch_size),
275
  workers=int(args.workers),
276
  device=device,
 
277
  threshold=float(args.threshold),
278
  peak_distance_samples=int(args.peak_distance_samples),
279
  tolerance_samples=int(args.tolerance_samples),
 
93
  batch_size: int,
94
  workers: int,
95
  device: torch.device,
96
+ num_gpus: int,
97
  threshold: float,
98
  peak_distance_samples: int,
99
  tolerance_samples: int,
 
124
  model: EQTransformer = build_eqtransformer_for_state(waves.shape[-1], state).to(device)
125
  model.load_state_dict(state)
126
  model.eval()
127
+ if device.type == "cuda" and num_gpus > 1:
128
+ model = torch.nn.DataParallel(model, device_ids=list(range(num_gpus)))
129
 
130
  rows: list[dict[str, Any]] = []
131
  prob_chunks: list[np.ndarray] = []
 
230
  parser.add_argument("--tasks", nargs="*", default=DEFAULT_TASKS)
231
  parser.add_argument("--extra-checkpoints", nargs="*", default=[], help="Additional task=checkpoint entries, e.g. original=work/EQT/eqtransformer_original_state_dict.pt")
232
  parser.add_argument("--device", default="cuda")
233
+ parser.add_argument("--num-gpus", type=int, default=1)
234
  parser.add_argument("--batch-size", type=int, default=1280)
235
  parser.add_argument("--workers", type=int, default=8)
236
  parser.add_argument("--threshold", type=float, default=0.1)
 
265
  print(f"Tasks ({len(args.tasks)}): {' '.join(args.tasks)}")
266
 
267
  device = torch.device(str(args.device) if torch.cuda.is_available() else "cpu")
268
+ num_gpus = int(args.num_gpus)
269
+ if num_gpus < 1:
270
+ raise ValueError("--num-gpus must be a positive integer")
271
+ if device.type == "cuda" and num_gpus > torch.cuda.device_count():
272
+ raise ValueError(
273
+ f"Requested {num_gpus} GPUs, but only {torch.cuda.device_count()} are visible"
274
+ )
275
+ if device.type != "cuda":
276
+ num_gpus = 1
277
  rows = []
278
  for task in args.tasks:
279
  checkpoint = resolve_checkpoint(task, run_root, extra_checkpoints)
 
287
  batch_size=int(args.batch_size),
288
  workers=int(args.workers),
289
  device=device,
290
+ num_gpus=num_gpus,
291
  threshold=float(args.threshold),
292
  peak_distance_samples=int(args.peak_distance_samples),
293
  tolerance_samples=int(args.tolerance_samples),
Transfer_and_Test/src/generate_controlnet_two_condition.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
 
3
  import argparse
4
  import csv
 
5
  import random
6
  import sys
7
  from pathlib import Path
@@ -9,6 +10,7 @@ from pathlib import Path
9
  import h5py
10
  import numpy as np
11
  import torch
 
12
  from tqdm import tqdm
13
 
14
  # This generator is bundled directly under <bundle>/src on the server.
@@ -19,6 +21,19 @@ from diffusion import create_diffusion
19
  from models_controlnet_two_condition import TwoConditionControlNetSWaG
20
 
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  def main() -> None:
23
  parser = argparse.ArgumentParser()
24
  parser.add_argument("--checkpoint", type=Path, required=True)
@@ -41,10 +56,16 @@ def main() -> None:
41
  if not torch.cuda.is_available():
42
  raise RuntimeError("CUDA is required for sampling")
43
 
44
- random.seed(args.seed)
45
- np.random.seed(args.seed)
46
- torch.manual_seed(args.seed)
47
- torch.cuda.manual_seed_all(args.seed)
 
 
 
 
 
 
48
  rng = np.random.default_rng(args.seed)
49
 
50
  with h5py.File(args.source_h5, "r") as handle:
@@ -64,36 +85,40 @@ def main() -> None:
64
 
65
  checkpoint = torch.load(args.checkpoint, map_location="cpu", weights_only=False)
66
  config = checkpoint["config"]
67
- model = TwoConditionControlNetSWaG(**config["model"]).cuda().eval()
68
  model.load_state_dict(checkpoint["model"], strict=True)
 
69
  respacing = f"ddim{args.sampling_steps}" if args.sampler == "ddim" else str(args.sampling_steps)
70
  diffusion = create_diffusion(timestep_respacing=respacing, **config["diffusion"])
71
- generator = torch.Generator(device="cuda").manual_seed(args.seed)
72
 
73
- args.output_h5.parent.mkdir(parents=True, exist_ok=True)
74
- args.output_meta.parent.mkdir(parents=True, exist_ok=True)
75
- partial = args.output_h5.with_name(args.output_h5.name + ".partial")
76
- partial.unlink(missing_ok=True)
 
 
 
77
  shape = (args.num_samples, int(config["model"]["length"]), int(config["model"]["in_channels"]))
78
- with h5py.File(partial, "w") as handle:
79
- output = handle.create_dataset("data", shape=shape, dtype="float32", compression="gzip", shuffle=True)
80
- handle.create_dataset("labels", data=labels, dtype="float32")
81
- handle.attrs.update(
82
- checkpoint=str(args.checkpoint.resolve()),
83
- sampling_steps=args.sampling_steps,
84
- sampler=args.sampler,
85
- cfg_scale=args.cfg_scale,
86
- seed=args.seed,
87
- )
88
  with torch.no_grad():
89
- for start in tqdm(range(0, args.num_samples, args.batch_size), desc="sampling"):
90
- end = min(start + args.batch_size, args.num_samples)
91
- batch_labels = torch.from_numpy(labels[start:end]).cuda()
 
 
 
 
 
92
  noise = torch.randn(
93
  end - start,
94
  shape[2],
95
  shape[1],
96
- device="cuda",
97
  generator=generator,
98
  )
99
 
@@ -106,25 +131,51 @@ def main() -> None:
106
  model_fn,
107
  noise.shape,
108
  noise=noise,
109
- device=torch.device("cuda"),
110
  progress=False,
111
  clip_denoised=False,
112
  )
113
  output[start:end] = samples.float().transpose(1, 2).cpu().numpy().astype(np.float32)
114
- partial.replace(args.output_h5)
115
-
116
- with args.output_meta.open("w", newline="", encoding="utf-8") as handle:
117
- writer = csv.writer(handle)
118
- writer.writerow(["generated_index", "source_test_index", "p_index", "s_index"])
119
- writer.writerows(
120
- (
121
- i,
122
- int(indices[i]),
123
- float(labels[i, 0]),
124
- float(labels[i, 1]),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  )
126
- for i in range(len(labels))
127
- )
 
128
 
129
 
130
  if __name__ == "__main__":
 
2
 
3
  import argparse
4
  import csv
5
+ import os
6
  import random
7
  import sys
8
  from pathlib import Path
 
10
  import h5py
11
  import numpy as np
12
  import torch
13
+ import torch.distributed as dist
14
  from tqdm import tqdm
15
 
16
  # This generator is bundled directly under <bundle>/src on the server.
 
21
  from models_controlnet_two_condition import TwoConditionControlNetSWaG
22
 
23
 
24
+ def distributed_context() -> tuple[int, int, int]:
25
+ world_size = int(os.environ.get("WORLD_SIZE", "1"))
26
+ rank = int(os.environ.get("RANK", "0"))
27
+ local_rank = int(os.environ.get("LOCAL_RANK", "0"))
28
+ if world_size > 1:
29
+ dist.init_process_group("nccl")
30
+ return rank, local_rank, world_size
31
+
32
+
33
+ def shard_path(output_path: Path, rank: int) -> Path:
34
+ return output_path.with_name(f".{output_path.name}.rank{rank}.partial.h5")
35
+
36
+
37
  def main() -> None:
38
  parser = argparse.ArgumentParser()
39
  parser.add_argument("--checkpoint", type=Path, required=True)
 
56
  if not torch.cuda.is_available():
57
  raise RuntimeError("CUDA is required for sampling")
58
 
59
+ rank, local_rank, world_size = distributed_context()
60
+ device = torch.device("cuda", local_rank)
61
+ torch.cuda.set_device(device)
62
+ if args.batch_size % world_size:
63
+ raise ValueError(f"batch-size must be divisible by world size ({world_size})")
64
+ local_batch = args.batch_size // world_size
65
+ random.seed(args.seed + rank)
66
+ np.random.seed(args.seed + rank)
67
+ torch.manual_seed(args.seed + rank)
68
+ torch.cuda.manual_seed_all(args.seed + rank)
69
  rng = np.random.default_rng(args.seed)
70
 
71
  with h5py.File(args.source_h5, "r") as handle:
 
85
 
86
  checkpoint = torch.load(args.checkpoint, map_location="cpu", weights_only=False)
87
  config = checkpoint["config"]
88
+ model = TwoConditionControlNetSWaG(**config["model"]).to(device).eval()
89
  model.load_state_dict(checkpoint["model"], strict=True)
90
+ del checkpoint
91
  respacing = f"ddim{args.sampling_steps}" if args.sampler == "ddim" else str(args.sampling_steps)
92
  diffusion = create_diffusion(timestep_respacing=respacing, **config["diffusion"])
93
+ generator = torch.Generator(device=device).manual_seed(args.seed + rank)
94
 
95
+ if rank == 0:
96
+ args.output_h5.parent.mkdir(parents=True, exist_ok=True)
97
+ args.output_meta.parent.mkdir(parents=True, exist_ok=True)
98
+ for shard_rank in range(world_size):
99
+ shard_path(args.output_h5, shard_rank).unlink(missing_ok=True)
100
+ if world_size > 1:
101
+ dist.barrier()
102
  shape = (args.num_samples, int(config["model"]["length"]), int(config["model"]["in_channels"]))
103
+ assigned = np.array_split(np.arange(args.num_samples, dtype=np.int64), world_size)[rank]
104
+ local_shape = (len(assigned), shape[1], shape[2])
105
+ with h5py.File(shard_path(args.output_h5, rank), "w") as handle:
106
+ output = handle.create_dataset("data", shape=local_shape, dtype="float32", compression="gzip", shuffle=True)
107
+ handle.create_dataset("indices", data=assigned, dtype="int64")
 
 
 
 
 
108
  with torch.no_grad():
109
+ for start in tqdm(
110
+ range(0, len(assigned), local_batch),
111
+ desc=f"sampling rank {rank}",
112
+ disable=rank != 0,
113
+ ):
114
+ end = min(start + local_batch, len(assigned))
115
+ global_positions = assigned[start:end]
116
+ batch_labels = torch.from_numpy(labels[global_positions]).to(device)
117
  noise = torch.randn(
118
  end - start,
119
  shape[2],
120
  shape[1],
121
+ device=device,
122
  generator=generator,
123
  )
124
 
 
131
  model_fn,
132
  noise.shape,
133
  noise=noise,
134
+ device=device,
135
  progress=False,
136
  clip_denoised=False,
137
  )
138
  output[start:end] = samples.float().transpose(1, 2).cpu().numpy().astype(np.float32)
139
+
140
+ if world_size > 1:
141
+ dist.barrier()
142
+ if rank == 0:
143
+ partial = args.output_h5.with_name(args.output_h5.name + ".partial")
144
+ partial.unlink(missing_ok=True)
145
+ with h5py.File(partial, "w") as handle:
146
+ output = handle.create_dataset("data", shape=shape, dtype="float32", compression="gzip", shuffle=True)
147
+ handle.create_dataset("labels", data=labels, dtype="float32")
148
+ handle.attrs.update(
149
+ checkpoint=str(args.checkpoint.resolve()),
150
+ sampling_steps=args.sampling_steps,
151
+ sampler=args.sampler,
152
+ cfg_scale=args.cfg_scale,
153
+ seed=args.seed,
154
+ world_size=world_size,
155
+ )
156
+ for shard_rank in range(world_size):
157
+ current = shard_path(args.output_h5, shard_rank)
158
+ with h5py.File(current, "r") as shard:
159
+ positions = np.asarray(shard["indices"], dtype=np.int64)
160
+ output[positions] = shard["data"][:]
161
+ current.unlink()
162
+ partial.replace(args.output_h5)
163
+
164
+ with args.output_meta.open("w", newline="", encoding="utf-8") as handle:
165
+ writer = csv.writer(handle)
166
+ writer.writerow(["generated_index", "source_test_index", "p_index", "s_index"])
167
+ writer.writerows(
168
+ (
169
+ i,
170
+ int(indices[i]),
171
+ float(labels[i, 0]),
172
+ float(labels[i, 1]),
173
+ )
174
+ for i in range(len(labels))
175
  )
176
+ if world_size > 1:
177
+ dist.barrier()
178
+ dist.destroy_process_group()
179
 
180
 
181
  if __name__ == "__main__":
Transfer_and_Test/src/train_controlnet_two_condition.py CHANGED
@@ -1,12 +1,16 @@
1
  from __future__ import annotations
2
 
3
  import argparse
 
4
  import random
5
  from pathlib import Path
6
 
7
  import numpy as np
8
  import torch
 
 
9
  from torch.utils.data import DataLoader
 
10
  from tqdm import tqdm
11
 
12
  from config import load_config, resolve_path
@@ -19,9 +23,19 @@ from models_controlnet_two_condition import TwoConditionControlNetSWaG
19
  ROOT = Path(__file__).resolve().parents[1]
20
 
21
 
 
 
 
 
 
 
 
 
 
22
  def save_checkpoint(path, model, optimizer, epoch, step, config, include_optimizer: bool):
 
23
  payload = {
24
- "model": model.state_dict(),
25
  "epoch": epoch,
26
  "step": step,
27
  "config": config,
@@ -44,27 +58,39 @@ def main():
44
  if not torch.cuda.is_available():
45
  raise RuntimeError("CUDA is required")
46
 
 
 
 
47
  config_path = Path(args.config).resolve()
48
  config = load_config(config_path)
49
  training = config["training"]
50
- seed = int(training.get("seed", 0))
51
  random.seed(seed)
52
  np.random.seed(seed)
53
  torch.manual_seed(seed)
54
  torch.cuda.manual_seed_all(seed)
55
  output = resolve_path(ROOT, config["output"]["directory"])
56
  checkpoint_dir = output / "checkpoints"
57
- checkpoint_dir.mkdir(parents=True, exist_ok=True)
58
- (output / "config.yaml").write_text(config_path.read_text(encoding="utf-8"), encoding="utf-8")
 
 
 
59
 
60
  dataset = TwoConditionDataset(
61
  resolve_path(ROOT, config["data"]["train_h5"]),
62
  cache_in_memory=bool(training.get("cache_in_memory", False)),
63
  )
 
 
 
 
 
64
  loader = DataLoader(
65
  dataset,
66
- batch_size=int(training["global_batch_size"]),
67
- shuffle=True,
 
68
  num_workers=int(training.get("num_workers", 0)),
69
  pin_memory=True,
70
  drop_last=True,
@@ -117,12 +143,16 @@ def main():
117
  for name, parameter in model.named_parameters():
118
  parameter.requires_grad_(name.startswith(train_prefixes))
119
  trainable = [parameter for parameter in model.parameters() if parameter.requires_grad]
120
- print(f"resume_checkpoint={resume_checkpoint}")
121
- print(f"control_depth={model.control_depth}")
122
- print(f"trainable_parameters={sum(p.numel() for p in trainable):,}")
123
- print(f"frozen_parameters={sum(p.numel() for p in model.parameters() if not p.requires_grad):,}")
124
-
125
- model.cuda()
 
 
 
 
126
  optimizer = torch.optim.AdamW(
127
  trainable,
128
  lr=float(training["learning_rate"]),
@@ -139,24 +169,27 @@ def main():
139
  optimizer.load_state_dict(checkpoint["optimizer"])
140
  elif resume_checkpoint is not None:
141
  print("WARNING: resume checkpoint has no optimizer state; optimizer restarts fresh.")
 
142
  epochs = 1 if args.smoke else (int(args.target_epoch) if args.target_epoch else int(training["epochs"]))
143
  if epochs < start_epoch:
144
  raise ValueError(f"target epoch {epochs} is before checkpoint epoch {start_epoch}")
145
  optimizer.zero_grad(set_to_none=True)
146
  for epoch in range(start_epoch, epochs):
 
 
147
  model.train()
148
  total_loss = 0.0
149
  for batch_index, (waveforms, labels) in enumerate(
150
  tqdm(
151
  loader,
152
  desc=f"epoch {epoch + 1}/{epochs}",
153
- disable=not bool(training.get("progress_bar", True)),
154
  )
155
  ):
156
- waveforms = waveforms.cuda(non_blocking=True)
157
- labels = labels.cuda(non_blocking=True)
158
  timesteps = torch.randint(
159
- 0, diffusion.num_timesteps, (waveforms.shape[0],), device="cuda"
160
  )
161
  with torch.autocast("cuda", enabled=use_amp, dtype=torch.float16):
162
  loss = diffusion.training_losses(
@@ -175,9 +208,14 @@ def main():
175
  if args.smoke:
176
  break
177
  batches = 1 if args.smoke else len(loader)
178
- print(f"epoch={epoch + 1} loss={total_loss / batches:.6f}")
 
 
 
 
 
179
  interval = int(config["output"].get("checkpoint_every_epochs", 10))
180
- if not args.smoke and ((epoch + 1) % interval == 0 or epoch + 1 == epochs):
181
  save_checkpoint(
182
  checkpoint_dir / f"checkpoint_epoch_{epoch + 1:05d}.pt",
183
  model,
@@ -187,7 +225,11 @@ def main():
187
  config,
188
  include_optimizer=bool(config["output"].get("save_optimizer_state", True)),
189
  )
 
 
190
  dataset.close()
 
 
191
 
192
 
193
  if __name__ == "__main__":
 
1
  from __future__ import annotations
2
 
3
  import argparse
4
+ import os
5
  import random
6
  from pathlib import Path
7
 
8
  import numpy as np
9
  import torch
10
+ import torch.distributed as dist
11
+ from torch.nn.parallel import DistributedDataParallel
12
  from torch.utils.data import DataLoader
13
+ from torch.utils.data.distributed import DistributedSampler
14
  from tqdm import tqdm
15
 
16
  from config import load_config, resolve_path
 
23
  ROOT = Path(__file__).resolve().parents[1]
24
 
25
 
26
+ def distributed_context() -> tuple[int, int, int]:
27
+ world_size = int(os.environ.get("WORLD_SIZE", "1"))
28
+ rank = int(os.environ.get("RANK", "0"))
29
+ local_rank = int(os.environ.get("LOCAL_RANK", "0"))
30
+ if world_size > 1:
31
+ dist.init_process_group("nccl")
32
+ return rank, local_rank, world_size
33
+
34
+
35
  def save_checkpoint(path, model, optimizer, epoch, step, config, include_optimizer: bool):
36
+ source = model.module if isinstance(model, DistributedDataParallel) else model
37
  payload = {
38
+ "model": source.state_dict(),
39
  "epoch": epoch,
40
  "step": step,
41
  "config": config,
 
58
  if not torch.cuda.is_available():
59
  raise RuntimeError("CUDA is required")
60
 
61
+ rank, local_rank, world_size = distributed_context()
62
+ device = torch.device("cuda", local_rank)
63
+ torch.cuda.set_device(device)
64
  config_path = Path(args.config).resolve()
65
  config = load_config(config_path)
66
  training = config["training"]
67
+ seed = int(training.get("seed", 0)) + rank
68
  random.seed(seed)
69
  np.random.seed(seed)
70
  torch.manual_seed(seed)
71
  torch.cuda.manual_seed_all(seed)
72
  output = resolve_path(ROOT, config["output"]["directory"])
73
  checkpoint_dir = output / "checkpoints"
74
+ if rank == 0:
75
+ checkpoint_dir.mkdir(parents=True, exist_ok=True)
76
+ (output / "config.yaml").write_text(config_path.read_text(encoding="utf-8"), encoding="utf-8")
77
+ if world_size > 1:
78
+ dist.barrier()
79
 
80
  dataset = TwoConditionDataset(
81
  resolve_path(ROOT, config["data"]["train_h5"]),
82
  cache_in_memory=bool(training.get("cache_in_memory", False)),
83
  )
84
+ global_batch = int(training["global_batch_size"])
85
+ if global_batch % world_size:
86
+ raise ValueError(f"global_batch_size must be divisible by world_size ({world_size})")
87
+ local_batch = global_batch // world_size
88
+ sampler = DistributedSampler(dataset, shuffle=True, seed=int(training.get("seed", 0))) if world_size > 1 else None
89
  loader = DataLoader(
90
  dataset,
91
+ batch_size=local_batch,
92
+ shuffle=sampler is None,
93
+ sampler=sampler,
94
  num_workers=int(training.get("num_workers", 0)),
95
  pin_memory=True,
96
  drop_last=True,
 
143
  for name, parameter in model.named_parameters():
144
  parameter.requires_grad_(name.startswith(train_prefixes))
145
  trainable = [parameter for parameter in model.parameters() if parameter.requires_grad]
146
+ if rank == 0:
147
+ print(f"resume_checkpoint={resume_checkpoint}")
148
+ print(f"world_size={world_size} global_batch={global_batch} per_gpu_batch={local_batch}")
149
+ print(f"control_depth={model.control_depth}")
150
+ print(f"trainable_parameters={sum(p.numel() for p in trainable):,}")
151
+ print(f"frozen_parameters={sum(p.numel() for p in model.parameters() if not p.requires_grad):,}")
152
+
153
+ model.to(device)
154
+ if world_size > 1:
155
+ model = DistributedDataParallel(model, device_ids=[local_rank], output_device=local_rank)
156
  optimizer = torch.optim.AdamW(
157
  trainable,
158
  lr=float(training["learning_rate"]),
 
169
  optimizer.load_state_dict(checkpoint["optimizer"])
170
  elif resume_checkpoint is not None:
171
  print("WARNING: resume checkpoint has no optimizer state; optimizer restarts fresh.")
172
+ del checkpoint
173
  epochs = 1 if args.smoke else (int(args.target_epoch) if args.target_epoch else int(training["epochs"]))
174
  if epochs < start_epoch:
175
  raise ValueError(f"target epoch {epochs} is before checkpoint epoch {start_epoch}")
176
  optimizer.zero_grad(set_to_none=True)
177
  for epoch in range(start_epoch, epochs):
178
+ if sampler is not None:
179
+ sampler.set_epoch(epoch)
180
  model.train()
181
  total_loss = 0.0
182
  for batch_index, (waveforms, labels) in enumerate(
183
  tqdm(
184
  loader,
185
  desc=f"epoch {epoch + 1}/{epochs}",
186
+ disable=rank != 0 or not bool(training.get("progress_bar", True)),
187
  )
188
  ):
189
+ waveforms = waveforms.to(device, non_blocking=True)
190
+ labels = labels.to(device, non_blocking=True)
191
  timesteps = torch.randint(
192
+ 0, diffusion.num_timesteps, (waveforms.shape[0],), device=device
193
  )
194
  with torch.autocast("cuda", enabled=use_amp, dtype=torch.float16):
195
  loss = diffusion.training_losses(
 
208
  if args.smoke:
209
  break
210
  batches = 1 if args.smoke else len(loader)
211
+ mean_loss = torch.tensor(total_loss / batches, device=device)
212
+ if world_size > 1:
213
+ dist.all_reduce(mean_loss, op=dist.ReduceOp.SUM)
214
+ mean_loss /= world_size
215
+ if rank == 0:
216
+ print(f"epoch={epoch + 1} loss={mean_loss.item():.6f}")
217
  interval = int(config["output"].get("checkpoint_every_epochs", 10))
218
+ if rank == 0 and not args.smoke and ((epoch + 1) % interval == 0 or epoch + 1 == epochs):
219
  save_checkpoint(
220
  checkpoint_dir / f"checkpoint_epoch_{epoch + 1:05d}.pt",
221
  model,
 
225
  config,
226
  include_optimizer=bool(config["output"].get("save_optimizer_state", True)),
227
  )
228
+ if world_size > 1:
229
+ dist.barrier()
230
  dataset.close()
231
+ if world_size > 1:
232
+ dist.destroy_process_group()
233
 
234
 
235
  if __name__ == "__main__":
generate.py CHANGED
@@ -1,12 +1,14 @@
1
  from __future__ import annotations
2
 
3
  import argparse
 
4
  import sys
5
  from pathlib import Path
6
 
7
  import h5py
8
  import numpy as np
9
  import torch
 
10
  from tqdm import tqdm
11
 
12
  PROJECT_ROOT = Path(__file__).resolve().parent.parent
@@ -19,6 +21,19 @@ from diffusion import create_diffusion
19
  from models import EmptyConditionSWaG, SWaG
20
 
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  def main() -> None:
24
  parser = argparse.ArgumentParser(description="Generate waveforms with SWaG")
@@ -40,52 +55,90 @@ def main() -> None:
40
  if not torch.cuda.is_available():
41
  raise RuntimeError("CUDA is required for sampling")
42
 
 
 
 
 
 
 
43
  checkpoint_path = resolve_path(PROJECT_ROOT, str(args.checkpoint))
44
  output_path = resolve_path(PROJECT_ROOT, str(args.output))
45
  checkpoint = torch.load(checkpoint_path, map_location="cpu", weights_only=False)
46
  config = checkpoint["config"]
47
  model_class = EmptyConditionSWaG if int(config["model"].get("condition_slot_count", 0)) == 8 else SWaG
48
- model = model_class(**config["model"]).cuda().eval()
49
  model.load_state_dict(checkpoint["ema"], strict=True)
 
50
  diffusion_config = dict(config["diffusion"])
51
  respacing = f"ddim{args.sampling_steps}" if args.sampler == "ddim" else str(args.sampling_steps)
52
  diffusion = create_diffusion(timestep_respacing=respacing, **diffusion_config)
53
- generator = torch.Generator(device="cuda").manual_seed(args.seed)
54
- output_path.parent.mkdir(parents=True, exist_ok=True)
 
 
 
 
 
55
  shape = (args.num_samples, int(config["model"]["length"]), int(config["model"]["in_channels"]))
56
- with h5py.File(output_path, "w") as handle:
57
- output = handle.create_dataset("data", shape=shape, dtype="float32", compression="gzip", shuffle=True)
58
- output.attrs["checkpoint"] = str(checkpoint_path)
59
- output.attrs["sampling_steps"] = args.sampling_steps
60
- output.attrs["sampler"] = args.sampler
61
- output.attrs["seed"] = args.seed
62
- output.attrs["clip_denoised"] = args.clip_denoised
63
- for start in tqdm(range(0, args.num_samples, args.batch_size), desc="sampling"):
64
- count = min(args.batch_size, args.num_samples - start)
 
 
65
  noise = torch.randn(
66
  count,
67
  int(config["model"]["in_channels"]),
68
  int(config["model"]["length"]),
69
- device="cuda",
70
  generator=generator,
71
  )
72
  sample_loop = diffusion.ddim_sample_loop if args.sampler == "ddim" else diffusion.p_sample_loop
73
  model_kwargs = None
74
  if int(config["model"].get("condition_slot_count", 0)) == 8:
75
  model_kwargs = {
76
- "conditions": torch.zeros(count, 8, device="cuda", dtype=torch.float32)
77
  }
78
  samples = sample_loop(
79
  model,
80
  noise.shape,
81
  noise=noise,
82
- device=torch.device("cuda"),
83
  progress=False,
84
  clip_denoised=args.clip_denoised,
85
  model_kwargs=model_kwargs,
86
  )
87
  output[start : start + count] = samples.transpose(1, 2).cpu().numpy().astype(np.float32)
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
  if __name__ == "__main__":
91
  main()
 
1
  from __future__ import annotations
2
 
3
  import argparse
4
+ import os
5
  import sys
6
  from pathlib import Path
7
 
8
  import h5py
9
  import numpy as np
10
  import torch
11
+ import torch.distributed as dist
12
  from tqdm import tqdm
13
 
14
  PROJECT_ROOT = Path(__file__).resolve().parent.parent
 
21
  from models import EmptyConditionSWaG, SWaG
22
 
23
 
24
+ def distributed_context() -> tuple[int, int, int]:
25
+ world_size = int(os.environ.get("WORLD_SIZE", "1"))
26
+ rank = int(os.environ.get("RANK", "0"))
27
+ local_rank = int(os.environ.get("LOCAL_RANK", "0"))
28
+ if world_size > 1:
29
+ dist.init_process_group("nccl")
30
+ return rank, local_rank, world_size
31
+
32
+
33
+ def shard_path(output_path: Path, rank: int) -> Path:
34
+ return output_path.with_name(f".{output_path.name}.rank{rank}.partial.h5")
35
+
36
+
37
 
38
  def main() -> None:
39
  parser = argparse.ArgumentParser(description="Generate waveforms with SWaG")
 
55
  if not torch.cuda.is_available():
56
  raise RuntimeError("CUDA is required for sampling")
57
 
58
+ rank, local_rank, world_size = distributed_context()
59
+ device = torch.device("cuda", local_rank)
60
+ torch.cuda.set_device(device)
61
+ if args.batch_size % world_size:
62
+ raise ValueError(f"batch-size must be divisible by world size ({world_size})")
63
+ local_batch = args.batch_size // world_size
64
  checkpoint_path = resolve_path(PROJECT_ROOT, str(args.checkpoint))
65
  output_path = resolve_path(PROJECT_ROOT, str(args.output))
66
  checkpoint = torch.load(checkpoint_path, map_location="cpu", weights_only=False)
67
  config = checkpoint["config"]
68
  model_class = EmptyConditionSWaG if int(config["model"].get("condition_slot_count", 0)) == 8 else SWaG
69
+ model = model_class(**config["model"]).to(device).eval()
70
  model.load_state_dict(checkpoint["ema"], strict=True)
71
+ del checkpoint
72
  diffusion_config = dict(config["diffusion"])
73
  respacing = f"ddim{args.sampling_steps}" if args.sampler == "ddim" else str(args.sampling_steps)
74
  diffusion = create_diffusion(timestep_respacing=respacing, **diffusion_config)
75
+ generator = torch.Generator(device=device).manual_seed(args.seed + rank)
76
+ if rank == 0:
77
+ output_path.parent.mkdir(parents=True, exist_ok=True)
78
+ for shard_rank in range(world_size):
79
+ shard_path(output_path, shard_rank).unlink(missing_ok=True)
80
+ if world_size > 1:
81
+ dist.barrier()
82
  shape = (args.num_samples, int(config["model"]["length"]), int(config["model"]["in_channels"]))
83
+ assigned = np.array_split(np.arange(args.num_samples, dtype=np.int64), world_size)[rank]
84
+ local_shape = (len(assigned), shape[1], shape[2])
85
+ with h5py.File(shard_path(output_path, rank), "w") as handle:
86
+ output = handle.create_dataset("data", shape=local_shape, dtype="float32", compression="gzip", shuffle=True)
87
+ handle.create_dataset("indices", data=assigned, dtype="int64")
88
+ for start in tqdm(
89
+ range(0, len(assigned), local_batch),
90
+ desc=f"sampling rank {rank}",
91
+ disable=rank != 0,
92
+ ):
93
+ count = min(local_batch, len(assigned) - start)
94
  noise = torch.randn(
95
  count,
96
  int(config["model"]["in_channels"]),
97
  int(config["model"]["length"]),
98
+ device=device,
99
  generator=generator,
100
  )
101
  sample_loop = diffusion.ddim_sample_loop if args.sampler == "ddim" else diffusion.p_sample_loop
102
  model_kwargs = None
103
  if int(config["model"].get("condition_slot_count", 0)) == 8:
104
  model_kwargs = {
105
+ "conditions": torch.zeros(count, 8, device=device, dtype=torch.float32)
106
  }
107
  samples = sample_loop(
108
  model,
109
  noise.shape,
110
  noise=noise,
111
+ device=device,
112
  progress=False,
113
  clip_denoised=args.clip_denoised,
114
  model_kwargs=model_kwargs,
115
  )
116
  output[start : start + count] = samples.transpose(1, 2).cpu().numpy().astype(np.float32)
117
 
118
+ if world_size > 1:
119
+ dist.barrier()
120
+ if rank == 0:
121
+ partial = output_path.with_name(f".{output_path.name}.partial")
122
+ partial.unlink(missing_ok=True)
123
+ with h5py.File(partial, "w") as handle:
124
+ output = handle.create_dataset("data", shape=shape, dtype="float32", compression="gzip", shuffle=True)
125
+ handle.attrs["checkpoint"] = str(checkpoint_path)
126
+ handle.attrs["sampling_steps"] = args.sampling_steps
127
+ handle.attrs["sampler"] = args.sampler
128
+ handle.attrs["seed"] = args.seed
129
+ handle.attrs["clip_denoised"] = args.clip_denoised
130
+ handle.attrs["world_size"] = world_size
131
+ for shard_rank in range(world_size):
132
+ current = shard_path(output_path, shard_rank)
133
+ with h5py.File(current, "r") as shard:
134
+ indices = np.asarray(shard["indices"], dtype=np.int64)
135
+ output[indices] = shard["data"][:]
136
+ current.unlink()
137
+ partial.replace(output_path)
138
+ if world_size > 1:
139
+ dist.barrier()
140
+ dist.destroy_process_group()
141
+
142
 
143
  if __name__ == "__main__":
144
  main()
run_all_local.sh CHANGED
@@ -4,5 +4,4 @@ set -euo pipefail
4
  SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
5
 
6
  bash "${SCRIPT_DIR}/run_download_prepare.sh"
7
- bash "${SCRIPT_DIR}/run_train_local.sh"
8
-
 
4
  SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
5
 
6
  bash "${SCRIPT_DIR}/run_download_prepare.sh"
7
+ bash "${SCRIPT_DIR}/run_train_local.sh" "$@"
 
run_generate_100.sh CHANGED
@@ -7,14 +7,38 @@ PYTHON_BIN="${PYTHON_BIN:-python}"
7
  CHECKPOINT="${CHECKPOINT:-${SCRIPT_DIR}/results/stead_empty8_local/checkpoints/checkpoint_epoch_00015.pt}"
8
  RESULT_DIR="${RESULT_DIR:-${SCRIPT_DIR}/results/stead_empty8_local}"
9
  OUTPUT="${OUTPUT:-${RESULT_DIR}/generated_100/generated_ddpm25_seed2026.h5}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  cd "${PROJECT_ROOT}"
12
  [[ -f "${CHECKPOINT}" ]] || { echo "Missing checkpoint: ${CHECKPOINT}" >&2; exit 1; }
13
- exec "${PYTHON_BIN}" Train/generate.py \
14
- --checkpoint "${CHECKPOINT}" \
15
- --output "${OUTPUT}" \
16
- --num-samples 100 \
17
- --batch-size 256 \
18
- --sampling-steps 25 \
19
- --sampler ddpm \
 
20
  --seed 2026
 
 
 
 
 
 
 
 
 
 
7
  CHECKPOINT="${CHECKPOINT:-${SCRIPT_DIR}/results/stead_empty8_local/checkpoints/checkpoint_epoch_00015.pt}"
8
  RESULT_DIR="${RESULT_DIR:-${SCRIPT_DIR}/results/stead_empty8_local}"
9
  OUTPUT="${OUTPUT:-${RESULT_DIR}/generated_100/generated_ddpm25_seed2026.h5}"
10
+ NUM_GPUS="${NUM_GPUS:-8}"
11
+
12
+ while [[ $# -gt 0 ]]; do
13
+ case "$1" in
14
+ --num-gpus)
15
+ [[ $# -ge 2 ]] || { echo "--num-gpus requires a value" >&2; exit 2; }
16
+ NUM_GPUS="$2"
17
+ shift 2
18
+ ;;
19
+ --num-gpus=*) NUM_GPUS="${1#*=}"; shift ;;
20
+ *) echo "Usage: bash Train/run_generate_100.sh [--num-gpus N]" >&2; exit 2 ;;
21
+ esac
22
+ done
23
+ [[ "${NUM_GPUS}" =~ ^[1-9][0-9]*$ ]] || { echo "NUM_GPUS must be a positive integer" >&2; exit 2; }
24
 
25
  cd "${PROJECT_ROOT}"
26
  [[ -f "${CHECKPOINT}" ]] || { echo "Missing checkpoint: ${CHECKPOINT}" >&2; exit 1; }
27
+ ARGS=(
28
+ Train/generate.py
29
+ --checkpoint "${CHECKPOINT}"
30
+ --output "${OUTPUT}"
31
+ --num-samples 100
32
+ --batch-size 256
33
+ --sampling-steps 25
34
+ --sampler ddpm
35
  --seed 2026
36
+ )
37
+ echo "Generating with ${NUM_GPUS} GPU(s); global generation batch size is 256"
38
+ if [[ "${NUM_GPUS}" -eq 1 ]]; then
39
+ exec "${PYTHON_BIN}" "${ARGS[@]}"
40
+ fi
41
+ exec "${PYTHON_BIN}" -m torch.distributed.run \
42
+ --standalone \
43
+ --nproc_per_node="${NUM_GPUS}" \
44
+ "${ARGS[@]}"
run_train_local.sh CHANGED
@@ -6,6 +6,27 @@ PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
6
  PYTHON_BIN="${PYTHON_BIN:-python}"
7
  CONFIG="${CONFIG:-${SCRIPT_DIR}/configs/stead_empty8_local.yaml}"
8
  TRAIN_H5="${PROJECT_ROOT}/Train/data/processed/from_raw/stead/train/stead_100hz_60s_train.h5"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  cd "${PROJECT_ROOT}"
11
  [[ -f "${TRAIN_H5}" ]] || {
@@ -15,5 +36,11 @@ cd "${PROJECT_ROOT}"
15
  }
16
 
17
  export PYTHONUNBUFFERED=1
18
- exec "${PYTHON_BIN}" Train/training/train.py --config "${CONFIG}"
19
-
 
 
 
 
 
 
 
6
  PYTHON_BIN="${PYTHON_BIN:-python}"
7
  CONFIG="${CONFIG:-${SCRIPT_DIR}/configs/stead_empty8_local.yaml}"
8
  TRAIN_H5="${PROJECT_ROOT}/Train/data/processed/from_raw/stead/train/stead_100hz_60s_train.h5"
9
+ NUM_GPUS="${NUM_GPUS:-8}"
10
+
11
+ while [[ $# -gt 0 ]]; do
12
+ case "$1" in
13
+ --num-gpus)
14
+ [[ $# -ge 2 ]] || { echo "--num-gpus requires a value" >&2; exit 2; }
15
+ NUM_GPUS="$2"
16
+ shift 2
17
+ ;;
18
+ --num-gpus=*)
19
+ NUM_GPUS="${1#*=}"
20
+ shift
21
+ ;;
22
+ *)
23
+ echo "Unknown argument: $1" >&2
24
+ echo "Usage: bash Train/run_train_local.sh [--num-gpus N]" >&2
25
+ exit 2
26
+ ;;
27
+ esac
28
+ done
29
+ [[ "${NUM_GPUS}" =~ ^[1-9][0-9]*$ ]] || { echo "NUM_GPUS must be a positive integer" >&2; exit 2; }
30
 
31
  cd "${PROJECT_ROOT}"
32
  [[ -f "${TRAIN_H5}" ]] || {
 
36
  }
37
 
38
  export PYTHONUNBUFFERED=1
39
+ echo "Training with ${NUM_GPUS} GPU(s); global batch size is read from ${CONFIG}"
40
+ if [[ "${NUM_GPUS}" -eq 1 ]]; then
41
+ exec "${PYTHON_BIN}" Train/training/train.py --config "${CONFIG}"
42
+ fi
43
+ exec "${PYTHON_BIN}" -m torch.distributed.run \
44
+ --standalone \
45
+ --nproc_per_node="${NUM_GPUS}" \
46
+ Train/training/train.py --config "${CONFIG}"
training/train.py CHANGED
@@ -195,8 +195,12 @@ def main() -> None:
195
  step += 1
196
  if rank == 0:
197
  progress.set_postfix(loss=f"{running_loss / (batch_index + 1):.5f}")
 
 
 
 
198
  if rank == 0:
199
- metrics = {"epoch": epoch + 1, "step": step, "loss": running_loss / max(len(loader), 1)}
200
  with (output_dir / "metrics.jsonl").open("a", encoding="utf-8") as handle:
201
  handle.write(json.dumps(metrics) + "\n")
202
  if (epoch + 1) % checkpoint_interval == 0 or epoch + 1 == epochs:
@@ -204,6 +208,8 @@ def main() -> None:
204
  checkpoint_dir / f"checkpoint_epoch_{epoch + 1:05d}.pt",
205
  model, ema, optimizer, epoch + 1, step, config,
206
  )
 
 
207
 
208
  dataset.close()
209
  if world_size > 1:
 
195
  step += 1
196
  if rank == 0:
197
  progress.set_postfix(loss=f"{running_loss / (batch_index + 1):.5f}")
198
+ mean_loss = torch.tensor(running_loss / max(len(loader), 1), device=device)
199
+ if world_size > 1:
200
+ dist.all_reduce(mean_loss, op=dist.ReduceOp.SUM)
201
+ mean_loss /= world_size
202
  if rank == 0:
203
+ metrics = {"epoch": epoch + 1, "step": step, "loss": mean_loss.item()}
204
  with (output_dir / "metrics.jsonl").open("a", encoding="utf-8") as handle:
205
  handle.write(json.dumps(metrics) + "\n")
206
  if (epoch + 1) % checkpoint_interval == 0 or epoch + 1 == epochs:
 
208
  checkpoint_dir / f"checkpoint_epoch_{epoch + 1:05d}.pt",
209
  model, ema, optimizer, epoch + 1, step, config,
210
  )
211
+ if world_size > 1:
212
+ dist.barrier()
213
 
214
  dataset.close()
215
  if world_size > 1: