代码指南和提示
本文档用于记录 TVM 代码库中供审查者和贡献者参考的提示。其中大多数是通过贡献和流程中的经验教训总结出来的。
C++ 代码风格
使用 Google C/C++ 风格。
公共接口函数以 doxygen 格式记录。
如果类型简短,则倾向于使用具体类型声明而不是
auto
。倾向于通过常量引用传递(例如
const Expr&
)而不是按值传递。除非函数通过复制构造函数或移动来消耗该值,在这种情况下,按值传递比按常量引用传递更好。尽可能倾向于使用
const
成员函数。
我们使用 clang-format
来强制执行代码风格。由于不同版本的 clang-format 可能会因其版本而更改,建议使用与主版本相同的 clang-format 版本。您也可以通过 docker 使用以下命令。
# Run a specific file through clang-format
docker/bash.sh ci_lint clang-format-10 [path-to-file]
# Run all linters, including clang-format
python tests/scripts/ci.py lint
clang-format 也不是完美的,必要时,您可以在某些代码区域禁用 clang-format。
// clang-format off
void Test() {
// clang-format will be disabled in this region.
}
// clang-format on
由于 clang-format 可能无法识别宏,建议像普通函数风格一样使用宏。
#define MACRO_IMPL { custom impl; }
#define MACRO_FUNC(x)
// not preferred, because clang-format might recognize it as types.
virtual void Func1() MACRO_IMPL
// preferred
virtual void Func2() MACRO_IMPL;
void Func3() {
// preferred
MACRO_FUNC(xyz);
}
Python 代码风格
函数和类以 numpydoc 格式记录。
使用
python tests/scripts/ci.py lint
检查您的代码风格坚持使用
python 3.7
中的语言特性对于具有提前返回的函数,对于条件体并行且简短的函数,例如对参数应用简单映射的函数,优先使用
if
/elif
/else
链。对于更过程化的函数,特别是当最终的else
块比if
和elif
块长得多时,优先选择不缩进最终的else
情况。禁用 pylint 检查
no-else-return
以允许这种区分。请参阅 此处 <https://github.com/apache/tvm/pull/11327> 的进一步讨论。# All cases have bodies with similar flow control. While this could # be expressed as a sequence of if conditions, a reader would need to # inspect the body of each condition to know that only one conditional # body may be reached. def sign(x): if x > 0: return "+" elif x < 0: return "-" else: return "" # The initial special case is an early return for a special case, # followed by a more general method. Using an else block for the # condition would add unnecessary indentation for the remainder of the # function. def num_unique_subsets(values): if len(values)==0: return 1 # Longer, more general solution here ...
编写 Python 测试
我们使用 pytest 进行所有 python 测试。tests/python
包含所有测试。
如果您希望您的测试在各种目标上运行,请使用 tvm.testing.parametrize_targets()
装饰器。例如
@tvm.testing.parametrize_targets
def test_mytest(target, dev):
...
将使用 target="llvm"
、target="cuda"
和其他一些目标运行 test_mytest
。 这也确保您的测试由 CI 在正确的硬件上运行。如果您只想针对几个目标进行测试,请使用 @tvm.testing.parametrize_targets("target_1", "target_2")
。 如果您想在单个目标上进行测试,请使用 tvm.testing()
中的关联装饰器。 例如,CUDA 测试使用 @tvm.testing.requires_cuda
装饰器。
网络资源
在 CI 中,从互联网下载文件是导致测试失败不稳定的一个重要来源(例如,远程服务器可能会宕机或速度缓慢),因此请尽量避免在测试期间使用网络。在某些情况下,这并非合理的建议(例如,需要下载模型的文档教程)。
在这些情况下,您可以将文件重新托管在 S3 中,以便在 CI 中快速访问。committer 可以使用 upload_ci_resource.yml GitHub Actions 工作流程 上的 workflow_dispatch
事件上传由名称、哈希和 S3 中的路径指定的文件。sha256 必须与文件匹配,否则将不会上传。上传路径是用户定义的,因此它可以是任何路径(不允许尾部或前导斜杠),但请注意不要意外地与现有资源冲突。上传后,您应该发送 PR 以使用新 URL 更新 request_hook.py 中的 URL_MAP
。
处理整数常量表达式
我们经常需要在 TVM 中处理常量整数表达式。在此之前,我们要问的第一个问题是,是否有必要获取常量整数。如果符号表达式也有效并让逻辑流动,我们应该尽可能多地使用符号表达式。这样生成的代码适用于事先未知的形状。
请注意,在某些情况下,我们无法知道某些信息,例如符号变量的符号,在某些情况下做出假设是可以的。同时为变量是常量的情况添加精确的支持。
如果我们必须获取常量整数表达式,我们应该使用 int64_t
类型而不是 int
获取常量值,以避免潜在的整数溢出。我们始终可以通过 make_const
使用相应的表达式类型重建整数。以下代码给出了一个示例。
Expr CalculateExpr(Expr value) {
int64_t int_value = GetConstInt<int64_t>(value);
int_value = CalculateExprInInt64(int_value);
return make_const(value.type(), int_value);
}