ci: GPU 없는 서버 Windows VM 지원 + 전 파이프라인 자동 검증
Some checks failed
Windows / verify (push) Has been cancelled
Some checks failed
Windows / verify (push) Has been cancelled
RTX 5050 은 PCIe 패스스루로 이 VM(.9)이 독점하므로 Proxmox 에 Windows VM 을
띄워도 GPU 를 못 준다. 그래서 "Windows 검증에 GPU 가 정말 필요한가"를 실제로
재봤다 — GPU 를 완전히 가린 채 점검기를 돌린 결과, 실패한 건 'GPU 인식' 하나뿐이고
그건 이미 경고 처리라 빌드를 막지 않는다. 나머지(단축키·모니터·오디오 열거·
exe 빌드·기동·오버레이 렌더링)는 전부 통과한다. 즉 GPU 없는 서버 VM 으로 충분하고,
CUDA/VRAM 관련은 GPU 가 물려 있는 .9 에서 이미 검증 중이다.
- tests/fixtures/en_callout_16k.wav (139KB)
MeloTTS 로 만든 4.4초 영어 콜아웃. CI 가 실제 음성으로 검증할 수 있게 커밋.
- windows_smoke.py --pipeline
오디오 파일 -> 음성인식 -> 번역 -> 한국어 자막까지 끝까지 확인한다.
부품이 다 통과해도 이어 붙이면 안 되는 경우를 잡기 위한 것.
CPU + 가장 가벼운 티어로 돌려 GPU 없는 VM 에서도 그대로 된다.
실측: 27초, 'Enemy coming from the left, fall back now.'
-> '적 왼쪽에서 오는, 지금 뒤로 물러서.'
음성인식 결과에 핵심 단어가 있는지, 자막에 한글이 있는지까지 검사한다.
- 워크플로에 해당 단계 추가 (30분 타임아웃, 첫 회만 모델 다운로드)
- docs/WINDOWS-TESTING.md 에 서버 VM 구축 절차
Proxmox qm create 예시와 함께, 빠뜨리면 조용히 실패하는 두 가지를 명시:
(1) --audio0 없으면 오디오 장치가 안 잡혀 WASAPI 점검이 실패
(2) 자동 로그온이 없으면 데스크톱 세션이 없어 GUI/스크린샷이 전부 실패
(러너를 서비스로 돌리면 안 되는 실질적 이유이기도 하다)
검증: pytest 189개 통과, ruff clean, 워크플로 11스텝 파싱 확인(bash 0개),
GPU 숨긴 조건에서 점검기 실행, --pipeline 실제 통과
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -160,6 +160,76 @@ def run_source_checks() -> None:
|
||||
return "스크린샷 2장 저장 (artifacts/)"
|
||||
|
||||
|
||||
# --- 전 파이프라인 점검 -----------------------------------------------------
|
||||
|
||||
|
||||
def run_pipeline_check(timeout_s: int = 900) -> None:
|
||||
"""오디오 파일 하나를 넣어 한국어 자막이 나오는지 끝까지 본다.
|
||||
|
||||
개별 부품이 다 통과해도 이어 붙이면 안 되는 경우가 있다. 이 점검이
|
||||
통과하면 "음성인식 -> 번역 -> 자막" 이 실제로 동작한다는 뜻이다.
|
||||
|
||||
GPU 없이 CPU 로 돌린다. 서버의 GPU 없는 Windows VM 에서도 그대로 된다.
|
||||
모델은 처음 한 번만 받고 그 뒤로는 캐시를 쓴다.
|
||||
"""
|
||||
print(f"\n=== 전 파이프라인 (오디오 -> 자막), 플랫폼: {sys.platform} ===")
|
||||
|
||||
@check("오디오 파일 -> 한국어 자막")
|
||||
def _pipeline():
|
||||
from livesub.config import AppConfig
|
||||
from livesub.core.engine import TranslationEngine
|
||||
|
||||
clip = ROOT / "tests" / "fixtures" / "en_callout_16k.wav"
|
||||
if not clip.is_file():
|
||||
raise RuntimeError(f"테스트 음성이 없습니다: {clip}")
|
||||
|
||||
cfg = AppConfig()
|
||||
cfg.models.tier = "lightning" # CI 에서는 가장 가벼운 티어로 충분하다
|
||||
cfg.models.device = "cpu" # GPU 없는 VM 에서도 돌아야 한다
|
||||
cfg.models.preload_on_start = True
|
||||
cfg.audio.backend = "file"
|
||||
cfg.audio.target_process_name = str(clip)
|
||||
cfg.audio.silence_ms = 400
|
||||
cfg.performance.game_mode = False
|
||||
|
||||
lines: list = []
|
||||
errors: list[str] = []
|
||||
|
||||
def on_status(status) -> None:
|
||||
if status.state.value == "error":
|
||||
errors.append(status.message)
|
||||
|
||||
engine = TranslationEngine(cfg, on_line=lines.append, on_status=on_status)
|
||||
engine.start()
|
||||
deadline = time.time() + timeout_s
|
||||
try:
|
||||
while time.time() < deadline and not errors:
|
||||
if any(line.is_final for line in lines):
|
||||
break
|
||||
time.sleep(0.5)
|
||||
finally:
|
||||
engine.shutdown()
|
||||
|
||||
if errors:
|
||||
raise RuntimeError(f"엔진 오류: {errors[0]}")
|
||||
finals = [line for line in lines if line.is_final]
|
||||
if not finals:
|
||||
raise RuntimeError(f"{timeout_s}초 안에 자막이 나오지 않았습니다")
|
||||
|
||||
line = finals[0]
|
||||
if "enemy" not in line.source_text.lower():
|
||||
raise RuntimeError(f"음성인식이 빗나갔습니다: {line.source_text!r}")
|
||||
if not any("\uac00" <= ch <= "\ud7a3" for ch in line.translated_text):
|
||||
raise RuntimeError(f"한국어 자막이 아닙니다: {line.translated_text!r}")
|
||||
|
||||
ARTIFACTS.mkdir(exist_ok=True)
|
||||
(ARTIFACTS / "pipeline.txt").write_text(
|
||||
f"원문: {line.source_text}\n자막: {line.translated_text}\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
return f"{line.source_text!r} -> {line.translated_text!r}"
|
||||
|
||||
|
||||
# --- 빌드된 exe 점검 --------------------------------------------------------
|
||||
|
||||
|
||||
@@ -195,10 +265,16 @@ def run_exe_check(exe: Path) -> None:
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Windows 전용 점검")
|
||||
parser.add_argument("--exe", type=Path, help="빌드된 exe 를 점검")
|
||||
parser.add_argument(
|
||||
"--pipeline", action="store_true",
|
||||
help="오디오 파일로 전 파이프라인 점검 (모델을 내려받으므로 느립니다)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.exe:
|
||||
run_exe_check(args.exe)
|
||||
elif args.pipeline:
|
||||
run_pipeline_check()
|
||||
else:
|
||||
run_source_checks()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user