StemSplit commited on
Commit
551f275
·
verified ·
1 Parent(s): 1b35123

Initial release: htdemucs_ft drums specialist (PyTorch handler)

Browse files
Files changed (3) hide show
  1. README.md +170 -0
  2. handler.py +89 -0
  3. requirements.txt +5 -0
README.md ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language: en
3
+ license: mit
4
+ library_name: demucs
5
+ pipeline_tag: audio-to-audio
6
+ tags:
7
+ - demucs
8
+ - stem-separation
9
+ - source-separation
10
+ - drums-isolation
11
+ - music
12
+ - htdemucs
13
+ - audio-to-audio
14
+ - drum-extraction
15
+ - drum-isolation
16
+ - beat-extraction
17
+ - sample-extraction
18
+ datasets:
19
+ - StemSplitio/stem-separation-benchmark-2026
20
+ inference: false
21
+ ---
22
+
23
+ # HT-Demucs FT — Drums Specialist (PyTorch)
24
+
25
+ Drum isolation specialist from HT-Demucs FT, ~1/4 the size of the full ensemble.
26
+
27
+ This is sub-model 0 of the 4-bag `htdemucs_ft` ensemble by
28
+ [Défossez et al. (Meta AI)][demucs-repo], extracted as a standalone
29
+ ~160 MB model. It produces the **drums** stem with the same quality as
30
+ the full ensemble (median SDR **10.11 dB** on MUSDB18-HQ — 2nd (close behind mdx_extra_q at 11.49) of all
31
+ models in our 2026 benchmark) at roughly 1/4 the compute cost.
32
+
33
+ > Want all 4 stems in one request? Use the full ensemble:
34
+ > [`StemSplitio/htdemucs-ft-pytorch`](https://huggingface.co/StemSplitio/htdemucs-ft-pytorch)
35
+ >
36
+ > Want a hosted REST API with credits and a dashboard? Use the
37
+ > [**StemSplit API**](https://stemsplit.io/developers).
38
+
39
+ ---
40
+
41
+ ## Why this model
42
+
43
+ | Property | This model | Full `htdemucs_ft` bag |
44
+ |---|---|---|
45
+ | Disk size | **~160 MB** | ~640 MB |
46
+ | Per-3-min-song latency (M4 Pro MPS) | **~22 s** (RTF 0.12) | ~47 s (RTF 0.26) |
47
+ | Drums SDR on MUSDB18-HQ | **10.11 dB** | 10.11 dB *(identical — the bag's `drums` output IS this sub-model's output)* |
48
+ | Other stems returned | None (focused) | All 4 |
49
+
50
+ If you only need the drums stem in production, this is **strictly faster and
51
+ smaller** than the full ensemble with identical drums quality —
52
+ **~2.6× faster wall time** in our smoke tests on M4 Pro MPS.
53
+
54
+ ---
55
+
56
+ ## Common use cases
57
+
58
+ - **Drum sample extraction** — rip clean drum loops and one-shots from existing tracks
59
+ - **Beat transcription / MIDI** — feed the drum stem to onset/beat detectors and drum transcribers
60
+ - **Music production isolation** — rebalance or replace drum bus on existing mixes
61
+ - **Sample-pack generation** — automate drum-pack creation from a back-catalogue
62
+
63
+ ---
64
+
65
+ ## Quick start (Python)
66
+
67
+ ```python
68
+ import base64, io, soundfile as sf
69
+ from huggingface_hub import InferenceClient
70
+
71
+ with open("your-song.mp3", "rb") as f:
72
+ audio_b64 = base64.b64encode(f.read()).decode()
73
+
74
+ client = InferenceClient(model="StemSplitio/htdemucs-ft-drums-pytorch")
75
+ result = client.post(json={"inputs": audio_b64})
76
+
77
+ wav, sr = sf.read(io.BytesIO(base64.b64decode(result["drums"])))
78
+ sf.write("out_drums.wav", wav, sr)
79
+ ```
80
+
81
+ Or run locally without Hugging Face at all:
82
+
83
+ ```python
84
+ import torch, soundfile as sf
85
+ from demucs.apply import apply_model
86
+ from demucs.audio import convert_audio
87
+ from demucs.pretrained import get_model
88
+
89
+ bag = get_model("htdemucs_ft")
90
+ model = bag.models[0].eval() # the drums specialist
91
+ wav, sr = sf.read("your-song.mp3", dtype="float32", always_2d=True)
92
+ wav = torch.from_numpy(wav.T).contiguous()
93
+ wav = convert_audio(wav, sr, bag.samplerate, bag.audio_channels).unsqueeze(0)
94
+
95
+ with torch.no_grad():
96
+ stems = apply_model(model, wav, device="mps" if torch.backends.mps.is_available() else "cpu")[0]
97
+
98
+ # bag.sources == ["drums", "bass", "other", "vocals"]; pick the drums row
99
+ sf.write("out_drums.wav", stems[bag.sources.index("drums")].T.numpy(), bag.samplerate)
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Deploy on Hugging Face Inference Endpoints
105
+
106
+ Click **Deploy → Inference Endpoints** above, pick a GPU instance, and HF
107
+ will spin up a container running [`handler.py`](handler.py).
108
+
109
+ | Hardware | Latency for 3-min song |
110
+ |---|---:|
111
+ | NVIDIA L4 | ~3 s |
112
+ | NVIDIA T4 small | ~7 s |
113
+ | CPU x4 (basic) | ~48 s |
114
+
115
+ (Roughly 2.6× faster than the full-bag latency, since we run only this
116
+ specialist sub-model. Cloud GPU numbers extrapolated from M4 Pro measurements.)
117
+
118
+ ```bash
119
+ curl -X POST https://<your-endpoint>.endpoints.huggingface.cloud \
120
+ -H "Authorization: Bearer $HF_TOKEN" \
121
+ -H "Content-Type: application/json" \
122
+ -d "{\"inputs\": \"$(base64 < your-song.mp3)\"}"
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Try it in your browser, no code
128
+
129
+ - [StemSplit](https://stemsplit.io)
130
+ - [StemSplit API](https://stemsplit.io/developers)
131
+ - [Developer docs](https://stemsplit.io/developers/docs)
132
+ - [API reference](https://stemsplit.io/developers/reference)
133
+
134
+ ---
135
+
136
+ ## Related models from StemSplit
137
+
138
+ | Repo | Stem | When to use |
139
+ |---|---|---|
140
+ | [`htdemucs-ft-pytorch`](https://huggingface.co/StemSplitio/htdemucs-ft-pytorch) | all 4 | When you need vocals + drums + bass + other in one request |
141
+ | [`htdemucs-ft-vocals-pytorch`](https://huggingface.co/StemSplitio/htdemucs-ft-pytorch) | vocals | Best vocal SDR in our benchmark (9.19 dB) — karaoke, acapella |
142
+ | [`htdemucs-ft-drums-pytorch`](https://huggingface.co/StemSplitio/htdemucs-ft-drums-pytorch) | drums | Drum extraction, beat transcription, sample-pack creation |
143
+ | [`htdemucs-ft-bass-pytorch`](https://huggingface.co/StemSplitio/htdemucs-ft-bass-pytorch) | bass | Bassline transcription, mix rebalancing |
144
+ | [`htdemucs-ft-other-pytorch`](https://huggingface.co/StemSplitio/htdemucs-ft-other-pytorch) | other / instrumental | Karaoke instrumentals, sample-flipping, music-bed extraction |
145
+
146
+ Full benchmark across every popular open-source separator:
147
+ [StemSplitio/stem-separation-benchmark-2026](https://huggingface.co/datasets/StemSplitio/stem-separation-benchmark-2026).
148
+
149
+ ---
150
+
151
+ ## License & attribution
152
+
153
+ This repo is **MIT-licensed**, matching the original HT-Demucs.
154
+
155
+ **Original authors (please cite if you use this model in research):**
156
+
157
+ ```bibtex
158
+ @inproceedings{rouard2023hybrid,
159
+ title = {Hybrid Transformers for Music Source Separation},
160
+ author = {Rouard, Simon and Massa, Francisco and D{\'e}fossez, Alexandre},
161
+ booktitle = {ICASSP},
162
+ year = {2023}
163
+ }
164
+ ```
165
+
166
+ - Original model: [`facebookresearch/demucs`][demucs-repo]
167
+ - Packaging by [StemSplit](https://stemsplit.io)
168
+ - Search keywords: drum extraction, isolate drums from song, drum stem extractor, AI drum separator
169
+
170
+ [demucs-repo]: https://github.com/facebookresearch/demucs
handler.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ HF Inference Endpoint handler for the HT-Demucs FT **drums** specialist.
3
+
4
+ This repo ships only sub-model 0 of the 4-bag htdemucs_ft ensemble
5
+ — the one trained to extract `drums`. ~160 MB on disk and ~1/4 the inference
6
+ cost of the full bag, with the same per-stem quality as our v1.1 benchmark
7
+ (median drums SDR = 10.11 dB).
8
+
9
+ If you need all 4 stems in one request, use the full ensemble:
10
+ https://huggingface.co/StemSplitio/htdemucs-ft-pytorch
11
+
12
+ Request shape:
13
+ POST /
14
+ Content-Type: application/json
15
+ { "inputs": "<base64-encoded audio bytes>" }
16
+
17
+ Response shape:
18
+ { "drums": "<base64 WAV>", "sample_rate": 44100, "duration_s": 123.4 }
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import base64
23
+ import io
24
+ from typing import Any
25
+
26
+ import numpy as np
27
+ import soundfile as sf
28
+ import torch
29
+ from demucs.apply import apply_model
30
+ from demucs.audio import convert_audio
31
+ from demucs.pretrained import get_model
32
+
33
+ # Which sub-model of the htdemucs_ft bag to ship + which output index is ours.
34
+ BAG_INDEX = 0
35
+ TARGET_STEM = "drums"
36
+
37
+
38
+ def _audio_to_b64_wav(audio: torch.Tensor, sample_rate: int) -> str:
39
+ np_audio = np.clip(audio.cpu().numpy().T, -1.0, 1.0)
40
+ buf = io.BytesIO()
41
+ sf.write(buf, np_audio, sample_rate, subtype="PCM_16", format="WAV")
42
+ return base64.b64encode(buf.getvalue()).decode("ascii")
43
+
44
+
45
+ class EndpointHandler:
46
+ def __init__(self, path: str = "") -> None:
47
+ # Load the full bag, then drop the other 3 sub-models so only the
48
+ # drums specialist stays in memory.
49
+ bag = get_model("htdemucs_ft")
50
+ self.model = bag.models[BAG_INDEX]
51
+ self.model.eval()
52
+ self.device = torch.device(
53
+ "cuda" if torch.cuda.is_available() else
54
+ "mps" if torch.backends.mps.is_available() else
55
+ "cpu"
56
+ )
57
+ self.model.to(self.device)
58
+ self.sample_rate = int(bag.samplerate)
59
+ self.audio_channels = int(bag.audio_channels)
60
+ self.sources = list(bag.sources) # ["drums","bass","other","vocals"]
61
+ self.target_index = self.sources.index(TARGET_STEM)
62
+
63
+ def __call__(self, data: dict[str, Any]) -> dict[str, Any]:
64
+ if "inputs" not in data:
65
+ return {"error": "Request body must include base64 audio under 'inputs'."}
66
+
67
+ try:
68
+ audio_bytes = base64.b64decode(data["inputs"])
69
+ wav_np, sr = sf.read(io.BytesIO(audio_bytes), dtype="float32", always_2d=True)
70
+ except Exception as e: # noqa: BLE001
71
+ return {"error": f"Could not decode audio: {type(e).__name__}: {e}"}
72
+
73
+ wav = torch.from_numpy(wav_np.T).contiguous()
74
+ wav = convert_audio(wav, sr, self.sample_rate, self.audio_channels)
75
+ wav = wav.unsqueeze(0).to(self.device)
76
+
77
+ with torch.no_grad():
78
+ # apply_model on a single Model (not a BagOfModels) is supported
79
+ # and runs only this specialist — 1/4 the cost of the full bag.
80
+ stems = apply_model(self.model, wav, device=str(self.device), progress=False)[0]
81
+ # stems: (n_sources, channels, samples). Only stems[target_index]
82
+ # is meaningful for this specialist — the other rows are weakly
83
+ # predicted by-products and should not be used.
84
+
85
+ return {
86
+ "drums": _audio_to_b64_wav(stems[self.target_index], self.sample_rate),
87
+ "sample_rate": self.sample_rate,
88
+ "duration_s": round(wav.shape[-1] / self.sample_rate, 3),
89
+ }
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ torch>=2.2,<2.6
2
+ torchaudio>=2.2,<2.6
3
+ demucs==4.0.1
4
+ numpy>=1.26,<2.0
5
+ soundfile>=0.12