Skip to content

Conversation

@codeflash-ai
Copy link

@codeflash-ai codeflash-ai bot commented Oct 28, 2025

📄 9% (0.09x) speedup for EasyOCRBlockV1.run in inference/core/workflows/core_steps/models/foundation/easy_ocr/v1.py

⏱️ Runtime : 10.1 microseconds 9.27 microseconds (best of 38 runs)

📝 Explanation and details

Optimizations applied:

  • For run_locally:
    • Reduced repeated lookups and indirections in the loop by pre-binding .append and constructing requests as a list upfront before inference. Moved method lookups (to_inference_format, model_dump) and all instance lookups outside the loop where possible.
    • Maintained original batching logic, but all method calls and attribute accesses are minimized, making the loop slightly faster (especially for large batches).
  • For run_remotely.
    • More efficient list comprehension for base64 image extraction.
    • The initialization and method calls for client are outside any per-image processing, ensuring efficiency.

Behavior and side-effects are fully preserved. Variable naming, comments, function signatures, and exception raising are unmodified.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 15 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 75.0%
🌀 Generated Regression Tests and Runtime
from enum import Enum
from typing import Any, Dict, List, Optional

# imports
import pytest
from inference.core.workflows.core_steps.models.foundation.easy_ocr.v1 import \
    EasyOCRBlockV1

# --- Minimal stubs and mocks for dependencies ---

# StepExecutionMode enum
class StepExecutionMode(Enum):
    LOCAL = "local"
    REMOTE = "remote"

# Dummy ModelManager
class DummyModelManager:
    def __init__(self):
        self.infer_calls = []

    def infer_from_request_sync(self, model_id, request):
        # Return a mock response with the same fields as expected
        # Simulate an OCR result with a dummy prediction
        return DummyInferenceResponse(
            {
                "predictions": [
                    {"detection_id": 1, "text": "dummy", "bbox": [0, 0, 1, 1]}
                ],
                "result": "ok",
                "parent_id": "pid",
                "root_parent_id": "rpid",
                "prediction_type": "ocr",
            }
        )

# Dummy Inference Response
class DummyInferenceResponse:
    def __init__(self, d: dict):
        self._d = d
    def model_dump(self, by_alias=True, exclude_none=True):
        return self._d.copy()

# --- Entities for images and batches ---
class DummyParentMetadata:
    def __init__(self, parent_id):
        self.parent_id = parent_id

class WorkflowImageData:
    def __init__(
        self,
        parent_metadata,
        workflow_root_ancestor_metadata=None,
        image_reference=None,
        base64_image=None,
        numpy_image=None,
        video_metadata=None,
    ):
        if not base64_image and numpy_image is None and not image_reference:
            raise ValueError("Could not initialise empty `WorkflowImageData`.")
        self.parent_metadata = parent_metadata
        self.workflow_root_ancestor_metadata = (
            workflow_root_ancestor_metadata or self.parent_metadata
        )
        self.image_reference = image_reference
        self.base64_image = base64_image
        self.numpy_image = numpy_image
        self.video_metadata = video_metadata

    def to_inference_format(self, numpy_preferred=False):
        if numpy_preferred:
            return {"type": "numpy_object", "value": self.numpy_image}
        if self.image_reference:
            if self.image_reference.startswith("http://") or self.image_reference.startswith("https://"):
                return {"type": "url", "value": self.image_reference}
            return {"type": "file", "value": self.image_reference}
        if self.base64_image:
            return {"type": "base64", "value": self.base64_image}
        return {"type": "numpy_object", "value": self.numpy_image}

MODELS = {
    "English": ("english_g2", ["en"]),
    "Japanese": ("japanese_g2", ["en", "ja"]),
    "Kannada": ("kannada_g2", ["en", "kn"]),
    "Korean": ("korean_g2", ["en", "ko"]),
    "Latin": ("latin_g2", ["en", "la", "es", "fr", "it", "pt", "de", "pl", "nl"]),
    "Telugu": ("telugu_g2", ["en", "te"]),
    "Simplified Chinese": ("zh_sim_g2", ["en", "ch_sim"]),
}

EXPECTED_OUTPUT_KEYS = {
    "result",
    "parent_id",
    "root_parent_id",
    "prediction_type",
    "predictions",
}

