2018年11月28日星期三

lxc launch a new container

lxc launch ubuntu:16.04 first

lxc exec first -- /bin/bash

userdel ubuntu

adduser --home /home/u --shell /bin/bash u

usermod u -aG sudo


/etc/init.d/ssh start
or
service ssh start
or
systemctl start ssh


vi /etc/ssh/sshd_config

PasswordAuthentication no --> yes

service ssh restart


lxc config device add xxx projectdir disk source=/home/user/prjs path=/home/user/prjs

lxc stop xxx
lxc config set xxx raw.idmap 'both 1000 1000'
lxc start xxx


2018年10月26日星期五

Tensor convert to AttrTensor (tensor proto)

Breakpoint 1, main () at tf_c_test.cc:106
main () at tf_c_test.cc:115        google::protobuf::RepeatedField<float>::AddAlreadyReserved                                               L1134 PC: 0x7fffebfe66dc
#1  0x00007fffebfdf282 in google::protobuf::RepeatedField<float>::RepeatedField<float const*> (this=0x7fffffffe010, begin=0x7ffff7ff5000,
    end=@0x7fffffffe008: 0x7ffff7ff5004) at external/protobuf_archive/src/google/protobuf/repeated_field.h:1065
#2  0x00007fffebfc88cd in tensorflow::(anonymous namespace)::ProtoHelper<float>::Fill (data=0x7ffff7ff5000, n=1, proto=0xde57c0)
    at tensorflow/core/framework/tensor.cc:289
#3  0x00007fffebfd4e0f in tensorflow::(anonymous namespace)::ToProtoField<float> (in=..., n=1, out=0xde57c0) at tensorflow/core/framework/tensor.cc:587
#4  0x00007fffebfcd60e in tensorflow::Tensor::AsProtoField (this=0x7fffffffe310, proto=0xde57c0) at tensorflow/core/framework/tensor.cc:868
#5  0x00007fffebee8382 in tensorflow::SetAttrValue (value=..., out=0x7fffffffe270) at tensorflow/core/framework/attr_value_util.cc:525
#6  0x00007fffebf64191 in tensorflow::NodeDefBuilder::Attr (this=0xc1d8e0, name=..., value=...) at tensorflow/core/framework/node_def_builder.cc:280
#7  0x00007fffedef0288 in tensorflow::NodeBuilder::Attr<tensorflow::Tensor&> (this=0xc1d8e0, attr_name=..., value=...) at ./tensorflow/core/graph/node_builder.h:150
#8  0x00007fffedee2604 in TF_SetAttrTensor (desc=0xc1d8e0, attr_name=0x401d4d "value", value=0xbd1ca0, status=0x891430) at tensorflow/c/c_api.cc:1373
#9  0x00000000004016cc in Const (graph=0xc3ba10, status=0x891430, tensor=0xbd1ca0, name=0x401da7 "const") at tf_c_test.cc:59
---Type <return> to continue, or q <return> to quit---
#10 0x0000000000401ac9 in main () at tf_c_test.cc:132

2018年10月23日星期二

allocate tensor

TF_Tensor* TF_AllocateTensor(TF_DataType dtype, const int64_t* dims,
                             int num_dims, size_t len) {
  void* data = allocate_tensor("TF_AllocateTensor", len);
  return TF_NewTensor(dtype, dims, num_dims, data, len, deallocate_buffer,
                      nullptr);
}

TF_Tensor* TF_NewTensor(TF_DataType dtype, const int64_t* dims, int num_dims,
                        void* data, size_t len,
                        void (*deallocator)(void* data, size_t len, void* arg),
                        void* deallocator_arg) {
  std::vector<tensorflow::int64> dimvec(num_dims);
  for (int i = 0; i < num_dims; ++i) {
    dimvec[i] = static_cast<tensorflow::int64>(dims[i]);
  }

  TF_ManagedBuffer* buf = new TF_ManagedBuffer;
  buf->len_ = len;
  if (dtype != TF_STRING && dtype != TF_RESOURCE &&
      tensorflow::DataTypeCanUseMemcpy(static_cast<DataType>(dtype)) &&
      reinterpret_cast<intptr_t>(data) % EIGEN_MAX_ALIGN_BYTES != 0) {
    // TF_STRING and TF_RESOURCE tensors have a different representation in
    // TF_Tensor than they do in tensorflow::Tensor. So a copy here is a waste
    // (any alignment requirements will be taken care of by TF_TensorToTensor
    // and TF_TensorFromTensor).
    //
    // Other types have the same representation, so copy only if it is safe to
    // do so.
    buf->data_ = allocate_tensor("TF_NewTensor", len);
    std::memcpy(buf->data_, data, len);
    buf->deallocator_ = deallocate_buffer;
    buf->deallocator_arg_ = nullptr;
    // Free the original buffer.
    deallocator(data, len, deallocator_arg);
  } else {
    buf->data_ = data;
    buf->deallocator_ = deallocator;
    buf->deallocator_arg_ = deallocator_arg;
  }
  TF_Tensor* ret = new TF_Tensor{dtype, TensorShape(dimvec), buf};
  size_t elem_size = TF_DataTypeSize(dtype);
  if (elem_size > 0 && len < (elem_size * ret->shape.num_elements())) {
    delete ret;
    return nullptr;
  }
  return ret;
}

