Skip to content

Add typed-channel PPR C++ extraction support - #724

Draft
mkolodner-sc wants to merge 11 commits into
mkolodner-sc/ppr-selector-helperfrom
mkolodner-sc/typed-channel-ppr-cpp
Draft

Add typed-channel PPR C++ extraction support#724
mkolodner-sc wants to merge 11 commits into
mkolodner-sc/ppr-selector-helperfrom
mkolodner-sc/typed-channel-ppr-cpp

Conversation

@mkolodner-sc

@mkolodner-sc mkolodner-sc commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds the C++ support needed by typed-channel PPR sampling.

This is the lower PR in the split stack. It builds on #723 and provides the typed-channel drain/extraction primitives used by the Python sampler layer in #709.

What Changed

  • Adds typed-channel frontier draining for PPR forward-push states.

    • Drains one PPRForwardPush state per typed channel.
    • Tracks which channels still need pushResiduals().
    • Unions fetch frontiers by edge type so shared one-hop fetches can be issued once.
  • Adds typed PPR extraction in C++.

    • Builds candidate views from completed channel states.
    • Includes residual top-up candidates when enabled.
    • Deduplicates nodes reached through multiple channels.
    • Selects the final per-seed sequence globally by best calibrated score across channels.
    • Caps output at maxPPRNodes.
  • Emits typed edge-attr feature rows as:
    [best_score, channel_scores..., channel_presence_bits...].

  • Updates the pybind wrapper and Python stub for the typed extraction API.

Tests

Added focused C++ coverage in gigl-core/tests/ppr_forward_push_test.cpp for:

  • typed frontier draining
  • fetch-budget behavior
  • typed extraction and cross-channel deduplication
  • residual top-up handling
  • disabling residual top-up
  • typed edge-attr feature layout

@mkolodner-sc
mkolodner-sc force-pushed the mkolodner-sc/typed-channel-ppr-cpp branch from b8bca9e to 41b08d4 Compare July 29, 2026 16:48
// Helper function for adding one channel's extracted PPR candidates into the
// emitted typed feature table and a fill-pass candidate list.
//
// Unlike addTypedPPRChannelCandidates, this helper owns the output view: it

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.

Does it own the output view?

Comment on lines +565 to +568
if (a.channelIndex != b.channelIndex) {
return a.channelIndex < b.channelIndex;
}
return a.nodeId < b.nodeId;

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.

btw is there a case where the same node id can be returned from the same channel index twice?

// Typed channels are constructed from the same seed batch and graph schema;
// only the edge-type traversal allowlist differs. Use the first state as
// the shared schema source for batch size and node-type count.
const auto* firstState = states.front();

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.

qq we do guarantee there's always at least one state rght?

}

PPRExtractResult extractedTypedPPRByNodeTypeId;
std::vector<int32_t> topUpChannelQuotas(static_cast<size_t>(numChannels), maxPPRNodes);

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.

If these are always the same value then does this even need to be a vector?

Or as we're discussing offline maybe we should ignore this and only use the per-channel quota?

Comment thread gigl-core/core/sampling/ppr_forward_push.cpp Outdated
}
}

if (enableResidualTopUp && selectedNodes.size() > 1) {

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.

qq why > 1?

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.

Sorry silly question but this code all uses the neighbor cache we have right?


queueDrainResult.drainedChannelIndices.push_back(static_cast<int32_t>(channelIndex));

bool fetchBudgetRemaining = maxFetchIterations < 0 || fetchIterationCounts[channelIndex] < maxFetchIterations;

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.

should this be maxFetchIterations <= 0?

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