Add ATen Dialect (#16)

This patch adds a dialect intended to be used as a frontend dialect
to facilitate lowering from "A Tensor Library" in torch/pytorch.

This patch includes several passes that are useful in conjuction with the
dialect:

--aten-layer-name: Generates layer names for each operation, which are not
  present in the original pytorch.
--aten-to-std: Lower the ATen dialect into standard dialect function calls.
--return-elimination-pass: convert functions (primarily the toplevel function)
  to pass return values by reference.  This simplifies pytorch integration.
--aten-op-report: generate a textual report about the model
--liveness-report

Future patches will implement actual integration with the pytorch jit to
intercept and generates MLIR in this dialect, then lower the resulting MLIR
into function calls through aten-layer-name -> aten-to-std ->
return-elimination -> std-to-llvm. The result would then jitted using the LLVM
jit, linked against a runtime library which makes calls back into pytorch to
implement all the layers.

Co-authored-by: Jeff Fifield <jeff.fifield@xilinx.com>

Co-authored-by: Jeff Fifield <jeff.fifield@xilinx.com>
pull/31/head
stephenneuendorffer 2020-08-12 19:28:04 -07:00 committed by GitHub
parent 14f614396d
commit bb668e6e26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 4560 additions and 0 deletions

View File

@ -0,0 +1,160 @@
//===- ATen.td ---------------------------------------------*- tablegen -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
include "mlir/IR/OpBase.td"
#ifndef ATEN_OPS
#define ATEN_OPS
include "mlir/Interfaces/SideEffectInterfaces.td"
include "npcomp/Dialect/ATen/ATenOpInterface.td"
def aten_Dialect : Dialect {
let name = "aten";
let cppNamespace = "aten";
}
// TODO: convert to "let results =" style
class aten_Op<string mnemonic, list<OpTrait> traits = [StatisticsOpInterface]> :
Op<aten_Dialect, mnemonic, traits>;
// Most ops are automatically generated from pytorch specs.
include "npcomp/Dialect/ATen/ATenOps.td"
def aten_BatchNormOp: aten_Op<"batch_norm", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor:$output, AnyTensor:$save_mean, AnyTensor:$save_invstd)> {
let arguments = (
ins AnyType:$arg0,
AnyType:$arg1,
AnyType:$arg2,
AnyType:$arg3,
AnyType:$arg4,
AnyType:$arg5,
AnyType:$arg6,
AnyType:$arg7,
AnyType:$arg8
);
let summary = "BatchNorm operator";
let description = [{
BatchNorm operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
// We have list constants, which come out of pytorch. Represent them using
// our own constant-like type, which gets lowered to std_ConstantOp later.
def aten_ConstantOp: aten_Op<"constant", [NoSideEffect]>,
Results<(outs AnyType)> {
let summary = "Constant operator";
let description = [{
Constant operator
}];
}
// Our jit library only supports 6 argument convolutions, rather than 9
// arguments supported by pytorch. This operation allows us to represent this
// limitation temporarily.
def aten_ConvolutionOp: aten_Op<"_convolution", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$input,
AnyTensor:$weight,
AnyTensor:$bias,
AnyType:$stride,
AnyType:$padding,
AnyType:$dilation
);
let summary = "Convolution operator";
let description = [{
Convolution operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
uint64_t getOperandTransferVolume(unsigned int idx, bool read);
uint64_t getResultTransferVolume(unsigned int idx, bool read);
}];
}
// Our jit library only supports 6 argument convolutions, rather than 9
// arguments supported by pytorch. This operation allows us to represent this
// limitation temporarily.
def aten_ConvolutionBackwardOp: aten_Op<"_convolution_backward", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor:$dx, AnyTensor:$dw, AnyTensor:$db)> {
let arguments = (
ins AnyTensor:$grad_output,
AnyTensor:$input,
AnyTensor:$weight,
AnyType:$stride,
AnyType:$padding,
AnyType:$dilation
);
let summary = "ConvolutionBackward operator";
let description = [{
ConvolutionBackward operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_FlattenOp: aten_Op<"flatten", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyType:$arg0,
AnyType:$arg1,
AnyType:$arg2
);
let summary = "Flatten operator";
let description = [{
Flatten operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_MaxPool2dOp: aten_Op<"max_pool2d", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyType:$arg0,
AnyType:$arg1,
AnyType:$arg2,
AnyType:$arg3,
AnyType:$arg4,
AnyType:$arg5
);
let summary = "MaxPool2d operator";
let description = [{
MaxPool2d operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_TypeCastOp : aten_Op<"type_cast", [NoSideEffect]>,
Results<(outs AnyType)> {
let summary = "TypeCast operator";
let arguments = (
ins AnyType:$x
);
}
#endif

View File

@ -0,0 +1,131 @@
//===- ATenDialect.h --------------------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef NPCOMP_DIALECT_ATEN_DIALECT_H
#define NPCOMP_DIALECT_ATEN_DIALECT_H
#include "mlir/IR/Builders.h"
#include "mlir/IR/Dialect.h"
#include "mlir/IR/Function.h"
#include "mlir/IR/OpDefinition.h"
#include "mlir/IR/OpImplementation.h"
#include "mlir/IR/StandardTypes.h"
#include "mlir/IR/TypeSupport.h"
#include "mlir/IR/Types.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include <map>
namespace mlir {
namespace NPCOMP {
namespace aten {
/// The ATenDialect models 'A Tensor library' from Pytorch. The intention
/// is to provide an abstraction which is isomorphic with datastructures
/// returned from the pytorch jit, enabling integration with Pytorch models.
/// Most of the actual operation definitions in tablegen are themselves
/// generated from C APIs exported by Pytorch.
class ATenDialect : public mlir::Dialect {
public:
explicit ATenDialect(mlir::MLIRContext *ctx);
static StringRef getDialectNamespace() { return "aten"; }
/// Parse a type registered to this dialect. Overridding this method is
/// required for dialects that have custom types.
/// Technically this is only needed to be able to round-trip to textual IR.
mlir::Type parseType(DialectAsmParser &parser) const override;
/// Print a type registered to this dialect. Overridding this method is
/// only required for dialects that have custom types.
/// Technically this is only needed to be able to round-trip to textual IR.
void printType(mlir::Type type, DialectAsmPrinter &os) const override;
};
////////////////////////////////////////////////////////////////////////////////
/////////////////////// Custom Types for the Dialect ///////////////////////////
////////////////////////////////////////////////////////////////////////////////
namespace detail {
struct ATenListTypeStorage;
}
/// LLVM-style RTTI: one entry per subclass to allow dyn_cast/isa.
enum ATenTypeKind {
// The enum starts at the range reserved for this dialect.
ATEN_TYPE = mlir::Type::FIRST_PRIVATE_EXPERIMENTAL_0_TYPE,
ATEN_LIST,
};
/// A variadic list of arguments in ATen
class ATenListType : public mlir::Type::TypeBase<ATenListType, mlir::Type,
detail::ATenListTypeStorage> {
public:
using Base::Base;
/// Return the type of individual elements in the array.
mlir::Type getElementType();
/// Get the unique instance of this Type from the context.
static ATenListType get(mlir::Type elementType);
/// Support method to enable LLVM-style RTTI type casting.
static bool kindof(unsigned kind) { return kind == ATenTypeKind::ATEN_LIST; }
};
////////////////////////////////////////////////////////////////////////////////
//////////////////// Custom Operations for the Dialect /////////////////////////
////////////////////////////////////////////////////////////////////////////////
namespace {
// Return the tensor volume (i.e., the number of elements) of the given shaped
// type. If the type does not have a rank, return 1. If the type doesn't
// have a static shape, return 0.
uint64_t getTensorVolume(const ShapedType ty) {
if (!ty.hasRank())
return 1;
if (!ty.hasStaticShape())
return 0;
uint64_t volume = 1;
for (auto &d : ty.getShape())
volume *= d;
return volume;
}
// Return the tensor volume (i.e., the number of elements) of the given type.
// If the type doesn't have a shape, return 1. If the type is shaped, but
// does not have a rank, return 1. If the type is shaped, but doesn't have a
// static shape, return 0.
uint64_t getTensorVolume(const Type ty) {
if (auto t = ty.dyn_cast<ShapedType>()) {
return getTensorVolume(t);
} else {
return 1;
}
}
} // namespace
} // namespace aten
} // namespace NPCOMP
} // namespace mlir
#include "npcomp/Dialect/ATen/ATenOpInterfaces.h"
namespace mlir {
namespace NPCOMP {
namespace aten {
// include TableGen generated Op definitions
#define GET_OP_CLASSES
#include "npcomp/Dialect/ATen/ATen.h.inc"
} // namespace aten
} // namespace NPCOMP
} // namespace mlir
#endif

View File

@ -0,0 +1,29 @@
//===- ATenLayerNamePass.h --------------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef NPCOMP_DIALECT_ATEN_LAYERNAMEPASS_H
#define NPCOMP_DIALECT_ATEN_LAYERNAMEPASS_H
#include <memory>
namespace mlir {
class Pass;
} // namespace mlir
namespace mlir {
namespace NPCOMP {
namespace aten {
std::unique_ptr<mlir::Pass> createATenLayerNamePass();
void registerATenLayerNamePass();
} // namespace aten
} // namespace NPCOMP
} // namespace mlir
#endif

View File

@ -0,0 +1,29 @@
//===- ATenLoweringPass.h ---------------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef NPCOMP_DIALECT_ATEN_LOWERING_H
#define NPCOMP_DIALECT_ATEN_LOWERING_H
#include <memory>
namespace mlir {
class Pass;
class DialectConversion;
} // namespace mlir
namespace mlir {
namespace NPCOMP {
namespace aten {
std::unique_ptr<mlir::Pass> createATenLoweringPass();
void registerATenLoweringPass();
} // namespace aten
} // namespace NPCOMP
} // namespace mlir
#endif // NPCOMP_DIALECT_ATEN_LOWERING_H

View File

@ -0,0 +1,70 @@
//===- ATenOpInterface.td ----------------------------------*- tablegen -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
include "mlir/IR/OpBase.td"
#ifndef ATEN_OP_INTERFACES
#define ATEN_OP_INTERFACES
def StatisticsOpInterface : OpInterface<"StatisticsOpInterface"> {
let description = [{
This interface allows ops to expose a static operation profile,
describing the computational behavior of their function.
}];
let methods = [
InterfaceMethod<[{
Return statistics about the compute requirements of an op. The return
value maps an arbitrary set of statistic names to an integer value.
Users are currently expected to accept any statistic names and statistic
names are arbitrary for different operations. In the future this
interface could be expanded and standardized.
}],
"std::map<std::string, uint64_t>", "getStatistics"
>,
InterfaceMethod<[{
Return memory transfer requirements of the operation for the
operand with the given index.
}],
"uint64_t", "getOperandTransferVolume",
(ins "unsigned int":$idx, "bool":$read), /*methodBody=*/[{}], [{
ConcreteOp *op = static_cast<ConcreteOp *>(this);
if (!read) return 0;
Value v = op->getODSOperands(idx).front();
auto ty = v.getType();
return mlir::NPCOMP::aten::getTensorVolume(ty);
}]>,
InterfaceMethod<[{
Return memory transfer requirements of the operation for the
result with the given index.
}],
"uint64_t", "getResultTransferVolume",
(ins "unsigned int":$idx, "bool":$write), /*methodBody=*/[{}], [{
ConcreteOp *op = static_cast<ConcreteOp *>(this);
if (!write) return 0;
Value v = op->getODSResults(idx).front();
auto ty = v.getType();
return mlir::NPCOMP::aten::getTensorVolume(ty);
}]>,
];
}
def AnyScalarOrTensor : TypeConstraint<Or<[AnySignlessInteger.predicate,
AnyFloat.predicate,
AnyTensor.predicate]>,
"scalar-or-tensor">;
def AnyScalar : TypeConstraint<Or<[AnySignlessInteger.predicate,
AnyFloat.predicate]>,
"scalar">;
#endif

View File

@ -0,0 +1,20 @@
//===- ATenOpInterfaces.h ---------------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef NPCOMP_DIALECT_ATEN_OPINTERFACES_H
#define NPCOMP_DIALECT_ATEN_OPINTERFACES_H
#include "mlir/IR/Types.h"
namespace mlir {
namespace NPCOMP {
#include "npcomp/Dialect/ATen/ATenOpInterfaces.h.inc"
} // namespace aten
} // namespace NPCOMP
#endif

View File

@ -0,0 +1,32 @@
//===- ATenOpReport.h -------------------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef NPCOMP_DIALECT_ATEN_OPREPORT_H
#define NPCOMP_DIALECT_ATEN_OPREPORT_H
#include <memory>
namespace mlir {
class Pass;
} // namespace mlir
namespace mlir {
namespace NPCOMP {
namespace aten {
// Generate report on standard error.
std::unique_ptr<mlir::Pass> createATenOpReportPass();
// Return the report in the given output string.
std::unique_ptr<mlir::Pass> createATenOpReportPass(std::string &output);
void registerATenOpReportPass();
} // namespace aten
} // namespace NPCOMP
} // namespace mlir
#endif

View File

@ -0,0 +1,276 @@
//===- ATenOpStatisticsUtils.h ----------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef NPCOMP_DIALECT_ATEN_OPSTATISTICSUTILS_H
#define NPCOMP_DIALECT_ATEN_OPSTATISTICSUTILS_H
#include "npcomp/Dialect/ATen/ATenDialect.h"
#include "llvm/Support/Debug.h"
#include "mlir/IR/StandardTypes.h"
#include "mlir/IR/Types.h"
#define DEBUG_TYPE "aten-op-stats"
/// This file generally contains utility methods that factor common code
/// out from operations that implement Statisticsopinterface.
namespace mlir {
namespace NPCOMP {
namespace aten {
// Return the op statistics for conv2d-like operations.
template <class T> std::map<std::string, uint64_t> getConv2dStatistics(T *o, uint64_t groups) {
std::map<std::string, uint64_t> toReturn;
TensorType resultTy = o->getResult().getType().template cast<TensorType>();
TensorType inputTy = o->input().getType().template cast<TensorType>();
TensorType weightTy = o->weight().getType().template cast<TensorType>();
TensorType biasTy = o->bias().getType().template cast<TensorType>();
uint64_t ofm_volume = getTensorVolume(resultTy);
uint64_t ofm_depth = resultTy.getShape()[1];
uint64_t ifm_depth = inputTy.getShape()[1];
uint64_t kernel_height = weightTy.getShape()[2];
uint64_t kernel_width = weightTy.getShape()[3];
// Number of forward MACs per pixel =
// kernel_width * kernel_height * ifm_depth / groups
uint64_t MACs_per_OFM = (ifm_depth / groups) * kernel_height * kernel_width;
uint64_t total_MACs = ofm_volume * MACs_per_OFM;
uint64_t ifm_volume = getTensorVolume(inputTy);
uint64_t weight_volume = getTensorVolume(weightTy);
uint64_t bias_volume = getTensorVolume(biasTy);
// Should be gated on whether there is bias at all
toReturn["ops:+"] = ofm_volume;
toReturn["ops:MAC"] = total_MACs;
toReturn["operand:0:activation_in"] = ifm_volume;
toReturn["result:0:activation_out"] = ofm_volume;
toReturn["operand:1:parameters_in:weight"] = weight_volume;
toReturn["operand:2:parameters_in:bias"] = bias_volume;
toReturn["reads"] = weight_volume + bias_volume + ifm_volume;
toReturn["writes"] = ofm_volume;
return toReturn;
}
// Return the op statistics for conv2dBackward-like operations.
template<typename T>
std::map<std::string, uint64_t> getConv2dBackwardStatistics(T op, uint64_t groups) {
std::map<std::string, uint64_t> toReturn;
TensorType dx_out_resultTy = op.getResult(0).getType().template cast<TensorType>();
uint64_t dx_out_volume = getTensorVolume(dx_out_resultTy);
TensorType weightTy = op.getOperand(2).getType().template cast<TensorType>();
uint64_t weight_volume = getTensorVolume(weightTy);
uint64_t loss_in_depth = weightTy.getShape()[0];
uint64_t kernel_width = weightTy.getShape()[2];
uint64_t kernel_height = weightTy.getShape()[3];
uint64_t MACs_per_loss =
(loss_in_depth / groups) * kernel_height * kernel_width;
uint64_t total_MACs = dx_out_volume * MACs_per_loss;
TensorType ifmTy = op.getOperand(1).getType().template cast<TensorType>();
uint64_t ifm_volume = getTensorVolume(ifmTy);
auto ifm_shape = ifmTy.getShape();
uint64_t ifm_bwh = ifm_shape[0] * ifm_shape[2] *
ifm_shape[3]; // Batch * height * width: the depth is in
// the weight shape already
total_MACs += ifm_bwh * weight_volume;
TensorType dx_inTy = op.getOperand(0).getType().template cast<TensorType>();
uint64_t dx_in_volume = getTensorVolume(dx_inTy);
toReturn["ops:+"] = dx_in_volume;
// Reads: Conv_backward reads 3 tensors: the loss in, the activation in and
// the transposed weights
toReturn["reads"] = dx_in_volume + ifm_volume + weight_volume;
// Writes: Conv_backward writes 3 tensors: the loss out, gradients for the
// weights, and gradients for the biases
TensorType biasTy = op.getResult(2).getType().template cast<TensorType>();
uint64_t bias_volume = getTensorVolume(biasTy);
toReturn["writes"] = dx_out_volume + weight_volume + bias_volume;
toReturn["ops:MAC"] = total_MACs;
toReturn["operand:0:activation_in"] = dx_in_volume;
toReturn["operand:1:activation_in"] = ifm_volume;
toReturn["operand:2:parameters_in:weight"] = weight_volume;
toReturn["result:0:grad:dx"] = dx_out_volume;
toReturn["result:1:grad:dw"] = weight_volume;
toReturn["result:2:grad:db"] = bias_volume;
return toReturn;
}
// Return a model of the number of bytes needed to represent the operand of
// the given convolution-like operation with the given index. The shape is
// assumed to be in NCHW order with a simple tiled model of data reuse. TODO:
// convert this to a target-specific interface.
template <class T>
uint64_t getConv2dOperandTransferVolume(T *o, unsigned int idx, bool read) {
if (!read)
return 0;
double vol = getTensorVolume(o->getOperand(idx).getType());
TensorType inputTy = o->input().getType().template cast<TensorType>();
TensorType weightTy = o->weight().getType().template cast<TensorType>();
TensorType resultTy = o->getResult().getType().template cast<TensorType>();
float filter_width = weightTy.getShape()[2];
float filter_height = weightTy.getShape()[3];
float batch_sw = inputTy.getShape()[0];
float ifm_depth_sw = inputTy.getShape()[1];
float ih = inputTy.getShape()[2];
float iw = inputTy.getShape()[3];
float ofm_depth_sw = resultTy.getShape()[1];
const float batch_hw = 4;
const float ifm_depth_hw = 32;
const float ofm_depth_hw = 32;
const float ifm_tile_height = 4;
const float ifm_tile_width = 4;
const float ofm_tile_height = 4;
const float ofm_tile_width = 4;
float ifm_aperture = ifm_tile_height - ceilf(filter_height / 2.0f);
float ifm_overlap = ceilf(filter_height / 2.0f);
float bl = ceilf(batch_sw / batch_hw);
float ol = ceilf(ofm_depth_sw / ofm_depth_hw);
float il = ceilf(ifm_depth_sw / ifm_depth_hw);
float ifm_overhead = 1.0f;
float weight_overhead = 1.0f;
if (filter_width > 1) {
ifm_overhead =
ol * ifm_tile_height * ((ih - ifm_overlap) / (ih * ifm_aperture));
weight_overhead = bl;
} else {
ifm_overhead = ol;
}
if (idx == 0) {
LLVM_DEBUG(llvm::outs() << "ifm_overhead:" << ifm_overhead << "\n");
return vol * ifm_overhead;
}
if (idx == 1) {
LLVM_DEBUG(llvm::outs() << "weight_overhead:" << weight_overhead << "\n");
return vol * weight_overhead;
}
return vol;
}
// Return a model of the number of bytes needed to represent the result of
// the given convolution-like operation with the given index. The shape is
// assumed to be in NCHW order with a simple tiled model of data reuse. TODO:
// convert this to a target-specific interface.
template <class T>
uint64_t getConv2dResultTransferVolume(T *o, unsigned int idx, bool write) {
TensorType inputTy = o->input().getType().template cast<TensorType>();
TensorType resultTy = o->getResult().getType().template cast<TensorType>();
TensorType weightTy = o->weight().getType().template cast<TensorType>();
float filter_width = weightTy.getShape()[2];
// float filter_height = weightTy.getShape()[3];
float ifm_depth_sw = inputTy.getShape()[1];
const float ifm_depth_hw = 32;
float il = ceilf(ifm_depth_sw / ifm_depth_hw);
float write_output_overhead = 1.0f;
float read_output_cost = 1.0f;
if (filter_width > 1) {
write_output_overhead = il;
read_output_cost = il;
}
double vol = getTensorVolume(resultTy);
if (write) {
LLVM_DEBUG(llvm::outs()
<< "write_output_overhead:" << write_output_overhead << "\n");
return vol * write_output_overhead;
} else {
LLVM_DEBUG(llvm::outs() << "read_output_cost:" << read_output_cost << "\n");
return vol * read_output_cost;
}
}
// Return the op statistics for matrixmultiply-like operations.
template<typename T>
std::map<std::string, uint64_t> getMMOpStatistics(T op) {
std::map<std::string, uint64_t> toReturn;
TensorType resultTy = op.getResult().getType().template cast<TensorType>();
uint64_t ofm_volume = getTensorVolume(resultTy);
// Use the weight tensor to find the number of input neurons
TensorType lossTy = op.getOperand(0).getType().template cast<TensorType>();
TensorType weightTy = op.getOperand(1).getType().template cast<TensorType>();
uint64_t num_input_neurons = weightTy.getShape()[0];
uint64_t total_MACs = ofm_volume * num_input_neurons;
toReturn["ops:MAC"] = total_MACs;
uint64_t loss_in_volume = getTensorVolume(lossTy);
uint64_t weight_volume = getTensorVolume(weightTy);
toReturn["reads"] = loss_in_volume + weight_volume;
toReturn["writes"] = ofm_volume;
toReturn["operand:0:activation_in"] = loss_in_volume;
toReturn["operand:1:activation_in"] = weight_volume;
toReturn["result:0:activation_out"] = ofm_volume;
return toReturn;
}
// Return the op statistics for ReLU-like operations.
template <typename T>
std::map<std::string, uint64_t> getReLUOpStatistics(T op) {
std::map<std::string, uint64_t> toReturn;
TensorType inputTy = op.getOperand().getType().template cast<TensorType>();
TensorType resultTy = op.getResult().getType().template cast<TensorType>();
uint64_t in_volume = getTensorVolume(inputTy);
uint64_t out_volume = getTensorVolume(resultTy);
toReturn["operand:0:activation_in"] = in_volume;
toReturn["result:0:activation_out"] = out_volume;
toReturn["reads"] = in_volume;
toReturn["writes"] = out_volume;
toReturn["ops:>"] = out_volume;
return toReturn;
}
} // namespace aten
} // namespace NPCOMP
} // namespace mlir
#endif

View File

@ -0,0 +1,733 @@
// This file is (mostly) automatically generated. Please do not edit.
//===- ATenOps.td ------------------------------------------*- tablegen -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef ATEN_OP_DEFS
#define ATEN_OP_DEFS
def aten_AddOp: aten_Op<"add", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyTensor:$other,
AnyScalar:$alpha
);
let summary = "aten add operator";
let description = [{
AddOp
aten add operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_AddUnderOp: aten_Op<"add_", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyTensor:$other,
AnyScalar:$alpha
);
let summary = "aten add_ operator";
let description = [{
AddUnderOp
aten add_ operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_AsStridedOp: aten_Op<"as_strided", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyType:$size,
AnyType:$stride
);
let summary = "aten as_strided operator";
let description = [{
AsStridedOp
aten as_strided operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_ConvolutionOverrideableOp: aten_Op<"convolution_overrideable", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$input,
AnyTensor:$weight,
AnyTensor:$bias,
AnyType:$stride,
AnyType:$padding,
AnyType:$dilation,
AnyScalar:$transposed,
AnyType:$output_padding,
AnyScalar:$groups
);
let summary = "aten convolution_overrideable operator";
let description = [{
ConvolutionOverrideableOp
aten convolution_overrideable operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_ConvolutionBackwardOverrideableOp: aten_Op<"convolution_backward_overrideable", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor, AnyTensor, AnyTensor)> {
let arguments = (
ins AnyTensor:$grad_output,
AnyTensor:$input,
AnyTensor:$weight,
AnyType:$stride,
AnyType:$padding,
AnyType:$dilation,
AnyScalar:$transposed,
AnyType:$output_padding,
AnyScalar:$groups
);
let summary = "aten convolution_backward_overrideable operator";
let description = [{
ConvolutionBackwardOverrideableOp
aten convolution_backward_overrideable operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_DivOp: aten_Op<"div", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyTensor:$other
);
let summary = "aten div operator";
let description = [{
DivOp
aten div operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_DivUnderOp: aten_Op<"div_", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyTensor:$other
);
let summary = "aten div_ operator";
let description = [{
DivUnderOp
aten div_ operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_ExpandOp: aten_Op<"expand", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyType:$size,
AnyScalar:$implicit
);
let summary = "aten expand operator";
let description = [{
ExpandOp
aten expand operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_LogSoftmaxOp: aten_Op<"_log_softmax", [NoSideEffect]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyScalar:$dim,
AnyScalar:$half_to_float
);
let summary = "aten _log_softmax operator";
let description = [{
LogSoftmaxOp
aten _log_softmax operator
}];
}
def aten_LogSoftmaxBackwardDataOp: aten_Op<"_log_softmax_backward_data", [NoSideEffect]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$grad_output,
AnyTensor:$output,
AnyScalar:$dim,
AnyTensor:$self
);
let summary = "aten _log_softmax_backward_data operator";
let description = [{
LogSoftmaxBackwardDataOp
aten _log_softmax_backward_data operator
}];
}
def aten_MeanOp: aten_Op<"mean", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self
);
let summary = "aten mean operator";
let description = [{
MeanOp
aten mean operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_MmOp: aten_Op<"mm", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyTensor:$mat2
);
let summary = "aten mm operator";
let description = [{
MmOp
aten mm operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_MulOp: aten_Op<"mul", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyTensor:$other
);
let summary = "aten mul operator";
let description = [{
MulOp
aten mul operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_MulUnderOp: aten_Op<"mul_", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyTensor:$other
);
let summary = "aten mul_ operator";
let description = [{
MulUnderOp
aten mul_ operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_NativeBatchNormOp: aten_Op<"native_batch_norm", [NoSideEffect, StatisticsOpInterface]>,
// FIXME: not quite automatically generated names
Results<(outs AnyTensor:$output, AnyTensor:$save_mean, AnyTensor:$save_invstd)> {
let arguments = (
ins AnyTensor:$input,
AnyTensor:$weight,
AnyTensor:$bias,
AnyTensor:$running_mean,
AnyTensor:$running_var,
AnyScalar:$training,
AnyScalar:$momentum,
AnyScalar:$eps
);
let summary = "aten native_batch_norm operator";
let description = [{
NativeBatchNormOp
aten native_batch_norm operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_NativeBatchNormBackwardOp: aten_Op<"native_batch_norm_backward", [NoSideEffect, StatisticsOpInterface]>,
// FIXME: not quite automatically generated
Results<(outs AnyTensor:$dx, AnyTensor:$dm, AnyTensor:$dv)> {
let arguments = (
ins AnyTensor:$grad_out,
AnyTensor:$input,
AnyTensor:$weight,
AnyTensor:$running_mean,
AnyTensor:$running_var,
AnyTensor:$save_mean,
AnyTensor:$save_invstd,
AnyScalar:$train,
AnyScalar:$eps
);
let summary = "aten native_batch_norm_backward operator";
let description = [{
NativeBatchNormBackwardOp
aten native_batch_norm_backward operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_NegOp: aten_Op<"neg", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self
);
let summary = "aten neg operator";
let description = [{
NegOp
aten neg operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_ReluOp: aten_Op<"relu", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self
);
let summary = "aten relu operator";
let description = [{
ReluOp
aten relu operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_ReluUnderOp: aten_Op<"relu_", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self
);
let summary = "aten relu_ operator";
let description = [{
ReluUnderOp
aten relu_ operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_SizeOp: aten_Op<"size", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyScalar:$dim
);
let summary = "aten size operator";
let description = [{
SizeOp
aten size operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_SqueezeOp: aten_Op<"squeeze", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyScalar:$dim
);
let summary = "aten squeeze operator";
let description = [{
SqueezeOp
aten squeeze operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_SumOp: aten_Op<"sum", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyType:$dim,
AnyScalar:$keepdim
);
let summary = "aten sum operator";
let description = [{
SumOp
aten sum operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_TOp: aten_Op<"t", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self
);
let summary = "aten t operator";
let description = [{
TOp
aten t operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_ThresholdBackwardOp: aten_Op<"threshold_backward", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$grad_output,
AnyTensor:$self,
AnyScalar:$threshold
);
let summary = "aten threshold_backward operator";
let description = [{
ThresholdBackwardOp
aten threshold_backward operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_UnsqueezeOp: aten_Op<"unsqueeze", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyScalar:$dim
);
let summary = "aten unsqueeze operator";
let description = [{
UnsqueezeOp
aten unsqueeze operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_SubOp: aten_Op<"sub", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyTensor:$other,
AnyScalar:$alpha
);
let summary = "aten sub operator";
let description = [{
SubOp
aten sub operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_SubUnderOp: aten_Op<"sub_", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyTensor:$other,
AnyScalar:$alpha
);
let summary = "aten sub_ operator";
let description = [{
SubUnderOp
aten sub_ operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_AddmmOp: aten_Op<"addmm", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyTensor:$mat1,
AnyTensor:$mat2,
AnyScalar:$beta,
AnyScalar:$alpha
);
let summary = "aten addmm operator";
let description = [{
AddmmOp
aten addmm operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_ViewOp: aten_Op<"view", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyType:$size
);
let summary = "aten view operator";
let description = [{
ViewOp
aten view operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_GatherOp: aten_Op<"gather", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyScalar:$dim,
AnyTensor:$index,
AnyScalar:$sparse_grad
);
let summary = "aten gather operator";
let description = [{
GatherOp
aten gather operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_NllLossForwardOp: aten_Op<"nll_loss_forward", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor, AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyTensor:$target,
AnyTensor:$weight,
AnyScalar:$reduction,
AnyScalar:$ignore_index
);
let summary = "aten nll_loss_forward operator";
let description = [{
NllLossForwardOp
aten nll_loss_forward operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_NllLossBackwardOp: aten_Op<"nll_loss_backward", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$grad_output,
AnyTensor:$self,
AnyTensor:$target,
AnyTensor:$weight,
AnyScalar:$reduction,
AnyScalar:$ignore_index,
AnyTensor:$total_weight
);
let summary = "aten nll_loss_backward operator";
let description = [{
NllLossBackwardOp
aten nll_loss_backward operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_NllLoss2dForwardOp: aten_Op<"nll_loss2d_forward", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor, AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyTensor:$target,
AnyTensor:$weight,
AnyScalar:$reduction,
AnyScalar:$ignore_index
);
let summary = "aten nll_loss2d_forward operator";
let description = [{
NllLoss2dForwardOp
aten nll_loss2d_forward operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_NllLoss2dBackwardOp: aten_Op<"nll_loss2d_backward", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$grad_output,
AnyTensor:$self,
AnyTensor:$target,
AnyTensor:$weight,
AnyScalar:$reduction,
AnyScalar:$ignore_index,
AnyTensor:$total_weight
);
let summary = "aten nll_loss2d_backward operator";
let description = [{
NllLoss2dBackwardOp
aten nll_loss2d_backward operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_HardtanhOp: aten_Op<"hardtanh", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyScalar:$min_val,
AnyScalar:$max_val
);
let summary = "aten hardtanh operator";
let description = [{
HardtanhOp
aten hardtanh operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_HardtanhBackwardOp: aten_Op<"hardtanh_backward", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$grad_output,
AnyTensor:$self,
AnyScalar:$min_val,
AnyScalar:$max_val
);
let summary = "aten hardtanh_backward operator";
let description = [{
HardtanhBackwardOp
aten hardtanh_backward operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_HardtanhUnderOp: aten_Op<"hardtanh_", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyScalar:$min_val,
AnyScalar:$max_val
);
let summary = "aten hardtanh_ operator";
let description = [{
HardtanhUnderOp
aten hardtanh_ operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_AdaptiveAvgPool2dOp: aten_Op<"_adaptive_avg_pool2d", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyType:$output_size
);
let summary = "aten _adaptive_avg_pool2d operator";
let description = [{
AdaptiveAvgPool2dOp
aten _adaptive_avg_pool2d operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_AdaptiveAvgPool2dBackwardOp: aten_Op<"_adaptive_avg_pool2d_backward", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$grad_output,
AnyTensor:$self
);
let summary = "aten _adaptive_avg_pool2d_backward operator";
let description = [{
AdaptiveAvgPool2dBackwardOp
aten _adaptive_avg_pool2d_backward operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_MaxPool2dWithIndicesOp: aten_Op<"max_pool2d_with_indices", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor, AnyTensor)> {
let arguments = (
ins AnyTensor:$self,
AnyType:$kernel_size,
AnyType:$stride,
AnyType:$padding,
AnyType:$dilation,
AnyScalar:$ceil_mode
);
let summary = "aten max_pool2d_with_indices operator";
let description = [{
MaxPool2dWithIndicesOp
aten max_pool2d_with_indices operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
def aten_MaxPool2dWithIndicesBackwardOp: aten_Op<"max_pool2d_with_indices_backward", [NoSideEffect, StatisticsOpInterface]>,
Results<(outs AnyTensor)> {
let arguments = (
ins AnyTensor:$grad_output,
AnyTensor:$self,
AnyType:$kernel_size,
AnyType:$stride,
AnyType:$padding,
AnyType:$dilation,
AnyScalar:$ceil_mode,
AnyTensor:$indices
);
let summary = "aten max_pool2d_with_indices_backward operator";
let description = [{
MaxPool2dWithIndicesBackwardOp
aten max_pool2d_with_indices_backward operator
}];
let extraClassDeclaration = [{
std::map<std::string, uint64_t> getStatistics();
}];
}
#endif

View File

@ -0,0 +1,28 @@
//===- ATenPasses.h ---------------------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef NPCOMP_DIALECT_ATEN_PASSES_H
#define NPCOMP_DIALECT_ATEN_PASSES_H
#include "npcomp/Dialect/ATen/ATenLayerNamePass.h"
#include "npcomp/Dialect/ATen/ATenLoweringPass.h"
#include "npcomp/Dialect/ATen/ATenOpReport.h"
#include "npcomp/Dialect/ATen/ReturnEliminationPass.h"
namespace mlir {
namespace NPCOMP {
namespace aten {
// #define GEN_PASS_CLASSES
// #include "npcomp/Dialect/ATen/ATenPasses.h.inc"
void registerATenPasses();
} // namespace aten
} // namespace NPCOMP
} // namespace mlir
#endif // NPCOMP_DIALECT_ATEN_PASSES_H

View File

@ -0,0 +1,31 @@
//===- ATenToStd.h ----------------------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef NPCOMP_DIALECT_ATEN_TO_STD_H
#define NPCOMP_DIALECT_ATEN_TO_STD_H
#include "mlir/Transforms/DialectConversion.h"
namespace mlir {
namespace NPCOMP {
namespace aten {
class ATenDialect;
} // namespace aten
} // namespace NPCOMP
} // namespace mlir
namespace mlir {
void populateATenToStdPatterns(MLIRContext *context,
OwningRewritePatternList &patterns);
} // namespace mlir
#endif // NPCOMP_DIALECT_ATEN_TO_STD_H

View File

@ -0,0 +1,34 @@
//===- ATenToStd.td ----------------------------------------*- tablegen -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifdef MLIR_ATEN_TO_STD_TD
#else
#define MLIR_ATEN_TO_STD_TD
#ifdef STANDARD_OPS
#else
include "mlir/Dialect/StandardOps/IR/Ops.td"
#endif // STANDARD_OPS
#ifdef ATEN_OPS
#else
include "ATen.td"
#endif
// The pytorch convolution operator has 9 arguments, but we only have a jit
// library that supports the first six at the moment.
def : Pat<(aten_ConvolutionOverrideableOp $a1, $a2, $a3, $a4, $a5, $a6,
$a7, $a8, $a9),
(aten_ConvolutionOp $a1, $a2, $a3, $a4, $a5, $a6)>;
def : Pat<(aten_ConvolutionBackwardOverrideableOp $a1, $a2, $a3, $a4, $a5, $a6,
$a7, $a8, $a9),
(aten_ConvolutionBackwardOp $a1, $a2, $a3, $a4, $a5, $a6)>;
#endif

View File

@ -0,0 +1,17 @@
include_directories(${PROJECT_SOURCE_DIR}/dialect)
add_mlir_dialect(ATen aten)
set(LLVM_TARGET_DEFINITIONS ATen.td)
mlir_tablegen(ATenEnums.h.inc -gen-enum-decls)
mlir_tablegen(ATenEnums.cpp.inc -gen-enum-defs)
add_public_tablegen_target(MLIRATenEnumsIncGen)
set(LLVM_TARGET_DEFINITIONS ATenOpInterface.td)
mlir_tablegen(ATenOpInterfaces.h.inc -gen-op-interface-decls)
mlir_tablegen(ATenOpInterfaces.cpp.inc -gen-op-interface-defs)
add_public_tablegen_target(MLIRATenOpInterfacesIncGen)
add_dependencies(mlir-generic-headers MLIRATenOpInterfacesIncGen)
set(LLVM_TARGET_DEFINITIONS ATenToStd.td)
mlir_tablegen(ATenToStd.cpp.inc -gen-rewriters)
add_public_tablegen_target(MLIRATenToStdIncGen)

View File

@ -0,0 +1,40 @@
//===- LivenessReport.h -----------------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef NPCOMP_DIALECT_ATEN_LIVENESSREPORT_H
#define NPCOMP_DIALECT_ATEN_LIVENESSREPORT_H
#include <string>
namespace mlir {
namespace NPCOMP {
namespace aten {
struct LivenessReport {
public:
LivenessReport(mlir::ModuleOp &module) : module(module) {}
std::string generateTextReport();
std::string emitJSONReport();
llvm::DenseMap<Value, std::vector<Operation *>> &getLiveness() {
return livenessIntervals;
};
private:
void resolveLiveness();
mlir::ModuleOp &module;
llvm::DenseMap<Value, std::vector<Operation *>> livenessIntervals;
};
} // namespace aten
} // namespace NPCOMP
} // namespace mlir
#endif

View File

@ -0,0 +1,28 @@
//===- ReturnEliminationPass.h ----------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef NPCOMP_DIALECT_ATEN_RETURNELIMINATIONPASS_H
#define NPCOMP_DIALECT_ATEN_RETURNELIMINATIONPASS_H
#include <memory>
namespace mlir {
class Pass;
} // namespace mlir
namespace mlir {
namespace NPCOMP {
namespace aten {
std::unique_ptr<mlir::Pass> createReturnEliminationPass();
void registerReturnEliminationPass();
} // namespace aten
} // namespace NPCOMP
} // namespace mlir
#endif

View File

@ -1,3 +1,4 @@
add_subdirectory(ATen)
add_subdirectory(Basicpy)
add_subdirectory(Npcomprt)
add_subdirectory(Numpy)

View File

@ -43,6 +43,7 @@ add_mlir_library(NPCOMPInitAll
NPCOMPTCP
NPCOMPTCF
NPCOMPNpcomprt
NPCOMPATenDialect
NPCOMPBasicpyDialect
NPCOMPBasicpyPasses
NPCOMPNumpyDialect

View File

@ -0,0 +1,106 @@
//===- ATenDialect.cpp ------------------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "npcomp/Dialect/ATen/ATenDialect.h"
#include "mlir/IR/DialectImplementation.h"
using namespace mlir;
namespace mlir {
namespace NPCOMP {
namespace aten {
namespace detail {
/// This class holds the implementation of the ATenListType.
/// It is intended to be uniqued based on its content and owned by the context.
struct ATenListTypeStorage : public mlir::TypeStorage {
ATenListTypeStorage(Type elementType) : elementType(elementType) {}
/// The hash key used for uniquing.
using KeyTy = mlir::Type;
bool operator==(const KeyTy &key) const { return key == getElementType(); }
/// This is a factory method to create our type storage. It is only
/// invoked after looking up the type in the context using the key and not
/// finding it.
static ATenListTypeStorage *construct(mlir::TypeStorageAllocator &allocator,
const KeyTy &key) {
// Allocate the instance for the ATenListTypeStorage itself
auto *storage = allocator.allocate<ATenListTypeStorage>();
// Initialize the instance using placement new.
return new (storage) ATenListTypeStorage(key);
}
Type getElementType() const { return elementType; }
private:
Type elementType;
};
} // namespace detail
ATenListType ATenListType::get(mlir::Type elemType) {
return Base::get(elemType.getContext(), ATenTypeKind::ATEN_LIST, elemType);
}
mlir::Type ATenListType::getElementType() {
return getImpl()->getElementType();
}
mlir::Type ATenDialect::parseType(DialectAsmParser &parser) const {
Location loc = parser.getEncodedSourceLoc(parser.getNameLoc());
// All types start with an identifier that we switch on.
StringRef typeNameSpelling;
if (failed(parser.parseKeyword(&typeNameSpelling)))
return nullptr;
if (typeNameSpelling == "list") {
if (failed(parser.parseLess()))
return nullptr;
Type t;
if (failed(parser.parseType(t)))
return nullptr;
if (failed(parser.parseGreater()))
return nullptr;
return ATenListType::get(t);
}
parser.emitError(parser.getCurrentLocation(),
"Invalid ATen type '" + typeNameSpelling + "'");
return nullptr;
}
/// Print a ATenListType
void ATenDialect::printType(mlir::Type type, DialectAsmPrinter &os) const {
auto ty = type.dyn_cast<ATenListType>();
if (!ty) {
os << "unknown aten type";
return;
}
os << "list<";
os.getStream() << ty.getElementType();
os << ">";
}
ATenDialect::ATenDialect(mlir::MLIRContext *ctx) : mlir::Dialect("aten", ctx) {
addTypes<ATenListType>();
addOperations<
#define GET_OP_LIST
#include "npcomp/Dialect/ATen/ATen.cpp.inc"
>();
}
#define GET_OP_CLASSES
#include "npcomp/Dialect/ATen/ATen.cpp.inc"
} // namespace aten
#include "npcomp/Dialect/ATen/ATenOpInterfaces.cpp.inc"
} // namespace NPCOMP
} // namespace mlir

View File

@ -0,0 +1,826 @@
//===- ATenDialectOpStats.cpp -----------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "npcomp/Dialect/ATen/ATenDialect.h"
#include "npcomp/Dialect/ATen/ATenOpStatisticsUtils.h"
#include "llvm/Support/Debug.h"
#include "mlir/IR/StandardTypes.h"
#include "mlir/IR/Types.h"
#include <iostream>
#define DEBUG_TYPE "aten-op-stats"
// This file contains the StatisticsOpInterface implementations
// for ATDialect operations
using namespace mlir;
namespace {
std::vector<uint64_t> unpackListConstant(Value op) {
std::vector<uint64_t> v;
auto co = cast<mlir::NPCOMP::aten::ConstantOp>(op.getDefiningOp());
DenseElementsAttr a = co.template getAttrOfType<DenseElementsAttr>("value");
for (auto i : a.getIntValues())
v.push_back(i.getSExtValue());
return v;
};
} // namespace
namespace mlir {
namespace NPCOMP {
namespace aten {
std::map<std::string, uint64_t> AdaptiveAvgPool2dOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
// FIXME: unimplemented
toReturn["reads"] = -1;
toReturn["writes"] = -1;
return toReturn;
}
std::map<std::string, uint64_t> AdaptiveAvgPool2dBackwardOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
// FIXME: unimplemented
toReturn["reads"] = -1;
toReturn["writes"] = -1;
return toReturn;
}
// add
std::map<std::string, uint64_t> AddOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
TensorType resultTy = getResult().getType().cast<TensorType>();
TensorType aType = getOperand(0).getType().cast<TensorType>();
Type bType = getOperand(1).getType();
uint64_t ofm_volume = getTensorVolume(resultTy);
toReturn["ops:+"] = ofm_volume;
toReturn["result:0:activation_out"] = ofm_volume;
// Find the size of the A and B operands
uint64_t a_volume = getTensorVolume(aType);
uint64_t b_volume = getTensorVolume(bType);
toReturn["operand:0:activation_in"] = a_volume;
toReturn["operand:1:activation_in"] = b_volume;
toReturn["reads"] = a_volume + b_volume;
toReturn["writes"] = ofm_volume;
return toReturn;
}
// add_
std::map<std::string, uint64_t> AddUnderOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
TensorType resultTy = getResult().getType().cast<TensorType>();
TensorType aType = getOperand(0).getType().cast<TensorType>();
Type bType = getOperand(1).getType();
uint64_t ofm_volume = getTensorVolume(resultTy);
toReturn["ops:+"] = ofm_volume;
toReturn["result:0:activation_out"] = ofm_volume;
// Find the size of the A and B operands
uint64_t a_volume = getTensorVolume(aType);
uint64_t b_volume = getTensorVolume(bType);
toReturn["operand:0:activation_in"] = a_volume;
toReturn["operand:1:activation_in"] = b_volume;
toReturn["reads"] = a_volume + b_volume;
toReturn["writes"] = ofm_volume;
return toReturn;
}
// addmm
std::map<std::string, uint64_t> AddmmOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
// For linear, we need the number of output neurons and the number of input
// neurons Then the number of forward MACs is input * output And the number of
// adds is output if there is bias
TensorType resultTy = getResult().getType().cast<TensorType>();
TensorType biasTy = getOperand(0).getType().cast<TensorType>();
TensorType inputTy = getOperand(1).getType().cast<TensorType>();
TensorType weightTy = getOperand(2).getType().cast<TensorType>();
uint64_t num_output_neurons = resultTy.getShape()[1];
uint64_t ofm_volume = getTensorVolume(resultTy);
// Use the weight tensor to find the number of input neurons
uint64_t num_input_neurons = weightTy.getShape()[0];
uint64_t total_MACs = ofm_volume * num_input_neurons;
uint64_t weight_volume = getTensorVolume(weightTy);
uint64_t ifm_volume = getTensorVolume(inputTy);
toReturn["ops:MAC"] = total_MACs;
toReturn["ops:+"] =
ofm_volume; // Should be gated on whether there is bias at all
toReturn["operand:1:activation_in"] = ifm_volume;
toReturn["result:0:activation_out"] = ofm_volume;
toReturn["operand:0:parameters_in:bias"] = getTensorVolume(biasTy);
toReturn["operand:2:parameters_in:weight"] = weight_volume;
toReturn["reads"] = ifm_volume + weight_volume + num_output_neurons;
toReturn["writes"] = ofm_volume;
return toReturn;
}
// as_strided can be zero overhead
std::map<std::string, uint64_t> AsStridedOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
toReturn["reads"] = 0;
toReturn["writes"] = 0;
toReturn["operand:0:activation_in"] = 0;
toReturn["result:0:activation_out"] = 0;
return toReturn;
}
// batch_norm
std::map<std::string, uint64_t> BatchNormOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
TensorType resultTy = getResult(0).getType().cast<TensorType>();
uint64_t op_volume = getTensorVolume(resultTy);
uint64_t weight_volume = getTensorVolume(getOperand(1).getType());
uint64_t bias_volume = getTensorVolume(getOperand(2).getType());
toReturn["operand:0:activation_in"] = op_volume;
toReturn["result:0:activation_out"] = op_volume;
toReturn["operand:1:parameters_in:weight"] = weight_volume;
toReturn["operand:2:parameters_in:bias"] = bias_volume;
// Now for the arithmetic. Assume variance is calculated as sum of squares
uint64_t ifm_depth = resultTy.getShape()[1];
toReturn["ops:+"] = op_volume; // Add up for mean
toReturn["ops:*"] = op_volume; // Square for variance
toReturn["ops:+"] += op_volume; // Add up squares for variance
toReturn["ops:*"] += ifm_depth; // Calc channel means
toReturn["ops:-"] += ifm_depth; // Calc channel vars
toReturn["ops:*"] += ifm_depth; // Calc channel vars
toReturn["ops:sqrt"] = ifm_depth; // Convert to SD
toReturn["ops:/"] = ifm_depth; // Get the reciprocal
toReturn["ops:+"] += op_volume; // Subtract mean off each pixel
toReturn["ops:*"] += op_volume; // Multiply by 1/SD for each pixel
toReturn["ops:+"] += op_volume; // Bias
toReturn["ops:*"] += op_volume; // Scale
toReturn["reads"] = op_volume + weight_volume + bias_volume;
toReturn["writes"] = op_volume;
return toReturn;
}
// _convolution
std::map<std::string, uint64_t> ConvolutionOp::getStatistics() {
return getConv2dStatistics(this, /*groups*/ 1);
}
std::map<std::string, uint64_t> ConvolutionOverrideableOp::getStatistics() {
// FIXME
auto co = cast<mlir::NPCOMP::aten::ConstantOp>(groups().getDefiningOp());
auto ia = co.template getAttrOfType<IntegerAttr>("value");
uint64_t groups = ia.getValue().getZExtValue();
return getConv2dStatistics(this, groups);
}
uint64_t ConvolutionOp::getOperandTransferVolume(unsigned int idx, bool read) {
return getConv2dOperandTransferVolume<ConvolutionOp>(this, idx, read);
}
uint64_t ConvolutionOp::getResultTransferVolume(unsigned int idx, bool write) {
return getConv2dResultTransferVolume<ConvolutionOp>(this, idx, write);
}
// _convolution_backward
std::map<std::string, uint64_t> ConvolutionBackwardOp::getStatistics() {
return getConv2dBackwardStatistics(*this, 1);
}
std::map<std::string, uint64_t> ConvolutionBackwardOverrideableOp::getStatistics() {
auto co = cast<mlir::NPCOMP::aten::ConstantOp>(groups().getDefiningOp());
auto ia = co.template getAttrOfType<IntegerAttr>("value");
uint64_t groups = ia.getValue().getZExtValue();
return getConv2dBackwardStatistics(*this, groups);
}
// div
std::map<std::string, uint64_t> DivOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
TensorType resultTy = getResult().getType().cast<TensorType>();
TensorType aType = getOperand(0).getType().cast<TensorType>();
Type bType = getOperand(1).getType();
uint64_t ofm_volume = getTensorVolume(resultTy);
toReturn["ops:/"] = ofm_volume;
toReturn["result:0:activation_out"] = ofm_volume;
// Find the size of the A and B operands
uint64_t a_volume = getTensorVolume(aType);
uint64_t b_volume = getTensorVolume(bType);
toReturn["operand:0:activation_in"] = a_volume;
toReturn["operand:1:activation_in"] = b_volume;
toReturn["reads"] = a_volume + b_volume;
toReturn["writes"] = ofm_volume;
return toReturn;
}
// div_
std::map<std::string, uint64_t> DivUnderOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
TensorType resultTy = getResult().getType().cast<TensorType>();
TensorType aType = getOperand(0).getType().cast<TensorType>();
Type bType = getOperand(1).getType();
uint64_t ofm_volume = getTensorVolume(resultTy);
toReturn["ops:/"] = ofm_volume;
toReturn["result:0:activation_out"] = ofm_volume;
// Find the size of the A and B operands
uint64_t a_volume = getTensorVolume(aType);
uint64_t b_volume = getTensorVolume(bType);
toReturn["operand:0:activation_in"] = a_volume;
toReturn["operand:1:activation_in"] = b_volume;
toReturn["reads"] = a_volume + b_volume;
toReturn["writes"] = ofm_volume;
return toReturn;
}
// expand can be zero overhead
std::map<std::string, uint64_t> ExpandOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
toReturn["reads"] = toReturn["operand:0:activation_in"] = 0;
toReturn["writes"] = toReturn["result:0:activation_out"] = 0;
return toReturn;
}
// flatten can be zero overhead
std::map<std::string, uint64_t> FlattenOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
toReturn["reads"] = toReturn["operand:0:activation_in"] = 0;
toReturn["writes"] = toReturn["result:0:activation_out"] = 0;
return toReturn;
}
std::map<std::string, uint64_t> GatherOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
// FIXME: unimplemented
toReturn["reads"] = -1;
toReturn["writes"] = -1;
return toReturn;
}
// hardtanh
std::map<std::string, uint64_t> HardtanhOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
TensorType inputTy = getOperand(0).getType().cast<TensorType>();
TensorType resultTy = getResult().getType().cast<TensorType>();
uint64_t in_volume = getTensorVolume(inputTy);
uint64_t out_volume = getTensorVolume(resultTy);
toReturn["operand:0:activation_in"] = in_volume;
toReturn["result:0:activation_out"] = out_volume;
toReturn["reads"] = in_volume;
toReturn["writes"] = out_volume;
toReturn["ops:>"] = out_volume;
return toReturn;
}
// hardtanh_
std::map<std::string, uint64_t> HardtanhUnderOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
TensorType inputTy = getOperand(0).getType().cast<TensorType>();
TensorType resultTy = getResult().getType().cast<TensorType>();
uint64_t in_volume = getTensorVolume(inputTy);
uint64_t out_volume = getTensorVolume(resultTy);
toReturn["operand:0:activation_in"] = in_volume;
toReturn["result:0:activation_out"] = out_volume;
toReturn["reads"] = in_volume;
toReturn["writes"] = out_volume;
toReturn["ops:>"] = out_volume;
return toReturn;
}
std::map<std::string, uint64_t> HardtanhBackwardOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
// FIXME: unimplemented
return toReturn;
}
// max_pool2d
std::map<std::string, uint64_t> MaxPool2dOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
TensorType resultTy = getResult().getType().cast<TensorType>();
TensorType inputType = getOperand(0).getType().cast<TensorType>();
uint64_t ofm_volume = getTensorVolume(resultTy);
toReturn["result:0:activation_out"] = ofm_volume;
uint64_t ifm_volume = getTensorVolume(inputType);
toReturn["input:0:activation_in"] = ifm_volume;
// To find the number of compares, we need the filter extent
std::vector<uint64_t> kernel_size = unpackListConstant(getOperand(1));
uint64_t aperture = kernel_size[0] * kernel_size[1];
toReturn["ops:>"] = ofm_volume * (aperture - 1);
toReturn["reads"] = ifm_volume;
toReturn["writes"] = ofm_volume;
return toReturn;
}
// max_pool2d_with_indices
std::map<std::string, uint64_t> MaxPool2dWithIndicesOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
uint64_t ofm_volume =
getTensorVolume(getResult(0).getType().cast<TensorType>());
uint64_t indices_volume =
getTensorVolume(getResult(1).getType().cast<TensorType>());
toReturn["writes"] = ofm_volume + indices_volume;
toReturn["result:0:activation_out"] = ofm_volume;
toReturn["result:1:indices_out"] = indices_volume;
uint64_t ifm_volume =
getTensorVolume(getOperand(0).getType().cast<TensorType>());
toReturn["reads"] = ifm_volume;
toReturn["operand:0:activation_in"] = ifm_volume;
// To find the number of compares, we need the filter extent
std::vector<uint64_t> kernel_size = unpackListConstant(getOperand(1));
uint64_t aperture = kernel_size[0] * kernel_size[1];
toReturn["ops:>"] = ofm_volume * (aperture - 1);
return toReturn;
}
// max_pool2d_with_indices_backward
std::map<std::string, uint64_t>
MaxPool2dWithIndicesBackwardOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
Type resultTy = getResult().getType();
TensorType tensorResultTy = resultTy.cast<TensorType>();
uint64_t loss_out_volume = getTensorVolume(tensorResultTy);
toReturn["writes"] = loss_out_volume;
uint64_t loss_in_volume =
getTensorVolume(getOperand(0).getType().cast<TensorType>());
uint64_t act_in_volume = getTensorVolume(
getOperand(1).getType().cast<TensorType>()); // TODO: Why is this needed?
uint64_t indices_volume =
getTensorVolume(getOperand(7).getType().cast<TensorType>());
toReturn["reads"] = loss_in_volume + act_in_volume + indices_volume;
toReturn["operand:0:activation_in"] = loss_in_volume;
toReturn["operand:1:activation_in"] = act_in_volume;
toReturn["operand:3:activation_in"] = indices_volume;
toReturn["result:0:grad:dx"] = loss_out_volume;
return toReturn;
}
// mean
std::map<std::string, uint64_t> MeanOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
TensorType resultTy = getResult().getType().cast<TensorType>();
TensorType aType = getOperand().getType().cast<TensorType>();
uint64_t ofm_volume = getTensorVolume(resultTy);
toReturn["ops:+"] = ofm_volume;
toReturn["result:0:activation_out"] = ofm_volume;
// Find the size of the A and B operands
uint64_t a_volume = getTensorVolume(aType);
toReturn["operand:0:activation_in"] = a_volume;
toReturn["reads"] = a_volume;
toReturn["writes"] = ofm_volume;
return toReturn;
}
// mm
// std::map<std::string, uint64_t> MMOp::getStatistics() {
// getMMOpStatistics(*this);
// }
std::map<std::string, uint64_t> MmOp::getStatistics() {
return getMMOpStatistics(*this );
}
// mul
std::map<std::string, uint64_t> MulOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
TensorType resultTy = getResult().getType().cast<TensorType>();
TensorType aType = getOperand(0).getType().cast<TensorType>();
Type bType = getOperand(1).getType();
uint64_t ofm_volume = getTensorVolume(resultTy);
toReturn["ops:*"] = ofm_volume;
toReturn["result:0:activation_out"] = ofm_volume;
// Find the size of the A and B operands
uint64_t a_volume = getTensorVolume(aType);
uint64_t b_volume = getTensorVolume(bType);
toReturn["operand:0:activation_in"] = a_volume;
toReturn["operand:1:activation_in"] = b_volume;
toReturn["reads"] = a_volume + b_volume;
toReturn["writes"] = ofm_volume;
return toReturn;
}
// mul_
std::map<std::string, uint64_t> MulUnderOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
TensorType resultTy = getResult().getType().cast<TensorType>();
TensorType aType = getOperand(0).getType().cast<TensorType>();
Type bType = getOperand(1).getType();
uint64_t ofm_volume = getTensorVolume(resultTy);
toReturn["ops:*"] = ofm_volume;
toReturn["result:0:activation_out"] = ofm_volume;
// Find the size of the A and B operands
uint64_t a_volume = getTensorVolume(aType);
uint64_t b_volume = getTensorVolume(bType);
toReturn["operand:0:activation_in"] = a_volume;
toReturn["operand:1:activation_in"] = b_volume;
toReturn["reads"] = a_volume + b_volume;
toReturn["writes"] = ofm_volume;
return toReturn;
}
// native_batch_norm
std::map<std::string, uint64_t> NativeBatchNormOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
TensorType resultTy = getResult(0).getType().cast<TensorType>();
uint64_t op_volume = getTensorVolume(resultTy);
uint64_t weight_volume = getTensorVolume(getOperand(1).getType());
uint64_t bias_volume = getTensorVolume(getOperand(2).getType());
toReturn["operand:0:activation_in"] = op_volume;
toReturn["result:0:activation_out"] = op_volume;
toReturn["operand:1:parameters_in:weight"] = weight_volume;
toReturn["operand:2:parameters_in:bias"] = bias_volume;
// Now for the arithmetic. Assume variance is calculated as sum of squares
uint64_t ifm_depth = resultTy.getShape()[1];
toReturn["ops:+"] = op_volume; // Add up for mean
toReturn["ops:*"] = op_volume; // Square for variance
toReturn["ops:+"] += op_volume; // Add up squares for variance
toReturn["ops:*"] += ifm_depth; // Calc channel means
toReturn["ops:-"] += ifm_depth; // Calc channel vars
toReturn["ops:*"] += ifm_depth; // Calc channel vars
toReturn["ops:sqrt"] = ifm_depth; // Convert to SD
toReturn["ops:/"] = ifm_depth; // Get the reciprocal
toReturn["ops:+"] += op_volume; // Subtract mean off each pixel
toReturn["ops:*"] += op_volume; // Multiply by 1/SD for each pixel
toReturn["ops:+"] += op_volume; // Bias
toReturn["ops:*"] += op_volume; // Scale
toReturn["reads"] = op_volume + weight_volume + bias_volume;
toReturn["writes"] = op_volume;
return toReturn;
}
// batchnorm backward
std::map<std::string, uint64_t> NativeBatchNormBackwardOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
ShapedType inputTy = getOperand(0).getType().cast<ShapedType>();
uint64_t input_volume = getTensorVolume(inputTy);
uint64_t input_channels = inputTy.getShape()[1];
// # 3 components make up the gradInput: 1 gradInput, 2 gradMean, 3 gradVar
// # totalGradInput = gradInput + (dL / dMean * dMean / dInput) +
// # (dL / dVar * dVar / dInput)
// # gradInput
// total_ops["backward"]["*"] = in_c * (in_h*in_w*batch_size) # scale
// # Bootstrap from previous
// #total_ops["backward"]["sqrt"] = in_c # Convert to std_dev
// #total_ops["backward"]["/"] = in_c # Calculate inverse sqrt first
toReturn["ops:*"] = input_volume; // scale
// # dL / dGradVar
// total_ops["backward"]["pow"] = in_c
// total_ops["backward"]["*"] = total_ops["backward"]["*"] + in_c
// #total_ops["backward"]["+"] = total_ops["backward"]["+"] + in_c *
// in_h*in_w*batch_size # Subtract mean, bootstrap from previous calculation
// total_ops["backward"]["*"] = total_ops["backward"]["*"] + in_c *
// (in_h*in_w*batch_size)
toReturn["ops:pow"] = input_channels;
;
toReturn["ops:*"] += input_channels;
toReturn["ops:*"] += input_volume;
// # dL / dGradMean
// #total_ops["backward"]["+"] = total_ops["backward"]["+"] + in_c *
// (in_h*in_w*batch_size) # bootstrap from previous total_ops["backward"]["*"]
// = total_ops["backward"]["*"] + in_c # scale gradMean
// total_ops["backward"]["*"] = total_ops["backward"]["*"] + in_c # eltwise
// with dL / dGradVar total_ops["backward"]["+"] = in_c *
// (in_h*in_w*batch_size) # sum gradXhat total_ops["backward"]["*"] =
// total_ops["backward"]["*"] + in_c # scale gradXhat
toReturn["ops:*"] += input_channels; // scale gradMean
toReturn["ops:*"] += input_channels; // eltwise with dL / dGradVar
toReturn["ops:+"] = input_volume; // sum gradXhat
toReturn["ops:*"] += input_channels; // scale gradXhat
// # totalGradInput
// total_ops["backward"]["+"] = total_ops["backward"]["+"] + in_c *
// (in_h*in_w*batch_size) # Subtract mean, can't bootstrap this one
// total_ops["backward"]["*"] = total_ops["backward"]["*"] + in_c # scale dL /
// dMean total_ops["backward"]["*"] = total_ops["backward"]["*"] + in_c #
// scale dL / dVar total_ops["backward"]["*"] = total_ops["backward"]["*"] +
// in_c * (in_h*in_w*batch_size) # Eltwise multiply by dL / dVar
// total_ops["backward"]["+"] = total_ops["backward"]["+"] + 2 * in_c *
// (in_h*in_w*batch_size) # Accumulate gradient terms
toReturn["ops:+"] += input_volume; // Subtract mean, can't bootstrap this one
toReturn["ops:*"] += input_channels; // scale dL / dMean
toReturn["ops:*"] += input_channels; // scale dL / dVar
toReturn["ops:*"] += input_volume; // Eltwise multiply by dL / dVar
toReturn["OPS:+"] += 2 * input_volume; // Accumulate gradient terms
uint64_t reads = 0;
for (int i = 0; i < 7; i++) {
auto v = getTensorVolume(getOperand(i).getType());
toReturn["operand:" + std::to_string(i) + ":activation_in"] = v;
reads += v;
}
uint64_t writes = 0;
for (int i = 0; i < 3; i++) {
auto v = getTensorVolume(getResult(i).getType());
toReturn["result:" + std::to_string(i) + ":grad"] = v;
writes += v;
}
toReturn["reads"] = reads;
toReturn["writes"] = writes;
return toReturn;
}
std::map<std::string, uint64_t> NllLossForwardOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
// FIXME: unimplemented
toReturn["reads"] = -1;
toReturn["writes"] = -1;
return toReturn;
}
std::map<std::string, uint64_t> NllLossBackwardOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
// FIXME: unimplemented
toReturn["reads"] = -1;
toReturn["writes"] = -1;
return toReturn;
}
std::map<std::string, uint64_t> NllLoss2dForwardOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
// FIXME: unimplemented
toReturn["reads"] = -1;
toReturn["writes"] = -1;
return toReturn;
}
std::map<std::string, uint64_t> NllLoss2dBackwardOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
// FIXME: unimplemented
toReturn["reads"] = -1;
toReturn["writes"] = -1;
return toReturn;
}
// neg op
std::map<std::string, uint64_t> NegOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
auto insize = getTensorVolume(getOperand().getType());
auto outsize = getTensorVolume(getResult().getType());
toReturn["reads"] = toReturn["operand:0:activation_in"] = insize;
toReturn["writes"] = toReturn["result:0:activation_out"] = outsize;
return toReturn;
}
// relu
// std::map<std::string, uint64_t> ReLUOp::getStatistics() {
// return getReLUOpStatistics(*this);
// }
std::map<std::string, uint64_t> ReluOp::getStatistics() {
return getReLUOpStatistics(*this);
}
// std::map<std::string, uint64_t> ReLUUnderOp::getStatistics() {
// return getReLUOpStatistics(*this);
// }
std::map<std::string, uint64_t> ReluUnderOp::getStatistics() {
return getReLUOpStatistics(*this);
}
// sub
std::map<std::string, uint64_t> SubOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
TensorType resultTy = getResult().getType().cast<TensorType>();
TensorType aType = getOperand(0).getType().cast<TensorType>();
Type bType = getOperand(1).getType();
uint64_t ofm_volume = getTensorVolume(resultTy);
toReturn["ops:-"] = ofm_volume;
toReturn["result:0:activation_out"] = ofm_volume;
// Find the size of the A and B operands
uint64_t a_volume = getTensorVolume(aType);
uint64_t b_volume = getTensorVolume(bType);
toReturn["operand:0:activation_in"] = a_volume;
toReturn["operand:1:activation_in"] = b_volume;
toReturn["reads"] = a_volume + b_volume;
toReturn["writes"] = ofm_volume;
return toReturn;
}
// sub_
std::map<std::string, uint64_t> SubUnderOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
TensorType resultTy = getResult().getType().cast<TensorType>();
TensorType aType = getOperand(0).getType().cast<TensorType>();
Type bType = getOperand(1).getType();
uint64_t ofm_volume = getTensorVolume(resultTy);
toReturn["ops:-"] = ofm_volume;
toReturn["result:0:activation_out"] = ofm_volume;
// Find the size of the A and B operands
uint64_t a_volume = getTensorVolume(aType);
uint64_t b_volume = getTensorVolume(bType);
toReturn["operand:0:activation_in"] = a_volume;
toReturn["operand:1:activation_in"] = b_volume;
toReturn["reads"] = a_volume + b_volume;
toReturn["writes"] = ofm_volume;
return toReturn;
}
// sum
std::map<std::string, uint64_t> SumOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
TensorType ty = getOperand(0).getType().cast<TensorType>();
uint64_t volume = getTensorVolume(ty);
toReturn["ops:+"] = volume;
toReturn["operand:0:activation_in"] = volume;
toReturn["result:0:activation_out"] = volume;
toReturn["reads"] = volume;
toReturn["writes"] = volume;
return toReturn;
}
// size op can be zero overhead
std::map<std::string, uint64_t> SizeOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
toReturn["reads"] = toReturn["operand:0:activation_in"] = 0;
toReturn["writes"] = toReturn["result:0:activation_out"] = 0;
return toReturn;
}
// squeeze can be zero overhead
std::map<std::string, uint64_t> SqueezeOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
toReturn["reads"] = toReturn["operand:0:activation_in"] = 0;
toReturn["writes"] = toReturn["result:0:activation_out"] = 0;
return toReturn;
}
// transpose can be zero overhead
std::map<std::string, uint64_t> TOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
toReturn["reads"] = toReturn["operand:0:activation_in"] = 0;
toReturn["writes"] = toReturn["result:0:activation_out"] = 0;
return toReturn;
}
// threshold_backward
std::map<std::string, uint64_t> ThresholdBackwardOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
uint64_t loss_in_volume =
getTensorVolume(getOperand(0).getType().cast<TensorType>());
uint64_t act_in_volume =
getTensorVolume(getOperand(1).getType().cast<TensorType>());
uint64_t loss_out_volume =
getTensorVolume(getResult().getType().cast<TensorType>());
toReturn["reads"] = toReturn["operand:0:activation_in"] =
loss_in_volume + act_in_volume;
toReturn["writes"] = toReturn["result:0:grad:dx"] = loss_out_volume;
return toReturn;
}
// unsqueeze can be zero overhead
std::map<std::string, uint64_t> UnsqueezeOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
toReturn["reads"] = toReturn["operand:0:activation_in"] = 0;
toReturn["writes"] = toReturn["result:0:activation_out"] = 0;
return toReturn;
}
// view can be zero overhead
std::map<std::string, uint64_t> ViewOp::getStatistics() {
std::map<std::string, uint64_t> toReturn;
toReturn["reads"] = toReturn["operand:0:activation_in"] = 0;
toReturn["writes"] = toReturn["result:0:activation_out"] = 0;
return toReturn;
}
} // namespace aten
} // namespace NPCOMP
} // namespace mlir

View File

@ -0,0 +1,98 @@
//===- ATenLayerNamePass.cpp ------------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "npcomp/Dialect/ATen/ATenLayerNamePass.h"
#include "npcomp/Dialect/ATen/ATenDialect.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
#include "mlir/Pass/Pass.h"
#include <iostream>
#include <vector>
#define DEBUG_TYPE "aten-layer-name"
using namespace mlir;
namespace {
struct ATenLayerNamePass
: public PassWrapper<ATenLayerNamePass, OperationPass<ModuleOp>> {
private:
std::map<Operation *, std::string> opToName;
public:
ATenLayerNamePass() {}
void runOnOperation() override {
markAllAnalysesPreserved();
auto module = getOperation();
// find the function called 'graph'
auto graph = module.lookupSymbol<mlir::FuncOp>("graph");
if (!graph) {
emitError(mlir::UnknownLoc::get(module.getContext()),
"OpReportPass failed: can't find a graph function\n");
signalPassFailure();
return;
}
// Construct a name for each aten operation
std::map<std::string, uint64_t> layerIDmap;
unsigned currentLayer = 0;
graph.walk([&](Operation *op) {
auto name = op->getName().getStringRef();
// if it's not an aten operation, continue
// TODO: need an interface for this rather than just
// doing string compare.
if (!name.startswith("aten."))
return;
// strip the aten prefix to get the operation type
auto type = name.split("aten.").second;
// if it's an aten constant op, continue
if (type.equals("constant"))
return;
unsigned ID = 0;
if (layerIDmap.count(type.str()) == 0)
layerIDmap[type.str()] = 0;
else
ID = ++layerIDmap[type.str()];
std::string layerName = "L" + std::to_string(currentLayer++) + "-" +
type.str() + "-" + std::to_string(ID);
LLVM_DEBUG(llvm::dbgs()
<< "generated layer_name: '" << layerName << "'\n");
auto attr = StringAttr::get(layerName, module.getContext());
op->setAttr(StringRef("layer_name"), attr);
});
}
};
} // namespace
std::unique_ptr<mlir::Pass> mlir::NPCOMP::aten::createATenLayerNamePass() {
return std::make_unique<ATenLayerNamePass>();
}
void mlir::NPCOMP::aten::registerATenLayerNamePass() {
PassRegistration<ATenLayerNamePass>("aten-layer-name",
"Generate layer names for ATen Dialect");
}

View File

@ -0,0 +1,962 @@
//===- ATenLoweringPass.cpp -------------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "npcomp/Dialect/ATen/ATenLoweringPass.h"
#include "npcomp/Dialect/ATen/ATenDialect.h"
#include "npcomp/Dialect/ATen/ATenToStd.h"
#include "mlir/Dialect/Affine/EDSC/Builders.h"
#include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/Dialect/Affine/IR/AffineValueMap.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/Dialect/SCF/EDSC/Builders.h"
#include "mlir/Dialect/SCF/SCF.h"
#include "mlir/Dialect/StandardOps/EDSC/Builders.h"
#include "mlir/Dialect/StandardOps/EDSC/Intrinsics.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
#include "mlir/EDSC/Builders.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/OperationSupport.h"
#include "mlir/IR/StandardTypes.h"
#include "mlir/Parser.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/DialectConversion.h"
#include "mlir/Conversion/StandardToLLVM/ConvertStandardToLLVM.h"
#include "mlir/Conversion/StandardToLLVM/ConvertStandardToLLVMPass.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Type.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <sstream>
using namespace mlir;
using namespace edsc::intrinsics;
using namespace mlir::NPCOMP::aten;
using callOperation = edsc::OperationBuilder<mlir::CallOp>;
using call = edsc::ValueBuilder<mlir::CallOp>;
using constInt = edsc::intrinsics::std_constant_int;
using constFloat = edsc::intrinsics::std_constant_float;
namespace {
/// Utility function for type casting: this is making the type checker happy,
/// while delaying the actual work involved to convert the type. Most of the
/// time both side of the cast (producer and consumer) will be lowered to a
/// dialect like LLVM and end up with the same LLVM representation, at which
/// point this becomes a no-op and is eliminated.
static Value typeCast(PatternRewriter &builder, Value val, Type destTy) {
if (val.getType() == destTy)
return val;
return builder.create<mlir::NPCOMP::aten::TypeCastOp>(val.getLoc(), destTy, val)
.getResult();
}
/// Given a MemRefType, return a new MemRefType with the same rank, but
/// unknown shape.
static MemRefType getShapeErasedMemRefType(MemRefType type) {
std::vector<int64_t> shape = type.getShape();
for(int i = 0; i < shape.size(); i++) {
shape[i] = -1;
}
return MemRefType::get(shape, type.getElementType(),
type.getAffineMaps(), type.getMemorySpace());
}
/// Create a type cast to memref
static Value memRefTypeCast(PatternRewriter &builder, Value val) {
Type type = val.getType();
if (auto memrefTy = type.dyn_cast<MemRefType>()) {
MemRefType newType = getShapeErasedMemRefType(memrefTy);
return builder.create<MemRefCastOp>(val.getLoc(),
val, newType)
.getResult();
}
if (auto tensorTy = type.dyn_cast<TensorType>()) {
auto memRefType = mlir::MemRefType::get(tensorTy.getShape(),
tensorTy.getElementType(), {}, 0);
return typeCast(builder, val, memRefType);
}
return val;
}
// Mangle a type in a way that encodes the full shape information.
// TODO: Currently only supports MemRef, Float, Integer, and AtenList (poorly)
static std::string getFullyMangledType(const Type ty) {
std::stringstream ret;
if (const MemRefType mrt = ty.dyn_cast<const MemRefType>()) {
ret << "M";
auto shape = mrt.getShape();
const Type elem = mrt.getElementType();
for (auto s : shape)
ret << s << "x";
ret << getFullyMangledType(elem);
} else if (FloatType ft = ty.dyn_cast<FloatType>()) {
ret << "F" << ft.getWidth();
} else if (const IntegerType it = ty.dyn_cast<const IntegerType>()) {
ret << "I" << it.getWidth();
} else if (const mlir::NPCOMP::aten::ATenListType alt =
ty.dyn_cast<const mlir::NPCOMP::aten::ATenListType>()) {
} else {
Type t = ty;
t.dump();
assert(0 && "unhandled type in getFullyMangledType");
}
return ret.str();
}
// Mangle the argument shapes into the function name. This is impractical for
// a library-based implementation, since each different shape has to be
// implemented by a different function. The function name is constructed
// from the prefix, the mangled result types, the mangled operand types.
// Types are mangled in a way that encodes the full shape information.
static std::string getFullyMangledFuncName(std::string prefix,
FunctionType fnTy) {
std::string sep = "_";
ArrayRef<Type> resultTy = fnTy.getResults();
ArrayRef<Type> operTy = fnTy.getInputs();
std::string ret = prefix + "_AtenAcapOp_";
for (const Type t : resultTy)
ret = ret + sep + getFullyMangledType(t);
for (const Type t : operTy)
ret = ret + sep + getFullyMangledType(t);
return ret;
}
// Mangle the argument ranks into the function name.
// TODO: Currently only supports MemRef, Float, Integer, and AtenList (poorly)
static std::string getSimplyMangledType(const Type ty) {
std::stringstream ret;
if (const MemRefType mrt = ty.dyn_cast<const MemRefType>()) {
// ret << "M";
ArrayRef<int64_t> shape = mrt.getShape();
const Type elem = mrt.getElementType();
ret << shape.size();
ret << getFullyMangledType(elem);
} else if (FloatType ft = ty.dyn_cast<FloatType>()) {
// ret << "F" << ft.getWidth();
} else if (const IntegerType it = ty.dyn_cast<const IntegerType>()) {
// ret << "I" << it.getWidth();
} else if (const mlir::NPCOMP::aten::ATenListType alt =
ty.dyn_cast<const mlir::NPCOMP::aten::ATenListType>()) {
} else {
Type t = ty;
t.dump();
assert(0 && "unhandled type in getSimplyMangledType");
}
return ret.str();
}
// Return a simply mangled function name. The function name is constructed
// from the prefix, the mangled result types, the mangled operand types.
// Types are mangled in a way that encodes only the rank. Shape information
// is passed runtime using the standard calling convention. This simpler
// version of mangling allows us to implement most of the functions with only
// a few variations. However, it means we need to convert from tensor types
// with known size to tensor types with unknown size to have a consistent
// runtime calling convention.
static std::string getSimplyMangledFuncName(std::string prefix,
ArrayRef<Type> operTy,
ArrayRef<Type> resultTy) {
std::string sep = "_";
std::string ret = prefix;
for (const Type t : resultTy)
ret = ret + sep + getSimplyMangledType(t);
for (const Type t : operTy) {
std::string s = getSimplyMangledType(t);
if(s.size() > 0)
ret = ret + sep + getSimplyMangledType(t);
}
ret += "_out";
return ret;
}
static std::string getSimplyMangledFuncName(std::string prefix,
FunctionType fnTy) {
return getSimplyMangledFuncName(prefix, fnTy.getInputs(), fnTy.getResults());
}
std::string getMangledFuncName(std::string prefix,
FunctionType fnTy) {
return getSimplyMangledFuncName(prefix, fnTy);
}
std::string getMangledFuncName(std::string prefix,
ArrayRef<Type> opTys,
ArrayRef<Type> retTys) {
return getSimplyMangledFuncName(prefix, opTys, retTys);
}
static FuncOp getATenFn(ModuleOp module, std::string mangledFunctionName,
ArrayRef<Value> operands,
ArrayRef<Type> retTys) {
Builder builder(module);
SmallVector<Type, 8> tys;
for (Value o : operands) {
Type t = o.getType();
// Erase the dimensions of the memref.
if (t.isa<MemRefType>()) {
auto mt = t.cast<MemRefType>();
tys.push_back(getShapeErasedMemRefType(mt));
} else
tys.push_back(t);
}
auto fnTy = builder.getFunctionType(tys, retTys);
auto fn = module.lookupSymbol<FuncOp>(mangledFunctionName);
if (!fn) {
fn = FuncOp::create(builder.getUnknownLoc(), mangledFunctionName, fnTy);
module.push_back(fn);
}
return fn;
}
/// Lower an aten.add to an affine loop nest.
class AddOpConversion_affine : public ConversionPattern {
public:
explicit AddOpConversion_affine(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::AddOp::getOperationName(), 1, context) {
}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
auto add = cast<mlir::NPCOMP::aten::AddOp>(op);
auto loc = add.getLoc();
Type resultTy = add.getResult().getType();
TensorType tensorResultTy = resultTy.cast<TensorType>();
MemRefType memRefResultTy = mlir::MemRefType::get(
tensorResultTy.getShape(), tensorResultTy.getElementType(), {}, 0);
Value result = rewriter.create<AllocOp>(loc, memRefResultTy);
Value lhs = memRefTypeCast(rewriter, operands[0]);
Value rhs = memRefTypeCast(rewriter, operands[1]);
auto indexType = IndexType::get(op->getContext());
using namespace edsc;
ScopedContext scope(rewriter, loc);
Value zero = intrinsics::std_constant_index(0);
Value one = intrinsics::std_constant_index(1);
MemRefBoundsCapture vRes(result), vLHS(lhs), vRHS(rhs);
StdIndexedValue iRes(result), iLHS(lhs), iRHS(rhs);
Value M(vRes.ub(0));
if (vRes.rank() == 1) {
affineLoopNestBuilder({zero}, {M}, 1, [&](ValueRange ivs) {
Value i = ivs[0];
iRes(i) = iLHS(i) + iRHS(i);
});
} else if (vRes.rank() == 2) {
Value N(vRes.ub(1));
affineLoopNestBuilder({zero, zero}, {M, N}, {1, 1}, [&](ValueRange ivs) {
Value i = ivs[0];
Value j = ivs[1];
iRes(i, j) = iLHS(i, j) + iRHS(i, j);
});
} else if (vRes.rank() == 3) {
Value N(vRes.ub(1));
Value O(vRes.ub(2));
affineLoopNestBuilder({zero, zero, zero}, {M, N, O}, {1, 1, 1},
[&](ValueRange ivs) {
Value i = ivs[0];
Value j = ivs[1];
Value k = ivs[2];
iRes(i, j, k) = iLHS(i, j, k) + iRHS(i, j, k);
});
} else {
Value N(vRes.ub(1));
Value O(vRes.ub(2));
Value P(vRes.ub(3));
affineLoopNestBuilder({zero, zero, zero, zero}, {M, N, O, P},
{1, 1, 1, 1}, [&](ValueRange ivs) {
Value i = ivs[0];
Value j = ivs[1];
Value k = ivs[2];
Value l = ivs[3];
iRes(i, j, k, l) =
iLHS(i, j, k, l) + iRHS(i, j, k, l);
});
}
// Return the newly allocated buffer.
rewriter.replaceOp(op, {result});
return success();
}
};
// Replace the given operation with a call to the given function.
// The function is assumed to accept memrefs and scalar types and return
// Memrefs. Here the result types are converted back to the result types of op,
// but operands are NOT converted. This allows non-standard mappings from
// operand types to function types.
LogicalResult
rewriteWithVoidFunctionCallExplicit(Operation *op,
ArrayRef<Value> callops,
ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter,
std::string functionName) {
auto loc = op->getLoc();
edsc::ScopedContext scope(rewriter, loc);
// The original operation types.
SmallVector<Type, 8> opTys;
// Shape erased versions of the original operation types.
SmallVector<Type, 8> erasedOpTys;
for (const Value &o: callops) {
Type t = o.getType();
opTys.push_back(t);
if (t.isa<MemRefType>())
erasedOpTys.push_back(getShapeErasedMemRefType(t.cast<MemRefType>()));
else
erasedOpTys.push_back(t);
}
std::vector<Value> newOps = callops;
SmallVector<Value, 8> newResults;
// Result types of the original operation, converted to memrefs.
SmallVector<Type, 8> retTys;
// Erased version of the return type. This is the return types of the
// generated function call.
SmallVector<Type, 8> erasedRetTys;
for (const auto &o: op->getResults()) {
Type t = o.getType();
if (t.isa<TensorType>()) {
TensorType tensorResultTy = t.cast<TensorType>();
MemRefType memRefResultTy =
mlir::MemRefType::get(tensorResultTy.getShape(),
tensorResultTy.getElementType(), {}, 0);
MemRefType erasedMemRefResultTy = getShapeErasedMemRefType(memRefResultTy);
retTys.push_back(memRefResultTy);
// assume memRefResultTy has known shape, so we don't need any
// dynamic dimensions for the alloc.
assert(memRefResultTy.hasStaticShape());
Value allocVal = rewriter.create<AllocOp>(op->getLoc(),
memRefResultTy);
Value castVal = memRefTypeCast(rewriter, allocVal);
newOps.push_back(castVal);
newResults.push_back(allocVal);
} else {
return failure();
}
}
SmallVector<Type, 8> empty;
std::string mangledFunctionName = getMangledFuncName(functionName, opTys, retTys);
FuncOp funcOp = getATenFn(op->getParentOfType<ModuleOp>(),
mangledFunctionName,
newOps,
empty);
auto new_call = callOperation(empty,
rewriter.getSymbolRefAttr(funcOp), newOps);
rewriter.replaceOp(op, newResults);
return success();
}
// Replace the given operation with a call to the given function.
// The function is assumed to accept memrefs and scalar types and return
// Memrefs. Other operand types (e.g. aten.list and tensor<> are converted
// appropriately. The called function passes results of the original function
// as memref arguments at the end of the original set of operands.
LogicalResult
rewriteWithFunctionCall(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter,
std::string functionName) {
auto loc = op->getLoc();
edsc::ScopedContext scope(rewriter, loc);
// Convert the arguments to the original call.
SmallVector<Value, 8> callops;
for (auto &o: operands) {
Type t = o.getType();
if (t.isa<MemRefType>()) {
// Cast it to some memref type that we accept
callops.push_back(memRefTypeCast(rewriter, o));
} else if (t.isa<IntegerType>() || t.isa<FloatType>()) {
callops.push_back(o);
} else if (t.isa<ATenListType>()) {
// FIXME: lots of assumptions here.
auto unpack = [](auto &op, auto &v) -> void {
auto co = cast<mlir::NPCOMP::aten::ConstantOp>(op.getDefiningOp());
DenseElementsAttr a =
co.template getAttrOfType<DenseElementsAttr>("value");
for (auto i : a.getIntValues())
v.push_back(i.getSExtValue());
};
std::vector<uint64_t> values;
unpack(o, values);
callops.push_back(constInt(values[0], 32));
} else {
return failure();
}
}
return rewriteWithVoidFunctionCallExplicit(op, callops, operands, rewriter, functionName);
}
/// Lower Add
template<typename Op>
class ATenFunctionCallConversion : public ConversionPattern {
public:
explicit ATenFunctionCallConversion(MLIRContext *context)
: ConversionPattern(Op::getOperationName(), 1, context) {
}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, Op::getFunctionConversionName());
}
};
/// Lower aten.constant
class ConstantOpConversion : public ConversionPattern {
public:
explicit ConstantOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::ConstantOp::getOperationName(), 1, context) {
}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
auto loc = op->getLoc();
edsc::ScopedContext scope(rewriter, loc);
auto constOp = cast<mlir::NPCOMP::aten::ConstantOp>(op);
Value result = op->getResult(0);
Type t = result.getType();
if (t.isa<IntegerType>()) {
auto it = t.cast<IntegerType>();
if(it.getWidth() > 1) {
auto a = op->getAttrOfType<IntegerAttr>("value");
SmallVector<Value, 8> newValues {rewriter.create<mlir::ConstantOp>(loc, a)};
rewriter.replaceOp(op, newValues);
return success();
} else {
auto a = op->getAttrOfType<BoolAttr>("value");
SmallVector<Value, 8> newValues {constInt(a.getValue(), it.getWidth())};
rewriter.replaceOp(op, newValues);
return success();
}
}
// FIXME: support float types
// if(t.isa<FloatType>()) {
// APFloat f = *(a.float_value_begin());
// rewriter.replaceOp(op, constFloat(f));
// return success();
// }
return failure();
}
};
/// Lower Add
class AddOpConversion : public ConversionPattern {
public:
explicit AddOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::AddOp::getOperationName(), 1, context) {
}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "add");
}
};
/// Lower Addmm
class AddmmOpConversion : public ConversionPattern {
public:
explicit AddmmOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::AddmmOp::getOperationName(), 1,
context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "addmm");
}
};
/// Lower AsStrided
class AsStridedOpConversion : public ConversionPattern {
public:
explicit AsStridedOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::AsStridedOp::getOperationName(), 1,
context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
auto loc = op->getLoc();
edsc::ScopedContext scope(rewriter, loc);
Value xVal = memRefTypeCast(rewriter, operands[0]);
// construct the shape argument
std::vector<Value> shape;
std::vector<int64_t> result_shape;
auto co0 = cast<mlir::NPCOMP::aten::ConstantOp>(operands[1].getDefiningOp());
DenseElementsAttr a0 =
co0.template getAttrOfType<DenseElementsAttr>("value");
for (auto i : a0.getAttributeValues())
shape.push_back(rewriter.create<mlir::ConstantOp>(co0.getLoc(), i));
// pad out the shape with -1 to make it 4d
while (shape.size() < 4)
shape.push_back(constInt(-1, 32));
// construct the stride argument
std::vector<Value> stride;
auto co1 = cast<mlir::NPCOMP::aten::ConstantOp>(operands[2].getDefiningOp());
DenseElementsAttr a1 =
co1.template getAttrOfType<DenseElementsAttr>("value");
for (auto i : a1.getAttributeValues())
stride.push_back(rewriter.create<mlir::ConstantOp>(co1.getLoc(), i));
// pad out the stride with -1 to make it 4d
while (stride.size() < 4)
stride.push_back(constInt(-1, 32));
APInt offset(32, 0);
if (operands.size() > 3) {
auto co2 = cast<mlir::NPCOMP::aten::ConstantOp>(operands[3].getDefiningOp());
auto ia2 = co2.getAttrOfType<IntegerAttr>("value");
offset = ia2.getValue();
}
SmallVector<Value, 8> callops{xVal, shape[0],
shape[1], shape[2],
shape[3], stride[0],
stride[1], stride[2],
stride[3], constInt(offset.getSExtValue(), 32)};
return rewriteWithVoidFunctionCallExplicit(op, callops, operands, rewriter, "as_strided");
}
};
/// Lower batchnorm
class BatchNormOpConversion : public ConversionPattern {
public:
explicit BatchNormOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::BatchNormOp::getOperationName(), 1,
context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "batch_norm");
}
};
/// Lower conv2d
class ConvolutionOpConversion : public ConversionPattern {
public:
explicit ConvolutionOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::ConvolutionOp::getOperationName(), 1,
context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "conv2d");
}
};
/// Lower conv2d backward
class ConvolutionBackwardOpConversion : public ConversionPattern {
public:
explicit ConvolutionBackwardOpConversion(MLIRContext *context)
: ConversionPattern(
mlir::NPCOMP::aten::ConvolutionBackwardOp::getOperationName(), 1,
context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "conv2d_backward");
}
};
/// Lower Div
class DivOpConversion : public ConversionPattern {
public:
explicit DivOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::DivOp::getOperationName(), 1, context) {
}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "div");
}
};
/// Lower LogSoftmax
class LogSoftmaxOpConversion : public ConversionPattern {
public:
explicit LogSoftmaxOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::LogSoftmaxOp::getOperationName(), 1,
context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "log_softmax");
}
};
/// Lower LogSoftmaxBackwardData
class LogSoftmaxBackwardDataOpConversion : public ConversionPattern {
public:
explicit LogSoftmaxBackwardDataOpConversion(MLIRContext *context)
: ConversionPattern(
mlir::NPCOMP::aten::LogSoftmaxBackwardDataOp::getOperationName(), 1,
context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter,
"log_softmax_backward_data");
}
};
/// Lower maxpool2d
class MaxPoolOpConversion : public ConversionPattern {
public:
explicit MaxPoolOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::MaxPool2dOp::getOperationName(), 1,
context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "max_pool2d");
}
};
/// Lower maxpool2d
class MaxPool2dWithIndicesOpConversion : public ConversionPattern {
public:
explicit MaxPool2dWithIndicesOpConversion(MLIRContext *context)
: ConversionPattern(
mlir::NPCOMP::aten::MaxPool2dWithIndicesOp::getOperationName(), 1,
context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "max_pool2d_with_indices");
}
};
/// Lower max_pool2d_with_indices_backward
class MaxPool2dWithIndicesBackwardOpConversion : public ConversionPattern {
public:
explicit MaxPool2dWithIndicesBackwardOpConversion(MLIRContext *context)
: ConversionPattern(
mlir::NPCOMP::aten::MaxPool2dWithIndicesBackwardOp::getOperationName(), 1,
context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "max_pool2d_with_indices_backward");
}
};
/// Lower MM
class MMOpConversion : public ConversionPattern {
public:
explicit MMOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::MmOp::getOperationName(), 1, context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "mm");
}
};
/// Lower Mul
class MulOpConversion : public ConversionPattern {
public:
explicit MulOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::MulOp::getOperationName(), 1, context) {
}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "mul");
}
};
/// Lower batchnorm
class NativeBatchNormOpConversion : public ConversionPattern {
public:
explicit NativeBatchNormOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::NativeBatchNormOp::getOperationName(),
1, context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "native_batch_norm");
}
};
/// lower NLL Loss backward
class NllLoss2dBackwardOpConversion : public ConversionPattern {
public:
explicit NllLoss2dBackwardOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::NllLoss2dBackwardOp::getOperationName(),
1, context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "nll_loss2d_backward");
}
};
/// lower NLL Loss forward
class NllLoss2dForwardOpConversion : public ConversionPattern {
public:
explicit NllLoss2dForwardOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::NllLoss2dForwardOp::getOperationName(),
1, context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "nll_loss2d_forward");
}
};
/// lower NLL Loss backward
class NllLossBackwardOpConversion : public ConversionPattern {
public:
explicit NllLossBackwardOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::NllLossBackwardOp::getOperationName(),
1, context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "nll_loss_backward");
}
};
/// lower NLL Loss forward
class NllLossForwardOpConversion : public ConversionPattern {
public:
explicit NllLossForwardOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::NllLossForwardOp::getOperationName(), 1,
context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "nll_loss_forward"); }
};
/// Lower ReLU
class ReLUOpConversion : public ConversionPattern {
public:
explicit ReLUOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::ReluOp::getOperationName(), 1,
context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "relu");
}
};
/// Lower ThresholdBackward
class ThresholdBackwardOpConversion : public ConversionPattern {
public:
explicit ThresholdBackwardOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::ThresholdBackwardOp::getOperationName(),
1, context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "threshold_backward");
}
};
/// Lower transpose
class TransposeOpConversion : public ConversionPattern {
public:
explicit TransposeOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::TOp::getOperationName(), 1, context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
return rewriteWithFunctionCall(op, operands, rewriter, "t");
}
};
/// Lower view
class ViewOpConversion : public ConversionPattern {
public:
explicit ViewOpConversion(MLIRContext *context)
: ConversionPattern(mlir::NPCOMP::aten::ViewOp::getOperationName(), 1,
context) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
auto loc = op->getLoc();
edsc::ScopedContext scope(rewriter, loc);
Value xVal = memRefTypeCast(rewriter, operands[0]);
// construct the shape argument
SmallVector<Value, 8> shape;
auto co = dyn_cast<mlir::NPCOMP::aten::ConstantOp>(operands[1].getDefiningOp());
DenseElementsAttr a = co.template getAttrOfType<DenseElementsAttr>("value");
for (auto i : a.getAttributeValues())
shape.push_back(rewriter.create<mlir::ConstantOp>(co.getLoc(), i));
// pad out the shape with -1 to make it 4d
while (shape.size() < 4)
shape.push_back(constInt(-1, 32));
SmallVector<Value, 8> callops{xVal, shape[0], shape[1], shape[2], shape[3]};
return rewriteWithVoidFunctionCallExplicit(op, callops, operands, rewriter, "view");
}
};
/// Convert an ATen type, this gets called for block and region arguments, and
/// attributes.
MemRefType convertTensorType(TensorType tensor) {
return mlir::MemRefType::get(tensor.getShape(), tensor.getElementType(), {},
0);
}
/// Lower ATen to Standard dialect. Currently most of the lowerings are done
/// through function calls, which are expected to be implemented through an
/// external library and linked into the resulting code. In the future, the
/// expectation is that the preferred lowering path would go through TCP.
/// FIXME: Audit this for completeness
struct ATenLoweringPass
: public PassWrapper<ATenLoweringPass, OperationPass<ModuleOp>> {
void runOnOperation() override {
LLVMTypeConverter typeConverter(getOperation().getContext());
typeConverter.addConversion([&](Type type) {
if (auto tensor = type.dyn_cast<TensorType>())
return convertTensorType(tensor).cast<Type>();
return type;
});
OwningRewritePatternList acapPatterns;
auto module = getOperation();
auto context = module.getContext();
// c++ patterns
acapPatterns.insert<
ConstantOpConversion,
AddOpConversion, ConvolutionOpConversion, ReLUOpConversion,
TransposeOpConversion, BatchNormOpConversion,
NativeBatchNormOpConversion, MaxPoolOpConversion,
MaxPool2dWithIndicesOpConversion, AddmmOpConversion, ViewOpConversion,
MulOpConversion, MMOpConversion, AsStridedOpConversion,
LogSoftmaxOpConversion, ThresholdBackwardOpConversion,
MaxPool2dWithIndicesBackwardOpConversion,
ConvolutionBackwardOpConversion, NllLossForwardOpConversion,
NllLossBackwardOpConversion, NllLoss2dForwardOpConversion,
NllLoss2dBackwardOpConversion, LogSoftmaxOpConversion,
LogSoftmaxBackwardDataOpConversion, DivOpConversion>(context);
mlir::populateFuncOpTypeConversionPattern(acapPatterns, context,
typeConverter);
// tablegen patterns
populateATenToStdPatterns(context, acapPatterns);
// Perform acap specific lowering.
ConversionTarget target(getContext());
target.addLegalDialect<LLVM::LLVMDialect, StandardOpsDialect,
scf::SCFDialect>();
target.addLegalOp<AffineForOp, AffineApplyOp, AffineYieldOp>();
target.addDynamicallyLegalOp<FuncOp>([&](FuncOp op) {
return typeConverter.isSignatureLegal(op.getType());
});
if (failed(applyPartialConversion(module, target, acapPatterns))) {
emitError(UnknownLoc::get(context), "error lowering ATen\n");
signalPassFailure();
}
// remove dead constant ops
for (auto function : getOperation().getOps<FuncOp>()) {
function.walk([&](Operation *op) {
auto constOp = dyn_cast<mlir::NPCOMP::aten::ConstantOp>(op);
if (!constOp)
return;
if (op->use_empty())
op->erase();
});
}
}
};
} // namespace
namespace mlir {
namespace NPCOMP {
namespace aten {
std::unique_ptr<mlir::Pass> createATenLoweringPass() {
return std::make_unique<ATenLoweringPass>();
}
} // namespace aten
} // namespace NPCOMP
} // namespace mlir
void mlir::NPCOMP::aten::registerATenLoweringPass() {
PassRegistration<ATenLoweringPass>("aten-to-std",
"ATen dialect lowering to function calls");
}

