better synchronization
better deadlock detection to also track wait/sync
This commit is contained in:
@@ -96,7 +96,7 @@ llvm::cl::opt<bool> pimEmitJson("pim-emit-json",
|
||||
|
||||
llvm::cl::opt<bool> pimDetectCommunicationDeadlock(
|
||||
"pim-detect-communication-deadlock",
|
||||
llvm::cl::desc("Expensively simulate the statically expanded Pim send/receive order at verification time and fail if a blocking communication deadlock is found"),
|
||||
llvm::cl::desc("Expensively simulate statically expanded Pim SEND/RECV and exact-count SYNC/WAIT order at verification time and fail on a blocking deadlock"),
|
||||
llvm::cl::init(false),
|
||||
llvm::cl::cat(OnnxMlirOptions));
|
||||
|
||||
|
||||
@@ -373,9 +373,6 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeOp(spatial::SpatScheduledCom
|
||||
rewriter, receiveOp->getLoc(), outputBuffer.getType(), zero,
|
||||
hostWaitLoad.getHostOffset(), outputBuffer, *hostBuffer, *sizeAttr)
|
||||
.getOutput();
|
||||
PimSyncOp::create(
|
||||
rewriter, receiveOp->getLoc(), hostWaitLoad.getSourceCoreId(),
|
||||
hostWaitLoad.getAcknowledgementEventRegister());
|
||||
} else {
|
||||
received = PimReceiveOp::create(
|
||||
rewriter, receiveOp->getLoc(), outputBuffer.getType(), outputBuffer,
|
||||
|
||||
@@ -152,10 +152,6 @@ struct HostWaitLoadLowering : OpRewritePattern<spatial::SpatHostWaitLoadOp> {
|
||||
Value output = pim::PimMemCopyHostToDevOp::create(
|
||||
rewriter, op.getLoc(), outputBuffer.getType(), zero,
|
||||
op.getHostOffset(), outputBuffer, *hostBuffer, sizeAttr).getOutput();
|
||||
auto sync = pim::PimSyncOp::create(
|
||||
rewriter, op.getLoc(), op.getSourceCoreId(),
|
||||
op.getAcknowledgementEventRegister());
|
||||
copyRaptorDebugAttrs(op.getOperation(), sync.getOperation());
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
#include "mlir/Pass/Pass.h"
|
||||
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
#include "llvm/Support/FormatVariadic.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
@@ -278,7 +280,9 @@ static bool isHostAddressableValue(Value value, const StaticValueKnowledge& know
|
||||
|
||||
enum class CommunicationEventKind {
|
||||
Send,
|
||||
Receive
|
||||
Receive,
|
||||
Sync,
|
||||
Wait
|
||||
};
|
||||
|
||||
struct CommunicationEvent {
|
||||
@@ -286,14 +290,25 @@ struct CommunicationEvent {
|
||||
int64_t coreId = 0;
|
||||
int64_t peerCoreId = 0;
|
||||
int64_t size = 0;
|
||||
int64_t eventRegister = 0;
|
||||
int64_t waitValue = 0;
|
||||
uint64_t ordinal = 0;
|
||||
Operation* op = nullptr;
|
||||
};
|
||||
|
||||
using CommunicationEventVector = SmallVector<CommunicationEvent, 0>;
|
||||
using SynchronizationEventKey = std::pair<int64_t, int64_t>;
|
||||
using SynchronizationSourceCounts =
|
||||
DenseMap<SynchronizationEventKey, DenseMap<int64_t, int64_t>>;
|
||||
|
||||
static StringRef getCommunicationEventKindName(CommunicationEventKind kind) {
|
||||
return kind == CommunicationEventKind::Send ? "send" : "receive";
|
||||
switch (kind) {
|
||||
case CommunicationEventKind::Send: return "send";
|
||||
case CommunicationEventKind::Receive: return "receive";
|
||||
case CommunicationEventKind::Sync: return "sync";
|
||||
case CommunicationEventKind::Wait: return "wait";
|
||||
}
|
||||
llvm_unreachable("unknown communication event kind");
|
||||
}
|
||||
|
||||
constexpr StringLiteral kRaptorMinChannelIdAttr = "raptor.min_channel_id";
|
||||
@@ -356,9 +371,15 @@ static std::string formatCommunicationEvent(const CommunicationEvent& event) {
|
||||
|
||||
std::string text;
|
||||
llvm::raw_string_ostream os(text);
|
||||
os << "core " << event.coreId << " " << getCommunicationEventKindName(event.kind) << " "
|
||||
<< (event.kind == CommunicationEventKind::Send ? "to" : "from") << " " << event.peerCoreId << " size "
|
||||
<< event.size << "B ordinal " << event.ordinal;
|
||||
os << "core " << event.coreId << " " << getCommunicationEventKindName(event.kind);
|
||||
if (event.kind == CommunicationEventKind::Send || event.kind == CommunicationEventKind::Receive)
|
||||
os << " " << (event.kind == CommunicationEventKind::Send ? "to" : "from") << " " << event.peerCoreId
|
||||
<< " size " << event.size << "B";
|
||||
else if (event.kind == CommunicationEventKind::Sync)
|
||||
os << " event " << event.eventRegister << " to " << event.peerCoreId;
|
||||
else
|
||||
os << " event " << event.eventRegister << " value " << event.waitValue;
|
||||
os << " ordinal " << event.ordinal;
|
||||
if (minChannelId)
|
||||
os << " min_channel " << *minChannelId;
|
||||
if (commOrder)
|
||||
@@ -383,6 +404,9 @@ static std::string formatCommunicationEvent(const CommunicationEvent& event) {
|
||||
}
|
||||
|
||||
static bool areMatchedCommunicationEvents(const CommunicationEvent& lhs, const CommunicationEvent& rhs) {
|
||||
if ((lhs.kind != CommunicationEventKind::Send && lhs.kind != CommunicationEventKind::Receive)
|
||||
|| (rhs.kind != CommunicationEventKind::Send && rhs.kind != CommunicationEventKind::Receive))
|
||||
return false;
|
||||
if (lhs.coreId != rhs.peerCoreId || lhs.peerCoreId != rhs.coreId || lhs.size != rhs.size)
|
||||
return false;
|
||||
|
||||
@@ -402,6 +426,27 @@ static void printCounterpartProbe(llvm::raw_ostream& os,
|
||||
const DenseMap<int64_t, CommunicationEventVector>& coreEvents,
|
||||
const DenseMap<int64_t, size_t>& programCounters,
|
||||
const CommunicationEvent& blockedEvent) {
|
||||
if (blockedEvent.kind == CommunicationEventKind::Wait) {
|
||||
os << " SYNC probes for " << formatCommunicationEvent(blockedEvent) << "\n";
|
||||
bool found = false;
|
||||
for (const auto& [sourceCore, events] : coreEvents) {
|
||||
size_t begin = programCounters.lookup(sourceCore);
|
||||
for (size_t index = begin; index < events.size(); ++index) {
|
||||
const CommunicationEvent& candidate = events[index];
|
||||
if (candidate.kind != CommunicationEventKind::Sync
|
||||
|| candidate.peerCoreId != blockedEvent.coreId
|
||||
|| candidate.eventRegister != blockedEvent.eventRegister)
|
||||
continue;
|
||||
os << " core " << sourceCore << " next matching SYNC at ordinal "
|
||||
<< index << " (distance +" << index - begin << ")\n";
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
os << " no remaining matching SYNC exists\n";
|
||||
return;
|
||||
}
|
||||
auto peerEventsIt = coreEvents.find(blockedEvent.peerCoreId);
|
||||
if (peerEventsIt == coreEvents.end()) {
|
||||
os << " no local stream was collected for peer core " << blockedEvent.peerCoreId << "\n";
|
||||
@@ -456,7 +501,7 @@ static void printCounterpartProbe(llvm::raw_ostream& os,
|
||||
|
||||
static CommunicationEvent makeCommunicationEvent(
|
||||
CommunicationEventKind kind, int64_t coreId, int64_t peerCoreId, int64_t size, uint64_t ordinal, Operation* op) {
|
||||
return CommunicationEvent {kind, coreId, peerCoreId, size, ordinal, op};
|
||||
return CommunicationEvent {kind, coreId, peerCoreId, size, 0, 0, ordinal, op};
|
||||
}
|
||||
|
||||
static LogicalResult appendCoreCommunicationEvents(Block& block,
|
||||
@@ -504,6 +549,39 @@ static LogicalResult appendCoreCommunicationEvents(Block& block,
|
||||
return success();
|
||||
}
|
||||
|
||||
if (auto syncOp = dyn_cast<pim::PimSyncOp>(&op)) {
|
||||
auto targetCoreId = resolveIndexValue(syncOp.getTargetCoreId(), knowledge);
|
||||
auto eventRegister = resolveIndexValue(syncOp.getEventRegister(), knowledge);
|
||||
if (failed(targetCoreId) || failed(eventRegister)) {
|
||||
diagnostics.report(&op, [](Operation* illegalOp) {
|
||||
illegalOp->emitOpError(
|
||||
"cannot statically resolve SYNC operands for Pim communication deadlock check");
|
||||
});
|
||||
return failure();
|
||||
}
|
||||
events.push_back(CommunicationEvent {
|
||||
CommunicationEventKind::Sync, coreId, *targetCoreId, 0,
|
||||
*eventRegister, 0, static_cast<uint64_t>(events.size()), &op});
|
||||
return success();
|
||||
}
|
||||
|
||||
if (auto waitOp = dyn_cast<pim::PimWaitOp>(&op)) {
|
||||
auto eventRegister = resolveIndexValue(waitOp.getEventRegister(), knowledge);
|
||||
auto waitValue = resolveIndexValue(waitOp.getWaitValue(), knowledge);
|
||||
if (failed(eventRegister) || failed(waitValue)) {
|
||||
diagnostics.report(&op, [](Operation* illegalOp) {
|
||||
illegalOp->emitOpError(
|
||||
"cannot statically resolve WAIT operands for Pim communication deadlock check");
|
||||
});
|
||||
return failure();
|
||||
}
|
||||
events.push_back(CommunicationEvent {
|
||||
CommunicationEventKind::Wait, coreId, coreId, 0,
|
||||
*eventRegister, *waitValue,
|
||||
static_cast<uint64_t>(events.size()), &op});
|
||||
return success();
|
||||
}
|
||||
|
||||
return success();
|
||||
});
|
||||
}
|
||||
@@ -530,7 +608,7 @@ static void printCommunicationWindow(llvm::raw_ostream& os,
|
||||
static void printCommunicationDeadlockReport(const DenseMap<int64_t, CommunicationEventVector>& coreEvents,
|
||||
const DenseMap<int64_t, size_t>& programCounters,
|
||||
ArrayRef<int64_t> cycle) {
|
||||
llvm::errs() << "\n=== Pim static communication deadlock report ===\n";
|
||||
llvm::errs() << "\n=== Pim static communication/synchronization deadlock report ===\n";
|
||||
llvm::errs() << "wait cycle:";
|
||||
for (int64_t coreId : cycle)
|
||||
llvm::errs() << " " << coreId;
|
||||
@@ -565,7 +643,7 @@ static void printCommunicationDeadlockReport(const DenseMap<int64_t, Communicati
|
||||
continue;
|
||||
printCommunicationWindow(llvm::errs(), coreEvents, coreId, pcIt->second);
|
||||
}
|
||||
llvm::errs() << "=== end Pim static communication deadlock report ===\n\n";
|
||||
llvm::errs() << "=== end Pim static communication/synchronization deadlock report ===\n\n";
|
||||
}
|
||||
|
||||
static void emitCommunicationDeadlockCycle(ModuleOp moduleOp,
|
||||
@@ -576,8 +654,8 @@ static void emitCommunicationDeadlockCycle(ModuleOp moduleOp,
|
||||
|
||||
auto diagnostic =
|
||||
moduleOp.emitError()
|
||||
<< "Pim communication deadlock check found a blocking send/receive cycle while statically simulating the "
|
||||
"expanded per-core communication streams; see the Pim static communication deadlock report above";
|
||||
<< "Pim communication deadlock check found a blocking SEND/RECV/WAIT cycle while statically simulating the "
|
||||
"expanded per-core communication streams; see the static deadlock report above";
|
||||
|
||||
for (int64_t coreId : cycle) {
|
||||
auto eventsIt = coreEvents.find(coreId);
|
||||
@@ -596,46 +674,82 @@ static void emitCommunicationDeadlockCycle(ModuleOp moduleOp,
|
||||
|
||||
static FailureOr<SmallVector<int64_t>>
|
||||
findCommunicationWaitCycle(const DenseMap<int64_t, CommunicationEventVector>& coreEvents,
|
||||
const DenseMap<int64_t, size_t>& programCounters) {
|
||||
for (const auto& [startCoreId, events] : coreEvents) {
|
||||
auto startPcIt = programCounters.find(startCoreId);
|
||||
if (startPcIt == programCounters.end() || startPcIt->second >= events.size())
|
||||
const DenseMap<int64_t, size_t>& programCounters,
|
||||
const DenseSet<int64_t>& repeatingCores,
|
||||
const SynchronizationSourceCounts& sourceCounts) {
|
||||
DenseMap<int64_t, SmallVector<int64_t>> dependencies;
|
||||
for (const auto& [coreId, events] : coreEvents) {
|
||||
size_t pc = programCounters.lookup(coreId);
|
||||
if (pc >= events.size())
|
||||
continue;
|
||||
|
||||
DenseMap<int64_t, size_t> positionInPath;
|
||||
SmallVector<int64_t, 8> path;
|
||||
int64_t currentCoreId = startCoreId;
|
||||
while (true) {
|
||||
auto eventsIt = coreEvents.find(currentCoreId);
|
||||
auto pcIt = programCounters.find(currentCoreId);
|
||||
if (eventsIt == coreEvents.end() || pcIt == programCounters.end() || pcIt->second >= eventsIt->second.size())
|
||||
break;
|
||||
|
||||
auto positionIt = positionInPath.find(currentCoreId);
|
||||
if (positionIt != positionInPath.end()) {
|
||||
SmallVector<int64_t> cycle;
|
||||
for (size_t index = positionIt->second; index < path.size(); ++index)
|
||||
cycle.push_back(path[index]);
|
||||
return cycle;
|
||||
}
|
||||
|
||||
positionInPath[currentCoreId] = path.size();
|
||||
path.push_back(currentCoreId);
|
||||
currentCoreId = eventsIt->second[pcIt->second].peerCoreId;
|
||||
const CommunicationEvent& event = events[pc];
|
||||
if (event.kind == CommunicationEventKind::Send
|
||||
|| event.kind == CommunicationEventKind::Receive) {
|
||||
dependencies[coreId].push_back(event.peerCoreId);
|
||||
continue;
|
||||
}
|
||||
if (event.kind != CommunicationEventKind::Wait)
|
||||
continue;
|
||||
int64_t blockedCoreId = coreId;
|
||||
SynchronizationEventKey eventKey {coreId, event.eventRegister};
|
||||
auto contributions = sourceCounts.find(eventKey);
|
||||
for (const auto& [sourceCore, sourceEvents] : coreEvents) {
|
||||
size_t sourcePc = programCounters.lookup(sourceCore);
|
||||
auto matches = [&](const CommunicationEvent& candidate) {
|
||||
return candidate.kind == CommunicationEventKind::Sync
|
||||
&& candidate.peerCoreId == blockedCoreId
|
||||
&& candidate.eventRegister == event.eventRegister;
|
||||
};
|
||||
int64_t signalsPerPhase = llvm::count_if(sourceEvents, matches);
|
||||
if (repeatingCores.contains(sourceCore))
|
||||
signalsPerPhase /= 2;
|
||||
int64_t contributed = contributions == sourceCounts.end()
|
||||
? 0 : contributions->second.lookup(sourceCore);
|
||||
if (contributed >= signalsPerPhase)
|
||||
continue;
|
||||
bool canSignal = llvm::any_of(
|
||||
llvm::drop_begin(sourceEvents, sourcePc), matches);
|
||||
if (canSignal)
|
||||
dependencies[coreId].push_back(sourceCore);
|
||||
}
|
||||
}
|
||||
|
||||
DenseSet<int64_t> visited;
|
||||
DenseMap<int64_t, size_t> positionInPath;
|
||||
SmallVector<int64_t, 8> path;
|
||||
std::function<std::optional<SmallVector<int64_t>>(int64_t)> visit =
|
||||
[&](int64_t coreId) -> std::optional<SmallVector<int64_t>> {
|
||||
auto position = positionInPath.find(coreId);
|
||||
if (position != positionInPath.end())
|
||||
return SmallVector<int64_t>(
|
||||
path.begin() + position->second, path.end());
|
||||
if (!visited.insert(coreId).second)
|
||||
return std::nullopt;
|
||||
positionInPath[coreId] = path.size();
|
||||
path.push_back(coreId);
|
||||
for (int64_t target : dependencies.lookup(coreId))
|
||||
if (auto cycle = visit(target))
|
||||
return cycle;
|
||||
path.pop_back();
|
||||
positionInPath.erase(coreId);
|
||||
return std::nullopt;
|
||||
};
|
||||
for (const auto& [coreId, unused] : dependencies)
|
||||
if (auto cycle = visit(coreId))
|
||||
return *cycle;
|
||||
return failure();
|
||||
}
|
||||
|
||||
static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||
pim::CappedDiagnosticReporter& diagnostics) {
|
||||
DenseMap<int64_t, CommunicationEventVector> coreEvents;
|
||||
DenseSet<int64_t> repeatingCores;
|
||||
bool hasFailure = false;
|
||||
|
||||
for (func::FuncOp funcOp : moduleOp.getOps<func::FuncOp>()) {
|
||||
if (funcOp.isExternal())
|
||||
continue;
|
||||
bool repeating = funcOp->hasAttr("pim.pipeline_host_buffer_bytes");
|
||||
|
||||
for (Operation& op : funcOp.getBody().front().getOperations()) {
|
||||
if (auto coreOp = dyn_cast<pim::PimCoreOp>(&op)) {
|
||||
@@ -648,6 +762,8 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||
coreEvents[coreId],
|
||||
diagnostics)))
|
||||
hasFailure = true;
|
||||
if (repeating)
|
||||
repeatingCores.insert(coreId);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -668,8 +784,11 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||
coreId,
|
||||
laneKnowledge,
|
||||
coreEvents[coreId],
|
||||
diagnostics)))
|
||||
diagnostics))) {
|
||||
hasFailure = true;
|
||||
} else if (repeating) {
|
||||
repeatingCores.insert(coreId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -678,10 +797,20 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||
if (hasFailure)
|
||||
return failure();
|
||||
|
||||
for (int64_t coreId : repeatingCores) {
|
||||
CommunicationEventVector iteration = coreEvents[coreId];
|
||||
for (CommunicationEvent event : iteration) {
|
||||
event.ordinal = coreEvents[coreId].size();
|
||||
coreEvents[coreId].push_back(event);
|
||||
}
|
||||
}
|
||||
|
||||
DenseMap<int64_t, size_t> programCounters;
|
||||
for (const auto& [coreId, events] : coreEvents)
|
||||
programCounters[coreId] = 0;
|
||||
|
||||
DenseMap<SynchronizationEventKey, int64_t> eventCounts;
|
||||
SynchronizationSourceCounts sourceCounts;
|
||||
while (true) {
|
||||
bool madeProgress = false;
|
||||
for (const auto& [coreId, events] : coreEvents) {
|
||||
@@ -690,6 +819,35 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||
continue;
|
||||
|
||||
const CommunicationEvent& event = events[pc];
|
||||
if (event.kind == CommunicationEventKind::Sync) {
|
||||
SynchronizationEventKey eventKey {
|
||||
event.peerCoreId, event.eventRegister};
|
||||
++eventCounts[eventKey];
|
||||
++sourceCounts[eventKey][coreId];
|
||||
++programCounters[coreId];
|
||||
madeProgress = true;
|
||||
continue;
|
||||
}
|
||||
if (event.kind == CommunicationEventKind::Wait) {
|
||||
int64_t observed = eventCounts.lookup(
|
||||
{coreId, event.eventRegister});
|
||||
if (observed == event.waitValue) {
|
||||
SynchronizationEventKey eventKey {coreId, event.eventRegister};
|
||||
eventCounts[eventKey] = 0;
|
||||
sourceCounts.erase(eventKey);
|
||||
++programCounters[coreId];
|
||||
madeProgress = true;
|
||||
} else if (observed > event.waitValue) {
|
||||
auto diagnostic = event.op->emitOpError()
|
||||
<< "Pim synchronization deadlock check found exact-count WAIT overshoot on core "
|
||||
<< coreId << " event " << event.eventRegister << ": expected "
|
||||
<< event.waitValue << ", observed " << observed;
|
||||
diagnostic.attachNote()
|
||||
<< "WAIT requires exact equality and this event cannot decrease without a successful WAIT";
|
||||
return failure();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
auto peerEventsIt = coreEvents.find(event.peerCoreId);
|
||||
if (peerEventsIt == coreEvents.end())
|
||||
continue;
|
||||
@@ -720,7 +878,8 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||
if (allDone)
|
||||
return success();
|
||||
|
||||
auto cycle = findCommunicationWaitCycle(coreEvents, programCounters);
|
||||
auto cycle = findCommunicationWaitCycle(
|
||||
coreEvents, programCounters, repeatingCores, sourceCounts);
|
||||
if (succeeded(cycle)) {
|
||||
emitCommunicationDeadlockCycle(moduleOp, coreEvents, programCounters, *cycle);
|
||||
return failure();
|
||||
@@ -729,7 +888,7 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||
auto diagnostic =
|
||||
moduleOp.emitError()
|
||||
<< "Pim communication deadlock check stalled without finding a closed wait cycle; this usually means a "
|
||||
"send/receive peer is missing or ordered after a finished core";
|
||||
"SEND/RECV peer or exact-count SYNC signal is missing or ordered after a finished core";
|
||||
for (const auto& [coreId, events] : coreEvents) {
|
||||
size_t pc = programCounters[coreId];
|
||||
if (pc >= events.size())
|
||||
|
||||
+168
-41
@@ -2,6 +2,8 @@
|
||||
#include "DeferredCommunicationScheduling.hpp"
|
||||
#include "DeferredTransferPlanning.hpp"
|
||||
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
|
||||
namespace onnx_mlir::spatial {
|
||||
using namespace mlir;
|
||||
namespace {
|
||||
@@ -256,7 +258,7 @@ static unsigned getBarrierRoundCount(size_t coreCount) {
|
||||
|
||||
static LogicalResult assignPipelineSynchronization(
|
||||
DeferredTransferPlan &transfers,
|
||||
ArrayRef<BoundaryProgram> boundaries,
|
||||
MutableArrayRef<BoundaryProgram> boundaries,
|
||||
size_t synchronizationRegisterCount) {
|
||||
bool pipelined = false;
|
||||
for (ScheduledInfo &scheduled : transfers.scheduled) {
|
||||
@@ -284,14 +286,13 @@ static LogicalResult assignPipelineSynchronization(
|
||||
});
|
||||
|
||||
DenseMap<int64_t, SmallVector<HostTransferRef>> incomingByCore;
|
||||
DenseMap<int64_t, llvm::SmallSetVector<int64_t, 4>> readersByWriter;
|
||||
DenseMap<ExternalTransferFamily *, SmallVector<int64_t>> eventRegisters;
|
||||
DenseMap<ExternalTransferFamily *, SmallVector<int64_t>> waitValues;
|
||||
DenseMap<ExternalTransferFamily *, SmallVector<int64_t>> acknowledgementRegisters;
|
||||
auto initialize = [&](ExternalTransferFamily &family) {
|
||||
size_t count = family.targetCores.size();
|
||||
eventRegisters.try_emplace(&family, count, 0);
|
||||
waitValues.try_emplace(&family, count, 0);
|
||||
acknowledgementRegisters.try_emplace(&family, count, 0);
|
||||
};
|
||||
for (const BoundaryProgram &boundary : boundaries)
|
||||
for (const BoundaryInstruction &instruction : boundary.instructions) {
|
||||
@@ -307,32 +308,117 @@ static LogicalResult assignPipelineSynchronization(
|
||||
int64_t source = family.sourceCores.valueAt(index);
|
||||
int64_t target = family.targetCores.valueAt(index);
|
||||
incomingByCore[target].push_back({&family, index});
|
||||
++transfers.hostAcknowledgementCounts[source];
|
||||
readersByWriter[source].insert(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DenseMap<int64_t, SmallVector<SmallVector<int64_t>>>
|
||||
overwriteBoundaryReadersByWriter;
|
||||
DenseSet<std::pair<int64_t, int64_t>> plannedRelations;
|
||||
for (const BoundaryProgram &boundary : boundaries) {
|
||||
DenseMap<int64_t, llvm::SmallSetVector<int64_t, 4>> readersAtBoundary;
|
||||
for (const BoundaryInstruction &instruction : boundary.instructions) {
|
||||
auto *send = std::get_if<EmitSendRun>(&instruction);
|
||||
if (!send || send->slices.empty()
|
||||
|| !send->slices.front().family->hostRouted)
|
||||
continue;
|
||||
for (const ScheduledTransferSlice &slice : send->slices)
|
||||
for (size_t offset = 0; offset < slice.transferCount; ++offset) {
|
||||
size_t index = slice.familyOffset + offset;
|
||||
int64_t writer = slice.family->sourceCores.valueAt(index);
|
||||
int64_t reader = slice.family->targetCores.valueAt(index);
|
||||
if (plannedRelations.insert({writer, reader}).second)
|
||||
readersAtBoundary[writer].insert(reader);
|
||||
}
|
||||
}
|
||||
for (const auto &[writer, readers] : readersAtBoundary)
|
||||
overwriteBoundaryReadersByWriter[writer].emplace_back(
|
||||
readers.begin(), readers.end());
|
||||
}
|
||||
|
||||
unsigned barrierRounds = getBarrierRoundCount(
|
||||
transfers.stageZeroCores.size());
|
||||
bool stageZeroNeedsAcknowledgements = llvm::any_of(
|
||||
transfers.stageZeroCores, [&](int64_t core) {
|
||||
return transfers.hostAcknowledgementCounts.contains(core);
|
||||
});
|
||||
for (auto &[target, incoming] : incomingByCore) {
|
||||
bool needsAcknowledgementRegister =
|
||||
transfers.hostAcknowledgementCounts.contains(target);
|
||||
bool stageZero = llvm::is_contained(transfers.stageZeroCores, target);
|
||||
size_t reserved = stageZero
|
||||
? barrierRounds + (stageZeroNeedsAcknowledgements ? 1 : 0)
|
||||
: 1 + (needsAcknowledgementRegister ? 1 : 0);
|
||||
if (reserved >= synchronizationRegisterCount) {
|
||||
incoming.front().family->requirement->exchange->deferred.emitOpError(
|
||||
"pipeline synchronization leaves no event register for incoming host transfers");
|
||||
auto reservedRegisterCount = [&](int64_t core) -> size_t {
|
||||
if (llvm::is_contained(transfers.stageZeroCores, core)) {
|
||||
bool releasesDownstream = !transfers.downstreamCores.empty()
|
||||
&& core == transfers.stageZeroCores.front();
|
||||
return barrierRounds + releasesDownstream;
|
||||
}
|
||||
auto downstream = llvm::find(transfers.downstreamCores, core);
|
||||
if (downstream == transfers.downstreamCores.end())
|
||||
return 0;
|
||||
size_t rank = downstream - transfers.downstreamCores.begin();
|
||||
bool releasesChildren = 2 * rank + 1 < transfers.downstreamCores.size();
|
||||
return 1 + releasesChildren;
|
||||
};
|
||||
for (int64_t core : transfers.stageZeroCores)
|
||||
if (reservedRegisterCount(core) > synchronizationRegisterCount)
|
||||
return transfers.scheduled.front().op->emitOpError(
|
||||
"pipeline stage-zero synchronization requires more event registers than the target provides");
|
||||
for (int64_t core : transfers.downstreamCores)
|
||||
if (reservedRegisterCount(core) > synchronizationRegisterCount)
|
||||
return transfers.scheduled.front().op->emitOpError(
|
||||
"pipeline downstream restart synchronization requires more event registers than the target provides");
|
||||
SmallVector<int64_t> synchronizationCores;
|
||||
for (const auto &[core, incoming] : incomingByCore)
|
||||
synchronizationCores.push_back(core);
|
||||
for (const auto &[core, readers] : readersByWriter)
|
||||
if (!llvm::is_contained(synchronizationCores, core))
|
||||
synchronizationCores.push_back(core);
|
||||
llvm::sort(synchronizationCores);
|
||||
|
||||
DenseMap<int64_t, SmallVector<unsigned>> freeRegisters;
|
||||
DenseMap<int64_t, SmallVector<unsigned>> freeWaitCounts;
|
||||
DenseMap<int64_t, DenseMap<int64_t, unsigned>> freeGroups;
|
||||
for (int64_t core : synchronizationCores) {
|
||||
auto incomingIt = incomingByCore.find(core);
|
||||
ArrayRef<HostTransferRef> incoming = incomingIt == incomingByCore.end()
|
||||
? ArrayRef<HostTransferRef>() : ArrayRef(incomingIt->second);
|
||||
size_t readerCount = readersByWriter.lookup(core).size();
|
||||
bool needsFreeRegister = readerCount != 0;
|
||||
size_t reserved = reservedRegisterCount(core);
|
||||
if (reserved + needsFreeRegister > synchronizationRegisterCount
|
||||
|| (!incoming.empty()
|
||||
&& reserved + needsFreeRegister == synchronizationRegisterCount)) {
|
||||
transfers.scheduled.front().op->emitOpError(
|
||||
"pipeline synchronization leaves no event register for host communication");
|
||||
return failure();
|
||||
}
|
||||
size_t groupCount = std::min(
|
||||
incoming.size(), synchronizationRegisterCount - reserved);
|
||||
// One wait consumes a complete consecutive group of producer signals.
|
||||
size_t available = synchronizationRegisterCount - reserved;
|
||||
size_t readyRegisterCount = std::min(
|
||||
incoming.size(), available - needsFreeRegister);
|
||||
auto overwriteBoundaries = overwriteBoundaryReadersByWriter.find(core);
|
||||
size_t overwriteBoundaryCount = overwriteBoundaries
|
||||
== overwriteBoundaryReadersByWriter.end()
|
||||
? 0 : overwriteBoundaries->second.size();
|
||||
size_t freeGroupCount = std::min(
|
||||
overwriteBoundaryCount, available - readyRegisterCount);
|
||||
if (needsFreeRegister) {
|
||||
size_t overwriteReaderCount = 0;
|
||||
if (overwriteBoundaries != overwriteBoundaryReadersByWriter.end())
|
||||
for (ArrayRef<int64_t> readers : overwriteBoundaries->second)
|
||||
overwriteReaderCount += readers.size();
|
||||
if (overwriteReaderCount != readerCount)
|
||||
return transfers.scheduled.front().op->emitOpError(
|
||||
"host reuse readers do not match writer overwrite relations");
|
||||
freeRegisters[core].reserve(freeGroupCount);
|
||||
freeWaitCounts[core].assign(freeGroupCount, 0);
|
||||
for (unsigned group = 0; group < freeGroupCount; ++group)
|
||||
freeRegisters[core].push_back(readyRegisterCount + group);
|
||||
for (auto [ordinal, readers] :
|
||||
llvm::enumerate(overwriteBoundaries->second)) {
|
||||
unsigned group = ordinal * freeGroupCount / overwriteBoundaryCount;
|
||||
for (int64_t reader : readers) {
|
||||
freeGroups[core][reader] = group;
|
||||
++freeWaitCounts[core][group];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (incoming.empty())
|
||||
continue;
|
||||
|
||||
size_t groupCount = readyRegisterCount;
|
||||
SmallVector<size_t> groupSizes(groupCount);
|
||||
for (size_t ordinal = 0; ordinal < incoming.size(); ++ordinal)
|
||||
++groupSizes[ordinal * groupCount / incoming.size()];
|
||||
@@ -341,8 +427,6 @@ static LogicalResult assignPipelineSynchronization(
|
||||
size_t group = ordinal * groupCount / incoming.size();
|
||||
HostTransferRef transfer = incoming[ordinal];
|
||||
eventRegisters[transfer.family][transfer.index] = group;
|
||||
acknowledgementRegisters[transfer.family][transfer.index] =
|
||||
synchronizationRegisterCount - 1;
|
||||
if (first[group]) {
|
||||
waitValues[transfer.family][transfer.index] = groupSizes[group];
|
||||
first[group] = false;
|
||||
@@ -352,27 +436,70 @@ static LogicalResult assignPipelineSynchronization(
|
||||
for (auto &[family, values] : eventRegisters) {
|
||||
family->eventRegisters = StaticIntSequence::fromValues(values);
|
||||
family->waitValues = StaticIntSequence::fromValues(waitValues[family]);
|
||||
family->acknowledgementEventRegisters =
|
||||
StaticIntSequence::fromValues(acknowledgementRegisters[family]);
|
||||
}
|
||||
|
||||
if (!transfers.stageZeroCores.empty()) {
|
||||
size_t reserved = barrierRounds
|
||||
+ (stageZeroNeedsAcknowledgements ? 1 : 0);
|
||||
if (reserved > synchronizationRegisterCount)
|
||||
return transfers.scheduled.front().op->emitOpError(
|
||||
"pipeline stage-zero barrier requires more synchronization registers than the target provides");
|
||||
}
|
||||
if (!transfers.downstreamCores.empty()) {
|
||||
bool needsAcknowledgements = llvm::any_of(
|
||||
transfers.downstreamCores, [&](int64_t core) {
|
||||
return transfers.hostAcknowledgementCounts.contains(core);
|
||||
});
|
||||
if (1 + (needsAcknowledgements ? 1 : 0)
|
||||
> synchronizationRegisterCount)
|
||||
return transfers.scheduled.front().op->emitOpError(
|
||||
"pipeline stage-zero release requires more synchronization registers than the target provides");
|
||||
SmallVector<int64_t> writers;
|
||||
for (const auto &[writer, readers] : readersByWriter)
|
||||
writers.push_back(writer);
|
||||
llvm::sort(writers);
|
||||
for (int64_t writer : writers)
|
||||
for (int64_t reader : readersByWriter[writer]) {
|
||||
unsigned group = freeGroups[writer].lookup(reader);
|
||||
transfers.hostReleaseSignals[reader].push_back(
|
||||
{writer, freeRegisters[writer][group]});
|
||||
}
|
||||
|
||||
DenseSet<std::pair<int64_t, unsigned>> pendingGroups;
|
||||
for (int64_t writer : writers)
|
||||
for (unsigned group = 0; group < freeRegisters[writer].size(); ++group)
|
||||
pendingGroups.insert({writer, group});
|
||||
for (BoundaryProgram &boundary : boundaries) {
|
||||
SmallVector<BoundaryInstruction, 0> instructions;
|
||||
for (BoundaryInstruction &instruction : boundary.instructions) {
|
||||
auto *send = std::get_if<EmitSendRun>(&instruction);
|
||||
SmallVector<std::pair<unsigned, unsigned>> waits;
|
||||
if (send && !send->slices.empty()
|
||||
&& send->slices.front().family->hostRouted) {
|
||||
for (const ScheduledTransferSlice &slice : send->slices) {
|
||||
ExternalTransferFamily &family = *slice.family;
|
||||
unsigned lane = family.requirement->producer->scheduledLane;
|
||||
if (lane >= boundary.key.first->cores.size())
|
||||
return family.requirement->exchange->deferred.emitOpError(
|
||||
"host reuse wait references an invalid writer lane"), failure();
|
||||
for (size_t offset = 0; offset < slice.transferCount; ++offset) {
|
||||
int64_t writer = family.sourceCores.valueAt(
|
||||
slice.familyOffset + offset);
|
||||
if (boundary.key.first->cores[lane] != writer)
|
||||
return family.requirement->exchange->deferred.emitOpError(
|
||||
"host reuse wait writer does not match its scheduled lane"),
|
||||
failure();
|
||||
int64_t reader = family.targetCores.valueAt(
|
||||
slice.familyOffset + offset);
|
||||
unsigned group = freeGroups[writer].lookup(reader);
|
||||
if (!pendingGroups.erase({writer, group}))
|
||||
continue;
|
||||
waits.push_back({lane, group});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto [lane, group] : waits) {
|
||||
int64_t writer = boundary.key.first->cores[lane];
|
||||
SmallVector<int64_t> registers(boundary.key.first->cores.size());
|
||||
SmallVector<int64_t> counts(boundary.key.first->cores.size());
|
||||
registers[lane] = freeRegisters[writer][group];
|
||||
counts[lane] = freeWaitCounts[writer][group];
|
||||
instructions.push_back(EmitHostReuseWait {
|
||||
LaneSet::range(lane, lane + 1),
|
||||
StaticIntSequence::fromValues(registers),
|
||||
StaticIntSequence::fromValues(counts)});
|
||||
}
|
||||
instructions.push_back(std::move(instruction));
|
||||
}
|
||||
boundary.instructions = std::move(instructions);
|
||||
}
|
||||
if (!pendingGroups.empty())
|
||||
return transfers.scheduled.front().op->emitOpError(
|
||||
"host reuse permission has no writer overwrite boundary");
|
||||
return success();
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -34,12 +34,17 @@ struct EmitReceiveAssemblyRun {
|
||||
llvm::SmallVector<LaneSet> entryLanes;
|
||||
LaneSet lanes;
|
||||
};
|
||||
struct EmitHostReuseWait {
|
||||
LaneSet lanes;
|
||||
StaticIntSequence eventRegisters = StaticIntSequence::uniform(0, 1);
|
||||
StaticIntSequence waitValues = StaticIntSequence::uniform(0, 1);
|
||||
};
|
||||
struct ProduceDeferredResult {
|
||||
DeferredExchangePlan* exchange = nullptr;
|
||||
};
|
||||
|
||||
using BoundaryInstruction =
|
||||
std::variant<EmitSendRun, EmitLocalCollectionRun,
|
||||
std::variant<EmitSendRun, EmitHostReuseWait, EmitLocalCollectionRun,
|
||||
EmitLocalCollectionLoopRun, EmitReceiveAssemblyRun,
|
||||
ProduceDeferredResult>;
|
||||
struct BoundaryProgram {
|
||||
|
||||
+216
-105
@@ -10,6 +10,7 @@
|
||||
#include "src/Accelerators/PIM/Common/IR/StaticIntSequence.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/TensorSliceUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
#include <array>
|
||||
namespace onnx_mlir::spatial {
|
||||
using namespace mlir;
|
||||
@@ -23,7 +24,6 @@ struct LogicalTransferMetadataView {
|
||||
StaticIntSequenceChain hostOffsets;
|
||||
StaticIntSequenceChain eventRegisters;
|
||||
StaticIntSequenceChain waitValues;
|
||||
StaticIntSequenceChain acknowledgementEventRegisters;
|
||||
StaticIntSequenceChain targetLanes;
|
||||
StaticIntSequenceChain localOffsets;
|
||||
SmallVector<StaticIntSequenceChain> projectionOffsets;
|
||||
@@ -78,6 +78,29 @@ static FailureOr<Value> emitLaneCondition(const LaneSet &lanes, Value lane, unsi
|
||||
Value selected = active->emitLookup(context.constants.getIndex(0), lane, anchor, context.constants, context.rewriter, loc);
|
||||
return arith::CmpIOp::create(context.rewriter, loc, arith::CmpIPredicate::ne, selected, context.constants.getIndex(0)).getResult();
|
||||
}
|
||||
template <typename Emit>
|
||||
static LogicalResult emitForLanes(
|
||||
const LaneSet &active, Value lane, unsigned laneCount, Operation *anchor,
|
||||
DeferredEmissionContext &context, Location loc, Emit emit) {
|
||||
if (active.empty())
|
||||
return success();
|
||||
if (!lane) {
|
||||
if (active.contains(0))
|
||||
emit();
|
||||
return success();
|
||||
}
|
||||
auto condition = emitLaneCondition(
|
||||
active, lane, laneCount, anchor, context, loc);
|
||||
if (failed(condition))
|
||||
return failure();
|
||||
auto conditional = scf::IfOp::create(
|
||||
context.rewriter, loc, TypeRange {}, *condition, false);
|
||||
OpBuilder::InsertionGuard guard(context.rewriter);
|
||||
context.rewriter.setInsertionPoint(
|
||||
conditional.getThenRegion().front().getTerminator());
|
||||
emit();
|
||||
return success();
|
||||
}
|
||||
static void appendMetadata(const ScheduledTransferSlice &slice, LogicalTransferMetadataView &metadata) {
|
||||
ExternalTransferFamily &family = *slice.family;
|
||||
LaneInterval familyLanes = family.targetLanes.intervals().front();
|
||||
@@ -95,8 +118,6 @@ static void appendMetadata(const ScheduledTransferSlice &slice, LogicalTransferM
|
||||
metadata.eventRegisters.append(
|
||||
family.eventRegisters, familyIndex, count);
|
||||
metadata.waitValues.append(family.waitValues, familyIndex, count);
|
||||
metadata.acknowledgementEventRegisters.append(
|
||||
family.acknowledgementEventRegisters, familyIndex, count);
|
||||
}
|
||||
metadata.targetLanes.append(StaticIntSequence::affine(targetLane, 1, count));
|
||||
if (family.requirement->producerLocalOffsets)
|
||||
@@ -303,20 +324,15 @@ static FailureOr<Value> emitReceiveValue(ArrayRef<ScheduledTransferSlice> slices
|
||||
std::optional<StaticIntGrid> hostOffsets;
|
||||
std::optional<StaticIntGrid> eventRegisters;
|
||||
std::optional<StaticIntGrid> waitValues;
|
||||
std::optional<StaticIntGrid> acknowledgementEventRegisters;
|
||||
if (slices.front().family->hostRouted) {
|
||||
auto offsets = buildGrid(metadata.hostOffsets);
|
||||
auto events = buildGrid(metadata.eventRegisters);
|
||||
auto waits = buildGrid(metadata.waitValues);
|
||||
auto acknowledgements = buildGrid(
|
||||
metadata.acknowledgementEventRegisters);
|
||||
if (failed(offsets) || failed(events) || failed(waits)
|
||||
|| failed(acknowledgements))
|
||||
if (failed(offsets) || failed(events) || failed(waits))
|
||||
return failure();
|
||||
hostOffsets = std::move(*offsets);
|
||||
eventRegisters = std::move(*events);
|
||||
waitValues = std::move(*waits);
|
||||
acknowledgementEventRegisters = std::move(*acknowledgements);
|
||||
}
|
||||
Value position = lane ? lane : context.constants.getIndex(0);
|
||||
Value row = context.constants.getIndex(0);
|
||||
@@ -335,8 +351,6 @@ static FailureOr<Value> emitReceiveValue(ArrayRef<ScheduledTransferSlice> slices
|
||||
eventRegisters->emitLookup(
|
||||
row, position, anchor, context.constants, context.rewriter, anchor->getLoc()),
|
||||
waitValues->emitLookup(
|
||||
row, position, anchor, context.constants, context.rewriter, anchor->getLoc()),
|
||||
acknowledgementEventRegisters->emitLookup(
|
||||
row, position, anchor, context.constants, context.rewriter, anchor->getLoc()));
|
||||
receive = op;
|
||||
output = op.getOutput();
|
||||
@@ -406,7 +420,6 @@ static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, V
|
||||
std::optional<StaticIntGrid> hostOffsets;
|
||||
std::optional<StaticIntGrid> eventRegisters;
|
||||
std::optional<StaticIntGrid> waitValues;
|
||||
std::optional<StaticIntGrid> acknowledgementEventRegisters;
|
||||
bool hostRouted = run.slices.front().family->hostRouted;
|
||||
auto metadataByEntry = buildRectangularReceiveMetadata(run, laneCount);
|
||||
if (succeeded(metadataByEntry)) {
|
||||
@@ -424,15 +437,11 @@ static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, V
|
||||
&LogicalTransferMetadataView::eventRegisters);
|
||||
auto waits = buildRows(
|
||||
&LogicalTransferMetadataView::waitValues);
|
||||
auto acknowledgements = buildRows(
|
||||
&LogicalTransferMetadataView::acknowledgementEventRegisters);
|
||||
if (failed(offsets) || failed(events) || failed(waits)
|
||||
|| failed(acknowledgements))
|
||||
if (failed(offsets) || failed(events) || failed(waits))
|
||||
return failure();
|
||||
hostOffsets = std::move(*offsets);
|
||||
eventRegisters = std::move(*events);
|
||||
waitValues = std::move(*waits);
|
||||
acknowledgementEventRegisters = std::move(*acknowledgements);
|
||||
}
|
||||
SmallVector<StaticIntSequence> positionRows;
|
||||
for (unsigned position : run.positions)
|
||||
@@ -485,15 +494,11 @@ static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, V
|
||||
&LogicalTransferMetadataView::eventRegisters);
|
||||
auto waits = buildGrid(
|
||||
&LogicalTransferMetadataView::waitValues);
|
||||
auto acknowledgements = buildGrid(
|
||||
&LogicalTransferMetadataView::acknowledgementEventRegisters);
|
||||
if (failed(offsets) || failed(events) || failed(waits)
|
||||
|| failed(acknowledgements))
|
||||
if (failed(offsets) || failed(events) || failed(waits))
|
||||
return failure();
|
||||
hostOffsets = std::move(*offsets);
|
||||
eventRegisters = std::move(*events);
|
||||
waitValues = std::move(*waits);
|
||||
acknowledgementEventRegisters = std::move(*acknowledgements);
|
||||
}
|
||||
SmallVector<StaticIntSequence> positionColumns;
|
||||
for (const StaticIntSequenceChain &values : positionsByLane)
|
||||
@@ -527,8 +532,6 @@ static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, V
|
||||
eventRegisters->emitLookup(
|
||||
entry, runtimeLane, anchor, context.constants, context.rewriter, loc),
|
||||
waitValues->emitLookup(
|
||||
entry, runtimeLane, anchor, context.constants, context.rewriter, loc),
|
||||
acknowledgementEventRegisters->emitLookup(
|
||||
entry, runtimeLane, anchor, context.constants, context.rewriter, loc));
|
||||
receive = op;
|
||||
output = op.getOutput();
|
||||
@@ -1083,7 +1086,8 @@ static LogicalResult emitLocalCollectionUpdate(const EmitLocalCollectionRun &upd
|
||||
}
|
||||
|
||||
static FailureOr<SmallVector<Value>> emitInstructions(ArrayRef<BoundaryInstruction> instructions, Value lane, unsigned laneCount,
|
||||
ArrayRef<DeferredResultPlan> results, DeferredEmissionContext &context) {
|
||||
ArrayRef<DeferredResultPlan> results, ScheduledInfo &scheduled,
|
||||
DeferredEmissionContext &context) {
|
||||
SmallVector<Value> produced;
|
||||
for (size_t instructionIndex = 0;
|
||||
instructionIndex < instructions.size(); ++instructionIndex) {
|
||||
@@ -1108,7 +1112,22 @@ static FailureOr<SmallVector<Value>> emitInstructions(ArrayRef<BoundaryInstructi
|
||||
}
|
||||
}
|
||||
}
|
||||
if (auto send = std::get_if<EmitSendRun>(&instruction)) {
|
||||
if (auto reuse = std::get_if<EmitHostReuseWait>(&instruction)) {
|
||||
Value runtimeLane = lane ? lane : context.constants.getIndex(0);
|
||||
Location loc = scheduled.op->getLoc();
|
||||
if (failed(emitForLanes(
|
||||
reuse->lanes, lane, laneCount, scheduled.op, context, loc, [&]() {
|
||||
SpatWaitOp::create(
|
||||
context.rewriter, loc,
|
||||
emitStaticIntLookup(
|
||||
reuse->eventRegisters, runtimeLane, scheduled.op,
|
||||
context.constants, context.rewriter, loc),
|
||||
emitStaticIntLookup(
|
||||
reuse->waitValues, runtimeLane, scheduled.op,
|
||||
context.constants, context.rewriter, loc));
|
||||
})))
|
||||
return failure();
|
||||
} else if (auto send = std::get_if<EmitSendRun>(&instruction)) {
|
||||
if (failed(emitConditionalSendRun(*send, lane, laneCount, context)))
|
||||
return failure();
|
||||
} else if (auto update = std::get_if<EmitLocalCollectionRun>(&instruction)) {
|
||||
@@ -1192,7 +1211,9 @@ static LogicalResult emitBoundary(const BoundaryProgram &boundary, ArrayRef<Defe
|
||||
if (auto batch = dyn_cast<SpatScheduledComputeBatch>(boundary.key.first->op))
|
||||
lane = *batch.getLaneArgument();
|
||||
SmallVector<DeferredExchangePlan *> exchanges = getProducedExchanges(boundary.instructions);
|
||||
auto values = emitInstructions(boundary.instructions, lane, laneCount, results, context);
|
||||
auto values = emitInstructions(
|
||||
boundary.instructions, lane, laneCount, results, *boundary.key.first,
|
||||
context);
|
||||
return failed(values) ? failure() : replaceResults(exchanges, *values, replacements);
|
||||
}
|
||||
|
||||
@@ -1203,30 +1224,129 @@ static unsigned getBarrierRoundCount(size_t coreCount) {
|
||||
return rounds;
|
||||
}
|
||||
|
||||
static LogicalResult emitHostReleaseSynchronization(
|
||||
DeferredTransferPlan &transfers, DeferredEmissionContext &context) {
|
||||
struct Emission {
|
||||
HostReleaseSignal signal;
|
||||
LaneSet lanes;
|
||||
};
|
||||
DenseSet<int64_t> emittedReaders;
|
||||
for (ScheduledInfo &scheduled : transfers.scheduled) {
|
||||
SmallVector<Emission> emissions;
|
||||
for (auto [laneIndex, core] : llvm::enumerate(scheduled.cores)) {
|
||||
auto found = transfers.hostReleaseSignals.find(core);
|
||||
if (found == transfers.hostReleaseSignals.end())
|
||||
continue;
|
||||
if (!emittedReaders.insert(core).second)
|
||||
return scheduled.op->emitOpError(
|
||||
"host release reader is represented by multiple scheduled lanes");
|
||||
for (HostReleaseSignal signal : found->second) {
|
||||
auto emission = llvm::find_if(emissions, [&](const Emission &item) {
|
||||
return item.signal.writerCore == signal.writerCore
|
||||
&& item.signal.eventRegister == signal.eventRegister;
|
||||
});
|
||||
if (emission == emissions.end()) {
|
||||
emissions.push_back({signal, LaneSet::range(
|
||||
laneIndex, laneIndex + 1)});
|
||||
} else {
|
||||
emission->lanes = emission->lanes.unite(
|
||||
LaneSet::range(laneIndex, laneIndex + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (emissions.empty())
|
||||
continue;
|
||||
Block *block = scheduled.blocks.front();
|
||||
context.rewriter.setInsertionPointToStart(block);
|
||||
Location loc = scheduled.op->getLoc();
|
||||
Value lane;
|
||||
if (auto batch = dyn_cast<SpatScheduledComputeBatch>(scheduled.op))
|
||||
lane = *batch.getLaneArgument();
|
||||
for (const Emission &emission : emissions)
|
||||
if (failed(emitForLanes(
|
||||
emission.lanes, lane, scheduled.cores.size(), scheduled.op,
|
||||
context, loc, [&]() {
|
||||
SpatSyncOp::create(
|
||||
context.rewriter, loc,
|
||||
context.constants.getIndex(emission.signal.writerCore),
|
||||
context.constants.getIndex(emission.signal.eventRegister));
|
||||
})))
|
||||
return failure();
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
static LogicalResult emitRestartReleasePermissions(
|
||||
DeferredTransferPlan &transfers, DeferredEmissionContext &context) {
|
||||
if (transfers.downstreamCores.empty())
|
||||
return success();
|
||||
unsigned barrierRounds = getBarrierRoundCount(
|
||||
transfers.stageZeroCores.size());
|
||||
size_t restartRegister = transfers.synchronizationRegisterCount - 1;
|
||||
size_t leaderPermissionRegister = restartRegister - barrierRounds;
|
||||
size_t downstreamPermissionRegister = restartRegister - 1;
|
||||
DenseMap<int64_t, unsigned> downstreamRank;
|
||||
for (auto [rank, core] : llvm::enumerate(transfers.downstreamCores))
|
||||
downstreamRank[core] = rank;
|
||||
|
||||
for (ScheduledInfo &scheduled : transfers.scheduled) {
|
||||
LaneSet lanes;
|
||||
SmallVector<int64_t> targets(scheduled.cores.size());
|
||||
SmallVector<int64_t> registers(scheduled.cores.size());
|
||||
for (auto [lane, core] : llvm::enumerate(scheduled.cores)) {
|
||||
auto rank = downstreamRank.find(core);
|
||||
if (rank == downstreamRank.end())
|
||||
continue;
|
||||
lanes = lanes.unite(LaneSet::range(lane, lane + 1));
|
||||
if (rank->second == 0) {
|
||||
targets[lane] = transfers.stageZeroCores.front();
|
||||
registers[lane] = leaderPermissionRegister;
|
||||
} else {
|
||||
targets[lane] = transfers.downstreamCores[(rank->second - 1) / 2];
|
||||
registers[lane] = downstreamPermissionRegister;
|
||||
}
|
||||
}
|
||||
if (lanes.empty())
|
||||
continue;
|
||||
Block *block = scheduled.blocks.front();
|
||||
context.rewriter.setInsertionPointToStart(block);
|
||||
Location loc = scheduled.op->getLoc();
|
||||
Value lane;
|
||||
if (auto batch = dyn_cast<SpatScheduledComputeBatch>(scheduled.op))
|
||||
lane = *batch.getLaneArgument();
|
||||
Value runtimeLane = lane ? lane : context.constants.getIndex(0);
|
||||
if (failed(emitForLanes(
|
||||
lanes, lane, scheduled.cores.size(), scheduled.op, context, loc,
|
||||
[&]() {
|
||||
Value target = emitStaticIntLookup(
|
||||
StaticIntSequence::fromValues(targets), runtimeLane,
|
||||
scheduled.op, context.constants, context.rewriter, loc);
|
||||
Value eventRegister = emitStaticIntLookup(
|
||||
StaticIntSequence::fromValues(registers), runtimeLane,
|
||||
scheduled.op, context.constants, context.rewriter, loc);
|
||||
SpatSyncOp::create(
|
||||
context.rewriter, loc, target, eventRegister);
|
||||
})))
|
||||
return failure();
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
static LogicalResult emitCompletionSynchronization(
|
||||
DeferredTransferPlan &transfers, DeferredEmissionContext &context) {
|
||||
if (transfers.synchronizationRegisterCount == 0)
|
||||
return success();
|
||||
size_t acknowledgementRegister =
|
||||
size_t restartRegister =
|
||||
transfers.synchronizationRegisterCount - 1;
|
||||
unsigned barrierRounds = getBarrierRoundCount(
|
||||
transfers.stageZeroCores.size());
|
||||
bool stageZeroNeedsAcknowledgements = llvm::any_of(
|
||||
transfers.stageZeroCores, [&](int64_t core) {
|
||||
return transfers.hostAcknowledgementCounts.contains(core);
|
||||
});
|
||||
size_t firstBarrierRegister = acknowledgementRegister
|
||||
- (stageZeroNeedsAcknowledgements ? 1 : 0);
|
||||
size_t firstBarrierRegister = restartRegister;
|
||||
DenseMap<int64_t, unsigned> stageZeroRank;
|
||||
for (auto [rank, core] : llvm::enumerate(transfers.stageZeroCores))
|
||||
stageZeroRank[core] = rank;
|
||||
DenseMap<int64_t, unsigned> downstreamRank;
|
||||
for (auto [rank, core] : llvm::enumerate(transfers.downstreamCores))
|
||||
downstreamRank[core] = rank;
|
||||
auto getReleaseRegister = [&](int64_t core) {
|
||||
return acknowledgementRegister
|
||||
- (transfers.hostAcknowledgementCounts.contains(core) ? 1 : 0);
|
||||
};
|
||||
|
||||
for (ScheduledInfo &scheduled : transfers.scheduled) {
|
||||
Block *block = scheduled.blocks.front();
|
||||
@@ -1236,13 +1356,10 @@ static LogicalResult emitCompletionSynchronization(
|
||||
if (auto batch = dyn_cast<SpatScheduledComputeBatch>(scheduled.op))
|
||||
lane = *batch.getLaneArgument();
|
||||
|
||||
SmallVector<int64_t> acknowledgementCounts, releaseRegisters;
|
||||
SmallVector<int64_t> releaseWaitValues, leftTargets, leftRegisters;
|
||||
SmallVector<int64_t> rightTargets, rightRegisters;
|
||||
LaneSet barrierLanes, leaderLanes, leftLanes, rightLanes;
|
||||
SmallVector<int64_t> leftTargets, rightTargets, childCounts;
|
||||
LaneSet barrierLanes, leaderLanes, downstreamLanes, parentLanes,
|
||||
leftLanes, rightLanes;
|
||||
for (auto [index, core] : llvm::enumerate(scheduled.cores)) {
|
||||
acknowledgementCounts.push_back(
|
||||
transfers.hostAcknowledgementCounts.lookup(core));
|
||||
if (stageZeroRank.contains(core))
|
||||
barrierLanes = barrierLanes.unite(
|
||||
LaneSet::range(index, index + 1));
|
||||
@@ -1252,66 +1369,37 @@ static LogicalResult emitCompletionSynchronization(
|
||||
|
||||
auto rank = downstreamRank.find(core);
|
||||
if (rank == downstreamRank.end()) {
|
||||
releaseRegisters.push_back(0);
|
||||
releaseWaitValues.push_back(0);
|
||||
leftTargets.push_back(core);
|
||||
leftRegisters.push_back(0);
|
||||
rightTargets.push_back(core);
|
||||
rightRegisters.push_back(0);
|
||||
childCounts.push_back(0);
|
||||
continue;
|
||||
}
|
||||
releaseRegisters.push_back(getReleaseRegister(core));
|
||||
releaseWaitValues.push_back(1);
|
||||
downstreamLanes = downstreamLanes.unite(
|
||||
LaneSet::range(index, index + 1));
|
||||
size_t left = 2 * rank->second + 1;
|
||||
size_t right = left + 1;
|
||||
unsigned childCount = 0;
|
||||
if (left < transfers.downstreamCores.size()) {
|
||||
int64_t child = transfers.downstreamCores[left];
|
||||
leftTargets.push_back(child);
|
||||
leftRegisters.push_back(getReleaseRegister(child));
|
||||
leftLanes = leftLanes.unite(LaneSet::range(index, index + 1));
|
||||
++childCount;
|
||||
} else {
|
||||
leftTargets.push_back(core);
|
||||
leftRegisters.push_back(0);
|
||||
}
|
||||
if (right < transfers.downstreamCores.size()) {
|
||||
int64_t child = transfers.downstreamCores[right];
|
||||
rightTargets.push_back(child);
|
||||
rightRegisters.push_back(getReleaseRegister(child));
|
||||
rightLanes = rightLanes.unite(LaneSet::range(index, index + 1));
|
||||
++childCount;
|
||||
} else {
|
||||
rightTargets.push_back(core);
|
||||
rightRegisters.push_back(0);
|
||||
}
|
||||
childCounts.push_back(childCount);
|
||||
if (childCount != 0)
|
||||
parentLanes = parentLanes.unite(LaneSet::range(index, index + 1));
|
||||
}
|
||||
Value runtimeLane = lane ? lane : context.constants.getIndex(0);
|
||||
auto emitForLanes = [&](const LaneSet &active, auto emit) -> LogicalResult {
|
||||
if (active.empty())
|
||||
return success();
|
||||
if (!lane) {
|
||||
if (active.contains(0))
|
||||
emit();
|
||||
return success();
|
||||
}
|
||||
auto condition = emitLaneCondition(
|
||||
active, lane, scheduled.cores.size(), scheduled.op, context, loc);
|
||||
if (failed(condition))
|
||||
return failure();
|
||||
auto conditional = scf::IfOp::create(
|
||||
context.rewriter, loc, TypeRange {}, *condition, false);
|
||||
OpBuilder::InsertionGuard guard(context.rewriter);
|
||||
context.rewriter.setInsertionPoint(
|
||||
conditional.getThenRegion().front().getTerminator());
|
||||
emit();
|
||||
return success();
|
||||
};
|
||||
Value acknowledgementCount = emitStaticIntLookup(
|
||||
StaticIntSequence::fromValues(acknowledgementCounts),
|
||||
runtimeLane, scheduled.op,
|
||||
context.constants, context.rewriter, loc);
|
||||
SpatWaitOp::create(
|
||||
context.rewriter, loc,
|
||||
context.constants.getIndex(acknowledgementRegister),
|
||||
acknowledgementCount);
|
||||
|
||||
// Dissemination barrier: every round doubles the covered stage-zero peers.
|
||||
auto emitBarrier = [&]() {
|
||||
@@ -1341,45 +1429,64 @@ static LogicalResult emitCompletionSynchronization(
|
||||
}
|
||||
};
|
||||
if (barrierRounds > 0
|
||||
&& failed(emitForLanes(barrierLanes, emitBarrier)))
|
||||
&& failed(emitForLanes(
|
||||
barrierLanes, lane, scheduled.cores.size(), scheduled.op, context,
|
||||
loc, emitBarrier)))
|
||||
return failure();
|
||||
|
||||
// Gate downstream restarts so no core advances the simulator input
|
||||
// iteration ahead of stage zero.
|
||||
if (!transfers.downstreamCores.empty()
|
||||
&& failed(emitForLanes(leaderLanes, [&]() {
|
||||
&& failed(emitForLanes(
|
||||
leaderLanes, lane, scheduled.cores.size(), scheduled.op, context,
|
||||
loc, [&]() {
|
||||
SpatWaitOp::create(
|
||||
context.rewriter, loc,
|
||||
context.constants.getIndex(firstBarrierRegister - barrierRounds),
|
||||
context.constants.getIndex(1));
|
||||
int64_t root = transfers.downstreamCores.front();
|
||||
SpatSyncOp::create(
|
||||
context.rewriter, loc, context.constants.getIndex(root),
|
||||
context.constants.getIndex(getReleaseRegister(root)));
|
||||
context.constants.getIndex(restartRegister));
|
||||
})))
|
||||
return failure();
|
||||
|
||||
Value releaseRegister = emitStaticIntLookup(
|
||||
StaticIntSequence::fromValues(releaseRegisters), runtimeLane,
|
||||
scheduled.op, context.constants, context.rewriter, loc);
|
||||
Value releaseWaitValue = emitStaticIntLookup(
|
||||
StaticIntSequence::fromValues(releaseWaitValues), runtimeLane,
|
||||
scheduled.op, context.constants, context.rewriter, loc);
|
||||
SpatWaitOp::create(
|
||||
context.rewriter, loc, releaseRegister, releaseWaitValue);
|
||||
if (failed(emitForLanes(
|
||||
downstreamLanes, lane, scheduled.cores.size(), scheduled.op,
|
||||
context, loc, [&]() {
|
||||
SpatWaitOp::create(
|
||||
context.rewriter, loc,
|
||||
context.constants.getIndex(restartRegister),
|
||||
context.constants.getIndex(1));
|
||||
})))
|
||||
return failure();
|
||||
|
||||
auto emitChild = [&](ArrayRef<int64_t> targets,
|
||||
ArrayRef<int64_t> registers) {
|
||||
if (failed(emitForLanes(
|
||||
parentLanes, lane, scheduled.cores.size(), scheduled.op, context,
|
||||
loc, [&]() {
|
||||
Value count = emitStaticIntLookup(
|
||||
StaticIntSequence::fromValues(childCounts), runtimeLane,
|
||||
scheduled.op, context.constants, context.rewriter, loc);
|
||||
SpatWaitOp::create(
|
||||
context.rewriter, loc,
|
||||
context.constants.getIndex(restartRegister - 1), count);
|
||||
})))
|
||||
return failure();
|
||||
|
||||
auto emitChild = [&](ArrayRef<int64_t> targets) {
|
||||
Value target = emitStaticIntLookup(
|
||||
StaticIntSequence::fromValues(targets), runtimeLane, scheduled.op,
|
||||
context.constants, context.rewriter, loc);
|
||||
Value eventRegister = emitStaticIntLookup(
|
||||
StaticIntSequence::fromValues(registers), runtimeLane, scheduled.op,
|
||||
context.constants, context.rewriter, loc);
|
||||
SpatSyncOp::create(context.rewriter, loc, target, eventRegister);
|
||||
SpatSyncOp::create(
|
||||
context.rewriter, loc, target,
|
||||
context.constants.getIndex(restartRegister));
|
||||
};
|
||||
if (failed(emitForLanes(leftLanes, [&]() {
|
||||
emitChild(leftTargets, leftRegisters);
|
||||
}))
|
||||
|| failed(emitForLanes(rightLanes, [&]() {
|
||||
emitChild(rightTargets, rightRegisters);
|
||||
})))
|
||||
if (failed(emitForLanes(
|
||||
leftLanes, lane, scheduled.cores.size(), scheduled.op, context, loc,
|
||||
[&]() { emitChild(leftTargets); }))
|
||||
|| failed(emitForLanes(
|
||||
rightLanes, lane, scheduled.cores.size(), scheduled.op, context,
|
||||
loc, [&]() { emitChild(rightTargets); })))
|
||||
return failure();
|
||||
}
|
||||
return success();
|
||||
@@ -1399,6 +1506,10 @@ LogicalResult realizeDeferredBoundaries(ArrayRef<BoundaryProgram> boundaries, Ar
|
||||
if (failed(emitBoundary(boundary, results, context, replacements)))
|
||||
return boundary.key.first->op->emitOpError("phase 2 failed to realize a communication boundary");
|
||||
}
|
||||
if (failed(emitRestartReleasePermissions(transfers, context)))
|
||||
return failure();
|
||||
if (failed(emitHostReleaseSynchronization(transfers, context)))
|
||||
return failure();
|
||||
return emitCompletionSynchronization(transfers, context);
|
||||
}
|
||||
|
||||
|
||||
-2
@@ -237,8 +237,6 @@ struct ExternalTransferFamily {
|
||||
StaticIntSequence hostOffsets = StaticIntSequence::uniform(0, 1);
|
||||
StaticIntSequence eventRegisters = StaticIntSequence::uniform(0, 1);
|
||||
StaticIntSequence waitValues = StaticIntSequence::uniform(1, 1);
|
||||
StaticIntSequence acknowledgementEventRegisters =
|
||||
StaticIntSequence::uniform(0, 1);
|
||||
bool hostRouted = false;
|
||||
};
|
||||
|
||||
|
||||
+7
-1
@@ -7,6 +7,11 @@
|
||||
|
||||
namespace onnx_mlir::spatial {
|
||||
|
||||
struct HostReleaseSignal {
|
||||
int64_t writerCore = -1;
|
||||
unsigned eventRegister = 0;
|
||||
};
|
||||
|
||||
struct DeferredTransferPlan {
|
||||
std::vector<size_t> processorStages;
|
||||
llvm::SmallVector<ScheduledInfo, 0> scheduled;
|
||||
@@ -14,7 +19,8 @@ struct DeferredTransferPlan {
|
||||
llvm::DenseMap<int64_t, llvm::SmallVector<ProducedValue*>> producedByGraph;
|
||||
llvm::SmallVector<std::unique_ptr<DeferredExchangePlan>> exchanges;
|
||||
llvm::SmallVector<unsigned> stepCounts;
|
||||
llvm::DenseMap<int64_t, unsigned> hostAcknowledgementCounts;
|
||||
llvm::DenseMap<int64_t, llvm::SmallVector<HostReleaseSignal>>
|
||||
hostReleaseSignals;
|
||||
llvm::SmallVector<int64_t> stageZeroCores;
|
||||
llvm::SmallVector<int64_t> downstreamCores;
|
||||
size_t synchronizationRegisterCount = 0;
|
||||
|
||||
@@ -592,15 +592,14 @@ def SpatHostStoreSyncOp : SpatOp<"host_store_sync", []> {
|
||||
}
|
||||
|
||||
def SpatHostWaitLoadOp : SpatOp<"host_wait_load", []> {
|
||||
let summary = "Wait for producers, load from host memory, and acknowledge consumption";
|
||||
let summary = "Wait for producers and load from host memory";
|
||||
|
||||
let arguments = (ins
|
||||
Index:$sourceCoreId,
|
||||
Index:$targetCoreId,
|
||||
Index:$hostOffset,
|
||||
Index:$eventRegister,
|
||||
Index:$waitValue,
|
||||
Index:$acknowledgementEventRegister
|
||||
Index:$waitValue
|
||||
);
|
||||
|
||||
let results = (outs
|
||||
@@ -610,7 +609,7 @@ def SpatHostWaitLoadOp : SpatOp<"host_wait_load", []> {
|
||||
let assemblyFormat = [{
|
||||
`from` $sourceCoreId `to` $targetCoreId
|
||||
`host_offset` $hostOffset `event` $eventRegister `count` $waitValue
|
||||
`ack` $acknowledgementEventRegister attr-dict `:` type($output)
|
||||
attr-dict `:` type($output)
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user