# 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
返回
维度
未更改的维度
返回
带路径的树映射(
应用修复,
动态形状,
动态形状)