View File

@ -0,0 +1,155 @@
//===- ATenOpReport.cpp -----------------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "npcomp/Dialect/ATen/ATenOpReport.h"
#include "npcomp/Dialect/ATen/ATenDialect.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/raw_ostream.h"
#include "mlir/Pass/Pass.h"
#include <iostream>
#include <vector>
#define DEBUG_TYPE "aten-op-stats"
using namespace mlir;
namespace {
std::string getAsString(std::map<std::string, uint64_t> &m, std::string &e) {
return m.count(e) ? std::to_string(m[e]) : " ";
}
/// Query operations through the StatisticsOpInterface and print the result
/// in a human-readable way. This replicates the functionality in various
/// network analysis tools and is a stepping stone toward using the information
/// as an analysis to drive optimization.
struct ATenOpReportPass
: public PassWrapper<ATenOpReportPass, OperationPass<ModuleOp>> {
private:
std::string *output;
std::vector<std::string> tableFields;
std::map<Operation *, std::string> opToName;
public:
ATenOpReportPass() :
output(nullptr),
tableFields({"reads", "writes", "activation_in", "activation_out",
"parameters_in", "ops:MAC", "ops:==", "ops:>", "ops:*",
"ops:+", "ops:/", "ops:sqrt", "ops:-", "grad"}) {}
ATenOpReportPass(std::string *output) :
output(output),
tableFields({"reads", "writes", "activation_in", "activation_out",
"parameters_in", "ops:MAC", "ops:==", "ops:>", "ops:*",
"ops:+", "ops:/", "ops:sqrt", "ops:-", "grad"}) {}
std::string emitJSONReport() {
llvm::json::Object top;
auto graph = getOperation().lookupSymbol<mlir::FuncOp>("graph");
graph.walk([&](Operation *op) {
if (auto stats =
mlir::dyn_cast<mlir::NPCOMP::StatisticsOpInterface>(op)) {
// name for this layer
std::string layerName = opToName[op];
// raw stats for this layer
std::map<std::string, uint64_t> layerStatsMap = stats.getStatistics();
// JSON version of the stats we are building
llvm::json::Object layerStatsJSON;
// foreach string f in tableField,
// get the sum of all entries in layerStatsMap containing f
for (auto &f : tableFields) {
for (auto &p : layerStatsMap) {
if (p.first.find(f) != std::string::npos) {
if (auto count = layerStatsJSON[f].getAsInteger())
layerStatsJSON[f] = (int64_t)p.second + *count;
else
layerStatsJSON[f] = (int64_t)p.second;
}
}
}
top[layerName] = llvm::json::Value(std::move(layerStatsJSON));
}
});
llvm::json::Value topv(std::move(top));
std::string ret;
llvm::raw_string_ostream ss(ret);
ss << llvm::formatv("{0:2}", topv) << "\n";
return ss.str();
}
void runOnOperation() override {
// I don't change anything
markAllAnalysesPreserved();
auto module = getOperation();
// check that a function called "graph" exists
auto graph = module.lookupSymbol<mlir::FuncOp>("graph");
if (!graph) {
emitError(mlir::UnknownLoc::get(module.getContext()),
"OpReportPass failed: can't find a graph function\n");
signalPassFailure();
return;
}
unsigned currentLayer = 0;
opToName.clear();
graph.walk([&](Operation *op) {
auto attr = op->getAttrOfType<StringAttr>("layer_name");
if (attr)
opToName[op] = attr.getValue().str();
else
opToName[op] = "unknown-layer-" + std::to_string(currentLayer);
currentLayer++;
});
std::string report = emitJSONReport();
if(output) {
*output = report;
} else {
graph.emitWarning(report);
}
}
};
} // namespace
namespace mlir {
namespace NPCOMP {
namespace aten {
std::unique_ptr<mlir::Pass> createATenOpReportPass() {
return std::make_unique<ATenOpReportPass>();
}
std::unique_ptr<mlir::Pass> createATenOpReportPass(std::string &report) {
return std::make_unique<ATenOpReportPass>(&report);
}
void mlir::NPCOMP::aten::registerATenOpReportPass() {
PassRegistration<ATenOpReportPass>("aten-op-report",
"Generate ATen operation report");
}
} // namespace aten
} // namespace NPCOMP
} // namespace mlir

