Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

inference-cli with batch processing for Workflows #838

Merged
merged 20 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
dd1fa44
WIP
PawelPeczek-Roboflow Nov 25, 2024
86cecaa
Add first working version of video processing
PawelPeczek-Roboflow Nov 26, 2024
33160af
Add commands to execute batch processing on directory of images
PawelPeczek-Roboflow Nov 26, 2024
8a36892
Fix import issues
PawelPeczek-Roboflow Nov 26, 2024
42ff3e7
Merge branch 'main' into feature/inference_cli_with_workflows
PawelPeczek-Roboflow Nov 27, 2024
22001c8
Fix requirements
PawelPeczek-Roboflow Nov 27, 2024
469a4de
Fix requirements
PawelPeczek-Roboflow Nov 27, 2024
fbaba5a
Add basic test on parsing workflows parameters
PawelPeczek-Roboflow Nov 27, 2024
ef72b3a
Add tests to check workflow params parsing
PawelPeczek-Roboflow Nov 27, 2024
120dfa6
Merge branch 'main' into feature/inference_cli_with_workflows
PawelPeczek-Roboflow Nov 27, 2024
ee5c494
Add threshold for erros in API speed benchmark
PawelPeczek-Roboflow Nov 27, 2024
b284abb
Add tests for part of common utils for workflows command
PawelPeczek-Roboflow Nov 27, 2024
e26727a
Add tests for common utils
PawelPeczek-Roboflow Nov 27, 2024
f669696
Add new tests suiteS
PawelPeczek-Roboflow Nov 27, 2024
73e8386
Correct tests
PawelPeczek-Roboflow Nov 27, 2024
39afe60
Reduce size of assets for tests
PawelPeczek-Roboflow Nov 27, 2024
8c34827
Add docs
PawelPeczek-Roboflow Nov 27, 2024
b230a02
Modify docs and remove security issue
PawelPeczek-Roboflow Nov 28, 2024
d9f4359
Merge branch 'main' into feature/inference_cli_with_workflows
PawelPeczek-Roboflow Nov 28, 2024
fdac101
Merge branch 'main' into feature/inference_cli_with_workflows
PawelPeczek-Roboflow Nov 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: INTEGRATION TESTS - inference CLI + inference CORE

on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch:

