[pytorch]从 torch.* 函数返回命名元组,并为 C++ 运算符提供多个返回参数

2024-03-20 918 views
8

部分修复:https ://github.com/pytorch/pytorch/issues/394

实施细节:

Codegen 被修改为生成如下所示的代码:

static PyObject * THPVariable_svd(PyObject* self_, PyObject* args, PyObject* kwargs)
{
  HANDLE_TH_ERRORS
  static PythonArgParser parser({
    "svd(Tensor input, bool some=True, bool compute_uv=True, *, TensorList[3] out=None)",
  }, /*traceable=*/true);

  ParsedArgs<6> parsed_args;
  auto r = parser.parse(args, kwargs, parsed_args);
  static PyStructSequence_Field fields0[] = {
    {"U", ""}, {"S", ""}, {"V", ""}, {nullptr}
  };
  static PyStructSequence_Desc desc0 = {
    "torch.return_types.svd_out", nullptr,
    fields0, 3
  };
  static PyTypeObject type0;
  static bool namedtuple_type_initialized0 = false;
  if (!namedtuple_type_initialized0) {
    PyStructSequence_InitType(&type0, &desc0);
    namedtuple_type_initialized0 = true;
  }
  static PyStructSequence_Field fields1[] = {
    {"U", ""}, {"S", ""}, {"V", ""}, {nullptr}
  };
  static PyStructSequence_Desc desc1 = {
    "torch.return_types.svd", nullptr,
    fields1, 3
  };
  static PyTypeObject type1;
  static bool namedtuple_type_initialized1 = false;
  if (!namedtuple_type_initialized1) {
    PyStructSequence_InitType(&type1, &desc1);
    namedtuple_type_initialized1 = true;
  }
  if (r.idx == 0) {
    if (r.isNone(3)) {
      return wrap(&type1, dispatch_svd(r.tensor(0), r.toBool(1), r.toBool(2)));
    } else {
      auto results = r.tensorlist_n<3>(3);
      return wrap(&type0, dispatch_svd(r.tensor(0), r.toBool(1), r.toBool(2), results[0], results[1], results[2]));
    }
  }
  Py_RETURN_NONE;
  END_HANDLE_TH_ERRORS
}

类型被定义为THPVariable_${op_name}函数的静态成员,并在第一次调用函数时初始化。

当解析 中的函数原型时native_functions.yaml,解析器将设置指定的名称,就像field_name看到-> (Tensor t1, ...). 这些字段名称将是namedtuple的字段名称。命名元组的类将被命名为torch.return_types.${op_name}

在某些Python 2中,PyStructSequence它不是元组的子类型,因此我们必须创建一些函数来检查对象是否是元组或namedtuple,以解决兼容性问题。

中的运算符native_functions.yaml已更改,仅maxsvd生成为命名元组。为这两个运算符添加了测试,以查看返回值是否按预期工作。这两个操作的文档也已更新,明确提到返回值是一个命名元组。更多操作将在后续 PR 中添加。

Windows 版本的链接器存在一些无法解决的问题PyStructSequence_UnnamedField,并且添加了一些解决方法来处理这种情况。

回答

1

我认为测试失败是真实存在的。

这一更改的后果之一是返回名称是公共 API 的一部分,并且不再可以更改。您添加的名称看起来不错,但让我们认识到这一点。

1

@ezyang 是的,它看起来是真的,我会调查一下。

对于“API”的考虑,是的,我们需要非常小心。但是,采取以下策略是个好主意吗?

如果在文档中明确表示“返回一个namedtuple XXXXX”,我们将其视为公共API,否则,我们仅将其名称视为内部用法,并且可能会更改,恕不另行通知。

由于许多操作都有很多更改,因此该策略允许我们逐步进行更改:

  • 在此 PR 中,我们更新了native_functions.yaml,但不触及文档。这意味着,我们只是添加基础设施来支持namedtuple返回,但尚未更改API。
  • 在未来的 PR 中,我们更改文档并向这些操作的命名元组 API 添加测试以进行单元测试,每个 PR 几个操作。

这种方式将使 PR 的撰写和审查变得更加容易。

6

不幸的是,这不是 BC 的工作原理:) 无论你是否愿意,人们都会依赖于这样的东西。(需要明确的是,我并不是说我们不应该这样做;只是我们应该承诺使用我们提供的名称。)

7

@apaszke 现在已准备好进行审核。我将这些类型的构造移至THPVariable_${op_name}静态成员,因此不应该有任何性能问题。编辑后的 ​​PR 消息中解释了该设计https://github.com/pytorch/pytorch/pull/15429#issue-240086566

@ezyang 我进行了修改,native_functions.yaml以便对于此 PR,仅max返回svdnamedtuple。添加了对它们的测试,并更新了文档以提及namedtuple。

4

Lint 故障是真实存在的

35.40s$ flake8
./torch/onnx/symbolic.py:1058:9: E126 continuation line over-indented for hanging indent
./torch/onnx/symbolic.py:1068:1: E302 expected 2 blank lines, found 1
The command "flake8" exited with 1.

编辑:哦,我认为你所在的主人坏了

8

我们的内部构建通过缓冲区溢出触发了此差异的 ASAN 错误。

==2704==ERROR: AddressSanitizer: global-buffer-overflow on address 0x7f46524f61e0 at pc 0x7f46525f6129 bp 0x7ffcb17e3e60 sp 0x7ffcb17e3610

不幸的是我没有看到更多信息。正在寻找我可以给你的复制品;也许同时重新读取缓冲区溢出补丁?

3

更新:这是溢出时的回溯:

=== How to use this, how to get the raw stack trace, and more: fburl.com/ASAN ===
READ of size 400 at 0x7f46524f61e0 thread T0
SCARINESS: 26 (multi-byte-read-global-buffer-overflow)
     #0 libtools_build_sanitizers_asan-ubsan-py.so+0x89128 __interceptor_memcpy.part.38
     #1 libpython3.6m.so.1.0+0x12a83a            PyStructSequence_InitType2
     #2 buck-out/dev/gen/caffe2/generate-code=python_variable_methods.cpp/python_variable_methods.cpp:3124 torch::autograd::THPVariable_max(_object*, _object*, _object*)
     #3 libpython3.6m.so.1.0+0x10fbcd            _PyCFunction_FastCallDict
     #4 libpython3.6m.so.1.0+0x20f366            call_function
     #5 libpython3.6m.so.1.0+0x21646e            _PyEval_EvalFrameDefault
     #6 libpython3.6m.so.1.0+0x20d708            _PyFunction_FastCall
     #7 libpython3.6m.so.1.0+0x21d546            _PyFunction_FastCallDict
     #8 libpython3.6m.so.1.0+0x7e21d             _PyObject_FastCallDict
     #9 libpython3.6m.so.1.0+0x7e31a             _PyObject_Call_Prepend
    #10 libpython3.6m.so.1.0+0x7ddc9             PyObject_Call
    #11 libpython3.6m.so.1.0+0x1386fa            slot_tp_init
    #12 libpython3.6m.so.1.0+0x1347e6            type_call
9

更多信息:

0x7ff12e0e01e0 is located 0 bytes to the right of global variable 'PyStructSequ
ence_UnnamedField' defined in 'buck-out/dev/gen/caffe2/generate-code=python_var
iable_methods.cpp/python_variable_methods.cpp:54:7' (0x7ff12e0e01d8) of size 8
SUMMARY: AddressSanitizer: global-buffer-overflow (/data/users/ezyang/fbsource/
fbcode/buck-out/dev/gen/pytorch/tensorlist/test#binary,link-tree/libtools_build
_sanitizers_asan-ubsan-py.so+0x89128) in __interceptor_memcpy.part.38
1

@ezyang我手动将大量代码从cpython的structseq.c复制到命名空间下的pytorch testing_namedtuple,请参阅https://github.com/pytorch/pytorch/pull/16143/commits/4479f8885c48b56140e52deec2464c16fa43f39b。您能否导入https://github.com/pytorch/pytorch/pull/16143来看看 ASAN 是否提供了比此 PR 更有用的消息?

这些代码部分从 python 3.6 复制,部分从 python 3.7 复制,以便使其在我的 python 3.7 archlinux 机器上编译。我没有尝试使用 python 3.6,但如果 ASAN 编译不好,请告诉我,我将粘贴 3.6 中的所有内容,我想尝试使其编译。

3

当然可以,我是导入的。我还将在我们这边仔细研究它/看看我们是否可以在开源中重现它。

7
=== How to use this, how to get the raw stack trace, and more: fburl.com/ASAN ===
READ of size 400 at 0x7f46524f61e0 thread T0
SCARINESS: 26 (multi-byte-read-global-buffer-overflow)
     #0 libtools_build_sanitizers_asan-ubsan-py.so+0x89128 __interceptor_memcpy.part.38
     #1 libpython3.6m.so.1.0+0x12a83a            PyStructSequence_InitType2
     #2 buck-out/dev/gen/caffe2/generate-code=python_variable_methods.cpp/python_variable_methods.cpp:3124 torch::autograd::THPVariable_max(_object*, _object*, _object*)
     #3 libpython3.6m.so.1.0+0x10fbcd            _PyCFunction_FastCallDict
     #4 libpython3.6m.so.1.0+0x20f366            call_function
     #5 libpython3.6m.so.1.0+0x21646e            _PyEval_EvalFrameDefault
     #6 libpython3.6m.so.1.0+0x20d708            _PyFunction_FastCall
     #7 libpython3.6m.so.1.0+0x21d546            _PyFunction_FastCallDict
     #8 libpython3.6m.so.1.0+0x7e21d             _PyObject_FastCallDict
     #9 libpython3.6m.so.1.0+0x7e31a             _PyObject_Call_Prepend
    #10 libpython3.6m.so.1.0+0x7ddc9             PyObject_Call
    #11 libpython3.6m.so.1.0+0x1386fa            slot_tp_init
    #12 libpython3.6m.so.1.0+0x1347e6            type_call

