Files
paddlepaddle--paddle/test/legacy_test/prim_op_test.py
T
2026-07-13 12:40:42 +08:00

1437 lines
58 KiB
Python

# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import struct
from collections import defaultdict
from functools import partial
import config
import numpy as np
from utils import dygraph_guard, static_guard
import paddle
from paddle.autograd.backward_utils import ValueSet
from paddle.autograd.ir_backward import grad as ir_grad
from paddle.base import Scope, core
from paddle.base.executor import scope_guard
from paddle.base.framework import (
OpProtoHolder,
_dygraph_tracer,
canonicalize_attrs,
datatype_to_vartype,
in_dygraph_mode,
in_pir_mode,
use_pir_api,
)
from paddle.decomposition import decompose
from paddle.incubate.autograd import primapi
from paddle.jit.dy2static.utils import parse_arg_and_kwargs
from paddle.pir.core import vartype_to_datatype
def flatten(nest_list):
out = []
for i in nest_list:
if isinstance(i, (list, tuple)):
tmp_list = flatten(i)
for j in tmp_list:
out.append(j)
else:
out.append(i)
return out
def _as_list(x):
if x is None:
return []
return list(x) if isinstance(x, (list, tuple)) else [x]
def convert_uint16_to_float(in_list):
in_list = np.asarray(in_list)
out = np.vectorize(
lambda x: struct.unpack(
'<f', struct.pack('<I', np.uint32(x) << np.uint32(16))
)[0],
otypes=[np.float32],
)(in_list.flat)
return np.reshape(out, in_list.shape)
def patch_for_one_hot(inputs, attrs, args):
if 'depth_tensor' in inputs.keys():
args[1] = inputs['depth_tensor'].item()
else:
args[1] = attrs['depth']
return args
# TODO(wanghao107): OpTestUtils will be moved to op_test.py
class OpTestUtils:
@classmethod
def _get_kernel_signature(
cls, op_type, eager_tensor_inputs, eager_tensor_outputs, attrs_outputs
):
try:
op_proto = OpProtoHolder.instance().get_op_proto(op_type)
canonicalized_attrs = canonicalize_attrs(attrs_outputs, op_proto)
except ValueError:
canonicalized_attrs = attrs_outputs
try:
kernel_sig = _dygraph_tracer()._get_kernel_signature(
op_type,
eager_tensor_inputs,
eager_tensor_outputs,
canonicalized_attrs,
)
except RuntimeError as re:
"""we think the kernel_sig is missing."""
kernel_sig = None
print(
f"[Warning: op_test.py] Kernel Signature is not found for {op_type}, fall back to intermediate state."
)
return kernel_sig
@classmethod
def prepare_python_api_arguments(
cls, api, op_proto_ins, op_proto_attrs, kernel_sig, target_dtype=None
):
"""map from `op proto inputs and attrs` to `api input list and api attrs dict`
NOTE: the op_proto_attrs and op_proto_ins is a default dict. default value is []
"""
class Empty:
pass
def is_empty(a):
return isinstance(a, Empty)
def get_default(idx, defaults):
assert not isinstance(defaults[idx], Empty), (
f"{idx}-th params of python api don't have default value."
)
return defaults[idx]
def to_defaults_list(params, defaults):
return [defaults[p] for p in params if p in defaults]
def parse_attri_value(name, op_inputs, op_proto_attrs):
"""parse true value from inputs and attrs, if there is no name passed by OpTest, return Empty
1. if the name in op_attrs, use the op_attrs[name]
2. if the name in op_inputs, convert the op_inputs to [type of default value]
3. if the name not in op_attrs ans op_inputs, return Empty. (this will use the default value from python api)
"""
if name in op_proto_attrs:
return op_proto_attrs[name]
elif name in op_inputs:
if len(op_inputs[name]) == 1:
# why don't use numpy().item() : if the Tensor is float64, we will change it to python.float32, where we loss accuracy: [allclose_op]
# why we reconstruct a tensor: because we want the tensor in cpu.
if in_dygraph_mode():
return paddle.to_tensor(
op_inputs[name][0].numpy(), place='cpu'
)
else:
return op_inputs[name][0]
else:
# if this is a list (test_unsqueeze2_op): we just pass it into the python api.
return op_inputs[name]
else:
return Empty()
def convert_dtype(dtype, target_dtype):
if target_dtype is None:
return dtype
if (
isinstance(dtype, core.VarDesc.VarType)
and target_dtype is paddle.pir.core.DataType
):
return vartype_to_datatype[dtype]
if (
isinstance(dtype, paddle.pir.core.DataType)
and target_dtype is core.VarDesc.VarType
):
return datatype_to_vartype[dtype]
return dtype
# NOTE(xiongkun): the logic of constructing parameters:
# for example:
# python api: cumprod(x, dim, dtype=None, name=None)
# kernel sig: [["x"], ["dim"], ["out"]]"
#
# we will construct a lot of list with the same length : len == len(api_params), here is 4
# api_params = ["x", "dim", "dtype", "name"]
# api_defaults = [Empty, Empty, None, None]; empty means no defaults.
# inputs_and_attrs = ["x", "dim"] , the length may shorter or longer than api_params
# input_arguments = [RealValue in self.inputs and self.attrs]
# then ,we will loop for the api_params, construct a result list:
# if the name in ['name', 'dtype', 'out', 'output'], we will use the default value
# else, we will consume a input_arguments. (because the name is not corresponding, so we only use the order)
api_params, api_defaults = parse_arg_and_kwargs(api)
api_defaults = to_defaults_list(api_params, api_defaults)
api_defaults = [
Empty() for i in range(len(api_params) - len(api_defaults))
] + api_defaults
# patch for one hot -> fill the api params
if "one_hot" in str(api):
api_defaults = [None for x in range(len(api_params))]
assert len(api_defaults) == len(api_params), (
"Error happens. contact xiongkun03 to solve."
)
inputs_sig, attrs_sig, outputs_sig = kernel_sig
inputs_and_attrs = inputs_sig + attrs_sig
input_arguments = [
op_proto_ins.get(name, Empty()) for name in inputs_sig
] + [
parse_attri_value(name, op_proto_ins, op_proto_attrs)
for name in attrs_sig
]
results = []
# hack support variable length parameter(such as paddle.meshgrid(*args,**kwargs)
if api_params == []:
results.append(input_arguments)
return results
api_ignore_param_list = {'name', 'dtype', 'out', 'output'}
idx_of_op_proto_arguments = 0
for idx, arg_name in enumerate(api_params):
if arg_name in api_ignore_param_list:
to_append = (
get_default(idx, api_defaults)
if arg_name not in op_proto_attrs
else op_proto_attrs[arg_name]
)
results.append(to_append)
if idx_of_op_proto_arguments < len(input_arguments):
idx_of_op_proto_arguments += 1
else:
if idx_of_op_proto_arguments < len(input_arguments):
tmp = input_arguments[idx_of_op_proto_arguments]
idx_of_op_proto_arguments += 1
else:
# tmp = Empty() # use the default value
tmp = parse_attri_value(
arg_name, op_proto_ins, op_proto_attrs
)
if isinstance(tmp, Empty):
results.append(get_default(idx, api_defaults))
else:
results.append(tmp)
assert len(results) == len(api_params)
results = paddle.utils.map_structure(
partial(convert_dtype, target_dtype=target_dtype), results
)
return results
@classmethod
def assumption_assert_and_transform(cls, args, inp_num):
"""
transform inputs by the following rules:
Note: it may not be possible to distinguish list with one Tensor,you should use wrapper to distinguish.
1. [Tensor] -> Tensor
2. [Tensor, Tensor, ...] -> list of Tensors
3. None -> None
4. Others: raise Error
only support "X" is list of Tensor, currently don't support other structure like dict.
"""
inp_args = [
[inp] if inp is None else inp for inp in args[:inp_num]
] # convert None -> [None]
for inp in inp_args:
assert isinstance(inp, list), (
"currently only support `X` is [Tensor], don't support other structure."
)
args = [inp[0] if len(inp) == 1 else inp for inp in inp_args] + args[
inp_num:
]
return args
@classmethod
def is_bfloat16_type(cls, np_type):
if np_type == np.dtype('uint16'):
return True
return False
def apply_to_static(net, use_cinn):
if not paddle.framework.use_pir_api():
build_strategy = paddle.static.BuildStrategy()
build_strategy.build_cinn_pass = use_cinn
return paddle.jit.to_static(
net, build_strategy=build_strategy, full_graph=True
)
backend = "CINN" if use_cinn else None
return paddle.jit.to_static(net, backend=backend, full_graph=True)
class PrimNet(paddle.nn.Layer):
def __init__(self, public_python_api):
super().__init__()
self.public_python_api = public_python_api
def forward(self, args):
out = self.public_python_api(*args)
return out
class PrimForwardChecker:
def __init__(self, op_test, place):
self.checker_name = "PrimForwardChecker"
self.place = place
self.op_test = op_test
self.init()
self.init_checker()
def init(self):
pass
def init_checker(self):
assert hasattr(self.op_test, 'prim_op_type'), (
"If you want to test comp op, please set prim_op_type with 'prim' or 'comp' in setUp function."
)
assert self.op_test.prim_op_type in [
"comp",
"prim",
], "prim_op_type must be comp or prim in setUp function."
assert hasattr(self.op_test, 'dtype'), (
"Please set dtype in setUp function."
)
self.op_type = self.op_test.op_type
self.prim_op_type = self.op_test.prim_op_type
assert hasattr(self.op_test, 'public_python_api'), (
"If you want to check prim, please set public_python_api in setUp function."
)
self.public_python_api = self.op_test.public_python_api
self.dtype = np.dtype(self.op_test.dtype)
self.inputs = self.op_test.inputs
self.attrs = (
self.op_test.attrs if hasattr(self.op_test, 'attrs') else {}
)
self.outputs = self.op_test.outputs
self.init_checker_threshold()
self.enable_fw_comp = (
self.op_test.enable_fw_comp
if hasattr(self.op_test, 'enable_fw_comp')
else True
)
self.enable_rev_comp = (
self.op_test.enable_rev_comp
if hasattr(self.op_test, 'enable_rev_comp')
else True
)
self.enable_cinn = (
self.op_test.enable_cinn
if hasattr(self.op_test, 'enable_cinn')
else True
)
if os.getenv('FLAGS_enable_cinn'):
self.enable_cinn = True
self.enable_check_eager_comp = (
self.op_test.enable_check_eager_comp
if hasattr(self.op_test, 'enable_check_eager_comp')
else True
)
self.enable_check_static_comp = (
self.op_test.enable_check_static_comp
if hasattr(self.op_test, 'enable_check_static_comp')
else True
)
self.enable_check_jit_comp = (
self.op_test.enable_check_jit_comp
if hasattr(self.op_test, 'enable_check_jit_comp')
else True
)
self.enable_check_jit_comp_with_cinn = (
self.op_test.enable_check_jit_comp_with_cinn
if hasattr(self.op_test, 'enable_check_jit_comp_with_cinn')
else True
)
self.kernel_sig = self.get_kernel_sig()
def init_checker_threshold(self):
if hasattr(self.op_test, 'jit_comp_rtol'):
self.jit_comp_rtol = self.op_test.jit_comp_rtol
else:
self.jit_comp_rtol = (
config.TOLERANCE[self.dtype]['jit_comp']['rtol']
if self.dtype in config.TOLERANCE
else 0
)
if hasattr(self.op_test, 'jit_comp_atol'):
self.jit_comp_atol = self.op_test.jit_comp_atol
else:
self.jit_comp_atol = (
config.TOLERANCE[self.dtype]['jit_comp']['atol']
if self.dtype in config.TOLERANCE
else 0
)
if hasattr(self.op_test, 'fw_comp_rtol'):
self.fw_comp_rtol = self.op_test.fw_comp_rtol
else:
self.fw_comp_rtol = (
config.TOLERANCE[self.dtype]['fw_comp']['rtol']
if self.dtype in config.TOLERANCE
else 0
)
if hasattr(self.op_test, 'fw_comp_atol'):
self.fw_comp_atol = self.op_test.fw_comp_atol
else:
self.fw_comp_atol = (
config.TOLERANCE[self.dtype]['fw_comp']['atol']
if self.dtype in config.TOLERANCE
else 0
)
if hasattr(self.op_test, 'rev_comp_rtol'):
self.rev_comp_rtol = self.op_test.rev_comp_rtol
else:
self.rev_comp_rtol = (
config.TOLERANCE[self.dtype]['rev_comp']['rtol']
if self.dtype in config.TOLERANCE
else 0
)
if hasattr(self.op_test, 'rev_comp_atol'):
self.rev_comp_atol = self.op_test.rev_comp_atol
else:
self.rev_comp_atol = (
config.TOLERANCE[self.dtype]['rev_comp']['atol']
if self.dtype in config.TOLERANCE
else 0
)
if hasattr(self.op_test, 'cinn_rtol'):
self.cinn_rtol = self.op_test.cinn_rtol
else:
self.cinn_rtol = (
config.TOLERANCE[self.dtype]['cinn']['rtol']
if self.dtype in config.TOLERANCE
else 0
)
if hasattr(self.op_test, 'cinn_atol'):
self.cinn_atol = self.op_test.cinn_atol
else:
self.cinn_atol = (
config.TOLERANCE[self.dtype]['cinn']['atol']
if self.dtype in config.TOLERANCE
else 0
)
def check(self):
if (
type(self.place) is paddle.base.libpaddle.CUDAPlace
and not paddle.is_compiled_with_cuda()
):
return
self.eager_desire = self.get_eager_desire()
if not in_pir_mode():
if self.enable_check_static_comp:
self.check_static_comp()
if self.enable_check_jit_comp:
self.check_jit_comp()
if self.enable_check_jit_comp_with_cinn:
self.check_jit_comp_with_cinn()
else:
if self.enable_check_static_comp:
with scope_guard(Scope()):
self.check_static_comp()
if self.enable_check_jit_comp:
with scope_guard(Scope()):
self.check_jit_comp()
def get_kernel_sig(self):
with dygraph_guard():
if type(self.place) is paddle.base.libpaddle.CPUPlace:
paddle.device.set_device("cpu")
if type(self.place) is paddle.base.libpaddle.CUDAPlace:
paddle.device.set_device("gpu:0")
(
eager_tensor_inputs,
attrs_outputs,
_,
) = self.get_eager_input_attr_and_inputdict(stop_gradient=True)
eager_tensor_outputs = self.get_eager_empty_output(
stop_gradient=True
)
kernel_sig = OpTestUtils._get_kernel_signature(
self.op_type,
eager_tensor_inputs,
eager_tensor_outputs,
attrs_outputs,
)
return kernel_sig
def get_eager_desire(self):
with dygraph_guard():
if type(self.place) is paddle.base.libpaddle.CPUPlace:
paddle.device.set_device("cpu")
if type(self.place) is paddle.base.libpaddle.CUDAPlace:
paddle.device.set_device("gpu:0")
(
eager_tensor_inputs,
attrs_outputs,
_,
) = self.get_eager_input_attr_and_inputdict(stop_gradient=True)
args = OpTestUtils.prepare_python_api_arguments(
self.public_python_api,
eager_tensor_inputs,
attrs_outputs,
self.kernel_sig,
target_dtype=paddle.core.VarDesc.VarType,
)
if "one_hot" in self.op_type:
args = patch_for_one_hot(self.inputs, self.attrs, args)
inputs_sig, _, _ = self.kernel_sig
args = OpTestUtils.assumption_assert_and_transform(
args, len(inputs_sig)
)
ret = flatten(_as_list(self.public_python_api(*args)))
ret = paddle.utils.map_structure(lambda x: x.numpy(), ret)
if OpTestUtils.is_bfloat16_type(self.dtype):
ret = paddle.utils.map_structure(
lambda x: convert_uint16_to_float(x), ret
)
return ret
def get_eager_input_attr_and_inputdict(self, stop_gradient):
attrs_outputs = {}
for attrs_name in self.attrs:
if self.attrs[attrs_name] is not None:
attrs_outputs[attrs_name] = self.attrs[attrs_name]
input_dict = {}
eager_inputs = defaultdict(list)
for name, item in self.inputs.items():
if isinstance(item, list):
for tup in item:
dtype = (
"bfloat16"
if OpTestUtils.is_bfloat16_type(tup[1].dtype)
else tup[1].dtype
)
x = paddle.to_tensor(
data=tup[1],
place=self.place,
stop_gradient=stop_gradient,
dtype=dtype,
)
eager_inputs[name].append(x)
input_dict.update({str(tup[0]): x})
else:
dtype = (
"bfloat16"
if OpTestUtils.is_bfloat16_type(item.dtype)
else item.dtype
)
x = paddle.to_tensor(
data=item,
place=self.place,
stop_gradient=stop_gradient,
dtype=dtype,
)
eager_inputs[name].append(x)
input_dict.update({name: x})
return eager_inputs, attrs_outputs, input_dict
def get_eager_empty_output(self, stop_gradient):
eager_outputs = defaultdict(list)
for name, item in self.outputs.items():
if isinstance(item, list):
for tup in item:
dtype = (
"bfloat16"
if OpTestUtils.is_bfloat16_type(tup[1].dtype)
else tup[1].dtype
)
x = paddle.to_tensor(
data=[],
place=self.place,
stop_gradient=stop_gradient,
dtype=dtype,
)
eager_outputs[name].append(x)
else:
dtype = (
"bfloat16"
if OpTestUtils.is_bfloat16_type(item.dtype)
else item.dtype
)
x = paddle.to_tensor(
data=[],
place=self.place,
stop_gradient=stop_gradient,
dtype=dtype,
)
eager_outputs[name].append(x)
return eager_outputs
def get_static_input_attr_inputdict_and_feed(self, stop_gradient):
attrs_outputs = {}
for attrs_name in self.attrs:
if self.attrs[attrs_name] is not None:
attrs_outputs[attrs_name] = self.attrs[attrs_name]
input_dict = {}
static_inputs = defaultdict(list)
feed = {}
for name, item in self.inputs.items():
if isinstance(item, list):
for tup in item:
dtype = (
"bfloat16"
if OpTestUtils.is_bfloat16_type(tup[1].dtype)
else tup[1].dtype
)
x = paddle.static.data(
name=str(tup[0]), shape=tup[1].shape, dtype=dtype
)
x.stop_gradient = stop_gradient
static_inputs[name].append(x)
feed.update({str(tup[0]): tup[1]})
input_dict.update({str(tup[0]): x})
else:
dtype = (
"bfloat16"
if OpTestUtils.is_bfloat16_type(item.dtype)
else item.dtype
)
x = paddle.static.data(name=name, shape=item.shape, dtype=dtype)
x.stop_gradient = stop_gradient
static_inputs[name].append(x)
feed.update({name: item})
input_dict.update({name: x})
return static_inputs, attrs_outputs, input_dict, feed
def check_eager_comp(self):
pass
def check_static_comp(self):
# forward comp only for comp op
if self.prim_op_type == "prim":
return
with static_guard():
core._set_prim_forward_enabled(self.enable_fw_comp)
startup_program, main_program = (
paddle.static.Program(),
paddle.static.Program(),
)
with paddle.static.program_guard(main_program, startup_program):
(
static_inputs,
attrs,
input_dict,
feed,
) = self.get_static_input_attr_inputdict_and_feed(
stop_gradient=True
)
args = OpTestUtils.prepare_python_api_arguments(
self.public_python_api,
static_inputs,
attrs,
self.kernel_sig,
target_dtype=(
paddle.pir.core.DataType
if in_pir_mode()
else paddle.core.VarDesc.VarType
),
)
if "one_hot" in self.op_type:
args = patch_for_one_hot(self.inputs, self.attrs, args)
inputs_sig, _, _ = self.kernel_sig
args = OpTestUtils.assumption_assert_and_transform(
args, len(inputs_sig)
)
ret = flatten(_as_list(self.public_python_api(*args)))
if not in_pir_mode():
primapi.to_prim(main_program.blocks)
else:
before_ops = [
op.name() for op in main_program.global_block().ops
]
ret = decompose(main_program, ret)
after_ops = [
op.name() for op in main_program.global_block().ops
]
assert before_ops != after_ops, (
f"For {after_ops} , since op which has been decomposed should not exist, the op list should differ from origin ones."
)
# ensure the operator not in program if check_prim is True
if not in_pir_mode():
forward_ops = [op.type for op in main_program.blocks[0].ops]
assert self.op_type not in forward_ops, (
f"{self.op_type} shouldn't appear in program when check_prim is True"
)
exe = paddle.static.Executor(self.place)
exe.run(startup_program)
ret = exe.run(main_program, feed=feed, fetch_list=ret)
if OpTestUtils.is_bfloat16_type(self.dtype):
ret = paddle.utils.map_structure(
lambda x: convert_uint16_to_float(x), ret
)
# check static forward
if len(ret) != len(self.eager_desire):
msg = (
f"The static comp forward api out tensor nums is different with eager forward api out tensor nums on {self.place}."
f'when enable_fw_comp is {self.enable_fw_comp}, static comp forward api out tensor nums = {len(ret)}, eager forward api out tensor nums = {len(self.eager_desire)}. \n'
)
raise RuntimeError(msg)
for i in range(len(ret)):
np.testing.assert_allclose(
ret[i],
self.eager_desire[i],
rtol=self.fw_comp_rtol,
atol=self.fw_comp_atol,
err_msg=(
'Check static comp forward api out failed. Mismatch between static comp '
f'and eager on {self.place!s}, when enable_fw_comp is {self.enable_fw_comp},'
f'the forward api out tensor\'s index is : {i} \n'
f'static comp forward api out tensor:\n{ret[i]}\n '
f'eager forward api out tensor:\n{self.eager_desire[i]}\n'
),
)
with dygraph_guard():
core._set_prim_forward_enabled(False)
def check_jit_comp(self):
if self.prim_op_type == "prim":
return
with dygraph_guard():
if type(self.place) == paddle.base.libpaddle.CPUPlace:
paddle.device.set_device("cpu")
if type(self.place) == paddle.base.libpaddle.CUDAPlace:
paddle.device.set_device("gpu:0")
atol = (
self.fw_comp_atol if self.enable_fw_comp else self.jit_comp_atol
)
rtol = (
self.fw_comp_rtol if self.enable_fw_comp else self.jit_comp_rtol
)
core._set_prim_forward_enabled(self.enable_fw_comp)
(
eager_tensor_inputs,
attrs_outputs,
_,
) = self.get_eager_input_attr_and_inputdict(stop_gradient=True)
args = OpTestUtils.prepare_python_api_arguments(
self.public_python_api,
eager_tensor_inputs,
attrs_outputs,
self.kernel_sig,
target_dtype=(
paddle.pir.core.DataType
if use_pir_api()
else paddle.core.VarDesc.VarType
),
)
if "one_hot" in self.op_type:
args = patch_for_one_hot(self.inputs, self.attrs, args)
inputs_sig, _, _ = self.kernel_sig
args = OpTestUtils.assumption_assert_and_transform(
args, len(inputs_sig)
)
net = PrimNet(self.public_python_api)
net = apply_to_static(net, False)
# ensure the operator not in program if check_prim is True
if not use_pir_api():
forward_ops = [
op.type
for op in net.forward.get_concrete_program(args)[1]
.forward_program.block(0)
.ops
]
assert self.op_type not in forward_ops, (
f"{self.op_type} shouldn't appear in program when check_prim is True"
)
ret = flatten(_as_list(net(args)))
ret = paddle.utils.map_structure(lambda x: x.numpy(), ret)
if OpTestUtils.is_bfloat16_type(self.dtype):
ret = paddle.utils.map_structure(
lambda x: convert_uint16_to_float(x), ret
)
# check jit comp forward
if len(ret) != len(self.eager_desire):
msg = (
f"The jit comp forward api out tensor nums is different with eager forward api out tensor nums on {self.place}."
f'when enable_fw_comp is {self.enable_fw_comp}, jit comp forward api out tensor nums = {len(ret)}, eager forward api out tensor nums = {len(self.eager_desire)}. \n'
)
raise RuntimeError(msg)
for i in range(len(ret)):
np.testing.assert_allclose(
ret[i],
self.eager_desire[i],
rtol=rtol,
atol=atol,
err_msg=(
'Check jit comp forward api out failed. Mismatch between jit comp '
f'and eager on {self.place!s}, when enable_fw_comp is {self.enable_fw_comp},'
f'the forward api out tensor\'s index is : {i} \n'
f'jit comp forward api out tensor:\n{ret[i]}\n '
f'eager forward api out tensor:\n{self.eager_desire[i]}\n'
),
)
core._set_prim_forward_enabled(False)
net.forward.program_cache.clear()
def check_jit_comp_with_cinn(self):
if self.prim_op_type == "prim":
return
# cinn doesn't support cpu place
if (
type(self.place) == paddle.base.libpaddle.CPUPlace
and self.enable_cinn
and core.is_compiled_with_cinn()
):
return
with dygraph_guard():
atol = (
self.cinn_atol
if self.enable_cinn and core.is_compiled_with_cinn()
else self.fw_comp_atol
)
rtol = (
self.cinn_rtol
if self.enable_cinn and core.is_compiled_with_cinn()
else self.fw_comp_rtol
)
core._set_prim_forward_enabled(self.enable_fw_comp)
if type(self.place) is paddle.base.libpaddle.CPUPlace:
paddle.device.set_device("cpu")
if type(self.place) is paddle.base.libpaddle.CUDAPlace:
paddle.device.set_device("gpu:0")
(
eager_tensor_inputs,
attrs_outputs,
_,
) = self.get_eager_input_attr_and_inputdict(stop_gradient=True)
args = OpTestUtils.prepare_python_api_arguments(
self.public_python_api,
eager_tensor_inputs,
attrs_outputs,
self.kernel_sig,
target_dtype=(
paddle.pir.core.DataType
if use_pir_api()
else paddle.core.VarDesc.VarType
),
)
inputs_sig, _, _ = self.kernel_sig
args = OpTestUtils.assumption_assert_and_transform(
args, len(inputs_sig)
)
net = PrimNet(self.public_python_api)
net = apply_to_static(
net, core.is_compiled_with_cinn() and self.enable_cinn
)
# check the operator not in program if check prim is True
forward_ops = [
op.type
for op in net.forward.get_concrete_program(args)[1]
.forward_program.block(0)
.ops
]
assert self.op_type not in forward_ops, (
f"{self.op_type} shouldn't appear in program when check_prim is True"
)
ret = flatten(_as_list(net(args)))
ret = paddle.utils.map_structure(lambda x: x.numpy(), ret)
if OpTestUtils.is_bfloat16_type(self.dtype):
ret = paddle.utils.map_structure(
lambda x: convert_uint16_to_float(x), ret
)
# check jit comp forward
if len(ret) != len(self.eager_desire):
msg = (
f"The jit comp with cinn forward api out tensor nums is different with eager forward api out tensor nums on {self.place}."
f'when enable_fw_comp is {self.enable_fw_comp}, enable_cinn is {core.is_compiled_with_cinn() and self.enable_cinn}, jit comp forward api out tensor nums = {len(ret)}, eager forward api out tensor nums = {len(self.eager_desire)}. \n'
)
raise RuntimeError(msg)
for i in range(len(ret)):
np.testing.assert_allclose(
ret[i],
self.eager_desire[i],
rtol=rtol,
atol=atol,
err_msg=(
f'Check jit comp with cinn forward api out failed. Mismatch between jit comp and eager on {self.place!s}, '
f'when enable_fw_comp is {self.enable_fw_comp}, '
f'enable_cinn is {core.is_compiled_with_cinn() and self.enable_cinn}, '
f'the forward api out tensor\'s index is : {i} \n'
f'jit comp forward api out tensor:\n{ret[i]}\n '
f'eager forward api out tensor:\n{self.eager_desire[i]}\n'
),
)
core._set_prim_forward_enabled(False)
net.forward.program_cache.clear()
class PrimGradChecker(PrimForwardChecker):
def __init__(
self,
op_test,
place,
inputs_to_check,
output_names,
no_grad_set,
grad_outputs,
):
PrimForwardChecker.__init__(self, op_test, place)
self.inputs_to_check = inputs_to_check
self.output_names = output_names
self.no_grad_set = no_grad_set
self.grad_outputs = grad_outputs
def init(self):
self.checker_name = "PrimGradChecker"
def check(self):
if (
type(self.place) is paddle.base.libpaddle.CUDAPlace
and not paddle.is_compiled_with_cuda()
):
return
self.eager_desire = self.get_eager_desire()
if not in_pir_mode():
if self.enable_check_eager_comp:
self.check_eager_comp()
if self.enable_check_static_comp:
self.check_static_comp()
if self.enable_check_jit_comp:
self.check_jit_comp()
if self.enable_check_jit_comp_with_cinn:
self.check_jit_comp_with_cinn()
else:
if self.enable_check_static_comp:
with scope_guard(Scope()):
self.check_static_comp()
if self.enable_check_jit_comp:
with scope_guard(Scope()):
self.check_jit_comp()
def get_output_dict(self, np_outputs, api_outputs, outputs_sig):
assert len(api_outputs) <= len(outputs_sig), (
f"forward api outputs length must be the less than or equal to KernelSignature outputs,but receive {len(api_outputs)} and {len(outputs_sig)}"
)
output_dict = {}
for i in range(len(api_outputs)):
output_name = outputs_sig[i]
if output_name in np_outputs and isinstance(
np_outputs[output_name], list
):
for j, tup in enumerate(np_outputs[output_name]):
output_dict.update({tup[0]: api_outputs[i][j]})
else:
output_dict.update({output_name: api_outputs[i]})
return output_dict
def gen_eager_grad_outputs(self):
if self.grad_outputs is None:
return None
eager_vs = []
for np_v in self.grad_outputs:
eager_vs.append(
paddle.to_tensor(
data=np_v,
place=self.place,
dtype=(
"bfloat16"
if OpTestUtils.is_bfloat16_type(np_v.dtype)
else np_v.dtype
),
)
)
return eager_vs
def gen_static_grad_outputs_and_feed(self):
if self.grad_outputs is None:
return None, {}
static_vs = []
feed = {}
for i, np_v in enumerate(self.grad_outputs):
static_vs.append(
paddle.static.data(
name='v_' + str(i),
shape=np_v.shape,
dtype=(
"bfloat16"
if OpTestUtils.is_bfloat16_type(np_v.dtype)
else np_v.dtype
),
)
)
feed.update({'v_' + str(i): np_v})
return static_vs, feed
def gen_no_grad_set(self, var_dict):
if self.no_grad_set is None:
return None
no_grad_set = ValueSet() if in_pir_mode() else set()
for name in self.no_grad_set:
if name in var_dict:
no_grad_set.add(var_dict[name])
return no_grad_set
def get_eager_desire(self):
with dygraph_guard():
if type(self.place) is paddle.base.libpaddle.CPUPlace:
paddle.device.set_device("cpu")
if type(self.place) is paddle.base.libpaddle.CUDAPlace:
paddle.device.set_device("gpu:0")
(
eager_tensor_inputs,
attrs_outputs,
inputs_dict,
) = self.get_eager_input_attr_and_inputdict(stop_gradient=False)
args = OpTestUtils.prepare_python_api_arguments(
self.public_python_api,
eager_tensor_inputs,
attrs_outputs,
self.kernel_sig,
target_dtype=paddle.core.VarDesc.VarType,
)
inputs_sig, _, outputs_sig = self.kernel_sig
if hasattr(self.op_test, "python_out_sig"):
outputs_sig = self.op_test.python_out_sig
args = OpTestUtils.assumption_assert_and_transform(
args, len(inputs_sig)
)
ret = _as_list(self.public_python_api(*args))
outputs_dict = self.get_output_dict(self.outputs, ret, outputs_sig)
ys = []
if isinstance(self.output_names, list):
for output_name in self.output_names:
ys.append(outputs_dict[output_name])
else:
ys.append(outputs_dict[self.output_names])
xs = []
if isinstance(self.inputs_to_check, list):
for input_name in self.inputs_to_check:
xs.append(inputs_dict[input_name])
else:
xs.append(inputs_dict[self.inputs_to_check])
vs = self.gen_eager_grad_outputs()
no_grad_vars = self.gen_no_grad_set(
var_dict=inputs_dict | outputs_dict
)
ret = paddle.grad(
ys, xs, vs, allow_unused=True, no_grad_vars=no_grad_vars
)
ret = paddle.utils.map_structure(lambda x: x.numpy(), ret)
if OpTestUtils.is_bfloat16_type(self.dtype):
ret = paddle.utils.map_structure(
lambda x: convert_uint16_to_float(x), ret
)
return ret
def check_eager_comp(self):
if self.prim_op_type == "comp":
return
with dygraph_guard():
if type(self.place) is paddle.base.libpaddle.CPUPlace:
paddle.device.set_device("cpu")
if type(self.place) is paddle.base.libpaddle.CUDAPlace:
paddle.device.set_device("gpu:0")
atol = self.rev_comp_atol
rtol = self.rev_comp_rtol
core.set_prim_eager_enabled(self.enable_rev_comp)
actual_ret = self.get_eager_desire()
# check static forward
if len(actual_ret) != len(self.eager_desire):
msg = (
f"The eager comp grad out tensor nums is different with eager grad out tensor nums on {self.place}."
f'when enable_rev_comp is {self.enable_rev_comp}, eager comp grad api out tensor nums = {len(actual_ret)}, eager grad out tensor nums = {len(self.eager_desire)}. \n'
)
raise RuntimeError(msg)
for i in range(len(actual_ret)):
np.testing.assert_allclose(
actual_ret[i],
self.eager_desire[i],
rtol=atol,
atol=rtol,
err_msg=(
'Check eager comp grad out failed. Mismatch between eager comp '
f'and eager on {self.place!s}, when enable_rev_comp is {self.enable_rev_comp},'
f'the eager comp grad out tensor\'s index is : {i} \n'
f'eager comp grad out tensor:\n{actual_ret[i]}\n eager grad out tensor:\n{self.eager_desire[i]}\n'
),
)
core.set_prim_eager_enabled(False)
def check_static_comp(self):
if self.prim_op_type == "prim":
core._set_prim_backward_enabled(self.enable_rev_comp)
else:
core._set_prim_forward_enabled(self.enable_fw_comp)
core._set_prim_backward_enabled(self.enable_rev_comp)
atol = self.rev_comp_atol if self.enable_rev_comp else self.fw_comp_atol
rtol = self.rev_comp_rtol if self.enable_rev_comp else self.fw_comp_rtol
with static_guard():
startup_program, main_program = (
paddle.static.Program(),
paddle.static.Program(),
)
with paddle.static.program_guard(main_program, startup_program):
(
static_inputs,
attrs,
inputs_dict,
feed,
) = self.get_static_input_attr_inputdict_and_feed(
stop_gradient=False
)
args = OpTestUtils.prepare_python_api_arguments(
self.public_python_api,
static_inputs,
attrs,
self.kernel_sig,
target_dtype=(
paddle.pir.core.DataType
if in_pir_mode()
else paddle.core.VarDesc.VarType
),
)
inputs_sig, _, outputs_sig = self.kernel_sig
if hasattr(self.op_test, "python_out_sig"):
outputs_sig = self.op_test.python_out_sig
args = OpTestUtils.assumption_assert_and_transform(
args, len(inputs_sig)
)
fw_outs = _as_list(self.public_python_api(*args))
if not in_pir_mode():
primapi.to_prim(main_program.blocks)
else:
blacklist = set()
for op in main_program.global_block().ops:
if core.has_custom_vjp(op):
blacklist.add(op.name())
fw_outs = decompose(
main_program,
fw_outs,
blacklist=blacklist,
)
outputs_dict = self.get_output_dict(
self.outputs, fw_outs, outputs_sig
)
ys = []
if isinstance(self.output_names, list):
for output_name in self.output_names:
ys.append(outputs_dict[output_name])
else:
ys.append(outputs_dict[self.output_names])
xs = []
if isinstance(self.inputs_to_check, list):
for input_name in self.inputs_to_check:
xs.append(inputs_dict[input_name])
else:
xs.append(inputs_dict[self.inputs_to_check])
vs, vs_feed = self.gen_static_grad_outputs_and_feed()
feed.update(vs_feed)
no_grad_vars = self.gen_no_grad_set(
var_dict=inputs_dict | outputs_dict
)
if not in_pir_mode():
ret = paddle.static.gradients(
ys, xs, vs, no_grad_set=no_grad_vars
)
else:
ret = ir_grad(ys, xs, vs, no_grad_vars=no_grad_vars)
# check the backward operator not in program when check_prim is True
if not in_pir_mode():
ops = [op.type for op in main_program.blocks[0].ops]
backward_op_type = self.op_type + "_grad"
assert backward_op_type not in ops, (
f"{backward_op_type} shouldn't appear in program when check_prim is True"
)
elif self.prim_op_type == "prim":
grad_ops = []
for op in main_program.global_block().ops:
if op.name().endswith("_grad"):
grad_ops.append(op.name())
assert not grad_ops, (
f"For {grad_ops} , grad op shouldn't appear in program when check_prim is True"
)
exe = paddle.static.Executor(self.place)
exe.run(startup_program)
actual_ret = exe.run(main_program, feed=feed, fetch_list=ret)
if OpTestUtils.is_bfloat16_type(self.dtype):
actual_ret = paddle.utils.map_structure(
lambda x: convert_uint16_to_float(x), actual_ret
)
# check static grad out
if len(actual_ret) != len(self.eager_desire):
msg = (
f"The static comp grad out tensor nums is different with eager grad out tensor nums on {self.place}."
f'when enable_fw_comp is {self.enable_fw_comp},enable_rev_comp is {self.enable_rev_comp}, static comp grad out tensor nums = {len(actual_ret)}, eager grad out tensor nums = {len(self.eager_desire)}. \n'
)
raise RuntimeError(msg)
for i in range(len(actual_ret)):
np.testing.assert_allclose(
actual_ret[i],
self.eager_desire[i],
rtol=rtol,
atol=atol,
err_msg=(
'Check static comp grad out failed. Mismatch between static comp '
f'and eager on {self.place}, when enable_fw_comp is {self.enable_fw_comp},enable_rev_comp is {self.enable_rev_comp},'
f'the forward api out tensor\'s index is : {i} \n'
f'static comp grad out tensor:\n{actual_ret[i]}\n eager grad out tensor:\n{self.eager_desire[i]}\n'
),
)
core._set_prim_forward_enabled(False)
core._set_prim_backward_enabled(False)
def check_jit_comp(self):
with dygraph_guard():
if type(self.place) is paddle.base.libpaddle.CPUPlace:
paddle.device.set_device("cpu")
if type(self.place) is paddle.base.libpaddle.CUDAPlace:
paddle.device.set_device("gpu:0")
if self.prim_op_type == "prim":
core._set_prim_backward_enabled(self.enable_rev_comp)
else:
core._set_prim_forward_enabled(self.enable_fw_comp)
core._set_prim_backward_enabled(self.enable_rev_comp)
atol = (
self.fw_comp_atol
if self.enable_fw_comp and not self.enable_rev_comp
else self.jit_comp_atol
)
rtol = (
self.fw_comp_rtol
if self.enable_fw_comp and not self.enable_rev_comp
else self.jit_comp_rtol
)
atol = self.rev_comp_atol if self.enable_rev_comp else atol
rtol = self.rev_comp_rtol if self.enable_rev_comp else rtol
(
eager_tensor_inputs,
attrs_outputs,
inputs_dict,
) = self.get_eager_input_attr_and_inputdict(stop_gradient=False)
args = OpTestUtils.prepare_python_api_arguments(
self.public_python_api,
eager_tensor_inputs,
attrs_outputs,
self.kernel_sig,
target_dtype=(
paddle.pir.core.DataType
if use_pir_api()
else paddle.core.VarDesc.VarType
),
)
inputs_sig, _, outputs_sig = self.kernel_sig
args = OpTestUtils.assumption_assert_and_transform(
args, len(inputs_sig)
)
net = PrimNet(self.public_python_api)
net = apply_to_static(net, False)
# check the backward operator not in program when check_prim is True
if not use_pir_api():
ops = [
op.type
for op in net.forward.get_concrete_program(args)[1]
.backward_program.block(0)
.ops
]
backward_op_type = self.op_type + "_grad"
assert backward_op_type not in ops, (
f"{backward_op_type} shouldn't appear in program when check_prim is True"
)
out = _as_list(net(args))
if hasattr(self.op_test, "python_out_sig"):
outputs_sig = self.op_test.python_out_sig
outputs_dict = self.get_output_dict(self.outputs, out, outputs_sig)
ys = []
if isinstance(self.output_names, list):
for output_name in self.output_names:
ys.append(outputs_dict[output_name])
else:
ys.append(outputs_dict[self.output_names])
xs = []
if isinstance(self.inputs_to_check, list):
for input_name in self.inputs_to_check:
xs.append(inputs_dict[input_name])
else:
xs.append(inputs_dict[self.inputs_to_check])
vs = self.gen_eager_grad_outputs()
no_grad_vars = self.gen_no_grad_set(
var_dict=inputs_dict | outputs_dict
)
ret = paddle.grad(
ys, xs, vs, allow_unused=True, no_grad_vars=no_grad_vars
)
ret = paddle.utils.map_structure(lambda x: x.numpy(), ret)
if OpTestUtils.is_bfloat16_type(self.dtype):
ret = paddle.utils.map_structure(
lambda x: convert_uint16_to_float(x), ret
)
# check jit comp grad out
if len(ret) != len(self.eager_desire):
msg = (
f"The jit comp grad out tensor nums is different with eager grad out tensor nums on {self.place}."
f'when enable_fw_comp is {self.enable_fw_comp}, enable_rev_comp is {self.enable_rev_comp}, jit comp grad out tensor nums = {len(ret)}, eager grad out tensor nums = {len(self.eager_desire)}. \n'
)
raise RuntimeError(msg)
for i in range(len(ret)):
np.testing.assert_allclose(
ret[i],
self.eager_desire[i],
rtol=rtol,
atol=atol,
err_msg=(
'Check jit comp grad out failed. Mismatch between jit comp '
f'and eager on {self.place!s}, when enable_fw_comp is {self.enable_fw_comp}, '
f'enable_rev_comp is {self.enable_rev_comp},the grad out tensor\'s index is : {i} \n'
f'jit comp grad out tensor:\n{ret[i]}\n eager grad out out tensor:\n{self.eager_desire[i]}\n'
),
)
core._set_prim_forward_enabled(False)
core._set_prim_backward_enabled(False)
net.forward.program_cache.clear()
def check_jit_comp_with_cinn(self):
# cinn doesn't support cpu place
if (
type(self.place) is paddle.base.libpaddle.CPUPlace
and self.enable_cinn
and core.is_compiled_with_cinn()
):
return
with dygraph_guard():
if type(self.place) is paddle.base.libpaddle.CPUPlace:
paddle.device.set_device("cpu")
if type(self.place) is paddle.base.libpaddle.CUDAPlace:
paddle.device.set_device("gpu:0")
if self.prim_op_type == "prim":
core._set_prim_backward_enabled(self.enable_rev_comp)
else:
core._set_prim_forward_enabled(self.enable_fw_comp)
core._set_prim_backward_enabled(self.enable_rev_comp)
if self.enable_cinn and core.is_compiled_with_cinn():
atol = self.cinn_atol
rtol = self.cinn_rtol
else:
atol = (
self.fw_comp_atol
if self.enable_fw_comp and not self.enable_rev_comp
else self.jit_comp_atol
)
rtol = (
self.fw_comp_rtol
if self.enable_fw_comp and not self.enable_rev_comp
else self.jit_comp_rtol
)
atol = self.rev_comp_atol if self.enable_rev_comp else atol
rtol = self.rev_comp_rtol if self.enable_rev_comp else rtol
(
eager_tensor_inputs,
attrs_outputs,
inputs_dict,
) = self.get_eager_input_attr_and_inputdict(stop_gradient=False)
args = OpTestUtils.prepare_python_api_arguments(
self.public_python_api,
eager_tensor_inputs,
attrs_outputs,
self.kernel_sig,
target_dtype=(
paddle.pir.core.DataType
if use_pir_api()
else paddle.core.VarDesc.VarType
),
)
inputs_sig, _, outputs_sig = self.kernel_sig
args = OpTestUtils.assumption_assert_and_transform(
args, len(inputs_sig)
)
net = PrimNet(self.public_python_api)
net = apply_to_static(
net, core.is_compiled_with_cinn() and self.enable_cinn
)
# check the backward operator not in program when check_prim is True
ops = [
op.type
for op in net.forward.get_concrete_program(args)[1]
.backward_program.block(0)
.ops
]
backward_op_type = self.op_type + "_grad"
assert backward_op_type not in ops, (
f"{backward_op_type} shouldn't appear in program when check_prim is True"
)
out = _as_list(net(args))
if hasattr(self.op_test, "python_out_sig"):
outputs_sig = self.op_test.python_out_sig
outputs_dict = self.get_output_dict(self.outputs, out, outputs_sig)
ys = []
if isinstance(self.output_names, list):
for output_name in self.output_names:
ys.append(outputs_dict[output_name])
else:
ys.append(outputs_dict[self.output_names])
xs = []
if isinstance(self.inputs_to_check, list):
for input_name in self.inputs_to_check:
xs.append(inputs_dict[input_name])
else:
xs.append(inputs_dict[self.inputs_to_check])
vs = self.gen_eager_grad_outputs()
no_grad_vars = self.gen_no_grad_set(
var_dict=inputs_dict | outputs_dict
)
ret = paddle.grad(
ys, xs, vs, allow_unused=True, no_grad_vars=no_grad_vars
)
ret = paddle.utils.map_structure(lambda x: x.numpy(), ret)
if OpTestUtils.is_bfloat16_type(self.dtype):
ret = paddle.utils.map_structure(
lambda x: convert_uint16_to_float(x), ret
)
# check jit comp grad out
if len(ret) != len(self.eager_desire):
msg = (
f"The jit comp with cinn grad out tensor nums is different with eager grad out tensor nums on {self.place}."
f'when enable_fw_comp is {self.enable_fw_comp}, enable_rev_comp is {self.enable_rev_comp}, enable_cinn is {self.enable_cinn and core.is_compiled_with_cinn()}, jit comp grad out tensor nums = {len(ret)}, eager grad out tensor nums = {len(self.eager_desire)}. \n'
)
raise RuntimeError(msg)
for i in range(len(ret)):
np.testing.assert_allclose(
ret[i],
self.eager_desire[i],
rtol=rtol,
atol=atol,
err_msg=(
'Check jit comp with cinn grad out failed. Mismatch between jit comp with cinn '
f'and eager on {self.place!s}, when enable_fw_comp is {self.enable_fw_comp}, '
f'enable_rev_comp is {self.enable_rev_comp}, enable_cinn is {self.enable_cinn and core.is_compiled_with_cinn()},'
f'the grad out tensor\'s index is : {i} ,jit comp with cinn grad out tensor:\n{ret[i]}\n eager grad out out tensor:\n{self.eager_desire[i]}\n'
),
)
core._set_prim_forward_enabled(False)
core._set_prim_backward_enabled(False)
net.forward.program_cache.clear()