lemonade-sdk 7.0.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.
Potentially problematic release.
This version of lemonade-sdk might be problematic. Click here for more details.
- lemonade/__init__.py +5 -0
- lemonade/api.py +125 -0
- lemonade/cache.py +85 -0
- lemonade/cli.py +135 -0
- lemonade/common/__init__.py +0 -0
- lemonade/common/analyze_model.py +26 -0
- lemonade/common/build.py +223 -0
- lemonade/common/cli_helpers.py +139 -0
- lemonade/common/exceptions.py +98 -0
- lemonade/common/filesystem.py +368 -0
- lemonade/common/labels.py +61 -0
- lemonade/common/onnx_helpers.py +176 -0
- lemonade/common/plugins.py +10 -0
- lemonade/common/printing.py +110 -0
- lemonade/common/status.py +490 -0
- lemonade/common/system_info.py +390 -0
- lemonade/common/tensor_helpers.py +83 -0
- lemonade/common/test_helpers.py +28 -0
- lemonade/profilers/__init__.py +1 -0
- lemonade/profilers/memory_tracker.py +257 -0
- lemonade/profilers/profiler.py +55 -0
- lemonade/sequence.py +363 -0
- lemonade/state.py +159 -0
- lemonade/tools/__init__.py +1 -0
- lemonade/tools/adapter.py +104 -0
- lemonade/tools/bench.py +284 -0
- lemonade/tools/huggingface_bench.py +267 -0
- lemonade/tools/huggingface_load.py +520 -0
- lemonade/tools/humaneval.py +258 -0
- lemonade/tools/llamacpp.py +261 -0
- lemonade/tools/llamacpp_bench.py +154 -0
- lemonade/tools/management_tools.py +273 -0
- lemonade/tools/mmlu.py +327 -0
- lemonade/tools/ort_genai/__init__.py +0 -0
- lemonade/tools/ort_genai/oga.py +1129 -0
- lemonade/tools/ort_genai/oga_bench.py +142 -0
- lemonade/tools/perplexity.py +146 -0
- lemonade/tools/prompt.py +228 -0
- lemonade/tools/quark/__init__.py +0 -0
- lemonade/tools/quark/quark_load.py +172 -0
- lemonade/tools/quark/quark_quantize.py +439 -0
- lemonade/tools/report/__init__.py +0 -0
- lemonade/tools/report/llm_report.py +203 -0
- lemonade/tools/report/table.py +739 -0
- lemonade/tools/server/__init__.py +0 -0
- lemonade/tools/server/serve.py +1354 -0
- lemonade/tools/server/tool_calls.py +146 -0
- lemonade/tools/tool.py +374 -0
- lemonade/version.py +1 -0
- lemonade_install/__init__.py +1 -0
- lemonade_install/install.py +774 -0
- lemonade_sdk-7.0.0.dist-info/METADATA +116 -0
- lemonade_sdk-7.0.0.dist-info/RECORD +61 -0
- lemonade_sdk-7.0.0.dist-info/WHEEL +5 -0
- lemonade_sdk-7.0.0.dist-info/entry_points.txt +4 -0
- lemonade_sdk-7.0.0.dist-info/licenses/LICENSE +201 -0
- lemonade_sdk-7.0.0.dist-info/licenses/NOTICE.md +21 -0
- lemonade_sdk-7.0.0.dist-info/top_level.txt +3 -0
- lemonade_server/cli.py +260 -0
- lemonade_server/model_manager.py +98 -0
- lemonade_server/server_models.json +142 -0
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
# Utility that helps users install software. It is structured like a
|
|
2
|
+
# ManagementTool, however it is not a ManagementTool because it cannot
|
|
3
|
+
# import any lemonade modules in order to avoid any installation
|
|
4
|
+
# collisions on imported modules.
|
|
5
|
+
#
|
|
6
|
+
# This tool can install Ryzen AI software artifacts (libraries, wheels, etc.).
|
|
7
|
+
# The Ryzen AI hybrid artifacts directory hierarchy is:
|
|
8
|
+
#
|
|
9
|
+
# RYZEN_AI\hybrid
|
|
10
|
+
# libs
|
|
11
|
+
# wheels
|
|
12
|
+
#
|
|
13
|
+
# The Ryzen AI npu artifacts directory hierarchy is:
|
|
14
|
+
#
|
|
15
|
+
# RYZEN_AI\npu
|
|
16
|
+
# libs
|
|
17
|
+
# wheels
|
|
18
|
+
#
|
|
19
|
+
# In the above, RYZEN_AI is the path to the folder ryzen_ai that is contained in the
|
|
20
|
+
# same folder as the executable python.exe.
|
|
21
|
+
# The folder RYZEN_AI also contains a file `version_info.json` that contains information
|
|
22
|
+
# about the installed Ryzen AI artifacts.
|
|
23
|
+
#
|
|
24
|
+
# In any python environment, only one set of artifacts can be installed at a time.
|
|
25
|
+
# Python environments created by Lemonade v6.1.x or earlier will need to be recreated.
|
|
26
|
+
#
|
|
27
|
+
# The Ryzen AI 1.3.0 artifact files use a different directory hierarchy.
|
|
28
|
+
# The Ryzen AI 1.3.0 hybrid artifacts directory hierarchy is:
|
|
29
|
+
#
|
|
30
|
+
# RYZEN_AI\hybrid\hybrid-llm-artifacts_1.3.0_lounge\hybrid-llm-artifacts\
|
|
31
|
+
# onnxruntime_genai\lib
|
|
32
|
+
# onnxruntime_genai\wheel
|
|
33
|
+
# onnx_utils\bin
|
|
34
|
+
# eula\eula
|
|
35
|
+
#
|
|
36
|
+
# The Ryzen AI 1.3.0 npu artifacts directory hierarchy is:
|
|
37
|
+
#
|
|
38
|
+
# RYZEN_AI\npu\amd_oga\
|
|
39
|
+
# bins\xclbin\stx
|
|
40
|
+
# libs
|
|
41
|
+
# wheels
|
|
42
|
+
#
|
|
43
|
+
|
|
44
|
+
import argparse
|
|
45
|
+
import glob
|
|
46
|
+
import json
|
|
47
|
+
import os
|
|
48
|
+
from pathlib import Path
|
|
49
|
+
import re
|
|
50
|
+
import shutil
|
|
51
|
+
import subprocess
|
|
52
|
+
import sys
|
|
53
|
+
from typing import Optional
|
|
54
|
+
import zipfile
|
|
55
|
+
import requests
|
|
56
|
+
|
|
57
|
+
DEFAULT_RYZEN_AI_VERSION = "1.4.0"
|
|
58
|
+
version_info_filename = "version_info.json"
|
|
59
|
+
|
|
60
|
+
lemonade_install_dir = Path(__file__).parent.parent.parent
|
|
61
|
+
DEFAULT_QUARK_VERSION = "quark-0.6.0"
|
|
62
|
+
DEFAULT_QUARK_DIR = os.path.join(
|
|
63
|
+
lemonade_install_dir, "install", "quark", DEFAULT_QUARK_VERSION
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# List of supported Ryzen AI processor series (can be extended in the future)
|
|
67
|
+
SUPPORTED_RYZEN_AI_SERIES = ["300"]
|
|
68
|
+
|
|
69
|
+
npu_install_data = {
|
|
70
|
+
"1.3.0": {
|
|
71
|
+
"artifacts_zipfile": "ryzen_ai_13_ga/npu-llm-artifacts_1.3.0.zip",
|
|
72
|
+
"license_file": (
|
|
73
|
+
"https://account.amd.com/content/dam/account/en/licenses/download/"
|
|
74
|
+
"amd-end-user-license-agreement.pdf"
|
|
75
|
+
),
|
|
76
|
+
"license_tag": "Beta ",
|
|
77
|
+
},
|
|
78
|
+
"1.4.0": {
|
|
79
|
+
"artifacts_zipfile": (
|
|
80
|
+
"https://www.xilinx.com/bin/public/openDownload?"
|
|
81
|
+
"filename=npu-llm-artifacts_1.4.0_032925.zip"
|
|
82
|
+
),
|
|
83
|
+
"license_file": (
|
|
84
|
+
"https://account.amd.com/content/dam/account/en/licenses/download/"
|
|
85
|
+
"amd-end-user-license-agreement.pdf"
|
|
86
|
+
),
|
|
87
|
+
"license_tag": "",
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
hybrid_install_data = {
|
|
92
|
+
"1.3.0": {
|
|
93
|
+
"artifacts_zipfile": (
|
|
94
|
+
"https://www.xilinx.com/bin/public/openDownload?"
|
|
95
|
+
"filename=hybrid-llm-artifacts_1.3.0_012725.zip"
|
|
96
|
+
),
|
|
97
|
+
"license_file": (
|
|
98
|
+
"https://www.xilinx.com/bin/public/openDownload?"
|
|
99
|
+
"filename=AMD%20End%20User%20License%20Agreement.pdf"
|
|
100
|
+
),
|
|
101
|
+
"license_tag": "",
|
|
102
|
+
},
|
|
103
|
+
"1.4.0": {
|
|
104
|
+
"artifacts_zipfile": (
|
|
105
|
+
"https://www.xilinx.com/bin/public/openDownload?"
|
|
106
|
+
"filename=hybrid-llm-artifacts_1.4.0_032925.zip"
|
|
107
|
+
),
|
|
108
|
+
"license_file": (
|
|
109
|
+
"https://account.amd.com/content/dam/account/en/licenses/download/"
|
|
110
|
+
"amd-end-user-license-agreement.pdf"
|
|
111
|
+
),
|
|
112
|
+
"license_tag": "",
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
model_prep_install_data = {
|
|
117
|
+
"1.4.0": {
|
|
118
|
+
"model_prep_artifacts_zipfile": (
|
|
119
|
+
"https://www.xilinx.com/bin/public/openDownload?"
|
|
120
|
+
"filename=model-prep-artifacts-1.4.0-0331.zip"
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_ryzen_ai_path(check_exists=True):
|
|
127
|
+
python_executable_path = sys.executable
|
|
128
|
+
python_executable_dir = os.path.dirname(python_executable_path)
|
|
129
|
+
ryzen_ai_folder = os.path.join(python_executable_dir, "ryzen_ai")
|
|
130
|
+
if check_exists:
|
|
131
|
+
if not os.path.isdir(ryzen_ai_folder):
|
|
132
|
+
raise RuntimeError(
|
|
133
|
+
"Please use `lemonade-install --ryzenai` to install Ryzen AI into the current "
|
|
134
|
+
"python environment. For more info, see "
|
|
135
|
+
"https://ryzenai.docs.amd.com/en/latest/llm/high_level_python.html."
|
|
136
|
+
)
|
|
137
|
+
return ryzen_ai_folder
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_ryzen_ai_version_info():
|
|
141
|
+
ryzen_ai_folder = get_ryzen_ai_path()
|
|
142
|
+
version_info_path = os.path.join(ryzen_ai_folder, version_info_filename)
|
|
143
|
+
if not os.path.isfile(version_info_path):
|
|
144
|
+
raise RuntimeError(
|
|
145
|
+
f"The file {version_info_filename} is missing from the Ryzen AI "
|
|
146
|
+
f"folder {ryzen_ai_folder}. Please run `lemonade-install` to fix. For more info, see "
|
|
147
|
+
"https://ryzenai.docs.amd.com/en/latest/llm/high_level_python.html."
|
|
148
|
+
)
|
|
149
|
+
with open(version_info_path, encoding="utf-8") as file:
|
|
150
|
+
version_info = json.load(file)
|
|
151
|
+
return version_info
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def get_oga_npu_dir():
|
|
155
|
+
version_info = get_ryzen_ai_version_info()
|
|
156
|
+
version = version_info["version"]
|
|
157
|
+
ryzen_ai_folder = get_ryzen_ai_path()
|
|
158
|
+
if "1.3.0" in version:
|
|
159
|
+
npu_dir = os.path.join(ryzen_ai_folder, "npu", "amd_oga")
|
|
160
|
+
else:
|
|
161
|
+
npu_dir = os.path.join(ryzen_ai_folder, "npu")
|
|
162
|
+
if not os.path.isdir(npu_dir):
|
|
163
|
+
raise RuntimeError(
|
|
164
|
+
f"The npu artifacts are missing from the Ryzen AI folder {ryzen_ai_folder}. "
|
|
165
|
+
" Please run `lemonade-install --ryzenai npu` again to fix. For more info, see"
|
|
166
|
+
"https://ryzenai.docs.amd.com/en/latest/llm/high_level_python.html."
|
|
167
|
+
)
|
|
168
|
+
return npu_dir, version
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def get_oga_hybrid_dir():
|
|
172
|
+
version_info = get_ryzen_ai_version_info()
|
|
173
|
+
version = version_info["version"]
|
|
174
|
+
ryzen_ai_folder = get_ryzen_ai_path()
|
|
175
|
+
if "1.3.0" in version:
|
|
176
|
+
hybrid_dir = os.path.join(
|
|
177
|
+
ryzen_ai_folder,
|
|
178
|
+
"hybrid",
|
|
179
|
+
"hybrid-llm-artifacts_1.3.0_lounge",
|
|
180
|
+
"hybrid-llm-artifacts",
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
hybrid_dir = os.path.join(ryzen_ai_folder, "hybrid")
|
|
184
|
+
if not os.path.isdir(hybrid_dir):
|
|
185
|
+
raise RuntimeError(
|
|
186
|
+
f"The hybrid artifacts are missing from the Ryzen AI folder {ryzen_ai_folder}. "
|
|
187
|
+
" Please run `lemonade-install --ryzenai hybrid` again to fix. For more info, see "
|
|
188
|
+
"https://ryzenai.docs.amd.com/en/latest/llm/high_level_python.html."
|
|
189
|
+
)
|
|
190
|
+
return hybrid_dir, version
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def download_lfs_file(token, file, output_filename):
|
|
194
|
+
"""Downloads a file from LFS"""
|
|
195
|
+
# Set up the headers for the request
|
|
196
|
+
headers = {
|
|
197
|
+
"Authorization": f"token {token}",
|
|
198
|
+
"Accept": "application/vnd.github.v3+json",
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
response = requests.get(
|
|
202
|
+
f"https://api.github.com/repos/aigdat/ryzenai-sw-ea/contents/{file}",
|
|
203
|
+
headers=headers,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Check if the request was successful
|
|
207
|
+
if response.status_code == 200:
|
|
208
|
+
# Parse the JSON response to get the download URL
|
|
209
|
+
content = response.json()
|
|
210
|
+
download_url = content.get("download_url")
|
|
211
|
+
|
|
212
|
+
if download_url:
|
|
213
|
+
# Download the file from the download URL
|
|
214
|
+
file_response = requests.get(download_url)
|
|
215
|
+
|
|
216
|
+
# Write the content to a file
|
|
217
|
+
with open(output_filename, "wb") as file:
|
|
218
|
+
file.write(file_response.content)
|
|
219
|
+
else:
|
|
220
|
+
print("Download URL not found in the response.")
|
|
221
|
+
else:
|
|
222
|
+
raise ValueError(
|
|
223
|
+
"Failed to fetch the content from GitHub API. "
|
|
224
|
+
f"Status code: {response.status_code}, Response: {response.json()}"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
if not os.path.isfile(output_filename):
|
|
228
|
+
raise ValueError(f"Error: {output_filename} does not exist.")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def download_file(url: str, output_filename: str, description: str = None):
|
|
232
|
+
try:
|
|
233
|
+
response = requests.get(url)
|
|
234
|
+
if response.status_code != 200:
|
|
235
|
+
raise Exception(
|
|
236
|
+
f"Failed to fetch the content from GitHub API. \
|
|
237
|
+
Status code: {response.status_code}, Response: {response.json()}"
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
with open(output_filename, "wb") as file:
|
|
241
|
+
file.write(response.content)
|
|
242
|
+
|
|
243
|
+
if not os.path.isfile(output_filename):
|
|
244
|
+
raise Exception(f"\nError: Failed to write to {output_filename}")
|
|
245
|
+
|
|
246
|
+
except Exception as e:
|
|
247
|
+
raise Exception(f"\nError downloading {description or 'file'}: {str(e)}")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def unzip_file(zip_path, extract_to):
|
|
251
|
+
"""Unzips the specified zip file to the given directory."""
|
|
252
|
+
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
|
253
|
+
zip_ref.extractall(extract_to)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def check_ryzen_ai_processor():
|
|
257
|
+
"""
|
|
258
|
+
Checks if the current system has a supported Ryzen AI processor.
|
|
259
|
+
|
|
260
|
+
Raises:
|
|
261
|
+
UnsupportedPlatformError: If the processor is not a supported Ryzen AI models.
|
|
262
|
+
"""
|
|
263
|
+
if not sys.platform.startswith("win"):
|
|
264
|
+
raise UnsupportedPlatformError(
|
|
265
|
+
"Ryzen AI installation is only supported on Windows."
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
is_supported = False
|
|
269
|
+
cpu_name = ""
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
# Use Windows registry to get CPU information
|
|
273
|
+
result = subprocess.run(
|
|
274
|
+
[
|
|
275
|
+
"reg",
|
|
276
|
+
"query",
|
|
277
|
+
"HKEY_LOCAL_MACHINE\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0",
|
|
278
|
+
"/v",
|
|
279
|
+
"ProcessorNameString",
|
|
280
|
+
],
|
|
281
|
+
capture_output=True,
|
|
282
|
+
text=True,
|
|
283
|
+
check=True,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Parse the output to extract the CPU name
|
|
287
|
+
for line in result.stdout.splitlines():
|
|
288
|
+
if "ProcessorNameString" in line:
|
|
289
|
+
# The format is typically: name REG_SZ value
|
|
290
|
+
parts = line.strip().split("REG_SZ")
|
|
291
|
+
if len(parts) >= 2:
|
|
292
|
+
cpu_name = parts[1].strip()
|
|
293
|
+
break
|
|
294
|
+
|
|
295
|
+
# Check for any supported series
|
|
296
|
+
for series in SUPPORTED_RYZEN_AI_SERIES:
|
|
297
|
+
# Look for the series number pattern - matches any processor in the supported series
|
|
298
|
+
pattern = rf"ryzen ai.*\b{series[0]}\d{{2}}\b"
|
|
299
|
+
match = re.search(pattern, cpu_name.lower(), re.IGNORECASE)
|
|
300
|
+
if match:
|
|
301
|
+
is_supported = True
|
|
302
|
+
break
|
|
303
|
+
|
|
304
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
305
|
+
supported_series_str = ", ".join(SUPPORTED_RYZEN_AI_SERIES)
|
|
306
|
+
raise UnsupportedPlatformError(
|
|
307
|
+
f"Ryzen AI installation requires a Ryzen AI {supported_series_str} "
|
|
308
|
+
f"series processor. Processor detection failed."
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if not is_supported:
|
|
312
|
+
supported_series_str = ", ".join(SUPPORTED_RYZEN_AI_SERIES)
|
|
313
|
+
raise UnsupportedPlatformError(
|
|
314
|
+
f"Ryzen AI installation requires a Ryzen AI {supported_series_str} "
|
|
315
|
+
f"series processor. Your current processor ({cpu_name}) is not supported."
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def download_and_extract_package(
|
|
320
|
+
url: str,
|
|
321
|
+
version: str,
|
|
322
|
+
install_dir: str,
|
|
323
|
+
package_name: str,
|
|
324
|
+
) -> str:
|
|
325
|
+
"""
|
|
326
|
+
Downloads, Extracts and Renames the folder
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
url: Download URL for the package
|
|
330
|
+
version: Version string
|
|
331
|
+
install_dir: Directory to install to
|
|
332
|
+
package_name: Name of the package
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
str: Path where package was extracted (renamed to package-version)
|
|
336
|
+
"""
|
|
337
|
+
zip_filename = f"{package_name}-{version}.zip"
|
|
338
|
+
zip_path = os.path.join(install_dir, zip_filename)
|
|
339
|
+
target_folder = os.path.join(install_dir, f"{package_name}-{version}")
|
|
340
|
+
|
|
341
|
+
print(f"\nDownloading {package_name} from {url}")
|
|
342
|
+
response = requests.get(url)
|
|
343
|
+
if response.status_code == 200:
|
|
344
|
+
with open(zip_path, "wb") as f:
|
|
345
|
+
f.write(response.content)
|
|
346
|
+
else:
|
|
347
|
+
raise Exception(
|
|
348
|
+
f"Failed to download {package_name}. Status code: {response.status_code}"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
print("\n[INFO]: Extracting zip file ...")
|
|
352
|
+
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
|
353
|
+
zip_ref.extractall(install_dir)
|
|
354
|
+
print("\n[INFO]: Extraction completed.")
|
|
355
|
+
|
|
356
|
+
os.remove(zip_path)
|
|
357
|
+
|
|
358
|
+
extracted_folder = None
|
|
359
|
+
for folder in os.listdir(install_dir):
|
|
360
|
+
folder_path = os.path.join(install_dir, folder)
|
|
361
|
+
if os.path.isdir(folder_path) and folder.startswith(f"{package_name}-"):
|
|
362
|
+
extracted_folder = folder_path
|
|
363
|
+
break
|
|
364
|
+
|
|
365
|
+
if extracted_folder is None:
|
|
366
|
+
raise ValueError(
|
|
367
|
+
f"Error: Extracted folder for {package_name} version {version} not found."
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# Rename extracted folder to package-version
|
|
371
|
+
if extracted_folder != target_folder:
|
|
372
|
+
if os.path.exists(target_folder):
|
|
373
|
+
shutil.rmtree(target_folder) # Remove if already exists
|
|
374
|
+
os.rename(extracted_folder, target_folder)
|
|
375
|
+
print(f"\n[INFO]: Renamed folder to {target_folder}")
|
|
376
|
+
|
|
377
|
+
return target_folder
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class LicenseRejected(Exception):
|
|
381
|
+
"""
|
|
382
|
+
Raise an exception if the user rejects the license prompt.
|
|
383
|
+
"""
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class UnsupportedPlatformError(Exception):
|
|
387
|
+
"""
|
|
388
|
+
Raise an exception if the hardware is not supported.
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
class Install:
|
|
393
|
+
"""
|
|
394
|
+
Installs the necessary software for specific lemonade features.
|
|
395
|
+
"""
|
|
396
|
+
|
|
397
|
+
@staticmethod
|
|
398
|
+
def parser() -> argparse.ArgumentParser:
|
|
399
|
+
parser = argparse.ArgumentParser(
|
|
400
|
+
description="Installs the necessary software for specific lemonade features",
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
parser.add_argument(
|
|
404
|
+
"--ryzenai",
|
|
405
|
+
help="Install Ryzen AI software for LLMs. Requires an authentication token. "
|
|
406
|
+
"The 'npu' and 'hybrid' choices install the default "
|
|
407
|
+
f"{DEFAULT_RYZEN_AI_VERSION} version.",
|
|
408
|
+
choices=[
|
|
409
|
+
"npu",
|
|
410
|
+
"hybrid",
|
|
411
|
+
"unified",
|
|
412
|
+
"npu-1.3.0",
|
|
413
|
+
"hybrid-1.3.0",
|
|
414
|
+
"npu-1.4.0",
|
|
415
|
+
"hybrid-1.4.0",
|
|
416
|
+
"unified-1.4.0",
|
|
417
|
+
],
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
parser.add_argument(
|
|
421
|
+
"--build-model",
|
|
422
|
+
action="store_true",
|
|
423
|
+
help="If specified, installs additional model prep artifacts for Ryzen AI.",
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
parser.add_argument(
|
|
427
|
+
"-y",
|
|
428
|
+
"--yes",
|
|
429
|
+
action="store_true",
|
|
430
|
+
help="Answer 'yes' to all questions. "
|
|
431
|
+
"Make sure to review all legal agreements before selecting this option.",
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
parser.add_argument(
|
|
435
|
+
"--token",
|
|
436
|
+
help="Some software requires an authentication token to download. "
|
|
437
|
+
"If this argument is not provided, the token can come from an environment "
|
|
438
|
+
"variable (e.g., Ryzen AI uses environment variable OGA_TOKEN).",
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
parser.add_argument(
|
|
442
|
+
"--quark",
|
|
443
|
+
help="Install Quark Quantization tool for LLMs",
|
|
444
|
+
choices=["0.6.0"],
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
return parser
|
|
448
|
+
|
|
449
|
+
@staticmethod
|
|
450
|
+
def _get_license_acceptance(version, license_file, license_tag, yes):
|
|
451
|
+
if yes:
|
|
452
|
+
print(
|
|
453
|
+
f"\nYou have accepted the AMD {license_tag}Software End User License "
|
|
454
|
+
f"Agreement for Ryzen AI {version} by providing the `--yes` option. "
|
|
455
|
+
"The license file is available for your review at "
|
|
456
|
+
f"{license_file}\n"
|
|
457
|
+
)
|
|
458
|
+
else:
|
|
459
|
+
print(
|
|
460
|
+
f"\nYou must accept the AMD {license_tag}Software End User License "
|
|
461
|
+
"Agreement in order to install this software. To continue, type the word "
|
|
462
|
+
"yes to assert that you agree and are authorized to agree "
|
|
463
|
+
"on behalf of your organization, to the terms and "
|
|
464
|
+
f"conditions, in the {license_tag}Software End User License Agreement, "
|
|
465
|
+
"which terms and conditions may be reviewed, downloaded and "
|
|
466
|
+
"printed from this link: "
|
|
467
|
+
f"{license_file}\n"
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
response = input("Would you like to accept the license (yes/No)? ")
|
|
471
|
+
if response.lower() == "yes" or response.lower() == "y":
|
|
472
|
+
pass
|
|
473
|
+
else:
|
|
474
|
+
raise LicenseRejected("Exiting because the license was not accepted.")
|
|
475
|
+
|
|
476
|
+
@staticmethod
|
|
477
|
+
def _install_artifacts(version, install_dir, token, file, wheels_full_path):
|
|
478
|
+
archive_file_name = "artifacts.zip"
|
|
479
|
+
archive_file_path = os.path.join(install_dir, archive_file_name)
|
|
480
|
+
|
|
481
|
+
if token:
|
|
482
|
+
token_to_use = token
|
|
483
|
+
else:
|
|
484
|
+
token_to_use = os.environ.get("OGA_TOKEN")
|
|
485
|
+
|
|
486
|
+
# Retrieve the installation artifacts
|
|
487
|
+
if os.path.exists(install_dir):
|
|
488
|
+
# Remove any artifacts from a previous installation attempt
|
|
489
|
+
shutil.rmtree(install_dir)
|
|
490
|
+
os.makedirs(install_dir)
|
|
491
|
+
if any(proto in file for proto in ["https:", "http:"]):
|
|
492
|
+
print(f"\nDownloading {file}\n")
|
|
493
|
+
download_file(file, archive_file_path)
|
|
494
|
+
elif "file:" in file:
|
|
495
|
+
local_file = file.replace("file://", "C:/")
|
|
496
|
+
print(f"\nCopying {local_file}\n")
|
|
497
|
+
shutil.copy(local_file, archive_file_path)
|
|
498
|
+
else:
|
|
499
|
+
print(f"\nDownloading {file} from GitHub LFS to {install_dir}\n")
|
|
500
|
+
download_lfs_file(token_to_use, file, archive_file_path)
|
|
501
|
+
|
|
502
|
+
# Unzip the file
|
|
503
|
+
print(f"\nUnzipping archive {archive_file_path}\n")
|
|
504
|
+
unzip_file(archive_file_path, install_dir)
|
|
505
|
+
print(f"\nDLLs installed\n")
|
|
506
|
+
|
|
507
|
+
# Install all whl files in the specified wheels folder
|
|
508
|
+
if wheels_full_path is not None:
|
|
509
|
+
print(f"\nInstalling wheels from {wheels_full_path}\n")
|
|
510
|
+
if version == "1.3.0":
|
|
511
|
+
# Install one wheel file at a time (1.3.0 npu build only works this way)
|
|
512
|
+
for file in os.listdir(wheels_full_path):
|
|
513
|
+
if file.endswith(".whl"):
|
|
514
|
+
install_cmd = (
|
|
515
|
+
f"{sys.executable} -m pip install "
|
|
516
|
+
f"{os.path.join(wheels_full_path, file)}"
|
|
517
|
+
)
|
|
518
|
+
print(f"\nInstalling {file} with command {install_cmd}\n")
|
|
519
|
+
subprocess.run(install_cmd, check=True, shell=True)
|
|
520
|
+
else:
|
|
521
|
+
# Install all the wheel files together, allowing pip to work out the dependencies
|
|
522
|
+
wheel_files = glob.glob(os.path.join(wheels_full_path, "*.whl"))
|
|
523
|
+
install_cmd = [sys.executable, "-m", "pip", "install"] + wheel_files
|
|
524
|
+
subprocess.run(
|
|
525
|
+
install_cmd,
|
|
526
|
+
check=True,
|
|
527
|
+
shell=True,
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
# Delete the zip file
|
|
531
|
+
print(f"\nCleaning up, removing {archive_file_path}\n")
|
|
532
|
+
os.remove(archive_file_path)
|
|
533
|
+
|
|
534
|
+
@staticmethod
|
|
535
|
+
def _install_ryzenai_model_artifacts(ryzen_ai_folder, version):
|
|
536
|
+
"""
|
|
537
|
+
Installs the Ryzen AI model prep artifacts if the build-model flag is provided.
|
|
538
|
+
|
|
539
|
+
Args:
|
|
540
|
+
ryzen_ai_folder (str): Path to the Ryzen AI installation folder.
|
|
541
|
+
version (str): Version of the Ryzen AI artifacts.
|
|
542
|
+
"""
|
|
543
|
+
|
|
544
|
+
# Check if model prep artifacts are available for the given version
|
|
545
|
+
if version not in model_prep_install_data:
|
|
546
|
+
raise ValueError(
|
|
547
|
+
"Model prep artifacts are only available "
|
|
548
|
+
"for version 1.4.0 and above, "
|
|
549
|
+
"and only for 'hybrid' and 'npu-only' targets."
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
# Get the model prep artifacts zipfile URL
|
|
553
|
+
file = model_prep_install_data[version].get(
|
|
554
|
+
"model_prep_artifacts_zipfile", None
|
|
555
|
+
)
|
|
556
|
+
if not file:
|
|
557
|
+
raise ValueError(
|
|
558
|
+
"Model prep artifacts zipfile URL is missing for version " f"{version}."
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
# Download and extract the model prep artifacts
|
|
562
|
+
print(f"\nDownloading model prep artifacts for Ryzen AI {version}.")
|
|
563
|
+
model_prep_dir = download_and_extract_package(
|
|
564
|
+
url=file,
|
|
565
|
+
version=version,
|
|
566
|
+
install_dir=ryzen_ai_folder,
|
|
567
|
+
package_name="model-prep",
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
# Install all .whl files from the extracted model prep artifacts
|
|
571
|
+
wheels_full_path = os.path.join(model_prep_dir)
|
|
572
|
+
print(f"\nInstalling model prep wheels from {wheels_full_path}\n")
|
|
573
|
+
wheel_files = glob.glob(os.path.join(wheels_full_path, "*.whl"))
|
|
574
|
+
if not wheel_files:
|
|
575
|
+
raise ValueError(
|
|
576
|
+
f"No .whl files found in the model prep artifacts directory: {wheels_full_path}"
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
install_cmd = [sys.executable, "-m", "pip", "install"] + wheel_files
|
|
580
|
+
subprocess.run(install_cmd, check=True, shell=True)
|
|
581
|
+
|
|
582
|
+
print(f"\nModel prep artifacts installed successfully from {model_prep_dir}.")
|
|
583
|
+
return file
|
|
584
|
+
|
|
585
|
+
@staticmethod
|
|
586
|
+
def _install_ryzenai_npu(ryzen_ai_folder, version, yes, token, skip_wheels=False):
|
|
587
|
+
# Check version is valid
|
|
588
|
+
if version not in npu_install_data:
|
|
589
|
+
raise ValueError(
|
|
590
|
+
"Invalid Ryzen AI version for NPU. Valid options are "
|
|
591
|
+
f"{list(npu_install_data.keys())}."
|
|
592
|
+
)
|
|
593
|
+
file = npu_install_data[version].get("artifacts_zipfile", None)
|
|
594
|
+
license_file = npu_install_data[version].get("license_file", None)
|
|
595
|
+
license_tag = npu_install_data[version].get("license_tag", None)
|
|
596
|
+
install_dir = os.path.join(ryzen_ai_folder, "npu")
|
|
597
|
+
if version == "1.3.0":
|
|
598
|
+
wheels_full_path = os.path.join(install_dir, "amd_oga/wheels")
|
|
599
|
+
else:
|
|
600
|
+
wheels_full_path = os.path.join(install_dir, "wheels")
|
|
601
|
+
|
|
602
|
+
if license_file:
|
|
603
|
+
Install._get_license_acceptance(version, license_file, license_tag, yes)
|
|
604
|
+
|
|
605
|
+
print(f"Downloading NPU artifacts for Ryzen AI {version}.")
|
|
606
|
+
if skip_wheels:
|
|
607
|
+
wheels_full_path = None
|
|
608
|
+
else:
|
|
609
|
+
print("Wheels will be added to current activated environment.")
|
|
610
|
+
Install._install_artifacts(version, install_dir, token, file, wheels_full_path)
|
|
611
|
+
return file
|
|
612
|
+
|
|
613
|
+
@staticmethod
|
|
614
|
+
def _install_ryzenai_hybrid(
|
|
615
|
+
ryzen_ai_folder, version, yes, token, skip_wheels=False
|
|
616
|
+
):
|
|
617
|
+
# Check version is valid
|
|
618
|
+
if version not in hybrid_install_data:
|
|
619
|
+
raise ValueError(
|
|
620
|
+
"Invalid version for hybrid. Valid options are "
|
|
621
|
+
f"{list(hybrid_install_data.keys())}."
|
|
622
|
+
)
|
|
623
|
+
file = hybrid_install_data[version].get("artifacts_zipfile", None)
|
|
624
|
+
license_file = hybrid_install_data[version].get("license_file", None)
|
|
625
|
+
license_tag = hybrid_install_data[version].get("license_tag", None)
|
|
626
|
+
install_dir = os.path.join(ryzen_ai_folder, "hybrid")
|
|
627
|
+
if version == "1.3.0":
|
|
628
|
+
wheels_full_path = os.path.join(
|
|
629
|
+
ryzen_ai_folder,
|
|
630
|
+
"hybrid",
|
|
631
|
+
"hybrid-llm-artifacts_1.3.0_lounge",
|
|
632
|
+
"hybrid-llm-artifacts",
|
|
633
|
+
"onnxruntime_genai",
|
|
634
|
+
"wheel",
|
|
635
|
+
)
|
|
636
|
+
else:
|
|
637
|
+
wheels_full_path = os.path.join(install_dir, "wheels")
|
|
638
|
+
|
|
639
|
+
if license_file:
|
|
640
|
+
Install._get_license_acceptance(version, license_file, license_tag, yes)
|
|
641
|
+
|
|
642
|
+
print(f"Downloading hybrid artifacts for Ryzen AI {version}.")
|
|
643
|
+
if skip_wheels:
|
|
644
|
+
wheels_full_path = None
|
|
645
|
+
else:
|
|
646
|
+
print("Wheels will be added to current activated environment.")
|
|
647
|
+
Install._install_artifacts(version, install_dir, token, file, wheels_full_path)
|
|
648
|
+
return file
|
|
649
|
+
|
|
650
|
+
@staticmethod
|
|
651
|
+
def _install_ryzenai(ryzenai, build_model, yes, token):
|
|
652
|
+
# Check if the processor is supported before proceeding
|
|
653
|
+
check_ryzen_ai_processor()
|
|
654
|
+
|
|
655
|
+
# Delete any previous Ryzen AI installation in this environment
|
|
656
|
+
ryzen_ai_folder = get_ryzen_ai_path(check_exists=False)
|
|
657
|
+
if os.path.exists(ryzen_ai_folder):
|
|
658
|
+
print("Deleting previous Ryzen AI installation in this environment.")
|
|
659
|
+
shutil.rmtree(ryzen_ai_folder)
|
|
660
|
+
|
|
661
|
+
# Determine Ryzen AI version to install
|
|
662
|
+
version = DEFAULT_RYZEN_AI_VERSION
|
|
663
|
+
if "-" in ryzenai:
|
|
664
|
+
# ryzenai is: (npu|hybrid)-(<VERSION>)
|
|
665
|
+
parts = ryzenai.split("-")
|
|
666
|
+
ryzenai = parts[0]
|
|
667
|
+
version = parts[1]
|
|
668
|
+
|
|
669
|
+
# Install artifacts needed for npu, hybrid, or unified (both) inference
|
|
670
|
+
npu_file = None
|
|
671
|
+
hybrid_file = None
|
|
672
|
+
model_prep_file = None
|
|
673
|
+
if ryzenai == "npu":
|
|
674
|
+
npu_file = Install._install_ryzenai_npu(
|
|
675
|
+
ryzen_ai_folder, version, yes, token
|
|
676
|
+
)
|
|
677
|
+
elif ryzenai == "hybrid":
|
|
678
|
+
hybrid_file = Install._install_ryzenai_hybrid(
|
|
679
|
+
ryzen_ai_folder, version, yes, token
|
|
680
|
+
)
|
|
681
|
+
elif ryzenai == "unified":
|
|
682
|
+
# Download NPU artifacts and use the NPU wheels
|
|
683
|
+
npu_file = Install._install_ryzenai_npu(
|
|
684
|
+
ryzen_ai_folder, version, yes, token
|
|
685
|
+
)
|
|
686
|
+
# Download Hybrid artifacts (and skip wheels as they are already loaded)
|
|
687
|
+
hybrid_file = Install._install_ryzenai_hybrid(
|
|
688
|
+
ryzen_ai_folder, version, yes, token, skip_wheels=True
|
|
689
|
+
)
|
|
690
|
+
else:
|
|
691
|
+
raise ValueError(
|
|
692
|
+
f"Value passed to ryzenai argument is not supported: {ryzenai}"
|
|
693
|
+
)
|
|
694
|
+
if build_model:
|
|
695
|
+
model_prep_file = Install._install_ryzenai_model_artifacts(
|
|
696
|
+
ryzen_ai_folder, version
|
|
697
|
+
)
|
|
698
|
+
|
|
699
|
+
# Write version_info file
|
|
700
|
+
version_info = {
|
|
701
|
+
"version": version,
|
|
702
|
+
"hybrid_artifacts": hybrid_file,
|
|
703
|
+
"npu_artifacts": npu_file,
|
|
704
|
+
"model_prep_artifacts": model_prep_file,
|
|
705
|
+
}
|
|
706
|
+
version_info_path = os.path.join(ryzen_ai_folder, version_info_filename)
|
|
707
|
+
try:
|
|
708
|
+
with open(version_info_path, "w", encoding="utf-8") as file:
|
|
709
|
+
json.dump(version_info, file, indent=4)
|
|
710
|
+
except IOError as e:
|
|
711
|
+
print(f"An error occurred while writing {version_info_path}: {e}")
|
|
712
|
+
|
|
713
|
+
@staticmethod
|
|
714
|
+
def _install_quark(quark):
|
|
715
|
+
quark_install_dir = os.path.join(lemonade_install_dir, "install", "quark")
|
|
716
|
+
os.makedirs(quark_install_dir, exist_ok=True)
|
|
717
|
+
|
|
718
|
+
# Install Quark utilities
|
|
719
|
+
quark_url = (
|
|
720
|
+
f"https://www.xilinx.com/bin/public/openDownload?filename=quark-{quark}.zip"
|
|
721
|
+
)
|
|
722
|
+
quark_path = download_and_extract_package(
|
|
723
|
+
url=quark_url,
|
|
724
|
+
version=quark,
|
|
725
|
+
install_dir=quark_install_dir,
|
|
726
|
+
package_name="quark",
|
|
727
|
+
)
|
|
728
|
+
# Install Quark wheel
|
|
729
|
+
wheel_url = (
|
|
730
|
+
"https://www.xilinx.com/bin/public/openDownload?"
|
|
731
|
+
f"filename=quark-{quark}-py3-none-any.whl"
|
|
732
|
+
)
|
|
733
|
+
wheel_path = os.path.join(quark_install_dir, f"quark-{quark}-py3-none-any.whl")
|
|
734
|
+
print(f"\nInstalling Quark wheel from {wheel_url}")
|
|
735
|
+
download_file(wheel_url, wheel_path, "wheel file")
|
|
736
|
+
|
|
737
|
+
install_cmd = f"{sys.executable} -m pip install --no-deps {wheel_path}"
|
|
738
|
+
subprocess.run(install_cmd, check=True, shell=True)
|
|
739
|
+
os.remove(wheel_path)
|
|
740
|
+
|
|
741
|
+
print(f"\nQuark installed successfully at: {quark_path}")
|
|
742
|
+
|
|
743
|
+
def run(
|
|
744
|
+
self,
|
|
745
|
+
ryzenai: Optional[str] = None,
|
|
746
|
+
build_model: Optional[str] = None,
|
|
747
|
+
quark: Optional[str] = None,
|
|
748
|
+
yes: bool = False,
|
|
749
|
+
token: Optional[str] = None,
|
|
750
|
+
):
|
|
751
|
+
if ryzenai is None and quark is None and models is None:
|
|
752
|
+
raise ValueError(
|
|
753
|
+
"You must select something to install, "
|
|
754
|
+
"for example `--ryzenai`, `--quark`, or `--models`"
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
if ryzenai is not None:
|
|
758
|
+
self._install_ryzenai(ryzenai, build_model, yes, token)
|
|
759
|
+
|
|
760
|
+
if quark is not None:
|
|
761
|
+
self._install_quark(quark)
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
def main():
|
|
765
|
+
installer = Install()
|
|
766
|
+
args = installer.parser().parse_args()
|
|
767
|
+
installer.run(**args.__dict__)
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
if __name__ == "__main__":
|
|
771
|
+
main()
|
|
772
|
+
|
|
773
|
+
# This file was originally licensed under Apache 2.0. It has been modified.
|
|
774
|
+
# Modifications Copyright (c) 2025 AMD
|