From 62f0cc860eacc6adda3e8c346d9d682c1cebbd63 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Mon, 27 Jan 2025 00:34:25 +0530 Subject: [PATCH 01/36] Added zenodo alternative link for bert fake quantized model --- script/get-ml-model-bert-large-squad/meta.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/script/get-ml-model-bert-large-squad/meta.yaml b/script/get-ml-model-bert-large-squad/meta.yaml index 51bdd93d3..9a11e4d50 100644 --- a/script/get-ml-model-bert-large-squad/meta.yaml +++ b/script/get-ml-model-bert-large-squad/meta.yaml @@ -111,7 +111,8 @@ variations: env: MLC_DOWNLOAD_CHECKSUM: 45f88ffb2915362242703c85c38ec2d4 MLC_ML_MODEL_F1: '90.067' - MLC_PACKAGE_URL: https://zenodo.org/record/3750364/files/bert_large_v1_1_fake_quant.onnx + MLC_PACKAGE_URL: https://armi.in/files/bert_large_v1_1_fake_quant.onnx + MLC_PACKAGE_URL1: https://zenodo.org/record/3750364/files/bert_large_v1_1_fake_quant.onnx onnx,int8,amazon-s3: env: MLC_PACKAGE_URL: https://mlperf-public.s3.us-west-2.amazonaws.com/bert_large_v1_1_fake_quant.onnx From 59e2ce19070ddf0be2b68b44bf21f57713a61e86 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Sun, 26 Jan 2025 20:32:21 +0000 Subject: [PATCH 02/36] Update VERSION --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 8acdd82b7..81340c7e7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.1 +0.0.4 From a87f4cd8a0ba796cf05c54dc527f8490950c13ed Mon Sep 17 00:00:00 2001 From: mlcommons-bot Date: Sun, 26 Jan 2025 20:32:33 +0000 Subject: [PATCH 03/36] Updated git_commit_hash.txt --- git_commit_hash.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git_commit_hash.txt b/git_commit_hash.txt index 544b69b88..e18f41faa 100644 --- a/git_commit_hash.txt +++ b/git_commit_hash.txt @@ -1 +1 @@ -04d9d38807dc640eef528b282a2100a95332e395 +59e2ce19070ddf0be2b68b44bf21f57713a61e86 From 83a09e2d793204053eddd857dd17035603e801f3 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Sun, 26 Jan 2025 20:36:49 +0000 Subject: [PATCH 04/36] Update setup.py --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 16ff5cc7a..c8d014c5c 100644 --- a/setup.py +++ b/setup.py @@ -108,10 +108,13 @@ def get_commit_hash(): # Get project metadata from pyproject.toml project_meta = get_project_meta() +# Read version from the VERSION file +version = read_file("VERSION", default="0.0.1") + setup( name=project_meta.get("name", "mlperf"), - version=project_meta.get("version", "0.0.1"), + version=version, description=project_meta.get("description", "MLPerf Automations."), author=", ".join(a.get("name", "") for a in project_meta.get("authors", [])), From 99b3674f0ea00f0f8aa8b9f78884c12abaa4cfb0 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Sun, 26 Jan 2025 20:37:06 +0000 Subject: [PATCH 05/36] Update pyproject.toml --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b089f0614..d96eee0c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,6 @@ build-backend = "setuptools.build_meta" [project] name = "mlc-scripts" -version = "0.0.1" description = "Automation scripts for running ML applications using MLC interface" authors = [ { name = "MLCommons", email = "systems@mlcommons.org" } From d65285819cd8a80baa326fc453297c64d85caade Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Sun, 26 Jan 2025 20:38:23 +0000 Subject: [PATCH 06/36] Update VERSION --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 81340c7e7..bbdeab622 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.4 +0.0.5 From 52aa0511e4b91ea8275b61a998c354d61f9ad4e1 Mon Sep 17 00:00:00 2001 From: mlcommons-bot Date: Sun, 26 Jan 2025 20:38:36 +0000 Subject: [PATCH 07/36] Updated git_commit_hash.txt --- git_commit_hash.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git_commit_hash.txt b/git_commit_hash.txt index e18f41faa..2c81389ee 100644 --- a/git_commit_hash.txt +++ b/git_commit_hash.txt @@ -1 +1 @@ -59e2ce19070ddf0be2b68b44bf21f57713a61e86 +d65285819cd8a80baa326fc453297c64d85caade From 3fa4446c5ac3319f8f0ff1f1cc91ceef6fb21643 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Sun, 26 Jan 2025 21:57:00 +0000 Subject: [PATCH 08/36] Fixes for nvidia mlperf inference (#156) * Support different usernames in MLC docker mounts * Fix self-hosted github actions --- ...t-amd-mlperf-inference-implementations.yml | 4 +-- ...intel-mlperf-inference-implementations.yml | 2 +- ...rf-wheel.yml => test-mlcscripts-wheel.yml} | 7 ++-- .../workflows/test-mlperf-inference-gptj.yml | 4 +-- .../test-mlperf-inference-llama2.yml | 4 +-- .../test-mlperf-inference-mixtral.yml | 4 +-- .../test-mlperf-inference-resnet50.yml | 11 +++++++ .../workflows/test-mlperf-inference-rnnt.yml | 4 +-- .../workflows/test-mlperf-inference-sdxl.yaml | 4 +-- ...vidia-mlperf-inference-implementations.yml | 10 +++--- .../workflows/test-qaic-compute-sdk-build.yml | 2 +- .github/workflows/test-qaic-software-kit.yml | 2 +- automation/script/docker_utils.py | 9 +++--- pyproject.toml | 1 + script/build-dockerfile/customize.py | 32 +++++++++---------- script/build-dockerfile/dockerinfo.json | 2 -- setup.py | 6 ++-- 17 files changed, 61 insertions(+), 47 deletions(-) rename .github/workflows/{test-mlperf-wheel.yml => test-mlcscripts-wheel.yml} (83%) diff --git a/.github/workflows/test-amd-mlperf-inference-implementations.yml b/.github/workflows/test-amd-mlperf-inference-implementations.yml index 9ff0b4da4..4c4b6f749 100644 --- a/.github/workflows/test-amd-mlperf-inference-implementations.yml +++ b/.github/workflows/test-amd-mlperf-inference-implementations.yml @@ -20,7 +20,7 @@ jobs: python3 -m venv gh_action source gh_action/bin/activate export MLC_REPOS=$HOME/GH_MLC - pip install --upgrade cm4mlops - cm pull repo + pip install --upgrade mlc-scripts + mlc pull repo mlcr --tags=run-mlperf,inference,_all-scenarios,_full,_r4.1-dev --execution_mode=valid --pull_changes=yes --pull_inference_changes=yes --model=${{ matrix.model }} --submitter="MLCommons" --hw_name=IntelSPR.24c --implementation=amd --backend=pytorch --category=datacenter --division=open --scenario=Offline --docker_dt=yes --docker_it=no --docker_mlc_repo=gateoverflow@mlperf-automations --docker_mlc_repo_branch=dev --adr.compiler.tags=gcc --device=rocm --use_dataset_from_host=yes --results_dir=$HOME/gh_action_results --submission_dir=$HOME/gh_action_submissions --clean --docker --quiet --docker_skip_run_cmd=yes # mlcr --tags=push,github,mlperf,inference,submission --repo_url=https://github.com/mlcommons/mlperf_inference_unofficial_submissions_v5.0 --repo_branch=dev --commit_message="Results from GH action on SPR.24c" --quiet --submission_dir=$HOME/gh_action_submissions --hw_name=IntelSPR.24c diff --git a/.github/workflows/test-intel-mlperf-inference-implementations.yml b/.github/workflows/test-intel-mlperf-inference-implementations.yml index d56302111..0041f9762 100644 --- a/.github/workflows/test-intel-mlperf-inference-implementations.yml +++ b/.github/workflows/test-intel-mlperf-inference-implementations.yml @@ -20,7 +20,7 @@ jobs: python3 -m venv gh_action_conda source gh_action_conda/bin/activate export MLC_REPOS=$HOME/GH_MLC - pip install --upgrade cm4mlops + pip install --upgrade mlc-scripts pip install tabulate mlcr --tags=run-mlperf,inference,_all-scenarios,_submission,_full,_r4.1-dev --preprocess_submission=yes --execution_mode=valid --pull_changes=yes --pull_inference_changes=yes --model=${{ matrix.model }} --submitter="MLCommons" --hw_name=IntelSPR.24c --implementation=intel --backend=pytorch --category=datacenter --division=open --scenario=Offline --docker_dt=yes --docker_it=no --docker_mlc_repo=mlcommons@mlperf-automations --docker_mlc_repo_branch=dev --adr.compiler.tags=gcc --device=cpu --use_dataset_from_host=yes --results_dir=$HOME/gh_action_results --submission_dir=$HOME/gh_action_submissions --clean --docker --quiet mlcr --tags=push,github,mlperf,inference,submission --repo_url=https://github.com/mlcommons/mlperf_inference_unofficial_submissions_v5.0 --repo_branch=auto-update --commit_message="Results from GH action on SPR.24c" --quiet --submission_dir=$HOME/gh_action_submissions --hw_name=IntelSPR.24c diff --git a/.github/workflows/test-mlperf-wheel.yml b/.github/workflows/test-mlcscripts-wheel.yml similarity index 83% rename from .github/workflows/test-mlperf-wheel.yml rename to .github/workflows/test-mlcscripts-wheel.yml index 2ff1595fb..7abf40bbc 100644 --- a/.github/workflows/test-mlperf-wheel.yml +++ b/.github/workflows/test-mlcscripts-wheel.yml @@ -1,4 +1,4 @@ -name: Build Python Wheel +name: Build mlc-scripts Wheel on: pull_request: @@ -6,7 +6,7 @@ on: - main - dev paths: - - '.github/workflows/test-mlperf-wheel.yml' + - '.github/workflows/test-mlcscripts-wheel.yml' - 'setup.py' jobs: @@ -16,6 +16,9 @@ jobs: matrix: os: [macos-latest, ubuntu-latest, windows-latest] python-version: [ '3.8', '3.13'] + exclude: + - os: windows-latest + python-version: "3.8" runs-on: ${{ matrix.os }} diff --git a/.github/workflows/test-mlperf-inference-gptj.yml b/.github/workflows/test-mlperf-inference-gptj.yml index c99c503ff..341e2e818 100644 --- a/.github/workflows/test-mlperf-inference-gptj.yml +++ b/.github/workflows/test-mlperf-inference-gptj.yml @@ -24,8 +24,8 @@ jobs: source gh_action/bin/deactivate || python3 -m venv gh_action source gh_action/bin/activate export MLC_REPOS=$HOME/GH_MLC - python3 -m pip install cm4mlops - cm pull repo + python3 -m pip install --upgrade mlc-scripts + mlc pull repo mlcr --tags=run-mlperf,inference,_submission,_short --submitter="MLCommons" --docker --pull_changes=yes --pull_inference_changes=yes --model=gptj-99 --backend=${{ matrix.backend }} --device=cuda --scenario=Offline --test_query_count=1 --precision=${{ matrix.precision }} --target_qps=1 --quiet --docker_it=no --docker_mlc_repo=gateoverflow@mlperf-automations --docker_mlc_repo_branch=dev --adr.compiler.tags=gcc --beam_size=1 --hw_name=gh_action --docker_dt=yes --results_dir=$HOME/gh_action_results --submission_dir=$HOME/gh_action_submissions --get_platform_details=yes --implementation=reference --clean mlcr --tags=push,github,mlperf,inference,submission --repo_url=https://github.com/mlcommons/mlperf_inference_test_submissions_v5.0 --repo_branch=auto-update --commit_message="Results from self hosted Github actions - NVIDIARTX4090" --quiet --submission_dir=$HOME/gh_action_submissions diff --git a/.github/workflows/test-mlperf-inference-llama2.yml b/.github/workflows/test-mlperf-inference-llama2.yml index 8de010505..70e4e4909 100644 --- a/.github/workflows/test-mlperf-inference-llama2.yml +++ b/.github/workflows/test-mlperf-inference-llama2.yml @@ -25,9 +25,9 @@ jobs: source gh_action/bin/deactivate || python3 -m venv gh_action source gh_action/bin/activate export MLC_REPOS=$HOME/GH_MLC - pip install cm4mlops + pip install mlc-scripts pip install tabulate - cm pull repo + mlc pull repo pip install "huggingface_hub[cli]" git config --global credential.helper store huggingface-cli login --token ${{ secrets.HF_TOKEN }} --add-to-git-credential diff --git a/.github/workflows/test-mlperf-inference-mixtral.yml b/.github/workflows/test-mlperf-inference-mixtral.yml index 26b369c09..e48cbb1e9 100644 --- a/.github/workflows/test-mlperf-inference-mixtral.yml +++ b/.github/workflows/test-mlperf-inference-mixtral.yml @@ -26,10 +26,10 @@ jobs: source gh_action/bin/deactivate || python3 -m venv gh_action source gh_action/bin/activate export MLC_REPOS=$HOME/GH_MLC - pip install cm4mlops + pip install --upgrade mlc-scripts pip install "huggingface_hub[cli]" git config --global credential.helper store huggingface-cli login --token ${{ secrets.HF_TOKEN }} --add-to-git-credential - cm pull repo + mlc pull repo mlcr --tags=run-mlperf,inference,_submission,_short --adr.inference-src.tags=_branch.dev --submitter="MLCommons" --pull_changes=yes --pull_inference_changes=yes --model=mixtral-8x7b --implementation=reference --batch_size=1 --precision=${{ matrix.precision }} --backend=${{ matrix.backend }} --category=datacenter --scenario=Offline --execution_mode=test --device=${{ matrix.device }} --docker_it=no --docker_mlc_repo=gateoverflow@mlperf-automations --adr.compiler.tags=gcc --hw_name=gh_action --docker_dt=yes --results_dir=$HOME/gh_action_results --submission_dir=$HOME/gh_action_submissions --docker --quiet --test_query_count=3 --target_qps=0.001 --clean --env.MLC_MLPERF_MODEL_MIXTRAL_8X7B_DOWNLOAD_TO_HOST=yes --env.MLC_MLPERF_DATASET_MIXTRAL_8X7B_DOWNLOAD_TO_HOST=yes --adr.openorca-mbxp-gsm8k-combined-preprocessed.tags=_size.1 mlcr --tags=push,github,mlperf,inference,submission --repo_url=https://github.com/mlcommons/mlperf_inference_test_submissions_v5.0 --repo_branch=auto-update --commit_message="Results from self hosted Github actions - GO-phoenix" --quiet --submission_dir=$HOME/gh_action_submissions diff --git a/.github/workflows/test-mlperf-inference-resnet50.yml b/.github/workflows/test-mlperf-inference-resnet50.yml index 85bcd3cc2..4b7413d97 100644 --- a/.github/workflows/test-mlperf-inference-resnet50.yml +++ b/.github/workflows/test-mlperf-inference-resnet50.yml @@ -61,6 +61,15 @@ jobs: - name: Randomly Execute Step id: random-check run: | + if [[ "$RUNNER_OS" == "Windows" ]]; then + $RANDOM_NUMBER = Get-Random -Maximum 10 + Write-Host "Random number is $RANDOM_NUMBER" + if ($RANDOM_NUMBER -eq 0) { + Write-Host "run_step=true" | Out-File -FilePath $Env:GITHUB_ENV -Append + } else { + Write-Host "run_step=false" | Out-File -FilePath $Env:GITHUB_ENV -Append + } + else RANDOM_NUMBER=$((RANDOM % 10)) echo "Random number is $RANDOM_NUMBER" if [ "$RANDOM_NUMBER" -eq 0 ]; then @@ -68,6 +77,8 @@ jobs: else echo "run_step=false" >> $GITHUB_ENV fi + fi + - name: Retrieve secrets from Keeper if: github.repository_owner == 'mlcommons' && env.run_step == 'true' id: ksecrets diff --git a/.github/workflows/test-mlperf-inference-rnnt.yml b/.github/workflows/test-mlperf-inference-rnnt.yml index 352272370..89ec6e4e2 100644 --- a/.github/workflows/test-mlperf-inference-rnnt.yml +++ b/.github/workflows/test-mlperf-inference-rnnt.yml @@ -30,10 +30,10 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies on Unix Platforms run: | - MLC_PULL_DEFAULT_MLOPS_REPO=no pip install cm4mlops + pip install mlcflow - name: Pull MLOps repository run: | - cm pull repo --url=${{ github.event.pull_request.head.repo.html_url }} --checkout=${{ github.event.pull_request.head.ref }} + mlc pull repo --url=${{ github.event.pull_request.head.repo.html_url }} --checkout=${{ github.event.pull_request.head.ref }} mlcr --quiet --tags=get,sys-utils-cm - name: Test MLPerf Inference RNNT run: | diff --git a/.github/workflows/test-mlperf-inference-sdxl.yaml b/.github/workflows/test-mlperf-inference-sdxl.yaml index 7f7ce1fea..2e287a0be 100644 --- a/.github/workflows/test-mlperf-inference-sdxl.yaml +++ b/.github/workflows/test-mlperf-inference-sdxl.yaml @@ -19,7 +19,7 @@ jobs: source gh_action/bin/deactivate || python3 -m venv gh_action source gh_action/bin/activate export MLC_REPOS=$HOME/GH_MLC - python3 -m pip install cm4mlops - cm pull repo + python3 -m pip install mlc-scripts + mlc pull repo mlcr --tags=run-mlperf,inference,_submission,_short --submitter="MLCommons" --pull_changes=yes --pull_inference_changes=yes --docker --model=sdxl --backend=${{ matrix.backend }} --device=cuda --scenario=Offline --test_query_count=1 --precision=${{ matrix.precision }} --quiet --docker_it=no --docker_mlc_repo=gateoverflow@mlperf-automations --adr.compiler.tags=gcc --hw_name=gh_action --docker_dt=yes --results_dir=$HOME/gh_action_results --submission_dir=$HOME/gh_action_submissions --env.MLC_MLPERF_MODEL_SDXL_DOWNLOAD_TO_HOST=yes --clean mlcr --tags=push,github,mlperf,inference,submission --repo_url=https://github.com/mlcommons/mlperf_inference_test_submissions_v5.0 --repo_branch=auto-update --commit_message="Results from self hosted Github actions - NVIDIARTX4090" --quiet --submission_dir=$HOME/gh_action_submissions diff --git a/.github/workflows/test-nvidia-mlperf-inference-implementations.yml b/.github/workflows/test-nvidia-mlperf-inference-implementations.yml index 86f06873d..30f74e605 100644 --- a/.github/workflows/test-nvidia-mlperf-inference-implementations.yml +++ b/.github/workflows/test-nvidia-mlperf-inference-implementations.yml @@ -2,7 +2,7 @@ name: MLPerf Inference Nvidia implementations on: schedule: - - cron: "58 23 * * *" #to be adjusted + - cron: "45 17 * * *" #to be adjusted jobs: run_nvidia: @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - system: [ "GO-spr", "phoenix-Amd-Am5", "GO-i9"] + system: [ "GO-spr", "phoenix", "GO-i9"] # system: [ "mlc-server" ] python-version: [ "3.12" ] model: [ "resnet50", "retinanet", "bert-99", "bert-99.9", "gptj-99.9", "3d-unet-99.9", "sdxl" ] @@ -48,9 +48,9 @@ jobs: python3 -m venv gh_action source gh_action/bin/activate export MLC_REPOS=$HOME/GH_MLC - MLC_PULL_DEFAULT_MLOPS_REPO=no pip install --upgrade cm4mlops - cm pull repo + pip install --upgrade mlcflow + mlc pull repo mlcommons@mlperf-automations --branch=dev - mlcr --tags=run-mlperf,inference,_all-scenarios,_submission,_full,_r4.1-dev --preprocess_submission=yes --pull_changes=yes --pull_inference_changes=yes --execution_mode=valid --gpu_name=$gpu_name --pull_changes=yes --pull_inference_changes=yes --model=${{ matrix.model }} --submitter="MLCommons" --hw_name=$hw_name --implementation=nvidia --backend=tensorrt --category=datacenter,edge --division=closed --docker_dt=yes --docker_it=no --docker_mlc_repo=mlcommons@mlperf-automations --docker_mlc_repo_branch=dev --adr.compiler.tags=gcc --device=cuda --use_model_from_host=yes --use_dataset_from_host=yes --results_dir=$HOME/gh_action_results --submission_dir=$HOME/gh_action_submissions --clean $docker_string --quiet + mlcr --tags=run-mlperf,inference,_all-scenarios,_submission,_full,_r5.0-dev --preprocess_submission=yes --pull_changes=yes --pull_inference_changes=yes --execution_mode=valid --gpu_name=$gpu_name --pull_changes=yes --pull_inference_changes=yes --model=${{ matrix.model }} --submitter="MLCommons" --hw_name=$hw_name --implementation=nvidia --backend=tensorrt --category=datacenter,edge --division=closed --docker_dt --docker_mlc_repo=mlcommons@mlperf-automations --docker_mlc_repo_branch=dev --adr.compiler.tags=gcc --device=cuda --use_model_from_host=yes --use_dataset_from_host=yes --results_dir=$HOME/gh_action_results --submission_dir=$HOME/gh_action_submissions --clean $docker_string --quiet mlcr --tags=push,github,mlperf,inference,submission --repo_url=https://github.com/mlcommons/mlperf_inference_unofficial_submissions_v5.0 --repo_branch=auto-update --commit_message="Results from GH action on NVIDIA_$hw_name" --quiet --submission_dir=$HOME/gh_action_submissions --hw_name=$hw_name diff --git a/.github/workflows/test-qaic-compute-sdk-build.yml b/.github/workflows/test-qaic-compute-sdk-build.yml index 810da9e8e..0dff27cd0 100644 --- a/.github/workflows/test-qaic-compute-sdk-build.yml +++ b/.github/workflows/test-qaic-compute-sdk-build.yml @@ -26,7 +26,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - MLC_PULL_DEFAULT_MLOPS_REPO=no pip install cm4mlops + pip install mlc-scripts mlcr --tags=get,sys-utils-cm --quiet - name: Test QAIC Compute SDK for compilation diff --git a/.github/workflows/test-qaic-software-kit.yml b/.github/workflows/test-qaic-software-kit.yml index 127de2323..5cbfc0add 100644 --- a/.github/workflows/test-qaic-software-kit.yml +++ b/.github/workflows/test-qaic-software-kit.yml @@ -31,7 +31,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Pull MLOps repository run: | - pip install mlperf + pip install mlc-scripts mlcr --tags=get,sys-utils-mlc --quiet - name: Test Software Kit for compilation on Ubuntu 20.04 diff --git a/automation/script/docker_utils.py b/automation/script/docker_utils.py index 6379c515f..f67211fcb 100644 --- a/automation/script/docker_utils.py +++ b/automation/script/docker_utils.py @@ -60,7 +60,7 @@ def process_mounts(mounts, env, docker_settings, f_run_cmd): for placeholder in container_placeholders: if placeholder in env: new_container_mount, container_env_key = get_container_path( - env[placeholder]) + env[placeholder], docker_settings.get('user', 'mlcuser')) else: # Skip mount if variable is missing mounts[index] = None break @@ -400,18 +400,19 @@ def get_host_path(value): def get_container_path_script(i): tmp_dep_cached_path = i['tmp_dep_cached_path'] - value_mnt, value_env = get_container_path(tmp_dep_cached_path) + value_mnt, value_env = get_container_path( + tmp_dep_cached_path, os.getlogin()) return {'return': 0, 'value_mnt': value_mnt, 'value_env': value_env} -def get_container_path(value): +def get_container_path(value, username="mlcuser"): path_split = value.split(os.sep) if len(path_split) == 1: return value new_value = '' if "cache" in path_split and "local" in path_split: - new_path_split = ["", "home", "mlcuser", "MLC", "repos"] + new_path_split = ["", "home", username, "MLC", "repos"] repo_entry_index = path_split.index("local") if len(path_split) >= repo_entry_index + 3: new_path_split1 = new_path_split + \ diff --git a/pyproject.toml b/pyproject.toml index d96eee0c6..7c1b4e8a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,6 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "mlc-scripts" +version="0.0.5" description = "Automation scripts for running ML applications using MLC interface" authors = [ { name = "MLCommons", email = "systems@mlcommons.org" } diff --git a/script/build-dockerfile/customize.py b/script/build-dockerfile/customize.py index 50a4c987c..8a01ef753 100644 --- a/script/build-dockerfile/customize.py +++ b/script/build-dockerfile/customize.py @@ -4,6 +4,7 @@ import json import re import shutil +from utils import * def preprocess(i): @@ -255,8 +256,7 @@ def preprocess(i): 'MLC_DOCKER_USE_DEFAULT_USER', '') == '': env['MLC_DOCKER_USE_DEFAULT_USER'] = 'yes' - if docker_user and str(env.get('MLC_DOCKER_USE_DEFAULT_USER', '')).lower() not in [ - "yes", "1", "true"]: + if docker_user and not is_true(env.get('MLC_DOCKER_USE_DEFAULT_USER', '')): f.write('RUN groupadd -g $GID -o ' + docker_group + EOL) @@ -283,16 +283,19 @@ def preprocess(i): dockerfile_env_input_string = dockerfile_env_input_string + " --env." + \ docker_env_key + "=" + str(dockerfile_env[docker_env_key]) - workdir = get_value(env, config, 'WORKDIR', 'MLC_DOCKER_WORKDIR') - if workdir and (f"""/home/{docker_user}""" not in workdir or str(env.get('MLC_DOCKER_USE_DEFAULT_USER', '')).lower() not in [ - "yes", "1", "true"]): + workdir = env.get('WORKDIR', '') + if workdir == '': + workdir = f"""/home/{docker_user}""" + + if f"""/home/{docker_user}""" not in workdir or not is_true( + env.get('MLC_DOCKER_USE_DEFAULT_USER', '')): f.write('WORKDIR ' + workdir + EOL) f.write(EOL + '# Install python packages' + EOL) python = get_value(env, config, 'PYTHON', 'MLC_DOCKERFILE_PYTHON') docker_use_virtual_python = env.get('MLC_DOCKER_USE_VIRTUAL_PYTHON', "yes") - if str(docker_use_virtual_python).lower() not in ["no", "0", "false"]: + if not is_false(docker_use_virtual_python): f.write('RUN {} -m venv $HOME/venv/mlc'.format(python) + " " + EOL) f.write('ENV PATH="$HOME/venv/mlc/bin:$PATH"' + EOL) # f.write('RUN . /opt/venv/mlc/bin/activate' + EOL) @@ -342,8 +345,7 @@ def preprocess(i): for y in x.split(','): f.write('RUN ' + y + EOL) - if str(env.get('MLC_DOCKER_SKIP_MLC_SYS_UPGRADE', False) - ).lower() not in ["true", "1", "yes"]: + if not is_true(env.get('MLC_DOCKER_SKIP_MLC_SYS_UPGRADE', False)): f.write(EOL + '# Install all system dependencies' + EOL) f.write('RUN mlc run script --tags=get,sys-utils-mlc --quiet' + EOL) @@ -368,14 +370,12 @@ def preprocess(i): env['MLC_DOCKER_RUN_CMD'] += "mlc version" skip_extra = True else: - if str(env.get('MLC_DOCKER_NOT_PULL_UPDATE', 'False') - ).lower() not in ["yes", "1", "true"]: + if not is_true(env.get('MLC_DOCKER_NOT_PULL_UPDATE', 'False')): env['MLC_DOCKER_RUN_CMD'] += "mlc pull repo && " env['MLC_DOCKER_RUN_CMD'] += "mlc run script --tags=" + \ env['MLC_DOCKER_RUN_SCRIPT_TAGS'] + ' --quiet' else: - if str(env.get('MLC_DOCKER_NOT_PULL_UPDATE', 'False') - ).lower() not in ["yes", "1", "true"]: + if not is_true(env.get('MLC_DOCKER_NOT_PULL_UPDATE', 'False')): env['MLC_DOCKER_RUN_CMD'] = "mlc pull repo && " + \ env['MLC_DOCKER_RUN_CMD'] @@ -394,8 +394,8 @@ def preprocess(i): if run_cmd_extra != '': x += ' ' + run_cmd_extra - if env.get('MLC_DOCKER_RUN_SCRIPT_TAGS', '') != '' and str(env.get( - 'MLC_DOCKER_ADD_DEPENDENT_SCRIPTS_RUN_COMMANDS', '')).lower() in ["yes", "1", "true"]: + if env.get('MLC_DOCKER_RUN_SCRIPT_TAGS', '') != '' and is_true(env.get( + 'MLC_DOCKER_ADD_DEPENDENT_SCRIPTS_RUN_COMMANDS', '')): mlc_input = {'action': 'run', 'automation': 'script', 'tags': f"""{env['MLC_DOCKER_RUN_SCRIPT_TAGS']}""", @@ -417,8 +417,8 @@ def preprocess(i): f.write(x + EOL) # fake_run to install the dependent scripts and caching them - if not "run" in env['MLC_DOCKER_RUN_CMD'] and str( - env.get('MLC_REAL_RUN', False)).lower() in ["false", "0", "no"]: + if not "run" in env['MLC_DOCKER_RUN_CMD'] and is_false( + env.get('MLC_REAL_RUN', False)): fake_run = dockerfile_env_input_string x = 'RUN ' + env['MLC_DOCKER_RUN_CMD'] + fake_run + run_cmd_extra diff --git a/script/build-dockerfile/dockerinfo.json b/script/build-dockerfile/dockerinfo.json index cfb739e77..2b222c01c 100644 --- a/script/build-dockerfile/dockerinfo.json +++ b/script/build-dockerfile/dockerinfo.json @@ -22,11 +22,9 @@ "GID": "", "GROUP": "mlc", "SHELL": "[\"/bin/bash\", \"-c\"]", - "WORKDIR": "/home/mlcuser", "distros": { "ubuntu": { "USER": "ubuntu", - "WORKDIR": "/home/ubuntu", "package-manager-update-cmd": "apt-get update -y", "package-manager-get-cmd": "apt-get install -y", "packages": [ diff --git a/setup.py b/setup.py index c8d014c5c..5a18472c6 100644 --- a/setup.py +++ b/setup.py @@ -69,11 +69,11 @@ def run(self): print("Running custom post-install command...") commit_hash = get_commit_hash() import mlc - branch = os.environ.get('MLC_REPO_BRANCH', 'mlc') + branch = os.environ.get('MLC_REPO_BRANCH', 'dev') res = mlc.access({'action': 'pull', - 'automation': 'repo', - 'url': 'mlcommons@mlperf-automations', + 'target': 'repo', + 'repo': 'mlcommons@mlperf-automations', 'branch': branch, 'checkout': commit_hash }) From 93a3be62f42e0c40dfd32d28730ad0137a6667a5 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Sun, 26 Jan 2025 22:02:51 +0000 Subject: [PATCH 09/36] Fix cuda-python version --- script/app-mlperf-inference-nvidia/meta.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/script/app-mlperf-inference-nvidia/meta.yaml b/script/app-mlperf-inference-nvidia/meta.yaml index eac133277..472eb9383 100644 --- a/script/app-mlperf-inference-nvidia/meta.yaml +++ b/script/app-mlperf-inference-nvidia/meta.yaml @@ -481,6 +481,8 @@ variations: names: - nvtx - tags: get,generic-python-lib,_package.cuda-python + version_max: 12.6.2 + version_max_usable: 12.6.2 names: - cuda-python - tags: get,generic-python-lib,_package.ninja From a63e5e261e328ff9c1a9b8a24896b0ec8ee9a1ef Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Mon, 27 Jan 2025 04:31:40 +0530 Subject: [PATCH 10/36] Fix typo in docker_utils --- automation/script/docker_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/automation/script/docker_utils.py b/automation/script/docker_utils.py index f67211fcb..04d7c388d 100644 --- a/automation/script/docker_utils.py +++ b/automation/script/docker_utils.py @@ -77,7 +77,6 @@ def process_mounts(mounts, env, docker_settings, f_run_cmd): container_env_string += f" --env.{host_env_key}={container_env_key} " for key, value in docker_input_mapping.items(): if value == host_env_key: - i[key] = container_env_key f_run_cmd[key] = container_env_key # Remove invalid mounts and construct mount string From 61e7be527352a6ec562e351f7e9d550d14dbdf1a Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Sun, 26 Jan 2025 23:28:59 +0000 Subject: [PATCH 11/36] Cleanup (#158) * Cleanup old files --- ...lperf-inference-mlcommons-cpp-resnet50.yml | 7 +- .../test-mlperf-inference-resnet50.yml | 37 +-- ...vidia-mlperf-inference-implementations.yml | 2 +- automation/cache/README-extra.md | 71 ------ automation/cache/README.md | 87 ------- automation/cache/meta.json | 12 - automation/cache/module.py | 236 ------------------ automation/cache/module_misc.py | 109 -------- automation/script/README.md | 29 +-- automation/script/assets/scripts-workflow.png | Bin 242876 -> 0 bytes automation/script/module_help.py | 106 -------- .../script/template-ae-python/README-extra.md | 2 - automation/script/template-ae-python/_cm.yaml | 38 --- .../script/template-ae-python/analyze.bat | 12 - .../script/template-ae-python/analyze.sh | 12 - .../template-ae-python/install_deps.bat | 18 -- .../script/template-ae-python/install_deps.sh | 17 -- automation/script/template-ae-python/main.py | 10 - automation/script/template-ae-python/plot.bat | 12 - automation/script/template-ae-python/plot.sh | 12 - .../script/template-ae-python/reproduce.bat | 12 - .../script/template-ae-python/reproduce.sh | 12 - automation/script/template-ae-python/run.bat | 12 - automation/script/template-ae-python/run.sh | 12 - .../script/template-ae-python/validate.bat | 12 - .../script/template-ae-python/validate.sh | 12 - .../script/template-python/README-extra.md | 1 - automation/script/template-python/_cm.yaml | 23 -- .../script/template-python/customize.py | 32 --- automation/script/template-python/main.py | 10 - .../script/template-python/requirements.txt | 0 automation/script/template-python/run.bat | 25 -- automation/script/template-python/run.sh | 24 -- .../script/template-pytorch/README-extra.md | 1 - automation/script/template-pytorch/_cm.yaml | 42 ---- .../script/template-pytorch/customize.py | 32 --- automation/script/template-pytorch/main.py | 15 -- .../script/template-pytorch/requirements.txt | 0 automation/script/template-pytorch/run.bat | 25 -- automation/script/template-pytorch/run.sh | 24 -- automation/script/template/README-extra.md | 1 - automation/script/template/customize.py | 24 -- automation/script/template_list_of_scripts.md | 52 ---- .../default}/customize.py | 0 .../{template => templates/default}/run.bat | 0 .../{template => templates/default}/run.sh | 0 46 files changed, 35 insertions(+), 1197 deletions(-) delete mode 100644 automation/cache/README-extra.md delete mode 100644 automation/cache/README.md delete mode 100644 automation/cache/meta.json delete mode 100644 automation/cache/module.py delete mode 100644 automation/cache/module_misc.py delete mode 100644 automation/script/assets/scripts-workflow.png delete mode 100644 automation/script/module_help.py delete mode 100644 automation/script/template-ae-python/README-extra.md delete mode 100644 automation/script/template-ae-python/_cm.yaml delete mode 100644 automation/script/template-ae-python/analyze.bat delete mode 100644 automation/script/template-ae-python/analyze.sh delete mode 100644 automation/script/template-ae-python/install_deps.bat delete mode 100644 automation/script/template-ae-python/install_deps.sh delete mode 100644 automation/script/template-ae-python/main.py delete mode 100644 automation/script/template-ae-python/plot.bat delete mode 100644 automation/script/template-ae-python/plot.sh delete mode 100644 automation/script/template-ae-python/reproduce.bat delete mode 100644 automation/script/template-ae-python/reproduce.sh delete mode 100644 automation/script/template-ae-python/run.bat delete mode 100644 automation/script/template-ae-python/run.sh delete mode 100644 automation/script/template-ae-python/validate.bat delete mode 100644 automation/script/template-ae-python/validate.sh delete mode 100644 automation/script/template-python/README-extra.md delete mode 100644 automation/script/template-python/_cm.yaml delete mode 100644 automation/script/template-python/customize.py delete mode 100644 automation/script/template-python/main.py delete mode 100644 automation/script/template-python/requirements.txt delete mode 100644 automation/script/template-python/run.bat delete mode 100644 automation/script/template-python/run.sh delete mode 100644 automation/script/template-pytorch/README-extra.md delete mode 100644 automation/script/template-pytorch/_cm.yaml delete mode 100644 automation/script/template-pytorch/customize.py delete mode 100644 automation/script/template-pytorch/main.py delete mode 100644 automation/script/template-pytorch/requirements.txt delete mode 100644 automation/script/template-pytorch/run.bat delete mode 100644 automation/script/template-pytorch/run.sh delete mode 100644 automation/script/template/README-extra.md delete mode 100644 automation/script/template/customize.py delete mode 100644 automation/script/template_list_of_scripts.md rename automation/script/{template-ae-python => templates/default}/customize.py (100%) rename automation/script/{template => templates/default}/run.bat (100%) rename automation/script/{template => templates/default}/run.sh (100%) diff --git a/.github/workflows/test-mlperf-inference-mlcommons-cpp-resnet50.yml b/.github/workflows/test-mlperf-inference-mlcommons-cpp-resnet50.yml index 5bbec09b8..500a9dfdc 100644 --- a/.github/workflows/test-mlperf-inference-mlcommons-cpp-resnet50.yml +++ b/.github/workflows/test-mlperf-inference-mlcommons-cpp-resnet50.yml @@ -1,11 +1,8 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - name: MLPerf inference MLCommons C++ ResNet50 on: - pull_request: - branches: [ "main", "dev", "mlperf-inference" ] + pull_request_target: + branches: [ "main", "dev" ] paths: - '.github/workflows/test-mlperf-inference-mlcommons-cpp-resnet50.yml' - '**' diff --git a/.github/workflows/test-mlperf-inference-resnet50.yml b/.github/workflows/test-mlperf-inference-resnet50.yml index 4b7413d97..75e9fe6f9 100644 --- a/.github/workflows/test-mlperf-inference-resnet50.yml +++ b/.github/workflows/test-mlperf-inference-resnet50.yml @@ -58,27 +58,30 @@ jobs: if: matrix.os != 'windows-latest' run: | mlcr --tags=run-mlperf,inference,_submission,_short --submitter="MLCommons" --pull_changes=yes --pull_inference_changes=yes --hw_name=gh_${{ matrix.os }}_x86 --model=resnet50 --implementation=${{ matrix.implementation }} --backend=${{ matrix.backend }} --device=cpu --scenario=Offline --test_query_count=500 --target_qps=1 -v --quiet - - name: Randomly Execute Step - id: random-check + # Step for Linux/MacOS + - name: Randomly Execute Step (Linux/MacOS) + if: runner.os != 'Windows' run: | - if [[ "$RUNNER_OS" == "Windows" ]]; then - $RANDOM_NUMBER = Get-Random -Maximum 10 - Write-Host "Random number is $RANDOM_NUMBER" - if ($RANDOM_NUMBER -eq 0) { - Write-Host "run_step=true" | Out-File -FilePath $Env:GITHUB_ENV -Append - } else { - Write-Host "run_step=false" | Out-File -FilePath $Env:GITHUB_ENV -Append - } + RANDOM_NUMBER=$((RANDOM % 10)) + echo "Random number is $RANDOM_NUMBER" + if [ "$RANDOM_NUMBER" -eq 0 ]; then + echo "run_step=true" >> $GITHUB_ENV else - RANDOM_NUMBER=$((RANDOM % 10)) - echo "Random number is $RANDOM_NUMBER" - if [ "$RANDOM_NUMBER" -eq 0 ]; then - echo "run_step=true" >> $GITHUB_ENV - else - echo "run_step=false" >> $GITHUB_ENV - fi + echo "run_step=false" >> $GITHUB_ENV fi + # Step for Windows + - name: Randomly Execute Step (Windows) + if: runner.os == 'Windows' + run: | + $RANDOM_NUMBER = Get-Random -Maximum 10 + Write-Host "Random number is $RANDOM_NUMBER" + if ($RANDOM_NUMBER -eq 0) { + Write-Host "run_step=true" | Out-File -FilePath $Env:GITHUB_ENV -Append + } else { + Write-Host "run_step=false" | Out-File -FilePath $Env:GITHUB_ENV -Append + } + - name: Retrieve secrets from Keeper if: github.repository_owner == 'mlcommons' && env.run_step == 'true' id: ksecrets diff --git a/.github/workflows/test-nvidia-mlperf-inference-implementations.yml b/.github/workflows/test-nvidia-mlperf-inference-implementations.yml index 30f74e605..d05079335 100644 --- a/.github/workflows/test-nvidia-mlperf-inference-implementations.yml +++ b/.github/workflows/test-nvidia-mlperf-inference-implementations.yml @@ -2,7 +2,7 @@ name: MLPerf Inference Nvidia implementations on: schedule: - - cron: "45 17 * * *" #to be adjusted + - cron: "25 23 * * *" #to be adjusted jobs: run_nvidia: diff --git a/automation/cache/README-extra.md b/automation/cache/README-extra.md deleted file mode 100644 index 84d274179..000000000 --- a/automation/cache/README-extra.md +++ /dev/null @@ -1,71 +0,0 @@ -[ [Back to index](../../../docs/README.md) ] - -# CM "cache" automation - -*We suggest you to check [CM introduction](https://github.com/mlcommons/ck/blob/master/docs/introduction-cm.md) - and [CM CLI/API](https://github.com/mlcommons/ck/blob/master/docs/interface.md) to understand CM motivation and concepts.* - -## CM script CLI - -Whenever a [given CM script]() caches the output, you can find it - -Whenever a [CM script](https://access.cknowledge.org/playground/?action=scripts) -caches its output (such as downloaded model or pre-processed data set or built code), -you can find it using the CM "cache" automation as follows: - -```bash -cm show cache -``` - -You can prune cache entries by tags and variations: -```bash -cm show cache --tags=ml-model -cm show cache --tags=python -``` - -You can find a path to a given cache artifact as follows: -```bash -cm find cache --tags=ml-model,bert -``` - -You can delete one or more cache artifacts as follows: -```bash -cm rm cache --tags=ml-model -``` - -You can skip user prompt by adding `-f` flag as follows: -```bash -cm rm cache --tags=ml-model -f -``` - -You can clean the whole cache as follows: -```bash -cm rm cache -f -``` - -## CM python API - -You can access the same functionality via CM Python API as follows: - -```python - -import cmind - -output = cmind.access({'action':'show', - 'automation':'cache,541d6f712a6b464e'}) - -if output['return']>0: - cmind.error(output) - -artifacts = output['list'] - -for artifact in artifacts: - print ('') - print (artifact.path) - print (artifact.meta) - -``` - -## Related - -* [CM "script" automation](../script/README-extra.md) diff --git a/automation/cache/README.md b/automation/cache/README.md deleted file mode 100644 index 0a3114d3b..000000000 --- a/automation/cache/README.md +++ /dev/null @@ -1,87 +0,0 @@ -*This README is automatically generated - don't edit! See [extra README](README-extra.md) for extra notes!* - -### Automation actions - -#### test - - * CM CLI: ```cm test cache``` ([add flags (dict keys) from this API](https://github.com/mlcommons/ck/tree/master/cm-mlops/automation/cache/module.py#L15)) - * CM CLI with UID: ```cm test cache,541d6f712a6b464e``` ([add flags (dict keys) from this API](https://github.com/mlcommons/ck/tree/master/cm-mlops/automation/cache/module.py#L15)) - * CM Python API: - ```python - import cmind - - r=cm.access({ - 'action':'test' - 'automation':'cache,541d6f712a6b464e' - 'out':'con' - ``` - [add keys from this API](https://github.com/mlcommons/ck/tree/master/cm-mlops/automation/cache/module.py#L15) - ```python - }) - if r['return']>0: - print(r['error']) - ``` - -#### show - - * CM CLI: ```cm show cache``` ([add flags (dict keys) from this API](https://github.com/mlcommons/ck/tree/master/cm-mlops/automation/cache/module.py#L54)) - * CM CLI with UID: ```cm show cache,541d6f712a6b464e``` ([add flags (dict keys) from this API](https://github.com/mlcommons/ck/tree/master/cm-mlops/automation/cache/module.py#L54)) - * CM Python API: - ```python - import cmind - - r=cm.access({ - 'action':'show' - 'automation':'cache,541d6f712a6b464e' - 'out':'con' - ``` - [add keys from this API](https://github.com/mlcommons/ck/tree/master/cm-mlops/automation/cache/module.py#L54) - ```python - }) - if r['return']>0: - print(r['error']) - ``` - -#### search - - * CM CLI: ```cm search cache``` ([add flags (dict keys) from this API](https://github.com/mlcommons/ck/tree/master/cm-mlops/automation/cache/module.py#L153)) - * CM CLI with UID: ```cm search cache,541d6f712a6b464e``` ([add flags (dict keys) from this API](https://github.com/mlcommons/ck/tree/master/cm-mlops/automation/cache/module.py#L153)) - * CM Python API: - ```python - import cmind - - r=cm.access({ - 'action':'search' - 'automation':'cache,541d6f712a6b464e' - 'out':'con' - ``` - [add keys from this API](https://github.com/mlcommons/ck/tree/master/cm-mlops/automation/cache/module.py#L153) - ```python - }) - if r['return']>0: - print(r['error']) - ``` - -#### copy_to_remote - - * CM CLI: ```cm copy_to_remote cache``` ([add flags (dict keys) from this API](https://github.com/mlcommons/ck/tree/master/cm-mlops/automation/cache/module.py#L186)) - * CM CLI with UID: ```cm copy_to_remote cache,541d6f712a6b464e``` ([add flags (dict keys) from this API](https://github.com/mlcommons/ck/tree/master/cm-mlops/automation/cache/module.py#L186)) - * CM Python API: - ```python - import cmind - - r=cm.access({ - 'action':'copy_to_remote' - 'automation':'cache,541d6f712a6b464e' - 'out':'con' - ``` - [add keys from this API](https://github.com/mlcommons/ck/tree/master/cm-mlops/automation/cache/module.py#L186) - ```python - }) - if r['return']>0: - print(r['error']) - ``` - -### Maintainers - -* [Open MLCommons taskforce on automation and reproducibility](https://cKnowledge.org/mlcommons-taskforce) \ No newline at end of file diff --git a/automation/cache/meta.json b/automation/cache/meta.json deleted file mode 100644 index ac383f937..000000000 --- a/automation/cache/meta.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "alias": "cache", - "automation_alias": "automation", - "automation_uid": "bbeb15d8f0a944a4", - "desc": "Caching cross-platform CM scripts", - "developers": "[Arjun Suresh](https://www.linkedin.com/in/arjunsuresh), [Grigori Fursin](https://cKnowledge.org/gfursin)", - "sort": 900, - "tags": [ - "automation" - ], - "uid": "541d6f712a6b464e" -} diff --git a/automation/cache/module.py b/automation/cache/module.py deleted file mode 100644 index b205b539f..000000000 --- a/automation/cache/module.py +++ /dev/null @@ -1,236 +0,0 @@ -import os - -from cmind.automation import Automation -from cmind import utils - - -class CAutomation(Automation): - """ - Automation actions - """ - - ############################################################ - def __init__(self, cmind, automation_file): - super().__init__(cmind, __file__) - - ############################################################ - def test(self, i): - """ - Test automation - - Args: - (CM input dict): - - (out) (str): if 'con', output to console - - automation (str): automation as CM string object - - parsed_automation (list): prepared in CM CLI or CM access function - [ (automation alias, automation UID) ] or - [ (automation alias, automation UID), (automation repo alias, automation repo UID) ] - - (artifact) (str): artifact as CM string object - - (parsed_artifact) (list): prepared in CM CLI or CM access function - [ (artifact alias, artifact UID) ] or - [ (artifact alias, artifact UID), (artifact repo alias, artifact repo UID) ] - - ... - - Returns: - (CM return dict): - - * return (int): return code == 0 if no error and >0 if error - * (error) (str): error string if return>0 - - * Output from this automation action - - """ - - import json - print(json.dumps(i, indent=2)) - - return {'return': 0} - - ############################################################ - def show(self, i): - """ - Show cache - - Args: - (CM input dict): - - (out) (str): if 'con', output to console - - (env) (bool): if True, show env from cm-cached-state.json - ... - - Returns: - (CM return dict): - - * return (int): return code == 0 if no error and >0 if error - * (error) (str): error string if return>0 - - * Output from this automation action - - """ - import json - - # Check parsed automation - if 'parsed_automation' not in i: - return {'return': 1, 'error': 'automation is not specified'} - - console = i.get('out') == 'con' - - show_env = i.get('env', False) - -# Moved to search function -# # Check simplified CMD: cm show cache "get python" -# # If artifact has spaces, treat them as tags! -# artifact = i.get('artifact','') -# tags = i.get('tags','').strip() -# if ' ' in artifact or ',' in artifact: -# del(i['artifact']) -# if 'parsed_artifact' in i: del(i['parsed_artifact']) -# -# new_tags = artifact.replace(' ',',') -# tags = new_tags if tags=='' else new_tags+','+tags -# -# i['tags'] = tags - - # Find CM artifact(s) - i['out'] = None - r = self.search(i) - - if r['return'] > 0: - return r - - lst = r['list'] - for artifact in sorted(lst, key=lambda x: sorted(x.meta['tags'])): - # for artifact in lst: - path = artifact.path - meta = artifact.meta - dependent_cached_path = meta.get( - 'dependent_cached_path', '') - if dependent_cached_path and not os.path.exists( - dependent_cached_path): - continue - - original_meta = artifact.original_meta - - alias = meta.get('alias', '') - uid = meta.get('uid', '') - - tags = meta.get('tags', []) - tags1 = sorted([x for x in tags if not x.startswith('_')]) - tags2 = sorted([x for x in tags if x.startswith('_')]) - tags = tags1 + tags2 - - version = meta.get('version', '') - - if console: - print('') -# print ('* UID: {}'.format(uid)) - print('* Tags: {}'.format(','.join(tags))) - print(' Path: {}'.format(path)) - if version != '': - print(' Version: {}'.format(version)) - - if show_env and console: - path_to_cached_state_file = os.path.join( - path, 'cm-cached-state.json') - - if os.path.isfile(path_to_cached_state_file): - r = utils.load_json(file_name=path_to_cached_state_file) - if r['return'] > 0: - return r - - # Update env and state from cache! - cached_state = r['meta'] - - new_env = cached_state.get('new_env', {}) - if len(new_env) > 0: - print(' New env:') - print( - json.dumps( - new_env, - indent=6, - sort_keys=True).replace( - '{', - '').replace( - '}', - '')) - - new_state = cached_state.get('new_state', {}) - if len(new_state) > 0: - print(' New state:') - print(json.dumps(new_env, indent=6, sort_keys=True)) - - return {'return': 0, 'list': lst} - - ############################################################ - def search(self, i): - """ - Overriding the automation search function to add support for a simplified CMD with tags with spaces - - TBD: add input/output description - """ - # Check simplified CMD: cm show cache "get python" - # If artifact has spaces, treat them as tags! - artifact = i.get('artifact', '') - tags = i.get('tags', '') - - # Tags may be a list (if comes internally from CM scripts) or string if - # comes from CMD - if not isinstance(tags, list): - tags = tags.strip() - - if ' ' in artifact: # or ',' in artifact: - del (i['artifact']) - if 'parsed_artifact' in i: - del (i['parsed_artifact']) - - new_tags = artifact.replace(' ', ',') - tags = new_tags if tags == '' else new_tags + ',' + tags - - i['tags'] = tags - - # Force automation when reruning access with processed input - i['automation'] = 'cache,541d6f712a6b464e' - i['action'] = 'search' - # Avoid recursion - use internal CM add function to add the script - # artifact - i['common'] = True - - # Find CM artifact(s) - return self.cmind.access(i) - - ############################################################ - - def copy_to_remote(self, i): - """ - Add CM automation. - - Args: - (CM input dict): - - (out) (str): if 'con', output to console - - parsed_artifact (list): prepared in CM CLI or CM access function - [ (artifact alias, artifact UID) ] or - [ (artifact alias, artifact UID), (artifact repo alias, artifact repo UID) ] - - (repos) (str): list of repositories to search for automations (internal & mlcommons@ck by default) - - (output_dir) (str): output directory (./ by default) - - Returns: - (CM return dict): - - * return (int): return code == 0 if no error and >0 if error - * (error) (str): error string if return>0 - - """ - - return utils.call_internal_module( - self, __file__, 'module_misc', 'copy_to_remote', i) diff --git a/automation/cache/module_misc.py b/automation/cache/module_misc.py deleted file mode 100644 index d5895edd4..000000000 --- a/automation/cache/module_misc.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -from cmind import utils - - -############################################################ -def copy_to_remote(i): - """ - Add CM automation. - - Args: - (CM input dict): - - (out) (str): if 'con', output to console - - parsed_artifact (list): prepared in CM CLI or CM access function - [ (artifact alias, artifact UID) ] or - [ (artifact alias, artifact UID), (artifact repo alias, artifact repo UID) ] - - (repos) (str): list of repositories to search for automations (internal & mlcommons@ck by default) - - (output_dir) (str): output directory (./ by default) - - Returns: - (CM return dict): - - * return (int): return code == 0 if no error and >0 if error - * (error) (str): error string if return>0 - - """ - - self_module = i['self_module'] - - remote_host = i.get('remote_host') - if not remote_host: - return {'return': 1, - 'error': 'Please input remote host_name/IP via --remote_host'} - remote_mlc_repos_location = i.get( - 'remote_mlc_repos_location', os.path.join( - "/home", os.getlogin(), "CM", "repos")) - remote_mlc_cache_location = os.path.join( - remote_mlc_repos_location, "local", "cache") - - remote_port = i.get('remote_port', '22') - remote_user = i.get('remote_user', os.getlogin()) - - tag_string = i['tags'] - tag_string += ",-tmp" - - mlc_input = {'action': 'show', - 'automation': 'cache', - 'tags': f'{tag_string}', - 'quiet': True - } - r = self_module.cmind.access(mlc_input) - if r['return'] > 0: - return r - - if len(r['list']) == 0: - pass # fixme - elif len(r['list']) > 1: - print("Multiple cache entries found: ") - for k in sorted(r['list'], key=lambda x: x.meta.get('alias', '')): - print(k.path) - x = input("Would you like to copy them all? Y/n: ") - if x.lower() == 'n': - return {'return': 0} - - import json - - for k in sorted(r['list'], key=lambda x: x.meta.get('alias', '')): - path = k.path - cacheid = os.path.basename(path) - - copy_cmd = f"rsync -avz --exclude cm-cached-state.json -e 'ssh -p {remote_port}' {path} {remote_user}@{remote_host}:{remote_mlc_cache_location}" - print(copy_cmd) - os.system(copy_cmd) - - mlc_cached_state_json_file = os.path.join(path, "cm-cached-state.json") - if not os.path.exists(mlc_cached_state_json_file): - return {'return': 1, - 'error': f'cm-cached-state.json file missing in {path}'} - - with open(mlc_cached_state_json_file, "r") as f: - mlc_cached_state = json.load(f) - - new_env = mlc_cached_state['new_env'] - new_state = mlc_cached_state['new_state'] # Todo fix new state - mlc_repos_path = os.environ.get( - 'MLC_REPOS', os.path.join( - os.path.expanduser("~"), "CM", "repos")) - mlc_cache_path = os.path.realpath( - os.path.join(mlc_repos_path, "local", "cache")) - - for key, val in new_env.items(): - - -if isinstance(val, if ) new_env[key] = val.replace( - mlc_cache_path, remote_mlc_cache_location) - - with open("tmp_remote_cached_state.json", "w") as f: - json.dump(mlc_cached_state, f, indent=2) - - remote_cached_state_file_location = os.path.join( - remote_mlc_cache_location, cacheid, "cm-cached-state.json") - copy_cmd = f"rsync -avz -e 'ssh -p {remote_port}' tmp_remote_cached_state.json {remote_user}@{remote_host}:{remote_cached_state_file_location}" - print(copy_cmd) - os.system(copy_cmd) - - return {'return': 0} diff --git a/automation/script/README.md b/automation/script/README.md index bbedf887d..397f19c35 100644 --- a/automation/script/README.md +++ b/automation/script/README.md @@ -1,28 +1,24 @@ -# CM "script" automation specification +# "Script" automation specification -Please check the [CM documentation](https://github.com/mlcommons/ck/tree/master/docs#collective-mind-language-cm) -for more details about the CM automation language. +## Getting started with script automation +* A script is identified by a set of tags and by unique ID. +* Further each script can have multiple variations and they are identified by variation tags which are treated in the same way as tags and identified by a `_` prefix. -## Getting started with CM scripts - -* A CM script is identified by a set of tags and by unique ID. -* Further each CM script can have multiple variations and they are identified by variation tags which are treated in the same way as tags and identified by a `_` prefix. - -### CM script execution flow -* When a CM script is invoked (either by tags or by unique ID), its `_cm.json` is processed first which will check for any `deps` script and if there are, then they are executed in order. +### MLC script execution flow +* When a script is invoked (either by tags or by unique ID), its `meta.yaml` is processed first which will check for any `deps` script and if there are, then they are executed in order. * Once all the `deps` scripts are executed, `customize.py` file is checked and if existing `preprocess` function inside it is executed if present. -* Then any `prehook_deps` CM scripts mentioned in `_cm.json` are executed similar to `deps` +* Then any `prehook_deps` scripts mentioned in `meta.yaml` are executed similar to `deps` * After this, keys in `env` dictionary is exported as `ENV` variables and `run` file if exists is executed. -* Once run file execution is done, any `posthook_deps` CM scripts mentioned in `_cm.json` are executed similar to `deps` +* Once run file execution is done, any `posthook_deps` scripts mentioned in `meta.yaml` are executed similar to `deps` * Then `postprocess` function inside customize.py is executed if present. -* After this stage any `post_deps` CM scripts mentioned in `_cm.json` is executed. +* After this stage any `post_deps` scripts mentioned in `meta.yaml` is executed. ** If a script is already cached, then the `preprocess`, `run file` and `postprocess` executions won't happen and only the dependencies marked as `dynamic` will be executed from `deps`, `prehook_deps`, `posthook_deps` and `postdeps`. ### Input flags -When we run a CM script we can also pass inputs to it and any input added in `input_mapping` dictionary inside `_cm.json` gets converted to the corresponding `ENV` variable. +When we run an MLC script we can also pass inputs to it and any input added in `input_mapping` dictionary inside `meta.yaml` gets converted to the corresponding `ENV` variable. ### Conditional execution of any `deps`, `post_deps` We can use `skip_if_env` dictionary inside any `deps`, `prehook_deps`, `posthook_deps` or `post_deps` to make its execution conditional @@ -33,7 +29,7 @@ We can specify any specific version of a script using `version`. `version_max` a * When `version_max` is given, any version below this if present in the cache or detected in the system can be chosen. If nothing is detected `default_version` if present and if below `version_max` will be used for installation. Otherwise `version_max_usable` (additional needed input for `version_max`) will be used as `version`. ### Variations -* Variations are used to customize CM script and each unique combination of variations uses a unique cache entry. Each variation can turn on `env` keys also any other meta including dependencies specific to it. Variations are turned on like tags but with a `_` prefix. For example, if a script is having tags `"get,myscript"`, to call the variation `"test"` inside it, we have to use tags `"get,myscript,_test"`. +* Variations are used to customize scripts and each unique combination of variations uses a unique cache entry. Each variation can turn on `env` keys also any other meta including dependencies specific to it. Variations are turned on like tags but with a `_` prefix. For example, if a script is having tags `"get,myscript"`, to call the variation `"test"` inside it, we have to use tags `"get,myscript,_test"`. #### Variation groups `group` is a key to map variations into a group and at any time only one variation from a group can be used in the variation tags. For example, both `cpu` and `cuda` can be two variations under the `device` group, but user can at any time use either `cpu` or `cuda` as variation tags but not both. @@ -41,8 +37,7 @@ We can specify any specific version of a script using `version`. `version_max` a #### Dynamic variations Sometimes it is difficult to add all variations needed for a script like say `batch_size` which can take many different values. To handle this case, we support dynamic variations using '#' where '#' can be dynamically replaced by any string. For example, `"_batch_size.8"` can be used as a tag to turn on the dynamic variation `"_batch_size.#"`. -### ENV flow during CM script execution -* [TBD] Issue added [here](https://github.com/mlcommons/ck/issues/382) +### ENV flow during MLC script execution * During a given script execution incoming `env` dictionary is saved `(saved_env)` and all the updates happens on a copy of it. * Once a script execution is over (which includes all the dependent script executions as well), newly created keys and any updated keys are merged with the `saved_env` provided the keys are mentioned in `new_env_keys` * Same behaviour applies to `state` dictionary. diff --git a/automation/script/assets/scripts-workflow.png b/automation/script/assets/scripts-workflow.png deleted file mode 100644 index 60d0ef7157eaed67afe1a06b0a3b09a3795b43e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 242876 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%0Ac_nY~DpQ7aF7g~o^ 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∓oMW0HoPxzTTRKARAD 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 z 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 D(4EVg diff --git a/automation/script/module_help.py b/automation/script/module_help.py deleted file mode 100644 index 820378180..000000000 --- a/automation/script/module_help.py +++ /dev/null @@ -1,106 +0,0 @@ -import os -from cmind import utils - -# Pring help about script - - -def print_help(i): - - meta = i.get('meta', '') - path = i.get('path', '') - - if len(meta) == 0 and path == '': - return {'return': 0} - - print('') - print( - 'Help for this CM script ({},{}):'.format( - meta.get( - 'alias', ''), meta.get( - 'uid', ''))) - - print('') - print('Path to this automation recipe: {}'.format(path)) - - variations = meta.get('variations', {}) - if len(variations) > 0: - print('') - print('Available variations:') - print('') - for v in sorted(variations): - print(' _' + v) - - input_mapping = meta.get('input_mapping', {}) - if len(input_mapping) > 0: - print('') - print('Available flags mapped to environment variables:') - print('') - for k in sorted(input_mapping): - v = input_mapping[k] - - print(' --{} -> --env.{}'.format(k, v)) - - input_description = meta.get('input_description', {}) - if len(input_description) > 0: - # Check if has important ones (sort) - sorted_keys = [] - all_keys = sorted(list(input_description.keys())) - - for k in sorted( - all_keys, key=lambda x: input_description[x].get('sort', 0)): - v = input_description[k] - if v.get('sort', 0) > 0: - sorted_keys.append(k) - - print('') - print('Available flags (Python API dict keys):') - print('') - for k in all_keys: - v = input_description[k] - n = v.get('desc', '') - - x = ' --' + k - if n != '': - x += ' ({})'.format(n) - - print(x) - - if len(sorted_keys) > 0: - print('') - print('Main flags:') - print('') - for k in sorted_keys: - v = input_description[k] - n = v.get('desc', '') - - x = ' --' + k - - d = None - if 'default' in v: - d = v.get('default', '') - - if d is not None: - x += '=' + d - - c = v.get('choices', []) - if len(c) > 0: - x += ' {' + ','.join(c) + '}' - - if n != '': - x += ' ({})'.format(n) - - print(x) - - print('') - x = input('Would you like to see a Python API with a list of common keys/flags for all scripts including this one (y/N)? ') - - x = x.strip().lower() - - skip_delayed_help = False if x in ['y', 'yes'] else True - - r = {'return': 0} - - if skip_delayed_help: - r['skip_delayed_help'] = True - - return r diff --git a/automation/script/template-ae-python/README-extra.md b/automation/script/template-ae-python/README-extra.md deleted file mode 100644 index 05e53dc1a..000000000 --- a/automation/script/template-ae-python/README-extra.md +++ /dev/null @@ -1,2 +0,0 @@ -# CM script to run and reproduce experiments - diff --git a/automation/script/template-ae-python/_cm.yaml b/automation/script/template-ae-python/_cm.yaml deleted file mode 100644 index 261e4cf75..000000000 --- a/automation/script/template-ae-python/_cm.yaml +++ /dev/null @@ -1,38 +0,0 @@ -cache: false - -deps: - # Detect host OS features - - tags: detect,os - - # Detect/install python - - tags: get,python - names: - - python - - python3 - -script_name: run - -input_mapping: - experiment: MLC_EXPERIMENT - -default_env: - MLC_EXPERIMENT: '1' - -variations: - install_deps: - script_name: install_deps - - run: - script_name: run - - reproduce: - script_name: reproduce - - plot: - script_name: plot - - analyze: - script_name: analyze - - validate: - script_name: validate diff --git a/automation/script/template-ae-python/analyze.bat b/automation/script/template-ae-python/analyze.bat deleted file mode 100644 index 375cfaebf..000000000 --- a/automation/script/template-ae-python/analyze.bat +++ /dev/null @@ -1,12 +0,0 @@ -@echo off - -set CUR_DIR=%cd% - -echo. -echo Current execution path: %CUR_DIR% -echo Path to script: %MLC_TMP_CURRENT_SCRIPT_PATH% -echo ENV MLC_EXPERIMENT: %MLC_EXPERIMENT% - -rem echo. -rem %MLC_PYTHON_BIN_WITH_PATH% %MLC_TMP_CURRENT_SCRIPT_PATH%\main.py -rem IF %ERRORLEVEL% NEQ 0 EXIT %ERRORLEVEL% diff --git a/automation/script/template-ae-python/analyze.sh b/automation/script/template-ae-python/analyze.sh deleted file mode 100644 index 53c10c73c..000000000 --- a/automation/script/template-ae-python/analyze.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -CUR_DIR=${PWD} - -echo "" -echo "Current execution path: ${CUR_DIR}" -echo "Path to script: ${MLC_TMP_CURRENT_SCRIPT_PATH}" -echo "ENV MLC_EXPERIMENT: ${MLC_EXPERIMENT}" - -#echo "" -#${MLC_PYTHON_BIN_WITH_PATH} ${MLC_TMP_CURRENT_SCRIPT_PATH}/main.py -#test $? -eq 0 || exit 1 diff --git a/automation/script/template-ae-python/install_deps.bat b/automation/script/template-ae-python/install_deps.bat deleted file mode 100644 index 3419d9511..000000000 --- a/automation/script/template-ae-python/install_deps.bat +++ /dev/null @@ -1,18 +0,0 @@ -@echo off - -set CUR_DIR=%cd% - -echo. -echo Current execution path: %CUR_DIR% -echo Path to script: %MLC_TMP_CURRENT_SCRIPT_PATH% -echo ENV MLC_EXPERIMENT: %MLC_EXPERIMENT% - -if exist "%MLC_TMP_CURRENT_SCRIPT_PATH%\requirements.txt" ( - - echo. - echo Installing requirements.txt ... - echo. - - %MLC_PYTHON_BIN_WITH_PATH% -m pip install -r %MLC_TMP_CURRENT_SCRIPT_PATH%\requirements.txt - IF %ERRORLEVEL% NEQ 0 EXIT %ERRORLEVEL% -) diff --git a/automation/script/template-ae-python/install_deps.sh b/automation/script/template-ae-python/install_deps.sh deleted file mode 100644 index 5e8c50a20..000000000 --- a/automation/script/template-ae-python/install_deps.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -CUR_DIR=${PWD} - -echo "" -echo "Current execution path: ${CUR_DIR}" -echo "Path to script: ${MLC_TMP_CURRENT_SCRIPT_PATH}" -echo "ENV MLC_EXPERIMENT: ${MLC_EXPERIMENT}" - -if test -f "${MLC_TMP_CURRENT_SCRIPT_PATH}/requirements.txt"; then - echo "" - echo "Installing requirements.txt ..." - echo "" - - ${MLC_PYTHON_BIN_WITH_PATH} -m pip install -r ${MLC_TMP_CURRENT_SCRIPT_PATH}/requirements.txt - test $? -eq 0 || exit 1 -fi diff --git a/automation/script/template-ae-python/main.py b/automation/script/template-ae-python/main.py deleted file mode 100644 index 48b974b7f..000000000 --- a/automation/script/template-ae-python/main.py +++ /dev/null @@ -1,10 +0,0 @@ -import os - -if __name__ == "__main__": - - print('') - print('Main script:') - print('Experiment: {}'.format(os.environ.get('MLC_EXPERIMENT', ''))) - print('') - - exit(0) diff --git a/automation/script/template-ae-python/plot.bat b/automation/script/template-ae-python/plot.bat deleted file mode 100644 index 375cfaebf..000000000 --- a/automation/script/template-ae-python/plot.bat +++ /dev/null @@ -1,12 +0,0 @@ -@echo off - -set CUR_DIR=%cd% - -echo. -echo Current execution path: %CUR_DIR% -echo Path to script: %MLC_TMP_CURRENT_SCRIPT_PATH% -echo ENV MLC_EXPERIMENT: %MLC_EXPERIMENT% - -rem echo. -rem %MLC_PYTHON_BIN_WITH_PATH% %MLC_TMP_CURRENT_SCRIPT_PATH%\main.py -rem IF %ERRORLEVEL% NEQ 0 EXIT %ERRORLEVEL% diff --git a/automation/script/template-ae-python/plot.sh b/automation/script/template-ae-python/plot.sh deleted file mode 100644 index 53c10c73c..000000000 --- a/automation/script/template-ae-python/plot.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -CUR_DIR=${PWD} - -echo "" -echo "Current execution path: ${CUR_DIR}" -echo "Path to script: ${MLC_TMP_CURRENT_SCRIPT_PATH}" -echo "ENV MLC_EXPERIMENT: ${MLC_EXPERIMENT}" - -#echo "" -#${MLC_PYTHON_BIN_WITH_PATH} ${MLC_TMP_CURRENT_SCRIPT_PATH}/main.py -#test $? -eq 0 || exit 1 diff --git a/automation/script/template-ae-python/reproduce.bat b/automation/script/template-ae-python/reproduce.bat deleted file mode 100644 index 375cfaebf..000000000 --- a/automation/script/template-ae-python/reproduce.bat +++ /dev/null @@ -1,12 +0,0 @@ -@echo off - -set CUR_DIR=%cd% - -echo. -echo Current execution path: %CUR_DIR% -echo Path to script: %MLC_TMP_CURRENT_SCRIPT_PATH% -echo ENV MLC_EXPERIMENT: %MLC_EXPERIMENT% - -rem echo. -rem %MLC_PYTHON_BIN_WITH_PATH% %MLC_TMP_CURRENT_SCRIPT_PATH%\main.py -rem IF %ERRORLEVEL% NEQ 0 EXIT %ERRORLEVEL% diff --git a/automation/script/template-ae-python/reproduce.sh b/automation/script/template-ae-python/reproduce.sh deleted file mode 100644 index 53c10c73c..000000000 --- a/automation/script/template-ae-python/reproduce.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -CUR_DIR=${PWD} - -echo "" -echo "Current execution path: ${CUR_DIR}" -echo "Path to script: ${MLC_TMP_CURRENT_SCRIPT_PATH}" -echo "ENV MLC_EXPERIMENT: ${MLC_EXPERIMENT}" - -#echo "" -#${MLC_PYTHON_BIN_WITH_PATH} ${MLC_TMP_CURRENT_SCRIPT_PATH}/main.py -#test $? -eq 0 || exit 1 diff --git a/automation/script/template-ae-python/run.bat b/automation/script/template-ae-python/run.bat deleted file mode 100644 index f1b69d26d..000000000 --- a/automation/script/template-ae-python/run.bat +++ /dev/null @@ -1,12 +0,0 @@ -@echo off - -set CUR_DIR=%cd% - -echo. -echo Current execution path: %CUR_DIR% -echo Path to script: %MLC_TMP_CURRENT_SCRIPT_PATH% -echo ENV MLC_EXPERIMENT: %MLC_EXPERIMENT% - -echo. -%MLC_PYTHON_BIN_WITH_PATH% %MLC_TMP_CURRENT_SCRIPT_PATH%\main.py -IF %ERRORLEVEL% NEQ 0 EXIT %ERRORLEVEL% diff --git a/automation/script/template-ae-python/run.sh b/automation/script/template-ae-python/run.sh deleted file mode 100644 index a4b86e69a..000000000 --- a/automation/script/template-ae-python/run.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -CUR_DIR=${PWD} - -echo "" -echo "Current execution path: ${CUR_DIR}" -echo "Path to script: ${MLC_TMP_CURRENT_SCRIPT_PATH}" -echo "ENV MLC_EXPERIMENT: ${MLC_EXPERIMENT}" - -echo "" -${MLC_PYTHON_BIN_WITH_PATH} ${MLC_TMP_CURRENT_SCRIPT_PATH}/main.py -test $? -eq 0 || exit 1 diff --git a/automation/script/template-ae-python/validate.bat b/automation/script/template-ae-python/validate.bat deleted file mode 100644 index 375cfaebf..000000000 --- a/automation/script/template-ae-python/validate.bat +++ /dev/null @@ -1,12 +0,0 @@ -@echo off - -set CUR_DIR=%cd% - -echo. -echo Current execution path: %CUR_DIR% -echo Path to script: %MLC_TMP_CURRENT_SCRIPT_PATH% -echo ENV MLC_EXPERIMENT: %MLC_EXPERIMENT% - -rem echo. -rem %MLC_PYTHON_BIN_WITH_PATH% %MLC_TMP_CURRENT_SCRIPT_PATH%\main.py -rem IF %ERRORLEVEL% NEQ 0 EXIT %ERRORLEVEL% diff --git a/automation/script/template-ae-python/validate.sh b/automation/script/template-ae-python/validate.sh deleted file mode 100644 index 53c10c73c..000000000 --- a/automation/script/template-ae-python/validate.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -CUR_DIR=${PWD} - -echo "" -echo "Current execution path: ${CUR_DIR}" -echo "Path to script: ${MLC_TMP_CURRENT_SCRIPT_PATH}" -echo "ENV MLC_EXPERIMENT: ${MLC_EXPERIMENT}" - -#echo "" -#${MLC_PYTHON_BIN_WITH_PATH} ${MLC_TMP_CURRENT_SCRIPT_PATH}/main.py -#test $? -eq 0 || exit 1 diff --git a/automation/script/template-python/README-extra.md b/automation/script/template-python/README-extra.md deleted file mode 100644 index 582991f6d..000000000 --- a/automation/script/template-python/README-extra.md +++ /dev/null @@ -1 +0,0 @@ -# CM script diff --git a/automation/script/template-python/_cm.yaml b/automation/script/template-python/_cm.yaml deleted file mode 100644 index 11f646860..000000000 --- a/automation/script/template-python/_cm.yaml +++ /dev/null @@ -1,23 +0,0 @@ -cache: false - -deps: - # Detect host OS features - - tags: detect,os - - # Detect/install python - - tags: get,python - names: - - python - - python3 - -input_mapping: - var1: MLC_VAR1 - req: PIP_REQUIREMENTS - -default_env: - MLC_VAR1: 'something' - -variations: - req: - env: - PIP_REQUIREMENTS: True diff --git a/automation/script/template-python/customize.py b/automation/script/template-python/customize.py deleted file mode 100644 index 8961ab5ca..000000000 --- a/automation/script/template-python/customize.py +++ /dev/null @@ -1,32 +0,0 @@ -from cmind import utils -import os - - -def preprocess(i): - - print('') - print('Preprocessing ...') - - os_info = i['os_info'] - - env = i['env'] - - meta = i['meta'] - - automation = i['automation'] - - quiet = (env.get('MLC_QUIET', False) == 'yes') - - print(' ENV MLC_VAR1: {}'.format(env.get('MLC_VAR1', ''))) - - return {'return': 0} - - -def postprocess(i): - - print('') - print('Postprocessing ...') - - env = i['env'] - - return {'return': 0} diff --git a/automation/script/template-python/main.py b/automation/script/template-python/main.py deleted file mode 100644 index 68245e7bd..000000000 --- a/automation/script/template-python/main.py +++ /dev/null @@ -1,10 +0,0 @@ -import os - -if __name__ == "__main__": - - print('') - print('Main script:') - print('ENV MLC_VAR1: {}'.format(os.environ.get('MLC_VAR1', ''))) - print('') - - exit(0) diff --git a/automation/script/template-python/requirements.txt b/automation/script/template-python/requirements.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/automation/script/template-python/run.bat b/automation/script/template-python/run.bat deleted file mode 100644 index 11e897362..000000000 --- a/automation/script/template-python/run.bat +++ /dev/null @@ -1,25 +0,0 @@ -@echo off - -set CUR_DIR=%cd% - -echo. -echo Current execution path: %CUR_DIR% -echo Path to script: %MLC_TMP_CURRENT_SCRIPT_PATH% -echo ENV PIP_REQUIREMENTS: %PIP_REQUIREMENTS% -echo ENV MLC_VAR1: %MLC_VAR1% - -if "%PIP_REQUIREMENTS%" == "True" ( - if exist "%MLC_TMP_CURRENT_SCRIPT_PATH%\requirements.txt" ( - - echo. - echo Installing requirements.txt ... - echo. - - %MLC_PYTHON_BIN_WITH_PATH% -m pip install -r %MLC_TMP_CURRENT_SCRIPT_PATH%\requirements.txt - IF %ERRORLEVEL% NEQ 0 EXIT %ERRORLEVEL% - ) -) - -echo. -%MLC_PYTHON_BIN_WITH_PATH% %MLC_TMP_CURRENT_SCRIPT_PATH%\main.py -IF %ERRORLEVEL% NEQ 0 EXIT %ERRORLEVEL% diff --git a/automation/script/template-python/run.sh b/automation/script/template-python/run.sh deleted file mode 100644 index a3e2021b9..000000000 --- a/automation/script/template-python/run.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -CUR_DIR=${PWD} - -echo "" -echo "Current execution path: ${CUR_DIR}" -echo "Path to script: ${MLC_TMP_CURRENT_SCRIPT_PATH}" -echo "ENV PIP_REQUIREMENTS: ${PIP_REQUIREMENTS}" -echo "ENV MLC_VAR1: ${MLC_VAR1}" - -if [ "${PIP_REQUIREMENTS}" == "True" ]; then - if test -f "${MLC_TMP_CURRENT_SCRIPT_PATH}/requirements.txt"; then - echo "" - echo "Installing requirements.txt ..." - echo "" - - ${MLC_PYTHON_BIN_WITH_PATH} -m pip install -r ${MLC_TMP_CURRENT_SCRIPT_PATH}/requirements.txt - test $? -eq 0 || exit 1 - fi -fi - -echo "" -${MLC_PYTHON_BIN_WITH_PATH} ${MLC_TMP_CURRENT_SCRIPT_PATH}/main.py -test $? -eq 0 || exit 1 diff --git a/automation/script/template-pytorch/README-extra.md b/automation/script/template-pytorch/README-extra.md deleted file mode 100644 index 582991f6d..000000000 --- a/automation/script/template-pytorch/README-extra.md +++ /dev/null @@ -1 +0,0 @@ -# CM script diff --git a/automation/script/template-pytorch/_cm.yaml b/automation/script/template-pytorch/_cm.yaml deleted file mode 100644 index 22cd7a635..000000000 --- a/automation/script/template-pytorch/_cm.yaml +++ /dev/null @@ -1,42 +0,0 @@ -cache: false - -deps: - # Detect host OS features - - tags: detect,os - - # Detect/install python - - tags: get,python - names: - - python - - python3 - - - tags: get,generic-python-lib,_torch - skip_if_env: - USE_CUDA: - - yes - - - tags: get,generic-python-lib,_torch_cuda - enable_if_env: - USE_CUDA: - - yes - - - tags: get,generic-python-lib,_package.numpy - - -input_mapping: - var1: MLC_VAR1 - req: PIP_REQUIREMENTS - -default_env: - MLC_VAR1: 'something' - -variations: - req: - env: - PIP_REQUIREMENTS: True - - cuda: - env: - USE_CUDA: yes - deps: - - tags: get,cuda diff --git a/automation/script/template-pytorch/customize.py b/automation/script/template-pytorch/customize.py deleted file mode 100644 index 8961ab5ca..000000000 --- a/automation/script/template-pytorch/customize.py +++ /dev/null @@ -1,32 +0,0 @@ -from cmind import utils -import os - - -def preprocess(i): - - print('') - print('Preprocessing ...') - - os_info = i['os_info'] - - env = i['env'] - - meta = i['meta'] - - automation = i['automation'] - - quiet = (env.get('MLC_QUIET', False) == 'yes') - - print(' ENV MLC_VAR1: {}'.format(env.get('MLC_VAR1', ''))) - - return {'return': 0} - - -def postprocess(i): - - print('') - print('Postprocessing ...') - - env = i['env'] - - return {'return': 0} diff --git a/automation/script/template-pytorch/main.py b/automation/script/template-pytorch/main.py deleted file mode 100644 index 3bfcd7572..000000000 --- a/automation/script/template-pytorch/main.py +++ /dev/null @@ -1,15 +0,0 @@ -import os - -import torch - -if __name__ == "__main__": - - print('') - print('Main script:') - print('ENV MLC_VAR1: {}'.format(os.environ.get('MLC_VAR1', ''))) - print('ENV USE_CUDA: {}'.format(os.environ.get('USE_CUDA', ''))) - print('') - print('PyTorch version: {}'.format(torch.__version__)) - print('') - - exit(0) diff --git a/automation/script/template-pytorch/requirements.txt b/automation/script/template-pytorch/requirements.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/automation/script/template-pytorch/run.bat b/automation/script/template-pytorch/run.bat deleted file mode 100644 index 11e897362..000000000 --- a/automation/script/template-pytorch/run.bat +++ /dev/null @@ -1,25 +0,0 @@ -@echo off - -set CUR_DIR=%cd% - -echo. -echo Current execution path: %CUR_DIR% -echo Path to script: %MLC_TMP_CURRENT_SCRIPT_PATH% -echo ENV PIP_REQUIREMENTS: %PIP_REQUIREMENTS% -echo ENV MLC_VAR1: %MLC_VAR1% - -if "%PIP_REQUIREMENTS%" == "True" ( - if exist "%MLC_TMP_CURRENT_SCRIPT_PATH%\requirements.txt" ( - - echo. - echo Installing requirements.txt ... - echo. - - %MLC_PYTHON_BIN_WITH_PATH% -m pip install -r %MLC_TMP_CURRENT_SCRIPT_PATH%\requirements.txt - IF %ERRORLEVEL% NEQ 0 EXIT %ERRORLEVEL% - ) -) - -echo. -%MLC_PYTHON_BIN_WITH_PATH% %MLC_TMP_CURRENT_SCRIPT_PATH%\main.py -IF %ERRORLEVEL% NEQ 0 EXIT %ERRORLEVEL% diff --git a/automation/script/template-pytorch/run.sh b/automation/script/template-pytorch/run.sh deleted file mode 100644 index a3e2021b9..000000000 --- a/automation/script/template-pytorch/run.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -CUR_DIR=${PWD} - -echo "" -echo "Current execution path: ${CUR_DIR}" -echo "Path to script: ${MLC_TMP_CURRENT_SCRIPT_PATH}" -echo "ENV PIP_REQUIREMENTS: ${PIP_REQUIREMENTS}" -echo "ENV MLC_VAR1: ${MLC_VAR1}" - -if [ "${PIP_REQUIREMENTS}" == "True" ]; then - if test -f "${MLC_TMP_CURRENT_SCRIPT_PATH}/requirements.txt"; then - echo "" - echo "Installing requirements.txt ..." - echo "" - - ${MLC_PYTHON_BIN_WITH_PATH} -m pip install -r ${MLC_TMP_CURRENT_SCRIPT_PATH}/requirements.txt - test $? -eq 0 || exit 1 - fi -fi - -echo "" -${MLC_PYTHON_BIN_WITH_PATH} ${MLC_TMP_CURRENT_SCRIPT_PATH}/main.py -test $? -eq 0 || exit 1 diff --git a/automation/script/template/README-extra.md b/automation/script/template/README-extra.md deleted file mode 100644 index 582991f6d..000000000 --- a/automation/script/template/README-extra.md +++ /dev/null @@ -1 +0,0 @@ -# CM script diff --git a/automation/script/template/customize.py b/automation/script/template/customize.py deleted file mode 100644 index bd7c12dd3..000000000 --- a/automation/script/template/customize.py +++ /dev/null @@ -1,24 +0,0 @@ -from cmind import utils -import os - - -def preprocess(i): - - os_info = i['os_info'] - - env = i['env'] - - meta = i['meta'] - - automation = i['automation'] - - quiet = (env.get('MLC_QUIET', False) == 'yes') - - return {'return': 0} - - -def postprocess(i): - - env = i['env'] - - return {'return': 0} diff --git a/automation/script/template_list_of_scripts.md b/automation/script/template_list_of_scripts.md deleted file mode 100644 index 07fb95cb7..000000000 --- a/automation/script/template_list_of_scripts.md +++ /dev/null @@ -1,52 +0,0 @@ -[ [Back to index](README.md) ] - - - -This is an automatically generated list of portable and reusable automation recipes (CM scripts) -with a [human-friendly interface (CM)](https://github.com/mlcommons/ck) -to run a growing number of ad-hoc MLPerf, MLOps, and DevOps scripts -from [MLCommons projects](https://github.com/mlcommons/cm4mlops/tree/main/script) -and [research papers](https://www.youtube.com/watch?v=7zpeIVwICa4) -in a unified way on any operating system with any software and hardware -natively or inside containers. - -Click on any automation recipe below to learn how to run and reuse it -via CM command line, Python API or GUI. - -CM scripts can easily chained together into automation workflows using `deps` and `tags` keys -while automatically updating all environment variables and paths -for a given task and platform [using simple JSON or YAML](https://github.com/mlcommons/ck/blob/master/mlc-mlops/script/app-image-classification-onnx-py/_cm.yaml). - - -*Note that CM is a community project being developed and extended by [MLCommons members and individual contributors](../CONTRIBUTING.md) - - you can find source code of CM scripts maintained by MLCommons [here](../mlc-mlops/script). - Please join [Discord server](https://discord.gg/JjWNWXKxwT) to participate in collaborative developments or provide your feedback.* - - -# License - -[Apache 2.0](LICENSE.md) - - -# Copyright - -2022-2024 [MLCommons](https://mlcommons.org) - - - - - -# List of CM scripts by categories - -{{MLC_TOC_CATEGORIES}} - -{{MLC_TOC2}} - -# List of all sorted CM scripts - -{{MLC_TOC}} - - -{{MLC_MAIN}} diff --git a/automation/script/template-ae-python/customize.py b/automation/script/templates/default/customize.py similarity index 100% rename from automation/script/template-ae-python/customize.py rename to automation/script/templates/default/customize.py diff --git a/automation/script/template/run.bat b/automation/script/templates/default/run.bat similarity index 100% rename from automation/script/template/run.bat rename to automation/script/templates/default/run.bat diff --git a/automation/script/template/run.sh b/automation/script/templates/default/run.sh similarity index 100% rename from automation/script/template/run.sh rename to automation/script/templates/default/run.sh From 819953c113d9f419aaba9c622bf94fbb5ed5ce22 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Mon, 27 Jan 2025 00:09:58 +0000 Subject: [PATCH 12/36] Update test-nvidia-mlperf-inference-implementations.yml (#159) * Update test-nvidia-mlperf-inference-implementations.yml --- .../test-mlperf-inference-retinanet.yml | 33 ++++++++++++++----- ...vidia-mlperf-inference-implementations.yml | 2 +- script/get-gh-actions-runner/customize.py | 6 ++-- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test-mlperf-inference-retinanet.yml b/.github/workflows/test-mlperf-inference-retinanet.yml index 373eef9b4..c1777beae 100644 --- a/.github/workflows/test-mlperf-inference-retinanet.yml +++ b/.github/workflows/test-mlperf-inference-retinanet.yml @@ -52,16 +52,31 @@ jobs: if: matrix.os != 'windows-latest' run: | mlcr --tags=run,mlperf,inference,generate-run-cmds,_submission,_short --submitter="MLCommons" --pull_changes=yes --pull_inference_changes=yes --hw_name=gh_${{ matrix.os }}_x86 --model=retinanet --implementation=${{ matrix.implementation }} --backend=${{ matrix.backend }} --device=cpu --scenario=Offline --test_query_count=5 --quiet -v --target_qps=1 - - name: Randomly Execute Step - id: random-check + + # Step for Linux/MacOS + - name: Randomly Execute Step (Linux/MacOS) + if: runner.os != 'Windows' + run: | + RANDOM_NUMBER=$((RANDOM % 10)) + echo "Random number is $RANDOM_NUMBER" + if [ "$RANDOM_NUMBER" -eq 0 ]; then + echo "run_step=true" >> $GITHUB_ENV + else + echo "run_step=false" >> $GITHUB_ENV + fi + + # Step for Windows + - name: Randomly Execute Step (Windows) + if: runner.os == 'Windows' run: | - RANDOM_NUMBER=$((RANDOM % 10)) - echo "Random number is $RANDOM_NUMBER" - if [ "$RANDOM_NUMBER" -eq 0 ]; then - echo "run_step=true" >> $GITHUB_ENV - else - echo "run_step=false" >> $GITHUB_ENV - fi + $RANDOM_NUMBER = Get-Random -Maximum 10 + Write-Host "Random number is $RANDOM_NUMBER" + if ($RANDOM_NUMBER -eq 0) { + Write-Host "run_step=true" | Out-File -FilePath $Env:GITHUB_ENV -Append + } else { + Write-Host "run_step=false" | Out-File -FilePath $Env:GITHUB_ENV -Append + } + - name: Retrieve secrets from Keeper if: github.repository_owner == 'mlcommons' && env.run_step == 'true' id: ksecrets diff --git a/.github/workflows/test-nvidia-mlperf-inference-implementations.yml b/.github/workflows/test-nvidia-mlperf-inference-implementations.yml index d05079335..942455e73 100644 --- a/.github/workflows/test-nvidia-mlperf-inference-implementations.yml +++ b/.github/workflows/test-nvidia-mlperf-inference-implementations.yml @@ -2,7 +2,7 @@ name: MLPerf Inference Nvidia implementations on: schedule: - - cron: "25 23 * * *" #to be adjusted + - cron: "54 23 * * *" #to be adjusted jobs: run_nvidia: diff --git a/script/get-gh-actions-runner/customize.py b/script/get-gh-actions-runner/customize.py index 564065fb4..3b04e54dd 100644 --- a/script/get-gh-actions-runner/customize.py +++ b/script/get-gh-actions-runner/customize.py @@ -1,6 +1,5 @@ from mlc import utils import os -import cmind as cm def preprocess(i): @@ -12,6 +11,7 @@ def preprocess(i): meta = i['meta'] automation = i['automation'] + mlc = automation.action_runner quiet = (env.get('MLC_QUIET', False) == 'yes') @@ -25,8 +25,8 @@ def preprocess(i): elif cmd == "uninstall": run_cmd = f"cd {env['MLC_GH_ACTIONS_RUNNER_CODE_PATH']} && sudo ./svc.sh uninstall" cache_rm_tags = "gh,runner,_install" - r = cm.access({'action': 'rm', 'automation': 'cache', - 'tags': cache_rm_tags, 'f': True}) + r = mlc.access({'action': 'rm', 'automation': 'cache', + 'tags': cache_rm_tags, 'f': True}) print(r) if r['return'] != 0 and r['return'] != 16: # ignore missing ones return r From a4a380ee0cba879c7de49d1fe9c05b750ac61405 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Mon, 27 Jan 2025 00:12:05 +0000 Subject: [PATCH 13/36] Update build_wheel.yml --- .github/workflows/build_wheel.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index 867f93586..3718736a4 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -6,7 +6,6 @@ on: push: branches: - main - - dev paths: - VERSION From 97bca70eac52026c8678b95983ebd7c7c4aba5d5 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Mon, 27 Jan 2025 00:12:23 +0000 Subject: [PATCH 14/36] Update VERSION --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index bbdeab622..1750564f2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.5 +0.0.6 From 3a02626b69201d24ef25d6effaa11aa5e2b35f7b Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Mon, 27 Jan 2025 11:26:37 +0000 Subject: [PATCH 15/36] Update test-mlc-script-features.yml --- .github/workflows/test-mlc-script-features.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-mlc-script-features.yml b/.github/workflows/test-mlc-script-features.yml index 898512b7e..8c526bc0d 100644 --- a/.github/workflows/test-mlc-script-features.yml +++ b/.github/workflows/test-mlc-script-features.yml @@ -1,7 +1,7 @@ name: MLC script automation features test on: - pull_request: + pull_request_target: branches: [ "main", "dev" ] paths: - '.github/workflows/test-mlc-script-features.yml' From f8572f9decc1d0e7b941c738e74cbe2c6ea7fa96 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Mon, 27 Jan 2025 11:31:50 +0000 Subject: [PATCH 16/36] Fix getuser inside container (#160) * Use getpass to get container user * artifact -> item in cache meta * Parallelize the mlc-script-features test --- .../workflows/test-mlc-script-features.yml | 86 ++++++++++++- automation/script/docker_utils.py | 7 +- automation/script/module.py | 120 +++++++++--------- script/get-gh-actions-runner/customize.py | 2 +- script/get-gh-actions-runner/meta.yaml | 2 +- 5 files changed, 150 insertions(+), 67 deletions(-) diff --git a/.github/workflows/test-mlc-script-features.yml b/.github/workflows/test-mlc-script-features.yml index 8c526bc0d..d2928e37f 100644 --- a/.github/workflows/test-mlc-script-features.yml +++ b/.github/workflows/test-mlc-script-features.yml @@ -61,18 +61,51 @@ jobs: mlcr --tags=python,src,install,_shared --version=3.9.10 --quiet mlc search cache --tags=python,src,install,_shared,version-3.9.10 + test_docker: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.12", "3.8"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Pull MLOps repository + run: | + pip install mlcflow + mlc pull repo ${{ github.event.pull_request.head.repo.html_url }} --branch=${{ github.event.pull_request.head.ref }} + - name: Run docker container from dockerhub on linux - if: runner.os == 'linux' run: | mlcr --tags=run,docker,container --adr.compiler.tags=gcc --docker_mlc_repo=mlcommons@mlperf-automations --docker_mlc_repo_branch=dev --image_name=cm-script-app-image-classification-onnx-py --env.MLC_DOCKER_RUN_SCRIPT_TAGS=app,image-classification,onnx,python --env.MLC_DOCKER_IMAGE_BASE=ubuntu:22.04 --env.MLC_DOCKER_IMAGE_REPO=cknowledge --quiet - name: Run docker container locally on linux - if: runner.os == 'linux' run: | mlcr --tags=run,docker,container --adr.compiler.tags=gcc --docker_mlc_repo=mlcommons@mlperf-automations --docker_mlc_repo_branch=dev --image_name=mlc-script-app-image-classification-onnx-py --env.MLC_DOCKER_RUN_SCRIPT_TAGS=app,image-classification,onnx,python --env.MLC_DOCKER_IMAGE_BASE=ubuntu:22.04 --env.MLC_DOCKER_IMAGE_REPO=local --quiet + test_mlperf_retinanet_cpp_venv: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.12", "3.8"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Pull MLOps repository + run: | + pip install mlcflow + mlc pull repo ${{ github.event.pull_request.head.repo.html_url }} --branch=${{ github.event.pull_request.head.ref }} + - name: Run MLPerf Inference Retinanet with native and virtual Python - if: runner.os == 'linux' run: | mlcr --tags=app,mlperf,inference,generic,_cpp,_retinanet,_onnxruntime,_cpu --adr.python.version_min=3.8 --adr.compiler.tags=gcc --adr.openimages-preprocessed.tags=_50 --scenario=Offline --mode=accuracy --test_query_count=10 --rerun --quiet @@ -80,4 +113,49 @@ jobs: mlcr --tags=install,python-venv --version=3.10.8 --name=mlperf --quiet - mlcr --tags=run,mlperf,inference,generate-run-cmds,_submission,_short --adr.python.name=mlperf --adr.python.version_min=3.8 --adr.compiler.tags=gcc --adr.openimages-preprocessed.tags=_50 --submitter=Community --implementation=cpp --hw_name=default --model=retinanet --backend=onnxruntime --device=cpu --scenario=Offline --quiet + mlcr --tags=run,mlperf,inference,_submission,_short --adr.python.name=mlperf --adr.python.version_min=3.8 --adr.compiler.tags=gcc --adr.openimages-preprocessed.tags=_50 --submitter=MLCommons --implementation=cpp --hw_name=default --model=retinanet --backend=onnxruntime --device=cpu --scenario=Offline --quiet + + # Step for Linux/MacOS + - name: Randomly Execute Step (Linux/MacOS) + if: runner.os != 'Windows' + run: | + RANDOM_NUMBER=$((RANDOM % 10)) + echo "Random number is $RANDOM_NUMBER" + if [ "$RANDOM_NUMBER" -eq 0 ]; then + echo "run_step=true" >> $GITHUB_ENV + else + echo "run_step=false" >> $GITHUB_ENV + fi + + # Step for Windows + - name: Randomly Execute Step (Windows) + if: runner.os == 'Windows' + run: | + $RANDOM_NUMBER = Get-Random -Maximum 10 + Write-Host "Random number is $RANDOM_NUMBER" + if ($RANDOM_NUMBER -eq 0) { + Write-Host "run_step=true" | Out-File -FilePath $Env:GITHUB_ENV -Append + } else { + Write-Host "run_step=false" | Out-File -FilePath $Env:GITHUB_ENV -Append + } + + - name: Retrieve secrets from Keeper + if: github.repository_owner == 'mlcommons' && env.run_step == 'true' + id: ksecrets + uses: Keeper-Security/ksm-action@master + with: + keeper-secret-config: ${{ secrets.KSM_CONFIG }} + secrets: |- + ubwkjh-Ii8UJDpG2EoU6GQ/field/Access Token > env:PAT + - name: Push Results + env: + GITHUB_TOKEN: ${{ env.PAT }} + if: github.repository_owner == 'mlcommons' && env.run_step == 'true' + run: | + git config --global user.name "mlcommons-bot" + git config --global user.email "mlcommons-bot@users.noreply.github.com" + git config --global credential.https://github.com.helper "" + git config --global credential.https://github.com.helper "!gh auth git-credential" + git config --global credential.https://gist.github.com.helper "" + git config --global credential.https://gist.github.com.helper "!gh auth git-credential" + mlcr --tags=push,github,mlperf,inference,submission --repo_url=https://github.com/mlcommons/mlperf_inference_test_submissions_v5.0 --repo_branch=auto-update --commit_message="Results from R50 GH action on ${{ matrix.os }}" --quiet diff --git a/automation/script/docker_utils.py b/automation/script/docker_utils.py index 04d7c388d..3061b3096 100644 --- a/automation/script/docker_utils.py +++ b/automation/script/docker_utils.py @@ -398,9 +398,14 @@ def get_host_path(value): def get_container_path_script(i): + import getpass + cur_user = getpass.getuser() + if not cur_user or cur_user == '': + cur_user = os.environ.get('USER', 'mlcuser') + tmp_dep_cached_path = i['tmp_dep_cached_path'] value_mnt, value_env = get_container_path( - tmp_dep_cached_path, os.getlogin()) + tmp_dep_cached_path, cur_user) return {'return': 0, 'value_mnt': value_mnt, 'value_env': value_env} diff --git a/automation/script/module.py b/automation/script/module.py index 868178f49..8f3d82e97 100644 --- a/automation/script/module.py +++ b/automation/script/module.py @@ -708,21 +708,21 @@ def _run(self, i): for cache_entry in cache_list: # Find associated script and add to the # list_of_found_scripts - associated_script_artifact = cache_entry.meta['associated_script_artifact'] + associated_script_item = cache_entry.meta['associated_script_item'] - x = associated_script_artifact.find(',') + x = associated_script_item.find(',') if x < 0: return {'return': 1, 'error': 'MLC artifact format is wrong "{}" - no comma found'.format( - associated_script_artifact)} + associated_script_item)} - associated_script_artifact_uid = associated_script_artifact[x + 1:] + associated_script_item_uid = associated_script_item[x + 1:] - cache_entry.meta['associated_script_artifact_uid'] = associated_script_artifact_uid + cache_entry.meta['associated_script_item_uid'] = associated_script_item_uid for script in list_of_found_scripts: script_uid = script.meta['uid'] - if associated_script_artifact_uid == script_uid: + if associated_script_item_uid == script_uid: if script not in new_list_of_found_scripts: new_list_of_found_scripts.append(script) @@ -733,7 +733,7 @@ def _run(self, i): # Select scripts if len(list_of_found_scripts) > 1: - select_script = select_script_artifact( + select_script = select_script_item( list_of_found_scripts, 'script', recursion_spaces, @@ -752,23 +752,23 @@ def _run(self, i): # Prune cache list with the selected script if len(list_of_found_scripts) > 0: - script_artifact_uid = list_of_found_scripts[select_script].meta['uid'] + script_item_uid = list_of_found_scripts[select_script].meta['uid'] new_cache_list = [] for cache_entry in cache_list: - if cache_entry.meta['associated_script_artifact_uid'] == script_artifact_uid: + if cache_entry.meta['associated_script_item_uid'] == script_item_uid: new_cache_list.append(cache_entry) cache_list = new_cache_list # Here a specific script is found and meta obtained # Set some useful local variables - script_artifact = list_of_found_scripts[select_script] + script_item = list_of_found_scripts[select_script] # print(list_of_found_scripts) - meta = script_artifact.meta + meta = script_item.meta # print(meta) - path = script_artifact.path + path = script_item.path # Check min MLC version requirement min_mlc_version = meta.get('min_mlc_version', '').strip() @@ -785,12 +785,12 @@ def _run(self, i): error = format(e) # Check path to repo - script_repo_path = script_artifact.repo.path + script_repo_path = script_item.repo.path - script_repo_path_with_prefix = script_artifact.repo.path - if script_artifact.repo.meta.get('prefix', '') != '': + script_repo_path_with_prefix = script_item.repo.path + if script_item.repo.meta.get('prefix', '') != '': script_repo_path_with_prefix = os.path.join( - script_repo_path, script_artifact.repo.meta['prefix']) + script_repo_path, script_item.repo.meta['prefix']) env['MLC_TMP_CURRENT_SCRIPT_REPO_PATH'] = script_repo_path env['MLC_TMP_CURRENT_SCRIPT_REPO_PATH_WITH_PREFIX'] = script_repo_path_with_prefix @@ -803,18 +803,18 @@ def _run(self, i): run_state['script_id'] = meta['alias'] + "," + meta['uid'] run_state['script_tags'] = script_tags run_state['script_variation_tags'] = variation_tags - run_state['script_repo_alias'] = script_artifact.repo.meta.get( + run_state['script_repo_alias'] = script_item.repo.meta.get( 'alias', '') - run_state['script_repo_git'] = script_artifact.repo.meta.get( + run_state['script_repo_git'] = script_item.repo.meta.get( 'git', False) run_state['cache'] = meta.get('cache', False) if not recursion: run_state['script_entry_repo_to_report_errors'] = meta.get( 'repo_to_report_errors', '') - run_state['script_entry_repo_alias'] = script_artifact.repo.meta.get( + run_state['script_entry_repo_alias'] = script_item.repo.meta.get( 'alias', '') - run_state['script_entry_repo_git'] = script_artifact.repo.meta.get( + run_state['script_entry_repo_git'] = script_item.repo.meta.get( 'git', False) deps = meta.get('deps', []) @@ -826,7 +826,7 @@ def _run(self, i): new_env_keys_from_meta = meta.get('new_env_keys', []) new_state_keys_from_meta = meta.get('new_state_keys', []) - found_script_artifact = utils.assemble_object( + found_script_item = utils.assemble_object( meta['alias'], meta['uid']) found_script_tags = meta.get('tags', []) @@ -835,9 +835,9 @@ def _run(self, i): debug_script_tags = ','.join(found_script_tags) logging.debug(recursion_spaces + - ' - Found script::{} in {}'.format(found_script_artifact, path)) + ' - Found script::{} in {}'.format(found_script_item, path)) - # STEP 500 output: script_artifact - unique selected script artifact + # STEP 500 output: script_item - unique selected script artifact # (cache_list) pruned for the unique script if cache is used # meta - script meta # path - script path @@ -848,21 +848,21 @@ def _run(self, i): # STEP 600: Continue updating env # Add default env from meta to new env if not empty # (env NO OVERWRITE) - script_artifact_default_env = meta.get('default_env', {}) - for key in script_artifact_default_env: - env.setdefault(key, script_artifact_default_env[key]) + script_item_default_env = meta.get('default_env', {}) + for key in script_item_default_env: + env.setdefault(key, script_item_default_env[key]) # Force env from meta['env'] as a CONST # (env OVERWRITE) - script_artifact_env = meta.get('env', {}) - # print(f"script meta env= {script_artifact_env}") + script_item_env = meta.get('env', {}) + # print(f"script meta env= {script_item_env}") - env.update(script_artifact_env) + env.update(script_item_env) # print(f"env = {env}") - script_artifact_state = meta.get('state', {}) + script_item_state = meta.get('state', {}) utils.merge_dicts({'dict1': state, - 'dict2': script_artifact_state, + 'dict2': script_item_state, 'append_lists': True, 'append_unique': True}) @@ -901,7 +901,7 @@ def _run(self, i): # VARIATIONS OVERWRITE current ENV but not input keys (they become # const) - variations = script_artifact.meta.get('variations', {}) + variations = script_item.meta.get('variations', {}) state['docker'] = meta.get('docker', {}) r = self._update_state_from_variations( @@ -998,7 +998,7 @@ def _run(self, i): # STEP 1000: Update version only if in "versions" (not obligatory) # can be useful when handling complex Git revisions - versions = script_artifact.meta.get('versions', {}) + versions = script_item.meta.get('versions', {}) if version != '' and version in versions: versions_meta = versions[version] @@ -1048,7 +1048,7 @@ def _run(self, i): ).lower() in ['false', '0', 'no']: logging.info( recursion_spaces + - ' - Skipping script::{} run as we are inside docker'.format(found_script_artifact)) + ' - Skipping script::{} run as we are inside docker'.format(found_script_item)) # restore env and state for k in list(env.keys()): @@ -1071,7 +1071,7 @@ def _run(self, i): elif str(state['docker'].get('real_run', True)).lower() in ['false', '0', 'no']: logging.info( recursion_spaces + - ' - Doing fake run for script::{} as we are inside docker'.format(found_script_artifact)) + ' - Doing fake run for script::{} as we are inside docker'.format(found_script_item)) fake_run = True env['MLC_TMP_FAKE_RUN'] = 'yes' @@ -1146,8 +1146,8 @@ def _run(self, i): customize_common_input = { 'input': i, 'automation': self, - 'artifact': script_artifact, - 'customize': script_artifact.meta.get('customize', {}), + 'artifact': script_item, + 'customize': script_item.meta.get('customize', {}), 'os_info': os_info, 'recursion_spaces': recursion_spaces, 'script_tags': script_tags, @@ -1216,7 +1216,7 @@ def _run(self, i): num_found_cached_scripts = 1 if num_found_cached_scripts > 1: - selection = select_script_artifact( + selection = select_script_item( found_cached_scripts, 'cached script output', recursion_spaces, @@ -1353,7 +1353,7 @@ def _run(self, i): if renew or (not found_cached and num_found_cached_scripts == 0): # Add more tags to cached tags # based on meta information of the found script - x = 'script-artifact-' + meta['uid'] + x = 'script-item-' + meta['uid'] if x not in cached_tags: cached_tags.append(x) @@ -1608,8 +1608,8 @@ def _run(self, i): customize_common_input = { 'input': i, 'automation': self, - 'artifact': script_artifact, - 'customize': script_artifact.meta.get('customize', {}), + 'artifact': script_item, + 'customize': script_item.meta.get('customize', {}), 'os_info': os_info, 'recursion_spaces': recursion_spaces, 'script_tags': script_tags, @@ -1710,8 +1710,8 @@ def _run(self, i): customize_common_input = { 'input': i, 'automation': self, - 'artifact': script_artifact, - 'customize': script_artifact.meta.get('customize', {}), + 'artifact': script_item, + 'customize': script_item.meta.get('customize', {}), 'os_info': os_info, 'recursion_spaces': recursion_spaces, 'script_tags': script_tags, @@ -2023,15 +2023,15 @@ def _run(self, i): if detected_version != '': cached_meta['version'] = detected_version - if found_script_artifact != '': - cached_meta['associated_script_artifact'] = found_script_artifact + if found_script_item != '': + cached_meta['associated_script_item'] = found_script_item - x = found_script_artifact.find(',') + x = found_script_item.find(',') if x < 0: return { - 'return': 1, 'error': 'MLC artifact format is wrong "{}" - no comma found'.format(found_script_artifact)} + 'return': 1, 'error': 'MLC artifact format is wrong "{}" - no comma found'.format(found_script_item)} - cached_meta['associated_script_artifact_uid'] = found_script_artifact[x + 1:] + cached_meta['associated_script_item_uid'] = found_script_item[x + 1:] # Check if the cached entry is dependent on any path if dependent_cached_path != '': @@ -2124,11 +2124,11 @@ def _run(self, i): # to aggregate all resolved versions and dump them at the end # if requested (for better reproducibility/replicability) - script_uid = script_artifact.meta.get('uid') - script_alias = script_artifact.meta.get('alias') + script_uid = script_item.meta.get('uid') + script_alias = script_item.meta.get('alias') # we should use user-friendly tags here - # script_tags = script_artifact.meta.get('tags') + # script_tags = script_item.meta.get('tags') version_info_tags = ",".join(script_tags) @@ -2830,8 +2830,8 @@ def search(self, i): if found_scripts and len(variation_tags) > 0: filtered = [] - for script_artifact in lst: - meta = script_artifact.meta + for script_item in lst: + meta = script_item.meta variations = meta.get('variations', {}) matched = True @@ -2851,7 +2851,7 @@ def search(self, i): if not matched: continue - filtered.append(script_artifact) + filtered.append(script_item) if len(lst) > 0 and not filtered: warning = [""] @@ -2925,10 +2925,10 @@ def test(self, i): return r lst = r['list'] - for script_artifact in lst: - path = script_artifact.path - meta = script_artifact.meta - original_meta = script_artifact.original_meta + for script_item in lst: + path = script_item.path + meta = script_item.meta + original_meta = script_item.original_meta alias = meta.get('alias', '') uid = meta.get('uid', '') @@ -6153,8 +6153,8 @@ def detect_state_diff(env, saved_env, new_env_keys, ############################################################################## -def select_script_artifact(lst, text, recursion_spaces, - can_skip, script_tags_string, quiet, verbose): +def select_script_item(lst, text, recursion_spaces, + can_skip, script_tags_string, quiet, verbose): """ Internal: select script """ diff --git a/script/get-gh-actions-runner/customize.py b/script/get-gh-actions-runner/customize.py index 3b04e54dd..360308e91 100644 --- a/script/get-gh-actions-runner/customize.py +++ b/script/get-gh-actions-runner/customize.py @@ -11,7 +11,7 @@ def preprocess(i): meta = i['meta'] automation = i['automation'] - mlc = automation.action_runner + mlc = automation.action_object quiet = (env.get('MLC_QUIET', False) == 'yes') diff --git a/script/get-gh-actions-runner/meta.yaml b/script/get-gh-actions-runner/meta.yaml index 67eabf7fb..dbd4a299b 100644 --- a/script/get-gh-actions-runner/meta.yaml +++ b/script/get-gh-actions-runner/meta.yaml @@ -31,7 +31,6 @@ deps: variations: config: group: command - default: true env: MLC_GH_ACTIONS_RUNNER_COMMAND: config remove: @@ -51,6 +50,7 @@ variations: MLC_GH_ACTIONS_RUNNER_COMMAND: uninstall start: group: command + default: true deps: - tags: get,gh,actions-runner,_install force_cache: yes From 29eddc1be4f2160fdcd2cc3854af4392a322ab4d Mon Sep 17 00:00:00 2001 From: ANANDHU S <71482562+anandhu-eng@users.noreply.github.com> Date: Mon, 27 Jan 2025 17:22:56 +0530 Subject: [PATCH 17/36] Dataset and model scripts for automotive reference implementation (#161) * add dataset and model download scripts automotive ref --- script/get-dataset-waymo/COPYRIGHT.md | 9 +++++ script/get-dataset-waymo/customize.py | 28 ++++++++++++++++ script/get-dataset-waymo/meta.yaml | 19 +++++++++++ script/get-dataset-waymo/run.sh | 8 +++++ script/get-ml-model-pointpillars/COPYRIGHT.md | 9 +++++ script/get-ml-model-pointpillars/customize.py | 32 ++++++++++++++++++ script/get-ml-model-pointpillars/meta.yaml | 26 +++++++++++++++ script/get-ml-model-pointpillars/run.sh | 8 +++++ .../COPYRIGHT.md | 9 +++++ .../customize.py | 33 +++++++++++++++++++ .../get-ml-model-resnet50-deeplab/meta.yaml | 27 +++++++++++++++ script/get-ml-model-resnet50-deeplab/run.sh | 8 +++++ 12 files changed, 216 insertions(+) create mode 100644 script/get-dataset-waymo/COPYRIGHT.md create mode 100644 script/get-dataset-waymo/customize.py create mode 100644 script/get-dataset-waymo/meta.yaml create mode 100644 script/get-dataset-waymo/run.sh create mode 100644 script/get-ml-model-pointpillars/COPYRIGHT.md create mode 100644 script/get-ml-model-pointpillars/customize.py create mode 100644 script/get-ml-model-pointpillars/meta.yaml create mode 100644 script/get-ml-model-pointpillars/run.sh create mode 100644 script/get-ml-model-resnet50-deeplab/COPYRIGHT.md create mode 100644 script/get-ml-model-resnet50-deeplab/customize.py create mode 100644 script/get-ml-model-resnet50-deeplab/meta.yaml create mode 100644 script/get-ml-model-resnet50-deeplab/run.sh diff --git a/script/get-dataset-waymo/COPYRIGHT.md b/script/get-dataset-waymo/COPYRIGHT.md new file mode 100644 index 000000000..d2ceead84 --- /dev/null +++ b/script/get-dataset-waymo/COPYRIGHT.md @@ -0,0 +1,9 @@ +# Copyright Notice + +© 2025-2026 MLCommons. All Rights Reserved. + +This file is licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License can be obtained at: + +[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + +Unless required by applicable law or agreed to in writing, software distributed under the License is provided on an "AS IS" basis, without warranties or conditions of any kind, either express or implied. Please refer to the License for the specific language governing permissions and limitations under the License. diff --git a/script/get-dataset-waymo/customize.py b/script/get-dataset-waymo/customize.py new file mode 100644 index 000000000..273feef06 --- /dev/null +++ b/script/get-dataset-waymo/customize.py @@ -0,0 +1,28 @@ +from mlc import utils +import os + + +def preprocess(i): + + os_info = i['os_info'] + + env = i['env'] + + if os_info['platform'] == "windows": + return {'return': 1, 'error': 'Script not supported in windows yet!'} + + if env.get('MLC_DATASET_WAYMO_PATH', '') == '': + return {'return': 1, 'error': 'Please provide path to kitti dataset using tag \\`--waymo_path\\`as automatic download of this dataset is not supported yet.'} + + if not os.path.exists(env['MLC_DATASET_WAYMO_PATH']): + return { + 'return': 1, 'error': f"Path {env['MLC_DATASET_WAYMO_PATH']} does not exists!"} + + return {'return': 0} + + +def postprocess(i): + + env = i['env'] + + return {'return': 0} diff --git a/script/get-dataset-waymo/meta.yaml b/script/get-dataset-waymo/meta.yaml new file mode 100644 index 000000000..bfbba995f --- /dev/null +++ b/script/get-dataset-waymo/meta.yaml @@ -0,0 +1,19 @@ +alias: get-dataset-waymo +automation_alias: script +automation_uid: 5b4e0237da074764 +cache: true +tags: +- get +- dataset +- waymo +uid: 21b269c753b64437 +new_env_keys: + - MLC_DATASET_WAYMO_PATH +input_mapping: + waymo_path: MLC_DATASET_WAYMO_PATH +variations: + kitti_format: + default: true + group: dataset-format + env: + MLC_DATASET_WAYMO_FORMAT: kitti diff --git a/script/get-dataset-waymo/run.sh b/script/get-dataset-waymo/run.sh new file mode 100644 index 000000000..3197bb8ad --- /dev/null +++ b/script/get-dataset-waymo/run.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +#CM Script location: ${MLC_TMP_CURRENT_SCRIPT_PATH} + +#To export any variable +#echo "VARIABLE_NAME=VARIABLE_VALUE" >>tmp-run-env.out + +#${MLC_PYTHON_BIN_WITH_PATH} contains the path to python binary if "get,python" is added as a dependency diff --git a/script/get-ml-model-pointpillars/COPYRIGHT.md b/script/get-ml-model-pointpillars/COPYRIGHT.md new file mode 100644 index 000000000..d2ceead84 --- /dev/null +++ b/script/get-ml-model-pointpillars/COPYRIGHT.md @@ -0,0 +1,9 @@ +# Copyright Notice + +© 2025-2026 MLCommons. All Rights Reserved. + +This file is licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License can be obtained at: + +[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + +Unless required by applicable law or agreed to in writing, software distributed under the License is provided on an "AS IS" basis, without warranties or conditions of any kind, either express or implied. Please refer to the License for the specific language governing permissions and limitations under the License. diff --git a/script/get-ml-model-pointpillars/customize.py b/script/get-ml-model-pointpillars/customize.py new file mode 100644 index 000000000..b6685c889 --- /dev/null +++ b/script/get-ml-model-pointpillars/customize.py @@ -0,0 +1,32 @@ +from mlc import utils +import os + + +def preprocess(i): + + os_info = i['os_info'] + + env = i['env'] + + if os_info['platform'] == "windows": + return {'return': 1, 'error': 'Script not supported in windows yet!'} + + if env.get('MLC_ML_MODEL_POINT_PILLARS_PATH', '') == '': + return {'return': 1, 'error': 'Please provide path to pointpillars model using tag \\`--pp_path\\`as automatic download of this model is not supported yet.'} + + if os.path.isdir(env['MLC_ML_MODEL_POINT_PILLARS_PATH']): + if env['MLC_ML_MODEL_PP_FORMAT'] == "onnx": + env['MLC_ML_MODEL_POINT_PILLARS_PATH'] = os.path.join( + env['MLC_ML_MODEL_POINT_PILLARS_PATH'], "pp.onnx") + else: + env['MLC_ML_MODEL_POINT_PILLARS_PATH'] = os.path.join( + env['MLC_ML_MODEL_POINT_PILLARS_PATH'], "pp_ep36.pth") + + return {'return': 0} + + +def postprocess(i): + + env = i['env'] + + return {'return': 0} diff --git a/script/get-ml-model-pointpillars/meta.yaml b/script/get-ml-model-pointpillars/meta.yaml new file mode 100644 index 000000000..18470e4c0 --- /dev/null +++ b/script/get-ml-model-pointpillars/meta.yaml @@ -0,0 +1,26 @@ +alias: get-ml-model-pointpillars +automation_alias: script +automation_uid: 5b4e0237da074764 +cache: true +tags: +- get +- ml-model +- ml +- model +- pointpillars +uid: 3562621a8994411d +new_env_keys: + - MLC_ML_MODEL_POINT_PILLARS_PATH +input_mapping: + pp_path: MLC_ML_MODEL_POINT_PILLARS_PATH +variations: + gpu: + default: true + group: device + env: + MLC_ML_MODEL_PP_FORMAT: pth + cpu: + group: device + env: + MLC_ML_MODEL_PP_FORMAT: onnx + diff --git a/script/get-ml-model-pointpillars/run.sh b/script/get-ml-model-pointpillars/run.sh new file mode 100644 index 000000000..3197bb8ad --- /dev/null +++ b/script/get-ml-model-pointpillars/run.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +#CM Script location: ${MLC_TMP_CURRENT_SCRIPT_PATH} + +#To export any variable +#echo "VARIABLE_NAME=VARIABLE_VALUE" >>tmp-run-env.out + +#${MLC_PYTHON_BIN_WITH_PATH} contains the path to python binary if "get,python" is added as a dependency diff --git a/script/get-ml-model-resnet50-deeplab/COPYRIGHT.md b/script/get-ml-model-resnet50-deeplab/COPYRIGHT.md new file mode 100644 index 000000000..d2ceead84 --- /dev/null +++ b/script/get-ml-model-resnet50-deeplab/COPYRIGHT.md @@ -0,0 +1,9 @@ +# Copyright Notice + +© 2025-2026 MLCommons. All Rights Reserved. + +This file is licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License can be obtained at: + +[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + +Unless required by applicable law or agreed to in writing, software distributed under the License is provided on an "AS IS" basis, without warranties or conditions of any kind, either express or implied. Please refer to the License for the specific language governing permissions and limitations under the License. diff --git a/script/get-ml-model-resnet50-deeplab/customize.py b/script/get-ml-model-resnet50-deeplab/customize.py new file mode 100644 index 000000000..0df3b1c3f --- /dev/null +++ b/script/get-ml-model-resnet50-deeplab/customize.py @@ -0,0 +1,33 @@ +from mlc import utils +import os + + +def preprocess(i): + + os_info = i['os_info'] + + env = i['env'] + + if os_info['platform'] == "windows": + return {'return': 1, 'error': 'Script not supported in windows yet!'} + + if env.get('MLC_ML_MODEL_DPLAB_RESNET50_PATH', '') == '': + return {'return': 1, 'error': 'Please provide path to deeplab resnet 50 model using tag \\`--dp_resnet50_path\\`as automatic download of this dataset is not supported yet.'} + + if os.path.isdir(env['MLC_ML_MODEL_DPLAB_RESNET50_PATH']): + if env['MLC_ML_MODEL_DPLAB_RESNET50_FORMAT'] == "onnx": + env['MLC_ML_MODEL_DPLAB_RESNET50_PATH'] = os.path.join( + env['MLC_ML_MODEL_DPLAB_RESNET50_PATH'], "deeplabv3+.onnx") + else: + env['MLC_ML_MODEL_DPLAB_RESNET50_PATH'] = os.path.join( + env['MLC_ML_MODEL_DPLAB_RESNET50_PATH'], + "best_deeplabv3plus_resnet50_waymo_os16.pth") + + return {'return': 0} + + +def postprocess(i): + + env = i['env'] + + return {'return': 0} diff --git a/script/get-ml-model-resnet50-deeplab/meta.yaml b/script/get-ml-model-resnet50-deeplab/meta.yaml new file mode 100644 index 000000000..c8c8b84e1 --- /dev/null +++ b/script/get-ml-model-resnet50-deeplab/meta.yaml @@ -0,0 +1,27 @@ +alias: get-dataset-deeplab-resnet50 +automation_alias: script +automation_uid: 5b4e0237da074764 +cache: true +tags: +- get +- ml-model +- ml +- model +- resnet50-deeplab +- resnet50 +- deeplab +uid: 93097b691a6a4fce +new_env_keys: + - MLC_ML_MODEL_DPLAB_RESNET50_PATH +input_mapping: + dp_resnet50_path: MLC_ML_MODEL_DPLAB_RESNET50_PATH +variations: + gpu: + default: true + group: device + env: + MLC_ML_MODEL_DPLAB_RESNET50_FORMAT: pth + cpu: + group: device + env: + MLC_ML_MODEL_DPLAB_RESNET50_FORMAT: onnx diff --git a/script/get-ml-model-resnet50-deeplab/run.sh b/script/get-ml-model-resnet50-deeplab/run.sh new file mode 100644 index 000000000..3197bb8ad --- /dev/null +++ b/script/get-ml-model-resnet50-deeplab/run.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +#CM Script location: ${MLC_TMP_CURRENT_SCRIPT_PATH} + +#To export any variable +#echo "VARIABLE_NAME=VARIABLE_VALUE" >>tmp-run-env.out + +#${MLC_PYTHON_BIN_WITH_PATH} contains the path to python binary if "get,python" is added as a dependency From c2abbc700961ed4e3f1aa988adca28ee4f611217 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Mon, 27 Jan 2025 12:33:20 +0000 Subject: [PATCH 18/36] Update VERSION --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 1750564f2..5a5831ab6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.6 +0.0.7 From 4b39d5497ad93c3f5074a2903e0c14fdd22568df Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Mon, 27 Jan 2025 12:34:08 +0000 Subject: [PATCH 19/36] Update build_wheel.yml --- .github/workflows/build_wheel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index 3718736a4..fe22fc33b 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -5,7 +5,7 @@ on: types: [published] push: branches: - - main + - dev paths: - VERSION From f0d5b3034813a391cb5da676b2a57e16317b6824 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Mon, 27 Jan 2025 12:35:14 +0000 Subject: [PATCH 20/36] Update VERSION --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 5a5831ab6..d169b2f2d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.7 +0.0.8 From 9291850c9eefbc09170f221342beed3bf4e0ed9c Mon Sep 17 00:00:00 2001 From: mlcommons-bot Date: Mon, 27 Jan 2025 12:35:27 +0000 Subject: [PATCH 21/36] Updated git_commit_hash.txt --- git_commit_hash.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git_commit_hash.txt b/git_commit_hash.txt index 2c81389ee..eb452456e 100644 --- a/git_commit_hash.txt +++ b/git_commit_hash.txt @@ -1 +1 @@ -d65285819cd8a80baa326fc453297c64d85caade +f0d5b3034813a391cb5da676b2a57e16317b6824 From 3a8dad38018dbfcc974715694b021c1af66ef1ef Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Mon, 27 Jan 2025 12:44:23 +0000 Subject: [PATCH 22/36] Update pyproject.toml --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7c1b4e8a1..aeeba2ae3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "mlc-scripts" -version="0.0.5" +dynamic = ["version"] description = "Automation scripts for running ML applications using MLC interface" authors = [ { name = "MLCommons", email = "systems@mlcommons.org" } @@ -36,5 +36,8 @@ Issues = "https://github.com/mlcommons/mlperf-automations/issues" packages = [] include-package-data = true +[tool.setuptools.dynamic] +version = {file = "VERSION"} + [tool.setuptools.package-data] "mlcr" = ["README.md", "VERSION", "git_commit_hash.txt"] From 90fbda0c7079a0a09688aa2045d2f571ef61c222 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Mon, 27 Jan 2025 12:44:41 +0000 Subject: [PATCH 23/36] Update VERSION --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index d169b2f2d..1750564f2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.8 +0.0.6 From 606c2b68aa37edbc59d3a6c8a480c2e32e35de41 Mon Sep 17 00:00:00 2001 From: mlcommons-bot Date: Mon, 27 Jan 2025 12:44:55 +0000 Subject: [PATCH 24/36] Updated git_commit_hash.txt --- git_commit_hash.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git_commit_hash.txt b/git_commit_hash.txt index eb452456e..d5693f79a 100644 --- a/git_commit_hash.txt +++ b/git_commit_hash.txt @@ -1 +1 @@ -f0d5b3034813a391cb5da676b2a57e16317b6824 +90fbda0c7079a0a09688aa2045d2f571ef61c222 From 3657548d8220a2a89f32cf8b07a7a41cd36659cc Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Mon, 27 Jan 2025 18:10:46 +0000 Subject: [PATCH 25/36] Use search and not find in script module (#162) --- ...vidia-mlperf-inference-implementations.yml | 2 +- automation/script/module.py | 23 ++++--------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/.github/workflows/test-nvidia-mlperf-inference-implementations.yml b/.github/workflows/test-nvidia-mlperf-inference-implementations.yml index 942455e73..18f7b7ee4 100644 --- a/.github/workflows/test-nvidia-mlperf-inference-implementations.yml +++ b/.github/workflows/test-nvidia-mlperf-inference-implementations.yml @@ -2,7 +2,7 @@ name: MLPerf Inference Nvidia implementations on: schedule: - - cron: "54 23 * * *" #to be adjusted + - cron: "50 17 * * *" #to be adjusted jobs: run_nvidia: diff --git a/automation/script/module.py b/automation/script/module.py index 8f3d82e97..2d256838c 100644 --- a/automation/script/module.py +++ b/automation/script/module.py @@ -257,19 +257,6 @@ def _run(self, i): return { 'return': 1, 'error': 'Current directory "{}" is not writable - please change it'.format(os.getcwd())} - ''' - # Check if has default config - r = self.action_object.access({'action': 'load', - 'automation': 'cfg,88dce9c160324c5d', - 'artifact': 'default'}) - if r['return'] == 0: - config = r['config'] - - script_input = config.get('script', {}) - - if len(script_input) > 0: - utils.merge_dicts({'dict1': i, 'dict2': script_input}) - ''' recursion_int = int(i.get('recursion_int', 0)) + 1 start_time = time.time() @@ -469,8 +456,6 @@ def _run(self, i): # manage OS environment if len(self.os_info) == 0: r = get_host_os_info() - # r = self.access({'action': 'get_host_os_info', - # 'automation': 'utils,dc2743f8450541e3'}) if r['return'] > 0: return r @@ -677,8 +662,8 @@ def _run(self, i): recursion_spaces + ' - Searching for cached script outputs with the following tags: {}'.format(cache_tags_without_tmp_string)) - search_cache = {'action': 'find', - 'automation': self.meta['deps']['cache'], + search_cache = {'action': 'search', + 'target_name': 'cache', 'tags': cache_tags_without_tmp_string} rc = self.action_object.access(search_cache) if rc['return'] > 0: @@ -4905,8 +4890,8 @@ def find_cached_script(i): recursion_spaces + ' - Searching for cached script outputs with the following tags: {}'.format(search_tags)) - r = self_obj.action_object.access({'action': 'find', - 'automation': self_obj.meta['deps']['cache'], + r = self_obj.action_object.access({'action': 'search', + 'target_name': 'cache', 'tags': search_tags}) if r['return'] > 0: return r From 61610589947b9b219a1ba66e3291c5b0cd8c9408 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Mon, 27 Jan 2025 23:19:10 +0000 Subject: [PATCH 26/36] Fixes to docker detached mode (#163) * Clean up boolean compare in run-docker * Fix bug in detecting detached containers --- ...vidia-mlperf-inference-implementations.yml | 2 +- automation/script/docker_utils.py | 4 +- script/get-gh-actions-runner/meta.yaml | 2 +- script/run-docker-container/customize.py | 42 +++++++------------ script/run-docker-container/meta.yaml | 2 +- script/run-mlperf-inference-app/customize.py | 12 +++--- 6 files changed, 28 insertions(+), 36 deletions(-) diff --git a/.github/workflows/test-nvidia-mlperf-inference-implementations.yml b/.github/workflows/test-nvidia-mlperf-inference-implementations.yml index 18f7b7ee4..300c8a188 100644 --- a/.github/workflows/test-nvidia-mlperf-inference-implementations.yml +++ b/.github/workflows/test-nvidia-mlperf-inference-implementations.yml @@ -2,7 +2,7 @@ name: MLPerf Inference Nvidia implementations on: schedule: - - cron: "50 17 * * *" #to be adjusted + - cron: "38 18 * * *" #to be adjusted jobs: run_nvidia: diff --git a/automation/script/docker_utils.py b/automation/script/docker_utils.py index 3061b3096..419c4a0aa 100644 --- a/automation/script/docker_utils.py +++ b/automation/script/docker_utils.py @@ -110,7 +110,7 @@ def prepare_docker_inputs(input_params, docker_settings, keys += [ "skip_run_cmd", "pre_run_cmds", "run_cmd_prefix", "all_gpus", "num_gpus", "device", "gh_token", "port_maps", "shm_size", "pass_user_id", "pass_user_group", "extra_run_args", "detached", "interactive", - "dt", "it", "use_host_group_id", "use_host_user_id" + "dt", "it", "use_host_group_id", "use_host_user_id", "keep_detached", "reuse_existing" ] # Collect Dockerfile inputs docker_inputs = { @@ -377,6 +377,8 @@ def get_docker_default(key): "port_maps": [], "use_host_user_id": True, "use_host_group_id": True, + "keep_detached": False, + "reuse_existing": True } if key in defaults: return defaults[key] diff --git a/script/get-gh-actions-runner/meta.yaml b/script/get-gh-actions-runner/meta.yaml index dbd4a299b..67c512a45 100644 --- a/script/get-gh-actions-runner/meta.yaml +++ b/script/get-gh-actions-runner/meta.yaml @@ -22,7 +22,7 @@ new_env_keys: deps: - tags: detect-os - - tags: download-and-extract,_extract,_url.https://github.com/actions/runner/releases/download/v2.320.0/actions-runner-linux-x64-2.320.0.tar.gz + - tags: download-and-extract,_extract,_url.https://github.com/actions/runner/releases/download/v2.321.0/actions-runner-linux-x64-2.321.0.tar.gz force_cache: yes extra_cache_tags: gh-actions-runner-code,gh-actions,code env: diff --git a/script/run-docker-container/customize.py b/script/run-docker-container/customize.py index 97ff7b2aa..99f07fa14 100644 --- a/script/run-docker-container/customize.py +++ b/script/run-docker-container/customize.py @@ -16,7 +16,7 @@ def preprocess(i): interactive = env.get('MLC_DOCKER_INTERACTIVE_MODE', '') - if str(interactive).lower() in ['yes', 'true', '1']: + if is_true(interactive): env['MLC_DOCKER_DETACHED_MODE'] = 'no' if 'MLC_DOCKER_RUN_SCRIPT_TAGS' not in env: @@ -54,7 +54,9 @@ def preprocess(i): print('') print('Checking existing Docker container:') print('') - CMD = f"""{env['MLC_CONTAINER_TOOL']} ps --format=json --filter "ancestor={DOCKER_CONTAINER}" """ + # CMD = f"""{env['MLC_CONTAINER_TOOL']} ps --format=json --filter "ancestor={DOCKER_CONTAINER}" """ + CMD = f"""{env['MLC_CONTAINER_TOOL']} ps --format """ + \ + "'{{ .ID }},'" + f""" --filter "ancestor={DOCKER_CONTAINER}" """ if os_info['platform'] == 'windows': CMD += " 2> nul" else: @@ -71,27 +73,21 @@ def preprocess(i): 'error': 'Unexpected error occurred with docker run:\n{}'.format(e) } - if len(out) > 0 and str(env.get('MLC_DOCKER_REUSE_EXISTING_CONTAINER', - '')).lower() in ["1", "true", "yes"]: # container exists - # print(out) - out_split = out.splitlines() + existing_container_id = None + if len(out) > 0: + out_split = out.split(",") if len(out_split) > 0: - try: - out_json = json.loads(out_split[0]) - # print("JSON successfully loaded:", out_json) - except json.JSONDecodeError as e: - print(f"Error: First line of 'out' is not valid JSON: {e}") - return { - 'return': 1, 'error': f"Error: First line of 'out' is not valid JSON: {e}"} - else: - out_json = [] + existing_container_id = out_split[0].strip() - if isinstance(out_json, list) and len(out_json) > 0: - existing_container_id = out_json[0]['Id'] + if existing_container_id and is_true( + env.get('MLC_DOCKER_REUSE_EXISTING_CONTAINER', '')): print(f"Reusing existing container {existing_container_id}") env['MLC_DOCKER_CONTAINER_ID'] = existing_container_id else: + if existing_container_id: + print( + f"""Not using existing container {existing_container_id} as env['MLC_DOCKER_REUSE_EXISTING_CONTAINER'] is not set""") if env.get('MLC_DOCKER_CONTAINER_ID', '') != '': del (env['MLC_DOCKER_CONTAINER_ID']) # not valid ID @@ -237,13 +233,8 @@ def postprocess(i): run_opts += port_map_cmd_string # Currently have problem running Docker in detached mode on Windows: - detached = str( - env.get( - 'MLC_DOCKER_DETACHED_MODE', - '')).lower() in [ - 'yes', - 'true', - "1"] + detached = is_true(env.get('MLC_DOCKER_DETACHED_MODE', '')) + # if detached and os_info['platform'] != 'windows': if detached: if os_info['platform'] == 'windows': @@ -257,8 +248,7 @@ def postprocess(i): CONTAINER = f"""{env['MLC_CONTAINER_TOOL']} run -dt {run_opts} --rm {docker_image_repo}/{docker_image_name}:{docker_image_tag} bash""" CMD = f"""ID=`{CONTAINER}` && {env['MLC_CONTAINER_TOOL']} exec $ID bash -c '{run_cmd}'""" - if False and str(env.get('MLC_KEEP_DETACHED_CONTAINER', '')).lower() not in [ - 'yes', "1", 'true']: + if not is_true(env.get('MLC_KEEP_DETACHED_CONTAINER', '')): CMD += f""" && {env['MLC_CONTAINER_TOOL']} kill $ID >/dev/null""" CMD += ' && echo "ID=$ID"' diff --git a/script/run-docker-container/meta.yaml b/script/run-docker-container/meta.yaml index f6f3d19f0..aeaac021f 100644 --- a/script/run-docker-container/meta.yaml +++ b/script/run-docker-container/meta.yaml @@ -14,7 +14,6 @@ cache: false category: Docker automation default_env: - MLC_DOCKER_DETACHED_MODE: 'yes' MLC_DOCKER_REUSE_EXISTING_CONTAINER: 'no' MLC_DOCKER_PRIVILEGED_MODE: 'no' MLC_PODMAN_MAP_USER_ID: 'no' @@ -31,6 +30,7 @@ input_mapping: docker_base_image: MLC_DOCKER_IMAGE_BASE base_image: MLC_DOCKER_IMAGE_BASE keep_detached: MLC_KEEP_DETACHED_CONTAINER + reuse_existing: MLC_DOCKER_REUSE_EXISTING_CONTAINER docker_os: MLC_DOCKER_OS docker_os_version: MLC_DOCKER_OS_VERSION os: MLC_DOCKER_OS diff --git a/script/run-mlperf-inference-app/customize.py b/script/run-mlperf-inference-app/customize.py index c9fc33d52..3ac4e5a94 100644 --- a/script/run-mlperf-inference-app/customize.py +++ b/script/run-mlperf-inference-app/customize.py @@ -5,6 +5,7 @@ import subprocess import copy import mlperf_utils +from utils import * summary_ext = ['.csv', '.json', '.xlsx'] @@ -218,8 +219,7 @@ def preprocess(i): print('=========================================================') - if str(env.get('MLC_MLPERF_USE_DOCKER', '') - ).lower() in ["1", "true", "yes"]: + if is_true(env.get('MLC_MLPERF_USE_DOCKER', '')): action = "docker" # del(env['OUTPUT_BASE_DIR']) state = {} @@ -232,7 +232,7 @@ def preprocess(i): if k.startswith("docker_"): docker_extra_input[k] = inp[k] inp = {} - if str(docker_dt).lower() in ["yes", "true", "1"]: + if is_true(docker_dt): # turning it off for the first run and after that we turn it on if env.get('MLC_DOCKER_REUSE_EXISTING_CONTAINER', '') == '': env['MLC_DOCKER_REUSE_EXISTING_CONTAINER'] = 'no' @@ -292,7 +292,7 @@ def preprocess(i): env['OUTPUT_BASE_DIR'], f"{env['MLC_MLPERF_RUN_STYLE']}_results") if action == "docker": - if str(docker_dt).lower() not in ["yes", "true", "1"]: + if not is_true(docker_dt): print( f"\nStop Running loadgen scenario: {scenario} and mode: {mode}") # We run commands interactively inside the docker container @@ -320,8 +320,8 @@ def preprocess(i): if state.get('docker', {}): del (state['docker']) - if env.get('MLC_DOCKER_CONTAINER_ID', '') != '' and str(env.get( - 'MLC_DOCKER_CONTAINER_KEEP_ALIVE', '')).lower() not in ["yes", "1", "true"]: + if env.get('MLC_DOCKER_CONTAINER_ID', '') != '' and not is_true(env.get( + 'MLC_DOCKER_CONTAINER_KEEP_ALIVE', '')): container_id = env['MLC_DOCKER_CONTAINER_ID'] CMD = f"docker kill {container_id}" docker_out = subprocess.check_output(CMD, shell=True).decode("utf-8") From 09e07b4d691e058eb42aebe693f724e7e745a265 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Tue, 28 Jan 2025 00:38:53 +0000 Subject: [PATCH 27/36] Use global logger (#164) * Use global logger --- automation/script/module.py | 248 +++++++++++++++++++----------------- 1 file changed, 133 insertions(+), 115 deletions(-) diff --git a/automation/script/module.py b/automation/script/module.py index 2d256838c..ae8e3acd7 100644 --- a/automation/script/module.py +++ b/automation/script/module.py @@ -26,7 +26,7 @@ class ScriptAutomation(Automation): ############################################################ def __init__(self, action_object, automation_file): super().__init__(action_object, "script", automation_file) - logging.basicConfig(level=logging.INFO) + # logger.basicConfig(level=logger.INFO) self.os_info = {} self.run_state = {} self.run_state['deps'] = [] @@ -233,6 +233,8 @@ def _run(self, i): repro = i.get('repro', False) repro_prefix = '' + logger = self.action_object.logger + if repro: repro_prefix = i.get('repro_prefix', '') if repro_prefix == '': @@ -404,7 +406,7 @@ def _run(self, i): if verbose: env['MLC_VERBOSE'] = 'yes' run_state['tmp_verbose'] = True - logging.getLogger().setLevel(logging.DEBUG) + logger.setLevel(logging.DEBUG) print_deps = i.get('print_deps', False) print_versions = i.get('print_versions', False) @@ -561,10 +563,10 @@ def _run(self, i): mlc_script_info += y.join(x_variation_tags) # if verbose: -# logging.info('') +# logger.info('') if not run_state.get('tmp_silent', False): - logging.info(recursion_spaces + '* ' + mlc_script_info) + logger.info(recursion_spaces + '* ' + mlc_script_info) ####################################################################### # Report if scripts were not found or there is an ambiguity with UIDs @@ -596,8 +598,8 @@ def _run(self, i): # Sort scripts for better determinism list_of_found_scripts = sorted(list_of_found_scripts, key=lambda a: (a.meta.get('sort', 0), a.path)) - logging.debug(recursion_spaces + - ' - Number of scripts found: {}'.format(len(list_of_found_scripts))) + logger.debug(recursion_spaces + + ' - Number of scripts found: {}'.format(len(list_of_found_scripts))) # Check if script selection is remembered if not skip_remembered_selections and len(list_of_found_scripts) > 1: @@ -606,7 +608,7 @@ def _run(self, i): selection['tags'].split(',')) == set(script_tags_string.split(',')): # Leave 1 entry in the found list list_of_found_scripts = [selection['cached_script']] - logging.debug( + logger.debug( recursion_spaces + ' - Found remembered selection with tags: {}'.format(script_tags_string)) break @@ -658,7 +660,7 @@ def _run(self, i): cache_tags_without_tmp_string = cache_tags_without_tmp_string.replace( ",_-", ",-_") - logging.debug( + logger.debug( recursion_spaces + ' - Searching for cached script outputs with the following tags: {}'.format(cache_tags_without_tmp_string)) @@ -671,7 +673,7 @@ def _run(self, i): cache_list = rc['list'] - logging.debug( + logger.debug( recursion_spaces + ' - Number of cached script outputs found: {}'.format( len(cache_list))) @@ -725,7 +727,8 @@ def _run(self, i): False, script_tags_string, quiet, - verbose) + verbose, + logger) # Remember selection if not skip_remembered_selections: @@ -819,8 +822,8 @@ def _run(self, i): if i.get('debug_script', False): debug_script_tags = ','.join(found_script_tags) - logging.debug(recursion_spaces + - ' - Found script::{} in {}'.format(found_script_item, path)) + logger.debug(recursion_spaces + + ' - Found script::{} in {}'.format(found_script_item, path)) # STEP 500 output: script_item - unique selected script artifact # (cache_list) pruned for the unique script if cache is used @@ -973,7 +976,7 @@ def _run(self, i): # del(env[key]) if len(notes) > 0: - logging.debug( + logger.debug( recursion_spaces + ' - Requested version: ' + ' '.join(notes)) @@ -1031,7 +1034,7 @@ def _run(self, i): if state.get('docker'): if str(state['docker'].get('run', True) ).lower() in ['false', '0', 'no']: - logging.info( + logger.info( recursion_spaces + ' - Skipping script::{} run as we are inside docker'.format(found_script_item)) @@ -1054,7 +1057,7 @@ def _run(self, i): return rr elif str(state['docker'].get('real_run', True)).lower() in ['false', '0', 'no']: - logging.info( + logger.info( recursion_spaces + ' - Doing fake run for script::{} as we are inside docker'.format(found_script_item)) fake_run = True @@ -1208,7 +1211,8 @@ def _run(self, i): True, script_tags_string, quiet, - verbose) + verbose, + logger) if selection >= 0: if not skip_remembered_selections: @@ -1220,7 +1224,7 @@ def _run(self, i): num_found_cached_scripts = 0 elif num_found_cached_scripts == 1: - logging.debug( + logger.debug( recursion_spaces + ' - Found cached script output: {}'.format( found_cached_scripts[0].path)) @@ -1230,7 +1234,7 @@ def _run(self, i): # Check chain of dynamic dependencies on other MLC scripts if len(deps) > 0: - logging.debug( + logger.debug( recursion_spaces + ' - Checking dynamic dependencies on other MLC scripts:') @@ -1240,7 +1244,7 @@ def _run(self, i): if r['return'] > 0: return r - logging.debug( + logger.debug( recursion_spaces + ' - Processing env after dependencies ...') @@ -1250,7 +1254,7 @@ def _run(self, i): # Check chain of prehook dependencies on other MLC scripts. # (No execution of customize.py for cached scripts) - logging.debug( + logger.debug( recursion_spaces + ' - Checking prehook dependencies on other MLC scripts:') @@ -1263,7 +1267,7 @@ def _run(self, i): # Continue with the selected cached script cached_script = found_cached_scripts[selection] - logging.debug( + logger.debug( recursion_spaces + ' - Loading state from cached entry ...') @@ -1276,7 +1280,7 @@ def _run(self, i): version = r['meta'].get('version') if not run_state.get('tmp_silent', False): - logging.info( + logger.info( recursion_spaces + ' ! load {}'.format(path_to_cached_state_file)) @@ -1311,7 +1315,7 @@ def _run(self, i): if not fake_run: # Check chain of posthook dependencies on other MLC scripts. We consider them same as postdeps when # script is in cache - logging.debug( + logger.debug( recursion_spaces + ' - Checking posthook dependencies on other MLC scripts:') @@ -1324,7 +1328,7 @@ def _run(self, i): if r['return'] > 0: return r - logging.debug( + logger.debug( recursion_spaces + ' - Checking post dependencies on other MLC scripts:') @@ -1371,11 +1375,11 @@ def _run(self, i): tmp_tags.append(x) # Use update to update the tmp one if already exists - logging.debug( + logger.debug( recursion_spaces + ' - Creating new "cache" script artifact in the MLC local repository ...') - logging.debug(recursion_spaces + - ' - Tags: {}'.format(','.join(tmp_tags))) + logger.debug(recursion_spaces + + ' - Tags: {}'.format(','.join(tmp_tags))) if version != '': cached_meta['version'] = version @@ -1403,7 +1407,7 @@ def _run(self, i): # Changing path to MLC script artifact for cached output # to record data and files there - logging.debug( + logger.debug( recursion_spaces + ' - Changing to {}'.format(cached_path)) @@ -1418,7 +1422,7 @@ def _run(self, i): # Changing path to MLC script artifact for cached output # to record data and files there - logging.debug( + logger.debug( recursion_spaces + ' - Changing to {}'.format(cached_path)) @@ -1445,12 +1449,12 @@ def _run(self, i): ################################ if not found_cached: if len(warnings) > 0: - logging.warn( + logger.warn( '=================================================') - logging.warn('WARNINGS:') + logger.warn('WARNINGS:') for w in warnings: - logging.warn(' ' + w) - logging.warn( + logger.warn(' ' + w) + logger.warn( '=================================================') # Update default version meta if version is not set @@ -1480,7 +1484,7 @@ def _run(self, i): else: version = version_max - logging.debug( + logger.debug( recursion_spaces + ' - Version is not specified - use either default_version from meta or min/max/usable: {}'.format(version)) @@ -1532,7 +1536,7 @@ def _run(self, i): if len(docker_deps) > 0: - logging.debug( + logger.debug( recursion_spaces + ' - Checking docker run dependencies on other MLC scripts:') @@ -1542,7 +1546,7 @@ def _run(self, i): if r['return'] > 0: return r - logging.debug( + logger.debug( recursion_spaces + ' - Processing env after docker run dependencies ...') @@ -1609,7 +1613,7 @@ def _run(self, i): run_script_input['ignore_script_error'] = True if 'predeps' in dir(customize_code) and not fake_run: - logging.debug( + logger.debug( recursion_spaces + ' - Running preprocess ...') @@ -1630,8 +1634,8 @@ def _run(self, i): # print(f"before deps: ") # utils.print_env(env) if len(deps) > 0: - logging.debug(recursion_spaces + - ' - Checking dependencies on other MLC scripts:') + logger.debug(recursion_spaces + + ' - Checking dependencies on other MLC scripts:') r = self._call_run_deps(deps, self.local_env_keys, local_env_keys_from_meta, env, state, const, const_state, add_deps_recursive, recursion_spaces + extra_recursion_spaces, @@ -1639,8 +1643,8 @@ def _run(self, i): if r['return'] > 0: return r - logging.debug(recursion_spaces + - ' - Processing env after dependencies ...') + logger.debug(recursion_spaces + + ' - Processing env after dependencies ...') r = update_env_with_values(env) if r['return'] > 0: @@ -1740,7 +1744,7 @@ def _run(self, i): return r if pip_version_string != '': - logging.debug( + logger.debug( recursion_spaces + ' # potential PIP version string (if needed): ' + pip_version_string) @@ -1761,7 +1765,7 @@ def _run(self, i): # Check if pre-process and detect if 'preprocess' in dir(customize_code) and not fake_run: - logging.debug(recursion_spaces + ' - Running preprocess ...') + logger.debug(recursion_spaces + ' - Running preprocess ...') # print(f"preprocess_env:") # utils.print_env(env) @@ -1782,7 +1786,7 @@ def _run(self, i): skip = r.get('skip', False) if skip: - logging.debug( + logger.debug( recursion_spaces + ' - this script is skipped!') @@ -1793,7 +1797,7 @@ def _run(self, i): if len(another_script) == 0: return {'return': 0, 'skipped': True} - logging.debug( + logger.debug( recursion_spaces + ' - another script is executed instead!') @@ -1833,11 +1837,11 @@ def _run(self, i): if print_env: import json - logging.debug(json.dumps(env, indent=2, sort_keys=True)) + logger.debug(json.dumps(env, indent=2, sort_keys=True)) # Check chain of pre hook dependencies on other MLC scripts if len(prehook_deps) > 0: - logging.debug( + logger.debug( recursion_spaces + ' - Checking prehook dependencies on other MLC scripts:') @@ -2000,7 +2004,7 @@ def _run(self, i): return r # Remove tmp tag from the "cached" arifact to finalize caching - logging.debug( + logger.debug( recursion_spaces + ' - Removing tmp tag in the script cached output {} ...'.format(cached_uid)) @@ -2144,7 +2148,7 @@ def _run(self, i): elapsed_time = time.time() - start_time if verbose and cached_uid != '': - logging.info( + logger.info( recursion_spaces + ' - cache UID: {}'.format(cached_uid)) @@ -2175,7 +2179,7 @@ def _run(self, i): # Print output as json to console if i.get('json', False) or i.get('j', False): import json - logging.info(json.dumps(rr, indent=2)) + logger.info(json.dumps(rr, indent=2)) # Check if save json to file if repro_prefix != '': @@ -2186,7 +2190,7 @@ def _run(self, i): dump_repro(repro_prefix, rr, run_state) if verbose or show_time: - logging.info( + logger.info( recursion_spaces + ' - running time of script "{}": {:.2f} sec.'.format( ','.join(found_script_tags), @@ -2199,7 +2203,7 @@ def _run(self, i): (start_disk_stats.free - stop_disk_stats.free) / (1024 * 1024)) if used_disk_space_in_mb > 0: - logging.info( + logger.info( recursion_spaces + ' - used disk space: {} MB'.format(used_disk_space_in_mb)) @@ -2214,7 +2218,7 @@ def _run(self, i): v = new_env.get(p, None) - logging.info('{}: {}'.format(t, str(v))) + logger.info('{}: {}'.format(t, str(v))) # Check if print nice versions if print_versions: @@ -2309,7 +2313,7 @@ def _dump_version_info_for_script( pass for f in ['mlc-run-script-versions.json', 'version_info.json']: if not quiet and not silent: - logging.info('Dumping versions to {}'.format(f)) + logger.info('Dumping versions to {}'.format(f)) r = utils.save_json(f, self.run_state.get('version_info', [])) if r['return'] > 0: return r @@ -2320,6 +2324,7 @@ def _dump_version_info_for_script( def _update_state_from_variations(self, i, meta, variation_tags, variations, env, state, const, const_state, deps, post_deps, prehook_deps, posthook_deps, new_env_keys_from_meta, new_state_keys_from_meta, add_deps_recursive, run_state, recursion_spaces, verbose): + logger = self.action_object.logger # Save current explicit variations import copy explicit_variation_tags = copy.deepcopy(variation_tags) @@ -2405,7 +2410,7 @@ def _update_state_from_variations(self, i, meta, variation_tags, variations, env x = '_' + t variation_tags_string += x - logging.debug( + logger.debug( recursion_spaces + ' Prepared variations: {}'.format(variation_tags_string)) @@ -2740,7 +2745,7 @@ def version(self, i): version = self.__version__ if console: - logging.info(version) + self.action_object.logger.info(version) return {'return': 0, 'version': version} @@ -2853,8 +2858,8 @@ def search(self, i): # Print filtered paths if console if console: for script in r['list']: - # This should not be logging since the output can be consumed by other external tools and scripts - # logging.info(script.path) + # This should not be logger since the output can be consumed by other external tools and scripts + # logger.info(script.path) print(script.path) # Finalize output @@ -2909,6 +2914,7 @@ def test(self, i): if r['return'] > 0: return r + logger = self.action_object.logger lst = r['list'] for script_item in lst: path = script_item.path @@ -2918,10 +2924,10 @@ def test(self, i): alias = meta.get('alias', '') uid = meta.get('uid', '') if console: - logging.info(path) + logger.info(path) test_config = meta.get('tests', '') if test_config: - logging.info(test_config) + logger.info(test_config) variations = meta.get("variations") tags_string = ",".join(meta.get("tags")) test_input_index = i.get('test_input_index') @@ -3020,7 +3026,7 @@ def test(self, i): if i_env: import copy ii['env'] = copy.deepcopy(i_env) - logging.info(ii) + logger.info(ii) r = self.action_object.access(ii) if r['return'] > 0: return r @@ -3145,6 +3151,7 @@ def add(self, i): import shutil console = i.get('out') == 'con' + logger = self.action_object.logger # Try to find script artifact by alias and/or tags # ii = utils.sub_input(i, self.cmind.cfg['artifact_keys']) @@ -3293,7 +3300,7 @@ def add(self, i): new_script_path = r_obj['path'] if console: - logging.info('Created script in {}'.format(new_script_path)) + logger.info('Created script in {}'.format(new_script_path)) # Copy files from template (only if exist) files = [ @@ -3339,7 +3346,7 @@ def add(self, i): f2 = os.path.join(new_script_path, f2) if console: - logging.info(' * Copying {} to {}'.format(f1, f2)) + logger.info(' * Copying {} to {}'.format(f1, f2)) shutil.copyfile(f1, f2) @@ -3667,7 +3674,7 @@ def _run_deps(self, deps, clean_env_keys_deps, env, state, const, const_state, a run_state['script_variation_tags']) + " )" # Run collective script via MLC API: - # Not very efficient but allows logging - can be optimized + # Not very efficient but allows logger - can be optimized # later # print(f"env about to call deps {d}= {env}") @@ -3828,11 +3835,11 @@ def _print_versions(self, run_state): """ Print versions in the nice format """ - + logger = self.action_object.logger version_info = run_state.get('version_info', []) - logging.info('=========================') - logging.info('Versions of dependencies:') + logger.info('=========================') + logger.info('Versions of dependencies:') for v in version_info: k = list(v.keys())[0] version_info_dict = v[k] @@ -3840,9 +3847,9 @@ def _print_versions(self, run_state): version = version_info_dict.get('version', '') if version != '': - logging.info('* {}: {}'.format(k, version)) + logger.info('* {}: {}'.format(k, version)) - logging.info('=========================') + logger.info('=========================') return {} @@ -3861,11 +3868,12 @@ def _print_deps(self, deps): Prints the MLC run commands for the list of MLC script dependencies """ + logger = self.action_object.logger print_deps_data = [] run_cmds = self._get_deps_run_cmds(deps) for cmd in run_cmds: print_deps_data.append(cmd) - logging.info(cmd) + logger.info(cmd) return print_deps_data @@ -3973,7 +3981,7 @@ def find_file_in_paths(self, i): select = i.get('select', False) select_default = i.get('select_default', False) recursion_spaces = i.get('recursion_spaces', '') - + logger = self.action_object.logger hook = i.get('hook', None) verbose = i.get('verbose', False) @@ -4090,14 +4098,14 @@ def find_file_in_paths(self, i): x += ' <= {}'.format(version_max) if x != '': - logging.info( + logger.info( recursion_spaces + ' - Searching for versions: {}'.format(x)) new_recursion_spaces = recursion_spaces + ' ' for path_to_file in found_files: - logging.info(recursion_spaces + ' * ' + path_to_file) + logger.info(recursion_spaces + ' * ' + path_to_file) run_script_input['env'] = env run_script_input['env'][env_path_key] = path_to_file @@ -4117,7 +4125,7 @@ def find_file_in_paths(self, i): if detected_version != '': if detected_version == -1: - logging.info( + logger.info( recursion_spaces + ' SKIPPED due to incompatibility ...') else: ry = check_version_constraints({'detected_version': detected_version, @@ -4132,7 +4140,7 @@ def find_file_in_paths(self, i): found_files_with_good_version.append( path_to_file) else: - logging.info( + logger.info( recursion_spaces + ' SKIPPED due to version constraints ...') found_files = found_files_with_good_version @@ -4143,13 +4151,13 @@ def find_file_in_paths(self, i): selection = 0 else: # Select 1 and proceed - logging.info( + logger.info( recursion_spaces + ' - More than 1 path found:') num = 0 for file in found_files: - logging.info( + logger.info( recursion_spaces + ' {}) {}'.format( num, @@ -4166,7 +4174,7 @@ def find_file_in_paths(self, i): if selection < 0 or selection >= num: selection = 0 - logging.info( + logger.info( recursion_spaces + ' Selected {}: {}'.format( selection, @@ -4204,6 +4212,7 @@ def detect_version_using_script(self, i): import copy detected = False + logger = self.action_object.logger env = i.get('env', {}) @@ -4223,7 +4232,7 @@ def detect_version_using_script(self, i): x += ' <= {}'.format(version_max) if x != '': - logging.info( + logger.info( recursion_spaces + ' - Searching for versions: {}'.format(x)) @@ -4298,11 +4307,11 @@ def find_artifact(self, i): file_name = i['file_name'] os_info = i['os_info'] - + logger = self.action_object.logger env = i['env'] env_path_key = i.get('env_path_key', '') - + logger = self.action_object.logger run_script_input = i.get('run_script_input', {}) extra_paths = i.get('extra_paths', {}) @@ -4347,7 +4356,7 @@ def find_artifact(self, i): if path == '': path_list_tmp = default_path_list else: - logging.info( + logger.info( recursion_spaces + ' # Requested paths: {}'.format(path)) path_list_tmp = path.split(os_info['env_separator']) @@ -4412,7 +4421,7 @@ def find_artifact(self, i): if extra_paths[extra_path] not in env: env[extra_paths[extra_path]] = [] env[extra_paths[extra_path]].append(epath) - logging.info( + logger.info( recursion_spaces + ' # Found artifact in {}'.format(file_path)) @@ -4449,6 +4458,8 @@ def find_file_deep(self, i): paths = i['paths'] file_name = i['file_name'] + logger = self.action_object.logger + restrict_paths = i.get('restrict_paths', []) found_paths = [] @@ -4793,10 +4804,11 @@ def find_cached_script(i): verbose = i.get('verbose', False) if not verbose: verbose = i.get('v', False) + logger = self_obj.action_object.logger found_cached_scripts = [] - logging.debug( + logger.debug( recursion_spaces + ' - Checking if script execution is already cached ...') @@ -4829,7 +4841,7 @@ def find_cached_script(i): if x not in explicit_cached_tags: explicit_cached_tags.append(x) - logging.debug( + logger.debug( recursion_spaces + ' - Prepared explicit variations: {}'.format(explicit_variation_tags_string)) @@ -4848,7 +4860,7 @@ def find_cached_script(i): if x not in cached_tags: cached_tags.append(x) - logging.debug( + logger.debug( recursion_spaces + ' - Prepared variations: {}'.format(variation_tags_string)) @@ -4886,7 +4898,7 @@ def find_cached_script(i): if len(cached_tags) > 0: search_tags += ',' + ','.join(explicit_cached_tags) - logging.debug( + logger.debug( recursion_spaces + ' - Searching for cached script outputs with the following tags: {}'.format(search_tags)) @@ -4915,7 +4927,7 @@ def find_cached_script(i): tmp_version_in_cached_script)} else: found_cached_scripts = [selection['cached_script']] - logging.debug( + logger.debug( recursion_spaces + ' - Found remembered selection with tags "{}"!'.format(search_tags)) break @@ -4974,7 +4986,7 @@ def find_cached_script(i): # Check if pre-process and detect # if 'preprocess' in dir(customize_code): - # logging.debug(recursion_spaces + ' - Running preprocess ...') + # logger.debug(recursion_spaces + ' - Running preprocess ...') # ii = copy.deepcopy(customize_common_input) # ii['env'] = env @@ -5025,7 +5037,7 @@ def enable_or_skip_script(meta, env): """ if not isinstance(meta, dict): - logging.info( + logger.info( "The meta entry is not a dictionary for skip/enable if_env: %s", meta) @@ -5227,6 +5239,7 @@ def prepare_and_run_script_with_postprocessing(i, postprocess="postprocess"): verbose = i.get('v', False) show_time = i.get('time', False) + logger = i['self'].action_object.logger recursion = i.get('recursion', False) found_script_tags = i.get('found_script_tags', []) @@ -5319,15 +5332,15 @@ def prepare_and_run_script_with_postprocessing(i, postprocess="postprocess"): run_script = tmp_file_run + bat_ext run_script_without_cm = tmp_file_run + '-without-cm' + bat_ext - logging.debug( + logger.debug( recursion_spaces + ' - Running native script "{}" from temporal script "{}" in "{}" ...'.format( path_to_run_script, run_script, cur_dir)) if not run_state.get('tmp_silent', False): - logging.info(recursion_spaces + ' ! cd {}'.format(cur_dir)) - logging.info( + logger.info(recursion_spaces + ' ! cd {}'.format(cur_dir)) + logger.info( recursion_spaces + ' ! call {} from {}'.format( path_to_run_script, @@ -5379,11 +5392,11 @@ def prepare_and_run_script_with_postprocessing(i, postprocess="postprocess"): import shutil shutil.copy(run_script, run_script_without_cm) - logging.info( + logger.info( '================================================================================') - logging.info( + logger.info( 'Debug script to run without MLC was recorded: {}'.format(run_script_without_cm)) - logging.info( + logger.info( '================================================================================') # Run final command @@ -5400,12 +5413,12 @@ def prepare_and_run_script_with_postprocessing(i, postprocess="postprocess"): if os.path.isfile(pr): r = utils.load_txt(file_name=pr) if r['return'] == 0: - logging.info( + logger.info( "========================================================") - logging.info("Print file {}:".format(pr)) - logging.info("") - logging.info(r['string']) - logging.info("") + logger.info("Print file {}:".format(pr)) + logger.info("") + logger.info(r['string']) + logger.info("") # Check where to report errors and failures repo_to_report = run_state.get( @@ -5473,7 +5486,7 @@ def prepare_and_run_script_with_postprocessing(i, postprocess="postprocess"): if postprocess != '' and customize_code is not None and postprocess in dir( customize_code): if not run_state.get('tmp_silent', False): - logging.info( + logger.info( recursion_spaces + ' ! call "{}" from {}'.format( postprocess, @@ -5504,7 +5517,9 @@ def run_detect_version(customize_code, customize_common_input, if customize_code is not None and 'detect_version' in dir(customize_code): import copy - logging.debug(recursion_spaces + ' - Running detect_version ...') + if "self" in customize_common_input: + logger = customize_common_input["self"].action_object.logger + logger.debug(recursion_spaces + ' - Running detect_version ...') # Update env and state with const utils.merge_dicts({'dict1': env, 'dict2': const, @@ -5532,8 +5547,9 @@ def run_postprocess(customize_code, customize_common_input, recursion_spaces, if customize_code is not None and 'postprocess' in dir(customize_code): import copy - - logging.debug(recursion_spaces + ' - Running postprocess ...') + if run_script_input: + logger = run_script_input['self'].action_object.logger + logger.debug(recursion_spaces + ' - Running postprocess ...') # Update env and state with const utils.merge_dicts({'dict1': env, 'dict2': const, @@ -5690,8 +5706,8 @@ def clean_tmp_files(clean_files, recursion_spaces): Internal: clean tmp files """ -# logging.info('') -# logging.info(recursion_spaces+' - cleaning files {} ...'.format(clean_files)) +# logger.info('') +# logger.info(recursion_spaces+' - cleaning files {} ...'.format(clean_files)) for tmp_file in clean_files: if os.path.isfile(tmp_file): @@ -6139,7 +6155,7 @@ def detect_state_diff(env, saved_env, new_env_keys, def select_script_item(lst, text, recursion_spaces, - can_skip, script_tags_string, quiet, verbose): + can_skip, script_tags_string, quiet, verbose, logger=None): """ Internal: select script """ @@ -6147,15 +6163,17 @@ def select_script_item(lst, text, recursion_spaces, string1 = recursion_spaces + \ ' - More than 1 {} found for "{}":'.format(text, script_tags_string) + if not logger: + logger = logging.getLoger() # If quiet, select 0 (can be sorted for determinism) if quiet: - logging.debug(string1) - logging.debug('Selected default due to "quiet" mode') + logger.debug(string1) + logger.debug('Selected default due to "quiet" mode') return 0 # Select 1 and proceed - logging.info(string1) + logger.info(string1) num = 0 for a in lst: @@ -6174,7 +6192,7 @@ def select_script_item(lst, text, recursion_spaces, if version != '': x += ' (Version {})'.format(version) - logging.info(x) + logger.info(x) num += 1 s = 'Make your selection or press Enter for 0' @@ -6192,11 +6210,11 @@ def select_script_item(lst, text, recursion_spaces, selection = 0 if selection < 0: - logging.info(recursion_spaces + ' Skipped') + logger.info(recursion_spaces + ' Skipped') else: if selection >= num: selection = 0 - logging.info( + logger.info( recursion_spaces + ' Selected {}: {}'.format( selection, @@ -6430,4 +6448,4 @@ def dump_repro(repro_prefix, rr, run_state): r = auto.test({'x': 'y'}) - logging.info(r) + auto.action_object.logger.info(r) From 30a1c0c5227d564cf472bbc128a6d1919f29ad25 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Tue, 28 Jan 2025 01:00:45 +0000 Subject: [PATCH 28/36] Support image_name in docker_settings (#166) --- automation/script/docker_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/automation/script/docker_utils.py b/automation/script/docker_utils.py index 419c4a0aa..471b494ef 100644 --- a/automation/script/docker_utils.py +++ b/automation/script/docker_utils.py @@ -102,7 +102,7 @@ def prepare_docker_inputs(input_params, docker_settings, keys = [ "mlc_repo", "mlc_repo_branch", "base_image", "os", "os_version", - "mlc_repos", "skip_mlc_sys_upgrade", "extra_sys_deps", + "mlc_repos", "skip_mlc_sys_upgrade", "extra_sys_deps", "image_name", "gh_token", "fake_run_deps", "run_final_cmds", "real_run", "copy_files", "path", "user" ] From fb9214c54056c69ff50db732ebc5362229c74988 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Tue, 28 Jan 2025 21:35:40 +0000 Subject: [PATCH 29/36] Fix logging (#167) * Fix logging --- VERSION | 2 +- automation/script/docker.py | 10 ++++---- automation/script/docker_utils.py | 1 - automation/script/module.py | 24 +++++++++++-------- git_commit_hash.txt | 2 +- script/app-mlperf-inference/customize.py | 20 ++++++++++------ .../customize.py | 2 +- script/run-docker-container/customize.py | 2 +- script/run-terraform/customize.py | 2 +- setup.py | 2 +- 10 files changed, 39 insertions(+), 28 deletions(-) diff --git a/VERSION b/VERSION index 1750564f2..d169b2f2d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.6 +0.0.8 diff --git a/automation/script/docker.py b/automation/script/docker.py index 9852937c1..bebee59e1 100644 --- a/automation/script/docker.py +++ b/automation/script/docker.py @@ -15,6 +15,8 @@ def dockerfile(self_module, input_params): if prune_result['return'] > 0: return prune_result + logger = self_module.logger + run_command_arc = prune_result['new_input'] current_directory = os.getcwd() is_quiet_mode = input_params.get('quiet', False) @@ -99,7 +101,7 @@ def dockerfile(self_module, input_params): if not docker_settings.get('run', True) and not input_params.get( 'docker_run_override', False): - logging.info("Docker 'run' is set to False in meta.json") + logger.info("Docker 'run' is set to False in meta.json") continue # Handle build dependencies @@ -186,7 +188,7 @@ def dockerfile(self_module, input_params): if dockerfile_result['return'] > 0: return dockerfile_result - logging.info(f"Dockerfile generated at {dockerfile_path}") + logger.info(f"Dockerfile generated at {dockerfile_path}") return {'return': 0} @@ -209,7 +211,7 @@ def docker_run(self_module, i): quiet = i.get('quiet', False) verbose = i.get('v', False) show_time = i.get('show_time', False) - + logger = self_module.logger env = i.get('env', {}) regenerate_docker_file = not i.get('docker_noregenerate', False) @@ -325,7 +327,7 @@ def docker_run(self_module, i): # Skip scripts marked as non-runnable if not docker_settings.get('run', True) and not i.get( 'docker_run_override', False): - logging.info("docker.run set to False in meta.yaml") + logger.info("docker.run set to False in meta.yaml") continue r = self_module._update_env_from_input(env, i) diff --git a/automation/script/docker_utils.py b/automation/script/docker_utils.py index 471b494ef..75c1f68a4 100644 --- a/automation/script/docker_utils.py +++ b/automation/script/docker_utils.py @@ -1,7 +1,6 @@ import os from mlc import utils from utils import * -import logging from pathlib import PureWindowsPath, PurePosixPath from script.docker_utils import * import copy diff --git a/automation/script/module.py b/automation/script/module.py index ae8e3acd7..cbb3bcb58 100644 --- a/automation/script/module.py +++ b/automation/script/module.py @@ -26,7 +26,6 @@ class ScriptAutomation(Automation): ############################################################ def __init__(self, action_object, automation_file): super().__init__(action_object, "script", automation_file) - # logger.basicConfig(level=logger.INFO) self.os_info = {} self.run_state = {} self.run_state['deps'] = [] @@ -35,6 +34,10 @@ def __init__(self, action_object, automation_file): self.run_state['version_info'] = [] self.run_state['cache'] = False self.file_with_cached_state = 'mlc-cached-state.json' + # self.logger = logging.getLogger() + # logging.basicConfig(level=logging.INFO) + self.logger = self.action_object.logger + self.logger.propagate = False self.tmp_file_env = 'tmp-env' self.tmp_file_env_all = 'tmp-env-all' @@ -188,7 +191,7 @@ def run(self, i): (print_readme) (bool): if True, will print README with all MLC steps (deps) to run a given script - (script_call_prefix) (str): how to call script in logs and READMEs (mlc run script) + (script_call_prefix) (str): how to call script in logs and READMEs (mlcr) (skip_sys_utils) (bool): if True, set env['MLC_SKIP_SYS_UTILS']='yes' to skip MLC sys installation @@ -233,7 +236,7 @@ def _run(self, i): repro = i.get('repro', False) repro_prefix = '' - logger = self.action_object.logger + logger = self.logger if repro: repro_prefix = i.get('repro_prefix', '') @@ -539,15 +542,15 @@ def _run(self, i): mlc_script_info = i.get('script_call_prefix', '').strip() if mlc_script_info == '': - mlc_script_info = 'mlc run script' + mlc_script_info = 'mlcr ' if not mlc_script_info.endswith(' '): mlc_script_info += ' ' - x = '--tags=' + x = '' y = ',' if parsed_script_alias != '': mlc_script_info += parsed_script_alias - x = '--tags="' + x = '"' if len(script_tags) > 0 or len(variation_tags) > 0: mlc_script_info += x @@ -3800,7 +3803,7 @@ def _get_readme(self, cmd_parts, run_state): ```bash """ - cmd = "mlc run script " + cmd = "mlcr " for cmd_part in cmd_parts: x = '"' if ' ' in cmd_part and not cmd_part.startswith('-') else '' @@ -3823,7 +3826,7 @@ def _get_readme(self, cmd_parts, run_state): xversion = ' --version={}\n'.format(version) content += "```bash\n" - content += "mlc run script --tags=" + \ + content += "mlcr " + \ dep_tags + "{}\n".format(xversion) content += "```\n\n" @@ -3887,7 +3890,7 @@ def _get_deps_run_cmds(self, deps): run_cmds = [] for dep_tags in deps: - run_cmds.append("mlc run script --tags=" + dep_tags) + run_cmds.append("mlcr " + dep_tags) return run_cmds @@ -6164,7 +6167,8 @@ def select_script_item(lst, text, recursion_spaces, ' - More than 1 {} found for "{}":'.format(text, script_tags_string) if not logger: - logger = logging.getLoger() + return {'return': 1, 'error': 'No logger provided'} + # If quiet, select 0 (can be sorted for determinism) if quiet: logger.debug(string1) diff --git a/git_commit_hash.txt b/git_commit_hash.txt index d5693f79a..d21d33c55 100644 --- a/git_commit_hash.txt +++ b/git_commit_hash.txt @@ -1 +1 @@ -90fbda0c7079a0a09688aa2045d2f571ef61c222 +30a1c0c5227d564cf472bbc128a6d1919f29ad25 diff --git a/script/app-mlperf-inference/customize.py b/script/app-mlperf-inference/customize.py index fbe70dde4..f94dfa86b 100644 --- a/script/app-mlperf-inference/customize.py +++ b/script/app-mlperf-inference/customize.py @@ -5,7 +5,6 @@ import shutil import subprocess import copy -import mlc import platform import sys import mlperf_utils @@ -354,8 +353,14 @@ def postprocess(i): "os_version": platform.platform(), "cpu_version": platform.processor(), "python_version": sys.version, - "mlc_version": mlc.__version__ } + try: + import importlib.metadata + mlc_version = importlib.metadata.version("mlc") + host_info["mlc_version"] = mlc_version + except Exception as e: + error = format(e) + mlc_version = "unknown" x = '' if env.get('MLC_HOST_OS_FLAVOR', '') != '': @@ -371,6 +376,7 @@ def postprocess(i): # Check CM automation repository repo_name = 'mlcommons@mlperf-automations' repo_hash = '' + mlc = i['automation'].action_object r = mlc.access({'action': 'find', 'automation': 'repo', 'item': 'mlcommons@mlperf-automations,9e97bb72b0474657'}) if r['return'] == 0 and len(r['list']) == 1: @@ -405,7 +411,7 @@ def postprocess(i): readme_init = "*Check [CM MLPerf docs](https://docs.mlcommons.org/inference) for more details.*\n\n" readme_body = "## Host platform\n\n* OS version: {}\n* CPU version: {}\n* Python version: {}\n* MLC version: {}\n\n".format(platform.platform(), - platform.processor(), sys.version, mlc.__version__) + platform.processor(), sys.version, mlc_version) x = repo_name if repo_hash != '': @@ -629,7 +635,7 @@ def postprocess(i): 'new_env', os.path.join( output_dir, - "os_info.json")) + "os_info.json"), mlc) dump_script_output( "detect,cpu", env, @@ -637,7 +643,7 @@ def postprocess(i): 'new_env', os.path.join( output_dir, - "cpu_info.json")) + "cpu_info.json"), mlc) env['MLC_DUMP_RAW_PIP_FREEZE_FILE_PATH'] = os.path.join( env['MLC_MLPERF_OUTPUT_DIR'], "pip_freeze.raw") dump_script_output( @@ -647,12 +653,12 @@ def postprocess(i): 'new_state', os.path.join( output_dir, - "pip_freeze.json")) + "pip_freeze.json"), mlc) return {'return': 0} -def dump_script_output(script_tags, env, state, output_key, dump_file): +def dump_script_output(script_tags, env, state, output_key, dump_file, mlc): mlc_input = {'action': 'run', 'automation': 'script', diff --git a/script/generate-mlperf-inference-submission/customize.py b/script/generate-mlperf-inference-submission/customize.py index 384b0c9b8..44ca20167 100644 --- a/script/generate-mlperf-inference-submission/customize.py +++ b/script/generate-mlperf-inference-submission/customize.py @@ -2,7 +2,6 @@ import os import json import shutil -import mlc import sys from tabulate import tabulate import mlperf_utils @@ -700,6 +699,7 @@ def generate_submission(env, state, inp, submission_division): 'env': {'MLC_PLATFORM_DETAILS_FILE_PATH': os.path.join(measurement_path, "system_info.txt")}, 'quiet': True } + mlc = i['automation'].action_object r = mlc.access(mlc_input) if r['return'] > 0: return r diff --git a/script/run-docker-container/customize.py b/script/run-docker-container/customize.py index 99f07fa14..9277ab7f0 100644 --- a/script/run-docker-container/customize.py +++ b/script/run-docker-container/customize.py @@ -248,7 +248,7 @@ def postprocess(i): CONTAINER = f"""{env['MLC_CONTAINER_TOOL']} run -dt {run_opts} --rm {docker_image_repo}/{docker_image_name}:{docker_image_tag} bash""" CMD = f"""ID=`{CONTAINER}` && {env['MLC_CONTAINER_TOOL']} exec $ID bash -c '{run_cmd}'""" - if not is_true(env.get('MLC_KEEP_DETACHED_CONTAINER', '')): + if is_true(env.get('MLC_KILL_DETACHED_CONTAINER', False)): CMD += f""" && {env['MLC_CONTAINER_TOOL']} kill $ID >/dev/null""" CMD += ' && echo "ID=$ID"' diff --git a/script/run-terraform/customize.py b/script/run-terraform/customize.py index 5728ce687..15687e6a9 100644 --- a/script/run-terraform/customize.py +++ b/script/run-terraform/customize.py @@ -1,5 +1,4 @@ from mlc import utils -import mlc import os import shutil import json @@ -72,6 +71,7 @@ def postprocess(i): cmd = cmd.replace(":", "=") cmd = cmd.replace(";;", ",") run_input['run_cmds'].append(cmd) + mlc = i['automation'].action_object r = mlc.access(run_input) if r['return'] > 0: return r diff --git a/setup.py b/setup.py index 5a18472c6..0ba25f32d 100644 --- a/setup.py +++ b/setup.py @@ -75,7 +75,7 @@ def run(self): 'target': 'repo', 'repo': 'mlcommons@mlperf-automations', 'branch': branch, - 'checkout': commit_hash + # 'checkout': commit_hash }) print(res) if res['return'] > 0: From 4fe42deabb6b081edb31fd3bb4e9b33a36404210 Mon Sep 17 00:00:00 2001 From: mlcommons-bot Date: Tue, 28 Jan 2025 21:35:53 +0000 Subject: [PATCH 30/36] Updated git_commit_hash.txt --- git_commit_hash.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git_commit_hash.txt b/git_commit_hash.txt index d21d33c55..0595c8eaf 100644 --- a/git_commit_hash.txt +++ b/git_commit_hash.txt @@ -1 +1 @@ -30a1c0c5227d564cf472bbc128a6d1919f29ad25 +fb9214c54056c69ff50db732ebc5362229c74988 From 3dacfdc14894006f456d3b14d1b174e2e9e6e19f Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Wed, 29 Jan 2025 00:30:52 +0000 Subject: [PATCH 31/36] Fix clean-nvidia-scratch-space (#168) * Update test-nvidia-mlperf-inference-implementations.yml * Fix clean-nvidia-scratch-space --- .../workflows/test-nvidia-mlperf-inference-implementations.yml | 2 +- VERSION | 2 +- script/clean-nvidia-mlperf-inference-scratch-space/customize.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-nvidia-mlperf-inference-implementations.yml b/.github/workflows/test-nvidia-mlperf-inference-implementations.yml index 300c8a188..e91258172 100644 --- a/.github/workflows/test-nvidia-mlperf-inference-implementations.yml +++ b/.github/workflows/test-nvidia-mlperf-inference-implementations.yml @@ -2,7 +2,7 @@ name: MLPerf Inference Nvidia implementations on: schedule: - - cron: "38 18 * * *" #to be adjusted + - cron: "36 00 * * *" #to be adjusted jobs: run_nvidia: diff --git a/VERSION b/VERSION index d169b2f2d..c5d54ec32 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.8 +0.0.9 diff --git a/script/clean-nvidia-mlperf-inference-scratch-space/customize.py b/script/clean-nvidia-mlperf-inference-scratch-space/customize.py index 2e3d4bc64..aeb40f6c0 100644 --- a/script/clean-nvidia-mlperf-inference-scratch-space/customize.py +++ b/script/clean-nvidia-mlperf-inference-scratch-space/customize.py @@ -30,6 +30,7 @@ def preprocess(i): cache_rm_tags = "nvidia-harness,_download_model,_sdxl" cache_rm_tags = cache_rm_tags + extra_cache_rm_tags + mlc = i['automation'].action_target if cache_rm_tags: r = mlc.access({'action': 'rm', 'automation': 'cache', From 615cc25c2ce346b6241391f718de4a58e2e4a112 Mon Sep 17 00:00:00 2001 From: mlcommons-bot Date: Wed, 29 Jan 2025 00:31:11 +0000 Subject: [PATCH 32/36] Updated git_commit_hash.txt --- git_commit_hash.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git_commit_hash.txt b/git_commit_hash.txt index 0595c8eaf..99d2a9a26 100644 --- a/git_commit_hash.txt +++ b/git_commit_hash.txt @@ -1 +1 @@ -fb9214c54056c69ff50db732ebc5362229c74988 +3dacfdc14894006f456d3b14d1b174e2e9e6e19f From 7cccb35aa3907f62065997308a2202552568a0da Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Wed, 29 Jan 2025 09:28:27 +0000 Subject: [PATCH 33/36] Bug fix (#169) * Fix bug in app-mlperf-inference --- .../workflows/test-nvidia-mlperf-inference-implementations.yml | 2 +- script/app-mlperf-inference/customize.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-nvidia-mlperf-inference-implementations.yml b/.github/workflows/test-nvidia-mlperf-inference-implementations.yml index e91258172..4a6b91c41 100644 --- a/.github/workflows/test-nvidia-mlperf-inference-implementations.yml +++ b/.github/workflows/test-nvidia-mlperf-inference-implementations.yml @@ -2,7 +2,7 @@ name: MLPerf Inference Nvidia implementations on: schedule: - - cron: "36 00 * * *" #to be adjusted + - cron: "35 01 * * *" jobs: run_nvidia: diff --git a/script/app-mlperf-inference/customize.py b/script/app-mlperf-inference/customize.py index f94dfa86b..243b78db1 100644 --- a/script/app-mlperf-inference/customize.py +++ b/script/app-mlperf-inference/customize.py @@ -59,6 +59,7 @@ def postprocess(i): inp = i['input'] env['CMD'] = '' state = i['state'] + mlc = i['automation'].action_object # if env.get('MLC_MLPERF_USER_CONF', '') == '': # return {'return': 0} @@ -376,7 +377,6 @@ def postprocess(i): # Check CM automation repository repo_name = 'mlcommons@mlperf-automations' repo_hash = '' - mlc = i['automation'].action_object r = mlc.access({'action': 'find', 'automation': 'repo', 'item': 'mlcommons@mlperf-automations,9e97bb72b0474657'}) if r['return'] == 0 and len(r['list']) == 1: From 7f1550ac1c2f254c951802093923a3c1423f7b86 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Wed, 29 Jan 2025 23:19:02 +0000 Subject: [PATCH 34/36] Fix typo in clean-nvidia-scratch-space (#170) --- script/clean-nvidia-mlperf-inference-scratch-space/customize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/clean-nvidia-mlperf-inference-scratch-space/customize.py b/script/clean-nvidia-mlperf-inference-scratch-space/customize.py index aeb40f6c0..6724c845e 100644 --- a/script/clean-nvidia-mlperf-inference-scratch-space/customize.py +++ b/script/clean-nvidia-mlperf-inference-scratch-space/customize.py @@ -30,7 +30,7 @@ def preprocess(i): cache_rm_tags = "nvidia-harness,_download_model,_sdxl" cache_rm_tags = cache_rm_tags + extra_cache_rm_tags - mlc = i['automation'].action_target + mlc = i['automation'].action_object if cache_rm_tags: r = mlc.access({'action': 'rm', 'automation': 'cache', From 1dd7264890508cba1118941b17c250d08c5b92b4 Mon Sep 17 00:00:00 2001 From: Arjun Suresh Date: Thu, 30 Jan 2025 20:40:04 +0000 Subject: [PATCH 35/36] Fix duplicate cache entries, added CacheAction inside ScriptAutomation (#171) * Fix typo in clean-nvidia-scratch-space * CacheAction object added inside ScriptAction, fix duplicate cache entries being created for git repos --- ...vidia-mlperf-inference-implementations.yml | 2 +- automation/script/module.py | 83 ++++++++++--------- .../customize.py | 6 +- 3 files changed, 50 insertions(+), 41 deletions(-) diff --git a/.github/workflows/test-nvidia-mlperf-inference-implementations.yml b/.github/workflows/test-nvidia-mlperf-inference-implementations.yml index 4a6b91c41..e4d780780 100644 --- a/.github/workflows/test-nvidia-mlperf-inference-implementations.yml +++ b/.github/workflows/test-nvidia-mlperf-inference-implementations.yml @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - system: [ "GO-spr", "phoenix", "GO-i9"] + system: [ "GO-spr", "phoenix"] # system: [ "mlc-server" ] python-version: [ "3.12" ] model: [ "resnet50", "retinanet", "bert-99", "bert-99.9", "gptj-99.9", "3d-unet-99.9", "sdxl" ] diff --git a/automation/script/module.py b/automation/script/module.py index cbb3bcb58..8e7556eb4 100644 --- a/automation/script/module.py +++ b/automation/script/module.py @@ -12,6 +12,7 @@ import logging from mlc.main import Automation +from mlc.main import CacheAction import mlc.utils as utils from utils import * @@ -39,6 +40,8 @@ def __init__(self, action_object, automation_file): self.logger = self.action_object.logger self.logger.propagate = False + # Create CacheAction using the same parent as the Script + self.cache_action = CacheAction(self.action_object.parent) self.tmp_file_env = 'tmp-env' self.tmp_file_env_all = 'tmp-env-all' self.tmp_file_run = 'tmp-run' @@ -351,7 +354,6 @@ def _run(self, i): skip_cache = i.get('skip_cache', False) force_cache = i.get('force_cache', False) - fake_run = i.get('fake_run', False) fake_run = i.get( 'fake_run', False) if 'fake_run' in i else i.get( @@ -670,7 +672,7 @@ def _run(self, i): search_cache = {'action': 'search', 'target_name': 'cache', 'tags': cache_tags_without_tmp_string} - rc = self.action_object.access(search_cache) + rc = self.cache_action.access(search_cache) if rc['return'] > 0: return rc @@ -1383,19 +1385,18 @@ def _run(self, i): ' - Creating new "cache" script artifact in the MLC local repository ...') logger.debug(recursion_spaces + ' - Tags: {}'.format(','.join(tmp_tags))) - if version != '': cached_meta['version'] = version ii = {'action': 'update', - 'automation': self.meta['deps']['cache'], + 'target': 'cache', 'search_tags': tmp_tags, 'script_alias': meta['alias'], 'tags': ','.join(tmp_tags), 'meta': cached_meta, 'force': True} - r = self.action_object.access(ii) + r = self.cache_action.access(ii) if r['return'] > 0: return r @@ -1495,8 +1496,9 @@ def _run(self, i): if r['return'] > 0: return r - if 'version-' + version not in cached_tags: - cached_tags.append('version-' + version) + r = get_version_tag_from_version(version, cached_tags) + if r['return'] > 0: + return r if default_version in versions: versions_meta = versions[default_version] @@ -1828,10 +1830,13 @@ def _run(self, i): # If return version if cache: - if r.get('version', '') != '': + version = r.get('version', '') + if version != '': cached_tags = [ x for x in cached_tags if not x.startswith('version-')] - cached_tags.append('version-' + r['version']) + r = get_version_tag_from_version(version, cached_tags) + if r['return'] > 0: + return r if len(r.get('add_extra_cache_tags', [])) > 0: for t in r['add_extra_cache_tags']: @@ -1873,9 +1878,14 @@ def _run(self, i): if r.get('version', '') != '': version = r.get('version') if cache: - cached_tags = [ - x for x in cached_tags if not x.startswith('version-')] - cached_tags.append('version-' + r['version']) + version = r.get('version', '') + if version != '': + cached_tags = [ + x for x in cached_tags if not x.startswith('version-')] + r = get_version_tag_from_version( + version, cached_tags) + if r['return'] > 0: + return r if len(r.get('add_extra_cache_tags', [])) > 0 and cache: for t in r['add_extra_cache_tags']: @@ -2034,14 +2044,14 @@ def _run(self, i): cached_meta['dependent_cached_path'] = dependent_cached_path ii = {'action': 'update', - 'automation': self.meta['deps']['cache'], + 'target': 'cache', 'uid': cached_uid, 'meta': cached_meta, 'script_alias': meta['alias'], 'replace_lists': True, # To replace tags 'tags': ','.join(cached_tags)} - r = self.action_object.access(ii) + r = self.cache_action.access(ii) if r['return'] > 0: return r @@ -4757,7 +4767,20 @@ def clean_some_tmp_files(self, i): return {'return': 0} +def get_version_tag_from_version(version, cached_tags): + tags_to_add = [] + if version != '': + if 'version-' + version not in cached_tags: + cached_tags.append('version-' + version) + if '-git-' in version: + version_without_git_commit = version.split("-git-")[0] + if 'version-' + version_without_git_commit not in cached_tags: + cached_tags.append('version-' + version_without_git_commit) + return {'return': 0} + ############################################################################## + + def find_cached_script(i): """ Internal automation function: find cached script @@ -4867,11 +4890,12 @@ def find_cached_script(i): recursion_spaces + ' - Prepared variations: {}'.format(variation_tags_string)) - # Add version - if version != '': - if 'version-' + version not in cached_tags: - cached_tags.append('version-' + version) - explicit_cached_tags.append('version-' + version) + r = get_version_tag_from_version(version, cached_tags) + if r['return'] > 0: + return r + get_version_tag_from_version(version, explicit_cached_tags) + if r['return'] > 0: + return r # Add extra cache tags (such as "virtual" for python) if len(extra_cache_tags) > 0: @@ -4905,9 +4929,9 @@ def find_cached_script(i): recursion_spaces + ' - Searching for cached script outputs with the following tags: {}'.format(search_tags)) - r = self_obj.action_object.access({'action': 'search', - 'target_name': 'cache', - 'tags': search_tags}) + r = self_obj.cache_action.access({'action': 'search', + 'target_name': 'cache', + 'tags': search_tags}) if r['return'] > 0: return r @@ -4986,21 +5010,6 @@ def find_cached_script(i): if r['return'] > 0: return r - # Check if pre-process and detect - # if 'preprocess' in dir(customize_code): - - # logger.debug(recursion_spaces + ' - Running preprocess ...') - - # ii = copy.deepcopy(customize_common_input) - # ii['env'] = env - # ii['meta'] = meta - # # may need to detect versions in multiple paths - # ii['run_script_input'] = run_script_input - - # r = customize_code.preprocess(ii) - # if r['return'] > 0: - # return r - ii = { 'run_script_input': run_script_input, 'env': env, diff --git a/script/clean-nvidia-mlperf-inference-scratch-space/customize.py b/script/clean-nvidia-mlperf-inference-scratch-space/customize.py index 6724c845e..d719c02a6 100644 --- a/script/clean-nvidia-mlperf-inference-scratch-space/customize.py +++ b/script/clean-nvidia-mlperf-inference-scratch-space/customize.py @@ -30,11 +30,11 @@ def preprocess(i): cache_rm_tags = "nvidia-harness,_download_model,_sdxl" cache_rm_tags = cache_rm_tags + extra_cache_rm_tags - mlc = i['automation'].action_object + mlc_cache = i['automation'].cache_action if cache_rm_tags: - r = mlc.access({'action': 'rm', 'automation': 'cache', - 'tags': cache_rm_tags, 'f': True}) + r = mlc_cache.access({'action': 'rm', 'target': 'cache', + 'tags': cache_rm_tags, 'f': True}) print(r) if r['return'] != 0 and r['return'] != 16: # ignore missing ones return r From 7ee9cf4a777f9fbada7d0d42fdb3ebb1d7939733 Mon Sep 17 00:00:00 2001 From: ANANDHU S <71482562+anandhu-eng@users.noreply.github.com> Date: Fri, 31 Jan 2025 15:27:55 +0530 Subject: [PATCH 36/36] Added support for automotive pointpainting benchmark (#172) * initial commit for pointpillar * rename benchmark name + add deps * [Automated Commit] Format Codebase * added scipy dependency * Revert "added scipy dependency" This reverts commit 3b3a0e86c8ff93bf6fa53aa5717a18338e8c7c1c. * add scipy dependency * version max as string * changes for registering to docker cache * added opencv dependency * add more dependencies * support custom query count for pointpainting * fix version check ffmpeg * version dlted * remove scipy dependencu * fixed scipy version * version change - test commit * integrated accuracy script * add extra loadgen options * fix for accuracy * added starting weights filename * commit for valid perf run * submission checker version defaulted to 5.0 * fix version bug * commit for compliance * add path to new env * add test 4 for pointpainting * commit to test numpy fix * add dep name for scipy * fix name keys --- .../customize.py | 24 +++++- .../meta.yaml | 74 ++++++++++++++++++- script/app-mlperf-inference/customize.py | 7 ++ script/app-mlperf-inference/meta.yaml | 52 +++++++++++++ script/get-generic-sys-util/meta.yaml | 37 ++++++++++ script/get-mlperf-inference-src/customize.py | 2 + script/get-mlperf-inference-src/meta.yaml | 1 + script/process-mlperf-accuracy/customize.py | 4 + script/process-mlperf-accuracy/meta.yaml | 4 + script/run-mlperf-inference-app/customize.py | 3 + script/run-mlperf-inference-app/meta.yaml | 4 + .../customize.py | 2 +- 12 files changed, 211 insertions(+), 3 deletions(-) diff --git a/script/app-mlperf-inference-mlcommons-python/customize.py b/script/app-mlperf-inference-mlcommons-python/customize.py index c621b74cf..b7c0a5efc 100644 --- a/script/app-mlperf-inference-mlcommons-python/customize.py +++ b/script/app-mlperf-inference-mlcommons-python/customize.py @@ -68,7 +68,7 @@ def preprocess(i): str(env['MLC_MLPERF_LOADGEN_BATCH_SIZE']) if env.get('MLC_MLPERF_LOADGEN_QUERY_COUNT', '') != '' and not env.get('MLC_TMP_IGNORE_MLPERF_QUERY_COUNT', False) and ( - env['MLC_MLPERF_LOADGEN_MODE'] == 'accuracy' or 'gptj' in env['MLC_MODEL'] or 'llama2' in env['MLC_MODEL'] or 'mixtral' in env['MLC_MODEL'] or 'llama3' in env['MLC_MODEL']) and env.get('MLC_MLPERF_RUN_STYLE', '') != "valid": + env['MLC_MLPERF_LOADGEN_MODE'] == 'accuracy' or 'gptj' in env['MLC_MODEL'] or 'llama2' in env['MLC_MODEL'] or 'mixtral' in env['MLC_MODEL'] or 'llama3' in env['MLC_MODEL'] or 'pointpainting' in env['MLC_MODEL']) and (env.get('MLC_MLPERF_RUN_STYLE', '') != "valid" or 'pointpainting' in env['MLC_MODEL']): env['MLC_MLPERF_LOADGEN_EXTRA_OPTIONS'] += " --count " + \ env['MLC_MLPERF_LOADGEN_QUERY_COUNT'] @@ -524,6 +524,28 @@ def get_run_cmd_reference( cmd = cmd.replace("--count", "--total-sample-count") cmd = cmd.replace("--max-batchsize", "--batch-size") + elif "pointpainting" in env['MLC_MODEL']: + env['RUN_DIR'] = os.path.join( + env['MLC_MLPERF_INFERENCE_SOURCE'], + "automotive", + "3d-object-detection") + + cmd = env['MLC_PYTHON_BIN_WITH_PATH'] + " main.py " \ + " --dataset waymo" + \ + " --dataset-path " + env['MLC_DATASET_WAYMO_PATH'] + \ + " --lidar-path " + env['MLC_ML_MODEL_POINT_PILLARS_PATH'] + \ + " --segmentor-path " + env['MLC_ML_MODEL_DPLAB_RESNET50_PATH'] + \ + " --scenario " + env['MLC_MLPERF_LOADGEN_SCENARIO'] + \ + " --output " + env['MLC_MLPERF_OUTPUT_DIR'] + \ + " --dtype " + env['MLC_MLPERF_MODEL_PRECISION'].replace("float", "fp") + \ + scenario_extra_options + \ + env['MLC_MLPERF_LOADGEN_EXTRA_OPTIONS'] + mode_extra_options + + if env.get('MLC_MLPERF_POINTPAINTING_TIME', '') != '': + cmd += f" --time {env['MLC_MLPERF_POINTPAINTING_TIME']}" + + print(cmd) + if env.get('MLC_NETWORK_LOADGEN', '') in ["lon", "sut"]: cmd = cmd + " " + "--network " + env['MLC_NETWORK_LOADGEN'] if env.get('MLC_NETWORK_LOADGEN_SUT_SERVERS', []): diff --git a/script/app-mlperf-inference-mlcommons-python/meta.yaml b/script/app-mlperf-inference-mlcommons-python/meta.yaml index a23acee4d..17907749f 100644 --- a/script/app-mlperf-inference-mlcommons-python/meta.yaml +++ b/script/app-mlperf-inference-mlcommons-python/meta.yaml @@ -66,6 +66,9 @@ input_mapping: multistream_target_latency: MLC_MLPERF_LOADGEN_MULTISTREAM_TARGET_LATENCY network: MLC_NETWORK_LOADGEN sut_servers: MLC_NETWORK_LOADGEN_SUT_SERVERS + pointpillars_checkpoint_path: MLC_ML_MODEL_POINT_PILLARS_PATH + deeplab_resnet50_path: MLC_ML_MODEL_DPLAB_RESNET50_PATH + waymo_path: MLC_DATASET_WAYMO_PATH # Duplicate CM environment variables to the ones used in native apps env_key_mappings: @@ -491,7 +494,6 @@ deps: - tags: get,ml-model,llama3 names: - llama3-405b-model - - llama3-402b-model enable_if_env: MLC_MODEL: - llama3_1-405b @@ -502,6 +504,26 @@ deps: MLC_RUN_STATE_DOCKER: - "yes" + ## pointpainting + - tags: get,ml-model,pointpillars + names: + - pointpillars-model + enable_if_env: + MLC_MODEL: + - pointpainting + skip_if_env: + MLC_RUN_STATE_DOCKER: + - "yes" + - tags: get,ml-model,resnet50-deeplab + enable_if_env: + MLC_MODEL: + - pointpainting + skip_if_env: + MLC_RUN_STATE_DOCKER: + - "yes" + names: + - resnet50-deeplab-model + ######################################################################## # Install datasets @@ -641,6 +663,17 @@ deps: MLC_USE_DATASET_FROM_HOST: - "yes" + ## waymo for pointpillats + - tags: get,dataset,waymo + enable_if_env: + MLC_MODEL: + - pointpainting + skip_if_env: + MLC_RUN_STATE_DOCKER: + - "yes" + names: + - waymo-dataset + ## llama3_1 dataset - tags: get,dataset,mlperf,inference,llama3,_validation names: @@ -1299,6 +1332,45 @@ variations: MLC_TMP_GENERIC_PYTHON_PIP_EXTRA_FIND_LINKS_URL: "https://data.pyg.org/whl/torch-<<>>+cpu.html" MLC_TMP_GENERIC_PYTHON_PIP_EXTRA_FIND_LINKS_URL_DGL: "https://data.dgl.ai/wheels/torch-<<>>/repo.html" + pointpainting: + group: models + env: + MLC_MODEL: pointpainting + MLC_ML_MODEL_STARTING_WEIGHTS_FILENAME: "https://github.com/mlcommons/mlperf-automations/tree/dev/script/get-ml-model-pointpainting,https://github.com/mlcommons/mlperf-automations/tree/dev/script/get-ml-model-resnet50-deeplab" + adr: + pytorch: + version_max: "2.2.2" + torchvision: + version_max: "0.17.2" + deps: + - tags: get,generic-python-lib,_package.shapely + - tags: get,generic-python-lib,_package.numba + - tags: get,generic-python-lib,_package.open3d + - tags: get,generic-python-lib,_package.numpy + version_max: "1.26.4" + names: + - numpy + - tags: get,generic-python-lib,_package.numpy + version_max: "2.0.2" + names: + - numpy-upgrade + - tags: get,generic-python-lib,_package.numpy + version_max: "1.26.4" + names: + - numpy-downgrade + - tags: get,generic-python-lib,_package.tensorboard + - tags: get,generic-python-lib,_package.onnxruntime + - tags: get,generic-python-lib,_package.opencv-python + - tags: get,generic-python-lib,_package.scikit-image + - tags: get,generic-python-lib,_package.scipy + version_max: "1.11.2" + names: + - scipy + - tags: get,generic-python-lib,_package.ninja + - tags: get,generic-sys-util,_ffmpeg + - tags: get,generic-sys-util,_libsm6 + - tags: get,generic-sys-util,_libxext6 + llama3_1-405b: group: models env: diff --git a/script/app-mlperf-inference/customize.py b/script/app-mlperf-inference/customize.py index 243b78db1..f5e3619d0 100644 --- a/script/app-mlperf-inference/customize.py +++ b/script/app-mlperf-inference/customize.py @@ -129,6 +129,13 @@ def postprocess(i): accuracy_log_file_option_name = " --mlperf-accuracy-file " datatype_option = " --dtype " + env['MLC_IMAGENET_ACCURACY_DTYPE'] + elif model == "pointpainting": + accuracy_filename = "accuracy-waymo.py" + accuracy_filepath = os.path.join( + env['MLC_MLPERF_INFERENCE_POINTPAINTING_PATH'], accuracy_filename) + accuracy_log_file_option_name = " --mlperf-accuracy-file " + datatype_option = "" + elif model == "retinanet": accuracy_filename = "accuracy-openimages.py" accuracy_filepath = os.path.join(env['MLC_MLPERF_INFERENCE_CLASSIFICATION_AND_DETECTION_PATH'], "tools", diff --git a/script/app-mlperf-inference/meta.yaml b/script/app-mlperf-inference/meta.yaml index a8381c323..9330c6d49 100644 --- a/script/app-mlperf-inference/meta.yaml +++ b/script/app-mlperf-inference/meta.yaml @@ -261,6 +261,10 @@ variations: reference,rgat: default_variations: backend: pytorch + + reference,pointpainting: + default_variations: + backend: pytorch reference,sdxl_: default_variations: @@ -823,6 +827,51 @@ variations: - igbh-original - igbh-dataset + pointpainting: + group: + model + add_deps_recursive: + mlperf-inference-implementation: + tags: _pointpainting + env: + MLC_MODEL: + pointpainting + docker: + deps: + - tags: get,dataset,waymo + enable_if_env: + MLC_USE_DATASET_FROM_HOST: + - 'yes' + names: + - waymo-dataset + - tags: get,ml-model,pointpillars + enable_if_env: + MLC_USE_DATASET_FROM_HOST: + - 'yes' + names: + - pointpillars-model + - tags: get,ml-model,resnet50-deeplab + enable_if_env: + MLC_USE_DATASET_FROM_HOST: + - 'yes' + names: + - resnet50-deeplab-model + posthook_deps: + - enable_if_env: + MLC_MLPERF_LOADGEN_MODE: + - accuracy + - all + MLC_MLPERF_ACCURACY_RESULTS_DIR: + - 'on' + skip_if_env: + MLC_MLPERF_IMPLEMENTATION: + - nvidia + names: + - mlperf-accuracy-script + - waymo-accuracy-script + tags: run,accuracy,mlperf,_waymo + + llama3_1-405b: group: model @@ -1904,6 +1953,9 @@ docker: - "${{ MLC_DATASET_KITS19_PREPROCESSED_PATH }}:${{ MLC_DATASET_KITS19_PREPROCESSED_PATH }}" - "${{ MLC_DATASET_IGBH_PATH }}:${{ MLC_DATASET_IGBH_PATH }}" - "${{ MLC_ML_MODEL_RGAT_CHECKPOINT_PATH }}:${{ MLC_ML_MODEL_RGAT_CHECKPOINT_PATH }}" + - "${{ MLC_DATASET_WAYMO_PATH }}:${{ MLC_DATASET_WAYMO_PATH }}" + - "${{ MLC_ML_MODEL_POINT_PILLARS_PATH }}:${{ MLC_ML_MODEL_POINT_PILLARS_PATH }}" + - "${{ MLC_ML_MODEL_DPLAB_RESNET50_PATH }}:${{ MLC_ML_MODEL_DPLAB_RESNET50_PATH }}" skip_run_cmd: 'no' shm_size: '32gb' interactive: True diff --git a/script/get-generic-sys-util/meta.yaml b/script/get-generic-sys-util/meta.yaml index 0436ba72d..d4ef86050 100644 --- a/script/get-generic-sys-util/meta.yaml +++ b/script/get-generic-sys-util/meta.yaml @@ -95,6 +95,43 @@ variations: brew: '' dnf: dmidecode yum: dmidecode + ffmpeg: + env: + MLC_SYS_UTIL_NAME: ffmpeg + MLC_SYS_UTIL_VERSION_CMD: ffmpeg -version # tbd: regular expression for version + MLC_TMP_VERSION_DETECT_GROUP_NUMBER: 0 + new_env_keys: + - MLC_FFMPEG_VERSION + state: + ffmpeg: # tbd: complete for other flavours of linux + apt: ffmpeg + brew: '' + dnf: '' + yum: '' + libsm6: + env: + MLC_SYS_UTIL_NAME: libsm6 # tbd: regular expression for version as well as whether its installed? + MLC_TMP_VERSION_DETECT_GROUP_NUMBER: 0 + new_env_keys: + - MLC_LIBSM6_VERSION + state: + libsm6: # tbd: complete for other flavours of linux + apt: libsm6 + brew: '' + dnf: '' + yum: '' + libxext6: + env: + MLC_SYS_UTIL_NAME: libxext6 # tbd: regular expression for version as well as whether its installed? + MLC_TMP_VERSION_DETECT_GROUP_NUMBER: 0 + new_env_keys: + - MLC_LIBEXT6_VERSION + state: + libxext6: # tbd: complete for other flavours of linux + apt: libxext6 + brew: '' + dnf: '' + yum: '' g++-11: env: MLC_GENERIC_SYS_UTIL_IGNORE_VERSION_DETECTION_FAILURE: 'yes' diff --git a/script/get-mlperf-inference-src/customize.py b/script/get-mlperf-inference-src/customize.py index d523f6abe..4fb2c0c31 100644 --- a/script/get-mlperf-inference-src/customize.py +++ b/script/get-mlperf-inference-src/customize.py @@ -114,6 +114,8 @@ def postprocess(i): inference_root, 'graph', 'R-GAT') env['MLC_MLPERF_INFERENCE_3DUNET_PATH'] = os.path.join( inference_root, 'vision', 'medical_imaging', '3d-unet-kits19') + env['MLC_MLPERF_INFERENCE_POINTPAINTING_PATH'] = os.path.join( + inference_root, 'automotive', '3d-object-detection') env['MLC_GET_DEPENDENT_CACHED_PATH'] = inference_root diff --git a/script/get-mlperf-inference-src/meta.yaml b/script/get-mlperf-inference-src/meta.yaml index 1d2db1989..919178125 100644 --- a/script/get-mlperf-inference-src/meta.yaml +++ b/script/get-mlperf-inference-src/meta.yaml @@ -30,6 +30,7 @@ new_env_keys: - MLC_MLPERF_INFERENCE_VERSION - MLC_MLPERF_INFERENCE_VISION_PATH - MLC_MLPERF_LAST_RELEASE +- MLC_MLPERF_INFERENCE_POINTPAINTING_PATH - +PYTHONPATH prehook_deps: - env: diff --git a/script/process-mlperf-accuracy/customize.py b/script/process-mlperf-accuracy/customize.py index c3cf7ac1b..967fde0b5 100644 --- a/script/process-mlperf-accuracy/customize.py +++ b/script/process-mlperf-accuracy/customize.py @@ -202,6 +202,10 @@ def preprocess(i): CMD = env['MLC_PYTHON_BIN_WITH_PATH'] + " '" + os.path.join(env['MLC_MLPERF_INFERENCE_SOURCE'], "language", "llama3.1-405b", "evaluate-accuracy.py") + "' --checkpoint-path '" + env['MLC_ML_MODEL_LLAMA3_CHECKPOINT_PATH'] + "' --mlperf-accuracy-file '" + os.path.join( result_dir, "mlperf_log_accuracy.json") + "' --dtype '" + env['MLC_ACCURACY_DTYPE'] + "' --dataset-file '" + env['MLC_DATASET_LLAMA3_PATH'] + "' > '" + out_file + "'" + elif dataset == "waymo": + CMD = env['MLC_PYTHON_BIN_WITH_PATH'] + " '" + os.path.join(env['MLC_MLPERF_INFERENCE_SOURCE'], "automotive", "3d-object-detection", "accuracy_waymo.py") + "' --mlperf-accuracy-file '" + os.path.join( + result_dir, "mlperf_log_accuracy.json") + "' --waymo-dir '" + env['MLC_DATASET_WAYMO_PATH'] + "' > '" + out_file + "'" + else: return {'return': 1, 'error': 'Unsupported dataset'} diff --git a/script/process-mlperf-accuracy/meta.yaml b/script/process-mlperf-accuracy/meta.yaml index f45e3f485..cd14ef67a 100644 --- a/script/process-mlperf-accuracy/meta.yaml +++ b/script/process-mlperf-accuracy/meta.yaml @@ -269,3 +269,7 @@ variations: env: MLC_DATASET: dataset_llama3 group: dataset + waymo: + env: + MLC_DATASET: waymo + group: dataset diff --git a/script/run-mlperf-inference-app/customize.py b/script/run-mlperf-inference-app/customize.py index 3ac4e5a94..885d1d8b3 100644 --- a/script/run-mlperf-inference-app/customize.py +++ b/script/run-mlperf-inference-app/customize.py @@ -128,6 +128,9 @@ def preprocess(i): test_list.remove("TEST01") # test_list.remove("TEST05") + if "pointpainting" in env["MLC_MODEL"].lower(): + test_list.append("TEST04") + if "llama2" in env['MLC_MODEL'].lower( ) or "mixtral-8x7b" in env['MLC_MODEL']: test_list.append("TEST06") diff --git a/script/run-mlperf-inference-app/meta.yaml b/script/run-mlperf-inference-app/meta.yaml index 9dc4408d6..71db7e876 100644 --- a/script/run-mlperf-inference-app/meta.yaml +++ b/script/run-mlperf-inference-app/meta.yaml @@ -119,6 +119,9 @@ input_mapping: use_dataset_from_host: MLC_USE_DATASET_FROM_HOST use_model_from_host: MLC_USE_MODEL_FROM_HOST rgat_checkpoint_path: RGAT_CHECKPOINT_PATH + pointpillars_checkpoint_path: MLC_ML_MODEL_POINT_PILLARS_PATH + deeplab_resnet50_path: MLC_ML_MODEL_DPLAB_RESNET50_PATH + waymo_path: MLC_DATASET_WAYMO_PATH new_state_keys: - app_mlperf_inference_* @@ -466,6 +469,7 @@ input_description: - efficientnet - rgat - llama3_1-405b + - pointpainting default: resnet50 desc: MLPerf model sort: 200 diff --git a/script/run-mlperf-inference-submission-checker/customize.py b/script/run-mlperf-inference-submission-checker/customize.py index 4d22d4867..818981d72 100644 --- a/script/run-mlperf-inference-submission-checker/customize.py +++ b/script/run-mlperf-inference-submission-checker/customize.py @@ -11,7 +11,7 @@ def preprocess(i): submission_dir = env.get("MLC_MLPERF_INFERENCE_SUBMISSION_DIR", "") - version = env.get('MLC_MLPERF_SUBMISSION_CHECKER_VERSION', '') + version = env.get('MLC_MLPERF_SUBMISSION_CHECKER_VERSION', 'v5.0') if submission_dir == "": return {'return': 1,