Files
video-v1/vav2/docs/working/CUDA_API_Unification_Design.md
2025-10-07 03:49:32 +09:00

12 KiB

CUDA API 통합 및 에러 복구 강화 설계

  • 문서 작성일: 2025년 10월 7일
  • 작성자: Claude
  • 관련 문서:
    • Color_Space_Correction_Design.md
    • NVDECAV1Decoder_Resource_Mgmt_Refactor_Design.md
    • NVDECAV1Decoder_Sync_Stabilization_Design.md

1. 문제 정의

1.1. CUDA API 불일치 문제

현재 D3D12SurfaceHandler에서 CUDA Runtime API와 Driver API가 혼용되어 사용되고 있어 CUDA_ERROR_INVALID_HANDLE (400) 에러가 발생한다.

문제 발생 지점:

// ExternalMemoryCache.cpp:273 - Runtime API
cudaSurfaceObject_t surface;
cudaCreateSurfaceObject(&surface, &res_desc);  // Runtime API surface 생성

// D3D12SurfaceHandler.cpp:339 - Driver API
CUresult result = cuLaunchKernel(
    m_surfaceWriteKernel,  // Driver API kernel
    grid_size.x, grid_size.y, 1,
    block_size.x, block_size.y, 1,
    0,
    (CUstream)stream,
    kernel_args,  // Runtime API surface를 전달 → ERROR!
    nullptr
);

에러 메시지:

[D3D12SurfaceHandler] Kernel launch failed: 400 (CUDA_ERROR_INVALID_HANDLE: invalid resource handle)

근본 원인:

  • cudaCreateSurfaceObject() (Runtime API)로 생성된 surface handle
  • cuLaunchKernel() (Driver API)에 전달 시 호환되지 않음
  • CUDA Runtime API와 Driver API는 별도의 핸들 공간을 사용

1.2. 에러 복구 메커니즘 부재

CUDA 커널 실패 시 DecodeSlot 정리 로직이 누락되어 다음 문제가 발생:

  1. 슬롯 누수: DecodeSlot.in_use 플래그가 true로 남아 슬롯 재사용 불가
  2. 순서 보장 파괴: m_returnCounter 증가 안됨 → FIFO 순서 보장 로직 데드락
  3. 콜백 중복 호출: NVDEC이 동일 picture_index를 여러 번 디스플레이로 발행
  4. 픽셀 데이터 손상: 동일 프레임이 중복 처리되어 Frame 6에서 441,600개 픽셀 에러

테스트 결과:

picture_index=0: 1회 (정상)
picture_index=1~7: 각 3회 (비정상 - HandlePictureDisplay 중복 호출)
picture_index=8: 2회

Frame 6: FAIL (441,600 pixel errors)

2. 목표

2.1. CUDA API 통합

  • Runtime API와 Driver API 혼용 제거
  • 일관된 API 사용으로 안정성 확보

2.2. 에러 복구 메커니즘 강화

  • CUDA 커널 실패 시 슬롯 정리 보장
  • 순서 보장 로직 데드락 방지
  • HandlePictureDisplay 중복 호출 차단

3. 설계 옵션

옵션 A: Runtime API 통일 (권장)

장점:

  • Surface 생성 코드 변경 불필요 (ExternalMemoryCache.cpp)
  • 커널 로딩 단순화 (PTX 모듈 관리 불필요)
  • 더 나은 에러 메시지
  • 코드 변경 최소화

단점:

  • ⚠️ PTX 기반 커널을 Runtime API로 전환 필요
  • ⚠️ cuModuleLoadData → CUDA Runtime 컴파일로 변경

구현 방안:

3.1.1. D3D12SurfaceHandler.h 수정

class D3D12SurfaceHandler {
private:
    // Driver API → Runtime API 전환
    // CUmodule m_surfaceWriteModule;  // 제거
    // CUfunction m_surfaceWriteKernel;  // 제거

    // Runtime API 커널 함수 포인터 추가
    void (*m_kernelFunction)(const unsigned char*, unsigned long long,
                              unsigned int, unsigned int, unsigned int);
};

3.1.2. D3D12SurfaceHandler.cpp 수정

LoadSurfaceWriteKernel() 함수 변경:

bool D3D12SurfaceHandler::LoadSurfaceWriteKernel()
{
    LOGF_DEBUG("[D3D12SurfaceHandler] Loading kernel via Runtime API");

    // Runtime API: PTX를 모듈로 컴파일
    // 1. PTX를 파일로 저장 (임시)
    // 2. nvrtcCompileProgram으로 컴파일
    // 3. 커널 함수 포인터 획득

    // 또는: CUDA C++ 소스를 직접 컴파일하여 링크
    // (구현 세부사항은 4단계에서 결정)

    LOGF_DEBUG("[D3D12SurfaceHandler] Kernel loaded successfully");
    return true;
}

CopyRGBAToSurfaceViaKernel() 함수 변경:

