SpellSpeak Tone rc2.1
Browse files- MANIFEST.json +13 -9
- README.md +40 -8
- example.py +9 -6
- model_fp16.onnx +3 -0
- runtime/harness/expression/classifier.py +20 -3
MANIFEST.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
| 1 |
{
|
| 2 |
"name": "SpellSpeak Tone",
|
| 3 |
-
"release": "rc2",
|
| 4 |
-
"package": "spellspeak-tone-rc2.
|
| 5 |
-
"created": "2026-10-
|
| 6 |
"contract": "line-tags-0.2",
|
| 7 |
"files": {
|
| 8 |
"README.md": {
|
| 9 |
-
"bytes":
|
| 10 |
-
"sha256": "
|
| 11 |
},
|
| 12 |
"config.json": {
|
| 13 |
"bytes": 1515,
|
| 14 |
"sha256": "531d5558cd620780ac3fe02d2acf2aad960e768619440d48d8c0abf4b32fa182"
|
| 15 |
},
|
| 16 |
"example.py": {
|
| 17 |
-
"bytes":
|
| 18 |
-
"sha256": "
|
| 19 |
},
|
| 20 |
"model.onnx": {
|
| 21 |
"bytes": 111170339,
|
|
@@ -25,6 +25,10 @@
|
|
| 25 |
"bytes": 111132591,
|
| 26 |
"sha256": "d6809b08375415f0cd2bbc0282d9d3c8d8282b010001ee52559f7fae5b857e60"
|
| 27 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
"requirements.txt": {
|
| 29 |
"bytes": 287,
|
| 30 |
"sha256": "91061e9d80e2cdaf62f522debf73ebcefcfa88930d50c391142b0d73cd72faee"
|
|
@@ -54,8 +58,8 @@
|
|
| 54 |
"sha256": "79774b330a98949865f3c0b22b1f72b5d9510effaa3d9635579c2b09ea9db04f"
|
| 55 |
},
|
| 56 |
"runtime/harness/expression/classifier.py": {
|
| 57 |
-
"bytes":
|
| 58 |
-
"sha256": "
|
| 59 |
},
|
| 60 |
"runtime/harness/expression/decide.py": {
|
| 61 |
"bytes": 1410,
|
|
|
|
| 1 |
{
|
| 2 |
"name": "SpellSpeak Tone",
|
| 3 |
+
"release": "rc2.1",
|
| 4 |
+
"package": "spellspeak-tone-rc2.2",
|
| 5 |
+
"created": "2026-10-09T22:16:39Z",
|
| 6 |
"contract": "line-tags-0.2",
|
| 7 |
"files": {
|
| 8 |
"README.md": {
|
| 9 |
+
"bytes": 14297,
|
| 10 |
+
"sha256": "3013c4ff392150819362ac5c7735f894aff751aa3aa0b6f334948d3ecab40a3b"
|
| 11 |
},
|
| 12 |
"config.json": {
|
| 13 |
"bytes": 1515,
|
| 14 |
"sha256": "531d5558cd620780ac3fe02d2acf2aad960e768619440d48d8c0abf4b32fa182"
|
| 15 |
},
|
| 16 |
"example.py": {
|
| 17 |
+
"bytes": 2930,
|
| 18 |
+
"sha256": "91087ec1faa9557ccae9cf31d4550a4a983cb3bb334cafc884c411aa93f23f77"
|
| 19 |
},
|
| 20 |
"model.onnx": {
|
| 21 |
"bytes": 111170339,
|
|
|
|
| 25 |
"bytes": 111132591,
|
| 26 |
"sha256": "d6809b08375415f0cd2bbc0282d9d3c8d8282b010001ee52559f7fae5b857e60"
|
| 27 |
},
|
| 28 |
+
"model_fp16.onnx": {
|
| 29 |
+
"bytes": 55694715,
|
| 30 |
+
"sha256": "08201561726a6e80cf454ab57e786b268ee428d152dc9299f80941e541ad11d7"
|
| 31 |
+
},
|
| 32 |
"requirements.txt": {
|
| 33 |
"bytes": 287,
|
| 34 |
"sha256": "91061e9d80e2cdaf62f522debf73ebcefcfa88930d50c391142b0d73cd72faee"
|
|
|
|
| 58 |
"sha256": "79774b330a98949865f3c0b22b1f72b5d9510effaa3d9635579c2b09ea9db04f"
|
| 59 |
},
|
| 60 |
"runtime/harness/expression/classifier.py": {
|
| 61 |
+
"bytes": 7321,
|
| 62 |
+
"sha256": "4b9f54a4e3d9390f7b080996cc5cead459132a78835f93d0b0f734b53a8cc965"
|
| 63 |
},
|
| 64 |
"runtime/harness/expression/decide.py": {
|
| 65 |
"bytes": 1410,
|
README.md
CHANGED
|
@@ -20,7 +20,7 @@ tags:
|
|
| 20 |
- dialogue
|
| 21 |
---
|
| 22 |
|
| 23 |
-
# SpellSpeak Tone · RC2
|
| 24 |
|
| 25 |
When several AI characters share one conversation, as in a game, an interactive story or any other multi-character voice experience, each of them needs a structured reading of every line: how the speaker sounds, what the line does to each person present, and whether it leaves a question open for someone else to answer. The language model voicing a character could produce that reading, but only through extra output tokens, training or tool calls. That adds latency to every turn, takes capacity from the dialogue, risks malformed output, and repeats the work for each character in the scene.
|
| 26 |
|
|
@@ -28,22 +28,30 @@ SpellSpeak Tone treats the reading as multi-task classification by a separate sm
|
|
| 28 |
|
| 29 |
[Examples and source](https://github.com/spellspeak/models/tree/main/tone)
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
| | |
|
| 32 |
|---|---|
|
| 33 |
-
| **Released** | 2026-10-09 · rc2 (package `spellspeak-tone-rc2.
|
| 34 |
| **What it does** | Reads one line of dialogue and tags the speaker's emotion, the social act toward each person present, and the line's move in the conversation |
|
| 35 |
| **What changed from rc1** | One new output per line, the move, from two extra encoder layers and a small head beside rc1's unchanged encoder, in the same pass. Every rc1 output (emotion, acts, intensities, backchannel, hostility, confidences) is rc1's, checked on every one of 3,754 test lines |
|
| 36 |
-
| **Size** | 27.9M parameters (rc1's 23.6M plus 4.3M for the move) · 111 MB ONNX (fp32) |
|
| 37 |
-
| **Speed** | p95 per line on 4 CPU threads (Apple M1 Max): 7.0 ms for 1 person, 7.9 for 4, 9.7 for 8 (rc1: 5.5, 6.6, 7.3). On 1 thread: 19 to 28 ms |
|
| 38 |
| **Runtime** | ONNX Runtime (1.24 or later) and tokenizers, on CPU. A Python reference runtime ships with it |
|
| 39 |
-
| **Files** | `model.opt.onnx` (the graph to run), `model.onnx` (the same graph unoptimized), `tokenizer.json` and `config.json`, with the reference runtime (`runtime/`), `example.py` and `requirements.txt`. `MANIFEST.json` lists every file with its size and sha256 |
|
| 40 |
| **Licence** | Apache-2.0 |
|
| 41 |
|
| 42 |
## Quick start
|
| 43 |
|
| 44 |
```bash
|
| 45 |
pip install -U huggingface_hub
|
| 46 |
-
hf download spellspeak/tone --revision rc2 --local-dir spellspeak-tone
|
| 47 |
cd spellspeak-tone
|
| 48 |
pip install -r requirements.txt
|
| 49 |
python example.py
|
|
@@ -70,6 +78,17 @@ player to Tomas: "Touch my horse again and you'll regret it."
|
|
| 70 |
|
| 71 |
A group conversation can play Mara's answer first, add Tomas's rumour as a second voice, and drop the dodge and the "no idea".
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
In your own code, with the downloaded folder as the working directory:
|
| 74 |
|
| 75 |
```python
|
|
@@ -79,7 +98,8 @@ sys.path.insert(0, "runtime")
|
|
| 79 |
from contracts.schemas.line_tags import LineInput
|
| 80 |
from harness.expression.classifier import LineClassifier
|
| 81 |
|
| 82 |
-
tone = LineClassifier(".") #
|
|
|
|
| 83 |
tags = tone.tag(LineInput(speaker="Wren", speaker_kind="npc", to="Tomas", targets=["Tomas", "Mara", "player"],
|
| 84 |
text="Aye, I saw the whole thing.", previous="Tomas: Wren, you were there, weren't you?"))
|
| 85 |
print(tags.exchange) # label='answers' confidence=0.9413
|
|
@@ -141,6 +161,16 @@ The same test lines, scored again without who a line is said to, without the lin
|
|
| 141 |
- **Send who a line is said to when you know it.** Without it, a "you" in a group is read as aimed at everyone. On those lines, harmless pairs called hostile go from 2.1% to 17.6%.
|
| 142 |
- **Send the line before for the move.** A bare "Yes." is an answer only after a question. Without the line before, "answered or still open" drops 15 points.
|
| 143 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
## How it was made
|
| 145 |
|
| 146 |
- **Model:** MiniLM-L6 (`sentence-transformers/all-MiniLM-L6-v2` at 1110a24, Apache-2.0) with six small heads, unchanged from rc1. For the move, trainable copies of its top two layers read its fourth layer's output, and a small head reads them. The encoder itself was frozen, so every rc1 output is unchanged.
|
|
@@ -148,6 +178,7 @@ The same test lines, scored again without who a line is said to, without the lin
|
|
| 148 |
- **Labels:** the majority vote of a committee of three large language models. 4,992 lines were labelled for the move.
|
| 149 |
- **Lines:** our own game dialogue, Project Gutenberg dialogue, GoEmotions (Apache-2.0), Civil Comments (CC0), and lines written for this project by Qwen3.8-27B. The last include 95,000 of natural player speech, 48,000 aimed at sarcasm, doubling down and implied threats, and 9,126 short exchanges for the rarer moves: rumours, dodges, "don't know", closings, and exchanges between characters.
|
| 150 |
- **Calibration:** the hostility threshold and the act and emotion curves are rc1's. A curve for the move's confidence was fitted on the dev set.
|
|
|
|
| 151 |
|
| 152 |
## Use and limits
|
| 153 |
|
|
@@ -157,6 +188,7 @@ The same test lines, scored again without who a line is said to, without the lin
|
|
| 157 |
- **It misses things.** It gets "answered or still open" wrong about one time in six. It misses about two in three sarcastic jabs and about one in four hostile lines.
|
| 158 |
- **Player lines are harder** than characters' lines for the move (macro-F1 0.74 against 0.81), and new settings harder still (0.71).
|
| 159 |
- **About 30% slower than rc1**, because two extra encoder layers run for the move.
|
|
|
|
| 160 |
- **It has only been tested in game settings.** Its test lines are committee-labelled generated and game dialogue, not logs from real players or from other multi-character voice experiences.
|
| 161 |
|
| 162 |
-
The numbers come from the training repository's
|
|
|
|
| 20 |
- dialogue
|
| 21 |
---
|
| 22 |
|
| 23 |
+
# SpellSpeak Tone · RC2.1
|
| 24 |
|
| 25 |
When several AI characters share one conversation, as in a game, an interactive story or any other multi-character voice experience, each of them needs a structured reading of every line: how the speaker sounds, what the line does to each person present, and whether it leaves a question open for someone else to answer. The language model voicing a character could produce that reading, but only through extra output tokens, training or tool calls. That adds latency to every turn, takes capacity from the dialogue, risks malformed output, and repeats the work for each character in the scene.
|
| 26 |
|
|
|
|
| 28 |
|
| 29 |
[Examples and source](https://github.com/spellspeak/models/tree/main/tone)
|
| 30 |
|
| 31 |
+
## What is new in RC2.1
|
| 32 |
+
|
| 33 |
+
- **A 16-bit file at half the size, with the same answers.** `model_fp16.onnx` is rc2's model with its weights stored in 16 bits: 55.7 MB against 111.1 MB. On 5,204 test lines, read in all four columns with and without the optional inputs, at most one line in a column gets a different tag from full precision. No scorecard measure changes beyond noise.
|
| 34 |
+
- **The same speed, and no memory saved.** It computes in 32 bits. p95 per line for 8 people on 4 threads of an Apple M1 Max: 10.0 ms against 9.9 ms for full precision, timed side by side (1 thread: 29.4 against 29.5 ms). Resident memory: 222 MB against 201 MB. What it saves is the download and the disk.
|
| 35 |
+
- **Full precision is unchanged and stays the default.** `model.opt.onnx`, `model.onnx`, `tokenizer.json` and `config.json` are rc2's, byte for byte. Ask for the 16-bit file with `precision="fp16"`, or `python example.py --fp16`.
|
| 36 |
+
|
| 37 |
+
Full precision is the reference: every other number on this card is full precision's, and it is the file to keep for any further training.
|
| 38 |
+
|
| 39 |
| | |
|
| 40 |
|---|---|
|
| 41 |
+
| **Released** | 2026-10-09 · rc2.1 (package `spellspeak-tone-rc2.2`) |
|
| 42 |
| **What it does** | Reads one line of dialogue and tags the speaker's emotion, the social act toward each person present, and the line's move in the conversation |
|
| 43 |
| **What changed from rc1** | One new output per line, the move, from two extra encoder layers and a small head beside rc1's unchanged encoder, in the same pass. Every rc1 output (emotion, acts, intensities, backchannel, hostility, confidences) is rc1's, checked on every one of 3,754 test lines |
|
| 44 |
+
| **Size** | 27.9M parameters (rc1's 23.6M plus 4.3M for the move) · 111.1 MB ONNX in full precision (fp32), 55.7 MB with the weights in 16 bits (`model_fp16.onnx`) |
|
| 45 |
+
| **Speed** | p95 per line on 4 CPU threads (Apple M1 Max): 7.0 ms for 1 person, 7.9 for 4, 9.7 for 8 (rc1: 5.5, 6.6, 7.3). On 1 thread: 19 to 28 ms. The 16-bit file takes the same time |
|
| 46 |
| **Runtime** | ONNX Runtime (1.24 or later) and tokenizers, on CPU. A Python reference runtime ships with it |
|
| 47 |
+
| **Files** | `model.opt.onnx` (the graph to run), `model.onnx` (the same graph unoptimized), `model_fp16.onnx` (the graph to run with its weights in 16 bits), `tokenizer.json` and `config.json`, with the reference runtime (`runtime/`), `example.py` and `requirements.txt`. `MANIFEST.json` lists every file with its size and sha256 |
|
| 48 |
| **Licence** | Apache-2.0 |
|
| 49 |
|
| 50 |
## Quick start
|
| 51 |
|
| 52 |
```bash
|
| 53 |
pip install -U huggingface_hub
|
| 54 |
+
hf download spellspeak/tone --revision rc2.1 --local-dir spellspeak-tone
|
| 55 |
cd spellspeak-tone
|
| 56 |
pip install -r requirements.txt
|
| 57 |
python example.py
|
|
|
|
| 78 |
|
| 79 |
A group conversation can play Mara's answer first, add Tomas's rumour as a second voice, and drop the dodge and the "no idea".
|
| 80 |
|
| 81 |
+
The small download leaves out the two full-precision graphs, about 56 MB in all, and runs the 16-bit file:
|
| 82 |
+
|
| 83 |
+
```bash
|
| 84 |
+
hf download spellspeak/tone --revision rc2.1 --local-dir spellspeak-tone-small --exclude model.onnx --exclude model.opt.onnx
|
| 85 |
+
cd spellspeak-tone-small
|
| 86 |
+
pip install -r requirements.txt
|
| 87 |
+
python example.py --fp16
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
It prints exactly the same output.
|
| 91 |
+
|
| 92 |
In your own code, with the downloaded folder as the working directory:
|
| 93 |
|
| 94 |
```python
|
|
|
|
| 98 |
from contracts.schemas.line_tags import LineInput
|
| 99 |
from harness.expression.classifier import LineClassifier
|
| 100 |
|
| 101 |
+
tone = LineClassifier(".") # full precision (model.opt.onnx), the default
|
| 102 |
+
# tone = LineClassifier(".", precision="fp16") # the 16-bit file (model_fp16.onnx), for the small download
|
| 103 |
tags = tone.tag(LineInput(speaker="Wren", speaker_kind="npc", to="Tomas", targets=["Tomas", "Mara", "player"],
|
| 104 |
text="Aye, I saw the whole thing.", previous="Tomas: Wren, you were there, weren't you?"))
|
| 105 |
print(tags.exchange) # label='answers' confidence=0.9413
|
|
|
|
| 161 |
- **Send who a line is said to when you know it.** Without it, a "you" in a group is read as aimed at everyone. On those lines, harmless pairs called hostile go from 2.1% to 17.6%.
|
| 162 |
- **Send the line before for the move.** A bare "Yes." is an answer only after a question. Without the line before, "answered or still open" drops 15 points.
|
| 163 |
|
| 164 |
+
### The 16-bit file
|
| 165 |
+
|
| 166 |
+
The same 5,204 test lines, tagged by `model_fp16.onnx` and by full precision, in the same four columns:
|
| 167 |
+
|
| 168 |
+
| | As given | Without `to` | Without the line before | Without either |
|
| 169 |
+
|---|---|---|---|---|
|
| 170 |
+
| Lines with every tag the same as full precision | 100% | 100% | 99.98% (one move differs) | 99.98% (one act toward one person differs) |
|
| 171 |
+
|
| 172 |
+
Rounded as the tables above show them, every measure is the same with the 16-bit file. Calibration error moves by 0.0012 at most.
|
| 173 |
+
|
| 174 |
## How it was made
|
| 175 |
|
| 176 |
- **Model:** MiniLM-L6 (`sentence-transformers/all-MiniLM-L6-v2` at 1110a24, Apache-2.0) with six small heads, unchanged from rc1. For the move, trainable copies of its top two layers read its fourth layer's output, and a small head reads them. The encoder itself was frozen, so every rc1 output is unchanged.
|
|
|
|
| 178 |
- **Labels:** the majority vote of a committee of three large language models. 4,992 lines were labelled for the move.
|
| 179 |
- **Lines:** our own game dialogue, Project Gutenberg dialogue, GoEmotions (Apache-2.0), Civil Comments (CC0), and lines written for this project by Qwen3.8-27B. The last include 95,000 of natural player speech, 48,000 aimed at sarcasm, doubling down and implied threats, and 9,126 short exchanges for the rarer moves: rumours, dodges, "don't know", closings, and exchanges between characters.
|
| 180 |
- **Calibration:** the hostility threshold and the act and emotion curves are rc1's. A curve for the move's confidence was fitted on the dev set.
|
| 181 |
+
- **The 16-bit file:** full precision's graph with every weight tensor of 1,024 values or more stored in 16 bits and turned back into 32 bits by the graph itself. Inputs, outputs and arithmetic stay 32-bit, so the same runtime and the same calibration curves serve both files.
|
| 182 |
|
| 183 |
## Use and limits
|
| 184 |
|
|
|
|
| 188 |
- **It misses things.** It gets "answered or still open" wrong about one time in six. It misses about two in three sarcastic jabs and about one in four hostile lines.
|
| 189 |
- **Player lines are harder** than characters' lines for the move (macro-F1 0.74 against 0.81), and new settings harder still (0.71).
|
| 190 |
- **About 30% slower than rc1**, because two extra encoder layers run for the move.
|
| 191 |
+
- **The 16-bit file saves download and disk, not time or memory.** It has been timed on CPU only.
|
| 192 |
- **It has only been tested in game settings.** Its test lines are committee-labelled generated and game dialogue, not logs from real players or from other multi-character voice experiences.
|
| 193 |
|
| 194 |
+
The numbers come from the training repository's reports: for the 16-bit file, `2026-10-09-linecls-precision.md` (at commit 9498f60); for the model, `2026-10-09-linecls-r8-exchange.md` (at commit a31743c) and, for the emotion and act heads unchanged since rc1, `2026-10-08-linecls-r6-sarcasm.md` and `2026-10-08-linecls-r7-calibration.md`. The full-precision files were packaged from git tag `spellspeak-tone-rc2` (commit 691b8e2), the 16-bit file was built from them by the code at commit 0c8ed10, and this release was packaged from git tag `spellspeak-tone-rc2.2`. That repository is private.
|
example.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
| 1 |
"""Tag a few lines of a group conversation with SpellSpeak Tone.
|
| 2 |
|
| 3 |
pip install -r requirements.txt
|
| 4 |
-
python example.py
|
|
|
|
| 5 |
|
| 6 |
-
The model files
|
| 7 |
-
|
| 8 |
"""
|
| 9 |
import pathlib
|
| 10 |
import sys
|
|
@@ -15,13 +16,15 @@ sys.path.insert(0, str(HERE / "runtime"))
|
|
| 15 |
from contracts.schemas.line_tags import LineInput # noqa: E402
|
| 16 |
from harness.expression.classifier import LineClassifier # noqa: E402
|
| 17 |
|
| 18 |
-
|
|
|
|
|
|
|
| 19 |
model_dir = HERE
|
| 20 |
if not all((HERE / f).exists() for f in MODEL_FILES):
|
| 21 |
from huggingface_hub import snapshot_download
|
| 22 |
|
| 23 |
-
model_dir = snapshot_download("spellspeak/tone", revision="rc2", allow_patterns=MODEL_FILES)
|
| 24 |
-
tone = LineClassifier(model_dir, threads=4)
|
| 25 |
|
| 26 |
# Three characters and the player. Each line names who speaks, who it is to (None: the room) and the line before it.
|
| 27 |
CAST = ["Mara", "Tomas", "Wren", "player"]
|
|
|
|
| 1 |
"""Tag a few lines of a group conversation with SpellSpeak Tone.
|
| 2 |
|
| 3 |
pip install -r requirements.txt
|
| 4 |
+
python example.py # full precision: model.opt.onnx
|
| 5 |
+
python example.py --fp16 # the 16-bit file: model_fp16.onnx, half the size
|
| 6 |
|
| 7 |
+
The model files are read from this folder. Without them, as in a git checkout, they are downloaded once from Hugging
|
| 8 |
+
Face (spellspeak/tone, release rc2.1). With --fp16, only model_fp16.onnx, tokenizer.json and config.json are needed.
|
| 9 |
"""
|
| 10 |
import pathlib
|
| 11 |
import sys
|
|
|
|
| 16 |
from contracts.schemas.line_tags import LineInput # noqa: E402
|
| 17 |
from harness.expression.classifier import LineClassifier # noqa: E402
|
| 18 |
|
| 19 |
+
PRECISION = "fp16" if "--fp16" in sys.argv[1:] else "fp32"
|
| 20 |
+
MODEL_FILES = (["model_fp16.onnx", "tokenizer.json", "config.json"] if PRECISION == "fp16"
|
| 21 |
+
else ["model.opt.onnx", "model.onnx", "tokenizer.json", "config.json"])
|
| 22 |
model_dir = HERE
|
| 23 |
if not all((HERE / f).exists() for f in MODEL_FILES):
|
| 24 |
from huggingface_hub import snapshot_download
|
| 25 |
|
| 26 |
+
model_dir = snapshot_download("spellspeak/tone", revision="rc2.1", allow_patterns=MODEL_FILES)
|
| 27 |
+
tone = LineClassifier(model_dir, threads=4, precision=PRECISION)
|
| 28 |
|
| 29 |
# Three characters and the player. Each line names who speaks, who it is to (None: the room) and the line before it.
|
| 30 |
CAST = ["Mara", "Tomas", "Wren", "player"]
|
model_fp16.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:08201561726a6e80cf454ab57e786b268ee428d152dc9299f80941e541ad11d7
|
| 3 |
+
size 55694715
|
runtime/harness/expression/classifier.py
CHANGED
|
@@ -11,6 +11,10 @@ its hostility probability reaches tau (an invented grudge costs more than a miss
|
|
| 11 |
r8 (line-tags-0.2): a model whose graph has an `exchange` output and whose config lists `exchanges` also returns the
|
| 12 |
line's exchange move (asks, answers, hedges, doesnt_know, withholds, closes, other) with its confidence, as
|
| 13 |
`LineTags02`. A model without it (RC1) returns `LineTags` exactly as before.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
"""
|
| 15 |
from __future__ import annotations
|
| 16 |
|
|
@@ -24,16 +28,29 @@ from harness.expression.decide import decide, summed_hostility
|
|
| 24 |
from harness.expression.render import render, span_mask
|
| 25 |
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
class LineClassifier:
|
| 28 |
-
def __init__(self, model_dir: str | pathlib.Path, onnx_file: str | None = None, threads: int = 4):
|
| 29 |
-
"""`
|
| 30 |
-
a little faster), else the plain graph.""
|
|
|
|
| 31 |
import onnxruntime as ort
|
| 32 |
from tokenizers import Tokenizer
|
| 33 |
|
|
|
|
|
|
|
| 34 |
d = pathlib.Path(model_dir)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
onnx_file = onnx_file or ("model.opt.onnx" if (d / "model.opt.onnx").exists() else "model.onnx")
|
| 36 |
self.onnx_file = onnx_file
|
|
|
|
| 37 |
self.config = json.loads((d / "config.json").read_text())
|
| 38 |
self.tok = Tokenizer.from_file(str(d / "tokenizer.json"))
|
| 39 |
self.tok.enable_truncation(self.config["max_len"])
|
|
|
|
| 11 |
r8 (line-tags-0.2): a model whose graph has an `exchange` output and whose config lists `exchanges` also returns the
|
| 12 |
line's exchange move (asks, answers, hedges, doesnt_know, withholds, closes, other) with its confidence, as
|
| 13 |
`LineTags02`. A model without it (RC1) returns `LineTags` exactly as before.
|
| 14 |
+
|
| 15 |
+
Precision: full precision ("fp32") is the default and the reference. From release rc2.1 a folder may also hold
|
| 16 |
+
`model_fp16.onnx`, the same graph with its weights stored in 16 bits (half the size, computed in 32 bits), picked with
|
| 17 |
+
`precision="fp16"`.
|
| 18 |
"""
|
| 19 |
from __future__ import annotations
|
| 20 |
|
|
|
|
| 28 |
from harness.expression.render import render, span_mask
|
| 29 |
|
| 30 |
|
| 31 |
+
PRECISIONS = ("fp32", "fp16")
|
| 32 |
+
FP16_FILE = "model_fp16.onnx"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
class LineClassifier:
|
| 36 |
+
def __init__(self, model_dir: str | pathlib.Path, onnx_file: str | None = None, threads: int = 4, precision: str = "fp32"):
|
| 37 |
+
"""`precision` picks the graph. "fp32" (the default): the transformer optimizer's graph when the export has one
|
| 38 |
+
(r3 on: same outputs, a little faster), else the plain graph. "fp16": `model_fp16.onnx`, the weights stored in
|
| 39 |
+
16 bits. `onnx_file` names a graph directly and overrides `precision`."""
|
| 40 |
import onnxruntime as ort
|
| 41 |
from tokenizers import Tokenizer
|
| 42 |
|
| 43 |
+
if precision not in PRECISIONS:
|
| 44 |
+
raise ValueError(f"precision must be one of {PRECISIONS}, not {precision!r}")
|
| 45 |
d = pathlib.Path(model_dir)
|
| 46 |
+
if onnx_file is None and precision == "fp16":
|
| 47 |
+
if not (d / FP16_FILE).exists():
|
| 48 |
+
raise FileNotFoundError(f"precision='fp16' needs {FP16_FILE} in {d}. It ships with release rc2.1 and later; "
|
| 49 |
+
"download it, or use precision='fp32'")
|
| 50 |
+
onnx_file = FP16_FILE
|
| 51 |
onnx_file = onnx_file or ("model.opt.onnx" if (d / "model.opt.onnx").exists() else "model.onnx")
|
| 52 |
self.onnx_file = onnx_file
|
| 53 |
+
self.precision = precision
|
| 54 |
self.config = json.loads((d / "config.json").read_text())
|
| 55 |
self.tok = Tokenizer.from_file(str(d / "tokenizer.json"))
|
| 56 |
self.tok.enable_truncation(self.config["max_len"])
|