From a2b3659fe91d6759add8a7b956833ba9bf12bdd8 Mon Sep 17 00:00:00 2001 From: JieguangZhou Date: Wed, 21 Sep 2022 10:45:05 +0800 Subject: [PATCH] [improve] Optimize MLFlow task plugin for easy of use (#12071) optimize code (cherry picked from commit cf522e2f) --- docs/docs/en/guide/task/mlflow.md | 38 ++-- docs/docs/zh/guide/task/mlflow.md | 59 +++--- .../demo/mlflow-models-docker-compose.png | Bin 24671 -> 0 bytes .../src/main/resources/common.properties | 7 +- .../examples/yaml_define/mlflow.yaml | 19 +- .../examples/task_mlflow_example.py | 23 +-- .../src/pydolphinscheduler/tasks/mlflow.py | 11 +- .../tests/tasks/test_mlflow.py | 10 +- .../plugin/task/mlflow/MlflowConstants.java | 31 +-- .../plugin/task/mlflow/MlflowParameters.java | 194 +++--------------- .../plugin/task/mlflow/MlflowTask.java | 132 +++++++----- .../src/main/resources/docker-compose.yml | 39 ---- .../plugin/task/mlflow/MlflowTaskTest.java | 120 ++++++----- .../src/locales/en_US/project.ts | 2 +- .../src/locales/zh_CN/project.ts | 2 +- .../node/fields/use-mlflow-models.ts | 26 --- .../node/fields/use-mlflow-projects.ts | 16 +- .../task/components/node/fields/use-mlflow.ts | 5 +- .../task/components/node/format-data.ts | 2 - .../task/components/node/tasks/use-mlflow.ts | 3 - 20 files changed, 278 insertions(+), 461 deletions(-) delete mode 100644 docs/img/tasks/demo/mlflow-models-docker-compose.png delete mode 100644 dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/main/resources/docker-compose.yml diff --git a/docs/docs/en/guide/task/mlflow.md b/docs/docs/en/guide/task/mlflow.md index 75911c03ae..0eec455238 100644 --- a/docs/docs/en/guide/task/mlflow.md +++ b/docs/docs/en/guide/task/mlflow.md @@ -20,7 +20,6 @@ The MLflow plugin currently supports and will support the following: - MLflow Models - MLFLOW: Use `MLflow models serve` to deploy a model service - Docker: Run the container after packaging the docker image - - Docker Compose: Use docker compose to run the container, it will replace the docker run above ## Create Task @@ -98,22 +97,26 @@ You can now use this feature to run all MLFlow projects on Github (For example [ ![mlflow-models-docker](../../../../img/tasks/demo/mlflow-models-docker.png) -#### DOCKER COMPOSE +## Environment to Prepare -![mlflow-models-docker-compose](../../../../img/tasks/demo/mlflow-models-docker-compose.png) +### Conda Environment +Please install [anaconda](https://docs.continuum.io/anaconda/install/) or [miniconda](https://docs.conda.io/en/latest/miniconda.html#installing) in advance. -| **Parameter** | **Description** | -|------------------|----------------------------------------------------------| -| Max Cpu Limit | For example, `1.0` or `0.5`, the same as docker compose. | -| Max Memory Limit | For example `1G` or `500M`, the same as docker compose. | +**Method A:** -## Environment to Prepare +Config anaconda environment in `/dolphinscheduler/conf/env/dolphinscheduler_env.sh`. -### Conda Environment +Add the following content to the file: + +```bash +# config anaconda environment +export PATH=/opt/anaconda3/bin:$PATH +``` -You need to enter the admin account to configure a conda environment variable(Please -install [anaconda](https://docs.continuum.io/anaconda/install/) -or [miniconda](https://docs.conda.io/en/latest/miniconda.html#installing) in advance). + +**Method B:** + +You need to enter the admin account to configure a conda environment variable. ![mlflow-conda-env](../../../../img/tasks/demo/mlflow-conda-env.png) @@ -139,3 +142,14 @@ After running, an MLflow service is started. After this, you can visit the MLflow service (`http://localhost:5000`) page to view the experiments and models. ![mlflow-server](../../../../img/tasks/demo/mlflow-server.png) + +### Preset Algorithm Repository Configuration + +If you can't access github, you can modify the following fields in the `commom.properties` configuration file to replace the github address with an accessible address. + +```yaml +# mlflow task plugin preset repository +ml.mlflow.preset_repository=https://github.com/apache/dolphinscheduler-mlflow +# mlflow task plugin preset repository version +ml.mlflow.preset_repository_version="main" +``` diff --git a/docs/docs/zh/guide/task/mlflow.md b/docs/docs/zh/guide/task/mlflow.md index 97754ebd55..3446e02e77 100644 --- a/docs/docs/zh/guide/task/mlflow.md +++ b/docs/docs/zh/guide/task/mlflow.md @@ -4,7 +4,7 @@ [MLflow](https://mlflow.org) 是一个MLops领域一个优秀的开源项目, 用于管理机器学习的生命周期,包括实验、可再现性、部署和中心模型注册。 -MLflow 组件用于执行 MLflow 任务,目前包含Mlflow Projects, 和MLflow Models。(Model Registry将在不就的将来支持)。 +MLflow 组件用于执行 MLflow 任务,目前包含Mlflow Projects,和MLflow Models。(Model Registry将在不就的将来支持)。 - MLflow Projects: 将代码打包,并可以运行到任务的平台上。 - MLflow Models: 在不同的服务环境中部署机器学习模型。 @@ -12,19 +12,13 @@ MLflow 组件用于执行 MLflow 任务,目前包含Mlflow Projects, 和MLflow 目前 Mlflow 组件支持的和即将支持的内容如下中: -- [x] MLflow Projects - - [x] BasicAlgorithm: 基础算法,包含LogisticRegression, svm, lightgbm, xgboost - - [x] AutoML: AutoML工具,包含autosklean, flaml - - [x] Custom projects: 支持运行自己的MLflow Projects项目 -- [ ] MLflow Models - - [x] MLFLOW: 直接使用 `mlflow models serve` 部署模型。 - - [x] Docker: 打包 DOCKER 镜像后部署模型。 - - [x] Docker Compose: 使用Docker Compose 部署模型,将会取代上面的Docker部署。 - - [ ] Seldon core: 构建完镜像后,使用Seldon Core 部署到k8s集群上, 可以使用Seldon Core的生成模型管理能力。 - - [ ] k8s: 构建完镜像后, 部署到k8s集群上。 - - [ ] MLflow deployments: 内置的允许MLflow 部署模块, 如内置的部署到Sagemaker等。 -- [ ] Model Registry - - [ ] Register Model: 注册相关工件(模型以及相关的参数,指标)到模型中心 +- MLflow Projects + - BasicAlgorithm: 基础算法,包含LogisticRegression, svm, lightgbm, xgboost + - AutoML: AutoML工具,包含autosklean, flaml + - Custom projects: 支持运行自己的MLflow Projects项目 +- MLflow Models + - MLFLOW: 直接使用 `mlflow models serve` 部署模型。 + - Docker: 打包 DOCKER 镜像后部署模型。 ## 创建任务 @@ -95,21 +89,25 @@ MLflow 组件用于执行 MLflow 任务,目前包含Mlflow Projects, 和MLflow ![mlflow-models-docker](../../../../img/tasks/demo/mlflow-models-docker.png) -#### DOCKER COMPOSE +## 环境准备 -![mlflow-models-docker-compose](../../../../img/tasks/demo/mlflow-models-docker-compose.png) +### conda 环境配置 -| **任务参数** | **描述** | -|----------|--------------------------------------| -| 最大CPU限制 | 如 `1.0` 或者 `0.5`,与 docker compose 一致 | -| 最大内存限制 | 如 `1G` 或者 `500M`,与 docker compose 一致 | +请提前[安装anaconda](https://docs.continuum.io/anaconda/install/) 或者[安装miniconda](https://docs.conda.io/en/latest/miniconda.html#installing) -## 环境准备 +**方法A:** -### conda 环境配置 +配置文件:/dolphinscheduler/conf/env/dolphinscheduler_env.sh。 + +在文件最后添加内容 +``` +# 配置你的conda环境路径 +export PATH=/opt/anaconda3/bin:$PATH +``` -你需要进入admin账户配置一个conda环境变量(请提前[安装anaconda](https://docs.continuum.io/anaconda/install/) -或者[安装miniconda](https://docs.conda.io/en/latest/miniconda.html#installing) )。 +**方法B:** + +你需要进入admin账户配置一个conda环境变量。 ![mlflow-conda-env](../../../../img/tasks/demo/mlflow-conda-env.png) @@ -117,6 +115,7 @@ MLflow 组件用于执行 MLflow 任务,目前包含Mlflow Projects, 和MLflow ![mlflow-set-conda-env](../../../../img/tasks/demo/mlflow-set-conda-env.png) + ### MLflow service 启动 确保你已经安装MLflow,可以使用`pip install mlflow`进行安装。 @@ -135,3 +134,15 @@ mlflow server -h 0.0.0.0 -p 5000 --serve-artifacts --backend-store-uri sqlite:// ![mlflow-server](../../../../img/tasks/demo/mlflow-server.png) + +### 内置算法仓库配置 + +如果遇到github无法访问的情况,可以修改`commom.properties`配置文件的以下字段,将github地址替换能访问的地址。 + +```yaml +# mlflow task plugin preset repository +ml.mlflow.preset_repository=https://github.com/apache/dolphinscheduler-mlflow +# mlflow task plugin preset repository version +ml.mlflow.preset_repository_version="main" +``` + diff --git a/docs/img/tasks/demo/mlflow-models-docker-compose.png b/docs/img/tasks/demo/mlflow-models-docker-compose.png deleted file mode 100644 index 7aad9641a2dc429d7a1190bad81e21b0c6404769..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24671 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 deploy_mlflow - # run automl to train model train_automl = MLFlowProjectsAutoML( name="train_automl", @@ -88,16 +77,16 @@ with ProcessDefinition( search_params="max_depth=[5, 10];n_estimators=[100, 200]", ) - # Using DOCKER COMPOSE to deploy model from train_basic_algorithm - deploy_docker_compose = MLflowModels( - name="deploy_docker_compose", + # Using MLFLOW to deploy model from training lightgbm project + deploy_mlflow = MLflowModels( + name="deploy_mlflow", model_uri="models:/iris_B/Production", mlflow_tracking_uri=mlflow_tracking_uri, - deploy_mode=MLflowDeployType.DOCKER_COMPOSE, - port=7003, + deploy_mode=MLflowDeployType.MLFLOW, + port=7001, ) - train_basic_algorithm >> deploy_docker_compose + train_basic_algorithm >> deploy_mlflow pd.submit() diff --git a/dolphinscheduler-python/pydolphinscheduler/src/pydolphinscheduler/tasks/mlflow.py b/dolphinscheduler-python/pydolphinscheduler/src/pydolphinscheduler/tasks/mlflow.py index 44e6634822..e86797aadf 100644 --- a/dolphinscheduler-python/pydolphinscheduler/src/pydolphinscheduler/tasks/mlflow.py +++ b/dolphinscheduler-python/pydolphinscheduler/src/pydolphinscheduler/tasks/mlflow.py @@ -43,7 +43,6 @@ class MLflowDeployType(str): MLFLOW = "MLFLOW" DOCKER = "DOCKER" - DOCKER_COMPOSE = "DOCKER COMPOSE" DEFAULT_MLFLOW_TRACKING_URI = "http://127.0.0.1:5000" @@ -83,10 +82,8 @@ class MLflowModels(BaseMLflow): :param model_uri: Model-URI of MLflow , support models://suffix format and runs:/ format. See https://mlflow.org/docs/latest/tracking.html#artifact-stores :param mlflow_tracking_uri: MLflow tracking server uri, default is http://127.0.0.1:5000 - :param deploy_mode: MLflow deploy mode, support MLFLOW, DOCKER, DOCKER COMPOSE, default is DOCKER + :param deploy_mode: MLflow deploy mode, support MLFLOW, DOCKER, default is DOCKER :param port: deploy port, default is 7000 - :param cpu_limit: cpu limit, default is 1.0 - :param memory_limit: memory limit, default is 500M """ mlflow_task_type = MLflowTaskType.MLFLOW_MODELS @@ -95,8 +92,6 @@ class MLflowModels(BaseMLflow): "deploy_type", "deploy_model_key", "deploy_port", - "cpu_limit", - "memory_limit", } def __init__( @@ -106,8 +101,6 @@ class MLflowModels(BaseMLflow): mlflow_tracking_uri: Optional[str] = DEFAULT_MLFLOW_TRACKING_URI, deploy_mode: Optional[str] = MLflowDeployType.DOCKER, port: Optional[int] = 7000, - cpu_limit: Optional[float] = 1.0, - memory_limit: Optional[str] = "500M", *args, **kwargs ): @@ -116,8 +109,6 @@ class MLflowModels(BaseMLflow): self.deploy_type = deploy_mode.upper() self.deploy_model_key = model_uri self.deploy_port = port - self.cpu_limit = cpu_limit - self.memory_limit = memory_limit class MLFlowProjectsCustom(BaseMLflow): diff --git a/dolphinscheduler-python/pydolphinscheduler/tests/tasks/test_mlflow.py b/dolphinscheduler-python/pydolphinscheduler/tests/tasks/test_mlflow.py index 2159b6c77e..af0a324b53 100644 --- a/dolphinscheduler-python/pydolphinscheduler/tests/tasks/test_mlflow.py +++ b/dolphinscheduler-python/pydolphinscheduler/tests/tasks/test_mlflow.py @@ -63,19 +63,15 @@ def test_mlflow_models_get_define(): name = "mlflow_models" model_uri = "models:/xgboost_native/Production" port = 7001 - cpu_limit = 2.0 - memory_limit = "600M" expect = deepcopy(EXPECT) expect["name"] = name task_params = expect["taskParams"] task_params["mlflowTrackingUri"] = MLFLOW_TRACKING_URI task_params["mlflowTaskType"] = MLflowTaskType.MLFLOW_MODELS - task_params["deployType"] = MLflowDeployType.DOCKER_COMPOSE + task_params["deployType"] = MLflowDeployType.DOCKER task_params["deployModelKey"] = model_uri task_params["deployPort"] = port - task_params["cpuLimit"] = cpu_limit - task_params["memoryLimit"] = memory_limit with patch( "pydolphinscheduler.core.task.Task.gen_code_and_version", @@ -85,10 +81,8 @@ def test_mlflow_models_get_define(): name=name, model_uri=model_uri, mlflow_tracking_uri=MLFLOW_TRACKING_URI, - deploy_mode=MLflowDeployType.DOCKER_COMPOSE, + deploy_mode=MLflowDeployType.DOCKER, port=port, - cpu_limit=cpu_limit, - memory_limit=memory_limit, ) assert task.get_define() == expect diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/main/java/org/apache/dolphinscheduler/plugin/task/mlflow/MlflowConstants.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/main/java/org/apache/dolphinscheduler/plugin/task/mlflow/MlflowConstants.java index c2701fb543..a13741230f 100644 --- a/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/main/java/org/apache/dolphinscheduler/plugin/task/mlflow/MlflowConstants.java +++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/main/java/org/apache/dolphinscheduler/plugin/task/mlflow/MlflowConstants.java @@ -28,15 +28,17 @@ public class MlflowConstants { public static final String JOB_TYPE_CUSTOM_PROJECT = "CustomProject"; - public static final String PRESET_REPOSITORY = "https://github.com/apache/dolphinscheduler-mlflow"; + public static final String PRESET_REPOSITORY_KEY = "ml.mlflow.preset_repository"; + + public static final String PRESET_REPOSITORY_VERSION_KEY = "ml.mlflow.preset_repository_version"; - public static final String PRESET_PATH = "dolphinscheduler-mlflow"; + public static final String PRESET_REPOSITORY = "https://github.com/apache/dolphinscheduler-mlflow"; public static final String PRESET_REPOSITORY_VERSION = "main"; - public static final String PRESET_AUTOML_PROJECT = PRESET_PATH + "#Project-AutoML"; + public static final String PRESET_AUTOML_PROJECT = "#Project-AutoML"; - public static final String PRESET_BASIC_ALGORITHM_PROJECT = PRESET_PATH + "#Project-BasicAlgorithm"; + public static final String PRESET_BASIC_ALGORITHM_PROJECT = "#Project-BasicAlgorithm"; public static final String MLFLOW_TASK_TYPE_PROJECTS = "MLflow Projects"; @@ -46,14 +48,6 @@ public class MlflowConstants { public static final String MLFLOW_MODELS_DEPLOY_TYPE_DOCKER = "DOCKER"; - public static final String MLFLOW_MODELS_DEPLOY_TYPE_DOCKER_COMPOSE = "DOCKER COMPOSE"; - - /** - * template file - */ - public static final String TEMPLATE_DOCKER_COMPOSE = "docker-compose.yml"; - - /** * mlflow command */ @@ -81,8 +75,7 @@ public class MlflowConstants { public static final String MLFLOW_RUN_CUSTOM_PROJECT = "mlflow run $repo " + "%s " - + "--experiment-name=\"%s\" " - + "--version=\"%s\" "; + + "--experiment-name=\"%s\""; public static final String MLFLOW_MODELS_SERVE = "mlflow models serve -m %s --port %s -h 0.0.0.0"; @@ -94,20 +87,10 @@ public class MlflowConstants { + "--health-cmd \"curl --fail http://127.0.0.1:8080/ping || exit 1\" --health-interval 5s --health-retries 20" + " %s"; - public static final String DOCKER_COMPOSE_RUN = "docker-compose up -d"; - - public static final String SET_DOCKER_COMPOSE_ENV = "export DS_TASK_MLFLOW_IMAGE_NAME=%s\n" - + "export DS_TASK_MLFLOW_CONTAINER_NAME=%s\n" - + "export DS_TASK_MLFLOW_DEPLOY_PORT=%s\n" - + "export DS_TASK_MLFLOW_CPU_LIMIT=%s\n" - + "export DS_TASK_MLFLOW_MEMORY_LIMIT=%s"; - - public static final String DOCKER_HEALTH_CHECK = "docker inspect --format \"{{json .State.Health.Status }}\" %s"; public static final int DOCKER_HEALTH_CHECK_TIMEOUT = 20; public static final int DOCKER_HEALTH_CHECK_INTERVAL = 5000; - public static final String GIT_CLONE_REPO = "git clone %s %s"; } diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/main/java/org/apache/dolphinscheduler/plugin/task/mlflow/MlflowParameters.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/main/java/org/apache/dolphinscheduler/plugin/task/mlflow/MlflowParameters.java index 4e47c8ae64..819cb27bd8 100644 --- a/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/main/java/org/apache/dolphinscheduler/plugin/task/mlflow/MlflowParameters.java +++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/main/java/org/apache/dolphinscheduler/plugin/task/mlflow/MlflowParameters.java @@ -17,10 +17,14 @@ package org.apache.dolphinscheduler.plugin.task.mlflow; +import lombok.Data; + import org.apache.dolphinscheduler.plugin.task.api.parameters.AbstractParameters; +import org.apache.dolphinscheduler.spi.utils.StringUtils; import java.util.HashMap; +@Data public class MlflowParameters extends AbstractParameters { /** @@ -36,7 +40,7 @@ public class MlflowParameters extends AbstractParameters { */ private String mlflowProjectRepository; - private String mlflowProjectVersion = "master"; + private String mlflowProjectVersion = ""; /** * AutoML parameters @@ -76,160 +80,9 @@ public class MlflowParameters extends AbstractParameters { private String deployPort; - private String cpuLimit; - - private String memoryLimit; - - public void setAlgorithm(String algorithm) { - this.algorithm = algorithm; - } - - public String getAlgorithm() { - return algorithm; - } - - public void setParams(String params) { - this.params = params; - } - - public String getParams() { - return params; - } - - public void setSearchParams(String searchParams) { - this.searchParams = searchParams; - } - - public String getSearchParams() { - return searchParams; - } - - public void setDataPaths(String dataPath) { - this.dataPath = dataPath; - } - - public String getDataPath() { - return dataPath; - } - - public void setMlflowTaskType(String mlflowTaskType) { - this.mlflowTaskType = mlflowTaskType; - } - - public String getMlflowTaskType() { - return mlflowTaskType; - } - - public void setExperimentNames(String experimentName) { - this.experimentName = experimentName; - } - - public String getExperimentName() { - return experimentName; - } - - public void setModelNames(String modelName) { - this.modelName = modelName; - } - - public String getModelName() { - return modelName; - } - - public void setMlflowTrackingUris(String mlflowTrackingUri) { - this.mlflowTrackingUri = mlflowTrackingUri; - } - - public String getMlflowTrackingUri() { - return mlflowTrackingUri; - } - - public void setMlflowJobType(String mlflowJobType) { - this.mlflowJobType = mlflowJobType; - } - - public String getMlflowJobType() { - return mlflowJobType; - } - - public void setAutomlTool(String automlTool) { - this.automlTool = automlTool; - } - - public String getMlflowProjectRepository() { - return mlflowProjectRepository; - } - - public void setMlflowProjectRepository(String mlflowProjectRepository) { - this.mlflowProjectRepository = mlflowProjectRepository; - } - - public String getMlflowProjectVersion() { - return mlflowProjectVersion; - } - - public void setMlflowProjectVersion(String mlflowProjectVersion) { - this.mlflowProjectVersion = mlflowProjectVersion; - } - - public String getAutomlTool() { - return automlTool; - } - - public void setDeployType(String deployType) { - this.deployType = deployType; - } - - public String getDeployType() { - return deployType; - } - - public void setDeployModelKey(String deployModelKey) { - this.deployModelKey = deployModelKey; - } - - public String getDeployModelKey() { - return deployModelKey; - } - - public void setDeployPort(String deployPort) { - this.deployPort = deployPort; - } - - public String getDeployPort() { - return deployPort; - } - - public void setCpuLimit(String cpuLimit) { - this.cpuLimit = cpuLimit; - } - - public String getCpuLimit() { - return cpuLimit; - } - - public void setMemoryLimit(String memoryLimit) { - this.memoryLimit = memoryLimit; - } - - public String getMemoryLimit() { - return memoryLimit; - } - @Override public boolean checkParameters() { - Boolean checkResult = true; -// Boolean checkResult = mlflowTrackingUri != null; -// if (mlflowJobType.equals(MlflowConstants.JOB_TYPE_BASIC_ALGORITHM)) { -// checkResult &= dataPath != null; -// checkResult &= experimentName != null; -// } else if (mlflowJobType.equals(MlflowConstants.JOB_TYPE_AUTOML)) { -// checkResult &= dataPath != null; -// checkResult &= automlTool != null; -// checkResult &= experimentName != null; -// } else { -// } - return checkResult; + return StringUtils.isNotEmpty(mlflowTrackingUri); } public HashMap getParamsMap() { @@ -240,11 +93,13 @@ public class MlflowParameters extends AbstractParameters { paramsMap.put("experiment_name", experimentName); paramsMap.put("model_name", modelName); paramsMap.put("MLFLOW_TRACKING_URI", mlflowTrackingUri); - if (mlflowJobType.equals(MlflowConstants.JOB_TYPE_BASIC_ALGORITHM)) { - addParamsMapForBasicAlgorithm(paramsMap); - } else if (mlflowJobType.equals(MlflowConstants.JOB_TYPE_AUTOML)) { - getParamsMapForAutoML(paramsMap); - } else { + switch (mlflowJobType){ + case MlflowConstants.JOB_TYPE_BASIC_ALGORITHM: + addParamsMapForBasicAlgorithm(paramsMap); + break; + case MlflowConstants.JOB_TYPE_AUTOML: + getParamsMapForAutoML(paramsMap); + break; } return paramsMap; } @@ -262,6 +117,10 @@ public class MlflowParameters extends AbstractParameters { paramsMap.put("repo_version", MlflowConstants.PRESET_REPOSITORY_VERSION); } + public Boolean isCustomProject() { + return mlflowJobType.equals(MlflowConstants.JOB_TYPE_CUSTOM_PROJECT); + } + public String getModelKeyName(String tag) throws IllegalArgumentException { String imageName; if (deployModelKey.startsWith("runs:")) { @@ -271,23 +130,18 @@ public class MlflowParameters extends AbstractParameters { } else { throw new IllegalArgumentException("model key must start with runs:/ or models:/ "); } - imageName = imageName.replace("/", tag); + imageName = imageName.replace("/", tag).toLowerCase(); return imageName; } - public String getDockerComposeEnvCommand() { - String imageName = "mlflow/" + getModelKeyName(":"); - String env = String.format(MlflowConstants.SET_DOCKER_COMPOSE_ENV, imageName, getContainerName(), deployPort, cpuLimit, memoryLimit); - return env; - } - public String getContainerName(){ - String containerName = "ds-mlflow-" + getModelKeyName("-"); - return containerName; + return "ds-mlflow-" + getModelKeyName("-"); } public boolean getIsDeployDocker(){ - return deployType.equals(MlflowConstants.MLFLOW_MODELS_DEPLOY_TYPE_DOCKER) || deployType.equals(MlflowConstants.MLFLOW_MODELS_DEPLOY_TYPE_DOCKER_COMPOSE); + if (StringUtils.isEmpty(deployType)) { + return false; + } + return deployType.equals(MlflowConstants.MLFLOW_MODELS_DEPLOY_TYPE_DOCKER); } - -}; +} diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/main/java/org/apache/dolphinscheduler/plugin/task/mlflow/MlflowTask.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/main/java/org/apache/dolphinscheduler/plugin/task/mlflow/MlflowTask.java index 0c42a5c09b..26e7e260a4 100644 --- a/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/main/java/org/apache/dolphinscheduler/plugin/task/mlflow/MlflowTask.java +++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/main/java/org/apache/dolphinscheduler/plugin/task/mlflow/MlflowTask.java @@ -17,6 +17,8 @@ package org.apache.dolphinscheduler.plugin.task.mlflow; +import static org.apache.dolphinscheduler.plugin.task.api.TaskConstants.EXIT_CODE_FAILURE; + import org.apache.dolphinscheduler.common.thread.ThreadUtils; import org.apache.dolphinscheduler.plugin.task.api.AbstractTaskExecutor; import org.apache.dolphinscheduler.plugin.task.api.ShellCommandExecutor; @@ -25,28 +27,24 @@ import org.apache.dolphinscheduler.plugin.task.api.TaskException; import org.apache.dolphinscheduler.plugin.task.api.TaskExecutionContext; import org.apache.dolphinscheduler.plugin.task.api.model.Property; import org.apache.dolphinscheduler.plugin.task.api.model.TaskResponse; -import org.apache.dolphinscheduler.plugin.task.api.parameters.AbstractParameters; import org.apache.dolphinscheduler.plugin.task.api.parser.ParamUtils; import org.apache.dolphinscheduler.plugin.task.api.parser.ParameterUtils; import org.apache.dolphinscheduler.plugin.task.api.utils.OSUtils; import org.apache.dolphinscheduler.spi.utils.JSONUtils; +import org.apache.dolphinscheduler.spi.utils.PropertyUtils; +import org.apache.dolphinscheduler.spi.utils.StringUtils; import java.util.ArrayList; import java.util.List; import java.util.Map; - -import static org.apache.dolphinscheduler.plugin.task.api.TaskConstants.EXIT_CODE_FAILURE; +import java.util.regex.Pattern; /** * shell task */ public class MlflowTask extends AbstractTaskExecutor { - /** - * shell parameters - */ - private MlflowParameters mlflowParameters; - + private static final Pattern GIT_CHECK_PATTERN = Pattern.compile("^(git@|https?://)"); /** * shell command executor */ @@ -56,6 +54,10 @@ public class MlflowTask extends AbstractTaskExecutor { * taskExecutionContext */ private final TaskExecutionContext taskExecutionContext; + /** + * shell parameters + */ + private MlflowParameters mlflowParameters; /** * constructor @@ -69,6 +71,34 @@ public class MlflowTask extends AbstractTaskExecutor { this.shellCommandExecutor = new ShellCommandExecutor(this::logHandle, taskExecutionContext, logger); } + static public String getPresetRepository() { + String presetRepository = PropertyUtils.getString(MlflowConstants.PRESET_REPOSITORY_KEY); + if (StringUtils.isEmpty(presetRepository)) { + presetRepository = MlflowConstants.PRESET_REPOSITORY; + } + return presetRepository; + } + + static public String getPresetRepositoryVersion() { + String version = PropertyUtils.getString(MlflowConstants.PRESET_REPOSITORY_VERSION_KEY); + if (StringUtils.isEmpty(version)) { + version = MlflowConstants.PRESET_REPOSITORY_VERSION; + } + return version; + } + + static public String getVersionString(String version, String repository) { + String versionString; + if (StringUtils.isEmpty(version)) { + versionString = ""; + } else if (GIT_CHECK_PATTERN.matcher(repository).find()) { + versionString = String.format("--version=%s", version); + } else { + versionString = ""; + } + return versionString; + } + @Override public void init() { logger.info("shell task params {}", taskExecutionContext.getTaskParams()); @@ -137,43 +167,59 @@ public class MlflowTask extends AbstractTaskExecutor { args.add(String.format(MlflowConstants.EXPORT_MLFLOW_TRACKING_URI_ENV, mlflowParameters.getMlflowTrackingUri())); String runCommand; + String versionString; - if (mlflowParameters.getMlflowJobType().equals(MlflowConstants.JOB_TYPE_BASIC_ALGORITHM)) { - args.add(String.format(MlflowConstants.SET_DATA_PATH, mlflowParameters.getDataPath())); - args.add(String.format(MlflowConstants.SET_REPOSITORY, MlflowConstants.PRESET_BASIC_ALGORITHM_PROJECT)); - args.add(String.format(MlflowConstants.GIT_CLONE_REPO, MlflowConstants.PRESET_REPOSITORY, MlflowConstants.PRESET_PATH)); + if (mlflowParameters.isCustomProject()) { + versionString = getVersionString(mlflowParameters.getMlflowProjectVersion(), mlflowParameters.getMlflowProjectRepository()); + } else { + versionString = getVersionString(getPresetRepositoryVersion(), getPresetRepository()); + } - runCommand = MlflowConstants.MLFLOW_RUN_BASIC_ALGORITHM; - runCommand = String.format(runCommand, mlflowParameters.getAlgorithm(), mlflowParameters.getParams(), mlflowParameters.getSearchParams(), mlflowParameters.getModelName(), - mlflowParameters.getExperimentName()); - } else if (mlflowParameters.getMlflowJobType().equals(MlflowConstants.JOB_TYPE_AUTOML)) { - args.add(String.format(MlflowConstants.SET_DATA_PATH, mlflowParameters.getDataPath())); - args.add(String.format(MlflowConstants.SET_REPOSITORY, MlflowConstants.PRESET_AUTOML_PROJECT)); - args.add(String.format(MlflowConstants.GIT_CLONE_REPO, MlflowConstants.PRESET_REPOSITORY, MlflowConstants.PRESET_PATH)); + switch (mlflowParameters.getMlflowJobType()) { + case MlflowConstants.JOB_TYPE_BASIC_ALGORITHM: + args.add(String.format(MlflowConstants.SET_DATA_PATH, mlflowParameters.getDataPath())); - runCommand = MlflowConstants.MLFLOW_RUN_AUTOML_PROJECT; - runCommand = String.format(runCommand, mlflowParameters.getAutomlTool(), mlflowParameters.getParams(), mlflowParameters.getModelName(), mlflowParameters.getExperimentName()); + String repoBasicAlgorithm = getPresetRepository() + MlflowConstants.PRESET_BASIC_ALGORITHM_PROJECT; + args.add(String.format(MlflowConstants.SET_REPOSITORY, repoBasicAlgorithm)); - } else if (mlflowParameters.getMlflowJobType().equals(MlflowConstants.JOB_TYPE_CUSTOM_PROJECT)) { - args.add(String.format(MlflowConstants.SET_REPOSITORY, mlflowParameters.getMlflowProjectRepository())); + runCommand = MlflowConstants.MLFLOW_RUN_BASIC_ALGORITHM; + runCommand = String.format(runCommand, mlflowParameters.getAlgorithm(), mlflowParameters.getParams(), mlflowParameters.getSearchParams(), mlflowParameters.getModelName(), + mlflowParameters.getExperimentName()); + break; - runCommand = MlflowConstants.MLFLOW_RUN_CUSTOM_PROJECT; - runCommand = String.format(runCommand, mlflowParameters.getParams(), mlflowParameters.getExperimentName(), mlflowParameters.getMlflowProjectVersion()); - } else { - runCommand = String.format("Cant not Support %s", mlflowParameters.getMlflowJobType()); + case MlflowConstants.JOB_TYPE_AUTOML: + args.add(String.format(MlflowConstants.SET_DATA_PATH, mlflowParameters.getDataPath())); + String repoAutoML = getPresetRepository() + MlflowConstants.PRESET_AUTOML_PROJECT; + args.add(String.format(MlflowConstants.SET_REPOSITORY, repoAutoML)); + runCommand = MlflowConstants.MLFLOW_RUN_AUTOML_PROJECT; + runCommand = String.format(runCommand, mlflowParameters.getAutomlTool(), mlflowParameters.getParams(), mlflowParameters.getModelName(), mlflowParameters.getExperimentName()); + break; + + case MlflowConstants.JOB_TYPE_CUSTOM_PROJECT: + args.add(String.format(MlflowConstants.SET_REPOSITORY, mlflowParameters.getMlflowProjectRepository())); + runCommand = MlflowConstants.MLFLOW_RUN_CUSTOM_PROJECT; + runCommand = String.format(runCommand, mlflowParameters.getParams(), mlflowParameters.getExperimentName()); + break; + + default: + throw new TaskException("Unsupported mlflow job type: " + mlflowParameters.getMlflowJobType()); + } + + // add version string to command if repository is local path + if (StringUtils.isNotEmpty(versionString)) { + runCommand = runCommand + " " + versionString; } args.add(runCommand); - String command = ParameterUtils.convertParameterPlaceholders(String.join("\n", args), ParamUtils.convert(paramsMap)); - return command; + return ParameterUtils.convertParameterPlaceholders(String.join("\n", args), ParamUtils.convert(paramsMap)); } + /** + * build mlflow models command + */ protected String buildCommandForMlflowModels() { - /** - * build mlflow models command - */ Map paramsMap = getParamsMap(); List args = new ArrayList<>(); @@ -190,20 +236,9 @@ public class MlflowTask extends AbstractTaskExecutor { args.add(String.format(MlflowConstants.MLFLOW_BUILD_DOCKER, deployModelKey, imageName)); args.add(String.format(MlflowConstants.DOCKER_RREMOVE_CONTAINER, containerName)); args.add(String.format(MlflowConstants.DOCKER_RUN, containerName, mlflowParameters.getDeployPort(), imageName)); - } else if (mlflowParameters.getDeployType().equals(MlflowConstants.MLFLOW_MODELS_DEPLOY_TYPE_DOCKER_COMPOSE)) { - String templatePath = getTemplatePath(MlflowConstants.TEMPLATE_DOCKER_COMPOSE); - args.add(String.format("cp %s %s", templatePath, taskExecutionContext.getExecutePath())); - String imageName = "mlflow/" + mlflowParameters.getModelKeyName(":"); - String containerName = mlflowParameters.getContainerName(); - - args.add(String.format(MlflowConstants.MLFLOW_BUILD_DOCKER, deployModelKey, imageName)); - args.add(String.format(MlflowConstants.DOCKER_RREMOVE_CONTAINER, containerName)); - args.add(mlflowParameters.getDockerComposeEnvCommand()); - args.add(MlflowConstants.DOCKER_COMPOSE_RUN); } - String command = ParameterUtils.convertParameterPlaceholders(String.join("\n", args), ParamUtils.convert(paramsMap)); - return command; + return ParameterUtils.convertParameterPlaceholders(String.join("\n", args), ParamUtils.convert(paramsMap)); } private Map getParamsMap() { @@ -212,7 +247,7 @@ public class MlflowTask extends AbstractTaskExecutor { } - public int checkDockerHealth() throws Exception { + public int checkDockerHealth() { logger.info("checking container healthy ... "); int exitCode = -1; String[] command = {"sh", "-c", String.format(MlflowConstants.DOCKER_HEALTH_CHECK, mlflowParameters.getContainerName())}; @@ -240,13 +275,8 @@ public class MlflowTask extends AbstractTaskExecutor { } @Override - public AbstractParameters getParameters() { + public MlflowParameters getParameters() { return mlflowParameters; } - public String getTemplatePath(String template) { - String templatePath = MlflowTask.class.getClassLoader().getResource(template).getPath(); - return templatePath; - } - } diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/main/resources/docker-compose.yml b/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/main/resources/docker-compose.yml deleted file mode 100644 index f9211b9bae..0000000000 --- a/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/main/resources/docker-compose.yml +++ /dev/null @@ -1,39 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -version: "3" - -services: - mlflow-model: - image: "${DS_TASK_MLFLOW_IMAGE_NAME}" - container_name: "${DS_TASK_MLFLOW_CONTAINER_NAME}" - ports: - - "${DS_TASK_MLFLOW_DEPLOY_PORT}:8080" - deploy: - resources: - limits: - cpus: "${DS_TASK_MLFLOW_CPU_LIMIT}" - memory: "${DS_TASK_MLFLOW_MEMORY_LIMIT}" - - environment: - PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION: python - - - healthcheck: - test: ["CMD", "curl", "http://127.0.0.1:8080/ping"] - interval: 5s - timeout: 5s - retries: 5 \ No newline at end of file diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/test/java/org/apache/dolphinler/plugin/task/mlflow/MlflowTaskTest.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/test/java/org/apache/dolphinler/plugin/task/mlflow/MlflowTaskTest.java index ea29d3b4bd..5f0c1aa581 100644 --- a/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/test/java/org/apache/dolphinler/plugin/task/mlflow/MlflowTaskTest.java +++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-mlflow/src/test/java/org/apache/dolphinler/plugin/task/mlflow/MlflowTaskTest.java @@ -17,6 +17,8 @@ package org.apache.dolphinler.plugin.task.mlflow; +import static org.powermock.api.mockito.PowerMockito.when; + import org.apache.dolphinscheduler.plugin.task.api.TaskExecutionContext; import org.apache.dolphinscheduler.plugin.task.api.TaskExecutionContextCacheManager; import org.apache.dolphinscheduler.plugin.task.mlflow.MlflowConstants; @@ -76,21 +78,46 @@ public class MlflowTaskTest { return taskExecutionContext; } + @Test + public void testGetPresetRepositoryData() { + Assert.assertEquals("https://github.com/apache/dolphinscheduler-mlflow", MlflowTask.getPresetRepository()); + + Assert.assertEquals("main", MlflowTask.getPresetRepositoryVersion()); + + String definedRepository = "https://github.com//dolphinscheduler-mlflow"; + when(PropertyUtils.getString(MlflowConstants.PRESET_REPOSITORY_KEY)).thenAnswer(invocation -> definedRepository); + Assert.assertEquals(definedRepository, MlflowTask.getPresetRepository()); + + String definedRepositoryVersion = "dev"; + when(PropertyUtils.getString(MlflowConstants.PRESET_REPOSITORY_VERSION_KEY)).thenAnswer(invocation -> definedRepositoryVersion); + Assert.assertEquals(definedRepositoryVersion, MlflowTask.getPresetRepositoryVersion()); + } + + @Test + public void testGetVersionString() { + Assert.assertEquals("--version=main", MlflowTask.getVersionString("main", "https://github.com/apache/dolphinscheduler-mlflow")); + Assert.assertEquals("--version=master", MlflowTask.getVersionString("master", "https://github.com/apache/dolphinscheduler-mlflow")); + Assert.assertEquals("--version=main", MlflowTask.getVersionString("main", "git@github.com:apache/dolphinscheduler-mlflow.git")); + Assert.assertEquals("--version=master", MlflowTask.getVersionString("master", "git@github.com:apache/dolphinscheduler-mlflow.git")); + Assert.assertEquals("", MlflowTask.getVersionString("main", "/tmp/dolphinscheduler-mlflow")); + Assert.assertEquals("", MlflowTask.getVersionString("master", "/tmp/dolphinscheduler-mlflow")); + } + @Test public void testInitBasicAlgorithmTask() { MlflowTask mlflowTask = initTask(createBasicAlgorithmParameters()); Assert.assertEquals(mlflowTask.buildCommand(), "export MLFLOW_TRACKING_URI=http://127.0.0.1:5000\n" + "data_path=/data/iris.csv\n" - + "repo=dolphinscheduler-mlflow#Project-BasicAlgorithm\n" - + "git clone https://github.com/apache/dolphinscheduler-mlflow dolphinscheduler-mlflow\n" + + "repo=https://github.com/apache/dolphinscheduler-mlflow#Project-BasicAlgorithm\n" + "mlflow run $repo " + "-P algorithm=xgboost " + "-P data_path=$data_path " + "-P params=\"n_estimators=100\" " + "-P search_params=\"\" " + "-P model_name=\"BasicAlgorithm\" " - + "--experiment-name=\"BasicAlgorithm\""); + + "--experiment-name=\"BasicAlgorithm\" " + + "--version=main"); } @Test @@ -99,19 +126,32 @@ public class MlflowTaskTest { Assert.assertEquals(mlflowTask.buildCommand(), "export MLFLOW_TRACKING_URI=http://127.0.0.1:5000\n" + "data_path=/data/iris.csv\n" - + "repo=dolphinscheduler-mlflow#Project-AutoML\n" - + "git clone https://github.com/apache/dolphinscheduler-mlflow dolphinscheduler-mlflow\n" + + "repo=https://github.com/apache/dolphinscheduler-mlflow#Project-AutoML\n" + "mlflow run $repo " + "-P tool=autosklearn " + "-P data_path=$data_path " + "-P params=\"time_left_for_this_task=30\" " + "-P model_name=\"AutoML\" " - + "--experiment-name=\"AutoML\""); + + "--experiment-name=\"AutoML\" " + + "--version=main"); } @Test public void testInitCustomProjectTask() { MlflowTask mlflowTask = initTask(createCustomProjectParameters()); + + // Version will be set if parameter.mlflowProjectVersion is empty + Assert.assertEquals(mlflowTask.buildCommand(), + "export MLFLOW_TRACKING_URI=http://127.0.0.1:5000\n" + + "repo=https://github.com/mlflow/mlflow#examples/xgboost/xgboost_native\n" + + "mlflow run $repo " + + "-P learning_rate=0.2 " + + "-P colsample_bytree=0.8 " + + "-P subsample=0.9 " + + "--experiment-name=\"custom_project\""); + + // Version will be set if repository is remote path + mlflowTask.getParameters().setMlflowProjectVersion("dev"); Assert.assertEquals(mlflowTask.buildCommand(), "export MLFLOW_TRACKING_URI=http://127.0.0.1:5000\n" + "repo=https://github.com/mlflow/mlflow#examples/xgboost/xgboost_native\n" @@ -120,7 +160,19 @@ public class MlflowTaskTest { + "-P colsample_bytree=0.8 " + "-P subsample=0.9 " + "--experiment-name=\"custom_project\" " - + "--version=\"master\" "); + + "--version=dev"); + + // Version will not be set if repository is local path + mlflowTask.getParameters().setMlflowProjectRepository("/tmp/dolphinscheduler-mlflow"); + Assert.assertEquals(mlflowTask.buildCommand(), + "export MLFLOW_TRACKING_URI=http://127.0.0.1:5000\n" + + "repo=/tmp/dolphinscheduler-mlflow\n" + + "mlflow run $repo " + + "-P learning_rate=0.2 " + + "-P colsample_bytree=0.8 " + + "-P subsample=0.9 " + + "--experiment-name=\"custom_project\""); + } @Test @@ -143,24 +195,6 @@ public class MlflowTaskTest { + "mlflow/model:1"); } - @Test - public void testModelsDeployDockerCompose() throws Exception { - MlflowTask mlflowTask = initTask(createModelDeplyDockerComposeParameters()); - Assert.assertEquals(mlflowTask.buildCommand(), - "export MLFLOW_TRACKING_URI=http://127.0.0.1:5000\n" - + "cp " - + mlflowTask.getTemplatePath(MlflowConstants.TEMPLATE_DOCKER_COMPOSE) - + " /tmp/dolphinscheduler_test\n" - + "mlflow models build-docker -m models:/model/1 -n mlflow/model:1 --enable-mlserver\n" - + "docker rm -f ds-mlflow-model-1\n" - + "export DS_TASK_MLFLOW_IMAGE_NAME=mlflow/model:1\n" - + "export DS_TASK_MLFLOW_CONTAINER_NAME=ds-mlflow-model-1\n" - + "export DS_TASK_MLFLOW_DEPLOY_PORT=7000\n" - + "export DS_TASK_MLFLOW_CPU_LIMIT=0.5\n" - + "export DS_TASK_MLFLOW_MEMORY_LIMIT=200m\n" - + "docker-compose up -d"); - } - private MlflowTask initTask(MlflowParameters mlflowParameters) { TaskExecutionContext taskExecutionContext = createContext(mlflowParameters); MlflowTask mlflowTask = new MlflowTask(taskExecutionContext); @@ -174,11 +208,11 @@ public class MlflowTaskTest { mlflowParameters.setMlflowTaskType(MlflowConstants.MLFLOW_TASK_TYPE_PROJECTS); mlflowParameters.setMlflowJobType(MlflowConstants.JOB_TYPE_BASIC_ALGORITHM); mlflowParameters.setAlgorithm("xgboost"); - mlflowParameters.setDataPaths("/data/iris.csv"); + mlflowParameters.setDataPath("/data/iris.csv"); mlflowParameters.setParams("n_estimators=100"); - mlflowParameters.setExperimentNames("BasicAlgorithm"); - mlflowParameters.setModelNames("BasicAlgorithm"); - mlflowParameters.setMlflowTrackingUris("http://127.0.0.1:5000"); + mlflowParameters.setExperimentName("BasicAlgorithm"); + mlflowParameters.setModelName("BasicAlgorithm"); + mlflowParameters.setMlflowTrackingUri("http://127.0.0.1:5000"); return mlflowParameters; } @@ -188,10 +222,10 @@ public class MlflowTaskTest { mlflowParameters.setMlflowJobType(MlflowConstants.JOB_TYPE_AUTOML); mlflowParameters.setAutomlTool("autosklearn"); mlflowParameters.setParams("time_left_for_this_task=30"); - mlflowParameters.setDataPaths("/data/iris.csv"); - mlflowParameters.setExperimentNames("AutoML"); - mlflowParameters.setModelNames("AutoML"); - mlflowParameters.setMlflowTrackingUris("http://127.0.0.1:5000"); + mlflowParameters.setDataPath("/data/iris.csv"); + mlflowParameters.setExperimentName("AutoML"); + mlflowParameters.setModelName("AutoML"); + mlflowParameters.setMlflowTrackingUri("http://127.0.0.1:5000"); return mlflowParameters; } @@ -199,8 +233,8 @@ public class MlflowTaskTest { MlflowParameters mlflowParameters = new MlflowParameters(); mlflowParameters.setMlflowTaskType(MlflowConstants.MLFLOW_TASK_TYPE_PROJECTS); mlflowParameters.setMlflowJobType(MlflowConstants.JOB_TYPE_CUSTOM_PROJECT); - mlflowParameters.setMlflowTrackingUris("http://127.0.0.1:5000"); - mlflowParameters.setExperimentNames("custom_project"); + mlflowParameters.setMlflowTrackingUri("http://127.0.0.1:5000"); + mlflowParameters.setExperimentName("custom_project"); mlflowParameters.setParams("-P learning_rate=0.2 -P colsample_bytree=0.8 -P subsample=0.9"); mlflowParameters.setMlflowProjectRepository("https://github.com/mlflow/mlflow#examples/xgboost/xgboost_native"); @@ -211,7 +245,7 @@ public class MlflowTaskTest { MlflowParameters mlflowParameters = new MlflowParameters(); mlflowParameters.setMlflowTaskType(MlflowConstants.MLFLOW_TASK_TYPE_MODELS); mlflowParameters.setDeployType(MlflowConstants.MLFLOW_MODELS_DEPLOY_TYPE_MLFLOW); - mlflowParameters.setMlflowTrackingUris("http://127.0.0.1:5000"); + mlflowParameters.setMlflowTrackingUri("http://127.0.0.1:5000"); mlflowParameters.setDeployModelKey("models:/model/1"); mlflowParameters.setDeployPort("7000"); return mlflowParameters; @@ -221,21 +255,9 @@ public class MlflowTaskTest { MlflowParameters mlflowParameters = new MlflowParameters(); mlflowParameters.setMlflowTaskType(MlflowConstants.MLFLOW_TASK_TYPE_MODELS); mlflowParameters.setDeployType(MlflowConstants.MLFLOW_MODELS_DEPLOY_TYPE_DOCKER); - mlflowParameters.setMlflowTrackingUris("http://127.0.0.1:5000"); - mlflowParameters.setDeployModelKey("models:/model/1"); - mlflowParameters.setDeployPort("7000"); - return mlflowParameters; - } - - private MlflowParameters createModelDeplyDockerComposeParameters() { - MlflowParameters mlflowParameters = new MlflowParameters(); - mlflowParameters.setMlflowTaskType(MlflowConstants.MLFLOW_TASK_TYPE_MODELS); - mlflowParameters.setDeployType(MlflowConstants.MLFLOW_MODELS_DEPLOY_TYPE_DOCKER_COMPOSE); - mlflowParameters.setMlflowTrackingUris("http://127.0.0.1:5000"); + mlflowParameters.setMlflowTrackingUri("http://127.0.0.1:5000"); mlflowParameters.setDeployModelKey("models:/model/1"); mlflowParameters.setDeployPort("7000"); - mlflowParameters.setCpuLimit("0.5"); - mlflowParameters.setMemoryLimit("200m"); return mlflowParameters; } } diff --git a/dolphinscheduler-ui/src/locales/en_US/project.ts b/dolphinscheduler-ui/src/locales/en_US/project.ts index fa599a466c..1242d17af4 100644 --- a/dolphinscheduler-ui/src/locales/en_US/project.ts +++ b/dolphinscheduler-ui/src/locales/en_US/project.ts @@ -710,7 +710,7 @@ export default { mlflow_deployModelKey: 'Model-URI', mlflow_deployPort: 'Port', mlflowProjectRepository: 'Repository', - mlflowProjectRepository_tips: 'github respository or path on worker', + mlflowProjectRepository_tips: 'git respository or path on worker', mlflowProjectVersion: 'Project Version', mlflowProjectVersion_tips: 'git version', mlflow_cpuLimit: 'Max Cpu Limit', diff --git a/dolphinscheduler-ui/src/locales/zh_CN/project.ts b/dolphinscheduler-ui/src/locales/zh_CN/project.ts index a08d0dc678..50873d8771 100644 --- a/dolphinscheduler-ui/src/locales/zh_CN/project.ts +++ b/dolphinscheduler-ui/src/locales/zh_CN/project.ts @@ -693,7 +693,7 @@ export default { mlflow_deployModelKey: '部署的模型URI', mlflow_deployPort: '监听端口', mlflowProjectRepository: '运行仓库', - mlflowProjectRepository_tips: '可以为github仓库或worker上的路径', + mlflowProjectRepository_tips: '可以为git仓库或worker上的路径', mlflowProjectVersion: '项目版本', mlflowProjectVersion_tips: '项目git版本', mlflow_cpuLimit: '最大cpu限制', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-mlflow-models.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-mlflow-models.ts index 9939bbb5aa..eef3837fde 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-mlflow-models.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-mlflow-models.ts @@ -23,8 +23,6 @@ export function useMlflowModels(model: { [field: string]: any }): IJsonItem[] { const deployTypeSpan = ref(0) const deployModelKeySpan = ref(0) const deployPortSpan = ref(0) - const cpuLimitSpan = ref(0) - const memoryLimitSpan = ref(0) const setFlag = () => { model.isModels = model.mlflowTaskType === 'MLflow Models' ? true : false @@ -44,14 +42,6 @@ export function useMlflowModels(model: { [field: string]: any }): IJsonItem[] { } ) - watch( - () => [model.deployType], - () => { - cpuLimitSpan.value = model.deployType === 'DOCKER COMPOSE' ? 12 : 0 - memoryLimitSpan.value = model.deployType === 'DOCKER COMPOSE' ? 12 : 0 - } - ) - setFlag() resetSpan() @@ -74,18 +64,6 @@ export function useMlflowModels(model: { [field: string]: any }): IJsonItem[] { field: 'deployPort', name: t('project.node.mlflow_deployPort'), span: deployPortSpan - }, - { - type: 'input', - field: 'cpuLimit', - name: t('project.node.mlflow_cpuLimit'), - span: cpuLimitSpan - }, - { - type: 'input', - field: 'memoryLimit', - name: t('project.node.mlflow_memoryLimit'), - span: memoryLimitSpan } ] } @@ -98,9 +76,5 @@ const DEPLOY_TYPE = [ { label: 'DOCKER', value: 'DOCKER' - }, - { - label: 'DOCKER COMPOSE', - value: 'DOCKER COMPOSE' } ] diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-mlflow-projects.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-mlflow-projects.ts index ea033cbb83..c312ee435e 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-mlflow-projects.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-mlflow-projects.ts @@ -280,16 +280,16 @@ export function useCustomProject(model: { [field: string]: any }): IJsonItem[] { export const MLFLOW_JOB_TYPE = [ { - label: 'BasicAlgorithm', - value: 'BasicAlgorithm' + label: 'Custom Project', + value: 'CustomProject' }, { label: 'AutoML', value: 'AutoML' }, { - label: 'Custom Project', - value: 'CustomProject' + label: 'BasicAlgorithm', + value: 'BasicAlgorithm' } ] export const ALGORITHM = [ @@ -311,12 +311,12 @@ export const ALGORITHM = [ } ] export const AutoMLTOOL = [ - { - label: 'autosklearn', - value: 'autosklearn' - }, { label: 'flaml', value: 'flaml' + }, + { + label: 'autosklearn', + value: 'autosklearn' } ] diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-mlflow.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-mlflow.ts index 66ce7cd8c2..efed24661f 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-mlflow.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-mlflow.ts @@ -17,6 +17,7 @@ import { useI18n } from 'vue-i18n' import type { IJsonItem } from '../types' import { useMlflowProjects, useMlflowModels } from '.' +import { useCustomParams, useResources } from '.' export const MLFLOW_TASK_TYPE = [ { @@ -61,6 +62,8 @@ export function useMlflow(model: { [field: string]: any }): IJsonItem[] { options: MLFLOW_TASK_TYPE }, ...useMlflowProjects(model), - ...useMlflowModels(model) + ...useMlflowModels(model), + useResources(), + ...useCustomParams({ model, field: 'localParams', isSimple: true }) ] } diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/format-data.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/format-data.ts index 0d845fafc1..c7be65e8ca 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/format-data.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/format-data.ts @@ -369,8 +369,6 @@ export function formatParams(data: INodeData): { taskParams.deployModelKey = data.deployModelKey taskParams.mlflowProjectRepository = data.mlflowProjectRepository taskParams.mlflowProjectVersion = data.mlflowProjectVersion - taskParams.cpuLimit = data.cpuLimit - taskParams.memoryLimit = data.memoryLimit } if (data.taskType === 'DVC') { diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-mlflow.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-mlflow.ts index e1957f138e..6c6ed21db1 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-mlflow.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-mlflow.ts @@ -47,10 +47,7 @@ export function useMlflow({ deployType: 'MLFLOW', deployPort: '7000', mlflowJobType: 'CustomProject', - mlflowProjectVersion: 'master', automlTool: 'flaml', - cpuLimit: '0.5', - memoryLimit: '500M', mlflowCustomProjectParameters: [], delayTime: 0, timeout: 30,