LabHub
배우기 러닝패스 코스

The AI Diet Gone Wrong

Count What You Actually Got: Execution Providers and Error Accumulation

LabHub 에서 이어서 보기

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

목표

요청한 실행 공급자와 세션이 실제로 쥔 공급자를 재고, 지원 연산자 목록에 따라 그래프가 몇 조각으로 갈리는지 세고, 층 경계마다 양자화 오차가 어떻게 자라는지 표로 만드는 도구 eprun.py 를 만든다.

왜 중요한가

실행 공급자는 요청하는 것이지 보장받는 것이 아니다. 이 환경에서 직접 재어 보면, 없는 공급자를 목록에 넣어도 예외가 나지 않고 그대로 CPU 로 떨어져 돌아간다. 그래서 "가속기를 쓰고 있다" 는 착각이 로그에 아무 흔적도 남기지 않은 채 오래 간다. 확인하는 길은 하나뿐이다 — 세션에 물어본다. 공급자는 모델 전체를 맡는 것이 아니라 노드마다 맡을 수 있는 것만 가져간다. 그래서 지원 목록이 그래프 순서 위에서 끊기면 모델이 여러 조각으로 갈리고 조각 사이마다 값을 넘긴다. 조각이 다르게 갈리면 계산 순서가 달라지고, 같은 모델에 같은 입력을 넣어도 기계마다 다른 수가 나올 수 있다. 오차 쪽도 같은 태도가 필요하다. 양자화 오차는 층마다 새로 생기고 앞 층의 오차가 다음 층으로 들어간다. 다만 층마다 고르게 자라지는 않으므로, 이야기로 넘기지 말고 층 경계마다 재서 표로 만들어야 어느 층이 문제인지 보인다. 이 파드에는 공급자가 둘뿐입니다(AzureExecutionProvider · CPUExecutionProvider). 없는 가속기를 있는 것처럼 다루지 않고, 여기서 관찰할 수 있는 것만 다룹니다. 채점기는 매번 다른 모델·씨앗·지원 목록으로 여러분의 도구를 실제로 실행해 같은 계산을 다시 하고 대조합니다.

단계

  1. /root/ep/gen_ep.py 를 만들어 실행해 /root/ep/fp32.onnx/root/ep/int8.onnx 를 만드세요. 층은 다섯 겹 이상입니다.
  2. /root/ep/eprun.pyproviders 를 만들고, 요청 목록별 결과를 /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 를 네 절로 쓰세요.

참고

층을 여러 겹 쌓은 모델과 그 정수 판 만들기

/root/ep/gen_ep.py 를 만들어 실행해 /root/ep/fp32.onnx/root/ep/int8.onnx 를 만드세요. 선형 연산자가 다섯 개 이상이어야 합니다.

층이 둘이나 셋이면 오차가 자라는 모습이 안 보입니다. 다섯 겹 이상 쌓으세요. 정적 양자화에는 CalibrationDataReader 가 필요하고, 보정 자료는 시험 입력과 같은 분포에서 뽑되 같은 표본일 필요는 없습니다.

요청한 것과 쥔 것을 나란히 놓기

/root/ep/eprun.pyproviders <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## 무엇을 요청했고 무엇을 쥐었나 ## 그래프가 쪼개지는 자리 ## 층을 거듭하며 자란 오차 ## 이 기계에서만 참인 것 네 절로 쓰세요. 쓸 수 있는 공급자 수와 전체 오차 증가 배수가 숫자로 들어가야 합니다.

마지막 절이 이 보고서의 핵심입니다. 여기서 잰 값 가운데 어떤 것이 이 파드의 공급자 구성과 이 설정에 묶여 있는지 적으세요. 시간을 재지 않았다는 것도 적습니다.