litert-cli-nightly 0.1.0.dev20260513__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- litert_cli/__init__.py +15 -0
- litert_cli/commands/benchmark/__init__.py +16 -0
- litert_cli/commands/benchmark/android.py +177 -0
- litert_cli/commands/benchmark/cli.py +163 -0
- litert_cli/commands/benchmark/gcp.py +257 -0
- litert_cli/commands/clean.py +73 -0
- litert_cli/commands/compile.py +199 -0
- litert_cli/commands/convert/__init__.py +20 -0
- litert_cli/commands/convert/cli.py +148 -0
- litert_cli/commands/convert/generic.py +154 -0
- litert_cli/commands/convert/huggingface.py +105 -0
- litert_cli/commands/delete.py +56 -0
- litert_cli/commands/download.py +274 -0
- litert_cli/commands/import.py +124 -0
- litert_cli/commands/list.py +132 -0
- litert_cli/commands/lm.py +74 -0
- litert_cli/commands/quantize.py +201 -0
- litert_cli/commands/run/__init__.py +16 -0
- litert_cli/commands/run/android.py +394 -0
- litert_cli/commands/run/cli.py +297 -0
- litert_cli/commands/run/desktop.py +340 -0
- litert_cli/commands/visualize.py +234 -0
- litert_cli/core/android_utils.py +304 -0
- litert_cli/core/android_utils_test.py +236 -0
- litert_cli/core/constants.py +125 -0
- litert_cli/core/deps.py +161 -0
- litert_cli/core/deps_test.py +97 -0
- litert_cli/core/inputs.py +203 -0
- litert_cli/core/inputs_test.py +176 -0
- litert_cli/core/log_filters.py +50 -0
- litert_cli/core/models.py +96 -0
- litert_cli/core/npu_utils.py +382 -0
- litert_cli/core/targets_manager.py +192 -0
- litert_cli/core/utils.py +43 -0
- litert_cli/litert.py +99 -0
- litert_cli/litert_help_test.py +51 -0
- litert_cli/litert_test.py +88 -0
- litert_cli/models/__init__.py +142 -0
- litert_cli/models/asr/__init__.py +15 -0
- litert_cli/models/asr/asr_model.py +108 -0
- litert_cli/models/asr/parakeet_ctc.py +165 -0
- litert_cli/models/asr/runner.py +482 -0
- litert_cli/models/base.py +57 -0
- litert_cli/test_data/dummy_cv_model.py +52 -0
- litert_cli/test_data/dummy_cv_model.tflite +0 -0
- litert_cli/test_data/generate_test_inputs.py +51 -0
- litert_cli/test_data/mobilenet_v3_calib_data.py +25 -0
- litert_cli/test_data/quantize_recipe.json +16 -0
- litert_cli/test_data/resnet18.py +31 -0
- litert_cli_nightly-0.1.0.dev20260513.dist-info/METADATA +37 -0
- litert_cli_nightly-0.1.0.dev20260513.dist-info/RECORD +61 -0
- litert_cli_nightly-0.1.0.dev20260513.dist-info/WHEEL +5 -0
- litert_cli_nightly-0.1.0.dev20260513.dist-info/entry_points.txt +2 -0
- litert_cli_nightly-0.1.0.dev20260513.dist-info/licenses/LICENSE +202 -0
- litert_cli_nightly-0.1.0.dev20260513.dist-info/top_level.txt +2 -0
- test_scripts/litert_cli.ipynb +282 -0
- test_scripts/litert_cli_test_commands.sh +333 -0
- test_scripts/run_cli_demo.sh +88 -0
- test_scripts/run_cli_npu.sh +116 -0
- test_scripts/run_cli_oss.sh +58 -0
- test_scripts/run_smoke_tests.sh +55 -0
litert_cli/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright 2026 The LiteRT CLI Authors.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Copyright 2026 The LiteRT CLI Authors.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
|
|
16
|
+
"""CLI command for benchmarking LiteRT models."""
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Copyright 2026 The LiteRT CLI Authors.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
|
|
16
|
+
"""Android Benchmark Module."""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import pathlib
|
|
21
|
+
import shlex
|
|
22
|
+
import subprocess
|
|
23
|
+
|
|
24
|
+
import click
|
|
25
|
+
from litert_cli.core import android_utils
|
|
26
|
+
from litert_cli.core import constants
|
|
27
|
+
from litert_cli.core import npu_utils as npu
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def run_android(*, model_path: pathlib.Path, accelerator: str) -> None:
|
|
31
|
+
"""Runs the model on an Android device.
|
|
32
|
+
|
|
33
|
+
Pushes the model and benchmark_model binary to the device and runs it.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
model_path: Path to the local LiteRT model file.
|
|
37
|
+
accelerator: Hardware accelerator to use (cpu, gpu, npu).
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
subprocess.CalledProcessError: If any adb command fails on the device.
|
|
41
|
+
"""
|
|
42
|
+
click.echo("Preparing to run on Android device via adb...")
|
|
43
|
+
|
|
44
|
+
android_utils.check_adb()
|
|
45
|
+
abi = android_utils.get_android_abi()
|
|
46
|
+
click.echo(f"Detected Android device ABI: {abi}")
|
|
47
|
+
|
|
48
|
+
cli_android_root = constants.LITERT_CLI_ANDROID_ROOT
|
|
49
|
+
|
|
50
|
+
model_name = model_path.name
|
|
51
|
+
remote_model_path = f"{cli_android_root}/{model_name}"
|
|
52
|
+
|
|
53
|
+
benchmark_model_bin = android_utils.find_android_binary(
|
|
54
|
+
"benchmark_model", abi
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
remote_dispatch_dir = ""
|
|
58
|
+
if accelerator == "npu":
|
|
59
|
+
remote_dispatch_dir = npu.push_npu_runtime_libraries(None, cli_android_root)
|
|
60
|
+
|
|
61
|
+
# Download and push SOC-specific LiteRT dispatch and compiler plugin libraries
|
|
62
|
+
target_model = npu.get_soc_target_model(None)
|
|
63
|
+
soc_vendor = "mediatek" if "mt" in target_model else "qualcomm"
|
|
64
|
+
lib_dispatch = android_utils.find_npu_dispatch_lib(soc_vendor, abi)
|
|
65
|
+
lib_compiler = android_utils.find_npu_compiler_plugin_lib(soc_vendor, abi)
|
|
66
|
+
|
|
67
|
+
remote_lib_dispatch = f"{cli_android_root}/{lib_dispatch.name}"
|
|
68
|
+
if (
|
|
69
|
+
subprocess.run(
|
|
70
|
+
["adb", "shell", f"[ -f {remote_lib_dispatch} ]"], check=False
|
|
71
|
+
).returncode
|
|
72
|
+
== 0
|
|
73
|
+
):
|
|
74
|
+
click.echo(f" Skipping {lib_dispatch.name} (already on device)")
|
|
75
|
+
else:
|
|
76
|
+
click.echo(f"Pushing {lib_dispatch.name} to device...")
|
|
77
|
+
subprocess.run(
|
|
78
|
+
["adb", "push", str(lib_dispatch), remote_lib_dispatch], check=True
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
remote_lib_compiler = f"{cli_android_root}/{lib_compiler.name}"
|
|
82
|
+
if (
|
|
83
|
+
subprocess.run(
|
|
84
|
+
["adb", "shell", f"[ -f {remote_lib_compiler} ]"], check=False
|
|
85
|
+
).returncode
|
|
86
|
+
== 0
|
|
87
|
+
):
|
|
88
|
+
click.echo(f" Skipping {lib_compiler.name} (already on device)")
|
|
89
|
+
else:
|
|
90
|
+
click.echo(f"Pushing {lib_compiler.name} to device...")
|
|
91
|
+
subprocess.run(
|
|
92
|
+
["adb", "push", str(lib_compiler), remote_lib_compiler], check=True
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
click.echo(f"Pushing model {model_name} to device...")
|
|
96
|
+
subprocess.run(["adb", "shell", "mkdir", "-p", cli_android_root], check=True)
|
|
97
|
+
subprocess.run(
|
|
98
|
+
["adb", "push", str(model_path), remote_model_path], check=True
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
click.echo("Pushing benchmark_model to device...")
|
|
102
|
+
subprocess.run(
|
|
103
|
+
[
|
|
104
|
+
"adb",
|
|
105
|
+
"push",
|
|
106
|
+
str(benchmark_model_bin),
|
|
107
|
+
f"{cli_android_root}/benchmark_model",
|
|
108
|
+
],
|
|
109
|
+
check=True,
|
|
110
|
+
)
|
|
111
|
+
subprocess.run(
|
|
112
|
+
["adb", "shell", "chmod", "+x", f"{cli_android_root}/benchmark_model"],
|
|
113
|
+
check=True,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
click.echo("Executing benchmark on device...\n")
|
|
117
|
+
try:
|
|
118
|
+
bench_args = [
|
|
119
|
+
f"{cli_android_root}/benchmark_model",
|
|
120
|
+
f"--graph={shlex.quote(remote_model_path)}",
|
|
121
|
+
]
|
|
122
|
+
if accelerator == "gpu":
|
|
123
|
+
bench_args.append("--use_gpu=true")
|
|
124
|
+
elif accelerator == "npu":
|
|
125
|
+
bench_args.append("--use_npu=true")
|
|
126
|
+
bench_args.append(f"--dispatch_library_path={shlex.quote(cli_android_root)}")
|
|
127
|
+
bench_args.append(
|
|
128
|
+
f"--compiler_plugin_library_path={shlex.quote(cli_android_root)}"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if soc_vendor == "mediatek":
|
|
132
|
+
recommend_version = constants.MEDIATEK_SOC_VERSION_MAP.get(
|
|
133
|
+
target_model, ""
|
|
134
|
+
)
|
|
135
|
+
if "v9" in recommend_version:
|
|
136
|
+
bench_args.append("--mediatek_nerun_pilot_version=version9")
|
|
137
|
+
elif "v8" in recommend_version:
|
|
138
|
+
bench_args.append("--mediatek_nerun_pilot_version=version8")
|
|
139
|
+
|
|
140
|
+
env_vars = ""
|
|
141
|
+
if remote_dispatch_dir:
|
|
142
|
+
quoted_dispatch_dir = shlex.quote(remote_dispatch_dir)
|
|
143
|
+
env_vars = (
|
|
144
|
+
f"LD_LIBRARY_PATH={quoted_dispatch_dir} "
|
|
145
|
+
f"ADSP_LIBRARY_PATH={quoted_dispatch_dir} "
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
full_command = env_vars + " ".join(bench_args)
|
|
149
|
+
process = subprocess.Popen(
|
|
150
|
+
["adb", "shell", full_command],
|
|
151
|
+
stdout=subprocess.PIPE,
|
|
152
|
+
stderr=subprocess.STDOUT,
|
|
153
|
+
text=True,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
from litert_cli.core.log_filters import BenchmarkLogFilter
|
|
157
|
+
|
|
158
|
+
output_lines = []
|
|
159
|
+
log_filter = BenchmarkLogFilter(constants.DEFAULT_QUIET)
|
|
160
|
+
|
|
161
|
+
for line in process.stdout:
|
|
162
|
+
output_lines.append(line)
|
|
163
|
+
if log_filter.should_show(line):
|
|
164
|
+
click.echo(line, nl=False)
|
|
165
|
+
|
|
166
|
+
process.wait()
|
|
167
|
+
if process.returncode != 0:
|
|
168
|
+
click.secho(
|
|
169
|
+
f"Execution failed on device with exit code {process.returncode}",
|
|
170
|
+
fg="red",
|
|
171
|
+
)
|
|
172
|
+
click.echo("Full output for debugging:")
|
|
173
|
+
for line in output_lines:
|
|
174
|
+
click.echo(line, nl=False)
|
|
175
|
+
raise click.ClickException("Benchmark failed on device.")
|
|
176
|
+
except Exception as e:
|
|
177
|
+
raise click.ClickException(f"Failed to execute benchmark on device: {e}")
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Copyright 2026 The LiteRT CLI Authors.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
|
|
16
|
+
"""CLI Benchmark Module providing commands to benchmark LiteRT models.
|
|
17
|
+
|
|
18
|
+
This module defines the `benchmark` command using Click. It supports:
|
|
19
|
+
1. Benchmarking on a local Android device via adb (Default).
|
|
20
|
+
2. Benchmarking on Google Cloud Platform (GCP) via AI Edge Portal Cloud API.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import pathlib
|
|
26
|
+
import textwrap
|
|
27
|
+
|
|
28
|
+
import click
|
|
29
|
+
from litert_cli.core import constants
|
|
30
|
+
from litert_cli.core import utils
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@click.command(
|
|
34
|
+
"benchmark",
|
|
35
|
+
help="""\b
|
|
36
|
+
Benchmark LiteRT models on different platforms.
|
|
37
|
+
\b
|
|
38
|
+
MODEL: Path to the LiteRT model file.
|
|
39
|
+
\b
|
|
40
|
+
Examples:
|
|
41
|
+
\b
|
|
42
|
+
# Benchmark on Desktop with CPU (Default) or GPU
|
|
43
|
+
\b
|
|
44
|
+
$ litert benchmark model.tflite
|
|
45
|
+
$ litert benchmark model.tflite --gpu
|
|
46
|
+
\b
|
|
47
|
+
# Benchmark on Android with CPU (Default) or GPU or NPU
|
|
48
|
+
$ litert benchmark model.tflite --android
|
|
49
|
+
$ litert benchmark model.tflite --android --gpu
|
|
50
|
+
$ litert benchmark model.tflite --android --npu
|
|
51
|
+
\b
|
|
52
|
+
# Benchmark on Google AI Edge Portal in Google Cloud. Prerequisites:
|
|
53
|
+
# - Set up your Google AI Edge Portal account by following up the instructions at:
|
|
54
|
+
# https://ai.google.dev/edge/ai-edge-portal
|
|
55
|
+
# - Set up authentication by running: gcloud auth login
|
|
56
|
+
# - You can set the default GCP project by setting the environment variable LITERT_GCP_PROJECT,
|
|
57
|
+
# or by providing the --gcp-project option.
|
|
58
|
+
#
|
|
59
|
+
$ litert benchmark model.tflite --gcp --device "pixel 7"
|
|
60
|
+
$ litert benchmark model.tflite --gcp --device "pixel 7" --gcp-project "your-gcp-project-id"
|
|
61
|
+
$ litert benchmark model.tflite --gcp --devices "pixel 7, sm-s931u1" --gpu
|
|
62
|
+
""",
|
|
63
|
+
)
|
|
64
|
+
@click.argument("model", type=str)
|
|
65
|
+
@click.option(
|
|
66
|
+
"--android",
|
|
67
|
+
"target",
|
|
68
|
+
flag_value="android",
|
|
69
|
+
default=True,
|
|
70
|
+
help="Benchmark on Android device (Default)",
|
|
71
|
+
)
|
|
72
|
+
@click.option(
|
|
73
|
+
"--gcp",
|
|
74
|
+
"target",
|
|
75
|
+
flag_value="gcp",
|
|
76
|
+
help="Benchmark on Google AI Edge Portal in Google Cloud.",
|
|
77
|
+
)
|
|
78
|
+
@click.option(
|
|
79
|
+
"--cpu",
|
|
80
|
+
"accelerator",
|
|
81
|
+
flag_value="cpu",
|
|
82
|
+
default=True,
|
|
83
|
+
help="Use CPU accelerator (Default)",
|
|
84
|
+
)
|
|
85
|
+
@click.option(
|
|
86
|
+
"--gpu",
|
|
87
|
+
"accelerator",
|
|
88
|
+
flag_value="gpu",
|
|
89
|
+
help="Use GPU accelerator",
|
|
90
|
+
)
|
|
91
|
+
@click.option(
|
|
92
|
+
"--npu",
|
|
93
|
+
"accelerator",
|
|
94
|
+
flag_value="npu",
|
|
95
|
+
help="Use NPU accelerator",
|
|
96
|
+
)
|
|
97
|
+
@click.option(
|
|
98
|
+
"--device",
|
|
99
|
+
"--devices",
|
|
100
|
+
"devices",
|
|
101
|
+
type=str,
|
|
102
|
+
multiple=True,
|
|
103
|
+
default=["pixel 7"],
|
|
104
|
+
help=(
|
|
105
|
+
"Target device model name(s) (e.g., 'pixel 7'). Can be specified"
|
|
106
|
+
" --device multiple times or use --devices 'pixel 7, sm-s931u1'."
|
|
107
|
+
" Default is 'pixel 7'"
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
@click.option(
|
|
111
|
+
"--gcp-project",
|
|
112
|
+
type=str,
|
|
113
|
+
help="GCP project ID for benchmarking (Only for GCP target).",
|
|
114
|
+
)
|
|
115
|
+
def benchmark_cmd(
|
|
116
|
+
model: str,
|
|
117
|
+
target: str,
|
|
118
|
+
accelerator: str,
|
|
119
|
+
devices: tuple[str, ...],
|
|
120
|
+
gcp_project: str | None = None,
|
|
121
|
+
) -> None:
|
|
122
|
+
"""Benchmarks LiteRT models on different platforms.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
model: Path to the LiteRT model file or Model Reference.
|
|
126
|
+
target: Target platform for benchmark (android, gcp).
|
|
127
|
+
accelerator: Accelerator to use (cpu, gpu, npu).
|
|
128
|
+
devices: Target device model(s) (e.g., 'pixel 7').
|
|
129
|
+
gcp_project: GCP project ID for benchmarking.
|
|
130
|
+
"""
|
|
131
|
+
from litert_cli.core import models as core_models
|
|
132
|
+
|
|
133
|
+
# Quiet if default is true
|
|
134
|
+
if constants.DEFAULT_QUIET:
|
|
135
|
+
utils.enable_quiet_mode()
|
|
136
|
+
|
|
137
|
+
resolved_model_path, _ = core_models.resolve_model_reference(model)
|
|
138
|
+
|
|
139
|
+
if resolved_model_path != model:
|
|
140
|
+
click.echo(f"Resolved model '{model}' to '{resolved_model_path}'")
|
|
141
|
+
|
|
142
|
+
model_path = pathlib.Path(resolved_model_path)
|
|
143
|
+
|
|
144
|
+
if target == "android":
|
|
145
|
+
# pylint: disable=g-import-not-at-top
|
|
146
|
+
from litert_cli.commands.benchmark import android
|
|
147
|
+
|
|
148
|
+
if not model_path.exists():
|
|
149
|
+
raise click.ClickException(f"Local model file not found: {model_path}")
|
|
150
|
+
|
|
151
|
+
android.run_android(model_path=model_path, accelerator=accelerator)
|
|
152
|
+
elif target == "gcp":
|
|
153
|
+
# pylint: disable=g-import-not-at-top
|
|
154
|
+
from litert_cli.commands.benchmark import gcp
|
|
155
|
+
|
|
156
|
+
gcp.run_gcp(
|
|
157
|
+
str(model_path),
|
|
158
|
+
accelerator,
|
|
159
|
+
devices,
|
|
160
|
+
gcp_project,
|
|
161
|
+
)
|
|
162
|
+
else:
|
|
163
|
+
click.secho(f"Target '{target}' is not yet supported.", fg="red")
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Copyright 2026 The LiteRT CLI Authors.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
|
|
16
|
+
"""Benchmarking on Google AI Edge Portal in GCP."""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import pathlib
|
|
23
|
+
import subprocess
|
|
24
|
+
import time
|
|
25
|
+
import urllib.error
|
|
26
|
+
import urllib.request
|
|
27
|
+
import uuid
|
|
28
|
+
|
|
29
|
+
import click
|
|
30
|
+
|
|
31
|
+
_DEFAULT_GCP_PROJECT = os.environ.get("LITERT_GCP_PROJECT", "aep-e2e-test")
|
|
32
|
+
_DEFAULT_GCP_LOCATION = "us-central1"
|
|
33
|
+
_GCP_BUCKET = os.environ.get("LITERT_GCP_BUCKET", "litert-cli-test")
|
|
34
|
+
_DEFAULT_PORTAL_ENDPOINT = "https://aiedgeportal.googleapis.com/v1alpha"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _get_submission_url(
|
|
38
|
+
portal_endpoint: str, gcp_project: str, gcp_location: str, job_id: str
|
|
39
|
+
) -> str:
|
|
40
|
+
"""Returns the URL for submitting a benchmark job."""
|
|
41
|
+
return (
|
|
42
|
+
f"{portal_endpoint}/projects/{gcp_project}/locations/{gcp_location}"
|
|
43
|
+
f"/benchmarks?benchmarkId={job_id}"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _get_operation_url(portal_endpoint: str, op_name: str) -> str:
|
|
48
|
+
"""Returns the URL for polling operation status."""
|
|
49
|
+
return f"{portal_endpoint}/{op_name}"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _get_console_url(
|
|
53
|
+
gcp_project: str, gcp_location: str, benchmark_id: str
|
|
54
|
+
) -> str:
|
|
55
|
+
"""Returns the Google Cloud Console URL for viewing progress."""
|
|
56
|
+
return (
|
|
57
|
+
"https://console.cloud.google.com/ai-edge-portal/benchmarks/details/"
|
|
58
|
+
f"{gcp_location}/{benchmark_id}?project={gcp_project}"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def run_gcp(
|
|
63
|
+
model_path_str: str,
|
|
64
|
+
accelerator: str,
|
|
65
|
+
devices: list[str],
|
|
66
|
+
gcp_project: str | None = None,
|
|
67
|
+
) -> None:
|
|
68
|
+
"""Runs the model on GCP via AI Edge Portal Cloud API.
|
|
69
|
+
|
|
70
|
+
Uploads model to GCS if it's not already there.
|
|
71
|
+
Submits benchmark job to AI Edge Portal Cloud API.
|
|
72
|
+
Polls benchmark job status.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
model_path_str: Path to the LiteRT model file (local or gs://).
|
|
76
|
+
accelerator: Hardware accelerator to use (cpu, gpu, npu).
|
|
77
|
+
devices: Target device model(s) (e.g., 'pixel 7', 'pixel 8').
|
|
78
|
+
gcp_project: GCP project ID for benchmarking.
|
|
79
|
+
"""
|
|
80
|
+
if accelerator.lower() == "npu":
|
|
81
|
+
click.secho(
|
|
82
|
+
"Warning: NPU benchmarking on GCP is not fully implemented via CLI yet."
|
|
83
|
+
" Please use the Google AI Edge Portal web UI to run and test NPU"
|
|
84
|
+
" benchmarks.",
|
|
85
|
+
fg="yellow",
|
|
86
|
+
)
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
device_list = []
|
|
90
|
+
if isinstance(devices, str):
|
|
91
|
+
items = [devices]
|
|
92
|
+
else:
|
|
93
|
+
items = devices
|
|
94
|
+
|
|
95
|
+
for item in items:
|
|
96
|
+
if item:
|
|
97
|
+
parts = [p.strip() for p in item.split(",") if p.strip()]
|
|
98
|
+
device_list.extend(parts)
|
|
99
|
+
|
|
100
|
+
if not device_list:
|
|
101
|
+
device_list = ["pixel 7"]
|
|
102
|
+
|
|
103
|
+
if not gcp_project:
|
|
104
|
+
gcp_project = _DEFAULT_GCP_PROJECT
|
|
105
|
+
model_path = model_path_str
|
|
106
|
+
# Upload model to GCS if it's not already there.
|
|
107
|
+
if not model_path.startswith("gs://"):
|
|
108
|
+
local_model = pathlib.Path(model_path)
|
|
109
|
+
if not local_model.exists():
|
|
110
|
+
click.secho(f"Error: Local model file not found: {model_path}", fg="red")
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
click.secho(
|
|
114
|
+
f"Uploading local model '{model_path}' to"
|
|
115
|
+
f" gs://{_GCP_BUCKET}/...",
|
|
116
|
+
fg="cyan",
|
|
117
|
+
)
|
|
118
|
+
try:
|
|
119
|
+
subprocess.run(
|
|
120
|
+
[
|
|
121
|
+
"gcloud",
|
|
122
|
+
"storage",
|
|
123
|
+
"cp",
|
|
124
|
+
str(local_model),
|
|
125
|
+
f"gs://{_GCP_BUCKET}/",
|
|
126
|
+
],
|
|
127
|
+
check=True,
|
|
128
|
+
)
|
|
129
|
+
model_path = f"gs://{_GCP_BUCKET}/{local_model.name}"
|
|
130
|
+
except subprocess.CalledProcessError as e:
|
|
131
|
+
click.secho(
|
|
132
|
+
f"Error: Failed to upload '{model_path}' to Google Cloud Storage:"
|
|
133
|
+
f" {e}",
|
|
134
|
+
fg="red",
|
|
135
|
+
)
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
job_id = f"litert-cli-benchmark-{uuid.uuid4().hex[:8]}"
|
|
139
|
+
|
|
140
|
+
click.echo("Fetching GCP access token...")
|
|
141
|
+
try:
|
|
142
|
+
token = subprocess.check_output(
|
|
143
|
+
["gcloud", "auth", "print-access-token"], text=True
|
|
144
|
+
).strip()
|
|
145
|
+
except subprocess.CalledProcessError as e:
|
|
146
|
+
click.secho(
|
|
147
|
+
"Error: Failed to get gcloud access token. Please run 'gcloud auth"
|
|
148
|
+
f" login' first. Details: {e}",
|
|
149
|
+
fg="red",
|
|
150
|
+
)
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
portal_endpoint = os.environ.get(
|
|
154
|
+
"AI_EDGE_PORTAL_ENDPOINT", _DEFAULT_PORTAL_ENDPOINT
|
|
155
|
+
).rstrip("/")
|
|
156
|
+
url = _get_submission_url(
|
|
157
|
+
portal_endpoint, gcp_project, _DEFAULT_GCP_LOCATION, job_id
|
|
158
|
+
)
|
|
159
|
+
headers = {
|
|
160
|
+
"Authorization": f"Bearer {token}",
|
|
161
|
+
"Content-Type": "application/json",
|
|
162
|
+
"X-Goog-User-Project": gcp_project,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
accel_name = accelerator.upper()
|
|
166
|
+
|
|
167
|
+
body = {
|
|
168
|
+
"display_name": job_id,
|
|
169
|
+
"device_configs": [{"device_model": d} for d in device_list],
|
|
170
|
+
"run_specs": [{
|
|
171
|
+
"accelerator": accel_name,
|
|
172
|
+
"model_path": model_path.replace("gs://", ""),
|
|
173
|
+
"id": accelerator.lower(),
|
|
174
|
+
"display_name": f"{accelerator.lower()}_test",
|
|
175
|
+
}],
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if gcp_project == "aep-e2e-test":
|
|
179
|
+
click.secho(
|
|
180
|
+
"Warning: You need to specify your own GCP project by passing"
|
|
181
|
+
" '--gcp-project <PROJECT_ID>' or by setting the 'LITERT_GCP_PROJECT'"
|
|
182
|
+
" environment variable.",
|
|
183
|
+
fg="yellow",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Submit the benchmark job via http requests to AI Edge Portal Cloud API.
|
|
187
|
+
req = urllib.request.Request(
|
|
188
|
+
url, data=json.dumps(body).encode("utf-8"), headers=headers, method="POST"
|
|
189
|
+
)
|
|
190
|
+
click.echo(
|
|
191
|
+
f"Submitting '{accelerator}' benchmark job '{job_id}' to AI Edge Portal"
|
|
192
|
+
f" (Project: {gcp_project}, Location:"
|
|
193
|
+
f" {_DEFAULT_GCP_LOCATION})..."
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
with urllib.request.urlopen(req) as response:
|
|
198
|
+
resp_data = json.loads(response.read().decode())
|
|
199
|
+
click.secho("Benchmark job submitted successfully!", fg="green")
|
|
200
|
+
|
|
201
|
+
op_name = resp_data.get("name", "")
|
|
202
|
+
if op_name and "operations" in op_name:
|
|
203
|
+
op_url = _get_operation_url(portal_endpoint, op_name)
|
|
204
|
+
console_url = _get_console_url(
|
|
205
|
+
gcp_project, _DEFAULT_GCP_LOCATION, job_id
|
|
206
|
+
)
|
|
207
|
+
click.echo(
|
|
208
|
+
f"Waiting for benchmark to complete (Operation: {op_name}). This"
|
|
209
|
+
" may take a few minutes..."
|
|
210
|
+
)
|
|
211
|
+
click.secho(
|
|
212
|
+
f"View progress on the Cloud Console: {console_url}", fg="cyan"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
while True:
|
|
217
|
+
time.sleep(15)
|
|
218
|
+
click.echo(".", nl=False)
|
|
219
|
+
req_op = urllib.request.Request(op_url, headers=headers)
|
|
220
|
+
try:
|
|
221
|
+
with urllib.request.urlopen(req_op) as res_op:
|
|
222
|
+
op_data = json.loads(res_op.read().decode())
|
|
223
|
+
except urllib.error.HTTPError as e:
|
|
224
|
+
with e:
|
|
225
|
+
click.secho(f"\nError polling operation: {e}", fg="yellow")
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
if op_data.get("done"):
|
|
229
|
+
click.echo("") # Print a newline after the dots
|
|
230
|
+
if "error" in op_data:
|
|
231
|
+
click.secho(
|
|
232
|
+
"Benchmark failed:"
|
|
233
|
+
f" {json.dumps(op_data['error'], indent=2)}",
|
|
234
|
+
fg="red",
|
|
235
|
+
)
|
|
236
|
+
else:
|
|
237
|
+
click.secho(
|
|
238
|
+
"Benchmark operation completed successfully!", fg="green"
|
|
239
|
+
)
|
|
240
|
+
click.echo(json.dumps(op_data.get("response", {}), indent=2))
|
|
241
|
+
break
|
|
242
|
+
except KeyboardInterrupt:
|
|
243
|
+
click.echo("")
|
|
244
|
+
click.secho(
|
|
245
|
+
"\nPolling interrupted. The benchmark job is still running.",
|
|
246
|
+
fg="yellow",
|
|
247
|
+
)
|
|
248
|
+
click.echo(
|
|
249
|
+
f"You can check its status later by viewing it in the console:"
|
|
250
|
+
" {console_url}"
|
|
251
|
+
)
|
|
252
|
+
else:
|
|
253
|
+
click.echo(json.dumps(resp_data, indent=2))
|
|
254
|
+
except urllib.error.HTTPError as e:
|
|
255
|
+
err_body = e.read().decode()
|
|
256
|
+
click.secho(f"Failed to submit benchmark: {e.code} {e.reason}", fg="red")
|
|
257
|
+
click.secho(f"Details: {err_body}", fg="red")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Copyright 2026 The LiteRT CLI Authors.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
|
|
16
|
+
"""Clean command for LiteRT CLI."""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import pathlib
|
|
21
|
+
import shlex
|
|
22
|
+
import shutil
|
|
23
|
+
import subprocess
|
|
24
|
+
|
|
25
|
+
import click
|
|
26
|
+
from litert_cli.core import android_utils
|
|
27
|
+
from litert_cli.core import constants
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@click.command(name="clean")
|
|
31
|
+
def clean_cmd() -> None:
|
|
32
|
+
"""Cleans up local caches, downloaded files, and remote Android directories."""
|
|
33
|
+
|
|
34
|
+
click.echo("Cleaning LiteRT CLI workspace...")
|
|
35
|
+
|
|
36
|
+
# 1. Clean local downloaded CLI root qairt directory
|
|
37
|
+
qairt_dir = pathlib.Path(constants.LITERT_CLI_ROOT) / "qairt"
|
|
38
|
+
try:
|
|
39
|
+
shutil.rmtree(qairt_dir)
|
|
40
|
+
click.echo(f"Removing local qairt workspace: {qairt_dir}")
|
|
41
|
+
except FileNotFoundError:
|
|
42
|
+
pass
|
|
43
|
+
except OSError as e:
|
|
44
|
+
click.secho(f"Warning: Failed to remove {qairt_dir}: {e}", fg="yellow")
|
|
45
|
+
|
|
46
|
+
# 2. Clean local cache directory (e.g., ~/.cache/litert-cli)
|
|
47
|
+
cache_dir = pathlib.Path(constants.LITERT_CLI_CACHE_DIR)
|
|
48
|
+
try:
|
|
49
|
+
shutil.rmtree(cache_dir)
|
|
50
|
+
click.echo(f"Removing local cache: {cache_dir}")
|
|
51
|
+
except FileNotFoundError:
|
|
52
|
+
pass
|
|
53
|
+
except OSError as e:
|
|
54
|
+
click.secho(f"Warning: Failed to remove {cache_dir}: {e}", fg="yellow")
|
|
55
|
+
|
|
56
|
+
# 3. Clean remote Android directory
|
|
57
|
+
try:
|
|
58
|
+
android_utils.check_adb()
|
|
59
|
+
android_root = constants.LITERT_CLI_ANDROID_ROOT
|
|
60
|
+
click.echo(f"Removing remote Android workspace via adb: {android_root}")
|
|
61
|
+
|
|
62
|
+
subprocess.run(
|
|
63
|
+
["adb", "shell", f"rm -rf {shlex.quote(android_root)}"],
|
|
64
|
+
check=False,
|
|
65
|
+
stdout=subprocess.DEVNULL,
|
|
66
|
+
stderr=subprocess.DEVNULL,
|
|
67
|
+
)
|
|
68
|
+
except click.ClickException:
|
|
69
|
+
click.echo(
|
|
70
|
+
"No active Android device found via adb. Skipping remote cleanup."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
click.secho("Cleanup complete!", fg="green")
|