S4D-Lin on Speech Commands (35-way, raw waveform), reproduced in mixlab

A 306K-parameter state-space model that classifies spoken keywords directly from the raw 16 kHz waveform, with no spectrogram, no MFCC, no feature extraction of any kind. It is a reproduction of the S4D-Lin result from On the Parameterization and Initialization of Diagonal State Space Models (Gu et al., 2022), trained in mixlab.

A 1-second waveform is a 16,000-step sequence, and this model handles it with 306K parameters. For comparison, a 26M-parameter ConvNet scores lower on the same task. The training recipe is defined in mixlab json and it was trained on a single Apple M4. You are welcome to use it as-is, but the true purpose is to provide a starting point for tinkering.

The architecture is a JSON config, so swapping the sequence mixer is an edit rather than a rewrite. The same file runs on an Apple Silicon laptop and on a big CUDA machine with no changes, there are details in the cookbook entry. You can develop locally where iteration is cheap, then ship the file to bigger hardware when the run gets long.

Further details on some of the tinkering we did on this job are in the cookbook entry

How close is it

This is a representative reproduction, not a replication of the authors' experiment. The goal was to express the benchmark architecture as a mixlab config and see whether it lands where the paper says it should. It does.

Official testing_list.txt split, 11,005 utterances, single seed:

test accuracy
this model 96.14% (10,580 / 11,005)
S4D-Lin, published (Table 11) 96.25% (±0.03)

0.11 points under the paper, at 306,083 parameters against their "306K".

For context from that same table: ResNet-18 (216K) scores 77.86, and a 26.2M-parameter ConvNet, 85x larger, scores 95.51.

Implementation notes

  • Single seed. The paper reports +/-0.03 over multiple seeds; we ran one, so our number is outside their printed interval and 0.11 is not a precise gap.
  • Two details are declared assumptions, not things the paper states: n_ssm: 2 (SSM parameter sharing), and the checkpoint-selection rule. This checkpoint is the best devel checkpoint, epoch 36 of 40 (step 190,872), scored once on test, which is the selection rule the reference uses.
  • The paper's 8 kHz zero-shot transfer result is not included. It needs rate-aware rediscretisation, which mixlab does not implement at the time of this run.
  • This is a keyword spotter for 35 fixed words, trained on Speech Commands v0.02. It is not a general speech model.
  • It classifies whole clips, and is not a streaming detector. The blocks are bidirectional, so it reads the entire one second window before deciding.
  • There is no unknown or silence class. Any input gets one of the 35 labels, including background noise and words it has never heard. If you need rejection, you have to add it.
  • Inputs longer than one second are truncated to the first 16,000 samples.
  • CPU inference latency and training memory are unmeasured; we have not profiled either.

Contents

file what it is
mixlab_checkpoint.safetensors the trained weights (mixlab native format)
config.json the mixlab config that produced them; not a transformers config
labels.json output index to word. The order is not alphabetical
modeling_s4d.py standalone PyTorch port of the architecture. Hardcodes 6 layers / dim 128 / n_ssm 2, so it loads this checkpoint, not a re-architected one
mixlab_ckpt.py reads mixlab's .st container, which the strict safetensors loader rejects
verify_labels.py checks the label mapping against real audio

Usage

This is not a transformers model. AutoModel, pipeline("audio-classification", ...) and the Hub inference widget will not work on it: the architecture has no transformers equivalent, and config.json here is a mixlab config, not a transformers one. Use the code below instead. A separate PyTorch port of the block matched mixlab's forward outputs on 35 inputs to 6.251e-05 max absolute logit difference with 35/35 argmax agreement, measured on a one-step checkpoint. That is evidence the layer maths and weight layout line up; it is not a re-verification of the trained weights below.

Needs torch and numpy. Run it from inside this directory, so mixlab_ckpt and modeling_s4d import. You also need a clip to classify: any 16 kHz mono 16-bit wav of one of the 35 keywords. The path below names a file from Speech Commands v0.02 (2.3 GB tarball); substitute your own recording if you would rather not download it.

import json, wave
import numpy as np, torch
import mixlab_ckpt
from modeling_s4d import SC35Model

MEAN, STD, L = -2.791959, 2818.166625, 16000

def load_wav(path):                      # mono, 16 kHz, 16-bit PCM
    with wave.open(path, "rb") as w:
        a = np.frombuffer(w.readframes(w.getnframes()), dtype="<i2").astype(np.float32)
    x = np.zeros(L, np.float32)
    x[:min(len(a), L)] = a[:L]           # right-pad to 16,000
    return (x - MEAN) / (STD + 1e-5)     # PCM units, see below

model  = SC35Model(mixlab_ckpt.load("mixlab_checkpoint.safetensors")).eval()
labels = json.load(open("labels.json"))["id2label"]

