faxenoff commited on
Commit
63d607a
·
verified ·
1 Parent(s): dd0d81f

Upload README.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. README.md +109 -1
README.md CHANGED
@@ -1,3 +1,111 @@
1
  ---
2
- license: mit
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ license: apache-2.0
3
+ language:
4
+ - en
5
+ - ru
6
+ tags:
7
+ - feature-extraction
8
+ - text-classification
9
+ - denoising
10
+ - multilingual
11
+ - openvino
12
+ - tensorrt
13
+ pipeline_tag: feature-extraction
14
+ base_model:
15
+ - intfloat/multilingual-e5-small
16
  ---
17
+
18
+ # code-daemon-denoise-v1
19
+
20
+ A tiny, fast **bilingual (EN + RU) word denoiser** — it decides whether a single word form is a
21
+ **meaningful technical term** (keep) or **noise / ballast** (drop). It ships with the
22
+ [UltraCode](https://github.com/faxenoff/ultracode) MCP server, where it runs Stage 6
23
+ (`SYS_BOOTSTRAP`) of the knowledge-graph pipeline: classifying the UNKNOWN word forms harvested from a
24
+ codebase's docs/identifiers so the search vocabulary stays clean.
25
+
26
+ It **replaces a prompt-based LLM denoiser** (Qwen2.5-Coder-1.5B answering YES/NO per word) with a
27
+ frozen encoder + a trained linear head. On the daemon's bootstrap pass this is **~60× faster**
28
+ (~0.8 s vs ~400 s) at the same quality, runs on the **CPU** (OpenVINO INT8), and never competes with
29
+ the main LLM for VRAM.
30
+
31
+ - **Frozen encoder** — [`intfloat/multilingual-e5-small`](https://huggingface.co/intfloat/multilingual-e5-small)
32
+ (XLM-RoBERTa, 384-dim), **no weight changes**. Mean-pooling + L2-norm are baked into the graph.
33
+ - **Trained linear head** — a logistic-regression probe (scikit-learn) over the 384-dim embedding,
34
+ **folded with its input scaler into a single affine** `P(keep) = sigmoid(w·e + b)`. Ships as
35
+ `denoise_head.json` (`{dim, w[384], b, strip_threshold}`) — no Python at runtime; the daemon does
36
+ the dot product in-process.
37
+ - **Vocab-pruned** — the 250k-token SentencePiece vocab is cut by character class to **Latin +
38
+ Cyrillic + punctuation (142k tokens)**, lossless for EN + RU, dropping the INT8 weights from ~121 MB
39
+ to **~76 MB**. The pruned-vocab id map is folded into a remap-Gather at the model input.
40
+
41
+ ## How it was made
42
+
43
+ 1. **Encoder**: export the frozen mE5-small to ONNX with mean-pool + L2-norm fused, prune the
44
+ embedding table to the kept character classes, and PTQ-quantize to INT8 (NNCF) for OpenVINO.
45
+ 2. **Head**: embed a bilingual word-label set (EN: WordNet/BNC mid-frequency lemmas; RU:
46
+ Taiga/OpenCorpora/Nerus mid-Zipf) plus per-language manual gold, fit
47
+ `LogisticRegression(class_weight="balanced")`, then **fold** `StandardScaler` + LR into one
48
+ `(w, b)`. A `strip_threshold` (default **0.95**) trades strip precision vs recall.
49
+
50
+ Words are embedded with a fixed `"vocab: "` prefix (the daemon pads every candidate word the same
51
+ way) so very short inputs are not dropped by batch de-duplication — the head is trained on the
52
+ **prefixed** embeddings, so reproduce the prefix for standalone use.
53
+
54
+ ## Built for speed
55
+
56
+ - **Short, single-word inputs** — one length bucket only: **batch 64 × seq 40** (`-s_…_b64_s40`).
57
+ - **INT8** weights (OpenVINO CPU); the embedding **mean-pool + L2-norm are fused** into the graph so
58
+ the output is already `[batch, 384]`.
59
+ - **CPU-first by design** — on the daemon it runs on OpenVINO CPU and is moved to a discrete GPU
60
+ (TensorRT / TVM) only when the card is large (≥12 GB total VRAM) with free room.
61
+
62
+ ## Intended use
63
+
64
+ Per-word "is this a technical term?" classification for cleaning a search vocabulary. Encode a word
65
+ (with the `"vocab: "` prefix) with the bundled SentencePiece + mE5-small, then apply the linear head:
66
+
67
+ ```python
68
+ import onnxruntime as ort, sentencepiece as spm, numpy as np, json
69
+
70
+ sp = spm.SentencePieceProcessor(model_file="sentencepiece.bpe.model")
71
+ sess = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"])
72
+ head = json.load(open("denoise_head.json")) # {dim, w[dim], b, strip_threshold}
73
+ w, b, thr = np.array(head["w"], np.float32), head["b"], head["strip_threshold"]
74
+
75
+ def p_keep(words, max_len=40):
76
+ toks = [[2, *sp.encode("vocab: " + x)[: max_len - 2], 3] for x in words] # bos … eos
77
+ L = max(len(t) for t in toks)
78
+ ids = np.array([t + [0] * (L - len(t)) for t in toks], dtype=np.int64) # pad=0
79
+ mask = (ids != 0).astype(np.int64)
80
+ emb = sess.run(None, {"input_ids": ids, "attention_mask": mask})[0] # mean-pooled+L2 [B,384]
81
+ return 1.0 / (1.0 + np.exp(-(emb @ w + b))) # P(keep)
82
+
83
+ scores = p_keep(["mutex", "tensorrt", "пожалуйста", "asdfgh"])
84
+ # keep where score >= thr ; the rest is ballast
85
+ ```
86
+
87
+ ## What's in this repo
88
+
89
+ Pre-compiled, ready-to-run engines named per **runtime × GPU arch × OS** (single `s` bucket):
90
+
91
+ - **OpenVINO** `*_ov_cpu_int8_b64_s40.{xml,bin}` — Intel/AMD/any CPU, INT8 (the default lane).
92
+ - **TensorRT** `*_{win_x64,linux_x64}_trt_sm_{86,89,120}.engine` — NVIDIA, INT8 (optional GPU lane).
93
+ - **TVM** `*_b64_s40_{win_x64,…}_tvm_vulkan.{dll,so}` — Vulkan fallback (optional GPU lane).
94
+ - **Head** — `denoise_head.json` (the trained affine; required).
95
+ - **Tokenizer** — `sentencepiece.bpe.model` (+ `tokenizer_config.json`). The daemon feeds raw
96
+ SentencePiece ids; the fairseq +1 offset and pruned-vocab remap are baked into the ONNX.
97
+ - **ONNX source** — `model.onnx` (FP32, pruned, mean-pool + L2-norm + remap fused) — the build
98
+ source for the TRT/TVM engines and for standalone `onnxruntime` use.
99
+
100
+ ## Evaluation
101
+
102
+ On a frozen held-out word set (EN + RU): **SAFE F1 ≈ 0.79**, BALLAST F1 ≈ 0.84, strip precision ≈
103
+ 0.88 at `strip_threshold = 0.95`. The INT8 vocab-pruned build matches the full-vocab FP build (F1 0.79
104
+ vs 0.79) at 38 % of the size. (INT4 was evaluated and rejected: −10 MB for measurable precision loss.)
105
+
106
+ ## License & attribution
107
+
108
+ The encoder weights are **[`intfloat/multilingual-e5-small`](https://huggingface.co/intfloat/multilingual-e5-small)**
109
+ (Apache-2.0), redistributed here in compiled form **unchanged**; this repo is therefore released under
110
+ **Apache-2.0**. The linear head and the build/quantization tooling are original to UltraCode. Backbone:
111
+ XLM-RoBERTa. Not legal advice.