Skip to content

⚡ Bolt: Optimize Hamming distance with np.bitwise_count#30

Open
RobinALG87 wants to merge 1 commit into
mainfrom
bolt/optimize-hamming-distance-popcount-7941847535009991281
Open

⚡ Bolt: Optimize Hamming distance with np.bitwise_count#30
RobinALG87 wants to merge 1 commit into
mainfrom
bolt/optimize-hamming-distance-popcount-7941847535009991281

Conversation

@RobinALG87
Copy link
Copy Markdown
Owner

@RobinALG87 RobinALG87 commented Feb 20, 2026

💡 What: Replaced lookup-table based population count with np.bitwise_count (NumPy 2.0+).
🎯 Why: Hamming distance calculation is a core bottleneck in vector search (HAIMEngine queries).
📊 Impact: Reduces Hamming distance computation time by ~80% (5x speedup).
🔬 Measurement: Benchmarked with benchmarks/benchmark_popcount.py and benchmarks/benchmark_hdv_hamming.py.

The implementation is backward compatible with older NumPy versions via hasattr check.


PR created automatically by Jules for task 7941847535009991281 started by @RobinALG87

Summary by CodeRabbit

  • Performance Improvements

    • Enhanced Hamming distance calculations for binary HDVs with improved computational efficiency
    • Optimized batch operation performance with better memory handling, particularly for large index operations
    • Improved compatibility with NumPy 2.0+ while maintaining backward compatibility with older versions
  • Documentation

    • Added performance learning notes documenting optimization strategies and benchmark insights

Replaces the previous lookup-table implementation for population count in BinaryHDV with `np.bitwise_count` (available in NumPy 2.0+). This results in a ~5x performance improvement for Hamming distance calculations, which are critical for vector search operations.

- Added `_popcount_array` helper that conditionally uses `np.bitwise_count` if available.
- Updated `hamming_distance`, `batch_hamming_distance`, `batch_hamming_distance_matrix`, and `__repr__` to use the optimized helper.
- Falls back to lookup table for older NumPy versions.

Benchmarks show a reduction in batch Hamming distance calculation time from ~0.84s to ~0.17s (for 100 iterations of 1000 vectors).

Co-authored-by: RobinALG87 <55180608+RobinALG87@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Copilot AI review requested due to automatic review settings February 20, 2026 15:34
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Copy link
Copy Markdown

Copilot AI left a comment

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 optimizes BinaryHDV Hamming distance/popcount computations by preferring NumPy’s np.bitwise_count (NumPy 2.0+) while keeping a lookup-table fallback for older NumPy versions, targeting a key vector-search bottleneck in HAIMEngine queries.

Changes:

  • Added a _popcount_array() helper that uses np.bitwise_count when available, otherwise uses the existing cached lookup table.
  • Switched hamming_distance, __repr__, and batch Hamming routines to use _popcount_array() for popcount.
  • Added a Jules “bolt” note documenting the optimization learnings.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/mnemocore/core/binary_hdv.py Introduces _popcount_array() and routes all popcount usage through it to leverage np.bitwise_count where possible.
.jules/bolt.md Documents the performance learning and guidance for preferring np.bitwise_count with a compatibility fallback.
Comments suppressed due to low confidence (2)

src/mnemocore/core/binary_hdv.py:172

  • hamming_distance() docstring still says it "Uses lookup table for speed", but the implementation now prefers np.bitwise_count when available. Please update the docstring to reflect the new behavior (and keep the lookup-table wording as the fallback path).
        # Optimized: use np.bitwise_count or precomputed table
        return int(_popcount_array(xor_result).sum())

src/mnemocore/core/binary_hdv.py:49

  • _popcount_array() checks hasattr(np, 'bitwise_count') on every call. Since this is in the hot path, consider caching the availability at import time (e.g., a module-level BITWISE_COUNT = getattr(np, 'bitwise_count', None) and branching on that) to avoid repeated attribute lookups.
    if hasattr(np, "bitwise_count"):
        return np.bitwise_count(arr)
    return _build_popcount_table()[arr]

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 20, 2026

