
AI 비디오에는 더 나은 프롬프트보다 CI가 더 필요합니다
요약
AI 비디오 생성 과정에서 발생하는 비용 효율성 문제를 해결하기 위해 소프트웨어 공학의 CI(지속적 통합) 개념을 도입해야 함을 강조합니다. 프롬프트 개선만으로는 한계가 있으며, 생성된 결과물이 요구사항을 준수하는지 자동으로 검증하는 시스템 구축이 필수적입니다.
핵심 포인트
- 비디오 생성은 텍1스트와 달리 결과물을 확인한 후 비용이 발생함
- 프롬프트는 결과물의 품질을 보장하는 계약 역할을 하기 어려움
- 비디오 생성 모델의 예산 낭비를 막기 위해 CI(지속적 통합) 도입이 필요함
- LLM 브레인스토밍 결과에서도 비디오 검증 시스템 구축 아이디어는 매우 희소함
여기서 저는 Qwen Cloud와 함께한 Global AI Hackathon Series의 Track 2를 위해 제가 구축한 것과, 눈에 보이지 않는 것들을 테스트하는 것에 대해 배운 점을 공유하고자 합니다.
나의 비싼 작은 문제
저는 5초짜리 비디오 클립을 생성했습니다. 첫 번째 프레임은 완벽해 보였습니다.
그런 다음 나머지 4초를 지켜보았습니다.
카메라가 왼쪽으로 흐르고 있었습니다. 제 브리프(brief)에는 정지 상태를 유지하라고 되어 있었습니다.
여기서 저를 짜증 나게 만드는 부분이 있습니다. 결제를 한 후에야 알게 되었다는 점입니다. Qwen Cloud의 Wan 모델에서는 클립이 사용 가능한 것이든 쓰레기든 상관없이 프리미엄 쿼터(premium quota) 5초가 고정적으로 소모됩니다. 청구서는 신경 쓰지 않습니다.
텍스트 모델은 이렇게 작동하지 않습니다. 답변을 읽고, 그것이 틀렸다는 것을 확인하면, 다음으로 넘어가면 됩니다. 결과물(artifact)과 판정(verdict)이 함께 나타나며, 틀렸을 때 드는 비용은 몇 백 토큰 정도입니다.
비디오는 움직임 속에서 실패합니다. 실패는 당신이 아직 보지 않은 프레임 속에 존재합니다.
그래서 당신은 먼저 지불하고, 나중에 알게 됩니다.
하지만 상황은 더 나빠집니다...
이 트랙을 위한 명백한 빌드(build)는 전제(premise)를 입력하면 에피소드가 출력되는 생성기입니다. 저도 거기서 시작했습니다. 며칠 만에 데모 릴(demo reel)을 제작하는 결과물을 만들어냈습니다.
하지만 데모 릴은 흥미로운 실패 사례들을 숨깁니다.
최종 렌더링(render)에 도달하는 모든 잘못된 테이크(take)는 이미 지출한 돈입니다. 그리고 저는 생성과는 전혀 상관없는 질문을 스스로에게 계속 던졌습니다:
시스템이 프리미엄 예산을 소모하기 전에, 비디오가 브리프를 준수했는지 여부를 판단할 수 있는가?
소프트웨어 분야에서 그 질문에는 이름이 있습니다. 바로 CI(지속적 통합, Continuous Integration)입니다.
그리고 생성된 비디오에 CI를 실행하는 사람은 거의 없습니다.
그런데 왜 굳이 그래야 할까요? 그냥 프롬프트를 더 잘 작성하면 되지 않나요?
그것이 저의 첫 번째 본능이기도 했습니다. 더 나은 프롬프트를 작성해서 더 나은 클립을 얻는 것 말입니다.
하지만 프롬프트는 계약이 아닙니다. 결과물이 모델 사이를 이동함에 따라 프롬프트를 다시 작성하고, 늘리고, 잊어버리기가 너무 쉽습니다. 프롬프트에 대해서는 단언(assert)할 수 없습니다. 프롬프트만으로는 빌드를 실패하게 만들 수 없습니다.
이 도박에 일주일을 태우기 전에, 저는 이것이 단순히 듣기 좋은 슬로건이 아니라는 것을 알고 싶었습니다. 그래서 저는 숫자를 세어 보았습니다. 두 번이나 말이죠.
테스트 1 — 시뮬레이션된 필드 (simulated field). 저는 이 트랙의 공개 브리프(public brief)를 대상으로 15회의 독립적인 LLM 브레인스토밍을 실행했습니다. 두 개의 모델 제품군(model families), 14개의 페르소나(personas)를 사용했으며, 이들은 제 리포지토리(repo)나 서로를 전혀 보지 못했습니다. 약 150개의 아이디어가 나왔습니다.
15개 모두가 생성기(generator)를 만들어냈습니다.
하지만 생성된 에피소드가 제대로 나왔는지 확인할 수 있는 방법을 만들어낸 것은 단 하나도 없었습니다.
그중 두 개는 심지어 "AI 생성 비디오를 위한 CI (CI for AI-generated video)"라는 문구를 토씨 하나 틀리지 않고 그대로 내뱉고는, 그럼에도 불구하고 그것을 생성기에 그냥 붙여버렸습니다.
테스트 2 — 관찰된 필드 (observed field). 마감 이틀 전, 저는 이 트랙의 공개 GitHub 필드에 제출된 가장 실질적인 8개의 결과물을 읽어보았습니다. 모든 결과물이 품질 검사(quality checks)를 토큰 비용이 발생하는 모델 호출(model call)을 통해 수행하고 있었습니다. 8개 중 6개는 컴퓨터 비전 라이브러리(computer-vision library)를 전혀 임포트(import)하지 않았습니다. 폐쇄형 어설션 어휘집(closed assertion vocabulary)을 포함하여 출시한 것도 없었습니다. 자신이 직접 생성하지 않은 비디오를 검증(gate)할 수 있는 것도 없었습니다.
그러다 저는 한 로그라인-to-비디오(logline-to-video) 파이프라인의 설정(config)에서 이 한 줄을 발견했습니다:
DAILIES_QC = False # off = faster pipeline
누군가 리뷰 단계(review stage)를 만들었습니다. 이름을 'dailies'라고 붙였죠. 그리고 속도를 위해 그것을 꺼진(off) 상태로 출시했습니다.
이것이 문제의 핵심을 한 줄로 요약한 것입니다.
검증 게이트(gate)가 생성기의 코드베이스 내부에 존재할 때, 그 게이트는 처리량(throughput)을 위해 가장 먼저 희생되는 대상이 됩니다.
대신 테스트를 구축할 시간
영화 제작에서 *데일리스(dailies)*란, 추가 비용을 들여 촬영을 이어가기 전에 제작진이 어제의 촬영분을 함께 시청하는 상영회를 의미합니다. 이는 매일 수행되는 품질 게이트(quality gate)입니다.
그래서 이름이 'Dailies'입니다. Dailies. 검증 대상인 파이프라인에 속해 있지 않기 때문에, 조용히 꺼버릴 수 없는 리뷰 단계입니다.
이것은 생성기의 내부(internals)가 아니라 프레임(frames)을 읽습니다. 어떤 모델에서 나온 mp4 파일이든 그곳을 향하게 하면 됩니다.
작동 방식은 다음과 같습니다.
사용자는 샷(shot)이 수행해야 할 작업을 작성합니다. 모든 테이크(take)는 출시되기 전에 그 기준에 따라 측정됩니다. 기준을 충족하지 못한 테이크는 제한된 횟수의 수정 시도(repair attempt)를 거칩니다. 여전히 기준을 충족하지 못한 테이크는 절대로 당신의 편집본(cut)에 도달할 수 없습니다.
스펙(Spec)은 폐쇄형 단언 DSL(closed assertion DSL) — 3개 계층에 걸친 10가지 문장 유형으로 구성된 짧은 프로그램입니다. 컴파일러는 각 문장을 공유 체크 라이브러리의 호출로 변환하며, 단 하나의 토큰도 소비하기 전에 문법에 어긋나는 모든 것을 거부합니다.
시스템이 인식하지 못하는 것을 보내면 parse_assertions가 발생합니다. MCP 서버에 {"type": "vibe_check"}를 보내면 추측이 아닌 isError가 반환됩니다.
그러한 거부(rejection)야말로 "생성된 비디오를 위한 CI"에서의 문자 그대로의 컴파일 에러(compile error)입니다.
| 단언 유형 (Assertion type) | 계층 (Tier) | 컴파일 결과 (Compiles to) |
|---|---|---|
duration_between | A · 결정론적 (deterministic) | 범위 내의 클립 길이 |
| ... | ||
| 이 표 뒤에 숨겨진 조직적인 아이디어가 있음에 주목하십시오. |
현재는 "정답"을 정의하는 사람(브랜드, 법무, 마케팅)이 생성기를 운영하는 사람과 결합되어 있습니다. 이들은 모든 클립을 눈으로 일일이 확인하는 과도하게 업무가 몰린 검토자 한 명에 의해 연결되어 있습니다.
이해관계자는 이러한 단언(assertions)을 평이한 언어로 한 번만 작성합니다. 그러면 모든 샷(shot)은 이 단언들에 따라 자동으로 테스트됩니다.
비디오를 위한 스펙 주도 개발(Spec-driven development)입니다.
계층(Tiers)은 의도적으로 지루하게 설계되었습니다
모든 체크가 동일한 예산을 소모할 가치가 있는 것은 아닙니다. 따라서 캐스케이드(cascade)는 가장 저렴하고 확실한 것부터 실행됩니다.
Tier-A는 결정론적인 OpenCV입니다. 지속 시간, 밝기, 플리커(flicker), 장면 전환(scene cuts), 광학 흐름(optical-flow) 카메라 움직임, 팔레트 ΔE 등을 다룹니다. 토큰 소모가 전혀 없으므로, *모든 테이크(take)*에서 실행됩니다.
이는 의도적으로 화려하지 않게 설계되었습니다. OpenCV는 당신의 이야기가 제대로 작동하는지 알지 못합니다. 하지만 저렴한 비용으로 엄청난 양의 깨진 비디오를 잡아내며, 숫자로 방어할 수 없는 의견은 절대 내놓지 않습니다.
25번의 반복 측정 결과: 5초 길이의 클립에 대해 6가지 체크를 모두 수행하는 데 드는 비용은 중앙값 317ms의 CPU를 사용했습니다. 그 외에는 아무것도 들지 않습니다.
Tier-B는 Qwen-VL이며, 이는 권고 사항(advisory)입니다. VLM(시각 언어 모델)은 "이것이 브리프(brief)와 일치하는가"를 판단하는 데 능숙합니다. 하지만 모델의 판단은 픽셀 측정값보다 증거가 약하므로, Tier-B는 인간에게 플래그(flag)를 표시할 뿐 절대로 승급(promotion)을 차단하지는 않습니다.
결정론적 체크(Deterministic checks)가 기초입니다. 모델 기반의 판단(Model-graded judgment)은 그 위에 놓입니다. 결코 기초가 되어서는 안 됩니다.
Tier-0는 정지 이미지 사전 심사(still-image pre-screen)입니다. 어떠한 움직임이 생성되기 전에, wan2.1-t2i-plus가 비디오 비용의 약 1/25 수준으로 정지 이미지를 렌더링하며, qwen-vl-plus에게는 단 하나의 질문이 던져집니다: 브리핑된 피사체가 프레임 안에 들어있는가?
만약 프롬프트가 자신의 피사체를 정지 이미지로도 렌더링할 수 없다면, 움직임을 위해 비용을 지불하는 것은 단지 25배의 가격으로 똑같은 교훈을 사는 것과 같습니다.
이 모든 과정을 통과한 후에야 비로소 샷(shot)은 승격될 자격을 얻습니다:
전제(premise) → 스크립트 + 사양(script + specs) (qwen-plus) → 컴파일된 단언 체크리스트(compiled assertion checklist)
→ Tier-0 정지 이미지 사전 심사 (Tier-0 still pre-screen) (wan2.1-t2i-plus)
→ [인간 검토 게이트(human review gate) — 비디오 비용을 지불하기 전의 유일한 체크포인트]
...
렌더링된 아키텍처 다이어그램(architecture diagram)은 어차피 필수 결과물이었으므로, 여기 정식으로 그려진 동일한 내용을 제시합니다. 알아둘 만한 관례 하나는, 빨간색은 가장 중요한 두 가지 경로를 위해 예약되어 있다는 점입니다. 즉, 비용 지불 전 거부된 사양(spec)과 차단된 Tier-A 실패(blocking Tier-A fail)를 의미합니다.
[
오늘날 지친 한 명의 검토자로 통합되어 버린 두 가지 인간의 역할이 있습니다. **이해관계자(stakeholder)**는 '정확성'을 소유하며 사양(spec)을 한 번 작성합니다. **운영자(operator)**는 런타임(runtime) 시점에, 즉 비디오 비용이 결제되기 전의 단일 게이트에서 루프 내에 존재하는 유일한 인간입니다.
Level 2는 해커톤이 실제로 물었던 질문, 즉 Qwen Cloud가 프론트엔드(frontend), 백엔드(backend), 그리고 스토어(store)와 어떻게 연결되는지에 대해 답합니다:
[
그것이 설계였습니다.
그 후 실제로 실행해 보았고, 현실이 저를 교정하기 시작했습니다.
저를 계속 구해준 규칙: 모델이 선택하게 하되, 절대 철자를 쓰게 하지 마라 (let the model choose, never let it spell)
전투 이야기(war stories)를 하기 전에, 제가 했던 그 어떤 일보다 더 중요했던 것으로 판명된 한 가지 설계 결정에 대해 말씀드리겠습니다.
그 폐쇄형 어휘(closed vocabulary)는 처음에는 어설션 레이어(assertion-layer)의 세부 사항으로 시작되었습니다. 하지만 결국 시스템 전체를 구동하는 규칙이 되었습니다.
Dailies에서 Qwen 모델이 실질적인 결정을 내리는 곳은 세 군데입니다. 이 세 곳 모두에서 모델은 _서버가 소유한 집합 내의 선택지(choice from a set the server owns)_를 출력하며, 결과물(artifact) 자체를 직접 출력하지는 않습니다.
| 위치 | 모델이 출력하는 것 | 서버가 생성하는 것 | 방지하는 실패 유형 |
|---|---|---|---|
| 어설션 (Assertions) | 폐쇄된 집합 내의 type + 타입화된 파라미터 (typed params) | 체크 라이브러리(check library) 호출 | 말하는 바와 다른 의미를 갖는 체크 |
| ... | |||
그 중간 행이 제 데모의 핵심이 되었습니다.
사용자는 평범한 언어로 요청을 입력합니다. qwen-plus는 tool_choice="auto"와 함께 build_pipeline_graph 도구를 호출합니다. 서버는 이러한 파라미터들을 실행될 그래프로 결정론적(deterministically)으로 확장하며, 이는 대시보드가 이미 사용 중인 2.5초 간격의 폴링(poll)을 통해 React Flow 상에 실시간으로 렌더링됩니다.
따라서 "에이전트가 파이프라인을 구성했다"라는 주장은 제가 주저 없이 할 수 있는 말입니다. 모델이 실행(run)을 작성했습니다. 모델은 결코 망가진 그래프를 출력할 수 없었는데, 왜냐하면 모델은 토폴로지(topology) 자체를 아예 출력하지 않았기 때문입니다.
이것이 모델의 출력을 검증(validating)하는 것과, 잘못된 출력이 아예 표현 불가능하도록(unrepresentable) 만드는 것의 차이입니다.
검증은 실행하는 것을 잊어버릴 수 있는 체크 방식입니다. 혹은 두 개의 코드 경로 중 하나에서만 실행될 수도 있습니다. 저도 실제로 그렇게 했고, 그 부분에 대해서는 나중에 말씀드리겠습니다.
파라미터 계약(parameter contract)은 잘못된 값이 통과할 수 없는 형태(shape)입니다.
내 그린 테스트(green tests)가 나에게 거짓말을 했던 날
제가 작성했던 모든 엔드 투 엔드(end-to-end) 테스트는 합성 클립(synthetic clips)을 사용했습니다. 파이프라인을 무료로 테스트할 수 있도록 생성된 mp4를 반환하는 가짜 Wan 클라이언트였습니다.
그것은 제어 흐름(control flow)을 검증할 뿐입니다. API의 계약(contract)에 대해서는 아무것도 검증하지 못합니다.
가짜 클라이언트는 실제 서비스가 거부할 요청을 거부할 수 없습니다.
그래서 저는 픽스처(fixtures) 모드를 구축했습니다. 실제 Wan 클라이언트, 실제 Tier-A, 실제 Tier-B, 실제 어셈블리(assembly)를 사용하되, 비결정론적인(non-deterministic) 두 가지 텍스트 단계만 고정된 프롬프트(fixed prompts)로 고정했습니다. (프롬프트는 캐시 키이므로, 고정된 실행은 쿼터 소모 없이 동일하게 재현됩니다.)
그 후, 처음으로 정직하게 실제 API를 대상으로 콜드 런(cold run)을 수행했습니다.
그 결과 네 개의 버그를 찾아냈습니다.
통과된 79개의 테스트는 단 하나도 이를 놓치지 않았습니다.
| 버그 | 79개의 통과된 테스트가 이를 놓친 이유 |
|---|---|
wan2.2-t2v-plus가 1280*720을 거부함 — 결과적으로 이 프로젝트가 진행했던 모든 프리미엄 프로모션이 소리 없이 실패하고 있었음 | _promote는 프로모션 실패를 "통과된 초안 유지"로 처리합니다. 따라서 거부된 최종 결과물과 정상적인 스킵(skip)이 동일한 상태를 생성했습니다. 저의 핵심 전략이었던 프리미엄 티어는 실제로 단 한 번도 실행된 적이 없었습니다. |
| ... |
첫 번째 행의 내용을 잠시 곱씹어 보십시오.
저의 전체 논지는 생성된 비디오가 검증되지 않은 채 배포된다는 것입니다. 왜냐하면 아무도 결과물(artifact)에 대해 그 주장을 검증하지 않기 때문입니다.
그리고 저의 프리미엄 티어는 프로젝트의 전 생애 동안 죽어 있었습니다. 적합성 게이트(conformance gate)가 논지인 저장소(repo)에서 말입니다.
그 게이트가 자신의 제작자를 잡아낸 것입니다.
저는 단 하나의 커밋으로 네 가지 버그를 모두 수정했으며, 각각의 수정 사항에 대해 수정 사항을 되돌려 테스트가 실패하는 것을 확인하는 회귀 테스트(regression test)를 수행했습니다. 하지만 이후 로그에 제가 남긴 문구는 다음과 같으며, 저는 이 문구를 계속 되새기게 됩니다.
모크(Mocks)는 당신이 작성한 코드를 테스트합니다. 픽스처(Fixtures)는 당신이 세운 가정을 테스트합니다. 이 버그는 그 간극 사이에 존재했습니다.
모크는 당신이 지시한 대로 반환합니다. 따라서 모크 세트는 당신의 정신적 모델(mental model)을 확인해 줄 뿐입니다. 그것은 당신의 가정을 다시 당신에게 초록색(통과)으로 재확인시켜 줄 뿐입니다.
진정한 계약(contract)은 외부 서비스와의 경계에 존재합니다. 그 경계에 닿는 유일한 테스트는 실제로 해당 기능을 호출하는 테스트뿐입니다.
만약 어떤 생성형 API(generative API)를 통합하고 있다면 — 초기에 토큰을 써서라도 실제 엔드 투 엔드(end-to-end) 실행을 한 번 수행하십시오. 그것은 당신이 만들 수 있는 가장 저렴한 버그 탐지기입니다.
동일한 계열의 다섯 번째 버그가 있었는데, 이것은 테스트 설계에 회의적인 사람에게 보여줄 만한 사례입니다.
저의 Tier-0 체크 항목인 subject_present는 선언되었고, 차단 클래스(blocking-class)로 표시되었지만, 그 무엇도 이를 평가하지 않았습니다. 세 가지 런타임(runtime) 모두 이를 lambda spec, still: []로 연결해 두었으며, 그 사이 파이프라인은 모든 샷에 대해 스틸(still) 이미지를 생성하고 비용을 청구한 뒤, 반환받은 빈 리스트를 성실히 저장했습니다.
통과된 테스트 세트는 이를 잡아낼 수 없었습니다. 단 한 줄의 코드 때문입니다:
take.passed = not [r for r in results if not r.advisory and r.status is Status.FAIL]
결과가 없는 (no result) 체크와 통과 (passes) 하는 체크는 동일한 빈 리스트입니다.
이를 실제 qwen-vl-plus 읽기에 연결하는 비용은 5초짜리 프리미엄 클립을 피하기 위해 샷(shot)당 325 토큰이 소요됩니다. 저는 이를 두 가지 방향(피사체가 있는 경우와 없는 경우) 모두에서 측정했습니다. 왜냐하면 오직 PASS라고만 답하는 체크는 체크가 아니기 때문입니다.
내가 이 기술을 믿게 만든 영수증
3e1f628d4acf 실행, 샷 0, 계약 camera_motion: static.
터보 드래프트(turbo draft)는 광학 흐름(optical-flow) 크기 |v|=0.30에서 깔끔하게 통과되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기