2018年10月22日星期一

struct TF_Tensor, class Tensor

Struct TF_Tensor is defined in c_api_internal.h

struct TF_Tensor {
  ~TF_Tensor();

  TF_DataType dtype;
  tensorflow::TensorShape shape;
  tensorflow::TensorBuffer* buffer;
};

It's simple compares to the definition to class Tensor which is defined in tensorflow/core/framework/tensor.h

/// @ingroup core
/// Represents an n-dimensional array of values.
class Tensor {
 public:
  /// \brief Creates a 1-dimensional, 0-element float tensor.
  ///
  /// The returned Tensor is not a scalar (shape {}), but is instead
  /// an empty one-dimensional Tensor (shape {0}, NumElements() ==
  /// 0). Since it has no elements, it does not need to be assigned a
  /// value and is initialized by default (IsInitialized() is
  /// true). If this is undesirable, consider creating a one-element
  /// scalar which does require initialization:
  ///
  /// ```c++
  ///
  ///     Tensor(DT_FLOAT, TensorShape({}))
  ///
  /// ```
  Tensor();

  /// \brief Creates a Tensor of the given `type` and `shape`.  If
  /// LogMemory::IsEnabled() the allocation is logged as coming from
  /// an unknown kernel and step. Calling the Tensor constructor
  /// directly from within an Op is deprecated: use the
  /// OpKernelConstruction/OpKernelContext allocate_* methods to
  /// allocate a new tensor, which record the kernel and step.
  ///
  /// The underlying buffer is allocated using a `CPUAllocator`.
  Tensor(DataType type, const TensorShape& shape);


...


  // Only needed by variable op to set the shape of an uninitialized
  // Tensor.
  // TODO: Remove this when we have a better story for detecting
  // uninitialized tensors.
  void set_shape(const TensorShape& shape) {
    DataType dt = dtype();
    shape_ = shape;
    set_dtype(dt);
  }

  void CopyFromInternal(const Tensor& other, const TensorShape& shape);

  template <typename T>
  T* base() const;

  template <size_t NDIMS>
  void FillDimsAndValidateCompatibleShape(
      gtl::ArraySlice<int64> new_sizes,
      Eigen::array<Eigen::DenseIndex, NDIMS>* dims) const;

  template <typename T, size_t NDIMS>
  void FillDimsAndValidateCompatibleShape(
      gtl::ArraySlice<int64> new_sizes,
      Eigen::array<Eigen::DenseIndex, NDIMS>* dims) const;
};

}


Since a class has many functions , those will be a function pointer at the virtual function table, don't know where that is.  But once TF_Tensor is changed, class Tensor needs to be changed too.

Member functions besides, I really can't match the member variables in class Tensor with struct TF_Tensor.  There are tensorflow::TensorShape shape;  tensorflow::TensorBuffer* buffer;

no where to find TF_DataType dtype;

If I want to add a new member variable, I can put it at the end, it works.


They do have differences, from the comments in TF_NewTensor()


    // TF_STRING and TF_RESOURCE tensors have a different representation in
    // TF_Tensor than they do in tensorflow::Tensor. So a copy here is a waste
    // (any alignment requirements will be taken care of by TF_TensorToTensor
    // and TF_TensorFromTensor).
    //
    // Other types have the same representation, so copy only if it is safe to
    // do so.


2018年10月21日星期日

node_builder.Attr()

Saw it in

void TF_SetAttrTensor(TF_OperationDescription* desc, const char* attr_name,
                      TF_Tensor* value, TF_Status* status) {
  Tensor t;
  status->status = TF_TensorToTensor(value, &t);
  if (status->status.ok()) desc->node_builder.Attr(attr_name, t);
}

So what it means to build a node.


TF_Operation * Const(TF_Graph * graph, TF_Status * status, TF_Tensor * tensor, const char * name) {
        TF_OperationDescription * desc = TF_NewOperation(graph, "Const", name);
        TF_SetAttrTensor(desc, "value", tensor, status);
        TF_SetAttrType(desc, "dtype", TF_TensorType(tensor));
        return TF_FinishOperation(desc, status);
}

That's how to build a node use C interface.

C interface is easier to understand, plus c++ interface code is generated when compile, so it's difficult to change any of it.


c_api_h:

// Operation being built. The underlying graph must outlive this.
typedef struct TF_OperationDescription TF_OperationDescription;

// Operation that has been added to the graph. Valid until the graph is
// deleted -- in particular adding a new operation to the graph does not
// invalidate old TF_Operation* pointers.
typedef struct TF_Operation TF_Operation;

// Represents a specific input of an operation.
typedef struct TF_Input {
  TF_Operation* oper;
  int index;  // The index of the input within oper.
} TF_Input;

// Represents a specific output of an operation.
typedef struct TF_Output {
  TF_Operation* oper;
  int index;  // The index of the output within oper.
} TF_Output;


TF_Input is equivalent to TF_Output, they do use it interchangeably.

c_api_internal.h:

struct TF_Operation {
  tensorflow::Node node;
};

So the TF_Operation pointer just a pointer that points to the new node just created.


struct TF_OperationDescription {
  TF_OperationDescription(TF_Graph* g, const char* op_type,
                          const char* node_name)
      : node_builder(node_name, op_type, g->graph.op_registry()), graph(g) {}

  tensorflow::NodeBuilder node_builder;
  TF_Graph* graph;
  std::set<tensorflow::string> colocation_constraints;
};

TF_OperationDescription more like a class, to help building the node.

tensorflow::NodeBuilder is a class, defined in tensorflow/core/graph/node_builder.h

// This is a helper for creating a Node and adding it to a Graph.
// Internally, it uses a NodeDefBuilder to automatically set attrs
// that can be inferred from the inputs, and use default values
// (where they exist) for unspecified attrs.  Example usage:
//
//  Node* node;
//  Status status = NodeBuilder(node_name, op_name)
//                           .Input(...)
//                           .Attr(...)
//                           .Finalize(&graph, &node);
//  if (!status.ok()) return status;
//  // Use node here.
class NodeBuilder {
 public:
  // For specifying the output of a Node to provide to one of the Input()
  // functions below.  It supports both regular inputs (where you are
  // connecting to an existing Node*), and inputs from outside the graph
  // (or haven't been added to the graph yet, like back edges, where
  // you don't have a Node*). Both types can be mixed, e.g. in an
  // ArraySlice.
  struct NodeOut {
    // For referencing an existing Node.
    NodeOut(Node* n, int32 i = 0);

    // For referencing Nodes not in the graph being built. It is
    // useful when preparing a graph for ExtendSession or creating a
    // back edge to a node that hasn't been added to the graph yet,
    // but will be.
    NodeOut(StringPiece name, int32 i, DataType t);

    // Default constructor for std::vector<NodeOut>.
    NodeOut();

    Node* node;
    // error is set to true if:
    // * the NodeOut was default constructed and never overwritten,
    // * a nullptr Node* was passed to the NodeOut constructor, or
    // * an out-of-range index was passed to the NodeOut constructor.
    bool error;
    string name;
    int32 index;
    DataType dt;
  };

  // Specify the name and the Op (either via an OpDef or the name of
  // the Op plus a registry) for the Node.  Other fields are
  // specified by calling the methods below.
  // REQUIRES: The OpDef must satisfy ValidateOpDef().
  NodeBuilder(StringPiece name, StringPiece op_name,
              const OpRegistryInterface* op_registry = OpRegistry::Global());
  NodeBuilder(StringPiece name, const OpDef* op_def);

  // Create a NodeBuilder from an existing NodeDefBuilder.
  NodeBuilder(const NodeDefBuilder& def_builder);

  // You must call one Input() function per input_arg in the Op,
  // *and in the same order as the input_args appear in the OpDef.*

  // For inputs that take a single tensor.
  NodeBuilder& Input(Node* src_node, int src_index = 0);
  NodeBuilder& Input(NodeOut src);

  // For inputs that take a list of tensors.
  NodeBuilder& Input(gtl::ArraySlice<NodeOut> src_list);

  // Require that this node run after src_node(s).
  NodeBuilder& ControlInput(Node* src_node);
  NodeBuilder& ControlInputs(gtl::ArraySlice<Node*> src_nodes);

  // Sets the "requested device spec" in the NodeDef (not the
  // "assigned device" in the Node).
  NodeBuilder& Device(StringPiece device_spec);

  // Set the value of an attr.  attr_name must match the name of one of
  // attrs defined by the Op, and value must have the corresponding type
  // (see SetAttrValue() in ../framework/attr_value_util.h for legal
  // types for value).  Note that attrs will be set automatically if
  // they can be determined by the inputs.
  template <class T>
  NodeBuilder& Attr(StringPiece attr_name, T&& value);
  template <class T>
  NodeBuilder& Attr(StringPiece attr_name, std::initializer_list<T> value);

  // Validates the described node and adds it to *graph, adding edges
  // for all (non-back) inputs.  If created_node is not nullptr,
  // *created_node will be set to the new node (or nullptr on error).
  Status Finalize(Graph* graph, Node** created_node) const;

  // Accessors for the values set in the constructor.
  const string& node_name() const { return def_builder_.node_name(); }
  const OpDef& op_def() const { return def_builder_.op_def(); }

 private:
  static DataType SafeGetOutput(const Node* node, int i, bool* error) {
    if (node != nullptr && i >= 0 && i < node->num_outputs()) {
      *error = false;
      return node->output_type(i);
    } else {
      *error = true;
      return DT_FLOAT;
    }
  }

  // If SafeGetOutput indicates a range error, add it to errors_.
  void AddIndexError(const Node* node, int i);