📝 Walkthrough

Walkthrough

A new documentation file records performance learnings about vectorized operations and popcount optimization. Concurrently, the codebase introduces a _popcount_array helper function that leverages NumPy 2.0+'s native np.bitwise_count for bit-population counting, falling back to a precomputed lookup table for older NumPy versions. This helper replaces direct table lookups in Hamming distance calculations.

Changes

Cohort / File(s) Summary
Performance Documentation
.jules/bolt.md
New learning notes documenting two performance insights: vectorized popcount optimization and batch rolling overhead observations from 2025-05-23.
Popcount Optimization
src/mnemocore/core/binary_hdv.py
Introduced _popcount_array helper function using np.bitwise_count (NumPy 2.0+) with lookup-table fallback; replaced direct table lookups in hamming_distance, __repr__, batch_hamming_distance, and batch_hamming_distance_matrix with calls to this helper.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • PR #29: Adds precomputed byte-wise lookup table for popcount operations in BinaryHDV; this PR builds upon that foundation by introducing a wrapper that intelligently routes to np.bitwise_count when available or falls back to the lookup table.

Poem

🐰 A byte at a time, we count our bits,
NumPy's swift hand now truly fits!
When two point-oh comes around,
Faster popcounts shall abound,
Yet gracefully we fall back—logic grits! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main change: optimizing Hamming distance computation with np.bitwise_count, which is the core modification across binary_hdv.py and the accompanying documentation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bolt/optimize-hamming-distance-popcount-7941847535009991281

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/mnemocore/core/binary_hdv.py (1)

47-49: Hoist hasattr check to a module-level constant.

hasattr(np, "bitwise_count") is re-evaluated on every single call to _popcount_array, including once per row in the batch_hamming_distance_matrix loop. This attribute lookup is cheap but unnecessary; the numpy version doesn't change at runtime.

♻️ Proposed refactor
+# Detect NumPy 2.0+ native popcount support once at import time
+_NP_HAS_BITWISE_COUNT: bool = hasattr(np, "bitwise_count")
+
+
 def _popcount_array(arr: np.ndarray) -> np.ndarray:
     """
     Compute population count (number of 1-bits) for a uint8 array.
     Uses np.bitwise_count if available (NumPy 2.0+), otherwise uses lookup table.
     """
-    if hasattr(np, "bitwise_count"):
+    if _NP_HAS_BITWISE_COUNT:
         return np.bitwise_count(arr)
     return _build_popcount_table()[arr]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mnemocore/core/binary_hdv.py` around lines 47 - 49, Hoist the repeated
hasattr lookup by evaluating hasattr(np, "bitwise_count") once at import time
(e.g., define a module-level boolean like HAS_BITWISE_COUNT) and replace the
per-call check inside _popcount_array with that constant; keep the fallback to
_build_popcount_table()[arr] and ensure batch_hamming_distance_matrix and any
other callers still call _popcount_array unchanged so the runtime behavior
remains identical.
.jules/bolt.md (1)

1-7: Consider whether .jules/bolt.md belongs in the repository.

This file appears to be auto-generated AI agent working notes. Committing it into the main repo means it ships in distributions and appears to contributors as first-class documentation. If the content is worth preserving, consider either moving it to a proper location (e.g., docs/performance-notes.md) or adding .jules/ to .gitignore.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.jules/bolt.md around lines 1 - 7, The committed file .jules/bolt.md looks
like ephemeral AI agent notes and should not be shipped as top-level
documentation; either move its contents into a proper docs file (e.g.,
docs/performance-notes.md) and commit that instead, or remove .jules/bolt.md
from the repo and add the .jules/ directory to .gitignore so these agent
artifacts are not tracked; after choosing, update any references and commit the
move/remove so the repository no longer contains the auto-generated notes as
first-class docs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/mnemocore/core/binary_hdv.py`:
- Around line 47-49: _popcount_array currently returns different dtypes
depending on whether np.bitwise_count exists (uint8) or the fallback table
(int32), causing batch_hamming_distance to be version-dependent; fix by
normalizing the popcount output to a single consistent integer dtype (e.g., cast
the result of either np.bitwise_count(arr) or _build_popcount_table()[arr] to a
fixed dtype such as np.int32 or np.int64) before returning from _popcount_array
so callers like batch_hamming_distance get a stable dtype; update
_popcount_array (and if preferred adjust batch_hamming_distance) to perform this
explicit np.astype(...) cast and keep references to _popcount_array,
np.bitwise_count, and _build_popcount_table to locate the change.

