🌐English
目录
  1. MNIST 网站计数器
  2. Cambricon-Q:量化训练架构
  3. Cambricon-FR:分形可重配指令集结构计算机(通用分形计算机)
  4. Cambricon-F:一种具有分形冯·诺伊曼体系结构的机器学习计算机

MNIST 网站计数器

为本站设计的点击计数器,就是页面底部的那个。 虽然我估计如今99%的人和MNIST打交道时都是使用Python的, 但本站承诺不使用Python,选择用C++实现了这个小功能。 整个做下来体验并不比Python复杂太多,料想能节约不少碳排放😁。

实现

下载MNIST测试集,用gzip解压。 因为LeCun说测试集的前5000个比较容易识别,所以程序里只使用了这5000个。 访问次数记录在名为fcounter.db的文件里, 每一位数字从测试集中随机抽选,组成PNG图片 (这里使用了Magick++来比较方便地生成PNG), 然后通过FastCGI接口返回给webserver。

代码
counter.cppview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <iostream>
#include <fstream>
#include <cstring>
#include <random>
#include <map>
#include <Magick++.h>
#include <fcgio.h>

std::ifstream t10k_images("./t10k-images-idx3-ubyte", std::ios_base::binary | std::ios_base::in);
std::ifstream t10k_labels("./t10k-labels-idx1-ubyte", std::ios_base::binary | std::ios_base::in);
size_t count = 0;

enum {
IMAGE_IN = 16,
IMAGE_NUM = 5000,
IMAGE_SIZE = 28,
IMAGE_BYTE = IMAGE_SIZE * IMAGE_SIZE,
LABEL_IN = 8,
};

std::multimap<int, size_t> categories;
std::random_device rd;
std::mt19937 mt(rd());

void init() {
t10k_labels.seekg(LABEL_IN);
for (size_t i = 0; i < IMAGE_NUM; i++) {
unsigned char c;
t10k_labels.read(reinterpret_cast<char*>(&c), 1);
categories.insert({c, i * IMAGE_BYTE + IMAGE_IN});
}
std::ifstream fcounter("./fcounter.db", std::ios_base::binary | std::ios_base::in);
fcounter.read(reinterpret_cast<char*>(&count), sizeof(count));
fcounter.close();
}

void select(std::array<unsigned char, IMAGE_BYTE>& img, unsigned char c) {
auto range = categories.equal_range(c);
auto first = range.first; auto last = range.second;
auto n = std::distance(first, last);
std::uniform_int_distribution<> dist(0, n - 1);
auto sk = std::next(first, dist(mt))->second;
t10k_images.seekg(sk);
t10k_images.read(reinterpret_cast<char*>(img.data()), IMAGE_BYTE);
}

void hit(std::ostream& os) {
count++;
std::ofstream fcounter("./fcounter.db", std::ios_base::binary | std::ios_base::out);
fcounter.write(reinterpret_cast<char*>(&count), sizeof(count));
fcounter.close();
std::string str = std::to_string(count);
if (str.length() < 6)
str = std::string(6 - str.length(), '0') + str;
size_t w = IMAGE_SIZE * str.length(), h = IMAGE_SIZE;
std::vector<unsigned char> canvas(w*h, 0);
size_t i = 0;
for (auto&& c : str) {
std::array<unsigned char, IMAGE_BYTE> img;
select(img, c - '0');
for (int y = 0; y < IMAGE_SIZE; y++) {
std::memcpy(&canvas[y * w + i * IMAGE_SIZE], &img[y * IMAGE_SIZE], IMAGE_SIZE);
}
i++;
}
Magick::Image image(IMAGE_SIZE*str.length(), IMAGE_SIZE, "I", Magick::CharPixel, canvas.data());
Magick::Blob blob;
image.type(Magick::GrayscaleType);
image.magick("PNG");
image.write(&blob);
os << "Content-Type: image/png\r\n";
os << "Content-length: " << blob.length() << "\r\n\r\n";
os.write(reinterpret_cast<const char*>(blob.data()), blob.length()) << std::flush;
}

int main() {
FCGX_Request request;
init();
FCGX_Init();
FCGX_InitRequest(&request, 0, 0);
while (FCGX_Accept_r(&request) == 0) {
fcgi_streambuf osbuf(request.out);
std::ostream os(&osbuf);
hit(os);
}
return 0;
}

以上代码就贡献给Public Domain了。

编译

  • APT安装libmagick++-dev libfcgi-dev
  • 为了使用FastCGI++,需要添加编译选项-lfcgi++ -lfcgi
  • 一个简单的CMake就可以自动找到Magick++;不使用CMake的话,添加magick++-config提供的编译选项。

部署

我用spawn-fcgi来启动编译出来的二进制(在systemd里通过设置服务,自动启动)。 主流的webserver都支持FastCGI接口,设置一个FastCGI反向代理,指向spawn-fcgi启动的端口,部署配置就完成了。 我用的是Caddy:

1
2
3
reverse_proxy /counter.png localhost:21930 {
transport fastcgi
}

/counter.png加在页面底部HTML里,每次刷新页面都能观察到数字增加; 如果浏览器缓存了图片,在链接之间跳转不触发对服务器的访问,数字就不会增加。

Cambricon-Q:量化训练架构

为了追求高能效,深度学习加速器大多采用8比特乃至更低位宽的运算单元,尤其是在移动平台上。这样的低位宽加速器采用特殊技术手段可以满足推理任务的精度要求,但是在训练时就不行了,因为训练过程对数值精度的敏感性远远高于推理。怎样扩展架构才能让移动平台上的加速器支持高效的移动端训练呢?

针对这个问题,我们开展了Cambricon-Q的研究。

Cambricon-Q引入了三种新模块:

  • SQU支持数据传输过程中的沿途统计和量化
  • QBC管理片上缓存中的混合数据精度和格式;
  • NDPO在近存端完成权重更新。

该结构能够支持多种量化训练方法。实验表明,Cambricon-Q在几乎不损失训练精度的前提下,实现了高效的深度学习训练。

论文发表在ISCA 2021。[DOI]

Cambricon-FR:分形可重配指令集结构计算机(通用分形计算机)

本文工作是 Cambricon-F:一种具有分形冯·诺伊曼体系结构的机器学习计算机 的延续。

Cambricon-F通过分形执行实现了编程与系统规模无关的重要性质,缓解了机器学习计算机的编程难题。 但是,该计算机上的分形执行是由硬件控制器操控实现的,只支持少数常用基本算子(卷积、池化等), 其他功能需要通过这些基本算子串行拼搭来实现。 我们发现,用有限、固定的指令集支持复杂多变的实际应用负载时,会产生失效现象,影响机器的计算效率。

在支持传统的卷积神经网络等规整的算法时,该机能够达到最优效率。 但在复杂多变的应用场景下,即使应用本身符合分形运算的定义,也会产生失效现象。 失效现象定义为某些应用执行在分形计算机上时,计算或通信复杂度发生了改变。 本文还以TopK和3DConv两种并不十分复杂的算子举例说明了失效现象。

举一个直观的例子: 用户希望执行应用“贝叶斯网络”,该应用是符合分形运算定义的,本能够以分形方式高效执行; 但由于Cambricon-F中并不存在专门的“贝叶斯网络”指令,该应用只能分解为一系列基本运算后串行执行。 如果能够扩展指令集,增加一条BAYES分形指令,就可以在达到叶子节点前一直保持分形执行,显著提升计算效率了。

据此,我们改进了Cambricon-F的架构,提出具有分形可重配指令集结构的Cambricon-FR。 理论地看,Cambricon-F是一台分形计算机,而Cambricon-FR则可以称为一台通用分形计算机; Cambricon-F能够在某种特定应用负载上实现高效计算,Cambricon-FR则能够在复杂多变的应用负载上实现高效计算。

论文发表于《IEEE Transactions on Computers》。[DOI]

Cambricon-F:一种具有分形冯·诺伊曼体系结构的机器学习计算机

在寒武纪从事软件架构工作期间,我深切体会到了软件开发的痛处。2016年我刚刚接手的时候,核心软件是由我和王禹卿两人负责开发,代码规模1万5千行;在2018年我离开的时候,开发团队增加到60余人,代码规模72万行。从代码行数来看,软件的复杂度每5个月就翻一倍。无论增加多少人手,团队仍旧承担着巨大的开发压力:客户需求急迫,需要立刻应对;硬件规模在变化,冲击了现有软件设计方案,很可能需要针对新硬件重新设计;新功能需要实现,老代码需要重构,越积攒越多;文档还没建立;测试还没建立……

我可能称不上一个专业的软件架构师,但是又有谁能在一开始就保证预见了未来的变化?试想一下,底层硬件一开始是单核,一年后变为多核,又一年后变为NUMA多核。在这种变化速度和幅度之下,同一套软件不经过大规模重构,怎么可能能够保持适应?问题的关键在于硬件的规模增加了,因而需要编程控制的抽象层次也在增多,使编程变得复杂。我们将问题定义为编程-规模相关性

为了解决这个工程实践问题,我们开始了Cambricon-F的研究。

为了解决编程的规模相关性,需要引入一种规模不变量,我们找到的这种不变量是分形:分形的几何图形,在不同规模尺度上自相似。我们将应用负载以分形方式定义,将硬件结构也以分形方式定义,两个规模不变量均可自由缩放,直至找到互相适配的规模。

Cambricon-F首次提出分形冯·诺伊曼体系结构。该结构最大的特点是:

  • 串行编程,自动展开为适合硬件规模的并行执行;
  • 编程-规模无关性——程序中不体现硬件规模,可以在不同规模的Cambricon-F实例上迁移;
  • 通过分形流水线,保持了高效率。

论文发表在ISCA 2019。[DOI]