What Does This File Promise? Reading What the Export Froze
한국어 원문으로 표시합니다.
목표
onnx.helper 로 MLP 를 직접 지어 /root/onnxq-export/mlp.onnx 를 만들고, ONNX 파일에서 굳어 있는 것들을 읽어 내는 도구 /root/onnxq-export/modelmeta.py 를 만든다. 판 번호를 갈아 끼우며 onnx.checker 와 onnxruntime 에 차례로 물어, 검사기는 통과하는데 런타임이 거절하는 자리를 직접 찾아 문서로 남긴다.
왜 중요한가
ONNX 파일은 그래프만 담지 않는다. 어느 연산자 집합 판으로 썼는지, IR 버전이 몇인지, 누가 만들었는지, 무엇이 입력이고 무엇이 이미 정해진 가중치인지가 내보내는 순간 함께 굳는다. 받는 쪽은 그 결정을 바꿀 수 없고, 바꾸려면 다시 내보내야 한다.
그래서 "우리 서버에서 안 열린다" 는 신고의 원인은 대개 변환 옵션이 아니라 파일 첫머리에 있다. 판 번호를 낮춰 다시 저장해도 연산자 정의가 따라 내려가지 않기 때문에, 검사기는 통과시키고 런타임만 거절하는 파일이 만들어진다. 오류 문구가 "판이 낮다" 가 아니라 "이 연산자의 구현이 없다" 로 나와서 원인이 더 안 보인다.
onnx.checker 도 한 겹이 아니다. 기본 검사는 구조만 보고, full_check=True 를 주어야 모양 추론까지 돈다. 곱셈이 성립하지 않는 MatMul 은 기본 검사를 그냥 통과한다. 그리고 두 검사 모두 등록되지 않은 도메인의 연산자를 막지 못한다.
채점기는 여러분이 적어 낸 문구를 믿지 않는다. 임시 디렉터리에 채점기가 직접 지은 ONNX 파일을 차려 놓고 여러분의 도구를 실제로 실행해, 같은 파일을 채점기가 읽어 얻은 답과 대조한다. 모양·이름·판 번호·활성 함수는 실행마다 바뀐다.
단계
- /root/onnxq-export/build_mlp.py 를 만들어 실행해 /root/onnxq-export/mlp.onnx 를 만드세요.
- /root/onnxq-export/modelmeta.py 에
info를 만들어 판 번호·producer·입출력·initializer·노드를 읽어 내게 하세요. info에input_overrides와runtime_inputs를 더해 initializer 와 입력의 경계를 드러내게 하세요.check를 더해onnx.checker를 기본과full_check=True두 번 돌리고 판정을 따로 적게 하세요.load를 더해 onnxruntime 이 세션을 여는지, 못 열면 어떤 예외인지 적게 하세요.stamp를 더해 연산자를 그대로 두고 판 번호만 갈아 다시 저장하게 하세요.scan을 더해 이 런타임이 여는 판의 범위를 실제로 재게 하고, 자기 모델의 범위를 /root/onnxq-export/opset_range.json 에 적으세요.- /root/onnxq-export/export_report.json 과 /root/onnxq-export/export_report.md 로 인수인계 문서를 만드세요.
참고
- 파이썬은 /opt/onnx-lab/bin/python 입니다. 시스템
python3에는 onnx 도 numpy 도 없습니다. 실행 예:/opt/onnx-lab/bin/python /root/onnxq-export/modelmeta.py info /root/onnxq-export/mlp.onnx - 이 파드는 네트워크가 없습니다. 설치는 되지 않고, 이미 받아 둔 모델도 없습니다. 재료는 직접 짓습니다.
- 모델 계약: 입력
x는 FLOAT 이고 축은 2개, 0번 축은 심볼 이름(문자열), 1번 축은 8 입니다. 출력y는 FLOAT 이고 1번 축은 4 입니다. 노드에는 MatMul·Add·Relu 가 들어가고, 가중치는 initializer 2개 이상으로 굳힙니다.producer_name은 비우지 않습니다. opset 은 7 부터 26 사이, ir_version 은 13 이하로 둡니다. - 실행 계약:
modelmeta.py <명령> .... 답은 JSON 한 덩어리로 표준출력에 냅니다. 성공하면 종료 코드 0, 모르는 명령이면 2 입니다. onnxruntime 이 표준오류에 찍는 경고는 답이 아니니 표준출력만 깨끗하게 유지하세요. info <모델>응답:ir_version정수,producer_name문자열,opsets는 도메인을 키로 한 객체,inputs·outputs는{"name", "elem_type", "dims"}목록,initializers는 이름을 정렬한 목록,nodes는 op_type 목록입니다.dims의 각 축은 고정이면 정수, 심볼이면 문자열, 아무것도 없으면 null 입니다.elem_type은onnx.TensorProto.DataType.Name(...)이 주는 이름(예: FLOAT)입니다.- 3단계부터
info응답에input_overrides(graph.input 과 initializer 양쪽에 있는 이름을 정렬한 목록)와runtime_inputs(세션이 실제로 달라고 하는 입력 이름, 세션을 못 열면 null)가 더해집니다.inputs에는 initializer 로 채워지는 이름을 넣지 않습니다. check <모델>응답:{"checker": "ok"|"error", "full_check": "ok"|"error", "message": 문자열}. 실패한 경우 message 에는 잡힌 예외의 첫 줄을 그대로 싣습니다.load <모델>응답:{"load": "ok"|"error", "error_type": 예외 클래스 이름 또는 null, "message": 문자열}.stamp <모델> <opset> <ir> <출력>은 기본 도메인의 opset 과 ir_version 만 바꿔 다른 파일로 저장합니다. 노드·가중치·producer_name 은 건드리지 않습니다. 응답은{"out", "opset", "ir_version", "nodes"}입니다.scan <모델>응답:{"min_ok": 정수 또는 null, "max_ok": 정수 또는 null, "ok": 정수 목록, "failed": 정수 목록}. 판 번호 1 부터 27 까지 갈아 끼우며 세션이 열리는지만 봅니다(검사기 결과가 아니라 세션 열기로 판정합니다).opset_range.json에는 최소한min_ok와max_ok를 적습니다.export_report.json에는model·ir_version·producer_name·opset·nodes·initializers·runtime_inputs·min_ok_opset·max_ok_opset과, 검사기는 통과하는데 런타임이 거절하는 판을 담은checker_ok_runtime_error객체(opset·checker·load)를 적습니다.export_report.md는## 무엇을 내보냈나## 판이 굳는 자리## 검사기가 못 잡는 것## 다음 사람에게 넘길 것네 절로 쓰고, 열리는 판의 하한과 상한을 숫자로 적습니다.- 공식 문서: ONNX Concepts · ONNX Versioning · ONNX IR · ORT Compatibility · ORT Python API
- 흔한 실수:
graph.input을 세어 입력 개수라고 말하기, 기본 검사만 돌리고 통과라고 보고하기, 판 번호를 낮추면 연산자도 따라 내려간다고 믿기, 런타임 경고를 표준출력에 섞어 JSON 을 깨뜨리기.
모델을 직접 짓기
/root/onnxq-export/build_mlp.py 를 만들어 실행해 /root/onnxq-export/mlp.onnx 를 만드세요. 입력 x 는 [심볼, 8], 출력 y 는 [심볼, 4] 이고, MatMul·Add·Relu 로 두 층을 쌓습니다. 가중치는 initializer 로 굳힙니다.
helper.make_tensor_value_info 의 모양 목록에 문자열을 넣으면 그 축은 심볼 이름이 되고, 정수를 넣으면 고정됩니다. 가중치는 numpy_helper.from_array(배열, 이름) 으로 만들어 make_graph 의 다섯 번째 인자에 넣습니다. 그 이름을 노드의 입력으로 쓰되 make_graph 의 입력 목록에는 넣지 마세요. 저장하기 전에 onnx.checker.check_model(model, full_check=True) 로 한 번 걸러 보세요.
파일에 굳어 있는 것들 읽어 내기
/root/onnxq-export/modelmeta.py 에 info <모델> 을 만들어 ir_version·producer_name·opsets·inputs·outputs·initializers·nodes 를 JSON 으로 내게 하세요.
onnx.load(path) 가 ModelProto 를 줍니다. model.opset_import 는 도메인과 판 번호의 목록이고, 기본 도메인은 빈 문자열입니다. 축은 dim_param 이 있으면 심볼, dim_value 가 있으면 정수, 둘 다 없으면 null 로 적습니다. inputs 에는 initializer 이름을 빼세요 — 그 값은 파일 안에 이미 들어 있습니다.
가중치는 입력이 아니다
info 응답에 input_overrides(graph.input 과 initializer 양쪽에 있는 이름)와 runtime_inputs(세션이 실제로 달라고 하는 입력 이름)를 더하세요. 세션을 못 열면 runtime_inputs 는 null 입니다.
IR 4 이전에는 initializer 를 반드시 graph.input 에도 선언해야 했습니다. 그래서 낡은 도구가 만든 파일에는 가중치가 입력 목록에 함께 적혀 있고, 그 이름은 '기본값이 있는 입력' 이라는 뜻입니다. 런타임에게 물어보면 그 이름을 필수 입력으로 요구하지 않는다는 것을 바로 볼 수 있습니다. 세션을 여는 코드는 예외를 삼키고 null 을 돌려주세요 — 열리지 않는 파일도 info 로는 읽을 수 있어야 합니다.
검사기는 한 겹이 아니다
check <모델> 을 더해 onnx.checker 를 기본과 full_check=True 로 각각 돌리고 {"checker", "full_check", "message"} 를 내게 하세요. 실패했으면 잡힌 예외의 첫 줄을 그대로 message 에 실으세요.
기본 검사는 구조만 봅니다. full_check=True 를 주면 모양 추론까지 돌아서, 곱셈이 성립하지 않는 MatMul 이나 선언한 출력 모양과 추론된 모양이 다른 경우를 잡습니다. 예외 문구를 지어내지 말고 str(exc) 의 첫 줄을 그대로 옮기세요 — 채점기가 자기가 심어 둔 이름이 그 안에 있는지 봅니다.
런타임에게 직접 물어보기
load <모델> 을 더해 onnxruntime 세션을 열어 보고 {"load", "error_type", "message"} 를 내게 하세요. error_type 은 잡힌 예외의 클래스 이름이고, 열렸으면 null 입니다.
검사기를 통과한 파일도 런타임은 거절할 수 있습니다. 등록되지 않은 도메인의 연산자, 판 번호가 너무 낮아 그 판에 정의가 없는 연산자, 런타임이 아직 열지 않는 높은 판이 그렇습니다. 거절의 종류가 서로 달라서 예외 클래스 이름까지 적어 두면 다음 사람이 원인을 바로 가릅니다.
판 번호만 갈아 끼우기
stamp <모델> <opset> <ir> <출력> 을 더해 기본 도메인의 opset 과 ir_version 만 바꿔 다른 파일로 저장하게 하세요. 노드·가중치·producer_name 은 그대로 두어야 합니다.
model.opset_import 를 돌며 도메인이 빈 문자열인 항목의 version 을 바꾸고, 없으면 새로 붙입니다. 이것은 변환이 아니라 도장 찍기입니다 — 연산자의 정의가 따라 내려가지도, 올라가지도 않습니다. 그 사실을 다음 단계에서 눈으로 보게 됩니다.
이 런타임이 여는 판의 범위 재기
scan <모델> 을 더해 판 번호 1 부터 27 까지 갈아 끼우며 세션이 열리는지 재고 {"min_ok", "max_ok", "ok", "failed"} 를 내게 하세요. 그리고 자기 모델의 범위를 /root/onnxq-export/opset_range.json 에 min_ok·max_ok 로 적으세요.
앞 단계의 stamp 와 load 를 그대로 이어 붙이면 됩니다. 임시 디렉터리에 찍어 내고 열어 보기만 하면 되니 원본은 건드리지 마세요. 하한은 모델이 쓰는 연산자가 어느 판부터 정의되었는지에 따라 달라지고, 상한은 런타임이 어디까지 여는지에 따라 달라집니다. 두 숫자의 출처가 다르다는 점이 이 단계의 핵심입니다.
인수인계 한 장으로 남기기
/root/onnxq-export/export_report.json 에 모델에서 읽어 낸 값과 판 범위, 그리고 검사기는 통과하는데 런타임이 거절하는 판을 담은 checker_ok_runtime_error 를 적고, /root/onnxq-export/export_report.md 를 네 절로 쓰세요.
checker_ok_runtime_error 의 opset 은 열리는 하한보다 낮은 판이면 됩니다. 그 판으로 찍은 파일을 check 와 load 에 각각 물어 실제로 나온 판정을 적으세요 — 채점기도 같은 파일을 만들어 다시 확인합니다. 보고서에는 하한과 상한을 숫자로 적어야 받는 쪽이 자기 런타임과 견줄 수 있습니다.