---

Nitpick comments:
In @.jules/bolt.md:
- Around line 1-7: The committed file .jules/bolt.md looks like ephemeral AI
agent notes and should not be shipped as top-level documentation; either move
its contents into a proper docs file (e.g., docs/performance-notes.md) and
commit that instead, or remove .jules/bolt.md from the repo and add the .jules/
directory to .gitignore so these agent artifacts are not tracked; after
choosing, update any references and commit the move/remove so the repository no
longer contains the auto-generated notes as first-class docs.

In `@src/mnemocore/core/binary_hdv.py`:
- Around line 47-49: Hoist the repeated hasattr lookup by evaluating hasattr(np,
"bitwise_count") once at import time (e.g., define a module-level boolean like
HAS_BITWISE_COUNT) and replace the per-call check inside _popcount_array with
that constant; keep the fallback to _build_popcount_table()[arr] and ensure
batch_hamming_distance_matrix and any other callers still call _popcount_array
unchanged so the runtime behavior remains identical.

Comment on lines +47 to +49
if hasattr(np, "bitwise_count"):
return np.bitwise_count(arr)
return _build_popcount_table()[arr]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

_popcount_array returns different dtypes across numpy versions, making batch_hamming_distance's return dtype version-dependent.

np.bitwise_count is a ufunc that, for uint8 input, returns dtype=uint8. The fallback _build_popcount_table()[arr] returns dtype=int32 (the table is built with dtype=np.int32). Callers that don't explicitly wrap with int(...) — namely batch_hamming_distance at line 278 — will receive uint64 on NumPy 2.0+ and int64 on older versions. This is a silent behavioral change that could break downstream code comparing or storing the distances array with an assumed dtype.

Fix: normalize the output to a consistent dtype at the end of _popcount_array, or explicitly cast at the call site.

🛡️ Proposed fix: normalize return dtype
 def _popcount_array(arr: np.ndarray) -> np.ndarray:
     """
     Compute population count (number of 1-bits) for a uint8 array.
     Uses np.bitwise_count if available (NumPy 2.0+), otherwise uses lookup table.
+    Always returns dtype=np.int32 for a consistent interface.
     """
     if hasattr(np, "bitwise_count"):
-        return np.bitwise_count(arr)
+        return np.bitwise_count(arr).astype(np.int32)
     return _build_popcount_table()[arr]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if hasattr(np, "bitwise_count"):
return np.bitwise_count(arr)
return _build_popcount_table()[arr]
if hasattr(np, "bitwise_count"):
return np.bitwise_count(arr).astype(np.int32)
return _build_popcount_table()[arr]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mnemocore/core/binary_hdv.py` around lines 47 - 49, _popcount_array
currently returns different dtypes depending on whether np.bitwise_count exists
(uint8) or the fallback table (int32), causing batch_hamming_distance to be
version-dependent; fix by normalizing the popcount output to a single consistent
integer dtype (e.g., cast the result of either np.bitwise_count(arr) or
_build_popcount_table()[arr] to a fixed dtype such as np.int32 or np.int64)
before returning from _popcount_array so callers like batch_hamming_distance get
a stable dtype; update _popcount_array (and if preferred adjust
batch_hamming_distance) to perform this explicit np.astype(...) cast and keep
references to _popcount_array, np.bitwise_count, and _build_popcount_table to
locate the change.

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.

2 participants