gpustack-runner 0.1.23.post5__py3-none-any.whl → 0.1.24.post1__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.
@@ -1,5 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from .__utils__ import (
4
+ merge_image,
5
+ parse_image,
6
+ replace_image_with,
7
+ split_image,
8
+ )
3
9
  from ._version import commit_id, version, version_tuple
4
10
  from .runner import (
5
11
  BackendRunners,
@@ -21,7 +27,11 @@ __all__ = [
21
27
  "list_backend_runners",
22
28
  "list_runners",
23
29
  "list_service_runners",
30
+ "merge_image",
31
+ "parse_image",
32
+ "replace_image_with",
24
33
  "set_re_docker_image",
34
+ "split_image",
25
35
  "version",
26
36
  "version_tuple",
27
37
  ]
@@ -11,6 +11,7 @@ from .cmds import (
11
11
  CompareImagesSubCommand,
12
12
  CopyImagesSubCommand,
13
13
  ListImagesSubCommand,
14
+ LoadImagesSubCommand,
14
15
  SaveImagesSubCommand,
15
16
  )
16
17
 
@@ -35,6 +36,7 @@ def main():
35
36
  SaveImagesSubCommand.register(subcommand_parser)
36
37
  CopyImagesSubCommand.register(subcommand_parser)
37
38
  CompareImagesSubCommand.register(subcommand_parser)
39
+ LoadImagesSubCommand.register(subcommand_parser)
38
40
 
39
41
  # Autocomplete
40
42
  argcomplete.autocomplete(parser)
@@ -0,0 +1,155 @@
1
+ from __future__ import annotations
2
+
3
+ _BLANK_TAG = "latest"
4
+
5
+
6
+ def split_image(image: str, fill_blank_tag: bool = False) -> tuple:
7
+ """
8
+ Split the Docker completed image string into its image_name([registry/][namespace/]repository) and image_tag.
9
+
10
+ Args:
11
+ image:
12
+ The Docker completed image string to split.
13
+ fill_blank_tag:
14
+ If True, fill the blank tag with `latest`.
15
+
16
+ Returns:
17
+ A tuple of (image_name, image_tag).
18
+
19
+ """
20
+ parts = image.rsplit("@", maxsplit=1)
21
+ if len(parts) == 2:
22
+ return tuple(parts)
23
+ parts = image.rsplit(":", maxsplit=1)
24
+ if len(parts) == 2 and "/" not in parts[1]:
25
+ if fill_blank_tag:
26
+ parts[1] = parts[1] or _BLANK_TAG
27
+ return tuple(parts)
28
+ return image, _BLANK_TAG if fill_blank_tag else None
29
+
30
+
31
+ def merge_image(image_name: str, image_tag: str | None = None) -> str:
32
+ """
33
+ Merge the Docker image and image_tag into a single string.
34
+
35
+ Args:
36
+ image_name:
37
+ The Docker image name, in form of [registry/][namespace/]repository.
38
+ image_tag:
39
+ The Docker image tag.
40
+
41
+ Returns:
42
+ The completed Docker image string.
43
+
44
+ """
45
+ if not image_tag:
46
+ return image_name
47
+ if image_tag.startswith("sha256:"):
48
+ return f"{image_name}@{image_tag}"
49
+ return f"{image_name}:{image_tag}"
50
+
51
+
52
+ def parse_image(
53
+ image: str,
54
+ fill_blank_tag: bool = False,
55
+ ) -> tuple[str | None, str | None, str, str | None] | None:
56
+ """
57
+ Parse the Docker image string into its components:
58
+ registry, namespace, repository, and tag.
59
+
60
+ Args:
61
+ image:
62
+ The Docker image string to parse.
63
+ fill_blank_tag:
64
+ If True, fill the blank tag with `latest`.
65
+
66
+ Returns:
67
+ A tuple of (registry, namespace, repository, tag).
68
+ Registry, namespace, and tag can be None if not present.
69
+ If the image string is invalid, return None.
70
+
71
+ """
72
+ image_reg, image_ns, image_repo, image_tag = (
73
+ None,
74
+ None,
75
+ None,
76
+ None,
77
+ )
78
+ image_rest = image.strip()
79
+
80
+ # Get tag.
81
+ image_rest, image_tag = split_image(image_rest, fill_blank_tag=fill_blank_tag)
82
+ if not image_rest:
83
+ return None
84
+
85
+ # Get repository.
86
+ parts = image_rest.rsplit("/", maxsplit=1)
87
+ if len(parts) == 2:
88
+ image_rest, image_repo = parts
89
+ else:
90
+ image_rest, image_repo = None, image_rest
91
+
92
+ # Get namespace.
93
+ if image_rest:
94
+ parts = image_rest.rsplit("/", maxsplit=1)
95
+ if len(parts) == 2:
96
+ image_reg, image_ns = parts
97
+ else:
98
+ image_reg, image_ns = None, image_rest
99
+
100
+ return image_reg, image_ns, image_repo, image_tag
101
+
102
+
103
+ def replace_image_with(
104
+ image: str,
105
+ registry: str | None = None,
106
+ namespace: str | None = None,
107
+ repository: str | None = None,
108
+ ) -> str:
109
+ """
110
+ Replace the registry, namespace, and repository of a Docker image string.
111
+
112
+ The given image string is parsed into its components (registry, namespace, repository, tag),
113
+ and the specified components are replaced with the provided values.
114
+
115
+ The format of a Docker image string is:
116
+ [registry/][namespace/]repository[:tag|@digest]
117
+
118
+ Args:
119
+ image:
120
+ The original Docker image string.
121
+ registry:
122
+ The new registry to use. If None, keep the original registry.
123
+ namespace:
124
+ The new namespace to use. If None, keep the original namespace.
125
+ repository:
126
+ The new repository to use. If None, keep the original repository.
127
+
128
+ Returns:
129
+ The modified Docker image string.
130
+
131
+ """
132
+ if not image or (not registry and not namespace and not repository):
133
+ return image
134
+
135
+ registry = registry.strip() if registry else None
136
+ namespace = namespace.strip() if namespace else None
137
+ repository = repository.strip() if repository else None
138
+
139
+ image_reg, image_ns, image_repo, image_tag = parse_image(image)
140
+
141
+ registry = registry or image_reg
142
+ namespace = namespace or image_ns
143
+ repository = repository or image_repo
144
+
145
+ image_name = ""
146
+ if registry:
147
+ image_name += f"{registry}/"
148
+ if namespace:
149
+ image_name += f"{namespace}/"
150
+ elif registry:
151
+ image_name += "library/"
152
+ image_name += repository
153
+
154
+ image = merge_image(image_name, image_tag)
155
+ return image
@@ -27,8 +27,8 @@ version_tuple: VERSION_TUPLE
27
27
  __commit_id__: COMMIT_ID
28
28
  commit_id: COMMIT_ID
29
29
 
30
- __version__ = version = '0.1.23.post5'
31
- __version_tuple__ = version_tuple = (0, 1, 23, 'post5')
30
+ __version__ = version = '0.1.24.post1'
31
+ __version_tuple__ = version_tuple = (0, 1, 24, 'post1')
32
32
  try:
33
33
  from ._version_appendix import git_commit
34
34
  __commit_id__ = commit_id = git_commit
@@ -1 +1 @@
1
- git_commit = "d297d69"
1
+ git_commit = "ed41ee9"
@@ -4,6 +4,7 @@ from .images import (
4
4
  CompareImagesSubCommand,
5
5
  CopyImagesSubCommand,
6
6
  ListImagesSubCommand,
7
+ LoadImagesSubCommand,
7
8
  PlatformedImage,
8
9
  SaveImagesSubCommand,
9
10
  append_images,
@@ -14,6 +15,7 @@ __all__ = [
14
15
  "CompareImagesSubCommand",
15
16
  "CopyImagesSubCommand",
16
17
  "ListImagesSubCommand",
18
+ "LoadImagesSubCommand",
17
19
  "PlatformedImage",
18
20
  "SaveImagesSubCommand",
19
21
  "append_images",
@@ -9,7 +9,7 @@ import sys
9
9
  import tempfile
10
10
  import time
11
11
  from argparse import OPTIONAL
12
- from concurrent.futures import CancelledError, ThreadPoolExecutor, as_completed
12
+ from concurrent.futures import CancelledError, Future, ThreadPoolExecutor, as_completed
13
13
  from dataclasses import dataclass
14
14
  from pathlib import Path
15
15
  from typing import TYPE_CHECKING
@@ -17,7 +17,14 @@ from typing import TYPE_CHECKING
17
17
  import requests
18
18
  from dataclasses_json import dataclass_json
19
19
 
20
- from gpustack_runner import BackendRunners, envs, list_backend_runners
20
+ from gpustack_runner import (
21
+ BackendRunners,
22
+ envs,
23
+ list_backend_runners,
24
+ merge_image,
25
+ replace_image_with,
26
+ split_image,
27
+ )
21
28
 
22
29
  from .__types__ import SubCommand
23
30
 
@@ -29,6 +36,7 @@ _AVAILABLE_BACKENDS = [
29
36
  "corex",
30
37
  "cuda",
31
38
  "dtk",
39
+ "hggc",
32
40
  "maca",
33
41
  "musa",
34
42
  "neuware",
@@ -46,10 +54,6 @@ _AVAILABLE_PLATFORMS = [
46
54
  ]
47
55
 
48
56
 
49
- # Disable overriding default namespace at images operations.
50
- os.environ["GPUSTACK_RUNNER_DEFAULT_NAMESPACE"] = "gpustack"
51
-
52
-
53
57
  class ListImagesSubCommand(SubCommand):
54
58
  """
55
59
  Command to list images.
@@ -187,7 +191,7 @@ class ListImagesSubCommand(SubCommand):
187
191
 
188
192
  class SaveImagesSubCommand(SubCommand):
189
193
  """
190
- Command to save images to local that matched Docker Archive.
194
+ Command to save images to local that matched Docker/OCI archive.
191
195
  """
192
196
 
193
197
  backend: str
@@ -206,13 +210,14 @@ class SaveImagesSubCommand(SubCommand):
206
210
  source_namespace: str
207
211
  source_username: str
208
212
  source_password: str
213
+ archive_format: str
209
214
  output: Path
210
215
 
211
216
  @staticmethod
212
217
  def register(parser: _SubParsersAction):
213
218
  save_parser = parser.add_parser(
214
219
  "save-images",
215
- help="Save images as Docker Archive to local path, "
220
+ help="Save images as OCI/Docker Archive to local path, "
216
221
  "powered by https://github.com/containers/skopeo",
217
222
  )
218
223
 
@@ -305,10 +310,7 @@ class SaveImagesSubCommand(SubCommand):
305
310
  "--source-namespace",
306
311
  "--src-namespace",
307
312
  type=str,
308
- help="Source namespace in the source registry, "
309
- "if the namespace has multiple levels, "
310
- "please specify the parent levels to --source, "
311
- "e.g --source my.registry.com/a/b --source-namespace c",
313
+ help="Namespace in the source registry",
312
314
  )
313
315
 
314
316
  save_parser.add_argument(
@@ -322,7 +324,16 @@ class SaveImagesSubCommand(SubCommand):
322
324
  "--source-password",
323
325
  "--src-passwd",
324
326
  type=str,
325
- help="Password/Token for source registry authentication (env: SOURCE_PASSWORD)",
327
+ help="Password/Token for source registry authentication "
328
+ "(env: SOURCE_PASSWORD)",
329
+ )
330
+
331
+ save_parser.add_argument(
332
+ "--archive-format",
333
+ type=str,
334
+ choices=["oci", "docker"],
335
+ default="oci",
336
+ help="Archive format to save (default: oci)",
326
337
  )
327
338
 
328
339
  save_parser.add_argument(
@@ -352,16 +363,21 @@ class SaveImagesSubCommand(SubCommand):
352
363
  self.source_namespace = args.source_namespace
353
364
  self.source_username = args.source_username or os.getenv("SOURCE_USERNAME")
354
365
  self.source_password = args.source_password or os.getenv("SOURCE_PASSWORD")
366
+ self.archive_format = args.archive_format
355
367
  self.output = Path(args.output or Path.cwd())
356
368
 
357
369
  try:
358
370
  if not self.output.exists():
359
371
  self.output.mkdir(parents=True, exist_ok=True)
360
372
  except OSError as e:
361
- msg = f"Failed to create output directory '{self.output}'"
362
- raise RuntimeError(
363
- msg,
364
- ) from e
373
+ msg = (
374
+ f"Failed to prepare output directory '{self.output}' for saving images"
375
+ )
376
+ raise RuntimeError(msg) from e
377
+
378
+ if not self.output.is_dir():
379
+ msg = f"Output path '{self.output}' is not a directory"
380
+ raise RuntimeError(msg)
365
381
 
366
382
  def run(self):
367
383
  images = list_images(
@@ -389,11 +405,23 @@ class SaveImagesSubCommand(SubCommand):
389
405
 
390
406
  print("\033[2J\033[H", end="")
391
407
 
392
- print(f"Output Directory: {self.output}")
393
- print(f"Image Platform: {self.platform} ")
394
- print(f"Total Images ({len(images)}): ")
395
- for img in images:
396
- print(" -", img.name)
408
+ saving_tasks: list[tuple[str, str, Path, Path]] = []
409
+ print(
410
+ f"Output: {self.output} | Archive: {self.archive_format} | Platform: {self.platform}",
411
+ )
412
+ print(f"Saving Images ({len(images)}):")
413
+ for task_idx, img in enumerate(images):
414
+ task_name = f"task-{task_idx:0>2d}"
415
+ src_img = f"{self.source}/{img.name}"
416
+ img_name, img_tag = split_image(src_img, fill_blank_tag=True)
417
+ dst_file = self.output / img_name / f"{img_tag}.tar"
418
+ dst_file_relative = dst_file.relative_to(self.output)
419
+ saving_tasks.append(
420
+ (task_name, src_img, dst_file, dst_file_relative),
421
+ )
422
+ print(
423
+ f" - [{task_name}]: {src_img} -> {dst_file_relative}",
424
+ )
397
425
  print()
398
426
 
399
427
  for i in range(5, 0, -1):
@@ -410,37 +438,35 @@ class SaveImagesSubCommand(SubCommand):
410
438
  max_workers=self.max_workers,
411
439
  thread_name_prefix="gpustack-saving-image",
412
440
  ) as executor:
413
- futures = {}
414
- failures = []
441
+ futures: dict[Future, tuple[str, str, Path, Path]] = {}
442
+ failures: list[tuple[str, str, Path, str]] = []
415
443
 
416
444
  def check_result(f):
417
- img_name, img_output = futures[f]
445
+ _task_name, _src_img, _dst_file, _dst_file_relative = futures[f]
418
446
  try:
419
447
  result = f.result()
420
448
  if result.returncode == 0:
421
- print(f"✅ Downloaded image '{img_name}'")
449
+ print(f"✅ Saved {_src_img} -> {_dst_file_relative}")
422
450
  return
423
- img_err = result.stderr
451
+ _save_err = result.stderr
424
452
  except subprocess.CalledProcessError as cpe:
425
- img_err = cpe.stderr if cpe.stderr else str(cpe)
453
+ _save_err = cpe.stderr if cpe.stderr else str(cpe)
426
454
  except CancelledError:
427
455
  return
428
456
  except Exception as e:
429
- img_err = str(e)
430
- print(f"❌ Error downloading image '{img_name}'")
431
- failures.append((img_name, img_err))
432
- img_output.unlink(missing_ok=True)
457
+ _save_err = str(e)
458
+ print(f"❌ Error saving {_src_img} -> {_dst_file_relative}")
459
+ _dst_file.unlink(missing_ok=True)
460
+ failures.append((_task_name, _src_img, _dst_file_relative, _save_err))
433
461
 
434
462
  override_os, override_arch = self.platform.split("/", maxsplit=1)
435
463
 
436
464
  # Submit tasks
437
- for img in images:
438
- output_path = (
439
- self.output / f"{img.name.replace('/', '-').replace(':', '_')}.tar"
440
- )
441
- if output_path.exists():
442
- print(f"Image {img.name} already exists, skipping download.")
465
+ for task_name, src_img, dst_file, dst_file_relative in saving_tasks:
466
+ if dst_file.exists():
467
+ print(f"{dst_file.name} already exists, skipping save {src_img}.")
443
468
  continue
469
+ dst_file.parent.mkdir(parents=True, exist_ok=True, mode=0o744)
444
470
 
445
471
  command = [
446
472
  "skopeo",
@@ -462,19 +488,19 @@ class SaveImagesSubCommand(SubCommand):
462
488
  )
463
489
  command.extend(
464
490
  [
465
- f"docker://{self.source}/{img.name}",
466
- f"docker-archive:{output_path}",
491
+ f"docker://{src_img}",
492
+ f"{self.archive_format}-archive:{dst_file}:{src_img}",
467
493
  ],
468
494
  )
469
495
 
470
496
  future = executor.submit(
471
497
  _execute_command,
472
- title=img.name,
473
- description=f"⏳ Downloading image '{img.name}'...",
498
+ title=task_name,
499
+ description=f"⏳ Saving {src_img} -> {dst_file_relative}...",
474
500
  command=command,
475
501
  )
476
502
  future.add_done_callback(check_result)
477
- futures[future] = (img.name, output_path)
503
+ futures[future] = (task_name, src_img, dst_file, dst_file_relative)
478
504
 
479
505
  # Wait
480
506
  try:
@@ -488,11 +514,11 @@ class SaveImagesSubCommand(SubCommand):
488
514
  # Review
489
515
  print()
490
516
  if failures:
491
- print(f"⚠️ Error downloading {len(failures)} images:")
492
- for name, err in failures:
493
- print(f" - {name}:")
494
- if err:
495
- for line in err.splitlines():
517
+ print(f"⚠️ Error saving {len(failures)} images:")
518
+ for task_name, src_img, dst_file_relative, save_err in failures:
519
+ print(f" - [{task_name}]: {src_img} -> {dst_file_relative}")
520
+ if save_err:
521
+ for line in save_err.splitlines():
496
522
  print(f" {line}")
497
523
  else:
498
524
  print(" (no error message)")
@@ -622,10 +648,7 @@ class CopyImagesSubCommand(SubCommand):
622
648
  "--source-namespace",
623
649
  "--src-namespace",
624
650
  type=str,
625
- help="Source namespace in the source registry, "
626
- "if the namespace has multiple levels, "
627
- "please specify the parent levels to --source, "
628
- "e.g --source my.registry.com/a/b --source-namespace c",
651
+ help="Namespace in the source registry",
629
652
  )
630
653
 
631
654
  copy_parser.add_argument(
@@ -639,39 +662,40 @@ class CopyImagesSubCommand(SubCommand):
639
662
  "--source-password",
640
663
  "--src-passwd",
641
664
  type=str,
642
- help="Password/Token for source registry authentication (env: SOURCE_PASSWORD)",
665
+ help="Password/Token for source registry authentication "
666
+ "(env: SOURCE_PASSWORD)",
643
667
  )
644
668
 
645
669
  copy_parser.add_argument(
646
670
  "--destination",
647
671
  "--dest",
648
672
  type=str,
649
- default="docker.io",
650
- help="Destination registry (default: docker.io)",
673
+ help="Destination registry (default: docker.io) "
674
+ "(env: GPUSTACK_RUNNER_DEFAULT_CONTAINER_REGISTRY, GPUSTACK_RUNTIME_DEPLOY_DEFAULT_CONTAINER_REGISTRY, GPUSTACK_SYSTEM_DEFAULT_CONTAINER_REGISTRY)",
651
675
  )
652
676
 
653
677
  copy_parser.add_argument(
654
678
  "--destination-namespace",
655
679
  "--dest-namespace",
656
680
  type=str,
657
- help="Source namespace in the destination registry, "
658
- "if the namespace has multiple levels, "
659
- "please specify the parent levels to --destination, "
660
- "e.g --destination my.registry.com/a/b --destination-namespace c",
681
+ help="Namespace in the destination registry "
682
+ "(env: GPUSTACK_RUNNER_DEFAULT_CONTAINER_NAMESPACE, GPUSTACK_RUNTIME_DEPLOY_DEFAULT_CONTAINER_NAMESPACE)",
661
683
  )
662
684
 
663
685
  copy_parser.add_argument(
664
686
  "--destination-username",
665
687
  "--dest-user",
666
688
  type=str,
667
- help="Username for destination registry authentication (env: DESTINATION_USERNAME)",
689
+ help="Username for destination registry authentication "
690
+ "(env: DESTINATION_USERNAME)",
668
691
  )
669
692
 
670
693
  copy_parser.add_argument(
671
694
  "--destination-password",
672
695
  "--dest-passwd",
673
696
  type=str,
674
- help="Password/Token for destination registry authentication (env: DESTINATION_PASSWORD)",
697
+ help="Password/Token for destination registry authentication "
698
+ "(env: DESTINATION_PASSWORD)",
675
699
  )
676
700
 
677
701
  copy_parser.set_defaults(func=CopyImagesSubCommand)
@@ -695,8 +719,15 @@ class CopyImagesSubCommand(SubCommand):
695
719
  self.source_namespace = args.source_namespace
696
720
  self.source_username = args.source_username or os.getenv("SOURCE_USERNAME")
697
721
  self.source_password = args.source_password or os.getenv("SOURCE_PASSWORD")
698
- self.destination = args.destination
699
- self.destination_namespace = args.destination_namespace
722
+ self.destination = (
723
+ args.destination
724
+ or envs.GPUSTACK_RUNNER_DEFAULT_CONTAINER_REGISTRY
725
+ or "docker.io"
726
+ )
727
+ self.destination_namespace = (
728
+ args.destination_namespace
729
+ or envs.GPUSTACK_RUNNER_DEFAULT_CONTAINER_NAMESPACE
730
+ )
700
731
  self.destination_username = args.destination_username or os.getenv(
701
732
  "DESTINATION_USERNAME",
702
733
  )
@@ -730,11 +761,20 @@ class CopyImagesSubCommand(SubCommand):
730
761
 
731
762
  print("\033[2J\033[H", end="")
732
763
 
733
- print(f"Destination: {self.destination}")
734
- print(f"Source: {self.source} ")
735
- print(f"Total Images ({len(images)}): ")
736
- for img in images:
737
- print(" -", img.name)
764
+ copying_tasks: list[tuple[str, str, str]] = []
765
+ print(f"Copying Images ({len(images)}):")
766
+ for task_idx, img in enumerate(images):
767
+ task_name = f"task-{task_idx:0>2d}"
768
+ src_img = f"{self.source}/{img.name}"
769
+ dst_img_name = img.name
770
+ if self.destination_namespace:
771
+ _, suffix = img.name.split("/", maxsplit=1)
772
+ dst_img_name = f"{self.destination_namespace}/{suffix}"
773
+ dst_img = f"{self.destination}/{dst_img_name}"
774
+ copying_tasks.append(
775
+ (task_name, src_img, dst_img),
776
+ )
777
+ print(f" - [{task_name}]: {src_img} -> {dst_img}")
738
778
  print()
739
779
 
740
780
  for i in range(5, 0, -1):
@@ -751,32 +791,32 @@ class CopyImagesSubCommand(SubCommand):
751
791
  max_workers=self.max_workers,
752
792
  thread_name_prefix="gpustack-copying-image",
753
793
  ) as executor:
754
- futures = {}
755
- failures = []
794
+ futures: dict[Future, tuple[str, str, str]] = {}
795
+ failures: list[tuple[str, str, str, str]] = []
756
796
 
757
797
  def check_result(f):
758
- img_name = futures[f]
798
+ _task_name, _src_img, _dst_img = futures[f]
759
799
  try:
760
800
  result = f.result()
761
801
  if result.returncode == 0:
762
- print(f"✅ Synced image '{img_name}'")
802
+ print(f"✅ Copied {_src_img} -> {_dst_img}")
763
803
  return
764
- img_err = result.stderr
804
+ _copy_err = result.stderr
765
805
  except subprocess.CalledProcessError as cpe:
766
- img_err = cpe.stderr if cpe.stderr else str(cpe)
806
+ _copy_err = cpe.stderr if cpe.stderr else str(cpe)
767
807
  except CancelledError:
768
808
  return
769
809
  except Exception as e:
770
- img_err = str(e)
771
- print(f"❌ Error syncing image '{img_name}'")
772
- failures.append((img_name, img_err))
810
+ _copy_err = str(e)
811
+ print(f"❌ Error copying {_src_img} -> {_dst_img}")
812
+ failures.append((_task_name, _src_img, _dst_img, _copy_err))
773
813
 
774
814
  override_os, override_arch = None, None
775
815
  if self.platform:
776
816
  override_os, override_arch = self.platform.split("/", maxsplit=1)
777
817
 
778
818
  # Submit tasks
779
- for img in images:
819
+ for task_name, src_img, dst_img in copying_tasks:
780
820
  command = [
781
821
  "skopeo",
782
822
  "copy",
@@ -810,29 +850,265 @@ class CopyImagesSubCommand(SubCommand):
810
850
  f"{self.destination_username}:{self.destination_password}",
811
851
  ],
812
852
  )
813
- dest_img_name = img.name
814
- if self.destination_namespace:
815
- _, suffix = img.name.split("/", maxsplit=1)
816
- dest_img_name = f"{self.destination_namespace}/{suffix}"
817
853
  command.extend(
818
854
  [
819
- f"docker://{self.source}/{img.name}",
820
- f"docker://{self.destination}/{dest_img_name}",
855
+ f"docker://{src_img}",
856
+ f"docker://{dst_img}",
821
857
  ],
822
858
  )
823
859
 
824
860
  future = executor.submit(
825
861
  _execute_command,
826
- title=img.name,
827
- description=(
828
- f"⏳ Syncing image '{img.name}' to '{dest_img_name}'..."
829
- if img.name != dest_img_name
830
- else f"⏳ Syncing image '{img.name}'..."
831
- ),
862
+ title=task_name,
863
+ description=f"⏳ Copying {src_img} -> {dst_img}...",
864
+ command=command,
865
+ )
866
+ future.add_done_callback(check_result)
867
+ futures[future] = (task_name, src_img, dst_img)
868
+
869
+ # Wait
870
+ try:
871
+ for _ in as_completed(futures):
872
+ pass
873
+ except Exception:
874
+ for future in futures:
875
+ future.cancel()
876
+ raise
877
+
878
+ # Review
879
+ print()
880
+ if failures:
881
+ print(f"⚠️ Error copying {len(failures)} images:")
882
+ for task_name, src_img, dst_img, copy_err in failures:
883
+ print(f" - [{task_name}]: {src_img} -> {dst_img}:")
884
+ if copy_err:
885
+ for line in copy_err.splitlines():
886
+ print(f" {line}")
887
+ else:
888
+ print(" (no error message)")
889
+ sys.exit(1)
890
+
891
+
892
+ class LoadImagesSubCommand(SubCommand):
893
+ """
894
+ Command to load images to local container image storage that matched Docker/OCI archive.
895
+
896
+ """
897
+
898
+ repository: str
899
+ platform: str
900
+ max_workers: int
901
+ max_retries: int
902
+ destination: str
903
+ destination_namespace: str
904
+ archive_format: str
905
+ storage: str
906
+ input: Path
907
+ archives: list[Path]
908
+
909
+ @staticmethod
910
+ def register(parser: _SubParsersAction):
911
+ load_parser = parser.add_parser(
912
+ "load-images",
913
+ help="Load images from OCI/Docker Archive to local container image storage, "
914
+ "powered by https://github.com/containers/skopeo",
915
+ )
916
+
917
+ load_parser.add_argument(
918
+ "--repository",
919
+ type=str,
920
+ help="Filter images by repository name",
921
+ )
922
+
923
+ load_parser.add_argument(
924
+ "--platform",
925
+ type=str,
926
+ help="Filter images by platform (default: current platform)",
927
+ choices=_AVAILABLE_PLATFORMS,
928
+ )
929
+
930
+ load_parser.add_argument(
931
+ "--max-workers",
932
+ type=int,
933
+ default=1,
934
+ help="Maximum number of worker threads to use for loading images concurrently (default: 1)",
935
+ )
936
+
937
+ load_parser.add_argument(
938
+ "--max-retries",
939
+ type=int,
940
+ default=1,
941
+ help="Maximum number of retries for loading an image (default: 1)",
942
+ )
943
+
944
+ load_parser.add_argument(
945
+ "--destination",
946
+ "--dest",
947
+ type=str,
948
+ help="Override destination registry "
949
+ "(env: GPUSTACK_RUNNER_DEFAULT_CONTAINER_REGISTRY, GPUSTACK_RUNTIME_DEPLOY_DEFAULT_CONTAINER_REGISTRY, GPUSTACK_SYSTEM_DEFAULT_CONTAINER_REGISTRY)",
950
+ )
951
+
952
+ load_parser.add_argument(
953
+ "--destination-namespace",
954
+ "--dest-namespace",
955
+ type=str,
956
+ help="Override namespace in the destination registry "
957
+ "(env: GPUSTACK_RUNNER_DEFAULT_CONTAINER_NAMESPACE, GPUSTACK_RUNTIME_DEPLOY_DEFAULT_CONTAINER_NAMESPACE)",
958
+ )
959
+
960
+ load_parser.add_argument(
961
+ "--archive-format",
962
+ type=str,
963
+ choices=["oci", "docker"],
964
+ default="oci",
965
+ help="Archive format to load (default: oci)",
966
+ )
967
+
968
+ load_parser.add_argument(
969
+ "--storage",
970
+ type=str,
971
+ choices=["docker", "podman"],
972
+ default="docker",
973
+ help="Container image storage to load images into (default: docker)",
974
+ )
975
+
976
+ load_parser.add_argument(
977
+ "input",
978
+ nargs=OPTIONAL,
979
+ help="Input directory to load images (default: current working directory)",
980
+ )
981
+
982
+ load_parser.set_defaults(func=LoadImagesSubCommand)
983
+
984
+ def __init__(self, args: Namespace):
985
+ _ensure_required_tools()
986
+
987
+ self.repository = args.repository
988
+ self.platform = args.platform or _get_current_platform()
989
+ self.max_workers = args.max_workers
990
+ self.max_retries = args.max_retries
991
+ self.destination = (
992
+ args.destination or envs.GPUSTACK_RUNNER_DEFAULT_CONTAINER_REGISTRY
993
+ )
994
+ self.destination_namespace = (
995
+ args.destination_namespace
996
+ or envs.GPUSTACK_RUNNER_DEFAULT_CONTAINER_NAMESPACE
997
+ )
998
+ self.archive_format = args.archive_format
999
+ self.storage = args.storage
1000
+ self.input = Path(args.input)
1001
+ self.archives = []
1002
+
1003
+ if not self.input.exists() or not self.input.is_dir():
1004
+ msg = f"Input path '{self.input}' is not a valid directory"
1005
+ raise RuntimeError(msg)
1006
+
1007
+ if self.input.exists() and self.input.is_dir():
1008
+ for archive_path in self.input.rglob("*.tar"):
1009
+ if not archive_path.is_file():
1010
+ continue
1011
+ if self.repository and archive_path.parent.name != self.repository:
1012
+ continue
1013
+ self.archives.append(archive_path)
1014
+
1015
+ def run(self):
1016
+ if not self.archives:
1017
+ print("No matching image archives found.")
1018
+ return
1019
+
1020
+ print("\033[2J\033[H", end="")
1021
+
1022
+ loading_tasks: list[tuple[str, Path, Path, str]] = []
1023
+ print(
1024
+ f"Input: {self.input} | Archive: {self.archive_format} | Storage: {self.storage}",
1025
+ )
1026
+ print(f"Loading Images ({len(self.archives)}):")
1027
+ for task_idx, src_file in enumerate(self.archives):
1028
+ task_name = f"task-{task_idx:0>2d}"
1029
+ src_file_relative = src_file.relative_to(self.input)
1030
+ dst_img_repo = str(src_file.parent.relative_to(self.input))
1031
+ dst_img_tag = src_file.name.removesuffix(src_file.suffix)
1032
+ dst_img = replace_image_with(
1033
+ merge_image(dst_img_repo, dst_img_tag),
1034
+ registry=self.destination,
1035
+ namespace=self.destination_namespace,
1036
+ )
1037
+ loading_tasks.append(
1038
+ (task_name, src_file, src_file_relative, dst_img),
1039
+ )
1040
+ print(f" - [{task_name}]: {src_file_relative} -> {dst_img}")
1041
+ print()
1042
+
1043
+ for i in range(5, 0, -1):
1044
+ if sys.stdout.isatty():
1045
+ print(f"\rStarting in {i} seconds... ", end="", flush=True)
1046
+ else:
1047
+ print(f"Starting in {i} seconds...")
1048
+ time.sleep(1)
1049
+ if sys.stdout.isatty():
1050
+ print("\rStarting now... ", end="", flush=True)
1051
+ print()
1052
+
1053
+ with ThreadPoolExecutor(
1054
+ max_workers=self.max_workers,
1055
+ thread_name_prefix="gpustack-loading-image",
1056
+ ) as executor:
1057
+ futures: dict[Future, tuple[str, Path, str]] = {}
1058
+ failures: list[tuple[str, Path, str, str]] = []
1059
+
1060
+ def check_result(f):
1061
+ _task_name, _src_file_relative, _dst_img = futures[f]
1062
+ try:
1063
+ result = f.result()
1064
+ if result.returncode == 0:
1065
+ print(
1066
+ f"✅ Loaded {_src_file_relative} -> {_dst_img}",
1067
+ )
1068
+ return
1069
+ _load_err = result.stderr
1070
+ except subprocess.CalledProcessError as cpe:
1071
+ _load_err = cpe.stderr if cpe.stderr else str(cpe)
1072
+ except CancelledError:
1073
+ return
1074
+ except Exception as e:
1075
+ _load_err = str(e)
1076
+ print(f"❌ Error loading {_src_file_relative} -> {_dst_img}")
1077
+ failures.append((_task_name, _src_file_relative, _dst_img, _load_err))
1078
+
1079
+ override_os, override_arch = self.platform.split("/", maxsplit=1)
1080
+
1081
+ # Submit tasks
1082
+ for task_name, src_file, src_file_relative, dst_img in loading_tasks:
1083
+ command = [
1084
+ "skopeo",
1085
+ "copy",
1086
+ "--dest-tls-verify=false",
1087
+ "--retry-times",
1088
+ str(self.max_retries),
1089
+ "--override-os",
1090
+ override_os,
1091
+ "--override-arch",
1092
+ override_arch,
1093
+ f"{self.archive_format}-archive:{src_file}",
1094
+ ]
1095
+ if self.storage == "docker":
1096
+ command.append(
1097
+ f"docker-daemon:{dst_img}",
1098
+ )
1099
+ else:
1100
+ command.append(
1101
+ f"containers-storage:{dst_img}",
1102
+ )
1103
+
1104
+ future = executor.submit(
1105
+ _execute_command,
1106
+ title=task_name,
1107
+ description=f"⏳ Loading {src_file_relative} -> {dst_img}...",
832
1108
  command=command,
833
1109
  )
834
1110
  future.add_done_callback(check_result)
835
- futures[future] = img.name
1111
+ futures[future] = (task_name, src_file_relative, dst_img)
836
1112
 
837
1113
  # Wait
838
1114
  try:
@@ -846,11 +1122,11 @@ class CopyImagesSubCommand(SubCommand):
846
1122
  # Review
847
1123
  print()
848
1124
  if failures:
849
- print(f"⚠️ Error syncing {len(failures)} images:")
850
- for name, err in failures:
851
- print(f" - {name}:")
852
- if err:
853
- for line in err.splitlines():
1125
+ print(f"⚠️ Error loading {len(failures)} images:")
1126
+ for task_name, src_file_relative, dst_img, load_err in failures:
1127
+ print(f" - [{task_name}]: {src_file_relative} -> {dst_img}:")
1128
+ if load_err:
1129
+ for line in load_err.splitlines():
854
1130
  print(f" {line}")
855
1131
  else:
856
1132
  print(" (no error message)")
@@ -1108,9 +1384,6 @@ def list_images(**kwargs) -> list[PlatformedImage]:
1108
1384
  name = img.name
1109
1385
  if not name:
1110
1386
  continue
1111
- if namespace := envs.GPUSTACK_RUNNER_DEFAULT_CONTAINER_NAMESPACE:
1112
- name = name.replace("gpustack/", f"{namespace}/")
1113
- img.name = name
1114
1387
  if name not in image_names_index:
1115
1388
  image_names_index[name] = len(images)
1116
1389
  images.append(img)
gpustack_runner/envs.py CHANGED
@@ -9,6 +9,11 @@ if TYPE_CHECKING:
9
9
 
10
10
  # Global
11
11
 
12
+ GPUSTACK_RUNNER_DEFAULT_CONTAINER_REGISTRY: str | None = None
13
+ """
14
+ Default container registry for copying images.
15
+ If not set, it should be "docker.io".
16
+ """
12
17
  GPUSTACK_RUNNER_DEFAULT_CONTAINER_NAMESPACE: str | None = None
13
18
  """
14
19
  Namespace for default runner container images.
@@ -19,13 +24,24 @@ if TYPE_CHECKING:
19
24
 
20
25
  variables: dict[str, Callable[[], Any]] = {
21
26
  # Global
27
+ "GPUSTACK_RUNNER_DEFAULT_CONTAINER_REGISTRY": lambda: trim_str(
28
+ getenvs(
29
+ [
30
+ "GPUSTACK_RUNNER_DEFAULT_CONTAINER_REGISTRY",
31
+ # Compatible with gpustack/gpustack_runtime.
32
+ "GPUSTACK_RUNTIME_DEPLOY_DEFAULT_CONTAINER_REGISTRY",
33
+ # Compatible with gpustack/gpustack.
34
+ "GPUSTACK_SYSTEM_DEFAULT_CONTAINER_REGISTRY",
35
+ ],
36
+ ),
37
+ ),
22
38
  "GPUSTACK_RUNNER_DEFAULT_CONTAINER_NAMESPACE": lambda: trim_str(
23
39
  getenvs(
24
- keys=[
40
+ [
25
41
  "GPUSTACK_RUNNER_DEFAULT_CONTAINER_NAMESPACE",
26
- ## Compatible with gpustack/gpustack_runtime.
42
+ # Compatible with gpustack/gpustack_runtime.
27
43
  "GPUSTACK_RUNTIME_DEPLOY_DEFAULT_CONTAINER_NAMESPACE",
28
- ## Legacy compatibility.
44
+ # Legacy compatibility.
29
45
  "GPUSTACK_RUNNER_DEFAULT_IMAGE_NAMESPACE",
30
46
  "GPUSTACK_RUNTIME_DEPLOY_DEFAULT_IMAGE_NAMESPACE",
31
47
  ],
gpustack_runner/runner.py CHANGED
@@ -10,10 +10,8 @@ from typing import Any
10
10
 
11
11
  from dataclasses_json import dataclass_json
12
12
 
13
- from . import envs
14
-
15
13
  _RE_DOCKER_IMAGE = re.compile(
16
- r"(?:(?P<prefix>[\w\\.\-]+(?:/[\w\\.\-]+)*)/)?runner:(?P<backend>(Host|cann|corex|cuda|dtk|maca|musa|rocm))(?P<backend_version>[XY\d\\.]+)(?:-(?P<backend_variant>\w+))?-(?P<service>(vllm|voxbox|mindie|sglang))(?P<service_version>[\w\\.]+)(?:-(?P<suffix>\w+))?",
14
+ r"(?:(?P<prefix>[\w\\.\-]+(?:/[\w\\.\-]+)*)/)?runner:(?P<backend>(Host|cann|corex|cuda|dtk|hggc|maca|musa|rocm))(?P<backend_version>[XY\d\\.]+)(?:-(?P<backend_variant>\w+))?-(?P<service>(vllm|voxbox|mindie|sglang))(?P<service_version>[\w\\.]+)(?:-(?P<suffix>\w+))?",
17
15
  )
18
16
  """
19
17
  Regex for Docker image parsing,
@@ -239,10 +237,6 @@ def list_runners(**kwargs) -> Runners | list[dict]:
239
237
  json_list = json.load(f)
240
238
  runners = []
241
239
  for item in json_list:
242
- if namespace := envs.GPUSTACK_RUNNER_DEFAULT_CONTAINER_NAMESPACE:
243
- docker_image = item["docker_image"]
244
- docker_image = docker_image.replace("gpustack/", f"{namespace}/")
245
- item["docker_image"] = docker_image
246
240
  runners.append(Runner.from_dict(item))
247
241
 
248
242
  todict = kwargs.pop("todict", False)
@@ -1396,6 +1396,28 @@
1396
1396
  "docker_image": "gpustack/runner:dtk25.04-vllm0.8.5",
1397
1397
  "deprecated": false
1398
1398
  },
1399
+ {
1400
+ "backend": "hggc",
1401
+ "backend_version": "12.3",
1402
+ "original_backend_version": "12.3",
1403
+ "backend_variant": "",
1404
+ "service": "sglang",
1405
+ "service_version": "0.5.5",
1406
+ "platform": "linux/amd64",
1407
+ "docker_image": "gpustack/runner:hggc12.3-sglang0.5.5",
1408
+ "deprecated": false
1409
+ },
1410
+ {
1411
+ "backend": "hggc",
1412
+ "backend_version": "12.3",
1413
+ "original_backend_version": "12.3",
1414
+ "backend_variant": "",
1415
+ "service": "vllm",
1416
+ "service_version": "0.11.1",
1417
+ "platform": "linux/amd64",
1418
+ "docker_image": "gpustack/runner:hggc12.3-vllm0.11.1",
1419
+ "deprecated": false
1420
+ },
1399
1421
  {
1400
1422
  "backend": "maca",
1401
1423
  "backend_version": "3.2",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gpustack-runner
3
- Version: 0.1.23.post5
3
+ Version: 0.1.24.post1
4
4
  Summary: GPUStack Runner is library for registering runnable accelerated backends and services in GPUStack.
5
5
  Project-URL: Homepage, https://github.com/gpustack/runner
6
6
  Project-URL: Bug Tracker, https://github.com/gpustack/gpustack/issues
@@ -46,24 +46,20 @@ The following table lists the supported accelerated backends and their correspon
46
46
 
47
47
  ### Ascend CANN
48
48
 
49
- > [!CAUTION]
50
- > Since v0.1.23:
51
- > - Deprecated MindIE `2.1.rc1`.
52
-
53
49
  > [!WARNING]
54
50
  > - The Atlas 300I series is currently experimental in vLLM, only supporting eager mode and float16 data type. And there
55
51
  are some known issues for running vLLM, you can refer to
56
52
  vllm-ascend [#3316](https://github.com/vllm-project/vllm-ascend/issues/3316)
57
53
  and [#2795](https://github.com/vllm-project/vllm-ascend/issues/2795).
58
54
 
59
- | CANN Version <br/> (Variant) | MindIE | vLLM | SGLang |
60
- |------------------------------|--------------------------|------------------------------------------------------------|------------------------|
61
- | 8.3 (A3/910C) | `2.2.rc1` | `0.13.0`, `0.12.0`, `0.11.0` | `0.5.7`, `0.5.6.post2` |
62
- | 8.3 (910B) | `2.2.rc1` | `0.13.0`, `0.12.0`, `0.11.0` | `0.5.7`, `0.5.6.post2` |
63
- | 8.3 (310P) | `2.2.rc1` | | |
64
- | 8.2 (A3/910C) | `2.1.rc2` | `0.10.2`, `0.10.1.1` | `0.5.2`, `0.5.1.post3` |
65
- | 8.2 (910B) | `2.1.rc2`, ~~`2.1.rc1`~~ | `0.10.2`, `0.10.1.1`, <br/>`0.10.0`, `0.9.2`, <br/>`0.9.1` | `0.5.2`, `0.5.1.post3` |
66
- | 8.2 (310P) | `2.1.rc2`, ~~`2.1.rc1`~~ | `0.10.0`, `0.9.2` | |
55
+ | CANN Version <br/> (Variant) | MindIE | vLLM | SGLang |
56
+ |------------------------------|-----------|------------------------------------------------------------|------------------------|
57
+ | 8.3 (A3/910C) | `2.2.rc1` | `0.13.0`, `0.12.0`, `0.11.0` | `0.5.7`, `0.5.6.post2` |
58
+ | 8.3 (910B) | `2.2.rc1` | `0.13.0`, `0.12.0`, `0.11.0` | `0.5.7`, `0.5.6.post2` |
59
+ | 8.3 (310P) | `2.2.rc1` | | |
60
+ | 8.2 (A3/910C) | `2.1.rc2` | `0.10.2`, `0.10.1.1` | `0.5.2`, `0.5.1.post3` |
61
+ | 8.2 (910B) | `2.1.rc2` | `0.10.2`, `0.10.1.1`, <br/>`0.10.0`, `0.9.2`, <br/>`0.9.1` | `0.5.2`, `0.5.1.post3` |
62
+ | 8.2 (310P) | `2.1.rc2` | `0.10.0`, `0.9.2` | |
67
63
 
68
64
  ### Iluvatar CoreX
69
65
 
@@ -73,13 +69,6 @@ The following table lists the supported accelerated backends and their correspon
73
69
 
74
70
  ### NVIDIA CUDA
75
71
 
76
- > [!CAUTION]
77
- > Since v0.1.23:
78
- > - Deprecated all services for CUDA 12.4.
79
- > - Deprecated vLLM `0.11.0`, `0.10.1.1`, `0.10.0`.
80
- > - Deprecated SGLang `0.5.5`.
81
- > - Deprecated VoxBox `0.0.20`.
82
-
83
72
  > [!NOTE]
84
73
  > - CUDA 12.9 supports Compute Capabilities:
85
74
  `7.5 8.0+PTX 8.9 9.0 10.0 10.3 12.0 12.1+PTX`.
@@ -88,12 +77,11 @@ The following table lists the supported accelerated backends and their correspon
88
77
  > - CUDA 12.6/12.4 supports Compute Capabilities:
89
78
  `7.5 8.0+PTX 8.9 9.0+PTX`.
90
79
 
91
- | CUDA Version <br/> (Variant) | vLLM | SGLang | VoxBox |
92
- |------------------------------|---------------------------------------------------------------------------------------------------|------------------------------------------------------------------------|------------------------|
93
- | 12.9 | `0.13.0`, `0.12.0`, <br/>`0.11.2` | `0.5.7`, `0.5.6.post2` | |
94
- | 12.8 | `0.13.0`, `0.12.0`, <br/>`0.11.2`, ~~`0.11.0`~~, <br/>`0.10.2`, ~~`0.10.1.1`~~, <br/>~~`0.10.0`~~ | `0.5.7`, `0.5.6.post2`, `0.5.5.post3`, <br/>~~`0.5.5`~~, `0.5.4.post3` | `0.0.21`, ~~`0.0.20`~~ |
95
- | 12.6 | `0.13.0`, `0.12.0`, <br/>`0.11.2`, ~~`0.11.0`~~, <br/>`0.10.2`, ~~`0.10.1.1`~~, <br/>~~`0.10.0`~~ | | `0.0.21`, ~~`0.0.20`~~ |
96
- | 12.4 | ~~`0.11.0`~~, ~~`0.10.2`~~, <br/>~~`0.10.1.1`~~, ~~`0.10.0`~~ | | ~~`0.0.20`~~ |
80
+ | CUDA Version <br/> (Variant) | vLLM | SGLang | VoxBox |
81
+ |------------------------------|---------------------------------------------|-----------------------------------------------------------|----------|
82
+ | 12.9 | `0.13.0`, `0.12.0`, <br/>`0.11.2` | `0.5.7`, `0.5.6.post2` | |
83
+ | 12.8 | `0.13.0`, `0.12.0`, <br/>`0.11.2`, `0.10.2` | `0.5.7`, `0.5.6.post2`, <br/>`0.5.5.post3`, `0.5.4.post3` | `0.0.21` |
84
+ | 12.6 | `0.13.0`, `0.12.0`, <br/>`0.11.2`,`0.10.2` | | `0.0.21` |
97
85
 
98
86
  ### Hygon DTK
99
87
 
@@ -101,6 +89,12 @@ The following table lists the supported accelerated backends and their correspon
101
89
  |-----------------------------|----------------------------|
102
90
  | 25.04 | `0.11.0`, `0.9.2`, `0.8.5` |
103
91
 
92
+ ### THead HGGC
93
+
94
+ | HGGC Version <br/> (Variant) | vLLM | SGLang |
95
+ |------------------------------|----------|---------|
96
+ | 12.3 | `0.11.1` | `0.5.5` |
97
+
104
98
  ### MetaX MACA
105
99
 
106
100
  | MACA Version <br/> (Variant) | vLLM |
@@ -117,29 +111,23 @@ The following table lists the supported accelerated backends and their correspon
117
111
 
118
112
  ### AMD ROCm
119
113
 
120
- > [!CAUTION]
121
- > Since v0.1.23:
122
- > - Deprecated all services for ROCm 6.3.
123
- > - Deprecated vLLM `0.11.0`.
124
-
125
114
  > [!NOTE]
126
115
  > - ROCm 7.0 supports LLVM targets:
127
116
  `gfx908 gfx90a gfx942 gfx950 gfx1030 gfx1100 gfx1101 gfx1200 gfx1201 gfx1150 gfx1151`.
128
- > - ROCm 6.4/6.3 supports LLVM targets:
117
+ > - ROCm 6.4 supports LLVM targets:
129
118
  `gfx908 gfx90a gfx942 gfx1030 gfx1100`.
130
119
 
131
120
  > [!WARNING]
132
121
  > - ROCm 7.0 vLLM `0.11.2/0.11.0` are reusing the official ROCm 6.4 PyTorch 2.9 wheel package rather than a ROCm
133
- 7.0 specific PyTorch build. Although supports ROCm 7.0 in vLLM `0.11.2/0.11.0`, `gfx1150/gfx1151` are not supported yet.
122
+ 7.0 specific PyTorch build. Although supports ROCm 7.0 in vLLM `0.11.2`, `gfx1150/gfx1151` are not supported yet.
134
123
  > - ROCm 6.4 vLLM `0.13.0` supports `gfx903 gfx90a gfx942` only.
135
124
  > - ROCm 6.4 SGLang supports `gfx942` only.
136
125
  > - ROCm 7.0 SGLang supports `gfx950` only.
137
126
 
138
- | ROCm Version <br/> (Variant) | vLLM | SGLang |
139
- |------------------------------|-------------------------------------------------|---------------------------------------|
140
- | 7.0 | `0.13.0`, `0.12.0`, <br/>`0.11.2`, ~~`0.11.0`~~ | `0.5.7`, `0.5.6.post2` |
141
- | 6.4 | `0.13.0`, `0.12.0`, <br/>`0.11.2`, `0.10.2` | `0.5.7`, `0.5.6.post2`, `0.5.5.post3` |
142
- | 6.3 | ~~`0.10.1.1`~~, ~~`0.10.0`~~ | |
127
+ | ROCm Version <br/> (Variant) | vLLM | SGLang |
128
+ |------------------------------|---------------------------------------------|--------------------------------------------|
129
+ | 7.0 | `0.13.0`, `0.12.0`, <br/>`0.11.2` | `0.5.7`, `0.5.6.post2` |
130
+ | 6.4 | `0.13.0`, `0.12.0`, <br/>`0.11.2`, `0.10.2` | `0.5.7`, `0.5.6.post2`, <br/>`0.5.5.post3` |
143
131
 
144
132
  ## Directory Structure
145
133
 
@@ -251,7 +239,7 @@ To add support for a new accelerated backend:
251
239
 
252
240
  1. Create a new directory under `pack/` named with the new backend.
253
241
  2. Add a `Dockerfile` in the new directory following the [Dockerfile Convention](#dockerfile-convention).
254
- 3. Update [pack.yml](.github/workflows/pack.yml) to include the new backend in the build matrix.
242
+ 3. Update [pack.yml](.github/workflows/pack.yml), [discard.yml](.github/workflows/discard.yml) and [prune.yml](.github/workflows/prune.yml) to include the new backend in the build matrix.
255
243
  4. Update [matrix.yml](pack/matrix.yaml) to include the new backend and its variants.
256
244
  5. Update `_RE_DOCKER_IMAGE` in [runner.py](gpustack_runner/runner.py) to recognize the new backend.
257
245
  6. [Optional] Update [tests](tests/gpustack_runner) if necessary.
@@ -0,0 +1,17 @@
1
+ gpustack_runner/__init__.py,sha256=ss8_wsk1oo13qFlP9usrXKX4ypHs1NZb2OVd6uFx5cc,715
2
+ gpustack_runner/__main__.py,sha256=uvpk9GtyJGtHaKM7DyM64N5mwehwpDD3v8ba6Yy8V3A,1364
3
+ gpustack_runner/__utils__.py,sha256=LSo0Iqxd5OjQFncVOYcqC8cncjtywf0qac31UPw7Ou4,4372
4
+ gpustack_runner/_version.py,sha256=Ix4zhdok3sdfQ1mOI_tph_pbj5GmpJ04rohojJ2w17c,792
5
+ gpustack_runner/_version.pyi,sha256=A42NoSgcqEXVy2OeNm4LXC9CbyonbooYrSUBlPm2lGY,156
6
+ gpustack_runner/envs.py,sha256=Wm0GTIiDJIT1zEjLpaPZNLbOs23NNFc2Y6zZuRLlTKQ,3470
7
+ gpustack_runner/runner.py,sha256=Rk4nyHj7Bn7ibAdxspXpXplMgWVMsG9Jb7GV8sHk2Ig,26310
8
+ gpustack_runner/runner.py.json,sha256=rQEZrBRAQngUY9z2AntWTnnwjhTzZI_yywMxhjAAcAw,45403
9
+ gpustack_runner/cmds/__init__.py,sha256=zjdv_OC674KAcitjiHrHbXnAwLtw8Ju3psW0IKFqPIg,471
10
+ gpustack_runner/cmds/__types__.py,sha256=7C4kQM0EHPD8WpJpTo6kh9rEdkrYALcLQ-GAzMMsqV8,789
11
+ gpustack_runner/cmds/images.py,sha256=dH9gSoOpUCyjarfpJ1I7MSR98hYtGVsfcochcC3JqhI,49025
12
+ gpustack_runner/_version_appendix.py,sha256=P3_fpwmk6qL5k6PhLsWQyJeGhOHfewu1ukL_vQh56KU,23
13
+ gpustack_runner-0.1.24.post1.dist-info/METADATA,sha256=owCPWsrDOrEOZTrHTceS4yeMFOwvWkKmAdxNSyFvcgU,12323
14
+ gpustack_runner-0.1.24.post1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
15
+ gpustack_runner-0.1.24.post1.dist-info/entry_points.txt,sha256=M1Dxl6cY0kIgf2I4pPsV-_kU6BAtjj93spmsXAdwW3s,66
16
+ gpustack_runner-0.1.24.post1.dist-info/licenses/LICENSE,sha256=OiPibowBvB-NHV3TP_NOj18XNBlXcshXZFMpa3uvKVE,10362
17
+ gpustack_runner-0.1.24.post1.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- gpustack_runner/__init__.py,sha256=0_0jsxo1xjLtHTOIEU0_-A1qFEANzsVw-uXGjcILDwk,530
2
- gpustack_runner/__main__.py,sha256=wtcp9lwMkaXGbQkuOY08EQhKfIHcTLSjMdnj2W3UGwk,1285
3
- gpustack_runner/_version.py,sha256=NDbGgX_SVQwK4ytZFVrGe_PzIARK2sZAIaj9J28Xql4,792
4
- gpustack_runner/_version.pyi,sha256=A42NoSgcqEXVy2OeNm4LXC9CbyonbooYrSUBlPm2lGY,156
5
- gpustack_runner/envs.py,sha256=EVQvU1bdF5DHSY83X1mHkQWiF4jlLhre1YCArAFe5Wk,2862
6
- gpustack_runner/runner.py,sha256=PlwYWNj_9kVB-Aoo0dmCHVPiAjKVT3ZoezhAiYtJiJA,26589
7
- gpustack_runner/runner.py.json,sha256=a6U7utY4wZ2yEGZ2wEGmbDzoVb3GvzbK_1GrIcM9yQA,44801
8
- gpustack_runner/cmds/__init__.py,sha256=Os8FdvqNjLYiVn_jnDo7rFEtAeVLJJI1odKHEqWF-Fw,417
9
- gpustack_runner/cmds/__types__.py,sha256=7C4kQM0EHPD8WpJpTo6kh9rEdkrYALcLQ-GAzMMsqV8,789
10
- gpustack_runner/cmds/images.py,sha256=UM-fO_7lKzfaFep8_kHxy36AkjSw-ZUk3Ty2dCQmvnQ,38909
11
- gpustack_runner/_version_appendix.py,sha256=YRx5Cq7oDt_x3DaU5ub-D03qpMmWfqMHO-Dr6CBv2fY,23
12
- gpustack_runner-0.1.23.post5.dist-info/METADATA,sha256=BFlwdr9yVQiF0uYhilCS-GL3EAPhraqf9kf_T5Kcq4g,13295
13
- gpustack_runner-0.1.23.post5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
14
- gpustack_runner-0.1.23.post5.dist-info/entry_points.txt,sha256=M1Dxl6cY0kIgf2I4pPsV-_kU6BAtjj93spmsXAdwW3s,66
15
- gpustack_runner-0.1.23.post5.dist-info/licenses/LICENSE,sha256=OiPibowBvB-NHV3TP_NOj18XNBlXcshXZFMpa3uvKVE,10362
16
- gpustack_runner-0.1.23.post5.dist-info/RECORD,,