【问题标题】:Efficient logic to pad tensor填充张量的有效逻辑
【发布时间】:2022-08-19 19:54:43
【问题描述】:

我正在尝试填充某种形状的张量,以使张量使用的总内存始终是 512 的倍数 例如。 SI32 类型的张量形状 16x1x1x4(乘以 4 得到总大小)

The total elements are 16x4x1x1 = 64
Total Memory required 64x**4** = 256 (Not multiple of 512)
Padded shape would be 32x1x1x4 = 512

以下逻辑适用于基本形状,但会与形状中断,例如16x51x1x4 SI32 或者随便说什么80x240x1x1 U8 填充逻辑如下所示

from functools import reduce

DATA_TYPE_MULTIPLYER = 2 # This would change at runtime with different type e.g. 8 with U8 16 with F16 32 with SI32

ALIGNMENT = 512 #Always Constant
CHAR_BIT = 8    # Always Const for given fixed Arch

def approachOne(tensor):
    totalElements = reduce((lambda x, y: x * y), tensor)
    totalMemory = totalElements * DATA_TYPE_MULTIPLYER
    
    divisor = tensor[1] * tensor[2] * tensor[3]
    tempDimToPad = totalElements/divisor
    orgDimToPad = totalElements/divisor
    while (True):
        if ((tempDimToPad * divisor * DATA_TYPE_MULTIPLYER) % ALIGNMENT == 0):
            return int(tempDimToPad - orgDimToPad)
        tempDimToPad = tempDimToPad + 1;
    
def getPadding(tensor):
    totalElements = reduce((lambda x, y: x * y), tensor)
    totalMemory = totalElements * DATA_TYPE_MULTIPLYER
    newSize = totalMemory + (ALIGNMENT - (totalMemory % ALIGNMENT))
    newTotalElements = (newSize * CHAR_BIT) / (CHAR_BIT * DATA_TYPE_MULTIPLYER)
    
    # Any DIM can be padded, using first for now
    paddingValue = tensor[0] 
    padding =  int(((newTotalElements * paddingValue) / totalElements) - paddingValue)
    return padding
    
tensor = [11, 7, 3, 5]
print(getPadding(tensor))
print(approachOne(tensor))

tensorflow 包在这里可能会有所帮助,但我最初是用 C++ 编码的,所以只需在 python 中发布一个最小的工作示例 任何帮助表示赞赏,谢谢

方法一蛮力方法是在任何选定的维度上继续递增 1 并检查 totalMemory 是否是 512 的倍数。蛮力方法有效,但不提供最小填充并且使张量膨胀

更新条件最初的方法是在第一个暗处填充。因为总是填充第一个维度我不是最好的解决方案,只是摆脱这个约束

  • 你需要在你的张量中正好有 128 个整数才能有 512 个字节(假设一个整数有 4 个字节),所以这个想法是用更少的整数填充所有张量到那个数字。但是您给出的非工作案例的示例在张量中已经有超过 128 个整数,因此您无法填充它们以达到该数字。
  • @Schnitte 它需要是 512 的倍数。因此,如果所需的 totalMemory 为 800,则填充应该使 totalMemory 调整为 1024
  • @CMouse 有什么限制?我们可以只填充一个维度或任意数量的维度吗?结果是否总是必须是可能的最小尺寸?

标签: python python-3.x


【解决方案1】:

如果您希望总内存是512 的倍数,那么张量中的元素数必须是512 // DATA_TYPE_MULTIPLIER 的倍数,例如128 在你的情况下。不管那个数字是什么,它都会有一个2**n 形式的素数分解。张量中的元素数量由s[0]*s[1]*...*s[d-1] 给出,其中s 是包含张量形状的序列,d 是一个整数,即维数。乘积s[0]*s[1]*...*s[d-1] 也有一些素因数分解,当且仅当它包含这些素因数时,它是2**n 的倍数。 IE。任务是填充各个维度s[i],使得乘积s[0]*s[1]*...*s[d-1] 的素因数分解包含2**n

