Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Object FIFO間の暗黙的コピー:DistributeとJoinパターン

Object FIFOの暗黙的コピー

設計上、Object FIFOはプロデューサとコンシューマ間のデータ移動の構成と、Workerのメモリモジュール上のオブジェクトの割り当ての両方を処理します。1つのObject FIFOから消費されたデータは、2つのFIFO間で共有されるWorkerのコアコードで別のObject FIFOに明示的にコピーできます。これは、以下のコードスニペットに示されており、Workerはof_inからof_outにデータをコピーします:

of_in = ObjectFifo(line_type, name="in")
of_out = ObjectFifo(line_type, name="out")

def core_fn(of_in, of_out, copy_fn):
    elem_in = of_in.acquire(1)
    elem_out = of_out.acquire(1)
    copy_fn(elem_in, elem_out)
    of_in.release(1)
    out.release(1)

my_worker = Worker(core_fn, [of_in.cons(), of_out.prod(), copy_fn])

ただし、目標がデータを変更せずに単に1つのObject FIFOから別のObject FIFOにコピーすることである場合、上記の方法で行うと、必要以上に多くのオブジェクトを割り当てることになります。つまり、2番目のObject FIFOにコピーされるデータは、最初のObject FIFOですでに利用可能です。さらに、ShimタイルとMemタイルには、コピーを明示的に実行できるコアがありません。

明示的コピーの代わりに、Object FIFO APIはforward()関数(objectfifo.pyで定義)を介して暗黙的コピーを提供します。ここで、コンシューマ型のObjectFifoHandleが新しく構築されたObject FIFOのプロデューサに転送されます:

def forward(
    self,
    placement: PlacementTile = AnyMemTile,
    obj_type: type[np.ndarray] | None = None,
    depth: int | None = None,
    name: str | None = None,
    dims_to_stream: list[Sequence[int]] | None = None,
    dims_from_stream: list[Sequence[int]] | None = None,
    plio: bool = False,
)

forward()関数は、ユーザーが通常のObject FIFOと同じ入力を追加で指定できる新しいObject FIFOを作成します。placementタイルは暗黙的コピーが実行される場所であり、デフォルトではMemタイルに設定されています。

暗黙的コピーを使用すると、前のコードは次のように記述できます:

of_in = ObjectFifo(line_type, name="in")
of_out = of_in.cons().forward(obj_type=line_type, name="out")

ここで、of_inへのコンシューマObjectFifoHandleof_outのプロデューサとして転送されます。

この機能は、明示的に配置された抽象化レベルでも使用できます。Object FIFO APIは、object_fifo_linkを介して暗黙的コピーを提供します。これは、クラスコンストラクタ(aie.pyで定義)を使用して初期化できます:

class object_fifo_link(ObjectFifoLinkOp):
    def __init__(
        self,
        fifoIns,
        fifoOuts,
        srcOffsets=[],
        dstOffsets=[],
    )

リンクを使用すると、ユーザーはfifoIns入力を介して入力Object FIFOのセットを指定し、fifoOuts入力を介して出力Object FIFOのセットを指定できます。各Object FIFOは、そのnameまたはそのPythonオブジェクトのいずれかを使用して指定できます。両方の入力は、単一のObject FIFOまたはそれらの配列のいずれかになります。リンクが有効であるためには、fifoInsのコンシューマタイルとfifoOutsのプロデューサタイルの間に少なくとも1つの共有タイルが存在する必要があります。これは、データの暗黙的コピーがそのタイルのダイレクトメモリアクセスチャネル(DMA)を使用して行われるためです。

以下は、2つのFIFO of_inof_outの間に作成されたリンクの例です。ここで、タイルBはそれらの間の共有タイルです:

A = tile(1, 0)
B = tile(1, 1)
C = tile(1, 3)
of_in = object_fifo("in", A, B, 2, np.ndarray[(256,), np.dtype[np.int32]])
of_out = object_fifo("out", B, C, 2, np.ndarray[(256,), np.dtype[np.int32]])
object_fifo_link(of_in, of_out)

