channel별 독립 계산이 큰 MAC array에 까다로운 이유
depthwise convolution은 곱셈을 많이 줄이는 연산이다. 그런데 dense convolution에서 잘 작동하던 큰 MAC array에 넣으면 기대만큼 빨라지지 않을 수 있다. depthwise mapping의 어려움은 연산이 부족하다는 말보다, 서로 독립적인 channel의 작업을 기존의 공유 경로에 어떻게 넣을지에 있다. 연산량과 공급 가능한 병렬성을 함께 봐야 한다.
channel마다 자기 입력과 filter를 쓴다
일반적인 dense convolution의 출력 channel 하나는 여러 input channel의 값을 filter와 곱해 더한다. 입력 하나가 여러 출력 channel의 계산에 사용될 수 있다. 반면 여기서 다루는 depth multiplier 1의 depthwise convolution은 input channel마다 filter 하나를 적용하고 같은 channel의 출력을 만든다. reduction은 kernel의 공간 방향에서 일어나며 서로 다른 channel끼리 더하지 않는다.
MobileNet의 원 논문은 이 filtering 단계와, 뒤에서 channel을 섞는 1×1 pointwise 단계를 구분한다. 두 단계를 합친 depthwise separable convolution의 MAC 수와 depthwise 부분만의 MAC 수는 다르다. 이번 질문은 앞의 독립적인 filtering을 array에 놓는 방법이다. MobileNet 논문, 2–3쪽 3.1절
각 channel의 출력은 아래처럼 적을 수 있다. p는 출력 위치이며 r,s는 kernel 좌표다.
Y[p,c] = Σ(r,s) X[window(p,r,s),c] × W[r,s,c]
filter가 3×3이면 출력 원소 하나의 reduction 길이는 9다. channel이 64개라도 한 출력에서 9×64개의 항을 합치지 않는다. 64개의 서로 다른 짧은 reduction이 있다는 쪽에 가깝다.
같은 연산을 큰 dense GEMM으로 나타내면 0이 늘어난다
가상 입력에 channel 네 개와 출력 위치 네 개가 있다고 하자. 각 위치에서 channel별 3×3 window를 펼치면, 한 위치의 입력 row는 4×9=36개다. 위치 네 개를 모은 P는 4×36이다.
weight 행렬 B를 36×4로 만들되, 출력 channel c의 column에는 같은 input channel c의 kernel 아홉 개만 놓고 나머지는 0으로 둔다. 그러면 P×B는 원하는 4×4 depthwise 출력을 만든다. 수학적으로 같은 filtering을 만든 표현이지만 dense GEMM은 4×36×4=576MAC 자리를 계산한다. 실제 필요한 비영 weight 항의 곱은 4×4×9=144개로 1/4이다.
한 PE가 출력 원소 하나를 담당하는 4×4 array를 생각하면, 36번의 공통 reduction 중 각 PE에 유효한 weight가 있는 경우는 아홉 번이다. 다른 channel의 구간에서는 곱의 weight가 0이다. 0을 gate해서 전력을 줄일 수 있어도, 별도 압축 실행 없이 36번의 공통 진행 시간을 자동으로 아홉 번으로 줄일 수는 없다.
이 25%는 정의한 dense lowering의 유효 곱 비율이다. 전체 실행 시간이나 실제 switching의 비율은 아니다. kernel 값 자체에 0이 있는 경우도 넣지 않았다. 또한 dense convolution과 depthwise convolution을 바꾸어 같은 모델의 정확도를 보장한다는 비교도 아니다.
네 channel을 병렬로 놓으려면 입력 경로도 나누어야 한다
출력 위치 네 개를 channel마다 맡겨 총 16개의 독립 dot product를 병렬로 진행하고 싶을 수 있다. 각 dot product가 매 cycle 자기 channel의 다음 kernel 항을 받으면, 이상적으로 아홉 번의 MAC으로 각 결과를 만들 수 있다. 그러나 이 설명에는 PE마다 필요한 입력을 공급할 수 있다는 조건이 숨어 있다.
기존 dense array에서 같은 row의 PE들은 하나의 A 값을 공유한다. depthwise의 서로 다른 channel을 옆 column에 놓으면 같은 시점에 필요한 X 값도 channel마다 달라진다. 하나의 공통 row 입력으로는 그 값들을 동시에 표현할 수 없다. channel별 partition과 독립 공급, 여러 bank의 port나 다른 dataflow가 필요한 이유다.
필요한 MAC이 독립적이라는 사실은 hardware의 모든 입력선도 독립적이라는 뜻이 아니다. 이미 만들어진 array에서 software의 좌표만 바꾸면 해결되는지, 새 routing이 필요한지 나누어 검토한다. 작은 channel 묶음으로 실행하는 방법도 가능하지만 묶음마다 생기는 시작·저장 비용을 더해야 한다.
재사용이 사라지는 방향과 남는 방향을 구분한다
depthwise에서는 한 input channel을 여러 output channel에 공유하는 dense 연산의 재사용이 줄어든다. 그렇다고 모든 재사용이 사라지는 것은 아니다. 같은 filter는 여러 출력 위치에 쓰이고, 이웃 window는 입력 영역이 겹칠 수 있다. channel 방향의 공유를 못 하는 것과 공간 방향의 재사용까지 못 하는 것은 다른 문제다.
Gemmini 원 논문도 MobileNet의 depthwise layer가 가진 낮은 데이터 재사용 때문에 해당 spatial accelerator에 효율적으로 매핑하기 어렵다고 설명한다. 그 평가는 논문의 특정 구성에 관한 것이며, 모든 depthwise 전용 구조가 비효율적이라는 결론은 아니다. Gemmini 논문, 4쪽 IV.B절
좋은 depthwise mapping은 channel 수만 보고 PE 수를 채우지 않는다. 각 출력의 짧은 reduction, 입력의 channel별 독립성, filter와 window의 공간 재사용을 함께 배치한다. 이 관계를 실제 port와 buffer에 대응시키면, 곱셈을 줄인 모델이 기존 array에서는 왜 덜 활용되고 어떤 공급 경로에서 다시 병렬성을 얻는지 설명할 수 있다.