moozy 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- moozy/__init__.py +0 -0
- moozy/__main__.py +4 -0
- moozy/cli/__init__.py +3 -0
- moozy/cli/_types.py +65 -0
- moozy/cli/encode.py +47 -0
- moozy/cli/main.py +16 -0
- moozy/cli/train/__init__.py +29 -0
- moozy/cli/train/_stage1.py +317 -0
- moozy/cli/train/_stage2.py +273 -0
- moozy/config/__init__.py +0 -0
- moozy/config/data.py +60 -0
- moozy/config/model.py +54 -0
- moozy/config/stage1.py +133 -0
- moozy/config/stage2.py +123 -0
- moozy/config/tasks.py +28 -0
- moozy/config/training.py +61 -0
- moozy/data/__init__.py +1 -0
- moozy/data/features/__init__.py +26 -0
- moozy/data/features/grid.py +50 -0
- moozy/data/features/index.py +39 -0
- moozy/data/features/io.py +187 -0
- moozy/data/features/token_cap.py +91 -0
- moozy/data/features/transforms.py +83 -0
- moozy/data/stage1/__init__.py +14 -0
- moozy/data/stage1/collate.py +61 -0
- moozy/data/stage1/crops.py +63 -0
- moozy/data/stage1/dataset.py +326 -0
- moozy/data/stage1/loader.py +56 -0
- moozy/data/stage1/masking.py +127 -0
- moozy/data/stage1/transforms.py +32 -0
- moozy/data/stage1/types.py +25 -0
- moozy/data/stage2/__init__.py +16 -0
- moozy/data/stage2/batching.py +31 -0
- moozy/data/stage2/dataset.py +206 -0
- moozy/data/stage2/loader.py +30 -0
- moozy/data/stage2/transforms.py +158 -0
- moozy/data/stage2/types.py +35 -0
- moozy/encoding.py +301 -0
- moozy/hf_hub.py +19 -0
- moozy/models/__init__.py +0 -0
- moozy/models/case_transformer.py +219 -0
- moozy/models/encoder.py +42 -0
- moozy/models/factory.py +222 -0
- moozy/models/heads.py +100 -0
- moozy/models/layers.py +43 -0
- moozy/models/moozy_slide_encoder.py +420 -0
- moozy/models/serialization.py +236 -0
- moozy/models/stage1_encoding.py +202 -0
- moozy/models/stage1_ssl.py +252 -0
- moozy/models/stage2_encoding.py +82 -0
- moozy/models/stage2_supervised.py +159 -0
- moozy/models/stage2_tasks.py +96 -0
- moozy/models/variants.py +50 -0
- moozy/py.typed +0 -0
- moozy/tasks/__init__.py +18 -0
- moozy/tasks/coverage.py +41 -0
- moozy/tasks/loader.py +308 -0
- moozy/tasks/matrices.py +63 -0
- moozy/tasks/resolution.py +41 -0
- moozy/tasks/splits.py +117 -0
- moozy/tasks/survival.py +103 -0
- moozy/training/__init__.py +41 -0
- moozy/training/callbacks/__init__.py +15 -0
- moozy/training/callbacks/checkpoint.py +307 -0
- moozy/training/callbacks/logging.py +230 -0
- moozy/training/engine/__init__.py +9 -0
- moozy/training/engine/base.py +39 -0
- moozy/training/engine/stage1.py +302 -0
- moozy/training/engine/stage2.py +530 -0
- moozy/training/logging.py +119 -0
- moozy/training/loss/__init__.py +27 -0
- moozy/training/loss/classification.py +45 -0
- moozy/training/loss/distillation.py +96 -0
- moozy/training/loss/survival.py +124 -0
- moozy/training/metrics.py +110 -0
- moozy/training/optimization.py +227 -0
- moozy/training/runners/__init__.py +7 -0
- moozy/training/runners/stage1.py +439 -0
- moozy/training/runners/stage2.py +472 -0
- moozy/training/runtime.py +243 -0
- moozy-0.1.0.dist-info/METADATA +685 -0
- moozy-0.1.0.dist-info/RECORD +86 -0
- moozy-0.1.0.dist-info/WHEEL +5 -0
- moozy-0.1.0.dist-info/entry_points.txt +2 -0
- moozy-0.1.0.dist-info/licenses/LICENSE +437 -0
- moozy-0.1.0.dist-info/top_level.txt +1 -0
moozy/__init__.py
ADDED
|
File without changes
|
moozy/__main__.py
ADDED
moozy/cli/__init__.py
ADDED
moozy/cli/_types.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class H5Format(str, Enum):
|
|
5
|
+
"""Feature H5 schema to load."""
|
|
6
|
+
|
|
7
|
+
auto = "auto"
|
|
8
|
+
trident = "trident"
|
|
9
|
+
atlaspatch = "atlaspatch"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Schedule(str, Enum):
|
|
13
|
+
"""Learning-rate or momentum schedule."""
|
|
14
|
+
|
|
15
|
+
linear = "linear"
|
|
16
|
+
cosine = "cosine"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class WDSchedule(str, Enum):
|
|
20
|
+
"""Weight-decay schedule (includes *none* to disable)."""
|
|
21
|
+
|
|
22
|
+
linear = "linear"
|
|
23
|
+
cosine = "cosine"
|
|
24
|
+
none = "none"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class OptimizerChoice(str, Enum):
|
|
28
|
+
"""Optimizer algorithm."""
|
|
29
|
+
|
|
30
|
+
adamw = "adamw"
|
|
31
|
+
adam = "adam"
|
|
32
|
+
sgd = "sgd"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Backend(str, Enum):
|
|
36
|
+
"""Distributed training backend."""
|
|
37
|
+
|
|
38
|
+
nccl = "nccl"
|
|
39
|
+
gloo = "gloo"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class NormType(str, Enum):
|
|
43
|
+
"""Normalization type for projection head layers."""
|
|
44
|
+
|
|
45
|
+
none = "none"
|
|
46
|
+
ln = "ln"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class HeadType(str, Enum):
|
|
50
|
+
"""Task-head architecture."""
|
|
51
|
+
|
|
52
|
+
linear = "linear"
|
|
53
|
+
mlp = "mlp"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class TokenCapSampling(str, Enum):
|
|
57
|
+
"""Sampling strategy when a slide exceeds the valid-token cap."""
|
|
58
|
+
|
|
59
|
+
deterministic = "deterministic"
|
|
60
|
+
random_stratified = "random_stratified"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def enum_val(v: object) -> str:
|
|
64
|
+
"""Return the plain string value of a CLI enum, or ``str(v)`` for non-enums."""
|
|
65
|
+
return v.value if isinstance(v, Enum) else str(v)
|
moozy/cli/encode.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def encode_command(
|
|
7
|
+
slides: Annotated[
|
|
8
|
+
list[str],
|
|
9
|
+
typer.Argument(
|
|
10
|
+
help="H5 feature files or raw slides (.svs, .tiff, etc.) to process as a single case.",
|
|
11
|
+
),
|
|
12
|
+
],
|
|
13
|
+
output: Annotated[
|
|
14
|
+
str,
|
|
15
|
+
typer.Option("--output", "-o", help="Output H5 path for the case embedding."),
|
|
16
|
+
],
|
|
17
|
+
mixed_precision: Annotated[
|
|
18
|
+
bool,
|
|
19
|
+
typer.Option("--mixed_precision/--no_mixed_precision", help="Enable bf16 autocast."),
|
|
20
|
+
] = False,
|
|
21
|
+
target_mag: Annotated[
|
|
22
|
+
int,
|
|
23
|
+
typer.Option("--target_mag", help="Target magnification for patching (raw slides only)."),
|
|
24
|
+
] = 20,
|
|
25
|
+
step_size: Annotated[
|
|
26
|
+
int,
|
|
27
|
+
typer.Option("--step_size", help="Stride between patches in pixels (raw slides only)."),
|
|
28
|
+
] = 224,
|
|
29
|
+
mpp_csv: Annotated[
|
|
30
|
+
str,
|
|
31
|
+
typer.Option("--mpp_csv", help="CSV with wsi,mpp columns for microns-per-pixel override (raw slides only)."),
|
|
32
|
+
] = "",
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Encode slides into a case-level embedding with a MOOZY checkpoint."""
|
|
35
|
+
import logging
|
|
36
|
+
|
|
37
|
+
from moozy.encoding import run_encoding
|
|
38
|
+
|
|
39
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
|
40
|
+
run_encoding(
|
|
41
|
+
slide_paths=list(slides),
|
|
42
|
+
output_path=output,
|
|
43
|
+
mixed_precision=mixed_precision,
|
|
44
|
+
target_mag=target_mag,
|
|
45
|
+
step_size=step_size,
|
|
46
|
+
mpp_csv=mpp_csv or None,
|
|
47
|
+
)
|
moozy/cli/main.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from .encode import encode_command
|
|
4
|
+
from .train import app as train_app
|
|
5
|
+
|
|
6
|
+
app = typer.Typer(
|
|
7
|
+
add_completion=False,
|
|
8
|
+
no_args_is_help=True,
|
|
9
|
+
help="MOOZY command-line interface.",
|
|
10
|
+
)
|
|
11
|
+
app.add_typer(train_app, name="train")
|
|
12
|
+
app.command(name="encode", help="Encode slides into a case-level embedding.")(encode_command)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main() -> None:
|
|
16
|
+
app()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from ._stage1 import stage1_command
|
|
4
|
+
from ._stage2 import stage2_command
|
|
5
|
+
|
|
6
|
+
app = typer.Typer(
|
|
7
|
+
add_completion=False,
|
|
8
|
+
help="Train a MOOZY model.",
|
|
9
|
+
invoke_without_command=True,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.callback()
|
|
14
|
+
def _train_callback(ctx: typer.Context) -> None:
|
|
15
|
+
"""Show help when invoked without a sub-command."""
|
|
16
|
+
if ctx.invoked_subcommand is None:
|
|
17
|
+
typer.echo(ctx.get_help(), nl=False)
|
|
18
|
+
raise typer.Exit(0)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Primary commands
|
|
22
|
+
app.command(name="stage1")(stage1_command)
|
|
23
|
+
app.command(name="stage2")(stage2_command)
|
|
24
|
+
|
|
25
|
+
# Hidden aliases so users can type ``moozy train 1``, ``moozy train stage-1``, etc.
|
|
26
|
+
for _alias in ("1", "stage-1", "stage_1"):
|
|
27
|
+
app.command(name=_alias, hidden=True)(stage1_command)
|
|
28
|
+
for _alias in ("2", "stage-2", "stage_2"):
|
|
29
|
+
app.command(name=_alias, hidden=True)(stage2_command)
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from .._types import Backend, H5Format, NormType, OptimizerChoice, Schedule, WDSchedule, enum_val
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def stage1_command(
|
|
9
|
+
feature_dirs: Annotated[
|
|
10
|
+
list[str],
|
|
11
|
+
typer.Option("--feature_dirs", help="Directories containing h5 feature files."),
|
|
12
|
+
],
|
|
13
|
+
feature_h5_format: Annotated[
|
|
14
|
+
H5Format,
|
|
15
|
+
typer.Option("--feature_h5_format", help="Feature file H5 schema to load."),
|
|
16
|
+
] = H5Format.auto,
|
|
17
|
+
feature_h5_key: Annotated[
|
|
18
|
+
str,
|
|
19
|
+
typer.Option("--feature_h5_key", help="Optional AtlasPatch feature key under features/."),
|
|
20
|
+
] = "",
|
|
21
|
+
batch_size: Annotated[int, typer.Option("--batch_size", help="Batch size for training.")] = 64,
|
|
22
|
+
num_workers: Annotated[int, typer.Option("--num_workers", help="Number of data-loading workers.")] = 4,
|
|
23
|
+
prefetch_factor: Annotated[int, typer.Option("--prefetch_factor", help="Batches to prefetch per worker.")] = 4,
|
|
24
|
+
lazy_feature_loading: Annotated[
|
|
25
|
+
bool,
|
|
26
|
+
typer.Option("--lazy_feature_loading/--no_lazy_feature_loading", help="Load feature grids on demand."),
|
|
27
|
+
] = False,
|
|
28
|
+
max_cached_slides: Annotated[
|
|
29
|
+
int,
|
|
30
|
+
typer.Option("--max_cached_slides", help="Max lazily-decoded slides to cache per process (0 disables)."),
|
|
31
|
+
] = 0,
|
|
32
|
+
global_crop_size: Annotated[int, typer.Option("--global_crop_size", help="Size of global crops.")] = 20,
|
|
33
|
+
local_crop_size: Annotated[int, typer.Option("--local_crop_size", help="Size of local crops.")] = 12,
|
|
34
|
+
num_global_crops: Annotated[int, typer.Option("--num_global_crops", help="Global crops per sample.")] = 2,
|
|
35
|
+
num_local_crops: Annotated[int, typer.Option("--num_local_crops", help="Local crops per sample.")] = 4,
|
|
36
|
+
mask_ratio_min: Annotated[
|
|
37
|
+
float, typer.Option("--mask_ratio_min", help="Minimum mask ratio per masked global crop.")
|
|
38
|
+
] = 0.1,
|
|
39
|
+
mask_ratio_max: Annotated[
|
|
40
|
+
float, typer.Option("--mask_ratio_max", help="Maximum mask ratio per masked global crop.")
|
|
41
|
+
] = 0.5,
|
|
42
|
+
min_num_mask_patches: Annotated[
|
|
43
|
+
int, typer.Option("--min_num_mask_patches", help="Min patches per masking block.")
|
|
44
|
+
] = 4,
|
|
45
|
+
max_num_mask_patches: Annotated[
|
|
46
|
+
int, typer.Option("--max_num_mask_patches", help="Max patches per masking block (-1 disables cap).")
|
|
47
|
+
] = -1,
|
|
48
|
+
mask_min_aspect: Annotated[
|
|
49
|
+
float, typer.Option("--mask_min_aspect", help="Min aspect ratio for block-masking rectangles.")
|
|
50
|
+
] = 0.3,
|
|
51
|
+
mask_max_aspect: Annotated[
|
|
52
|
+
float | None,
|
|
53
|
+
typer.Option("--mask_max_aspect", help="Max aspect ratio (defaults to 1/mask_min_aspect)."),
|
|
54
|
+
] = None,
|
|
55
|
+
mask_sample_probability: Annotated[
|
|
56
|
+
float,
|
|
57
|
+
typer.Option("--mask_sample_probability", help="Probability of masking each global crop."),
|
|
58
|
+
] = 0.5,
|
|
59
|
+
min_window_patch_ratio: Annotated[
|
|
60
|
+
float,
|
|
61
|
+
typer.Option("--min_window_patch_ratio", help="Min fraction of non-zero patches per crop."),
|
|
62
|
+
] = 0.25,
|
|
63
|
+
crop_resample_attempts: Annotated[
|
|
64
|
+
int, typer.Option("--crop_resample_attempts", help="Attempts to resample a sparse crop.")
|
|
65
|
+
] = 3,
|
|
66
|
+
hflip_prob: Annotated[float, typer.Option("--hflip_prob", help="Horizontal flip probability.")] = 0.5,
|
|
67
|
+
vflip_prob: Annotated[float, typer.Option("--vflip_prob", help="Vertical flip probability.")] = 0.5,
|
|
68
|
+
rotate_prob: Annotated[float, typer.Option("--rotate_prob", help="Probability of 90/180/270° rotation.")] = 0.5,
|
|
69
|
+
encoder_variant: Annotated[
|
|
70
|
+
str,
|
|
71
|
+
typer.Option("--encoder_variant", help="Encoder variant preset (e.g. base_half_depth, tiny, large)."),
|
|
72
|
+
] = "base_half_depth",
|
|
73
|
+
output_dim: Annotated[int, typer.Option("--output_dim", help="Output dimension of the projection head.")] = 8192,
|
|
74
|
+
proj_hidden_dim: Annotated[
|
|
75
|
+
int, typer.Option("--proj_hidden_dim", help="Hidden dimension of the projection MLP.")
|
|
76
|
+
] = 2048,
|
|
77
|
+
proj_bottleneck_dim: Annotated[
|
|
78
|
+
int, typer.Option("--proj_bottleneck_dim", help="Bottleneck dimension before final weight-normalised layer.")
|
|
79
|
+
] = 256,
|
|
80
|
+
proj_norm_last_layer: Annotated[
|
|
81
|
+
bool,
|
|
82
|
+
typer.Option(
|
|
83
|
+
"--proj_norm_last_layer/--no_proj_norm_last_layer",
|
|
84
|
+
help="Freeze weight_norm gain in the projection head's last layer.",
|
|
85
|
+
),
|
|
86
|
+
] = True,
|
|
87
|
+
proj_norm: Annotated[
|
|
88
|
+
NormType,
|
|
89
|
+
typer.Option("--proj_norm", help="Normalisation inside projection MLP after fc1/fc2."),
|
|
90
|
+
] = NormType.none,
|
|
91
|
+
proj_last_norm: Annotated[
|
|
92
|
+
NormType,
|
|
93
|
+
typer.Option("--proj_last_norm", help="Optional normalisation after the final projection layer."),
|
|
94
|
+
] = NormType.none,
|
|
95
|
+
num_registers: Annotated[
|
|
96
|
+
int, typer.Option("--num_registers", help="Register tokens prepended after CLS (0 disables).")
|
|
97
|
+
] = 4,
|
|
98
|
+
layer_drop: Annotated[float, typer.Option("--layer_drop", help="Stochastic depth (layer drop) rate.")] = 0.1,
|
|
99
|
+
dropout: Annotated[float, typer.Option("--dropout", help="MLP dropout rate inside transformer blocks.")] = 0.1,
|
|
100
|
+
attn_dropout: Annotated[float, typer.Option("--attn_dropout", help="Attention dropout probability.")] = 0.0,
|
|
101
|
+
qk_norm: Annotated[
|
|
102
|
+
bool, typer.Option("--qk_norm/--no_qk_norm", help="Per-head LayerNorm on q/k projections.")
|
|
103
|
+
] = False,
|
|
104
|
+
layerscale_init: Annotated[
|
|
105
|
+
float, typer.Option("--layerscale_init", help="Initial LayerScale gamma (<=0 disables).")
|
|
106
|
+
] = 0.0,
|
|
107
|
+
learnable_alibi: Annotated[
|
|
108
|
+
bool,
|
|
109
|
+
typer.Option("--learnable_alibi/--no_learnable_alibi", help="Make ALiBi slopes trainable."),
|
|
110
|
+
] = False,
|
|
111
|
+
ema_momentum_start: Annotated[
|
|
112
|
+
float, typer.Option("--ema_momentum_start", help="Initial EMA momentum for teacher update.")
|
|
113
|
+
] = 0.996,
|
|
114
|
+
ema_momentum: Annotated[float, typer.Option("--ema_momentum", help="Final EMA momentum for teacher update.")] = 1.0,
|
|
115
|
+
momentum_schedule: Annotated[
|
|
116
|
+
Schedule, typer.Option("--momentum_schedule", help="Teacher EMA momentum schedule.")
|
|
117
|
+
] = Schedule.cosine,
|
|
118
|
+
student_temp: Annotated[float, typer.Option("--student_temp", help="Student temperature.")] = 0.1,
|
|
119
|
+
teacher_temp: Annotated[float, typer.Option("--teacher_temp", help="Final teacher CLS temperature.")] = 0.07,
|
|
120
|
+
teacher_patch_temp: Annotated[
|
|
121
|
+
float, typer.Option("--teacher_patch_temp", help="Final teacher patch temperature.")
|
|
122
|
+
] = 0.07,
|
|
123
|
+
warmup_teacher_temp: Annotated[
|
|
124
|
+
float, typer.Option("--warmup_teacher_temp", help="Starting teacher CLS temperature during warmup.")
|
|
125
|
+
] = 0.04,
|
|
126
|
+
warmup_teacher_patch_temp: Annotated[
|
|
127
|
+
float, typer.Option("--warmup_teacher_patch_temp", help="Starting teacher patch temperature during warmup.")
|
|
128
|
+
] = 0.04,
|
|
129
|
+
warmup_teacher_temp_epochs: Annotated[
|
|
130
|
+
int, typer.Option("--warmup_teacher_temp_epochs", help="Epochs to warm up teacher temperatures (0 disables).")
|
|
131
|
+
] = 30,
|
|
132
|
+
center_momentum: Annotated[float, typer.Option("--center_momentum", help="Momentum for center updates.")] = 0.9,
|
|
133
|
+
total_steps: Annotated[
|
|
134
|
+
int,
|
|
135
|
+
typer.Option("--total_steps", help="Total training steps when --epochs is 0 or negative."),
|
|
136
|
+
] = 0,
|
|
137
|
+
epochs: Annotated[
|
|
138
|
+
float,
|
|
139
|
+
typer.Option("--epochs", help="Number of epochs. Positive values override --total_steps. Supports fractional."),
|
|
140
|
+
] = 200,
|
|
141
|
+
lr: Annotated[float, typer.Option("--lr", help="Base learning rate.")] = 5e-4,
|
|
142
|
+
lr_min: Annotated[float, typer.Option("--lr_min", help="Minimum learning rate at end of decay.")] = 2e-6,
|
|
143
|
+
lr_schedule: Annotated[
|
|
144
|
+
Schedule, typer.Option("--lr_schedule", help="LR decay schedule after warmup.")
|
|
145
|
+
] = Schedule.cosine,
|
|
146
|
+
lr_base_batch_size: Annotated[
|
|
147
|
+
int, typer.Option("--lr_base_batch_size", help="Reference global batch size for LR scaling.")
|
|
148
|
+
] = 256,
|
|
149
|
+
weight_decay: Annotated[float, typer.Option("--weight_decay", help="Global weight decay.")] = 0.04,
|
|
150
|
+
weight_decay_start: Annotated[float, typer.Option("--weight_decay_start", help="Starting weight decay.")] = 0.04,
|
|
151
|
+
weight_decay_end: Annotated[float, typer.Option("--weight_decay_end", help="Final weight decay.")] = 0.4,
|
|
152
|
+
wd_schedule: Annotated[
|
|
153
|
+
WDSchedule, typer.Option("--wd_schedule", help="Weight-decay schedule type.")
|
|
154
|
+
] = WDSchedule.cosine,
|
|
155
|
+
warmup_steps: Annotated[int, typer.Option("--warmup_steps", help="Linear warmup steps for LR.")] = 0,
|
|
156
|
+
warmup_epochs: Annotated[
|
|
157
|
+
float,
|
|
158
|
+
typer.Option("--warmup_epochs", help="LR warmup epochs when --warmup_steps is 0."),
|
|
159
|
+
] = 5,
|
|
160
|
+
grad_clip: Annotated[float, typer.Option("--grad_clip", help="Gradient clipping max norm (0 disables).")] = 0.3,
|
|
161
|
+
grad_accumulation_steps: Annotated[
|
|
162
|
+
int, typer.Option("--grad_accumulation_steps", help="Gradient accumulation steps.")
|
|
163
|
+
] = 1,
|
|
164
|
+
freeze_last_layer_steps: Annotated[
|
|
165
|
+
int | None,
|
|
166
|
+
typer.Option("--freeze_last_layer_steps", help="Freeze last projection layer for N steps."),
|
|
167
|
+
] = None,
|
|
168
|
+
freeze_last_layer_epochs: Annotated[
|
|
169
|
+
float,
|
|
170
|
+
typer.Option("--freeze_last_layer_epochs", help="Freeze last projection layer for N epochs."),
|
|
171
|
+
] = 3.0,
|
|
172
|
+
optimizer: Annotated[
|
|
173
|
+
OptimizerChoice, typer.Option("--optimizer", help="Optimiser algorithm.")
|
|
174
|
+
] = OptimizerChoice.adamw,
|
|
175
|
+
log_every: Annotated[int, typer.Option("--log_every", help="Log training loss every N steps.")] = 72,
|
|
176
|
+
val_ratio: Annotated[float, typer.Option("--val_ratio", help="Validation split ratio.")] = 0.05,
|
|
177
|
+
val_every: Annotated[int, typer.Option("--val_every", help="Validate every N steps.")] = 1000,
|
|
178
|
+
mixed_precision: Annotated[
|
|
179
|
+
bool,
|
|
180
|
+
typer.Option("--mixed_precision/--no_mixed_precision", help="Enable mixed-precision training (bf16)."),
|
|
181
|
+
] = False,
|
|
182
|
+
save_every: Annotated[int, typer.Option("--save_every", help="Save checkpoint every N steps (0 disables).")] = 72,
|
|
183
|
+
resume_from: Annotated[str | None, typer.Option("--resume_from", help="Path to checkpoint to resume from.")] = None,
|
|
184
|
+
keep_last_n: Annotated[int, typer.Option("--keep_last_n", help="Keep only last N checkpoints.")] = 50,
|
|
185
|
+
save_teacher: Annotated[
|
|
186
|
+
bool,
|
|
187
|
+
typer.Option("--save_teacher/--no_save_teacher", help="Save teacher-only checkpoint at each save."),
|
|
188
|
+
] = True,
|
|
189
|
+
teacher_save_prefix: Annotated[
|
|
190
|
+
str, typer.Option("--teacher_save_prefix", help="Filename prefix for teacher-only checkpoints.")
|
|
191
|
+
] = "teacher_step",
|
|
192
|
+
distributed: Annotated[
|
|
193
|
+
bool, typer.Option("--distributed/--no_distributed", help="Enable distributed training.")
|
|
194
|
+
] = False,
|
|
195
|
+
backend: Annotated[Backend, typer.Option("--backend", help="Distributed backend.")] = Backend.nccl,
|
|
196
|
+
local_rank: Annotated[int, typer.Option("--local_rank", help="Local rank for distributed training.")] = 0,
|
|
197
|
+
output_dir: Annotated[
|
|
198
|
+
str, typer.Option("--output_dir", help="Output directory for logs and checkpoints.")
|
|
199
|
+
] = "./results",
|
|
200
|
+
wandb: Annotated[bool, typer.Option("--wandb/--no_wandb", help="Enable Weights & Biases logging.")] = False,
|
|
201
|
+
wandb_project: Annotated[str, typer.Option("--wandb_project", help="Weights & Biases project name.")] = "moozy",
|
|
202
|
+
wandb_tags: Annotated[
|
|
203
|
+
str,
|
|
204
|
+
typer.Option("--wandb_tags", help="Space or comma-separated tags for Weights & Biases run."),
|
|
205
|
+
] = "",
|
|
206
|
+
seed: Annotated[int, typer.Option("--seed", help="Random seed.")] = 42,
|
|
207
|
+
debug: Annotated[bool, typer.Option("--debug/--no_debug", help="Debug mode with limited data.")] = False,
|
|
208
|
+
) -> None:
|
|
209
|
+
"""Train the Stage-1 MOOZY self-supervised pretraining model."""
|
|
210
|
+
from moozy.config.data import Stage1DataConfig
|
|
211
|
+
from moozy.config.model import ProjectionConfig, SlideEncoderConfig
|
|
212
|
+
from moozy.config.stage1 import Stage1TrainConfig
|
|
213
|
+
from moozy.config.training import CheckpointConfig, OptimizationConfig, SchedulerConfig
|
|
214
|
+
|
|
215
|
+
parsed_wandb_tags = [t for raw in wandb_tags.replace(",", " ").split() if (t := raw.strip())] if wandb_tags else []
|
|
216
|
+
|
|
217
|
+
config = Stage1TrainConfig(
|
|
218
|
+
slide_encoder=SlideEncoderConfig(
|
|
219
|
+
variant=encoder_variant,
|
|
220
|
+
num_registers=num_registers,
|
|
221
|
+
dropout=dropout,
|
|
222
|
+
attn_dropout=attn_dropout,
|
|
223
|
+
layer_drop=layer_drop,
|
|
224
|
+
qk_norm=qk_norm,
|
|
225
|
+
layerscale_init=layerscale_init,
|
|
226
|
+
learnable_alibi=learnable_alibi,
|
|
227
|
+
).resolve_variant(),
|
|
228
|
+
projection=ProjectionConfig(
|
|
229
|
+
output_dim=output_dim,
|
|
230
|
+
proj_hidden_dim=proj_hidden_dim,
|
|
231
|
+
proj_bottleneck_dim=proj_bottleneck_dim,
|
|
232
|
+
proj_norm_last_layer=proj_norm_last_layer,
|
|
233
|
+
proj_norm=enum_val(proj_norm),
|
|
234
|
+
proj_last_norm=enum_val(proj_last_norm),
|
|
235
|
+
),
|
|
236
|
+
data=Stage1DataConfig(
|
|
237
|
+
feature_dirs=list(feature_dirs),
|
|
238
|
+
feature_h5_format=enum_val(feature_h5_format),
|
|
239
|
+
feature_h5_key=feature_h5_key,
|
|
240
|
+
batch_size=batch_size,
|
|
241
|
+
num_workers=num_workers,
|
|
242
|
+
prefetch_factor=prefetch_factor,
|
|
243
|
+
lazy_feature_loading=lazy_feature_loading,
|
|
244
|
+
max_cached_slides=max_cached_slides,
|
|
245
|
+
global_crop_size=global_crop_size,
|
|
246
|
+
local_crop_size=local_crop_size,
|
|
247
|
+
num_global_crops=num_global_crops,
|
|
248
|
+
num_local_crops=num_local_crops,
|
|
249
|
+
mask_ratio_min=mask_ratio_min,
|
|
250
|
+
mask_ratio_max=mask_ratio_max,
|
|
251
|
+
min_num_mask_patches=min_num_mask_patches,
|
|
252
|
+
max_num_mask_patches=max_num_mask_patches,
|
|
253
|
+
mask_min_aspect=mask_min_aspect,
|
|
254
|
+
mask_max_aspect=mask_max_aspect,
|
|
255
|
+
mask_sample_probability=mask_sample_probability,
|
|
256
|
+
min_window_patch_ratio=min_window_patch_ratio,
|
|
257
|
+
crop_resample_attempts=crop_resample_attempts,
|
|
258
|
+
hflip_prob=hflip_prob,
|
|
259
|
+
vflip_prob=vflip_prob,
|
|
260
|
+
rotate_prob=rotate_prob,
|
|
261
|
+
),
|
|
262
|
+
optimization=OptimizationConfig(
|
|
263
|
+
optimizer=enum_val(optimizer),
|
|
264
|
+
lr=lr,
|
|
265
|
+
lr_min=lr_min,
|
|
266
|
+
lr_base_batch_size=lr_base_batch_size,
|
|
267
|
+
weight_decay=weight_decay,
|
|
268
|
+
grad_clip=grad_clip,
|
|
269
|
+
grad_accumulation_steps=grad_accumulation_steps,
|
|
270
|
+
mixed_precision=mixed_precision,
|
|
271
|
+
),
|
|
272
|
+
scheduler=SchedulerConfig(
|
|
273
|
+
lr_schedule=enum_val(lr_schedule),
|
|
274
|
+
warmup_steps=warmup_steps,
|
|
275
|
+
warmup_epochs=warmup_epochs,
|
|
276
|
+
weight_decay_start=weight_decay_start,
|
|
277
|
+
weight_decay_end=weight_decay_end,
|
|
278
|
+
wd_schedule=enum_val(wd_schedule),
|
|
279
|
+
ema_momentum_start=ema_momentum_start,
|
|
280
|
+
ema_momentum=ema_momentum,
|
|
281
|
+
momentum_schedule=enum_val(momentum_schedule),
|
|
282
|
+
student_temp=student_temp,
|
|
283
|
+
teacher_temp=teacher_temp,
|
|
284
|
+
teacher_patch_temp=teacher_patch_temp,
|
|
285
|
+
warmup_teacher_temp=warmup_teacher_temp,
|
|
286
|
+
warmup_teacher_patch_temp=warmup_teacher_patch_temp,
|
|
287
|
+
warmup_teacher_temp_epochs=warmup_teacher_temp_epochs,
|
|
288
|
+
center_momentum=center_momentum,
|
|
289
|
+
freeze_last_layer_steps=freeze_last_layer_steps,
|
|
290
|
+
freeze_last_layer_epochs=freeze_last_layer_epochs,
|
|
291
|
+
),
|
|
292
|
+
checkpoint=CheckpointConfig(
|
|
293
|
+
save_every=save_every,
|
|
294
|
+
resume_from=resume_from,
|
|
295
|
+
keep_last_n=keep_last_n,
|
|
296
|
+
save_teacher=save_teacher,
|
|
297
|
+
teacher_save_prefix=teacher_save_prefix,
|
|
298
|
+
),
|
|
299
|
+
total_steps=total_steps,
|
|
300
|
+
epochs=epochs,
|
|
301
|
+
log_every=log_every,
|
|
302
|
+
val_ratio=val_ratio,
|
|
303
|
+
val_every=val_every,
|
|
304
|
+
distributed=distributed,
|
|
305
|
+
backend=enum_val(backend),
|
|
306
|
+
local_rank=local_rank,
|
|
307
|
+
output_dir=output_dir,
|
|
308
|
+
seed=seed,
|
|
309
|
+
debug=debug,
|
|
310
|
+
wandb=wandb,
|
|
311
|
+
wandb_project=wandb_project,
|
|
312
|
+
wandb_tags=parsed_wandb_tags,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
from moozy.training.runners.stage1 import run_stage1
|
|
316
|
+
|
|
317
|
+
run_stage1(config)
|