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.
Files changed (61) hide show
  1. litert_cli/__init__.py +15 -0
  2. litert_cli/commands/benchmark/__init__.py +16 -0
  3. litert_cli/commands/benchmark/android.py +177 -0
  4. litert_cli/commands/benchmark/cli.py +163 -0
  5. litert_cli/commands/benchmark/gcp.py +257 -0
  6. litert_cli/commands/clean.py +73 -0
  7. litert_cli/commands/compile.py +199 -0
  8. litert_cli/commands/convert/__init__.py +20 -0
  9. litert_cli/commands/convert/cli.py +148 -0
  10. litert_cli/commands/convert/generic.py +154 -0
  11. litert_cli/commands/convert/huggingface.py +105 -0
  12. litert_cli/commands/delete.py +56 -0
  13. litert_cli/commands/download.py +274 -0
  14. litert_cli/commands/import.py +124 -0
  15. litert_cli/commands/list.py +132 -0
  16. litert_cli/commands/lm.py +74 -0
  17. litert_cli/commands/quantize.py +201 -0
  18. litert_cli/commands/run/__init__.py +16 -0
  19. litert_cli/commands/run/android.py +394 -0
  20. litert_cli/commands/run/cli.py +297 -0
  21. litert_cli/commands/run/desktop.py +340 -0
  22. litert_cli/commands/visualize.py +234 -0
  23. litert_cli/core/android_utils.py +304 -0
  24. litert_cli/core/android_utils_test.py +236 -0
  25. litert_cli/core/constants.py +125 -0
  26. litert_cli/core/deps.py +161 -0
  27. litert_cli/core/deps_test.py +97 -0
  28. litert_cli/core/inputs.py +203 -0
  29. litert_cli/core/inputs_test.py +176 -0
  30. litert_cli/core/log_filters.py +50 -0
  31. litert_cli/core/models.py +96 -0
  32. litert_cli/core/npu_utils.py +382 -0
  33. litert_cli/core/targets_manager.py +192 -0
  34. litert_cli/core/utils.py +43 -0
  35. litert_cli/litert.py +99 -0
  36. litert_cli/litert_help_test.py +51 -0
  37. litert_cli/litert_test.py +88 -0
  38. litert_cli/models/__init__.py +142 -0
  39. litert_cli/models/asr/__init__.py +15 -0
  40. litert_cli/models/asr/asr_model.py +108 -0
  41. litert_cli/models/asr/parakeet_ctc.py +165 -0
  42. litert_cli/models/asr/runner.py +482 -0
  43. litert_cli/models/base.py +57 -0
  44. litert_cli/test_data/dummy_cv_model.py +52 -0
  45. litert_cli/test_data/dummy_cv_model.tflite +0 -0
  46. litert_cli/test_data/generate_test_inputs.py +51 -0
  47. litert_cli/test_data/mobilenet_v3_calib_data.py +25 -0
  48. litert_cli/test_data/quantize_recipe.json +16 -0
  49. litert_cli/test_data/resnet18.py +31 -0
  50. litert_cli_nightly-0.1.0.dev20260513.dist-info/METADATA +37 -0
  51. litert_cli_nightly-0.1.0.dev20260513.dist-info/RECORD +61 -0
  52. litert_cli_nightly-0.1.0.dev20260513.dist-info/WHEEL +5 -0
  53. litert_cli_nightly-0.1.0.dev20260513.dist-info/entry_points.txt +2 -0
  54. litert_cli_nightly-0.1.0.dev20260513.dist-info/licenses/LICENSE +202 -0
  55. litert_cli_nightly-0.1.0.dev20260513.dist-info/top_level.txt +2 -0
  56. test_scripts/litert_cli.ipynb +282 -0
  57. test_scripts/litert_cli_test_commands.sh +333 -0
  58. test_scripts/run_cli_demo.sh +88 -0
  59. test_scripts/run_cli_npu.sh +116 -0
  60. test_scripts/run_cli_oss.sh +58 -0
  61. 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")