litert-cli 0.1.0__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.
- examples/litert_cli.ipynb +313 -0
- examples/models/presets/default.py +19 -0
- examples/run_cli_demo.sh +38 -0
- examples/run_cli_npu.sh +89 -0
- examples/run_commands.sh +67 -0
- examples/run_models.sh +63 -0
- examples/run_smoke_tests.sh +58 -0
- examples/utils.ps1 +163 -0
- examples/utils.sh +184 -0
- litert_cli/__init__.py +15 -0
- litert_cli/commands/benchmark/__init__.py +16 -0
- litert_cli/commands/benchmark/android.py +212 -0
- litert_cli/commands/benchmark/cli.py +294 -0
- litert_cli/commands/benchmark/desktop.py +228 -0
- litert_cli/commands/benchmark/gcp.py +336 -0
- litert_cli/commands/clean.py +73 -0
- litert_cli/commands/compile.py +211 -0
- litert_cli/commands/convert/__init__.py +20 -0
- litert_cli/commands/convert/cli.py +255 -0
- litert_cli/commands/convert/generic.py +211 -0
- litert_cli/commands/convert/huggingface.py +175 -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 +193 -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 +131 -0
- litert_cli/core/deps.py +180 -0
- litert_cli/core/deps_test.py +101 -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 +58 -0
- litert_cli/litert.py +119 -0
- litert_cli/litert_help_test.py +51 -0
- litert_cli/litert_test.py +88 -0
- litert_cli/models/__init__.py +145 -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_calib_data.py +26 -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-0.1.0.dist-info/METADATA +38 -0
- litert_cli-0.1.0.dist-info/RECORD +67 -0
- litert_cli-0.1.0.dist-info/WHEEL +5 -0
- litert_cli-0.1.0.dist-info/entry_points.txt +2 -0
- litert_cli-0.1.0.dist-info/licenses/LICENSE +202 -0
- litert_cli-0.1.0.dist-info/top_level.txt +3 -0
- tools/build_wheels.py +122 -0
|
@@ -0,0 +1,234 @@
|
|
|
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
|
+
"""Command line interface for visualizing LiteRT models.
|
|
17
|
+
|
|
18
|
+
This module provides the `litert visualize` command, which launches the
|
|
19
|
+
Model Explorer web application to inspect the architecture of TFLite models.
|
|
20
|
+
|
|
21
|
+
Key Features:
|
|
22
|
+
- Background Execution: The web server runs entirely in the background,
|
|
23
|
+
instantly returning control of the terminal to the user.
|
|
24
|
+
- URL Printing: The command calculates and prints a click-able URL directly
|
|
25
|
+
in the terminal, bypassing IDE port-forwarding interceptions that often
|
|
26
|
+
strip URL query parameters.
|
|
27
|
+
- Server Reuse: By default, multiple visualizations reuse the same server
|
|
28
|
+
instance, preventing port exhaustion and reducing memory footprint.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import json
|
|
34
|
+
import pathlib
|
|
35
|
+
import socket
|
|
36
|
+
import subprocess
|
|
37
|
+
import sys
|
|
38
|
+
import textwrap
|
|
39
|
+
import urllib.parse
|
|
40
|
+
|
|
41
|
+
import click
|
|
42
|
+
|
|
43
|
+
from ..core import deps
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _is_port_in_use(port_num: int) -> bool:
|
|
47
|
+
"""Checks if a port is in use on localhost.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
port_num: Port to check.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
True if the port is bound and in use, False otherwise.
|
|
54
|
+
"""
|
|
55
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
56
|
+
return s.connect_ex(('localhost', port_num)) == 0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _find_available_port(start_port: int, max_attempts: int = 20) -> int:
|
|
60
|
+
"""Finds the first available port on localhost starting from start_port.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
start_port: Port to start searching from.
|
|
64
|
+
max_attempts: Maximum number of ports to check.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
The first available port found.
|
|
68
|
+
"""
|
|
69
|
+
return next(
|
|
70
|
+
(
|
|
71
|
+
p
|
|
72
|
+
for p in range(start_port, start_port + max_attempts)
|
|
73
|
+
if not _is_port_in_use(p)
|
|
74
|
+
),
|
|
75
|
+
start_port,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@click.command(
|
|
80
|
+
'visualize',
|
|
81
|
+
help=textwrap.dedent("""\
|
|
82
|
+
Runs model explorer to visualize the model architecture.
|
|
83
|
+
|
|
84
|
+
MODEL_PATH: Path to the LiteRT model (.tflite) to visualize.
|
|
85
|
+
|
|
86
|
+
Examples:
|
|
87
|
+
|
|
88
|
+
1. Visualize a single model (starts a new server if none exists):
|
|
89
|
+
|
|
90
|
+
$ litert visualize /path/to/model.tflite
|
|
91
|
+
|
|
92
|
+
2. Visualize a different model (reuses existing server, refreshes browser):
|
|
93
|
+
|
|
94
|
+
$ litert visualize /path/to/another_model.tflite
|
|
95
|
+
|
|
96
|
+
3. Compare two models side-by-side (forces a new server on a new port):
|
|
97
|
+
|
|
98
|
+
$ litert visualize /path/to/model_A.tflite
|
|
99
|
+
$ litert visualize /path/to/model_B.tflite --no-reuse-server
|
|
100
|
+
|
|
101
|
+
4. Clean up and stop all running Model Explorer servers:
|
|
102
|
+
|
|
103
|
+
$ litert visualize --stop-all
|
|
104
|
+
"""),
|
|
105
|
+
)
|
|
106
|
+
@click.argument(
|
|
107
|
+
'model_path',
|
|
108
|
+
type=str,
|
|
109
|
+
required=False,
|
|
110
|
+
)
|
|
111
|
+
@click.option(
|
|
112
|
+
'--reuse-server/--no-reuse-server',
|
|
113
|
+
default=True,
|
|
114
|
+
help='Reuse running Model Explorer server.',
|
|
115
|
+
)
|
|
116
|
+
@click.option(
|
|
117
|
+
'--stop-all',
|
|
118
|
+
is_flag=True,
|
|
119
|
+
help='Stop all running Model Explorer servers.',
|
|
120
|
+
)
|
|
121
|
+
@deps.require_extra('visualize')
|
|
122
|
+
def visualize_cmd(
|
|
123
|
+
model_path: str | None, *, reuse_server: bool, stop_all: bool
|
|
124
|
+
) -> None:
|
|
125
|
+
"""Runs model explorer to visualize the model architecture.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
model_path: Path to the LiteRT model (.tflite) or Model Reference to
|
|
129
|
+
visualize.
|
|
130
|
+
reuse_server: Whether to reuse an already running Model Explorer server.
|
|
131
|
+
stop_all: If True, stops all running Model Explorer servers instead.
|
|
132
|
+
"""
|
|
133
|
+
from litert_cli.core import models as core_models
|
|
134
|
+
|
|
135
|
+
if stop_all:
|
|
136
|
+
click.echo('Attempting to stop all running Model Explorer servers...')
|
|
137
|
+
try:
|
|
138
|
+
# Use pkill to find and terminate all model-explorer processes
|
|
139
|
+
subprocess.run(
|
|
140
|
+
['pkill', '-f', 'model-explorer'],
|
|
141
|
+
check=True,
|
|
142
|
+
stdout=subprocess.DEVNULL,
|
|
143
|
+
stderr=subprocess.DEVNULL,
|
|
144
|
+
)
|
|
145
|
+
click.secho(
|
|
146
|
+
'Successfully stopped all Model Explorer servers.',
|
|
147
|
+
fg='green',
|
|
148
|
+
)
|
|
149
|
+
except subprocess.CalledProcessError:
|
|
150
|
+
click.secho('No running Model Explorer servers found.', fg='yellow')
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
if not model_path:
|
|
154
|
+
raise click.UsageError('Missing argument "MODEL_PATH".')
|
|
155
|
+
|
|
156
|
+
resolved_model_path, _ = core_models.resolve_model_reference(model_path)
|
|
157
|
+
if str(resolved_model_path) != str(model_path):
|
|
158
|
+
click.echo(f"Resolved model '{model_path}' to '{resolved_model_path}'")
|
|
159
|
+
|
|
160
|
+
resolved_model_path = pathlib.Path(resolved_model_path).resolve()
|
|
161
|
+
|
|
162
|
+
click.echo(
|
|
163
|
+
f'Starting Model Explorer visualization for {resolved_model_path} in the'
|
|
164
|
+
' background...'
|
|
165
|
+
)
|
|
166
|
+
# Check for available port so we can print the exact URL
|
|
167
|
+
port = (
|
|
168
|
+
8081
|
|
169
|
+
if reuse_server and _is_port_in_use(8081)
|
|
170
|
+
else _find_available_port(8081)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Build the exact model explorer data URL
|
|
174
|
+
data = {'models': [{'url': str(resolved_model_path)}]}
|
|
175
|
+
data_param = urllib.parse.quote(
|
|
176
|
+
json.dumps(data, separators=(',', ':')), safe=':,'
|
|
177
|
+
)
|
|
178
|
+
url = f'http://localhost:{port}/?data={data_param}'
|
|
179
|
+
|
|
180
|
+
model_explorer_bin = None
|
|
181
|
+
if sys.executable:
|
|
182
|
+
python_dir = pathlib.Path(sys.executable).parent
|
|
183
|
+
# Assume model_explorer binary is in the same directory as the python
|
|
184
|
+
model_explorer_bin = python_dir / 'model-explorer'
|
|
185
|
+
|
|
186
|
+
cmd = [
|
|
187
|
+
str(model_explorer_bin)
|
|
188
|
+
if model_explorer_bin and model_explorer_bin.exists()
|
|
189
|
+
else 'model-explorer',
|
|
190
|
+
str(resolved_model_path),
|
|
191
|
+
'--no_open_in_browser',
|
|
192
|
+
f'--port={port}',
|
|
193
|
+
]
|
|
194
|
+
if reuse_server:
|
|
195
|
+
cmd.append('--reuse_server')
|
|
196
|
+
cmd.append(f'--reuse_server_port={port}')
|
|
197
|
+
|
|
198
|
+
# Launch as a fully detached daemon so the terminal isn't blocked.
|
|
199
|
+
try:
|
|
200
|
+
subprocess.Popen(
|
|
201
|
+
cmd,
|
|
202
|
+
stdout=subprocess.DEVNULL,
|
|
203
|
+
stderr=subprocess.DEVNULL,
|
|
204
|
+
start_new_session=True,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
click.secho(
|
|
208
|
+
'\nModel Explorer server is running in the background.', fg='green'
|
|
209
|
+
)
|
|
210
|
+
click.secho(
|
|
211
|
+
'► Please click the link below to view your model:',
|
|
212
|
+
fg='cyan',
|
|
213
|
+
bold=True,
|
|
214
|
+
)
|
|
215
|
+
click.secho(f'\n {url}\n')
|
|
216
|
+
if reuse_server:
|
|
217
|
+
click.secho(
|
|
218
|
+
'It will reuse the existing server instance if one is already'
|
|
219
|
+
' running, avoiding port conflicts.',
|
|
220
|
+
fg='cyan',
|
|
221
|
+
)
|
|
222
|
+
except FileNotFoundError:
|
|
223
|
+
click.secho(
|
|
224
|
+
'\nError: "model-explorer" command not found.', fg='red', bold=True
|
|
225
|
+
)
|
|
226
|
+
click.secho(
|
|
227
|
+
'Please make sure Model Explorer is installed and available in your'
|
|
228
|
+
' PATH.',
|
|
229
|
+
fg='yellow',
|
|
230
|
+
)
|
|
231
|
+
click.secho(
|
|
232
|
+
'You can install it via: pip install model-explorer',
|
|
233
|
+
fg='cyan',
|
|
234
|
+
)
|
|
@@ -0,0 +1,304 @@
|
|
|
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 utility functions for LiteRT CLI."""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from collections.abc import Sequence
|
|
21
|
+
import pathlib
|
|
22
|
+
import subprocess
|
|
23
|
+
|
|
24
|
+
import click
|
|
25
|
+
import requests
|
|
26
|
+
|
|
27
|
+
from litert_cli.core import constants
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DownloadError(Exception):
|
|
31
|
+
"""A file download failed."""
|
|
32
|
+
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _construct_adb_command(command: Sequence[str], device_id: str | None) -> list[str]:
|
|
37
|
+
"""Constructs an adb command list, including the device ID if provided."""
|
|
38
|
+
if device_id:
|
|
39
|
+
return ["adb", "-s", device_id, *command]
|
|
40
|
+
return ["adb", *command]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def check_adb(device_id: str | None = None) -> None:
|
|
44
|
+
"""Checks if adb device is available.
|
|
45
|
+
|
|
46
|
+
Verifies that exactly one authorized device is connected and responding to
|
|
47
|
+
commands.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
device_id: Optional. The serial number of the target device.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
click.ClickException: If adb is missing or device is unauthorized/offline.
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
subprocess.run(
|
|
57
|
+
_construct_adb_command(["get-state"], device_id),
|
|
58
|
+
check=True,
|
|
59
|
+
stdout=subprocess.DEVNULL,
|
|
60
|
+
stderr=subprocess.DEVNULL,
|
|
61
|
+
)
|
|
62
|
+
except FileNotFoundError as e:
|
|
63
|
+
raise click.ClickException(
|
|
64
|
+
"adb command not found. Please ensure Android platform-tools is in"
|
|
65
|
+
" your PATH."
|
|
66
|
+
) from e
|
|
67
|
+
except subprocess.CalledProcessError as e:
|
|
68
|
+
device_info = f" for device {device_id}" if device_id else ""
|
|
69
|
+
raise click.ClickException(
|
|
70
|
+
f"No Android device found{device_info} or it is not authorized. Try"
|
|
71
|
+
" running 'adb devices' to check."
|
|
72
|
+
) from e
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_android_abi(device_id: str | None = None) -> str:
|
|
76
|
+
"""Gets the CPU ABI of the connected Android device via adb.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
device_id: Optional. The serial number of the target device.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The CPU ABI string (e.g., 'arm64-v8a').
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
click.ClickException: If querying Android ABI fails or adb is missing.
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
return subprocess.run(
|
|
89
|
+
_construct_adb_command(
|
|
90
|
+
["shell", "getprop", "ro.product.cpu.abi"], device_id
|
|
91
|
+
),
|
|
92
|
+
capture_output=True,
|
|
93
|
+
text=True,
|
|
94
|
+
check=True,
|
|
95
|
+
).stdout.strip()
|
|
96
|
+
except subprocess.CalledProcessError as e:
|
|
97
|
+
device_info = f" for device {device_id}" if device_id else ""
|
|
98
|
+
raise click.ClickException(f"Error querying Android ABI{device_info}") from e
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _ensure_downloaded_file(
|
|
102
|
+
abi: str,
|
|
103
|
+
file_name: str,
|
|
104
|
+
base_url: str,
|
|
105
|
+
*,
|
|
106
|
+
make_executable: bool = False,
|
|
107
|
+
) -> pathlib.Path:
|
|
108
|
+
"""Downloads a file for the given ABI if not cached.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
abi: The Android CPU ABI (e.g., 'arm64-v8a').
|
|
112
|
+
file_name: The file name to download.
|
|
113
|
+
base_url: The base URL to download from.
|
|
114
|
+
make_executable: Whether to make the file executable.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
The absolute local path to the cached file.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
click.ClickException: If the architecture is not supported.
|
|
121
|
+
DownloadError: If the download or file operations fail.
|
|
122
|
+
"""
|
|
123
|
+
if "arm64" not in abi:
|
|
124
|
+
raise click.ClickException(
|
|
125
|
+
f"Architecture {abi!r} is not supported for automatic downloading of"
|
|
126
|
+
f" {file_name!r}. Only arm64 is supported for these specific binaries."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
download_url = f"{base_url}/{file_name}"
|
|
130
|
+
cache_dir = pathlib.Path(constants.LITERT_CLI_CACHE_DIR) / "binaries" / abi
|
|
131
|
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
132
|
+
|
|
133
|
+
cached_file_path = cache_dir / file_name
|
|
134
|
+
if cached_file_path.exists():
|
|
135
|
+
return cached_file_path
|
|
136
|
+
|
|
137
|
+
click.secho(f"Downloading {file_name!r} for {abi!r}...", fg="cyan")
|
|
138
|
+
tmp_cached_file = cached_file_path.with_suffix(".tmp")
|
|
139
|
+
try:
|
|
140
|
+
with requests.get(download_url, stream=True, timeout=10) as response:
|
|
141
|
+
response.raise_for_status()
|
|
142
|
+
content_length = response.headers.get("Content-Length")
|
|
143
|
+
total_size = int(content_length) if content_length else 0
|
|
144
|
+
|
|
145
|
+
bar_length = total_size if total_size > 0 else None
|
|
146
|
+
bar_label = f"Downloading {file_name}"
|
|
147
|
+
if bar_length is None:
|
|
148
|
+
click.secho(
|
|
149
|
+
f"Content-Length header not found for {file_name!r}, using"
|
|
150
|
+
" indeterminate progress bar.",
|
|
151
|
+
fg="yellow",
|
|
152
|
+
)
|
|
153
|
+
bar_label += " (size unknown)"
|
|
154
|
+
|
|
155
|
+
with click.progressbar(
|
|
156
|
+
length=bar_length, label=bar_label
|
|
157
|
+
) as bar:
|
|
158
|
+
with open(tmp_cached_file, "wb") as f:
|
|
159
|
+
for buffer in response.iter_content(chunk_size=8192):
|
|
160
|
+
f.write(buffer)
|
|
161
|
+
bar.update(len(buffer))
|
|
162
|
+
|
|
163
|
+
tmp_cached_file.rename(cached_file_path)
|
|
164
|
+
if make_executable:
|
|
165
|
+
cached_file_path.chmod(0o755)
|
|
166
|
+
return cached_file_path
|
|
167
|
+
except (requests.exceptions.RequestException, OSError) as e:
|
|
168
|
+
if cached_file_path.exists():
|
|
169
|
+
cached_file_path.unlink()
|
|
170
|
+
if tmp_cached_file.exists():
|
|
171
|
+
tmp_cached_file.unlink()
|
|
172
|
+
raise DownloadError(
|
|
173
|
+
f"Failed to download {file_name!r} from {download_url!r}"
|
|
174
|
+
) from e
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _ensure_downloaded_binary(abi: str, tool_name: str) -> pathlib.Path:
|
|
178
|
+
"""Downloads the pre-built binary for the given ABI if not cached."""
|
|
179
|
+
return _ensure_downloaded_file(
|
|
180
|
+
abi,
|
|
181
|
+
tool_name,
|
|
182
|
+
constants.LITERT_BINARIES_BASE_URL_ANDROID,
|
|
183
|
+
make_executable=True,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def find_android_binary(tool_name: str, abi: str) -> pathlib.Path:
|
|
188
|
+
"""Locates or downloads an Android executable binary.
|
|
189
|
+
|
|
190
|
+
Always downloads from a fixed URL (cached locally).
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
tool_name: Binary name (e.g. 'run_model').
|
|
194
|
+
abi: Target Android CPU ABI.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
The absolute path to the binary.
|
|
198
|
+
|
|
199
|
+
Raises:
|
|
200
|
+
click.ClickException: If the binary could not be found or downloaded.
|
|
201
|
+
"""
|
|
202
|
+
try:
|
|
203
|
+
downloaded_bin = _ensure_downloaded_binary(abi, tool_name)
|
|
204
|
+
if downloaded_bin.exists():
|
|
205
|
+
return downloaded_bin
|
|
206
|
+
else:
|
|
207
|
+
raise click.ClickException(
|
|
208
|
+
f"Downloaded binary {tool_name!r} for ABI {abi!r} does not exist."
|
|
209
|
+
)
|
|
210
|
+
except DownloadError as e:
|
|
211
|
+
raise click.ClickException(
|
|
212
|
+
f"Could not find or download {tool_name!r} for ABI {abi!r}: {e}"
|
|
213
|
+
) from e
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _ensure_downloaded_library(
|
|
217
|
+
abi: str,
|
|
218
|
+
lib_name: str,
|
|
219
|
+
base_url: str = constants.LITERT_BINARIES_BASE_URL_ANDROID,
|
|
220
|
+
) -> pathlib.Path:
|
|
221
|
+
"""Downloads the pre-built library for the given ABI if not cached."""
|
|
222
|
+
return _ensure_downloaded_file(abi, lib_name, base_url, make_executable=False)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def find_android_lib(
|
|
226
|
+
lib_name: str,
|
|
227
|
+
abi: str,
|
|
228
|
+
base_url: str = constants.LITERT_BINARIES_BASE_URL_ANDROID,
|
|
229
|
+
) -> pathlib.Path:
|
|
230
|
+
"""Locates or downloads an Android library.
|
|
231
|
+
|
|
232
|
+
Always downloads from a fixed URL (cached locally).
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
lib_name: library name (e.g. 'libLiteRt.so').
|
|
236
|
+
abi: Target Android CPU ABI.
|
|
237
|
+
base_url: The base URL to download the library from.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
The absolute path to the library.
|
|
241
|
+
|
|
242
|
+
Raises:
|
|
243
|
+
click.ClickException: If the library could not be found or downloaded.
|
|
244
|
+
"""
|
|
245
|
+
try:
|
|
246
|
+
downloaded_bin = _ensure_downloaded_library(abi, lib_name, base_url)
|
|
247
|
+
if downloaded_bin.exists():
|
|
248
|
+
return downloaded_bin
|
|
249
|
+
else:
|
|
250
|
+
raise click.ClickException(
|
|
251
|
+
f"Downloaded library {lib_name!r} for ABI {abi!r} does not exist."
|
|
252
|
+
)
|
|
253
|
+
except DownloadError as e:
|
|
254
|
+
raise click.ClickException(
|
|
255
|
+
f"Could not find or download {lib_name!r} for ABI {abi!r}: {e}"
|
|
256
|
+
) from e
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def find_npu_dispatch_lib(soc_vendor: str, abi: str) -> pathlib.Path:
|
|
260
|
+
"""Finds and downloads the NPU dispatch library for the given SoC vendor.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
soc_vendor: The NPU vendor ("qualcomm" or "mediatek").
|
|
264
|
+
abi: Target Android CPU ABI.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
The absolute path to the local downloaded backend dispatch library.
|
|
268
|
+
"""
|
|
269
|
+
if soc_vendor == "qualcomm":
|
|
270
|
+
lib_name = "libLiteRtDispatch_Qualcomm.so"
|
|
271
|
+
elif soc_vendor == "mediatek":
|
|
272
|
+
lib_name = "libLiteRtDispatch_MediaTek.so"
|
|
273
|
+
else:
|
|
274
|
+
raise click.ClickException(
|
|
275
|
+
f"Unsupported NPU vendor for dispatch: {soc_vendor!r}"
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
return find_android_lib(
|
|
279
|
+
lib_name, abi, base_url=constants.LITERT_BINARIES_BASE_URL_ANDROID
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def find_npu_compiler_plugin_lib(soc_vendor: str, abi: str) -> pathlib.Path:
|
|
284
|
+
"""Finds and downloads the NPU compiler plugin library for the given SoC vendor.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
soc_vendor: The NPU vendor ("qualcomm" or "mediatek").
|
|
288
|
+
abi: Target Android CPU ABI.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
The absolute path to the local downloaded backend compiler plugin library.
|
|
292
|
+
"""
|
|
293
|
+
if soc_vendor == "qualcomm":
|
|
294
|
+
lib_name = "libLiteRtCompilerPlugin_Qualcomm.so"
|
|
295
|
+
elif soc_vendor == "mediatek":
|
|
296
|
+
lib_name = "libLiteRtCompilerPlugin_MediaTek.so"
|
|
297
|
+
else:
|
|
298
|
+
raise click.ClickException(
|
|
299
|
+
f"Unsupported NPU vendor for compiler plugin: {soc_vendor!r}"
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
return find_android_lib(
|
|
303
|
+
lib_name, abi, base_url=constants.LITERT_BINARIES_BASE_URL_ANDROID
|
|
304
|
+
)
|