개요

AI 비디오 생성은 막대한 계산 리소스를 요구합니다. Open-Sora는 11B 파라미터 모델로 768px 비디오를 생성할 때 80GB 이상의 GPU 메모리가 필요할 수 있습니다. 이번 포스트에서는 Open-Sora가 사용하는 다양한 성능 최적화 기법을 상세히 분석해보겠습니다.

메모리 최적화 전략

1. CUDA 메모리 할당 최적화

# Dockerfile에서의 CUDA 메모리 설정
ENV PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:8,expandable_segments:True,garbage_collection_threshold:0.95
ENV PYTORCH_NO_CUDA_MEMORY_CACHING=1
ENV CUDA_MEMORY_FRACTION=0.4
ENV PYTORCH_CUDA_ALLOC_CONF=backend=cudaMallocAsync,max_split_size_mb:16

각 설정의 효과

max_split_size_mb:8

  • 목적: 메모리 조각화 방지
  • 효과: 큰 메모리 블록을 8MB 단위로 분할
  • 장점: 메모리 부족 에러 감소

expandable_segments:True

  • 목적: 동적 메모리 확장
  • 효과: 필요에 따라 메모리 세그먼트 확장
  • 장점: 메모리 사용량 최적화

garbage_collection_threshold:0.95

  • 목적: 적극적인 가비지 컬렉션
  • 효과: 메모리 사용률 95% 도달 시 정리
  • 장점: 메모리 누수 방지

cudaMallocAsync 백엔드

  • 목적: 비동기 메모리 할당
  • 효과: 메모리 할당/해제 속도 향상
  • 장점: GPU 아이들 타임 감소

2. 데이터 타입 최적화

# 모델 정밀도 최적화
vae = vae.to(torch.bfloat16).eval()
stdit = stdit.to(torch.bfloat16).eval()

# T5는 fp32 유지 (정확도 중요)
text_encoder.t5.model = text_encoder.t5.model.eval()

bfloat16 vs float32 vs float16

데이터 타입 메모리 사용량 속도 정확도 수치 안정성
float32 100% (기준) 1.0x ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
bfloat16 50% 1.5-2x ⭐⭐⭐⭐ ⭐⭐⭐⭐
float16 50% 1.5-2x ⭐⭐⭐ ⭐⭐⭐

bfloat16 선택 이유:

  • 메모리 절약: 50% 메모리 감소
  • 속도 향상: 1.5-2배 빠른 연산
  • 안정성: float16보다 넓은 지수 범위
  • 하드웨어 지원: 최신 GPU에서 네이티브 지원

3. 그래디언트 및 추론 최적화

# 추론 모드 활성화
with torch.inference_mode():
    # 그래디언트 계산 완전 비활성화
    samples = scheduler.sample(...)

# 모델 평가 모드
model.eval().requires_grad_(False)

# CUDA 캐시 정리
torch.cuda.empty_cache()

torch.inference_mode() vs torch.no_grad()

# torch.no_grad(): 그래디언트 계산만 비활성화
with torch.no_grad():
    output = model(input)

# torch.inference_mode(): 완전한 추론 최적화
with torch.inference_mode():
    output = model(input)  # 더 빠름, 메모리 절약

Flash Attention 최적화

1. Flash Attention 2 설치 및 활용

# Dockerfile에서 Flash Attention 설치
RUN pip install --no-cache-dir \
    flash-attn==2.2.3.post2 \
    --no-build-isolation \
    --config-settings="--global-option=build_ext" \
    --config-settings="--global-option=-j1" \
    --no-deps

2. Flash Attention의 성능 향상 원리

전통적인 Attention의 메모리 복잡도

# 표준 어텐션: O(n²) 메모리 복잡도
def standard_attention(Q, K, V):
    # Q @ K^T: [seq_len, seq_len] 매트릭스 생성
    attention_scores = torch.matmul(Q, K.transpose(-2, -1))  # O(n²) 메모리
    attention_weights = torch.softmax(attention_scores, dim=-1)
    output = torch.matmul(attention_weights, V)
    return output

