AI 다이어트 실패 사건 · 실행 공급자와 오차 누적 · 실습
쥔 것을 세어 본다 — 실행 공급자와 오차 누적
목표
요청한 실행 공급자와 세션이 실제로 쥔 공급자를 재고, 지원 연산자 목록에 따라 그래프가 몇 조각으로 갈리는지 세고, 층 경계마다 양자화 오차가 어떻게 자라는지 표로 만드는 도구 eprun.py 를 만든다.
왜 중요한가
실행 공급자는 요청하는 것이지 보장받는 것이 아니다. 이 환경에서 직접 재어 보면, 없는 공급자를 목록에 넣어도 예외가 나지 않고 그대로 CPU 로 떨어져 돌아간다. 그래서 "가속기를 쓰고 있다" 는 착각이 로그에 아무 흔적도 남기지 않은 채 오래 간다. 확인하는 길은 하나뿐이다 — 세션에 물어본다.
공급자는 모델 전체를 맡는 것이 아니라 노드마다 맡을 수 있는 것만 가져간다. 그래서 지원 목록이 그래프 순서 위에서 끊기면 모델이 여러 조각으로 갈리고 조각 사이마다 값을 넘긴다. 조각이 다르게 갈리면 계산 순서가 달라지고, 같은 모델에 같은 입력을 넣어도 기계마다 다른 수가 나올 수 있다.
오차 쪽도 같은 태도가 필요하다. 양자화 오차는 층마다 새로 생기고 앞 층의 오차가 다음 층으로 들어간다. 다만 층마다 고르게 자라지는 않으므로, 이야기로 넘기지 말고 층 경계마다 재서 표로 만들어야 어느 층이 문제인지 보인다.
이 파드에는 공급자가 둘뿐입니다(AzureExecutionProvider · CPUExecutionProvider). 없는 가속기를 있는 것처럼 다루지 않고, 여기서 관찰할 수 있는 것만 다룹니다. 채점기는 매번 다른 모델·씨앗·지원 목록으로 여러분의 도구를 실제로 실행해 같은 계산을 다시 하고 대조합니다.
단계
1. /root/ep/gen_ep.py 를 만들어 실행해 /root/ep/fp32.onnx 와 /root/ep/int8.onnx 를 만드세요. 층은 다섯 겹 이상입니다.
2. /root/ep/eprun.py 에 providers 를 만들고, 요청 목록별 결과를 /root/ep/providers.json 에 적으세요.
3. partition 을 더해 지원 연산자 목록이 주어졌을 때 그래프가 몇 조각으로 갈릴지 세게 하세요.
4. expose 를 더해 층 경계 텐서를 그래프 출력으로 꺼낸 판을 만들고, /root/ep/fp32_exposed.onnx 와 /root/ep/int8_exposed.onnx 를 저장하세요.
5. layers 를 더해 층 경계마다 최대 절대 오차·상대 오차·최소 코사인을 내게 하세요.
6. growth 를 더해 층을 거듭하며 오차가 자라는 비율을 내게 하고, 여러분 모델의 결과를 /root/ep/growth.json 에 적으세요.
7. settings 를 더해 같은 모델·같은 입력을 설정만 달리해 돌린 결과를 내게 하고, /root/ep/settings.json 에 적으세요.
8. /root/ep/ep_report.md 를 네 절로 쓰세요.
참고
- 파이썬은
/opt/onnx-lab/bin/python입니다. 시스템python3에는 onnxruntime 이 없습니다. - 실행 계약:
/opt/onnx-lab/bin/python /root/ep/eprun.py <명령> <인자...>. 성공하면 종료 코드 0, 파일이 없으면 3, 사용법이 틀리면 2 입니다. 답은 JSON 한 덩어리로 표준출력에 냅니다. EP 목록과 지원 연산자 목록은 쉼표로 이어 인자 하나로 줍니다. - 모델은 입력 이름
x, 입력 모양[None, 특징수]이고 MatMul·Add·Relu 를 다섯 겹 이상 쌓습니다.int8.onnx는quantize_static(quant_format=QDQ)로 만듭니다. providers <model> <목록> [...]응답:{"model", "available", "cases"}. available 은onnxruntime.get_available_providers()그대로입니다. cases 의 각 항목은{"requested", "actual", "missing", "fell_back", "error"}이고, actual 은 세션의get_providers(), missing 은 요청했지만 available 에 없는 이름, fell_back 은 actual 이 requested 와 다른지, error 는 세션을 못 만들었을 때의 예외 이름(아니면 null)입니다.- 없는 공급자를 요청하면 onnxruntime 이 폴백 안내를 표준출력에 찍습니다. 그대로 두면 답인 JSON 에 섞이므로 세션을 만드는 동안 표준출력을 표준오류로 돌려 두거나, 적어도 JSON 이 마지막 줄에 오게 하세요.
providers.json은 위 응답에note한 줄을 더한 것입니다. 있는 공급자만 적은 요청과, 없는 공급자를 섞은 요청을 모두 담으세요.partition <model> <지원연산자목록>응답:{"model","supported","nodes","claimed","partition_count","handoffs","partitions"}. 그래프에 적힌 노드 순서대로 훑으며 지원 목록에 든 연산자는ep, 아니면cpu로 표시하고, 같은 주인이 이어지는 구간을 한 조각으로 묶습니다. partitions 의 각 항목은{"owner","nodes","size"}이고 handoffs 는 조각 수에서 1을 뺀 값(조각이 없으면 0)입니다. 이것은 실제 배치가 아니라 모의입니다.expose <model> <out.onnx>응답:{"model","out","boundaries","outputs"}. 층 경계는 MatMul·Gemm 노드가 받아 드는 첫 입력 텐서를 그래프 순서대로 모으고 맨 뒤에 그래프의 첫 출력을 붙인 것입니다. 이미 출력인 이름은 다시 넣지 않습니다. 정수 판에서 정수 텐서를 float 로 꺼내려 하면 세션이 열리지 않으니, 받아 드는 쪽 텐서를 경계로 잡는 규칙을 지키세요.layers <fp32> <int8> <seed>응답:{"seed","rows","layer_count","layers"}. 입력은numpy.random.default_rng(seed).standard_normal((64, 특징수)).astype(numpy.float32)이고 rows 는 64 입니다. 세션은providers=["CPUExecutionProvider"]로 만들고 다른 설정은 기본값으로 둡니다. layers 의 각 항목은{"index","fp32","int8","max_abs_error","scale","rel_error","cos_min"}이고, scale 은 FP32 쪽 텐서의 최대 절댓값, rel_error 는 최대 절대 오차를 scale 로 나눈 값(scale 이 0이면 0.0), cos_min 은 행마다 구한 코사인의 최솟값(분모가 0이면 그 행은 1.0)입니다. 두 그래프의 경계는 자리 순서로 짝지읍니다.growth <fp32> <int8> <seed>응답:{"seed","layer_count","rel_error","ratios","monotone","total_growth","max_ratio"}. ratios 의 k번째는 k번 층의 상대 오차를 그 앞 층의 것으로 나눈 값이고, 앞 층이 0이면 null 입니다. monotone 은 상대 오차가 한 번도 줄지 않았는지, total_growth 는 마지막을 처음으로 나눈 값(처음이 0이면 null), max_ratio 는 가장 큰 비율의{"index","ratio"}이고 동률이면 앞선 자리입니다.settings <model> <seed>응답:{"model","seed","baseline","cases","all_equal"}. 기준은ORT_DISABLE_ALL에 intra/inter 스레드 1이고, cases 는 (ORT_ENABLE_ALL, 1) · (ORT_DISABLE_ALL, 2) · (ORT_ENABLE_ALL, 2) 세 가지를 이 순서로 담습니다. 각 항목은{"level","intra_op","max_abs_diff","bitwise_equal"}이고 baseline 은{"level","intra_op","max_abs_output","mean_output"}입니다.growth.json과settings.json은 여러분 모델에 대한 위 응답 그대로입니다. 씨앗은 여러분이 고르고 그 값이 파일에 들어갑니다.ep_report.md는## 무엇을 요청했고 무엇을 쥐었나## 그래프가 쪼개지는 자리## 층을 거듭하며 자란 오차## 이 기계에서만 참인 것네 절입니다.- 공식 문서: [Execution Providers](https://onnxruntime.ai/docs/execution-providers/) · [Python API](https://onnxruntime.ai/docs/api/python/api_summary.html) · [그래프 최적화](https://onnxruntime.ai/docs/performance/model-optimizations/graph-optimizations.html) · [스레드 관리](https://onnxruntime.ai/docs/performance/tune-performance/threading.html) · [ONNX Concepts](https://onnx.ai/onnx/intro/concepts.html)
- 흔한 실수: 요청 목록을 그대로 로그에 남기기, 없는 EP 를 넣으면 예외가 날 것이라고 믿기, 정수 판의 중간 텐서를 float 로 꺼내려다 세션이 안 열리는 것, 오차가 층마다 단조롭게 커진다고 가정하기.
- 이 실습은 시간·처리량을 재지 않습니다. 이 맥은 에뮬레이션이라 지연이 두 배까지 흔들립니다.
단계 8개
- 층을 여러 겹 쌓은 모델과 그 정수 판 만들기
- 요청한 것과 쥔 것을 나란히 놓기
- 그래프가 몇 조각으로 갈리는지 세기
- 층 경계 텐서를 꺼내기
- 층 경계마다 오차 재기
- 자라는 비율을 표로 만들기
- 설정만 바꿔 같은 입력을 돌려 보기
- 무엇이 이 기계에서만 참인지 적기