Skip to the demos
v0.2Open source, MIT licensed

Typed decisions over your data, right in the browseron WebGPUin Node.js and Bunoffline, on the device

Run typed decisions on text and images right in your browser, or in Node.js and Bun: yes/no and multiple choice, entities, personal data, evidence, relations, image regions and OCR checks. The model, kyr0/winzling-decide-1.0, builds on Vela3-fast by the vLLM Semantic Router team and is optimized to be much smaller while keeping its answers, so it runs fast on edge devices.

bun add defuss-decide

Model, inference and decision pipeline implemented by Aron Homberg (kyr0) (opens in a new tab) - Open Source (MIT (opens in a new tab)). Model: kyr0/winzling-decide-1.0 (opens in a new tab) on Hugging Face

Natural language question:

Is the order fulfilled?

  • yes0.943
  • no0.057

Evidence order.status: delivered

smaller download for the four text tasks in q4
12.7x
or less per short text question in Chromium and WebKit on WebGPU
90 ms
runtimes, same answers: Node.js, Bun, Chromium, WebKit
4
bundles validated against the original models
26

Why it is different

§Small models that stay close to the originals

One library, every runtime

Node.js, Bun and the browser run the same code on the same models: WebGPU where the device has it, else WASM or the CPU.

Downloads you can ship

The q4 bundles share one backbone file: all four text tasks take 482 MB instead of 6.1 GB as full-precision bundles.

Your question, no training

Write any yes/no or multiple-choice question in plain words. The answer comes with a probability per option and references to the fields it read.

Same answers as the original

Every bundle reproduces the original model's outputs on its reference inputs. A bundle that fails the check does not load.

Exact offsets

Spans, entities and personal data come with character offsets into your input, correct for any Unicode text.

Private by design

Only the model files download. State, questions and images stay in the tab or the process.

Live demos

§Edit the input, run it, see the answer

Every tab opens with a recorded answer of the real models. Pick another example, change the state or the question, or drop in your own image, then run it here with Ctrl Enter.

