| # Auto-Mode ๋ค์ค GPU ๋ณ๋ ฌ ์ฒ๋ฆฌ ๊ตฌํ ๊ณํ |
|
|
| ์์ฑ์ผ: 2026-04-25 |
| ๋์: HuggingFace Spaces ์ dedicated multi-GPU ํ๋์จ์ด (์: 4ร A100-80GB) ์์ |
| Auto-Mode ํ์ ์
๋ก๋ํ N ๊ฐ์ ์์์ **๊ฐ GPU 1๋๋น 1์์**์ฉ ๋์์ ๋ถํ (segment) ํ๋๋ก |
| ๊ตฌ์กฐ ๋ณ๊ฒฝ. |
|
|
| --- |
|
|
| ## 0. ๋ฐฐ๊ฒฝ / ํ์ฌ ๊ตฌ์กฐ ํ๋์ ๋ณด๊ธฐ |
|
|
| | ํญ๋ชฉ | ํ์ฌ (sequential) | |
| |------|-------------------| |
| | Auto-Mode ์ง์
์ | `app.py:5033 _auto_mode_process(file_list, text_prompt)` | |
| | ๋จ์ผ ์์ ๋ถํ | `app.py:4896 @spaces.GPU(duration=119) def segment_video(...)` | |
| | ์์ปค ํ๋ก์ธ์ค | `mp.get_context("spawn").Process(target=_segment_video_worker_entry, ...)` (`app.py:4854`) | |
| | ์ฝ์ด ๋ก์ง | `app.py:4239 _segment_video_core(...)` (chunk-wise SAM3 ์ถ๋ก ) | |
| | ๋ชจ๋ธ ์ธ์คํด์คํ | `app.py:4453 predictor_cls = _get_sam3_predictor_cls(); predictor = predictor_cls(...)` | |
| | ๊ฒฐ๊ณผ ์ ์ฅ ๋๋ ํ ๋ฆฌ | `build/downloads/` (`_persist_for_download` ํธ์ถ) | |
| | ์งํ ํต์ | `mp.Queue` ๋ก `progress / status / result / error` ๋ฉ์์ง ์คํธ๋ฆฌ๋ฐ | |
| | ์์ ๊ฐ ์ฒ๋ฆฌ | `for path in paths:` ์ง๋ ฌ ๋ฃจํ, GPU cleanup (`_cleanup_cuda_cache()`) ํ ๋ค์ ์์ ์ฒ๋ฆฌ | |
|
|
| ### HuggingFace Spaces ํ๋์จ์ด / `spaces.GPU` ๋์ |
|
|
| - `@spaces.GPU` ๋ฐ์ฝ๋ ์ดํฐ๋ `Config.zero_gpu` (= `SPACES_ZERO_GPU=true`) ์ธ ๊ฒฝ์ฐ์๋ง ZeroGPU ์ฌ๋ผ์ด์ค ํ ๋น ๋ก์ง์ด ๋ถ๋๋ค (`spaces/zero/decorator.py:83`). dedicated GPU Space (4รA100 ๋ฑ) ์์๋ ๋ฐ์ฝ๋ ์ดํฐ๊ฐ **no-op** ์ด๋ฉฐ, ์ผ๋ฐ Python ํ๋ก์ธ์ค๊ฐ CUDA 4 ์ฅ ๋ชจ๋๋ฅผ ์ง์ ๋ณธ๋ค (`torch.cuda.device_count() == 4`). |
| - ZeroGPU(MIG slice) ๋ชจ๋๋ ํ ๋ฒ์ ํ GPU ์ฌ๋ผ์ด์ค๋ง ํ ๋น๋๋ฏ๋ก **์ด ๊ณํ์ dedicated multi-GPU ํ๋์จ์ด ์ ์ **์ด๋ค. ZeroGPU ํ๊ฒฝ์์ ์๋์ผ๋ก ๊ธฐ์กด ์ง๋ ฌ ๊ฒฝ๋ก๋ก fallback ํ๋ค. |
|
|
| ### ๊ฒฉ๋ฆฌ(isolation) ์๊ตฌ์ฌํญ ์ ๋ฆฌ |
|
|
| | ์์ | ์ถฉ๋ ๊ฐ๋ฅ์ฑ | ํด๊ฒฐ ๋ฐฉ๋ฒ | |
| |------|-------------|-----------| |
| | GPU ๋ฉ๋ชจ๋ฆฌ / ์ปจํ
์คํธ | ๊ฐ์ device ์์์ 4 ์์์ด ๋ชจ๋ธ์ ๋์ ์ ์ฌ โ OOM, ์ปจํ
์คํธ ๊ฐ์ญ | ์์ปค๋ง๋ค `CUDA_VISIBLE_DEVICES=N` ํ๊ฒฝ๋ณ์๋ก 1์ฅ๋ง ๋ณด์ด๊ฒ ๊ณ ์ | |
| | `sam3.*` ๋ชจ๋ in-process ์บ์ (`_SAM3_PREDICTOR_CLS`, `_LAST_SEG_CACHE`, `cached_frame_outputs` ๋ฑ) | ๊ฐ์ ์ธํฐํ๋ฆฌํฐ ๋ด 4-way concurrent ํธ์ถ์ ์ํ๊ฐ ์ฝํ | spawn ๋ฐฉ์์ ๋ณ๋ ํ๋ก์ธ์ค โ ๋ชจ๋ ์ํ ์์ฒด๊ฐ ๋ถ๋ฆฌ๋จ | |
| | `tempfile.mkdtemp()` (chunk ์
๋ ฅ dir, ํธ๋ฆฌ๋ฐ๋ mp4) | `mkdtemp` ๋ ์๋์ผ๋ก ์ถฉ๋ ์๋ ์ด๋ฆ ์์ฑ โ ์์ | ์ถ๊ฐ ์กฐ์น ๋ถํ์ | |
| | `build/downloads/` ์ฐ์ถ๋ฌผ ํ์ผ๋ช
| ๋์ ์์ ์์์ด ๋์ผ timestamp โ `auto_mode_results_YYYYMMDD_HHMMSS.zip` / `*_overlay.mp4` ์ถฉ๋ | ํ์ผ๋ช
์ short uuid (`uuid.uuid4().hex[:8]`) + ์์ ์ธ๋ฑ์ค ์ถ๊ฐ | |
| | ๋ชจ๋ธ ์ฒดํฌํฌ์ธํธ ๋ค์ด๋ก๋ / BPE vocab | 4 ์์ปค๊ฐ ๋์์ ๊ฐ์ ํ์ผ์ download/write โ race | ๋ถ๋ชจ(๋ฉ์ธ process)์์ ์ฌ์ 1ํ ๋ณด์ฅ ํ ์์ปค๋ read-only | |
| | `.zerogpu/tensors` ๋ฑ ์บ์ | dedicated ๋ชจ๋์์ ZeroGPU ์บ์๋ ์ฌ์ฉ ์ ํจ | ์ํฅ ์์ | |
| | `sam3/` ๋๋ ํ ๋ฆฌ ์์ฒด | Python import ๋ ํ๋ก์ธ์ค๋ง๋ค ๋
๋ฆฝ โ **๋๋ ํ ๋ฆฌ ์ฌ๋ณธ ๋ถํ์** | ์ฌ๋ณธ ์์ฑ X | |
|
|
| ### ๊ฒฐ๋ก |
|
|
| - **`sam3` ํด๋ ๋ณต์ ๋ ํ์ ์๋ค.** ๊ฒฉ๋ฆฌ ๋จ์๋ โํ๋ก์ธ์คโ ํ ๋จ๊ณ๋ก ์ถฉ๋ถํ๋ค. |
| - **๊ฐ ์์์ด 1 ๊ฐ์ spawn child process** ์์ ์คํ๋๋ฉฐ, child ์ง์
์งํ (torch import ์ ) `CUDA_VISIBLE_DEVICES` ๋ฅผ 1 ์ฅ์ผ๋ก ์ขํ๋ค โ child ์
์ฅ์์ ํญ์ `cuda:0` ํ ๊ฐ๋ง ์กด์ฌ โ ๋ชจ๋ธ/SAM3 ์ฝ๋์ `cuda` / `cuda:0` ํ๋์ฝ๋ฉ ์ด๋๋ ์์ . |
| - **๋ถ๋ชจ ํ๋ก์ธ์ค๋ GPU ์ฌ์ฉ X**. ๋จ์ํ 4-์ฌ๋กฏ ํ์ ์ด์ํ๋ ๋์คํจ์ฒ ์ญํ ๋ง ์ํ. ๋ชจ๋ ๋ฌด๊ฑฐ์ด import ๋ ์์ปค ์์์. |
|
|
| --- |
|
|
| ## 1. ๋์์ธ ๊ฐ์ |
|
|
| ### 1.1 ์์ปค ํ ๊ตฌ์กฐ |
|
|
| ``` |
| โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ |
| โ Gradio main process (no torch CUDA usage) โ |
| โ โโ _auto_mode_process() generator โ |
| โ โโ ParallelSegmentDispatcher โ |
| โ โ โโ pool of N workers (N = min(num_gpus, num_videos)) โ |
| โ โ โโ submit queue (video_path โ free worker) โ |
| โ โ โโ event queue (progress / status / result / err) โ |
| โ โ โโ per-video state: gpu_idx, started_at, last_pctโฆ โ |
| โ โโ yields UI updates (status / per-video progress / files)โ |
| โโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ |
| โ spawn child ร N |
| โผ |
| โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ |
| โ Worker GPU 0 โ โ Worker GPU 1 โ โ Worker GPU N-1 โ |
| โ CUDA_VISIBLE=0 โ โ CUDA_VISIBLE=1 โ โ CUDA_VISIBLE=N-1โ |
| โ runs โ โ runs โ โ runs โ |
| โ _segment_video โ โ _segment_video โ โ _segment_video โ |
| โ _core(...) โ โ _core(...) โ โ _core(...) โ |
| โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ |
| ``` |
|
|
| - ๊ฐ ์์ปค๋ **์์ ์ด์์๋** โpersistent workerโ๋ก ์ด์ํด ๋ชจ๋ธ ๊ฐ์ค์น ๋ก๋ฉ ๋น์ฉ์ ์ฒซ ์์ 1ํ๋ง ๋ถ๋ดํ๋ค (์ ํ ์ต์ ํ: ยง6.2). 1์ฐจ ๊ตฌํ์ ๋จ์ํจ์ ์ํด ์์๋ง๋ค ์์ปค ์๋ก spawn ํ๋ โfresh-per-videoโ ๊ตฌ์กฐ๋ก ์์ โ ์์ ํ ํ ์ฌ์ฌ์ฉํ์ผ๋ก ์ ํ. |
| - N ๊ฐ์ ์์์ด 4 GPU ๋ณด๋ค ๋ง์ผ๋ฉด, ํ + ํ ๊ตฌ์กฐ๋ผ ์๋์ผ๋ก ์ง๋ ฌํ๋๋ค (ํ GPU ๊ฐ ํ๋ ๋๋ด๋ฉด ๋ค์ ์์์ ๋ฐ์). |
|
|
| ### 1.2 ์์ปค entry ๋ชจ๋ ๋ถ๋ฆฌ โ `parallel_segment_worker.py` |
|
|
| **์ ๋ณ๋ ํ์ผ์ด ํ์ํ๊ฐ:** |
| - ํ์ฌ worker target (`_segment_video_worker_entry`) ์ `app.py` ๋ด๋ถ ํจ์๋ค. |
| - spawn ์์ ํ๋ก์ธ์ค๊ฐ ์ด target ์ unpickle ํ๋ ค๋ฉด `app.py` ๋ฅผ import ํด์ผ ํ๊ณ , `app.py:4` ์์ `import torch` ๊ฐ ์ฆ์ ์คํ๋๋ค. |
| - ๊ทธ ์์ ์ ์์์ด ์์ง `os.environ["CUDA_VISIBLE_DEVICES"]` ๋ฅผ ์ขํ๊ธฐ ์ ์ด๋ฏ๋ก, torch ๊ฐ 4 ์ฅ ๋ชจ๋ ๋ณด์ด๋ ์ํ๋ก cuda runtime ์ ์ด๊ธฐํํ๋ค โ ์ฐ๋ฆฌ๊ฐ `cuda:0` ๋ง ์ฐ๋ ค ํด๋ ๋ค๋ฅธ ์ฅ์น ์ปจํ
์คํธ๊ฐ ๋ฐ๋ผ์จ๋ค. |
| - ํด๊ฒฐ: ์์ปค entry ๋ฅผ **torch ๋ฅผ top-level ์์ import ํ์ง ์๋** ์ ํ์ผ๋ก ๋ถ๋ฆฌ. ์์์ด ๊ทธ ํ์ผ๋ง import ํ ๋ค, ํจ์ ๋ณธ๋ฌธ ์ฒซ ์ค์์ `os.environ["CUDA_VISIBLE_DEVICES"]` ์ค์ ํ๊ณ , ๊ทธ *๋ค์* torch / app ์ import. |
|
|
| ```python |
| # parallel_segment_worker.py (intentionally minimal top-level imports) |
| import os |
| import sys |
| import traceback |
| |
| def worker_main(gpu_index, args, progress_queue): |
| os.environ["CUDA_VISIBLE_DEVICES"] = str(gpu_index) |
| os.environ["SAM3_WORKER_MODE"] = "1" # skip Gradio launch in app.py |
| os.environ.setdefault("SAM3_CACHE_FRAME_OUTPUTS", "0") |
| os.environ.setdefault("SAM3_OFFLOAD_TRACKER_STATE_TO_CPU", "1") |
| |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) |
| |
| # NOW it is safe to import torch / app |
| import torch |
| if torch.cuda.is_available(): |
| torch.cuda.set_device(0) # only one device visible: cuda:0 |
| |
| from app import _segment_video_core # imports torch but env is already set |
| |
| (video_path, text_prompt, duration_limit, id_corrections_text, |
| id_drop_text, id_override_start_sec, show_trails, view_mode) = args |
| |
| def _progress_cb(val, desc): |
| progress_queue.put({"type": "progress", "value": val, "desc": desc, |
| "gpu_index": gpu_index}) |
| |
| def _status_cb(msg): |
| progress_queue.put({"type": "status", "message": msg, |
| "gpu_index": gpu_index}) |
| |
| try: |
| progress_queue.put({"type": "progress", "value": 0.0, |
| "desc": f"GPU {gpu_index}: starting...", |
| "gpu_index": gpu_index}) |
| out_path, status, loc_path = _segment_video_core( |
| video_path, text_prompt, duration_limit, |
| id_corrections_text=id_corrections_text, |
| id_drop_text=id_drop_text, |
| id_override_start_sec=id_override_start_sec, |
| show_trails=show_trails, |
| view_mode=view_mode, |
| progress_callback=_progress_cb, |
| status_callback=_status_cb, |
| ) |
| progress_queue.put({"type": "result", |
| "data": (out_path, status, loc_path), |
| "gpu_index": gpu_index}) |
| except Exception as exc: # noqa: BLE001 |
| progress_queue.put({"type": "error", |
| "message": str(exc), |
| "traceback": traceback.format_exc(), |
| "gpu_index": gpu_index}) |
| finally: |
| try: |
| import torch |
| if torch.cuda.is_available(): |
| torch.cuda.empty_cache() |
| torch.cuda.ipc_collect() |
| except Exception: |
| pass |
| ``` |
|
|
| ### 1.3 ๋์คํจ์ฒ ํด๋์ค โ `app.py` ๋ด๋ถ ์ถ๊ฐ |
|
|
| ```python |
| class ParallelSegmentDispatcher: |
| """Distribute one video per GPU concurrently and stream events back.""" |
| |
| def __init__(self, num_gpus: int): |
| self.num_gpus = num_gpus |
| self.ctx = mp.get_context("spawn") |
| self.event_queue = self.ctx.Queue() |
| self.workers: dict[int, mp.Process] = {} # gpu_index -> Process |
| self.gpu_assignments: dict[int, dict] = {} # gpu_index -> task meta |
| |
| def submit(self, gpu_index, video_meta, args): |
| from parallel_segment_worker import worker_main |
| p = self.ctx.Process( |
| target=worker_main, |
| args=(gpu_index, args, self.event_queue), |
| daemon=False, |
| ) |
| p.start() |
| self.workers[gpu_index] = p |
| self.gpu_assignments[gpu_index] = video_meta |
| |
| def free_gpu(self, gpu_index): |
| proc = self.workers.pop(gpu_index, None) |
| meta = self.gpu_assignments.pop(gpu_index, None) |
| if proc is not None: |
| proc.join(timeout=5) |
| if proc.is_alive(): |
| proc.terminate() |
| proc.join(timeout=5) |
| return meta |
| |
| def shutdown(self): |
| for gi in list(self.workers.keys()): |
| self.free_gpu(gi) |
| ``` |
|
|
| ### 1.4 `_auto_mode_process` ์ ๋ณ๋ ฌ ๋ณํ โ `_auto_mode_process_parallel` |
| |
| ๋๋ต์ ์๊ณ ๋ฆฌ์ฆ: |
| |
| ```text |
| ๊ฐ์ฉ GPU ์ G = torch.cuda.device_count() |
| ์์ ์ N = len(paths) |
| slot_count = min(G, N) |
| |
| dispatcher = ParallelSegmentDispatcher(slot_count) |
|
|
| # 1) ์ด๊ธฐ N ๊ฐ ์ค ์ฒซ slot_count ๊ฐ๋ฅผ ๊ฐ GPU ์ ๋ฐฐ์ |
| free_gpus = list(range(slot_count)) |
| queue_index = 0 |
| in_flight = 0 |
| while queue_index < N and free_gpus: |
| gi = free_gpus.pop(0) |
| dispatcher.submit(gi, meta_for(queue_index), args_for(queue_index)) |
| queue_index += 1 |
| in_flight += 1 |
| yield UI status |
| |
| # 2) ์ด๋ฒคํธ ๋ฃจํ |
| while in_flight > 0: |
| msg = dispatcher.event_queue.get(timeout=...) |
| gi = msg["gpu_index"] |
| if msg["type"] == "progress": |
| update per-GPU progress bar text; aggregate overall progress |
| yield UI status |
| elif msg["type"] == "status": |
| append status for that GPU |
| yield UI status |
| elif msg["type"] == "result": |
| out_path, status, loc_path = msg["data"] |
| finalize: rename/persist with disambiguating suffix |
| append (mp4, csv) to all_results |
| yield UI status (with newly visible result) |
| dispatcher.free_gpu(gi) |
| in_flight -= 1 |
| if queue_index < N: |
| dispatcher.submit(gi, meta_for(queue_index), args_for(queue_index)) |
| queue_index += 1 |
| in_flight += 1 |
| yield UI status |
| elif msg["type"] == "error": |
| record failure for that video |
| yield UI status |
| dispatcher.free_gpu(gi) |
| in_flight -= 1 |
| # same re-fill logic as result |
| |
| # 3) ์ข
๋ฃ ์ ๋ฆฌ |
| dispatcher.shutdown() |
| yield final summary |
| ``` |
| |
| --- |
| |
| ## 2. UI ๋ณ๊ฒฝ |
| |
| ### 2.1 ์ถ๊ฐ ์ปดํฌ๋ํธ โ `Auto-Mode (Batch Queue)` accordion ์ |
| |
| | ์ปดํฌ๋ํธ | ์ฉ๋ | |
| |---------|------| |
| | `auto_mode_parallel_status` (Markdown) | GPU ์ / ํ์ฑ ์์ปค ์ / ํ์ ๋จ์ ์์ ์ / ๋ถ๋ฅ๋ณ ์งํ๋ฅ (์: `GPU0: video_a.mp4 73%`, `GPU1: video_b.mp4 41%` โฆ) | |
| | ์์๋ณ ๊ฒฐ๊ณผ ๋์ ์ ๊ธฐ์กด `auto_results_files_state` / `auto_results_list` ์ฌ์ฌ์ฉ | ๋ณ๊ฒฝ ์์ | |
| |
| ### 2.2 ๋จ์ผ ์์ ๋ฏธ๋ฆฌ๋ณด๊ธฐ / overlay ์ปดํฌ๋ํธ |
| |
| ๋ณ๋ ฌ ๋ชจ๋์์ โํ์ฌ ์ฒ๋ฆฌ์คโ ๋จ์ผ ์์์ด ์์ผ๋ฏ๋ก: |
| - `video_input` / `video_output` ๋ฑ ๋จ์ผ ์ฌ๋กฏ ์์ ฏ์ **๋ง์ง๋ง์ผ๋ก ์๋ฃ๋** ์์ ๊ฒฐ๊ณผ๋ก ๊ฐฑ์ (UX ์น์ ). |
| - ์ฃผ๋ ์งํ ํ์๋ multiline `auto_mode_parallel_status` ๊ฐ ๋ด๋น. |
| |
| ### 2.3 fallback |
| |
| - `torch.cuda.device_count() <= 1` โ `_auto_mode_process` (ํ์ฌ ์ง๋ ฌ ๋์) ๊ทธ๋๋ก ์ ์ง. |
| - `>1` โ `_auto_mode_process_parallel` ๋ถ๊ธฐ. |
| - ํ ๊ธ: ํ๊ฒฝ๋ณ์ `SAM3_PARALLEL_AUTO_MODE` (๊ธฐ๋ณธ `auto`, `0` ์ผ๋ก ๋นํ์ฑ, `1` ๋ก ๊ฐ์ ) ๋ก ์ต์
ํ. |
| |
| --- |
| |
| ## 3. ํ์ผ๋ช
/ ์ถ๋ ฅ ์ถฉ๋ ๋ฐฉ์ง |
| |
| `build/downloads/` ๋๋ ํ ๋ฆฌ ์์ 4 ๊ฐ ์์์ด ๊ฑฐ์ ๋์์ ๊ฒฐ๊ณผ๋ฅผ ๋จ์ด๋จ๋ฆด ๋: |
| |
| | ํจ์ | ๋ณ๊ฒฝ | |
| |------|------| |
| | `_rename_with_rule` | ๊ฒฐ๊ณผ ํ์ผ๋ช
์ ์งง์ ์์-์ธ์คํด์ค ID ๋ฅผ ๋ผ์๋ฃ๋๋ก ๋ณด๊ฐ. ์: `{stem}_{video_id8}_seg_{dur}_{elapsed}s.mp4` | |
| | `_persist_for_download` | ์ค๋ณต basename ์ธ ๊ฒฝ์ฐ `_{n}` ์ ๋ฏธ์ฌ ๋ถ์ฌ (์ด๋ฏธ ์ด๋ ์ ๋ ์ฒ๋ฆฌ๋์ง๋ง race-safe ํ๊ฒ `os.rename` ํ ์ฌํ์ธ) | |
| | `_build_zip_from_paths` | ์ด๋ฏธ basename ์ค๋ณต disambiguation ๋ก์ง ์์ (`seen_names`) โ ๊ทธ๋๋ก ์ฌ์ฉ | |
| |
| `video_id8` ๋ ๋์คํจ์ฒ๊ฐ ์์ ํ์ ๋ฃ์ ๋ `uuid.uuid4().hex[:8]` ๋ก ํ ๋ฒ ์์ฑํ์ฌ `meta` ์ ์ ์ฅ. |
| |
| --- |
| |
| ## 4. ์์ ์ฅ์น / ์ฃ์ง ์ผ์ด์ค |
| |
| 1. **GPU ๋ฉ๋ชจ๋ฆฌ ์ฌ์ ์ฒดํฌ**: ์์ปค ์ง์
์งํ `_check_gpu_memory_safe()` ๊ฐ true ์ธ์ง ํ์ธ (๋ชจ๋ธ ์ต์ด ์ ์ฌ ์ ). false ๋ฉด `error` ๋ฉ์์ง๋ก ๋์คํจ์ฒ์ ๋ณด๊ณ ํ๊ณ ์ข
๋ฃ. |
| 2. **์์ปค ๋น์ ์ ์ข
๋ฃ**: ๋์คํจ์ฒ๋ `event_queue.get(timeout=heartbeat)` ์ผ๋ก ํด๋งํ๋ฉฐ, heartbeat ์๊ฐ ๋ด ๋ฉ์์ง๊ฐ ์๊ณ ํด๋น ์์ปค๊ฐ `is_alive() == False` ๋ฉด `error` ์ฒ๋ฆฌ + `free_gpu`. |
| 3. **๋ถ๋ชจ ํ๋ก์ธ์ค์ daemon ์ฒดํฌ**: ๊ธฐ์กด `segment_video` ๊ฐ `mp.current_process().daemon` ๋ฉด in-process ๋ก ํด๋ฐฑํ๋ ๋ถ๊ธฐ (`app.py:4918`) ์ ๋์ผํ ์ ์ ์ผ๋ก, ๋์คํจ์ฒ๋ daemon ๋ถ๋ชจ์์ ๋นํ์ฑํ โ ์์ฐจ ํด๋ฐฑ. |
| 4. **์ทจ์(์คํฑ ๋ฒํผ)**: 1์ฐจ ๊ตฌํ์ ๋ฏธํฌํจ (ํ์ฌ ์ง๋ ฌ ๋ชจ๋์๋ stop ์์). ํ์ ์์
. |
| 5. **๋ก๊ทธ prefix**: ์์ปค๊ฐ ๋ณด๋ด๋ progress/status ๋ฉ์์ง ์์ `[GPU{n}]` ์ ๋๋ฅผ ๋ถ์ฌ์ UI ์ stdout ๊ตฌ๋ถ. |
| 6. **๊ฒฐ์ ์ ๋๋ฐ์ด์ค ๋ถ๋ฐฐ**: ์์ i ๊ฐ ๋ชจ๋ ๊ฐ์ GPU ๋ก ๊ฐ์ง ์๋๋ก ๋์คํจ์ฒ๊ฐ round-robin (์ฌ์ค์ โ๋จผ์ ๋๋ GPU ์ ๋ค์ ์์โ). |
| |
| --- |
| |
| ## 5. ํ
์คํธ / ๊ฒ์ฆ |
| |
| ### 5.1 ๋ก์ปฌ (๋จ์ผ GPU) |
| - `_parallel_dispatcher` ๊ฐ `device_count == 1` ์ผ ๋ ์๋์ผ๋ก ์ง๋ ฌ ๊ฒฝ๋ก๋ก ํด๋ฐฑ๋๋์ง ํ์ธ. |
| - ํ๊ฒฝ๋ณ์ `SAM3_PARALLEL_AUTO_MODE=1` + `CUDA_VISIBLE_DEVICES=0` โ ๋์คํจ์ฒ๊ฐ 1-์ฌ๋กฏ ๋ชจ๋๋ก ๋์ (์์ปค 1๊ฐ) โ ๊ฒฐ๊ณผ๊ฐ ๊ธฐ์กด `_auto_mode_process` ์ ๋์ผํด์ผ ํจ. |
| |
| ### 5.2 ๋ก์ปฌ (๊ฐ์ง ๋ฉํฐ GPU ์๋ฎฌ๋ ์ด์
) |
| - `SAM3_PARALLEL_AUTO_MODE=1` + `SAM3_FAKE_GPU_COUNT=4` ๋ก ๋์คํจ์ฒ ์ฝ๋๊ฐ 4-์ฌ๋กฏ ํ์ ๋ง๋ค์ง๋ง ์ค์ ๋ก ๋ชจ๋ ๋์ผํ device 0 ์ ๊ณต์ (ํ
์คํธ์ฉ; ๋จ์ dispatcher ๋ก์ง ๊ฒ์ฆ). |
| |
| ### 5.3 HF Space (4รA100) |
| - 4 ๊ฐ ์์ ์
๋ก๋ โ ๊ฐ ์์ ์ฒ๋ฆฌ ์๊ฐ์ด single-GPU ๋๋น 3.5~4ร ๋นจ๋ผ์ง๋์ง ํ์ธ. |
| - `nvidia-smi` ๋ก 4 ์ฅ ๋ชจ๋ utilization ์ฌ๋ผ๊ฐ๋์ง ํ์ธ (๋๋ฒ๊ทธ ๋ก๊ทธ์ `GPU memory util:` ์ถ๋ ฅ). |
| |
| ### 5.4 ํ๊ท |
| - ๋จ์ผ ์์ โRun Segmentationโ ๋ฒํผ์ ๋ณ๊ฒฝ ์์ โ ํ๊ท ์ํ ๋ฎ์. |
| - ๊ฒฐ๊ณผ mp4 / csv ์ ๋ถํ ์ ํ๋๋ ๋จ์ผ/๋ณ๋ ฌ ๋ชจ๋์์ bit-identical (๊ฐ์ ์๋๋ผ๋ฉด) โ ๋จ์ผ vs ๋ณ๋ ฌ ๊ฒฐ๊ณผ mp4 ์ frame-by-frame mask IoU ๋ก sanity check. |
| |
| --- |
| |
| ## 6. ๋จ๊ณ๋ณ ๊ตฌํ ์ฒดํฌ๋ฆฌ์คํธ (์คํ ์์) |
| |
| ์ด ๋ฌธ์์ ์ ํ ์์๋๋ก ์ฝ๋ ์์ . |
| |
| ### Step 1 โ ์ ํ์ผ `parallel_segment_worker.py` ์์ฑ |
| - top-level imports: `os, sys, traceback` ๋ง. |
| - `worker_main(gpu_index, args, progress_queue)` ํจ์ 1.2 ์ ์ฝ๋๋๋ก ์์ฑ. |
| |
| ### Step 2 โ `app.py` ์ ๋์คํจ์ฒ ํด๋์ค ์ถ๊ฐ |
| - `class ParallelSegmentDispatcher:` ์ ์ (1.3 ์ ). |
| - `import uuid` ๊ฐ ์ด๋ฏธ ์๋์ง ํ์ธ (`app.py:30`) โ โ
์์. |
| |
| ### Step 3 โ `app.py` ์ `_auto_mode_process_parallel(...)` ์ ๋๋ ์ดํฐ ์ถ๊ฐ |
| - ์๊ณ ๋ฆฌ์ฆ์ 1.4 ์ . ์ถ๋ ฅ ํํ ํํ๋ ๊ธฐ์กด `_auto_mode_process` ์ `_pkg(...)` ์ ๋์ผํ๊ฒ 19-tuple ์ ์ง (UI ์์ด์ด๋ง ๋ณ๊ฒฝ ์ ํจ). |
| - `auto_mode_status` ๋ฉ์์ง๋ฅผ multiline ์ผ๋ก ๊ตฌ์ฑํด GPU ๋ณ ์งํ๋ฅ ๋
ธ์ถ. |
| - ๊ฒฐ๊ณผ ํ์ผ๋ช
disambiguation: video meta ์ `vid8 = uuid.uuid4().hex[:8]`, `_rename_with_rule` ํธ์ถ ํ `_persist_for_download` ์ ๋จ๊ณ์์ stem ์ `_{vid8}` ์ฝ์
. |
| |
| ### Step 4 โ `app.py` ์ `_auto_mode_process` ์ง์
๋ถ์ ๋ผ์ฐํฐ ์ถ๊ฐ |
| - ํจ์ ์ฒซ ๋ถ๋ถ์์: |
| ```python |
| num_gpus = torch.cuda.device_count() if torch.cuda.is_available() else 0 |
| parallel_env = os.getenv("SAM3_PARALLEL_AUTO_MODE", "auto").lower() |
| use_parallel = ( |
| (parallel_env == "1") or |
| (parallel_env == "auto" and num_gpus > 1) |
| ) and not bool(os.getenv("SPACES_ZERO_GPU")) |
| if use_parallel: |
| yield from _auto_mode_process_parallel(file_list, text_prompt, num_gpus, progress) |
| return |
| ``` |
| - ZeroGPU ๋ชจ๋์์ ๋นํ์ฑ (๊ฐ ํธ์ถ์ด ์ฌ๋ผ์ด์ค ๋จ์๋ก ๋ง GPU ํ ๋น๋ฐ์ โ ๋์์ฑ ๋ฌด์๋ฏธ). |
| |
| ### Step 5 โ `_segment_video_worker_entry` ์์ ์ฝ๋ ์ค๋ณต ์ ๋ฆฌ |
| - ๊ธฐ์กด single-video ๊ฒฝ๋ก (`segment_video` โ `_segment_video_worker_entry`) ๋ ์ ์ง์ ์ผ๋ก `parallel_segment_worker.worker_main` ์ ์ฌ์ฉํด ํ ๊ณณ์์ ๊ด๋ฆฌํ๋๋ก ํตํฉ (์ ํ). 1์ฐจ ๊ตฌํ์์ **๊ฑด๋๋ฆฌ์ง ์๋๋ค** (ํ๊ท ์ํ ์ต์ํ). |
|
|
| ### Step 6 โ ์ถ๋ ฅ ํ์ผ๋ช
disambiguation ํจ์น |
| - `_rename_with_rule` ์๊ทธ๋์ฒ์ `extra_tag: str = ""` ์ต์
์ถ๊ฐ (๊ธฐ๋ณธ ๋น ๋ฌธ์์ด๋ก ํ๋ฐฉํธํ). |
| - ๋ณ๋ ฌ ๊ฒฝ๋ก์์๋ง `extra_tag=vid8` ์ ๋ฌ. |
|
|
| ### Step 7 โ UI ํ
์คํธ ๋ณด๊ฐ |
| - `auto_mode_status` Markdown ์ multi-line ์ถ๋ ฅ (GPU ๋ณ 1์ค). ๋๋ฌด ๊ธธ๋ฉด ์ ๊ธฐ ๊ฐ๋ฅํ ์ฝ๋๋ธ๋ญ์ผ๋ก. |
| - `gr.Progress` ๋ ๋จ์ผ ๋ง๋์ด๋ฏ๋ก, ๋ณ๋ ฌ ๋ชจ๋์ โ์ ์ฒด ํ๊ท ์งํ๋ฅ โ ๋ง ๊ฑฐ๊ธฐ์ ๋ณด๋ด๊ณ GPU ๋ณ ์ธ๋ถ๋ ํ
์คํธ๋ก. |
|
|
| ### Step 8 โ ์ค๋ชจํฌ ํ
์คํธ |
| - ๋ก์ปฌ์์ `python app.py` ๋ก ๋์ฐ๊ณ : |
| - ์์ 2๊ฐ ์
๋ก๋ โ ๋จ์ผ GPU ํ๊ฒฝ์์ ์ง๋ ฌ ๋ชจ๋๋ก ๋์ (GPU 1 ์ฅ๋ง ๋ณด์). |
| - `SAM3_PARALLEL_AUTO_MODE=1 CUDA_VISIBLE_DEVICES=0 python app.py` โ 1-์ฌ๋กฏ ํ๋ก ๋์. |
| - ๊ฒฐ๊ณผ mp4 / csv ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์์ฑ๋๋์ง, status UI ๊ฐ ๊ฐฑ์ ๋๋์ง ํ์ธ. |
|
|
| ### Step 9 โ ํธ์ |
| - `requirements.txt` ๋ณ๊ฒฝ ์์ (multiprocessing / uuid ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ). |
| - HF Space ์์ ํ๋์จ์ด ํญ์์ `4xA100-large` (๋๋ ๋ฑ๊ฐ) ๋ก ์
๊ทธ๋ ์ด๋ํ ํ ๋์ผ ์ฝ๋๋ฅผ ํธ์ํ๋ฉด ์๋์ผ๋ก ๋ณ๋ ฌ ๋ชจ๋ ์ง์
. |
|
|
| --- |
|
|
| ## 7. ํฅํ ํ์ฅ (์ด๋ฒ PR ๋ฒ์ ์ธ) |
|
|
| - **์์ปค ์ฌ์ฌ์ฉ (persistent)**: ๋งค ์์๋ง๋ค spawn ๋์ `Connection`/`Pipe` ๊ธฐ๋ฐ RPC ๋ก ๋ช
๋ น์ ์์ปค์ ๋ณด๋ด ๋ชจ๋ธ 1ํ๋ง ์ ์ฌ. SAM3 ๊ฐ์ค์น ๋ก๋ฉ ๋น์ฉ์ด ์์๋น 1~3 ๋ถ์ด๋ผ๋ฉด ์๋ ์ด๋ ํผ. |
| - **์ทจ์ / ์ผ์์ ์ง**: stop ๋ฒํผ โ ๋์คํจ์ฒ๊ฐ ๋ชจ๋ ์์ปค์ SIGTERM ๋ณด๋ด๊ณ partial ๊ฒฐ๊ณผ ๋ฐํ. |
| - **GPU ๋ณ ๋ฉ๋ชจ๋ฆฌ ๋ค๋ฅธ ๊ฒฝ์ฐ**: ํฐ ์์์ 80 GB GPU ๋ก, ์์ ์์์ ์์ GPU ๋ก ๋ผ์ฐํ
ํ๋ ์ฐ์ ์์ ํ. |
| - **๋ถ์ฐ (multi-node)**: ๋์ผ ์ธํฐํ์ด์ค๋ก worker ๋ฅผ SSH ๋๋จธ ๋
ธ๋๋ก ๋์ธ ์ ์๊ฒ ์ถ์ํ. |
|
|