View File

@ -0,0 +1,21 @@
//===- ATenPasses.cpp -------------------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "npcomp/Dialect/ATen/ATenPasses.h"
using namespace mlir::NPCOMP::aten;
void mlir::NPCOMP::aten::registerATenPasses() {
// TODO: Use the automatically generated pass registration.
// #define GEN_PASS_REGISTRATION
// #include "npcomp/Dialect/ATen/ATenPasses.h.inc"
registerATenLayerNamePass();
registerATenOpReportPass();
registerATenLoweringPass();
registerReturnEliminationPass();
}

View File

@ -0,0 +1,26 @@
//===- ATenToStd.cpp --------------------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "npcomp/Dialect/ATen/ATenToStd.h"
#include "npcomp/Dialect/ATen/ATenDialect.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
using namespace mlir;
using namespace mlir::NPCOMP;
namespace {
// import patterns
#include "npcomp/Dialect/ATen/ATenToStd.cpp.inc"
} // namespace
namespace mlir {
void populateATenToStdPatterns(MLIRContext *context,
OwningRewritePatternList &patterns) {
populateWithGenerated(context, &patterns);
}
} // namespace mlir

View File

@ -0,0 +1,25 @@
add_mlir_dialect_library(NPCOMPATenDialect
ATenDialect.cpp
ATenDialectOpStats.cpp
ATenPasses.cpp
ATenLayerNamePass.cpp
ATenLoweringPass.cpp
ATenOpReport.cpp
ATenToStd.cpp
LivenessReport.cpp
ReturnEliminationPass.cpp
ADDITIONAL_HEADER_DIRS
${PROJECT_SOURCE_DIR}/dialect/include
${PROJECT_BINARY_DIR}/dialect/include
DEPENDS
MLIRATenIncGen
MLIRATenEnumsIncGen
MLIRATenOpInterfacesIncGen
MLIRATenToStdIncGen
LINK_LIBS PUBLIC
MLIRPass
MLIRTransformUtils
)

