From 897545ac9bbbaddc9ae4720da3f37c053b4b82bc Mon Sep 17 00:00:00 2001 From: Conan Jeffrey Truong Date: Wed, 30 Jul 2025 15:55:22 -0700 Subject: [PATCH 1/6] Copied demo files --- .gitmodules | 4 + mv2/wasm/CMakeLists.txt | 52 ++ mv2/wasm/README.md | 26 + mv2/wasm/build.sh | 27 ++ mv2/wasm/classes.js | 1010 +++++++++++++++++++++++++++++++++++++++ mv2/wasm/demo.html | 24 + mv2/wasm/demo.js | 161 +++++++ mv2/wasm/executorch | 1 + 8 files changed, 1305 insertions(+) create mode 100644 mv2/wasm/CMakeLists.txt create mode 100644 mv2/wasm/README.md create mode 100644 mv2/wasm/build.sh create mode 100644 mv2/wasm/classes.js create mode 100644 mv2/wasm/demo.html create mode 100644 mv2/wasm/demo.js create mode 160000 mv2/wasm/executorch diff --git a/.gitmodules b/.gitmodules index 6a391707..831eafc9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,7 @@ path = program-data-separation/cpp/executorch url = https://github.com/pytorch/executorch.git branch = release/0.7 + +[submodule "mv2/wasm/executorch"] + path = mv2/wasm/executorch + url = https://github.com/pytorch/executorch.git diff --git a/mv2/wasm/CMakeLists.txt b/mv2/wasm/CMakeLists.txt new file mode 100644 index 00000000..0b64d3ca --- /dev/null +++ b/mv2/wasm/CMakeLists.txt @@ -0,0 +1,52 @@ +# 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. + +# Please this file formatted by running: +# ~~~ +# cmake-format -i CMakeLists.txt +# ~~~ + +add_subdirectory("executorch") + +add_executable(executorch_wasm_demo_lib) +target_link_libraries(executorch_wasm_demo_lib PUBLIC executorch_wasm) +target_link_options( + executorch_wasm_demo_lib PUBLIC -sALLOW_MEMORY_GROWTH +) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/demo.js + COMMAND + ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/demo.js + ${CMAKE_CURRENT_BINARY_DIR}/demo.js + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/demo.js + COMMENT "Copying demo.js to build output directory" +) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/classes.js + COMMAND + ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/classes.js + ${CMAKE_CURRENT_BINARY_DIR}/classes.js + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/classes.js + COMMENT "Copying classes.js to build output directory" +) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/demo.html + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/demo.html + ${CMAKE_CURRENT_BINARY_DIR}/demo.html + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/demo.html + COMMENT "Copying demo.html to build output directory" +) + +add_custom_target( + executorch_wasm_demo + DEPENDS executorch_wasm_demo_lib + ${CMAKE_CURRENT_BINARY_DIR}/classes.js + ${CMAKE_CURRENT_BINARY_DIR}/demo.js + ${CMAKE_CURRENT_BINARY_DIR}/demo.html +) diff --git a/mv2/wasm/README.md b/mv2/wasm/README.md new file mode 100644 index 00000000..f17659b6 --- /dev/null +++ b/mv2/wasm/README.md @@ -0,0 +1,26 @@ +# ExecuTorch JavaScript Bindings Demo + +This demo showcases the capabilities of ExecuTorch's JavaScript bindings. It is able to load a model, run inference, and classify an image natively in the browser. + +## Prerequisites + +- [Emscripten](https://emscripten.org/docs/getting_started/Tutorial.html) + +## Building and Running + +``` +# Build the demo +bash build.sh + +# Run the demo +python3 -m http.server --directory build/ +``` + +The page will be available at http://localhost:8000/demo.html. + +## Demo Features + +- Load a model from a file + - It currently only supports the MobileNetv2 model. Passing in a model with different input/output shapes will result in an error. + - You can generate the model file by following the instructions in the [Portable Mode Readme](https://github.com/pytorch/executorch/blob/main/examples/portable/README.md). +- Run inference on an image diff --git a/mv2/wasm/build.sh b/mv2/wasm/build.sh new file mode 100644 index 00000000..dcef3113 --- /dev/null +++ b/mv2/wasm/build.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# 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. + +CMAKE_OUT=build + +emcmake cmake . -DEXECUTORCH_BUILD_WASM=ON \ + -DEXECUTORCH_BUILD_EXTENSION_DATA_LOADER=ON \ + -DEXECUTORCH_BUILD_EXTENSION_FLAT_TENSOR=ON \ + -DEXECUTORCH_BUILD_DEVTOOLS=ON \ + -DEXECUTORCH_BUILD_EXTENSION_MODULE=ON \ + -DEXECUTORCH_BUILD_EXTENSION_TENSOR=ON \ + -DEXECUTORCH_BUILD_EXTENSION_RUNNER_UTIL=ON \ + -DEXECUTORCH_BUILD_WASM_DEMO=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -B"${CMAKE_OUT}" + +if [ "$(uname)" == "Darwin" ]; then + CMAKE_JOBS=$(( $(sysctl -n hw.ncpu) - 1 )) +else + CMAKE_JOBS=$(( $(nproc) - 1 )) +fi + +cmake --build ${CMAKE_OUT} --target executorch_wasm_demo -j ${CMAKE_JOBS} diff --git a/mv2/wasm/classes.js b/mv2/wasm/classes.js new file mode 100644 index 00000000..349b2000 --- /dev/null +++ b/mv2/wasm/classes.js @@ -0,0 +1,1010 @@ +/* + * 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. + */ + +const _IMAGENET_CATEGORIES = [ + "tench", + "goldfish", + "great white shark", + "tiger shark", + "hammerhead", + "electric ray", + "stingray", + "cock", + "hen", + "ostrich", + "brambling", + "goldfinch", + "house finch", + "junco", + "indigo bunting", + "robin", + "bulbul", + "jay", + "magpie", + "chickadee", + "water ouzel", + "kite", + "bald eagle", + "vulture", + "great grey owl", + "European fire salamander", + "common newt", + "eft", + "spotted salamander", + "axolotl", + "bullfrog", + "tree frog", + "tailed frog", + "loggerhead", + "leatherback turtle", + "mud turtle", + "terrapin", + "box turtle", + "banded gecko", + "common iguana", + "American chameleon", + "whiptail", + "agama", + "frilled lizard", + "alligator lizard", + "Gila monster", + "green lizard", + "African chameleon", + "Komodo dragon", + "African crocodile", + "American alligator", + "triceratops", + "thunder snake", + "ringneck snake", + "hognose snake", + "green snake", + "king snake", + "garter snake", + "water snake", + "vine snake", + "night snake", + "boa constrictor", + "rock python", + "Indian cobra", + "green mamba", + "sea snake", + "horned viper", + "diamondback", + "sidewinder", + "trilobite", + "harvestman", + "scorpion", + "black and gold garden spider", + "barn spider", + "garden spider", + "black widow", + "tarantula", + "wolf spider", + "tick", + "centipede", + "black grouse", + "ptarmigan", + "ruffed grouse", + "prairie chicken", + "peacock", + "quail", + "partridge", + "African grey", + "macaw", + "sulphur-crested cockatoo", + "lorikeet", + "coucal", + "bee eater", + "hornbill", + "hummingbird", + "jacamar", + "toucan", + "drake", + "red-breasted merganser", + "goose", + "black swan", + "tusker", + "echidna", + "platypus", + "wallaby", + "koala", + "wombat", + "jellyfish", + "sea anemone", + "brain coral", + "flatworm", + "nematode", + "conch", + "snail", + "slug", + "sea slug", + "chiton", + "chambered nautilus", + "Dungeness crab", + "rock crab", + "fiddler crab", + "king crab", + "American lobster", + "spiny lobster", + "crayfish", + "hermit crab", + "isopod", + "white stork", + "black stork", + "spoonbill", + "flamingo", + "little blue heron", + "American egret", + "bittern", + "crane bird", + "limpkin", + "European gallinule", + "American coot", + "bustard", + "ruddy turnstone", + "red-backed sandpiper", + "redshank", + "dowitcher", + "oystercatcher", + "pelican", + "king penguin", + "albatross", + "grey whale", + "killer whale", + "dugong", + "sea lion", + "Chihuahua", + "Japanese spaniel", + "Maltese dog", + "Pekinese", + "Shih-Tzu", + "Blenheim spaniel", + "papillon", + "toy terrier", + "Rhodesian ridgeback", + "Afghan hound", + "basset", + "beagle", + "bloodhound", + "bluetick", + "black-and-tan coonhound", + "Walker hound", + "English foxhound", + "redbone", + "borzoi", + "Irish wolfhound", + "Italian greyhound", + "whippet", + "Ibizan hound", + "Norwegian elkhound", + "otterhound", + "Saluki", + "Scottish deerhound", + "Weimaraner", + "Staffordshire bullterrier", + "American Staffordshire terrier", + "Bedlington terrier", + "Border terrier", + "Kerry blue terrier", + "Irish terrier", + "Norfolk terrier", + "Norwich terrier", + "Yorkshire terrier", + "wire-haired fox terrier", + "Lakeland terrier", + "Sealyham terrier", + "Airedale", + "cairn", + "Australian terrier", + "Dandie Dinmont", + "Boston bull", + "miniature schnauzer", + "giant schnauzer", + "standard schnauzer", + "Scotch terrier", + "Tibetan terrier", + "silky terrier", + "soft-coated wheaten terrier", + "West Highland white terrier", + "Lhasa", + "flat-coated retriever", + "curly-coated retriever", + "golden retriever", + "Labrador retriever", + "Chesapeake Bay retriever", + "German short-haired pointer", + "vizsla", + "English setter", + "Irish setter", + "Gordon setter", + "Brittany spaniel", + "clumber", + "English springer", + "Welsh springer spaniel", + "cocker spaniel", + "Sussex spaniel", + "Irish water spaniel", + "kuvasz", + "schipperke", + "groenendael", + "malinois", + "briard", + "kelpie", + "komondor", + "Old English sheepdog", + "Shetland sheepdog", + "collie", + "Border collie", + "Bouvier des Flandres", + "Rottweiler", + "German shepherd", + "Doberman", + "miniature pinscher", + "Greater Swiss Mountain dog", + "Bernese mountain dog", + "Appenzeller", + "EntleBucher", + "boxer", + "bull mastiff", + "Tibetan mastiff", + "French bulldog", + "Great Dane", + "Saint Bernard", + "Eskimo dog", + "malamute", + "Siberian husky", + "dalmatian", + "affenpinscher", + "basenji", + "pug", + "Leonberg", + "Newfoundland", + "Great Pyrenees", + "Samoyed", + "Pomeranian", + "chow", + "keeshond", + "Brabancon griffon", + "Pembroke", + "Cardigan", + "toy poodle", + "miniature poodle", + "standard poodle", + "Mexican hairless", + "timber wolf", + "white wolf", + "red wolf", + "coyote", + "dingo", + "dhole", + "African hunting dog", + "hyena", + "red fox", + "kit fox", + "Arctic fox", + "grey fox", + "tabby", + "tiger cat", + "Persian cat", + "Siamese cat", + "Egyptian cat", + "cougar", + "lynx", + "leopard", + "snow leopard", + "jaguar", + "lion", + "tiger", + "cheetah", + "brown bear", + "American black bear", + "ice bear", + "sloth bear", + "mongoose", + "meerkat", + "tiger beetle", + "ladybug", + "ground beetle", + "long-horned beetle", + "leaf beetle", + "dung beetle", + "rhinoceros beetle", + "weevil", + "fly", + "bee", + "ant", + "grasshopper", + "cricket", + "walking stick", + "cockroach", + "mantis", + "cicada", + "leafhopper", + "lacewing", + "dragonfly", + "damselfly", + "admiral", + "ringlet", + "monarch", + "cabbage butterfly", + "sulphur butterfly", + "lycaenid", + "starfish", + "sea urchin", + "sea cucumber", + "wood rabbit", + "hare", + "Angora", + "hamster", + "porcupine", + "fox squirrel", + "marmot", + "beaver", + "guinea pig", + "sorrel", + "zebra", + "hog", + "wild boar", + "warthog", + "hippopotamus", + "ox", + "water buffalo", + "bison", + "ram", + "bighorn", + "ibex", + "hartebeest", + "impala", + "gazelle", + "Arabian camel", + "llama", + "weasel", + "mink", + "polecat", + "black-footed ferret", + "otter", + "skunk", + "badger", + "armadillo", + "three-toed sloth", + "orangutan", + "gorilla", + "chimpanzee", + "gibbon", + "siamang", + "guenon", + "patas", + "baboon", + "macaque", + "langur", + "colobus", + "proboscis monkey", + "marmoset", + "capuchin", + "howler monkey", + "titi", + "spider monkey", + "squirrel monkey", + "Madagascar cat", + "indri", + "Indian elephant", + "African elephant", + "lesser panda", + "giant panda", + "barracouta", + "eel", + "coho", + "rock beauty", + "anemone fish", + "sturgeon", + "gar", + "lionfish", + "puffer", + "abacus", + "abaya", + "academic gown", + "accordion", + "acoustic guitar", + "aircraft carrier", + "airliner", + "airship", + "altar", + "ambulance", + "amphibian", + "analog clock", + "apiary", + "apron", + "ashcan", + "assault rifle", + "backpack", + "bakery", + "balance beam", + "balloon", + "ballpoint", + "Band Aid", + "banjo", + "bannister", + "barbell", + "barber chair", + "barbershop", + "barn", + "barometer", + "barrel", + "barrow", + "baseball", + "basketball", + "bassinet", + "bassoon", + "bathing cap", + "bath towel", + "bathtub", + "beach wagon", + "beacon", + "beaker", + "bearskin", + "beer bottle", + "beer glass", + "bell cote", + "bib", + "bicycle-built-for-two", + "bikini", + "binder", + "binoculars", + "birdhouse", + "boathouse", + "bobsled", + "bolo tie", + "bonnet", + "bookcase", + "bookshop", + "bottlecap", + "bow", + "bow tie", + "brass", + "brassiere", + "breakwater", + "breastplate", + "broom", + "bucket", + "buckle", + "bulletproof vest", + "bullet train", + "butcher shop", + "cab", + "caldron", + "candle", + "cannon", + "canoe", + "can opener", + "cardigan", + "car mirror", + "carousel", + "carpenter's kit", + "carton", + "car wheel", + "cash machine", + "cassette", + "cassette player", + "castle", + "catamaran", + "CD player", + "cello", + "cellular telephone", + "chain", + "chainlink fence", + "chain mail", + "chain saw", + "chest", + "chiffonier", + "chime", + "china cabinet", + "Christmas stocking", + "church", + "cinema", + "cleaver", + "cliff dwelling", + "cloak", + "clog", + "cocktail shaker", + "coffee mug", + "coffeepot", + "coil", + "combination lock", + "computer keyboard", + "confectionery", + "container ship", + "convertible", + "corkscrew", + "cornet", + "cowboy boot", + "cowboy hat", + "cradle", + "crane", + "crash helmet", + "crate", + "crib", + "Crock Pot", + "croquet ball", + "crutch", + "cuirass", + "dam", + "desk", + "desktop computer", + "dial telephone", + "diaper", + "digital clock", + "digital watch", + "dining table", + "dishrag", + "dishwasher", + "disk brake", + "dock", + "dogsled", + "dome", + "doormat", + "drilling platform", + "drum", + "drumstick", + "dumbbell", + "Dutch oven", + "electric fan", + "electric guitar", + "electric locomotive", + "entertainment center", + "envelope", + "espresso maker", + "face powder", + "feather boa", + "file", + "fireboat", + "fire engine", + "fire screen", + "flagpole", + "flute", + "folding chair", + "football helmet", + "forklift", + "fountain", + "fountain pen", + "four-poster", + "freight car", + "French horn", + "frying pan", + "fur coat", + "garbage truck", + "gasmask", + "gas pump", + "goblet", + "go-kart", + "golf ball", + "golfcart", + "gondola", + "gong", + "gown", + "grand piano", + "greenhouse", + "grille", + "grocery store", + "guillotine", + "hair slide", + "hair spray", + "half track", + "hammer", + "hamper", + "hand blower", + "hand-held computer", + "handkerchief", + "hard disc", + "harmonica", + "harp", + "harvester", + "hatchet", + "holster", + "home theater", + "honeycomb", + "hook", + "hoopskirt", + "horizontal bar", + "horse cart", + "hourglass", + "iPod", + "iron", + "jack-o'-lantern", + "jean", + "jeep", + "jersey", + "jigsaw puzzle", + "jinrikisha", + "joystick", + "kimono", + "knee pad", + "knot", + "lab coat", + "ladle", + "lampshade", + "laptop", + "lawn mower", + "lens cap", + "letter opener", + "library", + "lifeboat", + "lighter", + "limousine", + "liner", + "lipstick", + "Loafer", + "lotion", + "loudspeaker", + "loupe", + "lumbermill", + "magnetic compass", + "mailbag", + "mailbox", + "maillot", + "maillot tank suit", + "manhole cover", + "maraca", + "marimba", + "mask", + "matchstick", + "maypole", + "maze", + "measuring cup", + "medicine chest", + "megalith", + "microphone", + "microwave", + "military uniform", + "milk can", + "minibus", + "miniskirt", + "minivan", + "missile", + "mitten", + "mixing bowl", + "mobile home", + "Model T", + "modem", + "monastery", + "monitor", + "moped", + "mortar", + "mortarboard", + "mosque", + "mosquito net", + "motor scooter", + "mountain bike", + "mountain tent", + "mouse", + "mousetrap", + "moving van", + "muzzle", + "nail", + "neck brace", + "necklace", + "nipple", + "notebook", + "obelisk", + "oboe", + "ocarina", + "odometer", + "oil filter", + "organ", + "oscilloscope", + "overskirt", + "oxcart", + "oxygen mask", + "packet", + "paddle", + "paddlewheel", + "padlock", + "paintbrush", + "pajama", + "palace", + "panpipe", + "paper towel", + "parachute", + "parallel bars", + "park bench", + "parking meter", + "passenger car", + "patio", + "pay-phone", + "pedestal", + "pencil box", + "pencil sharpener", + "perfume", + "Petri dish", + "photocopier", + "pick", + "pickelhaube", + "picket fence", + "pickup", + "pier", + "piggy bank", + "pill bottle", + "pillow", + "ping-pong ball", + "pinwheel", + "pirate", + "pitcher", + "plane", + "planetarium", + "plastic bag", + "plate rack", + "plow", + "plunger", + "Polaroid camera", + "pole", + "police van", + "poncho", + "pool table", + "pop bottle", + "pot", + "potter's wheel", + "power drill", + "prayer rug", + "printer", + "prison", + "projectile", + "projector", + "puck", + "punching bag", + "purse", + "quill", + "quilt", + "racer", + "racket", + "radiator", + "radio", + "radio telescope", + "rain barrel", + "recreational vehicle", + "reel", + "reflex camera", + "refrigerator", + "remote control", + "restaurant", + "revolver", + "rifle", + "rocking chair", + "rotisserie", + "rubber eraser", + "rugby ball", + "rule", + "running shoe", + "safe", + "safety pin", + "saltshaker", + "sandal", + "sarong", + "sax", + "scabbard", + "scale", + "school bus", + "schooner", + "scoreboard", + "screen", + "screw", + "screwdriver", + "seat belt", + "sewing machine", + "shield", + "shoe shop", + "shoji", + "shopping basket", + "shopping cart", + "shovel", + "shower cap", + "shower curtain", + "ski", + "ski mask", + "sleeping bag", + "slide rule", + "sliding door", + "slot", + "snorkel", + "snowmobile", + "snowplow", + "soap dispenser", + "soccer ball", + "sock", + "solar dish", + "sombrero", + "soup bowl", + "space bar", + "space heater", + "space shuttle", + "spatula", + "speedboat", + "spider web", + "spindle", + "sports car", + "spotlight", + "stage", + "steam locomotive", + "steel arch bridge", + "steel drum", + "stethoscope", + "stole", + "stone wall", + "stopwatch", + "stove", + "strainer", + "streetcar", + "stretcher", + "studio couch", + "stupa", + "submarine", + "suit", + "sundial", + "sunglass", + "sunglasses", + "sunscreen", + "suspension bridge", + "swab", + "sweatshirt", + "swimming trunks", + "swing", + "switch", + "syringe", + "table lamp", + "tank", + "tape player", + "teapot", + "teddy", + "television", + "tennis ball", + "thatch", + "theater curtain", + "thimble", + "thresher", + "throne", + "tile roof", + "toaster", + "tobacco shop", + "toilet seat", + "torch", + "totem pole", + "tow truck", + "toyshop", + "tractor", + "trailer truck", + "tray", + "trench coat", + "tricycle", + "trimaran", + "tripod", + "triumphal arch", + "trolleybus", + "trombone", + "tub", + "turnstile", + "typewriter keyboard", + "umbrella", + "unicycle", + "upright", + "vacuum", + "vase", + "vault", + "velvet", + "vending machine", + "vestment", + "viaduct", + "violin", + "volleyball", + "waffle iron", + "wall clock", + "wallet", + "wardrobe", + "warplane", + "washbasin", + "washer", + "water bottle", + "water jug", + "water tower", + "whiskey jug", + "whistle", + "wig", + "window screen", + "window shade", + "Windsor tie", + "wine bottle", + "wing", + "wok", + "wooden spoon", + "wool", + "worm fence", + "wreck", + "yawl", + "yurt", + "web site", + "comic book", + "crossword puzzle", + "street sign", + "traffic light", + "book jacket", + "menu", + "plate", + "guacamole", + "consomme", + "hot pot", + "trifle", + "ice cream", + "ice lolly", + "French loaf", + "bagel", + "pretzel", + "cheeseburger", + "hotdog", + "mashed potato", + "head cabbage", + "broccoli", + "cauliflower", + "zucchini", + "spaghetti squash", + "acorn squash", + "butternut squash", + "cucumber", + "artichoke", + "bell pepper", + "cardoon", + "mushroom", + "Granny Smith", + "strawberry", + "orange", + "lemon", + "fig", + "pineapple", + "banana", + "jackfruit", + "custard apple", + "pomegranate", + "hay", + "carbonara", + "chocolate sauce", + "dough", + "meat loaf", + "pizza", + "potpie", + "burrito", + "red wine", + "espresso", + "cup", + "eggnog", + "alp", + "bubble", + "cliff", + "coral reef", + "geyser", + "lakeside", + "promontory", + "sandbar", + "seashore", + "valley", + "volcano", + "ballplayer", + "groom", + "scuba diver", + "rapeseed", + "daisy", + "yellow lady's slipper", + "corn", + "acorn", + "hip", + "buckeye", + "coral fungus", + "agaric", + "gyromitra", + "stinkhorn", + "earthstar", + "hen-of-the-woods", + "bolete", + "ear", + "toilet tissue", +]; diff --git a/mv2/wasm/demo.html b/mv2/wasm/demo.html new file mode 100644 index 00000000..db62fd9d --- /dev/null +++ b/mv2/wasm/demo.html @@ -0,0 +1,24 @@ + + + + + + + Executorch Wasm Demo + + + + +

No model uploaded

+ +

+ + + + + diff --git a/mv2/wasm/demo.js b/mv2/wasm/demo.js new file mode 100644 index 00000000..739f601e --- /dev/null +++ b/mv2/wasm/demo.js @@ -0,0 +1,161 @@ +/* + * 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. + */ + +const et = Module; +et.onRuntimeInitialized = () => { + const model_button = document.getElementById("upload_model_button"); + model_button.addEventListener("click", openFilePickerModel); + + const image_button = document.getElementById("upload_image_button"); + image_button.addEventListener("click", openFilePickerImage); +} + +let module = null; + +function loadModelFile(file) { + const reader = new FileReader(); + reader.onload = function(event) { + const buffer = event.target.result; + + const mod = et.Module.load(buffer); + const modelText = document.getElementById("model_text"); + + try { + mod.loadMethod("forward"); + } catch (e) { + modelText.textContent = "Failed to load forward method: " + e; + return; + } + + const methodMeta = mod.getMethodMeta("forward"); + if (methodMeta.inputTags.length != 1) { + modelText.textContent = "Error: Expected input size of 1, got " + methodMeta.inputTags.length; + modelText.style.color = "red"; + return; + } + + if (methodMeta.inputTags[0] != et.Tag.Tensor) { + modelText.textContent = "Error: Expected input type to be Tensor, got " + methodMeta.inputTags[0].name; + modelText.style.color = "red"; + return; + } + + const inputMeta = methodMeta.inputTensorMeta[0]; + + if (inputMeta.sizes[0] != 1 || inputMeta.sizes[1] != 3 || inputMeta.sizes[2] != 224 || inputMeta.sizes[3] != 224) { + modelText.textContent = "Error: Expected input shape to be [1, 3, 224, 224], got " + inputMeta.sizes; + modelText.style.color = "red"; + return; + } + + if (inputMeta.scalarType != et.ScalarType.Float) { + modelText.textContent = "Error: Expected input type to be Float, got " + inputMeta.scalarType.name; + modelText.style.color = "red"; + return; + } + + if (methodMeta.outputTags.length != 1) { + modelText.textContent = "Error: Expected output size of 1, got " + methodMeta.outputTags.length; + modelText.style.color = "red"; + return; + } + + if (methodMeta.outputTags[0] != et.Tag.Tensor) { + modelText.textContent = "Error: Expected output type to be Tensor, got " + methodMeta.outputTags[0].name; + modelText.style.color = "red"; + return; + } + + const outputMeta = methodMeta.outputTensorMeta[0]; + + if (outputMeta.sizes[0] != 1 || outputMeta.sizes[1] != 1000) { + modelText.textContent = "Error: Expected output shape to be [1, 1000], got " + outputMeta.sizes; + modelText.style.color = "red"; + return; + } + + if (outputMeta.scalarType != et.ScalarType.Float) { + modelText.textContent = "Error: Expected output type to be Float, got " + outputMeta.scalarType.name; + modelText.style.color = "red"; + return; + } + + module = mod; + modelText.textContent = 'Uploaded model: ' + file.name; + modelText.style.color = null; + document.getElementById("upload_image_button").disabled = false; + }; + reader.readAsArrayBuffer(file); +} + +function* generateTensorData(data) { + for (let j = 0; j < 3; j++) { + for (let i = 0; i < data.length; i += 4) { + yield data[i + j] / 255.0; + } + } +} + +function loadImageFile(file) { + const img = new Image(); + img.onload = function() { + const canvas = document.getElementById("canvas"); + const ctx = canvas.getContext("2d", { willReadFrequently: true }); + ctx.drawImage(img, 0, 0, canvas.width, canvas.height); + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const inputTensor = et.Tensor.fromIter([1, 3, 224, 224], generateTensorData(imageData.data)); + const output = module.forward(inputTensor); + const argmax = output[0].data.reduce((iMax, elem, i, arr) => elem > arr[iMax] ? i : iMax, 0); + document.getElementById("class_text").textContent = _IMAGENET_CATEGORIES[argmax]; + } + img.src = URL.createObjectURL(file); +} + +async function openFilePickerModel() { + try { + const [fileHandle] = await window.showOpenFilePicker({ + types: [{ + description: 'Model Files', + accept: { + 'application/octet-stream': ['.pte'], + }, + }], + multiple: false, // Set to true for multiple file selection + }); + const file = await fileHandle.getFile(); + loadModelFile(file); + } catch (err) { + if (err.name === 'AbortError') { + // Handle user abort silently + } else { + console.error('File picker error:', err); + } + } +} + +async function openFilePickerImage() { + try { + const [fileHandle] = await window.showOpenFilePicker({ + types: [{ + description: "Images", + accept: { + "image/*": [".png", ".gif", ".jpeg", ".jpg"], + }, + }], + multiple: false, // Set to true for multiple file selection + }); + const file = await fileHandle.getFile(); + loadImageFile(file); + } catch (err) { + if (err.name === 'AbortError') { + // Handle user abort silently + } else { + console.error('File picker error:', err); + } + } +} diff --git a/mv2/wasm/executorch b/mv2/wasm/executorch new file mode 160000 index 00000000..d4c78ab2 --- /dev/null +++ b/mv2/wasm/executorch @@ -0,0 +1 @@ +Subproject commit d4c78ab22c47b510bdf4c82ba1bc523f44747633 From 05981196be3199b87d1ff967f01f238202a452b4 Mon Sep 17 00:00:00 2001 From: Conan Jeffrey Truong Date: Thu, 31 Jul 2025 11:32:54 -0700 Subject: [PATCH 2/6] Added more instructions --- mv2/wasm/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mv2/wasm/README.md b/mv2/wasm/README.md index f17659b6..87719a32 100644 --- a/mv2/wasm/README.md +++ b/mv2/wasm/README.md @@ -5,10 +5,21 @@ This demo showcases the capabilities of ExecuTorch's JavaScript bindings. It is ## Prerequisites - [Emscripten](https://emscripten.org/docs/getting_started/Tutorial.html) + - Refer to the [Wasm example Readme](https://github.com/pytorch/executorch/blob/main/examples/wasm/README.md) for a quick setup guide. ## Building and Running ``` +# Clone executorch submodule +git submodule update --init + +# Set up Executorch +cd executorch +./install_executorch.sh +./install_executorch.sh --clean + +cd .. + # Build the demo bash build.sh From 23297c7be5a2d0816ff6c9546e9fa021fde9325c Mon Sep 17 00:00:00 2001 From: Conan Jeffrey Truong Date: Fri, 1 Aug 2025 10:53:33 -0700 Subject: [PATCH 3/6] Moved error checking to separate function --- mv2/wasm/demo.js | 160 ++++++++++++++++++++++++----------------------- 1 file changed, 83 insertions(+), 77 deletions(-) diff --git a/mv2/wasm/demo.js b/mv2/wasm/demo.js index 739f601e..2cc0b119 100644 --- a/mv2/wasm/demo.js +++ b/mv2/wasm/demo.js @@ -8,89 +8,95 @@ const et = Module; et.onRuntimeInitialized = () => { - const model_button = document.getElementById("upload_model_button"); - model_button.addEventListener("click", openFilePickerModel); + const model_button = document.getElementById("upload_model_button"); + model_button.addEventListener("click", openFilePickerModel); - const image_button = document.getElementById("upload_image_button"); - image_button.addEventListener("click", openFilePickerImage); + const image_button = document.getElementById("upload_image_button"); + image_button.addEventListener("click", openFilePickerImage); } let module = null; +function verifyModel(mod, modelText) { + try { + mod.loadMethod("forward"); + } catch (e) { + modelText.textContent = "Failed to load forward method: " + e; + return false; + } + + const methodMeta = mod.getMethodMeta("forward"); + if (methodMeta.inputTags.length != 1) { + modelText.textContent = "Error: Expected input size of 1, got " + methodMeta.inputTags.length; + modelText.style.color = "red"; + return false; + } + + if (methodMeta.inputTags[0] != et.Tag.Tensor) { + modelText.textContent = "Error: Expected input type to be Tensor, got " + methodMeta.inputTags[0].name; + modelText.style.color = "red"; + return false; + } + + const inputMeta = methodMeta.inputTensorMeta[0]; + + if (inputMeta.sizes[0] != 1 || inputMeta.sizes[1] != 3 || inputMeta.sizes[2] != 224 || inputMeta.sizes[3] != 224) { + modelText.textContent = "Error: Expected input shape to be [1, 3, 224, 224], got " + inputMeta.sizes; + modelText.style.color = "red"; + return false; + } + + if (inputMeta.scalarType != et.ScalarType.Float) { + modelText.textContent = "Error: Expected input type to be Float, got " + inputMeta.scalarType.name; + modelText.style.color = "red"; + return false; + } + + if (methodMeta.outputTags.length != 1) { + modelText.textContent = "Error: Expected output size of 1, got " + methodMeta.outputTags.length; + modelText.style.color = "red"; + return false; + } + + if (methodMeta.outputTags[0] != et.Tag.Tensor) { + modelText.textContent = "Error: Expected output type to be Tensor, got " + methodMeta.outputTags[0].name; + modelText.style.color = "red"; + return false; + } + + const outputMeta = methodMeta.outputTensorMeta[0]; + + if (outputMeta.sizes[0] != 1 || outputMeta.sizes[1] != 1000) { + modelText.textContent = "Error: Expected output shape to be [1, 1000], got " + outputMeta.sizes; + modelText.style.color = "red"; + return false; + } + + if (outputMeta.scalarType != et.ScalarType.Float) { + modelText.textContent = "Error: Expected output type to be Float, got " + outputMeta.scalarType.name; + modelText.style.color = "red"; + return false; + } + + return true; +} + function loadModelFile(file) { - const reader = new FileReader(); - reader.onload = function(event) { - const buffer = event.target.result; - - const mod = et.Module.load(buffer); - const modelText = document.getElementById("model_text"); - - try { - mod.loadMethod("forward"); - } catch (e) { - modelText.textContent = "Failed to load forward method: " + e; - return; - } - - const methodMeta = mod.getMethodMeta("forward"); - if (methodMeta.inputTags.length != 1) { - modelText.textContent = "Error: Expected input size of 1, got " + methodMeta.inputTags.length; - modelText.style.color = "red"; - return; - } - - if (methodMeta.inputTags[0] != et.Tag.Tensor) { - modelText.textContent = "Error: Expected input type to be Tensor, got " + methodMeta.inputTags[0].name; - modelText.style.color = "red"; - return; - } - - const inputMeta = methodMeta.inputTensorMeta[0]; - - if (inputMeta.sizes[0] != 1 || inputMeta.sizes[1] != 3 || inputMeta.sizes[2] != 224 || inputMeta.sizes[3] != 224) { - modelText.textContent = "Error: Expected input shape to be [1, 3, 224, 224], got " + inputMeta.sizes; - modelText.style.color = "red"; - return; - } - - if (inputMeta.scalarType != et.ScalarType.Float) { - modelText.textContent = "Error: Expected input type to be Float, got " + inputMeta.scalarType.name; - modelText.style.color = "red"; - return; - } - - if (methodMeta.outputTags.length != 1) { - modelText.textContent = "Error: Expected output size of 1, got " + methodMeta.outputTags.length; - modelText.style.color = "red"; - return; - } - - if (methodMeta.outputTags[0] != et.Tag.Tensor) { - modelText.textContent = "Error: Expected output type to be Tensor, got " + methodMeta.outputTags[0].name; - modelText.style.color = "red"; - return; - } - - const outputMeta = methodMeta.outputTensorMeta[0]; - - if (outputMeta.sizes[0] != 1 || outputMeta.sizes[1] != 1000) { - modelText.textContent = "Error: Expected output shape to be [1, 1000], got " + outputMeta.sizes; - modelText.style.color = "red"; - return; - } - - if (outputMeta.scalarType != et.ScalarType.Float) { - modelText.textContent = "Error: Expected output type to be Float, got " + outputMeta.scalarType.name; - modelText.style.color = "red"; - return; - } - - module = mod; - modelText.textContent = 'Uploaded model: ' + file.name; - modelText.style.color = null; - document.getElementById("upload_image_button").disabled = false; - }; - reader.readAsArrayBuffer(file); + const reader = new FileReader(); + reader.onload = function(event) { + const buffer = event.target.result; + + const mod = et.Module.load(buffer); + const modelText = document.getElementById("model_text"); + + if (verifyModel(mod, modelText)) { + module = mod; + modelText.textContent = 'Uploaded model: ' + file.name; + modelText.style.color = null; + document.getElementById("upload_image_button").disabled = false; + } + }; + reader.readAsArrayBuffer(file); } function* generateTensorData(data) { From 4f854317940520d6de93034d126d105fc9dabb8c Mon Sep 17 00:00:00 2001 From: Conan Jeffrey Truong Date: Fri, 1 Aug 2025 13:08:00 -0700 Subject: [PATCH 4/6] Clarified the readme --- mv2/wasm/README.md | 51 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/mv2/wasm/README.md b/mv2/wasm/README.md index 87719a32..ddcb11c6 100644 --- a/mv2/wasm/README.md +++ b/mv2/wasm/README.md @@ -2,23 +2,48 @@ This demo showcases the capabilities of ExecuTorch's JavaScript bindings. It is able to load a model, run inference, and classify an image natively in the browser. -## Prerequisites +## Installing Emscripten -- [Emscripten](https://emscripten.org/docs/getting_started/Tutorial.html) - - Refer to the [Wasm example Readme](https://github.com/pytorch/executorch/blob/main/examples/wasm/README.md) for a quick setup guide. +[Emscripten](https://emscripten.org/index.html) is necessary to compile ExecuTorch for Wasm. You can install Emscripten with these commands: -## Building and Running +```bash +# Clone the emsdk repository +git clone https://github.com/emscripten-core/emsdk.git +cd emsdk + +# Download and install version 4.0.10 of the SDK +./emsdk install 4.0.10 +./emsdk activate 4.0.10 + +# Add the Emscripten environment variables to your shell +source ./emsdk_env.sh +``` + +## Setting up ExecuTorch and Generating the Model File + +1. Clone the ExecuTorch submodule +```bash +git submodule update --init executorch +``` + +2. Following the setup guide in [Setting up ExecuTorch](https://pytorch.org/executorch/main/getting-started-setup) +you should be able to get the basic development environment for ExecuTorch working. + +3. Using the script `portable/scripts/export.py` generate the MobileNetV2 binary file for this demo. +```bash +cd executorch # To the root of the executorch repo + +# Export the model file for the demo +python3 -m examples.portable.scripts.export --model_name="mv2" ``` -# Clone executorch submodule -git submodule update --init -# Set up Executorch -cd executorch -./install_executorch.sh -./install_executorch.sh --clean +## Building and Running + +Once you have Emscripten installed, ExecuTorch set up, and the model file generated, you can build and run the demo. -cd .. +```bash +cd mv2/wasm # The directory containing this README # Build the demo bash build.sh @@ -32,6 +57,6 @@ The page will be available at http://localhost:8000/demo.html. ## Demo Features - Load a model from a file - - It currently only supports the MobileNetv2 model. Passing in a model with different input/output shapes will result in an error. - - You can generate the model file by following the instructions in the [Portable Mode Readme](https://github.com/pytorch/executorch/blob/main/examples/portable/README.md). + - It currently only supports the MobileNetV2 model. Passing in a model with different input/output shapes will result in an error. - Run inference on an image + - Supported formats: `.png`, `.gif`, `.jpeg`, `.jpg` From 3e231118783b67000bad6a6db0ea7154c5f9a1a7 Mon Sep 17 00:00:00 2001 From: Conan Jeffrey Truong Date: Fri, 1 Aug 2025 16:16:50 -0700 Subject: [PATCH 5/6] Simplified installation instructions --- mv2/wasm/README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/mv2/wasm/README.md b/mv2/wasm/README.md index ddcb11c6..bc39de16 100644 --- a/mv2/wasm/README.md +++ b/mv2/wasm/README.md @@ -21,15 +21,19 @@ source ./emsdk_env.sh ## Setting up ExecuTorch and Generating the Model File -1. Clone the ExecuTorch submodule +Make sure you have the system requirements listed in the [Getting Started Guide](https://docs.pytorch.org/executorch/main/getting-started.html#system-requirements) before continuing. + +1. Install ExecuTorch from PyPI. ```bash -git submodule update --init executorch +pip3 install executorch ``` -2. Following the setup guide in [Setting up ExecuTorch](https://pytorch.org/executorch/main/getting-started-setup) -you should be able to get the basic development environment for ExecuTorch working. +2. Update the ExecuTorch submodule. +```bash +git submodule update --init --recursive executorch +``` -3. Using the script `portable/scripts/export.py` generate the MobileNetV2 binary file for this demo. +3. Using the script `examples/portable/scripts/export.py` generate the MobileNetV2 binary file for this demo. ```bash cd executorch # To the root of the executorch repo From d499be67c566b3302bee24473dbb25d6a9f85831 Mon Sep 17 00:00:00 2001 From: Conan Jeffrey Truong Date: Tue, 5 Aug 2025 10:01:07 -0700 Subject: [PATCH 6/6] Removed unnecessary options --- mv2/wasm/build.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/mv2/wasm/build.sh b/mv2/wasm/build.sh index dcef3113..59f9ab69 100644 --- a/mv2/wasm/build.sh +++ b/mv2/wasm/build.sh @@ -10,11 +10,8 @@ CMAKE_OUT=build emcmake cmake . -DEXECUTORCH_BUILD_WASM=ON \ -DEXECUTORCH_BUILD_EXTENSION_DATA_LOADER=ON \ -DEXECUTORCH_BUILD_EXTENSION_FLAT_TENSOR=ON \ - -DEXECUTORCH_BUILD_DEVTOOLS=ON \ -DEXECUTORCH_BUILD_EXTENSION_MODULE=ON \ -DEXECUTORCH_BUILD_EXTENSION_TENSOR=ON \ - -DEXECUTORCH_BUILD_EXTENSION_RUNNER_UTIL=ON \ - -DEXECUTORCH_BUILD_WASM_DEMO=ON \ -DCMAKE_BUILD_TYPE=Release \ -B"${CMAKE_OUT}"