LabHub
学习 学习路径 课程

AI瘦身失败事件

校准数据决定精度 — 窄的、准的、宽的,各做一次

在 LabHub 中继续学习

한국어 원문으로 표시합니다.

목표

CalibrationDataReader 를 직접 구현하고, 같은 모델을 좁은·맞는·넓은 보정 자료 세 벌로 정적 양자화해 그래프에 박히는 입력 스케일과 자기가 잰 최대 절대 오차를 나란히 남긴다. 그리고 어긋난 보정은 표본 수로 못 고친다는 것과, QDQ 와 QOperator 가 같은 계산의 다른 모양이라는 것을 재어서 확인한다.

왜 중요한가

정적 양자화는 활성의 스케일을 변환 시점에 못박고, 그 스케일은 보정 자료가 정한다. 그래서 보정 자료는 설정값이 아니라 입력값이다. 같은 모델과 같은 명령이라도 보정 자료가 다르면 완전히 다른 모델이 나온다. 그런데 그 자료는 코드에도 로그에도 남지 않는다. 무너지는 방향은 둘이다. 보정이 본 범위가 실제보다 좁으면 밖으로 나간 값이 정수 자료형의 끝에 붙어 버리고 역양자화로 돌아오지 않는다. 넓으면 잘리지는 않지만 쓰이지도 않는 범위에 눈금을 나눠 주므로 실제로 값이 사는 구간의 해상도가 떨어진다. 어느 쪽인지는 오차 하나만 봐서는 모르고, 세 벌로 재어 봐야 갈린다. 그리고 표본 수로 못 고치는 종류가 있다. 보정 자료의 분포 자체가 어긋나 있으면 그 자료를 512배로 늘려도 관찰 범위는 조금밖에 안 넓어진다. 수가 아니라 분포가 문제일 때를 구분할 수 있어야 시간을 엉뚱한 곳에 쓰지 않는다. 채점기는 여러분이 적어 낸 숫자를 믿지 않는다. 여러분의 공급기를 직접 몰아 계약을 지키는지 보고, 채점기가 지은 모델에 여러분의 도구를 걸어 보고, 적어 낸 오차를 여러분의 모델 파일로 다시 계산해 대조한다.

단계

  1. /root/onnxq-calib/gen_model.py 를 만들어 실행해 /root/onnxq-calib/fp32.onnx 를 만드세요.
  2. /root/onnxq-calib/reader.pymake_reader(arrays, input_name) 를 만드세요.
  3. /root/onnxq-calib/calibtool.pyquantizescales 를 만들어 /root/onnxq-calib/int8_match.onnx/root/onnxq-calib/match.json 을 만드세요.
  4. error 를 더하고 좁은 보정으로 /root/onnxq-calib/int8_narrow.onnx/root/onnxq-calib/narrow.json 을 만드세요.
  5. 넓은 보정으로 /root/onnxq-calib/int8_wide.onnx/root/onnxq-calib/wide.json 을 만드세요.
  6. 셋을 한자리에 모아 /root/onnxq-calib/calib_report.json 을 만드세요.
  7. 좁은 보정을 1·8·64·512 묶음으로 늘려 보고 /root/onnxq-calib/samples.json 에 적으세요.
  8. 같은 보정으로 QOperator 형식도 뽑아 /root/onnxq-calib/format.json/root/onnxq-calib/report.md 를 만드세요.

참고

잴 대상을 손에 쥔다

/root/onnxq-calib/gen_model.py 를 만들어 실행해 /root/onnxq-calib/fp32.onnx 를 만드세요. 배치 축은 이름만 있는 동적 축이어야 하고, 행렬 두 개와 편향 두 개를 담은 MLP 입니다.

onnx.helper 로 그래프를 짓고 onnx.checker 로 확인한 뒤 저장하세요. 입력 모양의 첫 축은 숫자가 아니라 이름("N")으로 둡니다. 만든 뒤 onnxruntime 으로 한 번 돌려 보면 확실합니다.

변환기에 자료를 흘려 넣는 계약

/root/onnxq-calib/reader.pymake_reader(arrays, input_name) 를 만드세요. CalibrationDataReader 를 상속한 객체를 돌려주고, get_next() 는 입력 사전을 하나씩 내다가 자료가 떨어지면 계속 None 을 돌려줘야 합니다.

계약은 짧지만 끝 처리가 핵심입니다. 변환기는 None 이 나올 때까지 계속 부르므로, 자료가 떨어진 뒤 예외를 던지면 보정 도중에 죽습니다. 반복자 하나를 들고 next(it, None) 로 내는 방법도 있고 목록을 앞에서부터 꺼내는 방법도 있습니다. 한 번 소진한 객체는 다시 쓰지 못한다는 점을 기억하세요.

맞는 보정으로 한 번

