From 7a8cc12cb326bff030bf009ca8be63df6d256096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=83=81=ED=98=B8=20Sangho=20Park?= Date: Tue, 9 Jun 2026 07:29:37 +0900 Subject: [PATCH] feat(cli): --beam-size + --correct; add COLAB.md GPU full-transcribe guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - transcribe: --beam-size(CPU 속도), --correct(사내 LLM 청크 보정, SCRIBE_LLM_*), config.beam_size(CPU 1~2 권장). 보정 시 전체 수집 후 한 번에 출력. - COLAB.md: Colab(전사 전용·게이트 미도달) + 온프렘 GPU(전사+보정 풀 파이프라인) 가이드. 23 tests pass, ruff clean. --correct 미설정 시 우아한 에러 검증. Co-Authored-By: Claude Opus 4.8 --- COLAB.md | 79 +++++++++++++++++++++++++++++++++++++++ src/luke_scribe/cli.py | 46 +++++++++++++++++++---- src/luke_scribe/config.py | 1 + 3 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 COLAB.md diff --git a/COLAB.md b/COLAB.md new file mode 100644 index 0000000..54148e2 --- /dev/null +++ b/COLAB.md @@ -0,0 +1,79 @@ +# Colab / GPU 풀 전사 가이드 + +GPU 환경(Colab T4/A100 또는 온프렘 GPU)에서 **풀 강연을 빠르게** 전사(+선택 보정)합니다. +CPU(개발 박스)는 풀 강연이 느려(turbo ~RTF 5×) 비권장 — 여기서 돌리세요. +GPU(T4)에서 turbo는 대략 실시간의 ~0.1~0.3× → **37분 강연이 수 분**. + +--- + +## A) Google Colab — 전사 전용 + +> Colab은 외부 클라우드라 **사내 LLM 게이트(192.168.0.123)에 못 닿습니다** → `--correct`(보정) 불가, **전사만**. +> 런타임 → 런타임 유형 변경 → **GPU(T4)** 선택. + +```python +# 1) 시스템 의존성 + uv +!apt-get -qq update && apt-get -qq install -y ffmpeg +!curl -LsSf https://astral.sh/uv/install.sh | sh +import os; os.environ["PATH"] = "/root/.local/bin:" + os.environ["PATH"] + +# 2) 코드 (저장소 익명 read 허용) +!git clone -b feat/p1-core https://git.lukehemmin.com/lukehemmin/luke_scribe.git +%cd luke_scribe + +# 3) 의존성 (엔진 + GPU CUDA 런타임) +!uv sync --extra engine --extra gpu + +# 4) GPU 인식 확인 (T3면 turbo+large-v3 동시상주) +!uv run luke-scribe detect + +# 5) 오디오 업로드 (또는 Drive 마운트) +from google.colab import files +AUDIO = list(files.upload().keys())[0] + +# 6) 풀 전사 (large-v3-turbo) — 더 높은 정확도는 --model large-v3 +!uv run luke-scribe transcribe "$AUDIO" --model large-v3-turbo --language ko --timestamps | tee transcript.txt +``` + +### Colab을 API로 외부 노출하려면 +```python +# cloudflared 공개 URL 발급 → 외부에서 curl +!uv sync --extra engine --extra gpu --extra api +import subprocess, os +os.environ["SCRIBE_API_KEYS"] = '["colab-test"]' +!nohup uv run luke-scribe serve --host 0.0.0.0 --port 8000 --tunnel cloudflare > serve.log 2>&1 & +import time; time.sleep(8); print(open("serve.log").read()) # public *.trycloudflare.com URL 확인 +``` + +--- + +## B) 온프렘 GPU — 전사 + 사내 LLM 보정 (풀 파이프라인) + +사내망(게이트 192.168.0.123 도달) + GPU 머신이면 **음차→영문 복원까지** 한 번에: + +```bash +git clone -b feat/p1-core https://git.lukehemmin.com/lukehemmin/luke_scribe.git && cd luke_scribe +uv sync --extra engine --extra gpu + +export SCRIBE_LLM_BASE_URL=http://192.168.0.123:8080/v1 +export SCRIBE_LLM_API_KEY=<사내 키> # 셸 히스토리 주의 +export SCRIBE_LLM_MODEL=copilot-gpt-4o +export SCRIBE_LLM_MAX_CHARS=3000 # 사내 LLM 컨텍스트 창에 맞춰(~8k→1500/~16k→3000/~30k→6000) + +# 전사 + 청크 보정을 한 명령으로 +uv run luke-scribe transcribe talk.m4a --model large-v3-turbo --language ko --correct | tee transcript.txt +``` + +API로: +```bash +uv run luke-scribe serve # 출력된 X-API-Key 사용 +curl -H "X-API-Key: <키>" -F file=@talk.m4a -F model=large-v3-turbo -F correct=true \ + http://localhost:8000/v1/transcribe +``` + +--- + +## 참고 +- 보정은 긴 전사를 `SCRIBE_LLM_MAX_CHARS` 청크로 분할 + **러닝 글로서리**로 처리(작은 컨텍스트 창 대응). +- 약 GPU(1050/2GB)는 turbo도 안 들어가 자동으로 **CPU(T0)** 로 강등 — `detect`로 등급 확인. +- 오디오 파일은 저장소에 없음(`.gitignore`) — Colab 업로드/Drive 또는 온프렘 로컬 경로 사용. diff --git a/src/luke_scribe/cli.py b/src/luke_scribe/cli.py index 9fb8ee8..5f3f7ab 100644 --- a/src/luke_scribe/cli.py +++ b/src/luke_scribe/cli.py @@ -55,6 +55,8 @@ def transcribe( device: str = typer.Option("auto", help="auto|cpu|cuda"), word_timestamps: bool = typer.Option(False, "--word-timestamps"), vad: bool = typer.Option(True, "--vad/--no-vad", help="무음 제거"), + beam_size: int = typer.Option(None, "--beam-size", help="디코딩 빔(CPU 1~2 권장=속도↑)"), + correct: bool = typer.Option(False, "--correct", help="사내 LLM 보정(SCRIBE_LLM_* 설정 필요)"), timestamps: bool = typer.Option(False, "--timestamps", help="세그먼트 [start–end] 표시"), ) -> None: """단발 파일 전사 (faster-whisper, CPU/GPU 자동, AC-4 일부).""" @@ -90,17 +92,45 @@ def transcribe( ) engine = FasterWhisperEngine(model_name, dev, profile.compute_type, cache_dir=settings.model_cache_dir) - segments, tinfo = engine.transcribe(file, language=lang, word_timestamps=word_timestamps, vad=vad) + segments, tinfo = engine.transcribe( + file, language=lang, word_timestamps=word_timestamps, vad=vad, + beam_size=(beam_size or settings.beam_size), + ) - count = 0 + seg_list = [] for seg in segments: - count += 1 - if timestamps: - console.print(f"[cyan][{seg.start:6.2f}–{seg.end:6.2f}][/] {seg.text.strip()}") - else: - console.print(seg.text.strip()) + seg_list.append({"start": seg.start, "end": seg.end, "text": seg.text.strip()}) + if not correct: # 스트리밍 출력(보정 시엔 전체를 모은 뒤 한 번에) + if timestamps: + console.print(f"[cyan][{seg.start:6.2f}–{seg.end:6.2f}][/] {seg.text.strip()}") + else: + console.print(seg.text.strip()) + + if correct: + from .postprocess import llm as llm_correct + from .postprocess import rules + + text = " ".join(s["text"] for s in seg_list).strip() + try: + text = rules.normalize( + llm_correct.correct( + text, + base_url=settings.llm_base_url, + api_key=settings.llm_api_key, + model=settings.llm_model, + max_chars=settings.llm_max_chars, + ) + ) + except llm_correct.LLMNotConfigured as exc: + console.print(f"[red]--correct:[/] {exc}") + raise typer.Exit(code=1) from exc + console.print(text) + detected = getattr(tinfo, "language", None) - console.print(f"[green]✓ {count} segments · detected_lang={detected} · model_used={model_name}[/]") + console.print( + f"[green]✓ {len(seg_list)} segments · detected_lang={detected} · " + f"model_used={model_name} · corrected={correct}[/]" + ) @app.command() diff --git a/src/luke_scribe/config.py b/src/luke_scribe/config.py index 652ce97..ff27472 100644 --- a/src/luke_scribe/config.py +++ b/src/luke_scribe/config.py @@ -15,6 +15,7 @@ class Settings(BaseSettings): device: str = "auto" compute_type: str | None = None # None=자동(cc/VRAM 기반) workers: int | None = None # None=자동 산정 + beam_size: int = 5 # 디코딩 빔(CPU는 1~2 권장=속도↑, GPU는 5) # 언어 (기본 ko, 요청별 override) language: str = "ko"