LabHub
배우기 러닝패스 코스

The AI Diet Gone Wrong

Count the Float Islands: Operator Support and Substitution

LabHub 에서 이어서 보기

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

목표

같은 모델을 다섯 가지로 내보낸 뒤, 그래프에서 int8 로 감싸인 연산자와 float 로 남은 섬을 세는 분석기 graphscan.py 를 만든다. 범위를 좁히는 두 가지 옵션의 효과를 숫자로 확인하고, 모델을 Gemm 으로 고쳐 Q/DQ 왕복을 줄인다.

왜 중요한가

양자화 도구는 자기가 아는 연산자만 int8 모양으로 바꾼다. QDQ 형식에서는 연산자 이름이 그대로 남고 텐서마다 QuantizeLinear 와 DequantizeLinear 가 끼워질 뿐이다. 그래서 "양자화했다" 와 "int8 로 돈다" 는 다른 말이고, 둘을 가르려면 그래프를 세어야 한다. 이 실습은 판정 규칙을 이렇게 고정한다. 모든 입력이 DequantizeLinear 에서 오고 모든 출력이 QuantizeLinear 로만 가는 연산자를 int8 로 감싸인 것으로 본다. 나머지는 float 로 남고, 이어진 것끼리 묶으면 float 섬이 된다. 섬은 개수보다 경계가 문제다. 섬마다 들어오는 DQ 와 나가는 Q 가 붙고, 경계마다 값을 옮겨 담는 일과 반올림이 한 번씩 더 생긴다. 섬을 줄이는 길은 둘이다. 범위를 좁혀 어긋나는 노드를 아예 빼거나, 모델을 고쳐 양자화되는 모양으로 만들거나. 채점기는 여러분이 적은 숫자를 믿지 않는다. 매번 다른 층 수·모양·제외 노드로 자기 모델을 임시 디렉터리에 만들어 여러분의 분석기를 실제로 실행하고, 채점기가 같은 규칙으로 센 값과 대조한다. 6·7단계는 여러분이 만든 모델 파일을 채점기가 직접 읽어 다시 셉니다.

단계

  1. /root/ops/gen_models.py 를 만들어 실행해 /root/ops 아래 모델 다섯 개를 만드세요.
  2. /root/ops/graphscan.pynodes 를 만들어 연산자 census 와 초기값 자료형을 세게 하세요.
  3. qdq 를 더해 QuantizeLinear·DequantizeLinear 와 Q/DQ 왕복을 세게 하세요.
  4. islands 를 더해 int8 로 감싸인 연산자와 float 섬을 가르게 하세요.
  5. boundary 를 더해 섬마다 드나드는 경계를 세게 하세요.
  6. diff 를 더해 두 모델의 차이를 내게 하고, 범위를 좁힌 결과를 /root/ops/scope.json 에 적으세요.
  7. MatMul 과 Add 를 Gemm 으로 합친 /root/ops/gemm.onnx 와 그것을 양자화한 /root/ops/gemm_full.onnx 를 만들고 /root/ops/fuse.json 에 적으세요.
  8. /root/ops/ops_report.md 를 네 절로 쓰세요.

참고

같은 모델을 다섯 가지로 내보내기

/root/ops/gen_models.py 를 만들어 실행해 /root/ops 아래 fp32.onnx·full.onnx·matmul_only.onnx·excluded.onnx·dynamic.onnx 를 만드세요.

onnx.helper 로 MatMul·Add·Relu 를 이어 붙이고 노드마다 이름을 주세요. 이름이 있어야 뒤에서 nodes_to_exclude 로 하나를 뺄 수 있습니다. 정적 양자화에는 CalibrationDataReader 가 필요하고, 같은 보정 자료를 여러 번 쓰려면 읽개를 그때마다 새로 만드세요 — 한 번 다 읽은 읽개는 다시 돌려주지 않습니다.

무엇이 들어 있는지부터 세기

