mblt-npu-python 0.0.1__tar.gz → 0.1.0__tar.gz
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.
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/PKG-INFO +7 -5
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/README.md +5 -3
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/mblt_npu/__init__.py +1 -1
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/mblt_npu/npu_backend.py +39 -9
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/mblt_npu/npu_target.py +109 -7
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/mblt_npu_python.egg-info/PKG-INFO +7 -5
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/mblt_npu_python.egg-info/requires.txt +1 -1
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/pyproject.toml +1 -1
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/tests/test_npu_backend_multi_slot.py +3 -2
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/tests/test_target_device.py +184 -1
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/LICENSE +0 -0
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/mblt_npu/core_mode.py +0 -0
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/mblt_npu/logging.py +0 -0
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/mblt_npu/onnx_backend.py +0 -0
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/mblt_npu/pytest_plugin.py +0 -0
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/mblt_npu_python.egg-info/SOURCES.txt +0 -0
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/mblt_npu_python.egg-info/dependency_links.txt +0 -0
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/mblt_npu_python.egg-info/top_level.txt +0 -0
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/setup.cfg +0 -0
- {mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/tests/test_onnx_backend.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mblt-npu-python
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.0
|
|
4
4
|
Summary: Shared NPU access for the Mobilint Python packages
|
|
5
5
|
Author: Mobilint
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -18,7 +18,7 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
18
18
|
Requires-Python: <3.13,>=3.10
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
-
Requires-Dist: mobilint-qb-runtime>=1.
|
|
21
|
+
Requires-Dist: mobilint-qb-runtime>=1.4.0
|
|
22
22
|
Requires-Dist: huggingface-hub
|
|
23
23
|
Provides-Extra: test
|
|
24
24
|
Requires-Dist: pytest; extra == "test"
|
|
@@ -84,9 +84,11 @@ finally:
|
|
|
84
84
|
|
|
85
85
|
`MobilintNPUBackend` selects the appropriate implementation from
|
|
86
86
|
`target_device` (default: `"aries-rb"`). `"aries-rb"` selects
|
|
87
|
-
`MobilintAriesBackend`; `"regulus-ra"
|
|
88
|
-
`MobilintRegulusBackend`. The former generic
|
|
89
|
-
remain accepted when loading older
|
|
87
|
+
`MobilintAriesBackend`; `"regulus-ra"`, `"regulus-rb"`, `"regulus-ra-usb"`,
|
|
88
|
+
and `"regulus-rb-usb"` select `MobilintRegulusBackend`. The former generic
|
|
89
|
+
values `"aries"` and `"regulus"` remain accepted when loading older
|
|
90
|
+
configurations. The board name is forwarded to `qbruntime.Accelerator` so the
|
|
91
|
+
runtime opens the matching device (requires `mobilint-qb-runtime>=1.4.0`).
|
|
90
92
|
`backend_class_for()` and `BACKEND_CLASSES` are available for integrations that
|
|
91
93
|
need to inspect the supported targets.
|
|
92
94
|
|
|
@@ -56,9 +56,11 @@ finally:
|
|
|
56
56
|
|
|
57
57
|
`MobilintNPUBackend` selects the appropriate implementation from
|
|
58
58
|
`target_device` (default: `"aries-rb"`). `"aries-rb"` selects
|
|
59
|
-
`MobilintAriesBackend`; `"regulus-ra"
|
|
60
|
-
`MobilintRegulusBackend`. The former generic
|
|
61
|
-
remain accepted when loading older
|
|
59
|
+
`MobilintAriesBackend`; `"regulus-ra"`, `"regulus-rb"`, `"regulus-ra-usb"`,
|
|
60
|
+
and `"regulus-rb-usb"` select `MobilintRegulusBackend`. The former generic
|
|
61
|
+
values `"aries"` and `"regulus"` remain accepted when loading older
|
|
62
|
+
configurations. The board name is forwarded to `qbruntime.Accelerator` so the
|
|
63
|
+
runtime opens the matching device (requires `mobilint-qb-runtime>=1.4.0`).
|
|
62
64
|
`backend_class_for()` and `BACKEND_CLASSES` are available for integrations that
|
|
63
65
|
need to inspect the supported targets.
|
|
64
66
|
|
|
@@ -31,7 +31,7 @@ import math
|
|
|
31
31
|
import os
|
|
32
32
|
import re
|
|
33
33
|
import sys
|
|
34
|
-
from typing import Any, Dict, List, Literal, Optional, Union
|
|
34
|
+
from typing import Any, Dict, List, Literal, Optional, Sequence, Union
|
|
35
35
|
|
|
36
36
|
from huggingface_hub import HfApi, hf_hub_download
|
|
37
37
|
from huggingface_hub.errors import EntryNotFoundError
|
|
@@ -74,7 +74,13 @@ def normalize_target_device(target_device: str) -> str:
|
|
|
74
74
|
normalized = _TARGET_DEVICE_ALIASES.get(
|
|
75
75
|
target_device.lower(), target_device.lower()
|
|
76
76
|
)
|
|
77
|
-
if normalized not in {
|
|
77
|
+
if normalized not in {
|
|
78
|
+
"aries-rb",
|
|
79
|
+
"regulus-ra",
|
|
80
|
+
"regulus-rb",
|
|
81
|
+
"regulus-ra-usb",
|
|
82
|
+
"regulus-rb-usb",
|
|
83
|
+
}:
|
|
78
84
|
raise ValueError(f"unknown target_device {target_device!r}")
|
|
79
85
|
return normalized
|
|
80
86
|
|
|
@@ -194,7 +200,13 @@ class MobilintNPUBackend:
|
|
|
194
200
|
num_of_cores_in_cluster = 4
|
|
195
201
|
|
|
196
202
|
default_target_device = DEFAULT_TARGET_DEVICE
|
|
197
|
-
supported_target_devices = (
|
|
203
|
+
supported_target_devices = (
|
|
204
|
+
"aries-rb",
|
|
205
|
+
"regulus-ra",
|
|
206
|
+
"regulus-rb",
|
|
207
|
+
"regulus-ra-usb",
|
|
208
|
+
"regulus-rb-usb",
|
|
209
|
+
)
|
|
198
210
|
|
|
199
211
|
def __new__(cls, *args: Any, **kwargs: Any):
|
|
200
212
|
"""Select a board-specific backend while retaining the legacy constructor."""
|
|
@@ -211,7 +223,7 @@ class MobilintNPUBackend:
|
|
|
211
223
|
dev_no: Optional[Union[int, List[int]]] = None,
|
|
212
224
|
core_mode: CoreMode = "single",
|
|
213
225
|
target_cores: Optional[List[Union[str, "CoreId"]]] = None,
|
|
214
|
-
target_clusters: Optional[
|
|
226
|
+
target_clusters: Optional[Sequence[Union[int, str, "Cluster"]]] = None,
|
|
215
227
|
revision: Optional[str] = None,
|
|
216
228
|
commit_hash: Optional[str] = None,
|
|
217
229
|
target_device: Optional[str] = None,
|
|
@@ -300,9 +312,14 @@ class MobilintNPUBackend:
|
|
|
300
312
|
spec_kwargs["target_cores"] = list(target_cores)
|
|
301
313
|
if target_clusters is not None:
|
|
302
314
|
spec_kwargs["target_clusters"] = list(target_clusters)
|
|
315
|
+
# Config-layer normalization reads ``target_device`` directly from
|
|
316
|
+
# ``spec_kwargs``. Setter-chain normalization runs later without
|
|
317
|
+
# target_device in scope, so seed it on the pending here.
|
|
318
|
+
spec_kwargs["target_device"] = self.target_device
|
|
303
319
|
initial_spec = NPUTargetSpec.from_kwargs(spec_kwargs)
|
|
304
320
|
self._pending: NPUTargetSpecPending = NPUTargetSpecPending(
|
|
305
|
-
baseline=initial_spec
|
|
321
|
+
baseline=initial_spec,
|
|
322
|
+
target_device=self.target_device,
|
|
306
323
|
)
|
|
307
324
|
self._finalized: Optional[NPUTargetSpec] = initial_spec
|
|
308
325
|
|
|
@@ -337,7 +354,11 @@ class MobilintNPUBackend:
|
|
|
337
354
|
self._finalized = self._pending.finalize()
|
|
338
355
|
# Close the current override epoch: the next setter chain
|
|
339
356
|
# accumulates on a fresh baseline with all intent flags cleared.
|
|
340
|
-
|
|
357
|
+
# Carry ``target_device`` forward so a later setter chain that
|
|
358
|
+
# re-expands ``dev_no`` sugar still sees the backend's board.
|
|
359
|
+
self._pending = NPUTargetSpecPending.from_baseline(
|
|
360
|
+
self._finalized, target_device=self.target_device
|
|
361
|
+
)
|
|
341
362
|
return self._finalized
|
|
342
363
|
|
|
343
364
|
# ---- Target-topology accessors ------------------------------------------
|
|
@@ -943,7 +964,9 @@ class MobilintNPUBackend:
|
|
|
943
964
|
if not unique_devs:
|
|
944
965
|
unique_devs = [self._fallback_dev()]
|
|
945
966
|
|
|
946
|
-
self.accs = {
|
|
967
|
+
self.accs = {
|
|
968
|
+
int(d): Accelerator(self.target_device, int(d)) for d in unique_devs
|
|
969
|
+
}
|
|
947
970
|
self.mxq_models = []
|
|
948
971
|
self.model_dev_no = []
|
|
949
972
|
self.n_models = 0
|
|
@@ -1350,7 +1373,7 @@ class MobilintNPUBackend:
|
|
|
1350
1373
|
return result
|
|
1351
1374
|
|
|
1352
1375
|
@target_clusters.setter
|
|
1353
|
-
def target_clusters(self, values:
|
|
1376
|
+
def target_clusters(self, values: Sequence[Union[int, str, "Cluster"]]) -> None:
|
|
1354
1377
|
"""Record a raw ``target_clusters`` override on the pending accumulator.
|
|
1355
1378
|
|
|
1356
1379
|
Normalization (legacy migration, grain fold/unfold, device-set
|
|
@@ -1489,7 +1512,12 @@ class MobilintRegulusBackend(MobilintNPUBackend):
|
|
|
1489
1512
|
"""Regulus backend: validates its one-core, single/auto-only topology."""
|
|
1490
1513
|
|
|
1491
1514
|
default_target_device = "regulus-ra"
|
|
1492
|
-
supported_target_devices = (
|
|
1515
|
+
supported_target_devices = (
|
|
1516
|
+
"regulus-ra",
|
|
1517
|
+
"regulus-rb",
|
|
1518
|
+
"regulus-ra-usb",
|
|
1519
|
+
"regulus-rb-usb",
|
|
1520
|
+
)
|
|
1493
1521
|
num_of_clusters = 1
|
|
1494
1522
|
num_of_cores_in_cluster = 1
|
|
1495
1523
|
|
|
@@ -1576,6 +1604,8 @@ BACKEND_CLASSES = {
|
|
|
1576
1604
|
"aries-rb": MobilintAriesBackend,
|
|
1577
1605
|
"regulus-ra": MobilintRegulusBackend,
|
|
1578
1606
|
"regulus-rb": MobilintRegulusBackend,
|
|
1607
|
+
"regulus-ra-usb": MobilintRegulusBackend,
|
|
1608
|
+
"regulus-rb-usb": MobilintRegulusBackend,
|
|
1579
1609
|
}
|
|
1580
1610
|
|
|
1581
1611
|
|
|
@@ -52,11 +52,32 @@ from .core_mode import CoreMode, normalize_core_mode
|
|
|
52
52
|
# Default device index for ``dev_no`` when the caller does not pin one.
|
|
53
53
|
_DEFAULT_DEV_NO: int = 0
|
|
54
54
|
|
|
55
|
+
|
|
55
56
|
# Sentinel used to distinguish "field not overridden this session" from
|
|
56
57
|
# "field explicitly set to None/empty". Kept module-private and reused by
|
|
57
58
|
# both :class:`NPUTargetSpec` (single-call ``_with`` shim) and
|
|
58
59
|
# :class:`NPUTargetSpecPending` (accumulator).
|
|
59
|
-
|
|
60
|
+
#
|
|
61
|
+
# Identity, not equality, is what every ``raw_* is not _UNSET`` check relies
|
|
62
|
+
# on. A bare ``object()`` does not survive ``copy.deepcopy`` as the same
|
|
63
|
+
# instance, and upstream ``transformers.PretrainedConfig.to_dict()`` runs
|
|
64
|
+
# ``copy.deepcopy(self.__dict__)`` on every config it serializes -- including
|
|
65
|
+
# nested sub-configs whose ``npu_backend`` still holds a pending spec with
|
|
66
|
+
# unresolved ``_UNSET`` slots. That deep-copied sentinel then fails every
|
|
67
|
+
# ``is not _UNSET`` check, so callers see a bogus "overridden" field. Make the
|
|
68
|
+
# sentinel copy/deepcopy to itself so its identity survives that path.
|
|
69
|
+
class _UnsetType:
|
|
70
|
+
def __repr__(self) -> str:
|
|
71
|
+
return "_UNSET"
|
|
72
|
+
|
|
73
|
+
def __copy__(self) -> "_UnsetType":
|
|
74
|
+
return self
|
|
75
|
+
|
|
76
|
+
def __deepcopy__(self, memo: Dict[int, Any]) -> "_UnsetType":
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
_UNSET: Any = _UnsetType()
|
|
60
81
|
|
|
61
82
|
|
|
62
83
|
cluster_map: Dict[int, "Cluster"] = {
|
|
@@ -110,6 +131,33 @@ _VALID_CLUSTER_INDICES: frozenset = frozenset(cluster_map.keys())
|
|
|
110
131
|
_VALID_CORE_INDICES: frozenset = frozenset(core_map.keys())
|
|
111
132
|
|
|
112
133
|
|
|
134
|
+
def _topology_for_target(target_device: Optional[str]) -> tuple[int, int]:
|
|
135
|
+
"""Return ``(num_clusters, num_cores_per_cluster)`` for a board identifier.
|
|
136
|
+
|
|
137
|
+
Consumed by :func:`_resolve_targets` when expanding ``dev_no`` sugar into
|
|
138
|
+
a default target-core / target-cluster list: without a board-appropriate
|
|
139
|
+
topology, a Regulus config that names only ``target_device`` would be
|
|
140
|
+
populated with Aries's 2×4 grid and then rejected by
|
|
141
|
+
:class:`MobilintRegulusBackend` for referencing cores its single-cluster
|
|
142
|
+
hardware does not have.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
target_device: The board identifier from the caller's config
|
|
146
|
+
(``"aries-rb"``, ``"regulus-rb-usb"``, legacy ``"aries"`` /
|
|
147
|
+
``"regulus"``, ...), or ``None`` when the caller has not
|
|
148
|
+
declared one. Case-insensitive.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
``(2, 4)`` for Aries (and the historical unspecified default so
|
|
152
|
+
callers with legacy configs keep the same sugar expansion); ``(1, 1)``
|
|
153
|
+
for every Regulus variant (RA / RB / RA-USB / RB-USB, the ``regulus``
|
|
154
|
+
family alias, and any future ``regulus-*`` addition).
|
|
155
|
+
"""
|
|
156
|
+
if target_device and target_device.lower().startswith("regulus"):
|
|
157
|
+
return (1, 1)
|
|
158
|
+
return (2, 4)
|
|
159
|
+
|
|
160
|
+
|
|
113
161
|
def _check_cluster_core_indices(c_val: int, k_val: int, entry: Any) -> None:
|
|
114
162
|
"""Raise ``ValueError`` when ``c_val`` / ``k_val`` fall outside the Aries2 topology."""
|
|
115
163
|
if (
|
|
@@ -453,6 +501,16 @@ class NPUTargetSpec:
|
|
|
453
501
|
_pending: Optional["NPUTargetSpecPending"] = field(
|
|
454
502
|
default=None, compare=False, hash=False, repr=False
|
|
455
503
|
)
|
|
504
|
+
# Board identifier carried alongside the canonical fields so a subsequent
|
|
505
|
+
# :meth:`_with` (or a fresh :class:`NPUTargetSpecPending` derived from
|
|
506
|
+
# this spec) still knows which topology to use when ``dev_no`` sugar has
|
|
507
|
+
# to re-expand. Excluded from equality/hash/repr because it is not part
|
|
508
|
+
# of the canonical ``(dev_no, core_mode, cores, clusters)`` identity —
|
|
509
|
+
# two Aries specs with identical grain must still compare equal even if
|
|
510
|
+
# only one of them recorded ``target_device``.
|
|
511
|
+
target_device: Optional[str] = field(
|
|
512
|
+
default=None, compare=False, hash=False, repr=False
|
|
513
|
+
)
|
|
456
514
|
|
|
457
515
|
@property
|
|
458
516
|
def _dev_no_overridden(self) -> bool:
|
|
@@ -518,6 +576,7 @@ class NPUTargetSpec:
|
|
|
518
576
|
dev_no_key = f"{prefix}dev_no"
|
|
519
577
|
cores_key = f"{prefix}target_cores"
|
|
520
578
|
clusters_key = f"{prefix}target_clusters"
|
|
579
|
+
target_device_key = f"{prefix}target_device"
|
|
521
580
|
|
|
522
581
|
core_mode = normalize_core_mode(kwargs.get(core_mode_key, "single"))
|
|
523
582
|
dev_no = _dedup_dev_no(kwargs.get(dev_no_key, _DEFAULT_DEV_NO))
|
|
@@ -525,6 +584,7 @@ class NPUTargetSpec:
|
|
|
525
584
|
dev_list = _normalize_dev_list(dev_no)
|
|
526
585
|
fallback_dev = dev_list[0]
|
|
527
586
|
dev_no_given = dev_no_key in kwargs
|
|
587
|
+
target_device = kwargs.get(target_device_key)
|
|
528
588
|
|
|
529
589
|
raw_cores = kwargs.get(cores_key)
|
|
530
590
|
raw_clusters = kwargs.get(clusters_key)
|
|
@@ -537,6 +597,7 @@ class NPUTargetSpec:
|
|
|
537
597
|
dev_no_given=dev_no_given,
|
|
538
598
|
raw_cores=list(raw_cores) if raw_cores else [],
|
|
539
599
|
raw_clusters=list(raw_clusters) if raw_clusters else [],
|
|
600
|
+
target_device=target_device,
|
|
540
601
|
)
|
|
541
602
|
|
|
542
603
|
# Mutate the caller's ``kwargs`` dict in place for downstream code
|
|
@@ -566,6 +627,7 @@ class NPUTargetSpec:
|
|
|
566
627
|
core_mode=core_mode,
|
|
567
628
|
cores=tuple(cores),
|
|
568
629
|
clusters=tuple(clusters),
|
|
630
|
+
target_device=target_device,
|
|
569
631
|
)
|
|
570
632
|
|
|
571
633
|
def _with(
|
|
@@ -606,7 +668,10 @@ class NPUTargetSpec:
|
|
|
606
668
|
:meth:`NPUTargetSpecPending.finalize`.
|
|
607
669
|
"""
|
|
608
670
|
if self._pending is None:
|
|
609
|
-
base_pending = NPUTargetSpecPending(
|
|
671
|
+
base_pending = NPUTargetSpecPending(
|
|
672
|
+
baseline=replace(self, _pending=None),
|
|
673
|
+
target_device=self.target_device,
|
|
674
|
+
)
|
|
610
675
|
else:
|
|
611
676
|
base_pending = self._pending
|
|
612
677
|
new_pending = base_pending._with(
|
|
@@ -663,6 +728,7 @@ def _resolve_targets(
|
|
|
663
728
|
dev_no_given: bool,
|
|
664
729
|
raw_cores: List[Any],
|
|
665
730
|
raw_clusters: List[Any],
|
|
731
|
+
target_device: Optional[str] = None,
|
|
666
732
|
) -> tuple[List[str], List[str]]:
|
|
667
733
|
"""Canonicalize a ``(dev_no, core_mode, cores, clusters)`` payload.
|
|
668
734
|
|
|
@@ -700,11 +766,24 @@ def _resolve_targets(
|
|
|
700
766
|
)
|
|
701
767
|
|
|
702
768
|
if not cores and not clusters:
|
|
703
|
-
# ``dev_no`` sugar expansion when both target lists are absent.
|
|
769
|
+
# ``dev_no`` sugar expansion when both target lists are absent. The
|
|
770
|
+
# topology is board-dependent: Aries exposes 2 clusters × 4 cores per
|
|
771
|
+
# device, Regulus exposes 1 cluster × 1 core. Without threading
|
|
772
|
+
# ``target_device`` here, a Regulus config that only names
|
|
773
|
+
# ``target_device`` receives Aries's 8-core grid and later fails
|
|
774
|
+
# :class:`MobilintRegulusBackend`'s topology check.
|
|
775
|
+
num_clusters, num_cores = _topology_for_target(target_device)
|
|
704
776
|
if core_mode == "single":
|
|
705
|
-
cores = [
|
|
777
|
+
cores = [
|
|
778
|
+
f"{d}:{c}:{k}"
|
|
779
|
+
for d in dev_list
|
|
780
|
+
for c in range(num_clusters)
|
|
781
|
+
for k in range(num_cores)
|
|
782
|
+
]
|
|
706
783
|
else:
|
|
707
|
-
clusters = [
|
|
784
|
+
clusters = [
|
|
785
|
+
f"{d}:{c}" for d in dev_list for c in range(num_clusters)
|
|
786
|
+
]
|
|
708
787
|
else:
|
|
709
788
|
# Grain unification per core_mode.
|
|
710
789
|
if core_mode == "single":
|
|
@@ -776,6 +855,13 @@ class NPUTargetSpecPending:
|
|
|
776
855
|
strings, or canonical ``"d:c:k"`` strings), or :data:`_UNSET`.
|
|
777
856
|
raw_clusters: The caller's raw ``target_clusters`` override, or
|
|
778
857
|
:data:`_UNSET`.
|
|
858
|
+
target_device: Board identifier carried alongside the override
|
|
859
|
+
history so :meth:`finalize` can pick the right ``dev_no`` sugar
|
|
860
|
+
topology (Aries 2×4 vs Regulus 1×1). Attached by the backend at
|
|
861
|
+
init time (and preserved across chained :meth:`_with` /
|
|
862
|
+
:meth:`from_baseline` calls) because the spec-level normalization
|
|
863
|
+
layer sees target_device only at the config-load entry point;
|
|
864
|
+
per-field setter chains would otherwise lose it.
|
|
779
865
|
"""
|
|
780
866
|
|
|
781
867
|
baseline: NPUTargetSpec
|
|
@@ -783,9 +869,15 @@ class NPUTargetSpecPending:
|
|
|
783
869
|
raw_core_mode: Any = _UNSET
|
|
784
870
|
raw_cores: Any = _UNSET
|
|
785
871
|
raw_clusters: Any = _UNSET
|
|
872
|
+
target_device: Optional[str] = None
|
|
786
873
|
|
|
787
874
|
@classmethod
|
|
788
|
-
def from_baseline(
|
|
875
|
+
def from_baseline(
|
|
876
|
+
cls,
|
|
877
|
+
spec: NPUTargetSpec,
|
|
878
|
+
*,
|
|
879
|
+
target_device: Optional[str] = None,
|
|
880
|
+
) -> "NPUTargetSpecPending":
|
|
789
881
|
"""Return a fresh pending baseline for the next override epoch.
|
|
790
882
|
|
|
791
883
|
:class:`MobilintNPUBackend` calls this immediately after materializing
|
|
@@ -801,13 +893,21 @@ class NPUTargetSpecPending:
|
|
|
801
893
|
spec: Canonical :class:`NPUTargetSpec` to seed the fresh pending's
|
|
802
894
|
baseline with. Typically the finalized result of the previous
|
|
803
895
|
override epoch.
|
|
896
|
+
target_device: Board identifier attached to the fresh pending so
|
|
897
|
+
:meth:`finalize` picks the right ``dev_no`` sugar topology.
|
|
898
|
+
Defaults to ``spec.target_device`` so callers that omit it
|
|
899
|
+
(or specs constructed outside the backend) still keep their
|
|
900
|
+
board; the backend's explicit override wins when provided.
|
|
804
901
|
|
|
805
902
|
Returns:
|
|
806
903
|
A new :class:`NPUTargetSpecPending` whose baseline is ``spec``
|
|
807
904
|
(with any prior ``_pending`` history stripped) and whose intent
|
|
808
905
|
slots are all :data:`_UNSET`.
|
|
809
906
|
"""
|
|
810
|
-
return cls(
|
|
907
|
+
return cls(
|
|
908
|
+
baseline=replace(spec, _pending=None),
|
|
909
|
+
target_device=target_device if target_device is not None else spec.target_device,
|
|
910
|
+
)
|
|
811
911
|
|
|
812
912
|
def _with(
|
|
813
913
|
self,
|
|
@@ -941,6 +1041,7 @@ class NPUTargetSpecPending:
|
|
|
941
1041
|
dev_no_given=dev_no_overridden,
|
|
942
1042
|
raw_cores=raw_cores,
|
|
943
1043
|
raw_clusters=raw_clusters,
|
|
1044
|
+
target_device=self.target_device,
|
|
944
1045
|
)
|
|
945
1046
|
|
|
946
1047
|
# Resolve the effective ``dev_no`` for the returned canonical spec.
|
|
@@ -962,6 +1063,7 @@ class NPUTargetSpecPending:
|
|
|
962
1063
|
cores=tuple(cores),
|
|
963
1064
|
clusters=tuple(clusters),
|
|
964
1065
|
_pending=self,
|
|
1066
|
+
target_device=self.target_device,
|
|
965
1067
|
)
|
|
966
1068
|
|
|
967
1069
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mblt-npu-python
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.0
|
|
4
4
|
Summary: Shared NPU access for the Mobilint Python packages
|
|
5
5
|
Author: Mobilint
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -18,7 +18,7 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
18
18
|
Requires-Python: <3.13,>=3.10
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
-
Requires-Dist: mobilint-qb-runtime>=1.
|
|
21
|
+
Requires-Dist: mobilint-qb-runtime>=1.4.0
|
|
22
22
|
Requires-Dist: huggingface-hub
|
|
23
23
|
Provides-Extra: test
|
|
24
24
|
Requires-Dist: pytest; extra == "test"
|
|
@@ -84,9 +84,11 @@ finally:
|
|
|
84
84
|
|
|
85
85
|
`MobilintNPUBackend` selects the appropriate implementation from
|
|
86
86
|
`target_device` (default: `"aries-rb"`). `"aries-rb"` selects
|
|
87
|
-
`MobilintAriesBackend`; `"regulus-ra"
|
|
88
|
-
`MobilintRegulusBackend`. The former generic
|
|
89
|
-
remain accepted when loading older
|
|
87
|
+
`MobilintAriesBackend`; `"regulus-ra"`, `"regulus-rb"`, `"regulus-ra-usb"`,
|
|
88
|
+
and `"regulus-rb-usb"` select `MobilintRegulusBackend`. The former generic
|
|
89
|
+
values `"aries"` and `"regulus"` remain accepted when loading older
|
|
90
|
+
configurations. The board name is forwarded to `qbruntime.Accelerator` so the
|
|
91
|
+
runtime opens the matching device (requires `mobilint-qb-runtime>=1.4.0`).
|
|
90
92
|
`backend_class_for()` and `BACKEND_CLASSES` are available for integrations that
|
|
91
93
|
need to inspect the supported targets.
|
|
92
94
|
|
|
@@ -28,9 +28,10 @@ from mblt_npu.npu_backend import (
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class _FakeAccelerator:
|
|
31
|
-
"""Records the device number an accelerator was opened for."""
|
|
31
|
+
"""Records the target device name and device number an accelerator was opened for."""
|
|
32
32
|
|
|
33
|
-
def __init__(self, dev_no: int) -> None:
|
|
33
|
+
def __init__(self, target_device: str, dev_no: int) -> None:
|
|
34
|
+
self.target_device = target_device
|
|
34
35
|
self.dev_no = int(dev_no)
|
|
35
36
|
|
|
36
37
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import copy
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Any, cast
|
|
7
8
|
|
|
@@ -9,6 +10,7 @@ import pytest
|
|
|
9
10
|
|
|
10
11
|
from mblt_npu import MobilintAriesBackend, MobilintNPUBackend, MobilintRegulusBackend
|
|
11
12
|
import mblt_npu.npu_backend as npu_backend
|
|
13
|
+
from mblt_npu.npu_target import _UNSET, NPUTargetSpec
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
@pytest.mark.parametrize(
|
|
@@ -17,6 +19,8 @@ import mblt_npu.npu_backend as npu_backend
|
|
|
17
19
|
("aries-rb", MobilintAriesBackend),
|
|
18
20
|
("regulus-ra", MobilintRegulusBackend),
|
|
19
21
|
("regulus-rb", MobilintRegulusBackend),
|
|
22
|
+
("regulus-ra-usb", MobilintRegulusBackend),
|
|
23
|
+
("regulus-rb-usb", MobilintRegulusBackend),
|
|
20
24
|
],
|
|
21
25
|
)
|
|
22
26
|
def test_target_device_selects_the_product_backend(
|
|
@@ -31,6 +35,40 @@ def test_target_device_selects_the_product_backend(
|
|
|
31
35
|
assert backend.to_dict()["target_device"] == target_device
|
|
32
36
|
|
|
33
37
|
|
|
38
|
+
def test_unset_sentinel_survives_deepcopy() -> None:
|
|
39
|
+
"""Keep the ``_UNSET`` sentinel's identity across ``copy.deepcopy``.
|
|
40
|
+
|
|
41
|
+
``transformers.PretrainedConfig.to_dict()`` runs ``copy.deepcopy(self.__dict__)``
|
|
42
|
+
on every config it serializes, including nested sub-configs whose
|
|
43
|
+
``npu_backend`` still holds unresolved ``_UNSET`` slots. A bare ``object()``
|
|
44
|
+
sentinel would be reconstructed as a distinct instance there, so every
|
|
45
|
+
``is not _UNSET`` override check on the copy would wrongly report the
|
|
46
|
+
field as overridden.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
assert copy.deepcopy(_UNSET) is _UNSET
|
|
50
|
+
assert copy.copy(_UNSET) is _UNSET
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_backend_survives_deepcopy_before_finalization() -> None:
|
|
54
|
+
"""Finalize identically after a deepcopy taken before the next ``_spec`` read.
|
|
55
|
+
|
|
56
|
+
Reproduces the HF ``PretrainedConfig.to_dict()`` path: overriding one field
|
|
57
|
+
(``target_clusters``) invalidates ``_finalized`` without touching the other
|
|
58
|
+
fields' still-``_UNSET`` raw slots. Deep-copying the backend in that state
|
|
59
|
+
(as upstream does for nested sub-configs, before their own ``to_dict()``
|
|
60
|
+
ever ran) must not corrupt those slots when the copy is finalized.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
backend = MobilintAriesBackend()
|
|
64
|
+
backend.target_clusters = [0, 1]
|
|
65
|
+
assert backend._finalized is None
|
|
66
|
+
|
|
67
|
+
copied = copy.deepcopy(backend)
|
|
68
|
+
|
|
69
|
+
assert copied.to_dict() == backend.to_dict()
|
|
70
|
+
|
|
71
|
+
|
|
34
72
|
def test_default_target_device_is_aries_rb() -> None:
|
|
35
73
|
"""Use Aries RB when callers do not declare a board."""
|
|
36
74
|
|
|
@@ -40,7 +78,10 @@ def test_default_target_device_is_aries_rb() -> None:
|
|
|
40
78
|
assert backend.target_device == "aries-rb"
|
|
41
79
|
|
|
42
80
|
|
|
43
|
-
@pytest.mark.parametrize(
|
|
81
|
+
@pytest.mark.parametrize(
|
|
82
|
+
"target_device",
|
|
83
|
+
["regulus-ra", "regulus-rb", "regulus-ra-usb", "regulus-rb-usb"],
|
|
84
|
+
)
|
|
44
85
|
def test_positional_target_device_selects_the_product_backend(
|
|
45
86
|
target_device: str,
|
|
46
87
|
) -> None:
|
|
@@ -376,3 +417,145 @@ def test_regulus_auto_positional_empty_cluster_targets_are_canonicalized() -> No
|
|
|
376
417
|
backend = MobilintRegulusBackend("", 0, "auto", None, [])
|
|
377
418
|
|
|
378
419
|
assert backend.to_dict()["target_clusters"] == ["0:0"]
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@pytest.mark.parametrize(
|
|
423
|
+
"target_device",
|
|
424
|
+
["regulus-ra", "regulus-rb", "regulus-ra-usb", "regulus-rb-usb"],
|
|
425
|
+
)
|
|
426
|
+
def test_regulus_dev_no_sugar_expands_to_single_core(target_device: str) -> None:
|
|
427
|
+
"""Expand Regulus ``dev_no`` sugar to its one-core topology, not Aries's grid.
|
|
428
|
+
|
|
429
|
+
Without target-device-aware sugar expansion, a Regulus config that only
|
|
430
|
+
names ``target_device`` receives Aries's 2 clusters × 4 cores default
|
|
431
|
+
and is then rejected by :class:`MobilintRegulusBackend`'s topology check.
|
|
432
|
+
Every Regulus board (PCIe and USB) must produce a single ``d:0:0`` core.
|
|
433
|
+
"""
|
|
434
|
+
|
|
435
|
+
backend = MobilintNPUBackend(target_device=target_device)
|
|
436
|
+
|
|
437
|
+
assert isinstance(backend, MobilintRegulusBackend)
|
|
438
|
+
assert backend.to_dict()["target_cores"] == ["0:0:0"]
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def test_regulus_dev_no_sugar_respects_device_list() -> None:
|
|
442
|
+
"""Expand Regulus ``dev_no=[0, 1]`` sugar to one core per named device."""
|
|
443
|
+
|
|
444
|
+
backend = MobilintNPUBackend(target_device="regulus-rb-usb", dev_no=[0, 1])
|
|
445
|
+
|
|
446
|
+
assert set(backend.to_dict()["target_cores"]) == {"0:0:0", "1:0:0"}
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
@pytest.mark.parametrize(
|
|
450
|
+
"target_device",
|
|
451
|
+
["regulus-ra", "regulus-rb", "regulus-ra-usb", "regulus-rb-usb"],
|
|
452
|
+
)
|
|
453
|
+
def test_from_kwargs_sugar_uses_regulus_topology(target_device: str) -> None:
|
|
454
|
+
"""Populate ``dev_no`` sugar with Regulus topology at the config layer.
|
|
455
|
+
|
|
456
|
+
Direct :meth:`NPUTargetSpec.from_kwargs` exercises the sugar-expansion
|
|
457
|
+
branch that Model Zoo hits before the concrete backend __init__ can
|
|
458
|
+
populate its own single-core default: when the config layer sees only
|
|
459
|
+
``target_device`` (no ``target_cores`` / ``target_clusters``), the spec
|
|
460
|
+
must emit ``["0:0:0"]`` for every Regulus board so a subsequent
|
|
461
|
+
:class:`MobilintRegulusBackend` construction accepts the pre-populated
|
|
462
|
+
grain.
|
|
463
|
+
"""
|
|
464
|
+
|
|
465
|
+
spec = NPUTargetSpec.from_kwargs(
|
|
466
|
+
{"target_device": target_device, "core_mode": "single"}
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
assert list(spec.cores) == ["0:0:0"]
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
@pytest.mark.parametrize(
|
|
473
|
+
"target_device",
|
|
474
|
+
["regulus-ra", "regulus-rb", "regulus-ra-usb", "regulus-rb-usb"],
|
|
475
|
+
)
|
|
476
|
+
def test_spec_with_override_preserves_regulus_topology(target_device: str) -> None:
|
|
477
|
+
"""Keep a Regulus spec's board identity across a derived ``_with`` override.
|
|
478
|
+
|
|
479
|
+
Regression for the case where ``NPUTargetSpec.from_kwargs`` set
|
|
480
|
+
``target_device`` only on the fresh pending and lost it on the returned
|
|
481
|
+
spec. A follow-up ``spec._with(dev_no=...)`` then created a new pending
|
|
482
|
+
with ``target_device=None``, and the sugar re-expansion produced Aries's
|
|
483
|
+
2×4 grid, which :class:`MobilintRegulusBackend` would then reject for
|
|
484
|
+
referencing cores its single-cluster hardware does not have.
|
|
485
|
+
"""
|
|
486
|
+
|
|
487
|
+
root = NPUTargetSpec.from_kwargs(
|
|
488
|
+
{"target_device": target_device, "core_mode": "single"}
|
|
489
|
+
)
|
|
490
|
+
assert root.target_device == target_device
|
|
491
|
+
|
|
492
|
+
derived = root._with(dev_no=1)
|
|
493
|
+
|
|
494
|
+
assert derived.target_device == target_device
|
|
495
|
+
assert list(derived.cores) == ["1:0:0"]
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def test_from_kwargs_sugar_uses_aries_topology_by_default() -> None:
|
|
499
|
+
"""Fall back to Aries 2×4 sugar when no ``target_device`` is declared.
|
|
500
|
+
|
|
501
|
+
Callers with legacy configs that never named a board must keep receiving
|
|
502
|
+
the historical 8-core Aries default; :func:`_topology_for_target` treats
|
|
503
|
+
``None`` and any non-Regulus string as Aries.
|
|
504
|
+
"""
|
|
505
|
+
|
|
506
|
+
spec = NPUTargetSpec.from_kwargs({"core_mode": "single"})
|
|
507
|
+
|
|
508
|
+
assert len(spec.cores) == 8
|
|
509
|
+
assert list(spec.cores) == [
|
|
510
|
+
"0:0:0",
|
|
511
|
+
"0:0:1",
|
|
512
|
+
"0:0:2",
|
|
513
|
+
"0:0:3",
|
|
514
|
+
"0:1:0",
|
|
515
|
+
"0:1:1",
|
|
516
|
+
"0:1:2",
|
|
517
|
+
"0:1:3",
|
|
518
|
+
]
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
@pytest.mark.parametrize(
|
|
522
|
+
"target_device",
|
|
523
|
+
["aries-rb", "regulus-ra", "regulus-rb", "regulus-ra-usb", "regulus-rb-usb"],
|
|
524
|
+
)
|
|
525
|
+
def test_create_passes_target_device_to_accelerator(
|
|
526
|
+
monkeypatch: pytest.MonkeyPatch, target_device: str
|
|
527
|
+
) -> None:
|
|
528
|
+
"""Forward the resolved board name to ``qbruntime.Accelerator``.
|
|
529
|
+
|
|
530
|
+
``qbruntime>=1.4`` selects the physical device from the target-device
|
|
531
|
+
string passed as the first positional argument. Regressing that call
|
|
532
|
+
would silently strand USB and Regulus workloads on the default
|
|
533
|
+
accelerator, so pin the argument shape explicitly.
|
|
534
|
+
"""
|
|
535
|
+
|
|
536
|
+
captured: list[tuple[Any, ...]] = []
|
|
537
|
+
|
|
538
|
+
class _Acc:
|
|
539
|
+
def __init__(self, *args: Any) -> None:
|
|
540
|
+
captured.append(args)
|
|
541
|
+
|
|
542
|
+
def dispose(self) -> None:
|
|
543
|
+
pass
|
|
544
|
+
|
|
545
|
+
class _StopCreate(Exception):
|
|
546
|
+
pass
|
|
547
|
+
|
|
548
|
+
def _no_model(*_args: Any, **_kwargs: Any) -> None:
|
|
549
|
+
raise _StopCreate
|
|
550
|
+
|
|
551
|
+
monkeypatch.setattr(npu_backend, "Accelerator", _Acc)
|
|
552
|
+
monkeypatch.setattr(npu_backend, "Model", _no_model)
|
|
553
|
+
monkeypatch.setattr(
|
|
554
|
+
npu_backend.MobilintNPUBackend, "check_model_path", lambda self, path: path
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
backend = MobilintNPUBackend(mxq_path="m.mxq", target_device=target_device, dev_no=0)
|
|
558
|
+
with pytest.raises(_StopCreate):
|
|
559
|
+
backend.create()
|
|
560
|
+
|
|
561
|
+
assert captured == [(target_device, 0)]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mblt_npu_python-0.0.1 → mblt_npu_python-0.1.0}/mblt_npu_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|