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.
Files changed (174) hide show
  1. {slide2vec-5.9.2 → slide2vec-6.0.1}/PKG-INFO +38 -112
  2. slide2vec-6.0.1/README.md +90 -0
  3. {slide2vec-5.9.2 → slide2vec-6.0.1}/pyproject.toml +4 -4
  4. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/__init__.py +5 -1
  5. slide2vec-6.0.1/slide2vec/_cufile.py +21 -0
  6. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/api.py +4 -4
  7. slide2vec-6.0.1/slide2vec/cufile.json +5 -0
  8. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/distributed/__init__.py +17 -2
  9. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/distributed/direct_embed_worker.py +1 -1
  10. slide2vec-6.0.1/slide2vec/distributed/pin_gpu.py +47 -0
  11. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/distributed/pipeline_worker.py +1 -1
  12. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/distributed/worker_entry.py +14 -5
  13. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/base.py +43 -21
  14. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/__init__.py +2 -0
  15. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/dinov2.py +8 -5
  16. slide2vec-6.0.1/slide2vec/encoders/models/dinov3.py +79 -0
  17. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/gigapath.py +9 -7
  18. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/gpfm.py +10 -1
  19. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/mstar.py +1 -1
  20. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/registry.py +17 -5
  21. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/inference.py +11 -1
  22. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/artifacts_collect.py +1 -0
  23. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/batching.py +11 -1
  24. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_encoder_input.py +0 -12
  25. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_image_stage.py +1 -0
  26. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_regions.py +3 -2
  27. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_stage.py +1 -0
  28. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/distributed.py +19 -3
  29. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/distributed_stage.py +2 -0
  30. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/embedding.py +4 -1
  31. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/embedding_persist.py +3 -1
  32. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/encoder_input_contract.py +8 -2
  33. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/image_stage.py +1 -0
  34. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/model_settings.py +2 -0
  35. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/patient_pipeline.py +3 -1
  36. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/persist_callbacks.py +99 -18
  37. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/pooled_encoder_input.py +9 -28
  38. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/types.py +3 -0
  39. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec.egg-info/PKG-INFO +38 -112
  40. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec.egg-info/SOURCES.txt +9 -1
  41. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec.egg-info/requires.txt +1 -1
  42. slide2vec-6.0.1/tests/test_cufile_compat_mode.py +50 -0
  43. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_encoder_input.py +3 -3
  44. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_image_stage.py +3 -0
  45. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dinov2_natimage.py +3 -3
  46. slide2vec-6.0.1/tests/test_dinov3_natimage.py +188 -0
  47. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_encoder_capabilities.py +8 -9
  48. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_encoder_input_contract.py +50 -8
  49. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_encoder_plugins.py +4 -1
  50. slide2vec-6.0.1/tests/test_encoder_preprocessing.py +368 -0
  51. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_encoder_provider_failures.py +3 -0
  52. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_encoder_registry.py +73 -0
  53. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_patch_size_metadata.py +1 -0
  54. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_pooled_encoder_input.py +60 -44
  55. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_pooled_geometry.py +62 -24
  56. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_regression_core.py +1 -0
  57. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_regression_inference.py +240 -1
  58. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_regression_models.py +1 -1
  59. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_rudolfv2.py +2 -1
  60. slide2vec-6.0.1/tests/test_worker_gpu_pinning.py +234 -0
  61. slide2vec-5.9.2/README.md +0 -164
  62. {slide2vec-5.9.2 → slide2vec-6.0.1}/LICENSE +0 -0
  63. {slide2vec-5.9.2 → slide2vec-6.0.1}/setup.cfg +0 -0
  64. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/__main__.py +0 -0
  65. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/artifacts.py +0 -0
  66. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/cli.py +0 -0
  67. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/configs/__init__.py +0 -0
  68. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/configs/default.yaml +0 -0
  69. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/configs/resources.py +0 -0
  70. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/data/__init__.py +0 -0
  71. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/data/dataset.py +0 -0
  72. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/data/tile_reader.py +0 -0
  73. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/data/tile_store.py +0 -0
  74. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/distributed/dense_image_worker.py +0 -0
  75. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/distributed/dense_worker.py +0 -0
  76. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/distributed/image_worker.py +0 -0
  77. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/__init__.py +0 -0
  78. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/conch.py +0 -0
  79. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/genbio.py +0 -0
  80. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/hibou.py +0 -0
  81. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/hoptimus.py +0 -0
  82. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/isight.py +0 -0
  83. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/lunit.py +0 -0
  84. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/midnight.py +0 -0
  85. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/moozy/__init__.py +0 -0
  86. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/moozy/blocks.py +0 -0
  87. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/moozy/case.py +0 -0
  88. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/moozy/loading.py +0 -0
  89. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/moozy/slide.py +0 -0
  90. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/moozy/types.py +0 -0
  91. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/musk.py +0 -0
  92. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/phikon.py +0 -0
  93. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/prism.py +0 -0
  94. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/prism2.py +0 -0
  95. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/prost40m.py +0 -0
  96. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/rudolfv2.py +0 -0
  97. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/titan.py +0 -0
  98. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/uni.py +0 -0
  99. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/virchow.py +0 -0
  100. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/models/waiv.py +0 -0
  101. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/encoders/validation.py +0 -0
  102. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/progress.py +0 -0
  103. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/__init__.py +0 -0
  104. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/cpu_budget.py +0 -0
  105. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_encode.py +0 -0
  106. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_image_reading.py +0 -0
  107. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_image_recipe.py +0 -0
  108. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_image_shard.py +0 -0
  109. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_shard.py +0 -0
  110. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/dense_sliding.py +0 -0
  111. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/effective_encoder_input.py +0 -0
  112. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/embedding_pipeline.py +0 -0
  113. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/hierarchical.py +0 -0
  114. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/image_shard.py +0 -0
  115. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/image_specs.py +0 -0
  116. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/manifest.py +0 -0
  117. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/persistence.py +0 -0
  118. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/preprocessing.py +0 -0
  119. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/process_list.py +0 -0
  120. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/progress_bridge.py +0 -0
  121. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/registry.py +0 -0
  122. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/serialization.py +0 -0
  123. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/sharding.py +0 -0
  124. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/slide_encode.py +0 -0
  125. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/tiling.py +0 -0
  126. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/tiling_pipeline.py +0 -0
  127. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/runtime/worker_io.py +0 -0
  128. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/utils/__init__.py +0 -0
  129. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/utils/config.py +0 -0
  130. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/utils/coordinates.py +0 -0
  131. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/utils/log_utils.py +0 -0
  132. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/utils/tiling_io.py +0 -0
  133. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec/utils/utils.py +0 -0
  134. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec.egg-info/dependency_links.txt +0 -0
  135. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec.egg-info/entry_points.txt +0 -0
  136. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec.egg-info/not-zip-safe +0 -0
  137. {slide2vec-5.9.2 → slide2vec-6.0.1}/slide2vec.egg-info/top_level.txt +0 -0
  138. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_architecture_runtime_split.py +0 -0
  139. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_attention_extraction.py +0 -0
  140. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_benchmark_tooling.py +0 -0
  141. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_encode_kit.py +0 -0
  142. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_extraction.py +0 -0
  143. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_image_reading.py +0 -0
  144. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_image_resume.py +0 -0
  145. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_image_shard.py +0 -0
  146. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_regions.py +0 -0
  147. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_shard.py +0 -0
  148. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_sliding.py +0 -0
  149. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_source_spacing.py +0 -0
  150. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_stage.py +0 -0
  151. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_dense_worker.py +0 -0
  152. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_gpfm_genbio_heavy.py +0 -0
  153. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_hs2p_package_cutover.py +0 -0
  154. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_image_shard.py +0 -0
  155. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_image_stage.py +0 -0
  156. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_inference_benchmark.py +0 -0
  157. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_isight.py +0 -0
  158. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_load_model_hf_auth.py +0 -0
  159. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_mascaret.py +0 -0
  160. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_on_slide_persisted.py +0 -0
  161. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_output_consistency.py +0 -0
  162. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_patient_manifest.py +0 -0
  163. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_phaet.py +0 -0
  164. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_prism.py +0 -0
  165. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_prism2.py +0 -0
  166. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_process_list_performance.py +0 -0
  167. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_progress.py +0 -0
  168. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_runtime_batching.py +0 -0
  169. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_sharding.py +0 -0
  170. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_slide_coordinate_preparation.py +0 -0
  171. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_soma_migration.py +0 -0
  172. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_tile_store.py +0 -0
  173. {slide2vec-5.9.2 → slide2vec-6.0.1}/tests/test_tiling_pipeline.py +0 -0
  174. {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: 5.9.2
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.3; extra == "fm"
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
  [![PyPI version](https://img.shields.io/pypi/v/slide2vec?label=pypi&logo=pypi&color=3776AB)](https://pypi.org/project/slide2vec/)
120
120
  [![Docs](https://img.shields.io/badge/docs-website-blue)](https://clemsgrs.github.io/slide2vec/)
121
121
 
122
- `slide2vec` is a Python package for efficient encoding of whole-slide images using publicly available foundation models. It builds on [`hs2p`](https://pypi.org/project/hs2p/) for fast preprocessing and exposes a focused surface around `Model`, `Pipeline`, and `ExecutionOptions`.
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
- Documentation site: [https://clemsgrs.github.io/slide2vec/](https://clemsgrs.github.io/slide2vec/)
124
+ ## Install
125
125
 
126
- ## Installation
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
- `slide2vec` keeps the base install focused on the core package surface. Use `slide2vec[fm]` when you want the PyPI-hosted FM dependencies.
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
- Some model backends still rely on upstream Git repositories that PyPI will not accept as package metadata. Install those separately when needed:
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
- ```shell
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
- embedded = model.embed_slide("/path/to/slide.svs")
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()` when you want to inspect the shipped presets programmatically:
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
- ```python
168
- from slide2vec import list_models
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
- all_models = list_models()
171
- tile_models = list_models("tile")
172
- slide_models = list_models("slide")
173
- patient_models = list_models("patient")
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
- Use `Pipeline(...)` for manifest-driven batch processing when you want artifacts written to disk instead of only in-memory outputs:
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=model,
183
- preprocessing=PreprocessingConfig(
184
- requested_spacing_um=0.5,
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
- By default, `ExecutionOptions()` uses all available GPUs. Set `ExecutionOptions(num_gpus=4)` when you want to cap the sharding explicitly.
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
- ### Outputs
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 package writes explicit artifact directories:
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
- By default, manifest-driven CLI runs use all available GPUs. Set `speed.num_gpus=4` when you want to cap the sharding explicitly.
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
  [![Docker Version](https://img.shields.io/docker/v/waticlems/slide2vec?sort=semver&label=docker&logo=docker&color=2496ED)](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=<your-huggingface-api-token> \
196
+ -e HF_TOKEN \
270
197
  waticlems/slide2vec:latest
271
198
  ```
272
199
 
273
- ## Documentation
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](docs/performance.md)
280
- - [`tutorials/api_walkthrough.ipynb`](tutorials/api_walkthrough.ipynb) for a notebook walkthrough of the API
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
+ [![PyPI version](https://img.shields.io/pypi/v/slide2vec?label=pypi&logo=pypi&color=3776AB)](https://pypi.org/project/slide2vec/)
4
+ [![Docs](https://img.shields.io/badge/docs-website-blue)](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
+ [![Docker Version](https://img.shields.io/docker/v/waticlems/slide2vec?sort=semver&label=docker&logo=docker&color=2496ED)](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 = "5.9.2"
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.3",
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 = "5.9.2"
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__ = "5.9.2"
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 and plan.requires_variable_model_input:
1013
+ if emit_run_info:
1014
1014
  logging.getLogger("slide2vec").info(
1015
- "Pooled encoder input for '%s': preset %dpx, requested %dpx, "
1016
- "exact encoder input %dpx; using normalization-only preprocessing.",
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.expected_encoder_input_size_px,
1020
+ plan.requested_tile_size_px,
1021
1021
  )
1022
1022
  return contract
1023
1023
 
@@ -0,0 +1,5 @@
1
+ {
2
+ "properties": {
3
+ "allow_compat_mode": false
4
+ }
5
+ }
@@ -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 the one matching the local rank.
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:{local_rank}",
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:{local_rank}",
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
- #: Local device ordinal, i.e. ``cuda:<local_rank>``.
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.local_rank}"
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=int(os.environ.get("LOCAL_RANK", "0")),
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.device
94
+ progress_events_path, rank=rank.global_rank, progress_label=f"cuda:{rank.local_rank}"
86
95
  )
87
96
  )