Skip to content

Commit ee20ee3

Browse files
authored
Merge pull request #4 from PAICookers/dev
Release: v1.1.0
2 parents 6312fc2 + 3292da2 commit ee20ee3

18 files changed

+346
-324
lines changed

.github/workflows/pytest-ci.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Python CI with pytest
2+
3+
on:
4+
pull_request:
5+
branches: [master, dev]
6+
7+
jobs:
8+
pytest-ci:
9+
strategy:
10+
matrix:
11+
python-version: ["3.8", "3.9", "3.10", "3.11"]
12+
os: [ubuntu-latest, macos-latest, windows-latest]
13+
runs-on: ${{ matrix.os }}
14+
15+
steps:
16+
- uses: actions/checkout@v4
17+
- name: Set up Python ${{ matrix.python-version }}
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: ${{ matrix.python-version }}
21+
- name: Install poetry
22+
uses: abatilo/actions-poetry@v2
23+
with:
24+
poetry-version: "1.8.2"
25+
- name: Install test dependencies
26+
run: |
27+
poetry install --with test
28+
- name: Run pytest
29+
uses: pavelzw/pytest-action@v2
30+
with:
31+
verbose: false
32+
emoji: false
33+
job-summary: true
34+
custom-arguments: "-q"
35+
custom-pytest: "poetry run pytest"
36+
click-to-expand: true
37+
report-title: "Test Report"

.pre-commit-config.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ repos:
1313
stages: [commit]
1414

1515
- repo: https://github.com/psf/black
16-
rev: 24.4.0
16+
rev: 24.4.2
1717
hooks:
1818
- id: black
1919
stages: [commit]
@@ -25,6 +25,11 @@ repos:
2525
types_or: [markdown, yaml, json]
2626
stages: [commit]
2727

28+
- repo: https://github.com/dannysepler/rm_unneeded_f_str
29+
rev: v0.2.0
30+
hooks:
31+
- id: rm-unneeded-f-str
32+
2833
- repo: https://github.com/pre-commit/pre-commit-hooks
2934
rev: v4.6.0
3035
hooks:

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,8 @@
66

77
-`ReplicaionId` 新增魔法方法
88
-`CoordOffset` 新增 `from_offset` 方法
9+
10+
## v1.1.0
11+
12+
- 添加基础路由类型以支持多芯片
13+
- 芯片坐标将导出为字符串类型,形如 `"(1, 0)"`

README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
<a href="https://github.com/PAICookers/PAIlib/blob/master/pyproject.toml">
99
<img src="https://img.shields.io/pypi/pyversions/paicorelib">
1010
</a>
11-
<a href="https://github.com/PAICookers/PAIlib/releases/tag/v1.0.0">
12-
<img src="https://img.shields.io/github/v/release/PAICookers/PAIlib?include_prereleases&color=orange">
11+
<a href="https://github.com/PAICookers/PAIlib/releases/tag/v1.1.0">
12+
<img src="https://img.shields.io/github/v/release/PAICookers/PAIlib?color=orange">
1313
</a>
1414
<a href="https://www.codefactor.io/repository/github/PAICookers/PAIlib">
1515
<img src="https://img.shields.io/codefactor/grade/github/PAICookers/PAIlib/master?color=red">
@@ -20,6 +20,4 @@
2020

2121
</p>
2222

23-
术语对照表:[Table of Terms](docs/Table-of-Terms.md)
24-
2523
[Changelog](./CHANGELOG.md)

paicorelib/coordinate.py

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,21 @@
1515
from .hw_defs import HwParams
1616

1717
__all__ = [
18+
"ChipCoord",
1819
"Coord",
20+
"CoordAddr",
1921
"CoordOffset",
2022
"ReplicationId",
2123
"CoordLike",
2224
"RIdLike",
2325
"to_coord",
26+
"to_coords",
2427
"to_coordoffset",
2528
"to_rid",
2629
]
2730

2831
CoordTuple: TypeAlias = Tuple[int, int]
32+
CoordAddr: TypeAlias = int
2933

3034

3135
def _xy_parser(other: Union[CoordTuple, "CoordOffset"]) -> CoordTuple:
@@ -37,7 +41,7 @@ def _xy_parser(other: Union[CoordTuple, "CoordOffset"]) -> CoordTuple:
3741
if len(other) != 2:
3842
raise ValueError(f"expected a tuple of 2 elements, but got {len(other)}.")
3943

