Skip to content

perf(pyxlog): release GIL during epistemic evaluation - #253

Merged
levi770 merged 1 commit into
mainfrom
agent/pyxlog-epistemic-gil
Aug 17, 2026
Merged

perf(pyxlog): release GIL during epistemic evaluation#253
levi770 merged 1 commit into
mainfrom
agent/pyxlog-epistemic-gil

Conversation

@levi770

@levi770 levi770 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Release Python's global interpreter lock while the two epistemic APIs execute their native logic and chained GPU pipeline.

Root cause

Both epistemic_evidence and evaluate_conditioned entered long-running Rust/CUDA work while holding the Python GIL. That serialized unrelated Python threads for the full evaluation, including conditioned compilation, device allocation/copies, and DLPack preparation.

Result

  • clone all owned native inputs before detaching from Python
  • run the complete native and GPU pipeline inside Python::detach
  • reacquire Python only to construct dictionaries and DLPack capsules
  • add deterministic, synchronized concurrency regressions for both public methods
  • keep the regression importable on Python 3.8 by postponing annotation evaluation
  • restrict the observer helper to ordinary Exception failures instead of swallowing process-control BaseException values

The Python-visible signatures and return schemas are unchanged.

Verification

Base: 33dfea9d197035f6c0ac01c7ab4ff15a71665adf
Head: f9022971515958abd7cec2c5d06032fdb2a87295

  • pre-fix installed wheel: 2 concurrency tests failed, 0 skipped; observer threads ran only after native return
  • fixed installed wheel: 2 passed, 0 skipped
  • combined epistemic API/GPU/concurrency selection: 12 passed, 0 skipped
  • python3.8 -m py_compile python/tests/test_pyxlog_epistemic_concurrency.py
  • ruff check python/tests/test_pyxlog_epistemic_concurrency.py
  • cargo check -p pyxlog --all-features (compile-only proof that detached closures satisfy PyO3's Ungil bounds)
  • cargo test -p pyxlog with default features: 1 passed, 0 ignored
  • cargo check -p pyxlog --no-default-features
  • cargo clippy -p pyxlog --all-features --no-deps -- -D warnings
  • cargo fmt --all -- --check
  • git diff --check

The broad make check replay was not accepted as evidence: one attempt exhausted the filesystem while compiling CUDA, and strict workspace Clippy is independently blocked on pre-existing warnings in xlog-cuda/src/provider/ilp_exact_nary.rs.

Risk

Low API risk: signatures and result construction are unchanged. The principal risk is exposing latent synchronization assumptions now that unrelated Python threads can run concurrently; the tests exercise both real CUDA entry points with barriers/events and validate returned results.

The per-call owned LogicProgram clone is removed by the stacked conditioned-circuit follow-up, which stores the compiled program in an Arc and makes detached captures constant-time.

Fixes #212

Comment thread python/tests/test_pyxlog_epistemic_concurrency.py Fixed
@levi770
levi770 marked this pull request as ready for review August 13, 2026 12:27
Copilot AI lite review requested due to automatic review settings August 13, 2026 12:27

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the pyxlog Python bindings to release the CPython GIL while running long-running Rust/CUDA epistemic evaluation work, improving Python-side concurrency without changing public Python signatures or return schemas.

Changes:

  • Run evaluate_conditioned’s full native + GPU pipeline inside Python::detach, reacquiring the GIL only to build Python objects/DLPack capsules.
  • Update epistemic_evidence to also execute the native work under Python::detach.
  • Add GPU concurrency regression tests to ensure observer threads can progress during both public epistemic calls.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
crates/pyxlog/src/epistemic.rs Clones inputs and detaches from the GIL while running epistemic + conditioned GPU pipelines; repacks results after reacquiring Python.
python/tests/test_pyxlog_epistemic_concurrency.py Adds deterministic concurrency tests validating the GIL is released during long GPU-backed epistemic calls.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread python/tests/test_pyxlog_epistemic_concurrency.py

@levi770 levi770 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep review — PR #253 @ 9995a8c16d9ed1b9aba31da7f1db664a0e92d883 (fixes #212)

Verdict: approve. 0 HIGH / 0 MEDIUM / 2 LOW / 3 NOTEs. Issue #212's specific asks — including the one the previous reverted attempt failed on — are all satisfied, and the live legs reproduce on a pinned head wheel (12/12, 0 skipped).

The Sync question from #212 — answered the right way

The issue recorded why the previous fix was reverted: the Sync argument was hand-derived against pyo3 0.24, and it needed a compiler check against 0.29. This PR delivers exactly that:

  • Both closures capture only owned, pre-cloned values — no &self, no Py<…> types, no Python token (verified line-by-line). Python::detach bounds closure and return by Ungil (= Send), so CudaKernelProvider: Send + Sync — the fact #212 said was only hand-derived — is now compiler-checked; cargo check -p pyxlog --all-features (exit 0, reproduced) is that proof.
  • Honest caveat (NOTE): the proof composes over pre-existing unsafe impl Send/Sync leaves in xlog-cuda (dlpack.rs:161, device.rs:25, cuda_graph.rs, arrow_device.rs). Those leaves were already load-bearing at the merge-base for the 5 existing detach sites in neural.rs/ilp.rs — this PR doesn't widen the trusted base, it adds traffic. Your "principal risk" sentence names this honestly.
  • The scope split is exactly right: only enforce_call_memory_limit runs before detach; only PyDict/DLPack-capsule construction runs after, on the GIL. Return schemas byte-identical; the argument-free epistemic_evidence signature guard passes live.

Live verification (head debug wheel, probe venv, pyxlog.__file__ pinned, native sha 4c062920…9408, GPU-serialized, XLOG_REQUIRE_CUDA=1)

Claim Observed
fixed wheel: 2 concurrency tests pass 2/2 passed
combined selection: 12 passed, 0 skipped exactly 12 passed, 0 skipped, 4.05s
checks/clippy-strict/fmt/diff-check all reproduced
make check declined (fs exhaustion + pre-existing xlog-cuda clippy) both reasons reproduced first-hand (ENOSPC hit this review too; the 4 warnings are at ilp_exact_nary.rs:102/263/266/310, pre-existing)

The red leg ("pre-fix wheel: 2 failed") was verified structurally rather than live (a merge-base wheel needs a second full CUDA tree; disk was at 99%): the merge-base has zero GIL-release sites, GIL-drop requests are serviced only at bytecode boundaries, and sys.setswitchinterval(60.0) closes the pre-call window — so the observer necessarily sees the return first and the base assertion necessarily fails. The tests themselves are deterministic by construction (Barrier/Event with timeouts, error channel, no sleeps) — this is the strongest concurrency-test design in the python suite.

Findings

  1. LOW (inline)self.program.clone() at the two detach sites is a full deep clone of LogicProgram (two complete host ASTs — your own test program carries 4096 fact clauses — plus the boxed executable plan and schema maps), paid per call to give the closure an owned Send value. Dominated by the GPU pipeline, so not a perf regression worth blocking on — but Arc<LogicProgram> would make the capture free, and #257 adds a third and fourth clone site of the same pattern, so it's worth doing when that lands.
  2. LOW (process) — the verification bullet cargo test -p pyxlog --all-features executes zero tests (the extension-module feature cfg-empties the only integration target; [lib] test = false). True-but-vacuous evidence; the non-vacuous form is default-features (1 passed, reproduced). The meaningful evidence for this PR is the wheel pytest legs, which are real — just use the default-features form in future verification lists.
  3. NOTE — closed predecessor #246 (same branch, closed-not-merged) leaves no misleading residue; #253 carries the Fixes #212 linkage. Stacking: #257 modifies this file additively only (new methods + trace keys), so the 253→257 review/merge order is clean.

Process

Single conventional perf(pyxlog) commit — patch bump, honest for a throughput-only change; no AI trailers; no version/CHANGELOG edits.


Methodology: pinned worktree; line-by-line closure-capture audit; compiler-check reproduction on 0.29.2; head wheel built in an isolated probe venv with import provenance pinned (stale user-site pyxlog exists on this host and was guarded against); GPU runs serialized; red leg verified structurally with the reasoning stated.

Comment thread crates/pyxlog/src/epistemic.rs
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.

perf(pyxlog): epistemic methods hold the GIL across chained GPU pipelines

2 participants