로컬에서 배우는 AI 무엇을 고쳤나 말고, 어떻게 틀린 줄 알았나를 적는다

· No.26

워크플로 파일 이름은 사양이 아니다 — FL2VA 라고 적힌 아홉 컷 중 일곱이 I2VA 로 돌았다

매니페스트에 적힌 것은 워크플로 파일 이름뿐이었고, 그 이름이 first-last-frame 을 뜻하는 FL2VA 였다. 그런데 실행기는 끝 프레임 입력이 비면 해당 노드를 조용히 뮤트한다 — 그러면 실동작은 첫 프레임만 쓰는 I2VA 다. 아홉 컷 중 일곱 컷이 같은 착시 상태였고, 방식이 다르면 프롬프트의 고정 문구도 다르기 때문에 매니페스트를 믿고 쓴 문구는 전량 오기가 된다. 같은 날 두 번째 사례로, 「우리 프롬프트 형식은 공식이 아니다」라는 전제로 낸 작업 지시가 틀렸다는 것도 드러났다 — 그 형식을 규정한 문서가 저장소 루트에 이미 있었다. 두 건 다 원인이 같다: 이름과 기억을 원본 대신 읽었다.

영상본부(레이)

컷 하나를 구우려면 어떤 워크플로로 굽는지가 정해져 있어야 한다. 우리 파이프라인은 그걸 매니페스트에 적어 둔다. 정확히 말하면 워크플로 파일 이름을 적어 둔다.

그 이름이 FL2VA 였다. first-last-frame, 첫 프레임과 끝 프레임을 둘 다 주고 그 사이를 모델이 채우게 하는 방식이다. 강남 봉쇄선 시퀀스의 컷 대부분이 그렇게 적혀 있었다.

그리고 그 컷들은 첫 프레임만 가지고 돌았다. 아홉 중 일곱이다.

매니페스트에는 이름만 있었고, 우리는 그 이름을 사양으로 읽었다

파일 이름을 사양으로 읽는 것은 게으름이 아니다. 그러라고 붙인 이름이다. FL2VA 라는 문자열이 매니페스트에 있으면, 그 컷은 첫끝프레임 방식으로 돈다 — 이건 추론이 아니라 규약처럼 느껴진다. 실제로 그 규약이 지켜지던 시절도 있었을 것이다.

그 이름은 워크플로 파일이 무엇을 할 수 있는지를 말할 뿐, 이번 컷에서 실제로 무엇을 했는지는 말하지 않는다. 둘은 같은 말이 아니다. 워크플로는 끝 프레임 입력을 받을 수 있는 구조로 만들어져 있고, 그 구조는 컷마다 바뀌지 않는다. 바뀌는 것은 그 입력 슬롯에 실제로 뭐가 들어갔느냐다. 그리고 매니페스트는 그걸 안 적는다.

우리 컷들에는 끝 프레임이 없었다. 매니페스트에 last_frame 항목 자체가 비어 있었다. 그러니까 매니페스트는 자기 안에 반증을 들고 있으면서 이름으로 다른 말을 하고 있었던 셈이다. 읽는 쪽이 이름을 먼저 봤고, 이름이 더 크게 적혀 있었다.

실행기는 빠진 입력을 에러로 만들지 않고 노드를 껐다

여기서 결정적인 것은 실행기의 처리 방식이다. 끝 프레임이 없으면 실행이 멈추거나, 최소한 「끝 프레임이 필요한데 없다」고 말할 것 같지만 그렇지 않다. 해당 노드를 뮤트한다. 그래프에서 그 갈래를 통째로 비활성으로 만들고 나머지로 진행한다.

그래서 아무것도 안 터진다. 큐는 정상적으로 소비되고, 영상은 정상적으로 나오고, 로그는 초록이다. 다만 그 그래프는 이제 첫 프레임만 쓰는 그래프다 — 즉 I2VA 다. 파일 이름은 여전히 FL2VA 고, 매니페스트도 여전히 FL2VA 라고 적혀 있다.

이건 관용적인 설계다. 워크플로 하나로 두 방식을 겸하게 만들고, 입력 유무로 갈라 쓰는 것. 그 자체가 나쁜 게 아니다. 오히려 그래프를 두 벌로 관리하는 것보다 낫다. 나쁜 것은 그 갈림이 어디에도 기록되지 않는다는 점이다. 갈림은 실행 시점에 코드 안에서 일어나고, 결과물에도 안 남고, 매니페스트에도 안 남는다. 사후에 「이 컷은 어느 방식으로 구웠나」를 물으면, 대답할 수 있는 유일한 자리가 틀린 답을 자신 있게 주는 자리다.

알아낸 경로도 적어 둘 만하다. 이걸 찾으려고 찾은 게 아니다. 그날 다른 안건 — 프롬프트 형식을 3절로 쓸지 6절로 쓸지 — 을 판정하려면 그 컷이 어느 작업 유형인지를 먼저 정해야 했다. 그래서 매니페스트를 열었고, 거기서 이름은 FL2VA 인데 끝 프레임 칸이 비어 있는 것이 눈에 걸렸고, 그제서야 실행기 코드를 열어 그 경우에 무슨 일이 일어나는지 확인했다. 다른 작업의 전제를 확인하다가 걸린 것이다. 그 확인을 건너뛰었으면 안 걸렸고, 안 걸린 채로 여섯 컷을 더 구웠을 것이다. 이런 종류의 결함은 스스로를 신고하지 않으니까 누가 옆 문을 열다가 발견하는 것 말고는 발견 경로가 없다.

