This is an automated email from the ASF dual-hosted git repository.

byronhsu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/submarine.git


The following commit(s) were added to refs/heads/master by this push:
     new 56bfbbb  SUBMARINE-858. Implement log_param and log_metric for 
distributed scenarios
56bfbbb is described below

commit 56bfbbbc0f6f0eaedd4c06fb0ad2d8625c3af501
Author: ByronHsu <[email protected]>
AuthorDate: Mon Jun 21 11:58:39 2021 +0800

    SUBMARINE-858. Implement log_param and log_metric for distributed scenarios
    
    ### What is this PR for?
    Implement the backbone of distributed model management sdk and provide an 
example.
    
    **Implement details:**
    1. Calling start(), ModelsClient will parse the worker_id + experiment_id 
and creates a Mlflow run.
    2. The Mlflow run name is worker_id, and the Mflow experiment name is 
experiment_id.
    3. Calling log_param() or log_metric(), their info will be directed to the 
Mlflow runs previously set.
    
    **I also wrote an example docker image to test and demo the function.**
    You can follow [this 
instruction](https://github.com/apache/submarine/blob/67a595a8c7122d8026f151e5bb49292b7df3ef51/dev-support/examples/tracking/readme.md)
 to try this new function. (**REMEMBER TO INSTALL LATEST PYSUBMARINE LOCALLY 
FIRST**)
    
    ### What type of PR is it?
    [Feature]
    
    ### Todos
    * [ ] - Task
    
    ### What is the Jira issue?
    https://issues.apache.org/jira/browse/SUBMARINE-858
    
    ### How should this be tested?
    Manually test by the example provided.
    
    ### Screenshots (if appropriate)
    
    https://www.loom.com/share/bc0e9db28a0243bdacb564aacef6306b
    
    ### Questions:
    * Do the license files need updating? No
    * Are there breaking changes for older versions? No
    * Does this need new documentation? No
    
    Author: ByronHsu <[email protected]>
    Author: Byron <[email protected]>
    
    Signed-off-by: byronhsu <[email protected]>
    
    Closes #609 from ByronHsu/distributed_mm and squashes the following commits:
    
    2fa03bb9 [ByronHsu] fix typo
    42deeb70 [ByronHsu] add license
    a3543565 [ByronHsu] lint fails
    59f44298 [ByronHsu] uncomment script
    82869057 [ByronHsu] add example readme
    b2dcfd06 [ByronHsu] remove tmp test
    dce819b6 [ByronHsu] add tracking example
    2205ea08 [Byron] distributed mm initial commit
---
 .github/workflows/master.yml                       |  2 +-
 .gitignore                                         |  6 +-
 dev-support/examples/tracking/Dockerfile           | 22 +++++
 dev-support/examples/tracking/build.sh             | 44 ++++++++++
 dev-support/examples/tracking/post.sh              | 42 ++++++++++
 dev-support/examples/tracking/readme.md            | 22 +++++
 .../examples/tracking/tracking.py                  | 13 +++
 .../pysubmarine/submarine/models/client.py         | 45 ++++++++++-
 .../pysubmarine/submarine/models/utils.py          | 94 ++++++++++++++++++----
 9 files changed, 269 insertions(+), 21 deletions(-)

diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml
index 24af976..2fed76f 100644
--- a/.github/workflows/master.yml
+++ b/.github/workflows/master.yml
@@ -22,7 +22,7 @@ jobs:
           java -version
       - uses: actions/cache@v1
         with:
-          path: ~/.m2/repository/com1
+          path: ~/.m2/repository/com
           key: ${{ runner.os }}-maven-com-${{ hashFiles('**/pom.xml') }}
           restore-keys: |
             ${{ runner.os }}-maven-com-
diff --git a/.gitignore b/.gitignore
index 1bd36f5..04fb86b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -102,4 +102,8 @@ submarine-cloud-v2/charts/*
 .ipynb_checkpoints
 
 # redundant logging files
-*.log
\ No newline at end of file
+*.log
+
+# mlflow
+mlruns/
+mlflow*
diff --git a/dev-support/examples/tracking/Dockerfile 
b/dev-support/examples/tracking/Dockerfile
new file mode 100644
index 0000000..d6148af
--- /dev/null
+++ b/dev-support/examples/tracking/Dockerfile
@@ -0,0 +1,22 @@
+# 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.
+
+FROM python:3.7
+MAINTAINER Apache Software Foundation <[email protected]>
+
+ADD ./tmp/submarine-sdk /opt/
+RUN pip install /opt/pysubmarine
+
+ADD ./tracking.py /opt/
\ No newline at end of file
diff --git a/dev-support/examples/tracking/build.sh 
b/dev-support/examples/tracking/build.sh
new file mode 100755
index 0000000..b83f0e8
--- /dev/null
+++ b/dev-support/examples/tracking/build.sh
@@ -0,0 +1,44 @@
+#!/usr/bin/env bash
+# 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.
+
+set -euxo pipefail
+
+SUBMARINE_VERSION=0.6.0-SNAPSHOT
+SUBMARINE_IMAGE_NAME="tracking:${SUBMARINE_VERSION}"
+
+if [ -L ${BASH_SOURCE-$0} ]; then
+  PWD=$(dirname $(readlink "${BASH_SOURCE-$0}"))
+else
+  PWD=$(dirname ${BASH_SOURCE-$0})
+fi
+export CURRENT_PATH=$(cd "${PWD}">/dev/null; pwd)
+export SUBMARINE_HOME=${CURRENT_PATH}/../../..
+
+if [ -d "${CURRENT_PATH}/tmp" ] # if old tmp folder is still there, delete it.
+then
+  rm -rf "${CURRENT_PATH}/tmp"
+fi
+
+mkdir -p "${CURRENT_PATH}/tmp"
+cp -r "${SUBMARINE_HOME}/submarine-sdk" "${CURRENT_PATH}/tmp"
+
+# build image
+cd ${CURRENT_PATH}
+echo "Start building the ${SUBMARINE_IMAGE_NAME} docker image ..."
+docker build -t ${SUBMARINE_IMAGE_NAME} .
+
+# clean temp file
+rm -rf "${CURRENT_PATH}/tmp"
diff --git a/dev-support/examples/tracking/post.sh 
b/dev-support/examples/tracking/post.sh
new file mode 100755
index 0000000..7adbd7d
--- /dev/null
+++ b/dev-support/examples/tracking/post.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+# 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.
+
+curl -X POST -H "Content-Type: application/json" -d '
+{
+  "meta": {
+    "name": "tracking-example",
+    "namespace": "default",
+    "framework": "TensorFlow",
+    "cmd": "python /opt/tracking.py",
+    "envVars": {
+      "ENV_1": "ENV1"
+    }
+  },
+  "environment": {
+    "image": "tracking:0.6.0-SNAPSHOT"
+  },
+  "spec": {
+    "Ps": {
+      "replicas": 1,
+      "resources": "cpu=1,memory=128M"
+    },
+    "Worker": {
+      "replicas": 3,
+      "resources": "cpu=1,memory=128M"
+    }
+  }
+}
+' http://127.0.0.1:32080/api/v1/experiment
\ No newline at end of file
diff --git a/dev-support/examples/tracking/readme.md 
b/dev-support/examples/tracking/readme.md
new file mode 100644
index 0000000..e452a16
--- /dev/null
+++ b/dev-support/examples/tracking/readme.md
@@ -0,0 +1,22 @@
+# Tracking Example
+
+## Usage
+This is an easy example of how to track the metric and paramater in 
submarine-sdk. Basically, the sdk will detect which experiment and worker-id 
are you at, and log your data to the corresponding place. 
+
+For example, you start an experiment with 3 workers. Suppose the experiment is 
assigned with an ID `experiment_12345678`, and the operator launches 3 pods 
with worker_id `worker-0`, `worker-1` and `worker-2` respectively. 
+
+The logging of `worker-i` will be directed to `experiment_12345678` / 
`worker-i` in the mlflow server 
+
+## How to execute
+
+1. Build the docker image
+
+```bash
+./dev-support/examples/tracking/build.sh
+```
+
+2. Submit a post request
+
+```bash
+./dev-support/examples/tracking/post.sh
+```
diff --git a/submarine-sdk/pysubmarine/submarine/models/utils.py 
b/dev-support/examples/tracking/tracking.py
similarity index 67%
copy from submarine-sdk/pysubmarine/submarine/models/utils.py
copy to dev-support/examples/tracking/tracking.py
index 273b024..641c25e 100644
--- a/submarine-sdk/pysubmarine/submarine/models/utils.py
+++ b/dev-support/examples/tracking/tracking.py
@@ -14,3 +14,16 @@
  specific language governing permissions and limitations
  under the License.
 """
+
+from submarine import ModelsClient
+import random
+import time
+
+if __name__ == "__main__":
+  periscope = ModelsClient()
+  with periscope.start() as run:
+      periscope.log_param("learning_rate", random.random())
+      for i in range(100):
+        time.sleep(1)
+        periscope.log_metric("mse", random.random() * 100, i)
+        periscope.log_metric("acc", random.random(), i)
\ No newline at end of file
diff --git a/submarine-sdk/pysubmarine/submarine/models/client.py 
b/submarine-sdk/pysubmarine/submarine/models/client.py
index 7bf6495..1bd7f22 100644
--- a/submarine-sdk/pysubmarine/submarine/models/client.py
+++ b/submarine-sdk/pysubmarine/submarine/models/client.py
@@ -17,10 +17,12 @@
 import os
 
 import mlflow
+from mlflow.exceptions import MlflowException
 from mlflow.tracking import MlflowClient
 
 from .constant import (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY,
                        MLFLOW_S3_ENDPOINT_URL, MLFLOW_TRACKING_URI)
+from .utils import get_job_id, get_worker_index
 
 
 class ModelsClient():
@@ -29,12 +31,34 @@ class ModelsClient():
         """
         Set up mlflow server connection, including: s3 endpoint, aws, tracking 
server
         """
+        # if setting url in environment variable,
+        # there is no need to set it by MlflowClient() or 
mlflow.set_tracking_uri() again
         os.environ[
             "MLFLOW_S3_ENDPOINT_URL"] = registry_uri or MLFLOW_S3_ENDPOINT_URL
         os.environ["AWS_ACCESS_KEY_ID"] = AWS_ACCESS_KEY_ID
         os.environ["AWS_SECRET_ACCESS_KEY"] = AWS_SECRET_ACCESS_KEY
         os.environ["MLFLOW_TRACKING_URI"] = tracking_uri or MLFLOW_TRACKING_URI
-        self._client = MlflowClient()
+        self.client = MlflowClient()
+
+    def start(self):
+        """
+        1. Start a new Mlflow run
+        2. Direct the logging of the artifacts and metadata
+            to the Run named "worker_i" under Experiment "job_id"
+        3. If in distributed training, worker and job id would be parsed from 
environment variable
+        4. If in local traning, worker and job id will be generated.
+        :return: Active Run
+        """
+        experiment_name = get_job_id()
+        run_name = get_worker_index()
+        experiment_id = self._get_or_create_experiment(experiment_name)
+        return mlflow.start_run(run_name=run_name, experiment_id=experiment_id)
+
+    def log_param(self, key, value):
+        mlflow.log_param(key, value)
+
+    def log_metric(self, key, value, step=None):
+        mlflow.log_metric(key, value, step)
 
     def log_model(self, name, checkpoint):
         mlflow.pytorch.log_model(registered_model_name=name,
@@ -46,7 +70,22 @@ class ModelsClient():
         return model
 
     def update_model(self, name, new_name):
-        self._client.rename_registered_model(name=name, new_name=new_name)
+        self.client.rename_registered_model(name=name, new_name=new_name)
 
     def delete_model(self, name, version):
-        self._client.delete_model_version(name=name, version=version)
+        self.client.delete_model_version(name=name, version=version)
+
+    def _get_or_create_experiment(self, experiment_name):
+        """
+        Return the id of experiment.
+        If non-exist, create one. Otherwise, return the existing one.
+        :return: Experiment id
+        """
+        try:
+            experiment = mlflow.get_experiment_by_name(experiment_name)
+            if experiment is None:  # if not found
+                raise MlflowException("No valid experiment has been found")
+            return experiment.experiment_id  # if found
+        except MlflowException:
+            experiment = mlflow.create_experiment(name=experiment_name)
+            return experiment
diff --git a/submarine-sdk/pysubmarine/submarine/models/utils.py 
b/submarine-sdk/pysubmarine/submarine/models/utils.py
index 273b024..7bb5d5d 100644
--- a/submarine-sdk/pysubmarine/submarine/models/utils.py
+++ b/submarine-sdk/pysubmarine/submarine/models/utils.py
@@ -1,16 +1,78 @@
-"""
- 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.
-"""
+# 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.
+
+from __future__ import print_function
+
+import json
+import os
+import uuid
+
+from submarine.utils import env
+
+_JOB_ID_ENV_VAR = "JOB_ID"
+
+_TF_CONFIG = "TF_CONFIG"
+_CLUSTER_SPEC = "CLUSTER_SPEC"
+_JOB_NAME = "JOB_NAME"
+_TYPE = "type"
+_TASK = "task"
+_INDEX = "index"
+_RANK = "RANK"
+
+
+def get_job_id():
+    """
+    Get the current experiment id.
+    :return The experiment id:
+    """
+    # Get yarn application or K8s experiment ID when running distributed 
training
+    if env.get_env(_JOB_ID_ENV_VAR) is not None:
+        return env.get_env(_JOB_ID_ENV_VAR)
+    else:  # set Random ID when running local training
+        job_id = uuid.uuid4().hex
+        os.environ[_JOB_ID_ENV_VAR] = job_id
+        return job_id
+
+
+def get_worker_index():
+    """
+    Get the current worker index.
+    :return: The worker index:
+    """
+    # Get TensorFlow worker index
+    if env.get_env(_TF_CONFIG) is not None:
+        tf_config = json.loads(os.environ.get(_TF_CONFIG))
+        task_config = tf_config.get(_TASK)
+        task_type = task_config.get(_TYPE)
+        task_index = task_config.get(_INDEX)
+        worker_index = task_type + '-' + str(task_index)
+    elif env.get_env(_CLUSTER_SPEC) is not None:
+        cluster_spec = json.loads(os.environ.get(_CLUSTER_SPEC))
+        task_config = cluster_spec.get(_TASK)
+        task_type = task_config.get(_JOB_NAME)
+        task_index = task_config.get(_INDEX)
+        worker_index = task_type + '-' + str(task_index)
+    # Get PyTorch worker index
+    elif env.get_env(_RANK) is not None:
+        rank = env.get_env(_RANK)
+        if rank == "0":
+            worker_index = "master-0"
+        else:
+            worker_index = "worker-" + rank
+    # Set worker index to "worker-0" When running local training
+    else:
+        worker_index = "worker-0"
+
+    return worker_index

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to