管理 MLflow 模型中的依赖项
MLflow 模型 是一种标准格式,用于将机器学习模型及其依赖项和其他元数据打包在一起。通过将模型及其依赖项构建在一起,可以实现跨多种平台和工具的可重复性和可移植性。
例如,当你使用 MLflow 跟踪 API(如 mlflow.pytorch.log_model())创建 MLflow 模型时,MLflow 会自动推断你所使用的模型类型(flavor)所需的依赖项,并将它们记录为模型元数据的一部分。随后,当你部署模型进行预测时,MLflow 会自动将这些依赖项安装到环境中。因此,通常情况下,你无需担心 MLflow 模型中的依赖项管理问题。
然而,在某些情况下,你可能需要添加或修改某些依赖项。本页简要介绍了 MLflow 如何管理依赖项,并为针对你的用例自定义依赖项提供了指导。
提高 MLflow 依赖项推断准确性的一个技巧是在保存模型时添加 input_example(输入示例)。这使得 MLflow 能够在保存模型之前执行一次模型预测,从而捕获预测期间使用的依赖项。关于此参数的更多详细用法,请参阅 模型输入示例。
MLflow 如何记录模型依赖项
MLflow 模型保存在指定目录中,具有以下结构
my_model/
├── MLmodel
├── model.pkl
├── conda.yaml
├── python_env.yaml
└── requirements.txt
模型依赖项由以下文件定义(对于其他文件,请参阅关于存储格式章节中的指南)
python_env.yaml- 该文件包含使用 virtualenv 恢复模型环境所需的信息:(1) Python 版本,(2) 构建工具(如 pip、setuptools 和 wheel),(3) 模型的 pip 依赖项(引用 requirements.txt)。requirements.txt- 定义运行模型所需的 pip 依赖项集合。conda.yaml- 定义运行模型所需的 conda 环境。当你指定conda作为恢复模型环境的环境管理器时,将使用此文件。
请注意,不建议手动编辑这些文件以添加或删除依赖项。它们是由 MLflow 自动生成的,你手动进行的任何更改在下次保存模型时都会被覆盖。相反,你应该使用以下各节中描述的推荐方法。
使用 MLFLOW_LOCK_MODEL_DEPENDENCIES 锁定模型依赖项
自 MLflow 2.16.0 起可用
在记录模型时,MLflow 可以自动将直接和传递性模型依赖项锁定到其精确版本。这通过在模型创建时捕获完整的依赖树来确保可重复性。
要启用依赖项锁定,请设置 MLFLOW_LOCK_MODEL_DEPENDENCIES 环境变量
export MLFLOW_LOCK_MODEL_DEPENDENCIES=true
启用后,MLflow 会使用 uv(如果已安装)来解析并锁定所有依赖项,包括它们的传递性依赖项。锁定的依赖项随后会保存在模型的 requirements.txt 文件中。
无锁定的示例(默认)
mlflow==2.9.2
scikit-learn==1.3.2
cloudpickle==3.0.0
启用锁定后的示例
mlflow==2.9.2
scikit-learn==1.3.2
cloudpickle==3.0.0
numpy==1.24.3
scipy==1.11.4
joblib==1.3.2
threadpoolctl==3.2.0
# ... other transitive dependencies
依赖项锁定需要安装 uv。如果 uv 不可用,MLflow 将跳过锁定步骤并使用标准的依赖项推断。安装 uv:
pip install uv
锁定依赖项可能会显著增加 requirements.txt 文件的大小,因为它包含了所有传递性依赖项。这提供了更好的可重复性,但可能会使环境变得更僵化。
示例
以下展示了使用 mlflow.sklearn.log_model 记录模型时 MLflow 生成的环境文件示例
python_env.yaml
python: 3.9.8
build_dependencies:
- pip==23.3.2
- setuptools==69.0.3
- wheel==0.42.0
dependencies:
- -r requirements.txt
requirements.txt
mlflow==2.9.2
scikit-learn==1.3.2
cloudpickle==3.0.0
conda.yaml
name: mlflow-env
channels:
- conda-forge
dependencies:
- python=3.9.8
- pip
- pip:
- mlflow==2.9.2
- scikit-learn==1.3.2
- cloudpickle==3.0.0
使用 uv 进行依赖项管理
实验性功能:这些参数可能会在未来版本中更改或删除,恕不另行通知。
uv 是一款快速的 Python 包管理器,支持基于锁文件的依赖项解析。当你的项目使用 uv 时,MLflow 可以在记录模型时直接读取你的 uv.lock 文件,以捕获精确、可重复的依赖项。这避免了仅依赖于基于导入的推断,并确保每个传递性依赖项都被固定在你测试过的版本上。
与 MLFLOW_LOCK_MODEL_DEPENDENCIES 的区别
MLflow 提供了两种独立的机制来捕获锁定的依赖项
-
MLFLOW_LOCK_MODEL_DEPENDENCIES:在模型记录时,根据推断出的需求即时触发解析和锁定。它不需要预先存在的uv.lock文件,但由于依赖项是从推断的需求规范中重新解析的,因此生成的版本可能与你当前环境中安装的精确包版本有所不同。 -
使用 uv 进行依赖项管理(本节):直接从你基于 uv 管理的项目中现有的
uv.lock文件读取依赖项。这种方法捕获了你锁定并测试过的精确版本,而不是在记录时进行重新解析。
如果你的项目已经使用 uv 作为包管理器且拥有 uv.lock 文件,请使用本节中的方法。如果你希望在不管理 uv 项目的情况下自动锁定传递性依赖项,请使用 MLFLOW_LOCK_MODEL_DEPENDENCIES。
限制
- 不支持 uv 工作区(workspaces)。 如果你的项目使用了 uv 工作区,MLflow 将无法正确处理工作区成员。为了使 MLflow uv 集成生效,每个工作区成员都必须被视为独立的 uv 项目。
工作原理
当你记录模型时,MLflow 会检查当前工作目录中是否存在 uv.lock 和 pyproject.toml。如果两个文件都存在且已安装 uv(0.6.10 或更高版本),MLflow 将运行 uv export 以从锁文件生成固定的需求列表。该列表将替代标准的基于导入的依赖项推断。
MLflow 还会将 uv 项目文件(uv.lock 和 pyproject.toml)以及 .python-version 文件(如果存在)复制到模型工件目录中。这些文件使得在后续加载模型时可以使用 uv sync 精确恢复环境。
这适用于所有 MLflow 模型类型。例如,在 uv 管理的项目中记录 scikit-learn 模型
import mlflow
from sklearn.linear_model import LinearRegression
from sklearn.datasets import make_regression
X, y = make_regression(n_samples=100, n_features=4)
model = LinearRegression().fit(X, y)
# uv.lock is auto-detected in the current directory
mlflow.sklearn.log_model(model, name="model")
# Requirements are exported from uv.lock, not inferred from imports
自动检测
默认情况下,uv 项目检测处于启用状态。当你使用任何类型(包括 mlflow.sklearn.log_model()、mlflow.pytorch.log_model()、mlflow.transformers.log_model()、mlflow.pyfunc.log_model() 等)记录模型时,MLflow 会在当前工作目录中查找 uv.lock 和 pyproject.toml。所有调用 infer_pip_requirements 的类型都会受益于 uv 自动检测。
要禁用自动检测,请设置 MLFLOW_UV_AUTO_DETECT 环境变量
export MLFLOW_UV_AUTO_DETECT=false
你还可以通过 MLFLOW_LOG_UV_FILES 控制是否将 uv 项目文件(uv.lock、pyproject.toml 和 .python-version)保存为模型工件
# Disable copying uv.lock, pyproject.toml, and .python-version into model artifacts
export MLFLOW_LOG_UV_FILES=false
指定 uv 项目路径
如果你的 uv 项目不在当前工作目录中(这在 monorepo 或非标准布局中很常见),请使用 uv_project_path 参数指向正确的目录
import mlflow
mlflow.pyfunc.log_model(
python_model=my_model,
name="model",
input_example=input_data,
uv_project_path="/path/to/my-uv-project",
)
MLflow 将在指定目录中查找 uv.lock 和 pyproject.toml,而不是在当前工作目录中。
包含依赖项组
uv 支持依赖项组来组织相关包(例如用于模型特定库的 ml 组)。在导出需求时,使用 uv_groups 参数包含一个或多个组
import mlflow
mlflow.pyfunc.log_model(
python_model=my_model,
name="model",
input_example=input_data,
uv_groups=["ml", "data"],
)
这对应于 uv export --group ml --group data。组依赖项将与项目的默认依赖项一起包含。
包含可选扩展
uv 支持在 pyproject.toml 中定义的可选扩展(标准的 [project.optional-dependencies] 表)。使用 uv_extras 参数包含一个或多个扩展
import mlflow
mlflow.pyfunc.log_model(
python_model=my_model,
name="model",
input_example=input_data,
uv_extras=["gpu", "serving"],
)
这对应于 uv export --extra gpu --extra serving。
有关包含 pyproject.toml、uv.lock 以及演示自动检测、显式路径、组和扩展的脚本的完整可运行示例,请参阅完整示例代码。
uv_project_path、uv_groups 和 uv_extras 参数在 mlflow.pyfunc.log_model() 和 mlflow.pyfunc.save_model() 上可用。对于其他类型(sklearn、pytorch、transformers 等),当当前工作目录中存在 uv.lock 和 pyproject.toml 时,uv 自动检测会自动生效。
必须安装 uv 0.6.10 或更高版本才能使用这些功能。如果 uv 不可用,MLflow 将回退到标准的基于导入的依赖项推断并记录警告。
向 MLflow 模型添加额外依赖项
MLflow 会推断模型类型库所需的依赖项,但你的模型可能还依赖其他库,例如用于数据预处理的库。在这种情况下,你可以通过在记录模型时指定 extra_pip_requirements 参数来向模型添加额外依赖项。例如,
import mlflow
class CustomModel(mlflow.pyfunc.PythonModel):
def predict(self, context, model_input):
# your model depends on pandas
import pandas as pd
...
return prediction
# Log the model
mlflow.pyfunc.log_model(
python_model=CustomModel(),
name="model",
extra_pip_requirements=["pandas==2.0.3"],
input_example=input_data,
)
额外的依赖项将按如下方式添加到 requirements.txt 中(对于 conda.yaml 也是如此)
mlflow==2.9.2
cloudpickle==3.0.0
pandas==2.0.3 # added
在这种情况下,MLflow 在部署模型进行预测时,除了推断出的依赖项外,还会安装 Pandas 2.0.3。
一旦你记录了带有依赖项的模型,建议在沙盒环境中进行测试,以避免将模型部署到生产环境时出现任何依赖项问题。自 MLflow 2.10.0 起,你可以使用 mlflow.models.predict() API 在虚拟环境中快速测试模型。请参阅 验证预测环境 以获取更多详细信息。
自行定义所有依赖项
或者,你也可以从头开始定义所有依赖项,而不是添加额外的依赖项。为此,请在记录模型时指定 pip_requirements。例如,
import mlflow
# Log the model
mlflow.sklearn.log_model(
sk_model=model,
name="model",
pip_requirements=[
"mlflow-skinny==2.9.2",
"cloudpickle==2.5.8",
"scikit-learn==1.3.1",
],
)
手动定义的依赖项将覆盖 MLflow 从模型类型库中检测到的默认依赖项。
mlflow-skinny==2.9.2
cloudpickle==2.5.8
scikit-learn==1.3.1
在声明与训练期间不同的依赖项时请务必小心,因为这可能会有风险并导致意外行为。确保一致性的最安全方法是依赖 MLflow 自动推断出的默认依赖项。
一旦你记录了带有依赖项的模型,建议在沙盒环境中进行测试,以避免将模型部署到生产环境时出现任何依赖项问题。自 MLflow 2.10.0 起,你可以使用 mlflow.models.predict() API 在虚拟环境中快速测试模型。请参阅 验证预测环境 以获取更多详细信息。
保存额外代码依赖项与 MLflow 模型 - 自动推断
自动代码依赖项推断目前仅支持 Python Function 模型。对其他命名模型类型的支持将在 MLflow 的未来版本中提供。
在 MLflow 2.13.0 版本中,引入了一种包含自定义依赖代码的新方法,扩展了保存或记录模型时声明 code_paths 的现有功能。此新功能利用导入依赖项分析,通过检查 Python 模型定义中引用的导入模块,自动推断模型所需的代码依赖项。
要使用此功能,你只需在记录时将参数 infer_code_paths(默认为 False)设置为 True。使用这种依赖项推断方法时,你无需像 MLflow 2.13.0 之前那样显式定义 code_paths 目录位置。
下面是使用此功能的示例,我们正在记录一个包含外部依赖项的模型。在第一部分,我们定义了一个名为 custom_code 的外部模块,它存在于与模型定义不同的位置。
from typing import List
iris_types = ["setosa", "versicolor", "viginica"]
def map_iris_types(predictions: int) -> List[str]:
return [iris_types[pred] for pred in predictions]
定义了 custom_code.py 模块后,它就可以在 Python 模型中使用了
from typing import Any, Dict, List, Optional
from custom_code import map_iris_types # import the external reference
import mlflow
class FlowerMapping(mlflow.pyfunc.PythonModel):
"""Custom model with an external dependency"""
def predict(self, context, model_input, params: Optional[Dict[str, Any]] = None) -> List[str]:
predictions = [pred % 3 for pred in model_input]
# Call the external function
return map_iris_types(predictions)
with mlflow.start_run():
model_info = mlflow.pyfunc.log_model(
name="flowers",
python_model=FlowerMapping(),
infer_code_paths=True, # Enabling automatic code dependency inference
)
设置 infer_code_paths=True 后,map_iris_types 的依赖项将被分析,其源声明将被检测为源自 custom_code.py 模块,并且 custom_code.py 中的代码引用将与模型工件一起存储。注意,不需要使用 code_paths 参数(在下一节讨论)来定义外部代码依赖项。
只有当前工作目录内的模块才是可访问的。依赖项推断无法跨越模块边界工作,或者如果你的自定义代码定义在完全不同的库中,它也无法工作。如果你的代码库结构使得公共模块完全处于执行模型记录代码的路径之外,则需要使用原始的 code_paths 选项来记录这些依赖项,因为 infer_code_paths 依赖项推断不会捕获那些需求。
infer_code_paths 的限制
在使用 infer_code_paths 进行依赖项推断之前,请确保你的依赖代码模块中没有硬编码敏感数据(例如密码、访问令牌或机密信息)。代码推断不会混淆敏感信息,它将捕获并记录(保存)该模块,无论其中包含什么。
使用 infer_code_paths 时关于代码结构的一个重要注意事项是,避免在代码的主要入口点内定义依赖项。当 Python 代码文件作为 __main__ 模块加载时,它无法被推断为代码路径文件。这意味着如果你直接运行脚本(例如使用 python script.py),在该脚本中定义的函数和类将成为 __main__ 模块的一部分,而不易被其他模块访问。
如果你的模型依赖于这些类或函数,这可能会成为问题,因为它们不是标准模块命名空间的一部分,因此不容易序列化。要处理这种情况,你应该使用 cloudpickle 来序列化你的模型实例。cloudpickle 是 Python 的 pickle 模块的扩展版本,可以序列化更广泛的 Python 对象,包括在 __main__ 模块中定义的函数和类。
为什么这很重要
- 代码路径推断:MLflow 使用代码路径来理解和记录与你的模型关联的代码。当脚本作为
__main__执行时,代码路径无法被推断,这使得 MLflow 实验的跟踪和可重复性变得复杂。 - 序列化:标准的序列化方法(如
pickle)可能无法处理__main__模块对象,导致在尝试保存和加载模型时出现问题。cloudpickle通过支持这些对象的序列化提供了一种变通方案,确保你的模型能够被正确保存和恢复。
- 代码路径推断:MLflow 使用代码路径来理解和记录与你的模型关联的代码。当脚本作为
最佳实践
- 避免在
__main__模块中定义关键函数和类。相反,将它们放在可以根据需要导入的独立模块文件中。 - 如果你必须在
__main__模块中定义函数和类,请使用cloudpickle序列化你的模型,以确保所有依赖项都得到正确处理。
- 避免在
保存额外代码与 MLflow 模型 - 手动声明
MLflow 还支持将你的自定义 Python 代码保存为模型的依赖项。当你希望部署模型预测所需的自定义模块时,这特别有用。为此,请在记录模型时指定 code_paths。例如,如果你的项目中有以下文件结构
my_project/
├── utils.py
└── train.py
import mlflow
class MyModel(mlflow.pyfunc.PythonModel):
def predict(self, context, model_input):
from utils import my_func
x = my_func(model_input)
# .. your prediction logic
return prediction
# Log the model
with mlflow.start_run() as run:
mlflow.pyfunc.log_model(
python_model=MyModel(),
name="model",
input_example=input_data,
code_paths=["utils.py"],
)
那么 MLflow 将把 utils.py 保存在模型目录下的 code/ 目录中
model/
├── MLmodel
├── ...
└── code/
└── utils.py
当 MLflow 加载模型进行部署时,code 目录将被添加到系统路径中,以便你可以在模型代码中使用该模块,例如 from utils import my_func。你还可以将目录路径指定为 code_paths,以保存目录下的多个文件。
将 code_paths 选项用于自定义库
要在记录模型时包含 PyPI 上不可用的自定义库,可以使用 code_paths 参数。此选项允许你上传 .whl 文件或其他依赖项,确保所有必需的库在部署期间可用。
以下示例演示了用于开发目的包含自定义库的快捷方法。不建议将此方法用于生产环境。对于生产用途,请将库上传到自定义 PyPI 服务器或云存储,以确保可靠且安全的访问。
例如,假设你的项目具有以下文件结构
my_project/
|── train.py
└── custom_package.whl
那么以下代码可以使用自定义包来记录你的模型
import mlflow
from custom_package import my_func
class MyModel(mlflow.pyfunc.PythonModel):
def predict(self, context, model_input):
x = my_func(model_input)
# .. your prediction logic
return prediction
# Log the model
with mlflow.start_run() as run:
mlflow.pyfunc.log_model(
python_model=MyModel(),
name="model",
extra_pip_requirements=["code/custom_package.whl"],
input_example=input_data,
code_paths=["custom_package.whl"],
)
code_paths 选项的注意事项
当使用 code_paths 选项时,请注意指定的代码文件或目录必须与你的模型脚本在同一个目录中。如果指定的文件或目录位于父目录或子目录中(如 my_project/src/utils.py),模型部署将因 ModuleNotFoundError 而失败。例如,假设你的项目中有以下文件结构
my_project/
|── train.py
└── src/
└── utils.py
那么以下模型代码是无法工作的
class MyModel(mlflow.pyfunc.PythonModel):
def predict(self, context, model_input):
from src.utils import my_func
# .. your prediction logic
return prediction
with mlflow.start_run() as run:
mlflow.pyfunc.log_model(
python_model=MyModel(),
name="model",
input_example=input_data,
code_paths=[
"src/utils.py"
], # the file will be saved at code/utils.py not code/src/utils.py
)
# => Model serving will fail with ModuleNotFoundError: No module named 'src'
此限制是由于 MLflow 保存和加载指定文件和目录的方式所致。当它将指定的文件或目录复制到 code/ 目标时,它不会保留它们最初所在的相对路径。例如,在上面的示例中,MLflow 会将 utils.py 复制到 code/utils.py,而不是 code/src/utils.py。结果,它必须以 from utils import my_func 导入,而不是 from src.utils import my_func。但这可能不理想,因为导入路径与原始训练脚本不同。
为了解决这个问题,code_paths 应该指定父目录,在此示例中即 code_paths=["src"]。通过这种方式,MLflow 将把整个 src/ 目录复制到 code/ 下,你的模型代码将能够导入 src.utils。
class MyModel(mlflow.pyfunc.PythonModel):
def predict(self, context, model_input):
from src.utils import my_func
# .. your prediction logic
return prediction
with mlflow.start_run() as run:
mlflow.pyfunc.log_model(
python_model=model,
name="model",
input_example=input_data,
code_paths=["src"], # the whole /src directory will be saved at code/src
)
同样,code_paths 选项也不处理类似 code_paths=["../src"] 的相对导入。
在加载多个具有相同模块名但不同实现的模型时,code_paths 的限制
code_paths 选项的当前实现有一个限制:它不支持在同一 Python 进程中加载多个依赖于同名但实现不同的模块的模型,如下例所示
import importlib
import sys
import tempfile
from pathlib import Path
import mlflow
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir = Path(tmpdir)
my_model_path = tmpdir / "my_model.py"
code_template = """
import mlflow
class MyModel(mlflow.pyfunc.PythonModel):
def predict(self, context, model_input):
return [{n}] * len(model_input)
"""
my_model_path.write_text(code_template.format(n=1))
sys.path.insert(0, str(tmpdir))
import my_model
# model 1
model1 = my_model.MyModel()
assert model1.predict(context=None, model_input=[0]) == [1]
with mlflow.start_run():
info1 = mlflow.pyfunc.log_model(
name="model",
python_model=model1,
code_paths=[my_model_path],
)
# model 2
my_model_path.write_text(code_template.format(n=2))
importlib.reload(my_model)
model2 = my_model.MyModel()
assert model2.predict(context=None, model_input=[0]) == [2]
with mlflow.start_run():
info2 = mlflow.pyfunc.log_model(
name="model",
python_model=model2,
code_paths=[my_model_path],
)
# To simulate a fresh Python process, remove the `my_model` module from the cache
sys.modules.pop("my_model")
# Now we have two models that depend on modules with the same name but different implementations.
# Let's load them and check the prediction results.
pred = mlflow.pyfunc.load_model(info1.model_uri).predict([0])
assert pred == [1], pred # passes
# As the `my_model` module was loaded and cached in the previous `load_model` call,
# the next `load_model` call will reuse it and return the wrong prediction result.
assert "my_model" in sys.modules
pred = mlflow.pyfunc.load_model(info2.model_uri).predict([0])
assert pred == [2], pred # doesn't pass, `pred` is [1]
要解决此限制,你可以在加载模型之前从缓存中删除该模块。例如
model1 = mlflow.pyfunc.load_model(info1.model_uri)
sys.modules.pop("my_model")
model2 = mlflow.pyfunc.load_model(info2.model_uri)
另一个变通方法是为不同的实现使用不同的模块名称。例如
mlflow.pyfunc.log_model(
name="model1",
python_model=model1,
code_paths=["my_model1.py"],
)
mlflow.pyfunc.log_model(
name="model",
python_model=model2,
code_paths=["my_model2.py"],
)
推荐的项目结构
考虑到 code_paths 的这一限制,推荐的项目结构如下所示
my_project/
|-- model.py # Defines the custom pyfunc model
|── train.py # Trains and logs the model
|── core/ # Required modules for prediction
| |── preprocessing.py
| └── ...
└── helper/ # Other helper modules used for training, evaluation
|── evaluation.py
└── ...
这样,你可以通过 code_paths=["core"] 记录模型以包含预测所需的模块,同时排除仅用于开发的辅助模块。
验证预测环境
在部署之前验证你的模型是确保生产就绪的关键步骤。MLflow 提供了几种在本地测试模型的方法,无论是在虚拟环境还是 Docker 容器中。如果在验证期间发现任何依赖项问题,请遵循“如何修复服务模型时的依赖项错误?”中的指南。
在虚拟环境中测试离线预测
你可以通过 Python 或 CLI 使用 MLflow Models 的 predict API 对模型进行测试预测。这将从模型 URI 加载你的模型,创建一个包含模型依赖项(定义在 MLflow Model 中)的虚拟环境,并使用模型运行离线预测。请参考 mlflow.models.predict() 或 CLI 参考以获取更详细的 predict API 用法。
Python API 自 MLflow 2.10.0 起可用。如果你使用的是旧版本,请使用 CLI 选项。
- Python
- Bash
import mlflow
mlflow.models.predict(
model_uri="runs:/<run_id>/model",
input_data="<input_data>",
)
mlflow models predict -m runs:/<run_id>/model-i <input_path>
使用 mlflow.models.predict() API 可以方便地快速测试你的模型和推理环境。但是,它可能不是对部署环境的完美模拟,因为它不会启动在线推理服务器。尽管如此,它是测试预测输入格式是否正确的绝佳方法。
格式取决于你所记录模型的方法 predict() 所支持的类型。如果模型在记录时附带了签名(signature),则可以从 MLflow UI 或通过 mlflow.models.get_model_info() 查看输入数据,它具有 signature 字段。
更广泛地说,MLflow 能够支持各种特定于类型的输入,例如 tensorflow tensor。MLflow 还支持不特定于给定类型的类型,例如 pandas DataFrame、numpy ndarray、python Dict、python List、scipy.sparse 矩阵和 spark 数据帧。
在虚拟环境中测试在线推理端点
如果你想通过实际运行在线推理服务器来测试模型,可以使用 MLflow serve API。这将创建一个包含你的模型和依赖项的虚拟环境,类似于 predict API,但会启动推理服务器并公开 REST 端点。然后你可以发送测试请求并验证响应。请参考 CLI 参考以获取 serve API 的更详细用法。
mlflow models serve -m runs:/<run_id>/model -p <port>
# In another terminal
curl -X POST -H "Content-Type: application/json" \
--data '{"inputs": [[1, 2], [3, 4]]}' \
https://:<port>/invocations
虽然这是在部署前测试模型的可靠方法,但一个需要注意的地方是,虚拟环境无法吸收你的机器与生产环境之间的操作系统级差异。例如,如果你使用 MacOS 作为本地开发机器,但部署目标在 Linux 上运行,你可能会遇到在虚拟环境中无法复现的问题。
在这种情况下,你可以使用 Docker 容器来测试你的模型。虽然它不像虚拟机那样提供完全的操作系统级隔离(例如,我们不能在 Linux 机器上运行 Windows 容器),但 Docker 涵盖了一些常见的测试场景,例如运行不同版本的 Linux,或在 Mac 或 Windows 上模拟 Linux 环境。
在 Docker 容器中测试在线推理端点
用于 CLI 和 Python 的 MLflow build-docker API 能够构建一个基于 Ubuntu 的 Docker 镜像来部署你的模型。该镜像将包含你的模型和依赖项,并具有用于启动推理服务器的入口点。类似于 serve API,你可以发送测试请求并验证响应。请参考 CLI 参考以获取 build-docker API 的更详细用法。
mlflow models build-docker -m runs:/<run_id>/model -n <image_name>
docker run -p <port>:8080 <image_name>
# In another terminal
curl -X POST -H "Content-Type: application/json" \
--data '{"inputs": [[1, 2], [3, 4]]}' \
https://:<port>/invocations
故障排除
如何修复服务模型时的依赖项错误
模型部署中最常见的问题集中在依赖项上。在记录或保存模型时,MLflow 会尝试推断模型依赖项并将它们保存为 MLflow 模型元数据的一部分。然而,这并不总是完整的,有时会遗漏某些依赖项,例如某些库的 [extras] 依赖项。这可能导致部署模型时出现错误,例如“ModuleNotFoundError”或“ImportError”。以下是一些有助于诊断和修复缺失依赖项错误的步骤。
为了减少依赖项错误的发生,你可以在保存模型时添加 input_example。这使 MLflow 能够在保存模型之前执行一次模型预测,从而捕获预测期间使用的依赖项。关于此参数的更多详细用法,请参阅 模型输入示例。
1. 检查缺失的依赖项
缺失的依赖项会列在错误消息中。例如,如果你看到以下错误消息
ModuleNotFoundError: No module named 'cv2'
2. 尝试使用 predict API 添加依赖项
现在你知道了缺失的依赖项,你可以创建一个具有正确依赖项的新模型版本。然而,为尝试新依赖项而创建新模型可能有点繁琐,特别是当你可能需要多次迭代才能找到正确解决方案时。相反,你可以使用 mlflow.models.predict() API 在 troubleshooting(排查)安装错误时测试你的更改,而无需反复重新记录模型。
为此,请使用 pip-requirements-override 选项来指定 pip 依赖项,例如 opencv-python==4.8.0。
- Python
- Bash
import mlflow
mlflow.models.predict(
model_uri="runs:/<run_id>/<model_path>",
input_data="<input_data>",
pip_requirements_override=["opencv-python==4.8.0"],
)
mlflow models predict \
-m runs:/<run_id>/<model_path> \
-I <input_path> \
--pip-requirements-override opencv-python==4.8.0
指定的依赖项将被安装到虚拟环境中,除了(或替代)模型元数据中定义的依赖项。由于这不会改变模型本身,你可以快速且安全地迭代以找到正确的依赖项。
请注意,对于 Python 实现中的 input_data 参数,该函数接受你的模型 predict() 函数支持的 Python 对象。一些示例可能包括特定于类型的输入,如 tensorflow tensor,或更通用的类型,如 pandas DataFrame、numpy ndarray、python Dict 或 python List。在使用 CLI 时,我们无法传递 python 对象,而是传递包含输入负载的 CSV 或 JSON 文件路径。
pip-requirements-override 选项自 MLflow 2.10.0 起可用。
3. 更新模型元数据
一旦找到正确的依赖项,就可以使用 mlflow.models.update_model_requirements() API 更新已记录模型的依赖项。
import mlflow
mlflow.models.update_model_requirements(
model_uri="runs:/<run_id>/<model_path>",
operation="add",
requirement_list=["opencv-python==4.8.0"],
)
请注意,你也可以利用 CLI 来更新模型要求
mlflow models update-pip-requirements -m runs:/<run_id>/<model_path> add "opencv-python==4.8.0"
或者,你也可以通过在记录模型时指定 extra_pip_requirements 选项来记录一个带有更新依赖项的新模型。
import mlflow
mlflow.pyfunc.log_model(
name="model",
python_model=python_model,
extra_pip_requirements=["opencv-python==4.8.0"],
input_example=input_data,
)
使用 mlflow.models.predict 时如何启用预发布版本安装
如果你在部署前使用 mlflow.models.predict 验证模型并遇到如下错误
hint: `mlflow-skinny` was requested with a pre-release marker (e.g.,
mlflow-skinny==3.2.0rc0), but pre-releases weren't enabled (try:
`--prerelease=allow`)
你可以通过 extra_envs 字段设置环境变量 UV_PRERELEASE=allow 来解决它。这将使 uv 在环境设置期间能够安装预发布包。
mlflow.models.predict(
model_uri=model_info.model_uri,
input_data=["a", "b", "c"],
extra_envs={"UV_PRERELEASE": "allow"},
)
如何针对许可证变更迁移 Anaconda 依赖项
Anaconda Inc. 更新了其 anaconda.org 通道的服务条款。根据新条款,如果你依赖 Anaconda 的打包和分发,可能需要商业许可证。有关更多信息,请参阅 Anaconda 商业版 FAQ。你对任何 Anaconda 通道的使用均受其服务条款的约束。
在 v1.18 之前记录的 MLflow 模型默认将 conda defaults 通道(https://repo.anaconda.com/pkgs)记录为依赖项。由于此许可证变更,MLflow 已停止对使用 MLflow v1.18 及更高版本记录的模型使用 defaults 通道。现在记录的默认通道是 conda-forge,它指向社区管理的 https://forge.conda.org.cn。
如果你在 MLflow v1.18 之前记录了一个模型,且未从该模型的 conda 环境中排除 defaults 通道,那么该模型可能具有你不希望有的对 defaults 通道的依赖。要手动确认模型是否具有此依赖项,你可以检查与模型打包在一起的 conda.yaml 文件中的 channel 值。例如,带有 defaults 通道依赖的模型 conda.yaml 可能如下所示
name: mlflow-env
channels:
- defaults
dependencies:
- python=3.8.8
- pip
- pip:
- mlflow==2.3
- scikit-learn==0.23.2
- cloudpickle==1.6.0
如果你想更改模型环境使用的通道,可以使用新的 conda.yaml 将模型重新注册到模型注册中心。你可以通过在 log_model() 的 conda_env 参数中指定通道来执行此操作。
有关 log_model() API 的更多信息,请参阅你正在使用的模型类型的 MLflow 文档,例如 mlflow.sklearn.log_model()。