mshadow的原理--MXNet

mshadow的原理--MXNet

這文章主要解釋了表達式模板的工做原理(也是mshadow的主要原理),文章的前半部分是翻譯自exp-template/README.md。咱們會解釋它爲何會影響編譯代碼的性能,表達式模板也是C++矩陣運算庫的用到的主要技巧,好比Eigen,GSL,boost.uBLAS。html

如何寫出機器學習的高效代碼?

在開始以前,咱們先考一個問題,假如更新的規則以下:(這裏是爲了達到解釋的目的,一般更新規則是這樣的:weight += - eta * (grad + lambda * weight)c++

weight =  - eta * (grad + lambda * weight);

這裏權重與梯度都是長度爲n的向量。當你選擇C++做爲你的編程語言時,我想你主要考慮是效率。下面這個很重要而且用在大多數C/C++程序中:git

  • 預先分配必要的內存,在程序運行的過程當中沒有臨時內存

這裏是一個例子:github

void UpdateWeight (const float *grad, float eta, float lambda,
                   int n, float *weight) {
  for (int i = 0; i < n; ++i) {
    weight[i] =  - eta * (grad[i] + lambda * weight[i]);
  }
}

這個函數用了預先分配的梯度、權重空間來計算,寫這樣的一個函數十分簡單,然而當咱們要重複這樣寫的時候會十分煩惱。因此問題是若是咱們寫成如下的樣子,能獲得和上面代碼同樣的性能嗎?算法

void UpdateWeight (const Vec& grad, float eta, float lambda, Vec& weight) {
  weight = -eta * (grad + lambda * weight);
}

答案是能夠的,但這不是最顯然的答案。express

一個原始的方法

讓咱們首先來看一下最直接的解決方法:運算符重載。編程

// Naive solution for vector operation overloading 
struct Vec {
  int len;
  float* dptr;
  Vec(int len) : len(len) { 
    dptr = new float[len];
  }
  Vec(const Vec& src) : len(src.len) {
    dptr = new float[len];
    memcpy(dptr, src.dptr, sizeof(float)*len ); 
  }
  ~Vec(void) {
    delete [] dptr;
  }
};

inline Vec operator+(const Vec &lhs, const Vec &rhs) {
  Vec res(lhs.len);
  for (int i = 0; i < lhs.len; ++i) {
    res.dptr[i] = lhs.dptr[i] + rhs.dptr[i];
  } 
  return res;
}

若是咱們用一樣的方式增長更多的運算符重載,咱們就能夠獲得咱們的想要的直接寫等式而不是循環的方法。然而,這種方法明顯是低效的,由於中間有臨時內存被分配與釋放,因此咱們能作得更好。數組

有一種更高效的選擇是:咱們能夠僅重載運算符+=,-=,這兩個運算符是不用分配臨時內存的,可是這會限制咱們寫等式。等會咱們將會討論爲何咱們須要表達式模板,雖然C++11在本教程的末尾提供了移動賦值運算符和右值引用。數據結構

延遲計算

在作運算符+時,爲何咱們要分配臨時內存呢?這是由於咱們不知道將在運算符+中分配的目標,不然咱們能夠直接將結果存入到目標中,而不是放在臨時變量中。app

可是若是咱們知道目標呢?這個結代碼的實如今exp_lazy.cpp中:

// Example Lazy evaluation code
// for simplicity, we use struct and make all members public
#include <cstdio>
struct Vec;
// expression structure holds the expression
struct BinaryAddExp {
  const Vec &lhs;
  const Vec &rhs;
  BinaryAddExp(const Vec &lhs, const Vec &rhs)
  : lhs(lhs), rhs(rhs) {}
};
// no constructor and destructor to allocate and de-allocate memory,
//  allocation done by user
struct Vec {
  int len;
  float* dptr;
  Vec(void) {}
  Vec(float *dptr, int len)
      : len(len), dptr(dptr) {}
  // here is where evaluation happens
  inline Vec &operator=(const BinaryAddExp &src) {
    for (int i = 0; i < len; ++i) {
      dptr[i] = src.lhs.dptr[i] + src.rhs.dptr[i];
    }
    return *this;
  }
};
// no evaluation happens here
inline BinaryAddExp operator+(const Vec &lhs, const Vec &rhs) {
  return BinaryAddExp(lhs, rhs);
}

const int n = 3;
int main(void) {
  float sa[n] = {1, 2, 3};
  float sb[n] = {2, 3, 4};
  float sc[n] = {3, 4, 5};
  Vec A(sa, n), B(sb, n), C(sc, n);
  // run expression
  A = B + C;
  for (int i = 0; i < n; ++i) {
    printf("%d:%f==%f+%f\n", i, A.dptr[i], B.dptr[i], C.dptr[i]);
  }
  return 0;
}

咱們實現的思想是在運算符+並無直接的計算,而是返回一個表達的對象(像抽象語法樹),當咱們重載運算符=時,咱們就能夠知道目標和全部的操做時,這樣咱們就能夠直接計算並且不須要臨時變量。一樣地,咱們定義DotDxp和在運算符=上定義延遲計算,並將矩陣(向量)的乘法定向的BLAS庫上計算。

更長的表達式與表達式模板

使用延遲計算,咱們能夠很好地避免了臨時變量的分配,可是代碼的擴展能力被限制了:

  • 咱們只能寫出A=B+C,不能寫出更出的表達式了。
  • 當咱們加入表達式時,咱們要重載更多的運算符=來計算每個等式。

這裏咱們實現了一個魔法模板程序來解決這兩個問題,代碼(exp_template.cpp)以下,代碼雖然有點長,但能夠容許你寫更多的等式。

// Example code, expression template, and more length equations
// for simplicity, we use struct and make all members public
#include <cstdio>

// this is expression, all expressions must inheritate it,
//  and put their type in subtype
template<typename SubType>
struct Exp {
  // returns const reference of the actual type of this expression
  inline const SubType& self(void) const {
    return *static_cast<const SubType*>(this);
  }
};

// binary add expression
// note how it is inheritates from Exp
// and put its own type into the template argument
template<typename TLhs, typename TRhs>
struct BinaryAddExp: public Exp<BinaryAddExp<TLhs, TRhs> > {
  const TLhs &lhs;
  const TRhs &rhs;
  BinaryAddExp(const TLhs& lhs, const TRhs& rhs)
      : lhs(lhs), rhs(rhs) {}
  // evaluation function, evaluate this expression at position i
  inline float Eval(int i) const {
    return lhs.Eval(i) + rhs.Eval(i);
  }
};
// no constructor and destructor to allocate
// and de-allocate memory, allocation done by user
struct Vec: public Exp<Vec> {
  int len;
  float* dptr;
  Vec(void) {}
  Vec(float *dptr, int len)
      :len(len), dptr(dptr) {}
  // here is where evaluation happens
  template<typename EType>
  inline Vec& operator= (const Exp<EType>& src_) {
    const EType &src = src_.self();
    for (int i = 0; i < len; ++i) {
      dptr[i] = src.Eval(i);
    }
    return *this;
  }
  // evaluation function, evaluate this expression at position i
  inline float Eval(int i) const {
    return dptr[i];
  }
};
// template add, works for any expressions
template<typename TLhs, typename TRhs>
inline BinaryAddExp<TLhs, TRhs>
operator+(const Exp<TLhs> &lhs, const Exp<TRhs> &rhs) {
  return BinaryAddExp<TLhs, TRhs>(lhs.self(), rhs.self());
}

const int n = 3;
int main(void) {
  float sa[n] = {1, 2, 3};
  float sb[n] = {2, 3, 4};
  float sc[n] = {3, 4, 5};
  Vec A(sa, n), B(sb, n), C(sc, n);
  // run expression, this expression is longer:)
  A = B + C + C;
  for (int i = 0; i < n; ++i) {
    printf("%d:%f == %f + %f + %f\n", i,
           A.dptr[i], B.dptr[i],
           C.dptr[i], C.dptr[i]);
  }
  return 0;
}

關鍵的思想是模板Exp<SubType>將派生的類做爲模板參數,這樣就能夠將這個模板的自身經過self()轉換成SubTpye(就是派生類)。BinaryAddExp如今是一個模板類,能夠將表達式複合在一塊兒,就像一個複合模式的模板版本同樣。計算經過函數Eval完成,它在BinaryAddExp中以遞歸的方式完成。

  • 因爲內聯,當函數在運算符=調用src.Eval(i) 會在編譯時被編譯成B.dptr[i] + C.dptr[i] + C.dptr[i]
  • 咱們能夠像循環同樣高效地將等式寫成逐元素的方式。

使它更靈活

經過上面的例子,模板編程編譯時能夠強大地更程序更加靈活,最後的例子比較接近mshadow了,能夠請容許用戶使用雙目運算符(exp_template_op.cpp)。

// Example code, expression template
// with binary operator definition and extension
// for simplicity, we use struct and make all members public
#include <cstdio>

// this is expression, all expressions must inheritate it,
// and put their type in subtype
template<typename SubType>
struct Exp{
  // returns const reference of the actual type of this expression
  inline const SubType& self(void) const {
    return *static_cast<const SubType*>(this);
  }
};

// binary operators
struct mul{
  inline static float Map(float a, float b) {
    return a * b;
  }
};

// binary add expression
// note how it is inheritates from Exp
// and put its own type into the template argument
template<typename OP, typename TLhs, typename TRhs>
struct BinaryMapExp: public Exp<BinaryMapExp<OP, TLhs, TRhs> >{
  const TLhs& lhs;
  const TRhs& rhs;
  BinaryMapExp(const TLhs& lhs, const TRhs& rhs)
      :lhs(lhs), rhs(rhs) {}
  // evaluation function, evaluate this expression at position i
  inline float Eval(int i) const {
    return OP::Map(lhs.Eval(i), rhs.Eval(i));
  }
};
// no constructor and destructor to allocate and de-allocate memory
// allocation done by user
struct Vec: public Exp<Vec>{
  int len;
  float* dptr;
  Vec(void) {}
  Vec(float *dptr, int len)
      : len(len), dptr(dptr) {}
  // here is where evaluation happens
  template<typename EType>
  inline Vec& operator=(const Exp<EType>& src_) {
    const EType &src = src_.self();
    for (int i = 0; i < len; ++i) {
      dptr[i] = src.Eval(i);
    }
    return *this;
  }
  // evaluation function, evaluate this expression at position i
  inline float Eval(int i) const {
    return dptr[i];
  }
};
// template binary operation, works for any expressions
template<typename OP, typename TLhs, typename TRhs>
inline BinaryMapExp<OP, TLhs, TRhs>
F(const Exp<TLhs>& lhs, const Exp<TRhs>& rhs) {
  return BinaryMapExp<OP, TLhs, TRhs>(lhs.self(), rhs.self());
}

template<typename TLhs, typename TRhs>
inline BinaryMapExp<mul, TLhs, TRhs>
operator*(const Exp<TLhs>& lhs, const Exp<TRhs>& rhs) {
  return F<mul>(lhs, rhs);
}

// user defined operation
struct maximum{
  inline static float Map(float a, float b) {
    return a > b ? a : b;
  }
};

const int n = 3;
int main(void) {
  float sa[n] = {1, 2, 3};
  float sb[n] = {2, 3, 4};
  float sc[n] = {3, 4, 5};
  Vec A(sa, n), B(sb, n), C(sc, n);
  // run expression, this expression is longer:)
  A = B * F<maximum>(C, B);
  for (int i = 0; i < n; ++i) {
    printf("%d:%f == %f * max(%f, %f)\n",
           i, A.dptr[i], B.dptr[i], C.dptr[i], B.dptr[i]);
  }
  return 0;
}

總結

到這裏爲止,你應該明白它工做的基本思想:

  • 延遲計算,使得咱們能知道全部的操做數和目標。
  • 複合模板和遞歸計算,使得咱們可以計算逐元素操做的任意複合表達式。
  • 因爲模板和內聯的設計,咱們寫出來的表達式像用循環實現更新規則的同樣高效。

因此在編寫機器學習代碼時寫表達式,並將精力集中在重要的算法部分上。

在MShadow中的表達式模板

在Mshadow的表達式模板用到的上面咱們介紹的關鍵思想,但有幾個小的不一樣點:

  • 咱們將評估代碼與表達式構建和組成代碼分開:
    • 在表達中建立Plan類用來替代Exp類的計算函數Eval,用來計算結果。
    • 這容許咱們在Plan中放置較少的變量,例如,當咱們評估數據時,咱們不須要數組長度。
    • 一個重要的緣由是CUDA內核不能使用const引用來接受類。
    • 雖然這種設計選擇是有爭議的,但咱們發現迄今爲止仍是有用的。
  • 延遲還支持複式的表達式,好比矩陣的乘法
    • 除了逐元素的表達式,咱們還支持比這樣A = dot(B.T(), C)的運算,一樣延遲表達是不須要分配臨時內存的。
  • 類型檢查和數組長度檢查。

備註

  • 表達式模板與C++11:在C ++ 11中,移動構造函數能夠用來保存重複的分配內存,這樣就省去了一些須要的表達模板。而後,仍然要分配最少一次的空間。
    • 這只是刪除了表達式模板中表達式所需的內存,好比dst = A+B+Cdst並無包括賦值前所分配的空間。
    • 若是咱們想保留全部的變量預先分配內存的語法,而且表達式執行時沒有內存分配(這是咱們在mshadow中所作的),咱們仍然須要表達式模板。

Mshadow源碼解析

Mshadow採用了表達式模板加強了c++矩陣庫的性能,四個主要的數據類型的繼承關係以下:

Tensor --> TRValue --> RValueExp --> Exp

基類Exp

能夠看到基類是Exp,除了一些基本的數據類型(如intfloat等),其它的數據類型都是繼承於Exp,Exp的設計特殊之處在於能夠將它的派生類做爲模板參數,這樣就能夠將這個模板的自身經過self()(返回的是一個不可修改的實例)或者ptrself()(返回的是一個可修改的指針)轉換成SubTpye(就是派生類)

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);
  }
};

