
25년 된 게임 인트로를 실제 1080p로 AI 리마스터링하며 배운 점 — 모델보다 소스가 더 중요하다는 사실
요약
25년 된 고전 게임의 인트로를 AI를 활용해 1080p로 리마스터링하며 겪은 기술적 도전과 해결책을 다룹니다. 모델 크기보다 VAE 디코드 단계의 병목 현상과 latent noise 조절을 통한 깜빡임 제어가 더 중요함을 강조합니다.
핵심 포인트
- 모델 크기(3B vs 7B)보다 fp16 VAE 디코드 단계가 실행 시간의 주요 병목임
- 영상 깜빡임 해결을 위해 batch_size 조절보다 latent_noise_scale 조절이 효과적임
- AMD iGPU 환경에서 MIOpen 프리징 방지를 위한 특정 ROCm 설정 필요
- VRAM 부족 시 --vae_decode_tiled 옵션을 통해 OOM 문제 해결 가능
- 최상의 품질을 위해 장면 단위 분할 및 콘텐츠별 파라미터 개별 조정 권장
저는 2000년에 출시된 우주 전략 게임인 _Imperium Galactica 2 – Solarian_의 인트로를 AI를 사용하여 깨끗한 1080p로 리마스터링하는 데 너무 많은 시간을 보냈습니다. 그 과정에서 SeedVR2, temporal flicker (시간적 깜빡임), 작은 AMD iGPU에서 diffusion (확산) 모델 실행하기, 그리고
시스템 ROCm이 필요 없는 독립형 ROCm wheel
pip install --index-url https://rocm.nightlies.amd.com/v2/gfx1151/ torch torchaudio torchvision
export HSA_OVERRIDE_GFX_VERSION=11.5.1 # 11.0.0 -> hipErrorInvalidImage; unset -> GPU invisible
...
그 MIOpen 프리징(hang) 때문에 FAST find-mode를 알아내기 전까지, 진전 없이 GPU 점유율만 100%인 상태를 약 27분 동안 멍하니 바라봐야 했습니다. 만약 Strix급 iGPU에서 SeedVR2(또는 MIOpen을 많이 사용하는 다른 작업)를 실행한다면, 이 두 줄을 꼭 기억하세요. :D
SeedVR2, 그리고 모델 크기가 거의 중요하지 않은 이유
SeedVR2가 가장 자연스러운 결과(실제 표면 질감, 시간적 인지력)를 보여주었습니다. 3B 모델과 7B 모델을 비교해 보았는데... 실행 시간이 거의 동일했습니다. 왜일까요?
병목 현상(bottleneck)은 diffusion transformer (DiT)가 아니라 fp16 VAE decode 단계에서 발생합니다. DiT를 양자화(Quantizing)하거나 크기를 줄여도 실행 시간은 반올림 오차 수준으로만 변합니다. 제 눈에는 3B-FP8 모델이 더 자연스러워 보여서 그것을 사용했습니다.
깜빡임(flicker) 추적: batch_size가 아니라 latent_noise 문제
인트로에는 우주 전투 장면이 많이 포함되어 있습니다.
작고 빠르게 움직이는 물체들(작은 전투기, 떠다니는 그림자 등)이 깜빡거렸습니다. 모델이 매 프레임마다 그들의 디테일을 새로 만들어내기 때문입니다 (하지만 주의 깊게 살펴보니 원본 장면 자체에도 유사한 깜빡임이 있었습니다 -> 업스케일링을 하니 그 현상이 더 눈에 띄게 된 것입니다). 직관적인 해결책은 더 큰 temporal batch를 사용하는 것이지만... 효과가 있긴 해도 곧 **정체기(plateaus)**에 도달합니다.
실제로 효과가 있었던 것은 latent_noise_scale 조절이었습니다. 얼굴 부분은 낮게 설정하여(디테일 유지), 빠른 움직임이 있는 부분은 높게 설정하여(프레임당 재-환각(re-hallucination) 억제) 조절했습니다. 효과가 없었던 것들은 다음과 같습니다: temporal deflicker 필터(motion-compensated 방식이 아님)와 RIFE 프레임 보간(RIFE frame interpolation, 부드럽기는 하지만 깜빡임이 생성된 프레임에 그대로 박혀 있으며, 60 fps는 약간의 멀미를 유발했습니다).
가장 심각한 사례는 오프닝 우주 전투에 등장하는 작은 전투기입니다 (영상 ~0:58 지점).
왼쪽 = 원본, 오른쪽 = 리마스터:
Oh, 그리고 낮은/공유 VRAM GPU에서는 큰 배치(batch)가 VAE 디코드(decode) 시 OOM을 발생시킵니다 — --vae_decode_tiled 옵션이 이를 해결해 줍니다.
장면별로 (Scene-by-scene), 인간의 개입을 거쳐
최상의 결과를 얻으려면 장면 단위로 처리해야 합니다: 모든 컷마다 분할하고, 각 장면에 대해 하나의 배치(batch)를 실행하며, 콘텐츠에 따라 latent_noise 값을 조정합니다 (얼굴은 ~0.05, 빠른 액션은 ~0.15, 작고 빠른 물체는 ~0.20). 하지만 이 컷 목록을 얻는 것이 진짜 어려움이었습니다:
- ffmpeg의 장면 필터(scene filter)는 시각적으로 유사한 우주 장면 사이의 컷과 과도하게 분할된 폭발 장면들을 놓쳤습니다. '72초짜리 장면'은 실제로는 약 5개의 장면으로 구성되어 있었습니다.
- PySceneDetect의 AdaptiveDetector는 몇 개의 수동 표시된 컷을 기준으로 보정(calibrated)한 결과, 훨씬 더 좋은 성능을 보였습니다.
- 오프닝의 '컷'은 **1초짜리 크로스 디졸브(cross-dissolve)**였는데, 콘텐츠 감지기에는 눈에 띄지 않았고 오직 제 눈만이 그것을 포착했습니다.
작동했던 워크플로우는 다음과 같습니다: 자동 감지 → 컨택 시트(contact sheet) 렌더링 (장면당 하나의 썸네일) → 검증 및 수동 편집. 자동 장면 감지는 '초안'으로 취급해야 합니다.
수동 편집의 대부분은 잘못된 컷들을 '병합(merging)'하는 것이었습니다. 제가 정한 규칙은 이렇습니다: 카메라/피사체가
또 다른 함정: **프레임을 정확히 나누는 것(split frame-exactly)**입니다. 시간 기반의 -ss/-to 옵션은 컷당 약 1프레임씩을 추가하여, 비디오 전체에 걸쳐 약 2초의 오차를 발생시켰고 립싱크(lip-sync)를 망가뜨렸습니다. trim=start_frame:end_frame을 사용하면 정확한 소스 프레임 수를 얻을 수 있습니다. 이는 엄청난 시간을 아껴줄 수 있으며, 만약 비디오 전체가 렌더링된 후에야 이 문제를 발견한다면 정말 화가 날 것입니다. :)
수치로 보는 데이터: 5초간의 시험장
사실 저는 위의 내용들을 3.5분 전체 인트로를 통해 발견한 것이 아닙니다. 모든 결정은 단 하나의 5초짜리 클립을 대상으로 이루어졌으며, 이 클립을 iGPU를 통해 파이프라인에 반복해서 돌려보았습니다. 소스 선택, 3B 대 7B, fp8 대 Q4 대 fp16, 배치(batch) 5 → 25 → 49 → 73, 타일링(tiling) On/Off, latent_noise 0 → 0.1 → 0.15, 그리고 디플리커(deflicker)와 RIFE의 시행착오까지 — 그 하나의 클립에 대해 대략 12번의 전체 SeedVR2 실행(그중 2번은 OOM(Out of Memory)이 발생하여 중단됨)을 거쳤으며, 레시피를 조정하는 데만 iGPU 연산 시간이 약 16시간 소요되었습니다.
이 과정에서 약 15개의 병렬 비교(side-by-side) 릴이 만들어졌습니다. 진정으로 즐거웠던 부분은 동일한 5초를 동시에 2가지, 3가지, 심지어 4가지 방식으로 실행하는 것을 지켜보는 것이었습니다:
짧은 클립으로 먼저 반복 작업(Iterating)을 하는 것이 핵심 비결입니다. 인내심을 가질 수 있을 만큼 비용이 저렴하면서도, 시간적 동작(temporal behavior)을 판단할 수 있을 만큼은 충분히 길기 때문입니다. 그 5초가 제대로 보이기 시작했을 때 비로소 전체 렌더링을 위해 1분을 할당했습니다.
하드웨어의 현실: iGPU의 나날 vs 클라우드의 시간
890M iGPU에서 전체 작업을 수행할 경우 약 74시간(~프레임당 약 40초)이 소요될 것으로 추정되었습니다. 그래서 저는 vast.ai에서 시간당 약 1.1달러에 **RTX PRO 6000 (Blackwell, 96 GB)**를 대여했습니다:
- Blackwell (sm_120)에는 CUDA 12.8 torch (PyTorch 2.12 / cuDNN 9.2)가 필요합니다.
- 인코딩(Encode) 시간이 배치당 약 12분에서 약 28초/배치로 단축되었습니다. 96 GB를 사용하면 타일링(tiling) 없이 전체 샷(whole-shot) 단위로 배치를 처리할 수 있습니다.
- 3.5분 전체 인트로: 2시간 21분 소요, 비용은 약 $2.70.
시간을 잡아먹었던 몇 가지 클라우드 사용 시 주의사항(gotchas)이 있었습니다: pkill -f inference_cli.py 명령어가 자기 자신의 커맨드 라인과 일치하여 셸(shell)을 종료시켜 버렸고, "홈 폴더만" 새 서버로 옮겼더니 Python 의존성(deps)들이 남겨진 채로 왔으며, 대여한 서버의 오래된 CA 번들(CA bundle) 때문에 curl이 유효한 인증서를 거부했습니다 (certificate has expired).
와이드 샷(wide shot)에서 전체 렌더링 결과가 어떻게 나오는지 보여드립니다. 동일한 50/50 분할이며, 왼쪽이 리마스터링된 결과입니다:
마무리 + YouTube 팁
오디오를 무음 마스터 파일에 멀티플렉싱(Mux) 하세요 (-c:v copy). 프레임 단위로 정확하게 분할했기 때문에 싱크가 유지됩니다. 그리고 업로드할 때는 먼저 4K로 업스케일(upscale) 하세요. YouTube는 해상도 계층에 따라 비트레이트(bitrate)를 할당하므로, 4K로 업로드하면 YouTube의 재인코딩(re-encode) 과정을 거치더라도 1080p 콘텐츠를 훨씬 더 잘 보존할 수 있습니다.
레시피 (리포지토리 사용)
- 가장 품질이 좋은 소스를 선택하세요. 오디오는 별도로 유지합니다.
- 샷(shot) 감지 → 수동 검증 (콘택트 시트 활용); 디졸브(dissolve) 효과를 주의 깊게 확인하세요.
- 프레임 단위의 정확한 분할 → **샷별
latent_noise**를 적용한 per-shot SeedVR2 (3B-FP8), 배치는 샷 길이에 맞춥니다. - VRAM이 부족한가요?
--vae_decode_tiled를 사용하세요. 대용량 클라우드 GPU를 사용 중인가요? 전체 샷(whole-shot) 단위 배치를 사용하세요. - 연결(Concat) → 오디오 멀티플렉싱(mux) → 4K로 업로드.
범용적이고 어디서든 사용 가능한 파이프라인 (CUDA 및 AMD ROCm 지원), 샷 감지기(shot detector), 그리고 전체 기술 문서가 모두 여기에 있습니다:
👉 https://github.com/andyskw/ig2-solarian-seedvr2-remaster
크레딧: SeedVR2 (ByteDance Seed · NumZ · AInVFX), PySceneDetect.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기


