LabHub
배우기 러닝패스 코스

AI 다이어트 실패 사건 · 실행 공급자와 오차 누적 · 이론

요청한 공급자와 실제로 쥔 공급자는 다르다

LabHub 에서 이어서 보기

한 줄 요약

실행 공급자(EP)는 요청하는 것이지 보장받는 것이 아니다. 없는 EP 를 넣어도 오류가 아니라 조용한 폴백이고, 세션에 물어봐야 무엇을 쥐었는지 알 수 있다.

왜 이게 문제가 되나

가속기를 쓰겠다고 목록에 적었는데 실제로는 CPU 로 돌고 있는 일이 자주 생긴다. 문제는 그때 아무 일도 안 일어난다는 것이다. 예외도 안 나고 종료 코드도 0이다. 경고 한 줄이 표준오류로 지나갈 뿐이고, 로그를 모으는 자리에서는 그 한 줄이 묻힌다.

이 실습 환경에서 직접 재어 보면 이렇다. 있지도 않은 CUDAExecutionProvider 를 목록 맨 앞에 넣어도 세션은 만들어지고 추론도 된다. session.get_providers() 를 물어보면 CPUExecutionProvider 하나만 돌아온다. 아예 없는 이름을 지어내 넣으면 "Unknown Provider Type" 이라는 안내가 뜨고 역시 CPU 로 떨어져 돌아간다.

그래서 규칙은 하나다. 요청 목록을 믿지 말고 세션에 물어본다. 배포 로그에 남겨야 하는 것은 요청한 목록이 아니라 세션이 돌려준 목록이다.

EP 는 무엇을 정하나

EP 는 "이 모델을 어디서 돌릴까" 를 고르는 장치가 아니라 노드마다 누가 맡을지를 정하는 장치다. 실행기는 요청 목록을 순서대로 훑으며 각 EP 에게 "이 노드 맡을 수 있나" 를 묻고, 맡겠다는 노드만 가져간다. 남은 노드는 다음 EP 로, 끝내 아무도 안 맡으면 CPU 가 맡는다.

그래서 한 모델이 여러 조각으로 갈려 서로 다른 곳에서 돈다. 조각과 조각 사이에서는 값을 넘겨야 한다. 지원하는 연산자 목록이 그래프 순서 위에서 자꾸 끊기면 조각 수가 늘고, 넘기는 횟수도 늘어난다.

지원: MatMul 만               조각 5개 · 넘김 4번  [MatMul] -> Add -> Relu -> [MatMul] -> Add    EP        CPU    CPU        EP       CPU

목록의 순서가 곧 우선순위다. 앞에 적은 공급자가 먼저 묻고, 맡겠다고 한 노드를 가져간다. 그래서 같은 두 공급자를 순서만 바꿔 적어도 배치가 달라질 수 있다. 그리고 CPU 는 적지 않아도 마지막에 따라붙는다 — 이 환경에서 AzureExecutionProvider 하나만 적고 세션을 만들면 실제 목록은 그것과 CPUExecutionProvider 둘이 된다. 맡을 사람이 아무도 없는 노드를 남겨 둘 수는 없기 때문이다. 요청한 목록과 실제 목록이 다른 것이 오류가 아니라 정상 동작인 까닭도 여기에 있다.

여기서 중요한 결론이 하나 나온다. 같은 모델에 같은 입력을 넣어도 기계마다 다른 수가 나올 수 있다. 조각이 다르게 갈리면 계산 순서가 달라지고, 부동소수점 덧셈은 순서가 바뀌면 결과가 미세하게 달라진다. 그러니 "우리 노트북에서는 맞았다" 는 근거가 되기 어렵다.

층을 거듭하면 오차는 어떻게 되나

양자화 오차는 층마다 새로 생기고, 앞 층에서 생긴 오차는 다음 층의 입력으로 들어간다. 그래서 오차는 쌓인다. 다만 실제로 재어 보면 층마다 고르게 자라지는 않는다. 이 실습에서 다섯 겹짜리 모델을 재었을 때 층 경계의 상대 오차는 0.099, 0.074, 0.063, 0.214, 0.167, 0.369 이었다. 줄었다 늘었다 하면서 전체로는 3.7배가 되었다.

줄어드는 구간이 있는 이유는 층마다 값의 크기가 달라지고, 활성 함수가 일부를 잘라 내고, 스케일이 새로 잡히기 때문이다. 그러니 "층마다 조금씩 커진다" 는 이야기로 끝내면 안 되고, 층 경계마다 재서 표로 만들어야 어디가 문제인지 보인다. 어느 한 층에서 비율이 세 배로 뛴다면 그 층이 범위를 좁힐 후보다.

오차가 자란다고 해서 정확도가 그만큼 무너지는 것도 아니다. 마지막 경계의 상대 오차가 0.37 이어도 결정이 바뀌는 표본은 몇 안 될 수 있다. 출력 벡터가 통째로 조금씩 밀리는 것과 순위가 뒤집히는 것은 다른 일이기 때문이다. 그러니 층별 오차 표는 어디를 손볼지 고르는 데 쓰고, 배포 여부는 과제 지표로 따로 판단한다. 두 가지를 한 숫자로 합치려 들면 둘 다 흐려진다.

층 경계를 재려면 중간 텐서를 볼 수 있어야 한다. ONNX 그래프에서는 보고 싶은 텐서를 그래프 출력 목록에 덧붙이면 된다. 다만 정수 판에서는 정수 텐서를 float 로 꺼낼 수 없으니, 선형 연산자가 받아 드는 쪽 텐서를 경계로 잡는 것이 안전하다. 그 자리는 양쪽 그래프 모두 float 이다.

현장에서 해야 하는 일

다음 실습에서 할 것

있는 EP 와 없는 EP 를 섞어 요청해 무엇을 쥐게 되는지 직접 재고, 지원 연산자 목록이 주어졌을 때 그래프가 몇 조각으로 갈릴지 세는 모의를 만든다. 그다음 층 경계 텐서를 그래프 출력으로 꺼내 FP32 판과 정수 판을 같은 입력으로 돌려 층마다 오차를 재고, 자라는 비율을 표로 만든다. 마지막으로 같은 모델·같은 입력을 최적화 수준과 스레드 수만 바꿔 돌려 결과가 달라지는지 본다. 채점기는 매번 다른 모델·씨앗·지원 목록으로 여러분의 도구를 실제로 돌리고 같은 계산을 다시 해 대조한다.