我猜似乎是memcpyhttps://github.com/python/cpython/blob/3.6/Objects/structseq.c#L345 ,因为我刚刚打印sizeof(PyTypeObject)正好是 400 。所以地址0x7f46524f61e0应该是 的地址_struct_sequence_template

我认为问题是读取_struct_sequence_template溢出全局缓冲区,尽管我不知道为什么会发生这种情况......

考虑到我必须在https://github.com/pytorch/pytorch/pull/15429/files#diff-a28f5f21f6b1ee0627c8669db92d93a4R44添加解决方法来手动定义PyStructSequence_UnnamedField以清除 MSVC 的链接器错误(我不知道为什么会出现这样的情况)一个错误,我猜这是 CI 系统中使用的链接器或 python 二进制文件的错误),这个问题可能是类似的吗?也就是说,也许 python 二进制文件或链接器有一些错误并且没有为 分配内存_struct_sequence_template

6

为了知道问题是否如我上面所描述的那样,我们可以尝试编写一些非常简单的程序并在相同的链接器、相同的 python 二进制文件上运行 ASAN 检查:

#include <Python.h>

void test() {
  static PyStructSequence_Field fields0[] = {
    {"U", ""}, {"S", ""}, {"V", ""}, {nullptr}
  };
  static PyStructSequence_Desc desc0 = {
    "torch.return_types.svd_out", nullptr,
    fields0, 3
  };
  static PyTypeObject type0;
  static bool namedtuple_type_initialized0 = false;
  if (!namedtuple_type_initialized0) {
    PyStructSequence_InitType(&type0, &desc0);
    namedtuple_type_initialized0 = true;
  }
}

int main() {
  test();
}
8

MSVC平台上的ASAN故障吗?

0x7ff12e0e01e0 is located 0 bytes to the right of global variable 'PyStructSequ
ence_UnnamedField' defined in 'buck-out/dev/gen/caffe2/generate-code=python_var
iable_methods.cpp/python_variable_methods.cpp:54:7' (0x7ff12e0e01d8) of size 8
SUMMARY: AddressSanitizer: global-buffer-overflow (/data/users/ezyang/fbsource/
fbcode/buck-out/dev/gen/pytorch/tensorlist/test#binary,link-tree/libtools_build
_sanitizers_asan-ubsan-py.so+0x89128) in __interceptor_memcpy.part.38

看起来很奇怪,因为python_variable_methods.cpp:54:7https://github.com/pytorch/pytorch/pull/15429/files#diff-a28f5f21f6b1ee0627c8669db92d93a4R54

仅在 MSVC 上启用。

0

是的,当我构建你的示例时,调整为fields0

#include <Python.h>

void test() {
  static PyStructSequence_Field fields0[] = {
    {"U", ""}, {"S", ""}, {"V", ""}, {nullptr}
  };
}

我得到:

stderr: experimental/ezyang/scrap/ext.cpp:6:6: error: ISO C++11 does not allow co
nversion from string literal to 'char *' [-Werror,-Wwritable-strings]
    {"U", ""}, {"S", ""}, {"V", ""}, {nullptr}
2

当我抑制此错误时,我没有遇到 ASAN 故障。所以我猜问题出在其他地方。

8

哦,看看这个:

#ifndef _MSC_VER

该宏是倒置的:)

0

取消反转宏可以解决我们内部构建的问题。但现在我有点担心这个宏在 Windows 上是否真的是正确的做法。

4

重新审查差异,我认为我们应该完全摆脱未命名的字段逻辑。看起来在某个时间点,您在 codegen 中支持 UnnamedField(并且您添加了此 Windows 解决方法来修复 Windows 构建),但此 PR 的最新版本已注释掉该代码,因此我们永远不会生成对 unnamedField 的引用场地。这似乎非常值得怀疑,CI 表明我们从未真正达到过ValueError("Unnamed field is not supported by codegen"). 那么我们为什么不删除该代码(除了错误引发之外)。@zasdfgbnm,你觉得怎么样?

当您这样做时,您可以将field_name注释添加到该字段的定义中吗?对未来的代码阅读者非常有帮助。

7

@ezyang 是的,我也在想同样的事情,让我快速禁用那段代码。

3

@apaszke 上面要求的基准测试完成了吗?当我短暂地看时,我没有看到它们,但可能错过了它们。

0

@gchanan 在 PR 的最新版本中,类型被缓存,所以我认为这引起了 Adam 的基准测试请求。

4

我的猜测是这可能并不重要,但最好进行健全性检查。

1

@ezyang 你想让我按照 @gchanan 的说法进行基准测试吗?或者你会做吗?

6

如果你能做到的话,那将会非常有帮助。

5

@ezyang 我会做的,也许今天晚上。

1

@gchanan @ezyang 此 PR 带来的开销可以忽略不计。

在此之前的提交:1e19fd941f60d6296dacc11568126ab6e4c0619b 结果: 图像

后: 后

8

谢谢您,非常感谢!

8

谢谢!