# --- Fixtures for reusable test data ---

@pytest.fixture
def dummy_model_manager():
    return DummyModelManager()

@pytest.fixture
def dummy_image():
    # Provide a valid WorkflowImageData with base64_image
    return WorkflowImageData(
        parent_metadata=DummyParentMetadata("pid"),
        workflow_root_ancestor_metadata=DummyParentMetadata("rpid"),
        base64_image="ZmFrZV9pbWFnZQ=="
    )

@pytest.fixture
def dummy_images(dummy_image):
    # Return a list of 3 images
    return [dummy_image, dummy_image, dummy_image]

@pytest.fixture
def easyocr_block_local(dummy_model_manager):
    return EasyOCRBlockV1(
        model_manager=dummy_model_manager,
        api_key="dummy_key",
        step_execution_mode=StepExecutionMode.LOCAL,
    )

@pytest.fixture
def easyocr_block_remote(dummy_model_manager):
    return EasyOCRBlockV1(
        model_manager=dummy_model_manager,
        api_key="dummy_key",
        step_execution_mode=StepExecutionMode.REMOTE,
    )

# --- Basic Test Cases ---








def test_run_invalid_language_raises(easyocr_block_local, dummy_image):
    """Test that an unsupported language raises ValueError."""
    with pytest.raises(ValueError) as excinfo:
        easyocr_block_local.run([dummy_image], language="Martian") # 1.47μs -> 1.44μs (2.08% faster)

def test_run_invalid_execution_mode(dummy_model_manager, dummy_image):
    """Test that an unknown execution mode raises ValueError."""
    block = EasyOCRBlockV1(
        model_manager=dummy_model_manager,
        api_key="dummy_key",
        step_execution_mode="UNKNOWN_MODE",
    )
    with pytest.raises(ValueError) as excinfo:
        block.run([dummy_image]) # 2.06μs -> 1.63μs (26.8% faster)



def test_run_image_with_missing_all_fields_raises():
    """Test image with no image data raises ValueError."""
    with pytest.raises(ValueError):
        WorkflowImageData(
            parent_metadata=DummyParentMetadata("pid"),
        )


def test_run_local_language_case_sensitivity(easyocr_block_local, dummy_image):
    """Test that language name is case sensitive and fails for wrong case."""
    with pytest.raises(ValueError):
        easyocr_block_local.run([dummy_image], language="english") # 1.49μs -> 1.49μs (0.067% faster)

def test_run_local_language_whitespace(easyocr_block_local, dummy_image):
    """Test that language name with extra whitespace is rejected."""
    with pytest.raises(ValueError):
        easyocr_block_local.run([dummy_image], language=" English ") # 1.30μs -> 1.22μs (6.90% faster)

# --- Large Scale Test Cases ---





#------------------------------------------------
from enum import Enum
from typing import Any, Dict, List, Optional

# imports
import pytest
from inference.core.workflows.core_steps.models.foundation.easy_ocr.v1 import \
    EasyOCRBlockV1

# --- Minimal stubs and mocks for dependencies ---

# StepExecutionMode Enum
class StepExecutionMode(Enum):
    LOCAL = "local"
    REMOTE = "remote"

# Mock ModelManager with deterministic behavior
class DummyModelManager:
    def __init__(self):
        self.infer_calls = []

    def infer_from_request_sync(self, model_id, request):
        # Return a deterministic fake OCR result
        return DummyInferenceResponse(
            {
                "predictions": [
                    {"text": "hello", "detection_id": 1},
                    {"text": "world", "detection_id": 2},
                ],
                "result": "success",
                "parent_id": "parent-123",
                "root_parent_id": "root-123",
                "prediction_type": "ocr",
            }
        )

class DummyInferenceResponse:
    def __init__(self, data):
        self._data = data

    def model_dump(self, by_alias=True, exclude_none=True):
        return self._data

# --- WorkflowImageData and Batch ---

class DummyParentMetadata:
    def __init__(self, parent_id):
        self.parent_id = parent_id

