Expression.h¶
./expression.h
consists of the definitions of abstract expressions and their related expression templates.
- Type
- Exp
- ScalarExp
: public Exp<ScalarExp<DType>, DType, type::kMapper>
- TypecastExp
: public Exp<TypecastExp<DstDType, SrcDType, EType, etype>, DstDType, etype>
- TransposeExp
: public Exp<TransposeExp<EType, DType>, DType, type::kChainer>
- ExpEngine
- RValueExp
: public Exp<Container, DType, type::kRValue>
- DotExp
: public Exp<DotExp<TA, TB, ltrans, rtrans, DType>, DType, type::kComplex>
- TernaryMapExp
: public Exp<TernaryMapExp<OP, TA, TB, TC, DType, etype>, DType, etype>
- UnaryMapExp
: public Exp<UnaryMapExp<OP, TA, DType, etype>, DType, etype>
- BinaryMapExp
: public Exp<BinaryMapExp<OP, TA, TB, DType, etype>, DType, etype>
Type¶
Every expression in MShadow has to own its own type.
kRValue
: this expression directly correspnds to a data class, can be used to assign data.kMapper
: this expression contains element-wise tensor operations, can map a expression to same shape.kChainer
: this expression can be chained with other expressions. Most of functions in file./extension/
are of this type.kComplex
: othercase: e.g dot product.
namespace type {
// type expression type are defined as bitmask
// subtype relationshop kRValue < kMapper < kPull < kComplex
const int kRValue = 0;
const int kMapper = 1;
const int kChainer = 3;
const int kComplex = 7;
}
Exp¶
The struct Exp
is the base class for every expression.
Each class inheritated from Exp
has to put their type into SubType
, while DType
is the
type of data being manipulated and exp_type
is to indicate the expression type inheritated
from it.
template<typename SubType, typename DType, int exp_type>
struct Exp {
public:
// return subtype instance of current class
inline const SubType& self(void) const {
return *static_cast<const SubType*>(this);
}
// return reference of subtype instance of current class
inline SubType* ptrself(void) {
return static_cast<SubType*>(this);
}
};
ScalarExp¶
: public Exp<ScalarExp<DType>, DType, type::kMapper>
The ScalarExp
is to map a scalar type to a sub-class ScalarExp
of expression type Exp
.
For now, we can just think explicit constructor is majorly used for avoiding the implicit consturction of class in declaration. the reason we cannot make the constructor explicit here is that, if the constructor is explicit, the occurence of scalar in either its lhs or rhs will not call this constructor.
template<typename DType>
struct ScalarExp: public Exp<ScalarExp<DType>, DType, type::kMapper> {
// scalar value
DType scalar_;
// implicit constructor, MUST NOT BE explicit
ScalarExp(DType scalar) : scalar_(scalar) {}
};
// this is a type cast function that turns the scalar s with type Dtype into type ScalarExp
template<typename DType>
inline ScalarExp<DType> scalar(DType s) {
return ScalarExp<DType>(s); // turn the scalar s with type Dtype into type ScalarExp
}
// usage
scalar<float>(10.0f)
// it is worthy noting that this process is generally implicitly performed
// (by overloaded operator in struct RValueExp)
TypecastExp¶
: public Exp<TypecastExp<DstDType, SrcDType, EType, etype>, DstDType, etype>
The TypecastExp
is to explicitly change the DType
of a expression type Exp
.
template<typename DstDType, typename SrcDType, typename EType, int etype>
struct TypecastExp:
public Exp<TypecastExp<DstDType, SrcDType, EType, etype>, DstDType, etype> {
// expression to be typecasted
const EType &exp;
// constructor
explicit TypecastExp(const EType &e) : exp(e) {}
};
// create an Typecast expression
template<typename DstDType, typename SrcDType, typename EType, int etype>
inline TypecastExp<DstDType, SrcDType, EType, (etype|type::kMapper)>
tcast(const Exp<EType, SrcDType, etype> &exp) {
return TypecastExp<DstDType, SrcDType, EType, (etype|type::kMapper)>(exp.self());
}
// usage
float data[10];
int data1[10];
Tensor<cpu,2> mat(data,Shape2(5,2));
Tensor<cpu,2,int> mat1(data1,Shape2(5,2));
mat = 3.2f;
mat1 = tcast<int>(mat);
for (index_t i = 0; i < mat.size(0); ++i) {
for (index_t j = 0; j < mat.size(1); ++j) {
printf("%.2f ", mat[i][j]);
}
printf("\n");
}
for (index_t i = 0; i < mat1.size(0); ++i) {
for (index_t j = 0; j < mat1.size(1); ++j) {
printf("%d ", mat1[i][j]);
}
printf("\n");
}
// output:
// 3.20 3.20
// 3.20 3.20
// 3.20 3.20
// 3.20 3.20
// 3.20 3.20
// 3 3
// 3 3
// 3 3
// 3 3
// 3 3
TransposeExp¶
: public Exp<TransposeExp<EType, DType>, DType, type::kChainer>
The TransposeExp
is generated by .T()
function in class RValueExp
.
Through the constructor function, a same type Tensor
is generated.
The actual transpose actually happens in the `Eval()` function.
Whether its member function `T()` is useful or not remains to be checking.
template<typename EType, typename DType>
struct TransposeExp: public Exp<TransposeExp<EType, DType>,
DType, type::kChainer> {
// expression to be transposed, the generated one is same
const EType &exp;
// constructor
explicit TransposeExp(const EType &e) : exp(e) {}
// transpose expression
inline const EType &T(void) const {
return exp;
}
};
ExpEngine¶
ExpEngine
is the struct that starts to interpret all of the expressions.
It is generated by member functions in RValueExp
, while its implementation is in ./expr_engine-inl.h
, which call the MapExp
in ./tensor_cpu-inl.h
or ./tensor_gpu-inl.h
to begin the interpretion of
expressions.
The details of its discussion is left to its implementation codes.
template<typename Saver, typename RValue, typename DType>
struct ExpEngine;
RValueExp¶
: public Exp<Container, DType, type::kRValue>
RValueExp
is a special class, that is inheritated by TRValue
(Tensor Rvalue), which is the super class
of all kinds of tensors.
Since it explicitly set the exp_type
(expression type) in Exp
to kRValue
during inheritance,
it correspnds to every situation with a form dst () src
, where dst
is the destination to receive the
result of computation, ()
is +=
/ -=
/ *=
/ /=
/ =
, and src
can be a value or expression.
As a result, in this class, we have eight reloaded operator
and two assign functions
, with a extra transpose function
.
template<typename Container, typename DType>
class RValueExp: public Exp<Container, DType, type::kRValue> {
public:
// transpose of a matrix, return transpose of current expression
// usage: *.T()
inline const TransposeExp<Container, DType> T(void) const {
return TransposeExp<Container, DType>(this->self());
}
// operator overload
// += for scalar
inline Container &operator+=(DType s) {
ExpEngine<sv::plusto, Container, DType>::Eval(this->ptrself(), scalar<DType>(s));
return *(this->ptrself());
}
// -= for scalar
inline Container &operator-=(DType s) {
ExpEngine<sv::minusto, Container, DType>::Eval(this->ptrself(), scalar<DType>(s));
return *(this->ptrself());
}
// *= for scalar
inline Container &operator*=(DType s) {
ExpEngine<sv::multo, Container, DType>::Eval(this->ptrself(), scalar<DType>(s));
return *(this->ptrself());
}
// /= for scalar
inline Container &operator/=(DType s) {
ExpEngine<sv::divto, Container, DType>::Eval(this->ptrself(), scalar<DType>(s));
return *(this->ptrself());
}
// assign for scalar
inline Container &__assign(DType s) {
ExpEngine<sv::saveto, Container, DType>::Eval(this->ptrself(), scalar<DType>(s));
return *(this->ptrself());
}
// assign for expression
template<typename E, int etype>
inline Container &__assign(const Exp<E, DType, etype> &exp) {
ExpEngine<sv::saveto, Container, DType>::Eval(this->ptrself(), exp.self());
return *(this->ptrself());
}
// not sure the difference to above function
inline Container &__assign(const Exp<Container, DType, type::kRValue> &exp);
// += for expression
template<typename E, int etype>
inline Container &operator+=(const Exp<E, DType, etype> &exp) {
ExpEngine<sv::plusto, Container, DType>::Eval(this->ptrself(), exp.self());
return *(this->ptrself());
}
// -= for expression
template<typename E, int etype>
inline Container &operator-=(const Exp<E, DType, etype> &exp) {
ExpEngine<sv::minusto, Container, DType>::Eval(this->ptrself(), exp.self());
return *(this->ptrself());
}
// *= for expression
template<typename E, int etype>
inline Container &operator*=(const Exp<E, DType, etype> &exp) {
ExpEngine<sv::multo, Container, DType>::Eval(this->ptrself(), exp.self());
return *(this->ptrself());
}
// /= for expression
template<typename E, int etype>
inline Container &operator/=(const Exp<E, DType, etype> &exp) {
ExpEngine<sv::divto, Container, DType>::Eval(this->ptrself(), exp.self());
return *(this->ptrself());
}
};
In every reloaded function, ExpEngine
is used to trigger the Eval
function,
which use template MapExp
to MakePlan
as described above.
At last, the namespace sv
is defined in the ./base.h
to perform the final assignment.
DotExp¶
: public Exp<DotExp<TA, TB, ltrans, rtrans, DType>, DType, type::kComplex>
The DotExp
is an expression to do matrix computation between two Tensors
.
template<typename TA, typename TB, bool ltrans, bool rtrans, typename DType>
struct DotExp: public Exp<DotExp<TA, TB, ltrans, rtrans, DType>, DType, type::kComplex> {
const TA &lhs_;
const TB &rhs_;
DType scale_; // scale over result
explicit DotExp(const TA &lhs, const TB &rhs, DType scale)
: lhs_(lhs), rhs_(rhs), scale_(scale) {}
};
The generation of struct DotExp
is triggered by the function dot()
with following four different
styles by consideration of transpose.
- both lhs and rhs are without transpose
template<typename TA, typename TB, typename DType>
inline DotExp<TA, TB, false, false, DType>
dot(const RValueExp<TA, DType> &lhs, const RValueExp<TB, DType> &rhs) {
return DotExp<TA, TB, false, false, DType>(lhs.self(), rhs.self(), DType(1.0f));
}
- lhs is with transpose, while rhs is not
template<typename TA, typename TB, typename DType>
inline DotExp<TA, TB, true, false, DType>
dot(const TransposeExp<TA, DType> &lhs, const RValueExp<TB, DType> &rhs) {
return DotExp<TA, TB, true, false, DType>(lhs.exp, rhs.self(), DType(1.0f));
}
- rhs is with transpose, while lhs is not
template<typename TA, typename TB, typename DType>
inline DotExp<TA, TB, false, true, DType>
dot(const RValueExp<TA, DType> &lhs, const TransposeExp<TB, DType> &rhs) {
return DotExp<TA, TB, false, true, DType>(lhs.self(), rhs.exp, DType(1.0f));
}
- both lhs and rhs are with transpose
template<typename TA, typename TB, typename DType>
inline DotExp<TA, TB, true, true, DType>
dot(const TransposeExp<TA, DType> &lhs, const TransposeExp<TB, DType> &rhs) {
return DotExp<TA, TB, true, true, DType>(lhs.exp, rhs.exp, DType(1.0f));
}
the usage of batch_dot is unclear yet!
template<bool transpose_left, bool transpose_right, typename TA, typename TB, typename DType>
inline DotExp<TA, TB, transpose_left, transpose_right, DType>
batch_dot(const RValueExp<TA, DType> &lhs, const RValueExp<TB, DType> &rhs) {
return DotExp<TA, TB, transpose_left, transpose_right, DType>(
lhs.self(), rhs.self(), DType(1.0f));
}
TernaryMapExp¶
public Exp<TernaryMapExp<OP, TA, TB, TC, DType, etype>, DType, etype>
TernaryMapExp
is designed to handle the ternary operation expression. Its member variables contain three
different expressions as their original types.
template<typename OP, typename TA, typename TB, typename TC, typename DType, int etype>
struct TernaryMapExp: public Exp<TernaryMapExp<OP, TA, TB, TC, DType, etype>, DType, etype> {
const TA &item1_;
const TB &item2_;
const TC &item3_;
explicit TernaryMapExp(const TA &item1, const TB &item2, const TC &item3)
:item1_(item1), item2_(item2), item3_(item3) {}
};
The struct TernaryMapExp
is generated by the function MakeExp
.
template<typename OP, typename TA, typename TB, typename TC, typename DType, int ta, int tb, int tc>
inline TernaryMapExp<OP, TA, TB, TC, DType, (ta|tb|tc|type::kMapper)>
MakeExp(const Exp<TA, DType, ta> &item1, const Exp<TB, DType, tb> &item2,
const Exp<TC, DType, tc> &item3) {
return TernaryMapExp<OP, TA, TB, TC, DType,
(ta|tb|tc|type::kMapper)>(item1.self(), item2.self(), item3.self());
}
The function F<op>()
provides a short hand for MakeExp()
, with a operator defined in namespace op
, which
provides Map()
function to guide the way of evaluation.
template<typename OP, typename TA, typename TB, typename TC, typename DType, int ta, int tb, int tc>
inline TernaryMapExp<OP, TA, TB, TC, DType, (ta|tb|tc|type::kMapper)>
F(const Exp<TA, DType, ta> &item1, const Exp<TB, DType, tb> &item2,
const Exp<TC, DType, tc> &item3) {
return MakeExp<OP>(item1, item2, item3);
}
UnaryMapExp¶
public public Exp<UnaryMapExp<OP, TA, DType, etype>, DType, etype>
UnaryMapExp
is designed to handle the unary operation expression. Its member variables contain only one
expression as its original type.
template<typename OP, typename TA, typename DType, int etype>
struct UnaryMapExp: public Exp<UnaryMapExp<OP, TA, DType, etype>, DType, etype> {
const TA &src_;
explicit UnaryMapExp(const TA &src) : src_(src) {}
};
The struct UnaryMapExp
is also generated by the function MakeExp
.
template<typename OP, typename TA, typename DType, int ta>
inline UnaryMapExp<OP, TA, DType, (ta|type::kMapper)>
MakeExp(const Exp<TA, DType, ta> &src) {
return UnaryMapExp<OP, TA, DType, (ta|type::kMapper)>(src.self());
}
Similarly, UnaryMapExp
also has its own function F<op>()
providing a short hand for MakeExp()
,
with a operator defined in namespace op
, which provides Map()
function to guide the way of evaluation.
template<typename OP, typename TA, typename DType, int ta>
inline UnaryMapExp<OP, TA, DType, (ta|type::kMapper)>
F(const Exp<TA, DType, ta> &src) {
return MakeExp<OP>(src);
}
BinaryMapExp¶
: public Exp<BinaryMapExp<OP, TA, TB, DType, etype>, DType, etype>
BinaryMapExp
is designed to handle the binary operation expression. Its member variables contain two different
expressions as their original type.
template<typename OP, typename TA, typename TB, typename DType, int etype>
struct BinaryMapExp: public Exp<BinaryMapExp<OP, TA, TB, DType, etype>,
DType, etype> {
const TA &lhs_;
const TB &rhs_;
explicit BinaryMapExp(const TA &lhs, const TB &rhs): lhs_(lhs), rhs_(rhs) {}
};
The struct BinaryMapExp
is also generated by the function MakeExp
.
template<typename OP, typename TA, typename TB, typename DType, int ta, int tb>
inline BinaryMapExp<OP, TA, TB, DType, (ta|tb|type::kMapper)>
MakeExp(const Exp<TA, DType, ta> &lhs, const Exp<TB, DType, tb> &rhs) {
return BinaryMapExp<OP, TA, TB, DType, (ta|tb|type::kMapper)>(lhs.self(), rhs.self());
}
Similarly, BinaryMapExp
also has its own function F<op>()
providing a short hand for MakeExp()
,
with a operator defined in namespace op
, which provides Map()
function to guide the way of evaluation.
template<typename OP, typename TA, typename TB, typename DType, int ta, int tb>
inline BinaryMapExp<OP, TA, TB, DType, (ta|tb|type::kMapper)>
F(const Exp<TA, DType, ta> &lhs, const Exp<TB, DType, tb> &rhs) {
return MakeExp<OP>(lhs, rhs);
}
Moreover, the four basic operators +
/ -
/ *
/ /
are also overloaded to make sure
we can add an Exp
type to built-in scalar type
, e.g. a+3.0f
where a
is a Tensor
.
Since the constructor of ScalarExp
is not explicit, the 3.0f
will be implicitly converted
to type ScalarExp
.
template<typename TA, typename TB, typename DType, int ta, int tb>
inline BinaryMapExp<op::plus, TA, TB, DType, (ta|tb|type::kMapper)>
operator+(const Exp<TA, DType, ta> &lhs, const Exp<TB, DType, tb> &rhs) {
return MakeExp<op::plus>(lhs, rhs);
}
template<typename TA, typename TB, typename DType, int ta, int tb>
inline BinaryMapExp<op::minus, TA, TB, DType, (ta|tb|type::kMapper)>
operator-(const Exp<TA, DType, ta> &lhs, const Exp<TB, DType, tb> &rhs) {
return MakeExp<op::minus>(lhs, rhs);
}
template<typename TA, typename TB, typename DType, int ta, int tb>
inline BinaryMapExp<op::mul, TA, TB, DType, (ta|tb|type::kMapper)>
operator*(const Exp<TA, DType, ta> &lhs, const Exp<TB, DType, tb> &rhs) {
return MakeExp<op::mul>(lhs, rhs);
}
template<typename TA, typename TB, typename DType, int ta, int tb>
inline BinaryMapExp<op::div, TA, TB, DType, (ta|tb|type::kMapper)>
operator/(const Exp<TA, DType, ta> &lhs, const Exp<TB, DType, tb> &rhs) {
return MakeExp<op::div>(lhs, rhs);
}