RValueExp

RValueExp僅定義了一些賦值函數、重載運算符等,要注意的是它將表達式的類型寫成默認的kRValue=0,後面全部的數據定義時表達類型都是這個,真正改變表達式類型的是運算符,好比dot、一些重載的運算符。

template<typename Container, typename DType>
class RValueExp: public Exp<Container, DType, type::kRValue> {
    //...
}
  • 來看一下表達式的定義,能夠看到的是等級高的表達式包括了等級低的,總結起來就是
    • kRValue = 0,直接對應一個數據類,能夠用來分配數據,比。
    • kMapper = 1,表達式包含元素張量運算,將表達式映射到相同的形狀。
    • kChainer = 3,表達式能夠被寫成與其餘表達式的連接,一般它具備定義的函數Eval(i,j)中所定義,這個能從輸入的表達式中抽出結果(i,j)並和輸出到特定位置中。
    • kComplex = 7,用在其它運算中,好比dot
namespace type {
// type expression type are defined as bitmask
// subtype relationshop kRValue < kMapper < kMapper < kComplex
/*!
 * \brief this expression directly correspnds to a data class,
 *   can be used to assign data
 */
const int kRValue = 0;
/*!
 * \brief expression contains element-wise tensor operations,
 *   map a expression to same shape
 */
const int kMapper = 1;
/*!
 * \brief expression that can be chained with other expressiones
 *    Usually it have function Eval(i,j) defined, which pulls the result (i, j) from input
 *    expression and output the result at certain position.
 */
const int kChainer = 3;
/*! \brief othercase: e.g dot product */
const int kComplex = 7;
}  // namespace type