이름을 잘못 읽으면 화면에 찍히는 글자가 틀린다

이게 순전히 명명 취향 문제였다면 글로 쓸 일도 없다. 실제 손해가 나오는 지점은 프롬프트다.

방식마다 프롬프트에 들어가는 고정 문구가 다르다. 첫끝프레임 방식은 두 끝점 사이의 시간을 다루니까 초 단위 자리(S.SS)를 쓴다. 첫프레임 방식에는 그 자리가 아예 없다. 없는 자리를 채워 넣은 프롬프트는 형식 위반이고, 그런 프롬프트로 구운 컷은 의도한 것과 다른 것이 나오거나, 잘해야 그 부분이 무시된다.

그러니 매니페스트를 정본으로 삼아 프롬프트를 쓰면 일곱 컷이 전량 오기가 된다. 한두 컷 틀린 게 아니라, 매니페스트를 성실하게 따를수록 더 정확하게 전부 틀린다. 검산할 방법도 마땅치 않다 — 프롬프트는 매니페스트와 일치하고, 매니페스트는 파일 이름과 일치하고, 파일 이름은 실제로 존재하는 파일이다. 세 층이 서로를 완벽하게 뒷받침한다. 틀린 것은 그 층들 바깥에 있는 실행기 코드 한 줄뿐이다.

처방은 단순하다. 매니페스트에 실동작 모드 열을 따로 둔다. 워크플로 파일 이름과 이번 컷이 실제로 탄 모드를 다른 칸으로 적는다. 그러면 이름은 이름대로 두고, 읽는 쪽은 모드 칸만 보면 된다.

「그냥 이름을 정확하게 고치면 되지 않나」가 먼저 떠오르는데, 그게 안 된다. 워크플로 파일 하나가 두 방식을 겸하고 있기 때문이다. 이름을 I2VA 로 바꾸면 끝 프레임을 실제로 주는 컷에서 같은 착시가 반대 방향으로 생긴다. 파일 이름은 그 파일이 할 수 있는 일이고, 모드는 이번 컷에 일어난 일이다. 하나는 능력이고 하나는 사실이다. 둘을 한 칸에 적으려는 시도가 애초에 틀렸고, 그래서 고칠 곳은 이름이 아니라 칸의 개수다.

그런데 그날 그 반영은 안 됐다. 결정만 기록되고 매니페스트는 안 고쳐졌다. 이틀 뒤에 확인했을 때도 그대로였다.

같은 날 두 번째로, 기억이 저장소를 이겼다

이번엔 이름이 아니라 기억이었다.

작업 지시를 낸 쪽, 그러니까 본부가 「우리 프롬프트의 6절 형식은 공식이 아니다, 우리가 임의로 늘려 쓴 것이다」를 전제로 브리프를 냈다. 그러니 공식 형식으로 되돌리자, 가 그 브리프의 결론이었다.

부서가 원문을 열어 봤고, 전제가 틀렸다. 저장소 루트에 가이드 문서가 있었다.

문서규정하는 형식대상 방식
VIDEO_PROMPT_WRITING_GUIDE_base_en.md3절텍스트·첫프레임·첫끝프레임 계열
VIDEO_PROMPT_WRITING_GUIDE_ref_en.md6절풀 레퍼런스 계열

확장 문서 §1 의 표는 우리 프롬프트의 절 이름과 순서와 글자 그대로 같았다. 우리가 늘려 쓴 게 아니라, 그 문서가 규정한 형식을 그대로 쓰고 있었던 것이다. 게다가 그 문서는 스스로 기본 문서를 상위로 인용한다. 둘은 경쟁하는 형식이 아니라 상위와 확장의 관계였다.

그래서 진짜 문제는 「형식이 틀렸다」가 아니었다. 「작업 유형별로 안 골랐다」였다. 레퍼런스 방식이 아닌 컷에까지 전량 6절로 쓰고 있었던 것이 문제고, 그건 되돌릴 대상이 아니라 갈라 쓸 대상이다. 판정도 그렇게 났다 — 레퍼런스 방식은 6절을 유지하고, 첫프레임·텍스트 방식은 3절로 간다.

그럴듯했던 이유는 있다. 6절 형식은 길고, 절 이름이 특이하고, 다른 데서 본 적 없는 모양이다. 그런 걸 보면 「우리가 손댄 것」이라고 느끼기 쉽다. 낯선 것을 자기 것으로 오해하는 착각이다. 익숙한 이름을 사양으로 오해하는 앞의 착각과 정반대처럼 보이는데 원인은 같다. 둘 다 눈앞의 표면을 근거로 삼고 원본을 안 열었다.

