Repository navigation
feat: add AMD ROCm (HIP) GPU support #311
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| .git | ||
| .github | ||
| .venv | ||
| venv | ||
| __pycache__ | ||
| *.pyc | ||
| *.pyo | ||
| .pytest_cache | ||
| .ruff_cache | ||
| dist | ||
| build | ||
| *.egg-info | ||
| models | ||
| *.flac | ||
| *.wav | ||
| *.mp3 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # Use ROCm base image with Python | ||
| FROM rocm/dev-ubuntu-22.04:7.2-complete | ||
|
|
||
| # Set the working directory in the container | ||
| WORKDIR /workdir | ||
|
|
||
| # Install necessary packages | ||
| RUN apt-get update && apt-get install -y --no-install-recommends \ | ||
| ffmpeg \ | ||
| python3 \ | ||
| python3-pip \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| RUN python3 -m pip install --upgrade pip | ||
|
|
||
| # Install PyTorch with ROCm support | ||
| RUN --mount=type=cache,target=/root/.cache \ | ||
| pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm7.2 | ||
|
|
||
| # Install audio-separator from this local checkout so the ROCm support in the | ||
| # repository is used, not a released PyPI version that may predate it. | ||
| # The [rocm] extra installs onnxruntime-gpu. Its CUDAExecutionProvider is CUDA-linked | ||
| # and cannot activate on a ROCm host, so MDX-Net ONNX models run through the PyTorch | ||
| # HIP path (see MDXSeparator._prefer_pytorch_inference_on_rocm); VR, Demucs, and | ||
| # MDXC/RoFormer already run on the ROCm GPU through PyTorch. | ||
| COPY . /src | ||
| RUN --mount=type=cache,target=/root/.cache \ | ||
| cd /src && pip3 install ".[rocm]" | ||
|
|
||
| # GPU compatibility overrides are opt-in and unset by default. PYTORCH_ROCM_ARCH is a | ||
| # kernel build target that is not needed with prebuilt ROCm PyTorch wheels, and | ||
| # HSA_OVERRIDE_GFX_VERSION is only needed for consumer GPUs whose gfx target the | ||
| # installed ROCm build does not natively support (for example RX 6600, gfx1032). | ||
| # Supply it at run time with `-e`: | ||
| # docker run -e HSA_OVERRIDE_GFX_VERSION=10.3.0 ... | ||
|
|
||
| # Run as a non-root user. ROCm device access still comes from the caller's | ||
| # --device /dev/kfd --device /dev/dri and --group-add=video --group-add=render, which | ||
| # add the host GPU groups to this user. APP_UID/APP_GID default to 1000; override at build time | ||
| # (--build-arg APP_UID=$(id -u) APP_GID=$(id -g)) or pass --user at run time so the | ||
| # host-owned /workdir bind mount stays writable. | ||
| ARG APP_UID=1000 | ||
| ARG APP_GID=1000 | ||
| RUN groupadd --gid "${APP_GID}" appuser \ | ||
| && useradd --uid "${APP_UID}" --gid "${APP_GID}" --create-home --shell /bin/bash appuser \ | ||
| && mkdir -p /workdir \ | ||
| && chown -R appuser:appuser /workdir | ||
| USER appuser | ||
|
|
||
| # Run audio-separator when the container launches | ||
| ENTRYPOINT ["audio-separator"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ class MDXSeparator(CommonSeparator): | |
| """ | ||
|
|
||
| def __init__(self, common_config, arch_config): | ||
| """Initialize the MDX separator with shared and MDX-specific parameters.""" | ||
| # Any configuration values which can be shared between architectures should be set already in CommonSeparator, | ||
| # e.g. user-specified functionality choices (self.output_single_stem) or common model parameters (self.primary_stem_name) | ||
| super().__init__(config=common_config) | ||
|
|
@@ -112,8 +113,12 @@ def load_model(self): | |
| """ | ||
| self.logger.debug("Loading ONNX model for inference...") | ||
|
|
||
| if self.segment_size == self.dim_t: | ||
| self.uses_pytorch_inference = False | ||
| use_onnx_runtime = self.segment_size == self.dim_t | ||
| if use_onnx_runtime and self._prefer_pytorch_inference_on_rocm(): | ||
| use_onnx_runtime = False | ||
|
|
||
| ort_inference_session = None | ||
| if use_onnx_runtime: | ||
| ort_session_options = ort.SessionOptions() | ||
| if self.log_level > 10: | ||
| ort_session_options.log_severity_level = 3 | ||
|
|
@@ -122,28 +127,109 @@ def load_model(self): | |
|
|
||
| ort_inference_session = ort.InferenceSession(self.model_path, providers=self.onnx_execution_provider, sess_options=ort_session_options) | ||
| session_providers = ort_inference_session.get_providers() | ||
|
|
||
| requested_provider = self.onnx_execution_provider[0] if self.onnx_execution_provider else None | ||
|
|
||
| if requested_provider and requested_provider not in session_providers: | ||
| self.logger.warning( | ||
| f"ONNX Runtime could not activate requested provider {requested_provider}; " | ||
| f"session is using {session_providers}. This usually means required CUDA/cuDNN " | ||
| f"runtime libraries are not visible to the dynamic loader." | ||
| f"session is using {session_providers}. This usually means required runtime " | ||
| f"libraries are not visible to the dynamic loader." | ||
| ) | ||
| if self._should_convert_inactive_amd_provider_on_rocm(requested_provider): | ||
| use_onnx_runtime = False | ||
| ort_inference_session = None | ||
| else: | ||
| self.logger.debug(f"ONNX Runtime session providers: {session_providers}") | ||
|
|
||
| if use_onnx_runtime: | ||
| self.model_run = lambda spek: ort_inference_session.run(None, {"input": spek.cpu().numpy()})[0] | ||
| self.logger.debug("Model loaded successfully using ONNXruntime inferencing session.") | ||
| else: | ||
| if platform.system() == 'Windows': | ||
| if platform.system() == "Windows": | ||
| onnx_model = onnx.load(self.model_path) | ||
| self.model_run = onnx2torch.convert(onnx_model) | ||
| else: | ||
| self.model_run = onnx2torch.convert(self.model_path) | ||
|
|
||
| self.model_run.to(self.torch_device).eval() | ||
| self.logger.warning("Model converted from onnx to pytorch due to segment size not matching dim_t, processing may be slower.") | ||
| self.logger.warning( | ||
| "Model converted from ONNX to PyTorch (segment size does not match dim_t, " | ||
| "or ROCm has no usable ONNX GPU provider); processing may be slower." | ||
| ) | ||
|
|
||
| self.uses_pytorch_inference = not use_onnx_runtime | ||
|
|
||
| def _rocm_gpu_selected(self): | ||
| """ | ||
| Return True when ROCm PyTorch is active and the resolved device is the GPU. | ||
|
|
||
| torch.version.hip is set for any ROCm build of PyTorch, including a CPU-only run | ||
| on such a build. Requiring the selected device to be "cuda" keeps the ONNX | ||
| Runtime path on CPU, so a ROCm host without a usable GPU is unaffected. | ||
| """ | ||
| if getattr(torch.version, "hip", None) is None: | ||
| return False | ||
| return getattr(self.torch_device, "type", None) == "cuda" | ||
|
|
||
| def _prefer_pytorch_inference_on_rocm(self): | ||
| """ | ||
| Return True when an ROCm host should run the converted PyTorch graph. | ||
|
|
||
| Official onnxruntime-gpu wheels link against CUDA runtime libraries, which are | ||
| not present on an ROCm host, so requesting CUDAExecutionProvider silently falls | ||
| back to the CPU provider. When no real AMD ONNX GPU provider is selected, | ||
| converting the graph and running it on the HIP device keeps MDX-Net on the GPU. | ||
| Non-ROCm hosts, and ROCm hosts whose selected device is not the GPU, return | ||
| False, so CPU/CUDA/MPS/DirectML are unchanged. | ||
| """ | ||
| if not self._rocm_gpu_selected(): | ||
| return False | ||
|
|
||
| if self._onnx_runtime_override_requested(): | ||
| return False | ||
|
|
||
| provider = (self.onnx_execution_provider or [None])[0] | ||
| if provider in {"MIGraphXExecutionProvider", "ROCMExecutionProvider"}: | ||
| return False | ||
|
Comment on lines
+192
to
+193
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: set -eu
printf '%s\n' '--- affected source ---'
nl -ba audio_separator/separator/architectures/mdx_separator.py | sed -n '100,215p'
printf '%s\n' '--- provider/session references ---'
rg -n -F --glob '*.py' -- 'get_providers(' audio_separator tests || test "$?" -eq 1
rg -n -F --glob '*.py' -- 'InferenceSession' audio_separator/separator/architectures/mdx_separator.py tests || test "$?" -eq 1
printf '%s\n' '--- directly relevant tests ---'
rg -n -F --glob '*.py' -- 'prefer_pytorch_inference_on_rocm' tests audio_separator || test "$?" -eq 1
rg -n -F --glob '*.py' -- 'uses_pytorch_inference' tests audio_separator || test "$?" -eq 1Repository: nomadkaraoke/python-audio-separator Length of output: 8236 Fallback to HIP when the requested AMD provider is not active. When 🤖 Prompt for AI AgentsSource: Learnings |
||
|
|
||
| self.logger.info( | ||
| "ROCm detected and no AMD ONNX GPU provider is selected; running MDX-Net " | ||
| "through PyTorch (HIP) instead of the ONNX Runtime CUDA/CPU fallback." | ||
| ) | ||
| return True | ||
|
|
||
| def _should_convert_inactive_amd_provider_on_rocm(self, requested_provider): | ||
| """ | ||
| Return True when a requested but inactive AMD ONNX provider should run on HIP. | ||
|
|
||
| A requested MIGraphXExecutionProvider or ROCMExecutionProvider can still fail to | ||
| activate (missing native libraries or an unsupported gfx target) and silently | ||
| leave the session on CPUExecutionProvider. On ROCm, converting the graph and | ||
| running it on the HIP device keeps MDX-Net on the GPU. The explicit | ||
| AUDIO_SEPARATOR_ROCM_ONNX override keeps the ONNX Runtime path. | ||
| """ | ||
| if requested_provider not in {"MIGraphXExecutionProvider", "ROCMExecutionProvider"}: | ||
| return False | ||
| if not self._rocm_gpu_selected(): | ||
| return False | ||
| if self._onnx_runtime_override_requested(): | ||
| return False | ||
|
|
||
| self.logger.info( | ||
| "ROCm detected but the requested AMD ONNX provider did not activate; running " | ||
| "MDX-Net through PyTorch (HIP) instead of the ONNX Runtime CPU fallback." | ||
| ) | ||
| return True | ||
|
|
||
| def _onnx_runtime_override_requested(self): | ||
| """ | ||
| Return True when AUDIO_SEPARATOR_ROCM_ONNX asks to keep the ONNX Runtime path. | ||
|
|
||
| Accepted values (case-insensitive): "ort", "cpu", and "0". They only disable the | ||
| PyTorch conversion; the provider still comes from onnx_execution_provider, which | ||
| can be a CUDA-named provider that ONNX Runtime falls back to CPU. | ||
| """ | ||
| return os.environ.get("AUDIO_SEPARATOR_ROCM_ONNX", "").strip().lower() in {"cpu", "ort", "0"} | ||
|
|
||
| def separate(self, audio_file_path, custom_output_names=None): | ||
| """ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: nomadkaraoke/python-audio-separator
Length of output: 17369
🏁 Script executed:
Repository: nomadkaraoke/python-audio-separator
Length of output: 19689
Handle provider failure after the ONNX session starts.
If a MIGraphX or ROCm provider raises
EPFail, ONNX Runtime can retry the chunk withCPUExecutionProvider. The retained runner then uses the CPU session for later chunks. The MDX loop still processes every chunk and produces output, so this is a recoverable performance degradation rather than a major workflow failure.When
AUDIO_SEPARATOR_ROCM_ONNXis not set, detect the provider change and reuse the existing PyTorch/HIP conversion path. Rerun the triggering chunk when the CPU retry already returned a result. Preserve the explicit override behavior. The existing conversion branch makes this a localized change, not a high-effort redesign.🤖 Prompt for AI Agents