embeddinggemma-2-coreai / export /eg2_text_mm.py
iannedev's picture
v1.1-images
80c945e verified
Raw History Blame Contribute Delete
3.12 kB
# Copyright 2026 Everpaper. Licensed under the Apache License, Version 2.0 (see ../LICENSE).
"""The EmbeddingGemma 2 text encoder with multimodal (soft-token) inputs: `eg2_text.EmbeddingGemma2Text` plus a
mix of token embeddings and externally computed soft tokens at the start of the graph.
input_ids [1, S] int32 token ids; placeholder slots (<|image|> = 258880) are looked up like any id
attention_mask [1, S] fp16 1 = token, 0 = right padding
mm_embeds [1, S, 512] fp16 soft tokens placed at their slots (zeros elsewhere); NOT scaled by sqrt(512)
mm_mask [1, S] fp16 1 at a soft-token slot, 0 elsewhere
-> x = embed(input_ids) * (1 - mm_mask) + mm_embeds * mm_mask, then exactly the text graph (per-layer
embeddings, 24 layers, final norm, projection, masked mean pooling over ALL tokens, L2 normalization)
-> embedding [1, 768] fp16
With mm_mask all zero this is the text encoder (x * 1 + 0 is exact in fp16). eg2_text.py is unchanged (it is the
file that produced the text-only bundle); this module only re-states its forward with the mix.
"""
from __future__ import annotations
import torch
import eg2_text
class EmbeddingGemma2TextMM(eg2_text.EmbeddingGemma2Text):
def forward(self, input_ids, attention_mask, mm_embeds, mm_mask): # noqa: D102
S = input_ids.shape[1]
keep = (self.one - mm_mask)[:, :, None]
x = self.embed_tokens(input_ids) * keep + mm_embeds * mm_mask[:, :, None] # [1, S, 512]
ple = self.per_layer_projection_norm(
self.per_layer_model_projection(x).view(1, S, self.cfg.n_layers, self.cfg.ple_dim))
full, sliding = self._biases(attention_mask, S)
cs = {}
for lt in ("sliding_attention", "full_attention"):
cs[lt] = (getattr(self, f"cos_{lt}")[:S].to(x.dtype)[None, None], getattr(self, f"sin_{lt}")[:S].to(x.dtype)[None, None])
for i, layer in enumerate(self.layers):
lt = self.cfg.layer_types[i]
x = layer(x, ple[:, :, i, :], cs[lt][0], cs[lt][1], sliding if lt == "sliding_attention" else full)
x = self.embedding_projection(self.norm(x))
if eg2_text.PRECISION_MODE == "ane":
w = attention_mask * eg2_text._recip(attention_mask.sum(-1, keepdim=True))
pooled = torch.matmul(w[:, None, :], x)[:, 0, :]
u = pooled * eg2_text._recip(torch.maximum(pooled.abs().amax(-1, keepdim=True), self.floor16))
return u * torch.rsqrt((u * u).sum(-1, keepdim=True))
xf = x.float()
wf = attention_mask.float()
pooled = (xf * wf[:, :, None]).sum(1) / wf.sum(-1, keepdim=True)
return (pooled / pooled.norm(dim=-1, keepdim=True)).to(x.dtype)
def load_model(model_dir: str, dtype: torch.dtype, max_seq: int = 2048) -> EmbeddingGemma2TextMM:
"""Same weights and folding as eg2_text.load_model, mm forward."""
cfg = eg2_text.Cfg(model_dir)
m = EmbeddingGemma2TextMM(cfg, max_seq)
base = eg2_text.load_model(model_dir, dtype, max_seq)
m.load_state_dict(base.state_dict(), strict=True)
return m.to(dtype).eval()