diff --git a/Tizen.web/TextGenerationLlama2/NsdService/.cproject b/Tizen.web/TextGenerationLlama2/NsdService/.cproject new file mode 100644 index 00000000..cf005731 --- /dev/null +++ b/Tizen.web/TextGenerationLlama2/NsdService/.cproject @@ -0,0 +1,658 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tizen.web/TextGenerationLlama2/NsdService/.exportMap b/Tizen.web/TextGenerationLlama2/NsdService/.exportMap new file mode 100644 index 00000000..de305161 --- /dev/null +++ b/Tizen.web/TextGenerationLlama2/NsdService/.exportMap @@ -0,0 +1,5 @@ +{ + global: main; + _IO_*; + local: *; +}; diff --git a/Tizen.web/TextGenerationLlama2/NsdService/.project b/Tizen.web/TextGenerationLlama2/NsdService/.project new file mode 100644 index 00000000..09323794 --- /dev/null +++ b/Tizen.web/TextGenerationLlama2/NsdService/.project @@ -0,0 +1,46 @@ + + + NsdService + + + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + full,incremental, + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + + + + 1729128501799 + + 26 + + org.eclipse.ui.ide.multiFilter + 1.0-projectRelativePath-matches-false-false-*/.tpk + + + + 1729128501802 + + 6 + + org.eclipse.ui.ide.multiFilter + 1.0-name-matches-false-false-project_def.prop + + + + diff --git a/Tizen.web/TextGenerationLlama2/NsdService/.tproject b/Tizen.web/TextGenerationLlama2/NsdService/.tproject new file mode 100644 index 00000000..3d473b57 --- /dev/null +++ b/Tizen.web/TextGenerationLlama2/NsdService/.tproject @@ -0,0 +1,12 @@ + + + + + tizen-8.0 + + + + + + + diff --git a/Tizen.web/TextGenerationLlama2/NsdService/inc/nsdservice.h b/Tizen.web/TextGenerationLlama2/NsdService/inc/nsdservice.h new file mode 100644 index 00000000..baec0f31 --- /dev/null +++ b/Tizen.web/TextGenerationLlama2/NsdService/inc/nsdservice.h @@ -0,0 +1,21 @@ +/** + * @file main.h + * @date 04 Jul 2024 + * @brief Tizen native service for hybrid application + * @author Yelin Jeong + * @bug No known bugs. + */ + +#ifndef __NSD_SERVICE_H__ +#define __NSD_SERVICE_H__ + +#include + +#ifdef LOG_TAG +#undef LOG_TAG +#endif +#define LOG_TAG "nsd_service" + +#define REMOTE_APP_ID "lfebC6UrMY.Llama2" + +#endif /* __NSD_SERVICE_H__ */ diff --git a/Tizen.web/TextGenerationLlama2/NsdService/project_def.prop b/Tizen.web/TextGenerationLlama2/NsdService/project_def.prop new file mode 100644 index 00000000..b07939a3 --- /dev/null +++ b/Tizen.web/TextGenerationLlama2/NsdService/project_def.prop @@ -0,0 +1,11 @@ +APPNAME = nsdservice + +type = app +profile = tizen-8.0 + +USER_SRCS = src/nsdservice.c +USER_DEFS = +USER_INC_DIRS = inc +USER_OBJS = +USER_LIBS = +USER_EDCS = diff --git a/Tizen.web/TextGenerationLlama2/NsdService/shared/res/nsdservice.png b/Tizen.web/TextGenerationLlama2/NsdService/shared/res/nsdservice.png new file mode 100644 index 00000000..9765b1bd Binary files /dev/null and b/Tizen.web/TextGenerationLlama2/NsdService/shared/res/nsdservice.png differ diff --git a/Tizen.web/TextGenerationLlama2/NsdService/src/nsdservice.c b/Tizen.web/TextGenerationLlama2/NsdService/src/nsdservice.c new file mode 100644 index 00000000..879aadba --- /dev/null +++ b/Tizen.web/TextGenerationLlama2/NsdService/src/nsdservice.c @@ -0,0 +1,170 @@ +/** + * @file main.c + * @date 18 October 2024 + * @brief Tizen native service for hybrid application + * @author Yelin Jeong + * @bug No known bugs. + */ + +#include "nsdservice.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief Service app create callback + */ +static bool _create_cb(void *user_data) { + dlog_print(DLOG_INFO, LOG_TAG, "Callback create\n"); + return true; +} + +/** + * @brief Service app terminate callback + */ +static void _terminate_cb(void *user_data) { + dlog_print(DLOG_INFO, LOG_TAG, "Callback terminate\n"); +} + +/** + * @brief Service app app control callback + */ +static void _app_control_cb(app_control_h app_control, void *user_data) { + dlog_print(DLOG_INFO, LOG_TAG, "Callback app_control\n"); +} + +/** + * @brief Send dnssd remote service information + */ +void _dnssd_send_found(dnssd_service_h dnssd_remote_service, bool is_available) { + char *service_name = NULL; + char *service_type = NULL; + char *ip_v4_address = NULL; + char *ip_v6_address = NULL; + int ret, port = 0; + + ret = dnssd_service_get_type(dnssd_remote_service, &service_type); + if (ret != DNSSD_ERROR_NONE || service_type == NULL) { + dlog_print(DLOG_ERROR, LOG_TAG, "Failed to get service type (%d)", ret); + return; + } + + ret = dnssd_service_get_name(dnssd_remote_service, &service_name); + if (ret != DNSSD_ERROR_NONE || service_name == NULL) { + dlog_print(DLOG_ERROR, LOG_TAG, "Failed to get service name (%d)", ret); + return; + } + + ret = dnssd_service_get_ip(dnssd_remote_service, &ip_v4_address, + &ip_v6_address); + if (ret != DNSSD_ERROR_NONE || ip_v4_address == NULL) { + dlog_print(DLOG_ERROR, LOG_TAG, "Failed to get IP address (%d)", ret); + return; + } + + ret = dnssd_service_get_port(dnssd_remote_service, &port); + if (ret != DNSSD_ERROR_NONE) { + dlog_print(DLOG_ERROR, LOG_TAG, "Failed to get port number (%d)", ret); + return; + } + + char port_buffer[CHAR_BIT]; + snprintf(port_buffer, CHAR_BIT, "%d", port); + + bundle *b = bundle_create(); + bundle_add_str(b, "name", service_name); + bundle_add_str(b, "ip", ip_v4_address); + bundle_add_str(b, "port", port_buffer); + + if (is_available) + ret = message_port_send_message(REMOTE_APP_ID, "STATE_AVAILABLE", b); + else + ret = message_port_send_message(REMOTE_APP_ID, "STATE_UNAVAILABLE", b); + + if (ret != MESSAGE_PORT_ERROR_NONE) { + dlog_print(DLOG_ERROR, LOG_TAG, "Failed to send message to %s", + REMOTE_APP_ID); + } + bundle_free(b); + + if (service_name) + free(service_name); + if (service_type) + free(service_type); + if (ip_v4_address) + free(ip_v4_address); + if (ip_v6_address) + free(ip_v6_address); +} + +/** + * @brief dnssd found callback + */ +void _found_cb(dnssd_service_state_e state, + dnssd_service_h dnssd_remote_service, void *user_data) { + switch (state) { + case DNSSD_SERVICE_STATE_AVAILABLE: + /* DNS-SD service found */ + _dnssd_send_found(dnssd_remote_service, true); + break; + case DNSSD_SERVICE_STATE_UNAVAILABLE: + /* DNS-SD service becomes unavailable */ + _dnssd_send_found(dnssd_remote_service, false); + break; + case DNSSD_SERVICE_STATE_NAME_LOOKUP_FAILED: + /* Browsing failed */ + dlog_print(DLOG_ERROR, LOG_TAG, "Browse Failure\n"); + break; + case DNSSD_SERVICE_STATE_HOST_NAME_LOOKUP_FAILED: + /* Resolving service name failed */ + dlog_print(DLOG_ERROR, LOG_TAG, "Resolve Service Name Failure\n"); + break; + case DNSSD_SERVICE_STATE_ADDRESS_LOOKUP_FAILED: + /* Resolving service address failed */ + dlog_print(DLOG_ERROR, LOG_TAG, "Resolve Service Address\n"); + break; + default: + dlog_print(DLOG_ERROR, LOG_TAG, "Unknown Browse State\n"); + break; + } +} + +/** + * @brief Main function. + */ +int main(int argc, char *argv[]) { + dnssd_browser_h browser_handle; + char *target = "_nsd_offloading._tcp"; + int ret; + bool found; + + service_app_lifecycle_callback_s event_callback = {.create = _create_cb, + .terminate = _terminate_cb, + .app_control = + _app_control_cb}; + + ret = dnssd_initialize(); + if (ret != DNSSD_ERROR_NONE) + dlog_print(DLOG_ERROR, LOG_TAG, "Dnssd initialize failed"); + + ret = dnssd_start_browsing_service(target, &browser_handle, _found_cb, NULL); + if (ret == DNSSD_ERROR_NONE) + dlog_print(DLOG_DEBUG, LOG_TAG, "Start browsing"); + + ret = message_port_check_remote_port(REMOTE_APP_ID, "STATE_AVAILABLE", &found); + if (ret != MESSAGE_PORT_ERROR_NONE || !found) { + dlog_print(DLOG_ERROR, LOG_TAG, "Failed to check remote port"); + } + + ret = message_port_check_remote_port(REMOTE_APP_ID, "STATE_UNAVAILABLE", &found); + if (ret != MESSAGE_PORT_ERROR_NONE || !found) { + dlog_print(DLOG_ERROR, LOG_TAG, "Failed to check remote port"); + } + + return service_app_main(argc, argv, &event_callback, NULL); +} diff --git a/Tizen.web/TextGenerationLlama2/NsdService/tizen-manifest.xml b/Tizen.web/TextGenerationLlama2/NsdService/tizen-manifest.xml new file mode 100644 index 00000000..10a8ef53 --- /dev/null +++ b/Tizen.web/TextGenerationLlama2/NsdService/tizen-manifest.xml @@ -0,0 +1,11 @@ + + + + + nsdservice.png + + + + http://tizen.org/privilege/internet + + diff --git a/Tizen.web/TextGenerationLlama2/README.md b/Tizen.web/TextGenerationLlama2/README.md new file mode 100644 index 00000000..83814ed0 --- /dev/null +++ b/Tizen.web/TextGenerationLlama2/README.md @@ -0,0 +1,19 @@ +# Text Generation Sample App +## Description +* This is a sample application of Tizen ML offloading. +* If you want to run it on your device, Tizen 8.0 or higher is required. +* `appsrc` and `tensor_query_client` element are used. + +## How to build and run +```sh +$ tizen build-web -- WebApplication +$ tizen package -t wgt -- WebApplication/.buildResult/ +$ tizen build-native -a arm -c llvm -C Debug -- NsdService/ +$ tizen package -t tpk -- NsdService/Debug/ +$ tizen package -t wgt -r NsdService/Debug/lfebC6UrMY.nsdservice-1.0.0-arm.tpk -- WebApplication/.buildResult/Llama2.wgt +$ tizen install -n WebApplication/.buildResult/Llama2.wgt -t rpi4 +$ tizen run -p lfebC6UrMY -t rpi4 +``` + +## Demo +![Alt demo](./text_generation.png) diff --git a/Tizen.web/TextGenerationLlama2/WebApplication/.project b/Tizen.web/TextGenerationLlama2/WebApplication/.project new file mode 100644 index 00000000..c736f137 --- /dev/null +++ b/Tizen.web/TextGenerationLlama2/WebApplication/.project @@ -0,0 +1,24 @@ + + + Llama2 + + + + + + json.validation.builder + + + + + org.tizen.web.project.builder.WebBuilder + + + + + + json.validation.nature + org.eclipse.wst.jsdt.core.jsNature + org.tizen.web.project.builder.WebNature + + diff --git a/Tizen.web/TextGenerationLlama2/WebApplication/.tproject b/Tizen.web/TextGenerationLlama2/WebApplication/.tproject new file mode 100644 index 00000000..0e708fec --- /dev/null +++ b/Tizen.web/TextGenerationLlama2/WebApplication/.tproject @@ -0,0 +1,11 @@ + + + + + tizen-8.0 + + + + + + diff --git a/Tizen.web/TextGenerationLlama2/WebApplication/config.xml b/Tizen.web/TextGenerationLlama2/WebApplication/config.xml new file mode 100644 index 00000000..d80455c5 --- /dev/null +++ b/Tizen.web/TextGenerationLlama2/WebApplication/config.xml @@ -0,0 +1,13 @@ + + + + + + + Llama2 + + + + + + diff --git a/Tizen.web/TextGenerationLlama2/WebApplication/css/style.css b/Tizen.web/TextGenerationLlama2/WebApplication/css/style.css new file mode 100644 index 00000000..9943dfb0 --- /dev/null +++ b/Tizen.web/TextGenerationLlama2/WebApplication/css/style.css @@ -0,0 +1,37 @@ +html, +body { + width: 100%; + height: 100%; + margin: auto; + padding: 10px 10px 10px 10px; + background-color: #222222; + color: #ffffff; +} +.page { + width: 100%; + height: 100%; +} +.contents { + display: table-cell; + vertical-align: middle; + text-align: center; + -webkit-tap-highlight-color: transparent; +} +.button { + padding: 20px 100px; + font-size: 30px; + margin: auto; +} +.input { + padding: 20px 2px; + font-size: 30px; + margin: auto; +} +.output { + width: 80%; + font-size: 30px; +} +#content-text { + font-weight: bold; + font-size: 5em; +} \ No newline at end of file diff --git a/Tizen.web/TextGenerationLlama2/WebApplication/icon.png b/Tizen.web/TextGenerationLlama2/WebApplication/icon.png new file mode 100644 index 00000000..9765b1bd Binary files /dev/null and b/Tizen.web/TextGenerationLlama2/WebApplication/icon.png differ diff --git a/Tizen.web/TextGenerationLlama2/WebApplication/images/tizen_32.png b/Tizen.web/TextGenerationLlama2/WebApplication/images/tizen_32.png new file mode 100644 index 00000000..647c3f9f Binary files /dev/null and b/Tizen.web/TextGenerationLlama2/WebApplication/images/tizen_32.png differ diff --git a/Tizen.web/TextGenerationLlama2/WebApplication/index.html b/Tizen.web/TextGenerationLlama2/WebApplication/index.html new file mode 100644 index 00000000..f2508c50 --- /dev/null +++ b/Tizen.web/TextGenerationLlama2/WebApplication/index.html @@ -0,0 +1,25 @@ + + + + + + + + + Tizen Text Generation Offloading Example + + + + + + + +
+ +

