---
title: "마지막 tile이 큰 array의 연산 자리를 비우는 이유"
author: "브레이브브라우저쓰자"
published_at: "2026-10-31T10:37:02.303Z"
updated_at: "2026-10-06T03:34:00.680Z"
tags: ["array tail utilization"]
canonical_url: "https://ariser.io/@writer_a9817d5a8de0/%EB%A7%88%EC%A7%80%EB%A7%89-tile%EC%9D%B4-%ED%81%B0-array%EC%9D%98-%EC%97%B0%EC%82%B0-%EC%9E%90%EB%A6%AC%EB%A5%BC-%EB%B9%84%EC%9A%B0%EB%8A%94-%EC%9D%B4%EC%9C%A0"
ai_assisted: true
---

# 마지막 tile이 큰 array의 연산 자리를 비우는 이유

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

[원문](https://ariser.io/@writer_a9817d5a8de0/%EB%A7%88%EC%A7%80%EB%A7%89-tile%EC%9D%B4-%ED%81%B0-array%EC%9D%98-%EC%97%B0%EC%82%B0-%EC%9E%90%EB%A6%AC%EB%A5%BC-%EB%B9%84%EC%9A%B0%EB%8A%94-%EC%9D%B4%EC%9C%A0)

5×5 출력 행렬을 4×4 array에서 계산한다고 하자. 첫 tile은 넉넉하게 채울 수 있다. 하지만 오른쪽과 아래쪽의 마지막 tile은 한 column이나 한 row만 남는다. array tail utilization은 이 경계에서 실제로 필요한 연산이 물리적 연산 자리를 얼마나 채우는지 묻는다. array가 커지는 만큼 한 번의 계산도 같은 비율로 빨라질지를 판단하는 데 필요한 질문이다.

## 마지막 tile에도 자리는 같은 수만큼 있다

C=A×B의 출력 크기를 M×N, reduction 길이를 K로 두자. 가상 R×C array의 각 PE가 출력 원소 하나를 맡고 K개의 곱을 순서대로 누산한다고 정한다. M과 N을 R, C 크기로 잘라 tile을 만들고, 마지막 tile의 부족한 위치는 valid를 끄거나 0으로 채운다. 부족한 위치에 다른 작업을 함께 넣는 기능은 없다고 가정한다.

출력 방향의 tile 수는 `ceil(M/R)×ceil(N/C)`다. 각 tile이 가진 물리적 출력 자리는 R×C개지만, 유효 출력의 전체 개수는 MN개다. 따라서 공간의 빈자리만 보는 비율은 다음과 같다.

```text
spatial fraction = MN / (ceil(M/R) × ceil(N/C) × R × C)
```

이 식은 유효 결과를 만드는 자리가 몇 개인지 센다. array를 채우고 비우는 시간, 메모리 공급, 결과 저장을 합친 시간 이용률 식은 아니다. 두 비율을 구분해야 원인을 찾을 수 있다.

MAESTRO는 데이터 차원의 초기·정상·경계 경우를 나누어 runtime과 데이터 이동을 계산한다. 실제 분석에서는 각 경우의 작업량과 발생 횟수가 달라지기 때문이다. 하나의 평균 tile만 계산하면 마지막 tile의 불균형을 지우기 쉽다. [MAESTRO 논문, 9–10쪽 4.2–4.4절](https://arxiv.org/pdf/1805.02566)

## 5×5를 4×4로 나누면 25개의 결과에 64개 자리를 쓴다

M=N=5, R=C=4로 정하면 출력 tile은 네 개다. 유효 크기는 각각 4×4, 4×1, 1×4, 1×1이고 필요한 결과 수는 16+4+4+1=25개다. 물리적 출력 자리는 4×16=64개이므로 공간 비율은 25/64, 약 39.1%다.

K=16으로 두면 유효 MAC은 25×16=400개다. 각 tile에 K cycle의 누산 자리만 세는 단순 기준에서는 64×16=1024MAC 자리가 있고 같은 39.1%가 나온다. 마지막 1×1 tile은 16PE 가운데 한 PE의 결과만 필요하다. 나머지를 0으로 계산해도 그 계산은 유효 출력의 MAC 수에는 넣지 않는다.

그렇다고 이용하지 않는 PE가 전력을 전혀 쓰지 않는다고 결론 내릴 수는 없다. clock gating이 되는지, 입력이 계속 toggle하는지, 제어와 이동 회로가 작동하는지는 별도의 구현 문제다. 유효 연산 수와 실제 switching activity를 동일하게 놓지 않는다.

같은 5×5 출력을 2×2 array에 배치하면 tile은 아홉 개다. 물리적 자리는 9×4=36개로, 공간 비율은 약 69.4%다. 빈자리 비율은 개선되지만 한 번에 처리하는 결과 수가 줄고 tile의 시작 횟수는 늘어난다. 따라서 69.4%라는 숫자만으로 2×2가 4×4보다 빠르다고 말할 수 없다.

## 0으로 채워도 되는 연산인지부터 확인한다

일반적인 행렬 곱에서 부족한 입력을 0으로 넣으면 그 항의 곱이 0이어서 유효 결과를 바꾸지 않을 수 있다. 그러나 출력 범위를 벗어난 위치를 외부 memory에 써도 된다는 뜻은 아니다. 연산의 neutral 값과 주소의 유효성은 다른 조건이다.

입력뿐 아니라 bias나 후처리를 적용하는 위치도 확인한다. 예를 들어 padding 출력에도 bias를 더한 뒤 실수로 저장하면 값이 0이 아닐 수 있다. 정상 결과 영역을 정한 mask와 실제 store 범위가 일치해야 한다. mask가 MAC을 막는 역할과 memory write를 막는 역할을 모두 수행한다고 자동으로 가정하지 않는다.

Gemmini는 큰 행렬을 작은 DIM 크기의 연산으로 tile하고, software가 hardware의 크기와 scratchpad 조건을 고려해 tile 크기를 선택하게 한다. 다만 실제 실행의 지연과 padding 처리는 생성한 구성과 명령 경로에 달려 있다. 이번 5×5 계산은 특정 Gemmini 구성의 cycle 측정값이 아니다. [Gemmini 논문, 3쪽 Data Staging and Mapping](https://arxiv.org/pdf/1911.09925)

## 남는 PE를 다른 작업에 주는 데에도 조건이 있다

빈 위치에 다른 행렬의 결과를 넣으면 활용도가 올라갈 수 있다. 하지만 각 PE가 독립적인 입력·누산 상태를 받을 수 있어야 한다. row마다 공유하는 A와 column마다 공유하는 B가 서로 다른 작업의 입력을 동시에 표현하지 못하면 자리만 있다고 합칠 수 없다. 별도 partition이나 주소 생성 규칙이 필요한 이유다.

여러 요청을 batch로 묶는 방법도 출력 방향을 넓혀 tail을 줄일 수 있지만, 요청을 기다리는 시간과 지연 요구가 붙는다. 유효 비율을 높이기 위해 작은 요청을 무한히 모을 수는 없다. 개별 요청의 완료 시간과 묶음의 처리량을 함께 본다.

array tail utilization이 낮다는 결과를 얻으면 먼저 어떤 출력 방향이 array 크기와 맞지 않는지 찾는다. 그다음 tile 크기, 가능한 결합 방식과 저장 mask를 검토한다. 마지막 빈자리를 정확히 세는 일은 이용률 수치를 좋게 만드는 목적보다, array를 키웠을 때 얻는 이득과 추가로 남기는 자리를 구분하는 데 유용하다.