fifoInsfifoOutsで指定されているObject FIFOの数に応じて、2つの異なるデータパターンを実現できます:DistributeまたはJoinです。これらは次の2つのサブセクションで説明されています。現在、両方のパターンを一度に実行することはできません。つまり、fifoInsが配列の場合、fifoOutsは単一のObject FIFOのみであり、その逆も同様です。最高レベルの抽象化では、これらのパターンも利用可能です。

この機能を使用する完全なデザイン例は、セクション2fで利用できます:03_external_mem_to_core_L2

Distribute

ユーザーは、Object FIFO APIを使用して、プロデューサからのすべてのオブジェクトのデータの一部が複数のコンシューマに分散されるdistributeパターンを記述できます。これはsplit()関数(objectfifo.pyで定義)で実行できます:

def split(
    self,
    offsets: list[int],
    placement: PlacementTile = AnyMemTile,
    depths: list[int] | None = None,
    obj_types: list[type[np.ndarray]] = None,
    names: list[str] | None = None,
    dims_to_stream: list[list[Sequence[int]]] | None = None,
    dims_from_stream: list[list[Sequence[int]]] | None = None,
    plio: bool = False,
) -> list[ObjectFifo]

split()関数は、ユーザーが通常のObject FIFOと同じ入力を追加で指定できる複数のコンシューマObject FIFOを作成します。offsetsは、プロデューサObject FIFOの割り当てられたメモリのどの場所から各コンシューマObject FIFOにデータを送信するかを指定するために使用されます。

以下は、Object FIFOのコンシューマObjectFifoHandle2つのコンシューマに分割される例です。つまり、デザインで使用されるコアの数です。split()関数には、各コンシューマObject FIFOにデータが送信されるオフセット、そのオブジェクトのデータ型、およびそれらの名前が追加で与えられます。

