---
title: "입력의 도착 순서가 systolic array를 채우는 방식"
author: "브레이브브라우저쓰자"
published_at: "2026-10-31T08:16:00.047Z"
updated_at: "2026-10-06T03:34:01.271Z"
tags: ["systolic wavefront"]
canonical_url: "https://ariser.io/@writer_a9817d5a8de0/%EC%9E%85%EB%A0%A5%EC%9D%98-%EB%8F%84%EC%B0%A9-%EC%88%9C%EC%84%9C%EA%B0%80-systolic-array%EB%A5%BC-%EC%B1%84%EC%9A%B0%EB%8A%94-%EB%B0%A9%EC%8B%9D"
ai_assisted: true
---

# 입력의 도착 순서가 systolic array를 채우는 방식

이 글은 AI가 활용됐습니다.

[원문](https://ariser.io/@writer_a9817d5a8de0/%EC%9E%85%EB%A0%A5%EC%9D%98-%EB%8F%84%EC%B0%A9-%EC%88%9C%EC%84%9C%EA%B0%80-systolic-array%EB%A5%BC-%EC%B1%84%EC%9A%B0%EB%8A%94-%EB%B0%A9%EC%8B%9D)

작은 systolic array에 곱셈기가 네 개 있으면 매 cycle 네 번의 MAC이 일어날 것 같다. 충분히 긴 계산의 가운데에서는 가능하지만, 첫 입력이 들어온 순간부터 그렇게 움직이지는 않는다. 데이터가 이웃 PE로 이동하는 데 시간이 필요하기 때문이다. systolic wavefront를 보면 peak 처리량과 한 행렬의 완료 시간이 왜 다른 숫자인지 이해할 수 있다.

## 같은 k의 두 입력을 만나게 한다

행렬 곱 C=A×B에서 C[i,j]는 A[i,k]와 B[k,j]의 곱을 k에 대해 더한 값이다. 한 PE에 C의 한 원소를 맡기는 가상 구조를 정하자. A는 왼쪽에서 오른쪽으로, B는 위에서 아래로 이동한다. 각 PE 사이 이동은 한 cycle이고, PE는 두 입력이 유효하게 만난 cycle에 MAC 하나를 끝낸다. 초기 누산값은 0이며 입력이나 내부 이동이 멈추는 경우는 제외한다.

좌표와 k를 0부터 세면 A[i,k]를 왼쪽 경계에 넣는 시점은 k+i, B[k,j]를 위쪽 경계에 넣는 시점은 k+j로 정한다. 그러면 A가 j번 이동하고 B가 i번 이동한 뒤 둘 다 `t=k+i+j`에 PE[i,j]에 도착한다. row와 column의 입구에서 붙이는 지연이 같은 k를 맞추는 역할을 한다.

Gemmini의 원문도 입력별 shift register가 두 행렬의 올바른 원소를 올바른 시점에 만나게 한다고 설명한다. 다만 실제 Gemmini는 tile 안의 조합 연결과 tile 사이 pipeline을 구분한다. 여기의 모든 PE 사이 한 cycle이라는 조건은 계산을 보여 주는 별도 모델이다. [Gemmini, Systolic Array and Transposer](https://github.com/ucb-bar/gemmini#systolic-array-and-transposer)

## 네 PE가 일하는 시점은 대각선으로 이동한다

2×2 array로 2×3 행렬 A와 3×2 행렬 B를 곱해 보자.

```text
A = [[1, 2, 3],   B = [[ 7,  8],
     [4, 5, 6]]        [ 9, 10],
                       [11, 12]]
```

PE[0,0]은 t=0,1,2에 1×7, 2×9, 3×11을 더해 58을 만든다. PE[0,1]은 같은 A row와 B의 두 번째 column을 t=1,2,3에 만나 64를 만든다. 아래 row의 결과는 139와 154이며 마지막 PE[1,1]은 t=4에 세 번째 곱을 끝낸다.

각 cycle의 유효 MAC 수를 세면 1, 3, 4, 3, 1이다. 전체 12MAC은 맞지만, 네 PE가 모두 일하는 cycle은 가운데 하나뿐이다. t=0부터 t=4까지 다섯 cycle에 물리적으로 가능한 20MAC 자리 중 12개를 사용하므로 이 구간의 유효 이용률은 60%다. 이는 메모리나 명령 overhead까지 넣은 제품 성능 수치가 아니다.

출력 tile이 R×C이고 reduction 길이가 K인 이 모델에서는 마지막 곱이 `K−1+R−1+C−1`에 끝난다. 첫 유효 MAC부터 마지막 유효 MAC까지는 K+R+C−2 cycle이다. 마지막 PE가 끝나기 전에 다른 PE의 결과가 준비될 수 있지만, 결과 이동과 외부 저장까지 완료됐다는 뜻은 아니다. 여기서는 그 시간을 세지 않았다.

## 지연을 빼면 빨라지는 대신 짝이 어긋날 수 있다

입구의 지연이 불필요해 보여 A와 B의 모든 row·column을 같은 시점에 넣는다고 하자. PE[0,1]에서 A[0,0]은 오른쪽으로 한 번 이동해 t=1에 도착한다. 그러나 B[0,1]은 위쪽 경계의 같은 PE에 t=0에 도착하고, t=1에는 B[1,1]이 온다. 그대로 곱하면 1×8이어야 할 첫 항을 1×10으로 바꿀 수 있다.

원소를 빨리 공급한 것과 올바른 원소를 동시에 공급한 것은 다르다. 이런 모델에서 valid만 보고 두 값을 곱하면 데이터 순서의 오류를 숨길 수 있다. 입력에 k 표식을 붙여 같은 k끼리 만나게 되는지 확인하면 수치 결과에 앞서 정렬 계약을 검사할 수 있다.

실제 구조가 backpressure를 허용한다면 데이터뿐 아니라 valid와 제어 상태도 함께 정렬해야 한다. 한쪽만 멈추고 다른 쪽은 계속 이동하는 상황은 지금의 일정 간격 가정을 깨뜨린다. 필요한 buffering과 정지 규칙은 구현에서 별도로 정의한다.

## peak를 읽을 때 파동의 앞뒤도 포함한다

TPU의 원 논문은 systolic 계산이 대각선 wavefront로 진행하고, software가 기능적으로는 그 내부 이동을 보지 않아도 성능에서는 latency를 고려한다고 설명한다. 그 논문의 weight가 미리 놓이는 구조와 이번 output 누산 모델을 같은 timing으로 취급할 필요는 없다. 공통 질문은 입력을 array 전체의 작업으로 바꾸는 데 걸리는 시간이다. [TPU 논문, 4쪽](https://arxiv.org/pdf/1704.04760)

K가 길어지면 이 모델의 앞뒤 빈 구간이 전체에서 차지하는 비중은 작아진다. 하지만 짧은 tile을 자주 시작하거나 tile 사이에 데이터를 기다리면 다시 커진다. 여러 tile의 파동을 겹칠 수 있는지도 누산 상태와 출력 경로의 조건에 달려 있다.

첫 입력 뒤 array가 천천히 채워지는 이유는 MAC이 부족해서가 아니라 필요한 두 입력이 각 위치에 도착해야 해서다. 성능을 계산할 때 첫 MAC, 마지막 MAC과 외부에서 결과를 읽을 수 있는 시점을 나누어 세면, 같은 peak 수치 아래에서 달라지는 완료 시간을 설명할 수 있다.

