报错信息文案书写规范¶
规范概要:
第 1 节,报错文案书写模板,属于推荐参考的形式,根据情景不同,如果有简洁并且更易于用户理解的写法,可以灵活使用
第 2 节,必须遵循的规范条目,为必须遵守的报错信息书写规则,前四条已加入 CI 监控
第 3 节,报错信息规范示例库,是从 Paddle 中抽取的一些已有的 PADDLE_ENFORCE,将其改写为合规的示例,便于参考
第 4 节,后续补充的一些辅助性规范
执行说明:
规范在执行过程中,可能会发现现有规范未考虑到的方面,需要在实施过程中不断补充与完善,也请大家积极反馈意见
报错信息规范示例库,示例越丰富,越有参考价值,非常鼓励大家补充新的示例
规范匹配情况较为复杂,可能出现符合规范的写法被匹配为不合规,届时请找 chenwhql approve
1. 报错文案书写模板¶
Paddle 中的报错提示文案均推荐按照以下结构书写,具体包括:
Python 端抛出的各类错误的提示信息
C++端 PADDLE_ENFORCE_*与 PADDLE_THROW 的提示信息
注:文案的关键是要将错误描述清楚,模板仅供参考
1.1 三段式报错文案书写(错误 - 预期 - 建议)¶
1.1.1 第一段:指明错误(必写)¶
直接陈述错误:
推荐的描述:
A 出错,B 没有初始化,C 不存在,D 的值不正确,E 不匹配等
示例:
Mismatched label shape.
不推荐的描述:A 应该怎么样,B 不应该怎么样
出错了,首先直接告诉用户出错即可
除非必要,不建议以应该/不应该的语气指出错误
应该或者不应该如何,属于第二段的阐述期望结果的内容
本段注意事项:
属性变量要指明错误的主体,例如 Op 输入输出要写明是哪个 Op 的输入输出错误,区分前反向 Op
指明错误是告诉用户一个事实,一般不允许出现 magic number(意义不明的数字),用英语句子陈述即可
1.1.2 第二段:期望值与实际值对比(尽可能提供)¶
写明此处期望的输入是什么,而实际的输入是什么
示例:
Expected labels dimension=1. Received 4.
本段注意事项:
将必要信息提供完整,比如 Shape 出错,需要将具体的 Shape 输出进行对比,并指明出错的维度
如果第一段的错误是单值描述,本段可以省略。例如 A 为空指针,B 不存在,没必要在此处写明期望 A 不为空,B 应该存在等
1.1.3 第三段:修改建议(尽可能提供)¶
写明此处的错误可能是由什么导致的,应该如何修改
示例:
Suggested Fix: If your classifier expects one-hot encoding label, check your n_classes argument to the estimatorand/or the shape of your label.Otherwise, check the shape of your label.
本段注意事项:
可以写明修改建议的情况一般适用于一些共性问题,例如
startup_program 没有执行
某个重要参数没有设置
某个环境配置可能有问题
更多具体的示例见第 3 节。
2. 必须遵循的规范条目¶
目前框架内可以使用的报错检查宏包括:
PADDLE_ENFORCE:布尔检查宏
PADDLE_ENFORCE_NOT_NULL:空指针检查宏
PADDLE_ENFORCE_EQ:相等检查宏
PADDLE_ENFORCE_NE:不相等检查宏
PADDLE_ENFORCE_GT:大于检查宏
PADDLE_ENFORCE_GE:大于等于检查宏
PADDLE_ENFORCE_LT:小于检查宏
PADDLE_ENFORCE_LE:小于等于检查宏
PADDLE_THROW:抛出异常宏
PADDLE_ENFORCE_GPU_SUCCESS:GPU API 执行成功检查宏
PADDLE_ENFORCE_*与 PADDLE_THROW 的提示信息书写必须遵循以下条目:
2.1 C++端原则上不允许使用PADDLE_ENFORCE
表达式(CI 测试中有检查)¶
为什么不推荐
PADDLE_ENFORCE
表达式?PADDLE_ENFORCE(COND, ...)
表达式接收的是 bool 型表达式,只能判断 true 和 false,不能给出具体错误提示。PADDLE_ENFORCE_GT(__VAL0, __VAL1, ...)
等表达式接收具体参数,并对应着具体比较条件,易于给出精准的错误信息。如 PADDLE_ENFORCE(A>B)
只能报true
和false
,但改成PADDLE_ENFORCE_GT(A, B)
,会报出 A 和 B 的具体值是什么。
注意:
GPU 内核函数中需要检查报错的话,由于 cuda C 语法限制,仍然需要使用
PADDLE_ENFORCE
,且不需要带有报错类型。GPU 内核函数在
.cu
文件中,但并不是所有.cu
文件中的都是 GPU 内核函数。只有使用__global__
,HOSTDEVICE
等关键字装饰的函数才是。
__global__ void ComputeDifferent(T *centers_diff, const T *X, const T *centers,
const int64_t *ids, const int64_t N,
const int64_t K, const int64_t D) {
int idx = threadIdx.x;
int idy = blockIdx.x + threadIdx.y * GridDimX;
while (idy < K) {
int64_t id = ids[idy];
PADDLE_ENFORCE(id >= 0, "received id:", id);
PADDLE_ENFORCE(id < N, "received id:", id);
T *out = centers_diff + idy * D;
const T *x = X + idy * D;
const T *cent = centers + id * D;
for (int i = idx; i < D; i += BlockDimX) {
out[i] = x[i] - cent[i];
}
idy += BlockDimY * GridDimX;
}
}
2.2 不允许省略或为空字符串(CI 测试中有检查)¶
错误示例:
PADDLE_ENFORCE(ctx->HasInput("X"));
PADDLE_ENFORCE(ctx->HasInput("X"), "");
2.4 必须指明错误类型(CI 测试中有检查)¶
Python 端直接选择合适的 Python 类型错误即可
C++端当前声明了 12 种错误类型(具体见第三节中的详细示例)
InvalidArgument:参数错误
NotFound:目标未找到
OutOfRange:越界错误
AlreadyExists:目标已存在,目标重复
ResourceExhausted:资源耗尽
PreconditionNotMet:前提条件不满足
PermissionDenied:操作不允许
ExecutionTimeout:执行超时
Unimplemented:功能未实现
Unavailable:服务不可用
Fatal:严重错误
External:外部错误,第三方库错误
用法概要: 在整个错误提示字符串(包含变长参数列表)的外面包裹
phi::errors::ErrorType()
简要示例(注意后面括号的位置):
未指明错误类型的:
PADDLE_ENFORCE(true, "example: %s", str);
指明错误类型的:
PADDLE_ENFORCE(true, phi::errors::InvalidArgument("example: %s", str));
正确示例:
PADDLE_ENFORCE_GT(y_dims.size(), y_num_col_dims,
phi::errors::InvalidArgument("The input tensor Y's dimensions of MulOp "
"should be larger than y_num_col_dims. But received Y's "
"dimensions = %d, Y's shape = [%s], y_num_col_dims = %d.",
y_dims.size(), y_dims, y_num_col_dims));
错误示例:
PADDLE_ENFORCE_GT(y_dims.size(), y_num_col_dims,
"The input tensor Y's dimensions of MulOp "
"should be larger than y_num_col_dims. But received Y's "
"dimensions = %d, Y's shape = [%s], y_num_col_dims = %d.",
y_dims.size(), y_dims, y_num_col_dims);
注意:__CUDA_ARCH__下的 PADDLE_ENFORCE 尚不支持声明错误类型,如果遇到,找审核人员 approve 即可
2.5 不允许在提示中使用 C++端开发人员定义的变量缩写,应展开为完整英语单词¶
错误示例:
PADDLE_ENFORCE(forward_pd != nullptr,
"Fail to find eltwise_fwd_pd in device context");
2.6 确保提示不存在语法错误¶
错误示例:
PADDLE_ENFORCE(context->HasInput("X"),
"ArrayToLoDTensorOp must has input X."); //must has?
3. 报错信息规范示例库¶
考虑到开发者对于前述标准理解存在差异,对于错误的归类也可能存在疑惑,所以此处尽可能地提供了各类错误的示例,以及相关提示的参考写法,请开发者在优化报错信息的时候,主动参考此处的规范示例。
3.1 InvalidArgument - 参数有误¶
用户传入了非法的参数,包含各种参数类型错误,应该是最为普遍的错误类型
3.1.1 ShapeError¶
PADDLE_ENFORCE_EQ(
output_shape[unk_dim_idx] * capacity, -in_size,
phi::errors::InvalidArgument(
"The 'shape' attribute in ReshapeOp is invalid. "
"The input tensor X'size must be divisible by known "
"capacity of 'shape'. "
"But received X's shape = [%s], X's size = %d, "
"'shape' is [%s], known "
"capacity of 'shape' is %d.",
in_dims, in_size, framework::make_ddim(shape), capacity));
3.1.2 参数为空(列表为空,空指针等)¶
PADDLE_ENFORCE_NE(vars.empty(), true, phi::errors::InvalidArgument(
"Variable names are empty."));
3.1.3 参数有误,与预期值不相等¶
PADDLE_ENFORCE_GT(batch_size, 0, phi::errors::InvalidArgument(
"Batch size %d is illegal.", batch_size));
PADDLE_ENFORCE_NE(
num, 0,
phi::errors::InvalidArgument(
"The number of ids can not be zero, you need padding "
"it in data generator, or if there is something wrong with "
"the data, please check if the data contains unresolvable "
"characters.\nplease check this error line: %s.",
str));
3.1.4 参数格式错误¶
PADDLE_ENFORCE_NE(in.format(), MKLDNNMemoryFormat::format_undef,
phi::errors::InvalidArgument(
"Input tensor format is invalid. Input tensor should "
"have specified memory format."));
3.1.5 参数未初始化¶
PADDLE_ENFORCE_EQ(proto_->IsInitialized(), true,
phi::errors::InvalidArgument(
"Operator's Proto in op info is not initialized."));
PADDLE_ENFORCE_EQ(
t->IsInitialized(), true,
phi::errors::InvalidArgument(
"The Tensor in the %s Op's Input Variable %s(%s) is "
"not initialized.",
Type(), name, ctx.Inputs(name).at(i)));
3.1.6 参数类型错误¶
PADDLE_ENFORCE(
tmp == *data_type || *data_type == dafault_data_type,
phi::errors::InvalidArgument(
"The DataType of %s Op's duplicable Variable %s must be "
"consistent. The current variable type is (%s), but the "
"previous variable type is (%s).",
Type(), name, DataTypeToString(tmp),
DataTypeToString(*data_type)));
PADDLE_ENFORCE_EQ(
valid, true,
phi::errors::InvalidArgument(
"Tensor holds the wrong type, it holds %s, but desires to be %s.",
DataTypeToString(type_),
DataTypeToString(DataTypeTrait<T>::DataType())));
3.1.7 参数解析错误¶
PADDLE_ENFORCE_EQ(success, true,
phi::errors::InvalidArgument(
"Fail to parse DataFeedDesc from string: %s.",
data_feed_desc_str.c_str()));
3.1.8 LoD 错误¶
PADDLE_ENFORCE_GT(lod_level, 0, phi::errors::InvalidArgument(
"Input(X) Tensor of SequencePoolOp "
"does not contain LoD information."));
3.2 NotFound - 未找到目标¶
申请的实体找不到,要找的变量为空,输入输出不存在等
和空指针区分开,找不到变量和变量没有被正确赋值,是两个层面的概念
3.2.1 Op 输入输出未找到¶
PADDLE_ENFORCE_EQ(
ctx->HasInput("X"), true,
phi::errors::NotFound("Input(X) of MulOp is not found."));
PADDLE_ENFORCE_EQ(
ctx->HasInput("Y"), true,
phi::errors::NotFound("Input(Y) of MulOp is not found."));
PADDLE_ENFORCE_EQ(
ctx->HasOutput("Out"), true,
phi::errors::NotFound("Output(Out) of MulOp is not found."));
3.2.2 缺少节点¶
PADDLE_ENFORCE_NOT_NULL(
p, phi::errors::NotFound("subgraph has no node %s.", name.c_str()));
3.2.3 文件未找到¶
PADDLE_ENFORCE_GT(file_cnt, 0,
phi::errors::NotFound("Input file list is empty."));
3.2.4 其他¶
PADDLE_ENFORCE_NOT_NULL(
var_desc, phi::errors::NotFound("%s is not found.", var_name));
PADDLE_ENFORCE_NOT_NULL(
proto_,
phi::errors::NotFound("Operator's Proto has not been registered."));
3.3 OutOfRange - 越界错误¶
PADDLE_ENFORCE_LT(
i, N, phi::errors::OutOfRange("Array index out of bounds."));
PADDLE_ENFORCE_GT(value, lower_bound_,
phi::errors::OutOfRange("Attribute GreaterThan check failed."));
3.4 AlreadyExists - 目标已存在 / 目标重复¶
查找的实体已存在,或者某些仅允许存在单个实例的个体,却找到了多个
PADDLE_ENFORCE_EQ(
attrs_.count(attr_name), 0,
phi::errors::AlreadyExists(
"The attribute %s has been set in the graph.", attr_name));
PADDLE_ENFORCE_NE(Has(pass_type), true,
phi::errors::AlreadyExists(
"Pass %s has been registered.", pass_type));
PADDLE_ENFORCE_LE(ins.size(), 1UL,
phi::errors::AlreadyExists(
"Operator %s's input %s should contain only one variable.", type_, name));
PADDLE_ENFORCE_EQ(
fused_var_set.count(fused_var_name), 0,
phi::errors::AlreadyExists(
"The fused variable already exists."));
3.5 PermissionDenied - 操作不允许¶
当前操作不允许被执行。
PADDLE_ENFORCE_NE(a, b, phi::errors::PermissionDenied(
"Cannot connect the same node in the graph."));
3.6 ResourceExhausted - 资源耗尽¶
PADDLE_THROW_BAD_ALLOC(phi::errors::ResourceExhausted(
"\n\nOut of memory error on GPU %d. "
"Cannot allocate %s memory on GPU %d, "
"available memory is only %s.\n\n"
"Please check whether there is any other process using GPU %d.\n"
"1. If yes, please stop them, or start PaddlePaddle on another GPU.\n"
"2. If no, please decrease the batch size of your model.\n",
place_.device, string::HumanReadableSize(size), place_.device,
string::HumanReadableSize(avail), place_.device));
PADDLE_THROW_BAD_ALLOC(phi::errors::ResourceExhausted(
"\n\nOut of memory error on GPU %d. "
"Cannot allocate %s memory on GPU %d, "
"available memory is only %s.\n\n"
"Please check whether there is any other process using GPU %d.\n"
"1. If yes, please stop them, or start PaddlePaddle on another GPU.\n"
"2. If no, please try one of the following suggestions:\n"
" 1) Decrease the batch size of your model.\n"
" 2) FLAGS_fraction_of_gpu_memory_to_use is %.2lf now, "
"please set it to a higher value but less than 1.0.\n"
" The command is "
"`export FLAGS_fraction_of_gpu_memory_to_use=xxx`.\n\n",
gpu_id_, string::HumanReadableSize(size), gpu_id_,
string::HumanReadableSize(avail), gpu_id_,
FLAGS_fraction_of_gpu_memory_to_use));
3.7 PreconditionNotMet - 前提条件有误¶
当前执行的操作,需要一定的前提条件满足才能够执行
PADDLE_ENFORCE_NOT_NULL(
mutex_for_pick_file_,
phi::errors::PreconditionNotMet(
"You should call SetFileListMutex before PickOneFile"));
PADDLE_ENFORCE_NOT_NULL(
root_scope_,
phi::errors::PreconditionNotMet(
"root_scope should be set before creating thread scope."));
PADDLE_ENFORCE_NE(
fetched_var_it, fetched_vars->end(),
phi::errors::PreconditionNotMet(
"Cannot find fetched variable(%s). Perhaps the main_program "
"is not set to ParallelExecutor.",
var_name));
PADDLE_ENFORCE_EQ(finish_start_, true,
phi::errors::PreconditionNotMet(
"Datafeed has not started running yet."));
PADDLE_ENFORCE_NE(framework::product(y_dims), 0,
phi::errors::PreconditionNotMet(
"The Input variable Y(%s) has not "
"been initialized. You may need to confirm "
"if you put exe.run(startup_program) "
"after optimizer.minimize function.",
ctx->Inputs("Y").front());
PADDLE_ENFORCE_NE(FLAGS_use_ngraph, true,
phi::errors::PreconditionNotMet(
"Please compile with NGRAPH first to use NGRAPH."));
3.8 ExecutionTimeout - 执行超时¶
执行响应时间过长,或者通信超时。
PADDLE_THROW(
phi::errors::ExecutionTimeout(
"Node communication timeout, self rank is %d pair rank is %d.",
self_rank_,
last_check_rank));
3.9. Unimplemented - 功能尚未实现¶
尚未实现或支持,但之后有可能会实现
PADDLE_ENFORCE_NE(iter, operations_.end(),
phi::errors::Unimplemented(
"Operation %s is not supported yet.", op_type));
PADDLE_ENFORCE_EQ(
all_reduce_ops.size(), grads.size(),
phi::errors::Unimplemented(
"The number of all_reduce OpHandle is not equal to the "
"number of grads. Maybe some gradients are sparse type, "
"it is not supported currently."));
3.11 Fatal - 严重错误¶
未预料到的,严重的错误,例如段错误。
用于后期增加 try-catch 处理非预期的异常,开发者一般不会用到。
PADDLE_THROW(platform::errors::Fatal(
"Custom operator raises an unknown exception in runtime."));
3.12 External - 外部 / 第三方库错误¶
使用 PADDLE_ENFORCE_GPU_SUCCESS 检查宏的时候,会自动封装这个错误类型,一般来说,不需要开发者专门指定这个类型
如果有除 CUDA 之外的类库使用可能会出错的话,也可以使用这个类型
4. 补充规范¶
4.1 PADDLE_ENFORCE_GPU_SUCCESS 为特殊宏,无需再报错类型¶
PADDLE_ENFORCE_GPU_SUCCESS 用于处理 Cuda/Hip 及其相关 lib 的错误,但该类错误环境原因居多,即使要求开发人员写报错信息,也较难起到关键的提示作用,使用 CUDA 官方提供的错误代码及描述是更好的选择,因此我们对该宏进行了升级
使用该宏时,直接在其中写 CUDA 的相关调用即可,不再需要写报错类型与报错信息,例如
PADDLE_ENFORCE_GPU_SUCCESS(platform::dynload::ncclAllReduce(
src_ptr, dst_ptr, src.numel(), nccl_dtype, ncclSum, comm, stream));
4.2 访问 variant 推荐使用 PADDLE_GET 系列宏,并禁止直接使用paddle::get
¶
paddle::get
是一个不安全的第三方库函数,get 失败会直接抛出bad_variant_access
,可能不会有栈信息,也不会有出错文件与行号的提示,导致此类问题很难调试,所以新定义了 PADDLE_GET 系列宏共新增了三个 PADDLE_GET 宏,具体如下:
- PADDLE_GET (e.g. return int& or int*)
- PADDLE_GET_CONST(e.g. return const int& or const int*)
- PADDLE_GET_MUTABLE (e.g. return int or int*)
这三个宏可以满足的 paddle 中对于paddle::get
从 variant 获取值的需求
CI 中对此有规则检查,不允许直接使用
paddle::get
,需要使用新增的 PADDLE_GET 系列宏,如果不想使用 PADDLE_GET 系列宏,可以为paddle::get
加 try-catch,然后找指定人员 approve
4.3 禁止使用 LOG(FATAL)¶
LOG(FATAL)也可以在程序运行中抛出错误,但是抛出的错误难以阅读,无效信息多,且没有出错文件行号等关键信息,paddle 中有 100 余处使用了 LOG(FATAL),这些均替换为 PADDLE_THROW,且在 CI 中禁止 LOG(FATAL)的使用