Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f9458ea
feat(cudf): accelerate bounded distributed TopN and Grace restore
winningsix Aug 7, 2026
d136b92
fix(cudf): stabilize Job 144 spill restore and UCX progress
winningsix Aug 8, 2026
1445b46
test(cudf): stabilize standalone TopN coverage
winningsix Aug 8, 2026
13d4cf7
fix(ucx): close receive backpressure lost wake
winningsix Aug 8, 2026
b08075a
refactor(cudf): layer spillable Top-1 on community operator
winningsix Aug 10, 2026
3f70880
refactor(cudf): integrate bounded Top-1 into community operator
winningsix Aug 10, 2026
6a05d6d
fix(cudf): address bounded execution review feedback
winningsix Aug 10, 2026
a118c84
Merge remote-tracking branch 'origin/dev' into codex/job144-community…
winningsix Aug 10, 2026
8b3e7aa
Merge remote-tracking branch 'origin/dev' into codex/job144-community…
winningsix Aug 10, 2026
4d16937
Merge remote-tracking branch 'origin/dev' into codex/pr39-refresh
winningsix Aug 10, 2026
9c1b4a5
fix(cudf): Preserve external function evaluator ABI
winningsix Aug 10, 2026
42c6b2b
perf(cudf): Avoid amplified join reclaim waves
winningsix Aug 10, 2026
20b680e
perf(cudf): Generalize replayable spill admission
winningsix Aug 11, 2026
906c31f
fix(cudf): Complete selective reader license headers
winningsix Aug 11, 2026
18485c9
style(cudf): Apply repository format hooks
winningsix Aug 11, 2026
c0c7c90
refactor(cudf): Own replayable spill admission in operator base
winningsix Aug 11, 2026
7b7b34e
refactor(cudf): align spill ownership with Velox bridges
winningsix Aug 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions velox/core/PlanNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2913,17 +2913,23 @@ TopNRowNumberNode::TopNRowNumberNode(
std::vector<SortOrder> sortingOrders,
const std::optional<std::string>& rowNumberColumnName,
int32_t limit,
PlanNodePtr source)
PlanNodePtr source,
bool partialOutput)
: PlanNode(std::move(id)),
function_(function),
partitionKeys_{std::move(partitionKeys)},
sortingKeys_{std::move(sortingKeys)},
sortingOrders_{std::move(sortingOrders)},
limit_{limit},
partialOutput_{partialOutput},
Comment thread
winningsix marked this conversation as resolved.
sources_{std::move(source)},
outputType_{getOptionalRowNumberOutputType(
sources_[0]->outputType(),
rowNumberColumnName)} {
VELOX_USER_CHECK(
!partialOutput_ || !rowNumberColumnName.has_value(),
"Partial TopNRowNumber output cannot include a rank column");

VELOX_USER_CHECK_EQ(
sortingKeys_.size(),
sortingOrders_.size(),
Expand Down Expand Up @@ -2978,6 +2984,7 @@ folly::dynamic TopNRowNumberNode::serialize() const {
obj["rowNumberColumnName"] = outputType_->names().back();
}
obj["limit"] = limit_;
obj["partialOutput"] = partialOutput_;
return obj;
}

Expand Down Expand Up @@ -3011,7 +3018,8 @@ PlanNodePtr TopNRowNumberNode::create(
sortingOrders,
rowNumberColumnName,
obj["limit"].asInt(),
source);
source,
obj.count("partialOutput") ? obj["partialOutput"].asBool() : false);
}

void LocalMergeNode::addDetails(std::stringstream& stream) const {
Expand Down
20 changes: 18 additions & 2 deletions velox/core/PlanNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -5986,7 +5986,8 @@ class TopNRowNumberNode : public PlanNode {
std::vector<SortOrder> sortingOrders,
const std::optional<std::string>& rowNumberColumnName,
int32_t limit,
PlanNodePtr source);
PlanNodePtr source,
bool partialOutput = false);
Comment thread
winningsix marked this conversation as resolved.