View File

@ -0,0 +1,253 @@
//===- LivenessReport.cpp ---------------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "npcomp/Dialect/ATen/ATenDialect.h"
#include "mlir/Analysis/Liveness.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/Module.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/JSON.h"
#include "npcomp/Dialect/ATen/LivenessReport.h"
#include <iostream>
#include <sstream>
#include <vector>
#define DEBUG_TYPE "liveness-report"
using namespace mlir;
namespace {
uint64_t getTensorVolume(const ShapedType ty) {
if (!ty.hasRank())
return 1;
uint64_t volume = 1;
for (auto &d : ty.getShape())
volume *= d;
return volume;
}
uint64_t getTensorVolume(const Type ty) {
if (auto t = ty.dyn_cast<ShapedType>()) {
return getTensorVolume(t);
} else {
return 1;
}
}
} // namespace
namespace mlir {
namespace NPCOMP {
namespace aten {
std::string LivenessReport::generateTextReport() {
resolveLiveness();
std::string output;
for (auto &p : livenessIntervals) {
Value v = p.first;
std::vector<Operation *> &oplist = p.second;
llvm::outs() << "// begin\n";
v.print(llvm::outs());
llvm::outs() << "\n";
for (auto *o : oplist) {
o->print(llvm::outs());
llvm::outs() << "\n";
}
llvm::outs() << "// end \n";
}
return output;
}
std::string LivenessReport::emitJSONReport() {
resolveLiveness();
llvm::json::Object top;
auto context = module.getContext();
auto loc = mlir::UnknownLoc::get(context);
auto graph = module.lookupSymbol<mlir::FuncOp>("graph");
std::map<Operation *, std::vector<Value>> liveAt;
graph.walk([&](Operation *op) {
for (Value result : op->getResults()) {
for (auto &p : livenessIntervals) {
Value v = p.first;
if (v == result) {
std::vector<Operation *> &oplist = p.second;
for (auto *o : oplist)
liveAt[o].push_back(result);
}
}
}
});
auto argList = graph.getBody().getBlocks().front().getArguments();
for (Value &arg : argList) {
for (auto &p : livenessIntervals) {
Value v = p.first;
if (v == arg) {
std::vector<Operation *> &oplist = p.second;
for (auto *o : oplist)
liveAt[o].push_back(arg);
}
}
}
graph.walk([&](Operation *op) {
llvm::json::Object layerDetail;
auto attr = op->getAttrOfType<StringAttr>("layer_name");
if (!attr)
return;
std::vector<Value> &vlist = liveAt[op];
int64_t parameterVol = 0;
int64_t returnVol = 0;
for (auto v : vlist) {
int64_t vol = getTensorVolume(v.getType());
if (v.getDefiningOp()) {
if (auto a = v.getDefiningOp()->getAttrOfType<StringAttr>(
"layer_name")) {
auto definingOp = v.getDefiningOp();
auto ld = layerDetail.getInteger(a.getValue().str());
if (ld)
layerDetail[a.getValue().str()] = *ld + vol;
else
layerDetail[a.getValue().str()] = vol;
} else {
llvm_unreachable("unknown type");
}
} else if (std::find(argList.begin(), argList.end(), v) !=
argList.end()) {
parameterVol += vol;
} else {
llvm_unreachable("unknown type");
}
auto ret = cast<ReturnOp>(op->getBlock()->getTerminator());
for (auto oper : ret.getOperands()) {
if (oper == v) {
returnVol += vol;
break;
}
}
}
if (parameterVol) {
layerDetail["parameters"] = parameterVol;
}
if (returnVol) {
layerDetail["returns"] = returnVol;
}
top[attr.getValue().str()] = llvm::json::Value(std::move(layerDetail));
});
llvm::json::Value topv(std::move(top));
std::string ret;
llvm::raw_string_ostream ss(ret);
ss << llvm::formatv("{0:2}", topv) << "\n";
return ss.str();
}
void LivenessReport::resolveLiveness() {
auto context = module.getContext();
auto loc = mlir::UnknownLoc::get(context);
// check that a function called "graph" exists
auto graph = module.lookupSymbol<mlir::FuncOp>("graph");
if (!graph) {
emitError(mlir::UnknownLoc::get(module.getContext()),
"LivenessReport failed: can't find a graph function\n");
return;
}
// put each aten operation into its own basic block,
// so that we can use standard liveness
Region &bodyRegion = graph.getBody();
Block *entryBB = &bodyRegion.getBlocks().front();
Block *BB = entryBB;
std::vector<Block *> new_blocks;
while (true) {
std::vector<Operation *> ops;
for (Operation &op : BB->getOperations())
ops.push_back(&op);
// skip over constant ops
int idx = 0;
while (dyn_cast<mlir::NPCOMP::aten::ConstantOp>(ops[idx]))
idx++;
if (dyn_cast<ReturnOp>(ops[idx]))
break;
Block *newBB = BB->splitBlock(ops[idx + 1]);
new_blocks.push_back(newBB);
mlir::OpBuilder builder = mlir::OpBuilder::atBlockBegin(BB);
builder.create<BranchOp>(loc, newBB);
BB = newBB;
}
// dump transformed function
// graph.dump();
// run MLIR Liveness analysis
auto liveness = Liveness(graph);
for (BlockArgument &arg :
graph.getBody().getBlocks().front().getArguments()) {
auto liveOps = liveness.resolveLiveness(arg);
for (Operation *o : liveOps) {
livenessIntervals[arg].push_back(o);
}
}
graph.walk([&](Operation *op) {
auto attr = op->getAttrOfType<StringAttr>("layer_name");
if (!attr)
return;
for (Value v : op->getResults()) {
auto liveOps = liveness.resolveLiveness(v);
for (Operation *o : liveOps)
if (auto a = o->getAttrOfType<StringAttr>("layer_name"))
livenessIntervals[v].push_back(o);
}
});
// undo the BB insert
auto *deadBr = bodyRegion.getBlocks().front().getTerminator();
for (Block *b : new_blocks) {
auto *br = b->getTerminator();
std::vector<Operation *> ops;
for (auto &op : b->getOperations())
ops.push_back(&op);
for (auto *op : ops) {
if (op == br && !dyn_cast<ReturnOp>(op)) {
op->erase();
continue;
}
op->moveBefore(deadBr);
}
}
deadBr->erase();
for (Block *b : new_blocks)
b->erase();
// graph.dump();
}
} // namespace aten
} // namespace NPCOMP
} // namespace mlir

