Minus ad-detection models

What it does

These models decide whether one element of a web page is a third-party paid ad (display ad, sponsored or native ad, ad iframe). The site's own promotions, subscription offers, navigation, editorial content and cookie banners count as not ads.

They are meant to run after filter lists, on the candidate elements the lists did not hide (iframes, IAB-sized boxes, fixed bars, elements with ad-like tokens or "Advertisement" labels). Each call scores one element from a screenshot of the page.

There are two stages: a tiny always-on image classifier (tier 1), then one vision-language model (tier 2) in one of four sizes. All tier-2 models are MLX exports for Apple-silicon Macs.

Models

Folder Stage Base Weights Size Peak RAM Latency / call, M2 Max M1 (8 GB) Floor / threshold
tier1-web-cls-v3 tier 1 FastViT-T8 + 45 page features Core ML fp32 (+ PyTorch) 13 MB - ~1 ms - -
minus-xs-v3 tier 2 Qwen3.5-0.8B MLX 4-bit + LoRA + head 0.66 GB 0.8 GB 51 ms 174 ms 0.05 / 0.57
minus-s-v1 tier 2 baselabs/joey-1 (2B) MLX 8-bit 2.7 GB 2.7 GB 220 ms 997 ms 0.025 / 0.93
minus-m-v1 tier 2 baselabs/elephant-1 (9B) MLX 8-bit 10.5 GB 10 GB ~720 ms does not fit 0.05 / 0.77
minus-l-v1 tier 2 baselabs/mammoth-1 (27B) MLX 4-bit 16.1 GB 15.2 GB 2,848 ms does not fit 0 / 0.90 (reference)

Latency is one warm tier-2 call (one prefill, no generation) at float16 compute. Floor: tier 2 runs only when the tier-1 score is at least this. Threshold: the element is hidden when the combined score is at least this. minus-l was not shipped as a default; its point is the offline reference point.

Input format

minus-xs reads the bare crop: the element's pixels, longest side at most 1,024 px.

minus-s / m / l read a composite image, top to bottom, on a grey (128, 128, 128) background with 8 px margins:

  1. a dark header bar with the text CROP;
  2. the bare crop scaled to fit 896 x 640 (never enlarged);
  3. a dark header bar: CONTEXT - page: <domain> - element outlined in magenta (<domain> = the registrable domain of the page URL, e.g. bbc.co.uk);
  4. the page around the element (the box grown by max(160, w/2) CSS px left and right and max(100, h/2) up and down, clipped to the viewport), with the element outlined by a ~3 px magenta (#ff00ff) rectangle, scaled to fit 896 x 448.

Every image is then scaled down to at most 200,000 pixels (BICUBIC, aspect kept, never enlarged). examples/make_composite.py builds both images byte-for-byte as the app does.

Prompt. The exact prompt string (chat template already applied) is prompt_text in each folder's decision_config.json. For s / m / l it asks whether the element is a third-party ad and offers A. yes / B. no, ending with an empty thinking block after the assistant tag.

Readout. One forward pass over prompt + image; no text is generated. At the last position:

  • s / m / l: the language-model logits of the answer letters A (token 32, ad) and B (token 33, not ad): p(ad) = sigmoid(l_A - l_B). minus-l uses a block-bidirectional attention mask: the last 112 tokens (the question) also attend to each other; everything else is causal.
  • xs: the prompt ends in an extra readout token <|fim_pad|>; its final hidden state goes through the 2-way linear head in decision_head.safetensors (options not_ad, ad): p(ad) = sigmoid(l_ad - l_not_ad).

Weights load with float16 compute (compute_dtype). minus-xs loads its LoRA adapter from adapters/ at load time.

The two-stage cascade

For each candidate element:

  1. Tier 1 gives p1 (Core ML, a 320 x 256 image of crop + context, plus 45 page-structure features).
  2. If p1 >= floor, tier 2 gives p2.
  3. p = sigmoid((logit(p1) + logit(p2)) / 2), the mean of the two logits (p clamped to [1e-6, 1 - 1e-6] before the logit). If tier 2 did not run, p = p1.
  4. Hide the element if p >= threshold.

Floor and threshold are per model (table above). examples/tier1.py has tier 1, the 45 features and this rule.

Results

Offline. 102 ad units that got past filter lists, labelled by eye, across ~1,500 pages in four groups of sites. A hit is a caught unit at the model's operating point, chosen so that every group stays at or below 0.01 wrong hides per page.

tier 1 + v3 (xs) S M L
Ads caught (of 102) 16 27 39 50

Live. 442 real pages loaded in Chrome with the blocker on; every AI hide was labelled blind by eye.

XS S M L
Ads hidden / wrong hides 9 / 2 8 / 0 15 / 2 partial: 8 / 0 on the first 192 pages

Training data, in brief

Element crops and composites from pages of Tranco-ranked sites, labelled by GPT, with by-eye checks for the evaluation sets. minus-xs is a distilled Qwen3.5-0.8B student (LoRA on the top layers + a readout head, ~53k labelled crops). minus-s / m / l are LoRA fine-tunes (rank 16, every language-model layer, one epoch, ~9.7k composites at 200k px), merged and then quantized with mlx-vlm. Tier 1 is FastViT-T8 trained on the same crops plus page-structure features. Datasets are not published.

Limitations

  • It misses some ads, including some that carry an explicit "Advertisement" label.
  • It can hide a site's own promotions or empty ad slots.
  • It was trained mostly on English sites and on some Chinese and Russian sites; other languages are weaker.

Quick start

pip install mlx==0.32.4 mlx-vlm==0.7.9 pillow==13.0.0
cd examples
python make_composite.py shot.jpg --box 40 300 728 90 --url https://www.example.com/news -o comp.png --bare-out bare.png
python score.py ../minus-s-v1 comp.png      # s / m / l: the composite
python score.py ../minus-xs-v3 bare.png     # xs: the bare crop
from PIL import Image
import make_composite as mc
from score import Scorer

shot = Image.open("shot.jpg").convert("RGB")          # viewport screenshot (any device scale)
comp, bare = mc.build(shot, (40, 300, 728, 90), mc.page_domain("https://www.example.com/news"), view=(1280, 900))
print(Scorer("../minus-s-v1").p_ad(comp))             # p(ad); then the cascade rule above

--box is the element's box in CSS px relative to the viewport; view is the viewport size in CSS px.

Downloads last month
13
MLX
Hardware compatibility
Log In to add your hardware

Quantized

Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support

Model tree for cebeuq/minus-ad-detectors

Quantized
(428)
this model