QAI Connect · LAYA · Team Mavericks

LAYA: a 0.5B Qwen that tells you how much time is left to act

Team Mavericks · QAI Connect

Try LAYA on Spaces →

Most monitoring tools answer one question: what is happening right now? A greenhouse is idle. A reefer truck is warming. A power plant is breathing dust. The dashboard turns red, and someone has to work out whether there is still time to do anything about it.

We built QAI Connect around a different question:

How much time is left to make a safe and useful decision before our options run out?

We call that number the Decision Reserve. This post covers how we compute it from open data, how we fine-tuned LAYA, a 0.5B-parameter Qwen, to explain it, and how we run LAYA in the browser on a Hugging Face Space behind a governance layer we call TAILSTRIKE.

👉 Try it: LAYA on Spaces


The Decision Reserve

Every decision has a clock. A planting window closes. A load of lettuce crosses its safe temperature. A plant hits a heat or dust constraint. We split that clock into parts:

DR = Tf − Td − Ta − Te − Tb
Term Meaning
Tf time to failure or critical constraint
Td time to detect and validate the problem (data latency lives here)
Ta time to approve a decision
Te time to execute the intervention
Tb a safety buffer

Nothing in that list is certain, so we report a probability rather than a single number:

P(DR > 0) = P(Tf > Td + Ta + Te + Tb)

We estimate it with a normal model, or with Monte Carlo draws for the location engine.

The useful part is Td. Data latency goes straight into lost decision time. Here is one of our live cold-chain examples, a truck of lettuce at 10 °C with about 60 minutes before it is lost:

How often we see the data Decision Reserve P(DR > 0)
Manual check every 30 min −5 min 0.31
Telematics poll every 15 min +10 min 0.84
Streaming +24 min 0.99

Same truck, same cold store, same approval process. Only the data speed changed, and that alone decides whether the load can still be saved.

Built on open data

QAI Connect is an integration layer. It connects partners' existing systems (ERPs, spreadsheets, sensors, government data) instead of replacing them. For the prototype, every number comes from public sources:

Type any place into the app and it builds a heat map, finds the energy sites in range and computes each site's Decision Reserve. For Ras Laffan in Qatar that is 4 sites and 7,113 MW, with Ras Rakan as the binding site at about 229 hours (P50), limited by airborne dust.

Why fine-tune a 0.5B model?

The maths is deterministic code; we never let a language model produce the numbers. People still need the result in a sentence: which site binds, why, what can be done, and by when. Large hosted models do this well, but we wanted:

  1. Exact numbers. The explanation has to repeat the engine's figures, not "about 230 hours".
  2. On-device inference. Partner data should not have to leave the browser.
  3. Low cost. We wanted the whole thing to run on the cheapest tier we could find.

So we fine-tuned the smallest Qwen chat model.

Data: 1,780 rows, every target computed by the app's own engines

We used the app's own engines to write the training targets. Every number in a target was computed, not invented.

Slice Rows Source
Location (real) 900 Plants from the HF dataset with real ERA5 weather and CAMS air quality on random days from 2022 to 2025
Location (stress) 400 Synthetic heatwaves and shamal dust storms, for extremes that real days rarely hit
Fleet sites 480 8 farm sites × 60 days of real weather, scored for growing method and planting window

The prompts are byte-for-byte the strings the app sends at inference time, so the model sees exactly the input format it was trained on.

Training: TRL + LoRA on HF Jobs

Metric Result
Eval loss 0.18
Token accuracy 94%
Exact numeric match, held-out (real locations) 11 / 12
Exact numeric match, held-out (stress) 10 / 12

"Exact numeric match" is the metric we cared about most: greedy decoding on held-out cases, where every number in the answer must equal the engine's number.

Running it in the browser: what went wrong

We exported to ONNX for Transformers.js. Three things went wrong first, and they may save you a few hours:

  1. rope_theta silently fell back to the default. We trained with transformers 5, which stores rope_theta inside rope_parameters. The exporter ran transformers 4.46, which did not find it and used 10000 instead of Qwen2.5's 1e6. The ONNX model behaved like the base model, with the fine-tune lost. Fix: copy rope_theta back to the top level of config.json before exporting.
  2. int8 broke the model. Every int8 (q8) variant we tried produced garbage or base-model text: per-channel or not, MatMul-only or not. The 4-bit build (MatMulNBits, block size 32) kept the fine-tune intact, so we ship q4 only (855 MB). It runs on WebGPU, with a WASM fallback.
  3. We now verify before publishing. The export job generates on held-out cases with PyTorch and with each ONNX build, and refuses to push a build that does not reproduce the numbers. That gate caught all of the problems above before anything reached users.

The Space is a free static Space. The page and its scripts are a few kilobytes. The weights load from the ONNX repo, jonathan4e/qai-laya-qwen2.5-0.5b-onnx, pinned to the exact revision that passed verification. LAYA runs in a Web Worker so the page stays responsive.

TAILSTRIKE: the model proposes, the gate decides

A model that suggests "shift 154 MW of dispatch to another plant" must not be able to do it. TAILSTRIKE is a small governance kernel in front of LAYA. Its design follows the governed-runtime pattern: proposing, admitting and acting are separate steps.

Try the "Try an injection" button on the Space. The gate denies the request before the model runs, and the denial shows up in the ledger.

What's next

The measure we care about is simple: did someone find the problem early enough to still respond?


Links: - Space: jonathan4e/qai-laya - Browser model (ONNX, q4): jonathan4e/qai-laya-qwen2.5-0.5b-onnx - Base model: Qwen/Qwen2.5-0.5B-Instruct - Power-plant dataset: Inzinion/power-plant-climate-exposure-index