From 1743751ffac5c775c88cbfedf5186b7c87d9c522 Mon Sep 17 00:00:00 2001 From: Jesus Armando Anaya Date: Sun, 9 Aug 2026 02:24:55 -0700 Subject: [PATCH] chore(inference): remove what the fast auto-labeling slices left behind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three tests in tests/inference/test_provider.py were re-created in test_providers.py when the family resolver got its own file, and the originals were never removed — one of them character-for-character the same name. Each survivor is the stronger test: it drives provider_for through a real InferenceConnectionService rather than a hand-built model. best_of never read its `masks` argument; the choice comes entirely from the IoU head's scores. ConnectionAction had no importers and, unlike its three neighbours, was not package API either. Four comments no longer described the code beneath them: pyproject's claim that uv.lock does not carry extras (it carries them twice), AnnotationPage's note that the Inference surface had not shipped yet, and two ragged docstring wraps. No behaviour changes. Closes #459 --- .../ui-core/src/annotator/AnnotationPage.tsx | 9 +-- frontend/ui-core/src/data/inferenceQueries.ts | 1 - pyproject.toml | 8 ++- src/visionset/inference/__init__.py | 9 ++- src/visionset/inference/sam_provider.py | 4 +- tests/architecture/test_optional_runtime.py | 7 +-- tests/inference/test_provider.py | 62 +++---------------- tests/inference/test_sam_provider.py | 4 +- 8 files changed, 29 insertions(+), 75 deletions(-) diff --git a/frontend/ui-core/src/annotator/AnnotationPage.tsx b/frontend/ui-core/src/annotator/AnnotationPage.tsx index 274e146a..73b94c4d 100644 --- a/frontend/ui-core/src/annotator/AnnotationPage.tsx +++ b/frontend/ui-core/src/annotator/AnnotationPage.tsx @@ -372,10 +372,11 @@ export interface AnnotationPageProps { * Where somebody goes to set up a model connection, if the app has such a * screen (#424, D6). * - * Optional, and it is expected to be absent for now: the Inference surface - * waits on #421's open rail question, and `ui-core` imports no router. Absent, - * the suggest tool's panel still says what is missing and simply renders no - * control — a host that cannot honour one renders none rather than a dead one. + * Optional because `ui-core` imports no router and cannot know whether its + * host has such a screen — the app does, and wires this to the Inference + * section. Absent, the suggest tool's panel still says what is missing and + * simply renders no control: a host that cannot honour one renders none + * rather than a dead one. */ readonly onConfigureInference?: () => void; } diff --git a/frontend/ui-core/src/data/inferenceQueries.ts b/frontend/ui-core/src/data/inferenceQueries.ts index e45f2173..7a258a18 100644 --- a/frontend/ui-core/src/data/inferenceQueries.ts +++ b/frontend/ui-core/src/data/inferenceQueries.ts @@ -93,7 +93,6 @@ export interface SuggestionOut { readonly region?: SuggestedRegion | null; } -export type ConnectionAction = components["schemas"]["ConnectionAction"]; export type ConnectionType = components["schemas"]["ConnectionType"]; export type ConnectionSetupState = components["schemas"]["ConnectionSetupState"]; export type DownloadSizeOut = components["schemas"]["DownloadSizeOut"]; diff --git a/pyproject.toml b/pyproject.toml index 51c882b6..7a6ffbb4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,8 +63,12 @@ dependencies = [ # # The canonical spelling is `local-inference`; it supersedes the earlier # `autolabel` framing. Version floors rather than pins: this is a user's -# environment and their CUDA build decides most of it. `uv.lock` does not carry -# extras, so nothing here is resolved by a `uv sync` in this repository. +# environment and their CUDA build decides most of it. `uv.lock` does record the +# extra — once as a member list, once per member under `requires-dist` — so +# editing these lines without re-running `uv lock` leaves it stale and +# `uv lock --check` fails on the result. What a plain `uv sync` does not do is +# *install* it: that takes `--extra local-inference`, which is how two gigabytes +# of CUDA wheels stay out of a developer's environment by default. # # `torchvision` is here because SAM 2's image processor imports it, and nothing # else in this list brings it: `Sam2ImageProcessor` refuses to construct without diff --git a/src/visionset/inference/__init__.py b/src/visionset/inference/__init__.py index a8749923..df08f750 100644 --- a/src/visionset/inference/__init__.py +++ b/src/visionset/inference/__init__.py @@ -14,11 +14,10 @@ **Importing this package imports nothing heavy.** Every reference to torch, transformers, accelerate and huggingface_hub is inside a function, and torchvision — which nothing here names, and ``transformers`` imports for us — is -held to the same line — see -``_extra`` for why that is load-bearing rather than tidy — so a base install -starts a server, runs a worker and imports this module without the optional -runtime present. ``tests/architecture/test_optional_runtime.py`` proves it in a -fresh interpreter. +held to the same line. See ``_extra`` for why that is load-bearing rather than +tidy: a base install starts a server, runs a worker and imports this module +without the optional runtime present, and +``tests/architecture/test_optional_runtime.py`` proves it in a fresh interpreter. **There is no plugin registry, and resolution happens in two steps.** #418's recorded decision is that adapters are instantiated from user-created model diff --git a/src/visionset/inference/sam_provider.py b/src/visionset/inference/sam_provider.py index 571e2ea9..5a6012b4 100644 --- a/src/visionset/inference/sam_provider.py +++ b/src/visionset/inference/sam_provider.py @@ -87,7 +87,7 @@ def points_and_labels(prompt: PointPrompt) -> tuple[list[list[float]], list[int] return points, labels -def best_of(iou_scores: list[float], masks: list[Any]) -> tuple[int, float]: +def best_of(iou_scores: list[float]) -> tuple[int, float]: """Which of the multi-mask answers to offer, and how sure it is. A segmenter of this family answers a single click with several masks at @@ -256,7 +256,7 @@ def _regions( outputs.pred_masks, original_sizes=[list(size)], binarize=True )[0] scores = [float(value) for value in outputs.iou_scores.flatten().tolist()] - chosen, confidence = best_of(scores, lifted) + chosen, confidence = best_of(scores) if confidence < minimum_confidence: return () mask = lifted.reshape(-1, *lifted.shape[-2:])[chosen] diff --git a/tests/architecture/test_optional_runtime.py b/tests/architecture/test_optional_runtime.py index e48945de..2d12b5d5 100644 --- a/tests/architecture/test_optional_runtime.py +++ b/tests/architecture/test_optional_runtime.py @@ -4,10 +4,9 @@ huggingface_hub in the `local-inference` extra, and `visionset/inference/_extra.py` reaches every one of them from inside a function — torchvision by never naming it at all, since it is `transformers` that imports it. Neither of those is -self-enforcing: a single -module-level `import torch` anywhere under `visionset.inference` would leave the -metadata unchanged and make roughly two gigabytes of CUDA wheels a condition of -starting the server. +self-enforcing: a single module-level `import torch` anywhere under +`visionset.inference` would leave the metadata unchanged and make roughly two +gigabytes of CUDA wheels a condition of starting the server. It is a condition of *starting* rather than of running a model because `visionset.jobs` imports its handler modules at package import to populate the diff --git a/tests/inference/test_provider.py b/tests/inference/test_provider.py index 42113df9..9a6e1cdf 100644 --- a/tests/inference/test_provider.py +++ b/tests/inference/test_provider.py @@ -1,9 +1,12 @@ -"""The composition root's refusals, and the adapter's tensor-to-domain half. +"""What the runtime's presence decides, and the adapter's tensor-to-domain half. -Two subjects, both reachable without the optional runtime and without a GPU: +Two subjects, both reachable without a GPU: -- `provider_for`, which answers "can this connection predict here?" and must - refuse with a sentence rather than a `None` or a stack trace; +- the half of `provider_for` that turns on whether the optional runtime is + installed here — the refusal that names the install command when it is not, + and structural conformance to the port when it is. The refusals that turn on + the *connection* instead are `test_providers.py`'s, beside the resolution + they belong to; - `regions_from`, the conversion the adapter does after a forward — the one part of running a model that can be wrong in a way no weights are needed to see. @@ -35,8 +38,6 @@ TextPrompt, ) from visionset.kernel.errors import ( - InferenceConnectionNotRunnable, - InferenceConnectionNotSetUp, LocalInferenceUnavailable, UnsupportedPrompt, ) @@ -59,58 +60,9 @@ def local( ) -def remote() -> InferenceConnection: - return InferenceConnection( - name="remote", - connection_type=ConnectionType.HTTP, - model_id="some/model", - model_revision="abc123", - endpoint_url="https://example.invalid/predict", - setup_state=ConnectionSetupState.READY, - ) - - # --- resolving a connection to something that can answer ---------------------- -def test_a_local_connection_without_weights_is_refused_by_state(tmp_path: Path) -> None: - """And the message names the action that fixes it. - - "Not set up" alone tells an operator what they already knew; the remedy is - the point, and it is a real one — the identical call succeeds after the - download. - """ - with pytest.raises(InferenceConnectionNotSetUp) as raised: - provider_for(local(), workspace_root=tmp_path) - assert "download_weights" in str(raised.value) - - -def test_an_http_connection_is_refused_because_this_build_has_no_adapter( - tmp_path: Path, -) -> None: - """A different refusal from the one above, deliberately. - - No state change fixes this and no wait helps — the adapter that would speak - to an endpoint is a later slice — so it must not be the error whose whole - meaning is "change the state and resubmit". - """ - with pytest.raises(InferenceConnectionNotRunnable) as raised: - provider_for(remote(), workspace_root=tmp_path) - assert "http" in str(raised.value) - - -def test_the_connections_own_state_is_reported_before_the_machines(tmp_path: Path) -> None: - """Order of the two checks, and it matters to whoever reads the answer. - - A not-set-up connection on a machine with no extra has two problems. Telling - somebody to run a `pip install` when what they needed was a download sends - them to the wrong place; the state is the one they can act on from where they - are standing. - """ - with pytest.raises(InferenceConnectionNotSetUp): - provider_for(local(), workspace_root=tmp_path) - - @pytest.mark.skipif(EXTRA_INSTALLED, reason="the local runtime is installed here") def test_a_ready_connection_without_the_runtime_names_the_install_command( tmp_path: Path, diff --git a/tests/inference/test_sam_provider.py b/tests/inference/test_sam_provider.py index ddf19556..5156ebab 100644 --- a/tests/inference/test_sam_provider.py +++ b/tests/inference/test_sam_provider.py @@ -94,11 +94,11 @@ def test_a_prompt_with_no_negatives_still_labels_every_point() -> None: def test_the_highest_scoring_mask_is_the_one_offered() -> None: """A click is ambiguous about scale; the model's own IoU head is what resolves it.""" - assert best_of([0.2, 0.91, 0.5], [[], [], []]) == (1, 0.91) + assert best_of([0.2, 0.91, 0.5]) == (1, 0.91) def test_a_score_outside_the_domains_bounds_is_clamped_rather_than_refused() -> None: - assert best_of([1.0000001], [[]])[1] == 1.0 + assert best_of([1.0000001])[1] == 1.0 # --- what it refuses ----------------------------------------------------------