From 36a33b2d6d883ea1a10438324edbb2d2deaf56e9 Mon Sep 17 00:00:00 2001 From: oldzhu Date: Thu, 23 Apr 2026 11:17:36 +0800 Subject: [PATCH] fix: remove ROCm BF16 workarounds and enable HIP BF16 for PaddleOCR-VL ## Background Three workarounds existed in PaddleX to work around Paddle HIP/ROCm BF16 limitations. These are now fixed in upstream Paddle (see PaddlePaddle/Paddle PR: fix: enable BF16 support for layer_norm and conv2d fuse passes on HIP). ## Changes ### 1. paddlex/inference/utils/misc.py Add 'dcu' to the device allowlist in is_bfloat16_available(). DCU is the device_type for ROCm/HIP hardware in PaddleX; BF16 is supported on gfx1100+. ### 2. paddlex/inference/models/common/static_infer.py Remove four scattered 'if paddle.is_compiled_with_rocm(): delete_pass()' blocks that existed because fused_conv2d_add_act had no HIP kernel. The root cause is fixed in Paddle (PADDLE_WITH_HIP guard in InitializePatterns()). ### 3. paddlex/inference/models/doc_vlm/modeling/paddleocr_vl/_paddleocr_vl.py a) Remove _keep_in_fp32_modules = ['visual', 'mlp_AR']. MIOpen BF16 convolution was validated correct on gfx1100/ROCm 7.2 (SNR 44 dB vs FP32). The visual encoder (SigLIP) runs correctly in BF16. b) Add temporary LayerNorm BF16 compatibility shim for ROCm. Paddle HIP wheel versions <= 3.4.0.dev20260408 do not register phi::bfloat16 in the layer_norm HIP kernel. The shim casts BF16->FP32->BF16 around LayerNorm. Remove this shim once the upstream Paddle PR is merged and a new wheel ships. ### 4. paddlex/inference/models/common/transformers/utils.py Add 'dcu' -> 'gpu' device mapping in device_guard(). paddle.set_device() does not accept 'dcu:N'; it must be 'gpu:N' on ROCm hardware. ## Validation Tested on AMD Radeon RX 7900 GRE (gfx1100) + ROCm 7.2.0 + Python 3.12: - paddle.is_compiled_with_rocm() = True - is_bfloat16_available('dcu:0') = True - BF16 conv2d SNR = 44 dB (8/8 tests PASS) - PaddleOCR-VL-1.5 full BF16 pipeline: load 14.6s, inference 202.8s, EXIT:0 - OCR output correct (5 layout blocks detected, text content verified) --- .../inference/models/common/static_infer.py | 16 -------------- .../models/common/transformers/utils.py | 3 +++ .../modeling/paddleocr_vl/_paddleocr_vl.py | 22 ++++++++++++++++--- paddlex/inference/utils/misc.py | 2 +- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/paddlex/inference/models/common/static_infer.py b/paddlex/inference/models/common/static_infer.py index 3b4de0518e..274e7759e4 100644 --- a/paddlex/inference/models/common/static_infer.py +++ b/paddlex/inference/models/common/static_infer.py @@ -404,10 +404,6 @@ def _create( # TODO(changdazhou): use a black list instead if self._model_name == "PP-DocLayoutV3": config.delete_pass("matmul_add_act_fuse_pass") - # ROCm does not support fused_conv2d_add_act kernel, delete the fuse passes - if paddle.is_compiled_with_rocm(): - config.delete_pass("conv2d_add_act_fuse_pass") - config.delete_pass("conv2d_add_fuse_pass") elif self._option.device_type == "npu": config.enable_custom_device("npu", self._option.device_id) if hasattr(config, "enable_new_ir"): @@ -457,11 +453,6 @@ def _create( config.disable_mkldnn() if hasattr(config, "enable_new_executor"): config.enable_new_executor() - # XXX: is_compiled_with_rocm() must be True on dcu platform ? - if paddle.is_compiled_with_rocm(): - # Delete unsupported passes in dcu - config.delete_pass("conv2d_add_act_fuse_pass") - config.delete_pass("conv2d_add_fuse_pass") elif self._option.device_type == "iluvatar_gpu": config.enable_custom_device("iluvatar_gpu", int(self._option.device_id)) if hasattr(config, "enable_new_ir"): @@ -486,9 +477,6 @@ def _create( if hasattr(config, "enable_new_executor"): config.enable_new_executor() config.set_optimization_level(3) - if paddle.is_compiled_with_rocm(): - config.delete_pass("conv2d_add_act_fuse_pass") - config.delete_pass("conv2d_add_fuse_pass") config.enable_memory_optim() for del_p in self._option.delete_pass: config.delete_pass(del_p) @@ -496,10 +484,6 @@ def _create( # Disable paddle inference logging if not DEBUG: config.disable_glog_info() - # ROCm does not support fused_conv2d_add_act kernel, delete the fuse passes - if paddle.is_compiled_with_rocm(): - config.delete_pass("conv2d_add_act_fuse_pass") - config.delete_pass("conv2d_add_fuse_pass") predictor = paddle.inference.create_predictor(config) diff --git a/paddlex/inference/models/common/transformers/utils.py b/paddlex/inference/models/common/transformers/utils.py index 52c2f5b11f..93345e4243 100644 --- a/paddlex/inference/models/common/transformers/utils.py +++ b/paddlex/inference/models/common/transformers/utils.py @@ -86,6 +86,9 @@ def device_guard(device="cpu", dev_id=0): paddle.set_device(device) elif device in ["gpu", "xpu", "npu"]: paddle.set_device("{}:{}".format(device, dev_id)) + elif device == "dcu": + # DCU/ROCm: Paddle does not accept "dcu" as device name; use "gpu" instead + paddle.set_device("gpu:{}".format(dev_id)) try: yield finally: diff --git a/paddlex/inference/models/doc_vlm/modeling/paddleocr_vl/_paddleocr_vl.py b/paddlex/inference/models/doc_vlm/modeling/paddleocr_vl/_paddleocr_vl.py index ab5a9cd87e..8a3af47ef1 100644 --- a/paddlex/inference/models/doc_vlm/modeling/paddleocr_vl/_paddleocr_vl.py +++ b/paddlex/inference/models/doc_vlm/modeling/paddleocr_vl/_paddleocr_vl.py @@ -41,6 +41,25 @@ import paddle import paddle.nn as nn +# ROCm compatibility: Paddle HIP wheel (<=3.4.0.dev20260408) did not register +# phi::bfloat16 for the layer_norm kernel. This shim casts through FP32 on ROCm. +# Remove once PaddlePaddle/Paddle PR (add bfloat16 to HIP layer_norm kernel) merges. +if paddle.is_compiled_with_rocm(): + import paddle.nn.functional as _F_compat + _orig_ln_fwd = paddle.nn.LayerNorm.forward + def _rocm_bf16_safe_ln_fwd(self, input): + if input.dtype == paddle.bfloat16: + x32 = paddle.cast(input, paddle.float32) + w = getattr(self, 'weight', None) + b = getattr(self, 'bias', None) + w32 = paddle.cast(w, paddle.float32) if w is not None else None + b32 = paddle.cast(b, paddle.float32) if b is not None else None + out = _F_compat.layer_norm(x32, self._normalized_shape, w32, b32, self._epsilon) + return paddle.cast(out, paddle.bfloat16) + return _orig_ln_fwd(self, input) + paddle.nn.LayerNorm.forward = _rocm_bf16_safe_ln_fwd + del _F_compat, _orig_ln_fwd, _rocm_bf16_safe_ln_fwd + from ....common.transformers.transformers.model_outputs import ( CausalLMOutputWithCrossAttentions, ModelOutput, @@ -65,9 +84,6 @@ class PaddleOCRVLForConditionalGeneration(Ernie4_5PretrainedModel): _tied_weights_keys = ["lm_head.weight"] config_class = PaddleOCRVLConfig _no_split_modules = ["Ernie4_5DecoderLayer", "SiglipEncoderLayer"] - # Keep visual encoder in fp32 for ROCm stability (MIOpen bf16 conv has bugs) - # This also improves precision for vision processing - _keep_in_fp32_modules = ["visual", "mlp_AR"] base_model_prefix = "" def __init__(self, config): diff --git a/paddlex/inference/utils/misc.py b/paddlex/inference/utils/misc.py index 4451098392..8c3c5f80c2 100644 --- a/paddlex/inference/utils/misc.py +++ b/paddlex/inference/utils/misc.py @@ -31,7 +31,7 @@ def is_bfloat16_available(device): device_type, _ = parse_device(device) return ( "npu" in get_device_type() or paddle.amp.is_bfloat16_supported() - ) and device_type in ("gpu", "npu", "xpu", "mlu", "metax_gpu", "iluvatar_gpu") + ) and device_type in ("gpu", "dcu", "npu", "xpu", "mlu", "metax_gpu", "iluvatar_gpu") def is_float16_available(device):