Repository navigation
feat(embed): EmbeddingGemma 2 as a local, text-only embedding provider - #50
Merged
Merged
Conversation
…ansformers, CPU torch) EmbeddingGemma 2 runs on this machine through sentence-transformers, so the embed package gains one optional extra, `local`, and the published root carries the same extra (`atif-sql[local]`). Nothing installs it by default: CI and a plain `uv sync --all-packages` stay torch-free, and Cohere on Bedrock stays the default provider. - transformers 5.19.0 is the first release with EmbeddingGemma 2's model class. It was uploaded 2026-10-06T16:38:56Z, inside the seven-day cooldown, so it gets a per-package exclude-newer exemption to 2026-10-06T16:39:00Z, to be deleted after 2026-10-13T16:39Z. - pillow and torchvision are in the extra because sentence-transformers builds the model's AutoProcessor even for text, and its image half imports both: a text-only load without them fails with "requires the PIL library", then "No module named 'torchvision'". - torch and torchvision on Linux resolve from PyTorch's CPU index (explicit, marker-gated), so uv.lock holds 2.14.1+cpu and 0.29.1+cpu and no nvidia-* or cuda-* packages. The index publishes PEP 700 upload times, so the cooldown applies to it too. - test_distribution.py holds the root's extras equal to the members' extras, as it already does for dependencies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`ATIF_SQL_EMBED_PROVIDER=gemma` embeds and searches with Google's EmbeddingGemma 2 on this machine instead of Cohere Embed v4 on Bedrock, so step text never leaves the machine and an embed costs nothing. Cohere stays the default and nothing changes for an existing store. - The adapter loads google/embeddinggemma-2 through sentence-transformers text-only (vision_config and audio_config None, 270M of 740M parameters) at pinned revision 914f7f89142e33e77833254d9c9b90c3cef7303b, bfloat16 on a CUDA device that supports it and float32 elsewhere, never float16. Documents encode under the model's Document prompt and queries under SearchQuery, truncated to the Matryoshka width (768, 512, 256 or 128) and re-normalized. A failing batch or a non-finite vector leaves None slots, the port's bounded-loss contract. - Batches form shortest first under a padded-character budget: 32 texts near the 8,192-token window took 26 GB of RSS on CPU, and the tokenizer declares no maximum, so the loader caps max_seq_length at 8,192. - Each provider has its own default store: Cohere keeps <corpus_root>/embeddings_lance, Gemma writes embeddings_lance_gemma, so a switch never meets the (model, dim) guard and both stores coexist. `status` reports the selected provider and store, and the refresh script reads that store path for its terminal-marker check. - One factory, build_embedder, picks the adapter for backfill and search; the dry-run plan names provider, model, dim and store. - Selecting gemma without the `local` extra raises a terminal domain error naming the install command (exit 78), a width the provider cannot emit exits 64, and `search` now classifies a failed query embedding (70 or 78) instead of raising a traceback. - ATIF_SQL_OUTPUT_DIMENSION parses from the environment: as an integer Literal it refused every value an environment variable can carry. - torch, transformers and sentence_transformers join the lean-import gate. Tests use a fake SentenceTransformer and fake torch modules: no download, no network, and they pass with or without the extra installed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Says how to keep semantic search on the machine: install the `local` extra, set ATIF_SQL_EMBED_PROVIDER=gemma, and `embed` and `search` run EmbeddingGemma 2 locally, free, into their own store. - README: the install line for the extra (with `--torch-backend cpu` on Linux), a matrix row for the goal, and a section with the first-run download (1.5 GB), the measured CPU speed (200 steps of a Claude Code corpus in 156 s on a shared 16-core machine, about 4 GB of RSS) and the per-provider stores. - CLI reference: `embed` and `search` name both providers, the dry-run plan keys, the Gemma knobs and the exit codes the provider adds. - atif-embed README, AGENTS.md, CONTRIBUTING.md, SECURITY.md, the site index and CONTRACT-V2 name the second adapter where they named one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Embed and search on this machine with EmbeddingGemma 2
atif-sql embedandatif-sql searchsend step text and queries to Cohere Embed v4 on Bedrock, which bills an AWS account and takes the text off the machine. This adds Google's EmbeddingGemma 2 (google/embeddinggemma-2, Apache 2.0, released 2026-10-06) as a second embedding provider, run locally in its text-only setup.ATIF_SQL_EMBED_PROVIDER=gemmaselects it; Cohere stays the default and nothing changes for an existing store.What changed
local(build(deps)commit), on the embed package and the published root:sentence-transformers>=6.1.0,transformers>=5.19.0,torch>=2.14,torchvision>=0.29,pillow>=12.3.0. A plainuv sync --all-packages, CI included, stays torch-free.exclude-newer-packageexemption to 2026-10-06T16:39:00Z, to delete after 2026-10-13T16:39Z.explicit = true, markersys_platform == 'linux'), souv.lockholds2.14.1+cpuand0.29.1+cpuand nonvidia-*,cuda-*ortritonpackage (grep -cgives 0). The index publishes PEP 700 upload times (data-upload-time), so the cooldown applies to it as to PyPI. macOS and Windows take PyPI's CPU builds.test_distribution.pyholds the root's extras equal to the members' extras, as it does for dependencies.feat(embed)commit),atif_embed/infrastructure/gemma_local.py:config_kwargs={"vision_config": None, "audio_config": None}(270M of 740M parameters) at pinned revision914f7f89142e33e77833254d9c9b90c3cef7303b;Documentprompt and queries underSearchQuery, withtruncate_dimandnormalize_embeddings=True; widths 768 (default), 512, 256, 128;Noneslots, the port's bounded-loss contract;max_seq_lengthat the model's 8,192-token window. Both came from the smoke run: the tokenizer declares no maximum, and 32 long texts padded into one batch took 26 GB of RSS on CPU.<corpus_root>/embeddings_lance; Gemma writes<corpus_root>/embeddings_lance_gemma. Switching never meets the(model, dim)guard and both stores coexist;ATIF_SQL_LANCE_URIstill names one store for either.statusreportsembed_providerandembeddings_store, andscripts/atif-sql-refresh.shreads that path for its terminal-marker check, falling back to Cohere's path for an older CLI.build_embedder(settings)is the one place that names an adapter, for both backfill and search. The dry-run plan addsprovider,dimandstore. Gemma without the extra raises a terminal domain error naming the install command (exit 78); a width the provider cannot emit exits 64 instead of a pydantic traceback;searchclassifies a failed query embedding (70 or 78) instead of raising a traceback.ATIF_SQL_OUTPUT_DIMENSIONnever parsed from the environment: as an integerLiteral, pydantic refuses the string an environment variable carries (Input should be 256, 512, 1024 or 1536 [type=literal_error, input_value='512', input_type=str]on main). It is anintchecked against the selected provider's widths.torch,transformersandsentence_transformersjoin the forbidden list oftest_lean_import.py, and a new probe checks the Cohere path imports none of them. The adapter imports them by name, so ty and pyright pass without the extra.docscommit): README install line, a "What you can do" row, a "Search without sending text off the machine" section; CLI reference forembedandsearch; the embed package README, AGENTS.md, CONTRIBUTING.md, SECURITY.md, the site index and CONTRACT-V2.Verification
mise run checkgreen on each of the three commits from a fresh worktree, withmise run lock:checkafter it:build(deps): test 2495 passed, 1 skipped, 1 xfailed; pyright 0 errors;security:vex:checkgreen;lock:check"Resolved 156 packages".feat(embed): test 2541 passed, 1 skipped, 1 xfailed; pyright 0 errors;lock:checkgreen.docs: test 2541 passed, 1 skipped, 1 xfailed;docs:prose0 errors at the 273 baseline;lock:checkgreen.mise run test:covon the head: 90.47% against the 89% floor;gemma_local.py95%,providers.pyandsettings.py100%.mise run docs:install && mise run docs:gate: 0 broken internal links over 20 pages, astro check 0 errors, 73 vitest tests passed.docs:prose: 0 errors, 273 warnings, at the baseline.scripts/atif-sql-refresh-selftest.sh: 0 failures.uv.lock(torch 2.14.1, transformers 5.19.0, sentence-transformers 6.1.0, pillow 12.3.0, torchvision 0.29.1 and the rest): "No issues found".torch/sentence_transformersmodules, so they need no download or network and pass with or without the extra; 62 of them also ran in a venv with the extra installed. Planted defects: returningtorch.float16on CUDA turnstest_device_and_dtype_never_float16red; encoding queries under theDocumentprompt turns three query tests red; dropping torch from the root extra turnstest_root_extras_equal_the_members_extrasred.2.14.1+cpu, float32,HF_HUB_OFFLINE=1after one download of 1,488,915,288 bytes), 20 sessions copied from a Claude Code corpus,--no-lake:embed --dry-run: providergemma, modelgoogle/embeddinggemma-2, dim 768, 919 candidates, batch 32, concurrency 1, store.../embeddings_lance_gemma.embed --limit 200: 200 rows in 156 s of encoding (1.3 vectors/s; 170.6 s wall with the load), peak RSS 3.9 GB, on a shared 16-core host with load average 12 to 20. Short steps run at about 30 vectors/s (32 texts of 150 characters in 1.04 s); the 8 steps over 2,000 characters take most of the time. The store holds 200 rows stampedgoogle/embeddinggemma-2, dim 768,fixed_size_list<float>[768], unit-norm vectors.searchwith a paraphrase of one embedded step (it shares only "forecast bands" and "instrument" with the step, which says CUDA where the query says GPU) ranks that step first at sim 0.7794, next 0.7247; 9.8 s wall and 2.3 GB RSS, most of it the model load.uv tool install --torch-backend cpu "atif-sql[local] @ file://<checkout>"installstorch 2.14.1+cpuandtorchvision 0.29.1+cpuand nonvidia-*orcuda-*package.Risks
--torch-backend auto) to get bfloat16 on the GPU, which this PR did not measure.uv lock --upgradeafter 2026-10-13T16:39Z.What to look at
plan_batchesandBATCH_CHAR_BUDGETingemma_local.py: the budget (32,000 padded characters per forward pass) is a measured CPU choice, not a model limit.embed_store_dir.Sources read 2026-10-09: the model card (huggingface.co/google/embeddinggemma-2), uv's PyTorch guide (docs.astral.sh/uv/guides/integration/pytorch) and resolution docs on
exclude-newerand upload times (docs.astral.sh/uv/concepts/resolution), PyPI JSON for transformers 5.19.0, sentence-transformers 6.1.0, torch 2.14.1, torchvision 0.29.1 and pillow 12.3.0, and the PyTorch CPU index'sdata-upload-timeattributes.