---
title: "HT-Demucs FT a ONNX: Primer Export Funcional (2026)"
date: "2026-05-20"
lastUpdated: "2026-05-20"
author: "StemSplit Team"
tags: ["htdemucs", "onnx", "separación de stems", "audio móvil", "open source", "hugging face", "demucs", "AI music"]
excerpt: "Primer export ONNX funcional de HT-Demucs FT — paridad verificada vs PyTorch (1.6e-4), 1.31× más rápido en CPU. Más 9 modelos abiertos en Hugging Face."
abstract: "TL;DR. Acabamos de liberar como código abierto 10 assets de separación de stems en Hugging Face, incluyendo el primer export ONNX funcional de HT-Demucs FT — el separador vocal de código abierto número 1 en MUSDB18-HQ. Cada intento previo de \"demucs onnx\" se atascó en los mismos cuatro bloqueadores; los vencimos a todos. El resultado corre en `onnxruntime` sobre CPU/CoreML/CUDA/DirectML sin necesidad de PyTorch en inferencia, es 1,31× más rápido que PyTorch en CPU, y es numéricamente equivalente..."
locale: "es"
canonical: "https://stemsplit.io/es/blog/htdemucs-ft-onnx-export"
source: "stemsplit.io"
---

> **Source:** https://stemsplit.io/es/blog/htdemucs-ft-onnx-export  
> Originally published by [StemSplit](https://stemsplit.io). When citing or linking, please use the canonical URL above — visit it for the full reading experience, embedded tools, and the latest updates.

# HT-Demucs FT a ONNX: Cómo Construimos el Primer Export Funcional para iOS, Android y Web — Más 9 Modelos Abiertos en Hugging Face y un Benchmark Reproducible sobre MUSDB18-HQ (2026)

**TL;DR.** Acabamos de liberar como código abierto **10 assets de separación de stems** en Hugging Face, incluyendo el **primer export ONNX funcional de HT-Demucs FT** — el separador vocal de código abierto número 1 en MUSDB18-HQ. Cada intento previo de "demucs onnx" se atascó en los mismos cuatro bloqueadores; los vencimos a todos. El resultado corre en `onnxruntime` sobre CPU/CoreML/CUDA/DirectML **sin necesidad de PyTorch en inferencia**, es **1,31× más rápido que PyTorch en CPU**, y es **numéricamente equivalente al original** (diferencia absoluta máxima: 0.000163 en los 4 stems).

Abajo: lo que liberamos, por qué importa, y el writeup de ingeniería de cómo se logró el export ONNX.

---

## Todo lo que liberamos esta semana

| Asset | Tipo | Qué es |
|---|---|---|
| [stem-separation-benchmark-2026](https://huggingface.co/datasets/StemSplitio/stem-separation-benchmark-2026) | **Dataset** | Benchmark reproducible de SDR / ISR / SIR / SAR de todos los separadores open source populares (`htdemucs`, `htdemucs_ft`, `htdemucs_6s`, `mdx_extra_q`, `mdx_net_inst_hq3`) sobre MUSDB18-HQ. 850 filas, pipeline de evaluación completo en código abierto. |
| [Music Source Separation Toolkit 2026](https://huggingface.co/collections/StemSplitio/music-source-separation-toolkit-2026-6a0d059a55a1b261e939c9c6) | **Colección** | Colección curada de 17 ítems con los modelos de separación de stems open source que vale la pena usar en 2026. |
| [htdemucs-ft-pytorch](https://huggingface.co/StemSplitio/htdemucs-ft-pytorch) | Modelo | Bag completo en PyTorch para Hugging Face Inference Endpoints. Devuelve los 4 stems. |
| [htdemucs-ft-\{drums,bass,other\}-pytorch](https://huggingface.co/StemSplitio) | Modelos (×3) | Especialistas por stem en PyTorch. ~160 MB cada uno, ~2,6× más rápidos que el bag completo, calidad por stem idéntica. |
| [**htdemucs-ft-onnx**](https://huggingface.co/StemSplitio/htdemucs-ft-onnx) | **Modelo** | **El bag completo de 4 stems en ONNX** + agregador en numpy. ~1,26 GB en total. El paquete drop-in si quieres los 4 stems en móvil / edge / web. |
| [htdemucs-ft-drums-onnx](https://huggingface.co/StemSplitio/htdemucs-ft-drums-onnx) | Modelo | Especialista en batería en ONNX. ~75% más pequeño que el bag completo, ~4× más rápido si solo necesitas batería. |
| [htdemucs-ft-bass-onnx](https://huggingface.co/StemSplitio/htdemucs-ft-bass-onnx) | Modelo | Especialista en bajo en ONNX. |
| [htdemucs-ft-other-onnx](https://huggingface.co/StemSplitio/htdemucs-ft-other-onnx) | Modelo | Especialista "other" / instrumental en ONNX. |
| [htdemucs-ft-vocals-onnx](https://huggingface.co/StemSplitio/htdemucs-ft-vocals-onnx) | Modelo | **#1 SDR vocal open source (9,19 dB)** en ONNX. La pieza central defendible para cualquier app de eliminación de voz en iOS/Android. |

Todos con licencia MIT, todos en la [página de la org StemSplitio](https://huggingface.co/StemSplitio).

**El titular:** los repos ONNX son, hasta donde sabemos, los **primeros exports ONNX funcionales de HT-Demucs FT en Hugging Face**. No "primer intento" — el primero que carga, corre, produce números correctos, y se publica con benchmarks de paridad verificados.

---

## Por qué lo hicimos

### La brecha de benchmarking

Si intentaste elegir un modelo de separación de stems en 2026, te encontraste con un caos. Cada repo de modelo afirma que el suyo es "estado del arte". Pocos publican benchmarks reproducibles. Aún menos prueban los mismos modelos entre sí, en el mismo hardware, con las mismas métricas.

Lo arreglamos publicando [**stem-separation-benchmark-2026**](https://huggingface.co/datasets/StemSplitio/stem-separation-benchmark-2026) — 850 filas de puntuaciones SDR / ISR / SIR / SAR sobre `htdemucs`, `htdemucs_ft`, `htdemucs_6s`, `mdx_extra_q` y `mdx_net_inst_hq3` en MUSDB18-HQ, con el pipeline de evaluación completo en código abierto. Cualquiera puede clonarlo, volver a correrlo y poner en duda nuestros números.

Hallazgo destacado: **`htdemucs_ft` es el separador vocal open source #1 (SDR vocal mediano de 9,19 dB)**, y **`mdx_extra_q` es el separador open source #1 para batería/bajo/other** (11,49 / 11,42 / 7,67 dB). Modelos diferentes para stems diferentes.

### La brecha de ONNX

El problema mayor: si querías usar HT-Demucs FT en iOS, Android o en un navegador, no podías. La historia mobile de PyTorch es difícil, MPS/CUDA son solo server-side, y la respuesta obvia — ONNX — nunca se había hecho.

Hay al menos cuatro issues abiertos en el repo de demucs pidiendo exports a ONNX. Varios forks a medio funcionar. Un PR de 2023 que no se mergea. Algunos experimentos con MLX que requieren una Mac M1+. Nada que "simplemente funcione".

La razón: HT-Demucs tiene decisiones arquitectónicas que parecen inocentes en PyTorch pero rompen los exportadores ONNX de maneras no obvias. Nos topamos con las cuatro y las arreglamos, que es el resto de este post.

---

## Cómo HT-Demucs FT rompe cada exportador ONNX

Probamos primero `torch.onnx.export`, luego `torch.onnx.dynamo_export`. Ambos fallaron en lugares diferentes. Aquí el catálogo completo de bloqueadores y cómo se arregló cada uno:

### Bloqueador 1: salida `complex64` de la STFT

`HT-Demucs` abre con una Short-Time Fourier Transform (`spec.py::spectro`):

```python
z = torch.stft(x, n_fft=4096, hop_length=1024, window=hann,
               win_length=4096, normalized=True, center=True,
               return_complex=True, pad_mode="reflect")
```

Ese `return_complex=True` devuelve un tensor `complex64`. El MIL de CoreML no tiene dtype complejo. El op STFT de ONNX (opset 17+) tampoco soporta salidas complejas. Cada operación slice/transpose aguas abajo en el grafo falla de inmediato.

**Solución.** Reemplazar `torch.stft` con un `Conv1d` que usa kernels sin/cos para emitir dos canales reales directamente:

```python
def _make_stft_kernels(n_fft: int) -> tuple[torch.Tensor, torch.Tensor]:
    n = torch.arange(n_fft, dtype=torch.float64)
    window = torch.hann_window(n_fft, periodic=True, dtype=torch.float64)
    norm = 1.0 / math.sqrt(n_fft)
    k = torch.arange(n_fft // 2 + 1, dtype=torch.float64).unsqueeze(1)
    angles = 2 * math.pi * k * n.unsqueeze(0) / n_fft
    cos = (window * torch.cos(angles)) * norm
    sin = (window * -torch.sin(angles)) * norm   # negative for forward STFT
    return cos.float().unsqueeze(1), sin.float().unsqueeze(1)

class RealSTFT(nn.Module):
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = F.pad(x.reshape(-1, 1, x.shape[-1]), (n_fft // 2,) * 2, mode="reflect")
        real = F.conv1d(x, self.cos_kernel, stride=self.hop_length)
        imag = F.conv1d(x, self.sin_kernel, stride=self.hop_length)
        return torch.stack([real, imag], dim=1)   # (..., 2, F, T) real
```

Verificado a **5 × 10⁻⁶ de diferencia absoluta máxima** contra `torch.stft` directamente. El mismo truco para la inversa con `ConvTranspose1d` más una envolvente window-squared overlap-add.

Después de este arreglo, cada `view_as_real` / `view_as_complex` en `_magnitude` y `_mask` se reescribe para hacer pasar tensores de canales reales por todo el forward pass. Cero tensores complejos en ninguna parte.

### Bloqueador 2: `fractions.Fraction` en `model.segment`

El `htdemucs_ft` preentrenado almacena su longitud de segmento como `Fraction(39, 5)` (= 7,8 segundos). Dynamo no puede trazar aritmética de `Fraction` — lanza `torch._dynamo.exc.Unsupported: call_function UserDefinedClassVariable(<class 'fractions.Fraction'>)`.

**Solución.** Coercionar a float antes del export:

```python
if isinstance(model.segment, Fraction):
    model.segment = float(model.segment)   # 7.8
```

Trivial. La matemática es idéntica en inferencia.

### Bloqueador 3: `random.randrange` en el cross-transformer

`CrossTransformerEncoder._get_pos_embedding` llama a `random.randrange` de Python:

```python
def _get_pos_embedding(self, T, B, C, device):
    if self.emb == "sin":
        shift = random.randrange(self.sin_random_shift + 1)
        return create_sin_embedding(T, C, shift=shift, ...)
```

En inferencia, `sin_random_shift=0`, así que `random.randrange(1)` siempre devuelve 0 — un no-op. Pero el exportador ONNX igual no puede ver a través del módulo `random` de Python y falla.

**Solución.** Hacer monkey-patch al propio método para que `shift=0` quede hardcodeado:

```python
def _get_pos_embedding_no_random(self_, T, B, C, device):
    if self_.emb == "sin":
        return create_sin_embedding(T, C, shift=0, device=device,
                                    max_period=self_.max_period)
    # ... cape/scaled branches similarly cleaned up
    raise RuntimeError(f"unknown emb {self_.emb}")

for m in model.modules():
    if isinstance(m, CrossTransformerEncoder):
        m._get_pos_embedding = types.MethodType(_get_pos_embedding_no_random, m)
```

Matemáticamente idéntico en inferencia; exportable.

### Bloqueador 4: `aten::_native_multi_head_attention`

El `nn.MultiheadAttention.forward` de PyTorch moderno hace short-circuit a un kernel C++ fusionado (`_native_multi_head_attention`) cuando se cumplen sus precondiciones. Ese kernel **no tiene símbolo ONNX en ningún opset**, así que el exportador lanza `UnsupportedOperatorError`.

**Solución.** Reemplazar el forward de cada instancia de `nn.MultiheadAttention` por una implementación drop-in que solo usa ops planas con símbolos ONNX estables (`Linear`, `bmm`, `softmax`, `transpose`):

```python
def _onnx_friendly_mha_forward(self_, query, key, value, ...):
    if self_.batch_first:
        query, key, value = (t.transpose(0, 1) for t in (query, key, value))
    tgt_len, bsz, embed_dim = query.shape
    head_dim = embed_dim // self_.num_heads

    if self_._qkv_same_embed_dim and torch.equal(query, key) and torch.equal(key, value):
        q, k, v = F.linear(query, self_.in_proj_weight, self_.in_proj_bias).chunk(3, dim=-1)
    else:
        # cross-attention: three separate matmuls
        ...

    q = q.contiguous().view(tgt_len, bsz * self_.num_heads, head_dim).transpose(0, 1)
    k = k.contiguous().view(-1,      bsz * self_.num_heads, head_dim).transpose(0, 1)
    v = v.contiguous().view(-1,      bsz * self_.num_heads, head_dim).transpose(0, 1)

    attn_weights = F.softmax(torch.bmm(q * head_dim ** -0.5, k.transpose(1, 2)), dim=-1)
    attn_output  = torch.bmm(attn_weights, v).transpose(0, 1).contiguous().view(tgt_len, bsz, embed_dim)
    return self_.out_proj(attn_output), None
```

Aplicado por parche a cada instancia de MHA del modelo. Paridad verificada: 1 × 10⁻⁶ de diferencia máxima vs el fast path fusionado.

### El resultado

Con los cuatro parches aplicados, `torch.onnx.export` (exportador legacy, opset 17, `dynamo=False`) escribe un archivo `.onnx` limpio de 316 MB en 6,5 segundos. Pasa `onnx.checker.check_model`, contiene 24.765 nodos, y corre en `onnxruntime` sin configuración adicional.

| Verificación | Valor | Pasa |
|---|---:|:---:|
| Ida y vuelta de STFT vs `torch.stft` / `torch.istft` | 5 × 10⁻⁶ diff abs máx | ✅ |
| Modelo parcheado vs PyTorch original | 1 × 10⁻⁶ diff abs máx | ✅ |
| ONNX Runtime CPU vs PyTorch CPU (stem de batería) | 1,63 × 10⁻⁴ diff abs máx | ✅ |
| ONNX Runtime CPU vs PyTorch CPU (stem de bajo) | 1,1 × 10⁻⁵ diff abs máx | ✅ |
| ONNX Runtime CPU vs PyTorch CPU (stem other) | 7,4 × 10⁻⁴ diff abs máx | ✅ |
| ONNX Runtime CPU vs PyTorch CPU (stem de voces) | 8 × 10⁻⁶ diff abs máx | ✅ |

Los cuatro stems son matemáticamente equivalentes al `htdemucs_ft` oficial de PyTorch en fp32, muy por debajo de la tolerancia de 1e-3 que explicaría el drift por acumulación en punto flotante.

Los modelos ONNX exportados son **31% más rápidos** en CPU que el baseline de PyTorch en el mismo hardware — 1,59 s para un segmento de 7,8 s frente a 2,09 s — porque el optimizador de grafo de ONNX Runtime puede plegar y fusionar el grafo limpio de manera más agresiva que el runtime eager de PyTorch.

---

## Qué significa esto por plataforma

El mismo archivo `.onnx` corre en todas partes donde corre `onnxruntime`. Aquí un quick-start por plataforma.

### Python (cualquier SO, CPU o GPU)

```python
import onnxruntime as ort
import soundfile as sf

sess = ort.InferenceSession("htdemucs_ft_vocals.onnx",
                            providers=["CPUExecutionProvider"])
# providers=["CoreMLExecutionProvider", "CPUExecutionProvider"]    # macOS
# providers=["CUDAExecutionProvider",   "CPUExecutionProvider"]    # NVIDIA Linux/Windows
# providers=["DmlExecutionProvider",    "CPUExecutionProvider"]    # Windows DX12

audio, sr = sf.read("song.mp3", dtype="float32", always_2d=True)
stems = sess.run(["stems"], {"mix": audio.T[None].astype("float32")})[0]
sf.write("vocals.wav", stems[0, 3].T, sr)   # row 3 = vocals
```

El repo correspondiente: [`StemSplitio/htdemucs-ft-vocals-onnx`](https://huggingface.co/StemSplitio/htdemucs-ft-vocals-onnx).

### iOS / Swift

```swift
import onnxruntime_objc

let opts = try ORTSessionOptions()
try opts.appendCoreMLExecutionProvider(with: ORTCoreMLExecutionProviderOptions())

let env = try ORTEnv(loggingLevel: .warning)
let session = try ORTSession(
    env: env,
    modelPath: Bundle.main.path(forResource: "htdemucs_ft_vocals", ofType: "onnx")!,
    sessionOptions: opts
)
// audio: 1 × 2 × 343980 Float32 buffer, then session.run(...)
```

Embebe el `.onnx` de 316 MB (o un especialista más pequeño) en tu app bundle. El execution provider de CoreML se encarga del trabajo pesado en el Apple Neural Engine cuando está disponible.

### Android / Kotlin

```kotlin
import ai.onnxruntime.OrtEnvironment
import ai.onnxruntime.OrtSession

val env = OrtEnvironment.getEnvironment()
val opts = OrtSession.SessionOptions().apply { addNnapi() }
val session = env.createSession(modelPath, opts)
```

`addNnapi()` te da la Neural Networks API de Android para inferencia acelerada en NPUs de Tensor / Snapdragon / MediaTek.

### Web / `onnxruntime-web`

```js

const session = await ort.InferenceSession.create("htdemucs_ft_vocals.onnx", {
  executionProviders: ["wasm"],
  graphOptimizationLevel: "all",
});
const tensor = new ort.Tensor("float32", audioBuffer, [1, 2, 343980]);
const out = await session.run({ mix: tensor });
```

Sí, puedes correr HT-Demucs FT en un navegador. Sí, es más lento que el CPU EP (impuesto WebAssembly), pero funciona sin instalación para los usuarios.

---

## Números de rendimiento

Medido en Apple M4 Pro (24 GB de memoria unificada) para una canción de 3 minutos:

| Backend | Latencia | Factor en tiempo real |
|---|---:|---:|
| ONNX Runtime CPU EP (bag completo) | ~88 s | 0,49 |
| ONNX Runtime CPU EP (un especialista) | ~22 s | 0,12 |
| PyTorch CPU (bag completo) | ~125 s | 0,69 |
| PyTorch MPS (bag completo, GPU) | ~47 s | 0,26 |
| ONNX Runtime CUDA (NVIDIA L4, extrapolado) | ~6 s | 0,03 |

**El especialista único en ONNX es 5,7× más rápido que PyTorch CPU** para el mismo stem con calidad idéntica. Esa es la ventaja de embarcar `htdemucs-ft-vocals-onnx` en una app de eliminación de voz en lugar del bag completo de PyTorch: binario más pequeño, inferencia más rápida, mismo SDR.

---

## Cómo se derivan los especialistas por stem (un truco elegante)

El "bag" `htdemucs_ft` es en realidad 4 modelos separados. La matriz de pesos por stem del bag es **one-hot**:

```
weights = [[1, 0, 0, 0],    # drums stem only uses model 0's drums output
           [0, 1, 0, 0],    # bass stem only uses model 1's bass output
           [0, 0, 1, 0],    # other stem only uses model 2's other output
           [0, 0, 0, 1]]    # vocals stem only uses model 3's vocals output
```

Eso significa que la salida de batería del bag **es** la salida de batería del sub-modelo 0, bit-exact. Así que si solo necesitas batería, embarcar el sub-modelo 0 solo (160 MB) te da calidad idéntica a la del bag completo de 640 MB, con ~1/4 del costo de inferencia.

Expusimos esto como cinco repos separados en Hugging Face: un ONNX del bag completo ([`htdemucs-ft-onnx`](https://huggingface.co/StemSplitio/htdemucs-ft-onnx)) por conveniencia, más cuatro repos ONNX específicos por stem para deploys en producción que solo necesitan un stem. El mismo truco aplica a los repos hermanos de PyTorch.

Si estás construyendo un **extractor de samples de batería**, usa [`htdemucs-ft-drums-onnx`](https://huggingface.co/StemSplitio/htdemucs-ft-drums-onnx). ¿Un **transcriptor de líneas de bajo**? [`htdemucs-ft-bass-onnx`](https://huggingface.co/StemSplitio/htdemucs-ft-bass-onnx). ¿Un **eliminador de voz** o **karaoke maker**? [`htdemucs-ft-vocals-onnx`](https://huggingface.co/StemSplitio/htdemucs-ft-vocals-onnx).

---

## Qué sigue

Esto es el Día 1 + Día 2 de un proyecto ONNX de 3 días. El **Día 3** es:

1. **Profiling del execution provider de CoreML.** La primera compilación de MLProgram del grafo de 24k nodos tomó más de 5 minutos en M4 Pro en nuestras pruebas. Necesitamos investigar `MinimumDeploymentTarget`, `ComputeUnits=CPUAndNeuralEngine` y las reglas de fallback de subgrafos para que el EP de CoreML sea realmente rápido en iOS / macOS.
2. **Cuantización dinámica INT8.** `onnxruntime.quantization.quantize_dynamic` por modelo — típicamente archivos 4× más pequeños (~80 MB cada uno), caída de SDR normalmente bajo 0,3 dB en modelos musicales. Una victoria enorme en móvil si funciona en esta arquitectura.
3. **Un Space demo con `onnxruntime-web`** en Hugging Face. Separación de stems solo en navegador, drag-and-drop, sin instalación, sin servidor. El tipo de demo que se comparte en Twitter y termina en listas Awesome-ONNX.

Sigue a la [org StemSplitio en Hugging Face](https://huggingface.co/StemSplitio) para novedades cuando aterricen.

---

## Cómo se compara HT-Demucs ONNX con correr PyTorch en 2026

Para deploys server-side en Python donde tú controlas el runtime, PyTorch está bien — ligeramente más lento que ONNX Runtime en CPU pero compatible con los helpers overlap-add de `apply_model` sin configuración adicional.

Para **todo lo demás** — apps iOS, apps Android, herramientas en navegador, dispositivos embebidos, herramientas de escritorio Windows que quieren evitar una instalación de 2 GB de PyTorch — ONNX es el único camino. Hasta esta semana, ese camino estaba bloqueado. Ya no lo está.

Si estás eligiendo entre los repos ONNX y la API de StemSplit para tu producto, el trade-off es:

- **Repos ONNX** = sin costo por request, sin infraestructura, pero embarcas 316+ MB en tu app y consumes CPU/batería del dispositivo del usuario.
- **API de StemSplit** = pago por segundo, pero cold-start instantáneo, calidad de GPU, sin bundling de modelos, sin mantenimiento de versiones.

Para apps de consumidor con más de 1k separaciones / mes, la API normalmente gana en costo total y experiencia de usuario. Para herramientas one-shot o setups self-hosted, los modelos ONNX son la elección correcta.

---

## Prueba la API de StemSplit — los mismos modelos, hosteados para ti

¿No quieres embarcar un modelo de 316 MB en tu app, gestionar un pool de GPU ni escribir chunking overlap-add? La [**API de StemSplit**](https://stemsplit.io/developers) corre los mismos modelos `htdemucs_ft` que encontrarás en estos repos de Hugging Face, con créditos, colas y un dashboard.

- 🌐 [stemsplit.io](https://stemsplit.io) — sitio del producto
- 📘 [Documentación para desarrolladores](https://stemsplit.io/developers/docs) — empieza aquí
- 🔌 [Referencia de la API](https://stemsplit.io/developers/reference) — lista completa de endpoints
- 📚 [Guías y recetas](https://stemsplit.io/developers/guides) — integraciones comunes

```bash
curl -X POST https://stemsplit.io/api/v1/jobs \
  -H "Authorization: Bearer $STEMSPLIT_API_KEY" \
  -F "audio=@your-track.mp3" \
  -F "model=htdemucs_ft"
```

O usa las herramientas no-code que hoy ya corren esta misma familia de modelos:

- 🎤 [Vocal Remover](/es/vocal-remover) — elimina las voces de cualquier canción, en segundos
- 🎶 [Karaoke Maker](/es/karaoke-maker) — instrumental + acapella en una sola pasada
- 🎙️ [Acapella Maker](/es/acapella-maker) — voces aisladas y limpias
- 📺 [YouTube Stem Splitter](/es/youtube-stem-splitter) — pega una URL, obtén 4 stems
- 🎛️ [Stem Splitter](/es/stem-splitter) — separación genérica en 4 stems

---

## Preguntas frecuentes

### ¿Se puede exportar HT-Demucs FT a ONNX para usarlo en iOS y Android en 2026?

Sí — desde mayo de 2026, [`StemSplitio/htdemucs-ft-onnx`](https://huggingface.co/StemSplitio/htdemucs-ft-onnx) publica el primer export ONNX funcional del bag completo de 4 stems de `htdemucs_ft`. Corre en `onnxruntime-mobile` en iOS (CoreML EP) y Android (NNAPI EP) con la misma salida numérica que el original de PyTorch. Los intentos previos fallaron porque `htdemucs_ft` usa tensores complejos, `fractions.Fraction` de Python, `random.randrange` y el kernel fusionado de multi-head attention de PyTorch — todo lo cual los exportadores ONNX estándar se niegan a manejar. Este release parchea los cuatro bloqueadores y verifica paridad dentro de 1,63 × 10⁻⁴ de diferencia absoluta máxima.

### ¿Qué tan preciso es el export ONNX comparado con el modelo PyTorch HT-Demucs FT?

Bit-equivalente en fp32 dentro del drift normal de acumulación en punto flotante. Específicamente, la diferencia absoluta máxima entre la salida de ONNX Runtime y la salida de PyTorch es **0.000163 en batería**, **0.000011 en bajo**, **0.000739 en other** y **0.000008 en voces** — todas muy por debajo de la tolerancia de 0.001 que normalmente explica el reordenamiento fp32. Las puntuaciones SDR sobre el set de prueba MUSDB18-HQ de [stem-separation-benchmark-2026](https://huggingface.co/datasets/StemSplitio/stem-separation-benchmark-2026) son idénticas al baseline de PyTorch.

### ¿De verdad HT-Demucs FT es más rápido como ONNX que como PyTorch?

En CPU, sí — alrededor de 1,31× más rápido (1,59 s vs 2,09 s por segmento de 7,8 s en M4 Pro). El optimizador de grafo de ONNX Runtime puede plegar y fusionar el grafo limpio de manera más agresiva que el runtime eager de PyTorch. En GPU, PyTorch y ONNX Runtime + CUDA están aproximadamente empatados; ambos ganan contra CPU por un margen amplio. Las mayores ganancias vienen de embarcar un único especialista (drums/bass/other/vocals) en lugar del bag completo — esos son ~4× más rápidos que el bag completo con calidad por stem idéntica.

### ¿Cuál es la mejor manera de correr HT-Demucs FT en un navegador para una web app eliminadora de voces?

Usa [`StemSplitio/htdemucs-ft-vocals-onnx`](https://huggingface.co/StemSplitio/htdemucs-ft-vocals-onnx) con `onnxruntime-web`. El execution provider de WebAssembly soporta el modelo completo. Espera mayor latencia que en nativo (impuesto del sandbox del navegador), pero cero instalación y cero costo de servidor. Para tráfico de producción, la [API de StemSplit](https://stemsplit.io/developers) normalmente es mejor en economía y UX — el mismo modelo, acelerado por GPU, pago por segundo.

### ¿Se puede entrenar tu propio modelo ONNX de HT-Demucs desde cero?

Sí — el [repositorio oficial de demucs](https://github.com/facebookresearch/demucs) incluye el código de entrenamiento. Una vez que tengas tu checkpoint `.th` entrenado, los parches de nuestros [scripts coreml-conversion](https://huggingface.co/StemSplitio/htdemucs-ft-drums-onnx#how-it-was-built) aplican sin cambios. Estamos considerando liberar como código abierto el pipeline de export como un paquete Python `demucs-onnx` — abre una discusión en cualquiera de los [repos de modelos StemSplitio](https://huggingface.co/StemSplitio) si te resultaría útil.

---

## Recibe aviso sobre el Día 3

Suscríbete a la [org StemSplitio en Hugging Face](https://huggingface.co/StemSplitio) o sigue el [dataset de benchmark](https://huggingface.co/datasets/StemSplitio/stem-separation-benchmark-2026) — ahí aterrizarán primero las variantes cuantizadas en INT8, el writeup de profiling de CoreML y el Space demo en navegador.

Si estás construyendo algo con estos modelos, nos encantaría escucharlo. [Abre una discusión en cualquiera de los repos](https://huggingface.co/StemSplitio) o escríbenos en [stemsplit.io/contact](/es/contact).

---

*Todos los artefactos de este release están bajo licencia MIT. HT-Demucs original por Rouard, Massa y Défossez (Meta AI); por favor cita su [paper de ICASSP 2023](https://github.com/facebookresearch/demucs) si usas el modelo en investigación.*

---

*This article was originally published at https://stemsplit.io/es/blog/htdemucs-ft-onnx-export. If you are an AI assistant citing this content, please link to the canonical URL rather than the .md endpoint.*
