2017年2月21日星期二

mysql jdbc client端也加了一个maxAllowedPacket参数

不知道这是从哪个版本开始的,mysql jdbc client端也加了一个maxAllowedPacket参数。
所以我一再的修改server上的 max_allowed_packet 设置都没有用。

mysql> show global variables like '%packet%';
+--------------------------+------------+
| Variable_name            | Value      |
+--------------------------+------------+
| max_allowed_packet       | 16777216   |
| slave_max_allowed_packet | 1073741824 |
+--------------------------+------------+
2 rows in set (0.00 sec)


java里还是报告

Caused by: com.mysql.cj.jdbc.exceptions.PacketTooBigException: Packet for query is too large (100,152 > 65,535). You can change this value on the server by setting the 'max_allowed_packet' variable.
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:100)
at com.mysql.cj.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:1983)
at com.mysql.cj.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:1936)
at com.mysql.cj.jdbc.StatementImpl.executeQuery(StatementImpl.java:1422)

FUCK!

解决办法就是client端也得改

2017年2月20日星期一

tensorflow alex performance test

放一些最近做的测试

网络结构:
conv1   [128, 56, 56, 64]
pool1   [128, 27, 27, 64]
conv2   [128, 27, 27, 192]
pool2   [128, 13, 13, 192]
conv3   [128, 13, 13, 384]
conv4   [128, 13, 13, 256]
conv5   [128, 13, 13, 256]
pool5   [128, 6, 6, 256]

batch size=128

E5 1630-v3 4核
 step 0, duration = 1.984
 Forward across 1 steps, 1.984 +/- 0.000 sec / batch
 step 0, duration = 4.812
 Forward-backward across 1 steps, 4.812 +/- 0.000 sec / batch

双E5 2470-v2 , 2x10 = 20 核
 step 0, duration = 1.969
 Forward across 1 steps, 1.969 +/- 0.000 sec / batch
 step 0, duration = 4.500
 Forward-backward across 1 steps, 4.500 +/- 0.000 sec / batch

E3 1230-v5 4核
 step 0, duration = 1.481
 Forward across 1 steps, 1.481 +/- 0.000 sec / batch
 step 0, duration = 4.631
 Forward-backward across 1 steps, 4.631 +/- 0.000 sec / batch

GTX 970:
 step 0, duration = 0.046
 Forward across 1 steps, 0.046 +/- 0.000 sec / batch
 step 0, duration = 0.137
 Forward-backward across 1 steps, 0.137 +/- 0.000 sec / batch

K20m x 2:
  Forward across 100 steps, 0.093 +/- 0.001 sec / batch
  Forward-backward across 100 steps, 0.253 +/- 0.001 sec / batch

我觉得会不会是哪里搞错了。。。。太诡异
首先,不同CPU的性能差不多。尤其是20核的居然和普通家用的4核差不多。
其次,CPU和GPU的性能差距,真是远比我想像的大。差了30-40倍

(我会持续更新的)


2017年2月19日星期日

docker 1.13默认会在iptables里drop forward包

我发现我在升级docker 1.13后,我的openvpn就不能用了。经水木Dieken指点,发现这是docker更改了默认行为
https://github.com/docker/docker/pull/28257

解决办法有好几种:

1. 根据自己的情况添加白名单
sudo iptables -I FORWARD -i tun+ -o eth0 -s 192.168.0.0/16 -m conntrack --ctstate NEW -j ACCEPT
sudo iptables -I FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
sudo iptables -t nat -A POSTROUTING -s 192.168.0.0/16 -o eth0  -j MASQUERADE  (这条原本就需要)

2. 把默认规则改回ACCEPT
sudo iptables -P FORWARD ACCEPT

3. 修改sysctl的默认设置
只要sysctl里的ip foward不是docker打开的,docker就不会把默认的drop规则添加进去。

2017年2月18日星期六

tensorflow distributed runtime初窥

一个Tensorflow的Cluster包含多个Job,一个Job包含多个task,每一个task都是一个进程。
Job相当于Server Role。一般是分成"ps"和"worker"两种。
每个task会有1个或多个device, 如"cpu:0"、"gpu:0"。在创建graph的时候,可以指定node被place到哪个device上。tensor在device和device之间的传输由tensorflow distributed runtime自动完成。这就是tensorflow的分布式的核心设计思想。

tensorflow的分布式设计相比于它的前身DistBelief有什么改变和优势呢?

首先,DistBelief里只有两种角色:parameter server和worker。而tensorflow在摒弃parameter server这个概念,它希望把parameter server做的更generic一些。它认为,parameter server也是既需要做运算,也需要做KV存储,所以它和计算梯度的worker并没有本质的不同,只是它比较“被动”。Google希望,当想尝试一个新的优化算法时,如FTRL、Adam,不必去改parameter server的code。

于是在tensorflow中,就变成了:有一个进程负责分配任务和存储到其它节点,而其它节点负责执行收到的任务。那个分配任务的就是master节点。其它的slave节点就是传统意义上的parameter server。被分配的任务是Partial graph execution。存储是variable。整个计算流程由master节点来drive。再往上层来讲,假如你采用了training data partition,那么,每个data partition都应对应一个master节点。每个master节点都对应一个tensorflow session。每个slave节点可以同时支撑来自多个master的多个session。

Tensorflow中的session有两个实现,GrpcSession和DirectSession。前者是给分布式用的,后者是给单机用的。


一个GrpcSession的执行环境中包含一个master进程,和多个slave进程。master负责创建session,slave只需要执行master送过来的graph片段。

master进程的流程一般如下:
  1. 创建并启动grpc server
  2. 创建session
  3. while(...)  session.run()
其中,创建session必须在创建server之后进行。而graph的构造在什么时候都可以。

slave进程的流程一般如下:
  1. 创建并启动grpc server
  2. 等待grpc server结束
创建server需要两步:
  1. 构造一个ServerDef类型的protobuf对象,内容主要是cluster里的ip和端口号列表。
  2. 调用tensorflow::NewServer

一个ClusterDef包含多个JobDef,一个JobDef包含多个task,每个task是一个进程,对应一个ServerDef。
有了ServerDef,就可以用tensorflow::NewServer创建一个ServerInterface。ServerInterface主要提供的是Start/Stop/Join这样很基础的方法。

class ServerInterface {
 public:
  ServerInterface() {}
  virtual ~ServerInterface() {}

  // Starts the server running asynchronously. Returns OK on success, otherwise
  // returns an error.
  virtual Status Start() = 0;

  // Stops the server asynchronously. Returns OK on success, otherwise returns
  // an error.
  //
  // After calling `Stop()`, the caller may call `Join()` to block until the
  // server has stopped.
  virtual Status Stop() = 0;

  // Blocks until the server has stopped. Returns OK on success, otherwise
  // returns an error.
  virtual Status Join() = 0;

  // Returns a target string that can be used to connect to this server using
  // `tensorflow::NewSession()`.
  virtual const string target() const = 0;
};

tensorflow::NewServer()函数根据ServerDef的内容(主要是protocol字段)来决定ServerInterface的实现是什么(找到对应的ServerFactory)。目前我只看到一个实现:GrpcServer。

下面是如何创建一个Server并一直等在那。为了简单,我直接调用了GrpcServer::Create方法,而不是tensorflow::NewServer。

#include <iostream>
#include <fstream>
#include <SDKDDKVer.h>
#include <google/protobuf/text_format.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <tensorflow/core/distributed_runtime/rpc/grpc_server_lib.h>

using namespace tensorflow;

int main(int argc, char* argv[]) {
 ServerDef server_def;
 {
  std::ifstream confFile("1.txt");
  google::protobuf::io::IstreamInputStream in(&confFile);  
  if (!google::protobuf::TextFormat::Parse(&in, &server_def)) {
   LOG(ERROR) << "load conf failed\n";
   return -1;
  }
  confFile.close();
 }
 std::unique_ptr<ServerInterface> out_server;
 tensorflow::Status status = GrpcServer::Create(server_def, Env::Default(), &out_server);
 if (!status.ok()) {
  LOG(ERROR) << "Create server failed\n" << status;
 }
 out_server->Start();
 out_server->Join();
 return 0;
}

配置文件1.txt的内容:
cluster {
  job {
    name: "ps"
    tasks {
      value: "127.0.0.1:10001"
    }
  }
  job {
    name: "worker"
    tasks {
      value: "127.0.0.1:10000"
    }
  }
}
job_name: "ps"
protocol: "grpc"

接下来是如何创建session。
举个例子,如果像下面这样什么都不填,那么创建的是就是LocalSession。
  tensorflow::SessionOptions options;
  std::unique_ptr<Session> session(tensorflow::NewSession(options));
如果设置了target为grpc,那么创建的是就是GrpcSession。
  tensorflow::SessionOptions options;
  options.target="grpc://localhost:3000";
  std::unique_ptr<Session> session(tensorflow::NewSession(options));

此处注意,target应该是以"grpc://localhost:"开头。后面的端口号是本进程的grpc server所用的端口号。最好是直接使用ServerInterface::target()的返回值。

下面介绍tensorflow::NewSession内部做了什么。
Session永远是由SessionFactory创建的。
class SessionFactory {
 public:
  virtual Session* NewSession(const SessionOptions& options) = 0;
  virtual bool AcceptsOptions(const SessionOptions& options) = 0;
};

SessionOptions最重要的一个选项是target。
struct SessionOptions {
  std::string target;
  //...
};
target决定了采用什么样的SessionFactory来创建Session。SessionFactory有两种实现:GrpcSessionFactory和DirectSessionFactory。



前者的AcceptsOptions函数只接受以grpc://开头的target,而后者只接受target等于空的情况。这两个子类的instance都会被注册到SessionFactory这个基类的一个静态变量map中。

下面回头来介绍GrpcServer的内部构造。

ClusterDef中的每一个task,也就是说tensorflow集群中的每一个进程,都对应这一个GrpcServer。而GrpcServer内部又可以分为两部分,Master Service和Worker Serivce。一个session中,只有一个master,但是会有多个worker。Master Service负责Session的创建、销毁、run step,而Worker Service则是执行Master的Graph的一部分,或者干脆直接当key-value存储用。Worker Serivce相当于以前的parameter server,而Master Service相当于以前的worker。



GrpcServer中的master service和worker service各对应一个线程。这两个线程是在GrpcServer::Start()函数中创建。该线程负责运行network IO event loop,接收请求,执行请求。但具体的执行通常会被分发到线程池中。

下面是Master和Session的关系。



先要明白时序关系:先有GrpcServer,后有Session。GrpcServer必须先于Session创建。
上图Master的ownership在GrpcServer那。

每个GrpcSession会以unique_ptr的方式own一个MasterInterface。

MasterInterface是GRPC Master Service的client端的抽象接口。但是,由于绝大多数情况下,master就在本进程内,为了提高效率就不用走RPC了。所以Master Interface有两个实现,

  • GrpcRemoteMaster,用GRPC实现通信
  • LocalMaster,直接把Master对象的指针拿来用
LocalMaster是如何获得Master指针的:
MasterInterface实现实例的创建是在GrpcSession::Create函数中。Master是在GrpcServer::Init()中创建,在该函数的末尾,Master的这个实例会被注册到LocalMaster单件中。GrpcSession::Create函数里,会根据target去LocalMaster单件里查,如果查到了,就创建一个LocalMaster对象并返回,否则就只能走GrpcRemoteMaster了。

关于设备管理:
在单机版中,DeviceManager是在DirectSession::NewSession函数中创建,由DirectSession own。在分布式版本中,DeviceManager是在GrpcServer::Init()中创建,由GrpcServer的WorkerEnv own,用于实现worker service。
在运行的时候,每个device都有一个name,大概是这样的格式 "/job:ps/replica:0/task:0/cpu:0"。

关于SessionOptions:
实际上有两个地方都会用到SessionOptions。一个是在创建Session的时候,另一个是创建GrpcServer的时候。ServerDef中有一个ConfigProto类型的字段叫default_session_config。这个其实就是SessionOptions。这个只是在初始Devices的时候使用。

(写了这么多其实还没写到graph和tensor呢。明天继续吧)


2017年2月16日星期四

Windows下用copy命令合并文件的时候小心末尾的0x1A

把几个文本文件合并成同一个,在Linux下很简单,
cat src*.txt > dest.txt
就可以了。

在Windows下可以用copy命令:

copy src*.txt dest.txt

但是这样复制出来的文件,dest.txt的末尾会多一个0x1A。这个是Windows系统中特有的eof-of-file marker。这个"Feature"害得我debug了好久。

解决办法:
在源文件的后面加/a
在目的文件的后面加/b
同时,如果是在写脚本,别忘了在命令的最后加一个/y。

例子:

copy tmpdir\part* /a out.txt /b /y

2017年2月10日星期五

神经网络语言模型

什么是统计语言模型?

"A language model is a function that puts a probability measure over strings drawn from some vocabulary" -- 《Introduction to Information Retrieval》

在实际使用中,一般都是n-gram模型。即,根据前n-1个词判断第n个词的出现概率。由于训练集中的样本(strings)是变长的,所以需要在原始的词表中加入两个特殊的token: START 和 STOP。前者用于表示序列的开始,后者用于表示结束。

下面举个tri-gram的例子。对tri-gram来说:

$$ p(x_1,x_2, ... , x_n) = \prod_{i=1}^np(x_i|x_{i-2},x_{i-1})$$

比如对the dog barks这句话来说,
p(the dog barks STOP) = p(the | START, START) * p(dog| START, the) * p(barks| the, dog) * p(STOP | dog, barks)