  // Set *dt and returns true if i is in range. Combines
  // SafeGetOutput() and AddIndexError().
  bool GetOutputType(const Node* node, int i, DataType* dt);

  NodeDefBuilder def_builder_;
  std::vector<NodeOut> inputs_;
  std::vector<Node*> control_inputs_;
  std::vector<string> errors_;
};



Where NodeDefBuilder is defined in tensorflow/core/framework/node_def_builder.h


// This is a helper for creating a NodeDef.  Automatically sets attrs
// that can be inferred from the inputs, and uses default values
// (where they exist) for unspecified attrs.  Example usage:
//
//  NodeDef node_def;
//  Status status = NodeDefBuilder(node_name, op_name)
//                           .Input(...)
//                           .Attr(...)
//                           .Finalize(&node_def);
//  if (!status.ok()) return status;
//  // Use node_def here.
class NodeDefBuilder {
 public:
  // To specify an output to be consumed by one of the Input() methods below.
  struct NodeOut {
    NodeOut(StringPiece n, int i, DataType dt);
    NodeOut();  // uninitialized, call Reset() before use.
    void Reset(StringPiece n, int i, DataType dt);
    string node;
    int index;
    DataType data_type;
  };

  // Specify the name and the Op (either via an OpDef or the name of
  // the Op plus a registry) for the NodeDef.  Other fields are
  // specified by calling the methods below.
  // REQUIRES: The OpDef must satisfy ValidateOpDef().
  NodeDefBuilder(StringPiece name, StringPiece op_name,
                 const OpRegistryInterface* op_registry = OpRegistry::Global());
  // REQUIRES: in addition, *op_def must outlive *this.
  NodeDefBuilder(StringPiece name, const OpDef* op_def);

...


  const OpDef* op_def_;
  NodeDef node_def_;
  int inputs_specified_;
  std::vector<string> control_inputs_;
  std::vector<string> errors_;
};



We can tell that to build a node, we need to file a NodeDef structure, and probably link it to the graph.

Then what's the differences be Node and NodeDef?

First of all, the definition of class Node is easy to find. It's located in tensorflow/core/graph/graph.h

class Node {
 public:
  string DebugString() const;
  int id() const { return id_; }
  int cost_id() const { return cost_id_; }
  const string& name() const;
  const string& type_string() const;

  // def() provides the NodeDef the user supplied, but the specifics
  // of this Node may have changed due to placement, optimization, etc.
  // In particular:
  // * def().name() will match name();
  // * def().op() will match type_string() and op_def().name();
  // * def().input() is not reliable, use "in_edges()" below instead;
  // * def().device() is the "user's requested device" and may not match
  //   the actual assigned device, see assigned_device_name() below;
  // * def().attr() is authoritative.
  // TODO(irving): Replace with NodeInfo.
  const NodeDef& def() const;
  const OpDef& op_def() const;

  // input and output types
  int32 num_inputs() const;
  DataType input_type(int32 i) const;
  const DataTypeVector& input_types() const;

  int32 num_outputs() const;
  DataType output_type(int32 o) const;
  const DataTypeVector& output_types() const;

  // The device requested by the user.  For the actual assigned device,
  // use assigned_device_name() below.


....


  int id_;       // -1 until Initialize() is called
  int cost_id_;  // -1 if there is no corresponding cost accounting node
  NodeClass class_;

  EdgeSet in_edges_;
  EdgeSet out_edges_;

  // NOTE(skyewm): inheriting from core::RefCounted may have a slight
  // performance benefit over using shared_ptr, at the cost of manual ref
  // counting
  std::shared_ptr<NodeProperties> props_;

  // Index within Graph::device_names_ of the name of device assigned
  // to perform this computation.
  int assigned_device_name_index_;

  // A back-pointer to the Graph that owns this node.  Currently, this exists
  // solely to allow Node::[set_]assigned_device_name() to work. However, if all
  // callers of Node::[set_]assigned_device_name() are modified to use the
  // equivalent methods defined directly on Graph, then we can remove this
  // field and reclaim that memory.
  Graph* graph_;

  // Set if this is an exit node of a while loop with an associated
  // WhileContext. Otherwise null. (This is only set for exit nodes because
  // they're the first nodes of a loop encountered while creating the gradient
  // graph. Exit nodes that are part of while loop gradient graphs will not have
  // this set.)
  WhileContext* while_ctx_;

  TF_DISALLOW_COPY_AND_ASSIGN(Node);
};


But NodeDef is not so easy to find.

It's defined by a non c/c++ code at tensorflow/core/framework/node_def.proto

