Skip to content

feat: support pyramid raw vector retrieval - #2503

Open
jac0626 wants to merge 5 commits into
antgroup:mainfrom
jac0626:codex/pyramid-raw-vector
Open

feat: support pyramid raw vector retrieval#2503
jac0626 wants to merge 5 commits into
antgroup:mainfrom
jac0626:codex/pyramid-raw-vector

Conversation

@jac0626

@jac0626 jac0626 commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Change Type

  • Bug fix
  • New feature
  • Improvement/Refactor
  • Documentation
  • CI/Build/Infra

Linked Issue

What Changed

  • Add Pyramid store_raw_vector support and expose raw vectors through GetRawVectorByIds/GetVectorByInnerId.
  • Reuse an existing in-memory FP32 base or reorder data cell when possible; otherwise maintain separate FP32 raw-vector storage across Train, Build, Add, ODescent, and serialization.
  • Add live Pyramid GetMemoryUsage()/GetMemoryUsageDetail() reporting, including hierarchy graphs and separate raw-vector storage without double-counting aliases.
  • Persist FP32 hold_molds, validate legacy deserialization parameters before reading the body, and preserve backward compatibility for older Pyramid parameter snapshots.
  • Document the new parameters in English and Chinese.

Test Evidence

  • make fmt
  • make lint
  • make test
  • make cov, run tests, and collect coverage
  • Other (describe below)

Test details:

/opt/homebrew/opt/llvm@15/bin/clang-format --dry-run --Werror <changed C++ files>
cmake --build build --target unittests -j6
./build/tests/unittests
  All tests passed (62,573,683 assertions in 561 test cases)

./build/tests/unittests '[ut][pyramid][raw_vector]'
  All tests passed (45 assertions in 6 test cases)

Compatibility Impact

  • API/ABI compatibility: additive behavior; no public signature changes
  • Behavior changes: Pyramid can preserve exact FP32 vectors when store_raw_vector=true; FP32 parameter snapshots now retain hold_molds

Performance and Concurrency Impact

  • Performance impact: separate FP32 storage is allocated only when existing in-memory FP32 codes cannot be reused
  • Concurrency/thread-safety impact: memory reporting and raw-vector reads use the existing Pyramid resize lock

Documentation Impact

  • No docs update needed
  • Updated docs:
    • README.md
    • DEVELOPMENT.md
    • CONTRIBUTING.md
    • Other: English/Chinese Pyramid and streaming-serialization documentation

Risk and Rollback

  • Risk level: medium
  • Rollback plan: revert this commit to restore the previous Pyramid storage and serialization behavior

Checklist

  • I have linked the relevant issue (required for kind/bug and kind/feature; see "Linked Issue" above)
  • I have added/updated tests for new behavior or bug fixes
  • I have considered API compatibility impact
  • I have updated docs if behavior/workflow changed
  • My commit messages follow project conventions (Conventional Commits, optional [skip ci] prefix)

Copilot AI review requested due to automatic review settings July 22, 2026 04:08
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@vsag-bot

vsag-bot commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

/label status/waiting-for-review
/waiting-on reviewer
/request-review @jiaweizone
/request-review @wxyucs
/request-review @inabao

@jac0626 jac0626 added kind/feature Brand-new functionality or capabilities 引入全新的功能、新特性或新能力 version/1.0 labels Jul 22, 2026
@vsag-bot
vsag-bot self-requested a review July 22, 2026 04:08
@vsag-bot

vsag-bot commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Automated pull request review completed.

Review effort: high (855 changed lines across 9 files).

Submitted 3 inline comments.
Review: #2503 (review)

@mergify

mergify Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 All 3 merge protections satisfied — ready to merge.

Show 3 satisfied protections

🟢 Require kind label

  • label~=^kind/

🟢 Require version label

  • label~=^version/

🟢 Require linked issue for feature/bug PRs

  • body~=(?im)(?:^|[\s\-\*])(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s*:?\s+(?:#\d+|[\w.\-]+/[\w.\-]+#\d+|https?://github\.com/[\w.\-]+/[\w.\-]+/issues/\d+)

Copilot AI 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.

Pull request overview

Adds Pyramid support for preserving and retrieving FP32 raw vectors (including through streaming/legacy serialization), along with detailed live memory reporting to enable accurate HGraph vs Pyramid memory comparisons.

Changes:

  • Implement Pyramid raw-vector storage/reuse logic and expose it via GetVectorByInnerId / GetRawVectorByIds, including streaming + legacy serialization support.
  • Add Pyramid GetMemoryUsage() / GetMemoryUsageDetail() with hierarchy graph accounting and alias-aware raw-vector reporting.
  • Update unit tests, docs (EN/ZH), and add a C++ example to reproduce HGraph/Pyramid memory comparisons.

Reviewed changes

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

Show a summary per file
File Description
src/quantization/fp32_quantizer_parameter.cpp Persist hold_molds in FP32 quantizer JSON.
src/quantization/fp32_quantizer_parameter_test.cpp Extend FP32 parameter JSON roundtrip coverage for hold_molds.
src/algorithm/pyramid/pyramid.h Add raw-vector members/hooks and memory usage APIs to Pyramid declarations.
src/algorithm/pyramid/pyramid.cpp Implement raw-vector reuse/storage, serialization blocks, and memory usage reporting.
src/algorithm/pyramid/pyramid_zparameters.cpp Backward-compat restore for legacy FP32 hold_molds and compatibility checks for raw-vector params.
src/algorithm/pyramid/pyramid_test.cpp Add regression tests covering raw vectors, (de)serialization, cosine behavior, and memory reporting.
examples/cpp/README.md Document new memory comparison example.
examples/cpp/CMakeLists.txt Build target for the new memory comparison example.
examples/cpp/325_feature_compare_hgraph_pyramid_memory.cpp New example program to compare RSS/allocator/index-reported memory.
docs/docs/zh/src/indexes/pyramid.md Document new Pyramid raw-vector parameters (ZH).
docs/docs/zh/src/advanced/new_serialization.md Document Pyramid streaming raw_vector block (ZH).
docs/docs/en/src/indexes/pyramid.md Document new Pyramid raw-vector parameters (EN).
docs/docs/en/src/advanced/new_serialization.md Document Pyramid streaming raw_vector block (EN).

Comment thread src/algorithm/pyramid/pyramid.cpp Outdated
@jac0626

jac0626 commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator Author

/retest

@jac0626
jac0626 marked this pull request as ready for review July 22, 2026 06:01
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

Copilot AI review requested due to automatic review settings July 22, 2026 06:10

Copilot AI 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.

Pull request overview

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

Comments suppressed due to low confidence (1)

src/algorithm/pyramid/pyramid.cpp:978

  • SUPPORT_GET_RAW_VECTOR_BY_IDS is enabled whenever raw_vector_ != nullptr, but for cosine metrics an FP32 flatten without HoldMolds() stores normalized vectors, so GetRawVectorByIds would not return the original/raw vectors. This should follow the same gating used by BruteForce/HGraph: only advertise raw-vector support for cosine when molds are preserved.
    if (raw_vector_ != nullptr) {
        this->index_feature_list_->SetFeature(IndexFeature::SUPPORT_GET_RAW_VECTOR_BY_IDS);
    }

Comment thread src/algorithm/pyramid/pyramid.cpp Outdated

@vsag-bot vsag-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated inline review completed.

Review effort: high (531 changed lines across 10 files).
Submitted 4 inline comments.

Reviewed commit c519510.

Comment thread src/quantization/fp32_quantizer_parameter.cpp Outdated
Comment thread src/algorithm/pyramid/pyramid.cpp
Comment thread src/algorithm/pyramid/pyramid.cpp Outdated
Comment thread src/algorithm/pyramid/pyramid.cpp
Copilot AI review requested due to automatic review settings July 23, 2026 03:19
@vsag-bot
vsag-bot self-requested a review July 23, 2026 03:19

Copilot AI 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.

Pull request overview

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

@jac0626
jac0626 force-pushed the codex/pyramid-raw-vector branch from 08b4de1 to 360048a Compare July 23, 2026 03:31
Comment thread src/algorithm/pyramid/pyramid.cpp
Comment thread src/algorithm/pyramid/pyramid.cpp
@wxyucs

wxyucs commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

/version 1.1

@jac0626
jac0626 force-pushed the codex/pyramid-raw-vector branch from cff0f1c to 220ef23 Compare July 29, 2026 03:03
Copilot AI review requested due to automatic review settings July 29, 2026 03:03
@vsag-bot
vsag-bot self-requested a review July 29, 2026 03:03

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@LHT129 LHT129 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[suggestion] ExportModel does not export raw_vector_ data. When create_new_raw_vector_ is true, the exported model will lose raw vector storage, causing GetRawVectorByIds to fail on the exported index. Consider adding:

if (create_new_raw_vector_) {
    this->raw_vector_->ExportModel(index->raw_vector_);
}

in Pyramid::ExportModel (pyramid.cpp, around line 877).

@vsag-bot vsag-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated inline review completed.

Review effort: high (811 changed lines across 8 files).
Submitted 2 inline comments.

Reviewed commit 220ef23.

Comment thread src/algorithm/pyramid/pyramid.cpp
Comment thread src/algorithm/pyramid/pyramid.cpp Outdated
@jac0626

jac0626 commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator Author

/retest

Copilot AI review requested due to automatic review settings July 30, 2026 07:49
@jac0626
jac0626 force-pushed the codex/pyramid-raw-vector branch from 220ef23 to 44608a1 Compare July 30, 2026 07:49
@jac0626

jac0626 commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

Review follow-up: I also evaluated the non-inline ExportModel suggestion and intentionally made no change. Pyramid::ExportModel exports reusable quantizer/model state, not indexed vector data. A dedicated Pyramid raw cell is constrained to FP32, which has no trained model state to transfer; the target is already constructed with an empty raw cell from the same parameters. Copying raw vectors would violate the existing empty-model contract rather than fix retrieval.

@vsag-bot
vsag-bot self-requested a review July 30, 2026 07:50
jac0626 and others added 5 commits July 30, 2026 15:51
Signed-off-by: jac0626 <jac0626@users.noreply.github.com>
Assisted-by: Codex:gpt-5
Signed-off-by: jac0626 <jac0626@users.noreply.github.com>
Assisted-by: Codex:gpt-5
Signed-off-by: jac0626 <jac0626@users.noreply.github.com>
Assisted-by: Codex:gpt-5
Signed-off-by: jc543239 <jc543239@antgroup.com>
Assisted-by: Codex:gpt-5
Signed-off-by: jac0626 <jac0626@users.noreply.github.com>
Assisted-by: Codex:gpt-5
@jac0626
jac0626 force-pushed the codex/pyramid-raw-vector branch from 44608a1 to d8dc386 Compare July 30, 2026 07:54
@jac0626

jac0626 commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

Final rebase note: upstream/main advanced again while validation was running. The review-fix commit referenced in the thread replies was rewritten from 44608a1 to d8dc386; the fixes and tests are unchanged.

@LHT129 LHT129 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review of the latest push (44608a1). This push addresses several prior review comments (label publication order, null-pointer checks, factory load path). A few remaining observations below.

std::unordered_map<std::string, uint64_t>
Pyramid::GetMemoryUsageDetail() const {
std::lock_guard count_lock(cur_element_count_mutex_);
std::unique_lock resize_lock(resize_mutex_);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[suggestion] GetMemoryUsageDetail() acquires unique_lock on resize_mutex_, which blocks concurrent search, GetVectorByInnerId, and distance-by-id calls. Since this method only reads state, a shared_lock would be sufficient and would avoid serializing all read operations against memory-usage queries.

Consider changing std::unique_lock resize_lock(resize_mutex_) to std::shared_lock resize_lock(resize_mutex_).

for (const auto& [key, child] : children_) {
memory += key.capacity() + 1;
memory += child->GetMemoryUsage();
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[suggestion] IndexNode::GetMemoryUsage() multiplies children_.bucket_count() by value_type size, which overestimates memory because bucket_count() is the hash-table bucket count, not the number of stored entries. Use children_.size() instead to get the actual number of child nodes.

"{RAW_VECTOR_KEY}": {
"{IO_PARAMS_KEY}": {
"{TYPE_KEY}": "{IO_TYPE_VALUE_BLOCK_MEMORY_IO}",
"{IO_FILE_PATH_KEY}": "{DEFAULT_FILE_PATH_VALUE}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[suggestion] The raw_vector cell defaults to the same ./default_file_path used by base and precise codes. With a documented configuration such as SQ8 base_io_type=buffer_io and raw_vector_io_type=buffer_io, both cells would open the same file path, leading to corruption. Consider using a distinct default path (e.g. ./default_raw_vector_path) or requiring an explicit path when the raw-vector backend is file-backed.

@LHT129 LHT129 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Additional observations on the latest push.


void
Pyramid::check_and_init_raw_vector(const FlattenInterfaceParamPtr& raw_vector_param,
const IndexCommonParam& common_param) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[suggestion] check_and_init_raw_vector checks raw_vector_param->io_parameter->GetTypeName() to decide whether to attempt aliasing, but the aliasing decision should be based on the candidate cell's IO type (i.e. whether base_codes_ or precise_codes_ is in-memory FP32), not the requested raw-vector backend. If the user requests raw_vector_io_type=buffer_io but base_codes_ happens to be in-memory FP32, aliasing is still desirable and safe.

Consider calling find_raw_vector_source(true) unconditionally (regardless of raw_vector_param IO type) and only falling back to MakeInstance when no alias is found.

auto io_type_name = raw_vector_param->io_parameter->GetTypeName();
if (io_type_name == IO_TYPE_VALUE_BLOCK_MEMORY_IO || io_type_name == IO_TYPE_VALUE_MEMORY_IO) {
raw_vector_ = find_raw_vector_source(true);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[note] check_and_init_raw_vector sets has_raw_vector_ = raw_vector_ != nullptr at line 1546, but raw_vector_ is guaranteed non-null at this point (either from find_raw_vector_source or MakeInstance). The null check is redundant; has_raw_vector_ will always be true when this code is reached. Consider setting has_raw_vector_ = true directly.

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@LHT129 LHT129 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review for PR #2503

[critical] Missing has_raw_vector_ declaration in pyramid.h

has_raw_vector_ is assigned in check_and_init_raw_vector() (pyramid.cpp line ~1558) and read in InitFeatures() (pyramid.cpp line ~999), but this member variable is never declared in pyramid.h. The header only declares create_new_raw_vector_. This will cause a compilation error.

Fix: Add bool has_raw_vector_{false}; to the member variables section of pyramid.h, alongside create_new_raw_vector_:

bool create_new_raw_vector_{false};  // whether raw_vector_ owns separate storage
bool has_raw_vector_{false};         // whether raw vector retrieval is available

[suggestion] Unnecessary FlattenInterface allocation in check_and_init_raw_vector

In check_and_init_raw_vector(), FlattenInterface::MakeInstance(raw_vector_param, common_param) is always called first (line ~1543), even when raw_vector_ will ultimately alias base_codes_ or precise_codes_ via find_raw_vector_source(). When aliasing succeeds, the newly constructed instance is leaked (overwritten by the aliased pointer). Consider checking for an aliasable source first, and only constructing a new instance when necessary.

[note] CalDistanceById precedence order

Both CalcDistanceById overloads override flat with raw_vector_ when raw_vector_ != nullptr && calculate_precise_distance. This means raw_vector_ takes precedence over precise_codes_ which takes precedence over base_codes_. This is correct behavior (raw vectors are always the most precise), but the precedence chain is implicit. A brief comment noting the priority would help future readers.

@vsag-bot vsag-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated inline review completed.

Review effort: high (855 changed lines across 9 files).
Submitted 3 inline comments.

Reviewed commit d8dc386.

"{RAW_VECTOR_KEY}": {
"{IO_PARAMS_KEY}": {
"{TYPE_KEY}": "{IO_TYPE_VALUE_BLOCK_MEMORY_IO}",
"{IO_FILE_PATH_KEY}": "{DEFAULT_FILE_PATH_VALUE}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] Give raw storage a cell-specific backing file

This uses the same DEFAULT_FILE_PATH_VALUE as the base and precise cells. With accepted settings such as SQ8 base_io_type=buffer_io plus raw_vector_io_type=buffer_io and omitted paths, both cells truncate and write the same file from offset zero; FP32 raw writes then overwrite the quantized search codes, silently corrupting the index. Reject shared file paths or derive a raw-specific default.

precise_codes_->InsertVector(data_vectors + dim_ * i, inner_id);
}
if (create_new_raw_vector_) {
raw_vector_->InsertVector(data_vectors + dim_ * i, inner_id);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Roll back counts when the raw-vector write fails

FlattenDataCell::InsertVector advances its count before performing the IO write, and base/precise cells have already advanced by this line. If the separate raw backend fails here, no label, logical count, or hierarchy node is published, but GetNumElements() still observes the advanced base count; in a multi-vector Add, retrying can reuse inner ID 0 and overwrite an earlier published vector because cur_element_count_ was never updated. Make the cell writes transactional or explicitly restore every affected count/state on failure.

if (create_new_raw_vector_) {
raw_vector_->InsertVector(data_vectors + dim_ * i, inner_id);
}
label_table_->Insert(inner_id, data_ids[i]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Synchronize label publication with raw-vector lookup

This mutates LabelTable while holding only cur_element_count_mutex_, whereas the newly exposed GetRawVectorByIds path delegates to GetVectorByIds, which calls GetIdByLabel without that mutex or label_lookup_mutex_. A raw lookup concurrent with Add can therefore race the label vector/hash map and invoke undefined behavior even though the vector bytes are written first. Publish and resolve labels under the same lock.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/feature Brand-new functionality or capabilities 引入全新的功能、新特性或新能力 module/docs module/example size/XL version/1.1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat](other): support raw vector retrieval in pyramid

5 participants