Skip to content

Commit e34463c

Browse files
committed
feat: Enhance Whisper processing with device detection and resource optimization for GPU transcribing support for macOS Silicon, NVIDIA, and AMD support.
1 parent 9de93c6 commit e34463c

6 files changed

Lines changed: 489 additions & 127 deletions

File tree

EncodeForge/src/main/java/com/encodeforge/controller/MainController.java

Lines changed: 402 additions & 114 deletions
Large diffs are not rendered by default.

EncodeForge/src/main/resources/python/resource_manager.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,15 @@ def _calculate_whisper_workers(self) -> int:
7676
- Medium: ~10GB
7777
- Large: ~15GB
7878
79-
We assume Base model (2GB) as default, with 2GB buffer for system
79+
We assume Small model (5GB) as default, with 4GB buffer for system
80+
Uses AVAILABLE RAM (not total) to avoid overloading the system
8081
"""
81-
# Conservative estimate: 4GB per Whisper instance (2GB model + 2GB working memory)
82-
gb_per_instance = 4.0
82+
# Conservative estimate: 5GB per Whisper instance (small model is most common)
83+
# Adjust based on actual model being used
84+
gb_per_instance = 5.0
8385

84-
# Reserve 2GB for system
85-
available_for_whisper = max(0, self.available_ram_gb - 2)
86+
# Reserve 4GB for system and other processes
87+
available_for_whisper = max(0, self.available_ram_gb - 4)
8688

8789
# Calculate how many instances we can run
8890
max_instances = int(available_for_whisper / gb_per_instance)
@@ -93,7 +95,9 @@ def _calculate_whisper_workers(self) -> int:
9395
# At least 1, at most 4 (diminishing returns beyond 4 parallel transcriptions)
9496
optimal = max(1, min(4, max_instances))
9597

96-
logger.info(f"Whisper workers: {optimal} (based on {self.available_ram_gb:.2f}GB available RAM)")
98+
logger.info(f"Whisper workers: {optimal} (based on {self.available_ram_gb:.2f}GB available RAM, {gb_per_instance}GB per instance)")
99+
logger.info(f" Available RAM after system reserve: {available_for_whisper:.2f}GB")
100+
logger.info(f" Theoretical max instances: {int(available_for_whisper / gb_per_instance)}, using: {optimal}")
97101
return optimal
98102

99103
def get_system_info(self) -> Dict:

EncodeForge/src/main/resources/python/subtitle_providers/whisper_manager.py

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,74 @@ class WhisperManager:
7575
def __init__(self):
7676
self.whisper_available = False
7777
self.installed_models = []
78+
self.device = self._detect_device()
7879
self._check_installation()
7980

81+
def _detect_device(self) -> str:
82+
"""
83+
Detect best available device for PyTorch/Whisper
84+
Supports: NVIDIA CUDA, AMD ROCm (Windows/Linux), Apple Silicon MPS (macOS)
85+
"""
86+
try:
87+
import torch
88+
89+
# NVIDIA CUDA or AMD ROCm (both use torch.cuda API)
90+
# ROCm on Windows: Requires PyTorch built with ROCm support
91+
# Install with: pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm5.7
92+
if torch.cuda.is_available():
93+
device = "cuda"
94+
try:
95+
gpu_name = torch.cuda.get_device_name(0)
96+
# Detect AMD vs NVIDIA
97+
if 'AMD' in gpu_name.upper() or 'RADEON' in gpu_name.upper():
98+
# Check if ROCm is actually being used
99+
if hasattr(torch.version, 'hip') and torch.version.hip:
100+
logger.info(f"🔴 AMD GPU with ROCm detected: {gpu_name}")
101+
logger.info(f" ROCm version: {torch.version.hip} - Whisper will use GPU acceleration")
102+
else:
103+
logger.info(f"🔴 AMD GPU detected: {gpu_name} - Whisper will use ROCm/CUDA acceleration")
104+
else:
105+
cuda_version = torch.version.cuda if hasattr(torch.version, 'cuda') else "Unknown"
106+
logger.info(f"🎮 NVIDIA GPU detected: {gpu_name}")
107+
logger.info(f" CUDA version: {cuda_version} - Whisper will use GPU acceleration")
108+
except Exception as e:
109+
logger.info(f"🎮 GPU detected - Whisper will use CUDA/ROCm acceleration")
110+
logger.debug(f"GPU detection details failed: {e}")
111+
return device
112+
113+
# Apple Silicon M1/M2/M3/M4 (macOS ARM64)
114+
if hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():
115+
device = "mps"
116+
logger.info(f"🍎 Apple Silicon GPU detected - Whisper will use Metal Performance Shaders (MPS)")
117+
logger.info(f" This provides significant speedup on M-series Macs")
118+
return device
119+
120+
# DirectML support (experimental - requires special PyTorch build)
121+
# Not currently supported by Whisper, but may work with custom builds
122+
123+
except ImportError:
124+
logger.debug("PyTorch not available for GPU detection")
125+
pass
126+
except Exception as e:
127+
logger.warning(f"Error detecting GPU: {e}")
128+
129+
# Fallback to CPU
130+
import platform
131+
system = platform.system()
132+
logger.info("💻 No GPU detected - Whisper will use CPU")
133+
134+
# Provide helpful hints based on platform
135+
if system == "Windows":
136+
logger.info(" 💡 For AMD GPU: Install PyTorch with ROCm support")
137+
logger.info(" 💡 For NVIDIA GPU: Install PyTorch with CUDA support")
138+
elif system == "Darwin": # macOS
139+
logger.info(" 💡 For Apple Silicon (M1/M2/M3): Install PyTorch with MPS support")
140+
elif system == "Linux":
141+
logger.info(" 💡 For AMD GPU: Install PyTorch with ROCm support")
142+
logger.info(" 💡 For NVIDIA GPU: Install PyTorch with CUDA support")
143+
144+
return "cpu"
145+
80146
def _convert_language_code(self, lang_code: Optional[str]) -> Optional[str]:
81147
"""Convert 3-letter ISO 639-2 code to 2-letter ISO 639-1 code for Whisper"""
82148
if not lang_code:
@@ -269,9 +335,9 @@ def download_model(self, model_name: str, progress_callback=None) -> tuple[bool,
269335
# This will download the model if not already present
270336
# Whisper handles checksum verification internally - trust it!
271337
# The download can take several minutes for large models
272-
logger.info(f"Calling whisper.load_model('{model_name}')...")
273-
model = whisper.load_model(model_name, download_root=str(model_dir))
274-
logger.info(f"Model {model_name} loaded and verified by Whisper library")
338+
logger.info(f"Calling whisper.load_model('{model_name}') with device={self.device}...")
339+
model = whisper.load_model(model_name, download_root=str(model_dir), device=self.device)
340+
logger.info(f"Model {model_name} loaded and verified by Whisper library on {self.device}")
275341
finally:
276342
# Restore stderr
277343
sys.stderr.close()
@@ -369,9 +435,10 @@ def generate_subtitles(
369435
sys.stderr = open(os.devnull, 'w')
370436

371437
# Load model - Whisper handles its own checksum verification
372-
model = whisper.load_model(model_name)
438+
logger.info(f"Loading model on device: {self.device}")
439+
model = whisper.load_model(model_name, device=self.device)
373440

374-
logger.info(f"Transcribing: {video_path}")
441+
logger.info(f"Transcribing: {video_path} using {self.device.upper()}")
375442

376443
# Send initial progress if callback provided
377444
if progress_callback:

requirements-ai.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
openai-whisper>=20231117
22
torch>=2.0.0
33
numba>=0.58.0,<0.63.0
4+
psutil>=5.9.0
45

requirements-core.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
# No external Python dependencies required for core functionality
2-
# All Python scripts use only standard library modules
1+
# Core Python dependencies for EncodeForge
2+
# Required for resource management and parallel processing optimization
3+
psutil>=5.9.0
34

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ pandas>=1.5.0
33
openai-whisper>=20231117
44
requests>=2.31.0
55
numba>=0.58.0,<0.63.0
6+
psutil>=5.9.0
67

0 commit comments

Comments
 (0)