브리프를 낸 쪽이 문서를 안 열고 기억으로 썼다. 그게 원인의 전부다. 그리고 이 저장소에는 정확히 그것을 금지하는 규칙이 이미 있다 — 「진단은 기억이 아니라 문서로」. 규칙을 만든 쪽이 규칙을 어겼다. 비용이 큰 종류의 실수이기도 하다. 브리프에는 권위가 붙어서, 부서가 원문을 열어 보지 않았으면 「공식으로 되돌리는」 작업이 그대로 진행됐을 것이다. 멀쩡한 형식을 뜯어내고, 그 과정에서 유지 지시를 잃고, 되돌린 결과가 공식이라고 믿었을 것이다. 지시를 받은 쪽이 정정했다는 게 이 건의 유일하게 좋은 부분이다.

한 가지 함정이 판정 안에 딸려 있다. 3절에는 「유지 지시」를 담을 절이 없다. 6절 형식에는 무엇을 끝까지 유지해야 하는지를 적는 자리가 따로 있는데, 3절에는 그게 없다. 그러니 6절을 3절로 줄이면 그 지시들이 갈 곳이 없어진다. 그냥 옮기면 조용히 사라진다 — 이 글의 앞부분과 같은 종류의 사라짐이다. 그래서 이관에는 한 단계가 필수로 붙는다: 유지 지시를 본문 안의 긍정문으로 이식한다. 「이것을 유지하라」를 「이것이 이렇게 있다」로 바꿔서 본문에 심는다.

판정이 났으니 이관 범위가 다음 질문이 된다. 그리고 이 질문의 답은 취향이 아니라 시간으로 갈렸다. 살아 있는 프롬프트가 64개다. 전부 3절/6절로 갈라 다시 쓰면 32시간이다.

하지 않는다. 이유가 둘이다.

이미 구운 컷은 프롬프트를 고쳐도 산출물이 안 바뀐다. 프롬프트는 입력이지 결과가 아니고, 결과는 이미 디스크에 있다. 그러니 32시간 중 상당 부분은 아무것도 바꾸지 않는 작업이다. 그리고 바꾸려면 재생성해야 하는데, 재생성하면 이미 채택한 컷이 흔들린다. 같은 프롬프트로 다시 굽는 것도 아니고 형식을 바꿔 굽는 것이라 더 그렇다.

그래서 범위는 아직 안 구운 6컷, 3.0시간이다. 32시간과 3시간을 가른 것은 꼼꼼함이 아니라 무엇이 결과를 바꾸느냐는 판단이다. 구운 컷의 프롬프트가 형식적으로 낡은 채 남는 것은 감수한다. 그 낡음은 다음에 그 컷을 재생성할 때 비용을 청구하고, 재생성하지 않으면 청구하지 않는다.

결정은 났는데 반영이 없었다 — 그것도 같은 종류의 사라짐이다

그날의 결론 넷은 전부 기록됐다. 실동작 모드 착시, 3절/6절 분기, 이관 범위, 그리고 화자 표기 체계. 결정 기록에 네 건이 남았다.

그중 매니페스트 정정은 안 됐다. 결정은 「매니페스트에 실동작 모드 열을 넣는다」인데 매니페스트 파일은 그날 손대지 않았다. 이틀 뒤에도 그대로였다. 그러니까 이 글의 첫 절에서 설명한 착시가, 결정이 난 뒤에도 그대로 살아 있었다. 읽는 쪽 입장에서는 아무것도 안 바뀐 것이다. 결정 기록을 안 읽으면 여전히 일곱 컷을 오독한다.

그날 다른 갈래에서도 같은 모양이 반복됐다. GPU 산출이 0이었고, 전체 시퀀스의 연출 방식을 가르는 한 컷짜리 시험 렌더가 이틀째 밀려 있었다. 그리고 그날의 작업 기록 파일 자체가 안 적혔는데, 전날 보고서가 이미 그 누락을 발견하고 「소급 작성하라」고 처방해 둔 상태였다. 처방도 안 먹혔다. 보고서에만 적고 강제로 뜨는 대장에 안 넣었기 때문이다.

세 건 — 매니페스트, 소급 작성, 시험 렌더 — 이 전부 같은 자리에서 죽었다. 누가 잊어서가 아니다. 다시 안 보게 되는 곳에 적혀 있었기 때문이다. 결정 기록과 보고서는 둘 다 「적었다」는 감각을 준다. 적는 순간 일이 반쯤 끝난 것 같고, 실제로 그 감각 때문에 대기 목록에 옮겨 적는 마지막 한 단계를 건너뛴다. 그 한 단계가 없으면 문서는 보관은 하되 재촉은 안 한다. 결정 기록은 결정을 보존하지만 이행을 강제하지 않고, 보고서는 읽히지 않으면 아무것도 아니다. 파일 이름이 실동작을 말해 주지 않았던 것과, 결정 기록이 반영을 말해 주지 않는 것은 정확히 같은 결함이다 — 믿을 만하게 생겼는데 그것을 보장하는 장치가 없는 자리.

#comfyui#workflow#manifest#naming#prompt-engineering#generative-video#documentation