AdVig

RACER IS OP

Block ads and trackers at the DNS layer before they load - 16 KB of int8 weights running on a NodeMCU. No cloud, no runtime lists, no API calls. The offline, no-dependency Pi-hole, distilled into a single dot product.

Priorities: Quality > Size > Speed

Trained on: AdTrap v1 - 713,539 domains (93,541 ad/tracker + 619,998 legitimate), built from StevenBlack/hosts, AdAway, Yoyo, and Majestic Million.

Sibling of saidutta69/PhishScout.


Model Overview

AdVig is a tiny hybrid logistic regression that classifies any bare domain as BLOCK (ad/tracker) or ALLOW (legitimate) using the domain string alone - the exact input a DNS query carries. No page content, no network calls at inference time. Score = one int32 dot product over hashed character n-grams plus 34 structural features.

Property Value
Architecture Logistic regression: hashed char n-grams + structural features
Features 16,384 hashed n-gram buckets (FNV-1a, sign trick) + 34 structural
Input Bare domain string (DNS query hostname)
Model size 64.4 KB float32 ONNX / 16.0 KB int8 quantized
Training time ~18 s CPU
Inference 933 us/domain measured on NodeMCU @160 MHz; ~113 us reference Python
License MIT

Performance

Final model (test split, held out, group-disjoint by registrable domain, seed 42):

Metric float32 int8 quantized
Accuracy 0.9485 0.9486
Precision 0.8491 0.8597*
Recall 0.7307 0.7069*
F1 0.7854 0.7862
ROC AUC 0.9478 0.9477
False positive rate 1.87% 1.87%

*int8 row evaluated at its own val-tuned threshold; quantization changed F1 by +0.0007 (hash-collision regularization).

Why not bigger? (bucket sweep)

Smaller hashing spaces beat larger ones - collisions regularize:

buckets C F1 AUC int8 size
2^16 10 0.7271 0.9288 64 KB
2^15 10 0.7415 0.9337 32 KB
2^14 1 0.7854 0.9478 16 KB
2^13 10 0.7756 0.9444 8 KB
2^12 10 0.7585 0.9372 4 KB

Baseline benchmark

Structural features alone top out at F1 0.634 (XGBoost): most blocklisted spam/parked domains have no structural tells. Lexical memory is what unlocks quality:

model F1 AUC
gaussian_nb 0.5674 0.8197
logistic_regression (structural) 0.6006 0.8298
decision_tree d8 (structural) 0.6157 0.8112
lightgbm 30x6L15 (structural) 0.6222 0.8515
xgboost 30x4 (structural) 0.6341 0.8598
gram-only LR 2^15 0.6953 0.9180
AdVig hybrid LR 2^14 0.7854 0.9478

On-Device Benchmarks (NodeMCU ESP8266)

Measured on hardware (Arduino core 3.1.2, 240 stratified test domains x 30 passes = 7,200 inferences per run):

Metric 80 MHz 160 MHz
Avg latency 1840 us/domain 933 us/domain
Min / Max 1280 / 3043 us 650 / 1879 us
Throughput ~543 domains/s ~1072 domains/s
Gram-hash phase 144 us 77 us
Structural phase 1685 us 850 us
  • Memory: int8 weights live in flash (PROGMEM, 16.4 KB, zero RAM); working set is a 2048-entry tally table + bookkeeping (~7.7 KB static). Free heap: 42,192 B.
  • Accuracy on-device (balanced sample): acc 0.8792, precision 0.9789, recall 0.7750, F1 0.8651 - bit-exact with host emulation, 100% parity (240/240), identical prediction bitmap at both clock speeds.
  • Scaling is linear with clock (compute-bound); other MCUs scale predictably.
  • Known headroom: the structural phase dominates (~92% of latency) due to linear PROGMEM lexicon scans; sorted arrays + binary search should roughly halve total latency.

At ~1,000 domains/s, one NodeMCU comfortably keeps up with household-scale DNS traffic.

Use Cases

  • DNS-level Pi-hole replacement - answer DNS queries with an on-device verdict; fully offline, zero external dependencies
  • Router/firewall firmware integration - classify unknown domains at query time, complementing exact-match blocklists
  • Parental controls & IoT gateways - block ad/tracker endpoints on devices that cannot run browser extensions
  • Privacy tooling research - a compact baseline model for tracker-domain generalization studies
  • Browser/proxy pre-fetch screening - cheap first-pass filter ahead of heavier analysis

Features

Structural (34)

length, label_count, max_label_len, digit_count, max_digit_run, hyphen_count, entropy, vowel_ratio, starts_with_www, has_punycode, subdomain_depth, tld_trusted, tld_adheavy, tld_is_cctld, tld_length, tok_ad, tok_advert, tok_banner, tok_promo, tok_sponsor, tok_track, tok_analytics, tok_metrics, tok_telemetry, tok_beacon, tok_pixel, tok_tag, tok_click, tok_impression, tok_affiliate, tok_syndication, tok_vendor, tok_bigtech, bigtech_and_adtoken