jobs:
call_is_mergeable:
uses: ./.github/workflows/is_mergeable.yml
secrets: inherit
build-dev-test:
needs: call_is_mergeable
if: ${{ github.event_name != 'pull_request' || needs.call_is_mergeable.outputs.mergeable_state != 'not_clean' }}
runs-on:
labels: depot-ubuntu-22.04-small
group: public-depot
timeout-minutes: 30
strategy:
matrix:
python-version: ["3.9", "3.10"]
steps:
- name: 🛎️ Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- name: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
check-latest: true
- name: 📦 Cache Python packages
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('requirements/**') }}
restore-keys: |
${{ runner.os }}-pip-${{ matrix.python-version }}-
- name: 📦 Install dependencies
run: |
python -m pip install --upgrade pip
pip install --upgrade setuptools
pip install --extra-index-url https://download.pytorch.org/whl/cpu -r requirements/_requirements.txt -r requirements/requirements.cpu.txt -r requirements/requirements.sdk.http.txt -r requirements/requirements.test.unit.txt -r requirements/requirements.http.txt -r requirements/requirements.yolo_world.txt -r requirements/requirements.doctr.txt -r requirements/requirements.sam.txt -r requirements/requirements.transformers.txt -r requirements/requirements.cli.txt -r requirements/requirements.sdk.http.txt
- name: 🧪 Integration Tests of Inference CLI
run: RUN_TESTS_WITH_INFERENCE_PACKAGE=True INFERENCE_CLI_TESTS_API_KEY=${{ secrets.LOAD_TEST_PRODUCTION_API_KEY }} python -m pytest tests/inference_cli/integration_tests/test_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ jobs:
pip install --upgrade setuptools
pip install -r requirements/requirements.cli.txt -r requirements/requirements.sdk.http.txt -r requirements/requirements.test.unit.txt
- name: 🧪 Integration Tests of Inference CLI
run: python -m pytest tests/inference_cli/integration_tests
run: RUN_TESTS_EXPECTING_ERROR_WHEN_INFERENCE_NOT_INSTALLED=True INFERENCE_CLI_TESTS_API_KEY=${{ secrets.LOAD_TEST_PRODUCTION_API_KEY }} python -m pytest tests/inference_cli/integration_tests
17 changes: 9 additions & 8 deletions .github/workflows/load_test_hosted_inference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,35 +51,36 @@ jobs:
- name: 🏋️‍♂️ Load test 🚨 PRODUCTION 🚨 | object-detection 🔥🔥🔥🔥
if: ${{ github.event.inputs.environment == 'production' && github.event.inputs.model_type == 'object-detection' }}
run: |
ROBOFLOW_API_KEY=${{ secrets.LOAD_TEST_PRODUCTION_API_KEY }} python -m inference_cli.main benchmark api-speed -m coco/16 -d coco -rps 5 -br 500 -h https://detect.roboflow.com --yes --output_location test_results.json
ROBOFLOW_API_KEY=${{ secrets.LOAD_TEST_PRODUCTION_API_KEY }} python -m inference_cli.main benchmark api-speed -m coco/16 -d coco -rps 5 -br 500 -h https://detect.roboflow.com --yes --output_location test_results.json --max_error_rate 5.0
- name: 🏋️‍♂️ Load test 🚨 PRODUCTION 🚨 | instance-segmentation 🔥🔥🔥🔥
if: ${{ github.event.inputs.environment == 'production' && github.event.inputs.model_type == 'instance-segmentation' }}
run: |
ROBOFLOW_API_KEY=${{ secrets.LOAD_TEST_PRODUCTION_API_KEY }} python -m inference_cli.main benchmark api-speed -m asl-poly-instance-seg/53 -d coco -rps 5 -br 500 -h https://outline.roboflow.com --yes --output_location test_results.json
ROBOFLOW_API_KEY=${{ secrets.LOAD_TEST_PRODUCTION_API_KEY }} python -m inference_cli.main benchmark api-speed -m asl-poly-instance-seg/53 -d coco -rps 5 -br 500 -h https://outline.roboflow.com --yes --output_location test_results.json --max_error_rate 5.0
- name: 🏋️‍♂️ Load test 🚨 PRODUCTION 🚨 | classification 🔥🔥🔥🔥
if: ${{ github.event.inputs.environment == 'production' && github.event.inputs.model_type == 'classification' }}
run: |
ROBOFLOW_API_KEY=${{ secrets.LOAD_TEST_PRODUCTION_API_KEY }} python -m inference_cli.main benchmark api-speed -m vehicle-classification-eapcd/2 -d coco -rps 5 -br 500 -h https://classify.roboflow.com --yes --output_location test_results.json
ROBOFLOW_API_KEY=${{ secrets.LOAD_TEST_PRODUCTION_API_KEY }} python -m inference_cli.main benchmark api-speed -m vehicle-classification-eapcd/2 -d coco -rps 5 -br 500 -h https://classify.roboflow.com --yes --output_location test_results.json --max_error_rate 5.0
- name: 🏋️‍♂️ Load test 🚨 PRODUCTION 🚨 | workflows 🔥🔥🔥🔥
if: ${{ github.event.inputs.environment == 'production' && github.event.inputs.model_type == 'workflows' }}
run: |
ROBOFLOW_API_KEY=${{ secrets.LOAD_TEST_PRODUCTION_API_KEY }} python -m inference_cli.main benchmark api-speed -wid workflows-production-test -wn paul-guerrie-tang1 -d coco -rps 5 -br 500 -h https://classify.roboflow.com --yes --output_location test_results.json
ROBOFLOW_API_KEY=${{ secrets.LOAD_TEST_PRODUCTION_API_KEY }} python -m inference_cli.main benchmark api-speed -wid workflows-production-test -wn paul-guerrie-tang1 -d coco -rps 5 -br 500 -h https://classify.roboflow.com --yes --output_location test_results.json --max_error_rate 5.0