TRValue

TRValue並無什麼實現的內容,但它是全部可能Teson的超類:

template<typename Container, typename Device, int dimension, typename DType>
struct TRValue: public expr::RValueExp<Container, DType> {
};

Tensor

Tensor是咱們的計算基本數據類型,在mshadow中,除了基本類型,其它數據結果最終於都要在回到Tensor中計算,包括繼承它了類TensorContainer和用於圖模型中更抽象靈活的數據結構TBlob

template<typename Device, int dimension,
         typename DType MSHADOW_DEFAULT_DTYPE>
struct Tensor: public TRValue<Tensor<Device, dimension, DType>,
                              Device, dimension, DType> {
 public:
  //--------------------------------
  // struct memembers
  //--------------------------------
  /*! \brief whether current type lies in cpu */
  static const bool kDevCPU = Device::kDevCPU;
  /*! \brief dimension of subtype */
  static const int  kSubdim = dimension - 1;
  //--------------------------------
  // struct memembers
  //--------------------------------
  /*! \brief pointer to the data */
  DType *dptr_;
  /*! \brief shape of the tensor */
  Shape<dimension> shape_;
  /*!
   * \brief storing the stride information in x dimension
   *    this is used to deal with pitch allocation in gpu or sse(align x dimension to 64bit) for efficiency
   */
  index_t stride_;
  /*!
   * \brief stream where the computation lies
   * stream is a device dependency concept where each computation
   */
  Stream<Device> *stream_;
  //--------------------------------
  // functions
  //--------------------------------
  ...
}

