Files
e49c282879 perf: KB caching, compiled index, lazy loading, parallel resolution (#38) (#142)
* perf: KB load-once cache, compiled index, lazy loading, parallel resolution (#38)

Closes #130, #131, #132, #133, #134.

- Loader node routes through a process-wide KnowledgeBaseLoader cache so the KB
  loads once per (path, version) and is reused across run() calls.
- Normalized lookup table compiled once per KB (per NSFW gating) and reused across
  resolves instead of rebuilt per resolve_scene. normalize_concept moved to
  knowledge_base (re-exported from the resolver).
- Incremental per-category lazy file loading behind KnowledgeBaseLoader.
- Optional parallel per-concept resolution (thread pool), merged in exact
  discovery order; deterministic and threshold-gated (default sequential).
- Performance regression guard (generous ceilings + relative ratio). Refreshed
  docs/benchmarks.md (resolution 57.6us -> 17.8us).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(changelog): note V2 performance improvements (#38)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 18:51:42 +02:00

82 lines
2.5 KiB
Python

"""Parallel resolution determinism (issue #133, epic #38)."""
from __future__ import annotations
from compiler.common.config import Config
from compiler.resolver.illustrious_resolver import resolve_scene
from schemas.models import Scene
from tests.regression.golden_scenes import load_reference_knowledge_base
# A mix of exact hits, alias hits, head-noun reductions, and unknowns.
_BASE = [
"long hair",
"blue eyes",
"thighhighs",
"jacket",
"open white shirt",
"white summer dress",
"smile",
"sitting",
"classroom",
"sunset",
"quantum flux inverter",
]
def _big_scene(n: int) -> Scene:
concepts = [_BASE[i % len(_BASE)] for i in range(n - 5)] + [
f"unique_unknown_{i}" for i in range(5)
]
return Scene.from_json(
{
"characters": [
{
"id": 0,
"identity": concepts,
"appearance": [],
"clothing": [],
"accessories": [],
"pose": [],
"expression": [],
"actions": [],
}
],
"interactions": [],
"objects": [],
"environment": [],
"camera": [],
"lighting": [],
"metadata": {"schema_version": "1.0"},
}
)
def _resolve(scene, threshold):
return resolve_scene(
scene, load_reference_knowledge_base(), Config(), parallel_threshold=threshold
)
def test_parallel_matches_sequential_exactly() -> None:
scene = _big_scene(80) # above the default threshold
sequential = _resolve(scene, 0) # forced sequential
parallel = _resolve(scene, 1) # forced parallel
assert [t.tag for t in parallel.data] == [t.tag for t in sequential.data]
assert [m.code for m in parallel.warnings] == [m.code for m in sequential.warnings]
def test_parallel_is_deterministic_across_runs() -> None:
scene = _big_scene(90)
first = [t.tag for t in _resolve(scene, 1).data]
second = [t.tag for t in _resolve(scene, 1).data]
assert first == second
def test_many_scene_sizes_match_sequential() -> None:
kb = load_reference_knowledge_base()
for n in (1, 10, 63, 64, 65, 128):
scene = _big_scene(n)
seq = resolve_scene(scene, kb, Config(), parallel_threshold=0)
par = resolve_scene(scene, kb, Config(), parallel_threshold=1)
assert [t.tag for t in par.data] == [t.tag for t in seq.data], n