GitHub - chessire/shelter-puppy

Contribute to chessire/shelter-puppy development by creating an account on GitHub.

github.com

파이프라인을 빠르게 만드는 첫 질문은
"스레드를 몇 개 띄울까?"가 아니라
"어떤 자원이 어디서 놀고 있나?"입니다.

 

"느리면 그냥 멀티스레딩 돌리면 되는 거 아냐?"

 

이번 함정은 그 직관이 절반만 맞다는 데 있습니다. 스레드를 늘려도 공유 자원 앞에서는 줄만 서기 때문에 안 빨라지는 구간이 있습니다. 스레드와 무관하게 놀고 있던 자원(GPU)도 있습니다. 심지어 빨라졌는지 판정하는 것조차 같은 코드 실행에도 실행 속도가 2배 차이가 될 수 있는 함정이 있습니다. 이번 최적화로 자동 영상 편집에 8분 걸리던 파이프라인을 4분으로 줄였는데 결론부터 말하면 가장 큰 배수는 멀티스레딩이 아닌 한 줄 수정에서 나왔습니다.

 

스레드보다 먼저 자원의 지도를 그린다.
겹칠 수 있는 것만 겹치고,
결과는 완전히 동일해야 한다.

 

이번 편은 실사용 병목 3구간(전처리+검출, 관찰 프로필, 동작 판정)의 속도 최적화 기록입니다. 원칙은 하나로 요약됩니다. 단계 간 직렬은 유지하고, 단계 안에서만 병렬화한다. 파이프라인의 순서와 게이트 구조는 건드리지 않았습니다.

 

Multi-Threading vs Resources

16. 스레드보단 자원을 살펴보자 - 다섯 개의 자원

병렬화를 시작하기 전에 파이프라인이 쓰는 자원부터 그렸습니다. 다섯입니다. Ollama(Gemma, GPU), MLX(TTS, GPU), MPS(YOLO, GPU), ffmpeg(CPU 서브프로세스), OpenCV / Torch(CPU). 지도를 그리고 나니 제일 중요한 사실이 보였습니다. Ollama는 서버가 요청을 직렬로 처리하는 공유 자원입니다. 클라이언트에서 스레드를 아무리 늘려도 서버 앞에서 줄만 섭니다.

 

그래서 이번 최적화의 이득은 전부 한 곳에서 나왔습니다. Ollama 작업과 비-Ollama 작업을 겹치는 것. LLM이 크롭 하나를 판정하는 동안 CPU는 다음 영상의 모션 곡선을 계산하고, ffmpeg는 다음 영상을 정규화합니다. LLM을 빠르게 만든 게 아니라, LLM이 일하는 동안 다른 자원이 놀지 않게 만든 겁니다.

 

병렬화에도 안전을 위한 규칙을 먼저 세웠습니다. 병렬 구간은 meta파일을 만지지 않고 쓰기는 오케스트레이터가 루프 밖에서 한 번, 판정 로직은 입력이 전부 모인 뒤의 직렬 흐름 그대로 두고, 완료 순서는 비결정이어도 조립은 인덱스로 고정합니다. 그리고 합격 기준은 속도가 아니라 결과 동일성입니다. 빨라졌는데 결과가 달라졌다면 그건 최적화가 아니라 버그니까요.


17. 최대 병목은 병렬화가 아니었다 - 놀고 있는 GPU

전처리(ffmpeg)와 검출(YOLO)을 파이프라이닝했습니다. 영상 i를 검출하는 동안 영상 i+1을 정규화하는 구조는 교과서적인 이종 자원 오버랩입니다. 그런데 체감이 없다는 피드백이 왔습니다.

 

구간별 비중을 실측해 보니 범인은 따로 있었습니다. 검출 모델이 내내 CPU로 돌고 있었습니다. Mac에서 이 라이브러리의 디바이스 자동 선택은 GPU가 아니라 CPU였습니다. Apple GPU(MPS)를 쓰려면 명시해야 했습니다. 그래서 한 줄을 고쳤습니다. 157.9초 → 45.4초, 3.5배. 이번 최적화 전체에서 가장 큰 배수가 스레드 하나 없이 나왔습니다.

 