TensorContainer

TensorContainer繼承自Tensor,最大的區別是TensorContainer在建立對象的時候會分配相應的空間,使用時相對比較方便。

template<typename Device, int dimension, typename DType = default_real_t>
class TensorContainer: public Tensor<Device, dimension, DType> {
}

TBlob

對於Tensor來講,它的Shape是不能改變的,但對於深度學習的圖模型來講,這顯然是不合適的。爲了適應這個需求,設計了用於圖模型中更抽象靈活的數據結構TBlob。經過TBlob的成員函數能獲取它在儲存的數據並轉於相應的Tensor

class TBlob {
 public:
  /*! \brief pointer to the data */
  void *dptr_;
  /*! \brief shape of the tensor */
  TShape shape_;
  /*!
   * \brief storing the stride information in x dimension
   */
  index_t stride_;
  /*! \brief device mask of the corresponding device */
  int dev_mask_;
  /*! \brief type flag of the tensor blob */
  int type_flag_;
  ...
}

計算的過程

表達式和對應的計算是分開的,並且用了延遲計算的思想,到賦值運算時會一塊兒計算。新構建一個運算符的表達式有以下步驟:

  • 先定義表達類,要繼承於Exp,內建構造函數存儲相應的數據。
  • 構建運算符,生成表達類。
  • 重載該表達類的Plan類,生成表達類的Plan對象,內建Eval函數計算Plan對象的值。
  • 重載該表達類的MakePlan類,用來生成該表達類的Plan類。
  • 重載該表達類的ExpInfo類,用來存儲該表達類的相關信息。

