{
 "nbformat": 4,
 "nbformat_minor": 0,
 "metadata": {
  "colab": {
   "provenance": [],
   "gpuType": "A100"
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3"
  },
  "accelerator": "GPU"
 },
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# AI 新視界 Colab 實測 2：用送的運算單元跑 LTX-2.5（22B，原生有聲音，本站硬體跑不動）\n",
    "做的事：裝 Lightricks 官方的 `ltx-pipelines` → 下載 LTX-2.5 distilled 權重（transformer 約 44 GB＋Gemma 4 12B 文字編碼器約 24 GB＋VAE＋空間放大器）→ 用一段提示生成 5 秒 1920×1088 **有聲** B-roll → 記錄時間、GPU 尖峰記憶體、運算單元。\n",
    "\n",
    "用法同實測 1：選 GPU（這本要 A100；80 GB 最好，40 GB 會自動開 CPU 卸載）→ 全部執行 → 填 UNITS_PER_HOUR → 貼回最後一格的結果。\n",
    "若下載權重出現 401／403，到 Hugging Face 的 Lightricks/LTX-2.5 頁面按同意授權，再把 token 放進 Colab 左側「密鑰」叫 HF_TOKEN。"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    "#@title 1. 看看拿到什麼機器\n",
    "import subprocess, time, json, os, shutil, re\n",
    "T0 = time.time()\n",
    "UNITS_PER_HOUR = 0  #@param {type:\"number\"}\n",
    "#@markdown ↑ 右上角「資源」面板的「每小時使用 N 個運算單元」\n",
    "gpu = subprocess.run([\"nvidia-smi\",\"--query-gpu=name,memory.total,driver_version\",\"--format=csv,noheader\"],capture_output=True,text=True).stdout.strip()\n",
    "vram_gb = int(re.search(r\"(\\d+) MiB\", gpu).group(1)) // 1024\n",
    "ram = int(next(l for l in open(\"/proc/meminfo\") if l.startswith(\"MemTotal\")).split()[1])//2**20\n",
    "disk = shutil.disk_usage(\"/content\").free//2**30\n",
    "print(\"GPU:\", gpu); print(f\"VRAM {vram_gb} GB, RAM {ram} GB, 可用磁碟 {disk} GB\")\n",
    "OFFLOAD = \"none\" if vram_gb >= 70 else \"cpu\"      # 40 GB 的 A100 把 transformer 權重放到主記憶體串流\n",
    "print(\"offload:\", OFFLOAD)\n",
    "RESULT = {\"gpu\": gpu, \"vram_gb\": vram_gb, \"ram_gb\": ram, \"disk_free_gb\": disk, \"units_per_hour\": UNITS_PER_HOUR, \"offload\": OFFLOAD, \"t\": {}}\n",
    "def mark(k): RESULT[\"t\"][k] = round(time.time()-T0); print(f\"[{RESULT['t'][k]:>5}s] {k}\")\n",
    "mark(\"probe\")"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    "#@title 2. 掛 Google Drive（結果存到 MyDrive/aiwv-colab/）\n",
    "from google.colab import drive\n",
    "drive.mount(\"/content/drive\")\n",
    "OUT = \"/content/drive/MyDrive/aiwv-colab\"; os.makedirs(OUT, exist_ok=True)\n",
    "mark(\"drive\")"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    "#@title 3. 安裝 Lightricks 官方 LTX-2 套件（uv sync 會建自己的 venv，含 torch）\n",
    "%cd /content\n",
    "if not os.path.exists(\"/content/LTX-2\"):\n",
    "    !git clone -q --depth 1 https://github.com/Lightricks/LTX-2.git\n",
    "%cd /content/LTX-2\n",
    "!pip install -q uv 2>&1 | tail -1\n",
    "!uv sync --frozen 2>&1 | tail -3\n",
    "!uv run python -c \"import torch, ltx_pipelines; print('torch', torch.__version__, 'cuda', torch.cuda.is_available())\"\n",
    "mark(\"install_ltx\")"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    "#@title 4. 下載 LTX-2.5 權重（Lightricks/LTX-2.5，約 70 GB）\n",
    "os.environ[\"HF_HUB_ENABLE_HF_TRANSFER\"] = \"1\"\n",
    "!pip install -q hf_transfer huggingface_hub 2>&1 | tail -1\n",
    "try:\n",
    "    from google.colab import userdata; os.environ[\"HF_TOKEN\"] = userdata.get(\"HF_TOKEN\")\n",
    "except Exception: pass\n",
    "from huggingface_hub import hf_hub_download\n",
    "M = \"/content/models/ltx-2.5\"\n",
    "FILES = [\"diffusion_models/ltx-2.5-22b-distilled-transformer-bf16.safetensors\",\n",
    "         \"text_encoders/gemma4-12b-with-proj-ltx-2.5-bf16.safetensors\",\n",
    "         \"vae/ltx-2.5-video-vae-bf16.safetensors\",\n",
    "         \"vae/ltx-2.5-audio-vae-bf16.safetensors\",\n",
    "         \"latent_upscale_models/ltx-2.5-latent-spatial-upscaler-x2-bf16-1.0.safetensors\"]\n",
    "t = time.time(); total = 0\n",
    "for f in FILES:\n",
    "    p = hf_hub_download(\"Lightricks/LTX-2.5\", f, local_dir=M); total += os.path.getsize(p); print(f\"  {f}  {os.path.getsize(p)/2**30:.1f} GB\")\n",
    "dl = time.time()-t\n",
    "RESULT[\"download_gb\"] = round(total/2**30, 1); RESULT[\"download_mbps\"] = round(total/2**20/dl)\n",
    "print(f\"下載 {RESULT['download_gb']} GB 用 {dl:.0f} 秒 ≈ {RESULT['download_mbps']} MB/s\"); mark(\"download\")"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    "#@title 5. 提示詞與參數（和本站短片的 B-roll 需求一致：乾淨、無字；這個模型會自己配環境音）\n",
    "PROMPT = (\"A modern Taiwanese newsroom at night, wide shot of a desk with a monitor showing colorful line charts, soft blue studio lighting, \"\n",
    "          \"slow dolly-in, cinematic, realistic, quiet room tone with a faint keyboard typing sound, no people, no text, no subtitles\")\n",
    "W, H, FRAMES, SEED = 1920, 1088, 121, 1     # 5 秒 24fps；寬高要能被 64 整除\n",
    "RESULT.update({\"size\": f\"{W}x{H}\", \"frames\": FRAMES, \"seed\": SEED})"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    "#@title 6. 生成（官方 distilled 兩段式管線：8＋4 步，不用 guidance）\n",
    "import threading\n",
    "PEAK = {\"mb\": 0, \"stop\": False}\n",
    "def _watch():\n",
    "    while not PEAK[\"stop\"]:\n",
    "        try:\n",
    "            mb = int(subprocess.run([\"nvidia-smi\",\"--query-gpu=memory.used\",\"--format=csv,noheader,nounits\"],capture_output=True,text=True).stdout.split()[0]); PEAK[\"mb\"] = max(PEAK[\"mb\"], mb)\n",
    "        except Exception: pass\n",
    "        time.sleep(3)\n",
    "threading.Thread(target=_watch, daemon=True).start()\n",
    "os.environ[\"PYTORCH_CUDA_ALLOC_CONF\"] = \"expandable_segments:True\"\n",
    "cmd = [\"uv\", \"run\", \"python\", \"-m\", \"ltx_pipelines.distilled\",\n",
    "       \"--transformer-path\", f\"{M}/diffusion_models/ltx-2.5-22b-distilled-transformer-bf16.safetensors\",\n",
    "       \"--text-encoder-path\", f\"{M}/text_encoders/gemma4-12b-with-proj-ltx-2.5-bf16.safetensors\",\n",
    "       \"--video-vae-path\", f\"{M}/vae/ltx-2.5-video-vae-bf16.safetensors\",\n",
    "       \"--audio-vae-path\", f\"{M}/vae/ltx-2.5-audio-vae-bf16.safetensors\",\n",
    "       \"--spatial-upsampler-path\", f\"{M}/latent_upscale_models/ltx-2.5-latent-spatial-upscaler-x2-bf16-1.0.safetensors\",\n",
    "       \"--width\", str(W), \"--height\", str(H), \"--num-frames\", str(FRAMES), \"--seed\", str(SEED),\n",
    "       \"--offload\", OFFLOAD, \"--output-path\", \"/content/ltx25-broll.mp4\", \"--prompt\", PROMPT]\n",
    "print(\" \".join(cmd)[:600])\n",
    "t = time.time()\n",
    "p = subprocess.run(cmd, cwd=\"/content/LTX-2\", capture_output=True, text=True)\n",
    "secs = round(time.time()-t)\n",
    "print(p.stdout[-3000:]); print(p.stderr[-3000:])\n",
    "ok = p.returncode == 0 and os.path.exists(\"/content/ltx25-broll.mp4\")\n",
    "RESULT[\"t\"][\"generate_s\"] = secs; RESULT[\"ok\"] = ok; RESULT[\"peak_vram_mb\"] = PEAK[\"mb\"]\n",
    "print(\"成功\" if ok else \"失敗\", f\"{secs} 秒，尖峰 VRAM {PEAK['mb']} MB\")\n",
    "if ok:\n",
    "    shutil.copy(\"/content/ltx25-broll.mp4\", f\"{OUT}/ltx25-{gpu.split(',')[0].replace(' ','_')}.mp4\")\n",
    "    from IPython.display import Video, display; display(Video(\"/content/ltx25-broll.mp4\", embed=True, width=640))\n",
    "mark(\"generate_done\")"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    "#@title 7. 結果整理（把這一格印出來的整段貼回給站長的 AI 助理）\n",
    "PEAK[\"stop\"] = True\n",
    "RESULT[\"total_s\"] = round(time.time()-T0)\n",
    "gen = RESULT[\"t\"].get(\"generate_s\")\n",
    "if UNITS_PER_HOUR and gen:\n",
    "    RESULT[\"units_per_clip\"] = round(UNITS_PER_HOUR * gen / 3600, 2)\n",
    "    RESULT[\"units_per_session\"] = round(UNITS_PER_HOUR * RESULT[\"total_s\"] / 3600, 2)\n",
    "    RESULT[\"twd_per_clip\"] = round(RESULT[\"units_per_clip\"] * 0.1049 * 32, 1)     # Pay-as-you-go 100 單元 US$10.49；1 美元約 32 元\n",
    "json.dump(RESULT, open(f\"{OUT}/ltx25-result.json\",\"w\"), ensure_ascii=False, indent=1)\n",
    "print(\"=== ltx25 RESULT ===\"); print(json.dumps(RESULT, ensure_ascii=False, indent=1)); print(\"=== END ===\")"
   ],
   "execution_count": null,
   "outputs": []
  }
 ]
}