better throughput scheduling
Validate Operations / validate-operations (push) Failing after 3h13m5s

update results_comparison.csv
This commit is contained in:
NiccoloN
2026-10-19 22:11:51 +02:00
parent 034c197260
commit 48e9fb3795
21 changed files with 1636 additions and 259 deletions
+372 -5
View File
@@ -149,7 +149,8 @@ static void testBatchedReceiveForwarding() {
static void testResidentWeightViewCost() {
mlir::MLIRContext context(mlir::MLIRContext::Threading::DISABLED);
context.loadDialect<SpatialDialect, mlir::tensor::TensorDialect>();
context.loadDialect<SpatialDialect, mlir::tensor::TensorDialect,
mlir::arith::ArithDialect, mlir::scf::SCFDialect>();
mlir::Block operands;
mlir::OwningOpRef<mlir::ModuleOp> owner =
mlir::ModuleOp::create(mlir::UnknownLoc::get(&context));
@@ -161,7 +162,7 @@ static void testResidentWeightViewCost() {
auto input = operands.addArgument(inputType, loc);
auto wideType = mlir::RankedTensorType::get({1, 256}, builder.getF32Type());
auto wideInput = operands.addArgument(wideType, loc);
auto makeCompute = [&](bool weightView, unsigned inputView) {
auto makeCompute = [&](bool weightView, unsigned inputView, int64_t iterations = 0) {
builder.setInsertionPointToEnd(owner->getBody());
auto compute = SpatGraphCompute::create(builder, loc,
mlir::TypeRange{inputType}, mlir::ValueRange{weight},
@@ -186,14 +187,42 @@ static void testResidentWeightViewCost() {
w = slice(w, weightType);
if (inputView)
x = slice(x, inputType, inputView == 2);
auto result = SpatVMMOp::create(builder, loc, inputType, w, x);
SpatYieldOp::create(builder, loc, mlir::ValueRange{result.getResult()});
mlir::scf::ForOp loop;
if (iterations) {
auto lower = mlir::arith::ConstantIndexOp::create(builder, loc, 0);
auto upper = mlir::arith::ConstantIndexOp::create(builder, loc, iterations);
auto step = mlir::arith::ConstantIndexOp::create(builder, loc, 1);
loop = mlir::scf::ForOp::create(builder, loc, lower, upper, step, mlir::ValueRange{x});
builder.setInsertionPointToStart(loop.getBody());
x = loop.getRegionIterArg(0);
}
mlir::Value result = SpatVMMOp::create(builder, loc, inputType, w, x);
if (loop) {
mlir::scf::YieldOp::create(builder, loc, mlir::ValueRange{result});
builder.setInsertionPointAfter(loop);
result = loop.getResult(0);
}
SpatYieldOp::create(builder, loc, mlir::ValueRange{result});
return ComputeInstance{compute, 0, 1};
};
auto direct = makeCompute(false, false);
auto weightView = makeCompute(true, false);
auto inputView = makeCompute(false, true);
auto stridedInput = makeCompute(false, 2);
auto invariantLoop = makeCompute(false, false, 1000000000);
assert(collectDistinctResidentWeights(invariantLoop.op, 0).size() == 1);
Cost loopBodyCost = getComputeInstanceCost(invariantLoop, SchedulingTarget{}) / 1000000000;
auto loop = *mlir::cast<SpatGraphCompute>(invariantLoop.op)
.getBody().front().getOps<mlir::scf::ForOp>().begin();
builder.setInsertionPoint(loop);
auto lower = mlir::arith::ConstantIndexOp::create(builder, loc, std::numeric_limits<int64_t>::min());
auto upper = mlir::arith::ConstantIndexOp::create(builder, loc, std::numeric_limits<int64_t>::max());
auto step = mlir::arith::ConstantIndexOp::create(builder, loc, std::numeric_limits<int64_t>::max());
loop.getLowerBoundMutable().assign(lower.getResult());
loop.getUpperBoundMutable().assign(upper.getResult());
loop.getStepMutable().assign(step.getResult());
assert(getComputeInstanceCost(invariantLoop, SchedulingTarget{})
== 3 * loopBodyCost);
for (Cost readLatency : {1, 20}) {
SchedulingTarget target;
target.localMemoryReadLatencyCycles = readLatency;
@@ -373,6 +402,53 @@ static void testScalarProjectedTransferCost() {
}
}
static void testStridedPublicationMemory() {
using namespace mlir;
MLIRContext context(MLIRContext::Threading::DISABLED);
context.loadDialect<SpatialDialect, func::FuncDialect, tensor::TensorDialect>();
auto module = ModuleOp::create(UnknownLoc::get(&context));
OpBuilder builder(module.getBodyRegion());
auto loc = module.getLoc();
auto image = RankedTensorType::get({2, 16}, builder.getF32Type());
auto fragment = RankedTensorType::get({2, 8}, builder.getF32Type());
auto function = func::FuncOp::create(builder, loc, "strided_publication",
builder.getFunctionType({image}, {}));
builder.setInsertionPointToStart(function.addEntryBlock());
auto producer = onnx_mlir::createEmptySpatGraphCompute(builder, loc,
TypeRange{image}, ValueRange{}, ValueRange{function.getArgument(0)});
builder.setInsertionPointToEnd(&producer.getBody().front());
Value value = SpatReluOp::create(builder, loc, image, *producer.getInputArgument(0));
SpatYieldOp::create(builder, loc, ValueRange{value});
builder.setInsertionPointAfter(producer);
for (unsigned reader = 0; reader < 2; ++reader) {
auto consumer = onnx_mlir::createEmptySpatGraphCompute(builder, loc,
TypeRange{fragment}, ValueRange{}, ValueRange{producer.getResult(0)});
builder.setInsertionPointToEnd(&consumer.getBody().front());
value = tensor::ExtractSliceOp::create(builder, loc, fragment,
*consumer.getInputArgument(0),
SmallVector<OpFoldResult>(2, builder.getIndexAttr(0)),
SmallVector<OpFoldResult>{builder.getIndexAttr(2), builder.getIndexAttr(8)},
SmallVector<OpFoldResult>(2, builder.getIndexAttr(1)));
value = SpatReluOp::create(builder, loc, fragment, value);
SpatYieldOp::create(builder, loc, ValueRange{value});
builder.setInsertionPointAfter(consumer);
}
func::ReturnOp::create(builder, loc);
for (Cost width : {4, 32}) {
SchedulingTarget target;
target.processorCount = 3;
target.sharedMemoryWidthBytes = width;
target.sharedMemoryReadLatencyNs = 7;
target.sharedMemoryWriteLatencyNs = 11;
auto graph = buildCheckedComputeGraph(function, target);
auto demand = getPipelineBoundaryMemoryDemand(graph, {0, 1, 1}, target);
assert(demand.readBytes == 32 && demand.writeBytes == 32);
assert(demand.readRequests == 2 && demand.writeRequests == 4);
assert(demand.serviceNs == (width == 4 ? 144 : 58));
}
module.erase();
}
static void testProducerChunkTransferCost() {
using namespace mlir;
MLIRContext context(MLIRContext::Threading::DISABLED);
@@ -908,6 +984,100 @@ static void testMaterializedPeftGapOrder() {
}
}
static void checkPublicationMemoryTraffic(mlir::func::FuncOp function,
const ComputeGraph &graph, const SchedulingTarget &target,
mlir::PatternRewriter &builder) {
MergeScheduleResult schedule;
schedule.processorCount = target.processorCount;
schedule.processorStages = {0, 0, 1, 1};
std::vector<std::vector<size_t>> tasks(4);
std::vector<size_t> stages;
for (auto [task, node] : llvm::enumerate(graph.nodes)) {
bool producer = node.instance.op == graph.nodes.front().instance.op;
size_t core = producer ? node.instance.laneStart % 2 : 2 + task % 2;
stages.push_back(producer ? 0 : 1);
schedule.dominanceOrderCompute.push_back(node.instance);
schedule.computeToCpuMap[node.instance] = core;
schedule.computeToCpuSlotMap[node.instance] = tasks[core].size();
tasks[core].push_back(task);
}
auto expected = getPipelineBoundaryMemoryDemand(graph, stages, target);
populateScheduleEquivalenceClasses(graph, tasks, schedule);
auto materialized = materializeScheduledCompute(function, schedule, builder);
assert(mlir::succeeded(materialized));
auto plan = buildDeferredTransferPlan(function, *materialized, 2, 4);
assert(mlir::succeeded(plan));
SharedMemoryDemand actual;
llvm::DenseSet<int64_t> stored;
for (const auto &exchange : plan->exchanges)
for (const auto &transfer : exchange->external)
if (transfer.hostRouted) {
auto type = mlir::cast<mlir::ShapedType>(transfer.requirement->publicationFragmentType);
Cost bytes = (type.getNumElements() * target.computeBitwidth + 7) / 8;
Cost requests = transfer.targetLanes.size();
actual += target.getSharedMemoryDemand(bytes, requests, false);
for (size_t index = 0; index < requests; ++index)
if (stored.insert(transfer.hostOffsets.valueAt(index)).second)
actual += target.getSharedMemoryDemand(bytes, 1, true);
}
assert(actual.readBytes == expected.readBytes && actual.writeBytes == expected.writeBytes);
assert(actual.readRequests == expected.readRequests && actual.writeRequests == expected.writeRequests);
assert(actual.serviceNs == expected.serviceNs);
for (unsigned registers : {3u, 16u}) {
auto shared = buildDeferredTransferPlan(function, *materialized, 2, 4);
assert(mlir::succeeded(shared));
auto communication = scheduleDeferredCommunication(function, *shared);
assert(mlir::succeeded(communication));
auto boundaries = buildDeferredBoundaryPlan(*shared, *communication, registers);
assert(mlir::succeeded(boundaries));
size_t stores = 0, signals = 0, readyWaits = 0, freeWaits = 0, releases = 0;
llvm::DenseSet<int64_t> published;
for (const auto &boundary : boundaries->boundaries)
for (const auto &instruction : boundary.instructions) {
if (auto send = std::get_if<EmitSendRun>(&instruction))
for (const auto &slice : send->slices)
if (slice.family->hostRouted)
for (size_t offset = 0; offset < slice.transferCount; ++offset) {
size_t index = slice.familyOffset + offset;
bool first = published.insert(slice.family->hostOffsets.valueAt(index)).second;
assert(bool(slice.family->hostStoreFlags.valueAt(index)) == first);
stores += first;
++signals;
}
if (auto wait = std::get_if<EmitHostReuseWait>(&instruction))
for (auto interval : wait->lanes.intervals())
for (unsigned lane = interval.begin; lane < interval.end; ++lane) {
assert(wait->eventRegisters.valueAt(lane) < registers);
freeWaits += wait->waitValues.valueAt(lane);
}
}
for (const auto &exchange : shared->exchanges)
for (const auto &family : exchange->external)
if (family.hostRouted)
for (size_t index = 0; index < family.targetCores.size(); ++index) {
readyWaits += family.waitValues.valueAt(index);
assert(family.eventRegisters.valueAt(index) < registers);
}
for (const auto &[reader, releaseSignals] : shared->hostReleaseSignals) {
llvm::DenseSet<int64_t> writers;
for (const auto &release : releaseSignals) {
assert(writers.insert(release.writerCore).second);
assert(release.eventRegister < registers);
++releases;
}
}
assert(stores == expected.writeRequests && signals == expected.readRequests);
assert(readyWaits == signals && freeWaits == releases && releases == 4);
}
auto insufficient = buildDeferredTransferPlan(function, *materialized, 2, 4);
assert(mlir::succeeded(insufficient));
auto communication = scheduleDeferredCommunication(function, *insufficient);
assert(mlir::succeeded(communication));
mlir::ScopedDiagnosticHandler suppress(function.getContext(),
[](mlir::Diagnostic &) { return mlir::success(); });
assert(mlir::failed(buildDeferredBoundaryPlan(*insufficient, *communication, 2)));
}
static void testPhysicalPublicationDependencies() {
using namespace mlir;
MLIRContext context(MLIRContext::Threading::DISABLED);
@@ -923,7 +1093,7 @@ static void testPhysicalPublicationDependencies() {
Block *body = function.addEntryBlock();
builder.setInsertionPointToStart(body);
auto makeBatch = [&](Value input, uint32_t lanes, int64_t projectionSize,
bool reverse) {
bool reverse, int64_t iterations = 0, bool invariantProjection = false) {
auto inputType = cast<RankedTensorType>(input.getType());
auto fragment = projectionSize
? RankedTensorType::get({projectionSize, 1}, builder.getF32Type())
@@ -934,6 +1104,16 @@ static void testPhysicalPublicationDependencies() {
assert(succeeded(batch));
Value lane = *batch->getLaneArgument();
Value value = *batch->getInputArgument(0);
scf::ForOp loop;
if (iterations) {
Value lower = arith::ConstantIndexOp::create(builder, loc, 0);
Value upper = arith::ConstantIndexOp::create(builder, loc, iterations);
Value step = arith::ConstantIndexOp::create(builder, loc, 1);
Value empty = tensor::EmptyOp::create(builder, loc, fragment.getShape(), builder.getF32Type());
loop = scf::ForOp::create(builder, loc, lower, upper, step, ValueRange{empty});
builder.setInsertionPointToStart(loop.getBody());
lane = invariantProjection ? lower : loop.getInductionVar();
}
if (projectionSize) {
Value size = arith::ConstantIndexOp::create(builder, loc, projectionSize);
Value offset = arith::MulIOp::create(builder, loc, lane, size);
@@ -944,6 +1124,12 @@ static void testPhysicalPublicationDependencies() {
SmallVector<OpFoldResult>(2, builder.getIndexAttr(1)));
}
value = SpatReluOp::create(builder, loc, fragment, value);
if (loop) {
scf::YieldOp::create(builder, loc, ValueRange{value});
builder.setInsertionPointAfter(loop);
value = loop.getResult(0);
lane = *batch->getLaneArgument();
}
Value slot = lane;
if (reverse) {
Value last = arith::ConstantIndexOp::create(builder, loc, lanes - 1);
@@ -965,6 +1151,8 @@ static void testPhysicalPublicationDependencies() {
auto producer = makeBatch(body->getArgument(0), 4, 0, true);
auto projected = makeBatch(producer.getResult(0), 2, 2, false);
auto conservative = makeBatch(producer.getResult(0), 2, 0, false);
auto looped = makeBatch(producer.getResult(0), 1, 2, false, 2);
auto repeated = makeBatch(producer.getResult(0), 1, 2, false, 2, true);
SmallVector<onnx_mlir::FragmentAssemblyEntry> entries{
{0, 0, {0}, {1}}, {2, 0, {1}, {1}}};
auto logicalType = RankedTensorType::get({2}, builder.getF32Type());
@@ -1004,7 +1192,19 @@ static void testPhysicalPublicationDependencies() {
SchedulingTarget target;
target.processorCount = 4;
target.processorPeriodNs = period;
target.sharedMemoryWidthBytes = 32;
target.sharedMemoryReadLatencyNs = 7;
target.sharedMemoryWriteLatencyNs = 11;
ComputeGraph graph = buildCheckedComputeGraph(function, target);
std::vector<size_t> memoryStages(graph.nodes.size(), 1);
for (size_t task = 0; task < graph.nodes.size(); ++task)
if (graph.nodes[task].instance.op == producer)
memoryStages[task] = 0;
auto memory = getPipelineBoundaryMemoryDemand(graph, memoryStages, target);
assert(memory.readBytes == 26 && memory.writeBytes == 4);
assert(memory.readRequests == 26 && memory.writeRequests == 4);
assert(memory.serviceNs == 26 * 7 + 4 * 11);
assert(graph.baselineMemory.readBytes == 4 && graph.baselineMemory.readRequests == 4);
auto predecessorLanes = [&](ComputeInstance instance) {
std::vector<uint32_t> lanes;
for (const auto &[task, cost] : graph.predecessors[graph.instanceToIndex.lookup(instance)])
@@ -1016,6 +1216,8 @@ static void testPhysicalPublicationDependencies() {
assert((predecessorLanes({projected, 1, 1}) == std::vector<uint32_t>{0, 1}));
for (uint32_t lane : {0, 1})
assert((predecessorLanes({conservative, lane, 1}) == std::vector<uint32_t>{0, 1, 2, 3}));
assert((predecessorLanes({looped, 0, 1}) == std::vector<uint32_t>{0, 1, 2, 3}));
assert((predecessorLanes({repeated, 0, 1}) == std::vector<uint32_t>{2, 3}));
assert((predecessorLanes({scalar, 0, 1}) == std::vector<uint32_t>{1, 3}));
// Two local fragment copies and the vector operation use the same ns unit.
assert(graph.nodes[graph.instanceToIndex.lookup({scalar, 0, 1})].cost == 10 * period);
@@ -1031,8 +1233,24 @@ static void testPhysicalPublicationDependencies() {
assert(dependencies.size() == 1);
assert(dependencies.front().second.fixed == 8 * period);
assert(dependencies.front().second.networkFlits == 12);
if (period == 3)
checkPublicationMemoryTraffic(function, graph, target, builder);
}
builder.setInsertionPointToEnd(owner->getBody());
auto largeLoop = func::FuncOp::create(builder, loc, "invariant_loop",
builder.getFunctionType({fragmentType}, {}));
builder.setInsertionPointToStart(largeLoop.addEntryBlock());
auto loopProducer = makeBatch(largeLoop.getArgument(0), 4, 0, false);
makeBatch(loopProducer.getResult(0), 1, 2, false, 1000000000, true);
func::ReturnOp::create(builder, loc);
SchedulingTarget loopTarget;
loopTarget.processorCount = 4;
auto compactGraph = buildCheckedComputeGraph(largeLoop, loopTarget);
assert(compactGraph.publicationTransfers.size() == 2);
for (const auto &transfer : compactGraph.publicationTransfers)
assert(transfer.count == 1000000000);
builder.setInsertionPointToEnd(owner->getBody());
auto independent = func::FuncOp::create(builder, loc, "batch_order",
builder.getFunctionType({fragmentType}, {}));
@@ -1303,6 +1521,152 @@ static void testPipelineInputArrival() {
}
}
static void testBaselineConstantMemory() {
using namespace mlir;
MLIRContext context(MLIRContext::Threading::DISABLED);
context.loadDialect<SpatialDialect, func::FuncDialect, arith::ArithDialect,
tensor::TensorDialect, scf::SCFDialect>();
OwningOpRef<ModuleOp> module = ModuleOp::create(UnknownLoc::get(&context));
OpBuilder builder(module->getBodyRegion());
auto loc = module->getLoc();
auto type = RankedTensorType::get({16}, builder.getF32Type());
auto function = func::FuncOp::create(builder, loc, "constant_memory",
builder.getFunctionType({type}, {type}));
builder.setInsertionPointToStart(function.addEntryBlock());
Value external = arith::ConstantOp::create(builder, loc, type,
DenseElementsAttr::get(type, builder.getF32FloatAttr(2.5)));
auto compute = onnx_mlir::createEmptySpatGraphCompute(builder, loc,
TypeRange{type}, ValueRange{}, ValueRange{function.getArgument(0), external});
Value start = arith::ConstantIndexOp::create(builder, loc, 0);
Value end = arith::ConstantIndexOp::create(builder, loc, 3);
Value step = arith::ConstantIndexOp::create(builder, loc, 1);
Value captured = arith::ConstantOp::create(builder, loc, type,
DenseElementsAttr::get(type, builder.getF32FloatAttr(1.5)));
auto loop = scf::ForOp::create(builder, loc, start, end, step,
ValueRange{*compute.getInputArgument(0)});
builder.setInsertionPointToStart(loop.getBody());
Value result = SpatVAddOp::create(builder, loc, type,
loop.getRegionIterArg(0), captured);
scf::YieldOp::create(builder, loc, ValueRange{result});
builder.setInsertionPointAfter(loop);
result = SpatVAddOp::create(builder, loc, type, loop.getResult(0), *compute.getInputArgument(1));
SpatYieldOp::create(builder, loc, ValueRange{result});
builder.setInsertionPointAfter(compute);
func::ReturnOp::create(builder, loc, compute.getResults());
assert(succeeded(verify(function)));
SchedulingTarget target;
target.processorCount = 1;
target.sharedMemoryWidthBytes = 32;
target.sharedMemoryReadLatencyNs = 7;
target.sharedMemoryWriteLatencyNs = 11;
auto graph = buildCheckedComputeGraph(function, target);
assert(graph.baselineMemory.readBytes == 80);
assert(graph.baselineMemory.readRequests == 5);
assert(graph.baselineMemory.writeBytes == 16 && graph.baselineMemory.writeRequests == 1);
assert(graph.baselineMemory.serviceNs == 46);
}
static void testPipelineSharedMemory() {
mlir::MLIRContext context(mlir::MLIRContext::Threading::DISABLED);
auto fragment = mlir::RankedTensorType::get({33}, mlir::IntegerType::get(&context, 8));
mlir::Block generations;
auto firstGeneration = generations.addArgument(fragment, mlir::UnknownLoc::get(&context));
auto nextGeneration = generations.addArgument(fragment, mlir::UnknownLoc::get(&context));
auto shared = getHostPublicationIdentity(firstGeneration.getAsOpaquePointer(), 0, fragment, true);
assert(shared == getHostPublicationIdentity(firstGeneration.getAsOpaquePointer(), 0, fragment, true));
assert(shared != getHostPublicationIdentity(firstGeneration.getAsOpaquePointer(), 1, fragment, true));
assert(shared != getHostPublicationIdentity(nextGeneration.getAsOpaquePointer(), 0, fragment, true));
assert(!getHostPublicationIdentity(firstGeneration.getAsOpaquePointer(), 0, fragment, false));
ScheduleCostSummary previous, candidate;
previous.stageSpanNs = {25000};
previous.maximumCoreWorkNs = 15000;
previous.baselineMemory.serviceNs = 50000;
candidate.stageSpanNs = {32000};
candidate.maximumCoreWorkNs = 27000;
candidate.baselineMemory.serviceNs = 40000;
// Reduced memory and stage demand must not hide concentrated core work.
assert(!isBetterPipelineSchedule(candidate, previous));
candidate.maximumCoreWorkNs = 24000;
assert(isBetterPipelineSchedule(candidate, previous));
candidate.stageSpanNs = {36000};
assert(!isBetterPipelineSchedule(candidate, previous));
assert(!isBetterPipelineSchedule(previous, previous));
ComputeGraph graph;
graph.predecessors.resize(4);
graph.successors.resize(4);
for (uint32_t task = 0; task < 4; ++task) {
graph.nodes.push_back({{nullptr, task, 1}, 1000, {}, task});
if (task) {
graph.predecessors[task].push_back({task - 1, {}});
graph.successors[task - 1].push_back({task, {}});
graph.publicationTransfers.push_back({task - 1, task, {}, 0, {},
task == 2 ? Cost{16384} : Cost{32}, 1});
}
}
SchedulingTarget target;
target.processorCount = 4;
target.residentWeightCapacity = 4;
target.interProcessorLatencyNs.assign(16, 0);
target.sharedMemoryWidthBytes = 32;
auto stages = [&](const MergeScheduleResult &schedule) {
std::vector<size_t> result;
for (const auto &node : graph.nodes)
result.push_back(schedule.processorStages[schedule.computeToCpuMap.lookup(node.instance)]);
return result;
};
for (Cost latency : {Cost{1}, Cost{10}}) {
target.sharedMemoryReadLatencyNs = latency;
target.sharedMemoryWriteLatencyNs = latency;
graph.baselineMemory = target.getSharedMemoryDemand(32, 16, false);
MergeScheduleResult schedule;
std::string error;
assert(mlir::succeeded(runPipelineScheduler(graph, schedule, 2, target, error)));
auto assignment = stages(schedule);
assert(assignment.front() == 0 && assignment.back() == 1);
for (size_t task = 1; task < 4; ++task)
assert(assignment[task] >= assignment[task - 1]
&& assignment[task] <= assignment[task - 1] + 1);
auto memory = getPipelineBoundaryMemoryDemand(graph, assignment, target);
assert(memory.readRequests == 1 && memory.writeRequests == 1);
assert(memory.readBytes == (latency == 1 ? 16384 : 32));
assert(memory.writeBytes == memory.readBytes);
assert(schedule.costs.boundaryMemory.serviceNs == memory.serviceNs);
}
// Per-fragment rounding and fan-out must precede aggregation.
auto rounded = target.getSharedMemoryDemand(33, 3, true);
assert(rounded.writeBytes == 99 && rounded.writeRequests == 3);
assert(rounded.serviceNs == 60);
graph.publicationTransfers = {{0, 1, {}, 3, {}, 33, 2}, {0, 2, {}, 3, {}, 33, 1}};
auto fanout = getPipelineBoundaryMemoryDemand(graph, {0, 1, 1, 1}, target);
assert(fanout.readBytes == 99 && fanout.writeBytes == 99);
assert(fanout.readRequests == 3 && fanout.writeRequests == 3);
assert(fanout.serviceNs == 120);
graph.publicationTransfers = {
{0, 1, firstGeneration, 0, {}, 33, 2, fragment},
{0, 2, firstGeneration, 0, {}, 33, 1, fragment}};
fanout = getPipelineBoundaryMemoryDemand(graph, {0, 1, 1, 1}, target);
assert(fanout.readBytes == 99 && fanout.writeBytes == 33);
assert(fanout.readRequests == 3 && fanout.writeRequests == 1);
assert(fanout.serviceNs == 80);
graph.publicationTransfers.push_back({0, 3, nextGeneration, 0, {}, 33, 1, fragment});
graph.publicationTransfers.push_back({0, 3, firstGeneration, 1, {}, 33, 1, fragment});
fanout = getPipelineBoundaryMemoryDemand(graph, {0, 1, 1, 1}, target);
assert(fanout.readRequests == 5 && fanout.writeRequests == 3);
assert(getPipelineBoundaryMemoryDemand(graph, {0, 0, 0, 0}, target).serviceNs == 0);
graph.predecessors[3].push_back({0, {}});
graph.successors[0].push_back({3, {}});
graph.publicationTransfers.push_back({0, 3, {}, 0, {}, 4096, 1});
MergeScheduleResult constrained;
std::string error;
assert(mlir::succeeded(runPipelineScheduler(graph, constrained, 4, target, error)));
auto assignment = stages(constrained);
for (size_t task = 0; task < graph.nodes.size(); ++task)
for (const auto &[predecessor, transfer] : graph.predecessors[task])
assert(assignment[task] >= assignment[predecessor]
&& assignment[task] <= assignment[predecessor] + 1);
assert(mlir::failed(runPipelineScheduler(graph, constrained, 5, target, error)));
}
static void testReadyCommunication() {
mlir::MLIRContext context(mlir::MLIRContext::Threading::DISABLED);
mlir::OwningOpRef<mlir::ModuleOp> owner =
@@ -1372,6 +1736,9 @@ static void testPipelineRestartOrder() {
}
int main() {
testStridedPublicationMemory();
testPipelineSharedMemory();
testBaselineConstantMemory();
testResidentWeightPackingRepair();
testProducerChunkTransferCost();
testPeftReservationPassFailure();