407 lines
12 KiB
Markdown
407 lines
12 KiB
Markdown
|
|
# 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)` 에러가 발생한다.
|
||
|
|
|
||
|
|
**문제 발생 지점:**
|
||
|
|
```cpp
|
||
|
|
// 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 수정
|
||
|
|
```cpp
|
||
|
|
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() 함수 변경:**
|
||
|
|
```cpp
|
||
|
|
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() 함수 변경:**
|
||
|
|
```cpp
|
||
|
|
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 수정
|
||
|
|
```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 수정
|
||
|
|
```cpp
|
||
|
|
// 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 수정 위치:**
|
||
|
|
```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 중복 호출 방지
|
||
|
|
|
||
|
|
**추가 안전장치:**
|
||
|
|
```cpp
|
||
|
|
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 수정**
|
||
|
|
- `DecodeToSurface`에 `cleanup_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**: 동기화 개선 설계
|