message NodeDef {
  // The name given to this operator. Used for naming inputs,
  // logging, visualization, etc.  Unique within a single GraphDef.
  // Must match the regexp "[A-Za-z0-9.][A-Za-z0-9_./]*".
  string name = 1;

  // The operation name.  There may be custom parameters in attrs.
  // Op names starting with an underscore are reserved for internal use.
  string op = 2;

  // Each input is "node:src_output" with "node" being a string name and
  // "src_output" indicating which output tensor to use from "node". If
  // "src_output" is 0 the ":0" suffix can be omitted.  Regular inputs
  // may optionally be followed by control inputs that have the format
  // "^node".
  repeated string input = 3;

  // A (possibly partial) specification for the device on which this
  // node should be placed.
  // The expected syntax for this string is as follows:
  //
  // DEVICE_SPEC ::= PARTIAL_SPEC
  //
  // PARTIAL_SPEC ::= ("/" CONSTRAINT) *
  // CONSTRAINT ::= ("job:" JOB_NAME)
  //              | ("replica:" [1-9][0-9]*)
  //              | ("task:" [1-9][0-9]*)
  //              | ("device:" [A-Za-z]* ":" ([1-9][0-9]* | "*") )
  //
  // Valid values for this string include:
  // * "/job:worker/replica:0/task:1/device:GPU:3"  (full specification)
  // * "/job:worker/device:GPU:3"                   (partial specification)
  // * ""                                    (no specification)
  //
  // If the constraints do not resolve to a single device (or if this
  // field is empty or not present), the runtime will attempt to
  // choose a device automatically.
  string device = 4;

  // Operation-specific graph-construction-time configuration.
  // Note that this should include all attrs defined in the
  // corresponding OpDef, including those with a value matching
  // the default -- this allows the default to change and makes
  // NodeDefs easier to interpret on their own.  However, if
  // an attr with a default is not specified in this list, the
  // default will be used.
  // The "names" (keys) must match the regexp "[a-z][a-z0-9_]+" (and
  // one of the names from the corresponding OpDef's attr field).
  // The values must have a type matching the corresponding OpDef
  // attr's type field.
  // TODO(josh11b): Add some examples here showing best practices.
  map<string, AttrValue> attr = 5;
};

The comment in the header tells the differences.

 // def() provides the NodeDef the user supplied, but the specifics
  // of this Node may have changed due to placement, optimization, etc.
  // In particular:
  // * def().name() will match name();
  // * def().op() will match type_string() and op_def().name();
  // * def().input() is not reliable, use "in_edges()" below instead;
  // * def().device() is the "user's requested device" and may not match
  //   the actual assigned device, see assigned_device_name() below;
  // * def().attr() is authoritative.

NodeDef is only for user to establish a node, but after going through the framework, NodeDef structure is regulated as a Node.

Also, we see map<string, AttrValue> attr = 5;
Each node has several Attr, the node builder need to fill them.  Every Attr has a name, and the name is associated with other object. For example, "value" with tensor, "shape" with tensor shape. 

FIX: It's AttrValue, not Tensor but TensorProto


tensorflow/core/framework/node_def_util.cc

void AddNodeAttr(StringPiece name, const AttrValue& value, NodeDef* node_def) {
  node_def->mutable_attr()->insert(
      AttrValueMap::value_type(std::string(name), value));
}



Tensorflow's tensor

A simple program that use tensorflow's C API interface.

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include "tensorflow/c/c_api.h"

/*
 * Super basic example of using google tensorflow directly from C
 *
 */

// Using stack input data nothing to free
void tensor_free_none(void * data, size_t len, void* arg) {
}

TF_Operation * PlaceHolder(TF_Graph * graph, TF_Status * status, TF_DataType dtype, const char * name) {
        TF_OperationDescription * desc = TF_NewOperation(graph, "Placeholder", name);
        TF_SetAttrType(desc, "dtype", TF_FLOAT);
        return TF_FinishOperation(desc, status);
}

TF_Operation * Const(TF_Graph * graph, TF_Status * status, TF_Tensor * tensor, const char * name) {
        TF_OperationDescription * desc = TF_NewOperation(graph, "Const", name);
        TF_SetAttrTensor(desc, "value", tensor, status);
        TF_SetAttrType(desc, "dtype", TF_TensorType(tensor));
        return TF_FinishOperation(desc, status);
}

TF_Operation * Add(TF_Graph * graph, TF_Status * status, TF_Operation * one, TF_Operation * two, const char * name) {
        TF_OperationDescription * desc = TF_NewOperation(graph, "AddN", name);
        TF_Output add_inputs[2] = {{one, 0}, {two, 0}};
        TF_AddInputList(desc, add_inputs, 2);
        return TF_FinishOperation(desc, status);
}


