File size: 7,078 Bytes
c7072e6 070765e c7072e6 070765e c7072e6 070765e c7072e6 070765e c7072e6 070765e c7072e6 070765e c7072e6 070765e c7072e6 070765e c7072e6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | ---
license: mit
base_model: microsoft/deberta-v3-base
language:
- en
library_name: onnx
tags:
- token-classification
- intent-classification
- onnx
- daw
- music-production
---
# MAGDA command model
Turns a typed request into a [MAGDA](https://github.com/Conceptual-Machines/magda-core)
DAW command, offline and on-device. "put @serum on a new track" becomes
`track(name="", new=true).fx.add(name="<serum>")`, which the DAW executes as one
undoable transaction.
Fine-tuned from `microsoft/deberta-v3-base`. English only.
## Scope: edits, not gestures
The command set covers **editing operations** — creating and naming tracks,
adding devices, building racks, clip and note operations, grouping, selection
by predicate. Deliberately excluded are real-time transport and mixer states
(play/stop, track mute, solo): those have keyboard shortcuts, and a model with
latency is strictly worse than the button. Queries ("what's the tempo") are
excluded too — they are answers, not edits.
Those requests are not merely unsupported; they are trained as an explicit
**abstain** class, so the model returns nothing rather than executing whichever
command happened to be nearest. A closed label set cannot decline unless
declining is itself a label.
Note that clip enable/disable *is* covered: it is a persistent property of the
project, unlike a mixer mute, and picking one clip out by name is exactly the
kind of thing typing beats clicking at.
## What it actually does
**It never writes the command.** It does perception only, in a single forward
pass, and emits two things:
1. which of 39 commands this is (one of which is "none of these")
2. a BIO tag per word saying what role that word plays
```
"can you drop @1176 onto the vocals"
O O O PLUGIN O O TRACK_NAME intent = add_plugin
```
Deterministic code then assembles the DSL from those labels. The model cannot
emit malformed output, because it does not emit output — a property a
generative model would have to buy back with constrained decoding.
Plugin references never reach the model as identities: the host rewrites every
`@mention` to an opaque `<alias>` token before tokenization and substitutes the
real one back by position. So an unseen plugin generalizes for free, and the
vocabulary does not grow with the plugin registry.
## Results
Scored as exact-match on the fully rendered command, not on tags.
| | in-distribution | **held-out phrasing** |
|---|---|---|
| previous model (51k-param conv net, from scratch) | 98.2% | **46.5%** |
| **this model** | 100% | **~90%** |
The two columns matter more than the numbers. The in-distribution set is
authored from the same case list the training templates come from, so a high
score there means the model recalls its templates. The held-out set is 68 cases
hand-written from the command list alone, in the register people actually type:
lowercase, filler, contractions, typos. That is the number that predicts
behaviour on input nobody has seen.
The previous model had no language prior — trained from scratch on synthetic
English, it named tracks after whichever word sat in the name position during
training:
```
"can you drop @1176 onto the vocals" -> track(name="Can You Drop @1176")...
"push Keys up to 2 dB" -> track(name="Push").track.set(volume_db=2)
"the bass needs @pro_q_3" -> track(name="Bass Needs").track.set(colour="None")
```
Politeness wrappers scored 10%. With a pretrained encoder they score 100%.
## Files
| file | size | what |
|---|---|---|
| `command_model.onnx` | 442 MB | encoder + intent head + slot head, one graph, opset 17 |
| `tokenizer.json` | 8 MB | HF fast tokenizer, including the `<alias>` special tokens |
| `maps.json` | 1 KB | intent and BIO tag id maps |
Inputs are `input_ids` and `attention_mask`, both `int64 [batch, seq]` with
dynamic sequence length. Outputs are `intent_logits [batch, 39]` and
`slot_logits [batch, seq, 20]`.
## Quantization warning
The embedding is int8; **the attention and FFN deliberately are not.**
| build | size | accuracy |
|---|---|---|
| fp32 | 736 MB | 96.5% |
| int8 embedding only (this) | 442 MB | 96.1% |
| int8 everything | 538 MB | **0.9%** |
DeBERTa-v3's disentangled attention is quantization-hostile. Running
`quantize_dynamic` over the whole graph produces a model that loads, runs at
full speed, and emits garbage. If you re-quantize this, re-measure it.
## Usage
Two details will cost you accuracy if you get them wrong.
**Words, not subwords.** The tags are per word, and the deterministic
reconstructor consumes per-word tags. Label the first subword of each word and
read the tag back at that same index. Reading tags positionally shifts every
tag after the first multi-piece word — it still produces plausible-looking
output, so it fails quietly.
**The alias tokens must stay atomic.** `<alias>` and `<alias.param>` are
registered as additional special tokens. If your tokenizer splits them into
`<`, `alias`, `>`, every plugin slot breaks.
```python
from transformers import AutoTokenizer
import onnxruntime as ort, numpy as np
tok = AutoTokenizer.from_pretrained("microsoft/deberta-v3-base", from_slow=True)
tok.add_special_tokens({"additional_special_tokens": ["<alias>", "<alias.param>"]})
sess = ort.InferenceSession("command_model.onnx")
words = ["put", "<alias>", "on", "a", "new", "track"]
enc = tok(words, is_split_into_words=True, return_tensors="np")
intent_logits, slot_logits = sess.run(None, {
"input_ids": enc["input_ids"].astype(np.int64),
"attention_mask": enc["attention_mask"].astype(np.int64),
})
```
A reference implementation in C++ (ONNX Runtime plus a hand-written
SentencePiece Unigram tokenizer) is in `magda/agents/command_model_onnx.cpp`
and `unigram_tokenizer.cpp` in the MAGDA repo.
## Training
24,000 template-generated English examples (no LLM paraphrasing in this
build), 4 epochs, lr 3e-5 for the encoder and 1e-3 for the two heads. Checkpoints are
selected on a hand-authored held-out set, never on the reported one — the
val split is drawn from the same templates as training and saturates at 100%
after one epoch, which makes it useless for ranking checkpoints.
## Limitations
- **English only.** The word splitter in front of the model is ASCII-only, so
non-English input reaches it as an empty sequence. This is a limitation of
the surrounding pipeline, not the encoder, which is multilingual-capable.
- **Plugin references need the `@` sigil.** `track with @serum` works;
`track with serum` does not, and degrades into an unrelated command rather
than failing cleanly.
- **Closed command set.** 38 commands plus an explicit abstain class. Anything
outside them returns nothing rather than being mapped onto the nearest
command — but abstain recall is not perfect, so an unusual out-of-scope
request can still land on a command.
- Sentences where the verb trails its object ("i don't need the perc track
anymore, delete it") are a known weak spot.
## License
MIT, following `microsoft/deberta-v3-base`.
|