From e2e01d460f5c77d7ed111a0a9f9b512996486ddb Mon Sep 17 00:00:00 2001 From: Conan Jeffrey Truong Date: Mon, 11 Aug 2025 16:22:02 -0700 Subject: [PATCH 1/2] Added ETDump to Wasm --- .github/workflows/pull.yml | 4 +- extension/wasm/CMakeLists.txt | 7 ++ extension/wasm/README.md | 5 ++ extension/wasm/test/CMakeLists.txt | 27 ++++++- extension/wasm/test/unittests_etdump.js | 24 ++++++ .../wasm/test/unittests_etdump_disabled.js | 15 ++++ extension/wasm/wasm_bindings.cpp | 79 ++++++++++++++++++- scripts/build_wasm_tests.sh | 10 ++- 8 files changed, 163 insertions(+), 8 deletions(-) create mode 100644 extension/wasm/test/unittests_etdump.js create mode 100644 extension/wasm/test/unittests_etdump_disabled.js diff --git a/.github/workflows/pull.yml b/.github/workflows/pull.yml index d39e9a43f25..80214cc8375 100644 --- a/.github/workflows/pull.yml +++ b/.github/workflows/pull.yml @@ -801,6 +801,8 @@ jobs: id-token: write contents: read strategy: + matrix: + enable-etdump: ['', '--enable-etdump'] fail-fast: false with: runner: linux.2xlarge @@ -820,7 +822,7 @@ jobs: source .ci/scripts/setup-emscripten.sh # Test selective build - bash scripts/build_wasm_tests.sh + bash scripts/build_wasm_tests.sh ${{ matrix.enable-etdump }} # Install Jest cd cmake-out-wasm/extension/wasm/test diff --git a/extension/wasm/CMakeLists.txt b/extension/wasm/CMakeLists.txt index c1ebab2b78a..36c336e17c5 100644 --- a/extension/wasm/CMakeLists.txt +++ b/extension/wasm/CMakeLists.txt @@ -44,6 +44,13 @@ list( add_library(executorch_wasm OBJECT wasm_bindings.cpp) +if(EXECUTORCH_ENABLE_EVENT_TRACER) + list(APPEND link_libraries etdump) + target_compile_definitions( + executorch_wasm PUBLIC EXECUTORCH_ENABLE_EVENT_TRACER + ) +endif() + target_compile_options(executorch_wasm PUBLIC ${_common_compile_options}) target_include_directories( executorch_wasm PUBLIC ${_common_include_directories} diff --git a/extension/wasm/README.md b/extension/wasm/README.md index 7eebb35f3e8..54b1168732d 100644 --- a/extension/wasm/README.md +++ b/extension/wasm/README.md @@ -82,6 +82,7 @@ The output `my_project.js` should contain both the emitted JS code and the conte - `getMethods()`: Returns the list of methods in the model. - `loadMethod(methodName)`: Load a method from the model. - `getMethodMetadata(methodName)`: Get the metadata of a method. +- `etdump()`: If enabled, flushes the etdump buffer and return the results. - `execute(methodName, inputs)`: Execute a method with the given inputs. - `forward(inputs)`: Execute the forward method with the given inputs. - `delete()`: Delete the model from memory. @@ -118,6 +119,10 @@ The output `my_project.js` should contain both the emitted JS code and the conte - `name`: The name of the tensor. - These are value types and do not need to be manually deleted. +### ETDumpResult +- `buffer`: The buffer containing the ETDump data. +- `delete()`: Delete the ETDumpResult from memory. + ### ScalarType - Only `Float` and `Long` are currently supported. - `value`: The int constant value of the enum. diff --git a/extension/wasm/test/CMakeLists.txt b/extension/wasm/test/CMakeLists.txt index ec8f07e05bf..fad2ab038cb 100644 --- a/extension/wasm/test/CMakeLists.txt +++ b/extension/wasm/test/CMakeLists.txt @@ -40,6 +40,27 @@ add_custom_target( DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/package.json ) +if(EXECUTORCH_ENABLE_EVENT_TRACER) + set(ETDUMP_UNIT_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/unittests_etdump.js) +else() + set(ETDUMP_UNIT_TESTS + ${CMAKE_CURRENT_SOURCE_DIR}/unittests_etdump_disabled.js + ) +endif() + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/unittests_full.js + COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/unittests.js ${ETDUMP_UNIT_TESTS} > + ${CMAKE_CURRENT_BINARY_DIR}/unittests_full.js + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/unittests.js ${ETDUMP_UNIT_TESTS} + COMMENT "Copying unittests_full.js to build output directory" +) + +add_custom_target( + executorch_wasm_unittests + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/unittests_full.js +) + add_executable(executorch_wasm_tests) target_link_libraries(executorch_wasm_tests PRIVATE executorch_wasm) target_link_options( @@ -48,7 +69,7 @@ target_link_options( --embed-file "${MODELS_DIR}@/" --pre-js - ${CMAKE_CURRENT_SOURCE_DIR}/unittests.js + ${CMAKE_CURRENT_BINARY_DIR}/unittests_full.js -sASSERTIONS=2 ) set_target_properties( @@ -57,9 +78,9 @@ set_target_properties( set_property( TARGET executorch_wasm_tests APPEND - PROPERTY LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/unittests.js + PROPERTY LINK_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/unittests_full.js ) add_dependencies( - executorch_wasm_tests executorch_wasm_test_models + executorch_wasm_tests executorch_wasm_unittests executorch_wasm_test_models executorch_wasm_test_package_json ) diff --git a/extension/wasm/test/unittests_etdump.js b/extension/wasm/test/unittests_etdump.js new file mode 100644 index 00000000000..18dbfe70303 --- /dev/null +++ b/extension/wasm/test/unittests_etdump.js @@ -0,0 +1,24 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +describe("ETDump", () => { + test("etdump enabled", () => { + const module = et.Module.load("add_mul.pte"); + const inputs = [et.Tensor.ones([2, 2]), et.Tensor.ones([2, 2]), et.Tensor.ones([2, 2])]; + const output = module.forward(inputs); + + inputs.forEach((input) => input.delete()); + output.forEach((output) => output.delete()); + const etdump = module.etdump(); + const buffer = etdump.buffer; + expect(buffer).toBeInstanceOf(Uint8Array); + expect(buffer.length).toBeGreaterThan(0); + etdump.delete(); + module.delete(); + }); +}); diff --git a/extension/wasm/test/unittests_etdump_disabled.js b/extension/wasm/test/unittests_etdump_disabled.js new file mode 100644 index 00000000000..a1f8a54ab9f --- /dev/null +++ b/extension/wasm/test/unittests_etdump_disabled.js @@ -0,0 +1,15 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +describe("ETDump", () => { + test("etdump disabled", () => { + const module = et.Module.load("add_mul.pte"); + expect(() => module.etdump()).toThrow(); + module.delete(); + }); +}); diff --git a/extension/wasm/wasm_bindings.cpp b/extension/wasm/wasm_bindings.cpp index 6ba41236868..c1cadacddc0 100644 --- a/extension/wasm/wasm_bindings.cpp +++ b/extension/wasm/wasm_bindings.cpp @@ -14,6 +14,10 @@ #include #include +#ifdef EXECUTORCH_ENABLE_EVENT_TRACER +#include +#endif + #define THROW_JS_ERROR(errorType, message, ...) \ ({ \ char msg_buf[256]; \ @@ -51,10 +55,15 @@ using executorch::aten::Tensor; using ::executorch::extension::BufferDataLoader; using ::executorch::runtime::Error; using ::executorch::runtime::EValue; +using ::executorch::runtime::EventTracer; using ::executorch::runtime::Result; using ::executorch::runtime::Tag; using ::executorch::runtime::TensorInfo; +#ifdef EXECUTORCH_ENABLE_EVENT_TRACER +using executorch::etdump::ETDumpGen; +#endif + namespace executorch { namespace extension { namespace wasm { @@ -495,6 +504,35 @@ struct ET_EXPERIMENTAL JsMethodMeta { } }; +/** + * EXPERIMENTAL: Wrapper around ETDumpResult for JavaScript. + */ +#ifdef EXECUTORCH_ENABLE_EVENT_TRACER +class ET_EXPERIMENTAL JsETDumpResult final { + public: + JsETDumpResult() = delete; + JsETDumpResult(const JsETDumpResult&) = delete; + JsETDumpResult& operator=(const JsETDumpResult&) = delete; + JsETDumpResult(JsETDumpResult&&) = default; + JsETDumpResult& operator=(JsETDumpResult&&) = default; + + explicit JsETDumpResult(uint8_t* buffer, size_t size) + : buffer_(buffer), size_(size) {} + + ~JsETDumpResult() { + free(buffer_); + } + + val get_buffer() const { + return val(typed_memory_view(size_, buffer_)); + } + + private: + uint8_t* buffer_; + size_t size_; +}; +#endif + /** * EXPERIMENTAL: Wrapper around extension/Module for JavaScript. */ @@ -518,8 +556,16 @@ class ET_EXPERIMENTAL JsModule final { val memory_view = val(typed_memory_view(length, buffer.data())); memory_view.call("set", data); auto loader = std::make_unique(buffer.data(), length); + +#ifdef EXECUTORCH_ENABLE_EVENT_TRACER + std::unique_ptr etdump_gen = std::make_unique(); +#else + std::unique_ptr etdump_gen = nullptr; +#endif return std::make_unique( - std::move(buffer), std::make_unique(std::move(loader))); + std::move(buffer), + std::make_unique( + std::move(loader), nullptr, nullptr, std::move(etdump_gen))); } static std::unique_ptr load(val data) { @@ -527,8 +573,15 @@ class ET_EXPERIMENTAL JsModule final { THROW_JS_ERROR(TypeError, "Data cannot be null or undefined"); } if (data.isString()) { - return std::make_unique( - std::make_unique(data.as())); +#ifdef EXECUTORCH_ENABLE_EVENT_TRACER + std::unique_ptr etdump_gen = std::make_unique(); +#else + std::unique_ptr etdump_gen = nullptr; +#endif + return std::make_unique(std::make_unique( + data.as(), + Module::LoadMode::File, + std::move(etdump_gen))); } else if (data.instanceof (val::global("Uint8Array"))) { return load_from_uint8_array(data); } else if (data.instanceof (val::global("ArrayBuffer"))) { @@ -569,6 +622,18 @@ class ET_EXPERIMENTAL JsModule final { return JsMethodMeta::from_method_meta(res.get()); } +#ifdef EXECUTORCH_ENABLE_EVENT_TRACER + std::unique_ptr etdump() { + ETDumpGen* etdump_gen = dynamic_cast(module_->event_tracer()); + if (etdump_gen == nullptr) { + return nullptr; + } + auto etdump_data = etdump_gen->get_etdump_data(); + return std::make_unique( + static_cast(etdump_data.buf), etdump_data.size); + } +#endif + val_array execute(const std::string& method, val js_inputs) { std::vector inputs; if (js_inputs.isArray()) { @@ -613,11 +678,19 @@ EMSCRIPTEN_BINDINGS(WasmBindings) { #define JS_DECLARE_TAG(NAME) .value(#NAME, Tag::NAME) EXECUTORCH_FORALL_TAGS(JS_DECLARE_TAG); +#ifdef EXECUTORCH_ENABLE_EVENT_TRACER + class_("ETDumpResult") + .property("buffer", &JsETDumpResult::get_buffer); +#endif + class_("Module") .class_function("load", &JsModule::load) .function("getMethods", &JsModule::get_methods) .function("loadMethod", &JsModule::load_method) .function("getMethodMeta", &JsModule::get_method_meta) +#ifdef EXECUTORCH_ENABLE_EVENT_TRACER + .function("etdump", &JsModule::etdump) +#endif .function("execute", &JsModule::execute) .function("forward", &JsModule::forward); class_("Tensor") diff --git a/scripts/build_wasm_tests.sh b/scripts/build_wasm_tests.sh index 6b88067133b..d11711b3959 100644 --- a/scripts/build_wasm_tests.sh +++ b/scripts/build_wasm_tests.sh @@ -5,6 +5,14 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. +for arg in "$@"; do + if [ "$arg" == "--enable-etdump" ]; then + ETDUMP_OPTS="-DEXECUTORCH_ENABLE_EVENT_TRACER=ON \ + -DEXECUTORCH_BUILD_DEVTOOLS=ON \ + -DFLATCC_ALLOW_WERROR=OFF" + fi +done + CMAKE_OUT=cmake-out-wasm cd "$(dirname "${BASH_SOURCE[0]}")/../" @@ -16,7 +24,7 @@ emcmake cmake . -DEXECUTORCH_BUILD_WASM=ON \ -DEXECUTORCH_SELECT_OPS_LIST="aten::mm.out,aten::add.out" \ -DEXECUTORCH_BUILD_TESTS=ON \ -DCMAKE_BUILD_TYPE=Release \ - -B"${CMAKE_OUT}" + ${ETDUMP_OPTS} -B"${CMAKE_OUT}" if [ "$(uname)" == "Darwin" ]; then CMAKE_JOBS=$(( $(sysctl -n hw.ncpu) - 1 )) From f15a8f2b5295d4b10dfefedfcf0246f4a9769de0 Mon Sep 17 00:00:00 2001 From: Conan Jeffrey Truong Date: Tue, 12 Aug 2025 10:51:21 -0700 Subject: [PATCH 2/2] Applied suggestions --- scripts/build_wasm_tests.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/build_wasm_tests.sh b/scripts/build_wasm_tests.sh index d11711b3959..9a09ddd2749 100644 --- a/scripts/build_wasm_tests.sh +++ b/scripts/build_wasm_tests.sh @@ -10,6 +10,8 @@ for arg in "$@"; do ETDUMP_OPTS="-DEXECUTORCH_ENABLE_EVENT_TRACER=ON \ -DEXECUTORCH_BUILD_DEVTOOLS=ON \ -DFLATCC_ALLOW_WERROR=OFF" + # FlatCC generates warnings depending on the compiler version. + # This may be removed once the warnings are fixed. fi done