Explorar el Código

!13 Bump version to 3.3.6
Merge pull request !13 from zhch158/release/3.3

zhch158 hace 2 semanas
padre
commit
04780efc20

+ 18 - 0
deploy/genai_vllm_server_docker/Dockerfile

@@ -0,0 +1,18 @@
+FROM python:3.10
+
+RUN apt-get update \
+    && apt-get install -y libgl1 \
+    && rm -rf /var/lib/apt/lists/*
+
+ENV PIP_NO_CACHE_DIR=0
+ENV PYTHONUNBUFFERED=1
+ENV PYTHONDONTWRITEBYTECODE=1
+
+RUN python -m pip install 'paddlex>=3.3.5,<3.4'
+
+RUN python -m pip install https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/download/v0.3.14/flash_attn-2.8.2+cu128torch2.8-cp310-cp310-linux_x86_64.whl \
+    && paddlex --install genai-vllm-server
+
+EXPOSE 8080
+
+CMD ["paddlex_genai_server", "--model_name", "PaddleOCR-VL-0.9B", "--host", "0.0.0.0", "--port", "8080", "--backend", "vllm"]

+ 9 - 0
deploy/genai_vllm_server_docker/build.sh

@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+docker build \
+    -t "ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddlex-genai-vllm-server:${1:latest}" \
+    --build-arg http_proxy="${http_proxy}" \
+    --build-arg https_proxy="${https_proxy}" \
+    --build-arg no_proxy="${no_proxy}" \
+    --build-arg PIP_INDEX_URL="${PIP_INDEX_URL}" \
+    .

+ 12 - 6
docs/installation/installation.en.md

@@ -148,13 +148,16 @@ If your Docker version >= 19.03, please use:
 
 ```bash
 # For CPU
-docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.1.2-paddlepaddle3.0.0-cpu /bin/bash
+docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.3.4-paddlepaddle3.2.0-cpu /bin/bash
 
 # gpu,requires GPU driver version ≥450.80.02 (Linux) or ≥452.39 (Windows)
-docker run --gpus all --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.1.2-paddlepaddle3.0.0-gpu-cuda11.8-cudnn8.9-trt8.6 /bin/bash
+docker run --gpus all --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.3.4-paddlepaddle3.2.0-gpu-cuda11.8-cudnn8.9-trt8.6 /bin/bash
 
 # gpu,requires GPU driver version ≥545.23.06(Linux) or ≥545.84(Windows)
-docker run --gpus all --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.1.2-paddlepaddle3.0.0-gpu-cuda12.6-cudnn9.5-trt10.5 /bin/bash
+docker run --gpus all --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.3.4-paddlepaddle3.2.0-gpu-cuda12.6-cudnn9.5 /bin/bash
+
+# gpu,requires GPU driver version ≥550.xx
+docker run --gpus all --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.3.4-paddlepaddle3.2.0-gpu-cuda12.9-cudnn9.9 /bin/bash
 ```
 
 * If your Docker version <= 19.03 and >= 17.06, please use:
@@ -162,14 +165,17 @@ docker run --gpus all --name paddlex -v $PWD:/paddle --shm-size=8g --network=hos
 <details><summary> Click Here</summary>
 
 <pre><code class="language-bash"># For CPU
-docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.1.2-paddlepaddle3.0.0-cpu /bin/bash
+docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.3.4-paddlepaddle3.2.0-cpu /bin/bash
 
 # For GPU
 # gpu,requires GPU driver version ≥450.80.02 (Linux) or ≥452.39 (Windows)
-nvidia-docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.1.2-paddlepaddle3.0.0-gpu-cuda11.8-cudnn8.9-trt8.6 /bin/bash
+nvidia-docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.3.4-paddlepaddle3.2.0-gpu-cuda11.8-cudnn8.9-trt8.6 /bin/bash
 
 # gpu,requires GPU driver version ≥545.23.06(Linux) or ≥545.84(Windows)
-nvidia-docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.1.2-paddlepaddle3.0.0-gpu-cuda12.6-cudnn9.5-trt10.5 /bin/bash
+nvidia-docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.3.4-paddlepaddle3.2.0-gpu-cuda12.6-cudnn9.5 /bin/bash
+
+# gpu,requires GPU driver version ≥550.xx
+nvidia-docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.3.4-paddlepaddle3.2.0-gpu-cuda12.9-cudnn9.9 /bin/bash
 
 </code></pre></details>
 

+ 12 - 7
docs/installation/installation.md

@@ -155,14 +155,17 @@ paddlex --install PaddleXXX  # 例如PaddleOCR
 
 ```bash
 # 对于 CPU 用户
-docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.1.2-paddlepaddle3.0.0-cpu /bin/bash
+docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.3.4-paddlepaddle3.2.0-cpu /bin/bash
 
 # 对于 GPU 用户
 # GPU 版本,需显卡驱动程序版本 ≥450.80.02(Linux)或 ≥452.39(Windows)
-docker run --gpus all --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.1.2-paddlepaddle3.0.0-gpu-cuda11.8-cudnn8.9-trt8.6 /bin/bash
+docker run --gpus all --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.3.4-paddlepaddle3.2.0-gpu-cuda11.8-cudnn8.9-trt8.6 /bin/bash
 
 # GPU 版本,需显卡驱动程序版本 ≥545.23.06(Linux)或 ≥545.84(Windows)
-docker run --gpus all --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.1.2-paddlepaddle3.0.0-gpu-cuda12.6-cudnn9.5-trt10.5 /bin/bash
+docker run --gpus all --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.3.4-paddlepaddle3.2.0-gpu-cuda12.6-cudnn9.5 /bin/bash
+
+# GPU 版本,需显卡驱动程序版本 ≥550.xx
+docker run --gpus all --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.3.4-paddlepaddle3.2.0-gpu-cuda12.9-cudnn9.9 /bin/bash
 ```
 
 
@@ -171,14 +174,16 @@ docker run --gpus all --name paddlex -v $PWD:/paddle --shm-size=8g --network=hos
 <details><summary> 点击展开</summary>
 
 <pre><code class="language-bash"># 对于 CPU 用户
-docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.1.2-paddlepaddle3.0.0-cpu /bin/bash
+docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.3.4-paddlepaddle3.2.0-cpu /bin/bash
 
-# 对于 GPU 用户
 # GPU 版本,需显卡驱动程序版本 ≥450.80.02(Linux)或 ≥452.39(Windows)
-nvidia-docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.1.2-paddlepaddle3.0.0-gpu-cuda11.8-cudnn8.9-trt8.6 /bin/bash
+nvidia-docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.3.4-paddlepaddle3.2.0-gpu-cuda11.8-cudnn8.9-trt8.6 /bin/bash
 
 # GPU 版本,需显卡驱动程序版本 ≥545.23.06(Linux)或 ≥545.84(Windows)
-nvidia-docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.1.2-paddlepaddle3.0.0-gpu-cuda12.6-cudnn9.5-trt10.5 /bin/bash
+nvidia-docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.3.4-paddlepaddle3.2.0-gpu-cuda12.6-cudnn9.5 /bin/bash
+
+# GPU 版本,需显卡驱动程序版本 ≥550.xx
+nvidia-docker run --name paddlex -v $PWD:/paddle --shm-size=8g --network=host -it ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.3.4-paddlepaddle3.2.0-gpu-cuda12.9-cudnn9.9 /bin/bash
 
 </code></pre></details>
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 300 - 0
docs/pipeline_usage/tutorials/ocr_pipelines/PaddleOCR-VL.en.md


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 268 - 267
docs/pipeline_usage/tutorials/ocr_pipelines/PaddleOCR-VL.md


+ 1 - 1
paddlex/.version

@@ -1 +1 @@
-3.3.5
+3.3.6

+ 0 - 3
paddlex/inference/models/__init__.py

@@ -70,9 +70,6 @@ def create_predictor(
 
     if need_local_model(genai_config):
         if model_dir is None:
-            assert (
-                model_name in official_models
-            ), f"The model ({model_name}) is not supported! Please using directory of local model files or model name supported by PaddleX!"
             model_dir = official_models[model_name]
         else:
             assert Path(model_dir).exists(), f"{model_dir} is not exists!"

+ 21 - 5
paddlex/inference/models/common/tokenizer/tokenizer_utils.py

@@ -713,11 +713,27 @@ class ChatTemplateMixin:
                     "apply_chat_template do not support applying batch conversations, "
                     "so you should apply the conversation one by one."
                 )
-        query = self.chat_template.render(
-            messages=conversations,
-            **self.special_tokens_map,
-            add_generation_prompt=add_generation_prompt,
-        )
+        try:
+            query = self.chat_template.render(
+                messages=conversations,
+                **self.special_tokens_map,
+                add_generation_prompt=add_generation_prompt,
+            )
+        except TypeError:
+            for i in range(len(conversations)):
+                content = conversations[i]["content"]
+                if isinstance(content, list):
+                    new_content = ""
+                    for part in content:
+                        if part.get("type") == "text":
+                            new_content = part["text"]
+                            break
+                conversations[i]["content"] = new_content
+            query = self.chat_template.render(
+                messages=conversations,
+                **self.special_tokens_map,
+                add_generation_prompt=add_generation_prompt,
+            )
         return query
 
     def encode_chat_inputs(

+ 10 - 6
paddlex/inference/models/doc_vlm/modeling/paddleocr_vl/_config.py

@@ -26,6 +26,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from ......utils.device import parse_device
+from ......utils.env import get_paddle_cuda_version
 from ....common.vlm.transformers import PretrainedConfig
 
 
@@ -120,6 +122,8 @@ class PaddleOCRVLConfig(PretrainedConfig):
         vision_config=None,
         **kwargs,
     ):
+        import paddle
+
         # Set default for tied embeddings if not specified.
         super().__init__(
             pad_token_id=pad_token_id,
@@ -165,13 +169,13 @@ class PaddleOCRVLConfig(PretrainedConfig):
         super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
 
         # Currently, these configuration items are hard-coded
-        from ......utils.env import get_paddle_cuda_version
 
-        cuda_version = get_paddle_cuda_version()
-        if cuda_version and cuda_version[0] > 11:
-            self.fuse_rms_norm = True
-        else:
-            self.fuse_rms_norm = False
+        self.fuse_rms_norm = False
+        device_type, _ = parse_device(paddle.device.get_device())
+        if device_type == "gpu":
+            cuda_version = get_paddle_cuda_version()
+            if cuda_version and cuda_version[0] > 11:
+                self.fuse_rms_norm = True
         self.use_sparse_flash_attn = True
         self.use_var_len_flash_attn = False
         self.scale_qk_coeff = 1.0

+ 0 - 43
paddlex/inference/models/doc_vlm/predictor.py

@@ -370,46 +370,6 @@ class DocVLMPredictor(BasePredictor):
         }
         return rst_dict
 
-    def crop_margin(self, img):  # 输入是OpenCV图像 (numpy数组)
-        import cv2
-
-        # 如果输入是彩色图像,转换为灰度图
-        if len(img.shape) == 3:
-            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
-        else:
-            gray = img.copy()
-
-        # 转换为0-255范围(确保是uint8类型)
-        if gray.dtype != np.uint8:
-            gray = gray.astype(np.uint8)
-
-        max_val = gray.max()
-        min_val = gray.min()
-
-        if max_val == min_val:
-            return img
-
-        # 归一化并二值化(与PIL版本逻辑一致)
-        data = (gray - min_val) / (max_val - min_val) * 255
-        data = data.astype(np.uint8)
-
-        # 创建二值图像(暗色区域为白色,亮色区域为黑色)
-        _, binary = cv2.threshold(data, 200, 255, cv2.THRESH_BINARY_INV)
-
-        # 查找非零像素坐标
-        coords = cv2.findNonZero(binary)
-
-        if coords is None:  # 如果没有找到任何内容,返回原图
-            return img
-
-        # 获取边界框
-        x, y, w, h = cv2.boundingRect(coords)
-
-        # 裁剪图像
-        cropped = img[y : y + h, x : x + w]
-
-        return cropped
-
     def _genai_client_process(
         self,
         data,
@@ -425,9 +385,6 @@ class DocVLMPredictor(BasePredictor):
 
         def _process(item):
             image = item["image"]
-            prompt = item["query"]
-            if prompt == "Formula Recognition:":
-                image = self.crop_margin(image)
             if isinstance(image, str):
                 if image.startswith("http://") or image.startswith("https://"):
                     image_url = image

+ 4 - 1
paddlex/inference/models/doc_vlm/processors/paddleocr_vl/_paddleocr_vl.py

@@ -82,7 +82,10 @@ class PaddleOCRVLProcessor(object):
             messages = [
                 {
                     "role": "user",
-                    "content": input_dict["query"],
+                    "content": [
+                        {"type": "image", "image": "placeholder"},  # placeholder
+                        {"type": "text", "text": input_dict["query"]},
+                    ],
                 }
             ]
             prompt = self.tokenizer.apply_chat_template(messages, tokenize=False)

+ 2 - 0
paddlex/inference/pipelines/paddleocr_vl/pipeline.py

@@ -35,6 +35,7 @@ from ..layout_parsing.utils import gather_imgs
 from .result import PaddleOCRVLBlock, PaddleOCRVLResult
 from .uilts import (
     convert_otsl_to_html,
+    crop_margin,
     filter_overlap_boxes,
     merge_blocks,
     tokenize_figure_of_table,
@@ -243,6 +244,7 @@ class _PaddleOCRVLPipeline(BasePipeline):
                         text_prompt = "Chart Recognition:"
                     elif "formula" in block_label and block_label != "formula_number":
                         text_prompt = "Formula Recognition:"
+                        block_img = crop_margin(block_img)
                     block_imgs.append(block_img)
                     text_prompts.append(text_prompt)
                     figure_token_maps.append(figure_token_map)

+ 10 - 0
paddlex/inference/pipelines/paddleocr_vl/result.py

@@ -324,6 +324,16 @@ class PaddleOCRVLResult(BaseCVResult, HtmlMixin, XlsxMixin, MarkdownMixin):
             data["doc_preprocessor_res"] = self["doc_preprocessor_res"].str["res"]
         if self["model_settings"]["use_layout_detection"]:
             data["layout_det_res"] = self["layout_det_res"].str["res"]
+        parsing_res_list = self["parsing_res_list"]
+        parsing_res_list = [
+            {
+                "block_label": parsing_res.label,
+                "block_content": parsing_res.content,
+                "block_bbox": parsing_res.bbox,
+            }
+            for parsing_res in parsing_res_list
+        ]
+        data["parsing_res_list"] = parsing_res_list
         return JsonMixin._to_str(data, *args, **kwargs)
 
     def _to_json(self, *args, **kwargs) -> dict[str, str]:

+ 36 - 4
paddlex/inference/pipelines/paddleocr_vl/uilts.py

@@ -18,7 +18,7 @@ import math
 import re
 from collections import Counter
 from copy import deepcopy
-from typing import Any, Dict, List, Tuple
+from typing import Any, Dict, List, Tuple, Union
 
 import numpy as np
 from PIL import Image
@@ -829,7 +829,7 @@ def convert_otsl_to_html(otsl_content: str):
     return export_to_html(table_data)
 
 
-def find_shortest_repeating_substring(s: str) -> str | None:
+def find_shortest_repeating_substring(s: str) -> Union[str, None]:
     """
     Find the shortest substring that repeats to form the entire string.
 
@@ -850,7 +850,7 @@ def find_shortest_repeating_substring(s: str) -> str | None:
 
 def find_repeating_suffix(
     s: str, min_len: int = 8, min_repeats: int = 5
-) -> Tuple[str, str, int] | None:
+) -> Union[Tuple[str, str, int], None]:
     """
     Detect if string ends with a repeating phrase.
 
@@ -888,7 +888,7 @@ def truncate_repetitive_content(
         min_len (int): Min length for char-level check.
 
     Returns:
-        Tuple[str, str]: (truncated_content, info_string)
+        Union[str, str]: (truncated_content, info_string)
     """
     stripped_content = content.strip()
     if not stripped_content:
@@ -923,3 +923,35 @@ def truncate_repetitive_content(
         return most_common_line
 
     return content
+
+
+def crop_margin(img):
+    import cv2
+
+    if len(img.shape) == 3:
+        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
+    else:
+        gray = img.copy()
+
+    if gray.dtype != np.uint8:
+        gray = gray.astype(np.uint8)
+
+    max_val = gray.max()
+    min_val = gray.min()
+
+    if max_val == min_val:
+        return img
+
+    data = (gray - min_val) / (max_val - min_val) * 255
+    data = data.astype(np.uint8)
+
+    _, binary = cv2.threshold(data, 200, 255, cv2.THRESH_BINARY_INV)
+    coords = cv2.findNonZero(binary)
+
+    if coords is None:
+        return img
+
+    x, y, w, h = cv2.boundingRect(coords)
+    cropped = img[y : y + h, x : x + w]
+
+    return cropped

+ 35 - 32
paddlex/inference/utils/official_models.py

@@ -45,7 +45,7 @@ ALL_MODELS = [
     "ResNet152",
     "ResNet152_vd",
     "ResNet200_vd",
-    "PaddleOCR-VL-0.9B",
+    "PaddleOCR-VL",
     "PP-LCNet_x0_25",
     "PP-LCNet_x0_25_textline_ori",
     "PP-LCNet_x0_35",
@@ -345,7 +345,7 @@ OCR_MODELS = [
     "en_PP-OCRv5_mobile_rec",
     "th_PP-OCRv5_mobile_rec",
     "el_PP-OCRv5_mobile_rec",
-    "PaddleOCR-VL-0.9B",
+    "PaddleOCR-VL",
     "PicoDet_layout_1x",
     "PicoDet_layout_1x_table",
     "PicoDet-L_layout_17cls",
@@ -419,27 +419,15 @@ class _BaseModelHoster(ABC):
         assert (
             model_name in self.model_list
         ), f"The model {model_name} is not supported on hosting {self.__class__.__name__}!"
-        if model_name == "PaddleOCR-VL-0.9B":
-            model_name = "PaddleOCR-VL"
 
         model_dir = self._save_dir / f"{model_name}"
-        if os.path.exists(model_dir):
-            logging.info(
-                f"Model files already exist. Using cached files. To redownload, please delete the directory manually: `{model_dir}`."
-            )
-        else:
-            logging.info(
-                f"Using official model ({model_name}), the model files will be automatically downloaded and saved in `{model_dir}`."
-            )
-            self._download(model_name, model_dir)
-            logging.debug(
-                f"`{model_name}` model files has been download from model source: `{self.alias}`!"
-            )
-
-        if model_name == "PaddleOCR-VL":
-            vl_model_dir = model_dir / "PaddleOCR-VL-0.9B"
-            if vl_model_dir.exists() and vl_model_dir.is_dir():
-                return vl_model_dir
+        logging.info(
+            f"Using official model ({model_name}), the model files will be automatically downloaded and saved in `{model_dir}`."
+        )
+        self._download(model_name, model_dir)
+        logging.debug(
+            f"`{model_name}` model files has been download from model source: `{self.alias}`!"
+        )
 
         return model_dir
 
@@ -573,21 +561,33 @@ class _ModelManager:
                     hosters.append(hoster_cls(self._save_dir))
         if len(hosters) == 0:
             logging.warning(
-                f"""No model hoster is available! Please check your network connection to one of the following model hosts:
-HuggingFace ({_HuggingFaceModelHoster.healthcheck_url}),
-ModelScope ({_ModelScopeModelHoster.healthcheck_url}),
-AIStudio ({_AIStudioModelHoster.healthcheck_url}), or
-BOS ({_BosModelHoster.healthcheck_url}).
-Otherwise, only local models can be used."""
+                f"No model hoster is available! Please check your network connection to one of the following model hosts: HuggingFace ({_HuggingFaceModelHoster.healthcheck_url}), ModelScope ({_ModelScopeModelHoster.healthcheck_url}), AIStudio ({_AIStudioModelHoster.healthcheck_url}), or BOS ({_BosModelHoster.healthcheck_url}). Otherwise, only local models can be used."
             )
         return hosters
 
     def _get_model_local_path(self, model_name):
-        if len(self._hosters) == 0:
-            msg = "No available model hosting platforms detected. Please check your network connection."
-            logging.error(msg)
-            raise Exception(msg)
-        return self._download_from_hoster(self._hosters, model_name)
+        if model_name == "PaddleOCR-VL-0.9B":
+            model_name = "PaddleOCR-VL"
+
+        model_dir = self._save_dir / f"{model_name}"
+        if os.path.exists(model_dir):
+            logging.info(
+                f"Model files already exist. Using cached files. To redownload, please delete the directory manually: `{model_dir}`."
+            )
+        else:
+            if len(self._hosters) == 0:
+                msg = "No available model hosting platforms detected. Please check your network connection."
+                logging.error(msg)
+                raise Exception(msg)
+
+            model_dir = self._download_from_hoster(self._hosters, model_name)
+
+        if model_name == "PaddleOCR-VL":
+            vl_model_dir = model_dir / "PaddleOCR-VL-0.9B"
+            if vl_model_dir.exists() and vl_model_dir.is_dir():
+                return vl_model_dir
+
+        return model_dir
 
     def _download_from_hoster(self, hosters, model_name):
         for idx, hoster in enumerate(hosters):
@@ -605,6 +605,9 @@ Otherwise, only local models can be used."""
                         f"Encountering exception when download model from {hoster.alias}: \n{e}, will try to download from other model sources: `{hosters[idx + 1].alias}`."
                     )
                     return self._download_from_hoster(hosters[idx + 1 :], model_name)
+        raise Exception(
+            f"No model source is available for model `{model_name}`! Please check model name and network, or use local model files!"
+        )
 
     def __contains__(self, model_name):
         return model_name in self.model_list

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio