diff --git a/alembic/versions/0069_default_siglip2.py b/alembic/versions/0069_default_siglip2.py new file mode 100644 index 0000000..7bef8b1 --- /dev/null +++ b/alembic/versions/0069_default_siglip2.py @@ -0,0 +1,51 @@ +"""default the embedder to SigLIP 2 — for FRESH installs only (#1203) + +Make SigLIP 2 (so400m, 512px; a 1152-d drop-in) the default embedder. New +installs start on it. An EXISTING library is NOT touched: flipping its stored +embedder version would mark every embedding stale (the scorer is version-gated) +and kill suggestions until a full re-embed+retrain — so an existing instance +switches deliberately via Settings → GPU agent → Embedding model → Re-embed → +Retrain. We detect "fresh" by the absence of any embedded image. + +Revision ID: 0069 +Revises: 0068 +Create Date: 2026-06-30 +""" +from typing import Sequence, Union + +from alembic import op + +revision: str = "0069" +down_revision: Union[str, None] = "0068" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + +_NEW_NAME = "google/siglip2-so400m-patch16-512" +_NEW_VERSION = "siglip2-so400m-patch16-512" +_OLD_NAME = "google/siglip-so400m-patch14-384" +_OLD_VERSION = "siglip-so400m-patch14-384" + + +def upgrade() -> None: + # Fresh install (nothing embedded yet) → adopt SigLIP 2. + op.execute( + f""" + UPDATE ml_settings SET + embedder_model_name = '{_NEW_NAME}', + embedder_model_version = '{_NEW_VERSION}' + WHERE NOT EXISTS ( + SELECT 1 FROM image_record WHERE siglip_embedding IS NOT NULL + ) + """ + ) + op.alter_column("ml_settings", "embedder_model_name", server_default=_NEW_NAME) + op.alter_column( + "ml_settings", "embedder_model_version", server_default=_NEW_VERSION + ) + + +def downgrade() -> None: + op.alter_column("ml_settings", "embedder_model_name", server_default=_OLD_NAME) + op.alter_column( + "ml_settings", "embedder_model_version", server_default=_OLD_VERSION + ) diff --git a/backend/app/api/ml_admin.py b/backend/app/api/ml_admin.py index 054fa69..5afeee8 100644 --- a/backend/app/api/ml_admin.py +++ b/backend/app/api/ml_admin.py @@ -23,6 +23,36 @@ _EDITABLE = ( ) +# Supported embedders for the Settings dropdown — all 1152-d so a swap is a +# drop-in (re-embed + retrain, no schema change). Server-authoritative so the UI +# never free-types a model name. +SUPPORTED_EMBEDDERS = ( + { + "name": "google/siglip2-so400m-patch16-512", + "version": "siglip2-so400m-patch16-512", + "label": "SigLIP 2 · so400m · 512px (recommended)", + "dim": 1152, + }, + { + "name": "google/siglip2-so400m-patch16-384", + "version": "siglip2-so400m-patch16-384", + "label": "SigLIP 2 · so400m · 384px (faster)", + "dim": 1152, + }, + { + "name": "google/siglip-so400m-patch14-384", + "version": "siglip-so400m-patch14-384", + "label": "SigLIP 1 · so400m · 384px (original)", + "dim": 1152, + }, +) + + +@ml_admin_bp.route("/embedder-models", methods=["GET"]) +async def embedder_models(): + return jsonify({"models": list(SUPPORTED_EMBEDDERS)}) + + @ml_admin_bp.route("/settings", methods=["GET"]) async def get_settings(): from sqlalchemy import select diff --git a/backend/app/models/ml_settings.py b/backend/app/models/ml_settings.py index 1d7c729..e415d9f 100644 --- a/backend/app/models/ml_settings.py +++ b/backend/app/models/ml_settings.py @@ -71,14 +71,16 @@ class MLSettings(Base): ccip_auto_apply_threshold: Mapped[float] = mapped_column( Float, nullable=False, default=0.92 ) + # Default = SigLIP 2 (so400m, 512px) for new installs (migration 0069); + # existing libraries keep their stored value until the operator re-embeds. embedder_model_version: Mapped[str] = mapped_column( - String(128), nullable=False, default="siglip-so400m-patch14-384" + String(128), nullable=False, default="siglip2-so400m-patch16-512" ) # The HF model NAME the embedder loads (server CPU embed + announced to the # GPU agent in the lease). Operator-settable so the embedder is a choice, not # a hardcode (#1190): set name + version together, then re-embed + retrain. embedder_model_name: Mapped[str] = mapped_column( - String(128), nullable=False, default="google/siglip-so400m-patch14-384" + String(128), nullable=False, default="google/siglip2-so400m-patch16-512" ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now() diff --git a/frontend/src/components/settings/GpuAgentCard.vue b/frontend/src/components/settings/GpuAgentCard.vue index 45f1638..37432bb 100644 --- a/frontend/src/components/settings/GpuAgentCard.vue +++ b/frontend/src/components/settings/GpuAgentCard.vue @@ -117,15 +117,12 @@ the suggest cut; 0.92 recommended.
- -
- Changing the model means a DIFFERENT embedding space. After saving a new
- model + version, run Re-embed library (the GPU agent re-embeds
- whole images + concept crops), then Retrain heads. Suggestions
- degrade until both finish. SigLIP 2 (google/siglip2-so400m-patch16-512,
- version siglip2-so400m-patch16-512) is a 1152-d drop-in at
- 512px — no schema change.
+ Switching the model is a DIFFERENT embedding space. After Save model,
+ run Re-embed library (the GPU agent re-embeds whole images + concept
+ crops), then Retrain heads — suggestions degrade until both finish.
+ SigLIP 2 (512px) is a 1152-d drop-in over SigLIP 1; new installs default to
+ it. Your existing library stays on its current model until you re-embed.