数清你真正拿到的 — 执行提供程序与误差累积
한국어 원문으로 표시합니다.
목표
요청한 실행 공급자와 세션이 실제로 쥔 공급자를 재고, 지원 연산자 목록에 따라 그래프가 몇 조각으로 갈리는지 세고, 층 경계마다 양자화 오차가 어떻게 자라는지 표로 만드는 도구 eprun.py 를 만든다.
왜 중요한가
실행 공급자는 요청하는 것이지 보장받는 것이 아니다. 이 환경에서 직접 재어 보면, 없는 공급자를 목록에 넣어도 예외가 나지 않고 그대로 CPU 로 떨어져 돌아간다. 그래서 "가속기를 쓰고 있다" 는 착각이 로그에 아무 흔적도 남기지 않은 채 오래 간다. 확인하는 길은 하나뿐이다 — 세션에 물어본다. 공급자는 모델 전체를 맡는 것이 아니라 노드마다 맡을 수 있는 것만 가져간다. 그래서 지원 목록이 그래프 순서 위에서 끊기면 모델이 여러 조각으로 갈리고 조각 사이마다 값을 넘긴다. 조각이 다르게 갈리면 계산 순서가 달라지고, 같은 모델에 같은 입력을 넣어도 기계마다 다른 수가 나올 수 있다. 오차 쪽도 같은 태도가 필요하다. 양자화 오차는 층마다 새로 생기고 앞 층의 오차가 다음 층으로 들어간다. 다만 층마다 고르게 자라지는 않으므로, 이야기로 넘기지 말고 층 경계마다 재서 표로 만들어야 어느 층이 문제인지 보인다. 이 파드에는 공급자가 둘뿐입니다(AzureExecutionProvider · CPUExecutionProvider). 없는 가속기를 있는 것처럼 다루지 않고, 여기서 관찰할 수 있는 것만 다룹니다. 채점기는 매번 다른 모델·씨앗·지원 목록으로 여러분의 도구를 실제로 실행해 같은 계산을 다시 하고 대조합니다.
단계
- /root/ep/gen_ep.py 를 만들어 실행해 /root/ep/fp32.onnx 와 /root/ep/int8.onnx 를 만드세요. 층은 다섯 겹 이상입니다.
- /root/ep/eprun.py 에
providers를 만들고, 요청 목록별 결과를 /root/ep/providers.json 에 적으세요. partition을 더해 지원 연산자 목록이 주어졌을 때 그래프가 몇 조각으로 갈릴지 세게 하세요.expose를 더해 층 경계 텐서를 그래프 출력으로 꺼낸 판을 만들고, /root/ep/fp32_exposed.onnx 와 /root/ep/int8_exposed.onnx 를 저장하세요.layers를 더해 층 경계마다 최대 절대 오차·상대 오차·최소 코사인을 내게 하세요.growth를 더해 층을 거듭하며 오차가 자라는 비율을 내게 하고, 여러분 모델의 결과를 /root/ep/growth.json 에 적으세요.settings를 더해 같은 모델·같은 입력을 설정만 달리해 돌린 결과를 내게 하고, /root/ep/settings.json 에 적으세요.- /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 · Python API · 그래프 최적화 · 스레드 관리 · ONNX Concepts
- 흔한 실수: 요청 목록을 그대로 로그에 남기기, 없는 EP 를 넣으면 예외가 날 것이라고 믿기, 정수 판의 중간 텐서를 float 로 꺼내려다 세션이 안 열리는 것, 오차가 층마다 단조롭게 커진다고 가정하기.
- 이 실습은 시간·처리량을 재지 않습니다. 이 맥은 에뮬레이션이라 지연이 두 배까지 흔들립니다.
층을 여러 겹 쌓은 모델과 그 정수 판 만들기
/root/ep/gen_ep.py 를 만들어 실행해 /root/ep/fp32.onnx 와 /root/ep/int8.onnx 를 만드세요. 선형 연산자가 다섯 개 이상이어야 합니다.
층이 둘이나 셋이면 오차가 자라는 모습이 안 보입니다. 다섯 겹 이상 쌓으세요. 정적 양자화에는 CalibrationDataReader 가 필요하고, 보정 자료는 시험 입력과 같은 분포에서 뽑되 같은 표본일 필요는 없습니다.
요청한 것과 쥔 것을 나란히 놓기
/root/ep/eprun.py 에 providers <model> <목록> [...] 을 만들고, 있는 공급자만 적은 요청과 없는 공급자를 섞은 요청의 결과를 /root/ep/providers.json 에 적으세요.
없는 공급자를 넣으면 예외가 날 것 같지만 이 환경에서는 그렇지 않습니다. 직접 넣어 보고 무슨 일이 일어나는지 재세요. session.get_providers() 가 답이고, 요청 목록은 답이 아닙니다. 혹시 예외를 내는 판이 있을 수 있으니 예외 이름도 담아 두세요. 그리고 폴백 안내가 표준출력으로 나오니 답이 섞이지 않게 막으세요 — contextlib.redirect_stdout 이 쓸 만합니다.
그래프가 몇 조각으로 갈리는지 세기
partition <model> <지원연산자목록> 을 더해 그 연산자만 지원하는 공급자가 있다면 그래프가 어떻게 쪼개질지 모의하게 하세요. 조각 수와 넘김 수를 함께 냅니다.
실제 배치를 흉내 내는 것이 아니라, 지원 목록이 그래프 순서 위에서 끊기는 자리를 세는 것입니다. 같은 주인이 이어지는 구간이 한 조각입니다. 정수 판에 MatMul 만 지원한다고 두면 Q/DQ 때문에 조각이 얼마나 늘어나는지 바로 보입니다.
층 경계 텐서를 꺼내기
expose <model> <out.onnx> 를 더해 층 경계 텐서를 그래프 출력으로 덧붙인 판을 저장하게 하고, /root/ep/fp32_exposed.onnx 와 /root/ep/int8_exposed.onnx 를 만드세요.
정수 판에서 QuantizeLinear 의 출력은 int8 입니다. 그것을 float 로 꺼내겠다고 적으면 세션이 열리지 않습니다. 선형 연산자가 받아 드는 쪽 텐서는 양쪽 그래프 모두 float 이라 안전합니다. 저장한 판을 실제로 열어 돌려 보고 넘어가세요.
층 경계마다 오차 재기
layers <fp32> <int8> <seed> 를 더해 씨앗으로 만든 같은 입력을 두 판에 넣고 층 경계마다 최대 절대 오차·크기·상대 오차·최소 코사인을 내게 하세요.
절대 오차만 보면 뒤 층이 항상 나빠 보입니다. 값 자체가 커지기 때문입니다. 그래서 그 층 텐서의 최대 절댓값으로 나눈 상대 오차를 함께 냅니다. 두 그래프의 경계 이름은 다르니 자리 순서로 짝지으세요.
자라는 비율을 표로 만들기
growth <fp32> <int8> <seed> 를 더해 층별 상대 오차와 그 비율, 단조 여부, 전체 증가 배수, 가장 크게 뛴 층을 내게 하고, 여러분 모델의 결과를 /root/ep/growth.json 에 적으세요.
재어 보면 비율이 1보다 작은 구간도 나옵니다. 활성 함수가 일부를 잘라 내고 층마다 스케일이 새로 잡히기 때문입니다. 단조롭지 않다는 것 자체가 결과이니 그대로 적으세요. 앞 층의 상대 오차가 0이면 나눌 수 없으니 null 로 둡니다.
설정만 바꿔 같은 입력을 돌려 보기
settings <model> <seed> 를 더해 같은 모델·같은 입력을 최적화 수준과 스레드 수만 바꿔 돌리고 기준과의 차이를 내게 하세요. 여러분 모델의 결과를 /root/ep/settings.json 에 적으세요.
이 파드에서는 네 경우가 모두 같은 값을 냈습니다. 그 결과를 그대로 적으세요 — 달라야 한다고 꾸며 내지 않습니다. 중요한 것은 같았다는 사실이 아니라, 같은지 다른지를 재는 절차를 갖추었다는 것입니다. 다른 기계에서는 다른 답이 나올 수 있습니다.
무엇이 이 기계에서만 참인지 적기
/root/ep/ep_report.md 를 ## 무엇을 요청했고 무엇을 쥐었나 ## 그래프가 쪼개지는 자리 ## 층을 거듭하며 자란 오차 ## 이 기계에서만 참인 것 네 절로 쓰세요. 쓸 수 있는 공급자 수와 전체 오차 증가 배수가 숫자로 들어가야 합니다.
마지막 절이 이 보고서의 핵심입니다. 여기서 잰 값 가운데 어떤 것이 이 파드의 공급자 구성과 이 설정에 묶여 있는지 적으세요. 시간을 재지 않았다는 것도 적습니다.