Please use GitHub pull requests for new patches. Avoid migrating existing patches. Phabricator shutdown timeline
Changeset View
Standalone View
mlir/include/mlir/Dialect/SparseTensor/Utils/Merger.h
Show First 20 Lines • Show All 403 Lines • ▼ Show 20 Lines | public: | ||||
/// Sets the level number and level-type of the `t`th tensor on | /// Sets the level number and level-type of the `t`th tensor on | ||||
/// `i`th loop. | /// `i`th loop. | ||||
void setLevelAndType(TensorId t, LoopId i, Level lvl, DimLevelType dlt) { | void setLevelAndType(TensorId t, LoopId i, Level lvl, DimLevelType dlt) { | ||||
assert(isValidLevel(t, lvl) && isValidLoopId(i) && isValidDLT(dlt)); | assert(isValidLevel(t, lvl) && isValidLoopId(i) && isValidDLT(dlt)); | ||||
lvlTypes[t][i] = dlt; | lvlTypes[t][i] = dlt; | ||||
loopToLvl[t][i] = lvl; | loopToLvl[t][i] = lvl; | ||||
lvlToLoop[t][lvl] = i; | lvlToLoop[t][lvl] = i; | ||||
// TODO: Maybe we should favor a constant loop bound when there are multiple | |||||
aartbik: As a TODO? | |||||
What do you mean here? I'm guessing this should be "level", but I'm not sure what you mean by "favor(ing) constant levels" wrengr: What do you mean here? I'm guessing this should be "level", but I'm not sure what you mean by… | |||||
I mean to pick the a static known dimension size instead of dynamic ones if there are multiple candidate. (i.e., DimOp folds to constant value). It might lead to a slightly better code. Peiming: I mean to pick the a static known dimension size instead of dynamic ones if there are multiple… | |||||
Please mark such comments with a TODO, since you clearly have an idea on how to to it better. That way we can periodically grep for TODO's and fix them. Unless you think we will never do that, and then this is a note to self that should not be here. aartbik: Please mark such comments with a TODO, since you clearly have an idea on how to to it better. | |||||
// choices. | |||||
loopBounds[i] = std::make_pair(t, lvl); | |||||
} | } | ||||
using ForeachTensorLoopIdCallback = function_ref<void( | using ForeachTensorLoopIdCallback = function_ref<void( | ||||
TensorLoopId, TensorId, std::optional<Level>, DimLevelType, bool)>; | TensorLoopId, TensorId, std::optional<Level>, DimLevelType, bool)>; | ||||
/// Iterates over a set of `TensorLoopId`s, invoking the callback | /// Iterates over a set of `TensorLoopId`s, invoking the callback | ||||
/// for each `TensorLoopId` and passing it the corresponding tensor | /// for each `TensorLoopId` and passing it the corresponding tensor | ||||
/// identifier, level, and level-type, following with a boolean value | /// identifier, level, and level-type, following with a boolean value | ||||
Show All 11 Lines | void foreachTensorLoopId(LatPointId p, bool simple, | ||||
for (const TensorLoopId b : bits.set_bits()) { | for (const TensorLoopId b : bits.set_bits()) { | ||||
const TensorId t = tensor(b); | const TensorId t = tensor(b); | ||||
const auto optLvl = getLvl(b); | const auto optLvl = getLvl(b); | ||||
const auto lvlTp = getDimLevelType(b); | const auto lvlTp = getDimLevelType(b); | ||||
if (isLvlWithNonTrivialIdxExp(b)) { | if (isLvlWithNonTrivialIdxExp(b)) { | ||||
// This must be an undefined level. | // This must be an undefined level. | ||||
assert(!optLvl.has_value()); | assert(!optLvl.has_value()); | ||||
// Slice the tid along the dependent level to iterate current loop. | // Slice the tid along the dependent level to iterate current loop. | ||||
callback(b, t, loopToDependencies[loop(b)][t], lvlTp, | callback(b, t, getLoopDependentLevel(b), lvlTp, | ||||
/*isIdxReduc=*/true); | /*isIdxReduc=*/true); | ||||
} else { | } else { | ||||
callback(b, t, optLvl, lvlTp, /*isIdxReduc=*/false); | callback(b, t, optLvl, lvlTp, /*isIdxReduc=*/false); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
/// Sets whether the output tensor is sparse or not. | /// Sets whether the output tensor is sparse or not. | ||||
void setHasSparseOut(bool s) { hasSparseOut = s; } | void setHasSparseOut(bool s) { hasSparseOut = s; } | ||||
/// Establishes the two-way map that i <-> <t, lvl>. | /// Establishes the two-way map that i <-> <t, lvl, dlt>. | ||||
void setLoopDependentTensorLevel(LoopId i, TensorId t, Level lvl) { | void setLoopDependentTensorLevel(LoopId i, TensorId t, Level lvl, | ||||
DimLevelType dlt) { | |||||
assert(isValidLoopId(i) && isValidLevel(t, lvl)); | assert(isValidLoopId(i) && isValidLevel(t, lvl)); | ||||
loopToDependencies[i][t] = lvl; | assert(!loopToDependencies[i][t].has_value()); // must be the first def | ||||
since you added a parameter, you also need to update this comment aartbik: since you added a parameter, you also need to update this comment | |||||
levelToDependentIdx[t][lvl].push_back(i); | loopToDependencies[i][t] = std::make_pair(lvl, dlt); | ||||
That's the wrong bound to assert; you should instead compare against levelToDependentIdx[t].size() or maxLvlRank. For correctness you should also assert(i < numLoops && t < numTensors). If you rebase over D146684 to get the assertion helpers, then the full assertion would be assert(isValidLevel(t, lvl) && isValidLoopId(i) && !loopToDependencies[i][t].has_value()). The nice thing about those assertion helpers is that it gives the correct bound for the level (rather than falling back to maxLvlRank), and also guards against the case where i == kInvalidId. wrengr: That's the wrong bound to assert; you should instead compare against `levelToDependentIdx[t]. | |||||
Yeah, I haven't rebase against change that introduced maxLvlRank yet. Peiming: Yeah, I haven't rebase against change that introduced maxLvlRank yet. | |||||
levelToDependentLoop[t][lvl].push_back(i); | |||||
Isn't it redundant to store the level-type in loopToDependencies, since it's already stored in lvlTypes? That is, afaict the following code snippet should always return successfully (assuming i and t are valid): if (const auto dep = loopToDependencies[i][t]) { const Level depLvl = (*dep).first; const auto depOptLoop = lvlToLoop[t][depLvl]; assert(depOptLoop); const LoopId depLoop = *depOptLoop; assert(lvlTypes[t][depLoop] == (*dep).second); } Assuming that's correct, then you shouldn't store the level-type in loopToDependencies because it's redundant information. Or if you absolutely must store the redundant copy for some reason, then you need to verify that the level-type agrees with the one in lvlTypes (or if the one in lvlTypes is undefined, then you need to store the dlt parameter there too; and conversely you need to adjust the setLevelAndType method to also verify consistency with loopToDependencies). I agree that it's rather convoluted to need to say lvlTypes[t][*(lvlToLoop[t][*(loopToDependencies[i][t])])], but the only solution to that is to reconsider the design of all the fields of the Merger class. For example, if we had lvlTypes : (TensorId, Level) -> LevelType instead of the current lvlTypes : (TensorId, LoopId) -> LevelType, then you wouldn't need to use lvlToLoop there. Of course, you'd have to redo the rest of the Merger code to make that work (which may end up inserting more loopToLevel uses than however many levelToLoop uses it removes). Or a different design would be to combine lvlToLoop and lvlTypes into a single lvlInfo : (TensorId, Level) -> (LevelType, optional<LoopId>); of course that would require making different changes to the rest of the Merger code. In any case, I think it would be wise to wait for D146693 to land before trying to make any of these changes, since the newtypes of that CL will greatly simplify the process of rearranging all these vectors. wrengr: Isn't it redundant to store the level-type in `loopToDependencies`, since it's already stored… | |||||
No, it is not redundant. The lvlTypes current is a mapping for (tid, loopid) => dlt, not (tid, level) => dlt. I think storing a pair makes more sense than introducing a complete (tid, level) => dlt map here because the dlt is only required here when there are non-trivial index expressions on the level. Peiming: No, it is not redundant. The lvlTypes current is a mapping for `(tid, loopid) => dlt`, not `… | |||||
There is a lot of unnecessary complexity and redundancy in storing all of:
As I mentioned in my earlier comment, we can easily reconstruct the desired (tid, lvl) -> dlt map via [](t, l) { auto i = lvlToLoop[t][l]; return i ? lvlTypes[t][*i] : Undef; }. Therefore, we always have that loopToDependencies[i][t] == make_pair(l, reconstructedLvlTypes[t][l]). Consequently: since the first part of loopToDependencies has that every (t,i) pair determines l, it is trivial to construct the required (t,l) pair for passing to reconstructedLvlTypes; and since it's trivial to define reconstructedLvlTypes, therefore there is no benefit to storing this redundant information. And as I said before, whenever we store redundant information that means we must also therefore take pains to ensure that all the copies of that information remain consistent. I agree that it would be nice to store the (tid, lvl) -> dlt map directly, and to use that in lieu of the current (tid, loopid) -> dlt map. Especially since the former can be quickly constructed from the types of the tensors, and doesn't require knowing anything about lvlToLoop/loopToLvl. However, regardless of which one we store, the point remains the same: there's no benefit to loopToDependencies storing this information redundantly, and if it stores redundant information anyways then it needs to ensure that it remains consistent with the (tid, {lvl,loopid}) -> dlt map. wrengr: There is a lot of unnecessary complexity and redundancy in storing all of:
- `lvlToLoop… | |||||
I agree that we can probably clean it up, but I will address this in separate patches through ;-) Peiming: I agree that we can probably clean it up, but I will address this in separate patches through… | |||||
} | } | ||||
Top level comments usually apply to the block of code, I find e.g. assert(!loopToDependencies[i][t].has_value()); // must be first definition a lot more readable, since you follow it direclty with the make pair/pushback aartbik: Top level comments usually apply to the block of code, I find e.g.
assert(!loopToDependencies… | |||||
can we make this lvl < .... part also a helper method aartbik: can we make this lvl < .... part also a helper method
(that way, all our asserts read almost… | |||||
I found this is actually a redundant check, if the lvl is valid then it is definitely inbound. Peiming: I found this is actually a redundant check, if the lvl is valid then it is definitely inbound. | |||||
Why do you need this extra assertion? The isValidLevel assertion already ensures that the lvl is valid for the tensor t. Therefore, rather than checking the lvl twice, the rest of the code should instead maintain the invariant that levelToDependentLoop[t].size() == lvlTypes[t].size(). wrengr: Why do you need this extra assertion? The `isValidLevel` assertion already ensures that the… | |||||
/// Whether the loop has dependent slice. | /// Whether the loop has dependent slice. | ||||
bool hasDependentLvl(LoopId i, TensorId t) { | bool hasDependentLvl(LoopId i, TensorId t) { | ||||
assert(isValidTensorId(t) && isValidLoopId(i)); | assert(isValidTensorId(t) && isValidLoopId(i)); | ||||
return loopToDependencies[i][t].has_value(); | return loopToDependencies[i][t].has_value(); | ||||
} | } | ||||
/// Returns the list of loop indices which appear in the non-trivial index | /// Returns the list of loop indices which appear in the non-trivial index | ||||
Not Done ReplyInline ActionsThe non-trivial concept is used more widely now, but it would still be nice to define this per file, or at least per first occurrence what non-trivial really means. Or perhaps we should start using more standard terminology on affine expressions? aartbik: The non-trivial concept is used more widely now, but it would still be nice to define this per… | |||||
/// expression on t_l, e.g., A[i+j] => {i, j} | /// expression on t_l, e.g., A[i+j] => {i, j} | ||||
std::vector<LoopId> &getDependentLoops(TensorId t, Level lvl) { | std::vector<LoopId> &getDependentLoops(TensorId t, Level lvl) { | ||||
assert(isValidLevel(t, lvl)); | assert(isValidLevel(t, lvl)); | ||||
return levelToDependentIdx[t][lvl]; | return levelToDependentLoop[t][lvl]; | ||||
} | } | ||||
/// Returns the defining [tid, lvl] for the loop. | /// Returns the defining [tid, lvl] for the loop. | ||||
std::pair<TensorId, Level> getLoopDefiningLvl(LoopId i) const { | std::pair<TensorId, Level> getLoopDefiningLvl(LoopId i) const { | ||||
assert(isValidLoopId(i)); | assert(isValidLoopId(i)); | ||||
return loopBounds[i]; | return loopBounds[i]; | ||||
} | } | ||||
/// Checks whether the TensorLoopId represents a tensor level with | /// Checks whether the TensorLoopId represents a tensor level contains | ||||
/// non-trivial index expression on it. | /// non-trivial index expression. | ||||
bool isLvlWithNonTrivialIdxExp(TensorLoopId b) const { | bool isLvlWithNonTrivialIdxExp(TensorLoopId b) const { | ||||
const TensorId t = tensor(b); | const TensorId t = tensor(b); | ||||
const LoopId i = loop(b); | const LoopId i = loop(b); | ||||
assert(isValidTensorId(t) && isValidLoopId(i)); | assert(isValidTensorId(t) && isValidLoopId(i)); | ||||
return loopToDependencies[i][t].has_value(); | return loopToDependencies[i][t].has_value(); | ||||
} | } | ||||
/// Checks whether the TensorLoopId represents a sparse tensor level contains | |||||
/// non-trivial index expression. | |||||
bool isSparseLvlWithNonTrivialIdxExp(TensorLoopId b) const { | |||||
if (isLvlWithNonTrivialIdxExp(b)) { | |||||
auto dlt = getLoopDependentLevelType(b); | |||||
return isCompressedDLT(dlt) || isSingletonDLT(dlt); | |||||
} | |||||
return false; | |||||
} | |||||
Level getLoopDependentLevel(TensorLoopId b) const { | |||||
assert(isLvlWithNonTrivialIdxExp(b)); | |||||
tensor level with index expression on it, reads awkward how about must be a tensor level that contains a non-trivial index expression aartbik: tensor level with index expression on it, reads awkward
how about
must be a tensor level that… | |||||
return loopToDependencies[loop(b)][tensor(b)]->first; | |||||
} | |||||
DimLevelType getLoopDependentLevelType(TensorLoopId b) const { | |||||
assert(isLvlWithNonTrivialIdxExp(b)); | |||||
return loopToDependencies[loop(b)][tensor(b)]->second; | |||||
} | |||||
/// Convenience getters to immediately access the stored nodes. | /// Convenience getters to immediately access the stored nodes. | ||||
/// These methods return `const&` because the underlying objects must | /// These methods return `const&` because the underlying objects must | ||||
/// not be mutated by client code. The only exception is for mutating | /// not be mutated by client code. The only exception is for mutating | ||||
/// the value associated with an expression, for which there are | /// the value associated with an expression, for which there are | ||||
/// dedicated methods below. | /// dedicated methods below. | ||||
/// | /// | ||||
/// NOTE: It is inadvisable to keep the reference alive for a long | /// NOTE: It is inadvisable to keep the reference alive for a long | ||||
/// time (e.g., as in `TensorExpr &te = merger.exp(e)`), since insertions | /// time (e.g., as in `TensorExpr &te = merger.exp(e)`), since insertions | ||||
▲ Show 20 Lines • Show All 80 Lines • ▼ Show 20 Lines | |||||
private: | private: | ||||
/// Private helpers. | /// Private helpers. | ||||
constexpr bool isValidTensorId(TensorId t) const { return t < numTensors; } | constexpr bool isValidTensorId(TensorId t) const { return t < numTensors; } | ||||
constexpr bool isValidLoopId(LoopId i) const { | constexpr bool isValidLoopId(LoopId i) const { | ||||
return i != detail::kInvalidId && i < numLoops; | return i != detail::kInvalidId && i < numLoops; | ||||
} | } | ||||
bool isValidLevel(TensorId t, Level lvl) const { | bool isValidLevel(TensorId t, Level lvl) const { | ||||
assert(levelToDependentLoop[t].size() == lvlToLoop[t].size()); | |||||
return isValidTensorId(t) && lvl < lvlToLoop[t].size(); | return isValidTensorId(t) && lvl < lvlToLoop[t].size(); | ||||
} | } | ||||
bool isValidExprId(ExprId e) const { | bool isValidExprId(ExprId e) const { | ||||
return e != detail::kInvalidId && e < tensorExps.size(); | return e != detail::kInvalidId && e < tensorExps.size(); | ||||
} | } | ||||
bool isValidLatPointId(LatPointId p) const { | bool isValidLatPointId(LatPointId p) const { | ||||
return p != detail::kInvalidId && p < latPoints.size(); | return p != detail::kInvalidId && p < latPoints.size(); | ||||
} | } | ||||
Show All 32 Lines | private: | ||||
std::vector<std::vector<std::optional<Level>>> loopToLvl; | std::vector<std::vector<std::optional<Level>>> loopToLvl; | ||||
// Map that converts pair<TensorId, Level> to the corresponding LoopId. | // Map that converts pair<TensorId, Level> to the corresponding LoopId. | ||||
std::vector<std::vector<std::optional<LoopId>>> lvlToLoop; | std::vector<std::vector<std::optional<LoopId>>> lvlToLoop; | ||||
// Map from a loop to its dependencies if any. | // Map from a loop to its dependencies if any. | ||||
// The dependencies of a loop is a set of (tensor, level) pairs. | // The dependencies of a loop is a set of (tensor, level) pairs. | ||||
// It is currently only set for non-trivial index expressions. | // It is currently only set for non-trivial index expressions. | ||||
// E.g., A[i+j] => i and j will have dependencies {A0} to indicate that | // E.g., A[i+j] => i and j will have dependencies {A0, dlt(A0)} to indicate | ||||
// i and j are used in the non-trivial index expression on A0. | // that i and j are used in the non-trivial index expression on A0. | ||||
std::vector<std::vector<std::optional<Level>>> loopToDependencies; | std::vector<std::vector<std::optional<std::pair<Level, DimLevelType>>>> | ||||
loopToDependencies; | |||||
// The inverse map of ldxToDependencies from tensor level -> dependent loop | // The inverse map of ldxToDependencies from tensor level -> dependent loop | ||||
// E.g., A[i+j], we have A0 => {i, j}, to indicate that A0 uses both {i, j} | // E.g., A[i+j], we have A0 => {i, j}, to indicate that A0 uses both {i, j} | ||||
// to compute its indices. | // to compute its indices. | ||||
std::vector<std::vector<std::vector<LoopId>>> levelToDependentIdx; | std::vector<std::vector<std::vector<LoopId>>> levelToDependentLoop; | ||||
This should have been named levelToDependentLoop, since we don't use "idx" in this file anymore because it causes too much confusion. I mentioned this in the previous CL that introduced this field, but you landed the CL without fixing it wrengr: This should have been named `levelToDependentLoop`, since we don't use "idx" in this file… | |||||
// Map from a loop to the [tid, lvl] pair that defines the loop boundary. | // Map from a loop to the [tid, lvl] pair that defines the loop boundary. | ||||
std::vector<std::pair<TensorId, Level>> loopBounds; | std::vector<std::pair<TensorId, Level>> loopBounds; | ||||
llvm::SmallVector<TensorExp> tensorExps; | llvm::SmallVector<TensorExp> tensorExps; | ||||
llvm::SmallVector<LatPoint> latPoints; | llvm::SmallVector<LatPoint> latPoints; | ||||
llvm::SmallVector<SmallVector<LatPointId>> latSets; | llvm::SmallVector<SmallVector<LatPointId>> latSets; | ||||
}; | }; | ||||
} // namespace sparse_tensor | } // namespace sparse_tensor | ||||
} // namespace mlir | } // namespace mlir | ||||
#endif // MLIR_DIALECT_SPARSETENSOR_UTILS_MERGER_H_ | #endif // MLIR_DIALECT_SPARSETENSOR_UTILS_MERGER_H_ |
As a TODO?