Skip to content

feat: experimental step plasticity + GPU step options - #45

Merged
rmems merged 13 commits into
mainfrom
feature/experimental-step-plasticity-gpu
Aug 1, 2026
Merged

feat: experimental step plasticity + GPU step options#45
rmems merged 13 commits into
mainfrom
feature/experimental-step-plasticity-gpu

Conversation

@rmems

@rmems rmems commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Summary

  • Experimental step! / ensemble_step! kwargs: plasticity, recurrent_eta, sync, record_history, use_device_noise
  • Default :readout_only preserves frozen recurrent W + Hebbian readout
  • Opt-in :recurrent_stdp (pair STDP every tick on sparse edges; lazy edge lists; CUDA.jl only)
  • Ensemble forwards kwargs; single sync after lobes when sync=true
  • GPU tests written TDD-first; exploration re-implemented from tests
  • CUDA.jl 6.x (free_memory compat for Ensemble VRAM report)
  • Local verification: Julia 1.12 only (CI matrix unchanged)

Tracking

Test plan

  • julia --version → 1.12.x; Pkg.test()71/71 on RTX 5080 (Blackwell)
  • New GPU testsets for plasticity modes + kwargs
  • No playground logs, .explore-notes/, or third-party STDP deps in PR

Out of scope

  • myelin / plasticity-lab integration
  • Memory-capacity harness as package test
  • Changing CI Julia version matrix

Summary by cubic

Adds experimental plasticity to step!/ensemble_step! with GPU controls for sync, history, and device noise, plus pair STDP on sparse edges. Hardens validation, Sentry reporting, tests, and CI (Julia 1.12-only, self-hosted GPU) for reliability; aligns with Linear epic LIM-898.

  • Bug Fixes

    • Raise LiquidCortexValidationError for API misuse; ensemble validates plasticity/recurrent_eta before STDP prewarm via a CPU-safe validator.
    • STDP edge lists check readiness of both buffers and enforce strict CSC invariants.
    • Sentry: skip capture for LiquidCortexValidationError; tag GPU failures and serialize tag+capture per event; device RNG path rethrows CUDA.OutOfGPUMemoryError/CUDA.CuError and falls back to host only on non-GPU RNG failures.
    • Diagnostics run after aggregation; host spike reductions only when sync=true. GPU tests hard-reset (CUDA.device_reset!) between ensembles to stop VRAM pool growth and fold the :none freeze into the ensemble inhibition test.
  • Dependencies

    • Require CUDA.jl 6; use CUDA.free_memory for VRAM reporting.
    • CI: Julia 1.12-only across ci.yml and codecov.yml; self-hosted gpu-ci.yml with per-ref concurrency, fork PR isolation, runner Julia 1.12 assertion, and project-env CUDA.jl verification after Pkg.instantiate.

Written for commit 42d2d4d. Summary will update on new commits.

Review in cubic

rmems added 5 commits July 31, 2026 01:21
CUDA.jl 6 removed available_memory; EnsembleBrain VRAM report
now uses free_memory. Compat CUDA = "6".
Add plasticity modes (:readout_only, :none, :recurrent_stdp),
sync/record_history/use_device_noise, lazy STDP edge lists,
ensemble kwargs forwarding, and GPU TDD coverage.
codescene-access[bot]

This comment was marked as outdated.

@linear-code

linear-code Bot commented Jul 31, 2026

Copy link
Copy Markdown

LIM-898

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Summary

This PR introduces experimental plasticity features and performance optimizations for the GPU step functions. The implementation is comprehensive with excellent test coverage (71/71 tests passing).

Key Changes

  • ✅ Experimental pair STDP on sparse recurrent weights (:recurrent_stdp mode)
  • ✅ Lazy edge list materialization (saves ~340 MB/lobe)
  • ✅ Work buffer reuse to eliminate per-tick allocations
  • ✅ Optional kwargs: plasticity, sync, record_history, use_device_noise
  • ✅ CUDA.jl 6.x compatibility (free_memory API)
  • ✅ Comprehensive GPU test coverage for all plasticity modes

Review Findings

1 issue requiring attention:

  • Critical: Missing bounds validation in CSC edge list parsing could cause crashes with malformed sparse matrices

Recommendation

Address the bounds checking issue before merge. The experimental features are well-designed and properly gated behind explicit kwargs with safe defaults.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Comment thread src/sparse_brain.jl
@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 6.29921% with 119 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/sparse_brain.jl 4.58% 104 Missing ⚠️
src/LiquidCortex.jl 16.66% 15 Missing ⚠️

📢 Thoughts on this report? Let us know!

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0adcd17972

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/sparse_brain.jl
Comment thread test/runtests.jl Outdated
Comment thread src/sparse_brain.jl
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added experimental plasticity options, including recurrent pair-STDP and configurable learning rates.
    • Added controls for synchronization, history recording, and device-generated noise during simulation steps.
    • Extended ensemble stepping to support the same options.
  • Documentation

    • Expanded API guidance with experimental options and configuration details.
    • Documented CUDA.jl 6 compatibility and Julia 1.12 requirements.
  • Compatibility

    • Updated CUDA compatibility requirements to version 6.
  • Bug Fixes

    • Improved validation and error reporting while excluding expected input errors from production diagnostics.

Walkthrough

Added experimental plasticity and execution controls to sparse and ensemble stepping. Added recurrent pair-STDP, reusable GPU buffers, CUDA.jl 6 compatibility, exception filtering, documentation, repository metadata updates, and GPU test coverage.

Changes

Experimental stepping API