View File

@ -0,0 +1,185 @@
//===- ReturnEliminationPass.cpp --------------------------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "npcomp/Dialect/ATen/ReturnEliminationPass.h"
#include "npcomp/Dialect/ATen/ATenDialect.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
#include "mlir/Pass/Pass.h"
#include <set>
#include <vector>
#define DEBUG_TYPE "return-elimination"
using namespace mlir;
namespace {
/// In the process of lowering the ATenLoweringPass generates function calls
/// that take and return memrefs. However, this makes memory managment
/// somewhat awkward, since we need to take care of allocating and
/// deallocating memory. It also forces a copy to pytorch, which wants to
/// pass in a pre-allocated buffer for the return type. To simplify the
/// library, we convert the signature of function calls (particularly the
/// toplevel) to pass return values by reference.
class ReturnEliminationPass
: public PassWrapper<ReturnEliminationPass, OperationPass<ModuleOp>> {
public:
ReturnEliminationPass() {}
void runOn(Operation *op) {
auto module = getOperation();
if (visitedOps.count(op))
return;
visitedOps.insert(op);
if (auto callOp = dyn_cast<CallOp>(op)) {
auto builder = std::make_unique<mlir::OpBuilder>(op);
std::vector<Type> tys;
for (auto t : callOp.getCalleeType().getInputs())
tys.push_back(t);
for (auto t : callOp.getCalleeType().getResults())
tys.push_back(t);
auto newFnTy = FunctionType::get(tys, {}, op->getContext());
// FIXME: possible name collision
std::string newFnName = callOp.callee().str() + "_out";
if (!module.lookupSymbol<FuncOp>(newFnName)) {
auto fn = FuncOp::create(op->getLoc(), newFnName, newFnTy);
module.push_back(fn);
}
std::vector<Value> newCallArgs{callOp.arg_operand_begin(),
callOp.arg_operand_end()};
for (auto v : callOp.getResults()) {
if (!v.getType().isa<MemRefType>())
llvm_unreachable("function returns non-memref");
if (!valueMap.count(v)) {
valueMap[v] = builder->create<AllocOp>(
op->getLoc(), v.getType().cast<MemRefType>());
}
v.replaceAllUsesWith(valueMap[v]);
newCallArgs.push_back(valueMap[v]);
}
auto newCallOp = builder->create<CallOp>(op->getLoc(), newFnName,
ArrayRef<Type>{}, newCallArgs);
erasedOps.insert(op);
auto fn = module.lookupSymbol<FuncOp>(callOp.callee());
if (fn && fn.use_empty())
erasedOps.insert(fn);
} else if (isa<AllocOp>(op)) {
Value v = op->getResult(0);
if (valueMap.count(v)) {
v.replaceAllUsesWith(valueMap[v]);
erasedOps.insert(op);
}
}
for (Value v : op->getOperands()) {
if (!v.getType().isa<MemRefType>())
continue;
if (v.isa<BlockArgument>())
continue;
if (v.getDefiningOp())
runOn(v.getDefiningOp());
}
}
void runOnOperation() override {
auto module = getOperation();
auto context = module.getContext();
// check that a function called "graph" exists
auto graph = module.lookupSymbol<mlir::FuncOp>("graph");
if (!graph) {
emitError(mlir::UnknownLoc::get(module.getContext()),
"OpReportPass failed: can't find a graph function\n");
signalPassFailure();
return;
}
// assume a single bb with a single return statement
Block &BB = graph.front();
FunctionType funcTy = graph.getType();
std::vector<Type> newFuncInputTys;
for (auto ty : funcTy.getInputs())
newFuncInputTys.push_back(ty);
for (auto ty : funcTy.getResults())
newFuncInputTys.push_back(ty);
FunctionType newFuncTy =
FunctionType::get(newFuncInputTys, {}, module.getContext());
graph.setType(newFuncTy);
Operation *retOp = BB.getTerminator();
auto builder = std::make_unique<mlir::OpBuilder>(retOp);
builder->create<ReturnOp>(retOp->getLoc());
std::vector<Value> operands{retOp->getOperands().begin(),
retOp->getOperands().end()};
retOp->dropAllReferences();
erasedOps.insert(retOp);
for (Value v : operands)
valueMap[v] = BB.addArgument(v.getType());
for (Value v : operands) {
if (!v.getType().isa<MemRefType>())
llvm_unreachable("graph function returns non-memref");
if (v.getDefiningOp())
runOn(v.getDefiningOp());
}
for (auto oi = BB.rbegin(), oe = BB.rend(); oi != oe; ++oi) {
Operation *o = &*oi;
for (Value v : o->getResults()) {
if (v.getType().isa<MemRefType>()) {
runOn(o);
break;
}
}
}
for (Operation *o : erasedOps)
o->erase();
}
private:
llvm::DenseMap<Value, Value> valueMap;
std::set<Operation *> visitedOps;
std::set<Operation *> erasedOps;
};
} // namespace
std::unique_ptr<mlir::Pass> mlir::NPCOMP::aten::createReturnEliminationPass() {
return std::make_unique<ReturnEliminationPass>();
}
void mlir::NPCOMP::aten::registerReturnEliminationPass() {
PassRegistration<ReturnEliminationPass>("return-elimination",
"eliminate returns");
}