Hashed lexical memory

Char 3/4/5-grams over .domain. hashed with FNV-1a (random sign projection) into 2^14 buckets. Inference collapses to logit = SCALE * gram_sum + BIAS + SCALE * dot(STRUCT_W, feats) - trivially portable to any MCU in C. Note BIAS is negative (-1.245059); see the bias-correction note under Usage.

Usage

Python (ONNX Runtime)

import numpy as np
import onnxruntime as ort
from gramlib import hash_grams          # reference hasher (in repo files)
from features import extract_features   # reference structural extractor

sess = ort.InferenceSession("advig.onnx", providers=["CPUExecutionProvider"])
domain = "ads.tracker-cdn.example.com"

grams = np.zeros((1 << 14), dtype=np.float32)
for idx, val in hash_grams(domain, buckets=1 << 14).items():
    grams[idx] += np.sign(val)
x = np.concatenate([grams, extract_features(domain)]).astype(np.float32)[None]

p_block = sess.run(["prob"], {"features": x})[0].item()
blocked = p_block >= 0.477

ESP8266 / NodeMCU (C) - reference implementation included

This repo now ships a complete, parity-verified reference implementation:

  • advig_weights.h - int8 weights. On ESP8266/ESP32 the table carries __attribute__((progmem)), so read it with pgm_read_byte(&ADVIG_W[i]).
  • advig.c / advig.h - full feature extractor + streaming n-gram scorer (no histogram buffer needed: each gram contributes sign * W[bucket] independently, so inference is O(1) RAM).
  • advig_host_main.c - tiny host harness (cc -O2 host_main.c advig.c) for desktop testing.
  • parity_test.py - verifies the documented formula against advig.onnx.

Exact decision rule on device (verified bit-exact vs the ONNX, max |dp| < 1e-6 over 255 held-out domains):

// grams: FNV1a(seed 2166136261, prime 16777619) over ".<lowercase domain>."
// dot += ((h >> 31) & 1 ? +1 : -1) * ADVIG_W[h % 16384];   // int32

float logit = dot * ADVIG_SCALE_F          // 0.03952733799815178f
            + ADVIG_BIAS_F                 // (-1.245059f)
            + dot_struct(ADVIG_W + 16384, feats) * ADVIG_SCALE_F;
bool block = sigmoidf(logit) >= ADVIG_THRESHOLD_P;   // 0.4774f

Bias correction (2026-08-22): earlier revisions of this card implied a positive integer bias (31 * SCALE). The actual ONNX bias is -1.245059. advig_weights.h now ships the correct ADVIG_BIAS_F; do not reconstruct it from an integer.

Verification & Reproducibility

  • ONNX parity - 100% match between ONNX Runtime and closed-form sigmoid(Gemm).
  • On-device parity - 100% (240/240) agreement between NodeMCU firmware and host emulation; confusion matrices identical.
  • Deterministic - identical test predictions across seeds 42/1/7/123.
  • No leakage - train/val/test disjoint at the registrable-domain level; synthetic subdomains inherit their parent's split.
  • int8 verified end-to-end - quantized weights re-evaluated after quantization, not assumed lossless.

Limitations

  • Enumeration ceiling - random parked/spam domains (05tz2e9.com) carry zero signal in their names; no string-based model can catch them. Pair AdVig with an exact-match blocklist: the list memorizes the tail, AdVig generalizes over unseen trackers.
  • "ad-" prefix traps - adyen.com-style collateral exists (~1.9% FPR). Raise the threshold for allow-biased operation.
  • English-centric lexicons - token lists are Western-market oriented.
  • Feed dependence - inherits StevenBlack/AdAway/Yoyo coverage as of build date; retrain for fresh feeds.
  • Dataset noise - Majestic top-1M contains some parked/ad-heavy registrable domains treated as ALLOW.

Training Data

AdTrap v1: 713,539 rows, 70/15/15 split by registrable domain, seed 42.

Source Class Domains
StevenBlack hosts (ads+trackers base) BLOCK 93,512
AdAway BLOCK 6,540
Yoyo BLOCK 3,515
Majestic Million (overlap removed) ALLOW 400,000
Synthetic legit subdomains (augmentation) ALLOW 220,000

Citation

@misc{saidutta69_2026_advig,
  author = {Sai Dutta Abhishek Dash},
  title = {AdVig: Tiny On-Device Ad and Tracker Domain Classifier},
  year = {2026},
  publisher = {Hugging Face},
  howpublished = {\url{https://huggingface.co/saidutta69/AdVig}},
  note = {Trained on the AdTrap dataset}
}

Built on AdTrap + StevenBlack/hosts, AdAway, Yoyo, and Majestic Million. MIT licensed.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Evaluation results