+

+
+
+ + + diff --git a/Tizen.web/TextGenerationLlama2/WebApplication/js/main.js b/Tizen.web/TextGenerationLlama2/WebApplication/js/main.js new file mode 100644 index 00000000..418b5725 --- /dev/null +++ b/Tizen.web/TextGenerationLlama2/WebApplication/js/main.js @@ -0,0 +1,129 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +/** + * @file main.js + * @date 18 October 2024 + * @brief Text Generation Offloading example + * @author Yelin Jeong + */ + +import { + getNetworkType, + getIpAddress, + startHybridService, + startMessagePort, + gRemoteServices, +} from "./utils.js"; + +const serviceName = "llama2c"; +let serviceHandle = null; +let tensorsData = null; +let tensorsInfo = null; +let ip = null; + +function disposeData() { + if (tensorsData != null) { + tensorsData.dispose(); + } + + if (tensorsInfo != null) { + tensorsInfo.dispose(); + } +} + +/** + * Callback function for pipeline sink listener + */ +function sinkListenerCallback(sinkName, data) { + const tensorsRetData = data.getTensorRawData(0); + const decoder = new TextDecoder(); + const output = decoder.decode(tensorsRetData.data); + const label = document.getElementById("output"); + label.innerText = output; +} + +/** + * Run a pipeline that uses other device's resources + */ +function runClient() { + const remoteService = gRemoteServices.get(serviceName); + if ( + !gRemoteServices.has(serviceName) || + !Object.prototype.hasOwnProperty.call(remoteService, "ip") || + !Object.prototype.hasOwnProperty.call(remoteService, "port") + ) { + console.log("No remote service available"); + return; + } + + const filter = + "tensor_query_client host=" + + ip + + " port=" + + remoteService["port"] + + " dest-host=" + + remoteService["ip"] + + " dest-port=" + + remoteService["port"] + + " timeout=100000"; + + const pipelineDescription = + "appsrc name=srcx ! application/octet-stream ! tensor_converter ! other/tensors,format=flexible,num_tensors=1,types=uint8,framerate=0/1 ! " + + filter + + " ! appsink name=sinkx"; + + serviceHandle = tizen.ml.pipeline.createPipeline(pipelineDescription); + serviceHandle.start(); + serviceHandle.registerSinkListener("sinkx", sinkListenerCallback); +} + +/** + * Run a pipeline that uses other device's resources + */ +function inference(input) { + if (!gRemoteServices.has(serviceName)) { + console.log("Offloading service is disappeared"); + return; + } + + const encoder = new TextEncoder(); + const inputBuffer = encoder.encode(input); + + tensorsInfo = new tizen.ml.TensorsInfo(); + tensorsInfo.addTensorInfo("tensor", "UINT8", [input.length, 1, 1, 1]); + tensorsData = tensorsInfo.getTensorsData(); + tensorsData.setTensorRawData(0, inputBuffer); + serviceHandle.getSource("srcx").inputData(tensorsData); +} + +window.onload = async function () { + const networkType = await getNetworkType(); + ip = await getIpAddress(networkType); + + startMessagePort(); + startHybridService(); + + document.getElementById("start").addEventListener("click", function () { + runClient(); + }); + + document.getElementById("run").addEventListener("click", function () { + inference(document.getElementById("prompt").value); + }); + + // add eventListener for tizenhwkey + document.addEventListener("tizenhwkey", function (e) { + if (e.keyName === "back") { + try { + console.log("Pipeline is disposed!!"); + serviceHandle.stop(); + serviceHandle.dispose(); + + disposeData(); + tizen.application.getCurrentApplication().exit(); + } catch (ignore) { + console.log("error " + ignore); + } + } + }); +}; diff --git a/Tizen.web/TextGenerationLlama2/WebApplication/js/utils.js b/Tizen.web/TextGenerationLlama2/WebApplication/js/utils.js new file mode 100644 index 00000000..d0503e74 --- /dev/null +++ b/Tizen.web/TextGenerationLlama2/WebApplication/js/utils.js @@ -0,0 +1,133 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +/** + * @file utils.js + * @date 18 October 2024 + * @brief Utility function for Image Classification Offloading + * @author Yelin Jeong + */ + +let gServiceAppId = "lfebC6UrMY.nsdservice"; +export let gRemoteServices = new Map(); + +/** + * Get currently used network type + * @returns the network type + */ +export async function getNetworkType() { + return new Promise((resolve) => { + tizen.systeminfo.getPropertyValue("NETWORK", function (data) { + resolve(data.networkType); + }); + }); +} + +/** + * Get IP address of the given network type + * @param networkType the network type used + * @returns ip address of the network type + */ +export async function getIpAddress(networkType) { + return new Promise((resolve) => { + tizen.systeminfo.getPropertyValue( + networkType + "_NETWORK", + function (property) { + resolve(property.ipAddress); + }, + ); + }); +} + +function launchServiceApp() { + function onSuccess() { + console.log("Service App launched successfully"); + } + + function onError(err) { + console.error("Service App launch failed", err); + } + + try { + console.log("Launching [" + gServiceAppId + "] ..."); + tizen.application.launch(gServiceAppId, onSuccess, onError); + } catch (exc) { + console.error("Exception while launching HybridServiceApp: " + exc.message); + } +} + +function onGetAppsContextSuccess(contexts) { + let i = 0; + let appInfo = null; + + for (i = 0; i < contexts.length; i = i + 1) { + try { + appInfo = tizen.application.getAppInfo(contexts[i].appId); + } catch (exc) { + console.error("Exception while getting application info " + exc.message); + } + + if (appInfo.id === gServiceAppId) { + break; + } + } + + if (i >= contexts.length) { + console.log("Service App not found, Trying to launch service app"); + launchServiceApp(); + } +} + +function onGetAppsContextError(err) { + console.error("getAppsContext exc", err); +} + +/** + * Starts obtaining information about applications + * that are currently running on a device. + */ +export function startHybridService() { + try { + tizen.application.getAppsContext( + onGetAppsContextSuccess, + onGetAppsContextError, + ); + } catch (e) { + console.log("Get AppContext failed, " + e); + } +} + +export function startMessagePort() { + try { + const addService = + tizen.messageport.requestLocalMessagePort("STATE_AVAILABLE"); + addService.addMessagePortListener(function (data) { + let remoteService = new Object(); + let serviceName = ""; + for (var i = 0; i < data.length; i++) { + if (data[i].key == "name") { + var name = data[i].value.split(" ")[0]; + serviceName = name; + } else remoteService[data[i].key] = data[i].value; + } + if (gRemoteServices.has(serviceName)) { + gRemoteServices.delete(serviceName); + } + gRemoteServices.set(serviceName, remoteService); + }); + + const removeService = + tizen.messageport.requestLocalMessagePort("STATE_UNAVAILABLE"); + removeService.addMessagePortListener(function (data) { + let serviceName = ""; + for (var i = 0; i < data.length; i++) { + if (data[i].key == "name") { + var name = data[i].value.split("(")[0]; + serviceName = name; + } + } + gRemoteServices.delete(serviceName); + }); + } catch (e) { + console.log("Failed to create local message port " + e.name); + } +} diff --git a/Tizen.web/TextGenerationLlama2/text_generation.png b/Tizen.web/TextGenerationLlama2/text_generation.png new file mode 100644 index 00000000..fc5d5fd0 Binary files /dev/null and b/Tizen.web/TextGenerationLlama2/text_generation.png differ