maxffarrell commited on
Commit
307b208
·
verified ·
1 Parent(s): 075aa7e

Publish validated multimodal EmbeddingGemma 2 Apple Core AI conversion

Browse files
.gitattributes CHANGED
@@ -33,3 +33,7 @@ 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
+ audio.aimodel/main.mlirb filter=lfs diff=lfs merge=lfs -text
37
+ embedding.aimodel/main.mlirb filter=lfs diff=lfs merge=lfs -text
38
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
39
+ vision.aimodel/main.mlirb filter=lfs diff=lfs merge=lfs -text
1_Pooling/config.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "embedding_dimension": 768,
3
+ "pooling_mode": "mean",
4
+ "include_prompt": true
5
+ }
2_Normalize/config.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "module_input_name": "sentence_embedding",
3
+ "module_output_name": "sentence_embedding"
4
+ }
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 2026 Alibaba Cloud
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,4 @@
 
 
 
 
 
1
+ EmbeddingGemma 2 source weights and original model documentation by Google DeepMind, distributed under Apache-2.0.
2
+ Host tokenization and preprocessing use Hugging Face Transformers under Apache-2.0.
3
+ Core AI conversion and adapter by maxffarrell, 2026, Apache-2.0.
4
+ See UPSTREAM_README.md and SOURCE.json for attribution and pinned provenance.
README.md ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model: google/embeddinggemma-2
4
+ pipeline_tag: feature-extraction
5
+ library_name: coreai
6
+ tags:
7
+ - coreai
8
+ - apple
9
+ - embeddings
10
+ - multimodal
11
+ - image-feature-extraction
12
+ - audio-feature-extraction
13
+ - video-feature-extraction
14
+ - experimental
15
+ ---
16
+
17
+ # EmbeddingGemma 2 — Apple Core AI
18
+
19
+ Apple Core AI `.aimodel` conversion of [google/embeddinggemma-2](https://huggingface.co/google/embeddinggemma-2/tree/914f7f89142e33e77833254d9c9b90c3cef7303b), revision `914f7f89142e33e77833254d9c9b90c3cef7303b`, Apache-2.0. This is the new 740M multimodal model, distinct from EmbeddingGemma 300M.
20
+
21
+ Supports text/code, images, video frames, audio, and interleaved combinations within the limits below. Returns normalized 768-dimensional embeddings. All neural inference, projection, masked mean pooling and initial L2 normalization run in Core AI. The host uses Transformers for tokenization/media preprocessing and NumPy for feature placement and optional truncated-vector normalization. No source PyTorch checkpoint is needed for inference.
22
+
23
+ ## Limits and platform
24
+
25
+ - Batch size one; **1,024 combined text and media tokens**, including special tokens. The source supports 8,192; this conversion is a fixed smaller export. Oversized inputs raise an error instead of being truncated.
26
+ - Vision: 2,520 patches per image/frame, at most 280 soft tokens. Preserves the upstream aspect-ratio-aware image processor and padding. Each image/frame is processed sequentially; total context capacity still applies.
27
+ - Audio: one mono 16 kHz clip per `encode` call, maximum **11.2 seconds** (1,120 mel frames / 280 soft tokens). No resampling in the adapter. Longer clips must be handled separately by the caller; this does not implement the source's minute-scale audio context.
28
+ - Video accepts an already sampled NumPy `[frames,height,width,channels]` array. Validation used two frames. Decode and sample video files before calling the adapter; no file decoding, temporal sampling or audio-track extraction is implemented by this adapter. The upstream per-frame image processing is preserved.
29
+ - Text task prefixes are preserved via `prompt_name`, e.g. `SearchQuery`, `Document`, `CodeRetrieval`. For mixed content, manually supply placeholder text (`<|image|>`, `<|audio|>`, `<|video|>`) in the desired order and omit `prompt_name`.
30
+ - Optional Matryoshka truncation re-normalizes the vector; dimensions 128, 256 and 512 were exercised.
31
+ - FP32 inference with the source weights promoted from BF16; assets total approximately 3 GB. Tested on macOS 27.0.1 / Apple Silicon with default Core AI specialization. No independent iOS, ANE or GPU placement claim.
32
+ - Experimental community conversion. Numerical fixtures are conversion tests, not a retrieval-quality benchmark.
33
+
34
+ ## Download and run
35
+
36
+ ```bash
37
+ hf download maxffarrell/embeddinggemma-2-CoreAI --local-dir embeddinggemma-2-CoreAI
38
+ cd embeddinggemma-2-CoreAI
39
+ python3 -m venv .venv
40
+ source .venv/bin/activate
41
+ pip install -r requirements.txt
42
+ ```
43
+
44
+ ```python
45
+ import asyncio
46
+ import numpy as np
47
+ from PIL import Image
48
+ from coreai_embeddinggemma2 import EmbeddingGemma2CoreAI
49
+
50
+ async def main():
51
+ model = await EmbeddingGemma2CoreAI.load(".")
52
+ query = await model.encode("What causes the northern lights?", prompt_name="SearchQuery")
53
+ document = await model.encode("Charged particles from the sun cause auroras.", prompt_name="Document")
54
+ print(float(query @ document))
55
+ image = await model.encode(images=Image.open("photo.jpg"))
56
+ audio = await model.encode(audio=np.zeros(16000, dtype=np.float32), sampling_rate=16000)
57
+ small = await model.encode("An example sentence", truncate_dim=256)
58
+
59
+ asyncio.run(main())
60
+ ```
61
+
62
+ ## Validation
63
+
64
+ `validation.json`: **21 cases** against the full pinned PyTorch FP32 model with native-length source inputs, testing task prefixes, code, Spanish, Japanese, short/long/near-capacity text, images, mixed text/images, two images, short/long/limit audio, text/audio, video, text/video and all four modalities together. Worst elementwise error: **9.76026058e-07**; minimum cosine similarity: **0.999999881**. Predeclared final limits: cosine > 0.99999 and maximum elementwise error < 0.00005, plus finite unit vectors.
65
+
66
+ `smoke.json`: separate source-checkpoint-free native runtime smoke in five text/media cases with PyTorch neural model loading explicitly disabled, plus invalid-input rejection. Task-independent retrieval accuracy has not been benchmarked.
67
+
68
+ ## Reproduce
69
+
70
+ From a parent directory containing this bundle under `bundles/embeddinggemma-2-CoreAI`, download the pinned source into `upstream/embeddinggemma-2`, install `requirements.txt`, and run `convert_gemma2.py`, `validate_gemma2.py`, then `smoke_gemma2.py`. The validation/smoke scripts default to that directory layout. The converter uses the exact official Transformers commit in requirements because stable 5.17 predates support. `coreai-torch` 0.4.3 warns it was validated through PyTorch 2.13; this export was tested on 2.14.
71
+
72
+ Graphs: `embedding.aimodel` (text backbone + projection + pooling + normalization), `vision.aimodel` (vision encoder + projection + valid-token mask), `audio.aimodel` (audio encoder + projection + valid-token mask). Integer inputs are int32. `manifest.json` declares signatures, static shapes and output names. An equivalent slice/stack decomposition replaces unsupported `aten.unfold` in audio; the source weights are unchanged. The text mask preserves the source's inclusive bidirectional sliding window.
73
+
74
+ `SOURCE.json` records the verified upstream weight hash; `SHA256SUMS` covers every release file except itself. Original model documentation is in `UPSTREAM_README.md`. For Clef Flash, an existing Core AI conversion was found and verified to use current source weight hashes; see `RELATED_MODEL_RESEARCH.md`.
RELATED_MODEL_RESEARCH.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Clef / EmbeddingGemma follow-up, 2026-10-11
2
+
3
+ Hub author search found Cloudflare/clef, Cloudflare/clef-flash, Cloudflare/clef-omni. Hub searches for clef-CoreAI and embeddinggemma-2-CoreAI, plus internet searches, found one Clef Flash Core AI repository and no EmbeddingGemma 2 Core AI repository. Search is evidence of discoverability, not proof none exists anywhere.
4
+
5
+ Existing public Clef Flash conversion: https://huggingface.co/mlboydaisuke/clef-flash-CoreAI at 220ac149ed1136465f7d9dac93aaf0dfc55d6b73. Source revision 17f0b0ad64efb65d273590632833508766b2aae6. Compared every source safetensors file's LFS SHA256 with Cloudflare/clef-flash current revision 8b2e5fd17c09fd49bc2880b805d5323ec7ff4ff3: all five match, including joint_head. Assets are .aimodel directories. This check verifies published provenance, not an independent runtime audit. Runtime: https://github.com/john-rocky/coreai-model-zoo/tree/main/apps/ClefFlash
6
+
7
+ Clef 27B has 12 source shards and exceeds a safe conversion budget on this 64 GiB Mac once compiler copies are included. Clef Omni at 0db1cd2607d76a7bdb2a382f659e7b313079f84b has ~70.5 GB source weights, more than installed RAM. Neither was downloaded or attempted. These are capacity preflight exclusions, not observed converter failures.
8
+
9
+ The old embeddinggemma-300m-CoreAI repositories are not EmbeddingGemma 2. New source google/embeddinggemma-2 at 914f7f89142e33e77833254d9c9b90c3cef7303b is Apache-2.0, 740M multimodal. Transformers 5.17 lacked support. Installed official upstream commit 536ecc007387a50e77603bb5d92100e9b07514cc (5.19.0.dev0) for conversion and processing.
SHA256SUMS ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 8759bdf7c77efc7df7723f64856a593c8943b71ee38baf2a88771fbaf78438f9 1_Pooling/config.json
2
+ cdb09dfca347a56aa2d691744e38d5ad3c7cbc2834e7181272b9a15328b82524 2_Normalize/config.json
3
+ bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a LICENSE
4
+ 85ba8daf1bf1acd0a5cc239ff106203ace9c0c3717a9d8690dbb28bcb022d11d NOTICE
5
+ 8926ecfb31c048bca42fd7c93bb64e92e8a7af767c4ceeaf1531b6b3b8e06761 README.md
6
+ 9fc2f9fa2e72d89965809df5e8e74c541dd297acc781b156e156d7bdf545ab38 RELATED_MODEL_RESEARCH.md
7
+ 46a0001714f1f99a5a38d5fc8b12838c60f606f54a10cbaf64315c927a8853ba SOURCE.json
8
+ b677a0ee2818c36f091b65fd3d9b4baea9f04d53b02b49cae916c74bf9682037 UPSTREAM_README.md
9
+ 0db0c4704bd2b44e73dbf6544e8cca29a2b957e4666bd9bbe459442b4947303a audio.aimodel/main.hash
10
+ d34c4312378bfe6a41f5b12091f4141fa8717889c82a377314be573969b095a3 audio.aimodel/main.mlirb
11
+ 773527bec44748e8f64fedbb4a7488ba6c583fc4b7c0f3d650212923ad9b3199 audio.aimodel/metadata.json
12
+ 4b852efc0b9960283e735363331e6f325b33bc74bdbaa076f595bc4e9b94d85e chat_template.jinja
13
+ b8f1e9931b57fbc054acdb445c41765d55b0074c58d145fa82839941ad1b5bb3 config.json
14
+ 031e56a498d33c349ab489a21885bcfe25b4fcba841149dc99e1e90d4a7c28f5 config_sentence_transformers.json
15
+ 2a731c4dd36cac9b96187b144e58c4396f2e38dc6245dfeba78f4b6e177e6356 convert_gemma2.py
16
+ fc94d0291f4291e2d0acf22687843ba1e7ce2df91d03af900bc4aaeffdd09f6f coreai_embeddinggemma2.py
17
+ 0dda0cd4eb3a160e81fe3eccc8466de577a5ea553e8eac2b2f6cf6ddbbc78bad embedding.aimodel/main.hash
18
+ b14af3c45b7d3749b575a76a654843a6b1a843b83b317d1e8d3a2355a8809f9c embedding.aimodel/main.mlirb
19
+ 80eb34ddec601b8071d7f1cb4578e26bc98575f365fd79cc9e5080a921104bde embedding.aimodel/metadata.json
20
+ 098a1496be00f9c5351c798b7545ca58bec296d38643ef56a940054c65f3536a manifest.json
21
+ 3d02572a0455b832de67fb8e63a54981bc7e8b46e337c95e917bd8122a533bfd modules.json
22
+ ea2ae257e901064abdd98dceb19f2b0da06af600bed15e0f99f5c85c37ee9d78 preprocessor_config.json
23
+ 168f6a08522f3ce5dea596d94d003af2fd691742d4f41fe1f9d8cce76bfbf69c processor_config.json
24
+ dd0e8558adde3d71f5f8f69c169dce97ff7a86b9a8eddde0d921b8479440fb1a requirements.txt
25
+ b1bcd9f2dce3ae863b359e87d0710b5dbc3314a59ecb4e2f97c7778fc8e4b228 sentence_bert_config.json
26
+ c6de103dfdd0e4d7c523210c4a90e4643f3efcc871f85c6abeea0c729f156760 smoke.json
27
+ a13711b584f9ea10a5f78836b31af09129481d74af6665dee62bf906b51ac347 smoke_gemma2.py
28
+ 4d777ef5bdc1aa36227abdfb77c3e49e7b9c892d16e1b6bda41c393504828be4 tokenizer.json
29
+ e594c8a90eb08d8bda498ff4747977dc827ae0c3c56b5c0d41a605a22d02ef03 tokenizer.model
30
+ 17bd5d6e9364ca49a534e1502076593317c298d4a663623091ed45388f004874 tokenizer_config.json
31
+ 548a313b4610350ac2f7cd2d43742f6d242ad0e381600fa12e2558839fae0a36 validate_gemma2.py
32
+ 89fd15b469eabd478a046f10fad927f5d7a7678523f5d0efb1f438ff8840b699 validation.json
33
+ e2249be21920798ddd44486045f52ed7d4a89d0321c96499e9d0baefa252b306 vision.aimodel/main.hash
34
+ cabf97de2b23a35cecea87c802f4f9f12d0b38126d43af539e932ddf6935f978 vision.aimodel/main.mlirb
35
+ aedfcb0574f0ab8e395f4e19a33865551e942229945f966eb106d7191ea63f57 vision.aimodel/metadata.json
SOURCE.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "repo": "google/embeddinggemma-2",
3
+ "revision": "914f7f89142e33e77833254d9c9b90c3cef7303b",
4
+ "license": "apache-2.0",
5
+ "verified_source_weights": {
6
+ "model.safetensors": {
7
+ "sha256": "197a32965d4b1105faf060417baa899e193fb73cd401f42ec9295234d5553d79",
8
+ "size": 1488915288
9
+ },
10
+ "tokenizer.json": {
11
+ "sha256": "4d777ef5bdc1aa36227abdfb77c3e49e7b9c892d16e1b6bda41c393504828be4",
12
+ "size": 32170510
13
+ },
14
+ "tokenizer.model": {
15
+ "sha256": "e594c8a90eb08d8bda498ff4747977dc827ae0c3c56b5c0d41a605a22d02ef03",
16
+ "size": 4689013
17
+ }
18
+ }
19
+ }
UPSTREAM_README.md ADDED
@@ -0,0 +1,335 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ pipeline_tag: feature-extraction
4
+ tags:
5
+ - embedding
6
+ - feature-extraction
7
+ - sentence-transformers
8
+ - multimodal-embedding
9
+ - multimodal
10
+ - vision
11
+ - audio
12
+ - video
13
+ - image-feature-extraction
14
+ - audio-feature-extraction
15
+ - video-feature-extraction
16
+ - sentence-similarity
17
+ language:
18
+ - multilingual
19
+ library_name: transformers
20
+ ---
21
+
22
+ <div align="center">
23
+ <img src=https://ai.google.dev/gemma/images/embeddinggemma2_banner.png>
24
+ </div>
25
+
26
+ <p align="center">
27
+ <a href="https://huggingface.co/google/embeddinggemma-2" target="_blank">Hugging Face</a> |
28
+ <a href="https://github.com/google-gemma" target="_blank">GitHub</a> |
29
+ <a href="https://blog.google/innovation-and-ai/technology/developers-tools/embeddinggemma-2" target="_blank">Launch Blog</a> |
30
+ <a href="https://ai.google.dev/gemma/docs/embeddinggemma" target="_blank">Documentation</a> |
31
+ <br>
32
+ <b>License</b>: <a href="https://ai.google.dev/gemma/docs/gemma_4_license" target="_blank">Apache 2.0</a> | <b>Authors</b>: <a href="https://deepmind.google/models/gemma/" target="_blank">Google DeepMind</a>
33
+ </p>
34
+
35
+ **EmbeddingGemma 2** is an open multimodal embedding model built by Google DeepMind which maps text (incl. code), images, video, and audio inputs—and combinations thereof—into a single, unified 768-dimensional vector space. The model has 740M total parameters, combining a 270M parameter text model with modular vision (170M) and audio (300M) encoders.
36
+
37
+ Designed to run on consumer hardware such as mobile devices and laptops, EmbeddingGemma 2 delivers low-latency semantic representations for on-device applications, like search, retrieval-augmented generation (RAG), classification, and clustering.
38
+
39
+ EmbeddingGemma 2 builds upon the architectural and capability advancements of Gemma 4, offering several core features:&nbsp;
40
+
41
+ * **Native multimodality:** Native multimodality: Unifies 4 modalities (text, images, video, and audio) in a single shared 768-dimensional embedding space.
42
+ * **Multilinguality and code:** EmbeddingGemma 2 understands 100+ languages, and achieves a \~14% improvement on code tasks relative to its predecessor.&nbsp;
43
+ * **Flexible footprint:** Combines a 270M parameter text backbone (130M transformer \+ 140M embedder) with selectively loadable vision (170M) and audio (300M) encoders, allowing developers to load only the modalities required for their use case.
44
+ * **Matryoshka Representation Learning (MRL):** Native support for truncated embeddings across 128d, 256d, 512d, and 768d, enabling up to a **6x reduction** in vector storage costs with minimal impact on quality.
45
+ * **Context length:** 8K token context window, capable of processing minutes of audio or video.
46
+ * **Task-steered representations:** Uses lightweight text instruction prefixes to optimize embeddings for different tasks (search, classification, clustering, semantic similarity, etc.).
47
+
48
+ ### Model Overview
49
+
50
+ | Parameters | Total | 740M |
51
+ | :---- | :---- | :---- |
52
+ | | **Backbone** | 130M |
53
+ | | **Embedder** | 140M |
54
+ | | **Modality Encoders** | *Vision:* 170M *Audio:* 300M |
55
+ | **Architecture** | **Layers** | 24 |
56
+ | | **Model Dimension** | 512 |
57
+ | | **Hidden Dimension** | 2048 |
58
+ | | **Sliding Window** | 1024 tokens |
59
+ | | **Vocabulary Size** | 262,144 |
60
+ | | **\# Heads** | 4 |
61
+ | | **\# KV-Heads (Local/Global)** | 2/1 |
62
+ | | **Local:Global** | 5:1 |
63
+ | | **Attention** | GQA/MQA |
64
+ | | **Activation** | Gated FFN with GELU |
65
+ | | **Pooling** | Mean Pooling |
66
+ | | **Projection Layer** | 512→768 |
67
+ | **Input/Output** | **Supported Modalities** | Text, Images, Video, Audio |
68
+ | | **Context Window** | 8,192 tokens |
69
+ | | **Native Output Dimension** | 768 |
70
+ | | **MRL Truncation Dimensions** | 128, 256, 512 |
71
+
72
+ ## **Benchmark Results**
73
+
74
+ EmbeddingGemma 2 was evaluated across text, code, vision, visual document, video, and audio embedding benchmarks. All results reported below use the full-precision checkpoint.
75
+
76
+ ### Overall Evaluation Results (768d)
77
+
78
+ | Modality | Benchmark | Metric | EmbeddingGemma 2 | EmbeddingGemma 1 |
79
+ | :---- | :---- | :---- | :---- | :---- |
80
+ | *Text* | Massive Text Embedding Benchmark (MTEB, multilingual, v2) | Mean(Task), Multiple | 61.36 | 61.15 |
81
+ | | Massive Text Embedding Benchmark (MTEB, code, v1) | Mean(Task), NDCG@10 | 78.68 | 68.76 |
82
+ | *Image* | Massive Image Embedding Benchmark (MIEB, lite) | Mean(TaskType), Multiple | 64.64 | \- |
83
+ | | Massive Multimodal Embedding Benchmark (MMEB v2 \- Image) | Mean(Task), Hit@1 | 57.28 | \- |
84
+ | | Massive Multimodal Embedding Benchmark (MMEB v2 \- VisDoc) | Mean(Task), NDCG@5 | 67.84 | \- |
85
+ | *Video* | Massive Multimodal Embedding Benchmark (MMEB v2 \- Video) | Mean(Task), Hit@1 | 50.67 | \- |
86
+ | *Audio* | Massive Sound Embedding Benchmark (MSEB, Retrieval) | Mean(Task), MRR@10 | 69.54 | \- |
87
+ | | Massive Audio Embedding Benchmark (MAEB) *Hugging Face*&nbsp; | Mean(Task), Multiple | 49.39 | \- |
88
+
89
+ ### Evaluation Results with Vector Truncation
90
+
91
+ With MRL, EmbeddingGemma 2 representations can be truncated below the native 768d to 128d, 256d, and 512d representations and re-normalized.
92
+ With this, model users can reduce storage requirements, with minimal quality impact down to 256d. 128d is best suited to text-only workloads.
93
+
94
+ | Output Dimension | Compression Ratio | MTEB *(multilingual, v2) Mean(Task)* | MTEB *(eng, v2) Mean(Task)* | MTEB *(code, v1) Mean(Task)* | MIEB *(lite) Mean(TaskType)* | MMEB (v2) *Overall* | MSEB *(Retrieval) Mean(Task)* | MAEB *Mean(Task)* |
95
+ | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
96
+ | 768d (Full) | 1:1 | 61.36 | 68.46 | 78.68 | 64.64 | 59.01 | 69.54 | 49.39 |
97
+ | 512d | 1:1.5 | 61.17 | 68.41 | 77.24 | 64.32 | 58.38 | 69.18 | 49.21 |
98
+ | 256d | 1:3 | 60.41 | 67.78 | 76.18 | 63.13 | 56.24 | 66.76 | 48.91 |
99
+ | 128d | 1:6 | 57.89 | 65.68 | 71.41 | 59.06 | 45.65 | 56.71 | 46.92 |
100
+
101
+ ## **Quick Start**
102
+
103
+ Install the `sentence-transformers` library:
104
+
105
+ ```bash
106
+ pip install -U sentence-transformers transformers
107
+ ```
108
+
109
+ Generate text embeddings:
110
+ ```python
111
+ from sentence_transformers import SentenceTransformer
112
+
113
+ model = SentenceTransformer("google/embeddinggemma-2")
114
+
115
+ query = "What causes the northern lights?"
116
+ document = "The northern lights are caused by charged particles from the sun."
117
+
118
+ query_emb = model.encode(query, prompt_name="SearchQuery")
119
+ doc_emb = model.encode(document, prompt_name="Document")
120
+ print(model.similarity(query_emb, doc_emb))
121
+ ```
122
+
123
+
124
+ ## **Best Practices**
125
+
126
+ For optimal embedding quality and runtime efficiency, follow these configurations and best practices:
127
+
128
+ ### 1. Task Instruction Prefixes
129
+
130
+ EmbeddingGemma 2 is trained with short task instruction prefixes prepended to text inputs. Using the right prefix improves quality; omitting it still works but reduces precision. Prefixes apply to text only. Pass images, video, and audio without any prefix.
131
+
132
+ Documents with a real title should be formatted as `title: {title} | text: {content}`. Use `title: none` when no title is available.
133
+
134
+ **Prefix Notation & Usage**
135
+
136
+ We offer two types of task prefixes, depending on how embeddings are used in the task. There are two types of tasks:
137
+
138
+ * **Asymmetric Tasks (e.g. retrieval):** Use a query prefix for queries and a document prefix for corpus items.
139
+ * **Symmetric Tasks (e.g. classification, similarity)**: Apply the same task prefix to all inputs being compared.
140
+
141
+ | Use Case | Task Type | Prompt Name | Query Task Instruction | Document Task Instruction *(use `none` if no title)* |
142
+ | :---- | :---- | :---- | :---- | :---- |
143
+ | Web / document search | Asymmetric | SearchQuery | `task: search result \| query: {query}` | `title: {title} \| text: {content}` |
144
+ | Question answering | Asymmetric | QuestionAnswering | `task: question answering \| query: {question}` | `title: {title} \| text: {passage}` |
145
+ | Fact checking | Asymmetric | FactChecking | `task: fact checking \| query: {claim}` | `title: {title} \| text: {evidence}` |
146
+ | Code search | Asymmetric | CodeRetrieval | `task: code retrieval \| query: {query}` | `title: {title or filename} \| text: {code}` |
147
+ | Text classification | Symmetric | Classification | `task: classification \| query: {content}` | N/A |
148
+ | Clustering | Symmetric | Clustering | `task: clustering \| query: {content}` | N/A |
149
+ | Measuring similarity | Symmetric | SentenceSimilarity | `task: sentence similarity \| query: {content}` | N/A |
150
+
151
+ **Please note:** `prompt_name="Document"` applies `title: none`; titled documents must still be formatted manually, like `model.encode(f"title: {title} | text: {document}")`
152
+
153
+ ### 2. Selective Encoder Loading
154
+
155
+ The vision and audio encoders are independent components. To reduce memory consumption when deploying text-only or single-modality pipelines, disable unused modality encoders via SentenceTransformer’s `config_kwargs`:
156
+
157
+ **Please note:** configuring EmbeddingGemma 2 to load with omission of some encoders differs among model libraries; please refer to the appropriate documentation for more information on this.
158
+
159
+ | Active Modalities | `config_kwargs` | Effective Size |
160
+ | :---- | :---- | :---- |
161
+ | Text only | `{"vision_config": None, "audio_config": None}` | 270M |
162
+ | Text and image | `{"audio_config": None}` | 440M |
163
+ | Text and audio | `{"vision_config": None}` | 570M |
164
+ | Full multimodal | `{}` | 740M |
165
+
166
+ ### 3. Matryoshka Dimension Truncation
167
+
168
+ EmbeddingGemma 2 is trained with Matryoshka Representation Learning, so the 768-dimensional output vector can be shortened by keeping only its leading dimensions. The supported dimensions are 768, 512, 256, and 128\. Shorter vectors reduce storage and speed up similarity search at some cost to quality.
169
+
170
+ At runtime, please adhere to the following guidelines:
171
+
172
+ * **Re-normalize after truncating:** slicing a unit-length vector does not preserve unit length. The shortened vector must be L2-normalized before it is used for cosine similarity. Skipping this step degrades ranking quality silently—it produces plausible-looking scores rather than an error.
173
+ * **Queries and documents must share a dimension.** A 768-dimensional query cannot be scored against a 128-dimensional corpus.
174
+ *
175
+ To avoid truncating and normalizing yourself, you should pass in the `truncate_dim` and `normalize_embeddings` fields when calling `model.encode()`:
176
+
177
+ ```python
178
+ query_emb = model.encode(
179
+ query,
180
+ truncate_dim=128, # or 512, 256
181
+ normalize_embeddings=True,
182
+ )
183
+ ```
184
+
185
+ Model quality at each dimension is reported in the truncation table in the **Benchmark Results** section above. Quality is close to lossless down to 256 dimensions. 128 dimensions degrades multimodal quality substantially and should be validated against your own workload before adoption.
186
+
187
+ ### 4. Numerical Precision
188
+
189
+ Run inference in `bfloat16` or `float32`. Do not use `float16`.
190
+
191
+ EmbeddingGemma 2's activation range exceeds the dynamic range of `float16`. In `float16` the model returns NaN or silently degraded embeddings rather than raising an error, so the failure is easy to miss.
192
+
193
+ `bfloat16` is safe, and carries the same 8-bit exponent as `float32`. This is the recommended default on hardware with native support, and halves memory use relative to `float32`. Use `float32` elsewhere, including on most CPUs.
194
+
195
+ In SentenceTransformers, you can set this via `model_kwargs`, and determine it programmatically via `is_bf16_supported`:
196
+
197
+ ```python
198
+ dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float32
199
+
200
+ model = SentenceTransformer("google/embeddinggemma-2", model_kwargs={"torch_dtype": dtype})
201
+ ```
202
+
203
+ ### 5. Multimodal Input
204
+
205
+ #### Interleaving
206
+
207
+ A single input may mix text with images, video, and audio, using a single shared 8,192-token context.
208
+
209
+ The position of each media item within the sequence is marked in the text using placeholder tokens from the model's vocabulary:
210
+
211
+ * `<|image|>` marks the position of image input
212
+ * `<|video|>` marks the position of video input
213
+ * `<|audio|>` marks the position of audio input&nbsp;
214
+
215
+ For example, a product listing indexed for search might be encoded as:
216
+
217
+ ```python
218
+ emb_interleaved = model.encode({
219
+ "text": "Waterproof running shoes. <|image|> Featuring a breathable mesh upper. <|image|> Grip test on wet rock: <|video|>",
220
+ "image": ["shoe.jpg", "mesh.jpg"],
221
+ "video": "demo.mp4",
222
+ })
223
+ ```
224
+
225
+ Each placeholder in text is filled from the corresponding key, in order. The call returns a single embedding that represents the text, images and video together. This embedding can be compared directly against any other EmbeddingGemma 2 embedding; for example, a text-only query such as “waterproof shoes for trail running”.
226
+
227
+ #### Context Limits
228
+
229
+ All modalities share a single 8,192-token context window. Each modality consumes that budget at a fixed rate:
230
+
231
+ | Modality | Token Cost | Max Input |
232
+ | :---- | :---- | :---- |
233
+ | *Text* | 1 token per subword | 8,192 tokens |
234
+ | *Image* | 280 tokens per image *(default)* | ~29 images |
235
+ | *Video* | 140 tokens per frame *(default)* | ~58 frames |
236
+ | *Audio* | 25 tokens per second | ~327 seconds |
237
+
238
+ Note max input for images and video can be as high as ~114 images or frames when using a lower vision token budget (described below).
239
+
240
+ The maximums above assume a single modality with no accompanying text. Interleaved inputs draw from the same budget, so mixing modalities reduces the amount of each that fits. Note also max input for images and video can be as high as \~114 images or frames when using a lower vision token budget (described below).
241
+
242
+ #### Vision Token Budget
243
+
244
+ Model users can forgo the default sequence length for input images / video frames to represent images with “soft token” amounts ranging from 70 to 1120\. Increasing the vision budget for input images trades latency and token count for quality.&nbsp;
245
+
246
+ In general, higher input sequence lengths capture more information. Scaling up input sequence length means more expressive input representations; so, increasing input sequence length will improve fine-grained visual understanding, uplifting embedding quality and performance in downstream use cases.
247
+
248
+ #### Input Sampling Defaults
249
+
250
+ Audio and video inputs have the following default sampling rates:
251
+
252
+ * By default, video is processed as sampled frames through the vision encoder at **1 frame per second;** FPS sampling rate is configurable.
253
+ * Audio should be supplied as **mono at 16 kHz.**
254
+
255
+ ## **Model Data**
256
+
257
+ Our pre-training dataset is a large-scale, diverse collection of data encompassing a wide range of domains and modalities, which includes web documents, code, images, video, and audio, with a cutoff date of January 2025\. These include:
258
+
259
+ * **Web Documents:** A diverse collection of web text ensures the model is exposed to a broad range of linguistic styles, topics, and vocabulary. The training dataset includes content in over 140 languages.
260
+ * **Code:** Exposing the model to code helps it to learn the syntax and patterns of programming languages, which improves its ability to understand code-related semantics.&nbsp;
261
+ * **Images:** A wide range of images enables the model to perform image analysis and visual data extraction tasks
262
+ * **Video:** Video sequences and clips covering diverse visual scenes, human actions, and temporal dynamics.
263
+ * **Audio:** Speech recordings across multiple languages, environmental sounds, and acoustic events.
264
+ * **Cross-modality Samples:** Paired text, image, video, and audio data to align representations across different modalities.
265
+
266
+ ### Data Processing
267
+
268
+ Several data cleaning and filtering methods were applied to the training data:
269
+
270
+ * **CSAM Filtering**: Rigorous CSAM (Child Sexual Abuse Material) filtering was applied at multiple stages in the data preparation process to ensure the exclusion of harmful and illegal content.
271
+ * **Sensitive Data Filtering**: As part of making Gemma pre-trained models safe and reliable, automated techniques were used to filter out certain personal information and other sensitive data from training sets.
272
+ * **Additional Methods**: Filtering based on content quality and safety in line with [our policies](https://ai.google/static/documents/ai-responsibility-update-published-february-2025.pdf).
273
+
274
+ ### **Ethics & Safety**
275
+
276
+ EmbeddingGemma 2 is a pre-trained embedding model. Unlike generative models, it does not undergo post-training alignment, safety tuning, or output-level moderation. Safety mitigations during development were focused on pre-training data filtering to reduce exposure to harmful content and severe biases in the learned embedding space, in alignment with [Google's AI Principles](https://ai.google/principles/).
277
+
278
+ Since embedding models produce representations rather than user-facing text, safety risks manifest downstream in how those representations are used.&nbsp;
279
+
280
+ Developers and deployers are responsible for evaluating and implementing application-level safeguards, like retrieval filtering and fairness testing, appropriate to their specific production use case.&nbsp;
281
+
282
+ Deployments must adhere to the [Gemma Prohibited Use Policy](https://ai.google.dev/gemma/prohibited_use_policy).
283
+
284
+ ## **Usage and Limitations**
285
+
286
+ EmbeddingGemma 2 has certain limitations that users should be aware of.
287
+
288
+ ### Intended Usage
289
+
290
+ EmbeddingGemma 2 generates embeddings from input content, which can be used for a number of downstream applications.
291
+
292
+ The following list of potential uses is not comprehensive. The purpose of this list is to provide contextual information about the possible use-cases that the model creators considered as part of model training and development.
293
+
294
+ * **Retrieval:** Embeddings for semantic search across text, code, images, video, and audio, such as document search, RAG over enterprise knowledge bases, code search from natural language queries, or spoken-query search over audio archives.
295
+ * **Classification:** Embeddings to classify inputs according to preset labels, like sentiment analysis, content moderation, audio event detection, or image categorization.
296
+ * **Clustering:** Embeddings to group inputs based on semantic similarity, like organizing document collections, clustering customer feedback by theme, or discovering near-duplicate content across modalities.
297
+ * **Semantic Similarity:** Embeddings to measure pairwise similarity between inputs, such as recommendation systems, duplicate detection, paraphrase identification, or cross-lingual content alignment.
298
+ * **Fact Verification:** Embeddings for retrieving evidence documents given a claim, like automated fact-checking systems or source attribution pipelines.
299
+
300
+ ### Limitations
301
+
302
+ * **Training Data:** The quality and diversity of the training data significantly influence the model's capabilities. Biases or gaps in the training data can lead to limitations in the model's responses.
303
+ * The scope of the training dataset determines the subject areas the model can handle effectively.
304
+ * For example, while EmbeddingGemma 2 supports 100+ languages, the model may not exhibit equal performance across languages.&nbsp;
305
+ * **Context and Task Complexity:** Models perform well on tasks that can be framed with clear prompts and instructions.&nbsp;
306
+ * Open-ended or highly complex tasks might be challenging.
307
+ * A model's performance can be influenced by the amount of context provided (longer context generally leads to better outputs, up to a certain point).
308
+ * **Language Ambiguity and Nuance:** Natural language is inherently complex. Models might struggle to grasp subtle nuances, sarcasm, or figurative language.
309
+ * **Task Instruction Usage:** For text tasks, omitting the recommended task prefix may lead to sub-optimal embedding quality.
310
+
311
+ ### Ethical Considerations and Risks
312
+
313
+ In creating an open embedding model, we have carefully considered the following:
314
+
315
+ * **Bias and Fairness**
316
+ * Models trained on large-scale, real-world text and image data can reflect socio-cultural biases embedded in the training material.&nbsp;
317
+ * Training data used for EmbeddingGemma 2 underwent safety filtering to mitigate the risk of these biases.
318
+ * **Misinformation and Misuse**
319
+ * Embedding representations can be misused to retrieve, classify, or otherwise organize embedded content in false, misleading or harmful ways.&nbsp;
320
+ * Guidelines are provided for responsible use with the model, see the [Responsible Generative AI Toolkit](https://ai.google.dev/responsible).
321
+ * **Transparency and Accountability**
322
+ * This model card summarizes details on the model's architecture, capabilities, limitations, and evaluation processes.
323
+ * A responsibly developed open model offers the opportunity to share innovation by making Vision-Language Model (VLM) technology accessible to developers and researchers across the AI ecosystem.
324
+
325
+ ### Risks Identified and Mitigations
326
+
327
+ * **Misuse for malicious purposes:** Technical limitations and developer and end-user education can help mitigate against malicious applications of embedding models. Educational resources and reporting mechanisms for users to flag misuse are provided.
328
+ * **Privacy violations:** Models were trained on data filtered for removal of certain personal information and other sensitive data. Developers are encouraged to adhere to privacy regulations with privacy-preserving techniques.
329
+ * **Perpetuation of biases:** It's encouraged to perform continuous monitoring (using evaluation metrics, human review) and the exploration of de-biasing techniques during model training, fine-tuning, and other use cases.
330
+
331
+ ### Benefits
332
+
333
+ EmbeddingGemma 2 is among the strongest multimodal embedding models under 1B parameters. We’re excited to see how developers will use and adapt this model for on-device or edge AI applications.&nbsp;
334
+
335
+ The model is designed from the ground up for responsible AI development, like other Gemma-family models.
audio.aimodel/main.hash ADDED
@@ -0,0 +1 @@
 
 
1
+ �LC7��jA�� ���qx��*7s�W9i���
audio.aimodel/main.mlirb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d34c4312378bfe6a41f5b12091f4141fa8717889c82a377314be573969b095a3
3
+ size 1223634908
audio.aimodel/metadata.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "producer" : "coreai-core 1.0.0b3",
3
+ "assetVersion" : "2.0",
4
+ "creationDate" : "20261011T130713Z"
5
+ }
chat_template.jinja ADDED
@@ -0,0 +1 @@
 
 
1
+ {%- for msg in messages if msg.get('role') == 'system' -%}{%- if msg.get('content') is string -%}{{ msg['content'] }}{%- else -%}{%- for item in msg['content'] if item.get('type') == 'text' -%}{{ item['text'] }}{%- endfor -%}{%- endif -%}{%- endfor -%}{%- for msg in messages if msg.get('role') != 'system' -%}{%- if msg.get('content') is string -%}{{ msg['content'] }}{%- else -%}{%- set existing_text = msg['content'] | selectattr('type', 'equalto', 'text') | map(attribute='text') | join -%}{%- set has_manual_placeholders = ('<|image|>' in existing_text) or ('<|video|>' in existing_text) or ('<|audio|>' in existing_text) -%}{%- for item in msg['content'] -%}{%- if item.get('type') == 'text' -%}{{ item['text'] }}{%- elif not has_manual_placeholders and item.get('type') == 'image' -%}<|image|>{%- elif not has_manual_placeholders and item.get('type') == 'video' -%}<|video|>{%- elif not has_manual_placeholders and item.get('type') == 'audio' -%}<|audio|>{%- endif -%}{%- endfor -%}{%- endif -%}{%- endfor -%}
config.json ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "EmbeddingGemma2Model"
4
+ ],
5
+ "audio_config": {
6
+ "_name_or_path": "",
7
+ "architectures": null,
8
+ "attention_chunk_size": 12,
9
+ "attention_context_left": 13,
10
+ "attention_context_right": 0,
11
+ "attention_invalid_logits_value": -1000000000.0,
12
+ "attention_logit_cap": 50.0,
13
+ "chunk_size_feed_forward": 0,
14
+ "conv_kernel_size": 5,
15
+ "dtype": "bfloat16",
16
+ "gradient_clipping": 10000000000.0,
17
+ "hidden_act": "silu",
18
+ "hidden_size": 1024,
19
+ "id2label": {
20
+ "0": "LABEL_0",
21
+ "1": "LABEL_1"
22
+ },
23
+ "initializer_range": 0.02,
24
+ "is_encoder_decoder": false,
25
+ "label2id": {
26
+ "LABEL_0": 0,
27
+ "LABEL_1": 1
28
+ },
29
+ "model_type": "gemma4_audio",
30
+ "num_attention_heads": 8,
31
+ "num_hidden_layers": 12,
32
+ "output_attentions": false,
33
+ "output_hidden_states": false,
34
+ "output_proj_dims": 1536,
35
+ "problem_type": null,
36
+ "residual_weight": 0.5,
37
+ "return_dict": true,
38
+ "rms_norm_eps": 1e-06,
39
+ "subsampling_conv_channels": [
40
+ 128,
41
+ 32
42
+ ],
43
+ "use_clipped_linears": true
44
+ },
45
+ "audio_token_id": 258881,
46
+ "boa_token_id": 256000,
47
+ "boi_token_id": 255999,
48
+ "dtype": "bfloat16",
49
+ "eoa_token_index": 258883,
50
+ "eoi_token_id": 258882,
51
+ "image_token_id": 258880,
52
+ "initializer_range": 0.02,
53
+ "model_type": "embedding_gemma2",
54
+ "text_config": {
55
+ "attention_bias": false,
56
+ "attention_dropout": 0.0,
57
+ "bos_token_id": 2,
58
+ "dtype": "bfloat16",
59
+ "embedding_dim": 768,
60
+ "eos_token_id": 1,
61
+ "head_dim": 256,
62
+ "hidden_activation": "gelu_pytorch_tanh",
63
+ "hidden_size": 512,
64
+ "hidden_size_per_layer_input": 512,
65
+ "initializer_range": 0.02,
66
+ "intermediate_size": 2048,
67
+ "layer_types": [
68
+ "sliding_attention",
69
+ "sliding_attention",
70
+ "sliding_attention",
71
+ "sliding_attention",
72
+ "sliding_attention",
73
+ "full_attention",
74
+ "sliding_attention",
75
+ "sliding_attention",
76
+ "sliding_attention",
77
+ "sliding_attention",
78
+ "sliding_attention",
79
+ "full_attention",
80
+ "sliding_attention",
81
+ "sliding_attention",
82
+ "sliding_attention",
83
+ "sliding_attention",
84
+ "sliding_attention",
85
+ "full_attention",
86
+ "sliding_attention",
87
+ "sliding_attention",
88
+ "sliding_attention",
89
+ "sliding_attention",
90
+ "sliding_attention",
91
+ "full_attention"
92
+ ],
93
+ "max_position_embeddings": 262144,
94
+ "model_type": "embedding_gemma2_text",
95
+ "num_attention_heads": 4,
96
+ "num_hidden_layers": 24,
97
+ "num_key_value_heads": 2,
98
+ "pad_token_id": 0,
99
+ "per_layer_config": {
100
+ "05": {
101
+ "head_dim": 512,
102
+ "num_key_value_heads": 1
103
+ },
104
+ "11": {
105
+ "head_dim": 512,
106
+ "num_key_value_heads": 1
107
+ },
108
+ "17": {
109
+ "head_dim": 512,
110
+ "num_key_value_heads": 1
111
+ },
112
+ "23": {
113
+ "head_dim": 512,
114
+ "num_key_value_heads": 1
115
+ }
116
+ },
117
+ "rms_norm_eps": 1e-06,
118
+ "rope_parameters": {
119
+ "full_attention": {
120
+ "rope_theta": 1000000.0,
121
+ "rope_type": "default"
122
+ },
123
+ "sliding_attention": {
124
+ "rope_theta": 10000.0,
125
+ "rope_type": "default"
126
+ }
127
+ },
128
+ "sliding_window": 512,
129
+ "vocab_size": 262144
130
+ },
131
+ "transformers_version": "5.18.0.dev0",
132
+ "video_token_id": 258884,
133
+ "vision_config": {
134
+ "_name_or_path": "",
135
+ "architectures": null,
136
+ "attention_bias": false,
137
+ "attention_dropout": 0.0,
138
+ "chunk_size_feed_forward": 0,
139
+ "default_output_length": 280,
140
+ "dtype": "bfloat16",
141
+ "global_head_dim": 64,
142
+ "head_dim": 64,
143
+ "hidden_activation": "gelu_pytorch_tanh",
144
+ "hidden_size": 768,
145
+ "id2label": {
146
+ "0": "LABEL_0",
147
+ "1": "LABEL_1"
148
+ },
149
+ "initializer_range": 0.02,
150
+ "intermediate_size": 3072,
151
+ "is_encoder_decoder": false,
152
+ "label2id": {
153
+ "LABEL_0": 0,
154
+ "LABEL_1": 1
155
+ },
156
+ "max_position_embeddings": 131072,
157
+ "model_type": "gemma4_vision",
158
+ "num_attention_heads": 12,
159
+ "num_hidden_layers": 16,
160
+ "num_key_value_heads": 12,
161
+ "output_attentions": false,
162
+ "output_hidden_states": false,
163
+ "patch_size": 16,
164
+ "pooling_kernel_size": 3,
165
+ "position_embedding_size": 10240,
166
+ "problem_type": null,
167
+ "return_dict": true,
168
+ "rms_norm_eps": 1e-06,
169
+ "rope_parameters": {
170
+ "rope_theta": 100.0,
171
+ "rope_type": "axial"
172
+ },
173
+ "standardize": false,
174
+ "use_clipped_linears": false
175
+ },
176
+ "vision_soft_tokens_per_image": 280
177
+ }
config_sentence_transformers.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "__version__": {
3
+ "pytorch": "2.14.0+cu130",
4
+ "sentence_transformers": "6.1.0",
5
+ "transformers": "5.18.0.dev0"
6
+ },
7
+ "default_prompt_name": null,
8
+ "model_type": "SentenceTransformer",
9
+ "prompts": {
10
+ "BitextMining": "task: search result | query: ",
11
+ "Classification": "task: classification | query: ",
12
+ "Clustering": "task: clustering | query: ",
13
+ "CodeRetrieval": "task: code retrieval | query: ",
14
+ "Document": "title: none | text: ",
15
+ "FactChecking": "task: fact checking | query: ",
16
+ "InstructionRetrieval": "task: code retrieval | query: ",
17
+ "MultilabelClassification": "task: classification | query: ",
18
+ "PairClassification": "task: sentence similarity | query: ",
19
+ "QuestionAnswering": "task: question answering | query: ",
20
+ "Reranking": "task: search result | query: ",
21
+ "Retrieval": "task: search result | query: ",
22
+ "Retrieval-document": "title: none | text: ",
23
+ "Retrieval-query": "task: search result | query: ",
24
+ "STS": "task: sentence similarity | query: ",
25
+ "SearchQuery": "task: search result | query: ",
26
+ "SentenceSimilarity": "task: sentence similarity | query: ",
27
+ "Summarization": "task: sentence similarity | query: ",
28
+ "document": "title: none | text: ",
29
+ "query": "task: search result | query: "
30
+ },
31
+ "requirements": {
32
+ "sentence-transformers": {
33
+ "reason": "Older versions ignore the order of multimodal dict inputs and cannot put several images or texts into a single embedding.",
34
+ "specifier": ">=6.1.0"
35
+ }
36
+ },
37
+ "similarity_fn_name": "cosine"
38
+ }
convert_gemma2.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from pathlib import Path
3
+
4
+ import torch
5
+ import torch.nn.functional as F
6
+ from coreai_torch import TorchConverter, get_decomp_table
7
+ from transformers import AutoModel, AutoProcessor
8
+
9
+
10
+ def unfold(x, dimension, size, step):
11
+ dimension %= x.ndim
12
+ return torch.stack(
13
+ [
14
+ x.narrow(dimension, i, size)
15
+ for i in range(0, x.shape[dimension] - size + 1, step)
16
+ ],
17
+ dim=dimension,
18
+ ).movedim(dimension + 1, -1)
19
+
20
+
21
+ def save(model, sample, path):
22
+ with torch.no_grad():
23
+ ep = torch.export.export(model, sample, strict=False)
24
+ table = get_decomp_table()
25
+ table[torch.ops.aten.unfold.default] = unfold
26
+ ep = ep.run_decompositions(table)
27
+ print("Converting", path, flush=True)
28
+ TorchConverter().add_exported_program(ep).to_coreai().save_asset(path)
29
+ return list(ep.graph_signature.user_outputs)
30
+
31
+
32
+ class Text(torch.nn.Module):
33
+ def __init__(self, m):
34
+ super().__init__()
35
+ self.m = m.language_model
36
+
37
+ def forward(self, input_ids, attention_mask, media_embeddings, media_mask):
38
+ emb = self.m.embed_tokens(input_ids)
39
+ emb = torch.where(media_mask, media_embeddings.to(emb.dtype), emb)
40
+ n = input_ids.shape[1]
41
+ pos = torch.arange(n, device=input_ids.device).unsqueeze(0)
42
+ mask = attention_mask[:, None, None, :].bool().expand(1, 1, n, n)
43
+ sliding = (
44
+ pos[:, :, None] - pos[:, None, :]
45
+ ).abs() <= self.m.config.sliding_window
46
+ hidden = self.m(
47
+ inputs_embeds=emb,
48
+ position_ids=pos,
49
+ attention_mask={
50
+ "full_attention": mask,
51
+ "sliding_attention": mask & sliding[:, None],
52
+ },
53
+ ).last_hidden_state
54
+ weights = attention_mask[:, :, None].float()
55
+ pooled = (hidden.float() * weights).sum(1) / weights.sum(1).clamp(min=1)
56
+ return F.normalize(pooled, dim=-1)
57
+
58
+
59
+ class Vision(torch.nn.Module):
60
+ def __init__(self, m):
61
+ super().__init__()
62
+ self.v = m.vision_tower
63
+ self.project = m.embed_vision
64
+
65
+ def forward(self, pixel_values, pixel_position_ids):
66
+ v = self.v
67
+ pad = (pixel_position_ids == -1).all(-1)
68
+ emb = v.patch_embedder(pixel_values, pixel_position_ids, pad)
69
+ hidden = v.encoder(
70
+ inputs_embeds=emb,
71
+ attention_mask=~pad,
72
+ pixel_position_ids=pixel_position_ids,
73
+ ).last_hidden_state
74
+ hidden, mask = v.pooler(
75
+ hidden, pixel_position_ids, pad, pixel_values.shape[1] // 9
76
+ )
77
+ return self.project(hidden.to(emb.dtype)), mask
78
+
79
+
80
+ class Audio(torch.nn.Module):
81
+ def __init__(self, m):
82
+ super().__init__()
83
+ self.a = m.audio_tower
84
+ self.project = m.embed_audio
85
+
86
+ def forward(self, input_features, input_features_mask):
87
+ out = self.a(input_features, attention_mask=input_features_mask)
88
+ return self.project(out.last_hidden_state), out.attention_mask
89
+
90
+
91
+ if __name__ == "__main__":
92
+ torch.set_num_threads(4)
93
+ root = Path("bundles/embeddinggemma-2-CoreAI")
94
+ root.mkdir(parents=True, exist_ok=True)
95
+ m = AutoModel.from_pretrained(
96
+ "upstream/embeddinggemma-2",
97
+ dtype=torch.float32,
98
+ attn_implementation={
99
+ "language_model": "sdpa",
100
+ "vision_tower": "sdpa",
101
+ "audio_tower": "eager",
102
+ },
103
+ ).eval()
104
+ print("Loaded", flush=True)
105
+ manifest = {
106
+ "source": "google/embeddinggemma-2",
107
+ "revision": "914f7f89142e33e77833254d9c9b90c3cef7303b",
108
+ "precision": "float32",
109
+ "sequence_length": 1024,
110
+ "transformers_revision": "536ecc007387a50e77603bb5d92100e9b07514cc",
111
+ }
112
+ sample = (
113
+ torch.ones(1, 1024, dtype=torch.int32),
114
+ torch.ones(1, 1024, dtype=torch.int32),
115
+ torch.zeros(1, 1024, 512),
116
+ torch.zeros(1, 1024, 1, dtype=torch.bool),
117
+ )
118
+ manifest["text_outputs"] = save(Text(m).eval(), sample, root / "embedding.aimodel")
119
+ (root / "manifest.json").write_text(json.dumps(manifest, indent=2))
120
+ p = AutoProcessor.from_pretrained("upstream/embeddinggemma-2")
121
+ from PIL import Image
122
+
123
+ e = p(images=Image.new("RGB", (224, 224)), return_tensors="pt")
124
+ print("Image shapes", [(k, tuple(v.shape)) for k, v in e.items()], flush=True)
125
+ sample = (e["pixel_values"].float(), e["image_position_ids"].int())
126
+ manifest["vision_outputs"] = save(Vision(m).eval(), sample, root / "vision.aimodel")
127
+ manifest["vision_shape"] = list(sample[0].shape)
128
+ (root / "manifest.json").write_text(json.dumps(manifest, indent=2))
129
+ import numpy as np
130
+
131
+ e = p(
132
+ audio=np.sin(np.arange(16000) * 0.1).astype(np.float32),
133
+ sampling_rate=16000,
134
+ return_tensors="pt",
135
+ )
136
+ print("Audio shapes", [(k, tuple(v.shape)) for k, v in e.items()], flush=True)
137
+ sample = (
138
+ F.pad(
139
+ e["input_features"].float(), (0, 0, 0, 1120 - e["input_features"].shape[1])
140
+ ),
141
+ F.pad(
142
+ e["input_features_mask"].bool(),
143
+ (0, 1120 - e["input_features_mask"].shape[1]),
144
+ ),
145
+ )
146
+ manifest["audio_outputs"] = save(Audio(m).eval(), sample, root / "audio.aimodel")
147
+ manifest["audio_shape"] = list(sample[0].shape)
148
+ (root / "manifest.json").write_text(json.dumps(manifest, indent=2))
coreai_embeddinggemma2.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """EmbeddingGemma 2 adapter. Neural computation runs exclusively in Core AI."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ import numpy as np
7
+ from coreai.runtime import AIModel, NDArray, SpecializationOptions
8
+ from transformers import AutoProcessor
9
+
10
+
11
+ class EmbeddingGemma2CoreAI:
12
+ @classmethod
13
+ async def load(cls, bundle):
14
+ self = cls()
15
+ self.root = Path(bundle)
16
+ self.manifest = json.loads((self.root / "manifest.json").read_text())
17
+ self.processor = AutoProcessor.from_pretrained(self.root)
18
+ self.prompts = json.loads(
19
+ (self.root / "config_sentence_transformers.json").read_text()
20
+ )["prompts"]
21
+ self.models = {}
22
+ self.functions = {}
23
+ for name, asset in [
24
+ ("text", "embedding"),
25
+ ("vision", "vision"),
26
+ ("audio", "audio"),
27
+ ]:
28
+ self.models[name] = await AIModel.load(
29
+ self.root / f"{asset}.aimodel", SpecializationOptions.default()
30
+ )
31
+ self.functions[name] = self.models[name].load_function("main")
32
+ return self
33
+
34
+ async def call(self, name, inputs):
35
+ result = await self.functions[name](
36
+ {k: NDArray(np.ascontiguousarray(v)) for k, v in inputs.items()}
37
+ )
38
+ return [result[k].numpy() for k in self.manifest[f"{name}_outputs"]]
39
+
40
+ async def encode(
41
+ self,
42
+ text=None,
43
+ *,
44
+ images=None,
45
+ videos=None,
46
+ audio=None,
47
+ sampling_rate=16000,
48
+ prompt_name=None,
49
+ truncate_dim=768,
50
+ ):
51
+ if not 1 <= truncate_dim <= 768:
52
+ raise ValueError("truncate_dim must be between 1 and 768")
53
+ if prompt_name:
54
+ if images is not None or videos is not None or audio is not None:
55
+ raise ValueError("Task prompts apply to text-only inputs")
56
+ text = self.prompts[prompt_name] + (text or "")
57
+ kwargs = {"return_tensors": "np"}
58
+ if text is not None:
59
+ kwargs["text"] = text
60
+ if images is not None:
61
+ kwargs["images"] = images
62
+ if videos is not None:
63
+ if not isinstance(videos, np.ndarray) or videos.ndim != 4:
64
+ raise ValueError(
65
+ "Video must be an already sampled NumPy [frames,height,width,channels] array"
66
+ )
67
+ kwargs["videos"] = videos
68
+ kwargs["videos_kwargs"] = {"do_sample_frames": False}
69
+ if audio is not None:
70
+ samples = np.asarray(audio)
71
+ if sampling_rate != 16000:
72
+ raise ValueError("Audio must be sampled at 16000 Hz")
73
+ if samples.ndim != 1 or len(samples) > 179200:
74
+ raise ValueError("One mono clip of at most 11.2 seconds is supported")
75
+ kwargs.update(audio=samples, sampling_rate=sampling_rate)
76
+ encoded = self.processor(**kwargs)
77
+ return await self.encode_processed(encoded, truncate_dim=truncate_dim)
78
+
79
+ async def encode_processed(self, e, truncate_dim=768):
80
+ ids = np.asarray(e["input_ids"], dtype=np.int32)
81
+ if ids.shape[0] != 1:
82
+ raise ValueError(
83
+ "Batch size must be one; call encode separately per example"
84
+ )
85
+ n = ids.shape[1]
86
+ cap = self.manifest["sequence_length"]
87
+ if n > cap:
88
+ raise ValueError(
89
+ f"Input needs {n} tokens, capacity {cap}; input was not truncated"
90
+ )
91
+ media = np.zeros((1, cap, 512), np.float32)
92
+ media_mask = np.zeros((1, cap, 1), bool)
93
+ p = self.processor
94
+ for pixels_key, pos_key, token in [
95
+ ("pixel_values", "image_position_ids", p.image_token_id),
96
+ ("pixel_values_videos", "video_position_ids", p.video_token_id),
97
+ ]:
98
+ if pixels_key not in e:
99
+ continue
100
+ features = []
101
+ for pixels, positions in zip(e[pixels_key], e[pos_key]):
102
+ count = len(pixels)
103
+ capacity = self.manifest["vision_shape"][1]
104
+ if count > capacity:
105
+ raise ValueError("Vision patch capacity exceeded")
106
+ pix = np.pad(
107
+ np.asarray(pixels, dtype=np.float32),
108
+ ((0, capacity - count), (0, 0)),
109
+ )
110
+ pos = np.pad(
111
+ np.asarray(positions, dtype=np.int32),
112
+ ((0, capacity - count), (0, 0)),
113
+ constant_values=-1,
114
+ )
115
+ values, valid = await self.call(
116
+ "vision",
117
+ {"pixel_values": pix[None], "pixel_position_ids": pos[None]},
118
+ )
119
+ features.append(values[valid.astype(bool)])
120
+ features = np.concatenate(features, axis=0)
121
+ slots = np.flatnonzero(ids[0] == token)
122
+ if len(slots) != len(features):
123
+ raise ValueError("Visual placeholder count differs from encoder output")
124
+ media[0, slots] = features
125
+ media_mask[0, slots] = True
126
+ if "input_features" in e:
127
+ features = []
128
+ for mel, mask in zip(e["input_features"], e["input_features_mask"]):
129
+ count = len(mel)
130
+ capacity = self.manifest["audio_shape"][1]
131
+ if count > capacity:
132
+ raise ValueError("Audio feature capacity exceeded")
133
+ mel = np.pad(
134
+ np.asarray(mel, dtype=np.float32), ((0, capacity - count), (0, 0))
135
+ )
136
+ mask = np.pad(np.asarray(mask, dtype=bool), (0, capacity - count))
137
+ values, valid = await self.call(
138
+ "audio",
139
+ {"input_features": mel[None], "input_features_mask": mask[None]},
140
+ )
141
+ features.append(values[valid.astype(bool)])
142
+ features = np.concatenate(features, axis=0)
143
+ slots = np.flatnonzero(ids[0] == p.audio_token_id)
144
+ if len(slots) != len(features):
145
+ raise ValueError("Audio placeholder count differs from encoder output")
146
+ media[0, slots] = features
147
+ media_mask[0, slots] = True
148
+ padded = np.pad(ids, ((0, 0), (0, cap - n)))
149
+ mask = np.pad(
150
+ np.asarray(e["attention_mask"], dtype=np.int32), ((0, 0), (0, cap - n))
151
+ )
152
+ value = (
153
+ await self.call(
154
+ "text",
155
+ {
156
+ "input_ids": padded,
157
+ "attention_mask": mask,
158
+ "media_embeddings": media,
159
+ "media_mask": media_mask,
160
+ },
161
+ )
162
+ )[0][0, :truncate_dim]
163
+ return value / np.linalg.norm(value)
embedding.aimodel/main.hash ADDED
@@ -0,0 +1 @@
 
 
1
+ �J��[}7I�u�jeHC���C�;1}�:#U����
embedding.aimodel/main.mlirb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b14af3c45b7d3749b575a76a654843a6b1a843b83b317d1e8d3a2355a8809f9c
3
+ size 1108810117
embedding.aimodel/metadata.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "creationDate" : "20261011T130702Z",
3
+ "assetVersion" : "2.0",
4
+ "producer" : "coreai-core 1.0.0b3"
5
+ }
manifest.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "source": "google/embeddinggemma-2",
3
+ "revision": "914f7f89142e33e77833254d9c9b90c3cef7303b",
4
+ "precision": "float32",
5
+ "sequence_length": 1024,
6
+ "transformers_revision": "536ecc007387a50e77603bb5d92100e9b07514cc",
7
+ "text_outputs": [
8
+ "div_1"
9
+ ],
10
+ "vision_outputs": [
11
+ "view_294",
12
+ "logical_not_8"
13
+ ],
14
+ "vision_shape": [
15
+ 1,
16
+ 2520,
17
+ 768
18
+ ],
19
+ "audio_outputs": [
20
+ "view_542",
21
+ "slice_2"
22
+ ],
23
+ "audio_shape": [
24
+ 1,
25
+ 1120,
26
+ 128
27
+ ]
28
+ }
modules.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "idx": 0,
4
+ "name": "0",
5
+ "path": "",
6
+ "type": "sentence_transformers.base.modules.transformer.Transformer"
7
+ },
8
+ {
9
+ "idx": 1,
10
+ "name": "1",
11
+ "path": "1_Pooling",
12
+ "type": "sentence_transformers.sentence_transformer.modules.pooling.Pooling"
13
+ },
14
+ {
15
+ "idx": 2,
16
+ "name": "2",
17
+ "path": "2_Normalize",
18
+ "type": "sentence_transformers.base.modules.normalize.Normalize"
19
+ }
20
+ ]
preprocessor_config.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "dither": 0.0,
3
+ "feature_extractor_type": "Gemma4AudioFeatureExtractor",
4
+ "feature_size": 128,
5
+ "fft_length": 512,
6
+ "fft_overdrive": false,
7
+ "frame_length": 320,
8
+ "hop_length": 160,
9
+ "input_scale_factor": 1.0,
10
+ "max_frequency": 8000.0,
11
+ "mel_floor": 0.001,
12
+ "min_frequency": 0.0,
13
+ "padding_side": "right",
14
+ "padding_value": 0.0,
15
+ "per_bin_mean": null,
16
+ "per_bin_stddev": null,
17
+ "preemphasis": 0.0,
18
+ "preemphasis_htk_flavor": true,
19
+ "return_attention_mask": true,
20
+ "sampling_rate": 16000
21
+ }
processor_config.json ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "audio_ms_per_token": 40,
3
+ "audio_seq_length": 280,
4
+ "feature_extractor": {
5
+ "dither": 0.0,
6
+ "feature_extractor_type": "Gemma4AudioFeatureExtractor",
7
+ "feature_size": 128,
8
+ "fft_length": 512,
9
+ "fft_overdrive": false,
10
+ "frame_length": 320,
11
+ "hop_length": 160,
12
+ "input_scale_factor": 1.0,
13
+ "max_frequency": 8000.0,
14
+ "mel_floor": 0.001,
15
+ "min_frequency": 0.0,
16
+ "padding_side": "right",
17
+ "padding_value": 0.0,
18
+ "per_bin_mean": null,
19
+ "per_bin_stddev": null,
20
+ "preemphasis": 0.0,
21
+ "preemphasis_htk_flavor": true,
22
+ "return_attention_mask": true,
23
+ "sampling_rate": 16000
24
+ },
25
+ "image_processor": {
26
+ "do_convert_rgb": true,
27
+ "do_normalize": false,
28
+ "do_rescale": true,
29
+ "do_resize": true,
30
+ "image_mean": [
31
+ 0.0,
32
+ 0.0,
33
+ 0.0
34
+ ],
35
+ "image_processor_type": "Gemma4ImageProcessor",
36
+ "image_seq_length": 280,
37
+ "image_std": [
38
+ 1.0,
39
+ 1.0,
40
+ 1.0
41
+ ],
42
+ "max_soft_tokens": 280,
43
+ "patch_size": 16,
44
+ "pooling_kernel_size": 3,
45
+ "resample": 3,
46
+ "rescale_factor": 0.00392156862745098
47
+ },
48
+ "image_seq_length": 280,
49
+ "processor_class": "EmbeddingGemma2Processor",
50
+ "video_processor": {
51
+ "add_timestamps": false,
52
+ "do_convert_rgb": true,
53
+ "do_normalize": false,
54
+ "do_rescale": true,
55
+ "do_resize": true,
56
+ "do_sample_frames": true,
57
+ "fps": 1,
58
+ "image_mean": [
59
+ 0.0,
60
+ 0.0,
61
+ 0.0
62
+ ],
63
+ "image_std": [
64
+ 1.0,
65
+ 1.0,
66
+ 1.0
67
+ ],
68
+ "max_frames": 32,
69
+ "max_soft_tokens": 140,
70
+ "overflow_strategy": "uniform",
71
+ "patch_size": 16,
72
+ "pooling_kernel_size": 3,
73
+ "resample": 3,
74
+ "rescale_factor": 0.00392156862745098,
75
+ "return_metadata": false,
76
+ "video_processor_type": "EmbeddingGemma2VideoProcessor"
77
+ }
78
+ }
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ torch==2.14.0
2
+ coreai-torch==0.4.3
3
+ coreai-core==1.0.0b3
4
+ transformers @ git+https://github.com/huggingface/transformers.git@536ecc007387a50e77603bb5d92100e9b07514cc
5
+ numpy==2.4.6
6
+ Pillow==12.3.0
7
+ torchvision==0.29.1
8
+ safetensors==0.8.0
9
+ huggingface-hub==1.33.0
sentence_bert_config.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "transformer_task": "feature-extraction",
3
+ "modality_config": {
4
+ "text": {
5
+ "method": "forward",
6
+ "method_output_name": "last_hidden_state"
7
+ },
8
+ "image": {
9
+ "method": "forward",
10
+ "method_output_name": "last_hidden_state"
11
+ },
12
+ "audio": {
13
+ "method": "forward",
14
+ "method_output_name": "last_hidden_state"
15
+ },
16
+ "video": {
17
+ "method": "forward",
18
+ "method_output_name": "last_hidden_state"
19
+ },
20
+ "message": {
21
+ "method": "forward",
22
+ "method_output_name": "last_hidden_state",
23
+ "format": "structured"
24
+ }
25
+ },
26
+ "module_output_name": "token_embeddings"
27
+ }
smoke.json ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "pytorch_neural_loading_disabled": true,
3
+ "cases": [
4
+ {
5
+ "modalities": [
6
+ "text",
7
+ "prompt_name"
8
+ ],
9
+ "shape": [
10
+ 768
11
+ ],
12
+ "unit_norm": 1.0
13
+ },
14
+ {
15
+ "modalities": [
16
+ "images"
17
+ ],
18
+ "shape": [
19
+ 768
20
+ ],
21
+ "unit_norm": 1.0
22
+ },
23
+ {
24
+ "modalities": [
25
+ "audio"
26
+ ],
27
+ "shape": [
28
+ 768
29
+ ],
30
+ "unit_norm": 1.0
31
+ },
32
+ {
33
+ "modalities": [
34
+ "videos"
35
+ ],
36
+ "shape": [
37
+ 768
38
+ ],
39
+ "unit_norm": 1.0
40
+ },
41
+ {
42
+ "modalities": [
43
+ "text",
44
+ "images",
45
+ "audio"
46
+ ],
47
+ "shape": [
48
+ 768
49
+ ],
50
+ "unit_norm": 1.0
51
+ }
52
+ ],
53
+ "invalid_inputs_rejected": 4
54
+ }
smoke_gemma2.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Run with no source checkpoint and disable PyTorch neural weight loading."""
2
+
3
+ import asyncio
4
+ import json
5
+ from pathlib import Path
6
+
7
+ import numpy as np
8
+ from PIL import Image
9
+ from transformers import PreTrainedModel
10
+
11
+ from coreai_embeddinggemma2 import EmbeddingGemma2CoreAI
12
+
13
+
14
+ def forbidden(*args, **kwargs):
15
+ raise AssertionError(
16
+ "PyTorch neural weight loading is forbidden in this smoke test"
17
+ )
18
+
19
+
20
+ PreTrainedModel.from_pretrained = classmethod(forbidden)
21
+
22
+
23
+ async def main():
24
+ root = Path("bundles/embeddinggemma-2-CoreAI")
25
+ runtime = await EmbeddingGemma2CoreAI.load(root)
26
+ audio = (0.2 * np.sin(2 * np.pi * 440 * np.arange(8000) / 16000)).astype(np.float32)
27
+ image = Image.new("RGB", (64, 96), (30, 70, 200))
28
+ video = np.stack([np.asarray(image), np.asarray(image)])
29
+ inputs = [
30
+ {"text": "What causes auroras?", "prompt_name": "SearchQuery"},
31
+ {"images": image},
32
+ {"audio": audio},
33
+ {"videos": video},
34
+ {"text": "<|image|> Blue. <|audio|> Tone.", "images": image, "audio": audio},
35
+ ]
36
+ results = []
37
+ for data in inputs:
38
+ v = await runtime.encode(**data)
39
+ assert (
40
+ v.shape == (768,)
41
+ and np.isfinite(v).all()
42
+ and abs(np.linalg.norm(v) - 1) < 1e-5
43
+ )
44
+ results.append(
45
+ {
46
+ "modalities": list(data),
47
+ "shape": list(v.shape),
48
+ "unit_norm": float(np.linalg.norm(v)),
49
+ }
50
+ )
51
+ for data in [
52
+ {"text": "word " * 3000},
53
+ {"audio": np.zeros(179201, np.float32)},
54
+ {"audio": audio, "sampling_rate": 8000},
55
+ {"text": "hello", "truncate_dim": 769},
56
+ ]:
57
+ try:
58
+ await runtime.encode(**data)
59
+ except ValueError:
60
+ pass
61
+ else:
62
+ raise AssertionError("Invalid input accepted")
63
+ (root / "smoke.json").write_text(
64
+ json.dumps(
65
+ {
66
+ "pytorch_neural_loading_disabled": True,
67
+ "cases": results,
68
+ "invalid_inputs_rejected": 4,
69
+ },
70
+ indent=2,
71
+ )
72
+ + "\n"
73
+ )
74
+ print("Core AI runtime-only smoke passed", flush=True)
75
+
76
+
77
+ if __name__ == "__main__":
78
+ asyncio.run(main())
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4d777ef5bdc1aa36227abdfb77c3e49e7b9c892d16e1b6bda41c393504828be4
3
+ size 32170510
tokenizer.model ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e594c8a90eb08d8bda498ff4747977dc827ae0c3c56b5c0d41a605a22d02ef03
3
+ size 4689013
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
+ }
validate_gemma2.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import json
3
+ import shutil
4
+ from pathlib import Path
5
+
6
+ import numpy as np
7
+ import torch
8
+ from PIL import Image
9
+ from transformers import AutoModel, AutoProcessor
10
+
11
+ from coreai_embeddinggemma2 import EmbeddingGemma2CoreAI
12
+
13
+
14
+ def cases():
15
+ wave = (0.2 * np.sin(2 * np.pi * 440 * np.arange(16000) / 16000)).astype(np.float32)
16
+ image = Image.new("RGB", (320, 192), (20, 80, 210))
17
+ gradient = Image.fromarray(
18
+ np.tile(np.arange(256, dtype=np.uint8)[None, :, None], (128, 1, 3))
19
+ )
20
+ frames = np.stack(
21
+ [
22
+ np.asarray(image.resize((128, 96))),
23
+ np.asarray(Image.new("RGB", (128, 96), (210, 50, 20))),
24
+ ]
25
+ )
26
+ return [
27
+ (
28
+ "query",
29
+ {"text": "What causes the northern lights?", "prompt_name": "SearchQuery"},
30
+ ),
31
+ (
32
+ "document",
33
+ {
34
+ "text": "The northern lights are caused by charged particles from the sun.",
35
+ "prompt_name": "Document",
36
+ },
37
+ ),
38
+ ("unprefixed", {"text": "An apple a day keeps the doctor away."}),
39
+ (
40
+ "code",
41
+ {"text": "def add(a, b): return a + b", "prompt_name": "CodeRetrieval"},
42
+ ),
43
+ ("spanish", {"text": "La biblioteca está abierta hasta las ocho."}),
44
+ ("japanese", {"text": "桜の花が春に咲きます。"}),
45
+ ("short", {"text": "cat"}),
46
+ ("long", {"text": "The oceans contain diverse ecosystems. " * 90}),
47
+ ("near-capacity", {"text": "The oceans contain diverse ecosystems. " * 160}),
48
+ ("image", {"images": image}),
49
+ ("gradient", {"images": gradient}),
50
+ ("image-text", {"text": "<|image|> A blue rectangle.", "images": image}),
51
+ ("two-images", {"images": [image, gradient]}),
52
+ ("audio", {"audio": wave}),
53
+ ("audio-text", {"text": "<|audio|> A musical tone.", "audio": wave}),
54
+ ("audio-short", {"audio": wave[:3200]}),
55
+ ("audio-long", {"audio": np.tile(wave, 5)}),
56
+ ("audio-limit", {"audio": np.pad(np.tile(wave, 11), (0, 3200))}),
57
+ ("video", {"videos": frames}),
58
+ ("video-text", {"text": "<|video|> Colors changing.", "videos": frames}),
59
+ (
60
+ "mixed",
61
+ {
62
+ "text": "<|image|> Blue. <|audio|> Tone. <|video|> Colors.",
63
+ "images": image,
64
+ "audio": wave,
65
+ "videos": frames,
66
+ },
67
+ ),
68
+ ]
69
+
70
+
71
+ async def main():
72
+ torch.set_num_threads(4)
73
+ root = Path("bundles/embeddinggemma-2-CoreAI")
74
+ source = Path("upstream/embeddinggemma-2")
75
+ for f in source.iterdir():
76
+ if f.is_file() and f.suffix in [".json", ".jinja", ".model"]:
77
+ shutil.copy2(f, root / f.name)
78
+ m = AutoModel.from_pretrained(
79
+ source,
80
+ dtype=torch.float32,
81
+ attn_implementation={
82
+ "language_model": "sdpa",
83
+ "vision_tower": "sdpa",
84
+ "audio_tower": "eager",
85
+ },
86
+ ).eval()
87
+ p = AutoProcessor.from_pretrained(source)
88
+ print("Loading Core AI", flush=True)
89
+ runtime = await EmbeddingGemma2CoreAI.load(root)
90
+ print("Loaded Core AI", flush=True)
91
+ records = []
92
+ for name, args in cases():
93
+ data = dict(args)
94
+ prompt = data.pop("prompt_name", None)
95
+ if prompt:
96
+ data["text"] = runtime.prompts[prompt] + data["text"]
97
+ if "audio" in data:
98
+ data["sampling_rate"] = 16000
99
+ if "videos" in data:
100
+ data["videos_kwargs"] = {"do_sample_frames": False}
101
+ e = p(**data, return_tensors="pt")
102
+ with torch.no_grad():
103
+ h = m(**e).last_hidden_state
104
+ mask = e["attention_mask"][:, :, None].float()
105
+ reference = torch.nn.functional.normalize(
106
+ (h * mask).sum(1) / mask.sum(1), dim=-1
107
+ )[0].numpy()
108
+ actual = await runtime.encode_processed({k: v.numpy() for k, v in e.items()})
109
+ record = {
110
+ "name": name,
111
+ "tokens": e["input_ids"].shape[1],
112
+ "max_abs_error": float(np.max(np.abs(actual - reference))),
113
+ "cosine": float(actual @ reference),
114
+ "finite": bool(np.isfinite(actual).all()),
115
+ "unit_norm": float(np.linalg.norm(actual)),
116
+ }
117
+ print(record, flush=True)
118
+ records.append(record)
119
+ assert (
120
+ record["finite"]
121
+ and record["cosine"] > 0.99999
122
+ and record["max_abs_error"] < 0.00005
123
+ ), record
124
+ for dim in [128, 256, 512]:
125
+ actual = await runtime.encode("The sky is blue.", truncate_dim=dim)
126
+ assert actual.shape == (dim,) and abs(np.linalg.norm(actual) - 1) < 1e-5
127
+ try:
128
+ await runtime.encode("word " * 3000)
129
+ except ValueError:
130
+ pass
131
+ else:
132
+ raise AssertionError("Over-capacity input accepted")
133
+ (root / "validation.json").write_text(
134
+ json.dumps(
135
+ {
136
+ "thresholds": {"min_cosine": 0.99999, "max_abs_error": 0.00005},
137
+ "cases": records,
138
+ "truncation_dims": [128, 256, 512],
139
+ "oversized_input_rejected": True,
140
+ },
141
+ indent=2,
142
+ )
143
+ + "\n"
144
+ )
145
+
146
+
147
+ if __name__ == "__main__":
148
+ asyncio.run(main())
validation.json ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "thresholds": {
3
+ "min_cosine": 0.99999,
4
+ "max_abs_error": 5e-05
5
+ },
6
+ "cases": [
7
+ {
8
+ "name": "query",
9
+ "tokens": 15,
10
+ "max_abs_error": 1.0617077350616455e-07,
11
+ "cosine": 0.9999999403953552,
12
+ "finite": true,
13
+ "unit_norm": 1.0
14
+ },
15
+ {
16
+ "name": "document",
17
+ "tokens": 20,
18
+ "max_abs_error": 1.2968666851520538e-07,
19
+ "cosine": 1.0,
20
+ "finite": true,
21
+ "unit_norm": 1.0
22
+ },
23
+ {
24
+ "name": "unprefixed",
25
+ "tokens": 11,
26
+ "max_abs_error": 2.086162567138672e-07,
27
+ "cosine": 0.9999998807907104,
28
+ "finite": true,
29
+ "unit_norm": 1.0
30
+ },
31
+ {
32
+ "name": "code",
33
+ "tokens": 20,
34
+ "max_abs_error": 1.8719583749771118e-07,
35
+ "cosine": 1.0,
36
+ "finite": true,
37
+ "unit_norm": 1.0
38
+ },
39
+ {
40
+ "name": "spanish",
41
+ "tokens": 10,
42
+ "max_abs_error": 1.0803341865539551e-07,
43
+ "cosine": 1.0000001192092896,
44
+ "finite": true,
45
+ "unit_norm": 1.0
46
+ },
47
+ {
48
+ "name": "japanese",
49
+ "tokens": 10,
50
+ "max_abs_error": 1.043081283569336e-07,
51
+ "cosine": 1.0,
52
+ "finite": true,
53
+ "unit_norm": 1.0
54
+ },
55
+ {
56
+ "name": "short",
57
+ "tokens": 3,
58
+ "max_abs_error": 7.450580596923828e-08,
59
+ "cosine": 1.0000001192092896,
60
+ "finite": true,
61
+ "unit_norm": 1.0
62
+ },
63
+ {
64
+ "name": "long",
65
+ "tokens": 543,
66
+ "max_abs_error": 1.46450474858284e-07,
67
+ "cosine": 0.9999998807907104,
68
+ "finite": true,
69
+ "unit_norm": 1.0
70
+ },
71
+ {
72
+ "name": "near-capacity",
73
+ "tokens": 963,
74
+ "max_abs_error": 1.175212673842907e-07,
75
+ "cosine": 1.0,
76
+ "finite": true,
77
+ "unit_norm": 1.0
78
+ },
79
+ {
80
+ "name": "image",
81
+ "tokens": 256,
82
+ "max_abs_error": 6.705522537231445e-08,
83
+ "cosine": 1.0000001192092896,
84
+ "finite": true,
85
+ "unit_norm": 1.0
86
+ },
87
+ {
88
+ "name": "gradient",
89
+ "tokens": 257,
90
+ "max_abs_error": 8.940696716308594e-08,
91
+ "cosine": 0.9999999403953552,
92
+ "finite": true,
93
+ "unit_norm": 1.0
94
+ },
95
+ {
96
+ "name": "image-text",
97
+ "tokens": 260,
98
+ "max_abs_error": 1.1548399925231934e-07,
99
+ "cosine": 1.0000001192092896,
100
+ "finite": true,
101
+ "unit_norm": 1.0
102
+ },
103
+ {
104
+ "name": "two-images",
105
+ "tokens": 512,
106
+ "max_abs_error": 1.8440186977386475e-07,
107
+ "cosine": 1.0,
108
+ "finite": true,
109
+ "unit_norm": 1.0
110
+ },
111
+ {
112
+ "name": "audio",
113
+ "tokens": 29,
114
+ "max_abs_error": 3.3155083656311035e-07,
115
+ "cosine": 1.000000238418579,
116
+ "finite": true,
117
+ "unit_norm": 1.0
118
+ },
119
+ {
120
+ "name": "audio-text",
121
+ "tokens": 33,
122
+ "max_abs_error": 2.4493783712387085e-07,
123
+ "cosine": 1.0,
124
+ "finite": true,
125
+ "unit_norm": 1.0
126
+ },
127
+ {
128
+ "name": "audio-short",
129
+ "tokens": 9,
130
+ "max_abs_error": 1.7462298274040222e-07,
131
+ "cosine": 1.0000001192092896,
132
+ "finite": true,
133
+ "unit_norm": 1.0
134
+ },
135
+ {
136
+ "name": "audio-long",
137
+ "tokens": 129,
138
+ "max_abs_error": 7.130438461899757e-07,
139
+ "cosine": 1.0,
140
+ "finite": true,
141
+ "unit_norm": 1.0
142
+ },
143
+ {
144
+ "name": "audio-limit",
145
+ "tokens": 284,
146
+ "max_abs_error": 9.760260581970215e-07,
147
+ "cosine": 0.9999999403953552,
148
+ "finite": true,
149
+ "unit_norm": 1.0
150
+ },
151
+ {
152
+ "name": "video",
153
+ "tokens": 266,
154
+ "max_abs_error": 1.126900315284729e-07,
155
+ "cosine": 1.0000001192092896,
156
+ "finite": true,
157
+ "unit_norm": 1.0
158
+ },
159
+ {
160
+ "name": "video-text",
161
+ "tokens": 269,
162
+ "max_abs_error": 1.341104507446289e-07,
163
+ "cosine": 1.0,
164
+ "finite": true,
165
+ "unit_norm": 1.0
166
+ },
167
+ {
168
+ "name": "mixed",
169
+ "tokens": 555,
170
+ "max_abs_error": 2.0302832126617432e-07,
171
+ "cosine": 1.0000001192092896,
172
+ "finite": true,
173
+ "unit_norm": 1.0
174
+ }
175
+ ],
176
+ "truncation_dims": [
177
+ 128,
178
+ 256,
179
+ 512
180
+ ],
181
+ "oversized_input_rejected": true
182
+ }
vision.aimodel/main.hash ADDED
@@ -0,0 +1 @@
 
 
1
+ ʿ��+#�\������- 8mC�S��-�i5�x
vision.aimodel/main.mlirb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cabf97de2b23a35cecea87c802f4f9f12d0b38126d43af539e932ddf6935f978
3
+ size 671516334
vision.aimodel/metadata.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "assetVersion" : "2.0",
3
+ "creationDate" : "20261011T130707Z",
4
+ "producer" : "coreai-core 1.0.0b3"
5
+ }