快捷键

torch.export.dynamic_shapes 源代码

# mypy: 允许未类型化定义
导入 dataclasses
导入 检查
导入 记录日志
导入 系统
from 集合 导入 defaultdict
from 枚举 导入 自动, 枚举
from 打字 导入 任何, 可调用, 可选, 类型检查, 联合

导入 火炬
from torch.utils._pytree 导入 (
    获取节点类型,
    内置类型,
    keystr,
    叶子规范,
    映射键,
    序列键,
    支持的节点,
    树形展平,
    树形图带路径,
)

from .导出程序 导入 导出程序


如果 类型检查:
    from sympy 导入 符号

    from torch._guards 导入 
    from torch.fx.experimental.symbolic_shapes 导入 ShapeEnv, 严格的最小最大约束

全部 = [
    "约束",
    "维度",
    维度,
    从建议的修复中精炼动态形状,
]


日志 = 记录.获取日志记录器(__name__)


 _DimHint(枚举):
    ""
动态形状提示的枚举。
- AUTO 表示自动推断形状(静态或动态)。
- 静态表示静态形状(始终专用)。
- 动态表示动态,如果专用则出错。
""

    自动 = 自动()
    静态 = 自动()
    动态 = 自动()


 _暗(类型):
    ""
Dim 类型的元类。
""

    @staticmethod
    def 可读的(名称, 最小_, 最大_):
        from torch.utils._sympy.numbers 导入 整数_oo

        如果 最小_ == 2:
            最小_ = 
        如果 最大_ == 整数_oo:
            最大_ = 
        如果 最小_ is   最大_ is :
            返回 f"Dim('"{名称}')"
        如果 最小_ is :
            返回 f"Dim('"{名称}', max={最大_})"
        如果 最大_ is :
            返回 f"Dim('"{名称}, 最小值={最小_})"
        返回 f"Dim('"{名称}, 最小值={min_}, max={最大_})"

    def __add__(, 其他):
        例如,dim + 1
        如果 类型(其他) is  int:
            提升 不支持的操作异常(
                f尝试添加{其他}{.__name__},期望一个整数。
                仅支持具有整数系数的线性递增操作。"
            )
        返回 ._derive(lambda x: x + 其他)

    def __radd__(, 其他):
        返回  + 其他

    def __sub__(, 其他):
        # 例如,dim - 1
        如果 类型(其他) is  int:
            提升 不支持的操作异常(
                f"尝试进行减法操作"{其他}来自{.__name__},期望一个整数。
                仅支持具有整数系数的线性递增操作。"
            )
        返回 ._derive(lambda x: x - 其他)

    def __rsub__(, 其他):
        提升 不支持的操作异常(
            f尝试取反{.__name__}. 
            仅支持具有整数系数的线性递增操作。"
        )

    def __mul__(, 其他):
        # 例如,dim * 2
        如果 类型(其他) is  整型 或者 其他 <= 0:
            提升 不支持的操作异常(
                f尝试乘法{其他}{.__name__}预期的是一个正整数,"
                仅支持具有整数系数的线性递增操作。"
            )
        返回 ._derive(lambda x: x * 其他)

    def 矢量乘(, 其他):
        返回  * 其他

    def 导出名称(, 函数):
        from sympy 导入 sympify

        返回 字符串(函数(sympify(.__name__)))

    def _derive(, 函数):
        返回 派生维度(.导出名称(函数), (int,), {"root": , fn: 函数})


 _静态维(_暗):
    ""
元类,用于静态:func:`Dim`类型。

此类仅用于设置和检查静态维度约束,
用户不应与之交互。
"文档"

    @property
    def 最小值(self):
        返回 self.  # 类型:忽略[已定义]

    @property
    def 最大值(self):
        返回 self.  # 类型:忽略[已定义]


 派生维度(_暗):
    ""
派生 :func:`Dim` 类型的元类。

目前我们仅支持具有整数系数的递增线性表达式。
换句话说,派生 Dim 总可以写成 Ax + B 的形式,
其中 x 是一个常规 Dim(即非派生 Dim),A 和 B 是整数,且 A 是正数。
(特别是,后者确保 x < y => Ax + B < Ay + B。)
对派生 Dim 形式的这些限制使得元理论更简单:例如,
它简化了派生维度计算范围,求解基础常规维度,
决定派生维度之间的等式,等等。

