--- title: VisualOps Qwen3-VL Embeddings emoji: 🔍 colorFrom: blue colorTo: indigo sdk: gradio app_file: app.py pinned: false short_description: Qwen3-VL-Embedding-2B embeddings over a REST API --- # VisualOps Embedding Server (ZeroGPU) Hosts **Qwen/Qwen3-VL-Embedding-2B** and serves the VisualOps embedding contract. It replaces the Modal deployment; `app/embed_client.py` needs no change, only a new `EMBEDDING_SERVER_URL`. ## Contract | Method | Path | Body | Returns | |---|---|---|---| | `GET` | `/health` | – | status, model, backend, device, dtype, dimension, uptime | | `POST` | `/embed` | `{images: [b64], batch_size, dimension?}` | `{embeddings, dimension, count, model, normalized, elapsed_ms}` | | `POST` | `/embed_text` | `{texts: [str], is_query, batch_size, dimension?}` | same | Images are embedded **directly** — there is no captioning step. Text and images land in the same Qwen vector space, which is what makes cross-modal retrieval work. ## Setup 1. Create the Space with **SDK = Gradio** (ZeroGPU is compatible with the Gradio SDK only). 2. In **Settings → Hardware**, select **ZeroGPU**. Hosting requires a personal account in good standing (verified email, older than 30 days, max 2 Spaces) or PRO. 3. Optional: set the Space secret `EMBEDDING_SERVER_TOKEN` to require `Authorization: Bearer `. Leave unset to run open. ## Notes - The model is placed on `cuda` at **import**, not inside the GPU function — ZeroGPU's documented rule, and far faster than transferring per call. - Only the forward pass is wrapped in `@spaces.GPU`; decoding and serialisation stay outside it, because daily quota is consumed by GPU-held time. - Startup **fails loudly** if the model does not report 2048 dimensions, since the FAISS manifest pins that width. - Daily GPU quota: 5 min (free), 40 min (PRO). A full index rebuild plus the 25-query evaluation must fit inside it.