Runtime Operator Coverage
마지막 수정:
Quantized artifact가 있다고 해서 실제 실행이 quantized라는 뜻은 아니다.
INT8 weight file
!=
INT8 kernel execution
모델 크기는 줄었는데 latency가 그대로이거나 더 느릴 수 있다. 저장은 낮은 precision인데 runtime이 계산할 때 dequantize해서 FP16/FP32 kernel을 쓸 수 있기 때문이다.
Looks quantized
QDQ / MatMulInteger exists
- Risk
- graph has INT8 nodes, but expensive ops may stay FP
- Check
- inspect graph nodes
Covers compute
Conv / MatMul / Gemm quantized
- Risk
- node coverage can lie if FLOPs are elsewhere
- Check
- cross-check with FLOPs
Runs fast
native kernel selected
- Risk
- provider may dequantize or fallback
- Check
- profile runtime
Operator coverage를 확인한다
ONNX Runtime에서 quantized graph는 보통 두 가지 방식으로 보인다.
QDQ format:
QuantizeLinear / DequantizeLinear node가 op 주변에 붙음
Operator replacement:
MatMulInteger, DynamicQuantizeLinear 같은 quantized op가 등장
그래프에 이런 node가 있는지 확인하면 “quantization이 적용됐는가”를 볼 수 있다.
하지만 node 개수만 보면 부족하다.
많은 node가 INT8
하지만 가장 비싼 MatMul이 FP32
-> speedup 거의 없음
적은 node만 INT8
하지만 FLOPs 대부분을 차지하는 MatMul이 INT8
-> speedup 가능
그래서 coverage는 op count가 아니라 compute-heavy op 기준으로 봐야 한다.
Dynamic과 static은 coverage가 다르다
Dynamic quantization은 보통 MatMul/Gemm 위주로 적용된다.
Transformer:
MatMul이 많음
-> dynamic quantization이 효과적일 수 있음
CNN:
Conv가 많음
-> MatMul만 quantize하면 대부분 FP로 남음
Static quantization은 calibration을 거쳐 Conv, Add, MatMul 같은 더 많은 op를 QDQ로 감쌀 수 있다. CNN에서는 static QDQ가 더 맞는 경우가 많다.
반대로 transformer activation은 input마다 range가 크게 흔들릴 수 있다. Generic static quantization이 activation range를 잘못 잡으면 품질이 무너질 수 있다.
Execution provider가 kernel을 결정한다
ONNX Runtime은 execution provider를 통해 실제 kernel을 고른다.
CPUExecutionProvider
CUDAExecutionProvider
TensorRTExecutionProvider
OpenVINO
그래프에 INT8 op가 있어도 provider가 그 op의 native INT8 kernel을 지원하지 않으면 fallback이 생긴다.
INT8 op exists
-> provider has no kernel
-> dequantize or fallback
-> speedup 없음
그래서 speedup 검증에는 profiler가 필요하다.
어떤 op가 시간을 쓰는가?
그 op가 INT8 kernel인가?
dequantize / quantize overhead가 큰가?
문제를 해결하는 방법
Coverage가 낮다면 quantization 설정을 바꾼다.
dynamic -> static QDQ
op_types_to_quantize에 Conv / MatMul / Gemm 포함
unsupported op 제외
sensitive node는 FP32 유지
Runtime kernel이 문제라면 provider나 format을 바꾼다.
CPU -> CUDA / TensorRT provider
ConvInteger 대신 QDQ format
target hardware가 지원하는 quantization scheme 사용
Transformer graph가 문제라면 transformer-aware optimizer가 필요할 수 있다.
attention pattern fuse
layernorm pattern 정리
shape inference 보강
runtime이 알아볼 수 있는 graph로 변환
핵심은 “INT8 파일을 만들었는가”가 아니라 “중요한 연산이 실제로 INT8로 실행되는가”다.
확인
- INT8 artifact와 INT8 execution은 왜 다른가?
- Operator coverage를 node 개수만으로 보면 왜 위험한가?
- Execution provider가 speedup에 영향을 주는 이유는 무엇인가?