lambda x: Ax + B 函数可以用 `fn` 表示,其中 x 是一个正常的 Dim,即 `root`。
派生 Dim 的范围通过将 `fn` 映射到其 `root` 的范围来计算。
"文档"

    @property
    def 最小值(self):
        假设 self.fn 是一个递增函数
        TODO(avik):是否可以使用 sympy 的值范围分析代替?
        from sympy 导入 整数

        from torch.utils._sympy.numbers 导入 整数_oo

        如果 self..最小 is -整数_oo:  # 类型:忽略[已定义]
            返回 -整数_oo  无需 fn 因为递增

        _min_symint = self.函数(整数(self..最小))  # type: ignore[attr-defined]
         = .  # type: ignore[attr-defined]
        断言 _min_symint >= 0, (
            f"期望派生出的最小值应 >= "{self.__name__}为 >= 0. 
            f"请指定一个合适的最小值"{.__name__} "
            f"(目前"{.最小值}).
        )
        返回 int(_min_symint)

    @property
    def 最大值(self):
        假设 self.fn 是一个递增函数
        TODO(avik): 是否可以使用 sympy 的值范围分析?
        from sympy 导入 整数

        from torch.utils._sympy.numbers 导入 整数_oo

        如果 self..最大 is 整数_oo:  # 类型:忽略[已定义]
            返回 整数_oo  fn 不需要,因为它是递增的

        _max_symint = self.函数(整数(self..最大值))  # 类型: 忽略[attr-defined]
         = self.  # 类型:忽略[已定义]
        断言 _max_symint <= 系统模块.最大尺寸 - 1, (
            f预期派生最大值应为{self.__name__}应小于等于{系统模块.最大尺寸 - 1}. 
            f"请指定一个合适的最大值"{.__name__} "
            f"(目前"{.最大值}).
        )
        返回 int(_max_symint)

    def _derive(self, 函数):
        我们支持嵌套,例如 2*dim + 1。
        这通过在同一根上组合操作来实现。
        因此,根始终是常规的 Dim(即不是派生 Dim)。
        返回 派生维度(
            self.导出名称(函数),
            (int,),
            {"root": self., fn: lambda x: 函数(self.函数(x))},  # 类型:忽略[已定义]
        )