View File

@ -1,3 +1,4 @@
add_subdirectory(ATen)
add_subdirectory(Basicpy)
add_subdirectory(Npcomprt)
add_subdirectory(Numpy)

View File

@ -8,6 +8,8 @@
#include "npcomp/InitAll.h"
#include "npcomp/Dialect/ATen/ATenDialect.h"
#include "npcomp/Dialect/ATen/ATenPasses.h"
#include "npcomp/Dialect/Basicpy/IR/BasicpyDialect.h"
#include "npcomp/Dialect/Basicpy/Transforms/Passes.h"
#include "npcomp/Dialect/Npcomprt/IR/NpcomprtDialect.h"
@ -68,6 +70,7 @@ static void registerDependencyPasses() {
}
void mlir::NPCOMP::registerAllDialects() {
registerDialect<mlir::NPCOMP::aten::ATenDialect>();
registerDialect<Basicpy::BasicpyDialect>();
registerDialect<Numpy::NumpyDialect>();
registerDialect<npcomprt::NpcomprtDialect>();
@ -100,5 +103,6 @@ void mlir::NPCOMP::registerAllPasses() {
#include "npcomp/Dialect/TCF/Transforms/Passes.h.inc"
#define GEN_PASS_REGISTRATION
#include "npcomp/Typing/Transforms/Passes.h.inc"
mlir::NPCOMP::aten::registerATenPasses();
registerDependencyPasses();
}

View File

@ -0,0 +1,15 @@
// RUN: npcomp-opt %s -aten-layer-name -aten-op-report |& FileCheck %s
// CHECK-LABEL: "L0-add-0": {
// CHECK-NEXT: "activation_in": 12,
// CHECK-NEXT: "activation_out": 6,
// CHECK-NEXT: "ops:+": 6,
// CHECK-NEXT: "reads": 12,
// CHECK-NEXT: "writes": 6
// RUN: npcomp-opt %s -aten-to-std |& FileCheck %s --check-prefix=CHECK-CONVERSION
// CHECK-CONVERSION-LABEL: @graph
func @graph(%arg0: tensor<1x2x3xf32>, %arg1: tensor<1x2x3xf32>) -> tensor<1x2x3xf32> {
%1 = "aten.constant"() {type = "i32", value = 1 : i32} : () -> i32
%2 = "aten.add"(%arg0, %arg1, %1) : (tensor<1x2x3xf32>, tensor<1x2x3xf32>, i32) -> tensor<1x2x3xf32>
"std.return"(%2) : (tensor<1x2x3xf32>) -> ()
}

View File

@ -0,0 +1,20 @@
// RUN: npcomp-opt %s -aten-layer-name -aten-op-report |& FileCheck %s
// CHECK-LABEL: "L1-addmm-0": {
// CHECK-NEXT: "activation_in": 1024,
// CHECK-NEXT: "activation_out": 16,
// CHECK-NEXT: "ops:+": 16,
// CHECK-NEXT: "ops:MAC": 16384,
// CHECK-NEXT: "parameters_in": 16400,
// CHECK-NEXT: "reads": 17424,
// CHECK-NEXT: "writes": 16
//
module {
func @graph(%arg0: tensor<1x1024xf32>, %arg1: tensor<16x1024xf32>, %arg2: tensor<16xf32>) -> tensor<1x16xf32> {
%0 = "aten.t"(%arg1) : (tensor<16x1024xf32>) -> tensor<1024x16xf32>
%1 = "aten.constant"() {type = "i32", value = 1 : i32} : () -> i32
%2 = "aten.constant"() {type = "i32", value = 1 : i32} : () -> i32
%3 = "aten.addmm"(%arg2, %arg0, %0, %1, %2) : (tensor<16xf32>, tensor<1x1024xf32>, tensor<1024x16xf32>, i32, i32) -> tensor<1x16xf32>
"std.return"(%3) : (tensor<1x16xf32>) -> ()
}
}

View File

@ -0,0 +1,10 @@
// RUN: npcomp-opt %s -aten-layer-name -aten-to-std |& FileCheck %s
// CHECK: @graph
module {
func @graph(%arg0: tensor<64x36864xf32>) -> tensor<64x64x24x24xf32> {
%0 = "aten.constant"() {type = "List[i32]", value = dense<[64, 64, 24, 24]> : vector<4xi32>} : () -> !aten.list<i32>
%1 = "aten.constant"() {type = "List[i32]", value = dense<[1, 576, 24, 1]> : vector<4xi32>} : () -> !aten.list<i32>
%2 = "aten.as_strided"(%arg0, %0, %1) {layer_name = "L0-as_strided-0"} : (tensor<64x36864xf32>, !aten.list<i32>, !aten.list<i32>) -> tensor<64x64x24x24xf32>
return %2 : tensor<64x64x24x24xf32>
}
}

View File

@ -0,0 +1,24 @@
// RUN: npcomp-opt %s -aten-layer-name -aten-op-report |& FileCheck %s
// CHECK-LABEL: "L0-batch_norm-0": {
// CHECK-NEXT: "activation_in": 103320,
// CHECK-NEXT: "activation_out": 103320,
// CHECK-NEXT: "ops:*": 310206,
// CHECK-NEXT: "ops:+": 413280,
// CHECK-NEXT: "ops:-": 123,
// CHECK-NEXT: "ops:/": 123,
// CHECK-NEXT: "ops:sqrt": 123,
// CHECK-NEXT: "parameters_in": 246,
// CHECK-NEXT: "reads": 103566,
// CHECK-NEXT: "writes": 103320
module {
func @graph(%arg0: tensor<42x123x4x5xf32>, %arg1: tensor<123xf32>, %arg2: tensor<123xf32>, %arg3: tensor<123xf32>, %arg4: tensor<123xf32>, %arg5: tensor<?xi64>) -> tensor<42x123x4x5xf32> {
%0 = "aten.constant"() {type = "bool", value = 0 : i1} : () -> i1
%1 = "aten.constant"() {type = "f32", value = 1.000000e-01 : f32} : () -> f32
%2 = "aten.constant"() {type = "f32", value = 9.99999974E-6 : f32} : () -> f32
%3 = "aten.constant"() {type = "bool", value = 1 : i1} : () -> i1
%4:3 = "aten.batch_norm"(%arg0, %arg1, %arg2, %arg3, %arg4, %0, %1, %2, %3) : (tensor<42x123x4x5xf32>, tensor<123xf32>, tensor
<123xf32>, tensor<123xf32>, tensor<123xf32>, i1, f32, f32, i1) -> (tensor<42x123x4x5xf32>, tensor<123xf32>, tensor<123xf32>)
return %4#0 : tensor<42x123x4x5xf32>
}
}

View File

@ -0,0 +1,25 @@
// RUN: npcomp-opt %s -aten-layer-name -aten-op-report |& FileCheck %s
// CHECK-LABEL: "L0-_convolution-0": {
// CHECK-NEXT: "activation_in": 32768,
// CHECK-NEXT: "activation_out": 65536,
// CHECK-NEXT: "ops:+": 65536,
// CHECK-NEXT: "ops:MAC": 6422528,
// CHECK-NEXT: "parameters_in": 1584,
// CHECK-NEXT: "reads": 34352,
// CHECK-NEXT: "writes": 65536
module {
func @graph(%arg0: tensor<1x2x128x128xf32>, %arg1: tensor<16x2x7x7xf32>, %arg2: tensor<16xf32>) -> tensor<1x16x64x64xf32> {
%0 = "aten.constant"() {type = "List[i32]", value = dense<2> : vector<2xi64>} : () -> !aten.list<i32>
%1 = "aten.constant"() {type = "List[i32]", value = dense<3> : vector<2xi64>} : () -> !aten.list<i32>
%2 = "aten.constant"() {type = "List[i32]", value = dense<1> : vector<2xi64>} : () -> !aten.list<i32>
%3 = "aten.constant"() {type = "bool", value = 0 : i1} : () -> i1
%4 = "aten.constant"() {type = "List[i32]", value = dense<0> : vector<2xi64>} : () -> !aten.list<i32>
%5 = "aten.constant"() {type = "i32", value = 1 : i32} : () -> i32
%6 = "aten.constant"() {type = "bool", value = 0 : i1} : () -> i1
%7 = "aten.constant"() {type = "bool", value = 0 : i1} : () -> i1
%8 = "aten.constant"() {type = "bool", value = 1 : i1} : () -> i1
%9 = "aten._convolution"(%arg0, %arg1, %arg2, %0, %1, %2) : (tensor<1x2x128x128xf32>, tensor<16x2x7x7xf32>, tensor<16xf32>, !aten.list<i32>, !aten.list<i32>, !aten.list<i32>) -> tensor<1x16x64x64xf32>
"std.return"(%9) : (tensor<1x16x64x64xf32>) -> ()
}
}

View File

@ -0,0 +1,23 @@
// RUN: npcomp-opt %s -aten-layer-name -aten-op-report |& FileCheck %s
// CHECK-LABEL: "L0-convolution_backward_overrideable-0": {
// CHECK-NEXT: "activation_in": 5568,
// CHECK-NEXT: "grad": 5380,
// CHECK-NEXT: "ops:+": 768,
// CHECK-NEXT: "ops:MAC": 345600,
// CHECK-NEXT: "parameters_in": 576,
// CHECK-NEXT: "reads": 6144,
// CHECK-NEXT: "writes": 5380
// CHECK-NEXT: }
// RUN: npcomp-opt %s -aten-to-std |& FileCheck %s --check-prefix=CHECK-CONVERSION
// CHECK-CONVERSION-LABEL: @graph
module {
func @graph(%arg0: tensor<3x4x8x8xf32>, %arg1: tensor<3x16x10x10xf32>, %arg2: tensor<4x16x3x3xf32>) -> tensor<4x16x3x3xf32> {
%0 = "aten.constant"() {type = "List[i32]", value = dense<1> : vector<2xi32>} : () -> !aten.list<i32>
%1 = "aten.constant"() {type = "List[i32]", value = dense<0> : vector<2xi32>} : () -> !aten.list<i32>
%2 = "aten.constant"() {type = "bool", value = false} : () -> i1
%3 = "aten.constant"() {type = "i32", value = 1 : i32} : () -> i32
%10:3 = "aten.convolution_backward_overrideable"(%arg0, %arg1, %arg2, %0, %1, %0, %2, %1, %3) {layer_name = "L5-convolution_backward_overrideable-0"} : (tensor<3x4x8x8xf32>, tensor<3x16x10x10xf32>, tensor<4x16x3x3xf32>, !aten.list<i32>, !aten.list<i32>, !aten.list<i32>, i1, !aten.list<i32>, i32) -> (tensor<3x16x10x10xf32>, tensor<4x16x3x3xf32>, tensor<4xf32>)
return %10#1 : tensor<4x16x3x3xf32>
}
}

View File

@ -0,0 +1,19 @@
// RUN: npcomp-opt %s -aten-layer-name -aten-op-report |& FileCheck %s
// CHECK-LABEL: "L0-max_pool2d-0": {
// CHECK-NEXT: "activation_in": 8192,
// CHECK-NEXT: "activation_out": 2048,
// CHECK-NEXT: "ops:>": 16384,
// CHECK-NEXT: "reads": 8192,
// CHECK-NEXT: "writes": 2048
module {
func @graph(%arg0: tensor<1x32x16x16xf32>) -> tensor<1x32x8x8xf32> {
%0 = "aten.constant"() {type = "List[i32]", value = dense<3> : vector<2xi64>} : () -> !aten.list<i32>
%1 = "aten.constant"() {type = "List[i32]", value = dense<2> : vector<2xi64>} : () -> !aten.list<i32>
%2 = "aten.constant"() {type = "List[i32]", value = dense<1> : vector<2xi64>} : () -> !aten.list<i32>
%3 = "aten.constant"() {type = "List[i32]", value = dense<1> : vector<2xi64>} : () -> !aten.list<i32>
%4 = "aten.constant"() {type = "bool", value = 0 : i1} : () -> i1
%5 = "aten.max_pool2d"(%arg0, %0, %1, %2, %3, %4) : (tensor<1x32x16x16xf32>, !aten.list<i32>, !aten.list<i32>, !aten.list<i32>, !aten.list<i32>, i1) -> tensor<1x32x8x8xf32>
"std.return"(%5) : (tensor<1x32x8x8xf32>) -> ()
}
}

View File

@ -0,0 +1,15 @@
// RUN: npcomp-opt %s -aten-layer-name -aten-op-report |& FileCheck %s
// CHECK-LABEL: "L0-relu-0": {
// CHECK-NEXT: "activation_in": 6,
// CHECK-NEXT: "activation_out": 6,
// CHECK-NEXT: "ops:>": 6,
// CHECK-NEXT: "reads": 6,
// CHECK-NEXT: "writes": 6
module {
func @graph(%arg0: tensor<1x2x3xf32>) -> tensor<1x2x3xf32> {
%0 = "aten.relu"(%arg0) : (tensor<1x2x3xf32>) -> tensor<1x2x3xf32>
"std.return"(%0) : (tensor<1x2x3xf32>) -> ()
}
}

View File

@ -0,0 +1,61 @@
// RUN: npcomp-opt %s -aten-layer-name -aten-op-report |& FileCheck %s
// CHECK-LABEL: "L0-native_batch_norm-0": {
// CHECK-LABEL: "L1-relu-0": {
// CHECK-LABEL: "L2-_convolution-0": {
// CHECK-LABEL: "L3-native_batch_norm-1": {
// CHECK-LABEL: "L4-relu-1": {
// CHECK-LABEL: "L5-_convolution-1": {
// CHECK-LABEL: "L6-native_batch_norm-2": {
// CHECK-LABEL: "L7-relu-2": {
// CHECK-LABEL: "L8-_convolution-2": {
// CHECK-LABEL: "L9-add-0": {
module {
func @graph(%arg0: tensor<1x16x128x128xf32>, %arg1: tensor<1x16x128x128xf32>, %arg2: tensor<16xf32>, %arg3: tensor<16xf32>, %arg4: tensor<16xf32>, %arg5: tensor<16xf32>, %arg6: tensor<8x16x1x1xf32>, %arg7: tensor<8xf32>, %arg8: tensor<8xf32>, %arg9: tensor<8xf32>, %arg10: tensor<8xf32>, %arg11: tensor<8xf32>, %arg12: tensor<8x8x3x3xf32>, %arg13: tensor<8xf32>, %arg14: tensor<8xf32>, %arg15: tensor<8xf32>, %arg16: tensor<8xf32>, %arg17: tensor<8xf32>, %arg18: tensor<16x8x1x1xf32>, %arg19: tensor<16xf32>) -> tensor<1x16x128x128xf32> {
%0 = "aten.constant"() {type = "bool", value = 1 : i1} : () -> i1
%1 = "aten.constant"() {type = "f32", value = 1.000000e-01 : f32} : () -> f32
%2 = "aten.constant"() {type = "f32", value = 9.99999974E-6 : f32} : () -> f32
%3:3 = "aten.native_batch_norm"(%arg1, %arg2, %arg3, %arg4, %arg5, %0, %1, %2) : (tensor<1x16x128x128xf32>, tensor<16xf32>, tensor<16xf32>, tensor<16xf32>, tensor<16xf32>, i1, f32, f32) -> (tensor<1x16x128x128xf32>, tensor<16xf32>, tensor<16xf32>)
%4 = "aten.relu"(%3#0) : (tensor<1x16x128x128xf32>) -> tensor<1x16x128x128xf32>
%5 = "aten.constant"() {type = "List[i32]", value = dense<1> : vector<2xi32>} : () -> !aten.list<i32>
%6 = "aten.constant"() {type = "List[i32]", value = dense<0> : vector<2xi32>} : () -> !aten.list<i32>
%7 = "aten.constant"() {type = "List[i32]", value = dense<1> : vector<2xi32>} : () -> !aten.list<i32>
%8 = "aten.constant"() {type = "bool", value = 0 : i1} : () -> i1
%9 = "aten.constant"() {type = "List[i32]", value = dense<0> : vector<2xi32>} : () -> !aten.list<i32>
%10 = "aten.constant"() {type = "i32", value = 1 : i32} : () -> i32
%11 = "aten.constant"() {type = "bool", value = 0 : i1} : () -> i1
%12 = "aten.constant"() {type = "bool", value = 1 : i1} : () -> i1
%13 = "aten._convolution"(%4, %arg6, %arg7, %5, %6, %7) : (tensor<1x16x128x128xf32>, tensor<8x16x1x1xf32>, tensor<8xf32>, !aten.list<i32>, !aten.list<i32>, !aten.list<i32>) -> tensor<1x8x128x128xf32>
%14 = "aten.constant"() {type = "bool", value = 1 : i1} : () -> i1
%15 = "aten.constant"() {type = "f32", value = 1.000000e-01 : f32} : () -> f32
%16 = "aten.constant"() {type = "f32", value = 9.99999974E-6 : f32} : () -> f32
%17:3 = "aten.native_batch_norm"(%13, %arg8, %arg9, %arg10, %arg11, %14, %15, %16) : (tensor<1x8x128x128xf32>, tensor<8xf32>, tensor<8xf32>, tensor<8xf32>, tensor<8xf32>, i1, f32, f32) -> (tensor<1x8x128x128xf32>, tensor<8xf32>, tensor<8xf32>)
%18 = "aten.relu"(%17#0) : (tensor<1x8x128x128xf32>) -> tensor<1x8x128x128xf32>
%19 = "aten.constant"() {type = "List[i32]", value = dense<1> : vector<2xi32>} : () -> !aten.list<i32>
%20 = "aten.constant"() {type = "List[i32]", value = dense<1> : vector<2xi32>} : () -> !aten.list<i32>
%21 = "aten.constant"() {type = "List[i32]", value = dense<1> : vector<2xi32>} : () -> !aten.list<i32>
%22 = "aten.constant"() {type = "bool", value = 0 : i1} : () -> i1
%23 = "aten.constant"() {type = "List[i32]", value = dense<0> : vector<2xi32>} : () -> !aten.list<i32>
%24 = "aten.constant"() {type = "i32", value = 1 : i32} : () -> i32
%25 = "aten.constant"() {type = "bool", value = 0 : i1} : () -> i1
%26 = "aten.constant"() {type = "bool", value = 1 : i1} : () -> i1
%27 = "aten._convolution"(%18, %arg12, %arg13, %19, %20, %21) : (tensor<1x8x128x128xf32>, tensor<8x8x3x3xf32>, tensor<8xf32>, !aten.list<i32>, !aten.list<i32>, !aten.list<i32>) -> tensor<1x8x128x128xf32>
%28 = "aten.constant"() {type = "bool", value = 1 : i1} : () -> i1
%29 = "aten.constant"() {type = "f32", value = 1.000000e-01 : f32} : () -> f32
%30 = "aten.constant"() {type = "f32", value = 9.99999974E-6 : f32} : () -> f32
%31:3 = "aten.native_batch_norm"(%27, %arg14, %arg15, %arg16, %arg17, %28, %29, %30) : (tensor<1x8x128x128xf32>, tensor<8xf32>, tensor<8xf32>, tensor<8xf32>, tensor<8xf32>, i1, f32, f32) -> (tensor<1x8x128x128xf32>, tensor<8xf32>, tensor<8xf32>)
%32 = "aten.relu"(%31#0) : (tensor<1x8x128x128xf32>) -> tensor<1x8x128x128xf32>
%33 = "aten.constant"() {type = "List[i32]", value = dense<1> : vector<2xi32>} : () -> !aten.list<i32>
%34 = "aten.constant"() {type = "List[i32]", value = dense<0> : vector<2xi32>} : () -> !aten.list<i32>
%35 = "aten.constant"() {type = "List[i32]", value = dense<1> : vector<2xi32>} : () -> !aten.list<i32>
%36 = "aten.constant"() {type = "bool", value = 0 : i1} : () -> i1
%37 = "aten.constant"() {type = "List[i32]", value = dense<0> : vector<2xi32>} : () -> !aten.list<i32>
%38 = "aten.constant"() {type = "i32", value = 1 : i32} : () -> i32
%39 = "aten.constant"() {type = "bool", value = 0 : i1} : () -> i1
%40 = "aten.constant"() {type = "bool", value = 1 : i1} : () -> i1
%41 = "aten._convolution"(%32, %arg18, %arg19, %33, %34, %35) : (tensor<1x8x128x128xf32>, tensor<16x8x1x1xf32>, tensor<16xf32>, !aten.list<i32>, !aten.list<i32>, !aten.list<i32>) -> tensor<1x16x128x128xf32>
%42 = "aten.constant"() {type = "i32", value = 1 : i32} : () -> i32
%43 = "aten.add"(%arg0, %41, %42) : (tensor<1x16x128x128xf32>, tensor<1x16x128x128xf32>, i32) -> tensor<1x16x128x128xf32>
return %43 : tensor<1x16x128x128xf32>
}
}

View File

@ -0,0 +1,26 @@
// RUN: npcomp-opt %s -aten-to-std |& FileCheck %s --check-prefix=CHECK-CONVERSION
// CHECK-CONVERSION-LABEL: @graph
module {
func @graph(%arg0: tensor<10xf32>, %arg1: tensor<128xf32>, %arg2: tensor<4x1x28x28xf32>, %arg3: tensor<32x1x3x3xf32>, %arg4: tensor<32xf32>, %arg5: tensor<64x32x3x3xf32>, %arg6: tensor<64xf32>, %arg7: tensor<128x9216xf32>, %arg8: tensor<10x128xf32>) -> tensor<4x10xf32> {
%0 = "aten.constant"() {type = "List[i32]", value = dense<1> : vector<2xi32>} : () -> !aten.list<i32>
%1 = "aten.constant"() {type = "List[i32]", value = dense<0> : vector<2xi32>} : () -> !aten.list<i32>
%2 = "aten.constant"() {type = "bool", value = false} : () -> i1
%3 = "aten.constant"() {type = "i32", value = 1 : i32} : () -> i32
%4 = "aten.constant"() {type = "bool", value = true} : () -> i1
%5 = "aten._convolution"(%arg2, %arg3, %arg4, %0, %1, %0) {layer_name = "L0-_convolution-0"} : (tensor<4x1x28x28xf32>, tensor<32x1x3x3xf32>, tensor<32xf32>, !aten.list<i32>, !aten.list<i32>, !aten.list<i32>) -> tensor<4x32x26x26xf32>
%6 = "aten.relu"(%5) {layer_name = "L1-relu-0"} : (tensor<4x32x26x26xf32>) -> tensor<4x32x26x26xf32>
%7 = "aten._convolution"(%6, %arg5, %arg6, %0, %1, %0) {layer_name = "L2-_convolution-1"} : (tensor<4x32x26x26xf32>, tensor<64x32x3x3xf32>, tensor<64xf32>, !aten.list<i32>, !aten.list<i32>, !aten.list<i32>) -> tensor<4x64x24x24xf32>
%8 = "aten.constant"() {type = "List[i32]", value = dense<2> : vector<2xi32>} : () -> !aten.list<i32>
%9:2 = "aten.max_pool2d_with_indices"(%7, %8, %8, %1, %0, %2) {layer_name = "L3-max_pool2d_with_indices-0"} : (tensor<4x64x24x24xf32>, !aten.list<i32>, !aten.list<i32>, !aten.list<i32>, !aten.list<i32>, i1) -> (tensor<4x64x12x12xf32>, tensor<4x64x12x12xi64>)
%10 = "aten.constant"() {type = "List[i32]", value = dense<[4, 9216]> : vector<2xi32>} : () -> !aten.list<i32>
%11 = "aten.view"(%9#0, %10) {layer_name = "L4-view-0"} : (tensor<4x64x12x12xf32>, !aten.list<i32>) -> tensor<4x9216xf32>
%12 = "aten.t"(%arg7) {layer_name = "L5-t-0"} : (tensor<128x9216xf32>) -> tensor<9216x128xf32>
%13 = "aten.addmm"(%arg1, %11, %12, %3, %3) {layer_name = "L6-addmm-0"} : (tensor<128xf32>, tensor<4x9216xf32>, tensor<9216x128xf32>, i32, i32) -> tensor<4x128xf32>
%14 = "aten.relu"(%13) {layer_name = "L7-relu-1"} : (tensor<4x128xf32>) -> tensor<4x128xf32>
%15 = "aten.t"(%arg8) {layer_name = "L8-t-1"} : (tensor<10x128xf32>) -> tensor<128x10xf32>
%16 = "aten.addmm"(%arg0, %14, %15, %3, %3) {layer_name = "L9-addmm-1"} : (tensor<10xf32>, tensor<4x128xf32>, tensor<128x10xf32>, i32, i32) -> tensor<4x10xf32>
%17 = "aten._log_softmax"(%16, %3, %2) {layer_name = "L10-_log_softmax-0"} : (tensor<4x10xf32>, i32, i1) -> tensor<4x10xf32>
return %17 : tensor<4x10xf32>
}
}