diff --git a/.agents/invariants/PIPELINE_SCHEDULING_INVARIANT.md b/.agents/invariants/PIPELINE_SCHEDULING_INVARIANT.md index a462af1..506f1a3 100644 --- a/.agents/invariants/PIPELINE_SCHEDULING_INVARIANT.md +++ b/.agents/invariants/PIPELINE_SCHEDULING_INVARIANT.md @@ -18,17 +18,20 @@ Dynamic function inputs are stage-zero sources. Any operation that directly consumes one must belong to stage 0. A later stage may consume that data only through an explicit result forwarded by the preceding stage. -Each logical core belongs to exactly one stage capacity range before physical -placement. Those ranges cover every core but may have different sizes when the -initial partitioner predicts a lower maximum stage interval. Physical placement -may map a stage to arbitrary core IDs using the injected target topology. +PEFT assigns physical cores using the injected target topology and their actual +resident-weight capacity. Pipeline stage capacities cover every physical core +and may have different sizes when the partitioner predicts a lower maximum +stage interval. Before stage-local packing, the layout groups physical cores +by bidirectional link cost; stage cores need not have consecutive IDs. Packing +scores transfers on those physical links, and communication realization must +preserve the resulting core identities. Synchronization and deferred transfers consume the explicit stage identity; they must not infer it from a physical core number after placement. ## Ownership -Logical PEFT remains pipeline-agnostic. Stage partitioning is the first phase -of pipeline scheduling and owns this invariant. It must construct a valid +Physical PEFT supplies the initial placement. Stage partitioning is the first +phase of pipeline scheduling and owns stage adjacency. It must construct a valid operation-level partition before physical-core packing. Operations split for physical capacity retain one shared stage identity. Repacking may move work only within its assigned stage. Deferred-transfer planning and @@ -44,7 +47,7 @@ Before scheduled materialization, verify that: - every direct dynamic-function-input consumer belongs to stage 0; - every compute-graph edge stays within a stage or advances exactly one stage; - every stage-local resident-weight set fits its assigned physical core; and -- stage capacities cover all logical cores exactly once; and +- stage capacities cover all physical cores exactly once; and - physical placement is a permutation of all target cores. Pipeline scheduling tests must include an uneven physical-core layout and a diff --git a/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/DeferredCommunicationRealization.cpp b/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/DeferredCommunicationRealization.cpp index 350aeaf..422f08d 100644 --- a/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/DeferredCommunicationRealization.cpp +++ b/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/DeferredCommunicationRealization.cpp @@ -6,7 +6,6 @@ #include "DeferredCommunicationRealization.hpp" #include "DeferredCommunicationScheduling.hpp" #include "DeferredTransferPlanning.hpp" -#include "Scheduling/PeftScheduler.hpp" #include "src/Accelerators/PIM/Common/PimCommon.hpp" #include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp" @@ -34,76 +33,6 @@ static LogicalResult verifyNoEscapingRegionValues(Operation* owner, StringRef ph << escapingUser->getName() << " at " << escapingUser->getLoc(); } -static LogicalResult placeLogicalProcessorsOnPhysicalCores( - DeferredTransferPlan& plan, const SchedulingTarget& target, - size_t pipelineStages) { - std::vector logicalTrafficFlits(target.processorCount * target.processorCount, 0); - for (const std::unique_ptr& exchange : plan.exchanges) - for (const ExternalTransferFamily& transfer : exchange->external) { - auto fragmentType = dyn_cast(transfer.requirement->publicationFragmentType); - if (!fragmentType || !fragmentType.hasStaticShape()) - return exchange->deferred.emitOpError("physical core placement requires a static transfer fragment"); - auto fragmentBytes = pim::getCheckedShapedTypeSizeInBytes( - fragmentType, exchange->deferred, "physical core placement transfer fragment"); - if (failed(fragmentBytes)) - return failure(); - Cost flits = static_cast(*fragmentBytes) / target.transferWidthBytes - + (*fragmentBytes % target.transferWidthBytes != 0); - for (size_t index = 0; index < transfer.sourceCores.size(); ++index) { - size_t sourceLogicalProcessor = static_cast(transfer.sourceCores.valueAt(index)); - size_t targetLogicalProcessor = static_cast(transfer.targetCores.valueAt(index)); - Cost& traffic = logicalTrafficFlits[sourceLogicalProcessor * target.processorCount + targetLogicalProcessor]; - traffic = checkedAdd(traffic, flits); - } - } - - std::vector placementGroups; - if (pipelineStages > 1) { - if (plan.processorStages.size() != target.processorCount) - return failure(); - placementGroups = plan.processorStages; - } - std::vector physicalCoreForLogicalProcessor = - mapLogicalProcessorsToPhysicalCores( - logicalTrafficFlits, target, placementGroups); - auto getPhysicalCore = [&](int64_t logicalProcessor) { - assert(logicalProcessor >= 0 && static_cast(logicalProcessor) < physicalCoreForLogicalProcessor.size() - && "logical processor is outside the scheduling target"); - return static_cast(physicalCoreForLogicalProcessor[logicalProcessor]); - }; - auto remap = [&](StaticIntSequence& logicalProcessors) { - SmallVector physicalCores; - physicalCores.reserve(logicalProcessors.size()); - for (size_t index = 0; index < logicalProcessors.size(); ++index) - physicalCores.push_back(getPhysicalCore(logicalProcessors.valueAt(index))); - logicalProcessors = StaticIntSequence::fromValues(physicalCores); - }; - - for (ScheduledInfo& scheduled : plan.scheduled) { - for (int64_t& logicalProcessor : scheduled.cores) - logicalProcessor = getPhysicalCore(logicalProcessor); - if (isa(scheduled.op)) { - scheduled.op->setAttr( - kCoreIdAttrName, IntegerAttr::get(IntegerType::get(scheduled.op->getContext(), 32), scheduled.cores.front())); - } - else { - SmallVector physicalCores; - physicalCores.reserve(scheduled.cores.size()); - for (int64_t physicalCore : scheduled.cores) - physicalCores.push_back(static_cast(physicalCore)); - scheduled.op->setAttr(kCoreIdsAttrName, DenseI32ArrayAttr::get(scheduled.op->getContext(), physicalCores)); - } - } - for (const std::unique_ptr& produced : plan.producedStorage) - produced->core = getPhysicalCore(produced->core); - for (const std::unique_ptr& exchange : plan.exchanges) - for (ExternalTransferFamily& transfer : exchange->external) { - remap(transfer.sourceCores); - remap(transfer.targetCores); - } - return success(); -} - static LogicalResult replaceFinalGraphPublications(func::FuncOp funcOp, DeferredTransferPlan& plan) { for (Operation& op : funcOp.getOps()) { if (!isa(op)) @@ -227,9 +156,6 @@ LogicalResult realizeDeferredCommunication(func::FuncOp funcOp, funcOp, materialization, pipelineStages, target.processorCount); if (failed(transfers)) return funcOp.emitOpError("phase 2 failed to build symbolic transfer families"); - if (failed(placeLogicalProcessorsOnPhysicalCores( - *transfers, target, pipelineStages))) - return failure(); if (transfers->pipelineHostBufferBytes != 0) { auto bytes = pim::checkedCast( transfers->pipelineHostBufferBytes, funcOp, diff --git a/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/DeferredCommunicationScheduling.cpp b/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/DeferredCommunicationScheduling.cpp index 58f9a61..47c0790 100644 --- a/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/DeferredCommunicationScheduling.cpp +++ b/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/DeferredCommunicationScheduling.cpp @@ -285,7 +285,7 @@ FailureOr scheduleDeferredCommunication(func::FuncOp enqueue(stream); auto advance = [&] { bool changed = false; - while (!advanceable.empty()) { + while (!advanceable.empty() && ready.empty()) { unsigned stream = advanceable.front(); advanceable.pop(); StreamProgress& progress = streams[stream]; @@ -305,6 +305,8 @@ FailureOr scheduleDeferredCommunication(func::FuncOp ScheduledCommunicationPlan result; unsigned finishedGroups = 0; while (finishedGroups != groups.size()) { + while (!ready.empty() && groups[ready.top()].scheduled) + ready.pop(); bool progressed = advance(); std::optional chosen; unsigned bestExtension = 0; diff --git a/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/ScheduledComputeReport.cpp b/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/ScheduledComputeReport.cpp index 0d8e57b..4930a40 100644 --- a/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/ScheduledComputeReport.cpp +++ b/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/ScheduledComputeReport.cpp @@ -111,8 +111,16 @@ void dumpScheduledComputeReport(ModuleOp moduleOp, func::FuncOp funcOp, const Me << " materialized homogeneous runs: " << materializedRuns << "\n" << " largest run: " << largestRun << "\n" << " instances compacted: " << instancesCompacted << "\n" - << " compatible runs rejected: 0\n\n" - << "Materialized scheduled ops\n"; + << " compatible runs rejected: 0\n"; + llvm::MapVector> stageCores; + for (auto [core, stage] : llvm::enumerate(schedule.processorStages)) + stageCores[stage].push_back(core); + for (const auto &[stage, cores] : stageCores) { + os << " pipeline stage " << stage << " cores="; + printIndexedList(os, ArrayRef(cores)); + os << "\n"; + } + os << "\nMaterialized scheduled ops\n"; for (const ScheduledMaterializationRecord &record : records) { bool batch = isa(record.scheduledOp); diff --git a/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/ScheduledSpatialPasses.cpp b/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/ScheduledSpatialPasses.cpp index 4a95e58..18ba528 100644 --- a/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/ScheduledSpatialPasses.cpp +++ b/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/ScheduledSpatialPasses.cpp @@ -10,8 +10,6 @@ #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Passes/Analyses/ONNXToSpatialVerifier.hpp" #include "src/Accelerators/PIM/Passes/PIMPasses.h" -#include - using namespace mlir; namespace onnx_mlir { @@ -34,38 +32,6 @@ static FailureOr requireEntry(ModuleOp moduleOp) { return *entry; } -static SchedulingTarget getPipelineSchedulingTarget( - const SchedulingTarget& physicalTarget, size_t pipelineStages) { - if (pipelineStages == 1) - return physicalTarget; - - PipelineCoreLayout layout(physicalTarget.processorCount, pipelineStages); - SchedulingTarget schedulingTarget = physicalTarget; - schedulingTarget.processorCount = layout.getLogicalProcessorCount(); - schedulingTarget.residentWeightCapacity = checkedMultiply( - physicalTarget.residentWeightCapacity, pipelineStages); - schedulingTarget.interProcessorLatencyNs.assign( - schedulingTarget.processorCount * schedulingTarget.processorCount, 0); - Cost latencySum = 0; - size_t pairCount = 0; - for (size_t source = 0; source < schedulingTarget.processorCount; ++source) - for (size_t destination = 0; - destination < schedulingTarget.processorCount; ++destination) { - Cost latency = physicalTarget.getInterProcessorLatencyNs( - source, destination); - schedulingTarget.interProcessorLatencyNs[ - source * schedulingTarget.processorCount + destination] = latency; - if (source != destination) { - latencySum = checkedAdd(latencySum, latency); - ++pairCount; - } - } - schedulingTarget.averageInterProcessorLatencyNs = pairCount == 0 - ? 0 - : (latencySum + pairCount - 1) / pairCount; - return schedulingTarget; -} - struct ScheduleAndRealizeSpatialPass final : PassWrapper> { MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(ScheduleAndRealizeSpatialPass) @@ -92,9 +58,7 @@ struct ScheduleAndRealizeSpatialPass final PipelineCoreLayout pipelineLayout(target.processorCount, pipelineStages); if (!pipelineLayout.isValid() || (pipelineStages > 1 - && target.synchronizationRegisterCount == 0) - || target.residentWeightCapacity - > std::numeric_limits::max() / pipelineStages) { + && target.synchronizationRegisterCount == 0)) { moduleOp.emitError("ScheduleAndRealizeSpatial requires valid pipeline stages and resource counts"); signalPassFailure(); return; @@ -106,21 +70,15 @@ struct ScheduleAndRealizeSpatialPass final } func::FuncOp entryFunc = *entry; - SchedulingTarget schedulingTarget = getPipelineSchedulingTarget( - target, pipelineStages); ComputeGraph scheduledGraph; MergeScheduleResult schedule; for (;;) { - MergeSchedulingAnalysis analysis( - entryFunc, schedulingTarget, - pipelineStages > 1 ? target.processorCount : 0); - scheduledGraph = analysis.getGraph(); - schedule = std::move(analysis.getResult()); + scheduledGraph = buildComputeGraph(entryFunc, target); std::string pipelineError; if (pipelineStages > 1) { FailureOr preparation = preparePipelineWorkload( - scheduledGraph, schedule, pipelineStages, target, pipelineError); + scheduledGraph, pipelineStages, target, pipelineError); if (failed(preparation)) { moduleOp.emitError() << pipelineError; signalPassFailure(); @@ -129,6 +87,9 @@ struct ScheduleAndRealizeSpatialPass final if (*preparation == PipelineWorkloadPreparation::Changed) continue; } + MergeSchedulingAnalysis analysis( + scheduledGraph, target, entryFunc.getContext()); + schedule = std::move(analysis.getResult()); if (succeeded(applyPipelineScheduling( scheduledGraph, schedule, pipelineStages, target, pipelineError))) break; diff --git a/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/MergeSchedulingAnalysis.cpp b/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/MergeSchedulingAnalysis.cpp index f9433a1..7f635fc 100644 --- a/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/MergeSchedulingAnalysis.cpp +++ b/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/MergeSchedulingAnalysis.cpp @@ -88,22 +88,21 @@ void verifySchedule(const ComputeGraph& graph, } // namespace -MergeSchedulingAnalysis::MergeSchedulingAnalysis(mlir::Operation* op, +MergeSchedulingAnalysis::MergeSchedulingAnalysis(const ComputeGraph& computeGraph, const SchedulingTarget& schedulingTarget, - size_t partitionCount) -: entryOp(op), target(schedulingTarget), computePartitionCount(partitionCount) { + mlir::MLIRContext* context) +: context(context), target(schedulingTarget), graph(computeGraph) { result = run(); } MergeScheduleResult MergeSchedulingAnalysis::run() { - graph = buildComputeGraph(entryOp, target, computePartitionCount); if (!verifyAcyclic(graph)) llvm::report_fatal_error("merge scheduling: compute graph is cyclic"); MergeScheduleResult schedule = runPeftScheduler( graph, PeftScheduleOptions { target, - entryOp->getContext()}); + context}); verifySchedule(graph, schedule, target); return schedule; } diff --git a/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/MergeSchedulingAnalysis.hpp b/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/MergeSchedulingAnalysis.hpp index f22d65d..f5fba6b 100644 --- a/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/MergeSchedulingAnalysis.hpp +++ b/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/MergeSchedulingAnalysis.hpp @@ -11,17 +11,15 @@ namespace spatial { class MergeSchedulingAnalysis { public: - MergeSchedulingAnalysis(mlir::Operation* op, + MergeSchedulingAnalysis(const ComputeGraph& graph, const SchedulingTarget& target, - size_t computePartitionCount = 0); + mlir::MLIRContext* context); MergeScheduleResult& getResult() { return result; } - const ComputeGraph& getGraph() const { return graph; } private: - mlir::Operation* entryOp = nullptr; + mlir::MLIRContext* context = nullptr; const SchedulingTarget& target; - size_t computePartitionCount = 0; - ComputeGraph graph; + const ComputeGraph& graph; MergeScheduleResult result; MergeScheduleResult run(); diff --git a/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/PeftScheduler.cpp b/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/PeftScheduler.cpp index 00814f4..a298731 100644 --- a/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/PeftScheduler.cpp +++ b/src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/PeftScheduler.cpp @@ -5,7 +5,6 @@ #include "llvm/Support/FormatVariadic.h" #include -#include #include #include #include @@ -42,11 +41,6 @@ struct TopologyModel { } }; -Time getAverageTransferTime(const TransferCost& transferCost, const SchedulingTarget& target) { - return checkedAdd(transferCost.fixed, - checkedMultiply(transferCost.networkFlits, target.averageInterProcessorLatencyNs)); -} - std::vector> buildReverseLevels(const ComputeGraph& graph) { std::vector remainingSuccessors(graph.nodes.size(), 0); std::queue readySinks; @@ -83,24 +77,6 @@ std::vector> buildReverseLevels(const ComputeGraph& graph) { return reverseLevels; } -void verifyOctTableSize(size_t nodeCount, size_t processorCount) { - constexpr size_t kMaxOctTableBytes = 1ull << 35; - if (nodeCount == 0 || processorCount == 0) - return; - if (processorCount > std::numeric_limits::max() / sizeof(Time)) - llvm::report_fatal_error("PEFT scheduler: OCT table size overflow"); - size_t rowBytes = processorCount * sizeof(Time); - if (nodeCount > std::numeric_limits::max() / rowBytes) - llvm::report_fatal_error("PEFT scheduler: OCT table size overflow"); - size_t totalBytes = nodeCount * rowBytes; - if (totalBytes > kMaxOctTableBytes) { - std::string message = llvm::formatv("PEFT scheduler: OCT table would require {0} MiB, exceeding the 1024 MiB guard", - totalBytes / (1024 * 1024)) - .str(); - llvm::report_fatal_error(llvm::StringRef(message)); - } -} - bool hasHighResidentWeightPressure(const ComputeGraph& graph, size_t processorCount, size_t residentWeightCapacity) { if (residentWeightCapacity > std::numeric_limits::max() / processorCount) return false; @@ -243,79 +219,6 @@ FailureOr buildLanePublicationSignatures(SpatComputeB } // namespace -std::vector mapLogicalProcessorsToPhysicalCores(ArrayRef logicalTrafficFlits, - const SchedulingTarget& target, - ArrayRef placementGroups) { - const size_t processorCount = target.processorCount; - assert(logicalTrafficFlits.size() == processorCount * processorCount - && "logical traffic matrix must cover every processor pair"); - assert((placementGroups.empty() || placementGroups.size() == processorCount) - && "physical placement groups must cover every processor"); - std::vector physicalCoreForLogicalProcessor(processorCount); - std::iota(physicalCoreForLogicalProcessor.begin(), physicalCoreForLogicalProcessor.end(), 0); - - auto transferCost = [&](size_t sourceLogicalProcessor, - size_t targetLogicalProcessor, - size_t sourcePhysicalCore, - size_t targetPhysicalCore) { - Cost traffic = logicalTrafficFlits[sourceLogicalProcessor * processorCount + targetLogicalProcessor]; - return checkedMultiply(traffic, target.getInterProcessorLatencyNs(sourcePhysicalCore, targetPhysicalCore)); - }; - - for (size_t logicalProcessor = 0; logicalProcessor < processorCount; ++logicalProcessor) { - size_t bestPeerLogicalProcessor = logicalProcessor; - Cost bestSaving = 0; - for (size_t peerLogicalProcessor = 0; peerLogicalProcessor < processorCount; ++peerLogicalProcessor) { - if (peerLogicalProcessor == logicalProcessor) - continue; - if (!placementGroups.empty() - && placementGroups[peerLogicalProcessor] - != placementGroups[logicalProcessor]) - continue; - size_t physicalCore = physicalCoreForLogicalProcessor[logicalProcessor]; - size_t peerPhysicalCore = physicalCoreForLogicalProcessor[peerLogicalProcessor]; - Cost currentCost = 0; - Cost swappedCost = 0; - for (size_t otherLogicalProcessor = 0; otherLogicalProcessor < processorCount; ++otherLogicalProcessor) { - if (otherLogicalProcessor == logicalProcessor || otherLogicalProcessor == peerLogicalProcessor) - continue; - size_t otherPhysicalCore = physicalCoreForLogicalProcessor[otherLogicalProcessor]; - currentCost = checkedAdd( - currentCost, transferCost(logicalProcessor, otherLogicalProcessor, physicalCore, otherPhysicalCore)); - currentCost = checkedAdd( - currentCost, transferCost(otherLogicalProcessor, logicalProcessor, otherPhysicalCore, physicalCore)); - currentCost = checkedAdd( - currentCost, transferCost(peerLogicalProcessor, otherLogicalProcessor, peerPhysicalCore, otherPhysicalCore)); - currentCost = checkedAdd( - currentCost, transferCost(otherLogicalProcessor, peerLogicalProcessor, otherPhysicalCore, peerPhysicalCore)); - swappedCost = checkedAdd( - swappedCost, transferCost(logicalProcessor, otherLogicalProcessor, peerPhysicalCore, otherPhysicalCore)); - swappedCost = checkedAdd( - swappedCost, transferCost(otherLogicalProcessor, logicalProcessor, otherPhysicalCore, peerPhysicalCore)); - swappedCost = checkedAdd( - swappedCost, transferCost(peerLogicalProcessor, otherLogicalProcessor, physicalCore, otherPhysicalCore)); - swappedCost = checkedAdd( - swappedCost, transferCost(otherLogicalProcessor, peerLogicalProcessor, otherPhysicalCore, physicalCore)); - } - currentCost = - checkedAdd(currentCost, transferCost(logicalProcessor, peerLogicalProcessor, physicalCore, peerPhysicalCore)); - currentCost = - checkedAdd(currentCost, transferCost(peerLogicalProcessor, logicalProcessor, peerPhysicalCore, physicalCore)); - swappedCost = - checkedAdd(swappedCost, transferCost(logicalProcessor, peerLogicalProcessor, peerPhysicalCore, physicalCore)); - swappedCost = - checkedAdd(swappedCost, transferCost(peerLogicalProcessor, logicalProcessor, physicalCore, peerPhysicalCore)); - if (currentCost > swappedCost && currentCost - swappedCost > bestSaving) { - bestSaving = currentCost - swappedCost; - bestPeerLogicalProcessor = peerLogicalProcessor; - } - } - std::swap(physicalCoreForLogicalProcessor[logicalProcessor], - physicalCoreForLogicalProcessor[bestPeerLogicalProcessor]); - } - return physicalCoreForLogicalProcessor; -} - MergeScheduleResult runPeftScheduler(const ComputeGraph& graph, const PeftScheduleOptions& options) { const size_t nodeCount = graph.nodes.size(); const size_t processorCount = options.target.processorCount; @@ -327,36 +230,17 @@ MergeScheduleResult runPeftScheduler(const ComputeGraph& graph, const PeftSchedu std::vector capacityReservations = planResidentWeightReservations( graph, processorCount, options.target.residentWeightCapacity, topology, preferWeightReuse); - verifyOctTableSize(nodeCount, processorCount); std::vector> reverseLevels = buildReverseLevels(graph); - // MOCK: Replace this with your actual heterogeneous cost lookup. - // If graph.nodes[task] is modified to hold a vector of costs per processor, access it here. - auto getComputeCost = [&](size_t task, size_t processor) -> Time { return graph.nodes[task].cost; }; - std::vector