class WorkflowImageData:
    def __init__(
        self,
        parent_metadata,
        workflow_root_ancestor_metadata=None,
        image_reference=None,
        base64_image=None,
        numpy_image=None,
        video_metadata=None,
    ):
        # At least one of these must be present
        if not base64_image and numpy_image is None and not image_reference:
            raise ValueError("Could not initialise empty `WorkflowImageData`.")
        self.parent_metadata = parent_metadata
        self.workflow_root_ancestor_metadata = (
            workflow_root_ancestor_metadata or parent_metadata
        )
        self.image_reference = image_reference
        self.base64_image = base64_image
        self.numpy_image = numpy_image
        self.video_metadata = video_metadata

    def to_inference_format(self, numpy_preferred=False):
        # Return a dict describing the image input
        if numpy_preferred:
            return {"type": "numpy_object", "value": self.numpy_image}
        if self.image_reference:
            if self.image_reference.startswith("http://") or self.image_reference.startswith("https://"):
                return {"type": "url", "value": self.image_reference}
            return {"type": "file", "value": self.image_reference}
        if self.base64_image:
            return {"type": "base64", "value": self.base64_image}
        return {"type": "numpy_object", "value": self.numpy_image}

MODELS = {
    "English": ("english_g2", ["en"]),
    "Japanese": ("japanese_g2", ["en", "ja"]),
    "Kannada": ("kannada_g2", ["en", "kn"]),
    "Korean": ("korean_g2", ["en", "ko"]),
    "Latin": ("latin_g2", ["en", "la", "es", "fr", "it", "pt", "de", "pl", "nl"]),
    "Telugu": ("telugu_g2", ["en", "te"]),
    "Simplified Chinese": ("zh_sim_g2", ["en", "ch_sim"]),
}

EXPECTED_OUTPUT_KEYS = {
    "result",
    "parent_id",
    "root_parent_id",
    "prediction_type",
    "predictions",
}

# --- Helper for test image data ---
def make_image(
    parent_id="parent-123",
    root_parent_id=None,
    image_reference=None,
    base64_image="ZmFrZWltYWdlYmFzZTY0",  # fake base64
    numpy_image=None,
):
    parent = DummyParentMetadata(parent_id)
    root = DummyParentMetadata(root_parent_id) if root_parent_id else parent
    return WorkflowImageData(
        parent_metadata=parent,
        workflow_root_ancestor_metadata=root,
        image_reference=image_reference,
        base64_image=base64_image,
        numpy_image=numpy_image,
    )

# --- TESTS ---

# 1. BASIC TEST CASES





def test_run_invalid_language():
    """Test that ValueError is raised for unsupported language."""
    block = EasyOCRBlockV1(DummyModelManager(), api_key="key", step_execution_mode=StepExecutionMode.LOCAL)
    images = [make_image()]
    with pytest.raises(ValueError) as e:
        block.run(images, language="Martian") # 1.48μs -> 1.46μs (1.44% faster)




def test_run_unknown_step_execution_mode():
    """Test that ValueError is raised for unknown step execution mode."""
    block = EasyOCRBlockV1(DummyModelManager(), api_key="key", step_execution_mode="UNKNOWN")
    images = [make_image()]
    with pytest.raises(ValueError) as e:
        block.run(images) # 2.29μs -> 2.04μs (12.2% faster)

def test_workflowimagedata_empty_raises():
    """Test that WorkflowImageData raises ValueError if no image data is provided."""
    with pytest.raises(ValueError):
        WorkflowImageData(parent_metadata=DummyParentMetadata("p"))

To edit these changes git checkout codeflash/optimize-EasyOCRBlockV1.run-mha107ec and push.

Codeflash

**Optimizations applied:**
- For `run_locally`: 
    - Reduced repeated lookups and indirections in the loop by pre-binding `.append` and constructing requests as a list upfront before inference. Moved method lookups (`to_inference_format`, `model_dump`) and all instance lookups outside the loop where possible.
    - Maintained original batching logic, but all method calls and attribute accesses are minimized, making the loop slightly faster (especially for large batches).
- For `run_remotely`.
    - More efficient list comprehension for base64 image extraction.
    - The initialization and method calls for client are outside any per-image processing, ensuring efficiency.

**Behavior and side-effects are fully preserved. Variable naming, comments, function signatures, and exception raising are unmodified.**
@codeflash-ai codeflash-ai bot requested a review from mashraf-222 October 28, 2025 03:47
@codeflash-ai codeflash-ai bot added the ⚡️ codeflash Optimization PR opened by Codeflash AI label Oct 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant