bge-m3-lite 0.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: bge-m3-lite
3
+ Version: 0.0.2
4
+ Summary: Lightweight CPU inference for BAAI/bge-m3 (dense + sparse + ColBERT) with onnxruntime as the only dependency.
5
+ Keywords: bge-m3,colbert,embedding,onnxruntime,retrieval,sparse
6
+ Author: Allen Chou
7
+ License-Expression: MIT
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: MacOS
11
+ Classifier: Operating System :: POSIX :: Linux
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Requires-Dist: onnxruntime>=1.17
19
+ Requires-Dist: onnx>=1.16 ; extra == 'quant'
20
+ Requires-Dist: onnx-ir ; extra == 'quant'
21
+ Requires-Dist: sympy ; extra == 'quant'
22
+ Requires-Python: >=3.11
23
+ Project-URL: Homepage, https://github.com/allen2c/bge-m3-lite
24
+ Provides-Extra: quant
25
+ Description-Content-Type: text/markdown
26
+
27
+ # bge-m3-lite
28
+
29
+ CPU inference for [BAAI/bge-m3](https://huggingface.co/BAAI/bge-m3) with
30
+ **`onnxruntime` as the only dependency**. All three BGE-M3 outputs are supported
31
+ and match the official PyTorch implementation (FlagEmbedding) to fp32 precision:
32
+
33
+ | output | shape | notes |
34
+ |---|---|---|
35
+ | `dense_vecs` | `(n, 1024)` | CLS pooling, L2-normalised |
36
+ | `lexical_weights` | `list[dict[str, float]]` | token-id → weight, max-pooled, specials removed |
37
+ | `colbert_vecs` | `list[(len-1, 1024)]` | per-token vectors without `<s>`, L2-normalised |
38
+
39
+ Everything except the transformer forward pass is implemented in this package
40
+ from scratch: the XLM-RoBERTa tokenizer (SentencePiece unigram model, the
41
+ `nmt_nfkc` precompiled charsmap, Unicode grapheme segmentation), the torch-free
42
+ loader for the sparse / ColBERT heads, the model downloader and the pooling.
43
+
44
+ Platforms: Apple Silicon, Linux ARM64, Linux x86_64 (Python 3.11+).
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ uv add bge-m3-lite # or: pip install bge-m3-lite
50
+ ```
51
+
52
+ ## Use
53
+
54
+ ```python
55
+ from bge_m3_lite import BGEM3Embedder
56
+
57
+ embedder = BGEM3Embedder() # first call downloads ~2.3 GB into ~/.cache/bge-m3-lite
58
+ out = embedder.encode(
59
+ ["What is BGE M3?", "BGE M3 是一個多語言嵌入模型。"],
60
+ return_dense=True,
61
+ return_sparse=True,
62
+ return_colbert_vecs=True,
63
+ )
64
+ out["dense_vecs"].shape # (2, 1024)
65
+ out["lexical_weights"][0] # {'4865': 0.08, '83': 0.08, ...}
66
+ out["colbert_vecs"][0].shape # (7, 1024)
67
+
68
+ embedder.convert_id_to_token(out["lexical_weights"][0])
69
+ embedder.compute_lexical_matching_score(lw_query, lw_passage)
70
+ embedder.colbert_score(q_vecs, p_vecs)
71
+ ```
72
+
73
+ Passing a single string returns unwrapped values, like FlagEmbedding.
74
+ `BGEM3Embedder(precision="int8")` loads a 4× smaller quantised backbone
75
+ (see `docs/quantization.md` for the accuracy trade-off).
76
+
77
+ ### CLI
78
+
79
+ ```bash
80
+ bge-m3-lite download # pre-fetch the model files
81
+ bge-m3-lite info # cache state
82
+ echo "hello" | bge-m3-lite encode --sparse --colbert --tokens
83
+ ```
84
+
85
+ ### Environment variables
86
+
87
+ | variable | effect |
88
+ |---|---|
89
+ | `BGE_M3_LITE_CACHE` | cache directory (default `~/.cache/bge-m3-lite/BAAI--bge-m3`) |
90
+ | `HF_ENDPOINT` | Hugging Face mirror, e.g. `https://hf-mirror.com` |
91
+ | `BGE_M3_LITE_OFFLINE=1` | never download, fail if files are missing |
92
+ | `BGE_M3_LITE_THREADS` | onnxruntime intra-op threads (default: physical cores) |
93
+
94
+ Model files are pinned to a specific Hugging Face revision and verified by
95
+ SHA-256 after download.
96
+
97
+ ## Development
98
+
99
+ See `AGENTS.md` and `docs/` (architecture, tokenizer, verification, development).
100
+
101
+ ## Status
102
+
103
+ v0.0.2: fp32 with exact parity with FlagEmbedding, plus an opt-in int8 backbone.
@@ -0,0 +1,77 @@
1
+ # bge-m3-lite
2
+
3
+ CPU inference for [BAAI/bge-m3](https://huggingface.co/BAAI/bge-m3) with
4
+ **`onnxruntime` as the only dependency**. All three BGE-M3 outputs are supported
5
+ and match the official PyTorch implementation (FlagEmbedding) to fp32 precision:
6
+
7
+ | output | shape | notes |
8
+ |---|---|---|
9
+ | `dense_vecs` | `(n, 1024)` | CLS pooling, L2-normalised |
10
+ | `lexical_weights` | `list[dict[str, float]]` | token-id → weight, max-pooled, specials removed |
11
+ | `colbert_vecs` | `list[(len-1, 1024)]` | per-token vectors without `<s>`, L2-normalised |
12
+
13
+ Everything except the transformer forward pass is implemented in this package
14
+ from scratch: the XLM-RoBERTa tokenizer (SentencePiece unigram model, the
15
+ `nmt_nfkc` precompiled charsmap, Unicode grapheme segmentation), the torch-free
16
+ loader for the sparse / ColBERT heads, the model downloader and the pooling.
17
+
18
+ Platforms: Apple Silicon, Linux ARM64, Linux x86_64 (Python 3.11+).
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ uv add bge-m3-lite # or: pip install bge-m3-lite
24
+ ```
25
+
26
+ ## Use
27
+
28
+ ```python
29
+ from bge_m3_lite import BGEM3Embedder
30
+
31
+ embedder = BGEM3Embedder() # first call downloads ~2.3 GB into ~/.cache/bge-m3-lite
32
+ out = embedder.encode(
33
+ ["What is BGE M3?", "BGE M3 是一個多語言嵌入模型。"],
34
+ return_dense=True,
35
+ return_sparse=True,
36
+ return_colbert_vecs=True,
37
+ )
38
+ out["dense_vecs"].shape # (2, 1024)
39
+ out["lexical_weights"][0] # {'4865': 0.08, '83': 0.08, ...}
40
+ out["colbert_vecs"][0].shape # (7, 1024)
41
+
42
+ embedder.convert_id_to_token(out["lexical_weights"][0])
43
+ embedder.compute_lexical_matching_score(lw_query, lw_passage)
44
+ embedder.colbert_score(q_vecs, p_vecs)
45
+ ```
46
+
47
+ Passing a single string returns unwrapped values, like FlagEmbedding.
48
+ `BGEM3Embedder(precision="int8")` loads a 4× smaller quantised backbone
49
+ (see `docs/quantization.md` for the accuracy trade-off).
50
+
51
+ ### CLI
52
+
53
+ ```bash
54
+ bge-m3-lite download # pre-fetch the model files
55
+ bge-m3-lite info # cache state
56
+ echo "hello" | bge-m3-lite encode --sparse --colbert --tokens
57
+ ```
58
+
59
+ ### Environment variables
60
+
61
+ | variable | effect |
62
+ |---|---|
63
+ | `BGE_M3_LITE_CACHE` | cache directory (default `~/.cache/bge-m3-lite/BAAI--bge-m3`) |
64
+ | `HF_ENDPOINT` | Hugging Face mirror, e.g. `https://hf-mirror.com` |
65
+ | `BGE_M3_LITE_OFFLINE=1` | never download, fail if files are missing |
66
+ | `BGE_M3_LITE_THREADS` | onnxruntime intra-op threads (default: physical cores) |
67
+
68
+ Model files are pinned to a specific Hugging Face revision and verified by
69
+ SHA-256 after download.
70
+
71
+ ## Development
72
+
73
+ See `AGENTS.md` and `docs/` (architecture, tokenizer, verification, development).
74
+
75
+ ## Status
76
+
77
+ v0.0.2: fp32 with exact parity with FlagEmbedding, plus an opt-in int8 backbone.
@@ -0,0 +1,6 @@
1
+ """bge-m3-lite: CPU inference for BAAI/bge-m3, onnxruntime is the only dependency."""
2
+
3
+ from bge_m3_lite.embedder import BGEM3Embedder
4
+
5
+ __version__ = "0.0.2"
6
+ __all__ = ["BGEM3Embedder", "__version__"]
@@ -0,0 +1,128 @@
1
+ """Extended grapheme cluster segmentation (UAX #29), pure Python.
2
+
3
+ Needed because Hugging Face's ``Precompiled`` normalizer applies the
4
+ SentencePiece charsmap per grapheme cluster rather than by longest prefix.
5
+ Tables come from ``_grapheme_data`` (generated from the Unicode database).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from bisect import bisect_right
11
+ from collections.abc import Iterator
12
+
13
+ from bge_m3_lite import _grapheme_data as _d
14
+
15
+ # Grapheme_Cluster_Break values (indices into _d.GCB_NAMES)
16
+ _OTHER, _CR, _LF, _CONTROL, _EXTEND, _ZWJ, _RI, _PREPEND, _SPACINGMARK = range(9)
17
+ _L, _V, _T, _LV, _LVT = range(9, 14)
18
+ # Indic_Conjunct_Break values
19
+ _INCB_NONE, _INCB_CONSONANT, _INCB_EXTEND, _INCB_LINKER = range(4)
20
+
21
+
22
+ def _lookup(cp: int, starts, ends, values) -> int:
23
+ i = bisect_right(starts, cp) - 1
24
+ if i >= 0 and cp <= ends[i]:
25
+ return values[i]
26
+ return 0
27
+
28
+
29
+ _gcb_cache: dict[int, int] = {}
30
+ _incb_cache: dict[int, int] = {}
31
+ _ext_cache: dict[int, bool] = {}
32
+
33
+
34
+ def gcb(cp: int) -> int:
35
+ v = _gcb_cache.get(cp)
36
+ if v is None:
37
+ v = _lookup(cp, _d.GCB_STARTS, _d.GCB_ENDS, _d.GCB_VALUES)
38
+ if len(_gcb_cache) < 65536:
39
+ _gcb_cache[cp] = v
40
+ return v
41
+
42
+
43
+ def incb(cp: int) -> int:
44
+ v = _incb_cache.get(cp)
45
+ if v is None:
46
+ v = _lookup(cp, _d.INCB_STARTS, _d.INCB_ENDS, _d.INCB_VALUES)
47
+ if len(_incb_cache) < 65536:
48
+ _incb_cache[cp] = v
49
+ return v
50
+
51
+
52
+ def is_extended_pictographic(cp: int) -> bool:
53
+ v = _ext_cache.get(cp)
54
+ if v is None:
55
+ v = bool(_lookup(cp, _d.EXTPICT_STARTS, _d.EXTPICT_ENDS, _d.EXTPICT_VALUES))
56
+ if len(_ext_cache) < 65536:
57
+ _ext_cache[cp] = v
58
+ return v
59
+
60
+
61
+ def grapheme_boundaries(text: str) -> Iterator[int]:
62
+ """Yield every index ``i`` (0 < i < len) where a grapheme cluster boundary falls."""
63
+ n = len(text)
64
+ if n < 2:
65
+ return
66
+ cps = [ord(c) for c in text]
67
+ props = [gcb(cp) for cp in cps]
68
+ ri_run = 0 # number of consecutive Regional_Indicator chars ending at i-1
69
+ for i in range(1, n):
70
+ prev, cur = props[i - 1], props[i]
71
+ if prev == _RI:
72
+ ri_run += 1
73
+ else:
74
+ ri_run = 0
75
+ # GB3
76
+ if prev == _CR and cur == _LF:
77
+ continue
78
+ # GB4 / GB5
79
+ if prev in (_CONTROL, _CR, _LF) or cur in (_CONTROL, _CR, _LF):
80
+ yield i
81
+ continue
82
+ # GB6 / GB7 / GB8 (Hangul)
83
+ if prev == _L and cur in (_L, _V, _LV, _LVT):
84
+ continue
85
+ if prev in (_LV, _V) and cur in (_V, _T):
86
+ continue
87
+ if prev in (_LVT, _T) and cur == _T:
88
+ continue
89
+ # GB9 / GB9a / GB9b
90
+ if cur in (_EXTEND, _ZWJ, _SPACINGMARK) or prev == _PREPEND:
91
+ continue
92
+ # GB9c: Consonant [Extend Linker]* Linker [Extend Linker]* x Consonant
93
+ if incb(cps[i]) == _INCB_CONSONANT:
94
+ j = i - 1
95
+ seen_linker = False
96
+ while j >= 0:
97
+ v = incb(cps[j])
98
+ if v == _INCB_LINKER:
99
+ seen_linker = True
100
+ elif v != _INCB_EXTEND:
101
+ break
102
+ j -= 1
103
+ if seen_linker and j >= 0 and incb(cps[j]) == _INCB_CONSONANT:
104
+ continue
105
+ # GB11: ExtPict Extend* ZWJ x ExtPict
106
+ if prev == _ZWJ and is_extended_pictographic(cps[i]):
107
+ j = i - 2
108
+ while j >= 0 and props[j] == _EXTEND:
109
+ j -= 1
110
+ if j >= 0 and is_extended_pictographic(cps[j]):
111
+ continue
112
+ # GB12 / GB13: keep RI pairs together
113
+ if prev == _RI and cur == _RI and ri_run % 2 == 1:
114
+ continue
115
+ # GB999
116
+ yield i
117
+
118
+
119
+ def graphemes(text: str) -> list[str]:
120
+ """Split ``text`` into extended grapheme clusters."""
121
+ out: list[str] = []
122
+ start = 0
123
+ for b in grapheme_boundaries(text):
124
+ out.append(text[start:b])
125
+ start = b
126
+ if text:
127
+ out.append(text[start:])
128
+ return out