下面咱們用basic.cpp內的一段函數來講明上面的過程及原理:

40  TensorContainer<cpu, 2> lhs(Shape2(2, 3)), rhs(Shape2(2, 3)), ret(Shape2(2,2));
41  lhs = 1.0;
42  rhs = 1.0;
43  ret = implicit_dot(lhs, rhs.T());
  • 第40行:
    這個是聲明變量,正如上面所說,這裏爲TensorContainer變量分配了相應的空間。
  • 第4一、42行:
    這兩行說的是同一個東西,咱們就以41行lhs = 1.0爲例說明(過程有省略,由於調用堆棧比較深)。
    • 操做數1已是一個完整的對象了,不會再有操做,因此直接調用賦值運算符=
    • =兩邊的變量,左邊個是經常使用的類型double或者float(看編譯器),右邊是TensorContainer對象,因此調用Tensor_container.cpp中的函數:
    inline Tensor<Device, dimension, DType> &operator=(DType s) {
        return this->__assign(s);
    }
    • __assign在父類RValueExp(在文件Expresion.h)中定義了,查看數據類型,調用的是如下函數,其中saveto是一個運算符(也能夠說成操做符)。要注意的是,這個函數內有一個操做scalar<DType>(s),這個操做將類型從DTpye轉變成ScalarExp<Dtype>,並且運算符的類型變成了KMapper。另外,this->ptrself()指的是lhsscalar<DType>(s)則是1
    inline Container &__assign(DType s) {
        ExpEngine<sv::saveto, Container, DType>::Eval(this->ptrself(), scalar<DType>(s));
        return *(this->ptrself());
    }
    template<typename E>
    inline static void Eval(RV *dst, const Exp<E, DType, type::kMapper> &exp) {
        MapExp<SV>(dst, exp);
    }
    template<typename Saver, typename R, int dim, typename DType, typename E, int etype>
    inline void MapExp(TRValue<R, cpu, dim, DType> *dst, const expr::Exp<E, DType, etype> &exp) {
        ...
        MapExpCPUEngine<expr::PacketCheck<E, MSHADOW_DEFAULT_PACKET>::kPass,Saver, R, dim, DType, E, etype>
        ::Map(dst->ptrself(), exp);
    }
    
    template<bool pass_check, typename Saver, typename R, int dim, typename DType, typename E, int etype>
    struct MapExpCPUEngine {
            inline static void Map(TRValue<R, cpu, dim, DType> *dst, const expr::Exp<E, DType, etype> &exp) {
                MapPlan<Saver>(dst, MakePlan(exp.self()));
        }
    };
    • MapPlan有兩次調用,先是調用裏面的MapPlan,函數在Expr_engine-inl.h中,而且獲得Scalar<DType>對象的Plan對象。而後再調用Tensor_cpu-inl.hMapPlan
    template<typename DType>
    inline Plan<ScalarExp<DType>, DType> MakePlan(const ScalarExp<DType> &e) {
        return Plan<ScalarExp<DType>, DType>(e.scalar_);
    }
    
    template<typename Saver, typename R, int dim, typename DType, typename E>
    inline void MapPlan(TRValue<R, cpu, dim, DType> *dst, const expr::Plan<E, DType> &plan) {
        Shape<2> shape = expr::ShapeCheck<dim, R>::Check(dst->self()).FlatTo2D();
        expr::Plan<R, DType> dplan = expr::MakePlan(dst->self());
    #if (MSHADOW_USE_CUDA == 0)
        #pragma omp parallel for
    #endif
    // temp remove openmp, as default setting throttles CPU
        for (openmp_index_t y = 0; y < shape[0]; ++y) {
            for (index_t x = 0; x < shape[1]; ++x) {
            // trust your compiler! -_- they will optimize it
                Saver::template Save<DType>(dplan.REval(y, x), plan.Eval(y, x));
            }
        }
    }
    • 再調用plan.Eval(y, x)dplan.REval(y, x),兩個函數都在Expr_engine-inl.h中:
    // scalar
    template<typename DType>
    class Plan<ScalarExp<DType>, DType> {
        public:
        explicit Plan(DType scalar) : scalar_(scalar) {}
        MSHADOW_XINLINE DType Eval(index_t y, index_t x) const {
            return scalar_;
        }
        private:
        DType scalar_;
    };
    
    // tensor plan
    template <typename Device, int dim, typename DType>
    class Plan<Tensor<Device, dim, DType>, DType> {
        public:
        explicit Plan(const Tensor<Device, dim, DType> &t)
            : dptr_(t.dptr_), stride_(t.stride_) {}
        // for RValue, the return type should be reference
        MSHADOW_XINLINE DType &REval(index_t y, index_t x) {
            return dptr_[y * stride_ + x];
        }
        // const evaluation
        MSHADOW_XINLINE const DType &Eval(index_t y, index_t x) const {
            return dptr_[y * stride_ + x];
        }
    
        private:
        DType  *dptr_;
        index_t stride_;
    };
    • 最後調用的是Saver::template Save<DType>(dplan.REval(y, x), plan.Eval(y, x)),這個Save操做就是咱們以前的saveto,調用在Base.h函數:
    /*! \brief save to saver: = */
    struct saveto {
         /*! \brief save b to a using save method */
        template<typename DType>
        MSHADOW_XINLINE static void Save(DType &a, DType b) { // NOLINT(*)
            a = b;
        }
        ...
    };

    到這裏,基本的賦值操做就完成了,你會發現用表達式模板是十不直接的操做。

  • 第43行ret = implicit_dot(lhs, rhs.T())
    這個更加複雜,implicit_dot能夠當作一個獨立的操做符,若是想寫一個新的操做符,能夠參考Implicit_gemn.h。這個表達式複雜的緣由是有三個運算符——.T()implicit_dotret,要用到遞歸運算了。不管表達式多麼的複雜,咱們只要記住,除了賦值運算(其實它也沒有Plan類)外一個表達式的結果能夠用Plan.Eval來得到的。下面是一些這行代碼運算的基本過程:
    • rhs.T()這個操做在Expression.h中,返回一個TransposeExp表達式(注意的是inline的函數會在編譯中展開,這裏爲了方便理解,只是說了調用)。這裏要注意的是TransposeExp表達式的類型是kChainer
    inline const TransposeExp<Container, DType> T(void) const {
        return TransposeExp<Container, DType>(this->self());
    }
    • implicit_dot(lhs, rhs.T())函數在Implicit_gemn.h中,返回一個ImplicitGEMMExp表達式。這裏要注意的是implicit_dot表達式的類型是kChainer
    template<typename LhsExp, typename RhsExp, typename DType, int e1, int e2>
    inline ImplicitGEMMExp<LhsExp, RhsExp, DType>
    implicit_dot(const Exp<LhsExp, DType, e1> &lhs, const Exp<RhsExp, DType, e2> &rhs) {
        TypeCheckPass<ExpInfo<LhsExp>::kDim == 2 && ExpInfo<RhsExp>::kDim == 2>
            ::Error_Expression_Does_Not_Meet_Dimension_Req();
        return ImplicitGEMMExp<LhsExp, RhsExp, DType>(lhs.self(), rhs.self());
    }
    • 賦值運算符=與上面因此的步驟有相同之處,只是不一樣的類型,調用的重載函數是不同的。上面的調用的第一個MapPlan是調用Implicit_gemn.h中的,返回的是一個ImplicitGEMMExpPlan類,第二個則是同樣的。
    template<typename LhsExp, typename RhsExp, typename DType>
    inline Plan<ImplicitGEMMExp<LhsExp, RhsExp, DType>, DType>
    MakePlan(const ImplicitGEMMExp<LhsExp, RhsExp, DType> &exp) {
          return Plan<ImplicitGEMMExp<LhsExp, RhsExp, DType>, DType>(exp);
    }
    • 咱們來看程序運行到Saver::template Save<DType>(dplan.REval(y, x), plan.Eval(y, x))時,對於dplan.REval(y, x)Save咱們已經說過了,此次要說的是Plan.Eval(y, x),來看下是若是進行遞歸調用的。這個函數在Implicit_gemn.h中:
    template<typename LhsExp, typename RhsExp, typename DType>
    struct Plan<ImplicitGEMMExp<LhsExp, RhsExp, DType>, DType> {
     public:
      explicit Plan(const ImplicitGEMMExp<LhsExp, RhsExp, DType> &e)
          : lhs_(MakePlan(e.lhs_)),
            rhs_(MakePlan(e.rhs_)),
            prod_size_(e.prod_size_),
            prod_size_lower_align_(packet::LowerAlign<DType, MSHADOW_DEFAULT_PACKET>(e.prod_size_)) {
      }
    
      MSHADOW_XINLINE DType Eval(index_t y, index_t x) const {
        typedef packet::Packet<DType> Packet;
        Packet sum = Packet::Fill(0);
    
        const size_t packetSize = Packet::Size();
        DType lhs_temp[packetSize], rhs_temp[packetSize];
    
        for (index_t i = 0; i < prod_size_lower_align_; i += packetSize) {
          // unroll
          for (index_t j = 0; j < packetSize; ++j) {
            lhs_temp[j] = lhs_.Eval(y, i + j);
          }
          for (index_t j = 0; j < packetSize; ++j) {
            rhs_temp[j] = rhs_.Eval(i + j, x);
          }
          sum = sum + Packet::LoadUnAligned(lhs_temp) * Packet::LoadUnAligned(rhs_temp);
        }
        DType ret_result = sum.Sum();
    
        for (index_t i =  prod_size_lower_align_; i < prod_size_; ++i) {
          ret_result += lhs_.Eval(y, i) * rhs_.Eval(i, x);
        }
        return ret_result;
      }
    
     private:
      expr::Plan<LhsExp, DType> lhs_;
      expr::Plan<RhsExp, DType> rhs_;
      const index_t prod_size_;
      const index_t prod_size_lower_align_;
    };
    在進行計算中,要獲得lhs_rhs_表達式的值,而這兩個表達式也是Plan類,以前咱們說過:只要記得Plan.Eval是獲得表達式的值就好了。因此當調用rhs_.Eval一直遞歸到計算獲得值爲止,因此rhs_.Eval最後獲得的ret = implicit_dot(lhs, rhs.T())rhs.T()的值。
    • 餘下的過程和上述的過程差很少了。
  • 能夠看到上述的操做並無用臨時內存。
  • 對於傳統的一些操做符+-*/等,會被統一到TernaryMapExp(三目)、BinaryMapExp(雙目)、UnaryMapExp(單目)中,參考加文件Expression.h,它的相關類MakePlanPlan則定義在Exp_engine-inl.h

其它

  • 對於gpu的編程有它的一些限制,但設計的類和上而是差的多的。
  • logging.h是日誌系統,打印記錄一些系統的信息。
  • io.h是讀寫文件的操做,與mshadow相對來講是獨立的,只是它適配了一些類型的格式(好比Tensor)讀寫。

【防止爬蟲轉載而致使的格式問題——連接】:http://www.cnblogs.com/heguanyou/p/7545344.html

相關文章
相關標籤/搜索