훈련이 끝난 뒤 part 5 of 13

배치 하나에 96%가 낭비였다

guide / / 6 sections

2편에서 캐시가 산술을 134.5 배 줄였는데 시간은 2.94 배만 빨라졌다. 진단은 “작은 모형에서는 곱셈이 아니라 오버헤드가 값이다” 였다. 이번 편은 그 몫을 숫자로 못박고, 그 다음 그것을 되찾는다.

걸음 하나의 값을 배치로 쪼갠다

캐시가 채워진 상태에서 글자 하나를 뽑는 시간을 배치 크기만 바꿔 가며 잰다. 배치 안의 요청들은 서로 무관하고, 한 걸음에 같이 처리된다.

    B   한 걸음(us)   처리량(글자/초)   요청당 지연(us)
    1          434            2302              434
    8          655           12214              655
   32          929           34431              929
  128         2565           49899             2565
  256         4811           53207             4811

배치를 256배 키웠는데 한 걸음은 11.1 배만 길어졌다. 그 차이가 전부 처리량으로 간다 - 2302 에서 53207 글자/초, 23.1 배다.

a 와 b

시간이 배치에 대해 거의 직선이다. 두 항으로 갈라 적을 수 있다.

t = a + b · B

a 는 배치와 무관하게 드는 값이다. 파이썬 한 줄씩 해석하고, 커널을 준비하고, 텐서를 만들고 하는 몫. b 는 요청 하나가 실제로 더하는 곱셈이다. 최소제곱으로 맞추면

a = 456.4 us     b = 16.88 us     적합 오차 최대 9.7%

이 두 숫자가 이 편의 전부다.

배치 1에서 걸음의 96.4% 가 오버헤드다.

배치      곱셈 몫   오버헤드 몫
   1       3.6%        96.4%
   8      22.8%        77.2%
  27      50.0%        50.0%
  64      70.3%        29.7%
 256      90.4%         9.6%

2편에서 “줄인 것은 곱셈이고 값을 치르고 있던 것은 곱셈이 아니었다” 고 쓴 것이 3.6%96.4% 다. 캐시가 아낀 것이 걸음의 4%도 안 되는 몫이었으니 134.5 배가 2.94 배로 줄어든 것이 당연하다.

a/b = 27 이라는 숫자도 여기서 읽힌다. 배치 27에서 곱셈이 오버헤드와 같아진다. 그보다 작으면 기계가 놀고 있고, 그보다 크면 실제로 계산하고 있다.

포화 처리량은 1/b 다. 1e6 / 16.88 = 59250 글자/초. 배치를 무한히 키워도 이 선을 못 넘는다. 배치 256에서 이미 53207 로 그 90% 다.

처리량과 지연은 같이 안 간다

0k 20k 40k 60k 500 1000 2000 4000 1 4 16 64 256 배치 크기 처리량 (글자/초) 지연 (us) a/b = 27 포화 1/b = 59250 처리량 요청당 지연
배치 크기에 따른 처리량(왼쪽 축)과 요청당 지연(오른쪽 축, 로그). 처리량은 5만 글자/초 근처에서 포화하는데 지연은 끝까지 오른다. 세로선이 a/b = 27 이고, 그 지점에서 곱셈이 고정 오버헤드와 같아진다.

왼쪽 축이 처리량, 오른쪽 축이 요청당 지연이다. 처리량 곡선은 눕고 지연 곡선은 끝까지 오른다.

이게 추론 서비스의 실제 손잡이다. 배치를 키우면 초당 처리하는 글자 수는 늘지만 한 사람이 기다리는 시간은 길어진다. 배치 1에서 434 us, 배치 256에서 4811 us11.1 배다. 처리량 23.1 배를 지연 11.1 배로 산 것이다.

어느 쪽이 맞는지는 무엇을 파는지에 달렸다. 대화하는 화면이면 지연이 전부고, 밤새 돌리는 일괄 작업이면 처리량이 전부다. 같은 모형, 같은 가중치인데 배치 크기 하나로 성격이 바뀐다.

길이가 다르면 절반이 패딩이다

지금까지는 배치 안의 요청이 다 같은 길이라고 가정했다. 실제로는 아니다. 어텐션은 배치를 한 텐서로 묶어야 하므로 가장 긴 것에 맞춰 패딩하고, 짧은 요청 몫은 버려진다.

문맥 길이를 1에서 128 사이 균등분포로 뽑아 재 보면

배치    평균 길이   최대 길이     낭비
   1        64.0       63.5      0.0%
   4        64.1      103.0     37.5%
  16        64.5      121.4     46.6%
  64        64.4      126.5     49.0%
 256        64.5      127.8     49.5%

배치를 키우면 평균 길이는 그대로인데 최대 길이가 상한에 붙는다. 그래서 낭비가 50% 로 수렴한다. 균등분포에서 평균이 상한의 절반이니 당연한 값이다.

앞 절에서 배치 256이 처리량을 23.1 배로 올렸지만, 길이가 이렇게 흩어져 있으면 그중 절반이 패딩을 계산한 것이다. 실효 처리량은 그만큼 깎인다.

이 낭비를 없애는 것이 실제 서빙 시스템이 하는 일이다. 길이가 비슷한 요청끼리 묶거나, 끝난 자리에 새 요청을 바로 끼워 넣거나(연속 배치), 패딩 없이 이어 붙인 뒤 경계를 따로 관리한다. 이 실험은 그 필요성을 재기까지고, 해법을 재지는 않았다.

재는 방법에 대한 단서

ab 는 이 노트북 CPU 에서 스레드 4개로, 캐시를 무작위로 채운 상태에서 잰 값이다. 옮겨 쓸 수 있는 것은 두 숫자가 아니라 재는 방식이다. 다른 기계, 다른 모형 크기, GPU 에서는 ab 가 완전히 달라지고 a/b 도 달라진다.

b 가 배치에 대해 정말 상수인지도 단서가 필요하다. 적합 오차가 최대 9.7% 로 작지 않고, 큰 배치에서는 캐시 지역성이 나빠져 b 가 커지는 쪽으로 휜다. 직선 두 항으로 요약한 것은 근사이고, a/b = 27 은 정확한 경계가 아니라 자릿수로 읽어야 한다.

그래서

  • 걸음 하나의 값은 t = a + b·B 로 갈라진다. 여기서 a = 456.4 us, b = 16.88 us
  • 배치 1에서는 걸음의 96.4% 가 오버헤드다. 2편의 진단이 이 숫자다
  • a/b = 27 에서 곱셈이 오버헤드를 따라잡는다. 그 아래에서는 기계가 놀고 있다
  • 배치 256이면 처리량 23.1 배, 지연 11.1 배. 포화 처리량은 1/b, 59250 글자/초
  • 길이가 흩어져 있으면 배치를 키울수록 패딩 낭비가 50% 로 수렴한다
  • 두 숫자는 이 기계 것이다. 옮겨 갈 수 있는 것은 재는 방식이다

다음 편은 2편이 남긴 다른 문제로 간다. 캐시가 가중치보다 커졌는데, 오래된 것부터 버리면 무엇이 망가지는지 잰다.

Comments