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,274 @@
|
|
|
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 downloading LiteRT models from huggingface.
|
|
17
|
+
|
|
18
|
+
External packages like requests, or huggingface_hub are optional and will be
|
|
19
|
+
auto-installed when the command is invoked.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import datetime
|
|
25
|
+
import json
|
|
26
|
+
import pathlib
|
|
27
|
+
import textwrap
|
|
28
|
+
import urllib.parse
|
|
29
|
+
|
|
30
|
+
import click
|
|
31
|
+
import huggingface_hub
|
|
32
|
+
import huggingface_hub.utils
|
|
33
|
+
import requests
|
|
34
|
+
|
|
35
|
+
from ..core import constants
|
|
36
|
+
from ..core import deps
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _download_from_url(model_path: str, output_dir: str | pathlib.Path) -> None:
|
|
40
|
+
"""Downloads a model directly from a given URL.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
model_path: The direct URL to the model file.
|
|
44
|
+
output_dir: The directory where the model will be saved.
|
|
45
|
+
"""
|
|
46
|
+
click.echo(f"Downloading direct URL: {model_path} to {output_dir}")
|
|
47
|
+
|
|
48
|
+
parsed_url = urllib.parse.urlparse(model_path)
|
|
49
|
+
filename = pathlib.Path(parsed_url.path).name
|
|
50
|
+
if not filename:
|
|
51
|
+
filename = "downloaded_model.tflite"
|
|
52
|
+
|
|
53
|
+
filepath = pathlib.Path(output_dir) / filename
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
with requests.get(model_path, stream=True) as response:
|
|
57
|
+
response.raise_for_status()
|
|
58
|
+
|
|
59
|
+
total_size = int(response.headers.get("content-length", 0))
|
|
60
|
+
chunk_size = 8192
|
|
61
|
+
|
|
62
|
+
with click.progressbar(
|
|
63
|
+
length=total_size,
|
|
64
|
+
label=f"Downloading {filename}",
|
|
65
|
+
empty_char=" ",
|
|
66
|
+
fill_char=click.style("#", fg="green"),
|
|
67
|
+
) as bar:
|
|
68
|
+
with open(filepath, "wb") as f:
|
|
69
|
+
for chunk in response.iter_content(chunk_size=chunk_size):
|
|
70
|
+
if chunk:
|
|
71
|
+
f.write(chunk)
|
|
72
|
+
bar.update(len(chunk))
|
|
73
|
+
|
|
74
|
+
click.secho(f"Downloaded to {filepath}", fg="green")
|
|
75
|
+
except requests.exceptions.RequestException as e:
|
|
76
|
+
raise click.ClickException(f"Failed to download URL: {e}") from e
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _download_from_hf(
|
|
80
|
+
repo_id: str,
|
|
81
|
+
output_dir: str | pathlib.Path,
|
|
82
|
+
file_pattern: str | None = None,
|
|
83
|
+
token: str | None = None,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Downloads a model repository or specific files from HuggingFace Hub.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
repo_id: The HuggingFace Hub repository ID.
|
|
89
|
+
output_dir: The directory where the files will be saved.
|
|
90
|
+
file_pattern: Optional glob pattern to filter files to download.
|
|
91
|
+
token: Optional HuggingFace access token.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
click.echo(f"Downloading from HuggingFace Hub: {repo_id}")
|
|
95
|
+
|
|
96
|
+
kwargs = {
|
|
97
|
+
"local_dir": output_dir,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if token:
|
|
101
|
+
kwargs["token"] = token
|
|
102
|
+
if file_pattern:
|
|
103
|
+
kwargs["allow_patterns"] = file_pattern
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
downloaded_path = huggingface_hub.snapshot_download(
|
|
107
|
+
repo_id=repo_id, **kwargs
|
|
108
|
+
)
|
|
109
|
+
click.secho(f"Successfully downloaded to {downloaded_path}", fg="green")
|
|
110
|
+
except huggingface_hub.utils.HfHubHTTPError as e:
|
|
111
|
+
if "401" in str(e):
|
|
112
|
+
raise click.ClickException(
|
|
113
|
+
f"Failed to download from HuggingFace: {e}\n"
|
|
114
|
+
"This model might require a token. Use --token"
|
|
115
|
+
) from e
|
|
116
|
+
raise click.ClickException(
|
|
117
|
+
f"Failed to download from HuggingFace: {e}"
|
|
118
|
+
) from e
|
|
119
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
120
|
+
# Catching broad exception as fallback for other huggingface_hub errors
|
|
121
|
+
raise click.ClickException(
|
|
122
|
+
f"An error occurred during HuggingFace download: {e}"
|
|
123
|
+
) from e
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@click.command(
|
|
127
|
+
"download",
|
|
128
|
+
help=textwrap.dedent("""\
|
|
129
|
+
Download models from URL or HuggingFace.
|
|
130
|
+
|
|
131
|
+
MODEL_PATH: Direct URL to a file, or a HuggingFace Hub repository ID.
|
|
132
|
+
|
|
133
|
+
Examples:
|
|
134
|
+
|
|
135
|
+
Download a direct URL to the current directory:
|
|
136
|
+
|
|
137
|
+
$ litert download https://example.com/model.tflite
|
|
138
|
+
|
|
139
|
+
Download all .tflite files from a HuggingFace repo:
|
|
140
|
+
|
|
141
|
+
$ litert download Qwen/Qwen1.5-0.5B-Chat --file "*.tflite"
|
|
142
|
+
|
|
143
|
+
Download from a private repository using a token:
|
|
144
|
+
|
|
145
|
+
$ litert download MyOrg/PrivateModel --token hf_your_token
|
|
146
|
+
"""),
|
|
147
|
+
)
|
|
148
|
+
@click.argument("model_path")
|
|
149
|
+
@click.option(
|
|
150
|
+
"--model-ref",
|
|
151
|
+
help=(
|
|
152
|
+
"Reference name for the model in centralized cache (e.g."
|
|
153
|
+
" 'mobilenet:v3')."
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
@click.option(
|
|
157
|
+
"--output",
|
|
158
|
+
help="Specify output directory (defaults to centralized cache).",
|
|
159
|
+
)
|
|
160
|
+
@click.option(
|
|
161
|
+
"--file",
|
|
162
|
+
"file_pattern",
|
|
163
|
+
help="Only download specific files in the HF repository (e.g. '*.tflite')",
|
|
164
|
+
)
|
|
165
|
+
@click.option("--token", help="Private model requiring a Token")
|
|
166
|
+
@click.option(
|
|
167
|
+
"--hf-id", help="Optional Hugging Face ID to associate with the model."
|
|
168
|
+
)
|
|
169
|
+
@deps.require_extra("download")
|
|
170
|
+
def download_cmd(
|
|
171
|
+
model_path: str,
|
|
172
|
+
model_ref: str | None,
|
|
173
|
+
output: str | None,
|
|
174
|
+
file_pattern: str | None,
|
|
175
|
+
token: str | None,
|
|
176
|
+
hf_id: str | None,
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Downloads models from URL or HuggingFace.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
model_path: Direct URL to a file, or a HuggingFace Hub repository ID.
|
|
182
|
+
model_ref: Optional reference name for the model.
|
|
183
|
+
output: Optional custom directory to save files.
|
|
184
|
+
file_pattern: Glob pattern to filter downloaded files from HF.
|
|
185
|
+
token: HuggingFace access token for private repositories.
|
|
186
|
+
hf_id: Optional Hugging Face ID to associate with the model.
|
|
187
|
+
"""
|
|
188
|
+
# Resolve output directory and handle sub-ref in model_ref
|
|
189
|
+
if model_ref and ":" in model_ref:
|
|
190
|
+
ref, resolved_sub_ref = model_ref.split(":", 1)
|
|
191
|
+
else:
|
|
192
|
+
ref = model_ref
|
|
193
|
+
resolved_sub_ref = None
|
|
194
|
+
|
|
195
|
+
if output:
|
|
196
|
+
output_path = pathlib.Path(output)
|
|
197
|
+
else:
|
|
198
|
+
# Use centralized cache by default for HF models
|
|
199
|
+
if not model_path.startswith(("http://", "https://")):
|
|
200
|
+
# Default to hf_id as ref if not provided
|
|
201
|
+
ref = ref or model_path.replace("/", "__")
|
|
202
|
+
# Flatten ref for directory name consistency
|
|
203
|
+
ref_flat = ref.replace("/", "__") if "/" in ref else ref
|
|
204
|
+
output_path = pathlib.Path(constants.LITERT_MODELS_CACHE_DIR) / ref_flat
|
|
205
|
+
else:
|
|
206
|
+
output_path = pathlib.Path(".")
|
|
207
|
+
|
|
208
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
209
|
+
|
|
210
|
+
if model_path.startswith(("http://", "https://")):
|
|
211
|
+
_download_from_url(model_path, output_path)
|
|
212
|
+
else:
|
|
213
|
+
_download_from_hf(model_path, output_path, file_pattern, token)
|
|
214
|
+
|
|
215
|
+
# Save metadata if it's a managed download (non-URL) to the centralized cache
|
|
216
|
+
if not model_path.startswith(("http://", "https://")) and not output:
|
|
217
|
+
final_ref = ref or model_path.replace("/", "__")
|
|
218
|
+
|
|
219
|
+
# Load existing metadata if it exists (to preserve other sub-refs)
|
|
220
|
+
metadata_file = output_path / "metadata.json"
|
|
221
|
+
metadata = {}
|
|
222
|
+
try:
|
|
223
|
+
with open(metadata_file, "r") as f:
|
|
224
|
+
metadata = json.load(f)
|
|
225
|
+
except (IOError, json.JSONDecodeError):
|
|
226
|
+
# If the file doesn't exist or is malformed, start with empty metadata.
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
# Update or create metadata
|
|
230
|
+
metadata.setdefault("files", [])
|
|
231
|
+
metadata.setdefault("sub_references", {})
|
|
232
|
+
|
|
233
|
+
metadata.update({
|
|
234
|
+
"model_ref": final_ref,
|
|
235
|
+
"hf_id": hf_id or model_path,
|
|
236
|
+
"source": "huggingface",
|
|
237
|
+
"created_at": (
|
|
238
|
+
datetime.datetime.now(datetime.timezone.utc).isoformat()
|
|
239
|
+
),
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
if file_pattern and file_pattern not in metadata["files"]:
|
|
243
|
+
metadata["files"].append(file_pattern)
|
|
244
|
+
|
|
245
|
+
# Handle sub-references
|
|
246
|
+
if resolved_sub_ref:
|
|
247
|
+
sub_references = metadata["sub_references"]
|
|
248
|
+
|
|
249
|
+
# If we downloaded a specific file, find it in the directory!
|
|
250
|
+
# Since file_pattern might be a glob, we search for matching files.
|
|
251
|
+
matched_files = []
|
|
252
|
+
if file_pattern:
|
|
253
|
+
# Glob search in output_path
|
|
254
|
+
matched_files = list(output_path.glob(file_pattern))
|
|
255
|
+
|
|
256
|
+
file_to_map = ""
|
|
257
|
+
if matched_files:
|
|
258
|
+
# Use the first matched file for now
|
|
259
|
+
file_to_map = matched_files[0].name
|
|
260
|
+
elif file_pattern and "*" not in file_pattern:
|
|
261
|
+
# If it's not a glob, assume it's the file name
|
|
262
|
+
file_to_map = file_pattern
|
|
263
|
+
|
|
264
|
+
sub_references[resolved_sub_ref] = {
|
|
265
|
+
"file": file_to_map,
|
|
266
|
+
"hf_id": hf_id or model_path,
|
|
267
|
+
}
|
|
268
|
+
click.echo(
|
|
269
|
+
f"Mapped sub-reference '{resolved_sub_ref}' to file '{file_to_map}'"
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
with open(metadata_file, "w") as f:
|
|
273
|
+
json.dump(metadata, f, indent=2)
|
|
274
|
+
click.secho(f"Metadata saved to {metadata_file}", fg="green")
|
|
@@ -0,0 +1,124 @@
|
|
|
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 importing local models into LiteRT cache."""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import datetime
|
|
21
|
+
import json
|
|
22
|
+
import pathlib
|
|
23
|
+
import shutil
|
|
24
|
+
|
|
25
|
+
import click
|
|
26
|
+
|
|
27
|
+
from ..core import constants
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@click.command(
|
|
31
|
+
"import",
|
|
32
|
+
help="Import a local file or directory into the centralized cache.",
|
|
33
|
+
)
|
|
34
|
+
@click.argument(
|
|
35
|
+
"file_path",
|
|
36
|
+
type=click.Path(
|
|
37
|
+
exists=True, file_okay=True, dir_okay=True, path_type=pathlib.Path
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
@click.option(
|
|
41
|
+
"--model-ref",
|
|
42
|
+
required=True,
|
|
43
|
+
help="Reference name for the model (e.g. 'my_model:enc').",
|
|
44
|
+
)
|
|
45
|
+
@click.option("--hf-id", help="Hugging Face ID to associate with the model.")
|
|
46
|
+
def import_cmd(
|
|
47
|
+
file_path: pathlib.Path, model_ref: str, hf_id: str | None
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Imports a local file or directory into the centralized cache.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
file_path: Path to the local file or directory to import.
|
|
53
|
+
model_ref: Reference name for management.
|
|
54
|
+
hf_id: Optional Hugging Face ID.
|
|
55
|
+
"""
|
|
56
|
+
# Parse model_ref for sub-reference if using ':' syntax
|
|
57
|
+
main_ref = model_ref
|
|
58
|
+
resolved_sub_ref = None
|
|
59
|
+
|
|
60
|
+
if ":" in model_ref:
|
|
61
|
+
parts = model_ref.split(":", 1)
|
|
62
|
+
main_ref = parts[0]
|
|
63
|
+
resolved_sub_ref = parts[1]
|
|
64
|
+
|
|
65
|
+
# Flatten main_ref for directory name consistency (e.g. a/b -> a__b)
|
|
66
|
+
main_ref_flat = main_ref.replace("/", "__") if "/" in main_ref else main_ref
|
|
67
|
+
output_path = pathlib.Path(constants.LITERT_MODELS_CACHE_DIR) / main_ref_flat
|
|
68
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
69
|
+
|
|
70
|
+
click.echo(f"Importing {file_path} to {output_path}...")
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
if file_path.is_dir():
|
|
74
|
+
# Copy directory content, not the directory itself, to avoid nested
|
|
75
|
+
# folders
|
|
76
|
+
for item in file_path.iterdir():
|
|
77
|
+
if item.is_dir():
|
|
78
|
+
shutil.copytree(item, output_path / item.name, dirs_exist_ok=True)
|
|
79
|
+
else:
|
|
80
|
+
shutil.copy(item, output_path / item.name)
|
|
81
|
+
else:
|
|
82
|
+
shutil.copy(file_path, output_path / file_path.name)
|
|
83
|
+
|
|
84
|
+
click.secho(f"Successfully imported to {output_path}", fg="green")
|
|
85
|
+
|
|
86
|
+
# Save metadata
|
|
87
|
+
metadata_file = output_path / "metadata.json"
|
|
88
|
+
metadata = {}
|
|
89
|
+
if metadata_file.exists():
|
|
90
|
+
try:
|
|
91
|
+
with open(metadata_file, "r") as f:
|
|
92
|
+
metadata = json.load(f)
|
|
93
|
+
except (IOError, json.JSONDecodeError):
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
metadata.update({
|
|
97
|
+
"model_ref": main_ref,
|
|
98
|
+
"hf_id": hf_id,
|
|
99
|
+
"source": "imported",
|
|
100
|
+
"import_path": str(file_path.resolve()),
|
|
101
|
+
"created_at": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
# Handle sub-references
|
|
105
|
+
if resolved_sub_ref:
|
|
106
|
+
if "sub_references" not in metadata:
|
|
107
|
+
metadata["sub_references"] = {}
|
|
108
|
+
|
|
109
|
+
# Use the file name in the destination directory
|
|
110
|
+
file_to_map = file_path.name
|
|
111
|
+
metadata["sub_references"][resolved_sub_ref] = {
|
|
112
|
+
"file": file_to_map,
|
|
113
|
+
"hf_id": hf_id,
|
|
114
|
+
}
|
|
115
|
+
click.echo(
|
|
116
|
+
f"Mapped sub-reference '{resolved_sub_ref}' to file '{file_to_map}'"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
with open(metadata_file, "w") as f:
|
|
120
|
+
json.dump(metadata, f, indent=2)
|
|
121
|
+
click.secho(f"Metadata saved to {metadata_file}", fg="green")
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
raise click.ClickException(f"Failed to import model: {e}") from e
|
|
@@ -0,0 +1,132 @@
|
|
|
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 listing managed models in LiteRT cache."""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import collections
|
|
21
|
+
import json
|
|
22
|
+
import pathlib
|
|
23
|
+
import sys
|
|
24
|
+
|
|
25
|
+
import click
|
|
26
|
+
|
|
27
|
+
from ..core.constants import LITERT_MODELS_CACHE_DIR
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@click.command(
|
|
31
|
+
"list",
|
|
32
|
+
help="List all managed models or detailed contents of a specific model.",
|
|
33
|
+
)
|
|
34
|
+
@click.argument("model_ref", required=False)
|
|
35
|
+
def list_cmd(model_ref: str | None) -> None:
|
|
36
|
+
"""Lists managed models. If MODEL_REF is provided, shows detailed contents."""
|
|
37
|
+
cache_dir = pathlib.Path(LITERT_MODELS_CACHE_DIR)
|
|
38
|
+
|
|
39
|
+
if not cache_dir.exists() or not cache_dir.is_dir():
|
|
40
|
+
click.echo("No managed models found (cache directory does not exist).")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
# Case 1: List detailed contents of a specific model
|
|
44
|
+
if model_ref:
|
|
45
|
+
# Flatten for directory check
|
|
46
|
+
ref_flat = model_ref.replace("/", "__") if "/" in model_ref else model_ref
|
|
47
|
+
model_dir = cache_dir / ref_flat
|
|
48
|
+
|
|
49
|
+
if not model_dir.exists() or not model_dir.is_dir():
|
|
50
|
+
click.secho(f"Error: Managed model '{model_ref}' not found.", fg="red")
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
|
|
53
|
+
metadata_file = model_dir / "metadata.json"
|
|
54
|
+
metadata = {}
|
|
55
|
+
if metadata_file.exists():
|
|
56
|
+
try:
|
|
57
|
+
with open(metadata_file, "r") as f:
|
|
58
|
+
metadata = json.load(f)
|
|
59
|
+
except Exception:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
click.echo(
|
|
63
|
+
"Details for managed model:"
|
|
64
|
+
f" {click.style(model_ref, fg='green', bold=True)}"
|
|
65
|
+
)
|
|
66
|
+
if metadata:
|
|
67
|
+
click.echo(f" HF ID: {metadata.get('hf_id', 'N/A')}")
|
|
68
|
+
click.echo(f" Source: {metadata.get('source', 'N/A')}")
|
|
69
|
+
click.echo(f" Created At: {metadata.get('created_at', 'N/A')}")
|
|
70
|
+
|
|
71
|
+
click.echo("\nFiles in model directory:")
|
|
72
|
+
sub_refs = metadata.get("sub_references", {})
|
|
73
|
+
# Reverse mapping for display
|
|
74
|
+
file_to_subrefs = collections.defaultdict(list)
|
|
75
|
+
for sub, info in sub_refs.items():
|
|
76
|
+
f = info.get("file")
|
|
77
|
+
if f:
|
|
78
|
+
file_to_subrefs[f].append(sub)
|
|
79
|
+
|
|
80
|
+
for item in sorted(model_dir.iterdir()):
|
|
81
|
+
if item.name == "metadata.json":
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
size_kb = item.stat().st_size / 1024
|
|
85
|
+
suffix = ""
|
|
86
|
+
if item.name in file_to_subrefs:
|
|
87
|
+
subs = ", ".join(file_to_subrefs[item.name])
|
|
88
|
+
suffix = f" {click.style(f'[{subs}]', fg='cyan')}"
|
|
89
|
+
|
|
90
|
+
click.echo(f" - {item.name:<30} ({size_kb:>8.1f} KB){suffix}")
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
# Case 2: List all managed models (Default)
|
|
94
|
+
models = [d for d in cache_dir.iterdir() if d.is_dir()]
|
|
95
|
+
|
|
96
|
+
if not models:
|
|
97
|
+
click.echo("No managed models found in cache.")
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
click.echo(f"Managed models in {cache_dir}:")
|
|
101
|
+
click.echo("-" * 60)
|
|
102
|
+
|
|
103
|
+
for model_dir in sorted(models, key=lambda x: x.name):
|
|
104
|
+
ref = model_dir.name
|
|
105
|
+
metadata_file = model_dir / "metadata.json"
|
|
106
|
+
hf_id = "N/A"
|
|
107
|
+
source = "N/A"
|
|
108
|
+
sub_refs = {}
|
|
109
|
+
|
|
110
|
+
if metadata_file.exists():
|
|
111
|
+
try:
|
|
112
|
+
with open(metadata_file, "r") as f:
|
|
113
|
+
metadata = json.load(f)
|
|
114
|
+
# Use original model_ref from metadata if available
|
|
115
|
+
ref = metadata.get("model_ref", ref)
|
|
116
|
+
hf_id = metadata.get("hf_id", "N/A")
|
|
117
|
+
source = metadata.get("source", "N/A")
|
|
118
|
+
sub_refs = metadata.get("sub_references", {})
|
|
119
|
+
except Exception:
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
click.echo(f"Ref: {click.style(ref, fg='green', bold=True)}")
|
|
123
|
+
click.echo(f" HF ID: {hf_id}")
|
|
124
|
+
click.echo(f" Source: {source}")
|
|
125
|
+
|
|
126
|
+
if sub_refs:
|
|
127
|
+
click.echo(" Sub-references:")
|
|
128
|
+
for sub_ref, info in sub_refs.items():
|
|
129
|
+
file_name = info.get("file", "N/A")
|
|
130
|
+
click.echo(f" - {sub_ref} -> {file_name}")
|
|
131
|
+
|
|
132
|
+
click.echo("-" * 60)
|
|
@@ -0,0 +1,74 @@
|
|
|
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 to call LiteRT-LM CLI tool."""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
|
|
23
|
+
import click
|
|
24
|
+
from litert_cli.core import deps
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.command(
|
|
28
|
+
name="lm",
|
|
29
|
+
context_settings=dict(
|
|
30
|
+
ignore_unknown_options=True,
|
|
31
|
+
allow_extra_args=True,
|
|
32
|
+
help_option_names=[],
|
|
33
|
+
),
|
|
34
|
+
help="""LiteRT-LM CLI commands.
|
|
35
|
+
|
|
36
|
+
This command is a transparent proxy to the native `litert-lm-cli` package.
|
|
37
|
+
Any wildcard arguments used here are forwarded directly to the actual engine.
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
|
|
41
|
+
Display the native litert-lm help:
|
|
42
|
+
|
|
43
|
+
$ litert lm --help
|
|
44
|
+
|
|
45
|
+
Run a generative LLM model:
|
|
46
|
+
|
|
47
|
+
$ litert lm run model.litertlm
|
|
48
|
+
$ litert lm run gemma3-1b
|
|
49
|
+
|
|
50
|
+
Benchmark a generative LLM model:
|
|
51
|
+
|
|
52
|
+
$ litert lm benchmark gemma3-1b
|
|
53
|
+
""",
|
|
54
|
+
)
|
|
55
|
+
@deps.require_extra("lm")
|
|
56
|
+
@click.pass_context
|
|
57
|
+
def lm_cmd(ctx: click.Context) -> None:
|
|
58
|
+
"""LiteRT-LM related commands.
|
|
59
|
+
|
|
60
|
+
This command is a transparent proxy to the native `litert-lm-cli` package.
|
|
61
|
+
Any wildcard arguments used here are forwarded directly to the actual engine.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
ctx: click Context object containing forwarded arguments.
|
|
65
|
+
"""
|
|
66
|
+
try:
|
|
67
|
+
result = subprocess.run(["litert-lm"] + ctx.args, check=False)
|
|
68
|
+
sys.exit(result.returncode)
|
|
69
|
+
except FileNotFoundError:
|
|
70
|
+
click.secho("Error: 'litert-lm' executable not found in PATH.", fg="red")
|
|
71
|
+
click.secho(
|
|
72
|
+
"Please install 'litert-lm-cli' to use this command.", fg="yellow"
|
|
73
|
+
)
|
|
74
|
+
sys.exit(1)
|