Image-Text-to-Text
Transformers.js
ONNX
lfm2_vl
ad-detection
ad-blocking
webgpu
transformers-js
lfm2.5
vision-language-model
browser
conversational
Instructions to use TheGarageDev/Minus-v0.1 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers.js
How to use TheGarageDev/Minus-v0.1 with Transformers.js:
// npm i @huggingface/transformers import { pipeline } from '@huggingface/transformers'; // Allocate pipeline const pipe = await pipeline('image-text-to-text', 'TheGarageDev/Minus-v0.1');
File size: 7,604 Bytes
f622778 99dd319 f622778 fb79f45 f622778 99dd319 f622778 0db3dfc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | ---
license: other
license_name: lfm-open-license-v1.0
license_link: https://huggingface.co/LiquidAI/LFM2.5-VL-450M/blob/main/LICENSE
base_model: LiquidAI/LFM2.5-VL-450M
pipeline_tag: image-text-to-text
library_name: transformers.js
tags:
- ad-detection
- ad-blocking
- onnx
- webgpu
- transformers-js
- lfm2.5
- vision-language-model
- browser
---
# Minus-v0.1 — a vision model that blocks ads by *looking* at them

*Minus-v0.1 running in the Chrome extension on theverge.com: the banner and
sidebar ad slots are covered by language flashcards, each tagged with the
model's confidence. The article itself is untouched.*
**Minus-v0.1** is a 450M-parameter vision–language model fine-tuned to answer one question about an image: **"Is this an advertisement?"** It powers the [Minus Chrome extension](https://github.com/garagehq/Minus-chrome-extension), where it runs **entirely in the browser** (transformers.js + ONNX Runtime Web on WebGPU) and covers detected ads with language flashcards — no filter lists, no servers, no telemetry. It is the browser sibling of the [minus](https://github.com/garagehq/minus) HDMI ad-blocking device.
Instead of matching URLs or DOM patterns like a classic ad blocker, Minus classifies **pixels**: screenshots of page elements, video frames, and iframes. That means it generalizes to first-party ads, sponsored tiles, native placements and streaming-TV commercials that filter lists can't see — and it keeps working when ad-tech rotates domains.
- **Base model:** [LiquidAI/LFM2.5-VL-450M](https://huggingface.co/LiquidAI/LFM2.5-VL-450M)
- **Task:** binary ad / not-ad classification via a single-token answer (`Yes` / `No`), scored as `p(ad) = P(Yes)`
- **Prompt:** `Is this an advertisement? Answer Yes or No.`
- **Format in this repo:** ONNX, quantized for the browser (~430 MB total): q4 decoder, q8 token embeddings, q8 vision encoder. **WebGPU required** (the q4 decoder uses `GatherBlockQuantized`, which onnxruntime-web's WASM backend doesn't implement).
## How it was trained
Minus-v0.1 is **iteration 28** of a months-long training campaign (the first 27 iterations were exploration: base-model selection between FastVLM / SmolVLM / CLIP-style classifiers / LFM2.5-VL, then successive rounds of hard-negative and hard-positive mining, each gated by frozen benchmarks and *live* in-browser soak tests).
**Recipe (frozen across late iterations):** LoRA (r=16, α=32) on the language-model blocks only — vision tower and projector stay frozen — lr 2e-4, 3 epochs, effective batch 32. Trained on a single NVIDIA Jetson AGX Thor (128 GB unified memory). The LoRA is merged before export.
**Data (not released):** ~81,600 training samples across two domains:
- **Streaming TV** — frames sampled by the minus HDMI device during real viewing sessions: commercials vs. program content, label-audited before training.
- **Web pages** — display/banner ad creatives vs. hard negatives mined from real browsing: editorial content, product photography (e-commerce tiles that *look* like ads), site self-promo and UI elements, cookie/consent banners, chat widgets, site headers, and scale-jittered variants of all of these to survive browser resampling.
Roughly **45% of the web-ad positives are native/chum-box style** (Taboola/Outbrain-like), which classic blockers struggle with.
## Evaluation
All gates are held-out sets that were **frozen before** this iteration trained; the live number comes from headed-browser soaks on real sites.
| benchmark | result |
|---|---|
| Streaming holdout (1,956 frames, hand-verified) | **99.90%** ad recall / **98.06%** non-ad recall |
| Static-web bench (999 images) @ shipping gate | **98.0%** ad recall, **11** false positives |
| Product-image FP holdout (199 ad-look-alike product shots) | **1/199** false positives |
| Live in-browser precision (month of soak tests, real sites) | **~90–94%** of covered elements are actually ads |
The static-web PR curve dominates or ties the previous production model at **every** operating point with non-ad recall ≥ 95%.
Every word shown over a blocked ad feeds the extension's built-in
spaced-repetition review — the ads you don't watch become vocabulary you keep:

## Using the model
### transformers.js (what the extension does)
```js
import { AutoProcessor, AutoModelForVision2Seq, RawImage } from "@huggingface/transformers";
const processor = await AutoProcessor.from_pretrained("TheGarageDev/Minus-v0.1");
const model = await AutoModelForVision2Seq.from_pretrained("TheGarageDev/Minus-v0.1", {
device: "webgpu", // REQUIRED — the q4 decoder is WebGPU-only
dtype: { embed_tokens: "q8", vision_encoder: "q8", decoder_model_merged: "q4" },
});
const image = await RawImage.read(imageUrlOrCanvas);
const messages = [{ role: "user", content: [
{ type: "image" },
{ type: "text", text: "Is this an advertisement? Answer Yes or No." },
]}];
const prompt = processor.apply_chat_template(messages, { add_generation_prompt: true });
const inputs = await processor(prompt, image);
// One decode step; compare the logits of "Yes" vs "No" for a calibrated p(ad).
const { logits } = await model({ ...inputs });
// p_ad = softmax over {logit("Yes"), logit("No")} — see the extension's engine
// (offscreen.js) for the exact token ids + scoring code.
```
**Thresholding matters.** The extension does not block at p ≥ 0.5 — it uses per-context gates chosen from the PR curve: **0.60** for elements with ad context (iframes / ad-slot containers) and **0.88** for bare images. If you deploy this model, pick your own operating point for your precision target.
### Python (reference / server-side)
The ONNX graphs run under `onnxruntime` too (CPU/CUDA execution providers support the quantized ops). For full-precision experiments, start from the base model and the recipe above.
## Limitations & honest notes
- **Binary classifier, not a chat model.** The fine-tune deliberately collapses the model onto Yes/No answers for one prompt; don't expect general VLM behavior.
- **Domain:** English-centric web pages and US/EU streaming TV. Ads in other scripts/markets will work worse.
- **Preprocessing sensitivity:** browser-side image resampling can shift scores on borderline UI-like inputs (we train with scale-jitter to mitigate; a handful of known residuals remain, e.g. certain consent banners).
- **Screenshots of ads vs. ads:** the model sees pixels. Editorial *about* an ad, or a screenshot of an ad inside an article, can legitimately score high.
- The training dataset is **not** released.
## License
Inherits the **[LFM Open License v1.0](https://huggingface.co/LiquidAI/LFM2.5-VL-450M/blob/main/LICENSE)** from the base model ([LiquidAI/LFM2.5-VL-450M](https://huggingface.co/LiquidAI/LFM2.5-VL-450M)). The Minus extension source is at [garagehq/Minus-chrome-extension](https://github.com/garagehq/Minus-chrome-extension).
## Support
Minus is free and open-source — the model above, the [Chrome extension](https://github.com/garagehq/Minus-chrome-extension), and the device it grew out of. If it saves you from a few autoplay pre-rolls, you can buy me a coffee ☕:
<a href="https://buymeacoffee.com/cyrilengmann" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="50" width="210"></a>
> **[buymeacoffee.com/cyrilengmann](https://buymeacoffee.com/cyrilengmann)**
|