taejoon89 commited on
Commit
b9bd634
·
verified ·
1 Parent(s): 62a0e4e

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. OpenMidnight/.gitignore +0 -46
  2. OpenMidnight/HEST_evaluation.py +0 -51
  3. OpenMidnight/LICENSE +0 -203
  4. OpenMidnight/README.md +0 -225
  5. OpenMidnight/_utils.py +0 -166
  6. OpenMidnight/dinov2/__init__.py +0 -6
  7. OpenMidnight/dinov2/configs/__init__.py +0 -22
  8. OpenMidnight/dinov2/configs/eval/vitb14_pretrain.yaml +0 -6
  9. OpenMidnight/dinov2/configs/eval/vitb14_reg4_pretrain.yaml +0 -9
  10. OpenMidnight/dinov2/configs/eval/vitg14_pretrain.yaml +0 -7
  11. OpenMidnight/dinov2/configs/eval/vitg14_reg4_pretrain.yaml +0 -10
  12. OpenMidnight/dinov2/configs/eval/vitl14_pretrain.yaml +0 -6
  13. OpenMidnight/dinov2/configs/eval/vitl14_reg4_pretrain.yaml +0 -9
  14. OpenMidnight/dinov2/configs/eval/vits14_pretrain.yaml +0 -6
  15. OpenMidnight/dinov2/configs/eval/vits14_reg4_pretrain.yaml +0 -9
  16. OpenMidnight/dinov2/configs/ssl_default_config.yaml +0 -129
  17. OpenMidnight/dinov2/configs/train/openpath_vitg14.yaml +0 -95
  18. OpenMidnight/dinov2/configs/train/openpath_vitg14_bs64_test.yaml +0 -84
  19. OpenMidnight/dinov2/configs/train/openpath_vitg14_curated.yaml +0 -57
  20. OpenMidnight/dinov2/configs/train/openpath_vitg14_final.yaml +0 -80
  21. OpenMidnight/dinov2/configs/train/openpath_vitg14_final_1ep.yaml +0 -59
  22. OpenMidnight/dinov2/configs/train/openpath_vitg14_final_1ep_bs64.yaml +0 -59
  23. OpenMidnight/dinov2/configs/train/openpath_vitg14_full.yaml +0 -55
  24. OpenMidnight/dinov2/configs/train/openpath_vitg14_full_cptac.yaml +0 -55
  25. OpenMidnight/dinov2/configs/train/openpath_vitg14_run2.yaml +0 -84
  26. OpenMidnight/dinov2/configs/train/openpath_vitg14_run3_gram.yaml +0 -92
  27. OpenMidnight/dinov2/configs/train/openpath_vitg14_run3_smoke.yaml +0 -92
  28. OpenMidnight/dinov2/configs/train/openpath_vitg14_run4_gram.yaml +0 -92
  29. OpenMidnight/dinov2/configs/train/openpath_vitg14_run5_native.yaml +0 -84
  30. OpenMidnight/dinov2/configs/train/openpath_vitg14_run6_native_gram.yaml +0 -93
  31. OpenMidnight/dinov2/configs/train/openpath_vitg14_testB.yaml +0 -54
  32. OpenMidnight/dinov2/configs/train/openpath_vitl14_reg4.yaml +0 -58
  33. OpenMidnight/dinov2/configs/train/openpath_vitl14_reg4_curated.yaml +0 -58
  34. OpenMidnight/dinov2/configs/train/openpath_vitl14_testB.yaml +0 -59
  35. OpenMidnight/dinov2/configs/train/vitg14_reg4.yaml +0 -51
  36. OpenMidnight/dinov2/configs/train/vits14_reg4.yaml +0 -51
  37. OpenMidnight/dinov2/data/__init__.py +0 -10
  38. OpenMidnight/dinov2/data/adapters.py +0 -28
  39. OpenMidnight/dinov2/data/augmentations.py +0 -383
  40. OpenMidnight/dinov2/data/collate.py +0 -62
  41. OpenMidnight/dinov2/data/datasets/__init__.py +0 -9
  42. OpenMidnight/dinov2/data/datasets/decoders.py +0 -31
  43. OpenMidnight/dinov2/data/datasets/extended.py +0 -38
  44. OpenMidnight/dinov2/data/datasets/image_net.py +0 -290
  45. OpenMidnight/dinov2/data/datasets/image_net_22k.py +0 -302
  46. OpenMidnight/dinov2/data/datasets/slide_dataset.py +0 -99
  47. OpenMidnight/dinov2/data/loaders.py +0 -229
  48. OpenMidnight/dinov2/data/masking.py +0 -86
  49. OpenMidnight/dinov2/data/openpath_wds.py +0 -229
  50. OpenMidnight/dinov2/data/samplers.py +0 -229