40-
return CoordOffset.from_tuple(other).to_tuple()
44+
return CoordOffset(*other).to_tuple() # check the range of coordoffset
4145
else:
4246
return other.to_tuple()
4347

@@ -70,11 +74,7 @@ class Coord(_CoordIdentifier):
7074
)
7175

7276
@classmethod
73-
def from_tuple(cls, pos: CoordTuple) -> "Coord":
74-
return cls(*pos)
75-
76-
@classmethod
77-
def from_addr(cls, addr: int) -> "Coord":
77+
def from_addr(cls, addr: CoordAddr) -> "Coord":
7878
return cls(addr >> HwParams.N_BIT_CORE_Y, addr & HwParams.CORE_Y_MAX)
7979

8080
def __add__(self, __other: "CoordOffset") -> "Coord":
@@ -219,25 +219,22 @@ def __hash__(self) -> int:
219219
return hash(self.address)
220220

221221
def __str__(self) -> str:
222-
return f"({self.x}, {self.y})"
223-
224-
def __repr__(self) -> str:
225-
return f"Coord({self.x}, {self.y})"
222+
return f"({self.x},{self.y})"
226223

227224
def to_tuple(self) -> CoordTuple:
228225
"""Convert to tuple"""
229226
return (self.x, self.y)
230227

231228
@property
232-
def address(self) -> int:
229+
def address(self) -> CoordAddr:
233230
"""Convert to address, 10 bits"""
234231
return (self.x << HwParams.N_BIT_CORE_Y) | self.y
235232

236233

237234
@final
238235
class ReplicationId(Coord):
239236
@classmethod
240-
def from_addr(cls, addr: int) -> "ReplicationId":
237+
def from_addr(cls, addr: CoordAddr) -> "ReplicationId":
241238
return cls(addr >> HwParams.N_BIT_CORE_Y, addr & HwParams.CORE_Y_MAX)
242239

243240
def __and__(self, __other: Union[Coord, "ReplicationId"]) -> "ReplicationId":
@@ -297,10 +294,6 @@ class CoordOffset:
297294
default=HwParams.CORE_Y_MIN, ge=-HwParams.CORE_Y_MAX, le=HwParams.CORE_Y_MAX
298295
)
299296

300-
@classmethod
301-
def from_tuple(cls, pos: CoordTuple) -> "CoordOffset":
302-
return cls(*pos)
303-
304297
@classmethod
305298
def from_offset(cls, offset: int) -> "CoordOffset":
306299
return cls(offset >> HwParams.N_BIT_CORE_Y, offset & HwParams.CORE_Y_MAX)
@@ -488,12 +481,13 @@ def _sum_carry(cx: int, cy: int) -> CoordTuple:
488481
return cx, cy
489482

490483

491-
CoordLike = TypeVar("CoordLike", Coord, int, List[int], CoordTuple)
492-
RIdLike = TypeVar("RIdLike", ReplicationId, int, List[int], CoordTuple)
484+
ChipCoord: TypeAlias = Coord
485+
CoordLike = TypeVar("CoordLike", Coord, CoordAddr, CoordTuple)
486+
RIdLike = TypeVar("RIdLike", ReplicationId, CoordAddr, CoordTuple)
493487

494488

495489
def to_coord(coordlike: CoordLike) -> Coord:
496-
if isinstance(coordlike, int):
490+
if isinstance(coordlike, CoordAddr):
497491
return Coord.from_addr(coordlike)
498492

499493
if isinstance(coordlike, (list, tuple)):
@@ -518,7 +512,7 @@ def to_coordoffset(offset: int) -> CoordOffset:
518512

519513

520514
def to_rid(ridlike: RIdLike) -> ReplicationId:
521-
if isinstance(ridlike, int):
515+
if isinstance(ridlike, CoordAddr):
522516
return ReplicationId.from_addr(ridlike)
523517

524518
if isinstance(ridlike, (list, tuple)):