물론 검증은 시리즈의 규칙대로입니다. 골든셋 5영상을 GPU로 재검출해 재채점하여 박스 수, 트랙 수, miss/IDF1을 포함한 모든 지표가 소수 4자리까지 완전 동일했고, 게이트 판정도 그대로였습니다(예외 처리된 나쁜 footage의 FAIL까지 똑같이). 디바이스가 바뀌어도 자는 휘지 않았다는 확인입니다.

 

비슷한 사례가 하나 더 있었습니다. 모션 곡선이 프레임마다 랜덤 시크로 영상을 읽고 있었는데 랜덤 시크는 매번 키프레임부터 다시 디코드했습니다. 순차 읽기로 바꾸자 영상별로 4.6 -> 0.8초, 11.9 -> 1.7초, 약 6배. 이것도 병렬화가 아니라 직렬 최적화입니다. 교훈을 한 문장으로 정리하자면, [구조를 바꾸기 전에, 자원이 제대로 쓰이고 있는지부터 확인하라.] 였습니다.


18. 겹치기의 산수 - 비용을 서로의 뒤에 숨긴다

자원 지도가 그려지고 GPU를 활용한 뒤에야 오버랩이 값을 하기 시작했습니다. 세 구간입니다.

  • 전처리 || 검출 — ffmpeg(CPU)가 다음 영상을 정규화하는 동안 YOLO(GPU)로 현재 영상을 검출했습니다. 전처리 비용이 검출 뒤에 숨어, 준비 구간이 50.7초에서 17.1초(3배)로 줄었습니다.
  • 관찰 프로필 — 영상마다 캡션(Ollama, 직렬)과 센서 4축(오디오, 배경, 장소, 휘도, CPU)을 뽑는데 센서를 영상 간 병렬로 팬아웃시켰고 총시간이 캡션 합 + 센서 합에서 max(캡션 합, 센서 합)으로 바뀌면서 센서 비용을 통째로 은닉시켰습니다.
  • 동작 판정 — 모션 계산(CPU)을 팬아웃하고 LLM 판정만 직렬로 했습니다. 모션의 비용이 판정 뒤로 숨었습니다.

스레드가 들어오면서 따라오는 숙제도 처리했습니다. 모델 지연 로딩 3곳에 이중 확인 락(같은 모델을 두 번 로드해 메모리가 2배가 되는 사고 방지), 병렬 워커는 영상별 산출물 파일만 쓰기, 임시파일명에 인덱스 포함. 기존 테스트 102개는 전부 그대로 통과하였고 CLI와 같은 인터페이스는 하나도 안 바뀌었습니다. 병렬화는 전부 내부 구조입니다.


19. 단순 측정을 믿지 마라 - 분산부터 통제

마지막 함정은 측정 그 자체였습니다. 동작 판정 구간을 같은 입력으로 세 번 돌렸더니 28.1초, 22.9초, 14.3초. 같은 코드가 2배 차이 났습니다. Ollama 응답 시간의 분산이 구간 전체를 지배해서 한 번씩 재는 A / B 비교가 무의미해졌습니다.

 

실제로 두 번 속았습니다. 새 코드의 첫 실행이 옛 코드보다 느리게 나왔는데(9.7초 vs 10.7초) 첫 실행에 모델 로드와 Ollama 웜업이 섞여 있었습니다. 웜 상태로 다시 재니 7.8초(−20%)가 나왔습니다. 또 한 번은 22.9초(구코드)에서 28.1초(신코드)로 느려졌나 싶었는데 재실행에서 14.3초. 실행할 때마다 측정 결과가 달랐습니다.

 

그래서 판정 방법을 갈랐습니다. 결정론 구간(모션 곡선, 검출)은 조건을 맞춘 단발 A / B로 숫자를 재고, LLM이 섞인 구간은 단순 측정 대신 출력 동일성과 구조적 은닉(직렬 합이 max로 바뀌었다는 산수)으로만 판단합니다. 시리즈 첫 편에서 "자가 휘어 있으면 측정이 무의미하다"고 했는데, 마지막 편에서 하나 더 보탭니다. 분산을 통제하지 않은 단순 측정도 휜 자입니다.


