slide2vec 7.1.0__tar.gz → 7.1.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-7.1.0 → slide2vec-7.1.1}/PKG-INFO +1 -1
- {slide2vec-7.1.0 → slide2vec-7.1.1}/pyproject.toml +2 -2
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/__init__.py +1 -1
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/api.py +70 -37
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/inference.py +14 -14
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/cpu_budget.py +2 -2
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/dense_image_stage.py +4 -4
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/dense_stage.py +4 -4
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/distributed_stage.py +7 -7
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/embedding_pipeline.py +2 -2
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/image_stage.py +4 -4
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/serialization.py +4 -2
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/slide_encode.py +3 -2
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec.egg-info/PKG-INFO +1 -1
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec.egg-info/SOURCES.txt +1 -0
- slide2vec-7.1.1/tests/test_execution_options.py +116 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_regression_core.py +4 -6
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_regression_inference.py +65 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/LICENSE +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/README.md +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/setup.cfg +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/__main__.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/_cufile.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/artifacts.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/cli.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/configs/default.yaml +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/configs/resources.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/cufile.json +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/data/__init__.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/data/dataset.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/data/tile_reader.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/distributed/__init__.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/distributed/dense_image_worker.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/distributed/dense_worker.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/distributed/direct_embed_worker.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/distributed/image_worker.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/distributed/pin_gpu.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/distributed/pipeline_worker.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/distributed/worker_entry.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/__init__.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/base.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/__init__.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/conch.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/dinov2.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/dinov3.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/genbio.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/gigapath.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/gpfm.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/hibou.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/isight.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/lunit.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/mettle.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/midnight.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/moozy/__init__.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/moozy/blocks.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/moozy/case.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/moozy/loading.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/moozy/slide.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/moozy/types.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/mstar.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/phikon.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/prism2.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/rudolfv2.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/titan.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/virchow.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/models/waiv.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/registry.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/progress.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/__init__.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/artifacts_collect.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/batching.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/dense_encode.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/dense_encoder_input.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/dense_image_reading.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/dense_image_recipe.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/dense_image_shard.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/dense_regions.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/dense_shard.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/dense_sliding.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/distributed.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/effective_encoder_input.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/embedding.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/embedding_persist.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/encoder_input_contract.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/feature_identity.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/hierarchical.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/image_shard.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/image_specs.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/manifest.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/model_settings.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/patient_pipeline.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/persist_callbacks.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/persistence.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/pooled_encoder_input.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/preprocessing.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/process_list.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/progress_bridge.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/registry.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/sharding.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/tiling.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/tiling_pipeline.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/types.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/runtime/worker_io.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/utils/config.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/utils/log_utils.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/utils/tiling_io.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec/utils/utils.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec.egg-info/requires.txt +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_attention_extraction.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_benchmark_tooling.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_cli_noninteractive.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_cufile_compat_mode.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_dense_encode_kit.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_dense_encoder_input.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_dense_extraction.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_dense_image_reading.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_dense_image_resume.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_dense_image_shard.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_dense_image_stage.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_dense_regions.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_dense_shard.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_dense_sliding.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_dense_source_spacing.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_dense_stage.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_dense_worker.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_dinov2_natimage.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_dinov3_natimage.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_drop_in_encoders.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_encoder_capabilities.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_encoder_input_contract.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_encoder_plugins.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_encoder_preprocessing.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_encoder_provider_failures.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_encoder_registry.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_feature_identity.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_gigapath.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_gpfm_genbio_heavy.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_hierarchical_encoding.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_hs2p5_integration.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_hs2p_package_cutover.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_image_shard.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_image_stage.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_inference_benchmark.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_isight.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_load_model_hf_auth.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_mascaret.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_mettle.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_on_slide_persisted.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_output_consistency.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_patch_size_metadata.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_patient_annotation_sampling.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_patient_manifest.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_patient_tiling_only.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_phaet.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_pooled_encoder_input.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_pooled_geometry.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_pooled_identity_api.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_prism.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_prism2.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_process_list_performance.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_progress.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_regression_models.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_rudolfv2.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_runtime_batching.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_sharding.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_slide_coordinate_preparation.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_slide_latents.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_soma_migration.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_tile_store.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_tiling_pipeline.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_titan.py +0 -0
- {slide2vec-7.1.0 → slide2vec-7.1.1}/tests/test_worker_gpu_pinning.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "slide2vec"
|
|
7
|
-
version = "7.1.
|
|
7
|
+
version = "7.1.1"
|
|
8
8
|
description = "Embedding of whole slide images with Foundation Models"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -180,7 +180,7 @@ no_implicit_reexport = true
|
|
|
180
180
|
max-line-length = 160
|
|
181
181
|
|
|
182
182
|
[tool.bumpver]
|
|
183
|
-
current_version = "7.1.
|
|
183
|
+
current_version = "7.1.1"
|
|
184
184
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
185
185
|
commit = false
|
|
186
186
|
tag = false
|
|
@@ -7,7 +7,7 @@ import warnings
|
|
|
7
7
|
from dataclasses import dataclass, field, replace
|
|
8
8
|
from contextlib import contextmanager
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import Any, Callable, Mapping, Protocol, Sequence
|
|
10
|
+
from typing import Any, Callable, Mapping, NamedTuple, Protocol, Sequence
|
|
11
11
|
|
|
12
12
|
import torch
|
|
13
13
|
from hs2p import (
|
|
@@ -261,9 +261,12 @@ class ExecutionOptions:
|
|
|
261
261
|
#: (capped by CPU / SLURM limit, then split across the resolved GPU count).
|
|
262
262
|
#: Image-only routes safely use zero when auto selection happens after model loading.
|
|
263
263
|
num_workers_per_gpu: int | None = None
|
|
264
|
-
#: Tiling worker count. ``None`` means auto
|
|
264
|
+
#: Tiling worker count. ``None`` means auto. The field keeps the requested value;
|
|
265
|
+
#: :meth:`resolved_num_preprocessing_workers` gives the count a run uses, capped by the
|
|
266
|
+
#: CPU / SLURM limit.
|
|
265
267
|
num_preprocessing_workers: int | None = None
|
|
266
|
-
#: Number of GPUs to use. ``None``
|
|
268
|
+
#: Number of GPUs to use. ``None`` means all available GPUs. The field keeps the
|
|
269
|
+
#: requested value; :meth:`resolved_num_gpus` gives the count a run uses.
|
|
267
270
|
num_gpus: int | None = None
|
|
268
271
|
#: Forward-pass dtype — ``"fp16"``, ``"bf16"``, ``"fp32"``,
|
|
269
272
|
#: or ``None`` (auto-determined from the model preset).
|
|
@@ -313,11 +316,9 @@ class ExecutionOptions:
|
|
|
313
316
|
)
|
|
314
317
|
|
|
315
318
|
def __post_init__(self) -> None:
|
|
316
|
-
resolved_num_gpus = _default_num_gpus() if self.num_gpus is None else self.num_gpus
|
|
317
|
-
object.__setattr__(self, "num_gpus", resolved_num_gpus)
|
|
318
319
|
object.__setattr__(self, "precision", normalize_precision_name(self.precision))
|
|
319
320
|
object.__setattr__(self, "output_dtype", normalize_output_dtype(self.output_dtype))
|
|
320
|
-
if
|
|
321
|
+
if self.num_gpus is not None and self.num_gpus < 1:
|
|
321
322
|
raise ValueError("ExecutionOptions.num_gpus must be at least 1")
|
|
322
323
|
if self.batch_size < 1:
|
|
323
324
|
raise ValueError("ExecutionOptions.batch_size must be at least 1")
|
|
@@ -325,40 +326,62 @@ class ExecutionOptions:
|
|
|
325
326
|
raise ValueError("ExecutionOptions.prefetch_factor must be at least 1")
|
|
326
327
|
if self.num_workers_per_gpu is not None and self.num_workers_per_gpu < 0:
|
|
327
328
|
raise ValueError("ExecutionOptions.num_workers_per_gpu must be non-negative")
|
|
329
|
+
if self.num_preprocessing_workers is not None and self.num_preprocessing_workers < 0:
|
|
330
|
+
raise ValueError("ExecutionOptions.num_preprocessing_workers must be non-negative")
|
|
328
331
|
if self.on_image_mismatch not in IMAGE_MISMATCH_POLICIES:
|
|
329
332
|
raise ValueError(
|
|
330
333
|
"ExecutionOptions.on_image_mismatch must be one of "
|
|
331
334
|
f"{', '.join(repr(policy) for policy in IMAGE_MISMATCH_POLICIES)}; "
|
|
332
335
|
f"got {self.on_image_mismatch!r}"
|
|
333
336
|
)
|
|
337
|
+
|
|
338
|
+
def resolved_num_gpus(self) -> int:
|
|
339
|
+
"""GPU count a run uses: :attr:`num_gpus`, or every visible GPU when it is ``None``."""
|
|
340
|
+
return self._machine_resolution.num_gpus
|
|
341
|
+
|
|
342
|
+
def resolved_num_preprocessing_workers(self) -> int:
|
|
343
|
+
"""Tiling worker count a run uses: :attr:`num_preprocessing_workers`, or the CPU /
|
|
344
|
+
SLURM limit when it is ``None``, and never more than that limit."""
|
|
345
|
+
return self._machine_resolution.num_preprocessing_workers
|
|
346
|
+
|
|
347
|
+
def resolved_num_workers_per_gpu(self) -> int:
|
|
348
|
+
if self.num_workers_per_gpu is not None:
|
|
349
|
+
return self.num_workers_per_gpu
|
|
350
|
+
return max(1, cpu_worker_limit() // self.resolved_num_gpus())
|
|
351
|
+
|
|
352
|
+
@functools.cached_property
|
|
353
|
+
def _machine_resolution(self) -> "_MachineResolution":
|
|
354
|
+
# Resolved once per instance, on first use, so every reader in a run agrees and the
|
|
355
|
+
# log line is written once. The cache sits outside the dataclass fields: equality,
|
|
356
|
+
# hashing and repr see only what the caller asked for.
|
|
357
|
+
num_gpus = _default_num_gpus() if self.num_gpus is None else self.num_gpus
|
|
334
358
|
cap = cpu_worker_limit()
|
|
335
|
-
|
|
336
|
-
slurm_limit = slurm_cpu_limit()
|
|
337
|
-
capped_num_preprocessing_workers = (
|
|
359
|
+
num_preprocessing_workers = (
|
|
338
360
|
cap if self.num_preprocessing_workers is None else min(self.num_preprocessing_workers, cap)
|
|
339
361
|
)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
f"{
|
|
346
|
-
if self.num_workers_per_gpu is None
|
|
347
|
-
else str(resolved_num_workers)
|
|
362
|
+
num_workers_per_gpu = (
|
|
363
|
+
max(1, cap // num_gpus) if self.num_workers_per_gpu is None else self.num_workers_per_gpu
|
|
364
|
+
)
|
|
365
|
+
slurm_limit = slurm_cpu_limit()
|
|
366
|
+
cap_source = (
|
|
367
|
+
f"slurm_cpu_limit={slurm_limit}" if slurm_limit is not None else f"cpu_count={os.cpu_count() or 1}"
|
|
348
368
|
)
|
|
349
|
-
|
|
350
|
-
"ExecutionOptions: num_workers_per_gpu=%s, num_preprocessing_workers=%d "
|
|
369
|
+
logging.getLogger(__name__).info(
|
|
370
|
+
"ExecutionOptions: num_gpus=%s, num_workers_per_gpu=%s, num_preprocessing_workers=%d "
|
|
351
371
|
"(preprocessing cap=%d via %s)",
|
|
352
|
-
|
|
353
|
-
|
|
372
|
+
_auto_label(num_gpus, requested=self.num_gpus),
|
|
373
|
+
_auto_label(num_workers_per_gpu, requested=self.num_workers_per_gpu),
|
|
374
|
+
num_preprocessing_workers,
|
|
354
375
|
cap,
|
|
355
376
|
cap_source,
|
|
356
377
|
)
|
|
378
|
+
return _MachineResolution(num_gpus=num_gpus, num_preprocessing_workers=num_preprocessing_workers)
|
|
357
379
|
|
|
358
|
-
def
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
380
|
+
def __getstate__(self) -> dict[str, Any]:
|
|
381
|
+
# A copy or an unpickled instance resolves again on the machine that uses it.
|
|
382
|
+
state = dict(self.__dict__)
|
|
383
|
+
state.pop("_machine_resolution", None)
|
|
384
|
+
return state
|
|
362
385
|
|
|
363
386
|
def resolved_image_num_workers_per_gpu(self) -> int:
|
|
364
387
|
"""Resolve safe post-model-load image-transform workers for this rank."""
|
|
@@ -898,8 +921,9 @@ class Model:
|
|
|
898
921
|
The dense counterpart of the pooled coordinate path: each ``SlideRegions`` names a
|
|
899
922
|
slide + a set of level-0 ROI coordinates, and every ROI is read, encoded through the
|
|
900
923
|
dense transform, and written to ``dense_embeddings/[<class>/]<sample_id>/<x>_<y>.pt``
|
|
901
|
-
plus a geometry sidecar. The run splits its ROIs across
|
|
902
|
-
|
|
924
|
+
plus a geometry sidecar. The run splits its ROIs across
|
|
925
|
+
``execution.resolved_num_gpus()`` GPUs (all visible GPUs by default);
|
|
926
|
+
``num_gpus=1`` encodes fully in-process. Resume is
|
|
903
927
|
automatic — only ROIs whose sidecar has the same source-spacing declaration,
|
|
904
928
|
resolved hs2p read plan and feature identity are skipped. Returns one
|
|
905
929
|
:class:`~slide2vec.artifacts.DenseRegionArtifact` per input ROI.
|
|
@@ -932,10 +956,10 @@ class Model:
|
|
|
932
956
|
**normalization-only** transform, padded up to the encoder's patch multiple, encoded
|
|
933
957
|
— whole-image, or by sliding the encoder's native field and blending the token grids
|
|
934
958
|
— and written to ``dense_image_embeddings/<sample_id>.pt`` plus a geometry sidecar.
|
|
935
|
-
The run splits its images across
|
|
936
|
-
``num_gpus=1`` encodes fully in-process. Resume is
|
|
937
|
-
only when its payload exists and its sidecar records
|
|
938
|
-
identity and complete extraction recipe. Returns one
|
|
959
|
+
The run splits its images across ``execution.resolved_num_gpus()`` GPUs (all
|
|
960
|
+
visible GPUs by default); ``num_gpus=1`` encodes fully in-process. Resume is
|
|
961
|
+
automatic: an image is skipped only when its payload exists and its sidecar records
|
|
962
|
+
the same normalized source identity and complete extraction recipe. Returns one
|
|
939
963
|
:class:`~slide2vec.artifacts.DenseImageArtifact` per input image, in input order.
|
|
940
964
|
|
|
941
965
|
Every source is opened through hs2p. PNG/JPEG inputs use hs2p's one-level PIL reader
|
|
@@ -977,12 +1001,12 @@ class Model:
|
|
|
977
1001
|
set — and slide2vec neither tiles nor reads a slide. Each :class:`ImageSpec` is
|
|
978
1002
|
decoded, preprocessed with the encoder's **shipped** transform, encoded, and written
|
|
979
1003
|
to ``image_embeddings/<sample_id>.pt`` plus a provenance sidecar. The run splits its
|
|
980
|
-
images across all visible GPUs
|
|
981
|
-
fully in-process. Resume is automatic: an image is
|
|
982
|
-
records this ``output_format``, the requested
|
|
983
|
-
identity. A different recorded feature identity
|
|
984
|
-
``image_path`` raises unless
|
|
985
|
-
provenance re-encodes. Returns
|
|
1004
|
+
images across ``execution.resolved_num_gpus()`` GPUs (all visible GPUs by
|
|
1005
|
+
default); ``num_gpus=1`` encodes fully in-process. Resume is automatic: an image is
|
|
1006
|
+
skipped only when its sidecar records this ``output_format``, the requested
|
|
1007
|
+
``image_path`` and the full feature identity. A different recorded feature identity
|
|
1008
|
+
raises; a different recorded ``image_path`` raises unless
|
|
1009
|
+
``execution.on_image_mismatch="reencode"``; missing provenance re-encodes. Returns
|
|
986
1010
|
one :class:`~slide2vec.artifacts.ImageEmbeddingArtifact` per input image, in input
|
|
987
1011
|
order.
|
|
988
1012
|
|
|
@@ -1342,6 +1366,15 @@ def _default_num_gpus() -> int:
|
|
|
1342
1366
|
return max(1, torch.cuda.device_count()) if torch.cuda.is_available() else 1
|
|
1343
1367
|
|
|
1344
1368
|
|
|
1369
|
+
class _MachineResolution(NamedTuple):
|
|
1370
|
+
num_gpus: int
|
|
1371
|
+
num_preprocessing_workers: int
|
|
1372
|
+
|
|
1373
|
+
|
|
1374
|
+
def _auto_label(resolved: int, *, requested: int | None) -> str:
|
|
1375
|
+
return f"{resolved} (requested=auto)" if requested is None else str(resolved)
|
|
1376
|
+
|
|
1377
|
+
|
|
1345
1378
|
def _require_output_dir_for_persistence(execution: ExecutionOptions, *, method_name: str) -> None:
|
|
1346
1379
|
if execution.output_dir is None:
|
|
1347
1380
|
raise ValueError(f"ExecutionOptions.output_dir is required for {method_name}")
|
|
@@ -261,7 +261,7 @@ def embed_slides(
|
|
|
261
261
|
slide_records = [manifest.coerce_slide_spec(slide) for slide in slides]
|
|
262
262
|
if not slide_records:
|
|
263
263
|
raise ValueError("At least one slide is required")
|
|
264
|
-
if execution.
|
|
264
|
+
if execution.resolved_num_gpus() > 1:
|
|
265
265
|
distributed_stage.validate_multi_gpu_execution(model, execution)
|
|
266
266
|
emit_progress(
|
|
267
267
|
"run.started",
|
|
@@ -282,7 +282,7 @@ def embed_slides(
|
|
|
282
282
|
slide_records,
|
|
283
283
|
preprocessing,
|
|
284
284
|
output_dir=work_dir,
|
|
285
|
-
num_workers=execution.
|
|
285
|
+
num_workers=execution.resolved_num_preprocessing_workers(),
|
|
286
286
|
)
|
|
287
287
|
process_list.emit_tiling_summary(
|
|
288
288
|
process_list_path,
|
|
@@ -306,19 +306,19 @@ def embed_slides(
|
|
|
306
306
|
execution=execution,
|
|
307
307
|
)
|
|
308
308
|
emit_progress("embedding.started", slide_count=len(embeddable_slides))
|
|
309
|
-
if execution.
|
|
309
|
+
if execution.resolved_num_gpus() > 1 and len(embeddable_slides) > 1:
|
|
310
310
|
emit_progress(
|
|
311
311
|
"embedding.assignment.started",
|
|
312
312
|
slide_count=len(embeddable_slides),
|
|
313
|
-
num_gpus=execution.
|
|
313
|
+
num_gpus=execution.resolved_num_gpus(),
|
|
314
314
|
)
|
|
315
315
|
emit_progress(
|
|
316
316
|
"embedding.assignment.finished",
|
|
317
317
|
slide_count=len(embeddable_slides),
|
|
318
|
-
num_gpus=execution.
|
|
318
|
+
num_gpus=execution.resolved_num_gpus(),
|
|
319
319
|
)
|
|
320
320
|
local_persist_callback = None
|
|
321
|
-
if execution.output_dir is not None and execution.
|
|
321
|
+
if execution.output_dir is not None and execution.resolved_num_gpus() <= 1:
|
|
322
322
|
local_persist_callback, _, _ = persist_callbacks.build_incremental_persist_callback(
|
|
323
323
|
model=model,
|
|
324
324
|
preprocessing=preprocessing,
|
|
@@ -334,7 +334,7 @@ def embed_slides(
|
|
|
334
334
|
work_dir=work_dir,
|
|
335
335
|
on_embedded_slide=local_persist_callback,
|
|
336
336
|
)
|
|
337
|
-
if execution.output_dir is not None and execution.
|
|
337
|
+
if execution.output_dir is not None and execution.resolved_num_gpus() > 1:
|
|
338
338
|
tile_artifacts: list[TileEmbeddingArtifact] = []
|
|
339
339
|
hierarchical_artifacts: list[HierarchicalEmbeddingArtifact] = []
|
|
340
340
|
slide_artifacts: list[SlideEmbeddingArtifact] = []
|
|
@@ -464,7 +464,7 @@ def embed_patients(
|
|
|
464
464
|
slide_records,
|
|
465
465
|
preprocessing,
|
|
466
466
|
output_dir=work_dir,
|
|
467
|
-
num_workers=execution.
|
|
467
|
+
num_workers=execution.resolved_num_preprocessing_workers(),
|
|
468
468
|
)
|
|
469
469
|
process_list.emit_tiling_summary(
|
|
470
470
|
process_list_path,
|
|
@@ -562,7 +562,7 @@ def _select_embedding_path(
|
|
|
562
562
|
work_dir: Path,
|
|
563
563
|
on_embedded_slide: Callable[[SlideSpec, Any, EmbeddedSlide], None] | None = None,
|
|
564
564
|
):
|
|
565
|
-
if execution.
|
|
565
|
+
if execution.resolved_num_gpus() > 1:
|
|
566
566
|
if len(slide_records) == 1:
|
|
567
567
|
return [
|
|
568
568
|
distributed_stage.embed_single_slide_distributed(
|
|
@@ -780,7 +780,7 @@ def run_pipeline(
|
|
|
780
780
|
# initialize their own CUDA and do not depend on this parent-side check.
|
|
781
781
|
# Initializing a CUDA context in the parent here only buys needless fork
|
|
782
782
|
# risk for the downstream tiling pool with no benefit.
|
|
783
|
-
if not tiling_only and execution.
|
|
783
|
+
if not tiling_only and execution.resolved_num_gpus() > 1:
|
|
784
784
|
distributed_stage.validate_multi_gpu_execution(model, execution)
|
|
785
785
|
|
|
786
786
|
resolved_preprocessing = tiling_pipeline.resolve_model_preprocessing(model, preprocessing)
|
|
@@ -803,7 +803,7 @@ def run_pipeline(
|
|
|
803
803
|
slide_records,
|
|
804
804
|
resolved_preprocessing,
|
|
805
805
|
output_dir=output_dir,
|
|
806
|
-
num_workers=execution.
|
|
806
|
+
num_workers=execution.resolved_num_preprocessing_workers(),
|
|
807
807
|
)
|
|
808
808
|
process_list.emit_tiling_summary(
|
|
809
809
|
process_list_path,
|
|
@@ -872,7 +872,7 @@ def run_pipeline(
|
|
|
872
872
|
process_list_path=process_list_path,
|
|
873
873
|
)
|
|
874
874
|
|
|
875
|
-
if execution.
|
|
875
|
+
if execution.resolved_num_gpus() > 1:
|
|
876
876
|
tile_artifacts, hierarchical_artifacts, slide_artifacts = artifacts_collect.collect_distributed_pipeline_artifacts(
|
|
877
877
|
model=model,
|
|
878
878
|
successful_slides=embeddable_slides,
|
|
@@ -990,7 +990,7 @@ def run_pipeline_with_coordinates(
|
|
|
990
990
|
) -> RunResult:
|
|
991
991
|
if execution.output_dir is None:
|
|
992
992
|
raise ValueError("ExecutionOptions.output_dir is required for Pipeline.run_with_coordinates(...)")
|
|
993
|
-
if execution.
|
|
993
|
+
if execution.resolved_num_gpus() > 1:
|
|
994
994
|
distributed_stage.validate_multi_gpu_execution(model, execution)
|
|
995
995
|
|
|
996
996
|
output_dir = Path(execution.output_dir)
|
|
@@ -1036,7 +1036,7 @@ def run_pipeline_with_coordinates(
|
|
|
1036
1036
|
execution=execution,
|
|
1037
1037
|
)
|
|
1038
1038
|
emit_progress("embedding.started", slide_count=len(embeddable_slides))
|
|
1039
|
-
if execution.
|
|
1039
|
+
if execution.resolved_num_gpus() > 1:
|
|
1040
1040
|
tile_artifacts, hierarchical_artifacts, slide_artifacts = artifacts_collect.collect_distributed_pipeline_artifacts(
|
|
1041
1041
|
model=model,
|
|
1042
1042
|
successful_slides=embeddable_slides,
|
|
@@ -34,7 +34,7 @@ def serialize_execution(
|
|
|
34
34
|
if preprocessing is not None and preprocessing.on_the_fly and preprocessing.read_tiles_from is None:
|
|
35
35
|
effective_num_workers_per_gpu, _ = resolve_on_the_fly_num_workers(
|
|
36
36
|
preprocessing.num_cucim_workers,
|
|
37
|
-
num_gpus=execution.
|
|
37
|
+
num_gpus=execution.resolved_num_gpus(),
|
|
38
38
|
)
|
|
39
39
|
return serialize_execution_dict(
|
|
40
40
|
execution,
|
|
@@ -56,7 +56,7 @@ def log_on_the_fly_worker_override_once(
|
|
|
56
56
|
return
|
|
57
57
|
effective_num_workers_per_gpu, worker_context = resolve_on_the_fly_num_workers(
|
|
58
58
|
preprocessing.num_cucim_workers,
|
|
59
|
-
num_gpus=execution.
|
|
59
|
+
num_gpus=execution.resolved_num_gpus(),
|
|
60
60
|
)
|
|
61
61
|
if effective_num_workers_per_gpu == execution.resolved_num_workers_per_gpu():
|
|
62
62
|
return
|
|
@@ -179,7 +179,7 @@ def embed_images_dense(
|
|
|
179
179
|
recipe,
|
|
180
180
|
read_plans=read_plans,
|
|
181
181
|
resolve_transform=deferred_transform_record(
|
|
182
|
-
model, on_cpu_copy=execution.
|
|
182
|
+
model, on_cpu_copy=execution.resolved_num_gpus() > 1
|
|
183
183
|
),
|
|
184
184
|
)
|
|
185
185
|
if skipped:
|
|
@@ -192,10 +192,10 @@ def embed_images_dense(
|
|
|
192
192
|
total=len(specs),
|
|
193
193
|
skipped=skipped,
|
|
194
194
|
encoding=len(remaining),
|
|
195
|
-
num_gpus=execution.
|
|
195
|
+
num_gpus=execution.resolved_num_gpus(),
|
|
196
196
|
)
|
|
197
197
|
if remaining:
|
|
198
|
-
if execution.
|
|
198
|
+
if execution.resolved_num_gpus() == 1:
|
|
199
199
|
_run_dense_images_in_process(
|
|
200
200
|
model,
|
|
201
201
|
remaining,
|
|
@@ -286,7 +286,7 @@ def _run_dense_images_distributed(
|
|
|
286
286
|
run_torchrun_worker(
|
|
287
287
|
module="slide2vec.distributed.dense_image_worker",
|
|
288
288
|
pin_gpus=True, # No collectives: each rank needs only its own GPU.
|
|
289
|
-
num_gpus=execution.
|
|
289
|
+
num_gpus=execution.resolved_num_gpus(),
|
|
290
290
|
output_dir=out_dir,
|
|
291
291
|
request_path=request_path,
|
|
292
292
|
failure_title="Distributed dense image feature extraction failed",
|
|
@@ -207,7 +207,7 @@ def embed_regions_dense(
|
|
|
207
207
|
out_dir,
|
|
208
208
|
identity,
|
|
209
209
|
resolve_transform=deferred_transform_record(
|
|
210
|
-
model, on_cpu_copy=execution.
|
|
210
|
+
model, on_cpu_copy=execution.resolved_num_gpus() > 1
|
|
211
211
|
),
|
|
212
212
|
)
|
|
213
213
|
if skipped:
|
|
@@ -220,10 +220,10 @@ def embed_regions_dense(
|
|
|
220
220
|
total=len(flat),
|
|
221
221
|
skipped=skipped,
|
|
222
222
|
encoding=len(remaining),
|
|
223
|
-
num_gpus=execution.
|
|
223
|
+
num_gpus=execution.resolved_num_gpus(),
|
|
224
224
|
)
|
|
225
225
|
if remaining:
|
|
226
|
-
if execution.
|
|
226
|
+
if execution.resolved_num_gpus() == 1:
|
|
227
227
|
_run_dense_in_process(
|
|
228
228
|
model, remaining, dense=dense, execution=execution, out_dir=out_dir,
|
|
229
229
|
identity=identity,
|
|
@@ -323,7 +323,7 @@ def _run_dense_distributed(model, specs, *, dense, execution, out_dir) -> None:
|
|
|
323
323
|
run_torchrun_worker(
|
|
324
324
|
module="slide2vec.distributed.dense_worker",
|
|
325
325
|
pin_gpus=True, # No collectives: each rank needs only its own GPU.
|
|
326
|
-
num_gpus=execution.
|
|
326
|
+
num_gpus=execution.resolved_num_gpus(),
|
|
327
327
|
output_dir=out_dir,
|
|
328
328
|
request_path=request_path,
|
|
329
329
|
failure_title="Distributed dense feature extraction failed",
|
|
@@ -41,9 +41,9 @@ def validate_multi_gpu_execution(model, execution: ExecutionOptions) -> None:
|
|
|
41
41
|
if not torch.cuda.is_available():
|
|
42
42
|
raise RuntimeError("ExecutionOptions.num_gpus > 1 requires CUDA")
|
|
43
43
|
available_gpus = int(torch.cuda.device_count())
|
|
44
|
-
if execution.
|
|
44
|
+
if execution.resolved_num_gpus() > available_gpus:
|
|
45
45
|
raise ValueError(
|
|
46
|
-
f"ExecutionOptions.num_gpus={execution.
|
|
46
|
+
f"ExecutionOptions.num_gpus={execution.resolved_num_gpus()} exceeds available CUDA devices ({available_gpus})"
|
|
47
47
|
)
|
|
48
48
|
|
|
49
49
|
|
|
@@ -140,16 +140,16 @@ def run_distributed_embedding_stage(
|
|
|
140
140
|
emit_progress(
|
|
141
141
|
"embedding.assignment.started",
|
|
142
142
|
slide_count=len(successful_slides),
|
|
143
|
-
num_gpus=execution.
|
|
143
|
+
num_gpus=execution.resolved_num_gpus(),
|
|
144
144
|
)
|
|
145
145
|
emit_progress(
|
|
146
146
|
"embedding.assignment.finished",
|
|
147
147
|
slide_count=len(successful_slides),
|
|
148
|
-
num_gpus=execution.
|
|
148
|
+
num_gpus=execution.resolved_num_gpus(),
|
|
149
149
|
)
|
|
150
150
|
run_torchrun_worker(
|
|
151
151
|
module="slide2vec.distributed.pipeline_worker",
|
|
152
|
-
num_gpus=execution.
|
|
152
|
+
num_gpus=execution.resolved_num_gpus(),
|
|
153
153
|
output_dir=output_dir,
|
|
154
154
|
request_path=request_path,
|
|
155
155
|
failure_title="Distributed feature extraction failed",
|
|
@@ -187,7 +187,7 @@ def run_distributed_direct_embedding_stage(
|
|
|
187
187
|
request_path.write_text(json.dumps(request_payload, indent=2, sort_keys=True), encoding="utf-8")
|
|
188
188
|
run_torchrun_worker(
|
|
189
189
|
module="slide2vec.distributed.direct_embed_worker",
|
|
190
|
-
num_gpus=execution.
|
|
190
|
+
num_gpus=execution.resolved_num_gpus(),
|
|
191
191
|
output_dir=output_dir,
|
|
192
192
|
request_path=request_path,
|
|
193
193
|
failure_title="Distributed direct embedding failed",
|
|
@@ -276,7 +276,7 @@ def embed_multi_slides_distributed(
|
|
|
276
276
|
assignments = assign_slides_to_ranks(
|
|
277
277
|
slide_records,
|
|
278
278
|
tiling_results,
|
|
279
|
-
num_gpus=execution.
|
|
279
|
+
num_gpus=execution.resolved_num_gpus(),
|
|
280
280
|
)
|
|
281
281
|
with distributed_coordination_dir(work_dir) as coordination_dir:
|
|
282
282
|
run_distributed_direct_embedding_stage(
|
|
@@ -144,7 +144,7 @@ def compute_tile_embeddings_for_slide(
|
|
|
144
144
|
if preprocessing.on_the_fly and preprocessing.read_tiles_from is None and resolved_backend == "cucim":
|
|
145
145
|
effective_num_workers, _ = resolve_on_the_fly_num_workers(
|
|
146
146
|
preprocessing.num_cucim_workers,
|
|
147
|
-
num_gpus=execution.
|
|
147
|
+
num_gpus=execution.resolved_num_gpus(),
|
|
148
148
|
)
|
|
149
149
|
loader_kwargs["num_workers"] = effective_num_workers
|
|
150
150
|
if effective_num_workers == 0:
|
|
@@ -280,7 +280,7 @@ def _encode_hierarchical_subtiles(
|
|
|
280
280
|
if resolved_backend == "cucim":
|
|
281
281
|
effective_num_workers, _ = resolve_on_the_fly_num_workers(
|
|
282
282
|
preprocessing.num_cucim_workers,
|
|
283
|
-
num_gpus=execution.
|
|
283
|
+
num_gpus=execution.resolved_num_gpus(),
|
|
284
284
|
)
|
|
285
285
|
loader_kwargs["num_workers"] = effective_num_workers
|
|
286
286
|
if effective_num_workers == 0:
|
|
@@ -275,7 +275,7 @@ def embed_images(model, images: Sequence[ImageSpec], *, execution) -> list[Image
|
|
|
275
275
|
output_format=execution.output_format,
|
|
276
276
|
identity=identity,
|
|
277
277
|
resolve_transform=deferred_transform_record(
|
|
278
|
-
model, on_cpu_copy=execution.
|
|
278
|
+
model, on_cpu_copy=execution.resolved_num_gpus() > 1
|
|
279
279
|
),
|
|
280
280
|
on_image_mismatch=execution.on_image_mismatch,
|
|
281
281
|
)
|
|
@@ -291,7 +291,7 @@ def embed_images(model, images: Sequence[ImageSpec], *, execution) -> list[Image
|
|
|
291
291
|
total=len(specs),
|
|
292
292
|
skipped=skipped,
|
|
293
293
|
encoding=len(remaining),
|
|
294
|
-
num_gpus=execution.
|
|
294
|
+
num_gpus=execution.resolved_num_gpus(),
|
|
295
295
|
)
|
|
296
296
|
|
|
297
297
|
# The shard loop invalidates each image's stale files right before writing it, so a
|
|
@@ -300,7 +300,7 @@ def embed_images(model, images: Sequence[ImageSpec], *, execution) -> list[Image
|
|
|
300
300
|
# encodes nothing deletes nothing.
|
|
301
301
|
encoded_width = None
|
|
302
302
|
if remaining:
|
|
303
|
-
if execution.
|
|
303
|
+
if execution.resolved_num_gpus() == 1:
|
|
304
304
|
encoded_width = _run_images_in_process(
|
|
305
305
|
model, remaining, execution=execution, out_dir=out_dir, identity=identity,
|
|
306
306
|
stale=plan.stale,
|
|
@@ -417,7 +417,7 @@ def _run_images_distributed(model, specs, *, execution, out_dir, stale) -> int:
|
|
|
417
417
|
run_torchrun_worker(
|
|
418
418
|
module="slide2vec.distributed.image_worker",
|
|
419
419
|
pin_gpus=True, # No collectives: each rank needs only its own GPU.
|
|
420
|
-
num_gpus=execution.
|
|
420
|
+
num_gpus=execution.resolved_num_gpus(),
|
|
421
421
|
output_dir=out_dir,
|
|
422
422
|
request_path=request_path,
|
|
423
423
|
failure_title="Distributed image feature extraction failed",
|
|
@@ -87,8 +87,10 @@ def serialize_execution(
|
|
|
87
87
|
if effective_num_workers_per_gpu is not None
|
|
88
88
|
else execution.num_workers_per_gpu
|
|
89
89
|
),
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
# The launcher's resolution, not the request: a torchrun rank pinned to one GPU must
|
|
91
|
+
# still split its CPU budget across the launcher's world size.
|
|
92
|
+
"num_preprocessing_workers": execution.resolved_num_preprocessing_workers(),
|
|
93
|
+
"num_gpus": execution.resolved_num_gpus(),
|
|
92
94
|
"precision": execution.precision,
|
|
93
95
|
"output_dtype": execution.output_dtype,
|
|
94
96
|
"prefetch_factor": execution.prefetch_factor,
|
|
@@ -89,6 +89,7 @@ def describe_device_mode(model, execution: ExecutionOptions) -> str:
|
|
|
89
89
|
requested_device = getattr(model, "_requested_device", None)
|
|
90
90
|
if requested_device == "cpu":
|
|
91
91
|
return "cpu"
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
num_gpus = execution.resolved_num_gpus()
|
|
93
|
+
if num_gpus > 1:
|
|
94
|
+
return f"{num_gpus} gpus"
|
|
94
95
|
return "gpu"
|
|
@@ -141,6 +141,7 @@ tests/test_encoder_plugins.py
|
|
|
141
141
|
tests/test_encoder_preprocessing.py
|
|
142
142
|
tests/test_encoder_provider_failures.py
|
|
143
143
|
tests/test_encoder_registry.py
|
|
144
|
+
tests/test_execution_options.py
|
|
144
145
|
tests/test_feature_identity.py
|
|
145
146
|
tests/test_gigapath.py
|
|
146
147
|
tests/test_gpfm_genbio_heavy.py
|