int main() {
        printf("TensorFlow C library version: %s\n", TF_Version());

        //test();

        TF_Graph * graph = TF_NewGraph();
        TF_SessionOptions * options = TF_NewSessionOptions();
        TF_Status * status = TF_NewStatus();
        TF_Session * session = TF_NewSession(graph, options, status);

        float in_val_one = 4.0f;
        float const_two = 2.0f;

        TF_Tensor * tensor_in = TF_NewTensor(TF_FLOAT, NULL, 0, &in_val_one, sizeof(float), tensor_free_none, NULL);
        TF_Tensor * tensor_out = NULL; // easy access after this is allocated by TF_SessionRun
        TF_Tensor * tensor_const_two = TF_NewTensor(TF_FLOAT, NULL, 0, &const_two, sizeof(float), tensor_free_none, NULL);

        // Operations
        TF_Operation * feed = PlaceHolder(graph, status, TF_FLOAT, "feed");
        TF_Operation * two = Const(graph, status, tensor_const_two, "const");
        TF_Operation * add = Add(graph, status, feed, two, "add");

        // Session Inputs
        TF_Output input_operations[] = { feed, 0 };
        TF_Tensor ** input_tensors = {&tensor_in};

        // Session Outputs
        TF_Output output_operations[] = { add, 0 };
        TF_Tensor ** output_tensors = {&tensor_out};

        TF_SessionRun(session, NULL,
                        // Inputs
                        input_operations, input_tensors, 1,
                        // Outputs
                        output_operations, output_tensors, 1,
                        // Target operations
                        NULL, 0, NULL,
                        status);

        printf("Session Run Status: %d - %s\n", TF_GetCode(status), TF_Message(status) );
        printf("Output Tensor Type: %d\n", TF_TensorType(tensor_out));
        float * outval = (float*)TF_TensorData(tensor_out);
        printf("Output Tensor Value: %.2f\n", *outval);

        TF_CloseSession(session, status);
        TF_DeleteSession(session, status);

        TF_DeleteSessionOptions(options);

        TF_DeleteGraph(graph);

        TF_DeleteTensor(tensor_in);
        TF_DeleteTensor(tensor_out);
        TF_DeleteTensor(tensor_const_two);

        TF_DeleteStatus(status);
        return 0;
}


I am trying to set a special flag in the "const" tensor.

The program allocates a TF_Tensor for this const_two float variable.

TF_Tensor * tensor_const_two = TF_NewTensor(TF_FLOAT, NULL, 0, &const_two, sizeof(float), tensor_free_none, NULL);

But is this TF_Tensor the one that linked with the graph?


Similar to C++ interface, C code also needs to build operation (add node to the graph).

TF_Operation * Const(TF_Graph * graph, TF_Status * status, TF_Tensor * tensor, const char * name) {
        TF_OperationDescription * desc = TF_NewOperation(graph, "Const", name);
        TF_SetAttrTensor(desc, "value", tensor, status);
        TF_SetAttrType(desc, "dtype", TF_TensorType(tensor));
        return TF_FinishOperation(desc, status);
}

TF_SetAttrTensor(desc, "value", tensor, status); is where sends the tensor value into the operation.

In tensorflow/c/c_api.cc:

void TF_SetAttrTensor(TF_OperationDescription* desc, const char* attr_name,
                      TF_Tensor* value, TF_Status* status) {
  Tensor t;
  status->status = TF_TensorToTensor(value, &t);
  if (status->status.ok()) desc->node_builder.Attr(attr_name, t);
}


Basically, TF_TensorToTensor is a copy function, allocate a new tensor, and add this newly allocated one into the graph.

So, if I set some flag in the tensor, here is where I need to add code.


Status TF_TensorToTensor(const TF_Tensor* src, Tensor* dst) {
  if (src->dtype == TF_RESOURCE) {
    if (src->shape.dims() != 0) {
      return InvalidArgument(
          "Malformed TF_RESOURCE tensor: expected a scalar, got a tensor with "
          "shape ",
          src->shape.DebugString());
    }
    *dst = Tensor(DT_RESOURCE, src->shape);
    if (!dst->scalar<ResourceHandle>()().ParseFromString(
            string(static_cast<const char*>(TF_TensorData(src)),
                   TF_TensorByteSize(src)))) {
      return InvalidArgument(
          "Malformed TF_RESOUCE tensor: unable to parse resource handle");
    }
    return Status::OK();
  }
  if (src->dtype != TF_STRING) {
    *dst = TensorCApi::MakeTensor(src->dtype, src->shape, src->buffer);
    return Status::OK();
  }
  // TF_STRING tensors require copying since Tensor class expects a sequence of
  // string objects.
  const tensorflow::int64 num_elements = src->shape.num_elements();
  const char* input = reinterpret_cast<const char*>(TF_TensorData(src));
  const size_t src_size = TF_TensorByteSize(src);
  if (static_cast<tensorflow::int64>(src_size / sizeof(tensorflow::uint64)) <
      num_elements) {
    return InvalidArgument(
        "Malformed TF_STRING tensor; too short to hold number of elements");
  }
  const char* data_start = input + sizeof(tensorflow::uint64) * num_elements;
  const char* limit = input + src_size;

  *dst = Tensor(static_cast<DataType>(src->dtype), src->shape);
  auto dstarray = dst->flat<string>();
  for (tensorflow::int64 i = 0; i < num_elements; ++i) {
    tensorflow::uint64 offset =
        reinterpret_cast<const tensorflow::uint64*>(input)[i];
    if (static_cast<ptrdiff_t>(offset) >= (limit - data_start)) {
      return InvalidArgument("Malformed TF_STRING tensor; element ", i,
                             " out of range");
    }
    size_t len;
    const char* p;
    const char* srcp = data_start + offset;
    Status status = TF_StringDecode_Impl(srcp, limit - srcp, &p, &len);
    if (!status.ok()) return status;
    dstarray(i).assign(p, len);
  }
  return Status::OK();
}




