常数整数乘法优化

常数整数乘法优化


嵌入式机器学习或深度学习算法中很大一部分运算涉及常数乘法:

  • 神经网络的卷积层和全连接层将来自前一层的数据和固定的权重矩阵相乘计算输出,SVM算法中通过固定的矩阵和向量乘法计算判决函数
  • 图像处理中各种滤波器算法也需要计算图像数据和固定的滤波器核的二维卷积

基于正则有符号数的常数整数乘法优化

就是通过移位加减法实现常数乘法的优化

常数乘法分为:

  1. 单常数乘法 SCM —— Single Contant Multipilication
  2. 多常数乘法 MCM —— Multiple Constants Multiplication

Example 1: 20 x 20x 20x

20 = ( 10100 ) 2 = ( 10000 ) 2 + ( 100 ) 2 = 2 4 + 2 2 20=(1 0100)_2=(10000)_2+(100)_2=2^4+2^2 20=101002=(10000)2+(100)2=24+22

因此 20 x 20x 20x乘积可表示为
20 x = ( 2 4 + 2 2 ) x = 2 4 x + 2 2 x 20x=(2^4+2^2)x=2^4x+2^2x 20x=(24+22)x=24x+22x
对于整数 x ,上述运算进一步表示为
20 x = ( x ≪ 4 ) + ( x ≪ 2 ) 20x = (x≪4)+(x≪2) 20x=(x4)+(x2)
不难发现:加法次数和二进制形式中的1的个数有关

Example 2: 153 x 153x 153x

153 x = ( 10011001 ) 2 x 153x = (1001 1001)_2 x 153x=(10011001)2x

153 x = ( x ≪ 7 ) + ( x ≪ 4 ) + ( x ≪ 3 ) + x 153x=(x≪7)+(x≪4)+(x≪3)+x 153x=(x7)+(x4)+(x3)+x

需要进行3次加法,还能简化吗?

可以的。
153 = ( 10011001 ) 2 153=(1001 1001)_2 153=(10011001)2

= > 153 x = 10010000 x + 1001 x => 153x = 10010000x + 1001x =>153x=10010000x+1001x

令 y = x × ( 1001 ) 2 令 y=x×(1001)_2 y=x×(1001)2

153 x = y + y ≪ 4 153x = y + y ≪ 4 153x=y+y4
仅需要2次加法

Example 3: 15 x 15x 15x

15 = ( 1111 ) 2 15=(1111)_2 15=11112

15 x = x ≪ 3 + x ≪ 2 + x ≪ 1 + x 15x=x≪3+x≪2+x≪1+x 15x=x3+x2+x1+x

这还不是最优的,重新分解15
15 = ( 10000 ) 2 − ( 1 ) 2 15=(10000)_2−(1)_2 15=(10000)2(1)2

15 x = x ≪ 4 − x 15x=x≪4−x 15x=x4x
因此,定义新的“有符号二进制形式”:
15 = ( 1000 1 ˉ ) 2 = 2 4 − 2 0 15=(1 000\bar{1})_2=2^4−2^0 15=10001ˉ2=2420

正则有符号数编码 CSD

将有符号整数转换为非0位最少的“有符号二进制序列”,这一序列成为 CSD:Canonical Signed Digit

为了尽可能降低常数乘法的运算量,我们希望找出非 0 位尽可能少的有符号二进制序列表示给定的常数。

对于无符号整数的二进制形式,从低位开始扫描,把所有长度大于2的连续1位串替换成 100 ⋅ ⋅ ⋅ 0 1 ˉ 100···0\bar{1} 100⋅⋅⋅01ˉ

例如 十进制 15 599,二进制为11 1100 1110 1111,可以转换为 $ 1000\bar{1}01000\bar{1}000\bar{1}$

因此,可以实现乘法优化:
x × ( 11110011101111 ) 2 = x × ( 1000 1 ˉ 01000 1 ˉ 000 1 ˉ ) 2 x×(11110011101111)_2=x×(1000\bar{1}01000\bar{1}000\bar{1})_2 x×(11110011101111)2=x×(10001ˉ010001ˉ0001ˉ)2

= x ≪ ( 14 − x ) ≪ ( 10 + x ) ≪ ( 8 − x ) ≪ ( 4 − x ) =x≪(14−x)≪(10+x)≪(8−x)≪(4−x) =x(14x)(10+x)(8x)(4x)

