-
Notifications
You must be signed in to change notification settings - Fork 0
feat: tile embeddings pipeline with lazy parquet loading #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
53adbfd
feat: implement Virchow2 tile embedding extraction pipeline
vojtech-cifka 53526ea
fix(submit): align embeddings job script with project conventions
vojtech-cifka db5c017
fix: move tiling_run_id into experiment config
vojtech-cifka b5ff141
fix: move tiling_run_id into dataset.mlflow_artifacts
vojtech-cifka e50f6fb
fix(submit): update clone URL to GitHub
vojtech-cifka 7698c76
refactor: use configs that support different models
vojtech-cifka f08ef00
refactor: use new approach for generating embeddings
vojtech-cifka fa16999
Merge branch 'master' into feature/embeddings
vojtech-cifka ba96068
refactor: make some update and update dependencies
vojtech-cifka 1689d3e
fix: tweak submission script and concurrency
vojtech-cifka 1958069
fix: resolve path names
vojtech-cifka ea79dd4
fix: tiling splits filenames
vojtech-cifka 7e32bfb
fix: point rationai sdk to the github repo
vojtech-cifka 5392751
refactor: optimize data loading
vojtech-cifka d5e2c02
feat: limit RAM consumption
vojtech-cifka b2cb642
feat: dynamically split blocks
vojtech-cifka 4adbb56
feat: raise the amount of block
vojtech-cifka 85a914c
feat: add prints
vojtech-cifka 06c818a
feat: change the cpu balance
vojtech-cifka 014d11f
fix: change concurrency
vojtech-cifka f3e4cf4
fix: lower concurrency
vojtech-cifka 606b8e4
feat: add timout, raise concurrency
vojtech-cifka d36dd98
fix: format
vojtech-cifka 531f5f8
fix: use more memory
vojtech-cifka 0cdcebe
Merge branch 'master' into feature/embeddings
vojtech-cifka 7bd8725
fix: retry embed_image on failure and always free tile data
vojtech-cifka b23a0e5
feat: filter zero-coverage tiles before embedding
vojtech-cifka 60a9971
fix: use | operator for PyArrow expression OR, not pc.or_
vojtech-cifka 76dafae
chore: edge cases
vojtech-cifka 59a6516
refactor: use filter_tiles output in embeddings instead of inline fil…
vojtech-cifka d42561b
feat: add temporary filter run id for testing
vojtech-cifka 1434be8
Merge remote-tracking branch 'origin/master' into feature/embeddings
vojtech-cifka e109dc5
feat: generate tile masks from filtered tiles for both train and test…
vojtech-cifka 1d99fcb
fix: change block size to the power two
vojtech-cifka ef74b6c
chore: remove first-row debug log from EmbedTiles
vojtech-cifka 82163ac
refactor: use tenacity for embed retries, narrow to httpx errors
vojtech-cifka 434aa43
chore: drop per-actor throughput log from EmbedTiles
vojtech-cifka 287408f
chore: drop oversized memory reservation on read_parquet
vojtech-cifka 8130f78
fix: build tenacity retryer in __init__ to avoid pickle failure
vojtech-cifka aa37cf3
Revert "chore: drop oversized memory reservation on read_parquet"
vojtech-cifka 27da14e
Revert "refactor: use tenacity for embed retries, narrow to httpx err…
vojtech-cifka ec7a63a
Revert preprocessing/embeddings.py to commit 1d99fcb
vojtech-cifka 7306103
chore: drop oversized memory reservation on read_parquet
vojtech-cifka d1a2289
chore: drop first_row_logged debug print
vojtech-cifka ea437e5
refactor: use tenacity for embed retries, narrow to httpx errors
vojtech-cifka 3a8bacd
Revert "refactor: use tenacity for embed retries, narrow to httpx err…
vojtech-cifka 1c2615f
chore: drop per-actor throughput log, ray reports it
vojtech-cifka ad3a7c5
chore: switch ray data progress to tqdm for non-tty envs
vojtech-cifka 7a07f41
fix: format
vojtech-cifka 23f08cf
Merge remote-tracking branch 'origin/master' into feature/embeddings
vojtech-cifka ac2b4b3
chore: remove duplicate mlflow run id
vojtech-cifka e580771
Merge remote-tracking branch 'origin/master' into feature/embeddings
vojtech-cifka 5eae41c
fix: harden embed retries against pool timeouts
vojtech-cifka e4e83a2
fix: re-add 8GB memory reservation on read_parquet
vojtech-cifka File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
configs/experiment/preprocessing/embeddings_virchow2_05mpp.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # @package _global_ | ||
|
|
||
| defaults: | ||
| - /data: dataset | ||
| - _self_ | ||
|
|
||
| model: virchow2 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| # @package _global_ | ||
|
|
||
| model: ??? | ||
| output_dir: ${project_path}/embeddings | ||
| concurrency: 512 | ||
| block_size: 2048 | ||
| rows_per_file: 5000 | ||
|
|
||
| metadata: | ||
| run_name: "Embeddings: ${model}" | ||
| description: Tile embeddings using model ${model} | ||
| hyperparams: | ||
| model: ${model} | ||
| concurrency: ${concurrency} | ||
| block_size: ${block_size} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| import asyncio | ||
| import shutil | ||
| import time | ||
| from pathlib import Path | ||
| from typing import Any | ||
|
|
||
| import httpx | ||
| import hydra | ||
| import mlflow.artifacts | ||
| import pandas as pd | ||
| import pyarrow.dataset as pads | ||
| import ray | ||
| from omegaconf import DictConfig | ||
| from rationai import AsyncClient # type: ignore[attr-defined] | ||
| from rationai.mlkit import autolog, with_cli_args | ||
| from rationai.mlkit.lightning.loggers import MLFlowLogger | ||
| from ratiopath.tiling.read_slide_tiles import read_slide_tiles | ||
| from ray.data.expressions import col | ||
|
|
||
|
|
||
| class EmbedTiles: | ||
| def __init__(self, model: str, concurrency: int) -> None: | ||
| self.model = model | ||
| self.client = AsyncClient( | ||
| limits=httpx.Limits( | ||
| max_connections=concurrency, max_keepalive_connections=concurrency | ||
| ), | ||
| timeout=200, | ||
| ) | ||
|
|
||
| async def __call__(self, row: dict[str, Any]) -> dict[str, Any]: | ||
| try: | ||
| last_exc: httpx.HTTPError | None = None | ||
| for attempt in range(6): | ||
| try: | ||
| embedding = ( | ||
| (await self.client.models.embed_image(self.model, row["tile"])) | ||
| .reshape(-1) | ||
| .tolist() | ||
| ) | ||
| break | ||
| except httpx.HTTPError as exc: | ||
| last_exc = exc | ||
| if attempt < 5: | ||
| await asyncio.sleep(min(60, 2**attempt)) | ||
| else: | ||
| raise RuntimeError("embed_image failed after 6 attempts") from last_exc | ||
| finally: | ||
| del row["tile"] | ||
| row["embedding"] = embedding | ||
| return row | ||
|
vojtech-cifka marked this conversation as resolved.
|
||
|
|
||
|
|
||
| @with_cli_args(["+preprocessing=embeddings"]) | ||
| @hydra.main(config_path="../configs", config_name="preprocessing", version_base=None) | ||
| @autolog | ||
| def main(config: DictConfig, logger: MLFlowLogger) -> None: | ||
| for name in ["train", "test"]: | ||
| split_folder = Path( | ||
| mlflow.artifacts.download_artifacts( | ||
| run_id=config.dataset.mlflow_artifacts.tiling_run_id, | ||
| artifact_path=f"{name}_split", | ||
| ) | ||
| ) | ||
| slides = pd.read_parquet(split_folder / "slides.parquet") | ||
| slide_info = slides.set_index("id")[ | ||
| ["path", "level", "tile_extent_x", "tile_extent_y"] | ||
| ].to_dict("index") | ||
|
|
||
| tiles_path = Path( | ||
| mlflow.artifacts.download_artifacts( | ||
| run_id=config.dataset.mlflow_artifacts.filter_tiles_run_id, | ||
| artifact_path=f"filter_tiles/{name}_tiles.parquet", | ||
| ) | ||
| ) | ||
| num_rows = pads.dataset(str(tiles_path), format="parquet").count_rows() | ||
| num_blocks = max(1, num_rows // config.block_size) | ||
|
|
||
| ds = ray.data.read_parquet( | ||
| str(tiles_path), | ||
| columns=["slide_id", "x", "y"], | ||
| ray_remote_args={"memory": 8 * 1024**3}, | ||
| override_num_blocks=num_blocks, | ||
|
vojtech-cifka marked this conversation as resolved.
|
||
| ).map( | ||
| lambda row, si: {**row, **si[row["slide_id"]]}, | ||
| fn_kwargs={"si": slide_info}, | ||
| ) | ||
| ds = ds.with_column( | ||
| "tile", | ||
| read_slide_tiles( # pyright: ignore[reportCallIssue] | ||
| col("path"), | ||
| col("x"), | ||
| col("y"), | ||
| col("tile_extent_x"), | ||
| col("tile_extent_y"), | ||
| col("level"), | ||
| ), | ||
| num_cpus=1, | ||
| memory=4 * 1024**3, | ||
| ) | ||
| ds = ds.drop_columns(["path", "level", "tile_extent_x", "tile_extent_y"]) | ||
| ds = ds.map( | ||
| EmbedTiles, # pyright: ignore[reportArgumentType] | ||
| fn_constructor_args=(config.model, config.concurrency), | ||
| compute=ray.data.ActorPoolStrategy( | ||
| min_size=4, | ||
| max_size=4, | ||
| max_tasks_in_flight_per_actor=max(1, config.concurrency // 4), | ||
| ), | ||
| max_concurrency=config.concurrency, | ||
| ) | ||
|
|
||
| split_dir = Path(config.output_dir) / str(name) | ||
| split_dir.mkdir(parents=True, exist_ok=True) | ||
| tiles_parquet_dir = split_dir / "tiles" | ||
| if tiles_parquet_dir.exists(): | ||
| shutil.rmtree(tiles_parquet_dir) | ||
|
|
||
| slides.to_parquet(split_dir / "slides.parquet", index=False) | ||
|
|
||
| t = time.monotonic() | ||
| print(f"[main] starting write_parquet for split={name}") | ||
| ds.write_parquet(str(tiles_parquet_dir), min_rows_per_file=config.rows_per_file) | ||
| print(f"[main] write_parquet finished in {time.monotonic() - t:.1f}s") | ||
|
|
||
| logger.log_artifacts(str(split_dir), str(name)) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| ctx = ray.data.DataContext.get_current() | ||
| ctx.enable_rich_progress_bars = False | ||
| ctx.use_ray_tqdm = True | ||
| ctx.target_max_block_size = 64 * 1024 * 1024 | ||
|
|
||
| with ray.init( | ||
| runtime_env={"excludes": [".git", ".venv"]}, | ||
| object_store_memory=16 * 1024**3, | ||
| ): | ||
| main() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| from kube_jobs import storage, submit_job | ||
|
|
||
|
|
||
| submit_job( | ||
| job_name="tissue-classification-embeddings", | ||
| username=..., | ||
| public=False, | ||
| cpu=8, | ||
| memory="64Gi", | ||
| shm="24Gi", | ||
| script=[ | ||
| "git clone https://github.com/RationAI/tissue-classification.git workdir", | ||
| "cd workdir", | ||
| "uv sync", | ||
| "uv run -m preprocessing.embeddings +experiment=...", | ||
| ], | ||
| storage=[storage.secure.DATA, storage.secure.PROJECTS], | ||
| ) | ||
|
vojtech-cifka marked this conversation as resolved.
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.