맺음말

자동 영상 편집 1회 라이프사이클 기준, 총 8분이 4분이 됐습니다.

  • 자원 지도를 먼저 그려 공유 자원(Ollama)과 겹칠 수 있는 자원을 가르고,
  • 최대 배수(3.5배)는 스레드가 아니라 놀고 있던 GPU를 깨운 한 줄에서 얻고,
  • 오버랩으로 전처리, 센서, 모션의 비용을 LLM 작업 뒤에 숨기고,
  • 단순 측정의 분산을 통제해, 빨라졌다는 느낌 자체를 측정답게 만들었습니다.

그리고 이 모든 변경의 합격 기준은 속도가 아니라 결과 동일성이었습니다. 골든셋 지표 소수 4자리까지.

 

남은 부분도 있습니다. Ollama 서버 자체의 동시성, TTS와 렌더의 파이프라이닝. 하지만 raw 폰 영상 아홉 개와 요청 한 문단이 4분 만에 내레이션 붙은 숏폼이 되는 지금, 개발기로서의 이야기는 여기서 일단락하려 합니다. 골든셋으로 채점하고, 측정할 수 있는 것은 측정에게 맡기고, 소리도 채점하고, 구조의 주인을 하나로 세우고, 빠르게 만들 때조차 자부터 검증한다. 이 시리즈가 남긴 문장들입니다.


이 글에서 다룬 고정 함수 분류층은 ProjectDavid의 일부입니다.

전체를 바닥부터 만드는 과정은 강의 3부작으로 정리하고 있고, 10월에 오픈 예정입니다.

→ 오픈 알림 받기


다음글

 

pgvector와 bge-m3로 로컬 RAG 구축 - Provenance First AI

GitHub - chessire/shelter-puppyContribute to chessire/shelter-puppy development by creating an account on GitHub.github.com임베딩은 진위를 가리지 않고,유사도를 잽니다.검수는 검색 뒤가 아니라,저장 앞에 세웠습니다. 영

chessire.tistory.com

이전글

 

LLM은 지시만: 프롬프트 오염을 막는 설계 - Ownership First AI

GitHub - chessire/shelter-puppyContribute to chessire/shelter-puppy development by creating an account on GitHub.github.com요청이 짧다고 영상까지 짧아지면 안 됩니다.빈칸은 LLM의 저작으로 채우고,채워진 칸은 아무도 건드

chessire.tistory.com

관련글

 

오픈소스 LLM vs 빅테크 LLM API: AI 사업의 비용과 리스크

모델은 커모디티가 된다.마진은 판단에 남는다. 2025년 2월, OpenAI는 당시 가장 비싼 모델 GPT-4.5를 출시했습니다. 백만 토큰당 입력 75 달러, 출력 150 달러. 그리고 다섯 달이 지나기 전, API에서 제거

chessire.tistory.com

 

 

GitHub - chessire/shelter-puppy

Contribute to chessire/shelter-puppy development by creating an account on GitHub.

github.com

LLM은 영상을 만들지 않습니다.
영상은 결정론 코드가 만들고,
LLM은 사람의 말을 코드로 번역할 뿐입니다.

 

우선 작업 결과부터 보고 가겠습니다.

프롬프트

우리 토리 소개 영상 만들자. 먼저 토리가 가만히 있는 장면 하나를 가지고 토리 얼굴을 천천히 클로즈업으로 5초 보여주고 아래에 텍스트로 우리 토리를 소개합니다를 띄워줘. 그 다음 애견카페에서 뛰어 노는 모습 10초 편집해서 아래에 똥꼬발랄한 우리 토리 띄워주고 산책하고 비오는 날 뛰고 밤에 달리는 신나게 뛰어노는 모습을 10초 보여주면서 산책도 좋아하는 우리 토리를 아래에 텍스트로 띄워줘. 그리고 마지막으로 하이파이브 하는 장면으로 우리 예쁜 토리입니다. 텍스트를 아래에 띄우면서 끝내줘.



