fbe13dddcc
Greenfield setup for luke_scribe (local STT transcription API). No source
code yet; this captures the completed design phase so teammates can ramp
through oh-my-claudecode.
Includes:
- .omc/plans/consensus-luke-scribe-stt-api.md — consensus impl plan v2.2
- .omc/specs/deep-interview-luke-scribe-stt-api.md — deep-interview spec
- .omc/artifacts/ask/{codex,gemini}-*.md — external review (CCG)
- .omc/project-memory.json — omc project memory
- opencode.json, .claude/settings.json — shared tooling config
- .gitignore — excludes ephemeral omc state/session logs and local settings
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
267 lines
27 KiB
Markdown
267 lines
27 KiB
Markdown
# Consensus Implementation Plan: luke_scribe — 로컬 STT 전사 API
|
||
|
||
- **Status:** `pending approval` (consensus **v2.2** — v2.1 합의 + CCG 외부리뷰(Codex/Gemini) 반영; §3.6 능력등급·§3.10 프로비저닝/WS/공유스토어/Colab)
|
||
- **Mode:** `--consensus --direct --deliberate`
|
||
- **Source spec:** `.omc/specs/deep-interview-luke-scribe-stt-api.md` (ambiguity ~10%, PASSED)
|
||
- **Project:** greenfield `/root/luke_scribe`
|
||
- **Generated:** 2026-06-02 · **Revised:** 2026-06-02 (v2)
|
||
|
||
> **v2 changelog는 문서 맨 끝 §13 참조.** v1 대비 P0 3건(워커 실행모델, VRAM 측정/단계역전, 취소·임시파일·보관경합), P1 4건, P2 3건, 모호 AC 4건을 반영.
|
||
|
||
---
|
||
|
||
## 1. Requirements Summary
|
||
|
||
내부용(비공개) 로컬 STT 전사 API. 단일 `Job` 추상화로 **배치(파일/영상)** 와 **실시간(WebSocket)** 입력 처리. faster-whisper(CTranslate2) 런타임, **하이브리드 모델**(실시간=turbo, 배치=large-v3) — 단 **P1 bench 게이트로 검증**. **Device Manager** 가 GPU/CPU를 감지하고 **부팅 시 VRAM 실측**으로 정밀도·워커수를 산정(1050~H100), `auto|cpu|cuda` 강제 가능. **Redis(RQ) 영속 큐**(배치) + **전용 실시간 핸들러**(WS), `queue_position`·`progress` 보고. 요청별 출력옵션(txt/ts/word/diarize/SRT/VTT). 후처리(glossary→rules→LLM(local/external, 기본 off·신뢰도 게이팅)→confidence). API Key 인증(+스코프), 결과 7일 보관·**파생 오디오 포함 즉시 삭제**, 4h/2GB → `413`, 큐 만재 → `429`. 배포: CLI(dev/Colab)+Docker(prod)+Colab cloudflared. diarization 옵션(pyannote).
|
||
|
||
---
|
||
|
||
## 2. RALPLAN-DR Summary
|
||
|
||
### Principles
|
||
1. **Hardware-adaptive, fail-explicit** — 1050~H100 자동 감지·정밀도/동시성 산정. 적재 불가 시 **모델/정밀도 강등 → CPU**로 우아하게 내려가되, 강등이 불가능하면 **명확한 오류로 거부**(조용한 OOM·무한 강등 금지). "never fail"이 아니라 "never fail silently".
|
||
2. **One Job abstraction, two execution lanes** — 모든 입력을 Job 수명주기(queued→processing→completed/failed/cancelled)로 통일하되, **배치=RQ 워커 / 실시간=장수명 WS 핸들러**로 실행 레인을 분리(WS는 enqueue-once가 아니므로).
|
||
3. **Accuracy/latency 분리(검증 기반)** — batch=large-v3, realtime=turbo. 단 **하이브리드 채택은 P1 bench의 측정 델타로 게이트**(불충분하면 단일 모델로 단순화).
|
||
4. **Privacy-first, enforced** — 원본+파생 오디오 즉시 삭제(모든 종료 경로 `finally`), 결과 7d TTL, 외부 LLM egress는 **allowlist+opt-in+감사로그** 없이는 금지.
|
||
5. **Dev/prod parity** — 동일 코어, CLI(dev/Colab)/Docker(prod)는 설정 차이. 큐는 prod=RQ / dev=in-proc 폴백이되 **동일 Job 인터페이스 뒤**에 두어 의미 동등성 유지.
|
||
|
||
### Decision Drivers (top 3)
|
||
1. **혼용어(KO+EN) 정확도** — hotwords + 모델 선택 + 후처리.
|
||
2. **하드웨어 이식성 + 자동 스케일(실측 기반)**.
|
||
3. **동시성 + 가시성(queue_position/progress)**.
|
||
|
||
### Viable Options
|
||
|
||
**D1. 큐/동시성 백엔드**
|
||
- **(A) Redis + RQ `SimpleWorker`(no-fork) + 장수명 모델 보유 프로세스** ✅ *(채택, 사용자 Redis 확정 + fork 문제 회피)* — Pros: 영속·재시작 내성, 모델 1회 적재 후 재사용, CUDA-fork 충돌 회피. Cons: SimpleWorker는 작업 중 하트비트 없음 → progress emit로 보완 필요.
|
||
- **(B) Redis + Celery** — Pros: 라우팅/우선순위/재시도 성숙. Cons: GPU 단일박스엔 과함. *Invalidation:* RQ+SimpleWorker로 충분.
|
||
- **(C) in-process asyncio + GPU 세마포어** — dev/단발 폴백 + **P2 컨틴전시**(RQ/CUDA가 막히면 동일 Job 인터페이스로 폴백).
|
||
> ⚠️ **근거:** RQ 기본 워커는 작업당 `os.fork()`하며, 부모에서 초기화된 CUDA 컨텍스트는 fork된 자식에서 재사용 불가([pytorch#40403](https://github.com/pytorch/pytorch/issues/40403)). 따라서 **fork 금지(SimpleWorker/장수명)** 가 필수. RQ progress는 내장이 없어 `job.save_meta()` 수동 호출 필요([RQ docs](https://python-rq.org/docs/jobs/)).
|
||
|
||
**D2. 실시간 스트리밍 구현**
|
||
- **(A) faster-whisper 위 커스텀 LocalAgreement-2** ✅ *(채택)* — Pros: 큐/디바이스/후처리 통합 제어. Cons: 안정화 정확성 직접 구현(난이도 과소평가 금지 → §3.7c 계약 명시). 지연 3~5초 관대는 *지연*만 완화하지 *정확성*은 아님.
|
||
- **(B) WhisperLive 백엔드 vendoring** — 검증된 WS+VAD를 backend로 감싸기. *Invalidation:* 통합·하이브리드 제어가 우선이나 **청킹/안정화 휴리스틱은 차용**.
|
||
- **(C) WhisperLiveKit(AlignAtt/SimulStreaming)** — 2025 SOTA. *Invalidation:* 3~5초 목표엔 과투자(P5 옵션).
|
||
|
||
**D3. 추론 백엔드 추상화**
|
||
- **(A) faster-whisper 단일 엔진 + compute_type 자동** ✅ — Pros: GPU/CPU/int8/fp16 단일 경로. Cons: 타 엔진 미지원(범위상 불필요).
|
||
- **(B) 멀티 엔진 플러그인** — *Invalidation:* 조기 추상화 → 얇은 인터페이스(`engine/base.py`)만 두고 구현 1종.
|
||
|
||
---
|
||
|
||
## 3. Target Project Structure
|
||
|
||
```
|
||
luke_scribe/
|
||
├── pyproject.toml (uv; extras: gpu, diarize, llm) ├── run.sh ├── .env.example
|
||
├── docker/{Dockerfile.gpu, Dockerfile.cpu, docker-compose.yml}
|
||
└── src/luke_scribe/
|
||
├── config.py # pydantic-settings (model rt/batch, device, precision, redis, retention, api_keys+scopes, tunnel, corrector+allowlist)
|
||
├── cli.py # typer: serve | transcribe | bench | detect
|
||
├── api/{app.py, deps.py, schemas.py, routes/{jobs.py, stream.py, admin.py}}
|
||
├── devices/{manager.py, profile.py, vram_probe.py}
|
||
├── engine/{base.py, faster_whisper_engine.py, model_registry.py}
|
||
├── audio/{ingest.py, vad.py}
|
||
├── pipeline/{batch.py, realtime.py}
|
||
├── jobqueue/{broker.py, jobs.py, worker.py, inproc.py, cancel.py}
|
||
├── postprocess/{pipeline.py, glossary.py, rules.py, llm.py, confidence.py}
|
||
├── diarization/pyannote_diarizer.py
|
||
├── results/{store.py, formats.py, retention.py}
|
||
├── connectivity/tunnel.py
|
||
└── observability/{logging.py, metrics.py}
|
||
```
|
||
|
||
### 3.5 Worker Execution Model & GPU Concurrency *(P0-1 해소)*
|
||
- **배치 레인:** RQ **`SimpleWorker`**(또는 장수명 커스텀 워커). 워커 부팅 시 `WhisperModel`을 **1회 적재**해 프로세스 수명 동안 보유(재적재·fork 금지). **GPU당 워커 프로세스 1개 기본**, 워커 내 GPU 접근은 단일 스레드(동시 decode 금지). 동시성 = **device-bound 워커 프로세스 수**(인터프로세스), `--workers` 오버라이드.
|
||
- **실시간 레인:** WS 세션은 enqueue-once가 아니므로 RQ에 넣지 않고 **API 프로세스 내 장수명 turbo 핸들러**(asyncio + 단일 GPU 락)가 처리. 세션→Job 매핑은 상태 추적용으로만.
|
||
- **하트비트/heartbeat 공백 보완:** SimpleWorker는 작업 중 하트비트가 없으므로 §3.7d의 throttled progress emit가 사실상 하트비트 역할(장시간 작업이 "멈춤"으로 오인되지 않게).
|
||
- **컨틴전시/Colab:** GPU·RQ가 막히거나 **Colab(Docker 불가)** 이면 D1-C **in-proc 큐(Redis 불필요)** 로, 동일 Job 인터페이스 유지(§3.10d).
|
||
|
||
### 3.6 VRAM Sizing & Capability Tier *(P0-2 해소 + CCG ③: 능력 등급 자동판정)*
|
||
- **부팅 시 실측(`devices/vram_probe.py`):** GPU VRAM(총/여유)·**RAM**·**디스크 여유** 감지 + 대상 모델을 1회 로드해 `allocated` 실측(헤드룸 ×1.3). **정적 상수 비의존.**
|
||
- **보수 기본 상수(측정 전 폴백):** large-v3 fp16 ≈10GB·int8 ≈3.5GB, turbo fp16 ≈4GB·int8 ≈1.8GB.
|
||
- **능력 등급(자동 — 무음 모델강등이 아니라 "제공 가능 모델"을 등급이 결정):**
|
||
|
||
| 등급 | 조건(실측) | 제공 |
|
||
|---|---|---|
|
||
| T0 CPU | GPU로 turbo도 무리/GPU 없음 | turbo@CPU |
|
||
| T1 turbo-GPU | turbo는 GPU OK, large-v3 무리 | turbo@GPU (large-v3 미제공 → 배치도 turbo) |
|
||
| T2 스왑 | large-v3 OK, turbo와 동시상주 불가 | 호출별 load/unload(MRU 상주, 스왑 최소화) |
|
||
| T3 동시상주 | turbo+large-v3 동시 적재 가능 | 둘 다 상주 → rt(turbo)+batch(large-v3) 동시 |
|
||
| ~~T4~~ | 모델 다중복제 | 제외 |
|
||
|
||
- **정밀도:** cc≥7.0&free≥12GB→`float16`; cc≥7.0&free<12GB→`int8_float16`; Pascal(6.x)→`int8`; CPU→`int8`.
|
||
- **워커수:** `workers = max(1, floor((free_VRAM − reserve) / measured_per_worker))` (§3.9b의 reserve = 헤드룸 + 실시간 모델 footprint).
|
||
- **디스크 가드:** 다운로드 전 여유 공간 확인, 부족 시 명확 오류.
|
||
- **투명성:** `/v1/system`·`/v1/models`로 등급·제공모델, 결과에 **`model_used`/`compute_type_used`** 항상.
|
||
- **OOM 처리:** 강등 시도 ≤2회(fp16→int8→CPU) 후 실패(재큐 1회). **1050**: T1(turbo-int8)/T0. **T4~H100**: T3.
|
||
|
||
### 3.7 Job Lifecycle: Cancellation, Temp Files, Retention *(P0-3 해소)*
|
||
- **(a) 협조적 취소:** `DELETE /v1/jobs/{id}` → Redis에 cancel 플래그. 워커는 faster-whisper **세그먼트 제너레이터를 소비하며 세그먼트 경계마다 플래그 확인**(유일한 선점 지점). 현재 세그먼트 연산은 완료 후 중단 → 상태 `cancelled`. (긴급 hard-kill은 워커 프로세스 종료 옵션으로만.)
|
||
- **(b) 임시파일 수명:** ffmpeg 파생 wav는 추적 tempdir에 생성, **모든 종료 경로(success/fail/cancel/OOM)의 `finally`에서 삭제**. 업로드 원본도 전사 시작 시점 이후 보유하다 종료 시 삭제.
|
||
- **(c) 실시간 LocalAgreement 계약:** 설정 명시 — `redecode_window`(예: 마지막 15s 오디오), `confirmed_prefix` 절단 규칙, `retained_left_context`(예: 5s), VAD 무음 경계에서 확정. 확정 세그먼트 방출 후 버퍼 절단(메모리 평탄). 단위 테스트로 버퍼 절단 불변식 검증.
|
||
- **(d) progress 발행:** 워커가 제너레이터를 소비하며 `processed_sec/total_sec` 계산 → **throttled `job.save_meta()`**(N 세그먼트마다 또는 ≥1s). `total_sec`는 ingest 시 duration probe로 확보. `queue_position` = 레인 큐 인덱스.
|
||
- **(e) 보관 sweeper 경합:** `results/retention.py`는 **터미널 상태(completed/failed/cancelled) Job만** 7d TTL 청소. {queued, processing} 보유 결과·임시물은 건드리지 않음.
|
||
|
||
### 3.8 Security Boundary *(P1-7 해소)*
|
||
- **API Key + 스코프:** `X-API-Key` 검증 + `ApiKey.scopes` 강제(예: `transcribe`, `admin`). 키 회전/폐기 설정.
|
||
- **외부 egress 통제:** LLM `external`/`openai` 백엔드는 **config allowlist 엔드포인트에만** 송신 가능(SSRF 방지), **기본 off + 명시 opt-in + 전송 1건당 감사 로그(key id, endpoint, job id)**. 옵션으로 전송 전 PII 마스킹.
|
||
|
||
### 3.9 Shared-GPU Accounting & Realtime Concurrency *(v2.1 — 합의 잔여조건)*
|
||
- **(a) 이벤트 루프 비블로킹:** 실시간 turbo decode는 동기 CTranslate2 호출이므로 **`await loop.run_in_executor(single_thread_executor, decode)`** 로 오프로딩(단일 GPU 락 직렬성 유지). P3 착수 시 CT2의 GIL 해제 여부 검증 — 미해제면 실시간 레인을 **별도 디코드 프로세스**로 분리. *(NEW-1a)*
|
||
- **(b) 공유 GPU VRAM 회계:** `reserve = base_headroom + (realtime_enabled ? measured_realtime_vram : 0)`. 실시간 모델이 동일 GPU 상주 시 배치 워커수 공식(§3.6)이 그 footprint를 반드시 포함 → 단일 GPU + 실시간 동시 활성에서 oversubscribe 금지. *(NEW-1b)*
|
||
- **(c) 실시간 동시 세션:** turbo 인스턴스 1개를 전 세션이 **직렬 공유**(단일 GPU 락). **최대 동시 WS 세션 상한**(설정) + 초과 시 거부/대기, 실시간 레인 대기시간 메트릭. AC-8(≤5s)은 "≤N 세션 한도 내" 보장으로 명시. *(R3)*
|
||
- **(d) 실효 compute_type 로깅:** `vram_probe`/`/v1/system`이 **요청 vs 실효 compute_type** 보고, 불일치(T4 `int8_float16` 무음 강등 등) 경고. AC-2/3 계약에 포함. *(NEW-2/R2)*
|
||
- **(e) RQ job_timeout:** enqueue 시 `job_timeout ≥ 4h(+마진)`(duration probe 기반). RQ 기본 180s로는 장시간 작업이 3분에 강제 종료되어 AC-7 위반 → 반드시 상향. *(R1/NEW-3)*
|
||
- **(f) Phase Exit 구속력:** 각 Phase Exit는 **hard gate**(미충족 시 다음 Phase 착수 금지). 단 옵션 기능(diarization/LLM/tunnel) 미완은 "문서화된 제한"으로 soft 허용. *(R4)*
|
||
|
||
---
|
||
|
||
### 3.10 CCG 리뷰 반영 — 프로비저닝·WS·공유스토어·실행 프로파일 *(v2.2)*
|
||
- **(a) 모델 프로비저닝(오프라인 강제 폐기):** 시작 시 모델 존재 확인 → 없으면 HF 다운로드(인터넷 OK) → **체크섬/로드 실패 시 캐시 purge 후 1회 재다운로드** → 준비 전 API는 `503`/`status:"loading"`. ("로컬 실행"=외부 STT API 미사용이지 에어갭 아님 — 스펙 "오프라인" 문구 폐기.)
|
||
- **(b) WS init-frame 인증:** 연결 직후 첫 메시지 `{type:"init", api_key, audio:{codec,sample_rate,channels}, options}`, 2초 내 미수신 시 close. 헤더 못 쓰는 브라우저 대응 + 키가 URL/로그에 안 남음. 코덱/샘플레이트도 이 프레임에서 협상(Gemini WS 지적 흡수).
|
||
- **(c) 공유 스토어(#1, 프로덕션 Docker 한정):** api↔worker가 별도 컨테이너 → **입력 원본·파생 wav·결과를 공유 볼륨/오브젝트 스토어**, **Job 메타는 Redis**. per-container 로컬 SQLite 금지. **Colab/단일프로세스는 디스크 1개라 무관.**
|
||
- **(d) 실행 프로파일 2종(코드 동일):** Colab/개발=순수 Python(`run.sh`/`python -m ...`)·**in-proc 큐(Redis 불필요)**·로컬 디스크·**cloudflared 바이너리**. 프로덕션=Docker+Redis+worker+공유 스토어. *(Colab은 Docker 불가가 하드 제약.)*
|
||
- **(e) 언어/hotwords:** 기본 `language="ko"`(요청별 override). **hotwords는 선택**(반복 고정용어 1회 등록; 매 전사 예측 아님) — 기본 경로는 ko 앵커+모델+rules 후처리, P1 bench로 hotwords 불필요 여부 실측.
|
||
- (미채택·추후: webhook, Idempotency-Key, 페이지네이션, 만료 `410`.)
|
||
|
||
---
|
||
|
||
## 4. Implementation Steps (by phase, with file refs & exit criteria)
|
||
|
||
### P1 — Core + 측정 게이트
|
||
1. **스캐폴딩** — `pyproject.toml`(extras), `config.py`, `run.sh`.
|
||
2. **Device Manager + VRAM probe** — `devices/{manager.py, vram_probe.py, profile.py}`: 감지 + §3.6 실측·정밀도·워커수·Model-Fit 분기. AC-2/3.
|
||
3. **Engine + registry** — `engine/faster_whisper_engine.py`(transcribe: hotwords/initial_prompt/word_ts/vad), `model_registry.py`(rt=turbo/batch=large-v3, 오버라이드). AC-4.
|
||
4. **Audio ingest(스트리밍)** — `audio/ingest.py`: **ffmpeg를 파일로 파이프**(전체 배열 인메모리 금지), duration/size probe, 4h/2GB→`413`. `audio/vad.py`: Silero VAD. AC-7/9.
|
||
5. **CLI `detect`/`transcribe`/`bench`** — `bench`를 **P1로 전진**: 도메인 KO+EN 클립으로 turbo vs large-v3 **R-WER + entity 보존율 + 속도 + 실측 VRAM** 측정 → 하이브리드 게이트 판정. AC-4/12.
|
||
- **Exit:** CPU와 실 GPU 1종에서 단일 파일 전사 성공; `detect`가 measured VRAM·정밀도·워커수 출력; `bench`가 모델 델타 리포트 산출.
|
||
|
||
### P2 — API + Queue
|
||
6. **FastAPI + 인증** — `api/{app.py, deps.py, schemas.py}`: API Key+스코프(§3.8). AC-11.
|
||
7. **RQ 워커(SimpleWorker)** — `jobqueue/{broker.py, jobs.py, worker.py, cancel.py, inproc.py}`: §3.5 실행모델, §3.7d progress, §3.7a 취소, §3.6 OOM 강등. AC-5/6.
|
||
8. **Jobs 라우트** — `api/routes/jobs.py`: `POST /v1/jobs`(만재→`429`), `GET`(queue_position/progress), `result?format=`, `DELETE`(취소), `GET /v1/jobs`. AC-1/6.
|
||
9. **Results + retention** — `results/{store.py, formats.py, retention.py}`: §3.7b/e, 원본·파생 삭제, 7d TTL 터미널만. AC-11.
|
||
10. **Docker** — `Dockerfile.{gpu,cpu}` + compose(api+redis+worker). **CT2/CUDA/cuDNN 트리플 핀**(예: CUDA12+cuDNN9; 구형/Colab은 CT2 다운그레이드 경로 문서화). `detect`가 런타임 CUDA 버전 노출. AC-12.
|
||
11. **Admin** — `/health`, `/v1/system`(profile/워커/큐깊이/가용 VRAM), `/v1/models`.
|
||
- **Exit:** 다건 enqueue→progress→result; **강제 OOM 시 강등·실패 경로 시연**; 취소가 `cancelled`로 종료 + 임시파일 삭제 확인.
|
||
|
||
### P3 — Realtime
|
||
12. **실시간** — `pipeline/realtime.py`(§3.7c 계약: redecode_window/prefix/left-context/VAD), `api/routes/stream.py`(WS, partial/final/status, 백프레셔). AC-8.
|
||
- **Exit:** 부분결과 ≤5s + 최종 안정화; **30분 세션 메모리 평탄**(±15% 이내, 단조증가 없음).
|
||
|
||
### P4 — Output + Post-processing
|
||
13. **출력 옵션** — timestamps/word/SRT/VTT 요청별. AC-9.
|
||
14. **후처리(glossary/rules/flag)** — `postprocess/{pipeline.py, glossary.py, rules.py, confidence.py}`. AC-10.
|
||
- **Exit:** glossary on/off diff로 entity 보존 향상 측정; 저신뢰 플래그 부착.
|
||
|
||
### P5 — Advanced
|
||
15. **LLM 보정(옵션)** — `postprocess/llm.py`: local/external 백엔드, §3.8 egress 통제, confidence-gated, 기본 off. AC-10.
|
||
16. **Diarization(옵션)** — `diarization/pyannote_diarizer.py`(HF 토큰).
|
||
17. **Colab 터널** — `connectivity/tunnel.py`: **API lifespan에 종속 supervise**(같이 start/stop), URL 회전 시 재출력, 임시성 명시. AC-13.
|
||
18. **관측/벤치 확장** — `observability/{logging,metrics}.py`(큐깊이·워커가동·RTF·OOM 카운트), `bench` 확장.
|
||
- **Exit:** external egress allowlist+감사로그 동작; Colab `serve --tunnel cloudflare` 외부 200.
|
||
|
||
---
|
||
|
||
## 5. Acceptance Criteria (수치화)
|
||
|
||
스펙 AC-1~13 상속 + **모호 항목 절대 기준화**:
|
||
- **AC-2/3:** `detect`가 measured VRAM 기반 정밀도/워커수 산정 + **능력 등급(T0~T3) 자동판정·`/v1/models` 제공모델·`model_used` 표시**(§3.6); 1050→int8/CPU(large-v3 GPU 불가), T4→int8_float16, A100/H100→fp16.
|
||
- **AC-4(혼용어, 절대):** 도메인 entity 용어(vLLM·API·FastAPI·Kubernetes·LLM·GPU 등) **verbatim 보존율 ≥ 95%**(hotwords on, domain set), 그리고 도메인 **R-WER ≤ {P1 bench 기준선}**. 보조로 batch(v3) ≤ realtime(turbo).
|
||
- **AC-5/6:** 다건 동시 → 각 `GET`이 `queue_position`(앞 N건)·`progress %`(processed_sec/total_sec); 만재 `429`.
|
||
- **AC-7(메모리, 절대):** 2h 배치 + 30분 WS에서 **워밍업 후 peak RSS 변동 ±15% 이내, 단조 증가 없음**; OOM 없음; 4h/2GB→`413`.
|
||
- **AC-8:** 부분결과 ≤5s(동시 ≤N 세션 한도 내), 최종 안정화; 다중 세션 부하에서도 REST/배치 응답성 유지(이벤트 루프 비블로킹, §3.9a).
|
||
- **AC-11:** 전사 후 원본+파생 wav 부재; 결과 7d 후 만료; 터미널 Job만 청소.
|
||
|
||
---
|
||
|
||
## 6. Risks and Mitigations
|
||
| Risk | Mitigation |
|
||
|------|-----------|
|
||
| CUDA fork 실패 | SimpleWorker/장수명·model-load-once, fork 금지(§3.5) |
|
||
| OOM(동시성) | **측정 VRAM**(§3.6) 기반 워커수, semaphore, 강등 최대 2회 후 실패 |
|
||
| turbo 혼용어 부족 | P1 bench 게이트(§4-5), 하이브리드/hotwords/후처리, 모델 스왑 |
|
||
| 실시간 떨림/누수 | LocalAgreement 계약(§3.7c), 버퍼 절단 불변식 테스트 |
|
||
| 취소 no-op / 임시파일 누출 | 협조적 취소 + `finally` 삭제(§3.7a/b) |
|
||
| 보관 경합 | 터미널 상태만 sweeper(§3.7e) |
|
||
| 외부 LLM PII 유출 | allowlist+opt-in+감사로그+마스킹(§3.8) |
|
||
| CT2/cuDNN 버전 불일치 | Dockerfile 트리플 핀 + 다운그레이드 경로(P2-10) |
|
||
| Redis SPOF | in-proc 폴백(D1-C), 헬스체크 |
|
||
|
||
## 7. Pre-mortem (deliberate — 3 시나리오, 위험표와 비중복)
|
||
1. **"P2 통합에서 워커가 fork-CUDA로 즉시 죽었다."** 원인: RQ 기본 fork. 예방: §3.5 SimpleWorker 강제 + P2 Exit에 "실 GPU 워커 기동" 게이트, P1에서 워커 기동 스파이크 선검증.
|
||
2. **"데모에서 'vLLM'→'브이엘엘엠'으로 신뢰 상실."** 원인: 모델/hotwords 미검증. 예방: **P1 bench 게이트**(entity 보존율 ≥95% 기준), 기본 hotwords 사전 동봉, 미달 시 v3 채택 자동 판정.
|
||
3. **"30분 WS 세션에서 메모리 누수로 컨테이너 OOM-kill."** 원인: 링버퍼/가설 미절단. 예방: §3.7c 절단 계약 + 버퍼 불변식 단위테스트 + P3 Exit의 ±15% 메모리 게이트.
|
||
|
||
## 8. Expanded Test Plan (deliberate, 수치 게이트 포함)
|
||
- **Unit:** Device 결정(cc/measured-VRAM→compute_type/workers/Model-Fit 분기), OOM 강등 캡(≤2), LocalAgreement 버퍼 절단 불변식, formats(srt/vtt 타임코드), rules 정규화, retention(터미널만), options(413/429).
|
||
- **Integration:** ffmpeg(스트리밍, 인메모리 아님)→engine→result, Redis enqueue→SimpleWorker→progress(save_meta throttle)→status, 취소 플래그→`cancelled`+임시파일 삭제, glossary diff(entity 보존율), egress allowlist 차단/허용.
|
||
- **E2E:** 파일 Job 수명주기(생성→progress→결과·원본+파생 삭제), 동시 다건 queue_position, WS 세션(부분≤5s), Colab `serve --tunnel cloudflare` 200, 강제 OOM 강등 경로.
|
||
- **Observability:** 메트릭(큐깊이·워커가동률·RTF·OOM 카운트·**실시간 레인 대기시간**), 구조적 로그(job_id 상관), `/health`·`/v1/system` 계약(**요청 vs 실효 compute_type 보고 검증**), **2h 배치/30분 WS RSS ±15% 평탄 검증**, external egress 감사로그 1건/전송, **다중 세션 AC-8(≤5s) + 동시 `GET /v1/jobs` 응답성 바운드**, **>180s 작업이 job_timeout으로 강제종료되지 않음**, **공유 GPU(배치+실시간) VRAM 비-oversubscribe Model-Fit 상호작용 테스트**.
|
||
|
||
## 9. Verification Steps
|
||
1. `uv sync && uv run python -m luke_scribe.cli detect` → measured VRAM·정밀도·워커수.
|
||
2. `... bench --samples samples/ko_en/` → turbo vs v3 R-WER·entity 보존율·VRAM → 하이브리드 판정.
|
||
3. `... transcribe samples/ko_en.wav` → entity verbatim ≥95% 확인.
|
||
4. `docker compose up` → 다건 `POST`/`GET`(progress/위치), 결과 후 원본+파생 부재, `DELETE`→`cancelled`.
|
||
5. 강제 작은-VRAM 환경 → 강등(≤2)·실패 경로 시연.
|
||
6. WS 클라이언트 30분 → 부분≤5s, RSS ±15%.
|
||
7. `pytest tests/`(unit/integration) + e2e 스모크.
|
||
8. Colab → `serve --tunnel cloudflare` URL 외부 200; egress allowlist 차단 테스트.
|
||
|
||
## 10. ADR (refined)
|
||
- **Decision:** faster-whisper 단일 엔진 + (게이트된)하이브리드 모델 + Redis(RQ **SimpleWorker**, no-fork, model-load-once) 배치 + 전용 WS 실시간 핸들러 + **측정 기반 DeviceProfile** + CLI/Docker(버전 핀).
|
||
- **Drivers:** 혼용어 정확도, 이식성/자동스케일(실측), 동시성/가시성.
|
||
- **Alternatives:** Celery(과함), in-proc(폴백/컨틴전시), WhisperLive/Kit(참고/P5), 멀티엔진(조기), RQ 기본 fork(CUDA 불가→거부).
|
||
- **Why chosen:** 사용자 확정(하이브리드/Redis/후처리/보관/상한/diarize) 준수 + fork/VRAM/취소 리스크를 명시 메커니즘으로 해소.
|
||
- **Consequences:** SimpleWorker 하트비트 공백→progress로 보완; 실시간 안정화 자체 구현; 1050 배치 large-v3 GPU 불가; Redis 의존; 공유 GPU에서 배치+실시간 VRAM 합산 회계(§3.9b)·실시간 decode 오프로딩(§3.9a) 필요.
|
||
- **Follow-ups:** Redis HA, SimulStreaming(P5), egress 마스킹 고도화. (도메인 bench는 follow-up이 아니라 **P1 게이트**로 승격.)
|
||
|
||
---
|
||
|
||
## 11. Open Questions (사용자 확인 권장)
|
||
1. 실제 배포가 **다중 GPU 워커**를 필요로 하나, 아니면 단일 T4/Colab 위주? (후자면 워커수 공식 위험 축소.)
|
||
2. turbo의 KO entity 보존율이 P1 bench에서 ≥95%면 **단일 모델로 단순화**할 의향이 있는지(VRAM/복잡도 절감).
|
||
3. 취소는 **협조적(세그먼트 경계)** 으로 충분한지, 즉시 hard-kill이 필요한지.
|
||
|
||
---
|
||
|
||
## 12. v2 Changelog (적용 내역 → 리뷰 매핑)
|
||
- **[P0-1]** §3.5 Worker Execution Model 신설 — SimpleWorker/no-fork/model-load-once, 배치·실시간 레인 분리, in-proc 컨틴전시. (Arch P0-1, Critic F1)
|
||
- **[P0-2]** §3.6 VRAM 부팅 실측 + 보수 상수(large-v3 fp16 10GB) + Model-Fit 분기 + `bench`를 **P1로 전진**(단계 역전 수정). (Arch P0-2/P1-4, Critic F2)
|
||
- **[P0-3]** §3.7 협조적 취소 + 임시파일 `finally` 삭제 + 보관 sweeper 터미널-한정. (Arch P0-3/P1-6, Critic F3)
|
||
- **[P1]** §3.7d progress 메커니즘(save_meta throttle), §3.8 보안 egress allowlist+감사+스코프, 하이브리드 P1 게이트. (Arch P1-4/5/7, Critic F4/F5)
|
||
- **[P1-신규]** §4 단계별 **Exit Criteria** + in-proc 컨틴전시 명시. (Critic F6)
|
||
- **[P2]** §3.7c LocalAgreement 계약, §4-4 ffmpeg 스트리밍 보장, §4-10/17 cloudflared lifecycle + CT2/CUDA/cuDNN 핀. (Arch P2-8/9/10)
|
||
- **[AC]** AC-4 절대 기준(entity 보존율 ≥95% + R-WER 기준선), AC-7 메모리 ±15% 평탄, 워커당_추정=측정값, OOM 강등 ≤2회, `429`/`413` 분리. (Critic 모호성 4건)
|
||
- **[Risk]** OOM 위험을 측정 상수 기반으로 재작성(순환성 제거); pre-mortem #1을 위험표와 비중복화하고 예방을 P1/P2 게이트에 배선.
|
||
|
||
---
|
||
|
||
## 13. v2.1 Changelog (합의 잔여조건 반영)
|
||
- **[NEW-1a]** §3.9a 실시간 decode `run_in_executor` 오프로딩(+GIL 검증/프로세스 분리 폴백).
|
||
- **[NEW-1b]** §3.9b `reserve`에 실시간 모델 VRAM 포함 → 공유 GPU oversubscribe 방지.
|
||
- **[R1/NEW-3]** §3.9e RQ `job_timeout ≥ 4h`.
|
||
- **[NEW-2/R2]** §3.9d 요청 vs 실효 compute_type 로깅 + AC-2/3 계약 + 관측 테스트.
|
||
- **[R3]** §3.9c 실시간 최대 동시 세션 상한 + 대기시간 메트릭 + 다중세션 AC-8 테스트.
|
||
- **[R4]** §3.9f Phase Exit 구속력(hard/soft) 명시.
|
||
|
||
## 합의 결과 (Consensus Outcome)
|
||
- **Architect (v2): APPROVE WITH CONDITIONS** — P0×3 해소 확인, 잔여 NEW-1(a/b)/2/3.
|
||
- **Critic (v2): APPROVE WITH CONDITIONS** — CRITICAL×3·MAJOR×3 해소 확인, NEW-1 검증 + R1~R4.
|
||
- 모든 잔여 조건을 **v2.1**에 반영 → **합의 도달**. iteration 2/5.
|
||
|
||
---
|
||
## 14. v2.2 Changelog — CCG 외부 리뷰(Codex/Gemini) 반영
|
||
- **②→프로비저닝:** "오프라인 강제" 폐기. 다운로드 OK + 손상 재다운로드 + `loading` 상태(§3.10a). 스펙 제약 문구 수정.
|
||
- **③→능력 등급:** §3.6을 T0(CPU)~T3(동시상주) 자동판정으로 재작성, T4(다중복제) 제외, `model_used` 항상 표시(무음 모델강등 폐기).
|
||
- **④→WS init 인증:** §3.10b 첫 메시지 인증+코덱 협상. hotwords는 선택으로 강등(§3.10e).
|
||
- **⑤→기본 `ko`:** 요청별 override(§3.10e).
|
||
- **#1→공유 스토어:** 프로덕션 Docker 한정 공유 볼륨/오브젝트 스토어(§3.10c); Colab 무관.
|
||
- **Colab:** Docker 불가 → 순수 Python·in-proc·바이너리 cloudflared(§3.5, §3.10d).
|
||
- 출처: `.omc/artifacts/ask/{codex,gemini}-20260603-095739.md`. 미채택(추후): webhook·Idempotency-Key·페이지네이션·`410`.
|
||
|
||
---
|
||
*Consensus v2.2 — `pending approval`. 실행(team/ralph/autopilot)은 사용자의 별도 명시 승인이 있어야만 진행됩니다. 승인 전 소스 수정·커밋·실행 스킬 호출 없음.*
|