[文档]def Dim(name: str, *, min: Optional[int] = None, max: Optional[int] = None): """ func:`Dim` 构建一个类似于命名符号整数的类型,具有范围。 可以用来描述动态张量维度的多个可能值。 注意,同一张量的不同动态维度或不同张量的维度, 可以由相同的类型来描述。 参数: 调试时的人类可读名称。 min(可选[int]):给定符号可能的最小值(包含)。 max(可选[int]):给定符号可能的最大值(包含)。 返回值: 用于张量动态形状规范的类型。 """ 从 torch.utils._sympy.numbers 导入 int_oo _min = 0 if min is None else min `_max = int_oo if max is None else max` `assert _max > _min, f"Cannot create Dim with inconsistent min={min}, max={max}"` `assert name.isidentifier(), f"Dim name must be a valid identifier, got {name}"` `dim = _Dim(name, (int,), {"min": _min, "max": _max})` dim.__module__ = getattr( inspect.getmodule(inspect.stack()[1][0]), "__name__", "__main__" ) return dim
.自动 = _DimHint.自动 # 类型:忽略[已定义] .静态 = _DimHint.静态 # 类型:忽略[已定义] .动态 = _DimHint.动态 # 类型:忽略[已定义]
[文档]def dims( *names: str, min: 可选[int] = None, max: 可选[int] = None ) -> 元组[_Dim, ...]: """ 工具函数用于创建多个 :func:`Dim` 类型。 返回值: 一个包含 :func:`Dim` 类型的元组。 """ return tuple(Dim(name, min=min, max=max) for name in names)
@dataclasses.数据类
_约束目标: "" 这表示输入张量的维度。 "" t_id: 整型 : 整型 @dataclasses.数据类 约束(_约束目标): "" 这代表一个描述约束目标的 Dim。 `name` 是 Dim 的名称。 `constraint_range` 包含 Dim 的最小/最大边界。 "" 名称: 字符串 约束范围: 严格的最小最大约束 def _clone_with_range(self, 小写=0, =): 本地导入 sympy from torch.fx.experimental.symbolic_shapes 导入 严格的最小最大约束 from torch.utils._sympy.numbers 导入 整数_oo from torch.utils._sympy.value_ranges 导入 ValueRanges 如果 is : = 整数_oo 约束范围 = 严格的最小最大约束( vr=self.约束范围.虚拟现实 & ValueRanges(小写=小写, =), 仅警告=错误, ) 返回 约束( self.t_id, self., self.名称, 约束范围, ) def __ge__(self, 小写): 返回 self._clone_with_range(小写=小写) def __gt__(self, 小写): 返回 self._clone_with_range(小写=下限 + 1) def __le__(self, ): 返回 self._clone_with_range(=) def __lt__(self, ): 返回 self._clone_with_range(= - 1) def __bool__(self): # 注意(avik):我们不支持复合表达式如 a <= x <= b。 # 这是因为 Python 会隐式地将它们转换为 bool(a <= x) 和 bool(x <= b), # 此外,还强制要求任何 __bool__ 的重载必须返回 True 或 False。 # FWIW,sympy 在这种情况下也会引发 TypeError。 提升 类型错误( "无法确定_Constraint 的真相值。" "如果您尝试将_Constraint 与逻辑连接词结合," "您可以分别指定它们。" ) @property def 可序列化的规范(self): # 我们需要一个与约束兼容的序列化格式,以便 # 可以在图形模块中保存,而不会破坏模块的序列化。 # 保存的约束将直接用于导出后的处理阶段 将约束转换为运行时断言。保存的约束 不会保存在序列化的模块中。 TODO:需要更好的方法。目前我们使用 't_id' 来映射约束, 这并不可靠。 返回 { t_id: self.t_id, dim: self., min: self.约束范围.vr.小写, 最大: self.约束范围.vr., } @dataclasses.数据类 _幻影根: "" 这代表派生 Dim 的根,其中根不直接 指定任何输入维度的形状,但派生的 Dim 决定了。 例如,输入形状 2*dim 和 dim + 1 通过一个“幻影”维度相关联。 由“name”、“constraint_range”和“val”字段携带的幻影根 帮助为其创建一个符号。具有此幻影根的任何派生维度都是 由这个符号的表达式支持。 "" 名称: 字符串 约束范围: 严格的最小最大约束 val: 整型 @dataclasses.数据类 派生约束(_约束目标): "" 这代表一个派生的 Dim,其根是一个常规约束目标。 (直接指定某些输入维度的形状)或一个虚根 (间接地这样做)。 可以将其视为 `_Constraint` 的子类,除了它不支持 <, <=, >, >= 操作。 不支持 <, <=, >, >= 操作。 "文档" 名称: 字符串 约束范围: "严格最小最大约束" : 并集[_约束目标, _幻影根] 函数: 可调用 @property def 可序列化的规范(self): # 与_Constraint.serializable_spec 相同 返回 { t_id: self.t_id, dim: self., min: self.约束范围.虚拟现实.小写, 最大: self.约束范围.虚拟现实., } @dataclasses.数据类 _RelaxedConstraint(_约束目标): "" 这代表了一个使用 Dim.AUTO/DYNAMIC 标记的暗区(即 mark_dynamic()或 maybe_mark_dynamic()), 这样就为推理留下了关系和最小/最大范围,而不是需要显式指定。 目的是当 produce_guards()找到等式或 一个_RelaxedConstraint 与其他类型_Constraint 之间的关系时,不触发约束违规。 "" @property def 可序列化的规范(self): 返回 { t_id: self.t_id, dim: ., } 约束 = 联合[约束, 派生约束, _RelaxedConstraint] def 处理等式( 约束: 约束, 获取源: 可调用[[int, int] 列表[]], 形状环境: 形状环境, names: 字典[字符串, 元组[int, int]], 源对: 列表[元组[, ]], 导出等价性: 列表[元组[来源, 联合[来源, 符号] 可调用]], 幻影符号: 字典[字符串, 符号] 放松源: 设置[] ): "" 根据给定的输入 `constraint` 更新 `source_pairs`、`derived_equalities` 和 `phantom_symbols`(这些成为 `EqualityConstraint` 的字段)。 基于 `constraint` 输入更新 `EqualityConstraint` 的字段。 "" = 获取源(约束.t_id, 约束.) 如果 不是 : # 由于未使用的形状导致的空源 返回 , *其他源 = sources 当 t.size()[dim]映射到 src0,src1,...,srcN 时,我们添加 使 src0 "等于" src1,...,srcN 的约束。 source_pairs.扩展((, 其他来源) 其他来源 其他来源) 如果 isinstance(约束, 约束): 如果 约束.名称 names: 共享_t_id, 共享维度 = names[约束.名称] 其他来源 = 获取来源(共享_t_id, 共享_dim) 源对.扩展( (, 其他源) 其他源 其他来源 ) else: names[约束.名称] = (约束.t_id, 约束.) 如果...否则 isinstance(约束, 派生约束): # 以 _DerivedConstraint 的根为基础进行分支 如果 不是 isinstance(约束., _幻影根): # 根指向一个输入源 = 获取来源(约束..t_id, 约束..)0] else: # 或根指向一个幻影符号 如果 约束..名称 幻影符号: = 幻影符号[约束..名称] else: # 在形状环境中根据 _PhantomRoot 创建一个幻影符号 = 形状环境.创建符号( val=约束..val, =火炬._dynamo..恒定源(约束..名称), 动态维度=火炬.fx.试验性的.符号形状.动态维度.动态, 约束维度=约束..约束范围, ) 幻影符号[约束..名称] = fn = 约束.fn # 源(source)导出的等价性(source, root, fn)非正式地对应于 source = fn(root)。 # 这里源描述一个输入,根可能描述另一个输入或一个幻影符号。 导出等价性.append((, , 函数)) 如果...否则 isinstance(约束, _RelaxedConstraint): 放松源.添加() def 带路径的树映射( 函数: 可调用[..., 任何] : 任何, *动态形状: 任何, 树名: 可选[字符串] = , ) 翻译 任何: "" 定制的树映射,用于将 pytrees 映射到动态形状。 对于内置类型(例如,标准集合),此行为与 tree_map 完全相同。 反之,对于已注册为 pytree 的用户定义类 C,我们不能假设包含张量的 C 可以映射到包含动态形状的 C(即,C 可能不是 (是一个多态容器)。在这种情况下,我们使用 C 的扁平化形式。 因此,一个可以扁平化为(张量)的 C(**张量)将映射到(动态形状)。 参数: func:应用于每个(int,float,str,bool,None,torch.Tensor)的函数 tree:输入 pytree 动态形状:零个或多个(通常一个)动态形状以匹配 返回: 输出 pytree 映射函数到每个(int,float,str,bool,None,torch.Tensor) "" def 是否为叶子节点(t): # BUILTIN_TYPES 是 SUPPORTED_NODES 的子集,后者包含所有类型 # 在 pytree 中注册的类型,不在 BUILTIN_TYPES 中的类型包括原始类型 # (int, float, str, bool, None, torch.Tensor),这些不在 SUPPORTED_NODES 中 # 以及已注册到 pytree 的用户定义类,它们是。 返回 获取节点类型(t) 不是 内置类型 def f(路径, t, *动态形状): 类型 = 获取节点类型(t) # typ 不在 BUILTIN_TYPES 中 如果 类型 支持的节点: # 因此 typ 是已注册到 pytree 的用户定义类 # 在哪种情况下展开和递归 返回 树形图带路径( f, 支持的节点[typ].展平函数(t)0] *动态形状, 是否为叶子节点=是否为叶子节点, ) else: 返回 函数(路径, t, *动态形状) 尝试: 返回 树形图带路径(f, , *动态形状, 是否为叶子节点=是否为叶子节点) 除了 ValueError e: 如果 不匹配 e.参数[0]: 当 PyTree 在树和 dynamic_shapes 之间发现结构不匹配时, 错误信息不幸地相当糟糕。让我们来修复它。 断言 动态形状, 如果没有 dynamic_shapes,则不能是不匹配 断言 树名, "必须提供 tree_name,当可能存在不匹配时" def _键(类型_, 上下文, i): # 根据类型、上下文和 TreeSpec 的子项推导 PyTree 键 如果 类型 is 字典: 返回 映射键(上下文[i]) 如果 类型 (列表, 元组): 断言 上下文 is 返回 序列键(i) 提升 断言错误(f没有预料到类型{类型_}") def 引发不匹配错误(信息): from torch._dynamo.exc 导入 用户错误, 用户错误类型 提升 用户错误( 用户错误类型.无效输入, f"检测到 `tree_name` 和 `dynamic_shapes` 结构不匹配:"{树名}` 和 `dynamic_shapes`:{信息}", 案件名称=动态形状验证, ) def 比较(, 动态形状, 路径): 在树和动态形状不同的点上引发错误 包括到该点的路径以及差异的原因 渲染路径 = keystr(路径) 如果 isinstance(, 叶子规范): 返回 如果 isinstance(动态形状, 叶子规范): 引发不匹配错误( f``{树名}{渲染路径}` 是一个{.类型}," f"但 `dynamic_shapes"{渲染路径}不是 ) 如果 .类型 != 动态形状.类型: 引发不匹配错误( f``{树名}{渲染路径}` 是一个{.类型}," f"但 `dynamic_shapes"{渲染路径}` 是一个{动态形状.类型}" ) 如果 长度(.儿童规格) != 长度(动态形状.儿童规格): 引发不匹配错误( f``{树名}{渲染路径}` 包含{长度(.儿童规格)}个元素," f"但 `dynamic_shapes"{渲染路径}` 包含{长度(动态形状.儿童规格)}元素 ) 如果 .类型 is 字典: # 上下文,子元素可能顺序颠倒 如果 排序(.上下文) != 排序(动态形状.上下文): 引发不匹配错误( f``{树名}{渲染路径}` 拥有键{.上下文}," f"但 `dynamic_shapes"{渲染路径}` 拥有键{动态形状.上下文}" ) 重映射 = 字典( zip(动态形状.上下文, 动态形状.儿童规格) ) 动态形状子规格 = [重映射[k] k .上下文] else: 动态形状子规格 = 动态形状.儿童规格 i, (树_, 动态形状_) 列举( zip(.儿童规格, 动态形状的儿童规格) ): 比较( 树_, 动态形状_, 路径 + [_键(.类型, .上下文, i)], ) _, 树规范 = 树形展平(, 是否为叶子节点=是否为叶子节点) 其他树 动态形状: _, 其他树规格 = 树形展平(其他树, 是否为叶子节点) 比较(树规格, 其他树规格, [] 提升 def 合并参数(f, 参数, kwargs, _is_torch_jit_trace=错误) 翻译 字典[字符串, 任何] # 按照 f 函数的签名合并 args 和 kwargs,就像发生的那样 # 当 f 函数用*args, **kwargs 调用时,在 f 函数体内部 如果 isinstance(f, 导出程序): f = f.模块() 如果 _is_torch_jit_trace: 签名 = ( 检查.签名(f.前向) 如果 isinstance(f, 火炬.神经网络.模块) 否则 检查.签名(f) ) kwargs = kwargs 如果 kwargs is 不是 否则 {} 返回 签名.绑定(*参数, **kwargs).参数 返回 args
[文档] 形状集合: "" 动态形状构建器。 用于将动态形状规范分配给出现在输入中的张量。 这对于当 `:func:`args` 是嵌套输入结构时尤其有用 更容易索引输入张量,而不是复制 :func:`args` 的结构 动态形状规范。 示例:: args = ({"x": tensor_x, "others": [tensor_y, tensor_z]}) dim = torch.export.Dim(...) dynamic_shapes = torch.export.ShapesCollection() dynamic_shapes[tensor_x] = (dim, dim + 1, 8) dynamic_shapes[tensor_y] = {0: dim * 2} # 这相当于以下(现在自动生成): # dynamic_shapes = {"x": (dim, dim + 1, 8), "others": [{0: dim * 2}, None]} torch.export(..., args, dynamic_shapes=dynamic_shapes) "" def __init__(self): self._shapes = {} def __setitem__(self, t, 形状): 断言 isinstance( t, 火炬.张量 ), f无法将形状分配给非张量类型{类型(t)}" # TODO(avik): 检查形状确实是一个 Shape t_id = id(t) 如果 t_id self._shapes: 形状 = self._shapes[t_id] 断言 ( 形状 == 形状 ), f分配给张量的形状不匹配:期望{形状},获得{形状}" else: self._shapes[id(t)] = 形状 def __getitem__(self, t): t_id = id(t) 如果 t_id 不是 self._shapes: self._shapes[t_id] = {} 返回 self._shapes[t_id] def __len__(self): 返回 长度(self._shapes)
[文档] def dynamic_shapes(self, m, args, kwargs=None): """ 根据函数::func:`args` 和函数::func:`kwargs` 生成函数::func:`dynamic_shapes` 的 pytree 结构。 """ t_ids = set() def find_shape(path, t): t_id = id(t) if t_id in self._shapes: t_ids.add(t_id) return self._shapes[t_id] else: return None combined_args = _combine_args(m, args, kwargs) dynamic_shapes = _tree_map_with_path(find_shape, combined_args) 如果 self._shapes 中的任何 t_id 不在 t_ids 中 raise ValueError( "一些分配了形状的张量在 args 中未找到。" "也许这些张量在传递时被复制了?" "也许这些张量包含在未使用 pytree 注册的类中?" ) 返回动态形状
def _警告 None 动态形状维度(): msg = ( "使用 None 作为动态形状维度已被弃用。" "请使用 Dim.STATIC 代替。" ) # TODO(avik): 未来将引发错误 日志.警告(信息) def _检查动态形状( 合并参数: 字典[字符串, 任何] 动态形状: 联合[字典[字符串, 任何] 元组[任何] 列表[任何] ] ): "" 检查 dynamic_shapes 规范的正确性, 使用组合的 args 和 kwargs 作为输入结构参考。 "" from torch._dynamo.exc 导入 用户错误, 用户错误类型 如果 dynamic_shapes is 或者 长度(动态形状) == 0: 返回 如果 isinstance(动态形状, (元组, 列表)): combined_args = 类型(动态形状)(合并参数.()) # type: ignore[assignment, misc] 边界: 字典[字符串, 元组[int, int]] = {} def check_same_bounds(): 如果 .__name__ 范围: 最小_, 最大_ = 范围[.__name__] 如果 .最小 != 最小_ 或者 .最大 != 最大_: 这_ = _暗.可读的(.__name__, 最小_, 最大_) 那_ = _暗.可读的(.__name__, .最小值, .最大值) 提升 用户错误( 用户错误类型.无效输入, f找到不同的定义{这_}{那_} " f对于相同的符号维度{}, ) else: 范围[.__name__] = (.最小值, .最大值) def 检查符号(路径, 张量, 形状): 如果 isinstance(形状, 字典): i, 维度 形状.项目(): 如果 isinstance(, _暗): check_same_bounds() 如果...否则 维度 is : _警告 None 动态形状维度() 如果...否则 不是 (isinstance(, (int, _DimHint))): 提升 用户错误( 用户错误类型.无效输入, f"意外维度映射到索引"{i}输入张量的形状{形状} " f在 `dynamic_shapes` 中指定{keystr(路径)}"请提供需要翻译的文本,以便我进行翻译。" f"(预期为 None,一个 int,一个 Dim,Dim.AUTO,Dim.STATIC 或 Dim.DYNAMIC," f"但得到了"{}代替), 案件名称=动态形状验证, ) 如果...否则 isinstance(形状, (元组, 列表)): i, 维度 列举(形状): 如果 isinstance(, _暗): check_same_bounds() 如果...否则 维度 is : _警告 None 动态形状维度() 如果...否则 不是 (isinstance(, (int, _DimHint))): 提升 用户错误( 用户错误类型.无效输入, f"意外维度 #"{i}输入张量的形状{形状} " f在 `dynamic_shapes` 中指定{keystr(路径)}"请提供需要翻译的文本,以便我进行翻译。" f"(预期为 None,一个 int,一个 Dim,Dim.AUTO,Dim.STATIC 或 Dim.DYNAMIC," f"但得到了"{}代替), 案件名称=动态形状验证, ) 如果...否则 形状 is 不是 : 提升 用户错误( 用户错误类型.无效输入, f"意外的输入张量形状"{形状}指定在 `dynamic_shapes` 中{keystr(路径)}"请提供需要翻译的文本,以便我进行翻译。" f"(预期输入的是维度列表/元组,或者是一个将索引映射到维度的字典," f"其中每个维度是一个整数、Dim、Dim.AUTO、Dim.STATIC 或 Dim.DYNAMIC)", 案件名称=动态形状验证, ) 断言 isinstance(动态形状, (字典, 元组, 列表)) 如果 isinstance(动态形状, 字典): 获取的键 = 列表(动态形状.()) 预期参数名称 = 列表(合并参数.()) 如果 排序(got_keys) != 排序(预期参数名称): msg = ( f"当 `dynamic_shapes` 被指定为一个字典时,其顶级键必须是参数名称" f"必须是参数名称"{预期参数名称}的 `inputs`,但 " f这里它们是{got_keys}. ) 如果 ( 长度(合并参数) == 1 预期参数名称[0] 不是 获取的键 isinstance(合并参数[预期参数名称[0]], 字典) ): msg += ( "因为在这里 `inputs` 是一个包含单个字典的列表/元组," "也许你只是忘记将 `dynamic_shapes` 包含在一个列表/元组中?" ) else: msg += ( "或者,你也可以完全忽略参数名称 " "并将 `dynamic_shapes` 指定为一个与 `inputs` 匹配的列表/元组。" ) 提升 用户错误( 用户错误类型.无效输入, 信息, 案件名称=动态形状验证 ) def 检查形状(路径, t, 动态形状): 如果 isinstance(t, 火炬.张量): 检查符号(路径, t, 动态形状) else: 如果 动态形状 is 不是 : 渲染路径 = keystr(路径) 提升 用户错误( 用户错误类型.无效输入, f无法关联形状{动态形状}指定在 `dynamic_shapes` 中{渲染路径}"请提供需要翻译的文本,以便我进行翻译。" f"转换为非张量类型"{类型(t)}在 `inputs{渲染路径}(预期为 None), 案件名称=动态形状验证, ) 带路径的树映射(检查形状, 合并参数, 动态形状, 树名=输入) def 处理动态形状( 合并参数: 字典[字符串, 任何] 动态形状: 联合[字典[字符串, 任何] 元组[任何] 列表[任何] ] ) 翻译 列表[约束] "" 读取 dynamic_shapes 规范并生成约束列表。 "" from torch._dynamo.exc 导入 用户错误, 用户错误类型 如果 dynamic_shapes is 或者 长度(动态形状) == 0: 默认情况下我们使用动态,因此无需生成约束 返回 输入文本为空,请提供需要翻译的文本 如果 isinstance(动态形状, (元组, 列表)): combined_args = 类型(动态形状)(合并参数.()) # type: ignore[assignment, misc] 代表输入形状维度的 Dim 名称的映射,以及对其的约束 符号: 字典[字符串, 列表[约束]] = 默认字典(列表) 跟踪不直接代表输入形状维度的根 幻影根: 字典[字符串, _幻影根] = {} 基于幻影根的派生约束: 列表[派生约束] = 输入文本为空,请提供需要翻译的文本 返回列表中的约束 约束: 列表[约束] = 输入文本为空,请提供需要翻译的文本 def 到约束(, 张量, i): 导入 sympy from torch.fx.experimental.symbolic_shapes 导入 严格的最小最大约束 from torch.utils._sympy.solve 导入 try_solve from torch.utils._sympy.value_ranges 导入 ValueRanges def 根值(): # 给定张量.shape[i] 是 dim = fn(root) 的值 # 查找根值 符号 = sympy.符号(..__name__, 整数=) 表达式 = .函数(符号) 解决方案 = 尝试解决(sympy.等式(表达式, 张量.形状[i)] 符号) 如果 解决方案 is 不是 : 返回 int(解决方案[1]) else: 提升 用户错误( # noqa: B904 用户错误类型.约束违反, f预期形状[{i}] = {张量.形状[i]}输入张量的形状应为 " f"形式为"{表达式},其中{符号}是一个整数, ) 如果 isinstance(, 派生维度): 生成一个 _DerivedConstraint,其中根为: - 如果 dim.root 直接描述一个输入形状,则为 _ConstraintTarget - 否则,为 _PhantomRoot dim_root = . # 类型:忽略[已定义] 如果 dim_root.__name__ 符号: # 根代表输入形状维度 根约束 = 符号[dim_root.__name__] [0] = _约束目标( 根约束.t_id, 根约束., ) 如果...否则 dim_root.__name__ 不是 幻影根: 创建一个幽灵根 = _幻影根( # 类型:忽略[赋值] 名称=dim_root.__name__, 约束范围=严格的最小最大约束( vr=ValueRanges(小写=dim_root.最小值, =dim_root.最大值), 仅警告=错误, ), val=根值(), ) 幻影根[dim_root.__name__] = # 类型:忽略[赋值] else: = 幻影根[dim_root.__name__] # 类型:忽略[赋值] 约束 = 派生约束( id(张量), i, .__name__, 严格的最小最大约束( vr=ValueRanges(小写=.最小值, =.最大值), 仅警告=错误, ), , .函数, # 类型:忽略[已定义] ) 如果 isinstance(, _幻影根): # 注意(avik):由于我们尚未处理所有输入,我们可能稍后用代表输入形状维度的根来替换此内容(见下文) # 与之后(稍后)将用代表输入形状维度的根来替换此内容(见下文) 基于幻影根的派生约束.append(约束) 如果...否则 isinstance(, _静态维): 约束 = 约束( # 类型:忽略[赋值] id(张量), i, .__name__, 严格的最小最大约束( 虚拟现实=ValueRanges(小写=., =.), 仅警告= # 类型:忽略[已定义] ), ) else: 断言 isinstance(, _暗) 约束 = 约束( # 类型:忽略[赋值] id(张量), i, .__name__, 严格的最小最大约束( vr=ValueRanges(小写=.最小值, =.最大值), 仅警告= # 类型:忽略[已定义] ), ) 返回 约束 def 更新符号(路径, 张量, 形状): def _创建静态暗维度(张量, i, ): 返回 _静态维(字符串(), (int,), {"值": }) 清除用户端装饰器,或之前的导出调用 # 我们也在非_strict_utils.py/make_constraints() 中删除这些属性 张量._dynamo_weak_dynamic_indices = 设置() 张量._dynamo_dynamic_indices = 设置() 张量._dynamo_dynamic_range = 设置() 张量._dynamo 静态索引 = 设置() 张量._dynamo 未备份索引 = 设置() 如果 isinstance(形状, 字典): i, 维度 形状.项目(): 如果 isinstance(, (int, _暗)): 如果 isinstance(, int): 维度 = _创建静态暗维度(张量, i, ) 约束 = 到约束(, 张量, i) 符号[.__name__].append(约束) 如果...否则 isinstance(, _DimHint): 如果 维度 == _DimHint.自动: 火炬._dynamo.可能标记为动态(张量, i) 如果...否则 维度 == _DimHint.静态: 火炬._dynamo.静态标记(张量, i) 如果...否则 维度 == _DimHint.动态: 火炬._dynamo.标记动态(张量, i) 约束.append(_RelaxedConstraint(id(张量), i)) 如果...否则 维度 is : 火炬._dynamo.静态标记(张量, i) 如果...否则 isinstance(形状, (元组, 列表)): i, 维度 列举(形状): 如果 isinstance(, (int, _暗)): 如果 isinstance(, int): 维度 = _创建静态暗维度(张量, i, ) 约束 = 到约束(, 张量, i) 符号[.__name__].append(约束) 如果...否则 isinstance(, _DimHint): 如果 维度 == _DimHint.自动: 火炬._dynamo.可能标记为动态(张量, i) 如果...否则 维度 == _DimHint.静态: 火炬._dynamo.静态标记(张量, i) 如果...否则 维度 == _DimHint.动态: 火炬._dynamo.标记动态(张量, i) 约束.append(_RelaxedConstraint(id(张量), i)) 如果...否则 维度 is : 火炬._dynamo.静态标记(张量, i) 如果...否则 形状 is : i 范围(张量.()): 火炬._dynamo.静态标记(张量, i) def 关联形状(路径, t, 动态形状): 如果 isinstance(t, 火炬.张量): 更新符号(路径, t, 动态形状) 带路径的树映射(关联形状, 合并参数, 动态形状, 树名=输入) 带有虚根的派生约束 基于幻影根的派生约束: 幻影根名称 = 带有幻影根的导出约束..名称 # 类型:忽略[联合属性] 如果 幻影根名称 符号: # 我们找到了一个与该名称对应的输入形状维度,因此 # 最终不需要为它创建一个幻影符号。 # 注意(avik):总体上我们想要保持这样的不变性,即根节点 # 是幻影符号真的是“幻影”,即它们无法表示 通过任何输入源。当我们决定导出等式时,这很重要。 # 由于我们可以专注于输入源:决定 与幻影符号相关的导出等价性相比之下是微不足道的。 带有幻影根的导出约束. = 符号[幻影根名称] [0] 动态维度 符号.(): 约束.扩展(动态维度) 返回 约束 def _获取维度名称映射( 动态形状: 并集[字典[字符串, 任何] 元组[任何] 列表[任何] ] ): name_to_dim = {} 维度 树形展平( 动态形状, 是否为叶子节点=lambda x: isinstance(x, _暗), )0]: 如果 维度 is : # 备注:此时必须表示非 Tensor 或自动 continue 如果 isinstance(, int): continue 如果...否则 isinstance(, _暗): 名称到维度[.__name__] = 维度 如果 isinstance(, 派生维度): 名称到维度[..__name__] = . # 类型:忽略[已定义] else: 断言 isinstance(, _DimHint) 返回 name_to_dim
[文档]def 从建议的修复中精炼动态形状( 信息: 字符串, 动态形状: 并集[字典[字符串, 任何] 元组[任何] 列表[任何]], ) 翻译 并集[字典[字符串, 任何] 元组[任何] 列表[任何]] "" 当使用 :func:`dynamic_shapes` 导出时,如果规格与从模型跟踪中推断出的约束不匹配,导出可能会失败并出现约束违反错误。错误信息可能会提供建议的修复方案 - 不匹配。错误信息可能会提供建议的修复方案 - 可以对 :func:`dynamic_shapes` 进行哪些更改以成功导出。 示例约束违反错误信息:: 建议的修复方案: dim = Dim('dim', min=3, max=6) # 这只是细化了 dim 的范围 dim = 4 # 这专门对应一个常量 dy = dx + 1 # dy 被指定为一个独立的维度,但实际上与 dx 有这个关系 这是一个辅助函数,它接受 ConstraintViolation 错误信息和原始 :func:`dynamic_shapes` 规范, 并返回一个新的 :func:`dynamic_shapes` 规范,其中包含了建议的修复。 演示用法: 尝试: ep = export(mod, args, dynamic_shapes=dynamic_shapes) except torch._dynamo.exc.UserError as exc: new_shapes = refine_dynamic_shapes_from_suggested_fixes( exc.msg, dynamic_shapes ) ep = export(mod, args, dynamic_shapes=new_shapes) "文档" 导入 正则表达式 导入 sympy from torch._dynamo.exc 导入 用户错误, 用户错误类型 from torch.fx.experimental.symbolic_shapes 导入 _is_supported_equivalence 尝试: 形状修复信息 = 信息.分割("建议的修复:")1].strip() 除了 异常 异常: 提升 用户错误( 用户错误类型.无效输入, "在给定的错误消息中未找到建议的修复方案以用于 refine_dynamic_shapes_from_suggested_fixes()", ) from exc # 构建 shape_fixes 字典 shape_fixes = {} 修复 形状修复消息.分割("输入文本翻译为简体中文为:\n"): 修复 = fix.strip() 如果 匹配 := 正则表达式.匹配(r"(.*) = Dim\('(.*)'.*\)", fix): 名称 = 匹配.群组(1) 最小, 最大 = , 如果 匹配最小值 := 正则表达式.匹配(r".* = Dim\('.*', min\=([0-9]+).*\)", fix): _最小 = int(匹配最小值.群组(1)) 如果 匹配最大值 := 正则表达式.匹配(r".* = Dim\('.*'.*max\=([0-9]+)\)", fix): 最大 = int(匹配最大.群组(1)) 形状修复[名称] = (名称, 最小值=最小, 最大值=最大) else: 名称, 表达式 = fix.分割(等于) 表达式 = sympy.sympify(表达式) 如果 isinstance(表达式, sympy.数字): 静态,整数 形状修复[名称] = int(表达式) else: 关系或派生维度 形状修复[名称] = 表达式 name_to_dim = _获取维度名称映射(动态形状) # 跟踪派生维度根 : 设置[字符串] = 设置() k, c 形状修复.项目(): 断言 isinstance(c, (int, _暗, 派生维度, sympy.表达式)) 如果 isinstance(c, sympy.表达式): 检查维度/派生维度表达式 断言 _是否支持等价(c) 形状修复[k] = c .添加(字符串(下一(迭代(c.自由符号)))) 如果 isinstance(c, 派生维度): .添加(c..__name__) # 类型:忽略[已定义] # 检查键是否为现有维度或新根 k, c 形状修复.项目(): 断言 k name_to_dim 或者 k 避免产生多个派生 dim 对象 派生维度缓存: 字典[字符串, 派生维度] = {} def 应用修复(路径, , 虚拟): 如果 维度 is 或者 isinstance(, int): # not dynamic 返回 维度 如果...否则 .__name__ 形状修复: # directly fix 修复 = 形状修复[.__name__] 如果 isinstance(fix, sympy.表达式): # now derived or related 如果 字符串(fix) 派生维度缓存: 返回 派生维度缓存[字符串(fix)] else: 符号 = 下一(迭代(fix.自由符号)) 尝试定位符号 如果 符号.名称 形状修复: = 形状修复[符号.名称] else: 断言 符号.名称 name_to_dim = 名称到维度[符号.名称] # 确定 fix 的值 模数, 余数 = sympy.多项式.多项式工具.除法(fix, 符号) 维度 = 如果 模数 != 1: 维度 = int(模数) * 维度 如果 余数 != 0: 维度 = 维度 + int(剩余) 派生维度缓存[字符串(fix)] = 维度 返回 维度 else: 返回 修复 如果...否则 isinstance(, 派生维度) ..__name__ 形状修复: # 类型:忽略[已定义] 如果 .__name__ 派生维度缓存: 返回 派生维度缓存[.__name__] else: 基于根评估新派生值 _dim = .函数(形状修复[..__name__]) # 类型:忽略[已定义] 派生维度缓存[.__name__] = _dim 返回 _dim 返回 维度 未更改的维度 返回 带路径的树映射(应用修复, 动态形状, 动态形状)

© 版权所有 PyTorch 贡献者。

使用 Sphinx 构建,并使用 Read the Docs 提供的主题。

文档

查看 PyTorch 的全面开发者文档

查看文档

教程

深入了解初学者和高级开发者的教程

查看教程

资源

查找开发资源,获取您的疑问解答

查看资源