Flash Attention의 최적화

# Flash Attention: O(n) 메모리 복잡도
def flash_attention_concept(Q, K, V, block_size=64):
    """Flash Attention 핵심 아이디어 (의사코드)"""
    seq_len = Q.shape[1]
    output = torch.zeros_like(Q)
    
    # 블록 단위로 처리 (메모리 효율성)
    for i in range(0, seq_len, block_size):
        for j in range(0, seq_len, block_size):
            # 작은 블록 단위로 어텐션 계산
            q_block = Q[:, i:i+block_size]
            k_block = K[:, j:j+block_size]
            v_block = V[:, j:j+block_size]
            
            # 블록 내에서만 어텐션 계산
            attention_block = compute_attention_block(q_block, k_block, v_block)
            output[:, i:i+block_size] += attention_block
    
    return output

성능 향상 결과

시퀀스 길이 표준 Attention Flash Attention 메모리 절약 속도 향상
512 100% 45% 55% 2.2x
1024 100% 25% 75% 4.0x
2048 100% 15% 85% 6.7x
4096 100% 8% 92% 12.5x

3. Flash Attention 3 고급 최적화

# 선택적으로 Flash Attention 3 설치
RUN git clone https://github.com/Dao-AILab/flash-attention && \
    cd flash-attention/hopper && \
    python setup.py install

Flash Attention 3의 추가 개선사항:

  • H100 GPU 최적화: Hopper 아키텍처 전용 최적화
  • 더 긴 시퀀스 지원: 32K+ 토큰 처리 가능
  • 낮은 정밀도 지원: FP8 연산 활용

xformers 메모리 최적화

1. xformers 설치 및 설정

# 메모리 최적화를 위한 xformers 설치
RUN pip install --no-cache-dir xformers==0.0.27.post2 --no-build-isolation

2. xformers 최적화 기법

Memory-Efficient Attention

import xformers.ops as xops

def memory_efficient_attention(Q, K, V, attention_mask=None):
    """xformers의 메모리 효율적 어텐션"""
    return xops.memory_efficient_attention(
        Q, K, V,
        attn_bias=attention_mask,
        scale=1.0 / math.sqrt(Q.shape[-1])
    )

Checkpointing 최적화

import xformers.checkpoint as checkpoint

class MemoryEfficientTransformer(nn.Module):
    def forward(self, x):
        # 메모리 절약을 위한 그래디언트 체크포인팅
        x = checkpoint.checkpoint(self.layer1, x)
        x = checkpoint.checkpoint(self.layer2, x)
        return x

3. 성능 비교

최적화 기법 메모리 사용량 속도 구현 복잡도
기본 PyTorch 100% 1.0x
xformers 60-70% 1.3-1.5x ⭐⭐
Flash Attention 40-50% 2.0-3.0x ⭐⭐⭐
Flash + xformers 30-40% 2.5-4.0x ⭐⭐⭐⭐

분산 처리 최적화

1. NCCL 통신 최적화

# 분산 훈련을 위한 NCCL 설정
ENV NCCL_DEBUG=INFO
ENV NCCL_IB_DISABLE=1
ENV NCCL_P2P_DISABLE=1
ENV NCCL_SOCKET_IFNAME=eth0
ENV NCCL_BLOCKING_WAIT=1

각 설정의 의미

NCCL_IB_DISABLE=1

  • 목적: InfiniBand 비활성화
  • 상황: 일반 이더넷 환경
  • 효과: 통신 안정성 향상

NCCL_P2P_DISABLE=1

  • 목적: P2P 통신 비활성화
  • 상황: 다중 노드 환경
  • 효과: 네트워크 병목 방지

2. 멀티 GPU 추론 전략

