Add files using upload-large-folder tool
Browse files- README.md +19 -6
- Transfer_and_Test/README.md +7 -5
- Transfer_and_Test/run_transfer_and_test.sh +40 -5
- Transfer_and_Test/src/eqt/evaluate_transfer_eqt_test_in_memory.py +14 -0
- Transfer_and_Test/src/generate_controlnet_two_condition.py +89 -38
- Transfer_and_Test/src/train_controlnet_two_condition.py +60 -18
- generate.py +68 -15
- run_all_local.sh +1 -2
- run_generate_100.sh +31 -7
- run_train_local.sh +29 -2
- training/train.py +7 -1
README.md
CHANGED
|
@@ -97,17 +97,22 @@ bash Train/run_download_prepare.sh
|
|
| 97 |
|
| 98 |
## Training
|
| 99 |
|
| 100 |
-
The default runnable configuration uses a
|
| 101 |
-
no gradient accumulation, 15 epochs, and saves
|
|
|
|
| 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
|
| 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 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
the
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 56 |
-
echo "[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 45 |
-
|
| 46 |
-
torch.
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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"]).
|
| 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=
|
| 72 |
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
| 77 |
shape = (args.num_samples, int(config["model"]["length"]), int(config["model"]["in_channels"]))
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
handle.
|
| 82 |
-
|
| 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(
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
noise = torch.randn(
|
| 93 |
end - start,
|
| 94 |
shape[2],
|
| 95 |
shape[1],
|
| 96 |
-
device=
|
| 97 |
generator=generator,
|
| 98 |
)
|
| 99 |
|
|
@@ -106,25 +131,51 @@ def main() -> None:
|
|
| 106 |
model_fn,
|
| 107 |
noise.shape,
|
| 108 |
noise=noise,
|
| 109 |
-
device=
|
| 110 |
progress=False,
|
| 111 |
clip_denoised=False,
|
| 112 |
)
|
| 113 |
output[start:end] = samples.float().transpose(1, 2).cpu().numpy().astype(np.float32)
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
)
|
| 126 |
-
|
| 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":
|
| 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 |
-
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
| 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=
|
| 67 |
-
shuffle=
|
|
|
|
| 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 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 157 |
-
labels = labels.
|
| 158 |
timesteps = torch.randint(
|
| 159 |
-
0, diffusion.num_timesteps, (waveforms.shape[0],), device=
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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"]).
|
| 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=
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
shape = (args.num_samples, int(config["model"]["length"]), int(config["model"]["in_channels"]))
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
output.
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
| 65 |
noise = torch.randn(
|
| 66 |
count,
|
| 67 |
int(config["model"]["in_channels"]),
|
| 68 |
int(config["model"]["length"]),
|
| 69 |
-
device=
|
| 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=
|
| 77 |
}
|
| 78 |
samples = sample_loop(
|
| 79 |
model,
|
| 80 |
noise.shape,
|
| 81 |
noise=noise,
|
| 82 |
-
device=
|
| 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 |
-
|
| 14 |
-
|
| 15 |
-
--
|
| 16 |
-
--
|
| 17 |
-
--
|
| 18 |
-
--
|
| 19 |
-
--
|
|
|
|
| 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 |
-
|
| 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":
|
| 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:
|