12 KiB
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 handlecuLaunchKernel()(Driver API)에 전달 시 호환되지 않음- CUDA Runtime API와 Driver API는 별도의 핸들 공간을 사용
1.2. 에러 복구 메커니즘 부재
CUDA 커널 실패 시 DecodeSlot 정리 로직이 누락되어 다음 문제가 발생:
- 슬롯 누수:
DecodeSlot.in_use플래그가true로 남아 슬롯 재사용 불가 - 순서 보장 파괴:
m_returnCounter증가 안됨 → FIFO 순서 보장 로직 데드락 - 콜백 중복 호출: NVDEC이 동일
picture_index를 여러 번 디스플레이로 발행 - 픽셀 데이터 손상: 동일 프레임이 중복 처리되어 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)
- ❌
cudaCreateSurfaceObject→cuSurfObjectCreate전환 - ❌ 모든 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 통일)
이유:
- Surface 생성 코드가 이미 안정적으로 작동 중
- 커널 로딩만 변경하면 되어 영향 범위 최소화
- Runtime API가 더 간결하고 유지보수 용이
- 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 통합 (우선순위 높음)
-
D3D12SurfaceHandler.h 수정
- Driver API 멤버 변수 제거
- Runtime API 커널 함수 포인터 추가
-
D3D12SurfaceHandler.cpp 수정
LoadSurfaceWriteKernel()Runtime API로 전환CopyRGBAToSurfaceViaKernel()Runtime API로 전환
-
빌드 및 테스트
- VavCore 라이브러리 빌드
- RedSurfaceNVDECTest 실행
- CUDA 커널 에러 해결 확인
Phase 2: 에러 복구 강화 (안정성 개선)
-
NVDECAV1Decoder.cpp 수정
DecodeToSurface에cleanup_on_error라벨 추가- 슬롯 정리 로직 구현
-
HandlePictureDisplay 중복 방지
- 중복 호출 감지 로직 추가
-
테스트
- 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: 동기화 개선 설계