Better implementation of MergeComputeNodesPass
Validate Operations / validate-operations (push) Waiting to run

This commit is contained in:
NiccoloN
2026-07-15 10:40:14 +02:00
parent 51fdb830e5
commit c744f388dc
26 changed files with 4892 additions and 4217 deletions
+1
View File
@@ -9,6 +9,7 @@ add_pim_library(OMPimCommon
IR/LoopUtils.cpp
IR/ShapeUtils.cpp
IR/ShapingUtils.cpp
IR/StaticIntSequence.cpp
IR/SubviewUtils.cpp
IR/TensorSliceUtils.cpp
IR/WeightUtils.cpp
+26
View File
@@ -10,6 +10,32 @@ using namespace mlir;
namespace onnx_mlir {
ConstantPool::ConstantPool(Operation *constantAnchor, OpBuilder &builder)
: anchor(constantAnchor), block(getConstantInsertionBlock(constantAnchor)),
builder(builder) {
for (Operation &op : *block)
if (auto constant = dyn_cast<arith::ConstantOp>(&op))
cache.try_emplace(
std::make_pair(constant.getType(), constant.getValue()),
constant.getResult());
}
Value ConstantPool::getIndex(int64_t value) {
return get(builder.getIndexType(), builder.getIndexAttr(value));
}
Value ConstantPool::get(Type type, Attribute value) {
auto key = std::make_pair(type, value);
if (Value existing = cache.lookup(key))
return existing;
OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToStart(block);
Value constant = arith::ConstantOp::create(
builder, anchor->getLoc(), type, cast<TypedAttr>(value)).getResult();
cache.try_emplace(key, constant);
return constant;
}
static std::optional<int64_t> getIndexConstantValue(arith::ConstantOp constantOp) {
if (!constantOp.getType().isIndex())
return std::nullopt;
+16
View File
@@ -6,10 +6,26 @@
#include "mlir/IR/Value.h"
#include "mlir/Transforms/FoldUtils.h"
#include "llvm/ADT/DenseMap.h"
#include <optional>
namespace onnx_mlir {
class ConstantPool {
public:
ConstantPool(mlir::Operation *constantAnchor, mlir::OpBuilder &builder);
mlir::Value getIndex(int64_t value);
mlir::Value get(mlir::Type type, mlir::Attribute value);
private:
mlir::Operation *anchor;
mlir::Block *block;
mlir::OpBuilder &builder;
llvm::DenseMap<std::pair<mlir::Type, mlir::Attribute>, mlir::Value> cache;
};
mlir::Block* getConstantInsertionBlock(mlir::Operation* anchorOp);
mlir::Value
+539
View File
@@ -0,0 +1,539 @@
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/MathExtras.h"
#include <limits>
#include "AffineUtils.hpp"
#include "ConstantUtils.hpp"
#include "StaticIntSequence.hpp"
using namespace mlir;
namespace onnx_mlir {
namespace {
static bool getAffineValue(int64_t base, int64_t step, size_t index,
int64_t &value) {
if (index > static_cast<size_t>(std::numeric_limits<int64_t>::max()))
return false;
int64_t scaled;
return !llvm::MulOverflow(static_cast<int64_t>(index), step, scaled)
&& !llvm::AddOverflow(base, scaled, value);
}
static FailureOr<SmallVector<int64_t>> getI64Values(Operation *op,
StringRef name) {
Attribute attr = op->getAttr(name);
if (!attr)
return op->emitOpError() << "is missing " << name << " metadata",
failure();
if (auto scalar = dyn_cast<IntegerAttr>(attr))
return SmallVector<int64_t> {scalar.getInt()};
if (auto array = dyn_cast<DenseI64ArrayAttr>(attr))
return SmallVector<int64_t>(array.asArrayRef());
auto elements = dyn_cast<DenseIntElementsAttr>(attr);
auto type = elements ? dyn_cast<RankedTensorType>(elements.getType())
: RankedTensorType();
if (!elements || !type || type.getRank() != 1
|| !type.getElementType().isInteger(64))
return op->emitOpError() << "has invalid " << name << " metadata",
failure();
SmallVector<int64_t> values;
values.reserve(elements.getNumElements());
for (APInt value : elements.getValues<APInt>())
values.push_back(value.getSExtValue());
return values;
}
} // namespace
StaticIntSequence StaticIntSequence::uniform(int64_t value, size_t count) {
assert(count != 0 && "empty static integer sequence");
StaticIntSequence result;
result.kind = StaticIntSequenceKind::Uniform;
result.count = count;
result.base = value;
return result;
}
StaticIntSequence StaticIntSequence::affine(int64_t base, int64_t step,
size_t count) {
assert(count != 0 && "empty static integer sequence");
int64_t last;
assert(getAffineValue(base, step, count - 1, last)
&& "overflowing static affine sequence");
if (count == 1 || step == 0)
return uniform(base, count);
StaticIntSequence result;
result.kind = StaticIntSequenceKind::Affine;
result.count = count;
result.base = base;
result.step = step;
return result;
}
StaticIntSequence StaticIntSequence::runLengthEncoded(
ArrayRef<int64_t> runs, size_t count) {
assert(count != 0 && runs.size() % 2 == 0
&& "invalid run-length encoded sequence");
StaticIntSequence result;
result.kind = StaticIntSequenceKind::RunLengthEncoded;
result.count = count;
result.data.assign(runs);
return result;
}
StaticIntSequence StaticIntSequence::fromValues(ArrayRef<int64_t> values) {
assert(!values.empty() && "empty static integer sequence");
if (llvm::all_equal(values))
return uniform(values.front(), values.size());
int64_t step;
bool isAffine = !llvm::SubOverflow(values[1], values[0], step);
for (size_t index = 1; isAffine && index < values.size(); ++index) {
int64_t difference;
isAffine = !llvm::SubOverflow(values[index], values[index - 1],
difference)
&& difference == step;
}
if (isAffine)
return affine(values.front(), step, values.size());
SmallVector<int64_t> runs;
for (int64_t value : values) {
if (!runs.empty() && runs[runs.size() - 2] == value) {
++runs.back();
continue;
}
runs.push_back(value);
runs.push_back(1);
}
if (runs.size() < values.size())
return runLengthEncoded(runs, values.size());
StaticIntSequence result;
result.kind = StaticIntSequenceKind::Dense;
result.count = values.size();
result.data.assign(values);
return result;
}
int64_t StaticIntSequence::valueAt(size_t index) const {
assert(index < count && "static integer sequence index out of range");
if (kind == StaticIntSequenceKind::Uniform)
return base;
if (kind == StaticIntSequenceKind::Affine) {
int64_t value;
bool valid = getAffineValue(base, step, index, value);
assert(valid && "overflowing static affine sequence");
return value;
}
if (kind == StaticIntSequenceKind::Dense)
return data[index];
for (size_t run = 0; run < data.size(); run += 2) {
size_t length = static_cast<size_t>(data[run + 1]);
if (index < length)
return data[run];
index -= length;
}
llvm_unreachable("malformed run-length encoded sequence");
}
std::optional<size_t> StaticIntSequence::find(int64_t value, size_t begin,
size_t length) const {
assert(begin <= count && length <= count - begin
&& "invalid static integer sequence search");
if (length == 0)
return std::nullopt;
size_t end = begin + length;
if (kind == StaticIntSequenceKind::Uniform)
return value == base ? std::optional<size_t>(begin) : std::nullopt;
if (kind == StaticIntSequenceKind::Affine) {
int64_t delta;
if (llvm::SubOverflow(value, base, delta) || delta % step != 0)
return std::nullopt;
int64_t index = delta / step;
return index >= static_cast<int64_t>(begin)
&& index < static_cast<int64_t>(end)
? std::optional<size_t>(index)
: std::nullopt;
}
if (kind == StaticIntSequenceKind::Dense) {
ArrayRef<int64_t> selected = ArrayRef(data).slice(begin, length);
auto found = llvm::find(selected, value);
return found == selected.end()
? std::nullopt
: std::optional<size_t>(begin + (found - selected.begin()));
}
size_t runBegin = 0;
for (size_t run = 0; run < data.size(); run += 2) {
size_t runEnd = runBegin + static_cast<size_t>(data[run + 1]);
if (runEnd > begin && runBegin < end && data[run] == value)
return std::max(begin, runBegin);
if (runBegin >= end)
break;
runBegin = runEnd;
}
return std::nullopt;
}
StaticIntSequence StaticIntSequence::slice(size_t begin, size_t length) const {
assert(length != 0 && begin <= count - length && "invalid sequence slice");
if (kind == StaticIntSequenceKind::Uniform)
return uniform(base, length);
if (kind == StaticIntSequenceKind::Affine)
return affine(valueAt(begin), step, length);
if (kind == StaticIntSequenceKind::Dense)
return fromValues(ArrayRef(data).slice(begin, length));
SmallVector<int64_t> runs;
size_t end = begin + length;
forEachEqualRun([&](int64_t value, size_t runBegin, size_t runCount) {
size_t selectedBegin = std::max(begin, runBegin);
size_t selectedEnd = std::min(end, runBegin + runCount);
if (selectedBegin >= selectedEnd)
return;
if (!runs.empty() && runs[runs.size() - 2] == value)
runs.back() += selectedEnd - selectedBegin;
else {
runs.push_back(value);
runs.push_back(selectedEnd - selectedBegin);
}
});
if (runs.size() == 2)
return uniform(runs.front(), length);
if (runs.size() < length)
return runLengthEncoded(runs, length);
SmallVector<int64_t> values;
for (size_t run = 0; run < runs.size(); run += 2)
values.append(runs[run + 1], runs[run]);
return fromValues(values);
}
StaticIntSequence StaticIntSequence::remap(ArrayRef<unsigned> indices) const {
assert(!indices.empty() && "empty static integer sequence remap");
SmallVector<int64_t> values;
values.reserve(indices.size());
for (unsigned index : indices)
values.push_back(valueAt(index));
return fromValues(values);
}
bool StaticIntSequence::operator==(const StaticIntSequence& other) const {
return kind == other.kind && count == other.count && base == other.base
&& step == other.step && data == other.data;
}
llvm::hash_code StaticIntSequence::hash() const {
return llvm::hash_combine(kind, count, base, step,
llvm::hash_combine_range(data.begin(), data.end()));
}
void StaticIntSequence::forEachEqualRun(
llvm::function_ref<void(int64_t, size_t, size_t)> callback) const {
if (kind == StaticIntSequenceKind::Uniform) {
callback(base, 0, count);
return;
}
if (kind == StaticIntSequenceKind::RunLengthEncoded) {
size_t begin = 0;
for (size_t run = 0; run < data.size(); run += 2) {
size_t runCount = static_cast<size_t>(data[run + 1]);
callback(data[run], begin, runCount);
begin += runCount;
}
return;
}
size_t begin = 0;
while (begin < count) {
int64_t value = valueAt(begin);
size_t end = begin + 1;
while (end < count && valueAt(end) == value)
++end;
callback(value, begin, end - begin);
begin = end;
}
}
void StaticIntSequenceChain::append(const StaticIntSequence &sequence,
size_t begin, size_t length) {
assert(length != 0 && begin <= sequence.size() - length
&& "invalid static integer sequence chain slice");
if (!slices.empty()) {
StaticIntSequenceSlice &last = slices.back();
if (last.sequence == &sequence && last.begin + last.count == begin) {
last.count += length;
count += length;
return;
}
auto affinePart = [](const StaticIntSequenceSlice &slice,
int64_t &base, int64_t &step) {
base = slice.sequence->valueAt(slice.begin);
if (slice.count == 1) {
step = 0;
return true;
}
return !llvm::SubOverflow(slice.sequence->valueAt(slice.begin + 1),
base, step)
&& (slice.sequence->kind == StaticIntSequenceKind::Uniform
|| slice.sequence->kind == StaticIntSequenceKind::Affine);
};
StaticIntSequenceSlice next {&sequence, begin, length};
int64_t leftBase, leftStep, rightBase, rightStep, expected;
if (affinePart(last, leftBase, leftStep)
&& affinePart(next, rightBase, rightStep)
&& (last.count == 1 || length == 1 || leftStep == rightStep)) {
int64_t step = last.count == 1 ? rightStep : leftStep;
if (getAffineValue(leftBase, step, last.count, expected)
&& expected == rightBase) {
owned.push_back(std::make_unique<StaticIntSequence>(
StaticIntSequence::affine(leftBase, step, last.count + length)));
last = {owned.back().get(), 0, last.count + length};
count += length;
return;
}
}
}
slices.push_back({&sequence, begin, length});
count += length;
}
void StaticIntSequenceChain::append(StaticIntSequence sequence) {
size_t length = sequence.size();
owned.push_back(std::make_unique<StaticIntSequence>(std::move(sequence)));
append(*owned.back(), 0, length);
}
int64_t StaticIntSequenceChain::valueAt(size_t index) const {
assert(index < count && "static integer sequence chain index out of range");
for (const StaticIntSequenceSlice &slice : slices) {
if (index < slice.count)
return slice.sequence->valueAt(slice.begin + index);
index -= slice.count;
}
llvm_unreachable("malformed static integer sequence chain");
}
void StaticIntSequenceChain::forEachSegment(llvm::function_ref<void(
const StaticIntSequence &, size_t, size_t)> callback) const {
for (const StaticIntSequenceSlice &slice : slices)
callback(*slice.sequence, slice.begin, slice.count);
}
void StaticIntSequenceChain::forEachEqualRun(
llvm::function_ref<void(int64_t, size_t, size_t)> callback) const {
std::optional<int64_t> pendingValue;
size_t pendingBegin = 0, pendingCount = 0, chainBegin = 0;
auto flush = [&] {
if (pendingValue)
callback(*pendingValue, pendingBegin, pendingCount);
};
for (const StaticIntSequenceSlice &slice : slices) {
size_t sliceEnd = slice.begin + slice.count;
slice.sequence->forEachEqualRun(
[&](int64_t value, size_t runBegin, size_t runCount) {
size_t begin = std::max(slice.begin, runBegin);
size_t end = std::min(sliceEnd, runBegin + runCount);
if (begin >= end)
return;
size_t selectedCount = end - begin;
size_t globalBegin = chainBegin + begin - slice.begin;
if (pendingValue && *pendingValue == value
&& pendingBegin + pendingCount == globalBegin) {
pendingCount += selectedCount;
return;
}
flush();
pendingValue = value;
pendingBegin = globalBegin;
pendingCount = selectedCount;
});
chainBegin += slice.count;
}
flush();
}
StaticIntSequence StaticIntSequenceChain::canonicalize() const {
assert(count != 0 && "empty static integer sequence chain");
int64_t first = valueAt(0);
bool uniform = true;
forEachEqualRun([&](int64_t value, size_t, size_t) {
uniform &= value == first;
});
if (uniform)
return StaticIntSequence::uniform(first, count);
int64_t step = 0, previous = first;
bool affine = true, haveStep = false;
size_t position = 0;
forEachSegment([&](const StaticIntSequence &sequence, size_t begin,
size_t length) {
if (!affine)
return;
for (size_t index = 0; index < length; ++index) {
int64_t value = sequence.valueAt(begin + index);
if (position++ == 0) {
previous = value;
continue;
}
if (!haveStep) {
affine = !llvm::SubOverflow(value, previous, step);
haveStep = true;
} else if (haveStep) {
int64_t difference;
affine = !llvm::SubOverflow(value, previous, difference)
&& difference == step;
}
previous = value;
if (!affine)
break;
}
});
if (affine && haveStep)
return StaticIntSequence::affine(first, step, count);
SmallVector<int64_t> runs;
forEachEqualRun([&](int64_t value, size_t, size_t runCount) {
runs.push_back(value);
runs.push_back(runCount);
});
if (runs.size() < count)
return StaticIntSequence::runLengthEncoded(runs, count);
SmallVector<int64_t> values;
values.reserve(count);
for (size_t run = 0; run < runs.size(); run += 2)
values.append(runs[run + 1], runs[run]);
return StaticIntSequence::fromValues(values);
}
int64_t StaticIntSequenceChainCursor::value() const {
assert(!done() && "static integer sequence chain cursor is done");
const StaticIntSequenceSlice &current = chain.slices[slice];
return current.sequence->valueAt(current.begin + offset);
}
void StaticIntSequenceChainCursor::advance() {
assert(!done() && "static integer sequence chain cursor is done");
if (++offset != chain.slices[slice].count)
return;
offset = 0;
++slice;
}
void setStaticIntSequenceAttr(Operation *op, StringRef name,
const StaticIntSequence &sequence,
size_t logicalCount) {
assert(sequence.size() == logicalCount && logicalCount != 0
&& "invalid static integer metadata count");
SmallVector<int64_t> values;
StringRef encoding;
switch (sequence.kind) {
case StaticIntSequenceKind::Uniform:
encoding = "uniform";
values.push_back(sequence.base);
break;
case StaticIntSequenceKind::Affine:
encoding = "affine";
values = {sequence.base, sequence.step};
break;
case StaticIntSequenceKind::RunLengthEncoded:
encoding = "rle";
values = sequence.data;
break;
case StaticIntSequenceKind::Dense:
encoding = "dense";
values = sequence.data;
break;
}
OpBuilder builder(op);
auto type = RankedTensorType::get(
{static_cast<int64_t>(values.size())}, builder.getI64Type());
op->setAttr(name, DenseIntElementsAttr::get(type, values));
if (sequence.kind != StaticIntSequenceKind::Dense)
op->setAttr((name + "_encoding").str(), builder.getStringAttr(encoding));
}
FailureOr<StaticIntSequence> getStaticIntSequenceAttr(
Operation *op, StringRef name, size_t logicalCount) {
if (logicalCount == 0)
return op->emitOpError() << "has zero logical count for " << name,
failure();
auto values = getI64Values(op, name);
if (failed(values))
return failure();
auto encoding = op->getAttrOfType<StringAttr>((name + "_encoding").str());
if (!encoding) {
if (values->size() != logicalCount)
return op->emitOpError() << "has invalid dense " << name << " count",
failure();
return StaticIntSequence::fromValues(*values);
}
if (encoding.getValue() == "uniform") {
if (values->size() != 1)
return op->emitOpError() << "has invalid uniform " << name,
failure();
return StaticIntSequence::uniform(values->front(), logicalCount);
}
if (encoding.getValue() == "affine") {
int64_t last;
if (values->size() != 2
|| !getAffineValue((*values)[0], (*values)[1], logicalCount - 1, last))
return op->emitOpError() << "has invalid affine " << name,
failure();
return StaticIntSequence::affine((*values)[0], (*values)[1], logicalCount);
}
if (encoding.getValue() == "rle") {
size_t count = 0;
if (values->empty() || values->size() % 2 != 0)
return op->emitOpError() << "has invalid RLE " << name, failure();
for (size_t index = 1; index < values->size(); index += 2) {
if ((*values)[index] <= 0
|| static_cast<uint64_t>((*values)[index]) > logicalCount - count)
return op->emitOpError() << "has invalid RLE " << name, failure();
count += (*values)[index];
}
if (count != logicalCount)
return op->emitOpError() << "has mismatched RLE " << name << " count",
failure();
return StaticIntSequence::runLengthEncoded(*values, count);
}
if (encoding.getValue() == "dense") {
if (values->size() != logicalCount)
return op->emitOpError() << "has invalid dense " << name << " count",
failure();
return StaticIntSequence::fromValues(*values);
}
return op->emitOpError() << "has unknown " << name << " encoding",
failure();
}
Value emitStaticIntLookup(const StaticIntSequence& sequence, Value position,
Operation* constantAnchor,
ConstantPool& constants, OpBuilder& builder,
Location loc) {
if (sequence.getKind() == StaticIntSequenceKind::Uniform)
return constants.getIndex(sequence.valueAt(0));
if (sequence.getKind() == StaticIntSequenceKind::Affine) {
Value scaled = affineMulConst(builder, loc, position,
sequence.valueAt(1) - sequence.valueAt(0),
constantAnchor);
return affineAddConst(builder, loc, scaled, sequence.valueAt(0),
constantAnchor);
}
SmallVector<int64_t> values;
values.reserve(sequence.size());
sequence.forEachEqualRun([&](int64_t value, size_t, size_t count) {
values.append(count, value);
});
auto type = RankedTensorType::get(
{static_cast<int64_t>(values.size())}, builder.getI64Type());
Value table = constants.get(type,
DenseElementsAttr::get(type, ArrayRef<int64_t>(values)));
Value selected = tensor::ExtractOp::create(
builder, loc, table, ValueRange {position});
return arith::IndexCastOp::create(
builder, loc, builder.getIndexType(), selected);
}
} // namespace onnx_mlir
+117
View File
@@ -0,0 +1,117 @@
#pragma once
#include "mlir/IR/Builders.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/FunctionExtras.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
namespace onnx_mlir {
class ConstantPool;
enum class StaticIntSequenceKind {
Uniform,
Affine,
RunLengthEncoded,
Dense
};
class StaticIntSequence {
public:
static StaticIntSequence fromValues(llvm::ArrayRef<int64_t> values);
static StaticIntSequence uniform(int64_t value, size_t count);
static StaticIntSequence affine(int64_t base, int64_t step, size_t count);
size_t size() const { return count; }
int64_t valueAt(size_t index) const;
std::optional<size_t> find(int64_t value, size_t begin, size_t length) const;
StaticIntSequence slice(size_t begin, size_t count) const;
StaticIntSequence remap(llvm::ArrayRef<unsigned> indices) const;
StaticIntSequenceKind getKind() const { return kind; }
bool operator==(const StaticIntSequence& other) const;
llvm::hash_code hash() const;
void forEachEqualRun(
llvm::function_ref<void(int64_t, size_t, size_t)> callback) const;
private:
friend class StaticIntSequenceChain;
friend void setStaticIntSequenceAttr(mlir::Operation *, llvm::StringRef,
const StaticIntSequence &, size_t);
friend mlir::FailureOr<StaticIntSequence>
getStaticIntSequenceAttr(mlir::Operation *, llvm::StringRef, size_t);
static StaticIntSequence runLengthEncoded(
llvm::ArrayRef<int64_t> runs, size_t count);
StaticIntSequenceKind kind = StaticIntSequenceKind::Dense;
size_t count = 0;
int64_t base = 0;
int64_t step = 0;
llvm::SmallVector<int64_t> data;
};
struct StaticIntSequenceSlice {
const StaticIntSequence *sequence = nullptr;
size_t begin = 0;
size_t count = 0;
};
class StaticIntSequenceChain {
public:
void append(const StaticIntSequence &sequence, size_t begin, size_t count);
void append(StaticIntSequence sequence);
size_t size() const { return count; }
int64_t valueAt(size_t index) const;
void forEachSegment(llvm::function_ref<void(
const StaticIntSequence &, size_t, size_t)> callback) const;
void forEachEqualRun(
llvm::function_ref<void(int64_t, size_t, size_t)> callback) const;
StaticIntSequence canonicalize() const;
private:
friend class StaticIntSequenceChainCursor;
llvm::SmallVector<StaticIntSequenceSlice> slices;
llvm::SmallVector<std::unique_ptr<StaticIntSequence>> owned;
size_t count = 0;
};
class StaticIntSequenceChainCursor {
public:
explicit StaticIntSequenceChainCursor(const StaticIntSequenceChain &chain)
: chain(chain) {}
bool done() const { return slice == chain.slices.size(); }
int64_t value() const;
void advance();
private:
const StaticIntSequenceChain &chain;
size_t slice = 0;
size_t offset = 0;
};
void setStaticIntSequenceAttr(mlir::Operation *op, llvm::StringRef name,
const StaticIntSequence &sequence,
size_t logicalCount);
mlir::FailureOr<StaticIntSequence>
getStaticIntSequenceAttr(mlir::Operation *op, llvm::StringRef name,
size_t logicalCount);
mlir::Value emitStaticIntLookup(const StaticIntSequence& sequence,
mlir::Value position,
mlir::Operation* constantAnchor,
ConstantPool& constants,
mlir::OpBuilder& builder,
mlir::Location loc);
} // namespace onnx_mlir
+17
View File
@@ -68,6 +68,23 @@ Value insertStaticSlice(
.getResult();
}
Value extractMixedSliceOrIdentity(RewriterBase &rewriter,
Location loc,
Value source,
RankedTensorType resultType,
const MixedSliceGeometry &geometry) {
return extractStaticSliceOrIdentity(rewriter, loc, source, resultType,
geometry.offsets, geometry.sizes,
geometry.strides);
}
Value insertMixedSlice(OpBuilder &builder, Location loc, Value source,
Value dest, const MixedSliceGeometry &geometry) {
return tensor::InsertSliceOp::create(builder, loc, source, dest,
geometry.offsets, geometry.sizes,
geometry.strides);
}
FailureOr<Value> addLeadingUnitTensorDimension(OpBuilder& builder, Location loc, Value value) {
auto type = dyn_cast<RankedTensorType>(value.getType());
if (!type || !type.hasStaticShape())
+18
View File
@@ -8,6 +8,12 @@
namespace onnx_mlir {
struct MixedSliceGeometry {
llvm::SmallVector<mlir::OpFoldResult> offsets;
llvm::SmallVector<mlir::OpFoldResult> sizes;
llvm::SmallVector<mlir::OpFoldResult> strides;
};
mlir::Value extractAxisSlice(
mlir::PatternRewriter& rewriter, mlir::Location loc, mlir::Value source, int64_t axis, int64_t offset, int64_t size);
@@ -25,6 +31,18 @@ mlir::Value insertStaticSlice(mlir::PatternRewriter& rewriter,
mlir::Value dest,
llvm::ArrayRef<mlir::OpFoldResult> offsets);
mlir::Value extractMixedSliceOrIdentity(mlir::RewriterBase &rewriter,
mlir::Location loc,
mlir::Value source,
mlir::RankedTensorType resultType,
const MixedSliceGeometry &geometry);
mlir::Value insertMixedSlice(mlir::OpBuilder &builder,
mlir::Location loc,
mlir::Value source,
mlir::Value dest,
const MixedSliceGeometry &geometry);
mlir::FailureOr<mlir::Value>
addLeadingUnitTensorDimension(mlir::OpBuilder& builder, mlir::Location loc, mlir::Value value);
+5
View File
@@ -11,7 +11,12 @@ add_pim_library(SpatialOps
Transforms/MergeComputeNodes/Scheduling/ComputeInstanceUtils.cpp
Transforms/MergeComputeNodes/DeferredCommunicationPlanning.cpp
Transforms/MergeComputeNodes/DeferredProjectionAnalysis.cpp
Transforms/MergeComputeNodes/DeferredTransferPlanning.cpp
Transforms/MergeComputeNodes/DeferredCommunicationScheduling.cpp
Transforms/MergeComputeNodes/DeferredBoundaryPlanning.cpp
Transforms/MergeComputeNodes/DeferredCommunicationDeadlock.cpp
Transforms/MergeComputeNodes/DeferredBoundaryRealization.cpp
Transforms/MergeComputeNodes/DeferredResultRealization.cpp
Transforms/MergeComputeNodes/DeferredCommunicationRealization.cpp
Transforms/MergeComputeNodes/MergeComputeNodesPass.cpp
Transforms/MergeComputeNodes/ScheduledComputeMaterialization.cpp
@@ -0,0 +1,658 @@
#include "llvm/ADT/MapVector.h"
#include "DeferredBoundaryPlanning.hpp"
#include "DeferredCommunicationScheduling.hpp"
#include "DeferredProjectionAnalysis.hpp"
#include "DeferredTransferPlanning.hpp"
namespace onnx_mlir::spatial {
using namespace mlir;
namespace {
enum class BoundaryEventKind { Send, Receive };
struct BoundaryEvent {
BoundaryEventKind kind = BoundaryEventKind::Send;
ScheduledTransferSlice slice;
LaneSet activeLanes;
TransferEmissionSignature emission;
};
struct SequenceNode {
unsigned previous = 0;
unsigned instruction = 0;
};
struct IntervalClass {
LaneSet lanes;
unsigned sequence = 0;
};
enum class SemanticKind {
Send,
Availability,
Result
};
struct SemanticKey {
SemanticKind kind = SemanticKind::Send;
TransferEmissionSignature emission;
DeferredExchangePlan* exchange = nullptr;
RequirementCoordinate coordinate;
Type fragmentType;
bool operator==(const SemanticKey& other) const {
if (kind != other.kind)
return false;
if (kind == SemanticKind::Send)
return emission == other.emission;
if (kind == SemanticKind::Result)
return exchange == other.exchange;
return exchange == other.exchange && coordinate == other.coordinate
&& fragmentType == other.fragmentType;
}
};
struct PendingToken {
LaneSet lanes;
unsigned semantic = 0;
std::variant<unsigned, LocalAvailabilityFamily*, DeferredExchangePlan*> value;
};
struct SequenceCursor {
LaneSet lanes;
unsigned position = 0;
};
struct CanonicalAction {
SemanticKey key;
SmallVector<ScheduledTransferSlice> slices;
SmallVector<LocalAvailabilityFamily*> locals;
LaneSet instructionLanes;
LaneSet receiveLanes;
LaneSet localLanes;
DeferredExchangePlan* result = nullptr;
};
struct BoundaryWork {
BoundaryProgram program;
SmallVector<BoundaryEvent> events;
};
static unsigned getBoundaryIndex(SmallVectorImpl<BoundaryWork>& boundaries,
DenseMap<BoundaryKey, unsigned>& indices,
BoundaryKey key) {
if (auto it = indices.find(key); it != indices.end())
return it->second;
unsigned index = boundaries.size();
indices[key] = index;
BoundaryWork work;
work.program.key = key;
boundaries.push_back(std::move(work));
return index;
}
static unsigned getResultBoundary(DeferredExchangePlan& exchange,
const ScheduledCommunicationPlan& schedule) {
std::optional<unsigned> step;
for (const ScheduledTransferSlice& slice : schedule.slices)
if (slice.family->requirement->exchange == &exchange)
step = std::max(step.value_or(0), slice.targetInsertionStep);
return step.value_or(exchange.consumerStep);
}
static size_t hashSemanticKey(const SemanticKey& key) {
if (key.kind == SemanticKind::Send)
return llvm::hash_combine(key.kind,
key.emission.scheduled,
key.emission.payload.getAsOpaquePointer(),
key.emission.fragmentType.getAsOpaquePointer(),
key.emission.hasGraphLane,
key.emission.sourceIsBatch);
if (key.kind == SemanticKind::Result)
return llvm::hash_combine(key.kind, key.exchange);
return llvm::hash_combine(key.kind, key.exchange,
key.coordinate.leafIndex,
key.coordinate.selectedPosition,
key.fragmentType.getAsOpaquePointer());
}
static unsigned internSemantic(const SemanticKey& key,
SmallVectorImpl<SemanticKey>& keys,
DenseMap<size_t, SmallVector<unsigned>>& byHash) {
size_t hash = hashSemanticKey(key);
for (unsigned candidate : byHash.lookup(hash))
if (keys[candidate] == key)
return candidate;
unsigned id = keys.size();
keys.push_back(key);
byHash[hash].push_back(id);
return id;
}
static SemanticKey getEventKey(const BoundaryEvent& event) {
if (event.kind == BoundaryEventKind::Send) {
SemanticKey key;
key.kind = SemanticKind::Send;
key.emission = event.emission;
return key;
}
RequirementFamily& requirement = *event.slice.family->requirement;
SemanticKey key;
key.kind = SemanticKind::Availability;
key.exchange = requirement.exchange;
key.coordinate = requirement.coordinate;
key.fragmentType = requirement.publicationFragmentType;
return key;
}
static SemanticKey getLocalKey(LocalAvailabilityFamily& local) {
RequirementFamily& requirement = *local.requirement;
SemanticKey key;
key.kind = SemanticKind::Availability;
key.exchange = requirement.exchange;
key.coordinate = requirement.coordinate;
key.fragmentType = requirement.publicationFragmentType;
return key;
}
static SmallVector<ScheduledTransferSlice> intersectReceiveSlice(const ScheduledTransferSlice& slice,
const LaneSet& lanes) {
LaneInterval family = slice.family->targetLanes.intervals().front();
unsigned sliceBegin = family.begin + slice.familyOffset;
LaneSet active = LaneSet::range(sliceBegin, sliceBegin + slice.transferCount).intersect(lanes);
SmallVector<ScheduledTransferSlice> result;
for (LaneInterval selected : active.intervals()) {
ScheduledTransferSlice part = slice;
part.familyOffset += selected.begin - sliceBegin;
part.transferCount = selected.end - selected.begin;
result.push_back(part);
}
return result;
}
static void mergeSequenceCursors(SmallVectorImpl<SequenceCursor>& states) {
DenseMap<unsigned, unsigned> byPosition;
SmallVector<SequenceCursor> merged;
for (SequenceCursor& state : states) {
auto [it, inserted] = byPosition.try_emplace(state.position, merged.size());
if (inserted)
merged.push_back(std::move(state));
else
merged[it->second].lanes = merged[it->second].lanes.unite(state.lanes);
}
states = std::move(merged);
}
static LogicalResult appendReplayToken(const PendingToken& token,
const LaneSet& lanes,
CanonicalAction& action,
ArrayRef<BoundaryEvent> events) {
if (auto eventId = std::get_if<unsigned>(&token.value)) {
const BoundaryEvent& event = events[*eventId];
if (event.kind == BoundaryEventKind::Send) {
action.slices.push_back(event.slice);
action.instructionLanes =
action.instructionLanes.unite(event.activeLanes);
}
else {
llvm::append_range(action.slices, intersectReceiveSlice(event.slice, lanes));
action.receiveLanes = action.receiveLanes.unite(lanes);
}
return success();
}
if (auto local = std::get_if<LocalAvailabilityFamily*>(&token.value)) {
if (!llvm::is_contained(action.locals, *local))
action.locals.push_back(*local);
action.localLanes = action.localLanes.unite(lanes);
return success();
}
action.result = *std::get_if<DeferredExchangePlan*>(&token.value);
return success();
}
static FailureOr<SmallVector<CanonicalAction, 0>> collectCanonicalActions(
ArrayRef<unsigned> sequence, ArrayRef<PendingToken> tokens,
ArrayRef<SemanticKey> semantics, ArrayRef<BoundaryEvent> events,
const LaneSet& lanes) {
SmallVector<CanonicalAction, 0> actions;
for (unsigned semantic : sequence) {
CanonicalAction action;
action.key = semantics[semantic];
actions.push_back(std::move(action));
}
SmallVector<SequenceCursor> states {
{lanes, 0}
};
for (const PendingToken& token : tokens) {
SmallVector<SequenceCursor> next;
for (const SequenceCursor& state : states) {
LaneSet intersection = state.lanes.intersect(token.lanes);
LaneSet difference = state.lanes.subtract(token.lanes);
if (!difference.empty())
next.push_back({difference, state.position});
if (intersection.empty())
continue;
if (state.position == sequence.size()) {
next.push_back({intersection, state.position});
continue;
}
if (state.position >= sequence.size() || sequence[state.position] != token.semantic
|| failed(appendReplayToken(token, intersection, actions[state.position], events)))
return failure();
next.push_back({intersection, state.position + 1});
}
mergeSequenceCursors(next);
states = std::move(next);
}
if (llvm::any_of(states, [&](const SequenceCursor& state) { return state.position != sequence.size(); }))
return failure();
return actions;
}
static bool matchesAssembly(ArrayRef<CanonicalAction> actions, size_t begin, SmallVectorImpl<unsigned>& entryOrder) {
if (begin >= actions.size())
return false;
const CanonicalAction& first = actions[begin];
DeferredExchangePlan* exchange = first.key.exchange;
auto& assembly = exchange->program.insertAssembly;
if (first.key.kind != SemanticKind::Availability || !first.locals.empty()
|| !assembly || assembly->entries.empty()
|| begin + assembly->entries.size() > actions.size())
return false;
SmallVector<bool> matched(assembly->entries.size());
for (size_t offset = 0; offset < assembly->entries.size(); ++offset) {
const CanonicalAction& action = actions[begin + offset];
if (action.key.kind != SemanticKind::Availability || !action.locals.empty()
|| action.key.exchange != exchange || action.slices.empty())
return false;
std::optional<unsigned> entry;
for (auto [entryIndex, candidate] : llvm::enumerate(assembly->entries))
if (!matched[entryIndex] && action.key.coordinate == candidate.coordinate) {
entry = entryIndex;
break;
}
if (!entry)
return false;
matched[*entry] = true;
entryOrder.push_back(*entry);
}
if (assembly->entries.size() > 1
&& llvm::any_of(ArrayRef(assembly->entries).drop_front(), [&](const DeferredInsertAssemblyEntryTemplate& entry) {
return entry.sourceTransform != assembly->entries.front().sourceTransform
|| entry.sourceType != assembly->entries.front().sourceType;
}))
return false;
return true;
}
static bool matchesProjectionAssembly(ArrayRef<CanonicalAction> actions,
size_t begin,
const LaneSet& lanes,
unsigned& leafIndex,
SmallVectorImpl<unsigned>& positions) {
if (begin >= actions.size() || lanes.empty())
return false;
const CanonicalAction& first = actions[begin];
DeferredExchangePlan* exchange = first.key.exchange;
if (first.key.kind != SemanticKind::Availability || !first.locals.empty()
|| !exchange || exchange->program.insertAssembly
|| first.key.coordinate.leafIndex >= exchange->program.leaves.size())
return false;
leafIndex = first.key.coordinate.leafIndex;
const DeferredProjectionLeafTemplate& leaf = exchange->program.leaves[leafIndex];
if (leaf.form != DeferredLeafForm::DirectSource)
return false;
unsigned representative = lanes.intervals().front().begin;
unsigned positionCount = 0;
unsigned requirementCount = 0;
Type fragmentType;
for (RequirementFamily& requirement : exchange->requirements) {
if (requirement.coordinate.leafIndex != leafIndex || !requirement.targetLanes.contains(representative))
continue;
++requirementCount;
positionCount = std::max(positionCount, requirement.coordinate.selectedPosition + 1);
if (fragmentType && fragmentType != requirement.publicationFragmentType)
return false;
fragmentType = requirement.publicationFragmentType;
}
auto fragment = dyn_cast<RankedTensorType>(fragmentType);
if (positionCount < 2 || requirementCount != positionCount || !fragment
|| leaf.reconstructedType.getRank() != fragment.getRank() + 1
|| leaf.reconstructedType.getDimSize(0) != positionCount
|| leaf.reconstructedType.getShape().drop_front() != fragment.getShape())
return false;
SmallVector<bool> seen(positionCount);
for (size_t offset = 0; begin + offset < actions.size(); ++offset) {
const CanonicalAction& action = actions[begin + offset];
if (action.key.kind != SemanticKind::Availability || !action.locals.empty()
|| action.key.exchange != exchange
|| action.key.coordinate.leafIndex != leafIndex || action.key.fragmentType != fragmentType)
break;
unsigned position = action.key.coordinate.selectedPosition;
if (action.slices.empty() || position >= positionCount || seen[position])
return false;
seen[position] = true;
positions.push_back(position);
}
if (positions.size() < 2)
return false;
for (RequirementFamily& requirement : exchange->requirements) {
if (requirement.coordinate.leafIndex != leafIndex || !requirement.targetLanes.contains(representative)
|| seen[requirement.coordinate.selectedPosition])
continue;
bool local = llvm::any_of(exchange->local, [&](const LocalAvailabilityFamily& availability) {
return availability.requirement == &requirement && availability.targetLanes.contains(representative);
});
if (!local)
return false;
}
return true;
}
static BoundaryInstructionList materializeInstructions(ArrayRef<CanonicalAction> actions,
const LaneSet& lanes) {
BoundaryInstructionList result;
auto& instructions = result.instructions;
for (size_t index = 0; index < actions.size();) {
const CanonicalAction& action = actions[index];
if (action.key.kind == SemanticKind::Send) {
EmitSendRun run;
run.lanes = action.instructionLanes;
do {
llvm::append_range(run.slices, actions[index].slices);
run.lanes = run.lanes.unite(actions[index].instructionLanes);
++index;
}
while (index < actions.size() && actions[index].key.kind == SemanticKind::Send
&& actions[index].key.emission == action.key.emission);
instructions.push_back(std::move(run));
continue;
}
SmallVector<unsigned> assemblyEntries;
if (matchesAssembly(actions, index, assemblyEntries)) {
EmitReceiveAssemblyRun run;
run.lanes = lanes;
run.assemblyEntries = assemblyEntries;
run.entryOffsets.push_back(0);
for (size_t offset = 0; offset < assemblyEntries.size(); ++offset) {
llvm::append_range(run.slices, actions[index + offset].slices);
run.entryOffsets.push_back(run.slices.size());
}
instructions.push_back(std::move(run));
index += assemblyEntries.size();
continue;
}
unsigned projectionLeaf = 0;
SmallVector<unsigned> projectionPositions;
if (matchesProjectionAssembly(actions, index, lanes, projectionLeaf, projectionPositions)) {
EmitReceiveAssemblyRun run;
run.lanes = lanes;
run.projectionLeaf = projectionLeaf;
run.assemblyEntries = projectionPositions;
run.entryOffsets.push_back(0);
for (size_t offset = 0; offset < projectionPositions.size(); ++offset) {
llvm::append_range(run.slices, actions[index + offset].slices);
run.entryOffsets.push_back(run.slices.size());
}
instructions.push_back(std::move(run));
index += projectionPositions.size();
continue;
}
if (action.key.kind == SemanticKind::Availability) {
ResolveAvailability availability;
availability.exchange = action.key.exchange;
availability.coordinate = action.key.coordinate;
availability.fragmentType = action.key.fragmentType;
if (!action.slices.empty()) {
EmitReceiveRun receive;
receive.slices = action.slices;
receive.entryOffsets = {0, receive.slices.size()};
receive.lanes = action.receiveLanes;
availability.alternatives.push_back(
{receive.lanes, AvailabilitySource(std::move(receive))});
}
if (!action.locals.empty()) {
MaterializeLocalFamily local {action.locals, action.localLanes};
availability.alternatives.push_back(
{local.lanes, AvailabilitySource(std::move(local))});
}
instructions.push_back(std::move(availability));
++index;
continue;
}
instructions.push_back(ProduceDeferredResult {action.result, lanes});
++index;
}
return result;
}
static void addTokenToClasses(const LaneSet& active,
unsigned semantic,
SmallVectorImpl<IntervalClass>& classes,
SmallVectorImpl<SequenceNode>& nodes,
DenseMap<std::pair<unsigned, unsigned>, unsigned>& interned) {
SmallVector<IntervalClass> next;
for (const IntervalClass& current : classes) {
LaneSet intersection = current.lanes.intersect(active);
LaneSet difference = current.lanes.subtract(active);
if (!difference.empty())
next.push_back({difference, current.sequence});
if (intersection.empty())
continue;
auto key = std::make_pair(current.sequence, semantic);
auto [it, inserted] = interned.try_emplace(key, nodes.size());
if (inserted)
nodes.push_back({current.sequence, semantic});
next.push_back({intersection, it->second});
}
classes = std::move(next);
}
struct SequenceClass {
LaneSet lanes;
SmallVector<unsigned> sequence;
};
static SmallVector<SequenceClass>
buildSequenceClasses(unsigned laneCount, ArrayRef<PendingToken> tokens) {
SmallVector<IntervalClass> classes {
{LaneSet::all(laneCount), 0}
};
SmallVector<SequenceNode> nodes {
{0, 0}
};
DenseMap<std::pair<unsigned, unsigned>, unsigned> interned;
for (const PendingToken& token : tokens)
addTokenToClasses(token.lanes, token.semantic, classes, nodes, interned);
SmallVector<SequenceClass> result;
DenseMap<unsigned, unsigned> classBySequence;
SmallVector<unsigned> classSequences;
for (const IntervalClass& interval : classes) {
auto [it, inserted] = classBySequence.try_emplace(
interval.sequence, result.size());
if (inserted) {
result.push_back({interval.lanes, {}});
classSequences.push_back(interval.sequence);
}
else
result[it->second].lanes = result[it->second].lanes.unite(interval.lanes);
}
for (auto [behavior, sequence] : llvm::zip_equal(result, classSequences)) {
for (unsigned node = sequence; node != 0; node = nodes[node].previous)
behavior.sequence.push_back(nodes[node].instruction);
std::reverse(behavior.sequence.begin(), behavior.sequence.end());
}
return result;
}
static SmallVector<DeferredExchangePlan*>
getProducedExchanges(const BoundaryInstructionList& list) {
SmallVector<DeferredExchangePlan*> result;
for (const BoundaryInstruction& instruction : list.instructions)
if (auto produced = std::get_if<ProduceDeferredResult>(&instruction))
result.push_back(produced->exchange);
return result;
}
static LogicalResult buildCanonicalBoundary(
BoundaryProgram& boundary, ArrayRef<BoundaryEvent> events,
ArrayRef<PendingToken> tokens, ArrayRef<SemanticKey> semantics) {
unsigned laneCount = boundary.key.scheduled->cores.size();
SmallVector<SequenceClass> classes =
buildSequenceClasses(laneCount, tokens);
if (classes.empty())
return failure();
size_t prefix = classes.front().sequence.size();
for (const SequenceClass& behavior : ArrayRef(classes).drop_front()) {
prefix = std::min(prefix, behavior.sequence.size());
size_t index = 0;
while (index < prefix
&& behavior.sequence[index] == classes.front().sequence[index])
++index;
prefix = index;
}
auto prefixActions = collectCanonicalActions(
ArrayRef(classes.front().sequence).take_front(prefix), tokens, semantics,
events, LaneSet::all(laneCount));
if (failed(prefixActions))
return failure();
boundary.root = materializeInstructions(*prefixActions, LaneSet::all(laneCount));
if (classes.size() == 1)
return success();
auto dispatch = std::make_unique<LaneDispatch>();
SmallVector<int64_t> classIds(laneCount);
for (auto [classId, behavior] : llvm::enumerate(classes)) {
for (LaneInterval interval : behavior.lanes.intervals())
for (unsigned lane = interval.begin; lane < interval.end; ++lane)
classIds[lane] = classId;
auto actions = collectCanonicalActions(
behavior.sequence, tokens, semantics, events, behavior.lanes);
if (failed(actions))
return failure();
dispatch->branches.push_back(materializeInstructions(
ArrayRef(*actions).drop_front(prefix), behavior.lanes));
auto produced = getProducedExchanges(dispatch->branches.back());
if (classId == 0)
dispatch->producedExchanges = std::move(produced);
else if (produced != dispatch->producedExchanges)
return failure();
}
dispatch->branchByLane = StaticIntSequence::fromValues(classIds);
boundary.root.instructions.push_back(std::move(dispatch));
return success();
}
} // namespace
FailureOr<DeferredBoundaryPlan> buildDeferredBoundaryPlan(DeferredTransferPlan& transfers,
const ScheduledCommunicationPlan& schedule) {
DeferredBoundaryPlan result;
SmallVector<BoundaryWork> boundaries;
DenseMap<BoundaryKey, unsigned> boundaryIndices;
for (const ScheduledTransferSlice& slice : schedule.slices) {
ExternalTransferFamily& family = *slice.family;
unsigned sourceIndex =
getBoundaryIndex(boundaries, boundaryIndices,
{family.sourceScheduled, slice.sourceInsertionStep});
unsigned targetIndex =
getBoundaryIndex(boundaries, boundaryIndices,
{family.targetScheduled, slice.targetInsertionStep});
unsigned sourceLane = family.requirement->producer->scheduledLane;
boundaries[sourceIndex].events.push_back({BoundaryEventKind::Send,
slice,
LaneSet::range(sourceLane, sourceLane + 1),
getTransferEmissionSignature(family)});
LaneInterval familyLanes = family.targetLanes.intervals().front();
unsigned targetBegin = familyLanes.begin + slice.familyOffset;
boundaries[targetIndex].events.push_back({BoundaryEventKind::Receive,
slice,
LaneSet::range(targetBegin, targetBegin + slice.transferCount),
getTransferEmissionSignature(family)});
}
DenseMap<BoundaryKey, SmallVector<LocalAvailabilityFamily*>> locals;
DenseMap<BoundaryKey, SmallVector<DeferredExchangePlan*>> exchanges;
for (const std::unique_ptr<DeferredExchangePlan>& exchange : transfers.exchanges) {
unsigned step = getResultBoundary(*exchange, schedule);
BoundaryKey key {exchange->target, step};
getBoundaryIndex(boundaries, boundaryIndices, key);
for (LocalAvailabilityFamily& local : exchange->local)
locals[key].push_back(&local);
exchanges[key].push_back(exchange.get());
auto resultPlan = buildDeferredResultPlan(*exchange);
if (failed(resultPlan))
return exchange->deferred.emitOpError("cannot evaluate deferred result lane functions"), failure();
result.results.push_back(std::move(*resultPlan));
}
DenseMap<ScheduledInfo*, unsigned> scheduledOrder;
for (auto [index, scheduled] : llvm::enumerate(transfers.scheduled))
scheduledOrder[&scheduled] = index;
llvm::stable_sort(boundaries, [&](const BoundaryWork& lhs, const BoundaryWork& rhs) {
return std::tie(scheduledOrder[lhs.program.key.scheduled], lhs.program.key.insertionStep)
< std::tie(scheduledOrder[rhs.program.key.scheduled], rhs.program.key.insertionStep);
});
for (BoundaryWork& work : boundaries) {
BoundaryProgram& boundary = work.program;
SmallVector<PendingToken> tokens;
SmallVector<SemanticKey> semantics;
DenseMap<size_t, SmallVector<unsigned>> semanticsByHash;
DenseMap<unsigned, SmallVector<LocalAvailabilityFamily*>> localsBySemantic;
SmallPtrSet<LocalAvailabilityFamily*, 8> emittedLocals;
for (LocalAvailabilityFamily* local : locals[boundary.key]) {
unsigned semantic =
internSemantic(getLocalKey(*local), semantics, semanticsByHash);
localsBySemantic[semantic].push_back(local);
}
SmallVector<unsigned> eventSemantics;
DenseMap<unsigned, SmallVector<unsigned>> receivesBySemantic;
for (auto [eventId, event] : llvm::enumerate(work.events)) {
unsigned semantic =
internSemantic(getEventKey(event), semantics, semanticsByHash);
eventSemantics.push_back(semantic);
if (event.kind == BoundaryEventKind::Receive)
receivesBySemantic[semantic].push_back(eventId);
}
llvm::SmallDenseSet<unsigned, 8> emittedReceives;
for (auto [eventId, event] : llvm::enumerate(work.events)) {
unsigned semantic = eventSemantics[eventId];
if (event.kind == BoundaryEventKind::Send) {
tokens.push_back({LaneSet::all(
boundary.key.scheduled->cores.size()),
semantic, static_cast<unsigned>(eventId)});
continue;
}
if (!emittedReceives.insert(semantic).second)
continue;
for (unsigned receive : receivesBySemantic[semantic])
tokens.push_back({work.events[receive].activeLanes, semantic, receive});
for (LocalAvailabilityFamily* local : localsBySemantic[semantic]) {
tokens.push_back({local->targetLanes, semantic, local});
emittedLocals.insert(local);
}
localsBySemantic.erase(semantic);
}
for (LocalAvailabilityFamily* local : locals[boundary.key])
if (!emittedLocals.contains(local)) {
unsigned semantic =
internSemantic(getLocalKey(*local), semantics, semanticsByHash);
tokens.push_back({local->targetLanes, semantic, local});
}
for (DeferredExchangePlan* exchange : exchanges[boundary.key]) {
SemanticKey key;
key.kind = SemanticKind::Result;
key.exchange = exchange;
unsigned semantic = internSemantic(key, semantics, semanticsByHash);
tokens.push_back({LaneSet::all(exchange->targetLaneCount), semantic, exchange});
}
if (failed(buildCanonicalBoundary(boundary, work.events, tokens, semantics)))
return boundary.key.scheduled->op->emitOpError("cannot construct canonical boundary program"), failure();
result.boundaries.push_back(std::move(boundary));
}
return result;
}
} // namespace onnx_mlir::spatial
@@ -0,0 +1,102 @@
#pragma once
#include "DeferredCommunicationScheduling.hpp"
#include "DeferredResultRealization.hpp"
namespace onnx_mlir::spatial {
struct DeferredTransferPlan;
struct BoundaryKey {
ScheduledInfo* scheduled = nullptr;
unsigned insertionStep = 0;
bool operator==(const BoundaryKey& other) const {
return scheduled == other.scheduled
&& insertionStep == other.insertionStep;
}
};
struct EmitSendRun {
llvm::SmallVector<ScheduledTransferSlice> slices;
LaneSet lanes;
};
struct EmitReceiveRun {
llvm::SmallVector<ScheduledTransferSlice> slices;
llvm::SmallVector<size_t> entryOffsets;
LaneSet lanes;
};
struct EmitReceiveAssemblyRun {
llvm::SmallVector<ScheduledTransferSlice> slices;
llvm::SmallVector<size_t> entryOffsets;
llvm::SmallVector<unsigned> assemblyEntries;
std::optional<unsigned> projectionLeaf;
LaneSet lanes;
};
struct MaterializeLocalFamily {
llvm::SmallVector<LocalAvailabilityFamily*> families;
LaneSet lanes;
};
using AvailabilitySource =
std::variant<EmitReceiveRun, MaterializeLocalFamily>;
struct AvailabilityAlternative {
LaneSet lanes;
AvailabilitySource source;
};
struct ResolveAvailability {
DeferredExchangePlan* exchange = nullptr;
RequirementCoordinate coordinate;
mlir::Type fragmentType;
llvm::SmallVector<AvailabilityAlternative, 2> alternatives;
};
struct ProduceDeferredResult {
DeferredExchangePlan* exchange = nullptr;
LaneSet lanes;
};
struct BoundaryInstructionList;
struct LaneDispatch;
using BoundaryInstruction = std::variant<EmitSendRun, ResolveAvailability,
EmitReceiveAssemblyRun, ProduceDeferredResult,
std::unique_ptr<LaneDispatch>>;
struct BoundaryInstructionList {
llvm::SmallVector<BoundaryInstruction, 0> instructions;
};
struct LaneDispatch {
StaticIntSequence branchByLane = StaticIntSequence::uniform(0, 1);
llvm::SmallVector<BoundaryInstructionList> branches;
llvm::SmallVector<DeferredExchangePlan*> producedExchanges;
};
struct BoundaryProgram {
BoundaryKey key;
BoundaryInstructionList root;
};
struct DeferredBoundaryPlan {
llvm::SmallVector<BoundaryProgram, 0> boundaries;
llvm::SmallVector<DeferredResultPlan, 0> results;
};
mlir::FailureOr<DeferredBoundaryPlan> buildDeferredBoundaryPlan(DeferredTransferPlan& transfers,
const ScheduledCommunicationPlan& schedule);
} // namespace onnx_mlir::spatial
namespace llvm {
template <>
struct DenseMapInfo<onnx_mlir::spatial::BoundaryKey> {
static onnx_mlir::spatial::BoundaryKey getEmptyKey() {
return {DenseMapInfo<onnx_mlir::spatial::ScheduledInfo*>::getEmptyKey(), 0};
}
static onnx_mlir::spatial::BoundaryKey getTombstoneKey() {
return {DenseMapInfo<onnx_mlir::spatial::ScheduledInfo*>::getTombstoneKey(), 0};
}
static unsigned getHashValue(const onnx_mlir::spatial::BoundaryKey& key) {
return hash_combine(key.scheduled, key.insertionStep);
}
static bool isEqual(const onnx_mlir::spatial::BoundaryKey& lhs,
const onnx_mlir::spatial::BoundaryKey& rhs) {
return lhs == rhs;
}
};
} // namespace llvm
@@ -0,0 +1,824 @@
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "DeferredBoundaryRealization.hpp"
#include "DeferredResultRealization.hpp"
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
#include "src/Accelerators/PIM/Common/IR/StaticIntSequence.hpp"
#include "src/Accelerators/PIM/Common/IR/TensorSliceUtils.hpp"
namespace onnx_mlir::spatial {
using namespace mlir;
namespace {
struct LogicalTransferMetadataView {
StaticIntSequenceChain channels;
StaticIntSequenceChain parents;
StaticIntSequenceChain parentCounts;
StaticIntSequenceChain sourceCores;
StaticIntSequenceChain targetCores;
StaticIntSequenceChain targetLanes;
StaticIntSequenceChain localOffsets;
size_t size() const { return channels.size(); }
};
static void appendMetadata(const ScheduledTransferSlice& slice,
LogicalTransferMetadataView& metadata) {
ExternalTransferFamily& family = *slice.family;
LaneInterval familyLanes = family.targetLanes.intervals().front();
LaneInterval requirementLanes = family.requirement->targetLanes.intervals().front();
size_t count = slice.transferCount;
size_t familyIndex = slice.familyOffset;
size_t targetLane = familyLanes.begin + familyIndex;
metadata.channels.append(family.channelIds, familyIndex, count);
metadata.parents.append(StaticIntSequence::uniform(
family.requirement->exchange->exchangeId, count));
metadata.parentCounts.append(StaticIntSequence::uniform(
family.requirement->exchange->externalTransferCount, count));
metadata.sourceCores.append(family.sourceCores, familyIndex, count);
metadata.targetCores.append(family.targetCores, familyIndex, count);
metadata.targetLanes.append(
StaticIntSequence::affine(targetLane, 1, count));
if (family.requirement->producerLocalOffsets)
metadata.localOffsets.append(
*family.requirement->producerLocalOffsets,
targetLane - requirementLanes.begin, count);
else
metadata.localOffsets.append(StaticIntSequence::uniform(0, count));
}
static LogicalTransferMetadataView
buildMetadataView(ArrayRef<ScheduledTransferSlice> slices) {
LogicalTransferMetadataView metadata;
for (const ScheduledTransferSlice& slice : slices)
appendMetadata(slice, metadata);
return metadata;
}
static void setLogicalTransferMetadata(
Operation* op, const LogicalTransferMetadataView& metadata) {
size_t logicalCount = metadata.size();
OpBuilder builder(op);
if (logicalCount == 1) {
op->setAttr("raptor.exchange_id",
builder.getI64IntegerAttr(metadata.channels.valueAt(0)));
op->setAttr("raptor.channel_id",
builder.getI64IntegerAttr(metadata.channels.valueAt(0)));
op->setAttr("raptor.parent_exchange_id",
builder.getI64IntegerAttr(metadata.parents.valueAt(0)));
op->setAttr("raptor.parent_transfer_count",
builder.getI64IntegerAttr(metadata.parentCounts.valueAt(0)));
op->setAttr("raptor.source_core",
builder.getI64IntegerAttr(metadata.sourceCores.valueAt(0)));
op->setAttr("raptor.target_core",
builder.getI64IntegerAttr(metadata.targetCores.valueAt(0)));
return;
}
op->setAttr("raptor.batch_transfer_count", builder.getI64IntegerAttr(logicalCount));
setStaticIntSequenceAttr(op, "raptor.batch_channel_ids",
metadata.channels.canonicalize(), logicalCount);
setStaticIntSequenceAttr(op, "raptor.batch_source_cores",
metadata.sourceCores.canonicalize(), logicalCount);
setStaticIntSequenceAttr(op, "raptor.batch_target_cores",
metadata.targetCores.canonicalize(), logicalCount);
setStaticIntSequenceAttr(op, "raptor.batch_parent_exchange_ids",
metadata.parents.canonicalize(), logicalCount);
setStaticIntSequenceAttr(op, "raptor.batch_parent_transfer_counts",
metadata.parentCounts.canonicalize(), logicalCount);
}
static Value
lookup(ArrayRef<int64_t> table, Value position, Operation* anchor, DeferredEmissionContext& context, Location loc) {
return emitStaticIntLookup(
StaticIntSequence::fromValues(table), position, anchor, context.constants, context.rewriter, loc);
}
static OpFoldResult lookupGeometry(
ArrayRef<int64_t> table, Value position, Operation* anchor, DeferredEmissionContext& context, Location loc) {
StaticIntSequence sequence = StaticIntSequence::fromValues(table);
if (sequence.getKind() == StaticIntSequenceKind::Uniform)
return context.rewriter.getIndexAttr(sequence.valueAt(0));
return emitStaticIntLookup(sequence, position, anchor, context.constants, context.rewriter, loc);
}
static FailureOr<Value> materializeSendPayload(const RequirementFamily& requirement,
Value localOffset,
DeferredEmissionContext& context,
Location loc) {
Value payload = requirement.producer->payload;
if (payload.getType() == requirement.publicationFragmentType)
return payload;
auto payloadType = dyn_cast<RankedTensorType>(payload.getType());
auto fragmentType = dyn_cast<RankedTensorType>(requirement.publicationFragmentType);
if (!payloadType || !fragmentType || !requirement.graphLanes || payloadType.getRank() != fragmentType.getRank() + 1)
return failure();
MixedSliceGeometry geometry;
geometry.offsets.assign(payloadType.getRank(), context.rewriter.getIndexAttr(0));
geometry.sizes.push_back(context.rewriter.getIndexAttr(1));
geometry.strides.assign(payloadType.getRank(), context.rewriter.getIndexAttr(1));
geometry.offsets.front() = localOffset;
for (int64_t dimension : fragmentType.getShape())
geometry.sizes.push_back(context.rewriter.getIndexAttr(dimension));
SmallVector<int64_t> unitShape {1};
llvm::append_range(unitShape, fragmentType.getShape());
auto unitType = RankedTensorType::get(unitShape, fragmentType.getElementType());
Value unit = extractMixedSliceOrIdentity(context.rewriter, loc, payload, unitType, geometry);
return removeLeadingUnitTensorDimension(context.rewriter, loc, unit, fragmentType);
}
static LogicalResult
emitSendRun(const EmitSendRun& run, Value lane, unsigned laneCount, DeferredEmissionContext& context) {
SmallVector<LogicalTransferMetadataView, 0> metadataByLane(laneCount);
for (const ScheduledTransferSlice& slice : run.slices) {
unsigned sourceLane = slice.family->requirement->producer->scheduledLane;
appendMetadata(slice, metadataByLane[sourceLane]);
}
LogicalTransferMetadataView logical = buildMetadataView(run.slices);
size_t actionCount = 0;
for (const LogicalTransferMetadataView& laneMetadata : metadataByLane)
actionCount = std::max(actionCount, laneMetadata.size());
SmallVector<int64_t> channelTable(
actionCount * laneCount, logical.channels.valueAt(0));
SmallVector<int64_t> sourceTable(
actionCount * laneCount, logical.sourceCores.valueAt(0));
SmallVector<int64_t> targetTable(
actionCount * laneCount, logical.targetCores.valueAt(0));
SmallVector<int64_t> offsetTable(
actionCount * laneCount, logical.localOffsets.valueAt(0));
SmallVector<int64_t> counts(laneCount);
for (unsigned sourceLane = 0; sourceLane < laneCount; ++sourceLane) {
const LogicalTransferMetadataView& source = metadataByLane[sourceLane];
counts[sourceLane] = source.size();
for (size_t action = 0; action < source.size(); ++action) {
size_t index = action * laneCount + sourceLane;
channelTable[index] = source.channels.valueAt(action);
sourceTable[index] = source.sourceCores.valueAt(action);
targetTable[index] = source.targetCores.valueAt(action);
offsetTable[index] = source.localOffsets.valueAt(action);
}
}
ExternalTransferFamily& firstFamily = *run.slices.front().family;
RequirementFamily& requirement = *firstFamily.requirement;
Operation* anchor = requirement.exchange->deferred;
Location loc = requirement.exchange->deferred.getLoc();
auto emitOne = [&](Value position) -> LogicalResult {
Value localOffset = lookup(offsetTable, position, anchor, context, loc);
auto payload = materializeSendPayload(requirement, localOffset, context, loc);
if (failed(payload))
return failure();
auto send = SpatChannelSendOp::create(context.rewriter,
loc,
lookup(channelTable, position, anchor, context, loc),
lookup(sourceTable, position, anchor, context, loc),
lookup(targetTable, position, anchor, context, loc),
*payload);
setLogicalTransferMetadata(send, logical);
return success();
};
Value runtimeLane = lane ? lane : context.constants.getIndex(0);
if (actionCount == 1)
return emitOne(runtimeLane);
bool uniformCount = true;
std::optional<int64_t> firstCount;
for (LaneInterval interval : run.lanes.intervals())
for (unsigned activeLane = interval.begin; activeLane < interval.end; ++activeLane) {
if (!firstCount)
firstCount = counts[activeLane];
uniformCount &= counts[activeLane] == *firstCount;
}
Value count =
uniformCount ? context.constants.getIndex(*firstCount) : lookup(counts, runtimeLane, anchor, context, loc);
auto loop = buildNormalizedScfFor(context.rewriter,
loc,
context.constants.getIndex(0),
count,
context.constants.getIndex(1),
ValueRange {},
[&](OpBuilder&, Location, Value index, ValueRange, SmallVectorImpl<Value>&) {
Value base = affineMulConst(context.rewriter, loc, index, laneCount, anchor);
Value position = arith::AddIOp::create(context.rewriter, loc, base, runtimeLane);
return emitOne(position);
});
return success(succeeded(loop));
}
static FailureOr<Value>
emitReceiveValue(const EmitReceiveRun& run, Value lane, unsigned laneCount, DeferredEmissionContext& context) {
LogicalTransferMetadataView metadata = buildMetadataView(run.slices);
RequirementFamily& requirement = *run.slices.front().family->requirement;
Operation* anchor = requirement.exchange->deferred;
Location loc = requirement.exchange->deferred.getLoc();
SmallVector<int64_t> channelTable(laneCount, metadata.channels.valueAt(0));
SmallVector<int64_t> sourceTable(laneCount, metadata.sourceCores.valueAt(0));
SmallVector<int64_t> targetTable(laneCount, metadata.targetCores.valueAt(0));
for (size_t index = 0; index < metadata.size(); ++index) {
unsigned targetLane = metadata.targetLanes.valueAt(index);
channelTable[targetLane] = metadata.channels.valueAt(index);
sourceTable[targetLane] = metadata.sourceCores.valueAt(index);
targetTable[targetLane] = metadata.targetCores.valueAt(index);
}
Value position = lane ? lane : context.constants.getIndex(0);
auto receive = SpatChannelReceiveOp::create(context.rewriter,
loc,
requirement.publicationFragmentType,
lookup(channelTable, position, anchor, context, loc),
lookup(sourceTable, position, anchor, context, loc),
lookup(targetTable, position, anchor, context, loc));
setLogicalTransferMetadata(receive, metadata);
return receive.getOutput();
}
static LogicalResult
emitConditionalSendRun(const EmitSendRun& run, Value lane, unsigned laneCount, DeferredEmissionContext& context) {
if (run.lanes.size() == laneCount)
return emitSendRun(run, lane, laneCount, context);
if (!lane)
return failure();
SmallVector<int64_t> active(laneCount);
for (LaneInterval interval : run.lanes.intervals())
for (unsigned index = interval.begin; index < interval.end; ++index)
active[index] = 1;
Location loc = run.slices.front().family->requirement->exchange->deferred.getLoc();
Value selected = lookup(active, lane, run.slices.front().family->sourceScheduled->op, context, loc);
Value condition =
arith::CmpIOp::create(context.rewriter, loc, arith::CmpIPredicate::ne, selected, context.constants.getIndex(0));
auto conditional = scf::IfOp::create(context.rewriter, loc, TypeRange {}, condition, false);
Block& block = conditional.getThenRegion().front();
auto yield = cast<scf::YieldOp>(block.getTerminator());
OpBuilder::InsertionGuard guard(context.rewriter);
context.rewriter.setInsertionPoint(yield);
return emitSendRun(run, lane, laneCount, context);
}
static FailureOr<Value> materializeLocalValue(const MaterializeLocalFamily& local,
Value lane,
unsigned laneCount,
DeferredEmissionContext& context) {
RequirementFamily& reference = *local.families.front()->requirement;
Value fragment = reference.producer->payload;
if (fragment.getType() != reference.publicationFragmentType) {
SmallVector<int64_t> offsets(laneCount);
for (LocalAvailabilityFamily* family : local.families) {
RequirementFamily& requirement = *family->requirement;
LaneInterval requirementLanes = requirement.targetLanes.intervals().front();
for (LaneInterval interval : family->targetLanes.intervals())
for (unsigned targetLane = interval.begin; targetLane < interval.end; ++targetLane)
offsets[targetLane] = requirement.producerLocalOffsets->valueAt(targetLane - requirementLanes.begin);
}
Location loc = reference.exchange->deferred.getLoc();
Value position = lane ? lane : context.constants.getIndex(0);
auto materialized = materializeSendPayload(
reference, lookup(offsets, position, reference.exchange->deferred, context, loc), context, loc);
if (failed(materialized))
return failure();
fragment = *materialized;
}
return fragment;
}
static const DeferredResultPlan* findResultPlan(ArrayRef<DeferredResultPlan> results, DeferredExchangePlan* exchange) {
auto it = llvm::find_if(results, [&](const DeferredResultPlan& result) { return result.exchange == exchange; });
return it == results.end() ? nullptr : &*it;
}
static FailureOr<Value> applyAssemblyTransform(Value source,
const DeferredInsertAssemblyEntryTemplate& entry,
DeferredEmissionContext& context,
Location loc) {
auto sourceType = dyn_cast<RankedTensorType>(source.getType());
auto targetType = entry.sourceType;
if (!sourceType)
return failure();
if (sourceType == targetType)
return source;
if (targetType.getRank() == sourceType.getRank() + 1 && targetType.getDimSize(0) == 1
&& targetType.getShape().drop_front() == sourceType.getShape())
return addLeadingUnitTensorDimension(context.rewriter, loc, source);
if (sourceType.getRank() != targetType.getRank() + 1 || sourceType.getDimSize(0) != 1
|| sourceType.getShape().drop_front() != targetType.getShape())
return failure();
return removeLeadingUnitTensorDimension(context.rewriter, loc, source, targetType);
}
static FailureOr<Value> emitProjectionAssemblyRun(const EmitReceiveAssemblyRun& run,
Value lane,
unsigned laneCount,
DeferredEmissionContext& context) {
if (!run.projectionLeaf || run.slices.empty() || run.entryOffsets.size() != run.assemblyEntries.size() + 1)
return failure();
DeferredExchangePlan* exchange = run.slices.front().family->requirement->exchange;
unsigned leafIndex = *run.projectionLeaf;
if (leafIndex >= exchange->program.leaves.size())
return failure();
const DeferredProjectionLeafTemplate& leaf = exchange->program.leaves[leafIndex];
size_t entryCount = run.assemblyEntries.size();
LogicalTransferMetadataView metadata = buildMetadataView(run.slices);
SmallVector<int64_t> channelTable(
entryCount * laneCount, metadata.channels.valueAt(0));
SmallVector<int64_t> sourceTable(
entryCount * laneCount, metadata.sourceCores.valueAt(0));
SmallVector<int64_t> targetTable(
entryCount * laneCount, metadata.targetCores.valueAt(0));
SmallVector<int64_t> positions(entryCount * laneCount);
for (size_t entry = 0; entry < entryCount; ++entry) {
ArrayRef<ScheduledTransferSlice> slices =
ArrayRef(run.slices).slice(run.entryOffsets[entry], run.entryOffsets[entry + 1] - run.entryOffsets[entry]);
LogicalTransferMetadataView item = buildMetadataView(slices);
for (size_t index = 0; index < item.size(); ++index) {
size_t tableIndex = entry * laneCount + item.targetLanes.valueAt(index);
channelTable[tableIndex] = item.channels.valueAt(index);
sourceTable[tableIndex] = item.sourceCores.valueAt(index);
targetTable[tableIndex] = item.targetCores.valueAt(index);
positions[tableIndex] = run.assemblyEntries[entry];
}
}
Location loc = exchange->deferred.getLoc();
Value initial = tensor::EmptyOp::create(
context.rewriter, loc, leaf.reconstructedType.getShape(), leaf.reconstructedType.getElementType());
auto emitEntry = [&](Value entry, Value current) -> FailureOr<Value> {
Value tableIndex = entry;
if (lane) {
Value base = affineMulConst(context.rewriter, loc, entry, laneCount, exchange->deferred);
tableIndex = arith::AddIOp::create(context.rewriter, loc, base, lane);
}
Type fragmentType = run.slices.front().family->requirement->publicationFragmentType;
auto receive = SpatChannelReceiveOp::create(context.rewriter,
loc,
fragmentType,
lookup(channelTable, tableIndex, exchange->deferred, context, loc),
lookup(sourceTable, tableIndex, exchange->deferred, context, loc),
lookup(targetTable, tableIndex, exchange->deferred, context, loc));
setLogicalTransferMetadata(receive, metadata);
auto source = addLeadingUnitTensorDimension(context.rewriter, loc, receive.getOutput());
if (failed(source))
return failure();
auto sourceType = cast<RankedTensorType>(source->getType());
MixedSliceGeometry geometry;
geometry.offsets.assign(sourceType.getRank(), context.rewriter.getIndexAttr(0));
geometry.offsets.front() = lookupGeometry(positions, tableIndex, exchange->deferred, context, loc);
for (int64_t dimension : sourceType.getShape())
geometry.sizes.push_back(context.rewriter.getIndexAttr(dimension));
geometry.strides.assign(sourceType.getRank(), context.rewriter.getIndexAttr(1));
return insertMixedSlice(context.rewriter, loc, *source, current, geometry);
};
auto loop = buildNormalizedScfFor(
context.rewriter,
loc,
context.constants.getIndex(0),
context.constants.getIndex(entryCount),
context.constants.getIndex(1),
ValueRange {initial},
[&](OpBuilder&, Location, Value entry, ValueRange iterArgs, SmallVectorImpl<Value>& yielded) -> LogicalResult {
auto value = emitEntry(entry, iterArgs.front());
if (failed(value))
return failure();
yielded.push_back(*value);
return success();
});
if (failed(loop))
return failure();
Value result = loop->results.front();
context.projectionAssemblies[{exchange, leafIndex}] = result;
return result;
}
static FailureOr<Value> emitAssemblyRun(const EmitReceiveAssemblyRun& run,
Value lane,
unsigned laneCount,
ArrayRef<DeferredResultPlan> results,
DeferredEmissionContext& context) {
DeferredExchangePlan* exchange = run.slices.front().family->requirement->exchange;
const DeferredResultPlan* resultPlan = findResultPlan(results, exchange);
if (!resultPlan)
return failure();
auto& assembly = exchange->program.insertAssembly;
size_t entryCount = run.entryOffsets.empty() ? 0 : run.entryOffsets.size() - 1;
if (!assembly || assembly->entries.size() != entryCount)
return failure();
if (run.assemblyEntries.size() != entryCount)
return failure();
LogicalTransferMetadataView metadata = buildMetadataView(run.slices);
SmallVector<int64_t> channelTable(
entryCount * laneCount, metadata.channels.valueAt(0));
SmallVector<int64_t> sourceTable(
entryCount * laneCount, metadata.sourceCores.valueAt(0));
SmallVector<int64_t> targetTable(
entryCount * laneCount, metadata.targetCores.valueAt(0));
unsigned rank = assembly->resultType.getRank();
SmallVector<SmallVector<int64_t>> geometryOffsets(rank, SmallVector<int64_t>(entryCount * laneCount));
SmallVector<SmallVector<int64_t>> geometrySizes(rank, SmallVector<int64_t>(entryCount * laneCount, 1));
SmallVector<SmallVector<int64_t>> geometryStrides(rank, SmallVector<int64_t>(entryCount * laneCount, 1));
for (size_t entryIndex = 0; entryIndex < entryCount; ++entryIndex) {
unsigned assemblyEntry = run.assemblyEntries[entryIndex];
if (assemblyEntry >= resultPlan->assemblyGeometry.size())
return failure();
ArrayRef<ScheduledTransferSlice> entrySlices =
ArrayRef(run.slices)
.slice(run.entryOffsets[entryIndex], run.entryOffsets[entryIndex + 1] - run.entryOffsets[entryIndex]);
LogicalTransferMetadataView entry = buildMetadataView(entrySlices);
for (size_t index = 0; index < entry.size(); ++index) {
size_t tableIndex =
entryIndex * laneCount + entry.targetLanes.valueAt(index);
channelTable[tableIndex] = entry.channels.valueAt(index);
sourceTable[tableIndex] = entry.sourceCores.valueAt(index);
targetTable[tableIndex] = entry.targetCores.valueAt(index);
unsigned targetLane = entry.targetLanes.valueAt(index);
for (unsigned dimension = 0; dimension < rank; ++dimension) {
geometryOffsets[dimension][tableIndex] =
resultPlan->assemblyGeometry[assemblyEntry].offsets[dimension].valueAt(targetLane);
geometrySizes[dimension][tableIndex] =
resultPlan->assemblyGeometry[assemblyEntry].sizes[dimension].valueAt(targetLane);
geometryStrides[dimension][tableIndex] =
resultPlan->assemblyGeometry[assemblyEntry].strides[dimension].valueAt(targetLane);
}
}
}
Location loc = exchange->deferred.getLoc();
Operation* initial = context.rewriter.clone(*assembly->initialValue);
Value assembled = initial->getResult(0);
auto emitEntry = [&](Value entryIndex, unsigned staticEntry, Value current) -> FailureOr<Value> {
Value tableIndex = entryIndex;
if (lane) {
Value base = affineMulConst(context.rewriter, loc, entryIndex, laneCount, exchange->deferred);
AffineExpr d0 = context.rewriter.getAffineDimExpr(0);
AffineExpr d1 = context.rewriter.getAffineDimExpr(1);
tableIndex = createOrFoldAffineApply(context.rewriter, loc, d0 + d1, ValueRange {base, lane}, exchange->deferred);
}
Type fragmentType = run.slices[run.entryOffsets[staticEntry]].family->requirement->publicationFragmentType;
auto receive = SpatChannelReceiveOp::create(context.rewriter,
loc,
fragmentType,
lookup(channelTable, tableIndex, exchange->deferred, context, loc),
lookup(sourceTable, tableIndex, exchange->deferred, context, loc),
lookup(targetTable, tableIndex, exchange->deferred, context, loc));
setLogicalTransferMetadata(receive, metadata);
auto source =
applyAssemblyTransform(receive.getOutput(), assembly->entries[run.assemblyEntries[staticEntry]], context, loc);
if (failed(source))
return failure();
MixedSliceGeometry geometry;
for (unsigned dimension = 0; dimension < rank; ++dimension) {
geometry.offsets.push_back(
lookupGeometry(geometryOffsets[dimension], tableIndex, exchange->deferred, context, loc));
geometry.sizes.push_back(lookupGeometry(geometrySizes[dimension], tableIndex, exchange->deferred, context, loc));
geometry.strides.push_back(
lookupGeometry(geometryStrides[dimension], tableIndex, exchange->deferred, context, loc));
}
return insertMixedSlice(context.rewriter, loc, *source, current, geometry);
};
if (entryCount == 1) {
auto value = emitEntry(context.constants.getIndex(0), 0, assembled);
if (failed(value))
return failure();
context.assemblies[exchange] = *value;
return *value;
}
const DeferredInsertAssemblyEntryTemplate& firstEntry = assembly->entries[run.assemblyEntries.front()];
bool sameTransform = llvm::all_of(ArrayRef(run.assemblyEntries).drop_front(), [&](unsigned entryIndex) {
const DeferredInsertAssemblyEntryTemplate& entry = assembly->entries[entryIndex];
return entry.sourceTransform == firstEntry.sourceTransform && entry.sourceType == firstEntry.sourceType;
});
if (!sameTransform)
return failure();
auto loop = buildNormalizedScfFor(
context.rewriter,
loc,
context.constants.getIndex(0),
context.constants.getIndex(entryCount),
context.constants.getIndex(1),
ValueRange {assembled},
[&](OpBuilder&, Location, Value index, ValueRange iterArgs, SmallVectorImpl<Value>& yielded) -> LogicalResult {
auto value = emitEntry(index, 0, iterArgs.front());
if (failed(value))
return failure();
yielded.push_back(*value);
return success();
});
if (failed(loop))
return failure();
context.assemblies[exchange] = loop->results.front();
return loop->results.front();
}
static RequirementFamily* getAvailabilityRequirement(const AvailabilityAlternative& alternative) {
return std::visit(
[](const auto& value) -> RequirementFamily* {
using Alternative = std::decay_t<decltype(value)>;
if constexpr (std::is_same_v<Alternative, EmitReceiveRun>)
return value.slices.front().family->requirement;
else
return value.families.front()->requirement;
},
alternative.source);
}
static FailureOr<Value> emitAvailabilityAlternative(const AvailabilityAlternative& alternative,
Value lane,
unsigned laneCount,
DeferredEmissionContext& context) {
if (auto receive = std::get_if<EmitReceiveRun>(&alternative.source))
return emitReceiveValue(*receive, lane, laneCount, context);
return materializeLocalValue(
std::get<MaterializeLocalFamily>(alternative.source), lane, laneCount,
context);
}
static void recordAvailability(const ResolveAvailability& availability,
Value value,
DeferredEmissionContext& context) {
for (const AvailabilityAlternative& alternative : availability.alternatives) {
if (auto receive = std::get_if<EmitReceiveRun>(&alternative.source)) {
for (const ScheduledTransferSlice& slice : receive->slices)
context.receives[slice.family->requirement] = value;
continue;
}
for (LocalAvailabilityFamily* family :
std::get<MaterializeLocalFamily>(alternative.source).families)
context.receives[family->requirement] = value;
}
}
static FailureOr<SmallVector<Value>> emitSelection(
Value branch, ArrayRef<Type> resultTypes, size_t branchCount, Location loc,
DeferredEmissionContext& context,
llvm::function_ref<LogicalResult(Region&, size_t)> emitRegion) {
SmallVector<Value> results;
if (branchCount == 2) {
Value condition = arith::CmpIOp::create(context.rewriter, loc,
arith::CmpIPredicate::eq, branch, context.constants.getIndex(0));
auto selection = scf::IfOp::create(
context.rewriter, loc, resultTypes, condition, true);
if (failed(emitRegion(selection.getThenRegion(), 0))
|| failed(emitRegion(selection.getElseRegion(), 1)))
return failure();
llvm::append_range(results, selection.getResults());
return results;
}
SmallVector<int64_t> cases;
for (size_t index = 0; index + 1 < branchCount; ++index)
cases.push_back(index);
auto selection = scf::IndexSwitchOp::create(
context.rewriter, loc, resultTypes, branch, cases, cases.size());
for (auto [index, region] : llvm::enumerate(selection.getCaseRegions()))
if (failed(emitRegion(region, index)))
return failure();
if (failed(emitRegion(selection.getDefaultRegion(), branchCount - 1)))
return failure();
llvm::append_range(results, selection.getResults());
return results;
}
static LogicalResult emitAvailability(const ResolveAvailability& availability,
Value lane,
unsigned laneCount,
DeferredEmissionContext& context) {
if (availability.alternatives.empty())
return failure();
if (availability.alternatives.size() == 1) {
auto value = emitAvailabilityAlternative(availability.alternatives.front(), lane, laneCount, context);
if (failed(value))
return failure();
recordAvailability(availability, *value, context);
return success();
}
if (!lane)
return failure();
const AvailabilityAlternative& first = availability.alternatives.front();
RequirementFamily* requirement = getAvailabilityRequirement(first);
Location loc = requirement->exchange->deferred.getLoc();
SmallVector<Type> resultTypes {requirement->publicationFragmentType};
auto emitRegion = [&](Region& region, const AvailabilityAlternative& alternative) {
OpBuilder::InsertionGuard guard(context.rewriter);
Block& block = region.empty() ? *context.rewriter.createBlock(&region)
: region.front();
auto yield = block.empty() ? scf::YieldOp() : dyn_cast<scf::YieldOp>(&block.back());
if (yield)
context.rewriter.setInsertionPoint(yield);
else
context.rewriter.setInsertionPointToEnd(&block);
auto value = emitAvailabilityAlternative(alternative, lane, laneCount, context);
if (failed(value))
return failure();
if (yield)
yield->setOperands(ValueRange {*value});
else
scf::YieldOp::create(context.rewriter, loc, ValueRange {*value});
return success();
};
SmallVector<int64_t> branchByLane(laneCount);
SmallVector<bool> assigned(laneCount);
for (auto [branch, alternative] : llvm::enumerate(availability.alternatives))
for (LaneInterval interval : alternative.lanes.intervals())
for (unsigned activeLane = interval.begin; activeLane < interval.end;
++activeLane) {
if (assigned[activeLane])
return failure();
assigned[activeLane] = true;
branchByLane[activeLane] = branch;
}
Value branch = emitStaticIntLookup(StaticIntSequence::fromValues(branchByLane),
lane,
requirement->exchange->deferred,
context.constants,
context.rewriter,
loc);
auto selected = emitSelection(branch, resultTypes,
availability.alternatives.size(), loc, context,
[&](Region& region, size_t index) {
return emitRegion(region, availability.alternatives[index]);
});
if (failed(selected))
return failure();
Value value = selected->front();
recordAvailability(availability, value, context);
return success();
}
static FailureOr<SmallVector<Value>> emitInstructions(
const BoundaryInstructionList& list, Value lane, unsigned laneCount,
ArrayRef<DeferredResultPlan> results, DeferredEmissionContext& context);
static FailureOr<SmallVector<Value>> emitDispatch(
const LaneDispatch& dispatch, Value lane, unsigned laneCount,
ArrayRef<DeferredResultPlan> results, DeferredEmissionContext& context) {
if (dispatch.branches.empty())
return failure();
if (dispatch.branches.size() == 1)
return emitInstructions(
dispatch.branches.front(), lane, laneCount, results, context);
if (!lane)
return failure();
SmallVector<Type> resultTypes;
for (DeferredExchangePlan* exchange : dispatch.producedExchanges)
resultTypes.push_back(exchange->deferred.getOutput().getType());
Location loc = dispatch.producedExchanges.empty()
? lane.getLoc()
: dispatch.producedExchanges.front()->deferred.getLoc();
Value branch = emitStaticIntLookup(dispatch.branchByLane,
lane,
dispatch.producedExchanges.empty()
? lane.getDefiningOp()
: dispatch.producedExchanges.front()->deferred,
context.constants,
context.rewriter,
loc);
auto receives = context.receives;
auto assemblies = context.assemblies;
auto projectionAssemblies = context.projectionAssemblies;
auto emitRegion = [&](Region& region,
const BoundaryInstructionList& instructions) {
context.receives = receives;
context.assemblies = assemblies;
context.projectionAssemblies = projectionAssemblies;
OpBuilder::InsertionGuard guard(context.rewriter);
Block& block = region.empty() ? *context.rewriter.createBlock(&region)
: region.front();
auto yield = block.empty() ? scf::YieldOp()
: dyn_cast<scf::YieldOp>(&block.back());
if (yield)
context.rewriter.setInsertionPoint(yield);
else
context.rewriter.setInsertionPointToEnd(&block);
auto values = emitInstructions(
instructions, lane, laneCount, results, context);
if (failed(values))
return failure();
if (yield)
yield->setOperands(*values);
else
scf::YieldOp::create(context.rewriter, loc, *values);
return success();
};
auto produced = emitSelection(branch, resultTypes, dispatch.branches.size(),
loc, context, [&](Region& region, size_t index) {
return emitRegion(region, dispatch.branches[index]);
});
if (failed(produced))
return failure();
context.receives = std::move(receives);
context.assemblies = std::move(assemblies);
context.projectionAssemblies = std::move(projectionAssemblies);
return *produced;
}
static FailureOr<SmallVector<Value>> emitInstructions(
const BoundaryInstructionList& list, Value lane, unsigned laneCount,
ArrayRef<DeferredResultPlan> results, DeferredEmissionContext& context) {
SmallVector<Value> produced;
for (const BoundaryInstruction& instruction : list.instructions) {
if (auto send = std::get_if<EmitSendRun>(&instruction)) {
if (failed(emitConditionalSendRun(*send, lane, laneCount, context)))
return failure();
continue;
}
if (auto availability = std::get_if<ResolveAvailability>(&instruction)) {
if (failed(emitAvailability(*availability, lane, laneCount, context)))
return failure();
continue;
}
if (auto assembly = std::get_if<EmitReceiveAssemblyRun>(&instruction)) {
auto value = assembly->projectionLeaf
? emitProjectionAssemblyRun(
*assembly, lane, laneCount, context)
: emitAssemblyRun(
*assembly, lane, laneCount, results, context);
if (failed(value))
return failure();
continue;
}
if (auto result = std::get_if<ProduceDeferredResult>(&instruction)) {
const DeferredResultPlan* plan = findResultPlan(results, result->exchange);
if (!plan)
return failure();
auto value = realizeDeferredResult(*plan, result->lanes, lane, context);
if (failed(value))
return failure();
produced.push_back(*value);
continue;
}
auto values = emitDispatch(
**std::get_if<std::unique_ptr<LaneDispatch>>(&instruction), lane,
laneCount, results, context);
if (failed(values))
return failure();
llvm::append_range(produced, *values);
}
return produced;
}
static SmallVector<DeferredExchangePlan*> getProducedExchanges(
const BoundaryInstructionList& list) {
SmallVector<DeferredExchangePlan*> exchanges;
for (const BoundaryInstruction& instruction : list.instructions) {
if (auto result = std::get_if<ProduceDeferredResult>(&instruction))
exchanges.push_back(result->exchange);
else if (auto dispatch =
std::get_if<std::unique_ptr<LaneDispatch>>(&instruction))
llvm::append_range(exchanges, (*dispatch)->producedExchanges);
}
return exchanges;
}
static void setInsertionAtBoundary(IRRewriter& rewriter, const BoundaryKey& key) {
ScheduledInfo& scheduled = *key.scheduled;
if (key.insertionStep < scheduled.stepAnchors.size() && scheduled.stepAnchors[key.insertionStep]->getBlock()) {
rewriter.setInsertionPoint(scheduled.stepAnchors[key.insertionStep]);
return;
}
rewriter.setInsertionPoint(scheduled.blocks.front()->getTerminator());
}
static LogicalResult
replaceResults(ArrayRef<DeferredExchangePlan*> exchanges, ValueRange replacements, DeferredEraseSet& erase) {
if (exchanges.size() != replacements.size())
return failure();
for (auto [exchange, replacement] : llvm::zip_equal(exchanges, replacements)) {
exchange->deferred.getOutput().replaceAllUsesWith(replacement);
erase.insert(exchange->deferred);
}
return success();
}
static LogicalResult emitBoundary(const BoundaryProgram& boundary,
ArrayRef<DeferredResultPlan> results,
DeferredEmissionContext& context,
DeferredEraseSet& erase) {
setInsertionAtBoundary(context.rewriter, boundary.key);
unsigned laneCount = boundary.key.scheduled->cores.size();
Value lane;
if (auto batch = dyn_cast<SpatScheduledComputeBatch>(boundary.key.scheduled->op))
lane = *batch.getLaneArgument();
SmallVector<DeferredExchangePlan*> exchanges =
getProducedExchanges(boundary.root);
auto values = emitInstructions(
boundary.root, lane, laneCount, results, context);
return failed(values) ? failure()
: replaceResults(exchanges, *values, erase);
}
} // namespace
LogicalResult realizeDeferredBoundaries(ArrayRef<BoundaryProgram> boundaries,
ArrayRef<DeferredResultPlan> results,
DeferredEmissionContext& context,
DeferredEraseSet& erase) {
ScheduledInfo* scheduled = nullptr;
for (const BoundaryProgram& boundary : boundaries) {
if (scheduled != boundary.key.scheduled) {
context.receives.clear();
context.assemblies.clear();
context.projectionAssemblies.clear();
scheduled = boundary.key.scheduled;
}
if (failed(emitBoundary(boundary, results, context, erase)))
return boundary.key.scheduled->op->emitOpError("phase 2 failed to realize a communication boundary");
}
return success();
}
} // namespace onnx_mlir::spatial
@@ -0,0 +1,30 @@
#pragma once
#include "mlir/IR/PatternMatch.h"
#include "DeferredBoundaryPlanning.hpp"
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
namespace onnx_mlir::spatial {
struct DeferredEmissionContext {
DeferredEmissionContext(mlir::IRRewriter& rewriter,
ConstantPool& constants)
: rewriter(rewriter), constants(constants) {}
mlir::IRRewriter& rewriter;
ConstantPool& constants;
llvm::DenseMap<RequirementFamily*, mlir::Value> receives;
llvm::DenseMap<DeferredExchangePlan*, mlir::Value> assemblies;
llvm::DenseMap<std::pair<DeferredExchangePlan*, unsigned>, mlir::Value>
projectionAssemblies;
};
using DeferredEraseSet = llvm::SetVector<mlir::Operation*>;
mlir::LogicalResult realizeDeferredBoundaries(mlir::ArrayRef<BoundaryProgram> boundaries,
mlir::ArrayRef<DeferredResultPlan> results,
DeferredEmissionContext& context,
DeferredEraseSet& erase);
} // namespace onnx_mlir::spatial
@@ -1,190 +1,255 @@
#include "DeferredCommunicationDeadlock.hpp"
#include "src/Accelerators/PIM/Common/IR/StaticIntSequence.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include <map>
namespace onnx_mlir::spatial {
using namespace mlir;
namespace {
enum class EventKind { Compute, Send, Receive };
struct Event {
EventKind kind = EventKind::Compute;
uint64_t exchangeId = 0;
uint64_t channel = 0;
};
static LogicalResult simulate(Operation *anchor,
ArrayRef<SmallVector<Event>> streams,
StringRef phase) {
SmallVector<size_t> cursor(streams.size());
DenseMap<uint64_t, unsigned> headSends;
DenseMap<uint64_t, unsigned> headReceives;
SmallVector<unsigned> readyComputes;
SmallVector<uint64_t> readyExchanges;
size_t computeCursor = 0;
size_t exchangeCursor = 0;
unsigned finishedStreams = 0;
struct PlannedStreamCursor {
unsigned step = 0;
size_t slice = 0;
size_t offset = 0;
};
static std::optional<Event> getPlannedHead(
unsigned stream, PlannedStreamCursor &cursor, unsigned stepCount,
const ScheduledCommunicationPlan &plan) {
while (cursor.slice < plan.slices.size()) {
const ScheduledTransferSlice &slice = plan.slices[cursor.slice];
ExternalTransferFamily &family = *slice.family;
size_t begin = slice.familyOffset + cursor.offset;
size_t length = slice.transferCount - cursor.offset;
auto source = family.sourceStreams.find(stream, begin, length);
auto target = family.targetStreams.find(stream, begin, length);
std::optional<size_t> index = source;
EventKind kind = EventKind::Send;
unsigned insertionStep = slice.sourceInsertionStep;
if (target && (!index || *target < *index)) {
index = *target;
kind = EventKind::Receive;
insertionStep = slice.targetInsertionStep;
}
if (!index) {
++cursor.slice;
cursor.offset = 0;
continue;
}
cursor.offset = *index - slice.familyOffset;
if (cursor.step < insertionStep)
return Event {EventKind::Compute, 0};
return Event {kind, static_cast<uint64_t>(
family.channelIds.valueAt(*index))};
}
if (cursor.step < stepCount)
return Event {EventKind::Compute, 0};
return std::nullopt;
}
static LogicalResult simulatePlanned(
Operation *anchor, ArrayRef<unsigned> stepCounts,
const ScheduledCommunicationPlan &plan) {
SmallVector<PlannedStreamCursor> cursors(stepCounts.size());
DenseMap<uint64_t, unsigned> sends, receives;
SmallVector<unsigned> readyComputes;
SmallVector<uint64_t> readyChannels;
size_t computeCursor = 0, channelCursor = 0;
unsigned finished = 0;
auto registerHead = [&](unsigned stream) {
if (cursor[stream] == streams[stream].size()) {
++finishedStreams;
auto head = getPlannedHead(
stream, cursors[stream], stepCounts[stream], plan);
if (!head) {
++finished;
return;
}
const Event &event = streams[stream][cursor[stream]];
if (event.kind == EventKind::Compute) {
if (head->kind == EventKind::Compute) {
readyComputes.push_back(stream);
return;
}
DenseMap<uint64_t, unsigned> &heads =
event.kind == EventKind::Send ? headSends : headReceives;
DenseMap<uint64_t, unsigned> &peers =
event.kind == EventKind::Send ? headReceives : headSends;
heads[event.exchangeId] = stream;
if (peers.contains(event.exchangeId))
readyExchanges.push_back(event.exchangeId);
auto &own = head->kind == EventKind::Send ? sends : receives;
auto &peer = head->kind == EventKind::Send ? receives : sends;
own[head->channel] = stream;
if (peer.contains(head->channel))
readyChannels.push_back(head->channel);
};
for (unsigned stream = 0; stream < streams.size(); ++stream)
for (unsigned stream = 0; stream < stepCounts.size(); ++stream)
registerHead(stream);
while (computeCursor != readyComputes.size()
|| exchangeCursor != readyExchanges.size()) {
|| channelCursor != readyChannels.size()) {
if (computeCursor != readyComputes.size()) {
unsigned stream = readyComputes[computeCursor++];
++cursor[stream];
++cursors[stream].step;
registerHead(stream);
continue;
}
uint64_t exchange = readyExchanges[exchangeCursor++];
auto send = headSends.find(exchange);
auto receive = headReceives.find(exchange);
if (send == headSends.end() || receive == headReceives.end())
uint64_t channel = readyChannels[channelCursor++];
auto send = sends.find(channel);
auto receive = receives.find(channel);
if (send == sends.end() || receive == receives.end())
continue;
unsigned source = send->second;
unsigned target = receive->second;
headSends.erase(send);
headReceives.erase(receive);
++cursor[source];
++cursor[target];
sends.erase(send);
receives.erase(receive);
++cursors[source].offset;
++cursors[target].offset;
registerHead(source);
registerHead(target);
}
if (finishedStreams == streams.size())
if (finished == stepCounts.size())
return success();
InFlightDiagnostic diagnostic = anchor->emitError()
<< phase << " communication rendezvous simulation made no progress";
InFlightDiagnostic diagnostic = anchor->emitError(
"planned communication rendezvous simulation made no progress");
unsigned reported = 0;
for (unsigned stream = 0; stream < streams.size() && reported < 8; ++stream) {
if (cursor[stream] == streams[stream].size())
for (unsigned stream = 0;
stream < stepCounts.size() && reported < 8; ++stream) {
auto head = getPlannedHead(
stream, cursors[stream], stepCounts[stream], plan);
if (!head)
continue;
const Event &event = streams[stream][cursor[stream]];
diagnostic << (reported == 0 ? "; blocked " : ", ") << "stream " << stream
<< " at exchange " << event.exchangeId;
++reported;
diagnostic << (reported++ == 0 ? "; blocked " : ", ")
<< "stream " << stream << " at channel " << head->channel;
}
return failure();
}
static std::optional<int64_t> getI64Attr(Operation *op, StringRef name) {
if (auto attr = op->getAttrOfType<IntegerAttr>(name))
return attr.getInt();
return std::nullopt;
}
static LogicalResult getI64ArrayAttr(
Operation *op, StringRef name,
std::optional<SmallVector<int64_t>> &values) {
Attribute attr = op->getAttr(name);
if (!attr)
return success();
if (auto array = dyn_cast<DenseI64ArrayAttr>(attr)) {
values.emplace(array.asArrayRef());
return success();
}
auto elements = dyn_cast<DenseIntElementsAttr>(attr);
auto type = elements ? dyn_cast<RankedTensorType>(elements.getType())
: RankedTensorType();
if (!elements || !type || type.getRank() != 1
|| !type.getElementType().isInteger(64))
return op->emitOpError() << "has invalid " << name << " metadata";
values.emplace();
values->reserve(elements.getNumElements());
for (const APInt &value : elements.getValues<APInt>())
values->push_back(value.getSExtValue());
return success();
}
struct RealizedLogicalTransfer {
int64_t channelId = -1;
int64_t parentExchangeId = -1;
int64_t parentTransferCount = 0;
int64_t sourceCore = -1;
int64_t targetCore = -1;
struct RealizedOperation {
Operation *op = nullptr;
bool send = false;
StaticIntSequence channels;
StaticIntSequence parents;
StaticIntSequence counts;
StaticIntSequence sources;
StaticIntSequence targets;
};
static LogicalResult forEachRealizedLogicalTransfer(
Operation *op,
function_ref<LogicalResult(const RealizedLogicalTransfer &)> callback) {
auto scalarChannel = getI64Attr(op, "raptor.channel_id");
std::optional<SmallVector<int64_t>> batchChannels;
if (failed(getI64ArrayAttr(
op, "raptor.batch_channel_ids", batchChannels)))
return failure();
if (scalarChannel && batchChannels)
return op->emitOpError(
"mixes scalar and compact logical transfer metadata");
if (scalarChannel) {
auto exchange = getI64Attr(op, "raptor.exchange_id");
auto parent = getI64Attr(op, "raptor.parent_exchange_id");
auto count = getI64Attr(op, "raptor.parent_transfer_count");
auto source = getI64Attr(op, "raptor.source_core");
auto target = getI64Attr(op, "raptor.target_core");
if (!exchange || !parent || !count || !source || !target)
return op->emitOpError(
"is missing scalar logical transfer metadata");
RealizedLogicalTransfer transfer {
*scalarChannel, *parent, *count, *source, *target};
if (*exchange != transfer.channelId || transfer.channelId < 0
|| transfer.parentExchangeId < 0 || transfer.parentTransferCount <= 0
|| transfer.sourceCore < 0 || transfer.targetCore < 0)
return op->emitOpError("has invalid scalar logical transfer metadata");
return callback(transfer);
static FailureOr<size_t> getBatchTransferCount(Operation *op) {
if (auto count = op->getAttrOfType<IntegerAttr>(
"raptor.batch_transfer_count")) {
if (count.getInt() > 0)
return count.getInt();
return op->emitOpError("has invalid compact transfer count"), failure();
}
if (op->hasAttr("raptor.batch_channel_ids_encoding"))
return op->emitOpError("is missing compact transfer count"), failure();
Attribute channels = op->getAttr("raptor.batch_channel_ids");
if (auto array = dyn_cast_or_null<DenseI64ArrayAttr>(channels))
return array.empty()
? FailureOr<size_t>(failure())
: FailureOr<size_t>(array.size());
if (auto elements = dyn_cast_or_null<DenseIntElementsAttr>(channels);
elements && elements.getNumElements() > 0)
return elements.getNumElements();
return op->emitOpError("has invalid legacy compact transfer metadata"),
failure();
}
std::optional<SmallVector<int64_t>> sources, targets, parents, counts;
if (failed(getI64ArrayAttr(op, "raptor.batch_source_cores", sources))
|| failed(getI64ArrayAttr(op, "raptor.batch_target_cores", targets))
|| failed(getI64ArrayAttr(
op, "raptor.batch_parent_exchange_ids", parents))
|| failed(getI64ArrayAttr(
op, "raptor.batch_parent_transfer_counts", counts)))
static FailureOr<RealizedOperation> parseRealizedOperation(Operation *op) {
bool scalar = op->hasAttr("raptor.channel_id");
bool batch = op->hasAttr("raptor.batch_channel_ids");
if (scalar == batch) {
op->emitOpError(
"must have exactly one scalar or compact metadata form");
return failure();
if (!batchChannels || !sources || !targets || !parents || !counts)
return op->emitOpError(
"is missing compact logical transfer metadata");
size_t size = batchChannels->size();
if (size == 0 || sources->size() != size || targets->size() != size
|| parents->size() != size || counts->size() != size)
return op->emitOpError(
"has non-parallel compact logical transfer metadata");
for (auto values : llvm::zip_equal(
*batchChannels, *parents, *counts, *sources, *targets)) {
RealizedLogicalTransfer transfer {
std::get<0>(values), std::get<1>(values), std::get<2>(values),
std::get<3>(values), std::get<4>(values)};
if (transfer.channelId < 0 || transfer.parentExchangeId < 0
|| transfer.parentTransferCount <= 0 || transfer.sourceCore < 0
|| transfer.targetCore < 0)
return op->emitOpError("has invalid compact logical transfer metadata");
if (failed(callback(transfer)))
}
auto batchCount = scalar ? FailureOr<size_t>(1)
: getBatchTransferCount(op);
if (failed(batchCount))
return failure();
size_t size = *batchCount;
auto channels = getStaticIntSequenceAttr(
op, scalar ? "raptor.channel_id" : "raptor.batch_channel_ids", size);
auto parents = getStaticIntSequenceAttr(
op, scalar ? "raptor.parent_exchange_id"
: "raptor.batch_parent_exchange_ids", size);
auto counts = getStaticIntSequenceAttr(
op, scalar ? "raptor.parent_transfer_count"
: "raptor.batch_parent_transfer_counts", size);
auto sources = getStaticIntSequenceAttr(
op, scalar ? "raptor.source_core" : "raptor.batch_source_cores", size);
auto targets = getStaticIntSequenceAttr(
op, scalar ? "raptor.target_core" : "raptor.batch_target_cores", size);
if (failed(channels) || failed(parents) || failed(counts)
|| failed(sources) || failed(targets))
return failure();
if (scalar) {
auto exchange = op->getAttrOfType<IntegerAttr>("raptor.exchange_id");
if (!exchange || exchange.getInt() != channels->valueAt(0)) {
op->emitOpError("has inconsistent scalar exchange metadata");
return failure();
}
}
return RealizedOperation {op, isa<SpatChannelSendOp>(op),
*channels, *parents, *counts, *sources, *targets};
}
struct CoreTransferSequences {
DenseMap<int64_t, StaticIntSequenceChain> sends;
DenseMap<int64_t, StaticIntSequenceChain> receives;
};
struct ExpectedFamily {
ExternalTransferFamily *family = nullptr;
int64_t firstChannel = 0;
int64_t endChannel = 0;
};
static void appendByCore(DenseMap<int64_t, StaticIntSequenceChain> &result,
const StaticIntSequence &channels,
const StaticIntSequence &cores, size_t begin,
size_t count) {
size_t end = begin + count;
cores.forEachEqualRun(
[&](int64_t core, size_t runBegin, size_t runCount) {
size_t selectedBegin = std::max(begin, runBegin);
size_t selectedEnd = std::min(end, runBegin + runCount);
if (selectedBegin < selectedEnd)
result[core].append(
channels, selectedBegin, selectedEnd - selectedBegin);
});
}
static LogicalResult compareSequences(
func::FuncOp funcOp,
const DenseMap<int64_t, StaticIntSequenceChain> &expected,
const DenseMap<int64_t, StaticIntSequenceChain> &actual, StringRef kind) {
if (expected.size() != actual.size())
return funcOp.emitOpError()
<< "realized " << kind << " stream set differs from plan";
for (const auto &[core, sequence] : expected) {
auto found = actual.find(core);
if (found == actual.end())
return funcOp.emitOpError() << "realized " << kind
<< " stream is missing on core " << core;
StaticIntSequenceChainCursor expectedCursor(sequence);
StaticIntSequenceChainCursor actualCursor(found->second);
uint64_t ordinal = 0;
while (!expectedCursor.done() && !actualCursor.done()
&& expectedCursor.value() == actualCursor.value()) {
expectedCursor.advance();
actualCursor.advance();
++ordinal;
}
if (expectedCursor.done() && actualCursor.done())
continue;
return funcOp.emitOpError()
<< "realized " << kind << " logical order differs on core "
<< core << " at ordinal " << ordinal << ": expected channel "
<< (expectedCursor.done() ? -1 : expectedCursor.value())
<< ", actual channel "
<< (actualCursor.done() ? -1 : actualCursor.value());
}
return success();
}
@@ -192,145 +257,136 @@ static LogicalResult forEachRealizedLogicalTransfer(
} // namespace
LogicalResult verifyPlannedCommunicationDeadlockFree(
Operation *anchor,
unsigned streamCount,
ArrayRef<unsigned> stepCounts,
ArrayRef<PlannedCommunicationTransfer> transfers) {
if (stepCounts.size() != streamCount)
return anchor->emitError("communication plan stream count does not match step counts");
SmallVector<SmallVector<Event>> streams(streamCount);
SmallVector<SmallVector<SmallVector<Event>>> atBoundary(streamCount);
for (unsigned stream = 0; stream < streamCount; ++stream)
atBoundary[stream].resize(stepCounts[stream] + 1);
for (const PlannedCommunicationTransfer &transfer : transfers) {
if (transfer.sourceStream >= streamCount || transfer.targetStream >= streamCount
|| transfer.producerStep >= stepCounts[transfer.sourceStream]
|| transfer.consumerStep >= stepCounts[transfer.targetStream]
|| transfer.sourceInsertionStep > stepCounts[transfer.sourceStream]
|| transfer.targetInsertionStep > stepCounts[transfer.targetStream]
|| transfer.sourceInsertionStep <= transfer.producerStep
|| transfer.targetInsertionStep > transfer.consumerStep)
return anchor->emitError("communication plan references an invalid stream step");
atBoundary[transfer.sourceStream][transfer.sourceInsertionStep].push_back(
{EventKind::Send, transfer.exchangeId});
atBoundary[transfer.targetStream][transfer.targetInsertionStep].push_back(
{EventKind::Receive, transfer.exchangeId});
}
for (unsigned stream = 0; stream < streamCount; ++stream) {
for (unsigned step = 0; step < stepCounts[stream]; ++step) {
llvm::append_range(streams[stream], atBoundary[stream][step]);
streams[stream].push_back({EventKind::Compute, 0});
Operation *anchor, ArrayRef<unsigned> stepCounts,
const ScheduledCommunicationPlan &plan) {
for (const ScheduledTransferSlice &slice : plan.slices) {
ExternalTransferFamily &family = *slice.family;
for (size_t offset = 0; offset < slice.transferCount; ++offset) {
size_t index = slice.familyOffset + offset;
unsigned source = family.sourceStreams.valueAt(index);
unsigned target = family.targetStreams.valueAt(index);
if (source >= stepCounts.size() || target >= stepCounts.size()
|| slice.sourceInsertionStep > stepCounts[source]
|| slice.targetInsertionStep > stepCounts[target]
|| slice.sourceInsertionStep <= family.requirement->producer->step
|| slice.targetInsertionStep
> family.requirement->exchange->consumerStep)
return anchor->emitError(
"communication plan references an invalid stream step");
}
llvm::append_range(streams[stream], atBoundary[stream].back());
}
return simulate(anchor, streams, "planned");
return simulatePlanned(anchor, stepCounts, plan);
}
LogicalResult verifyRealizedCommunicationDeadlockFree(func::FuncOp funcOp) {
struct LogicalOperation {
Operation *op = nullptr;
RealizedLogicalTransfer transfer;
};
DenseMap<int64_t, SmallVector<LogicalOperation, 2>> operationsByExchange;
struct ParentExchange {
std::optional<int64_t> expectedTransfers;
DenseSet<int64_t> channels;
};
DenseMap<int64_t, ParentExchange> parentExchanges;
DenseMap<int64_t, unsigned> streamByCore;
SmallVector<int64_t> cores;
LogicalResult verifyRealizedCommunicationDeadlockFree(
func::FuncOp funcOp, const ScheduledCommunicationPlan &plan) {
SmallVector<ExpectedFamily> families;
DenseMap<ExternalTransferFamily *, unsigned> familyIndex;
for (const ScheduledTransferSlice &slice : plan.slices) {
ExternalTransferFamily *family = slice.family;
if (familyIndex.count(family))
continue;
size_t count = family->channelIds.size();
int64_t first = family->channelIds.valueAt(0);
for (size_t index = 1; index < count; ++index)
if (family->channelIds.valueAt(index)
!= first + static_cast<int64_t>(index))
return funcOp.emitOpError(
"planned communication family has non-consecutive channels");
familyIndex[family] = families.size();
families.push_back({family, first, first + static_cast<int64_t>(count)});
}
llvm::sort(families, [](const ExpectedFamily &lhs,
const ExpectedFamily &rhs) {
return lhs.firstChannel < rhs.firstChannel;
});
familyIndex.clear();
std::map<int64_t, unsigned> familyByFirstChannel;
int64_t nextChannel = 0;
for (auto [index, expected] : llvm::enumerate(families)) {
if (expected.firstChannel != nextChannel)
return funcOp.emitOpError(
"planned communication channels are not exactly contiguous");
nextChannel = expected.endChannel;
familyIndex[expected.family] = index;
familyByFirstChannel.emplace(expected.firstChannel, index);
}
if (static_cast<uint64_t>(nextChannel) != plan.logicalTransferCount)
return funcOp.emitOpError(
"planned communication channel count is inconsistent");
CoreTransferSequences expected;
for (const ScheduledTransferSlice &slice : plan.slices) {
ExternalTransferFamily &family = *slice.family;
appendByCore(expected.sends, family.channelIds, family.sourceCores,
slice.familyOffset, slice.transferCount);
appendByCore(expected.receives, family.channelIds, family.targetCores,
slice.familyOffset, slice.transferCount);
}
CoreTransferSequences actual;
SmallVector<std::unique_ptr<StaticIntSequence>> actualChannels;
bool invalid = false;
funcOp.walk([&](Operation *op) {
if (!isa<SpatChannelSendOp, SpatChannelReceiveOp>(op))
if (invalid || !isa<SpatChannelSendOp, SpatChannelReceiveOp>(op))
return;
if (failed(forEachRealizedLogicalTransfer(
op, [&](const RealizedLogicalTransfer &transfer) -> LogicalResult {
operationsByExchange[transfer.channelId].push_back(
{op, transfer});
ParentExchange &parent =
parentExchanges[transfer.parentExchangeId];
if (parent.expectedTransfers
&& *parent.expectedTransfers
!= transfer.parentTransferCount)
return op->emitOpError(
"declares an inconsistent parent transfer count");
parent.expectedTransfers = transfer.parentTransferCount;
parent.channels.insert(transfer.channelId);
for (int64_t core : {transfer.sourceCore, transfer.targetCore})
if (!llvm::is_contained(cores, core))
cores.push_back(core);
return success();
})))
auto realized = parseRealizedOperation(op);
if (failed(realized)) {
invalid = true;
});
llvm::sort(cores);
for (auto [index, core] : llvm::enumerate(cores))
streamByCore[core] = index;
SmallVector<SmallVector<Event>> streams(cores.size());
funcOp.walk([&](Operation *op) {
if (!isa<SpatChannelSendOp, SpatChannelReceiveOp>(op))
return;
if (failed(forEachRealizedLogicalTransfer(
op, [&](const RealizedLogicalTransfer &transfer) {
unsigned stream = streamByCore.lookup(
isa<SpatChannelSendOp>(op) ? transfer.sourceCore
: transfer.targetCore);
streams[stream].push_back(
{isa<SpatChannelSendOp>(op) ? EventKind::Send
: EventKind::Receive,
static_cast<uint64_t>(transfer.channelId)});
return success();
})))
invalid = true;
}
Type payloadType = realized->send
? cast<SpatChannelSendOp>(op).getInput().getType()
: cast<SpatChannelReceiveOp>(op).getOutput().getType();
for (size_t index = 0; index < realized->channels.size(); ++index) {
int64_t channel = realized->channels.valueAt(index);
auto upper = familyByFirstChannel.upper_bound(channel);
if (upper == familyByFirstChannel.begin()) {
op->emitOpError("references an unknown logical channel");
invalid = true;
return;
}
ExpectedFamily &expected = families[std::prev(upper)->second];
if (channel >= expected.endChannel) {
op->emitOpError("references an unknown logical channel");
invalid = true;
return;
}
ExternalTransferFamily &family = *expected.family;
size_t familyOffset = channel - expected.firstChannel;
RequirementFamily &requirement = *family.requirement;
if (realized->parents.valueAt(index)
!= static_cast<int64_t>(requirement.exchange->exchangeId)
|| realized->counts.valueAt(index)
!= requirement.exchange->externalTransferCount
|| realized->sources.valueAt(index)
!= family.sourceCores.valueAt(familyOffset)
|| realized->targets.valueAt(index)
!= family.targetCores.valueAt(familyOffset)
|| payloadType != requirement.publicationFragmentType) {
op->emitOpError(
"logical transfer metadata differs from its symbolic family");
invalid = true;
return;
}
}
if (invalid)
return;
actualChannels.push_back(
std::make_unique<StaticIntSequence>(std::move(realized->channels)));
appendByCore(realized->send ? actual.sends : actual.receives,
*actualChannels.back(),
realized->send ? realized->sources : realized->targets,
0, actualChannels.back()->size());
});
if (invalid)
return failure();
for (const auto &entry : parentExchanges)
if (!entry.second.expectedTransfers
|| entry.second.channels.size()
!= static_cast<size_t>(*entry.second.expectedTransfers))
return funcOp.emitOpError()
<< "parent exchange " << entry.first
<< " does not contain its declared lane transfer set";
for (const auto &entry : operationsByExchange) {
if (entry.second.size() != 2
|| isa<SpatChannelSendOp>(entry.second[0].op)
== isa<SpatChannelSendOp>(entry.second[1].op)) {
return funcOp.emitOpError()
<< "exchange " << entry.first
<< " does not have exactly one send and one receive (sends="
<< llvm::count_if(entry.second, [](const LogicalOperation &item) {
return isa<SpatChannelSendOp>(item.op);
})
<< ", receives="
<< llvm::count_if(entry.second, [](const LogicalOperation &item) {
return isa<SpatChannelReceiveOp>(item.op);
})
<< ")";
}
const LogicalOperation &first = entry.second[0];
const LogicalOperation &second = entry.second[1];
const LogicalOperation &sendRecord =
isa<SpatChannelSendOp>(first.op) ? first : second;
const LogicalOperation &receiveRecord =
isa<SpatChannelReceiveOp>(first.op) ? first : second;
auto send = cast<SpatChannelSendOp>(sendRecord.op);
auto receive = cast<SpatChannelReceiveOp>(receiveRecord.op);
if (send.getInput().getType() != receive.getOutput().getType())
return send.emitOpError("send and receive payload types do not match");
if (receiveRecord.transfer.sourceCore != sendRecord.transfer.sourceCore
|| receiveRecord.transfer.targetCore
!= sendRecord.transfer.targetCore
|| receiveRecord.transfer.parentExchangeId
!= sendRecord.transfer.parentExchangeId
|| receiveRecord.transfer.parentTransferCount
!= sendRecord.transfer.parentTransferCount)
return receive.emitOpError("receive core metadata does not match its send");
}
return simulate(funcOp, streams, "realized");
if (failed(compareSequences(
funcOp, expected.sends, actual.sends, "send"))
|| failed(compareSequences(
funcOp, expected.receives, actual.receives, "receive")))
return failure();
return success();
}
} // namespace onnx_mlir::spatial
@@ -1,27 +1,18 @@
#pragma once
#include "DeferredCommunicationScheduling.hpp"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Support/LLVM.h"
namespace onnx_mlir::spatial {
struct PlannedCommunicationTransfer {
uint64_t exchangeId = 0;
uint64_t parentExchangeId = 0;
unsigned sourceStream = 0;
unsigned targetStream = 0;
unsigned producerStep = 0;
unsigned consumerStep = 0;
unsigned sourceInsertionStep = 0;
unsigned targetInsertionStep = 0;
};
mlir::LogicalResult verifyPlannedCommunicationDeadlockFree(
mlir::Operation *anchor,
unsigned streamCount,
mlir::ArrayRef<unsigned> stepCounts,
mlir::ArrayRef<PlannedCommunicationTransfer> transfers);
const ScheduledCommunicationPlan &plan);
mlir::LogicalResult verifyRealizedCommunicationDeadlockFree(mlir::func::FuncOp funcOp);
mlir::LogicalResult verifyRealizedCommunicationDeadlockFree(
mlir::func::FuncOp funcOp,
const ScheduledCommunicationPlan &plan);
} // namespace onnx_mlir::spatial
@@ -0,0 +1,236 @@
#pragma once
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/IR/Operation.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallVector.h"
#include <memory>
#include <optional>
#include <variant>
#include "src/Accelerators/PIM/Common/IR/StaticIntSequence.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
namespace onnx_mlir::spatial {
struct LaneInterval {
unsigned begin = 0;
unsigned end = 0;
bool operator==(const LaneInterval& other) const { return begin == other.begin && end == other.end; }
};
class LaneSet {
public:
static LaneSet all(unsigned laneCount) { return range(0, laneCount); }
static LaneSet range(unsigned begin, unsigned end) {
LaneSet lanes;
if (begin < end)
lanes.ranges.push_back({begin, end});
return lanes;
}
bool empty() const { return ranges.empty(); }
size_t size() const {
size_t result = 0;
for (LaneInterval range : ranges)
result += range.end - range.begin;
return result;
}
bool contains(unsigned lane) const {
return llvm::any_of(ranges, [&](LaneInterval range) { return range.begin <= lane && lane < range.end; });
}
llvm::ArrayRef<LaneInterval> intervals() const { return ranges; }
LaneSet intersect(const LaneSet& other) const {
LaneSet result;
for (LaneInterval lhs : ranges)
for (LaneInterval rhs : other.ranges) {
unsigned begin = std::max(lhs.begin, rhs.begin);
unsigned end = std::min(lhs.end, rhs.end);
if (begin < end)
result.append(begin, end);
}
return result;
}
LaneSet subtract(const LaneSet& other) const {
LaneSet result;
for (LaneInterval source : ranges) {
unsigned cursor = source.begin;
for (LaneInterval removed : other.ranges) {
if (removed.end <= cursor || removed.begin >= source.end)
continue;
if (cursor < removed.begin)
result.append(cursor, std::min(removed.begin, source.end));
cursor = std::max(cursor, removed.end);
if (cursor >= source.end)
break;
}
if (cursor < source.end)
result.append(cursor, source.end);
}
return result;
}
LaneSet unite(const LaneSet& other) const {
llvm::SmallVector<LaneInterval, 4> combined(ranges.begin(), ranges.end());
llvm::append_range(combined, other.ranges);
llvm::sort(combined, [](LaneInterval lhs, LaneInterval rhs) { return lhs.begin < rhs.begin; });
LaneSet normalized;
for (LaneInterval range : combined)
normalized.append(range.begin, range.end);
return normalized;
}
bool operator==(const LaneSet& other) const { return ranges == other.ranges; }
private:
void append(unsigned begin, unsigned end) {
if (begin >= end)
return;
if (!ranges.empty() && begin <= ranges.back().end) {
ranges.back().end = std::max(ranges.back().end, end);
return;
}
ranges.push_back({begin, end});
}
llvm::SmallVector<LaneInterval, 2> ranges;
};
struct RequirementCoordinate {
unsigned leafIndex = 0;
unsigned selectedPosition = 0;
bool operator==(const RequirementCoordinate& other) const {
return leafIndex == other.leafIndex && selectedPosition == other.selectedPosition;
}
};
enum class DeferredLeafForm {
DirectSource,
GraphBatchProjection
};
enum class DeferredAssemblySourceTransform {
Identity,
AddLeadingUnitDimension,
RemoveLeadingUnitDimension
};
struct DeferredSliceTemplate {
llvm::SmallVector<mlir::OpFoldResult> offsets;
llvm::SmallVector<mlir::OpFoldResult> sizes;
llvm::SmallVector<mlir::OpFoldResult> strides;
};
struct DeferredProjectionLeafTemplate {
DeferredLeafForm form = DeferredLeafForm::DirectSource;
mlir::Value sourceRoot;
mlir::Value replacementRoot;
mlir::tensor::ExtractSliceOp leadingProjection;
DeferredSliceTemplate leadingGeometry;
DeferredSliceTemplate innerGeometry;
mlir::RankedTensorType reconstructedType;
};
struct DeferredInsertAssemblyEntryTemplate {
RequirementCoordinate coordinate;
DeferredAssemblySourceTransform sourceTransform = DeferredAssemblySourceTransform::Identity;
mlir::RankedTensorType sourceType;
DeferredSliceTemplate targetGeometry;
};
struct DeferredInsertAssemblyTemplate {
mlir::tensor::EmptyOp initialValue;
mlir::RankedTensorType resultType;
llvm::SmallVector<DeferredInsertAssemblyEntryTemplate> entries;
};
struct DeferredProgramTemplate {
SpatDeferredCommunicationOp deferred;
mlir::Value scheduledLane;
mlir::Value yieldedValue;
llvm::SmallVector<DeferredProjectionLeafTemplate, 0> leaves;
llvm::SmallVector<mlir::Operation*> residualOps;
std::optional<DeferredInsertAssemblyTemplate> insertAssembly;
};
struct ScheduledInfo;
struct ProducedValue {
ScheduledInfo* scheduled = nullptr;
unsigned step = 0;
unsigned resultIndex = 0;
int64_t graphId = -1;
int64_t core = -1;
int64_t laneStart = 0;
int64_t laneCount = 1;
unsigned scheduledLane = 0;
int64_t publishedSlotStart = 0;
int64_t publishedSlotCount = 1;
mlir::Value payload;
mlir::Value published;
};
struct ScheduledInfo {
mlir::Operation* op = nullptr;
llvm::SmallVector<mlir::Block*> blocks;
llvm::SmallVector<mlir::Operation*> stepAnchors;
llvm::SmallVector<int64_t> cores;
llvm::SmallVector<int64_t> stepSourceIds;
llvm::SmallVector<int64_t> resultOffsets;
llvm::SmallVector<int64_t> resultCounts;
llvm::SmallVector<ProducedValue*> produced;
llvm::SmallVector<unsigned> streamIds;
bool isBatch() const { return mlir::isa<SpatScheduledComputeBatch>(op); }
};
struct DeferredExchangePlan;
struct RequirementFamily {
DeferredExchangePlan* exchange = nullptr;
RequirementCoordinate coordinate;
LaneSet targetLanes;
ProducedValue* producer = nullptr;
mlir::Type publicationFragmentType;
std::optional<StaticIntSequence> graphLanes;
std::optional<StaticIntSequence> producerLocalOffsets;
};
struct LocalAvailabilityFamily {
RequirementFamily* requirement = nullptr;
LaneSet targetLanes;
};
struct ExternalTransferFamily {
RequirementFamily* requirement = nullptr;
LaneSet targetLanes;
ScheduledInfo* sourceScheduled = nullptr;
ScheduledInfo* targetScheduled = nullptr;
StaticIntSequence sourceStreams = StaticIntSequence::uniform(0, 1);
StaticIntSequence targetStreams = StaticIntSequence::uniform(0, 1);
StaticIntSequence sourceCores = StaticIntSequence::uniform(0, 1);
StaticIntSequence targetCores = StaticIntSequence::uniform(0, 1);
StaticIntSequence channelIds = StaticIntSequence::uniform(0, 1);
};
struct DeferredExchangePlan {
uint64_t exchangeId = 0;
SpatDeferredCommunicationOp deferred;
ScheduledInfo* target = nullptr;
unsigned consumerStep = 0;
unsigned targetLaneCount = 1;
DeferredProgramTemplate program;
llvm::SmallVector<RequirementFamily, 0> requirements;
llvm::SmallVector<LocalAvailabilityFamily> local;
llvm::SmallVector<ExternalTransferFamily, 0> external;
unsigned externalTransferCount = 0;
};
} // namespace onnx_mlir::spatial
@@ -0,0 +1,409 @@
#include "llvm/ADT/MapVector.h"
#include <limits>
#include <queue>
#include "DeferredCommunicationScheduling.hpp"
#include "DeferredTransferPlanning.hpp"
namespace onnx_mlir::spatial {
using namespace mlir;
namespace {
struct StreamThreshold {
unsigned stream = 0;
unsigned completedStep = 0;
};
struct TransferGroup {
DeferredExchangePlan* exchange = nullptr;
SmallVector<ScheduledTransferSlice> ordered;
SmallVector<StreamThreshold> dependencies;
unsigned unsatisfied = 0;
bool ready = false;
bool scheduled = false;
BoundaryCost cost;
std::tuple<unsigned, unsigned, unsigned, uint64_t> originalPriority;
std::optional<TransferEmissionSignature> firstSignature;
};
struct StreamProgress {
unsigned completedStep = 0;
SmallVector<unsigned> pendingAtStep;
SmallVector<SmallVector<unsigned>> unlockedAtStep;
bool queued = false;
};
static size_t hashSignature(const TransferEmissionSignature& signature) {
return llvm::hash_combine(signature.scheduled,
signature.payload.getAsOpaquePointer(),
signature.fragmentType.getAsOpaquePointer(),
signature.hasGraphLane,
signature.sourceIsBatch);
}
static bool betterStaticPriority(const TransferGroup& lhs, const TransferGroup& rhs) {
auto left = std::tuple(lhs.cost.instructionCount,
lhs.cost.branchRegions,
std::numeric_limits<unsigned>::max()
- lhs.cost.absorbedTransfers,
lhs.cost.lookupEntries,
lhs.originalPriority);
auto right = std::tuple(rhs.cost.instructionCount,
rhs.cost.branchRegions,
std::numeric_limits<unsigned>::max()
- rhs.cost.absorbedTransfers,
rhs.cost.lookupEntries,
rhs.originalPriority);
return left < right;
}
static bool orderPermutationCycles(SmallVectorImpl<ScheduledTransferSlice>& slices) {
if (slices.size() < 2)
return false;
DenseMap<unsigned, unsigned> edgeBySource;
DenseMap<unsigned, unsigned> edgeByTarget;
for (auto [index, slice] : llvm::enumerate(slices)) {
ExternalTransferFamily& family = *slice.family;
if (slice.transferCount != 1 || family.sourceScheduled != family.targetScheduled)
return false;
unsigned source = family.requirement->producer->scheduledLane;
unsigned target = family.targetLanes.intervals().front().begin + slice.familyOffset;
if (!edgeBySource.try_emplace(source, index).second || !edgeByTarget.try_emplace(target, index).second)
return false;
}
SmallVector<ScheduledTransferSlice> ordered;
SmallVector<bool> emitted(slices.size());
auto appendChain = [&](unsigned source) {
while (true) {
auto edge = edgeBySource.find(source);
if (edge == edgeBySource.end() || emitted[edge->second])
break;
unsigned index = edge->second;
emitted[index] = true;
ordered.push_back(slices[index]);
ExternalTransferFamily& family = *slices[index].family;
source = family.targetLanes.intervals().front().begin + slices[index].familyOffset;
}
};
SmallVector<unsigned> pathStarts;
for (auto [source, index] : edgeBySource)
if (!edgeByTarget.count(source))
pathStarts.push_back(source);
llvm::sort(pathStarts);
for (unsigned source : pathStarts)
appendChain(source);
while (ordered.size() != slices.size()) {
unsigned source = std::numeric_limits<unsigned>::max();
for (auto [candidate, index] : edgeBySource)
if (!emitted[index])
source = std::min(source, candidate);
appendChain(source);
}
slices = std::move(ordered);
return true;
}
static void orderGroupSlices(TransferGroup& group) {
SmallVector<TransferEmissionSignature> signatures;
DenseMap<size_t, SmallVector<unsigned>> signatureIdsByHash;
SmallVector<SmallVector<ScheduledTransferSlice>> slicesBySignature;
SmallVector<unsigned> order;
for (ExternalTransferFamily& family : group.exchange->external) {
ScheduledTransferSlice slice {&family, 0, family.targetLanes.size()};
TransferEmissionSignature signature = getTransferEmissionSignature(family);
size_t hash = hashSignature(signature);
std::optional<unsigned> id;
for (unsigned candidate : signatureIdsByHash.lookup(hash))
if (signatures[candidate] == signature) {
id = candidate;
break;
}
if (!id) {
id = signatures.size();
signatures.push_back(signature);
signatureIdsByHash[hash].push_back(*id);
slicesBySignature.emplace_back();
order.push_back(*id);
}
slicesBySignature[*id].push_back(slice);
}
llvm::stable_sort(order, [&](unsigned lhs, unsigned rhs) {
auto count = [&](unsigned id) {
size_t result = 0;
for (const ScheduledTransferSlice& slice : slicesBySignature[id])
result += slice.transferCount;
return result;
};
return count(lhs) > count(rhs);
});
for (unsigned id : order) {
if (!orderPermutationCycles(slicesBySignature[id]))
llvm::stable_sort(slicesBySignature[id], [](const ScheduledTransferSlice& lhs, const ScheduledTransferSlice& rhs) {
ExternalTransferFamily& left = *lhs.family;
ExternalTransferFamily& right = *rhs.family;
return std::tuple(left.sourceStreams.valueAt(lhs.familyOffset), left.targetLanes.intervals().front().begin)
> std::tuple(right.sourceStreams.valueAt(rhs.familyOffset), right.targetLanes.intervals().front().begin);
});
llvm::append_range(group.ordered, slicesBySignature[id]);
}
group.cost = estimateCanonicalBoundaryCost({}, group.ordered);
if (!group.ordered.empty())
group.firstSignature = getTransferEmissionSignature(*group.ordered.front().family);
}
static SmallVector<TransferGroup> buildGroups(DeferredTransferPlan& plan) {
SmallVector<TransferGroup> groups;
for (const std::unique_ptr<DeferredExchangePlan>& exchange : plan.exchanges) {
if (exchange->external.empty())
continue;
TransferGroup group;
group.exchange = exchange.get();
DenseMap<unsigned, unsigned> thresholdByStream;
unsigned minSource = std::numeric_limits<unsigned>::max();
unsigned minTarget = std::numeric_limits<unsigned>::max();
for (ExternalTransferFamily& family : exchange->external) {
unsigned source = family.sourceStreams.valueAt(0);
thresholdByStream[source] = std::max(thresholdByStream.lookup(source), family.requirement->producer->step + 1);
minSource = std::min(minSource, source);
for (size_t index = 0; index < family.targetStreams.size(); ++index)
minTarget = std::min<unsigned>(minTarget, family.targetStreams.valueAt(index));
}
for (LocalAvailabilityFamily& local : exchange->local)
for (LaneInterval interval : local.targetLanes.intervals())
for (unsigned lane = interval.begin; lane < interval.end; ++lane) {
unsigned stream = exchange->target->streamIds[lane];
thresholdByStream[stream] = std::max(thresholdByStream.lookup(stream), local.requirement->producer->step + 1);
}
SmallVector<unsigned> streams;
for (auto [stream, threshold] : thresholdByStream)
streams.push_back(stream);
llvm::sort(streams);
for (unsigned stream : streams)
group.dependencies.push_back({stream, thresholdByStream.lookup(stream)});
group.unsatisfied =
llvm::count_if(group.dependencies, [](StreamThreshold dependency) { return dependency.completedStep != 0; });
group.originalPriority = {exchange->consumerStep, minSource, minTarget, exchange->exchangeId};
orderGroupSlices(group);
groups.push_back(std::move(group));
}
return groups;
}
static unsigned tailExtension(const TransferGroup& group,
const TransferEmissionSignature& tail,
unsigned tailBoundary,
ArrayRef<StreamProgress> streams) {
unsigned extension = 0;
for (const ScheduledTransferSlice& slice : group.ordered) {
ExternalTransferFamily& family = *slice.family;
if (!(getTransferEmissionSignature(family) == tail))
break;
for (size_t index = 0; index < slice.transferCount; ++index) {
size_t familyIndex = slice.familyOffset + index;
if (streams[family.sourceStreams.valueAt(familyIndex)].completedStep
!= tailBoundary)
return extension;
++extension;
}
}
return extension;
}
} // namespace
TransferEmissionSignature getTransferEmissionSignature(const ExternalTransferFamily& family) {
ProducedValue* producer = family.requirement->producer;
return {producer->scheduled,
producer->payload,
family.requirement->publicationFragmentType,
family.requirement->graphLanes.has_value(),
producer->scheduled->isBatch()};
}
BoundaryCost estimateCanonicalBoundaryCost(
ArrayRef<ScheduledTransferSlice> existingTail,
ArrayRef<ScheduledTransferSlice> candidate) {
BoundaryCost cost;
std::optional<TransferEmissionSignature> previous;
RequirementFamily* previousRequirement = nullptr;
if (!existingTail.empty()) {
previous = getTransferEmissionSignature(*existingTail.back().family);
previousRequirement = existingTail.back().family->requirement;
}
for (const ScheduledTransferSlice& slice : candidate) {
TransferEmissionSignature signature = getTransferEmissionSignature(*slice.family);
if (!previous || !(*previous == signature))
++cost.instructionCount;
else
cost.absorbedTransfers += slice.transferCount;
previous = signature;
if (previousRequirement != slice.family->requirement)
++cost.instructionCount;
previousRequirement = slice.family->requirement;
cost.lookupEntries += slice.transferCount;
}
return cost;
}
FailureOr<ScheduledCommunicationPlan> scheduleDeferredCommunication(func::FuncOp funcOp, DeferredTransferPlan& plan) {
SmallVector<TransferGroup> groups = buildGroups(plan);
SmallVector<StreamProgress> streams(plan.stepCounts.size());
for (auto [stream, progress] : llvm::enumerate(streams)) {
progress.pendingAtStep.resize(plan.stepCounts[stream]);
progress.unlockedAtStep.resize(plan.stepCounts[stream] + 1);
}
for (const std::unique_ptr<DeferredExchangePlan>& exchange : plan.exchanges)
for (ExternalTransferFamily& family : exchange->external)
for (size_t index = 0; index < family.targetStreams.size(); ++index)
++streams[family.targetStreams.valueAt(index)].pendingAtStep[exchange->consumerStep];
for (auto [groupIndex, group] : llvm::enumerate(groups))
for (StreamThreshold dependency : group.dependencies)
streams[dependency.stream].unlockedAtStep[dependency.completedStep].push_back(groupIndex);
auto compare = [&](unsigned lhs, unsigned rhs) { return betterStaticPriority(groups[rhs], groups[lhs]); };
using Heap = std::priority_queue<unsigned, std::vector<unsigned>, decltype(compare)>;
Heap ready(compare);
DenseMap<size_t, std::unique_ptr<Heap>> bySignature;
auto addReady = [&](unsigned index) {
TransferGroup& group = groups[index];
if (group.ready || group.scheduled)
return;
group.ready = true;
ready.push(index);
if (group.firstSignature) {
auto& bucket = bySignature[hashSignature(*group.firstSignature)];
if (!bucket)
bucket = std::make_unique<Heap>(compare);
bucket->push(index);
}
};
for (auto [index, group] : llvm::enumerate(groups))
if (group.unsatisfied == 0)
addReady(index);
std::queue<unsigned> advanceable;
auto enqueue = [&](unsigned stream) {
StreamProgress& progress = streams[stream];
if (!progress.queued && progress.completedStep < plan.stepCounts[stream]
&& progress.pendingAtStep[progress.completedStep] == 0) {
progress.queued = true;
advanceable.push(stream);
}
};
for (unsigned stream = 0; stream < streams.size(); ++stream)
enqueue(stream);
auto advance = [&] {
bool changed = false;
while (!advanceable.empty()) {
unsigned stream = advanceable.front();
advanceable.pop();
StreamProgress& progress = streams[stream];
progress.queued = false;
if (progress.completedStep == plan.stepCounts[stream] || progress.pendingAtStep[progress.completedStep] != 0)
continue;
++progress.completedStep;
changed = true;
for (unsigned groupIndex : progress.unlockedAtStep[progress.completedStep])
if (--groups[groupIndex].unsatisfied == 0)
addReady(groupIndex);
enqueue(stream);
}
return changed;
};
ScheduledCommunicationPlan result;
unsigned finishedGroups = 0;
while (finishedGroups != groups.size()) {
bool progressed = advance();
std::optional<unsigned> chosen;
unsigned bestExtension = 0;
if (!result.slices.empty()) {
ScheduledTransferSlice& tailSlice = result.slices.back();
ExternalTransferFamily& tailFamily = *tailSlice.family;
TransferEmissionSignature tail = getTransferEmissionSignature(tailFamily);
size_t hash = hashSignature(tail);
auto bucket = bySignature.find(hash);
SmallVector<unsigned> inspected;
while (bucket != bySignature.end() && !bucket->second->empty()) {
unsigned candidate = bucket->second->top();
bucket->second->pop();
TransferGroup& group = groups[candidate];
if (!group.ready || group.scheduled)
continue;
inspected.push_back(candidate);
unsigned extension = tailExtension(
group, tail, tailSlice.sourceInsertionStep, streams);
if (extension > bestExtension
|| (extension == bestExtension && extension != 0
&& (!chosen || betterStaticPriority(group, groups[*chosen])))) {
chosen = candidate;
bestExtension = extension;
}
}
for (unsigned candidate : inspected)
if (!chosen || candidate != *chosen)
bucket->second->push(candidate);
}
while (!chosen && !ready.empty()) {
unsigned candidate = ready.top();
ready.pop();
if (groups[candidate].ready && !groups[candidate].scheduled)
chosen = candidate;
}
if (chosen) {
TransferGroup& group = groups[*chosen];
group.ready = false;
group.scheduled = true;
++finishedGroups;
for (ScheduledTransferSlice slice : group.ordered) {
ExternalTransferFamily& family = *slice.family;
size_t end = slice.familyOffset + slice.transferCount;
for (size_t begin = slice.familyOffset; begin < end;) {
unsigned sourceStep = streams[
family.sourceStreams.valueAt(begin)].completedStep;
unsigned targetStep = streams[
family.targetStreams.valueAt(begin)].completedStep;
size_t next = begin + 1;
while (next < end
&& streams[family.sourceStreams.valueAt(next)].completedStep
== sourceStep
&& streams[family.targetStreams.valueAt(next)].completedStep
== targetStep)
++next;
result.slices.push_back(
{&family, begin, next - begin, sourceStep, targetStep});
result.logicalTransferCount += next - begin;
begin = next;
}
for (size_t index = slice.familyOffset; index < end; ++index) {
unsigned stream = family.targetStreams.valueAt(index);
unsigned& pending = streams[stream].pendingAtStep[group.exchange->consumerStep];
--pending;
if (pending == 0 && streams[stream].completedStep == group.exchange->consumerStep)
enqueue(stream);
}
}
progressed = true;
}
if (progressed)
continue;
InFlightDiagnostic diagnostic =
funcOp.emitOpError("global communication scheduler made no progress before IR mutation");
unsigned reported = 0;
for (const TransferGroup& group : groups) {
if (group.scheduled || reported++ == 8)
continue;
diagnostic << "; blocked parent " << group.exchange->exchangeId;
auto dependency = llvm::find_if(group.dependencies, [&](StreamThreshold item) {
return streams[item.stream].completedStep < item.completedStep;
});
if (dependency != group.dependencies.end())
diagnostic << " stream " << dependency->stream << " requires " << dependency->completedStep;
}
return failure();
}
return result;
}
} // namespace onnx_mlir::spatial
@@ -0,0 +1,55 @@
#pragma once
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "DeferredCommunicationModel.hpp"
namespace onnx_mlir::spatial {
struct DeferredTransferPlan;
struct ScheduledTransferSlice {
ExternalTransferFamily* family = nullptr;
size_t familyOffset = 0;
size_t transferCount = 0;
unsigned sourceInsertionStep = 0;
unsigned targetInsertionStep = 0;
};
struct ScheduledCommunicationPlan {
llvm::SmallVector<ScheduledTransferSlice> slices;
uint64_t logicalTransferCount = 0;
};
struct TransferEmissionSignature {
ScheduledInfo* scheduled = nullptr;
mlir::Value payload;
mlir::Type fragmentType;
bool hasGraphLane = false;
bool sourceIsBatch = false;
bool operator==(const TransferEmissionSignature& other) const {
return scheduled == other.scheduled && payload == other.payload
&& fragmentType == other.fragmentType
&& hasGraphLane == other.hasGraphLane
&& sourceIsBatch == other.sourceIsBatch;
}
};
struct BoundaryCost {
unsigned instructionCount = 0;
unsigned branchRegions = 0;
unsigned absorbedTransfers = 0;
unsigned lookupEntries = 0;
};
TransferEmissionSignature getTransferEmissionSignature(const ExternalTransferFamily& family);
BoundaryCost estimateCanonicalBoundaryCost(
mlir::ArrayRef<ScheduledTransferSlice> existingTail,
mlir::ArrayRef<ScheduledTransferSlice> candidate);
mlir::FailureOr<ScheduledCommunicationPlan> scheduleDeferredCommunication(mlir::func::FuncOp funcOp,
DeferredTransferPlan& plan);
} // namespace onnx_mlir::spatial
File diff suppressed because it is too large Load Diff
@@ -1,8 +1,6 @@
#pragma once
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/IR/Operation.h"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
#include "DeferredCommunicationModel.hpp"
#include "llvm/ADT/DenseMap.h"
@@ -17,63 +15,32 @@ mlir::FailureOr<int64_t> evaluateDeferredIndex(
mlir::FailureOr<int64_t> evaluateDeferredIndex(
mlir::OpFoldResult value, const StaticIndexEnvironment &environment);
enum class DeferredLeafKind { ScalarSource, GraphBatchProjection, GraphBatchIdentity };
struct StaticSliceGeometry {
llvm::SmallVector<int64_t> offsets;
llvm::SmallVector<int64_t> sizes;
llvm::SmallVector<int64_t> strides;
};
struct DeferredProjectionLeaf {
DeferredLeafKind kind = DeferredLeafKind::ScalarSource;
unsigned sourceOperandIndex = 0;
mlir::Value replacementRoot;
mlir::tensor::ExtractSliceOp leadingProjection;
llvm::SmallVector<int64_t> physicalSlots;
StaticSliceGeometry innerGeometry;
mlir::RankedTensorType reconstructedType;
};
struct DeferredInsertAssemblyEntry {
unsigned requirementIndex = 0;
unsigned leafIndex = 0;
StaticSliceGeometry targetGeometry;
};
struct DeferredInsertAssembly {
mlir::tensor::EmptyOp initialValue;
mlir::RankedTensorType resultType;
llvm::SmallVector<DeferredInsertAssemblyEntry, 0> entries;
};
struct SpecializedDeferredProgram {
SpatDeferredCommunicationOp deferred;
std::optional<unsigned> targetScheduledLane;
mlir::Value scheduledLane;
mlir::Value yieldedValue;
llvm::SmallVector<DeferredProjectionLeaf, 0> leaves;
llvm::SmallVector<mlir::Operation *> residualOps;
llvm::DenseMap<mlir::Value, int64_t> staticValues;
std::optional<DeferredInsertAssembly> insertAssembly;
};
struct ResolvedDeferredSource {
unsigned sourceOperandIndex = 0;
mlir::Value selectedValue;
};
mlir::FailureOr<std::optional<ResolvedDeferredSource>> tryResolveDeferredSource(
mlir::Value value, SpatDeferredCommunicationOp deferred,
const StaticIndexEnvironment &environment);
mlir::FailureOr<ResolvedDeferredSource> requireResolvedDeferredSource(
mlir::Value value, SpatDeferredCommunicationOp deferred,
const StaticIndexEnvironment &environment);
mlir::LogicalResult verifyDeferredProgramContract(
SpatDeferredCommunicationOp deferred);
mlir::FailureOr<SpecializedDeferredProgram> analyzeDeferredProgram(
SpatDeferredCommunicationOp deferred,
std::optional<unsigned> targetScheduledLane);
mlir::FailureOr<DeferredProgramTemplate> analyzeDeferredProgramTemplate(
SpatDeferredCommunicationOp deferred);
class DeferredLaneValueEvaluator {
public:
DeferredLaneValueEvaluator(const DeferredProgramTemplate &program,
unsigned laneCount);
mlir::FailureOr<StaticIntSequence> evaluate(mlir::Value value);
mlir::FailureOr<StaticIntSequence> evaluate(mlir::OpFoldResult value);
mlir::FailureOr<StaticIntSequence> resolveSourceOperandIndices(
mlir::Value sourceRoot);
private:
const DeferredProgramTemplate &program;
unsigned laneCount;
llvm::DenseMap<mlir::Value, StaticIntSequence> values;
llvm::DenseMap<mlir::Value, StaticIntSequence> sourceOperands;
};
mlir::FailureOr<llvm::SmallVector<unsigned>>
getPossibleDeferredSourceOperandIndices(
mlir::Value sourceRoot, SpatDeferredCommunicationOp deferred);
struct GraphBatchPublicationMap {
mlir::RankedTensorType physicalResultType;
@@ -107,11 +74,13 @@ template <> struct DenseMapInfo<onnx_mlir::spatial::GraphBatchPublicationKey> {
static onnx_mlir::spatial::GraphBatchPublicationKey getTombstoneKey() {
return {DenseMapInfo<mlir::Operation *>::getTombstoneKey(), 0};
}
static unsigned getHashValue(const onnx_mlir::spatial::GraphBatchPublicationKey &key) {
static unsigned getHashValue(
const onnx_mlir::spatial::GraphBatchPublicationKey &key) {
return hash_combine(key.graphBatch, key.resultIndex);
}
static bool isEqual(const onnx_mlir::spatial::GraphBatchPublicationKey &lhs,
const onnx_mlir::spatial::GraphBatchPublicationKey &rhs) {
static bool isEqual(
const onnx_mlir::spatial::GraphBatchPublicationKey &lhs,
const onnx_mlir::spatial::GraphBatchPublicationKey &rhs) {
return lhs == rhs;
}
};
@@ -0,0 +1,306 @@
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/IR/IRMapping.h"
#include "DeferredResultRealization.hpp"
#include "DeferredBoundaryRealization.hpp"
#include "DeferredProjectionAnalysis.hpp"
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
#include "src/Accelerators/PIM/Common/IR/StaticIntSequence.hpp"
#include "src/Accelerators/PIM/Common/IR/TensorSliceUtils.hpp"
namespace onnx_mlir::spatial {
using namespace mlir;
namespace {
static Value getSequencePosition(
const LaneSet& familyLanes, Value lane, Operation* anchor, DeferredEmissionContext& context, Location loc) {
unsigned begin = familyLanes.intervals().front().begin;
if (!lane)
return context.constants.getIndex(0);
return affineAddConst(context.rewriter, loc, lane, -static_cast<int64_t>(begin), anchor);
}
static OpFoldResult materializeGeometryValue(
const StaticIntSequence& sequence, Value lane, Operation* anchor, DeferredEmissionContext& context, Location loc) {
if (sequence.getKind() == StaticIntSequenceKind::Uniform)
return context.rewriter.getIndexAttr(sequence.valueAt(0));
return emitStaticIntLookup(sequence, lane, anchor, context.constants, context.rewriter, loc);
}
static MixedSliceGeometry materializeGeometry(const DeferredResultPlan::SliceGeometry& geometry,
Value lane,
Operation* anchor,
DeferredEmissionContext& context,
Location loc) {
MixedSliceGeometry result;
for (const StaticIntSequence& value : geometry.offsets)
result.offsets.push_back(materializeGeometryValue(value, lane, anchor, context, loc));
for (const StaticIntSequence& value : geometry.sizes)
result.sizes.push_back(materializeGeometryValue(value, lane, anchor, context, loc));
for (const StaticIntSequence& value : geometry.strides)
result.strides.push_back(materializeGeometryValue(value, lane, anchor, context, loc));
return result;
}
static RequirementFamily*
findRequirement(const DeferredResultPlan& plan, RequirementCoordinate coordinate, unsigned representativeLane) {
RequirementFamily* match = nullptr;
for (RequirementFamily* requirement : plan.requirements)
if (requirement->coordinate == coordinate && requirement->targetLanes.contains(representativeLane)) {
assert(!match && "requirement coordinate is not unique");
match = requirement;
}
return match;
}
static bool isIdentityGeometry(const DeferredResultPlan::SliceGeometry& geometry, RankedTensorType type) {
if (geometry.offsets.size() != static_cast<size_t>(type.getRank()))
return false;
for (auto [dimension, values] :
llvm::enumerate(llvm::zip_equal(geometry.offsets, geometry.sizes, geometry.strides))) {
const StaticIntSequence& offset = std::get<0>(values);
const StaticIntSequence& size = std::get<1>(values);
const StaticIntSequence& stride = std::get<2>(values);
if (offset.getKind() != StaticIntSequenceKind::Uniform || size.getKind() != StaticIntSequenceKind::Uniform
|| stride.getKind() != StaticIntSequenceKind::Uniform || offset.valueAt(0) != 0
|| size.valueAt(0) != type.getDimSize(dimension) || stride.valueAt(0) != 1)
return false;
}
return true;
}
static FailureOr<Value> applyInnerGeometry(Value fragment,
const DeferredProjectionLeafTemplate& leaf,
const DeferredResultPlan::SliceGeometry& geometry,
Value lane,
DeferredEmissionContext& context) {
if (leaf.form != DeferredLeafForm::GraphBatchProjection)
return fragment;
auto fragmentType = dyn_cast<RankedTensorType>(fragment.getType());
if (!fragmentType || geometry.offsets.size() != static_cast<size_t>(fragmentType.getRank()))
return failure();
if (isIdentityGeometry(geometry, fragmentType))
return fragment;
SmallVector<int64_t> shape(leaf.reconstructedType.getShape().drop_front());
auto resultType = RankedTensorType::get(shape, fragmentType.getElementType());
Value sourceRoot = leaf.sourceRoot;
MixedSliceGeometry mixed =
materializeGeometry(geometry, lane, sourceRoot.getParentBlock()->getParentOp(), context, sourceRoot.getLoc());
return extractMixedSliceOrIdentity(context.rewriter, leaf.sourceRoot.getLoc(), fragment, resultType, mixed);
}
static FailureOr<Value> reconstructLeaf(const DeferredResultPlan& plan,
unsigned leafIndex,
const LaneSet& activeLanes,
Value lane,
DeferredEmissionContext& context) {
DeferredExchangePlan& exchange = *plan.exchange;
const DeferredProjectionLeafTemplate& leaf = exchange.program.leaves[leafIndex];
unsigned representative = activeLanes.intervals().front().begin;
if (Value assembled = context.projectionAssemblies.lookup({&exchange, leafIndex})) {
for (RequirementFamily* requirement : plan.requirements) {
if (requirement->coordinate.leafIndex != leafIndex || !requirement->targetLanes.contains(representative))
continue;
Value local = context.receives.lookup(requirement);
if (!local)
continue;
auto shaped = applyInnerGeometry(local, leaf, plan.innerGeometry[leafIndex], lane, context);
auto source = succeeded(shaped)
? addLeadingUnitTensorDimension(context.rewriter, exchange.deferred.getLoc(), *shaped)
: FailureOr<Value>(failure());
if (failed(source))
return failure();
auto sourceType = cast<RankedTensorType>(source->getType());
MixedSliceGeometry geometry;
geometry.offsets.assign(sourceType.getRank(), context.rewriter.getIndexAttr(0));
geometry.offsets.front() = context.rewriter.getIndexAttr(requirement->coordinate.selectedPosition);
for (int64_t dimension : sourceType.getShape())
geometry.sizes.push_back(context.rewriter.getIndexAttr(dimension));
geometry.strides.assign(sourceType.getRank(), context.rewriter.getIndexAttr(1));
assembled = insertMixedSlice(context.rewriter, exchange.deferred.getLoc(), *source, assembled, geometry);
}
return assembled;
}
unsigned positionCount = 0;
for (RequirementFamily* requirement : plan.requirements)
if (requirement->coordinate.leafIndex == leafIndex && requirement->targetLanes.contains(representative))
positionCount = std::max(positionCount, requirement->coordinate.selectedPosition + 1);
if (positionCount == 0)
return failure();
SmallVector<Value> expanded;
for (unsigned position = 0; position < positionCount; ++position) {
RequirementFamily* requirement = findRequirement(plan, {leafIndex, position}, representative);
if (!requirement)
return failure();
auto fragment = materializeDeferredRequirement(*requirement, activeLanes, lane, context);
if (failed(fragment))
return failure();
auto shaped = applyInnerGeometry(*fragment, leaf, plan.innerGeometry[leafIndex], lane, context);
if (failed(shaped))
return failure();
if (positionCount == 1 && shaped->getType() == leaf.reconstructedType)
return *shaped;
auto value = addLeadingUnitTensorDimension(context.rewriter, exchange.deferred.getLoc(), *shaped);
if (failed(value))
return failure();
expanded.push_back(*value);
}
if (expanded.size() == 1 && expanded.front().getType() == leaf.reconstructedType)
return expanded.front();
constexpr size_t maxConcatInputs = 64;
while (expanded.size() > 1) {
SmallVector<Value> next;
next.reserve((expanded.size() + maxConcatInputs - 1) / maxConcatInputs);
for (size_t index = 0; index < expanded.size(); index += maxConcatInputs) {
ValueRange inputs = ValueRange(expanded).slice(index, std::min(maxConcatInputs, expanded.size() - index));
if (inputs.size() == 1) {
next.push_back(inputs.front());
continue;
}
auto first = cast<RankedTensorType>(inputs.front().getType());
SmallVector<int64_t> shape(first.getShape());
shape.front() = 0;
for (Value input : inputs)
shape.front() += cast<RankedTensorType>(input.getType()).getDimSize(0);
auto resultType = RankedTensorType::get(shape, first.getElementType());
next.push_back(
tensor::ConcatOp::create(context.rewriter, exchange.deferred.getLoc(), resultType, 0, inputs).getResult());
}
expanded = std::move(next);
}
return expanded.front().getType() == leaf.reconstructedType ? FailureOr<Value>(expanded.front())
: FailureOr<Value>(failure());
}
} // namespace
FailureOr<DeferredResultPlan>
buildDeferredResultPlan(DeferredExchangePlan& exchange) {
DeferredResultPlan result;
result.exchange = &exchange;
for (RequirementFamily& requirement : exchange.requirements)
result.requirements.push_back(&requirement);
DeferredLaneValueEvaluator evaluator(
exchange.program, exchange.targetLaneCount);
auto buildGeometry = [&](const DeferredSliceTemplate& source,
DeferredResultPlan::SliceGeometry& target) {
auto append = [&](ArrayRef<OpFoldResult> values,
SmallVectorImpl<StaticIntSequence>& sequences) {
for (OpFoldResult value : values) {
auto sequence = evaluator.evaluate(value);
if (failed(sequence))
return failure();
sequences.push_back(*sequence);
}
return success();
};
return success(succeeded(append(source.offsets, target.offsets))
&& succeeded(append(source.sizes, target.sizes))
&& succeeded(append(source.strides, target.strides)));
};
for (const DeferredProjectionLeafTemplate& leaf : exchange.program.leaves) {
DeferredResultPlan::SliceGeometry geometry;
if (failed(buildGeometry(leaf.innerGeometry, geometry)))
return failure();
result.innerGeometry.push_back(std::move(geometry));
}
if (exchange.program.insertAssembly)
for (const DeferredInsertAssemblyEntryTemplate& entry :
exchange.program.insertAssembly->entries) {
DeferredResultPlan::SliceGeometry geometry;
if (failed(buildGeometry(entry.targetGeometry, geometry)))
return failure();
result.assemblyGeometry.push_back(std::move(geometry));
}
for (Operation* op : exchange.program.residualOps)
op->walk([&](Operation* nested) {
for (Value operand : nested->getOperands()) {
if ((!operand.getType().isIndex()
&& !isa<IntegerType>(operand.getType()))
|| result.residualValues.count(operand))
continue;
if (auto sequence = evaluator.evaluate(operand); succeeded(sequence))
result.residualValues.try_emplace(operand, *sequence);
}
});
return result;
}
FailureOr<Value> materializeDeferredRequirement(RequirementFamily& requirement,
const LaneSet& activeLanes,
Value lane,
DeferredEmissionContext& context) {
if (Value received = context.receives.lookup(&requirement))
return received;
ProducedValue& producer = *requirement.producer;
Value payload = producer.payload;
if (payload.getType() == requirement.publicationFragmentType)
return payload;
auto payloadType = dyn_cast<RankedTensorType>(payload.getType());
auto fragmentType = dyn_cast<RankedTensorType>(requirement.publicationFragmentType);
if (!payloadType || !fragmentType || !requirement.producerLocalOffsets
|| payloadType.getRank() != fragmentType.getRank() + 1)
return failure();
Location loc = requirement.exchange->deferred.getLoc();
Value position = getSequencePosition(requirement.targetLanes, lane, requirement.exchange->deferred, context, loc);
Value offset = emitStaticIntLookup(*requirement.producerLocalOffsets,
position,
requirement.exchange->deferred,
context.constants,
context.rewriter,
loc);
MixedSliceGeometry geometry;
geometry.offsets.assign(payloadType.getRank(), context.rewriter.getIndexAttr(0));
geometry.sizes.push_back(context.rewriter.getIndexAttr(1));
geometry.strides.assign(payloadType.getRank(), context.rewriter.getIndexAttr(1));
geometry.offsets.front() = offset;
for (int64_t dimension : fragmentType.getShape())
geometry.sizes.push_back(context.rewriter.getIndexAttr(dimension));
SmallVector<int64_t> unitShape {1};
llvm::append_range(unitShape, fragmentType.getShape());
auto unitType = RankedTensorType::get(unitShape, fragmentType.getElementType());
Value unit = extractMixedSliceOrIdentity(context.rewriter, loc, payload, unitType, geometry);
return removeLeadingUnitTensorDimension(context.rewriter, loc, unit, fragmentType);
}
FailureOr<Value> realizeDeferredResult(const DeferredResultPlan& plan,
const LaneSet& activeLanes,
Value lane,
DeferredEmissionContext& context) {
DeferredExchangePlan& exchange = *plan.exchange;
if (Value assembled = context.assemblies.lookup(&exchange))
return assembled;
IRMapping mapping;
if (exchange.program.scheduledLane)
mapping.map(exchange.program.scheduledLane, lane);
for (auto [value, sequence] : plan.residualValues) {
if (mapping.contains(value))
continue;
Value selected = emitStaticIntLookup(sequence,
lane ? lane : context.constants.getIndex(0),
exchange.deferred,
context.constants,
context.rewriter,
exchange.deferred.getLoc());
if (!value.getType().isIndex())
selected = arith::IndexCastOp::create(context.rewriter, exchange.deferred.getLoc(), value.getType(), selected);
mapping.map(value, selected);
}
for (unsigned leaf = 0; leaf < exchange.program.leaves.size(); ++leaf) {
auto value = reconstructLeaf(plan, leaf, activeLanes, lane, context);
if (failed(value))
return failure();
mapping.map(exchange.program.leaves[leaf].replacementRoot, *value);
}
for (Operation* op : exchange.program.residualOps) {
Operation* copy = context.rewriter.clone(*op, mapping);
for (auto [oldValue, newValue] : llvm::zip(op->getResults(), copy->getResults()))
mapping.map(oldValue, newValue);
}
Value result = mapping.lookupOrDefault(exchange.program.yieldedValue);
return result && result.getType() == exchange.deferred.getOutput().getType() ? FailureOr<Value>(result)
: FailureOr<Value>(failure());
}
} // namespace onnx_mlir::spatial
@@ -0,0 +1,35 @@
#pragma once
#include "DeferredCommunicationModel.hpp"
namespace onnx_mlir::spatial {
struct DeferredEmissionContext;
struct DeferredResultPlan {
DeferredExchangePlan* exchange = nullptr;
llvm::SmallVector<RequirementFamily*> requirements;
struct SliceGeometry {
llvm::SmallVector<StaticIntSequence> offsets;
llvm::SmallVector<StaticIntSequence> sizes;
llvm::SmallVector<StaticIntSequence> strides;
};
llvm::SmallVector<SliceGeometry, 0> innerGeometry;
llvm::SmallVector<SliceGeometry, 0> assemblyGeometry;
llvm::DenseMap<mlir::Value, StaticIntSequence> residualValues;
};
mlir::FailureOr<DeferredResultPlan>
buildDeferredResultPlan(DeferredExchangePlan& exchange);
mlir::FailureOr<mlir::Value> realizeDeferredResult(const DeferredResultPlan& plan,
const LaneSet& activeLanes,
mlir::Value lane,
DeferredEmissionContext& context);
mlir::FailureOr<mlir::Value> materializeDeferredRequirement(RequirementFamily& requirement,
const LaneSet& activeLanes,
mlir::Value lane,
DeferredEmissionContext& context);
} // namespace onnx_mlir::spatial
@@ -0,0 +1,600 @@
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "llvm/ADT/MapVector.h"
#include "DeferredProjectionAnalysis.hpp"
#include "DeferredTransferPlanning.hpp"
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
namespace onnx_mlir::spatial {
using namespace mlir;
namespace {
static FailureOr<SmallVector<int64_t>> getI64Array(Operation* op, StringRef name) {
auto attr = op->getAttrOfType<DenseI64ArrayAttr>(name);
if (!attr)
return op->emitOpError() << "phase 2 requires '" << name << "' metadata";
return SmallVector<int64_t>(attr.asArrayRef());
}
static FailureOr<SmallVector<int64_t>> getLaneTable(Operation* op, StringRef name, size_t expected) {
if (auto array = op->getAttrOfType<DenseI64ArrayAttr>(name)) {
if (array.size() != static_cast<int64_t>(expected))
return op->emitOpError() << "phase 2 metadata '" << name << "' has the wrong size";
return SmallVector<int64_t>(array.asArrayRef());
}
auto elements = op->getAttrOfType<DenseIntElementsAttr>(name);
if (!elements || elements.getNumElements() != static_cast<int64_t>(expected))
return op->emitOpError() << "phase 2 requires a correctly-sized '" << name << "' lane table";
SmallVector<int64_t> values;
for (APInt value : elements.getValues<APInt>())
values.push_back(value.getSExtValue());
return values;
}
static Block* getScheduledBlock(SpatDeferredCommunicationOp deferred, Operation* scheduled) {
Block* block = deferred->getBlock();
while (block && block->getParentOp() != scheduled) {
Operation* parent = block->getParentOp();
block = parent ? parent->getBlock() : nullptr;
}
return block;
}
static FailureOr<unsigned> getStepIndex(ScheduledInfo& info, Block* block) {
auto it = llvm::find(info.blocks, block);
return it == info.blocks.end() ? FailureOr<unsigned>(failure())
: FailureOr<unsigned>(std::distance(info.blocks.begin(), it));
}
static FailureOr<Value>
getScalarStepResult(SpatScheduledCompute scheduled, Block& block, unsigned resultIndex, unsigned resultCount) {
auto yield = dyn_cast<SpatBlockYieldOp>(block.getTerminator());
if (!yield || resultIndex >= resultCount || yield.getOutputs().size() < resultCount)
return scheduled.emitOpError("phase 2 cannot recover a scalar scheduled step result"), failure();
return yield.getOutputs()[yield.getOutputs().size() - resultCount + resultIndex];
}
struct BatchPublication {
Value payload;
tensor::ParallelInsertSliceOp insertion;
};
struct ProducedValueGeometry {
int64_t laneStart = 0;
int64_t laneCount = 1;
int64_t publishedSlotStart = 0;
int64_t publishedSlotCount = 1;
Value payload;
Value published;
};
static FailureOr<BatchPublication>
getBatchStepResult(SpatScheduledComputeBatch scheduled, Block& block, unsigned globalResultIndex) {
auto parallel = dyn_cast<SpatInParallelOp>(block.getTerminator());
if (!parallel)
return scheduled.emitOpError("phase 2 cannot recover a batched scheduled step result"), failure();
unsigned resultBase = 1 + scheduled.getWeights().size() + scheduled.getInputs().size();
for (Operation& op : parallel.getRegion().front()) {
auto insert = dyn_cast<tensor::ParallelInsertSliceOp>(op);
auto destination = insert ? dyn_cast<BlockArgument>(insert.getDest()) : BlockArgument();
if (destination && destination.getOwner() == &block && destination.getArgNumber() == resultBase + globalResultIndex)
return BatchPublication {insert.getSource(), insert};
}
return scheduled.emitOpError("phase 2 cannot find the batched scheduled result insertion"), failure();
}
static FailureOr<ProducedValueGeometry> verifyScheduledPublicationGeometry(
ScheduledInfo& info, unsigned step, unsigned globalResult, unsigned lane,
int64_t laneStart, int64_t laneCount, Value payload,
tensor::ParallelInsertSliceOp insertion = {}) {
ProducedValueGeometry result;
result.laneStart = laneStart;
result.laneCount = info.isBatch() ? laneCount : std::max<int64_t>(laneCount, 1);
result.payload = payload;
result.published = info.op->getResult(globalResult);
auto payloadType = dyn_cast<RankedTensorType>(payload.getType());
auto publishedType = dyn_cast<RankedTensorType>(result.published.getType());
if (laneStart < 0 || result.laneCount <= 0)
return info.op->emitOpError("phase 2 scheduled publication has an invalid source-lane range"), failure();
if (!info.isBatch()) {
result.publishedSlotCount = result.laneCount;
if (payload.getType() != result.published.getType()
|| (result.laneCount > 1
&& (!publishedType || !publishedType.hasStaticShape()
|| publishedType.getRank() == 0
|| publishedType.getDimSize(0) != result.laneCount)))
return info.op->emitOpError("phase 2 scalar scheduled publication is incompatible with its result"), failure();
return result;
}
if (!insertion || !payloadType || !payloadType.hasStaticShape()
|| !publishedType || !publishedType.hasStaticShape()
|| insertion.getDest() != info.blocks[step]->getArgument(
1 + info.op->getNumOperands() + globalResult))
return info.op->emitOpError("phase 2 batched publication is malformed"), failure();
unsigned rank = publishedType.getRank();
if (rank == 0 || payloadType.getRank() != rank
|| insertion.getMixedOffsets().size() != rank
|| insertion.getMixedSizes().size() != rank
|| insertion.getMixedStrides().size() != rank)
return info.op->emitOpError("phase 2 batched publication geometry rank is invalid"), failure();
StaticIndexEnvironment environment;
environment.bindings[info.blocks[step]->getArgument(0)] = lane;
SmallVector<int64_t> offsets, sizes, strides;
for (OpFoldResult value : insertion.getMixedOffsets()) {
auto evaluated = evaluateDeferredIndex(value, environment);
if (failed(evaluated))
return info.op->emitOpError("phase 2 batched publication offset is not static"), failure();
offsets.push_back(*evaluated);
}
for (OpFoldResult value : insertion.getMixedSizes()) {
auto evaluated = evaluateDeferredIndex(value, environment);
if (failed(evaluated))
return info.op->emitOpError("phase 2 batched publication size is not static"), failure();
sizes.push_back(*evaluated);
}
for (OpFoldResult value : insertion.getMixedStrides()) {
auto evaluated = evaluateDeferredIndex(value, environment);
if (failed(evaluated))
return info.op->emitOpError("phase 2 batched publication stride is not static"), failure();
strides.push_back(*evaluated);
}
if (offsets.front() < 0 || sizes.front() <= 0 || sizes.front() != result.laneCount
|| strides.front() != 1
|| offsets.front() + sizes.front() > publishedType.getDimSize(0))
return info.op->emitOpError("phase 2 batched publication leading geometry is invalid"), failure();
for (unsigned dimension = 1; dimension < rank; ++dimension)
if (offsets[dimension] != 0 || strides[dimension] != 1)
return info.op->emitOpError("phase 2 batched publication inner geometry is invalid"), failure();
for (unsigned dimension = 0; dimension < rank; ++dimension)
if (sizes[dimension] != payloadType.getDimSize(dimension))
return info.op->emitOpError("phase 2 batched publication payload shape is incompatible"), failure();
result.publishedSlotStart = offsets.front();
result.publishedSlotCount = sizes.front();
return result;
}
static LogicalResult collectScheduledOperations(func::FuncOp funcOp, DeferredTransferPlan& plan) {
unsigned nextStream = 0;
for (Operation& op : funcOp.getOps()) {
if (!isa<SpatScheduledCompute, SpatScheduledComputeBatch>(op))
continue;
ScheduledInfo info;
info.op = &op;
Region& body = isa<SpatScheduledCompute>(op) ? cast<SpatScheduledCompute>(op).getBody()
: cast<SpatScheduledComputeBatch>(op).getBody();
for (Block& block : body) {
info.blocks.push_back(&block);
info.stepAnchors.push_back(&block.front());
}
auto sourceIds = getI64Array(&op, "scheduled.step_source_ids");
auto offsets = getI64Array(&op, "scheduled.step_result_offsets");
auto counts = getI64Array(&op, "scheduled.step_result_counts");
if (failed(sourceIds) || failed(offsets) || failed(counts))
return failure();
info.stepSourceIds = std::move(*sourceIds);
info.resultOffsets = std::move(*offsets);
info.resultCounts = std::move(*counts);
if (info.blocks.size() != info.stepSourceIds.size() || info.blocks.size() != info.resultOffsets.size()
|| info.blocks.size() != info.resultCounts.size())
return op.emitOpError("phase 2 scheduled metadata does not match its block count");
if (auto scalar = dyn_cast<SpatScheduledCompute>(op)) {
auto core = scalar->getAttrOfType<IntegerAttr>(kCoreIdAttrName);
if (!core)
return scalar.emitOpError("phase 2 requires scalar coreId metadata");
info.cores.push_back(core.getInt());
}
else {
auto cores = op.getAttrOfType<DenseI32ArrayAttr>(kCoreIdsAttrName);
if (!cores)
return op.emitOpError("phase 2 requires batch coreIds metadata");
for (int32_t core : cores.asArrayRef())
info.cores.push_back(core);
}
for (size_t lane = 0; lane < info.cores.size(); ++lane)
info.streamIds.push_back(nextStream++);
plan.scheduled.push_back(std::move(info));
}
plan.stepCounts.resize(nextStream);
for (ScheduledInfo& info : plan.scheduled)
for (unsigned stream : info.streamIds)
plan.stepCounts[stream] = info.blocks.size();
return success();
}
static LogicalResult collectProducedValues(DeferredTransferPlan& plan) {
for (ScheduledInfo& info : plan.scheduled) {
SmallVector<int64_t> laneStarts, laneCounts;
size_t tableSize = info.blocks.size() * info.cores.size();
auto starts = info.isBatch() ? getLaneTable(info.op, "scheduled.source_lane_starts", tableSize)
: getI64Array(info.op, "scheduled.source_lane_starts");
auto counts = info.isBatch() ? getLaneTable(info.op, "scheduled.source_lane_counts", tableSize)
: getI64Array(info.op, "scheduled.source_lane_counts");
if (failed(starts) || failed(counts))
return failure();
laneStarts = std::move(*starts);
laneCounts = std::move(*counts);
for (unsigned step = 0; step < info.blocks.size(); ++step) {
if (info.resultOffsets[step] < 0 || info.resultCounts[step] < 0)
return info.op->emitOpError("phase 2 scheduled result metadata must be non-negative");
for (unsigned result = 0; result < static_cast<unsigned>(info.resultCounts[step]); ++result) {
unsigned globalResult = info.resultOffsets[step] + result;
if (!info.isBatch()) {
auto payload = getScalarStepResult(
cast<SpatScheduledCompute>(info.op), *info.blocks[step], result, info.resultCounts[step]);
if (failed(payload))
return failure();
auto geometry = verifyScheduledPublicationGeometry(
info, step, globalResult, 0, laneStarts[step], laneCounts[step], *payload);
if (failed(geometry))
return failure();
auto produced = std::make_unique<ProducedValue>(ProducedValue {
&info, step, result, info.stepSourceIds[step], info.cores.front(),
geometry->laneStart, geometry->laneCount, 0,
geometry->publishedSlotStart, geometry->publishedSlotCount,
geometry->payload, geometry->published});
info.produced.push_back(produced.get());
plan.producedByGraph[produced->graphId].push_back(produced.get());
plan.producedStorage.push_back(std::move(produced));
continue;
}
auto publication =
getBatchStepResult(cast<SpatScheduledComputeBatch>(info.op), *info.blocks[step], globalResult);
if (failed(publication))
return failure();
for (unsigned lane = 0; lane < info.cores.size(); ++lane) {
size_t laneIndex = step * info.cores.size() + lane;
auto geometry = verifyScheduledPublicationGeometry(
info, step, globalResult, lane, laneStarts[laneIndex],
laneCounts[laneIndex], publication->payload, publication->insertion);
if (failed(geometry))
return failure();
auto produced = std::make_unique<ProducedValue>(ProducedValue {
&info, step, result, info.stepSourceIds[step], info.cores[lane],
geometry->laneStart, geometry->laneCount, lane,
geometry->publishedSlotStart, geometry->publishedSlotCount,
geometry->payload, geometry->published});
info.produced.push_back(produced.get());
plan.producedByGraph[produced->graphId].push_back(produced.get());
plan.producedStorage.push_back(std::move(produced));
}
}
}
}
return success();
}
static FailureOr<ProducedValue*> findProducer(DeferredTransferPlan& plan,
Operation* diagnosticOwner,
int64_t graphId,
unsigned resultIndex,
std::optional<int64_t> graphLane) {
ProducedValue* match = nullptr;
for (ProducedValue* produced : plan.producedByGraph.lookup(graphId)) {
if (produced->resultIndex != resultIndex
|| (graphLane && (*graphLane < produced->laneStart || *graphLane >= produced->laneStart + produced->laneCount)))
continue;
if (match)
return diagnosticOwner->emitOpError("phase 2 cannot uniquely resolve graph publication ownership"), failure();
match = produced;
}
if (!match)
return diagnosticOwner->emitOpError("phase 2 cannot map a graph lane to a scheduled producer"), failure();
if (graphLane) {
int64_t relative = *graphLane - match->laneStart;
if (relative < 0 || relative >= match->laneCount
|| relative >= match->publishedSlotCount)
return diagnosticOwner->emitOpError(
"phase 2 graph lane is outside its scheduled publication window"), failure();
}
return match;
}
struct RequirementPoint {
ProducedValue* producer = nullptr;
Type fragmentType;
std::optional<int64_t> graphLane;
std::optional<int64_t> localOffset;
bool sameFamily(const RequirementPoint& other) const {
return producer == other.producer && fragmentType == other.fragmentType;
}
};
static FailureOr<std::optional<RequirementPoint>>
resolveRequirementPoint(DeferredTransferPlan& plan,
DeferredExchangePlan& exchange,
const DeferredProjectionLeafTemplate& leaf,
DeferredLaneValueEvaluator& evaluator,
unsigned lane,
unsigned position,
GraphBatchPublicationCache& publicationCache) {
auto sourceIndices = evaluator.resolveSourceOperandIndices(leaf.sourceRoot);
if (failed(sourceIndices))
return failure();
unsigned sourceIndex = sourceIndices->valueAt(lane);
auto source = dyn_cast<OpResult>(exchange.deferred.getSources()[sourceIndex]);
if (!source)
return exchange.deferred.emitOpError("phase 2 requires graph-result deferred sources"), failure();
auto graphId = source.getOwner()->getAttrOfType<IntegerAttr>("scheduled.graph_id");
if (!graphId)
return exchange.deferred.emitOpError("phase 2 cannot identify graph producer"), failure();
RequirementPoint point;
if (auto batch = dyn_cast<SpatGraphComputeBatch>(source.getOwner())) {
auto publication = getGraphBatchPublicationMap(batch, source.getResultNumber(), publicationCache);
if (failed(publication))
return failure();
int64_t physicalSlot = position;
if (leaf.form == DeferredLeafForm::GraphBatchProjection) {
auto offset = evaluator.evaluate(leaf.leadingGeometry.offsets.front());
auto size = evaluator.evaluate(leaf.leadingGeometry.sizes.front());
auto stride = evaluator.evaluate(leaf.leadingGeometry.strides.front());
if (failed(offset) || failed(size) || failed(stride))
return failure();
if (position >= static_cast<unsigned>(size->valueAt(lane)))
return std::optional<RequirementPoint>();
physicalSlot = offset->valueAt(lane) + static_cast<int64_t>(position) * stride->valueAt(lane);
}
else if (position >= (*publication)->physicalSlotToGraphLane.size()) {
return std::optional<RequirementPoint>();
}
if (physicalSlot < 0 || physicalSlot >= static_cast<int64_t>((*publication)->physicalSlotToGraphLane.size()))
return exchange.deferred.emitOpError("projection physical slot is outside publication map"), failure();
point.graphLane = (*publication)->physicalSlotToGraphLane[physicalSlot];
point.fragmentType = (*publication)->publicationFragmentType;
auto producer = findProducer(plan, exchange.deferred, graphId.getInt(), source.getResultNumber(), point.graphLane);
if (failed(producer))
return failure();
point.producer = *producer;
point.localOffset = *point.graphLane - point.producer->laneStart;
}
else {
if (position != 0 || !isa<SpatGraphCompute>(source.getOwner()))
return std::optional<RequirementPoint>();
point.fragmentType = source.getType();
auto producer = findProducer(plan, exchange.deferred, graphId.getInt(), source.getResultNumber(), std::nullopt);
if (failed(producer))
return failure();
point.producer = *producer;
}
return std::optional<RequirementPoint>(point);
}
static void appendRequirementFamily(DeferredExchangePlan& exchange,
RequirementCoordinate coordinate,
unsigned begin,
ArrayRef<RequirementPoint> points) {
RequirementFamily family;
family.exchange = &exchange;
family.coordinate = coordinate;
family.targetLanes = LaneSet::range(begin, begin + points.size());
family.producer = points.front().producer;
family.publicationFragmentType = points.front().fragmentType;
auto sequence = [&](auto member) -> std::optional<StaticIntSequence> {
if (!(points.front().*member))
return std::nullopt;
SmallVector<int64_t> values;
for (const RequirementPoint& point : points)
values.push_back(*(point.*member));
return StaticIntSequence::fromValues(values);
};
family.graphLanes = sequence(&RequirementPoint::graphLane);
family.producerLocalOffsets = sequence(&RequirementPoint::localOffset);
exchange.requirements.push_back(std::move(family));
}
static LogicalResult buildRequirementFamilies(DeferredTransferPlan& plan,
DeferredExchangePlan& exchange,
GraphBatchPublicationCache& publicationCache) {
DeferredLaneValueEvaluator evaluator(exchange.program, exchange.targetLaneCount);
for (auto leafItem : llvm::enumerate(exchange.program.leaves)) {
unsigned leafIndex = leafItem.index();
const DeferredProjectionLeafTemplate& leaf = leafItem.value();
unsigned positionCount = 1;
if (leaf.form == DeferredLeafForm::GraphBatchProjection) {
auto sizes = evaluator.evaluate(leaf.leadingGeometry.sizes.front());
if (failed(sizes))
return failure();
for (unsigned lane = 0; lane < exchange.targetLaneCount; ++lane)
positionCount = std::max<unsigned>(positionCount, sizes->valueAt(lane));
}
else {
auto sources = evaluator.resolveSourceOperandIndices(leaf.sourceRoot);
if (failed(sources))
return failure();
for (unsigned lane = 0; lane < exchange.targetLaneCount; ++lane) {
auto source = dyn_cast<OpResult>(exchange.deferred.getSources()[sources->valueAt(lane)]);
auto type = source ? dyn_cast<RankedTensorType>(source.getType()) : RankedTensorType();
if (source && isa<SpatGraphComputeBatch>(source.getOwner()) && type)
positionCount = std::max<unsigned>(positionCount, type.getDimSize(0));
}
}
for (unsigned position = 0; position < positionCount; ++position) {
unsigned runBegin = 0;
SmallVector<RequirementPoint> run;
auto flush = [&] {
if (!run.empty())
appendRequirementFamily(exchange, {static_cast<unsigned>(leafIndex), position}, runBegin, run);
run.clear();
};
for (unsigned lane = 0; lane < exchange.targetLaneCount; ++lane) {
auto point = resolveRequirementPoint(plan, exchange, leaf, evaluator, lane, position, publicationCache);
if (failed(point))
return failure();
if (!*point) {
flush();
continue;
}
if (!run.empty() && !run.front().sameFamily(**point))
flush();
if (run.empty())
runBegin = lane;
run.push_back(**point);
}
flush();
}
}
return success();
}
static void buildAvailabilityFamilies(DeferredExchangePlan& exchange, uint64_t& nextChannel) {
for (RequirementFamily& requirement : exchange.requirements) {
for (LaneInterval interval : requirement.targetLanes.intervals()) {
unsigned runBegin = interval.begin;
bool runLocal = false;
bool haveRun = false;
auto flush = [&](unsigned end) {
if (!haveRun || runBegin == end)
return;
LaneSet lanes = LaneSet::range(runBegin, end);
if (runLocal) {
exchange.local.push_back({&requirement, lanes});
}
else {
size_t count = end - runBegin;
unsigned sourceStream = requirement.producer->scheduled->streamIds[requirement.producer->scheduledLane];
SmallVector<int64_t> targetStreams, targetCores;
for (unsigned lane = runBegin; lane < end; ++lane) {
targetStreams.push_back(exchange.target->streamIds[lane]);
targetCores.push_back(exchange.target->cores[lane]);
}
ExternalTransferFamily family;
family.requirement = &requirement;
family.targetLanes = lanes;
family.sourceScheduled = requirement.producer->scheduled;
family.targetScheduled = exchange.target;
family.sourceStreams = StaticIntSequence::uniform(sourceStream, count);
family.targetStreams = StaticIntSequence::fromValues(targetStreams);
family.sourceCores = StaticIntSequence::uniform(requirement.producer->core, count);
family.targetCores = StaticIntSequence::fromValues(targetCores);
family.channelIds = StaticIntSequence::affine(nextChannel, 1, count);
nextChannel += count;
exchange.externalTransferCount += count;
exchange.external.push_back(std::move(family));
}
};
for (unsigned lane = interval.begin; lane < interval.end; ++lane) {
unsigned sourceStream = requirement.producer->scheduled->streamIds[requirement.producer->scheduledLane];
bool local =
sourceStream == exchange.target->streamIds[lane] && requirement.producer->step < exchange.consumerStep;
if (haveRun && local != runLocal) {
flush(lane);
runBegin = lane;
}
runLocal = local;
haveRun = true;
}
flush(interval.end);
}
}
}
static LogicalResult buildExchanges(func::FuncOp funcOp, DeferredTransferPlan& plan) {
DenseMap<Operation*, ScheduledInfo*> scheduledByOp;
for (ScheduledInfo& scheduled : plan.scheduled)
scheduledByOp[scheduled.op] = &scheduled;
SmallVector<SpatDeferredCommunicationOp> deferredOps;
funcOp.walk([&](SpatDeferredCommunicationOp op) { deferredOps.push_back(op); });
GraphBatchPublicationCache publicationCache;
uint64_t nextChannel = 0;
for (SpatDeferredCommunicationOp deferred : deferredOps) {
Operation* targetOp = deferred->getParentOfType<SpatScheduledCompute>();
if (!targetOp)
targetOp = deferred->getParentOfType<SpatScheduledComputeBatch>();
ScheduledInfo* target = scheduledByOp.lookup(targetOp);
auto step = target ? getStepIndex(*target, getScheduledBlock(deferred, targetOp)) : FailureOr<unsigned>(failure());
auto program = analyzeDeferredProgramTemplate(deferred);
if (!target || failed(step) || failed(program))
return deferred.emitOpError("phase 2 cannot normalize deferred communication");
auto exchange = std::make_unique<DeferredExchangePlan>();
exchange->exchangeId = plan.exchanges.size();
exchange->deferred = deferred;
exchange->target = target;
exchange->consumerStep = *step;
exchange->targetLaneCount = target->cores.size();
exchange->program = std::move(*program);
if (failed(buildRequirementFamilies(plan, *exchange, publicationCache)))
return failure();
buildAvailabilityFamilies(*exchange, nextChannel);
plan.exchanges.push_back(std::move(exchange));
}
return success();
}
static LogicalResult
retargetBlueprint(DeferredTransferPlan& plan, SpatBlueprintOp blueprint, GraphBatchPublicationCache& publicationCache) {
if (blueprint.getMode() != "fragment_assembly")
return success();
auto operandIndices = blueprint.getFragmentOperandIndices();
auto sourceSlots = blueprint.getFragmentSourceSlots();
if (!operandIndices || !sourceSlots)
return blueprint.emitOpError("phase 2 requires explicit Blueprint fragment ownership");
SmallVector<Value> oldOperands {blueprint.getInput()};
llvm::append_range(oldOperands, blueprint.getFragments());
SmallVector<Value> publications;
SmallVector<int64_t> newOperands, newSlots;
for (auto [fragmentIndex, operandIndex] : llvm::enumerate(*operandIndices)) {
Value source = oldOperands[operandIndex];
auto result = dyn_cast<OpResult>(source);
auto batch = result ? dyn_cast<SpatGraphComputeBatch>(result.getOwner()) : SpatGraphComputeBatch();
int64_t slot = (*sourceSlots)[fragmentIndex];
if (batch) {
auto graphId = batch->getAttrOfType<IntegerAttr>("scheduled.graph_id");
auto map = getGraphBatchPublicationMap(batch, result.getResultNumber(), publicationCache);
if (!graphId || failed(map) || slot < 0 || slot >= static_cast<int64_t>((*map)->physicalSlotToGraphLane.size()))
return blueprint.emitOpError("phase 2 cannot resolve Blueprint fragment ownership");
int64_t graphLane = (*map)->physicalSlotToGraphLane[slot];
auto producer = findProducer(plan, blueprint, graphId.getInt(), result.getResultNumber(), graphLane);
if (failed(producer))
return failure();
source = (*producer)->published;
slot = (*producer)->publishedSlotStart + graphLane - (*producer)->laneStart;
if (slot < (*producer)->publishedSlotStart
|| slot >= (*producer)->publishedSlotStart
+ (*producer)->publishedSlotCount)
return blueprint.emitOpError(
"phase 2 Blueprint slot is outside its scheduled publication window"), failure();
}
if (Operation* producer = source.getDefiningOp();
producer && blueprint->getBlock() == producer->getBlock() && blueprint->isBeforeInBlock(producer))
blueprint->moveAfter(producer);
auto it = llvm::find(publications, source);
if (it == publications.end()) {
publications.push_back(source);
it = std::prev(publications.end());
}
newOperands.push_back(std::distance(publications.begin(), it));
newSlots.push_back(slot);
}
blueprint->setOperands(publications);
OpBuilder builder(blueprint);
blueprint->setAttr("fragmentOperandIndices", builder.getDenseI64ArrayAttr(newOperands));
blueprint->setAttr("fragmentSourceSlots", builder.getDenseI64ArrayAttr(newSlots));
return success();
}
} // namespace
FailureOr<DeferredTransferPlan> buildDeferredTransferPlan(func::FuncOp funcOp) {
DeferredTransferPlan plan;
if (failed(collectScheduledOperations(funcOp, plan)) || failed(collectProducedValues(plan))
|| failed(buildExchanges(funcOp, plan)))
return failure();
return std::move(plan);
}
LogicalResult retargetDeferredPublications(func::FuncOp funcOp, DeferredTransferPlan& plan) {
GraphBatchPublicationCache publicationCache;
SmallVector<SpatBlueprintOp> blueprints;
funcOp.walk([&](SpatBlueprintOp blueprint) { blueprints.push_back(blueprint); });
for (SpatBlueprintOp blueprint : blueprints)
if (failed(retargetBlueprint(plan, blueprint, publicationCache)))
return failure();
return success();
}
} // namespace onnx_mlir::spatial
@@ -0,0 +1,21 @@
#pragma once
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "DeferredCommunicationModel.hpp"
namespace onnx_mlir::spatial {
struct DeferredTransferPlan {
llvm::SmallVector<ScheduledInfo, 0> scheduled;
llvm::SmallVector<std::unique_ptr<ProducedValue>> producedStorage;
llvm::DenseMap<int64_t, llvm::SmallVector<ProducedValue*>> producedByGraph;
llvm::SmallVector<std::unique_ptr<DeferredExchangePlan>> exchanges;
llvm::SmallVector<unsigned> stepCounts;
};
mlir::FailureOr<DeferredTransferPlan> buildDeferredTransferPlan(mlir::func::FuncOp funcOp);
mlir::LogicalResult retargetDeferredPublications(mlir::func::FuncOp funcOp, DeferredTransferPlan& plan);
} // namespace onnx_mlir::spatial
@@ -3,7 +3,6 @@
#include "ScheduledComputeVerification.hpp"
#include "SpatialDataflowCsvExporter.hpp"
#include "DeferredCommunicationRealization.hpp"
#include "DeferredCommunicationDeadlock.hpp"
#include "mlir/Pass/Pass.h"
@@ -101,11 +100,6 @@ struct MergeComputeNodesPass final : PassWrapper<MergeComputeNodesPass, Operatio
return;
}
dumpModule(moduleOp, "spatial3_scheduled", /*assumeVerified=*/true);
if (failed(verifyRealizedCommunicationDeadlockFree(funcOp))) {
moduleOp.emitError("MergeComputeNodes final communication verification failed");
signalPassFailure();
return;
}
if (failed(verifyScheduledSpatialInvariants(funcOp))) {
moduleOp.emitError("scheduled Spatial phase 2 verification failed");
signalPassFailure();
@@ -105,25 +105,14 @@ LogicalResult verifyDeferredTransferPhase1Invariants(func::FuncOp funcOp) {
diagnostics.report(transfer.getOperation(), [&](Operation *) {});
return;
}
if (auto scheduled = transfer->getParentOfType<SpatScheduledComputeBatch>()) {
for (unsigned lane = 0; lane < static_cast<unsigned>(scheduled.getLaneCount()); ++lane) {
auto program = analyzeDeferredProgram(transfer, lane);
if (failed(program)) {
diagnostics.report(transfer.getOperation(), [&](Operation *) {});
continue;
}
for (const DeferredProjectionLeaf &leaf : program->leaves) {
if (leaf.kind == DeferredLeafKind::ScalarSource)
continue;
auto source = dyn_cast<OpResult>(transfer.getSources()[leaf.sourceOperandIndex]);
auto graph = source ? dyn_cast<SpatGraphComputeBatch>(source.getOwner()) : SpatGraphComputeBatch();
if (!graph) continue;
if (failed(getGraphBatchPublicationMap(graph, source.getResultNumber(), publicationCache)))
diagnostics.report(transfer.getOperation(), [&](Operation *) {});
}
}
} else if (failed(analyzeDeferredProgram(transfer, std::nullopt))) {
diagnostics.report(transfer.getOperation(), [&](Operation *) {});
for (Value source : transfer.getSources()) {
auto result = dyn_cast<OpResult>(source);
auto batch = result
? dyn_cast<SpatGraphComputeBatch>(result.getOwner())
: SpatGraphComputeBatch();
if (batch && failed(getGraphBatchPublicationMap(
batch, result.getResultNumber(), publicationCache)))
diagnostics.report(transfer.getOperation(), [&](Operation *) {});
}
});