
Claude Code (Fable 5)를 사용하여 Mac에서 오픈 소스 비디오 모델을 로컬로 실행하기
요약
Claude Code(Fable 5)를 활용하여 CUDA 전용으로 설계된 오픈 소스 비디오 모델을 Apple Silicon(Mac) 환경에서 실행할 수 있도록 포팅하는 과정을 다룹니다. 하드코딩된 CUDA 설정을 MPS 및 SDPA로 수정하여 로컬 환경에서의 실행 가능성을 입증했습니다.
핵심 포인트
- Claude Code를 통해 CUDA 의존성이 높은 코드를 MPS 지원 코드로 신속하게 수정
- Apple Silicon에서 1.3B 규모의 오픈 소스 비디오 모델 로컬 실행 성공
- 스택 트레이스 분석을 통한 하드코딩된 디바이스 타입 및 Flash Attention 오류 해결
- 클라우드 API 없이 로컬 GPU/NPU를 활용한 모델 구동 워크플로우 공유
긴 글이며, 전체 워크플로우를 공유합니다. 클라우드 API를 대여하는 대신 비디오 모델을 로컬에서 실행하고 싶어서, 오픈 소스 모델(LingBot-Video, Apache-2.0)을 선택했고 Claude Code (Fable 5 실행)를 사용하여 이를 제 Mac으로 포팅하고 실제로 실행했습니다. 대부분의 "Mac에서 실행됨"이라는 주장들이 수치와 품질 차이를 생략하기 때문에, 장단점을 포함한 실제 결과를 게시합니다.
먼저, 전체 크기 모델(full-size model)이 원래 해야 하는 일:
솔직하게 기대치를 미리 설정하자면: 이 클립들은 해당 프로젝트의 자체적인 전체 크기 모델 출력물이며, 제 것이 아닙니다. 이것이 목표이며, 제가 왜 이것을 로컬에서 실행하고 싶었는지에 대한 이유입니다. 위 클립은 전체 크기 모델의 공식 데모(크롬 볼, 그 다음 물방울)로 시작합니다. 그것들은 제 결과물이 아니며, 영상 내에 그렇게 표시되어 있습니다. 모델이 원래 무엇을 해야 하는지 보여드리기 위해 이를 보여주는 것입니다. 그것은 실제 GPU에서 돌아가는 거대한 모델입니다. 저는 그것을 가지고 있지 않습니다. 여기서 솔직한 부분으로 넘어갑니다.
제 로컬 빌드가 실제로 생성하는 것:
노트북에서 실행되는 것은 작은 1.3B 변체(variant)이므로, 품질은 명확하게 저 클립들보다 낮습니다: 더 낮은 해상도, 더 짧은 길이, 더 거친 품질입니다. 여기 제 기기가 실제로 생성한 실제 프레임이 있습니다. 선별된 것이 아닙니다: 위 클립의 마지막 몇 초가 제 실제 로컬 출력물입니다: 제 Mac에서 192x320으로 실행되는 작은 1.3B 모델입니다(마찬가지로 표시됨). 동일한 모델 제품군이지만, 체급이 매우 다릅니다. 데모 영상이 아닙니다. 하지만 이는 Apple Silicon에서 엔드 투 엔드(end-to-end)로 실행되며 실제 프레임을 생성하고 있습니다. 2주 전만 해도 이 저장소(repo)가 모든 곳에서 CUDA를 가정하고 Mac에서 임포트(import) 시 오류가 발생했기 때문에 전혀 할 수 없었던 일입니다.
Claude Code (Fable 5)를 이용한 포팅:
이 부분이 실제 "Claude로 빌드함"에 해당하는 부분이었으며, 놀라울 정도로 간단했습니다. 파일 3개, 약 12줄 정도였습니다. 전체 작업은 CUDA 전용 저장소에 장치가 다른 것일 수도 있다는 것을 가르치는 것이었습니다: 장치 선택(Device selection)이 오직 cuda 또는 cpu만 선택하도록 되어 있었습니다. mps 브랜치를 추가하여 Apple Silicon이 cpu로 넘어가기 전에 선택되도록 했습니다.
몇몇 지점(transformer wrapper 및 autocast를 강제로 비활성화하는 RoPE block)에서 Autocast가 device_type="cuda"로 하드코딩되어 있었습니다. 이를 리터럴 문자열 대신 실제 디바이스 타입(device type)을 사용하도록 변경했습니다. 혼자서 했다면 한 시간은 걸렸을 부분은 다음과 같습니다: 텍스트 인코더(text encoder)는 기본값이 CUDA 전용인 flash_attention_3로 설정된 Qwen3-VL인데, 이 때문에 MPS 환경에서는 로드 시점에 바로 오류가 발생하며 터져버립니다. CUDA가 없을 때는 sdpa로 폴백(fallback)되도록 했습니다. Fable 5는 스택 트레이스(stack traces)를 읽고, 코드가 CUDA를 가정하고 있는 모든 지점을 찾아내어 다른 부분은 건드리지 않고 정확히 해당 부분들만 수정했습니다. 여러분도 자신의 그래픽 카드에서 1.3B 모델을 시도해 볼 수 있도록 세 개의 파일로 구성된 전체 diff를 공유합니다:
diff --git a/lingbot_video/pipeline_lingbot_video.py b/lingbot_video/pipeline_lingbot_video.py index 183b0a0..2e7e76f 100644 --- a/lingbot_video/pipeline_lingbot_video.py +++ b/lingbot_video/pipeline_lingbot_video.py @@ -71,9 +71,9 @@ def _transformer_timestep(timestep: torch.Tensor, transformer_dtype: torch.dtype def _transformer_autocast(device: torch.device, transformer_dtype: torch.dtype): - if device.type != "cuda" or transformer_dtype not in {torch.bfloat16, torch.float16}: + if device.type not in {"cuda", "mps"} or transformer_dtype not in {torch.bfloat16, torch.float16}: return nullcontext() - return torch.autocast(device_type="cuda", dtype=transformer_dtype) + return torch.autocast(device_type=device.type, dtype=transformer_dtype) def _module_device(module: torch.nn.Module) -> torch.device: diff --git a/lingbot_video/runner.py b/lingbot_video/runner.py index 99657b1..b401866 100644 --- a/lingbot_video/runner.py +++ b/lingbot_video/runner.py @@ -287,9 +287,11 @@ def _distributed_env() -> tuple[int, int, int]: def _default_device() -> torch.device: - if not torch.cuda.is_available(): - return torch.device("cpu") - return torch.device("cuda", torch.cuda.current_device()) + if torch.cuda.is_available(): + return torch.device("cuda", torch.cuda.current_device()) + if torch.backends.mps.is_available(): + return torch.device("mps") +
return torch.device("cpu") def _init_parallel( @@ -496,7 +498,10 @@ def _patch_qwen3vl_from_pretrained(): return original_from_pretrained = Qwen3VLForConditionalGeneration.from_pretrained - attn_implementation = os.environ.get("LINGBOT_QWEN_ATTN_IMPLEMENTATION", "flash_attention_3") + attn_implementation = os.environ.get( + "LINGBOT_QWEN_ATTN_IMPLEMENTATION", + "flash_attention_3" if torch.cuda.is_available() else "sdpa", + ) u/classmethod def patched_from_pretrained(cls, pretrained_model_name_or_path, *args, **kwargs): diff --git a/lingbot_video/transformer_lingbot_video.py b/lingbot_video/transformer_lingbot_video.py index a996bac..f06f163 100644 --- a/lingbot_video/transformer_lingbot_video.py +++ b/lingbot_video/transformer_lingbot_video.py @@ -114,7 +114,7 @@ class LingBotVideoRMSNorm(nn.Module): def apply_rotary_emb(x: torch.Tensor, freqs_cis: torch.Tensor) -> torch.Tensor: """Apply complex RoPE to (B, S, H, D) attention tensors.""" - with torch.amp.autocast("cuda", enabled=False): + with torch.amp.autocast(x.device.type, enabled=False): x_c = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2)) out = torch.view_as_real(x_c * freqs_cis.unsqueeze(2)).flatten(3) return out.type_as(x) One real detail it did NOT paper over: the text encoder (Qwen3-VL-4B) is 8.3GB on its own, bigger than the 2.6GB transformer it feeds, so on a 64GB laptop most of your memory goes to the conditioner, not the generator. Real numbers, from the run not the paper Machine: MacBook Pro, M5 Pro, 64GB, macOS 26.4. Weights about 12GB. Runtime: transformer and text encoder in bf16, VAE in float32. Smoke test, 192x320, 5 frames, 8 steps: about 5.5s at ~1.45 it/s. Produced the frame above. Real generation, 480x832, 49 frames, 20 steps: about 35s per step (the log shows 34 to 38s across the run). I let it reach step 16 of 20 at the 9:30 mark and stopped it there, so the VAE never decoded and I do not have a finished 480p clip yet. Not going to pretend I do.
20단계의 전체 실행과 디코딩(decode)을 포함하면 2초 분량의 클립을 만드는 데 대략 12~13분이 소요되며, 이것이 이 하드웨어의 솔직한 한계치입니다. 제가 얻은 결론은 다음과 같습니다. 포팅(port) 작업은 매우 작았습니다. 파일 3개와 수십 줄의 코드뿐이었습니다. 유일하게 명확하지 않았던 수정 사항은 텍스트 인코더(text encoder)에 있는 CUDA 전용 플래시 어텐션(flash-attention)이었습니다. 그 외의 모든 것은 단순히 디바이스 문자열(device-string)에 대한 가정들이었으며, 이는 전체 스택 트레이스(stack trace)를 읽을 수 있는 에이전트가 매우 잘 수행할 수 있는 지루하고 분산된 작업의 전형적인 사례였습니다. 노트북의 솔직한 한계는 짧은 저해상도 클립입니다. 전체 크기의 품질(상단의 클립들)을 얻으려면 실제 GPU가 필요합니다. 이 작은 모델은 "이것을 아예 실행할 수 있는가"에 대한 승리이지, "클라우드를 대체할 수 있는가"에 대한 승리가 아닙니다. 따라서 자신이 어떤 것을 주장하고 있는지 명확히 할 가치가 있습니다. 가장 큰 승수(multiplier)는 Claude Code가 지루한 전체 포팅 작업을 맡게 함으로써, 제가 디바이스 오류를 해결하며 고생하는 대신 무엇을 테스트할지 결정하는 데 시간을 쓸 수 있었다는 점입니다. 상단의 세련된 클립들은 전체 크기 모델의 공식 데모이며, 거친 프레임은 저의 로컬 1.3B 실행 결과로 체급이 다릅니다. 누군가 M4나 3060에서 동일한 1.3B 모델을 실행한다면 수치를 비교해 보고 싶습니다. submitted by /u/SecureSecretary683 [link] [comments]
AI 자동 생성 콘텐츠
본 콘텐츠는 r/ClaudeAI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기