fix: split 'cuda:N' into device+device_index for CTranslate2
Colab A100 run exposed a GPU-only bug: DeviceManager returns
selected_device='cuda:0' and the engine passed it verbatim to
faster-whisper, but CTranslate2 only accepts device='cuda' with a
separate device_index arg -> 'unsupported device cuda:0' on every GPU
transcription. Fix: FasterWhisperEngine._split_device() splits
'cuda:N' -> ('cuda', N) and passes device_index to WhisperModel.
Notebook: server cell now pkills stale servers (old process holds
port 8000 and 401s on new keys since KeyStore loads at startup),
sets LUKESCRIBE_API_KEY_FILE explicitly, and verifies auth with
RAW_KEY before proceeding; worker cell guards read_result() is None.
+ 4 unit tests (device split contract), 131 tests pass, ruff clean.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
"""FasterWhisperEngine 단위 테스트 — CTranslate2 호출 계약 (device 분리).
|
||||
|
||||
faster-whisper/CTranslate2는 ``device`` 문자열로 "cuda"만 허용하며 인덱스는
|
||||
별도 ``device_index`` 인자로 받는다. DeviceManager가 내려주는 "cuda:N"을
|
||||
제대로 분리해 전달하는지 mock으로 검증한다 (GPU 환경에서만 재현되는 버그
|
||||
— Colab 실전 테스트에서 ``unsupported device cuda:0``으로 확인됨).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import types
|
||||
|
||||
from luke_scribe.engine.base import TranscriptionOptions
|
||||
from luke_scribe.engine.faster_whisper_engine import FasterWhisperEngine
|
||||
|
||||
|
||||
class FakeWhisperModel:
|
||||
"""faster_whisper.WhisperModel 대체 — 생성 인자만 기록한다."""
|
||||
|
||||
calls: list[dict] = []
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
self.kwargs = kwargs
|
||||
self.__class__.calls.append(kwargs)
|
||||
|
||||
def transcribe(self, audio_path, **kwargs):
|
||||
return iter([]), {}
|
||||
|
||||
|
||||
def _install_fake(monkeypatch) -> None:
|
||||
mod = types.ModuleType("faster_whisper")
|
||||
mod.WhisperModel = FakeWhisperModel
|
||||
monkeypatch.setitem(sys.modules, "faster_whisper", mod)
|
||||
FakeWhisperModel.calls.clear()
|
||||
|
||||
|
||||
def _opts(**kw) -> TranscriptionOptions:
|
||||
kw.setdefault("model", "large-v3-turbo")
|
||||
kw.setdefault("device", "cuda:0")
|
||||
kw.setdefault("compute_type", "float16")
|
||||
return TranscriptionOptions(**kw)
|
||||
|
||||
|
||||
def test_cuda_device_index_split(monkeypatch):
|
||||
"""'cuda:0' → device='cuda', device_index=0 (Colab A100 첫 GPU)."""
|
||||
_install_fake(monkeypatch)
|
||||
FasterWhisperEngine().transcribe("/tmp/x.wav", _opts())
|
||||
kwargs = FakeWhisperModel.calls[-1]
|
||||
assert kwargs["device"] == "cuda"
|
||||
assert kwargs["device_index"] == 0
|
||||
assert kwargs["compute_type"] == "float16"
|
||||
|
||||
|
||||
def test_cuda_second_device_index(monkeypatch):
|
||||
"""'cuda:1' → device='cuda', device_index=1."""
|
||||
_install_fake(monkeypatch)
|
||||
FasterWhisperEngine().transcribe("/tmp/x.wav", _opts(device="cuda:1"))
|
||||
kwargs = FakeWhisperModel.calls[-1]
|
||||
assert kwargs["device"] == "cuda"
|
||||
assert kwargs["device_index"] == 1
|
||||
|
||||
|
||||
def test_cpu_passthrough(monkeypatch):
|
||||
"""'cpu' → 그대로 device='cpu', device_index=0."""
|
||||
_install_fake(monkeypatch)
|
||||
FasterWhisperEngine().transcribe("/tmp/x.wav", _opts(device="cpu", compute_type="int8"))
|
||||
kwargs = FakeWhisperModel.calls[-1]
|
||||
assert kwargs["device"] == "cpu"
|
||||
assert kwargs["device_index"] == 0
|
||||
assert kwargs["compute_type"] == "int8"
|
||||
|
||||
|
||||
def test_split_device_helper():
|
||||
assert FasterWhisperEngine._split_device("cuda:0") == ("cuda", 0)
|
||||
assert FasterWhisperEngine._split_device("cuda:3") == ("cuda", 3)
|
||||
assert FasterWhisperEngine._split_device("cpu") == ("cpu", 0)
|
||||
assert FasterWhisperEngine._split_device("cuda") == ("cuda", 0)
|
||||
# 파싱 불가 인덱스는 그대로 전달 (모델 로드 시 명시적 오류로 fail-explicit)
|
||||
assert FasterWhisperEngine._split_device("cuda:xx") == ("cuda:xx", 0)
|
||||
Reference in New Issue
Block a user