mirror of
https://github.com/zebrajr/pytorch.git
synced 2025-12-07 00:21:07 +01:00
Summary:
Things changed in this PR that requires review:
1. aten/src/ATen/core/interned_strings.h
2. torch/csrc/jit/ir/alias_analysis.h : exposing createValue to allow efficient mutation
3. torch/csrc/jit/runtime/symbolic_shape_registry.cpp : added gelu/tanh/erf in registry
4. torch/jit/_script.py : throws scripting model sees autocast as decorator since it's not supported
nvfuser code update:
1. codegen improvements and performance tuning
2. integration bug fixes for shape expression logic
3. kernel segmentation update to address perf regression from horizontal fusion
4. scalar cpu tensor promotion to support inter-device operation between cpu scalar tensor and cuda tensor
Things reverted from local changes:
aten::gelu with approximation (tracked in PR: https://github.com/pytorch/pytorch/pull/61439)
Pull Request resolved: https://github.com/pytorch/pytorch/pull/72127
Reviewed By: HamidShojanazeri
Differential Revision: D34113233
Pulled By: jbschlosser
fbshipit-source-id: b82cde32b71e324eca0ea57cb8c9f9647278ca74
(cherry picked from commit e009bc5c4e)
630 lines
17 KiB
C++
630 lines
17 KiB
C++
#include <torch/csrc/jit/codegen/cuda/arith.h>
|
|
#include <torch/csrc/jit/codegen/cuda/codegen.h>
|
|
#include <torch/csrc/jit/codegen/cuda/fusion.h>
|
|
#include <torch/csrc/jit/codegen/cuda/fusion_segmenter.h>
|
|
#include <torch/csrc/jit/codegen/cuda/instrumentation.h>
|
|
#include <torch/csrc/jit/codegen/cuda/ir_all_nodes.h>
|
|
#include <torch/csrc/jit/codegen/cuda/ir_cloner.h>
|
|
#include <torch/csrc/jit/codegen/cuda/ir_printer.h>
|
|
#include <torch/csrc/jit/codegen/cuda/ir_utils.h>
|
|
#include <torch/csrc/jit/codegen/cuda/iter_visitor.h>
|
|
#include <torch/csrc/jit/codegen/cuda/kernel.h>
|
|
#include <torch/csrc/jit/codegen/cuda/lower2device.h>
|
|
|
|
namespace torch {
|
|
namespace jit {
|
|
namespace fuser {
|
|
namespace cuda {
|
|
|
|
static thread_local Fusion* ACTIVE_FUSION = nullptr; // NOLINT
|
|
|
|
FusionGuard::FusionGuard(Fusion* fusion) {
|
|
prev_fusion = ACTIVE_FUSION;
|
|
ACTIVE_FUSION = fusion;
|
|
}
|
|
|
|
FusionGuard::~FusionGuard() {
|
|
ACTIVE_FUSION = prev_fusion;
|
|
}
|
|
|
|
Fusion* FusionGuard::getCurFusion() {
|
|
return ACTIVE_FUSION;
|
|
}
|
|
|
|
void swap(Fusion& a, Fusion& b) noexcept {
|
|
FUSER_PERF_SCOPE("Fusion swap");
|
|
|
|
using std::swap;
|
|
|
|
swap(static_cast<IrContainer&>(a), static_cast<IrContainer&>(b));
|
|
|
|
swap(a.inputs_, b.inputs_);
|
|
swap(a.outputs_, b.outputs_);
|
|
|
|
swap(a.io_alias_, b.io_alias_);
|
|
swap(a.permuted_input_map_, b.permuted_input_map_);
|
|
swap(a.permuted_output_map_, b.permuted_output_map_);
|
|
}
|
|
|
|
std::unique_ptr<SegmentedFusion> Fusion::segment(
|
|
const at::ArrayRef<IValue>& inputs) {
|
|
FUSER_PERF_SCOPE("Segment Fusion");
|
|
return SegmentCandidateFinder::segment(this, inputs);
|
|
}
|
|
|
|
IrCloner Fusion::copy(const Fusion* from, Fusion* to) {
|
|
to->clear();
|
|
auto ir_cloner = IrContainer::copy(from, to);
|
|
|
|
for (auto val : from->vals_) {
|
|
ir_cloner.clone(val)->setDefinition(ir_cloner.clone(val->definition_));
|
|
ir_cloner.clone(val)->setUses(ir_cloner.clone(val->uses_));
|
|
}
|
|
|
|
to->inputs_ = ir_cloner.clone(from->inputs_);
|
|
to->outputs_ = ir_cloner.clone(from->outputs_);
|
|
|
|
// TODO: put this into ir_cloner instead
|
|
for (const auto& entry : from->io_alias_) {
|
|
Val* copied_output = ir_cloner.clone(entry.first);
|
|
Val* copied_input = ir_cloner.clone(entry.second);
|
|
to->io_alias_[copied_output] = copied_input;
|
|
}
|
|
|
|
to->permuted_input_map_ = from->permuted_input_map_;
|
|
to->permuted_output_map_ = from->permuted_output_map_;
|
|
|
|
to->all_tv_uses_valid_ = from->all_tv_uses_valid_;
|
|
// This should never be true on copy, but copying for completeness.
|
|
to->is_during_update_uses_ = from->is_during_update_uses_;
|
|
|
|
return ir_cloner;
|
|
}
|
|
|
|
// Clang tidy complains when using default constructor for IrContainer instead
|
|
// of copy constructor. Fusion::copy has a call to IrContainer::copy, so it's
|
|
// redundant to use the IrContainer copy constructor, but it is harmless since
|
|
// Fusion::copy starts by calling clear().
|
|
Fusion::Fusion(const Fusion& other) : IrContainer(other) {
|
|
FUSER_PERF_SCOPE("Fusion copy");
|
|
Fusion::copy(&other, this);
|
|
}
|
|
|
|
Fusion::Fusion(Fusion&& other) noexcept {
|
|
FUSER_PERF_SCOPE("Fusion move");
|
|
swap(*this, other);
|
|
}
|
|
|
|
Fusion& Fusion::operator=(const Fusion& other) {
|
|
FUSER_PERF_SCOPE("Fusion copy assign");
|
|
Fusion copy(other);
|
|
clear();
|
|
swap(*this, copy);
|
|
return *this;
|
|
}
|
|
|
|
Fusion& Fusion::operator=(Fusion&& other) noexcept {
|
|
FUSER_PERF_SCOPE("Fusion move assign");
|
|
clear();
|
|
swap(*this, other);
|
|
return *this;
|
|
}
|
|
|
|
Fusion::~Fusion() {
|
|
clear();
|
|
}
|
|
|
|
void Fusion::clear() noexcept {
|
|
FUSER_PERF_SCOPE("Fusion clear");
|
|
|
|
IrContainer::clear();
|
|
|
|
inputs_.clear();
|
|
outputs_.clear();
|
|
|
|
io_alias_.clear();
|
|
|
|
permuted_input_map_.clear();
|
|
permuted_output_map_.clear();
|
|
|
|
all_tv_uses_valid_ = false;
|
|
is_during_update_uses_ = false;
|
|
}
|
|
|
|
void Fusion::removeExpr(Expr* expr) {
|
|
assertInContainer(expr, "Cannot remove expr ");
|
|
// If we hit this error too frequently, we could lighten the restrictions so
|
|
// that removing something that doesn't exist simply does nothing. For now,
|
|
// we're going with the strictest model which errors.
|
|
|
|
for (auto out : expr->outputs()) {
|
|
out->setDefinition(nullptr);
|
|
}
|
|
|
|
for (auto inp : expr->inputs()) {
|
|
auto uses_copy = inp->uses();
|
|
auto it = std::find(uses_copy.begin(), uses_copy.end(), expr);
|
|
if (it != uses_copy.end()) {
|
|
uses_copy.erase(it);
|
|
inp->setUses(uses_copy);
|
|
}
|
|
}
|
|
|
|
IrContainer::removeExpr(expr);
|
|
}
|
|
|
|
void Fusion::removeVal(Val* val) {
|
|
assertInContainer(val, "Cannot remove val ");
|
|
|
|
TORCH_CHECK(
|
|
!val->isFusionInput(),
|
|
"Cannot remove val as it is an input of the fusion.");
|
|
TORCH_CHECK(
|
|
!val->isFusionOutput(),
|
|
"Cannot remove val as it is an output of the fusion.");
|
|
|
|
Expr* orig = val->definition();
|
|
if (orig != nullptr)
|
|
removeExpr(val->definition());
|
|
|
|
for (Expr* use : unordered_uses(val)) {
|
|
removeExpr(use);
|
|
}
|
|
IrContainer::removeVal(val);
|
|
}
|
|
|
|
void Fusion::addInput(Val* input) {
|
|
assertInContainer(input, "Cannot register input ");
|
|
|
|
if (input->getValType().value() == ValType::TensorView) {
|
|
auto tv = input->as<TensorView>();
|
|
tv->setMemoryType(MemoryType::Global);
|
|
}
|
|
|
|
inputs_.push_back(input);
|
|
input->setIsFusionInput(true);
|
|
|
|
all_tv_uses_valid_ = false;
|
|
}
|
|
|
|
void Fusion::addOutput(Val* output) {
|
|
assertInContainer(output, "Cannot register output ");
|
|
if (output->getValType().value() == ValType::TensorView) {
|
|
auto tv = output->as<TensorView>();
|
|
tv->setMemoryType(MemoryType::Global);
|
|
}
|
|
outputs_.push_back(output);
|
|
output->setIsFusionOutput(true);
|
|
|
|
all_tv_uses_valid_ = false;
|
|
}
|
|
|
|
void Fusion::addOutput(WelfordResult& wr) {
|
|
// Want to always make sure the avg gets added last
|
|
// since avg will be the out() value of welfordOp,
|
|
// and want to make it the top of the computeAt chain
|
|
addOutput(wr.var_sum);
|
|
addOutput(wr.n);
|
|
addOutput(wr.avg);
|
|
}
|
|
|
|
void Fusion::removeInput(Val* input) {
|
|
auto find_input = std::find(inputs_.begin(), inputs_.end(), input);
|
|
if (find_input != inputs_.end()) {
|
|
inputs_.erase(find_input);
|
|
}
|
|
input->setIsFusionInput(false);
|
|
all_tv_uses_valid_ = false;
|
|
}
|
|
|
|
void Fusion::removeOutput(Val* output) {
|
|
auto find_output = std::find(outputs_.begin(), outputs_.end(), output);
|
|
if (find_output != outputs_.end()) {
|
|
outputs_.erase(find_output);
|
|
}
|
|
output->setIsFusionOutput(false);
|
|
all_tv_uses_valid_ = false;
|
|
}
|
|
|
|
void Fusion::replaceOutput(Val* output, Val* replacement) {
|
|
auto find_output = std::find(outputs_.begin(), outputs_.end(), output);
|
|
TORCH_CHECK(find_output != outputs_.end(), "Unable to find output in Fusion");
|
|
|
|
if (find_output != outputs_.end()) {
|
|
*find_output = replacement;
|
|
|
|
if (replacement->getValType().value() == ValType::TensorView) {
|
|
replacement->setIsFusionOutput(true);
|
|
replacement->as<TensorView>()->setMemoryType(MemoryType::Global);
|
|
}
|
|
if (output->getValType().value() == ValType::TensorView) {
|
|
output->setIsFusionOutput(false);
|
|
output->as<TensorView>()->setMemoryType(MemoryType::Local);
|
|
}
|
|
resetTvUses();
|
|
}
|
|
|
|
// Temporary WAR for issue #1112
|
|
// (https://github.com/csarofeen/pytorch/issues/1112)
|
|
if (io_alias_.count(output) != 0) {
|
|
auto input = io_alias_[output];
|
|
io_alias_.erase(output);
|
|
io_alias_[replacement] = input;
|
|
}
|
|
}
|
|
|
|
std::vector<Expr*> Fusion::exprs() {
|
|
return StmtSort::getExprs(this);
|
|
}
|
|
|
|
std::vector<Val*> Fusion::inputsOf(Val* val) {
|
|
return InputsOf::output(this, val);
|
|
}
|
|
|
|
void Fusion::validateInputs() {
|
|
std::unordered_set<Val*> all_inputs;
|
|
for (Val* out : outputs()) {
|
|
for (Val* input : inputsOf(out)) {
|
|
all_inputs.insert(input);
|
|
}
|
|
}
|
|
|
|
std::unordered_set<Val*> input_dims;
|
|
auto inp_tvs = ir_utils::filterByType<TensorView>(inputs());
|
|
for (auto tv : inp_tvs) {
|
|
for (auto id : tv->getMaybeRFactorDomain()) {
|
|
input_dims.emplace(id->extent());
|
|
}
|
|
}
|
|
for (Val* input : all_inputs) {
|
|
if (!input->isConstScalar()) {
|
|
TORCH_CHECK(
|
|
input->isFusionInput() ||
|
|
// TODO: Switch:
|
|
inContainer(input),
|
|
// to: input_dims.find(input) != input_dims.end(),
|
|
// https://github.com/csarofeen/pytorch/issues/1365
|
|
"Could not figure out how ",
|
|
input->toString(),
|
|
" is generated, however it was not specified as an input.");
|
|
}
|
|
}
|
|
}
|
|
|
|
void Fusion::print() {
|
|
FUSER_PERF_SCOPE("Fusion::print");
|
|
|
|
FusionGuard fg(this);
|
|
std::cout << "\n%kernel {\n";
|
|
IrMathPrinter op_exprs(std::cout);
|
|
op_exprs.handle(this);
|
|
std::cout << "\nTransformPrinter : \n";
|
|
IrTransformPrinter t_exprs(std::cout);
|
|
t_exprs.handle(this);
|
|
std::cout << "}\n\n";
|
|
}
|
|
|
|
void Fusion::printKernel() {
|
|
FUSER_PERF_SCOPE("Fusion::printKernel");
|
|
TORCH_INTERNAL_ASSERT(
|
|
!this->isA<kir::Kernel>(),
|
|
"Cannot \"print kernel\" of a kernel container. ",
|
|
"This would require lowering during lowering.");
|
|
std::cout << codegen::generateCudaKernel(GpuLower(this).kernel());
|
|
}
|
|
|
|
void Fusion::printMath(bool from_outputs_only) {
|
|
FUSER_PERF_SCOPE("Fusion::printMath");
|
|
|
|
FusionGuard fg(this);
|
|
auto exprs_for_print = exprs();
|
|
std::cout << "Inputs:" << std::endl;
|
|
for (auto inp : inputs()) {
|
|
std::cout << " " << inp << ", " << inp->getDataType().value() << std::endl;
|
|
}
|
|
|
|
std::cout << "Outputs:" << std::endl;
|
|
for (auto out : outputs()) {
|
|
std::cout << " " << out << ", " << out->getDataType().value() << std::endl;
|
|
}
|
|
|
|
// If we want everything in the fusion, grab all values without uses to
|
|
// traverse from.
|
|
if (!from_outputs_only) {
|
|
std::vector<Val*> leaf_vals;
|
|
for (auto val : deterministic_vals()) {
|
|
if (val->uses().empty()) {
|
|
leaf_vals.push_back(val);
|
|
}
|
|
}
|
|
exprs_for_print = StmtSort::getExprs(this, leaf_vals);
|
|
}
|
|
|
|
std::cout << "\n%kernel_math {\n";
|
|
for (auto expr : exprs_for_print) {
|
|
std::cout << expr;
|
|
}
|
|
std::cout << "}\n\n";
|
|
}
|
|
|
|
void Fusion::printTransforms() {
|
|
FUSER_PERF_SCOPE("Fusion::printTransforms");
|
|
|
|
FusionGuard fg(this);
|
|
IrTransformPrinter t_exprs(std::cout);
|
|
t_exprs.handle(this);
|
|
}
|
|
|
|
void Fusion::registerVal(Val* val) {
|
|
if (inContainer(val)) {
|
|
return;
|
|
}
|
|
|
|
if (val->fusion()) {
|
|
TORCH_CHECK(
|
|
val->fusion() == this, val, " was not found in the active fusion.");
|
|
}
|
|
|
|
IrContainer::registerVal(val);
|
|
}
|
|
|
|
void Fusion::registerExpr(Expr* expr) {
|
|
if (inContainer(expr)) {
|
|
return;
|
|
}
|
|
|
|
if (expr->fusion()) {
|
|
TORCH_CHECK(
|
|
expr->fusion() == this, expr, " was not found in the active fusion.");
|
|
}
|
|
|
|
IrContainer::registerExpr(expr);
|
|
|
|
bool has_tv = false;
|
|
|
|
for (Val* input : expr->inputs()) {
|
|
has_tv = has_tv || input->isA<TensorView>();
|
|
assertInContainer(input, "Input to expr is invalid, ");
|
|
auto uses_copy = input->uses();
|
|
if (std::find(uses_copy.begin(), uses_copy.end(), expr) ==
|
|
uses_copy.end()) {
|
|
uses_copy.push_back(expr);
|
|
input->setUses(uses_copy);
|
|
}
|
|
}
|
|
|
|
// Kernel is the only container type that is non-ssa. This is mainly (maybe
|
|
// only) because of initialization expressions which would overwrite tensor
|
|
// view definitions.
|
|
bool is_ssa = !this->isA<kir::Kernel>();
|
|
|
|
for (Val* output : expr->outputs()) {
|
|
has_tv = has_tv || output->isA<TensorView>();
|
|
assertInContainer(output, "Output to expr is invalid, ");
|
|
if (output->definition() != nullptr && is_ssa) {
|
|
removeExpr(output->definition());
|
|
}
|
|
if (is_ssa || (!is_ssa && output->definition() == nullptr)) {
|
|
output->setDefinition(expr);
|
|
}
|
|
}
|
|
|
|
if (has_tv) {
|
|
resetTvUses();
|
|
}
|
|
}
|
|
|
|
void Fusion::resetTvUses() {
|
|
FUSER_PERF_SCOPE("Fusion::resetTvUses");
|
|
is_during_update_uses_ = true;
|
|
|
|
// getExprs only uses definition, so even if we've modified uses already to
|
|
// remove dead exprs, this could reinsert them. getExprs is also boundeds by
|
|
// inputs as registered inputs will return nullptr as their definition.
|
|
const auto all_tvs = ir_utils::filterByType<TensorView>(vals_);
|
|
const auto used_exprs = StmtSort::getExprs(this);
|
|
|
|
for (auto tv : all_tvs) {
|
|
tv->setUses({});
|
|
}
|
|
|
|
// Same as in register expr
|
|
for (auto expr : used_exprs) {
|
|
for (Val* input : expr->inputs()) {
|
|
auto uses_copy = input->uses();
|
|
if (std::find(uses_copy.begin(), uses_copy.end(), expr) ==
|
|
uses_copy.end()) {
|
|
uses_copy.push_back(expr);
|
|
input->setUses(uses_copy);
|
|
}
|
|
}
|
|
}
|
|
|
|
all_tv_uses_valid_ = true;
|
|
is_during_update_uses_ = false;
|
|
}
|
|
|
|
std::vector<Val*> Fusion::usedMathVals() {
|
|
// Note that using fusion->inputs() as the argument for the first
|
|
// parameter of getAllValsBetween does not grab all used vals as
|
|
// there can be vals that are created inside a fusion without using
|
|
// anything from inputs. See, for example, tv0 in the
|
|
// FusionOuterSplit test.
|
|
const auto inputs = InputsOf::outputs(this, outputs());
|
|
auto used_math_vals = DependencyCheck::getAllValsBetween(
|
|
{inputs.begin(), inputs.end()}, outputs());
|
|
// When an expre has multiple outputs and only some of them are
|
|
// used, the rest aren't included in used_math_vals as they are not
|
|
// used. However, we want them to be included as they must show up
|
|
// in the fusion.
|
|
std::vector<Val*> vals_to_add;
|
|
std::unordered_set<Val*> added_vals;
|
|
|
|
for (auto val : used_math_vals) {
|
|
auto def = val->definition();
|
|
if (def == nullptr || def->outputs().size() < 2) {
|
|
continue;
|
|
}
|
|
for (auto out : def->outputs()) {
|
|
if (std::find(used_math_vals.begin(), used_math_vals.end(), out) ==
|
|
used_math_vals.end()) {
|
|
if (!added_vals.count(out)) {
|
|
vals_to_add.push_back(out);
|
|
added_vals.insert(out);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
used_math_vals.insert(
|
|
used_math_vals.end(), vals_to_add.begin(), vals_to_add.end());
|
|
|
|
return used_math_vals;
|
|
}
|
|
|
|
std::unordered_set<Expr*> Fusion::unordered_uses(Val* val) const {
|
|
return std::unordered_set<Expr*>(val->uses().begin(), val->uses().end());
|
|
}
|
|
|
|
Expr* Fusion::definition(const Val* val) const {
|
|
assertInContainer(val, "Cannot detect the definition of val, ");
|
|
return val->definition();
|
|
}
|
|
|
|
// Indicate to kernel to set itself up to generate random numbers
|
|
bool Fusion::isStochastic() {
|
|
for (auto expr : exprs())
|
|
if (expr->getExprType() == ExprType::UnaryOp)
|
|
if (expr->as<UnaryOp>()->getUnaryOpType() == UnaryOpType::RandLike)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
std::vector<Val*> Fusion::getTerminatingOutputs() {
|
|
FUSER_PERF_SCOPE("getTerminatingOutputs");
|
|
|
|
auto is_reachable_to_output = [](Val* val) {
|
|
// traverse to consumers of val and see if there is an output
|
|
std::deque<Val*> consumers;
|
|
for (auto use : val->uses()) {
|
|
for (auto consumer : use->outputs()) {
|
|
consumers.push_back(consumer);
|
|
}
|
|
}
|
|
while (!consumers.empty()) {
|
|
auto consumer = consumers.back();
|
|
consumers.pop_back();
|
|
if (consumer->isFusionOutput()) {
|
|
return true;
|
|
}
|
|
// consumer is not an output; proceed to its consumers
|
|
for (auto use : consumer->uses()) {
|
|
for (auto consumer_of_consumer : use->outputs()) {
|
|
consumers.push_back(consumer_of_consumer);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
std::vector<Val*> terminating_outputs;
|
|
|
|
for (auto out : outputs()) {
|
|
// If there is another output reachable from this output, it's not
|
|
// terminating.
|
|
if (is_reachable_to_output(out)) {
|
|
continue;
|
|
}
|
|
terminating_outputs.push_back(out);
|
|
}
|
|
|
|
return terminating_outputs;
|
|
}
|
|
|
|
bool Fusion::isAliasCompatible(Val* left, Val* right) {
|
|
// Nullptr check
|
|
if (left == nullptr || right == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
// DataType check
|
|
if (!left->getDataType().has_value() || !right->getDataType().has_value() ||
|
|
left->getDataType().value() != right->getDataType().value()) {
|
|
return false;
|
|
}
|
|
|
|
// ValType check
|
|
if (!left->getValType().has_value() || !right->getValType().has_value() ||
|
|
left->getValType().value() != right->getValType().value()) {
|
|
return false;
|
|
}
|
|
|
|
// Check same number of dimensions if both values are TensorViews
|
|
if (ir_utils::isTV(left) && ir_utils::isTV(right)) {
|
|
return left->as<TensorView>()->nDims() == right->as<TensorView>()->nDims();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Fusion::aliasOutputToInput(Val* output, Val* input) {
|
|
TORCH_INTERNAL_ASSERT(
|
|
isAliasCompatible(input, output),
|
|
"The input and output values are not alias-compatible.");
|
|
io_alias_[output] = input;
|
|
}
|
|
|
|
Val* Fusion::getOutputAlias(Val* output) {
|
|
auto search = io_alias_.find(output);
|
|
if (search != io_alias_.end()) {
|
|
return search->second;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::unordered_set<int> Fusion::getOutputAliasIndices() const {
|
|
if (io_alias_.empty()) {
|
|
return {};
|
|
}
|
|
|
|
std::unordered_set<int> alias_indices;
|
|
|
|
for (const auto i : c10::irange(outputs_.size())) {
|
|
if (io_alias_.count(outputs_[i]) != 0) {
|
|
alias_indices.insert(i);
|
|
}
|
|
}
|
|
return alias_indices;
|
|
}
|
|
|
|
std::vector<std::pair<int, int>> Fusion::getInputAliasIndices() const {
|
|
if (io_alias_.empty()) {
|
|
return {};
|
|
}
|
|
|
|
std::vector<std::pair<int, int>> alias_indices;
|
|
for (const auto i : c10::irange(outputs_.size())) {
|
|
if (io_alias_.count(outputs_[i]) != 0) {
|
|
bool found = false;
|
|
for (const auto j : c10::irange(inputs_.size())) {
|
|
if (io_alias_.at(outputs_[i]) == inputs_[j]) {
|
|
alias_indices.emplace_back(i, j);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
TORCH_INTERNAL_ASSERT(
|
|
found,
|
|
"io_alias_ mapping failure, alias output is not present in inputs");
|
|
}
|
|
}
|
|
// can't assert here, we could have segmented fusion where not all alias
|
|
// outputs are present
|
|
|
|
return alias_indices;
|
|
}
|
|
|
|
} // namespace cuda
|
|
} // namespace fuser
|
|
} // namespace jit
|
|
} // namespace torch
|