# 단일 GPU (256px)
torchrun --nproc_per_node 1 --standalone scripts/diffusion/inference.py \
    configs/diffusion/inference/t2i2v_256px.py \
    --save-dir samples --prompt "raining, sea"

# 8 GPU 분산 처리 (768px)
torchrun --nproc_per_node 8 --standalone scripts/diffusion/inference.py \
    configs/diffusion/inference/t2i2v_768px.py \
    --save-dir samples --prompt "raining, sea"

GPU별 성능 확장성

GPU 수 256px (시간/메모리) 768px (시간/메모리) 확장 효율성
1 GPU 60s / 52.5GB 1656s / 60.3GB -
2 GPU 40s / 44.3GB 863s / 48.3GB 75%
4 GPU 34s / 44.3GB 466s / 44.3GB 85%
8 GPU - 276s / 44.3GB 90%

컴파일 최적화

1. TensorFloat-32 (TF32) 활성화

# TF32 최적화 활성화
torch.backends.cuda.matmul.allow_tf32 = True
torch.backends.cudnn.allow_tf32 = True

TF32의 장점:

  • 자동 최적화: 코드 변경 없이 성능 향상
  • A100/H100 최적화: 최신 GPU에서 2-3배 속도 향상
  • 정확도 유지: float32와 거의 동일한 정확도

2. JIT 컴파일 최적화

# JIT 컴파일 비활성화 (Gradio 호환성)
torch.jit._state.disable()

# 또는 프로덕션에서 JIT 활용
@torch.jit.script
def optimized_function(x):
    return x * 2 + 1

동적 최적화 기법

1. 적응적 배치 크기

def adaptive_batch_size(model, input_shape, max_memory_gb=40):
    """GPU 메모리에 따른 적응적 배치 크기 설정"""
    batch_size = 1
    while True:
        try:
            # 테스트 배치로 메모리 사용량 확인
            test_input = torch.randn(batch_size, *input_shape).cuda()
            with torch.inference_mode():
                _ = model(test_input)
            
            # 메모리 사용량 체크
            memory_used = torch.cuda.memory_allocated() / 1024**3
            if memory_used > max_memory_gb * 0.8:  # 80% 임계값
                break
            
            batch_size += 1
        except RuntimeError as e:
            if "out of memory" in str(e):
                batch_size -= 1
                break
            raise e
    
    return max(1, batch_size)

2. 메모리 오프로딩

def memory_offloading_inference(model, input_data):
    """CPU-GPU 메모리 오프로딩"""
    # 모델을 CPU로 이동
    model.cpu()
    
    # 필요한 레이어만 GPU로 로드
    for layer_name, layer in model.named_modules():
        if "attention" in layer_name:  # 중요한 레이어만 GPU
            layer.cuda()
    
    # 추론 실행
    with torch.inference_mode():
        output = model(input_data.cuda())
    
    # 정리
    torch.cuda.empty_cache()
    return output.cpu()

실시간 모니터링 및 프로파일링

1. GPU 메모리 모니터링

def monitor_gpu_memory():
    """실시간 GPU 메모리 모니터링"""
    if torch.cuda.is_available():
        allocated = torch.cuda.memory_allocated() / 1024**3
        reserved = torch.cuda.memory_reserved() / 1024**3
        max_allocated = torch.cuda.max_memory_allocated() / 1024**3
        
        print(f"GPU Memory Usage:")
        print(f"  Allocated: {allocated:.2f} GB")
        print(f"  Reserved:  {reserved:.2f} GB")
        print(f"  Max Used:  {max_allocated:.2f} GB")
        
        # 메모리 사용률이 높으면 경고
        if allocated / reserved > 0.9:
            print("⚠️  GPU memory usage is high!")
        
        return {
            'allocated': allocated,
            'reserved': reserved,
            'max_allocated': max_allocated
        }

2. 성능 프로파일링