Layer / File(s) Summary
API and toolchain contracts
Project.toml, AGENTS.md, README.md, .github/workflows/*
Documented stepping keywords and compatibility requirements. CI and coverage jobs now use Julia 1.12.
Sparse plasticity state and kernel
src/sparse_brain.jl
Added lazy sparse edge indexing, reusable GPU buffers, and recurrent pair-STDP updates.
Stepping controls and ensemble flow
src/sparse_brain.jl
Added plasticity, synchronization, history, noise, and recurrent learning-rate controls.
Stepping validation and GPU cleanup
src/LiquidCortex.jl, test/runtests.jl
Added validation-error handling, GPU failure classification, cleanup, and coverage for the new stepping modes.

Repository support

Layer / File(s) Summary
Repository metadata and GPU CI
.gitignore, .env.example, .github/workflows/gpu-ci.yml, AGENTS.md
Added local artifact ignores, Sentry DSN guidance, GPU CI execution, and related workflow guidance.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant ensemble_step!
  participant SparseBrain
  participant CUDA
  Caller->>ensemble_step!: pass plasticity and execution controls
  ensemble_step!->>SparseBrain: step each lobe with sync=false
  SparseBrain->>ensemble_step!: return each lobe readout
  ensemble_step!->>CUDA: synchronize once after aggregation when sync=true
  ensemble_step!->>Caller: return aggregated readout
Loading

Possibly related issues

Possibly related PRs

Suggested labels: documentation

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main changes: experimental step plasticity and GPU step options.
Description check ✅ Passed The description directly covers the experimental plasticity, GPU controls, tests, documentation, and CI changes in the pull request.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feature/experimental-step-plasticity-gpu

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added the documentation Improvements or additions to documentation label Jul 31, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/sparse_brain.jl (1)

637-662: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Stale docstring for EnsembleBrain step! wrapper.

The docstring signature at Lines 637-638 and its body only describe inhibition, reflex_eta, and reflex_signal. The actual function signature (Lines 643-651) also accepts and forwards plasticity, recurrent_eta, sync, record_history, and use_device_noise to ensemble_step!. The docstring for ensemble_step! itself (Lines 571-579) does describe these forwarded kwargs, but this wrapper's docstring was not updated to match.

📝 Proposed docstring fix
 """
-    step!(eb::EnsembleBrain, u; inhibition=0.0, reflex_eta=ETA, reflex_signal=0.0)
+    step!(eb::EnsembleBrain, u; inhibition=0.0, reflex_eta=ETA, reflex_signal=0.0, plasticity=:readout_only, ...)
 
 Forwards to [`ensemble_step!`](`@ref`).
 Keyword `reflex_signal` (default `0`) controls fast-lobe reflex gating.
+Also forwards `plasticity`, `recurrent_eta`, `sync`, `record_history`, and `use_device_noise`.
 """

As per coding guidelines, "Keep docstrings aligned with the actual function signatures."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sparse_brain.jl` around lines 637 - 662, Update the
`step!(eb::EnsembleBrain, u::CuVector{Float32})` docstring to document all
supported keyword arguments, including `plasticity`, `recurrent_eta`, `sync`,
`record_history`, and `use_device_noise`, while retaining the existing
descriptions for the reflex-related options.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/sparse_brain.jl`:
- Around line 291-298: Validate the plasticity argument at the start of
_step_impl! against the supported symbols (:readout_only, :recurrent_stdp, and
:none), throwing an argument error for any other value before executing update
logic. Keep ensemble_step!, _ensemble_step_impl!, and the EnsembleBrain step!
wrapper forwarding the validated option without duplicating validation.
- Around line 317-321: Update the noise-generation branch in step! to preserve a
portable host-generated fallback: do not unconditionally use Random.randn! when
use_device_noise is enabled. Keep cpu_randn_cu (or the existing host
randn/copyto! path) as the default, and only use device RNG when a compatibility
check confirms it is available on the supported Julia/CUDA stack.

In `@test/runtests.jl`:
- Around line 235-248: Replace the tautological W_out assertion in the “GPU:
plasticity=:readout_only can update W_out” test with a direct check that
Array(brain.W_out) differs from W0. Keep the existing tick_count and output
finiteness assertions unchanged.

---

Outside diff comments:
In `@src/sparse_brain.jl`:
- Around line 637-662: Update the `step!(eb::EnsembleBrain,
u::CuVector{Float32})` docstring to document all supported keyword arguments,
including `plasticity`, `recurrent_eta`, `sync`, `record_history`, and
`use_device_noise`, while retaining the existing descriptions for the
reflex-related options.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f86ab190-3aa5-4246-957f-bc4622f736d8

📥 Commits

Reviewing files that changed from the base of the PR and between 4e2698c and 0adcd17.

📒 Files selected for processing (6)
  • .gitignore
  • AGENTS.md
  • Project.toml
  • README.md
  • src/sparse_brain.jl
  • test/runtests.jl

Comment thread src/sparse_brain.jl
Comment thread src/sparse_brain.jl
Comment thread test/runtests.jl

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 6 files

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread src/sparse_brain.jl
Comment thread src/sparse_brain.jl
Comment thread test/runtests.jl Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
- Validate plasticity modes; reject typos with ArgumentError
- CSC edge-list bounds checks
- Skip host spike sum when sync=false; ensemble diags after loop
- Device noise falls back to host on failure
- Strong W_out Hebbian assertion; README kwargs table clarity
codescene-access[bot]

This comment was marked as outdated.

rmems commented Jul 31, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Follow-up push 3d68752 addresses your CHANGES_REQUESTED items:

  • plasticity mode validation
  • device noise host fallback
  • strong Hebbian W_out assertion
  • EnsembleBrain step! docstring
  • plus CSC bounds, deferred host spike diags for sync=false, README clarity

Local GPU suite: 72/72 on Julia 1.12 + CUDA.jl 6.2.1.

Note: codecov/patch will stay red for GPU-only lines while CI has no CUDA device (coverage job is CPU-only).

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 3 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread src/sparse_brain.jl
Comment thread src/sparse_brain.jl
Comment thread src/sparse_brain.jl Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3d68752fe2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/sparse_brain.jl
Comment thread src/sparse_brain.jl Outdated
Comment thread src/sparse_brain.jl
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 31, 2026
Do not report ArgumentError/DimensionMismatch from step! validation
to Sentry (avoids noise from unit tests and caller misuse).
codescene-access[bot]

This comment was marked as outdated.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

@rmems I will review the follow-up changes in commit 3d68752. I will also check the GPU-only paths that the CPU-only coverage job cannot execute.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 31, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3a75df8ab3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/LiquidCortex.jl Outdated
Document project-id check so LiquidCortex events do not misroute
to the rust Sentry project.

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread src/LiquidCortex.jl Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
src/sparse_brain.jl (3)

76-90: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Kernel/function parameter nnz shadows the imported SparseArrays.nnz.

_pair_stdp_kernel!'s nnz::Int32 parameter (Line 78) and the local nnz = Int32(brain.nnz) in _apply_pair_stdp! (Line 284) both shadow SparseArrays.nnz, which is imported via using SparseArrays (Line 19). This is valid Julia and does not break other call sites, but it makes the code harder to read since nnz normally refers to a function that reports nonzero counts on a sparse matrix. Rename the local/parameter (for example edge_count) to avoid the shadowing.

Also applies to: 282-293

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sparse_brain.jl` around lines 76 - 90, Rename the nnz parameter in
_pair_stdp_kernel! and the local nnz variable in _apply_pair_stdp! to edge_count
(or another clear non-shadowing name), updating all references and kernel
arguments consistently while preserving the existing edge-count behavior.

328-338: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Default noise path still allocates and transfers a host array every tick.

When use_device_noise=false (the default), randn(Float32, N) allocates a new host Vector{Float32} of 65,536 elements and copyto! transfers it over PCIe on every call to _step_impl!. This runs once per lobe per tick, four times per ensemble_step! call. The GPU-side buffer reuse (brain.noise) avoids a device allocation, but the host allocation and transfer remain a per-tick cost on the default, "portable" code path.

Consider preallocating a persistent host buffer once (per lobe) and refilling it in place with randn!(host_noise_buf) before copyto!, to eliminate the repeated host allocation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sparse_brain.jl` around lines 328 - 338, The default noise path allocates
a new host random vector on every _step_impl! call. Add a persistent per-lobe
host noise buffer during brain initialization, refill it in place with randn! in
the use_device_noise=false branch, and copy it into brain.noise; preserve the
existing device RNG path and fallback behavior.

121-122: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

trace_pre and trace_post are always numerically identical.

Both traces use the same decay constant TAU_TRACE and the same input brain.S:

brain.trace_pre .= brain.trace_pre .* (1.0f0 - DT / TAU_TRACE) .+ brain.S
brain.trace_post .= brain.trace_post .* (1.0f0 - DT / TAU_TRACE) .+ brain.S

Both arrays start at zero and receive the identical recurrence every tick, so trace_pre equals trace_post at all times. The pair-STDP kernel still produces correct results because it indexes each array by role (trace_pre[pre], trace_post[post]), but keeping two identical arrays wastes GPU memory and doubles the per-tick elementwise cost with no functional benefit. Merge them into a single trace::CuVector{Float32} field, or give trace_post a distinct time constant if asymmetric causal/anti-causal windows are the intended design.

♻️ Proposed fix (single shared trace)
-    trace_pre::CuVector{Float32}  # Pre-synaptic trace
-    trace_post::CuVector{Float32} # Post-synaptic trace
+    trace::CuVector{Float32}      # Shared spike-eligibility trace (used for both pre/post roles)
-    brain.trace_pre .= brain.trace_pre .* (1.0f0 - DT / TAU_TRACE) .+ brain.S
-    brain.trace_post .= brain.trace_post .* (1.0f0 - DT / TAU_TRACE) .+ brain.S
+    brain.trace .= brain.trace .* (1.0f0 - DT / TAU_TRACE) .+ brain.S

(Update _apply_pair_stdp!/_pair_stdp_kernel! call sites to pass brain.trace for both trace_pre and trace_post arguments, or drop the duplicate kernel argument entirely.)

Also applies to: 371-372

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sparse_brain.jl` around lines 121 - 122, Replace the duplicate trace_pre
and trace_post fields with a single trace::CuVector{Float32} in the brain state,
and update the trace update logic to compute it only once per tick. Adjust
_apply_pair_stdp! and _pair_stdp_kernel! call sites to use brain.trace for both
pre- and post-synaptic trace lookups, or remove the redundant kernel argument
while preserving role-based indexing.
test/runtests.jl (4)

165-165: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Run GPU cleanup in finally blocks.

If a constructor or CUDA operation raises before these lines, reclaim_gpu!() is skipped. The large SparseBrain and EnsembleBrain allocations can then cause later GPU testsets to fail with out-of-memory errors.

Initialize each holder to nothing, wrap the test body in try, and reclaim in finally.

Proposed pattern
-    brain = SparseBrain(...)
-    ...
-    brain = nothing; reclaim_gpu!()
+    brain = nothing
+    try
+        brain = SparseBrain(...)
+        ...
+    finally
+        brain = nothing
+        reclaim_gpu!()
+    end

The file comment identifies these cases as large GPU allocations.

Also applies to: 175-175, 184-184, 193-193, 202-202, 211-212, 220-220, 233-233, 247-247, 257-257, 267-267, 278-278, 295-295, 307-307

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/runtests.jl` at line 165, Update each affected GPU test in
test/runtests.jl to initialize its brain holder to nothing, wrap construction
and test operations in try blocks, and move reclaim_gpu!() into finally blocks
so cleanup runs even when setup or CUDA operations throw. Apply this
consistently to the tests using SparseBrain and EnsembleBrain allocations,
preserving their existing assertions and test behavior.

236-246: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that :readout_only keeps recurrent W frozen.

This test captures only W_out and output. A regression that mutates brain.W.nzVal under :readout_only would pass.

Snapshot Array(brain.W.nzVal) before the loop and assert equality after the loop.

Proposed fix
             W0 = copy(Array(brain.W_out))
+            W_recurrent0 = copy(Array(brain.W.nzVal))
...
             `@test` !all(Array(brain.W_out) .== W0)
+            `@test` Array(brain.W.nzVal) == W_recurrent0

The PR contract states that :readout_only preserves recurrent W.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/runtests.jl` around lines 236 - 246, Extend the GPU testset for
plasticity=:readout_only by snapshotting Array(brain.W.nzVal) before the update
loop and asserting it remains equal afterward. Keep the existing W_out-change,
tick-count, and finite-output assertions unchanged.

226-230: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Compare the complete W_out state.

Line 226 stores only the norm. A different matrix can have the same norm, so Line 230 can pass while plasticity=:none still mutates readout weights.

Copy Array(brain.W_out) before the loop and compare the full matrix afterward.

Proposed fix
-            w0 = norm(Array(brain.W_out))
+            w0 = copy(Array(brain.W_out))
...
-            `@test` norm(Array(brain.W_out)) ≈ w0 atol=1e-5
+            `@test` Array(brain.W_out) == w0

The :none test should prove that the complete readout matrix remains unchanged.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/runtests.jl` around lines 226 - 230, Update the plasticity=:none test
around brain.W_out to copy the complete Array(brain.W_out) before the stepping
loop, then compare the final matrix directly against that snapshot instead of
comparing norms. Preserve the existing loop and tolerance while ensuring the
full readout state is verified unchanged.

298-306: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the ensemble forwarding check observable.

The current assertions do not prove that plasticity=:none reaches each lobe. If ensemble_step! drops the keyword, tick counts and finite output can still pass.

Add an invalid-mode assertion or compare each lobe's W_out before and after the :none call.

Minimal verification
             u = CUDA.zeros(Float32, 8)
+            `@test_throws` ArgumentError ensemble_step!(eb, u; plasticity=:typo)
             ensemble_step!(eb, u; plasticity=:none, inhibition=0.1f0)

The PR objective requires ensemble_step! to forward plasticity keywords.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/runtests.jl` around lines 298 - 306, Strengthen the GPU testset around
ensemble_step! so it directly verifies plasticity=:none reaches every lobe.
Capture each lobe’s W_out before the call and assert the weights remain
unchanged afterward, or add an invalid-mode assertion that would fail if the
keyword were dropped; retain the existing tick-count and output checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/LiquidCortex.jl`:
- Around line 69-77: Update _should_capture_runtime_exception and its callers so
ArgumentError and DimensionMismatch are suppressed only when explicitly marked
as expected validation failures, not based on exception type alone. Ensure the
step! catch path preserves Sentry capture for unexpected internal shape or
invariant errors, and add coverage for both marked validation errors and
unmarked exceptions.

In `@src/sparse_brain.jl`:
- Around line 328-334: Update the device-noise fallback around Random.randn! to
avoid a bare catch: catch only the expected RNG failure type, or log the caught
exception once before falling back to host noise. Preserve the existing copyto!
fallback for supported device RNG failures while allowing unrelated exceptions
such as interrupts or out-of-memory errors to remain visible.

In `@test/runtests.jl`:
- Line 232: Move or add the invalid plasticity-mode validation test outside the
GPU-only branch so CPU-only CI exercises it unconditionally. Update the tests
around step! to validate that plasticity=:typo raises ArgumentError without
requiring CUDA; retain the existing GPU integration coverage, and if step!
itself requires CUDA, test the underlying plasticity validator separately.

---

Outside diff comments:
In `@src/sparse_brain.jl`:
- Around line 76-90: Rename the nnz parameter in _pair_stdp_kernel! and the
local nnz variable in _apply_pair_stdp! to edge_count (or another clear
non-shadowing name), updating all references and kernel arguments consistently
while preserving the existing edge-count behavior.
- Around line 328-338: The default noise path allocates a new host random vector
on every _step_impl! call. Add a persistent per-lobe host noise buffer during
brain initialization, refill it in place with randn! in the
use_device_noise=false branch, and copy it into brain.noise; preserve the
existing device RNG path and fallback behavior.
- Around line 121-122: Replace the duplicate trace_pre and trace_post fields
with a single trace::CuVector{Float32} in the brain state, and update the trace
update logic to compute it only once per tick. Adjust _apply_pair_stdp! and
_pair_stdp_kernel! call sites to use brain.trace for both pre- and post-synaptic
trace lookups, or remove the redundant kernel argument while preserving
role-based indexing.

In `@test/runtests.jl`:
- Line 165: Update each affected GPU test in test/runtests.jl to initialize its
brain holder to nothing, wrap construction and test operations in try blocks,
and move reclaim_gpu!() into finally blocks so cleanup runs even when setup or
CUDA operations throw. Apply this consistently to the tests using SparseBrain
and EnsembleBrain allocations, preserving their existing assertions and test
behavior.
- Around line 236-246: Extend the GPU testset for plasticity=:readout_only by
snapshotting Array(brain.W.nzVal) before the update loop and asserting it
remains equal afterward. Keep the existing W_out-change, tick-count, and
finite-output assertions unchanged.
- Around line 226-230: Update the plasticity=:none test around brain.W_out to
copy the complete Array(brain.W_out) before the stepping loop, then compare the
final matrix directly against that snapshot instead of comparing norms. Preserve
the existing loop and tolerance while ensuring the full readout state is
verified unchanged.
- Around line 298-306: Strengthen the GPU testset around ensemble_step! so it
directly verifies plasticity=:none reaches every lobe. Capture each lobe’s W_out
before the call and assert the weights remain unchanged afterward, or add an
invalid-mode assertion that would fail if the keyword were dropped; retain the
existing tick-count and output checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e0add7d3-a760-4fec-ac2f-41539d1e747e

📥 Commits

Reviewing files that changed from the base of the PR and between 0adcd17 and 3a75df8.

📒 Files selected for processing (4)
  • README.md
  • src/LiquidCortex.jl
  • src/sparse_brain.jl
  • test/runtests.jl

Comment thread src/LiquidCortex.jl Outdated
Comment thread src/sparse_brain.jl
Comment thread test/runtests.jl Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
AGENTS.md (1)

28-28: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Replace the unqualified “latest” claim. Project.toml documents compatibility with CUDA.jl 6.x, not release recency. Use CUDA.jl \6.x`` or name the exact tested version.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` at line 28, Update the CUDA.jl compatibility entry in AGENTS.md to
remove the unqualified “latest” claim, using “CUDA.jl 6.x” or the exact tested
version while preserving the Julia 1.12 verification note.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@AGENTS.md`:
- Line 28: Update the CUDA.jl compatibility entry in AGENTS.md to remove the
unqualified “latest” claim, using “CUDA.jl 6.x” or the exact tested version
while preserving the Julia 1.12 verification note.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5979e159-7260-4ae9-afe4-bf938b81aec0

📥 Commits

Reviewing files that changed from the base of the PR and between 3a75df8 and 0179dc9.

📒 Files selected for processing (2)
  • .env.example
  • AGENTS.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/sparse_brain.jl`:
- Around line 393-404: The docstrings for the step! function use `...` notation
instead of explicitly listing supported keyword arguments and their defaults,
making it difficult for users to discover available options. In
src/sparse_brain.jl lines 393-404 (anchor site), replace the `...` in the step!
docstring signature with explicit keyword documentation for inhibition,
reflex_eta, plasticity, recurrent_eta, sync, record_history, and
use_device_noise, each with their default values clearly stated. In
src/sparse_brain.jl lines 599-606 (sibling site), do the same for the step!
function there, listing inhibition, reflex_eta, reflex_signal, plasticity,
recurrent_eta, sync, record_history, and use_device_noise with their defaults.
Ensure both docstrings match the actual function signatures they document.
- Around line 304-314: Validate recurrent_eta for finiteness in the user-facing
function before the STDP update or any kernel launch, rejecting NaN and infinite
values with the established argument-validation error pattern. Anchor the change
near the existing plasticity and input checks, before recurrent_eta is converted
to Float32; leave valid finite values and other plasticity paths unchanged.

In `@test/runtests.jl`:
- Around line 226-230: In test/runtests.jl at lines 226-230 (anchor), replace
the L2 norm assertion with a full snapshot of Array(brain.W_out) before the
stepping loop, then assert exact equality (using ==) of that snapshot with
Array(brain.W_out) after the loop to verify weight preservation. In
test/runtests.jl at lines 298-307 (sibling), snapshot each lobe's W_out before
the multi-step loop with activity-producing (non-zero) input, then assert exact
equality of each lobe's W_out snapshot with its value after stepping to confirm
plasticity=:none prevents any weight changes across all lobes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 952328c9-02ee-4897-9bfd-b6c0aa98829d

📥 Commits

Reviewing files that changed from the base of the PR and between 4e2698c and 0179dc9.

📒 Files selected for processing (8)
  • .env.example
  • .gitignore
  • AGENTS.md
  • Project.toml
  • README.md
  • src/LiquidCortex.jl
  • src/sparse_brain.jl
  • test/runtests.jl

Comment thread src/sparse_brain.jl
Comment thread src/sparse_brain.jl Outdated
Comment thread test/runtests.jl Outdated
- STDP kernel skips clamp/write when dw==0; eta=0 early-outs
- LiquidCortexValidationError for API misuse; keep internal ArgumentError in Sentry
- Tag OutOfGPUMemoryError / CuError as gpu_failure for alerts
- CSC colPtr start-at-1 + monotonic checks; rethrow InterruptException from noise
- Validate finite recurrent_eta; prewarm STDP edges before ensemble loop
- Document reflex gating as readout-only; full kwargs in step docstrings
- CPU plasticity validation tests; exact W_out freeze for :none
- CI and Codecov matrices: Julia 1.12 only
codescene-access[bot]

This comment was marked as outdated.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1ca03f0225

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/sparse_brain.jl Outdated
Comment thread src/LiquidCortex.jl
Comment thread src/sparse_brain.jl
Comment thread src/sparse_brain.jl Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/LiquidCortex.jl (1)

98-105: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Bound Sentry capture tasks.

When Sentry’s Channel{TaskPayload}(100) is full, push! blocks. timedwait stops waiting but does not cancel t. Repeated exceptions can therefore leave unbounded blocked tasks that retain exception backtraces.

Use a bounded worker or cap in-flight captures and drop reports when saturated. Add a stress test with a full queue.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/LiquidCortex.jl` around lines 98 - 105, Bound the Sentry capture work
around Sentry.capture_exception so a full Channel{TaskPayload}(100) cannot leave
blocked tasks after timedwait returns. Add an in-flight limit or bounded worker
that drops reports when saturated, and ensure timed-out capture tasks are
cancelled or otherwise released; add a stress test exercising a full queue and
verifying capture tasks remain bounded.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Line 26: Align Julia version declarations across
.github/workflows/ci.yml:26-26 and .github/workflows/codecov.yml:25-25 with
Project.toml compatibility: if Julia 1.10 and 1.11 remain supported, add both
versions to each workflow matrix; otherwise narrow Project.toml compatibility to
Julia 1.12.

In `@src/LiquidCortex.jl`:
- Around line 81-88: Update _tag_gpu_failure! so GPU failure metadata is
attached only to the specific captured Sentry event rather than through global
Sentry.set_tag state; pass or apply event-local tags at capture time and ensure
later or queued events cannot inherit or overwrite error_class. Add a regression
test covering multiple events and verifying tags remain isolated.

In `@src/sparse_brain.jl`:
- Around line 312-325: In src/sparse_brain.jl lines 312-325, extract plasticity
and recurrent_eta checks into a GPU-independent
_validate_plasticity_kwargs!(plasticity::Symbol, recurrent_eta::Real) helper,
then call it from _validate_step_kwargs! after the input-length check; in
test/runtests.jl lines 76-95, replace the fabricated exception test with direct
calls to this helper covering an invalid plasticity mode and :recurrent_stdp
with NaN32.
- Around line 602-609: Update the prewarm condition in the recurrent STDP block
around `_ensure_edge_indices!` to require `recurrent_eta` to be finite as well
as nonzero. Keep prewarming enabled only for valid, nonzero recurrent rates, so
NaN or Inf reaches the existing validation path without materializing edge
indices.

In `@test/runtests.jl`:
- Around line 76-95: Replace the direct LiquidCortexValidationError throw in the
“CPU: plasticity mode validation” testset with a call to the u-independent
helper introduced from _validate_step_kwargs!, such as
_validate_plasticity_kwargs!, passing an invalid mode and recurrent_eta value.
Keep the `@test_throws` assertion focused on the real validator so CPU tests
exercise rejection of invalid plasticity configuration without requiring GPU
execution.

---

Outside diff comments:
In `@src/LiquidCortex.jl`:
- Around line 98-105: Bound the Sentry capture work around
Sentry.capture_exception so a full Channel{TaskPayload}(100) cannot leave
blocked tasks after timedwait returns. Add an in-flight limit or bounded worker
that drops reports when saturated, and ensure timed-out capture tasks are
cancelled or otherwise released; add a stress test exercising a full queue and
verifying capture tasks remain bounded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2b58e95d-35e1-406b-8cad-d51329c67aaa

📥 Commits

Reviewing files that changed from the base of the PR and between 0179dc9 and 1ca03f0.

📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • .github/workflows/codecov.yml
  • AGENTS.md
  • README.md
  • src/LiquidCortex.jl
  • src/sparse_brain.jl
  • test/runtests.jl

Comment thread .github/workflows/ci.yml
Comment thread src/LiquidCortex.jl Outdated
Comment thread src/sparse_brain.jl Outdated
Comment thread src/sparse_brain.jl
Comment thread test/runtests.jl

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 7 files (changes from recent commits).

Tip: instead of fixing issues one by one fix them all with cubic
Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/LiquidCortex.jl
Comment thread src/sparse_brain.jl Outdated
Comment thread .github/workflows/ci.yml
Comment thread src/LiquidCortex.jl Outdated
Comment thread src/sparse_brain.jl Outdated
Comment thread AGENTS.md
Add gpu-ci.yml targeting labels self-hosted/Linux/X64/gpu with a
repo-wide concurrency group so only one suite uses the RTX host at a time.
CPU smoke remains on ubuntu-latest.
codescene-access[bot]

This comment was marked as outdated.

Verify CUDA.jl only after instantiate with --project=.;
host preflight is julia + nvidia-smi only.
codescene-access[bot]

This comment was marked as outdated.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5b9df1c211

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/gpu-ci.yml
Comment thread .github/workflows/gpu-ci.yml Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/gpu-ci.yml:
- Around line 38-39: Update the Checkout step using actions/checkout to set
persist-credentials to false, ensuring the GITHUB_TOKEN is not retained in the
local git configuration; leave the pinned action reference unchanged.
- Around line 41-49: Update the “Verify host toolchain” step in the GPU workflow
to assert that the installed Julia version is exactly 1.12, rather than only
printing it. Make the check fail clearly when the host version differs, while
preserving the existing Julia availability, version output, and GPU validation.
- Around line 5-13: Update the workflow trigger configuration under on to remove
pull_request for the GPU job, unless the job is moved to an isolated ephemeral
runner with no secrets; preserve push and workflow_dispatch triggers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 681404e4-9a3c-4ff3-a1c4-6a5b9039510b

📥 Commits

Reviewing files that changed from the base of the PR and between 1ca03f0 and 5b9df1c.

📒 Files selected for processing (2)
  • .github/workflows/gpu-ci.yml
  • AGENTS.md

Comment thread .github/workflows/gpu-ci.yml
Comment thread .github/workflows/gpu-ci.yml
Comment thread .github/workflows/gpu-ci.yml

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread .github/workflows/gpu-ci.yml
Comment thread .github/workflows/gpu-ci.yml Outdated
Comment thread .github/workflows/gpu-ci.yml
- Harden ensemble :none test with reclaim_gpu_hard! and fewer steps
- GPU workflow: skip fork PRs, per-ref concurrency, persist-credentials false,
  assert host Julia 1.12
- Validate ensemble kwargs before STDP edge prewarm
- CPU-safe plasticity validator; dual edge-buffer readiness check
- Re-scope Sentry gpu_failure tags every capture; rethrow CUDA OOM/CuError
  from device-noise path
codescene-access[bot]

This comment was marked as outdated.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 31, 2026

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 5 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread src/LiquidCortex.jl Outdated
Soft CUDA.reclaim left ~2GB pool growth per EnsembleBrain on 16GB,
so the late :none ensemble hit 99% VRAM and failed on cuBLAS.
Always device_reset! between ensembles; fold :none freeze into the
existing ensemble inhibition test; serialize Sentry tag+capture.

@codescene-access codescene-access Bot 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.

No application code in the PR — skipped Code Health checks.

See analysis details in CodeScene

Quality Gate Profile: Pay Down Tech Debt
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 42d2d4d55b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread test/runtests.jl
Comment on lines +22 to +23
catch
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Surface hard-reset failures before continuing

When CUDA.device_reset!() is unavailable or fails on the documented self-hosted GPU runner, this empty handler silently returns after only the soft reclaim, even though the helper states that a full reset is required on 16 GB cards. Subsequent ensemble tests can then reproduce the VRAM OOM this helper was added to prevent while hiding the actual reset failure; let the exception fail the test or explicitly verify that enough memory was reclaimed before continuing.

AGENTS.md reference: AGENTS.md:L36-L38

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot 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.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="test/runtests.jl">

<violation number="1" location="test/runtests.jl:20">
P2: The bare `catch` around `CUDA.device_reset!()` silently swallows reset failures. Since the comment above this helper states a full device reset is required between EnsembleBrain cases on 16GB cards (soft reclaim alone leaves ~2GB reserved per ensemble), a failed reset here will go unnoticed and subsequent GPU ensemble tests can hit the VRAM OOM this helper was added to prevent. Consider logging the caught error or verifying free memory afterward instead of silently continuing.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread test/runtests.jl
# Soft reclaim leaves the memory pool reserved (~2GB leak per ensemble in CI).
function reclaim_gpu_hard!()
reclaim_gpu!()
try

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The bare catch around CUDA.device_reset!() silently swallows reset failures. Since the comment above this helper states a full device reset is required between EnsembleBrain cases on 16GB cards (soft reclaim alone leaves ~2GB reserved per ensemble), a failed reset here will go unnoticed and subsequent GPU ensemble tests can hit the VRAM OOM this helper was added to prevent. Consider logging the caught error or verifying free memory afterward instead of silently continuing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/runtests.jl, line 20:

<comment>The bare `catch` around `CUDA.device_reset!()` silently swallows reset failures. Since the comment above this helper states a full device reset is required between EnsembleBrain cases on 16GB cards (soft reclaim alone leaves ~2GB reserved per ensemble), a failed reset here will go unnoticed and subsequent GPU ensemble tests can hit the VRAM OOM this helper was added to prevent. Consider logging the caught error or verifying free memory afterward instead of silently continuing.</comment>

<file context>
@@ -13,16 +13,13 @@ function reclaim_gpu!()
-        catch
-        end
-        reclaim_gpu!()
+    try
+        CUDA.device_reset!()
+    catch
</file context>
Fix with cubic

rmems commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

Local GPU CI assessment — how good is the GPU code?

Author: Grok Build / Grok 4.5 (high) (xAI)

Assessment of self-hosted GPU CI on this PR (head at time of writing: 42d2d4d).

Setup

Piece Value
Runner ShipOfTheseus (self-hosted)
GPU RTX 5080, ~16 GB, driver 610
Stack Julia 1.12 + CUDA.jl 6
Job .github/workflows/gpu-ci.yml → full Pkg.test()
Latest green run 30669832888

Latest green run numbers

  • 82 / 82 tests passed in ~1 min 17 s of test time (~2 min total job)
  • CUDA reported functional on the 5080
  • After tests, reclaim left ~13.8 / 16.7 GB free (cleanup worked)

Recent run history (honest)

Result Note
Fail (~18 s) Workflow bug: using CUDA without --project=.
Pass (~2 min) Early green
Fail (~2 min) Real OOM mid-suite (~99% VRAM)
Fail (long wait + fail) Same OOM on late ensemble
Pass (~2 min) After hard device_reset! + fewer full ensembles

CI is green now, but it was flaky under VRAM pressure until the suite was hardened. That is a test/hosting issue as much as a “bad kernel” issue.


What the GPU suite actually covers

Not a microbenchmark. It is a correctness + integration suite on real 65k-neuron lobes:

Area Covered?
Reference LSM (2k dense) Yes — init, dims, validation
SparseBrain construct + step! Yes
EnsembleBrain 4×65k construct Yes (with hard reclaim)
plasticity=:none freezes W_out Yes (single + ensemble)
plasticity=:readout_only updates readout Yes
plasticity=:recurrent_stdp mutates sparse W.nzVal Yes
eta=0 does not clamp-destroy weights Yes
sync=false, record_history=false, device noise Yes
Bad kwargs / NaN recurrent_eta Yes (validation errors)
Long-run learning quality / accuracy No
Throughput (steps/s) vs CUDA 5 or CPU No
Multi-GPU / multi-process No
Memory-capacity science metrics No (explicitly out of scope)

So CI answers: “Does the GPU path run and obey the contracts we wrote?”
It does not answer: “Is this state-of-the-art LSM performance?”


How good is the GPU code? (engineering judgment)

Strengths (genuinely good)

  1. Real GPU path, not a toy — F16 sparse SpMV (W * S_f16), dense F32 mul! for I/O, custom @cuda pair-STDP on ~43M edges, ensemble of 262k neurons on one consumer GPU.

  2. Sensible defaults — Default :readout_only freezes recurrent W (safe). Recurrent STDP is opt-in. Host noise by default (portable).

  3. Engineering around CUDA.jl 6 / 16 GB — Lazy STDP edge lists (don’t pay ~300 MB/lobe until needed), work-buffer reuse, deferred host sum when sync=false, explicit validation + Sentry filter for API noise.

  4. Contracts are tested on the device that matters — Plasticity modes, freeze vs learn, STDP mutation, ensemble forwarding — and that suite now passes on the 5080 in CI.

Weaknesses / risk (not “bad,” but real)

  1. Memory is tight by design — One 4-lobe ensemble is most of a 16 GB card. Soft reclaim was not enough; tests need hard reset. Production code that builds many ensembles without careful teardown will OOM. That is an architectural reality of 4×65k F16 sparse + history, not a small bug.

  2. STDP is experimental — Simple pair rule on existing edges only; no structural plasticity; constructor weights can exceed W_MAX until learning runs (clamp only on nonzero dw). Science quality is unproven in CI.

  3. No performance bar — CI does not assert “N steps/s” or compare SpMV paths. “Works” ≠ “fast.”

  4. CI path depends on one machine — Self-hosted, one GPU, sequential jobs. Offline runner = red/missing GPU checks. Fork PRs intentionally don’t run on that host (security).

  5. Coverage % on GitHub — GitHub-hosted coverage is still mostly CPU; GPU lines look “uncovered” there even when GPU CI is green. Don’t read codecov patch % as “GPU untested.”


Bottom line scorecard

Dimension Grade Plain English
Does it run on real GPU in CI? A 82/82 on RTX 5080, ~2 min
API / plasticity correctness A− Modes, freeze, STDP, kwargs solidly tested
VRAM / multi-ensemble hygiene B− Works with discipline; fragile if you allocate casually
Performance ? Not measured in CI
Scientific quality of STDP ? Experimental, not validated by CI
Overall GPU engineering for this PR B+ / solid Production-capable stepping stack; experimental learning path; memory-aware but not roomy

In one sentence:
Local GPU CI shows the GPU code is good enough to ship as a working CUDA LSM + experimental plasticity API on a 5080 — not “bad,” not “optimized endgame.” Correctness and integration look strong; headroom, perf, and learning quality are the open questions.

Useful follow-ups if we want a sharper bar: a short steps/s benchmark job, and a multi-ensemble stress test that fails if free VRAM after teardown is too low.


Posted by Grok Build using Grok 4.5 (high).

rmems commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

Local GPU test run (RTX 5080)

Author: Grok Build · Grok 4.5 (high) (xAI)

Full package test log from local/self-hosted GPU verification.

Result

Metric Value
Outcome passed
Assertions 82 / 82
Wall time 1m 17.2s
Device NVIDIA GeForce RTX 5080
CUDA functional
Peak ensemble VRAM (reported) 6.03 → 8.14 → 10.76 / 16.65 GB (36% → 49% → 65%)
Post-test free VRAM 13.84 / 16.65 GB

Notable log lines

[ Info: LiquidCortex: CUDA functional — GPU kernels available on NVIDIA GeForce RTX 5080.

[brain:test] Initializing 65,536-neuron lobe (τ_m=20.0ms, in=14, out=16)...
[brain:test] Generating sparse connectivity (~42.9M synapses)...
[brain:test] W_sparse: 42735064 nnz, ρ≈0.9
[brain:test] History buffer: 1000×65536 = 262.1 MB
[brain:test] ✓ Lobe initialized (τ_m=20.0ms)

[brain:custom] … in=8, out=4 … ✓ Lobe initialized

┌ Warning: `device_reset!` is deprecated and is now a no-op; CUDA.jl no longer supports resetting a device.
│   caller = device_reset! at deprecated.jl:20 [inlined]
└ @ Core ~/.julia/packages/CUDACore/NlVPI/lib/cudadrv/deprecated.jl:20

Ensemble Brain — 4 Lobes × 65,536 = 262,144 Neurons
  default dims (in=14, out=16): VRAM 6.03 / 16.65 GB (36% used)
  custom dims  (in=8,  out=4):  VRAM 8.14 / 16.65 GB (49% used)
  step ensemble (in=8, out=4):  VRAM 10.76 / 16.65 GB (65% used)

… single-lobe TDD cases (step-test, tdd-default, :none, :readout_only,
  history, sync, device noise, recurrent_stdp) all initialized and ran …

Test Summary: | Pass  Total     Time
LiquidCortex  |   82     82  1m17.2s
     Testing LiquidCortex tests passed
VRAM free/total GB: 13.84 / 16.65

Interpretation

  • Full GPU suite green on Julia 1.12 + CUDA.jl 6 on the 5080 in ~1.3 minutes.
  • Single lobes ~43M sparse F16 edges + 262 MB history; ensembles reach 262,144 neurons and stay within card VRAM in this run (peak ~65% during the third ensemble construction).
  • Soft reclaim is incomplete across ensembles (VRAM rises 36% → 49% → 65%); post-suite free 13.84 GB shows most memory returns after GC/reclaim at the end.
  • CUDA.device_reset! is a no-op (deprecated) on this CUDA.jl stack — the warning confirms our earlier hard-reset helper cannot fully clear the pool. Suite still passed, but VRAM hygiene should rely on dropping references + GC.gc + CUDA.reclaim(), not device_reset!.

Posted by Grok Build · Grok 4.5 (high).

@rmems
rmems merged commit 107d7d7 into main Aug 1, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation GitHub Actions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant