diff --git a/notebooks/luke-scribe-colab.ipynb b/notebooks/luke-scribe-colab.ipynb index 5b4bb35..d3ccdfb 100644 --- a/notebooks/luke-scribe-colab.ipynb +++ b/notebooks/luke-scribe-colab.ipynb @@ -59,7 +59,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": "# 3) 설치 — Colab은 시스템 pip가 표준 (venv는 Colab에서 ensurepip 오류로 실패할 수 있음)\n!pip install -q --upgrade pip\n!pip install -q -e '.[engine,api]'\n!pip install -q edge-tts # 샘플 음성 생성용\nprint('설치 완료')\n!which luke-scribe && luke-scribe --help 2>&1 | head -8\n" + "source": "# 3) 설치 — Colab은 시스템 pip가 표준 (venv는 Colab에서 ensurepip 오류로 실패할 수 있음)\n!pip install -q --upgrade pip\n!pip install -q -e '.[engine,api]'\n!pip install -q edge-tts # 샘플 음성 생성용\n\n# CUDA 13(Colab)에서는 CTranslate2 wheel(CUDA 12용)이 런타임 라이브러리를 못 찾음.\n# CUDA 12 런타임(cuBLAS/cuDNN)을 pip로 설치하고 LD_LIBRARY_PATH에 추가한다.\n!pip install -q nvidia-cublas-cu12 nvidia-cudnn-cu12\nimport os, nvidia.cublas.lib, nvidia.cudnn.lib\nos.environ['LD_LIBRARY_PATH'] = (\n os.path.dirname(nvidia.cublas.lib.__file__) + ':' +\n os.path.dirname(nvidia.cudnn.lib.__file__) + ':' +\n os.environ.get('LD_LIBRARY_PATH', '')\n)\nprint('설치 완료')\n!which luke-scribe && luke-scribe --help 2>&1 | head -8\n" }, { "cell_type": "markdown", @@ -102,7 +102,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": "# 7) 실전 전사 — GPU 자동 감지 + 모델 다운로드 (large-v3-turbo)\n# 첫 실행 시 Hugging Face에서 모델을 다운로드합니다 (turbo ≈ 1.6GB, 1~2분)\n!time luke-scribe transcribe samples/colab-ko-en.mp3 --language ko --device auto\n" + "source": "# 7) 실전 전사 — GPU 자동 감지 + 모델 다운로드 (large-v3-turbo)\n# 첫 실행 시 Hugging Face에서 모델을 다운로드합니다 (turbo ≈ 1.6GB, 1~2분)\n# CUDA 13 환경에서 CTranslate2가 GPU를 못 열면 자동으로 CPU로 폴백합니다.\nimport subprocess, json\nr = subprocess.run(\n ['luke-scribe', 'transcribe', 'samples/colab-ko-en.mp3', '--language', 'ko', '--device', 'auto'],\n capture_output=True, text=True,\n)\nprint(r.stdout[-2500:] if r.stdout else '')\nprint(r.stderr[-800:] if r.stderr else '')\n" }, { "cell_type": "markdown", @@ -126,7 +126,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": "# 9) API 키 생성 (1회 출력)\n!luke-scribe key --create --scopes transcribe,admin --file /content/api_keys.json\n" + "source": "# 9) API 키 생성 — raw 키는 1회만 출력되므로 여기서 캡처한다\nimport subprocess, json, os\nr = subprocess.run(\n ['luke-scribe', 'key', '--create', '--scopes', 'transcribe,admin', '--file', '/content/api_keys.json'],\n capture_output=True, text=True,\n)\ncreated = json.loads(r.stdout)\nRAW_KEY = created['key'] # 이후 셀에서 사용\nprint('key_id:', created['key_id'], '| scopes:', created['scopes'])\nprint('raw 키 캡처 완료 (표시 안 함)')\n" }, { "cell_type": "code", @@ -140,7 +140,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": "# 11) 업로드 → poll → 결과\nimport json, time, urllib.request, urllib.error\n\nKEY = json.load(open('/content/api_keys.json'))['keys'][0]\n\n# multipart 업로드 (curl 사용 — 간단)\nimport subprocess\nr = subprocess.run(\n ['curl', '-s', '-X', 'POST', 'http://localhost:8000/v1/jobs',\n '-H', f'X-API-Key: {KEY}',\n '-F', 'file=@samples/colab-ko-en.mp3',\n '-F', 'options={\"language\":\"ko\",\"formats\":[\"json\",\"srt\"]}'],\n capture_output=True, text=True,\n)\njob = json.loads(r.stdout)\nprint('생성:', job)\njob_id = job['job_id']\n\n# 완료까지 poll (워커 없이 in-proc이라 즉시 완료되지는 않음 → 여기선 생성까지만 확인)\nprint('job_id =', job_id)\n" + "source": "# 11) 업로드 → poll → 결과 (RAW_KEY는 셀 9에서 캡처된 값)\nimport json, time, urllib.request, urllib.error, subprocess\n\nassert 'RAW_KEY' in globals(), '셀 9(키 생성)를 먼저 실행하세요'\n\n# multipart 업로드 (curl 사용 — 간단)\nr = subprocess.run(\n ['curl', '-s', '-X', 'POST', 'http://localhost:8000/v1/jobs',\n '-H', f'X-API-Key: {RAW_KEY}',\n '-F', 'file=@samples/colab-ko-en.mp3',\n '-F', 'options={\"language\":\"ko\",\"formats\":[\"json\",\"srt\"]}'],\n capture_output=True, text=True,\n)\njob = json.loads(r.stdout)\nprint('생성:', job)\njob_id = job.get('job_id')\n\n# 완료까지 poll (in-proc 큐는 워커가 소비해야 하므로 셀 12에서 drain 처리)\nif job_id:\n print('job_id =', job_id)\n for _ in range(60):\n st = json.loads(urllib.request.urlopen(\n urllib.request.Request(f'http://localhost:8000/v1/jobs/{job_id}',\n headers={'X-API-Key': RAW_KEY})\n ).read())\n if st['status'] in ('completed', 'failed', 'cancelled'):\n print('최종 상태:', st['status'])\n break\n time.sleep(2)\n else:\n print('60초 내 미완료 — 셀 12(워커 drain) 실행 후 다시 확인')\nelse:\n print('업로드 실패 — 서버 로그 확인: /content/logs/server.log')\n" }, { "cell_type": "markdown", @@ -169,7 +169,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": "## 8) 문제 해결\n\n### CUDA 라이브러리 오류 (faster-whisper/CTranslate2)\n\nColab T4에는 CUDA 런타임이 있지만, CTranslate2가 pip wheel의 CUDA 라이브러리를\n찾지 못하면 아래를 실행하세요:\n\n```python\nimport os\nos.environ['LD_LIBRARY_PATH'] = (\n '/usr/local/cuda/lib64:' + os.environ.get('LD_LIBRARY_PATH', '')\n)\n```\n\n### 모델 다운로드 실패\n\n- Hugging Face 연결 필요. 재시도: `LUKESCRIBE_MODEL_DOWNLOAD_RETRIES=3`\n- 특정 모델만: `--model large-v3-turbo` (기본) / `--model large-v3`\n\n### OOM (16GB VRAM)\n\n- EngineOwner가 자동으로 정밀도를 강등합니다 (float16 → int8_float16 → int8 → CPU)\n- 수동 지정: `--compute-type int8_float16`\n\n### 저장소가 private인데 클론 안 됨\n\n1번 셀의 `GITEA_TOKEN`에 토큰을 입력하고 런타임 → **Restart session** 후 다시 실행하세요.\n" + "source": "## 8) 문제 해결\n\n### CUDA 13 환경에서 `unsupported device cuda:0` (faster-whisper/CTranslate2)\n\nColab이 CUDA 13.0(드라이버 580)으로 올라가면서, CUDA 12용으로 빌드된\nCTranslate2 wheel이 런타임 라이브러리(cuBLAS/cuDNN)를 찾지 못하는 이슈가 발생합니다.\n설치 셀 3에서 CUDA 12 런타임을 pip로 설치하고 `LD_LIBRARY_PATH`를 잡아 해결합니다.\n그래도 안 되면 CPU로 폴백: `--device cpu --compute-type int8` (느리지만 확실).\n\n### 모델 다운로드 실패\n\n- Hugging Face 연결 필요. 재시도: `LUKESCRIBE_MODEL_DOWNLOAD_RETRIES=3`\n- 특정 모델만: `--model large-v3-turbo` (기본) / `--model large-v3`\n\n### API 키 인증 실패 (401)\n\n`api_keys.json`에는 **다이제스트만** 저장되고 raw 키는 생성 시 1회만 출력됩니다\n(보안 설계). 셀 9에서 `RAW_KEY`를 캡처했는지 확인하세요 — 파일에서 키를 읽으면 401이 납니다.\n\n### OOM (16GB VRAM)\n\n- EngineOwner가 자동으로 정밀도를 강등합니다 (float16 → int8_float16 → int8 → CPU)\n- 수동 지정: `--compute-type int8_float16`\n\n### 저장소가 private인데 클론 안 됨\n\n1번 셀의 `GITEA_TOKEN`에 토큰을 입력하고 런타임 → **Restart session** 후 다시 실행하세요.\n" } ] } \ No newline at end of file diff --git a/scripts/build_colab_notebook.py b/scripts/build_colab_notebook.py index 3ac50ba..a91a84e 100644 --- a/scripts/build_colab_notebook.py +++ b/scripts/build_colab_notebook.py @@ -106,6 +106,16 @@ def cells() -> list[dict]: "!pip install -q --upgrade pip\n" "!pip install -q -e '.[engine,api]'\n" "!pip install -q edge-tts # 샘플 음성 생성용\n" + "\n" + "# CUDA 13(Colab)에서는 CTranslate2 wheel(CUDA 12용)이 런타임 라이브러리를 못 찾음.\n" + "# CUDA 12 런타임(cuBLAS/cuDNN)을 pip로 설치하고 LD_LIBRARY_PATH에 추가한다.\n" + "!pip install -q nvidia-cublas-cu12 nvidia-cudnn-cu12\n" + "import os, nvidia.cublas.lib, nvidia.cudnn.lib\n" + "os.environ['LD_LIBRARY_PATH'] = (\n" + " os.path.dirname(nvidia.cublas.lib.__file__) + ':' +\n" + " os.path.dirname(nvidia.cudnn.lib.__file__) + ':' +\n" + " os.environ.get('LD_LIBRARY_PATH', '')\n" + ")\n" "print('설치 완료')\n" "!which luke-scribe && luke-scribe --help 2>&1 | head -8\n" ), @@ -139,7 +149,14 @@ def cells() -> list[dict]: code( "# 7) 실전 전사 — GPU 자동 감지 + 모델 다운로드 (large-v3-turbo)\n" "# 첫 실행 시 Hugging Face에서 모델을 다운로드합니다 (turbo ≈ 1.6GB, 1~2분)\n" - "!time luke-scribe transcribe samples/colab-ko-en.mp3 --language ko --device auto\n" + "# CUDA 13 환경에서 CTranslate2가 GPU를 못 열면 자동으로 CPU로 폴백합니다.\n" + "import subprocess, json\n" + "r = subprocess.run(\n" + " ['luke-scribe', 'transcribe', 'samples/colab-ko-en.mp3', '--language', 'ko', '--device', 'auto'],\n" + " capture_output=True, text=True,\n" + ")\n" + "print(r.stdout[-2500:] if r.stdout else '')\n" + "print(r.stderr[-800:] if r.stderr else '')\n" ), md( "### 5-2) 후처리 검증\n\n" @@ -155,8 +172,16 @@ def cells() -> list[dict]: "서버를 백그라운드로 띄우고 **업로드 → poll → 결과(SRT)** 흐름을 검증합니다." ), code( - "# 9) API 키 생성 (1회 출력)\n" - "!luke-scribe key --create --scopes transcribe,admin --file /content/api_keys.json\n" + "# 9) API 키 생성 — raw 키는 1회만 출력되므로 여기서 캡처한다\n" + "import subprocess, json, os\n" + "r = subprocess.run(\n" + " ['luke-scribe', 'key', '--create', '--scopes', 'transcribe,admin', '--file', '/content/api_keys.json'],\n" + " capture_output=True, text=True,\n" + ")\n" + "created = json.loads(r.stdout)\n" + "RAW_KEY = created['key'] # 이후 셀에서 사용\n" + "print('key_id:', created['key_id'], '| scopes:', created['scopes'])\n" + "print('raw 키 캡처 완료 (표시 안 함)')\n" ), code( "# 10) 서버 기동 (in-proc 큐, 백그라운드 — Colab에서는 %%bash --bg 또는 nohup 사용)\n" @@ -176,26 +201,39 @@ def cells() -> list[dict]: " print(open('/content/logs/server.log').read()[-1500:])\n" ), code( - "# 11) 업로드 → poll → 결과\n" - "import json, time, urllib.request, urllib.error\n" + "# 11) 업로드 → poll → 결과 (RAW_KEY는 셀 9에서 캡처된 값)\n" + "import json, time, urllib.request, urllib.error, subprocess\n" "\n" - "KEY = json.load(open('/content/api_keys.json'))['keys'][0]\n" + "assert 'RAW_KEY' in globals(), '셀 9(키 생성)를 먼저 실행하세요'\n" "\n" "# multipart 업로드 (curl 사용 — 간단)\n" - "import subprocess\n" "r = subprocess.run(\n" " ['curl', '-s', '-X', 'POST', 'http://localhost:8000/v1/jobs',\n" - " '-H', f'X-API-Key: {KEY}',\n" + " '-H', f'X-API-Key: {RAW_KEY}',\n" " '-F', 'file=@samples/colab-ko-en.mp3',\n" " '-F', 'options={\"language\":\"ko\",\"formats\":[\"json\",\"srt\"]}'],\n" " capture_output=True, text=True,\n" ")\n" "job = json.loads(r.stdout)\n" "print('생성:', job)\n" - "job_id = job['job_id']\n" + "job_id = job.get('job_id')\n" "\n" - "# 완료까지 poll (워커 없이 in-proc이라 즉시 완료되지는 않음 → 여기선 생성까지만 확인)\n" - "print('job_id =', job_id)\n" + "# 완료까지 poll (in-proc 큐는 워커가 소비해야 하므로 셀 12에서 drain 처리)\n" + "if job_id:\n" + " print('job_id =', job_id)\n" + " for _ in range(60):\n" + " st = json.loads(urllib.request.urlopen(\n" + " urllib.request.Request(f'http://localhost:8000/v1/jobs/{job_id}',\n" + " headers={'X-API-Key': RAW_KEY})\n" + " ).read())\n" + " if st['status'] in ('completed', 'failed', 'cancelled'):\n" + " print('최종 상태:', st['status'])\n" + " break\n" + " time.sleep(2)\n" + " else:\n" + " print('60초 내 미완료 — 셀 12(워커 drain) 실행 후 다시 확인')\n" + "else:\n" + " print('업로드 실패 — 서버 로그 확인: /content/logs/server.log')\n" ), md( "### 참고 — 서버에서 실제 전사까지 실행하려면\n\n" @@ -254,21 +292,18 @@ def cells() -> list[dict]: ), md( "## 8) 문제 해결\n\n" - "### CUDA 라이브러리 오류 (faster-whisper/CTranslate2)\n\n" - "Colab T4에는 CUDA 런타임이 있지만, CTranslate2가 pip wheel의 CUDA 라이브러리를\n" - "찾지 못하면 아래를 실행하세요:\n" - "\n" - "```python\n" - "import os\n" - "os.environ['LD_LIBRARY_PATH'] = (\n" - " '/usr/local/cuda/lib64:' + os.environ.get('LD_LIBRARY_PATH', '')\n" - ")\n" - "```\n" - "\n" + "### CUDA 13 환경에서 `unsupported device cuda:0` (faster-whisper/CTranslate2)\n\n" + "Colab이 CUDA 13.0(드라이버 580)으로 올라가면서, CUDA 12용으로 빌드된\n" + "CTranslate2 wheel이 런타임 라이브러리(cuBLAS/cuDNN)를 찾지 못하는 이슈가 발생합니다.\n" + "설치 셀 3에서 CUDA 12 런타임을 pip로 설치하고 `LD_LIBRARY_PATH`를 잡아 해결합니다.\n" + "그래도 안 되면 CPU로 폴백: `--device cpu --compute-type int8` (느리지만 확실).\n\n" "### 모델 다운로드 실패\n\n" "- Hugging Face 연결 필요. 재시도: `LUKESCRIBE_MODEL_DOWNLOAD_RETRIES=3`\n" "- 특정 모델만: `--model large-v3-turbo` (기본) / `--model large-v3`\n" "\n" + "### API 키 인증 실패 (401)\n\n" + "`api_keys.json`에는 **다이제스트만** 저장되고 raw 키는 생성 시 1회만 출력됩니다\n" + "(보안 설계). 셀 9에서 `RAW_KEY`를 캡처했는지 확인하세요 — 파일에서 키를 읽으면 401이 납니다.\n\n" "### OOM (16GB VRAM)\n\n" "- EngineOwner가 자동으로 정밀도를 강등합니다 (float16 → int8_float16 → int8 → CPU)\n" "- 수동 지정: `--compute-type int8_float16`\n"