The training repository
One repository does the work: ZFTurbo/Music-Source-Separation-Training, usually shortened to MSST. It trains, validates and runs inference on the same architectures that UVR and the separation web apps use.
Get the code
- Download the repository
Open the repository page, press the green Code button and choose Download as ZIP, then extract it anywhere convenient.
- Pick the model you want to train
Scroll to the Pre-trained models section, open the List of Pre-trained models, and grab the Config and the Weights for your chosen architecture — for example a Mel-Band Roformer, or Kimberley Jensen's Roformer as a base.
- Place the two files correctly
The checkpoint goes into a folder named
results; the YAML goes at the repository root, next totrain.py,valid.pyandinference.py.
Music-Source-Separation-Training/ ├── train.py ├── valid.py ├── inference.py ├── requirements.txt ├── config_vocals_mel_band_roformer.yaml ← the config, at the root └── results/ └── model_mel_band_roformer.ckpt ← the weights
Config vs. checkpoint
.yamleditableEverything about the model and how it trains: architecture dimensions, chunk size, learning rate, epochs, which instruments to target.
- Where most of your tuning happens.
- Lives at the repo root, next to
train.py.
.ckptmillions of numbersThe model itself. You never edit it — you load it, train further, and let the trainer write new checkpoints.
- Lives in
results/. - One file, plus its matching config.
If you have used UVR before, you already know these two files: this is exactly the pairing it ships with.
- Some browsers display a config file instead of downloading it. If that happens, open it in a text editor (Notepad++ works fine), copy the contents, and save the file yourself with a
.yamlextension. - Every model published with the repository is open-source. Check the licence of the specific checkpoint before you build anything commercial on top of it.
Mel-Band and BS Roformers train with an additional spectrogram loss rather than a pure waveform loss. That is why you never need to add one manually unless you are deliberately training for fullness — see Chapter 10.
Commands & arguments
Everything runs through Python scripts, so everything is a command with arguments. Copy the templates, replace the placeholders, and keep them somewhere you can paste from.
On Windows, run these commands in a Command Prompt or PowerShell opened as administrator. Package installs and some file operations will fail silently or half-way without it.
Installation
# 1. Install PyTorch — pick the command for your CUDA version # https://pytorch.org/get-started/locally/ pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124 # 2. Install the repository requirements (from the repo folder) cd C:\path\to\Music-Source-Separation-Training pip install -r requirements.txt # 3. Install anything the previous step skipped pip install name-of-missing-package
The training command
This is the command you will run the most. Every highlighted value has to be replaced with your own paths and choices.
python train.py --model_type mel_band_roformer --config_path config_vocals.yaml --results_path results/ --data_path C:\path\to\dataset --dataset_type 2 --num_workers 4 --device_ids 0 --start_check_point results/model.ckpt --valid_path C:\path\to\validation --metric_for_scheduler sdr --metrics sdr fullness bleedless
The valid options for --metrics and --metric_for_scheduler are listed in the argument tables below; what each one measures is explained in Appendix B.
Paste the command into Notepad, edit the values there, and save it as run.bat in the repository root. From then on you double-click it, or type .\run.bat in the shell. No more re-typing the argument list every time — and no more typos in paths.
Inference: using the model
Once you have a checkpoint you are happy with, inference separates files with it.
python inference.py --model_type mel_band_roformer --config_path config_vocals.yaml --start_check_point results/model.ckpt --input_folder input --store_dir separation_results
Create two folders first: input, where you drop the files you want separated, and separation_results, where the script writes the stems.
On rented instances you may hit an OpenBLAS threading error during training. Fix it for the current session with:
export OPENBLAS_NUM_THREADS=1It only applies to the terminal session you run it in — see Chapter 12 for where it fits in the cloud sequence.
Argument reference — training
| Argument | What it does |
|---|---|
--model_type | Architecture to train: mdx23c, htdemucs, segm_models, mel_band_roformer, bs_roformer, swin_upernet, bandit. |
--config_path | Path to the YAML config for that model. |
--start_check_point | Initial checkpoint to start from (fine-tuning). Omit it to train from scratch. |
--results_path | Folder for outputs — both .ckpt files and run metadata. |
--data_path | Path to your dataset folder. |
--dataset_type | 1, 2, 3 or 4 — which layout your data follows. See the dataset types documentation. |
--valid_path | Path to the validation dataset folder. |
--num_workers | How many CPU workers load and pre-process audio in parallel. |
--pin_memory | Keeps host memory page-locked so transfers to the GPU are faster; worth enabling with several workers. |
--seed | Seeds the randomness in the run — experiment with values for reproducibility. |
--device_ids | List of GPU IDs to use; normally just 0. |
--use_multistft_loss | Multi-STFT loss (spectrogram based) — the setting behind fullness models. |
--use_mse_loss | Default MSE loss, waveform based. |
--use_l1_loss | L1 loss, waveform based. |
--wandb_key | Weights & Biases API key, for live run dashboards. |
--pre_valid | Runs a validation pass before training begins. |
--metrics | Metrics to compute each validation pass: sdr, l1_freq, si_sdr, neg_log_wmse, aura_stft, aura_mrstft, bleedless, fullness. |
--metric_for_scheduler | Which metric the learning-rate scheduler watches (same options as above). |
--train_lora | Train with LoRA (Low-Rank Adaptation) instead of full weights. |
--lora_checkpoint | Starting checkpoint for LoRA weights. |
Argument reference — inference
| Argument | What it does |
|---|---|
--model_type | Architecture, matching the checkpoint you loaded. |
--config_path | Path to the config file. |
--start_check_point | The checkpoint to run. |
--input_folder | Folder holding the mixtures you want separated. |
--store_dir | Where the resulting stems are written. |
--draw_spectro | Also renders spectrogram images of the results; the value sets how many seconds of the track to draw (default 0, off). |
--device_ids | List of GPU IDs to use. |
--extract_instrumental | Inverts the vocal output to produce an instrumental (and vice versa). |
--disable_detailed_pbar | Turns off the detailed progress bar. |
--force_cpu | Forces CPU inference even when CUDA is available. |
--flac_file | Write FLAC instead of WAV. |
--pcm_type | Bit depth for FLAC output: PCM_16 or PCM_24. |
--use_tta | Test-time augmentation (polarity and channel inversion). Roughly triples runtime, reduces noise and slightly improves quality. |
--lora_checkpoint | Starting checkpoint for LoRA weights. |
Configuring the YAML
The config is where the run is actually shaped. Its defaults are sane, but the training block is the part worth understanding — and the part worth experimenting with.
A config has four sections: audio, model, training and inference. You rarely touch the first two, with one exception: the audio chunk size. Which block owns the rest of the spectrum settings — n_fft, hop_length, dim_f — depends on the architecture, and there is a table of who owns what below the calculator.
chunk_size — the one audio setting you will change
The trainer reads the window itself from audio.chunk_size, in samples — that key is the one every architecture family reads. Configs also describe the same window in frames (dim_t), and because every STFT here runs with center=True the two are one value in two units:
chunk_size = (dim_t − 1) × hop_length
| dim_t | chunk_size (samples) | Audio window |
|---|---|---|
256 | 112455 | 2.55 s |
801 | 352800 | 8.00 s |
1101 | 485100 | 11.00 s |
1333 | 587412 | 13.32 s |
The conversions above assume hop_length: 441 — the step every published Roformer in this guide uses. A different hop gives a different sample count for the same dim_t, because the frame count is what follows from the window: floor(chunk_size ÷ hop_length) + 1. So the hop decides both the window and the memory, and it belongs to the model rather than to you — the hop_length section below the calculator has the details. Smaller chunks train faster and use less VRAM; larger chunks teach the model longer musical context. This is the main dial you change when a run will not fit.
dim_t); the trainer reads samples (chunk_size). Bars are scaled to a 16-second excerpt, so what you are looking at is how much musical context each setting sees.Chunk size & VRAM calculator
Type in your numbers and see what window you are actually asking for, and whether your card can hold it. Nothing is sent anywhere — the arithmetic runs in the page.
audio.chunk_sizemodel.stft_hop_lengthmodel.stft_n_fftwhere these settings live · Roformer—
—
—
How this is derived. The window is chunk_size — the number the YAML holds and the one key every architecture family reads. Every STFT in this ecosystem runs with center=True, so a window of chunk_size samples becomes floor(chunk_size ÷ hop_length) + 1 frames, and across it a whole number of frames is chunk_size = (dim_t − 1) × hop_length. The frame count is what the model is asked to attend over, and the memory figure is anchored on frames and batch size — one data point, a stereo Roformer at batch_size: 4 and dim_t: 1333 (13.32 s at hop_length: 441) filling a 140 GB H200. That is why the hop moves this number and dim_t follows it: at a fixed window, half the hop is twice the frames. The sample rate reaches the estimate only through frames — frames per second = rate ÷ hop_length — so under the rate ÷ 100 rule, where the hop halves with the rate, 100 frames per second stays 100 frames per second and halving the rate changes nothing; under a fixed hop it halves the frames, and the window it covers doubles until chunk_size is halved to match. The anchor is a Roformer, so pick a different architecture and the figure is that measurement extrapolated — the guide has no mdx23c, DTTNet or SCNet run to check it against. Real attention cost also grows faster than the frame count does, so wide windows land worse than this predicts; use_amp and use_torch_checkpoint move it the other way, and dim_f — the number of mel bands — is a second lever this tool does not model. Choosing Mono (1 ch) halves the sequence the transformer attends over and the tool halves its figure with it, the least certain step here. Use it to decide what to try first, not to predict an exact number.
hop_length and n_fft — which block of the config owns them
hop_length is the step between STFT frames, and it is not a free preference: it is baked into the spectrogram the weights were trained on, so change it and the checkpoint you are fine-tuning no longer matches its own input. Where you change it, though, depends on the architecture — and this is the mistake that costs people an afternoon, because a Roformer config and an mdx23c config both have an n_fft and a hop_length, in different blocks, and only one of the two counts in each.
| Architecture | n_fft / hop_length | Spectrogram width | Window |
|---|---|---|---|
| Roformer bs_roformer, mel_band_roformer, bs_conformer, bs_mamba2 | model.stft_n_fftmodel.stft_hop_length (and stft_win_length) | model.freqs_per_bands or model.num_bands, at model.sample_rate | audio.chunk_size |
| mdx23c · DTTNet | audio.n_fftaudio.hop_length | audio.dim_f (the model crops to it) | audio.chunk_sizeaudio.dim_t |
| SCNet | model.nfftmodel.hop_size (and win_size) | derived from model.nfft | audio.chunk_size |
The mechanism is plain in the source. mel_band_roformer and bs_roformer are constructed as MelBandRoformer(**config.model), and SCNet as SCNet(**config.model) — the model block is the only thing they see, and their STFT parameters are named there. mdx23c and DTTNet go the other way and are handed the whole audio block (STFT(config.audio)), so for them audio.n_fft, audio.hop_length and audio.dim_f are the real ones. What every family reads from audio is chunk_size, num_channels, sample_rate and min_mean_abs.
Two rules account for every config in this ecosystem, and which one is right depends entirely on where your config came from:
| Real config | n_fft | hop_length | Frame | Rule |
|---|---|---|---|---|
| Kimberley Jensen · vocals mel-band | 2048 | 441 | 10.0 ms | rate ÷ 100 |
| viperx · mel-band ep 3005 | 2048 | 441 | 10.0 ms | rate ÷ 100 |
| pcunwa · Mel-Band-Roformer-small | 2048 | 441 | 10.0 ms | rate ÷ 100 |
| MSST template · mel-band / BS Roformer | 2048 | 512 | 11.6 ms | n_fft ÷ 4 |
| MSST template · DTTNet | 4096 | 1024 | 23.2 ms | n_fft ÷ 4 |
| MSST template · mdx23c | 8192 | 1024 | 23.2 ms | n_fft ÷ 8 |
Every published Roformer checkpoint this guide tells you to fine-tune uses 441 — a tenth of the sample rate, so a frame lands exactly on 10 ms of audio at any full-band rate. The blank templates that ship with MSST, and the DTTNet config, use n_fft ÷ 4 — the ordinary 75 % overlap you would pick for a fresh STFT — and mdx23c halves that again. So “keep hop_length = n_fft ÷ 4” is good advice for a config you are writing from scratch and the wrong advice for a checkpoint you are continuing: load Kimberley Jensen's model with hop_length: 512 and its learned features no longer line up with the input you feed them.
That is why the calculator's hop rule is a choice rather than a constant, and why it carries an n_fft field: both STFT rules are measured from the window, and dim_f — the width the transformer actually reads — is half of it, so n_fft: 2048 is dim_f: 1024, 4096 is 2048. A wider window is therefore also a wider spectrogram and a heavier model, which is the one part of the memory estimate the calculator still does not scale for.
A Roformer config carries hop_length twice — once under audio, once as model.stft_hop_length — and the shipped ones annotate the first copy # don't work (use in model). viperx's BS-Roformer config says exactly that about both audio.hop_length: 441 and audio.dim_t: 801. For mdx23c and DTTNet there is no second copy to get wrong: edit audio.hop_length or edit nothing. Setting both copies to the same number keeps a config readable either way.
Because the hop decides how many frames the window divides into, and the frame count is what the model attends over, the hop is a memory setting as much as a spectral one. At a fixed chunk_size the relationship is exact: frames = floor(chunk_size ÷ hop_length) + 1, so chunk_size: 587412 is 1333 frames at hop_length: 441 and 1148 frames at 512 — the same 13.3 seconds of audio, seen through far fewer, wider steps. That is the direction the calculator's hop rule moves in: it holds the window and lets the frame count follow. Run it the other way — hold dim_t and let the window grow — and dim_t: 801 is an 8.00 s window at 441 but a 9.29 s one at 512. Which of the two you want depends on what you are doing: keeping chunk_size is how you switch conventions without changing the music the model hears, and keeping dim_t is what a fine-tune does when it inherits the frames its checkpoint was trained on.
Sample rate — the band each choice keeps
The sample rate is not a memory dial, but it does decide something you cannot undo later: everything above half the rate — the Nyquist frequency — is gone before training starts. A rate of 22 050 Hz keeps 0–11 025 Hz and throws away cymbals, sibilance and guitar harmonics: free for a bass model, fatal for a drum model.
| Rate | Keeps up to | What it costs | Fair target |
|---|---|---|---|
44 100 | 22 050 Hz | Nothing — and it is what every Roformer in this guide was trained at | Anything |
48 000 | 24 000 Hz | Nothing, but a 44.1 kHz checkpoint expects that rate's filterbank | Anything, if you train from scratch |
22 05044 100 ÷ 2 | 11 025 Hz | Cymbals, sibilance, synth harmonics — and the top of a guitar DI's pick scrape | Bass, kick, a guitar DI — low-band targets only |
24 00048 000 ÷ 2 | 12 000 Hz | The same, 975 Hz wider | Bass, kick, a guitar DI — low-band targets only |
Halving the rate halves what the loader reads and what the STFT transforms, and whether it halves the memory depends entirely on the hop — because frames = chunk_size ÷ hop_length + 1. At a fixed chunk_size a halved rate makes the same chunk twice as long, so you get twice the frames and the estimate doubles; halve chunk_size at the same time and it comes straight back to where it started. That is exactly what hop_length = rate ÷ 100 arranges without you thinking about it: frames per second = rate ÷ hop_length is 100 at every full-band rate, so the frame count follows the seconds of audio and nothing else. Under a fixed hop it does not: at hop_length: 512 a halved rate halves the frames per second, so the same 13.3 s window arrives as 574 frames instead of 1148 — half the memory, and each frame now steps 23.2 ms instead of 11.6 ms. One arithmetic wrinkle, and only under the rate ÷ 100 rule: 22 050 ÷ 100 is not a whole number, so the hop lands on 221 rather than 220.5 — 10.02 ms per frame instead of 10, which moves the window by tens of milliseconds — while 24 000 ÷ 100 = 240 lands exactly. Switch the rule to n_fft ÷ 4 and the wrinkle disappears: 512 at any rate.
No model in this guide's ecosystem is published below 44.1 kHz, and the mel filterbank is built from the sample rate — change the rate and a checkpoint's learned features no longer line up with the input they are being fed. If you try it, change audio.sample_rate, audio.hop_length and the sample_rate in the loss_multistft block together, then judge the run on validation metrics against an untouched full-band baseline.
Channels — stereo is architecture, not file format
Every Roformer published for this ecosystem is stereo, and the config says so twice: audio.num_channels: 2 and model.stereo: true. Those lines describe the model, not your files. The band-split front end turns each mel band's two channels into one longer sequence for the transformer to attend over, which is why a stereo run costs more than a mono one — and why the calculator now asks which you are training.
The distinction matters in both directions. If your source material is mono, you have not made training any cheaper: a stereo model still expects two channels, so the mono file arrives duplicated. And if you genuinely want a mono model, you change the architecture — num_channels: 1 together with stereo: false, trained on mono mixtures and targets — which halves the sequence and so most of the activation memory.
A checkpoint trained with stereo: true will not load into a stereo: false model — the shapes differ, so you are training from scratch, not fine-tuning. Mono suits material that is genuinely single-channel (a bass DI, a close-mic drum, a mono room recording) and is a bad deal wherever the stereo image carries information: wide guitars, spacious drums, reverb tails. Judge it on the same validation metrics as any other architecture change.
The training block
training: batch_size: 1 gradient_accumulation_steps: 1 grad_clip: 0 instruments: - vocals - other lr: 1.0e-05 patience: 2 reduce_factor: 0.95 target_instrument: vocals num_epochs: 1000 num_steps: 1000 optimizer: adam ema_momentum: 0.999 q: 0.95 coarse_loss_clip: false other_fix: true use_amp: true use_torch_checkpoint: true augmentation: false augmentation_type: null use_mp3_compress: false augmentation_mix: false augmentation_loudness: false augmentation_loudness_type: 1 augmentation_loudness_min: 0 augmentation_loudness_max: 0
| Key | Meaning |
|---|---|
batch_size | How many audio samples are used to update the weights. Higher values need more VRAM. |
gradient_accumulation_steps | Simulates a larger batch without the VRAM cost: effective batch becomes batch_size × steps. Weights are not updated on the in-between steps. |
grad_clip | Gradient clipping threshold. 0 leaves it off. |
instruments | The stems inside your dataset. Must match the folders you actually created. |
lr | Learning rate. 1.0e-05 is the common default; 5.0e-06 is the other frequent choice for fine-tuning. |
patience | How many epochs the scheduler waits without improvement before dropping the learning rate. |
reduce_factor | How much the learning rate is multiplied by when it drops (0.95 = −5%). |
target_instrument | The stem the model focuses on. Set to null when you are declaring stems explicitly. |
num_epochs | How many times the full num_steps cycle runs. Can be changed when resuming from a checkpoint. |
num_steps | Weight updates per epoch (divided by gradient accumulation). Validation runs each time this count is reached. Also resumable. |
optimizer | Optimiser for training; adam is the default. |
ema_momentum | Momentum of the exponential moving average of the weights — a smoother, usually more stable copy of the model. |
q | Quantile-style parameter used by the loss weighting. |
coarse_loss_clip | Clips extreme loss values early in training to avoid a first-epoch blow-up. |
other_fix | Needed on multisong datasets to check that other really is an instrumental. |
use_amp | Mixed-precision (float16) training. Usually should be true — it is a large speed and VRAM win. |
use_torch_checkpoint | Gradient checkpointing: trades a little speed for notably lower VRAM use. Enable when you hit out-of-memory errors. |
Augmentation options
augmentation— master switch for audiomentations and pedalboard augmentations.augmentation_type— which augmentation profile to use.augmentation_mix— mixes several stems of the same type with some probability, useful when your dataset is small.augmentation_loudness,_type,_min,_max— randomly change the loudness of each stem, with the range you specify.use_mp3_compress— deprecated; leave itfalse.
num_overlap in the inference section of the config controls how many passes are averaged at inference time. During training it only costs time — always set it to 1 for the training run, and raise it later when you actually separate audio.
There is no magic config. Expect to find your numbers by iterating: change one thing, train, listen, compare metrics. Add use_torch_checkpoint if you genuinely need the VRAM saving — that is, when you are seeing CUDA_OutOfMemory errors.