AI 다이어트 실패 사건 · 축 하나가 굳으면 배치가 막힌다 · 실습
32개를 넣었더니 거절당했다 — 죽은 배치 축 되살리기
목표
같은 가중치로 배치 축이 심볼인 /root/onnxq-shape/dyn.onnx 와 1로 굳은 /root/onnxq-shape/fixed.onnx 를 만들고, 축을 읽고 실제로 넣어 보는 도구 /root/onnxq-shape/axes.py 를 만든다. 마지막에는 상수 형이 박혀 배치 축이 죽은 모델을 찾아내 되살린다.
왜 중요한가
ONNX 의 축은 정수로 굳거나, 심볼 이름으로 열려 있거나, 아예 비어 있다. 셋은 전혀 다른 뜻인데 파일을 대충 보면 구분이 안 된다. 배치를 키워 처리량을 재려는 실험이 첫 줄에서 막히는 원인이 대개 여기 있다.
거절이 세션을 열 때가 아니라 값을 넣을 때 일어난다는 점도 중요하다. 세션이 열렸다고 모델 문제가 아니라고 결론 내리면 엉뚱한 곳을 파게 된다.
같은 심볼 이름이 두 입력에 쓰이면 그것은 "둘 다 동적" 이 아니라 "둘의 행수가 서로 같아야 한다" 는 제약이다. 어기면 거절당하는데, 런타임은 심볼 이름을 짚어 주지 않고 최적화를 거친 뒤의 노드 이름을 댄다. 원인과 증상 사이에 한 겹이 끼어 있다.
가장 조용한 사고는 Reshape 에 상수 형이 박힌 경우다. 입력은 심볼로 열려 있는데 그 지점부터 배치 축이 정수로 바뀐다. 오류도 경고도 없고, 모양 추론은 그 상수를 그대로 믿어 앞뒤가 맞는 답을 내놓는다.
채점기는 여러분이 적어 낸 문구를 믿지 않는다. 임시 디렉터리에 채점기가 직접 지은 파일을 차려 놓고 여러분의 도구를 실제로 실행해, 같은 파일을 채점기가 읽고 돌려 얻은 답과 대조한다. 축 이름과 모양, 박아 넣는 행수는 실행마다 바뀐다.
단계
1. /root/onnxq-shape/build_shapes.py 를 만들어 실행해 /root/onnxq-shape/dyn.onnx 와 /root/onnxq-shape/fixed.onnx 를 만드세요.
2. /root/onnxq-shape/axes.py 에 dims 를 만들어 입출력 축이 정수인지 심볼인지 읽어 내게 하세요.
3. feed 를 더해 주어진 행수로 값을 만들어 실제로 넣어 보고 결과나 거절을 적게 하세요.
4. infer 를 더해 onnx.shape_inference 가 채운 것과 못 채운 것을 가르게 하세요.
5. symbols 를 더해 같은 심볼 이름이 어디어디에 쓰였는지 모으게 하세요.
6. scan 을 더해 상수 형이 박힌 Reshape 를 찾아내게 하세요.
7. repair 를 더해 박힌 형의 배치 자리를 되돌리고 출력 축에 심볼 이름을 다시 붙이게 하세요.
8. /root/onnxq-shape/shape_report.json 과 /root/onnxq-shape/shape_report.md 로 보고서를 만드세요.
참고
- 파이썬은 /opt/onnx-lab/bin/python 입니다. 시스템
python3에는 onnx 도 numpy 도 없습니다. 실행 예:/opt/onnx-lab/bin/python /root/onnxq-shape/axes.py dims /root/onnxq-shape/dyn.onnx - 이 파드는 네트워크가 없습니다. 재료는 직접 짓습니다.
- 모델 계약: 두 파일 모두 입력 이름
x, 출력 이름y, 입력의 1번 축은 5, 출력의 1번 축은 3 입니다.dyn.onnx의 0번 축은 심볼 이름이고fixed.onnx의 0번 축은 정수 1 입니다. initializer 이름과 값은 두 파일이 완전히 같아야 합니다 — 축 선언만 다른 같은 모델이라야 비교가 됩니다. - 실행 계약:
axes.py <명령> .... 답은 JSON 한 덩어리로 표준출력에 냅니다. 성공하면 종료 코드 0, 모르는 명령이면 2 입니다. 런타임이 거절하는 것은 실패가 아니라 답 이니, 예외를 잡아 JSON 으로 적고 종료 코드는 0 으로 두세요. dims <모델>응답:{"inputs": {이름: 축목록}, "outputs": {이름: 축목록}}. 축은 고정이면 정수, 심볼이면 문자열, 없으면 null 이고, 모양 선언 자체가 없으면 축목록이 null 입니다. initializer 로 채워지는 이름은 inputs 에 넣지 않습니다.feed <모델> <행수...>는 런타임 입력 순서대로 행수를 하나씩 받습니다. 각 입력은 선언된 1번 축부터를 그대로 쓰고 0번 축만 주어진 행수로 채웁니다(값은 무엇이든 좋습니다). 응답:{"status": "ok"|"error", "error_type": 예외 클래스 이름 또는 null, "message": 문자열, "shapes": 출력 모양 목록 또는 null}.infer <모델>응답:{"value_info": {중간 텐서 이름: 축목록}, "unknown": [이름...]}. unknown 은 노드가 만들어 내지만 그래프 출력은 아닌 이름 가운데 추론이 모양을 채우지 못한 것들을 정렬한 목록입니다.symbols <모델>응답:{심볼 이름: ["텐서이름:축번호", ...]}. 런타임 입력과 그래프 출력에서 모읍니다.scan <모델>응답:{"frozen_reshape": [노드 이름 정렬], "shapes": {노드 이름: 상수 형}}. 목표 형이 initializer 이고 그 0번 자리가 양수인 Reshape 만 담습니다. 0번 자리가 -1 이거나 0 이면 축이 살아 있는 것이니 담지 않습니다. 노드 이름이 비어 있으면 그 노드의 첫 출력 이름을 씁니다.repair <모델> <출력>은 담긴 Reshape 의 상수 형에서 0번 자리를 -1 로 바꾸고, 그래프 출력의 0번 축을 첫 런타임 입력의 0번 축 심볼로 다시 선언한 뒤 새 파일로 저장합니다. 가중치와 노드는 건드리지 않습니다. 응답은{"out", "fixed", "batch_symbol"}입니다.- 6단계와 7단계는 현상을 눈으로 보려면 사고가 든 파일이 하나 필요합니다. /root/onnxq-shape/make_frozen.py 로 /root/onnxq-shape/frozen.onnx 를 지어
scan과feed를 걸어 보고, 고친 결과는 /root/onnxq-shape/healed.onnx 로 두세요. 채점기는 이 파일들을 읽지 않고 자기 파일을 따로 지어 여러분의 도구를 돌립니다. shape_report.json에는dyn_input·fixed_input(각각 입력의 축목록)과trials(항목마다model·rows·status, 4건 이상)를 적습니다. dyn.onnx 가 받아들인 기록과 fixed.onnx 가 거절한 기록이 모두 있어야 합니다.shape_report.md는## 어떤 축이 열려 있나## 굳은 축에 무엇을 넣었나## 추론이 못 채운 곳## 보내는 쪽에 요청할 것네 절로 쓰고, 배치 축의 심볼 이름을 본문에 적습니다.- 공식 문서: [ONNX Concepts](https://onnx.ai/onnx/intro/concepts.html) · [onnx.shape_inference](https://onnx.ai/onnx/api/shape_inference.html) · [ONNX IR](https://onnx.ai/onnx/repo-docs/IR.html) · [ORT Python API](https://onnxruntime.ai/docs/api/python/api_summary.html)
- 흔한 실수: 세션이 열렸으니 모양도 맞다고 보기, 거절을 예외로 터뜨려 기록을 남기지 않기,
value_info가 비었다고 추론이 실패했다고 보기, Reshape 의 배치 자리에 실제 행수를 적기.
단계 8개
- 축 선언만 다른 두 판 만들기
- 축이 열려 있는지 굳어 있는지 읽기
- 실제로 넣어 보기
- 추론이 채운 것과 못 채운 것
- 같은 이름은 같은 값이다
- 상수 형이 박힌 Reshape 찾기
- 죽은 배치 축 되살리기
- 두 판을 나란히 놓고 보고하기