Instructions to use dizyann/crivo-web with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers.js
How to use dizyann/crivo-web with Transformers.js:
// ⚠️ Unknown pipeline tag
feat: consolidate model variants in crivo-web
Browse files- README.md +20 -11
- biome.jsonc +1 -1
- scripts/benchmark/README.md +31 -0
- scripts/benchmark/playground.js +85 -0
- scripts/conversion/publish.py +3 -3
- space/README.md +4 -9
- space/app.ts +2 -2
- space/index.html +1 -1
- space/system-one.ts +3 -1
- space/worker.ts +1 -3
- src/cli/progress.ts +2 -2
- src/decisions/adapters/crivo.ts +16 -4
- src/decisions/models.ts +4 -11
- tests/models.test.ts +16 -0
- tests/system-one.test.ts +1 -1
README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
| 1 |
---
|
| 2 |
license: cc-by-nc-4.0
|
| 3 |
-
base_model:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
library_name: transformers.js
|
| 5 |
language:
|
| 6 |
- en
|
|
@@ -74,8 +80,8 @@ const run = decide(input, {
|
|
| 74 |
})
|
| 75 |
```
|
| 76 |
|
| 77 |
-
The default size is 150M **v2**, served from `dizyann/crivo-
|
| 78 |
-
|
| 79 |
The only runtime export remains `decide`; `ModelSize` and `ModelOptions` are types.
|
| 80 |
An injected `decisionRuntime` takes precedence over `model` options. When composing
|
| 81 |
through a Layer, provide `model: {}` alongside a runtime provider if using defaults.
|
|
@@ -88,12 +94,12 @@ conversion fixtures on native CPU, not an accuracy score.
|
|
| 88 |
|
| 89 |
| Model | FP32 (MiB) | INT8 (MiB) | Training context (tokens) | INT8 max probability difference |
|
| 90 |
| --- | ---: | ---: | ---: | ---: |
|
| 91 |
-
| [17M](https://huggingface.co/dizyann/crivo-
|
| 92 |
-
| [32M](https://huggingface.co/dizyann/crivo-
|
| 93 |
-
| [68M](https://huggingface.co/dizyann/crivo-
|
| 94 |
-
| [150M](https://huggingface.co/dizyann/crivo-
|
| 95 |
-
| [400M](https://huggingface.co/dizyann/crivo-
|
| 96 |
-
| [1B](https://huggingface.co/dizyann/crivo-
|
| 97 |
|
| 98 |
FP32 was checked against PyTorch, native JavaScript ONNX Runtime, and WebGPU for
|
| 99 |
all six variants. INT8 was checked on native CPU and WASM; WASM probabilities differed
|
|
@@ -108,7 +114,7 @@ Install Bun 1.4+ and the Hugging Face CLI (`hf`), then download the source witho
|
|
| 108 |
model weights. This includes nested source files, examples, configuration and the lockfile:
|
| 109 |
|
| 110 |
```bash
|
| 111 |
-
hf download dizyann/crivo-
|
| 112 |
cd crivo-web
|
| 113 |
bun install --frozen-lockfile
|
| 114 |
bun run decide examples/basic/input.json --device cpu
|
|
@@ -373,7 +379,10 @@ bun scripts/conversion/verify.ts 68m
|
|
| 373 |
```
|
| 374 |
|
| 375 |
Repeat the last two commands for each size. Output stays in `.cache/variants/<size>`;
|
| 376 |
-
source weights
|
|
|
|
|
|
|
|
|
|
| 377 |
The exporter checks varied sequence lengths, option counts and all three question types
|
| 378 |
against the original PyTorch model. The JavaScript verifier checks the same fixtures
|
| 379 |
against native ONNX Runtime. These are numerical smoke tests, not a rerun of Decision Index.
|
|
|
|
| 1 |
---
|
| 2 |
license: cc-by-nc-4.0
|
| 3 |
+
base_model:
|
| 4 |
+
- tardellirs/crivo-17m
|
| 5 |
+
- tardellirs/crivo-32m
|
| 6 |
+
- tardellirs/crivo-68m
|
| 7 |
+
- tardellirs/crivo-150m
|
| 8 |
+
- tardellirs/crivo-400m
|
| 9 |
+
- tardellirs/crivo-1b
|
| 10 |
library_name: transformers.js
|
| 11 |
language:
|
| 12 |
- en
|
|
|
|
| 80 |
})
|
| 81 |
```
|
| 82 |
|
| 83 |
+
The default size is 150M **v2**, served from `dizyann/crivo-web/models/150m`.
|
| 84 |
+
All variants live in `models/<size>/` in this repository. The legacy root weights remain unchanged; Hugging Face redirects the former `dizyann/crivo-150m-web` URL.
|
| 85 |
The only runtime export remains `decide`; `ModelSize` and `ModelOptions` are types.
|
| 86 |
An injected `decisionRuntime` takes precedence over `model` options. When composing
|
| 87 |
through a Layer, provide `model: {}` alongside a runtime provider if using defaults.
|
|
|
|
| 94 |
|
| 95 |
| Model | FP32 (MiB) | INT8 (MiB) | Training context (tokens) | INT8 max probability difference |
|
| 96 |
| --- | ---: | ---: | ---: | ---: |
|
| 97 |
+
| [17M](https://huggingface.co/dizyann/crivo-web/tree/main/models/17m) | 64.5 | 16.6 | 8,192 | 0.0270 |
|
| 98 |
+
| [32M](https://huggingface.co/dizyann/crivo-web/tree/main/models/32m) | 122.4 | 31.2 | 2,048 | 0.0260 |
|
| 99 |
+
| [68M](https://huggingface.co/dizyann/crivo-web/tree/main/models/68m) | 261.4 | 66.4 | 8,192 | 0.0321 |
|
| 100 |
+
| [150M](https://huggingface.co/dizyann/crivo-web/tree/main/models/150m) | 571.3 | 144.3 | 8,192 | 0.0863 |
|
| 101 |
+
| [400M](https://huggingface.co/dizyann/crivo-web/tree/main/models/400m) | 1510.8 | 380.1 | 8,192 | 0.0341 |
|
| 102 |
+
| [1B](https://huggingface.co/dizyann/crivo-web/tree/main/models/1b) | 3935.0 | 987.3 | 8,192 | 0.0088 |
|
| 103 |
|
| 104 |
FP32 was checked against PyTorch, native JavaScript ONNX Runtime, and WebGPU for
|
| 105 |
all six variants. INT8 was checked on native CPU and WASM; WASM probabilities differed
|
|
|
|
| 114 |
model weights. This includes nested source files, examples, configuration and the lockfile:
|
| 115 |
|
| 116 |
```bash
|
| 117 |
+
hf download dizyann/crivo-web --exclude 'onnx/*' --exclude 'models/*' --exclude 'tokenizer.json' --local-dir crivo-web
|
| 118 |
cd crivo-web
|
| 119 |
bun install --frozen-lockfile
|
| 120 |
bun run decide examples/basic/input.json --device cpu
|
|
|
|
| 379 |
```
|
| 380 |
|
| 381 |
Repeat the last two commands for each size. Output stays in `.cache/variants/<size>`;
|
| 382 |
+
source weights stay local. Publish generated graphs and metadata into this repository’s
|
| 383 |
+
`models/<size>/` directories with `python scripts/conversion/publish.py 17m 32m 68m 150m 400m 1b`.
|
| 384 |
+
The Hub stores the large binaries through Git LFS/Xet. Use `GIT_LFS_SKIP_SMUDGE=1 git clone`
|
| 385 |
+
when cloning only to work on source code.
|
| 386 |
The exporter checks varied sequence lengths, option counts and all three question types
|
| 387 |
against the original PyTorch model. The JavaScript verifier checks the same fixtures
|
| 388 |
against native ONNX Runtime. These are numerical smoke tests, not a rerun of Decision Index.
|
biome.jsonc
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
"$schema": "https://biomejs.dev/schemas/2.5.15/schema.json",
|
| 3 |
"files": {
|
| 4 |
"ignoreUnknown": true,
|
| 5 |
-
"includes": ["**", "!**/*.md", "!tokenizer.json", "!!**/.cache", "!!dist"]
|
| 6 |
},
|
| 7 |
"formatter": {
|
| 8 |
"indentStyle": "space",
|
|
|
|
| 2 |
"$schema": "https://biomejs.dev/schemas/2.5.15/schema.json",
|
| 3 |
"files": {
|
| 4 |
"ignoreUnknown": true,
|
| 5 |
+
"includes": ["**", "!**/*.md", "!tokenizer.json", "!!**/.cache", "!!dist", "!!models"]
|
| 6 |
},
|
| 7 |
"formatter": {
|
| 8 |
"indentStyle": "space",
|
scripts/benchmark/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Playground benchmark
|
| 2 |
+
|
| 3 |
+
Run `bun run browser`, open `http://localhost:4180` in a fresh browser tab, and paste
|
| 4 |
+
`playground.js` into DevTools before the first inference. Leave the default example
|
| 5 |
+
unchanged. The script operates the actual selectors and Run button and observes the
|
| 6 |
+
production worker messages; it does not substitute an inference implementation.
|
| 7 |
+
|
| 8 |
+
Inspect `window.crivoBenchmark.status` and `.error`. Once status is `complete`, save
|
| 9 |
+
`JSON.stringify(window.crivoBenchmark, null, 2)`. Each of the 17 supported configurations
|
| 10 |
+
runs once to initialize, three additional times to warm up, then 20 measured times.
|
| 11 |
+
All three questions are sent together in one request. Warm samples must report zero
|
| 12 |
+
model loading time. A 50 ms pause between requests lets the UI finish handling results.
|
| 13 |
+
Keep other workloads idle; hardware, power mode, browser and background activity affect timing.
|
| 14 |
+
|
| 15 |
+
`browser.json` records the measured input and all timing samples from the local playground.
|
| 16 |
+
Inference timing includes tokenization, the batched ONNX forward pass and answer mapping.
|
| 17 |
+
Worker total includes validation and other worker overhead, but excludes rendering and
|
| 18 |
+
main-thread messaging. First-run model setup and first inference are retained separately.
|
| 19 |
+
The README reports median and nearest-rank p95 inference times (19th of 20 sorted samples).
|
| 20 |
+
|
| 21 |
+
For the recorded run, exact locally converted weights were preloaded into the browser's
|
| 22 |
+
`transformers-cache` CacheStorage under their production Hugging Face resolve URLs.
|
| 23 |
+
These were the same artifacts published at the revision recorded in `browser.json`.
|
| 24 |
+
Model setup therefore measures cached asset reads and session initialization, **not downloads**.
|
| 25 |
+
To reproduce cached setup without local artifacts, run each selection once to download
|
| 26 |
+
its assets, reload the page, and then start the script. Do not clear browser storage.
|
| 27 |
+
Network speed is deliberately excluded. ONNX Runtime/CDN files may also be browser-cached.
|
| 28 |
+
|
| 29 |
+
The 1B FP32/WASM configuration is excluded because it exceeds the tested WASM memory
|
| 30 |
+
budget. INT8/WebGPU is unsupported. INT8 here is weight-only storage with FP32 arithmetic;
|
| 31 |
+
it is not expected to consistently outperform FP32.
|
scripts/benchmark/playground.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Run in a fresh `bun run browser` page's DevTools console. Uses the actual UI and worker.
|
| 2 |
+
window.crivoBenchmark = {
|
| 3 |
+
runs: [],
|
| 4 |
+
status: 'starting',
|
| 5 |
+
userAgent: navigator.userAgent,
|
| 6 |
+
threads: navigator.hardwareConcurrency
|
| 7 |
+
}
|
| 8 |
+
;(async () => {
|
| 9 |
+
const report = window.crivoBenchmark
|
| 10 |
+
const adapter = await navigator.gpu?.requestAdapter()
|
| 11 |
+
report.gpu = adapter
|
| 12 |
+
? {
|
| 13 |
+
...Object.fromEntries(
|
| 14 |
+
['vendor', 'architecture', 'device', 'description'].map((k) => [k, adapter.info[k]])
|
| 15 |
+
)
|
| 16 |
+
}
|
| 17 |
+
: null
|
| 18 |
+
const NativeWorker = window.Worker
|
| 19 |
+
let pending
|
| 20 |
+
let metrics
|
| 21 |
+
window.Worker = class extends NativeWorker {
|
| 22 |
+
constructor(...args) {
|
| 23 |
+
super(...args)
|
| 24 |
+
this.addEventListener('message', ({ data }) => {
|
| 25 |
+
if (data.kind === 'metrics') metrics = data.metrics
|
| 26 |
+
if (data.kind === 'error') pending?.reject(Error(data.message))
|
| 27 |
+
if (data.kind === 'result') {
|
| 28 |
+
if (!data.result.ok) pending?.reject(Error(JSON.stringify(data.result.error)))
|
| 29 |
+
else pending?.resolve({ ...metrics, totalMs: data.totalMs })
|
| 30 |
+
}
|
| 31 |
+
})
|
| 32 |
+
this.addEventListener('error', () => pending?.reject(Error('Worker stopped')))
|
| 33 |
+
}
|
| 34 |
+
postMessage(data, ...args) {
|
| 35 |
+
report.input = data.input
|
| 36 |
+
super.postMessage(data, ...args)
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
const run = () =>
|
| 40 |
+
new Promise((resolve, reject) => {
|
| 41 |
+
const timeout = setTimeout(() => reject(Error('Run timed out')), 240000)
|
| 42 |
+
pending = {
|
| 43 |
+
resolve: (data) => {
|
| 44 |
+
clearTimeout(timeout)
|
| 45 |
+
resolve(data)
|
| 46 |
+
},
|
| 47 |
+
reject: (error) => {
|
| 48 |
+
clearTimeout(timeout)
|
| 49 |
+
reject(error)
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
document.getElementById('run').click()
|
| 53 |
+
})
|
| 54 |
+
for (const size of ['17m', '32m', '68m', '150m', '400m', '1b']) {
|
| 55 |
+
for (const [device, dtype] of [
|
| 56 |
+
['cpu', 'q8'],
|
| 57 |
+
...(size === '1b' ? [] : [['cpu', 'fp32']]),
|
| 58 |
+
['gpu', 'fp32']
|
| 59 |
+
]) {
|
| 60 |
+
const entry = { size, device, dtype, samples: [] }
|
| 61 |
+
report.runs.push(entry)
|
| 62 |
+
report.status = `${size} ${device} ${dtype}`
|
| 63 |
+
for (const [id, value] of [
|
| 64 |
+
['size', size],
|
| 65 |
+
['device', device],
|
| 66 |
+
['dtype', dtype]
|
| 67 |
+
]) {
|
| 68 |
+
const select = document.getElementById(id)
|
| 69 |
+
select.value = value
|
| 70 |
+
select.dispatchEvent(new Event('change'))
|
| 71 |
+
}
|
| 72 |
+
await new Promise((resolve) => setTimeout(resolve, 50))
|
| 73 |
+
entry.first = await run()
|
| 74 |
+
for (let i = 0; i < 23; i++) {
|
| 75 |
+
await new Promise((resolve) => setTimeout(resolve, 50))
|
| 76 |
+
const result = await run()
|
| 77 |
+
if (result.loadMs !== 0) throw Error('Session was not reused')
|
| 78 |
+
if (i >= 3) entry.samples.push(result)
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
report.status = 'complete'
|
| 83 |
+
})().catch((error) => {
|
| 84 |
+
window.crivoBenchmark.error = String(error)
|
| 85 |
+
})
|
scripts/conversion/publish.py
CHANGED
|
@@ -5,7 +5,7 @@ parser=argparse.ArgumentParser();parser.add_argument('sizes',nargs='+');args=par
|
|
| 5 |
for size in args.sizes:
|
| 6 |
directory=Path('.cache/variants')/size
|
| 7 |
report=json.loads((directory/'parity.json').read_text())
|
| 8 |
-
repo=
|
| 9 |
source=report['source']
|
| 10 |
card=f'''---
|
| 11 |
license: cc-by-nc-4.0
|
|
@@ -20,7 +20,7 @@ Original architecture, training and weights by [tardellirs](https://huggingface.
|
|
| 20 |
No additional training was performed. Weights retain the CC BY-NC 4.0 license.
|
| 21 |
|
| 22 |
[Playground](https://huggingface.co/spaces/dizyann/crivo-demo) ·
|
| 23 |
-
[TypeScript library, CLI and model comparison](https://huggingface.co/dizyann/crivo-
|
| 24 |
|
| 25 |
Source revision: `{source['revision']}`.
|
| 26 |
See the [pinned original card](https://huggingface.co/{source['repo']}/blob/{source['revision']}/README.md)
|
|
@@ -41,4 +41,4 @@ older native ONNX Runtime versions can produce materially different INT8 probabi
|
|
| 41 |
'''
|
| 42 |
(directory/'README.md').write_text(card)
|
| 43 |
subprocess.run(['hf','repos','create',repo,'--type','model','--exist-ok'],check=True)
|
| 44 |
-
subprocess.run(['hf','upload',repo,str(directory),'--commit-message',f'Publish pinned Crivo {size} FP32 and INT8 web conversion'],check=True)
|
|
|
|
| 5 |
for size in args.sizes:
|
| 6 |
directory=Path('.cache/variants')/size
|
| 7 |
report=json.loads((directory/'parity.json').read_text())
|
| 8 |
+
repo='dizyann/crivo-web'
|
| 9 |
source=report['source']
|
| 10 |
card=f'''---
|
| 11 |
license: cc-by-nc-4.0
|
|
|
|
| 20 |
No additional training was performed. Weights retain the CC BY-NC 4.0 license.
|
| 21 |
|
| 22 |
[Playground](https://huggingface.co/spaces/dizyann/crivo-demo) ·
|
| 23 |
+
[TypeScript library, CLI and model comparison](https://huggingface.co/dizyann/crivo-web)
|
| 24 |
|
| 25 |
Source revision: `{source['revision']}`.
|
| 26 |
See the [pinned original card](https://huggingface.co/{source['repo']}/blob/{source['revision']}/README.md)
|
|
|
|
| 41 |
'''
|
| 42 |
(directory/'README.md').write_text(card)
|
| 43 |
subprocess.run(['hf','repos','create',repo,'--type','model','--exist-ok'],check=True)
|
| 44 |
+
subprocess.run(['hf','upload',repo,str(directory),f'models/{size}','--commit-message',f'Publish pinned Crivo {size} FP32 and INT8 web conversion'],check=True)
|
space/README.md
CHANGED
|
@@ -7,18 +7,13 @@ sdk: static
|
|
| 7 |
app_file: index.html
|
| 8 |
short_description: Typed decisions running locally in your browser
|
| 9 |
models:
|
| 10 |
-
- dizyann/crivo-
|
| 11 |
-
- dizyann/crivo-32m-web
|
| 12 |
-
- dizyann/crivo-68m-web
|
| 13 |
-
- dizyann/crivo-150m-v2-web
|
| 14 |
-
- dizyann/crivo-400m-web
|
| 15 |
-
- dizyann/crivo-1b-web
|
| 16 |
license: cc-by-nc-4.0
|
| 17 |
---
|
| 18 |
|
| 19 |
# Crivo Decision Playground
|
| 20 |
|
| 21 |
-
Explore choice, noul (probability of true), and ordered score decisions with [Crivo Web](https://huggingface.co/dizyann/crivo-
|
| 22 |
|
| 23 |
Edit the JSON state and decision definitions, then select **Run decisions**. Inference happens in your browser; request contents are not sent to a hosted inference service. Model and runtime assets download from the Hub and runtime CDN.
|
| 24 |
|
|
@@ -26,7 +21,7 @@ Choose 17M, 32M v2, 68M, 150M v2, 400M, or 1B. CPU/WASM supports INT8 weight sto
|
|
| 26 |
|
| 27 |
Scores are expected zero-based level indices. Probabilities are model estimates, not guarantees. The original model is primarily English. Weights retain the original CC BY-NC 4.0 license and non-commercial restriction. Original model: [tardellirs/crivo-150m](https://huggingface.co/collections/tardellirs/crivo).
|
| 28 |
|
| 29 |
-
Source and build instructions live in the [model repository](https://huggingface.co/dizyann/crivo-
|
| 30 |
|
| 31 |
## Credits
|
| 32 |
|
|
@@ -38,4 +33,4 @@ Questions follow the [official TypeSafe System One reference](https://docs.types
|
|
| 38 |
|
| 39 |
The Space translates this format into the local Crivo API. Structured descriptions are serialized as JSON, null choice descriptions use the option name, and omitted noul descriptions use true/false statements. Crivo requires at least two choices and retains its own token limit. This is format compatibility, not identical model preprocessing or predictions. Responses use `model` and `answers`, with `choice`, `noul`, or `score`/`legend` fields. Confidence and token usage are omitted because Crivo does not expose them.
|
| 40 |
|
| 41 |
-
All questions run together in a single ONNX batch.
|
|
|
|
| 7 |
app_file: index.html
|
| 8 |
short_description: Typed decisions running locally in your browser
|
| 9 |
models:
|
| 10 |
+
- dizyann/crivo-web
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
license: cc-by-nc-4.0
|
| 12 |
---
|
| 13 |
|
| 14 |
# Crivo Decision Playground
|
| 15 |
|
| 16 |
+
Explore choice, noul (probability of true), and ordered score decisions with [Crivo Web](https://huggingface.co/dizyann/crivo-web).
|
| 17 |
|
| 18 |
Edit the JSON state and decision definitions, then select **Run decisions**. Inference happens in your browser; request contents are not sent to a hosted inference service. Model and runtime assets download from the Hub and runtime CDN.
|
| 19 |
|
|
|
|
| 21 |
|
| 22 |
Scores are expected zero-based level indices. Probabilities are model estimates, not guarantees. The original model is primarily English. Weights retain the original CC BY-NC 4.0 license and non-commercial restriction. Original model: [tardellirs/crivo-150m](https://huggingface.co/collections/tardellirs/crivo).
|
| 23 |
|
| 24 |
+
Source and build instructions live in the [model repository](https://huggingface.co/dizyann/crivo-web). Build with `bun run space:build`; upload `dist/space` to a Static Space.
|
| 25 |
|
| 26 |
## Credits
|
| 27 |
|
|
|
|
| 33 |
|
| 34 |
The Space translates this format into the local Crivo API. Structured descriptions are serialized as JSON, null choice descriptions use the option name, and omitted noul descriptions use true/false statements. Crivo requires at least two choices and retains its own token limit. This is format compatibility, not identical model preprocessing or predictions. Responses use `model` and `answers`, with `choice`, `noul`, or `score`/`legend` fields. Confidence and token usage are omitted because Crivo does not expose them.
|
| 35 |
|
| 36 |
+
All questions run together in a single ONNX batch. Compare equivalent batches and precision settings when evaluating probabilities. FP32 batch/single equivalence is covered by the model integration tests.
|
space/app.ts
CHANGED
|
@@ -114,7 +114,7 @@ function render(answers: Record<string, DecisionAnswer<Decision>>) {
|
|
| 114 |
results.append(section)
|
| 115 |
}
|
| 116 |
outputEditor.value = JSON.stringify(
|
| 117 |
-
toResponse(answers, runDecisions, modelFor(size.value as ModelSize).modelId),
|
| 118 |
null,
|
| 119 |
2
|
| 120 |
)
|
|
@@ -196,7 +196,7 @@ element<HTMLFormElement>('form').onsubmit = (event) => {
|
|
| 196 |
}
|
| 197 |
render(data.result.answers)
|
| 198 |
element('metrics').textContent =
|
| 199 |
-
`${seconds(data.totalMs)} total · ${timings.loadMs === 0 ? 'session reused' : `${seconds(timings.loadMs)} model setup`} · ${seconds(timings.inferenceMs)} inference · ${dtype.value === 'q8' ? 'INT8' : 'FP32'} / ${device.value === 'gpu' ? 'WebGPU' : 'WASM'} · ${modelFor(size.value as ModelSize).modelId}`
|
| 200 |
finish('Finished locally. Model stays ready for the next run.')
|
| 201 |
}
|
| 202 |
}
|
|
|
|
| 114 |
results.append(section)
|
| 115 |
}
|
| 116 |
outputEditor.value = JSON.stringify(
|
| 117 |
+
toResponse(answers, runDecisions, modelFor(size.value as ModelSize).modelId, size.value),
|
| 118 |
null,
|
| 119 |
2
|
| 120 |
)
|
|
|
|
| 196 |
}
|
| 197 |
render(data.result.answers)
|
| 198 |
element('metrics').textContent =
|
| 199 |
+
`${seconds(data.totalMs)} total · ${timings.loadMs === 0 ? 'session reused' : `${seconds(timings.loadMs)} model setup`} · ${seconds(timings.inferenceMs)} inference · ${dtype.value === 'q8' ? 'INT8' : 'FP32'} / ${device.value === 'gpu' ? 'WebGPU' : 'WASM'} · ${modelFor(size.value as ModelSize).modelId} / ${size.value}`
|
| 200 |
finish('Finished locally. Model stays ready for the next run.')
|
| 201 |
}
|
| 202 |
}
|
space/index.html
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
<html lang="en">
|
| 3 |
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Crivo — Decision playground</title><link rel="stylesheet" href="./style.css"><script type="module" src="./app.ts"></script></head>
|
| 4 |
<body>
|
| 5 |
-
<header><a class="brand" href="https://huggingface.co/dizyann/crivo-
|
| 6 |
<main>
|
| 7 |
<section class="intro"><h1>Give context.<br>Get a decision.</h1><div><p>A small model for explicit choices.<br>Explore classification, yes/no probability, and ordered scores — right in your browser.</p><span class="local">Your input stays on this device.</span></div></section>
|
| 8 |
<div class="workspace">
|
|
|
|
| 2 |
<html lang="en">
|
| 3 |
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Crivo — Decision playground</title><link rel="stylesheet" href="./style.css"><script type="module" src="./app.ts"></script></head>
|
| 4 |
<body>
|
| 5 |
+
<header><a class="brand" href="https://huggingface.co/dizyann/crivo-web">crivo<span> / decision playground</span></a><a href="https://huggingface.co/dizyann/crivo-web">Model & documentation ↗</a></header>
|
| 6 |
<main>
|
| 7 |
<section class="intro"><h1>Give context.<br>Get a decision.</h1><div><p>A small model for explicit choices.<br>Explore classification, yes/no probability, and ordered scores — right in your browser.</p><span class="local">Your input stays on this device.</span></div></section>
|
| 8 |
<div class="workspace">
|
space/system-one.ts
CHANGED
|
@@ -74,10 +74,12 @@ export function fromQuestions(state: unknown, questions: unknown): DecideInput {
|
|
| 74 |
export function toResponse(
|
| 75 |
answers: Record<string, DecisionAnswer<Decision>>,
|
| 76 |
decisions: readonly Decision[],
|
| 77 |
-
modelId = 'dizyann/crivo-
|
|
|
|
| 78 |
) {
|
| 79 |
return {
|
| 80 |
model: modelId,
|
|
|
|
| 81 |
answers: Object.fromEntries(
|
| 82 |
Object.entries(answers).map(([id, answer]) => {
|
| 83 |
if (answer.type === 'choice')
|
|
|
|
| 74 |
export function toResponse(
|
| 75 |
answers: Record<string, DecisionAnswer<Decision>>,
|
| 76 |
decisions: readonly Decision[],
|
| 77 |
+
modelId = 'dizyann/crivo-web',
|
| 78 |
+
variant?: string
|
| 79 |
) {
|
| 80 |
return {
|
| 81 |
model: modelId,
|
| 82 |
+
...(variant ? { variant } : {}),
|
| 83 |
answers: Object.fromEntries(
|
| 84 |
Object.entries(answers).map(([id, answer]) => {
|
| 85 |
if (answer.type === 'choice')
|
space/worker.ts
CHANGED
|
@@ -45,9 +45,7 @@ self.onmessage = async ({ data }) => {
|
|
| 45 |
? 'unknown'
|
| 46 |
: cachedPaths.some(
|
| 47 |
(path) =>
|
| 48 |
-
path.includes(
|
| 49 |
-
`/${data.size === '150m' ? 'crivo-150m-v2-web' : `crivo-${data.size}-web`}/`
|
| 50 |
-
) && path.endsWith(`/${progress.file}`)
|
| 51 |
)
|
| 52 |
? 'cache'
|
| 53 |
: 'network'
|
|
|
|
| 45 |
? 'unknown'
|
| 46 |
: cachedPaths.some(
|
| 47 |
(path) =>
|
| 48 |
+
path.includes('/dizyann/crivo-web/') && path.endsWith(`/${progress.file}`)
|
|
|
|
|
|
|
| 49 |
)
|
| 50 |
? 'cache'
|
| 51 |
: 'network'
|
src/cli/progress.ts
CHANGED
|
@@ -34,7 +34,7 @@ export class TerminalProgress {
|
|
| 34 |
)
|
| 35 |
this.missing = new Set(files.filter((_, index) => !cached[index]))
|
| 36 |
this.write(
|
| 37 |
-
`Model: ${info.modelId} | precision: ${info.dtype} | providers: ${info.providers.join(', ')}`
|
| 38 |
)
|
| 39 |
if (this.missing.size) {
|
| 40 |
const weightMiB =
|
|
@@ -81,7 +81,7 @@ export class TerminalProgress {
|
|
| 81 |
)
|
| 82 |
if (this.info)
|
| 83 |
this.write(
|
| 84 |
-
`Model: ${this.info.modelId} | precision: ${this.info.dtype} | providers: ${this.info.providers.join(', ')}`
|
| 85 |
)
|
| 86 |
}
|
| 87 |
}
|
|
|
|
| 34 |
)
|
| 35 |
this.missing = new Set(files.filter((_, index) => !cached[index]))
|
| 36 |
this.write(
|
| 37 |
+
`Model: ${info.modelId}${info.variant ? ` / ${info.variant}` : ''} | precision: ${info.dtype} | providers: ${info.providers.join(', ')}`
|
| 38 |
)
|
| 39 |
if (this.missing.size) {
|
| 40 |
const weightMiB =
|
|
|
|
| 81 |
)
|
| 82 |
if (this.info)
|
| 83 |
this.write(
|
| 84 |
+
`Model: ${this.info.modelId}${this.info.variant ? ` / ${this.info.variant}` : ''} | precision: ${this.info.dtype} | providers: ${this.info.providers.join(', ')}`
|
| 85 |
)
|
| 86 |
}
|
| 87 |
}
|
src/decisions/adapters/crivo.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type { CrivoDevice } from './devices.js'
|
|
| 10 |
|
| 11 |
export interface ModelLoadInfo {
|
| 12 |
modelId: string
|
|
|
|
| 13 |
dtype: 'q8' | 'fp32'
|
| 14 |
providers: string[]
|
| 15 |
weightMiB?: number
|
|
@@ -99,11 +100,13 @@ export class Crivo {
|
|
| 99 |
static async load(options: CrivoLoadOptions = {}): Promise<Crivo> {
|
| 100 |
const selected = modelFor(options.size)
|
| 101 |
const modelId = options.modelId ?? selected.modelId
|
|
|
|
| 102 |
const execution = await resolveExecution(options)
|
| 103 |
const native = typeof process !== 'undefined' && process.release?.name === 'node'
|
| 104 |
if (
|
| 105 |
!native &&
|
| 106 |
-
|
|
|
|
| 107 |
execution.device === 'wasm' &&
|
| 108 |
execution.dtype === 'fp32'
|
| 109 |
)
|
|
@@ -120,10 +123,13 @@ export class Crivo {
|
|
| 120 |
const cacheDir = options.cacheDir ?? (native ? './.cache/crivo' : undefined)
|
| 121 |
await options.onLoadStart?.({
|
| 122 |
modelId,
|
| 123 |
-
...(
|
| 124 |
? {
|
| 125 |
weightMiB: Math.ceil(selected.assets[execution.dtype].bytes / 1024 ** 2),
|
| 126 |
-
|
|
|
|
|
|
|
|
|
|
| 127 |
}
|
| 128 |
: {}),
|
| 129 |
dtype: execution.dtype,
|
|
@@ -140,7 +146,13 @@ export class Crivo {
|
|
| 140 |
const tokenizer = await AutoTokenizer.from_pretrained(modelId, loading)
|
| 141 |
const model = await PreTrainedModel.from_pretrained(modelId, {
|
| 142 |
...loading,
|
| 143 |
-
...execution
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
})
|
| 145 |
// An encoder-only export cannot reproduce the Crivo decision interface.
|
| 146 |
const session = model.sessions.model as {
|
|
|
|
| 10 |
|
| 11 |
export interface ModelLoadInfo {
|
| 12 |
modelId: string
|
| 13 |
+
variant?: ModelSize
|
| 14 |
dtype: 'q8' | 'fp32'
|
| 15 |
providers: string[]
|
| 16 |
weightMiB?: number
|
|
|
|
| 100 |
static async load(options: CrivoLoadOptions = {}): Promise<Crivo> {
|
| 101 |
const selected = modelFor(options.size)
|
| 102 |
const modelId = options.modelId ?? selected.modelId
|
| 103 |
+
const family = !options.modelId || options.modelId === selected.modelId
|
| 104 |
const execution = await resolveExecution(options)
|
| 105 |
const native = typeof process !== 'undefined' && process.release?.name === 'node'
|
| 106 |
if (
|
| 107 |
!native &&
|
| 108 |
+
family &&
|
| 109 |
+
selected.size === '1b' &&
|
| 110 |
execution.device === 'wasm' &&
|
| 111 |
execution.dtype === 'fp32'
|
| 112 |
)
|
|
|
|
| 123 |
const cacheDir = options.cacheDir ?? (native ? './.cache/crivo' : undefined)
|
| 124 |
await options.onLoadStart?.({
|
| 125 |
modelId,
|
| 126 |
+
...(family
|
| 127 |
? {
|
| 128 |
weightMiB: Math.ceil(selected.assets[execution.dtype].bytes / 1024 ** 2),
|
| 129 |
+
variant: selected.size,
|
| 130 |
+
weightFiles: selected.assets[execution.dtype].files.map(
|
| 131 |
+
(file) => `models/${selected.size}/${file}`
|
| 132 |
+
)
|
| 133 |
}
|
| 134 |
: {}),
|
| 135 |
dtype: execution.dtype,
|
|
|
|
| 146 |
const tokenizer = await AutoTokenizer.from_pretrained(modelId, loading)
|
| 147 |
const model = await PreTrainedModel.from_pretrained(modelId, {
|
| 148 |
...loading,
|
| 149 |
+
...execution,
|
| 150 |
+
...(family
|
| 151 |
+
? {
|
| 152 |
+
subfolder: selected.subfolder,
|
| 153 |
+
use_external_data_format: selected.assets[execution.dtype].files.length - 1
|
| 154 |
+
}
|
| 155 |
+
: {})
|
| 156 |
})
|
| 157 |
// An encoder-only export cannot reproduce the Crivo decision interface.
|
| 158 |
const session = model.sessions.model as {
|
src/decisions/models.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import { MODEL_ASSETS } from './model-assets.js'
|
| 2 |
-
/** Latest upstream checkpoints.
|
| 3 |
export const MODEL_SIZES = ['17m', '32m', '68m', '150m', '400m', '1b'] as const
|
| 4 |
export type ModelSize = (typeof MODEL_SIZES)[number]
|
| 5 |
export interface ModelOptions {
|
|
@@ -7,16 +7,9 @@ export interface ModelOptions {
|
|
| 7 |
dtype?: 'q8' | 'fp32'
|
| 8 |
device?: 'cpu' | 'gpu' | 'auto'
|
| 9 |
}
|
| 10 |
-
const
|
| 11 |
-
'17m': { modelId: 'dizyann/crivo-17m-web' },
|
| 12 |
-
'32m': { modelId: 'dizyann/crivo-32m-web' },
|
| 13 |
-
'68m': { modelId: 'dizyann/crivo-68m-web' },
|
| 14 |
-
'150m': { modelId: 'dizyann/crivo-150m-v2-web' },
|
| 15 |
-
'400m': { modelId: 'dizyann/crivo-400m-web' },
|
| 16 |
-
'1b': { modelId: 'dizyann/crivo-1b-web' }
|
| 17 |
-
} satisfies Record<ModelSize, { modelId: string }>
|
| 18 |
|
| 19 |
export function modelFor(size: ModelSize = '150m') {
|
| 20 |
-
if (!Object.hasOwn(
|
| 21 |
-
return {
|
| 22 |
}
|
|
|
|
| 1 |
import { MODEL_ASSETS } from './model-assets.js'
|
| 2 |
+
/** Latest upstream checkpoints. All sizes share one Hub repository and tokenizer. */
|
| 3 |
export const MODEL_SIZES = ['17m', '32m', '68m', '150m', '400m', '1b'] as const
|
| 4 |
export type ModelSize = (typeof MODEL_SIZES)[number]
|
| 5 |
export interface ModelOptions {
|
|
|
|
| 7 |
dtype?: 'q8' | 'fp32'
|
| 8 |
device?: 'cpu' | 'gpu' | 'auto'
|
| 9 |
}
|
| 10 |
+
export const MODEL_REPO = 'dizyann/crivo-web'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
export function modelFor(size: ModelSize = '150m') {
|
| 13 |
+
if (!Object.hasOwn(MODEL_ASSETS, size)) throw new Error(`Unknown Crivo size: ${size}`)
|
| 14 |
+
return { modelId: MODEL_REPO, size, subfolder: `models/${size}/onnx`, assets: MODEL_ASSETS[size] }
|
| 15 |
}
|
tests/models.test.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { expect, test } from 'bun:test'
|
| 2 |
+
import { MODEL_REPO, MODEL_SIZES, modelFor } from '../src/decisions/models.js'
|
| 3 |
+
|
| 4 |
+
test('every variant resolves to its own graph folder in the shared repository', () => {
|
| 5 |
+
expect(MODEL_REPO).toBe('dizyann/crivo-web')
|
| 6 |
+
expect(modelFor().size).toBe('150m')
|
| 7 |
+
const folders = MODEL_SIZES.map((size) => {
|
| 8 |
+
const model = modelFor(size)
|
| 9 |
+
expect(model.modelId).toBe(MODEL_REPO)
|
| 10 |
+
expect(model.subfolder).toBe(`models/${size}/onnx`)
|
| 11 |
+
return model.subfolder
|
| 12 |
+
})
|
| 13 |
+
expect(new Set(folders).size).toBe(6)
|
| 14 |
+
expect(modelFor('1b').assets.fp32.files).toHaveLength(5)
|
| 15 |
+
expect(modelFor('1b').assets.q8.files).toHaveLength(2)
|
| 16 |
+
})
|
tests/system-one.test.ts
CHANGED
|
@@ -37,7 +37,7 @@ test('System One questions map to all three local decision types', () => {
|
|
| 37 |
request.decisions
|
| 38 |
)
|
| 39 |
).toEqual({
|
| 40 |
-
model: 'dizyann/crivo-
|
| 41 |
answers: {
|
| 42 |
route: { type: 'choice', choice: 'billing', probabilities: { billing: 0.8, support: 0.2 } },
|
| 43 |
urgent: { type: 'noul', noul: 0.9 },
|
|
|
|
| 37 |
request.decisions
|
| 38 |
)
|
| 39 |
).toEqual({
|
| 40 |
+
model: 'dizyann/crivo-web',
|
| 41 |
answers: {
|
| 42 |
route: { type: 'choice', choice: 'billing', probabilities: { billing: 0.8, support: 0.2 } },
|
| 43 |
urgent: { type: 'noul', noul: 0.9 },
|