of0 = ObjectFifo(mem_tile_ty, name="objfifo0")
n_cores = 2
of_offsets = [
    (np.prod(np_ndarray_type_get_shape(mem_tile_ty)) // n_cores) * i
    for i in range(n_cores)
]
of0_fifos = of0.cons().split(
    of_offsets,
    obj_types=[aie_tile_ty] * n_cores,
    names=[f"objfifo{i}" for i in range(n_cores)],
)

コンシューマ型のObjectFifoHandleのみを分割できます。出力FIFOのobj_typesは入力FIFOよりも小さいサイズである必要があり、出力FIFOのサイズの合計は入力FIFOのobj_typeのサイズと等しくなければなりません。

1つの入力Object FIFOと複数の出力Object FIFOを持つリンクを使用することで、ユーザーはプロデューサタイルからのすべてのオブジェクトのデータの一部が各出力FIFOに分散されるdistributeパターンを記述できます。出力FIFOのdatatypeは入力FIFOよりも小さいサイズである必要があり、出力FIFOのサイズの合計は入力FIFOのdatatypeのサイズと等しくなければなりません。

現在、Object FIFOの低レベル化では、fifoOutsで出力FIFOが指定されている順序を使用して、入力オブジェクトのどの部分が各出力FIFOに行くべきかを知ります。distributeを実現するために、低レベル化では、共有タイルの1つの出力ポートを使用して、以下の図のように出力FIFOごとに接続を確立します:

次のコードスニペットは、上記の図を説明しています。3つのObject FIFOがあります:of0はプロデューサタイルAとコンシューマタイルBを持ち、of1of2はBをプロデューサタイルとし、それぞれCとDをコンシューマタイルとして持ちます。リンクは、of0からのデータがof1of2に分散されることを指定します。このリンクでは、BはBのDMAを介して暗黙的データコピーが行われる共有タイルです。また、of1of2のデータ型がof0の半分であることにも注意できます。これは、of0のオブジェクトの前半がof1に、後半がof2に行くことを意味します。これはリンク内の順序に基づいています。これは、リンクのdstOffsetsオプションを指定することで明示的に設定されます。

A = tile(1, 0)
B = tile(1, 1)
C = tile(1, 3)
D = tile(2, 3)
of0 = object_fifo("objfifo0", A, B, 2, np.ndarray[(256,), np.dtype[np.int32]])
of1 = object_fifo("objfifo1", B, C, 2, np.ndarray[(128,), np.dtype[np.int32]])
of2 = object_fifo("objfifo2", B, D, 2, np.ndarray[(128,), np.dtype[np.int32]])
object_fifo_link(of0, [of1, of2], [], [0, 128])

この機能を使用する完全なデザイン例は、セクション2fで利用できます:04_distribute_L2

Join

joinパターンは、複数のObject FIFOから受信されたデータが結合され、単一の出力Object FIFOに送信されるdistributeパターンの逆です。これはjoin()関数(objectfifo.pyで定義)で実行できます:

def join(
    self,
    offsets: list[int],
    placement: PlacementTile = AnyMemTile,
    depths: list[int] | None = None,
    obj_types: list[type[np.ndarray]] = None,
    names: list[str] | None = None,
    dims_to_stream: list[list[Sequence[int] | None]] | None = None,
    dims_from_stream: list[list[Sequence[int] | None]] | None = None,
    plio: bool = False,
) -> list[ObjectFifo]

join()関数は、ユーザーが通常のObject FIFOと同じ入力を追加で指定できる複数のプロデューサObject FIFOを作成します。offsetsは、各プロデューサObject FIFOからプロデューサObject FIFOの割り当てられたメモリのどの場所にデータを書き込むかを指定するために使用されます。

以下は、2つのObject FIFOが作成され、of0のプロデューサObjectFifoHandleに結合される例です。join()関数には、各プロデューサObject FIFOによってデータが書き込まれるオフセット、そのオブジェクトのデータ型、およびそれらの名前が追加で与えられます。

of0 = ObjectFifo(mem_tile_ty, name="objfifo0")
n_cores = 2
of_offsets = [
    (np.prod(np_ndarray_type_get_shape(mem_tile_ty)) // n_cores) * i
    for i in range(n_cores)
]
of0_fifos = of0.prod().join(
    of_offsets,
    obj_types=[aie_tile_ty] * n_cores,
    names=[f"objfifo{i}" for i in range(n_cores)],
)

プロデューサ型のObjectFifoHandleのみを結合できます。入力FIFOのobj_typesは出力FIFOよりも小さいサイズである必要があり、入力FIFOのサイズの合計は出力FIFOのobj_typeのサイズと等しくなければなりません。

リンクを使用したjoinパターンは、複数の入力Object FIFOと単一の出力Object FIFOを持ちます。このパターンを使用すると、ユーザーは複数のソースからの小さな入力を単一の大きな出力データ移動に結合できます。入力FIFOのdatatypeは出力FIFOよりも小さいサイズである必要があり、入力FIFOのサイズの合計は出力FIFOのdatatypeのサイズと等しくなければなりません。

同様に、fifoIns内の順序は、どの入力オブジェクトが出力Object FIFOのより大きなオブジェクトのどの部分を構成するかを指定します。joinを実現するために、低レベル化では、共有タイルの1つの入力ポートを使用して、以下の図のように入力FIFOごとに接続を確立します:

次のコードスニペットは、上記の図を説明しています。3つのObject FIFOがあります:of0はプロデューサタイルBとコンシューマタイルAを持ち、of1of2はそれぞれCとDをプロデューサタイルとし、Bをコンシューマタイルとして持ちます。リンクは、of1of2からのデータがof0に結合されることを指定します。このリンクでは、BはBのDMAを介して暗黙的データコピーが行われる共有タイルです。また、of1of2のデータ型がof0の半分であることにも注意できます。これは、of1からのオブジェクトがof0のオブジェクトの前半になり、of2のオブジェクトが後半になることを意味します。これはリンク内の順序に基づいています。

A = tile(1, 0)
B = tile(1, 1)
C = tile(1, 3)
D = tile(2, 3)
of0 = object_fifo("objfifo0", B, A, 2, np.ndarray[(256,), np.dtype[np.int32]])
of1 = object_fifo("objfifo1", C, B, 2, np.ndarray[(128,), np.dtype[np.int32]])
of2 = object_fifo("objfifo2", D, B, 2, np.ndarray[(128,), np.dtype[np.int32]])
object_fifo_link([of1, of2], of0, [0, 128], [])

これらの機能を使用する完全なデザイン例は、セクション2fで利用できます:05_join_L2


注意: より詳細な情報については、公式ドキュメントを参照してください。