bool D3D12SurfaceHandler::CopyRGBAToSurfaceViaKernel(
    cudaSurfaceObject_t dst_surface,
    CUdeviceptr src_rgba,
    uint32_t width, uint32_t height, uint32_t src_pitch,
    cudaStream_t stream)
{
    if (!m_kernelFunction) {
        LOGF_ERROR("[D3D12SurfaceHandler] Kernel not loaded");
        return false;
    }

    // Launch parameters: 16x16 thread blocks
    dim3 block_size(16, 16);
    dim3 grid_size((width + 15) / 16, (height + 15) / 16);

    LOGF_DEBUG("[D3D12SurfaceHandler] Launching kernel via Runtime API: grid=(%u,%u), block=(%u,%u)",
               grid_size.x, grid_size.y, block_size.x, block_size.y);

    // Runtime API kernel launch (<<<>>> syntax)
    // Note: This requires the kernel to be compiled as a device function
    // For now, we'll use a workaround with a global function pointer

    // Alternative: Use CUDA Runtime kernel launch
    void* kernel_args[] = {
        (void*)&src_rgba,
        (void*)&dst_surface,
        (void*)&width,
        (void*)&height,
        (void*)&src_pitch
    };

    cudaError_t err = cudaLaunchKernel(
        (void*)m_kernelFunction,
        grid_size,
        block_size,
        kernel_args,
        0,  // Shared memory
        stream
    );

    if (err != cudaSuccess) {
        LOGF_ERROR("[D3D12SurfaceHandler] Kernel launch failed: %s", cudaGetErrorString(err));
        return false;
    }

    // Synchronize to ensure kernel completes
    err = cudaStreamSynchronize(stream);
    if (err != cudaSuccess) {
        LOGF_ERROR("[D3D12SurfaceHandler] Kernel synchronization failed: %s", cudaGetErrorString(err));
        return false;
    }

    LOGF_DEBUG("[D3D12SurfaceHandler] Kernel completed successfully");
    return true;
}

옵션 B: Driver API 통일

장점:

  • PTX 모듈 관리 유지 (기존 코드 활용)
  • 더 세밀한 제어 가능

단점:

  • Surface 생성 코드 대규모 수정 필요 (ExternalMemoryCache.cpp)
  • cudaCreateSurfaceObjectcuSurfObjectCreate 전환
  • 모든 CUDA Runtime API 호출을 Driver API로 변경
  • 코드 복잡도 증가

구현 방안:

3.2.1. ExternalMemoryCache.cpp 수정

bool ExternalMemoryCache::ImportD3D12TextureAsSurface(ID3D12Resource* resource,
                                                       CUexternalMemory* out_ext_mem,
                                                       CUmipmappedArray* out_mipmap,
                                                       CUsurfObject* out_surface)
{
    // Step 6: Create surface object via Driver API
    CUDA_RESOURCE_DESC res_desc = {};
    res_desc.resType = CU_RESOURCE_TYPE_ARRAY;
    res_desc.res.array.hArray = array;

    CUsurfObject surface;
    CUresult result = cuSurfObjectCreate(&surface, &res_desc);

    if (result != CUDA_SUCCESS) {
        const char* errorName = nullptr;
        cuGetErrorName(result, &errorName);
        LOGF_ERROR("[ExternalMemoryCache] cuSurfObjectCreate failed: %s", errorName);
        // ... cleanup ...
        return false;
    }

    *out_surface = surface;
    return true;
}

3.2.2. D3D12SurfaceHandler.h 수정

// cudaSurfaceObject_t → CUsurfObject
bool CopyRGBAFrame(CUdeviceptr src_rgba,
                   ID3D12Resource* dst_texture,
                   uint32_t width,
                   uint32_t height,
                   CUstream stream);  // cudaStream_t → CUstream

bool CopyRGBAToSurfaceViaKernel(CUsurfObject dst_surface,  // 타입 변경
                                 CUdeviceptr src_rgba,
                                 uint32_t width, uint32_t height, uint32_t src_pitch,
                                 CUstream stream);

권장 사항: 옵션 A (Runtime API 통일)

이유:

  1. Surface 생성 코드가 이미 안정적으로 작동 중
  2. 커널 로딩만 변경하면 되어 영향 범위 최소화
  3. Runtime API가 더 간결하고 유지보수 용이
  4. todo20.txt에서도 옵션 A를 권장

4. 에러 복구 메커니즘 강화

4.1. DecodeToSurface 슬롯 정리 로직 추가

NVDECAV1Decoder.cpp 수정 위치:

