Skip to content

Commit 2dc84d2

Browse files
authored
Enable missing mixed precision plug for joint training setup (#1302)
* Enable missing mixed precision plug for joint training setup * Pin per model joint training configuration
1 parent 0555bbc commit 2dc84d2

4 files changed

Lines changed: 52 additions & 12 deletions

File tree

finetuning/v2/generalist/train_joint.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,27 @@
44
import torch
55

66

7+
CHOSEN_PARAMETERS = {
8+
"hvit_t": (10, 10, 5),
9+
"hvit_s": (10, 10, 5),
10+
"hvit_b": (8, 10, 5),
11+
"hvit_l": (8, 8, 4),
12+
}
13+
14+
715
def main():
816
parser = argparse.ArgumentParser()
917
parser.add_argument("--n_epochs", type=int, default=100)
1018
parser.add_argument("--n_iterations", type=int, default=None)
19+
parser.add_argument("--model_type", default="hvit_t", choices=["hvit_t", "hvit_s", "hvit_b", "hvit_l"])
20+
parser.add_argument("--batch_size", type=int, default=1)
21+
parser.add_argument("--dataset_choice", default="both", choices=["lm", "em", "both"])
1122
args = parser.parse_args()
1223

13-
model_type = "hvit_t"
24+
model_type = args.model_type
25+
# Pinned per-model config (batch_size_2d, z_slices, max_num_objects); not CLI-tunable.
26+
batch_size_2d, z_slice, max_num_objects = CHOSEN_PARAMETERS[model_type]
27+
z_slices = [z_slice]
1428
data_path = "/mnt/vast-nhr/projects/cidas/cca/data"
1529
save_root = os.environ.get("SAVE_ROOT", "/mnt/vast-nhr/projects/cidas/cca/models/micro_sam2/joint/v0")
1630

@@ -23,17 +37,17 @@ def main():
2337
name=name,
2438
model_type=model_type,
2539
input_path=data_path,
26-
batch_size=1,
27-
batch_size_2d=8,
28-
z_slices=[8],
29-
dataset_choice="both",
40+
batch_size=args.batch_size,
41+
batch_size_2d=batch_size_2d,
42+
z_slices=z_slices,
43+
dataset_choice=args.dataset_choice,
3044
n_workers=8,
3145
n_epochs=args.n_epochs,
3246
n_iterations=args.n_iterations,
3347
lr=1e-5, # single LR for all parameters
3448
save_root=save_root,
3549
checkpoint_path=None, # downloads default SAM2 weights if None
36-
max_num_objects=5, # lower than interactive-only (8): joint also holds the UNETR decoder
50+
max_num_objects=max_num_objects, # lower than interactive-only (8): joint also holds the UNETR decoder
3751
prob_to_use_pt_input=1.0, # always point/box prompts, never the GT mask
3852
prob_to_use_box_input=0.5, # conditional prob of a box instead of a click
3953
num_frames_to_correct=2, # max frames per volume receiving correction clicks
@@ -58,6 +72,17 @@ def main():
5872
from micro_sam.v2.training import train_joint_sam2
5973
train_joint_sam2(**common)
6074

75+
rank = int(os.environ.get("RANK", "0"))
76+
if torch.cuda.is_available() and rank == 0:
77+
peak_alloc = torch.cuda.max_memory_allocated() / 1024**3
78+
peak_reserved = torch.cuda.max_memory_reserved() / 1024**3
79+
print(
80+
f"[peak-memory] model_type={model_type} batch_size={args.batch_size} "
81+
f"batch_size_2d={batch_size_2d} z_slices={z_slices} "
82+
f"max_num_objects={max_num_objects} "
83+
f"allocated={peak_alloc:.2f}GiB reserved={peak_reserved:.2f}GiB", flush=True
84+
)
85+
6186

6287
if __name__ == "__main__":
6388
main()

micro_sam/v2/training/sam2_trainer.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@ def _amp_context(self):
128128
return torch.amp.autocast(device_type="cuda", dtype=self.amp_dtype)
129129
return contextlib.nullcontext()
130130

131+
def _train_epoch(self, progress):
132+
# mixed_precision is disabled on the parent (see __init__), so DefaultTrainer would pass
133+
# contextlib.nullcontext as forward_context. Inject our bf16 autocast instead so every
134+
# _train_epoch_impl (this trainer and JointSam2Trainer) runs the forward pass under AMP.
135+
return self._train_epoch_impl(progress, self._amp_context, self._sam2_backprop)
136+
137+
def _validate(self):
138+
return self._validate_impl(self._amp_context)
139+
131140
def _check_input_normalization(self, x, input_check_done):
132141
if not input_check_done:
133142
data_min, data_max = x.min(), x.max()
@@ -182,7 +191,7 @@ def _train_epoch_impl(self, progress, forward_context, backprop):
182191
input_check_done = self._check_input_normalization(x, input_check_done)
183192
self.optimizer.zero_grad()
184193

185-
with self._amp_context():
194+
with forward_context():
186195
loss, batch, outputs = self._interactive_step(x, y)
187196

188197
grad_norm = self._sam2_backprop(loss)
@@ -235,7 +244,7 @@ def _validate_impl(self, forward_context):
235244
with torch.no_grad():
236245
for i, (x, y) in enumerate(self.val_loader):
237246
input_check_done = self._check_input_normalization(x, input_check_done)
238-
with self._amp_context():
247+
with forward_context():
239248
loss, batch, outputs = self._interactive_step(x, y)
240249
val_loss += loss.item()
241250
val_dice_loss += self.metric(outputs, batch.masks).item()

micro_sam/v2/transforms/labels.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@ def __call__(self, labels: np.ndarray) -> np.ndarray:
132132
labels = labels.byteswap().view(labels.dtype.newbyteorder())
133133

134134
if self.apply_label:
135-
labels = connected_components(labels).astype("uint32")
135+
# Cast to uint32: connected_components rejects int16 and labels fit uint32.
136+
labels = connected_components(labels.astype("uint32")).astype("uint32")
136137
else: # Otherwise just relabel the segmentation.
137138
# Cast to uint32: relabel_sequential rejects uint8/16 and labels fit uint32.
138139
labels = relabel_sequential(labels.astype("uint32"))[0].astype("uint32")

micro_sam/v2/transforms/raw.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -392,10 +392,15 @@ def get_random_percentile_normalization(
392392
"""
393393
augmentation2 = None
394394
if isinstance(raw_transform, RawTransform):
395-
if not isinstance(raw_transform.normalizer, RandomPercentileNormalization):
396-
raise ValueError(f"Unsupported generalist raw transform: {raw_transform!r}")
397395
augmentation1, augmentation2 = raw_transform.augmentation1, raw_transform.augmentation2
398-
axis = raw_transform.normalizer.axis
396+
if isinstance(raw_transform.normalizer, RandomPercentileNormalization):
397+
axis = raw_transform.normalizer.axis
398+
elif raw_transform.normalizer is _identity:
399+
# Defect-augmented EM datasets (CREMI): the defect pipeline lives in augmentation1 with an
400+
# identity normalizer. Preserve it and apply per-channel percentile normalization.
401+
axis = (1, 2)
402+
else:
403+
raise ValueError(f"Unsupported normalizer in generalist raw transform: {raw_transform.normalizer!r}")
399404
elif isinstance(raw_transform, RandomPercentileNormalization):
400405
augmentation1, axis = None, raw_transform.axis
401406
elif raw_transform is _identity:

0 commit comments

Comments
 (0)