OpenMidnight/.gitignore DELETED
@@ -1,46 +0,0 @@
1
- # Byte-compiled / optimized / DLL files
2
- __pycache__/
3
- *.py[cod]
4
-
5
- # Distribution / packaging
6
- build/
7
- dist/
8
- *.egg-info/
9
- _version.py
10
-
11
- # IDE
12
- .vscode/
13
-
14
- # Virtual environment
15
- .venv/
16
- .python-version
17
-
18
- # Environment variables
19
- .env
20
- .env.*
21
-
22
- # Jupyter Notebook
23
- .ipynb_checkpoints
24
-
25
- # Local data
26
- slurms
27
- checkpoints
28
- wandb
29
- jobs
30
-
31
- # Local scratch/tmp
32
- .scratch
33
- tmp
34
- .tmp*
35
-
36
- # specific files
37
- output*
38
- sample_dataset_30.txt
39
- uv.lock
40
- watch_run.sh
41
- wandb/
42
- HEST/
43
- eva-probe/
44
- eval/
45
- CLAUDE.md
46
- AGENTS.md
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/HEST_evaluation.py DELETED
@@ -1,51 +0,0 @@
1
- from hest.bench import benchmark
2
- import torch
3
- from torchvision import transforms
4
-
5
- print("loading base")
6
- dinov2 = torch.hub.load('facebookresearch/dinov2', 'dinov2_vitg14_reg')
7
-
8
- ours = torch.load("checkpoints/teacher_epoch250000.pth")
9
- checkpoint = ours["teacher"]
10
- checkpoint_new = {}
11
-
12
- for key in list(checkpoint.keys()):
13
- if "dino" in str(key) or "ibot" in str(key):
14
- checkpoint.pop(key, None)
15
-
16
- for key, keyb in zip(checkpoint.keys(), dinov2.state_dict().keys()):
17
- checkpoint_new[keyb] = checkpoint[key]
18
-
19
- checkpoint = checkpoint_new
20
-
21
- new_shape = checkpoint["pos_embed"] #The pos embed is the only different shape
22
- dinov2.pos_embed = torch.nn.parameter.Parameter(new_shape)
23
-
24
- dinov2.load_state_dict(checkpoint)
25
-
26
- PATH_TO_CONFIG = "./HEST/bench_config/bench_config.yaml"
27
- model = dinov2
28
-
29
- RESIZE_DIM = 224
30
- NORMALIZE_MEAN = [0.485, 0.456, 0.406]
31
- NORMALIZE_STD = [0.229, 0.224, 0.225]
32
-
33
- model_transforms = transforms.Compose([
34
- #transforms.Resize(224), # Resize the smaller side of the image to 256
35
- #transforms.CenterCrop(RESIZE_DIM), # Crop the center of the image to 224x224
36
-
37
- # Step 2: Convert the image (PIL/numpy) to a PyTorch tensor
38
- transforms.ToTensor(),
39
-
40
- # Step 3: Normalize the tensor
41
- transforms.Normalize(
42
- mean=NORMALIZE_MEAN,
43
- std=NORMALIZE_STD)
44
- ])
45
-
46
- benchmark(
47
- model,
48
- model_transforms,
49
- torch.float32,
50
- config=PATH_TO_CONFIG,
51
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/LICENSE DELETED
@@ -1,203 +0,0 @@
1
-
2
-
3
- Apache License
4
- Version 2.0, January 2004
5
- http://www.apache.org/licenses/
6
-
7
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
8
-
9
- 1. Definitions.
10
-
11
- "License" shall mean the terms and conditions for use, reproduction,
12
- and distribution as defined by Sections 1 through 9 of this document.
13
-
14
- "Licensor" shall mean the copyright owner or entity authorized by
15
- the copyright owner that is granting the License.
16
-
17
- "Legal Entity" shall mean the union of the acting entity and all
18
- other entities that control, are controlled by, or are under common
19
- control with that entity. For the purposes of this definition,
20
- "control" means (i) the power, direct or indirect, to cause the
21
- direction or management of such entity, whether by contract or
22
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
23
- outstanding shares, or (iii) beneficial ownership of such entity.
24
-
25
- "You" (or "Your") shall mean an individual or Legal Entity
26
- exercising permissions granted by this License.
27
-
28
- "Source" form shall mean the preferred form for making modifications,
29
- including but not limited to software source code, documentation
30
- source, and configuration files.
31
-
32
- "Object" form shall mean any form resulting from mechanical
33
- transformation or translation of a Source form, including but
34
- not limited to compiled object code, generated documentation,
35
- and conversions to other media types.
36
-
37
- "Work" shall mean the work of authorship, whether in Source or
38
- Object form, made available under the License, as indicated by a
39
- copyright notice that is included in or attached to the work
40
- (an example is provided in the Appendix below).
41
-
42
- "Derivative Works" shall mean any work, whether in Source or Object
43
- form, that is based on (or derived from) the Work and for which the
44
- editorial revisions, annotations, elaborations, or other modifications
45
- represent, as a whole, an original work of authorship. For the purposes
46
- of this License, Derivative Works shall not include works that remain
47
- separable from, or merely link (or bind by name) to the interfaces of,
48
- the Work and Derivative Works thereof.
49
-
50
- "Contribution" shall mean any work of authorship, including
51
- the original version of the Work and any modifications or additions
52
- to that Work or Derivative Works thereof, that is intentionally
53
- submitted to Licensor for inclusion in the Work by the copyright owner
54
- or by an individual or Legal Entity authorized to submit on behalf of
55
- the copyright owner. For the purposes of this definition, "submitted"
56
- means any form of electronic, verbal, or written communication sent
57
- to the Licensor or its representatives, including but not limited to
58
- communication on electronic mailing lists, source code control systems,
59
- and issue tracking systems that are managed by, or on behalf of, the
60
- Licensor for the purpose of discussing and improving the Work, but
61
- excluding communication that is conspicuously marked or otherwise
62
- designated in writing by the copyright owner as "Not a Contribution."
63
-
64
- "Contributor" shall mean Licensor and any individual or Legal Entity
65
- on behalf of whom a Contribution has been received by Licensor and
66
- subsequently incorporated within the Work.
67
-
68
- 2. Grant of Copyright License. Subject to the terms and conditions of
69
- this License, each Contributor hereby grants to You a perpetual,
70
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
71
- copyright license to reproduce, prepare Derivative Works of,
72
- publicly display, publicly perform, sublicense, and distribute the
73
- Work and such Derivative Works in Source or Object form.
74
-
75
- 3. Grant of Patent License. Subject to the terms and conditions of
76
- this License, each Contributor hereby grants to You a perpetual,
77
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
78
- (except as stated in this section) patent license to make, have made,
79
- use, offer to sell, sell, import, and otherwise transfer the Work,
80
- where such license applies only to those patent claims licensable
81
- by such Contributor that are necessarily infringed by their
82
- Contribution(s) alone or by combination of their Contribution(s)
83
- with the Work to which such Contribution(s) was submitted. If You
84
- institute patent litigation against any entity (including a
85
- cross-claim or counterclaim in a lawsuit) alleging that the Work
86
- or a Contribution incorporated within the Work constitutes direct
87
- or contributory patent infringement, then any patent licenses
88
- granted to You under this License for that Work shall terminate
89
- as of the date such litigation is filed.
90
-
91
- 4. Redistribution. You may reproduce and distribute copies of the
92
- Work or Derivative Works thereof in any medium, with or without
93
- modifications, and in Source or Object form, provided that You
94
- meet the following conditions:
95
-
96
- (a) You must give any other recipients of the Work or
97
- Derivative Works a copy of this License; and
98
-
99
- (b) You must cause any modified files to carry prominent notices
100
- stating that You changed the files; and
101
-
102
- (c) You must retain, in the Source form of any Derivative Works
103
- that You distribute, all copyright, patent, trademark, and
104
- attribution notices from the Source form of the Work,
105
- excluding those notices that do not pertain to any part of
106
- the Derivative Works; and
107
-
108
- (d) If the Work includes a "NOTICE" text file as part of its
109
- distribution, then any Derivative Works that You distribute must
110
- include a readable copy of the attribution notices contained
111
- within such NOTICE file, excluding those notices that do not
112
- pertain to any part of the Derivative Works, in at least one
113
- of the following places: within a NOTICE text file distributed
114
- as part of the Derivative Works; within the Source form or
115
- documentation, if provided along with the Derivative Works; or,
116
- within a display generated by the Derivative Works, if and
117
- wherever such third-party notices normally appear. The contents
118
- of the NOTICE file are for informational purposes only and
119
- do not modify the License. You may add Your own attribution
120
- notices within Derivative Works that You distribute, alongside
121
- or as an addendum to the NOTICE text from the Work, provided
122
- that such additional attribution notices cannot be construed
123
- as modifying the License.
124
-
125
- You may add Your own copyright statement to Your modifications and
126
- may provide additional or different license terms and conditions
127
- for use, reproduction, or distribution of Your modifications, or
128
- for any such Derivative Works as a whole, provided Your use,
129
- reproduction, and distribution of the Work otherwise complies with
130
- the conditions stated in this License.
131
-
132
- 5. Submission of Contributions. Unless You explicitly state otherwise,
133
- any Contribution intentionally submitted for inclusion in the Work
134
- by You to the Licensor shall be under the terms and conditions of
135
- this License, without any additional terms or conditions.
136
- Notwithstanding the above, nothing herein shall supersede or modify
137
- the terms of any separate license agreement you may have executed
138
- with Licensor regarding such Contributions.
139
-
140
- 6. Trademarks. This License does not grant permission to use the trade
141
- names, trademarks, service marks, or product names of the Licensor,
142
- except as required for reasonable and customary use in describing the
143
- origin of the Work and reproducing the content of the NOTICE file.
144
-
145
- 7. Disclaimer of Warranty. Unless required by applicable law or
146
- agreed to in writing, Licensor provides the Work (and each
147
- Contributor provides its Contributions) on an "AS IS" BASIS,
148
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
149
- implied, including, without limitation, any warranties or conditions
150
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
151
- PARTICULAR PURPOSE. You are solely responsible for determining the
152
- appropriateness of using or redistributing the Work and assume any
153
- risks associated with Your exercise of permissions under this License.
154
-
155
- 8. Limitation of Liability. In no event and under no legal theory,
156
- whether in tort (including negligence), contract, or otherwise,
157
- unless required by applicable law (such as deliberate and grossly
158
- negligent acts) or agreed to in writing, shall any Contributor be
159
- liable to You for damages, including any direct, indirect, special,
160
- incidental, or consequential damages of any character arising as a
161
- result of this License or out of the use or inability to use the
162
- Work (including but not limited to damages for loss of goodwill,
163
- work stoppage, computer failure or malfunction, or any and all
164
- other commercial damages or losses), even if such Contributor
165
- has been advised of the possibility of such damages.
166
-
167
- 9. Accepting Warranty or Additional Liability. While redistributing
168
- the Work or Derivative Works thereof, You may choose to offer,
169
- and charge a fee for, acceptance of support, warranty, indemnity,
170
- or other liability obligations and/or rights consistent with this
171
- License. However, in accepting such obligations, You may act only
172
- on Your own behalf and on Your sole responsibility, not on behalf
173
- of any other Contributor, and only if You agree to indemnify,
174
- defend, and hold each Contributor harmless for any liability
175
- incurred by, or claims asserted against, such Contributor by reason
176
- of your accepting any such warranty or additional liability.
177
-
178
- END OF TERMS AND CONDITIONS
179
-
180
- APPENDIX: How to apply the Apache License to your work.
181
-
182
- To apply the Apache License to your work, attach the following
183
- boilerplate notice, with the fields enclosed by brackets "[]"
184
- replaced with your own identifying information. (Don't include
185
- the brackets!) The text should be enclosed in the appropriate
186
- comment syntax for the file format. We also recommend that a
187
- file or class name and description of purpose be included on the
188
- same "printed page" as the copyright notice for easier
189
- identification within third-party archives.
190
-
191
- Copyright [yyyy] [name of copyright owner]
192
-
193
- Licensed under the Apache License, Version 2.0 (the "License");
194
- you may not use this file except in compliance with the License.
195
- You may obtain a copy of the License at
196
-
197
- http://www.apache.org/licenses/LICENSE-2.0
198
-
199
- Unless required by applicable law or agreed to in writing, software
200
- distributed under the License is distributed on an "AS IS" BASIS,
201
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
202
- See the License for the specific language governing permissions and
203
- limitations under the License.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/README.md DELETED
@@ -1,225 +0,0 @@
1
- <h1> OpenMidnight </h1>
2
-
3
- <p align="center"><img width="735" height="400" alt="2PJpVl2k51pfjFg2Vs-oS" src="https://github.com/user-attachments/assets/fd00a9f5-3248-46ed-883a-f423e54ac2b6" /></p>
4
- <p align="center">Fully open-source and improved replication of Kaiko.AI's pathology foundation model <a href="https://arxiv.org/abs/2504.05186v1">Midnight</a>.</p>
5
- <div id="badges" align="center">
6
- <a href="https://sophont.med/blog/openmidnight"><img src="https://img.shields.io/badge/Blog-Training%20SOTA%20Pathology%20Foundation%20Model%20with%20%241.6k-111827?style=for-the-badge&logo=read.cv&logoColor=white" /> </a>
7
- <a href="https://discord.gg/tVR4TWnRM9"><img src="https://img.shields.io/badge/Discord-Collaborate%20with%20us-5865F2?style=for-the-badge&logo=discord&logoColor=white" /></a>
8
- </div>
9
- <p align="center"> <a href="https://sophont.med">Sophont</a> · <a href="https://medarc.ai">MedARC</a> </p>
10
-
11
- This is a publicly developed, open-source project by [MedARC](https://www.medarc.ai/). If you are interested in helping out, [join our Discord server](https://discord.gg/tVR4TWnRM9) and introduce yourself in our `#path-fm` channel.
12
-
13
-
14
- ## Features
15
- - Trains faster with improved average benchmarking performance compared to the original Midnight-12K model (~3 days to train using 1×8×H100)
16
- - Supports single‑GPU up to multi‑node training with FSDP
17
- - Robust resuming from last checkpoint functionality if training gets interrupted
18
- - Weights & Biases (wandb) logging for monitoring/tracking model training
19
- - Optionally stream data from Hugging Face so no need to download any data in advance (TCGA-12K is approximately 12 TB)
20
-
21
- # Installation
22
-
23
- Clone the repository:
24
-
25
- ```bash
26
- git clone https://github.com/MedARC-AI/openmidnight.git
27
- ```
28
-
29
- Change into the directory, then run the installation script:
30
-
31
- ```bash
32
- ./install.sh
33
- ```
34
-
35
- This will create a virtual environment, "pathologydino", with all necessary packages pre-installed, located as a .venv folder in the same directory as openmidnight. It will automatically detect your machine's CUDA version and install packages appropriately.
36
-
37
- Note: We have only personally verified training works as intended with this repository using H100 GPUs.
38
-
39
- ```bash
40
- source .venv/bin/activate
41
- wandb init
42
- ```
43
-
44
- By default, we log model training to wandb. Run `wandb init` inside of `openmidnight/` before starting your training run so that wandb is properly configured.
45
-
46
- You can now run one of our `run*.sh` scripts to train your model (see Training section below), using the YAML config specified in that script.
47
-
48
- Once you have successfully completed model training (or have downloaded [our pretrained checkpoint](https://huggingface.co/SophontAI/OpenMidnight/blob/main/teacher_checkpoint.pth)), you can evaluate using [Kaiko.AI's eva framework](https://github.com/kaiko-ai/eva) and the [Mahmood Lab's HEST benchmark](https://github.com/mahmoodlab/HEST) (skip to the Evaluation section below).
49
-
50
- # Training
51
-
52
- We use `run*.sh` to initiate training runs via torchrun. Depending on your compute setting, modify and run the corresponding shell script described in the relevant subsection below.
53
-
54
- We use YAML configs for specifying important hyperparameters during training. These files are located in `dinov2/configs/train/` (not in `eval_configs/`, which stores YAML configs for eva evaluation benchmarking). Our replication checkpoint specifically used `dinov2/configs/train/vitg14_reg4.yaml`.
55
-
56
- There are some variables that are specified in `run*.sh` directly (as opposed to the YAML config), such as the output directory for saving checkpoints, whether to enable resume functionality, and the specific CUDA devices you want to train with.
57
-
58
- If you are getting rate limited by huggingface, one easy method to increase your rate is to first `export HF_TOKEN=<your HF token here>` before running your code (https://huggingface.co/settings/tokens).
59
-
60
- ## Dataset prep
61
-
62
- If you are wanting to exactly replicate our checkpoint, note that we did not train via streaming from huggingface. This feature was subsequently added and needs to be enabled in your YAML config (`train.streaming_from_hf=[True,False]`). Unless you enable this, you will need to first locally download the TCGA-12K dataset (~12TB) and then use the scripts provided in `prepatching_scripts/` to create a txt file containing the svs filepaths and locations/magnitude from which to create patches on-the-fly during model training. You can alternatively use [our original sample_dataset_30.txt file](https://huggingface.co/SophontAI/OpenMidnight/blob/main/sample_dataset_30.txt), but note you would need to modify that txt to correct its use of absolute filepaths.
63
-
64
- ## Training Single GPU (Short Config)
65
-
66
- ```bash
67
- ./run_ablation.sh
68
- ```
69
-
70
- This does finetuning with a YAML config (`dinov2/configs/train/vits14_reg_ablations.yaml`) tweaked to support an informative, short training run on a single GPU that can be completed in under 24 hours. We hope this can be particularly useful for debugging and ablation experiments.
71
-
72
- ## Training Single Node, Multi‑GPU (Full Reproduction)
73
-
74
- ```bash
75
- ./run_1node.sh
76
- ```
77
-
78
- Train across multiple GPUs on a single node. Our released checkpoint used this script for training.
79
-
80
- ## Training Multi‑Node (Full Reproduction)
81
-
82
- Same as training single‑node multi‑GPU, except increase `NNODES` in both `run_master_node.sh` and `run_other_nodes.sh` to the number of total nodes you are training across. Then run the corresponding scripts.
83
-
84
- ```bash
85
- ./run_master_node.sh # on master node
86
- ```
87
- ```bash
88
- ./run_other_nodes.sh # on non-master nodes
89
- ```
90
-
91
- If during training you get HTTP Error 429, try reducing the number of workers (set in the YAML config) and lowering the DataLoader's `prefetch_factor` (defined in `dinov2/train/train.py`). This error can happen when Hugging Face is being pinged too frequently during streaming. Another solution is to [download the data locally](https://huggingface.co/datasets/medarc/TCGA-12K-parquet) and replace `medarc/TCGA-12K-parquet` in `dinov2/train/train.py` with the full path to your locally downloaded dataset folder.
92
-
93
- # Methods / Training Recipe
94
-
95
- Below is a high‑level overview of our training recipe, with particular attention to deviations from the original DINOv2 paper. For additional context, refer to the [Midnight paper](https://arxiv.org/abs/2504.05186).
96
-
97
- - Base model + init
98
- - Student/teacher are ViT‑G/14 with 4 register tokens.
99
- - We initialize the student backbone from Meta’s DINOv2 ViT‑G/14 register checkpoint via `torch.hub`.
100
- - Heads are re‑initialized, as Meta only shared pretrained weights for their model backbone.
101
-
102
- - Objectives and heads
103
- - DINO self‑distillation on CLS tokens, with 131072 prototypes and a 384‑dim bottleneck head. iBOT masked patch prediction on global crops.
104
- - DINOv2's KoLeo regularization is replaced by a KDE‑based entropy regularizer as done in the Midnight paper.
105
-
106
- - Data and augmentations
107
- - Streaming directly from a Parquet dataset of pre-patched TCGA slides hosted on Hugging Face (medarc/TCGA-12K-parquet). See `prepatching_scripts/` and read our [hf dataset card](https://huggingface.co/datasets/medarc/TCGA-12K-parquet/blob/main/README.md) for more information on pre-patching. We read `image_bytes`, decode to RGB, and apply DINO augmentations.
108
- - H&E augmentation: before normalization, images are converted to HED space and perturbed, then converted back to RGB. See `DataAugmentationDINO` in `dinov2/data/augmentations.py`.
109
- - Multi‑crop: 2 global crops (224) and multiple local crops (98). iBOT masks are sampled per‑image with ratios drawn uniformly from a min/max range.
110
-
111
- - Optimization and schedules
112
- - LR is scaled with sqrt(batch/1024) from a base LR of 2e‑4. Midnight paper originally used a base LR of 3.5e-4 but we observed that this led to training collapse.
113
- - We train for 8000 “epochs” by schedule (1 epoch = 1250 steps), but early‑stop at 200 epochs. We found early stopping was necessary to prevent worsening downstream performance with longer model training.
114
-
115
- - Checkpointing, evaluation, logging
116
- - We save LOCAL_STATE_DICT FSDP checkpoints per rank and tag `last_checkpoint.rank_*` files (for resuming functionality).
117
- - The teacher weights are exported every cfg.evaluation.eval_period_iterations steps to `output_dir/eval/training_<iter>/teacher_checkpoint.pth`.
118
- - Weights & Biases logging is enabled with a persistent run id stored in the output directory to support resuming, in case model training gets interrupted.
119
-
120
- # Downstream Evaluation
121
-
122
- ## eva Benchmarks
123
-
124
- First ensure you have a checkpoint ready to be evaluated. Place your .pth file for your teacher checkpoint in the /checkpoints folder. You can download our pretrained checkpoint here: https://huggingface.co/SophontAI/OpenMidnight/blob/main/teacher_checkpoint.pth
125
-
126
- Then, `cd` into the same `openmidnight` folder cloned from our Installation steps and clone our modified GitHub repo forked from the original [kaiko-eva](https://github.com/kaiko-ai/eva):
127
-
128
- ```bash
129
- cd openmidnight
130
- source .venv/bin/activate
131
- git clone https://github.com/MedARC-AI/eva-probe
132
- ```
133
-
134
- Then, install the eva framework to enable use of the `eva` command in your terminal (using `--no-deps` because the `openmidnight` virtual environment already contains the necessary packages):
135
-
136
- ```bash
137
- uv pip install -e './eva-probe[vision]' --no-deps
138
- ```
139
-
140
- We provide every YAML config file we used in our replication in `openmidnight/eval_configs/`. Not every dataset permits automatic downloading. For datasets like BACH that do, we automatically download the dataset when you specify `DOWNLOAD_DATA=true` when calling `eva predict_fit`. For other datasets, follow the manual download steps described in the [eva datasets documentation](https://kaiko-ai.github.io/eva/main/datasets/) and revise each YAML config to provide the path to your downloaded dataset prior to eva benchmarking.
141
-
142
- Below are the steps for running the [BACH](https://kaiko-ai.github.io/eva/main/datasets/bach/) evaluation. If your teacher checkpoint is not stored as `openmidnight/checkpoints/teacher_epoch250000.pth`, you will first need to modify your eva YAML file's `checkpoint_path` variable to specify the path to your model weights.
143
-
144
- ```bash
145
- cd eva-probe # should be located in openmidnight/eva-probe
146
- CUDA_VISIBLE_DEVICES=0 DOWNLOAD_DATA=true eva predict_fit --config ../eval_configs/bach.yaml
147
- ```
148
-
149
- All eva evaluations should be run on a single GPU by setting `CUDA_VISIBLE_DEVICES=0`. We observed inconsistent and worse results when trying to evaluate using multiple GPUs.
150
-
151
- ## HEST Benchmark
152
-
153
- First ensure you have a checkpoint ready to be evaluated. Place the .pth file for your teacher model in the /checkpoints folder. You can download our pretrained checkpoint here: https://huggingface.co/SophontAI/OpenMidnight/blob/main/teacher_checkpoint.pth
154
-
155
- Then, `cd` into the same `openmidnight` folder cloned from our Installation steps and clone the [Mahmood Lab's HEST GitHub repo](https://github.com/mahmoodlab/HEST):
156
-
157
- ```bash
158
- cd openmidnight
159
- source .venv/bin/activate
160
- git clone https://github.com/mahmoodlab/HEST.git
161
- cd HEST
162
- git checkout afd42c3143092c51e6bcc0f1df65bbf58a467e5e
163
- cd .. # cd back to openmidnight/ for subsequent install steps
164
- ```
165
-
166
- Then install HEST framework so that we can import invoke their benchmark function (using `--no-deps` because the `openmidnight` virtual environment already contains the necessary packages).
167
-
168
- ```bash
169
- uv pip install -e ./HEST --no-deps
170
- ```
171
-
172
- Now uncomment out specific lines in the HEST YAML config to enable PCA dimensionality reduction, benchmark across all HEST datasets used in the original paper, and solely benchmark our DINOv2 model as the encoder:
173
-
174
- ```bash
175
- sed -i -E '/^datasets:/,/^\]/{s/^([[:space:]]*)#([[:space:]]*)"([^"]+)",/\1"\3",/;s/^([[:space:]]*)"HCC"(,?)/\1# "HCC"\2/}; s/^[[:space:]]*#([[:space:]]*dimreduce:[[:space:]]*".*")/\1/; /^encoders:/,/^\]/{s/^([[:space:]]*)"resnet50"(,?)/\1# "resnet50"\2/}' ./HEST/bench_config/bench_config.yaml
176
- ```
177
-
178
- Then finally run our HEST_evaluation.py script to benchmark your checkpoint. Running HEST_evaluation.py will automatically download the necessary preprocessed patches and patch encoders and output results into a new `openmidnight/eval` folder (specifically, the benchmark results dump to the `ST_pred_results/` subfolder).
179
-
180
- ```bash
181
- python HEST_evaluation.py
182
- ```
183
-
184
- ## Related Work / Citation
185
-
186
- This repository adapts and extends Meta AI's DINOv2 codebase and follows modifications introduced by Kaiko's Midnight work. If you use this repository or models in academic work, please cite their and our work:
187
-
188
- Kaplan, D., Grandhi, R. S., Lane, C., Warner, B., Abraham, T. M., & Scotti, P. S. (2025). How to train a state-of-the-art pathology foundation model with $1.6k. Sophont. https://sophont.med/blog/openmidnight
189
-
190
- ```
191
- @article{kaplan2025openmidnight,
192
- author = {Kaplan, Daniel and Grandhi, Ratna Sagari and Lane, Connor and Warner, Benjamin and Abraham, Tanishq Mathew and Scotti, Paul S.},
193
- title = {How to Train a State-of-the-Art Pathology Foundation Model with \$1.6k},
194
- year = {2025},
195
- url = {https://sophont.med/blog/openmidnight},
196
- }
197
- ```
198
-
199
- Karasikov, M., van Doorn, J., Känzig, N., Erdal Cesur, M., Horlings, H. M., Berke, R., ... & Otálora, S. (2025). Training state-of-the-art pathology foundation models with orders of magnitude less data. In International Conference on Medical Image Computing and Computer-Assisted Intervention (pp. 573-583). Cham: Springer Nature Switzerland.
200
-
201
- ```
202
- @inproceedings{karasikov2025training,
203
- title={Training state-of-the-art pathology foundation models with orders of magnitude less data},
204
- author={Karasikov, Mikhail and van Doorn, Joost and K{\"a}nzig, Nicolas and Erdal Cesur, Melis and Horlings, Hugo Mark and Berke, Robert and Tang, Fei and Ot{\'a}lora, Sebastian},
205
- booktitle={International Conference on Medical Image Computing and Computer-Assisted Intervention},
206
- pages={573—583},
207
- year={2025},
208
- organization={Springer}
209
- }
210
- ```
211
-
212
- Oquab, M., Darcet, T., Moutakanni, T., Vo, H., Szafraniec, M., Khalidov, V., ... & Bojanowski, P. (2023). DINOv2: Learning robust visual features without supervision. arXiv preprint arXiv:2304.07193.
213
-
214
- ```
215
- @article{oquab2023dinov2,
216
- title={DINOv2: Learning Robust Visual Features without Supervision},
217
- author={Maxime Oquab and Timothée Darcet and Théo Moutakanni and Huy Vo and Marc Szafraniec and Vasil Khalidov and Pierre Fernandez and Daniel Haziza and Francisco Massa and Alaaeldin El-Nouby and Mahmoud Assran and Nicolas Ballas and Wojciech Galuba and Russell Howes and Po-Yao Huang and Shang-Wen Li and Ishan Misra and Michael Rabbat and Vasu Sharma and Gabriel Synnaeve and Hu Xu and Hervé Jegou and Julien Mairal and Patrick Labatut and Armand Joulin and Piotr Bojanowski},
218
- year={2024},
219
- eprint={2304.07193},
220
- archivePrefix={arXiv},
221
- primaryClass={cs.CV},
222
- url={https://arxiv.org/abs/2304.07193},
223
- }
224
- ```
225
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/_utils.py DELETED
@@ -1,166 +0,0 @@
1
- """Utilities and helper functions for models."""
2
-
3
- #/home/daniel/pathologyDino/dino_env/lib/python3.11/site-packages/eva/core/models/wrappers
4
-
5
- import hashlib
6
- import os
7
- import sys
8
- from typing import Any, Dict
9
-
10
- import torch
11
- from fsspec.core import url_to_fs
12
- from lightning_fabric.utilities import cloud_io
13
- from loguru import logger
14
- from torch import hub, nn
15
-
16
- from eva.core.utils.progress_bar import tqdm
17
-
18
-
19
- def load_model_weights(model: nn.Module, checkpoint_path: str) -> None:
20
- """Loads (local or remote) weights to the model in-place.
21
-
22
- Args:
23
- model: The model to load the weights to.
24
- checkpoint_path: The path to the model weights/checkpoint.
25
- """
26
- logger.info(f"Loading '{model.__class__.__name__}' model from checkpoint '{checkpoint_path}'")
27
-
28
- print("tstingi")
29
- print(model.state_dict().keys())
30
- fs = cloud_io.get_filesystem(checkpoint_path)
31
- with fs.open(checkpoint_path, "rb") as file:
32
- checkpoint = cloud_io._load(file, map_location="cpu") # type: ignore
33
- if isinstance(checkpoint, dict) and "state_dict" in checkpoint:
34
- checkpoint = checkpoint["state_dict"]
35
-
36
-
37
- if "teacher" in checkpoint:
38
- checkpoint = checkpoint["teacher"]
39
- #Need to remove the word backbone from everything I think?
40
- checkpoint_new = {}
41
- for key in list(checkpoint.keys()):
42
- if "dino" in str(key) or "ibot" in str(key):
43
- checkpoint.pop(key, None)
44
- for key, keyb in zip(checkpoint.keys(), model.state_dict().keys()):
45
- checkpoint_new[keyb] = checkpoint[key]
46
-
47
- checkpoint = checkpoint_new
48
- #The pos embed is the only different one, idk why
49
- new_shape = checkpoint["pos_embed"]
50
- model.pos_embed = torch.nn.parameter.Parameter(new_shape)
51
-
52
- model.load_state_dict(checkpoint, strict=True)
53
-
54
-
55
- logger.info(f"Loading weights from '{checkpoint_path}' completed successfully.")
56
-
57
-
58
- def load_state_dict_from_url(
59
- url: str,
60
- *,
61
- model_dir: str | None = None,
62
- filename: str | None = None,
63
- progress: bool = True,
64
- md5: str | None = None,
65
- force: bool = False,
66
- ) -> Dict[str, Any]:
67
- """Loads the Torch serialized object at the given URL.
68
-
69
- If the object is already present and valid in `model_dir`, it's
70
- deserialized and returned.
71
-
72
- The default value of ``model_dir`` is ``<hub_dir>/checkpoints`` where
73
- ``hub_dir`` is the directory returned by :func:`~torch.hub.get_dir`.
74
-
75
- Args:
76
- url: URL of the object to download.
77
- model_dir: Directory in which to save the object.
78
- filename: Name for the downloaded file. Filename from ``url`` will be used if not set.
79
- progress: Whether or not to display a progress bar to stderr.
80
- md5: MD5 file code to check whether the file is valid. If not, it will re-download it.
81
- force: Whether to download the file regardless if it exists.
82
- """
83
- model_dir = model_dir or os.path.join(hub.get_dir(), "checkpoints")
84
- os.makedirs(model_dir, exist_ok=True)
85
-
86
- cached_file = os.path.join(model_dir, filename or os.path.basename(url))
87
- if force or not os.path.exists(cached_file) or not _check_integrity(cached_file, md5):
88
- sys.stderr.write(f"Downloading: '{url}' to {cached_file}\n")
89
- _download_url_to_file(url, cached_file, progress=progress)
90
- if md5 is None or not _check_integrity(cached_file, md5):
91
- sys.stderr.write(f"File MD5: {_calculate_md5(cached_file)}\n")
92
-
93
- return torch.load(cached_file, map_location="cpu")
94
-
95
-
96
- def _download_url_to_file(
97
- url: str,
98
- dst: str,
99
- *,
100
- progress: bool = True,
101
- ) -> None:
102
- """Download object at the given URL to a local path.
103
-
104
- Args:
105
- url: URL of the object to download.
106
- dst: Full path where object will be saved.
107
- chunk_size: The size of each chunk to read in bytes.
108
- progress: Whether or not to display a progress bar to stderr.
109
- """
110
- try:
111
- _download_with_fsspec(url=url, dst=dst, progress=progress)
112
- except Exception:
113
- try:
114
- hub.download_url_to_file(url=url, dst=dst, progress=progress)
115
- except Exception as hub_e:
116
- raise RuntimeError(
117
- f"Failed to download file from {url} using both fsspec and hub."
118
- ) from hub_e
119
-
120
-
121
- def _download_with_fsspec(
122
- url: str,
123
- dst: str,
124
- *,
125
- chunk_size: int = 1024 * 1024,
126
- progress: bool = True,
127
- ) -> None:
128
- """Download object at the given URL to a local path using fsspec.
129
-
130
- Args:
131
- url: URL of the object to download.
132
- dst: Full path where object will be saved.
133
- chunk_size: The size of each chunk to read in bytes.
134
- progress: Whether or not to display a progress bar to stderr.
135
- """
136
- filesystem, _ = url_to_fs(url, anon=False)
137
- total_size_bytes = filesystem.size(url)
138
- with (
139
- filesystem.open(url, "rb") as remote_file,
140
- tqdm(
141
- total=total_size_bytes,
142
- unit="iB",
143
- unit_scale=True,
144
- unit_divisor=1024,
145
- disable=not progress,
146
- ) as pbar,
147
- ):
148
- with open(dst, "wb") as local_file:
149
- while True:
150
- data = remote_file.read(chunk_size)
151
- if not data:
152
- break
153
-
154
- local_file.write(data)
155
- pbar.update(chunk_size)
156
-
157
-
158
- def _calculate_md5(path: str) -> str:
159
- """Calculate the md5 hash of a file."""
160
- with open(path, "rb") as file:
161
- return hashlib.md5(file.read(), usedforsecurity=False).hexdigest()
162
-
163
-
164
- def _check_integrity(path: str, md5: str | None) -> bool:
165
- """Check if the file matches the specified md5 hash."""
166
- return (md5 is None) or (md5 == _calculate_md5(path))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/__init__.py DELETED
@@ -1,6 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- __version__ = "0.0.1"
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/__init__.py DELETED
@@ -1,22 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- import pathlib
7
-
8
- from omegaconf import OmegaConf
9
-
10
-
11
- def load_config(config_name: str):
12
- config_filename = config_name + ".yaml"
13
- return OmegaConf.load(pathlib.Path(__file__).parent.resolve() / config_filename)
14
-
15
-
16
- dinov2_default_config = load_config("ssl_default_config")
17
-
18
-
19
- def load_and_merge_config(config_name: str):
20
- default_config = OmegaConf.create(dinov2_default_config)
21
- loaded_config = load_config(config_name)
22
- return OmegaConf.merge(default_config, loaded_config)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/eval/vitb14_pretrain.yaml DELETED
@@ -1,6 +0,0 @@
1
- student:
2
- arch: vit_base
3
- patch_size: 14
4
- crops:
5
- global_crops_size: 518 # this is to set up the position embeddings properly
6
- local_crops_size: 98
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/eval/vitb14_reg4_pretrain.yaml DELETED
@@ -1,9 +0,0 @@
1
- student:
2
- arch: vit_base
3
- patch_size: 14
4
- num_register_tokens: 4
5
- interpolate_antialias: true
6
- interpolate_offset: 0.0
7
- crops:
8
- global_crops_size: 518 # this is to set up the position embeddings properly
9
- local_crops_size: 98
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/eval/vitg14_pretrain.yaml DELETED
@@ -1,7 +0,0 @@
1
- student:
2
- arch: vit_giant2
3
- patch_size: 14
4
- ffn_layer: swiglufused
5
- crops:
6
- global_crops_size: 518 # this is to set up the position embeddings properly
7
- local_crops_size: 98
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/eval/vitg14_reg4_pretrain.yaml DELETED
@@ -1,10 +0,0 @@
1
- student:
2
- arch: vit_giant2
3
- patch_size: 14
4
- ffn_layer: swiglufused
5
- num_register_tokens: 4
6
- interpolate_antialias: true
7
- interpolate_offset: 0.0
8
- crops:
9
- global_crops_size: 518 # this is to set up the position embeddings properly
10
- local_crops_size: 98
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/eval/vitl14_pretrain.yaml DELETED
@@ -1,6 +0,0 @@
1
- student:
2
- arch: vit_large
3
- patch_size: 14
4
- crops:
5
- global_crops_size: 518 # this is to set up the position embeddings properly
6
- local_crops_size: 98
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/eval/vitl14_reg4_pretrain.yaml DELETED
@@ -1,9 +0,0 @@
1
- student:
2
- arch: vit_large
3
- patch_size: 14
4
- num_register_tokens: 4
5
- interpolate_antialias: true
6
- interpolate_offset: 0.0
7
- crops:
8
- global_crops_size: 518 # this is to set up the position embeddings properly
9
- local_crops_size: 98
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/eval/vits14_pretrain.yaml DELETED
@@ -1,6 +0,0 @@
1
- student:
2
- arch: vit_small
3
- patch_size: 14
4
- crops:
5
- global_crops_size: 518 # this is to set up the position embeddings properly
6
- local_crops_size: 98
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/eval/vits14_reg4_pretrain.yaml DELETED
@@ -1,9 +0,0 @@
1
- student:
2
- arch: vit_small
3
- patch_size: 14
4
- num_register_tokens: 4
5
- interpolate_antialias: true
6
- interpolate_offset: 0.0
7
- crops:
8
- global_crops_size: 518 # this is to set up the position embeddings properly
9
- local_crops_size: 98
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/ssl_default_config.yaml DELETED
@@ -1,129 +0,0 @@
1
- MODEL:
2
- WEIGHTS: ''
3
- compute_precision:
4
- grad_scaler: true
5
- teacher:
6
- backbone:
7
- sharding_strategy: SHARD_GRAD_OP
8
- mixed_precision:
9
- param_dtype: fp16
10
- reduce_dtype: fp16
11
- buffer_dtype: fp32
12
- dino_head:
13
- sharding_strategy: SHARD_GRAD_OP
14
- mixed_precision:
15
- param_dtype: fp16
16
- reduce_dtype: fp16
17
- buffer_dtype: fp32
18
- ibot_head:
19
- sharding_strategy: SHARD_GRAD_OP
20
- mixed_precision:
21
- param_dtype: fp16
22
- reduce_dtype: fp16
23
- buffer_dtype: fp32
24
- student:
25
- backbone:
26
- sharding_strategy: SHARD_GRAD_OP
27
- mixed_precision:
28
- param_dtype: fp16
29
- reduce_dtype: fp16
30
- buffer_dtype: fp32
31
- dino_head:
32
- sharding_strategy: SHARD_GRAD_OP
33
- mixed_precision:
34
- param_dtype: fp16
35
- reduce_dtype: fp32
36
- buffer_dtype: fp32
37
- ibot_head:
38
- sharding_strategy: SHARD_GRAD_OP
39
- mixed_precision:
40
- param_dtype: fp16
41
- reduce_dtype: fp32
42
- buffer_dtype: fp32
43
- dino:
44
- loss_weight: 1.0
45
- head_n_prototypes: 65536
46
- head_bottleneck_dim: 256
47
- head_nlayers: 3
48
- head_hidden_dim: 2048
49
- koleo_loss_weight: 0.1
50
- ibot:
51
- loss_weight: 1.0
52
- mask_sample_probability: 0.5
53
- mask_ratio_min_max:
54
- - 0.1
55
- - 0.5
56
- separate_head: false
57
- head_n_prototypes: 65536
58
- head_bottleneck_dim: 256
59
- head_nlayers: 3
60
- head_hidden_dim: 2048
61
- train:
62
- batch_size_per_gpu: 64
63
- dataset_path: ImageNet:split=TRAIN
64
- output_dir: .
65
- saveckp_freq: 20
66
- skip_checkpointer: null # if null, auto-skip when running on a single GPU
67
- seed: 0
68
- num_workers: 10
69
- OFFICIAL_EPOCH_LENGTH: 1250
70
- cache_dataset: true
71
- centering: "centering" # or "sinkhorn_knopp"
72
- unfreeze_last_n_blocks: 40
73
- student:
74
- arch: vit_large
75
- patch_size: 16
76
- drop_path_rate: 0.3
77
- layerscale: 1.0e-05
78
- drop_path_uniform: true
79
- pretrained_weights: ''
80
- ffn_layer: "mlp"
81
- block_chunks: 0
82
- qkv_bias: true
83
- proj_bias: true
84
- ffn_bias: true
85
- num_register_tokens: 0
86
- interpolate_antialias: false
87
- interpolate_offset: 0.1
88
- teacher:
89
- momentum_teacher: 0.992
90
- final_momentum_teacher: 1
91
- warmup_teacher_temp: 0.04
92
- teacher_temp: 0.07
93
- warmup_teacher_temp_epochs: 30
94
- optim:
95
- epochs: 100
96
- weight_decay: 0.04
97
- weight_decay_end: 0.4
98
- base_lr: 0.004 # learning rate for a batch size of 1024
99
- lr: 0. # will be set after applying scaling rule
100
- warmup_epochs: 10
101
- min_lr: 1.0e-06
102
- clip_grad: 3.0
103
- freeze_last_layer_epochs: 1
104
- scaling_rule: sqrt_wrt_1024
105
- patch_embed_lr_mult: 0.2
106
- layerwise_decay: 0.9
107
- adamw_beta1: 0.9
108
- adamw_beta2: 0.999
109
- crops:
110
- global_crops_scale:
111
- - 0.32
112
- - 1.0
113
- local_crops_number: 8
114
- local_crops_scale:
115
- - 0.05
116
- - 0.32
117
- global_crops_size: 224
118
- local_crops_size: 96
119
- evaluation:
120
- eval_period_iterations: 12500
121
- bach_root: /data/eva-data/bach
122
- gram:
123
- use_loss: false
124
- normalized: true
125
- remove_neg: true
126
- loss_weight: 1.0
127
- it_first_update: 34000
128
- ramp_iters: 5000
129
- ckpt: ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitg14.yaml DELETED
@@ -1,95 +0,0 @@
1
- # OpenPath — ViT-g/14 pathology FM pre-training config.
2
- # DINOv2 (DINO + iBOT + KDE) + gram anchoring, warm-started from Meta DINOv2 ViT-g/14-reg,
3
- # on the public OpenPath corpus (native 40x tiles). Global batch = 64/GPU x 40 GPU = 2560.
4
- #
5
- # LR regime: near-constant (flat). `epochs` only sets the schedule horizon (very large), so the
6
- # cosine barely descends; the run actually stops at `early_stop`. This keeps LR/wd/momentum
7
- # near their initial values throughout training.
8
- compute_precision:
9
- grad_scaler: false
10
- teacher:
11
- backbone:
12
- sharding_strategy: SHARD_GRAD_OP
13
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
14
- dino_head:
15
- sharding_strategy: SHARD_GRAD_OP
16
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
17
- ibot_head:
18
- sharding_strategy: SHARD_GRAD_OP
19
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
20
- student:
21
- backbone:
22
- sharding_strategy: SHARD_GRAD_OP
23
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
24
- dino_head:
25
- sharding_strategy: SHARD_GRAD_OP
26
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
27
- ibot_head:
28
- sharding_strategy: SHARD_GRAD_OP
29
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
30
- dino:
31
- head_n_prototypes: 131072
32
- head_bottleneck_dim: 384
33
- do_kde: True
34
- kde_loss_weight: .05
35
- koleo_loss_weight: 0
36
- do_koleo: False
37
- ibot:
38
- loss_weight: 1.0
39
- mask_sample_probability: 0.5
40
- mask_ratio_min_max:
41
- - 0.1
42
- - 0.45
43
- separate_head: true
44
- head_n_prototypes: 131072
45
- train:
46
- # OpenPath corpus (taejoon89/openpath-corpus). Point to the downloaded shards:
47
- sample_list_path: "openpath:glob=/path/to/openpath-corpus/*/tiles/shards/w*/*.tar"
48
- streaming_from_hf: false
49
- batch_size_per_gpu: 64 # x 40 GPU = global 2560
50
- centering: sinkhorn_knopp
51
- use_pretrained: True # Meta DINOv2 ViT-g/14-reg warm-start (set MODEL.WEIGHTS)
52
- OFFICIAL_EPOCH_LENGTH: 1250
53
- saveckp_freq: 8 # resume ckpt every 8 x 1250 = 10,000 iters
54
- num_workers: 10
55
- prefetch_factor: 4
56
- skip_checkpointer: false
57
- student:
58
- arch: vit_giant2
59
- patch_size: 14
60
- drop_path_rate: 0.4
61
- ffn_layer: swiglufused
62
- block_chunks: 4
63
- num_register_tokens: 4
64
- interpolate_antialias: true
65
- interpolate_offset: 0.0
66
- teacher:
67
- momentum_teacher: 0.994
68
- warmup_teacher_temp_epochs: 41 # 0.27 epoch = 51,250 iters
69
- optim:
70
- epochs: 8000 # schedule horizon only (=10,000,000 iter); LR/wd/momentum near-constant
71
- early_stop: 276 # actual stop: 276 x 1250 = 345,000 iters (~1 native epoch)
72
- weight_decay_end: 0.2 # not reached within early_stop (wd stays ~0.040)
73
- base_lr: 2.0e-04 # sqrt_wrt_1024 -> effective ~3.16e-4 @ global 2560
74
- warmup_epochs: 9 # ~11,250 iter (~4.9%); must be an integer
75
- freeze_last_layer_epochs: 1
76
- layerwise_decay: 1.0
77
- crops:
78
- local_crops_size: 98
79
- evaluation:
80
- eval_period_iterations: 5750 # save a teacher checkpoint every 5,750 iters
81
- bach_root: ""
82
- breakhis_root: ""
83
- pcam_root: ""
84
-
85
- # Gram anchoring (ported from DINOv3): MSE between L2-normalized patch-token Gram matrices of
86
- # student vs a frozen anchor. Dampens dense-feature degradation during long training.
87
- gram:
88
- use_loss: true
89
- normalized: true
90
- remove_neg: true
91
- loss_weight: 40.0 # ~5-8% of total loss; lower if it over-constrains DINO/iBOT
92
- it_first_update: 57500 # activate near the dense-feature peak
93
- ramp_iters: 3000 # linear ramp of the gram weight
94
- # Anchor = a strong earlier OpenPath teacher checkpoint (see taejoon89/openpath-checkpoints):
95
- ckpt: /path/to/openpath-checkpoints/training_63250/teacher_checkpoint.pth
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitg14_bs64_test.yaml DELETED
@@ -1,84 +0,0 @@
1
- # bs=64 대용량 batch 검증 — full+CPTAC(582.0M) ViT-g/14, 48 GPU, bs=64/GPU(global 3072).
2
- # ★ 공정비교: batch 8배↑ → steps 1/8↓ = 같은 데이터노출. bs=8 full+cptac 137.5k스케줄을 통째로 1/8 축소.
3
- # = OFFICIAL_EPOCH_LENGTH 1250→156(=1250/8), 나머지 epoch-단위 파라미터는 bs=8 config와 동일.
4
- # → 데이터·스케줄위치 완벽 1/8 복제. LR만 sqrt스케일로 자동상향(1.84e-4→5.20e-4).
5
- # 판정: early_stop 80(=12,480 iters ≈ bs8의 100k/8, 동일 38.4M samples)서 정지 → HEST probe.
6
- # bs=8 full+cptac 100k=0.3753(full 100k=0.3834)와 비교. 유지되면 bs=64로 1epoch 최종학습.
7
- # ★ fp16→bf16 전환(아래 compute_precision): 1차 bs=64 런이 iter~8695서 fp16 오버플로로 NaN 크래시
8
- # (loss는 7.9서 평탄·안정하다 단발 NaN=고LR발산 아님). bf16은 fp32 표현범위라 오버플로 원천차단.
9
- # bf16이므로 grad_scaler off(loss scaling 불필요). LR·batch·스케줄은 1차와 동일(bf16만 변경).
10
- compute_precision:
11
- grad_scaler: false # bf16은 loss scaling 불필요 → off
12
- teacher:
13
- backbone:
14
- sharding_strategy: SHARD_GRAD_OP
15
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
16
- dino_head:
17
- sharding_strategy: SHARD_GRAD_OP
18
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
19
- ibot_head:
20
- sharding_strategy: SHARD_GRAD_OP
21
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
22
- student:
23
- backbone:
24
- sharding_strategy: SHARD_GRAD_OP
25
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
26
- dino_head:
27
- sharding_strategy: SHARD_GRAD_OP
28
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
29
- ibot_head:
30
- sharding_strategy: SHARD_GRAD_OP
31
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
32
- dino:
33
- head_n_prototypes: 131072
34
- head_bottleneck_dim: 384
35
- do_kde: True
36
- kde_loss_weight: .05
37
- koleo_loss_weight: 0
38
- do_koleo: False
39
- ibot:
40
- loss_weight: 1.0
41
- mask_sample_probability: 0.5
42
- mask_ratio_min_max:
43
- - 0.1
44
- - 0.45
45
- separate_head: true
46
- head_n_prototypes: 131072
47
- train:
48
- sample_list_path: "openpath:glob=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/tiles/shards/w*/*.tar,/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data_cptac/tiles/shards/w*/*.tar"
49
- streaming_from_hf: false
50
- batch_size_per_gpu: 64 # × 48 GPU = global 3072 (bs=8의 8배)
51
- centering: sinkhorn_knopp
52
- use_pretrained: True # ★ Meta DINOv2 ViT-g/14-reg warm-start
53
- OFFICIAL_EPOCH_LENGTH: 156 # = 1250/8 → 전 스케줄 1/8 축소(데이터노출 동일)
54
- saveckp_freq: 16 # resume ckpt = 16×156 = 2,496 iters
55
- num_workers: 10
56
- prefetch_factor: 4
57
- skip_checkpointer: false
58
- student:
59
- arch: vit_giant2
60
- patch_size: 14
61
- drop_path_rate: 0.4
62
- ffn_layer: swiglufused
63
- block_chunks: 4
64
- num_register_tokens: 4
65
- interpolate_antialias: true
66
- interpolate_offset: 0.0
67
- teacher:
68
- momentum_teacher: 0.994
69
- warmup_teacher_temp_epochs: 30 # = bs=8 동일(30×156=4,680 iters = 37,500/8)
70
- optim:
71
- epochs: 110 # × 156 = 17,160 iters (≈137.5k/8) — schedule 지평
72
- early_stop: 80 # ★ 12,480 iters서 정지(=100k/8, 동일 38.4M samples) → probe
73
- weight_decay_end: 0.2
74
- base_lr: 3.0e-04 # 원래값 유지(LR 안 건드림). NaN 원인은 LR 아닌 fp16 오버플로였음 → bf16으로 해결
75
- warmup_epochs: 10 # = bs=8 동일(10×156=1,560 = 12,500/8)
76
- freeze_last_layer_epochs: 1 # 156 iters (=1,250/8)
77
- layerwise_decay: 1.0
78
- crops:
79
- local_crops_size: 98
80
- evaluation:
81
- eval_period_iterations: 2496 # probe ckpt at 2496,4992,7488,9984,12480 (~5개)
82
- bach_root: ""
83
- breakhis_root: ""
84
- pcam_root: ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitg14_curated.yaml DELETED
@@ -1,57 +0,0 @@
1
- # OpenPath × OpenMidnight — ViT-g/14 warm-start, 우리 mag20 데이터(큐레이션).
2
- # 목적: 2×2의 빈 칸 채우기 = ViT-g × 우리데이터(mag20).
3
- # ViT-L 우리데이터=0.264, ViT-g 그들데이터(native)=0.382. 이 런이 ViT-g 우리데이터.
4
- # 결과로 "남은 격차 중 데이터 해상도 몫"을 분리 → native 재추출 가치 판정.
5
- # 레시피·아키텍처는 openpath_vitg14_testB.yaml과 동일(깨끗한 A/B), 데이터만 우리 TAR(큐레이션 암@20x).
6
- dino:
7
- head_n_prototypes: 131072
8
- head_bottleneck_dim: 384
9
- do_kde: True
10
- kde_loss_weight: .05
11
- koleo_loss_weight: 0
12
- do_koleo: False
13
- ibot:
14
- loss_weight: 1.0
15
- mask_sample_probability: 0.5
16
- mask_ratio_min_max:
17
- - 0.1
18
- - 0.45
19
- separate_head: true
20
- head_n_prototypes: 131072
21
- train:
22
- # openpath: 분기 → openpath_wds.py TAR 로더(interleave=24 수정 = ~22 WSI/배치 다양성).
23
- # 큐레이션: 암 41659 WSI @ 20x (GTEx 제거 + mag20). ViT-L 0.264와 동일 데이터 셀.
24
- sample_list_path: "openpath:glob=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/tiles/shards/w*/*.tar:split=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/splits/pretrain_train_cancer.txt:mag=20"
25
- streaming_from_hf: false
26
- batch_size_per_gpu: 8 # × 48 GPU = global 384 (Test B와 동일)
27
- centering: sinkhorn_knopp
28
- use_pretrained: True # ★ Meta DINOv2 ViT-g/14-reg warm-start
29
- OFFICIAL_EPOCH_LENGTH: 1250
30
- num_workers: 10 # 다운로드 타일워커와 CPU 공유 → 약간 보수적
31
- prefetch_factor: 4
32
- skip_checkpointer: false # resume 가능
33
- student:
34
- arch: vit_giant2
35
- patch_size: 14
36
- drop_path_rate: 0.4
37
- ffn_layer: swiglufused # ★ Meta vitg14 = swiglu
38
- block_chunks: 4
39
- num_register_tokens: 4
40
- interpolate_antialias: true
41
- interpolate_offset: 0.0
42
- teacher:
43
- momentum_teacher: 0.994
44
- optim:
45
- epochs: 110
46
- early_stop: 110
47
- weight_decay_end: 0.2
48
- base_lr: 3.0e-04
49
- warmup_epochs: 10
50
- layerwise_decay: 1.0
51
- crops:
52
- local_crops_size: 98
53
- evaluation:
54
- eval_period_iterations: 2500
55
- bach_root: ""
56
- breakhis_root: ""
57
- pcam_root: ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitg14_final.yaml DELETED
@@ -1,80 +0,0 @@
1
- # ★★ OpenPath 최종 학습 — full+CPTAC(582.0M) ViT-g/14, 48 GPU, bs=64+bf16, 1 EPOCH.
2
- # 검증완료(2026-07-01): bs=64+bf16 @12,480 = 0.3721 ≈ bs=8 @100k 0.3753(품질유지). throughput 4배(~1.8일).
3
- # 1 epoch = 582.0M / global3072 = 189,453 iters. schedule 지평 190,000(OEL 1250 × epochs 152).
4
- # 체크포인트 0.05 epoch(=9,473 iters)마다 = eval_period 9500 → 정확히 20개 teacher_checkpoint(probe용).
5
- # ★ bf16 필수(fp16은 bs=64서 오버플로 NaN). grad_scaler off. LR=sqrt스케일 eff 5.2e-4(검증런 동일).
6
- compute_precision:
7
- grad_scaler: false
8
- teacher:
9
- backbone:
10
- sharding_strategy: SHARD_GRAD_OP
11
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
12
- dino_head:
13
- sharding_strategy: SHARD_GRAD_OP
14
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
15
- ibot_head:
16
- sharding_strategy: SHARD_GRAD_OP
17
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
18
- student:
19
- backbone:
20
- sharding_strategy: SHARD_GRAD_OP
21
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
22
- dino_head:
23
- sharding_strategy: SHARD_GRAD_OP
24
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
25
- ibot_head:
26
- sharding_strategy: SHARD_GRAD_OP
27
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
28
- dino:
29
- head_n_prototypes: 131072
30
- head_bottleneck_dim: 384
31
- do_kde: True
32
- kde_loss_weight: .05
33
- koleo_loss_weight: 0
34
- do_koleo: False
35
- ibot:
36
- loss_weight: 1.0
37
- mask_sample_probability: 0.5
38
- mask_ratio_min_max:
39
- - 0.1
40
- - 0.45
41
- separate_head: true
42
- head_n_prototypes: 131072
43
- train:
44
- sample_list_path: "openpath:glob=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/tiles/shards/w*/*.tar,/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data_cptac/tiles/shards/w*/*.tar"
45
- streaming_from_hf: false
46
- batch_size_per_gpu: 64 # × 48 GPU = global 3072
47
- centering: sinkhorn_knopp
48
- use_pretrained: True # ★ Meta DINOv2 ViT-g/14-reg warm-start
49
- OFFICIAL_EPOCH_LENGTH: 1250
50
- saveckp_freq: 8 # resume ckpt = 8×1250 = 10,000 iters ≈ 0.05 epoch
51
- num_workers: 10
52
- prefetch_factor: 4
53
- skip_checkpointer: false
54
- student:
55
- arch: vit_giant2
56
- patch_size: 14
57
- drop_path_rate: 0.4
58
- ffn_layer: swiglufused
59
- block_chunks: 4
60
- num_register_tokens: 4
61
- interpolate_antialias: true
62
- interpolate_offset: 0.0
63
- teacher:
64
- momentum_teacher: 0.994
65
- warmup_teacher_temp_epochs: 41 # 0.27 epoch (검증 27% 비율) = 51,250 iters
66
- optim:
67
- epochs: 152 # × 1250 = 190,000 iters ≈ 1 epoch(189,453)
68
- early_stop: 152 # 완주(테스트의 early_stop 80 제거)
69
- weight_decay_end: 0.2
70
- base_lr: 3.0e-04 # sqrt_wrt_1024 → 실효 5.20e-4 (검증런 동일)
71
- warmup_epochs: 14 # 0.09 epoch (검증 9% 비율) = 17,500 iters
72
- freeze_last_layer_epochs: 1 # 1250 iters
73
- layerwise_decay: 1.0
74
- crops:
75
- local_crops_size: 98
76
- evaluation:
77
- eval_period_iterations: 9500 # 0.05 epoch마다 teacher_checkpoint(probe용) → 190000/9500 = 정확히 20개
78
- bach_root: ""
79
- breakhis_root: ""
80
- pcam_root: ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitg14_final_1ep.yaml DELETED
@@ -1,59 +0,0 @@
1
- # ★ OpenPath 최종 학습 — full + CPTAC 코퍼스(582.0M 타일) ViT-g/14, 48 GPU, 1 EPOCH.
2
- # 코퍼스: full(566.2M, 전 슬라이드·전 배율) + CPTAC(15.79M, 6암종) = 582.0M 타일.
3
- # 1 epoch 정의: 582.0M / global384 = 1,516,250 iters (OFFICIAL_EPOCH_LENGTH 1250 × epochs 1213).
4
- # per-epoch 단위 파라미터(warmup/ttemp/freeze)는 1250-iter 단위 유지 → 검증레시피(0.3873) 비율을 1ep로 stretch.
5
- # 체크포인트: 0.1 epoch(=151,250 iters)마다 = saveckp_freq 121 + eval_period 151250 → 약 10개.
6
- dino:
7
- head_n_prototypes: 131072
8
- head_bottleneck_dim: 384
9
- do_kde: True
10
- kde_loss_weight: .05
11
- koleo_loss_weight: 0
12
- do_koleo: False
13
- ibot:
14
- loss_weight: 1.0
15
- mask_sample_probability: 0.5
16
- mask_ratio_min_max:
17
- - 0.1
18
- - 0.45
19
- separate_head: true
20
- head_n_prototypes: 131072
21
- train:
22
- # openpath TAR 로더(interleave=24). 콤마구분 다중 glob = full + cptac union. split·mag 필터 없음.
23
- sample_list_path: "openpath:glob=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/tiles/shards/w*/*.tar,/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data_cptac/tiles/shards/w*/*.tar"
24
- streaming_from_hf: false
25
- batch_size_per_gpu: 8 # × 48 GPU = global 384 (검증 레시피 동일)
26
- centering: sinkhorn_knopp
27
- use_pretrained: True # ★ Meta DINOv2 ViT-g/14-reg warm-start
28
- OFFICIAL_EPOCH_LENGTH: 1250 # 스케줄 단위(검증런과 동일) — per-epoch 파라미터 절대 iter값 보존
29
- saveckp_freq: 121 # resume 체크포인트 = 121×1250 = 151,250 iters ≈ 0.1 epoch
30
- num_workers: 10
31
- prefetch_factor: 4
32
- skip_checkpointer: false # resume 가능
33
- student:
34
- arch: vit_giant2
35
- patch_size: 14
36
- drop_path_rate: 0.4
37
- ffn_layer: swiglufused
38
- block_chunks: 4
39
- num_register_tokens: 4
40
- interpolate_antialias: true
41
- interpolate_offset: 0.0
42
- teacher:
43
- momentum_teacher: 0.994
44
- warmup_teacher_temp_epochs: 328 # 0.27 epoch (검증런 30/110=27% 비율 그대로) = 410k iters
45
- optim:
46
- epochs: 1213 # × 1250 = 1,516,250 iters ≈ 1 epoch(582.0M/384)
47
- early_stop: 1213
48
- weight_decay_end: 0.2
49
- base_lr: 3.0e-04 # sqrt_wrt_1024 스케일 → 실효 lr 1.84e-4 (검증런 동일)
50
- warmup_epochs: 109 # 0.09 epoch (검증런 10/110=9% 비율) = 136,250 iters warmup
51
- freeze_last_layer_epochs: 1 # 1250 iters last-layer freeze (검증런 동일, warm-start 안정화)
52
- layerwise_decay: 1.0
53
- crops:
54
- local_crops_size: 98
55
- evaluation:
56
- eval_period_iterations: 151250 # 0.1 epoch마다 teacher_checkpoint 저장(probe용) → ~10개
57
- bach_root: ""
58
- breakhis_root: ""
59
- pcam_root: ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitg14_final_1ep_bs64.yaml DELETED
@@ -1,59 +0,0 @@
1
- # ★ OpenPath 최종학습 후보 — full+CPTAC(582.0M) ViT-g/14, 48 GPU, bs=64/GPU(global 3072), 1 EPOCH.
2
- # 목적: 대용량 batch(3072)가 (1) throughput↑로 epoch 단축, (2) HEST 성능 유지하는지 100k서 검증.
3
- # 1 epoch = 582.0M / 3072 = 189,453 iters (bs=8 1.516M의 1/8 스텝). schedule은 1ep 지평.
4
- # ★ early_stop=80(=100,000 iters)에서 멈춰 판정 → 성능 유지되면 early_stop 늘려 resume·완주.
5
- # 체크포인트 0.1 epoch(=18,750 iters)마다(saveckp_freq 15 + eval_period 18750).
6
- # per-epoch 파라미터는 1250-iter 단위 유지 → 검증레시피(0.3873) 비율을 1ep로 stretch. LR=sqrt스케일 자동상향.
7
- dino:
8
- head_n_prototypes: 131072
9
- head_bottleneck_dim: 384
10
- do_kde: True
11
- kde_loss_weight: .05
12
- koleo_loss_weight: 0
13
- do_koleo: False
14
- ibot:
15
- loss_weight: 1.0
16
- mask_sample_probability: 0.5
17
- mask_ratio_min_max:
18
- - 0.1
19
- - 0.45
20
- separate_head: true
21
- head_n_prototypes: 131072
22
- train:
23
- sample_list_path: "openpath:glob=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/tiles/shards/w*/*.tar,/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data_cptac/tiles/shards/w*/*.tar"
24
- streaming_from_hf: false
25
- batch_size_per_gpu: 64 # × 48 GPU = global 3072
26
- centering: sinkhorn_knopp
27
- use_pretrained: True # ★ Meta DINOv2 ViT-g/14-reg warm-start
28
- OFFICIAL_EPOCH_LENGTH: 1250
29
- saveckp_freq: 15 # resume ckpt = 15×1250 = 18,750 iters ≈ 0.1 epoch
30
- num_workers: 10
31
- prefetch_factor: 4
32
- skip_checkpointer: false
33
- student:
34
- arch: vit_giant2
35
- patch_size: 14
36
- drop_path_rate: 0.4
37
- ffn_layer: swiglufused
38
- block_chunks: 4
39
- num_register_tokens: 4
40
- interpolate_antialias: true
41
- interpolate_offset: 0.0
42
- teacher:
43
- momentum_teacher: 0.994
44
- warmup_teacher_temp_epochs: 41 # 0.27 epoch (검증 27% 비율) = 51,250 iters
45
- optim:
46
- epochs: 152 # × 1250 = 190,000 iters ≈ 1 epoch(189,453) — schedule 지평
47
- early_stop: 80 # ★ 100,000 iters서 정지(bs=64 검증). 통과시 152로 늘려 resume
48
- weight_decay_end: 0.2
49
- base_lr: 3.0e-04 # sqrt_wrt_1024 → 실효 lr 5.20e-4 (global 3072)
50
- warmup_epochs: 14 # 0.09 epoch (검증 9% 비율) = 17,500 iters warmup
51
- freeze_last_layer_epochs: 1 # 1250 iters last-layer freeze (warm-start 안정화)
52
- layerwise_decay: 1.0
53
- crops:
54
- local_crops_size: 98
55
- evaluation:
56
- eval_period_iterations: 18750 # 0.1 epoch마다 teacher_checkpoint(probe용)
57
- bach_root: ""
58
- breakhis_root: ""
59
- pcam_root: ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitg14_full.yaml DELETED
@@ -1,55 +0,0 @@
1
- # 풀코퍼스 ViT-g/14 — 전 슬라이드(GTEx 정상 포함 ~68k) + 전 배율(20x/10x/5x).
2
- # 목적: 큐레이션(암@20x)과 동일 레시피·스케줄(137.5k 코사인)로 데이터만 풀코퍼스 → 100k서 probe.
3
- # 비교기준 = 큐레이션 100k=0.3765. 데이터 다양성(멀티스케일+정상)이 HEST 유지/향상하는지 검증.
4
- # 큐레이션과의 유일 차이 = sample_list_path에서 :split·:mag 필터 제거(전 타일 사용).
5
- dino:
6
- head_n_prototypes: 131072
7
- head_bottleneck_dim: 384
8
- do_kde: True
9
- kde_loss_weight: .05
10
- koleo_loss_weight: 0
11
- do_koleo: False
12
- ibot:
13
- loss_weight: 1.0
14
- mask_sample_probability: 0.5
15
- mask_ratio_min_max:
16
- - 0.1
17
- - 0.45
18
- separate_head: true
19
- head_n_prototypes: 131072
20
- train:
21
- # openpath: TAR 로더(interleave=24). split·mag 필터 없음 → 전 슬라이드·전 배율(565.9M 타일).
22
- sample_list_path: "openpath:glob=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/tiles/shards/w*/*.tar"
23
- streaming_from_hf: false
24
- batch_size_per_gpu: 8 # × 48 GPU = global 384 (큐레이션 동일)
25
- centering: sinkhorn_knopp
26
- use_pretrained: True # ★ Meta DINOv2 ViT-g/14-reg warm-start
27
- OFFICIAL_EPOCH_LENGTH: 1250
28
- num_workers: 10
29
- prefetch_factor: 4
30
- skip_checkpointer: false # resume 가능
31
- student:
32
- arch: vit_giant2
33
- patch_size: 14
34
- drop_path_rate: 0.4
35
- ffn_layer: swiglufused
36
- block_chunks: 4
37
- num_register_tokens: 4
38
- interpolate_antialias: true
39
- interpolate_offset: 0.0
40
- teacher:
41
- momentum_teacher: 0.994
42
- optim:
43
- epochs: 110 # 137.5k 스케줄(큐레이션 동일) — 100k서 probe해 LR시점 일치
44
- early_stop: 110
45
- weight_decay_end: 0.2
46
- base_lr: 3.0e-04
47
- warmup_epochs: 10
48
- layerwise_decay: 1.0
49
- crops:
50
- local_crops_size: 98
51
- evaluation:
52
- eval_period_iterations: 2500
53
- bach_root: ""
54
- breakhis_root: ""
55
- pcam_root: ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitg14_full_cptac.yaml DELETED
@@ -1,55 +0,0 @@
1
- # 풀코퍼스 + CPTAC ViT-g/14 — full(전 슬라이드·전 배율, 565.9M 타일) + CPTAC(15.79M 타일, 6암종).
2
- # 목적: 본모델(full final HEST 0.3873)에 CPTAC 추가가 HEST를 더 올리는지 검증.
3
- # full 대비 유일 차이 = sample_list_path에 data_cptac 샤드 glob을 콤마로 추가(union).
4
- # 레시피·스케줄은 full과 100% 동일(137.5k 코사인, ViT-g warm-start) → 데이터만의 효과를 분리.
5
- dino:
6
- head_n_prototypes: 131072
7
- head_bottleneck_dim: 384
8
- do_kde: True
9
- kde_loss_weight: .05
10
- koleo_loss_weight: 0
11
- do_koleo: False
12
- ibot:
13
- loss_weight: 1.0
14
- mask_sample_probability: 0.5
15
- mask_ratio_min_max:
16
- - 0.1
17
- - 0.45
18
- separate_head: true
19
- head_n_prototypes: 131072
20
- train:
21
- # openpath TAR 로더(interleave=24). 콤마구분 다중 glob = full + cptac union. split·mag 필터 없음.
22
- sample_list_path: "openpath:glob=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/tiles/shards/w*/*.tar,/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data_cptac/tiles/shards/w*/*.tar"
23
- streaming_from_hf: false
24
- batch_size_per_gpu: 8 # × 48 GPU = global 384 (full 동일)
25
- centering: sinkhorn_knopp
26
- use_pretrained: True # ★ Meta DINOv2 ViT-g/14-reg warm-start
27
- OFFICIAL_EPOCH_LENGTH: 1250
28
- num_workers: 10
29
- prefetch_factor: 4
30
- skip_checkpointer: false # resume 가능
31
- student:
32
- arch: vit_giant2
33
- patch_size: 14
34
- drop_path_rate: 0.4
35
- ffn_layer: swiglufused
36
- block_chunks: 4
37
- num_register_tokens: 4
38
- interpolate_antialias: true
39
- interpolate_offset: 0.0
40
- teacher:
41
- momentum_teacher: 0.994
42
- optim:
43
- epochs: 110 # 137.5k 스케줄(full 동일) — 100k서 probe해 LR시점 일치
44
- early_stop: 110
45
- weight_decay_end: 0.2
46
- base_lr: 3.0e-04
47
- warmup_epochs: 10
48
- layerwise_decay: 1.0
49
- crops:
50
- local_crops_size: 98
51
- evaluation:
52
- eval_period_iterations: 2500
53
- bach_root: ""
54
- breakhis_root: ""
55
- pcam_root: ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitg14_run2.yaml DELETED
@@ -1,84 +0,0 @@
1
- # ★★ OpenPath run 2 — full+CPTAC(582M) ViT-g/14, ★40 GPU(TJ-2~6, 5노드), bs=64+bf16, 1 EPOCH.
2
- # TJ-1은 학습 제외(에이전트+실시간 eval watcher 전용). global batch = 64×40 = 2560.
3
- # run1(0.3792 peak@161500 후 하락, 전체최고는 28500=0.3816) 대비 = "OpenMidnight식 LR 레짐"만 교체(코퍼스·bs 동일).
4
- # ① base_lr 3e-4→2e-4 (실효 → 2e-4×sqrt(2560/1024)=3.16e-4, OpenMidnight base값).
5
- # ② ★ anneal 제거: epochs 8000(스케줄 지평만 확대=10M iter) + early_stop 182(=227,500 iter≈1 epoch서 실제 정지).
6
- # → LR이 227.5k/10M=2.3%만 밟아 peak의 ~99.7% 유지(near-constant). wd·teacher-momentum도 near-initial(OM 레짐).
7
- # ③ warmup 5% (warmup_epochs 9.1 = 11,375 iter).
8
- # ④ eval_period 5700 → ~40 teacher_checkpoint(0.025ep마다). TJ-1 watcher가 실시간 HEST 곡선.
9
- # ★ 1 epoch = 582.0M / 2560 = 227,343 iter. 검증필요: 3.16e-4 flat+1ep under-training 여부 → 실시간 곡선으로 조기판단, 상승지속시 early_stop 상향.
10
- compute_precision:
11
- grad_scaler: false
12
- teacher:
13
- backbone:
14
- sharding_strategy: SHARD_GRAD_OP
15
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
16
- dino_head:
17
- sharding_strategy: SHARD_GRAD_OP
18
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
19
- ibot_head:
20
- sharding_strategy: SHARD_GRAD_OP
21
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
22
- student:
23
- backbone:
24
- sharding_strategy: SHARD_GRAD_OP
25
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
26
- dino_head:
27
- sharding_strategy: SHARD_GRAD_OP
28
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
29
- ibot_head:
30
- sharding_strategy: SHARD_GRAD_OP
31
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
32
- dino:
33
- head_n_prototypes: 131072
34
- head_bottleneck_dim: 384
35
- do_kde: True
36
- kde_loss_weight: .05
37
- koleo_loss_weight: 0
38
- do_koleo: False
39
- ibot:
40
- loss_weight: 1.0
41
- mask_sample_probability: 0.5
42
- mask_ratio_min_max:
43
- - 0.1
44
- - 0.45
45
- separate_head: true
46
- head_n_prototypes: 131072
47
- train:
48
- sample_list_path: "openpath:glob=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/tiles/shards/w*/*.tar,/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data_cptac/tiles/shards/w*/*.tar"
49
- streaming_from_hf: false
50
- batch_size_per_gpu: 64 # × 40 GPU(TJ-2~6) = global 2560
51
- centering: sinkhorn_knopp
52
- use_pretrained: True # ★ Meta DINOv2 ViT-g/14-reg warm-start
53
- OFFICIAL_EPOCH_LENGTH: 1250
54
- saveckp_freq: 8 # resume ckpt = 8×1250 = 10,000 iters
55
- num_workers: 10
56
- prefetch_factor: 4
57
- skip_checkpointer: false
58
- student:
59
- arch: vit_giant2
60
- patch_size: 14
61
- drop_path_rate: 0.4
62
- ffn_layer: swiglufused
63
- block_chunks: 4
64
- num_register_tokens: 4
65
- interpolate_antialias: true
66
- interpolate_offset: 0.0
67
- teacher:
68
- momentum_teacher: 0.994
69
- warmup_teacher_temp_epochs: 41 # 0.27 epoch = 51,250 iters (epochs와 독립, run1과 동일)
70
- optim:
71
- epochs: 8000 # ★ 스케줄 지평만 확대(=10,000,000 iter). LR/wd/momentum near-constant.
72
- early_stop: 184 # ★ 정지 = 184×1250 = 230,000 iter ≈ 1.01 epoch. eval_period 5750의 배수(40×5750)=정지점서 최종ckpt 저장
73
- weight_decay_end: 0.2 # 8000ep 지평이라 227.5k선 wd≈0.040(near-initial, 미도달)
74
- base_lr: 2.0e-04 # ★ sqrt_wrt_1024 → 실효 3.16e-4 @batch2560 (OpenMidnight base)
75
- warmup_epochs: 9 # ★ 11,250 iter ≈ 4.9% (정수 필수: np.linspace(...,warmup_iters)가 int만 허용. 9.1이면 crash)
76
- freeze_last_layer_epochs: 1 # 1250 iters
77
- layerwise_decay: 1.0
78
- crops:
79
- local_crops_size: 98
80
- evaluation:
81
- eval_period_iterations: 5750 # ★ teacher_checkpoint 40개(5750×1..40=230,000, 정지점 포함). TJ-1 watcher가 실시간 probe.
82
- bach_root: ""
83
- breakhis_root: ""
84
- pcam_root: ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitg14_run3_gram.yaml DELETED
@@ -1,92 +0,0 @@
1
- # ★★ OpenPath run 2 — full+CPTAC(582M) ViT-g/14, ★40 GPU(TJ-2~6, 5노드), bs=64+bf16, 1 EPOCH.
2
- # TJ-1은 학습 제외(에이전트+실시간 eval watcher 전용). global batch = 64×40 = 2560.
3
- # run1(0.3792 peak@161500 후 하락, 전체최고는 28500=0.3816) 대비 = "OpenMidnight식 LR 레짐"만 교체(코퍼스·bs 동일).
4
- # ① base_lr 3e-4→2e-4 (실효 → 2e-4×sqrt(2560/1024)=3.16e-4, OpenMidnight base값).
5
- # ② ★ anneal 제거: epochs 8000(스케줄 지평만 확대=10M iter) + early_stop 182(=227,500 iter≈1 epoch서 실제 정지).
6
- # → LR이 227.5k/10M=2.3%만 밟아 peak의 ~99.7% 유지(near-constant). wd·teacher-momentum도 near-initial(OM 레짐).
7
- # ③ warmup 5% (warmup_epochs 9.1 = 11,375 iter).
8
- # ④ eval_period 5700 → ~40 teacher_checkpoint(0.025ep마다). TJ-1 watcher가 실시간 HEST 곡선.
9
- # ★ 1 epoch = 582.0M / 2560 = 227,343 iter. 검증필요: 3.16e-4 flat+1ep under-training 여부 → 실시간 곡선으로 조기판단, 상승지속시 early_stop 상향.
10
- compute_precision:
11
- grad_scaler: false
12
- teacher:
13
- backbone:
14
- sharding_strategy: SHARD_GRAD_OP
15
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
16
- dino_head:
17
- sharding_strategy: SHARD_GRAD_OP
18
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
19
- ibot_head:
20
- sharding_strategy: SHARD_GRAD_OP
21
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
22
- student:
23
- backbone:
24
- sharding_strategy: SHARD_GRAD_OP
25
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
26
- dino_head:
27
- sharding_strategy: SHARD_GRAD_OP
28
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
29
- ibot_head:
30
- sharding_strategy: SHARD_GRAD_OP
31
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
32
- dino:
33
- head_n_prototypes: 131072
34
- head_bottleneck_dim: 384
35
- do_kde: True
36
- kde_loss_weight: .05
37
- koleo_loss_weight: 0
38
- do_koleo: False
39
- ibot:
40
- loss_weight: 1.0
41
- mask_sample_probability: 0.5
42
- mask_ratio_min_max:
43
- - 0.1
44
- - 0.45
45
- separate_head: true
46
- head_n_prototypes: 131072
47
- train:
48
- sample_list_path: "openpath:glob=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/tiles/shards/w*/*.tar,/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data_cptac/tiles/shards/w*/*.tar"
49
- streaming_from_hf: false
50
- batch_size_per_gpu: 64 # × 40 GPU(TJ-2~6) = global 2560
51
- centering: sinkhorn_knopp
52
- use_pretrained: True # ★ Meta DINOv2 ViT-g/14-reg warm-start
53
- OFFICIAL_EPOCH_LENGTH: 1250
54
- saveckp_freq: 8 # resume ckpt = 8×1250 = 10,000 iters
55
- num_workers: 10
56
- prefetch_factor: 4
57
- skip_checkpointer: false
58
- student:
59
- arch: vit_giant2
60
- patch_size: 14
61
- drop_path_rate: 0.4
62
- ffn_layer: swiglufused
63
- block_chunks: 4
64
- num_register_tokens: 4
65
- interpolate_antialias: true
66
- interpolate_offset: 0.0
67
- teacher:
68
- momentum_teacher: 0.994
69
- warmup_teacher_temp_epochs: 41 # 0.27 epoch = 51,250 iters (epochs와 독립, run1과 동일)
70
- optim:
71
- epochs: 8000 # ★ 스케줄 지평만 확대(=10,000,000 iter). LR/wd/momentum near-constant.
72
- early_stop: 184 # ★ 정지 = 184×1250 = 230,000 iter ≈ 1.01 epoch. eval_period 5750의 배수(40×5750)=정지점서 최종ckpt 저장
73
- weight_decay_end: 0.2 # 8000ep 지평이라 227.5k선 wd≈0.040(near-initial, 미도달)
74
- base_lr: 2.0e-04 # ★ sqrt_wrt_1024 → 실효 3.16e-4 @batch2560 (OpenMidnight base)
75
- warmup_epochs: 9 # ★ 11,250 iter ≈ 4.9% (정수 필수: np.linspace(...,warmup_iters)가 int만 허용. 9.1이면 crash)
76
- freeze_last_layer_epochs: 1 # 1250 iters
77
- layerwise_decay: 1.0
78
- crops:
79
- local_crops_size: 98
80
- evaluation:
81
- eval_period_iterations: 5750 # ★ teacher_checkpoint 40개(5750×1..40=230,000, 정지점 포함). TJ-1 watcher가 실시간 probe.
82
- bach_root: ""
83
- breakhis_root: ""
84
- pcam_root: ""
85
- gram:
86
- use_loss: true # ★ gram anchoring 켜기
87
- normalized: true
88
- remove_neg: true
89
- loss_weight: 1.0
90
- it_first_update: 34000 # ~0.15 epoch: 우리 peak 부근서 gram 활성 → 이후 하락 방지
91
- ramp_iters: 5000 # 34k~39k 선형 ramp
92
- ckpt: /NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/runs/om_vitg14_final/eval/training_28500/teacher_checkpoint.pth
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitg14_run3_smoke.yaml DELETED
@@ -1,92 +0,0 @@
1
- # ★★ OpenPath run 2 — full+CPTAC(582M) ViT-g/14, ★40 GPU(TJ-2~6, 5노드), bs=64+bf16, 1 EPOCH.
2
- # TJ-1은 학습 제외(에이전트+실시간 eval watcher 전용). global batch = 64×40 = 2560.
3
- # run1(0.3792 peak@161500 후 하락, 전체최고는 28500=0.3816) 대비 = "OpenMidnight식 LR 레짐"만 교체(코퍼스·bs 동일).
4
- # ① base_lr 3e-4→2e-4 (실효 → 2e-4×sqrt(2560/1024)=3.16e-4, OpenMidnight base값).
5
- # ② ★ anneal 제거: epochs 8000(스케줄 지평만 확대=10M iter) + early_stop 182(=227,500 iter≈1 epoch서 실제 정지).
6
- # → LR이 227.5k/10M=2.3%만 밟아 peak의 ~99.7% 유지(near-constant). wd·teacher-momentum도 near-initial(OM 레짐).
7
- # ③ warmup 5% (warmup_epochs 9.1 = 11,375 iter).
8
- # ④ eval_period 5700 → ~40 teacher_checkpoint(0.025ep마다). TJ-1 watcher가 실시간 HEST 곡선.
9
- # ★ 1 epoch = 582.0M / 2560 = 227,343 iter. 검증필요: 3.16e-4 flat+1ep under-training 여부 → 실시간 곡선으로 조기판단, 상승지속시 early_stop 상향.
10
- compute_precision:
11
- grad_scaler: false
12
- teacher:
13
- backbone:
14
- sharding_strategy: SHARD_GRAD_OP
15
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
16
- dino_head:
17
- sharding_strategy: SHARD_GRAD_OP
18
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
19
- ibot_head:
20
- sharding_strategy: SHARD_GRAD_OP
21
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
22
- student:
23
- backbone:
24
- sharding_strategy: SHARD_GRAD_OP
25
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
26
- dino_head:
27
- sharding_strategy: SHARD_GRAD_OP
28
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
29
- ibot_head:
30
- sharding_strategy: SHARD_GRAD_OP
31
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
32
- dino:
33
- head_n_prototypes: 131072
34
- head_bottleneck_dim: 384
35
- do_kde: True
36
- kde_loss_weight: .05
37
- koleo_loss_weight: 0
38
- do_koleo: False
39
- ibot:
40
- loss_weight: 1.0
41
- mask_sample_probability: 0.5
42
- mask_ratio_min_max:
43
- - 0.1
44
- - 0.45
45
- separate_head: true
46
- head_n_prototypes: 131072
47
- train:
48
- sample_list_path: "openpath:glob=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/tiles/shards/w*/*.tar,/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data_cptac/tiles/shards/w*/*.tar"
49
- streaming_from_hf: false
50
- batch_size_per_gpu: 64 # × 40 GPU(TJ-2~6) = global 2560
51
- centering: sinkhorn_knopp
52
- use_pretrained: True # ★ Meta DINOv2 ViT-g/14-reg warm-start
53
- OFFICIAL_EPOCH_LENGTH: 1250
54
- saveckp_freq: 8 # resume ckpt = 8×1250 = 10,000 iters
55
- num_workers: 10
56
- prefetch_factor: 4
57
- skip_checkpointer: false
58
- student:
59
- arch: vit_giant2
60
- patch_size: 14
61
- drop_path_rate: 0.4
62
- ffn_layer: swiglufused
63
- block_chunks: 4
64
- num_register_tokens: 4
65
- interpolate_antialias: true
66
- interpolate_offset: 0.0
67
- teacher:
68
- momentum_teacher: 0.994
69
- warmup_teacher_temp_epochs: 41 # 0.27 epoch = 51,250 iters (epochs와 독립, run1과 동일)
70
- optim:
71
- epochs: 8000 # ★ 스케줄 지평만 확대(=10,000,000 iter). LR/wd/momentum near-constant.
72
- early_stop: 184 # ★ 정지 = 184×1250 = 230,000 iter ≈ 1.01 epoch. eval_period 5750의 배수(40×5750)=정지점서 최종ckpt 저장
73
- weight_decay_end: 0.2 # 8000ep 지평이라 227.5k선 wd≈0.040(near-initial, 미도달)
74
- base_lr: 2.0e-04 # ★ sqrt_wrt_1024 → 실효 3.16e-4 @batch2560 (OpenMidnight base)
75
- warmup_epochs: 9 # ★ 11,250 iter ≈ 4.9% (정수 필수: np.linspace(...,warmup_iters)가 int만 허용. 9.1이면 crash)
76
- freeze_last_layer_epochs: 1 # 1250 iters
77
- layerwise_decay: 1.0
78
- crops:
79
- local_crops_size: 98
80
- evaluation:
81
- eval_period_iterations: 5750 # ★ teacher_checkpoint 40개(5750×1..40=230,000, 정지점 포함). TJ-1 watcher가 실시간 probe.
82
- bach_root: ""
83
- breakhis_root: ""
84
- pcam_root: ""
85
- gram:
86
- use_loss: true # ★ gram anchoring 켜기
87
- normalized: true
88
- remove_neg: true
89
- loss_weight: 1.0
90
- it_first_update: 5
91
- ramp_iters: 3
92
- ckpt: /NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/runs/om_vitg14_final/eval/training_28500/teacher_checkpoint.pth
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitg14_run4_gram.yaml DELETED
@@ -1,92 +0,0 @@
1
- # ★★ OpenPath run 2 — full+CPTAC(582M) ViT-g/14, ★40 GPU(TJ-2~6, 5노드), bs=64+bf16, 1 EPOCH.
2
- # TJ-1은 학습 제외(에이전트+실시간 eval watcher 전용). global batch = 64×40 = 2560.
3
- # run1(0.3792 peak@161500 후 하락, 전체최고는 28500=0.3816) 대비 = "OpenMidnight식 LR 레짐"만 교체(코퍼스·bs 동일).
4
- # ① base_lr 3e-4→2e-4 (실효 → 2e-4×sqrt(2560/1024)=3.16e-4, OpenMidnight base값).
5
- # ② ★ anneal 제거: epochs 8000(스케줄 지평만 확대=10M iter) + early_stop 182(=227,500 iter≈1 epoch서 실제 정지).
6
- # → LR이 227.5k/10M=2.3%만 밟아 peak의 ~99.7% 유지(near-constant). wd·teacher-momentum도 near-initial(OM 레짐).
7
- # ③ warmup 5% (warmup_epochs 9.1 = 11,375 iter).
8
- # ④ eval_period 5700 → ~40 teacher_checkpoint(0.025ep마다). TJ-1 watcher가 실시간 HEST 곡선.
9
- # ★ 1 epoch = 582.0M / 2560 = 227,343 iter. 검증필요: 3.16e-4 flat+1ep under-training 여부 → 실시간 곡선으로 조기판단, 상승지속시 early_stop 상향.
10
- compute_precision:
11
- grad_scaler: false
12
- teacher:
13
- backbone:
14
- sharding_strategy: SHARD_GRAD_OP
15
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
16
- dino_head:
17
- sharding_strategy: SHARD_GRAD_OP
18
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
19
- ibot_head:
20
- sharding_strategy: SHARD_GRAD_OP
21
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
22
- student:
23
- backbone:
24
- sharding_strategy: SHARD_GRAD_OP
25
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
26
- dino_head:
27
- sharding_strategy: SHARD_GRAD_OP
28
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
29
- ibot_head:
30
- sharding_strategy: SHARD_GRAD_OP
31
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
32
- dino:
33
- head_n_prototypes: 131072
34
- head_bottleneck_dim: 384
35
- do_kde: True
36
- kde_loss_weight: .05
37
- koleo_loss_weight: 0
38
- do_koleo: False
39
- ibot:
40
- loss_weight: 1.0
41
- mask_sample_probability: 0.5
42
- mask_ratio_min_max:
43
- - 0.1
44
- - 0.45
45
- separate_head: true
46
- head_n_prototypes: 131072
47
- train:
48
- sample_list_path: "openpath:glob=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/tiles/shards/w*/*.tar,/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data_cptac/tiles/shards/w*/*.tar"
49
- streaming_from_hf: false
50
- batch_size_per_gpu: 64 # × 40 GPU(TJ-2~6) = global 2560
51
- centering: sinkhorn_knopp
52
- use_pretrained: True # ★ Meta DINOv2 ViT-g/14-reg warm-start
53
- OFFICIAL_EPOCH_LENGTH: 1250
54
- saveckp_freq: 8 # resume ckpt = 8×1250 = 10,000 iters
55
- num_workers: 10
56
- prefetch_factor: 4
57
- skip_checkpointer: false
58
- student:
59
- arch: vit_giant2
60
- patch_size: 14
61
- drop_path_rate: 0.4
62
- ffn_layer: swiglufused
63
- block_chunks: 4
64
- num_register_tokens: 4
65
- interpolate_antialias: true
66
- interpolate_offset: 0.0
67
- teacher:
68
- momentum_teacher: 0.994
69
- warmup_teacher_temp_epochs: 41 # 0.27 epoch = 51,250 iters (epochs와 독립, run1과 동일)
70
- optim:
71
- epochs: 8000 # ★ 스케줄 지평만 확대(=10,000,000 iter). LR/wd/momentum near-constant.
72
- early_stop: 184 # ★ 정지 = 184×1250 = 230,000 iter ≈ 1.01 epoch. eval_period 5750의 배수(40×5750)=정지점서 최종ckpt 저장
73
- weight_decay_end: 0.2 # 8000ep 지평이라 227.5k선 wd≈0.040(near-initial, 미도달)
74
- base_lr: 2.0e-04 # ★ sqrt_wrt_1024 → 실효 3.16e-4 @batch2560 (OpenMidnight base)
75
- warmup_epochs: 9 # ★ 11,250 iter ≈ 4.9% (정수 필수: np.linspace(...,warmup_iters)가 int만 허용. 9.1이면 crash)
76
- freeze_last_layer_epochs: 1 # 1250 iters
77
- layerwise_decay: 1.0
78
- crops:
79
- local_crops_size: 98
80
- evaluation:
81
- eval_period_iterations: 5750 # ★ teacher_checkpoint 40개(5750×1..40=230,000, 정지점 포함). TJ-1 watcher가 실시간 probe.
82
- bach_root: ""
83
- breakhis_root: ""
84
- pcam_root: ""
85
- gram:
86
- use_loss: true
87
- normalized: true
88
- remove_neg: true
89
- loss_weight: 20.0 # ★ run3(1.0)의 20배 — 앵커서 ~3.4%, drift시 ~10%+로 물게. 과제약이면 실시간곡선서 감지→하향
90
- it_first_update: 23000 # ★ peak(23k)에서 gram 활성(run3는 34k 지각 활성해 peak·dip 놓침)
91
- ramp_iters: 3000 # 23k~26k ramp
92
- ckpt: /NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/runs/om_vitg14_run3/eval/training_23000/teacher_checkpoint.pth
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitg14_run5_native.yaml DELETED
@@ -1,84 +0,0 @@
1
- # ★★ OpenPath run5 — ★native 40× 재추출 코퍼스(33,991 shard, 암85%) + flat-LR 40GPU + early-stop(gram 없음). 데이터 품질 레버.
2
- # TJ-1은 학습 제외(에이전트+실시간 eval watcher 전용). global batch = 64×40 = 2560.
3
- # run1(0.3792 peak@161500 후 하락, 전체최고는 28500=0.3816) 대비 = "OpenMidnight식 LR 레짐"만 교체(코퍼스·bs 동일).
4
- # ① base_lr 3e-4→2e-4 (실효 → 2e-4×sqrt(2560/1024)=3.16e-4, OpenMidnight base값).
5
- # ② ★ anneal 제거: epochs 8000(스케줄 지평만 확대=10M iter) + early_stop 182(=227,500 iter≈1 epoch서 실제 정지).
6
- # → LR이 227.5k/10M=2.3%만 밟아 peak의 ~99.7% 유지(near-constant). wd·teacher-momentum도 near-initial(OM 레짐).
7
- # ③ warmup 5% (warmup_epochs 9.1 = 11,375 iter).
8
- # ④ eval_period 5700 → ~40 teacher_checkpoint(0.025ep마다). TJ-1 watcher가 실시간 HEST 곡선.
9
- # ★ 1 epoch = 582.0M / 2560 = 227,343 iter. 검증필요: 3.16e-4 flat+1ep under-training 여부 → 실시간 곡선으로 조기판단, 상승지속시 early_stop 상향.
10
- compute_precision:
11
- grad_scaler: false
12
- teacher:
13
- backbone:
14
- sharding_strategy: SHARD_GRAD_OP
15
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
16
- dino_head:
17
- sharding_strategy: SHARD_GRAD_OP
18
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
19
- ibot_head:
20
- sharding_strategy: SHARD_GRAD_OP
21
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
22
- student:
23
- backbone:
24
- sharding_strategy: SHARD_GRAD_OP
25
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
26
- dino_head:
27
- sharding_strategy: SHARD_GRAD_OP
28
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
29
- ibot_head:
30
- sharding_strategy: SHARD_GRAD_OP
31
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
32
- dino:
33
- head_n_prototypes: 131072
34
- head_bottleneck_dim: 384
35
- do_kde: True
36
- kde_loss_weight: .05
37
- koleo_loss_weight: 0
38
- do_koleo: False
39
- ibot:
40
- loss_weight: 1.0
41
- mask_sample_probability: 0.5
42
- mask_ratio_min_max:
43
- - 0.1
44
- - 0.45
45
- separate_head: true
46
- head_n_prototypes: 131072
47
- train:
48
- sample_list_path: "openpath:glob=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data_native/*/tiles/shards/w*/*.tar"
49
- streaming_from_hf: false
50
- batch_size_per_gpu: 64 # × 40 GPU(TJ-2~6) = global 2560
51
- centering: sinkhorn_knopp
52
- use_pretrained: True # ★ Meta DINOv2 ViT-g/14-reg warm-start
53
- OFFICIAL_EPOCH_LENGTH: 1250
54
- saveckp_freq: 8 # resume ckpt = 8×1250 = 10,000 iters
55
- num_workers: 10
56
- prefetch_factor: 4
57
- skip_checkpointer: false
58
- student:
59
- arch: vit_giant2
60
- patch_size: 14
61
- drop_path_rate: 0.4
62
- ffn_layer: swiglufused
63
- block_chunks: 4
64
- num_register_tokens: 4
65
- interpolate_antialias: true
66
- interpolate_offset: 0.0
67
- teacher:
68
- momentum_teacher: 0.994
69
- warmup_teacher_temp_epochs: 41 # 0.27 epoch = 51,250 iters (epochs와 독립, run1과 동일)
70
- optim:
71
- epochs: 8000 # ★ 스케줄 지평만 확대(=10,000,000 iter). LR/wd/momentum near-constant.
72
- early_stop: 276 # ★ native 1 full epoch: 276×1250=345,000 iter(=1.06ep, 5750×60 정렬). run2값 184(230k=0.71ep native) 교정
73
- weight_decay_end: 0.2 # 8000ep 지평이라 227.5k선 wd≈0.040(near-initial, 미도달)
74
- base_lr: 2.0e-04 # ★ sqrt_wrt_1024 → 실효 3.16e-4 @batch2560 (OpenMidnight base)
75
- warmup_epochs: 9 # ★ 11,250 iter ≈ 4.9% (정수 필수: np.linspace(...,warmup_iters)가 int만 허용. 9.1이면 crash)
76
- freeze_last_layer_epochs: 1 # 1250 iters
77
- layerwise_decay: 1.0
78
- crops:
79
- local_crops_size: 98
80
- evaluation:
81
- eval_period_iterations: 5750 # ★ teacher_checkpoint 40개(5750×1..40=230,000, 정지점 포함). TJ-1 watcher가 실시간 probe.
82
- bach_root: ""
83
- breakhis_root: ""
84
- pcam_root: ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitg14_run6_native_gram.yaml DELETED
@@ -1,93 +0,0 @@
1
- # ★★ OpenPath run6 — native 40× 코퍼스 + gram anchoring(weight 40, 앵커=run5 native peak 0.3834). native 하락 방지 시도.
2
- # TJ-1은 학습 제외(에이전트+실시간 eval watcher 전용). global batch = 64×40 = 2560.
3
- # run1(0.3792 peak@161500 후 하락, 전체최고는 28500=0.3816) 대비 = "OpenMidnight식 LR 레짐"만 교체(코퍼스·bs 동일).
4
- # ① base_lr 3e-4→2e-4 (실효 → 2e-4×sqrt(2560/1024)=3.16e-4, OpenMidnight base값).
5
- # ② ★ anneal 제거: epochs 8000(스케줄 지평만 확대=10M iter) + early_stop 182(=227,500 iter≈1 epoch서 실제 정지).
6
- # → LR이 227.5k/10M=2.3%만 밟아 peak의 ~99.7% 유지(near-constant). wd·teacher-momentum도 near-initial(OM 레짐).
7
- # ③ warmup 5% (warmup_epochs 9.1 = 11,375 iter).
8
- # ④ eval_period 5700 → ~40 teacher_checkpoint(0.025ep마다). TJ-1 watcher가 실시간 HEST 곡선.
9
- # ★ 1 epoch = 582.0M / 2560 = 227,343 iter. 검증필요: 3.16e-4 flat+1ep under-training 여부 → 실시간 곡선으로 조기판단, 상승지속시 early_stop 상향.
10
- compute_precision:
11
- grad_scaler: false
12
- teacher:
13
- backbone:
14
- sharding_strategy: SHARD_GRAD_OP
15
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
16
- dino_head:
17
- sharding_strategy: SHARD_GRAD_OP
18
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
19
- ibot_head:
20
- sharding_strategy: SHARD_GRAD_OP
21
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
22
- student:
23
- backbone:
24
- sharding_strategy: SHARD_GRAD_OP
25
- mixed_precision: {param_dtype: bf16, reduce_dtype: bf16, buffer_dtype: fp32}
26
- dino_head:
27
- sharding_strategy: SHARD_GRAD_OP
28
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
29
- ibot_head:
30
- sharding_strategy: SHARD_GRAD_OP
31
- mixed_precision: {param_dtype: bf16, reduce_dtype: fp32, buffer_dtype: fp32}
32
- dino:
33
- head_n_prototypes: 131072
34
- head_bottleneck_dim: 384
35
- do_kde: True
36
- kde_loss_weight: .05
37
- koleo_loss_weight: 0
38
- do_koleo: False
39
- ibot:
40
- loss_weight: 1.0
41
- mask_sample_probability: 0.5
42
- mask_ratio_min_max:
43
- - 0.1
44
- - 0.45
45
- separate_head: true
46
- head_n_prototypes: 131072
47
- train:
48
- sample_list_path: "openpath:glob=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data_native/*/tiles/shards/w*/*.tar"
49
- streaming_from_hf: false
50
- batch_size_per_gpu: 64 # × 40 GPU(TJ-2~6) = global 2560
51
- centering: sinkhorn_knopp
52
- use_pretrained: True # ★ Meta DINOv2 ViT-g/14-reg warm-start
53
- OFFICIAL_EPOCH_LENGTH: 1250
54
- saveckp_freq: 8 # resume ckpt = 8×1250 = 10,000 iters
55
- num_workers: 10
56
- prefetch_factor: 4
57
- skip_checkpointer: false
58
- student:
59
- arch: vit_giant2
60
- patch_size: 14
61
- drop_path_rate: 0.4
62
- ffn_layer: swiglufused
63
- block_chunks: 4
64
- num_register_tokens: 4
65
- interpolate_antialias: true
66
- interpolate_offset: 0.0
67
- teacher:
68
- momentum_teacher: 0.994
69
- warmup_teacher_temp_epochs: 41 # 0.27 epoch = 51,250 iters (epochs와 독립, run1과 동일)
70
- optim:
71
- epochs: 8000 # ★ 스케줄 지평만 확대(=10,000,000 iter). LR/wd/momentum near-constant.
72
- early_stop: 276 # ★ native 1 full epoch: 276×1250=345,000 iter(=1.06ep, 5750×60 정렬). run2값 184(230k=0.71ep native) 교정
73
- weight_decay_end: 0.2 # 8000ep 지평이라 227.5k선 wd≈0.040(near-initial, 미도달)
74
- base_lr: 2.0e-04 # ★ sqrt_wrt_1024 → 실효 3.16e-4 @batch2560 (OpenMidnight base)
75
- warmup_epochs: 9 # ★ 11,250 iter ≈ 4.9% (정수 필수: np.linspace(...,warmup_iters)가 int만 허용. 9.1이면 crash)
76
- freeze_last_layer_epochs: 1 # 1250 iters
77
- layerwise_decay: 1.0
78
- crops:
79
- local_crops_size: 98
80
- evaluation:
81
- eval_period_iterations: 5750 # ★ teacher_checkpoint 40개(5750×1..40=230,000, 정지점 포함). TJ-1 watcher가 실시간 probe.
82
- bach_root: ""
83
- breakhis_root: ""
84
- pcam_root: ""
85
-
86
- gram:
87
- use_loss: true
88
- normalized: true
89
- remove_neg: true
90
- loss_weight: 40.0 # ★ run4(20)의 2배 — native 가파른 하락(0.3834→0.36) 잡으려 강화. 목표 기여 ~5~8%. 과제약이면 실시간곡선서 감지→하향
91
- it_first_update: 57500 # native peak(~63k) 직전 활성 → ramp 후 peak~하락 구간 커버
92
- ramp_iters: 3000 # 57.5k~60.5k ramp
93
- ckpt: /NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/runs/om_vitg14_run5/eval/training_63250/teacher_checkpoint.pth
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitg14_testB.yaml DELETED
@@ -1,54 +0,0 @@
1
- # Test B (ViT-g/14) — 그들 TCGA-12K parquet + 우리 수정 파이프라인.
2
- # 목적: ViT-L Test B가 100k에서 0.299로 0.39 미달 → 아키텍처(ViT-g) 격차인지 검증.
3
- # OpenMidnight vitg14_reg4 레시피 그대로 + 데이터만 parquet(버퍼50000 수정 로더).
4
- # 48 GPU × batch 8 = global 384 (= OpenMidnight 1-node와 동일 batch/LR, 클린 비교).
5
- dino:
6
- head_n_prototypes: 131072
7
- head_bottleneck_dim: 384
8
- do_kde: True
9
- kde_loss_weight: .05
10
- koleo_loss_weight: 0
11
- do_koleo: False
12
- ibot:
13
- loss_weight: 1.0
14
- mask_sample_probability: 0.5
15
- mask_ratio_min_max:
16
- - 0.1
17
- - 0.45
18
- separate_head: true
19
- head_n_prototypes: 131072
20
- train:
21
- sample_list_path: "parquet:glob=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/external/tcga12k_parquet/1/*.parquet"
22
- streaming_from_hf: false
23
- batch_size_per_gpu: 8 # × 48 GPU = global 384 (OpenMidnight 동일)
24
- centering: sinkhorn_knopp
25
- use_pretrained: True # ★ Meta DINOv2 ViT-g/14-reg warm-start
26
- OFFICIAL_EPOCH_LENGTH: 1250
27
- num_workers: 12
28
- prefetch_factor: 4
29
- skip_checkpointer: true
30
- student:
31
- arch: vit_giant2
32
- patch_size: 14
33
- drop_path_rate: 0.4
34
- ffn_layer: swiglufused # ★ Meta vitg14 = swiglu
35
- block_chunks: 4
36
- num_register_tokens: 4
37
- interpolate_antialias: true
38
- interpolate_offset: 0.0
39
- teacher:
40
- momentum_teacher: 0.994
41
- optim:
42
- epochs: 110
43
- early_stop: 110
44
- weight_decay_end: 0.2
45
- base_lr: 3.0e-04
46
- warmup_epochs: 10
47
- layerwise_decay: 1.0
48
- crops:
49
- local_crops_size: 98
50
- evaluation:
51
- eval_period_iterations: 2500
52
- bach_root: ""
53
- breakhis_root: ""
54
- pcam_root: ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitl14_reg4.yaml DELETED
@@ -1,58 +0,0 @@
1
- # OpenPath × OpenMidnight — ViT-L/14 warm-start 검증 런.
2
- # 목적: "Meta DINOv2 warm-start + OpenMidnight 레시피(LR/reg4/sinkhorn/KDE/HED/early-stop)
3
- # + 우리 full-corpus 데이터"가 v2(scratch) 실패를 뒤집는지 단일 노드에서 깨끗이 격리.
4
- # v2 대비 핵심 변경: scratch→warm-start(use_pretrained), reg 0→4, centering→sinkhorn_knopp,
5
- # KoLeo→KDE, 1.475M완주→137.5k early-stop. base_lr는 단일노드(global 384) 기준 보수적.
6
- # 백본: ViT-L/14 (Meta dinov2_vitl14_reg에서 warm-start → ffn_layer MUST be "mlp").
7
- dino:
8
- head_n_prototypes: 131072
9
- head_bottleneck_dim: 384
10
- do_kde: True
11
- kde_loss_weight: .05
12
- koleo_loss_weight: 0
13
- do_koleo: False
14
- ibot:
15
- loss_weight: 1.0
16
- mask_sample_probability: 0.5
17
- mask_ratio_min_max:
18
- - 0.1
19
- - 0.45
20
- separate_head: true
21
- head_n_prototypes: 131072
22
- train:
23
- # openpath: 분기 → dinov2/data/openpath_wds.py (tar shard 스트리밍, full corpus 66k WSI)
24
- sample_list_path: "openpath:glob=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/tiles/shards/w*/*.tar:split=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/splits/pretrain_train.txt"
25
- streaming_from_hf: false
26
- batch_size_per_gpu: 48 # 단일 노드 8 GPU → global 384 (= OpenMidnight 1node와 동일)
27
- centering: sinkhorn_knopp
28
- use_pretrained: True # ★ Meta DINOv2 ViT-L/14-reg warm-start
29
- OFFICIAL_EPOCH_LENGTH: 1250
30
- num_workers: 16
31
- prefetch_factor: 6
32
- skip_checkpointer: false # resume 가능하게(다중 시간 런)
33
- student:
34
- arch: vit_large
35
- patch_size: 14
36
- drop_path_rate: 0.4
37
- ffn_layer: "mlp" # ★ Meta vitl14 = MLP (swiglu 아님) — warm-start 로드 일치 필수
38
- block_chunks: 4
39
- num_register_tokens: 4 # ★ Meta dinov2_vitl14_reg = 4 registers
40
- interpolate_antialias: true
41
- interpolate_offset: 0.0
42
- teacher:
43
- momentum_teacher: 0.994
44
- optim:
45
- epochs: 110 # ×1250 = 137,500 step
46
- early_stop: 110
47
- weight_decay_end: 0.2
48
- base_lr: 3.0e-04 # 보수적(warm-start). 실효 LR = 3e-4×sqrt(384/1024)≈1.84e-4
49
- warmup_epochs: 10
50
- layerwise_decay: 1.0
51
- crops:
52
- local_crops_size: 98
53
- evaluation:
54
- eval_period_iterations: 2500 # teacher 체크포인트 자주 저장(step별 probe용)
55
- # in-training eva 평가는 비활성(빈 root → graceful skip). 평가는 외부 검증 probe로 수행.
56
- bach_root: ""
57
- breakhis_root: ""
58
- pcam_root: ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitl14_reg4_curated.yaml DELETED
@@ -1,58 +0,0 @@
1
- # OpenPath × OpenMidnight — ViT-L/14 warm-start 검증 런.
2
- # 목적: "Meta DINOv2 warm-start + OpenMidnight 레시피(LR/reg4/sinkhorn/KDE/HED/early-stop)
3
- # + 우리 full-corpus 데이터"가 v2(scratch) 실패를 뒤집는지 단일 노드에서 깨끗이 격리.
4
- # v2 대비 핵심 변경: scratch→warm-start(use_pretrained), reg 0→4, centering→sinkhorn_knopp,
5
- # KoLeo→KDE, 1.475M완주→137.5k early-stop. base_lr는 단일노드(global 384) 기준 보수적.
6
- # 백본: ViT-L/14 (Meta dinov2_vitl14_reg에서 warm-start → ffn_layer MUST be "mlp").
7
- dino:
8
- head_n_prototypes: 131072
9
- head_bottleneck_dim: 384
10
- do_kde: True
11
- kde_loss_weight: .05
12
- koleo_loss_weight: 0
13
- do_koleo: False
14
- ibot:
15
- loss_weight: 1.0
16
- mask_sample_probability: 0.5
17
- mask_ratio_min_max:
18
- - 0.1
19
- - 0.45
20
- separate_head: true
21
- head_n_prototypes: 131072
22
- train:
23
- # openpath: 분기 → dinov2/data/openpath_wds.py (tar shard 스트리밍, 큐레이션: 암 41659 WSI @ 20x [GTEx 제거+mag20])
24
- sample_list_path: "openpath:glob=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/tiles/shards/w*/*.tar:split=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/splits/pretrain_train_cancer.txt:mag=20"
25
- streaming_from_hf: false
26
- batch_size_per_gpu: 48 # 단일 노드 8 GPU → global 384 (= OpenMidnight 1node와 동일)
27
- centering: sinkhorn_knopp
28
- use_pretrained: True # ★ Meta DINOv2 ViT-L/14-reg warm-start
29
- OFFICIAL_EPOCH_LENGTH: 1250
30
- num_workers: 16
31
- prefetch_factor: 6
32
- skip_checkpointer: false # resume 가능하게(다중 시간 런)
33
- student:
34
- arch: vit_large
35
- patch_size: 14
36
- drop_path_rate: 0.4
37
- ffn_layer: "mlp" # ★ Meta vitl14 = MLP (swiglu 아님) — warm-start 로드 일치 필수
38
- block_chunks: 4
39
- num_register_tokens: 4 # ★ Meta dinov2_vitl14_reg = 4 registers
40
- interpolate_antialias: true
41
- interpolate_offset: 0.0
42
- teacher:
43
- momentum_teacher: 0.994
44
- optim:
45
- epochs: 110 # ×1250 = 137,500 step
46
- early_stop: 110
47
- weight_decay_end: 0.2
48
- base_lr: 3.0e-04 # 보수적(warm-start). 실효 LR = 3e-4×sqrt(384/1024)≈1.84e-4
49
- warmup_epochs: 10
50
- layerwise_decay: 1.0
51
- crops:
52
- local_crops_size: 98
53
- evaluation:
54
- eval_period_iterations: 2500 # teacher 체크포인트 자주 저장(step별 probe용)
55
- # in-training eva 평가는 비활성(빈 root → graceful skip). 평가는 외부 검증 probe로 수행.
56
- bach_root: ""
57
- breakhis_root: ""
58
- pcam_root: ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/openpath_vitl14_testB.yaml DELETED
@@ -1,59 +0,0 @@
1
- # OpenPath × OpenMidnight — ViT-L/14 warm-start 검증 런.
2
- # 목적: "Meta DINOv2 warm-start + OpenMidnight 레시피(LR/reg4/sinkhorn/KDE/HED/early-stop)
3
- # + 우리 full-corpus 데이터"가 v2(scratch) 실패를 뒤집는지 단일 노드에서 깨끗이 격리.
4
- # v2 대비 핵심 변경: scratch→warm-start(use_pretrained), reg 0→4, centering→sinkhorn_knopp,
5
- # KoLeo→KDE, 1.475M완주→137.5k early-stop. base_lr는 단일노드(global 384) 기준 보수적.
6
- # 백본: ViT-L/14 (Meta dinov2_vitl14_reg에서 warm-start → ffn_layer MUST be "mlp").
7
- dino:
8
- head_n_prototypes: 131072
9
- head_bottleneck_dim: 384
10
- do_kde: True
11
- kde_loss_weight: .05
12
- koleo_loss_weight: 0
13
- do_koleo: False
14
- ibot:
15
- loss_weight: 1.0
16
- mask_sample_probability: 0.5
17
- mask_ratio_min_max:
18
- - 0.1
19
- - 0.45
20
- separate_head: true
21
- head_n_prototypes: 131072
22
- train:
23
- # openpath: 분기 → dinov2/data/openpath_wds.py (tar shard 스트리밍, full corpus 66k WSI)
24
- sample_list_path: "parquet:glob=/NHNHOME/WORKSPACE/0526040027_A/OpenPath/data/external/tcga12k_parquet/1/*.parquet"
25
- streaming_from_hf: false
26
-
27
- batch_size_per_gpu: 48 # 단일 노드 8 GPU → global 384 (= OpenMidnight 1node와 동일)
28
- centering: sinkhorn_knopp
29
- use_pretrained: True # ★ Meta DINOv2 ViT-L/14-reg warm-start
30
- OFFICIAL_EPOCH_LENGTH: 1250
31
- num_workers: 16
32
- prefetch_factor: 6
33
- skip_checkpointer: false # resume 가능하게(다중 시간 런)
34
- student:
35
- arch: vit_large
36
- patch_size: 14
37
- drop_path_rate: 0.4
38
- ffn_layer: "mlp" # ★ Meta vitl14 = MLP (swiglu 아님) — warm-start 로드 일치 필수
39
- block_chunks: 4
40
- num_register_tokens: 4 # ★ Meta dinov2_vitl14_reg = 4 registers
41
- interpolate_antialias: true
42
- interpolate_offset: 0.0
43
- teacher:
44
- momentum_teacher: 0.994
45
- optim:
46
- epochs: 110 # ×1250 = 137,500 step
47
- early_stop: 110
48
- weight_decay_end: 0.2
49
- base_lr: 3.0e-04 # 보수적(warm-start). 실효 LR = 3e-4×sqrt(384/1024)≈1.84e-4
50
- warmup_epochs: 10
51
- layerwise_decay: 1.0
52
- crops:
53
- local_crops_size: 98
54
- evaluation:
55
- eval_period_iterations: 2500 # teacher 체크포인트 자주 저장(step별 probe용)
56
- # in-training eva 평가는 비활성(빈 root → graceful skip). 평가는 외부 검증 probe로 수행.
57
- bach_root: ""
58
- breakhis_root: ""
59
- pcam_root: ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/vitg14_reg4.yaml DELETED
@@ -1,51 +0,0 @@
1
- dino:
2
- head_n_prototypes: 131072
3
- head_bottleneck_dim: 384
4
- do_kde: True
5
- kde_loss_weight: .05
6
- koleo_loss_weight: 0
7
- do_koleo: False
8
- ibot:
9
- loss_weight: 1.0
10
- mask_sample_probability: 0.5
11
- mask_ratio_min_max:
12
- - 0.1
13
- - 0.45
14
- separate_head: true
15
- head_n_prototypes: 131072
16
- train:
17
- sample_list_path: /block/TCGA/sample_dataset_30.txt # gives paths to svs files for data loading if streaming_from_hf=False
18
- streaming_from_hf: false # if True, dataset is streamed from HuggingFace Hub, and sample_list_path is ignored. You should lower num_workers to 4
19
- streaming_dataset_path: medarc/TCGA-12K-parquet
20
- batch_size_per_gpu: 48
21
- centering: sinkhorn_knopp
22
- use_pretrained: True
23
- OFFICIAL_EPOCH_LENGTH: 1250
24
- num_workers: 24 # set to 4 if streaming_from_hf=True
25
- prefetch_factor: 8
26
- skip_checkpointer: true # set True if you don't need to resume training (this yields training speedup and less space usage)
27
- student:
28
- arch: vit_giant2
29
- patch_size: 14
30
- drop_path_rate: 0.4
31
- ffn_layer: swiglufused
32
- block_chunks: 4
33
- num_register_tokens: 4
34
- interpolate_antialias: true # positional embeddings are interpolated with antialiasing
35
- interpolate_offset: 0.0
36
- teacher:
37
- momentum_teacher: 0.994
38
- optim:
39
- epochs: 110 # epochs and early_stop for 137.5k pretraining steps
40
- early_stop: 110
41
- weight_decay_end: 0.2
42
- base_lr: 3.0e-04 # learning rate for vit_giant2
43
- warmup_epochs: 10
44
- layerwise_decay: 1.0
45
- crops:
46
- local_crops_size: 98
47
- evaluation:
48
- eval_period_iterations: 2500 # save checkpoints frequently for evaluation
49
- bach_root: /block/eva-data/bach
50
- breakhis_root: /block/eva-data/breakhis
51
- pcam_root: /block/eva-data/patch_camelyon
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/configs/train/vits14_reg4.yaml DELETED
@@ -1,51 +0,0 @@
1
- dino:
2
- head_n_prototypes: 131072
3
- head_bottleneck_dim: 384
4
- do_kde: True
5
- kde_loss_weight: .05
6
- koleo_loss_weight: 0
7
- do_koleo: False
8
- ibot:
9
- loss_weight: 1.0
10
- mask_sample_probability: 0.5
11
- mask_ratio_min_max:
12
- - 0.1
13
- - 0.45
14
- separate_head: true
15
- head_n_prototypes: 131072
16
- train:
17
- sample_list_path: /block/TCGA/sample_dataset_30.txt # gives paths to svs files for data loading if streaming_from_hf=False
18
- streaming_from_hf: false # if True, dataset is streamed from HuggingFace Hub, and sample_list_path is ignored. You should lower num_workers to 4
19
- streaming_dataset_path: medarc/TCGA-12K-parquet
20
- batch_size_per_gpu: 48
21
- centering: sinkhorn_knopp
22
- use_pretrained: True
23
- OFFICIAL_EPOCH_LENGTH: 1250
24
- num_workers: 24 # set to 4 if streaming_from_hf=True
25
- prefetch_factor: 8
26
- skip_checkpointer: true # set True if you don't need to resume training (this yields training speedup and less space usage)
27
- student:
28
- arch: vit_small
29
- patch_size: 14
30
- drop_path_rate: 0.1 # drop path rate for vit_small
31
- ffn_layer: swiglufused
32
- block_chunks: 4
33
- num_register_tokens: 4
34
- interpolate_antialias: true # positional embeddings are interpolated with antialiasing
35
- interpolate_offset: 0.0
36
- teacher:
37
- momentum_teacher: 0.994
38
- optim:
39
- epochs: 110 # epochs and early_stop for 137.5k pretraining steps
40
- early_stop: 110
41
- weight_decay_end: 0.2
42
- base_lr: 7.0e-04 # learning rate for vit_small
43
- warmup_epochs: 10
44
- layerwise_decay: 1.0
45
- crops:
46
- local_crops_size: 98
47
- evaluation:
48
- eval_period_iterations: 2500 # save checkpoints frequently for evaluation
49
- bach_root: /block/eva-data/bach
50
- breakhis_root: /block/eva-data/breakhis
51
- pcam_root: /block/eva-data/patch_camelyon
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/data/__init__.py DELETED
@@ -1,10 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- from .adapters import DatasetWithEnumeratedTargets
7
- from .loaders import make_data_loader, make_dataset, SamplerType
8
- from .collate import collate_data_and_cast
9
- from .masking import MaskingGenerator
10
- from .augmentations import DataAugmentationDINO
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/data/adapters.py DELETED
@@ -1,28 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- from typing import Any, Tuple
7
-
8
- from torch.utils.data import Dataset
9
-
10
-
11
- class DatasetWithEnumeratedTargets(Dataset):
12
- def __init__(self, dataset):
13
- self._dataset = dataset
14
-
15
- def get_image_data(self, index: int) -> bytes:
16
- return self._dataset.get_image_data(index)
17
-
18
- def get_target(self, index: int) -> Tuple[Any, int]:
19
- target = self._dataset.get_target(index)
20
- return (index, target)
21
-
22
- def __getitem__(self, index: int) -> Tuple[Any, Tuple[Any, int]]:
23
- image, target = self._dataset[index]
24
- target = index if target is None else target
25
- return image, (index, target)
26
-
27
- def __len__(self) -> int:
28
- return len(self._dataset)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/data/augmentations.py DELETED
@@ -1,383 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- import logging
7
- import cv2
8
- import numpy as np
9
- import random
10
- import torch
11
- import torchvision
12
- from torchvision import transforms
13
- from skimage import color as skimage_color
14
- from skimage.color import rgb2hed, hed2rgb
15
- from einops import rearrange
16
- from PIL import Image
17
-
18
- from .transforms import (
19
- GaussianBlur,
20
- make_normalize_transform,
21
- )
22
-
23
- logger = logging.getLogger("dinov2")
24
-
25
-
26
- class RandStainNA(torch.nn.Module):
27
- """
28
- RandStainNA: Random Stain Normalization and Augmentation.
29
- Bridges stain normalization and augmentation by constraining variable stain styles
30
- in a practicable range using virtual template generation.
31
-
32
- Based on: "RandStainNA: Learning Stain-Agnostic Features from Histology Slides
33
- by Bridging Stain Augmentation and Normalization" (MICCAI 2022)
34
-
35
- Reference: https://github.com/yiqings/RandStainNA
36
- """
37
-
38
- # Default statistics for LAB color space from CRC dataset
39
- # These can be overridden by providing a yaml_file
40
- DEFAULT_LAB_STATS = {
41
- 'L': {'avg': {'mean': 158.033, 'std': 48.792}, 'std': {'mean': 36.899, 'std': 14.383}},
42
- 'A': {'avg': {'mean': 151.187, 'std': 10.958}, 'std': {'mean': 8.134, 'std': 2.822}},
43
- 'B': {'avg': {'mean': 116.812, 'std': 6.643}, 'std': {'mean': 6.129, 'std': 2.013}},
44
- }
45
-
46
- # Default statistics for HED color space
47
- DEFAULT_HED_STATS = {
48
- 'H': {'avg': {'mean': 0.05, 'std': 0.02}, 'std': {'mean': 0.03, 'std': 0.01}},
49
- 'E': {'avg': {'mean': 0.02, 'std': 0.01}, 'std': {'mean': 0.02, 'std': 0.008}},
50
- 'D': {'avg': {'mean': 0.0, 'std': 0.005}, 'std': {'mean': 0.01, 'std': 0.005}},
51
- }
52
-
53
- def __init__(
54
- self,
55
- color_space='LAB',
56
- std_hyper=-0.3,
57
- distribution='normal',
58
- probability=1.0,
59
- ):
60
- super().__init__()
61
-
62
- assert distribution in ['normal', 'laplace', 'uniform'], \
63
- f"Unsupported distribution: {distribution}"
64
- assert color_space in ['LAB', 'HSV', 'HED'], \
65
- f"Unsupported color space: {color_space}"
66
-
67
- self.color_space = color_space
68
- self.std_hyper = std_hyper
69
- self.distribution = distribution
70
- self.probability = probability
71
-
72
- if color_space == 'LAB':
73
- stats = self.DEFAULT_LAB_STATS
74
- self.channels = ['L', 'A', 'B']
75
- elif color_space == 'HED':
76
- stats = self.DEFAULT_HED_STATS
77
- self.channels = ['H', 'E', 'D']
78
- else:
79
- stats = self.DEFAULT_LAB_STATS
80
- self.channels = ['H', 'S', 'V']
81
-
82
- self.channel_avgs_mean = [stats[c]['avg']['mean'] for c in self.channels]
83
- self.channel_avgs_std = [stats[c]['avg']['std'] for c in self.channels]
84
- self.channel_stds_mean = [stats[c]['std']['mean'] for c in self.channels]
85
- self.channel_stds_std = [stats[c]['std']['std'] for c in self.channels]
86
-
87
- def _getavgstd(self, image):
88
- """Get mean and std for each channel."""
89
- avgs = []
90
- stds = []
91
- for idx in range(image.shape[2]):
92
- avgs.append(np.mean(image[:, :, idx]))
93
- stds.append(np.std(image[:, :, idx]))
94
- return np.array(avgs), np.array(stds)
95
-
96
- def _normalize(self, img, img_avgs, img_stds, tar_avgs, tar_stds):
97
- """Normalize image to target statistics."""
98
- img_stds = np.clip(img_stds, 0.0001, 255)
99
- img = (img - img_avgs) * (tar_stds / img_stds) + tar_avgs
100
-
101
- if self.color_space in ['LAB', 'HSV']:
102
- img = np.clip(img, 0, 255).astype(np.uint8)
103
-
104
- return img
105
-
106
- def _generate_virtual_template(self):
107
- """Generate virtual template statistics based on distribution."""
108
- tar_avgs = []
109
- tar_stds = []
110
-
111
- if self.distribution == 'uniform':
112
- for idx in range(3):
113
- tar_avg = np.random.uniform(
114
- low=self.channel_avgs_mean[idx] - 3 * self.channel_avgs_std[idx],
115
- high=self.channel_avgs_mean[idx] + 3 * self.channel_avgs_std[idx],
116
- )
117
- tar_std = np.random.uniform(
118
- low=self.channel_stds_mean[idx] - 3 * self.channel_stds_std[idx],
119
- high=self.channel_stds_mean[idx] + 3 * self.channel_stds_std[idx],
120
- )
121
- tar_avgs.append(tar_avg)
122
- tar_stds.append(tar_std)
123
- else:
124
- if self.distribution == 'normal':
125
- np_distribution = np.random.normal
126
- else:
127
- np_distribution = np.random.laplace
128
-
129
- for idx in range(3):
130
- tar_avg = np_distribution(
131
- loc=self.channel_avgs_mean[idx],
132
- scale=self.channel_avgs_std[idx] * (1 + self.std_hyper),
133
- )
134
- tar_std = np_distribution(
135
- loc=self.channel_stds_mean[idx],
136
- scale=self.channel_stds_std[idx] * (1 + self.std_hyper),
137
- )
138
- tar_avgs.append(tar_avg)
139
- tar_stds.append(tar_std)
140
-
141
- return np.array(tar_avgs), np.array(tar_stds)
142
-
143
- def augment(self, img):
144
- """Apply stain augmentation."""
145
- if isinstance(img, Image.Image):
146
- image = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
147
- was_pil = True
148
- else:
149
- image = img
150
- was_pil = False
151
-
152
- # Color space conversion
153
- if self.color_space == 'LAB':
154
- image = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
155
- elif self.color_space == 'HSV':
156
- image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
157
- elif self.color_space == 'HED':
158
- image = skimage_color.rgb2hed(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
159
-
160
- # Generate virtual template and normalize
161
- tar_avgs, tar_stds = self._generate_virtual_template()
162
- img_avgs, img_stds = self._getavgstd(image)
163
-
164
- image = self._normalize(
165
- img=image,
166
- img_avgs=img_avgs,
167
- img_stds=img_stds,
168
- tar_avgs=tar_avgs,
169
- tar_stds=tar_stds,
170
- )
171
-
172
- # Convert back to BGR/RGB
173
- if self.color_space == 'LAB':
174
- image = cv2.cvtColor(image, cv2.COLOR_LAB2BGR)
175
- elif self.color_space == 'HSV':
176
- image = cv2.cvtColor(image, cv2.COLOR_HSV2BGR)
177
- elif self.color_space == 'HED':
178
- nimg = skimage_color.hed2rgb(image)
179
- imin = nimg.min()
180
- imax = nimg.max()
181
- rsimg = (255 * (nimg - imin) / (imax - imin + 1e-8)).astype('uint8')
182
- image = cv2.cvtColor(rsimg, cv2.COLOR_RGB2BGR)
183
-
184
- # Convert back to RGB for PIL
185
- image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
186
-
187
- if was_pil:
188
- return Image.fromarray(image)
189
- return image
190
-
191
- def forward(self, img):
192
- if random.random() > self.probability:
193
- return img
194
- return self.augment(img)
195
-
196
-
197
- class hed_mod(torch.nn.Module):
198
- """
199
- HED color space augmentation for H&E stained histopathology images.
200
- Randomly perturbs Hematoxylin, Eosin, and DAB channels.
201
- """
202
-
203
- def __init__(self, probability=0.5, perturbation_range=0.05):
204
- super().__init__()
205
- self.probability = probability
206
- self.mini = -perturbation_range
207
- self.maxi = perturbation_range
208
-
209
- def forward(self, img, label=None):
210
- if random.random() > self.probability:
211
- return img
212
-
213
- if img is not None:
214
- img = torchvision.transforms.functional.pil_to_tensor(img)
215
- img = rearrange(img, 'c h w -> h w c')
216
- hed_image = rgb2hed(img)
217
-
218
- hed_image[..., 0] += random.uniform(self.mini, self.maxi) # H
219
- hed_image[..., 1] += random.uniform(self.mini, self.maxi) # E
220
- hed_image[..., 2] += random.uniform(self.mini, self.maxi) # D
221
-
222
- hed_image = np.clip(hed_image, 0, 1)
223
- img = hed2rgb(hed_image)
224
-
225
- img = rearrange(img, 'h w c -> c h w')
226
- img = torch.from_numpy(img)
227
- img = torchvision.transforms.functional.to_pil_image(img)
228
-
229
- if label is not None:
230
- label = rearrange(label, 'c h w -> h w c')
231
- hed_image = rgb2hed(label)
232
- hed_image[..., 0] += random.uniform(self.mini, self.maxi)
233
- hed_image[..., 1] += random.uniform(self.mini, self.maxi)
234
- hed_image[..., 2] += random.uniform(self.mini, self.maxi)
235
- label = rearrange(label, 'h w c -> c h w')
236
- label = torch.from_numpy(label)
237
- return img, label
238
-
239
- return img
240
-
241
-
242
- class RandomRotation90(torch.nn.Module):
243
- """
244
- Random 90-degree rotation augmentation for histopathology images.
245
- Pathology images are rotation-invariant, so we can apply 0, 90, 180, or 270 degree rotations.
246
- """
247
-
248
- def __init__(self):
249
- super().__init__()
250
- self.angles = [0, 90, 180, 270]
251
-
252
- def forward(self, img):
253
- angle = random.choice(self.angles)
254
- if angle == 0:
255
- return img
256
- return transforms.functional.rotate(img, angle)
257
-
258
-
259
- class DataAugmentationDINO(object):
260
- """
261
- Data augmentation pipeline for DINOv2 training on histopathology images.
262
-
263
- Includes pathology-specific augmentations:
264
- - RandStainNA: Stain normalization/augmentation in LAB color space
265
- - HED augmentation: Color space perturbation
266
- - 90-degree rotations: Rotation invariance
267
- - Vertical and horizontal flips
268
- - Gaussian blur
269
- - Color jitter (no grayscale for H&E images)
270
- """
271
-
272
- def __init__(
273
- self,
274
- global_crops_scale,
275
- local_crops_scale,
276
- local_crops_number,
277
- global_crops_size=224,
278
- local_crops_size=96,
279
- ):
280
- self.global_crops_scale = global_crops_scale
281
- self.local_crops_scale = local_crops_scale
282
- self.local_crops_number = local_crops_number
283
- self.global_crops_size = global_crops_size
284
- self.local_crops_size = local_crops_size
285
-
286
- logger.info("###################################")
287
- logger.info("Using data augmentation parameters:")
288
- logger.info(f"global_crops_scale: {global_crops_scale}")
289
- logger.info(f"local_crops_scale: {local_crops_scale}")
290
- logger.info(f"local_crops_number: {local_crops_number}")
291
- logger.info(f"global_crops_size: {global_crops_size}")
292
- logger.info(f"local_crops_size: {local_crops_size}")
293
- logger.info("###################################")
294
-
295
- # Geometric augmentations with rotation and both flips
296
- self.geometric_augmentation_global = transforms.Compose(
297
- [
298
- # RandomRotation90(),
299
- transforms.RandomResizedCrop(
300
- global_crops_size, scale=global_crops_scale, interpolation=transforms.InterpolationMode.BICUBIC
301
- ),
302
- transforms.RandomHorizontalFlip(p=0.5),
303
- # transforms.RandomVerticalFlip(p=0.5),
304
- ]
305
- )
306
-
307
- self.geometric_augmentation_local = transforms.Compose(
308
- [
309
- # RandomRotation90(),
310
- transforms.RandomResizedCrop(
311
- local_crops_size, scale=local_crops_scale, interpolation=transforms.InterpolationMode.BICUBIC
312
- ),
313
- transforms.RandomHorizontalFlip(p=0.5),
314
- # transforms.RandomVerticalFlip(p=0.5),
315
- ]
316
- )
317
-
318
- # Normalization (ImageNet stats used by default)
319
- self.normalize = transforms.Compose(
320
- [
321
- transforms.ToTensor(),
322
- make_normalize_transform(),
323
- ]
324
- )
325
-
326
- # Pathology-specific stain augmentations
327
- randstainna = RandStainNA(
328
- color_space='LAB',
329
- std_hyper=-0.3,
330
- distribution='normal',
331
- probability=0.5,
332
- )
333
-
334
- hed_aug = hed_mod(probability=0.5, perturbation_range=0.05)
335
-
336
- self.global_transfo1 = transforms.Compose([
337
- # randstainna,
338
- hed_aug,
339
- transforms.RandomApply([transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.1, hue=0.05)], p=0.8),
340
- transforms.RandomGrayscale(p=0.2),
341
- GaussianBlur(p=1.0),
342
- self.normalize
343
- ])
344
-
345
- self.global_transfo2 = transforms.Compose([
346
- # randstainna,
347
- hed_aug,
348
- transforms.RandomApply([transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.1, hue=0.05)], p=0.8),
349
- transforms.RandomGrayscale(p=0.2),
350
- GaussianBlur(p=0.1),
351
- self.normalize
352
- ])
353
-
354
- self.local_transfo = transforms.Compose([
355
- # randstainna,
356
- hed_aug,
357
- transforms.RandomApply([transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.1, hue=0.05)], p=0.8),
358
- transforms.RandomGrayscale(p=0.2),
359
- GaussianBlur(p=0.5),
360
- self.normalize
361
- ])
362
-
363
- def __call__(self, image):
364
- output = {}
365
-
366
- # Global crops
367
- im1_base = self.geometric_augmentation_global(image)
368
- global_crop_1 = self.global_transfo1(im1_base)
369
-
370
- im2_base = self.geometric_augmentation_global(image)
371
- global_crop_2 = self.global_transfo2(im2_base)
372
-
373
- output["global_crops"] = [global_crop_1, global_crop_2]
374
- output["global_crops_teacher"] = [global_crop_1, global_crop_2]
375
-
376
- # Local crops
377
- local_crops = [
378
- self.local_transfo(self.geometric_augmentation_local(image)) for _ in range(self.local_crops_number)
379
- ]
380
- output["local_crops"] = local_crops
381
- output["offsets"] = ()
382
-
383
- return output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/data/collate.py DELETED
@@ -1,62 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- import torch
7
- import random
8
-
9
-
10
- def collate_data_and_cast(samples_list, mask_ratio_tuple, mask_probability, dtype, n_tokens=None, mask_generator=None):
11
- # dtype = torch.half # TODO: Remove
12
-
13
- #So this is our batch basically, just in a list.
14
-
15
- #print(samples_list[-1][-1])
16
-
17
- images = []
18
- indexes = []
19
- for sample in samples_list:
20
- images.append(sample[0])
21
- indexes.append(sample[1])
22
-
23
- samples_list = images
24
-
25
- n_global_crops = len(samples_list[0][0]["global_crops"])
26
- n_local_crops = len(samples_list[0][0]["local_crops"])
27
-
28
- collated_global_crops = torch.stack([s[0]["global_crops"][i] for i in range(n_global_crops) for s in samples_list])
29
-
30
- collated_local_crops = torch.stack([s[0]["local_crops"][i] for i in range(n_local_crops) for s in samples_list])
31
-
32
- B = len(collated_global_crops)
33
- N = n_tokens
34
- n_samples_masked = int(B * mask_probability)
35
- probs = torch.linspace(*mask_ratio_tuple, n_samples_masked + 1)
36
- upperbound = 0
37
- masks_list = []
38
- for i in range(0, n_samples_masked):
39
- prob_min = probs[i]
40
- prob_max = probs[i + 1]
41
- masks_list.append(torch.BoolTensor(mask_generator(int(N * random.uniform(prob_min, prob_max)))))
42
- upperbound += int(N * prob_max)
43
- for i in range(n_samples_masked, B):
44
- masks_list.append(torch.BoolTensor(mask_generator(0)))
45
-
46
- random.shuffle(masks_list)
47
-
48
- collated_masks = torch.stack(masks_list).flatten(1)
49
- mask_indices_list = collated_masks.flatten().nonzero().flatten()
50
-
51
- masks_weight = (1 / collated_masks.sum(-1).clamp(min=1.0)).unsqueeze(-1).expand_as(collated_masks)[collated_masks]
52
-
53
- return {
54
- "collated_global_crops": collated_global_crops.to(dtype),
55
- "collated_local_crops": collated_local_crops.to(dtype),
56
- "collated_masks": collated_masks,
57
- "mask_indices_list": mask_indices_list,
58
- "masks_weight": masks_weight,
59
- "upperbound": upperbound,
60
- "n_masked_patches": torch.full((1,), fill_value=mask_indices_list.shape[0], dtype=torch.long),
61
- "indexes": indexes
62
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/data/datasets/__init__.py DELETED
@@ -1,9 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- from .image_net import ImageNet
7
- from .image_net_22k import ImageNet22k
8
- from .test_data import TestVisionDataset
9
- from .slide_dataset import SlideDataset
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/data/datasets/decoders.py DELETED
@@ -1,31 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- from io import BytesIO
7
- from typing import Any
8
-
9
- from PIL import Image
10
-
11
-
12
- class Decoder:
13
- def decode(self) -> Any:
14
- raise NotImplementedError
15
-
16
-
17
- class ImageDataDecoder(Decoder):
18
- def __init__(self, image_data: bytes) -> None:
19
- self._image_data = image_data
20
-
21
- def decode(self) -> Image:
22
- f = BytesIO(self._image_data)
23
- return Image.open(f).convert(mode="RGB")
24
-
25
-
26
- class TargetDecoder(Decoder):
27
- def __init__(self, target: Any):
28
- self._target = target
29
-
30
- def decode(self) -> Any:
31
- return self._target
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/data/datasets/extended.py DELETED
@@ -1,38 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- from typing import Any, Tuple
7
-
8
- from torchvision.datasets import VisionDataset
9
-
10
- from .decoders import TargetDecoder, ImageDataDecoder
11
-
12
-
13
- class ExtendedVisionDataset(VisionDataset):
14
- def __init__(self, *args, **kwargs) -> None:
15
- super().__init__(*args, **kwargs) # type: ignore
16
-
17
- def get_image_data(self, index: int) -> bytes:
18
- raise NotImplementedError
19
-
20
- def get_target(self, index: int) -> Any:
21
- raise NotImplementedError
22
-
23
- def __getitem__(self, index: int) -> Tuple[Any, Any]:
24
- try:
25
- image_data = self.get_image_data(index)
26
- image = ImageDataDecoder(image_data).decode()
27
- except Exception as e:
28
- raise RuntimeError(f"can not read image for sample {index}") from e
29
- target = self.get_target(index)
30
- target = TargetDecoder(target).decode()
31
-
32
- if self.transforms is not None:
33
- image, target = self.transforms(image, target)
34
-
35
- return image, target
36
-
37
- def __len__(self) -> int:
38
- raise NotImplementedError
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/data/datasets/image_net.py DELETED
@@ -1,290 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- import csv
7
- from enum import Enum
8
- import logging
9
- import os
10
- from typing import Callable, List, Optional, Tuple, Union
11
-
12
- import numpy as np
13
-
14
- from .extended import ExtendedVisionDataset
15
-
16
-
17
- logger = logging.getLogger("dinov2")
18
- _Target = int
19
-
20
-
21
- class _Split(Enum):
22
- TRAIN = "train"
23
- VAL = "val"
24
- TEST = "test" # NOTE: torchvision does not support the test split
25
-
26
- @property
27
- def length(self) -> int:
28
- split_lengths = {
29
- _Split.TRAIN: 1_281_167,
30
- _Split.VAL: 50_000,
31
- _Split.TEST: 100_000,
32
- }
33
- return split_lengths[self]
34
-
35
- def get_dirname(self, class_id: Optional[str] = None) -> str:
36
- return self.value if class_id is None else os.path.join(self.value, class_id)
37
-
38
- def get_image_relpath(self, actual_index: int, class_id: Optional[str] = None) -> str:
39
- dirname = self.get_dirname(class_id)
40
- if self == _Split.TRAIN:
41
- basename = f"{class_id}_{actual_index}"
42
- else: # self in (_Split.VAL, _Split.TEST):
43
- basename = f"ILSVRC2012_{self.value}_{actual_index:08d}"
44
- return os.path.join(dirname, basename + ".JPEG")
45
-
46
- def parse_image_relpath(self, image_relpath: str) -> Tuple[str, int]:
47
- assert self != _Split.TEST
48
- dirname, filename = os.path.split(image_relpath)
49
- class_id = os.path.split(dirname)[-1]
50
- basename, _ = os.path.splitext(filename)
51
- actual_index = int(basename.split("_")[-1])
52
- return class_id, actual_index
53
-
54
-
55
- class ImageNet(ExtendedVisionDataset):
56
- Target = Union[_Target]
57
- Split = Union[_Split]
58
-
59
- def __init__(
60
- self,
61
- *,
62
- split: "ImageNet.Split",
63
- root: str,
64
- extra: str,
65
- transforms: Optional[Callable] = None,
66
- transform: Optional[Callable] = None,
67
- target_transform: Optional[Callable] = None,
68
- ) -> None:
69
- super().__init__(root, transforms, transform, target_transform)
70
- self._extra_root = extra
71
- self._split = split
72
-
73
- self._entries = None
74
- self._class_ids = None
75
- self._class_names = None
76
-
77
- @property
78
- def split(self) -> "ImageNet.Split":
79
- return self._split
80
-
81
- def _get_extra_full_path(self, extra_path: str) -> str:
82
- return os.path.join(self._extra_root, extra_path)
83
-
84
- def _load_extra(self, extra_path: str) -> np.ndarray:
85
- extra_full_path = self._get_extra_full_path(extra_path)
86
- return np.load(extra_full_path, mmap_mode="r")
87
-
88
- def _save_extra(self, extra_array: np.ndarray, extra_path: str) -> None:
89
- extra_full_path = self._get_extra_full_path(extra_path)
90
- os.makedirs(self._extra_root, exist_ok=True)
91
- np.save(extra_full_path, extra_array)
92
-
93
- @property
94
- def _entries_path(self) -> str:
95
- return f"entries-{self._split.value.upper()}.npy"
96
-
97
- @property
98
- def _class_ids_path(self) -> str:
99
- return f"class-ids-{self._split.value.upper()}.npy"
100
-
101
- @property
102
- def _class_names_path(self) -> str:
103
- return f"class-names-{self._split.value.upper()}.npy"
104
-
105
- def _get_entries(self) -> np.ndarray:
106
- if self._entries is None:
107
- self._entries = self._load_extra(self._entries_path)
108
- assert self._entries is not None
109
- return self._entries
110
-
111
- def _get_class_ids(self) -> np.ndarray:
112
- if self._split == _Split.TEST:
113
- assert False, "Class IDs are not available in TEST split"
114
- if self._class_ids is None:
115
- self._class_ids = self._load_extra(self._class_ids_path)
116
- assert self._class_ids is not None
117
- return self._class_ids
118
-
119
- def _get_class_names(self) -> np.ndarray:
120
- if self._split == _Split.TEST:
121
- assert False, "Class names are not available in TEST split"
122
- if self._class_names is None:
123
- self._class_names = self._load_extra(self._class_names_path)
124
- assert self._class_names is not None
125
- return self._class_names
126
-
127
- def find_class_id(self, class_index: int) -> str:
128
- class_ids = self._get_class_ids()
129
- return str(class_ids[class_index])
130
-
131
- def find_class_name(self, class_index: int) -> str:
132
- class_names = self._get_class_names()
133
- return str(class_names[class_index])
134
-
135
- def get_image_data(self, index: int) -> bytes:
136
- entries = self._get_entries()
137
- actual_index = entries[index]["actual_index"]
138
-
139
- class_id = self.get_class_id(index)
140
-
141
- image_relpath = self.split.get_image_relpath(actual_index, class_id)
142
- image_full_path = os.path.join(self.root, image_relpath)
143
- with open(image_full_path, mode="rb") as f:
144
- image_data = f.read()
145
- return image_data
146
-
147
- def get_target(self, index: int) -> Optional[Target]:
148
- entries = self._get_entries()
149
- class_index = entries[index]["class_index"]
150
- return None if self.split == _Split.TEST else int(class_index)
151
-
152
- def get_targets(self) -> Optional[np.ndarray]:
153
- entries = self._get_entries()
154
- return None if self.split == _Split.TEST else entries["class_index"]
155
-
156
- def get_class_id(self, index: int) -> Optional[str]:
157
- entries = self._get_entries()
158
- class_id = entries[index]["class_id"]
159
- return None if self.split == _Split.TEST else str(class_id)
160
-
161
- def get_class_name(self, index: int) -> Optional[str]:
162
- entries = self._get_entries()
163
- class_name = entries[index]["class_name"]
164
- return None if self.split == _Split.TEST else str(class_name)
165
-
166
- def __len__(self) -> int:
167
- entries = self._get_entries()
168
- assert len(entries) == self.split.length
169
- return len(entries)
170
-
171
- def _load_labels(self, labels_path: str) -> List[Tuple[str, str]]:
172
- labels_full_path = os.path.join(self.root, labels_path)
173
- labels = []
174
-
175
- try:
176
- with open(labels_full_path, "r") as f:
177
- reader = csv.reader(f)
178
- for row in reader:
179
- class_id, class_name = row
180
- labels.append((class_id, class_name))
181
- except OSError as e:
182
- raise RuntimeError(f'can not read labels file "{labels_full_path}"') from e
183
-
184
- return labels
185
-
186
- def _dump_entries(self) -> None:
187
- split = self.split
188
- if split == ImageNet.Split.TEST:
189
- dataset = None
190
- sample_count = split.length
191
- max_class_id_length, max_class_name_length = 0, 0
192
- else:
193
- labels_path = "labels.txt"
194
- logger.info(f'loading labels from "{labels_path}"')
195
- labels = self._load_labels(labels_path)
196
-
197
- # NOTE: Using torchvision ImageFolder for consistency
198
- from torchvision.datasets import ImageFolder
199
-
200
- dataset_root = os.path.join(self.root, split.get_dirname())
201
- dataset = ImageFolder(dataset_root)
202
- sample_count = len(dataset)
203
- max_class_id_length, max_class_name_length = -1, -1
204
- for sample in dataset.samples:
205
- _, class_index = sample
206
- class_id, class_name = labels[class_index]
207
- max_class_id_length = max(len(class_id), max_class_id_length)
208
- max_class_name_length = max(len(class_name), max_class_name_length)
209
-
210
- dtype = np.dtype(
211
- [
212
- ("actual_index", "<u4"),
213
- ("class_index", "<u4"),
214
- ("class_id", f"U{max_class_id_length}"),
215
- ("class_name", f"U{max_class_name_length}"),
216
- ]
217
- )
218
- entries_array = np.empty(sample_count, dtype=dtype)
219
-
220
- if split == ImageNet.Split.TEST:
221
- old_percent = -1
222
- for index in range(sample_count):
223
- percent = 100 * (index + 1) // sample_count
224
- if percent > old_percent:
225
- logger.info(f"creating entries: {percent}%")
226
- old_percent = percent
227
-
228
- actual_index = index + 1
229
- class_index = np.uint32(-1)
230
- class_id, class_name = "", ""
231
- entries_array[index] = (actual_index, class_index, class_id, class_name)
232
- else:
233
- class_names = {class_id: class_name for class_id, class_name in labels}
234
-
235
- assert dataset
236
- old_percent = -1
237
- for index in range(sample_count):
238
- percent = 100 * (index + 1) // sample_count
239
- if percent > old_percent:
240
- logger.info(f"creating entries: {percent}%")
241
- old_percent = percent
242
-
243
- image_full_path, class_index = dataset.samples[index]
244
- image_relpath = os.path.relpath(image_full_path, self.root)
245
- class_id, actual_index = split.parse_image_relpath(image_relpath)
246
- class_name = class_names[class_id]
247
- entries_array[index] = (actual_index, class_index, class_id, class_name)
248
-
249
- logger.info(f'saving entries to "{self._entries_path}"')
250
- self._save_extra(entries_array, self._entries_path)
251
-
252
- def _dump_class_ids_and_names(self) -> None:
253
- split = self.split
254
- if split == ImageNet.Split.TEST:
255
- return
256
-
257
- entries_array = self._load_extra(self._entries_path)
258
-
259
- max_class_id_length, max_class_name_length, max_class_index = -1, -1, -1
260
- for entry in entries_array:
261
- class_index, class_id, class_name = (
262
- entry["class_index"],
263
- entry["class_id"],
264
- entry["class_name"],
265
- )
266
- max_class_index = max(int(class_index), max_class_index)
267
- max_class_id_length = max(len(str(class_id)), max_class_id_length)
268
- max_class_name_length = max(len(str(class_name)), max_class_name_length)
269
-
270
- class_count = max_class_index + 1
271
- class_ids_array = np.empty(class_count, dtype=f"U{max_class_id_length}")
272
- class_names_array = np.empty(class_count, dtype=f"U{max_class_name_length}")
273
- for entry in entries_array:
274
- class_index, class_id, class_name = (
275
- entry["class_index"],
276
- entry["class_id"],
277
- entry["class_name"],
278
- )
279
- class_ids_array[class_index] = class_id
280
- class_names_array[class_index] = class_name
281
-
282
- logger.info(f'saving class IDs to "{self._class_ids_path}"')
283
- self._save_extra(class_ids_array, self._class_ids_path)
284
-
285
- logger.info(f'saving class names to "{self._class_names_path}"')
286
- self._save_extra(class_names_array, self._class_names_path)
287
-
288
- def dump_extra(self) -> None:
289
- self._dump_entries()
290
- self._dump_class_ids_and_names()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/data/datasets/image_net_22k.py DELETED
@@ -1,302 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- from dataclasses import dataclass
7
- from enum import Enum
8
- from functools import lru_cache
9
- from gzip import GzipFile
10
- from io import BytesIO
11
- from mmap import ACCESS_READ, mmap
12
- import os
13
- from typing import Any, Callable, List, Optional, Set, Tuple
14
- import warnings
15
-
16
- import numpy as np
17
-
18
- from .extended import ExtendedVisionDataset
19
-
20
-
21
- _Labels = int
22
-
23
- _DEFAULT_MMAP_CACHE_SIZE = 16 # Warning: This can exhaust file descriptors
24
-
25
-
26
- @dataclass
27
- class _ClassEntry:
28
- block_offset: int
29
- maybe_filename: Optional[str] = None
30
-
31
-
32
- @dataclass
33
- class _Entry:
34
- class_index: int # noqa: E701
35
- start_offset: int
36
- end_offset: int
37
- filename: str
38
-
39
-
40
- class _Split(Enum):
41
- TRAIN = "train"
42
- VAL = "val"
43
-
44
- @property
45
- def length(self) -> int:
46
- return {
47
- _Split.TRAIN: 11_797_647,
48
- _Split.VAL: 561_050,
49
- }[self]
50
-
51
- def entries_path(self):
52
- return f"imagenet21kp_{self.value}.txt"
53
-
54
-
55
- def _get_tarball_path(class_id: str) -> str:
56
- return f"{class_id}.tar"
57
-
58
-
59
- def _make_mmap_tarball(tarballs_root: str, mmap_cache_size: int):
60
- @lru_cache(maxsize=mmap_cache_size)
61
- def _mmap_tarball(class_id: str) -> mmap:
62
- tarball_path = _get_tarball_path(class_id)
63
- tarball_full_path = os.path.join(tarballs_root, tarball_path)
64
- with open(tarball_full_path) as f:
65
- return mmap(fileno=f.fileno(), length=0, access=ACCESS_READ)
66
-
67
- return _mmap_tarball
68
-
69
-
70
- class ImageNet22k(ExtendedVisionDataset):
71
- _GZIPPED_INDICES: Set[int] = {
72
- 841_545,
73
- 1_304_131,
74
- 2_437_921,
75
- 2_672_079,
76
- 2_795_676,
77
- 2_969_786,
78
- 6_902_965,
79
- 6_903_550,
80
- 6_903_628,
81
- 7_432_557,
82
- 7_432_589,
83
- 7_813_809,
84
- 8_329_633,
85
- 10_296_990,
86
- 10_417_652,
87
- 10_492_265,
88
- 10_598_078,
89
- 10_782_398,
90
- 10_902_612,
91
- 11_203_736,
92
- 11_342_890,
93
- 11_397_596,
94
- 11_589_762,
95
- 11_705_103,
96
- 12_936_875,
97
- 13_289_782,
98
- }
99
- Labels = _Labels
100
-
101
- def __init__(
102
- self,
103
- *,
104
- root: str,
105
- extra: str,
106
- transforms: Optional[Callable] = None,
107
- transform: Optional[Callable] = None,
108
- target_transform: Optional[Callable] = None,
109
- mmap_cache_size: int = _DEFAULT_MMAP_CACHE_SIZE,
110
- ) -> None:
111
- super().__init__(root, transforms, transform, target_transform)
112
- self._extra_root = extra
113
-
114
- entries_path = self._get_entries_path(root)
115
- self._entries = self._load_extra(entries_path)
116
-
117
- class_ids_path = self._get_class_ids_path(root)
118
- self._class_ids = self._load_extra(class_ids_path)
119
-
120
- self._gzipped_indices = ImageNet22k._GZIPPED_INDICES
121
- self._mmap_tarball = _make_mmap_tarball(self._tarballs_root, mmap_cache_size)
122
-
123
- def _get_entries_path(self, root: Optional[str] = None) -> str:
124
- return "entries.npy"
125
-
126
- def _get_class_ids_path(self, root: Optional[str] = None) -> str:
127
- return "class-ids.npy"
128
-
129
- def _find_class_ids(self, path: str) -> List[str]:
130
- class_ids = []
131
-
132
- with os.scandir(path) as entries:
133
- for entry in entries:
134
- root, ext = os.path.splitext(entry.name)
135
- if ext != ".tar":
136
- continue
137
- class_ids.append(root)
138
-
139
- return sorted(class_ids)
140
-
141
- def _load_entries_class_ids(self, root: Optional[str] = None) -> Tuple[List[_Entry], List[str]]:
142
- root = self.get_root(root)
143
- entries: List[_Entry] = []
144
- class_ids = self._find_class_ids(root)
145
-
146
- for class_index, class_id in enumerate(class_ids):
147
- path = os.path.join(root, "blocks", f"{class_id}.log")
148
- class_entries = []
149
-
150
- try:
151
- with open(path) as f:
152
- for line in f:
153
- line = line.rstrip()
154
- block, filename = line.split(":")
155
- block_offset = int(block[6:])
156
- filename = filename[1:]
157
-
158
- maybe_filename = None
159
- if filename != "** Block of NULs **":
160
- maybe_filename = filename
161
- _, ext = os.path.splitext(filename)
162
- # assert ext == ".JPEG"
163
-
164
- class_entry = _ClassEntry(block_offset, maybe_filename)
165
- class_entries.append(class_entry)
166
- except OSError as e:
167
- raise RuntimeError(f'can not read blocks file "{path}"') from e
168
-
169
- assert class_entries[-1].maybe_filename is None
170
-
171
- for class_entry1, class_entry2 in zip(class_entries, class_entries[1:]):
172
- assert class_entry1.block_offset <= class_entry2.block_offset
173
- start_offset = 512 * class_entry1.block_offset
174
- end_offset = 512 * class_entry2.block_offset
175
- assert class_entry1.maybe_filename is not None
176
- filename = class_entry1.maybe_filename
177
- entry = _Entry(class_index, start_offset, end_offset, filename)
178
- # Skip invalid image files (PIL throws UnidentifiedImageError)
179
- if filename == "n06470073_47249.JPEG":
180
- continue
181
- entries.append(entry)
182
-
183
- return entries, class_ids
184
-
185
- def _load_extra(self, extra_path: str) -> np.ndarray:
186
- extra_root = self._extra_root
187
- extra_full_path = os.path.join(extra_root, extra_path)
188
- return np.load(extra_full_path, mmap_mode="r")
189
-
190
- def _save_extra(self, extra_array: np.ndarray, extra_path: str) -> None:
191
- extra_root = self._extra_root
192
- extra_full_path = os.path.join(extra_root, extra_path)
193
- os.makedirs(extra_root, exist_ok=True)
194
- np.save(extra_full_path, extra_array)
195
-
196
- @property
197
- def _tarballs_root(self) -> str:
198
- return self.root
199
-
200
- def find_class_id(self, class_index: int) -> str:
201
- return str(self._class_ids[class_index])
202
-
203
- def get_image_data(self, index: int) -> bytes:
204
- entry = self._entries[index]
205
- class_id = entry["class_id"]
206
- class_mmap = self._mmap_tarball(class_id)
207
-
208
- start_offset, end_offset = entry["start_offset"], entry["end_offset"]
209
- try:
210
- mapped_data = class_mmap[start_offset:end_offset]
211
- data = mapped_data[512:] # Skip entry header block
212
-
213
- if len(data) >= 2 and tuple(data[:2]) == (0x1F, 0x8B):
214
- assert index in self._gzipped_indices, f"unexpected gzip header for sample {index}"
215
- with GzipFile(fileobj=BytesIO(data)) as g:
216
- data = g.read()
217
- except Exception as e:
218
- raise RuntimeError(f"can not retrieve image data for sample {index} " f'from "{class_id}" tarball') from e
219
-
220
- return data
221
-
222
- def get_target(self, index: int) -> Any:
223
- return int(self._entries[index]["class_index"])
224
-
225
- def get_targets(self) -> np.ndarray:
226
- return self._entries["class_index"]
227
-
228
- def get_class_id(self, index: int) -> str:
229
- return str(self._entries[index]["class_id"])
230
-
231
- def get_class_ids(self) -> np.ndarray:
232
- return self._entries["class_id"]
233
-
234
- def __getitem__(self, index: int) -> Tuple[Any, Any]:
235
- with warnings.catch_warnings():
236
- warnings.simplefilter("ignore")
237
- return super().__getitem__(index)
238
-
239
- def __len__(self) -> int:
240
- return len(self._entries)
241
-
242
- def _dump_entries(self, *args, **kwargs) -> None:
243
- entries, class_ids = self._load_entries_class_ids(*args, **kwargs)
244
-
245
- max_class_id_length, max_filename_length, max_class_index = -1, -1, -1
246
- for entry in entries:
247
- class_id = class_ids[entry.class_index]
248
- max_class_index = max(entry.class_index, max_class_index)
249
- max_class_id_length = max(len(class_id), max_class_id_length)
250
- max_filename_length = max(len(entry.filename), max_filename_length)
251
-
252
- dtype = np.dtype(
253
- [
254
- ("class_index", "<u4"),
255
- ("class_id", f"U{max_class_id_length}"),
256
- ("start_offset", "<u4"),
257
- ("end_offset", "<u4"),
258
- ("filename", f"U{max_filename_length}"),
259
- ]
260
- )
261
- sample_count = len(entries)
262
- entries_array = np.empty(sample_count, dtype=dtype)
263
- for i, entry in enumerate(entries):
264
- class_index = entry.class_index
265
- class_id = class_ids[class_index]
266
- start_offset = entry.start_offset
267
- end_offset = entry.end_offset
268
- filename = entry.filename
269
- entries_array[i] = (
270
- class_index,
271
- class_id,
272
- start_offset,
273
- end_offset,
274
- filename,
275
- )
276
-
277
- entries_path = self._get_entries_path(*args, **kwargs)
278
- self._save_extra(entries_array, entries_path)
279
-
280
- def _dump_class_ids(self, *args, **kwargs) -> None:
281
- entries_path = self._get_entries_path(*args, **kwargs)
282
- entries_array = self._load_extra(entries_path)
283
-
284
- max_class_id_length, max_class_index = -1, -1
285
- for entry in entries_array:
286
- class_index, class_id = entry["class_index"], entry["class_id"]
287
- max_class_index = max(int(class_index), max_class_index)
288
- max_class_id_length = max(len(str(class_id)), max_class_id_length)
289
-
290
- class_ids_array = np.empty(max_class_index + 1, dtype=f"U{max_class_id_length}")
291
- for entry in entries_array:
292
- class_index, class_id = entry["class_index"], entry["class_id"]
293
- class_ids_array[class_index] = class_id
294
- class_ids_path = self._get_class_ids_path(*args, **kwargs)
295
- self._save_extra(class_ids_array, class_ids_path)
296
-
297
- def _dump_extra(self, *args, **kwargs) -> None:
298
- self._dump_entries(*args, *kwargs)
299
- self._dump_class_ids(*args, *kwargs)
300
-
301
- def dump_extra(self, root: Optional[str] = None) -> None:
302
- return self._dump_extra(root)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/data/datasets/slide_dataset.py DELETED
@@ -1,99 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- # This source code is licensed under the Apache License, Version 2.0
3
- # found in the LICENSE file in the root directory of this source tree.
4
-
5
- import atexit
6
- from collections import OrderedDict
7
- from typing import Any, Tuple
8
-
9
- from .extended import ExtendedVisionDataset
10
- from pathlib import Path
11
- from openslide import OpenSlide
12
- import numpy as np
13
- import cv2
14
-
15
- _SLIDE_CACHE: "OrderedDict[str, OpenSlide]" = OrderedDict()
16
- _SLIDE_CACHE_LIMIT = 512
17
-
18
-
19
- def _close_all_slides():
20
- for slide in _SLIDE_CACHE.values():
21
- slide.close()
22
- _SLIDE_CACHE.clear()
23
-
24
-
25
- atexit.register(_close_all_slides)
26
-
27
-
28
- class SlideDataset(ExtendedVisionDataset):
29
- def __init__(self, root, sample_list_path, *args, **kwargs) -> None:
30
- super().__init__(root, *args, **kwargs)
31
- self.sample_list_path = Path(sample_list_path)
32
- if not self.sample_list_path.is_file():
33
- raise FileNotFoundError(f"Sample list not found at {self.sample_list_path}")
34
-
35
- with self.sample_list_path.open("r") as f:
36
- self.image_files = [line.strip() for line in f if line.strip()]
37
-
38
- print(f"This many resolved paths {len(self.image_files)} from {self.sample_list_path}")
39
-
40
- def get_all(self, index):
41
- parts = self.image_files[index].split(" ")
42
- path = parts[0]
43
- image = _SLIDE_CACHE.get(path)
44
- if image is None:
45
- image = OpenSlide(path)
46
- _SLIDE_CACHE[path] = image
47
- if len(_SLIDE_CACHE) > _SLIDE_CACHE_LIMIT:
48
- _, old = _SLIDE_CACHE.popitem(last=False)
49
- old.close()
50
- else:
51
- _SLIDE_CACHE.move_to_end(path)
52
- return image, path
53
-
54
- def __getitem__(self, index: int) -> Tuple[Any, Any]:
55
- path = self.image_files[index]
56
- parts = path.split(" ")
57
- path, x, y, level = parts
58
- x = int(x)
59
- y = int(y)
60
- level = int(level)
61
-
62
- image = _SLIDE_CACHE.get(path)
63
- if image is None:
64
- image = OpenSlide(path)
65
- _SLIDE_CACHE[path] = image
66
- if len(_SLIDE_CACHE) > _SLIDE_CACHE_LIMIT:
67
- _, old = _SLIDE_CACHE.popitem(last=False)
68
- old.close()
69
- else:
70
- _SLIDE_CACHE.move_to_end(path)
71
-
72
- patch_size = 224
73
-
74
- patch = image.read_region((x, y), level=level, size=(patch_size, patch_size))
75
-
76
- res = patch.convert("RGB")
77
- if self.transforms is not None:
78
- return self.transforms(res, None), index
79
-
80
- return res, None, index
81
-
82
- def hsv(self, tile_rgb, patch_size):
83
- tile = np.array(tile_rgb)
84
- tile = cv2.cvtColor(tile, cv2.COLOR_RGB2HSV)
85
- min_ratio = .6
86
-
87
- lower_bound = np.array([90, 8, 103])
88
- upper_bound = np.array([180, 255, 255])
89
-
90
- mask = cv2.inRange(tile, lower_bound, upper_bound)
91
-
92
- ratio = np.count_nonzero(mask) / mask.size
93
- if ratio > min_ratio:
94
- return tile_rgb
95
- else: # ratio failed, reject
96
- return None
97
-
98
- def __len__(self) -> int:
99
- return len(self.image_files)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/data/loaders.py DELETED
@@ -1,229 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- import logging
7
- from enum import Enum
8
- from typing import Any, Callable, List, Optional, TypeVar
9
-
10
- import torch
11
- from torch.utils.data import Sampler
12
-
13
- from .datasets import ImageNet, ImageNet22k, TestVisionDataset, SlideDataset
14
- from .samplers import EpochSampler, InfiniteSampler, ShardedInfiniteSampler
15
-
16
-
17
- logger = logging.getLogger("dinov2")
18
-
19
-
20
- class SamplerType(Enum):
21
- DISTRIBUTED = 0
22
- EPOCH = 1
23
- INFINITE = 2
24
- SHARDED_INFINITE = 3
25
- SHARDED_INFINITE_NEW = 4
26
-
27
-
28
- def _make_bool_str(b: bool) -> str:
29
- return "yes" if b else "no"
30
-
31
-
32
- def _make_sample_transform(image_transform: Optional[Callable] = None, target_transform: Optional[Callable] = None):
33
- def transform(sample):
34
- image, target = sample
35
- if image_transform is not None:
36
- image = image_transform(image)
37
- if target_transform is not None:
38
- target = target_transform(target)
39
- return image, target
40
-
41
- return transform
42
-
43
-
44
- def _parse_dataset_str(dataset_str: str):
45
-
46
- tokens = dataset_str.split(":")
47
-
48
- name = tokens[0]
49
- kwargs = {}
50
-
51
- for token in tokens[1:]:
52
- key, value = token.split("=")
53
- assert key in ("root", "extra", "split", "sample_list_path")
54
- kwargs[key] = value
55
-
56
- if name == "ImageNet":
57
- class_ = ImageNet
58
- if "split" in kwargs:
59
- kwargs["split"] = ImageNet.Split[kwargs["split"]]
60
- elif name == "ImageNet22k":
61
- class_ = ImageNet22k
62
- elif name.lower() == "pathology":
63
- class_ = SlideDataset
64
- print("kwargs", kwargs)
65
- else:
66
- raise ValueError(f'Unsupported dataset "{name}"')
67
-
68
- return class_, kwargs
69
-
70
-
71
- def make_dataset(
72
- *,
73
- dataset_str: str,
74
- transform: Optional[Callable] = None,
75
- target_transform: Optional[Callable] = None,
76
- ):
77
- """
78
- Creates a dataset with the specified parameters.
79
-
80
- Args:
81
- dataset_str: A dataset string description (e.g. ImageNet:split=TRAIN).
82
- transform: A transform to apply to images.
83
- target_transform: A transform to apply to targets.
84
-
85
- Returns:
86
- The created dataset.
87
- """
88
- logger.info(f'using dataset: "{dataset_str}"')
89
-
90
- class_, kwargs = _parse_dataset_str(dataset_str)
91
- dataset = class_(transform=transform, target_transform=target_transform, **kwargs)
92
-
93
- logger.info(f"# of dataset samples: {len(dataset):,d}")
94
-
95
- # Aggregated datasets do not expose (yet) these attributes, so add them.
96
- if not hasattr(dataset, "transform"):
97
- setattr(dataset, "transform", transform)
98
- if not hasattr(dataset, "target_transform"):
99
- setattr(dataset, "target_transform", target_transform)
100
-
101
- return dataset
102
-
103
-
104
- def _make_sampler(
105
- *,
106
- dataset,
107
- type: Optional[SamplerType] = None,
108
- shuffle: bool = False,
109
- seed: int = 0,
110
- size: int = -1,
111
- advance: int = 0,
112
- ) -> Optional[Sampler]:
113
- sample_count = len(dataset)
114
-
115
- if type == SamplerType.INFINITE:
116
- logger.info("sampler: infinite")
117
- if size > 0:
118
- raise ValueError("sampler size > 0 is invalid")
119
- return InfiniteSampler(
120
- sample_count=sample_count,
121
- shuffle=shuffle,
122
- seed=seed,
123
- advance=advance,
124
- )
125
- elif type in (SamplerType.SHARDED_INFINITE, SamplerType.SHARDED_INFINITE_NEW):
126
- logger.info("sampler: sharded infinite")
127
- if size > 0:
128
- raise ValueError("sampler size > 0 is invalid")
129
- # TODO: Remove support for old shuffling
130
- use_new_shuffle_tensor_slice = type == SamplerType.SHARDED_INFINITE_NEW
131
- return ShardedInfiniteSampler(
132
- sample_count=sample_count,
133
- shuffle=shuffle,
134
- seed=seed,
135
- advance=advance,
136
- use_new_shuffle_tensor_slice=use_new_shuffle_tensor_slice,
137
- )
138
- elif type == SamplerType.EPOCH:
139
- logger.info("sampler: epoch")
140
- if advance > 0:
141
- raise NotImplementedError("sampler advance > 0 is not supported")
142
- size = size if size > 0 else sample_count
143
- logger.info(f"# of samples / epoch: {size:,d}")
144
- return EpochSampler(
145
- size=size,
146
- sample_count=sample_count,
147
- shuffle=shuffle,
148
- seed=seed,
149
- )
150
- elif type == SamplerType.DISTRIBUTED:
151
- logger.info("sampler: distributed")
152
- if size > 0:
153
- raise ValueError("sampler size > 0 is invalid")
154
- if advance > 0:
155
- raise ValueError("sampler advance > 0 is invalid")
156
- return torch.utils.data.DistributedSampler(
157
- dataset=dataset,
158
- shuffle=shuffle,
159
- seed=seed,
160
- drop_last=False,
161
- )
162
-
163
- logger.info("sampler: none")
164
- return None
165
-
166
-
167
- T = TypeVar("T")
168
-
169
-
170
- def make_data_loader(
171
- *,
172
- dataset,
173
- batch_size: int,
174
- num_workers: int,
175
- shuffle: bool = True,
176
- seed: int = 0,
177
- sampler_type: Optional[SamplerType] = SamplerType.INFINITE,
178
- sampler_size: int = -1,
179
- sampler_advance: int = 0,
180
- drop_last: bool = True,
181
- persistent_workers: bool = False,
182
- collate_fn: Optional[Callable[[List[T]], Any]] = None,
183
- prefetch_factor: int=2,
184
- ):
185
- """
186
- Creates a data loader with the specified parameters.
187
-
188
- Args:
189
- dataset: A dataset (third party, LaViDa or WebDataset).
190
- batch_size: The size of batches to generate.
191
- num_workers: The number of workers to use.
192
- shuffle: Whether to shuffle samples.
193
- seed: The random seed to use.
194
- sampler_type: Which sampler to use: EPOCH, INFINITE, SHARDED_INFINITE, SHARDED_INFINITE_NEW, DISTRIBUTED or None.
195
- sampler_size: The number of images per epoch (when applicable) or -1 for the entire dataset.
196
- sampler_advance: How many samples to skip (when applicable).
197
- drop_last: Whether the last non-full batch of data should be dropped.
198
- persistent_workers: maintain the workers Dataset instances alive after a dataset has been consumed once.
199
- collate_fn: Function that performs batch collation
200
- """
201
-
202
- sampler = _make_sampler(
203
- dataset=dataset,
204
- type=sampler_type,
205
- shuffle=shuffle,
206
- seed=seed,
207
- size=sampler_size,
208
- advance=sampler_advance,
209
- )
210
-
211
- logger.info("using PyTorch data loader")
212
- data_loader = torch.utils.data.DataLoader(
213
- dataset,
214
- sampler=sampler,
215
- batch_size=batch_size,
216
- num_workers=num_workers,
217
- pin_memory=True,
218
- drop_last=drop_last,
219
- persistent_workers=persistent_workers,
220
- collate_fn=collate_fn,
221
- timeout=600,
222
- prefetch_factor=prefetch_factor,
223
- )
224
-
225
- try:
226
- logger.info(f"# of batches: {len(data_loader):,d}")
227
- except TypeError: # data loader has no length
228
- logger.info("infinite data loader")
229
- return data_loader
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/data/masking.py DELETED
@@ -1,86 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- import random
7
- import math
8
- import numpy as np
9
-
10
-
11
- class MaskingGenerator:
12
- def __init__(
13
- self,
14
- input_size,
15
- num_masking_patches=None,
16
- min_num_patches=4,
17
- max_num_patches=None,
18
- min_aspect=0.3,
19
- max_aspect=None,
20
- ):
21
- if not isinstance(input_size, tuple):
22
- input_size = (input_size,) * 2
23
- self.height, self.width = input_size
24
-
25
- self.num_patches = self.height * self.width
26
- self.num_masking_patches = num_masking_patches
27
-
28
- self.min_num_patches = min_num_patches
29
- self.max_num_patches = num_masking_patches if max_num_patches is None else max_num_patches
30
-
31
- max_aspect = max_aspect or 1 / min_aspect
32
- self.log_aspect_ratio = (math.log(min_aspect), math.log(max_aspect))
33
-
34
- def __repr__(self):
35
- repr_str = "Generator(%d, %d -> [%d ~ %d], max = %d, %.3f ~ %.3f)" % (
36
- self.height,
37
- self.width,
38
- self.min_num_patches,
39
- self.max_num_patches,
40
- self.num_masking_patches,
41
- self.log_aspect_ratio[0],
42
- self.log_aspect_ratio[1],
43
- )
44
- return repr_str
45
-
46
- def get_shape(self):
47
- return self.height, self.width
48
-
49
- def _mask(self, mask, max_mask_patches):
50
- delta = 0
51
- for _ in range(10):
52
- target_area = random.uniform(self.min_num_patches, max_mask_patches)
53
- aspect_ratio = math.exp(random.uniform(*self.log_aspect_ratio))
54
- h = int(round(math.sqrt(target_area * aspect_ratio)))
55
- w = int(round(math.sqrt(target_area / aspect_ratio)))
56
- if w < self.width and h < self.height:
57
- top = random.randint(0, self.height - h)
58
- left = random.randint(0, self.width - w)
59
-
60
- num_masked = mask[top : top + h, left : left + w].sum()
61
- # Overlap
62
- if 0 < h * w - num_masked <= max_mask_patches:
63
- for i in range(top, top + h):
64
- for j in range(left, left + w):
65
- if mask[i, j] == 0:
66
- mask[i, j] = 1
67
- delta += 1
68
-
69
- if delta > 0:
70
- break
71
- return delta
72
-
73
- def __call__(self, num_masking_patches=0):
74
- mask = np.zeros(shape=self.get_shape(), dtype=bool)
75
- mask_count = 0
76
- while mask_count < num_masking_patches:
77
- max_mask_patches = num_masking_patches - mask_count
78
- max_mask_patches = min(max_mask_patches, self.max_num_patches)
79
-
80
- delta = self._mask(mask, max_mask_patches)
81
- if delta == 0:
82
- break
83
- else:
84
- mask_count += delta
85
-
86
- return mask
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/data/openpath_wds.py DELETED
@@ -1,229 +0,0 @@
1
- # OpenPath WebDataset loader for OpenMidnight (DINOv2 fork).
2
- #
3
- # OpenMidnight ships two data paths: HF-parquet streaming and on-the-fly SVS
4
- # patching. OpenPath data is ALREADY pre-patched into WebDataset tar shards
5
- # (`data/tiles/shards/w*/*.tar`, each sample = key.jpg + key.json). This module
6
- # adds a third path that streams those shards and yields samples shaped exactly
7
- # like OpenMidnight's collate expects: `((transform(pil), None), meta)`.
8
- #
9
- # It is self-contained (pure tarfile — no `webdataset` dependency) and replicates
10
- # the proven default behavior of our original loader: resampled random shard
11
- # sampling (with replacement) + a sample-level shuffle buffer + split filtering.
12
- # (The InterleavedShards round-robin variant was falsified downstream, so it is
13
- # intentionally not ported.)
14
- #
15
- # dataset_str format (reuses `cfg.train.sample_list_path`):
16
- # openpath:glob=/abs/shards/w*/*.tar:split=/abs/pretrain_train.txt[:mag=20]
17
- import glob as _glob
18
- import io as _io
19
- import json as _json
20
- import os as _os
21
- import random as _random
22
- import tarfile as _tarfile
23
-
24
- import torch
25
- from PIL import Image
26
-
27
-
28
- def parse_openpath_path(dataset_str):
29
- assert dataset_str.startswith("openpath:"), dataset_str
30
- out = {}
31
- for kv in dataset_str[len("openpath:"):].split(":"):
32
- if not kv:
33
- continue
34
- k, _, v = kv.partition("=")
35
- out[k] = v
36
- assert "glob" in out, "openpath dataset_path requires glob=..."
37
- mag = float(out["mag"]) if out.get("mag") else None
38
- return out["glob"], out.get("split") or None, mag
39
-
40
-
41
- def _iter_shard(path):
42
- """Yield {'__key__','jpg','json'} dicts from a tar shard. A sample's members
43
- (key.jpg, key.json) are contiguous in-tar, so group by key."""
44
- grp, cur = {}, None
45
- try:
46
- with _tarfile.open(path) as tar:
47
- for m in tar:
48
- if not m.isfile():
49
- continue
50
- key, _, ext = m.name.rpartition(".")
51
- if cur is not None and key != cur:
52
- if "jpg" in grp and "json" in grp:
53
- yield grp
54
- grp = {}
55
- cur = key
56
- grp["__key__"] = key
57
- f = tar.extractfile(m)
58
- if f is not None:
59
- grp[ext] = f.read()
60
- if "jpg" in grp and "json" in grp:
61
- yield grp
62
- except Exception:
63
- return
64
-
65
-
66
- class OpenPathWds(torch.utils.data.IterableDataset):
67
- """Infinite, rank/worker-sharded stream of transformed OpenPath tiles.
68
-
69
- Yields `((transform(pil_rgb), None), key)` to match OpenMidnight's
70
- `collate_data_and_cast` (reads sample[0]=(crops_dict,None), sample[1]=meta)."""
71
-
72
- def __init__(self, shards, transform, keep_ids=None, mag=None, shuffle=2000, base_seed=0, interleave=24):
73
- super().__init__()
74
- self.shards = shards
75
- self.transform = transform
76
- self.keep_ids = keep_ids
77
- self.mag = mag
78
- self.shuffle = shuffle
79
- self.base_seed = base_seed
80
- # ★ 각 shard=4-5 WSI 연속타일. K개 shard 동시 round-robin → ~K×4.5 WSI/배치 다양성.
81
- # 필수 — 단일/소수 WSI 배치는 DINO/iBOT centering·sharpening 통계 붕괴(검증됨).
82
- self.interleave = max(1, interleave)
83
-
84
- def _keep(self, raw_json):
85
- if self.keep_ids is None and self.mag is None:
86
- return True
87
- try:
88
- j = _json.loads(raw_json)
89
- except Exception:
90
- return False
91
- if self.keep_ids is not None and j.get("wsi_id") not in self.keep_ids:
92
- return False
93
- if self.mag is not None and j.get("mag") != self.mag:
94
- return False
95
- return True
96
-
97
- def __iter__(self):
98
- wi = torch.utils.data.get_worker_info()
99
- rank = int(_os.environ.get("RANK", 0))
100
- world = int(_os.environ.get("WORLD_SIZE", 1))
101
- wid = wi.id if wi else 0
102
- nw = wi.num_workers if wi else 1
103
- # Per-(rank,worker) RNG so every reader draws an independent shard stream
104
- # (resampled-with-replacement, like wds.WebDataset(resampled=True)).
105
- rng = _random.Random(self.base_seed + rank * 1_000_003 + wid * 9176 + 17)
106
-
107
- buf = []
108
- S = max(self.shuffle, 1)
109
-
110
- def _one_shard_stream():
111
- # 한 shard를 끝까지 흘리고, 소진되면 새 무작위 shard로 교체(무한).
112
- while True:
113
- shard = rng.choice(self.shards)
114
- for s in _iter_shard(shard):
115
- if self._keep(s.get("json", b"")):
116
- yield s
117
-
118
- def gen():
119
- # ★ K개 shard 스트림을 동시에 열어 round-robin → 연속샘플이 서로 다른 shard/WSI에서.
120
- K = min(self.interleave, len(self.shards))
121
- streams = [_one_shard_stream() for _ in range(K)]
122
- while True:
123
- for st in streams:
124
- yield next(st)
125
-
126
- src = gen()
127
- # prime shuffle buffer
128
- for _ in range(S):
129
- buf.append(next(src))
130
- while True:
131
- i = rng.randrange(len(buf))
132
- s = buf[i]
133
- buf[i] = next(src)
134
- try:
135
- img = Image.open(_io.BytesIO(s["jpg"])).convert("RGB")
136
- except Exception:
137
- continue
138
- yield (self.transform(img), None), s["__key__"]
139
-
140
-
141
- def _iter_parquet(path):
142
- """parquet 파일에서 {'jpg','__key__'} 샘플을 yield (image_bytes 컬럼=jpg/png 바이트)."""
143
- import pyarrow.parquet as _pq
144
- try:
145
- t = _pq.read_table(path, columns=["image_bytes", "slide_path", "x", "y"])
146
- cols = t.to_pydict()
147
- ib = cols["image_bytes"]; sp = cols["slide_path"]; xs = cols["x"]; ys = cols["y"]
148
- for i in range(len(ib)):
149
- yield {"jpg": ib[i], "__key__": f"{sp[i]}_{xs[i]}_{ys[i]}"}
150
- except Exception:
151
- return
152
-
153
-
154
- class ParquetTiles(torch.utils.data.IterableDataset):
155
- """parquet 파일 리스트를 resampled-with-replacement로 스트리밍(tar 로더와 동일 패턴)."""
156
- def __init__(self, files, transform, shuffle=1000, base_seed=0):
157
- super().__init__()
158
- self.files = files; self.transform = transform
159
- self.shuffle = shuffle; self.base_seed = base_seed
160
-
161
- def __iter__(self):
162
- wi = torch.utils.data.get_worker_info()
163
- rank = int(_os.environ.get("RANK", 0)); wid = wi.id if wi else 0
164
- rng = _random.Random(self.base_seed + rank * 1_000_003 + wid * 9176 + 17)
165
- S = max(self.shuffle, 1)
166
-
167
- def gen():
168
- while True:
169
- for s in _iter_parquet(rng.choice(self.files)):
170
- yield s
171
- src = gen()
172
- buf = [next(src) for _ in range(S)]
173
- while True:
174
- i = rng.randrange(len(buf)); s = buf[i]; buf[i] = next(src)
175
- try:
176
- img = Image.open(_io.BytesIO(s["jpg"])).convert("RGB")
177
- except Exception:
178
- continue
179
- yield (self.transform(img), None), s["__key__"]
180
-
181
-
182
- def make_openpath_parquet_loader(dataset_str, batch_size, num_workers, data_transform,
183
- collate_fn, shuffle=50000, prefetch_factor=4):
184
- # ★ shuffle=50000(OpenMidnight 동일): 각 parquet=1슬라이드라 큰 버퍼로 ~22슬라이드 혼합
185
- # 필수 — 작은 버퍼는 단일슬라이드 배치 → DINO/iBOT 통계 붕괴.
186
- # dataset_str: "parquet:glob=/abs/**/*.parquet"
187
- glob_pat = dataset_str[len("parquet:"):]
188
- if glob_pat.startswith("glob="):
189
- glob_pat = glob_pat[len("glob="):]
190
- files = sorted(_glob.glob(glob_pat))
191
- if not files:
192
- raise FileNotFoundError(f"no parquet match {glob_pat}")
193
- print(f"[openpath_parquet] files={len(files)}", flush=True)
194
- ds = ParquetTiles(files, data_transform, shuffle=shuffle)
195
- return torch.utils.data.DataLoader(
196
- ds, batch_size=batch_size, num_workers=num_workers, drop_last=True,
197
- pin_memory=True, persistent_workers=num_workers > 0, collate_fn=collate_fn,
198
- prefetch_factor=prefetch_factor if num_workers > 0 else None,
199
- )
200
-
201
-
202
- def make_openpath_loader(dataset_str, batch_size, num_workers, data_transform,
203
- collate_fn, shuffle=1000, prefetch_factor=4):
204
- shard_glob, split_path, mag = parse_openpath_path(dataset_str)
205
- # glob may be a single pattern or several comma-separated ones (e.g. to
206
- # union the base corpus with an extra source like CPTAC living under a
207
- # different data root). dedup in case patterns overlap.
208
- shards = sorted({s for pat in shard_glob.split(",") if pat
209
- for s in _glob.glob(pat)})
210
- if not shards:
211
- raise FileNotFoundError(f"no shards match {shard_glob}")
212
- keep_ids = None
213
- if split_path:
214
- with open(split_path) as f:
215
- keep_ids = set(f.read().split())
216
- print(f"[openpath_wds] shards={len(shards)} split={'Y' if keep_ids else 'N'} mag={mag}",
217
- flush=True)
218
-
219
- ds = OpenPathWds(shards, data_transform, keep_ids=keep_ids, mag=mag, shuffle=shuffle)
220
- return torch.utils.data.DataLoader(
221
- ds,
222
- batch_size=batch_size,
223
- num_workers=num_workers,
224
- drop_last=True,
225
- pin_memory=True,
226
- persistent_workers=num_workers > 0,
227
- collate_fn=collate_fn,
228
- prefetch_factor=prefetch_factor if num_workers > 0 else None,
229
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenMidnight/dinov2/data/samplers.py DELETED
@@ -1,229 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- import itertools
7
- from typing import Any, Optional
8
- import warnings
9
-
10
- import numpy as np
11
- import torch
12
- from torch.utils.data.sampler import Sampler
13
-
14
- import dinov2.distributed as distributed
15
-
16
-
17
- class EpochSampler(Sampler):
18
- def __init__(
19
- self,
20
- *,
21
- size: int,
22
- sample_count: int,
23
- shuffle: bool = False,
24
- seed: int = 0,
25
- start: Optional[int] = None,
26
- step: Optional[int] = None,
27
- ):
28
- self._size = size
29
- self._sample_count = sample_count
30
- self._shuffle = shuffle
31
- self._seed = seed
32
- self._start = distributed.get_global_rank() if start is None else start
33
- self._step = distributed.get_global_size() if step is None else step
34
- self._epoch = 0
35
-
36
- def __iter__(self):
37
- count = (self._size + self._sample_count - 1) // self._sample_count
38
- tiled_indices = np.tile(np.arange(self._sample_count), count)
39
- if self._shuffle:
40
- seed = self._seed * self._epoch if self._seed != 0 else self._epoch
41
- rng = np.random.default_rng(seed)
42
- iterable = rng.choice(tiled_indices, self._size, replace=False)
43
- else:
44
- iterable = tiled_indices[: self._size]
45
-
46
- yield from itertools.islice(iterable, self._start, None, self._step)
47
-
48
- def __len__(self):
49
- return (self._size - self._start + self._step - 1) // self._step
50
-
51
- def set_epoch(self, epoch):
52
- self._epoch = epoch
53
-
54
-
55
- def _get_numpy_dtype(size: int) -> Any:
56
- return np.int32 if size <= 2**31 else np.int64
57
-
58
-
59
- def _get_torch_dtype(size: int) -> Any:
60
- return torch.int32 if size <= 2**31 else torch.int64
61
-
62
-
63
- def _generate_randperm_indices(*, size: int, generator: torch.Generator):
64
- """Generate the indices of a random permutation."""
65
- dtype = _get_torch_dtype(size)
66
- # This is actually matching PyTorch's CPU implementation, see: https://github.com/pytorch/pytorch/blob/master/aten/src/ATen/native/TensorFactories.cpp#L900-L921
67
- perm = torch.arange(size, dtype=dtype)
68
- for i in range(size):
69
- j = torch.randint(i, size, size=(1,), generator=generator).item()
70
-
71
- # Always swap even if no-op
72
- value = perm[j].item()
73
- perm[j] = perm[i].item()
74
- perm[i] = value
75
- yield value
76
-
77
-
78
- class InfiniteSampler(Sampler):
79
- def __init__(
80
- self,
81
- *,
82
- sample_count: int,
83
- shuffle: bool = False,
84
- seed: int = 0,
85
- start: Optional[int] = None,
86
- step: Optional[int] = None,
87
- advance: int = 0,
88
- ):
89
- self._sample_count = sample_count
90
- self._seed = seed
91
- self._shuffle = shuffle
92
- self._start = distributed.get_global_rank() if start is None else start
93
- self._step = distributed.get_global_size() if step is None else step
94
- self._advance = advance
95
-
96
- def __iter__(self):
97
- if self._shuffle:
98
- iterator = self._shuffled_iterator()
99
- else:
100
- iterator = self._iterator()
101
-
102
- yield from itertools.islice(iterator, self._advance, None)
103
-
104
- def _iterator(self):
105
- assert not self._shuffle
106
-
107
- while True:
108
- iterable = range(self._sample_count)
109
- yield from itertools.islice(iterable, self._start, None, self._step)
110
-
111
- def _shuffled_iterator(self):
112
- assert self._shuffle
113
-
114
- # Instantiate a generator here (rather than in the ctor) to keep the class
115
- # picklable (requirement of mp.spawn)
116
- generator = torch.Generator().manual_seed(self._seed)
117
-
118
- while True:
119
- iterable = _generate_randperm_indices(size=self._sample_count, generator=generator)
120
- yield from itertools.islice(iterable, self._start, None, self._step)
121
-
122
-
123
- # The following function is somewhat equivalent to _new_shuffle_tensor_slice below,
124
- # but avoids a full in-place random permutation generation.
125
- def _shuffle_tensor_slice(
126
- *, tensor: torch.Tensor, start: int = 0, step: int = 1, generator: torch.Generator
127
- ) -> np.ndarray:
128
- stop = len(tensor)
129
- count = stop // step
130
- drop_count = stop - step * count
131
- if drop_count:
132
- warnings.warn(f"# of dropped samples: {drop_count}")
133
-
134
- dtype = _get_numpy_dtype(stop)
135
- result = np.empty(count, dtype=dtype)
136
-
137
- for i in range(count):
138
- j = torch.randint(0, i + 1, size=(1,), generator=generator).item() if i > 0 else 0
139
-
140
- result[i] = result[j]
141
- result[j] = tensor[start + i * step].item()
142
-
143
- return result
144
-
145
-
146
- def _new_shuffle_tensor_slice(
147
- *, tensor: torch.Tensor, start: int = 0, step: int = 1, generator: torch.Generator
148
- ) -> np.ndarray:
149
- stop = len(tensor)
150
- count = stop // step
151
- dtype = torch.int64 # Needed for using randperm result as indices
152
- count = stop // step
153
- drop_count = stop - step * count
154
- if drop_count:
155
- warnings.warn(f"# of dropped samples: {drop_count}")
156
- indices = torch.randperm(count, dtype=dtype, generator=generator)
157
- return tensor[start::step][indices].numpy()
158
-
159
-
160
- def _make_seed(seed: int, start: int, iter_count: int) -> int:
161
- # NOTE: Tried a few variants (including iter_count << 32), this one worked best.
162
- return seed + start + (iter_count << 24)
163
-
164
-
165
- class ShardedInfiniteSampler(Sampler):
166
- def __init__(
167
- self,
168
- *,
169
- sample_count: int,
170
- shuffle: bool = False,
171
- seed: int = 0,
172
- start: Optional[int] = None,
173
- step: Optional[int] = None,
174
- advance: int = 0,
175
- use_new_shuffle_tensor_slice: bool = False,
176
- ):
177
- self._sample_count = sample_count
178
- self._seed = seed
179
- self._shuffle = shuffle
180
- self._start = distributed.get_global_rank() if start is None else start
181
- self._step = distributed.get_global_size() if step is None else step
182
- self._advance = advance
183
- self._iter_count = 0
184
- self._shuffle_tensor_slice_fn = (
185
- _new_shuffle_tensor_slice if use_new_shuffle_tensor_slice else _shuffle_tensor_slice
186
- )
187
-
188
- def __iter__(self):
189
- iter_count = self._advance // self._sample_count
190
- if iter_count > 0:
191
- self._advance -= iter_count * self._sample_count
192
- self._iter_count += iter_count
193
-
194
- if self._shuffle:
195
- iterator = self._shuffled_iterator()
196
- else:
197
- iterator = self._iterator()
198
-
199
- yield from itertools.islice(iterator, self._advance, None)
200
-
201
- def _iterator(self):
202
- assert not self._shuffle
203
-
204
- while True:
205
- iterable = range(self._sample_count)
206
- yield from itertools.islice(iterable, self._start, None, self._step)
207
-
208
- def _shuffled_iterator(self):
209
- assert self._shuffle
210
-
211
- # Instantiate a generator here (rather than in the ctor) to be keep the class
212
- # picklable (requirement of mp.spawn)
213
- generator = torch.Generator()
214
-
215
- # Always shuffle everything first
216
- generator.manual_seed(self._seed)
217
- dtype = _get_torch_dtype(self._sample_count)
218
- perm = torch.randperm(self._sample_count, dtype=dtype, generator=generator)
219
-
220
- while True:
221
- # Re-seed on each iteration to allow skipping whole permutations
222
- seed = _make_seed(self._seed, self._start, self._iter_count)
223
- generator.manual_seed(seed)
224
-
225
- iterable = self._shuffle_tensor_slice_fn(
226
- tensor=perm, start=self._start, step=self._step, generator=generator
227
- )
228
- yield from iterable
229
- self._iter_count += 1