Files
2026-07-13 12:40:42 +08:00

292 lines
10 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Copyright (c) 2025 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 re
import numpy as np
from paddle.base import core
def _print_tensor_in_gpu(tensor):
"""
Print a GPU tensor's dtype, shape, and all data values directly from
the device using a single-thread CUDA kernel (device-side printf).
This function is **CUDA Graph safe**: no host/device memory transfer
is performed (shape is passed via kernel-argument registers), so it
can be called inside a CUDA Graph capture region.
Args:
tensor (paddle.Tensor): A GPU DenseTensor to print. Must already
reside on a CUDA device (call ``tensor.cuda()`` first if needed).
Raises:
ValueError: If PaddlePaddle is not compiled with CUDA support.
InvalidArgument: If the tensor is not a DenseTensor or not on GPU.
Examples:
.. code-block:: pycon
>>> # doctest: +REQUIRES(env:GPU)
>>> import paddle
>>> paddle.device.set_device('gpu')
>>> x = paddle.to_tensor([[1.0, 2.0], [3.0, 4.0]])
>>> paddle.utils.gpu_utils._print_tensor_in_gpu(x)
"""
if not core.is_compiled_with_cuda():
raise ValueError(
"paddle.utils._print_tensor_in_gpu is not supported in "
"CPU-only PaddlePaddle. Please reinstall PaddlePaddle with GPU "
"support to call this API."
)
core.eager._print_tensor_in_gpu(tensor)
# Mapping from the dtype name printed by DebugPrintGPUTensor to a numpy dtype.
_DTYPE_STR_TO_NUMPY = {
'FLOAT32': np.float32,
'FLOAT64': np.float64,
'FLOAT16': np.float16,
'BFLOAT16': np.float32, # NumPy has no bfloat16; promote to float32
'INT32': np.int32,
'INT64': np.int64,
'INT16': np.int16,
'INT8': np.int8,
'UINT8': np.uint8,
'BOOL': np.bool_,
}
# Mapping from the dtype name to the paddle dtype string.
_DTYPE_STR_TO_PADDLE = {
'FLOAT32': 'float32',
'FLOAT64': 'float64',
'FLOAT16': 'float16',
'BFLOAT16': 'bfloat16',
'INT32': 'int32',
'INT64': 'int64',
'INT16': 'int16',
'INT8': 'int8',
'UINT8': 'uint8',
'BOOL': 'bool',
}
def _parse_tensor_from_gpu_print(text):
"""
Reconstruct a ``paddle.Tensor`` from the text output produced by
:func:`paddle.utils.gpu_utils._print_tensor_in_gpu`.
The expected input format is the output written to stdout by the
``DebugPrintGPUTensor`` CUDA kernel::
[TensorDebug] dtype : FLOAT32
[TensorDebug] shape : [2, 3]
[TensorDebug] numel : 6
[TensorDebug] data :
[[1, 2, 3],
[4, 5, 6]]
Special cases handled:
* **Scalar (0-D tensor)** the data line is ``[TensorDebug] data : <value>``
(no newline before the value).
* **Empty tensor (numel == 0)** the data line ends with ``[]``.
Args:
text (str): The captured stdout string from a call to
``paddle.utils.gpu_utils._print_tensor_in_gpu``.
Returns:
paddle.Tensor: A GPU tensor with the dtype and values recovered from
*text*. Call ``.cpu()`` on the result if you need a CPU tensor.
Raises:
ValueError: If *text* cannot be parsed (missing header lines, unknown
dtype, mismatched numel, …).
Examples:
.. code-block:: pycon
>>> import paddle
>>> import os, sys, tempfile
>>> # Capture the C-level stdout written by the CUDA printf kernel.
>>> def capture(tensor):
... with tempfile.NamedTemporaryFile(mode='w+', suffix='.txt', delete=False) as f:
... path = f.name
... sys.stdout.flush()
... old = os.dup(1)
... fd = os.open(path, os.O_WRONLY | os.O_TRUNC)
... os.dup2(fd, 1)
... os.close(fd)
... paddle.utils.gpu_utils._print_tensor_in_gpu(tensor)
... paddle.device.synchronize()
... sys.stdout.flush()
... os.dup2(old, 1)
... os.close(old)
... text = open(path).read()
... os.remove(path)
... return text
>>> x = paddle.to_tensor([[1.0, 2.0], [3.0, 4.0]])
>>> text = capture(x)
>>> y = paddle.utils.gpu_utils._parse_tensor_from_gpu_print(text)
>>> print(y)
"""
import paddle # local import to avoid circular imports at module load time
# ------------------------------------------------------------------ #
# 1. Extract header fields #
# ------------------------------------------------------------------ #
dtype_m = re.search(r'\[TensorDebug\] dtype\s*:\s*(\w+)', text)
shape_m = re.search(r'\[TensorDebug\] shape\s*:\s*\[([^\]]*)\]', text)
numel_m = re.search(r'\[TensorDebug\] numel\s*:\s*(\d+)', text)
data_m = re.search(r'\[TensorDebug\] data\s*:(.*)', text, re.DOTALL)
if not dtype_m:
raise ValueError(
"_parse_tensor_from_gpu_print: could not find "
"'[TensorDebug] dtype' line in the provided text."
)
if not shape_m:
raise ValueError(
"_parse_tensor_from_gpu_print: could not find "
"'[TensorDebug] shape' line in the provided text."
)
if not numel_m:
raise ValueError(
"_parse_tensor_from_gpu_print: could not find "
"'[TensorDebug] numel' line in the provided text."
)
if not data_m:
raise ValueError(
"_parse_tensor_from_gpu_print: could not find "
"'[TensorDebug] data' line in the provided text."
)
dtype_str = dtype_m.group(1).strip().upper()
shape_raw = shape_m.group(1).strip()
numel = int(numel_m.group(1).strip())
data_raw = data_m.group(1).strip()
if dtype_str not in _DTYPE_STR_TO_NUMPY:
raise ValueError(
f"_parse_tensor_from_gpu_print: unknown dtype '{dtype_str}'. "
f"Supported: {list(_DTYPE_STR_TO_NUMPY.keys())}"
)
np_dtype = _DTYPE_STR_TO_NUMPY[dtype_str]
paddle_dtype = _DTYPE_STR_TO_PADDLE[dtype_str]
# Parse shape: "" means scalar (0-D), otherwise comma-separated ints.
if shape_raw == '':
shape = []
else:
shape = [int(s.strip()) for s in shape_raw.split(',') if s.strip()]
# ------------------------------------------------------------------ #
# 2. Parse data section #
# ------------------------------------------------------------------ #
gpu_place = paddle.CUDAPlace(0)
if numel == 0:
# Empty tensor no data values to parse.
arr = np.empty(shape, dtype=np_dtype)
t = paddle.to_tensor(arr, dtype=paddle_dtype, place=gpu_place)
return t
if len(shape) == 0:
# Scalar (0-D tensor): data_raw is the single value directly.
value_str = data_raw
arr = np.array(_parse_value(value_str, dtype_str), dtype=np_dtype)
t = paddle.to_tensor(arr, dtype=paddle_dtype, place=gpu_place)
return t
# General N-D case: strip all brackets, whitespace, and split by commas.
# The nested bracket notation produced by PrintTensorKernel uses standard
# Python list syntax, so ast.literal_eval can reconstruct the nested list
# directly but only for numeric dtypes. For 'BOOL', PrintValue emits
# "True"/"False" which are valid Python literals too.
flat_values = _extract_flat_values(data_raw, dtype_str, numel)
arr = np.array(flat_values, dtype=np_dtype).reshape(shape)
t = paddle.to_tensor(arr, dtype=paddle_dtype, place=gpu_place)
return t
def _parse_value(value_str, dtype_str):
"""Parse a single scalar value string from the debug output."""
value_str = value_str.strip()
if dtype_str == 'BOOL':
if value_str == 'True':
return True
elif value_str == 'False':
return False
else:
raise ValueError(
f"_parse_tensor_from_gpu_print: cannot parse bool value "
f"'{value_str}'."
)
return float(value_str)
def _extract_flat_values(data_raw, dtype_str, numel):
"""
Extract a flat list of Python scalars from the nested-bracket string
produced by PrintTensorKernel.
Strategy: remove all bracket characters and split the remaining string
on commas / whitespace to get individual token strings, then convert
each token to the appropriate scalar type.
"""
# Remove all '[' and ']' characters.
flat_str = data_raw.replace('[', '').replace(']', '')
# Split on commas (values are separated by ", " or ",\n indent").
tokens = re.split(r'[,\s]+', flat_str)
tokens = [t.strip() for t in tokens if t.strip()]
if len(tokens) != numel:
raise ValueError(
f"_parse_tensor_from_gpu_print: expected {numel} values but "
f"found {len(tokens)} tokens in the data section. "
f"Parsed tokens: {tokens[:20]}{'...' if len(tokens) > 20 else ''}"
)
if dtype_str == 'BOOL':
result = []
for tok in tokens:
if tok == 'True':
result.append(True)
elif tok == 'False':
result.append(False)
else:
raise ValueError(
f"_parse_tensor_from_gpu_print: cannot parse bool token "
f"'{tok}'."
)
return result
# Integer dtypes must preserve exact values.
# GPU printf may emit scientific notation (e.g. "1e+06"), so use
# Decimal for lossless string-to-integer conversion.
from decimal import Decimal
_INT_DTYPES = {'INT8', 'INT16', 'INT32', 'INT64', 'UINT8'}
if dtype_str in _INT_DTYPES:
return [int(Decimal(t)) for t in tokens]
return [float(t) for t in tokens]