담는 방식에서 짐 자체로
2부의 앞부분은 짐을 담는 방식에 관한 이야기였다. 여러 요청의 걸음을 묶어 운반을 나눠 쓰고, 캐시를 빈틈없이 배치해 탱크를 알뜰하게 썼다. 이 장은 관점을 바꾼다. 담는 방식을 아무리 다듬어도 짐 자체가 무거우면 한계가 있다. 그렇다면 짐의 무게를 직접 줄이면 어떨까. 이것이 양자화의 발상이다. 짐칸을 아무리 잘 정리해도 짐 하나하나가 무거우면 실을 수 있는 총량에 벽이 있듯, 모델을 다루는 데에도 같은 이치가 통한다.
3장에서 디코드의 병목을 진단하며 세 갈래의 처방을 예고했을 때, 그 첫 번째가 실어 나를 짐 자체를 줄이는 길이었다. 이 장이 바로 그 길이다. 디코드 한 걸음마다 모델 가중치 전체를 메모리에서 실어 와야 했고, 그 운반량이 디코드를 느리게 하는 병목이었다. 가중치의 무게를 줄이면 실어 나를 양이 줄고, 병목이 곧바로 완화된다. 게다가 앞 장에서 본 KV 캐시도 같은 방법으로 가볍게 만들 수 있으니, 양자화는 메모리 대역폭과 메모리 용량이라는 두 제약을 동시에 겨냥하는 드문 기법이다. 앞의 두 장이 각각 처리량과 메모리를 겨냥했다면, 양자화는 그 둘의 밑바탕인 데이터의 양 자체를 건드린다는 점에서 결이 다르다.
숫자를 줄인다는 것
양자화가 무엇인지는 숫자를 저장하는 방식에서 출발해야 이해된다. 지금까지 여러 번 나온 반정밀도, 즉 FP16은 숫자 하나를 2바이트, 16비트로 저장한다. 이 16비트가 하나의 실수 값을 꽤 정밀하게 표현한다. 정수와 소수를 아우르며 아주 큰 값부터 아주 작은 값까지 폭넓게 담아내는 형식이라, 신경망 계산에 필요한 정밀도를 무난히 감당한다. 양자화는 이 비트 수를 줄이는 일이다. 숫자 하나를 8비트, 즉 1바이트로 저장하면 8비트 양자화이고, 4비트로 저장하면 4비트 양자화다. 더 과감한 기법은 그보다 낮은 비트로도 내려가지만, 낮출수록 뒤에 볼 오차 문제가 커지므로 실용적으로 많이 쓰이는 구간은 8비트와 4비트 언저리다.
비트 수와 메모리의 관계는 곧바로 산술이 된다. 16비트에서 8비트로 줄이면 저장 공간이 절반이 되고, 4비트로 줄이면 사분의 일이 된다. 7B 모델의 가중치가 반정밀도로 약 14기가바이트였는데, 모든 가중치를 순수하게 4비트로만 담는다고 가정하면 이론적 크기는 약 3.5기가바이트다. 실제 양자화 모델은 그룹마다 두는 눈금 정보와 메타데이터, 고정밀도로 남겨 둔 일부 값 때문에 이보다 조금 커진다. 실어 나를 짐이 사분의 일로 줄어드는 것이다. 이 차이는 어떤 하드웨어에 모델을 올릴 수 있는가를 가르기도 한다. 14기가바이트 모델은 못 올리던 GPU가 3.5기가바이트 모델은 여유롭게 받아들이는 식이라, 양자화는 단지 속도를 높이는 데 그치지 않고 애초에 그 하드웨어에서 모델을 돌릴 수 있게 만드는 문턱의 기술이기도 하다. 디코드가 메모리 운반에 묶여 있었으므로, 이 감소는 속도로 이어질 수 있다. 다만 저장량이 준 만큼 속도가 같은 비율로 빨라지는 것은 아니다. 디코드처럼 메모리 대역폭이 병목이고, 그 비트 형식을 효율적으로 읽어 복원하는 전용 처리가 갖춰져 있을 때 줄어든 운반량이 속도로 돌아온다. 그런 뒷받침이 없거나 프리필처럼 연산이 병목인 구간에서는 메모리는 줄어도 속도 이득이 작고, 복원 비용 때문에 오히려 느려질 수도 있다.
같은 원리로 KV 캐시를 낮은 정밀도로 저장하면 앞 장에서 셈한 캐시 크기도 그만큼 줄어, 같은 탱크에 더 많은 요청을 담을 수 있다. 4장의 곱셈에서 값 하나의 바이트 수가 한 요인이었음을 떠올리면, 이 감소가 어디서 오는지가 바로 보인다. 바이트 수를 절반으로 줄이면 캐시도 절반이 되는 것이다.
그런데 비트 수를 줄인다는 것은 표현할 수 있는 숫자의 가짓수를 줄인다는 뜻이다. 16비트는 수만 가지 이상의 값을 구분할 수 있지만, 4비트 코드 하나는 열여섯 개의 눈금만 구분한다. 다만 실제 기법은 텐서 전체를 하나의 자로 재지 않는다. 가중치를 작은 그룹으로 나누고 그룹마다 별도의 눈금 간격을 두어, 같은 4비트 코드라도 어느 그룹에 속하느냐에 따라 다른 실수 값으로 복원한다. 그래도 한 그룹 안에서는 열여섯 단계밖에 없으니, 원래 다양하던 값들을 이 성긴 눈금에 맞춰 반올림하는 과정에서 오차가 생긴다. 자를 촘촘한 눈금에서 성긴 눈금으로 바꿔 길이를 재는 것과 같다. 성긴 눈금으로도 대강의 길이는 재지만, 눈금 사이에 걸친 값은 가장 가까운 눈금으로 반올림되며 미세한 오차가 남는다. 양자화는 숫자의 눈금을 성기게 만드는 일이고, 반올림의 오차가 그 대가다. 양자화의 모든 기술적 어려움은 이 오차를 어떻게 견딜 만한 수준으로 억제하는가에 있다.
왜 대충 저장해도 대체로 작동하는가
여기서 자연스러운 의문이 생긴다. 숫자를 대충 저장하는데 모델이 멀쩡히 작동한다는 게 이상하지 않은가. 계산이 어긋나 엉뚱한 답이 나와야 할 것 같은데 말이다.
직관적인 답의 절반은 이렇다. 신경망에는 어느 정도의 중복성과 수치 변화에 대한 견딜성이 있다. 하나의 파라미터가 조금 부정확해도 수억 개가 함께 만드는 결과는 크게 흔들리지 않고, 최종 출력인 다음 토큰의 확률 분포에서 가장 그럴듯한 후보가 무엇인지는 웬만한 오차에는 잘 바뀌지 않는다. 그러나 이 견딜성만 믿고 비트를 줄이는 것으로는 충분하지 않다. 나머지 절반은 기법의 몫이다. 이 견딜성은 신경망이 애초에 잡음에 어느 정도 강하도록 학습된다는 사정과도 맞닿아 있다. 학습 과정 자체가 완벽하게 정밀한 계산을 전제로 하지 않기 때문에, 추론 단계에서 약간의 정밀도를 덜어 내도 무너지지 않는 여유가 모델 안에 이미 있는 셈이다.
오차가 늘 순하게 상쇄되기만 하는 것은 아니기 때문이다. 오차는 누적되거나 특정 채널에서 증폭되기도 한다. 그래서 실제 양자화 기법은 견딜성에 기대는 대신 오차를 적극적으로 통제한다. 값들을 작은 그룹으로 나눠 그룹마다 눈금을 맞추고, 소량의 보정 데이터로 오차가 출력에 미치는 영향을 측정하며, 유난히 민감한 채널이나 활성값은 따로 다룬다. 기법마다 방식은 다르다. 어떤 기법은 활성값의 통계로 중요한 채널을 찾아 눈금을 조정하고, 어떤 기법은 근사적인 계산으로 층 출력의 오차를 줄이며, 어떤 기법은 값들 사이의 이상치를 옮겨 양자화를 쉽게 만든다. 공통점은 중요한 값을 무조건 고정밀도로 남긴다는 것이라기보다, 출력에 큰 영향을 주는 오차를 찾아 줄인다는 데 있다. 모델이 낮은 정밀도에서도 작동하는 것은 작은 오차를 무조건 견디기 때문이라기보다, 오차가 크게 번지는 지점을 찾아 통제하기 때문이다. 어떤 기법은 각 값의 중요도를 미리 분석해 예민한 것에 더 정밀한 자리를 배정하고, 어떤 기법은 값들을 조금씩 조정해 낮은 정밀도에서도 오차가 덜 나도록 미리 손질한다. 접근은 여럿이지만, 중요한 소수를 지키고 다수를 줄인다는 큰 방향은 공유한다.
무엇을 양자화하는가
양자화의 대상은 하나가 아니다. 최소한 세 가지를 구분해 두면 이 분야의 논의를 따라가기 쉽다.
첫째는 가중치다. 모델의 파라미터 그 자체로, 양자화의 가장 기본적인 대상이다. 가중치는 학습이 끝나면 고정된 값이라 미리 한 번 낮은 정밀도로 변환해 두면 되고, 그래서 다루기가 상대적으로 수월하다. 서비스를 켜기 전에 조용히 한 번 변환해 두면, 이후로는 그 가벼워진 가중치를 계속 쓰기만 하면 된다. 실시간으로 값이 바뀌지 않으니 오차를 미리 분석하고 다듬을 여유도 있다. 가중치만 줄여도 저장 공간과 운반량이 크게 줄어 디코드가 빨라진다. 오늘날 널리 쓰이는 양자화의 상당수가 이 가중치 양자화다. 특히 개인이나 작은 조직이 큰 모델을 감당 가능한 하드웨어에서 돌리게 해 주는 몫을 주로 이 가중치 양자화가 맡고 있어, 오픈 가중치 모델 생태계의 확산과도 맞물려 있다.
둘째는 활성값이다. 입력이 모델을 통과하며 층마다 생겨나는 중간 계산 값들로, 가중치와 달리 요청마다 그때그때 달라진다. 활성값까지 낮은 정밀도로 다루면 계산 자체를 더 빠른 저정밀 연산으로 처리할 수 있어 이득이 더 크지만, 값이 실시간으로 변하고 예민한 값이 끼기 쉬워 가중치보다 다루기가 까다롭다. 실제 요청에서 나올 값을 미리 정확히 알 수는 없지만, 대표적인 보정 데이터로 분포와 이상치의 경향을 미리 조사할 수는 있고, 실행 중에 값에 맞춰 눈금을 정하는 방법도 있다. 다만 입력에 따라 값이 달라지고 큰 이상치가 불쑥 나타날 수 있어, 고정된 가중치보다 다루기가 까다롭다. 활성값 양자화가 가중치 양자화보다 늦게, 더 조심스럽게 실용화된 데에는 이런 사정이 있다.
셋째는 KV 캐시다. 앞 장에서 본 그 캐시를 낮은 정밀도로 저장하는 것이다. 긴 문맥일수록 캐시가 메모리를 크게 차지했으므로, 캐시 양자화는 긴 문맥과 많은 동시 요청을 감당하는 데 직접적인 도움이 된다. 다만 캐시를 지나치게 줄이면 문패와 내용물에 생긴 오차가 어텐션의 판단과 문맥 정보의 혼합을 흔들 수 있다. 긴 문맥에서는 절약되는 메모리가 큰 만큼 효과도 크지만, 품질 영향이 어느 위치와 과제에서 두드러지는지는 모델과 양자화 방식에 따라 달라진다.
이 셋은 서로 독립적이라, 가중치만 줄이거나 가중치와 캐시를 함께 줄이는 식으로 조합할 수 있다. 실제로 널리 쓰이는 조합은 가중치를 낮은 정밀도로 두어 저장과 운반을 줄이고, 계산은 그보다 높은 정밀도로 되돌려 수행하는 방식이다. 이러면 메모리 이득은 챙기면서 계산의 오차는 억제할 수 있다. 어떤 것을 어느 정밀도로 둘지의 조합이 곧 양자화 기법의 설계다. 어디까지 줄일지는 확보하려는 이득과 감수할 품질 손실 사이의 저울질로 정해진다. 메모리가 극도로 빡빡한 상황이라면 캐시까지 과감히 줄여 요청 수를 확보하는 쪽이 낫고, 품질이 조금의 손상도 허용되지 않는 서비스라면 가중치만 보수적으로 줄이는 데 그치는 편이 낫다. 정답이 하나로 정해져 있지 않고 서비스의 우선순위를 따른다.
언제 줄이는가
양자화를 이야기할 때 무엇을 줄이는가와 함께 언제 줄이는가도 갈래를 이룬다. 크게 두 시점이 있다.
가장 흔한 방식은 학습이 다 끝난 모델을 나중에 변환하는 것이다. 완성된 가중치를 받아 낮은 정밀도로 옮기고, 필요하면 소량의 데이터로 오차를 보정한다. 이 방식의 장점은 분명하다. 원래의 무거운 학습 과정에 손대지 않고, 이미 세상에 나온 모델을 가져다 가볍게 만들 수 있다. 오픈 가중치 모델을 받아 자기 환경에서 4비트로 돌리는 흔한 작업이 대개 이 방식이다. 비용이 적게 들어 널리 쓰이지만, 학습이 낮은 정밀도를 염두에 두지 않았던 만큼 과감하게 줄일수록 품질 손실이 커지는 한계가 있다.
또 하나의 방식은 학습 과정에서부터 낮은 정밀도를 염두에 두는 것이다. 모델을 만들 때 미리 정밀도가 낮아질 상황을 겪게 해서, 그 조건에 강한 모델로 길러 내는 셈이다. 이 방식은 더 낮은 비트에서도 품질을 지키는 데 유리하지만, 학습 단계에 개입해야 하므로 비용과 수고가 크다. 그래서 이 방식은 모델을 직접 만드는 조직의 선택지에 가깝고, 만들어진 모델을 가져다 쓰는 쪽에서는 앞의 사후 변환 방식을 주로 만난다.
이 구분은 서빙하는 입장에서 실용적인 의미가 있다. 내가 쓰려는 양자화 모델이 어느 방식으로 만들어졌는지에 따라, 같은 비트 수라도 기대할 수 있는 품질이 다를 수 있기 때문이다. 몇 비트인가만 볼 것이 아니고 어떻게 그 비트에 도달했는가를 함께 보아야 한다.
품질이라는 대가를 정직하게
양자화를 다룰 때 가장 조심해야 할 것은 과장이다. 정밀도를 낮추면 대체로 잘 작동한다는 말은 품질이 전혀 변하지 않는다는 뜻이 아니다. 정밀도를 낮출수록 오차는 커지고, 어느 지점을 넘으면 품질 저하가 눈에 보이기 시작한다. 그 지점이 어디인가는 모델과 기법과 과제에 따라 다르다. 그래서 몇 비트가 안전하다는 식의 보편적인 정답은 없다. 어떤 모델은 4비트에서도 거의 멀쩡하고, 어떤 모델은 8비트에서 이미 흔들린다. 간단한 과제에서는 티가 안 나던 저하가 까다로운 추론 과제에서는 도드라지기도 한다.
그래서 양자화를 실무에 적용할 때 빠뜨리면 안 되는 단계가 평가다. 낮춘 정밀도의 모델이 실제로 쓰려는 과제에서 원래 모델과 견줄 만한지를 직접 재보아야 한다. 어떤 기법이 어느 모델에서 몇 비트까지 품질을 지키더라는 보고는 유용한 출발점이지만, 그것이 내 모델과 내 과제에서도 그대로 성립한다는 보장은 없다. 1장에서 품질을 다섯 번째 최적화 축으로 두지 않고 침범해서는 안 되는 제약 조건으로 다룬다고 했는데, 양자화야말로 그 제약이 시험되는 자리다. 속도와 메모리를 얻는 대신 품질의 경계를 건드리는 기법이므로, 그 경계를 넘지 않았는지를 반드시 확인하고 넘어가야 한다. 이 확인을 건너뛴 채 몇 비트로 줄여 얼마나 빨라졌다는 이야기만 하는 것은 절반의 보고에 지나지 않는다. 얼마나 빨라졌는가와 품질을 지켰는가는 언제나 한 쌍으로 이야기되어야 한다. 이 원칙은 남의 벤치마크를 읽을 때도 그대로 적용된다. 인상적인 속도 향상 수치를 만나면, 그 옆에 품질 지표가 함께 제시되어 있는지, 어떤 과제에서 측정한 것인지를 확인하는 습관이 필요하다.
짐과 저울
이 장이 2부에 놓인 자리를 정리하자. 배칭은 운반을 나눠 쓰는 기술이었고, 캐시 배치는 탱크를 알뜰하게 쓰는 기술이었다. 양자화는 그 둘과 다른 층에서 작동한다. 담는 방식을 손대는 대신 담기는 짐의 무게 자체를 줄이는 것이다. 그래서 양자화는 앞의 두 기술과 경쟁하지 않고 함께 쓰인다. 짐을 가볍게 만든 위에 그 가벼운 짐을 다시 나눠 싣고 촘촘히 담으면, 이득이 겹쳐 쌓인다.
동시에 양자화는 이 책에서 처음으로 품질이라는 대가가 전면에 등장하는 기법이기도 하다. 앞 장들의 기법은 대체로 메모리나 관리 비용을 대가로 치렀지만, 양자화가 지불하는 것은 모델의 답 그 자체의 품질일 수 있다. 그래서 이 기법을 다루는 태도는 앞의 기법들과 달라야 한다. 얼마나 줄일 수 있는가만 묻지 말고, 어디까지 줄여도 되는가를 함께 물어야 한다. 이 물음의 감각을 쥐고 다음 장으로 넘어가자. 다음 장은 짐을 줄이는 또 다른 길, 그러나 정밀도가 아니라 어텐션의 구조 자체를 손보아 캐시를 줄이는 길로 향한다.
이 장을 나가며
몇 비트라는 말 하나에 두 가지를 되물을 수 있게 되었다. 어떤 모델을 몇 비트로 양자화했다는 말을 들었을 때 그것이 저장과 속도에서 대략 얼마의 이득을 주는지를 비트 수의 산술로 가늠할 수 있고, 그 이득을 말하는 자리에서 품질은 어떻게 확인했는가를 함께 물을 수 있다.
네 가지를 점검하자. 비트 수를 절반으로 줄이면 메모리와 운반량은 어떻게 변하며, 그것이 왜 디코드 속도로 이어지는가. 줄어든 저장량이 늘 같은 비율의 속도로 돌아오지는 않는 까닭은 무엇인가. 숫자를 대충 저장해도 모델이 대체로 작동하는 이유와, 그 대체로가 깨지는 경우는 무엇인가. 양자화의 세 가지 대상은 각각 무엇이며, 그중 긴 문맥 서빙에 직접 도움이 되는 것은 무엇인가. 다음 장은 정밀도가 아니라 어텐션의 구조를 바꿔 캐시를 줄이는 길, 어텐션의 다이어트로 넘어간다.
표 7-1. 양자화의 세 대상
| 대상 | 성격 | 주된 이득 | 주의점 |
|---|---|---|---|
| 가중치 | 고정, 사전 변환 | 저장·운반 감소 | 상대적으로 수월 |
| 활성값 | 요청마다 변동 | 저정밀 연산 활용 | 예민한 값이 많음 |
| KV 캐시 | 문맥 정보 보관 | 캐시 용량 절감 | 먼 과거 정보 손실 우려 |


