Skip to content

Fix/avoid implicit cuda0 - #97

Closed
NLP-traveller wants to merge 102 commits into
AnswerDotAI:mainfrom
FOR-sight-ai:fix/avoid-implicit-cuda0
Closed

NLP-traveller wants to merge 102 commits into
AnswerDotAI:mainfrom
FOR-sight-ai:fix/avoid-implicit-cuda0

Conversation

@NLP-traveller

Copy link
Copy Markdown

Replace "cuda:0" by the use of given device

FOR team and others added 28 commits May 21, 2026 17:05
- actions/checkout@v4 -> @v5
- astral-sh/setup-uv@v3 -> @v8.1.0 (pinned exact tag; no floating v8 tag)
- softprops/action-gh-release@v2 -> @V3
- explicit 'version: latest' on setup-uv

Silences the Node.js 20 deprecation warnings reported on recent runs.
No version bump or tag triggered by this commit.
- vector_db_server: add GET /v1/admin/indexes and /v1/admin/data_folders,
  read-only walks of data_dir with a 60s TTL size cache. Auth follows
  the existing bearer-token middleware (reused api_key). Cache is
  invalidated on collection create/upsert/delete.
- embedding_server: add EmbeddingServerManager.device_info() which
  queries the remote host with nvidia-smi via the existing SSH channel
  and returns structured GPU info. Never raises.
- colpali: add an optional on_progress callback to MultiModalRetrieverModel.index
  / add_to_index. Emits start, file_start, page, file_done and all_done
  events. Best-effort: callback exceptions are swallowed.
- Tests: admin endpoints (12), device_info (3), on_progress (2).
- VectorDBServerManager and EmbeddingServerManager gain:
  * redeploy(on_line=None): force-rebuild + restart regardless of state
  * is_running(): non-raising container status probe
  * get_remote_metadata(): non-raising metadata getter
- _run_remote(cmd, on_line=None): streams stdout line by line into a
  callback when provided, otherwise preserves the existing buffered
  behaviour. Used by interactive UIs (Streamlit) to surface Docker
  build/pull/run output in real time.
- _deploy(on_line=None): threads the callback through every long-running
  remote command.

Tests: +8 (redeploy, on_line streaming, no-callback fallback for both
managers).
Both VectorDBServerManager and EmbeddingServerManager were calling
paramiko.SSHClient.connect with a raw hostname string, bypassing
~/.ssh/config. This caused socket.gaierror ('Name or service not
known') for any host that exists only as an alias in ~/.ssh/config
(e.g. with a ProxyCommand for a host behind a bastion).

Introduce foretrieval.ssh_utils.open_ssh_client(), a small helper
that:
  - Parses ~/.ssh/config via paramiko.SSHConfig
  - Resolves HostName, User, Port, IdentityFile, ProxyCommand and
    ProxyJump
  - Honours explicit overrides from the manager config (ssh_user,
    ssh_key_path) over the SSH config
  - Falls back to a direct connect when the SSH config file is
    missing, malformed, or has no matching Host block

Both managers' _get_ssh() delegate to this helper. Existing tests
unaffected (they mock _run_remote, not _get_ssh).

Tests: +12 (9 ssh_utils + 2 manager regressions + 1 cache check).
…a paths

The previous code used '~/foretrieval_db_build' for SFTP put and then
stripped the '~' with .replace('~', ''), producing a root-relative
'/foretrieval_db_build' path. SFTP returned ENOENT (FileNotFoundError)
on every real deploy. The remote shell happens to expand '~' in plain
_run_remote calls, so cd/cat/echo on the same path coincidentally
worked — masking the bug for some operations.

Fix:
- Drop '~/'-prefixed remote constants. Use relative subpaths:
    _REMOTE_BUILD_SUBDIR = 'foretrieval_db_build'
    _REMOTE_METADATA_SUBPATH = '.foretrieval/db_deployment.json' (vdb)
    _REMOTE_METADATA_SUBPATH = '.foretrieval/deployment.json'    (embed)
- New _remote_home() helper on both managers caches the result of
  sftp.normalize('.') for the duration of the manager's life.
- _upload_build_context, _read_remote_metadata, _write_remote_metadata,
  stop(): build absolute paths by joining _remote_home() with the
  relative subpath. No more '~' anywhere in remote command strings.

Tests: +7 (remote home resolution + caching, absolute metadata path,
absolute build-context SFTP destination, regression for the
stop()-removes-tilde-path case).
Files written by the FORetrieval vector-DB container under the
bind-mounted data_dir (/opt/for_index_db) were owned by root because
the container ran as root. This caused an ownership mismatch with
SFTP-uploaded _data/ directories (owned by the SSH user) and
prevented the SSH user from cleaning up or replacing index files.

- New _resolve_remote_uid_gid() helper: runs 'id -u && id -g' on the
  remote host via the existing SSH channel. Returns (uid, gid) on
  success or (None, None) on any failure.
- _build_docker_run_cmd(): adds '--user uid:gid' when resolution
  succeeds, so container-written files are owned by the SSH user on
  the host. Falls back silently when resolution fails (backward
  compatible with hosts without 'id' or where SSH is not configured).
- _make_manager() test helper now also stubs _resolve_remote_uid_gid
  so the existing _build_docker_run_cmd tests are unaffected.

Tests: +2 (--user flag present when uid resolved; absent when None).
The vector-DB server was unreachable because Python initialises the
top-level foretrieval package when any sub-module is imported with
'-m foretrieval.vector_db_server.server_main'. The eager __init__
pulled in colpali.py -> colpali_engine -> transformers -> torch, and
torch._dynamo crashed on the CPU-only Docker image:

  AssertionError: Artifact of type=precompile already registered
                  in mega-cache artifact factory

The server does not use ColPali, transformers or torch at all — it
only needs foretrieval.vector_db_server.* and foretrieval.vector_store.*.
Those sub-packages use relative imports and never touch the top-level
__init__, so the fix is simply to make __init__.py lazy.

Switch to PEP 562 module-level __getattr__:
  from foretrieval import MultiModalRetrieverModel  # still works
  import foretrieval; foretrieval.MultiModalRetrieverModel   # still works
  import foretrieval   # no longer imports colpali/torch as a side-effect

Tests: +4 (no colpali side-effect, both public names accessible,
AttributeError on unknown names).
…_dir

GET /v1/admin/indexes only returned an entry if the directory contained
index_config.json.gz or metadata.json.gz. Those files are client-side
sidecars written to ~/.forag/remote_indexes on the Streamlit host, never
to the server's data_dir. So the endpoint always returned an empty list
and the Load tab showed 'No indexes found.'

The correct sentinel is index.json, written by _write_meta() on every
POST /v1/collection, POST /v1/collection/open, and lazy upsert. This
file exists in data_dir/<name>/ on the server from the moment a
collection is created.

Updated the test fixture to write index.json instead of index_config.json.gz.
device_info() queried the remote host via nvidia-smi over SSH to surface
GPU load in the FORag Streamlit UI. That GPU panel has been removed from
the UI; device_info() has no remaining callers.

Remove the method and its three unit tests from test_embedding_server.py.
n_gpus and _resolve_n_gpus (used for vLLM tensor-parallel sizing) are
unchanged.
Add server-side persistence of ColPali index bookkeeping (model name,
doc metadata, file-name map, per-embedding extras) so that in remote
mode the client no longer needs a local index directory.

- base.VectorStore: add export_bookkeeping/load_bookkeeping (no-op
  default) + supports_remote_bookkeeping().
- vector_db_server.server: PUT/GET /v1/collection/{name}/bookkeeping,
  storing the blob under <data_dir>/<name>/bookkeeping.pt.
- vector_db_server.client: put_bookkeeping/get_bookkeeping.
- vector_store.remote: implement bookkeeping methods forwarding to the
  client.
- colpali: in remote mode, route _export_index/_load_index_state through
  the vector store instead of local sidecar files; from_index bootstraps
  the model name and state from the server (connection config supplied by
  the caller, never persisted).
- retriever.from_index: accept storage_backend to force remote loading.

Local/qdrant/milvus backends are unchanged and keep their local sidecar
files. Tests added for the new endpoints, client methods and remote
store; remote-backend dispatch tests updated for the new behaviour.
In remote mode the local index directory is irrelevant (vectors and
bookkeeping live on the server). The existing index() guard treated a
stale local index/<name> directory as 'already exists' and aborted
indexing, leaving the vector store unopened and causing search() to
fail. Skip that guard when remote bookkeeping is in use.
- index(), _process_directory(), update_index_from_folder() now use
  rglob('*') instead of iterdir(), so all files in subdirectories are
  indexed automatically
- replace print() in _process_directory with logger.debug + tqdm progress
- remove 9 unused imports found by ruff (F401) across 6 files
- fix 2 empty f-strings (F541)
- fix _FakeColPaliModel in test_colpali_on_progress: add missing
  storage_backend and storage_config attributes
- add test_recursive_indexing.py: 6 unit tests (no GPU) covering
  index(), _process_directory(), update_index_from_folder() recursive
  traversal and overwrite=False with no existing index
- remove test_colpali.py and test_colqwen.py: load-only tests with no
  functional assertions, superseded by test_e2e_rag.py
- update README: note recursive indexing behavior
test_init_lazy_import.py purges all foretrieval.* entries from
sys.modules to verify lazy-import semantics. Without cleanup this
permanently splits module identity for the rest of the pytest session:
tests that ran later would hold OLD class objects (bound at collection
time) while sys.modules contained NEW re-imported versions, causing
isinstance() checks, patch() targets, and module-global reads to all
fail silently.

Add an autouse function-scoped fixture that snapshots sys.modules
before each test and fully restores it (including overwriting any
re-imported variants) in teardown. This eliminates 10 order-dependent
failures in test_qdrant.py, test_vector_store_qdrant.py,
test_vector_store_milvus.py, test_vector_db_server_app.py, and
test_vector_store_factory.py. Fast suite: 362 passed, 0 failed.
…olormap

Bug 1 (models_metadata.py): build_metadata_list_for_dir was non-recursive
(iterdir + None placeholders for dirs) while index() enumerates files
recursively via rglob. Rewritten to match index() exactly: rglob sorted
by relative path, one DocMetadata per file, no dir placeholders.

Bug 2 (colpali.py): add _cleanup_failed_index helper that removes the
partial local index directory (shutil.rmtree) and best-effort deletes
the remote collection on failure. index() now wraps its body in
try/except and calls this helper on any exception, ensuring a clean
retry is always possible.

Bug 3 (colpali.py): _load_index_state and _apply_bookkeeping_blob derived
doc_ids / highest_doc_id from doc_id_to_metadata.keys(). When an index was
built without add_metadata those keys are empty, leaving doc_ids=set() and
highest_doc_id=-1 despite documents being present. Fixed to derive from
doc_ids_to_file_names (always written), falling back to metadata keys only
for legacy indexes that lack a filenames sidecar.

Bug 4 (plot_utils.py): replace deprecated matplotlib.cm.get_cmap (removed
in matplotlib >= 3.9) with matplotlib.colormaps[cmap].

Tests: update test_metadata_no_ai.py for new recursive semantics; add
test_task10_bugfixes.py covering Bugs 2, 3, and 4.
…ata load, colormap, filename display (v2026.6.2)
…e argmax

draw_circle_on_max_patch now accepts patch_grow_pct/grow_mode params and
applies grow_heatmap_patches_torch before computing the argmax patch, so the
circle center lands on the same visual peak as the heatmap overlay.

Colpali call site updated to pass patch_grow_pct=300.0, grow_mode='mean'
matching the heatmap call (same values, same function order).
grow_heatmap_patches_torch moved before draw_circle_on_max_patch to fix
forward-reference (no-op at runtime, required for clarity).

Tests: 8 new tests in tests/test_task11_features.py (all pass).
@NLP-traveller

Copy link
Copy Markdown
Author

Just a mistake. Sorry

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants