我在C++中使用SIMD指令实现了VGG19网络,仅用于推理。我想优化一个推理请求的延迟。
由于VGG19主要由卷积层组成,我主要关注于实现高效的卷积层。我在做这篇论文的时候关注了这篇论文:SIMD体系结构上的高性能深度学习卷积剖析。
我的实施提供了正确的结果。我使用SIMD本质和本文中描述的算法。所有砝码都预先加载。在运行实际推理之前,分配每一层的输入和输出缓冲区。
作为一个例子,让我们看看VGG19网络的第二个卷积层:
- 输入:(22422464((填充后的22622664(
- 输出:(22422464(
- 内核:(3,3,64,64((KH,KW,C_IN,C_OUT(
这是代码对应的代码:
void conv2d_block1_conv2(const float* in, const float* weights, float* out) {
constexpr int VLEN = 8; // to use _mm256_* intrisics
constexpr int C_OUT_B = VLEN;
constexpr int C_IN_B = VLEN;
constexpr int H = 226; // Input Height
constexpr int W = 226; // Input Width
constexpr int C_IN = 64; // Input Channels
constexpr int KH = 3; // Kernel Height
constexpr int KW = 3; // Kernel Width
constexpr int H_OUT = 224; // Output Height
constexpr int W_OUT = 224; // Output Width
constexpr int C_OUT = 64; // Output Channels
__m256 in_vec, weights_vec, out_vec;
for (int c_out = 0; c_out < C_OUT / C_OUT_B; c_out++)
for (int c_in_b = 0; c_in_b < C_IN / C_IN_B; c_in_b++)
for (int h_out = 0; h_out < H_OUT; h_out++)
for (int w_out = 0; w_out < W_OUT; w_out++){
const int outIdx = LINEAR_4(c_out, h_out, w_out, 0, H_OUT, W_OUT, C_OUT_B);
out_vec = _mm256_load_ps (&out[outIdx]);
for (int kh = 0; kh < KH; kh++)
for (int kw = 0; kw < KW; kw++)
for (int c_in = 0; c_in < C_IN_B; c_in++){
const int inIdx = LINEAR_4(c_in_b, h_out + kh, w_out + kw, c_in, H, W, C_IN_B);
const int weightsIdx = LINEAR_6(c_out, c_in_b, kh, kw, c_in, 0, C_IN / C_IN_B, KH, KW, C_IN_B, C_OUT_B);
in_vec = _mm256_set1_ps (in[inIdx]);
weights_vec = _mm256_load_ps(&weights[weightsIdx]);
out_vec = _mm256_fmadd_ps (in_vec, weights_vec, out_vec);
_mm256_store_ps(&out[outIdx], out_vec);
}
}
}
注意:我正在处理一个线性地址空间。函数CCD_ 1和CCD_ 2将多维索引映射为一维索引。
array[c_out][h_out][w_out][0] <-> LINEAR_4(c_out, h_out, w_out, 0, H_OUT, W_OUT, C_OUT_B);
array[c_out][c_in_b][kh][kw][c_in][0] <-> LINEAR_6(c_out, c_in_b, kh, kw, c_in, 0, C_IN / C_IN_B, KH, KW, C_IN_B, C_OUT_B);
我为每个卷积层创建了一个如上所述的函数,为编译器提供了最佳的优化可能性。
然而,执行时间相当糟糕。对于整个VGG19网络(均为单线程执行(:
- 我的实现:2400毫秒
- 使用
model.predict(image)
的Tensorflow后端Keras:600ms
这种巨大的性能差距让我怀疑自己做错了什么。我使用带有-O3
标志的clang。
所以我的问题是:
- 是否有我没有考虑的关键因素
- Keras/TensorFlow使用哪个实现。它们怎么这么快
我找到了性能不佳的原因。clang编译器只使用了2个SSE寄存器,而不是所有可用的寄存器。这导致对一级缓存进行不必要的写入和读取。
我手动展开了两个内部循环,编译器现在使用了所有可用的16个SSE寄存器。性能急剧提高。
如果使用SSE Intristics,请确保检查生成的程序集。