/root/ops/graphscan.pynodes <model> 을 만들어 연산자 census 와 초기값 자료형 census 를 내게 하세요.

초기값의 자료형은 onnx.TensorProto.DataType.Name(init.data_type) 으로 이름을 얻습니다. 정적 QDQ 판과 동적 판을 나란히 세어 보면 형식이 얼마나 다른지 바로 보입니다 — 동적 판에는 MatMul 이 아예 없습니다.

Q/DQ 왕복을 세기

qdq <model> 을 더해 quantize·dequantize·round_trips·weight_dequantize 를 세게 하세요.

가중치에 붙는 DequantizeLinear 는 짝이 되는 QuantizeLinear 가 없습니다. 가중치는 이미 int8 로 파일에 들어 있기 때문입니다. 그래서 DequantizeLinear 수에서 그만큼을 빼야 활성값의 왕복 수와 맞아떨어집니다.

float 로 남은 섬 가려내기

islands <model> 을 더해 int8 로 감싸인 연산자와 float 로 남은 연산자를 가르고, 이어진 것끼리 묶어 섬으로 내게 하세요.

먼저 생산자·소비자 표를 만들어 두면 나머지가 쉬워집니다. 감싸임 판정은 참고 절의 규칙을 그대로 쓰세요. 섬을 묶을 때는 float 노드 사이의 연결만 따라갑니다 — Q 나 DQ 를 지나면 다른 섬입니다.

섬마다 경계를 세기

boundary <model> 을 더해 섬마다 들어오는 DequantizeLinear 수와 나가는 QuantizeLinear 수를 세고 전체 경계 수를 내게 하세요.

같은 DequantizeLinear 가 한 섬의 두 노드에 들어갈 수 있습니다. 노드마다 세지 말고 집합으로 모아 세세요. 섬의 출력이 그래프 출력으로 곧장 나가면 나가는 QuantizeLinear 가 0일 수 있습니다.

범위를 좁히면 무엇이 달라지는가

diff <a> <b> 를 더해 두 모델의 차이를 내게 하고, 세 판의 수치와 두 diff 를 /root/ops/scope.json 에 참고 절의 모양으로 적으세요.

옵션을 주었다고 그대로 되었다고 믿지 마세요. 노드 이름으로 뺀 판에서는 그 노드의 가중치가 float 초기값으로 남아 있고, 연산자 종류로 좁힌 판에서는 그 종류 밖의 연산자가 통째로 섬이 됩니다. 두 좁히기는 그래프에 남기는 흔적이 다릅니다.

모델을 고쳐 왕복을 줄이기

MatMul 과 Add 를 Gemm 하나로 합친 /root/ops/gemm.onnx 를 만들고, 그것을 같은 설정으로 양자화한 /root/ops/gemm_full.onnx 를 만든 뒤, 두 판의 수치와 최대 절대 오차를 /root/ops/fuse.json 에 적으세요.

합치기 조건을 지키세요 — MatMul 의 출력이 그 Add 에서만 쓰이고, Add 의 다른 입력이 초기값일 때만 합칩니다. 합친 뒤에는 onnx.checker.check_model 을 지나야 하고, 원본과 같은 입력으로 돌려 답이 같은지 반드시 확인하세요. 값이 달라졌다면 합치기가 틀린 것입니다.

그래프로 답할 수 있게 적기

/root/ops/ops_report.md## 무엇이 int8 로 갔나 ## float 로 남은 섬 ## 범위를 좁히면 무엇이 달라지나 ## 모델을 고쳐 얻은 것 네 절로 쓰세요. 섬의 개수와 사라진 Q/DQ 수가 숫자로 들어가야 합니다.

이 보고서는 "왜 기대만큼 안 줄었나" 라는 질문에 답하는 글입니다. 세어 본 다섯 숫자(연산자 census·Q/DQ 왕복·섬 개수·섬 크기·경계 수)를 그대로 쓰면 답이 됩니다. 시간을 재지 않았다는 것도 적으세요.