"영상 편집 AI면, LLM한테 영상이랑 요청을 통째로 주면 되는 거 아냐?"

 

매번 같은 질문을 하게 됩니다. 요즘 멀티모달 모델은 영상도 보고 말도 알아듣습니다. 하지만 이 출발점에는 함정이 있습니다. 같은 요청을 두 번 돌리면 다른 결과가 나오고, 프레임 단위 타이밍은 어긋나고, 왜 그렇게 잘렸는지 아무도 설명할 수 없습니다. 그래서 역할을 분리했습니다.

 

LLM에게는 '의미'만 맡긴다.
픽셀, 프레임, 타이밍은 전부 결정론 함수가 만진다.

 

지난 편에서 강아지를 찾고(검출), 다시 알아보고(재식별), 움직임을 재는(모션) 첫 레이어를 세웠습니다. 이번 편은 그 위에 올라가는 나머지, 동작 판별(네번째 단계), 편집 실행(여섯번째 단계), 그리고 자연어 한 문단이 완성 숏폼이 되는 통합 테스트까지 다룹니다. 다섯번째 단계(내레이션)를 왜 건너뛰었는지도요. 결론부터 말하면 이번 구간에서 배운 가장 값진 교훈은 이것입니다. LLM은 못 하는 일에서 아주 자신있게 틀립니다.

LLM Interpreter

4. 동작을 판별하다 - LLM에게 오지선다를

네번째 단계는 동작 판별입니다. 모션 곡선이 "움직인다"까지는 말해주지만, 그게 달리기인지 걷기인지 점프인지는 모릅니다. 여기서 처음으로 로컬 LLM(Gemma)이 등장합니다.

 

방식이 재미있었습니다. LLM에게 "이 강아지 뭐 하고 있어?"라고 서술형으로 묻지 않습니다. 오지선다 객관식(점프·달리기·걷기·앉기·엎드림)으로 묻고, 답의 첫 토큰 확률(logprob)을 읽습니다. 동적 동작들의 확률 합과 정적 동작들의 확률 합을 비교해 "동적인가 정적인가"부터 확정합니다. 서술형 답변은 파싱도 채점도 어렵지만, 객관식 확률은 숫자라서 파이프라인이 다룰 수 있습니다.

 

여기에 안전장치를 겹칩니다. LLM 판정이 모션 곡선과 일치하고 확신도 높을 때만 확정(commit), 아니면 모름(uncertain)으로 폴백. 네번째 단계는 혼자 다 맞혀야 하는 시험이 아니라, 애매하면 모르겠다고 말해도 되는 과정입니다. 모호함은 상위 레이어가 흡수합니다.

 

정답 라벨은 이번에도 사람이 만들었습니다. 모션 곡선으로 구간 후보를 자동으로 잘라 사람은 동작 이름만 채우게 해 품은 줄였지만 라벨 자체를 AI에게 맡기지는 않았습니다. 모델이 모델을 채점하는 자기참조가 되니까요.

 

결과는 확정된 구간의 동적/정적 정확도 1.00, 모름 비율 0.15(나쁜 footage 제외 시 0.07). 게이트(정확도 >= 0.90, 모름 <= 0.25) 통과입니다. 정직하게 적어두면 100%는 테스트 영상의 경계가 뚜렷했던 덕도 있습니다. 쉬운 건 확정하고 애매한 건 물러선다. 설계가 의도대로 작동했습니다.


5. 다섯번째 단계를 건너뛰다 - 완결된 경로부터

원래 설계에는 저작 모드가 두 개 있습니다.

  • 모드 A (편집만) - "인트로 몇 초, 다음에 노는 장면…" 같은 편집 요청만으로 자막 숏폼을 만듭니다. TTS가 필요 없습니다.
  • 모드 B (내레이션) - 대본을 쓰면 TTS 음성이 깔리고, 영상이 거기에 맞춰 편집됩니다. 다섯번째 단계(TTS)가 필요합니다.