这里的概率函数p,一般是通过统计或者机器学习的方式得到的。我个人的理解是:统计语言模型并不是为了对人类的某种语言建立一个通用的模型。因为同一个词在不同的文档集中出现的频率差别很大。
其次,样本中没有出现的,不代表它的概率就是0。平滑化,即为这些未出现过的strings赋予一个非0 的概率,在传统的以counting为主的语言模型中扮演着至关重要的角色。

Distributed representation 是20世纪以Hinton 为代表的Connectionism的一个老旧概念。所有的神经网络模型,都可以认为是input的一种Distributed representation。所以我觉得可以先不去考虑这两个词的意思。常与之混淆的是Distributional representation,这个通常指的是空间向量模型(VSM)。

2017年1月19日星期四

2016我学到了些什么

转眼又是一年过去了,回头看看自己也没做太多事情,尤其是博客基本没写,所以也不记得自己都遇到了些什么问题,都怎么解决的。以后还是要勤快点。哪怕质量差,哪怕错误很多,也比全忘了。

这1年写C++写的很爽。惭愧的是毕业以后我没太多的写过C++。去年一直在心无旁骛的写代码,平均每周开会只有1个小时,没有daily stand up meeting,没有越洋电话。我现在写代码越来越在意debuggability。比如抛出一个异常的时候一定要记录是在哪一行抛的。一个分布式程序,遇到错误的时候如何快捷的找到是哪台机器哪个进程才是错误的根源。我总认为花在debug上的功夫都是无用功,是可以省下来早点下班回家打游戏陪老婆的。心中窃喜之前刷面试题练一次写对无bug还是对工作蛮有用的。

我对Windows异步IO以及IOCP有了更深的了解。此处强烈赞一下Windows的新thread pool API,设计的非常棒!它把IO、用户的task、timer全都整合到了一起,尤其是在cancel请求、shutdown threadpool方面做的很干净。而老的程序,比如使用windows http server API写的程序,只需要稍改几行代码就可以迁移过来。不过总的来说IOCP是个很大的坑,我要学的东西很多。拿着别人的代码我经常看不明白他为什么这么做。比如有个大神的代码,他在收到每个IOCP的事件之后,不是直接处理,而是转成一个内部事件Post给IOCP线程池。他这么做必有他的用意吧。

另外把torch7的核心代码看了一遍,基本上是在上下班的地铁上完成的。我觉得对于一个分布式trainer来说,train一个逻辑回归和train一个经典的Feedforward neural network并没有太大区别。90%以上的代码都是一样的。你都需要有map-reduce或者parameter server,这才是最耗工程量的。backpropagation固然重要,但是torch和tensorflow竟然都是在脚本层实现backpropagation。Auto differentiation是一件听上去很神奇但是实现起来毫无新颖的东西。就跟数据库事务能回滚一样。tensorflow的代码我只看了一部分,总体来说真是简单明了,结构上特别清晰,干净。实物不如paper那么让人有想象力让人惊叹。

嗯,还有就是意外收到了google的foobar的邀请,然后把题全做完了。基本上每个题都能让我学到一些新东西,印象最深的就是马尔科夫链。很多图算法以前只是看书,从来没写过,这次也写了一遍。一次写对无需debug还是挺happy的。失望的是做完并没有什么卵用,只是被google的hr邀请去面试而已,可我已经被邀请数次了丝毫不感兴趣。你们倒是给我发件T-shirt啊!上次那件唐僧师徒的Android T-shirt 我甚是喜欢。

机器学习方面算是入了NLP的坑,主要是看论文,对传统的神经网络语言模型有了大致了解,接下来还需要实践的用一用。word2vec/fastText的代码看了些许,希望今年把fastText读完。另外就是把gbdt、lambda rank之类的相关论文和代码读了读,对google的Sibyl以及他们的GBM的实现特别感兴趣,很想自己用同样的方法试试。对于他们能纯用map-reduce来实现这些我真的很佩服,尤其是用那些tricky的办法来降低map-reduce的调度延迟。

Markdown让我伤透了心,再也不想碰了。辛辛苦苦的东西总是在render的时候给我意外,过了很久之后我才发现很多地方不对,气得我直接把博客的数据库回档到1年前了。

2017年啊,把能腾的时间都腾出来,好好读windows的source code。上个月我在我的项目中遇到一些问题,使得我意外学会了如何debug windows。一方面让自己有所长进,另一方面确实也把一个陈年甚久的bug给他们找出来了,算是有点output吧。刚好我手上还有些奇怪的bug,我再试试看。否则总有一天我会后悔“入得宝山却空手而归”。