/root/onnxq-calib/calibtool.pyquantizescales 를 만들고, 씨앗 4242 · 배율 1.0 · 64묶음으로 /root/onnxq-calib/int8_match.onnx 를 만든 뒤 /root/onnxq-calib/match.json 에 span·batches·input_scale·input_zero_point·observed_span·quantize_nodes·dequantize_nodes 를 적으세요.

quantize_static 에 quant_format 을 QDQ 로, activation_type 과 weight_type 을 QInt8 로 주세요. 보정 자료 규칙은 참고 절에 못박혀 있습니다 — 채점기가 같은 자료를 다시 만들어야 하므로 그대로 따르세요. scales 는 그래프에서 입력 텐서를 첫 입력으로 받는 QuantizeLinear 를 찾아 그 스케일 초기자를 읽습니다. observed_span 이 보정 자료의 범위 폭과 얼추 맞는지 확인해 보세요.

좁은 보정 — 잘린다

error 를 더하고, 씨앗 4242 · 배율 0.2 · 64묶음으로 /root/onnxq-calib/int8_narrow.onnx 를 만든 뒤 /root/onnxq-calib/narrow.json 에 span·batches·input_scale·observed_span·max_abs_error·match_max_abs_error 를 적으세요. 오차는 평가 씨앗 777 · 256행으로 잽니다.

평가 자료에는 배율을 곱하지 않습니다 — 그것이 실제 분포입니다. 보정이 본 범위가 실제보다 다섯 배 좁으면 평가 입력의 상당수가 자료형 끝에 붙습니다. 맞는 보정의 오차도 함께 적어 두면 다음 단계에서 비교가 쉽습니다.

넓은 보정 — 뭉개진다

씨앗 4242 · 배율 10.0 · 64묶음으로 /root/onnxq-calib/int8_wide.onnx 를 만들고 /root/onnxq-calib/wide.json 에 narrow.json 과 같은 모양으로 적으세요.

이번에는 잘리지 않습니다 — 보정이 실제보다 훨씬 넓게 봤으니까요. 그런데도 오차는 맞는 보정보다 큽니다. 눈금 256개를 쓰이지도 않는 범위에 나눠 준 탓입니다. observed_span 을 맞는 보정의 것과 견주어 보세요.

셋을 한자리에 놓기

/root/onnxq-calib/calib_report.json 에 eval·narrow·match·wide·best·clipping_worse_than_coarse 를 적으세요. 세 항목에는 span·input_scale·observed_span·max_abs_error 가 있어야 하고, best 는 오차가 가장 작은 것의 이름입니다.

clipping_worse_than_coarse 는 좁은 쪽 오차가 넓은 쪽 오차보다 큰지를 담은 불리언입니다. 세 오차를 나란히 놓으면 U 자가 보입니다 — 가운데가 가장 낮고 양쪽으로 올라갑니다. 어느 쪽 벽이 더 가파른지 확인해 보세요.

표본 수로는 못 고치는 것

좁은 보정(배율 0.2)을 1·8·64·512 묶음으로 네 번 양자화해 /root/onnxq-calib/samples 아래 narrow_n1.onnx narrow_n8.onnx narrow_n64.onnx narrow_n512.onnx 로 저장하고, 맞는 보정 8묶음도 match_n8.onnx 로 저장한 뒤 /root/onnxq-calib/samples.json 에 narrow_runs·match_small·narrow_best·narrow_gain·beats_narrow 를 적으세요.

narrow_best 는 네 번 중 가장 작은 오차이고, narrow_gain 은 1묶음 오차를 narrow_best 로 나눈 값입니다 — 표본을 512배로 늘려 얻은 개선이지요. beats_narrow 는 맞는 보정 8묶음이 좁은 보정 512묶음보다 나은지를 담은 불리언입니다. 512묶음은 시간이 조금 걸립니다.

같은 계산, 다른 모양

맞는 보정(씨앗 4242 · 배율 1.0 · 64묶음)으로 QOperator 형식 /root/onnxq-calib/int8_qop.onnx 를 만들고, /root/onnxq-calib/format.json 에 qdq·qoperator·same_input_scale·max_abs_difference·difference_vs_error 를 적은 뒤 /root/onnxq-calib/report.md 를 네 절로 쓰세요.

qdq·qoperator 항목에는 bytes·ops·max_abs_error 를 담습니다. max_abs_difference 는 두 형식의 모델에 같은 평가 배치를 넣어 나온 출력의 최대 절대 차이이고, difference_vs_error 는 그것을 QDQ 의 오차로 나눈 값입니다. 두 형식의 오차 크기는 같은 자릿수인데 둘 사이의 차이는 0 이 아닙니다 — QDQ 는 실행 방식을 런타임에 맡기기 때문입니다. 보고서 네 절의 제목은 과제 문장에 있는 그대로 두세요.