[dev] and [docs] for
working on the package itself. The split exists because the encoder is the expensive
part and not everybody runs it. Someone who only calls a server should not download a
multi-gigabyte deep-learning stack to send an HTTP request, so the base install is the
HTTP client and nothing else.
Both paths are first class. jevimage.connect(url) and jevimage.load() answer the same
calls and return the same objects. Two methods differ: ask_embedding is local only
because its point is skipping the network, and health is remote only because there is
no server to report on locally. One argument differs too: template= on train() and
compare() has no remote equivalent, because the server picks the template. The full
list is the difference table.
Which one you install is a deployment decision, not an API decision. The server can be
yours: the axis is how many processes need the model, not who owns the machine. One
process (a script, a batch job, a single worker): use load(). Several worker
processes on one box: run jev serve there and connect() to it from each, because
uvicorn --workers N or N app processes each load their own copy of the weights, and
you pay N times 1.5 GB and N cold starts for a model that is idle most of the time.
Which one do you want
[serve] and [openclip] both depend on [local], so you never need to list it twice.
pip install 'jevimage[serve,openclip]' is valid and gives you all of it.
Sizes measured on the machine this page was written on (Linux, CPython 3.12, x86-64;
yours will differ, especially for torch):
Model weights are a separate download on top of
[local], and they are the big number:
see Model weights and offline use.
The API-only install
This is the one most people should start with. Asking, embedding, listing heads, deleting heads and training all work through it, because the encoder that fits the head lives on the server:encoder toy in the output);
against a real encoder the accuracies are real accuracies and everything else about the
exchange is identical.
Two things behave differently without torch, both deliberately:
Remote.embed()returns a plain list of lists instead of a(n, dim)tensor. With torch installed it returns the tensor, so the local and remote calls stay interchangeable.jev headswithout--urlstill needs torch. Not because of the files: the local branch importsjevimage.trainingfor its one formatting helper, and that module imports torch at module scope, so the command fails even when the head directory is empty or absent. The CLI’s error says “this command runs the encoder locally”, which overstates it slightly (no encoder is loaded), but the fix it names is the right one.jev rmis the exception: it is anunlinkand never takes that branch, so it works on the base install.
jev encoders works in the API-only install — listing which encoders exist should not
require the stack that runs them:
What load() does without [local]
It raises ImportError naming both fixes, rather than letting a ModuleNotFoundError
from three frames down be your first hint:
jevimage.Jev. jevimage.Encoder, register, available,
DEFAULT and JevError resolve without torch, so you can inspect the registry and
catch errors in an API-only process.
Verifying an install landed
Run the check for the level you installed. Each of these was run as printed. Base. Imports, version, and the encoder registry:health() is the end-to-end check — it tells you which
encoder your answers and heads will come from:
[local]. The real check is loading an encoder, because that is what exercises
torch, transformers and the weight cache at once:
[serve]. Start it and ask it how it is:
auth: open means no API key is set and anyone who can reach the port can use it. See
Serving before you bind it to anything but localhost.
[openclip]. python -c "import open_clip" and then jevimage.load("dfn5b-h-14-384").
Without the extra, the failure is the plain import error from the encoder’s constructor.
It is not wrapped, so read the last line:
Python versions
requires-python = ">=3.10". The source is written in PEP 604 union syntax
(str | None) throughout, and while from __future__ import annotations keeps most of
that from being evaluated, pydantic resolves the server’s request-model annotations at
runtime, so 3.10 is a real floor. Everything on this page was run on CPython 3.12.3;
the declared floor is what pip enforces, and 3.10 and 3.11 are not exercised here.
There is no upper bound in the metadata, so on a very new Python the thing likely to
stop you is torch, not jevimage.
From source
The package is not on PyPI yet. Until it is, everypip install jevimage...
line in these docs means the equivalent against a clone: pip install .,
pip install '.[local]', and so on.
setuptools>=77. That is the release where license = "MIT" became
the correct spelling in pyproject.toml, and an older setuptools will reject the
metadata. Normally pip fetches it into an isolated build environment for you. On a
machine with no index reachable, supply it yourself:
--no-deps there means you still have to have pillow (and,
for [local], torch and transformers) present by some other route.
CPU, GPU, and how the device is chosen
jevimage.encoders.resolve_device() decides, in this order:
- an explicit
device=argument to the encoder constructor, $JEV_DEVICE,"cuda"iftorch.cuda.is_available(),"cpu".
$JEV_DEVICE takes any string torch accepts, so cuda:1 and mps work as far as torch
does. A value torch does not recognise, or a CUDA ordinal this machine does not have, is
refused before any weights load - one line, exit 1, no model download for a typo.
Two caveats:
- Only the built-in
HFCLIPandOpenCLIPencoders consultresolve_device. A custom encoder you pass toload()does whatever you wrote. /v1/healthreportsgetattr(jev.encoder, "device", "cpu"). An encoder without a.deviceattribute is reported ascpuwhether or not that is true. The built-ins always set it.
nvidia-* wheels
listed above.
Model weights and offline use
jevimage downloads nothing itself.HFCLIP hands the model id to transformers and
OpenCLIP hands it to open_clip; both cache through huggingface_hub, and all the
offline knobs are huggingface’s, not jevimage’s.
Default cache, as reported by the installed huggingface_hub:
HF_HOME moves everything; HF_HUB_CACHE moves just the model blobs. Point
them at a shared volume if several processes or containers should share one copy. The
cache is content-addressed and safe to share read-only once it is populated.
What the default encoder actually occupies, from the cache on this machine:
siglip2-base-224. siglip2-so400m-384 came to 4.3 GB in the same cache.
Budget accordingly before picking a bigger encoder in a container image.
Air-gapped. Pre-download on a connected machine with the same encoder name, copy the
cache directory over, and set HF_HUB_OFFLINE=1 on the air-gapped one. Loading then
never touches the network:
hf download google/siglip2-base-patch16-224. That is
huggingface_hub’s own CLI, and on hub 1.x it is the only spelling that works; the older
huggingface-cli download now exits with “huggingface-cli is deprecated and no longer
works”. Calling jevimage.load("siglip2-base-224") once while the network is up does the
same job. The registry name to model id mapping is in jevimage/encoders.py; see
Choosing an encoder.
A cache miss under HF_HUB_OFFLINE=1 fails from transformers, not from jevimage, and
the message is about the hub rather than about the encoder name. Do not read it as
“no such encoder”:
load() accepts any object
with .name, .dim, .img() and .txt(), so an encoder you load from a local
directory needs no registry entry and no network. That is Choosing an
encoder.
Nothing about the API-only install touches the hub at all — no weights, no cache, no
HF_* variables. That is often the simplest answer to an air-gap: one machine holds the
weights, everything else connects to it.
Environment variables
Every variable the package itself reads. These are the complete set; anything else is read by torch, transformers or huggingface_hub, not here.jev serve --encoder, --heads-dir and --api-key work by setting JEV_ENCODER,
JEV_HEADS_DIR and JEV_API_KEYS in the process before the app is built, so the flags
and the variables are the same knob, and a unit file or container that sets the
variables directly behaves identically.
heads_dir= passed to jevimage.load() wins over both directory variables. JEV_URL,
JEV_API_KEY and JEV_TIMEOUT are read by the CLI only: from Python you pass the URL,
key and timeout= to jevimage.connect() yourself.
Failure modes
Next
Quickstart runs the same walkthrough twice, once against a server and once in-process. Serving coversjev serve for real traffic. Choosing an
encoder covers the registry, ensembles and bringing your own model.
← Overview · Quickstart →