ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • pyannote 화자 분리가 GPU에서 안 돌아갈 때 — Blackwell nvrtc 패치 1줄의 힘
    IT 2026. 3. 19. 23:00

    33분짜리 녹음을 AI로 전사했다. 텍스트는 나왔는데, 누가 말한 건지 구분이 안 된다. 4명이 번갈아 발표한 녹음인데 전부 "Speaker 1"로 찍힌다. pyannote-audio라는 화자 분리(speaker diarization) 모델을 돌리면 해결된다. 문제는 GPU에서 돌리면 에러가 나고, CPU로 돌리면 40분이 걸린다는 것이었다. 이 글에서는 원인을 추적해서 코드 2줄을 고쳐 GPU 화자 분리를 되살린 과정을 정리한다. ---

    증상: GPU에서만 터지는 에러

    NVIDIA DGX Spark는 GB10이라는 Blackwell 아키텍처 GPU를 탑재하고 있다. CUDA compute capability(GPU 세대를 나타내는 숫자)가 12.1인데, 이게 너무 최신이라 문제가 생겼다.

    pyannote를 GPU 모드로 실행하면 이런 에러가 뜬다:

    NVRTC compilation failed: unsupported gpu architecture 'compute_121'

    nvrtc(NVIDIA Runtime Compilation)는 CUDA 코드를 실행 중에 컴파일하는 도구다. nvrtc 자체는 NVIDIA가 제공하는 것이니 당연히 sm_121을 지원한다. 문제는 PyTorch다. PyTorch 내부의 Jiterator라는 JIT 컴파일러가 nvrtc를 호출할 때 "이 GPU 아키텍처로 컴파일해라"라는 플래그를 전달하는데, PyTorch가 아직 sm_121을 자신의 아키텍처 목록에 등록하지 않았다. 그래서 nvrtc에 잘못된 플래그가 전달되고, nvrtc는 "그런 아키텍처 모른다"며 에러를 던지는 것이다. NVIDIA 도구의 문제가 아니라 PyTorch의 지원 범위 문제인 셈이다.

    CPU로 fallback하면 동작은 한다. 하지만 33분 녹음에 40분이 걸린다. 녹음보다 분석이 더 오래 걸리면 자동화의 의미가 없다.

     

    원인 추적: .abs() 한 줄이 범인

    에러 traceback을 따라가면 torchaudio/compliance/kaldi.py에 도착한다. 음성 처리의 핵심인 fbank(필터 뱅크) 특징을 추출하는 코드다.

    # torchaudio/compliance/kaldi.py, line 616
    spectrum = torch.fft.rfft(strided_input).abs()

    이 한 줄이 범인이다. torch.fft.rfft()는 FFT(고속 푸리에 변환)를 수행해서 complex tensor(실수부+허수부를 가진 텐서)를 반환한다. 여기에 .abs()를 호출하면 복소수의 절대값(크기)을 구하는데, complex tensor에 대한 이 연산이 바로 앞에서 말한 Jiterator → nvrtc 경로를 탄다.

    PyTorch가 sm_121을 모르니까 nvrtc에 잘못된 아키텍처 플래그가 전달되고, 에러가 발생한다. 모든 GPU 연산이 안 되는 게 아니라, complex tensor의 .abs()라는 아주 특정한 연산만 문제인 것이다.

     

    수정 1: spectrum 계산에서 .abs() 우회

    복소수의 절대값은 수학적으로 √(실수부² + 허수부²)와 같다. .abs()를 호출하는 대신 이걸 직접 계산하면 Jiterator를 거치지 않는다.

    # Before (line 616)
    spectrum = torch.fft.rfft(strided_input).abs()
    
    # After
    fft_result = torch.fft.rfft(strided_input)
    spectrum = torch.sqrt(fft_result.real**2 + fft_result.imag**2)

    fft_result.realfft_result.imag는 일반 float tensor라서 제곱, 덧셈, 제곱근 모두 표준 CUDA 커널로 처리된다. nvrtc JIT 컴파일이 필요 없다.

    왜 이게 되는가? |a + bi| = √(a² + b²)는 고등학교 수학이다. PyTorch의 .abs()도 내부적으로 같은 계산을 하지만, complex tensor에 대해 Jiterator라는 범용 JIT 경로를 타는 것이 차이점이다. 우리는 그 경로를 직접 우회한 것이다.

     

    수정 2: power_spectrum에서도 같은 패턴 적용

    같은 파일의 311번째 줄에도 동일한 패턴이 있다. spectrogram 함수에서 파워 스펙트럼을 계산하는 부분이다:

    # Before (line 311)
    power_spectrum = torch.max(fft.abs().pow(2.0), epsilon).log()
    
    # After
    power_spectrum = torch.max((fft.real**2 + fft.imag**2), epsilon).log()

    여기서는 .abs().pow(2.0)이니까 |z|² = real² + imag²이다. 제곱근을 구할 필요도 없어서 오히려 연산이 줄어든다.

    ---

    수정 3: CPU fallback을 GPU로 복원

    torchaudio 패치를 적용했으니, 음성 파이프라인 코드에서 CPU로 강제하던 부분을 GPU로 되돌린다:

    # Before — CPU 강제
    pipeline.to(torch.device("cpu"))
    
    # After — GPU 우선 사용
    if torch.cuda.is_available():
        pipeline.to(torch.device("cuda"))

    이 변경 자체는 단순하지만, 위의 torchaudio 패치 없이는 바로 에러가 난다. 라이브러리 내부의 1줄을 고쳐야 애플리케이션 코드 1줄을 바꿀 수 있는 상황이었다.

     

    결과: 40분 → 2분

    항목 CPU (패치 전) GPU (패치 후)
    33분 녹음 화자 분리 ~40분 ~2분
    감지된 화자 수 5명 5명 (동일)
    세그먼트 수 295개 295개 (동일)

    정확도는 CPU와 GPU가 동일하다. 같은 모델, 같은 가중치, 같은 연산이니까 당연하다. 단지 nvrtc JIT 컴파일 경로를 우회했을 뿐이다.

    diagram

    ---

    교훈: 최신 GPU의 함정

    이 문제의 핵심은 "GPU가 너무 새로워서" 생긴 문제라는 점이다. 보통 GPU 문제는 드라이버가 오래됐거나 CUDA 버전이 낮아서 생기는데, 여기서는 반대였다.

    PyTorch, torchaudio 같은 주류 라이브러리도 최신 GPU 아키텍처를 지원하는 데 시간이 걸린다. 그 사이에 "거의 다 되는데 특정 연산 하나만 안 되는" 상황이 생길 수 있다. 이번 경우는 complex tensor의 .abs()라는 매우 좁은 범위의 문제였기 때문에, 해당 연산만 우회하면 나머지는 전부 정상 동작했다.

    에러 메시지를 읽고 traceback을 따라가서 정확히 어떤 연산이 문제인지 파악한 것이 해결의 핵심이었다. "GPU가 안 되니까 CPU로 fallback"은 쉬운 해결책이지만, 40분 vs 2분의 차이를 감수하기엔 너무 아까웠다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.