- name: 🏋️‍♂️ Load test 😎 STAGING 😎 | object-detection 🔥🔥🔥🔥
if: ${{ github.event.inputs.environment == 'staging' && github.event.inputs.model_type == 'object-detection' }}
run: |
ROBOFLOW_API_KEY=${{ secrets.LOAD_TEST_STAGING_API_KEY }} python -m inference_cli.main benchmark api-speed -m eye-detection/35 -d coco -rps 5 -br 500 -h https://lambda-object-detection.staging.roboflow.com --legacy-endpoints --yes --output_location test_results.json
ROBOFLOW_API_KEY=${{ secrets.LOAD_TEST_STAGING_API_KEY }} python -m inference_cli.main benchmark api-speed -m eye-detection/35 -d coco -rps 5 -br 500 -h https://lambda-object-detection.staging.roboflow.com --legacy-endpoints --yes --output_location test_results.json --max_error_rate 5.0
- name: 🏋️‍♂️ Load test 😎 STAGING 😎 | instance-segmentation 🔥🔥🔥🔥
if: ${{ github.event.inputs.environment == 'staging' && github.event.inputs.model_type == 'instance-segmentation' }}
run: |
ROBOFLOW_API_KEY=${{ secrets.LOAD_TEST_STAGING_API_KEY }} python -m inference_cli.main benchmark api-speed -m asl-instance-seg/116 -d coco -rps 5 -br 500 -h https://lambda-instance-segmentation.staging.roboflow.com --legacy-endpoints --yes --output_location test_results.json
ROBOFLOW_API_KEY=${{ secrets.LOAD_TEST_STAGING_API_KEY }} python -m inference_cli.main benchmark api-speed -m asl-instance-seg/116 -d coco -rps 5 -br 500 -h https://lambda-instance-segmentation.staging.roboflow.com --legacy-endpoints --yes --output_location test_results.json --max_error_rate 5.0
- name: 🏋️‍♂️ Load test 😎 STAGING 😎 | classification 🔥🔥🔥🔥
if: ${{ github.event.inputs.environment == 'staging' && github.event.inputs.model_type == 'classification' }}
run: |
ROBOFLOW_API_KEY=${{ secrets.LOAD_TEST_STAGING_API_KEY }} python -m inference_cli.main benchmark api-speed -m catdog/28 -d coco -rps 5 -br 500 -h https://lambda-classification.staging.roboflow.com --legacy-endpoints --yes --output_location test_results.json
ROBOFLOW_API_KEY=${{ secrets.LOAD_TEST_STAGING_API_KEY }} python -m inference_cli.main benchmark api-speed -m catdog/28 -d coco -rps 5 -br 500 -h https://lambda-classification.staging.roboflow.com --legacy-endpoints --yes --output_location test_results.json --max_error_rate 5.0
- name: 🏋️‍♂️ Load test 😎 STAGING 😎 | workflows 🔥🔥🔥🔥
if: ${{ github.event.inputs.environment == 'staging' && github.event.inputs.model_type == 'workflows' }}
run: |
ROBOFLOW_API_KEY=${{ secrets.LOAD_TEST_STAGING_API_KEY }} python -m inference_cli.main benchmark api-speed -wid workflows-staging-test -wn paul-guerrie -d coco -rps 5 -br 500 -h https://lambda-classification.staging.roboflow.com --legacy-endpoints --yes --output_location test_results.json
ROBOFLOW_API_KEY=${{ secrets.LOAD_TEST_STAGING_API_KEY }} python -m inference_cli.main benchmark api-speed -wid workflows-staging-test -wn paul-guerrie -d coco -rps 5 -br 500 -h https://lambda-classification.staging.roboflow.com --legacy-endpoints --yes --output_location test_results.json --max_error_rate 5.0
- name: 📈 RESULTS
run: cat test_results.json | jq
if: always()
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,6 @@ docs/workflows/gallery/*
!tests/workflows/integration_tests/execution/assets/rock_paper_scissors/*.jpg
!tests/workflows/unit_tests/core_steps/models/third_party/assets/*.png
!tests/workflows/integration_tests/execution/assets/*.png

tests/inference_cli/integration_tests/assets/test_images/
inference_profiling
tests/inference_sdk/unit_tests/http/inference_profiling
86 changes: 86 additions & 0 deletions docs/inference_helpers/inference_cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,92 @@ option can be used (and `-c` will be ignored). Value provided in `--rps` option
are to be spawned **each second** without waiting for previous requests to be handled. In I/O intensive benchmark
scenarios - we suggest running command from multiple separate processes and possibly multiple hosts.

### inference workflows

In release `0.29.0`, `inference-cli` was extended with command to process data using Workflows. It is possible to
process:

* individual images

* directories of images

* video files

#### Processing individual image

Basic usage of the command is illustrated below:

```bash
inference workflows process-image \
--image_path {your-input-image} \
--output_dir {your-output-dir} \
--workspace_name {your-roboflow-workspace-url} \
--workflow_id {your-workflow-id} \
--api-key {your_roboflow_api_key}
```

which would take your input image, run it against your Workflow and save results in output directory. By default,
Workflow will be processed using Roboflow Hosted API. You can tweak behaviour of the command:

* if you want to process the image locally, using `inference` Python package - use
`--processing_target inference_package` option (*requires `inference` to be installed*)

* to see all options, use `inference workflows process-image --help` command

* any option that starts from `--` which is not enlisted in `--help` command will be treated as input parameter
to the workflow execution


#### Processing directory of images

Basic usage of the command is illustrated below:

```bash
inference workflows process-images-directory \
-i {your_input_directory} \
-o {your_output_directory} \
--workspace_name {your-roboflow-workspace-url} \
--workflow_id {your-workflow-id} \
--api-key {your_roboflow_api_key}
```

You can tweak behaviour of the command:

* if you want to process the image locally, using `inference` Python package - use
`--processing_target inference_package` option (*requires `inference` to be installed*)

* to see all options, use `inference workflows process-image --help` command

* any option that starts from `--` which is not enlisted in `--help` command will be treated as input parameter
to the workflow execution

#### Processing video file

!!! Note "`inference` required"

This command requires `inference` to be installed.

Basic usage of the command is illustrated below:

```bash
inference workflows process-video \
--video_path {video_to_be_processed} \
--output_dir {empty_directory} \
--workspace_name {your-roboflow-workspace-url} \
--workflow_id {your-workflow-id} \
--api-key {your_roboflow_api_key}
```

You can tweak behaviour of the command:

* `--max_fps` option can be used to subsample video frames while processing

* to see all options, use `inference workflows process-image --help` command

* any option that starts from `--` which is not enlisted in `--help` command will be treated as input parameter
to the workflow execution


## Supported Devices

Roboflow Inference CLI currently supports the following device targets:
Expand Down
2 changes: 1 addition & 1 deletion docs/workflows/kinds.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ provided at runtime, either from user inputs or from other function outputs.

To manage this, Workflows use *selectors*, which act like references, pointing to data without containing it directly.

!!! Example *selectors*
!!! example "selectors"

Selectors might refer to a named input - for example input image - like `$inputs.image`
or predictions generated by a previous step - like `$steps.my_model.predictions`
Expand Down
36 changes: 36 additions & 0 deletions docs/workflows/modes_of_running.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,42 @@ Explore the example below to see how to combine `InferencePipeline` with Workflo

Make sure you have `inference` or `inference-gpu` package installed in your Python environment

## Batch processing using `inference-cli`

[`inference-cli`](/inference_helpers/inference_cli/) is command-line wrapper library around `inference`. You can use it
to process your data using Workflows without writing a single line of code. You simply point the data to be processed,
select your Workflow and specify where results should be saved. Thanks to `inference-cli` you can process:

* individual images

* directories of images

* video files

!!! example "Processing directory of images"

You can start the processing using the following command:

```bash
inference workflows process-images-directory \
-i {your_input_directory} \
-o {your_output_directory} \[workflows.py](..%2F..%2Finference_cli%2Fworkflows.py)
--workspace_name {your-roboflow-workspace-url} \
--workflow_id {your-workflow-id} \
--api-key {your_roboflow_api_key}
```

As a result, in the directory specified in `-o` option you should be able to find:

* sub-directories named after files in your original directory with `results.json` file that contain Worklfow
results and optionally additional `*.jpg` files with images created during Workflow execution

* `aggregated_results.csv` file that contain concatenated results of Workflow execution for all input image file

!!! note

Make sure you have `inference` or `inference-cli` package installed in your Python environment


## Workflows in Python package

Expand Down
4 changes: 3 additions & 1 deletion inference/core/entities/responses/workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,9 @@ class ExecutionEngineVersions(BaseModel):


class WorkflowsBlocksSchemaDescription(BaseModel):
schema: dict = Field(description="Schema for validating block definitions")
blocks_schema: dict = Field(
description="Schema for validating block definitions", alias="schema"
)


class DescribeInterfaceResponse(BaseModel):
Expand Down
6 changes: 4 additions & 2 deletions inference/core/interfaces/stream/inference_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ def init_with_workflow(
batch_collection_timeout: Optional[float] = None,
profiling_directory: str = "./inference_profiling",
use_workflow_definition_cache: bool = True,
serialize_results: bool = False,
) -> "InferencePipeline":
"""
This class creates the abstraction for making inferences from given workflow against video stream.
Expand Down Expand Up @@ -540,6 +541,8 @@ def init_with_workflow(
use_workflow_definition_cache (bool): Controls usage of cache for workflow definitions. Set this to False
when you frequently modify definition saved in Roboflow app and want to fetch the
newest version for the request. Only applies for Workflows definitions saved on Roboflow platform.
serialize_results (bool): Boolean flag to decide if ExecutionEngine run should serialize workflow
results for each frame. If that is set true, sinks will receive serialized workflow responses.

Other ENV variables involved in low-level configuration:
* INFERENCE_PIPELINE_PREDICTIONS_QUEUE_SIZE - size of buffer for predictions that are ready for dispatching
Expand Down Expand Up @@ -604,8 +607,6 @@ def init_with_workflow(
model_manager,
max_size=MAX_ACTIVE_MODELS,
)
if api_key is None:
api_key = API_KEY
if workflow_init_parameters is None:
workflow_init_parameters = {}
thread_pool_executor = ThreadPoolExecutor(
Expand All @@ -629,6 +630,7 @@ def init_with_workflow(
execution_engine=execution_engine,
image_input_name=image_input_name,
video_metadata_input_name=video_metadata_input_name,
serialize_results=serialize_results,
)
except ImportError as error:
raise CannotInitialiseModelError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def run_workflow(
execution_engine: ExecutionEngine,
image_input_name: str,
video_metadata_input_name: str,
serialize_results: bool = False,
) -> List[dict]:
if workflows_parameters is None:
workflows_parameters = {}
Expand Down Expand Up @@ -53,4 +54,5 @@ def run_workflow(
return execution_engine.run(
runtime_parameters=workflows_parameters,
fps=fps,
serialize_results=serialize_results,
)
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ def get_execution_engine_compatibility(cls) -> Optional[str]:


class ParsedPrediction(BaseModel):
model_config = ConfigDict(
protected_namespaces=(),
)

class_name: str
confidence: float
inference_id: str
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,4 @@ def get_workflow_schema_description() -> WorkflowsBlocksSchemaDescription:
available_blocks=available_blocks
)
schema = workflow_definition_class.model_json_schema()
return WorkflowsBlocksSchemaDescription(schema=schema)
return WorkflowsBlocksSchemaDescription(blocks_schema=schema)
10 changes: 10 additions & 0 deletions inference_cli/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,14 @@ def api_speed(
help="Boolean flag to decide on auto `yes` answer given on user input required.",
),
] = False,
max_error_rate: Annotated[
Optional[float],
typer.Option(
"--max_error_rate",
help="Max error rate for API speed benchmark - if given and the error rate is higher - command will "
"return non-success error code. Expected percentage values in range 0.0-100.0",
),
] = None,
):
if "roboflow.com" in host and not proceed_automatically:
proceed = input(
Expand All @@ -158,6 +166,7 @@ def api_speed(
model_configuration=model_configuration,
output_location=output_location,
enforce_legacy_endpoints=enforce_legacy_endpoints,
max_error_rate=max_error_rate,
)
else:
if workflow_specification:
Expand All @@ -179,6 +188,7 @@ def api_speed(
api_key=api_key,
model_configuration=model_configuration,
output_location=output_location,
max_error_rate=max_error_rate,
)
except Exception as error:
typer.echo(f"Command failed. Cause: {error}")
Expand Down
6 changes: 5 additions & 1 deletion inference_cli/lib/benchmark/api_speed.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ def run_api_warm_up(
for _ in tqdm(
range(warm_up_requests), desc="Warming up API...", total=warm_up_requests
):
_ = client.infer(inference_input=image)
try:
_ = client.infer(inference_input=image)
except Exception:
# ignoring errors, without slight API instability may terminate benchmark
pass


def coordinate_infer_api_speed_benchmark(
Expand Down
Loading