better compile times for throughput scheduling
Validate Operations / validate-operations (push) Has been cancelled
Validate Operations / validate-operations (push) Has been cancelled
fix failing no-plan throughput ablation runs update results
This commit is contained in:
@@ -63,8 +63,11 @@ lanes of an operation and computes one ordered partition with a prefix DP.
|
|||||||
The DP jointly assigns operation prefixes and at most sixteen units of spare
|
The DP jointly assigns operation prefixes and at most sixteen units of spare
|
||||||
core capacity. Each configured stage reserves one physical core. Quantization
|
core capacity. Each configured stage reserves one physical core. Quantization
|
||||||
limits initialization only; refinement uses geometric core transfers, including one core.
|
limits initialization only; refinement uses geometric core transfers, including one core.
|
||||||
Quantization can miss a feasible allocation. Initialization failure reports a
|
If the quality grid has no feasible state, a minimum-resource prefix DP checks
|
||||||
bounded-search limitation, not proved hardware infeasibility.
|
every physical core count for each segment. This preserves feasibility over
|
||||||
|
the segment assignments found by the constructive packers, independently of
|
||||||
|
resource quantization. Packing failure remains a bounded-search limitation,
|
||||||
|
not proved hardware infeasibility.
|
||||||
The seed minimizes maximum stage packing/dependency priority over the legal
|
The seed minimizes maximum stage packing/dependency priority over the legal
|
||||||
active-stage counts within the configured depth. These priorities provide
|
active-stage counts within the configured depth. These priorities provide
|
||||||
a starting point, not a timing guarantee. Refinement can remove stage boundaries.
|
a starting point, not a timing guarantee. Refinement can remove stage boundaries.
|
||||||
@@ -76,6 +79,13 @@ constructive ordered-packing curves and uses reuse-first resident-weight packing
|
|||||||
when order alone cannot realize a capacity bound. A feasible packing remains
|
when order alone cannot realize a capacity bound. A feasible packing remains
|
||||||
feasible with idle extra cores. All placements pass the existing topology-aware
|
feasible with idle extra cores. All placements pass the existing topology-aware
|
||||||
physical packing, operation-group, adjacency and capacity checks.
|
physical packing, operation-group, adjacency and capacity checks.
|
||||||
|
Ordered packing returns an unavailable cost when no ordered assignment fits.
|
||||||
|
Every finite feasibility cost has an ordered or reuse-first constructive assignment.
|
||||||
|
Initialization retains that assignment before adding idle spare cores. If topology-aware
|
||||||
|
greedy placement and reconstruction in physical task order fail, it uses the
|
||||||
|
retained assignment.
|
||||||
|
Task reordering cannot discard the initial partition's constructive capacity evidence.
|
||||||
|
This evidence does not prove final backend legality.
|
||||||
|
|
||||||
Refinement uses a beam of two schedules ranked by the larger queue estimate.
|
Refinement uses a beam of two schedules ranked by the larger queue estimate.
|
||||||
Its ordered neighborhood moves operation groups across adjacent stage boundaries,
|
Its ordered neighborhood moves operation groups across adjacent stage boundaries,
|
||||||
@@ -163,7 +173,9 @@ Unsupported or incomplete target timing produces an `unavailable` report.
|
|||||||
|
|
||||||
The analysis brackets the maximum cycle ratio within one nanosecond. It tests
|
The analysis brackets the maximum cycle ratio within one nanosecond. It tests
|
||||||
edge delays `w - II*d`, sweeping the distance-zero DAG between recurrence
|
edge delays `w - II*d`, sweeping the distance-zero DAG between recurrence
|
||||||
relaxations. At most `R+1` sweeps detect a positive cycle, where `R` counts
|
relaxations. A positive predecessor cycle can terminate an infeasible probe early.
|
||||||
|
The final witness pass retains the full relaxation procedure and deterministic
|
||||||
|
witness selection. At most `R+1` sweeps detect a positive cycle, where `R` counts
|
||||||
positive-distance edges. Bisection uses at most 64 tests. Timing outside the
|
positive-distance edges. Bisection uses at most 64 tests. Timing outside the
|
||||||
supported numeric range produces an explicit unavailable result. A predecessor
|
supported numeric range produces an explicit unavailable result. A predecessor
|
||||||
cycle supplies a witness, including delay, iteration distance, cores, and links.
|
cycle supplies a witness, including delay, iteration distance, cores, and links.
|
||||||
@@ -337,6 +349,11 @@ Functional simulation still executes Softmax. Estimated timing and energy exclud
|
|||||||
The shared queue engine models controller arrival arbitration and cross-generation dependencies.
|
The shared queue engine models controller arrival arbitration and cross-generation dependencies.
|
||||||
The compiler does not execute candidate programs or invoke a simulator.
|
The compiler does not execute candidate programs or invoke a simulator.
|
||||||
|
|
||||||
|
Spatial extraction permits at most one million operation visits per physical core.
|
||||||
|
The total visit count is diagnostic; adding cores does not reduce another core's analysis allowance.
|
||||||
|
Abstract queue construction permits two million non-work event visits per candidate.
|
||||||
|
The complete-state recurrence limits remain unchanged.
|
||||||
|
|
||||||
Both arbitration policies must establish complete-state recurrence within bounded analysis budgets.
|
Both arbitration policies must establish complete-state recurrence within bounded analysis budgets.
|
||||||
Their estimates are sensitivity checks, not formal bounds.
|
Their estimates are sensitivity checks, not formal bounds.
|
||||||
The candidate with the lowest larger estimate must separate from the initialized schedule.
|
The candidate with the lowest larger estimate must separate from the initialized schedule.
|
||||||
@@ -362,6 +379,8 @@ If every candidate fails, one diagnostic lists all attempted candidates and caus
|
|||||||
|
|
||||||
`reports/pipeline_selection.json` records Spatial costs, eligibility, selection, and backend attempts.
|
`reports/pipeline_selection.json` records Spatial costs, eligibility, selection, and backend attempts.
|
||||||
It also records search parents, compact task-stage runs, core budgets, recurrence contributions, termination, attempts, queue budget, and phase times.
|
It also records search parents, compact task-stage runs, core budgets, recurrence contributions, termination, attempts, queue budget, and phase times.
|
||||||
|
Phase times separate neighborhood scoring, constructive packing, dependency-witness
|
||||||
|
analysis, and queue execution with its complete-state proof.
|
||||||
`--enable-timing` adds per-pass lowering times and IR operation/value counts.
|
`--enable-timing` adds per-pass lowering times and IR operation/value counts.
|
||||||
Final instruction generation uses the normal compiler timing report.
|
Final instruction generation uses the normal compiler timing report.
|
||||||
Explicit Spatial exports and IR dumps reconstruct pre-lowering artifacts for the selected schedule.
|
Explicit Spatial exports and IR dumps reconstruct pre-lowering artifacts for the selected schedule.
|
||||||
@@ -424,7 +443,8 @@ fragment/consumer demands, and resource quantization `Q <= 16`.
|
|||||||
| Weight indexing | Up to `O(R W)` equality comparisons |
|
| Weight indexing | Up to `O(R W)` equality comparisons |
|
||||||
| Weight lower bounds | `O(R + W)` / `O(W)` bits |
|
| Weight lower bounds | `O(R + W)` / `O(W)` bits |
|
||||||
| Constructive best fit | `O(T log T + P R + P W)` / `O(P W)` bits plus `O(T + P)`; bounded retries only on failure |
|
| Constructive best fit | `O(T log T + P R + P W)` / `O(P W)` bits plus `O(T + P)`; bounded retries only on failure |
|
||||||
| Joint initialization DP | `O(G² Q Kpack + S G² Q²)` time / `O(G² Q + S G Q)` storage; `Kpack` is one ordered-pack cost query |
|
| Joint initialization DP | `O(G² Q Kpack + S G² Q²)` time / `O(G² Q + S G Q)` storage; `Kpack` is one constructive pack-cost query |
|
||||||
|
| Feasibility completion, only if the quality grid fails | `O(G² P Kpack + S G²)` time / `O(G² + S G)` additional storage |
|
||||||
| Neighborhood memory service | Expected `O(T + F)` per partition / `O(T + F)` scratch |
|
| Neighborhood memory service | Expected `O(T + F)` per partition / `O(T + F)` scratch |
|
||||||
| Topology clustering / cached PEFT distances | `O(P²)` time |
|
| Topology clustering / cached PEFT distances | `O(P²)` time |
|
||||||
| Concat cost extraction | `O(A D)` for `A` inputs of rank `D`; constant auxiliary space |
|
| Concat cost extraction | `O(A D)` for `A` inputs of rank `D`; constant auxiliary space |
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ public:
|
|||||||
std::vector<std::pair<size_t, Cost>> memoryServices;
|
std::vector<std::pair<size_t, Cost>> memoryServices;
|
||||||
std::array<Cost, pim_binary::kOpcodeCount> opcodeService{}, opcodeCounts{};
|
std::array<Cost, pim_binary::kOpcodeCount> opcodeService{}, opcodeCounts{};
|
||||||
std::string error;
|
std::string error;
|
||||||
size_t abstractBudget = 1000000;
|
size_t abstractBudget = 2000000;
|
||||||
|
|
||||||
bool readCore(int64_t core, llvm::StringRef data, llvm::ArrayRef<int64_t> groups);
|
bool readCore(int64_t core, llvm::StringRef data, llvm::ArrayRef<int64_t> groups);
|
||||||
bool readExecution(int64_t core, const std::vector<PimExecutionEvent> &program);
|
bool readExecution(int64_t core, const std::vector<PimExecutionEvent> &program);
|
||||||
@@ -845,13 +845,17 @@ bool Analysis::connect() {
|
|||||||
|
|
||||||
EvaluatedAnalysis Analysis::evaluate() {
|
EvaluatedAnalysis Analysis::evaluate() {
|
||||||
EvaluatedAnalysis result;
|
EvaluatedAnalysis result;
|
||||||
|
double dependencySeconds = 0;
|
||||||
if (cores.empty()) result.error = "artifact has no active repeating instruction stream";
|
if (cores.empty()) result.error = "artifact has no active repeating instruction stream";
|
||||||
else if (!connect()) result.error = error;
|
else if (!connect()) result.error = error;
|
||||||
if (!result.error.empty()) { result.cost.reason = result.error; return result; }
|
if (!result.error.empty()) { result.cost.reason = result.error; return result; }
|
||||||
if (options.detailed || options.searchGuidance) {
|
if (options.detailed || options.searchGuidance) {
|
||||||
|
auto start = std::chrono::steady_clock::now();
|
||||||
auto recurrence = analyzePeriodicDependencies(events.size(), edges, result.error);
|
auto recurrence = analyzePeriodicDependencies(events.size(), edges, result.error);
|
||||||
if (mlir::failed(recurrence)) { result.cost.reason = result.error; return result; }
|
if (mlir::failed(recurrence)) { result.cost.reason = result.error; return result; }
|
||||||
result.recurrence = std::move(*recurrence);
|
result.recurrence = std::move(*recurrence);
|
||||||
|
dependencySeconds = std::chrono::duration<double>(
|
||||||
|
std::chrono::steady_clock::now() - start).count();
|
||||||
}
|
}
|
||||||
if (!options.detailed || memoryQueue) {
|
if (!options.detailed || memoryQueue) {
|
||||||
auto start = std::chrono::steady_clock::now();
|
auto start = std::chrono::steady_clock::now();
|
||||||
@@ -885,6 +889,7 @@ EvaluatedAnalysis Analysis::evaluate() {
|
|||||||
+= static_cast<double>(edge.delayNs) / distance;
|
+= static_cast<double>(edge.delayNs) / distance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
result.cost.timing.dependencySeconds = dependencySeconds;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -894,6 +899,8 @@ llvm::json::Object Analysis::report(const EvaluatedAnalysis &evaluation) const {
|
|||||||
result["status"] = "unavailable"; result["reason"] = evaluation.error; return result;
|
result["status"] = "unavailable"; result["reason"] = evaluation.error; return result;
|
||||||
}
|
}
|
||||||
result["global_memory_service_ns"] = static_cast<int64_t>(memory.serviceNs);
|
result["global_memory_service_ns"] = static_cast<int64_t>(memory.serviceNs);
|
||||||
|
if (options.measureTime)
|
||||||
|
result["dependency_analysis_ms"] = evaluation.cost.timing.dependencySeconds * 1000;
|
||||||
result["global_read_bytes"] = static_cast<int64_t>(memory.readBytes);
|
result["global_read_bytes"] = static_cast<int64_t>(memory.readBytes);
|
||||||
result["global_write_bytes"] = static_cast<int64_t>(memory.writeBytes);
|
result["global_write_bytes"] = static_cast<int64_t>(memory.writeBytes);
|
||||||
result["global_read_requests"] = static_cast<int64_t>(memory.readRequests);
|
result["global_read_requests"] = static_cast<int64_t>(memory.readRequests);
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ struct PimPipelineSelectionPass final : PassWrapper<PimPipelineSelectionPass, Op
|
|||||||
PreparedSpatialSchedules &prepared, std::vector<WholeScheduleCost> &costs,
|
PreparedSpatialSchedules &prepared, std::vector<WholeScheduleCost> &costs,
|
||||||
llvm::json::Object &report) {
|
llvm::json::Object &report) {
|
||||||
llvm::json::Array summaries;
|
llvm::json::Array summaries;
|
||||||
double realization = 0, extraction = 0, expansion = 0, queue = 0, evaluation = 0;
|
double realization = 0, extraction = 0, expansion = 0, queue = 0, evaluation = 0, dependency = 0;
|
||||||
auto evaluate = [&](const MergeScheduleResult &schedule) {
|
auto evaluate = [&](const MergeScheduleResult &schedule) {
|
||||||
size_t index = summaries.size();
|
size_t index = summaries.size();
|
||||||
llvm::json::Object summary{{"index", static_cast<int64_t>(index)}};
|
llvm::json::Object summary{{"index", static_cast<int64_t>(index)}};
|
||||||
@@ -131,6 +131,7 @@ struct PimPipelineSelectionPass final : PassWrapper<PimPipelineSelectionPass, Op
|
|||||||
extraction += cost.timing.extractionSeconds;
|
extraction += cost.timing.extractionSeconds;
|
||||||
expansion += cost.timing.expansionSeconds;
|
expansion += cost.timing.expansionSeconds;
|
||||||
queue += cost.timing.evaluationSeconds;
|
queue += cost.timing.evaluationSeconds;
|
||||||
|
dependency += cost.timing.dependencySeconds;
|
||||||
auto convergence = cost.convergenceProved(), support = cost.loweringSupported();
|
auto convergence = cost.convergenceProved(), support = cost.loweringSupported();
|
||||||
summary["cost_available"] = cost.available();
|
summary["cost_available"] = cost.available();
|
||||||
summary["convergence_proved"] = convergence
|
summary["convergence_proved"] = convergence
|
||||||
@@ -183,6 +184,7 @@ struct PimPipelineSelectionPass final : PassWrapper<PimPipelineSelectionPass, Op
|
|||||||
report["event_extraction_seconds"] = extraction;
|
report["event_extraction_seconds"] = extraction;
|
||||||
report["event_expansion_seconds"] = expansion;
|
report["event_expansion_seconds"] = expansion;
|
||||||
report["queue_seconds"] = queue;
|
report["queue_seconds"] = queue;
|
||||||
|
report["dependency_analysis_seconds"] = dependency;
|
||||||
report["candidates"] = std::move(summaries);
|
report["candidates"] = std::move(summaries);
|
||||||
if (failed(search)) {
|
if (failed(search)) {
|
||||||
report["status"] = "incomplete-costs";
|
report["status"] = "incomplete-costs";
|
||||||
@@ -204,6 +206,8 @@ struct PimPipelineSelectionPass final : PassWrapper<PimPipelineSelectionPass, Op
|
|||||||
report["single_candidate_analysis_skipped"] = single;
|
report["single_candidate_analysis_skipped"] = single;
|
||||||
report["search_rounds"] = static_cast<int64_t>(search->rounds);
|
report["search_rounds"] = static_cast<int64_t>(search->rounds);
|
||||||
report["packing_attempts"] = static_cast<int64_t>(search->packingAttempts);
|
report["packing_attempts"] = static_cast<int64_t>(search->packingAttempts);
|
||||||
|
report["neighborhood_seconds"] = search->neighborhoodSeconds;
|
||||||
|
report["constructive_packing_seconds"] = search->packingSeconds;
|
||||||
report["considered_assignments"] = static_cast<int64_t>(search->consideredAssignments);
|
report["considered_assignments"] = static_cast<int64_t>(search->consideredAssignments);
|
||||||
report["search_termination"] = search->termination;
|
report["search_termination"] = search->termination;
|
||||||
report["queue_evaluation_budget"] = static_cast<int64_t>(kPipelineQueueEvaluationBudget);
|
report["queue_evaluation_budget"] = static_cast<int64_t>(kPipelineQueueEvaluationBudget);
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ public:
|
|||||||
program(target.processorCount) {}
|
program(target.processorCount) {}
|
||||||
bool build(ModuleOp module);
|
bool build(ModuleOp module);
|
||||||
private:
|
private:
|
||||||
|
// Bound interpretation per physical core, independently of batch lane count.
|
||||||
|
static constexpr size_t maximumCoreVisits = 1000000;
|
||||||
|
size_t coreVisits = 0;
|
||||||
|
int64_t currentCore = -1;
|
||||||
llvm::SmallDenseSet<int64_t, 16> assignedCores;
|
llvm::SmallDenseSet<int64_t, 16> assignedCores;
|
||||||
DenseMap<Value, Residence> residence;
|
DenseMap<Value, Residence> residence;
|
||||||
DenseMap<Value, CopyAccessPlan> publicationAccess;
|
DenseMap<Value, CopyAccessPlan> publicationAccess;
|
||||||
@@ -378,7 +382,12 @@ bool Extraction::operation(Operation &op, Sequence &sequence) {
|
|||||||
|
|
||||||
bool Extraction::block(Block &body, Sequence &sequence) {
|
bool Extraction::block(Block &body, Sequence &sequence) {
|
||||||
for (Operation &op : body) {
|
for (Operation &op : body) {
|
||||||
if (++visited > 1000000) { error = "abstract extraction budget exceeded"; return false; }
|
++visited;
|
||||||
|
if (++coreVisits > maximumCoreVisits) {
|
||||||
|
error = "abstract extraction budget exceeded on physical core "
|
||||||
|
+ std::to_string(currentCore);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!operation(op, sequence)) return false;
|
if (!operation(op, sequence)) return false;
|
||||||
for (Value result : op.getResults())
|
for (Value result : op.getResults())
|
||||||
if (auto outputs = directOutputs.find(result); outputs != directOutputs.end())
|
if (auto outputs = directOutputs.find(result); outputs != directOutputs.end())
|
||||||
@@ -400,6 +409,7 @@ bool Extraction::core(Operation *op, int64_t physicalCore, unsigned lane) {
|
|||||||
loweringSupport = StaticLoweringSupport::Unsupported;
|
loweringSupport = StaticLoweringSupport::Unsupported;
|
||||||
error = "duplicate physical core assignment: " + std::to_string(physicalCore); return false;
|
error = "duplicate physical core assignment: " + std::to_string(physicalCore); return false;
|
||||||
}
|
}
|
||||||
|
coreVisits = 0; currentCore = physicalCore;
|
||||||
residence.clear(); publicationAccess.clear(); directOutputs.clear(); compactStorage.clear();
|
residence.clear(); publicationAccess.clear(); directOutputs.clear(); compactStorage.clear();
|
||||||
environment.bindings.clear();
|
environment.bindings.clear();
|
||||||
ValueRange inputs, weights;
|
ValueRange inputs, weights;
|
||||||
|
|||||||
+27
-2
@@ -57,7 +57,29 @@ mlir::FailureOr<PeriodicDependencyEstimate> analyzePeriodicDependencies(
|
|||||||
const size_t absent = std::numeric_limits<size_t>::max();
|
const size_t absent = std::numeric_limits<size_t>::max();
|
||||||
std::vector<double> potential(nodeCount);
|
std::vector<double> potential(nodeCount);
|
||||||
std::vector<size_t> predecessor(nodeCount, absent);
|
std::vector<size_t> predecessor(nodeCount, absent);
|
||||||
auto infeasible = [&](double interval) {
|
std::vector<size_t> cycleSeen(nodeCount);
|
||||||
|
auto hasPositivePredecessorCycle = [&](double interval) {
|
||||||
|
std::fill(cycleSeen.begin(), cycleSeen.end(), absent);
|
||||||
|
for (size_t start = 0; start < nodeCount; ++start) {
|
||||||
|
size_t node = start;
|
||||||
|
while (cycleSeen[node] == absent && predecessor[node] != absent) {
|
||||||
|
cycleSeen[node] = start;
|
||||||
|
node = edges[predecessor[node]].source;
|
||||||
|
}
|
||||||
|
if (cycleSeen[node] != start || predecessor[node] == absent) continue;
|
||||||
|
const size_t first = node;
|
||||||
|
Cost delay = 0, distance = 0;
|
||||||
|
do {
|
||||||
|
const auto &edge = edges[predecessor[node]];
|
||||||
|
delay = checkedAdd(delay, edge.delayNs);
|
||||||
|
distance = checkedAdd(distance, static_cast<Cost>(edge.distance));
|
||||||
|
node = edge.source;
|
||||||
|
} while (node != first);
|
||||||
|
if (static_cast<double>(delay) - interval * distance > 1e-7) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
auto infeasible = [&](double interval, bool earlyCycle = true) {
|
||||||
std::fill(potential.begin(), potential.end(), 0);
|
std::fill(potential.begin(), potential.end(), 0);
|
||||||
std::fill(predecessor.begin(), predecessor.end(), absent);
|
std::fill(predecessor.begin(), predecessor.end(), absent);
|
||||||
auto relax = [&](size_t index) {
|
auto relax = [&](size_t index) {
|
||||||
@@ -85,6 +107,9 @@ mlir::FailureOr<PeriodicDependencyEstimate> analyzePeriodicDependencies(
|
|||||||
changed |= relax(index);
|
changed |= relax(index);
|
||||||
if (!changed)
|
if (!changed)
|
||||||
return false;
|
return false;
|
||||||
|
// A positive predecessor cycle already proves infeasibility. Keep the
|
||||||
|
// final witness pass unchanged so search guidance remains deterministic.
|
||||||
|
if (earlyCycle && hasPositivePredecessorCycle(interval)) return true;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -104,7 +129,7 @@ mlir::FailureOr<PeriodicDependencyEstimate> analyzePeriodicDependencies(
|
|||||||
error = "periodic timing exceeds one-nanosecond floating-point precision";
|
error = "periodic timing exceeds one-nanosecond floating-point precision";
|
||||||
return mlir::failure();
|
return mlir::failure();
|
||||||
}
|
}
|
||||||
if (!infeasible(result.recurrenceLowerNs))
|
if (!infeasible(result.recurrenceLowerNs, false))
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
// Recover witness cycles from the last longest-path predecessor forest.
|
// Recover witness cycles from the last longest-path predecessor forest.
|
||||||
|
|||||||
+106
-42
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <chrono>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -71,6 +72,7 @@ struct PipelineGroup {
|
|||||||
struct PipelineStageAssignment {
|
struct PipelineStageAssignment {
|
||||||
std::vector<size_t> taskStages;
|
std::vector<size_t> taskStages;
|
||||||
std::vector<size_t> stageSizes;
|
std::vector<size_t> stageSizes;
|
||||||
|
std::vector<size_t> constructivePacks;
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool consumesPipelineInput(const ComputeGraphNode &node) {
|
static bool consumesPipelineInput(const ComputeGraphNode &node) {
|
||||||
@@ -229,7 +231,7 @@ static FailureOr<PipelineStageAssignment> initialPipelinePartition(
|
|||||||
std::string &error) {
|
std::string &error) {
|
||||||
size_t count = constraints.groups.size(), stages = layout.getStageCount();
|
size_t count = constraints.groups.size(), stages = layout.getStageCount();
|
||||||
if (!count) return PipelineStageAssignment{{},
|
if (!count) return PipelineStageAssignment{{},
|
||||||
{layout.getStageSizes().begin(), layout.getStageSizes().end()}};
|
{layout.getStageSizes().begin(), layout.getStageSizes().end()}, {}};
|
||||||
size_t extra = layout.getProcessorCount() - stages;
|
size_t extra = layout.getProcessorCount() - stages;
|
||||||
// Resource quantization bounds seed construction only. Refinement uses
|
// Resource quantization bounds seed construction only. Refinement uses
|
||||||
// individual physical cores and final allocation uses every available core.
|
// individual physical cores and final allocation uses every available core.
|
||||||
@@ -259,9 +261,9 @@ static FailureOr<PipelineStageAssignment> initialPipelinePartition(
|
|||||||
for (size_t amount = 0; amount <= units; ++amount) {
|
for (size_t amount = 0; amount <= units; ++amount) {
|
||||||
size_t cores = coreCount(amount);
|
size_t cores = coreCount(amount);
|
||||||
if (weights.count() > checkedMultiply(cores, residentWeightCapacity)) continue;
|
if (weights.count() > checkedMultiply(cores, residentWeightCapacity)) continue;
|
||||||
costs[start][end][amount] = checkedAdd(span - largest,
|
if (auto packing = model.weights.estimatePackCost(tasks,
|
||||||
estimateOrderedPackCost(model.schedulingCosts, model.weights.taskWeightIds,
|
model.schedulingCosts, residentWeightCapacity, cores))
|
||||||
model.weights.weightCount, tasks, residentWeightCapacity, cores));
|
costs[start][end][amount] = checkedAdd(span - largest, *packing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,8 +292,53 @@ static FailureOr<PipelineStageAssignment> initialPipelinePartition(
|
|||||||
&& (!selected || best[stage][count][units].priority
|
&& (!selected || best[stage][count][units].priority
|
||||||
< best[selected][count][units].priority))
|
< best[selected][count][units].priority))
|
||||||
selected = stage;
|
selected = stage;
|
||||||
|
// Quantization controls quality, not feasibility. Complete the same prefix
|
||||||
|
// DP with minimum constructive core counts if no quantized state survives.
|
||||||
|
std::vector<std::vector<Cell>> feasible;
|
||||||
|
if (!selected) {
|
||||||
|
feasible.resize(stages + 1, std::vector<Cell>(count + 1));
|
||||||
|
feasible[0][0].valid = true;
|
||||||
|
std::vector<std::vector<size_t>> minimum(count + 1,
|
||||||
|
std::vector<size_t>(count + 1, std::numeric_limits<size_t>::max()));
|
||||||
|
for (size_t start = 0; start < count; ++start) {
|
||||||
|
TaskList tasks;
|
||||||
|
for (size_t end = start + 1; end <= count; ++end) {
|
||||||
|
llvm::append_range(tasks, constraints.groups[constraints.groupOrder[end - 1]].tasks);
|
||||||
|
const size_t lower = std::max<size_t>(1,
|
||||||
|
model.weights.getPackCountLowerBound(tasks, residentWeightCapacity));
|
||||||
|
const size_t maximum = layout.getProcessorCount() - stages + 1;
|
||||||
|
for (size_t cores = lower; cores <= maximum; ++cores)
|
||||||
|
if (model.weights.estimatePackCost(tasks, model.schedulingCosts,
|
||||||
|
residentWeightCapacity, cores)) {
|
||||||
|
minimum[start][end] = cores;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t stage = 0; stage < stages; ++stage)
|
||||||
|
for (size_t start = 0; start < count; ++start) {
|
||||||
|
if (!feasible[stage][start].valid) continue;
|
||||||
|
for (size_t end = start + 1; end <= count; ++end) {
|
||||||
|
if (stage == 0 && end < constraints.minimumStageZeroEnd) continue;
|
||||||
|
if (stage && constraints.hasCrossingEdge
|
||||||
|
&& constraints.furthestSuccessorBefore[start] >= end) continue;
|
||||||
|
const size_t cores = minimum[start][end];
|
||||||
|
if (cores > layout.getProcessorCount()) continue;
|
||||||
|
Cost used = checkedAdd(feasible[stage][start].priority, static_cast<Cost>(cores));
|
||||||
|
if (used > layout.getProcessorCount() - (stages - stage - 1)) continue;
|
||||||
|
auto ¤t = feasible[stage + 1][end];
|
||||||
|
if (!current.valid || used < current.priority)
|
||||||
|
current = {used, start, cores, true};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t stage = 1; stage <= stages; ++stage)
|
||||||
|
if (feasible[stage][count].valid
|
||||||
|
&& (!selected || feasible[stage][count].priority < feasible[selected][count].priority))
|
||||||
|
selected = stage;
|
||||||
|
}
|
||||||
if (!selected && count) {
|
if (!selected && count) {
|
||||||
error = "pipeline initialization found no dependency-adjacent operation partition within its bounded core-budget search";
|
error = "pipeline initialization found no constructively packable dependency-adjacent partition; "
|
||||||
|
"bounded packing failure does not prove physical infeasibility";
|
||||||
return failure();
|
return failure();
|
||||||
}
|
}
|
||||||
PipelineStageAssignment result;
|
PipelineStageAssignment result;
|
||||||
@@ -299,13 +346,31 @@ static FailureOr<PipelineStageAssignment> initialPipelinePartition(
|
|||||||
result.taskStages.resize(constraints.taskToGroup.size());
|
result.taskStages.resize(constraints.taskToGroup.size());
|
||||||
size_t end = count, remaining = units;
|
size_t end = count, remaining = units;
|
||||||
for (size_t stage = selected; stage; --stage) {
|
for (size_t stage = selected; stage; --stage) {
|
||||||
const auto &cell = best[stage][end][remaining];
|
const auto &cell = feasible.empty() ? best[stage][end][remaining] : feasible[stage][end];
|
||||||
result.stageSizes[stage - 1] = coreCount(cell.units);
|
result.stageSizes[stage - 1] = feasible.empty() ? coreCount(cell.units) : cell.units;
|
||||||
for (size_t position = cell.start; position < end; ++position)
|
for (size_t position = cell.start; position < end; ++position)
|
||||||
for (size_t task : constraints.groups[constraints.groupOrder[position]].tasks)
|
for (size_t task : constraints.groups[constraints.groupOrder[position]].tasks)
|
||||||
result.taskStages[task] = stage - 1;
|
result.taskStages[task] = stage - 1;
|
||||||
end = cell.start;
|
end = cell.start;
|
||||||
remaining -= cell.units;
|
if (feasible.empty()) remaining -= cell.units;
|
||||||
|
}
|
||||||
|
// Preserve the constructive witness before adding spare cores. Reordering
|
||||||
|
// tasks for topology-aware placement must not lose DP feasibility evidence.
|
||||||
|
result.constructivePacks.resize(model.schedulingCosts.size());
|
||||||
|
std::vector<TaskList> stageTasks(selected);
|
||||||
|
for (size_t group : constraints.groupOrder)
|
||||||
|
for (size_t task : constraints.groups[group].tasks)
|
||||||
|
stageTasks[result.taskStages[task]].push_back(task);
|
||||||
|
for (size_t stage = 0; stage < selected; ++stage) {
|
||||||
|
const auto &tasks = stageTasks[stage];
|
||||||
|
TaskList packs;
|
||||||
|
if (!model.weights.estimatePackCost(tasks, model.schedulingCosts,
|
||||||
|
residentWeightCapacity, result.stageSizes[stage], &packs)) {
|
||||||
|
error = "pipeline initializer could not reconstruct its constructive packing witness";
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
for (auto [index, task] : llvm::enumerate(tasks))
|
||||||
|
result.constructivePacks[task] = packs[index];
|
||||||
}
|
}
|
||||||
size_t assigned = std::accumulate(result.stageSizes.begin(), result.stageSizes.end(), size_t{0});
|
size_t assigned = std::accumulate(result.stageSizes.begin(), result.stageSizes.end(), size_t{0});
|
||||||
for (; assigned < layout.getProcessorCount(); ++assigned) {
|
for (; assigned < layout.getProcessorCount(); ++assigned) {
|
||||||
@@ -431,7 +496,7 @@ static LogicalResult packPipelineStages(
|
|||||||
std::vector<TaskList> &tasksByCpu, const PipelineCoreLayout &layout,
|
std::vector<TaskList> &tasksByCpu, const PipelineCoreLayout &layout,
|
||||||
size_t pipelineStages, size_t residentWeightCapacity,
|
size_t pipelineStages, size_t residentWeightCapacity,
|
||||||
const SchedulingTarget &target, size_t &failedStage,
|
const SchedulingTarget &target, size_t &failedStage,
|
||||||
std::string &error) {
|
std::string &error, ArrayRef<size_t> constructivePacks) {
|
||||||
std::vector<size_t> indegree(graph.nodes.size());
|
std::vector<size_t> indegree(graph.nodes.size());
|
||||||
std::vector<TaskList> successors(graph.nodes.size());
|
std::vector<TaskList> successors(graph.nodes.size());
|
||||||
for (size_t task = 0; task < graph.nodes.size(); ++task)
|
for (size_t task = 0; task < graph.nodes.size(); ++task)
|
||||||
@@ -493,13 +558,11 @@ static LogicalResult packPipelineStages(
|
|||||||
llvm::sort(tasks, [&](size_t lhs, size_t rhs) {
|
llvm::sort(tasks, [&](size_t lhs, size_t rhs) {
|
||||||
return topologicalPosition[lhs] < topologicalPosition[rhs];
|
return topologicalPosition[lhs] < topologicalPosition[rhs];
|
||||||
});
|
});
|
||||||
estimateOrderedPackCost(model.schedulingCosts, model.weights.taskWeightIds,
|
model.weights.estimatePackCost(tasks, model.schedulingCosts,
|
||||||
model.weights.weightCount, tasks, residentWeightCapacity, range.size, &taskPacks);
|
residentWeightCapacity, range.size, &taskPacks);
|
||||||
if (taskPacks.size() != tasks.size()) {
|
if (taskPacks.size() != tasks.size() && !constructivePacks.empty()) {
|
||||||
auto assignment = model.weights.pack(
|
taskPacks.clear();
|
||||||
tasks, model.schedulingCosts, residentWeightCapacity, range.size);
|
for (size_t task : tasks) taskPacks.push_back(constructivePacks[task]);
|
||||||
if (assignment)
|
|
||||||
taskPacks = std::move(*assignment);
|
|
||||||
}
|
}
|
||||||
packed = taskPacks.size() == tasks.size() && packPipelineStage(
|
packed = taskPacks.size() == tasks.size() && packPipelineStage(
|
||||||
graph, model.schedulingCosts, tasksByCpu,
|
graph, model.schedulingCosts, tasksByCpu,
|
||||||
@@ -508,9 +571,9 @@ static LogicalResult packPipelineStages(
|
|||||||
}
|
}
|
||||||
if (!packed) {
|
if (!packed) {
|
||||||
failedStage = stage;
|
failedStage = stage;
|
||||||
error = "pipeline resident-weight heuristic search could not pack stage "
|
error = "pipeline constructive physical-placement search could not pack stage "
|
||||||
+ std::to_string(stage)
|
+ std::to_string(stage)
|
||||||
+ " within its physical core budget";
|
+ " within its physical core budget; bounded search failure does not prove physical infeasibility";
|
||||||
return failure();
|
return failure();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -633,7 +696,7 @@ static LogicalResult packPipelineAssignment(const ComputeGraph &graph,
|
|||||||
if (succeeded(packPipelineStages(
|
if (succeeded(packPipelineStages(
|
||||||
graph, taskModel, tasksByPhysicalCpu, candidateLayout,
|
graph, taskModel, tasksByPhysicalCpu, candidateLayout,
|
||||||
pipelineStages, physicalTarget.residentWeightCapacity,
|
pipelineStages, physicalTarget.residentWeightCapacity,
|
||||||
physicalTarget, failedStage, packingError))) {
|
physicalTarget, failedStage, packingError, assignment.constructivePacks))) {
|
||||||
packed = true;
|
packed = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -651,6 +714,7 @@ static LogicalResult packPipelineAssignment(const ComputeGraph &graph,
|
|||||||
donor = stage;
|
donor = stage;
|
||||||
if (!donor)
|
if (!donor)
|
||||||
break;
|
break;
|
||||||
|
assignment.constructivePacks.clear();
|
||||||
--assignment.stageSizes[*donor];
|
--assignment.stageSizes[*donor];
|
||||||
++assignment.stageSizes[failedStage];
|
++assignment.stageSizes[failedStage];
|
||||||
}
|
}
|
||||||
@@ -748,24 +812,10 @@ static std::optional<PipelineStageAssignment> allocatePipelineStageCores(
|
|||||||
largest = std::max(largest, model.schedulingCosts[task]);
|
largest = std::max(largest, model.schedulingCosts[task]);
|
||||||
}
|
}
|
||||||
for (size_t cores = minimum; cores <= target.processorCount; ++cores) {
|
for (size_t cores = minimum; cores <= target.processorCount; ++cores) {
|
||||||
TaskList packs;
|
|
||||||
Cost packedCost = std::max(largest, work[stage] / cores + (work[stage] % cores != 0));
|
Cost packedCost = std::max(largest, work[stage] / cores + (work[stage] % cores != 0));
|
||||||
if (constructPacks)
|
if (constructPacks)
|
||||||
packedCost = estimateOrderedPackCost(model.schedulingCosts,
|
packedCost = model.weights.estimatePackCost(tasks[stage], model.schedulingCosts,
|
||||||
model.weights.taskWeightIds, model.weights.weightCount, tasks[stage],
|
target.residentWeightCapacity, cores).value_or(std::numeric_limits<Cost>::max());
|
||||||
target.residentWeightCapacity, cores, &packs);
|
|
||||||
if (constructPacks && packs.size() != tasks[stage].size()) {
|
|
||||||
auto constructive = model.weights.pack(tasks[stage], model.schedulingCosts,
|
|
||||||
target.residentWeightCapacity, cores);
|
|
||||||
packedCost = std::numeric_limits<Cost>::max();
|
|
||||||
if (constructive) {
|
|
||||||
TaskCosts loads(cores);
|
|
||||||
for (size_t i = 0; i < tasks[stage].size(); ++i)
|
|
||||||
loads[(*constructive)[i]] = checkedAdd(loads[(*constructive)[i]],
|
|
||||||
model.schedulingCosts[tasks[stage][i]]);
|
|
||||||
packedCost = *llvm::max_element(loads);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// A feasible assignment remains feasible when extra cores stay idle.
|
// A feasible assignment remains feasible when extra cores stay idle.
|
||||||
if (cores > minimum) packedCost = std::min(packedCost, costs[stage][cores - 1]);
|
if (cores > minimum) packedCost = std::min(packedCost, costs[stage][cores - 1]);
|
||||||
costs[stage][cores] = packedCost;
|
costs[stage][cores] = packedCost;
|
||||||
@@ -1060,9 +1110,8 @@ static std::vector<PipelineProposal> pipelineNeighborhood(const ComputeGraph &gr
|
|||||||
auto packingCost = [&](const TaskList &tasks, size_t cores, size_t stage) {
|
auto packingCost = [&](const TaskList &tasks, size_t cores, size_t stage) {
|
||||||
auto *cached = tasks == originalView.tasks[stage] ? &packCosts[stage][cores] : nullptr;
|
auto *cached = tasks == originalView.tasks[stage] ? &packCosts[stage][cores] : nullptr;
|
||||||
if (cached && *cached) return **cached;
|
if (cached && *cached) return **cached;
|
||||||
Cost value = estimateOrderedPackCost(model.schedulingCosts,
|
Cost value = model.weights.estimatePackCost(tasks, model.schedulingCosts,
|
||||||
model.weights.taskWeightIds, model.weights.weightCount, tasks,
|
target.residentWeightCapacity, cores).value_or(std::numeric_limits<Cost>::max());
|
||||||
target.residentWeightCapacity, cores);
|
|
||||||
if (cached) *cached = value;
|
if (cached) *cached = value;
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
@@ -1093,6 +1142,7 @@ static std::vector<PipelineProposal> pipelineNeighborhood(const ComputeGraph &gr
|
|||||||
if (model.weights.getPackCountLowerBound(view.tasks[stage],
|
if (model.weights.getPackCountLowerBound(view.tasks[stage],
|
||||||
target.residentWeightCapacity) > assignment.stageSizes[stage]) return;
|
target.residentWeightCapacity) > assignment.stageSizes[stage]) return;
|
||||||
Cost work = packingCost(view.tasks[stage], assignment.stageSizes[stage], stage);
|
Cost work = packingCost(view.tasks[stage], assignment.stageSizes[stage], stage);
|
||||||
|
if (work == std::numeric_limits<Cost>::max()) return;
|
||||||
maximum = std::max(maximum, work);
|
maximum = std::max(maximum, work);
|
||||||
// A within-generation span is latency, not an initiation-interval bound.
|
// A within-generation span is latency, not an initiation-interval bound.
|
||||||
total = checkedAdd(total, view.spans[stage]);
|
total = checkedAdd(total, view.spans[stage]);
|
||||||
@@ -1174,7 +1224,10 @@ mlir::FailureOr<PipelineSearchResult> optimizePipelineSchedule(
|
|||||||
std::set<PipelineMove> visited;
|
std::set<PipelineMove> visited;
|
||||||
std::set<TaskList> placements{placementIdentity(graph, initial)};
|
std::set<TaskList> placements{placementIdentity(graph, initial)};
|
||||||
std::vector<size_t> beam{0};
|
std::vector<size_t> beam{0};
|
||||||
|
auto neighborhoodStart = std::chrono::steady_clock::now();
|
||||||
auto proposals = pipelineNeighborhood(graph, executionModel, *groups, result, beam, visited, 2 * evaluationBudget);
|
auto proposals = pipelineNeighborhood(graph, executionModel, *groups, result, beam, visited, 2 * evaluationBudget);
|
||||||
|
result.neighborhoodSeconds = std::chrono::duration<double>(
|
||||||
|
std::chrono::steady_clock::now() - neighborhoodStart).count();
|
||||||
if (proposals.empty() || evaluationBudget == 1) {
|
if (proposals.empty() || evaluationBudget == 1) {
|
||||||
result.termination = "one neighborhood schedule; queue comparison skipped";
|
result.termination = "one neighborhood schedule; queue comparison skipped";
|
||||||
return result;
|
return result;
|
||||||
@@ -1197,7 +1250,10 @@ mlir::FailureOr<PipelineSearchResult> optimizePipelineSchedule(
|
|||||||
if (!error.empty()) return failure();
|
if (!error.empty()) return failure();
|
||||||
constexpr size_t maximumPackingAttempts = 4 * kPipelineQueueEvaluationBudget;
|
constexpr size_t maximumPackingAttempts = 4 * kPipelineQueueEvaluationBudget;
|
||||||
while (result.costs.size() < evaluationBudget && result.packingAttempts < maximumPackingAttempts) {
|
while (result.costs.size() < evaluationBudget && result.packingAttempts < maximumPackingAttempts) {
|
||||||
|
neighborhoodStart = std::chrono::steady_clock::now();
|
||||||
proposals = pipelineNeighborhood(graph, executionModel, *groups, result, beam, visited, 2 * evaluationBudget);
|
proposals = pipelineNeighborhood(graph, executionModel, *groups, result, beam, visited, 2 * evaluationBudget);
|
||||||
|
result.neighborhoodSeconds += std::chrono::duration<double>(
|
||||||
|
std::chrono::steady_clock::now() - neighborhoodStart).count();
|
||||||
if (proposals.empty()) break;
|
if (proposals.empty()) break;
|
||||||
++result.rounds;
|
++result.rounds;
|
||||||
size_t evaluated = 0;
|
size_t evaluated = 0;
|
||||||
@@ -1211,17 +1267,25 @@ mlir::FailureOr<PipelineSearchResult> optimizePipelineSchedule(
|
|||||||
++result.packingAttempts;
|
++result.packingAttempts;
|
||||||
visited.emplace(proposal.parent, proposal.assignment.taskStages,
|
visited.emplace(proposal.parent, proposal.assignment.taskStages,
|
||||||
proposal.assignment.stageSizes, proposal.optimizeCores, proposal.adjustableStages);
|
proposal.assignment.stageSizes, proposal.optimizeCores, proposal.adjustableStages);
|
||||||
|
auto packingStart = std::chrono::steady_clock::now();
|
||||||
auto assignment = proposal.assignment;
|
auto assignment = proposal.assignment;
|
||||||
if (proposal.optimizeCores) {
|
if (proposal.optimizeCores) {
|
||||||
auto allocated = allocatePipelineStageCores(graph, executionModel, assignment.taskStages,
|
auto allocated = allocatePipelineStageCores(graph, executionModel, assignment.taskStages,
|
||||||
pipelineStages, target, assignment.stageSizes, proposal.adjustableStages);
|
pipelineStages, target, assignment.stageSizes, proposal.adjustableStages);
|
||||||
if (!allocated) continue;
|
if (!allocated) {
|
||||||
|
result.packingSeconds += std::chrono::duration<double>(
|
||||||
|
std::chrono::steady_clock::now() - packingStart).count();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
assignment = std::move(*allocated);
|
assignment = std::move(*allocated);
|
||||||
}
|
}
|
||||||
MergeScheduleResult schedule;
|
MergeScheduleResult schedule;
|
||||||
std::string packingError;
|
std::string packingError;
|
||||||
if (failed(packPipelineAssignment(graph, model, assignment, schedule,
|
auto packed = packPipelineAssignment(graph, model, assignment, schedule,
|
||||||
pipelineStages, target, packingError))) continue;
|
pipelineStages, target, packingError);
|
||||||
|
result.packingSeconds += std::chrono::duration<double>(
|
||||||
|
std::chrono::steady_clock::now() - packingStart).count();
|
||||||
|
if (failed(packed)) continue;
|
||||||
if (!placements.insert(placementIdentity(graph, schedule)).second) continue;
|
if (!placements.insert(placementIdentity(graph, schedule)).second) continue;
|
||||||
auto estimate = cost(schedule);
|
auto estimate = cost(schedule);
|
||||||
if (!error.empty()) return failure();
|
if (!error.empty()) return failure();
|
||||||
@@ -1261,7 +1325,7 @@ mlir::LogicalResult packPipelineSchedule(const ComputeGraph &graph,
|
|||||||
}
|
}
|
||||||
PipelineTaskModel model(graph, target);
|
PipelineTaskModel model(graph, target);
|
||||||
return packPipelineAssignment(graph, model,
|
return packPipelineAssignment(graph, model,
|
||||||
{{taskStages.begin(), taskStages.end()}, {stageSizes.begin(), stageSizes.end()}},
|
{{taskStages.begin(), taskStages.end()}, {stageSizes.begin(), stageSizes.end()}, {}},
|
||||||
schedule, stageSizes.size(), target, error);
|
schedule, stageSizes.size(), target, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
@@ -114,6 +114,7 @@ struct PipelineSearchResult {
|
|||||||
std::vector<WholeScheduleCost> costs;
|
std::vector<WholeScheduleCost> costs;
|
||||||
std::vector<size_t> parents;
|
std::vector<size_t> parents;
|
||||||
size_t rounds = 0, packingAttempts = 0, consideredAssignments = 0;
|
size_t rounds = 0, packingAttempts = 0, consideredAssignments = 0;
|
||||||
|
double neighborhoodSeconds = 0, packingSeconds = 0;
|
||||||
std::string termination;
|
std::string termination;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+23
-5
@@ -11,15 +11,18 @@
|
|||||||
namespace onnx_mlir::spatial {
|
namespace onnx_mlir::spatial {
|
||||||
|
|
||||||
// Greedy maximal prefixes minimize ordered pack count: both constraints are
|
// Greedy maximal prefixes minimize ordered pack count: both constraints are
|
||||||
// closed under taking subranges. The optimum cost cannot increase with packs.
|
// closed under taking subranges. Feasible optimum costs cannot increase with packs.
|
||||||
inline Cost estimateOrderedPackCost(
|
inline std::optional<Cost> estimateOrderedPackCost(
|
||||||
llvm::ArrayRef<Cost> taskCosts,
|
llvm::ArrayRef<Cost> taskCosts,
|
||||||
const std::vector<std::vector<size_t>> &taskWeightIds, size_t weightCount,
|
const std::vector<std::vector<size_t>> &taskWeightIds, size_t weightCount,
|
||||||
llvm::ArrayRef<size_t> tasks, size_t residentWeightCapacity,
|
llvm::ArrayRef<size_t> tasks, size_t residentWeightCapacity,
|
||||||
size_t maximumPacks, std::vector<size_t> *taskPacks = nullptr) {
|
size_t maximumPacks, std::vector<size_t> *taskPacks = nullptr) {
|
||||||
|
if (taskPacks) taskPacks->clear();
|
||||||
|
if (!maximumPacks) return std::nullopt;
|
||||||
Cost low = 0;
|
Cost low = 0;
|
||||||
Cost high = 0;
|
Cost high = 0;
|
||||||
for (size_t task : tasks) {
|
for (size_t task : tasks) {
|
||||||
|
if (taskWeightIds[task].size() > residentWeightCapacity) return std::nullopt;
|
||||||
low = std::max(low, taskCosts[task]);
|
low = std::max(low, taskCosts[task]);
|
||||||
high = checkedAdd(high, taskCosts[task]);
|
high = checkedAdd(high, taskCosts[task]);
|
||||||
}
|
}
|
||||||
@@ -65,6 +68,7 @@ inline Cost estimateOrderedPackCost(
|
|||||||
}
|
}
|
||||||
return fits;
|
return fits;
|
||||||
};
|
};
|
||||||
|
if (!pack(high, nullptr)) return std::nullopt;
|
||||||
while (low < high) {
|
while (low < high) {
|
||||||
Cost middle = low + (high - low) / 2;
|
Cost middle = low + (high - low) / 2;
|
||||||
if (pack(middle, nullptr))
|
if (pack(middle, nullptr))
|
||||||
@@ -73,9 +77,7 @@ inline Cost estimateOrderedPackCost(
|
|||||||
low = middle + 1;
|
low = middle + 1;
|
||||||
}
|
}
|
||||||
if (taskPacks) {
|
if (taskPacks) {
|
||||||
taskPacks->clear();
|
pack(low, taskPacks);
|
||||||
if (!pack(low, taskPacks))
|
|
||||||
taskPacks->clear();
|
|
||||||
}
|
}
|
||||||
return low;
|
return low;
|
||||||
}
|
}
|
||||||
@@ -113,6 +115,22 @@ struct IndexedResidentWeights {
|
|||||||
return std::max<size_t>(!tasks.empty(), count / capacity + (count % capacity != 0));
|
return std::max<size_t>(!tasks.empty(), count / capacity + (count % capacity != 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<Cost> estimatePackCost(llvm::ArrayRef<size_t> tasks,
|
||||||
|
llvm::ArrayRef<Cost> costs, size_t capacity, size_t packCount,
|
||||||
|
std::vector<size_t> *taskPacks = nullptr) const {
|
||||||
|
if (taskPacks) taskPacks->clear();
|
||||||
|
if (!packCount) return std::nullopt;
|
||||||
|
if (auto ordered = estimateOrderedPackCost(costs, taskWeightIds,
|
||||||
|
weightCount, tasks, capacity, packCount, taskPacks)) return ordered;
|
||||||
|
auto assignment = pack(tasks, costs, capacity, packCount);
|
||||||
|
if (!assignment) return std::nullopt;
|
||||||
|
std::vector<Cost> loads(packCount);
|
||||||
|
for (auto [index, task] : llvm::enumerate(tasks))
|
||||||
|
loads[(*assignment)[index]] = checkedAdd(loads[(*assignment)[index]], costs[task]);
|
||||||
|
if (taskPacks) *taskPacks = std::move(*assignment);
|
||||||
|
return *llvm::max_element(loads);
|
||||||
|
}
|
||||||
|
|
||||||
// A constructive reuse-first best fit; failure is not a proof of infeasibility.
|
// A constructive reuse-first best fit; failure is not a proof of infeasibility.
|
||||||
std::optional<std::vector<size_t>> pack(
|
std::optional<std::vector<size_t>> pack(
|
||||||
llvm::ArrayRef<size_t> tasks, llvm::ArrayRef<Cost> costs,
|
llvm::ArrayRef<size_t> tasks, llvm::ArrayRef<Cost> costs,
|
||||||
|
|||||||
+1
@@ -67,6 +67,7 @@ struct WholeScheduleCost {
|
|||||||
std::vector<double> processorTaskWorkNs, processorCriticalDelayNs;
|
std::vector<double> processorTaskWorkNs, processorCriticalDelayNs;
|
||||||
struct AnalysisTiming {
|
struct AnalysisTiming {
|
||||||
double extractionSeconds = 0, expansionSeconds = 0, evaluationSeconds = 0;
|
double extractionSeconds = 0, expansionSeconds = 0, evaluationSeconds = 0;
|
||||||
|
double dependencySeconds = 0;
|
||||||
} timing;
|
} timing;
|
||||||
std::optional<bool> convergenceProved() const;
|
std::optional<bool> convergenceProved() const;
|
||||||
std::optional<bool> loweringSupported() const;
|
std::optional<bool> loweringSupported() const;
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ add_pim_unittest(SpatialSchedulingTargetTest
|
|||||||
|
|
||||||
add_pim_unittest(PeriodicDependenciesTest
|
add_pim_unittest(PeriodicDependenciesTest
|
||||||
PeriodicDependenciesTest.cpp
|
PeriodicDependenciesTest.cpp
|
||||||
|
PeriodicDependenciesReference.cpp
|
||||||
|
|
||||||
LINK_LIBS PRIVATE
|
LINK_LIBS PRIVATE
|
||||||
OMPimCompilerUtils
|
OMPimCompilerUtils
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
// Test-only reference from commit 3c7679594beb29ba9497aaf3e341cee74fa0be50.
|
||||||
|
#include "src/Accelerators/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/PeriodicDependencies.hpp"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
|
||||||
|
mlir::FailureOr<PeriodicDependencyEstimate> analyzeFrozenPeriodicDependencies(
|
||||||
|
size_t nodeCount, llvm::ArrayRef<PeriodicDependency> edges,
|
||||||
|
std::string &error) {
|
||||||
|
std::vector<std::vector<size_t>> outgoing(nodeCount);
|
||||||
|
std::vector<size_t> indegree(nodeCount), recurrent;
|
||||||
|
Cost upper = 0;
|
||||||
|
for (auto [index, edge] : llvm::enumerate(edges)) {
|
||||||
|
if (edge.source >= nodeCount || edge.target >= nodeCount) {
|
||||||
|
error = "periodic dependency references an invalid event";
|
||||||
|
return mlir::failure();
|
||||||
|
}
|
||||||
|
if (edge.delayNs > std::numeric_limits<Cost>::max() - upper) {
|
||||||
|
error = "periodic delay sum exceeds the timing range";
|
||||||
|
return mlir::failure();
|
||||||
|
}
|
||||||
|
upper += edge.delayNs;
|
||||||
|
if (edge.distance) {
|
||||||
|
recurrent.push_back(index);
|
||||||
|
} else {
|
||||||
|
outgoing[edge.source].push_back(index);
|
||||||
|
++indegree[edge.target];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::vector<size_t> order;
|
||||||
|
for (size_t node = 0; node < nodeCount; ++node)
|
||||||
|
if (!indegree[node])
|
||||||
|
order.push_back(node);
|
||||||
|
for (size_t cursor = 0; cursor < order.size(); ++cursor)
|
||||||
|
for (size_t index : outgoing[order[cursor]])
|
||||||
|
if (!--indegree[edges[index].target])
|
||||||
|
order.push_back(edges[index].target);
|
||||||
|
if (order.size() != nodeCount) {
|
||||||
|
error = "periodic distance-zero dependencies contain a cycle";
|
||||||
|
return mlir::failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
PeriodicDependencyEstimate result;
|
||||||
|
std::vector<Cost> span(nodeCount);
|
||||||
|
for (size_t node : order) {
|
||||||
|
result.withinGenerationSpanNs =
|
||||||
|
std::max(result.withinGenerationSpanNs, span[node]);
|
||||||
|
for (size_t index : outgoing[node]) {
|
||||||
|
const auto &edge = edges[index];
|
||||||
|
span[edge.target] = std::max(span[edge.target],
|
||||||
|
checkedAdd(span[node], edge.delayNs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (recurrent.empty() || !upper)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
const size_t absent = std::numeric_limits<size_t>::max();
|
||||||
|
std::vector<double> potential(nodeCount);
|
||||||
|
std::vector<size_t> predecessor(nodeCount, absent);
|
||||||
|
auto infeasible = [&](double interval) {
|
||||||
|
std::fill(potential.begin(), potential.end(), 0);
|
||||||
|
std::fill(predecessor.begin(), predecessor.end(), absent);
|
||||||
|
auto relax = [&](size_t index) {
|
||||||
|
const auto &edge = edges[index];
|
||||||
|
double value = potential[edge.source] + edge.delayNs
|
||||||
|
- interval * edge.distance;
|
||||||
|
if (value <= potential[edge.target] + 1e-7)
|
||||||
|
return false;
|
||||||
|
potential[edge.target] = value;
|
||||||
|
predecessor[edge.target] = index;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
for (size_t node : order)
|
||||||
|
for (size_t index : outgoing[node])
|
||||||
|
relax(index);
|
||||||
|
// Eliminate distance-zero paths by a DAG sweep. A simple residual path
|
||||||
|
// visits at most R recurrent edges, so R+1 rounds detect a positive cycle.
|
||||||
|
for (size_t round = 0; round <= recurrent.size(); ++round) {
|
||||||
|
++result.relaxationRounds;
|
||||||
|
bool changed = false;
|
||||||
|
for (size_t index : recurrent)
|
||||||
|
changed |= relax(index);
|
||||||
|
for (size_t node : order)
|
||||||
|
for (size_t index : outgoing[node])
|
||||||
|
changed |= relax(index);
|
||||||
|
if (!changed)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
result.recurrenceUpperNs = upper;
|
||||||
|
for (unsigned iteration = 0;
|
||||||
|
iteration < 64 && result.recurrenceUpperNs - result.recurrenceLowerNs > 1;
|
||||||
|
++iteration) {
|
||||||
|
double middle = (result.recurrenceLowerNs + result.recurrenceUpperNs) / 2;
|
||||||
|
if (middle == result.recurrenceLowerNs || middle == result.recurrenceUpperNs)
|
||||||
|
break;
|
||||||
|
if (infeasible(middle))
|
||||||
|
result.recurrenceLowerNs = middle;
|
||||||
|
else
|
||||||
|
result.recurrenceUpperNs = middle;
|
||||||
|
}
|
||||||
|
if (result.recurrenceUpperNs - result.recurrenceLowerNs > 1) {
|
||||||
|
error = "periodic timing exceeds one-nanosecond floating-point precision";
|
||||||
|
return mlir::failure();
|
||||||
|
}
|
||||||
|
if (!infeasible(result.recurrenceLowerNs))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
// Recover witness cycles from the last longest-path predecessor forest.
|
||||||
|
std::vector<size_t> seen(nodeCount, absent);
|
||||||
|
double best = 0;
|
||||||
|
for (size_t start = 0; start < nodeCount; ++start) {
|
||||||
|
size_t node = start;
|
||||||
|
while (seen[node] == absent && predecessor[node] != absent) {
|
||||||
|
seen[node] = start;
|
||||||
|
node = edges[predecessor[node]].source;
|
||||||
|
}
|
||||||
|
if (seen[node] != start || predecessor[node] == absent)
|
||||||
|
continue;
|
||||||
|
size_t first = node;
|
||||||
|
Cost delay = 0, distance = 0;
|
||||||
|
std::vector<size_t> cycle;
|
||||||
|
do {
|
||||||
|
size_t index = predecessor[node];
|
||||||
|
cycle.push_back(index);
|
||||||
|
delay = checkedAdd(delay, edges[index].delayNs);
|
||||||
|
distance = checkedAdd(distance, static_cast<Cost>(edges[index].distance));
|
||||||
|
node = edges[index].source;
|
||||||
|
} while (node != first);
|
||||||
|
double ratio = distance ? static_cast<double>(delay) / distance : 0;
|
||||||
|
if (ratio > best) {
|
||||||
|
best = ratio;
|
||||||
|
std::reverse(cycle.begin(), cycle.end());
|
||||||
|
result.criticalCycle = std::move(cycle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
@@ -5,11 +5,96 @@
|
|||||||
#error "Periodic dependency tests require assertions"
|
#error "Periodic dependency tests require assertions"
|
||||||
#endif
|
#endif
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
using namespace onnx_mlir::spatial;
|
using namespace onnx_mlir::spatial;
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
mlir::FailureOr<PeriodicDependencyEstimate> analyzeFrozenPeriodicDependencies(
|
||||||
|
size_t, llvm::ArrayRef<PeriodicDependency>, std::string &);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t differentialCases = 0, acceleratedRounds = 0, frozenRounds = 0;
|
||||||
|
static double acceleratedSeconds = 0, frozenSeconds = 0;
|
||||||
|
|
||||||
|
static void differential(size_t count, const std::vector<PeriodicDependency> &edges) {
|
||||||
|
std::string actualError, referenceError;
|
||||||
|
auto start = std::chrono::steady_clock::now();
|
||||||
|
auto actual = analyzePeriodicDependencies(count, edges, actualError);
|
||||||
|
acceleratedSeconds += std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count();
|
||||||
|
start = std::chrono::steady_clock::now();
|
||||||
|
auto reference = analyzeFrozenPeriodicDependencies(count, edges, referenceError);
|
||||||
|
frozenSeconds += std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count();
|
||||||
|
++differentialCases;
|
||||||
|
assert(mlir::succeeded(actual) == mlir::succeeded(reference));
|
||||||
|
assert(actualError == referenceError);
|
||||||
|
if (mlir::failed(actual)) return;
|
||||||
|
acceleratedRounds += actual->relaxationRounds;
|
||||||
|
frozenRounds += reference->relaxationRounds;
|
||||||
|
assert(actual->recurrenceLowerNs == reference->recurrenceLowerNs);
|
||||||
|
assert(actual->recurrenceUpperNs == reference->recurrenceUpperNs);
|
||||||
|
assert(actual->withinGenerationSpanNs == reference->withinGenerationSpanNs);
|
||||||
|
assert(actual->criticalCycle == reference->criticalCycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testFrozenDifferential() {
|
||||||
|
std::mt19937 random(171221);
|
||||||
|
for (size_t sample = 0; sample < 800; ++sample) {
|
||||||
|
const size_t count = sample < 600 ? 2 + random() % 31 : 64 + random() % 193;
|
||||||
|
std::vector<PeriodicDependency> edges;
|
||||||
|
for (size_t source = 0; source < count; ++source) {
|
||||||
|
const size_t component = source / 32;
|
||||||
|
for (size_t link = 0; link < 4; ++link) {
|
||||||
|
size_t target = random() % count;
|
||||||
|
if (sample % 3 == 0 && target / 32 != component) continue;
|
||||||
|
unsigned distance = target > source && link % 2 == 0 ? 0 : 1 + random() % 7;
|
||||||
|
edges.push_back({source, target, random() % 100001, distance});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
differential(count, edges);
|
||||||
|
}
|
||||||
|
for (size_t sample = 0; sample < 100; ++sample) {
|
||||||
|
std::vector<PeriodicDependency> edges;
|
||||||
|
for (size_t node = 0; node < 32; ++node) {
|
||||||
|
edges.push_back({node, node, (Cost{1} << 40) + random() % 10001,
|
||||||
|
static_cast<unsigned>(1024 + random() % 7)});
|
||||||
|
edges.push_back({node, (node + 1) % 32, random() % 101, 1});
|
||||||
|
}
|
||||||
|
differential(32, edges);
|
||||||
|
}
|
||||||
|
differential(2, {{0,1,1,0}, {1,0,1000000000000ULL, std::numeric_limits<unsigned>::max()}});
|
||||||
|
for (size_t count : {2u, 64u, 1024u}) {
|
||||||
|
std::vector<PeriodicDependency> edges;
|
||||||
|
for (size_t node = 0; node < count; ++node) {
|
||||||
|
edges.push_back({node, node, 1000000 + node % 2, 3});
|
||||||
|
if (node + 1 < count) edges.push_back({node, node + 1, 1, 0});
|
||||||
|
}
|
||||||
|
differential(count, edges);
|
||||||
|
}
|
||||||
|
std::vector<PeriodicDependency> large;
|
||||||
|
constexpr size_t componentSize = 2048, components = 32;
|
||||||
|
for (size_t component = 0; component < components; ++component) {
|
||||||
|
size_t first = component * componentSize;
|
||||||
|
for (size_t node = first; node + 1 < first + componentSize; ++node)
|
||||||
|
large.push_back({node, node + 1, 1, 0});
|
||||||
|
large.push_back({first + componentSize - 1, first, 17 + component,
|
||||||
|
static_cast<unsigned>(1 + component % 7)});
|
||||||
|
}
|
||||||
|
differential(componentSize * components, large);
|
||||||
|
differential(0, {});
|
||||||
|
differential(2, {{0, 1, 0, 0}, {1, 0, 0, 0}});
|
||||||
|
differential(2, {{0, 1, 10, 0}, {1, 0, 3, 0}});
|
||||||
|
differential(1, {{0, 1, 1, 1}});
|
||||||
|
differential(1, {{0, 0, Cost{1} << 60, 1}});
|
||||||
|
differential(1, {{0, 0, std::numeric_limits<Cost>::max(), 1}, {0, 0, 1, 1}});
|
||||||
|
std::cout << "frozen_differential_cases=" << differentialCases
|
||||||
|
<< " accelerated_rounds=" << acceleratedRounds << " frozen_rounds=" << frozenRounds
|
||||||
|
<< " accelerated_seconds=" << acceleratedSeconds << " frozen_seconds=" << frozenSeconds << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
static double enumerateCycles(size_t count,
|
static double enumerateCycles(size_t count,
|
||||||
const std::vector<PeriodicDependency> &edges) {
|
const std::vector<PeriodicDependency> &edges) {
|
||||||
double maximum = 0;
|
double maximum = 0;
|
||||||
@@ -59,11 +144,22 @@ static void check(size_t count, const std::vector<PeriodicDependency> &edges,
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
testFrozenDifferential();
|
||||||
check(3, {{0, 1, 12, 0}, {1, 2, 7, 0}}, 0);
|
check(3, {{0, 1, 12, 0}, {1, 2, 7, 0}}, 0);
|
||||||
check(3, {{0, 1, 12, 1}, {1, 2, 7, 1}}, 0);
|
check(3, {{0, 1, 12, 1}, {1, 2, 7, 1}}, 0);
|
||||||
check(1, {{0, 0, 30, 2}}, 15);
|
check(1, {{0, 0, 30, 2}}, 15);
|
||||||
// Independent core periods remain parallel.
|
// Independent core periods remain parallel.
|
||||||
check(2, {{0, 0, 10, 1}, {1, 1, 20, 1}}, 20);
|
check(2, {{0, 0, 10, 1}, {1, 1, 20, 1}}, 20);
|
||||||
|
// Positive-cycle checks must not change the optimum or the final witness.
|
||||||
|
std::vector<PeriodicDependency> independent;
|
||||||
|
for (size_t core = 0; core < 128; ++core)
|
||||||
|
independent.push_back({core, core, 1000 + core, 1});
|
||||||
|
check(128, independent, 1127);
|
||||||
|
std::string performanceError;
|
||||||
|
auto accelerated = analyzePeriodicDependencies(128, independent, performanceError);
|
||||||
|
assert(mlir::succeeded(accelerated));
|
||||||
|
assert(accelerated->criticalCycle == std::vector<size_t>{127});
|
||||||
|
assert(accelerated->relaxationRounds < 512);
|
||||||
// Equal serial demand, different cross-core blocking recurrences.
|
// Equal serial demand, different cross-core blocking recurrences.
|
||||||
check(4, {{0, 1, 10, 0}, {2, 3, 10, 0},
|
check(4, {{0, 1, 10, 0}, {2, 3, 10, 0},
|
||||||
{1, 0, 0, 1}, {3, 2, 0, 1}}, 10);
|
{1, 0, 0, 1}, {3, 2, 0, 1}}, 10);
|
||||||
|
|||||||
@@ -243,6 +243,17 @@ static void testSharedEngine(PimInstructionTiming target) {
|
|||||||
actual = analyzePimExecution(abstract, target);
|
actual = analyzePimExecution(abstract, target);
|
||||||
TEST_CHECK(actual.getInteger("global_read_requests") == 2);
|
TEST_CHECK(actual.getInteger("global_read_requests") == 2);
|
||||||
TEST_CHECK(actual.getInteger("global_memory_service_ns") == 160);
|
TEST_CHECK(actual.getInteger("global_memory_service_ns") == 160);
|
||||||
|
// The construction limit applies to interpreted patterns, not just traffic.
|
||||||
|
// These zero-trip request bodies do not allocate a large event graph.
|
||||||
|
abstract[0] = {{E::Work, 4}, {E::Repeat, 1500000, 0, 0, 0,
|
||||||
|
{{E::Repeat, 0, 0, 0, 0, {{E::Load, 1}}}}}};
|
||||||
|
auto largerPattern = evaluatePimExecution(abstract, target);
|
||||||
|
TEST_CHECK(largerPattern.cost.available());
|
||||||
|
TEST_CHECK(largerPattern.report.getInteger("global_read_requests") == 0);
|
||||||
|
abstract[0] = {{E::Repeat, 2000001, 0, 0, 0, {{E::Load, 1}}}};
|
||||||
|
auto aboveBudget = evaluatePimExecution(abstract, target);
|
||||||
|
TEST_CHECK(!aboveBudget.cost.available());
|
||||||
|
TEST_CHECK(aboveBudget.cost.reason == "abstract repetition budget exceeded");
|
||||||
abstract[0] = {{E::Repeat, 1000000000000ull, 0, 0, 0, {{E::Load, 1}}}};
|
abstract[0] = {{E::Repeat, 1000000000000ull, 0, 0, 0, {{E::Load, 1}}}};
|
||||||
actual = analyzePimExecution(abstract, target);
|
actual = analyzePimExecution(abstract, target);
|
||||||
TEST_CHECK(actual.getString("status") == "unavailable");
|
TEST_CHECK(actual.getString("status") == "unavailable");
|
||||||
@@ -433,6 +444,48 @@ static void testDependencyTokens() {
|
|||||||
TEST_CHECK(drained.empty() && drained.remaining().empty());
|
TEST_CHECK(drained.empty() && drained.remaining().empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void testSpatialExtractionBudget(PimInstructionTiming target) {
|
||||||
|
MLIRContext context;
|
||||||
|
context.loadDialect<arith::ArithDialect, func::FuncDialect, scf::SCFDialect,
|
||||||
|
tensor::TensorDialect, spatial::SpatialDialect>();
|
||||||
|
auto module = ModuleOp::create(UnknownLoc::get(&context));
|
||||||
|
OpBuilder builder(module.getBodyRegion());
|
||||||
|
auto loc = module.getLoc();
|
||||||
|
auto function = func::FuncOp::create(builder, loc, "test", builder.getFunctionType({}, {}));
|
||||||
|
builder.setInsertionPointToStart(function.addEntryBlock());
|
||||||
|
auto batch = spatial::SpatScheduledComputeBatch::create(builder, loc, TypeRange{},
|
||||||
|
builder.getI32IntegerAttr(2), ValueRange{}, ValueRange{});
|
||||||
|
batch->setAttr("coreIds", builder.getDenseI32ArrayAttr({0, 1}));
|
||||||
|
batch.getBody().push_back(new Block);
|
||||||
|
batch.getBody().front().addArgument(builder.getIndexType(), loc);
|
||||||
|
builder.setInsertionPointToStart(&batch.getBody().front());
|
||||||
|
Value zero = arith::ConstantIndexOp::create(builder, loc, 0);
|
||||||
|
Value upper = arith::ConstantIndexOp::create(builder, loc, 100000);
|
||||||
|
Value one = arith::ConstantIndexOp::create(builder, loc, 1);
|
||||||
|
auto type = RankedTensorType::get({16}, builder.getF32Type());
|
||||||
|
Value local = tensor::EmptyOp::create(builder, loc, type.getShape(), type.getElementType());
|
||||||
|
spatial::SpatReluOp::create(builder, loc, type, local);
|
||||||
|
auto loop = scf::ForOp::create(builder, loc, zero, upper, one);
|
||||||
|
builder.setInsertionPointToStart(loop.getBody());
|
||||||
|
for (int64_t i = 0; i < 5; ++i) arith::ConstantIndexOp::create(builder, loc, i);
|
||||||
|
// A zero-count wait is runtime-free, but requires indexed interpretation.
|
||||||
|
spatial::SpatWaitOp::create(builder, loc, zero, zero);
|
||||||
|
builder.setInsertionPointAfter(loop);
|
||||||
|
spatial::SpatYieldOp::create(builder, loc, ValueRange{});
|
||||||
|
builder.setInsertionPointAfter(batch);
|
||||||
|
func::ReturnOp::create(builder, loc);
|
||||||
|
TEST_CHECK(succeeded(verify(module)));
|
||||||
|
auto supported = evaluatePimSpatialSchedule(module, target);
|
||||||
|
TEST_CHECK(supported.report.getInteger("visited_operations") > 1000000);
|
||||||
|
TEST_CHECK(supported.cost.available());
|
||||||
|
builder.setInsertionPointToStart(loop.getBody());
|
||||||
|
for (int64_t i = 0; i < 5; ++i) arith::ConstantIndexOp::create(builder, loc, i);
|
||||||
|
auto unsupported = evaluatePimSpatialSchedule(module, target);
|
||||||
|
TEST_CHECK(!unsupported.cost.available());
|
||||||
|
TEST_CHECK(unsupported.cost.reason == "abstract extraction budget exceeded on physical core 0");
|
||||||
|
module.erase();
|
||||||
|
}
|
||||||
|
|
||||||
static void testReadOnlySpatial(PimInstructionTiming target, bool batched) {
|
static void testReadOnlySpatial(PimInstructionTiming target, bool batched) {
|
||||||
MLIRContext context;
|
MLIRContext context;
|
||||||
context.loadDialect<arith::ArithDialect, func::FuncDialect, scf::SCFDialect,
|
context.loadDialect<arith::ArithDialect, func::FuncDialect, scf::SCFDialect,
|
||||||
@@ -918,6 +971,7 @@ int main() {
|
|||||||
testCapturedVectorWeights(hardware(period));
|
testCapturedVectorWeights(hardware(period));
|
||||||
testPublicationAndOutput(hardware(period));
|
testPublicationAndOutput(hardware(period));
|
||||||
testUnrepresentableSpatial(hardware(period));
|
testUnrepresentableSpatial(hardware(period));
|
||||||
|
testSpatialExtractionBudget(hardware(period));
|
||||||
testReadOnlySpatial(hardware(period), false);
|
testReadOnlySpatial(hardware(period), false);
|
||||||
testReadOnlySpatial(hardware(period), true);
|
testReadOnlySpatial(hardware(period), true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include "src/Accelerators/PIM/Compiler/PimInstructionTiming.hpp"
|
#include "src/Accelerators/PIM/Compiler/PimInstructionTiming.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/PipelineScheduling.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/PipelineScheduling.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/ScheduleEvaluation.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/ScheduleEvaluation.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/ResidentWeightPacking.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -14,6 +16,76 @@ using namespace onnx_mlir::spatial;
|
|||||||
using E = PimExecutionEvent;
|
using E = PimExecutionEvent;
|
||||||
static bool sawJointMove = false, sawSuccessiveMoves = false;
|
static bool sawJointMove = false, sawSuccessiveMoves = false;
|
||||||
|
|
||||||
|
static void testPackingFeasibility() {
|
||||||
|
ComputeGraph graph;
|
||||||
|
for (size_t task = 0; task < 4; ++task) {
|
||||||
|
ResidentWeight weight;
|
||||||
|
weight.opaqueLane = task % 2;
|
||||||
|
graph.nodes.push_back({{nullptr, static_cast<uint32_t>(task), 1}, 10, {weight}, task});
|
||||||
|
}
|
||||||
|
IndexedResidentWeights weights(graph);
|
||||||
|
const std::vector<size_t> tasks{0, 1, 2, 3};
|
||||||
|
const std::vector<Cost> work{10, 10, 10, 10};
|
||||||
|
std::vector<size_t> assignment{99};
|
||||||
|
TEST_CHECK(!estimateOrderedPackCost(work, weights.taskWeightIds, weights.weightCount,
|
||||||
|
tasks, 1, 2, &assignment));
|
||||||
|
TEST_CHECK(assignment.empty());
|
||||||
|
TEST_CHECK(!estimateOrderedPackCost(work, weights.taskWeightIds, weights.weightCount,
|
||||||
|
tasks, 1, 0));
|
||||||
|
TEST_CHECK(weights.estimatePackCost(tasks, work, 1, 2) == 20);
|
||||||
|
auto placement = weights.pack(tasks, work, 1, 2);
|
||||||
|
TEST_CHECK(placement && (*placement)[0] == (*placement)[2]
|
||||||
|
&& (*placement)[1] == (*placement)[3] && (*placement)[0] != (*placement)[1]);
|
||||||
|
TEST_CHECK(!weights.estimatePackCost(tasks, work, 1, 1));
|
||||||
|
for (size_t cores = 4; cores <= 8; ++cores)
|
||||||
|
TEST_CHECK(estimateOrderedPackCost(work, weights.taskWeightIds, weights.weightCount,
|
||||||
|
tasks, 1, cores) == 10);
|
||||||
|
weights.taskWeightIds[0] = {0, 1};
|
||||||
|
TEST_CHECK(!estimateOrderedPackCost(work, weights.taskWeightIds, weights.weightCount,
|
||||||
|
tasks, 1, 4));
|
||||||
|
weights.taskWeightIds = {{0, 1}, {2}, {0, 1}, {2}};
|
||||||
|
weights.weightCount = 3;
|
||||||
|
TEST_CHECK(!estimateOrderedPackCost(work, weights.taskWeightIds, weights.weightCount,
|
||||||
|
tasks, 2, 2));
|
||||||
|
TEST_CHECK(weights.estimatePackCost(tasks, work, 2, 2) == 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testQuantizedFeasibility(uint32_t first) {
|
||||||
|
mlir::MLIRContext context;
|
||||||
|
context.loadDialect<spatial::SpatialDialect>();
|
||||||
|
auto module = mlir::ModuleOp::create(mlir::UnknownLoc::get(&context));
|
||||||
|
mlir::OpBuilder builder(module.getBodyRegion());
|
||||||
|
ComputeGraph graph;
|
||||||
|
graph.predecessors.resize(40); graph.successors.resize(40);
|
||||||
|
for (uint32_t group = 0; group < 2; ++group) {
|
||||||
|
const uint32_t lanes = group ? 40 - first : first;
|
||||||
|
auto batch = spatial::SpatGraphComputeBatch::create(builder, module.getLoc(),
|
||||||
|
mlir::TypeRange{}, builder.getI32IntegerAttr(lanes), mlir::ValueRange{}, mlir::ValueRange{});
|
||||||
|
for (uint32_t lane = 0; lane < lanes; ++lane) {
|
||||||
|
size_t task = graph.nodes.size();
|
||||||
|
ResidentWeight weight; weight.opaqueLane = task;
|
||||||
|
graph.nodes.push_back({{batch, lane, 1}, 10, {weight}, task});
|
||||||
|
if (group) {
|
||||||
|
graph.predecessors[task].push_back({0, {0, 0}});
|
||||||
|
graph.successors[0].push_back({task, {0, 0}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SchedulingTarget target;
|
||||||
|
target.processorCount = 40; target.residentWeightCapacity = 1;
|
||||||
|
target.interProcessorLatencyNs.assign(1600, 0);
|
||||||
|
MergeScheduleResult schedule;
|
||||||
|
std::string error;
|
||||||
|
TEST_CHECK(mlir::succeeded(runPipelineScheduler(graph, schedule, 2, target, error)));
|
||||||
|
std::vector<size_t> budgets(2);
|
||||||
|
for (size_t stage : schedule.processorStages) ++budgets[stage];
|
||||||
|
TEST_CHECK(budgets == std::vector<size_t>({first, 40 - first}));
|
||||||
|
for (size_t task = 0; task < graph.nodes.size(); ++task)
|
||||||
|
TEST_CHECK(schedule.processorStages[schedule.computeToCpuMap.lookup(graph.nodes[task].instance)]
|
||||||
|
== (task < first ? 0 : 1));
|
||||||
|
module.erase();
|
||||||
|
}
|
||||||
|
|
||||||
// Test-only workloads use stage-boundary host transfers and local direct links.
|
// Test-only workloads use stage-boundary host transfers and local direct links.
|
||||||
// The same queue
|
// The same queue
|
||||||
// engine and complete-state evidence rank reference and searched schedules.
|
// engine and complete-state evidence rank reference and searched schedules.
|
||||||
@@ -112,6 +184,176 @@ static PimInstructionTiming hardware(size_t profile) {
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void testInitializerPackingWitness() {
|
||||||
|
mlir::MLIRContext context;
|
||||||
|
context.loadDialect<spatial::SpatialDialect>();
|
||||||
|
auto module = mlir::ModuleOp::create(mlir::UnknownLoc::get(&context));
|
||||||
|
mlir::OpBuilder builder(module.getBodyRegion());
|
||||||
|
std::vector<mlir::Operation *> operations;
|
||||||
|
for (size_t i = 0; i < 3; ++i) operations.push_back(
|
||||||
|
spatial::SpatGraphComputeBatch::create(builder, module.getLoc(),
|
||||||
|
mlir::TypeRange{}, builder.getI32IntegerAttr(3), mlir::ValueRange{}, mlir::ValueRange{}));
|
||||||
|
const std::vector<std::vector<size_t>> resident{{2,1,5},{2,4},{0,2},{0},{4,6},{1},{2},{2},{1,4}};
|
||||||
|
const std::vector<Cost> costs{4,1,5,4,1,2,5,2,2};
|
||||||
|
ComputeGraph graph;
|
||||||
|
graph.predecessors.resize(9); graph.successors.resize(9);
|
||||||
|
for (uint32_t task = 0; task < 9; ++task) {
|
||||||
|
ResidentWeightSet weights;
|
||||||
|
for (size_t id : resident[task]) {
|
||||||
|
ResidentWeight w; w.opaqueLane = id; weights.push_back(w);
|
||||||
|
}
|
||||||
|
graph.nodes.push_back({{operations[task % 3], task / 3, 1}, costs[task], weights, task});
|
||||||
|
}
|
||||||
|
IndexedResidentWeights indexed(graph);
|
||||||
|
TEST_CHECK(!indexed.estimatePackCost({1,2,4,5,7,8}, costs, 3, 2));
|
||||||
|
std::vector<size_t> witness;
|
||||||
|
TEST_CHECK(indexed.estimatePackCost({1,4,7,2,5,8}, costs, 3, 2, &witness));
|
||||||
|
TEST_CHECK(witness.size() == 6);
|
||||||
|
auto target = hardware(1);
|
||||||
|
target.processorCount = 4; target.residentWeightCapacity = 3;
|
||||||
|
target.interProcessorLatencyNs.assign(16, 1);
|
||||||
|
MergeScheduleResult schedule;
|
||||||
|
std::string error;
|
||||||
|
TEST_CHECK(mlir::succeeded(runPipelineScheduler(graph, schedule, 2, target, error)));
|
||||||
|
std::vector<ResidentWeightSet> actual(4);
|
||||||
|
for (const auto &node : graph.nodes) {
|
||||||
|
size_t core = schedule.computeToCpuMap.lookup(node.instance);
|
||||||
|
TEST_CHECK(core < 4);
|
||||||
|
insertResidentWeights(actual[core], node.residentWeights);
|
||||||
|
TEST_CHECK(actual[core].size() <= 3);
|
||||||
|
}
|
||||||
|
// Exhaust all core assignments under the same stage map. The initialized
|
||||||
|
// assignment must belong to this physical-capacity reference set.
|
||||||
|
size_t feasible = 0;
|
||||||
|
for (size_t encoded = 0; encoded < (size_t{1} << 18); ++encoded) {
|
||||||
|
size_t choices = encoded;
|
||||||
|
std::vector<ResidentWeightSet> weights(4);
|
||||||
|
std::vector<size_t> groupStages(3, 2);
|
||||||
|
bool valid = true;
|
||||||
|
for (size_t task = 0; task < 9 && valid; ++task) {
|
||||||
|
size_t core = choices % 4; choices /= 4;
|
||||||
|
size_t stage = schedule.processorStages[core], group = task % 3;
|
||||||
|
if (groupStages[group] == 2) groupStages[group] = stage;
|
||||||
|
valid &= groupStages[group] == stage;
|
||||||
|
insertResidentWeights(weights[core], graph.nodes[task].residentWeights);
|
||||||
|
valid &= weights[core].size() <= 3;
|
||||||
|
}
|
||||||
|
feasible += valid;
|
||||||
|
}
|
||||||
|
TEST_CHECK(feasible > 0);
|
||||||
|
MergeScheduleResult alternative;
|
||||||
|
TEST_CHECK(mlir::succeeded(packPipelineSchedule(graph, std::vector<size_t>(9),
|
||||||
|
{3, 1}, target, alternative, error)));
|
||||||
|
std::cout << "initializer_physical_feasible_assignments=" << feasible << '\n';
|
||||||
|
module.erase();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testPhysicalReference() {
|
||||||
|
auto target = hardware(1);
|
||||||
|
target.processorCount = 3;
|
||||||
|
target.sharedMemoryRequestLatencyNs.resize(3);
|
||||||
|
target.sharedMemoryResponseLatencyNs.resize(3);
|
||||||
|
target.interProcessorLatencyNs = {0, 2, 11, 2, 0, 7, 11, 7, 0};
|
||||||
|
ComputeGraph graph;
|
||||||
|
graph.predecessors.resize(3); graph.successors.resize(3);
|
||||||
|
for (uint32_t task = 0; task < 3; ++task) {
|
||||||
|
ResidentWeight weight; weight.opaqueLane = task % 2;
|
||||||
|
graph.nodes.push_back({{nullptr, task, 1}, task == 1 ? 700u : 80u, {weight}, task});
|
||||||
|
if (task) {
|
||||||
|
graph.predecessors[task].push_back({task - 1, {64, 1}});
|
||||||
|
graph.successors[task - 1].push_back({task, {64, 1}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
double optimum = std::numeric_limits<double>::infinity();
|
||||||
|
size_t evaluations = 0;
|
||||||
|
for (size_t mask = 1; mask < 7; ++mask)
|
||||||
|
for (size_t placement = 0; placement < 27; ++placement) {
|
||||||
|
MergeScheduleResult schedule;
|
||||||
|
for (size_t core = 0; core < 3; ++core) schedule.processorStages.push_back((mask >> core) & 1);
|
||||||
|
size_t encoded = placement;
|
||||||
|
std::vector<size_t> stages, slots(3);
|
||||||
|
std::vector<ResidentWeightSet> resident(3);
|
||||||
|
bool valid = true;
|
||||||
|
for (const auto &node : graph.nodes) {
|
||||||
|
size_t core = encoded % 3; encoded /= 3;
|
||||||
|
schedule.computeToCpuMap[node.instance] = core;
|
||||||
|
schedule.computeToCpuSlotMap[node.instance] = slots[core]++;
|
||||||
|
stages.push_back(schedule.processorStages[core]);
|
||||||
|
insertResidentWeights(resident[core], node.residentWeights);
|
||||||
|
valid &= resident[core].size() <= target.residentWeightCapacity;
|
||||||
|
}
|
||||||
|
if (!valid || stages[0] != 0 || stages[1] < stages[0] || stages[2] < stages[1]) continue;
|
||||||
|
auto cost = queueCost(graph, schedule, target);
|
||||||
|
optimum = std::min(optimum, std::max(*cost.primary, *cost.alternative));
|
||||||
|
++evaluations;
|
||||||
|
}
|
||||||
|
std::string error;
|
||||||
|
MergeScheduleResult initial;
|
||||||
|
TEST_CHECK(mlir::succeeded(runPipelineScheduler(graph, initial, 2, target, error)));
|
||||||
|
auto evaluate = [&](const MergeScheduleResult &schedule) { return queueCost(graph, schedule, target); };
|
||||||
|
auto search = optimizePipelineSchedule(graph, initial, 2, target, evaluate, error);
|
||||||
|
TEST_CHECK(mlir::succeeded(search));
|
||||||
|
size_t winner = search->costs.empty() ? 0 : selectPipelineCandidate(search->costs, error);
|
||||||
|
auto selected = evaluate(search->schedules[winner]);
|
||||||
|
double value = std::max(*selected.primary, *selected.alternative);
|
||||||
|
TEST_CHECK(value + 1e-6 >= optimum);
|
||||||
|
std::cout << "physical_reference_ns=" << optimum << " selected_ns=" << value
|
||||||
|
<< " gap_percent=" << 100 * (value / optimum - 1)
|
||||||
|
<< " physical_evaluations=" << evaluations
|
||||||
|
<< " search_evaluations=" << search->costs.size()
|
||||||
|
<< " packing_attempts=" << search->packingAttempts << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testWorseningIntermediate() {
|
||||||
|
// A controlled cost oracle isolates the search's non-monotone landscape.
|
||||||
|
// Hardware queue objectives are covered separately by runReference().
|
||||||
|
auto target = hardware(0);
|
||||||
|
target.processorCount = 8;
|
||||||
|
target.interProcessorLatencyNs.assign(64, 0);
|
||||||
|
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}, 10, {}, task});
|
||||||
|
auto evaluate = [&](const MergeScheduleResult &schedule) {
|
||||||
|
bool firstStage = llvm::all_of(graph.nodes, [&](const auto &node) {
|
||||||
|
return schedule.processorStages[schedule.computeToCpuMap.lookup(node.instance)] == 0;
|
||||||
|
});
|
||||||
|
size_t cores = llvm::count(schedule.processorStages, 0);
|
||||||
|
double value = !firstStage ? 120 : cores == 1 ? 80 : cores >= 6 ? 100 : 110;
|
||||||
|
WholeScheduleCost cost;
|
||||||
|
cost.primary = cost.alternative = value;
|
||||||
|
cost.loweringSupport = StaticLoweringSupport::Supported;
|
||||||
|
cost.selectionMargin = kPipelineSelectionMargin;
|
||||||
|
for (auto &proof : cost.recurrence)
|
||||||
|
proof = {ScheduleRecurrenceEvidence::Proved, 1, value, {}};
|
||||||
|
return cost;
|
||||||
|
};
|
||||||
|
std::string error;
|
||||||
|
MergeScheduleResult initial, intermediate;
|
||||||
|
TEST_CHECK(mlir::succeeded(runPipelineScheduler(graph, initial, 2, target, error)));
|
||||||
|
TEST_CHECK(evaluate(initial).primary == 100);
|
||||||
|
const std::vector<size_t> stages(4);
|
||||||
|
TEST_CHECK(mlir::succeeded(packPipelineSchedule(graph, stages, {3, 5}, target, intermediate, error)));
|
||||||
|
TEST_CHECK(evaluate(intermediate).primary == 110);
|
||||||
|
double reference = std::numeric_limits<double>::infinity();
|
||||||
|
for (size_t cores = 1; cores < 8; ++cores) {
|
||||||
|
MergeScheduleResult schedule;
|
||||||
|
TEST_CHECK(mlir::succeeded(packPipelineSchedule(graph, stages, {cores, 8 - cores}, target, schedule, error)));
|
||||||
|
reference = std::min(reference, *evaluate(schedule).primary);
|
||||||
|
}
|
||||||
|
TEST_CHECK(reference == 80);
|
||||||
|
auto search = optimizePipelineSchedule(graph, initial, 2, target, evaluate, error);
|
||||||
|
TEST_CHECK(mlir::succeeded(search));
|
||||||
|
size_t winner = selectPipelineCandidate(search->costs, error);
|
||||||
|
double selected = *search->costs[winner].primary;
|
||||||
|
TEST_CHECK(selected >= reference && selected <= 100);
|
||||||
|
TEST_CHECK(search->costs.size() <= kPipelineQueueEvaluationBudget);
|
||||||
|
std::cout << "non_monotone_oracle_reference=" << reference << " selected=" << selected
|
||||||
|
<< " gap_percent=" << 100 * (selected / reference - 1)
|
||||||
|
<< " evaluations=" << search->costs.size()
|
||||||
|
<< " packing_attempts=" << search->packingAttempts << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
static void runReference(size_t profile, size_t fixture) {
|
static void runReference(size_t profile, size_t fixture) {
|
||||||
auto target = hardware(profile);
|
auto target = hardware(profile);
|
||||||
ComputeGraph graph;
|
ComputeGraph graph;
|
||||||
@@ -217,8 +459,10 @@ static void runReference(size_t profile, size_t fixture) {
|
|||||||
<< " gap_percent=" << 100 * (cost / reference - 1)
|
<< " gap_percent=" << 100 * (cost / reference - 1)
|
||||||
<< " best_evaluated_ns=" << bestEvaluated
|
<< " best_evaluated_ns=" << bestEvaluated
|
||||||
<< " best_evaluated_gap_percent=" << 100 * (bestEvaluated / reference - 1)
|
<< " best_evaluated_gap_percent=" << 100 * (bestEvaluated / reference - 1)
|
||||||
|
<< " reference_scope=logical_with_deterministic_packing"
|
||||||
<< " reference_evaluations=" << referenceEvaluations
|
<< " reference_evaluations=" << referenceEvaluations
|
||||||
<< " search_evaluations=" << search->costs.size() << " refinement_depth=" << depth
|
<< " search_evaluations=" << search->costs.size()
|
||||||
|
<< " packing_attempts=" << search->packingAttempts << " refinement_depth=" << depth
|
||||||
<< " search_seconds=" << searchSeconds << " reference_seconds=" << referenceSeconds
|
<< " search_seconds=" << searchSeconds << " reference_seconds=" << referenceSeconds
|
||||||
<< " total_seconds=" << std::chrono::duration<double>(std::chrono::steady_clock::now() - begin).count()
|
<< " total_seconds=" << std::chrono::duration<double>(std::chrono::steady_clock::now() - begin).count()
|
||||||
<< '\n';
|
<< '\n';
|
||||||
@@ -246,6 +490,11 @@ static void runReference(size_t profile, size_t fixture) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
testInitializerPackingWitness();
|
||||||
|
testPackingFeasibility();
|
||||||
|
for (uint32_t first : {19u, 21u}) testQuantizedFeasibility(first);
|
||||||
|
testPhysicalReference();
|
||||||
|
testWorseningIntermediate();
|
||||||
for (size_t profile = 0; profile < 2; ++profile)
|
for (size_t profile = 0; profile < 2; ++profile)
|
||||||
for (size_t fixture = 0; fixture < 6; ++fixture) runReference(profile, fixture);
|
for (size_t fixture = 0; fixture < 6; ++fixture) runReference(profile, fixture);
|
||||||
TEST_CHECK(sawJointMove && sawSuccessiveMoves);
|
TEST_CHECK(sawJointMove && sawSuccessiveMoves);
|
||||||
|
|||||||
@@ -1642,14 +1642,14 @@ static void testOrderedPackCostCurve() {
|
|||||||
if (fits && packs <= cores) optimum = std::min(optimum, std::max(maximum, work));
|
if (fits && packs <= cores) optimum = std::min(optimum, std::max(maximum, work));
|
||||||
}
|
}
|
||||||
std::vector<size_t> assignment;
|
std::vector<size_t> assignment;
|
||||||
Cost cost = estimateOrderedPackCost(costs, weights, 3, tasks, capacity, cores, &assignment);
|
auto cost = estimateOrderedPackCost(costs, weights, 3, tasks, capacity, cores, &assignment);
|
||||||
if (optimum == std::numeric_limits<Cost>::max()) {
|
if (optimum == std::numeric_limits<Cost>::max()) {
|
||||||
assert(assignment.empty());
|
assert(!cost && assignment.empty());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
assert(cost == optimum && cost <= previous && assignment.size() == tasks.size());
|
assert(cost && *cost == optimum && *cost <= previous && assignment.size() == tasks.size());
|
||||||
plateau |= cost == previous;
|
plateau |= *cost == previous;
|
||||||
previous = cost;
|
previous = *cost;
|
||||||
std::vector<Cost> loads(cores);
|
std::vector<Cost> loads(cores);
|
||||||
std::vector<llvm::SmallBitVector> resident(cores, llvm::SmallBitVector(3));
|
std::vector<llvm::SmallBitVector> resident(cores, llvm::SmallBitVector(3));
|
||||||
for (size_t task : tasks) {
|
for (size_t task : tasks) {
|
||||||
@@ -1658,7 +1658,7 @@ static void testOrderedPackCostCurve() {
|
|||||||
for (size_t weight : weights[task]) resident[assignment[task]].set(weight);
|
for (size_t weight : weights[task]) resident[assignment[task]].set(weight);
|
||||||
}
|
}
|
||||||
for (size_t core = 0; core < cores; ++core) {
|
for (size_t core = 0; core < cores; ++core) {
|
||||||
assert(loads[core] <= cost && resident[core].count() <= capacity);
|
assert(loads[core] <= *cost && resident[core].count() <= capacity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert(plateau && previous == 11);
|
assert(plateau && previous == 11);
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ def main():
|
|||||||
parser.add_argument("--compiler", required=True)
|
parser.add_argument("--compiler", required=True)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
compiler = str(Path(args.compiler).resolve())
|
compiler = str(Path(args.compiler).resolve())
|
||||||
model = REPO / "validation/networks/pimcomp_models/googlenet/googlenet-12-pimsim-nn.onnx"
|
model = REPO / "validation/networks/pimcomp_models/googlenet/googlenet-12-no-lrn.onnx"
|
||||||
with tempfile.TemporaryDirectory(prefix="raptor-static-selection-") as temporary:
|
with tempfile.TemporaryDirectory(prefix="raptor-static-selection-") as temporary:
|
||||||
root = Path(temporary)
|
root = Path(temporary)
|
||||||
history = root / "validation/tools/pim/pimcomp/compare/results_comparison.csv"
|
history = root / "validation/tools/pim/pimcomp/compare/results_comparison.csv"
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ operations, standalone commands, configuration layout, and GA/OpenMP controls.
|
|||||||
|
|
||||||
| Directory | Model / input | Source |
|
| Directory | Model / input | Source |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `resnet18/` | ResNet-18 v1, `1x3x224x224` | [ONNX Model Zoo resnet18-v1-7](https://huggingface.co/onnxmodelzoo/resnet18-v1-7); symlink to `../resnet18/depth_68/resnet18_depth_68.onnx`. |
|
| `resnet18/` | ResNet-18 v1, `1x3x224x224` | [ONNX Model Zoo resnet18-v1-7](https://huggingface.co/onnxmodelzoo/resnet18-v1-7); symlink to `../../resnet18/depth_68/resnet18_depth_68.onnx`. |
|
||||||
| `resnet34/` | ResNet-34 v1, `1x3x224x224` | [ONNX Model Zoo resnet34-v1-7](https://huggingface.co/onnxmodelzoo/resnet34-v1-7); symbolic batch fixed to 1. |
|
| `resnet34/` | ResNet-34 v1, `1x3x224x224` | [ONNX Model Zoo resnet34-v1-7](https://huggingface.co/onnxmodelzoo/resnet34-v1-7); symbolic batch fixed to 1. |
|
||||||
| `googlenet/` | GoogLeNet, `1x3x224x224` | [ONNX Model Zoo googlenet-12](https://huggingface.co/onnxmodelzoo/googlenet-12), with only its two LRN nodes bypassed. |
|
| `googlenet/` | GoogLeNet, `1x3x224x224` | [ONNX Model Zoo googlenet-12](https://huggingface.co/onnxmodelzoo/googlenet-12), with only its two LRN nodes bypassed. |
|
||||||
| `vgg8/` | VGG-8 reconstruction, `1x1x28x28` | Six convolution and two fully connected layers with deterministic random weights. |
|
| `vgg8/` | VGG-8 reconstruction, `1x1x28x28` | Six convolution and two fully connected layers with deterministic random weights. |
|
||||||
| `yolo11n/` | YOLO11n detection, `1x3x640x640` | Uses `../yolo11n/depth_51/yolo11n_depth_51.onnx`; this directory retains the historical Softmax-free variant. |
|
| `yolo11n/` | YOLO11n detection, `1x3x640x640` | [Ultralytics YOLO11n](https://docs.ultralytics.com/models/yolo11/); symlink to `../../yolo11n/depth_51/yolo11n_depth_51.onnx`. |
|
||||||
|
|
||||||
The paper's checkpoints were not published. The three Model Zoo networks
|
The paper's checkpoints were not published. The three Model Zoo networks
|
||||||
produce frontend graphs matching Pimcomp's bundled JSON graphs. VGG-8 is
|
produce frontend graphs matching Pimcomp's bundled JSON graphs. VGG-8 is
|
||||||
@@ -29,15 +29,9 @@ Its terminal Softmax, output `prob_1`, weights, and other operations are retaine
|
|||||||
The native reference uses this same graph. VGG8 and both ResNet graphs have no
|
The native reference uses this same graph. VGG8 and both ResNet graphs have no
|
||||||
removed operators; ResNet-34 fixes the symbolic batch size to one.
|
removed operators; ResNet-34 fixes the symbolic batch size to one.
|
||||||
|
|
||||||
Historical performance models change the original computation:
|
The current YOLO comparison uses `yolo11n/yolo11n.onnx`,
|
||||||
|
|
||||||
- `googlenet/googlenet-12-pimsim-nn.onnx` removes two LRN nodes and terminal
|
|
||||||
Softmax, which Pimcomp does not schedule.
|
|
||||||
- `yolo11n/yolo11n-pimsim-nn.onnx` removes two internal Softmax nodes. It remains
|
|
||||||
available for reproducing historical measurements.
|
|
||||||
|
|
||||||
The current YOLO comparison uses `../yolo11n/depth_51/yolo11n_depth_51.onnx`,
|
|
||||||
including both Softmax operations. Its native reference uses the same full model.
|
including both Softmax operations. Its native reference uses the same full model.
|
||||||
|
The checked-in ONNX graph uses opset 18. The original exporter version and checkpoint hash are not recorded.
|
||||||
The functional simulator executes Softmax. Pimsim-nn skips `vsoftmax` before
|
The functional simulator executes Softmax. Pimsim-nn skips `vsoftmax` before
|
||||||
fetch, and Raptor's PIM queue estimator omits the same instruction cost.
|
fetch, and Raptor's PIM queue estimator omits the same instruction cost.
|
||||||
Timing and energy exclude Softmax work; they do not model hardware Softmax support.
|
Timing and energy exclude Softmax work; they do not model hardware Softmax support.
|
||||||
@@ -50,9 +44,8 @@ Model SHA-256 checksums:
|
|||||||
c3231061d081bdd47884137b02134f85142752a39e87263c529cd14ed242b096 resnet34/resnet34-v1-7.onnx
|
c3231061d081bdd47884137b02134f85142752a39e87263c529cd14ed242b096 resnet34/resnet34-v1-7.onnx
|
||||||
c99c507058eaf41de8723408fdda7db8325cb57f0a89f2ee07a716d6e963e14e googlenet/googlenet-12.onnx
|
c99c507058eaf41de8723408fdda7db8325cb57f0a89f2ee07a716d6e963e14e googlenet/googlenet-12.onnx
|
||||||
c960c195953423388733cd0e60712cf425ca1f8917e264f5cf98e028752c61cf googlenet/googlenet-12-no-lrn.onnx
|
c960c195953423388733cd0e60712cf425ca1f8917e264f5cf98e028752c61cf googlenet/googlenet-12-no-lrn.onnx
|
||||||
a26f9e33901c573e60c34a3f0abbb4744fff83e4e0f21b18fc66e20395e72982 googlenet/googlenet-12-pimsim-nn.onnx
|
|
||||||
396cdea21e5e7d02c3f26f14d22ef20975171702493f5c5e79b8e0d896e541ef vgg8/vgg8-mnist-reconstructed.onnx
|
396cdea21e5e7d02c3f26f14d22ef20975171702493f5c5e79b8e0d896e541ef vgg8/vgg8-mnist-reconstructed.onnx
|
||||||
229f3975af8933d39aee8d9031d969bff074b69c33e304a78abb35ff0c5f445f yolo11n/yolo11n-pimsim-nn.onnx
|
5e7bd85dc60b252674f8d7e1ad24f2317d00dfb8c524826536698462de04620d yolo11n/yolo11n.onnx
|
||||||
```
|
```
|
||||||
|
|
||||||
## Hardware profiles
|
## Hardware profiles
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
../../yolo11n/depth_51/yolo11n_depth_51.onnx
|
||||||
@@ -11,7 +11,7 @@ MODELS = {
|
|||||||
"resnet18": SUITE / "resnet18/resnet18-v1-7.onnx",
|
"resnet18": SUITE / "resnet18/resnet18-v1-7.onnx",
|
||||||
"resnet34": SUITE / "resnet34/resnet34-v1-7.onnx",
|
"resnet34": SUITE / "resnet34/resnet34-v1-7.onnx",
|
||||||
"googlenet": SUITE / "googlenet/googlenet-12-no-lrn.onnx",
|
"googlenet": SUITE / "googlenet/googlenet-12-no-lrn.onnx",
|
||||||
"yolo11n": REPO / "validation/networks/yolo11n/depth_51/yolo11n_depth_51.onnx",
|
"yolo11n": SUITE / "yolo11n/yolo11n.onnx",
|
||||||
}
|
}
|
||||||
MODEL_NAMES = tuple(MODELS)
|
MODEL_NAMES = tuple(MODELS)
|
||||||
DEFAULT_MODELS = MODEL_NAMES
|
DEFAULT_MODELS = MODEL_NAMES
|
||||||
|
|||||||
@@ -511,7 +511,9 @@ def test_comparison_uses_same_model_for_reference_and_execution(tmp_path, monkey
|
|||||||
|
|
||||||
|
|
||||||
def test_yolo_uses_full_model_without_moving_comparison_artifacts():
|
def test_yolo_uses_full_model_without_moving_comparison_artifacts():
|
||||||
assert suite.MODELS["yolo11n"] == suite.REPO / "validation/networks/yolo11n/depth_51/yolo11n_depth_51.onnx"
|
assert suite.MODELS["yolo11n"] == suite.SUITE / "yolo11n/yolo11n.onnx"
|
||||||
|
assert suite.MODELS["yolo11n"].is_symlink()
|
||||||
|
assert suite.MODELS["yolo11n"].resolve() == suite.REPO / "validation/networks/yolo11n/depth_51/yolo11n_depth_51.onnx"
|
||||||
assert suite.model_dir(None, "yolo11n") == suite.SUITE / "yolo11n"
|
assert suite.model_dir(None, "yolo11n") == suite.SUITE / "yolo11n"
|
||||||
assert suite.common_dir(None, "yolo11n") == suite.SUITE / "yolo11n/artifacts/common-full-model"
|
assert suite.common_dir(None, "yolo11n") == suite.SUITE / "yolo11n/artifacts/common-full-model"
|
||||||
|
|
||||||
@@ -756,3 +758,43 @@ def test_ablation_aggregate_keeps_pass_with_zero_performance(tmp_path):
|
|||||||
assert rows[0]["functional_validation"] == "PASS"
|
assert rows[0]["functional_validation"] == "PASS"
|
||||||
assert rows[0]["baseline_functional_validation"] == "PASS"
|
assert rows[0]["baseline_functional_validation"] == "PASS"
|
||||||
assert all(rows[0][f"{metric}_gain_percent"] == "NA" for _, metric in ablation.PERCENTAGE_FIELDS)
|
assert all(rows[0][f"{metric}_gain_percent"] == "NA" for _, metric in ablation.PERCENTAGE_FIELDS)
|
||||||
|
|
||||||
|
|
||||||
|
def test_completed_no_sync_ablation_preserves_numerical_failure():
|
||||||
|
report = {"pimsim_mode": "throughput", "raptor_extra_args": ["--pim-disable-synchronization"],
|
||||||
|
"raptor_validation": {"status": "done", "passed": False, "max_diffs": {"output": 1}},
|
||||||
|
"raptor_performance": {"throughput": 100, "output_count": 8},
|
||||||
|
"steps": [{"name": "Functional validation Raptor", "status": "passed"}],
|
||||||
|
"failures": []}
|
||||||
|
assert suite.completed_synchronization_ablation(report)
|
||||||
|
assert not suite.comparison_passed(report)
|
||||||
|
assert suite.functional_validation_status(report["raptor_validation"]) == "FAIL"
|
||||||
|
for field, value in (("pimsim_mode", "latency"), ("raptor_extra_args", []),
|
||||||
|
("raptor_validation", {"status": "failed", "passed": False}),
|
||||||
|
("raptor_validation", {"status": "done", "passed": False}),
|
||||||
|
("raptor_performance", {"error": "SIGSEGV", "throughput": 100}),
|
||||||
|
("raptor_performance", {"throughput": 100, "output_count": 0}),
|
||||||
|
("steps", [{"status": "failed"}]),
|
||||||
|
("failures", [{"stage": "Compile Raptor Pim", "error": "failed"}])):
|
||||||
|
assert not suite.completed_synchronization_ablation({**report, field: value})
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("process_failed", [False, True])
|
||||||
|
@pytest.mark.parametrize("variant,expected", [("no-sync", 0), ("no-plan", 1), ("baseline", 1)])
|
||||||
|
def test_ablation_runner_distinguishes_invalid_outputs_from_failed_execution(tmp_path, monkeypatch, variant, expected, process_failed):
|
||||||
|
report = {"pimsim_mode": "throughput", "raptor_extra_args": ["--pim-disable-synchronization"],
|
||||||
|
"raptor_validation": {"status": "done", "passed": False, "max_diffs": {"output": 1}},
|
||||||
|
"raptor_performance": {"throughput": 100, "output_count": 8},
|
||||||
|
"steps": [{"status": "passed"}], "failures": []}
|
||||||
|
output = suite.result_dir(tmp_path, "vgg8", "arch-a", "throughput", 4, variant) / "pimcomp/comparison_report.json"
|
||||||
|
output.parent.mkdir(parents=True)
|
||||||
|
output.write_text(json.dumps(report))
|
||||||
|
csv = tmp_path / "results.csv"
|
||||||
|
csv.write_text("summary\n")
|
||||||
|
monkeypatch.setattr(suite, "run", lambda *a, **k: 0)
|
||||||
|
monkeypatch.setattr(suite, "run_comparison_jobs", lambda *a: (["arch-a/vgg8/throughput/pipeline4"] if process_failed else [], 1))
|
||||||
|
monkeypatch.setattr(suite, "write_results_csv", lambda *a: csv)
|
||||||
|
monkeypatch.setattr(sys, "argv", ["run_pimcomp_models.py", "--out-dir", str(tmp_path),
|
||||||
|
"--models", "vgg8", "--archs", "arch-a", "--mode", "throughput", "--pipeline", "4",
|
||||||
|
"--raptor-only", "--ablation-variant", variant, "--jobs", "1"])
|
||||||
|
assert suite.main() == (1 if process_failed else expected)
|
||||||
|
|||||||
@@ -59,8 +59,10 @@ Both programs' functional statuses are recorded as `functional_validation`
|
|||||||
and `baseline_functional_validation`. The latter belongs to the disabled feature.
|
and `baseline_functional_validation`. The latter belongs to the disabled feature.
|
||||||
|
|
||||||
Disabling synchronization removes host-buffer lifetime and pipeline restart
|
Disabling synchronization removes host-buffer lifetime and pipeline restart
|
||||||
guarantees, so those variants may fail numerical validation and exit nonzero.
|
guarantees, so those variants may fail numerical validation. A completed
|
||||||
Baseline and `no-plan` must pass. Percentages involving failing programs remain
|
`no-sync` measurement retains `FAIL` in the CSV and is reported as diagnostic
|
||||||
|
in the runner summary. Compile, execution, missing-output, and measurement
|
||||||
|
failures still make the run fail. Baseline and `no-plan` must pass. Percentages involving failing programs remain
|
||||||
diagnostic comparisons, not valid speedups.
|
diagnostic comparisons, not valid speedups.
|
||||||
|
|
||||||
When fast-mode convergence fails, positive fallback throughput remains available.
|
When fast-mode convergence fails, positive fallback throughput remains available.
|
||||||
|
|||||||
@@ -1,65 +1,25 @@
|
|||||||
variant,arch,model,mode,raptor_pipeline,functional_validation,latency,throughput,power,energy
|
comparison,arch,model,mode,raptor_pipeline,baseline_variant,baseline_functional_validation,functional_validation,latency_gain_percent,throughput_gain_percent,power_gain_percent,energy_gain_percent
|
||||||
baseline,arch-a,googlenet,latency,1,PASS,+0.00%,NA,+0.00%,+0.00%
|
plan,arch-a,googlenet,latency,1,no-plan,PASS,PASS,+99.14%,NA,-226.98%,+97.19%
|
||||||
no-sync,arch-a,googlenet,latency,1,PASS,+0.00%,NA,+0.00%,-0.00%
|
plan,arch-a,googlenet,throughput,4,no-plan,PASS,PASS,NA,NA,-281.83%,NA
|
||||||
no-plan,arch-a,googlenet,latency,1,PASS,+11513.29%,NA,-69.42%,+3452.69%
|
sync,arch-a,googlenet,throughput,4,no-sync,FAIL,PASS,-5.17%,-4.96%,+12.23%,-1.65%
|
||||||
no-sync-no-plan,arch-a,googlenet,latency,1,PASS,+11512.99%,NA,-69.42%,+3452.59%
|
plan,arch-a,resnet18,latency,1,no-plan,PASS,PASS,+96.96%,NA,-156.23%,+92.21%
|
||||||
baseline,arch-a,googlenet,throughput,4,PASS,+0.00%,+0.00%,+0.00%,+0.00%
|
plan,arch-a,resnet18,throughput,4,no-plan,PASS,PASS,+95.17%,+1970.00%,-157.48%,+85.39%
|
||||||
no-sync,arch-a,googlenet,throughput,4,FAIL,+26.17%,-20.74%,+9.97%,+18.50%
|
sync,arch-a,resnet18,throughput,4,no-sync,FAIL,PASS,+7.15%,+7.81%,+14.70%,+7.92%
|
||||||
no-plan,arch-a,googlenet,throughput,4,PASS,-100.00%,-100.00%,-62.73%,-100.00%
|
plan,arch-a,resnet34,latency,1,no-plan,PASS,PASS,+95.94%,NA,-130.05%,+90.65%
|
||||||
no-sync-no-plan,arch-a,googlenet,throughput,4,FAIL,+4094.63%,-97.62%,-59.86%,+1311.01%
|
plan,arch-a,resnet34,throughput,4,no-plan,PASS,PASS,+96.00%,+2400.00%,-189.18%,+88.43%
|
||||||
baseline,arch-a,resnet18,latency,1,PASS,+0.00%,NA,+0.00%,+0.00%
|
sync,arch-a,resnet34,throughput,4,no-sync,FAIL,PASS,-9.11%,-8.42%,+7.32%,-1.02%
|
||||||
no-sync,arch-a,resnet18,latency,1,PASS,+0.00%,NA,+0.00%,+0.00%
|
plan,arch-a,vgg8,latency,1,no-plan,PASS,PASS,+94.75%,NA,-80.65%,+90.51%
|
||||||
no-plan,arch-a,resnet18,latency,1,PASS,+3191.30%,NA,-60.97%,+1184.24%
|
plan,arch-a,vgg8,throughput,4,no-plan,PASS,PASS,+92.77%,+1279.31%,-170.46%,+82.49%
|
||||||
no-sync-no-plan,arch-a,resnet18,latency,1,PASS,+3191.30%,NA,-60.97%,+1184.24%
|
sync,arch-a,vgg8,throughput,4,no-sync,FAIL,PASS,+19.18%,+24.03%,+21.29%,+18.62%
|
||||||
baseline,arch-a,resnet18,throughput,4,PASS,+0.00%,+0.00%,+0.00%,+0.00%
|
plan,arch-b,googlenet,latency,1,no-plan,PASS,PASS,+98.99%,NA,-239.10%,+96.59%
|
||||||
no-sync,arch-a,resnet18,throughput,4,FAIL,+110.65%,-52.55%,+43.25%,+177.48%
|
plan,arch-b,googlenet,throughput,4,no-plan,PASS,PASS,NA,NA,-306.99%,NA
|
||||||
no-plan,arch-a,resnet18,throughput,4,PASS,+1598.93%,-94.12%,-51.04%,+664.87%
|
sync,arch-b,googlenet,throughput,4,no-sync,FAIL,PASS,-1.28%,-1.63%,+9.99%,-4.29%
|
||||||
no-sync-no-plan,arch-a,resnet18,throughput,4,FAIL,+1174.21%,-92.16%,-45.06%,+543.74%
|
plan,arch-b,resnet18,latency,1,no-plan,PASS,PASS,+96.71%,NA,-171.16%,+91.07%
|
||||||
baseline,arch-a,resnet34,latency,1,PASS,+0.00%,NA,+0.00%,+0.00%
|
plan,arch-b,resnet18,throughput,4,no-plan,PASS,PASS,+95.46%,+2102.50%,-200.52%,+83.26%
|
||||||
no-sync,arch-a,resnet34,latency,1,PASS,+0.00%,NA,+0.00%,+0.00%
|
sync,arch-b,resnet18,throughput,4,no-sync,FAIL,PASS,+2.22%,+2.32%,+8.26%,-7.88%
|
||||||
no-plan,arch-a,resnet34,latency,1,PASS,+2361.37%,NA,-56.53%,+969.99%
|
plan,arch-b,resnet34,latency,1,no-plan,PASS,PASS,+96.23%,NA,-166.76%,+89.94%
|
||||||
no-sync-no-plan,arch-a,resnet34,latency,1,PASS,+2361.37%,NA,-56.53%,+969.99%
|
plan,arch-b,resnet34,throughput,4,no-plan,PASS,PASS,+97.58%,+4030.00%,-168.71%,+91.79%
|
||||||
baseline,arch-a,resnet34,throughput,4,PASS,+0.00%,+0.00%,+0.00%,+0.00%
|
sync,arch-b,resnet34,throughput,4,no-sync,FAIL,PASS,-0.80%,-0.96%,+10.61%,-13.51%
|
||||||
no-sync,arch-a,resnet34,throughput,4,FAIL,+33.65%,-25.21%,+31.76%,+15.56%
|
plan,arch-b,vgg8,latency,1,no-plan,PASS,PASS,+94.68%,NA,-98.04%,+89.47%
|
||||||
no-plan,arch-a,resnet34,throughput,4,PASS,+1726.82%,-94.52%,-54.50%,+455.98%
|
plan,arch-b,vgg8,throughput,4,no-plan,PASS,PASS,+93.70%,+1484.16%,-205.54%,+81.37%
|
||||||
no-sync-no-plan,arch-a,resnet34,throughput,4,FAIL,+813.41%,-89.04%,-49.77%,+206.84%
|
sync,arch-b,vgg8,throughput,4,no-sync,FAIL,PASS,+17.07%,+20.30%,+19.03%,+9.63%
|
||||||
baseline,arch-a,vgg8,latency,1,PASS,+0.00%,NA,+0.00%,+0.00%
|
|
||||||
no-sync,arch-a,vgg8,latency,1,PASS,+0.00%,NA,+0.00%,+0.00%
|
|
||||||
no-plan,arch-a,vgg8,latency,1,PASS,+1800.66%,NA,-44.65%,+953.39%
|
|
||||||
no-sync-no-plan,arch-a,vgg8,latency,1,PASS,+1800.66%,NA,-44.65%,+953.39%
|
|
||||||
baseline,arch-a,vgg8,throughput,4,PASS,+0.00%,+0.00%,+0.00%,+0.00%
|
|
||||||
no-sync,arch-a,vgg8,throughput,4,PASS,+2.33%,-1.72%,+18.49%,-0.98%
|
|
||||||
no-plan,arch-a,vgg8,throughput,4,PASS,+1251.16%,-92.58%,-56.68%,+577.04%
|
|
||||||
no-sync-no-plan,arch-a,vgg8,throughput,4,PASS,+1260.47%,-92.63%,-53.36%,+467.83%
|
|
||||||
baseline,arch-b,googlenet,latency,1,PASS,+0.00%,NA,+0.00%,+0.00%
|
|
||||||
no-sync,arch-b,googlenet,latency,1,PASS,+0.00%,NA,+0.00%,-0.00%
|
|
||||||
no-plan,arch-b,googlenet,latency,1,PASS,+9849.64%,NA,-70.51%,+2833.89%
|
|
||||||
no-sync-no-plan,arch-b,googlenet,latency,1,PASS,+9849.55%,NA,-70.51%,+2833.89%
|
|
||||||
baseline,arch-b,googlenet,throughput,4,PASS,+0.00%,+0.00%,+0.00%,+0.00%
|
|
||||||
no-sync,arch-b,googlenet,throughput,4,FAIL,+11.45%,-10.29%,+21.82%,+19.27%
|
|
||||||
no-plan,arch-b,googlenet,throughput,4,PASS,-100.00%,-100.00%,-63.39%,-100.00%
|
|
||||||
no-sync-no-plan,arch-b,googlenet,throughput,4,FAIL,+3348.28%,-97.10%,-61.58%,+1026.09%
|
|
||||||
baseline,arch-b,resnet18,latency,1,PASS,+0.00%,NA,+0.00%,+0.00%
|
|
||||||
no-sync,arch-b,resnet18,latency,1,PASS,+0.00%,NA,+0.00%,-0.00%
|
|
||||||
no-plan,arch-b,resnet18,latency,1,PASS,+2935.63%,NA,-63.12%,+1019.81%
|
|
||||||
no-sync-no-plan,arch-b,resnet18,latency,1,PASS,+2936.09%,NA,-63.12%,+1019.97%
|
|
||||||
baseline,arch-b,resnet18,throughput,4,PASS,+0.00%,+0.00%,+0.00%,+0.00%
|
|
||||||
no-sync,arch-b,resnet18,throughput,4,FAIL,+8.03%,-7.44%,+47.59%,+40.61%
|
|
||||||
no-plan,arch-b,resnet18,throughput,4,PASS,+1511.85%,-93.80%,-52.93%,+560.80%
|
|
||||||
no-sync-no-plan,arch-b,resnet18,throughput,4,FAIL,+867.12%,-89.67%,-45.41%,+359.83%
|
|
||||||
baseline,arch-b,resnet34,latency,1,PASS,+0.00%,NA,+0.00%,+0.00%
|
|
||||||
no-sync,arch-b,resnet34,latency,1,PASS,+0.00%,NA,+0.00%,+0.00%
|
|
||||||
no-plan,arch-b,resnet34,latency,1,PASS,+2551.16%,NA,-62.51%,+893.70%
|
|
||||||
no-sync-no-plan,arch-b,resnet34,latency,1,PASS,+2550.91%,NA,-62.51%,+893.62%
|
|
||||||
baseline,arch-b,resnet34,throughput,4,PASS,+0.00%,+0.00%,+0.00%,+0.00%
|
|
||||||
no-sync,arch-b,resnet34,throughput,4,FAIL,+28.25%,-22.13%,+49.76%,+57.30%
|
|
||||||
no-plan,arch-b,resnet34,throughput,4,PASS,+2427.17%,-96.05%,-49.58%,+938.62%
|
|
||||||
no-sync-no-plan,arch-b,resnet34,throughput,4,FAIL,+531.79%,-84.19%,-42.32%,+197.08%
|
|
||||||
baseline,arch-b,vgg8,latency,1,PASS,+0.00%,NA,+0.00%,+0.00%
|
|
||||||
no-sync,arch-b,vgg8,latency,1,PASS,+0.00%,NA,+0.00%,+0.00%
|
|
||||||
no-plan,arch-b,vgg8,latency,1,PASS,+1784.97%,NA,-49.50%,+849.73%
|
|
||||||
no-sync-no-plan,arch-b,vgg8,latency,1,PASS,+1784.97%,NA,-49.50%,+849.73%
|
|
||||||
baseline,arch-b,vgg8,throughput,4,PASS,+0.00%,+0.00%,+0.00%,+0.00%
|
|
||||||
no-sync,arch-b,vgg8,throughput,4,PASS,+23.91%,-19.82%,+25.67%,+23.93%
|
|
||||||
no-plan,arch-b,vgg8,throughput,4,PASS,+1158.70%,-92.07%,-58.95%,+408.33%
|
|
||||||
no-sync-no-plan,arch-b,vgg8,throughput,4,PASS,+1172.83%,-92.17%,-55.83%,+327.00%
|
|
||||||
|
|||||||
|
@@ -1,41 +1,41 @@
|
|||||||
arch,model,mode,raptor_pipeline,pimcomp_pipeline,raptor_functional_validation,pimcomp_functional_validation,raptor_latency_ms,pimcomp_latency_ms,raptor_throughput_samples_s,pimcomp_throughput_samples_s,raptor_power_mw,pimcomp_power_mw,raptor_energy_pj,pimcomp_energy_pj,better_compiler,speedup
|
arch,model,mode,raptor_pipeline,pimcomp_pipeline,raptor_functional_validation,pimcomp_functional_validation,raptor_latency_ms,pimcomp_latency_ms,raptor_throughput_samples_s,pimcomp_throughput_samples_s,raptor_power_mw,pimcomp_power_mw,raptor_energy_pj,pimcomp_energy_pj,better_compiler,speedup
|
||||||
arch-a,vgg8,latency,1,element,PASS,PASS,1.51833,7.98507,NA,NA,321.046,200.111,4.87455e+08,1.5979e+09,raptor,5.26
|
arch-a,vgg8,latency,1,element,PASS,PASS,1.51833,7.98507,NA,NA,321.046,200.111,4.87455e+08,1.5979e+09,raptor,5.26
|
||||||
arch-a,vgg8,throughput,2,batch,PASS,PASS,0.906321,1.0364,1100,965,429.311,482.597,4.07972e+08,5.41692e+08,raptor,1.14
|
arch-a,vgg8,throughput,2,batch,PASS,FAIL,0.625998,1.0364,1600,965,523.278,482.597,4.17419e+08,5.41692e+08,NA,1.66
|
||||||
arch-a,vgg8,throughput,4,batch,PASS,PASS,0.778242,1.0364,1280,965,457.039,482.597,4.15762e+08,5.41692e+08,raptor,1.33
|
arch-a,vgg8,throughput,4,batch,PASS,FAIL,0.625998,1.0364,1600,965,523.278,482.597,4.17419e+08,5.41692e+08,NA,1.66
|
||||||
arch-a,vgg8,throughput,8,batch,PASS,PASS,0.778094,1.0364,1290,965,464.071,482.597,3.96424e+08,5.41692e+08,raptor,1.34
|
arch-a,vgg8,throughput,8,batch,PASS,FAIL,0.62635,1.0364,1600,965,519.182,482.597,4.14242e+08,5.41692e+08,NA,1.66
|
||||||
arch-a,resnet18,latency,1,element,PASS,FAIL,14.0332,58.1775,NA,NA,455.67,238.389,6.3945e+09,1.38689e+10,raptor,4.15
|
arch-a,resnet18,latency,1,element,PASS,FAIL,14.0332,58.1775,NA,NA,455.67,238.389,6.3945e+09,1.38689e+10,NA,4.15
|
||||||
arch-a,resnet18,throughput,2,batch,PASS,FAIL,11.7314,13.4585,85.2,74.3,503.111,481.604,6.62807e+09,6.56345e+09,raptor,1.15
|
arch-a,resnet18,throughput,2,batch,PASS,FAIL,11.0904,13.4585,90.2,74.3,520.244,481.604,6.55167e+09,6.56345e+09,NA,1.21
|
||||||
arch-a,resnet18,throughput,4,batch,PASS,FAIL,19.2072,13.4585,52.1,74.3,380.1,481.604,7.95703e+09,6.56345e+09,pimcomp,1.43
|
arch-a,resnet18,throughput,4,batch,PASS,FAIL,12.0822,13.4585,82.8,74.3,487.576,481.604,6.91662e+09,6.56345e+09,NA,1.11
|
||||||
arch-a,resnet18,throughput,8,batch,PASS,FAIL,14.1578,13.4585,70.6,74.3,453.416,481.604,6.77909e+09,6.56345e+09,pimcomp,1.05
|
arch-a,resnet18,throughput,8,batch,PASS,FAIL,11.9279,13.4585,83.8,74.3,499.354,481.604,6.84729e+09,6.56345e+09,NA,1.13
|
||||||
arch-a,resnet34,latency,1,element,PASS,FAIL,29.7186,66.7114,NA,NA,411.806,277.485,1.22383e+10,1.85114e+10,raptor,2.24
|
arch-a,resnet34,latency,1,element,PASS,FAIL,29.7189,66.7114,NA,NA,411.804,277.485,1.22384e+10,1.85114e+10,NA,2.24
|
||||||
arch-a,resnet34,throughput,2,batch,PASS,FAIL,23.2705,23.5817,43,42.4,471.717,515.68,1.3055e+10,1.22836e+10,raptor,1.01
|
arch-a,resnet34,throughput,2,batch,PASS,FAIL,21.8196,23.5817,45.8,42.4,477.108,515.68,1.57899e+10,1.22836e+10,NA,1.08
|
||||||
arch-a,resnet34,throughput,4,batch,PASS,FAIL,27.468,23.5817,36.4,42.4,422.223,515.68,1.48991e+10,1.22836e+10,pimcomp,1.16
|
arch-a,resnet34,throughput,4,batch,PASS,FAIL,20,23.5817,50,42.4,548.372,515.68,1.09674e+10,1.22836e+10,NA,1.18
|
||||||
arch-a,resnet34,throughput,8,batch,PASS,FAIL,39.8392,23.5817,25.1,42.4,352.28,515.68,1.70627e+10,1.22836e+10,pimcomp,1.69
|
arch-a,resnet34,throughput,8,batch,PASS,FAIL,20.6363,23.5817,48.5,42.4,504.854,515.68,1.23725e+10,1.22836e+10,NA,1.14
|
||||||
arch-a,googlenet,latency,1,element,PASS,PASS,9.77772,62.9248,NA,NA,563.864,231.193,5.5133e+09,1.45477e+10,raptor,6.44
|
arch-a,googlenet,latency,1,element,PASS,FAIL,9.77731,62.9248,NA,NA,563.88,231.193,5.51323e+09,1.45477e+10,NA,6.44
|
||||||
arch-a,googlenet,throughput,2,batch,PASS,FAIL,8.41545,18.2643,119,54.8,610.746,435.107,5.77746e+09,8.16479e+09,raptor,2.17
|
arch-a,googlenet,throughput,2,batch,PASS,FAIL,7.34567,18.2643,136,54.8,679.351,435.107,5.5053e+09,8.16479e+09,NA,2.48
|
||||||
arch-a,googlenet,throughput,4,batch,PASS,FAIL,10.7292,18.2643,93.2,54.8,528.026,435.107,6.59289e+09,8.16479e+09,raptor,1.70
|
arch-a,googlenet,throughput,4,batch,PASS,FAIL,7.43728,18.2643,134,54.8,674.025,435.107,5.60809e+09,8.16479e+09,NA,2.45
|
||||||
arch-a,googlenet,throughput,8,batch,PASS,FAIL,10.1219,18.2643,98.8,54.8,523.808,435.107,6.52174e+09,8.16479e+09,raptor,1.80
|
arch-a,googlenet,throughput,8,batch,PASS,FAIL,9.01886,18.2643,111,54.8,595.582,435.107,5.71605e+09,8.16479e+09,NA,2.03
|
||||||
arch-a,yolo11n,latency,1,element,PASS,FAIL,147.49,NA,NA,NA,290.174,NA,4.27977e+10,NA,NA,NA
|
arch-a,yolo11n,latency,1,element,PASS,FAIL,149.46,NA,NA,NA,288.579,NA,4.31311e+10,NA,NA,NA
|
||||||
arch-a,yolo11n,throughput,2,batch,SKIPPED,FAIL,102.337,NA,9.77,NA,337.882,NA,3.83081e+10,NA,NA,NA
|
arch-a,yolo11n,throughput,2,batch,PASS,FAIL,94.946,NA,10.5,NA,346.466,NA,3.75539e+10,NA,NA,NA
|
||||||
arch-a,yolo11n,throughput,4,batch,SKIPPED,FAIL,87.9103,NA,11.4,NA,358.201,NA,3.87298e+10,NA,NA,NA
|
arch-a,yolo11n,throughput,4,batch,PASS,FAIL,80.1193,NA,12.5,NA,377.408,NA,3.8811e+10,NA,NA,NA
|
||||||
arch-a,yolo11n,throughput,8,batch,SKIPPED,FAIL,82.9508,NA,12.1,NA,366.725,NA,3.85347e+10,NA,NA,NA
|
arch-a,yolo11n,throughput,8,batch,PASS,FAIL,99.5488,NA,10,NA,339.331,NA,4.27113e+10,NA,NA,NA
|
||||||
arch-b,vgg8,latency,1,element,PASS,PASS,1.5333,7.15212,NA,NA,292.559,173.769,4.48582e+08,1.24281e+09,raptor,4.66
|
arch-b,vgg8,latency,1,element,PASS,PASS,1.5333,7.15212,NA,NA,292.559,173.769,4.48582e+08,1.24281e+09,raptor,4.66
|
||||||
arch-b,vgg8,throughput,2,batch,PASS,PASS,0.685556,1.07282,1460,932,456.044,409.47,4.03694e+08,4.39289e+08,raptor,1.57
|
arch-b,vgg8,throughput,2,batch,PASS,FAIL,0.624729,1.07282,1600,932,489.899,409.47,3.93036e+08,4.39289e+08,NA,1.72
|
||||||
arch-b,vgg8,throughput,4,batch,PASS,PASS,0.673192,1.07282,1490,932,448.898,409.47,4.13527e+08,4.39289e+08,raptor,1.60
|
arch-b,vgg8,throughput,4,batch,PASS,FAIL,0.62331,1.07282,1600,932,490.955,409.47,3.93418e+08,4.39289e+08,NA,1.72
|
||||||
arch-b,vgg8,throughput,8,batch,PASS,PASS,0.751106,1.07282,1330,932,425.742,409.47,3.94458e+08,4.39289e+08,raptor,1.43
|
arch-b,vgg8,throughput,8,batch,PASS,FAIL,0.62331,1.07282,1600,932,490.955,409.47,3.93418e+08,4.39289e+08,NA,1.72
|
||||||
arch-b,resnet18,latency,1,element,PASS,FAIL,15.3758,64.3818,NA,NA,400.661,201.663,6.16048e+09,1.29834e+10,raptor,4.19
|
arch-b,resnet18,latency,1,element,PASS,FAIL,15.3758,64.3818,NA,NA,400.661,201.663,6.16048e+09,1.29834e+10,NA,4.19
|
||||||
arch-b,resnet18,throughput,2,batch,PASS,FAIL,12.1214,13.434,82.5,74.4,461.407,484.804,6.29532e+09,6.56966e+09,raptor,1.11
|
arch-b,resnet18,throughput,2,batch,PASS,FAIL,12.9113,13.434,77.5,74.4,438.931,484.804,6.47794e+09,6.56966e+09,NA,1.04
|
||||||
arch-b,resnet18,throughput,4,batch,PASS,FAIL,11.3601,13.434,88,74.4,479.333,484.804,6.98745e+09,6.56966e+09,raptor,1.18
|
arch-b,resnet18,throughput,4,batch,PASS,FAIL,11.3505,13.434,88.1,74.4,478.606,484.804,6.66517e+09,6.56966e+09,NA,1.18
|
||||||
arch-b,resnet18,throughput,8,batch,PASS,FAIL,13.1216,13.434,76.2,74.4,441.248,484.804,6.38271e+09,6.56966e+09,raptor,1.02
|
arch-b,resnet18,throughput,8,batch,PASS,FAIL,11.3685,13.434,88,74.4,478.33,484.804,6.51513e+09,6.56966e+09,NA,1.18
|
||||||
arch-b,resnet34,latency,1,element,PASS,FAIL,27.9541,107.2,NA,NA,397.206,206.476,1.11035e+10,2.21342e+10,raptor,3.83
|
arch-b,resnet34,latency,1,element,PASS,FAIL,27.9541,107.2,NA,NA,397.206,206.476,1.11035e+10,2.21342e+10,NA,3.83
|
||||||
arch-b,resnet34,throughput,2,batch,PASS,FAIL,22.282,22.8454,44.9,43.8,438.238,493.332,1.42187e+10,1.13922e+10,raptor,1.03
|
arch-b,resnet34,throughput,2,batch,PASS,FAIL,22.4967,22.8454,44.5,43.8,440.06,493.332,1.49623e+10,1.13922e+10,NA,1.02
|
||||||
arch-b,resnet34,throughput,4,batch,PASS,FAIL,29.0906,22.8454,34.4,43.8,364.956,493.332,1.63285e+10,1.13922e+10,pimcomp,1.27
|
arch-b,resnet34,throughput,4,batch,PASS,FAIL,24.1838,22.8454,41.3,43.8,426.129,493.332,1.30195e+10,1.13922e+10,NA,1.06
|
||||||
arch-b,resnet34,throughput,8,batch,PASS,FAIL,47.68,22.8454,21,43.8,289.821,493.332,1.63464e+10,1.13922e+10,pimcomp,2.09
|
arch-b,resnet34,throughput,8,batch,PASS,FAIL,24.1516,22.8454,41.4,43.8,420.745,493.332,1.39184e+10,1.13922e+10,NA,1.06
|
||||||
arch-b,googlenet,latency,1,element,PASS,PASS,11.2212,37.9367,NA,NA,483.266,242.037,5.42282e+09,9.18206e+09,raptor,3.38
|
arch-b,googlenet,latency,1,element,PASS,FAIL,11.2206,37.9367,NA,NA,483.283,242.037,5.42274e+09,9.18206e+09,NA,3.38
|
||||||
arch-b,googlenet,throughput,2,batch,PASS,FAIL,9.21209,21.3902,109,46.8,545.488,417.866,5.5934e+09,9.01473e+09,raptor,2.33
|
arch-b,googlenet,throughput,2,batch,PASS,FAIL,9.08396,21.3902,110,46.8,550.292,417.866,5.70509e+09,9.01473e+09,NA,2.35
|
||||||
arch-b,googlenet,throughput,4,batch,PASS,FAIL,8.56194,21.3902,117,46.8,555.264,417.866,5.86312e+09,9.01473e+09,raptor,2.50
|
arch-b,googlenet,throughput,4,batch,PASS,FAIL,8.23423,21.3902,121,46.8,597.327,417.866,5.62683e+09,9.01473e+09,NA,2.59
|
||||||
arch-b,googlenet,throughput,8,batch,PASS,FAIL,10.8652,21.3902,92,46.8,483.477,417.866,6.12683e+09,9.01473e+09,raptor,1.97
|
arch-b,googlenet,throughput,8,batch,PASS,FAIL,7.78944,21.3902,128,46.8,620.65,417.866,5.47039e+09,9.01473e+09,NA,2.74
|
||||||
arch-b,yolo11n,latency,1,element,PASS,FAIL,168.144,NA,NA,NA,245.314,NA,4.1248e+10,NA,NA,NA
|
arch-b,yolo11n,latency,1,element,PASS,FAIL,171.598,NA,NA,NA,243.177,NA,4.17288e+10,NA,NA,NA
|
||||||
arch-b,yolo11n,throughput,2,batch,SKIPPED,FAIL,104.494,NA,9.57,NA,302.121,NA,3.58015e+10,NA,NA,NA
|
arch-b,yolo11n,throughput,2,batch,PASS,FAIL,99.5318,NA,10,NA,309.102,NA,3.5219e+10,NA,NA,NA
|
||||||
arch-b,yolo11n,throughput,4,batch,SKIPPED,FAIL,108.247,NA,9.24,NA,297.484,NA,3.76297e+10,NA,NA,NA
|
arch-b,yolo11n,throughput,4,batch,PASS,FAIL,85.3497,NA,11.7,NA,326.712,NA,3.58691e+10,NA,NA,NA
|
||||||
arch-b,yolo11n,throughput,8,batch,SKIPPED,FAIL,100.129,NA,9.99,NA,308.833,NA,3.71808e+10,NA,NA,NA
|
arch-b,yolo11n,throughput,8,batch,PASS,FAIL,97.9941,NA,10.2,NA,308.041,NA,3.5011e+10,NA,NA,NA
|
||||||
|
|||||||
|
@@ -282,6 +282,23 @@ def comparison_passed(report: dict) -> bool:
|
|||||||
return result.get("status") == "done" and bool(result.get("passed"))
|
return result.get("status") == "done" and bool(result.get("passed"))
|
||||||
|
|
||||||
|
|
||||||
|
def completed_synchronization_ablation(report: dict) -> bool:
|
||||||
|
"""A completed no-sync measurement may have intentionally invalid outputs."""
|
||||||
|
validation = report.get("raptor_validation") or {}
|
||||||
|
performance = report.get("raptor_performance") or {}
|
||||||
|
return (
|
||||||
|
report.get("pimsim_mode") == "throughput"
|
||||||
|
and "--pim-disable-synchronization" in report.get("raptor_extra_args", [])
|
||||||
|
and validation.get("status") == "done" and validation.get("passed") is False
|
||||||
|
and bool(validation.get("max_diffs"))
|
||||||
|
and not report.get("failures")
|
||||||
|
and bool(report.get("steps"))
|
||||||
|
and all(step.get("status") == "passed" for step in report["steps"])
|
||||||
|
and not performance.get("error")
|
||||||
|
and performance_values(performance)["throughput"] is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def functional_validation_status(result: dict | None) -> str:
|
def functional_validation_status(result: dict | None) -> str:
|
||||||
if result is None:
|
if result is None:
|
||||||
return "NA"
|
return "NA"
|
||||||
@@ -869,6 +886,7 @@ def main() -> int:
|
|||||||
assert results_path is not None
|
assert results_path is not None
|
||||||
print_stage(results_path.name, STAGE_COLORS["Compare outputs"])
|
print_stage(results_path.name, STAGE_COLORS["Compare outputs"])
|
||||||
print(results_path.read_text(encoding="utf-8"), end="")
|
print(results_path.read_text(encoding="utf-8"), end="")
|
||||||
|
diagnostic_failures = []
|
||||||
for arch in args.archs:
|
for arch in args.archs:
|
||||||
for name in args.models:
|
for name in args.models:
|
||||||
for mode, pipeline, _ in comparisons_by_arch[arch]:
|
for mode, pipeline, _ in comparisons_by_arch[arch]:
|
||||||
@@ -876,15 +894,19 @@ def main() -> int:
|
|||||||
report_path = result_dir(
|
report_path = result_dir(
|
||||||
out_dir, name, arch, mode, pipeline, args.ablation_variant
|
out_dir, name, arch, mode, pipeline, args.ablation_variant
|
||||||
) / "pimcomp/comparison_report.json"
|
) / "pimcomp/comparison_report.json"
|
||||||
if not report_path.exists() or not comparison_passed(
|
report = json.loads(report_path.read_text(encoding="utf-8")) if report_path.exists() else {}
|
||||||
json.loads(report_path.read_text(encoding="utf-8")),
|
if not comparison_passed(report):
|
||||||
):
|
if (label not in failed and args.ablation_variant == "no-sync"
|
||||||
if label not in failed:
|
and completed_synchronization_ablation(report)):
|
||||||
|
diagnostic_failures.append(label)
|
||||||
|
elif label not in failed:
|
||||||
failed.append(label)
|
failed.append(label)
|
||||||
print(Style.BRIGHT + Fore.CYAN + "Summary" + Style.RESET_ALL)
|
print(Style.BRIGHT + Fore.CYAN + "Summary" + Style.RESET_ALL)
|
||||||
total_jobs = sum(len(args.models) * len(comparisons) for comparisons in comparisons_by_arch.values())
|
total_jobs = sum(len(args.models) * len(comparisons) for comparisons in comparisons_by_arch.values())
|
||||||
print(Style.BRIGHT + f"Passed: {total_jobs - len(failed)}" + Style.RESET_ALL)
|
print(Style.BRIGHT + f"Passed: {total_jobs - len(failed) - len(diagnostic_failures)}" + Style.RESET_ALL)
|
||||||
print(Style.BRIGHT + f"Failed: {len(failed)}" + Style.RESET_ALL)
|
print(Style.BRIGHT + f"Failed: {len(failed)}" + Style.RESET_ALL)
|
||||||
|
if diagnostic_failures:
|
||||||
|
print("No-sync numerical failures (diagnostic only): " + ", ".join(diagnostic_failures))
|
||||||
print(Style.BRIGHT + f"Results: {results_path}" + Style.RESET_ALL)
|
print(Style.BRIGHT + f"Results: {results_path}" + Style.RESET_ALL)
|
||||||
if failed:
|
if failed:
|
||||||
print(
|
print(
|
||||||
|
|||||||
@@ -35,13 +35,15 @@ The corresponding scheduling call is disabled in
|
|||||||
Rust validation starts those buffers at zero, exposing the missing computation.
|
Rust validation starts those buffers at zero, exposing the missing computation.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
.venv/bin/python validation/tools/pim/pimcomp/correctness/prefill/run_experiment.py
|
.venv/bin/python validation/tools/pim/pimcomp/correctness/prefill/run_experiment.py \
|
||||||
|
/path/to/comparison /path/to/matching-model.onnx
|
||||||
```
|
```
|
||||||
|
|
||||||
The default uses GoogLeNet Arch-A throughput/pipeline2. Optional positional
|
The comparison directory and its matching model are required. Optional positional
|
||||||
arguments are comparison directory, model, work directory, and shared reference
|
arguments are work directory and shared reference directory. The default work
|
||||||
directory; the default work directory is `correctness/prefill/` below the
|
directory is `correctness/prefill/` below the comparison artifacts.
|
||||||
comparison artifacts.
|
Historical Softmax-free models can be recovered from Git for historical artifacts.
|
||||||
|
Current GoogLeNet retains Softmax; prefill alone does not correct omitted Softmax execution.
|
||||||
|
|
||||||
The experiment compares the original memory image with one prefilled using
|
The experiment compares the original memory image with one prefilled using
|
||||||
[`batch_memory.py`](prefill/batch_memory.py). In the recorded case, 20 of 39
|
[`batch_memory.py`](prefill/batch_memory.py). In the recorded case, 20 of 39
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ from batch_memory import prefill_batch_memory
|
|||||||
|
|
||||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||||
REPO_ROOT = SCRIPT_DIR.parents[5]
|
REPO_ROOT = SCRIPT_DIR.parents[5]
|
||||||
DEFAULT_COMPARISON_DIR = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/artifacts/arch-a/throughput/pipeline2"
|
|
||||||
DEFAULT_MODEL = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/googlenet-12-pimsim-nn.onnx"
|
|
||||||
DEFAULT_COMMON_DIR = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/artifacts/common"
|
DEFAULT_COMMON_DIR = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/artifacts/common"
|
||||||
SIMULATOR_MANIFEST = REPO_ROOT / "backend-simulators/pim/pim-simulator/Cargo.toml"
|
SIMULATOR_MANIFEST = REPO_ROOT / "backend-simulators/pim/pim-simulator/Cargo.toml"
|
||||||
|
|
||||||
@@ -78,8 +76,8 @@ def compare_outputs(
|
|||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
parser.add_argument("comparison_dir", nargs="?", type=Path, default=DEFAULT_COMPARISON_DIR)
|
parser.add_argument("comparison_dir", type=Path)
|
||||||
parser.add_argument("model", nargs="?", type=Path, default=DEFAULT_MODEL)
|
parser.add_argument("model", type=Path, help="ONNX model used to generate the comparison artifacts")
|
||||||
parser.add_argument("work_dir", nargs="?", type=Path)
|
parser.add_argument("work_dir", nargs="?", type=Path)
|
||||||
parser.add_argument("common_dir", nargs="?", type=Path, default=DEFAULT_COMMON_DIR)
|
parser.add_argument("common_dir", nargs="?", type=Path, default=DEFAULT_COMMON_DIR)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|||||||
Reference in New Issue
Block a user