paicorelib/framelib/frames.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ def __init__(
102102
def _payload_reorganized(reg_dict: Dict[str, Any]) -> FrameArrayType:
103103
# High 8 bits & low 7 bits of tick_wait_start
104104
tws_high8, tws_low7 = bin_split(reg_dict["tick_wait_start"], 7, 8)
105-
# High 3 bits & low 7 bits of test_chip_addrs
106-
tca_high3, tca_low7 = bin_split(reg_dict["test_chip_addr"], 7, 3)
105+
# High 3 bits & low 7 bits of test_chip_addr
106+
tca_high3, tca_low7 = bin_split(reg_dict["test_chip_addr"].address, 7, 3)
107107

108108
reg_frame1 = (
109109
(reg_dict["weight_width"] & RegF.WEIGHT_WIDTH_MASK)
@@ -571,7 +571,7 @@ def __init__(
571571
raise ShapeError(f"Size of data must be 1, {_data.size}.")
572572

573573
if _data < np.iinfo(np.int8).min or _data > np.iinfo(np.int8).max:
574-
raise ValueError(f"Data out of range np.int8.")
574+
raise ValueError("Data out of range np.int8.")
575575

576576
self.data = np.uint8(_data)
577577
self._axon = int(axon)

paicorelib/hw_defs.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ class HwParams:
2525
CORE_Y_MIN = 0
2626
CORE_Y_MAX = (1 << N_BIT_CORE_Y) - 1
2727

28+
N_CORE_MAX_INCHIP = 1024
2829
N_CORE_OFFLINE = 1008
29-
"""The #N of offline cores."""
30+
N_CORE_ONLINE = N_CORE_MAX_INCHIP - N_CORE_OFFLINE
31+
3032
CORE_X_OFFLINE_MIN = CORE_X_MIN
3133
CORE_Y_OFFLINE_MIN = CORE_Y_MIN
3234
CORE_X_OFFLINE_MAX = 0b11011

paicorelib/reg_model.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
from typing import Literal
22

3-
from pydantic import BaseModel, ConfigDict, Field, field_serializer, model_validator
3+
from pydantic import (
4+
BaseModel,
5+
ConfigDict,
6+
Field,
7+
field_serializer,
8+
field_validator,
9+
model_validator,
10+
)
411
from pydantic.type_adapter import TypeAdapter
512
from pydantic.types import NonNegativeInt
613
from typing_extensions import TypedDict # Use `typing_extensions.TypedDict`.
@@ -155,8 +162,8 @@ def _target_lcn(self, target_lcn: LCNExtensionType) -> int:
155162
return target_lcn.value
156163

157164
@field_serializer("test_chip_addr")
158-
def _test_chip_addr(self, test_chip_addr: Coord) -> int:
159-
return test_chip_addr.address
165+
def _test_chip_addr(self, test_chip_addr: Coord) -> str:
166+
return str(test_chip_addr)
160167

161168

162169
ParamsReg = CoreParams
@@ -175,7 +182,7 @@ class _ParamsRegDict(TypedDict):
175182
tick_wait_end: int
176183
snn_en: int
177184
target_LCN: int
178-
test_chip_addr: int
185+
test_chip_addr: Coord
179186

180187

181188
ParamsRegChecker = TypeAdapter(_ParamsRegDict)

paicorelib/routing_defs.py

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
"get_replication_id",
1818
]
1919

20+
N_ROUTING_LEVEL = 6 # L0~L5
21+
MAX_ROUTING_PATH_LENGTH = N_ROUTING_LEVEL - 1
22+
2023

2124
@unique
2225
class RoutingLevel(IntEnum):
@@ -35,26 +38,25 @@ class RoutingLevel(IntEnum):
3538

3639
@unique
3740
class RoutingDirection(Enum):
38-
"""Indicate the 4 children of a cluster.
39-
40-
NOTE: There is an X/Y coordinate priority method to specify the order.
41-
"""
41+
"""Indicate the direction of the four children in the cluster."""
4242

4343
X0Y0 = (0, 0)
4444
X0Y1 = (0, 1)
4545
X1Y0 = (1, 0)
4646
X1Y1 = (1, 1)
4747
ANY = (-1, -1)
48-
"""Don't care when a level direction is `ANY`."""
4948

5049
def to_index(self) -> int:
5150
"""Convert the direction to index in children list."""
5251
if self is RoutingDirection.ANY:
53-
raise TypeError(f"The direction of routing is not specified.")
52+
raise TypeError("The direction of routing is not specified.")
5453

5554
x, y = self.value
5655

57-
return (x << 1) + y
56+
if HwParams.COORD_Y_PRIORITY:
57+
return (x << 1) + y
58+
else:
59+
return (y << 1) + x
5860

5961

6062
@unique
@@ -80,14 +82,18 @@ class RoutingCost(NamedTuple):
8082
n_L2: int
8183
n_L3: int
8284
n_L4: int
85+
n_L5: int = 1
8386

8487
def get_routing_level(self) -> RoutingLevel:
85-
"""Return the routing cluster level.
88+
"""Return the routing cluster level. If the #N of Lx-level > 1, then we need a \
89+
cluster with level Lx+1. And we need the #N of routing sub-level clusters.
8690
87-
If the #N of Lx-level > 1, then we need a cluster with level Lx+1.
88-
And we need the #N of routing sub-level clusters.
91+
XXX: At present, if #N of L5 > 1, raise exception.
8992
"""
90-
for i in reversed(range(5)):
93+
if self.n_L5 > 1:
94+
raise ValueError(f"#N of L5-level node out of range, got {self.n_L5}.")
95+
96+
for i in reversed(range(N_ROUTING_LEVEL)):
9197
if self[i] > 1:
9298
return RoutingLevel(i + 1)
9399

@@ -114,24 +120,35 @@ def get_routing_level(self) -> RoutingLevel:
114120
class RoutingCoord(NamedTuple):
115121
"""Use router directions to represent the coordinate of a cluster."""
116122

117-
L4: RoutingDirection
118-
L3: RoutingDirection
119-
L2: RoutingDirection
120-
L1: RoutingDirection
121-
L0: RoutingDirection
123+
L4: RoutingDirection = RoutingDirection.ANY
124+
L3: RoutingDirection = RoutingDirection.ANY
125+
L2: RoutingDirection = RoutingDirection.ANY
126+
L1: RoutingDirection = RoutingDirection.ANY
127+
L0: RoutingDirection = RoutingDirection.ANY
128+
129+
def _coord_specify_check(self) -> None:
130+
if RoutingDirection.ANY in self:
131+
raise ValueError(
132+
f"The direction of routing is not specified completely, got {self}."
133+
)
134+
135+
def _L0_property(self) -> None:
136+
if self.level > RoutingLevel.L0:
137+
raise AttributeError(
138+
f"This property is only for L0-level cluster, but self is {self.level}."
139+
)
122140

123141
@property
124142
def level(self) -> RoutingLevel:
125143
for i in range(len(self)):
126144
if self[i] is RoutingDirection.ANY:
127-
return RoutingLevel(5 - i)
145+
return RoutingLevel(MAX_ROUTING_PATH_LENGTH - i)
128146

129147
return RoutingLevel.L0
130148

131-
@property
132-
def coordinate(self) -> Coord:
133-
if self.level > RoutingLevel.L0:
134-
raise AttributeError("This property is only for L0-level cluster.")
149+
def to_coord(self) -> Coord:
150+
self._L0_property()
151+
self._coord_specify_check()
135152

136153
x = (
137154
(self.L4.value[0] << 4)
@@ -157,15 +174,13 @@ def get_routing_consumption(n_core: int) -> RoutingCost:
157174
n_sub_node = HwParams.N_SUB_ROUTING_NODE
158175

159176
# Find the nearest #N(=2^X) to accommodate `n_core` L0-level clusters.
160-
# If n_core = 5, return 8.
161-
# If n_core = 20, return 32.
162-
n_L0 = 1 << (n_core - 1).bit_length()
163-
n_L1 = 1 if n_L0 < n_sub_node else (n_L0 // n_sub_node)
164-
n_L2 = 1 if n_L1 < n_sub_node else (n_L1 // n_sub_node)
165-
n_L3 = 1 if n_L2 < n_sub_node else (n_L2 // n_sub_node)
166-
n_L4 = 1 if n_L3 < n_sub_node else (n_L3 // n_sub_node)
167-
168-
return RoutingCost(n_L0, n_L1, n_L2, n_L3, n_L4)
177+
n_Lx = [0] * N_ROUTING_LEVEL
178+
n_Lx[0] = 1 << (n_core - 1).bit_length()
179+
180+
for i in range(N_ROUTING_LEVEL - 1):
181+
n_Lx[1 + i] = 1 if n_Lx[i] < n_sub_node else (n_Lx[i] // n_sub_node)
182+
183+
return RoutingCost(*n_Lx)
169184

170185

171186
def get_replication_id(coords: Sequence[Coord]) -> RId:

0 commit comments

Comments
 (0)