v1.0
Browse files- .gitattributes +2 -0
- CHANGELOG.md +32 -0
- CHECKSUMS.sha256 +26 -0
- LICENSE +202 -0
- NOTICE +85 -0
- README.md +346 -0
- export/eg2_text.py +288 -0
- export/export.py +215 -0
- model/MODEL-ATTRIBUTION.json +36 -0
- model/config.json +54 -0
- model/embeddinggemma-2-text.aimodel/main.hash +1 -0
- model/embeddinggemma-2-text.aimodel/main.mlirb +3 -0
- model/embeddinggemma-2-text.aimodel/metadata.json +8 -0
- model/tokenizer/tokenizer.json +3 -0
- model/tokenizer/tokenizer_config.json +57 -0
- python/embeddinggemma2_coreai.py +278 -0
- python/requirements.lock +19 -0
- python/requirements.txt +7 -0
- python/smoke_test.py +62 -0
- swift/Package.swift +26 -0
- swift/Sources/EmbeddingGemma2CoreAI/Embedder.swift +197 -0
- swift/Sources/EmbeddingGemma2CoreAI/ExactJSON.swift +202 -0
- swift/Sources/EmbeddingGemma2CoreAI/GemmaTokenizer.swift +281 -0
- swift/Sources/eg2-cli/main.swift +187 -0
- tests/make_smoke_reference.py +54 -0
- tests/make_tokenizer_cases.py +298 -0
- tests/smoke-reference.json +2333 -0
- tests/tokenizer-cases.json +0 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
model/embeddinggemma-2-text.aimodel/main.mlirb filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
model/tokenizer/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
CHANGELOG.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Changelog
|
| 2 |
+
|
| 3 |
+
## 1.0 — 2026-10-12
|
| 4 |
+
|
| 5 |
+
First release package.
|
| 6 |
+
|
| 7 |
+
- Model: `model/embeddinggemma-2-text.aimodel`, the text encoder of google/embeddinggemma-2 on Apple Core AI
|
| 8 |
+
(macOS 27), fp16, every function one Neural Engine region plus the token lookup; buckets 32…2048.
|
| 9 |
+
Parity vs the fp32 reference on 357 fictional texts: cosine min 0.99992, median 0.99997 (min 0.99985 on a
|
| 10 |
+
second, independent set of 100 texts: expect >= 0.9998); retrieval ranks
|
| 11 |
+
identical at 768 dims on a 1,712-chunk / 53-query fictional benchmark.
|
| 12 |
+
- Swift: `EmbeddingGemma2CoreAI` library (no third-party dependencies) with an exact Gemma tokenizer
|
| 13 |
+
(`GemmaTokenizer`, byte-identical to Hugging Face `tokenizers` on 3,758 fixture texts / 608,225 ids) and
|
| 14 |
+
the `eg2-cli` tool (embed, smoke, tokcheck, latency, prewarm, cleanup-temp).
|
| 15 |
+
- Python: `embeddinggemma2_coreai.py` runner (library + CLI) using the `tokenizers` library, `smoke_test.py`,
|
| 16 |
+
pinned requirements.
|
| 17 |
+
- Tests: `tests/smoke-reference.json` (3 fictional sentences, fp32 reference vectors),
|
| 18 |
+
`tests/tokenizer-cases.json` (3,758 fictional texts with reference ids) and the generators for both.
|
| 19 |
+
- Exporter: `export/eg2_text.py` + `export/export.py` reproduce the bundle from the original checkpoint.
|
| 20 |
+
- Temp-file hygiene: both runners can delete the MPSGraph temp folder their own process created
|
| 21 |
+
(`--cleanup-temp`, `removeOwnMPSGraphTempFiles()`, `remove_own_mpsgraph_temp_files()`).
|
| 22 |
+
- Licensing: full Apache-2.0 text, NOTICE with the modification list and the Gemma Prohibited Use Policy
|
| 23 |
+
pointer, SHA-256 checksums of every file.
|
| 24 |
+
|
| 25 |
+
Changes relative to the internal validation build that preceded this package:
|
| 26 |
+
|
| 27 |
+
- The global-attention layers use the 4-D matmul form (the earlier bundle fell back to the GPU on most
|
| 28 |
+
buckets); the earlier bundle is not included.
|
| 29 |
+
- The Swift runner no longer depends on swift-transformers (whose BPE differed from Hugging Face on CRLF,
|
| 30 |
+
decomposed accents, emoji sequences, Arabic diacritics, Thai and whitespace runs longer than 31).
|
| 31 |
+
- tokenizer.json is read with an exact JSON reader: Foundation's JSONSerialization drops a leading U+FEFF from
|
| 32 |
+
string values, which corrupted three vocabulary entries.
|
CHECKSUMS.sha256
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
7c059597beb76a3ce4e7cc6e0b08f6aa7e8eeb932ab9b18e13234ca9537809e1 CHANGELOG.md
|
| 2 |
+
cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 LICENSE
|
| 3 |
+
dfaaa2df3d3b356d6ab7d37795893ca3916f9855095658700b35f387f8e7d68f NOTICE
|
| 4 |
+
742bc793350bbe91c402dcdbc339403f671b46a8c953d366a41f803300b3f52b README.md
|
| 5 |
+
bb778eee49dab44fb997f67c875a615d754d54477e257d02771e94f250b4cd38 export/eg2_text.py
|
| 6 |
+
9392857facca6d9b9a735146c72a342ffa8c52c7c752ea2c499acb10686a3cdc export/export.py
|
| 7 |
+
7ddb494420af4401b1e3c6e0b54f176c218f9d5068079c9ccfa7871fad5f5a19 model/MODEL-ATTRIBUTION.json
|
| 8 |
+
1791655ef7f1c0c093d200904263989c0f7cfc637869b71c6fc5f90f0f800f18 model/config.json
|
| 9 |
+
c2de75ebee766df0e980f63bc56a60847272cfe393de744e766bee9ba1a8049b model/embeddinggemma-2-text.aimodel/main.hash
|
| 10 |
+
75d38431abcd2471b0540c863357710d305cb1687295f5b0cf859de6ac9d46eb model/embeddinggemma-2-text.aimodel/main.mlirb
|
| 11 |
+
c2362545fc6b46be69f394ff0081a6e919a9099b19a84b167fbdeb71ba7c793c model/embeddinggemma-2-text.aimodel/metadata.json
|
| 12 |
+
4d777ef5bdc1aa36227abdfb77c3e49e7b9c892d16e1b6bda41c393504828be4 model/tokenizer/tokenizer.json
|
| 13 |
+
17bd5d6e9364ca49a534e1502076593317c298d4a663623091ed45388f004874 model/tokenizer/tokenizer_config.json
|
| 14 |
+
2559a5e111a31b53e9568c962be2fb51ec84c17912bcfc40f0cafb7f73ca9f01 python/embeddinggemma2_coreai.py
|
| 15 |
+
3bc2e5931e7dde258a82637e082bf9f12f9a6eed6c4cc703f9b8e4e02a9d3b0c python/requirements.lock
|
| 16 |
+
2d7b80b555b56787f2a142da1164c359e306b7ad3332661ac158b3a1f114d581 python/requirements.txt
|
| 17 |
+
3126ad6f90704e025f57632664fe1954a7c88b734a9e695441f471dff79d863e python/smoke_test.py
|
| 18 |
+
de934714b85d008c8e56b8027c86c0f1319bb3ca1bb0dec9ffe3ab7ca8c35876 swift/Package.swift
|
| 19 |
+
5c36c78cf268da78c3a6981bcfd4974b94641f756fa9616950cd39ebe6fc096f swift/Sources/EmbeddingGemma2CoreAI/Embedder.swift
|
| 20 |
+
7b99edfefc47a343d07df32a3b06a8da2f94a49ae0a8e299e733c1a44c09e38f swift/Sources/EmbeddingGemma2CoreAI/ExactJSON.swift
|
| 21 |
+
4f5ef8a454a84937e503ba99c9f05e237347178933a442f2352a2c5db0ad87eb swift/Sources/EmbeddingGemma2CoreAI/GemmaTokenizer.swift
|
| 22 |
+
f59a75b9eb311777bba3f8b1146b75a11f6587cdca5a2c1dab6fffc40d96a9aa swift/Sources/eg2-cli/main.swift
|
| 23 |
+
1df8f74c85f4a02be0caaf4b99ee0bb8f4cfc1759ad091fdf65a98fdea917df8 tests/make_smoke_reference.py
|
| 24 |
+
07daa7415beb15e9049a307aa17bf048ba2edaa0613301293e6250a66ed552b8 tests/make_tokenizer_cases.py
|
| 25 |
+
dce930ac23f448c55d3e4fbf5e3d1080213395510637d627f67b54a889f339e1 tests/smoke-reference.json
|
| 26 |
+
0797f417a0fa96270dc2f439478000fe3b3a235eb1e83b3054cf69674e323ae9 tests/tokenizer-cases.json
|
LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Apache License
|
| 3 |
+
Version 2.0, January 2004
|
| 4 |
+
http://www.apache.org/licenses/
|
| 5 |
+
|
| 6 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 7 |
+
|
| 8 |
+
1. Definitions.
|
| 9 |
+
|
| 10 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 11 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 12 |
+
|
| 13 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 14 |
+
the copyright owner that is granting the License.
|
| 15 |
+
|
| 16 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 17 |
+
other entities that control, are controlled by, or are under common
|
| 18 |
+
control with that entity. For the purposes of this definition,
|
| 19 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 20 |
+
direction or management of such entity, whether by contract or
|
| 21 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 22 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 23 |
+
|
| 24 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 25 |
+
exercising permissions granted by this License.
|
| 26 |
+
|
| 27 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
+
including but not limited to software source code, documentation
|
| 29 |
+
source, and configuration files.
|
| 30 |
+
|
| 31 |
+
"Object" form shall mean any form resulting from mechanical
|
| 32 |
+
transformation or translation of a Source form, including but
|
| 33 |
+
not limited to compiled object code, generated documentation,
|
| 34 |
+
and conversions to other media types.
|
| 35 |
+
|
| 36 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
+
Object form, made available under the License, as indicated by a
|
| 38 |
+
copyright notice that is included in or attached to the work
|
| 39 |
+
(an example is provided in the Appendix below).
|
| 40 |
+
|
| 41 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
+
form, that is based on (or derived from) the Work and for which the
|
| 43 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
+
of this License, Derivative Works shall not include works that remain
|
| 46 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
+
the Work and Derivative Works thereof.
|
| 48 |
+
|
| 49 |
+
"Contribution" shall mean any work of authorship, including
|
| 50 |
+
the original version of the Work and any modifications or additions
|
| 51 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
+
means any form of electronic, verbal, or written communication sent
|
| 56 |
+
to the Licensor or its representatives, including but not limited to
|
| 57 |
+
communication on electronic mailing lists, source code control systems,
|
| 58 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
+
excluding communication that is conspicuously marked or otherwise
|
| 61 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
+
|
| 63 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
+
subsequently incorporated within the Work.
|
| 66 |
+
|
| 67 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
+
Work and such Derivative Works in Source or Object form.
|
| 73 |
+
|
| 74 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
+
(except as stated in this section) patent license to make, have made,
|
| 78 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
+
where such license applies only to those patent claims licensable
|
| 80 |
+
by such Contributor that are necessarily infringed by their
|
| 81 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
+
institute patent litigation against any entity (including a
|
| 84 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
+
or contributory patent infringement, then any patent licenses
|
| 87 |
+
granted to You under this License for that Work shall terminate
|
| 88 |
+
as of the date such litigation is filed.
|
| 89 |
+
|
| 90 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
+
modifications, and in Source or Object form, provided that You
|
| 93 |
+
meet the following conditions:
|
| 94 |
+
|
| 95 |
+
(a) You must give any other recipients of the Work or
|
| 96 |
+
Derivative Works a copy of this License; and
|
| 97 |
+
|
| 98 |
+
(b) You must cause any modified files to carry prominent notices
|
| 99 |
+
stating that You changed the files; and
|
| 100 |
+
|
| 101 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
+
that You distribute, all copyright, patent, trademark, and
|
| 103 |
+
attribution notices from the Source form of the Work,
|
| 104 |
+
excluding those notices that do not pertain to any part of
|
| 105 |
+
the Derivative Works; and
|
| 106 |
+
|
| 107 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
+
distribution, then any Derivative Works that You distribute must
|
| 109 |
+
include a readable copy of the attribution notices contained
|
| 110 |
+
within such NOTICE file, excluding those notices that do not
|
| 111 |
+
pertain to any part of the Derivative Works, in at least one
|
| 112 |
+
of the following places: within a NOTICE text file distributed
|
| 113 |
+
as part of the Derivative Works; within the Source form or
|
| 114 |
+
documentation, if provided along with the Derivative Works; or,
|
| 115 |
+
within a display generated by the Derivative Works, if and
|
| 116 |
+
wherever such third-party notices normally appear. The contents
|
| 117 |
+
of the NOTICE file are for informational purposes only and
|
| 118 |
+
do not modify the License. You may add Your own attribution
|
| 119 |
+
notices within Derivative Works that You distribute, alongside
|
| 120 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
+
that such additional attribution notices cannot be construed
|
| 122 |
+
as modifying the License.
|
| 123 |
+
|
| 124 |
+
You may add Your own copyright statement to Your modifications and
|
| 125 |
+
may provide additional or different license terms and conditions
|
| 126 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
+
the conditions stated in this License.
|
| 130 |
+
|
| 131 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
+
this License, without any additional terms or conditions.
|
| 135 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
+
the terms of any separate license agreement you may have executed
|
| 137 |
+
with Licensor regarding such Contributions.
|
| 138 |
+
|
| 139 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
+
except as required for reasonable and customary use in describing the
|
| 142 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
+
|
| 144 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
+
implied, including, without limitation, any warranties or conditions
|
| 149 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
+
appropriateness of using or redistributing the Work and assume any
|
| 152 |
+
risks associated with Your exercise of permissions under this License.
|
| 153 |
+
|
| 154 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
+
unless required by applicable law (such as deliberate and grossly
|
| 157 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
+
liable to You for damages, including any direct, indirect, special,
|
| 159 |
+
incidental, or consequential damages of any character arising as a
|
| 160 |
+
result of this License or out of the use or inability to use the
|
| 161 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
+
other commercial damages or losses), even if such Contributor
|
| 164 |
+
has been advised of the possibility of such damages.
|
| 165 |
+
|
| 166 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
+
or other liability obligations and/or rights consistent with this
|
| 170 |
+
License. However, in accepting such obligations, You may act only
|
| 171 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
+
defend, and hold each Contributor harmless for any liability
|
| 174 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
+
of your accepting any such warranty or additional liability.
|
| 176 |
+
|
| 177 |
+
END OF TERMS AND CONDITIONS
|
| 178 |
+
|
| 179 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 180 |
+
|
| 181 |
+
To apply the Apache License to your work, attach the following
|
| 182 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 183 |
+
replaced with your own identifying information. (Don't include
|
| 184 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 185 |
+
comment syntax for the file format. We also recommend that a
|
| 186 |
+
file or class name and description of purpose be included on the
|
| 187 |
+
same "printed page" as the copyright notice for easier
|
| 188 |
+
identification within third-party archives.
|
| 189 |
+
|
| 190 |
+
Copyright [yyyy] [name of copyright owner]
|
| 191 |
+
|
| 192 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 193 |
+
you may not use this file except in compliance with the License.
|
| 194 |
+
You may obtain a copy of the License at
|
| 195 |
+
|
| 196 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 197 |
+
|
| 198 |
+
Unless required by applicable law or agreed to in writing, software
|
| 199 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 200 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 201 |
+
See the License for the specific language governing permissions and
|
| 202 |
+
limitations under the License.
|
NOTICE
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
EmbeddingGemma-2-CoreAI-text v1.0
|
| 2 |
+
=================================
|
| 3 |
+
|
| 4 |
+
This package contains a derivative of the EmbeddingGemma 2 model.
|
| 5 |
+
|
| 6 |
+
EmbeddingGemma 2
|
| 7 |
+
Authors: Google DeepMind (as stated on the model card)
|
| 8 |
+
https://huggingface.co/google/embeddinggemma-2
|
| 9 |
+
License as linked by the model card: Apache 2.0, https://ai.google.dev/gemma/docs/gemma_4_license
|
| 10 |
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use these files except in
|
| 11 |
+
compliance with the License. A copy of the License is in the file LICENSE and at
|
| 12 |
+
http://www.apache.org/licenses/LICENSE-2.0.
|
| 13 |
+
Unless required by applicable law or agreed to in writing, software distributed under the License is
|
| 14 |
+
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 15 |
+
|
| 16 |
+
The model card of google/embeddinggemma-2 asks that deployments adhere to the Gemma Prohibited Use Policy:
|
| 17 |
+
https://ai.google.dev/gemma/prohibited_use_policy. Please read it before using this model in a product.
|
| 18 |
+
|
| 19 |
+
The tokenizer files in model/tokenizer/ (tokenizer.json, tokenizer_config.json) are copied unchanged from
|
| 20 |
+
google/embeddinggemma-2 and are covered by the same license and attribution.
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
MODIFICATIONS (Apache License, Section 4(b))
|
| 24 |
+
--------------------------------------------
|
| 25 |
+
|
| 26 |
+
Changes made to google/embeddinggemma-2, 11-12 October 2026, to produce model/embeddinggemma-2-text.aimodel:
|
| 27 |
+
|
| 28 |
+
2026-10-11 Text encoder only. The vision and audio encoders of the multimodal checkpoint were dropped.
|
| 29 |
+
2026-10-11 The encoder architecture (24 bidirectional Gemma 4 layers, per-layer embeddings, RMSNorm with
|
| 30 |
+
max-abs rescaling, RoPE, GQA/MQA attention, GELU-tanh MLP, final RMSNorm, 512->768 projection,
|
| 31 |
+
masked mean pooling, L2 normalization) was re-authored in plain PyTorch for the Core AI
|
| 32 |
+
converter. The weights are the original ones, rounded from bfloat16 to float16; the sqrt(512)
|
| 33 |
+
token-embedding scale and the 512^-0.5 per-layer-embedding scale are folded into the tables.
|
| 34 |
+
2026-10-11 fp16 handling for the Neural Engine: every RMSNorm and the final L2 normalization divide the row
|
| 35 |
+
by its max-abs value before squaring (activations reach ~2000, whose square overflows fp16);
|
| 36 |
+
in the model code every division is expressed as rsqrt(x)*rsqrt(x); the additive attention-mask
|
| 37 |
+
value is -10000. (The model card advises against float16; this rescaling is what makes fp16
|
| 38 |
+
safe here, see the parity figures in README.md.)
|
| 39 |
+
2026-10-11 Pooling, projection and normalization run inside the graph; the output is the final embedding.
|
| 40 |
+
2026-10-11 Fixed sequence buckets: one function main_<S> per S in 32, 64, 128, 256, 512, 1024, 2048.
|
| 41 |
+
Input longer than 2048 tokens is not supported by this build (the original accepts 8192).
|
| 42 |
+
2026-10-12 Attention of the four global (full-attention, single key/value head) layers rewritten from a
|
| 43 |
+
5-D grouped matmul to an equivalent plain 4-D matmul with the key/value tensors broadcast over
|
| 44 |
+
the four query heads, so that the Neural Engine compiler accepts the whole graph. Mathematically
|
| 45 |
+
identical to the reference (fp32 max abs diff 3e-7).
|
| 46 |
+
2026-10-12 Converted with coreai-torch 0.4.3 / coreai-core 1.0.0b3 on torch 2.9.0 to the Core AI
|
| 47 |
+
.aimodel format (macOS 27).
|
| 48 |
+
|
| 49 |
+
The exporter in export/ reproduces these changes from the original checkpoint.
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
THIRD-PARTY ATTRIBUTIONS
|
| 53 |
+
------------------------
|
| 54 |
+
|
| 55 |
+
HF transformers (Apache-2.0, Copyright 2026 The HuggingFace Team). export/eg2_text.py re-authors the
|
| 56 |
+
architecture described by modeling_embedding_gemma2.py / configuration_embedding_gemma2.py from
|
| 57 |
+
transformers 5.19. No transformers source files are included in this package; the re-authored module carries
|
| 58 |
+
its own Apache-2.0 header and credits transformers.
|
| 59 |
+
|
| 60 |
+
FluidInference Core ML port of EmbeddingGemma 2 (Apache-2.0, https://huggingface.co/FluidInference). The
|
| 61 |
+
idea of dividing each RMSNorm row by its max-abs value to keep fp16 in range was taken from the README of
|
| 62 |
+
that port and re-derived independently; no code was copied from it. Nothing else in this package derives from
|
| 63 |
+
it.
|
| 64 |
+
|
| 65 |
+
Apple Core AI (coreai-torch, coreai-core, the CoreAI framework): used as tools and runtime, not redistributed.
|
| 66 |
+
Hugging Face tokenizers (Apache-2.0): used by the Python runner and to generate the test fixtures; not
|
| 67 |
+
redistributed.
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
TRADEMARKS
|
| 71 |
+
----------
|
| 72 |
+
|
| 73 |
+
Gemma and EmbeddingGemma are trademarks of Google LLC. Apple, Core AI, Apple silicon and Neural Engine are
|
| 74 |
+
trademarks of Apple Inc. This port is not affiliated with, sponsored by or endorsed by Google or Apple.
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
OUR OWN CODE
|
| 78 |
+
------------
|
| 79 |
+
|
| 80 |
+
Everything in this package that is not the model weights or the tokenizer files, i.e. the exporter
|
| 81 |
+
(export/), the Python runner (python/), the Swift library and command-line tool (swift/), the tests (tests/)
|
| 82 |
+
and the documentation, is
|
| 83 |
+
|
| 84 |
+
Copyright 2026 Everpaper
|
| 85 |
+
Licensed under the Apache License, Version 2.0 (LICENSE).
|
README.md
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
base_model: google/embeddinggemma-2
|
| 4 |
+
library_name: coreai
|
| 5 |
+
pipeline_tag: feature-extraction
|
| 6 |
+
language:
|
| 7 |
+
- multilingual
|
| 8 |
+
tags:
|
| 9 |
+
- coreai
|
| 10 |
+
- apple-neural-engine
|
| 11 |
+
- on-device
|
| 12 |
+
- embeddings
|
| 13 |
+
- sentence-similarity
|
| 14 |
+
- gemma
|
| 15 |
+
- macos
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
# EmbeddingGemma 2 (text) for Apple Core AI
|
| 19 |
+
|
| 20 |
+
The text encoder of [google/embeddinggemma-2](https://huggingface.co/google/embeddinggemma-2) (Gemma 4 family,
|
| 21 |
+
~270 M parameters, 768-d Matryoshka embeddings) converted to Apple's **Core AI** runtime for **macOS 27 on Apple
|
| 22 |
+
silicon**. One graph does everything `SentenceTransformer("google/embeddinggemma-2").encode()` does for text: token
|
| 23 |
+
embedding, 24 bidirectional encoder layers, final norm, 512→768 projection, masked mean pooling and L2
|
| 24 |
+
normalization. Every function runs as one Neural Engine region plus the token lookup (a small GPU gather), so a
|
| 25 |
+
document costs about **0.3 ms of CPU** and **0.4–0.5 ms of GPU time**; the Neural Engine does the rest.
|
| 26 |
+
|
| 27 |
+
Ported by Everpaper — everpaper.app. Weights are the original ones (bf16 rounded to fp16). Apache-2.0, see
|
| 28 |
+
[LICENSE](LICENSE) and [NOTICE](NOTICE) (modification list, Gemma Prohibited Use Policy pointer).
|
| 29 |
+
|
| 30 |
+
## Download
|
| 31 |
+
|
| 32 |
+
```bash
|
| 33 |
+
hf download iannedev/embeddinggemma-2-coreai --local-dir EmbeddingGemma-2-CoreAI-text-v1.0
|
| 34 |
+
cd EmbeddingGemma-2-CoreAI-text-v1.0
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
(`hf` comes with `pip install -U huggingface_hub`.) Then run one of the quick starts below; both begin with a
|
| 38 |
+
smoke test that checks the model on your machine.
|
| 39 |
+
|
| 40 |
+
## What is in the package
|
| 41 |
+
|
| 42 |
+
```
|
| 43 |
+
EmbeddingGemma-2-CoreAI-text-v1.0/
|
| 44 |
+
README.md, LICENSE, NOTICE, CHANGELOG.md, CHECKSUMS.sha256
|
| 45 |
+
model/
|
| 46 |
+
embeddinggemma-2-text.aimodel/ Core AI asset: functions main_32 … main_2048 (fp16, 631.5 MB = 602 MiB)
|
| 47 |
+
config.json buckets, prompts, special ids, Matryoshka dims
|
| 48 |
+
tokenizer/ tokenizer.json + tokenizer_config.json, unchanged from google/embeddinggemma-2
|
| 49 |
+
MODEL-ATTRIBUTION.json base model, checkpoint sha256, exporter versions, modification list
|
| 50 |
+
swift/ SwiftPM package: library `EmbeddingGemma2CoreAI` + tool `eg2-cli` (no dependencies)
|
| 51 |
+
python/ embeddinggemma2_coreai.py (library + CLI), smoke_test.py, requirements(.lock)
|
| 52 |
+
tests/ smoke-reference.json, tokenizer-cases.json (+ the scripts that generate them)
|
| 53 |
+
export/ eg2_text.py + export.py: reproduce the .aimodel from the original checkpoint
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
## Requirements
|
| 57 |
+
|
| 58 |
+
- macOS 27.0 or later on Apple silicon (the package was validated on an M5 with 24 GB; macOS 27.0 build
|
| 59 |
+
26A5406e, Xcode 27 beta).
|
| 60 |
+
- Swift: Xcode 27 / Swift 6 toolchain; the `CoreAI` framework is part of the macOS 27 SDK.
|
| 61 |
+
- Python (runtime): Python 3.12 or 3.14 (both validated), Apple's `coreai-core` Python runtime (provides
|
| 62 |
+
`coreai.runtime`), `numpy`, `tokenizers` (`python/requirements.txt`; the exact validated versions are in
|
| 63 |
+
`python/requirements.lock`). Only the exporter (`export/`, optional) needs Python 3.12, for torch 2.9.0.
|
| 64 |
+
- Disk: 664 MB for `model/` (the `.aimodel` is 631.5 MB, the tokenizer 32 MB); the system keeps a per-executable
|
| 65 |
+
specialization cache of about 1.4 GB under `~/Library/Caches/coreai-cache/`.
|
| 66 |
+
|
| 67 |
+
## Inputs and outputs
|
| 68 |
+
|
| 69 |
+
| | |
|
| 70 |
+
|---|---|
|
| 71 |
+
| Functions | `main_32`, `main_64`, `main_128`, `main_256`, `main_512`, `main_1024`, `main_2048` (one per sequence bucket S) |
|
| 72 |
+
| `input_ids` | `[1, S]` int32: `<bos>`=2, prompt-prefix tokens, text tokens, `<eos>`=1, right-padded with `<pad>`=0 |
|
| 73 |
+
| `attention_mask` | `[1, S]` float16: 1 for real tokens, 0 for padding |
|
| 74 |
+
| `embedding` | `[1, 768]` float16, already L2-normalized |
|
| 75 |
+
| Max length | 2048 tokens including `<bos>`/`<eos>` and the prefix. Longer input is **truncated** by the runners (the original model accepts 8192; chunk long documents at ≤ 512 tokens for retrieval, the embedding is a mean over tokens anyway). |
|
| 76 |
+
| Bucket choice | the smallest S ≥ token count. Padding does not change the result (verified: padded and unpadded runs are bit-identical). |
|
| 77 |
+
| Matryoshka | 512 / 256 / 128 dims: take the first N values and re-normalize (`embed(..., dim:)` does it). |
|
| 78 |
+
|
| 79 |
+
**Task prompts** (exactly the upstream `config_sentence_transformers.json`; the runners prepend them by `kind`):
|
| 80 |
+
|
| 81 |
+
| kind | prefix |
|
| 82 |
+
|---|---|
|
| 83 |
+
| `document` | `title: none \| text: ` — for titled documents use `title: {title} \| text: {content}` yourself |
|
| 84 |
+
| `search_query` | `task: search result \| query: ` |
|
| 85 |
+
| `question_answering` | `task: question answering \| query: ` |
|
| 86 |
+
| `fact_checking` | `task: fact checking \| query: ` |
|
| 87 |
+
| `code_retrieval` | `task: code retrieval \| query: ` |
|
| 88 |
+
| `classification` | `task: classification \| query: ` |
|
| 89 |
+
| `clustering` | `task: clustering \| query: ` |
|
| 90 |
+
| `sentence_similarity` | `task: sentence similarity \| query: ` |
|
| 91 |
+
|
| 92 |
+
The tokenizer is the original Gemma `tokenizer.json`. The runners add `<bos>` and `<eos>` themselves, as
|
| 93 |
+
sentence-transformers does. Note that special tokens written literally in a text (e.g. `<eos>`) are recognised as
|
| 94 |
+
special tokens, exactly as in Hugging Face `tokenizers`; strip them if that is not what you want.
|
| 95 |
+
|
| 96 |
+
## Quick start — Swift
|
| 97 |
+
|
| 98 |
+
```swift
|
| 99 |
+
// Package.swift: .package(path: "…/EmbeddingGemma-2-CoreAI-text-v1.0/swift"), product "EmbeddingGemma2CoreAI"
|
| 100 |
+
import EmbeddingGemma2CoreAI
|
| 101 |
+
|
| 102 |
+
let model = try await EmbeddingGemma2(bundle: URL(fileURLWithPath: "…/model"), compute: .neuralEngine)
|
| 103 |
+
try await model.prewarm() // optional, one call per bucket (~0.4 s warm)
|
| 104 |
+
|
| 105 |
+
let q = try await model.embed("When does the warehouse lease expire?", kind: "search_query") // [Float], 768
|
| 106 |
+
let d = try await model.embed("The lease runs until the last day of March.", kind: "document", dim: 256)
|
| 107 |
+
let score = cosineSimilarity(q, try await model.embed("The lease runs until the last day of March.", kind: "document"))
|
| 108 |
+
|
| 109 |
+
// lower level: your own ids -> raw output of one bucket
|
| 110 |
+
let ids = model.tokenize("some text", kind: "document") // <bos> + prefix + text + <eos>, truncated to 2048
|
| 111 |
+
let raw = try await model.embed(ids: ids) // runs main_<smallest bucket that fits>
|
| 112 |
+
|
| 113 |
+
// when the model is released (or at exit) you may delete the temp files this process created, see "Temp files"
|
| 114 |
+
EmbeddingGemma2.removeOwnMPSGraphTempFiles()
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
`GemmaTokenizer` is public too (`encode`, `encodeWithSpecials`, `decode`): an exact re-implementation of the
|
| 118 |
+
Hugging Face `tokenizers` pipeline for this `tokenizer.json`, with no dependencies (see "Tokenizer" below).
|
| 119 |
+
|
| 120 |
+
Command line (`cd swift && swift build -c release`; the binary is `.build/release/eg2-cli` or
|
| 121 |
+
`.build/out/Products/Release/eg2-cli` depending on the toolchain):
|
| 122 |
+
|
| 123 |
+
```
|
| 124 |
+
eg2-cli smoke ../model ../tests/smoke-reference.json --compute ane --cleanup-temp
|
| 125 |
+
eg2-cli embed ../model --kind search_query "When does the warehouse lease expire?"
|
| 126 |
+
eg2-cli tokcheck ../model ../tests/tokenizer-cases.json
|
| 127 |
+
eg2-cli latency ../model --compute ane --trials 50
|
| 128 |
+
eg2-cli prewarm ../model
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
## Quick start — Python
|
| 132 |
+
|
| 133 |
+
From the package folder:
|
| 134 |
+
|
| 135 |
+
```
|
| 136 |
+
python3 -m venv .venv && .venv/bin/pip install -r python/requirements.txt
|
| 137 |
+
source .venv/bin/activate
|
| 138 |
+
cd python && python3 smoke_test.py --compute ane --cleanup-temp # first run: up to ~145 s to load, see below
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
```python
|
| 142 |
+
from embeddinggemma2_coreai import EmbeddingGemma2CoreAI, remove_own_mpsgraph_temp_files
|
| 143 |
+
|
| 144 |
+
m = EmbeddingGemma2CoreAI("../model", compute="ane") # ane | gpu | cpu | default
|
| 145 |
+
m.prewarm()
|
| 146 |
+
q = m.embed("When does the warehouse lease expire?", kind="search_query") # numpy float32 [768]
|
| 147 |
+
d = m.embed("The lease runs until the last day of March.", kind="document", dim=256)
|
| 148 |
+
del m
|
| 149 |
+
remove_own_mpsgraph_temp_files() # the Python runtime leaks ~358 MB per load, see below
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
`python3 embeddinggemma2_coreai.py embed|smoke|tokcheck|latency|prewarm …` mirrors the Swift tool (run it with the
|
| 153 |
+
venv's Python, as above).
|
| 154 |
+
|
| 155 |
+
## Accuracy (measured)
|
| 156 |
+
|
| 157 |
+
Reference: sentence-transformers 6.1 / transformers 5.19, fp32, CPU, original checkpoint. Test set: 357 fictional
|
| 158 |
+
texts (English and Italian documents and queries, code, mixed-language, Unicode/number/repetition stress cases, and
|
| 159 |
+
texts whose length lands exactly on each bucket boundary), 2 to 2048 tokens. The people, companies and events in
|
| 160 |
+
the test material are fictional; a few real place names appear in the multilingual stress texts.
|
| 161 |
+
|
| 162 |
+
**Parity** — cosine similarity between this port and the fp32 reference, full 768 dims (128-dim Matryoshka in
|
| 163 |
+
brackets):
|
| 164 |
+
|
| 165 |
+
| route | min | median | worst category |
|
| 166 |
+
|---|---|---|---|
|
| 167 |
+
| Neural Engine (`compute: .neuralEngine`), Swift and Python | 0.99992 (0.99995) | 0.99997 (0.99998) | 33-token bucket-boundary text |
|
| 168 |
+
| GPU (`compute: .gpu`) | 0.99998 (0.99999) | 0.999999 (1.000000) | code |
|
| 169 |
+
| CPU only (`compute: .cpu`), Swift and Python | 0.99916 (0.99914) | 0.99996 (0.99998) | 2047-token text (the CPU fp16 path is the least precise) |
|
| 170 |
+
| for scale: the original model itself in bf16 on the GPU (PyTorch MPS) | 0.99956 | 0.99996 | |
|
| 171 |
+
|
| 172 |
+
Per bucket on the Neural Engine the minimum is 0.99992–0.99996 and the median 0.99997–0.99998; no NaN/Inf on any
|
| 173 |
+
input, including fully padded and 1-token inputs. Texts longer than 2048 tokens are truncated by design and their
|
| 174 |
+
cosine to the full-length reference falls as expected (0.998 at 2100 tokens, 0.98 at 4500).
|
| 175 |
+
|
| 176 |
+
**Independent check.** A second, separate set of 100 new fictional texts (Italian, emoji, CRLF, Thai and other
|
| 177 |
+
scripts, NFD accents, code, one-word inputs, long texts, exact bucket boundaries, all 8 prompt kinds) against a
|
| 178 |
+
freshly downloaded fp32 sentence-transformers reference, Neural Engine route: minimum cosine 0.99985 (an emoji-heavy
|
| 179 |
+
text), median 0.99997 at 768 dims; 0.99993 / 0.999985 at 128 dims; Swift and Python identical (largest difference
|
| 180 |
+
2.7e-7). **Expect a cosine ≥ 0.9998 to the fp32 model on the Neural Engine and GPU routes, ≥ 0.999 on the CPU route.**
|
| 181 |
+
|
| 182 |
+
**About float16.** The upstream model card says *"Do not use float16"*: the model's activations exceed the fp16
|
| 183 |
+
range and a naive fp16 run returns NaN or degraded vectors. This port runs in fp16 on purpose (the Neural Engine is
|
| 184 |
+
fp16-only) and avoids the overflow by dividing every RMSNorm row and the final L2 normalization by its max-abs value
|
| 185 |
+
before squaring (see NOTICE). The parity figures above are the evidence that this works; if you change the graph, keep
|
| 186 |
+
that rescaling.
|
| 187 |
+
|
| 188 |
+
**Retrieval** — 1,712 fictional passages, 53 queries (16 Italian), gold ranks compared rank-by-rank with the fp32
|
| 189 |
+
reference:
|
| 190 |
+
|
| 191 |
+
| dims | fp32 reference MRR | this port, Neural Engine, MRR | queries with the same first-relevant rank |
|
| 192 |
+
|---|---|---|---|
|
| 193 |
+
| 768 | 0.766 | 0.766 | 53 / 53 |
|
| 194 |
+
| 512 | 0.790 | 0.782 | 51 / 53 |
|
| 195 |
+
| 256 | 0.786 | 0.785 | 52 / 53 |
|
| 196 |
+
| 128 | 0.735 | 0.735 | 52 / 53 |
|
| 197 |
+
|
| 198 |
+
The flipped ranks are gold passages moving by one rank among near-tied neighbours (e.g. fp32 scores 0.82757 vs
|
| 199 |
+
0.82755 at 512 dims) plus one query whose gold passage sits outside the top 10 in fp32 and at rank 10 here.
|
| 200 |
+
Italian MRR is identical at every size (0.831 / 0.830 / 0.834 / 0.716). The GPU route of this bundle was checked
|
| 201 |
+
for parity (table above) but not re-run on the retrieval benchmark.
|
| 202 |
+
|
| 203 |
+
**Smoke test** (`tests/smoke-reference.json`, 3 fictional sentences, threshold 0.999): Neural Engine cosine
|
| 204 |
+
0.999948 / 0.999979 / 0.999976 (Swift and Python); Matryoshka 512 / 256 / 128 of the second sentence 0.999979 /
|
| 205 |
+
0.999981 / 0.999989. GPU route 0.999999 / 0.999999 / 1.000000, CPU route 0.999970 / 0.999966 / 0.999970.
|
| 206 |
+
|
| 207 |
+
Caveats: these are one machine's numbers (M5, macOS 27.0 26A5406e). Core AI specializes the graph per machine
|
| 208 |
+
and OS build; the CPU-only route in particular depends on the OS's fp16 CPU kernels. Re-run `smoke` and, if you
|
| 209 |
+
care about the last digit, `tests/` on your target machines.
|
| 210 |
+
|
| 211 |
+
## Performance (measured, Swift `eg2-cli latency`, 50 trials per bucket, M5)
|
| 212 |
+
|
| 213 |
+
Single runs on one machine; repeated runs differ by a few percent (e.g. 195–199 ms at 2048 tokens on the Neural
|
| 214 |
+
Engine).
|
| 215 |
+
|
| 216 |
+
| bucket (tokens) | Neural Engine p50 | CPU ms per call (NE) | GPU route p50 | CPU ms per call (GPU) |
|
| 217 |
+
|---|---|---|---|---|
|
| 218 |
+
| 32 | 4.8 ms | 0.24 | 7.6 ms | 10.3 |
|
| 219 |
+
| 64 | 5.3 ms | 0.24 | 7.9 ms | 10.3 |
|
| 220 |
+
| 128 | 6.6 ms | 0.26 | 10.3 ms | 11.1 |
|
| 221 |
+
| 256 | 11.6 ms | 0.27 | 12.2 ms | 11.7 |
|
| 222 |
+
| 512 | 27.8 ms | 0.26 | 21.9 ms | 14.8 |
|
| 223 |
+
| 1024 | 69 ms | 0.33 | 49 ms | 20.1 |
|
| 224 |
+
| 2048 | 195 ms | 0.39 | 142 ms | 30.6 |
|
| 225 |
+
|
| 226 |
+
The Python runner gives the same Neural Engine latencies within a few percent (CPU per call 0.4–0.9 ms). On the
|
| 227 |
+
Neural Engine route the GPU is used only for the int32 token gather: 0.38–0.54 ms of GPU time per call (32–1024
|
| 228 |
+
tokens), measured during validation with `task_power_info_v2` over 100-call batches. The GPU route is faster from 512
|
| 229 |
+
tokens up but burns 10–30 ms of CPU per call (MPSGraph encoding) and 1.5 GB of memory.
|
| 230 |
+
Throughput on a realistic corpus (1,712 passages, median 194 tokens, one text per call): about 50 passages/s,
|
| 231 |
+
0.17 CPU-seconds per 100 passages.
|
| 232 |
+
|
| 233 |
+
Memory: process footprint ~230 MB after a warm load (peak ~700 MB during the first-ever load) plus ~600–770 MB of
|
| 234 |
+
Neural Engine footprint reported by the kernel; the GPU route peaks at ~1.5 GB.
|
| 235 |
+
|
| 236 |
+
**Load time** (`AIModel(contentsOf:)` + `loadFunction` for the 7 buckets):
|
| 237 |
+
|
| 238 |
+
| situation | time |
|
| 239 |
+
|---|---|
|
| 240 |
+
| warm (this executable has loaded this bundle before) | 0.4–1.5 s, plus 0.35 s for `prewarm()` |
|
| 241 |
+
| **first load by an executable that has not loaded this bundle before** (a new app, a new binary name, the Python interpreter, a new machine) | **~140 s** (139–144 s measured with three different executables) |
|
| 242 |
+
| first load by a new executable on a machine where another executable already compiled this exact bundle | can be shorter (~25–30 s observed), do not rely on it |
|
| 243 |
+
| GPU route, first load | ~9–22 s |
|
| 244 |
+
|
| 245 |
+
Recommendation: load and `prewarm()` in the background at app start (or in an installer / first-run step) and show
|
| 246 |
+
progress; never on the main thread and never lazily in front of the first query. The cache lives in
|
| 247 |
+
`~/Library/Caches/coreai-cache/<OS build>/<executable name>/` (about 1.4 GB per bundle per executable name, keyed
|
| 248 |
+
by the bundle's content hash) and survives reboots but not OS updates, a renamed executable or a re-exported
|
| 249 |
+
bundle. The tokenizer loads in 0.22 s (Swift) / 0.23 s (Python).
|
| 250 |
+
|
| 251 |
+
## Temp files (please read)
|
| 252 |
+
|
| 253 |
+
Each Neural Engine load writes ~358 MB of compiled-graph copies to
|
| 254 |
+
`$TMPDIR/com.apple.MetalPerformanceShadersGraph/mpsgraph-<pid>-<date>-<n>/`. Measured on macOS 27.0:
|
| 255 |
+
|
| 256 |
+
- Swift (`CoreAI` framework): the folder is deleted again by the runtime when the model is released; our CLI, which
|
| 257 |
+
releases the model and then exits, leaves nothing behind in any of our runs. If your app exits or is killed while
|
| 258 |
+
the model is loaded, the folder may remain.
|
| 259 |
+
- Python (`coreai.runtime`): the folder is **never** deleted, not even after `del model` and a clean interpreter exit
|
| 260 |
+
(358 MB per load, verified for normal exit, `os._exit`, and `kill -9`). Over a day of development this fills a disk.
|
| 261 |
+
|
| 262 |
+
Both runners therefore ship a cleanup that removes only the folders belonging to the **current process** (its pid,
|
| 263 |
+
created after the process started): `EmbeddingGemma2.removeOwnMPSGraphTempFiles()` and
|
| 264 |
+
`remove_own_mpsgraph_temp_files()`, or `--cleanup-temp` on every CLI command. Call it after the model has been
|
| 265 |
+
released, or at exit. Folders of other processes are never touched; a leftover from a crashed run has to be removed
|
| 266 |
+
by hand (the folder name carries the pid and date).
|
| 267 |
+
|
| 268 |
+
## Tokenizer
|
| 269 |
+
|
| 270 |
+
The Swift `GemmaTokenizer` re-implements the Hugging Face `tokenizers` pipeline described by `tokenizer.json`
|
| 271 |
+
(special-token extraction, `Replace " " → "▁"` normalizer, BPE with byte fallback, rank-ordered merges with
|
| 272 |
+
position tie-break). It works on Unicode scalars and exact UTF-8 bytes: no grapheme clusters, no
|
| 273 |
+
canonical-equivalence string comparisons, and `tokenizer.json` is read with an exact JSON reader because Foundation's
|
| 274 |
+
`JSONSerialization` strips a leading U+FEFF from string values (which silently corrupts three vocabulary entries).
|
| 275 |
+
|
| 276 |
+
Proof: `tests/tokenizer-cases.json` holds 3,758 fictional texts (608,225 ids) covering CRLF and mixed line endings,
|
| 277 |
+
NFD / NFKD / mixed-normalization accents, emoji ZWJ sequences, skin tones, flags and keycaps, Arabic and Persian
|
| 278 |
+
with diacritics, Hebrew, Thai, Hindi, Bengali, Tamil, Chinese (simplified and traditional), Japanese, Korean,
|
| 279 |
+
Georgian, Armenian, Amharic and 18 more languages, whitespace runs of 31–500 spaces / newlines / tabs / NBSP /
|
| 280 |
+
ideographic spaces, control characters and invisible code points, special-token look-alikes (`<bos>`, `<|tool>`,
|
| 281 |
+
`<start_of_turn>`, `< bos >`, `▁<bos>▁`, …), empty and 1-character inputs, random code points from 23 Unicode blocks
|
| 282 |
+
including unassigned, private-use and non-characters, and inputs of 5,000–80,000 characters. `eg2-cli tokcheck`
|
| 283 |
+
reports **3758 / 3758 identical** to `tokenizers` 0.23.3, in 0.15 s. The Python runner uses `tokenizers` directly.
|
| 284 |
+
|
| 285 |
+
Regenerate the fixture with `tests/make_tokenizer_cases.py` (needs `tokenizers`) and the reference vectors with
|
| 286 |
+
`tests/make_smoke_reference.py` (needs sentence-transformers and the original checkpoint).
|
| 287 |
+
|
| 288 |
+
## Known issues and limitations
|
| 289 |
+
|
| 290 |
+
- **2048-token cap.** Longer inputs are truncated by the runners. Buckets of 4096/8192 can be exported
|
| 291 |
+
(`export.py --buckets …,4096,8192`) but were not validated here and would run on the GPU.
|
| 292 |
+
- **One text per call.** The functions are batch-1. Throughput for a corpus comes from calling them back to back;
|
| 293 |
+
running two or four calls concurrently did not increase throughput in our tests (the Neural Engine serializes).
|
| 294 |
+
- **Short inputs are not faster than ~4.8 ms.** There is a per-call floor in this Core AI build; 32- and 64-token
|
| 295 |
+
inputs cost the same.
|
| 296 |
+
- **CPU-only route is less precise** (cosine ≥ 0.9991) — use it only as a fallback.
|
| 297 |
+
- **Latency is not shape-stable across bucket sets.** Core AI specializes the whole function set together; adding
|
| 298 |
+
or removing buckets changed the 512-token latency by half (22 vs 34 ms) in our experiments. Measure the exact
|
| 299 |
+
bucket set you ship.
|
| 300 |
+
- **Specialization cache is per executable name.** Renaming your binary costs one first load of up to ~140 s.
|
| 301 |
+
- **Temp files** as described above.
|
| 302 |
+
- **fp16 output.** The embedding is fp16 (relative precision ~5e-4); convert to float32 before accumulating dot
|
| 303 |
+
products over many vectors, as the runners do.
|
| 304 |
+
- Validated on one machine and one OS build (macOS 27.0 26A5406e). Core AI is new in macOS 27 and its runtime
|
| 305 |
+
behaviour (first-load time, placement, temp files) may change between OS releases.
|
| 306 |
+
|
| 307 |
+
## Reproducing the export
|
| 308 |
+
|
| 309 |
+
```
|
| 310 |
+
# a separate environment with Python 3.12 (torch 2.9.0); exact versions in python/requirements.lock
|
| 311 |
+
python3.12 -m venv .venv-export && source .venv-export/bin/activate
|
| 312 |
+
pip install coreai-core==1.0.0b3 torch==2.9.0 coreai-torch==0.4.3 coreai-opt==0.3.0 torchao==0.17.0 \
|
| 313 |
+
safetensors numpy tokenizers huggingface-hub
|
| 314 |
+
# the original checkpoint: config.json, model.safetensors, tokenizer.json, tokenizer_config.json
|
| 315 |
+
hf download google/embeddinggemma-2 --local-dir ./embeddinggemma-2
|
| 316 |
+
cd export
|
| 317 |
+
python3 -W ignore export.py --model-dir ../embeddinggemma-2 --out ../model-rebuilt --name embeddinggemma-2-text \
|
| 318 |
+
--mode ane --buckets 32,64,128,256,512,1024,2048 --date 2026-10-12
|
| 319 |
+
```
|
| 320 |
+
|
| 321 |
+
`eg2_text.py` is the re-authored encoder (plain PyTorch, `PRECISION_MODE = "ane"`: all fp16, max-abs-rescaled
|
| 322 |
+
norms, no divides in the model code; `GLOBAL_ATTN_MODE = "mqa4d"`). `export.py` does one dynamic-length `torch.export`, converts with
|
| 323 |
+
`coreai_torch.TorchConverter`, clones the entry point per bucket with `set_static_shape_config`, and writes the
|
| 324 |
+
bundle (~20 s). torchao must be 0.17.0 with torch 2.9 (0.18 breaks `import coreai_opt`).
|
| 325 |
+
|
| 326 |
+
The rebuilt bundle is **functionally identical, not byte-identical**: its smoke-test cosines match the shipped bundle
|
| 327 |
+
to all printed digits (0.999948 / 0.999979 / 0.999976), but the converter names internal graph resources at random,
|
| 328 |
+
so `main.mlirb` and `main.hash` differ by a few KB, and the runtime writes `metadata.json` with the export time and
|
| 329 |
+
its own key order. `config.json`, `MODEL-ATTRIBUTION.json` (with `--date 2026-10-12`) and the tokenizer files come out
|
| 330 |
+
byte-identical, so those four entries of `CHECKSUMS.sha256` match. Verify a rebuild with `smoke`, not with
|
| 331 |
+
checksums. A rebuilt bundle has a new content hash, so its first load takes the full ~140 s again.
|
| 332 |
+
|
| 333 |
+
To check Neural Engine eligibility without running: `xcrun coreai-build compile model/embeddinggemma-2-text.aimodel
|
| 334 |
+
--output ./compiled --preferred-compute neural-engine --architecture h17g 2>&1 | grep -c ane_validation_message`
|
| 335 |
+
prints 0 for this bundle (no op rejected; the output folder is about 700 MB, delete it afterwards).
|
| 336 |
+
|
| 337 |
+
## Credits and license
|
| 338 |
+
|
| 339 |
+
- Model: EmbeddingGemma 2 by the Gemma Team, Google DeepMind — Apache-2.0. Deployments must adhere to the
|
| 340 |
+
[Gemma Prohibited Use Policy](https://ai.google.dev/gemma/prohibited_use_policy).
|
| 341 |
+
- Architecture reference: Hugging Face transformers (Apache-2.0).
|
| 342 |
+
- The max-abs RMSNorm rescaling idea comes from the FluidInference Core ML port of this model (Apache-2.0); no code
|
| 343 |
+
was copied.
|
| 344 |
+
- Port, exporter, runners, tokenizer and tests: Copyright 2026 Everpaper, Apache-2.0.
|
| 345 |
+
- Gemma and EmbeddingGemma are trademarks of Google LLC; Apple, Core AI, Apple silicon and Neural Engine are
|
| 346 |
+
trademarks of Apple Inc. This port is not affiliated with, sponsored by or endorsed by Google or Apple.
|
export/eg2_text.py
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 Everpaper. Licensed under the Apache License, Version 2.0 (see ../LICENSE).
|
| 2 |
+
#
|
| 3 |
+
# Re-authoring of the EmbeddingGemma 2 text encoder architecture as described by
|
| 4 |
+
# modeling_embedding_gemma2.py / configuration_embedding_gemma2.py in Hugging Face transformers 5.19
|
| 5 |
+
# (Copyright 2026 The HuggingFace Team, Apache-2.0). Model weights: Google DeepMind (Gemma Team), Apache-2.0,
|
| 6 |
+
# https://huggingface.co/google/embeddinggemma-2 — see ../NOTICE for the full attribution and modification list.
|
| 7 |
+
"""EmbeddingGemma 2 text encoder re-authored for Core AI export (standard [B, S, D] layout, batch 1).
|
| 8 |
+
|
| 9 |
+
Everything the sentence-transformers pipeline does is inside one graph:
|
| 10 |
+
input_ids [1, S] int32 + attention_mask [1, S] (1 = token, 0 = right padding)
|
| 11 |
+
-> scaled token embedding (sqrt(512) folded into the fp16 table)
|
| 12 |
+
-> projection-only per-layer embeddings (PLE)
|
| 13 |
+
-> 24 bidirectional encoder layers (5 sliding-window-512 : 1 full, GQA 4 heads / 2 kv (sliding, hd 256) or 1 kv (full, hd 512))
|
| 14 |
+
-> final RMSNorm -> 512->768 projection -> masked mean pooling -> L2 normalization
|
| 15 |
+
-> embedding [1, 768]
|
| 16 |
+
|
| 17 |
+
Precision modes (PRECISION_MODE, set before construction):
|
| 18 |
+
"ane" every op fp16 (Neural Engine eligible). RMSNorm and the final L2 norm divide the row by its max-abs value
|
| 19 |
+
before squaring (the model's activations reach ~2000, whose square overflows fp16; this rescaling idea comes
|
| 20 |
+
from the README of the FluidInference Core ML port of this model and was re-derived here).
|
| 21 |
+
Softmax in fp16 (scores are bounded because q/k are RMS-normalized with scale 1.0).
|
| 22 |
+
"gpu" fp16 weights/matmuls, fp32 islands for every RMSNorm, the softmax, the pooling and the L2 norm
|
| 23 |
+
(the HF reference computes the norms and the softmax in fp32).
|
| 24 |
+
"fp32" the whole model in fp32 (correctness gate for the re-authoring itself; never exported).
|
| 25 |
+
"""
|
| 26 |
+
from __future__ import annotations
|
| 27 |
+
|
| 28 |
+
import json
|
| 29 |
+
import math
|
| 30 |
+
from pathlib import Path
|
| 31 |
+
|
| 32 |
+
import torch
|
| 33 |
+
import torch.nn as nn
|
| 34 |
+
import torch.nn.functional as F
|
| 35 |
+
|
| 36 |
+
PRECISION_MODE = "ane" # "ane" | "gpu" | "fp32"
|
| 37 |
+
GQA_MODE = "grouped5d" # "expand" (HF repeat_kv: expand+reshape) | "grouped5d" (no repeat, 5-D batched matmul) | "repeat_interleave"
|
| 38 |
+
# Global (full-attention, MQA: 1 kv head, head_dim 512) layers 5/11/17/23. "grouped5d" = the GQA_MODE path.
|
| 39 |
+
# "mqa4d" (the released bundle) = plain 4-D matmul with k/v broadcast over the heads. With grouped5d the Neural
|
| 40 |
+
# Engine compiler rejects the MQA matmul ("Unsupported mps.matmul op for this ANE architecture"), every global layer
|
| 41 |
+
# becomes a GPU region, and MPSGraph then places the short buckets entirely on the GPU; mqa4d keeps one GPU region
|
| 42 |
+
# (the token gather) and everything else on the ANE. Mathematically identical (fp32 max|diff| 3e-7).
|
| 43 |
+
GLOBAL_ATTN_MODE = __import__("os").environ.get("EG2_GLOBAL_ATTN", "mqa4d")
|
| 44 |
+
# repeat_interleave made the Neural Engine compiler's fused SDPA fail on the MQA layers (1 kv head, 4 q heads):
|
| 45 |
+
# "'mps_spi.sdpa' op failed: grouping for the value tensor does not match the one available on the key tensor".
|
| 46 |
+
MASK_VALUE = -10000.0 # finite (ANE softmax mishandles -inf); fp16-exact
|
| 47 |
+
NORM_FLOOR = 1e-3 # floor for the row max-abs in "ane" RMSNorm (sqrt(eps) = 1e-3 for eps 1e-6)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class Cfg:
|
| 51 |
+
def __init__(self, model_dir: str):
|
| 52 |
+
c = json.loads((Path(model_dir) / "config.json").read_text())["text_config"]
|
| 53 |
+
self.hidden = c["hidden_size"] # 512
|
| 54 |
+
self.inter = c["intermediate_size"] # 2048
|
| 55 |
+
self.n_layers = c["num_hidden_layers"] # 24
|
| 56 |
+
self.n_heads = c["num_attention_heads"] # 4
|
| 57 |
+
self.layer_types = c["layer_types"]
|
| 58 |
+
self.sliding_window = c["sliding_window"] # 512 (inclusive distance)
|
| 59 |
+
self.eps = c["rms_norm_eps"]
|
| 60 |
+
self.vocab = c["vocab_size"]
|
| 61 |
+
self.embed_dim = c["embedding_dim"] # 768
|
| 62 |
+
self.ple_dim = c["hidden_size_per_layer_input"] # 512
|
| 63 |
+
self.act = c["hidden_activation"]
|
| 64 |
+
assert self.act == "gelu_pytorch_tanh", self.act
|
| 65 |
+
self.rope_theta = {k: v["rope_theta"] for k, v in c["rope_parameters"].items()}
|
| 66 |
+
per = c.get("per_layer_config", {})
|
| 67 |
+
self.head_dim = [per.get(f"{i:02d}", {}).get("head_dim", c["head_dim"]) for i in range(self.n_layers)]
|
| 68 |
+
self.kv_heads = [per.get(f"{i:02d}", {}).get("num_key_value_heads", c["num_key_value_heads"])
|
| 69 |
+
for i in range(self.n_layers)]
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def _f16(x: float) -> torch.Tensor:
|
| 73 |
+
return torch.tensor(x, dtype=torch.float16)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def _recip(m: torch.Tensor) -> torch.Tensor:
|
| 77 |
+
"""1/m for m > 0 without a divide: the Neural Engine compiler rejects `mps.divide` (falls back to the GPU);
|
| 78 |
+
rsqrt is native. rsqrt(m)*rsqrt(m) stays in fp16 range for m in [6e-5, 65504]."""
|
| 79 |
+
r = torch.rsqrt(m)
|
| 80 |
+
return r * r
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
class RMSNorm(nn.Module):
|
| 84 |
+
"""Gemma RMSNorm: x * rsqrt(mean(x^2) + eps) * w (w stored as the raw weight, no +1)."""
|
| 85 |
+
|
| 86 |
+
def __init__(self, dim: int, eps: float, with_scale: bool = True):
|
| 87 |
+
super().__init__()
|
| 88 |
+
self.with_scale = with_scale
|
| 89 |
+
if with_scale:
|
| 90 |
+
self.weight = nn.Parameter(torch.ones(dim), requires_grad=False)
|
| 91 |
+
self.eps = eps
|
| 92 |
+
self.register_buffer("eps16", _f16(eps), persistent=False)
|
| 93 |
+
self.register_buffer("sqrt_eps16", _f16(math.sqrt(eps)), persistent=False)
|
| 94 |
+
self.register_buffer("floor16", _f16(NORM_FLOOR), persistent=False)
|
| 95 |
+
|
| 96 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 97 |
+
if PRECISION_MODE == "ane":
|
| 98 |
+
inv_m = _recip(torch.maximum(x.abs().amax(-1, keepdim=True), self.floor16))
|
| 99 |
+
u = x * inv_m
|
| 100 |
+
r = self.sqrt_eps16 * inv_m
|
| 101 |
+
inv = torch.rsqrt((u * u).mean(-1, keepdim=True) + r * r)
|
| 102 |
+
y = u * inv
|
| 103 |
+
return y * self.weight if self.with_scale else y
|
| 104 |
+
xf = x.float()
|
| 105 |
+
y = xf * torch.rsqrt(xf.pow(2).mean(-1, keepdim=True) + self.eps)
|
| 106 |
+
if self.with_scale:
|
| 107 |
+
y = y * self.weight.float()
|
| 108 |
+
return y.to(x.dtype)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def rotate_half(x: torch.Tensor) -> torch.Tensor:
|
| 112 |
+
h = x.shape[-1] // 2
|
| 113 |
+
return torch.cat((-x[..., h:], x[..., :h]), dim=-1)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
class Attention(nn.Module):
|
| 117 |
+
def __init__(self, cfg: Cfg, i: int):
|
| 118 |
+
super().__init__()
|
| 119 |
+
self.hd = cfg.head_dim[i]
|
| 120 |
+
self.n_heads = cfg.n_heads
|
| 121 |
+
self.n_kv = cfg.kv_heads[i]
|
| 122 |
+
self.groups = self.n_heads // self.n_kv
|
| 123 |
+
self.q_proj = nn.Linear(cfg.hidden, cfg.n_heads * self.hd, bias=False)
|
| 124 |
+
self.k_proj = nn.Linear(cfg.hidden, self.n_kv * self.hd, bias=False)
|
| 125 |
+
self.v_proj = nn.Linear(cfg.hidden, self.n_kv * self.hd, bias=False)
|
| 126 |
+
self.o_proj = nn.Linear(cfg.n_heads * self.hd, cfg.hidden, bias=False)
|
| 127 |
+
self.q_norm = RMSNorm(self.hd, cfg.eps)
|
| 128 |
+
self.k_norm = RMSNorm(self.hd, cfg.eps)
|
| 129 |
+
self.v_norm = RMSNorm(self.hd, cfg.eps, with_scale=False)
|
| 130 |
+
|
| 131 |
+
def forward(self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor, bias: torch.Tensor) -> torch.Tensor:
|
| 132 |
+
B, S, _ = x.shape
|
| 133 |
+
q = self.q_proj(x).view(B, S, self.n_heads, self.hd).transpose(1, 2)
|
| 134 |
+
k = self.k_proj(x).view(B, S, self.n_kv, self.hd).transpose(1, 2)
|
| 135 |
+
v = self.v_proj(x).view(B, S, self.n_kv, self.hd).transpose(1, 2)
|
| 136 |
+
q, k, v = self.q_norm(q), self.k_norm(k), self.v_norm(v)
|
| 137 |
+
q = q * cos + rotate_half(q) * sin
|
| 138 |
+
k = k * cos + rotate_half(k) * sin
|
| 139 |
+
if self.n_kv == 1 and GLOBAL_ATTN_MODE == "mqa4d":
|
| 140 |
+
# MQA (global layers, 1 kv head): plain 4-D matmul, k/v broadcast over the 4 query heads, no repeat
|
| 141 |
+
scores = torch.matmul(q, k.transpose(-1, -2)) + bias
|
| 142 |
+
p = torch.softmax(scores, dim=-1) if PRECISION_MODE == "ane" else torch.softmax(scores.float(), dim=-1).to(q.dtype)
|
| 143 |
+
o = torch.matmul(p, v)
|
| 144 |
+
elif GQA_MODE == "grouped5d" and self.groups > 1:
|
| 145 |
+
qg = q.view(B, self.n_kv, self.groups, S, self.hd)
|
| 146 |
+
scores = torch.matmul(qg, k.transpose(-1, -2)[:, :, None]) + bias[:, None] # [B, n_kv, groups, S, S]
|
| 147 |
+
p = torch.softmax(scores, dim=-1) if PRECISION_MODE == "ane" else torch.softmax(scores.float(), dim=-1).to(q.dtype)
|
| 148 |
+
o = torch.matmul(p, v[:, :, None]).view(B, self.n_heads, S, self.hd)
|
| 149 |
+
else:
|
| 150 |
+
if self.groups > 1:
|
| 151 |
+
if GQA_MODE == "repeat_interleave":
|
| 152 |
+
k = k.repeat_interleave(self.groups, dim=1)
|
| 153 |
+
v = v.repeat_interleave(self.groups, dim=1)
|
| 154 |
+
else:
|
| 155 |
+
k = k[:, :, None].expand(B, self.n_kv, self.groups, S, self.hd).reshape(B, self.n_heads, S, self.hd)
|
| 156 |
+
v = v[:, :, None].expand(B, self.n_kv, self.groups, S, self.hd).reshape(B, self.n_heads, S, self.hd)
|
| 157 |
+
scores = torch.matmul(q, k.transpose(-1, -2)) + bias # scale 1.0 (the model's q/k norms carry it)
|
| 158 |
+
p = torch.softmax(scores, dim=-1) if PRECISION_MODE == "ane" else torch.softmax(scores.float(), dim=-1).to(q.dtype)
|
| 159 |
+
o = torch.matmul(p, v)
|
| 160 |
+
o = o.transpose(1, 2).reshape(B, S, self.n_heads * self.hd)
|
| 161 |
+
return self.o_proj(o)
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
class MLP(nn.Module):
|
| 165 |
+
def __init__(self, cfg: Cfg):
|
| 166 |
+
super().__init__()
|
| 167 |
+
self.gate_proj = nn.Linear(cfg.hidden, cfg.inter, bias=False)
|
| 168 |
+
self.up_proj = nn.Linear(cfg.hidden, cfg.inter, bias=False)
|
| 169 |
+
self.down_proj = nn.Linear(cfg.inter, cfg.hidden, bias=False)
|
| 170 |
+
|
| 171 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 172 |
+
return self.down_proj(F.gelu(self.gate_proj(x), approximate="tanh") * self.up_proj(x))
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
class PLEBlock(nn.Module):
|
| 176 |
+
def __init__(self, cfg: Cfg):
|
| 177 |
+
super().__init__()
|
| 178 |
+
self.per_layer_input_gate = nn.Linear(cfg.hidden, cfg.ple_dim, bias=False)
|
| 179 |
+
self.per_layer_projection = nn.Linear(cfg.ple_dim, cfg.hidden, bias=False)
|
| 180 |
+
self.post_per_layer_input_norm = RMSNorm(cfg.hidden, cfg.eps)
|
| 181 |
+
|
| 182 |
+
def forward(self, x: torch.Tensor, ple: torch.Tensor) -> torch.Tensor:
|
| 183 |
+
h = F.gelu(self.per_layer_input_gate(x), approximate="tanh") * ple
|
| 184 |
+
return x + self.post_per_layer_input_norm(self.per_layer_projection(h))
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
class Layer(nn.Module):
|
| 188 |
+
def __init__(self, cfg: Cfg, i: int):
|
| 189 |
+
super().__init__()
|
| 190 |
+
self.self_attn = Attention(cfg, i)
|
| 191 |
+
self.mlp = MLP(cfg)
|
| 192 |
+
self.input_layernorm = RMSNorm(cfg.hidden, cfg.eps)
|
| 193 |
+
self.post_attention_layernorm = RMSNorm(cfg.hidden, cfg.eps)
|
| 194 |
+
self.pre_feedforward_layernorm = RMSNorm(cfg.hidden, cfg.eps)
|
| 195 |
+
self.post_feedforward_layernorm = RMSNorm(cfg.hidden, cfg.eps)
|
| 196 |
+
self.ple_block = PLEBlock(cfg)
|
| 197 |
+
self.register_buffer("layer_scalar", torch.ones(1))
|
| 198 |
+
|
| 199 |
+
def forward(self, x, ple, cos, sin, bias):
|
| 200 |
+
x = x + self.post_attention_layernorm(self.self_attn(self.input_layernorm(x), cos, sin, bias))
|
| 201 |
+
x = x + self.post_feedforward_layernorm(self.mlp(self.pre_feedforward_layernorm(x)))
|
| 202 |
+
x = self.ple_block(x, ple)
|
| 203 |
+
return x * self.layer_scalar
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
class EmbeddingGemma2Text(nn.Module):
|
| 207 |
+
def __init__(self, cfg: Cfg, max_seq: int = 8192):
|
| 208 |
+
super().__init__()
|
| 209 |
+
self.cfg = cfg
|
| 210 |
+
self.max_seq = max_seq
|
| 211 |
+
self.embed_tokens = nn.Embedding(cfg.vocab, cfg.hidden) # sqrt(hidden) folded in at load
|
| 212 |
+
self.per_layer_model_projection = nn.Linear(cfg.hidden, cfg.n_layers * cfg.ple_dim, bias=False) # scale folded
|
| 213 |
+
self.per_layer_projection_norm = RMSNorm(cfg.ple_dim, cfg.eps)
|
| 214 |
+
self.layers = nn.ModuleList([Layer(cfg, i) for i in range(cfg.n_layers)])
|
| 215 |
+
self.norm = RMSNorm(cfg.hidden, cfg.eps)
|
| 216 |
+
self.embedding_projection = nn.Linear(cfg.hidden, cfg.embed_dim, bias=False)
|
| 217 |
+
# RoPE tables [max_seq, hd] per (layer type); head dims differ: sliding 256 @ theta 1e4, full 512 @ theta 1e6
|
| 218 |
+
for lt in ("sliding_attention", "full_attention"):
|
| 219 |
+
hd = cfg.head_dim[cfg.layer_types.index(lt)]
|
| 220 |
+
inv = 1.0 / (cfg.rope_theta[lt] ** (torch.arange(0, hd, 2, dtype=torch.float32) / hd))
|
| 221 |
+
fr = torch.arange(max_seq, dtype=torch.float32)[:, None] * inv[None]
|
| 222 |
+
emb = torch.cat((fr, fr), dim=-1)
|
| 223 |
+
self.register_buffer(f"cos_{lt}", emb.cos(), persistent=False)
|
| 224 |
+
self.register_buffer(f"sin_{lt}", emb.sin(), persistent=False)
|
| 225 |
+
self.register_buffer("mask_value", _f16(MASK_VALUE), persistent=False)
|
| 226 |
+
self.register_buffer("one", _f16(1.0), persistent=False)
|
| 227 |
+
self.register_buffer("zero", _f16(0.0), persistent=False)
|
| 228 |
+
self.register_buffer("floor16", _f16(NORM_FLOOR), persistent=False)
|
| 229 |
+
self.register_buffer("window", torch.tensor(cfg.sliding_window, dtype=torch.int32), persistent=False)
|
| 230 |
+
|
| 231 |
+
def _biases(self, attention_mask: torch.Tensor, S: int):
|
| 232 |
+
pad = (self.one - attention_mask) * self.mask_value # [1, S] 0 for tokens, -1e4 for padding keys
|
| 233 |
+
full = pad[:, None, None, :] # [1, 1, 1, S] broadcast over heads and queries
|
| 234 |
+
pos = torch.arange(S, device=attention_mask.device, dtype=torch.int32)
|
| 235 |
+
dist = (pos[:, None] - pos[None, :]).abs()
|
| 236 |
+
band = torch.where(dist <= self.window, self.zero, self.mask_value) # [S, S]
|
| 237 |
+
sliding = full + band[None, None]
|
| 238 |
+
return full, sliding
|
| 239 |
+
|
| 240 |
+
def forward(self, input_ids: torch.Tensor, attention_mask: torch.Tensor) -> torch.Tensor:
|
| 241 |
+
S = input_ids.shape[1]
|
| 242 |
+
x = self.embed_tokens(input_ids) # [1, S, 512], already scaled
|
| 243 |
+
ple = self.per_layer_projection_norm(
|
| 244 |
+
self.per_layer_model_projection(x).view(1, S, self.cfg.n_layers, self.cfg.ple_dim))
|
| 245 |
+
full, sliding = self._biases(attention_mask, S)
|
| 246 |
+
cs = {}
|
| 247 |
+
for lt in ("sliding_attention", "full_attention"):
|
| 248 |
+
cs[lt] = (getattr(self, f"cos_{lt}")[:S].to(x.dtype)[None, None], getattr(self, f"sin_{lt}")[:S].to(x.dtype)[None, None])
|
| 249 |
+
for i, layer in enumerate(self.layers):
|
| 250 |
+
lt = self.cfg.layer_types[i]
|
| 251 |
+
x = layer(x, ple[:, :, i, :], cs[lt][0], cs[lt][1], sliding if lt == "sliding_attention" else full)
|
| 252 |
+
x = self.embedding_projection(self.norm(x)) # [1, S, 768]
|
| 253 |
+
# masked mean pooling: weights sum to 1, so no large partial sums in fp16
|
| 254 |
+
if PRECISION_MODE == "ane":
|
| 255 |
+
w = attention_mask * _recip(attention_mask.sum(-1, keepdim=True)) # [1, S] (count exact in fp16 up to 2048)
|
| 256 |
+
pooled = torch.matmul(w[:, None, :], x)[:, 0, :] # [1, 768]
|
| 257 |
+
u = pooled * _recip(torch.maximum(pooled.abs().amax(-1, keepdim=True), self.floor16))
|
| 258 |
+
return u * torch.rsqrt((u * u).sum(-1, keepdim=True))
|
| 259 |
+
xf = x.float()
|
| 260 |
+
wf = attention_mask.float()
|
| 261 |
+
pooled = (xf * wf[:, :, None]).sum(1) / wf.sum(-1, keepdim=True)
|
| 262 |
+
return (pooled / pooled.norm(dim=-1, keepdim=True)).to(x.dtype)
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
def load_model(model_dir: str, dtype: torch.dtype, max_seq: int = 8192) -> EmbeddingGemma2Text:
|
| 266 |
+
from safetensors import safe_open
|
| 267 |
+
|
| 268 |
+
cfg = Cfg(model_dir)
|
| 269 |
+
m = EmbeddingGemma2Text(cfg, max_seq)
|
| 270 |
+
sd = {}
|
| 271 |
+
with safe_open(str(Path(model_dir) / "model.safetensors"), "pt") as f:
|
| 272 |
+
for k in f.keys():
|
| 273 |
+
if not k.startswith("language_model."):
|
| 274 |
+
continue
|
| 275 |
+
t = f.get_tensor(k).float()
|
| 276 |
+
k = k[len("language_model."):]
|
| 277 |
+
if k == "embed_tokens.weight":
|
| 278 |
+
t = t * math.sqrt(cfg.hidden)
|
| 279 |
+
elif k == "ple.per_layer_model_projection.weight":
|
| 280 |
+
k = "per_layer_model_projection.weight"
|
| 281 |
+
t = t * cfg.hidden ** -0.5
|
| 282 |
+
elif k == "ple.per_layer_projection_norm.weight":
|
| 283 |
+
k = "per_layer_projection_norm.weight"
|
| 284 |
+
sd[k] = t.to(dtype)
|
| 285 |
+
missing, unexpected = m.load_state_dict(sd, strict=False)
|
| 286 |
+
missing = [k for k in missing if not any(s in k for s in ("cos_", "sin_", "eps16", "sqrt_eps16", "floor16", "mask_value", "one", "zero", "window"))]
|
| 287 |
+
assert not missing and not unexpected, (missing, unexpected)
|
| 288 |
+
return m.to(dtype).eval()
|
export/export.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 Everpaper. Licensed under the Apache License, Version 2.0 (see ../LICENSE).
|
| 2 |
+
"""Export the EmbeddingGemma 2 text encoder to a Core AI `.aimodel` with one static entrypoint per sequence bucket.
|
| 3 |
+
|
| 4 |
+
# 1. download the original checkpoint: hf download google/embeddinggemma-2 --local-dir ./embeddinggemma-2
|
| 5 |
+
# 2. python3 -W ignore export.py --model-dir ./embeddinggemma-2 --out <bundle folder> --name embeddinggemma-2-text \
|
| 6 |
+
# --mode ane --buckets 32,64,128,256,512,1024,2048 (the released bundle; ~20 s on an M-series Mac)
|
| 7 |
+
|
| 8 |
+
The result is functionally identical to the released bundle (same outputs), not byte-identical: the converter gives
|
| 9 |
+
internal graph resources random names, so main.mlirb / main.hash differ, and the runtime writes metadata.json with the
|
| 10 |
+
export time and its own key order. config.json, MODEL-ATTRIBUTION.json (with --date 2026-10-12) and the tokenizer
|
| 11 |
+
files come out byte-identical to the released ones.
|
| 12 |
+
|
| 13 |
+
One dynamic-S torch.export -> TorchConverter -> AIProgram.set_static_shape_config("main", {...}) which replaces
|
| 14 |
+
`main` by `main_<S>` clones (static shapes are what keeps the graph on the Neural Engine).
|
| 15 |
+
|
| 16 |
+
Bundle layout:
|
| 17 |
+
<out>/<name>.aimodel functions main_<S>: inputs input_ids [1,S] int32, attention_mask [1,S] fp16 -> embedding [1,768] fp16
|
| 18 |
+
<out>/tokenizer/ tokenizer.json + tokenizer_config.json (unchanged Gemma tokenizer)
|
| 19 |
+
<out>/config.json buckets, prompts, special tokens, matryoshka dims, precision mode
|
| 20 |
+
<out>/MODEL-ATTRIBUTION.json base model, checkpoint sha256, exporter versions, modification list
|
| 21 |
+
|
| 22 |
+
Requirements (see ../python/requirements.lock): Python 3.12, torch 2.9.0, coreai-torch 0.4.3, coreai-opt 0.3.0,
|
| 23 |
+
torchao 0.17.0, coreai-core 1.0.0b3, safetensors.
|
| 24 |
+
"""
|
| 25 |
+
from __future__ import annotations
|
| 26 |
+
|
| 27 |
+
import argparse
|
| 28 |
+
import hashlib
|
| 29 |
+
import json
|
| 30 |
+
import os
|
| 31 |
+
import shutil
|
| 32 |
+
import subprocess
|
| 33 |
+
import sys
|
| 34 |
+
import time
|
| 35 |
+
from pathlib import Path
|
| 36 |
+
|
| 37 |
+
import torch
|
| 38 |
+
|
| 39 |
+
LAB = Path(__file__).resolve().parent
|
| 40 |
+
sys.path.insert(0, str(LAB))
|
| 41 |
+
import eg2_text # noqa: E402
|
| 42 |
+
|
| 43 |
+
T0 = time.time()
|
| 44 |
+
PROMPTS = {
|
| 45 |
+
"document": "title: none | text: ",
|
| 46 |
+
"search_query": "task: search result | query: ",
|
| 47 |
+
"question_answering": "task: question answering | query: ",
|
| 48 |
+
"fact_checking": "task: fact checking | query: ",
|
| 49 |
+
"code_retrieval": "task: code retrieval | query: ",
|
| 50 |
+
"classification": "task: classification | query: ",
|
| 51 |
+
"clustering": "task: clustering | query: ",
|
| 52 |
+
"sentence_similarity": "task: sentence similarity | query: ",
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def log(msg: str) -> None:
|
| 57 |
+
print(f"[{time.time() - T0:7.1f}s] {msg}", flush=True)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def sha256_file(p: Path) -> str:
|
| 61 |
+
h = hashlib.sha256()
|
| 62 |
+
with open(p, "rb") as f:
|
| 63 |
+
for chunk in iter(lambda: f.read(1 << 24), b""):
|
| 64 |
+
h.update(chunk)
|
| 65 |
+
return h.hexdigest()
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def dir_bytes(p: Path) -> int:
|
| 69 |
+
return sum(f.stat().st_size for f in p.rglob("*") if f.is_file()) if p.is_dir() else p.stat().st_size
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def main() -> None:
|
| 73 |
+
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
| 74 |
+
ap.add_argument("--model-dir", required=True, help="folder with the original google/embeddinggemma-2 checkpoint "
|
| 75 |
+
"(config.json, model.safetensors, tokenizer.json, tokenizer_config.json)")
|
| 76 |
+
ap.add_argument("--hf-id", default="google/embeddinggemma-2")
|
| 77 |
+
ap.add_argument("--out", required=True)
|
| 78 |
+
ap.add_argument("--name", default=None)
|
| 79 |
+
ap.add_argument("--mode", default="ane", choices=["ane", "gpu"])
|
| 80 |
+
ap.add_argument("--buckets", default="32,64,128,256,512,1024,2048")
|
| 81 |
+
ap.add_argument("--num-layers", type=int, default=None, help="smoke export with the first N layers")
|
| 82 |
+
ap.add_argument("--debug-info", action="store_true")
|
| 83 |
+
ap.add_argument("--no-static", action="store_true", help="keep the single dynamic-S `main` (GPU only)")
|
| 84 |
+
ap.add_argument("--date", default=None, help="port date written to MODEL-ATTRIBUTION.json (default: today)")
|
| 85 |
+
args = ap.parse_args()
|
| 86 |
+
buckets = sorted(int(b) for b in args.buckets.split(","))
|
| 87 |
+
max_seq = max(buckets)
|
| 88 |
+
out = Path(args.out)
|
| 89 |
+
name = args.name or out.name
|
| 90 |
+
eg2_text.PRECISION_MODE = args.mode
|
| 91 |
+
log(f"args {vars(args)}")
|
| 92 |
+
|
| 93 |
+
model = eg2_text.load_model(args.model_dir, torch.float16, max_seq=max_seq)
|
| 94 |
+
if args.num_layers:
|
| 95 |
+
model.layers = model.layers[: args.num_layers]
|
| 96 |
+
model.cfg.layer_types = model.cfg.layer_types[: args.num_layers]
|
| 97 |
+
log(f"model loaded: {sum(p.numel() for p in model.parameters()) / 1e6:.1f} M params, mode={args.mode}")
|
| 98 |
+
|
| 99 |
+
import coreai_torch
|
| 100 |
+
from torch.export import Dim
|
| 101 |
+
|
| 102 |
+
S0 = buckets[min(1, len(buckets) - 1)]
|
| 103 |
+
ref = {"input_ids": torch.zeros(1, S0, dtype=torch.int32),
|
| 104 |
+
"attention_mask": torch.ones(1, S0, dtype=torch.float16)}
|
| 105 |
+
ref["input_ids"][0, :8] = torch.tensor([2, 8071, 236787, 3927, 1354, 1109, 7609, 1], dtype=torch.int32)
|
| 106 |
+
ref["attention_mask"][0, 8:] = 0
|
| 107 |
+
S = Dim("S", min=min(8, buckets[0]), max=max_seq)
|
| 108 |
+
dyn = {"input_ids": {1: S}, "attention_mask": {1: S}}
|
| 109 |
+
with torch.no_grad():
|
| 110 |
+
ep = torch.export.export(model, args=(), kwargs=ref, dynamic_shapes=dyn)
|
| 111 |
+
log("torch.export done")
|
| 112 |
+
ep = ep.run_decompositions(coreai_torch.get_decomp_table())
|
| 113 |
+
log("decomposed")
|
| 114 |
+
mode = coreai_torch.TorchConverter.Mode.DEBUG if args.debug_info else coreai_torch.TorchConverter.Mode.RELEASE
|
| 115 |
+
conv = coreai_torch.TorchConverter(mode=mode)
|
| 116 |
+
conv.add_exported_program(ep, input_names=["input_ids", "attention_mask"], output_names=["embedding"],
|
| 117 |
+
entrypoint_name="main")
|
| 118 |
+
prog = conv.to_coreai()
|
| 119 |
+
log("converted to AIProgram")
|
| 120 |
+
functions = ["main"]
|
| 121 |
+
if not args.no_static:
|
| 122 |
+
cfg = {f'"{b}"': {"input_ids": (1, b), "attention_mask": (1, b)} for b in buckets}
|
| 123 |
+
prog.set_static_shape_config("main", cfg)
|
| 124 |
+
functions = [f"main_{b}" for b in buckets]
|
| 125 |
+
log(f"static shape specializations: {functions}")
|
| 126 |
+
|
| 127 |
+
from coreai.runtime import AIModelAssetMetadata
|
| 128 |
+
|
| 129 |
+
out.mkdir(parents=True, exist_ok=True)
|
| 130 |
+
aimodel = out / f"{name}.aimodel"
|
| 131 |
+
shutil.rmtree(aimodel, ignore_errors=True)
|
| 132 |
+
meta = AIModelAssetMetadata()
|
| 133 |
+
meta.author = "Gemma Team (Google); Core AI port"
|
| 134 |
+
meta.license = "Apache-2.0"
|
| 135 |
+
meta.model_description = (f"EmbeddingGemma 2 text encoder ({args.hf_id}) with mean pooling, 768-d projection and L2 "
|
| 136 |
+
f"normalization in-graph; precision mode {args.mode}; sequence buckets {buckets}.")
|
| 137 |
+
meta.creation_date = int(time.time())
|
| 138 |
+
prog.save_asset(aimodel, meta)
|
| 139 |
+
log(f"saved {aimodel} ({dir_bytes(aimodel) / 2**20:.1f} MiB)")
|
| 140 |
+
|
| 141 |
+
# ---- bundle ---------------------------------------------------------------------
|
| 142 |
+
tok_dir = out / "tokenizer"
|
| 143 |
+
tok_dir.mkdir(exist_ok=True)
|
| 144 |
+
for f in ("tokenizer.json", "tokenizer_config.json"):
|
| 145 |
+
shutil.copy(Path(args.model_dir) / f, tok_dir / f)
|
| 146 |
+
cfg_json = {
|
| 147 |
+
"base_model": args.hf_id, "format": "coreai", "asset": aimodel.name, "functions": functions,
|
| 148 |
+
"precision_mode": args.mode, "buckets": buckets, "max_tokens": max_seq,
|
| 149 |
+
"inputs": {"input_ids": "[1, S] int32 (bos=2 ... eos=1, right-padded with pad=0)",
|
| 150 |
+
"attention_mask": "[1, S] float16 (1 = token, 0 = padding)"},
|
| 151 |
+
"output": {"embedding": "[1, 768] float16, L2-normalized; Matryoshka: keep the first 512/256/128 and re-normalize"},
|
| 152 |
+
"hidden_size": 512, "embedding_dim": 768, "matryoshka_dims": [768, 512, 256, 128],
|
| 153 |
+
"bos_token_id": 2, "eos_token_id": 1, "pad_token_id": 0, "prompts": PROMPTS,
|
| 154 |
+
"tokenizer": "tokenizer/tokenizer.json (unchanged Gemma tokenizer; the runner adds <bos> and <eos>)",
|
| 155 |
+
}
|
| 156 |
+
(out / "config.json").write_text(json.dumps(cfg_json, indent=2) + "\n")
|
| 157 |
+
import coreai_torch as ct
|
| 158 |
+
from importlib.metadata import version as pkg_version
|
| 159 |
+
|
| 160 |
+
rescale = ("fp16 everywhere (Neural Engine): every RMSNorm and the final L2 normalization divide the row by its "
|
| 161 |
+
"max-abs value before squaring, so activations (~2000) never overflow fp16; in the model code every "
|
| 162 |
+
"division is expressed as rsqrt(x)*rsqrt(x) (the Neural Engine compiler rejects divide)."
|
| 163 |
+
if args.mode == "ane" else "fp16 weights and matmuls; RMSNorm, softmax, pooling and L2 normalization in fp32.")
|
| 164 |
+
attribution = {
|
| 165 |
+
"name": "EmbeddingGemma-2-CoreAI-text",
|
| 166 |
+
"version": "1.0",
|
| 167 |
+
"base_model": args.hf_id,
|
| 168 |
+
"base_model_author": "Gemma Team, Google DeepMind",
|
| 169 |
+
"base_model_source": f"https://huggingface.co/{args.hf_id}",
|
| 170 |
+
"base_model_license": "Apache-2.0",
|
| 171 |
+
"base_model_terms": "Deployments must adhere to the Gemma Prohibited Use Policy: https://ai.google.dev/gemma/prohibited_use_policy",
|
| 172 |
+
"base_model_file_sha256": {"model.safetensors": sha256_file(Path(args.model_dir) / "model.safetensors")},
|
| 173 |
+
"port": {
|
| 174 |
+
"copyright": "2026 Everpaper",
|
| 175 |
+
"license": "Apache-2.0 (see LICENSE); attribution and modification notice in NOTICE",
|
| 176 |
+
"date": args.date or time.strftime("%Y-%m-%d"),
|
| 177 |
+
"format": "Apple Core AI .aimodel (macOS 27), fp16, static sequence buckets",
|
| 178 |
+
"exporter": {
|
| 179 |
+
"coreai_torch": ct.__version__,
|
| 180 |
+
"coreai_core": pkg_version("coreai-core"),
|
| 181 |
+
"torch": torch.__version__.split("+")[0],
|
| 182 |
+
"recipe": f"export/export.py --mode {args.mode} --buckets {','.join(map(str, buckets))}",
|
| 183 |
+
"global_attention": eg2_text.GLOBAL_ATTN_MODE,
|
| 184 |
+
"gqa": eg2_text.GQA_MODE,
|
| 185 |
+
},
|
| 186 |
+
},
|
| 187 |
+
"modifications": [
|
| 188 |
+
"Text encoder only: the vision and audio encoders of the multimodal checkpoint are not included.",
|
| 189 |
+
"Architecture re-authored in plain PyTorch for Core AI export; weights are the original ones, rounded from bf16 to fp16.",
|
| 190 |
+
"The sqrt(512) token-embedding scale and the 512^-0.5 per-layer-embedding scale are folded into the tables.",
|
| 191 |
+
"The four global (full-attention, single-kv-head) layers compute attention as a plain 4-D matmul with k/v "
|
| 192 |
+
"broadcast over the heads; the sliding-window layers use a 5-D grouped matmul. Mathematically identical to "
|
| 193 |
+
"the reference (fp32 max abs diff 3e-7).",
|
| 194 |
+
rescale,
|
| 195 |
+
"Bidirectional attention masks (padding + sliding window 512) are built inside the graph from attention_mask; "
|
| 196 |
+
"the additive mask value is -10000 (finite).",
|
| 197 |
+
"Masked mean pooling, the 512->768 embedding projection and the L2 normalization run inside the graph; the "
|
| 198 |
+
"output is the final sentence embedding.",
|
| 199 |
+
f"Fixed sequence buckets {', '.join(map(str, buckets))} as functions main_<S>; input longer than {max_seq} "
|
| 200 |
+
"tokens must be truncated or chunked by the caller (the original model accepts 8192).",
|
| 201 |
+
],
|
| 202 |
+
}
|
| 203 |
+
if args.num_layers:
|
| 204 |
+
attribution["port"]["exporter"]["num_layers"] = args.num_layers
|
| 205 |
+
(out / "MODEL-ATTRIBUTION.json").write_text(json.dumps(attribution, indent=2) + "\n")
|
| 206 |
+
log(f"bundle written: {out} ({dir_bytes(out) / 2**20:.1f} MiB)")
|
| 207 |
+
try:
|
| 208 |
+
print(subprocess.run(["xcrun", "coreai-build", "inspect", str(aimodel), "--no-metadata"], capture_output=True, text=True, timeout=120).stdout[:3000])
|
| 209 |
+
except Exception as e: # noqa: BLE001
|
| 210 |
+
log(f"inspect failed: {e}")
|
| 211 |
+
log("DONE")
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
if __name__ == "__main__":
|
| 215 |
+
main()
|
model/MODEL-ATTRIBUTION.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "EmbeddingGemma-2-CoreAI-text",
|
| 3 |
+
"version": "1.0",
|
| 4 |
+
"base_model": "google/embeddinggemma-2",
|
| 5 |
+
"base_model_author": "Gemma Team, Google DeepMind",
|
| 6 |
+
"base_model_source": "https://huggingface.co/google/embeddinggemma-2",
|
| 7 |
+
"base_model_license": "Apache-2.0",
|
| 8 |
+
"base_model_terms": "Deployments must adhere to the Gemma Prohibited Use Policy: https://ai.google.dev/gemma/prohibited_use_policy",
|
| 9 |
+
"base_model_file_sha256": {
|
| 10 |
+
"model.safetensors": "197a32965d4b1105faf060417baa899e193fb73cd401f42ec9295234d5553d79"
|
| 11 |
+
},
|
| 12 |
+
"port": {
|
| 13 |
+
"copyright": "2026 Everpaper",
|
| 14 |
+
"license": "Apache-2.0 (see LICENSE); attribution and modification notice in NOTICE",
|
| 15 |
+
"date": "2026-10-12",
|
| 16 |
+
"format": "Apple Core AI .aimodel (macOS 27), fp16, static sequence buckets",
|
| 17 |
+
"exporter": {
|
| 18 |
+
"coreai_torch": "0.4.3",
|
| 19 |
+
"coreai_core": "1.0.0b3",
|
| 20 |
+
"torch": "2.9.0",
|
| 21 |
+
"recipe": "export/export.py --mode ane --buckets 32,64,128,256,512,1024,2048",
|
| 22 |
+
"global_attention": "mqa4d",
|
| 23 |
+
"gqa": "grouped5d"
|
| 24 |
+
}
|
| 25 |
+
},
|
| 26 |
+
"modifications": [
|
| 27 |
+
"Text encoder only: the vision and audio encoders of the multimodal checkpoint are not included.",
|
| 28 |
+
"Architecture re-authored in plain PyTorch for Core AI export; weights are the original ones, rounded from bf16 to fp16.",
|
| 29 |
+
"The sqrt(512) token-embedding scale and the 512^-0.5 per-layer-embedding scale are folded into the tables.",
|
| 30 |
+
"The four global (full-attention, single-kv-head) layers compute attention as a plain 4-D matmul with k/v broadcast over the heads; the sliding-window layers use a 5-D grouped matmul. Mathematically identical to the reference (fp32 max abs diff 3e-7).",
|
| 31 |
+
"fp16 everywhere (Neural Engine): every RMSNorm and the final L2 normalization divide the row by its max-abs value before squaring, so activations (~2000) never overflow fp16; in the model code every division is expressed as rsqrt(x)*rsqrt(x) (the Neural Engine compiler rejects divide).",
|
| 32 |
+
"Bidirectional attention masks (padding + sliding window 512) are built inside the graph from attention_mask; the additive mask value is -10000 (finite).",
|
| 33 |
+
"Masked mean pooling, the 512->768 embedding projection and the L2 normalization run inside the graph; the output is the final sentence embedding.",
|
| 34 |
+
"Fixed sequence buckets 32, 64, 128, 256, 512, 1024, 2048 as functions main_<S>; input longer than 2048 tokens must be truncated or chunked by the caller (the original model accepts 8192)."
|
| 35 |
+
]
|
| 36 |
+
}
|
model/config.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"base_model": "google/embeddinggemma-2",
|
| 3 |
+
"format": "coreai",
|
| 4 |
+
"asset": "embeddinggemma-2-text.aimodel",
|
| 5 |
+
"functions": [
|
| 6 |
+
"main_32",
|
| 7 |
+
"main_64",
|
| 8 |
+
"main_128",
|
| 9 |
+
"main_256",
|
| 10 |
+
"main_512",
|
| 11 |
+
"main_1024",
|
| 12 |
+
"main_2048"
|
| 13 |
+
],
|
| 14 |
+
"precision_mode": "ane",
|
| 15 |
+
"buckets": [
|
| 16 |
+
32,
|
| 17 |
+
64,
|
| 18 |
+
128,
|
| 19 |
+
256,
|
| 20 |
+
512,
|
| 21 |
+
1024,
|
| 22 |
+
2048
|
| 23 |
+
],
|
| 24 |
+
"max_tokens": 2048,
|
| 25 |
+
"inputs": {
|
| 26 |
+
"input_ids": "[1, S] int32 (bos=2 ... eos=1, right-padded with pad=0)",
|
| 27 |
+
"attention_mask": "[1, S] float16 (1 = token, 0 = padding)"
|
| 28 |
+
},
|
| 29 |
+
"output": {
|
| 30 |
+
"embedding": "[1, 768] float16, L2-normalized; Matryoshka: keep the first 512/256/128 and re-normalize"
|
| 31 |
+
},
|
| 32 |
+
"hidden_size": 512,
|
| 33 |
+
"embedding_dim": 768,
|
| 34 |
+
"matryoshka_dims": [
|
| 35 |
+
768,
|
| 36 |
+
512,
|
| 37 |
+
256,
|
| 38 |
+
128
|
| 39 |
+
],
|
| 40 |
+
"bos_token_id": 2,
|
| 41 |
+
"eos_token_id": 1,
|
| 42 |
+
"pad_token_id": 0,
|
| 43 |
+
"prompts": {
|
| 44 |
+
"document": "title: none | text: ",
|
| 45 |
+
"search_query": "task: search result | query: ",
|
| 46 |
+
"question_answering": "task: question answering | query: ",
|
| 47 |
+
"fact_checking": "task: fact checking | query: ",
|
| 48 |
+
"code_retrieval": "task: code retrieval | query: ",
|
| 49 |
+
"classification": "task: classification | query: ",
|
| 50 |
+
"clustering": "task: clustering | query: ",
|
| 51 |
+
"sentence_similarity": "task: sentence similarity | query: "
|
| 52 |
+
},
|
| 53 |
+
"tokenizer": "tokenizer/tokenizer.json (unchanged Gemma tokenizer; the runner adds <bos> and <eos>)"
|
| 54 |
+
}
|
model/embeddinggemma-2-text.aimodel/main.hash
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
uӄ1��$q�T�3Wq
|
model/embeddinggemma-2-text.aimodel/main.mlirb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:75d38431abcd2471b0540c863357710d305cb1687295f5b0cf859de6ac9d46eb
|
| 3 |
+
size 631529960
|
model/embeddinggemma-2-text.aimodel/metadata.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"assetVersion" : "2.0",
|
| 3 |
+
"producer" : "coreai-core 1.0.0b3",
|
| 4 |
+
"license" : "Apache-2.0",
|
| 5 |
+
"author" : "Gemma Team (Google); Core AI port",
|
| 6 |
+
"creationDate" : "20261012T021653Z",
|
| 7 |
+
"description" : "EmbeddingGemma 2 text encoder (google\/embeddinggemma-2) with mean pooling, 768-d projection and L2 normalization in-graph; precision mode ane; sequence buckets [32, 64, 128, 256, 512, 1024, 2048]."
|
| 8 |
+
}
|
model/tokenizer/tokenizer.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4d777ef5bdc1aa36227abdfb77c3e49e7b9c892d16e1b6bda41c393504828be4
|
| 3 |
+
size 32170510
|
model/tokenizer/tokenizer_config.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"audio_token": "<|audio|>",
|
| 3 |
+
"backend": "tokenizers",
|
| 4 |
+
"boa_token": "<|audio>",
|
| 5 |
+
"boi_token": "<|image>",
|
| 6 |
+
"bos_token": "<bos>",
|
| 7 |
+
"eoa_token": "<audio|>",
|
| 8 |
+
"eoc_token": "<channel|>",
|
| 9 |
+
"eoi_token": "<image|>",
|
| 10 |
+
"eos_token": "<eos>",
|
| 11 |
+
"eot_token": "<turn|>",
|
| 12 |
+
"escape_token": "<|\"|>",
|
| 13 |
+
"etc_token": "<tool_call|>",
|
| 14 |
+
"etd_token": "<tool|>",
|
| 15 |
+
"etr_token": "<tool_response|>",
|
| 16 |
+
"extra_special_tokens": [
|
| 17 |
+
"<|video|>"
|
| 18 |
+
],
|
| 19 |
+
"image_token": "<|image|>",
|
| 20 |
+
"is_local": true,
|
| 21 |
+
"local_files_only": false,
|
| 22 |
+
"mask_token": "<mask>",
|
| 23 |
+
"model_max_length": 1000000000000000019884624838656,
|
| 24 |
+
"model_specific_special_tokens": {
|
| 25 |
+
"audio_token": "<|audio|>",
|
| 26 |
+
"boa_token": "<|audio>",
|
| 27 |
+
"boi_token": "<|image>",
|
| 28 |
+
"eoa_token": "<audio|>",
|
| 29 |
+
"eoc_token": "<channel|>",
|
| 30 |
+
"eoi_token": "<image|>",
|
| 31 |
+
"eot_token": "<turn|>",
|
| 32 |
+
"escape_token": "<|\"|>",
|
| 33 |
+
"etc_token": "<tool_call|>",
|
| 34 |
+
"etd_token": "<tool|>",
|
| 35 |
+
"etr_token": "<tool_response|>",
|
| 36 |
+
"image_token": "<|image|>",
|
| 37 |
+
"soc_token": "<|channel>",
|
| 38 |
+
"sot_token": "<|turn>",
|
| 39 |
+
"stc_token": "<|tool_call>",
|
| 40 |
+
"std_token": "<|tool>",
|
| 41 |
+
"str_token": "<|tool_response>",
|
| 42 |
+
"think_token": "<|think|>",
|
| 43 |
+
"video_token": "<|video|>"
|
| 44 |
+
},
|
| 45 |
+
"pad_token": "<pad>",
|
| 46 |
+
"padding_side": "right",
|
| 47 |
+
"processor_class": "EmbeddingGemma2Processor",
|
| 48 |
+
"soc_token": "<|channel>",
|
| 49 |
+
"sot_token": "<|turn>",
|
| 50 |
+
"stc_token": "<|tool_call>",
|
| 51 |
+
"std_token": "<|tool>",
|
| 52 |
+
"str_token": "<|tool_response>",
|
| 53 |
+
"think_token": "<|think|>",
|
| 54 |
+
"tokenizer_class": "GemmaTokenizer",
|
| 55 |
+
"unk_token": "<unk>",
|
| 56 |
+
"video_token": "<|video|>"
|
| 57 |
+
}
|
python/embeddinggemma2_coreai.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# Copyright 2026 Everpaper. Licensed under the Apache License, Version 2.0 (see ../LICENSE).
|
| 3 |
+
"""EmbeddingGemma 2 (text) on Apple Core AI, macOS 27 — Python runner (library + CLI).
|
| 4 |
+
|
| 5 |
+
Library:
|
| 6 |
+
|
| 7 |
+
from embeddinggemma2_coreai import EmbeddingGemma2CoreAI, remove_own_mpsgraph_temp_files
|
| 8 |
+
m = EmbeddingGemma2CoreAI("../model", compute="ane") # ane | gpu | cpu | default
|
| 9 |
+
m.prewarm() # optional: one call per bucket
|
| 10 |
+
q = m.embed("When do the cloud credits expire?", kind="search_query") # 768-d, L2-normalized
|
| 11 |
+
d = m.embed("The credits expire on the last day of March.", kind="document", dim=256)
|
| 12 |
+
del m
|
| 13 |
+
remove_own_mpsgraph_temp_files() # see "Temp files" in the README
|
| 14 |
+
|
| 15 |
+
CLI:
|
| 16 |
+
|
| 17 |
+
python3 embeddinggemma2_coreai.py embed ../model [--compute ane] [--kind document] [--dim 768] "text" ...
|
| 18 |
+
python3 embeddinggemma2_coreai.py smoke ../model ../tests/smoke-reference.json [--compute ane] [--cleanup-temp]
|
| 19 |
+
python3 embeddinggemma2_coreai.py tokcheck ../model ../tests/tokenizer-cases.json
|
| 20 |
+
python3 embeddinggemma2_coreai.py latency ../model [--compute ane] [--trials 50]
|
| 21 |
+
python3 embeddinggemma2_coreai.py prewarm ../model [--compute ane]
|
| 22 |
+
|
| 23 |
+
Tokenization uses the Hugging Face `tokenizers` library on the bundle's tokenizer.json (the reference tokenizer).
|
| 24 |
+
"""
|
| 25 |
+
from __future__ import annotations
|
| 26 |
+
|
| 27 |
+
import argparse
|
| 28 |
+
import asyncio
|
| 29 |
+
import ctypes
|
| 30 |
+
import json
|
| 31 |
+
import os
|
| 32 |
+
import sys
|
| 33 |
+
import time
|
| 34 |
+
from pathlib import Path
|
| 35 |
+
|
| 36 |
+
import numpy as np
|
| 37 |
+
|
| 38 |
+
_PROCESS_START = time.time()
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class EmbeddingGemma2CoreAI:
|
| 42 |
+
"""Loads the bundle folder (config.json, tokenizer/, the .aimodel) and embeds texts."""
|
| 43 |
+
|
| 44 |
+
def __init__(self, bundle: str | os.PathLike, compute: str = "ane", quiet: bool = True):
|
| 45 |
+
import coreai.runtime as rt
|
| 46 |
+
from tokenizers import Tokenizer
|
| 47 |
+
|
| 48 |
+
self._rt = rt
|
| 49 |
+
self.bundle = Path(bundle)
|
| 50 |
+
self.config = json.loads((self.bundle / "config.json").read_text(encoding="utf-8"))
|
| 51 |
+
t = time.perf_counter()
|
| 52 |
+
self.tokenizer = Tokenizer.from_file(str(self.bundle / "tokenizer" / "tokenizer.json"))
|
| 53 |
+
self.tokenizer_load_s = time.perf_counter() - t
|
| 54 |
+
self.compute = compute
|
| 55 |
+
if compute == "cpu":
|
| 56 |
+
opts = rt.SpecializationOptions.cpu_only()
|
| 57 |
+
elif compute == "gpu":
|
| 58 |
+
opts = rt.SpecializationOptions.from_preferred_compute_unit_kind(rt.ComputeUnitKind.gpu())
|
| 59 |
+
elif compute == "ane":
|
| 60 |
+
opts = rt.SpecializationOptions.from_preferred_compute_unit_kind(rt.ComputeUnitKind.neural_engine())
|
| 61 |
+
elif compute == "default":
|
| 62 |
+
opts = rt.SpecializationOptions.default()
|
| 63 |
+
else:
|
| 64 |
+
raise ValueError("compute must be ane | gpu | cpu | default")
|
| 65 |
+
t = time.perf_counter()
|
| 66 |
+
self.model = asyncio.run(rt.AIModel.load(self.bundle / self.config["asset"], opts))
|
| 67 |
+
self.functions: dict[int, object] = {}
|
| 68 |
+
for name in self.config["functions"]:
|
| 69 |
+
self.functions[int(name.rsplit("_", 1)[1])] = self.model.load_function(name)
|
| 70 |
+
self.buckets = sorted(self.functions)
|
| 71 |
+
self.load_s = time.perf_counter() - t
|
| 72 |
+
if not quiet:
|
| 73 |
+
print(f"loaded {self.bundle.name} compute={compute} buckets={self.buckets} in {self.load_s:.1f}s", flush=True)
|
| 74 |
+
|
| 75 |
+
# ---- tokenization -------------------------------------------------------------------------------------
|
| 76 |
+
def tokenize(self, text: str, kind: str | None = "document") -> list[int]:
|
| 77 |
+
"""<bos> + prompt prefix + text + <eos>, truncated to the largest bucket (longer input is cut)."""
|
| 78 |
+
prefix = self.config["prompts"].get(kind, "") if kind else ""
|
| 79 |
+
ids = self.tokenizer.encode(prefix + text, add_special_tokens=False).ids
|
| 80 |
+
max_body = self.buckets[-1] - 2
|
| 81 |
+
if len(ids) > max_body:
|
| 82 |
+
ids = ids[:max_body]
|
| 83 |
+
return [self.config["bos_token_id"]] + ids + [self.config["eos_token_id"]]
|
| 84 |
+
|
| 85 |
+
def bucket(self, n_tokens: int) -> int:
|
| 86 |
+
for b in self.buckets:
|
| 87 |
+
if b >= n_tokens:
|
| 88 |
+
return b
|
| 89 |
+
raise ValueError(f"{n_tokens} tokens exceeds the largest bucket {self.buckets[-1]}")
|
| 90 |
+
|
| 91 |
+
# ---- inference ----------------------------------------------------------------------------------------
|
| 92 |
+
def embed_ids(self, ids: list[int], bucket: int | None = None) -> np.ndarray:
|
| 93 |
+
"""Runs main_<bucket> on ids (with <bos>/<eos>), right-padded with <pad>. Returns the raw 768-d output."""
|
| 94 |
+
S = bucket or self.bucket(len(ids))
|
| 95 |
+
if len(ids) > S:
|
| 96 |
+
raise ValueError(f"{len(ids)} ids do not fit bucket {S}")
|
| 97 |
+
x = np.full((1, S), self.config["pad_token_id"], dtype=np.int32)
|
| 98 |
+
x[0, : len(ids)] = ids
|
| 99 |
+
m = np.zeros((1, S), dtype=np.float16)
|
| 100 |
+
m[0, : len(ids)] = 1
|
| 101 |
+
out = asyncio.run(self.functions[S](inputs={"input_ids": self._rt.NDArray(x), "attention_mask": self._rt.NDArray(m)}))
|
| 102 |
+
return np.array(out["embedding"].numpy(), dtype=np.float32).reshape(-1)
|
| 103 |
+
|
| 104 |
+
def embed(self, text: str, kind: str | None = "document", dim: int = 768) -> np.ndarray:
|
| 105 |
+
"""L2-normalized embedding. dim 768, or a Matryoshka size (512/256/128): leading dims, re-normalized."""
|
| 106 |
+
if dim not in self.config["matryoshka_dims"]:
|
| 107 |
+
raise ValueError(f"dim must be one of {self.config['matryoshka_dims']}")
|
| 108 |
+
v = self.embed_ids(self.tokenize(text, kind))[:dim]
|
| 109 |
+
return v / max(float(np.linalg.norm(v)), 1e-12)
|
| 110 |
+
|
| 111 |
+
def prewarm(self) -> float:
|
| 112 |
+
"""One call per bucket so the first real call pays no specialization/first-call cost. Returns seconds."""
|
| 113 |
+
t = time.perf_counter()
|
| 114 |
+
for S in self.buckets:
|
| 115 |
+
self.embed_ids([self.config["bos_token_id"], self.config["eos_token_id"]], S)
|
| 116 |
+
return time.perf_counter() - t
|
| 117 |
+
|
| 118 |
+
def close(self) -> None:
|
| 119 |
+
self.functions.clear()
|
| 120 |
+
self.model = None
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
# ---- temp-file hygiene ----------------------------------------------------------------------------------
|
| 124 |
+
def mpsgraph_temp_root() -> Path:
|
| 125 |
+
import tempfile
|
| 126 |
+
|
| 127 |
+
return Path(os.environ.get("TMPDIR") or tempfile.gettempdir()) / "com.apple.MetalPerformanceShadersGraph"
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def remove_own_mpsgraph_temp_files(pid: int | None = None, since: float | None = None) -> tuple[int, int]:
|
| 131 |
+
"""Each Neural Engine load of this model makes MPSGraph write ~358 MB of compiled-graph copies into
|
| 132 |
+
$TMPDIR/com.apple.MetalPerformanceShadersGraph/mpsgraph-<pid>-<date>-<n>/, and the Python runtime (coreai.runtime,
|
| 133 |
+
macOS 27.0) does NOT delete them when the model is released or the interpreter exits. This removes the folders of the CURRENT process only (its pid, created after
|
| 134 |
+
it started). Call it after the model object has been released, or at exit. Returns (folders, bytes)."""
|
| 135 |
+
import shutil
|
| 136 |
+
|
| 137 |
+
pid = pid or os.getpid()
|
| 138 |
+
since = _PROCESS_START - 2 if since is None else since
|
| 139 |
+
root = mpsgraph_temp_root()
|
| 140 |
+
folders = nbytes = 0
|
| 141 |
+
if not root.is_dir():
|
| 142 |
+
return 0, 0
|
| 143 |
+
for d in root.iterdir():
|
| 144 |
+
if not d.name.startswith(f"mpsgraph-{pid}-") or not d.is_dir():
|
| 145 |
+
continue
|
| 146 |
+
try:
|
| 147 |
+
if d.stat().st_birthtime < since:
|
| 148 |
+
continue # left by an older process that happened to have the same pid
|
| 149 |
+
nbytes += sum(f.stat().st_size for f in d.rglob("*") if f.is_file())
|
| 150 |
+
shutil.rmtree(d)
|
| 151 |
+
folders += 1
|
| 152 |
+
except OSError:
|
| 153 |
+
pass
|
| 154 |
+
return folders, nbytes
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
# ---- process accounting helpers (macOS) ------------------------------------------------------------------
|
| 158 |
+
def _rusage_v6():
|
| 159 |
+
class RU6(ctypes.Structure):
|
| 160 |
+
_fields_ = [("uuid", ctypes.c_uint8 * 16)] + [(f"f{i}", ctypes.c_uint64) for i in range(47)] + [("reserved", ctypes.c_uint64 * 9)]
|
| 161 |
+
|
| 162 |
+
libc = ctypes.CDLL("/usr/lib/libSystem.B.dylib")
|
| 163 |
+
r = RU6()
|
| 164 |
+
return r if libc.proc_pid_rusage(os.getpid(), 6, ctypes.byref(r)) == 0 else None
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def footprints_mb() -> tuple[float, float]:
|
| 168 |
+
"""(lifetime max physical footprint, Neural Engine footprint) in MB; the latter is > 0 only if the ANE ran."""
|
| 169 |
+
r = _rusage_v6()
|
| 170 |
+
if r is None:
|
| 171 |
+
return -1.0, -1.0
|
| 172 |
+
return r.f28 / 2**20, r.f44 / 2**20 # ri_lifetime_max_phys_footprint, ri_neural_footprint
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def cosine(a, b) -> float:
|
| 176 |
+
a = np.asarray(a, np.float64)
|
| 177 |
+
b = np.asarray(b, np.float64)
|
| 178 |
+
return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b)))
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
# ---- CLI -------------------------------------------------------------------------------------------------
|
| 182 |
+
def cmd_embed(a) -> int:
|
| 183 |
+
m = EmbeddingGemma2CoreAI(a.bundle, a.compute)
|
| 184 |
+
for t in a.args:
|
| 185 |
+
v = m.embed(t, None if a.kind == "none" else a.kind, a.dim)
|
| 186 |
+
print(json.dumps({"text": t, "kind": a.kind, "dim": int(v.shape[0]), "embedding": [round(float(x), 6) for x in v]}, ensure_ascii=False))
|
| 187 |
+
m.close()
|
| 188 |
+
return 0
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def cmd_smoke(a) -> int:
|
| 192 |
+
ref = json.loads(Path(a.args[0]).read_text(encoding="utf-8"))
|
| 193 |
+
m = EmbeddingGemma2CoreAI(a.bundle, a.compute)
|
| 194 |
+
print(f"loaded in {m.load_s:.1f} s (tokenizer {m.tokenizer_load_s:.2f} s), compute={a.compute}, buckets={m.buckets}")
|
| 195 |
+
ok = True
|
| 196 |
+
for c in ref["cases"]:
|
| 197 |
+
v = m.embed(c["text"], c["kind"])
|
| 198 |
+
cos = cosine(v, c["embedding"])
|
| 199 |
+
passed = cos >= ref["threshold_cosine"] and bool(np.all(np.isfinite(v)))
|
| 200 |
+
ok = ok and passed
|
| 201 |
+
print(f"{'PASS' if passed else 'FAIL'} cosine {cos:.6f} (threshold {ref['threshold_cosine']}) {c['kind']}: {c['text'][:60]}")
|
| 202 |
+
fp, nf = footprints_mb()
|
| 203 |
+
print(f"peak footprint {fp:.0f} MB, neural footprint {nf:.0f} MB")
|
| 204 |
+
print("SMOKE PASS" if ok else "SMOKE FAIL")
|
| 205 |
+
m.close()
|
| 206 |
+
return 0 if ok else 1
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def cmd_tokcheck(a) -> int:
|
| 210 |
+
from tokenizers import Tokenizer
|
| 211 |
+
|
| 212 |
+
cases = json.loads(Path(a.args[0]).read_text(encoding="utf-8"))
|
| 213 |
+
tok = Tokenizer.from_file(str(Path(a.bundle) / "tokenizer" / "tokenizer.json"))
|
| 214 |
+
prefixes = {"q": "task: search result | query: ", "d": "title: none | text: ", "raw": ""}
|
| 215 |
+
same = 0
|
| 216 |
+
t = time.perf_counter()
|
| 217 |
+
for c in cases:
|
| 218 |
+
ids = [2] + tok.encode(prefixes[c["kind"]] + c["text"], add_special_tokens=False).ids + [1]
|
| 219 |
+
same += ids == c["ids"]
|
| 220 |
+
print(f"TOKCHECK {same}/{len(cases)} identical (tokenizers {__import__('tokenizers').__version__}) in {time.perf_counter() - t:.2f} s")
|
| 221 |
+
return 0 if same == len(cases) else 1
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def cmd_latency(a) -> int:
|
| 225 |
+
m = EmbeddingGemma2CoreAI(a.bundle, a.compute)
|
| 226 |
+
print(f"loaded in {m.load_s:.1f} s, compute={a.compute}; prewarm {m.prewarm():.2f} s")
|
| 227 |
+
print("bucket p50 ms min ms p90 ms CPU ms/call tok/s")
|
| 228 |
+
for S in m.buckets:
|
| 229 |
+
ids = [m.config["bos_token_id"]] + [1000 + (i * 7919) % 200000 for i in range(1, S - 1)] + [m.config["eos_token_id"]]
|
| 230 |
+
for _ in range(3):
|
| 231 |
+
m.embed_ids(ids, S)
|
| 232 |
+
ts = []
|
| 233 |
+
c0 = time.process_time()
|
| 234 |
+
for _ in range(a.trials):
|
| 235 |
+
t = time.perf_counter()
|
| 236 |
+
m.embed_ids(ids, S)
|
| 237 |
+
ts.append((time.perf_counter() - t) * 1000)
|
| 238 |
+
cpu = (time.process_time() - c0) / a.trials * 1000
|
| 239 |
+
p50 = float(np.median(ts))
|
| 240 |
+
print(f"{S:5d} {p50:7.2f} {min(ts):7.2f} {float(np.percentile(ts, 90)):7.2f} {cpu:11.2f} {S / p50 * 1000:6.0f}")
|
| 241 |
+
fp, nf = footprints_mb()
|
| 242 |
+
print(f"peak footprint {fp:.0f} MB, neural footprint {nf:.0f} MB ({'Neural Engine in use' if nf > 0 else 'Neural Engine NOT used'})")
|
| 243 |
+
m.close()
|
| 244 |
+
return 0
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
def cmd_prewarm(a) -> int:
|
| 248 |
+
t0 = time.perf_counter()
|
| 249 |
+
m = EmbeddingGemma2CoreAI(a.bundle, a.compute)
|
| 250 |
+
w = m.prewarm()
|
| 251 |
+
print(f"load {m.load_s:.1f} s + prewarm {w:.2f} s = ready in {time.perf_counter() - t0:.1f} s")
|
| 252 |
+
m.close()
|
| 253 |
+
return 0
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
def main() -> None:
|
| 257 |
+
ap = argparse.ArgumentParser(description="EmbeddingGemma 2 (text) on Apple Core AI")
|
| 258 |
+
ap.add_argument("cmd", choices=["embed", "smoke", "tokcheck", "latency", "prewarm"])
|
| 259 |
+
ap.add_argument("bundle", help="the model folder (config.json, tokenizer/, *.aimodel)")
|
| 260 |
+
ap.add_argument("args", nargs="*", help="texts (embed) or a json file (smoke, tokcheck)")
|
| 261 |
+
ap.add_argument("--compute", default="ane", choices=["ane", "gpu", "cpu", "default"])
|
| 262 |
+
ap.add_argument("--kind", default="document", help="prompt kind from config.json (document, search_query, ...) or none")
|
| 263 |
+
ap.add_argument("--dim", type=int, default=768)
|
| 264 |
+
ap.add_argument("--trials", type=int, default=50)
|
| 265 |
+
ap.add_argument("--cleanup-temp", action="store_true", help="remove this process's MPSGraph temp folders at exit")
|
| 266 |
+
a = ap.parse_args()
|
| 267 |
+
code = {"embed": cmd_embed, "smoke": cmd_smoke, "tokcheck": cmd_tokcheck, "latency": cmd_latency, "prewarm": cmd_prewarm}[a.cmd](a)
|
| 268 |
+
if a.cleanup_temp:
|
| 269 |
+
import gc
|
| 270 |
+
|
| 271 |
+
gc.collect()
|
| 272 |
+
n, b = remove_own_mpsgraph_temp_files()
|
| 273 |
+
print(f"cleanup-temp: removed {n} MPSGraph temp folder(s), {b / 2**20:.1f} MB")
|
| 274 |
+
sys.exit(code)
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
if __name__ == "__main__":
|
| 278 |
+
main()
|
python/requirements.lock
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Exact versions of the environments this release was validated in (macOS 27.0, Apple silicon).
|
| 2 |
+
# Runtime (python/), Python 3.12 or 3.14:
|
| 3 |
+
coreai-core==1.0.0b3
|
| 4 |
+
numpy==2.4.6
|
| 5 |
+
tokenizers==0.23.3
|
| 6 |
+
huggingface-hub==2.2.0
|
| 7 |
+
# Exporter (export/), on top of the runtime, Python 3.12:
|
| 8 |
+
coreai-torch==0.4.3
|
| 9 |
+
coreai-opt==0.3.0
|
| 10 |
+
torch==2.9.0
|
| 11 |
+
torchao==0.17.0
|
| 12 |
+
safetensors==0.8.0
|
| 13 |
+
ml-dtypes==0.6.0
|
| 14 |
+
networkx==3.7
|
| 15 |
+
scipy==1.18.1
|
| 16 |
+
packaging==26.3
|
| 17 |
+
# Reference vectors (tests/make_smoke_reference.py), a separate environment:
|
| 18 |
+
# sentence-transformers==6.1.0 transformers==5.19.0 torch==2.14.1 torchvision==0.29.1 pillow tokenizers==0.23.3 numpy==2.5.3
|
| 19 |
+
# (pillow and torchvision are needed because the checkpoint's processor is multimodal, even for text-only use)
|
python/requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Runtime requirements for python/ (macOS 27, Apple silicon; validated with Python 3.12 and 3.14).
|
| 2 |
+
# coreai-core provides the `coreai.runtime` module (Apple's Core AI Python runtime); install it from the same
|
| 3 |
+
# distribution that provides coreai-torch. The exact versions this release was validated with are in
|
| 4 |
+
# requirements.lock.
|
| 5 |
+
coreai-core==1.0.0b3
|
| 6 |
+
numpy>=2.0,<2.5
|
| 7 |
+
tokenizers>=0.22,<0.24
|
python/smoke_test.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# Copyright 2026 Everpaper. Licensed under the Apache License, Version 2.0 (see ../LICENSE).
|
| 3 |
+
"""Smoke test: load the bundle, embed three fictional sentences, compare with the shipped fp32 reference vectors.
|
| 4 |
+
|
| 5 |
+
python3 smoke_test.py [--bundle ../model] [--reference ../tests/smoke-reference.json] [--compute ane] [--cleanup-temp]
|
| 6 |
+
|
| 7 |
+
Exit code 0 = every cosine >= threshold (0.999) and all values finite. The first run of a new executable (here: your
|
| 8 |
+
python binary) specializes the model for the Neural Engine: expect up to ~145 s (139-144 s measured). Later runs load
|
| 9 |
+
in 0.4-1.5 s.
|
| 10 |
+
"""
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import argparse
|
| 14 |
+
import sys
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
|
| 17 |
+
HERE = Path(__file__).resolve().parent
|
| 18 |
+
sys.path.insert(0, str(HERE))
|
| 19 |
+
from embeddinggemma2_coreai import EmbeddingGemma2CoreAI, cosine, footprints_mb, remove_own_mpsgraph_temp_files # noqa: E402
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def main() -> int:
|
| 23 |
+
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
| 24 |
+
ap.add_argument("--bundle", default=str(HERE.parent / "model"))
|
| 25 |
+
ap.add_argument("--reference", default=str(HERE.parent / "tests" / "smoke-reference.json"))
|
| 26 |
+
ap.add_argument("--compute", default="ane", choices=["ane", "gpu", "cpu", "default"])
|
| 27 |
+
ap.add_argument("--cleanup-temp", action="store_true", help="remove this process's MPSGraph temp folders at exit")
|
| 28 |
+
a = ap.parse_args()
|
| 29 |
+
import json
|
| 30 |
+
|
| 31 |
+
ref = json.loads(Path(a.reference).read_text(encoding="utf-8"))
|
| 32 |
+
m = EmbeddingGemma2CoreAI(a.bundle, a.compute)
|
| 33 |
+
print(f"loaded in {m.load_s:.1f} s (tokenizer {m.tokenizer_load_s:.2f} s), compute={a.compute}, buckets={m.buckets}")
|
| 34 |
+
ok = True
|
| 35 |
+
for c in ref["cases"]:
|
| 36 |
+
v = m.embed(c["text"], c["kind"])
|
| 37 |
+
cos = cosine(v, c["embedding"])
|
| 38 |
+
passed = cos >= ref["threshold_cosine"] and all(map(lambda x: x == x and abs(x) != float("inf"), v.tolist()))
|
| 39 |
+
ok = ok and passed
|
| 40 |
+
print(f"{'PASS' if passed else 'FAIL'} cosine {cos:.6f} (threshold {ref['threshold_cosine']}) {c['kind']}: {c['text'][:60]}")
|
| 41 |
+
# Matryoshka prefixes must stay close to the truncated+renormalized reference too
|
| 42 |
+
for dim in (512, 256, 128):
|
| 43 |
+
c = ref["cases"][1]
|
| 44 |
+
v = m.embed(c["text"], c["kind"], dim=dim)
|
| 45 |
+
r = c["embedding"][:dim]
|
| 46 |
+
print(f"{'PASS' if cosine(v, r) >= ref['threshold_cosine'] else 'FAIL'} cosine {cosine(v, r):.6f} at dim {dim}")
|
| 47 |
+
fp, nf = footprints_mb()
|
| 48 |
+
print(f"peak footprint {fp:.0f} MB, neural footprint {nf:.0f} MB ({'Neural Engine in use' if nf > 0 else 'Neural Engine not used'})")
|
| 49 |
+
print("SMOKE PASS" if ok else "SMOKE FAIL")
|
| 50 |
+
m.close()
|
| 51 |
+
del m
|
| 52 |
+
if a.cleanup_temp:
|
| 53 |
+
import gc
|
| 54 |
+
|
| 55 |
+
gc.collect()
|
| 56 |
+
n, b = remove_own_mpsgraph_temp_files()
|
| 57 |
+
print(f"cleanup-temp: removed {n} MPSGraph temp folder(s), {b / 2**20:.1f} MB")
|
| 58 |
+
return 0 if ok else 1
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
if __name__ == "__main__":
|
| 62 |
+
sys.exit(main())
|
swift/Package.swift
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// swift-tools-version: 6.0
|
| 2 |
+
// Copyright 2026 Everpaper. Licensed under the Apache License, Version 2.0 (see ../LICENSE).
|
| 3 |
+
import PackageDescription
|
| 4 |
+
|
| 5 |
+
let package = Package(
|
| 6 |
+
name: "EmbeddingGemma2CoreAI",
|
| 7 |
+
platforms: [.macOS("27.0")],
|
| 8 |
+
products: [
|
| 9 |
+
.library(name: "EmbeddingGemma2CoreAI", targets: ["EmbeddingGemma2CoreAI"]),
|
| 10 |
+
.executable(name: "eg2-cli", targets: ["eg2-cli"]),
|
| 11 |
+
],
|
| 12 |
+
targets: [
|
| 13 |
+
// Library: exact Gemma tokenizer + Core AI embedder. No third-party dependencies.
|
| 14 |
+
.target(
|
| 15 |
+
name: "EmbeddingGemma2CoreAI",
|
| 16 |
+
path: "Sources/EmbeddingGemma2CoreAI",
|
| 17 |
+
linkerSettings: [.linkedFramework("CoreAI")]
|
| 18 |
+
),
|
| 19 |
+
// Command-line tool: embed | smoke | tokcheck | latency | prewarm | cleanup-temp
|
| 20 |
+
.executableTarget(
|
| 21 |
+
name: "eg2-cli",
|
| 22 |
+
dependencies: ["EmbeddingGemma2CoreAI"],
|
| 23 |
+
path: "Sources/eg2-cli"
|
| 24 |
+
),
|
| 25 |
+
]
|
| 26 |
+
)
|
swift/Sources/EmbeddingGemma2CoreAI/Embedder.swift
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Copyright 2026 Everpaper. Licensed under the Apache License, Version 2.0 (see ../../../LICENSE).
|
| 2 |
+
//
|
| 3 |
+
// EmbeddingGemma 2 (text) on Apple Core AI, macOS 27. Loads the bundle folder (config.json, tokenizer/, the
|
| 4 |
+
// .aimodel), tokenizes exactly like Hugging Face `tokenizers`, pads to the smallest sequence bucket, runs the
|
| 5 |
+
// matching `main_<S>` function and returns the L2-normalized 768-d embedding (or a Matryoshka prefix).
|
| 6 |
+
|
| 7 |
+
import CoreAI
|
| 8 |
+
import Foundation
|
| 9 |
+
|
| 10 |
+
public struct BundleConfig: Decodable, Sendable {
|
| 11 |
+
public let asset: String
|
| 12 |
+
public let functions: [String]
|
| 13 |
+
public let buckets: [Int]
|
| 14 |
+
public let max_tokens: Int
|
| 15 |
+
public let embedding_dim: Int
|
| 16 |
+
public let matryoshka_dims: [Int]
|
| 17 |
+
public let prompts: [String: String]
|
| 18 |
+
public let bos_token_id: Int32
|
| 19 |
+
public let eos_token_id: Int32
|
| 20 |
+
public let pad_token_id: Int32
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
public enum ComputePreference: String, Sendable {
|
| 24 |
+
case neuralEngine = "ane"
|
| 25 |
+
case gpu
|
| 26 |
+
case cpu
|
| 27 |
+
case `default`
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
public struct EmbedderError: Error, CustomStringConvertible {
|
| 31 |
+
public let description: String
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
public final class EmbeddingGemma2 {
|
| 35 |
+
public let bundleURL: URL
|
| 36 |
+
public let config: BundleConfig
|
| 37 |
+
public let tokenizer: GemmaTokenizer
|
| 38 |
+
public let compute: ComputePreference
|
| 39 |
+
/// Wall-clock seconds spent in `AIModel(contentsOf:)` + `loadFunction` (specialization). 0.4–1.5 s warm;
|
| 40 |
+
/// the first load by an executable that has not loaded this bundle before takes up to ~145 s (see README).
|
| 41 |
+
public let loadSeconds: Double
|
| 42 |
+
public let tokenizerLoadSeconds: Double
|
| 43 |
+
|
| 44 |
+
private let model: AIModel
|
| 45 |
+
private let functions: [Int: InferenceFunction] // bucket -> main_<S>
|
| 46 |
+
public var buckets: [Int] { functions.keys.sorted() }
|
| 47 |
+
|
| 48 |
+
public init(bundle: URL, compute: ComputePreference = .neuralEngine) async throws {
|
| 49 |
+
bundleURL = bundle
|
| 50 |
+
self.compute = compute
|
| 51 |
+
config = try JSONDecoder().decode(BundleConfig.self, from: Data(contentsOf: bundle.appendingPathComponent("config.json")))
|
| 52 |
+
var t0 = Date()
|
| 53 |
+
tokenizer = try GemmaTokenizer(contentsOf: bundle.appendingPathComponent("tokenizer/tokenizer.json"))
|
| 54 |
+
tokenizerLoadSeconds = Date().timeIntervalSince(t0)
|
| 55 |
+
guard tokenizer.bosID == config.bos_token_id, tokenizer.eosID == config.eos_token_id, tokenizer.padID == config.pad_token_id else {
|
| 56 |
+
throw EmbedderError(description: "tokenizer special ids do not match config.json")
|
| 57 |
+
}
|
| 58 |
+
t0 = Date()
|
| 59 |
+
let options: SpecializationOptions
|
| 60 |
+
switch compute {
|
| 61 |
+
case .gpu: options = SpecializationOptions(preferredComputeUnitKind: .gpu)
|
| 62 |
+
case .cpu: options = SpecializationOptions.cpuOnly
|
| 63 |
+
case .neuralEngine: options = SpecializationOptions(preferredComputeUnitKind: .neuralEngine)
|
| 64 |
+
case .default: options = SpecializationOptions.default
|
| 65 |
+
}
|
| 66 |
+
model = try await AIModel(contentsOf: bundle.appendingPathComponent(config.asset), options: options)
|
| 67 |
+
var fns: [Int: InferenceFunction] = [:]
|
| 68 |
+
for name in config.functions {
|
| 69 |
+
guard let s = Int(name.split(separator: "_").last ?? ""), let fn = try model.loadFunction(named: name) else {
|
| 70 |
+
throw EmbedderError(description: "function \(name) missing from \(config.asset)")
|
| 71 |
+
}
|
| 72 |
+
fns[s] = fn
|
| 73 |
+
}
|
| 74 |
+
functions = fns
|
| 75 |
+
loadSeconds = Date().timeIntervalSince(t0)
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
// MARK: - Tokenization
|
| 79 |
+
|
| 80 |
+
/// `<bos>` + prompt prefix + text + `<eos>`, truncated to the largest bucket (2048 tokens: longer input is cut).
|
| 81 |
+
/// `kind` is a key of `config.prompts` ("document", "search_query", ...); nil = no prefix.
|
| 82 |
+
public func tokenize(_ text: String, kind: String? = "document") -> [Int32] {
|
| 83 |
+
let prefix = kind.flatMap { config.prompts[$0] } ?? ""
|
| 84 |
+
var ids = tokenizer.encode(prefix + text)
|
| 85 |
+
let maxBody = (buckets.last ?? 32) - 2
|
| 86 |
+
if ids.count > maxBody { ids = Array(ids.prefix(maxBody)) }
|
| 87 |
+
return [config.bos_token_id] + ids + [config.eos_token_id]
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
public func bucket(forTokenCount n: Int) throws -> Int {
|
| 91 |
+
guard let b = buckets.first(where: { $0 >= n }) else {
|
| 92 |
+
throw EmbedderError(description: "\(n) tokens exceeds the largest bucket \(buckets.last ?? 0)")
|
| 93 |
+
}
|
| 94 |
+
return b
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
// MARK: - Inference
|
| 98 |
+
|
| 99 |
+
/// Runs `main_<bucket>` on already-tokenized ids (with `<bos>`/`<eos>`), right-padded with `<pad>`.
|
| 100 |
+
/// Returns the raw 768-d output (already L2-normalized by the graph), as Float.
|
| 101 |
+
public func embed(ids: [Int32], bucket: Int? = nil) async throws -> [Float] {
|
| 102 |
+
let S = try bucket ?? self.bucket(forTokenCount: ids.count)
|
| 103 |
+
guard let fn = functions[S] else { throw EmbedderError(description: "no function for bucket \(S)") }
|
| 104 |
+
guard ids.count <= S else { throw EmbedderError(description: "\(ids.count) ids do not fit bucket \(S)") }
|
| 105 |
+
var inputIDs = NDArray(shape: [1, S], scalarType: .int32)
|
| 106 |
+
var mask = NDArray(shape: [1, S], scalarType: .float16)
|
| 107 |
+
do {
|
| 108 |
+
var v = inputIDs.mutableView(as: Int32.self)
|
| 109 |
+
var m = mask.mutableView(as: Float16.self)
|
| 110 |
+
let pad = config.pad_token_id
|
| 111 |
+
v.withUnsafeMutablePointer { p, _, _ in
|
| 112 |
+
for i in 0..<S { p[i] = i < ids.count ? ids[i] : pad }
|
| 113 |
+
}
|
| 114 |
+
m.withUnsafeMutablePointer { p, _, _ in
|
| 115 |
+
for i in 0..<S { p[i] = i < ids.count ? 1 : 0 }
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
var out = try await fn.run(inputs: ["input_ids": inputIDs, "attention_mask": mask])
|
| 119 |
+
guard let emb = out.remove("embedding")?.ndArray else { throw EmbedderError(description: "output 'embedding' missing") }
|
| 120 |
+
let dim = config.embedding_dim
|
| 121 |
+
var result = [Float](repeating: 0, count: dim)
|
| 122 |
+
let view = emb.view(as: Float16.self)
|
| 123 |
+
view.withUnsafePointer { p, _, _ in
|
| 124 |
+
for i in 0..<dim { result[i] = Float(p[i]) }
|
| 125 |
+
}
|
| 126 |
+
return result
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
/// Embeds one text. `dim` 768 (full) or a Matryoshka size 512/256/128: the leading dims, re-normalized.
|
| 130 |
+
public func embed(_ text: String, kind: String? = "document", dim: Int = 768) async throws -> [Float] {
|
| 131 |
+
guard config.matryoshka_dims.contains(dim) else { throw EmbedderError(description: "dim must be one of \(config.matryoshka_dims)") }
|
| 132 |
+
var v = Array(try await embed(ids: tokenize(text, kind: kind)).prefix(dim))
|
| 133 |
+
let norm = max(sqrt(v.reduce(0) { $0 + $1 * $1 }), 1e-12)
|
| 134 |
+
for i in v.indices { v[i] /= norm }
|
| 135 |
+
return v
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
/// Runs every bucket once so the first real call pays no specialization/first-call cost. Call it right after
|
| 139 |
+
/// init, off the main thread. Returns the seconds it took.
|
| 140 |
+
@discardableResult
|
| 141 |
+
public func prewarm() async throws -> Double {
|
| 142 |
+
let t0 = Date()
|
| 143 |
+
for S in buckets {
|
| 144 |
+
_ = try await embed(ids: [config.bos_token_id, config.eos_token_id], bucket: S)
|
| 145 |
+
}
|
| 146 |
+
return Date().timeIntervalSince(t0)
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
// MARK: - Temp-file hygiene
|
| 150 |
+
|
| 151 |
+
/// Each Neural Engine load of this model makes MPSGraph write ~358 MB of compiled graph copies into
|
| 152 |
+
/// `$TMPDIR/com.apple.MetalPerformanceShadersGraph/mpsgraph-<pid>-<date>-<n>/`. With the Swift `CoreAI`
|
| 153 |
+
/// framework (macOS 27.0) the runtime deletes that folder itself when the model is released, so this usually
|
| 154 |
+
/// finds nothing; it is a safety net for a folder that survives (e.g. the process exits or the model is still
|
| 155 |
+
/// alive). It removes only the folders that belong to the CURRENT process (its pid, created after it started).
|
| 156 |
+
/// Call it after the `EmbeddingGemma2` instance has been released, or at exit. Returns the number of folders
|
| 157 |
+
/// removed and their total bytes.
|
| 158 |
+
@discardableResult
|
| 159 |
+
public static func removeOwnMPSGraphTempFiles() -> (folders: Int, bytes: Int64) {
|
| 160 |
+
let fm = FileManager.default
|
| 161 |
+
let root = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("com.apple.MetalPerformanceShadersGraph")
|
| 162 |
+
let prefix = "mpsgraph-\(getpid())-"
|
| 163 |
+
let started = processStartDate()
|
| 164 |
+
var folders = 0
|
| 165 |
+
var bytes: Int64 = 0
|
| 166 |
+
guard let entries = try? fm.contentsOfDirectory(at: root, includingPropertiesForKeys: [.creationDateKey]) else { return (0, 0) }
|
| 167 |
+
for url in entries where url.lastPathComponent.hasPrefix(prefix) {
|
| 168 |
+
if let started, let created = (try? url.resourceValues(forKeys: [.creationDateKey]))?.creationDate, created < started - 2 {
|
| 169 |
+
continue // a folder left by an older process that happened to have the same pid
|
| 170 |
+
}
|
| 171 |
+
if let e = fm.enumerator(at: url, includingPropertiesForKeys: [.fileSizeKey]) {
|
| 172 |
+
for case let f as URL in e { bytes += Int64((try? f.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? 0) }
|
| 173 |
+
}
|
| 174 |
+
if (try? fm.removeItem(at: url)) != nil { folders += 1 }
|
| 175 |
+
}
|
| 176 |
+
return (folders, bytes)
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
private static func processStartDate() -> Date? {
|
| 180 |
+
var info = kinfo_proc()
|
| 181 |
+
var size = MemoryLayout<kinfo_proc>.stride
|
| 182 |
+
var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
|
| 183 |
+
guard sysctl(&mib, UInt32(mib.count), &info, &size, nil, 0) == 0 else { return nil }
|
| 184 |
+
let tv = info.kp_proc.p_starttime
|
| 185 |
+
return Date(timeIntervalSince1970: Double(tv.tv_sec) + Double(tv.tv_usec) / 1e6)
|
| 186 |
+
}
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
// MARK: - Small helpers for callers and tests
|
| 190 |
+
|
| 191 |
+
public func cosineSimilarity(_ a: [Float], _ b: [Float]) -> Double {
|
| 192 |
+
var dot = 0.0, na = 0.0, nb = 0.0
|
| 193 |
+
for i in 0..<min(a.count, b.count) {
|
| 194 |
+
dot += Double(a[i]) * Double(b[i]); na += Double(a[i]) * Double(a[i]); nb += Double(b[i]) * Double(b[i])
|
| 195 |
+
}
|
| 196 |
+
return na > 0 && nb > 0 ? dot / (sqrt(na) * sqrt(nb)) : 0
|
| 197 |
+
}
|
swift/Sources/EmbeddingGemma2CoreAI/ExactJSON.swift
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Copyright 2026 Everpaper. Licensed under the Apache License, Version 2.0 (see ../../../LICENSE).
|
| 2 |
+
//
|
| 3 |
+
// A small, exact JSON reader, written for this package and used to read tokenizer.json. Strings are returned as
|
| 4 |
+
// the exact UTF-8 bytes they encode (every \uXXXX escape, including surrogate pairs, is decoded to UTF-8; nothing
|
| 5 |
+
// is normalized or stripped). It exists because Foundation's JSONSerialization (macOS 27.0) drops a leading U+FEFF
|
| 6 |
+
// (byte-order mark) from every string VALUE, which turns the vocabulary entries "", "", ...
|
| 7 |
+
// into "", "", ... and breaks the tokenizer. (JSONDecoder does not have the bug, but a 32 MB vocabulary
|
| 8 |
+
// decoded through Codable is slow, and this reader keeps the bytes exact by construction.)
|
| 9 |
+
//
|
| 10 |
+
// Supported: the full JSON grammar (RFC 8259). Numbers are returned as Double. Object keys keep their order and
|
| 11 |
+
// duplicates (the vocabulary is read as an ordered list of pairs).
|
| 12 |
+
|
| 13 |
+
import Foundation
|
| 14 |
+
|
| 15 |
+
public indirect enum ExactJSON {
|
| 16 |
+
case object([(key: [UInt8], value: ExactJSON)])
|
| 17 |
+
case array([ExactJSON])
|
| 18 |
+
case string([UInt8])
|
| 19 |
+
case number(Double)
|
| 20 |
+
case bool(Bool)
|
| 21 |
+
case null
|
| 22 |
+
|
| 23 |
+
public struct ParseError: Error, CustomStringConvertible { public let description: String }
|
| 24 |
+
|
| 25 |
+
public static func parse(_ data: Data) throws -> ExactJSON {
|
| 26 |
+
try data.withUnsafeBytes { raw -> ExactJSON in
|
| 27 |
+
var p = Parser(bytes: raw.bindMemory(to: UInt8.self))
|
| 28 |
+
p.skipWS()
|
| 29 |
+
let v = try p.value()
|
| 30 |
+
p.skipWS()
|
| 31 |
+
guard p.i == p.bytes.count else { throw ParseError(description: "trailing characters at byte \(p.i)") }
|
| 32 |
+
return v
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
public static func parse(contentsOf url: URL) throws -> ExactJSON { try parse(Data(contentsOf: url)) }
|
| 37 |
+
|
| 38 |
+
// MARK: accessors
|
| 39 |
+
public subscript(key: String) -> ExactJSON? {
|
| 40 |
+
guard case .object(let pairs) = self else { return nil }
|
| 41 |
+
let k = Array(key.utf8)
|
| 42 |
+
for p in pairs where p.key == k { return p.value }
|
| 43 |
+
return nil
|
| 44 |
+
}
|
| 45 |
+
public var stringBytes: [UInt8]? { if case .string(let b) = self { return b }; return nil }
|
| 46 |
+
public var string: String? { stringBytes.map { String(decoding: $0, as: UTF8.self) } }
|
| 47 |
+
public var double: Double? { if case .number(let d) = self { return d }; return nil }
|
| 48 |
+
public var int: Int? { double.flatMap { $0 == $0.rounded() && abs($0) < 9e15 ? Int($0) : nil } }
|
| 49 |
+
public var bool: Bool? { if case .bool(let b) = self { return b }; return nil }
|
| 50 |
+
public var isNull: Bool { if case .null = self { return true }; return false }
|
| 51 |
+
public var array: [ExactJSON]? { if case .array(let a) = self { return a }; return nil }
|
| 52 |
+
public var pairs: [(key: [UInt8], value: ExactJSON)]? { if case .object(let p) = self { return p }; return nil }
|
| 53 |
+
|
| 54 |
+
// MARK: parser
|
| 55 |
+
private struct Parser {
|
| 56 |
+
let bytes: UnsafeBufferPointer<UInt8>
|
| 57 |
+
var i = 0
|
| 58 |
+
|
| 59 |
+
init(bytes: UnsafeBufferPointer<UInt8>) { self.bytes = bytes }
|
| 60 |
+
|
| 61 |
+
mutating func skipWS() {
|
| 62 |
+
while i < bytes.count, bytes[i] == 0x20 || bytes[i] == 0x0A || bytes[i] == 0x0D || bytes[i] == 0x09 { i += 1 }
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
func fail(_ what: String) -> ParseError { ParseError(description: "\(what) at byte \(i)") }
|
| 66 |
+
|
| 67 |
+
mutating func value() throws -> ExactJSON {
|
| 68 |
+
guard i < bytes.count else { throw fail("unexpected end") }
|
| 69 |
+
switch bytes[i] {
|
| 70 |
+
case 0x7B: return try object()
|
| 71 |
+
case 0x5B: return try array()
|
| 72 |
+
case 0x22: return .string(try string())
|
| 73 |
+
case 0x74: try literal("true"); return .bool(true)
|
| 74 |
+
case 0x66: try literal("false"); return .bool(false)
|
| 75 |
+
case 0x6E: try literal("null"); return .null
|
| 76 |
+
default: return .number(try number())
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
mutating func literal(_ s: StaticString) throws {
|
| 81 |
+
let n = s.utf8CodeUnitCount
|
| 82 |
+
guard i + n <= bytes.count else { throw fail("bad literal") }
|
| 83 |
+
for k in 0..<n where bytes[i + k] != s.utf8Start[k] { throw fail("bad literal") }
|
| 84 |
+
i += n
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
mutating func number() throws -> Double {
|
| 88 |
+
let start = i
|
| 89 |
+
while i < bytes.count {
|
| 90 |
+
let c = bytes[i]
|
| 91 |
+
if (c >= 0x30 && c <= 0x39) || c == 0x2D || c == 0x2B || c == 0x2E || c == 0x65 || c == 0x45 { i += 1 } else { break }
|
| 92 |
+
}
|
| 93 |
+
guard i > start, let d = Double(String(decoding: bytes[start..<i], as: UTF8.self)) else { throw fail("bad number") }
|
| 94 |
+
return d
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
mutating func object() throws -> ExactJSON {
|
| 98 |
+
i += 1
|
| 99 |
+
var pairs: [(key: [UInt8], value: ExactJSON)] = []
|
| 100 |
+
skipWS()
|
| 101 |
+
if i < bytes.count, bytes[i] == 0x7D { i += 1; return .object(pairs) }
|
| 102 |
+
while true {
|
| 103 |
+
skipWS()
|
| 104 |
+
guard i < bytes.count, bytes[i] == 0x22 else { throw fail("expected key") }
|
| 105 |
+
let k = try string()
|
| 106 |
+
skipWS()
|
| 107 |
+
guard i < bytes.count, bytes[i] == 0x3A else { throw fail("expected ':'") }
|
| 108 |
+
i += 1
|
| 109 |
+
skipWS()
|
| 110 |
+
pairs.append((k, try value()))
|
| 111 |
+
skipWS()
|
| 112 |
+
guard i < bytes.count else { throw fail("unterminated object") }
|
| 113 |
+
if bytes[i] == 0x2C { i += 1; continue }
|
| 114 |
+
if bytes[i] == 0x7D { i += 1; return .object(pairs) }
|
| 115 |
+
throw fail("expected ',' or '}'")
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
mutating func array() throws -> ExactJSON {
|
| 120 |
+
i += 1
|
| 121 |
+
var items: [ExactJSON] = []
|
| 122 |
+
skipWS()
|
| 123 |
+
if i < bytes.count, bytes[i] == 0x5D { i += 1; return .array(items) }
|
| 124 |
+
while true {
|
| 125 |
+
skipWS()
|
| 126 |
+
items.append(try value())
|
| 127 |
+
skipWS()
|
| 128 |
+
guard i < bytes.count else { throw fail("unterminated array") }
|
| 129 |
+
if bytes[i] == 0x2C { i += 1; continue }
|
| 130 |
+
if bytes[i] == 0x5D { i += 1; return .array(items) }
|
| 131 |
+
throw fail("expected ',' or ']'")
|
| 132 |
+
}
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
mutating func hex4() throws -> UInt32 {
|
| 136 |
+
guard i + 4 <= bytes.count else { throw fail("bad \\u escape") }
|
| 137 |
+
var v: UInt32 = 0
|
| 138 |
+
for _ in 0..<4 {
|
| 139 |
+
let c = bytes[i]
|
| 140 |
+
let d: UInt32
|
| 141 |
+
switch c {
|
| 142 |
+
case 0x30...0x39: d = UInt32(c - 0x30)
|
| 143 |
+
case 0x41...0x46: d = UInt32(c - 0x41 + 10)
|
| 144 |
+
case 0x61...0x66: d = UInt32(c - 0x61 + 10)
|
| 145 |
+
default: throw fail("bad hex digit")
|
| 146 |
+
}
|
| 147 |
+
v = v << 4 | d
|
| 148 |
+
i += 1
|
| 149 |
+
}
|
| 150 |
+
return v
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
/// Decodes a JSON string starting at the opening quote into exact UTF-8 bytes.
|
| 154 |
+
mutating func string() throws -> [UInt8] {
|
| 155 |
+
i += 1
|
| 156 |
+
var out: [UInt8] = []
|
| 157 |
+
while true {
|
| 158 |
+
guard i < bytes.count else { throw fail("unterminated string") }
|
| 159 |
+
let c = bytes[i]
|
| 160 |
+
if c == 0x22 { i += 1; return out }
|
| 161 |
+
if c != 0x5C { out.append(c); i += 1; continue }
|
| 162 |
+
i += 1
|
| 163 |
+
guard i < bytes.count else { throw fail("unterminated escape") }
|
| 164 |
+
let e = bytes[i]
|
| 165 |
+
i += 1
|
| 166 |
+
switch e {
|
| 167 |
+
case 0x22: out.append(0x22)
|
| 168 |
+
case 0x5C: out.append(0x5C)
|
| 169 |
+
case 0x2F: out.append(0x2F)
|
| 170 |
+
case 0x62: out.append(0x08)
|
| 171 |
+
case 0x66: out.append(0x0C)
|
| 172 |
+
case 0x6E: out.append(0x0A)
|
| 173 |
+
case 0x72: out.append(0x0D)
|
| 174 |
+
case 0x74: out.append(0x09)
|
| 175 |
+
case 0x75:
|
| 176 |
+
var cp = try hex4()
|
| 177 |
+
if cp >= 0xD800, cp <= 0xDBFF, i + 6 <= bytes.count, bytes[i] == 0x5C, bytes[i + 1] == 0x75 {
|
| 178 |
+
let save = i
|
| 179 |
+
i += 2
|
| 180 |
+
let lo = try hex4()
|
| 181 |
+
if lo >= 0xDC00, lo <= 0xDFFF {
|
| 182 |
+
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00)
|
| 183 |
+
} else {
|
| 184 |
+
i = save // not a pair: keep the lone surrogate (encoded below as 3 bytes, like Rust would reject; never occurs in our files)
|
| 185 |
+
}
|
| 186 |
+
}
|
| 187 |
+
appendUTF8(cp, to: &out)
|
| 188 |
+
default: throw fail("bad escape")
|
| 189 |
+
}
|
| 190 |
+
}
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
func appendUTF8(_ cp: UInt32, to out: inout [UInt8]) {
|
| 194 |
+
switch cp {
|
| 195 |
+
case 0..<0x80: out.append(UInt8(cp))
|
| 196 |
+
case 0x80..<0x800: out.append(UInt8(0xC0 | cp >> 6)); out.append(UInt8(0x80 | cp & 0x3F))
|
| 197 |
+
case 0x800..<0x10000: out.append(UInt8(0xE0 | cp >> 12)); out.append(UInt8(0x80 | (cp >> 6) & 0x3F)); out.append(UInt8(0x80 | cp & 0x3F))
|
| 198 |
+
default: out.append(UInt8(0xF0 | cp >> 18)); out.append(UInt8(0x80 | (cp >> 12) & 0x3F)); out.append(UInt8(0x80 | (cp >> 6) & 0x3F)); out.append(UInt8(0x80 | cp & 0x3F))
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
}
|
| 202 |
+
}
|
swift/Sources/EmbeddingGemma2CoreAI/GemmaTokenizer.swift
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Copyright 2026 Everpaper. Licensed under the Apache License, Version 2.0 (see ../../../LICENSE).
|
| 2 |
+
//
|
| 3 |
+
// Exact re-implementation of the Hugging Face `tokenizers` encode pipeline for the Gemma `tokenizer.json`
|
| 4 |
+
// shipped in this bundle:
|
| 5 |
+
//
|
| 6 |
+
// added tokens leftmost-longest match of the 24 special tokens on the RAW text (they are `normalized: false`)
|
| 7 |
+
// normalizer Replace " " -> "\u{2581}" on every remaining piece
|
| 8 |
+
// pre_tokenizer Split(" ") — a no-op after the normalizer, so each piece is ONE BPE word
|
| 9 |
+
// model BPE, byte_fallback (a scalar missing from the vocab becomes its UTF-8 bytes as `<0xNN>` tokens),
|
| 10 |
+
// merges applied by rank, ties broken by position (the `tokenizers` heap algorithm)
|
| 11 |
+
//
|
| 12 |
+
// Everything operates on Unicode scalars and UTF-8 bytes: no grapheme-cluster (`Character`) iteration and no
|
| 13 |
+
// String equality (which is canonical equivalence in Swift). The vocabulary is keyed by exact UTF-8 bytes, so
|
| 14 |
+
// "é" (U+00E9) and "e" + U+0301 are different keys, as they are for `tokenizers`. tokenizer.json is read with
|
| 15 |
+
// ExactJSON, not Foundation (which strips a leading U+FEFF from string values and corrupts three vocab entries).
|
| 16 |
+
// Verified byte-identical to `tokenizers` 0.23 on tests/tokenizer-cases.json (3,758 texts, 608,225 ids).
|
| 17 |
+
|
| 18 |
+
import Foundation
|
| 19 |
+
|
| 20 |
+
public final class GemmaTokenizer: Sendable {
|
| 21 |
+
public struct LoadError: Error, CustomStringConvertible {
|
| 22 |
+
public let description: String
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
public let bosID: Int32
|
| 26 |
+
public let eosID: Int32
|
| 27 |
+
public let padID: Int32
|
| 28 |
+
public let unkID: Int32
|
| 29 |
+
public let vocabSize: Int
|
| 30 |
+
|
| 31 |
+
private let vocab: [[UInt8]: Int32]
|
| 32 |
+
private let tokenBytes: [[UInt8]] // id -> bytes (for decode)
|
| 33 |
+
private let merges: [UInt64: MergeTarget] // (left id << 32 | right id) -> rank, merged id
|
| 34 |
+
private let byteIDs: [Int32] // 256 entries: id of "<0xNN>"
|
| 35 |
+
private let asciiIDs: [Int32] // 128 entries: id of the 1-byte token, or -1
|
| 36 |
+
private let added: [AddedToken] // longest content first
|
| 37 |
+
private let addedFirstScalars: Set<UInt32>
|
| 38 |
+
private let specialIDs: Set<Int32>
|
| 39 |
+
|
| 40 |
+
private struct MergeTarget { let rank: Int32; let id: Int32 }
|
| 41 |
+
private struct AddedToken { let scalars: [Unicode.Scalar]; let id: Int32 }
|
| 42 |
+
|
| 43 |
+
/// Loads `tokenizer.json`. Throws if the file is not the Gemma BPE configuration this class implements.
|
| 44 |
+
public convenience init(contentsOf url: URL) throws {
|
| 45 |
+
try self.init(data: try Data(contentsOf: url))
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
public init(data: Data) throws {
|
| 49 |
+
let root = try ExactJSON.parse(data)
|
| 50 |
+
// --- configuration guards: refuse anything this implementation does not reproduce exactly ---
|
| 51 |
+
guard let norm = root["normalizer"], norm["type"]?.string == "Replace",
|
| 52 |
+
norm["pattern"]?["String"]?.string == " ", norm["content"]?.stringBytes == Array("\u{2581}".utf8)
|
| 53 |
+
else { throw LoadError(description: "unsupported normalizer (expected Replace \" \" -> U+2581)") }
|
| 54 |
+
guard let pre = root["pre_tokenizer"], pre["type"]?.string == "Split", pre["pattern"]?["String"]?.string == " "
|
| 55 |
+
else { throw LoadError(description: "unsupported pre_tokenizer (expected Split \" \")") }
|
| 56 |
+
guard let model = root["model"], model["type"]?.string == "BPE", model["byte_fallback"]?.bool == true,
|
| 57 |
+
model["continuing_subword_prefix"]?.isNull ?? true, model["end_of_word_suffix"]?.isNull ?? true,
|
| 58 |
+
model["dropout"]?.isNull ?? true, (model["ignore_merges"]?.bool ?? false) == false
|
| 59 |
+
else { throw LoadError(description: "unsupported BPE model configuration") }
|
| 60 |
+
guard let vocabPairs = model["vocab"]?.pairs, let mergeList = model["merges"]?.array else {
|
| 61 |
+
throw LoadError(description: "tokenizer.json: missing vocab/merges")
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
// --- vocab: exact UTF-8 bytes -> id ---
|
| 65 |
+
var vocab = [[UInt8]: Int32](minimumCapacity: vocabPairs.count)
|
| 66 |
+
var maxID: Int32 = -1
|
| 67 |
+
for (key, value) in vocabPairs {
|
| 68 |
+
guard let id = value.int else { throw LoadError(description: "vocab value is not an integer") }
|
| 69 |
+
vocab[key] = Int32(id)
|
| 70 |
+
if Int32(id) > maxID { maxID = Int32(id) }
|
| 71 |
+
}
|
| 72 |
+
guard vocab.count == vocabPairs.count else {
|
| 73 |
+
throw LoadError(description: "duplicate vocab keys (\(vocab.count) != \(vocabPairs.count))")
|
| 74 |
+
}
|
| 75 |
+
var tokenBytes = [[UInt8]](repeating: [], count: Int(maxID) + 1)
|
| 76 |
+
for (bytes, id) in vocab { tokenBytes[Int(id)] = bytes }
|
| 77 |
+
|
| 78 |
+
// --- merges: [a, b] (or "a b") -> rank, id of a+b ---
|
| 79 |
+
var merges = [UInt64: MergeTarget](minimumCapacity: mergeList.count)
|
| 80 |
+
for (rank, m) in mergeList.enumerated() {
|
| 81 |
+
let a: [UInt8], b: [UInt8]
|
| 82 |
+
if let pair = m.array, pair.count == 2, let x = pair[0].stringBytes, let y = pair[1].stringBytes {
|
| 83 |
+
(a, b) = (x, y)
|
| 84 |
+
} else if let s = m.stringBytes, let sp = s.firstIndex(of: 0x20) {
|
| 85 |
+
(a, b) = (Array(s[..<sp]), Array(s[(sp + 1)...]))
|
| 86 |
+
} else {
|
| 87 |
+
throw LoadError(description: "merge #\(rank) has an unexpected shape")
|
| 88 |
+
}
|
| 89 |
+
guard let ia = vocab[a], let ib = vocab[b], let iab = vocab[a + b] else {
|
| 90 |
+
throw LoadError(description: "merge #\(rank) references a token missing from the vocab")
|
| 91 |
+
}
|
| 92 |
+
merges[GemmaTokenizer.pairKey(ia, ib)] = MergeTarget(rank: Int32(rank), id: iab)
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
// --- byte fallback tokens and the ASCII fast path ---
|
| 96 |
+
var byteIDs = [Int32](repeating: -1, count: 256)
|
| 97 |
+
for b in 0..<256 {
|
| 98 |
+
guard let id = vocab[Array(String(format: "<0x%02X>", b).utf8)] else {
|
| 99 |
+
throw LoadError(description: "byte fallback token <0x\(String(format: "%02X", b))> missing")
|
| 100 |
+
}
|
| 101 |
+
byteIDs[b] = id
|
| 102 |
+
}
|
| 103 |
+
var asciiIDs = [Int32](repeating: -1, count: 128)
|
| 104 |
+
for c in 0..<128 { asciiIDs[c] = vocab[[UInt8(c)]] ?? -1 }
|
| 105 |
+
|
| 106 |
+
// --- added (special) tokens: only the exact-match flavour is supported ---
|
| 107 |
+
var added: [AddedToken] = []
|
| 108 |
+
for t in root["added_tokens"]?.array ?? [] {
|
| 109 |
+
guard let content = t["content"]?.stringBytes, let id = t["id"]?.int else { continue }
|
| 110 |
+
let flags = ["normalized", "lstrip", "rstrip", "single_word"].map { t[$0]?.bool ?? false }
|
| 111 |
+
guard !flags.contains(true) else {
|
| 112 |
+
throw LoadError(description: "added token \(String(decoding: content, as: UTF8.self)) uses normalized/lstrip/rstrip/single_word, which is not supported")
|
| 113 |
+
}
|
| 114 |
+
added.append(AddedToken(scalars: Array(String(decoding: content, as: UTF8.self).unicodeScalars), id: Int32(id)))
|
| 115 |
+
}
|
| 116 |
+
added.sort { $0.scalars.count > $1.scalars.count }
|
| 117 |
+
|
| 118 |
+
func special(_ name: String) throws -> Int32 {
|
| 119 |
+
guard let id = vocab[Array(name.utf8)] else { throw LoadError(description: "\(name) missing from vocab") }
|
| 120 |
+
return id
|
| 121 |
+
}
|
| 122 |
+
self.vocab = vocab
|
| 123 |
+
self.tokenBytes = tokenBytes
|
| 124 |
+
self.merges = merges
|
| 125 |
+
self.byteIDs = byteIDs
|
| 126 |
+
self.asciiIDs = asciiIDs
|
| 127 |
+
self.added = added
|
| 128 |
+
self.addedFirstScalars = Set(added.compactMap { $0.scalars.first?.value })
|
| 129 |
+
self.specialIDs = Set(added.map { $0.id })
|
| 130 |
+
self.vocabSize = vocab.count
|
| 131 |
+
self.bosID = try special("<bos>")
|
| 132 |
+
self.eosID = try special("<eos>")
|
| 133 |
+
self.padID = try special("<pad>")
|
| 134 |
+
self.unkID = try special("<unk>")
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
@inline(__always) private static func pairKey(_ a: Int32, _ b: Int32) -> UInt64 {
|
| 138 |
+
(UInt64(UInt32(bitPattern: a)) << 32) | UInt64(UInt32(bitPattern: b))
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
/// Token ids of `text` without `<bos>`/`<eos>` (the same as `tokenizers` `encode(text, add_special_tokens=False)`).
|
| 142 |
+
/// Special tokens written literally in the text (e.g. "<eos>") are recognised, exactly as `tokenizers` does.
|
| 143 |
+
public func encode(_ text: String) -> [Int32] {
|
| 144 |
+
let scalars = Array(text.unicodeScalars)
|
| 145 |
+
var ids: [Int32] = []
|
| 146 |
+
ids.reserveCapacity(scalars.count / 3 + 4)
|
| 147 |
+
var i = 0, pieceStart = 0
|
| 148 |
+
while i < scalars.count {
|
| 149 |
+
if addedFirstScalars.contains(scalars[i].value), let hit = matchAdded(scalars, at: i) {
|
| 150 |
+
if i > pieceStart { bpe(scalars[pieceStart..<i], into: &ids) }
|
| 151 |
+
ids.append(hit.id)
|
| 152 |
+
i += hit.scalars.count
|
| 153 |
+
pieceStart = i
|
| 154 |
+
} else {
|
| 155 |
+
i += 1
|
| 156 |
+
}
|
| 157 |
+
}
|
| 158 |
+
if pieceStart < scalars.count { bpe(scalars[pieceStart...], into: &ids) }
|
| 159 |
+
return ids
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
/// `<bos>` + encode(text) + `<eos>`.
|
| 163 |
+
public func encodeWithSpecials(_ text: String) -> [Int32] {
|
| 164 |
+
[bosID] + encode(text) + [eosID]
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
/// Text for a sequence of ids (the bundle's decoder: "\u{2581}" -> " ", byte-fallback tokens fused into UTF-8).
|
| 168 |
+
public func decode(_ ids: [Int32], skipSpecialTokens: Bool = true) -> String {
|
| 169 |
+
var bytes: [UInt8] = []
|
| 170 |
+
for id in ids {
|
| 171 |
+
guard id >= 0, Int(id) < tokenBytes.count else { continue }
|
| 172 |
+
if skipSpecialTokens, specialIDs.contains(id) { continue }
|
| 173 |
+
if let b = byteIDs.firstIndex(of: id) { bytes.append(UInt8(b)); continue }
|
| 174 |
+
let t = tokenBytes[Int(id)]
|
| 175 |
+
var j = 0
|
| 176 |
+
while j < t.count { // replace every U+2581 (E2 96 81) by a space
|
| 177 |
+
if j + 2 < t.count, t[j] == 0xE2, t[j + 1] == 0x96, t[j + 2] == 0x81 { bytes.append(0x20); j += 3 }
|
| 178 |
+
else { bytes.append(t[j]); j += 1 }
|
| 179 |
+
}
|
| 180 |
+
}
|
| 181 |
+
return String(decoding: bytes, as: UTF8.self)
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
private func matchAdded(_ s: [Unicode.Scalar], at i: Int) -> AddedToken? {
|
| 185 |
+
for t in added where i + t.scalars.count <= s.count { // sorted longest first => first hit is the longest
|
| 186 |
+
var k = 0
|
| 187 |
+
while k < t.scalars.count, s[i + k] == t.scalars[k] { k += 1 }
|
| 188 |
+
if k == t.scalars.count { return t }
|
| 189 |
+
}
|
| 190 |
+
return nil
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
// MARK: - BPE on one word (the `tokenizers` Word::merge_all algorithm)
|
| 194 |
+
|
| 195 |
+
private struct HeapEntry { let rank: Int32; let pos: Int32; let id: Int32 }
|
| 196 |
+
|
| 197 |
+
private func bpe(_ piece: ArraySlice<Unicode.Scalar>, into out: inout [Int32]) {
|
| 198 |
+
// 1. initial symbols: one per scalar (space -> U+2581), byte fallback for scalars missing from the vocab
|
| 199 |
+
var syms: [Int32] = []
|
| 200 |
+
syms.reserveCapacity(piece.count)
|
| 201 |
+
var utf8 = [UInt8](repeating: 0, count: 4)
|
| 202 |
+
for sc in piece {
|
| 203 |
+
let v = sc.value
|
| 204 |
+
if v < 128, v != 0x20 {
|
| 205 |
+
let id = asciiIDs[Int(v)]
|
| 206 |
+
syms.append(id >= 0 ? id : byteIDs[Int(v)])
|
| 207 |
+
continue
|
| 208 |
+
}
|
| 209 |
+
let scalar = v == 0x20 ? Unicode.Scalar(0x2581)! : sc
|
| 210 |
+
var n = 0
|
| 211 |
+
for b in String(scalar).utf8 { utf8[n] = b; n += 1 } // the UTF-8 of ONE scalar: nothing is normalized
|
| 212 |
+
let key = Array(utf8[0..<n])
|
| 213 |
+
if let id = vocab[key] {
|
| 214 |
+
syms.append(id)
|
| 215 |
+
} else {
|
| 216 |
+
for b in key { syms.append(byteIDs[Int(b)]) }
|
| 217 |
+
}
|
| 218 |
+
}
|
| 219 |
+
let n = syms.count
|
| 220 |
+
if n < 2 { out.append(contentsOf: syms); return }
|
| 221 |
+
|
| 222 |
+
// 2. merge by rank, leftmost first among equal ranks (binary heap on (rank, pos))
|
| 223 |
+
var next = [Int32](repeating: 0, count: n), prev = [Int32](repeating: 0, count: n)
|
| 224 |
+
var alive = [Bool](repeating: true, count: n)
|
| 225 |
+
for i in 0..<n { next[i] = Int32(i + 1); prev[i] = Int32(i - 1) }
|
| 226 |
+
var heap: [HeapEntry] = []
|
| 227 |
+
heap.reserveCapacity(n)
|
| 228 |
+
@inline(__always) func less(_ a: HeapEntry, _ b: HeapEntry) -> Bool {
|
| 229 |
+
a.rank != b.rank ? a.rank < b.rank : a.pos < b.pos
|
| 230 |
+
}
|
| 231 |
+
func push(_ e: HeapEntry) {
|
| 232 |
+
heap.append(e)
|
| 233 |
+
var c = heap.count - 1
|
| 234 |
+
while c > 0 {
|
| 235 |
+
let p = (c - 1) >> 1
|
| 236 |
+
if less(heap[c], heap[p]) { heap.swapAt(c, p); c = p } else { break }
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
func pop() -> HeapEntry {
|
| 240 |
+
let top = heap[0]
|
| 241 |
+
let last = heap.removeLast()
|
| 242 |
+
if !heap.isEmpty {
|
| 243 |
+
heap[0] = last
|
| 244 |
+
var p = 0
|
| 245 |
+
while true {
|
| 246 |
+
let l = 2 * p + 1, r = l + 1
|
| 247 |
+
var m = p
|
| 248 |
+
if l < heap.count, less(heap[l], heap[m]) { m = l }
|
| 249 |
+
if r < heap.count, less(heap[r], heap[m]) { m = r }
|
| 250 |
+
if m == p { break }
|
| 251 |
+
heap.swapAt(p, m); p = m
|
| 252 |
+
}
|
| 253 |
+
}
|
| 254 |
+
return top
|
| 255 |
+
}
|
| 256 |
+
for i in 0..<(n - 1) {
|
| 257 |
+
if let m = merges[GemmaTokenizer.pairKey(syms[i], syms[i + 1])] {
|
| 258 |
+
push(HeapEntry(rank: m.rank, pos: Int32(i), id: m.id))
|
| 259 |
+
}
|
| 260 |
+
}
|
| 261 |
+
while !heap.isEmpty {
|
| 262 |
+
let e = pop()
|
| 263 |
+
let i = Int(e.pos)
|
| 264 |
+
if !alive[i] { continue }
|
| 265 |
+
let j = Int(next[i])
|
| 266 |
+
if j >= n || !alive[j] { continue }
|
| 267 |
+
guard let m = merges[GemmaTokenizer.pairKey(syms[i], syms[j])], m.rank == e.rank else { continue }
|
| 268 |
+
syms[i] = m.id
|
| 269 |
+
alive[j] = false
|
| 270 |
+
next[i] = next[j]
|
| 271 |
+
if Int(next[i]) < n { prev[Int(next[i])] = Int32(i) }
|
| 272 |
+
if prev[i] >= 0, let m2 = merges[GemmaTokenizer.pairKey(syms[Int(prev[i])], syms[i])] {
|
| 273 |
+
push(HeapEntry(rank: m2.rank, pos: prev[i], id: m2.id))
|
| 274 |
+
}
|
| 275 |
+
if Int(next[i]) < n, let m2 = merges[GemmaTokenizer.pairKey(syms[i], syms[Int(next[i])])] {
|
| 276 |
+
push(HeapEntry(rank: m2.rank, pos: Int32(i), id: m2.id))
|
| 277 |
+
}
|
| 278 |
+
}
|
| 279 |
+
for i in 0..<n where alive[i] { out.append(syms[i]) }
|
| 280 |
+
}
|
| 281 |
+
}
|
swift/Sources/eg2-cli/main.swift
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Copyright 2026 Everpaper. Licensed under the Apache License, Version 2.0 (see ../../../LICENSE).
|
| 2 |
+
//
|
| 3 |
+
// eg2-cli — command-line front end for the EmbeddingGemma2CoreAI library.
|
| 4 |
+
//
|
| 5 |
+
// eg2-cli embed <bundle> [--compute ane|gpu|cpu] [--kind document|search_query|...] [--dim 768] "text" ...
|
| 6 |
+
// eg2-cli smoke <bundle> <smoke-reference.json> [--compute ane|gpu|cpu] [--cleanup-temp]
|
| 7 |
+
// eg2-cli tokcheck <bundle> <tokenizer-cases.json>
|
| 8 |
+
// eg2-cli latency <bundle> [--compute ane|gpu|cpu] [--trials 50]
|
| 9 |
+
// eg2-cli prewarm <bundle> [--compute ane|gpu|cpu]
|
| 10 |
+
// eg2-cli cleanup-temp (removes this process's MPSGraph temp folders; mostly useful after the others)
|
| 11 |
+
//
|
| 12 |
+
// Every command accepts --cleanup-temp to delete the MPSGraph temp folders this process created, after the
|
| 13 |
+
// model is released (see EmbeddingGemma2.removeOwnMPSGraphTempFiles).
|
| 14 |
+
|
| 15 |
+
import EmbeddingGemma2CoreAI
|
| 16 |
+
import Foundation
|
| 17 |
+
|
| 18 |
+
var args = Array(CommandLine.arguments.dropFirst())
|
| 19 |
+
|
| 20 |
+
@MainActor func usage() -> Never {
|
| 21 |
+
print("""
|
| 22 |
+
usage: eg2-cli <embed|smoke|tokcheck|latency|prewarm|cleanup-temp> <bundle> [...] [--compute ane|gpu|cpu] [--cleanup-temp]
|
| 23 |
+
""")
|
| 24 |
+
exit(2)
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
@MainActor func takeOption(_ name: String) -> String? {
|
| 28 |
+
guard let i = args.firstIndex(of: name), i + 1 < args.count else { return nil }
|
| 29 |
+
let v = args[i + 1]
|
| 30 |
+
args.removeSubrange(i...(i + 1))
|
| 31 |
+
return v
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
@MainActor func takeFlag(_ name: String) -> Bool {
|
| 35 |
+
guard let i = args.firstIndex(of: name) else { return false }
|
| 36 |
+
args.remove(at: i)
|
| 37 |
+
return true
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
func fmt(_ x: Double, _ digits: Int = 2) -> String { String(format: "%.\(digits)f", x) }
|
| 41 |
+
|
| 42 |
+
func cpuSeconds() -> Double {
|
| 43 |
+
var u = rusage()
|
| 44 |
+
getrusage(RUSAGE_SELF, &u)
|
| 45 |
+
return Double(u.ru_utime.tv_sec) + Double(u.ru_utime.tv_usec) / 1e6 + Double(u.ru_stime.tv_sec) + Double(u.ru_stime.tv_usec) / 1e6
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
/// Peak physical footprint (MB) and the Neural Engine footprint (MB) of this process (proc_pid_rusage, RUSAGE_INFO_V6).
|
| 49 |
+
func footprints() -> (physMB: Double, neuralMB: Double) {
|
| 50 |
+
var r = rusage_info_v6()
|
| 51 |
+
let kr = withUnsafeMutablePointer(to: &r) { p -> Int32 in
|
| 52 |
+
p.withMemoryRebound(to: rusage_info_t?.self, capacity: 1) { proc_pid_rusage(getpid(), RUSAGE_INFO_V6, $0) }
|
| 53 |
+
}
|
| 54 |
+
guard kr == 0 else { return (-1, -1) }
|
| 55 |
+
return (Double(r.ri_lifetime_max_phys_footprint) / 1048576, Double(r.ri_neural_footprint) / 1048576)
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
guard !args.isEmpty else { usage() }
|
| 59 |
+
let cmd = args.removeFirst()
|
| 60 |
+
let cleanup = takeFlag("--cleanup-temp")
|
| 61 |
+
let compute = ComputePreference(rawValue: takeOption("--compute") ?? "ane") ?? .neuralEngine
|
| 62 |
+
|
| 63 |
+
@MainActor func finish(_ code: Int32) -> Never {
|
| 64 |
+
if cleanup {
|
| 65 |
+
let r = EmbeddingGemma2.removeOwnMPSGraphTempFiles()
|
| 66 |
+
print("cleanup-temp: removed \(r.folders) MPSGraph temp folder(s), \(fmt(Double(r.bytes) / 1048576, 1)) MB")
|
| 67 |
+
}
|
| 68 |
+
exit(code)
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
if cmd == "cleanup-temp" { finish(0) }
|
| 72 |
+
guard let bundlePath = args.first else { usage() }
|
| 73 |
+
args.removeFirst()
|
| 74 |
+
let bundle = URL(fileURLWithPath: bundlePath)
|
| 75 |
+
|
| 76 |
+
do {
|
| 77 |
+
switch cmd {
|
| 78 |
+
case "embed":
|
| 79 |
+
let kind = takeOption("--kind") ?? "document"
|
| 80 |
+
let dim = Int(takeOption("--dim") ?? "768") ?? 768
|
| 81 |
+
let eg2 = try await EmbeddingGemma2(bundle: bundle, compute: compute)
|
| 82 |
+
for text in args {
|
| 83 |
+
let v = try await eg2.embed(text, kind: kind == "none" ? nil : kind, dim: dim)
|
| 84 |
+
let json: [String: Any] = ["text": text, "kind": kind, "dim": dim, "embedding": v.map { Double($0) }]
|
| 85 |
+
print(String(decoding: try JSONSerialization.data(withJSONObject: json), as: UTF8.self))
|
| 86 |
+
}
|
| 87 |
+
_ = consume eg2
|
| 88 |
+
finish(0)
|
| 89 |
+
|
| 90 |
+
case "smoke":
|
| 91 |
+
guard let refPath = args.first else { usage() }
|
| 92 |
+
struct Ref: Decodable { let text: String; let kind: String; let embedding: [Float] }
|
| 93 |
+
struct RefFile: Decodable { let threshold_cosine: Double; let cases: [Ref] }
|
| 94 |
+
let ref = try JSONDecoder().decode(RefFile.self, from: Data(contentsOf: URL(fileURLWithPath: refPath)))
|
| 95 |
+
let eg2 = try await EmbeddingGemma2(bundle: bundle, compute: compute)
|
| 96 |
+
print("loaded in \(fmt(eg2.loadSeconds, 1)) s (tokenizer \(fmt(eg2.tokenizerLoadSeconds, 2)) s), compute=\(compute.rawValue), buckets=\(eg2.buckets)")
|
| 97 |
+
var ok = true
|
| 98 |
+
for c in ref.cases {
|
| 99 |
+
let v = try await eg2.embed(c.text, kind: c.kind)
|
| 100 |
+
let cos = cosineSimilarity(v, c.embedding)
|
| 101 |
+
let pass = cos >= ref.threshold_cosine && v.allSatisfy { $0.isFinite }
|
| 102 |
+
ok = ok && pass
|
| 103 |
+
print("\(pass ? "PASS" : "FAIL") cosine \(fmt(cos, 6)) (threshold \(ref.threshold_cosine)) \(c.kind): \(c.text.prefix(60))")
|
| 104 |
+
}
|
| 105 |
+
let fp = footprints()
|
| 106 |
+
print("peak footprint \(fmt(fp.physMB, 0)) MB, neural footprint \(fmt(fp.neuralMB, 0)) MB")
|
| 107 |
+
print(ok ? "SMOKE PASS" : "SMOKE FAIL")
|
| 108 |
+
_ = consume eg2
|
| 109 |
+
finish(ok ? 0 : 1)
|
| 110 |
+
|
| 111 |
+
case "tokcheck":
|
| 112 |
+
guard let casesPath = args.first else { usage() }
|
| 113 |
+
struct Case: Decodable { let cat: String; let kind: String; let text: String; let ids: [Int32] }
|
| 114 |
+
let cases = try JSONDecoder().decode([Case].self, from: Data(contentsOf: URL(fileURLWithPath: casesPath)))
|
| 115 |
+
let config = try JSONDecoder().decode(BundleConfig.self, from: Data(contentsOf: bundle.appendingPathComponent("config.json")))
|
| 116 |
+
let t0 = Date()
|
| 117 |
+
let tok = try GemmaTokenizer(contentsOf: bundle.appendingPathComponent("tokenizer/tokenizer.json"))
|
| 118 |
+
print("tokenizer loaded in \(fmt(Date().timeIntervalSince(t0), 2)) s, vocab \(tok.vocabSize)")
|
| 119 |
+
let prefixes = ["q": "task: search result | query: ", "d": "title: none | text: ", "raw": ""]
|
| 120 |
+
var same = 0, totalIDs = 0, shown = 0
|
| 121 |
+
var perCat: [String: (Int, Int)] = [:]
|
| 122 |
+
let t1 = Date()
|
| 123 |
+
let cpu0 = cpuSeconds()
|
| 124 |
+
for c in cases {
|
| 125 |
+
let ids = tok.encodeWithSpecials(prefixes[c.kind]! + c.text)
|
| 126 |
+
let ok = ids == c.ids
|
| 127 |
+
totalIDs += c.ids.count
|
| 128 |
+
var e = perCat[c.cat] ?? (0, 0)
|
| 129 |
+
e.1 += 1
|
| 130 |
+
if ok {
|
| 131 |
+
same += 1; e.0 += 1
|
| 132 |
+
} else if shown < 10 {
|
| 133 |
+
shown += 1
|
| 134 |
+
print("DIFFERENT [\(c.cat)] \(c.text.prefix(60).debugDescription): got \(ids.prefix(12)) expected \(c.ids.prefix(12))")
|
| 135 |
+
}
|
| 136 |
+
perCat[c.cat] = e
|
| 137 |
+
}
|
| 138 |
+
let secs = Date().timeIntervalSince(t1), cpu = cpuSeconds() - cpu0
|
| 139 |
+
for (cat, e) in perCat.sorted(by: { $0.key < $1.key }) { print(" \(cat): \(e.0)/\(e.1)") }
|
| 140 |
+
print("TOKCHECK \(same)/\(cases.count) texts identical to Hugging Face tokenizers (\(totalIDs) ids) in \(fmt(secs, 2)) s wall / \(fmt(cpu, 2)) s CPU")
|
| 141 |
+
print("config bos/eos/pad \(config.bos_token_id)/\(config.eos_token_id)/\(config.pad_token_id) == tokenizer \(tok.bosID)/\(tok.eosID)/\(tok.padID)")
|
| 142 |
+
finish(same == cases.count ? 0 : 1)
|
| 143 |
+
|
| 144 |
+
case "latency":
|
| 145 |
+
let trials = Int(takeOption("--trials") ?? "50") ?? 50
|
| 146 |
+
let eg2 = try await EmbeddingGemma2(bundle: bundle, compute: compute)
|
| 147 |
+
print("loaded in \(fmt(eg2.loadSeconds, 1)) s, compute=\(compute.rawValue)")
|
| 148 |
+
let warm = try await eg2.prewarm()
|
| 149 |
+
print("prewarm (one call per bucket) \(fmt(warm, 2)) s")
|
| 150 |
+
print("bucket p50 ms min ms p90 ms CPU ms/call tok/s")
|
| 151 |
+
for S in eg2.buckets {
|
| 152 |
+
var ids = [Int32](repeating: 0, count: S)
|
| 153 |
+
ids[0] = eg2.config.bos_token_id; ids[S - 1] = eg2.config.eos_token_id
|
| 154 |
+
for i in 1..<(S - 1) { ids[i] = Int32(1000 + (i * 7919) % 200000) }
|
| 155 |
+
for _ in 0..<3 { _ = try await eg2.embed(ids: ids, bucket: S) }
|
| 156 |
+
var ts: [Double] = []
|
| 157 |
+
let cpu0 = cpuSeconds()
|
| 158 |
+
for _ in 0..<trials {
|
| 159 |
+
let t = Date()
|
| 160 |
+
_ = try await eg2.embed(ids: ids, bucket: S)
|
| 161 |
+
ts.append(Date().timeIntervalSince(t) * 1000)
|
| 162 |
+
}
|
| 163 |
+
let cpu = (cpuSeconds() - cpu0) / Double(trials) * 1000
|
| 164 |
+
ts.sort()
|
| 165 |
+
let p50 = ts[ts.count / 2], p90 = ts[min(ts.count - 1, Int(Double(ts.count) * 0.9))]
|
| 166 |
+
print(String(format: "%5d %7.2f %7.2f %7.2f %11.2f %6.0f", S, p50, ts[0], p90, cpu, Double(S) / p50 * 1000))
|
| 167 |
+
}
|
| 168 |
+
let fp = footprints()
|
| 169 |
+
print("peak footprint \(fmt(fp.physMB, 0)) MB, neural footprint \(fmt(fp.neuralMB, 0)) MB (\(fp.neuralMB > 0 ? "Neural Engine in use" : "Neural Engine NOT used"))")
|
| 170 |
+
_ = consume eg2
|
| 171 |
+
finish(0)
|
| 172 |
+
|
| 173 |
+
case "prewarm":
|
| 174 |
+
let t0 = Date()
|
| 175 |
+
let eg2 = try await EmbeddingGemma2(bundle: bundle, compute: compute)
|
| 176 |
+
let warm = try await eg2.prewarm()
|
| 177 |
+
print("load \(fmt(eg2.loadSeconds, 1)) s + prewarm \(fmt(warm, 2)) s = ready in \(fmt(Date().timeIntervalSince(t0), 1)) s")
|
| 178 |
+
_ = consume eg2
|
| 179 |
+
finish(0)
|
| 180 |
+
|
| 181 |
+
default:
|
| 182 |
+
usage()
|
| 183 |
+
}
|
| 184 |
+
} catch {
|
| 185 |
+
print("error: \(error)")
|
| 186 |
+
finish(1)
|
| 187 |
+
}
|
tests/make_smoke_reference.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# Copyright 2026 Everpaper. Licensed under the Apache License, Version 2.0 (see ../LICENSE).
|
| 3 |
+
"""Regenerate `smoke-reference.json`: fp32 reference embeddings for three fictional sentences, computed with
|
| 4 |
+
sentence-transformers on the ORIGINAL google/embeddinggemma-2 checkpoint (text only, CPU, float32).
|
| 5 |
+
|
| 6 |
+
python3 make_smoke_reference.py --model google/embeddinggemma-2 --out smoke-reference.json
|
| 7 |
+
|
| 8 |
+
Needs: sentence-transformers >= 6.1, transformers >= 5.19, torch, torchvision and pillow (the checkpoint's processor
|
| 9 |
+
is multimodal and imports them even for text). The shipped file was produced with sentence-transformers 6.1.0 /
|
| 10 |
+
transformers 5.19.0 / torch 2.14.1; torchvision 0.29.1 is the matching version (see ../python/requirements.lock).
|
| 11 |
+
"""
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import argparse
|
| 15 |
+
import json
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
|
| 18 |
+
CASES = [
|
| 19 |
+
{"kind": "search_query", "text": "When does the warehouse lease in Brindlemoor expire?"},
|
| 20 |
+
{"kind": "document", "text": "The warehouse lease in Brindlemoor runs until the last day of March; the landlord agreed to fix the window before then."},
|
| 21 |
+
{"kind": "document", "text": "Lista della spesa: latte d'avena, due limoni, riso basmati, coriandolo e croccantini per il cane."},
|
| 22 |
+
]
|
| 23 |
+
PROMPT_NAME = {"search_query": "query", "document": "document"}
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def main() -> None:
|
| 27 |
+
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
| 28 |
+
ap.add_argument("--model", default="google/embeddinggemma-2", help="HF id or local checkpoint folder")
|
| 29 |
+
ap.add_argument("--out", default="smoke-reference.json")
|
| 30 |
+
ap.add_argument("--threshold", type=float, default=0.999)
|
| 31 |
+
args = ap.parse_args()
|
| 32 |
+
import sentence_transformers
|
| 33 |
+
import torch
|
| 34 |
+
import transformers
|
| 35 |
+
from sentence_transformers import SentenceTransformer
|
| 36 |
+
|
| 37 |
+
model = SentenceTransformer(args.model, device="cpu", model_kwargs={"torch_dtype": torch.float32})
|
| 38 |
+
out = {
|
| 39 |
+
"reference": "sentence-transformers fp32 on CPU, original checkpoint, text only",
|
| 40 |
+
"versions": {"sentence_transformers": sentence_transformers.__version__, "transformers": transformers.__version__,
|
| 41 |
+
"torch": torch.__version__},
|
| 42 |
+
"threshold_cosine": args.threshold,
|
| 43 |
+
"cases": [],
|
| 44 |
+
}
|
| 45 |
+
for c in CASES:
|
| 46 |
+
v = model.encode(c["text"], prompt_name=PROMPT_NAME[c["kind"]], normalize_embeddings=True, convert_to_numpy=True)
|
| 47 |
+
out["cases"].append({**c, "embedding": [round(float(x), 7) for x in v.tolist()]})
|
| 48 |
+
print(c["kind"], len(v), c["text"][:50])
|
| 49 |
+
Path(args.out).write_text(json.dumps(out, ensure_ascii=False, indent=1), encoding="utf-8")
|
| 50 |
+
print("wrote", args.out)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
if __name__ == "__main__":
|
| 54 |
+
main()
|
tests/make_tokenizer_cases.py
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# Copyright 2026 Everpaper. Licensed under the Apache License, Version 2.0 (see ../LICENSE).
|
| 3 |
+
"""Generate the tokenizer conformance fixture `tokenizer-cases.json`: >= 3,000 deterministic, FICTIONAL texts that
|
| 4 |
+
stress the Gemma tokenizer (CRLF, NFD accents, emoji ZWJ / skin tones / flags / keycaps, Arabic with diacritics, Thai,
|
| 5 |
+
Hindi, CJK, whitespace runs > 31, tabs, control characters, special-token look-alikes, very long inputs), with the ids
|
| 6 |
+
produced by the Hugging Face `tokenizers` library for `<bos> + encode(prefix + text) + <eos>` (no truncation).
|
| 7 |
+
|
| 8 |
+
python3 make_tokenizer_cases.py --tokenizer ../model/tokenizer/tokenizer.json --out tokenizer-cases.json
|
| 9 |
+
|
| 10 |
+
The people, companies, events and numbers below are invented. Most place names are invented too; a few real
|
| 11 |
+
place names and words appear in the accent and script stress strings (e.g. Łódź, Đà Nẵng, Ångström).
|
| 12 |
+
"""
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import argparse
|
| 16 |
+
import json
|
| 17 |
+
import random
|
| 18 |
+
import unicodedata
|
| 19 |
+
from pathlib import Path
|
| 20 |
+
|
| 21 |
+
PREFIX = {"q": "task: search result | query: ", "d": "title: none | text: ", "raw": ""}
|
| 22 |
+
|
| 23 |
+
FIRST = ["Orla", "Teodor", "Mirela", "Kasimir", "Ysolde", "Bram", "Fenna", "Ruaridh", "Ottilie", "Severin", "Jolan",
|
| 24 |
+
"Liesl", "Corvin", "Tamsin", "Ilse", "Quillon", "Saskia", "Elowen", "Thaddeus", "Perpetua"]
|
| 25 |
+
LAST = ["Vantrell", "Dunholm", "Brecklow", "Szarvas", "Penhallow", "Lindqvar", "Castelmoro", "Ravensby", "Quarterman",
|
| 26 |
+
"Halloran-Bey", "Møllergaard", "Åkerlund", "Nowakówna", "Çelikkaya", "Đặng", "O'Rourke-Vale"]
|
| 27 |
+
ORGS = ["Lumenvale", "Brightwater Mills", "Okkam Freight", "Velmora Labs", "Quillfeather", "Saltmarsh & Co",
|
| 28 |
+
"Norrow Analytics", "Cobaltine", "Haverly Bikes", "Studio Morello", "Tavola Verde", "Pinecrest Dental"]
|
| 29 |
+
TOWNS = ["Brindlemoor", "Castel Ferrino", "Ostwick", "Valdorna", "Pellham Cross", "Marrowgate", "San Lucetto",
|
| 30 |
+
"Hadleigh Fen", "Rocca Bruna", "Thistleby", "Søndervik", "Łęczyca Nowa", "Çamlıdere", "Žďár Horní"]
|
| 31 |
+
THINGS = ["the quarterly invoice", "the boiler service", "the onboarding checklist", "the API rate limits",
|
| 32 |
+
"the warehouse lease", "the beta feedback", "the dentist appointment", "the hiring plan", "the tax return",
|
| 33 |
+
"the server migration", "the book club", "the roof estimate", "the ferry timetable"]
|
| 34 |
+
COSE = ["la fattura trimestrale", "la revisione della caldaia", "il contratto d'affitto", "il preventivo del tetto",
|
| 35 |
+
"la riunione di condominio", "il volo per Valdorna", "la visita dal dentista", "il piano ferie",
|
| 36 |
+
"il trasloco di sabato", "il corso di ceramica"]
|
| 37 |
+
DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
|
| 38 |
+
MONTHS = ["January", "March", "May", "July", "September", "October", "November", "December"]
|
| 39 |
+
|
| 40 |
+
EN_TPL = [
|
| 41 |
+
"{p} from {o} moved {t} to {d} because the team in {town} is short-staffed this month.",
|
| 42 |
+
"The budget for {t} is {n},{m:03d} and {p} wants a second quote before {d}.",
|
| 43 |
+
"Remember to call {p} about {t}; the office in {town} closes at {h}:30.",
|
| 44 |
+
"{o} signed the renewal: {n} seats at {m} per seat, billed every {mo}.",
|
| 45 |
+
"On {d} the deploy failed twice because a config flag was read as seconds instead of milliseconds.",
|
| 46 |
+
"Minutes: {p} presented the roadmap, {p2} raised concerns about {t}, and the decision was postponed to {mo}.",
|
| 47 |
+
"Our p99 latency rose to {m} ms after the {d} release; rollback is ready if it passes {h}0 ms.",
|
| 48 |
+
"Grocery list: oat milk, two lemons, basmati rice, coriander, and dog treats for Biscuit.",
|
| 49 |
+
"Flight {o2}{n} to {town} departs at {h}:{m2:02d}; seat {m2}C, one checked bag.",
|
| 50 |
+
"Draft reply to {p}: thanks for the notes on {t}, we will send the revised version by {d}.",
|
| 51 |
+
"The landlord in {town} agreed to fix the window before {mo}; the rent stays at {n},{m:03d} a year.",
|
| 52 |
+
]
|
| 53 |
+
IT_TPL = [
|
| 54 |
+
"{p} di {o} ha spostato {c} a {d} perché l'ufficio di {town} è sotto organico.",
|
| 55 |
+
"Il preventivo per {c} è di {n}.{m:03d} euro e {p} vuole un secondo parere entro {d}.",
|
| 56 |
+
"Ricordati di chiamare {p} per {c}: lo studio di {town} chiude alle {h}:30.",
|
| 57 |
+
"Verbale: {p} ha presentato il piano, {p2} ha sollevato dubbi su {c} e la decisione slitta a {mo}.",
|
| 58 |
+
"La caldaia perde ancora; il tecnico dice che la valvola costa {m} euro e serve una settimana.",
|
| 59 |
+
"Lista della spesa: latte d'avena, due limoni, riso basmati, coriandolo e croccantini per il cane.",
|
| 60 |
+
"Il volo per {town} parte alle {h}:{m2:02d}; posto {m2}C, un bagaglio in stiva. Perché così presto?",
|
| 61 |
+
]
|
| 62 |
+
OTHER = {
|
| 63 |
+
"de": ["Die Heizung in {town} fällt seit {d} ständig aus; {p} ruft morgen den Installateur an.",
|
| 64 |
+
"Größere Änderungen am Vertrag mit {o} müssen bis {mo} unterschrieben werden."],
|
| 65 |
+
"fr": ["{p} a déplacé la réunion à {h} h parce que le train pour {town} était en retard.",
|
| 66 |
+
"Le devis de {o} s'élève à {n} {m:03d} € ; l'échéance est fixée à {mo}."],
|
| 67 |
+
"es": ["¿Quién se encarga de {t} en {o}? {p} dijo que el plazo termina el {d}.",
|
| 68 |
+
"El alquiler en {town} sube a {n}.{m:03d} al año; ¡hay que renegociar antes de {mo}!"],
|
| 69 |
+
"pt": ["{p} avisou que a entrega de {o} chega a {town} na {d}, por volta das {h}h.",
|
| 70 |
+
"O orçamento para {t} ficou em {n}.{m:03d}; a decisão foi adiada para {mo}."],
|
| 71 |
+
"nl": ["{p} belt {d} met {o} over {t}; het kantoor in {town} sluit om {h}:30.",
|
| 72 |
+
"De huur in {town} blijft {n}.{m:03d} per jaar als het raam vóór {mo} gemaakt is."],
|
| 73 |
+
"pl": ["{p} przeniósł spotkanie na {d}, bo zespół w {town} ma za mało ludzi.",
|
| 74 |
+
"Faktura od {o} opiewa na {n} {m:03d} zł; termin płatności mija w {mo}."],
|
| 75 |
+
"tr": ["{p}, {o} ile yapılan sözleşmeyi {mo} ayına kadar imzalamalı; ofis {h}:30'da kapanıyor.",
|
| 76 |
+
"{town}'daki kira yılda {n}.{m:03d} olarak kalıyor; pencere {mo} öncesi tamir edilecek."],
|
| 77 |
+
"vi": ["{p} đã dời cuộc họp sang {d} vì đội ở {town} đang thiếu người.",
|
| 78 |
+
"Hóa đơn của {o} là {n}.{m:03d}; hạn thanh toán là tháng {m2}."],
|
| 79 |
+
"ru": ["{p} перенёс встречу на {d}, потому что в {town} не хватает людей.",
|
| 80 |
+
"Счёт от {o} на {n} {m:03d}; оплатить нужно до {mo}."],
|
| 81 |
+
"el": ["Ο {p} μετέφερε τη συνάντηση την {d} επειδή η ομάδα στο {town} είναι λίγη.",
|
| 82 |
+
"Το τιμολόγιο της {o} είναι {n}.{m:03d}· πληρωμή μέχρι {mo}."],
|
| 83 |
+
"he": ["{p} הזיז את הפגישה ל-{d} כי הצוות ב-{town} קטן מדי.",
|
| 84 |
+
"החשבונית מ-{o} היא {n},{m:03d}; לשלם עד {mo}."],
|
| 85 |
+
"ar": ["نَقَلَ {p} الاجْتِماعَ إلى يَوْمِ {d} لِأَنَّ الفَريقَ في {town} قَليلُ العَدَدِ هذا الشَّهْرَ.",
|
| 86 |
+
"الفاتُورَةُ مِنْ {o} بِقيمَةِ {n}٬{m:03d}؛ يَجِبُ الدَّفْعُ قَبْلَ {mo}.",
|
| 87 |
+
"تَذَكَّرْ أَنْ تَتَّصِلَ بِـ{p} بِخُصُوصِ {t}؛ المَكْتَبُ يُغْلِقُ السّاعَةَ {h}:30."],
|
| 88 |
+
"fa": ["{p} جلسه را به {d} موکول کرد چون تیم {town} این ماه کمنیرو است.",
|
| 89 |
+
"صورتحساب {o} به مبلغ {n}٬{m:03d} است؛ پرداخت تا {mo}."],
|
| 90 |
+
"th": ["{p} เลื่อนการประชุมไปวัน{d} เพราะทีมที่{town}มีคนไม่พอในเดือนนี้",
|
| 91 |
+
"ใบแจ้งหนี้จาก {o} เป็นเงิน {n},{m:03d} บาท ต้องชำระก่อน{mo}",
|
| 92 |
+
"อย่าลืมโทรหา{p}เรื่อง{t} สำนักงานปิดเวลา {h}:30 น."],
|
| 93 |
+
"hi": ["{p} ने बैठक {d} को टाल दी क्योंकि {town} की टीम में इस महीने लोग कम हैं।",
|
| 94 |
+
"{o} का चालान {n},{m:03d} रुपये का है; भुगतान {mo} से पहले करना है।",
|
| 95 |
+
"{p} को {t} के बारे में फ़ोन करना याद रखें; दफ़्तर {h}:30 बजे बंद होता है।"],
|
| 96 |
+
"bn": ["{p} সভাটি {d}-এ পিছিয়ে দিয়েছেন কারণ {town}-এর দলে এই মাসে লোক কম।",
|
| 97 |
+
"{o}-এর চালান {n},{m:03d} টাকার; {mo}-এর আগে পরিশোধ করতে হবে।"],
|
| 98 |
+
"ta": ["{p} கூட்டத்தை {d} அன்று ஒத்திவைத்தார்; {town} குழுவில் இந்த மாதம் ஆட்கள் குறைவு.",
|
| 99 |
+
"{o} விலைப்பட்டியல் {n},{m:03d}; {mo}க்கு முன் செலுத்த வேண்டும்."],
|
| 100 |
+
"zh": ["{p}把会议改到了{d},因为{town}的团队这个月人手不够。",
|
| 101 |
+
"{o}的发票金额是{n},{m:03d}元,必须在{mo}之前付款。",
|
| 102 |
+
"记得给{p}打电话谈{t};办公室{h}点半关门。"],
|
| 103 |
+
"zh_tw": ["{p}把會議改到了{d},因為{town}的團隊這個月人手不夠。",
|
| 104 |
+
"{o}的發票金額是{n},{m:03d}元,必須在{mo}之前付款。"],
|
| 105 |
+
"ja": ["{p}さんは{town}のチームが今月人手不足なので、会議を{d}に延期しました。",
|
| 106 |
+
"{o}からの請求書は{n},{m:03d}円で、{mo}までに支払う必要があります。",
|
| 107 |
+
"{t}について{p}さんに電話するのを忘れないで。オフィスは{h}時半に閉まります。"],
|
| 108 |
+
"ko": ["{p}은(는) {town} 팀이 이번 달 인력이 부족해서 회의를 {d}로 미뤘습니다.",
|
| 109 |
+
"{o}의 청구서는 {n},{m:03d}원이며 {mo} 전에 결제해야 합니다.",
|
| 110 |
+
"{t} 건으로 {p}에게 전화하는 것 잊지 마세요. 사무실은 {h}시 30분에 닫습니다."],
|
| 111 |
+
"ka": ["{p}-მ��� შეხვედრა {d}-ზე გადადო, რადგან {town}-ის გუნდი ამ თვეში მცირეა."],
|
| 112 |
+
"hy": ["{p}-ը հանդիպումը տեղափոխեց {d}, քանի որ {town}-ի թիմը այս ամիս փոքր է։"],
|
| 113 |
+
"am": ["{p} ስብሰባውን ወደ {d} አዛወረው፣ ምክንያቱም በ{town} ያለው ቡድን በዚህ ወር ትንሽ ነው።"],
|
| 114 |
+
"uk": ["{p} переніс зустріч на {d}, бо команді в {town} бракує людей."],
|
| 115 |
+
"cs": ["{p} přesunul schůzku na {d}, protože tým v {town} má tento měsíc málo lidí."],
|
| 116 |
+
"fi": ["{p} siirsi kokouksen {d}, koska {town}n tiimi on tässä kuussa alimiehitetty."],
|
| 117 |
+
"hu": ["{p} áthelyezte a megbeszélést {d}-ra, mert a {town}-i csapat ebben a hónapban kevés."],
|
| 118 |
+
"sw": ["{p} alihamisha mkutano hadi {d} kwa sababu timu ya {town} ina watu wachache mwezi huu."],
|
| 119 |
+
"id": ["{p} memindahkan rapat ke {d} karena tim di {town} kekurangan orang bulan ini."],
|
| 120 |
+
}
|
| 121 |
+
QUERIES = ["When is {t} due?", "Who handles {t} at {o}?", "How much did {o} quote for {t}?",
|
| 122 |
+
"What did {p} decide about the {town} office?", "Which flight goes to {town} on {d}?",
|
| 123 |
+
"Why was the deploy rolled back?", "What is the rent in {town}?", "Quando scade {c}?",
|
| 124 |
+
"Chi si occupa di {c} a {town}?", "¿Cuánto cuesta {t}?", "Wann endet die Testphase von {o}?",
|
| 125 |
+
"{town} 사무실 임대료는 얼마인가요?", "{o}の請求書はいつまで?", "{p}是谁?", "ما هو إيجار {town}؟",
|
| 126 |
+
"{town}में किराया कितना है?", "ค่าเช่าใน{town}เท่าไหร่", "p99 latency", "SOC 2", "INC-{m}", "wifi password"]
|
| 127 |
+
CODE = [
|
| 128 |
+
"def backoff(attempt: int, base: float = 0.5, cap: float = {m}.0) -> float:\n return min(cap, base * 2 ** attempt)\n",
|
| 129 |
+
"func retryDelay(_ attempt: Int) -> Duration {{\n\t.milliseconds(min({m}_000, 500 << attempt))\n}}\n",
|
| 130 |
+
"SELECT customer_id, SUM(amount) AS total\nFROM invoices\nWHERE issued_at >= '2026-{m2:02d}-01'\nGROUP BY customer_id\nHAVING SUM(amount) > {n}{m};\n",
|
| 131 |
+
"const queue = new Map();\nexport function enqueue(id, payload) {{\n if (queue.size > {m}) throw new Error('queue full');\n queue.set(id, {{ payload, tries: 0 }});\n}}\n",
|
| 132 |
+
"#!/bin/sh\nset -eu\nfor f in logs/*.gz; do\n zcat \"$f\" | grep -c 'status={h}0' || true\ndone\n",
|
| 133 |
+
"{{\n \"service\": \"{o2}-webhooks\",\n \"region\": \"ams-1\",\n \"retries\": {h},\n \"timeout_ms\": {m}0\n}}\n",
|
| 134 |
+
"class DeliveryQueue:\n def __init__(self, limit={m}):\n self.items = []\n\n def push(self, item):\n if len(self.items) >= self.limit:\n raise OverflowError(item)\n",
|
| 135 |
+
"fn parse_port(s: &str) -> Result<u16, String> {{\n s.trim().parse::<u16>().map_err(|e| format!(\"bad port {{s}}: {{e}}\"))\n}}\n",
|
| 136 |
+
"<div class=\"card\">\r\n\t<span id=\"x{m}\"> </span>\r\n</div>\r\n",
|
| 137 |
+
"x = [i**2 for i in range({m}) if i % {h} == 0] # noqa\n\n\n\n",
|
| 138 |
+
]
|
| 139 |
+
EMOJI = ["👩💻", "👨👩👧👦", "🏳️🌈", "🏴☠️", "👍🏽", "🧑🏿🚀", "🇮🇹", "🇯🇵", "🇺🇳", "1️⃣", "#️⃣", "*️⃣", "❤️", "❤", "🫶🏼",
|
| 140 |
+
"👩🏻❤️💋👨🏾", "🏃♀️", "🧘🏽♂️", "🫠", "🐈⬛", "🐻❄️", "😶🌫️", "🇿🇿", "🏴", "✌🏻", "☝🏿", "🅰️", "™", "©️", "⚠️"]
|
| 141 |
+
LOOKALIKES = ["<bos>", "<eos>", "<pad>", "<unk>", "<mask>", "<|tool>", "<tool|>", "<|tool_call>", "<tool_call|>",
|
| 142 |
+
"<|tool_response>", "<tool_response|>", "<|\"|>", "<|think|>", "<|channel>", "<channel|>", "<|turn>",
|
| 143 |
+
"<turn|>", "<|image>", "<|audio>", "<|image|>", "<|audio|>", "<image|>", "<audio|>", "<|video|>",
|
| 144 |
+
"<start_of_turn>", "<end_of_turn>", "<bos", "bos>", "< bos >", "<BOS>", "<Bos>", "<eos><bos>", "<eos><eos>",
|
| 145 |
+
"<|tool><tool|>", "<|tool", "<|tool>>", "<<bos>>", "<bos>text<eos>", "<bos>", "<0x0A>", "<0xFF>",
|
| 146 |
+
"<unused0>", "[multimodal]", "<|im_start|>", "<s>", "</s>", "<|endoftext|>", "▁", "▁▁", "▁<bos>▁", "<▁bos>"]
|
| 147 |
+
CONTROLS = ["\x00", "\x01", "\x07", "\x08", "\x0b", "\x0c", "\x1b", "\x1f", "\x7f", "\x85", "\xa0", " ", " ",
|
| 148 |
+
" ", "", "", "", "", "", "
", "
", "", "", "",
|
| 149 |
+
"", "", " ", "", "", "", "�", "", "\U000e0001", "\U0010ffff",
|
| 150 |
+
"\U0001d11e", "\U00020000", "\U0002a6d6", "\U0003134a"]
|
| 151 |
+
ACCENTED = ["crème brûlée à la façon de {town}", "Ñandú y pingüino en el Río Zañartú", "Søren Ångström, Þórður Ægir",
|
| 152 |
+
"Łódź, Žilina, Ștefan, Țara", "ẞtraße, naïve, coöperate, résumé, exposé", "Việt Nam, Hồ Chí Minh? Đà Nẵng!",
|
| 153 |
+
"Džemal, Ljubljana, Njegoš", "Ḥamid, Ṣalāḥ, Ṭāriq, Ẓāhir", "Ễ ô̂ ạ̈ ṓ ṩ ǟ", "Ά έ ή ί ό ύ ώ ϊ ϋ ΐ ΰ"]
|
| 154 |
+
WS = [" ", "\n", "\t", "\r\n", "\r", " \t", " ", "\xa0", "\n\n"]
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def fill(tpl: str, r: random.Random) -> str:
|
| 158 |
+
o = r.choice(ORGS)
|
| 159 |
+
return tpl.format(p=f"{r.choice(FIRST)} {r.choice(LAST)}", p2=r.choice(FIRST), o=o, o2=o.split()[0].lower()[:6],
|
| 160 |
+
t=r.choice(THINGS), c=r.choice(COSE), d=r.choice(DAYS), mo=r.choice(MONTHS), town=r.choice(TOWNS),
|
| 161 |
+
n=r.randint(2, 98), m=r.randint(10, 999), m2=r.randint(1, 59), h=r.randint(7, 21))
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def sentence(r: random.Random, langs=None) -> str:
|
| 165 |
+
langs = langs or ["en", "en", "it"] + list(OTHER)
|
| 166 |
+
lang = r.choice(langs)
|
| 167 |
+
pool = {"en": EN_TPL, "it": IT_TPL}.get(lang) or OTHER[lang]
|
| 168 |
+
return fill(r.choice(pool), r)
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def rand_scalar(r: random.Random) -> str:
|
| 172 |
+
"""A random assigned-or-not scalar from a spread of blocks (never a surrogate)."""
|
| 173 |
+
ranges = [(0x20, 0x7e), (0xa0, 0x24f), (0x370, 0x3ff), (0x400, 0x4ff), (0x590, 0x6ff), (0x900, 0x97f),
|
| 174 |
+
(0xe00, 0xe7f), (0x1100, 0x11ff), (0x2000, 0x206f), (0x2190, 0x23ff), (0x2500, 0x27bf),
|
| 175 |
+
(0x3000, 0x30ff), (0x4e00, 0x9fff), (0xac00, 0xd7a3), (0xe000, 0xf8ff), (0xfb00, 0xfeff),
|
| 176 |
+
(0x10000, 0x1049f), (0x1f300, 0x1faff), (0x20000, 0x2a6df), (0xe0000, 0xe007f), (0x30000, 0x3134f),
|
| 177 |
+
(0xf0000, 0xffffd), (0x100000, 0x10ffff)]
|
| 178 |
+
lo, hi = r.choice(ranges)
|
| 179 |
+
return chr(r.randint(lo, hi))
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def build(r: random.Random) -> list[dict]:
|
| 183 |
+
out: list[dict] = []
|
| 184 |
+
|
| 185 |
+
def add(cat, text, kind=None):
|
| 186 |
+
out.append({"cat": cat, "kind": kind or r.choice(["d", "q", "raw"]), "text": text})
|
| 187 |
+
|
| 188 |
+
for _ in range(420):
|
| 189 |
+
add("en", " ".join(sentence(r, ["en"]) for _ in range(r.randint(1, 4))), "d")
|
| 190 |
+
for _ in range(220):
|
| 191 |
+
add("it", " ".join(sentence(r, ["it"]) for _ in range(r.randint(1, 3))), "d")
|
| 192 |
+
for lang in OTHER:
|
| 193 |
+
for _ in range(14 if lang in ("ar", "th", "hi", "zh", "ja", "ko") else 7):
|
| 194 |
+
add(lang, " ".join(sentence(r, [lang]) for _ in range(r.randint(1, 3))))
|
| 195 |
+
for _ in range(300):
|
| 196 |
+
add("query", fill(r.choice(QUERIES), r), "q")
|
| 197 |
+
for _ in range(200):
|
| 198 |
+
add("code", "".join(fill(r.choice(CODE), r) for _ in range(r.randint(1, 3))), "d")
|
| 199 |
+
# CRLF and mixed line endings
|
| 200 |
+
for _ in range(160):
|
| 201 |
+
s = "\r\n".join(sentence(r) for _ in range(r.randint(1, 5)))
|
| 202 |
+
s = r.choice([s, s + "\r\n", "\r\n" + s, s.replace("\r\n", "\r", 1), s.replace("\r\n", "\n\r", 1), s + "\r"])
|
| 203 |
+
add("crlf", s)
|
| 204 |
+
# NFD / NFKC / mixed normalization forms
|
| 205 |
+
for _ in range(160):
|
| 206 |
+
base = fill(r.choice(ACCENTED), r) + " " + sentence(r, ["it", "fr", "pl", "vi", "tr", "cs", "hu", "el", "ru"])
|
| 207 |
+
form = r.choice(["NFD", "NFD", "NFKD", "NFKC", "NFC"])
|
| 208 |
+
s = unicodedata.normalize(form, base)
|
| 209 |
+
if r.random() < 0.4: # mix NFC and NFD halves
|
| 210 |
+
s = unicodedata.normalize("NFC", base[: len(base) // 2]) + unicodedata.normalize("NFD", base[len(base) // 2:])
|
| 211 |
+
add("nfd", s)
|
| 212 |
+
# emoji sequences
|
| 213 |
+
for _ in range(220):
|
| 214 |
+
parts = [r.choice(EMOJI) for _ in range(r.randint(1, 8))]
|
| 215 |
+
words = sentence(r, ["en"]).split()
|
| 216 |
+
s = " ".join(w + (r.choice(parts) if r.random() < 0.3 else "") for w in words) if r.random() < 0.5 else "".join(parts)
|
| 217 |
+
add("emoji", s)
|
| 218 |
+
# scripts with combining marks and no spaces
|
| 219 |
+
for lang, n in (("ar", 100), ("fa", 40), ("he", 30), ("th", 110), ("hi", 100), ("bn", 40), ("ta", 40), ("zh", 70),
|
| 220 |
+
("zh_tw", 30), ("ja", 70), ("ko", 70), ("ka", 20), ("hy", 20), ("am", 20)):
|
| 221 |
+
for _ in range(n):
|
| 222 |
+
s = sentence(r, [lang])
|
| 223 |
+
if lang in ("ar", "fa") and r.random() < 0.5:
|
| 224 |
+
s = "".join(c + (r.choice("ًٌٍَُِّْ") if r.random() < 0.3 and "ء" <= c <= "ي" else "") for c in s)
|
| 225 |
+
if r.random() < 0.3:
|
| 226 |
+
s = s + " " + sentence(r, ["en"])
|
| 227 |
+
add(lang + "_x", s)
|
| 228 |
+
# whitespace runs > 31, tabs, newline runs, mixed
|
| 229 |
+
for _ in range(180):
|
| 230 |
+
ws = r.choice(WS)
|
| 231 |
+
k = r.choice([31, 32, 33, 40, 63, 64, 65, 100, 127, 128, 129, 200, 500])
|
| 232 |
+
a, b = sentence(r, ["en", "it"]), sentence(r, ["en"])
|
| 233 |
+
s = r.choice([a + ws * k + b, ws * k + a, a + ws * k, ws * k, a + " " * k + "\t" * (k // 3) + b, (ws * k + a) * 2])
|
| 234 |
+
add("whitespace", s)
|
| 235 |
+
for _ in range(100):
|
| 236 |
+
s = "\t".join(sentence(r).split(" ")[: r.randint(1, 12)])
|
| 237 |
+
s = r.choice([s, "\t" + s, s + "\t\t", s.replace("\t", "\t \t", 1), s.replace("\t", " \t ", 2)])
|
| 238 |
+
add("tabs", s)
|
| 239 |
+
# control characters and invisible code points
|
| 240 |
+
for _ in range(120):
|
| 241 |
+
words = sentence(r).split(" ")
|
| 242 |
+
s = " ".join(w + (r.choice(CONTROLS) if r.random() < 0.35 else "") for w in words)
|
| 243 |
+
s = r.choice([s, r.choice(CONTROLS) + s, s + r.choice(CONTROLS), "".join(r.choice(CONTROLS) for _ in range(r.randint(1, 12)))])
|
| 244 |
+
add("control", s)
|
| 245 |
+
# special-token look-alikes, alone, embedded, adjacent, repeated
|
| 246 |
+
for _ in range(170):
|
| 247 |
+
toks = [r.choice(LOOKALIKES) for _ in range(r.randint(1, 5))]
|
| 248 |
+
words = sentence(r, ["en"]).split(" ")
|
| 249 |
+
pos = r.randint(0, len(words))
|
| 250 |
+
s = r.choice([" ".join(words[:pos] + toks + words[pos:]), "".join(toks), " ".join(toks),
|
| 251 |
+
"".join(words[:pos]) + "".join(toks) + "".join(words[pos:]), toks[0] * r.randint(2, 6)])
|
| 252 |
+
add("lookalike", s)
|
| 253 |
+
# very short / empty
|
| 254 |
+
for s in ["", " ", " ", "\n", "a", "é", "é", "▁", "▁a", "🙂", "<", ">", "|", "title:", "task:", "none", "x" * 1,
|
| 255 |
+
"\u0000", "", "", "\U0010ffff", "0", "٠١٢٣", "१२३", "一", "あ", "한"]:
|
| 256 |
+
add("short", s, "raw")
|
| 257 |
+
add("short", s, "d")
|
| 258 |
+
# random scalar soup (assigned, unassigned, private use, non-characters)
|
| 259 |
+
for _ in range(220):
|
| 260 |
+
s = "".join(rand_scalar(r) for _ in range(r.randint(1, 60)))
|
| 261 |
+
add("soup", s)
|
| 262 |
+
# mixed everything
|
| 263 |
+
for _ in range(200):
|
| 264 |
+
bits = [sentence(r), r.choice(EMOJI), r.choice(LOOKALIKES), r.choice(CONTROLS), r.choice(WS) * r.randint(1, 40),
|
| 265 |
+
fill(r.choice(CODE), r), unicodedata.normalize("NFD", fill(r.choice(ACCENTED), r)), "".join(rand_scalar(r) for _ in range(5))]
|
| 266 |
+
r.shuffle(bits)
|
| 267 |
+
add("mixed", "".join(bits[: r.randint(2, 8)]))
|
| 268 |
+
# very long inputs (5k .. 80k characters)
|
| 269 |
+
for i in range(22):
|
| 270 |
+
n = [5000, 8000, 12000, 20000, 30000, 50000, 80000][i % 7]
|
| 271 |
+
parts = []
|
| 272 |
+
while sum(len(p) for p in parts) < n:
|
| 273 |
+
parts.append(r.choice([sentence(r), fill(r.choice(CODE), r), r.choice(EMOJI), r.choice(WS) * r.randint(1, 50),
|
| 274 |
+
"".join(rand_scalar(r) for _ in range(20)), r.choice(LOOKALIKES)]))
|
| 275 |
+
add("long", " ".join(parts), "d")
|
| 276 |
+
return out
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
def main() -> None:
|
| 280 |
+
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
| 281 |
+
ap.add_argument("--tokenizer", required=True, help="path to tokenizer.json")
|
| 282 |
+
ap.add_argument("--out", required=True)
|
| 283 |
+
ap.add_argument("--seed", type=int, default=20261012)
|
| 284 |
+
args = ap.parse_args()
|
| 285 |
+
from tokenizers import Tokenizer
|
| 286 |
+
|
| 287 |
+
tok = Tokenizer.from_file(args.tokenizer)
|
| 288 |
+
cases = build(random.Random(args.seed))
|
| 289 |
+
for c in cases:
|
| 290 |
+
c["ids"] = [2] + tok.encode(PREFIX[c["kind"]] + c["text"], add_special_tokens=False).ids + [1]
|
| 291 |
+
Path(args.out).write_text(json.dumps(cases, ensure_ascii=False), encoding="utf-8")
|
| 292 |
+
from collections import Counter
|
| 293 |
+
print(f"{len(cases)} cases, {sum(len(c['ids']) for c in cases)} ids ->", args.out)
|
| 294 |
+
print(dict(Counter(c["cat"] for c in cases)))
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
if __name__ == "__main__":
|
| 298 |
+
main()
|
tests/smoke-reference.json
ADDED
|
@@ -0,0 +1,2333 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"reference": "sentence-transformers fp32 on CPU, original checkpoint, text only",
|
| 3 |
+
"versions": {
|
| 4 |
+
"sentence_transformers": "6.1.0",
|
| 5 |
+
"transformers": "5.19.0",
|
| 6 |
+
"torch": "2.14.1"
|
| 7 |
+
},
|
| 8 |
+
"threshold_cosine": 0.999,
|
| 9 |
+
"cases": [
|
| 10 |
+
{
|
| 11 |
+
"kind": "search_query",
|
| 12 |
+
"text": "When does the warehouse lease in Brindlemoor expire?",
|
| 13 |
+
"embedding": [
|
| 14 |
+
0.0140514,
|
| 15 |
+
0.0200849,
|
| 16 |
+
0.0201675,
|
| 17 |
+
-0.0119595,
|
| 18 |
+
-0.035274,
|
| 19 |
+
0.0034859,
|
| 20 |
+
-0.0322359,
|
| 21 |
+
0.0260318,
|
| 22 |
+
-0.0253003,
|
| 23 |
+
-0.1322919,
|
| 24 |
+
0.0172573,
|
| 25 |
+
-0.0929851,
|
| 26 |
+
-0.0345029,
|
| 27 |
+
0.0034794,
|
| 28 |
+
0.0074408,
|
| 29 |
+
-0.0231558,
|
| 30 |
+
-0.0121178,
|
| 31 |
+
0.0041102,
|
| 32 |
+
-0.026006,
|
| 33 |
+
-0.0039767,
|
| 34 |
+
-0.0500095,
|
| 35 |
+
-0.0474389,
|
| 36 |
+
0.0046259,
|
| 37 |
+
-0.0144053,
|
| 38 |
+
0.0104898,
|
| 39 |
+
-0.0104195,
|
| 40 |
+
-0.0212026,
|
| 41 |
+
-0.011046,
|
| 42 |
+
-0.0204922,
|
| 43 |
+
0.1905717,
|
| 44 |
+
-0.0039958,
|
| 45 |
+
-0.0352325,
|
| 46 |
+
-0.0434769,
|
| 47 |
+
-0.0078017,
|
| 48 |
+
0.0146712,
|
| 49 |
+
-0.0420867,
|
| 50 |
+
-0.0191855,
|
| 51 |
+
0.0736523,
|
| 52 |
+
0.0112718,
|
| 53 |
+
0.0299183,
|
| 54 |
+
-0.0180284,
|
| 55 |
+
0.0087481,
|
| 56 |
+
-0.0032612,
|
| 57 |
+
-0.0007796,
|
| 58 |
+
0.0054152,
|
| 59 |
+
0.0057414,
|
| 60 |
+
0.0320906,
|
| 61 |
+
0.0198112,
|
| 62 |
+
-0.0058027,
|
| 63 |
+
0.0055605,
|
| 64 |
+
-0.011155,
|
| 65 |
+
0.0018056,
|
| 66 |
+
-0.0036217,
|
| 67 |
+
0.0599205,
|
| 68 |
+
-0.0395265,
|
| 69 |
+
0.0071835,
|
| 70 |
+
0.0045348,
|
| 71 |
+
0.0249366,
|
| 72 |
+
0.0060176,
|
| 73 |
+
-0.0363902,
|
| 74 |
+
-0.0180425,
|
| 75 |
+
-0.0019947,
|
| 76 |
+
0.013761,
|
| 77 |
+
0.0338091,
|
| 78 |
+
-0.0665499,
|
| 79 |
+
-0.0047037,
|
| 80 |
+
0.0470355,
|
| 81 |
+
-0.0196299,
|
| 82 |
+
0.0428396,
|
| 83 |
+
0.0634703,
|
| 84 |
+
0.0617316,
|
| 85 |
+
0.0512214,
|
| 86 |
+
0.0480056,
|
| 87 |
+
0.0396131,
|
| 88 |
+
-0.0358877,
|
| 89 |
+
-0.0718305,
|
| 90 |
+
-0.102156,
|
| 91 |
+
0.0629865,
|
| 92 |
+
-0.0244289,
|
| 93 |
+
-0.0405096,
|
| 94 |
+
0.0616369,
|
| 95 |
+
0.0004009,
|
| 96 |
+
-0.0722917,
|
| 97 |
+
-0.0258843,
|
| 98 |
+
-0.037575,
|
| 99 |
+
-0.0455716,
|
| 100 |
+
-0.0343866,
|
| 101 |
+
0.0075406,
|
| 102 |
+
0.035479,
|
| 103 |
+
0.0328424,
|
| 104 |
+
-0.008169,
|
| 105 |
+
0.0508599,
|
| 106 |
+
-0.0598774,
|
| 107 |
+
-0.0104931,
|
| 108 |
+
-0.067505,
|
| 109 |
+
0.0006392,
|
| 110 |
+
-0.0198671,
|
| 111 |
+
0.0011432,
|
| 112 |
+
0.0123858,
|
| 113 |
+
-0.0174422,
|
| 114 |
+
0.0056807,
|
| 115 |
+
-0.0079437,
|
| 116 |
+
-0.0044856,
|
| 117 |
+
0.0179937,
|
| 118 |
+
0.0756269,
|
| 119 |
+
0.0363916,
|
| 120 |
+
-0.0265536,
|
| 121 |
+
0.0695819,
|
| 122 |
+
0.040324,
|
| 123 |
+
-0.0622708,
|
| 124 |
+
-0.064421,
|
| 125 |
+
-0.2657686,
|
| 126 |
+
-0.0183439,
|
| 127 |
+
0.0499475,
|
| 128 |
+
0.0405476,
|
| 129 |
+
0.2633377,
|
| 130 |
+
-0.0433234,
|
| 131 |
+
-0.0144355,
|
| 132 |
+
-0.0109114,
|
| 133 |
+
0.0286458,
|
| 134 |
+
0.0041431,
|
| 135 |
+
0.0063956,
|
| 136 |
+
-0.0419718,
|
| 137 |
+
0.0039493,
|
| 138 |
+
-0.057251,
|
| 139 |
+
-0.0082195,
|
| 140 |
+
0.0578189,
|
| 141 |
+
0.0176498,
|
| 142 |
+
0.0127398,
|
| 143 |
+
0.0349538,
|
| 144 |
+
0.0112667,
|
| 145 |
+
-0.0230163,
|
| 146 |
+
0.0316641,
|
| 147 |
+
0.0500062,
|
| 148 |
+
-0.0358909,
|
| 149 |
+
-0.0121331,
|
| 150 |
+
-0.0285246,
|
| 151 |
+
-0.0099984,
|
| 152 |
+
-0.0417251,
|
| 153 |
+
-0.015272,
|
| 154 |
+
-0.0617455,
|
| 155 |
+
-0.0135424,
|
| 156 |
+
0.015151,
|
| 157 |
+
0.0473345,
|
| 158 |
+
-0.0048397,
|
| 159 |
+
0.0277582,
|
| 160 |
+
-0.0336748,
|
| 161 |
+
-0.032164,
|
| 162 |
+
0.0029421,
|
| 163 |
+
0.0061318,
|
| 164 |
+
0.0053797,
|
| 165 |
+
-0.0440732,
|
| 166 |
+
-0.0239873,
|
| 167 |
+
0.0194475,
|
| 168 |
+
0.0319975,
|
| 169 |
+
0.0183903,
|
| 170 |
+
0.0482337,
|
| 171 |
+
-0.007429,
|
| 172 |
+
-0.0113159,
|
| 173 |
+
-0.0271933,
|
| 174 |
+
-0.0209385,
|
| 175 |
+
0.002265,
|
| 176 |
+
0.017666,
|
| 177 |
+
0.0208267,
|
| 178 |
+
0.0063856,
|
| 179 |
+
0.0297219,
|
| 180 |
+
-0.0184708,
|
| 181 |
+
0.025398,
|
| 182 |
+
0.0026902,
|
| 183 |
+
0.0512845,
|
| 184 |
+
-0.0034231,
|
| 185 |
+
-0.0198501,
|
| 186 |
+
-0.000227,
|
| 187 |
+
-0.0331328,
|
| 188 |
+
0.0207864,
|
| 189 |
+
0.0023767,
|
| 190 |
+
0.0558418,
|
| 191 |
+
-0.0101271,
|
| 192 |
+
-0.0198393,
|
| 193 |
+
0.0030498,
|
| 194 |
+
-0.0618708,
|
| 195 |
+
-0.0085252,
|
| 196 |
+
-0.0102823,
|
| 197 |
+
-0.0304731,
|
| 198 |
+
0.0326227,
|
| 199 |
+
0.0539232,
|
| 200 |
+
-0.0254712,
|
| 201 |
+
0.003854,
|
| 202 |
+
-0.0184922,
|
| 203 |
+
-0.0071012,
|
| 204 |
+
0.0024879,
|
| 205 |
+
0.0039352,
|
| 206 |
+
0.0036627,
|
| 207 |
+
0.0092597,
|
| 208 |
+
-0.0218288,
|
| 209 |
+
0.0062251,
|
| 210 |
+
0.0374328,
|
| 211 |
+
-0.012431,
|
| 212 |
+
-0.0118201,
|
| 213 |
+
0.0184596,
|
| 214 |
+
0.0124813,
|
| 215 |
+
-0.0153233,
|
| 216 |
+
0.0424144,
|
| 217 |
+
-0.0017183,
|
| 218 |
+
-0.0481435,
|
| 219 |
+
0.0003325,
|
| 220 |
+
-0.0335241,
|
| 221 |
+
0.0318867,
|
| 222 |
+
0.0052809,
|
| 223 |
+
-0.0104564,
|
| 224 |
+
0.0022734,
|
| 225 |
+
-0.0028741,
|
| 226 |
+
0.0161565,
|
| 227 |
+
-0.0500812,
|
| 228 |
+
0.0328645,
|
| 229 |
+
0.0240628,
|
| 230 |
+
-0.0186112,
|
| 231 |
+
0.0400808,
|
| 232 |
+
0.0222267,
|
| 233 |
+
-0.025151,
|
| 234 |
+
-0.006919,
|
| 235 |
+
0.0431995,
|
| 236 |
+
0.0789402,
|
| 237 |
+
0.0139404,
|
| 238 |
+
-0.0049106,
|
| 239 |
+
-0.0538642,
|
| 240 |
+
0.0063854,
|
| 241 |
+
-0.0147278,
|
| 242 |
+
-0.0038739,
|
| 243 |
+
0.056846,
|
| 244 |
+
-0.0180968,
|
| 245 |
+
0.0210576,
|
| 246 |
+
-0.0370839,
|
| 247 |
+
-0.088229,
|
| 248 |
+
0.0906495,
|
| 249 |
+
-0.0048958,
|
| 250 |
+
-0.0581601,
|
| 251 |
+
0.0112045,
|
| 252 |
+
-0.0083936,
|
| 253 |
+
0.0677319,
|
| 254 |
+
-0.0318846,
|
| 255 |
+
-0.0155604,
|
| 256 |
+
0.0173322,
|
| 257 |
+
0.061947,
|
| 258 |
+
-0.0243701,
|
| 259 |
+
-0.0212206,
|
| 260 |
+
-0.0025019,
|
| 261 |
+
0.0102068,
|
| 262 |
+
0.0122189,
|
| 263 |
+
-0.0119761,
|
| 264 |
+
0.0699356,
|
| 265 |
+
-0.0088522,
|
| 266 |
+
-0.0546333,
|
| 267 |
+
-0.0722661,
|
| 268 |
+
-0.015771,
|
| 269 |
+
-0.0367368,
|
| 270 |
+
0.0324103,
|
| 271 |
+
-0.0347873,
|
| 272 |
+
0.0060024,
|
| 273 |
+
0.0400301,
|
| 274 |
+
-0.0096935,
|
| 275 |
+
-0.0621471,
|
| 276 |
+
-0.0305385,
|
| 277 |
+
0.024493,
|
| 278 |
+
0.004069,
|
| 279 |
+
0.0410262,
|
| 280 |
+
-0.0622035,
|
| 281 |
+
0.0020669,
|
| 282 |
+
0.0152834,
|
| 283 |
+
-0.0156231,
|
| 284 |
+
0.0165897,
|
| 285 |
+
0.0094615,
|
| 286 |
+
0.0054113,
|
| 287 |
+
0.0058183,
|
| 288 |
+
0.0291583,
|
| 289 |
+
0.0337521,
|
| 290 |
+
-0.0329887,
|
| 291 |
+
0.044419,
|
| 292 |
+
0.0099162,
|
| 293 |
+
-0.0130374,
|
| 294 |
+
0.0149398,
|
| 295 |
+
-0.0300086,
|
| 296 |
+
-0.0443118,
|
| 297 |
+
0.0060803,
|
| 298 |
+
-0.0163578,
|
| 299 |
+
0.0493293,
|
| 300 |
+
-0.0172757,
|
| 301 |
+
0.0178506,
|
| 302 |
+
0.0246122,
|
| 303 |
+
0.0469199,
|
| 304 |
+
0.0338066,
|
| 305 |
+
-0.035952,
|
| 306 |
+
0.0218767,
|
| 307 |
+
0.0120018,
|
| 308 |
+
-0.028764,
|
| 309 |
+
0.032157,
|
| 310 |
+
-0.0097965,
|
| 311 |
+
0.0423428,
|
| 312 |
+
0.0040818,
|
| 313 |
+
0.0187548,
|
| 314 |
+
-0.0104548,
|
| 315 |
+
0.0285643,
|
| 316 |
+
-0.0246368,
|
| 317 |
+
-0.032603,
|
| 318 |
+
0.0659568,
|
| 319 |
+
-0.0442716,
|
| 320 |
+
-0.0681514,
|
| 321 |
+
-0.022396,
|
| 322 |
+
-0.0112234,
|
| 323 |
+
-0.0442733,
|
| 324 |
+
-0.0107434,
|
| 325 |
+
-0.0283538,
|
| 326 |
+
-0.0115352,
|
| 327 |
+
-0.02458,
|
| 328 |
+
0.0270112,
|
| 329 |
+
0.0093793,
|
| 330 |
+
-0.0185534,
|
| 331 |
+
0.0137447,
|
| 332 |
+
0.0031274,
|
| 333 |
+
-0.079896,
|
| 334 |
+
-0.0106351,
|
| 335 |
+
0.0440398,
|
| 336 |
+
-0.0201902,
|
| 337 |
+
-0.0351441,
|
| 338 |
+
0.0031468,
|
| 339 |
+
0.0466262,
|
| 340 |
+
-0.0217505,
|
| 341 |
+
-0.0193922,
|
| 342 |
+
0.0082847,
|
| 343 |
+
-0.033297,
|
| 344 |
+
-0.0407191,
|
| 345 |
+
0.0030198,
|
| 346 |
+
-0.0266054,
|
| 347 |
+
0.0127826,
|
| 348 |
+
0.0090996,
|
| 349 |
+
0.0228752,
|
| 350 |
+
0.0433155,
|
| 351 |
+
0.0605334,
|
| 352 |
+
0.0233076,
|
| 353 |
+
-0.0209052,
|
| 354 |
+
-0.021734,
|
| 355 |
+
-0.1376118,
|
| 356 |
+
-0.062436,
|
| 357 |
+
0.0284235,
|
| 358 |
+
0.021142,
|
| 359 |
+
-0.0097345,
|
| 360 |
+
0.003243,
|
| 361 |
+
0.0095441,
|
| 362 |
+
-0.0628982,
|
| 363 |
+
0.0300183,
|
| 364 |
+
-0.0005559,
|
| 365 |
+
0.0380125,
|
| 366 |
+
0.0286191,
|
| 367 |
+
-0.0209423,
|
| 368 |
+
-0.0044779,
|
| 369 |
+
0.0035791,
|
| 370 |
+
0.0474121,
|
| 371 |
+
0.0384367,
|
| 372 |
+
0.00689,
|
| 373 |
+
0.0448739,
|
| 374 |
+
-0.0003337,
|
| 375 |
+
0.0272092,
|
| 376 |
+
-0.0048142,
|
| 377 |
+
0.0293282,
|
| 378 |
+
-0.0084391,
|
| 379 |
+
-0.0278422,
|
| 380 |
+
-0.0488862,
|
| 381 |
+
0.0090513,
|
| 382 |
+
-0.0323157,
|
| 383 |
+
0.0215345,
|
| 384 |
+
-0.0074275,
|
| 385 |
+
0.013417,
|
| 386 |
+
-0.0262015,
|
| 387 |
+
0.0041043,
|
| 388 |
+
0.0173136,
|
| 389 |
+
0.0766509,
|
| 390 |
+
0.0014924,
|
| 391 |
+
0.0104271,
|
| 392 |
+
-0.0027695,
|
| 393 |
+
-0.0264011,
|
| 394 |
+
0.0729229,
|
| 395 |
+
0.0227126,
|
| 396 |
+
-0.0357576,
|
| 397 |
+
0.0224196,
|
| 398 |
+
0.0429215,
|
| 399 |
+
-0.0572116,
|
| 400 |
+
0.030736,
|
| 401 |
+
-0.0222465,
|
| 402 |
+
0.0291479,
|
| 403 |
+
0.0198262,
|
| 404 |
+
0.0098066,
|
| 405 |
+
0.2381135,
|
| 406 |
+
0.0009308,
|
| 407 |
+
-0.0170637,
|
| 408 |
+
0.0399403,
|
| 409 |
+
0.0244772,
|
| 410 |
+
-0.0218695,
|
| 411 |
+
0.013202,
|
| 412 |
+
-0.0195465,
|
| 413 |
+
-0.009753,
|
| 414 |
+
0.0249087,
|
| 415 |
+
0.0040551,
|
| 416 |
+
-0.0182575,
|
| 417 |
+
0.0128712,
|
| 418 |
+
-0.0166567,
|
| 419 |
+
-0.0183231,
|
| 420 |
+
-0.0034079,
|
| 421 |
+
-0.0336708,
|
| 422 |
+
-0.0235771,
|
| 423 |
+
0.0139053,
|
| 424 |
+
-0.0131362,
|
| 425 |
+
0.0313214,
|
| 426 |
+
0.0032543,
|
| 427 |
+
-0.0036488,
|
| 428 |
+
-0.0122029,
|
| 429 |
+
-0.0135203,
|
| 430 |
+
-0.0332577,
|
| 431 |
+
-0.0270707,
|
| 432 |
+
-0.0539252,
|
| 433 |
+
-0.0376571,
|
| 434 |
+
0.0039163,
|
| 435 |
+
0.0268505,
|
| 436 |
+
-0.009067,
|
| 437 |
+
-0.0237761,
|
| 438 |
+
0.0446752,
|
| 439 |
+
0.015811,
|
| 440 |
+
-0.0068362,
|
| 441 |
+
0.0052214,
|
| 442 |
+
-0.004454,
|
| 443 |
+
0.0399815,
|
| 444 |
+
0.023899,
|
| 445 |
+
-0.031928,
|
| 446 |
+
-0.0199918,
|
| 447 |
+
0.0048249,
|
| 448 |
+
0.0395325,
|
| 449 |
+
0.0104519,
|
| 450 |
+
0.0263888,
|
| 451 |
+
-0.0090891,
|
| 452 |
+
-0.0206043,
|
| 453 |
+
-0.0156751,
|
| 454 |
+
0.0082406,
|
| 455 |
+
-0.0057344,
|
| 456 |
+
-0.0070633,
|
| 457 |
+
0.0328125,
|
| 458 |
+
0.0125726,
|
| 459 |
+
-0.0467525,
|
| 460 |
+
-0.0043885,
|
| 461 |
+
-0.0173589,
|
| 462 |
+
-0.0055486,
|
| 463 |
+
0.0196809,
|
| 464 |
+
-0.0101459,
|
| 465 |
+
-0.0079408,
|
| 466 |
+
0.0167622,
|
| 467 |
+
0.0093635,
|
| 468 |
+
-0.0186476,
|
| 469 |
+
-0.0031106,
|
| 470 |
+
-0.0500078,
|
| 471 |
+
-0.0372527,
|
| 472 |
+
0.0440114,
|
| 473 |
+
0.008821,
|
| 474 |
+
-0.0034126,
|
| 475 |
+
-0.0175557,
|
| 476 |
+
-0.005297,
|
| 477 |
+
-0.0138077,
|
| 478 |
+
0.0122492,
|
| 479 |
+
0.0161653,
|
| 480 |
+
-0.0163164,
|
| 481 |
+
-0.0130119,
|
| 482 |
+
-0.0301623,
|
| 483 |
+
-0.0263716,
|
| 484 |
+
-0.0042851,
|
| 485 |
+
0.0190618,
|
| 486 |
+
-0.0150112,
|
| 487 |
+
0.0062657,
|
| 488 |
+
-0.0044136,
|
| 489 |
+
-0.0191775,
|
| 490 |
+
0.0154332,
|
| 491 |
+
0.0233396,
|
| 492 |
+
0.0147409,
|
| 493 |
+
-0.0016429,
|
| 494 |
+
0.0397183,
|
| 495 |
+
-0.0267775,
|
| 496 |
+
-0.0249655,
|
| 497 |
+
-0.0084888,
|
| 498 |
+
-0.0365686,
|
| 499 |
+
0.0376978,
|
| 500 |
+
-0.0176997,
|
| 501 |
+
0.0466568,
|
| 502 |
+
-0.0013101,
|
| 503 |
+
0.0168162,
|
| 504 |
+
0.0361352,
|
| 505 |
+
0.0076559,
|
| 506 |
+
-0.038352,
|
| 507 |
+
0.0192798,
|
| 508 |
+
-0.0334455,
|
| 509 |
+
-0.0148572,
|
| 510 |
+
0.0290095,
|
| 511 |
+
-0.0101722,
|
| 512 |
+
-0.00072,
|
| 513 |
+
0.0198838,
|
| 514 |
+
-0.0442767,
|
| 515 |
+
0.0014474,
|
| 516 |
+
-0.024855,
|
| 517 |
+
0.0167283,
|
| 518 |
+
-0.0336968,
|
| 519 |
+
-0.0121257,
|
| 520 |
+
-0.0358068,
|
| 521 |
+
-0.1305676,
|
| 522 |
+
0.0021763,
|
| 523 |
+
0.0454077,
|
| 524 |
+
0.0026204,
|
| 525 |
+
0.040022,
|
| 526 |
+
0.0299537,
|
| 527 |
+
0.0282371,
|
| 528 |
+
0.0095981,
|
| 529 |
+
-0.0063128,
|
| 530 |
+
0.0010373,
|
| 531 |
+
0.0163293,
|
| 532 |
+
-0.0015985,
|
| 533 |
+
0.0251838,
|
| 534 |
+
0.0284173,
|
| 535 |
+
-0.028472,
|
| 536 |
+
-0.009489,
|
| 537 |
+
-0.0275945,
|
| 538 |
+
-0.0364336,
|
| 539 |
+
-0.0283635,
|
| 540 |
+
0.0044131,
|
| 541 |
+
0.0035401,
|
| 542 |
+
0.0103233,
|
| 543 |
+
-0.0083207,
|
| 544 |
+
0.0274399,
|
| 545 |
+
0.0147917,
|
| 546 |
+
-0.0156048,
|
| 547 |
+
-0.0215956,
|
| 548 |
+
-0.0052218,
|
| 549 |
+
-0.0390134,
|
| 550 |
+
-0.0142176,
|
| 551 |
+
-0.0004916,
|
| 552 |
+
0.0027648,
|
| 553 |
+
-0.0229333,
|
| 554 |
+
0.0540044,
|
| 555 |
+
-0.0389055,
|
| 556 |
+
-0.003413,
|
| 557 |
+
0.0124384,
|
| 558 |
+
-0.0203941,
|
| 559 |
+
0.0389145,
|
| 560 |
+
0.0146834,
|
| 561 |
+
0.0001552,
|
| 562 |
+
-0.0018129,
|
| 563 |
+
0.0674664,
|
| 564 |
+
0.0412302,
|
| 565 |
+
0.0415348,
|
| 566 |
+
-0.0023984,
|
| 567 |
+
-0.0284114,
|
| 568 |
+
-0.2059626,
|
| 569 |
+
-0.0090965,
|
| 570 |
+
-0.0156103,
|
| 571 |
+
0.0002269,
|
| 572 |
+
-0.0365953,
|
| 573 |
+
-0.0244425,
|
| 574 |
+
0.0057417,
|
| 575 |
+
0.048451,
|
| 576 |
+
0.0383219,
|
| 577 |
+
-0.0023854,
|
| 578 |
+
-0.0357098,
|
| 579 |
+
0.0177072,
|
| 580 |
+
-0.021637,
|
| 581 |
+
0.0201444,
|
| 582 |
+
-0.0428248,
|
| 583 |
+
0.0057723,
|
| 584 |
+
-0.0342198,
|
| 585 |
+
0.0354257,
|
| 586 |
+
-0.0277822,
|
| 587 |
+
0.0002803,
|
| 588 |
+
0.0401719,
|
| 589 |
+
-0.0344315,
|
| 590 |
+
0.0256965,
|
| 591 |
+
0.0327575,
|
| 592 |
+
0.0532876,
|
| 593 |
+
0.0113293,
|
| 594 |
+
-0.0113227,
|
| 595 |
+
0.0346123,
|
| 596 |
+
-0.0359698,
|
| 597 |
+
-0.0094106,
|
| 598 |
+
0.0203509,
|
| 599 |
+
-0.0320784,
|
| 600 |
+
-0.0114563,
|
| 601 |
+
-0.0019832,
|
| 602 |
+
-0.0448561,
|
| 603 |
+
0.0305796,
|
| 604 |
+
0.0081664,
|
| 605 |
+
-0.0176064,
|
| 606 |
+
0.0073882,
|
| 607 |
+
-0.015993,
|
| 608 |
+
-0.0134097,
|
| 609 |
+
-0.031895,
|
| 610 |
+
0.0357883,
|
| 611 |
+
-0.0018839,
|
| 612 |
+
-0.0136349,
|
| 613 |
+
0.0152895,
|
| 614 |
+
-0.0087174,
|
| 615 |
+
-0.013247,
|
| 616 |
+
0.0096113,
|
| 617 |
+
0.0168226,
|
| 618 |
+
-0.0005419,
|
| 619 |
+
-0.0159698,
|
| 620 |
+
0.0096558,
|
| 621 |
+
-0.0327691,
|
| 622 |
+
-0.0433982,
|
| 623 |
+
-0.0066851,
|
| 624 |
+
0.0044284,
|
| 625 |
+
0.0213365,
|
| 626 |
+
0.0320568,
|
| 627 |
+
0.0022381,
|
| 628 |
+
0.0257285,
|
| 629 |
+
0.0143513,
|
| 630 |
+
0.0022292,
|
| 631 |
+
-0.0166571,
|
| 632 |
+
-0.0146441,
|
| 633 |
+
-0.0404922,
|
| 634 |
+
-0.0189938,
|
| 635 |
+
-0.0101463,
|
| 636 |
+
0.0187055,
|
| 637 |
+
0.0037433,
|
| 638 |
+
0.0022404,
|
| 639 |
+
-0.0062486,
|
| 640 |
+
-0.0070731,
|
| 641 |
+
0.0032853,
|
| 642 |
+
0.0411538,
|
| 643 |
+
-0.0119422,
|
| 644 |
+
-0.0371468,
|
| 645 |
+
0.0130075,
|
| 646 |
+
0.0291758,
|
| 647 |
+
-0.0188306,
|
| 648 |
+
-0.0089265,
|
| 649 |
+
0.0263609,
|
| 650 |
+
0.0156603,
|
| 651 |
+
-0.004102,
|
| 652 |
+
-0.0210846,
|
| 653 |
+
0.0158581,
|
| 654 |
+
-5.31e-05,
|
| 655 |
+
0.0059412,
|
| 656 |
+
0.0064431,
|
| 657 |
+
-0.0194571,
|
| 658 |
+
0.0351863,
|
| 659 |
+
0.0506362,
|
| 660 |
+
0.0143477,
|
| 661 |
+
0.0433141,
|
| 662 |
+
-0.0236757,
|
| 663 |
+
0.0323491,
|
| 664 |
+
0.018349,
|
| 665 |
+
0.0509639,
|
| 666 |
+
0.0147666,
|
| 667 |
+
-0.0070182,
|
| 668 |
+
-0.002872,
|
| 669 |
+
-0.0116239,
|
| 670 |
+
-0.0142457,
|
| 671 |
+
0.0087374,
|
| 672 |
+
-0.0020458,
|
| 673 |
+
0.0024226,
|
| 674 |
+
0.0514395,
|
| 675 |
+
-0.0161209,
|
| 676 |
+
0.0376469,
|
| 677 |
+
0.013314,
|
| 678 |
+
-0.0078508,
|
| 679 |
+
0.0112804,
|
| 680 |
+
-0.0185661,
|
| 681 |
+
0.0004649,
|
| 682 |
+
-0.0206176,
|
| 683 |
+
0.0290994,
|
| 684 |
+
-0.0127025,
|
| 685 |
+
-0.0008931,
|
| 686 |
+
-0.0125401,
|
| 687 |
+
-0.011069,
|
| 688 |
+
-0.01393,
|
| 689 |
+
-0.0161357,
|
| 690 |
+
-0.0261728,
|
| 691 |
+
-0.0188071,
|
| 692 |
+
0.0273512,
|
| 693 |
+
0.0359035,
|
| 694 |
+
-0.0228979,
|
| 695 |
+
-0.0147417,
|
| 696 |
+
0.0048217,
|
| 697 |
+
0.0288668,
|
| 698 |
+
0.0073423,
|
| 699 |
+
0.0100393,
|
| 700 |
+
0.041307,
|
| 701 |
+
0.0198599,
|
| 702 |
+
0.0067079,
|
| 703 |
+
-0.0030872,
|
| 704 |
+
-0.0128413,
|
| 705 |
+
0.0158811,
|
| 706 |
+
0.0128299,
|
| 707 |
+
0.0302011,
|
| 708 |
+
0.009724,
|
| 709 |
+
-0.0548747,
|
| 710 |
+
0.0092055,
|
| 711 |
+
-0.0272114,
|
| 712 |
+
-0.024287,
|
| 713 |
+
0.0015032,
|
| 714 |
+
0.0542283,
|
| 715 |
+
0.0184035,
|
| 716 |
+
0.02366,
|
| 717 |
+
0.0377193,
|
| 718 |
+
0.0221289,
|
| 719 |
+
0.0158905,
|
| 720 |
+
0.0122361,
|
| 721 |
+
-0.0181141,
|
| 722 |
+
-0.0064896,
|
| 723 |
+
0.0225127,
|
| 724 |
+
0.0133703,
|
| 725 |
+
-0.0149417,
|
| 726 |
+
-0.028168,
|
| 727 |
+
0.0244618,
|
| 728 |
+
-0.0035144,
|
| 729 |
+
-0.0209218,
|
| 730 |
+
0.0446331,
|
| 731 |
+
0.0127702,
|
| 732 |
+
-0.0186521,
|
| 733 |
+
0.0005753,
|
| 734 |
+
-0.0012075,
|
| 735 |
+
-0.0183204,
|
| 736 |
+
0.0400032,
|
| 737 |
+
0.0197334,
|
| 738 |
+
0.0030626,
|
| 739 |
+
-0.0455221,
|
| 740 |
+
0.0269828,
|
| 741 |
+
-0.0142443,
|
| 742 |
+
-0.0115392,
|
| 743 |
+
0.0330398,
|
| 744 |
+
0.0007724,
|
| 745 |
+
0.0340609,
|
| 746 |
+
0.2108322,
|
| 747 |
+
0.0039355,
|
| 748 |
+
-0.0055149,
|
| 749 |
+
0.0161179,
|
| 750 |
+
-0.0071948,
|
| 751 |
+
-0.0103065,
|
| 752 |
+
0.0139283,
|
| 753 |
+
0.0374079,
|
| 754 |
+
-0.0069936,
|
| 755 |
+
0.0435351,
|
| 756 |
+
0.0173975,
|
| 757 |
+
0.0105077,
|
| 758 |
+
0.0359319,
|
| 759 |
+
-0.0259694,
|
| 760 |
+
-0.0158281,
|
| 761 |
+
-0.0211381,
|
| 762 |
+
0.029089,
|
| 763 |
+
-0.0099361,
|
| 764 |
+
0.022188,
|
| 765 |
+
0.0078775,
|
| 766 |
+
0.0127904,
|
| 767 |
+
-0.0391305,
|
| 768 |
+
-0.0249713,
|
| 769 |
+
0.0108958,
|
| 770 |
+
-0.0368597,
|
| 771 |
+
0.0160098,
|
| 772 |
+
-0.0362015,
|
| 773 |
+
0.0072925,
|
| 774 |
+
-0.0076356,
|
| 775 |
+
0.0512759,
|
| 776 |
+
-0.0074541,
|
| 777 |
+
-0.0033625,
|
| 778 |
+
-0.0056429,
|
| 779 |
+
0.0345776,
|
| 780 |
+
-0.016772,
|
| 781 |
+
0.016576
|
| 782 |
+
]
|
| 783 |
+
},
|
| 784 |
+
{
|
| 785 |
+
"kind": "document",
|
| 786 |
+
"text": "The warehouse lease in Brindlemoor runs until the last day of March; the landlord agreed to fix the window before then.",
|
| 787 |
+
"embedding": [
|
| 788 |
+
0.0264644,
|
| 789 |
+
-0.0242984,
|
| 790 |
+
0.0116434,
|
| 791 |
+
-0.0094456,
|
| 792 |
+
-0.0538755,
|
| 793 |
+
-0.0034127,
|
| 794 |
+
0.01068,
|
| 795 |
+
0.0363724,
|
| 796 |
+
-0.0184347,
|
| 797 |
+
-0.1608366,
|
| 798 |
+
0.0270172,
|
| 799 |
+
-0.0312475,
|
| 800 |
+
0.0268842,
|
| 801 |
+
0.0346971,
|
| 802 |
+
-0.0035566,
|
| 803 |
+
-0.0575296,
|
| 804 |
+
-0.0185978,
|
| 805 |
+
-0.0473271,
|
| 806 |
+
-0.0191439,
|
| 807 |
+
-0.006971,
|
| 808 |
+
-0.0709102,
|
| 809 |
+
-0.0006216,
|
| 810 |
+
-0.0111378,
|
| 811 |
+
-0.0304192,
|
| 812 |
+
0.0180717,
|
| 813 |
+
-0.0026377,
|
| 814 |
+
-0.0228687,
|
| 815 |
+
-0.0221057,
|
| 816 |
+
-0.0491751,
|
| 817 |
+
0.173117,
|
| 818 |
+
-0.0081147,
|
| 819 |
+
-0.0180295,
|
| 820 |
+
-0.0083443,
|
| 821 |
+
-0.0130251,
|
| 822 |
+
0.0615742,
|
| 823 |
+
-0.0135436,
|
| 824 |
+
-0.0290864,
|
| 825 |
+
0.0527058,
|
| 826 |
+
-0.0001754,
|
| 827 |
+
0.0241153,
|
| 828 |
+
-0.0342266,
|
| 829 |
+
0.0070813,
|
| 830 |
+
-0.0127815,
|
| 831 |
+
0.004976,
|
| 832 |
+
0.0269828,
|
| 833 |
+
0.0039893,
|
| 834 |
+
0.0317817,
|
| 835 |
+
0.0179321,
|
| 836 |
+
-0.0148058,
|
| 837 |
+
0.0165759,
|
| 838 |
+
0.0083154,
|
| 839 |
+
0.0053715,
|
| 840 |
+
-0.000684,
|
| 841 |
+
0.0518343,
|
| 842 |
+
-0.0072164,
|
| 843 |
+
0.0328747,
|
| 844 |
+
0.0133487,
|
| 845 |
+
0.0612634,
|
| 846 |
+
-0.0099483,
|
| 847 |
+
-0.0133693,
|
| 848 |
+
0.0050172,
|
| 849 |
+
-0.0203206,
|
| 850 |
+
0.0005791,
|
| 851 |
+
0.0136871,
|
| 852 |
+
-0.0553643,
|
| 853 |
+
0.0312551,
|
| 854 |
+
0.0846288,
|
| 855 |
+
-0.0247101,
|
| 856 |
+
0.0219906,
|
| 857 |
+
0.0775483,
|
| 858 |
+
0.0239536,
|
| 859 |
+
-6.89e-05,
|
| 860 |
+
0.0347904,
|
| 861 |
+
0.0412152,
|
| 862 |
+
-0.0377688,
|
| 863 |
+
-0.0377716,
|
| 864 |
+
-0.107596,
|
| 865 |
+
0.0521024,
|
| 866 |
+
-0.0165523,
|
| 867 |
+
-0.0078278,
|
| 868 |
+
0.0289683,
|
| 869 |
+
0.0188598,
|
| 870 |
+
-0.0292757,
|
| 871 |
+
0.0088558,
|
| 872 |
+
0.0036091,
|
| 873 |
+
-0.052127,
|
| 874 |
+
0.0031963,
|
| 875 |
+
0.012254,
|
| 876 |
+
-0.0349788,
|
| 877 |
+
0.0253031,
|
| 878 |
+
0.0281118,
|
| 879 |
+
0.0375488,
|
| 880 |
+
-0.0521213,
|
| 881 |
+
0.0173015,
|
| 882 |
+
-0.0504028,
|
| 883 |
+
0.0084912,
|
| 884 |
+
0.0133818,
|
| 885 |
+
0.0270663,
|
| 886 |
+
-0.0064787,
|
| 887 |
+
0.0219093,
|
| 888 |
+
0.0043792,
|
| 889 |
+
0.0296521,
|
| 890 |
+
-0.0094517,
|
| 891 |
+
0.0395419,
|
| 892 |
+
0.0519162,
|
| 893 |
+
0.0129672,
|
| 894 |
+
-0.0317267,
|
| 895 |
+
0.0651949,
|
| 896 |
+
0.0392844,
|
| 897 |
+
-0.0268617,
|
| 898 |
+
-0.0067156,
|
| 899 |
+
-0.2840129,
|
| 900 |
+
-0.0304729,
|
| 901 |
+
0.0118219,
|
| 902 |
+
0.044337,
|
| 903 |
+
0.3071545,
|
| 904 |
+
-0.0170552,
|
| 905 |
+
-0.0233961,
|
| 906 |
+
0.0009735,
|
| 907 |
+
0.0657148,
|
| 908 |
+
-0.0084815,
|
| 909 |
+
0.0454244,
|
| 910 |
+
-0.0533229,
|
| 911 |
+
0.0050689,
|
| 912 |
+
-0.0576392,
|
| 913 |
+
-0.0074852,
|
| 914 |
+
0.0658127,
|
| 915 |
+
-0.0100089,
|
| 916 |
+
-0.008696,
|
| 917 |
+
0.0184354,
|
| 918 |
+
-0.0248318,
|
| 919 |
+
-0.0052167,
|
| 920 |
+
0.0300652,
|
| 921 |
+
0.0693946,
|
| 922 |
+
-0.0166476,
|
| 923 |
+
-0.0157638,
|
| 924 |
+
-0.031577,
|
| 925 |
+
-0.0206295,
|
| 926 |
+
-0.0082261,
|
| 927 |
+
-0.0169719,
|
| 928 |
+
-0.0148738,
|
| 929 |
+
0.0184738,
|
| 930 |
+
0.0035616,
|
| 931 |
+
0.0650747,
|
| 932 |
+
-0.0467764,
|
| 933 |
+
0.0229762,
|
| 934 |
+
-0.019042,
|
| 935 |
+
0.0011456,
|
| 936 |
+
0.0141589,
|
| 937 |
+
-0.0006831,
|
| 938 |
+
0.0390894,
|
| 939 |
+
-0.0008999,
|
| 940 |
+
-0.0218775,
|
| 941 |
+
-0.0199317,
|
| 942 |
+
0.0193458,
|
| 943 |
+
0.0177277,
|
| 944 |
+
0.0344862,
|
| 945 |
+
0.025085,
|
| 946 |
+
-0.0311837,
|
| 947 |
+
-0.0123519,
|
| 948 |
+
-0.030943,
|
| 949 |
+
0.0107378,
|
| 950 |
+
0.0289433,
|
| 951 |
+
0.008099,
|
| 952 |
+
-0.0235138,
|
| 953 |
+
0.0252559,
|
| 954 |
+
-0.06868,
|
| 955 |
+
0.017455,
|
| 956 |
+
0.0419686,
|
| 957 |
+
0.0408476,
|
| 958 |
+
0.0254791,
|
| 959 |
+
-0.0242629,
|
| 960 |
+
-0.0216531,
|
| 961 |
+
-0.0102706,
|
| 962 |
+
0.0281387,
|
| 963 |
+
-0.0112468,
|
| 964 |
+
0.0444572,
|
| 965 |
+
-0.0275173,
|
| 966 |
+
0.0002826,
|
| 967 |
+
0.0086426,
|
| 968 |
+
-0.0212904,
|
| 969 |
+
-0.0579055,
|
| 970 |
+
0.0215879,
|
| 971 |
+
-0.0140405,
|
| 972 |
+
0.0252201,
|
| 973 |
+
0.0211788,
|
| 974 |
+
-0.0142696,
|
| 975 |
+
-0.0234745,
|
| 976 |
+
-0.0227191,
|
| 977 |
+
0.0323166,
|
| 978 |
+
0.0026156,
|
| 979 |
+
0.0010938,
|
| 980 |
+
0.0220431,
|
| 981 |
+
-0.0217016,
|
| 982 |
+
-0.0015277,
|
| 983 |
+
-0.0262872,
|
| 984 |
+
0.0427692,
|
| 985 |
+
0.0093728,
|
| 986 |
+
0.0139956,
|
| 987 |
+
0.0309493,
|
| 988 |
+
0.0226167,
|
| 989 |
+
-0.0272248,
|
| 990 |
+
0.0383349,
|
| 991 |
+
0.0175825,
|
| 992 |
+
-0.0447357,
|
| 993 |
+
-0.001296,
|
| 994 |
+
-0.0477331,
|
| 995 |
+
-0.0072662,
|
| 996 |
+
-0.0054068,
|
| 997 |
+
-0.019633,
|
| 998 |
+
0.0323781,
|
| 999 |
+
-0.0285428,
|
| 1000 |
+
0.0375163,
|
| 1001 |
+
0.000403,
|
| 1002 |
+
-0.0015404,
|
| 1003 |
+
0.0526022,
|
| 1004 |
+
0.0112267,
|
| 1005 |
+
0.0903424,
|
| 1006 |
+
-0.0030999,
|
| 1007 |
+
-0.0105379,
|
| 1008 |
+
0.0019585,
|
| 1009 |
+
0.0412905,
|
| 1010 |
+
0.0871157,
|
| 1011 |
+
0.0094184,
|
| 1012 |
+
-0.023252,
|
| 1013 |
+
-0.0291806,
|
| 1014 |
+
0.0024612,
|
| 1015 |
+
-0.0049254,
|
| 1016 |
+
-0.0248866,
|
| 1017 |
+
0.0566803,
|
| 1018 |
+
0.0235696,
|
| 1019 |
+
0.0388389,
|
| 1020 |
+
-0.0158421,
|
| 1021 |
+
-0.0421797,
|
| 1022 |
+
0.0584066,
|
| 1023 |
+
0.0114919,
|
| 1024 |
+
-0.0320118,
|
| 1025 |
+
-0.010658,
|
| 1026 |
+
-0.0164602,
|
| 1027 |
+
0.0407591,
|
| 1028 |
+
-0.0285565,
|
| 1029 |
+
0.0040359,
|
| 1030 |
+
-0.0325882,
|
| 1031 |
+
0.0494892,
|
| 1032 |
+
-0.0049442,
|
| 1033 |
+
-0.0034794,
|
| 1034 |
+
0.0059856,
|
| 1035 |
+
-0.0019577,
|
| 1036 |
+
0.0305299,
|
| 1037 |
+
-0.0129538,
|
| 1038 |
+
0.0508744,
|
| 1039 |
+
0.0091949,
|
| 1040 |
+
-0.0272065,
|
| 1041 |
+
-0.0722709,
|
| 1042 |
+
0.0146036,
|
| 1043 |
+
-0.0279869,
|
| 1044 |
+
0.042139,
|
| 1045 |
+
-0.0165828,
|
| 1046 |
+
-0.0148761,
|
| 1047 |
+
0.0239786,
|
| 1048 |
+
-0.0248741,
|
| 1049 |
+
-0.037085,
|
| 1050 |
+
-0.0145703,
|
| 1051 |
+
0.0046245,
|
| 1052 |
+
-0.0166312,
|
| 1053 |
+
0.0376871,
|
| 1054 |
+
-0.0232947,
|
| 1055 |
+
-0.0080422,
|
| 1056 |
+
-0.0138715,
|
| 1057 |
+
0.0056189,
|
| 1058 |
+
0.0084751,
|
| 1059 |
+
-0.0106014,
|
| 1060 |
+
-0.0011364,
|
| 1061 |
+
-0.004992,
|
| 1062 |
+
0.0041779,
|
| 1063 |
+
0.0412414,
|
| 1064 |
+
-0.0332662,
|
| 1065 |
+
0.0296032,
|
| 1066 |
+
-0.0010485,
|
| 1067 |
+
-0.0027399,
|
| 1068 |
+
0.0034219,
|
| 1069 |
+
-0.0054038,
|
| 1070 |
+
-0.0211052,
|
| 1071 |
+
0.0092755,
|
| 1072 |
+
-0.0005159,
|
| 1073 |
+
0.0146269,
|
| 1074 |
+
-0.0139352,
|
| 1075 |
+
0.0105331,
|
| 1076 |
+
0.0190807,
|
| 1077 |
+
0.0334958,
|
| 1078 |
+
0.0569536,
|
| 1079 |
+
-0.0163442,
|
| 1080 |
+
0.0040735,
|
| 1081 |
+
0.0267959,
|
| 1082 |
+
-0.020799,
|
| 1083 |
+
0.0280741,
|
| 1084 |
+
-0.0202714,
|
| 1085 |
+
0.037818,
|
| 1086 |
+
0.0111139,
|
| 1087 |
+
0.0095929,
|
| 1088 |
+
-0.0142725,
|
| 1089 |
+
-0.0020434,
|
| 1090 |
+
-0.0200119,
|
| 1091 |
+
0.0226144,
|
| 1092 |
+
0.0670394,
|
| 1093 |
+
-0.010802,
|
| 1094 |
+
-0.0725678,
|
| 1095 |
+
-0.0035054,
|
| 1096 |
+
-0.0368582,
|
| 1097 |
+
-0.0428894,
|
| 1098 |
+
-0.0163187,
|
| 1099 |
+
-0.0220368,
|
| 1100 |
+
-0.02653,
|
| 1101 |
+
-0.0019634,
|
| 1102 |
+
0.0407205,
|
| 1103 |
+
0.0271887,
|
| 1104 |
+
-0.0069772,
|
| 1105 |
+
-0.0075532,
|
| 1106 |
+
-0.0117692,
|
| 1107 |
+
-0.0747322,
|
| 1108 |
+
-0.0010772,
|
| 1109 |
+
0.0234839,
|
| 1110 |
+
-0.0173247,
|
| 1111 |
+
-0.025903,
|
| 1112 |
+
0.00921,
|
| 1113 |
+
0.0320551,
|
| 1114 |
+
-0.0262158,
|
| 1115 |
+
0.0017413,
|
| 1116 |
+
-0.0307316,
|
| 1117 |
+
-0.0400242,
|
| 1118 |
+
-0.0027447,
|
| 1119 |
+
-0.0017768,
|
| 1120 |
+
-0.0218839,
|
| 1121 |
+
0.0246199,
|
| 1122 |
+
0.0165034,
|
| 1123 |
+
-0.0065996,
|
| 1124 |
+
0.0269161,
|
| 1125 |
+
0.0112707,
|
| 1126 |
+
0.0273772,
|
| 1127 |
+
-0.0098787,
|
| 1128 |
+
-0.0179007,
|
| 1129 |
+
-0.1398847,
|
| 1130 |
+
-0.010778,
|
| 1131 |
+
3.89e-05,
|
| 1132 |
+
0.0183281,
|
| 1133 |
+
0.0210552,
|
| 1134 |
+
0.0155068,
|
| 1135 |
+
0.0270709,
|
| 1136 |
+
-0.0392508,
|
| 1137 |
+
0.0251768,
|
| 1138 |
+
0.0052068,
|
| 1139 |
+
0.0513872,
|
| 1140 |
+
0.0119341,
|
| 1141 |
+
-0.0019493,
|
| 1142 |
+
-0.0304656,
|
| 1143 |
+
0.0209176,
|
| 1144 |
+
0.0601201,
|
| 1145 |
+
0.0198429,
|
| 1146 |
+
0.0126918,
|
| 1147 |
+
0.0168446,
|
| 1148 |
+
-0.0221235,
|
| 1149 |
+
0.0020831,
|
| 1150 |
+
0.0003669,
|
| 1151 |
+
0.0489466,
|
| 1152 |
+
-0.0236555,
|
| 1153 |
+
-0.0086362,
|
| 1154 |
+
-0.0242799,
|
| 1155 |
+
0.016296,
|
| 1156 |
+
-0.0046918,
|
| 1157 |
+
0.0240075,
|
| 1158 |
+
0.0036074,
|
| 1159 |
+
0.0125185,
|
| 1160 |
+
0.0037332,
|
| 1161 |
+
0.0026463,
|
| 1162 |
+
0.0287793,
|
| 1163 |
+
0.0442534,
|
| 1164 |
+
-0.0008671,
|
| 1165 |
+
-0.0068142,
|
| 1166 |
+
-0.0095245,
|
| 1167 |
+
0.0267455,
|
| 1168 |
+
0.0584806,
|
| 1169 |
+
0.0285134,
|
| 1170 |
+
0.0102658,
|
| 1171 |
+
0.0434944,
|
| 1172 |
+
0.0367759,
|
| 1173 |
+
-0.0005722,
|
| 1174 |
+
0.0451684,
|
| 1175 |
+
0.0049044,
|
| 1176 |
+
0.0289554,
|
| 1177 |
+
0.0256482,
|
| 1178 |
+
-0.0042619,
|
| 1179 |
+
0.286271,
|
| 1180 |
+
0.0199974,
|
| 1181 |
+
0.0035183,
|
| 1182 |
+
0.0286582,
|
| 1183 |
+
0.0405934,
|
| 1184 |
+
-0.0167135,
|
| 1185 |
+
0.0291236,
|
| 1186 |
+
-0.0018019,
|
| 1187 |
+
-0.0219017,
|
| 1188 |
+
0.003888,
|
| 1189 |
+
0.0021741,
|
| 1190 |
+
0.0179549,
|
| 1191 |
+
-0.0181287,
|
| 1192 |
+
-0.003402,
|
| 1193 |
+
0.0241312,
|
| 1194 |
+
-0.0023668,
|
| 1195 |
+
-0.0459792,
|
| 1196 |
+
0.0055813,
|
| 1197 |
+
-0.005714,
|
| 1198 |
+
-0.0212274,
|
| 1199 |
+
0.0166356,
|
| 1200 |
+
0.0252439,
|
| 1201 |
+
0.0101637,
|
| 1202 |
+
0.0045283,
|
| 1203 |
+
-0.0259132,
|
| 1204 |
+
-0.0325882,
|
| 1205 |
+
-0.0176896,
|
| 1206 |
+
-0.0400428,
|
| 1207 |
+
0.0077737,
|
| 1208 |
+
-0.0154222,
|
| 1209 |
+
0.0193038,
|
| 1210 |
+
-0.0162069,
|
| 1211 |
+
0.0035849,
|
| 1212 |
+
0.0416583,
|
| 1213 |
+
0.009458,
|
| 1214 |
+
-0.0236569,
|
| 1215 |
+
0.0048055,
|
| 1216 |
+
-0.0021436,
|
| 1217 |
+
0.0053086,
|
| 1218 |
+
0.0072111,
|
| 1219 |
+
0.0059566,
|
| 1220 |
+
-0.0258894,
|
| 1221 |
+
0.0079106,
|
| 1222 |
+
0.0285769,
|
| 1223 |
+
0.0098245,
|
| 1224 |
+
0.036957,
|
| 1225 |
+
0.0063089,
|
| 1226 |
+
-0.0089957,
|
| 1227 |
+
0.0008741,
|
| 1228 |
+
0.0068467,
|
| 1229 |
+
-0.0078629,
|
| 1230 |
+
-0.0002527,
|
| 1231 |
+
-0.0080054,
|
| 1232 |
+
-0.0158927,
|
| 1233 |
+
-0.0252407,
|
| 1234 |
+
-0.0111276,
|
| 1235 |
+
-0.028465,
|
| 1236 |
+
-0.0106257,
|
| 1237 |
+
0.0402076,
|
| 1238 |
+
0.0120167,
|
| 1239 |
+
0.0064657,
|
| 1240 |
+
0.0467545,
|
| 1241 |
+
-0.0183397,
|
| 1242 |
+
-0.000893,
|
| 1243 |
+
-0.0129492,
|
| 1244 |
+
-0.0484937,
|
| 1245 |
+
-0.0369712,
|
| 1246 |
+
0.0168339,
|
| 1247 |
+
0.0526176,
|
| 1248 |
+
-0.0011955,
|
| 1249 |
+
-0.0661879,
|
| 1250 |
+
-0.0166612,
|
| 1251 |
+
-0.0033391,
|
| 1252 |
+
0.0409913,
|
| 1253 |
+
-0.0211322,
|
| 1254 |
+
0.003656,
|
| 1255 |
+
-0.0014469,
|
| 1256 |
+
-0.0320232,
|
| 1257 |
+
-0.0187179,
|
| 1258 |
+
-0.0282663,
|
| 1259 |
+
0.0106685,
|
| 1260 |
+
0.0107463,
|
| 1261 |
+
-0.0215654,
|
| 1262 |
+
-0.0187622,
|
| 1263 |
+
-0.0152856,
|
| 1264 |
+
0.0209789,
|
| 1265 |
+
0.0112883,
|
| 1266 |
+
0.0126507,
|
| 1267 |
+
-0.0026165,
|
| 1268 |
+
0.0558417,
|
| 1269 |
+
-0.0469727,
|
| 1270 |
+
-0.0207666,
|
| 1271 |
+
-0.0208326,
|
| 1272 |
+
-0.0426097,
|
| 1273 |
+
-0.00665,
|
| 1274 |
+
0.0050596,
|
| 1275 |
+
0.0485706,
|
| 1276 |
+
-0.0045574,
|
| 1277 |
+
0.023139,
|
| 1278 |
+
0.0228496,
|
| 1279 |
+
-0.001325,
|
| 1280 |
+
-0.0225157,
|
| 1281 |
+
0.0214948,
|
| 1282 |
+
-0.0123868,
|
| 1283 |
+
-0.0100371,
|
| 1284 |
+
0.0241164,
|
| 1285 |
+
-0.0104662,
|
| 1286 |
+
-0.0054405,
|
| 1287 |
+
0.0180619,
|
| 1288 |
+
-0.0388385,
|
| 1289 |
+
0.005823,
|
| 1290 |
+
0.0250867,
|
| 1291 |
+
0.0443838,
|
| 1292 |
+
0.0081462,
|
| 1293 |
+
-0.0252075,
|
| 1294 |
+
0.0075606,
|
| 1295 |
+
-0.134257,
|
| 1296 |
+
0.0238103,
|
| 1297 |
+
0.0403092,
|
| 1298 |
+
-0.033447,
|
| 1299 |
+
0.0609221,
|
| 1300 |
+
0.0102637,
|
| 1301 |
+
0.031284,
|
| 1302 |
+
-0.0042293,
|
| 1303 |
+
0.0090502,
|
| 1304 |
+
0.0206708,
|
| 1305 |
+
0.0153386,
|
| 1306 |
+
-0.0074572,
|
| 1307 |
+
0.0034939,
|
| 1308 |
+
-0.0141006,
|
| 1309 |
+
-0.0052234,
|
| 1310 |
+
0.0150197,
|
| 1311 |
+
-0.016243,
|
| 1312 |
+
-0.0435209,
|
| 1313 |
+
-0.0013051,
|
| 1314 |
+
0.0232986,
|
| 1315 |
+
-0.0497696,
|
| 1316 |
+
-0.0017224,
|
| 1317 |
+
-0.0076917,
|
| 1318 |
+
0.0083871,
|
| 1319 |
+
-0.0113315,
|
| 1320 |
+
-0.0265682,
|
| 1321 |
+
-0.0089959,
|
| 1322 |
+
-0.0379045,
|
| 1323 |
+
-0.0246575,
|
| 1324 |
+
-0.0098334,
|
| 1325 |
+
-0.004501,
|
| 1326 |
+
0.0203684,
|
| 1327 |
+
-0.0072931,
|
| 1328 |
+
0.0307912,
|
| 1329 |
+
0.0030027,
|
| 1330 |
+
-0.0012177,
|
| 1331 |
+
0.0056174,
|
| 1332 |
+
-0.0066306,
|
| 1333 |
+
0.017854,
|
| 1334 |
+
-0.0046366,
|
| 1335 |
+
-0.0017239,
|
| 1336 |
+
0.0141466,
|
| 1337 |
+
0.0568046,
|
| 1338 |
+
0.0336952,
|
| 1339 |
+
0.0252416,
|
| 1340 |
+
0.0015724,
|
| 1341 |
+
-0.0107673,
|
| 1342 |
+
-0.2303559,
|
| 1343 |
+
-0.0640194,
|
| 1344 |
+
0.002422,
|
| 1345 |
+
0.0279597,
|
| 1346 |
+
-4.16e-05,
|
| 1347 |
+
0.0091563,
|
| 1348 |
+
-0.0084589,
|
| 1349 |
+
0.0591685,
|
| 1350 |
+
0.006867,
|
| 1351 |
+
0.0021346,
|
| 1352 |
+
-0.0314672,
|
| 1353 |
+
0.0068378,
|
| 1354 |
+
-0.0169414,
|
| 1355 |
+
0.011671,
|
| 1356 |
+
-0.0496234,
|
| 1357 |
+
0.0066084,
|
| 1358 |
+
-0.022313,
|
| 1359 |
+
0.025086,
|
| 1360 |
+
-0.0206278,
|
| 1361 |
+
-0.0088476,
|
| 1362 |
+
0.0645719,
|
| 1363 |
+
-0.0277695,
|
| 1364 |
+
0.033116,
|
| 1365 |
+
0.0418436,
|
| 1366 |
+
0.0363094,
|
| 1367 |
+
0.0308525,
|
| 1368 |
+
-0.0173255,
|
| 1369 |
+
-0.0032406,
|
| 1370 |
+
0.0002146,
|
| 1371 |
+
-0.0165661,
|
| 1372 |
+
-0.0121446,
|
| 1373 |
+
-0.031358,
|
| 1374 |
+
-0.0283029,
|
| 1375 |
+
0.0191103,
|
| 1376 |
+
-0.0352197,
|
| 1377 |
+
0.0368655,
|
| 1378 |
+
0.025689,
|
| 1379 |
+
-0.0201068,
|
| 1380 |
+
0.0135953,
|
| 1381 |
+
-0.0169755,
|
| 1382 |
+
-0.0089089,
|
| 1383 |
+
-0.0187155,
|
| 1384 |
+
0.0421347,
|
| 1385 |
+
0.0302731,
|
| 1386 |
+
0.0062562,
|
| 1387 |
+
-0.009368,
|
| 1388 |
+
-0.0101766,
|
| 1389 |
+
-0.0238247,
|
| 1390 |
+
0.0058758,
|
| 1391 |
+
-0.0011821,
|
| 1392 |
+
0.0071059,
|
| 1393 |
+
-0.0118324,
|
| 1394 |
+
0.0148747,
|
| 1395 |
+
-0.0112144,
|
| 1396 |
+
-0.0351373,
|
| 1397 |
+
-0.0065663,
|
| 1398 |
+
-0.0335906,
|
| 1399 |
+
0.013,
|
| 1400 |
+
0.0116598,
|
| 1401 |
+
0.0019158,
|
| 1402 |
+
-0.0010235,
|
| 1403 |
+
0.0066092,
|
| 1404 |
+
-0.0084019,
|
| 1405 |
+
0.0062142,
|
| 1406 |
+
-0.0070109,
|
| 1407 |
+
-0.0236729,
|
| 1408 |
+
-0.0142321,
|
| 1409 |
+
-1.57e-05,
|
| 1410 |
+
0.0091445,
|
| 1411 |
+
0.0293063,
|
| 1412 |
+
0.0035479,
|
| 1413 |
+
0.0109397,
|
| 1414 |
+
-0.0091237,
|
| 1415 |
+
0.0246063,
|
| 1416 |
+
0.0384086,
|
| 1417 |
+
-0.0161373,
|
| 1418 |
+
-0.0127944,
|
| 1419 |
+
0.0076104,
|
| 1420 |
+
0.0206074,
|
| 1421 |
+
0.0328081,
|
| 1422 |
+
0.0080331,
|
| 1423 |
+
0.0056978,
|
| 1424 |
+
-0.0059247,
|
| 1425 |
+
0.008972,
|
| 1426 |
+
-0.014289,
|
| 1427 |
+
0.0126287,
|
| 1428 |
+
-0.0137993,
|
| 1429 |
+
0.0074459,
|
| 1430 |
+
0.0032179,
|
| 1431 |
+
-0.0166674,
|
| 1432 |
+
0.0199677,
|
| 1433 |
+
0.025108,
|
| 1434 |
+
-0.0031548,
|
| 1435 |
+
0.0136954,
|
| 1436 |
+
-0.0395122,
|
| 1437 |
+
0.041827,
|
| 1438 |
+
0.002465,
|
| 1439 |
+
0.050934,
|
| 1440 |
+
0.0119671,
|
| 1441 |
+
-0.0135284,
|
| 1442 |
+
0.0051843,
|
| 1443 |
+
-0.0149521,
|
| 1444 |
+
0.0001085,
|
| 1445 |
+
-0.0003902,
|
| 1446 |
+
-0.0051798,
|
| 1447 |
+
0.0069398,
|
| 1448 |
+
0.0121671,
|
| 1449 |
+
-0.0026059,
|
| 1450 |
+
0.0065326,
|
| 1451 |
+
-0.0162301,
|
| 1452 |
+
-0.0196698,
|
| 1453 |
+
0.0183328,
|
| 1454 |
+
-0.0255839,
|
| 1455 |
+
0.0067643,
|
| 1456 |
+
-0.023012,
|
| 1457 |
+
0.0014238,
|
| 1458 |
+
-0.0116004,
|
| 1459 |
+
-0.0156751,
|
| 1460 |
+
-0.0077851,
|
| 1461 |
+
-0.0268469,
|
| 1462 |
+
-0.0117132,
|
| 1463 |
+
-0.0267632,
|
| 1464 |
+
-0.012754,
|
| 1465 |
+
-0.0475532,
|
| 1466 |
+
0.009542,
|
| 1467 |
+
0.0140083,
|
| 1468 |
+
-0.0277571,
|
| 1469 |
+
-0.0022977,
|
| 1470 |
+
0.0071351,
|
| 1471 |
+
0.0189572,
|
| 1472 |
+
-0.0126652,
|
| 1473 |
+
0.0310627,
|
| 1474 |
+
0.0322423,
|
| 1475 |
+
-0.0091997,
|
| 1476 |
+
0.0089104,
|
| 1477 |
+
-0.0056425,
|
| 1478 |
+
-0.024342,
|
| 1479 |
+
0.0111729,
|
| 1480 |
+
0.0005422,
|
| 1481 |
+
0.0169579,
|
| 1482 |
+
0.0189936,
|
| 1483 |
+
-0.0415399,
|
| 1484 |
+
0.021819,
|
| 1485 |
+
-0.0405018,
|
| 1486 |
+
0.0107911,
|
| 1487 |
+
-0.0077887,
|
| 1488 |
+
0.048318,
|
| 1489 |
+
0.0013121,
|
| 1490 |
+
0.017062,
|
| 1491 |
+
0.0367175,
|
| 1492 |
+
0.0018443,
|
| 1493 |
+
-0.0108327,
|
| 1494 |
+
0.0287105,
|
| 1495 |
+
-0.024023,
|
| 1496 |
+
-0.0027745,
|
| 1497 |
+
0.0187717,
|
| 1498 |
+
0.0018345,
|
| 1499 |
+
-0.0108974,
|
| 1500 |
+
-0.0224133,
|
| 1501 |
+
0.0256002,
|
| 1502 |
+
-0.0080634,
|
| 1503 |
+
-0.0265149,
|
| 1504 |
+
0.0517693,
|
| 1505 |
+
0.0269079,
|
| 1506 |
+
0.0251603,
|
| 1507 |
+
0.0053892,
|
| 1508 |
+
-0.0136671,
|
| 1509 |
+
0.0394292,
|
| 1510 |
+
0.0463492,
|
| 1511 |
+
0.0071633,
|
| 1512 |
+
-0.0029102,
|
| 1513 |
+
-0.0272801,
|
| 1514 |
+
0.0164657,
|
| 1515 |
+
-0.0011746,
|
| 1516 |
+
0.0025788,
|
| 1517 |
+
0.0265455,
|
| 1518 |
+
-0.0069759,
|
| 1519 |
+
0.0037236,
|
| 1520 |
+
0.2317612,
|
| 1521 |
+
0.0251917,
|
| 1522 |
+
-0.0107766,
|
| 1523 |
+
-0.017,
|
| 1524 |
+
-0.0060105,
|
| 1525 |
+
-0.0122113,
|
| 1526 |
+
0.0047453,
|
| 1527 |
+
0.0309908,
|
| 1528 |
+
-0.0087099,
|
| 1529 |
+
0.0553553,
|
| 1530 |
+
0.012144,
|
| 1531 |
+
0.0458252,
|
| 1532 |
+
0.046074,
|
| 1533 |
+
-0.0357232,
|
| 1534 |
+
-0.0233153,
|
| 1535 |
+
0.0017353,
|
| 1536 |
+
-0.0060374,
|
| 1537 |
+
-0.0035919,
|
| 1538 |
+
0.020817,
|
| 1539 |
+
0.0342276,
|
| 1540 |
+
0.0057531,
|
| 1541 |
+
-0.0323938,
|
| 1542 |
+
-0.0293829,
|
| 1543 |
+
0.0287774,
|
| 1544 |
+
-0.0663916,
|
| 1545 |
+
0.0148558,
|
| 1546 |
+
-0.0343938,
|
| 1547 |
+
0.0079967,
|
| 1548 |
+
-0.0117679,
|
| 1549 |
+
0.0250412,
|
| 1550 |
+
0.0329488,
|
| 1551 |
+
-0.0064652,
|
| 1552 |
+
-0.0493818,
|
| 1553 |
+
0.0227841,
|
| 1554 |
+
-0.0245425,
|
| 1555 |
+
0.0195178
|
| 1556 |
+
]
|
| 1557 |
+
},
|
| 1558 |
+
{
|
| 1559 |
+
"kind": "document",
|
| 1560 |
+
"text": "Lista della spesa: latte d'avena, due limoni, riso basmati, coriandolo e croccantini per il cane.",
|
| 1561 |
+
"embedding": [
|
| 1562 |
+
0.0170374,
|
| 1563 |
+
-0.044635,
|
| 1564 |
+
-0.0047019,
|
| 1565 |
+
-0.0229727,
|
| 1566 |
+
0.0358784,
|
| 1567 |
+
0.0070688,
|
| 1568 |
+
0.0636706,
|
| 1569 |
+
-0.0095134,
|
| 1570 |
+
0.0034211,
|
| 1571 |
+
-0.0978533,
|
| 1572 |
+
0.02883,
|
| 1573 |
+
0.0047634,
|
| 1574 |
+
-0.045708,
|
| 1575 |
+
-0.0344993,
|
| 1576 |
+
0.0595915,
|
| 1577 |
+
-0.0239481,
|
| 1578 |
+
0.0599293,
|
| 1579 |
+
-0.0019344,
|
| 1580 |
+
0.0037584,
|
| 1581 |
+
0.0237904,
|
| 1582 |
+
-0.0470828,
|
| 1583 |
+
0.0045741,
|
| 1584 |
+
-0.0460006,
|
| 1585 |
+
0.0576605,
|
| 1586 |
+
0.0114495,
|
| 1587 |
+
-0.012251,
|
| 1588 |
+
0.0123977,
|
| 1589 |
+
-0.0349354,
|
| 1590 |
+
-0.018569,
|
| 1591 |
+
0.1733741,
|
| 1592 |
+
-0.0089963,
|
| 1593 |
+
-0.023553,
|
| 1594 |
+
-0.0083854,
|
| 1595 |
+
-0.0460702,
|
| 1596 |
+
0.0420627,
|
| 1597 |
+
-0.0377996,
|
| 1598 |
+
-0.0119087,
|
| 1599 |
+
-0.0360215,
|
| 1600 |
+
-0.0097449,
|
| 1601 |
+
-0.0203342,
|
| 1602 |
+
-0.0063737,
|
| 1603 |
+
-0.0413225,
|
| 1604 |
+
0.0101058,
|
| 1605 |
+
0.0178437,
|
| 1606 |
+
0.0132315,
|
| 1607 |
+
0.0265682,
|
| 1608 |
+
0.0069234,
|
| 1609 |
+
0.026995,
|
| 1610 |
+
0.003121,
|
| 1611 |
+
0.0125665,
|
| 1612 |
+
0.1006458,
|
| 1613 |
+
-0.0385414,
|
| 1614 |
+
0.0088992,
|
| 1615 |
+
0.1112718,
|
| 1616 |
+
0.0027232,
|
| 1617 |
+
0.0248341,
|
| 1618 |
+
0.0098803,
|
| 1619 |
+
0.0200804,
|
| 1620 |
+
0.0097934,
|
| 1621 |
+
0.0446817,
|
| 1622 |
+
0.0187769,
|
| 1623 |
+
0.0145933,
|
| 1624 |
+
-0.0395547,
|
| 1625 |
+
-0.021266,
|
| 1626 |
+
-0.0572196,
|
| 1627 |
+
0.0402977,
|
| 1628 |
+
0.0587767,
|
| 1629 |
+
0.0088406,
|
| 1630 |
+
-0.0020986,
|
| 1631 |
+
-0.042525,
|
| 1632 |
+
-0.0556863,
|
| 1633 |
+
0.0168584,
|
| 1634 |
+
0.0026634,
|
| 1635 |
+
0.0761056,
|
| 1636 |
+
-0.0228929,
|
| 1637 |
+
-0.0022126,
|
| 1638 |
+
-0.1013387,
|
| 1639 |
+
-0.0081393,
|
| 1640 |
+
-0.0288168,
|
| 1641 |
+
0.0312328,
|
| 1642 |
+
-0.0225599,
|
| 1643 |
+
-0.0985526,
|
| 1644 |
+
0.0337848,
|
| 1645 |
+
0.0062871,
|
| 1646 |
+
0.0147591,
|
| 1647 |
+
-0.0307018,
|
| 1648 |
+
-0.0281429,
|
| 1649 |
+
-0.0074711,
|
| 1650 |
+
-0.0092093,
|
| 1651 |
+
0.0195643,
|
| 1652 |
+
-0.0058784,
|
| 1653 |
+
0.0078961,
|
| 1654 |
+
-0.0018558,
|
| 1655 |
+
-0.0332802,
|
| 1656 |
+
-0.0126549,
|
| 1657 |
+
0.0003527,
|
| 1658 |
+
-0.0325215,
|
| 1659 |
+
0.0357396,
|
| 1660 |
+
-0.0350979,
|
| 1661 |
+
0.0203608,
|
| 1662 |
+
0.0657294,
|
| 1663 |
+
-0.0410924,
|
| 1664 |
+
-0.0014359,
|
| 1665 |
+
0.0259345,
|
| 1666 |
+
-0.027136,
|
| 1667 |
+
0.0457944,
|
| 1668 |
+
-0.0290958,
|
| 1669 |
+
0.0276919,
|
| 1670 |
+
-0.0201393,
|
| 1671 |
+
-0.0269499,
|
| 1672 |
+
-0.0059097,
|
| 1673 |
+
-0.238396,
|
| 1674 |
+
-0.012558,
|
| 1675 |
+
0.0606131,
|
| 1676 |
+
-0.0038405,
|
| 1677 |
+
0.3050316,
|
| 1678 |
+
0.0053073,
|
| 1679 |
+
-0.0133485,
|
| 1680 |
+
0.0361453,
|
| 1681 |
+
0.0484401,
|
| 1682 |
+
-0.0515617,
|
| 1683 |
+
0.0310277,
|
| 1684 |
+
-0.0136832,
|
| 1685 |
+
-0.0556593,
|
| 1686 |
+
-0.0321827,
|
| 1687 |
+
-0.0284951,
|
| 1688 |
+
0.0439218,
|
| 1689 |
+
-0.0254445,
|
| 1690 |
+
-0.0127552,
|
| 1691 |
+
0.0721373,
|
| 1692 |
+
0.0206097,
|
| 1693 |
+
0.0132098,
|
| 1694 |
+
-0.0089761,
|
| 1695 |
+
0.0079829,
|
| 1696 |
+
-0.0075321,
|
| 1697 |
+
0.0093124,
|
| 1698 |
+
-0.0187054,
|
| 1699 |
+
-0.0464838,
|
| 1700 |
+
0.057731,
|
| 1701 |
+
0.0051577,
|
| 1702 |
+
0.0629364,
|
| 1703 |
+
0.0032504,
|
| 1704 |
+
0.0047574,
|
| 1705 |
+
0.0042688,
|
| 1706 |
+
-0.0160958,
|
| 1707 |
+
0.0298165,
|
| 1708 |
+
-0.0073184,
|
| 1709 |
+
-0.0608206,
|
| 1710 |
+
0.0457134,
|
| 1711 |
+
0.0437723,
|
| 1712 |
+
-0.0025557,
|
| 1713 |
+
-0.0156474,
|
| 1714 |
+
-0.0127475,
|
| 1715 |
+
0.0342485,
|
| 1716 |
+
-0.0275448,
|
| 1717 |
+
-0.001125,
|
| 1718 |
+
0.0481392,
|
| 1719 |
+
-0.0004659,
|
| 1720 |
+
0.015135,
|
| 1721 |
+
0.0579678,
|
| 1722 |
+
-0.0078953,
|
| 1723 |
+
0.0246101,
|
| 1724 |
+
-0.0220383,
|
| 1725 |
+
0.026813,
|
| 1726 |
+
0.0231499,
|
| 1727 |
+
-0.0199915,
|
| 1728 |
+
0.00961,
|
| 1729 |
+
-0.0206561,
|
| 1730 |
+
0.0142155,
|
| 1731 |
+
0.0067647,
|
| 1732 |
+
0.0095087,
|
| 1733 |
+
0.0523173,
|
| 1734 |
+
-0.0174483,
|
| 1735 |
+
0.0398959,
|
| 1736 |
+
0.0364616,
|
| 1737 |
+
0.0350139,
|
| 1738 |
+
-0.0067026,
|
| 1739 |
+
-0.0409621,
|
| 1740 |
+
0.0211005,
|
| 1741 |
+
-0.0012647,
|
| 1742 |
+
-0.0170568,
|
| 1743 |
+
-0.0003618,
|
| 1744 |
+
-0.0184823,
|
| 1745 |
+
-0.0114733,
|
| 1746 |
+
0.0172884,
|
| 1747 |
+
0.0080164,
|
| 1748 |
+
-0.0111308,
|
| 1749 |
+
0.0265054,
|
| 1750 |
+
-0.0213923,
|
| 1751 |
+
-0.0088149,
|
| 1752 |
+
0.0117311,
|
| 1753 |
+
-0.0181282,
|
| 1754 |
+
0.0290931,
|
| 1755 |
+
0.007055,
|
| 1756 |
+
-0.0008388,
|
| 1757 |
+
0.0112384,
|
| 1758 |
+
0.0431553,
|
| 1759 |
+
0.0231443,
|
| 1760 |
+
0.0070247,
|
| 1761 |
+
0.0454419,
|
| 1762 |
+
0.0255751,
|
| 1763 |
+
0.0369707,
|
| 1764 |
+
-0.003059,
|
| 1765 |
+
-0.0032789,
|
| 1766 |
+
-0.0741629,
|
| 1767 |
+
-0.0015363,
|
| 1768 |
+
0.0230501,
|
| 1769 |
+
0.0400231,
|
| 1770 |
+
-0.0153894,
|
| 1771 |
+
-0.0068579,
|
| 1772 |
+
0.0070991,
|
| 1773 |
+
-0.0205951,
|
| 1774 |
+
-0.0253457,
|
| 1775 |
+
0.0338322,
|
| 1776 |
+
0.0225516,
|
| 1777 |
+
0.0175246,
|
| 1778 |
+
-0.0288264,
|
| 1779 |
+
0.0823566,
|
| 1780 |
+
0.0081965,
|
| 1781 |
+
-0.0106182,
|
| 1782 |
+
0.0156874,
|
| 1783 |
+
0.0001789,
|
| 1784 |
+
0.0148504,
|
| 1785 |
+
0.0117717,
|
| 1786 |
+
0.0237754,
|
| 1787 |
+
-0.0086117,
|
| 1788 |
+
0.0141754,
|
| 1789 |
+
0.040231,
|
| 1790 |
+
-0.0331057,
|
| 1791 |
+
0.0583299,
|
| 1792 |
+
0.0457594,
|
| 1793 |
+
0.0117246,
|
| 1794 |
+
0.0342566,
|
| 1795 |
+
-0.0196156,
|
| 1796 |
+
0.0901752,
|
| 1797 |
+
-0.0388455,
|
| 1798 |
+
-0.0651512,
|
| 1799 |
+
0.0140004,
|
| 1800 |
+
0.0339629,
|
| 1801 |
+
0.042578,
|
| 1802 |
+
-0.0354294,
|
| 1803 |
+
0.0235349,
|
| 1804 |
+
-0.0210134,
|
| 1805 |
+
0.0416971,
|
| 1806 |
+
0.0437426,
|
| 1807 |
+
-0.0424866,
|
| 1808 |
+
-0.0298579,
|
| 1809 |
+
0.0389892,
|
| 1810 |
+
0.0169898,
|
| 1811 |
+
-0.0264389,
|
| 1812 |
+
0.034758,
|
| 1813 |
+
0.0391694,
|
| 1814 |
+
0.0372253,
|
| 1815 |
+
-0.0370816,
|
| 1816 |
+
-0.009757,
|
| 1817 |
+
-0.0709113,
|
| 1818 |
+
0.0470219,
|
| 1819 |
+
0.0118337,
|
| 1820 |
+
-0.0328658,
|
| 1821 |
+
-0.0113846,
|
| 1822 |
+
-0.0286826,
|
| 1823 |
+
0.0172676,
|
| 1824 |
+
0.0031711,
|
| 1825 |
+
-0.0201598,
|
| 1826 |
+
0.0341731,
|
| 1827 |
+
0.023543,
|
| 1828 |
+
0.0063189,
|
| 1829 |
+
0.0039108,
|
| 1830 |
+
0.0083759,
|
| 1831 |
+
0.0220321,
|
| 1832 |
+
-0.0339149,
|
| 1833 |
+
0.0470052,
|
| 1834 |
+
0.0067079,
|
| 1835 |
+
-0.0155743,
|
| 1836 |
+
0.0610002,
|
| 1837 |
+
0.0137803,
|
| 1838 |
+
-0.0104527,
|
| 1839 |
+
0.0080595,
|
| 1840 |
+
-0.0055056,
|
| 1841 |
+
-0.0366666,
|
| 1842 |
+
0.0390951,
|
| 1843 |
+
0.0451955,
|
| 1844 |
+
6.55e-05,
|
| 1845 |
+
-0.0554373,
|
| 1846 |
+
0.0325824,
|
| 1847 |
+
0.0034411,
|
| 1848 |
+
-0.0094541,
|
| 1849 |
+
0.0053737,
|
| 1850 |
+
-0.0002473,
|
| 1851 |
+
-0.0061789,
|
| 1852 |
+
-0.007296,
|
| 1853 |
+
-0.0111808,
|
| 1854 |
+
-0.0176897,
|
| 1855 |
+
0.0280049,
|
| 1856 |
+
0.0177174,
|
| 1857 |
+
-0.0380043,
|
| 1858 |
+
-0.0497624,
|
| 1859 |
+
-0.0087323,
|
| 1860 |
+
0.0110018,
|
| 1861 |
+
-0.0265812,
|
| 1862 |
+
0.0269822,
|
| 1863 |
+
0.0264606,
|
| 1864 |
+
0.009159,
|
| 1865 |
+
-0.0071128,
|
| 1866 |
+
0.0436609,
|
| 1867 |
+
0.0021422,
|
| 1868 |
+
0.0072964,
|
| 1869 |
+
0.0140107,
|
| 1870 |
+
-0.0042789,
|
| 1871 |
+
0.0438331,
|
| 1872 |
+
-0.0138949,
|
| 1873 |
+
-0.0173498,
|
| 1874 |
+
-0.0026295,
|
| 1875 |
+
-0.025416,
|
| 1876 |
+
0.0098757,
|
| 1877 |
+
0.0498686,
|
| 1878 |
+
0.006806,
|
| 1879 |
+
-0.0143414,
|
| 1880 |
+
0.0148724,
|
| 1881 |
+
-0.0585902,
|
| 1882 |
+
-0.0366992,
|
| 1883 |
+
0.0212392,
|
| 1884 |
+
-0.0159478,
|
| 1885 |
+
-0.0019213,
|
| 1886 |
+
0.0226183,
|
| 1887 |
+
0.0002265,
|
| 1888 |
+
0.0102534,
|
| 1889 |
+
0.0026059,
|
| 1890 |
+
0.043171,
|
| 1891 |
+
0.0120144,
|
| 1892 |
+
-0.0175667,
|
| 1893 |
+
0.0179332,
|
| 1894 |
+
-0.0314321,
|
| 1895 |
+
0.0135192,
|
| 1896 |
+
0.0274326,
|
| 1897 |
+
-0.0141854,
|
| 1898 |
+
-0.0160875,
|
| 1899 |
+
0.0610543,
|
| 1900 |
+
0.0045262,
|
| 1901 |
+
-0.0099408,
|
| 1902 |
+
-0.0283346,
|
| 1903 |
+
-0.1284437,
|
| 1904 |
+
0.0084628,
|
| 1905 |
+
0.0090422,
|
| 1906 |
+
-0.0450438,
|
| 1907 |
+
-0.0010222,
|
| 1908 |
+
-0.0016807,
|
| 1909 |
+
0.0036075,
|
| 1910 |
+
-0.0028395,
|
| 1911 |
+
0.0069319,
|
| 1912 |
+
0.0187971,
|
| 1913 |
+
0.0372987,
|
| 1914 |
+
-0.0047398,
|
| 1915 |
+
-0.0177076,
|
| 1916 |
+
-0.0372152,
|
| 1917 |
+
-0.0088774,
|
| 1918 |
+
0.0449237,
|
| 1919 |
+
-0.031268,
|
| 1920 |
+
-0.0450276,
|
| 1921 |
+
0.0441433,
|
| 1922 |
+
-0.0191669,
|
| 1923 |
+
0.0403419,
|
| 1924 |
+
0.0248082,
|
| 1925 |
+
-0.0168166,
|
| 1926 |
+
-0.0134252,
|
| 1927 |
+
-0.0117406,
|
| 1928 |
+
-0.0309384,
|
| 1929 |
+
0.0038555,
|
| 1930 |
+
0.0036268,
|
| 1931 |
+
-0.0663664,
|
| 1932 |
+
-0.0120338,
|
| 1933 |
+
0.0290708,
|
| 1934 |
+
-0.0015897,
|
| 1935 |
+
0.0098999,
|
| 1936 |
+
0.0051596,
|
| 1937 |
+
0.0095467,
|
| 1938 |
+
0.0021275,
|
| 1939 |
+
0.0100098,
|
| 1940 |
+
0.0147187,
|
| 1941 |
+
0.0123174,
|
| 1942 |
+
-0.0022185,
|
| 1943 |
+
-0.0318748,
|
| 1944 |
+
0.0186827,
|
| 1945 |
+
-0.0021616,
|
| 1946 |
+
0.0121651,
|
| 1947 |
+
-0.0037288,
|
| 1948 |
+
0.003544,
|
| 1949 |
+
-0.0247473,
|
| 1950 |
+
-0.0347024,
|
| 1951 |
+
0.0048945,
|
| 1952 |
+
-0.0175091,
|
| 1953 |
+
0.2937374,
|
| 1954 |
+
0.00277,
|
| 1955 |
+
-0.0044884,
|
| 1956 |
+
-0.0576349,
|
| 1957 |
+
-0.0149706,
|
| 1958 |
+
-0.0010073,
|
| 1959 |
+
-0.0486672,
|
| 1960 |
+
0.0168386,
|
| 1961 |
+
-0.0088559,
|
| 1962 |
+
0.0072639,
|
| 1963 |
+
-0.0239114,
|
| 1964 |
+
0.0257517,
|
| 1965 |
+
-0.0026901,
|
| 1966 |
+
0.018372,
|
| 1967 |
+
0.0177089,
|
| 1968 |
+
0.0407887,
|
| 1969 |
+
0.002236,
|
| 1970 |
+
-0.019927,
|
| 1971 |
+
0.0092718,
|
| 1972 |
+
-0.0381663,
|
| 1973 |
+
-0.031039,
|
| 1974 |
+
0.0386571,
|
| 1975 |
+
0.0191666,
|
| 1976 |
+
0.0158292,
|
| 1977 |
+
0.0121963,
|
| 1978 |
+
0.0132033,
|
| 1979 |
+
0.0107741,
|
| 1980 |
+
-0.0330748,
|
| 1981 |
+
-0.0244974,
|
| 1982 |
+
-0.0072312,
|
| 1983 |
+
-0.0226028,
|
| 1984 |
+
-0.0474324,
|
| 1985 |
+
0.018386,
|
| 1986 |
+
0.0055725,
|
| 1987 |
+
0.0053387,
|
| 1988 |
+
-0.0067288,
|
| 1989 |
+
-0.0069327,
|
| 1990 |
+
0.0388352,
|
| 1991 |
+
-0.0092138,
|
| 1992 |
+
0.0017214,
|
| 1993 |
+
0.0108645,
|
| 1994 |
+
-0.007365,
|
| 1995 |
+
-0.0308875,
|
| 1996 |
+
0.0276345,
|
| 1997 |
+
0.0030492,
|
| 1998 |
+
0.0597437,
|
| 1999 |
+
0.0149274,
|
| 2000 |
+
0.0108632,
|
| 2001 |
+
0.0063664,
|
| 2002 |
+
-0.0472365,
|
| 2003 |
+
-0.0659725,
|
| 2004 |
+
0.0116199,
|
| 2005 |
+
-0.0311121,
|
| 2006 |
+
-0.0084875,
|
| 2007 |
+
0.021202,
|
| 2008 |
+
-0.0144852,
|
| 2009 |
+
-0.0276581,
|
| 2010 |
+
0.0346197,
|
| 2011 |
+
0.0190425,
|
| 2012 |
+
0.0021423,
|
| 2013 |
+
-0.0214823,
|
| 2014 |
+
0.0042432,
|
| 2015 |
+
0.0367772,
|
| 2016 |
+
-0.0352249,
|
| 2017 |
+
-0.0175849,
|
| 2018 |
+
-0.0085295,
|
| 2019 |
+
-0.0160579,
|
| 2020 |
+
-0.0221073,
|
| 2021 |
+
0.0715203,
|
| 2022 |
+
-0.022603,
|
| 2023 |
+
0.0121224,
|
| 2024 |
+
-0.0373269,
|
| 2025 |
+
0.0218277,
|
| 2026 |
+
0.0170323,
|
| 2027 |
+
-0.0286245,
|
| 2028 |
+
-0.0078001,
|
| 2029 |
+
0.0256803,
|
| 2030 |
+
-0.0148012,
|
| 2031 |
+
0.0099331,
|
| 2032 |
+
-0.0136366,
|
| 2033 |
+
0.0587975,
|
| 2034 |
+
-0.0419719,
|
| 2035 |
+
-0.0031683,
|
| 2036 |
+
0.0040288,
|
| 2037 |
+
-0.0167476,
|
| 2038 |
+
0.0084212,
|
| 2039 |
+
0.0180768,
|
| 2040 |
+
-0.0465007,
|
| 2041 |
+
-0.0154712,
|
| 2042 |
+
0.0111867,
|
| 2043 |
+
-0.0109722,
|
| 2044 |
+
-0.0218118,
|
| 2045 |
+
-0.0080581,
|
| 2046 |
+
0.0726468,
|
| 2047 |
+
-0.0298194,
|
| 2048 |
+
0.0206009,
|
| 2049 |
+
-0.0122841,
|
| 2050 |
+
0.0012644,
|
| 2051 |
+
-0.0214854,
|
| 2052 |
+
0.020771,
|
| 2053 |
+
0.0373861,
|
| 2054 |
+
-0.0202912,
|
| 2055 |
+
0.0086824,
|
| 2056 |
+
0.0104239,
|
| 2057 |
+
-0.0177915,
|
| 2058 |
+
-0.0195416,
|
| 2059 |
+
-0.0051046,
|
| 2060 |
+
0.00611,
|
| 2061 |
+
0.0131852,
|
| 2062 |
+
-0.0365019,
|
| 2063 |
+
0.0227813,
|
| 2064 |
+
0.0248264,
|
| 2065 |
+
-0.0207044,
|
| 2066 |
+
-0.0116764,
|
| 2067 |
+
0.0259135,
|
| 2068 |
+
0.0267124,
|
| 2069 |
+
-0.1291034,
|
| 2070 |
+
0.0239644,
|
| 2071 |
+
0.0625211,
|
| 2072 |
+
0.0244442,
|
| 2073 |
+
0.0325541,
|
| 2074 |
+
0.0351543,
|
| 2075 |
+
0.0217503,
|
| 2076 |
+
0.0121765,
|
| 2077 |
+
0.0449031,
|
| 2078 |
+
0.0567871,
|
| 2079 |
+
0.0054928,
|
| 2080 |
+
0.0165023,
|
| 2081 |
+
-0.0382681,
|
| 2082 |
+
0.0124143,
|
| 2083 |
+
0.037944,
|
| 2084 |
+
0.0284664,
|
| 2085 |
+
-0.0216116,
|
| 2086 |
+
0.0124165,
|
| 2087 |
+
0.0038941,
|
| 2088 |
+
-0.0059169,
|
| 2089 |
+
-0.0400646,
|
| 2090 |
+
-0.0100921,
|
| 2091 |
+
-0.0168236,
|
| 2092 |
+
0.0026536,
|
| 2093 |
+
0.0008066,
|
| 2094 |
+
0.0205489,
|
| 2095 |
+
0.0077813,
|
| 2096 |
+
-0.0524134,
|
| 2097 |
+
-0.0034027,
|
| 2098 |
+
-0.005347,
|
| 2099 |
+
-0.0321935,
|
| 2100 |
+
0.0125179,
|
| 2101 |
+
-0.0294378,
|
| 2102 |
+
0.0317069,
|
| 2103 |
+
0.010248,
|
| 2104 |
+
-0.0071578,
|
| 2105 |
+
-0.0060076,
|
| 2106 |
+
-0.0125832,
|
| 2107 |
+
-0.0200377,
|
| 2108 |
+
0.0289566,
|
| 2109 |
+
-0.0045058,
|
| 2110 |
+
-0.0214601,
|
| 2111 |
+
0.0466605,
|
| 2112 |
+
-0.0174458,
|
| 2113 |
+
-0.037283,
|
| 2114 |
+
0.0125868,
|
| 2115 |
+
-0.0232067,
|
| 2116 |
+
-0.2290279,
|
| 2117 |
+
0.0035629,
|
| 2118 |
+
-0.0056789,
|
| 2119 |
+
0.0374352,
|
| 2120 |
+
-0.0616575,
|
| 2121 |
+
-0.0058487,
|
| 2122 |
+
0.0131172,
|
| 2123 |
+
0.0539453,
|
| 2124 |
+
-0.0240027,
|
| 2125 |
+
-0.0390158,
|
| 2126 |
+
0.0107827,
|
| 2127 |
+
0.0038621,
|
| 2128 |
+
-0.0261863,
|
| 2129 |
+
0.0437467,
|
| 2130 |
+
-0.0608736,
|
| 2131 |
+
-0.0023836,
|
| 2132 |
+
0.0005415,
|
| 2133 |
+
0.0291586,
|
| 2134 |
+
0.0035115,
|
| 2135 |
+
-0.000339,
|
| 2136 |
+
0.0223815,
|
| 2137 |
+
-0.0281816,
|
| 2138 |
+
0.0592663,
|
| 2139 |
+
-0.007491,
|
| 2140 |
+
0.0226631,
|
| 2141 |
+
0.0274635,
|
| 2142 |
+
-0.0112232,
|
| 2143 |
+
-0.0176956,
|
| 2144 |
+
-0.021663,
|
| 2145 |
+
-0.0004892,
|
| 2146 |
+
-0.0213772,
|
| 2147 |
+
-0.0075747,
|
| 2148 |
+
-0.0309256,
|
| 2149 |
+
-0.0192184,
|
| 2150 |
+
0.0389742,
|
| 2151 |
+
0.0291572,
|
| 2152 |
+
0.0186152,
|
| 2153 |
+
-0.0461713,
|
| 2154 |
+
0.020704,
|
| 2155 |
+
-0.0140283,
|
| 2156 |
+
-0.0298755,
|
| 2157 |
+
-0.0019759,
|
| 2158 |
+
0.0245542,
|
| 2159 |
+
-0.017876,
|
| 2160 |
+
-0.000494,
|
| 2161 |
+
-0.0161591,
|
| 2162 |
+
0.0552246,
|
| 2163 |
+
0.0046217,
|
| 2164 |
+
0.0244851,
|
| 2165 |
+
-0.0494911,
|
| 2166 |
+
0.0113249,
|
| 2167 |
+
-0.0002698,
|
| 2168 |
+
0.022198,
|
| 2169 |
+
0.0076108,
|
| 2170 |
+
0.0061694,
|
| 2171 |
+
0.0151072,
|
| 2172 |
+
9.93e-05,
|
| 2173 |
+
0.0397179,
|
| 2174 |
+
0.0182748,
|
| 2175 |
+
-0.0288742,
|
| 2176 |
+
-0.0532453,
|
| 2177 |
+
0.0270052,
|
| 2178 |
+
0.038392,
|
| 2179 |
+
-0.001379,
|
| 2180 |
+
-0.0237011,
|
| 2181 |
+
0.0293171,
|
| 2182 |
+
0.004786,
|
| 2183 |
+
0.0394925,
|
| 2184 |
+
0.0042305,
|
| 2185 |
+
0.0089678,
|
| 2186 |
+
0.0208281,
|
| 2187 |
+
0.0092142,
|
| 2188 |
+
-0.0111049,
|
| 2189 |
+
0.0429073,
|
| 2190 |
+
-0.0086474,
|
| 2191 |
+
0.004501,
|
| 2192 |
+
-0.042835,
|
| 2193 |
+
-0.0161324,
|
| 2194 |
+
0.0090948,
|
| 2195 |
+
-0.0291586,
|
| 2196 |
+
-0.0044904,
|
| 2197 |
+
-0.0001088,
|
| 2198 |
+
0.0094551,
|
| 2199 |
+
-0.0286622,
|
| 2200 |
+
-0.0575663,
|
| 2201 |
+
0.0294203,
|
| 2202 |
+
-0.0009848,
|
| 2203 |
+
-0.0122502,
|
| 2204 |
+
-0.0377292,
|
| 2205 |
+
0.0191204,
|
| 2206 |
+
0.0062761,
|
| 2207 |
+
0.0521794,
|
| 2208 |
+
-0.0036846,
|
| 2209 |
+
0.0403789,
|
| 2210 |
+
-0.0335401,
|
| 2211 |
+
0.0054602,
|
| 2212 |
+
-0.0295457,
|
| 2213 |
+
0.0062646,
|
| 2214 |
+
-0.0318549,
|
| 2215 |
+
-0.0200798,
|
| 2216 |
+
-0.0350457,
|
| 2217 |
+
-0.0211371,
|
| 2218 |
+
-0.0045081,
|
| 2219 |
+
-0.0536032,
|
| 2220 |
+
0.0180079,
|
| 2221 |
+
0.0292347,
|
| 2222 |
+
-0.0373302,
|
| 2223 |
+
0.0102212,
|
| 2224 |
+
-0.0161656,
|
| 2225 |
+
0.0030769,
|
| 2226 |
+
-0.0042317,
|
| 2227 |
+
0.0422946,
|
| 2228 |
+
-0.0132999,
|
| 2229 |
+
0.0639726,
|
| 2230 |
+
0.0063302,
|
| 2231 |
+
0.0127261,
|
| 2232 |
+
0.0608007,
|
| 2233 |
+
0.0217346,
|
| 2234 |
+
0.0141945,
|
| 2235 |
+
-0.0055504,
|
| 2236 |
+
0.0164727,
|
| 2237 |
+
0.0310062,
|
| 2238 |
+
0.0074169,
|
| 2239 |
+
0.0124027,
|
| 2240 |
+
0.0103245,
|
| 2241 |
+
-0.0233224,
|
| 2242 |
+
-0.0102762,
|
| 2243 |
+
0.0091786,
|
| 2244 |
+
0.0063271,
|
| 2245 |
+
-0.0013132,
|
| 2246 |
+
0.0032481,
|
| 2247 |
+
0.0331999,
|
| 2248 |
+
0.0064559,
|
| 2249 |
+
-0.0219145,
|
| 2250 |
+
0.0096493,
|
| 2251 |
+
0.0178552,
|
| 2252 |
+
0.030541,
|
| 2253 |
+
0.0063936,
|
| 2254 |
+
0.0139802,
|
| 2255 |
+
-0.0252062,
|
| 2256 |
+
0.0227818,
|
| 2257 |
+
-0.0051253,
|
| 2258 |
+
-0.0292184,
|
| 2259 |
+
-0.0409617,
|
| 2260 |
+
0.022986,
|
| 2261 |
+
-0.0172845,
|
| 2262 |
+
0.0212696,
|
| 2263 |
+
-0.021213,
|
| 2264 |
+
-0.0138542,
|
| 2265 |
+
-0.0126601,
|
| 2266 |
+
-0.0104976,
|
| 2267 |
+
-0.020326,
|
| 2268 |
+
0.0113664,
|
| 2269 |
+
-0.0096861,
|
| 2270 |
+
0.0007517,
|
| 2271 |
+
0.0258834,
|
| 2272 |
+
-0.0228422,
|
| 2273 |
+
-0.0242639,
|
| 2274 |
+
-0.0164893,
|
| 2275 |
+
-0.0155036,
|
| 2276 |
+
-0.0012227,
|
| 2277 |
+
-0.0143082,
|
| 2278 |
+
-0.0010508,
|
| 2279 |
+
0.0134848,
|
| 2280 |
+
-0.0181285,
|
| 2281 |
+
-0.0109053,
|
| 2282 |
+
-0.0121414,
|
| 2283 |
+
-0.0269855,
|
| 2284 |
+
-0.0168856,
|
| 2285 |
+
0.0062585,
|
| 2286 |
+
0.0075284,
|
| 2287 |
+
-0.0223102,
|
| 2288 |
+
0.0206764,
|
| 2289 |
+
-0.0381524,
|
| 2290 |
+
-0.0254449,
|
| 2291 |
+
0.0046192,
|
| 2292 |
+
0.0094333,
|
| 2293 |
+
-0.0023871,
|
| 2294 |
+
0.1939883,
|
| 2295 |
+
-0.010786,
|
| 2296 |
+
-0.0109085,
|
| 2297 |
+
-0.0034179,
|
| 2298 |
+
-0.0061638,
|
| 2299 |
+
-0.0228264,
|
| 2300 |
+
0.0092627,
|
| 2301 |
+
0.0038894,
|
| 2302 |
+
-0.0105737,
|
| 2303 |
+
0.0254012,
|
| 2304 |
+
-0.0387676,
|
| 2305 |
+
0.0177953,
|
| 2306 |
+
0.0114002,
|
| 2307 |
+
-0.0082272,
|
| 2308 |
+
-0.0021445,
|
| 2309 |
+
0.0136881,
|
| 2310 |
+
-0.0476875,
|
| 2311 |
+
0.0161518,
|
| 2312 |
+
-0.0005517,
|
| 2313 |
+
0.0211178,
|
| 2314 |
+
0.0158259,
|
| 2315 |
+
0.0371481,
|
| 2316 |
+
0.0041266,
|
| 2317 |
+
0.0420225,
|
| 2318 |
+
0.0057804,
|
| 2319 |
+
0.0119182,
|
| 2320 |
+
0.00443,
|
| 2321 |
+
-0.0013008,
|
| 2322 |
+
-0.001863,
|
| 2323 |
+
0.0110961,
|
| 2324 |
+
0.0062314,
|
| 2325 |
+
-0.0355586,
|
| 2326 |
+
-0.0639177,
|
| 2327 |
+
0.0010688,
|
| 2328 |
+
0.0185759,
|
| 2329 |
+
0.0168337
|
| 2330 |
+
]
|
| 2331 |
+
}
|
| 2332 |
+
]
|
| 2333 |
+
}
|
tests/tokenizer-cases.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|