如果目标是达到填充张量的最小可能大小,那么可以简单地遍历给定目标元素数量的所有倍数,以找到可以通过填充(增加)张量的各个维度来满足的第一个(1).只要维度至少包含一个未包含在目标倍数大小中的素数,就必须增加维度。在所有维度都增加后,它们的主要因素包含在目标倍数大小中,可以检查候选形状的结果大小:如果它与目标倍数大小匹配,我们就完成了;如果它的素因子是目标多个素因子的严格子集,我们可以将缺失的素因子添加到任何维度(例如第一个);否则,我们可以使用多余的素因子来存储未来(更大)乘数的候选形状。然后,第一个这样的未来乘数标记所有可能乘数的迭代的上限,即算法将终止。但是,如果候选形状(在调整所有尺寸之后)具有过多的素因子 w.r.t.目标倍数大小以及错过一些其他主要因素,唯一的方法是迭代所有可能的填充形状,其大小受目标倍数大小的限制。

下面是一个示例实现:

from collections import Counter
import itertools as it
import math
from typing import Iterator, Sequence


def pad(shape: Sequence[int], target: int) -> tuple[int,...]:
    """Pad the given `shape` such that the total number of elements
       is a multiple of the given `target`.
    """
    size = math.prod(shape)
    if size % target == 0:
        return tuple(shape)

    target_prime_factors = get_prime_factors(target)

    solutions: dict[int, tuple[int,...]] = {}  # maps `target` multipliers to corresponding padded shapes

    for multiplier in it.count(math.ceil(size / target)):

        if multiplier in solutions:
            return solutions[multiplier]

        prime_factors = [*get_prime_factors(multiplier), *target_prime_factors]
        
        def good(x):
            return all(f in prime_factors for f in get_prime_factors(x))

        candidate = list(shape)
        for i, x in enumerate(candidate):
            while not good(x):
                x += 1
            candidate[i] = x

        if math.prod(candidate) == multiplier*target:
            return tuple(candidate)

        candidate_prime_factor_counts = Counter(f for x in candidate for f in get_prime_factors(x))
        target_prime_factor_counts = Counter(prime_factors)

        missing = target_prime_factor_counts - candidate_prime_factor_counts
        excess = candidate_prime_factor_counts - target_prime_factor_counts

        if not excess:
            return (
                candidate[0] * math.prod(k**v for k, v in missing.items()),
                *candidate[1:],
            )
        elif not missing:
            solutions[multiplier * math.prod(k**v for k, v in excess.items())] = tuple(candidate)
        else:
            for padded_shape in generate_all_padded_shapes(shape, bound=multiplier*target):
                padded_size = math.prod(padded_shape)
                if padded_size == multiplier*target:
                    return padded_shape
                elif padded_size % target == 0:
                    solutions[padded_size // target] = padded_shape


def generate_all_padded_shapes(shape: Sequence[int], *, bound: int) -> Iterator[tuple[int,...]]:
    head, *tail = shape
    if bound % head == 0:
        max_value = bound // math.prod(tail)
    else:
        max_value = math.floor(bound / math.prod(tail))
    for x in range(head, max_value+1):
        if tail:
            yield from ((x, *other) for other in generate_all_padded_shapes(tail, bound=math.floor(bound/x)))
        else:
            yield (x,)


def get_prime_factors(n: int) -> list[int]:
    """From: https://stackoverflow.com/a/16996439/3767239
       Replace with your favorite prime factorization method.
    """
    primfac = []
    d = 2
    while d*d <= n:
        while (n % d) == 0:
            primfac.append(d)  # supposing you want multiple factors repeated
            n //= d
        d += 1
    if n > 1:
       primfac.append(n)
    return primfac

这里有一些例子:

pad((16, 1, 1), 128) = (128, 1, 1)
pad((16, 51, 1, 4), 128) = (16, 52, 1, 4)
pad((80, 240, 1, 1), 128) = (80, 240, 1, 1)
pad((3, 5, 7, 11), 128) = (3, 5, 8, 16)
pad((3, 3, 3, 1), 128) = (8, 4, 4, 1)
pad((7, 7, 7, 7), 128) = (7, 8, 8, 8)
pad((9, 9, 9, 9), 128) = (10, 10, 10, 16)

脚注:(1) 实际上,我们需要在整数域上找到x[i] &gt;= 0 的多项式(s[0]+x[0])*(s[1]+x[1])*...*(s[d-1]+x[d-1]) - multiple*target 的根。但是,我不知道有什么算法可以解决这个问题。

【讨论】:

  • 不错的方法,脚注也指出了一个有趣的潜在兔子洞......
  • 谢谢!为了奖励,我又开始了赏金!
猜你喜欢
  • 2013-09-12
  • 1970-01-01
  • 2017-02-02
  • 1970-01-01
  • 2016-07-20
  • 1970-01-01
  • 2019-09-28
  • 2023-03-03
  • 2011-06-20
相关资源
最近更新 更多