mrfakename commited on
Commit
c8e429e
·
verified ·
1 Parent(s): fe5a017

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ plot.png filter=lfs diff=lfs merge=lfs -text
37
+ device_lab.png filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language:
4
+ - en
5
+ library_name: onnx
6
+ pipeline_tag: audio-classification
7
+ tags:
8
+ - function-calling
9
+ - tool-calling
10
+ - spoken-language-understanding
11
+ - onnx
12
+ - on-device
13
+ extra_gated_fields:
14
+ Affiliation (Company or Academic Institution): text
15
+ Role: text
16
+ ---
17
+
18
+ # NeuDecide
19
+
20
+ <p align="center">
21
+ <a href="https://huggingface.co/spaces/neuphonic/neudecide">
22
+ <img src="device_lab.png">
23
+ </a>
24
+ </p>
25
+
26
+ **A tiny voice-action model. Audio and a list of tools in, a tool call out. No transcript in between.**
27
+
28
+ [GitHub](https://github.com/neuphonic/neudecide) · [PyPI](https://pypi.org/project/neudecide/) · [HF Space](https://huggingface.co/spaces/neuphonic/neudecide) · arXiv (coming soon)
29
+
30
+ NeuDecide converts speech directly into function calls. It's 43 MB, can run on a single thread via ONNX Runtime, and needs no GPU or internet connection once downloaded. Tools are passed in as JSON schemas at inference time, so you change what the model can do by changing the JSON, not by retraining.
31
+
32
+ ```
33
+ "clean the bathroom" + [cleanRoom, goToBase, getBattery] → [{"name": "cleanRoom", "arguments": {"room": "bathroom"}}]
34
+ ```
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install neudecide
40
+ ```
41
+
42
+ Requires Python 3.10 – 3.13. The model weights are downloaded from the Hugging Face Hub on first use and cached after that.
43
+
44
+ ## Quickstart
45
+
46
+ This example turns a recorded voice command into a call for a robot vacuum. Save a WAV recording of yourself saying something like "clean the bathroom" as `command.wav`, then run:
47
+
48
+ ```python
49
+ from neudecide import NeuDecide
50
+
51
+ TOOLS = [
52
+ {
53
+ "name": "cleanRoom",
54
+ "description": "Vacuum-cleans one room.",
55
+ "parameters": {
56
+ "type": "object",
57
+ "properties": {
58
+ "room": {
59
+ "type": "string",
60
+ "enum": [
61
+ "living room",
62
+ "bathroom",
63
+ "kitchen",
64
+ "bedroom",
65
+ ],
66
+ },
67
+ },
68
+ "required": ["room"],
69
+ },
70
+ },
71
+ {
72
+ "name": "goToBase",
73
+ "description": "Sends the vacuum back to its charging base.",
74
+ "parameters": {
75
+ "type": "object",
76
+ "properties": {},
77
+ },
78
+ },
79
+ {
80
+ "name": "getBattery",
81
+ "description": "Reports the vacuum's battery level.",
82
+ "parameters": {
83
+ "type": "object",
84
+ "properties": {},
85
+ },
86
+ },
87
+ ]
88
+
89
+
90
+ def main():
91
+ model = NeuDecide.from_pretrained()
92
+ calls = model.generate("command.wav", TOOLS)
93
+ print(calls)
94
+
95
+
96
+ if __name__ == "__main__":
97
+ main()
98
+ ```
99
+
100
+ Output:
101
+
102
+ ```
103
+ [{'name': 'cleanRoom', 'arguments': {'room': 'bathroom'}}]
104
+ ```
105
+
106
+ `generate` returns a list of `{"name", "arguments"}` dicts, or `[]` when no tool applies. If the answer reaches the 128-token output limit, you get the calls completed before it, with a warning.
107
+
108
+ A command-line version is in [`examples/basic_example.py`](https://github.com/neuphonic/neudecide/blob/main/examples/basic_example.py). From a clone of the GitHub repo, `python examples/basic_example.py` runs it on `examples/command.wav` with the tools in `examples/tools.json`; pass `--audio` and `--tools` to use your own.
109
+
110
+ ## Usage
111
+
112
+ ### Audio input
113
+
114
+ `audio` can be a path to a PCM WAV file or a NumPy array. Arrays may be mono `(samples,)` or multi-channel `(samples, channels)`; channels are averaged to mono. Integer arrays, such as the int16 samples `scipy.io.wavfile.read` returns, are scaled to [-1, 1] automatically; float arrays should already be in [-1, 1]. Audio is resampled to 16 kHz automatically, so pass `sample_rate=` if your array is at a different rate:
115
+
116
+ ```python
117
+ calls = model.generate("command.wav", TOOLS) # WAV file
118
+ calls = model.generate(waveform, TOOLS, sample_rate=44100) # NumPy array
119
+ ```
120
+
121
+ Only WAV is read natively. For other formats (MP3, FLAC, OGG), load the audio yourself and pass the array. Audio longer than 30 seconds is truncated.
122
+
123
+ ### Tool definitions
124
+
125
+ Each tool has a `name`, a `description`, and `parameters` as a JSON Schema object. You can pass the list directly or as a JSON string. The model only knows what you tell it about each tool, so clear names and descriptions do most of the work. When an argument has a fixed set of values, like the rooms above, list them with `enum`.
126
+
127
+ ## How it works
128
+
129
+ NeuDecide is a pre-trained streaming English speech encoder joined to a compact tool-calling encoder–decoder, initialised from [Needle](https://github.com/cactus-compute/needle), and trained end to end as one system. Inference runs three ONNX graphs:
130
+
131
+ | Graph | Runs | Does | Parameters | Size |
132
+ |---|---|---|---|---|
133
+ | `audio_encoder` | Once per utterance | Waveform → 12.5 Hz latent frames | 28.7M | 22.5 MB |
134
+ | `tool_encoder` | Once per utterance and tool list | Audio frames + tool tokens → cross-attention keys and values | 11.8M | 10.1 MB |
135
+ | `decoder_step` | Once per output token | Next token, reusing a cached self-attention state | 15.0M | 10.0 MB |
136
+
137
+ | | |
138
+ |---|---|
139
+ | Model width | 512 |
140
+ | Decoder layers | 8 |
141
+ | Attention | Grouped-query, 4 key/value heads of 64 dimensions |
142
+ | Vocabulary | 8,192 SentencePiece BPE tokens |
143
+ | Sample rate | 16 kHz |
144
+ | Limits | 30 s of audio, 1,536 tool tokens, 128 output tokens |
145
+
146
+ Tools are serialised exactly as Python's `json.dumps` writes them by default, matching how the model was trained.
147
+
148
+ ## Results
149
+
150
+ <p align="center">
151
+ <img src="plot.png" alt="Exact match against tool accuracy on FSC, SNIPS SLU and SLURP for NeuDecide, Whistle → Needle 3, Parakeet 660M → FunctionGemma and Voxtral Mini 3B, with bubble area showing model size" width="720">
152
+ </p>
153
+
154
+ We compared NeuDecide with five speech-recognition cascades (Parakeet 110M and 660M feeding Needle or FunctionGemma, and Whistle feeding Needle 3) and with Voxtral Mini 3B, an end-to-end speech model. Each system chooses from 10 tools. Some requests need only the right tool; the rest also need its arguments filled, and exact match counts a call as right only if the tool and every argument match. The results cover both kinds of request together, weighted by how many of each SLURP has, as in the plot. The table shows two of the cascades alongside Voxtral Mini 3B; a cascade's size counts its ASR and decision model together.
155
+
156
+ | | | NeuDecide | Whistle → Needle 3 | Parakeet 660M → FunctionGemma | Voxtral Mini 3B |
157
+ |---|---|---|---|---|---|
158
+ | **Size** | Parameters | 55.5M | 176M | 870M | 3B |
159
+ | **Size** | File size | 43 MB | 52 MB | 3.0 GB | 18.7 GB |
160
+ | **Size** | File size vs NeuDecide | 1× | 1.2× | 71× | 435× |
161
+ | SLURP | Tool accuracy | **75.5%** | 25.6% | 25.2% | 48.7% |
162
+ | SLURP | Exact match | **37.5%** | 7.1% | 1.9% | 6.9% |
163
+
164
+ On SLURP, NeuDecide beats every other system on both metrics. With 10 tools, it gets more than five times as many commands exactly right as the best cascade or Voxtral Mini 3B.
165
+
166
+ ## Performance
167
+
168
+ Measured with the `q4` export:
169
+
170
+ | Device | Time to call | Load time |
171
+ |---|---|---|
172
+ | MacBook Pro M3 | 46 ms | 159 ms |
173
+ | Samsung S24+ | 82 ms | 267 ms |
174
+ | Raspberry Pi 5 | 206 ms | 499 ms |
175
+
176
+ Peak RAM ranged from 146 MB to 174 MB across these devices; it depends on the runtime and workload.
177
+
178
+ ## Limitations
179
+
180
+ - **It can fire on noise.** Given 30 s of low-level noise, it still produced a call. There is no calibrated confidence score yet, so put a gate in front: voice-activity detection, or an explicit "no action" tool.
181
+ - **Keep tool lists short.** We recommend at most 10 tools. Longer lists also cost more compute than longer audio, and lists over 1,536 tokens are truncated with a warning.
182
+ - **English only.**
183
+ - **Length limits.** Inputs are capped at 30 s of audio, and outputs are capped at 128 tokens.
184
+ - **Free-form arguments are harder.** On SNIPS, where arguments are often open-ended names such as artists and playlists, transcript-based cascades still fill arguments more accurately. Use `enum` wherever an argument has a fixed set of values.
185
+
186
+ Test it on your own tools and your users' voices before deploying. NeuDecide works best next to a larger model: let it handle the fast, frequent, bounded decisions, and route anything it can't place to a bigger model or a person.
187
+
188
+ ## Acknowledgements
189
+
190
+ - https://github.com/cactus-compute/needle
191
+ - https://huggingface.co/nvidia/stt_en_fastconformer_hybrid_medium_streaming_80ms
192
+
193
+ ## License
194
+
195
+ Apache 2.0. See [LICENSE](https://github.com/neuphonic/neudecide/blob/main/LICENSE).
audio_encoder_q4.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2ac5c48e1766382736ed8dd4d468568ee583536550fb741ac187219f8cd8da9b
3
+ size 22519972
config.json ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "step": 12500,
3
+ "fold": {
4
+ "lora": 0,
5
+ "lora+qat": 112,
6
+ "qat_linear": 147,
7
+ "act_fq": 0,
8
+ "embedding": 1
9
+ },
10
+ "variants": {
11
+ "q4": {
12
+ "graphs": {
13
+ "audio_encoder": "audio_encoder_q4.onnx",
14
+ "tool_encoder": "tool_encoder_q4.onnx",
15
+ "decoder_step": "decoder_step_q4.onnx"
16
+ },
17
+ "scheme": {
18
+ "bits": 4,
19
+ "group_size": 32,
20
+ "embedding": true,
21
+ "audio_encoder": true,
22
+ "conv_bits": 8
23
+ },
24
+ "accuracy_level": 4,
25
+ "compute": "int4 MatMulNBits / GatherBlockQuantized"
26
+ }
27
+ },
28
+ "sample_rate": 16000,
29
+ "max_audio_seconds": 30.0,
30
+ "max_tools_len": 2048,
31
+ "max_answer_len": 128,
32
+ "tools_json": "Python json.dumps(tools) defaults: ', ' and ': ' separators, ensure_ascii",
33
+ "special_tokens": {
34
+ "pad": 0,
35
+ "eos": 1,
36
+ "bos": 2,
37
+ "unk": 3,
38
+ "tool_call": 4,
39
+ "tools": 5
40
+ },
41
+ "vocab_size": 8192,
42
+ "d_model": 512,
43
+ "num_decoder_layers": 8,
44
+ "num_kv_heads": 4,
45
+ "head_dim": 64
46
+ }
decoder_step_q4.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ee68bbb4bbca552b909b41cb1fdbfdf0f9f3c852561e18036044ee2ff461e50d
3
+ size 9951225
device_lab.png ADDED

Git LFS Details

  • SHA256: 89c355f66357abdb248ef339a6f3df19b341b6e4d9b7054a9a28de0453c80a24
  • Pointer size: 131 Bytes
  • Size of remote file: 276 kB
plot.png ADDED

Git LFS Details

  • SHA256: 97e735f74ed894352043d4534375fee5c853c9eb7c6e1a71c2b6dca891578d79
  • Pointer size: 131 Bytes
  • Size of remote file: 200 kB
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer.model ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b39d6892d3b27e66a1782ee624d965e5e656c03e9226cbe036a80389090947ad
3
+ size 124892
tool_encoder_q4.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4a5075ee2868d90ea4be3234eae04001be6886c9730e8746421fa17e84313d85
3
+ size 10090217