def profile_model_performance(model, input_data, num_runs=10):
    """모델 성능 프로파일링"""
    import time
    
    # 워밍업
    for _ in range(3):
        with torch.inference_mode():
            _ = model(input_data)
    
    torch.cuda.synchronize()
    
    # 실제 측정
    times = []
    for i in range(num_runs):
        start_time = time.time()
        
        with torch.inference_mode():
            output = model(input_data)
        
        torch.cuda.synchronize()
        end_time = time.time()
        
        times.append(end_time - start_time)
        print(f"Run {i+1}: {times[-1]:.3f}s")
    
    avg_time = sum(times) / len(times)
    std_time = (sum((t - avg_time)**2 for t in times) / len(times))**0.5
    
    print(f"\nAverage: {avg_time:.3f}s ± {std_time:.3f}s")
    return avg_time, std_time

환경별 최적화 가이드

1. 로컬 개발 환경 (RTX 3090/4090)

# 메모리 제한 설정
export PYTORCH_CUDA_ALLOC_CONF="max_split_size_mb:512,garbage_collection_threshold:0.8"

# 256px 해상도로 제한
python inference.py --resolution 256px --length 65 --offload True

2. 클라우드 환경 (A100/H100)

# 최대 성능 설정
export PYTORCH_CUDA_ALLOC_CONF="max_split_size_mb:8,expandable_segments:True"

# 768px 고해상도 지원
python inference.py --resolution 768px --length 97 --batch_size 2

3. 멀티 노드 클러스터

# 분산 설정
export NCCL_DEBUG=INFO
export NCCL_TREE_THRESHOLD=0

# 노드 간 통신 최적화
torchrun --nnodes=2 --nproc_per_node=8 --master_addr=192.168.1.100 \
    inference.py --distributed

성능 향상 결과 요약

최적화 전후 비교

최적화 기법 메모리 절약 속도 향상 구현 난이도 안정성
bfloat16 변환 50% 30-50% ⭐⭐⭐⭐
Flash Attention 60-80% 100-300% ⭐⭐⭐ ⭐⭐⭐⭐
xformers 30-40% 20-50% ⭐⭐ ⭐⭐⭐⭐
CUDA 메모리 최적화 20-30% 10-20% ⭐⭐ ⭐⭐⭐⭐⭐
분산 처리 - 200-600% ⭐⭐⭐⭐ ⭐⭐⭐

실제 벤치마크 결과

768px 비디오 생성 (97 프레임, 5초)
├── 최적화 전: 3000s (50분), 80GB 메모리, 1 GPU
├── 부분 최적화: 1656s (28분), 60GB 메모리, 1 GPU
└── 완전 최적화: 276s (5분), 44GB 메모리, 8 GPU

256px 비디오 생성 (97 프레임, 5초)
├── 최적화 전: 120s (2분), 60GB 메모리, 1 GPU
├── 부분 최적화: 60s (1분), 52GB 메모리, 1 GPU
└── 완전 최적화: 34s (34초), 44GB 메모리, 4 GPU

결론

Open-Sora의 성능 최적화는 다층적 접근법을 통해 달성됩니다.

핵심 최적화 전략:

  • 메모리 계층 최적화: CUDA 설정 → 데이터 타입 → 알고리즘
  • 계산 최적화: Flash Attention → xformers → 분산 처리
  • 시스템 최적화: TF32 → JIT → 하드웨어 특화

이러한 최적화 기법들은 다른 대규모 AI 모델에도 적용할 수 있는 범용적 방법론입니다. 특히 메모리 제약이 있는 환경에서 AI 모델을 운영할 때 필수적인 기술들입니다.

다음 포스트에서는 Open-Sora의 실제 사용법과 다양한 활용 예제를 살펴보겠습니다.


이 글이 도움이 되셨다면 공유해주세요! 궁금한 점이 있으시면 댓글로 남겨주시기 바랍니다.