그래서 일단 다섯번째 단계는 스킵하였습니다. 우선순위에서 뺐습니다. 이유는 단순합니다. 모드 A만으로도 처음부터 끝까지 완결된 제품 경로 하나가 성립하기 때문입니다. 부품을 하나 더 얹어 두 경로를 어설프게 구현하는 것보다 한 경로를 끝까지 뚫어 raw 폰 영상 -> 완성 숏폼을 증명하는 쪽이 먼저였습니다.


6. 자연어를 편집으로 - 번역가와 실행자

여섯번째 단계는 편집 실행입니다. 편집 요청 한 문단이 들어오면 LLM이 이를 블록 시퀀스로 번역합니다. "먼저 인트로, 그 다음 노는 장면 10초, 마지막에 하이파이브" — 블록마다 어떤 구간을(동적/정적), 몇 초로, 어떤 자막과 전환으로 쓸지가 담긴 편집 계획이 됩니다.

 

그리고 LLM의 일은 거기서 끝납니다. 계획을 받아 실제로 자르고 붙이고 줌하는 것은 전부 ffmpeg와 OpenCV, 즉 결정론 코드입니다. 같은 입력이면 같은 출력이 나옵니다.

 

실행 쪽에서 제일 고생한 건 줌이었습니다. 점차 확대되는 줌을 ffmpeg 내장 필터로 만들면 정수 반올림 때문에 화면이 부들부들 떨렸습니다. 세 번 갈아엎은 끝에 OpenCV로 float 보간 + 서브픽셀 렌더링을 직접 구현해 떨림을 잡았습니다. 한글 자막은 이 ffmpeg 빌드에 drawtext가 없어 PIL로 텍스트 이미지를 만들어 overlay로 태웠는데, 오히려 폰트와 스타일이 자유로워졌습니다. 출력은 분석용 저해상도가 아니라 원본 화질에서 다시 추출하고, 가로 / 세로가 섞인 폰 영상은 9:16 세로 + 블러 배경으로 통일했습니다.

 

편집 감각도 코드에 넣었습니다. 같은 장면을 잘게 쪼개 여기저기 흩뿌려진 영상이 나왔는데 어지러웠습니다. 소스당 한 컷, 길게. 이런 선호도 결정론 규칙이라 일관되게 지켜졌습니다.


7. 통합 테스트 - LLM의 자신감은 증거가 아니다

이제 전체를 처음부터 끝까지 돌립니다. 폰에서 갓 꺼낸 raw 영상 9개, 그리고 편집 요청 한 문단, "우리 토리를 소개합니다. 가만히 있는 인트로, 카페에서 노는 장면 10초, 산책, 비 오는 날, 밤에 달리기 합쳐서 10초, 하이파이브로 엔딩." 목표는 개발용 정답 라벨 없이 검출 결과만으로 도는 26.5초 세로 숏폼입니다.

 

당연히 한 번에 되지 않았습니다. 이 디버깅이 이번 편의 하이라이트입니다.

 

하이파이브가 사라졌습니다. 하이파이브는 앉아서 발을 드는 재주라, 오지선다(점프, 달리기, 걷기, 앉기, 엎드림)에는 자리가 없습니다. LLM은 가장 가까운 "앉기"로 분류했고, 동적 장면을 찾던 엔딩 블록은 빈손이 됐습니다. 수정은 라벨셋에 재주(묘기) 축을 추가하고, 편집 계획이 "묘기 장면"을 직접 고를 수 있게 했습니다. 이건 모델의 문제가 아니라 라벨셋 설계의 문제였습니다.

 

진짜 문제는 다음이었습니다. "가만히 있는 인트로"에 밤에 질주하는 영상이 들어왔습니다. 어둡고 흐릿한 밤 크롭을 LLM이 "정적"이라고 판단했습니다. 그것도 확신도 0.99로 판정한 겁니다. 처음엔 LLM과 모션 곡선의 판정을 확신도만큼 가중 투표시키는 절충안을 시도했다가 기각했습니다. LLM이 스스로 헷갈려할 때(확신 0.51)는 투표가 작동하지만, 확신하며 틀릴 때(0.99)는 투표가 오히려 오답에 힘을 실어줬습니다.

 