Typed decisions. Any yes/no or multiple-choice question over JSON, text and images, without training for it: the image adapters add what they see to the state, evidence marks the fields that matter, and the decision head answers with probabilities.

    JSON
    JSON

    ResultCached

    yes0.943Decision
    no0.057
    Evidence in the state

    order.id: 17 order.status: deliveredevidence order.items.0.sku: A1 order.items.0.ordered: 2 order.items.0.shipped: 2 order.delivered_at: 2026-11-03

    Answer JSON
    {  "type": "decide",  "decision": "yes",  "probabilities": {    "no": 0.0568,    "yes": 0.9432  },  "evidence": [    {      "start": 13,      "end": 36,      "label": "evidence",      "probability": 0.7879,      "score": 0.7879,      "observations_ref": [],      "state_ref": [        "order.status"      ]    }  ],  "context": "order.id: 17\norder.status: delivered\norder.items.0.sku: A1\norder.items.0.ordered: 2\norder.items.0.shipped: 2\norder.delivered_at: 2026-11-03",  "observations": []}
    Where a run loads its bundles

    A run downloads the real bundles of kyr0/winzling-decide-1.0 from Hugging Face as Brotli files, keeps them in the browser's cache and answers with the library code shown under "Get started". Each task loads its smallest bundle unless you pick a larger precision; the text decision bundle in q4 changes 3 of 177 test answers against FP32. For your own exported bundles, run make demo and enter /models/, or any folder laid out like the repository with .br copies.

    How it works

    §From your input to a typed answer

    A question runs up to four small models one after another, all inside ONNX Runtime. The models return numbers, never generated text, so every answer can be checked against the input.

    Your input
    Images
    Context
    Models
    Answer
    Imageoptional, a file or a URL
    State and questionJSON or text
    Image tasksRegion and OCR checkwhat the image shows
    Text the heads readContext linespath: value, plus findings
    ExplainsEvidence adaptermarks the lines that matter
    DecidesDecision headyes/no or your options
    Typed answerdecision, probabilities, references
    1. pixels
    2. findings
    3. flattened
    4. lines
    5. lines
    6. references
    7. probabilities

    §No model writes text

    The models return probabilities, spans of your input and boxes. Every reference points into what you sent.

    §Everything runs where you call it

    ONNX Runtime runs the bundles on WebGPU, WASM or the CPU. Only the model files are downloaded, once, and the browser caches them.

    §Checked on every export

    A bundle is written only after ONNX Runtime matched the original PyTorch model on recorded inputs, and it loads only with that mark.

    The models

    §Two backbones, with adapters and heads on top

    Ten models come from two families, one for text and one for images. In each family an encoder turns the input into numbers per token, a task adapter adjusts that encoder for one task, and a small readout turns the result into scores. A box in the figures sits on the box below it.

    §Text models

    Exported from vllm-sr/vela3-fast

    Decisions
    Five span tasks
    Readout
    Decision headone score per answer option
    Span readoutone score per word and label
    Adapter
    No adapterthe head reads the backbone as trained
    LoRA adapter per tasksmall matrices beside all 168 backbone matrices, and its own layers 20 to 23
    Backbone
    UltraBERT text encoder24 layers, 1,024 numbers per token, 168 weight matrices; the same frozen weights under all six models
    Input
    Tokenizer and token table49,152 tokens and 8 marker tokens, such as the ones for the question and for each option

    §Image models

    Exported from vllm-sr/vela3-fast-vision

    Decisions
    Claims and OCR
    Regions
    Readout
    Decision headone score per answer option
    Span readoutone score per word and label
    Region readoutone score per image cell
    Adapter
    No adapterthe head reads its backbone as trained
    LoRA adapter per taskrank 64, for image claims and for the OCR check
    LoRA adapterrank 64, for image regions
    Backbone
    UltraBERT text encoder24 layers, 768 numbers per token; weights trained for decisions
    UltraBERT text encoderthe same size; base weights, one copy for both tasks
    UltraBERT text encoderthe same size; its own decision-trained weights
    Input
    SigLIP image encoder and token table12 layers over 512 px tiles in 16 px patches; its features take the place of the image tokens in the text

    Measured, not promised

    §Size, speed and fidelity in numbers

    Every value comes from the project's recorded measurements: how much a browser downloads, how long a question takes, and how close the smaller bundles stay to full precision.

    §What a browser downloads

    Each task's smallest bundle in MB, Brotli-compressed as the page fetches it. The q4 tasks of a kind share one backbone, which downloads once and has its own bar.

    Download size in MB
    DownloadPrecisionMB
    Text backbone sharedq4202
    Evidenceq462
    Entitiesq81,084
    Relationsq492
    Labeled spansq462
    PII and claimsq462
    Image backbone sharedq4116
    Image regionsq8420
    OCR checkq437
    Image claimsq437
    Text decisionsq411
    Image decisionsq4136

    Source: the .br files a load fetches (scripts/model-sizes.ts), reports/model-sizes.json

    §Time per question

    Median time of the q8 and q4 bundles on an Apple M-series Mac; a shorter bar is faster.

    Milliseconds per question
    Runtime, backend, precisionShort text1,800 tokensImage
    Node.js, CPU, q832.02,1421,675
    Node.js, CPU, q482.54,9944,107
    Node.js, WebGPU, q853.0353296
    Node.js, WebGPU, q450.5429379
    Chromium, WebGPU, q890.0-377
    Chromium, WebGPU, q476.5-482
    WebKit, WebGPU, q879.0-406
    WebKit, WebGPU, q473.5-465
    Chromium, WASM, q81,062-22,895
    Chromium, WASM, q4805-25,011

    Source: scripts/bench.ts (Node.js) and scripts/browser-tasks.ts (Chromium, WebKit), reports/bench and reports/tasks

    §Smaller bundles, nearly the same scores

    How far any score moved against full precision (FP32) on the reference inputs, for the precision each task loads here: -1% is a probability going from 0.90 to 0.89 or to 0.91. Every bundle here stays under the publishing limit of 2%, and no answer changes. The text decision bundle in q4, which the page loads by default, did not pass: 3 of 177 test decisions change against FP32. FP32 itself is within 0.03 points of the original model.

    Largest score change against FP32, for the precision this page loads
    BundlePrecisionChange
    Labeled spansq4-1.5%
    PII and claimsq4-0.28%
    Entitiesq8-0.27%
    Relationsq4-1.1%
    Evidenceq4-0.95%
    Image claimsq40%
    OCR checkq4-0.04%
    Image regionsq8-0.0026%
    Image decisionsq4-1.3%

    Source: scripts/verify-quantized.ts and scripts/verify-js.ts on the recorded reference inputs, reports/quantization-tasks.json

    For teams and enterprises

    §Need it for your domain?

    I adapt defuss-decide to your data, your hardware and your rules, and I stay until it runs in production.

    • New decision types and tasks, trained and validated on your examples
    • Smaller and faster models for your device budget, with the same parity checks
    • Integration into your product, your pipelines and your review workflows
    • On-premise and offline deployments, with no data leaving your network

    Get started

    §Add it to your own app in four steps

    1. 1

      §Install the package

      Pick your package manager. The library runs in Node.js 22 or later, Bun and current browsers.

      bun add defuss-decide
    2. 2

      §Download the model

      For Node.js and Bun, download kyr0/winzling-decide-1.0 into models/, where the library looks by default. Browsers load it from Hugging Face by themselves.

      hf download kyr0/winzling-decide-1.0 --exclude "*.br" --local-dir models
    3. 3

      §Ask in Node.js or Bun

      Load the bundles once, then ask typed questions about your state.

      import { loadVelaProvider } from "defuss-decide/node";
       
      // Every task of kyr0/winzling-decide-1.0 from models/, each in its smallest published precision.
      const vela = await loadVelaProvider();
      try {
        const r = await vela.systemOne({
          state: { order: { id: 17, status: "delivered", items: [{ sku: "A1", ordered: 2, shipped: 2 }] } },
          questions: { fulfilled: { type: "decide", instructions: "Is the order fulfilled?" } },
        });
        const { decision, probabilities, evidence } = r.answers.fulfilled;
        console.log(decision, probabilities.yes.toFixed(2));
        // => yes 0.97
        console.log(JSON.stringify(evidence.map((s) => s.state_ref)));
        // => [["order.status"]]
      } finally {
        await vela.dispose();
      }
    4. 4

      §Or in the browser

      The same API runs in the tab, on WebGPU when the device has it.

      import { loadVelaProvider } from "defuss-decide/web";
       
      const vela = await loadVelaProvider(
        {
          fast: { ner: "/models/vela3-fast-onnx/ner/q8/" },
          vision: { box: "/models/vela3-fast-onnx/vision-box/q8/" },
        },
        { backend: "auto" },
      );
      try {
        const text = await vela.systemOne({
          state: "Ada Lovelace lived in London.",
          questions: { entities: { type: "span", criteria: ["person", "city"] } },
        });
        console.log(JSON.stringify(text.answers.entities.spans.map((s) => s.text)));
        // => ["Ada Lovelace","London"]
       
        // images: File or Blob objects from an <input type="file">, image URLs, or {data, width, height} RGB bytes
        const where = await vela.systemOne({
          images: ["/models/vela3-fast-onnx/vision-box/q8/sample.png"],
          questions: { square: { type: "box", instructions: "the red square" } },
        });
        console.log(where.answers.square.box.left.toFixed(2));
        // => 0.19
      } finally {
        await vela.dispose();
      }

    Typed answers from small models, on the device

    Try the demos above, then add the library to your app.