bool NVDECAV1Decoder::DecodeToSurface(/* ... */)
{
    // ... 기존 코드 ...

    // D3D12 frame processing
    bool copy_success = false;
    if (target_type == VAVCORE_SURFACE_D3D12_RESOURCE) {
        if (isRGBAFormat) {
            // RGBA conversion and copy
            if (!m_rgbaConverter->Convert(/* ... */)) {
                LOGF_ERROR("[DecodeToSurface] RGBA conversion failed");
                goto cleanup_on_error;  // 에러 처리로 점프
            }

            if (!m_d3d12Handler->CopyRGBAFrame(/* ... */)) {
                LOGF_ERROR("[DecodeToSurface] Frame copy failed");
                goto cleanup_on_error;  // 에러 처리로 점프
            }
        } else {
            // NV12 copy
            if (!m_d3d12Handler->CopyNV12Frame(/* ... */)) {
                LOGF_ERROR("[DecodeToSurface] NV12 copy failed");
                goto cleanup_on_error;  // 에러 처리로 점프
            }
        }
        copy_success = true;
    }

    // ... 기존 성공 경로 ...

    // 10. Release slot
    my_slot.in_use.store(false);
    LOGF_DEBUG("[DecodeToSurface] Released slot %d", my_slot_idx);

    // 11. Increment return counter to allow next frame
    m_returnCounter.fetch_add(1);

    return true;

cleanup_on_error:
    // **NEW: 에러 복구 로직**
    LOGF_ERROR("[DecodeToSurface] Entering cleanup on error for slot %d, submission_id=%llu",
               my_slot_idx, my_submission_id);

    // 1. Release decode slot
    my_slot.in_use.store(false);
    my_slot.is_ready.store(false);

    // 2. Increment return counter to prevent FIFO deadlock
    m_returnCounter.fetch_add(1);

    // 3. Signal next waiting thread (if any)
    my_slot.slot_cv.notify_one();

    LOGF_ERROR("[DecodeToSurface] Cleanup complete, returning false");
    return false;
}

4.2. HandlePictureDisplay 중복 호출 방지

추가 안전장치:

int NVDECAV1Decoder::HandlePictureDisplay(CUVIDPARSERDISPINFO* disp_info)
{
    // ... 기존 코드 ...

    // **NEW: 중복 호출 감지**
    if (slot.is_ready.load()) {
        LOGF_WARNING("[HandlePictureDisplay] Duplicate display callback for picture_index=%d (already ready)",
                     disp_info->picture_index);
        return 1;  // Skip duplicate
    }

    // ... 기존 코드 계속 ...
}

5. 구현 단계

Phase 1: CUDA API 통합 (우선순위 높음)

  1. D3D12SurfaceHandler.h 수정

    • Driver API 멤버 변수 제거
    • Runtime API 커널 함수 포인터 추가
  2. D3D12SurfaceHandler.cpp 수정

    • LoadSurfaceWriteKernel() Runtime API로 전환
    • CopyRGBAToSurfaceViaKernel() Runtime API로 전환
  3. 빌드 및 테스트

    • VavCore 라이브러리 빌드
    • RedSurfaceNVDECTest 실행
    • CUDA 커널 에러 해결 확인

Phase 2: 에러 복구 강화 (안정성 개선)

  1. NVDECAV1Decoder.cpp 수정

    • DecodeToSurfacecleanup_on_error 라벨 추가
    • 슬롯 정리 로직 구현
  2. HandlePictureDisplay 중복 방지

    • 중복 호출 감지 로직 추가
  3. 테스트

    • 25프레임 모두 정상 디코딩 확인
    • HandlePictureDisplay 호출 횟수 검증
    • 픽셀 에러 0개 확인

6. 검증 기준

6.1. CUDA API 통합 성공 기준

  • CUDA_ERROR_INVALID_HANDLE 에러 0건
  • 모든 프레임 RGBA 변환 성공
  • Kernel launch 성공 로그 출력

6.2. 에러 복구 강화 성공 기준

  • HandlePictureDisplay 각 picture_index 1회만 호출
  • 슬롯 누수 0건
  • 25프레임 모두 픽셀 에러 0개
  • FIFO 순서 보장 정상 작동

6.3. 성능 검증

  • 디코딩 성능 유지 (기존 대비 ±5% 이내)
  • 메모리 사용량 증가 없음

7. 위험 요소 및 완화 방안

7.1. Runtime API 커널 로딩 복잡도

위험: PTX → Runtime API 전환 시 커널 로딩 로직 복잡화

완화:

  • NVRTC (NVIDIA Runtime Compilation) 사용
  • 또는 사전 컴파일된 CUBIN 파일 사용
  • 최악의 경우 옵션 B (Driver API 통일)로 전환

7.2. 기존 동기화 로직과의 충돌

위험: 에러 복구 로직이 기존 동기화 메커니즘과 충돌

완화:

  • cleanup_on_error에서 슬롯 뮤텍스 획득 후 정리
  • 철저한 단위 테스트

7.3. 성능 저하

위험: Runtime API 전환으로 인한 성능 저하

완화:

  • 벤치마크 테스트 수행
  • 성능 저하 시 Driver API 전환 고려

8. 참고 자료

  • CUDA Programming Guide: Runtime API vs Driver API
  • todo20.txt: CUDA API 불일치 분석 및 해결 제안
  • NVDECAV1Decoder_Sync_Stabilization_Design.md: 동기화 개선 설계