핵심 통찰은 이겁니다. LLM이 보는 건 정지된 크롭입니다. 흐릿하게 찍힌 강아지 사진 한 장으로는 뛰는 중인지 서 있는지 원리적으로 알 수 없습니다. 반면 모션 곡선(세번째 단계)은 카메라 움직임을 빼고 프레임 사이의 변화를 직접 잽니다. 그래서 절충 대신 역할을 완전히 분리했습니다.

축 담당 이유
동적/정적 (모션) 모션 곡선 전담 움직임의 직접 측정
동작 이름·재주 (의미) LLM 전담 선명한 한 장의 의미 판정

LLM은 밤 크롭을 여전히 오판합니다. 하지만 이제 그 오판이 결과를 흔들지 못합니다. 못 하는 일을 아예 맡기지 않았으니까요. 재테스트 결과는 인트로, 카페 두 영상, 밤 질주 포함, 하이파이브 엔딩까지 전부 정상. 서로 물고 물리던 문제들이 역할 분리 하나로 동시에 풀렸습니다.

 


맺음말

여기까지로 자연어 한 문단에서 완성 숏폼으로 파이프라인이 생겼습니다.

  • 동작 판별은 오지선다 확률로 묻되, 애매하면 물러서게 하고,
  • 내레이션은 완결 경로를 먼저 증명하기 위해 뒤로 미루고,
  • 편집 실행은 LLM이 번역한 계획을 결정론 코드가 재현 가능하게 렌더하고,
  • 통합 테스트에서 LLM이 못 하는 축(모션)을 측정에게 완전히 넘겼습니다.

지난 편의 교훈이 눈보다 정답지였다면 이번 편은 그 연장입니다. LLM의 확신이 보는 것과 같이 증거가 아닙니다. 측정할 수 있는 것은 측정으로 판정하고, LLM에게는 의미의 번역만 맡긴다. 이러한 설계가 흐릿한 밤에 질주하는 영상 하나에서 실제로 값을 했습니다.

 

남은 것은 뒤로 미룬 다섯번째 단계(TTS 내레이션), 수동으로 적고 있는 장면 태그의 자동화, 그리고 이 파이프라인을 제품으로 감싸는 일입니다. 다음 편에서 이어가겠습니다.


이 글에서 다룬 고정 함수 분류층은 ProjectDavid의 일부입니다.

전체를 바닥부터 만드는 과정은 강의 3부작으로 정리하고 있고, 10월에 오픈 예정입니다.

→ 오픈 알림 받기


다음글

 

로컬 TTS(mlx-audio)로 영상 타임라인 컨트롤 - Narration First AI

GitHub - chessire/shelter-puppyContribute to chessire/shelter-puppy development by creating an account on GitHub.github.com목소리를 얹는 순간,화면이 음성에 맞춰 늘어나고,타임라인은 음성이 컨트롤합니다. 우선 작업 결과

chessire.tistory.com

이전글

 

YOLO11 해상도 함정: 골든셋으로 검증 - Measurement First AI

좋은 모델이 좋은 파이프라인을 만드는 게 아닙니다.좋은 채점 기준이,모델을 갈아끼울 수 있게 해줍니다. "AI 영상 분석이면 일단 모델부터 돌려보는 거 아냐?" 틀린 말은 아닙니다. YOLO를 붙이

chessire.tistory.com

관련글

 

오픈소스 LLM vs 빅테크 LLM API: AI 사업의 비용과 리스크

모델은 커모디티가 된다.마진은 판단에 남는다. 2025년 2월, OpenAI는 당시 가장 비싼 모델 GPT-4.5를 출시했습니다. 백만 토큰당 입력 75 달러, 출력 150 달러. 그리고 다섯 달이 지나기 전, API에서 제거

chessire.tistory.com