slide2vec 5.9.2__tar.gz → 6.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {slide2vec-5.9.2 → slide2vec-6.0.1}/PKG-INFO +38 -112
- slide2vec-6.0.1/README.md +90 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/pyproject.toml +4 -4
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/__init__.py +5 -1
- slide2vec-6.0.1/slide2vec/_cufile.py +21 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/api.py +4 -4
- slide2vec-6.0.1/slide2vec/cufile.json +5 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/distributed/__init__.py +17 -2
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/distributed/direct_embed_worker.py +1 -1
- slide2vec-6.0.1/slide2vec/distributed/pin_gpu.py +47 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/distributed/pipeline_worker.py +1 -1
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/distributed/worker_entry.py +14 -5
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/base.py +43 -21
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/__init__.py +2 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/dinov2.py +8 -5
- slide2vec-6.0.1/slide2vec/encoders/models/dinov3.py +79 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/gigapath.py +9 -7
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/gpfm.py +10 -1
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/mstar.py +1 -1
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/registry.py +17 -5
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/inference.py +11 -1
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/artifacts_collect.py +1 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/batching.py +11 -1
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_encoder_input.py +0 -12
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_image_stage.py +1 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_regions.py +3 -2
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_stage.py +1 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/distributed.py +19 -3
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/distributed_stage.py +2 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/embedding.py +4 -1
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/embedding_persist.py +3 -1
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/encoder_input_contract.py +8 -2
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/image_stage.py +1 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/model_settings.py +2 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/patient_pipeline.py +3 -1
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/persist_callbacks.py +99 -18
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/pooled_encoder_input.py +9 -28
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/types.py +3 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec.egg-info/PKG-INFO +38 -112
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec.egg-info/SOURCES.txt +9 -1
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec.egg-info/requires.txt +1 -1
- slide2vec-6.0.1/tests/test_cufile_compat_mode.py +50 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_encoder_input.py +3 -3
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_image_stage.py +3 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dinov2_natimage.py +3 -3
- slide2vec-6.0.1/tests/test_dinov3_natimage.py +188 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_encoder_capabilities.py +8 -9
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_encoder_input_contract.py +50 -8
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_encoder_plugins.py +4 -1
- slide2vec-6.0.1/tests/test_encoder_preprocessing.py +368 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_encoder_provider_failures.py +3 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_encoder_registry.py +73 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_patch_size_metadata.py +1 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_pooled_encoder_input.py +60 -44
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_pooled_geometry.py +62 -24
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_regression_core.py +1 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_regression_inference.py +240 -1
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_regression_models.py +1 -1
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_rudolfv2.py +2 -1
- slide2vec-6.0.1/tests/test_worker_gpu_pinning.py +234 -0
- slide2vec-5.9.2/README.md +0 -164
- {slide2vec-5.9.2 → slide2vec-6.0.1}/LICENSE +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/setup.cfg +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/__main__.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/artifacts.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/cli.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/configs/default.yaml +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/configs/resources.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/data/__init__.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/data/dataset.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/data/tile_reader.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/distributed/dense_image_worker.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/distributed/dense_worker.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/distributed/image_worker.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/__init__.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/conch.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/genbio.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/hibou.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/isight.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/lunit.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/midnight.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/moozy/__init__.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/moozy/blocks.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/moozy/case.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/moozy/loading.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/moozy/slide.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/moozy/types.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/phikon.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/prism2.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/rudolfv2.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/titan.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/virchow.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/waiv.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/progress.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/__init__.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/cpu_budget.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_encode.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_image_reading.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_image_recipe.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_image_shard.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_shard.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_sliding.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/effective_encoder_input.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/embedding_pipeline.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/hierarchical.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/image_shard.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/image_specs.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/manifest.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/persistence.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/preprocessing.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/process_list.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/progress_bridge.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/registry.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/serialization.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/sharding.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/slide_encode.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/tiling.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/tiling_pipeline.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/worker_io.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/utils/config.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/utils/log_utils.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/utils/tiling_io.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/utils/utils.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_architecture_runtime_split.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_attention_extraction.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_benchmark_tooling.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_encode_kit.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_extraction.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_image_reading.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_image_resume.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_image_shard.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_regions.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_shard.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_sliding.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_source_spacing.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_stage.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_worker.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_gpfm_genbio_heavy.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_hs2p_package_cutover.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_image_shard.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_image_stage.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_inference_benchmark.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_isight.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_load_model_hf_auth.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_mascaret.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_on_slide_persisted.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_output_consistency.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_patient_manifest.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_phaet.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_prism.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_prism2.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_process_list_performance.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_progress.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_runtime_batching.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_sharding.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_slide_coordinate_preparation.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_soma_migration.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_tile_store.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_tiling_pipeline.py +0 -0
- {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_titan.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slide2vec
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6.0.1
|
|
4
4
|
Summary: Embedding of whole slide images with Foundation Models
|
|
5
5
|
Author-email: Clément Grisi <clement.grisi@radboudumc.nl>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -80,7 +80,7 @@ Requires-Dist: wandb; extra == "fm"
|
|
|
80
80
|
Requires-Dist: torch<2.8,>=2.3; extra == "fm"
|
|
81
81
|
Requires-Dist: torchvision>=0.18.0; extra == "fm"
|
|
82
82
|
Requires-Dist: einops>=0.8.0; extra == "fm"
|
|
83
|
-
Requires-Dist: timm>=1.0.
|
|
83
|
+
Requires-Dist: timm>=1.0.20; extra == "fm"
|
|
84
84
|
Requires-Dist: huggingface_hub<1.0,>=0.30.0; extra == "fm"
|
|
85
85
|
Requires-Dist: environs; extra == "fm"
|
|
86
86
|
Requires-Dist: einops-exts>=0.0.4; extra == "fm"
|
|
@@ -119,162 +119,88 @@ Dynamic: license-file
|
|
|
119
119
|
[](https://pypi.org/project/slide2vec/)
|
|
120
120
|
[](https://clemsgrs.github.io/slide2vec/)
|
|
121
121
|
|
|
122
|
-
`slide2vec`
|
|
122
|
+
`slide2vec` encodes whole-slide images with publicly available pathology foundation models. It uses [`hs2p`](https://pypi.org/project/hs2p/) for tissue detection and tiling, and handles batching, multi-GPU execution, and embedding storage.
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
## Install
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
Python 3.10 or newer is required:
|
|
127
127
|
|
|
128
128
|
```shell
|
|
129
129
|
pip install slide2vec
|
|
130
|
-
pip install "slide2vec[fm]"
|
|
131
130
|
```
|
|
132
131
|
|
|
133
|
-
|
|
132
|
+
Many models need additional dependencies available through `pip install "slide2vec[fm]"`. See the [model installation guide](https://clemsgrs.github.io/slide2vec/models.html#model-installation) for model-specific extras, separate environments, and upstream packages.
|
|
134
133
|
|
|
135
|
-
|
|
134
|
+
For gated models such as Virchow2, request access on the model's Hugging Face page and authenticate with `hf auth login` or an `HF_TOKEN` environment variable.
|
|
136
135
|
|
|
137
|
-
|
|
138
|
-
pip install git+https://github.com/lilab-stanford/MUSK.git
|
|
139
|
-
pip install git+https://github.com/Mahmoodlab/CONCH.git
|
|
140
|
-
pip install git+https://github.com/prov-gigapath/prov-gigapath.git
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
AtlasPatch-backed tissue segmentation is available through hs2p's `sam2` path in the bundled install.
|
|
144
|
-
|
|
145
|
-
Waiv encoders use a separately tested Transformers 5 runtime. Install them in
|
|
146
|
-
their own environment with `pip install "slide2vec[waiv]"`; the `waiv` extra is
|
|
147
|
-
incompatible with the existing `fm`, `prism`, and `titan` dependency pins.
|
|
148
|
-
|
|
149
|
-
## Python API
|
|
136
|
+
## Embed a slide
|
|
150
137
|
|
|
151
138
|
```python
|
|
152
|
-
from slide2vec import Model
|
|
153
|
-
from slide2vec.utils.config import hf_login
|
|
154
|
-
|
|
155
|
-
hf_login()
|
|
139
|
+
from slide2vec import Model, PreprocessingConfig
|
|
156
140
|
|
|
157
141
|
model = Model.from_preset("virchow2")
|
|
158
|
-
|
|
142
|
+
preprocessing = PreprocessingConfig(requested_spacing_um=0.5)
|
|
143
|
+
embedded = model.embed_slide("/path/to/slide.svs", preprocessing=preprocessing)
|
|
159
144
|
|
|
160
|
-
tile_embeddings = embedded.tile_embeddings
|
|
161
|
-
x = embedded.x
|
|
162
|
-
y = embedded.y
|
|
145
|
+
tile_embeddings = embedded.tile_embeddings # (N, 2560)
|
|
146
|
+
x, y = embedded.x, embedded.y # level-0 tile coordinates
|
|
163
147
|
```
|
|
164
148
|
|
|
165
|
-
Use `list_models()`
|
|
149
|
+
The preset supplies tile size and precision defaults. Declare spacing explicitly for models such as Virchow2 that support several scales. Use `list_models()` to list presets, or filter with `list_models("tile")`, `list_models("slide")`, or `list_models("patient")`.
|
|
166
150
|
|
|
167
|
-
|
|
168
|
-
|
|
151
|
+
See [getting started](https://clemsgrs.github.io/slide2vec/getting-started.html) for preprocessing and execution settings, and the [API guide](https://clemsgrs.github.io/slide2vec/api.html) for patient embeddings, image inputs, and dense grids.
|
|
152
|
+
|
|
153
|
+
## Save a batch
|
|
169
154
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
155
|
+
Create a CSV manifest:
|
|
156
|
+
|
|
157
|
+
```csv
|
|
158
|
+
sample_id,image_path
|
|
159
|
+
slide-1,/data/slide-1.svs
|
|
160
|
+
slide-2,/data/slide-2.svs
|
|
174
161
|
```
|
|
175
162
|
|
|
176
|
-
|
|
163
|
+
Optional `mask_path` and `spacing_at_level_0` columns supply a mask or correct missing or incorrect level-0 spacing. Patient-level models also require `patient_id`; see the [manifest schema](https://clemsgrs.github.io/slide2vec/manifest.html).
|
|
177
164
|
|
|
178
165
|
```python
|
|
179
|
-
from slide2vec import ExecutionOptions, Pipeline, PreprocessingConfig
|
|
166
|
+
from slide2vec import ExecutionOptions, Model, Pipeline, PreprocessingConfig
|
|
180
167
|
|
|
181
168
|
pipeline = Pipeline(
|
|
182
|
-
model=
|
|
183
|
-
preprocessing=PreprocessingConfig(
|
|
184
|
-
|
|
185
|
-
requested_tile_size_px=224,
|
|
186
|
-
masks={"min_coverage": {"tissue": 0.1}},
|
|
187
|
-
),
|
|
188
|
-
execution=ExecutionOptions(output_dir="outputs/demo"),
|
|
169
|
+
model=Model.from_preset("virchow2"),
|
|
170
|
+
preprocessing=PreprocessingConfig(requested_spacing_um=0.5),
|
|
171
|
+
execution=ExecutionOptions(output_dir="outputs/run"),
|
|
189
172
|
)
|
|
190
173
|
result = pipeline.run(manifest_path="/path/to/slides.csv")
|
|
191
174
|
```
|
|
192
175
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
### Hierarchical Feature Extraction
|
|
196
|
-
|
|
197
|
-
Tile embeddings can be spatially grouped into regions for downstream models that consume region-level structure. Enable it by setting `region_tile_multiple` on `PreprocessingConfig`:
|
|
198
|
-
|
|
199
|
-
```python
|
|
200
|
-
preprocessing = PreprocessingConfig(
|
|
201
|
-
requested_spacing_um=0.5,
|
|
202
|
-
requested_tile_size_px=224,
|
|
203
|
-
region_tile_multiple=6, # 6x6 tiles per region
|
|
204
|
-
)
|
|
205
|
-
embedded = model.embed_slide("/path/to/slide.svs", preprocessing=preprocessing)
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
Hierarchical outputs have shape `(num_regions, tiles_per_region, feature_dim)` and are written to `hierarchical_embeddings/` when persisted.
|
|
209
|
-
|
|
210
|
-
See the [hierarchical features guide](https://clemsgrs.github.io/slide2vec/hierarchical.html) for details.
|
|
211
|
-
|
|
212
|
-
### Input Manifest
|
|
213
|
-
|
|
214
|
-
Manifest-driven runs use the schema below. `mask_path` and `spacing_at_level_0` are optional.
|
|
215
|
-
|
|
216
|
-
```csv
|
|
217
|
-
sample_id,image_path,mask_path,spacing_at_level_0
|
|
218
|
-
slide-1,/path/to/slide-1.svs,/path/to/mask-1.png,0.25
|
|
219
|
-
slide-2,/path/to/slide-2.svs,,
|
|
220
|
-
...
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
Use `spacing_at_level_0` when the slide file reports a missing or incorrect level-0 spacing and you want to override it.
|
|
224
|
-
|
|
176
|
+
Runs use all available GPUs by default; set `ExecutionOptions(num_gpus=2)` to limit them. Embeddings are saved as `.pt` tensors with metadata sidecars. Use `ExecutionOptions(output_format="npz")` for NumPy archives. The [output guide](https://clemsgrs.github.io/slide2vec/output-layout.html) describes directories, shapes, coordinates, and progress records.
|
|
225
177
|
|
|
226
|
-
|
|
178
|
+
Add `region_tile_multiple=6` to the preprocessing config to group tiles into 6×6 regions. These produce `(num_regions, 36, feature_dim)` tensors in `hierarchical_embeddings/`; see [hierarchical features](https://clemsgrs.github.io/slide2vec/hierarchical.html).
|
|
227
179
|
|
|
228
|
-
The
|
|
229
|
-
|
|
230
|
-
- `tile_embeddings/<sample_id>.pt` or `.npz`
|
|
231
|
-
- `tile_embeddings/<sample_id>.meta.json`
|
|
232
|
-
- `hierarchical_embeddings/<sample_id>.pt` or `.npz` (when `region_tile_multiple` is set)
|
|
233
|
-
- `hierarchical_embeddings/<sample_id>.meta.json`
|
|
234
|
-
- `slide_embeddings/<sample_id>.pt` or `.npz`
|
|
235
|
-
- `slide_embeddings/<sample_id>.meta.json`
|
|
236
|
-
- optional `slide_latents/<sample_id>.pt` or `.npz`
|
|
237
|
-
|
|
238
|
-
`.pt` remains the default format. `.npz` is available through `ExecutionOptions(output_format="npz")`.
|
|
239
|
-
|
|
240
|
-
### Supported Models
|
|
241
|
-
|
|
242
|
-
`slide2vec` currently ships presets for 28 tile-level models, 4 slide-level models,
|
|
243
|
-
and 1 patient-level model.
|
|
244
|
-
For the full catalog and preset names, see the [model zoo](https://clemsgrs.github.io/slide2vec/models.html).
|
|
245
|
-
|
|
246
|
-
## CLI
|
|
247
|
-
|
|
248
|
-
The CLI is a thin wrapper over the package API.
|
|
249
|
-
Bundled configs live under `slide2vec/configs/preprocessing/` and `slide2vec/configs/models/`.
|
|
180
|
+
The same batch workflow is available from the terminal:
|
|
250
181
|
|
|
251
182
|
```shell
|
|
252
183
|
slide2vec /path/to/config.yaml
|
|
253
184
|
```
|
|
254
185
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
New to the CLI or doing batch runs to disk? Start with the [CLI guide](https://clemsgrs.github.io/slide2vec/cli.html) for the config-driven workflow and common run patterns.
|
|
186
|
+
The [CLI guide](https://clemsgrs.github.io/slide2vec/cli.html) provides a complete config example, overrides, and resume instructions.
|
|
258
187
|
|
|
259
188
|
## Docker
|
|
260
189
|
|
|
261
190
|
[](https://hub.docker.com/r/waticlems/slide2vec)
|
|
262
191
|
|
|
263
|
-
Docker remains available when you prefer a containerized runtime:
|
|
264
|
-
|
|
265
192
|
```shell
|
|
266
193
|
docker pull waticlems/slide2vec:latest
|
|
267
194
|
docker run --rm -it \
|
|
268
195
|
-v /path/to/your/data:/data \
|
|
269
|
-
-e HF_TOKEN
|
|
196
|
+
-e HF_TOKEN \
|
|
270
197
|
waticlems/slide2vec:latest
|
|
271
198
|
```
|
|
272
199
|
|
|
273
|
-
|
|
200
|
+
Set `HF_TOKEN` in your shell before starting the container.
|
|
201
|
+
|
|
202
|
+
## More documentation
|
|
274
203
|
|
|
275
|
-
- [Documentation website](https://clemsgrs.github.io/slide2vec/)
|
|
276
|
-
- [API guide](https://clemsgrs.github.io/slide2vec/api.html)
|
|
277
|
-
- [CLI guide](https://clemsgrs.github.io/slide2vec/cli.html)
|
|
278
204
|
- [Model zoo](https://clemsgrs.github.io/slide2vec/models.html)
|
|
279
|
-
- [Performance benchmarks and QA](
|
|
280
|
-
- [
|
|
205
|
+
- [Performance benchmarks and QA](https://clemsgrs.github.io/slide2vec/performance.html)
|
|
206
|
+
- [API walkthrough notebook](tutorials/api_walkthrough.ipynb)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# slide2vec
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/slide2vec/)
|
|
4
|
+
[](https://clemsgrs.github.io/slide2vec/)
|
|
5
|
+
|
|
6
|
+
`slide2vec` encodes whole-slide images with publicly available pathology foundation models. It uses [`hs2p`](https://pypi.org/project/hs2p/) for tissue detection and tiling, and handles batching, multi-GPU execution, and embedding storage.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
Python 3.10 or newer is required:
|
|
11
|
+
|
|
12
|
+
```shell
|
|
13
|
+
pip install slide2vec
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Many models need additional dependencies available through `pip install "slide2vec[fm]"`. See the [model installation guide](https://clemsgrs.github.io/slide2vec/models.html#model-installation) for model-specific extras, separate environments, and upstream packages.
|
|
17
|
+
|
|
18
|
+
For gated models such as Virchow2, request access on the model's Hugging Face page and authenticate with `hf auth login` or an `HF_TOKEN` environment variable.
|
|
19
|
+
|
|
20
|
+
## Embed a slide
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from slide2vec import Model, PreprocessingConfig
|
|
24
|
+
|
|
25
|
+
model = Model.from_preset("virchow2")
|
|
26
|
+
preprocessing = PreprocessingConfig(requested_spacing_um=0.5)
|
|
27
|
+
embedded = model.embed_slide("/path/to/slide.svs", preprocessing=preprocessing)
|
|
28
|
+
|
|
29
|
+
tile_embeddings = embedded.tile_embeddings # (N, 2560)
|
|
30
|
+
x, y = embedded.x, embedded.y # level-0 tile coordinates
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The preset supplies tile size and precision defaults. Declare spacing explicitly for models such as Virchow2 that support several scales. Use `list_models()` to list presets, or filter with `list_models("tile")`, `list_models("slide")`, or `list_models("patient")`.
|
|
34
|
+
|
|
35
|
+
See [getting started](https://clemsgrs.github.io/slide2vec/getting-started.html) for preprocessing and execution settings, and the [API guide](https://clemsgrs.github.io/slide2vec/api.html) for patient embeddings, image inputs, and dense grids.
|
|
36
|
+
|
|
37
|
+
## Save a batch
|
|
38
|
+
|
|
39
|
+
Create a CSV manifest:
|
|
40
|
+
|
|
41
|
+
```csv
|
|
42
|
+
sample_id,image_path
|
|
43
|
+
slide-1,/data/slide-1.svs
|
|
44
|
+
slide-2,/data/slide-2.svs
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Optional `mask_path` and `spacing_at_level_0` columns supply a mask or correct missing or incorrect level-0 spacing. Patient-level models also require `patient_id`; see the [manifest schema](https://clemsgrs.github.io/slide2vec/manifest.html).
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from slide2vec import ExecutionOptions, Model, Pipeline, PreprocessingConfig
|
|
51
|
+
|
|
52
|
+
pipeline = Pipeline(
|
|
53
|
+
model=Model.from_preset("virchow2"),
|
|
54
|
+
preprocessing=PreprocessingConfig(requested_spacing_um=0.5),
|
|
55
|
+
execution=ExecutionOptions(output_dir="outputs/run"),
|
|
56
|
+
)
|
|
57
|
+
result = pipeline.run(manifest_path="/path/to/slides.csv")
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Runs use all available GPUs by default; set `ExecutionOptions(num_gpus=2)` to limit them. Embeddings are saved as `.pt` tensors with metadata sidecars. Use `ExecutionOptions(output_format="npz")` for NumPy archives. The [output guide](https://clemsgrs.github.io/slide2vec/output-layout.html) describes directories, shapes, coordinates, and progress records.
|
|
61
|
+
|
|
62
|
+
Add `region_tile_multiple=6` to the preprocessing config to group tiles into 6×6 regions. These produce `(num_regions, 36, feature_dim)` tensors in `hierarchical_embeddings/`; see [hierarchical features](https://clemsgrs.github.io/slide2vec/hierarchical.html).
|
|
63
|
+
|
|
64
|
+
The same batch workflow is available from the terminal:
|
|
65
|
+
|
|
66
|
+
```shell
|
|
67
|
+
slide2vec /path/to/config.yaml
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The [CLI guide](https://clemsgrs.github.io/slide2vec/cli.html) provides a complete config example, overrides, and resume instructions.
|
|
71
|
+
|
|
72
|
+
## Docker
|
|
73
|
+
|
|
74
|
+
[](https://hub.docker.com/r/waticlems/slide2vec)
|
|
75
|
+
|
|
76
|
+
```shell
|
|
77
|
+
docker pull waticlems/slide2vec:latest
|
|
78
|
+
docker run --rm -it \
|
|
79
|
+
-v /path/to/your/data:/data \
|
|
80
|
+
-e HF_TOKEN \
|
|
81
|
+
waticlems/slide2vec:latest
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Set `HF_TOKEN` in your shell before starting the container.
|
|
85
|
+
|
|
86
|
+
## More documentation
|
|
87
|
+
|
|
88
|
+
- [Model zoo](https://clemsgrs.github.io/slide2vec/models.html)
|
|
89
|
+
- [Performance benchmarks and QA](https://clemsgrs.github.io/slide2vec/performance.html)
|
|
90
|
+
- [API walkthrough notebook](tutorials/api_walkthrough.ipynb)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "slide2vec"
|
|
7
|
-
version = "
|
|
7
|
+
version = "6.0.1"
|
|
8
8
|
description = "Embedding of whole slide images with Foundation Models"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -105,7 +105,7 @@ fm = [
|
|
|
105
105
|
"torch>=2.3,<2.8",
|
|
106
106
|
"torchvision>=0.18.0",
|
|
107
107
|
"einops>=0.8.0",
|
|
108
|
-
"timm>=1.0.
|
|
108
|
+
"timm>=1.0.20",
|
|
109
109
|
"huggingface_hub>=0.30.0,<1.0",
|
|
110
110
|
"environs",
|
|
111
111
|
"einops-exts>=0.0.4",
|
|
@@ -149,7 +149,7 @@ zip-safe = false
|
|
|
149
149
|
include = ["slide2vec*"]
|
|
150
150
|
|
|
151
151
|
[tool.setuptools.package-data]
|
|
152
|
-
slide2vec = ["py.typed"]
|
|
152
|
+
slide2vec = ["py.typed", "cufile.json"]
|
|
153
153
|
"slide2vec.configs" = ["*.yaml", "models/*.yaml", "preprocessing/*.yaml"]
|
|
154
154
|
|
|
155
155
|
[tool.pytest.ini_options]
|
|
@@ -180,7 +180,7 @@ no_implicit_reexport = true
|
|
|
180
180
|
max-line-length = 160
|
|
181
181
|
|
|
182
182
|
[tool.bumpver]
|
|
183
|
-
current_version = "
|
|
183
|
+
current_version = "6.0.1"
|
|
184
184
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
185
185
|
commit = false
|
|
186
186
|
tag = false
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
from slide2vec._cufile import disable_cufile_compat_mode
|
|
2
|
+
|
|
3
|
+
disable_cufile_compat_mode() # Before anything can import cucim.
|
|
4
|
+
|
|
1
5
|
from slide2vec.api import (
|
|
2
6
|
DenseImageOptions,
|
|
3
7
|
DenseOptions,
|
|
@@ -27,7 +31,7 @@ from slide2vec.encoders import (
|
|
|
27
31
|
)
|
|
28
32
|
|
|
29
33
|
|
|
30
|
-
__version__ = "
|
|
34
|
+
__version__ = "6.0.1"
|
|
31
35
|
|
|
32
36
|
__all__ = [
|
|
33
37
|
"Model",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Keep ``import cucim`` from opening a CUDA context on every visible GPU (issue #326).
|
|
2
|
+
|
|
3
|
+
cuCIM opens the cuFile (GPUDirect Storage) driver when it is imported. Without the
|
|
4
|
+
``nvidia-fs`` kernel module the driver falls back to "compat mode", which still creates a
|
|
5
|
+
~520 MiB CUDA context on each visible device although every read then goes through plain
|
|
6
|
+
POSIX I/O. Pointing cuFile at a config that disallows compat mode makes the driver open fail
|
|
7
|
+
instead: cuCIM reads through POSIX exactly as before, and no context is created. Where
|
|
8
|
+
``nvidia-fs`` is loaded the driver opens as usual and GDS keeps working.
|
|
9
|
+
|
|
10
|
+
cuFile reads ``CUFILE_ENV_PATH_JSON`` once, when cuCIM is imported, so this must run before
|
|
11
|
+
that import: ``slide2vec/__init__.py`` calls it first. A value the user already set wins.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
CUFILE_ENV = "CUFILE_ENV_PATH_JSON"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def disable_cufile_compat_mode(environ=os.environ) -> None:
|
|
21
|
+
environ.setdefault(CUFILE_ENV, str(Path(__file__).with_name("cufile.json")))
|
|
@@ -1010,14 +1010,14 @@ class Model:
|
|
|
1010
1010
|
)
|
|
1011
1011
|
self._encoder_input = contract
|
|
1012
1012
|
plan = contract.plan
|
|
1013
|
-
if emit_run_info
|
|
1013
|
+
if emit_run_info:
|
|
1014
1014
|
logging.getLogger("slide2vec").info(
|
|
1015
|
-
"Pooled encoder input for '%s': preset %dpx, requested %dpx
|
|
1016
|
-
"
|
|
1015
|
+
"Pooled encoder input for '%s': preset %dpx, requested %dpx; encoding "
|
|
1016
|
+
"exactly %dpx with geometry-preserving preprocessing.",
|
|
1017
1017
|
self.name,
|
|
1018
1018
|
plan.preset_input_size_px,
|
|
1019
1019
|
plan.requested_tile_size_px,
|
|
1020
|
-
plan.
|
|
1020
|
+
plan.requested_tile_size_px,
|
|
1021
1021
|
)
|
|
1022
1022
|
return contract
|
|
1023
1023
|
|
|
@@ -2,6 +2,8 @@ import os
|
|
|
2
2
|
|
|
3
3
|
import torch
|
|
4
4
|
|
|
5
|
+
from slide2vec.distributed.worker_entry import PINNED_ENV
|
|
6
|
+
|
|
5
7
|
_RANK = -1
|
|
6
8
|
_WORLD_SIZE = -1
|
|
7
9
|
_LOCAL_RANK = -1
|
|
@@ -43,6 +45,19 @@ def get_local_rank() -> int:
|
|
|
43
45
|
return _LOCAL_RANK
|
|
44
46
|
|
|
45
47
|
|
|
48
|
+
def get_device_ordinal() -> int:
|
|
49
|
+
"""
|
|
50
|
+
Returns:
|
|
51
|
+
The CUDA ordinal of this process's GPU: 0 once :mod:`slide2vec.distributed.pin_gpu`
|
|
52
|
+
has left it the only visible device, else the local rank.
|
|
53
|
+
"""
|
|
54
|
+
return _device_ordinal(get_local_rank())
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _device_ordinal(local_rank: int) -> int:
|
|
58
|
+
return 0 if os.environ.get(PINNED_ENV) == "1" else local_rank
|
|
59
|
+
|
|
60
|
+
|
|
46
61
|
def get_local_size() -> int:
|
|
47
62
|
"""
|
|
48
63
|
Returns:
|
|
@@ -181,7 +196,7 @@ def enable(
|
|
|
181
196
|
|
|
182
197
|
Args:
|
|
183
198
|
set_cuda_current_device: If True, call torch.cuda.set_device() to set the
|
|
184
|
-
current PyTorch CUDA device to
|
|
199
|
+
current PyTorch CUDA device to this rank's GPU (see get_device_ordinal()).
|
|
185
200
|
overwrite: If True, overwrites already set variables. Else fails.
|
|
186
201
|
"""
|
|
187
202
|
|
|
@@ -192,7 +207,7 @@ def enable(
|
|
|
192
207
|
torch_env.export(overwrite=overwrite)
|
|
193
208
|
|
|
194
209
|
if set_cuda_current_device:
|
|
195
|
-
torch.cuda.set_device(torch_env.local_rank)
|
|
210
|
+
torch.cuda.set_device(_device_ordinal(torch_env.local_rank))
|
|
196
211
|
|
|
197
212
|
_RANK = torch_env.rank
|
|
198
213
|
_WORLD_SIZE = torch_env.world_size
|
|
@@ -42,7 +42,7 @@ def main(argv=None) -> int:
|
|
|
42
42
|
model_spec = dict(request["model"])
|
|
43
43
|
model = Model.from_preset(
|
|
44
44
|
model_spec["name"],
|
|
45
|
-
device=f"cuda:{
|
|
45
|
+
device=f"cuda:{distributed.get_device_ordinal()}",
|
|
46
46
|
output_variant=model_spec.get("output_variant"),
|
|
47
47
|
allow_non_recommended_settings=bool(model_spec["allow_non_recommended_settings"]),
|
|
48
48
|
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Bootstrap that shows a torchrun worker only its own GPU, then runs the worker (issue #326).
|
|
2
|
+
|
|
3
|
+
``import cucim`` opens a CUDA context on every visible device, so a rank that can see its
|
|
4
|
+
siblings' GPUs holds an idle context on each of them. CUDA reads ``CUDA_VISIBLE_DEVICES``
|
|
5
|
+
once, when it initialises, and ``import slide2vec`` already initialises it (``transformers``
|
|
6
|
+
calls ``torch.cuda.is_available()`` at import). A worker module therefore cannot narrow its
|
|
7
|
+
own visibility: ``python -m slide2vec.distributed.<worker>`` has imported the package before
|
|
8
|
+
the worker's first line runs. torchrun runs this file **by path** instead, so the narrowing
|
|
9
|
+
happens before anything is imported. Keep this file free of slide2vec and torch imports.
|
|
10
|
+
|
|
11
|
+
Only for workers that run no collectives: a pinned rank cannot see its siblings' GPUs.
|
|
12
|
+
|
|
13
|
+
Usage: ``torchrun ... pin_gpu.py <worker module> [worker args...]``
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import runpy
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
PINNED_ENV = "SLIDE2VEC_WORKER_GPU_PINNED" # Mirrored in worker_entry.PINNED_ENV.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def pin_to_own_gpu(environ) -> None:
|
|
24
|
+
"""Narrow ``CUDA_VISIBLE_DEVICES`` to the ``LOCAL_RANK``-th GPU this process inherited."""
|
|
25
|
+
local_rank = int(environ["LOCAL_RANK"])
|
|
26
|
+
visible = [gpu.strip() for gpu in environ.get("CUDA_VISIBLE_DEVICES", "").split(",") if gpu.strip()]
|
|
27
|
+
if visible and local_rank >= len(visible):
|
|
28
|
+
raise RuntimeError(
|
|
29
|
+
f"LOCAL_RANK={local_rank} but CUDA_VISIBLE_DEVICES lists only {len(visible)} GPU(s)"
|
|
30
|
+
)
|
|
31
|
+
environ["CUDA_VISIBLE_DEVICES"] = visible[local_rank] if visible else str(local_rank)
|
|
32
|
+
environ[PINNED_ENV] = "1"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main() -> None:
|
|
36
|
+
pin_to_own_gpu(os.environ)
|
|
37
|
+
module, *worker_args = sys.argv[1:]
|
|
38
|
+
# Prefer the package that supplied this bootstrap over a competing checkout in cwd.
|
|
39
|
+
# Keep cwd importable without changing how relative data and output paths resolve.
|
|
40
|
+
package_root = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
41
|
+
sys.path[:1] = [package_root, os.getcwd()]
|
|
42
|
+
sys.argv = [module, *worker_args]
|
|
43
|
+
runpy.run_module(module, run_name="__main__", alter_sys=True)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
main()
|
|
@@ -36,7 +36,7 @@ def main(argv=None) -> int:
|
|
|
36
36
|
model_spec = dict(request["model"])
|
|
37
37
|
model = Model.from_preset(
|
|
38
38
|
model_spec["name"],
|
|
39
|
-
device=f"cuda:{
|
|
39
|
+
device=f"cuda:{distributed.get_device_ordinal()}",
|
|
40
40
|
output_variant=model_spec.get("output_variant"),
|
|
41
41
|
allow_non_recommended_settings=bool(model_spec["allow_non_recommended_settings"]),
|
|
42
42
|
)
|
|
@@ -18,6 +18,10 @@ from contextlib import nullcontext
|
|
|
18
18
|
from dataclasses import dataclass
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
#: Set by :mod:`slide2vec.distributed.pin_gpu` once this process sees only its own GPU.
|
|
22
|
+
PINNED_ENV = "SLIDE2VEC_WORKER_GPU_PINNED"
|
|
23
|
+
|
|
24
|
+
|
|
21
25
|
@dataclass(frozen=True, kw_only=True)
|
|
22
26
|
class WorkerRank:
|
|
23
27
|
"""This process's place in the torchrun world: which shard, and which GPU."""
|
|
@@ -26,12 +30,14 @@ class WorkerRank:
|
|
|
26
30
|
global_rank: int
|
|
27
31
|
#: Number of shards the work list is cut into.
|
|
28
32
|
world_size: int
|
|
29
|
-
#:
|
|
33
|
+
#: This process's index among the ranks on its node; names it in progress labels.
|
|
30
34
|
local_rank: int
|
|
35
|
+
#: CUDA ordinal of this rank's GPU: 0 once the process is pinned to it, else ``local_rank``.
|
|
36
|
+
device_ordinal: int
|
|
31
37
|
|
|
32
38
|
@property
|
|
33
39
|
def device(self) -> str:
|
|
34
|
-
return f"cuda:{self.
|
|
40
|
+
return f"cuda:{self.device_ordinal}"
|
|
35
41
|
|
|
36
42
|
|
|
37
43
|
def worker_args_parser(module: str, *, add_help: bool = True) -> argparse.ArgumentParser:
|
|
@@ -46,12 +52,15 @@ def resolve_worker_rank() -> WorkerRank:
|
|
|
46
52
|
"""Read this process's rank from the env torchrun exports.
|
|
47
53
|
|
|
48
54
|
Defaults to a single in-process rank when the module is launched bare, so a worker can
|
|
49
|
-
be run by hand (or in a test) without a torchrun agent.
|
|
55
|
+
be run by hand (or in a test) without a torchrun agent. A worker launched through
|
|
56
|
+
:mod:`slide2vec.distributed.pin_gpu` sees only its own GPU, which is then ``cuda:0``.
|
|
50
57
|
"""
|
|
58
|
+
local_rank = int(os.environ.get("LOCAL_RANK", "0"))
|
|
51
59
|
return WorkerRank(
|
|
52
60
|
global_rank=int(os.environ.get("RANK", "0")),
|
|
53
61
|
world_size=int(os.environ.get("WORLD_SIZE", "1")),
|
|
54
|
-
local_rank=
|
|
62
|
+
local_rank=local_rank,
|
|
63
|
+
device_ordinal=0 if os.environ.get(PINNED_ENV) == "1" else local_rank,
|
|
55
64
|
)
|
|
56
65
|
|
|
57
66
|
|
|
@@ -82,6 +91,6 @@ def worker_progress_context(request: dict, *, rank: WorkerRank):
|
|
|
82
91
|
return nullcontext()
|
|
83
92
|
return activate_progress_reporter(
|
|
84
93
|
JsonlProgressReporter(
|
|
85
|
-
progress_events_path, rank=rank.global_rank, progress_label=rank.
|
|
94
|
+
progress_events_path, rank=rank.global_rank, progress_label=f"cuda:{rank.local_rank}"
|
|
86
95
|
)
|
|
87
96
|
)
|