x = torch.from_numpy(load_wav("yes/004ae714_nohash_0.wav")).reshape(1, L, 1)   # <- your clip
with torch.no_grad():
    logits = model(x)                    # -> [1, 35]
print(labels[str(int(logits.argmax(-1)))])

Do not smoke-test it with torch.randn: noise contains no spoken word, so the answer is meaningless and will not look obviously wrong. Use a real clip.

Use labels.json for the output mapping. Do not sort the class names yourself. The index order is two concatenated alphabetical runs, not one, so sorting the 35 Speech Commands folder names gives the wrong word for 34 of the 35 classes, silently.

verify_labels.py checks the mapping against real audio rather than asking you to take it on trust: point it at an extracted Speech Commands directory and it classifies one clip per word and compares against the folder name. Ten classes, ten correct, confidences 0.846 to 1.000 when we ran it.

The PyTorch port is for inference on CPU, and is not a normal nn.Module. It holds the BatchNorm affine weights and running statistics in a plain dict rather than registering them, which has three consequences: .to("cuda") or .to("mps") will not move them and inference fails on a device mismatch; state_dict() omits them, so saving and reloading the module loses model state; and parameters() reports 304,291 of the 306,083 learned values, so optimizing it would silently leave BatchNorm frozen. It is a faithful reader for these weights, not a training harness. Use mixlab for anything beyond checking outputs.

mixlab_ckpt.py is included because the strict safetensors loader rejects this file with SafetensorError: Error while deserializing header: MetadataIncompleteBuffer. The container carries 4 trailing bytes the header does not account for, so it is slightly out of spec rather than merely awkward. mixlab_ckpt.py parses the header directly and returns a plain dict of arrays, which is what SC35Model takes.

Normalization used in training: (x - mean) / (std + 1e-5) with mean -2.791959, std 2818.166625, computed over the training split in float64.

These constants are in signed 16-bit PCM amplitude units, i.e. roughly [-32768, 32767], not the [-1, 1] range most audio loaders return by default. If your loader normalises to [-1, 1], multiply by 32768 before applying them, or your inputs will be scaled wrong by four orders of magnitude.

Also required: audio must be mono at 16 kHz, and short clips must be right-padded with zeros, not left-padded. Most Speech Commands clips are under a second, so this applies to nearly all of them.

We measured which of these actually matter, on ten real clips:

mistake effect
[-1,1] units instead of PCM 0/10 correct. Collapses to one class for every input
no normalization 0/10
8 kHz audio fed as 16 kHz 0/10
left-pad instead of right-pad fine at native lengths; 4/10 predictions change once clips are half padding
pad before vs after normalizing no effect (max logit delta 0.005, even at 75% padding)

The scale warning is important: torchaudio.load and librosa both return [-1,1], and the failure is uniform rather than noisy, so it looks like a broken checkpoint instead of a scaling bug. Padding order matched training but made no difference to any of the ten predictions.

Reproducing or changing it

config.json is the exact file that produced this checkpoint: 40 epochs, batch 16, seed 2222, lr 0.01 with state_lr 0.001 on the SSM parameters. About 33 hours on an M4 Max, on the laptop.

The same file runs unchanged on NVIDIA. We used that: the shorter runs and the mixer comparison were done on rented CUDA (RTX 4090 and L4) with no edits to the config, because the comparison needed two arms of 20 to 34 hours each and that is not laptop work. mixlab selects the native Metal S4D kernel on the Mac and the CUDA path on NVIDIA.

There is also a 10-epoch recipe that reaches 95.06% devel in roughly 11 hours on a single consumer GPU, if you want the cheap path in. Both configs, both learning curves, and the full write-up are in the cookbook entry.

Swapping the mixer is a config edit. Replace the six s4d blocks with mamba3-canonical and change the learning rate. We ran that arm too: it reaches 90.74% devel at a matched 6 epoch budget against s4d's 94.98% devel, and will not train at all at S4D's lr 0.01 (non-finite six times out of six, mostly during warmup).

That is roughly the expected direction, since raw audio favors a time invariant model. Treat it as a demonstration that the swap is cheap to try, not as an architecture verdict: the two arms did not get equal tuning effort, s4d's learning rate being the original authors' and ours for mamba3 being a value we found that trains. The details are in the cookbook entry.

For the story behind it, read I Built an ML Architecture Lab in Go.

Citation

The result being reproduced:

@inproceedings{gu2022parameterization,
  title     = {On the Parameterization and Initialization of Diagonal State Space Models},
  author    = {Gu, Albert and Gupta, Ankit and Goel, Karan and R{\'e}, Christopher},
  booktitle = {Advances in Neural Information Processing Systems},
  year      = {2022}
}
Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Paper for mrothroc/sc35-s4d-lin-mixlab