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.

Files changed (61) hide show
  1. lemonade/__init__.py +5 -0
  2. lemonade/api.py +125 -0
  3. lemonade/cache.py +85 -0
  4. lemonade/cli.py +135 -0
  5. lemonade/common/__init__.py +0 -0
  6. lemonade/common/analyze_model.py +26 -0
  7. lemonade/common/build.py +223 -0
  8. lemonade/common/cli_helpers.py +139 -0
  9. lemonade/common/exceptions.py +98 -0
  10. lemonade/common/filesystem.py +368 -0
  11. lemonade/common/labels.py +61 -0
  12. lemonade/common/onnx_helpers.py +176 -0
  13. lemonade/common/plugins.py +10 -0
  14. lemonade/common/printing.py +110 -0
  15. lemonade/common/status.py +490 -0
  16. lemonade/common/system_info.py +390 -0
  17. lemonade/common/tensor_helpers.py +83 -0
  18. lemonade/common/test_helpers.py +28 -0
  19. lemonade/profilers/__init__.py +1 -0
  20. lemonade/profilers/memory_tracker.py +257 -0
  21. lemonade/profilers/profiler.py +55 -0
  22. lemonade/sequence.py +363 -0
  23. lemonade/state.py +159 -0
  24. lemonade/tools/__init__.py +1 -0
  25. lemonade/tools/adapter.py +104 -0
  26. lemonade/tools/bench.py +284 -0
  27. lemonade/tools/huggingface_bench.py +267 -0
  28. lemonade/tools/huggingface_load.py +520 -0
  29. lemonade/tools/humaneval.py +258 -0
  30. lemonade/tools/llamacpp.py +261 -0
  31. lemonade/tools/llamacpp_bench.py +154 -0
  32. lemonade/tools/management_tools.py +273 -0
  33. lemonade/tools/mmlu.py +327 -0
  34. lemonade/tools/ort_genai/__init__.py +0 -0
  35. lemonade/tools/ort_genai/oga.py +1129 -0
  36. lemonade/tools/ort_genai/oga_bench.py +142 -0
  37. lemonade/tools/perplexity.py +146 -0
  38. lemonade/tools/prompt.py +228 -0
  39. lemonade/tools/quark/__init__.py +0 -0
  40. lemonade/tools/quark/quark_load.py +172 -0
  41. lemonade/tools/quark/quark_quantize.py +439 -0
  42. lemonade/tools/report/__init__.py +0 -0
  43. lemonade/tools/report/llm_report.py +203 -0
  44. lemonade/tools/report/table.py +739 -0
  45. lemonade/tools/server/__init__.py +0 -0
  46. lemonade/tools/server/serve.py +1354 -0
  47. lemonade/tools/server/tool_calls.py +146 -0
  48. lemonade/tools/tool.py +374 -0
  49. lemonade/version.py +1 -0
  50. lemonade_install/__init__.py +1 -0
  51. lemonade_install/install.py +774 -0
  52. lemonade_sdk-7.0.0.dist-info/METADATA +116 -0
  53. lemonade_sdk-7.0.0.dist-info/RECORD +61 -0
  54. lemonade_sdk-7.0.0.dist-info/WHEEL +5 -0
  55. lemonade_sdk-7.0.0.dist-info/entry_points.txt +4 -0
  56. lemonade_sdk-7.0.0.dist-info/licenses/LICENSE +201 -0
  57. lemonade_sdk-7.0.0.dist-info/licenses/NOTICE.md +21 -0
  58. lemonade_sdk-7.0.0.dist-info/top_level.txt +3 -0
  59. lemonade_server/cli.py +260 -0
  60. lemonade_server/model_manager.py +98 -0
  61. 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