Skip to content

Commit 1b5c12e

Browse files
authored
Merge branch 'dev' into weekly_preview_fix
2 parents b808df9 + fd8a819 commit 1b5c12e

12 files changed

Lines changed: 340 additions & 64 deletions

File tree

docs/requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ sphinxcontrib-serializinghtml
2020
sphinx-autodoc-typehints==1.11.1
2121
pandas
2222
einops
23-
transformers>=4.53.0
24-
mlflow>=2.12.2,<3.13
23+
transformers>=5.5.0
24+
mlflow>=3.15.2
2525
clearml>=1.10.0rc0
2626
tensorboardX
2727
imagecodecs; platform_system == "Linux" or platform_system == "Darwin"

monai/bundle/scripts.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -997,7 +997,8 @@ def run(
997997
common parameters shown below will be added and can be passed through the `override` parameter of this method.
998998
999999
- ``"output_dir"``: the path to save mlflow tracking outputs locally, default to "<bundle root>/eval".
1000-
- ``"tracking_uri"``: uri to save mlflow tracking outputs, default to "/output_dir/mlruns".
1000+
- ``"tracking_uri"``: uri to save mlflow tracking outputs, default to a local SQLite database
1001+
at "<output_dir>/mlruns.db" with run artifacts kept under "<output_dir>/mlruns".
10011002
- ``"experiment_name"``: experiment name for this run, default to "monai_experiment".
10021003
- ``"run_name"``: the name of current run.
10031004
- ``"save_execute_config"``: whether to save the executed config files. It can be `False`, `/path/to/artifacts`

monai/bundle/utils.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,10 @@
116116
"configs": {
117117
# if no "output_dir" in the bundle config, default to "<bundle root>/eval"
118118
"output_dir": "$@bundle_root + '/eval'",
119-
# use URI to support linux, mac and windows os
120-
"tracking_uri": "$monai.utils.path_to_uri(@output_dir) + '/mlruns'",
119+
# MLflow 3.13+ rejects the filesystem (file store) tracking backend, so default tracking
120+
# to a local SQLite database. The handler keeps run artifacts under "<output_dir>/mlruns"
121+
# (next to the db). A URI is used so the path is valid on linux, mac and windows os.
122+
"tracking_uri": "$monai.utils.path_to_sqlite_uri(@output_dir + '/mlruns.db')",
121123
"experiment_name": "monai_experiment",
122124
"run_name": None,
123125
# may fill it at runtime

monai/handlers/mlflow_handler.py

Lines changed: 98 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,16 @@
2222
from torch.utils.data import Dataset
2323

2424
from monai.apps.utils import get_logger
25-
from monai.utils import CommonKeys, IgniteInfo, ensure_tuple, flatten_dict, min_version, optional_import
25+
from monai.utils import (
26+
CommonKeys,
27+
IgniteInfo,
28+
ensure_tuple,
29+
flatten_dict,
30+
min_version,
31+
optional_import,
32+
path_to_sqlite_uri,
33+
path_to_uri,
34+
)
2635

2736
Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events")
2837
mlflow, _ = optional_import("mlflow", descriptor="Please install mlflow before using MLFlowHandler.")
@@ -68,7 +77,16 @@ class MLFlowHandler:
6877
tracking_uri: connects to a tracking URI. can also set the `MLFLOW_TRACKING_URI` environment
6978
variable to have MLflow find a URI from there. in both cases, the URI can either be
7079
an HTTP/HTTPS URI for a remote server, a database connection string, or a local path
71-
to log data to a directory. The URI defaults to path `mlruns`.
80+
to log data to a directory. When no ``tracking_uri`` is provided and the
81+
``MLFLOW_TRACKING_URI`` environment variable is unset, the handler now
82+
defaults to a local SQLite database backend at ``sqlite:///<cwd>/mlruns.db`` with
83+
artifacts stored under ``<cwd>/mlruns``. The default was changed from the filesystem
84+
(file store) backend because MLflow 3.13+ raises an exception for the file store unless
85+
``MLFLOW_ALLOW_FILE_STORE=true`` is set; SQLite is the backend MLflow recommends and it
86+
does not raise. Any explicitly provided ``tracking_uri`` is passed through unchanged
87+
unless ``MLFLOW_TRACKING_URI`` is set (which takes precedence); local file paths and
88+
``file://`` URIs are rejected because MLflow no longer supports the filesystem (file
89+
store) tracking backend.
7290
for more details: https://mlflow.org/docs/latest/python_api/mlflow.html#mlflow.set_tracking_uri.
7391
iteration_log: whether to log data to MLFlow when iteration completed, default to `True`.
7492
``iteration_log`` can be also a function and it will be interpreted as an event filter
@@ -113,6 +131,11 @@ class MLFlowHandler:
113131
optimizer_param_names: parameter names in the optimizer that need to be recorded during running the
114132
workflow, default to `'lr'`.
115133
close_on_complete: whether to close the mlflow run in `complete` phase in workflow, default to False.
134+
artifact_location: the location to store run artifacts in, passed to MLflow when the experiment is
135+
created. When ``None`` and a local SQLite backend is used (from the ``tracking_uri`` argument
136+
or the ``MLFLOW_TRACKING_URI`` environment variable), it defaults to an ``mlruns`` directory
137+
next to the database file; for other backends ``None`` lets MLflow decide based on the
138+
``tracking_uri``. Has no effect if the experiment already exists.
116139
117140
For more details of MLFlow usage, please refer to: https://mlflow.org/docs/latest/index.html.
118141
@@ -141,6 +164,7 @@ def __init__(
141164
artifacts: str | Sequence[Path] | None = None,
142165
optimizer_param_names: str | Sequence[str] = "lr",
143166
close_on_complete: bool = False,
167+
artifact_location: str | None = None,
144168
) -> None:
145169
self.iteration_log = iteration_log
146170
self.epoch_log = epoch_log
@@ -156,7 +180,39 @@ def __init__(
156180
self.experiment_param = experiment_param
157181
self.artifacts = ensure_tuple(artifacts)
158182
self.optimizer_param_names = ensure_tuple(optimizer_param_names)
159-
self.client = mlflow.MlflowClient(tracking_uri=tracking_uri if tracking_uri else None)
183+
# When no tracking_uri is provided, default to a local SQLite backend instead of the
184+
# filesystem (file store) backend. MLflow 3.13+ raises for the file store unless
185+
# `MLFLOW_ALLOW_FILE_STORE=true` is set, while SQLite is the recommended backend and does
186+
# not raise. Artifacts cannot live inside a database, so by default they are stored under
187+
# the `./mlruns` directory (where the previous file store default kept them) via the
188+
# experiment `artifact_location`. Any explicitly provided tracking_uri is left unchanged.
189+
self.artifact_location = artifact_location
190+
# Resolve the effective tracking URI. The `MLFLOW_TRACKING_URI` environment variable takes
191+
# priority so it can override a hard-coded `tracking_uri` argument; both configure the
192+
# artifact location the same way.
193+
env_tracking_uri = os.environ.get("MLFLOW_TRACKING_URI")
194+
effective_tracking_uri = env_tracking_uri or tracking_uri
195+
# When neither is set, fall back to the local SQLite default described above.
196+
if not effective_tracking_uri:
197+
tracking_uri = effective_tracking_uri = path_to_sqlite_uri(os.path.join(os.getcwd(), "mlruns.db"))
198+
# For a local SQLite backend, keep run artifacts in an `mlruns` directory next to the
199+
# database file (mirroring the previous file-store layout) unless the caller set
200+
# `artifact_location`. Other backends (e.g. a remote server) are left to MLflow to decide.
201+
if self.artifact_location is None and effective_tracking_uri.startswith("sqlite:///"):
202+
db_path = Path(effective_tracking_uri[len("sqlite:///") :])
203+
self.artifact_location = path_to_uri(db_path.parent / "mlruns")
204+
# MLflow 3.13+ refuses the filesystem (file store) tracking backend, and 3.14+ resolves
205+
# the store eagerly at client construction, so a local path or ``file://`` URI would raise
206+
# an opaque MlflowException. Reject those here with an actionable message instead.
207+
if effective_tracking_uri.startswith("file://") or "://" not in effective_tracking_uri:
208+
raise ValueError(
209+
"MLflow no longer supports the filesystem (file store) tracking backend; got "
210+
f"tracking_uri={effective_tracking_uri!r}. Use a SQLite URI "
211+
"(sqlite:///<path>/mlruns.db) or a remote tracking URI instead."
212+
)
213+
# Only the argument is passed to the client; when `MLFLOW_TRACKING_URI` took priority it
214+
# is left None so MLflow resolves the environment variable itself.
215+
self.client = mlflow.MlflowClient(tracking_uri=None if env_tracking_uri else tracking_uri)
160216
self.run_finish_status = mlflow.entities.RunStatus.to_string(mlflow.entities.RunStatus.FINISHED)
161217
self.close_on_complete = close_on_complete
162218
self.experiment = None
@@ -246,7 +302,12 @@ def _set_experiment(self):
246302
try:
247303
experiment = self.client.get_experiment_by_name(self.experiment_name)
248304
if not experiment:
249-
experiment_id = self.client.create_experiment(self.experiment_name)
305+
# pass an explicit artifact_location (set for the default SQLite backend, or
306+
# by the caller) so artifacts land in the intended directory; when it is
307+
# None MLflow decides based on the tracking_uri.
308+
experiment_id = self.client.create_experiment(
309+
self.experiment_name, artifact_location=self.artifact_location
310+
)
250311
experiment = self.client.get_experiment(experiment_id)
251312
break
252313
except MlflowException as e:
@@ -338,14 +399,43 @@ def complete(self) -> None:
338399
for artifact in artifact_list:
339400
self.client.log_artifact(self.cur_run.info.run_id, artifact)
340401

402+
def _dispose_sqlite_store(self) -> None:
403+
"""
404+
Release MLflow's SQLAlchemy engine when a local SQLite tracking backend is used.
405+
406+
MLflow keeps the SQLite connection open for the lifetime of the client, which on
407+
Windows prevents the database file from being deleted. MLflow exposes no public
408+
client close/dispose API, so this reaches into its internals defensively to release
409+
the engine. It is a no-op for non-SQLite backends.
410+
"""
411+
tracking_uri = getattr(self.client, "tracking_uri", "")
412+
if not isinstance(tracking_uri, str) or not tracking_uri.startswith("sqlite:"):
413+
return
414+
store = getattr(getattr(self.client, "_tracking_client", None), "store", None)
415+
if store is None:
416+
return
417+
dispose = getattr(store, "_dispose_engine", None)
418+
if callable(dispose):
419+
dispose()
420+
else:
421+
engine = getattr(store, "engine", None)
422+
if engine is not None:
423+
engine.dispose()
424+
read_engine = getattr(store, "read_engine", None)
425+
if read_engine is not None:
426+
read_engine.dispose()
427+
341428
def close(self) -> None:
342429
"""
343-
Stop current running logger of MLFlow.
430+
Stop current running logger of MLFlow and release local SQLite resources.
344431
345432
"""
346-
if self.cur_run:
347-
self.client.set_terminated(self.cur_run.info.run_id, self.run_finish_status)
348-
self.cur_run = None
433+
try:
434+
if self.cur_run:
435+
self.client.set_terminated(self.cur_run.info.run_id, self.run_finish_status)
436+
self.cur_run = None
437+
finally:
438+
self._dispose_sqlite_store()
349439

350440
def epoch_completed(self, engine: Engine) -> None:
351441
"""

monai/networks/nets/transchex.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
transformers = optional_import("transformers")
2424
load_tf_weights_in_bert = optional_import("transformers", name="load_tf_weights_in_bert")[0]
2525
cached_file = optional_import("transformers.utils", name="cached_file")[0]
26+
BertConfig = optional_import("transformers", name="BertConfig")[0]
2627
BertEmbeddings = optional_import("transformers.models.bert.modeling_bert", name="BertEmbeddings")[0]
2728
BertLayer = optional_import("transformers.models.bert.modeling_bert", name="BertLayer")[0]
2829

@@ -222,7 +223,11 @@ def __init__(
222223
223224
"""
224225
super().__init__()
225-
self.config = type("obj", (object,), bert_config)
226+
self.config = BertConfig(**bert_config)
227+
# explicitly select the eager attention path: transformers>=4.48 dispatches attention
228+
# implementations via `config._attn_implementation`, which is otherwise left unset since
229+
# `bert_config` above does not come from a `from_pretrained` call.
230+
self.config._attn_implementation = "eager"
226231
self.embeddings = BertEmbeddings(self.config)
227232
self.language_encoder = nn.ModuleList([BertLayer(self.config) for _ in range(num_language_layers)])
228233
self.vision_encoder = nn.ModuleList([BertLayer(self.config) for _ in range(num_vision_layers)])

monai/utils/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
is_sqrt,
9090
issequenceiterable,
9191
list_to_dict,
92+
path_to_sqlite_uri,
9293
path_to_uri,
9394
pprint_edges,
9495
progress_bar,

monai/utils/misc.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from math import log10
2828
from pathlib import Path
2929
from typing import TYPE_CHECKING, Any, TypeVar, cast, overload
30+
from urllib.parse import quote
3031

3132
import numpy as np
3233
import torch
@@ -69,6 +70,7 @@
6970
"save_obj",
7071
"label_union",
7172
"path_to_uri",
73+
"path_to_sqlite_uri",
7274
"pprint_edges",
7375
"check_key_duplicates",
7476
"CheckKeyDuplicatesYamlLoader",
@@ -727,6 +729,23 @@ def path_to_uri(path: PathLike) -> str:
727729
return Path(path).absolute().as_uri()
728730

729731

732+
def path_to_sqlite_uri(path: PathLike) -> str:
733+
"""
734+
Convert a database file path to a SQLite connection URI, e.g. for use as an MLflow
735+
``tracking_uri``. If not an absolute path, it is converted to an absolute path first.
736+
737+
A forward-slash (POSIX) path is used so the URI is valid on Windows as well as POSIX:
738+
on Windows this yields ``sqlite:///C:/path/db.sqlite`` and on POSIX ``sqlite:////path/db.sqlite``.
739+
URI-special characters in the path (e.g. ``?``, ``#``) are percent-encoded so they are not
740+
misparsed as query/fragment components by SQLAlchemy.
741+
742+
Args:
743+
path: input database file path, can be a string or `Path` object.
744+
745+
"""
746+
return f"sqlite:///{quote(Path(path).absolute().as_posix(), safe='/:')}"
747+
748+
730749
def pprint_edges(val: Any, n_lines: int = 20) -> str:
731750
"""
732751
Pretty print the head and tail ``n_lines`` of ``val``, and omit the middle part if the part has more than 3 lines.

pyproject.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
[build-system]
33
requires = [
4-
"setuptools",
4+
"setuptools>=78.1.1",
55
"wheel",
66
"versioneer[toml]",
77
"more-itertools>=8.0",
@@ -71,7 +71,7 @@ all = [
7171
"lpips==0.1.4",
7272
"matplotlib>=3.6.3",
7373
"MetricsReloaded @ git+https://github.com/Project-MONAI/MetricsReloaded@monai-support",
74-
"mlflow>=2.12.2,<3.13",
74+
"mlflow>=3.15.2",
7575
"nibabel",
7676
"ninja",
7777
"nni; platform_system == 'Linux' and 'arm' not in platform_machine and 'aarch' not in platform_machine",
@@ -103,7 +103,7 @@ all = [
103103
"torchio",
104104
"torchvision",
105105
"tqdm>=4.47.0",
106-
"transformers>=4.53.0, <5.0",
106+
"transformers>=5.5.0",
107107
"zarr"
108108
]
109109
clearml = ["clearml>=1.10.0rc0"]
@@ -126,7 +126,7 @@ lmdb = ["lmdb"]
126126
lpips = ["lpips==0.1.4"]
127127
matplotlib = ["matplotlib>=3.6.3"]
128128
metrics_reloaded = ["MetricsReloaded @ git+https://github.com/Project-MONAI/MetricsReloaded@monai-support"]
129-
mlflow = ["mlflow>=2.12.2,<3.13"]
129+
mlflow = ["mlflow>=3.15.2"]
130130
nibabel = ["nibabel"]
131131
nni = [
132132
"nni; platform_system == 'Linux' and 'arm' not in platform_machine and 'aarch' not in platform_machine",
@@ -155,7 +155,7 @@ tifffile = ["tifffile; platform_system == 'Linux' or platform_system == 'Darwin'
155155
torchio = ["torchio"]
156156
torchvision = ["torchvision"]
157157
tqdm = ["tqdm>=4.47.0"]
158-
transformers = ["transformers>=4.53.0, <5.0"] # 5.x references torch.float8_e8m0fnu absent in older PyTorch builds
158+
transformers = ["transformers>=5.5.0"] # 5.x needs the transchex BertLayer/BertConfig updates; re-verify the NGC image float8 concern
159159
zarr = ["zarr"]
160160
# these dependencies are for testing/building only, they aren't needed for regular use so don't appear in "all"
161161
testing = [

0 commit comments

Comments
 (0)