From 51b3686c2c149cc7c6b3cf94fc4d6e766c4464cc Mon Sep 17 00:00:00 2001 From: Hansong Zhang Date: Wed, 14 May 2025 17:51:44 -0700 Subject: [PATCH 1/6] Add etdump to android JNI not exposed yet. Need to think about API --- extension/android/CMakeLists.txt | 10 ++++++++ extension/android/jni/jni_layer.cpp | 36 ++++++++++++++++++++++++++--- scripts/build_android_library.sh | 3 +++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/extension/android/CMakeLists.txt b/extension/android/CMakeLists.txt index b2f7b8d9f47..06cae3475e8 100644 --- a/extension/android/CMakeLists.txt +++ b/extension/android/CMakeLists.txt @@ -80,6 +80,16 @@ list( fbjni ) +if(EXECUTORCH_ANDROID_PROFILING) + list( + APPEND + link_libraries + etdump + flatccrt + ) + target_compile_definitions(executorch_jni PUBLIC EXECUTORCH_ANDROID_PROFILING=1) +endif() + if(TARGET optimized_native_cpu_ops_lib) list( APPEND diff --git a/extension/android/jni/jni_layer.cpp b/extension/android/jni/jni_layer.cpp index f3c62e1d70f..1c277adac8c 100644 --- a/extension/android/jni/jni_layer.cpp +++ b/extension/android/jni/jni_layer.cpp @@ -14,7 +14,6 @@ #include #include #include - #include "jni_layer_constants.h" #include @@ -31,6 +30,12 @@ #include #endif +#ifdef EXECUTORCH_ANDROID_PROFILING +#include +#include +#include +#endif + #include #include @@ -237,8 +242,12 @@ class ExecuTorchJni : public facebook::jni::HybridClass { } else if (loadMode == 3) { load_mode = Module::LoadMode::MmapUseMlockIgnoreErrors; } - - module_ = std::make_unique(modelPath->toStdString(), load_mode); +#ifdef EXECUTORCH_ANDROID_PROFILING + auto etdump_gen = std::make_unique(); +#else + auto etdump_gen = nullptr; +#endif + module_ = std::make_unique(modelPath->toStdString(), load_mode, std::move(etdump_gen)); #ifdef ET_USE_THREADPOOL // Default to using cores/2 threadpool threads. The long-term plan is to @@ -361,6 +370,27 @@ class ExecuTorchJni : public facebook::jni::HybridClass { auto jevalue = JEValue::newJEValueFromEValue(result.get()[i]); jresult->setElement(i, *jevalue); } +#ifdef EXECUTORCH_ANDROID_PROFILING + executorch::etdump::ETDumpGen* etdumpgen = (executorch::etdump::ETDumpGen*) module_->event_tracer(); + auto etdump_data = etdumpgen->get_etdump_data(); + + if (etdump_data.buf != nullptr && etdump_data.size > 0) { + int etdump_file = open("/data/local/tmp/result.etdump", O_WRONLY | O_CREAT, 0644); + if (etdump_file == -1) { + ET_LOG(Error, "Cannot create result.etdump error: %d", errno); + } + ssize_t bytes_written = write(etdump_file, (uint8_t*)etdump_data.buf, etdump_data.size); + if (bytes_written == -1) { + ET_LOG(Error, "Cannot write result.etdump error: %d", errno); + } else { + ET_LOG(Info, "ETDump written %d bytes to file.", bytes_written); + } + close(etdump_file); + free(etdump_data.buf); + } else { + ET_LOG(Error, "No ETDump data available!"); + } +#endif return jresult; } diff --git a/scripts/build_android_library.sh b/scripts/build_android_library.sh index 5f0790adb82..f80cbf6dabb 100755 --- a/scripts/build_android_library.sh +++ b/scripts/build_android_library.sh @@ -40,6 +40,8 @@ build_android_native_library() { -DANDROID_PLATFORM=android-26 \ -DBUILD_TESTING=OFF \ -DEXECUTORCH_ENABLE_LOGGING=ON \ + -DEXECUTORCH_BUILD_DEVTOOLS=ON \ + -DEXECUTORCH_ENABLE_EVENT_TRACER=ON \ -DEXECUTORCH_LOG_LEVEL=Info \ -DEXECUTORCH_BUILD_XNNPACK=ON \ -DEXECUTORCH_XNNPACK_SHARED_WORKSPACE=ON \ @@ -75,6 +77,7 @@ build_android_native_library() { -DEXECUTORCH_ENABLE_LOGGING=ON \ -DEXECUTORCH_LOG_LEVEL=Info \ -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH \ + -DEXECUTORCH_ANDROID_PROFILING="${EXECUTORCH_ANDROID_PROFILING:-OFF}" \ -DNEURON_BUFFER_ALLOCATOR_LIB="$NEURON_BUFFER_ALLOCATOR_LIB" \ -DEXECUTORCH_BUILD_KERNELS_CUSTOM="${EXECUTORCH_BUILD_EXTENSION_LLM:-ON}" \ -DEXECUTORCH_BUILD_LLAMA_JNI="${EXECUTORCH_BUILD_EXTENSION_LLM:-ON}" \ From 9c014f07d10ff432504641d4539ff3c578f11692 Mon Sep 17 00:00:00 2001 From: Hansong Zhang Date: Thu, 15 May 2025 00:38:31 -0700 Subject: [PATCH 2/6] test --- .github/workflows/android-perf.yml | 2 +- .../java/org/pytorch/executorch/Module.java | 4 ++ .../org/pytorch/executorch/NativePeer.java | 3 ++ extension/android/jni/jni_layer.cpp | 52 +++++++++++-------- .../android-llm-device-farm-test-spec.yml.j2 | 1 + .../org/pytorch/minibench/ModelRunner.java | 2 + 6 files changed, 41 insertions(+), 23 deletions(-) diff --git a/.github/workflows/android-perf.yml b/.github/workflows/android-perf.yml index 266454c39ab..e2f85e05d3a 100644 --- a/.github/workflows/android-perf.yml +++ b/.github/workflows/android-perf.yml @@ -367,7 +367,7 @@ jobs: PYTHON_EXECUTABLE=python bash .ci/scripts/build-qnn-sdk.sh mkdir -p aar-out - PYTHON_EXECUTABLE=python ANDROID_ABIS="arm64-v8a" BUILD_AAR_DIR=aar-out EXECUTORCH_BUILD_QNN=ON QNN_SDK_ROOT=/tmp/qnn/2.28.0.241029 bash scripts/build_android_library.sh + PYTHON_EXECUTABLE=python ANDROID_ABIS="arm64-v8a" BUILD_AAR_DIR=aar-out EXECUTORCH_BUILD_QNN=ON QNN_SDK_ROOT=/tmp/qnn/2.28.0.241029 EXECUTORCH_ANDROID_PROFILING=ON bash scripts/build_android_library.sh mkdir -p extension/benchmark/android/benchmark/app/libs cp aar-out/executorch.aar extension/benchmark/android/benchmark/app/libs pushd extension/benchmark/android/benchmark diff --git a/extension/android/executorch_android/src/main/java/org/pytorch/executorch/Module.java b/extension/android/executorch_android/src/main/java/org/pytorch/executorch/Module.java index f3f543dc2a8..65677eb92a5 100644 --- a/extension/android/executorch_android/src/main/java/org/pytorch/executorch/Module.java +++ b/extension/android/executorch_android/src/main/java/org/pytorch/executorch/Module.java @@ -137,6 +137,10 @@ public String[] readLogBuffer() { return mNativePeer.readLogBuffer(); } + public boolean etdump() { + return mNativePeer.etdump(); + } + /** * Explicitly destroys the native torch::jit::Module. Calling this method is not required, as the * native object will be destroyed when this object is garbage-collected. However, the timing of diff --git a/extension/android/executorch_android/src/main/java/org/pytorch/executorch/NativePeer.java b/extension/android/executorch_android/src/main/java/org/pytorch/executorch/NativePeer.java index a5487a4702e..92c976d5c6b 100644 --- a/extension/android/executorch_android/src/main/java/org/pytorch/executorch/NativePeer.java +++ b/extension/android/executorch_android/src/main/java/org/pytorch/executorch/NativePeer.java @@ -58,4 +58,7 @@ public void resetNative() { /** Retrieve the in-memory log buffer, containing the most recent ExecuTorch log entries. */ @DoNotStrip public native String[] readLogBuffer(); + + @DoNotStrip + public native boolean etdump(); } diff --git a/extension/android/jni/jni_layer.cpp b/extension/android/jni/jni_layer.cpp index 1c277adac8c..75ca0fac763 100644 --- a/extension/android/jni/jni_layer.cpp +++ b/extension/android/jni/jni_layer.cpp @@ -370,28 +370,6 @@ class ExecuTorchJni : public facebook::jni::HybridClass { auto jevalue = JEValue::newJEValueFromEValue(result.get()[i]); jresult->setElement(i, *jevalue); } -#ifdef EXECUTORCH_ANDROID_PROFILING - executorch::etdump::ETDumpGen* etdumpgen = (executorch::etdump::ETDumpGen*) module_->event_tracer(); - auto etdump_data = etdumpgen->get_etdump_data(); - - if (etdump_data.buf != nullptr && etdump_data.size > 0) { - int etdump_file = open("/data/local/tmp/result.etdump", O_WRONLY | O_CREAT, 0644); - if (etdump_file == -1) { - ET_LOG(Error, "Cannot create result.etdump error: %d", errno); - } - ssize_t bytes_written = write(etdump_file, (uint8_t*)etdump_data.buf, etdump_data.size); - if (bytes_written == -1) { - ET_LOG(Error, "Cannot write result.etdump error: %d", errno); - } else { - ET_LOG(Info, "ETDump written %d bytes to file.", bytes_written); - } - close(etdump_file); - free(etdump_data.buf); - } else { - ET_LOG(Error, "No ETDump data available!"); - } -#endif - return jresult; } @@ -425,6 +403,35 @@ class ExecuTorchJni : public facebook::jni::HybridClass { #endif } + jboolean etdump() { +#ifdef EXECUTORCH_ANDROID_PROFILING + executorch::etdump::ETDumpGen* etdumpgen = (executorch::etdump::ETDumpGen*) module_->event_tracer(); + auto etdump_data = etdumpgen->get_etdump_data(); + + if (etdump_data.buf != nullptr && etdump_data.size > 0) { + int etdump_file = open("/data/local/tmp/result.etdump", O_WRONLY | O_CREAT, 0644); + if (etdump_file == -1) { + ET_LOG(Error, "Cannot create result.etdump error: %d", errno); + return false; + } + ssize_t bytes_written = write(etdump_file, (uint8_t*)etdump_data.buf, etdump_data.size); + if (bytes_written == -1) { + ET_LOG(Error, "Cannot write result.etdump error: %d", errno); + return false; + } else { + ET_LOG(Info, "ETDump written %d bytes to file.", bytes_written); + } + close(etdump_file); + free(etdump_data.buf); + return true; + } else { + ET_LOG(Error, "No ETDump data available!"); + } +#endif + return false; + + } + static void registerNatives() { registerHybrid({ makeNativeMethod("initHybrid", ExecuTorchJni::initHybrid), @@ -432,6 +439,7 @@ class ExecuTorchJni : public facebook::jni::HybridClass { makeNativeMethod("execute", ExecuTorchJni::execute), makeNativeMethod("loadMethod", ExecuTorchJni::load_method), makeNativeMethod("readLogBuffer", ExecuTorchJni::readLogBuffer), + makeNativeMethod("etdump", ExecuTorchJni::etdump), }); } }; diff --git a/extension/benchmark/android/benchmark/android-llm-device-farm-test-spec.yml.j2 b/extension/benchmark/android/benchmark/android-llm-device-farm-test-spec.yml.j2 index 3021d42038b..98cb52bab46 100644 --- a/extension/benchmark/android/benchmark/android-llm-device-farm-test-spec.yml.j2 +++ b/extension/benchmark/android/benchmark/android-llm-device-farm-test-spec.yml.j2 @@ -107,6 +107,7 @@ phases: - echo "Run benchmark" - | + adb -s $DEVICEFARM_DEVICE_UDID shell touch /data/local/tmp/result.etdump adb -s $DEVICEFARM_DEVICE_UDID shell am force-stop org.pytorch.minibench adb -s $DEVICEFARM_DEVICE_UDID shell dumpsys deviceidle force-idle diff --git a/extension/benchmark/android/benchmark/app/src/main/java/org/pytorch/minibench/ModelRunner.java b/extension/benchmark/android/benchmark/app/src/main/java/org/pytorch/minibench/ModelRunner.java index 3913a8d76f5..28f4e3728f0 100644 --- a/extension/benchmark/android/benchmark/app/src/main/java/org/pytorch/minibench/ModelRunner.java +++ b/extension/benchmark/android/benchmark/app/src/main/java/org/pytorch/minibench/ModelRunner.java @@ -41,6 +41,8 @@ public void runBenchmark( latency.add(forwardMs); } + module.etdump(); + final BenchmarkMetric.BenchmarkModel benchmarkModel = BenchmarkMetric.extractBackendAndQuantization(model.getName().replace(".pte", "")); // The list of metrics we have atm includes: From 65ecfd178fbbceb346d26e808e46141bb3380261 Mon Sep 17 00:00:00 2001 From: Hansong Zhang Date: Mon, 19 May 2025 13:55:22 -0700 Subject: [PATCH 3/6] fix --- extension/android/jni/jni_layer.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/extension/android/jni/jni_layer.cpp b/extension/android/jni/jni_layer.cpp index 2bcefed36b1..e5d3c7988e0 100644 --- a/extension/android/jni/jni_layer.cpp +++ b/extension/android/jni/jni_layer.cpp @@ -404,7 +404,6 @@ class ExecuTorchJni : public facebook::jni::HybridClass { #endif } -<<<<<<< HEAD jboolean etdump() { #ifdef EXECUTORCH_ANDROID_PROFILING executorch::etdump::ETDumpGen* etdumpgen = (executorch::etdump::ETDumpGen*) module_->event_tracer(); @@ -430,9 +429,9 @@ class ExecuTorchJni : public facebook::jni::HybridClass { ET_LOG(Error, "No ETDump data available!"); } #endif - return false; + return false; + } -======= facebook::jni::local_ref> getUsedBackends( facebook::jni::alias_ref methodName) { auto methodMeta = module_->method_meta(methodName->toStdString()).get(); From 554f81ed8f19260c68c9018e02e4c867308e877b Mon Sep 17 00:00:00 2001 From: Hansong Zhang Date: Mon, 19 May 2025 14:42:57 -0700 Subject: [PATCH 4/6] Pull etdump --- .../android/benchmark/android-llm-device-farm-test-spec.yml.j2 | 2 ++ scripts/build_android_library.sh | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/extension/benchmark/android/benchmark/android-llm-device-farm-test-spec.yml.j2 b/extension/benchmark/android/benchmark/android-llm-device-farm-test-spec.yml.j2 index 98cb52bab46..aa113561cc8 100644 --- a/extension/benchmark/android/benchmark/android-llm-device-farm-test-spec.yml.j2 +++ b/extension/benchmark/android/benchmark/android-llm-device-farm-test-spec.yml.j2 @@ -148,6 +148,8 @@ phases: # Trying to pull the file using adb ends up with permission error, but this works too, so why not echo "${BENCHMARK_RESULTS}" > $DEVICEFARM_LOG_DIR/benchmark_results.json + adb -s $DEVICEFARM_DEVICE_UDID pull /data/local/tmp/result.etdump $DEVICEFARM_LOG_DIR/result.etdump + artifacts: # By default, Device Farm will collect your artifacts from the $DEVICEFARM_LOG_DIR directory. - $DEVICEFARM_LOG_DIR diff --git a/scripts/build_android_library.sh b/scripts/build_android_library.sh index f80cbf6dabb..38916873103 100755 --- a/scripts/build_android_library.sh +++ b/scripts/build_android_library.sh @@ -41,7 +41,7 @@ build_android_native_library() { -DBUILD_TESTING=OFF \ -DEXECUTORCH_ENABLE_LOGGING=ON \ -DEXECUTORCH_BUILD_DEVTOOLS=ON \ - -DEXECUTORCH_ENABLE_EVENT_TRACER=ON \ + -DEXECUTORCH_ENABLE_EVENT_TRACER="${EXECUTORCH_ANDROID_PROFILING:-OFF}" \ -DEXECUTORCH_LOG_LEVEL=Info \ -DEXECUTORCH_BUILD_XNNPACK=ON \ -DEXECUTORCH_XNNPACK_SHARED_WORKSPACE=ON \ From 1b7155ac56ee8e359f50a4521f89983eb8cebe62 Mon Sep 17 00:00:00 2001 From: Hansong Zhang Date: Mon, 19 May 2025 14:48:59 -0700 Subject: [PATCH 5/6] Doc --- .../src/main/java/org/pytorch/executorch/Module.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/extension/android/executorch_android/src/main/java/org/pytorch/executorch/Module.java b/extension/android/executorch_android/src/main/java/org/pytorch/executorch/Module.java index 5ae8c8eae7d..b31641d5a37 100644 --- a/extension/android/executorch_android/src/main/java/org/pytorch/executorch/Module.java +++ b/extension/android/executorch_android/src/main/java/org/pytorch/executorch/Module.java @@ -152,6 +152,14 @@ public String[] readLogBuffer() { return mNativePeer.readLogBuffer(); } + /** + * Dump the ExecuTorch ETRecord file to /data/local/tmp/result.etdump. + * + *

Currently for internal (minibench) use only. + * + * @return true if the etdump was successfully written, false otherwise. + */ + @Experimental public boolean etdump() { return mNativePeer.etdump(); } From b8399118c3c696d9356d53a288d63c4faf0fbff3 Mon Sep 17 00:00:00 2001 From: Hansong Zhang Date: Mon, 19 May 2025 15:10:38 -0700 Subject: [PATCH 6/6] linter --- extension/android/jni/jni_layer.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/extension/android/jni/jni_layer.cpp b/extension/android/jni/jni_layer.cpp index e5d3c7988e0..048d5bffa78 100644 --- a/extension/android/jni/jni_layer.cpp +++ b/extension/android/jni/jni_layer.cpp @@ -32,9 +32,9 @@ #endif #ifdef EXECUTORCH_ANDROID_PROFILING +#include #include #include -#include #endif #include @@ -248,7 +248,8 @@ class ExecuTorchJni : public facebook::jni::HybridClass { #else auto etdump_gen = nullptr; #endif - module_ = std::make_unique(modelPath->toStdString(), load_mode, std::move(etdump_gen)); + module_ = std::make_unique( + modelPath->toStdString(), load_mode, std::move(etdump_gen)); #ifdef ET_USE_THREADPOOL // Default to using cores/2 threadpool threads. The long-term plan is to @@ -406,16 +407,19 @@ class ExecuTorchJni : public facebook::jni::HybridClass { jboolean etdump() { #ifdef EXECUTORCH_ANDROID_PROFILING - executorch::etdump::ETDumpGen* etdumpgen = (executorch::etdump::ETDumpGen*) module_->event_tracer(); + executorch::etdump::ETDumpGen* etdumpgen = + (executorch::etdump::ETDumpGen*)module_->event_tracer(); auto etdump_data = etdumpgen->get_etdump_data(); if (etdump_data.buf != nullptr && etdump_data.size > 0) { - int etdump_file = open("/data/local/tmp/result.etdump", O_WRONLY | O_CREAT, 0644); + int etdump_file = + open("/data/local/tmp/result.etdump", O_WRONLY | O_CREAT, 0644); if (etdump_file == -1) { ET_LOG(Error, "Cannot create result.etdump error: %d", errno); return false; } - ssize_t bytes_written = write(etdump_file, (uint8_t*)etdump_data.buf, etdump_data.size); + ssize_t bytes_written = + write(etdump_file, (uint8_t*)etdump_data.buf, etdump_data.size); if (bytes_written == -1) { ET_LOG(Error, "Cannot write result.etdump error: %d", errno); return false;