Files
paddlepaddle--paddle/python/paddle/base/data_feeder.py
T
2026-07-13 12:40:42 +08:00

597 lines
21 KiB
Python

# Copyright (c) 2018 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.
from __future__ import annotations
import struct
from typing import TYPE_CHECKING, Any
import numpy as np
import paddle
from paddle import pir
from ..pir import Value
from ..pir.core import ParameterMeta, datatype_to_str
from . import core
from .framework import (
EagerParamBase,
Variable,
_cpu_num,
_cuda_ids,
default_main_program,
in_dygraph_mode,
in_pir_mode,
)
if TYPE_CHECKING:
from typing import TypeAlias
from paddle._typing import DTypeLike, ShapeLike
from paddle._typing.dtype_like import _DTypeLiteral
_ClassInfo: TypeAlias = type[Any] | tuple["_ClassInfo", ...]
__all__ = []
vartype_to_str = {
core.VarDesc.VarType.BOOL: 'bool',
core.VarDesc.VarType.FP8_E4M3FN: 'float8_e4m3fn',
core.VarDesc.VarType.FP8_E5M2: 'float8_e5m2',
core.VarDesc.VarType.FP16: 'float16',
core.VarDesc.VarType.BF16: 'uint16',
core.VarDesc.VarType.FP32: 'float32',
core.VarDesc.VarType.FP64: 'float64',
core.VarDesc.VarType.INT8: 'int8',
core.VarDesc.VarType.INT16: 'int16',
core.VarDesc.VarType.INT32: 'int32',
core.VarDesc.VarType.INT64: 'int64',
core.VarDesc.VarType.UINT8: 'uint8',
core.VarDesc.VarType.UINT16: 'uint16',
core.VarDesc.VarType.UINT32: 'uint32',
core.VarDesc.VarType.UINT64: 'uint64',
core.VarDesc.VarType.COMPLEX64: 'complex64',
core.VarDesc.VarType.COMPLEX128: 'complex128',
core.VarDesc.VarType.STRING: 'pstring',
core.VarDesc.VarType.RAW: 'raw',
}
_PADDLE_DTYPE = [
core.DataType.UINT8,
core.DataType.INT8,
core.DataType.INT16,
core.DataType.INT32,
core.DataType.INT64,
core.DataType.FLOAT16,
core.DataType.FLOAT32,
core.DataType.FLOAT64,
core.DataType.COMPLEX64,
core.DataType.COMPLEX128,
core.DataType.BOOL,
core.DataType.BFLOAT16,
]
u1, i1, i2, i4, i8, f2, f4, f8, c4, c8, b1, bf = _PADDLE_DTYPE
_PROMOTE_MATRIX = [
# u1, i1, i2, i4, i8, f2, f4, f8, c4, c8, b1, bf
[u1, i2, i2, i4, i8, f2, f4, f8, c4, c8, u1, bf], # u1
[i2, i1, i2, i4, i8, f2, f4, f8, c4, c8, i1, bf], # i1
[i2, i2, i2, i4, i8, f2, f4, f8, c4, c8, i2, bf], # i2
[i4, i4, i4, i4, i8, f2, f4, f8, c4, c8, i4, bf], # i4
[i8, i8, i8, i8, i8, f2, f4, f8, c4, c8, i8, bf], # i8
[f2, f2, f2, f2, f2, f2, f4, f8, c4, c8, f2, f4], # f2
[f4, f4, f4, f4, f4, f4, f4, f8, c4, c8, f4, f4], # f4
[f8, f8, f8, f8, f8, f8, f8, f8, c8, c8, f8, f8], # f8
[c4, c4, c4, c4, c4, c4, c4, c8, c4, c8, c4, c4], # c4
[c8, c8, c8, c8, c8, c8, c8, c8, c8, c8, c8, c8], # c8
[u1, i1, i2, i4, i8, f2, f4, f8, c4, c8, b1, bf], # b1
[bf, bf, bf, bf, bf, f4, f4, f8, c4, c8, bf, bf], # bf
]
_TYPE_TO_IDX = {t: i for i, t in enumerate(_PADDLE_DTYPE)}
def promote_types(type1, type2):
idx1 = _TYPE_TO_IDX.get(type1)
idx2 = _TYPE_TO_IDX.get(type2)
if idx1 is None or idx2 is None:
raise TypeError(f"Unsupported dtype: {type1} or {type2}")
return _PROMOTE_MATRIX[idx1][idx2]
def convert_float_to_uint16(data, data_format="NCHW"):
if data.size == 0:
return data.view(np.uint16)
if data_format == "NHWC":
data = np.transpose(data, [0, 3, 1, 2])
new_data = np.vectorize(
lambda x: struct.unpack('<I', struct.pack('<f', x))[0] >> 16,
otypes=[np.uint16],
)(data.flat)
new_data = np.reshape(new_data, data.shape)
if data_format == "NHWC":
new_data = np.transpose(new_data, [0, 2, 3, 1])
return new_data
def convert_uint16_to_float(data):
new_data = np.vectorize(
lambda x: struct.unpack('<f', struct.pack('<I', x << 16))[0],
otypes=[np.float32],
)(data.flat)
return np.reshape(new_data, data.shape)
def convert_dtype(dtype: DTypeLike) -> _DTypeLiteral:
if isinstance(dtype, core.VarDesc.VarType):
if dtype in vartype_to_str:
return vartype_to_str[dtype]
if isinstance(dtype, core.DataType):
if dtype in datatype_to_str:
return datatype_to_str[dtype]
elif isinstance(dtype, type):
# This branch is for NumPy scalar types
if dtype in [
bool,
np.float16,
np.uint16,
np.uint32,
np.uint64,
np.float32,
np.float64,
np.int8,
np.int16,
np.int32,
np.int64,
np.uint8,
np.complex64,
np.complex128,
]:
return dtype.__name__
else:
# This branch is for np.dtype and str
if dtype in [
'bool',
'float16',
'uint16',
'uint32',
'uint64',
'float32',
'float64',
'int4',
'int8',
'int16',
'int32',
'int64',
'uint8',
'complex64',
'complex128',
'float8_e4m3fn',
'float8_e5m2',
]:
# NOTE(SigureMo): Since the np.dtype object is not an instance of
# type, so it will not be handled by the previous branch. We need
# to convert it to str here.
return str(dtype)
# NOTE(zhangbo): Now numpy does not support bfloat, so use numpy.uint16 to represent paddle.bfloat16, there binaries are consistent.
# If cast ndarray to uint16 and trans to tensor, should not ndarray.astype('uint16') directly
# should use function 'convert_float_to_uint16' above, otherwise bits is wrong
if dtype in ['bfloat16']:
return 'uint16'
raise TypeError(
"dtype must be any of [bool, float16, uint16, float32, float64, int8, int16, "
f"int32, int64, uint8, complex64, complex128, bfloat16], but received {dtype}"
)
def check_variable_and_dtype(
input, input_name, expected_dtype, op_name, extra_message=''
):
if in_pir_mode():
from ..nn.initializer.lazy_init import lazy_init_helper
if lazy_init_helper().state:
expected = (Value, ParameterMeta, EagerParamBase)
else:
expected = (Value, ParameterMeta)
check_type(input, input_name, expected, op_name, extra_message)
else:
check_type(input, input_name, (Variable, Value), op_name, extra_message)
check_dtype(input.dtype, input_name, expected_dtype, op_name, extra_message)
def check_type(input, input_name, expected_type, op_name, extra_message=''):
# NOTE [ Why skip dynamic graph check ]:
# 1. If the input type / dtype of a layer is wrong, it will be reported
# directly on that line. User can easily print the relevant information
# on which line. It is easier to debug, so there is no need to check
# in dynamic graph mode.
# 2. Performance considerations. Because these checks are executed at
# each step in dynamic graph mode, it will bring a heavy performance burden.
if in_dygraph_mode():
return
# NOTE: `in_to_static_mode` is used to determined whether this op is called under
# @to_static in transformation from dygraph to static layer. We add Tensor in
# expected_type to skip checking because Tensor may be created and used in unusual way.
from ..nn.initializer.lazy_init import lazy_init_helper
from .dygraph.base import in_to_static_mode
# Need a better design to be fix this.
if in_to_static_mode():
if not isinstance(expected_type, tuple):
expected_type = (expected_type,)
expected_type += (core.eager.Tensor,)
elif isinstance(input, core.eager.Tensor) and not lazy_init_helper().state:
raise TypeError(
"Please use `with base.dygraph.guard()` as context or `paddle.disable_static()` to switch to dygraph mode firstly. "
f"Because received '{input_name}' in {op_name} is an Eager Tensor."
)
if not isinstance(input, expected_type):
raise TypeError(
f"The type of '{input_name}' in {op_name} must be {expected_type}, but received {type(input)}. {extra_message}"
)
def check_dtype(
input_dtype, input_name, expected_dtype, op_name, extra_message=''
):
# See NOTE [ Why skip dynamic graph check ]
if in_dygraph_mode():
return
if convert_dtype(input_dtype) not in expected_dtype:
raise TypeError(
f"The data type of '{input_name}' in {op_name} must be {expected_dtype}, but received {convert_dtype(input_dtype)}. {extra_message}"
)
def check_shape(
shape: ShapeLike,
op_name: str,
expected_shape_type: _ClassInfo = (
list,
tuple,
Variable,
Value,
),
expected_element_type: _ClassInfo = (
int,
Variable,
Value,
),
expected_tensor_dtype: tuple[_DTypeLiteral, ...] = ('int32', 'int64'),
) -> None:
# See NOTE [ Why skip dynamic graph check ]
if in_dygraph_mode():
return
check_type(shape, 'shape', expected_shape_type, op_name)
if expected_element_type is not None and not isinstance(
shape, (Variable, Value)
):
for item in shape:
check_type(item, 'element of shape', expected_element_type, op_name)
if expected_tensor_dtype is not None and isinstance(
item, (Variable, Value)
):
check_dtype(
item.dtype,
'element of shape',
expected_tensor_dtype,
op_name,
'If element of shape is Tensor, its data type should be {}'.format(
', '.join(expected_tensor_dtype)
),
)
if expected_tensor_dtype is not None and isinstance(
shape, (Variable, Value)
):
check_dtype(shape.dtype, 'shape', expected_tensor_dtype, op_name)
class DataToDenseTensorConverter:
def __init__(self, place, lod_level, shape, dtype):
self.place = place
self.lod_level = lod_level
self.shape = shape
negative_count = 0
for s in self.shape:
if s < 0:
negative_count += 1
if negative_count > 1:
self.shape = None
break
self.dtype = convert_dtype(dtype)
self._reset()
def _reset(self):
self.data = []
self.lod = [[] for _ in range(self.lod_level)]
def feed(self, data):
self._feed_impl_(data, self.lod, self.lod_level)
def _feed_impl_(self, data, lod, lod_level):
if lod_level == 0:
self.data.append(data)
else:
lod[0].append(len(data))
for each_data in data:
self._feed_impl_(each_data, lod[1:], lod_level - 1)
def _check_shape(self, shape):
for s1, s2 in zip(self.shape, shape):
if s1 != s2 and s1 >= 0 and s2 >= 0:
raise ValueError(
f"Shape not match. What is defined in data layer is {self.shape}, but receive {shape}"
)
def done(self):
arr = np.array(self.data, dtype=self.dtype)
if self.shape:
if len(arr.shape) != len(self.shape):
try:
arr = arr.reshape(self.shape)
except ValueError:
raise ValueError(
f"Reshape error. What is defined in data layer is {self.shape}, but receive {arr.shape}"
)
t = core.DenseTensor()
t.set(arr, self.place)
if self.lod_level > 0:
t.set_recursive_sequence_lengths(self.lod)
self._reset()
return t
class BatchedTensorProvider:
def __init__(self, feed_list, place, batch_size, generator, drop_last):
self.place = place
self.batch_size = batch_size
self.generator = generator
self.converters = []
self.drop_last = drop_last
for var in feed_list:
if not in_pir_mode():
assert var.lod_level == 0, "lod_level must be 0"
self.converters.append(
DataToDenseTensorConverter(
place=self.place,
lod_level=0,
shape=var.shape,
dtype=var.dtype,
)
)
def _done(self):
return [c.done() for c in self.converters]
def __call__(self):
idx = 0
for each_sample in self.generator():
for each_slot, each_converter in zip(each_sample, self.converters):
each_converter.data.append(each_slot)
idx += 1
if idx == self.batch_size:
idx = 0
yield self._done()
if not self.drop_last and idx > 0:
yield self._done()
else:
[c._reset() for c in self.converters]
class DataFeeder:
"""
:api_attr: Static Graph
DataFeeder converts the data that returned by a reader into a data
structure that can feed into Executor. The reader is usually a
python generator that returns a list of mini-batch data entries.
Parameters:
feed_list (list): Variables or names of Variables that need
to feed.
place (:ref:`api_paddle_CPUPlace` | :ref:`api_paddle_CUDAPlace` ):
place indicates the device (CPU | GPU) the data will be fed into, if
you want to feed data into GPU, please using :code:`base.CUDAPlace(i)`
(:code:`i` represents the GPU id), or if you want to feed data into CPU,
please using :code:`base.CPUPlace()`.
program (:ref:`api_paddle_static_Program` , optional): The Program that will
feed data into, if program is None, it will use default_main_program().
Default None.
Raises:
:code:`ValueError` - If some Variables are not in this Program.
Example:
.. code-block:: pycon
>>> import numpy as np
>>> import paddle
>>> from paddle import base
>>> paddle.enable_static()
>>> place = paddle.CPUPlace()
>>> def reader():
... for _ in range(4):
... yield (
... np.random.random([4]).astype('float32'),
... np.random.random([3]).astype('float32'),
... )
>>> main_program = paddle.static.Program()
>>> startup_program = paddle.static.Program()
>>> with paddle.static.program_guard(main_program, startup_program):
... data_1 = paddle.static.data(name='data_1', shape=[None, 2, 2], dtype='float32')
... data_2 = paddle.static.data(name='data_2', shape=[None, 1, 3], dtype='float32')
... out = paddle.static.nn.fc(x=[data_1, data_2], size=2)
... # ...
>>> feeder = base.DataFeeder([data_1, data_2], place)
>>> exe = paddle.static.Executor(place)
>>> exe.run(startup_program)
>>> feed_data = feeder.feed(reader())
>>> # print feed_data to view feed results
>>> # print(feed_data['data_1'])
>>> # print(feed_data['data_2'])
>>> outs = exe.run(
... program=main_program,
... feed=feed_data,
... fetch_list=[out],
... )
>>> print(outs)
"""
def __init__(self, feed_list, place, program=None):
self.feed_dtypes = []
self.feed_names = []
self.feed_shapes = []
self.feed_lod_level = []
self.place = place
if in_pir_mode():
if program is None:
program = pir.core.default_main_program()
for each_var in feed_list:
if isinstance(each_var, str):
raise ValueError(
"In PIR Mode, Not supported string input yet"
)
if not isinstance(each_var, Value):
raise TypeError("Feed list should contain a list of Value")
self.feed_dtypes.append(each_var.dtype)
self.feed_names.append(each_var.name)
self.feed_lod_level.append(0)
self.feed_shapes.append(each_var.shape)
else:
if program is None:
program = default_main_program()
for each_var in feed_list:
if isinstance(each_var, str):
each_var = program.block(0).var(each_var)
if not isinstance(each_var, (Variable, Value)):
raise TypeError(
"Feed list should contain a list of variable"
)
self.feed_dtypes.append(each_var.dtype)
self.feed_names.append(each_var.name)
self.feed_lod_level.append(each_var.lod_level)
self.feed_shapes.append(each_var.shape)
def feed(self, iterable):
"""
According to :code:`feed_list` of :code:`DataFeeder` and :code:`iterable` , converts
the input into a data structure that can feed into Executor.
Parameters:
iterable (generator): user defined python generator to read the raw input data
Returns:
:code:`dict`: a :code:`dict` that contains (variable name - converted tensor) pairs
Example:
.. code-block:: pycon
>>> # In this example, reader - generator will return a list of ndarray of 3 elements
>>> # feed API will convert each ndarray input into a tensor
>>> # the return result is a dict with keys: data_1, data_2, data_3
>>> # result['data_1'] a LoD-Tensor with shape of [5, 2, 1, 3]. 5 is batch size, and [2, 1, 3] is the real shape of data_1.
>>> # result['data_2'], result['data_3'] are similar.
>>> import numpy as np
>>> import paddle
>>> from paddle import base
>>> paddle.enable_static()
>>> def reader(limit=5):
... for i in range(1, limit + 1):
... yield (
... np.ones([6]).astype('float32') * i,
... np.ones([1]).astype('int64') * i,
... np.random.random([9]).astype('float32'),
... )
>>> data_1 = paddle.static.data(name='data_1', shape=[None, 2, 1, 3])
>>> data_2 = paddle.static.data(name='data_2', shape=[None, 1], dtype='int64')
>>> data_3 = paddle.static.data(name='data_3', shape=[None, 3, 3], dtype='float32')
>>> feeder = base.DataFeeder(['data_1', 'data_2', 'data_3'], paddle.CPUPlace())
>>> result = feeder.feed(reader())
>>> print(result['data_1'])
>>> print(result['data_2'])
>>> print(result['data_3'])
"""
converter = []
for lod_level, shape, dtype in zip(
self.feed_lod_level, self.feed_shapes, self.feed_dtypes
):
converter.append(
DataToDenseTensorConverter(
place=self.place,
lod_level=lod_level,
shape=shape,
dtype=dtype,
)
)
def feed_data(converter, data):
if isinstance(data, (list, tuple)):
for item in data:
feed_data(converter, item)
else:
converter.feed(data)
if paddle.framework.use_pir_api():
for each_sample in iterable:
assert len(each_sample) == len(converter), (
"The number of fields in data (%d) does not match "
+ "len(feed_list) (%d)"
) % (len(each_sample), len(converter))
for each_converter, each_slot in zip(converter, each_sample):
feed_data(each_converter, each_slot)
else:
for each_sample in iterable:
assert len(each_sample) == len(converter), (
"The number of fields in data (%d) does not match "
+ "len(feed_list) (%d)"
) % (len(each_sample), len(converter))
for each_converter, each_slot in zip(converter, each_sample):
each_converter.feed(each_slot)
ret_dict = {}
for each_name, each_converter in zip(self.feed_names, converter):
ret_dict[each_name] = each_converter.done()
return ret_dict
def _get_number_of_places_(self, num_places):
if num_places is not None:
return int(num_places)
elif isinstance(self.place, core.CUDAPlace):
return len(_cuda_ids())
else:
return _cpu_num()