class Builder {
public:
Expand All @@ -6004,6 +6005,7 @@ class TopNRowNumberNode : public PlanNode {
VELOX_CHECK_EQ(other.sources().size(), 1);
source_ = other.sources()[0];
function_ = other.rankFunction();
partialOutput_ = other.partialOutput();
}

Builder& id(PlanNodeId id) {
Expand Down Expand Up @@ -6047,6 +6049,11 @@ class TopNRowNumberNode : public PlanNode {
return *this;
}

Builder& partialOutput(bool partialOutput) {
partialOutput_ = partialOutput;
return *this;
}

std::shared_ptr<TopNRowNumberNode> build() const {
VELOX_USER_CHECK(id_.has_value(), "TopNRowNumberNode id is not set");
VELOX_USER_CHECK(
Expand All @@ -6073,7 +6080,8 @@ class TopNRowNumberNode : public PlanNode {
sortingOrders_.value(),
rowNumberColumnName_.value(),
limit_.value(),
source_.value());
source_.value(),
partialOutput_);
}

private:
Expand All @@ -6085,6 +6093,7 @@ class TopNRowNumberNode : public PlanNode {
std::optional<std::optional<std::string>> rowNumberColumnName_;
std::optional<int32_t> limit_;
std::optional<PlanNodePtr> source_;
bool partialOutput_{false};
};

const std::vector<PlanNodePtr>& sources() const override {
Expand Down Expand Up @@ -6130,6 +6139,12 @@ class TopNRowNumberNode : public PlanNode {
return outputType_->size() > sources_[0]->outputType()->size();
}

/// Partial TopN may emit one reduced candidate set per input batch. A
/// downstream final TopN performs the globally exact reduction.
bool partialOutput() const {
return partialOutput_;
}

std::string_view name() const override {
return "TopNRowNumber";
}
Expand All @@ -6149,6 +6164,7 @@ class TopNRowNumberNode : public PlanNode {
const std::vector<SortOrder> sortingOrders_;

const int32_t limit_;
const bool partialOutput_;

const std::vector<PlanNodePtr> sources_;

Expand Down
4 changes: 2 additions & 2 deletions velox/core/QueryConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ class QueryConfig {
ucxPartitionedOutputBatchRows,
"cudf.partitioned_output_batch_rows",
int64_t,
10'000,
32'000'000,
Comment thread
winningsix marked this conversation as resolved.
"Target rows per CudfPartitionedOutput exchange chunk.")

/// Maximum input rows for one libcudf hash_partition call in UCX output.
Expand Down Expand Up @@ -395,7 +395,7 @@ class QueryConfig {
ucxPartitionedOutputBatchBytes,
"cudf.partitioned_output_batch_bytes",
uint64_t,
0,
128UL << 20,
"Target bytes per CudfPartitionedOutput exchange chunk.")

VELOX_QUERY_CONFIG(
Expand Down
4 changes: 4 additions & 0 deletions velox/core/tests/PlanNodeBuilderTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1346,4 +1346,8 @@ TEST_F(PlanNodeBuilderTest, topNRowNumberNode) {

const auto node2 = TopNRowNumberNode::Builder(*node).build();
verify(node2);

VELOX_ASSERT_THROW(
TopNRowNumberNode::Builder(*node).partialOutput(true).build(),
"Partial TopNRowNumber output cannot include a rank column");
}
1 change: 1 addition & 0 deletions velox/core/tests/QueryConfigTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ TEST_F(QueryConfigTest, emptyConfig) {

ASSERT_FALSE(config.isLegacyCast());
EXPECT_EQ(config.maxNumSplitsListenedTo(), 0);
EXPECT_EQ(config.ucxPartitionedOutputBatchBytes(), 128UL << 20);
}

TEST_F(QueryConfigTest, setConfig) {
Expand Down
6 changes: 4 additions & 2 deletions velox/exec/ExchangeClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,10 @@ void ExchangeClient::close() {

folly::F14FastMap<std::string, RuntimeMetric> ExchangeClient::stats() {
std::lock_guard<std::mutex> l(queue_->mutex());
if (stats_.empty()) {
stats_ = collectStatsLocked();
if (!closed_) {
// Sources are still progressing. Do not freeze the first snapshot: the
// exchange operator samples these stats while consuming pages.
return collectStatsLocked();
}
return stats_;
}
Expand Down
8 changes: 2 additions & 6 deletions velox/exec/HashJoinBridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -309,12 +309,8 @@ std::optional<HashJoinBridge::HashBuildResult> HashJoinBridge::tableOrFuture(
restoringSpillShards_.empty()));
probeStarted_ = true;
tableSpillFunc_ = nullptr;
if (buildResult_.has_value()) {
return buildResult_.value();
}
promises_.emplace_back("HashJoinBridge::tableOrFuture");
*future = promises_.back().getSemiFuture();
return std::nullopt;
return resultOrFutureLocked(
buildResult_, future, "HashJoinBridge::tableOrFuture");
}

void HashJoinBridge::probeFinished(bool restart) {
Expand Down
22 changes: 22 additions & 0 deletions velox/exec/JoinBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
*/
#pragma once

#include "velox/common/base/Exceptions.h"
#include "velox/common/future/VeloxPromise.h"

#include <optional>

namespace facebook::velox::exec {

class JoinBridge {
Expand All @@ -34,6 +37,25 @@ class JoinBridge {
protected:
static void notify(std::vector<ContinuePromise> promises);

/// Returns a bridge result or registers a waiter. The caller must hold
/// mutex_. Keeping this transition in JoinBridge gives CPU and accelerator
/// bridges the same start/cancel/future contract without constraining the
/// result payload type.
template <typename Result>
std::optional<Result> resultOrFutureLocked(
std::optional<Result> result,
ContinueFuture* future,
const char* waitReason) {
VELOX_CHECK(started_);
VELOX_CHECK(!cancelled_, "Getting join result after join is aborted");
if (result.has_value()) {
return result;
}
promises_.emplace_back(waitReason);
*future = promises_.back().getSemiFuture();
return std::nullopt;
}

std::mutex mutex_;
bool started_{false};
std::vector<ContinuePromise> promises_;
Expand Down
6 changes: 3 additions & 3 deletions velox/exec/Operator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,7 @@ bool Operator::MemoryReclaimer::reclaimableBytes(
if (FOLLY_UNLIKELY(driver == nullptr)) {
return false;
}
VELOX_CHECK_EQ(pool.name(), op_->pool()->name());
VELOX_CHECK_EQ(pool.name(), reclaimPool()->name());
return op_->reclaimableBytes(reclaimableBytes);
}

Expand All @@ -754,7 +754,7 @@ uint64_t Operator::MemoryReclaimer::reclaim(
if (!op_->canReclaim()) {
return 0;
}
VELOX_CHECK_EQ(pool->name(), op_->pool()->name());
VELOX_CHECK_EQ(pool->name(), reclaimPool()->name());
VELOX_CHECK(
!driver->state().isOnThread() || driver->state().suspended() ||
driver->state().isTerminated,
Expand Down Expand Up @@ -810,7 +810,7 @@ void Operator::MemoryReclaimer::abort(
if (driver == nullptr) {
return;
}
VELOX_CHECK_EQ(pool->name(), op_->pool()->name());
VELOX_CHECK_EQ(pool->name(), reclaimPool()->name());
VELOX_CHECK(
!driver->state().isOnThread() || driver->state().suspended() ||
driver->state().isTerminated);
Expand Down
8 changes: 8 additions & 0 deletions velox/exec/Operator.h
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,14 @@ class Operator : public BaseRuntimeStatWriter {
VELOX_CHECK_NOT_NULL(op_);
}

/// Returns the pool whose arbitration this reclaimer serves. The default
/// preserves the one-operator/one-pool contract. Resource-specific
/// operator bases may override this for a leaf in their own parallel pool
/// hierarchy without changing the default reclaimer factory API.
virtual memory::MemoryPool* reclaimPool() const {
return op_->pool();
}

// Gets the shared pointer to the associated driver to ensure the liveness
// of the operator during the memory reclaim operation.
//
Expand Down
56 changes: 0 additions & 56 deletions velox/exec/Spill.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -548,62 +548,6 @@ SpillPartition::createOrderedReader(
spillConfig.readBufferSize, pool, spillStats);
}

IterableSpillPartitionSet::IterableSpillPartitionSet() {
spillPartitionIter_ = spillPartitions_.begin();
}

void IterableSpillPartitionSet::insert(SpillPartitionSet&& spillPartitionSet) {
VELOX_CHECK(
!spillPartitionSet.empty(),
"Inserted spill partition set must not be empty.");

const auto parentId = spillPartitionSet.begin()->first.parentId();
if (!spillPartitions_.empty()) {
VELOX_CHECK(parentId.has_value());
VELOX_CHECK(spillPartitionIter_ != spillPartitions_.begin());
VELOX_CHECK_EQ(
std::prev(spillPartitionIter_)->first,
parentId.value(),
"Partition set does not have the same parent.");
spillPartitions_.erase(std::prev(spillPartitionIter_));
} else {
VELOX_CHECK(!parentId.has_value());
}

for (const auto& [id, partition] : spillPartitionSet) {
VELOX_CHECK_EQ(
id.parentId().value_or(SpillPartitionId()),
parentId.value_or(SpillPartitionId()));
spillPartitions_.emplace(id, std::make_unique<SpillPartition>(*partition));
}
spillPartitionIter_ = spillPartitions_.find(spillPartitionSet.begin()->first);
}

bool IterableSpillPartitionSet::hasNext() const {
return spillPartitionIter_ != spillPartitions_.end();
}

SpillPartition IterableSpillPartitionSet::next() {
VELOX_CHECK(hasNext(), "No more spill partitions to read.");
return *((spillPartitionIter_++)->second);
}

const SpillPartitionSet& IterableSpillPartitionSet::spillPartitions() const {
VELOX_CHECK(
!hasNext(),
"Spill partitions can only be extracted out after entire set is read.");
return spillPartitions_;
}

void IterableSpillPartitionSet::reset() {
spillPartitionIter_ = spillPartitions_.begin();
}

void IterableSpillPartitionSet::clear() {
spillPartitions_.clear();
spillPartitionIter_ = spillPartitions_.begin();
}

uint32_t FileSpillMergeStream::id() const {
VELOX_CHECK(!closed_);
return spillFile_->id();
Expand Down
Loading
Loading