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
Crivo Web — Typed decisions
Run typed choice, noul and score decisions through the decide API,
in Bun or the browser. A Commander.js CLI accepts the same state and decision definitions
as JSON. Inference runs locally with WASM, WebGPU or native ONNX Runtime providers.
The runtime supports the Crivo family by tardellirs, retaining its ModernBERT encoder, question-type embedding and option scorer. No additional training was performed. Web compatibility and TypeScript integration by Yann Cabral (dizyann). Credit for the original architecture, weights and training belongs to the original authors. Model weights remain licensed CC BY-NC 4.0.
Try the interactive browser playground to edit requests and inspect model probabilities.
Installation and quick start
Install Bun 1.4+ and the Hugging Face CLI (hf), then download the source without the
model weights. This includes nested source files, examples, configuration and the lockfile:
hf download dizyann/crivo-web --exclude 'onnx/*' --exclude 'models/*' --exclude 'tokenizer.json' --local-dir crivo-web
cd crivo-web
bun install --frozen-lockfile
bun run decide examples/basic/input.json --device cpu
The CLI announces missing model downloads before loading and prints a timing summary after the JSON response. The default 150M CPU mode downloads approximately 145 MiB of INT8 weights on first use. Later runs reuse the asset cache, but still initialize a model session.
bun run decide --help
cat examples/basic/input.json | bun run decide - --device cpu
bun run examples
bun run browser
The browser playground opens at http://localhost:4180/ and uses the same HTML,
JSON editors, and persistent model worker as the published Space. Select CPU/WASM
or GPU/WebGPU, model size and precision. GPU uses FP32 weights.
The command builds the Space assets and serves them locally.
Restart bun run browser after source changes; hot reloading is disabled.
Usage
The sole runtime export is decide. It accepts a JSON-serializable
state and an array of independent decisions, returning answers keyed by decision ID.
The tokenizer and resource lifecycle are implementation details; model execution can be configured through requirements.
Type-only exports describe decisions, results, failures and the optional runtime provider.
import { decide } from './src/index.js'
const result = await decide({
state: { message: 'Please refund the duplicate payment.' },
decisions: [
{
id: 'intent',
type: 'choice',
instruction: 'Identify the primary customer intent.',
options: [
{ value: 'refund', description: 'The customer wants money returned.' },
{ value: 'other', description: 'The customer has another request.' }
]
},
{
id: 'duplicateCharge',
type: 'noul',
instruction: 'The customer reports a duplicate charge.',
outcomes: {
false: { description: 'There is no reported duplicate charge.' },
true: { description: 'The same payment was charged more than once.' }
}
},
{
id: 'urgency',
type: 'score',
instruction: 'Rate the urgency using the ordered levels.',
levels: [
{ value: 'low', description: 'An informational request with no loss.' },
{ value: 'high', description: 'A financial loss requiring prompt attention.' }
]
}
]
})
.onSuccess((answers) => ({ ok: true as const, answers }))
.onError({ type: 'invalid-input' }, (error) => ({ ok: false as const, error }))
.onError({ type: 'input-too-long' }, (error) => ({ ok: false as const, error }))
if (result.ok) {
result.answers.intent.value // 'refund' | 'other'
result.answers.intent.probabilities.refund // number
result.answers.duplicateCharge.probability // P(true), between 0 and 1
result.answers.urgency.score // expected zero-based level index, between 0 and 1 here
result.answers.urgency.probabilities.high // number
}
decide.run(input, options) is equivalent to decide(input, options). Each run exposes
id, status(), cancel(), onSuccess() and onError() controls.
Cancellation rejects the run but does not interrupt an already-running inference;
resource cleanup happens when the underlying work finishes.
Each decision requires a unique non-empty id and a non-empty instruction.
Choice options and ordered score levels require unique non-empty value keys and
non-empty description metadata. Optional label is presentation-only and is not
sent to the model. At least two options/levels are required. Noul requires descriptions
for both false and true. The decision array must not be empty.
The order of score levels is meaningful: score is the expected index from 0 to
levels.length - 1, potentially fractional, not a percentage or a selected label.
Probabilities use the supplied level values as keys. Each decision receives the same
state, without consuming other decisions' answers.
Inline inputs infer literal IDs and option values automatically. For reusable input
constants, use as const satisfies DecideInput. JSON loaded at runtime still needs a
TypeScript type at the call site; runtime validation does not invent literal types.
State supports JSON primitives, arrays and plain objects; cycles, undefined, bigint,
non-finite numbers and class instances are rejected before model loading.
invalid-input contains path and message. input-too-long contains decisionId,
tokens and limit. Handle declared failures before awaiting the run. Unexpected
runtime failures (including download or backend errors) reject normally.
Select a model
Choose size (17m, 32m, 68m, 150m, 400m, or 1b),
dtype (q8 or fp32), and device (auto, cpu, or gpu) through
requirements.model, or the equivalent CLI flags. In the browser, WebGPU requires
FP32; 1B on CPU/WASM requires INT8. The default size is 150M v2.
bun run decide examples/basic/input.json --size 68m --dtype q8 --device cpu
bun run decide examples/basic/input.json --size 400m --dtype fp32 --device gpu
const run = decide(input, {
requirements: { model: { size: '68m', dtype: 'q8', device: 'cpu' } }
})
The default size is 150M v2, served from dizyann/crivo-web/models/150m.
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.
The only runtime export remains decide; ModelSize and ModelOptions are types.
An injected decisionRuntime takes precedence over model options. When composing
through a Layer, provide model: {} alongside a runtime provider if using defaults.
React: typed hook integration pattern
The following example describes an application-defined useCrivo hook. The project
currently exports decide, not a React hook; this is a proposed integration contract,
not a ready-to-import implementation. React and TanStack Query are not bundled.
The hook would use TanStack Query's useQuery for lazy model-session initialization,
keyed by size, precision and device, with automatic refetching disabled. Its decide
method would trigger inference explicitly, reuse the session and return typed answers.
A browser implementation should run inference in a Worker, serialize requests sharing
one session, and release resources when no longer used; query-cache eviction alone does
not dispose ONNX sessions.
import { useState } from 'react'
import type { DecideInput } from './src/index.js'
import { useCrivo } from './hooks/useCrivo' // Application-defined; not provided yet.
const input = {
state: { message: 'Please refund the duplicate payment.' },
decisions: [
{
id: 'intent',
type: 'choice',
instruction: 'Identify the customer intent.',
options: [
{ value: 'refund', description: 'The customer wants money returned.' },
{ value: 'other', description: 'Any other request.' }
]
}
]
} as const satisfies DecideInput
function DecisionPanel() {
const { decide, loading, deciding, error } = useCrivo({
size: '150m',
dtype: 'fp32',
device: 'gpu'
})
const runDecision = () => decide(input)
type Answers = Awaited<ReturnType<typeof runDecision>>
const [answers, setAnswers] = useState<Answers | null>(null)
async function handleDecide() {
try {
setAnswers(await runDecision())
} catch {
// The proposed hook exposes the failed request through `error`.
}
}
// answers?.intent.value: 'refund' | 'other' | undefined
// answers?.intent.probabilities.refund: number | undefined
return (
<section>
<button disabled={loading || deciding} onClick={handleDecide}>
{loading ? 'Loading model…' : deciding ? 'Deciding…' : 'Run'}
</button>
{error && <p role="alert">{error.message}</p>}
{answers && <p>{answers.intent.value}</p>}
</section>
)
}
as const satisfies DecideInput validates the input while preserving literal IDs and
option values. The hook's decide method must retain that generic relationship:
import type { DecideInput, Decision, DecisionResults } from './src/index.js'
type HookDecide = <const D extends readonly Decision[]>(
input: DecideInput<D>
) => Promise<DecisionResults<D>>
This makes the component's state infer intent.value as 'refund' | 'other', without
unknown, type assertions on answers, or a separately maintained response interface.
The proposed hook also exposes preload() to initialize early, ready for session
availability, and progress for download updates when available. loading covers
model download/setup; deciding covers inference. Its promise rejects on invalid
input or execution failure, and error exposes the failure for rendering.
Choosing a model
Latest checkpoints in the upstream collection, inspected October 19, 2026. These are author-reported Decision Index Public 0.3 scores, with all 140,178 requests completed. They are not accuracy percentages, Full scores, or benchmarks of our ONNX/INT8 conversions. Each model link pins the source card revision.
| Model | Public 0.3 ↑ | Knowledge / Language / Retrieval / Tools / Arts ↑ | Latency median / mean / p95 (ms) ↓ | Published hardware |
|---|---|---|---|---|
| 17M | 22.15 | 1.2 / 21.0 / 40.1 / 36.5 / 17.0 | 7.7 / 9.7 / 12.4 | A100 40 GB |
| 32M v2 | 27.34 | 2.7 / 25.0 / 45.6 / 50.0 / 19.0 | 26.9 / 30.5 / 41.3 | A100, shared workload |
| 68M | 32.82 | 5.0 / 30.8 / 48.8 / 64.0 / 20.9 | 16.4 / 21.2 / 28.6 | A100 |
| 150M v2 | 34.38 | 8.3 / 34.5 / 48.2 / 62.8 / 21.8 | 18.0 / 26.1 / 44.4 | A100 |
| 400M | 39.79 | 11.5 / 44.3 / 51.0 / 69.0 / 25.4 | 23 / 38 / 74 | A100 |
| 1B | 47.77 | 18.5 / 56.3 / 55.8 / 78.7 / 28.9 | 13.2 / 36.4 / 94.1 | H100 PCIe 80 GB |
Category headings abbreviate Knowledge & Reasoning, Language, Retrieval & Classification, Tools & Automation, and Arts & Taste. Do not rank browser speed from this latency column: upstream uses BF16 on server GPUs; 32M v2 was measured alongside a training workload, and 1B uses a different GPU. Full benchmark scores are pending for these checkpoints. Older 32M/150M results belong to earlier weights.
Start with 17M or 32M when download size and memory dominate. Try 68M or 150M for more capable classification and automation. Evaluate 400M or 1B when the higher published scores justify larger downloads and memory use. These are starting points; measure your own task, especially confidence thresholds after INT8 quantization. The 68M Tools score exceeds 150M v2 here: bigger is not better on every category.
Select size, precision and device independently. CPU supports INT8 (q8) and FP32;
WebGPU currently requires FP32: the tested INT8 graphs did not meet cross-backend numerical parity. INT8 stores weights in 8 bits and computes in FP32. It reduces download size;
it does not guarantee faster inference or smaller runtime memory. FP32 is recommended
for benchmark comparisons. Runtime memory exceeds weight download size,
especially with long states or many questions. The browser wrapper caps each encoded
question at 7,999 tokens; it does not claim upstream's extended context coverage.
Measured in the browser playground
Measured with bun run browser, Apple M4 / 24 GiB, macOS 26.6.2,
headless Chromium 154, WebGPU via Metal, ONNX Runtime Web 1.30.0.
The page was not cross-origin isolated (WASM threading unavailable). This was a local
development session, without controlled power or thermal conditions.
The default examples/basic request contains three questions in one batch.
Each configuration had one initialization run, three more warmups, then 20 measured
runs with the same session. Times include tokenization, ONNX inference and answer mapping;
they exclude model setup and UI rendering. These are workload-specific measurements,
not latency promises or a rerun of Decision Index. Longer inputs and different devices
can change both latency and model rankings.
Warm inference: median / p95, milliseconds per complete three-question request.
| Model | CPU · WASM · INT8 | CPU · WASM · FP32 | GPU · WebGPU · FP32 |
|---|---|---|---|
| 17M | 61.4 / 69.3 | 57.1 / 69.5 | 12.6 / 16.1 |
| 32M | 187.0 / 215.2 | 168.0 / 172.0 | 14.6 / 16.4 |
| 68M | 577.6 / 596.8 | 535.0 / 551.8 | 33.2 / 36.3 |
| 150M | 1466.2 / 1526.9 | 1302.0 / 1368.1 | 68.4 / 70.1 |
| 400M | 4406.5 / 4517.8 | 3903.4 / 4017.0 | 192.0 / 197.8 |
| 1B | 12442.0 / 13207.4 | Unsupported | 486.8 / 504.3 |
Model setup from cached weights, seconds (one observation per configuration). This includes cached asset reads and session initialization, but not network downloads or the first inference. Reusing a session makes this setup time zero on subsequent runs.
| Model | CPU · WASM · INT8 | CPU · WASM · FP32 | GPU · WebGPU · FP32 |
|---|---|---|---|
| 17M | 0.36 | 0.19 | 0.21 |
| 32M | 0.22 | 0.26 | 0.30 |
| 68M | 0.42 | 0.67 | 0.75 |
| 150M | 0.62 | 1.33 | 1.58 |
| 400M | 1.27 | 4.73 | 4.82 |
| 1B | 3.72 | — | 6.31 |
INT8 reduces weight downloads; these measurements do not show a consistent speed advantage. For interactive browser use, evaluate smaller variants on WASM or FP32 on WebGPU, then validate decision quality on your own inputs. The 1B FP32/WASM combination is disabled because it exceeded the tested memory budget; INT8/WebGPU is also unsupported.
Raw samples and exact input · Reproduction procedure · UI benchmark script.
Download sizes and conversion checks
Weights only; tokenizer and runtime downloads are additional. Memory use is higher. The last column is the largest INT8–FP32 probability difference across the three conversion fixtures on native CPU, not an accuracy score.
| Model | FP32 (MiB) | INT8 (MiB) | Training context (tokens) | INT8 max probability difference |
|---|---|---|---|---|
| 17M | 64.5 | 16.6 | 8,192 | 0.0270 |
| 32M | 122.4 | 31.2 | 2,048 | 0.0260 |
| 68M | 261.4 | 66.4 | 8,192 | 0.0321 |
| 150M | 571.3 | 144.3 | 8,192 | 0.0863 |
| 400M | 1510.8 | 380.1 | 8,192 | 0.0341 |
| 1B | 3935.0 | 987.3 | 8,192 | 0.0088 |
FP32 was checked against PyTorch, native JavaScript ONNX Runtime, and WebGPU for
all six variants. INT8 was checked on native CPU and WASM; WASM probabilities differed
from the native INT8 fixtures by up to 0.034 in these checks. Backend-specific
threshold calibration is necessary. The bundled numerical fixtures do not establish
benchmark accuracy for INT8. 1B FP32 is disabled on WASM after an allocation
failure in testing; choose INT8 on CPU or FP32 on WebGPU. Native CPU supports both. See scripts/conversion/results.json for provenance.
Execution
Importing the public entry does not load Transformers.js or download weights.
The default runtime loads on demand, shares one session across the decisions of a
run, and disposes it in finally. Separate calls create separate sessions; cached
assets avoid repeated downloads, but session initialization still has a cost.
All questions in a request are encoded as rows of one ONNX batch and evaluated in one forward pass. Token and option padding are masked per question; answers keep the original IDs and option keys. FP32 batch probabilities match individual runs within the tested tolerance of 1e-4. INT8 changes probabilities. Compare equivalent batches and calibrate thresholds on the selected backend.
The default runtime selects INT8 on CPU/WASM and FP32 on GPU. Native assets use
./.cache/crivo; browser assets use Transformers.js Cache Storage. For the default 150M v2, downloads are approximately 145 MiB for INT8 or 572 MiB for FP32, plus runtime/tokenizer assets. Other sizes have different requirements.
No hosted inference endpoint or API token is required for these public models.
A structural DecisionRuntime can be supplied through
decide(input, { requirements: { decisionRuntime } }) for tests or another backend.
A runtime must preserve IDs, answer discriminants and option keys,
and owns cleanup of resources it allocates. Backend configuration lives in the
composition, not in the state or decision definitions.
Development and examples
Terminal CLI
The Commander.js CLI accepts a JSON file containing { "state": ..., "decisions": [...] },
or - to read the same document from stdin. A complete request with all three decision
types is available in examples/basic/input.json.
bun run decide --help
bun run decide examples/basic/input.json --device cpu
cat examples/basic/input.json | bun run decide - --device cpu
bun run decide request.json --device auto --cache-dir ./.cache/crivo
Options: --size 17m|32m|68m|150m|400m|1b (default 150m), --device auto|cpu|gpu (default auto), --dtype q8|fp32 (default selected
for the device), and --cache-dir <path>. CLI configuration stays outside the request
document. Success writes { "ok": true, "answers": ... } as JSON to stdout, with exit
code 0. Input and inference failures write { "ok": false, "error": ... } to stderr,
with exit code 1. Commander prints usage errors and help as text. Native backend
diagnostics may also appear on stderr.
Before loading, the CLI checks the selected model's cache files and announces missing downloads with an estimate for the selected size and precision. Download progress, session initialization notices and the final timing summary go to stderr. The summary includes total elapsed time, loading/download time, inference time, resource cleanup time, model ID, precision and configured execution providers. Inference time includes input encoding and output decoding for all decisions. Providers describe the configured backend chain; individual graph nodes may fall back to CPU. Even with cached assets, session initialization takes time.
Development commands
bun install
bun run examples
bun run browser
bun run models:download --device cpu
bun run check
bun run typecheck
bun test
TEST_MODEL=1 bun test tests/model.integration.test.ts
examples/basic/input.ts demonstrates all three types and examples/basic/run.ts
uses only the public API. After changing the typed input, run bun run examples:generate
to update input.json; a test checks that both examples stay synchronized. The shared browser playground runs at http://localhost:4180/
with a Compute menu for CPU/WASM or GPU/WebGPU.
The model integration test is opt-in; MODEL_CACHE selects an existing cache.
Ordinary tests use a runtime provider and do not download or load model weights.
Implementation layout:
src/index.ts: publicdecideexport and types.src/decisions/: decision types, input validation and runtime requirement.src/decisions/adapters/: internal Crivo/Transformers.js implementation and devices.tests/: API behavior, compile-time inference and optional model integration.examples/basic/: typed input and terminal example.space/: shared HTML, JSON editors, and inference worker for local and hosted demos.scripts/serve-space.ts: serves the built playground locally.scripts/download-models.ts: downloads model assets into the local cache.scripts/generate-example.ts: generates the CLI JSON example from its typed source.- Root model JSONs and
onnx/: Hugging Face-compatible model assets.
Space demo
space/ contains the interactive Static Space. It runs inference in a Web Worker,
with CPU/WASM selected by default and optional WebGPU. The Space keeps its worker
and model session alive between runs, releasing the previous session when switching
size, precision or backend on the next run. The CLI and default API runtime still release resources per call.
bun run space:build
hf upload dizyann/crivo-demo dist/space --type space
The build writes deployable assets and a Space-specific README to dist/space.
All model variants live in dizyann/crivo-web, under models/<size>/, and download on demand.
Graph interface
| Tensor | Type | Shape |
|---|---|---|
input_ids |
int64 | batch × sequence |
attention_mask |
int64 | batch × sequence |
opt_positions |
int64 | batch × options |
opt_mask |
bool | batch × options |
qtype |
int64 | batch |
logits (output) |
float32 | batch × options |
Question types: noul=0, choice=1, score=2. The TypeScript wrapper implements all three, with a corresponding return type for each question.
Input format: [CLS] instructions [SEP] state [SEP] [OPT] key: description .... Serialize JSON state with sorted keys, compact separators, and unescaped Unicode. The example preserves the original encoding and pads sequences to multiples of 16. It rejects inputs above 7,999 tokens rather than truncating them.
Current API checks
- Offline tests cover metadata validation, declared failures, runtime injection, CLI errors, download notices, timing summaries and example synchronization.
- TypeScript checks cover inference of decision IDs, option values and answer types.
- Opt-in CPU INT8 integration tests exercise all three decision types and verify session cleanup after a token-limit failure.
- CLI file/stdin input and the default native backend were exercised with real weights.
- The browser demo was exercised end to end with real CPU/WASM weights through
bun run browseron Bun 1.4.0: choice, noul and score returned successfully with no JavaScript errors.
Historical model validation
These results describe the original conversion and examples. They are not accuracy claims for the current generic API; the circuit benchmark is no longer included.
- FP32 ONNX was compared with the PyTorch implementation on three inputs with different sequence lengths and option counts; the export script checks logits using
rtol=1e-3,atol=1e-4. - FP32 Transformers.js probabilities matched PyTorch on those three inputs within
1e-4absolute error. - In a local Node.js CPU run, both FP32 and INT8 passed 27/27 circuit-editor examples with deterministic target-resolution rules. Median decision latency after one warmup was approximately 480 ms for FP32 and 196 ms for INT8. These are small, application-specific checks, not a general accuracy benchmark or a performance guarantee.
- After renaming the circuit state key to
prompt, the INT8 wrapper still passed all 27 circuit-editor examples. The three published examples were executed and their output types, probability ranges, and score calculation were checked before publishing. - Browser WASM and FP32 WebGPU ran choice/noul/score. In the three-question test, FP32 probability/score differences were below
1.4e-6. Browser on-demand loading from Hugging Face and cached loading with model-network requests blocked both passed. - Apple Silicon CoreML was tested through Bun; 610 of 1,561 graph nodes were assigned to CoreML, with the remainder on CPU. This does not establish a performance improvement over CPU.
- INT8 changes numerical probabilities; validate confidence thresholds and accuracy on your own inputs.
Conversion details
The complete decision model was exported with PyTorch's legacy ONNX exporter, opset 17, using eager ModernBERT attention with reference compilation disabled. The current family uses per-channel signed INT8 weight storage with DequantizeLinear and FP32 arithmetic. This reduces downloads, but is not a promise of faster inference or lower runtime memory. The root legacy 150M files retain their original dynamic INT8 conversion. parity.json contains the original small PyTorch reference set. Inference and examples use TypeScript only.
Reproduce the family conversions
Conversion tools live under scripts/conversion/ and are not runtime dependencies.
sources.json pins all six upstream snapshots; results.json records numerical checks.
Use Python 3.14 and the pinned conversion packages:
python3 -m venv .cache/conversion-env
.cache/conversion-env/bin/pip install -r scripts/conversion/requirements.txt
.cache/conversion-env/bin/python scripts/conversion/download.py
.cache/conversion-env/bin/python scripts/conversion/export.py 68m
bun scripts/conversion/verify.ts 68m
Repeat the last two commands for each size. Output stays in .cache/variants/<size>;
source weights stay local. Publish generated graphs and metadata into this repository’s
models/<size>/ directories with python scripts/conversion/publish.py 17m 32m 68m 150m 400m 1b.
The Hub stores the large binaries through Git LFS/Xet. Use GIT_LFS_SKIP_SMUDGE=1 git clone
when cloning only to work on source code.
The exporter checks varied sequence lengths, option counts and all three question types
against the original PyTorch model. The JavaScript verifier checks the same fixtures
against native ONNX Runtime. These are numerical smoke tests, not a rerun of Decision Index.
License and limitations
The original weights are licensed CC BY-NC 4.0, including the non-commercial restriction. This conversion retains that license. See the original model card for attribution, training sources, intended uses, and limitations. The original model is primarily English; historical Portuguese checks do not establish general multilingual accuracy. No endorsement by the original authors is implied.
- Downloads last month
- 108
Model tree for dizyann/crivo-web
Base model
jhu-clsp/ettin-encoder-150m