Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions paddle/framework/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ cc_library(ddim SRCS ddim.cc)
cc_test(ddim_test SRCS ddim_test.cc DEPS ddim)

nv_test(dim_test SRCS dim_test.cu DEPS ddim)

cc_test(variable_test SRCS variable_test.cc)
2 changes: 0 additions & 2 deletions paddle/framework/ddim_test.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//#include <stdexcept>
//#include <unittest/unittest.h>
#include <sstream>
#include <vector>

Expand Down
68 changes: 68 additions & 0 deletions paddle/framework/variable.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once

#include <memory>
#include <typeindex>
#include <typeinfo>

#include "paddle/platform/assert.h"

namespace paddle {
namespace framework {

class Variable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the variable design, we want to add Reset(T* ptr) function, which can delete the hosted pointer and set a new ptr. This interface is useful when we want to reuse a Variable and do not need to new another empty Variable. Should we add this function?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had Reset but deleted it later, because it is out of the imagined usage in the design doc (README.md). I think we can add it at any time in the future if we believe we do need it.

public:
template <typename T>
const T& Get() const {
PADDLE_ASSERT(holder_ != nullptr);
PADDLE_ASSERT(std::type_index(typeid(T)) ==
std::type_index(holder_->Type()));
return *static_cast<const T*>(holder_->Ptr());
}

template <typename T>
T* GetMutable() {
if (holder_ == nullptr ||
std::type_index(typeid(T)) != std::type_index(holder_->Type())) {
holder_.reset(new PlaceholderImpl<T>(new T()));
}
return static_cast<T*>(holder_->Ptr());
}

private:
struct Placeholder {
virtual ~Placeholder() {}
virtual const std::type_info& Type() const = 0;
virtual void* Ptr() const = 0;
};

// Placeholder hides type T, so it doesn't appear as a template
// parameter of Variable.
template <typename T>
struct PlaceholderImpl : public Placeholder {
PlaceholderImpl(T* ptr) : ptr_(ptr), type_(typeid(T)) {}

virtual const std::type_info& Type() const { return type_; }
virtual void* Ptr() const { return static_cast<void*>(ptr_.get()); }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also want to use std::unique_ptr for boost::any and do not manage the delete method in the variable design at first. But it needs to use get() method when returning the hosted pointer. I'm not sure whether it is good by returning the value using get() method.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since cuDNN and other BLAS libraries would anyway need a raw pointer from unique_ptr or other smart pointers, I think it is OK (and indeed a mandatory) to call get().


BTW, I guess you wanted to say "We had thought about using std::unique_ptr ..."? :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. Thanks! :)


std::unique_ptr<T> ptr_;
const std::type_info& type_;
Copy link
Contributor

@qingqing01 qingqing01 Jun 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a unique id for the different type in the Caffe2's TypeMeta. The id is also used for type check in run-time. And the serializer method is created base on this id.

Here, we store the std::type_info to make check in run-time. There is a hash_code function in std:: type_info. This is equivalent to the unique id in the TypeMeta. Just comment here.

};

std::unique_ptr<Placeholder>
holder_; // pointers to a PlaceholderImpl object indeed.
};

} // namespace framework
} // namespace paddle
52 changes: 52 additions & 0 deletions paddle/framework/variable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Design Doc: Variable


Variable is also known as *blob* in MxNet and Caffe2. It is the input and output type of operators, where a neural network is a graph of operators.

## Requirements: Lazy Memory Allocation

For the flexibility of a DL system, a variable should be able to contain any typed value -- a tensor in most cases, but could also be some integer IDs or a scope of other variables in the case of RNN.

To use the minimum amount of memory, we'd like that a variable to allocate memory when it has to, or, lazy memory allocation. Let's take the following example:

```cpp
Variable vr, v1, v2;

Tensor* t1 = new Tensor();
Tensor* t2 = new Tensor();

Randomize(
/* malloc */ v1.GetMutable<Tensor>().mutable_data<float16>(DDim(100,200)),
/* size */ t1.Size());

Randomize(
/* malloc */ v2.GetMutable<Tensor>().mutable_data<float16>(DDim(200,300)),
/* size */ t2.Size());

Mult(
/*result*/ vr.GetMutable<Tensor>().mutable_data<v1.Type()>(SizeOfMult(v1, v2)),
/*input1*/ v1.Get<Tensor>().data(),
/*input2*/ v2.Get<Tensor>().data());
```

We see that a variable holds nothing until `Variable::GetMutable<Tensor>()` allocates a tensor and puts it in the variable. Similarly, a tensor gets its memory until `Tensor::mutable_data()`.
Copy link
Collaborator

@JiayiFeng JiayiFeng Jun 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that 'lazy allocation' is split into two parts: the part in Variable and the part in Tensor. I am wondering whether following design will be a little better:

In Tensor:

Tensor<Device, T, D> {
  // return an empty tensor with ptr_ pointed to nullptr
  Tensr();
  // return a tensor with newly allocated memory
  Tensor(Dim<D> size);
  // check whether ptr_ points to nullptr
  bool is_empty();
}

In Variable:
before GetMutable<Tensor>() is called, Variable is assigned with an empty Tenosr which is constructed by Tensor::Tensor(). When GetMutable<Tensor>() is called, it checks whether the tensor is empty, if so, construct a new tensor by Tensor::Tensor(Dim<D> size) and then replace the empty one with it.

The replacement costs little time because the empty tensor holds no memory.

The benefit of this design is that all implement of 'lazy allocation' is now limited in Variable::GetMutable<Tensor>(), and we have no need to think about it during tensor implementation. It is a little like Majel's approach.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Variable can hold anything -- it doens't have to be a Tensor, it could be a Scope even. Thus we might not initialize a Variable with an empty tensor.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks!


This syntax for lazy memory allocation when we call `Randomize` and `Mult`, those functions that mutate the variable, so it saves us some line of C++ code.


## Implementation: Type Hiding

To make memory allocation lazy, we cannot assume that we know the type held by a variable at definition time. In other words, `class Variable` cannot be a template `template <T> class Variable`.

Because we don't know the type `T`, we cannot save a `T*` as `Variable's` data member. Instead, we save an interface object `Placeholder`, who can return the pointer to the saved object via `Placeholder::Ptr()` as `void*`.

But anyway, Variable needs to know `T` so could it `delete<T>(ptr)` and so could `Variable::Get` checks the expected type and the saved object's type.

We save `T` in `PlaceholderImpl`, the implementation of `Placeholder`. Please be aware that `PlaceholderImpl` is a class template and `T` is passed in as a template parameter.

Because `PlaceholderImpl` knows `T`, it can save and return `typeid(T)` for the type comparison in `Variable::Get` and `Variable::GetMutable`.


## Conclusion

The technique type hiding utilizes C++ class templates, interface and derivation, and C++ RTTI (typeid). This combination saves us from definition something like `caffe2::TypeMata`, which takes hundreds of lines of C++ code.
40 changes: 40 additions & 0 deletions paddle/framework/variable_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include <memory>
#include <string>

#include "gtest/gtest.h"
#include "paddle/framework/variable.h"

TEST(Variable, GetMutable) {
using paddle::framework::Variable;

struct Tensor {
int content_;
};

std::unique_ptr<Variable> v(new Variable());

Tensor* t = v->GetMutable<Tensor>();
t->content_ = 1234;

const Tensor& tt = v->Get<Tensor>();
EXPECT_EQ(1234, tt.content_);

std::string* s = v->GetMutable<std::string>();
*s = "hello";

const std::string& ss = v->Get<std::string>();
EXPECT_EQ("hello", ss);
}