二进制序列转CSD编码算法流程

  1. 设置
    { b − 1 = 0 b N = b N − 1 γ − 1 = 0 \begin{cases} b_{-1} = 0 \\ b_N = b_{N-1} \\ \gamma_{-1} = 0 \end{cases} b1=0bN=bN1γ1=0

执行下面的循环 伪代码
f o r ( i = 1   t o   N − 1 ) for (i =1 \ to \ N-1) for(i=1 to N1)
{ \{ {

θ i = b i   ⨁ b i − 1 \theta_i = b_i \ \bigoplus b_{i-1} θi=bi bi1

$$
\gamma_i = (1 - \gamma_{i-1})\theta_{i}

$$

a i = ( 1 − 2 b i + 1 ) γ i a_i = (1-2b_{i+1})\gamma_i ai=(12bi+1)γi

} \} }

代码实现

import numpy as np

####################
# 生成二进制数的CSD表示
####################

## 将二进制字串(整数,无'0b'前缀)转成字典
# 代码清单 4-20
def bin_str_to_dict(bv): return {n:int(v=='1') for n,v in enumerate(bv[::-1])}

## 将二进制字串(整数)转成CSD表示
def bin_str_to_csd(bv):    
    w=len(bv)
    b=bin_str_to_dict(bv)
    b[-1]=0
    b[w]=b[w-1]
    gamma={-1:0}
    a={}
    theta={}
    for i in range(w):
        theta[i]=b[i]^b[i-1]
        gamma[i]=(1-gamma[i-1])*theta[i]
        a[i]=(1-2*b[i+1])*gamma[i]
    return a

## 将16-bit整数表示为二进制字串,用补码表示负数
def int16_to_bin_str(v):
    if v<0: v+=65536
    bv=bin(v)[2:]
    return '0'*(16-len(bv))+bv

## 将16-bit有符号整数转成CSD表示
def int16_to_csd(v):
    bv=int16_to_bin_str(v)
    csd=bin_str_to_csd(bv)
    return csd

## 将字典转回16-bit整数
def dict_to_int16(d): 
    v=sum([(2**k)*v for k,v in d.items()])
    if v>32767: v-=65536
    return v 

## 将字典内容打印成二进制字串
def dict_to_str(d):
    s=''
    for n in range(max(d.keys())+1):
        if n in d:
            s={-1:'n',0:'0',1:'1'}[d[n]]+s
        else:
            s='x'+x
    return s[::-1]

## 将CSD内容转成移位运算指令字符串
# 代码清单 4-21
def csd_to_code(csd):
    s=''
    for n,v in csd.items():
        if v==0: 
            continue
        elif n==0:
            s+='+x' if v>0 else '-x'
        else:
            s+='+(x<<%d)'%n if v>0 else '-(x<<%d)'%n
    return  s[1:] if s[0]=='+' else s

## 将16-bit有符号整数转成移位运算指令字符串
def int16_to_code(v):
    return csd_to_code(bin_str_to_csd(int16_to_bin_str(v)))
    
####################
# 单元测试
####################
if __name__=='__main__':
    print('\n[Randy] ---- test 1 ----')
    bv=int16_to_bin_str(-141)
    print('[Randy] bv:\n    ',bv)

    d=bin_str_to_dict(bv)
    print('[Randy] d=bin_str_to_dict(bv):\n    ',d)

    v=dict_to_int16(d)
    print('[Randy] v=dict_to_int16(d):\n    ',v)

    csd=bin_str_to_csd(bv)
    print('[Randy] csd                :\n    ',csd                )
    print('[Randy] dict_to_str(csd)   :\n    ',dict_to_str(csd)   )
    print('[Randy] dict_to_int16(csd) :\n    ',dict_to_int16(csd) )
    print('[Randy] csd_to_code(csd)   :\n    ',csd_to_code(csd)   )
    print('[Randy] int16_to_code(-141):\n    ',int16_to_code(-141))
    

    # test2
    print('\n[Randy] ---- test 2 ----')
    bv=int16_to_bin_str(141)
    print('[Randy] bv:\n    ',bv)

    d=bin_str_to_dict(bv)
    print('[Randy] d=bin_str_to_dict(bv):\n    ',d)

    v=dict_to_int16(d)
    print('[Randy] v=dict_to_int16(d):\n    ',v)

    csd=bin_str_to_csd(bv)
    print('[Randy] csd               :\n    ',csd               )
    print('[Randy] dict_to_str(csd)  :\n    ',dict_to_str(csd)  )
    print('[Randy] dict_to_int16(csd):\n    ',dict_to_int16(csd))
    print('[Randy] csd_to_code(csd)  :\n    ',csd_to_code(csd)  )
    print('[Randy] int16_to_code(141):\n    ',int16_to_code(141))

    # test3
    print('\n[Randy] ---- test 3 ----')
    for v in range(-32768,32768):
        csd=int16_to_csd(v)
        v1=dict_to_int16(csd)
        if v%10000==0:
            print('[Randy] %d'%v)
        if v!=v1: 
            print('[ERR] %d,%d'%(v,v1))
            print('[ERR]    ',bv)
            print('[ERR]    ',dict_to_str(csd))

结果:

[Randy] ---- test 1 ----
[Randy] bv:
     1111111101110011
[Randy] d=bin_str_to_dict(bv):
     {0: 1, 1: 1, 2: 0, 3: 0, 4: 1, 5: 1, 6: 1, 7: 0, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1}
[Randy] v=dict_to_int16(d):
     -141
[Randy] csd                :
     {0: -1, 1: 0, 2: 1, 3: 0, 4: -1, 5: 0, 6: 0, 7: -1, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, 15: 0}
[Randy] dict_to_str(csd)   :
     n010n00n00000000
[Randy] dict_to_int16(csd) :
     -141
[Randy] csd_to_code(csd)   :
     -x+(x<<2)-(x<<4)-(x<<7)
[Randy] int16_to_code(-141):
     -x+(x<<2)-(x<<4)-(x<<7)

[Randy] ---- test 2 ----
[Randy] bv:
     0000000010001101
[Randy] d=bin_str_to_dict(bv):
     {0: 1, 1: 0, 2: 1, 3: 1, 4: 0, 5: 0, 6: 0, 7: 1, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, 15: 0}
[Randy] v=dict_to_int16(d):
     141
[Randy] csd               :
     {0: 1, 1: 0, 2: -1, 3: 0, 4: 1, 5: 0, 6: 0, 7: 1, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, 15: 0}
[Randy] dict_to_str(csd)  :
     10n0100100000000
[Randy] dict_to_int16(csd):
     141
[Randy] csd_to_code(csd)  :
     x-(x<<2)+(x<<4)+(x<<7)
[Randy] int16_to_code(141):
     x-(x<<2)+(x<<4)+(x<<7)

[Randy] ---- test 3 ----
[Randy] -30000
[Randy] -20000
[Randy] -10000
[Randy] 0
[Randy] 10000
[Randy] 20000
[Randy] 30000

欢迎关注公众号【三戒纪元】

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值