docs/version3.x/inference_deployment/cross_platform/ios_deployment.en.md
PaddleOCR ships an iOS OCR sample app under deploy/ios_demo. The SwiftUI demo runs exported ONNX models on device using the ONNX Runtime Objective-C API for detection and recognition.
This guide covers environment requirements, model asset setup, building and running in Xcode, and optional ORT format conversion and benchmarking.
App sources, bundled resources, and third-party source vendored for this demo live under PaddleOCRDemo/. Unit tests are in PaddleOCRDemoTests/ next to the Xcode project. The project root also contains Podfile, scripts/, README.md, and NOTICE.
gem install cocoapods or Homebrew)curl, tarscripts/fetch_ios_demo_models.sh downloads ONNX bundles into PaddleOCRDemo/Models/. Supported presets:
PP-OCRv6_small (default)PP-OCRv6_tinyPP-OCRv5_mobileFrom the project root (deploy/ios_demo):
pod install
./scripts/fetch_ios_demo_models.sh
Optionally pass the model preset (bundle name such as PP-OCRv6_small) as a positional argument:
./scripts/fetch_ios_demo_models.sh PP-OCRv6_small
Intermediate .tar caches are stored under .fetch_ios_demo_models_work/ at the project root.
After pod install, open the workspace:
open PaddleOCRDemo.xcworkspace
Build the PaddleOCRDemo scheme. Ensure PaddleOCRDemo/Models/ and PaddleOCRDemo/Resources/SampleImages/ are included in the app target via folder references / Copy Bundle Resources, and PaddleOCRDemoTests/Fixtures/ in the test target (as in the checked-in project). The built-in picker sample is general_ocr_002.jpg.
The CPU / XNNPACK / Core ML segmented control chooses which ONNX Runtime EP is preferred first when creating the session. ONNX Runtime may still execute some operators on the default CPU EP when another EP lacks an implementation. Selecting CPU does not register the XNNPACK or Core ML EPs.
To produce ORT format weights:
python3 -m pip install -r requirements-onnx-convert-ort.txt
./scripts/convert_onnx_to_ort.sh
By default, conversion writes inference*.ort next to each inference.onnx under PaddleOCRDemo/Models/. The demo loads inference*.ort when present, so you do not need to ship ONNX in the bundle. To keep only one weight file in the app, delete the inference.onnx files manually, or use --output-dir and replace files under PaddleOCRDemo/Models/. Use --input-dir to point at any ONNX tree on the host; it defaults to PaddleOCRDemo/Models when omitted.
By default the script uses --optimization-style Fixed (one .ort per ONNX file). The raw Python tool defaults to both Fixed and Runtime (*.ort and *.with_runtime_opt.ort). For Runtime-style ORT files (e.g. Core ML–oriented exports), pass --optimization-style Runtime (alias: --optimization_style). You can pass both: --optimization-style Fixed Runtime.
The converter emits required_operators*.config for minimal ORT builds; by default the shell script deletes them after a successful run. Pass --keep-operator-config to retain them. They are not read at inference time. The iOS demo loads only .ort (or .onnx) weights.
Example (Runtime conversion to a separate folder):
./scripts/convert_onnx_to_ort.sh --output-dir ./out/ort_bundles --optimization-style Runtime
This demo provides a benchmark pipeline for measuring on-device OCR latency and memory.
Complete Quick start first. For benchmark runs on a physical device you also need:
xcodebuild requires a valid Apple Development certificate and provisioning profile. Open PaddleOCRDemo.xcworkspace in Xcode once, select your Development Team under Signing & Capabilities, and let Xcode manage provisioning; later run_benchmark.sh invocations reuse the cached profile. On first deploy, trust the developer certificate on the device under Settings → General → VPN & Device Management (one-time per device). Simulator runs do not require signing.python3 -m pip install -r requirements-accuracy.txt.From the project root, use ./scripts/run_benchmark.sh. The script resolves the input image, optionally runs an accuracy precheck, runs the XCTest benchmark on a simulator or device, extracts artifacts, and writes the Markdown report. It always invokes xcodebuild test with -configuration Release.
Prefer script flags. --image copies an arbitrary image into test fixtures for this run. --fixture selects an existing file under PaddleOCRDemoTests/Fixtures/. If neither is set, the script requires exactly one non-local-* image under Fixtures/ (e.g. ios_ocr_benchmark_reference.jpg).
| Intent | Flags |
|---|---|
| Destination | --udid <id> for a real device, or --simulator <name> |
| Image from an arbitrary path | --image <path> |
Image already under Fixtures/ | --fixture <name> |
| Benchmark intensity | --warmup <n>, --measured-iterations <n> |
| Preferred ONNX Runtime EP | --ort-execution-provider CPU, XNNPACK, or CORE_ML |
| ONNX Runtime profiling JSON | --ort-profiling |
| Optional accuracy precheck | --accuracy-check, optionally --accuracy-reference-json <path> |
Gate benchmark on accuracy FAIL | --accuracy-check --stop-on-accuracy-failure |
| Output directory | --output-dir <dir> (default: out/) |
| Clean previous artifacts | --clean |
./scripts/run_benchmark.sh --udid <device-udid> --warmup 5 --measured-iterations 30
./scripts/run_benchmark.sh --udid <udid> --image /path/to/photo.png --ort-execution-provider CPU
./scripts/run_benchmark.sh --fixture ios_ocr_benchmark_reference --warmup 2 --measured-iterations 20
./scripts/run_benchmark.sh
PADDLEOCR_BENCHMARK_MEASURED_ITERATIONS=30 ./scripts/run_benchmark.sh --warmup 0
PADDLEOCR_BENCHMARK_ORT_EXECUTION_PROVIDER=XNNPACK ./scripts/run_benchmark.sh --fixture ios_ocr_benchmark_reference --warmup 1
./scripts/run_benchmark.sh --accuracy-check --udid <device-udid> --measured-iterations 30
./scripts/run_benchmark.sh --accuracy-check --stop-on-accuracy-failure --udid <device-udid>
./scripts/run_benchmark.sh --output-dir ./benchmark-out --measured-iterations 30
./scripts/run_benchmark.sh --udid <device-udid> --ort-profiling
--accuracy-check runs before the benchmark in a separate XCTest invocation. Results are PASS, FAIL, or ERROR. By default, FAIL records a mismatch but continues; ERROR stops the pipeline. --stop-on-accuracy-failure also skips the benchmark on FAIL.
ORT session profiling (attachments ort_profile_detection, ort_profile_recognition): use --ort-profiling. Run profiling in a separate run from clean latency measurements.
After the benchmark completes, the script overwrites run-status.json, on-device-performance.json, xctest-memory-metrics.json, and benchmark-report.md under the output directory (out/ by default). On ERROR, those files may be missing or partial.
The report includes model input tensor shape distributions, first measured run line count, inferred model preset, actual model format (onnx or ort), det/rec/total weight sizes, app executable size when resolvable, cold model load time, measured latency, memory, etc.
| Artifact | Producer | Purpose |
|---|---|---|
accuracy-reference.json | ocr_reference_run.py | Optional reference JSON |
accuracy-result.xcresult | xcodebuild test | Optional accuracy precheck |
ios-ocr-export.json | extract_xcresult_attachments.py | Optional iOS accuracy payload |
accuracy-summary.json | compare_ocr_json.py | Optional accuracy summary |
latency-result.xcresult | xcodebuild test | Latency benchmark |
memory-result.xcresult | xcodebuild test | Memory benchmark |
on-device-performance.json | extract_xcresult_attachments.py | Latency stats from latency-result.xcresult |
ort_profile_detection, ort_profile_recognition | extract_xcresult_attachments.py | Optional ORT profiling JSON |
xctest-memory-metrics.json | extract_xctest_metrics.py | XCTest memory metrics |
logs/*.log | run_benchmark.sh | Per-step logs |
run-status.json | run_benchmark.sh | Step outcomes and metadata |
benchmark-report.md | generate_benchmark_report.py | Human-readable report |
Exit 0 when the pipeline reaches COMPLETED; non-zero on ERROR. Accuracy FAIL changes the exit code only with --stop-on-accuracy-failure; ERROR always stops the pipeline.
Most users should use run_benchmark.sh flags instead of setting XCTest environment variables directly. This section is for debugging the PaddleOCRDemoTests target from Xcode or a custom xcodebuild test command.
Benchmark tests read PADDLEOCR_BENCHMARK_* variables on the XCTest runner process.
| How you launch tests | What to configure |
|---|---|
| Xcode | Scheme PaddleOCRDemo → Test → Arguments → Environment Variables (PADDLEOCR_BENCHMARK_… as-is). |
Custom xcodebuild test | TEST_RUNNER_ + variable name (e.g. TEST_RUNNER_PADDLEOCR_BENCHMARK_IMAGE_NAME=…). |
| Variable | If unset | Role |
|---|---|---|
PADDLEOCR_BENCHMARK_IMAGE_NAME | ios_ocr_benchmark_reference | Bundled test image under Fixtures/. |
PADDLEOCR_BENCHMARK_WARMUP_ITERATIONS | 3 | Untimed full OCR runs before timing. |
PADDLEOCR_BENCHMARK_MEASURED_ITERATIONS | 10 | Timed runs. |
PADDLEOCR_BENCHMARK_ORT_EXECUTION_PROVIDER | CPU | CPU, XNNPACK, or CORE_ML. |
PADDLEOCR_BENCHMARK_ORT_PROFILING | (unset) | Enable ORT session profiling (1 / true / yes / on). |
PADDLEOCR_BENCHMARK_ONLY_TESTING_SCOPE | latency + memory benchmark tests | run_benchmark.sh only: comma-separated -only-testing scope. |
Non-negative integers for the two iteration variables.
Bundled Clipper (polyclipping 6.4.2) is under the Boost Software License 1.0; see NOTICE and PaddleOCRDemo/ThirdParty/Clipper1/LICENSE. CocoaPods pods are governed by their respective licenses (see Podfile.lock after pod install).