如何在CPU上优化GEMM(下)

Array Packing

另一个重要的技巧是数组打包。这个技巧是对数组的存储维度进行重新排序,将某个维度上的连续访问模式在平滑后转换为顺序模式。

如上图所示,在阻塞计算之后,可以观察到B的数组访问模式(扁平化后),它是规则的但不连续的。期望经过一些转换,可以得到连续访问模式。可以将[16][16]数组重新排序为[16/4][16][4]数组,这样当从压缩数组中获取相应的值时,B的访问模式将是顺序的。

# We have to re-write the algorithm slightly.

packedB = te.compute((N / bn, K, bn), lambda x, y, z: B[y, x * bn + z], name="packedB")

C = te.compute(

(M, N),

lambda x, y: te.sum(A[x, k] * packedB[y // bn, k, tvm.tir.indexmod(y, bn)], axis=k),

name="C",

)

s = te.create_schedule(C.op)

xo, yo, xi, yi = s[C].tile(C.op.axis[0], C.op.axis[1], bn, bn)

(k,) = s[C].op.reduce_axis

ko, ki = s[C].split(k, factor=4)

s[C].reorder(xo, yo, ko, xi, ki, yi)

s[C].vectorize(yi)

x, y, z = s[packedB].op.axis

s[packedB].vectorize(z)

s[packedB].parallel(x)

func = tvm.build(s, [A, B, C], target=target, name="mmult")

assert func

c = tvm.nd.array(numpy.zeros((M, N), dtype=dtype), ctx)

func(a, b, c)

tvm.testing.assert_allclose(c.asnumpy(), answer, rtol=1e-5)

evaluator = func.time_evaluator(func.entry_name, ctx, number=10)

print("Opt4: %f" % evaluator(a, b, c).mean)

Out:

Opt4: 0.105409

Here is the generated IR after array packing.

print(tvm.lower(s, [A, B, C], simple_mode=True))

Out:

primfn(A_1: handle, B_1: handle, C_1: handle) -> ()

attr = {"global_symbol": "main", "tir.noalias": True}

buffers = {C: Buffer(C_2: Pointer(float32), float32, [1024, 1024], []),

B: Buffer(B_2: Pointer(float32), float32, [1024, 1024], []),

A: Buffer(A_2: Pointer(float32), float32, [1024, 1024], [])}

buffer_map = {A_1: A, B_1: B, C_1: C} {

attr [packedB: Pointer(float32)] "storage_scope" = "global";

allocate(packedB, float32x32, [32768]) {

for (x: int32, 0, 32) "parallel" {

for (y: int32, 0, 1024) {

packedB[ramp(((x*32768) + (y*32)), 1, 32)] = (float32x32*)B_2[ramp(((y*1024) + (x*32)), 1, 32)]

}

}

for (x.outer: int32, 0, 32) {

for (y.outer: int32, 0, 32) {

for (x.inner.init: int32, 0, 32) {

C_2[ramp((((x.outer*32768) + (x.inner.init*1024)) + (y.outer*32)), 1, 32)] = broadcast(0f32, 32)

}

for (k.outer: int32, 0, 256) {

for (x.inner: int32, 0, 32) {

for (k.inner: int32, 0, 4) {

C_2[ramp((((x.outer*32768) + (x.inner*1024)) + (y.outer*32)), 1, 32)] = ((float32x32*)C_2[ramp((((x.outer*32768) + (x.inner*1024)) + (y.outer*32)), 1, 32)] + (broadcast((float32*)A_2[((((x.outer*32768) + (x.inner*1024)) + (k.outer*4)) + k.inner)], 32)*(float32x32*)packedB[ramp((((y.outer*32768) + (k.outer*128)) + (k.inner*32)), 1, 32)]))

}

}

}

}

}

}

}

Write cache for blocks

分块后,程序将结果逐块写入C,访问模式不是顺序的。因此,可以使用一个顺序缓存数组来保存块结果,并在所有块结果就绪时写入C。

s = te.create_schedule(C.op)

# Allocate write cache

CC = s.cache_write(C, "global")

xo, yo, xi, yi = s[C].tile(C.op.axis[0], C.op.axis[1], bn, bn)

# Write cache is computed at yo

s[CC].compute_at(s[C], yo)

# New inner axes

xc, yc = s[CC].op.axis

(k,) = s[CC].op.reduce_axis

ko, ki = s[CC].split(k, factor=4)

s[CC].reorder(ko, xc, ki, yc)

s[CC].unroll(ki)

s[CC].vectorize(yc)

x, y, z = s[packedB].op.axis

s[packedB].vectorize(z)

s[packedB].parallel(x)

func = tvm.build(s, [A, B, C], target=target, name="mmult")

assert func

c = tvm.nd.array(numpy.zeros((M, N), dtype=dtype), ctx)

func(a, b, c)

tvm.testing.assert_allclose(c.asnumpy(), answer, rtol=1e-5)

evaluator = func.time_evaluator(func.entry_name, ctx, number=10)

print("Opt5: %f" % evaluator(a, b, c).mean)

Out:

Opt5: 0.098048

Here is the generated IR after blocking.

print(tvm.lower(s, [A, B, C], simple_mode=True))

Out:

primfn(A_1: handle, B_1: handle, C_1: handle) -> ()

attr = {"global_symbol": "main", "tir.noalias": True}

buffers = {C: Buffer(C_2: Pointer(float32), float32, [1024, 1024], []),

B: Buffer(B_2: Pointer(float32), float32, [1024, 1024], []),

A: Buffer(A_2: Pointer(float32), float32, [1024, 1024], [])}

buffer_map = {A_1: A, B_1: B, C_1: C} {

attr [packedB: Pointer(float32)] "storage_scope" = "global";

allocate(packedB, float32x32, [32768]);

attr [C.global: Pointer(float32)] "storage_scope" = "global";

allocate(C.global, float32, [1024]) {

for (x: int32, 0, 32) "parallel" {

for (y: int32, 0, 1024) {

packedB[ramp(((x*32768) + (y*32)), 1, 32)] = (float32x32*)B_2[ramp(((y*1024) + (x*32)), 1, 32)]

}

}

for (x.outer: int32, 0, 32) {

for (y.outer: int32, 0, 32) {

for (x.c.init: int32, 0, 32) {

C.global[ramp((x.c.init*32), 1, 32)] = broadcast(0f32, 32)

}

for (k.outer: int32, 0, 256) {

for (x.c: int32, 0, 32) {

C.global[ramp((x.c*32), 1, 32)] = ((float32x32*)C.global[ramp((x.c*32), 1, 32)] + (broadcast((float32*)A_2[(((x.outer*32768) + (x.c*1024)) + (k.outer*4))], 32)*(float32x32*)packedB[ramp(((y.outer*32768) + (k.outer*128)), 1, 32)]))

C.global[ramp((x.c*32), 1, 32)] = ((float32x32*)C.global[ramp((x.c*32), 1, 32)] + (broadcast((float32*)A_2[((((x.outer*32768) + (x.c*1024)) + (k.outer*4)) + 1)], 32)*(float32x32*)packedB[ramp((((y.outer*32768) + (k.outer*128)) + 32), 1, 32)]))

C.global[ramp((x.c*32), 1, 32)] = ((float32x32*)C.global[ramp((x.c*32), 1, 32)] + (broadcast((float32*)A_2[((((x.outer*32768) + (x.c*1024)) + (k.outer*4)) + 2)], 32)*(float32x32*)packedB[ramp((((y.outer*32768) + (k.outer*128)) + 64), 1, 32)]))

C.global[ramp((x.c*32), 1, 32)] = ((float32x32*)C.global[ramp((x.c*32), 1, 32)] + (broadcast((float32*)A_2[((((x.outer*32768) + (x.c*1024)) + (k.outer*4)) + 3)], 32)*(float32x32*)packedB[ramp((((y.outer*32768) + (k.outer*128)) + 96), 1, 32)]))

}

}

for (x.inner: int32, 0, 32) {

for (y.inner: int32, 0, 32) {

C_2[((((x.outer*32768) + (x.inner*1024)) + (y.outer*32)) + y.inner)] = (float32*)C.global[((x.inner*32) + y.inner)]

}

}

}

}

}

}

Parallel

此外,还可以利用多核处理器来实现线程级的并行化。

s = te.create_schedule(C.op)

CC = s.cache_write(C, "global")

xo, yo, xi, yi = s[C].tile(C.op.axis[0], C.op.axis[1], bn, bn)

s[CC].compute_at(s[C], yo)

xc, yc = s[CC].op.axis

(k,) = s[CC].op.reduce_axis

ko, ki = s[CC].split(k, factor=4)

s[CC].reorder(ko, xc, ki, yc)

s[CC].unroll(ki)

s[CC].vectorize(yc)

# parallel

s[C].parallel(xo)

x, y, z = s[packedB].op.axis

s[packedB].vectorize(z)

s[packedB].parallel(x)

func = tvm.build(s, [A, B, C], target=target, name="mmult")

assert func

c = tvm.nd.array(numpy.zeros((M, N), dtype=dtype), ctx)

func(a, b, c)

tvm.testing.assert_allclose(c.asnumpy(), answer, rtol=1e-5)

evaluator = func.time_evaluator(func.entry_name, ctx, number=50)

opt6_time = evaluator(a, b, c).mean

print("Opt6: %f" % opt6_time)

Out:

Opt6: 0.032347

Here is the generated IR after parallelization.

print(tvm.lower(s, [A, B, C], simple_mode=True))

Out:

primfn(A_1: handle, B_1: handle, C_1: handle) -> ()

attr = {"global_symbol": "main", "tir.noalias": True}

buffers = {C: Buffer(C_2: Pointer(float32), float32, [1024, 1024], []),

B: Buffer(B_2: Pointer(float32), float32, [1024, 1024], []),

A: Buffer(A_2: Pointer(float32), float32, [1024, 1024], [])}

buffer_map = {A_1: A, B_1: B, C_1: C} {

attr [packedB: Pointer(float32)] "storage_scope" = "global";

allocate(packedB, float32x32, [32768]) {

for (x: int32, 0, 32) "parallel" {

for (y: int32, 0, 1024) {

packedB[ramp(((x*32768) + (y*32)), 1, 32)] = (float32x32*)B_2[ramp(((y*1024) + (x*32)), 1, 32)]

}

}

for (x.outer: int32, 0, 32) "parallel" {

attr [C.global: Pointer(float32)] "storage_scope" = "global";

allocate(C.global, float32, [1024]);

for (y.outer: int32, 0, 32) {

for (x.c.init: int32, 0, 32) {

C.global[ramp((x.c.init*32), 1, 32)] = broadcast(0f32, 32)

}

for (k.outer: int32, 0, 256) {

for (x.c: int32, 0, 32) {

C.global[ramp((x.c*32), 1, 32)] = ((float32x32*)C.global[ramp((x.c*32), 1, 32)] + (broadcast((float32*)A_2[(((x.outer*32768) + (x.c*1024)) + (k.outer*4))], 32)*(float32x32*)packedB[ramp(((y.outer*32768) + (k.outer*128)), 1, 32)]))

C.global[ramp((x.c*32), 1, 32)] = ((float32x32*)C.global[ramp((x.c*32), 1, 32)] + (broadcast((float32*)A_2[((((x.outer*32768) + (x.c*1024)) + (k.outer*4)) + 1)], 32)*(float32x32*)packedB[ramp((((y.outer*32768) + (k.outer*128)) + 32), 1, 32)]))

C.global[ramp((x.c*32), 1, 32)] = ((float32x32*)C.global[ramp((x.c*32), 1, 32)] + (broadcast((float32*)A_2[((((x.outer*32768) + (x.c*1024)) + (k.outer*4)) + 2)], 32)*(float32x32*)packedB[ramp((((y.outer*32768) + (k.outer*128)) + 64), 1, 32)]))

C.global[ramp((x.c*32), 1, 32)] = ((float32x32*)C.global[ramp((x.c*32), 1, 32)] + (broadcast((float32*)A_2[((((x.outer*32768) + (x.c*1024)) + (k.outer*4)) + 3)], 32)*(float32x32*)packedB[ramp((((y.outer*32768) + (k.outer*128)) + 96), 1, 32)]))

}

}

for (x.inner: int32, 0, 32) {

for (y.inner: int32, 0, 32) {

C_2[((((x.outer*32768) + (x.inner*1024)) + (y.outer*32)) + y.inner)] = (float32*)C.global[((x.inner*32) + y.inner)]

}

}

}

}

}

}

Summary

在用18行代码应用上述简单的优化之后,生成的代码可以达到MKL的60%的numpy性能。请注意,网页上的输出反映了非独占Docker容器上的运行时间,因此是不可靠的。强烈建议自己来完成,以观察TVM所获得的性能提升。

https://tvm.apache.org/docs/tutorials/optimize/opt_gemm.html#sphx-glr-tutorials-optimize-opt-gemm-py

如何在CPU上优化GEMM(下)的更多相关文章

  1. 如何在CPU上优化GEMM(上)

    如何在CPU上优化GEMM(上) (TL:DR)TVM提供了抽象接口,用户分别描述算法和算法的实现组织(所谓的调度).通常,在高性能调度中编写算法会破坏算法的可读性和模块性.尝试各种看似有希望的时间表 ...

  2. 如何在GPU上优化卷积

    本文将演示如何在TVM中编写高性能的卷积实现.以平方大小的输入张量和滤波器为例,并假设卷积的输入量很大.使用不同的布局来存储数据,以实现更好的数据局部性.缓冲区布局为HWCN,代表高度,宽度,通道,批 ...

  3. 【翻译】借助 NeoCPU 在 CPU 上进行 CNN 模型推理优化

    本文翻译自 Yizhi Liu, Yao Wang, Ruofei Yu.. 的  "Optimizing CNN Model Inference on CPUs" 原文链接: h ...

  4. YOLOv5】LabVIEW+OpenVINO让你的YOLOv5在CPU上飞起来

    前言 上一篇博客给大家介绍了使用opencv加载YOLOv5的onnx模型,但我们发现使用CPU进行推理检测确实有些慢,那难道在CPU上就不能愉快地进行物体识别了吗?当然可以啦,这不LabVIEW和O ...

  5. 一次线上服务高 CPU 占用优化实践 (转)

    线上有一个非常繁忙的服务的 JVM 进程 CPU 经常跑到 100% 以上,下面写了一下排查的过程.通过阅读这篇文章你会了解到下面这些知识. Java 程序 CPU 占用高的排查思路 可能造成线上服务 ...

  6. linux下将不同线程绑定到不同core和cpu上——pthread_setaffinity_np

    =============================================================== linux下的单进程多线程的程序,要实现每个线程平均分配到多核cpu,主 ...

  7. 如何在TVM上集成Codegen(下)

    如何在TVM上集成Codegen(下) Bring DNNL to TVM: JSON Codegen/Runtime 现在实现将中继图序列化为JSON表示的DNNL codegen,然后实现DNNL ...

  8. linxu下查看进程的线程方法;如何知道某个进程或者线程运行在哪个CPU上?

    1.top -H -p <pid>  ; top -H 在top命令后,按H键:或者top -H 2.ps -T -p <pid> “-T”选项可以开启线程查看 3.htop, ...

  9. TVM在ARM GPU上优化移动深度学习

    TVM在ARM GPU上优化移动深度学习 随着深度学习的巨大成功,将深度神经网络部署到移动设备的需求正在迅速增长.与在台式机平台上所做的类似,在移动设备中使用GPU可以提高推理速度和能源效率.但是,大 ...

随机推荐

  1. 洛谷P1424 小鱼的航程(改进版)

    题目背景 原来的题目太简单,现改进让小鱼周末也休息,请已经做过重做该题. 题目描述 有一只小鱼,它上午游泳150公里,下午游泳100公里,晚上和周末都休息(实行双休日),假设从周x(1<=x&l ...

  2. POJ1703带权并查集(距离或者异或)

    题意:       有两个黑社会帮派,有n个人,他们肯定属于两个帮派中的一个,然后有两种操作 1 D a b 给出a b 两个人不属于同一个帮派 2 A a b 问a b 两个人关系 输出 同一个帮派 ...

  3. MySQL模糊查询,查询语句是对的。但是就是没有查询结果

    问题 解决 当我用其他非中文的值去查询,发现可以查询到结果 所以问题就出现在数据库的配置中 spring.datasource.url=jdbc:mysql://localhost:3306/data ...

  4. python 键盘中断子线程及graceful exiting方案

    最近需要实现一个服务程序的graceful exiting,保证在退出前关闭所有已创建的子线程 python借助KeyboardInterrupted异常响应键盘中断,因此首先尝试在子线程中try-c ...

  5. select执行顺序

    先from 找到表on过滤 找到两张表有对应关系的记录按join的方式添加外部行where 过滤group by分组having 过滤select 从having 过滤出来的字段中选择需要的字段dis ...

  6. mac 安装jmeter

    1.打开jemeter网址 http://jmeter.apache.org/download_jmeter.cgi 2.下载Binaries中的 apache-jmeter-5.0.tgz 3.解压 ...

  7. ubuntu下载安装软件并创建图标

    本列以安装webstorm软件 1.官网下载软件的压缩包 2.解压 umlinux@umlinux-PC:~/idea$ tar -zxvf ideaIU-2020.3.1.tar.gz 3.找到we ...

  8. spring总结归纳

    愿历尽千帆,归来仍是少年 简介 spring: 1.是分层的full-stack(全栈)轻量级开源框架.2. 内核:IOC和AOP.3.提供web层springMvc和业务层事务管理,整合众多的开源框 ...

  9. Spring Cloud Alibaba(12)---Gatway概述、简单示例

    Gatway概述.项目搭建 前言 有关网关的概念之前这里不在概述,因为之前在写zuul网关的时候有详细陈述过,地址如下: SpringCloud(7)---网关概念.Zuul项目搭建 SpringCl ...

  10. 在写脚本时,在一开始(Shebang 之后)就加上这一句,或者它的缩略版: set -xeuo pipefail

    编写可靠 bash 脚本的一些技巧 腾讯技术工程 ​ 已认证的官方帐号   1,254 人赞同了该文章 写过很多 bash 脚本的人都知道,bash 的坑不是一般的多. 其实 bash 本身并不是一个 ...