LabHub
学习 学习路径 课程

AI瘦身失败事件

一次送入 32 行却被拒绝 — 救活死掉的批次轴

在 LabHub 中继续学习

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

목표

같은 가중치로 배치 축이 심볼인 /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.pydims 를 만들어 입출력 축이 정수인지 심볼인지 읽어 내게 하세요.
  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 로 보고서를 만드세요.

참고

축 선언만 다른 두 판 만들기

/root/onnxq-shape/build_shapes.py 를 만들어 실행해 /root/onnxq-shape/dyn.onnx/root/onnxq-shape/fixed.onnx 를 만드세요. 가중치는 두 파일이 완전히 같고 0번 축 선언만 다릅니다.

make_tensor_value_info 의 모양 목록에서 0번 자리에 문자열을 넣으면 심볼, 정수를 넣으면 그 값으로 굳습니다. 가중치는 한 번만 만들어 두 그래프에 같은 객체를 넣으세요 — 값이 다르면 뒤에서 두 모델을 견줄 수 없습니다.

축이 열려 있는지 굳어 있는지 읽기

/root/onnxq-shape/axes.pydims <모델> 을 만들어 런타임 입력과 출력의 축을 읽어 내게 하세요. 정수면 정수로, 심볼이면 문자열로, 없으면 null 로 적습니다.

축 하나는 dim_param(심볼) 이나 dim_value(정수) 중 하나를 가지거나 둘 다 없습니다. 셋을 구분해 적으세요. 그리고 initializer 로 채워지는 이름은 런타임 입력이 아니니 inputs 에서 빼세요.

실제로 넣어 보기

feed <모델> <행수...> 를 더해 런타임 입력마다 그 행수로 값을 만들어 넣어 보고 {"status", "error_type", "message", "shapes"} 를 내게 하세요. 거절당해도 종료 코드는 0 입니다.

뒤쪽 축은 선언된 정수를 그대로 쓰고 0번 축만 주어진 행수로 바꿉니다. 거절은 실패가 아니라 답입니다 — 예외를 잡아 클래스 이름과 첫 줄을 적어 두면, 나중에 어느 축에서 무엇을 기대했는지 그 문구가 알려 줍니다. 세션을 여는 단계와 값을 넣는 단계를 따로 감싸세요. 둘은 다른 자리에서 실패합니다.

추론이 채운 것과 못 채운 것

infer <모델> 을 더해 onnx.shape_inference 가 채운 중간 텐서와 채우지 못한 이름을 갈라 내게 하세요. 응답은 {"value_info", "unknown"} 입니다.

infer_shapes 는 새 ModelProto 를 돌려주고 채운 결과는 graph.value_info 에 들어갑니다. unknown 은 노드가 만들지만 그래프 출력은 아닌 이름 가운데 채워지지 않은 것들입니다. 중간 텐서가 아예 없는 그래프에서는 value_info 가 빈 채로 옵니다 — 그것은 실패가 아니라 채울 것이 없었다는 뜻입니다.

같은 이름은 같은 값이다

symbols <모델> 을 더해 심볼 이름마다 그것이 쓰인 자리를 "텐서이름:축번호" 로 모으게 하세요. 그리고 같은 심볼을 쓰는 두 입력에 서로 다른 행수를 넣어 feed 로 거절을 확인하세요.

두 입력의 0번 축에 같은 이름이 적혀 있으면 런타임에 두 행수가 같아야 한다는 뜻입니다. 어기면 거절당하는데, 런타임은 심볼 이름을 짚어 주지 않고 최적화를 거친 뒤의 노드 이름을 댑니다. 그래서 이 표를 미리 만들어 두면 그 오류를 해석할 수 있습니다.

상수 형이 박힌 Reshape 찾기

scan <모델> 을 더해 목표 형이 initializer 이고 그 0번 자리가 양수인 Reshape 를 찾아내게 하세요. 응답은 {"frozen_reshape", "shapes"} 입니다.

0번 자리가 -1 이면 나머지 축에서 계산되므로 축이 살아 있습니다. 0 은 입력의 그 축을 그대로 쓰라는 뜻이라 역시 살아 있습니다. 양수만 골라야 합니다. 목표 형이 initializer 가 아니라 다른 노드의 출력이면 실행할 때 정해지는 것이니 이 검사의 대상이 아닙니다.

죽은 배치 축 되살리기

repair <모델> <출력> 을 더해 박힌 상수 형의 0번 자리를 -1 로 바꾸고, 그래프 출력의 0번 축을 첫 런타임 입력의 심볼 이름으로 다시 선언한 뒤 새 파일로 저장하게 하세요.

initializer 를 제자리에서 바꾸려면 새 텐서를 만들어 CopyFrom 으로 덮어씌우면 됩니다. 출력 축은 dim_value 를 지우고 dim_param 에 이름을 넣습니다. 가중치와 노드는 건드리지 마세요 — 고친 파일이 원래 들어가던 행수에서 같은 값을 내야 고쳤다고 말할 수 있습니다.

두 판을 나란히 놓고 보고하기

/root/onnxq-shape/shape_report.jsondyn_input·fixed_input·trials 를 적고, /root/onnxq-shape/shape_report.md## 어떤 축이 열려 있나 ## 굳은 축에 무엇을 넣었나 ## 추론이 못 채운 곳 ## 보내는 쪽에 요청할 것 네 절로 쓰세요.

trials 는 어느 모델에 몇 행을 넣어 무엇이 나왔는지의 기록입니다. 채점기가 같은 조합을 직접 넣어 보고 대조하니 실제로 재서 적으세요. 보고서 본문에는 배치 축의 심볼 이름을 적어야 받는 쪽이 그 이름으로 이야기할 수 있습니다.