티스토리 뷰
[논문] 임베디드에서의 float32와 float 16, 그리고 int8 비교(양자화)
sikaro 2024. 2. 5. 09:16https://www.dbpia.co.kr/journal/articleDetail?nodeId=NODE11522634&language=ko_KR&hasTopBanner=true
https://www.dbpia.co.kr/journal/articleDetail?nodeId=NODE11132879
임베디드에 CNN 같은 모델이 적용되기 위해서는 양자화나 가지치기(드롭아웃)이 선행되어야 한다.
요즘에는 일반적으로 경량화가 되어 있는 모델들도 많아 스마트폰에서도 사용이 가능하지만, GPU가 없는 임베디드에서는 무리인 가능성이 없지 않다.
여기서 양자화는 float32 기반으로 스탄다드하게 훈련된 모델을, 타입 변환을 통해 용량을 줄이는 행위를 말한다.
그만큼 인식률이 떨어지는데, 두 논문은 얼마나 그 인식률이 떨어지는지에 대한 그래프를 담고 있어 가져와보았다.
필요한 기본 지식
일단 가장 중요한 건 float32,float16,int16,int8의 차이점을 알고 있어야 한다는 점이다. 무엇이 차이일까?
가장 확실한 건 범위의 차이라는 것이다.
마이크로소프트에서 발행한 다음의 레퍼런스에서 볼 수 있듯이, 각 형식의 차이는 기본적으로 엄청나게 벌어진다.
int8이나 int16의 경우 다들 알고 있을 것이다. -128 ~ 127, 그리고 -32,768 ~ 32,767이다.
그렇다면 Float은?
- Float16 stores 4 decimal digits and the max is about 32,000.
- Float32 stores 8 decimal digits and the max is about 10^38.
- Float64 stores 16 decimal digits and the max is about 10^307
여기서 보면 헷갈리는 경우가 많다.
그렇기에 더 세부적으로 본다면 float 16의 경우 5bit의 지수와 10bit의 소수로 구성된다는 사실을 알고 있어야 한다.
이를 IEEE 754 표준에서 half precision이라 하는데, 구체적으로는 다음과 같다.
Half Precision(float 16) | 1 bit | 5 bit | 10 bit |
Single Precision(float 32) | 1 bit | 8 bit | 23 bit |
여기서 Single Precision은 단 정밀도라고 한다.
정수부를 2진법으로 바꿔서 8bit로 저장하고, 소수부를 2진법으로 바꿔서 23bit로 저장하는 것.
말 그대로 정밀도의 의미로서, Double Precision과 Quadruple Precision도 있지만, 어떤 방식을 사용해도 실수를 오차 없이 표현하는 건 불가능하다.
만약에 십진수 13.1875를 Sing Precision으로 정규화한다고 하자.
13은 이진수로 나타내면 8+4+1이므로 1101로 나타낼 수 있다.
소수점 아래는 0.125 + 0.0625 이므로 0011로 나타낼 수 있다.
여기서 소수점을 이진수로 표기하는 방법은 소수부에 2를 곱하고, 그 결과가 1로 떨어지거나 똑같은 소수점이 나올때까지 반복하여 위에서부터 차례로 가져오는 것이다.
0.125 * 2 = 0.25
0.25 * 2 = 0.5
0.5 * 2 = 1.0
=> 0010
0.0625 *2 = 0.125
....(중략)
=> 0001
더하면? 0011
그래서 1101.0011이며, 졍규화하면 1.1010011 * 2^3으로 나타낼 수 있게 된다.(3단계 쉬프트)
여기서 가수를 채우는데, 소수점 자리수인 1010011에서 남은 자리수는 다 0으로 채운다.
무슨 소리냐면, 0을 16개 붙여서 총 23자리가 되도록, 10100110000000000000000이 된다는 소리다.
그리고 지수는 부호 비트가 없어서 음수 값을 처리하기 위해 바이어스 표현법을 사용하게 된다.
지수 값이 3이므로, 바이어스 표현법으로 나타내면 10000010이 된다.
(바이어스 표현법은 127+n으로 표기하므로, 130을 2진수로 표현한 10000010이 나온 것이다.)
잠시 짚고 가자.
바이어스 표현법
8비트 변수는 -128~127까지의 숫자를 표현할 수 있다.
- 0000_0000 : 0
- 0000_0001 : 1
- 0111_1111 : 127
- 1000_0000 : -128
- 1000_0001 : -127
- 1111_1111 : -1
여기서 -128이 1000_0000인 이유는 2의 보수법을 사용하기 때문.
11111111이 -1인 이유도 127을 뒤집고, 1을 더하기 때문이다.
따라서 바이어스 표현법은, 이 상태에서 127과의 차이를 표현한 방식이 된다.
.
- 0000_0000 : -127
- 0000_0001 : -126
- 0111_1111 : 0
- 1000_0000 : 1
- 1000_0010 : 3
- 1111_1111 : 128
바이어스 표현법을 쓰는 이유는 지수 비트에 부호 비트를 넣어서 음의 지수를 표현하는 것보다 더 쉽게 구현할 수 있기 때문이다.
따라서 13.185를 최종적으로 표현하면 부호 비트 + 지수부(8bit) + 가수부(23bit)로서 다음과 같다.
- 부호 비트: 0 (양수)
- 지수 부분: 10000010(8비트)
- 가수 부분: 01010011000000000000000
아직 논문 내용은 들어가지도 않았는데 뭔가 배워야 할게 많다.
그만큼 논문 학습이라는 건 엄청난 양의 베이스 지식을 필요로 한다는 소리다.
그런데 아직 안 끝났다.
그래서 float 32랑 float 16의 차이가 뭔데?
그래서 결론적으로 float 32랑 float16의 차이가 뭐냐고 물어본다면, 위의 개념을 다 알고 있다고 했을 때 다음과 같다.
float 16 = 지수부가 5비트, 가수부가 10비트이다.
float 32 = 지수부가 8비트, 가수부가 23비트이다.
한마디로, 소수점의 자릿수가 크게 차이 난다는 것.(10자리, 23자리)
지수부를 표현하는 방식의 경우에는 16의 경우 바이어스가 또 바뀐다.(이에 대해서는 생략하겠다. 궁금하면 더 찾아보면 된다)
그렇기에 양자화를 하는 방식이면 DNN에서 데이터당 메모리 사용량을 줄일 수 있다.
이는 정밀 데이터의 연구에서 float 16을 사용했을 때 상황에 따라 float 32와 별 차이 없는 예측률을 보여주기에, 사용한다.
정확히는 Loss scaling 이라는 기법을 잘 사용해야 가능한 거지만..설명이 길었으므로 이는 추후 추가하도록 하겠다.
그러나 오늘 학습할 논문에서는 On-device와 임베디드 시스템에서 사용하도록 만들어진 경량화 모델을 양자화하게 되면 어떻게 되는지에 대한 데이터를 담고 있다.
학습(논문 1)
https://www.dbpia.co.kr/journal/articleDetail?nodeId=NODE11132879
일단 논문을 보면, 노면 데이터셋은 학습 5000, 검증 1000, 테스트 1000으로 별로 데이터 셋이 없다.
그러나 모바일넷의 특성상 확실히 경량화된 모델에서도 데이터셋의 정확도는 높게 나타난다.
테스트 정확도가 96.4%니, 꽤 정확하다고 볼 수 있다.
float 32의 모델 8.9MB에서 float 16 모델로 4.5MB, int8과 Full int8 모델 2.7MB로 감소시켰다.
테스트 데이터 1000장 기준으로는 float 16 양자화의 경우 96.2%로 원본 모델모다 0.2% 감소했다.
int8과 int8(full)의 경우는 오히려 정확도가 증가(?!) 하는 모습이다.
그런데 확실히 작은 모델이었기 때문에 정확도가 유지되었다는 판단.
또한 실시간 추론 시간 면에서는 float 32와 float 16의 차이가 CPU 스레드가 3,4로 늘어나는 경우를 제외하고는 거의 같은 수준을 보여주었다.(그리고 2ms~3ms 차이면 다소 오차로서 볼 법도 하다)
결론적으로 DNN 양자화 측면에서 임베디드에 적용할 때도 float 16이 별 차이가 없다는 걸 알 수 있다.
또 다른 논문에서도 그렇다.
2번째 논문 학습
https://www.dbpia.co.kr/journal/articleDetail?nodeId=NODE11522634&language=ko_KR&hasTopBanner=true
본 논문에서는 양자화 뿐만 아니라, 부분 양자화, 양자화 인식 훈련 등 다양한 환경에서의 양자화 기법이 임베디드 환경에서 미치는 영향을 평가하고 있다.
yolo, ssd, RetinaNet 등 1-stae detector는 정확도는 소폭 떨이지지만 훨씬 빠른 추론속도로 성능 개선.
Yolo의 경우, 피처를 추출하는 백본 계층, 피처를 가공하는 넥 계층, 마지막으로 경계 박스 위치 정보를 추론하는 헤드 계층으로 구성
한마디로 필터 후 풀링 후 평탄화 전결합.
여기서 32비트로 훈련된 모델을 8비트 정수로 바꾼다.
양자화 인식 훈련(QAT)는 양자화기를 삽입하고 부동소수점으로 다시 훈련한다.
순전파에서 오류 시뮬레이션 후 재훈련 이후 역전파로 이를 계층마다 최소화하는 스케일 및 오프셋 결정
부분 양자화는 모델 압축과 추론 성능 유지라는 장점을 모두 활용하기 위해 제안된 기법
모델 균일 양자화가 아니라, 각 계층마다 비트폭을 조정하여 효율적으로 모델을 양자화한다.
결론 :
훈련 후 양자화(PTQ) - 모델 크기는 74.8% 줄어든다. 추론 속도도 48.4%(꽤나 높다) 빨라졌다.
그러나 정확도가 69.33%에서 67.54%로 매우 큰 손실
양자화 인식 훈련(QAT) - 모델 크기는 PTQ와 똑같지만, 정확도는 69.29로 0.06%밖에 안 줄어들었다.
부분 양자화 - 백본 계층은 추론 시간이 20.2% 빨라지고, 모델 크기 또한 50.5%로 크게 감소.
단, 정확도 또한 0.87% 감소
넥 계층은 1.4% 빨라졌으며 모델 크기는 17.98% 줄었고, 정확도는 0.32% 감소.
반면 헤드 계층은 모델 크기 및 추론 시간은 3.4%, 7.9%로 소폭 감소하였으나, 정확도가 1.15%나 감소하여 양자화가 효율적이지 않다.
부분 양자화 측면에서는 더 많은 연구가 필요해보인다.
헤드 계층은 양자화가 적합하지 않다.
판단이나 모델에 따라 백본 계층 또한 고려 대상이 될 것으로 보인다.
아니라면 여기서 32비트를 8비트로 바꾼 방식이 문제일 수도 있다.
추가적인 실험을 통해 알아나가야 할 사항일 듯 싶다.