diff --git a/app/components/Endpoint/Local.tsx b/app/components/Endpoint/Local.tsx index fcc4106..9f8b271 100644 --- a/app/components/Endpoint/Local.tsx +++ b/app/components/Endpoint/Local.tsx @@ -12,17 +12,23 @@ import { } from 'react-native' import { Dropdown } from 'react-native-element-dropdown' import { useMMKVBoolean, useMMKVObject, useMMKVString } from 'react-native-mmkv' +import * as Progress from 'react-native-progress' import { SliderItem } from '..' const Local = () => { - const { loadModel, unloadModel, modelName } = Llama.useLlama((state) => ({ - loadModel: state.load, - unloadModel: state.unload, - modelName: state.modelname, - })) + const { loadModel, unloadModel, modelName, loadProgress, setloadProgress } = Llama.useLlama( + (state) => ({ + loadModel: state.load, + unloadModel: state.unload, + modelName: state.modelname, + loadProgress: state.loadProgress, + setloadProgress: state.setLoadProgress, + }) + ) const [modelLoading, setModelLoading] = useState(false) + const [modelImporting, setModelImporting] = useState(false) const [modelList, setModelList] = useState([]) const dropdownValues = modelList.map((item) => { return { name: item } @@ -45,18 +51,11 @@ const Local = () => { const handleLoad = async () => { setModelLoading(true) + setloadProgress(0) await loadModel(currentModel ?? '', preset) setModelLoading(false) getModels() } - /* - const handleLoadExternal = async () => { - setModelLoading(true) - await Llama.loadModel('', preset, false).then(() => { - setLoadedModel(Llama.getModelname()) - }) - setModelLoading(false) - }*/ const handleDelete = async () => { if (!(await Llama.modelExists(currentModel ?? ''))) { @@ -94,10 +93,10 @@ const Local = () => { }*/ const handleImport = async () => { - setModelLoading(true) + setModelImporting(true) await Llama.importModel() await getModels() - setModelLoading(false) + setModelImporting(false) } const disableLoad = modelList.length === 0 || modelName !== undefined @@ -137,9 +136,52 @@ const Local = () => { /> - {modelLoading ? ( - - ) : ( + {!modelLoading && modelImporting && ( + + + + Importing... + + + )} + + {modelLoading && !modelImporting && ( + + + + {loadProgress}% + + + )} + + {!modelLoading && !modelImporting && ( { body={preset} setValue={setPreset} varname="context_length" - min={512} + min={1024} max={32768} - step={512} + step={1024} + disabled={modelImporting || modelLoading} /> { min={1} max={8} step={1} + disabled={modelImporting || modelLoading} /> { varname="batch" min={16} max={512} - step={1} + step={16} + disabled={modelImporting || modelLoading} /> {/* Note: llama.rn does not have any Android gpu acceleration */} {Platform.OS === 'ios' && ( diff --git a/app/components/SliderItem.tsx b/app/components/SliderItem.tsx index 0b0e312..f63a980 100644 --- a/app/components/SliderItem.tsx +++ b/app/components/SliderItem.tsx @@ -14,6 +14,7 @@ type SliderItemProps = { step?: number precision?: number showInput?: boolean + disabled?: boolean } const SliderItem: React.FC = ({ @@ -27,6 +28,7 @@ const SliderItem: React.FC = ({ precision = 0, onChange = undefined, showInput = true, + disabled = false, }) => { const clamp = (val: number) => Math.min(Math.max(parseFloat(val?.toFixed(2) ?? 0), min), max) const [textValue, setTextValue] = useState(body[varname]?.toFixed(precision)) @@ -57,9 +59,10 @@ const SliderItem: React.FC = ({ return ( - {name} + {name} = ({ /> {showInput && ( Promise + setLoadProgress: (progress: number) => void unload: () => Promise saveKV: () => Promise loadKV: () => Promise @@ -64,6 +66,7 @@ export namespace Llama { export const useLlama = create()((set, get) => ({ context: undefined, modelname: undefined, + loadProgress: 0, load: async ( name: string, preset: LlamaPreset = default_preset, @@ -99,10 +102,16 @@ export namespace Llama { } mmkv.set(Global.LocalSessionLoaded, false) - Logger.log(`Loading Model: ${name}`, true) - Logger.log(JSON.stringify(params)) + Logger.log(`Loading Model: ${name}`) + Logger.log( + `Starting with parameters: \nContext Length: ${params.n_ctx}\nThreads: ${params.n_threads}\nBatch Size: ${params.n_batch}` + ) - const llamaContext = await initLlama(params).catch((error) => { + const progressCallback = (progress: number) => { + if (progress % 5 === 0) get().setLoadProgress(progress) + } + + const llamaContext = await initLlama(params, progressCallback).catch((error) => { Logger.log(`Could Not Load Model: ${error} `, true) }) @@ -111,6 +120,9 @@ export namespace Llama { Logger.log('Model Loaded', true) } }, + setLoadProgress: (progress: number) => { + set((state) => ({ ...state, loadProgress: progress })) + }, unload: async () => { await get().context?.release() set((state) => ({ ...state, context: undefined, modelname: undefined })) diff --git a/package-lock.json b/package-lock.json index 317c49f..416a4ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "@shopify/flash-list": "1.6.4", "babel-plugin-inline-import": "^3.0.0", "babel-plugin-react-compiler": "^0.0.0-experimental-696af53-20240625", - "cui-llama.rn": "^1.1.7", + "cui-llama.rn": "^1.2.0", "drizzle-orm": "^0.33.0", "eas-cli": "^9.1.0", "expo": "~51.0.28", @@ -55,6 +55,7 @@ "react-native-markdown-package": "^1.8.2", "react-native-mmkv": "^2.12.2", "react-native-popup-menu": "^0.16.1", + "react-native-progress": "^5.0.1", "react-native-quick-base64": "^2.1.2", "react-native-reanimated": "~3.10.1", "react-native-safe-area-context": "4.10.5", @@ -13743,6 +13744,13 @@ "node": ">= 6" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC", + "peer": true + }, "node_modules/bplist-creator": { "version": "0.1.0", "license": "MIT", @@ -14695,6 +14703,50 @@ "node": ">=8" } }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/cssom": { "version": "0.5.0", "dev": true, @@ -14721,9 +14773,9 @@ "license": "MIT" }, "node_modules/cui-llama.rn": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/cui-llama.rn/-/cui-llama.rn-1.1.7.tgz", - "integrity": "sha512-7SakofkZFEGRXDGJ+OAmzS4LatuIqM52iF41LLOzWQZE2SeCdMya4slUaz2NRk8izTE6BkG2wdNrNSbjdDLDFg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cui-llama.rn/-/cui-llama.rn-1.2.0.tgz", + "integrity": "sha512-Gp5YOZI7RnexKh0Rto9DiftJymn7i2EW3xc+YWwf30efd+i83n6woOteFe2GASmsA2cVSN9uYfxQhsYtl7YiVA==", "license": "MIT", "engines": { "node": ">= 16.0.0" @@ -15131,6 +15183,34 @@ "node": ">=6.0.0" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause", + "peer": true + }, "node_modules/domexception": { "version": "4.0.0", "dev": true, @@ -15142,12 +15222,43 @@ "node": ">=12" } }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, "node_modules/domino": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/domino/-/domino-2.1.6.tgz", "integrity": "sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==", "license": "BSD-2-Clause" }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "16.4.5", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", @@ -16113,7 +16224,6 @@ }, "node_modules/entities": { "version": "4.5.0", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -23196,6 +23306,13 @@ "integrity": "sha512-c2YOUbp33+6thdCUi34xIyOU/a7bvGKj/3DB1iaPMTuPHf/Q2d5s4sn1FaCOO43XkXggnb08y5W2PU8UNYNLKQ==", "license": "MIT" }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0", + "peer": true + }, "node_modules/mem": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/mem/-/mem-5.1.1.tgz", @@ -24185,6 +24302,19 @@ "node": ">=4" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/nullthrows": { "version": "1.1.1", "license": "MIT" @@ -25954,6 +26084,18 @@ "resolved": "https://registry.npmjs.org/react-native-popup-menu/-/react-native-popup-menu-0.16.1.tgz", "integrity": "sha512-xRS7mRh0exwu7Iw8PPVHdM11d13A/KzYjy0/fZx3zVtxISxPkNaDGayau6oa7HqO3Nj0oS9ulFCYjcQfG6vahA==" }, + "node_modules/react-native-progress": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-native-progress/-/react-native-progress-5.0.1.tgz", + "integrity": "sha512-TYfJ4auAe5vubDma2yfFvt7ktSI+UCfysqJnkdHEcLXqAitRFOozgF/cLgN5VNi/iLdaf3ga1ETi2RF4jVZ/+g==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react-native-svg": "*" + } + }, "node_modules/react-native-quick-base64": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/react-native-quick-base64/-/react-native-quick-base64-2.1.2.tgz", @@ -26032,6 +26174,22 @@ "resolved": "https://registry.npmjs.org/react-native-sse/-/react-native-sse-1.2.1.tgz", "integrity": "sha512-zejanlScF+IB9tYnbdry0MT34qjBXbiV/E72qGz33W/tX1bx8MXsbB4lxiuPETc9v/008vYZ60yjIstW22VlVg==" }, + "node_modules/react-native-svg": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.7.1.tgz", + "integrity": "sha512-Xc11L4t6/DtmUwrQqHR7S45Qy3cIWpcfGlmEatVeZ9c1N8eAK79heJmGRgCOVrXESrrLEHfP/AYGf0BGyrvV6A==", + "license": "MIT", + "peer": true, + "dependencies": { + "css-select": "^5.1.0", + "css-tree": "^1.1.3", + "warn-once": "0.1.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-windows": { "version": "0.74.11", "resolved": "https://registry.npmjs.org/react-native-windows/-/react-native-windows-0.74.11.tgz", diff --git a/package.json b/package.json index 87dea4b..783cc10 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@shopify/flash-list": "1.6.4", "babel-plugin-inline-import": "^3.0.0", "babel-plugin-react-compiler": "^0.0.0-experimental-696af53-20240625", - "cui-llama.rn": "^1.1.7", + "cui-llama.rn": "^1.2.0", "drizzle-orm": "^0.33.0", "eas-cli": "^9.1.0", "expo": "~51.0.28", @@ -63,6 +63,7 @@ "react-native-markdown-package": "^1.8.2", "react-native-mmkv": "^2.12.2", "react-native-popup-menu": "^0.16.1", + "react-native-progress": "^5.0.1", "react-native-quick-base64": "^2.1.2", "react-native-reanimated": "~3.10.1", "react-native-safe-area-context": "4.10.5",