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

iOS Simulator tests #25

Merged
merged 4 commits into from
Jan 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 15 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ jobs:
steps:
- uses: actions/checkout@v3
- run: pod spec lint PLMLibTorchWrapper.podspec --use-libraries --allow-warnings
test-ios-simulator-x64:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '11'
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- run: xcrun simctl list devices iOS
- run: python3 --version
- run: pip3 install torch --extra-index-url https://download.pytorch.org/whl/cpu
- run: ./gradlew iosSimulatorX64Test
build:
runs-on: macos-latest
steps:
Expand All @@ -14,5 +29,4 @@ jobs:
with:
distribution: 'temurin'
java-version: '11'
- run: gem install cocoapods-generate
- run: ./gradlew build -x iosX64Test
15 changes: 4 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,20 +94,13 @@ To make management of resources allocated for your inference across Android and

## Running tests

To run the tests, first create the dummy torchscript module using:

```
python build_dummy_model.py
```

Use a Python environment where the torch dependency is available.

The tests will run inference against this module on iOS and Android using the multiplatform implementation.

### iOS

Copy the created `dummy_model.ptl` into your simulator documents directory and run the iOS tests using
To run the tests on iOS, execute the `iosSimulatorX64Test` gradle task:

```
./gradlew iosSimulatorX64Test
```

This will automatically call `build_dummy_model.py` to create the dummy torchscript module for testing, copy it into the simulator files directory and execute the tests.
Make sure to select a Python environment where the torch dependency is available.
107 changes: 71 additions & 36 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import java.io.ByteArrayOutputStream

plugins {
kotlin("multiplatform") version "1.8.0"
Expand All @@ -21,31 +22,18 @@ kotlin {
publishLibraryVariants("release")
}

ios {
val libTorchPodDir = project.file("build/cocoapods/synthetic/IOS/Pods/LibTorch-Lite")
val libTorchLibsDir = libTorchPodDir.resolve("install/lib")
val libs = listOf(
"c10", "torch", "torch_cpu", "XNNPACK", "clog",
"cpuinfo", "eigen_blas", "pthreadpool", "pytorch_qnnpack"
)

binaries.all {
linkerOpts(
"-L${libTorchLibsDir.absolutePath}",
*libs.map { "-l$it" }.toTypedArray(),
"-force_load", libTorchLibsDir.resolve("libtorch.a").absolutePath,
"-force_load", libTorchLibsDir.resolve("libtorch_cpu.a").absolutePath,
"-all_load"
)
}
}
ios()

cocoapods {
ios.deploymentTarget = "13.5"

homepage = "https://github.com/voize-gmbh/pytorch-lite-multiplatform"
summary = "Kotlin Multiplatform wrapper for PyTorch Lite"

framework {
isStatic = true
}

pod("PLMLibTorchWrapper") {
version = "0.5.1"
headers = "LibTorchWrapper.h"
Expand Down Expand Up @@ -77,38 +65,85 @@ kotlin {
}
}

fun createFrameworkFromStaticLib(platform: String) {
val basePath = project.file("build/cocoapods/synthetic/IOS/build/Release-$platform/PLMLibTorchWrapper")
val frameworkPath = basePath.resolve("PLMLibTorchWrapper.framework")
frameworkPath.mkdir()
val frameworkLibPath = frameworkPath.resolve("PLMLibTorchWrapper")
basePath.resolve("libPLMLibTorchWrapper.a").copyTo(frameworkLibPath, overwrite = true)
}
tasks.named("linkPodDebugFrameworkIosArm64").configure {
tasks.named("linkDebugTestIosX64").configure {
doFirst {
createFrameworkFromStaticLib("iphoneos")
}
}
tasks.named("linkPodDebugFrameworkIosX64").configure {
doFirst {
createFrameworkFromStaticLib("iphonesimulator")
val target = (kotlin.targets.getByName("iosX64") as KotlinNativeTarget)

target.binaries.all {
val syntheticIOSProjectDir = project.file("build/cocoapods/synthetic/IOS")
val libTorchPodDir = syntheticIOSProjectDir.resolve("Pods/LibTorch-Lite")
val libTorchLibsDir = libTorchPodDir.resolve("install/lib")
val podBuildDir = syntheticIOSProjectDir.resolve("build/Release-iphonesimulator")

linkerOpts(
"-L${libTorchLibsDir.absolutePath}",
"-lc10", "-ltorch", "-ltorch_cpu", "-lXNNPACK",
"-lclog", "-lcpuinfo", "-leigen_blas", "-lpthreadpool", "-lpytorch_qnnpack",
"-force_load", libTorchLibsDir.resolve("libtorch.a").absolutePath,
"-force_load", libTorchLibsDir.resolve("libtorch_cpu.a").absolutePath,
"-all_load",
"-L${podBuildDir.resolve("PLMLibTorchWrapper").absolutePath}",
"-lPLMLibTorchWrapper"
)
}
}
}

// inspired by: https://diamantidis.github.io/2019/08/25/kotlin-multiplatform-project-unit-tests-for-ios-and-android
task("iosSimulatorX64Test") {
val device = "iPhone 12"
val target = (kotlin.targets.getByName("iosX64") as KotlinNativeTarget)

dependsOn(target.binaries.getTest("DEBUG").linkTaskName)
group = JavaBasePlugin.VERIFICATION_GROUP
description = "Runs iOS tests on a simulator"

doLast {
val binary = target.binaries.getTest("DEBUG").outputFile
println(binary)
println("Retrieving runtime for iOS simulator")
val iOSRuntimesOutput = ByteArrayOutputStream()
exec {
commandLine("xcrun", "simctl", "list", "--json", "runtimes", "iOS")
standardOutput = iOSRuntimesOutput
}

val iOSRuntimesData = groovy.json.JsonSlurper().parseText(iOSRuntimesOutput.toString()) as Map<String, List<Map<String, Any>>>
val runtimesIdentifiers = iOSRuntimesData["runtimes"]!!.map { it["identifier"]!! } as List<String>
val latestRuntimeIdentifier = runtimesIdentifiers.maxOrNull()!!
println("Latest iOS runtime: $latestRuntimeIdentifier")

println("Retrieving device for iOS simulator")
val devicesOutput = ByteArrayOutputStream()
exec {
commandLine("xcrun", "simctl", "list", "--json", "devices")
standardOutput = devicesOutput
}
val devicesData = groovy.json.JsonSlurper().parseText(devicesOutput.toString()) as Map<String, Map<String, List<Map<String, String>>>>
val devices = devicesData["devices"]!!
val deviceName = "iPhone 12"
val device = devices[latestRuntimeIdentifier]!!.find { it["name"] == deviceName }
val udid = device!!["udid"]
println("Using device: $deviceName ($udid)")

exec {
println("Building test model")
commandLine("python3", "build_dummy_model.py")
}

val simulatorFilesPath = "/Users/${System.getProperty("user.name")}/Library/Developer/CoreSimulator/Devices/$udid/data/Documents"

exec {
println("Setting up iOS simulator documents directory")
commandLine("mkdir", "-p", simulatorFilesPath)
}

exec {
println("Copying model to iOS simulator files ($simulatorFilesPath)")
commandLine("cp", "dummy_module.ptl", simulatorFilesPath)
}

exec {
commandLine("xcrun", "simctl", "spawn", "--standalone", device, binary.absolutePath)
println("Running simulator tests")
val binary = target.binaries.getTest("DEBUG").outputFile
commandLine("xcrun", "simctl", "spawn", "--standalone", udid, binary.absolutePath)
}
}
}
Expand Down