fix(llm): gate ANN index build behind [llm].build_ann_index (avx-512 SIGILL) (refs #565 #567)

This commit is contained in:
kert
2026-07-17 14:29:00 -04:00
parent 195af7e0b5
commit b2c129fd06
5 changed files with 62 additions and 12 deletions

View File

@@ -1,9 +1,10 @@
"""[llm] configuration — stack.toml section + env overrides.
Env contract:
LLM_OLLAMA_HOSTS comma-separated Ollama base URLs (beats [llm].ollama)
LLM_PG_HOST beats [llm].pg_host (containers set this to "postgres")
LLM_DB_PASSWORD required for pg_url(); lives in .env, never stack.toml
LLM_OLLAMA_HOSTS comma-separated Ollama base URLs (beats [llm].ollama)
LLM_PG_HOST beats [llm].pg_host (containers set this to "postgres")
LLM_DB_PASSWORD required for pg_url(); lives in .env, never stack.toml
LLM_BUILD_ANN_INDEX beats [llm].build_ann_index ("1"/"true"/"yes" = True)
"""
from __future__ import annotations
@@ -18,6 +19,7 @@ class LlmConfig:
embed_model: str
instruct_model: str
embed_dim: int
build_ann_index: bool
pg_host: str
pg_port: int
pg_db: str
@@ -34,11 +36,17 @@ def load() -> LlmConfig:
hosts = tuple(h.strip() for h in hosts_env.split(",") if h.strip())
else:
hosts = (str(section.ollama),)
ann_env = os.environ.get("LLM_BUILD_ANN_INDEX")
if ann_env is not None:
build_ann_index = ann_env.strip().lower() in ("1", "true", "yes")
else:
build_ann_index = bool(section.build_ann_index)
return LlmConfig(
ollama_hosts=hosts,
embed_model=str(section.embed_model),
instruct_model=str(section.instruct_model),
embed_dim=int(section.embed_dim),
build_ann_index=build_ann_index,
pg_host=os.environ.get("LLM_PG_HOST", str(section.pg_host)),
pg_port=int(section.pg_port),
pg_db=str(section.pg_db),

View File

@@ -134,5 +134,8 @@ def index_docs(
if stats["indexed"] % 100 == 0:
log.info("indexed %(indexed)s (+%(chunks)s chunks)", stats)
ensure_hnsw(engine, cfg.embed_dim)
if cfg.build_ann_index:
ensure_hnsw(engine, cfg.embed_dim)
else:
log.info("ANN index skipped (build_ann_index=false); using exact search")
return stats

View File

@@ -106,6 +106,10 @@ ollama = "http://127.0.0.1:11434" # host-side default; containers override v
embed_model = "nomic-embed-text" # 768-dim; bake-off (P33 #564) may revise
instruct_model = "llama3.1:8b" # bake-off may revise
embed_dim = 768
# ANN index build (hnsw/ivfflat) SIGILLs on this Zen2 host — the mirrored
# postgres image's pgvector is AVX-512. Leave false until the image is
# rebuilt; exact search works regardless.
build_ann_index = false
pg_host = "127.0.0.1" # host-side default; containers set LLM_PG_HOST=postgres
pg_port = 5432
pg_db = "llm"

View File

@@ -9,12 +9,14 @@ class TestLoad:
def test_defaults_from_stack_toml(self, monkeypatch):
monkeypatch.delenv("LLM_OLLAMA_HOSTS", raising=False)
monkeypatch.delenv("LLM_PG_HOST", raising=False)
monkeypatch.delenv("LLM_BUILD_ANN_INDEX", raising=False)
cfg = llm_config.load()
assert cfg.ollama_hosts == ("http://127.0.0.1:11434",)
assert cfg.embed_model == "nomic-embed-text"
assert cfg.embed_dim == 768
assert cfg.pg_host == "127.0.0.1"
assert cfg.pg_db == "llm"
assert cfg.build_ann_index is False
def test_env_overrides(self, monkeypatch):
monkeypatch.setenv(
@@ -26,6 +28,16 @@ class TestLoad:
assert cfg.ollama_hosts == ("http://rig:11434", "http://laptop:11434")
assert cfg.pg_host == "postgres"
def test_build_ann_index_env_override_true(self, monkeypatch):
monkeypatch.setenv("LLM_BUILD_ANN_INDEX", "1")
cfg = llm_config.load()
assert cfg.build_ann_index is True
def test_build_ann_index_env_override_false(self, monkeypatch):
monkeypatch.setenv("LLM_BUILD_ANN_INDEX", "no")
cfg = llm_config.load()
assert cfg.build_ann_index is False
class TestPgUrl:
def test_url_includes_password_from_env(self, monkeypatch):

View File

@@ -11,6 +11,7 @@ CFG = LlmConfig(
embed_model="m",
instruct_model="g",
embed_dim=768,
build_ann_index=False,
pg_host="x",
pg_port=5432,
pg_db="llm",
@@ -20,7 +21,7 @@ CFG = LlmConfig(
DOC = Doc(key="K1", text="Some body text.", metadata={"docket": "D"})
def _run(docs, state_rows, force=False):
def _run(docs, state_rows, force=False, cfg=CFG):
"""Run index_docs with everything external mocked; return mocks."""
store = MagicMock()
engine = MagicMock()
@@ -30,22 +31,23 @@ def _run(docs, state_rows, force=False):
patch("llm.index._engine", return_value=engine),
patch("llm.index.vectorstore", return_value=store),
patch("llm.index.embed_texts", return_value=[[0.0] * 3]),
patch("llm.index.ensure_hnsw") as mock_ensure_hnsw,
patch("llm.index.HostPool") as MockPool,
):
MockPool.return_value.check.return_value = ["http://h1:11434"]
stats = index_docs(
docs,
collection="comments",
cfg=CFG,
cfg=cfg,
pool=MockPool.return_value,
force=force,
)
return stats, store, conn
return stats, store, conn, mock_ensure_hnsw
class TestIndexDocs:
def test_new_doc_embedded_and_recorded(self):
stats, store, conn = _run([DOC], state_rows=[])
stats, store, conn, _ensure_hnsw = _run([DOC], state_rows=[])
assert stats == {"indexed": 1, "skipped": 0, "chunks": 1}
store.add_embeddings.assert_called_once()
kwargs = store.add_embeddings.call_args.kwargs
@@ -53,12 +55,12 @@ class TestIndexDocs:
def test_unchanged_doc_skipped(self):
h = content_hash(DOC.text)
stats, store, _ = _run([DOC], state_rows=[("K1", h)])
stats, store, _, _ensure_hnsw = _run([DOC], state_rows=[("K1", h)])
assert stats["skipped"] == 1
store.add_embeddings.assert_not_called()
def test_changed_doc_deletes_old_chunks_first(self):
stats, store, conn = _run([DOC], state_rows=[("K1", "stalehash")])
stats, store, conn, _ensure_hnsw = _run([DOC], state_rows=[("K1", "stalehash")])
assert stats["indexed"] == 1
deletes = [
c
@@ -77,10 +79,31 @@ class TestIndexDocs:
def test_force_reembeds_unchanged(self):
h = content_hash(DOC.text)
stats, store, _ = _run([DOC], state_rows=[("K1", h)], force=True)
stats, store, _, _ensure_hnsw = _run([DOC], state_rows=[("K1", h)], force=True)
assert stats["indexed"] == 1
def test_empty_doc_counts_skipped(self):
empty = Doc(key="K2", text=" ", metadata={})
stats, store, _ = _run([empty], state_rows=[])
stats, store, _, _ensure_hnsw = _run([empty], state_rows=[])
assert stats == {"indexed": 0, "skipped": 1, "chunks": 0}
class TestAnnIndexGating:
def test_build_ann_index_false_skips_ensure_hnsw(self):
_stats, _store, _conn, mock_ensure_hnsw = _run([DOC], state_rows=[], cfg=CFG)
mock_ensure_hnsw.assert_not_called()
def test_build_ann_index_true_calls_ensure_hnsw(self):
cfg = LlmConfig(
ollama_hosts=("http://h1:11434",),
embed_model="m",
instruct_model="g",
embed_dim=768,
build_ann_index=True,
pg_host="x",
pg_port=5432,
pg_db="llm",
pg_user="llm",
)
_stats, _store, _conn, mock_ensure_hnsw = _run([DOC], state_rows=[], cfg=cfg)
assert mock_ensure_hnsw.called