2018年10月13日星期六

lxd container migration

A simple way to move a container to another host.

Containers are located /var/lib/lxd/containers/

for example, to move container xxx

$tar cxvf xxx.tar.gz /var/lib/lxd/containers/xxx

$scp xxx.tar.gz user@remotehost:/home/user


At remote host, unpack files /var/lib/lxd/containers/

Then

$sudo lxc image import xxx



The container setup a shared folder before. After moving to a new host, it can't start because of this.

There is no /etc/subuid and /etc/subgid


Note: $UID is likely to be 1000 on an Ubuntu system if you're the only user.

$echo "root:$UID:1" | sudo tee -a /etc/subuid /etc/subgid

After this, the container can start.


The following is how to create a shared folder device in container

$ sudo lxc config device add xxx projectdir disk source=/home/user/prjs path=/home/user/prjs

Then set idmap

$sudo lxc stop xxx
$sudo lxc config set xxx raw.idmap 'both 1000 1000'
$sudo lxc start xxx

2018年9月20日星期四

Tensorflow

How Tensorflow link an operation with a tensor?


        float in_val_one = 4.0f;
        float const_two = 2.0f;

        TF_Tensor * tensor_in = TF_NewTensor(TF_FLOAT, NULL, 0, &in_val_one, sizeof(float), tensor_free_none, NULL);
        TF_Tensor * tensor_out = NULL; // easy access after this is allocated by TF_SessionRun
        TF_Tensor * tensor_const_two = TF_NewTensor(TF_FLOAT, NULL, 0, &const_two, sizeof(float), tensor_free_none, NULL);

        // Operations
        TF_Operation * feed = PlaceHolder(graph, status, TF_FLOAT, "feed");
        TF_Operation * two = Const(graph, status, tensor_const_two, "const");
        TF_Operation * add = Add(graph, status, feed, two, "add");

        // Session Inputs
        TF_Output input_operations[] = { feed, 0 };
        TF_Tensor ** input_tensors = {&tensor_in};

        // Session Outputs
        TF_Output output_operations[] = { add, 0 };
        TF_Tensor ** output_tensors = {&tensor_out};

        TF_SessionRun(session, NULL,
                        // Inputs
                        input_operations, input_tensors, 1,
                        // Outputs
                        output_operations, output_tensors, 1,
                        // Target operations
                        NULL, 0, NULL,
                        status);


How it links input_operations with input_tensors? By name.

Each TF_Output contains an operation and an index, indicating the nth output of the operation. Each operation contains a node. every node has its own name.

  // Convert from TF_Output and TF_Tensor to a string and Tensor.
  std::vector<std::pair<string, Tensor>> input_pairs(ninputs);
  if (!TF_Run_Inputs(input_values, &input_pairs, status)) return;
  for (int i = 0; i < ninputs; ++i) {
    input_pairs[i].first = OutputName(inputs[i]);
  }

By using pair<>, link a string which is the name of a node with a tensor. Note the tensor is newly generated.


static bool TF_Run_Inputs(TF_Tensor* const* c_inputs,
                          std::vector<std::pair<string, Tensor>>* input_pairs,
                          TF_Status* status) {
  const int ninputs = input_pairs->size();
  for (int i = 0; i < ninputs; ++i) {
    status->status = TF_TensorToTensor(c_inputs[i], &(*input_pairs)[i].second);
    if (!status->status.ok()) return false;
  }
  return true;
}


Later vector pair<> becomes NamedTensorList;

typedef std::vector<std::pair<string, Tensor>> NamedTensorList;


Then we can see them in class DirectSession : public Session

  ::tensorflow::Status Run(const NamedTensorList& inputs,
                           const std::vector<string>& output_names,
                           const std::vector<string>& target_nodes,
                           std::vector<Tensor>* outputs) override;



Those tensors will be packed again to feed_args, then passed to call_frame.SetArgs(feed_args);

Status DirectSession::Run(const RunOptions& run_options,
                          const NamedTensorList& inputs,
                          const std::vector<string>& output_names,
                          const std::vector<string>& target_nodes,
                          std::vector<Tensor>* outputs,
                          RunMetadata* run_metadata) {
  TF_RETURN_IF_ERROR(CheckNotClosed());
  TF_RETURN_IF_ERROR(CheckGraphCreated("Run()"));
  direct_session_runs->GetCell()->IncrementBy(1);

  // Extract the inputs names for this run of the session.
  std::vector<string> input_tensor_names;
  input_tensor_names.reserve(inputs.size());
  for (const auto& it : inputs) {
    input_tensor_names.push_back(it.first);
  }

  // Check if we already have an executor for these arguments.
  ExecutorsAndKeys* executors_and_keys;
  RunStateArgs run_state_args(run_options.debug_options());

  TF_RETURN_IF_ERROR(GetOrCreateExecutors(input_tensor_names, output_names,
                                          target_nodes, &executors_and_keys,
                                          &run_state_args));

  // Configure a call frame for the step, which we use to feed and
  // fetch values to and from the executors.
  FunctionCallFrame call_frame(executors_and_keys->input_types,
                               executors_and_keys->output_types);
  gtl::InlinedVector<Tensor, 4> feed_args(inputs.size());
  for (const auto& it : inputs) {
    if (it.second.dtype() == DT_RESOURCE) {
      Tensor tensor_from_handle;
      TF_RETURN_IF_ERROR(
          ResourceHandleToInputTensor(it.second, &tensor_from_handle));
      feed_args[executors_and_keys->input_name_to_index[it.first]] =
          tensor_from_handle;
    } else {
      feed_args[executors_and_keys->input_name_to_index[it.first]] = it.second;
    }
  }
  const Status s = call_frame.SetArgs(feed_args);
  if (errors::IsInternal(s)) {
    return errors::InvalidArgument(s.error_message());
  } else if (!s.ok()) {




A graph may contain several subgraphs. For example, the one that initializes variables, you should first execute that to get random matrix or some constants ready.

Each of those is represented by struct ExecutorsAndKeys;


// An ExecutorsAndKeys is created for a given set of feeds/fetches.
  // 'step_count' is the number of times this graph is executed.
  // 'graph' is the entire graph being executed. 'name_to_node'
  // maps node name to node. We keep 'graph' and 'name_to_node' only in
  // the case of partial runs. Each item in 'items' is the executor for
  // a partition of the graph bundled with its dependent library runtime.
  // 'input_keys' are the rendezvous keys for the feeds and 'output_keys'
  // are rendezvous keys for the fetches.
  struct ExecutorsAndKeys {
    ExecutorsAndKeys() : step_count(0) {}

    std::atomic_int_fast64_t step_count;
    std::unique_ptr<Graph> graph;
    NameNodeMap name_to_node;
    std::vector<PerPartitionExecutorsAndLib> items;
    std::unordered_map<string, size_t> input_name_to_index;
    std::unordered_map<string, string> input_name_to_rendezvous_key;
    std::unordered_map<string, size_t> output_name_to_index;
    std::unordered_map<string, string> output_name_to_rendezvous_key;

    DataTypeVector input_types;
    DataTypeVector output_types;

    CallableOptions callable_options;
  };

2018年9月11日星期二

Set up a reverse tunnel with ssh

Yesterday I went back home, hoping I can work remotely on my laptop which is set at the lab.


But because of my laptop got rebooted, its IP address changed (It connects to wifi, IP address always change).


I have one desktop set in the lab and have a wired connection. So its IP address is mostly fixed.

I want my laptop to connect to my desktop and establishes a reverse tunnel. So later I can ssh to my desktop and forward to my laptop.

First, a reverse ssh tunnel.
It could be simple as :
ssh -R 9999:localhost:22 user@x.x.x.x

I need it to be done in a shell script so no password would be the best.

I found this tutorial, following steps:
Step 1: Create Authentication SSH-Keygen Keys on localhost
$ ssh-keygen -t rsa
It generates ~/.ssh/id_rsa and ~/.ssh/id_rsa.pub

Step 2: Create .ssh Directory on remote x.x.x.x
$ ssh user@x.x.x.x mkdir -p .ssh

Step 3: Upload Generated Public Keys to remote x.x.x.x
$ cat .ssh/id_rsa.pub | ssh user@x.x.x.x 'cat >> .ssh/authorized_keys'

Step 4: Set Permissions on remote x.x.x.x
$ ssh user@x.x.x.x "chmod 700 .ssh; chmod 640 .ssh/authorized_keys"

Step 5: Login without password
$ ssh user@x.x.x.x


I need the reverse tunnel to establish when the system boots up, and reestablish when the network recovers from a breakdown. It seems system daemon is a good choice.

$sudo vi /etc/systemd/system/sshreversetunnel.service

[Unit]
Description=SSH Reverse Tunnel
After=network.target

[Service]
Restart=always
RestartSec=20
User=user
ExecStart=/usr/bin/ssh -NT -o ServerAliveInterval=60 -o "ExitOnForwardFailure yes" -R 9999:localhost:22 user@x.x.x.x

[Install]
WantedBy=multi-user.target


It runs as the user 'user'.
"$ systemctl enable sshreversetunnel" to set it to start at boot time.
"$ systemctl start sshreversetunnel" to start immediately.
"$ systemctl daemon-reload" to update once config file is changed.



Also options on ssh:
-N      Do not execute a remote command.  This is useful for just for‐
             warding ports.
-T      Disable pseudo-terminal allocation.

"ExitOnForwardFailure yes"  if 9999 is occupied on the remote host then ssh exits.



It works well.

One little detail, if network connectivity is down silently, ssh shell will look like froze. It won't take Ctrl-C.  Enter ~ .  press these three keys will terminate it