From 333e0a372a707b417f2dac09ede3d4faca800fb0 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Thu, 11 May 2023 20:54:34 -0700 Subject: [PATCH] initial commit --- .github/workflows/release.yml | 43 + .gitignore | 4 + LICENSE | 21 + README.md | 126 + assets/demo.webp | Bin 0 -> 242852 bytes package.json | 25 + packages/headless/.eslintrc.json | 5 + packages/headless/.gitignore | 35 + packages/headless/LICENSE | 21 + packages/headless/dist/index.js | 1156 ++++ .../types/src/hooks/useConversationStore.d.ts | 27 + .../headless/dist/types/src/hooks/useLLM.d.ts | 36 + .../dist/types/src/hooks/useStore.d.ts | 2 + packages/headless/dist/types/src/index.d.ts | 3 + .../types/src/providers/ModelProvider.d.ts | 8 + .../headless/dist/types/src/types/chat.d.ts | 15 + .../dist/types/src/types/worker_message.d.ts | 26 + .../types/src/worker/lib/tvm/compact.d.ts | 10 + .../dist/types/src/worker/lib/tvm/ctypes.d.ts | 180 + .../types/src/worker/lib/tvm/environment.d.ts | 26 + .../dist/types/src/worker/lib/tvm/index.d.ts | 6 + .../dist/types/src/worker/lib/tvm/memory.d.ts | 144 + .../types/src/worker/lib/tvm/rpc_server.d.ts | 54 + .../types/src/worker/lib/tvm/runtime.d.ts | 700 ++ .../types/src/worker/lib/tvm/support.d.ts | 23 + .../dist/types/src/worker/lib/tvm/types.d.ts | 33 + .../dist/types/src/worker/lib/tvm/webgpu.d.ts | 124 + .../headless/dist/types/src/worker/llm.d.ts | 44 + .../dist/types/src/worker/worker.d.ts | 19 + packages/headless/dist/v4-2119d9d5.js | 3784 +++++++++++ packages/headless/dist/worker-cc79b531.js | 412 ++ packages/headless/package.json | 63 + packages/headless/rollup.config.js | 35 + .../src/hooks/useConversationStore.tsx | 178 + packages/headless/src/hooks/useLLM.tsx | 257 + packages/headless/src/hooks/useStore.tsx | 18 + packages/headless/src/index.d.ts | 6 + packages/headless/src/index.ts | 5 + .../headless/src/providers/ModelProvider.tsx | 24 + packages/headless/src/types/chat.ts | 16 + packages/headless/src/types/worker_message.ts | 32 + .../headless/src/worker/lib/tvm/compact.d.ts | 10 + .../headless/src/worker/lib/tvm/compact.ts | 34 + .../headless/src/worker/lib/tvm/ctypes.d.ts | 180 + .../headless/src/worker/lib/tvm/ctypes.ts | 251 + .../src/worker/lib/tvm/environment.d.ts | 26 + .../src/worker/lib/tvm/environment.ts | 146 + .../headless/src/worker/lib/tvm/index.d.ts | 6 + packages/headless/src/worker/lib/tvm/index.ts | 26 + .../headless/src/worker/lib/tvm/memory.d.ts | 144 + .../headless/src/worker/lib/tvm/memory.ts | 408 ++ .../src/worker/lib/tvm/rpc_server.d.ts | 54 + .../headless/src/worker/lib/tvm/rpc_server.ts | 457 ++ .../headless/src/worker/lib/tvm/runtime.d.ts | 700 ++ .../headless/src/worker/lib/tvm/runtime.ts | 2280 +++++++ .../headless/src/worker/lib/tvm/support.d.ts | 23 + .../headless/src/worker/lib/tvm/support.ts | 65 + .../headless/src/worker/lib/tvm/types.d.ts | 33 + packages/headless/src/worker/lib/tvm/types.ts | 53 + .../headless/src/worker/lib/tvm/webgpu.d.ts | 124 + .../headless/src/worker/lib/tvm/webgpu.ts | 862 +++ packages/headless/src/worker/llm.ts | 337 + packages/headless/src/worker/worker.ts | 54 + packages/headless/tsconfig.json | 42 + packages/retro-ui/.eslintrc.json | 3 + packages/retro-ui/.gitignore | 35 + packages/retro-ui/LICENSE | 21 + packages/retro-ui/README.md | 34 + packages/retro-ui/next.config.js | 28 + packages/retro-ui/package.json | 39 + packages/retro-ui/postcss.config.js | 6 + packages/retro-ui/public/buddy88.gif | Bin 0 -> 7012 bytes packages/retro-ui/public/favicon.ico | Bin 0 -> 1415 bytes packages/retro-ui/public/sounds/imrcv.wav | Bin 0 -> 18348 bytes packages/retro-ui/public/sounds/imsend.wav | Bin 0 -> 17772 bytes packages/retro-ui/public/xp.jpeg | Bin 0 -> 278797 bytes packages/retro-ui/src/app/layout.tsx | 25 + packages/retro-ui/src/app/page.jsx | 11 + .../src/assets/fonts/ms_sans_serif.woff2 | Bin 0 -> 6508 bytes packages/retro-ui/src/components/Chat.tsx | 143 + .../retro-ui/src/components/ChatWindow.jsx | 167 + .../src/components/ConversationList.jsx | 81 + packages/retro-ui/src/components/Loader.jsx | 63 + .../retro-ui/src/components/MessageList.jsx | 52 + packages/retro-ui/src/components/Options.jsx | 286 + packages/retro-ui/src/styles/globals.css | 3 + packages/retro-ui/tailwind.config.js | 8 + packages/retro-ui/tsconfig.json | 49 + pnpm-lock.yaml | 5704 +++++++++++++++++ pnpm-workspace.yaml | 2 + tsconfig.base.json | 10 + 91 files changed, 20831 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 assets/demo.webp create mode 100644 package.json create mode 100644 packages/headless/.eslintrc.json create mode 100644 packages/headless/.gitignore create mode 100644 packages/headless/LICENSE create mode 100644 packages/headless/dist/index.js create mode 100644 packages/headless/dist/types/src/hooks/useConversationStore.d.ts create mode 100644 packages/headless/dist/types/src/hooks/useLLM.d.ts create mode 100644 packages/headless/dist/types/src/hooks/useStore.d.ts create mode 100644 packages/headless/dist/types/src/index.d.ts create mode 100644 packages/headless/dist/types/src/providers/ModelProvider.d.ts create mode 100644 packages/headless/dist/types/src/types/chat.d.ts create mode 100644 packages/headless/dist/types/src/types/worker_message.d.ts create mode 100644 packages/headless/dist/types/src/worker/lib/tvm/compact.d.ts create mode 100644 packages/headless/dist/types/src/worker/lib/tvm/ctypes.d.ts create mode 100644 packages/headless/dist/types/src/worker/lib/tvm/environment.d.ts create mode 100644 packages/headless/dist/types/src/worker/lib/tvm/index.d.ts create mode 100644 packages/headless/dist/types/src/worker/lib/tvm/memory.d.ts create mode 100644 packages/headless/dist/types/src/worker/lib/tvm/rpc_server.d.ts create mode 100644 packages/headless/dist/types/src/worker/lib/tvm/runtime.d.ts create mode 100644 packages/headless/dist/types/src/worker/lib/tvm/support.d.ts create mode 100644 packages/headless/dist/types/src/worker/lib/tvm/types.d.ts create mode 100644 packages/headless/dist/types/src/worker/lib/tvm/webgpu.d.ts create mode 100644 packages/headless/dist/types/src/worker/llm.d.ts create mode 100644 packages/headless/dist/types/src/worker/worker.d.ts create mode 100644 packages/headless/dist/v4-2119d9d5.js create mode 100644 packages/headless/dist/worker-cc79b531.js create mode 100644 packages/headless/package.json create mode 100644 packages/headless/rollup.config.js create mode 100644 packages/headless/src/hooks/useConversationStore.tsx create mode 100644 packages/headless/src/hooks/useLLM.tsx create mode 100644 packages/headless/src/hooks/useStore.tsx create mode 100644 packages/headless/src/index.d.ts create mode 100644 packages/headless/src/index.ts create mode 100644 packages/headless/src/providers/ModelProvider.tsx create mode 100644 packages/headless/src/types/chat.ts create mode 100644 packages/headless/src/types/worker_message.ts create mode 100644 packages/headless/src/worker/lib/tvm/compact.d.ts create mode 100644 packages/headless/src/worker/lib/tvm/compact.ts create mode 100644 packages/headless/src/worker/lib/tvm/ctypes.d.ts create mode 100644 packages/headless/src/worker/lib/tvm/ctypes.ts create mode 100644 packages/headless/src/worker/lib/tvm/environment.d.ts create mode 100644 packages/headless/src/worker/lib/tvm/environment.ts create mode 100644 packages/headless/src/worker/lib/tvm/index.d.ts create mode 100644 packages/headless/src/worker/lib/tvm/index.ts create mode 100644 packages/headless/src/worker/lib/tvm/memory.d.ts create mode 100644 packages/headless/src/worker/lib/tvm/memory.ts create mode 100644 packages/headless/src/worker/lib/tvm/rpc_server.d.ts create mode 100644 packages/headless/src/worker/lib/tvm/rpc_server.ts create mode 100644 packages/headless/src/worker/lib/tvm/runtime.d.ts create mode 100644 packages/headless/src/worker/lib/tvm/runtime.ts create mode 100644 packages/headless/src/worker/lib/tvm/support.d.ts create mode 100644 packages/headless/src/worker/lib/tvm/support.ts create mode 100644 packages/headless/src/worker/lib/tvm/types.d.ts create mode 100644 packages/headless/src/worker/lib/tvm/types.ts create mode 100644 packages/headless/src/worker/lib/tvm/webgpu.d.ts create mode 100644 packages/headless/src/worker/lib/tvm/webgpu.ts create mode 100644 packages/headless/src/worker/llm.ts create mode 100644 packages/headless/src/worker/worker.ts create mode 100644 packages/headless/tsconfig.json create mode 100644 packages/retro-ui/.eslintrc.json create mode 100644 packages/retro-ui/.gitignore create mode 100644 packages/retro-ui/LICENSE create mode 100644 packages/retro-ui/README.md create mode 100644 packages/retro-ui/next.config.js create mode 100644 packages/retro-ui/package.json create mode 100644 packages/retro-ui/postcss.config.js create mode 100644 packages/retro-ui/public/buddy88.gif create mode 100644 packages/retro-ui/public/favicon.ico create mode 100644 packages/retro-ui/public/sounds/imrcv.wav create mode 100644 packages/retro-ui/public/sounds/imsend.wav create mode 100644 packages/retro-ui/public/xp.jpeg create mode 100644 packages/retro-ui/src/app/layout.tsx create mode 100644 packages/retro-ui/src/app/page.jsx create mode 100644 packages/retro-ui/src/assets/fonts/ms_sans_serif.woff2 create mode 100644 packages/retro-ui/src/components/Chat.tsx create mode 100644 packages/retro-ui/src/components/ChatWindow.jsx create mode 100644 packages/retro-ui/src/components/ConversationList.jsx create mode 100644 packages/retro-ui/src/components/Loader.jsx create mode 100644 packages/retro-ui/src/components/MessageList.jsx create mode 100644 packages/retro-ui/src/components/Options.jsx create mode 100644 packages/retro-ui/src/styles/globals.css create mode 100644 packages/retro-ui/tailwind.config.js create mode 100644 packages/retro-ui/tsconfig.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 tsconfig.base.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..09bc027 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,43 @@ +name: Publish Package to npmjs +on: + release: + types: [published] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "16.x" + registry-url: "https://registry.npmjs.org" + - uses: pnpm/action-setup@v2 + name: Install pnpm + id: pnpm-install + with: + version: 8 + run_install: false + + - name: Get pnpm store directory + id: pnpm-cache + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v3 + name: Setup pnpm cache + with: + path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: pnpm install + + - name: Build + run: pnpm build + - name: Publish to npmjs + run: pnpm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8334b44 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +**/*.tsbuildinfo +**/**/.next +**/**/node_modules diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..052f695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Matt Rickard + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3fe525b --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +# @react-llm/headless + +Easy-to-use headless React Hooks to run LLMs in the browser with WebGPU. As simple as `useLLM()`. + +### [**Live Demo**](https://chat.matt-rickard.com) + +![image](assets/demo.webp) + +**Features**: + +* Supports [Vicuna 13B](https://lmsys.org/blog/2023-03-30-vicuna/) +* Use custom system prompts and "user:"/"assistant:" role names +* Completion options like `max tokens` and `stop sequences` +* No data leaves the browser. Accelerated via WebGPU. +* Hooks built to 'Bring your own UI' +* Persistent storage for conversations in browser storage. Hooks for loading and saving conversations. +* Model caching for faster subsequent loads + +## Installation + +```bash +npm install @react-llm/headless +``` + + +## **useLLM** API +### Types +```typescript +// Model Initialization +init: () => void; + +// Model Generation +send: (msg: string, maxTokens: number, stopSequences: string[]) => void; +onMessage: (msg: GenerateTextResponse) => void; +setOnMessage: (cb: (msg: GenerateTextResponse) => void) => void; + +// Model Status +loadingStatus: InitProgressReport; +isGenerating: boolean; +gpuDevice: GPUDeviceInfo; + +// Model Configuration +userRoleName: string; +setUserRoleName: (roleName: string) => void; +assistantRoleName: string; +setAssistantRoleName: (roleName: string) => void; + +// Conversation Management +conversation: Conversation | undefined; +allConversations: Conversation[] | undefined; +createConversation: (title?: string, prompt?: string) => void; +setConversationId: (conversationId: string) => void; +deleteConversation: (conversationId: string) => void; +deleteAllConversations: () => void; +deleteMessages: () => void; +setConversationTitle: (conversationId: string, title: string) => void; +``` + +### Hooks +```typescript +import useLLM from '@react-llm/headless'; + +const MyComponent = () => { + const { + conversation, + allConversations, + loadingStatus, + isGenerating, + createConversation, + setConversationId, + deleteConversation, + deleteAllConversations, + deleteMessages, + setConversationTitle, + onMessage, + setOnMessage, + userRoleName, + setUserRoleName, + assistantRoleName, + setAssistantRoleName, + gpuDevice, + send, + init, + } = useLLM(); + + // Component logic... + + return null; +}; +``` + + +### Packages + +* `@react-llm/headless` - Headless React Hooks for running LLMs in the browser +* `@react-llm/retro-ui` - Retro-themed UI for the hooks + +## How does it work? + +This library is a set of React Hooks that provide a simple interface to run LLMs in the browser. It uses Vicuna 13B. + +* SentencePiece tokenizer (compiled for the browser via Emscripten) +* Vicuna 13B (transformed to Apache TVM format) +* Apache TVM and MLC Relax (compiled for the browser via Emscripten) +* Off-the-main-thread WebWorker to run the model (bundled with the library) + + +The model, tokenizer, and TVM runtime are loaded from a CDN (huggingface). The model is cached in browser storage for faster subsequent loads. + + + + +### Example +See [packages/retro-ui](packages/retro-ui) for the full demo code. This is a simple example of how to use the hooks. To run it, after cloning the repo, + +```bash +cd packages/retro-ui +pnpm install +pnpm dev +``` + + +### License +MIT + +The code under `packages/headless/worker/lib/tvm` is licensed under Apache 2.0. \ No newline at end of file diff --git a/assets/demo.webp b/assets/demo.webp new file mode 100644 index 0000000000000000000000000000000000000000..22d681d35712fd5d15b03956f6b04fbdb7d8b742 GIT binary patch literal 242852 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
2YpV}q(%y8PF zoEtr?a;)}g2lgsXK};WVYuFQkT@j?~RlcyTj73kc9{a-&8%ZyuPt8^2c)04) zlADK_ykXl+a9A=*vYI`$IcuvaKNQZqO-vq5`SvUKF`8wkalek{qx5A-wM@+qs1D8JVwX<~?W--G( zGjWF-n%N?2%qnX91HU}Z9A>IN_K00>yQv4pSd%>mG8yS$9su33bx9bbtAagL73T7h zx;I4|nuldAS5Jpytbv{`Ewl#VGd;JZYt-NxA7nzJn;DuKJ`*&JYXTl-!#&c zm(J#WwlE=l$Q_=8@t8xQ|1q)b$U{utwO+<6`IIQ>pF2Z7D-Ad!XAT3#wO^T1;dDo`3E$3yc;2X= zBRs~9yHn%r!n1WnPc8E8%qzDkCWdqfNWlm#cPkrx&cRj}OqXDR<#5P#ZDK`7txx<= zag!DF1@p)c>^9bxH$})0u_OqJfaz%-=B4C*dV&MpV+!Ln2-#B~!Ezu#WEw2h1|jeX zUk1m2UdncjnDJFW)jaF%)cEXdNmqevB*QHQfQH$H9OAgXOGoq9R{0@=Ga*e|1ZON% zv^=@l`zxf{rjl#Vn|;s85<||G+;E1)1khnD;I> zPoZx>=I1w*LZ*s}EcbNBQrKEjZIzZ^Z$C;Ihu3uGh*>E8sTo|qqr zp1eR1GfN}SsqzmFrK4hAg9caIFD{;Dw8*(t{T%^FLGkCPJH{u`KGO-os7K$ccg3Bf*L^vxk#slF8R+pl#4bZOo8xf_D(#zn-f;4&XeYv?7^IdEJ5 z;Hgb83@8dRT#s2tI&ZxMmgocY2Z6<)A`sDC(q+s8=yBz2WfNrkl;vsvwDCN0XX6#I z1Dpo#fP$`5t{ESE58WOpK#zo9fk#KSPmNGVMJPM{L-?n7jqaH0PR;O&tNme**pc=3 z2RrUpyULv0EN2GyOD9A?{_5Oz>#*M2&9%LRV?AzJclM88hxK(1A?B91U6BG-q`KA( z83K#5mgbp@-l*K^D4R?DI7>vIWQuy*M+aS!uRfGg86F)uI~kzKp02$Ap^=1W&^9ui zMat4b(Ea;J{uSL)M3gnxvSK172btKwiD7a%XKW%3_oGm1#+HtZ%41Ira5Msy#*A}< zgeER@1cwN>N|Sjb&W=|00)Gx9!uR)kKa@kPSh=}=7D5%Z9;ahe5f%o?=qFB(+@r0( zI)bHJmo8F7`a*5nlyV!k${u!D`eUgNeN-Mta91+$pO^1Sf(aJFIKKpmhZVYVFm}YY zvKFUbQgp%3BY)`1VeTH#_`=Nf-a14^1g@dFgfM^4R&T8QqP&C<$pYaw`hlU z(s#MHuE}vX-MJt^=@0)>LPhIor2Q8K?2F{xW}Ou5qN^X0nN=4`@nfvORI4OxvsXU* zxp?ym_t)tn0i?xGezZW}>z_e7`ThqOD=ar8pRd!qfwN_abW5?j0vNfx z_VS7Uu=p1qQxRGw@I!4n#B#9CtR7dz_Wvp*Hs>zP5|l|ygv!+#<=j@+)j=raBvBk8 z_%rMU02lpPxB8IW|HQJ8P3o(GtIfQnwKQ~iUoscICG>vO$0Geg%_s~!RAG@W z^|j(;K(yF=>sMs^+uzv!O|bxSc-Y1sLH?V@#ob&+5>rLur%N~AAD7?$n|YS__%X41 zy!`wAwm@pxf28y$r;PhW;19ES_+Wg(GXak%zi;n+Mx2uyc78J@UcRN_H(U){ozez4 zAaR3$j6MV*lqN5?tz~Ur+jA*E(6Es+RL->1%j!H`>iff;ch-{p0(dE4+X>Uz%IwPbCqd35fiwZ7_s*}d8TU6o*m5UJU3J;QtBoTwg`(y05v)Bvliiyx&xmM=& zov)O#v71fm*hJH1Y$xu*=ELAAK;aGE;q`#&_uiTL7&N#&n{9^(s*eYJH0jn4wB7jZ z(M!0BC{yCLbpeH(?Vf7h+x@X@K_bhe6u9z_yvoM#hRde;k2+BE*;Pjmxma?yWBX$D z3j|m1{kPR<`7h8BuJd{*?uaY>G}6Q#wa0tOZNwn}a=YODJ<)4Oq z8DgDK-&?0-lhb&wNhIr1)(_CvKi1FGq(SUFoBc$yjByP-G}4%tI;ZwN*>*78rr(m| zj3Wl}o;^t$_?&-aTy=jPS6E+Zhl5OS=DM@V^=r4$P1`b)cOygco@s%eauU5?lpco& z9SRv{S^_psimPUid~mRUbaOfyYlGo>Pl*HO-c>ACB>rmHX2j^5=2>f+gV|>#n%fbM zCqbi7#o1{ut{ZCEU979v13>~*y>@&98seWs&Klc|G9;fY8zu+w>g5gDUhkvBWL8ek zZi@DQdUAFXsZ&ZuR(iugTP=%e<+B!%_=WMjy2X9DVGwya+qcfF;BmGrzGC)H4@*bt zlp~hS-D+;yx{O1L!YX4*VT4^)E!TzaqeZ`)+&6_TI(T*oGr?w;91&1(ZvTw=R z&~FVSjQ!BO64N$L6=+*NHa2Rd&h+XrEggk9Eho?4_)ay)RIU0zQ)Bx^=7gBJPdFXL z5y`_bl58vlyPKCBATP({GoapYHSQQv}1}Y$|*%~O$w#YQBjPqqJ z7bRg9Ktx=Uw~6#-^d8au6c91GiG{HXr`(2csEb4${bosYetCK4MOCOUxt>jPB zt7kj&M&_Kh&*PPfi42ID=eV%yueMhqG!bZ+D+Xc5ZP;$(LVbJ z-YH~2vUu&(*+&}8{aF}1I(=s=SrU_ySxNyxcu+0{=uQKGFwCW_jG1zlJbb7gjnC?$ zr@koOPYH9dC>pC>4q1<{`!Te#U%4`VKe?M{!cZ(-7j4lpCXNwys3IKBVF(^{BO_Y%p`!ud@oSVsauUT^_o%`Dk{X6sYuo>1piY_ zZpRhCJGX5*T6Jdb=RhW8fqCTOZgx5u_r_dqyM5dHgs@CPW(|rWG2JxZ!(t2l>u%^; zvgr@m$gg&+kbBpm%JzkLBoC#8qItCpQq0`s+N6bB5&|~-^Mv0;>9bF`pR={<^brAz z4MNXzF^qhkkr;nmYbofO)=&4}9FU#~Me@bQVlqC8IHU)3H4x&SxF}wzCMX$0VFBsj;Ml zu`BSNf%T?4sY<@0K0K5C*Q)s}i3ii>n%)gYN0v}V#UqaqKh@7`_C7$uH2=Y-(FK;=J zCqqt)h~ULCtO_zM0Wd9+163P&Q1W6~El4|_Kmr#DPSS0yCjX8)iYBdO@DS%}sN|iS zJn>42p?Fi757l(BHy=shX5wI6-_&;8&!789QP=rj+R1$;aU51yvbmQKKZIiW9}q_& zuyj6vh%cqftMxdyz7ZEF610|~o)~-3(+gclj#Zc%aDOV2U}$K)y#O`W$?kGy4A0xe zHX?2$tc(ixdD{s|sQ-9$k-qu0a)x9l^Itcr}SQ#R#Was?5F*4Um%x-U$CfF0<#nLb?-k~lPt$4_#C z5vJVmW_v7Pb6keHqV2+cP$nY%PKtj|Oi{?%n=Q5yx9=(0r zGfy5zLKaOkzg?HqzV(q?UNgO0_m#Vyo8Wsg4)bj8ZQINYy5i`_C4en?&l z2{!nZ$z6w3Ouj`MglOwX?K~SYaq2_<+x_J=wLq(+7&3DA_4>viYne-t zQCq&Pn-(PezkX2Csu7-DkH z$81RHTzuqkr96-9UB^}2C1V@D98d+l*FdI zrE$2MK|eu%T69N0-##=N_UKsyx-OE{zFe!FVv9LqmY#P^a5usOuy~03x)b*O9QWQ- zGDiv#fc8_*8!ib`4Wf>|;mJi`aCA_^d*#7ivg=O9V}&^ULs^v@C3Bn#7&Ux@%15^A z;p(eKOZCe-v!|2+>+G7*Se^%bpqEV>ABk`0v!f(*PU&vl5De=Fi?z`{J)vX(i#wgm z(+VWmr|}ZyA_PvsK|D_*kAzu6tuK>{C-|wih0gyXpOyDRS zh_X50FZG=UIlLXeor<xTl19OAPC~zwMDP+56!b1$(zlfo920~Z9|^&AT+dzw1=uGtj`S^o@r2|% zNQK^#T5ySIzLAjx0G`L2{O}soA8YZ~L-I_Yk$?)L^IXf!1Em-y=Zm;SKtaC#Aw@oq zZ!6n2cxI7suU6Ol4agl@KkNhf-PJAz4(X`axvYqTavE}^@}M44pbwX7Og|X-cseMw zRzeJlwSSP)X_ywpEKj1TKLM?Ece3qMttJ>Le9;O1K^xIDtG*bcB=xF+)s;)?oy?jG zx`I{s_(aDG=U`<2L4@!(A|n3}M5H0Vg2Q%Nqu&0FNbK(@DWVrdyx+xv5s?NX0stez z9q#h_G9JI|QEOtnF=lw7BYzS-dH9Rp@$io`9u#!XVAM7+E**_rO%YdXR-bf71ikGj z5h!vEW;5!#KB8wcvcM2hAoq2SNj4m8^@kjDJ_gX}^{3WYz!#2%Otp@*J=3lOZ@U$X zDk)T;c&aId#tbqM(;?+%J4>Ruf7O9My9vo?nT!Y4c~@LaccnX9)2O> zlYEyL{vmeWj;j%8x7)_v=#(+^j3W9#`Uer?zjXw>*6uw3VEh*%MBo%cX^|tk;)%1F zId|i|m>eJ%Y10#O8w4vg9K!d#$;<&k|v?1hY0>9NjjsI6>i1`Z~>i4OEW zM(Jv?m%RTh&_mDA>?C-TJPuh4O)bH{W}P%#rLK`X^^i&8GnIA_9c5&A8O;tVijiS$ ziV2EBbbDZOvdgdqD$;z{OxrQMj1^^FVbWLF$6#;1Jd#f(&DvbmicTxEJh;zk0!Rj^qzH=?MNT@Hl{y4J}~Ik>&62V`$dt)G@Yk zvKrZ?uR7O{4+%-nV^qPce8e0};uA{Vau48Ja%a7%<>m;(r-v0At|@Sk33Y2W_Pb;M z>9YC zd@>DiiBfrX*dpayDD{J3@@Ax%N$2*6^YnzkWs&|@4?~+RHqr(}NQU2ia66g7+ zonrZo%YVjs{wFS#LINj3hafD4-?+TQd1Au8=pXCvc8Uw!P636$xV*Ge{1_Ks$JMYS zeFPmH(JqU2X_URlpOI`b+IjI18*%)bDnfl77;}C-gUw z6#8$k-5l_ch|>|%Dq{d$me4@YMcp<<5GOX){=+z@UGQaZ9_5LDHrQ0MKmG75Iam7h zi?BZ9`!tciSQ}9V?j!Y{&EwP@-539;HDVCz`IlIi=DsqnGjm5yDj6K*kgMy1XS4;P zvq+0z*Kaz>tu~MmbENMv->xmc4_@pXk;}gHnieux{m$O?j!j$1w6P2KMPI z4Zpi@a991J(JvBuk>I=EJvcM~=H)k7LQuAV=QHy^-%0(AFZXYJUnKiakNqF`ZbRKI zLR^E86rq0Sj12yA@^?+(dBHb|>$?<`K2DO?OB62zzu{%&5HSAOn|&rEF@=$6r#Y}& zPq;RtKG_S!j(F7s_U(bDje?*cZ!q^()k_cF;($2OtOpRMF4sC$}h1?>;Y#p628{_?*lh*u-3O9q_=`!y=@$Hipe{aRVd zyJb->IeLtgrwj`fw+TJfKt3!6%$}GLZtHT<~SWeo$S6uA4#P8Wqi9A5hU$=`B-d8t5uEUBJEI9>3$ zJgETf@9QEP|uxpf0teXepdDBrJmvf@W8SXNR;yNngW1D zWecGLFo=+~a?lby6kUi?%k3L+*~>a@13c$`dw9-QgdTm3u5@Etsqf~@aGwMdbOJgO zXcUj)31Q;gczWwL8sg%7YbtPWS;tEx@oYI)60hG9;cZl4=utSAf$HBF@?OcgJpi_j zs^XUS*1t>}=V9z?AtRY|1lpPK4QM8mo;r>(#v4R`GkLUP%^cx)_|@9=t3ehtnt!j* zZ*FbDlxO|M6aF`zFF3y7sR2&^;EC`Lo{L`{d6K}D_Ss+sqrvFJ|EQzI?v>c>Y*j(SL7d*U#yY@0HEb} zeFDo7+*|xER}P$B{#Bp;$T0=-5D37k%*)INJo4T0x5_SMThl^~(>D0o6^yUo91WM& zrPmy?^aI($DHiIEW{QX1(yXNJCV2wE`X<~C$e*R!v>Gg-+|IXuIdiTHc(5wLfh6VXsVjb*{9uAU8|Ky?ccUu59 zs=*olm-SwQv;npjmfgU{yHE-A13%q^MfO8rx_Lok?GJ^G(Bpl9(bsc#1KeO4 zzS#89`n_8;b=6kS+XcJPrgZ0Li!m$ksOZ~%Kr{wJ))VHzmeQ11=roR2&{ZaaiS$#I zW*SK83^O+4;6jeK-qit4rhQmy+wSdqaU`n~`6S%r=MO9pJ|v6Yl40H^dGI!poB-_N^`PQP9yqdXPQ;p}%)xE&UNgd2bCWS9lBja+BID0emI!DyI z&lB#(mKT!+F>~ujYc|ifCdlkdhEogb|K`MmZuXd0Fqzs(ZbzDN z76EUs#HGuK9|4!=x@d0hv+(+bTWoN7u@W!bVnL{aS1<4+$oj;HzHl3V(!C0_3$Zl5c>_rz~u>gY8cz@**dBGw;t%Wt;In-R4KoU+fY`^ps2v3 z>g!vjZb-dGJQ&Jw%Xuj1c^nHA}*YFbucA5c)@$@wQ0vX`Q#n%XsQ;T_cs3`m|LX%9TKxrOFkr#xv5ScNsvAjgpuI zdL|)spQfOnAA(R=c$VRU5Xb5SYuleY`x&KtZQk5*5t5>LZ6@~3f7+IJ3Ima*=1{-3 z*I=G13HT`S$}?jA6B547R>;7&Z}mH!(M(G>M?A|#NV!orEI{}PlFxR4ZvN(<+S=_5 zT8e_67hySwLxWWSLuc)fgW(~AR`9XwrFMlhAIeoJ?7KtEu7fvy#Me?h)v z`gYr<6tgHd1SQ1)WM_3*;UJjh1#Ynfpo!f+&J1)hqyqHa`6!pXd(?cDw~Ls+ zafbHOOs5@1thVL#?xY39Tg(=!%ctkHYw`K8V^L@451ZT*hvc#EHk977sO#PMnO}ED zlxO*WZ|=b_LDLD7C%QL9nNz?aAfsJgVOr!1uPEaw$xB{qB6iPpwv;AcRy&54<;)r) z6iHh9kUa%N{747|1DPW^A+Kin8R?HBuoU z%{L;}TWyK!4BbB})p1*v(4y4V!&Q&Nr|t29iatP}rB+uZMDYS9EMVK_AxnMS-1UI@ z`w9LKQO5OGro`pJpn94tyUKM)lCh!V0f;-@?}+ zUKRT&yQlc!Vv_1gkelj)fVQ&1gP8!*kB~Z%Jtol&4Ra~NRvpNXhr4n ziu9*!Th_jH?QXHlt8@6@$S4Km?1KER=#W`M+zB1@<~JtXSM+um>+$*{1;A^Y0;01F+-gI&lDU9o zwq~-MT{JBN0^1FAK*!th)g1wg54b)oQ!*=YxV(2b%i`HsXUj?jg+V#48?7L_4oV{( z>TLXzAKuT@W0f(UYx~V!yd;9Mxm-Ha-K<#(?%U#FDMm=Du&P-JFsBKgE5GKCo&%S2 zF?X^Kt=~}uOzvUvY=V~_x7OsT3c@pAvVhMxjLOA@cpDz$IUv;VP%FG~>BS&3my9izjB(kaCgWaK zb*62*4WW-5opVlYS#c3*VT*Z{aFWxLfW(oZnZi#swB#jSbT+&EW9vBaC<%f@U}XM z+Mx)zl8T;n2A#{ei4ffO@k_s}d$04Vnh%yJ0CK=WQ;7@lmp3!5bSrha8mG@v8=fUB z&MP`5GM!L(qKa7c@7Jx^gXdjJMn~nP8A75o8!WH!q{sUQ+RRNqlE$6p_GpR1#RZu> zDj31HOzgU7n2-^x=H*Lq&CHwc;(b0hFr`UNuiZE2As)I?XvbaVq6fXJ-gbff0#T5z z4^TNHIsRlQHHJSBWvC%x;(o>cNPUo~*eNDZWE;ndN6opvS6?&_c*^~{{YdIZq3rJs ziS83;3Q0umyyX?Ef8^@n@8$B4O?5t-7C|(mqWXi_JY5)e!vwjZpTXoL14`u;PIx7QB_Vh{mTQk8S%l}tlH}A^!{16a@93QM6n`zrD_$nsS}Z(?Ct2i}{RMYCw~Z%3{+wT2qy#BZ{~m zBKTQ@XkIaR{xR5#;d?~_g5R5Hve(xFzD`g~P0@AJ9fG*p?x%{B@g4&@hE$AQpXie9 zmBZBPXM)5dc##K!5x3H~x)VM>S{vfW+SXU_D9AWy9wN&igl+vqUPy)&cZm%i2f3rx(LN%jVgRyG#`027GoiTQi}Qvx5L4g7U=M9CD>l@{Rj0vFKjKe? zm^V1aC~sBJ%qaO0Y#71+!DIc9|82AE#oD{2ex_``OL>Xt<*=Ak|hOTQYM4qN- zad9M*`BD7Bkl59I!cc&YG;e)7ed^nl;$^_wxNJ@Vg0J=LaEz4$loEIB)<2qyEr)v_4BuGLZK9WrgCi;{N%AV z$#gUE^v6bi8EXXp9_N7vK;ZEf*k1lUvUnNS{2Av#{WH$n`Ie1I4ju*FZ|<024_JgE zx(fwa{xFFtD&(}VnVAJL*V zV7mWWA1q8M9XvTa+*-gBklyJUybF3&Ryee5Ec=$S^dM{oY(}ekD;VUcJB!H}1*bkf zoTR)2jmGG^%tB+9C$|>2VJB{Sq{a7tLSQnZ^S-gs9nu>-lpx z=-&DdULV2qyo-IYvj2UJa6ABPW;?yC5hexJ;*rRwkUgoNj`Wf2wkADeJr9DC4o-JK ziZFKz=ke;eNOizAzR)L!sy5nHclyVRHsDt4cqp_ciPbS2#Nyj0P8%5 z?|e@al$iSjlR;rC(gUrHz?LhttD7DDlh#HTAkc6Zb^|ynIOXOA#C`UEp1F69(huaE z6YTY@a|ZzFpKG5F9)R04<0L2g7odJ%%+tWN?_I<#@IikI9JHDSQU@hHzn*^&0@W)n zDjK8Arh``5tD52xK6-G%zJjiuiy*h7?4I9c5i?x7#F@IIEjMHfA)1}@lpg7(P9SBXVq0--cKks$L%9F4|7$tw$W#IRb^7~l1;+R4VvQBF)$Lv}NFCBWA}b~PIQUPS-tv(d+;pXaY% z?&+e|BxIK7jQzgQS$T^H=HrB1N>yAChqj_8r4(Cs^v8CU;`#hlW+lq~kzs z+0oe-c$<;Jm|iXhFH*ED@)>;cv!Djk;J=$dBEn%2MpG^tYWz3Ar^VVX?ASj8P!Sb9 z>(&0dKru#%W$@DBE9)r^t8W5;(aXp)Bv;%lEK2reIeMsn*#G=XQ_pVzs_<)P-^%o7 zkN==$c6@t+;m%l;liW%|ziCW=w?!!W-$;FV_pO7|yk@$#$w-fI5}$Q$o2<_wp5VX3 z4+bELlVdCdUuX{dGMbiMR5i=U<${enG9@;ZozMu!x-by>a?JZ*YV#Z5gc#?`8N``x zP&^}54NC1<^Qp{HJVafmIct4b?)aC&(o()M6G%qa|4Z*2Z`8XX`Bi&)W#zBF)+$`a zAwQuPPZkd^*ME3CRpoFBUMsZ8>P2#nL1_EUX!*VUH@kYGJ>8S2QnU|YNmz+4YHYdg zdi_pcAWNgJLZb0csImUghOSo5n_v-{(+2e7<@|X;4d09i|65?WU%R!398sCZBwM@y z2gLj4{Q&ruB+oq8^;FW+{rgPU6s3X~k3)pQqELqQfj+lP_{zdJx->~n=?|iBkfQE+ zH3iE`iar3Jk%Hp&^VpkkctPg5ooCqSp5gmw-V5yalm6CpIby8FVYxD)sltqx4OgkE`8T}S#eYTRZkD@Ra_)75|p7XG#i zp$h&_i;8PDPO$|?c8<@`yccA}rXF#R?M|(+5vDS>W6Qj{n{v9gr1jNqz9k@bd_U0& z(0PE^6r?h9k$59&!53w31|Vufeuk<_%jwQ|qya>k#cuEz8&_17SInVCL>mO{DrTBu zFJ_HHg6d}6qDl-1F(eG*XT>0MXoM^xZ--=gMNvOG3Dp}lIWO82nEsMvw?m~HylXLP zctExK1YzLPBC(cqi3Ir2J8ivUzl<1W1uEGG&R{S4@qTUz24jw{o==e&JS^YuBzG9} zo^pP*P0WbV9>gKbujA~i1Ydtn$TPOmdbP#k^-g@J8J}k@qA62V_Gl48Aba(_BprLh zRi`+UQf#r{^EKQRDe1Gi=R&IlpKsg35{65Ycgd?Gih-R#?MQTDGBW`cb98ZeyNQ9R zS|fn{8ymyn)f7@33*L#4Yb~V&HL30*;3%;N>OE|X=7{<$nQpfIy|n|obnSF}rMg$3 z%H8a|s_qq#uo738%1=p#m=2wzlSGN@3kor~KyK&C{eh!r*J1OV%iwg5cM=czNP;Xe zXxCP~eVtbWnBqbZ@kTjBgaY#-jY-gS4WFHih_|!DvPwAyD>WM$-Ka+)GF7WCDX6C` z1E3YZ)xEoxA7}KVwn0k-{sT|`JmYKrMgm9qGZc4ct&v1aa zy`LRY@b*N5sU~6ajuX+dKvBH*N86oruaRUX(81IJ=mm}$4|(WQ*?nu-C?8$pIS=}9Gi@q%)b9rfXnB# zh!{+)=k1q&!=2o(>Ln91nD?JP zix{PH&#^5B5&X)`fjV28>MXTNZU>T4iE^x$7VV;u44Vl8p9uaZ_Ahr+D-7THB0m!A_hb~aQC{3S9Lxnyl+$I(t9q$B&#dFE7zUh5UFo!>e+EMhOsW4=``N60L8}TD^P13FzB$m znggr#Ag@(D#PuuaZa<)S`en-E^~e$=&NwWftTpvb7dASI>=S3t+mko0R2s+xHapBc zrf=2RD9&e8?1mt@B!j8uF=bolr?V(506hNHIT1gsLl9ekp(G@dRZIS|WVF&XFD_2T zXl}X)OSdi4`SLMJV>mR^tbm6(Whyiv80{ep-(`Yf7Hcc?!EJ>jY?wfh9-p;P~CS>jMEf>&5xzreQ z2lm~L`lzsh(bwv$x!b`5eZvne({x+*-6-8-uK@8#EUFV)y!E|3Fg>LQUbI^rc_px+woJIu=C%9 zH}Ax0;iGb@N^m)3TNtelveGE&6>q%O%d3cL&nLsVi_L5)_D6Y>Kj_h4m+279|OeatdNBVQ)b`$JQNSqPobI`ktAyuR4ZLYO3& zlGPZ}%_pyeLtVyL$Vo*J*yIJmjkz2plYEl+g<+IMD?Y>~{a}j zs|{K9+h(`Y2nbE^?OOPuA?WFBL~pcvm?8LZrZE&6Cea220^owcnymwEFt`3 zT0r|Hdjh?5DZNXFjYn>X*1g`V`~(H=T%@h7PL>sn&icrmc-ZaPof=$OdjQwa;=6QJyxO(%fgHN z#bFpUXd5}mp=YcdP=3bVZ_#)2C&^y%l=BRDNCw7Anb=k@Pd)+VSn&N8^|ZFjt18GY zN)=LyXiy+G$I?P_6RlmQnB{5TTa)YhZ0vUx)oDs{q>wy|v{{Wao3W*U*hl@0a+%A9 zkz~H>%a_&B%xy9RThsOA-I6`AOtCmR#9s6d`xm?^FctZ<fAyo6QIXTNTVKCwiF^BG(|@2z9en2ts&>4; z+O-A#acoF~f5WKnYr)VK+(!4YwxoSwBckv#<8Ekm$efT7saq-lO zl&w^SkRb|8tu?aYX2Y3-7lGZE5JI|^-S1(lZZldZFmry^m?q7XW;K)}q381y3_t6a zG4MyX>k`VRelz8{T9+~3vku6;Y$ikpzyu2>Ck$xU%}_A@3@qsys8y*pFkBZzT`;mr zi!~JY<_%eO!{uyn>C$;7@bM&=4xo_!=+XHl$=7wH%JPR~FV@0c5FXSNySbke9h5en z7m?_~cE-u8C28%8#LAm-^GRg3e2O3QhbjbR`N3+n_#PpYs|=b|jy-tPS;4YC`K^zU z9Abw_)bE+fQr+4|3HIjKP{MD0TOyV(tkrnYlXZ<$;h~*7zu8A=ZYXve4)${^oLT&Y z&FFDmjKPk3e^dvhOF+Bc2SlL0AlsR1I|;Fgc$3CHy()Y~WN@QA&e9W#{yutxxJ5pb z^<(EkUhx>VSzmbVmQGj<8NTJHfl^(s+|)X{H{{Y6HweU4uPQaYnbL#I6N6SAcdYYh zu46PZG}Xmwqkfh0JSU16Am>}M%r230Ar51@uVIvh$JsOAmCsXi*{iKBBn@v78fCwo zy0day9Kg>GTEeGOBa2_nh!&AjDQ*r(aP4dRrwr0{1k(w#-qk>$i1HivWd(k(6W+0a%o?9LF|wR5!WxX8Xx{1|z+Ua;AF*$O`|bIe|1Zx^AGmve znLqvi^8Bn}XzWc9MU5wZqyati(gY$%A8z6hJ3)va{%dzQyqDeKU9u zFh)5ld)XUK6q_YLpUyR_h2W3pM+Qs}X7oRvpO-bmAAYYHesTOnC`QyV2Li<&na?Bk zfU75mu&;+7^KXA?LA`gIT0%=Yd12=sdL{d7bssQG#5zk3Ek(Bm{B}wvIj5xI+M0pUa=Y$6asjj7y%ho&$dYQ=f*fBkopi z(jNr3S|C8FKwVJ!GtKB(&h zDf9Bv_MiL5Zr~Rb#u^nc=m`_$10_5IN5=53(k?&jZN7Io+Ncd*<_oq#{Lyd~UBe{W zliKRru_&HZs<6!a=iy%oNf3^e1Y-(DR^Hs076oB{s$~HC@&!ad+6n_n$GlmT+IPwi zxj|UwS>jrMUDyNOdV@-^Cud0jnYC;y3&%c~8mVMVHKO2Il;hsIx@!#N4#QbFUU&}- zy0yug^qKhs<;lP(9%NYey#Fkl*Ja&4Q+eF@zVKGfHPZVkL|iYm$?0sN*M{itWO_bl z2?Ymw?sMKq-G4prZVo89l9NIH0%B<2r#)JUzW=Xp&qhn;j%`&$er!u7e4`hDLd?T#pXBZv~?x`b40-CaCd`dRyY9$6HI#H3WR;$ z?$}#1`rB#!f;W~uqFXBRsO#XPYW_U`S5lBS>;&Iq)HC+U@5hDb@N4)U2V?%NoIm;OCkEl_ARyv;RKQJg!Xn;b$$(|Up|y8$ zOb%XD28M_!Az!&o=^gS9;14@2{~ocu|7J~6TWc%1pi2*GGM}zrOt& zx?uiA8tv3ehX>ZKRUA@7G9=oPikOQ>;Lka*Qros&js}GPQzRk*!B8t%#cc6b5}+pb z_N*9T4t$xKQds(fd^{0Oi>rQ>-hADv9nX8Xst^B3&y+Znm7K3*&e}eA0qkQki7m4~+DHG9`&lMPt3(z;n0^Fv!v=dXGUVM+Ov)?(ujXxor!Moq zX^8ggs!z+&(i4l#-w&;xIq2DW<`6BbC!`2JN-M)#MOu`A9X-#7@yz=_pRo|d6^%q; z_}fdudFv9r@osmgT4yXDK5yN{QP`k$i?N@5DeXcp>m~#-j+>@_2nRh3SM(hY$Xo@j z(H|3dK6U-f$-5b)=Y%wkJ^8y|N zK=Ycq(f3!ZFJ;mq{ro!>H98ahdi(kt@ohzfbi>}Cq$ko^MqbuN!zg`FYjQtO$kx5e zS{Ei;Me|g8`JC*d+jg{r{ts(!0Tk!9MGZC{+}+*X-6c4|3Bd{O8k}IkEd+N9gy8N3 zcXxMp*PbT1_rCkyUsE$vH5665RLm}Q2fu%bZd(>SA$W+BOj2ai zK9+z}tCnMV&nH!);t^Hsz1B+a?o_f=Nq4%WNvIaU(=D;j9{bp9cS-3&Z*V0G2}Tg! zyQS5$6Tusu31fbPF@h_}#0j65?jda^wxgRMb}NQ~z`^E5_8F{^AMD=vSdWAz?OpR) z&HP~0JVUoBo70M%?*TZ(e#Vs$-E~-hUmgWOmY`yAllmL=#M2TvWd2#JZl0h8lqZ#K zOS*0Hq+u4BjsWMbS(<(k&krP|aC3`8RMt9&7h@N(e5)uj-o2OBFL4A2DgzL5X;<3F ztT8M!v@nUU9&z&AC!Q&OFVopsx(AUj5LFe}PZ= z0b(xX$6t5nvgVkBEjm9xK)D(lDk_|#s@I_-EphZvj}FZ!vaCTk%yi1terpgMGP4hZ zDqiNEwo1yULpQP95Z7ZT5HaKtTd_vaoy=-euFSCa%$q<4&b#D}{B~pg zPffb@SB;?t0et=2rl%+grj)Z9YeMrcwfLIY*Df2Demirm(1fE{ z8%G1?%{<_uu3gsci_r;xG0{EldD8bUHXzDGx*O1}EDK^U%n*GAW&zZQNpu|aZfoM- zUXuM9p?wrTvFnxSr3hQCUfGf%dvoYRbfYv?x9AhwpW(E#$UXHP_KZe>m*cZ@$&wKr zRm1W@Q3k8S2?F719cV39X#}4vjDkF4qEQM*>Np?jyMBp4Ys_s}V zmBEW(P#9tjX){yMT+2^g)whq+5G0D)b49q9Op_!DF3R+1Sai%byQQ4X*CY_8Au0>zX0buZwNJKxfRy~6i z+dPaf%)P4Pk-J>=e7Tnb<50wyaXO-zz3{r{6=k$@NP3eLPTqxheR;8*@s0ubJ0EaM z>wGC9#l~!PtheCRR%l>r^sq5@l^m*;*?zM$6YUTCgR3iA4@|U6hYB7M%<1%uD{VDQ+)Q}+kqpfQBG@EbOLX6lDG_}k% zEb((m^lM-TAXvu`U62b5AE4l22|E;ZO)qL$TOGN1vw~?Y$O*To$XLR?)%_8CrPo)= z&qsQrR>JrTR6T||iXgwDT$-uqRe`XTDvjZ%x#N=JN7MuO;13-iq5vgVOCyQ#G0rg43DCd$97J{Q|gv0+{XJ{A-; zeLnB)Uw42C#jW$+naJ*dP!E}Np80oy8SxJENqHkp_8UQGq&PV>(P<_bI>Yq7R zqy9Jq>&@(AgKJ4ghkNtGl`jaFtShV!Gh7j2c3|h+{peD2hAgIn(+v=t{{|(afe7dQ z`FDxoPhlCxBH-3uYd?E+#hk?iMF3#!9mb!bMD4X$>X_`S0!sNvSy+wNVU1cb;`9-t zoyjFzwuS^|+PkD%Z5-R2-UQ5AwxXn)pv|0$+#Y0-$t|x5r><8pJ)$nd`y?tKPso^e zs6Cb`g8(2zP@VNyc}ft=y-@c#yPc!Z@x1CHw;z_^t7KDyHbFoYnidD`YWlcpdCz?zjBQD!#g> zM^+mr0JuW!_MK%WMu()Og%Vkj7M(TVg|(8}dA@hblrp3Cj;xnRF}o1mJkR8mnGgOO z{cgw|(lim#zLeo-_E#Bplz+LB$H zjlceETq`7cwPvuyFE%8HB7|N2Dttb}(%gws6c89_WW^;-4daSbY!(W_4KHr&t~1qt z1TEnc`tuKPhV=C`gdpq>A*aRa)ZpJ1U1PA>3j6@bKOb(Shv8X-Km4Nj7#%CKNRv3) ztKz)fgTwgwVJcTE4pKqWPk^^Qpz^x0eG9SZ%TtmHH8tDA`enf0*i#^KVrZvyE&u+5 zXYQzgBVhCA7Hylth&bI^3HfsKl|^o_%yXt$!rQt_yR%2}fKPb>S;!l*CQCdhW<*Ki-hCo`#JnrOPz#eayBgIW1BYtS89)rKxoW2~j6{O9fQ&$!x25Rw9qiozyeD>EYK`*|HR9Xe&w7d=0VZ!f^R}kIVev(w) z8XVKdtPnVm@)GPj->&#&MNygc)@k_JBZ^3jEPa-p?*0ea*HY!BJIQa$wpfUD8MoI> z5NpU9Xh^adVskSyaR==FzPZvvj`oSt&f1fmnD70wPcWV<`#c(oSAx6M|~#Z)pn!BZQ?HL)CA10c)%i5z;g5~-2<)09Z_LhxnlVVT62jd9HC z>Jt#Kq%7i~g2ntYjYUw;(!6)>`qpb^nr>UpT!`?{({f#=7!}vm@{8aS6IPVs#mDxD0Q~1B4CiJFOaV8YL}pGm_b3~C;KACo zV9ISbr=Lsar&1lMNH*)mqG?7j9ZDm@qo-mR`X&}RXhSWQQi2Pv8$V}2lq{7a?(0vDDTJ$AnoALMVij-_V4CYt#1^oFbRsl!m$T2i zBpN;wtD~}rQod7`WMjJdNDY#h0zM7S;+fH(V?76)eCsVbDFCD=41HCnJW)D?Me!NH zXo4*DMYwqbl6veb&&rr)ma>=$RGITn=R?yq-upNcU~FMAry`t4H#YZy*E?NS_eR3( zWIR~AQNFVH$FoI*R1L%%u1zbW} zG{%DqPmH;QYiO4>?NCan%NLI2yGLmy$V)e2VrrNQ9j*em*gHw0MCNeN77@y%uaLva=A(qA7TO|MzVqp>eKOE`+4dH zrOP3|kJpB_OL5tMiShjSEkXLx^7(G(SdC>F@V2<(f|!{0X5ay$sQ zIntOJ$h*@09dkQ_(g(2IvD@}sn1ACUWDjgOL43-5*nKKLRgrTrOf6$a>(+ijc`}xP zLpNHZxMFCylM}lh>P8q_o|g9BBmunKr^U^c5&7`Z6~2EfSQ>S2vV_ zCy0416`{cy!OHC0toZIlv>Ir*hNri@-7Jofks==3l4t}^rT^uW;+L8pS&X}#AF#@u zwawWAw<%bAXO|lj+-*!=eT-nCD1(f2*>X@mXN6rcET6(AJ5cF$yNoB_t-}+L{?yVam)dXsd zkk@MMHGk3*_PtT9@hkzw(~Lj}TUFG{CzP_1kl^l&EGd|w*1O;)uAS*m8E_Gi{RH2> zw$bUgh)v(2LOwXiVV=B4Id+_p21xOzaIy-y&8eP}umuPt z)97WRUm2SP;S3-iIV`<+C*k|-pj6Xu&u$HQ_$#&YH?LYZQv35A#|?FxpfsKCW%X2< zPKG&gv~@>M&ev6%;pRNmePWu(wSp@LcJD2nF>wqC4(gu|JkMpuNtfQB#%BLWWgfA& zq1b#L-=%fCY2&RM1V*@A)z!h>K2EdU1=8H^d9p~HCBrybD;k~Vmex9%GUlG|suKv7 z|9o>vqc=2;?uVWttZKBmzRFMMRpXI*b0YHyvCy%qdgUQ%zukF=+}i3(z?oz(pY+#^ z7lZyyD`EuzS^xktu-_^JuMtyH(EER|a$*Vq3WVXk%rI<%GAU3RjjbIcmNuZGT#w&@0_Izd37i0L+8khz+^WCevW(KS#0|_z7ij_W!cdHVOs{7^iZ36h5fD|cEqQ8k9%>O9f0*$C8Lw$LLF^C4LDuaC(j7l zjQB$!%=ax#j-GqF3~3n&_oN5VgC-krR=#U(A)J%xkUoG^m>qS?p3A7d>Znf_Jn5*{L$l|MOeQGz1UcJvfibXVCI;6?t zu8V-O%c0X@2E21wNxSgR_X)I(NPEM)vzjHJ9o%u)A2Il529A=T4f5Jv-)lHUn)$UH zu<+H^t9OO%_|1iAg`q~3LJ~nV2yKG8<==siSlw;xT0_Nko@g!|6$w>M@WFd?VV3B= zil^&d%u!~>DM~1}8B}G+aryXTeP<1&ZM@CAqW8r~VHgzd;l}|pbP9##4^P5g^Jzv- z<1fe()SU0$&39q6n*(>qPR_?5Kz8(HqPIpYa10ApT4 zimJZ*{9*`+-Lyv7z%Xm5lPtopKk^~4@aEG5#s3kDC%C1+7Ib*wp2BaxZzq3o);#XF zIQ&ACR+YpDdnX{6u)$_C+j(jE*>coSB%&w#&?CEM7}np4+F5Vb>sH4_51eEYUvrq7 zyb_mFb1wKCQ{{M_`%L5&E|vYcRk`(zAN0D>R}fspJ#84ux{7WL8>*z zpJh!DdT|g_11Phwtk~z~I2}1BFhA0ez;N%@$_; z{a2sZhV8?B>E({@HJ1z7HSA-EIsdWH!Rm{o^>x~L-lsqhVUNy^VJm(sB)d1@t&?Sh zswMp6nFQPNR`$pbgI$^J=2Qd#$pxd3dz|;*6|@*T+-hu++VO4Iw`Yjw#VNaK3_SX+ zF&e9-+kdG+z@-lhB}*gL#+ekRYOPUZIr(FFAkq?;?_G+{W)iFie{;NoE!MObO}0wC z;w(K}TlxGk`5?9Kn&p@#@h$Nu!r1-jl*R9DnhQN9)(anF zJmn6I>KKCy zOHG8|aWt4Sp_JQNnRepc2m8y(ydgN+=`L&10TLImL-*ipV()pienG8x)E3Ih}|h;tHNovtRGG@*pymV1@O+A!9c=_~b(sAeh1! z^MVNhPmUz*?RSF~*plI=!TZ(&jYP}ZIOM5(#SC-*s4YEQqBm|DR|#$Ms!4HhU#MJx z`j<|*8;$+^OEeUklVqlH98^$OG48DGJl5p;?z`4>vgf6ijGc+PCa>}Y<-_Hlz>{Ae zzSe#=e@RmLR0DkA+_IZ?73y&zfBV&Vly~Q*=|mOTv*c$Q(^9f!rPAqACU*N%|2Hc$x^EEC<)#AEsZFe{~60*lY)0 z=FJ7}!6$%p{zzlg>bWOS9&|3{v0sRO#V}Zw@i0Y+{MkE!NV32T6tD{Rb`vKK1h7X%vN%w4&Ky`fCtQ>_$_>4N3Kir~(Z zZ*wy&QR6M}OM1wr^#&10l|lT}P!4ldt~5FG1sLkn0gB`(AS(&qMNBe3rz?431%%FU z7Kow?LE^Q8>Uw2*f&c-4j^BGa&<_=e@Ax;;{1y6h^_$5H^O{QPk6&0uo^J=eKrr@O zyY<_qF00Y=7atj`uG6d1l+khbUn=0!hV(GQ?Nk_!vzKY;a*mVw>_xnNKPM5qEH=?z z8l^kefgzyHV86MX<-fU{lKJ|6MH;iGAloNGG0&~@bHE@ zq!>nIj}Riu8OeW)4~E{zuya8_WnXMDU*O80vAn$n^-%zPtILir1t{lSm~}Kq6K5Qv z?-r_jh&2Hdd`tg=az!_vkL{#NCjxjm194%&H0^IS>Z*Ru!V)?}nG587V~Kl^lJhiV z9mU?RsaVAYq&qv4wJ@60GJV$?<8m-B7C5vkD(OuLa3bSSCmTo{7M2ajLivyx8^7L;46Voj28<=X`j&%pR0q@EUX*uHC5( zq#m4mH!2Q^!bmLbJ|Sm(wZab&?{5R11kAghDfOXOu7F763NmnDZ@hPwRva#BWho!; z`UDAY_uwORC=ld)KSc!iFq9cWD+lnwk8D2VOVENc;NHFKj`)F9JB0=2 zW|tk-!@nqOaDg7GwZ!=XgK9iJs?67~%eJr&W%qk(-opE6+K(^ihV^BiLoGibaUDAS z_;X(VzjssFf3@oWpS!7dApie;uO{|ayZAu1`uEEFuRfb#_WQq~k3eXg-p$3dokR9K z`2Aj6bD$D}e=}1+b%2Hd^!WEVKNiUT-{-i?0ljOaL~x_2V# zi;=iEJ9(>>SQE5LuD=V9IVJKlp#7f|cloBWv}*8hHV=l!dw5<%M+bAgIo8yAmEwxu z7f6v!ZlB!;5i9gm0XLRcm8A;Ey$?#Vqa?e{u1$r^NvH6aJXa;YU3x#Q<*Eb- z$>4uju?OJ};l@r`)zLQU1^wdoOnm|WfL-^(+7f!q{TB(35R7c4B1(c1llsVhi{tDv!ba(XOqfdahZGB#lzI-n=K)EO38oLVQMP}pE zxi;WBoq!$z%+@Tcfa<7i_89gE*m+*1L)@)ev=#22zdB z8^xt`Lg!pdo^;EE9qpUDL2A;pJa!{dSUlHxq827M?s9+j`x%OG#iAD~?Hw%%(b*s2 zLOWIXE?xFtM&-Du#iyNGwm&C*NT~K<@lets)JsaOV*!pGM=sRTiP($%-4074+SHEF z-N*bP{SfByk(njtqUa>ky00XLWkN9wtWo)VQ7`@>6ot~{9NJ3vsvMAT&+-Gf`uQZC z8u#r;LrjCe?mF52t6Go~EU^maSh`tC0jS@`Hwpjk(;pA>h1Smex%QqvP0V!S9H|7xcT?5{5{`SY2Km6+OQX+VW9sXRa-M%%)%y@ZQd44*vX7k-E-|1bAI z&b}>h$JW$$K*F>XIf{HwRFDn7`hWBSDUgRibCrqyMJMi5h3&yD{J*;4K=u1(m~5Lwx3UjOM3Vhptp3s$T)y?3y#E`50LHfb!Qs@O;F8vC;p$PybE z^YDNAI`~hu>t=Wwv)S_Yqy zpk1oINA6o0{`YLAbI)&p+y1g}?8ExiwP^0m&`a~5Yubq7fmg?+Fr-5xdXm?C_LS?5QXgp z&(xxaZ-hdd{MIl6ex$edHL|{?@V*9Y;e`Ib59}Ub>K)O(f{sf#kG#2tad8BqkG*) zb}gA|N$lV(20AizFxWA7)Xsl@P98hX2p&S56}#Q>!PIP41dQI>!m)`vB23jv9dcd9 zZ&k`MPf&P31*>OA%nqt7)TOwc%p`aQA{^ycF5)@@7sd0e(JYiD@h3yJO z#RlXVqI*(DtE?46)=B_E9UHOiKw(81V)oo)b&n7+#a=4E9|Pm+N-e*P+osD}9W9ia z6&9rO`;?GRNhBQK>d^P{SOW+}li^NF6{+}ps@sqwlBq)m&gCDEK3u-ck(T#%zV8sn zV97{%(rqSf%&*yb*T00oqc*;eq0-Le1>EaL$<>M-i2qUDNWRScJ!9TtIW9huL$OIr zoJVo76A>A<-?4s$%_-0a-hdjrPWcT$xTB9aOGJ+N`ROL54{PgLyKfCQCkxNPCV%Tz zkT1h(SG-!KSebm105-7deDoIy2El!##8=q0R6$S4iZ(4IrZo{+DRJ?dNy&G(WI7}r z>QF#WK&UGHiwbHKS{J#r4WZ*<>|?7dbr?-)ui+9ELvc`x*I{rfq=IT%0+!-dOV@YW z@9y%vD;==nPHrD~cp~yq^`V8|zAwe$<#wI#VtlZw(W(jJ4c` z<9Mm0?vSw2WhCAmQBJYBcxrxSRij|!zD7iYO=WcE-f`{*rZ7vz)`yd z06-&tIbA~(hHe8GRy%8z0CC>xl11M+5;YSYW_|4+jzz(b+|7745q5a-+76S(1Pleo z<&%JNhgYI#M>Tru$MX2{0jvve;!^;~Ond}p-|d!Hqr<{BkEmM9+c>gqq6O@_c+n68 z?INTy3(^I@51QzywbB!{ z-OvQi8Sg*{-kfBV7C5dK8XMxC8)-6ZzT43qmP>ZxWHKF^YDfhA3jXiY5#Wf)Se`gh zuu9x|WYyevv+P|Q@Dzf4Hi-=ZJvB{y7OVC>+{!iDcZFld1iMYV`5vkpb|x0_&xrKY zu*NF>4i0Vxmvh6oJKH4@KOf8mND!O?m!w${-sQ*&KfWG101yzOV=dG-X_K{*Fso#r z&6ULn3catD$8c?cTZWub0sEPpxedYh4f1HBgkeX^DAeQO9B=JYMPOnCt2HizzL4SU zcQ}Qos7eZBiHxE~Un65>=w7zr&XCW;jZtSUi!x^$fS+1CCT~@fh3@@WSbjLPb8=I% z*&}nc$vFp0K^W5Lgr6Ctg$IDa5>POE)KgMhoCsau3u?MyS3@y-Hj6^d7JUSEiN30_ z1By(RXV2woezw|O31Mv5O%&v+L3yyM`@^h}nIbL?5ZEGAw)sXUk)*Rc$S<^?nPEA2 z?Wc-$(!qUUxWSqn{83Q=NyoCrGnsg2vQBB!{FJ^M=Ju9r`OzEECI>9=N8&h&!RZ+w zj->DLJuULS}AA7;E(B&j*OTf7Bss-tF>2hA0up5M(Tzbt3y{L9gKb8I)oYQ zTh-_uiX}k_@Nel{sRn$ETqqN=b{|7(YAIr%h{|z4Fe7tjcyLy;>c-RcwI);Bwz1Kf zORt*?H78lsk7Sq_8~Dtt(B)XQ=L4;FVWWryt)suF=Y7md1kNgec~|sGw*eYh_WNpZ zs*{|#C&?u-vH%EBN-()cN*G=aF9jF@dOx~6*1RjRW_x0No;>`L^~IxXAiiG%8Dnv# z*3cm;-UaKWRQ4NR!-(I!ytJA;idZ8r-q(Mz7+y8jb(tF+bI%JAsl+YQ!l0J^L#os%7vR zn~vL+Ro*Nblr`_?BI}XD%~WSFdq`1@>j&5-+ZD3fBU`rKI3}ZW%$dsgW07wa&>14W@!s<$2lSxs)zQT`aFjvX92QsH_5dc-*P4L{qG#Dp$-& z1ZzzFR*Ovpj;BsLH1Zxa0RD-jke-W^6YBS75wAW;(nS;3S z^+8;{80me^UtFzTN+bxpwxxlaGplV-tT*pN1kNG%Urf+o0*Z_~)RB0w%bzp2Y#N5@ zSSMGvb;}bp+=!8d#qc_P#z>siIV%au>*~&P-m~ac^^ae=6ajV-yBCMfq)5Nc^9P&S z6JolmiOl8==py10(tjz2>8h#ppshRAFMr-Wn?;Kr0}wjtM&$IAC;7KH2JWvX4(m;V z6up{)jasd|(cY2(T4kzA)V(jTgJMp*)5j|@Jo`{YNRo&2IU2a+_ydAan~Qf?A4!)B z#+<3}m5|ls806MffAj;Do8v@HEOB!giYvoeC(sMo*G{p-@Yprk$~7-S5%2O9gmVMZJs$)mWD8Id3TMSpSjpW_KMI5 z{&C}J;t^RjSNJ&w=De16-MLqe_)zUH;$J?u`QJ;o!bh@yx%dT!KZC1K`bz&`-92Zr z98dJZ#TSW5s1pk54s`dVmMJ3j2@@e)d6zXzH5>qjhKI!?OY|0UDWl%r!s%|fDItaa z&N6*zxB;P5#}URKyIKLIp8LJG4 zwa7)^4Ww9@Eom%=JMWxo<4F;O{(iTdVy7B(^6ccXwPlV@wEOcsXy5Ds7I~S1S(noK z!p2=-`yy}#1*@^72S-5X!slM-KFaP}!oeF4x#tr+c0O)X-X3>O4bgphy-*{6(a@a| z>Y^Q8s41YE62+$gDw#uIFozOjis*=+pjbv&+6xconDyJRukj8F} zJNsho8aOhUT!mYeD;yN%bU8>4f%+#_tFL4oxSO!lSoI?xCS}Rzt2@Zv$May+)d}$a zjgiYra$~}L`b5LZPh?s&u zn^)rSz6Ek0K;7M5ww?R9Gs|zd^#Cibk*;%h^^bsc9@`$o5Beg)WG|^e^d*S9(aX$h zJ&IP674C=LGpMJkhuO!>bNxi$x5U4Ehk%UtiO2MpnJ1aozI|R0z}LWx^pkXrU3un0 zCP-NX6qrFvxIzO3W=?&jt+Eluzs&CIc#-2X>tFBK#W`o&&d(O2j(s{Hm<7LO3o)*V zbx(B*|NeREjorFFk113vGo<$Zw+!WU4yUkDGuJ1L%r~$L7dZnRL8cGmgq}aC0=p8g zlfNo*Vap_&kTf&21dh%cdsa;svhH4Le+;4w9*r$mKT7!e1;SVE2 z_>O{9NJi=mM6Lsm%3wF&!@PGtbZ=%;2PbFP{9U}<8)tS5>c6Yx!wo|_3%@v+LZq#y zXJp#^0bUAGaRZ!9hZkRF^CD*kU+<%@eQjNjx&6zA(&63_F8i%cIt)a?%UL;T-AnVH z&N2{k)WRwL3L40qz2-4&!5I3!^UlTAp~8; z+JjdgF0-wivu%FI92S9kmJ|>LAm!Mu{02JW^0W+RF24V1*%FgBCZ3eiBWVpg1{O2@ zilo)=sK~!uz{DQ0-L_a+bZoim4UAF~oACT0hf^u}J|EtW@ZVLgJF^U0cqqejq(!J{ zScAcnp%Ltu)!?Imyl`f;{mR(v0*X$6oFB?QZFrBzpW5rj7;|%kDVws``x=M&SB+Q@ z_O)(F(LbhiZx?g`{Nx(FnaSS&I~@LqOpP()m@Q8BoeTM)Ax|f%)c;*f#dBYHw+R7d zZjB6QIoki(ZNmT-XOert4`y718O*;lI)Lb;iggOtMal%b@)tOr%qx7?amz zLk87SPaAXj!{ZYyFy|wf{NbKF$KRv$=kk0QJfE;K-kLW~GV6tn0A<9!+OP8De52 zo`gxCxNN&ifY9F)pyu!SO7S(>F6sk5>F!T{Q115hsNcN~cTxa(eSth+WO~&XY763! z5Mx)lbo)tXuKM372s%>@WF*u))e$&Z@%eG$9XVi8^ZyXCiR8p}T9lWUrGiw)U8Ws0 z4CxFj1Loz58e4H;6FrxI=(Jlc+kE@zw5~>fcz+t93T3T-nzKX7+7PxaLhXcS%90_d z`2JIS?)=BZfs>CVqOr<^r9yrV(-=l^@;8_=>N+b4e8n?YMbjE}`6JhyHPj0((=x7F z-7|&wk5nnSBB8c!7>G12=1ZL|em^Xh<|jMb`@?MgQOH$J&{acIuAVm_WWO0@2oEBk zS(>nCOLR5IQ@62Vq<>IS%cG*HPDfy9s_9CeKC-g)+b^Md^LC}c&2WPhR&-It^Lr8j z;-PyYy#yBs@1G|$C{48buoFE>Iyep&P5cG*JfK6>_*|HhoIH z?yWUMV2!1AIFxVu{o7zt)83yJcn~6o_^XDppFVciNx7!&E?3{~CI>D~a%{^Hou!!c z=b^v2Juoyw!)pXE|;5Oibapfc#>l{1~n4lA59?ODb+ zJ8H}76iuYUC5mh@WeKhaHw%WxEjH??#w9BlmWkp~OUldNf5L1)54I+`jO&OpGXI5e zdu4`0ab36?E{NejAB;}|+G_;#Q7{Z@Tk!#T-X!lAL-s;{|KN;@IkPGX=nR+GX$0>F{)7!_r_?I@*53DxAv zaDvdnQ>57o86G3bl?aD1j(4BEWx!jkpRESRv(ow^NQaU~G@?a23#ZImm0Nd?`aA#z z)F1@q_0D(L(rbl@-`kb}e2Q3()nKyvv>PFqxs=AyVz7qrrAgKpX1Jc;{em>3D*(DK4Qsjj07FBO#(mbrf+JTE!IGlP^*lca z&F~)C^AAc(^`(=pQM{CBon`$b&eLhmybhjRjnkjk2Oeoc&|F7`=EPvEg5x>W7$NQv zKI`>WlylQ^i4_YZp>^Wn4;qV|&3W(jHgdMe05+=l>xEySABK*{IWShS)w*o+5mlIE zm@=q<`w_qw(8(+$#HA&i`4np;1jg2iw5-)P$x*QOP1co@mAk-fFf%h7{X+hxgaR>-6G(LM}kB@+7G5gkFJb?MOAI&Lr4kZn>G{o4R_WB}$#)P6>zV6KtY#$prH%5o5TPuP{;P6o%|AZE0?{H#d8jIW5*+OSMUNT=Z zcEGp!7BURiuM<=r)v4ZA6=DTYsbQ?#+UxqZee^^rEa*dNhlYoPO=PsIoGb=|nTk1n z#FMv1?O$QFm_a*9vv0+YRBMW2J(KUUTnLR{Os5!zOC4rb9e03gkl)=nc<=z2SpFlq zE3<+6$j7EzDw9W69gZSkapj>nkcSFN8hR>UlE{pC`bPUHHlVDgO#>O$rlQsirfgu| ztR_5)X5#-GiS&u#MVCmMa2I@3SX&Dg%NEGge%bqFZE;y7awjyKI{y_AGyh4kx0RsA zl$uv>>ii;W-z5RqZ_A|}ul4bm2^ZX{c!+B(G?a!4P+|?|YBS@tq3NAvi7wS$-4U{f zzXf-ST=GM$i*B;2Q)WzBQSxl)kZm?bUoP#%PxicBuGI%q6zdgrS8K~J zR{y#a{ta?N8<$?~*T(sq3FAj}?>qHpF$U$;Oy+M1J9vkHUbkUy4eh=;eNqr^qHqDs zUs_C><`&ikE;(}KDrPXzx-&aIn42<3a+D)P`PoUI&XWUHd@k298Q zTI^09Vd^?XBgmEe8X_KRU55LC*`GJ{*erz~uMeL)&+8s=_%tHDzp>|I&!YF=8|I~H z5UZ`}9?eCQA|?~do5>MlVevfs_#!ctP9#}>$Q<7tctG(WMyd6M(($1UINq&Z^h)ol z+j(@c$$T3Yn~MXr#KXpQy;)G>%kH(i@xi8zfWZAq9S^f^jDrT)I2Cfl-BFOugnlH=e8tY z56uy2z-9UTqT^_U-iP=w;jr4`x19nmL4*PLa53R!#KBY{XS1l^V9* zYA5xCUe%s5J&_Tt8#pM`%{4I9y+7HRI;7=BQ3WaR6-(FMwD^c}kPh#csWk3D1~XUi z`qBfE8|Qv?Gfmm5C!{7QOIM*4-(^ZA<$Eb^@fp-@4hQmf)%%*UHdLDD%Z04n&-kY#0)gG6M}CtZ-P3!kL4XvZj1szBS2dkNq`fCyd}J`4A<9f zsdDn*AT@T`bDAa|(I_XMdJkN1!L=RkG1Vr2o`9w`@O==c{OHVZNhjv3bM@p#@-%Gj zRZ9m-!tfeD5drO8K;P&f+!H912I&74#q)|dp#j;izwtqkuZiz|BlEy_U-M(U%qfeU zwztKDkoxjJ2r8ogl=F(@i~WrT0$mL{Rrwn~B?5Z=56%ecKRUJPG7NeofS1R){a@Vc zo&sMcZC)@@ou@xLHi`0%uHH$7*5~Kd#m3ekh6J*JKL6@x8pwYAorwVvK;?|6MM~*^l0bz+f&DXJ_qxA+{OR; z+<$O$Z~imxX?AXVm@=SdzZkxF?`Rz^Jq$@at(_?i1BbCfi~HOnACZ8&WgM93N={hXGV*6r%_g~2wm$6q((#V|Q4gQqo3i?#S?*jj{0~A*LU78-qa{SKZ^`CYi2eO*e z;A$YAfm(78YxQ%QuV}q);p1#<=Exli_4`r;f&_Zo@~6!^wL^ACwA9fu^fq4ksM_mp zbw#7h;s9GFf)l>3-!Oo<%A?(=Gd}R51{g5wxB`*gOm{03+CMCe{CD zG9iB(HY3^=aCvG=Aiz$jF9D>^YRkF=%vCHab&lA*3>I7o9;ifzXi>+tkBq8cFP>uw z5`porD!oz47OK^{LOs|$DZIvh(tGuG0yIQ}6@)7v~Xu!OaIS-ujwYk-N>3q4pMHbLc2PWOX zzZgF!dK;ixz6s3bjCi{n`lyM#$1^8L)6=WPg58TLZ_BS6DjiCJDI7<5N^5|mGl!NPif+9$ki&7sk#@0gHGg zdEc#?lRzrKE<2?k>q*g_ksTnbV4YrGg@GN;$3s&+rSWR%yL{Zy0DwJxuv!@7uZMZ2 zn+gfJY}mLYr#?1^S5HSXuUy4W7$*wj~)lz+PXCt11P95Z5EX_K50~5V~ zu%-}|_WUnZ$oCrT(bwbhL{IZTc=B5R5v(_Nn#C3KM&1HwXBkb__sTiI$5bn%` zHcKMva(emxvoZ80n!^>i9z9&Z;oB7?-(R!xCX6Dsf=RBXY`i8}>X2=^r;5ZvOWF8YTUrx0hHu2HvI*6QZ8tn- z1FR!9I!Dfsp&A$n7mz8|2G3!pMd@mduB(OH{&0?Sy_gF+(D1jUSeX5@N!R@=W)Y}R zi5wE|c5a-OjcjZY8{SN+rD9(eL><=ypBkL3ym`17V1XZks;^S);a2rvKtt64nhJ z^+ra->i8k#?8?<-q@nUgXJZP0O^mKy1Iu$7#X(U2<_?kRCMyNWeX%Jkg-}=7#d-OQ zrze}tr~n^4Q>TH2PHSZIotxbLQnr(@o))(d6MSAv(zfY=F^RrvBzMmWE`!jbx6$TY z-y)(jC%z-WJ6=?`q4Ms~eyRw#a(G5B%PZ(%f&&CJ$zuo9;L2){mLN+@8@RTcIsVG* z|Hau~0LAq*Z=mqv5`w!+(BSS8EI1?t2u=co;O_43?iSn~g1fuBySwgwOY;8yuY6Va zR$U4hc6OOF(=(@Mx=%m-1oer-Fh*1+M+g~fYSVD_zq9~x_KnHx2o;cC) z4E#0a@H`ef-v`dC+qBYOvVGSZ&NWfqR?D=Ai^j@e-M~U>wDe29?InM4s6+x2i)Z{vtE`cQ)0bp&57&xki+wR>mU%v3@0b8CZv^>wJS zk72SAA%bRC{tcX|4PW&*ZqR;;_Q+<#%?D^K?$YWvx3lDQdgqdT`nh%V1gqo^>rZY| zi)9T*B$LCa-Ki_=$FUX(gZWgvCmn=`BWjVmW2UwvxnINQ-`lkBFQBg^KiB%aQF$?p zz|6)a4t=z+%_G>ux|fo!ij}^bBNB8l%aeajgs3euTX?o@P|Trg(J%X@S(A{>Lc8h} z93ETOw#|ZiiluBjV4?nHoUbYwdHzI(F4I&2QC5glnAT#=rhg}SIPoTuHcn!4{%mu1 zA42f>*E=rIjZu$;(^W;XDiy8=IlRKbI|7bqH)xn$fde|nB!bwLdEILA(%iOQ4Db1R zLO;)iknbw^3I#$nK%EC*mGc0%y;I&qC$> zOVo=ww}#3LVd>V>7z8{-uh&u{B%?;Edfo=3TrqvJM4XYXUN zf;#7mJe(j`scCV=qm$7xxz4P#Wer4r)Z@w{uE_wBp`Vfg(5aOy{;X2$7jS4^&sc96 z5MD+FJHC3S4Y8`G^2@maC?iORdF^}I;6ED=OqbJ8wpc`l=EES6Z~w|&8&1oeazEE8 zT-zcndcL`oquz!J7NT!c6^U59BF8VFdurSW*cddn)Bkpph&5>#rx#J0hO`}WF*g|` ziNAeTPWAhIu)*JwY=;{HkWf(TD&3+DQYQIqV=P`fPu6^g!P%&FD`T6OLJ6g{waZz` z+xTLN>pmp_lpcbt2~ybY54wFHan?Y^4Rx3ArPd-hQ{IS~rVQ0|+#RH@ncB!u3?S;s z4FHi`9HEo`J)ga@tp)=Cl>h+OpCdW=!2job1`n>c{ex!U2A?$ryS zLja#E=O56d!kWWwm@)tq;_k6_0A_DE@Ot>k-LL7ZQ*u78Q^*+C-Xbqh3*9Q+Ob{~9 z*o2ROy8Cc`BYePENbg!30QAT18&6TeeZ7@51rh@5$oQ^;Eyg>t<1pSH|JslC;Jjde z@w?a9T#tq`Zp#pg$VAlveLn+fFX1=o$acJo{+FD70+K=8ZDf22qZAOv$N^S(;)3s{O^PGx`?+(q0K zbp@H6bK4PlPrA}67!!57q)IUj0Bn*zsf2`_gzXKLhZ>LfoK{r3H?a)$Qc8j|dkbgb zXN*N_3w{e5KWJcyb@2b4&9-2(rIxk(Awp=Aia3S}YlOd!dOmqIN_EyOa^(}c$N726 z06~f_ACJ_4N+*1en)!3;1<`0*dGOunXy&8|&ik$ThOv9YdHspfE~K#jJ=I>M4*yIFT-v6vk zUU2C8#~n?F+E%%Utx9?IO>5mM}b-%)SGbM@H4qq5ou5H8Yd_4z|kv&8RB-52I>g9WzJ>%&016mhJiZo(d}K zm~!_>r%Om~DPWYns~m{;wujNSjW9%D3)1RXI?x?P+Ti!k+VBF!U6|u@!8C_)jpu^s z&Rq;Ff26{$7tqZX!uJZG`xD8rX&B3C^|+yJ-`s~fBWu{lyb=Kj&p$R;?*FY_DgXfd zZ3`adtF`oM0YfN)xv#-2pSOwH`GJW5WP)7}|4GSB^w;?@1^*xWAHH8|0QD;)I5;mw znAtz87xBX9D!+p1a6d>Z3l5c}z#@dK_*1*?Y z5E1E7y^9^!M@VR4&MbHOVoNwr?y>5t2?6eu4MTVcdMS^Y0qE$e?1US|-HF;P#^|I^ zIer7$?%a#`9~55nR_2!{oocH}k!8?onfr;KNGKI~eN?A?O(w@(k4Wyg9$Zsh-us{g zmlv9g$(?wM1Cib+5AMPEKcAqh^5Hp(b@ojaK7{Qw_8Xmp9zDt);;ua|sBTgxk}O;z zvgP|eI46X}<2>spa0Qz~tOVM>B({w%zS(!ICG~ixLyRYJ5+n^$Ewxc>W+PZ*nMdL) zprWvYHfCqEe+)y8N$J`C!NN@T#@i3#Dztx%_j=3@@~T#??7-nlZIf?k}zgW5pBh*=ert3+0&>})T--iEippDlZTY>tJ9@SZ> zaoB5#Ypy5PgmQS0>N(aU&W*(hs48QQPr;2Mfyj~T0GKhrbe%CTWXQjE^ZpR+f%@v~ zDr2ev@v7~NFku4W^LjT;=$q?bWgla(&LfoQ3agaF5b+)(YX;S(*ckS zwpDvHfiNC|u9KcXFDRF52W^&anLslj4iI#I=2^a%G26E9;eMM10-Xxowk_b?{s7%e zl_B2Joqz3Ei{O}+jpa~&O`?kaKySRtYXVt?R$1=>8hpSxgx=@n#IaJKA!CI-Igkhz zi9a??+52LC0JR+R`2?m0MUdYvFe9{Mt)HOY?Ra+2C<*zdRz7i_fUEmg(R*>mrPe|l zBwuvE(m7t^awgLC#~cGz`3K#!&V>z@<9G20%M7_Oq;|!_|M6CvKDOP*Dzb1{sy6Fw z^1WcP{fEFB(FqhW0n}@7Ayhq65Ag!CW~P%`;kpNJ`fIjceZ|DC^xp*+3X4`xrksX< z+K#^{9+EzFBJ9B+aL~tX= zkUoje^=oR?@ZgC1kv6>gEzW1dwa}$aLO&GcN0Al$tm$K50dm0`Q~N+?vVE^E7pld+ z>r(f)^2ypql{V?Q17YA)f#^_Rb?X9MgCZN6J+OZ?4KD*_f)1C#>*vQFOX z)~Ff&KL+=&F^aK8%V95&_p=Sa@5rwFZ-P||j12io^FqY37uy#Rh1}NFEa3ZaZs!vI zL9PFi!-~ll8G1X4+gv}>@f-taR4PvfF-u_KRXDONS5tn_jj)yeP2q9jWAP&~A07Y-vM-r~zYb)9jH(srf%eqen`6U+82{ z%&*{DW_yX!h|h9o=?s5n?Vo{x=kNnw+~Oe+|R&^|7wM7VP{Z^uWJZ1 zmXE^v4Bc*fmzGFF$;|h2p+v~fpZ~pDW;jGO$L3xl6t}EDvJ{d5)^e~)M;VTU=_Pv< z)*3?oqvN6z$hXB`muPb6)v=ACvIFJ3o&sy|0(N9;GLo~jlEgp3Zw_e*+PQ#p>Vk)) z_E{oGFlS{?bOS7aHdQ@5-Hxvdi~nB9<4NfwO?=g-Xx}dUS0(Pdp`5;XF_SZ8kbN!1 z$)2O^{8}K9rXaf%gVN*T>ATH$BKn%2m)G5&n!DLE*Q&#?jS16P)M|zL@y38+=Cc^d z9fWMpMWiI9v@8H{yym7TI;y;Rr$;WQ>~`WgBbwA+R}wJ26ZG?E`$7q_D|O3n&LGCI zBYK|Nh2RRFz&UfG@6NDY*&3hM#z5DxCT4>CPIgOZOO$6XS??O}E@)J{xZkGq_7igw zmpX7xv;j4TQx z9=fdPaG1CxRK|8h@^Z0cd|UccI$3SZ1oW)VO3g)xtKmWGlo2SM<_w)p7P+8i@dZmH^Lr<pv3%L`ArSqcv2{~-tov7${VzUgVqm&p)P`cgtP$d1 zAt{p$6G3Y9F=p-!pKx5Bw+UOwv3zsg;%6UAvw$q5>=n}jrFkxg8*)-G<6AKagk5@) zOJfC;vWZWx5CTK1rY4CD1*23q5q~)zG^M z!1Zd|@mIco`dCpR&53@O%0PvTR8J@j(T20Ro9wHEUx=2MSEl&&tO0c)scbp^HtpUz zNoJ?)6i4RSzIDLb9+|CJjmyShLTI!MZ?x0f)BTIX$(l{9r4EZ#EbJ64r5Y&;<$a=} z3Vg2U*H#fL=FAebkN(xJIttw;l=|g5dlZxf$;yW90D$)cb~2l=t<9L1@PqjKxXig` zK6%B@;5Ps;hDK;rmm7i#c2u(hcr-)(5zr+4FFqd{gFTi(t!NR6*(79sXS)5yg{ zX5L-3mKIuEtU~xrD4``|Hj|h9_@x|r?5lXY?Ql*cbjIOYcO&>TtTPcA<0i-Hyl!t9 zGsT!(X?whW)%;#@nZsubmNA|}mQ);6%^z_NV$WfTAqyjlD$FRNXT%qnPd)b$FF4l) zhT276t$a-ZJ&lntziD|)&Z4(5pbfi5IkI1)$MWv||NrIdk!mfwE?+bwZK?IvrA`5k z36*$H#zX*4(DQoO%E0L0G1}L|+8)m}ER?kop@^rXt=TO3Q}k8F=LyO>*3igVxJm^~ zf@C&5#+e(!Jb`)ViQVJUSV?WCOY6*g^o)}^f@iQ9gN<6*$F}t;tRwX6mkVJs67TGg zxA;5n=Nr>a&IDS@^{uqqQ6UKov{{-`dwVl!Ba%#+wrT1vJs2ZsrU z^Oc<43(ra#5$_d2^`N{z$S%k**C&S0v(UYfN5gftExV8L{%XZ90fI`!lI=FZvg+RS z#Wz^KSJjkezBhBqH_>fwEC-P5gFuUq9-9vp3eX>1X|ER3Jskpc!ga>iOs2j2Zq*@I z%!{Vt=E4qQCHd)A0rvcQKj_>$?{+x>l zerQ6%q`6D>&@!Vq5;k|?V+jJa!^(iZpMF??UU7d|BubKddD6)*=SZn(0iitQ`=eGj~-R|QO)sH zvc7f!L8ve1zvzv-9vCss^h{J!sT-)3*S;!1Hn)7YZ74CQf@!ydpAsS`LrkWr z{kY6~qSf9??Q|yfJ7$j30!3A0Jk|`MlI>_hu5)n|N!-H8C#Zh@zQo^*mltWEos&Z_YXXKF$wpRDj;5GX0h+Ay)F$ zE^af(!T;rR>#1LQk1W=$Lgw7vz@<&yjEOn5`bl^QEAU6X70;|^@6Tqc8u6KvCsPvH zz*WNT;&=#AGseCQxs^<0)-g9rGMBAccTLCas=o5ElqSN+;v?|01D|UK?$BFKnq>n< z{$^hvTEYkg0)A5tkSMV1cX-sPKI8c$f4`n}EUTOco{`$;^A}>;?f7ra2av5_J0Qxf z^P4#H-;Y48_4tLO)V(P}$(Krmp8-?>!ADNFv5{&y=TrE4oSB}&Cs1;rmSX1#XU3JT zl*P5NuC^!miLHla+LJn|`$|G5qHzsW)#^)PfVvr?X$h>$>038l+WIGDG2aEekwT5T zTc0o$_ec*N1C~qW0DR_L{O3oK(R@ug_G8qfbN@4wkIWFbcJ1gF9EZJxGa6(_R#QRB zyC{AcTYCvq9x^&Pe2IuTdP4pO7xdXmloxx@ZIG&iD{BRn+tHIBnyslKHqd3p7qi{^ zk=D^fDjavBENS#M`d&z3T{HYW+s#e4;8VvtzESZ=e)T&TmLlLw;k4|W#ls2kzA6di8Y z)u48Z!#T-8(yw#_?o&DKF2VUC!z*b^U22{LZyks?Ki&+66b{IJd{a+Jl{bD6ckQpm z-|vQ%zD}JwJhOduberGw0|Z6)+&4BLBRu3Tk0iyKH=tiA=6k)m74)>eqlAuWB-C-gw6 z!I3F?5pG+e;+!Hq-zO&Eg7jUW>q0YAMbVIYCGGM@AS#x6m%YXmCVwg--}#;9rt*;?|*8c!y#{O8Ljhg3`yaRm=@u-)k1`KT1v%Z zSaREJH5lO|#qYR9zP}u+BXVY7tJG=I!o8`hdk)a!PkCVT{zYiPx%aLLqG2?rqYK-!_9zG%aGB44KxCwEE;R1;xH@tvyJe0B2WDC^GNy!B+VvV{r)0`B5&K8r$ zq!h@fU)4WGW`t)jQ&+R7Hc6o-Y%@3^V5#qojY4F9}FAA zP^Liv382Ox0a^hkHIjC5 z=&qa?wnc^+Ic_n2^_|7gpIai*Y_cDzkaGRwJHsR31~YTVLyK_lVqTFO8OrsZ520O= z{_$(ugZptE^>4%+f2yoE8bSg994+#Pn5h)+d5ryyanqL!F3{ZwI9m`n!@_?4hR*TW zD?^igeW$b>ItwZ1rU&4X@}_+Xu+lYPbMA0M0@0QRl6{TD3{uqI^iA?Af{H;1P^?@(uvxc;0)-2+9cL z8uJ))0eCP1JAh-)I=eq6b4;l9;PoNzzSTDmv4OIMcw-_=1e@{iM;p^MrqDRn@&R6;RDVe zS4zXvf{|rF3M1JkF1fN+Yy&Ge%o=VaSm|SIXw-S-NQ|yjD0Tz4(_z=gb%bh+EkBCI zl`Mx1(I@ED@UIGO=+CJP5{GDdRuf>qhE^tO9sI}3s!**1m#G-Tq*5f?t2T>3e;7 zpQMBFe$xN}t8Y43Eeq2C_xI%b5KFvLNDCuUHJ$-ZZWFcp_Z0W|qM3#*EDpiE{ORfG z9!{qo7tkCU|$C(!kM|4BH*I7r<}ju*M2~GkCiAN5KErYJENd4!M7Q!>5?Z z`{@Cu$eq6vX8nMyLpHR{EV{nV zwcn%YYN>l1<$bI8%tnP7|MuaZ3&nf$^%^%J%6;NnOcWBKxDcQO#$axwI5uUARkHHD z_V%Aof~S5sGSk2#sW~nk@ zXz5vkLN?&|1zEtp?WFdwUt8z!eQ>W{_u#*limU9899iwuq;a&*rcVw_<|~1edc@I# z$YR2IM1N%Vrxy?(VMi|vIUiAS66@*5O-&!q@4)h!FZb{}xKY4bnJ~>?Kmr_rRtg&N zYB98mo^Ev0PYVM`3iLnXQ0!OL^gNs#8S%K^FPK;(|IlOHFyzX)hB;)vZDPgZQCq*Q zR0YWxy4W_H1TIr7b6)%8Ft|YNw<{fu`_GDhKi>61Z*Ln*EBA5Z8A>t1h1Wj(5=RP| zK+|Nf&7iUUq2I^ZCoRVD*pw+V5sEm6IESoZ@4@!B$pm)!r{wJc(5!TDrAGw+>23mr!z<#ityEOhz}i zA^o(~b>E=se@d*f%Uw77sOn4^;^w7^6mU`Sf%IySN0v^F+)wIqxe>g}8XAM2b*m#D zaqnP}^r-Tet9T|7D7&-_Mmu0ZmJF-LCRFU^Xh3DOaUEZ~!qP?$gX$W0&CeA=;Y-7L zKc-}YAvtjyhpB&_PjRx?a9G(dtiAb+kAo)>j|z+D!8_*E`axgNHv{`2NG_14-{j$w zDsiql8~W8GPW-@p65?8DT)q$%4}OrU>mxs~^J!vuna+Hi-r2lk|9pP9O8mz>k_9Yu zsbD*rpyGL-s3b_?kmRg`@=0}QKlY&EAog0{c(;go zzh5HYhf2C=;nV~7$WA??bPBA`{4D!J^*hHtlI0?(zt$C)O)sDu0M?7HuVc~*(2d5S`$*I;A7SW9e0 z+^D9jhYjQEOJ?WQp^%f?T}r<@ z3=MjslCMDVtj6~g^ZC)sLKOh{Jf__UUb5l>VT#aSWj<{ z@95yboJ+hp#6R|$2NU~NFk#p;Y!xvl-6n)TOWlb>$WgDxXfYu5e|7<;rEs*8Zy~Q2 zX`5aDs?`f|x!{@5_v$&`2jYxxebBYe7p;q);}S=F2Q;X}9Gz`wPm5zo>q!32K*+ic zG_36rjQk=M@$IzO*IMC@w?j)0IS}j>D=gtHf)@e?Hr%@n(0li{*Z*{(3u^3mn7igU znO!I1i_7|Ybh8|gg!2Juneo6X$U_1};jD{1JX+vFI&LA)vY~+Zgr=|Uz2KH&tJSGZ za0`A5E?Y|h&e$3p=y%h^&OW|+{yQ?-EAP*H(D|bX3aRKB+>|1?&U)0)LFTh4n?HL8 z^tOd<`Sta6u<}62^y*yP(%x=ntxHOd%Vi@iAgY|&j(1tG{dO1Vn#4@ilL6wU)(ZQ~(eZzn`LQAElWne?G zEXv1F6RJ{v5*1(%kF@yyj5U>XBJuIQ(RyZy_?4EkbU-#q`?TyXR%mnabUS7M zP|epxNm$Z*B+vI=LnWU%9fVvtEUK3n+v*edOta(WKrw>*@x((7Ls#TWa>^o*{^{t= zKDFGVT30~$k&&5YqK2r#=g!NQ+St}+E~UtdGc4zHPJ8aWBme{GIBI5Jf>MLf*l_U| z=R{7a{OMZfa@R+jm?NZw7r13j0l8$fiA2t6GHr>d;Mw8JF} zX66`>CtCIPQmV}l#tzyBQPI@61C6TzuC#M2o|?X~5$?zucz%KYNp{t0xH2EfVSJos zZO08d9ObhGgX5r_ML-2Y)S`l&KY+5Q1`4Z9Ub6)oD`rME6Z|A%I z*(B`s@%(q5&i)saup|Tu$Y`Cp42&$nA=f@)S<=UI^P)URpR~3RfKXKH6awvt`>(-N zDoc#<4&+2)p|X9FKWEIBN(Y;`L@A=r0Z-KXtm#g~Yw1tFZffvWiWLC>EY`_=v=FK{ z@)>DFVvs*G3b@D!l1(bb%oSxs<+p3;V-De)=$TFnW3|JrSnJgDhlZed!F(n-uQlFz>xk84S#5Pe_ z5)gJfT0$hbsU3NqnqZ39_!aP;PY6qE6V=iEosTL6oL%Y~YBAAy?lh zcHGdodR89$k*W-H0=gy31RRTQuX|@HnB<>e;A|WS{6%#}#|(tKrNK{NlPE=8Vt?{E z2!BX^R>CcT0WeX*3y5lBm%Da&X+_0%j$`#}M0AqJYjVW>#87#Yqr{USpvdAz_^uK=kLu0TdPhTd=eLgUBP?gmnf%qI=yn- zg-RtPlWj&uYIC+_l9mZx}V{W+VckvWgi8CW*PWPFmj_+paX`pM2vE z|6)F@%-#4LE*q-4Xsqbxo_-SPCQ*QqF7-j+3-tAJpGY8|tLCWwZs#=!P|69luF?IK z;n%SC#-2xtJ$puJl@QexL#k+(Eo|1eG)=jqJ83%~VoMdb>hw>>?L7@BYl>~(@`jUw zh74KKl0p%Cjwfv~0z5V7&wuxJzx$NFVa+7hv@y4DV3)%QG1v9UF@b}wjhH#R@_Z8L z|D?ZwfU}=8Olh{5=NFWMFE3UhG1#XU*dnZ0bMt-U=-!mD&bp)}&aZLpitgmZNH694 zlTa{(Z(o?A=U*?#HW+0C2F9>}eI5`1FwEu^VfE?-0SA8liS~-Jd15L(oa`HH0LOd1 z%W`|Vx2TX@?7C6ol+059orVAC^cwq>4sJ!6?A3<>3Bd(MpS|*~SbeGT1p6=)-X9C=9+gBLZV-*TVM(%ZxS3hm+TLU__eJkv4Hq?O}9aJ)+ zmPl(3^0XN^jh*bO5b;~0-J`^`0Z#_BE1LP%G}yXKD73*bN;hL73}{g2_gy*Wzh z$888%I~pmuo|$E74uR4js!mHZ1QsnMRxAz9M^PeENn`@ex-9h$1PCh|sETTdN#y|7 zmN=fy?0D9uLd&W-3DqQ}C|{SHyu!tqF~K9PVeaO9M-)^Hwp4IvK@C&hDejkH`;=Qo zM61UnrWh|Ve_4dYcJAKzcA4n%|U{5%$4hRO8hV9lk`)+X@r9VTX_svy& z!)1orr@}zeqfo`?lX&?Sj`L~M9ewM7XNyHh_BoRZ&a~K|LXG^G7~id=Qlz&gCykiO zdo9EP@ps4YpD#AN8YQv5?5Ofh7!avZ?G&ZmvB&E(8eWbcJ^^Ox1wM+qhSB62?uS4L z3vvzjWOHj0f3Qg=sZ8zl4Pw0KxwjAv~m$MHtEP6=|}uJ)~AQP zjWWzFW3!=iTh5aW=P$KfU+TIfQ%+IFA0EB+ymMXWxY?a!o+T6K`&C&o(C^<~OW(ei zXMn10;LB$;%@xwP>rHl(ADUgD+$; zGlGomNz5~8&G<39@urCV@~;15c+5-{-&e7?3E>Sp)0`3^A|`b|g^;^j#g}9HcW2~) zvf-bg__J<@bJ?G64c(1eXMFf9)uXe6>#&lnK3a$0Dn3)2T+*0AZC4(kq_3vn$3jARV3j=?TaN+ew)`jrKh+%CVTSJ z6K4@+L2Q1_n+28 zqxn%$$;BwB`JI;fBTg(G0CG_OD+|ov%SDkFl4hw*y9PU39h;_mKPnwwGm|k%`|=>- zTmJZQzJw#F6?(;Ql2CVF(I(uS_%v`#b|3sdbWx}TM2!bf?=9~i97b4z35p}O?(4}U z+xitX+Q}8OQ7ZMhb37v)Xo%nRSQk=vrDn8T5@wsFX4u9wd5?v{a0T@4JcwM$ zrDK>aB>A0Jl2?Z+L?+kMN?0Q)lqdzi#!Fb^qx}%8Mqe}%fBSyWnX^{w;O7~7j z{bqWqLu|{;wVJWGmKzrSQhty~iARA5%8L&-@^#1K&W4%;=MRu3{9;7C9F*5qDySN+ zD&8ShX>Fv$VRh?&0qRhR#yh0*_mT7T#n#^urhuiloWv6OPVH8Kbj3|OSF+$zy_cW- z-jZ;fG^5+8{Z*%f(UqIv63ndi@lOyl7~c8n@qX3bgW!_=Puw{k7-S2M-26Z6a3lnG zye)(uS4#7+$MQKLupXrjO}5xd*yUCo9It$hBk4!^lS>}rrux?!=IUK#H`;`5wee6; z@z&;d*vg`BX_DnSd@VkxdnS}_bnY+ftz*k2c3&A*C6p|9^9;ApuoF+@qcH<^FN|9$ zE>|43m39h*?#4jS^)sv3r5*t+N}87u8y=SvLt*a{m75Ple#ScFK!NB$AT(?|i!x8L zSq9Z5?-YD9h6p~Lhe{8!Zx@k&N7@8Na3}m3sVM+(`gf$SyBaZgq<=UW*nqhK>uqA) z(EFKKY!@WJE{}T@P)B($%BwtF!T%kL`?{N%0nWhk;7^e8&JqN?!Z%N`FQ~>$3PE`H zH{+cy=yY5^E>K{`k{t9ri1y*EbWpjBA1**n$3D*)FKh3G_&wI|;9s<_+Yfo5v(pZa z6dt`L4l8gfF@d^H7AnKy=XYA&!$&NmnyW=37>7yJfoh`ehZwQW<2++B8)jU;|NT|g z0bP&p-HnaseNa)HqqS4Yo_P&?(^7pw`T-r)``3A07=rf#EOr;-o3R(lQWbqim`li~ z(vM#PX{WUpE5KNF2_w+0Bi;2WBSZ#sw01^|JF{XhRI+xP#dF3(5b2|w$o}zKUhYK7 z4s!~QQDWZ#(eHY8&1JdNynCelG`AV=_}moG=FZ6~yL|4;=-oXdtC6-SajxULaPIjp z;$3|ULZ!S>A-%rtpTg+h2SRAEp}Ku`yl9mJO)E3rHwM}^a&CnHX2JOx7iO)>?-F8Ksnxz(oSi-0r3PG{iZ>j#G6@1@#(vXGyGBF9oRZ;l+IJMJ zD+ZPC0?aoW^VGeu8=Bp$8;F4Y&Z$&RwsOLNXpt_+tA}feeS6 zx@!@$x*_yqlT#?eEM9xvlKN3-J%e!dWJPt3qsm%`RP~47@TdT|K%*l(f|_qGHN&YP zqXLKdCDA%Av36TyiZYvD0=-~$7Q?W2jACyUAcfH3m5e_@N|eJ#NBAjN9Fv-iSdH(M z5!ymUt&M)$QcKD1M79_pX%P;jrEl1alhLTW=a-t94o_r_8mSd)5Axm;-o{{!_ds4A zQ(aSq@xsl<=w-(w?v&o9>n`RB-&JvuXrzr7%}&p*HV?$nj{LnHAK!^H?jAWsnl*#o zX8)s7%;D&KKk4wB+wAaL={1SEzjv@Q_)h&#GzS{^_3OO}KCAw}acfG);7VbI%!KBA zJ;A_~rasVHkD9Z0TGxGC%K6(iBLo023IIs{(F|Bi}2<4VYg1mEdy8Z}Hrkm}{IoI$_mbo>W=eD01Q4H!)_QIq$;_d)cu z=pg}{rB1iwL0W5r?4|GmYS@Rt!|Cd&)_0ARf0*}MwtkXk#pCaJ5#N&}8azDuiZ+&= zQA1bOyg2enwE0v-3lB1=;jdYh7uj~q`>Y(0ixNy0)T}oh%JczjFKCY6Iej0h7s|e~ zqderf!B2*p%FC9xc}z~y$<_TnST9RC)o`M%cd*3fKeqTDlOVwKD0-DUIqTfvf^o!$ zhqL5es;3el?#wldYkfSoM_W#E+o4m2r{3@S!4F@D9387q+@i^&bLC+(Be|XlB@~DB zuhb;KQd<8bHL#e#QbYZh)EL3#^ql`2gFu=N(H}Ri57R&%aFbDKF)e1*k zX$=mqFj(HX!!6fVe0SmxS-=lJCE2D9E_asA4Ooi)IIW0be19GjGl z#5tH-)I6cu2Tr-_n_f*9#Vl-|Y*+yA4UjojkoZ0S?2wS)p{@TJBN&PD=HL8Whu~ht z{%eOs`rjQgA=*S3$`9%UDt~T-xE%^d?D7$zd(Kfp+lvOgN$!_pr2WuZ3ZN$g(!@F4 zp0za_n;h|@xzaaIP;}aSBFnPh!ja|9FsTG)X(#tCxG$#i9LF!Trf{{xR7_7PpJwI| z#yt*e@&FHH9a-=XkI*hLgZxBA3V6AmZAo>GGVgCaLbN{MMm(a_n*j^N9lZHaPy0O# zo!XWe`daFNuG{Q;&Xezy zcQ!<#q~MXho)rYa@(sLZSi!Gf@8VT(iTOvK*spime`G6(1W5e?eHk25MA=-Mkr+y?e1ZxI zA)G%q#RRXW{rrQm7n@9jBD(4#+LG$Ws4Hd6`r+UG8SI=TqDDMk#7rwuuA-5j>oEPS z>!W(*p|*m@OjaKS9e%9epNk8oFkT88PA}7}-7_~%L+6Hp1yb>d}h&rh!a*+fDMQq}@tYRtve}a16 zz>_RyUbe0jjp?{>AbUSgOp15#mi}E$uLAmOTf0NNa?$-`TY~|Ce@`Kwfw?kSzk>o1 z&Y_g8`d*J98*NK}(KOv)nO|eS-Ul8KoL~uwrAtHF@n(9yMg^RJjBW?cQI0(V@!O8D z7pUH6gg?Cj5opyK-s}#UTqW>pyQfD$7DB0Xi(47zl=>IfS)BHpZ2hX+aH3vQK`dA_ zcbG}~@}*J2R2-M4b)<2XT9R*QQ%_+tim8$Gb_oa924$aG?3lRw;nK3uE9OG<(nNaR zzfrHKBj-MtFNBZ4+}bH2OTlng+ap2uOeR-CcDv4cr4*rbObRdM3~g&kmdA?{F&{i( zZZfM)3j>|E=y+}~vBfn~r^XnFaU?G}F{}kbA@pcAejJ41f*Lq$fNzg#R6XgmBW!04 z*TnT5JEh$>mt8kdpvuW8LkLdyz=-Mt5-9xob)rJi>>=Z^)y`O(mc_DN14p)Ye2Qh{ zHw7yr){5-VauHX+E?${;4`zgHbE}Y7!FrUn=dgE!+z-v%f;QY3w8b%?V*4)~$nzFB z(&65lf*jv6;}KJ#3=*FZK#MB`?>Cdu(f%vF=ZUh;EkkyN>^u93~Zy&LK8K?0Pk)%S-+|2MmzheG4vk&IMt)?#>rBDkY@yEEpMk4EN4%NeI3@9b`oAcgghkO=!8$aE9@x_iCNyq$C?wZ^B%B@aa23(s0=wabQa7{=K=((n( zcMY_MaiybC_nYZu`jh#uJ{^lyj1*05I0GR@43_l4(RcARZ{Cwg#GRacJ}Q? zT@UJnarc(?j(xh~!P0v0&$Uz2@^2(|LN`6X7Y0N3nmr3e6~`#`AO> zhdmMwzwH8VyTHeRqryJnG`*MfH*8;dBLx~+Y194Xf-D3i=xb(6&z?2Xbxu&|e@K9y zqn}0Ii$Mz7_wTUJmis&$-ROa2#+DlUaYk8`G}0v2)#<^=w!7#p$IPXC>Qv@zrsE8` zldTDQ*9PXe4In+^%*mUeD5;m7$FPS=E6(!%V^S1#>$gDq#vWHU31Gh6pk@nCeul?L zW8}8Ow19z1EK%y8n4zHHCS3;1ga;hKIm61@2Q+vq|{Ok za+3x6JaT8c1D4Lwei{>HMAsP$8u}lp_JU_z8SPbv!a@*%rOKcD5z<6z9{@ob3lVWD zWlV5X?16A!J5F%aDz}g$bil#xQ32FB+ymCfmiKuyf(GtjUo`hUZyjH(ksBy>)IQ~q zZz+q|95&ruYs5*nlJOkN_v#!Y3e)6km;Nr1Z;o~uai?n5v5MV2?Ux~-%AaEdp0`s~r@mG(8vZyhOw2|&NUtlh;uIiFB_}=}BYQ|hJjd0I zPCnkjI`JkksCoj<$5F*2ex+IJEzU~)a&v9G&iAz}$JVV9=%h}T=4p`@qQbEbOwkp+ zDmubw48O-yBs3H8h6|K>&=l8JV(6^TYB%}XYSph+K0;<1O6PkTzW&Jdx!-J>go5Rz zJF`Vn_)J%VZ^Xynxf>zq(munH>cG`m+3riQH)Nfc7p{;3hh~(DXCR@eirfk3G9*Cn zD|5^z>~#>Oatu&4QBpgp@;_Q#oXtDiSdPgS%MHldP!`wJHusE_d(-=)s$T<+eM+!F zL|jg$j83&*$@=n%aT@z8!gj2nyg=PtdEc~yikhNkQPB}f>Ocjdklq&ZRvrhFt&ipSTnAl( zND~17(@5!f^P$O%j4Eg!_}`{-3?R60oUbOLcq+O5M$P#bsr7u<+}m*JSU%44QhiPU z{|6Mg5;oztI?E%sqBwG<3M%}-QE5VLVyYhUQ6JY+RE!d?RDMk9J!Bk;bBaj<6yFkhXd@ZgnK{EQz5 zj`UVP+Xkn+1K!HAAw2dYTbZ66#cqcsBGyn0L`0e`S>i)*$|W#{==IMEqXI3DzL5 zHq1Y-%x5E(LV0 zwWf_RWsUjtnB&2UQ>IHZySQ7m656;Ec7isG=z2ZSHhMFeh(I;t-Uv z*PtOyQlgR*B&ho=-u34IHr&z}l>H&uPhU84Z`9FjZE+xIYw|8H@{w)WYBc(zxH*p6 z)&YzCoN$rLIxOMN(!Iyz_K|!qhypnBM*Qh5*-O^gB4l^6P5~~W+i)@XikZo3VW?PjuJN47nv|@d)+5@c(~hiO^Mh30o0gpD zFjzMYefw+c(Uq#hqgdY+dN%oLdG|ko)Cfb^$#xj~Hn(h@(#3iql*HdFCPuqW2Kz6DjLfN zx2f%BQll2-0`BUbs0saxdpFd0fvbXDZj<+U=jx0wva~hni>m#(mb`f9_4A4l@n};laxpT=TtPZ zx3)hL3tq35C%g`dj)4*|(aUwFq!^}k5csZ9EC4&O*DsA|*g=mS1!zCt;5!LwEXu>u zQ+U|?^d;ZJ_*-+TMcc1elQ32igxgfEQ1@(jWs^H%8T%Ed6gP@r%dn|uKB@}g>;G2Z zY{pccrY?d?2wZ9ZBEkGC?UKKHma4ylm#$`zUJ(yHo>miAQ`SIaacA+8Vfj5AUy8)! za-1S@)$5M~J!^Cs%FvGzd~5??py_Ujgtg()fVBVz$iFx!D_^{^Dp#uK2&U7w$-}XQ z|0Z*7Juny>8mmmH`>7gSLAq!P&So@^tY!YiIZf^yw!LWI{PXvouRD4(ws$W`+>_L@ zCZNqvxUHqOUa3=O^h7Olzl~&1*A=PffUwU0pOp=Q*0uux2$1kt0Kh#&-G5^}Q!gtU z;e%VHC>f`gA`eowJee;mPa2bg1Vr@btykFqi>oN->w}v5h|cn+ zI6u9~SaRbXleW@4gAd(0(jOBjb}0Rrp7fB(U1E!~25rI%>VOZky2MiWo2kRPunc zW7usg+=c_P08x6A%77L@vr~L?80ipmZ~utnfON+faGwPlf^fvxUz}bGDj3c;i?p_Z z46jz#VD1pEvaZ3KKn}mSHaBn%Nb1k-0>*aRn$RgQ;uZyD`(y};zQwR4H6^VD^#W_a z;7;E+K^$5#qNWeFzpk5TvR8Xg0LyKH@Y+Hhxs_1|BsNqPn+NOYMM3_poAYF<^#yM% zx>4NA8RBm-5(-a3+gBKVd1w%dcG`N*?x*>)cN#0tzT00QO~xD=Chx&Z3;N>nNtAaf zfE;%BK;1eal}|NygwlN@J-i_RMmv_1oy?k*W&;+@TzpA5ptL_NB~eB7htZUYWO`I^ zRIV7p@Ck-bvYbZ+_aUs~`>js!635JOJ}yg9F?Q?ebBGA~kY5`D8O_PERg=4G;gj|8 zNUk1sV8(L?p|IV9JzqT!zfcK#8tk&sLPecc7xu{2FHZkiT^J@uvKfB~!@iyf)x%dq zW#isvCG<4?XbVW58vVC{*&Z$!=mG0v{JaR>myC22$P?8t;?%;dWfw5;sT6OD+oja~$B-NW(u}>IbJ2PR>B-jfa#YRh^)FfY*gy!1q&F9IS zWLh-Zfm3Avil@Er58ZUwgqRR5x>f+IX00M+IpJa0^-KQeN)%S)sI@gu*MEFBZ^x{f zg>985iad2-1O8V<&j}^z?S^x7$Rd&GA{Shaw>tHsvV>q2{@oEHTpXph50pM-ye`DC zC8kqT{jttx;`@-7d=s(YvpzhT6Ev7{liRqC#_=<~brq+wJ`3-a{Z~1o1LPCuJYLeQ zp9(OMg2J^G*Wmt##EMx8a=^iQ;%-SXhwVb&sXHN{DD^_n#edc0@qLSER)@g68a3ms z=`n+GcEU&DSQT)KbUi`xv$%8nf2V%~o>HGAiTnA>!y#&1ndqt2o;w92*gf~Z)vPEX zLwDIq%U6adQ?3Zh`j)8szr=#*dU)PI?<{v{XC~-GU>Vub3DP%z>-MFxy|ye~RH5o_5`}99mn9AQQvS}p9MQi zUWpRk7!(w5vZZhBAj*l+{V4}2CKAQmXq7f!G=+217-gIyQ;Y}DDyyT0PvN|i&oE0X z{BE9%%S0D4r)3)q`RCKEV~aarNs0b0W$u^foQ2CG`&!sjAz~=xAa75e3IFlnjqD(s zOrbP7d?L2(d)=?Ez^!8m#Mf=rjYjqaYR)^2U%6ljK4k>(UwxZvZcF1Yl=0sIy0ESy zoks2p)EM@Ta?ciIH;(M}nW{{IKKiUoyjvA>#(}$;SaDa?4YRu&Qt8OX#C<#2Q+hBy z!^T{S`gLddJ!x=8e+Z5VE0%7_;OqQA^+Nj@->y#^4gynb zi=(s0fGUmUe5(x;iZkn##5r|RL{IEd{NIjOn8eqIMhGyEDENN>``g*PxRzCm79=0v zg`g8Z1(lq*U2iJxUsvUxu}<=)U8g5-B9u5z^aSWE826IeV!?2 zNlcAkbu`zh>jKPFbJ18=vAeKF(z4yYo_uH9%0RT1$?RF)2K*MUVBn7(7fNPWL0lcI zzJ3?^-r-^Ed4rHV87_ZyaSj2gAf@Zf$pH#;Wta9Qh9Kt*i^&oF9EMM9h^~Hgj62xu z^N9((Z?g>y;kM5V(qEO++KGEcmFR!f!5Hnd@xKwn_J&8rW5(f{{2=nRZX#Yp1QOAK zH@4NWjuCtIUS!CA@Pze&S}3779D0dOQ*HN=hy3)F6e%mS=JE-;MKLi+>PWFM_igWV zeJS2_P;zu&#Ke=IlR-ViFWjlsUGodG1k?wU6Q%D@I~!j%x*-m2xoBM9ykP2Xl?1VL z?y3WRnvUpn)*K(FJOm!Mn8JOH6jNvuc%=K_h>hetoJYSImnVJW_krr7Itlp!5n3o= zpr$MIzLAB~?@a(Mg3|#_lWPirxiCkGs-HIv!PF$hS49kUz47AhIMG%cYA#gl4j$Q~ zb^;@7V&ihh%lJLzG*LgRg;*}cXe7tZSc?4Ic8D?Y{9~bVb%crVo?_eaUE>+Q1MUgq zTyE*-pyMiiPeG9nL!jHt7cw0w{`6|1m976oL+nF_k_=Bi)oBGovUmpSk5||(TCWca z?C3^IrGIoS1}b8uX{=~&a*3YbTZ?4`1$5%zYeW8FdGaVWc~P7 zu)b9VyE1vxp3sCm$Wfo#^6@O*&PynWPqSB;badeKNRYmLa2&kI_(7M2E0GB#waII8 zG+rU#^9g?7G~jI3U_;&J4ouv>PR6$h)R28V5qj{j;2ZdkBz#C|G527U5IRQTF@Mc* zAU?No(Mm-Qg@`WRe|ma9F}ai}w@nokaM$j`lF3fXgOWZ-@KCo{l2;giQ#{Qcam=iG{aqkx@BA za|wFC98GLhzOPxK1#u)5}{F3Gle_gH8>Cphz^Xks*7JJ3_qM@J8qCi=aK;#7bq2KT8tw#+H?Z`YMPISqedIUj2}>?pIYt!HY`wPB z;fwo}YSG|%a|ZYU*q#ybMx+p$jg~7KLaz{n(enWS`E8Wb+L-+7*UG5j zjsUw-!ZB8K`yZ0PZND$qak6RM6L=d=^s$?~!X9iB=vUyaBuB5{EfZC&S#6Pa=f);)-zp8Ah3+KGLnHrL9wvMvbJ#v73 z{k;X=`JK$e$Y7t~n`#E1%Uohm>F1~cXf0OBZ#Q2j_W%I5o{!**xOVSPYPjV@DS3FV zM_I<_89%>edmF|tubI`1#7m!-OiP?v3SIY3UgpYb&Fri|Em`~VA;B!GA4Gg#5t@8N zt{e8&l60j*S~I(klz;6h2PNjR`iaB^wB*%SVlm+a-~vxsb86Il*xRu+_iI8gL@em| zDD$LgcpPd?RixNddQ)rs+e=;TNcU_du%ZNt)CNjswml~-!X?=A3BM{c89-UhxGyj& zQx~JuMZ`38GtEmJKWyJ&kVQ+LWt@M3S$1`(-xcE5yFRWH!LXcgOpv$j-v$i)@{bIXtKjlDL6&$S7Ajgcq_B?Eq{+ z2+)AK{>?7Fuhr&AqF_?5beazgu*r7PIAuf=M~Yze_->i3^|tm&>JP6j@{oJ~rypM>ezZ_HzLrZTuP%UAh9Qr5w;8VB9&?1* zPFV)h`hl{jXhTwUOwR16Zy%v|^Z{p2Qko5U2d=fa_E(jpD$eiFUxV+v22V8JPs3cQ zA}(~)OJZ=+HvTfPw_1FMG~^O3*h8`~f8&V=*{nZGeFEiMh$wzBZn-6?hC!SYtwdeO zgknpl{*u;Rzf4E>X8`T>%*F(ZWvAO``TeOkKfm%%u|~be?xZmD&>p8vr1zgayn+9L zG|6OPGVXws(DAcBBDch7C$1}@Yj)1o@Cgumw|dLjCx>m`95x~8^SaI2q^1HV8?w#R z7wNrmWh3BAV9u4zed-WJ$2=`^NnHIhu_aj$lqGPg_q4}f%FdiBp6%&!pSv4>74LAc z$Pg`Bc3 z+pA+L`{U6#pUqTFWQHAlDj%aU{aM37+b{M002CabrheJMTE$LhB9ASAPNT9xcR+IC z>qoMoXy}gY-4p!uu5XGh%A^YRfY2+vit_x-C~w(lmWrhcD^e7J$XO*fp$!OaBbq$s8tnibxWr5TZSgK% zi=F~!9SafR^16%C?XVPsbtalIf19I4mr!NB*a)^2`TJ>2W$egv+|fEnaA1}jPAvAf z-LHWzHxByTzdq>cxN!nviWkdT$CZ8u!$pxxNlC0;$zfV21BhbQM5s+b@ph3?(ipf* zA(F1PPZ)&I?Qh;n6?0BlRy2scT3h@(u0z6Vb`5%CH!HU<{HWm-)N8K3n-@r%mwnw`Imn_Xt zEf}Cv8ie+?`DR#<@ssHvuKRim#UO#ID=72T3&X|7UNH4j;MMAITaZqGAKl~PqvW+< z#?vGS|GRrRmKgrB-&OQIa77@mCBkpnn-DYr1S8`*VNM&vRBhXODXgp1u21KTF(&iNz!Sm; zunO|)WwHy=zTaAco`tfI-MT7rNE@s|;VU%A)?1G5H!_T5EvrgEJKeSSOjsW~B>sZ< zXf)1xXKdo>)UDG|WU0f6Y$#*@LLd581LV^F7Rs3oF2&4TEuFTViL(jL3yU{djU#^| zWG_Q7p?>p4tTP^+{hS-hD3caRObH}{j3%^jv*${`&C+uvevkYooupspHhkw!^!w`f zg%ws>HMfdgdB60dGEFaAmUFST*MEGLcovs>|BI*Gznf6%FnNw*4rdOv03WsQ#_LB+ zJh6XY{T;g_Bu4v_Dt*&zy^v|q`uNfvI>NmFr=0M4@7_t$Rty5&lfJ)-&kB`tX(ARv zD+l?FR&euAJU>AHyVxTaWU?;0)k@w~vx;x?bO%cB>j?P@=Js!0 z+#wj#+1?3cgTG$Koa=(7^i(#l5V+2P%BmWForO{U?UDC?B+S-k@GZb#rg-%u3ZnOS zgb<_#qPJTLSLW~5XX5j~GRkX=++}aP3|{p9NhsozOpp8jcz$54G&_F7f{{(+S;UZ7 zF6ho23wl3t^DO5w1W|a=T9!A_MN2Emho0N&gs{XrP9=^8NXgGR*TSbdb1FJ;ii1UG z77on=Yz!XnwACHT9a=*6NpvXio>h3{@^_P6${r%1PZcPLtbjd%AHGSKPV>l4E}5Ve zZK!$tSAlKkDZ84wa|79N5hdBYo&gs~O9Lr?aDM{zr2ei?%n&l*5c7Fm31QiDx`$oY zU-82|$-is(m)L(dE@ee3)iPO$lIODG`t=F@e~Sg_tsg70J;Ji))(6~;Y_Hnup9@6} zX;SgwoWA`EA32xa`!4R#S$p!HwnY7-qzYkKi*zQsheM2Ww8rdH5`ZTw- zur0bix@UH4*fR!3k#+Ny4)EU3z-`rhJaDS|y`+xM?7B{S5>0j%PfszB%lOE0y&sD?%ffS)}lMD_uSFziOuD)YY#2wx`8lu zr}xzY2eZ?tGTR5S3a{UX1~4{X1#RXDzUDiE6Rp;%3VxKM2Ni{Av8ttW+G3MD($qNP zoM#~`j%!X@`}XBbM}lP+j&tDM#&y|Z{8$+ryebkk--wHbpQ4br+z+&;7UFBHdiUHs z6`2#g_bpXiRfQ@1ns-T?Jt^{tPL%Y84+))B=!dxF)-lcPgx(j6&hkArzj$wvUGPH23xW8s?C1#7 zs&kov@}KT}GK z`Zq~1BT2tjl1Th;%<8&l$zLO~bze7kEejxHFtNPFSV8mVh>eiA3pJ7NTX)3nup|4j z3rH^5(k|`#X=l=yc6LP4u&c|P!um>6h1PW@SMYL7>fCH=7g^#Dy6ml{r^nmYx?iti ztcLe%ZuSA)qGgG^`AE{tB!%?2+Y&o%s#+N2^Lp+(6G9fe=tfMuCg1sYzdq`?U@>?L z4po&3yJxtbR2lBqkbR@Pq4Ayj{e8lB)gOs}2T4Mve)$#<|J&%Bny=xm3IQfXyOgf@ z%z3XP352_au?bfm zl_-_@N+3v8A8lOGF~0yL5H1B%<$WLQKf)C=Kcc|iN~nPZ>i$t`WM%|C;e!2GW*(K! zsvT=`o#wul%As=sJhz_9v1-`dh$=Br(D#%{5_LQ)3wgizg~$N#YlO8KL$_Lij9K~P z^|qu_H3P1w4ee(}CmPK?H+d}I)U$mwoN}Nd>6e+mxEzv`er-(LXhk3t5rvX+vWgN< z#T;9#n`<2T)UKD1W8wJ6DW?y`qb28YMfJ|V&EsJluU^|wCk8(Hd zR+hl|fS_L;T>CbfpTsHf3l9IB30dhcdN$731c2F0=QSxG%`ODt@g&T#LfyW)u)^ms z&BO3a&cc4qhR5g?T4kgb+!;#lC(xMP0-i{AR2k0kj$99IE+DsM+_&>+D>p5Ae}99n z-VHV6hWO>aG9gs+_a?YLBwgAAO4PZ}Htr9nZ+zW=Cy`)(%ZCW7gR77(OiZ?Ym5(`q z#J-ax4)2HxIBTEbB~+vz4n)_k{fx#iSN`um30B)K*Si&`ywUOs5}~qDftm>hf9#x$ ziYfp_O3IClWO^hx)JvUWB8b15ZISzRt!(QP1)fi2DPwv%pd~C?+41`^x+H}TIV@o4 z6b>bMx-K6$LWTCEIlq9aQp4l!;d zqCBH|FR}2<)J0Cdh|l?c$E#epYad~3NnZoYaJE5AX2U)^a7Ied!DnG{iYPax$Ppl?!(C_ZTe*Q#nP&a|j$25m-*EnbjLH{U z#YA*@M3*h=4+h{jso62I*YQwRnj9pC+W}NxO~Je058-mIE9`$%XR7M-<7*yz_4&tH zNdlh}?1yWIl9Ro-P0Z*Z1y-0Pa;_uPx1cwUalJv|<|g%_JU)FN#ph|N9Q@*555c## zWI4q@Z9(|wM62WMl(Vhpd>eRFO&AIE4mqdHzt6P4CR>(&5=ELZVX?<_JQXzG$9mJ0 zEj~DkIP4YRp#>nG8Q4(}`Ih)5Di)hzi?+%~LI1|B@?$&e)Gv9X)_iXsGrCzumTu0p zH~s)LP#N_nu2h-#AGHUEr>$2>p|7|z@A78cf?VGpRnfo0D9A--w6a!rNX}EwN@Af6 z<*+*$kT$ITVH#aF0ydQu1VDo=$LmnhO>RA6rxa@K+u<DArmxRJa;yFPy? zELxmkyK+ZZDXyK<8!_1x7E46mwD8Sf!lJlsq`De@OYC~=+>YSFqHQY7-0XCBg+AN0 zB`LP`*cA01N$V}=$TY3OoD4w|^{wAPitO)+)*hqGpObm;7GoU@Fr1p)h-SL9OxVI) z_jsE1<|U0P*U-HkVT>-Y9*Ptk8%C3F0T<;+4(!--z)wmnZ^<<{35!TE*-tt!#sV-H zS#BcBpx4W3Ndab$!atb-gv59^=-Feg!abb{aSRiaX}cGv)TlYiKGu~6UFie9cOW)U`%}!2@jFu*;W!Iq;*8xm?^OS=&SqVqH)U~xY}bU?Jnt)~ zOkT# zIUm_vw<7rDj*^My@yM|5aW!q~*azB`km@4xi9&7LWaz`t%ERjg6mt}B={X&vcuq6P zn46E`muWlbevH-@t@Nu8aMv_9$nWZM>4{65n;A9ftxpZn!v&pK9?;k&_%<&#S=yr* zVF=LR-W{i~Fyq^$(!hZ zr;g9^%|`3&tEA>Bgw-8i5XGhFGW(OKs%uBW_lju?CM6xeN9p8c#tIe2JCp2`?GznO z*(wp`E^XM5Ome!^4QPoKBw@4Sa$W2iiyw2^j%hT#&}f58I%oWY zZeMA=m#cAcVWJc%ts0l8xO8%wJhsp;!{*ROm z0wxXllOQ+%)U|segpaU2y@k7B7M$(*n&sH}^XJq8oi~CfDVRCTZLUJB{6&K^KOH$@n)|4`Nnevp{&leYXeyPo_iPD+(}E0Y4H^0cJR*1M?A+WZJyN`Rie zs!_8}07K+pNlO|OL)12lb?w}>)1+QDS!9}b1)LcD>kMNZ4j4Tpzg`~hUm22Q;JOVG=k4k*vtDw+GDs;<-}?@jd)sJa<|ZYZ+N^oaqw%x80d0K}m$1^~4xyC;O-tOcp(;fD^ZEzD1u(`MgAGC|`M9Q75c zK;P2$P51P)ui147yG+;Hc1}HqGA!oIOp@x6+r~no(V#$TUn(KxKi3}t`Gow%zqrGr z{=*XN0=cjD!m^6|4_oYEqPEyfnoMm-JA^RSXy{=Pr+c9yJk_eYJgZ9|44#ds^tL(N z=Ph5PCIrLys##4=AG8PkVqwf$^v?sAh594c;oqI=0IPSgVqnU5arauiaR>PCyz8b& z?~0E;D$^PL)u!K&y8qG%&Wlc-y_=pncgyjf;)CE?IZ?}yKLo%}NZySE`fJ}uv zV}J1v#eLD&f1Df!{Dvx?WN9D!L#hN$BZNxy^Z-a$A zhD|Jf_#38kZ4Ym0Cq;Q%1>*zUreNq&k%2eY?QQF4ObFmEMmEM1bN?d@3dLn#WPN);U&qQ%Jqw-4U>Fz^D*f1JjGaf5Q77>CGF_ zkXX)w$Zy^-wgXzJLR)m#yned($wK=KHhFVIb~4@LU~-;YS?!H#F}MqKYn*` zb1QC~RM^*j-OCbT;)GtnLdtyD*D#?Tcg*suu^~D+0DI`SUV+(}d&<1ABSEML`Edh1 zOkkLCbe#Fmh1kRE_3$R)%2GdF6^-Wt9>1#RR#itE*)~}uDdn>d2Fx!Ztb?KYdK7BK z3~Y6FTTZ>_&#g~?h>Wp?Ry=gpHl~xk;VXDkB6QAOtP`3x=HQ;zr7u=osJ1G?^ts6z{<}IyPzsi4t{aEjrPk5U_aG16 zu~fRRe6J!)iRYM!%1S~eQt8=Pa$fxStyvmH3GiF6{X0fatHNrbaxMa}gQHrE_b}XR zMKMaw%Q{72g<7cS0tch>)<2`98DATx!`$RLBojtgb!I<>WSO8Nc@%tOa>tk417+5e zhl*;x9U_z1WIeJOE5_aJ8Z2xe$NynAS-rhhThr~*ylwrr4{z4Ho0`%y^Itu_L`zOo z4itV4?JFB;(Ey*GkA>!L#Bv$(?LUU}8Y&}*i4Q;~ zb*Y;>LsWF)lq{Ah;Iy}5LLK;O2wY2o8ZuL4>Z1n%M5XMb`4y1f+uM&jY#h#EAAc%9;?sWRe*ey{Ubv^80^G$}eM7B2# z zb$#LyIM_F5;5g^ZLPj|`--r1jpo+Ot?}>wUSRc_-eV0FmQn{y(W_80Hyp7bk=$T(a z!7ZS6Yr{pk$ap(D4~#Af$KK0zSBde{!{G2+O{7Y|vW ze@lcy$?ViS#O*lfIf{4m#cY!A$1{s^r`0Boz%90ts(g=-nS0uFE}fT=gH>BdXRCfzzGI znvS*NkR!-aR$}RWY6+W^oxQi8L5X9F_M&8Zm4>evgn(z|qF)^i_yA^@Y4hizBv7xx zOtKtwpx_yQKkeAEbm?~w4TXD~5}5X3ohZFj!u=r=d=qn&4#g=)FxUFSX(W4ZqY=4D zYwY3+$M<$jN+AmzISuh)zJptsMpSK2p?efY78f#y@V@M~G1#o%CLAg~YrQW5mkN{i zcqzjvmuht*@ie=_Q}qRUCscinv->=L#`)fuGYei=G*%^D}8Sh$K} z*wJH>|FMB;$4XYqq&K{{1O2#WrZC#VU$|8&_HlNS)Zgv(;x97^NmbwVDk!b(;dP%~ z3VPN#PE%UcJA(eY4#(j0=+m7KO`E=n`vE4Nb1em5L!=BF-B$+B$t^u`mcuIk#KA~Rs99JArUK4k9Q*6qG!@CJ(<-(`5cCt1 zXl3|iol4B7w42`;^Il2hQg?sCc?@YY2o5GUkOQd)!sQKH@oqbvJnh`PGv8;OSXVrd z0`D&@?6ACd6X*T^vS9lmmMX-quYS=K%s-I@o_AQ6FSa{38Ui%PP1u9J^shy*?o`ZTvG^lHK8 z^X4H=rP-u+y1--J_*vVijzUwY4a`h`c=y849n{0*7d2;=R(Qi}M?t?BiM(#8j0=Su zFr0R6?u4Cxlda#jt}{SQd;1|hQ#6XEzMzi3)!gTNj%vrWfHyE)~Jwt-(F&t zPyt9FtgEb$*iGvG?f>EoMme{>n0>@>fE*phYv#=cZR45JgK0G0Eue=SEl~@eI&Aqr z6Ru_sco^{7WatSY-8&>~$!y9|`oXju*xH=fLkL$1YKXd~qzIHj((k6}$0Qz4vrHJ> zAEFxmP&pqb_W^E~(X-;gTJCOK?3uQfHrx`ng2jS(f{Uk0&d9nqfat5AXzYix=3u6n zEpIj=I?aGj-jnF^lFW7_+5{U+eBP#g#U6ar-*q_J-U%7k60JTu`LV~`Mk=Dc!I=>` zg?xYKt;E094RhCg^k{fFCIPK}KJMqD33M?k1i~HFte_AhsXpv8@#|Vhh>rbBm%0De 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℞?8R-%Oo$RYLwj<&ydDXdGFLm0 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�H3^PC7074SS-8jq0mF+NnELi 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%0ABsNwK!E?!A^pyW_7bO&p@_m>;D7f9?E=#*xtH`i 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 + + {children} + + + ); +} diff --git a/packages/retro-ui/src/app/page.jsx b/packages/retro-ui/src/app/page.jsx new file mode 100644 index 0000000..ec1aebf --- /dev/null +++ b/packages/retro-ui/src/app/page.jsx @@ -0,0 +1,11 @@ +"use client"; +import Chat from "@/components/Chat"; +import { ModelProvider } from "@react-llm/headless"; + +export default function Home() { + return ( + + + + ); +} diff --git a/packages/retro-ui/src/assets/fonts/ms_sans_serif.woff2 b/packages/retro-ui/src/assets/fonts/ms_sans_serif.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..83ea806020dcccda6b9f8c9c6c3c6b573f550df9 GIT binary patch literal 6508 zcmV-y8I$IBPew8T0RR9102yom5C8xG08RJ+02vGb0RR9100000000000000000000 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 + + {showConversationList && ( +

+ )} + {showOptions && ( +
+ +
+ )} +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
Vicuna 13B
+
+ GitHub +
+
+ + Twitter + +
+
+
+
+
+ + + ); +} diff --git a/packages/retro-ui/src/components/ChatWindow.jsx b/packages/retro-ui/src/components/ChatWindow.jsx new file mode 100644 index 0000000..9ae049c --- /dev/null +++ b/packages/retro-ui/src/components/ChatWindow.jsx @@ -0,0 +1,167 @@ +import useLLM from "@react-llm/headless"; +import Image from "next/image"; +import { useCallback, useEffect, useState } from "react"; +import { + Button, + TextInput, + Toolbar, + Window, + WindowContent, + WindowHeader, +} from "react95"; +import Loader from "./Loader"; +import MessageList from "./MessageList"; + +import useSound from "use-sound"; + +function ChatWindow({ + stopStrings, + maxTokens, + screenName = "endlessbox5", + assistantScreenName = "SmartestChild", + soundLevel, +}) { + const { loadingStatus, send, isGenerating, setOnMessage } = useLLM(); + const [userInput, setUserInput] = useState(""); + const [playSend] = useSound("/sounds/imsend.wav", { volume: soundLevel }); + const [playRcv] = useSound("/sounds/imrcv.wav", { volume: soundLevel }); + + useEffect(() => { + const cb = () => (resp) => { + if (resp.step === 1) { + playRcv(); + } + }; + setOnMessage(cb); + }, [setOnMessage, playRcv]); + + const handleChange = (event) => { + setUserInput(event.target.value); + }; + + const isReady = loadingStatus.progress === 1; + + const handleSubmit = useCallback(() => { + if (isGenerating || !isReady) { + return; + } + playSend(); + send(userInput, maxTokens, stopStrings); + setUserInput(""); + }, [ + userInput, + send, + isGenerating, + isReady, + maxTokens, + stopStrings, + playSend, + ]); + + useEffect(() => { + const handleKeyPress = (event) => { + if (event.key === "Enter") { + event.preventDefault(); + handleSubmit(); + } + }; + window.addEventListener("keydown", handleKeyPress); + + return () => { + window.removeEventListener("keydown", handleKeyPress); + }; + }, [handleSubmit]); + + return ( + + + Instant Message with {assistantScreenName} + + + + + + + +
+ + {/* */} +
+ {isReady && ( +
+
+
+ +
+
+
+ {isGenerating && ( + {assistantScreenName} is typing... + )} +
+
+
+ {"buddy +
+
+ +
+
+
+
+ )} + {!isReady && } +
+ + + ); +} + +export default ChatWindow; diff --git a/packages/retro-ui/src/components/ConversationList.jsx b/packages/retro-ui/src/components/ConversationList.jsx new file mode 100644 index 0000000..48c4278 --- /dev/null +++ b/packages/retro-ui/src/components/ConversationList.jsx @@ -0,0 +1,81 @@ +"use client"; +import useLLM from "@react-llm/headless"; + +import { + Button, + GroupBox, + MenuList, + MenuListItem, + ScrollView, + Separator, + TextInput, + Window, + WindowHeader, +} from "react95"; + +import { useState } from "react"; + +const ConversationList = () => { + const { allConversations, createConversation, setConversationId } = useLLM(); + const [systemPrompt, setSystemPrompt] = useState( + "A chat between a curious user and a AI chatbot named SmartestChild on AIM who responds with lowercase, frequent emojis, and 2000s internet abbreviations." + ); + const [title, setTitle] = useState("New Conversation"); + + return ( +
+ + Conversations + + + {allConversations?.map((c) => ( +
+ { + setConversationId(c.id); + }} + > +
+
{c.title}
+
{new Date(c.updatedAt).toLocaleString()}
+ +
+
+
+ ))} +
+
+ + + setTitle(event.target.value)} + /> + + + setSystemPrompt(event.target.value)} + rows={6} + /> + + + +
+
+ ); +}; + +export default ConversationList; diff --git a/packages/retro-ui/src/components/Loader.jsx b/packages/retro-ui/src/components/Loader.jsx new file mode 100644 index 0000000..9e1d74d --- /dev/null +++ b/packages/retro-ui/src/components/Loader.jsx @@ -0,0 +1,63 @@ +import useLLM from "@react-llm/headless"; +import { Button, ProgressBar } from "react95"; + +const Loader = () => { + const { loadingStatus, isReady, init, gpuDevice } = useLLM(); + if (isReady) return null; + if (loadingStatus.progress === 1) return null; + + if (gpuDevice.unsupportedReason) { + return ( +
+

Sorry, unsupported!

+

Reason: {gpuDevice.unsupportedReason}

+

+ react-llm runs models in + the browser with WebGPU and only works in Google Chrome v113 and above + on Desktop with supported GPUs. +

+
+ ); + } + + if (loadingStatus.progress == 0) { + return ( +
+
+
+ This will download the model and may take a few minutes. After the + first time, it will be cached. +
+ + +
+
+ ); + } + + return ( +
+ Loading {loadingStatus.progress * 100}% + +
+ ); +}; + +export default Loader; diff --git a/packages/retro-ui/src/components/MessageList.jsx b/packages/retro-ui/src/components/MessageList.jsx new file mode 100644 index 0000000..5123d54 --- /dev/null +++ b/packages/retro-ui/src/components/MessageList.jsx @@ -0,0 +1,52 @@ +import useLLM from "@react-llm/headless"; +import { useEffect, useRef } from "react"; +import { Frame, ScrollView } from "react95"; + +function MessageList({ + screenName = "endlessbox5", + assistantScreenName = "SmartestChild", +}) { + const scrollRef = useRef(null); + const { conversation, userRoleName } = useLLM(); + const messages = conversation?.messages || []; + + const scrollToBottom = () => { + if (scrollRef.current) { + scrollRef.current.scrollIntoView(); + } + }; + + useEffect(() => { + scrollToBottom(); + }, [conversation, messages.length]); + + return ( + + + {conversation?.messages.map((m) => ( +
+
+ + {m.role === userRoleName ? screenName : assistantScreenName} + + : {m.text} +
+
+ ))} +
+ +
+ ); +} + +export default MessageList; diff --git a/packages/retro-ui/src/components/Options.jsx b/packages/retro-ui/src/components/Options.jsx new file mode 100644 index 0000000..f117639 --- /dev/null +++ b/packages/retro-ui/src/components/Options.jsx @@ -0,0 +1,286 @@ +import useLLM from "@react-llm/headless"; +import { useEffect, useState } from "react"; +import { + Anchor, + Button, + GroupBox, + NumberInput, + Select, + Slider, + Tab, + Tabs, + TextInput, + Window, + WindowContent, + WindowHeader, +} from "react95"; + +import { themeList } from "./Chat"; + +const Options = ({ + screenName, + setScreenName, + stopStrings, + setStopStrings, + maxTokens, + setMaxTokens, + theme, + setTheme, + soundLevel, + setSoundLevel, +}) => { + const { conversation } = useLLM(); + const [activeTab, setActiveTab] = useState(0); + + return ( +
+ + Options + + setActiveTab(value)}> + About + Conversation + Settings + + {activeTab === 0 && } + {activeTab === 1 && ( + + )} + {activeTab === 2 && ( + + )} + {activeTab === 3 && } + + +
+ ); +}; + +const StatsTab = () => { + const { gpuDevice } = useLLM(); + return ( +
+ {gpuDevice.checked && !gpuDevice.unsupportedReason && ( + +
Vendor={gpuDevice.adapterInfo.vendor}
+
Architecture={gpuDevice.adapterInfo.architecture}
+
Device={gpuDevice.adapterInfo.device}
+
Description={gpuDevice.adapterInfo.description}
+
+ maxBufferSize= + {gpuDevice.adapter.limits.maxBufferSize / (1024 * 1024)} MB +
+
+ )} + +
+ ); +}; + +const ConversationTab = ({ screenName, setScreenName, conversation }) => { + const { deleteMessages, setConversationTitle } = useLLM(); + + const [title, setTitle] = useState(conversation?.title); + + useEffect(() => { + setTitle(conversation?.title); + }, [conversation?.title]); + + return ( +
+ +
+ setTitle(event.target.value)} + /> + +
+
+ +
{conversation?.id}
+
+ +
+ {conversation?.systemPrompt} +
+
+
Screen Name
+ setScreenName(e.target.value)} + placeholder="Screen Name" + /> + +
+ ); +}; + +const SettingsTab = ({ + stopStrings, + setStopStrings, + maxTokens, + setMaxTokens, + theme, + setTheme, + soundLevel, + setSoundLevel, +}) => { + const { + init, + deleteConversation, + conversation, + userRoleName, + setUserRoleName, + assistantRoleName, + setAssistantRoleName, + } = useLLM(); + return ( +
+ + + + { + if (typeof value === "number") setMaxTokens(value); + }} + /> + + + setStopStrings(e.target.value.split(","))} + placeholder="Stop Strings" + /> + + +
+
+
Bot
+ setAssistantRoleName(e.target.value)} + placeholder="Assistant Role Name" + /> +
+
+
User
+ setUserRoleName(e.target.value)} + placeholder="User Role Name" + /> +
+
+
+ + ({ value: i / 10 }))} + onChange={setSoundLevel} + /> + + +

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%0AjrTtzYewO_gbiM%`xy=IWK%R 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 Udev`uUi4M7mDnIz0sw&j16mx6?f?J) literal 0 HcmV?d00001 diff --git a/package.json b/package.json new file mode 100644 index 0000000..6e7dc70 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "@react-llm/workspace", + "version": "0.0.1", + "type": "module", + "main": "dist/bundle.cjs.js", + "module": "dist/bundle.esm.js", + "author": "Matt Rickard ", + "license": "MIT", + "private": true, + "workspaces": [ + "packages/headless", + "packages/retro-ui" + ], + "scripts": { + "publish": "pnpm publish --access public", + "build": "pnpm recursive run build" + }, + "devDependencies": { + "typescript": "^5.0.4" + }, + "dependencies": { + "react95": "^4.0.0", + "styled-components": "^5.3.10" + } +} diff --git a/packages/headless/.eslintrc.json b/packages/headless/.eslintrc.json new file mode 100644 index 0000000..a4d4588 --- /dev/null +++ b/packages/headless/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "@typescript-eslint/no-unused-vars" + ] +} \ No newline at end of file diff --git a/packages/headless/.gitignore b/packages/headless/.gitignore new file mode 100644 index 0000000..8f322f0 --- /dev/null +++ b/packages/headless/.gitignore @@ -0,0 +1,35 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/packages/headless/LICENSE b/packages/headless/LICENSE new file mode 100644 index 0000000..052f695 --- /dev/null +++ b/packages/headless/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Matt Rickard + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/headless/dist/index.js b/packages/headless/dist/index.js new file mode 100644 index 0000000..ca7b287 --- /dev/null +++ b/packages/headless/dist/index.js @@ -0,0 +1,1156 @@ +import require$$0, { useDebugValue, useState, useEffect, useRef, useCallback, createContext, useContext } from 'react'; +import { v as v4, _ as __spreadArray, a as __assign, d as detectGPUDevice, w as wrap, p as proxy } from './v4-2119d9d5.js'; + +const createStoreImpl = createState => { + let state; + const listeners = /* @__PURE__ */new Set(); + const setState = (partial, replace) => { + const nextState = typeof partial === "function" ? partial(state) : partial; + if (!Object.is(nextState, state)) { + const previousState = state; + state = (replace != null ? replace : typeof nextState !== "object") ? nextState : Object.assign({}, state, nextState); + listeners.forEach(listener => listener(state, previousState)); + } + }; + const getState = () => state; + const subscribe = listener => { + listeners.add(listener); + return () => listeners.delete(listener); + }; + const destroy = () => { + if ((import.meta.env && import.meta.env.MODE) !== "production") { + console.warn("[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."); + } + listeners.clear(); + }; + const api = { + setState, + getState, + subscribe, + destroy + }; + state = createState(setState, getState, api); + return api; +}; +const createStore = createState => createState ? createStoreImpl(createState) : createStoreImpl; + +function getDefaultExportFromCjs (x) { + return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; +} + +var withSelector = {exports: {}}; + +var withSelector_production_min = {}; + +var shim = {exports: {}}; + +var useSyncExternalStoreShim_production_min = {}; + +/** + * @license React + * use-sync-external-store-shim.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +var hasRequiredUseSyncExternalStoreShim_production_min; + +function requireUseSyncExternalStoreShim_production_min () { + if (hasRequiredUseSyncExternalStoreShim_production_min) return useSyncExternalStoreShim_production_min; + hasRequiredUseSyncExternalStoreShim_production_min = 1; + + var e = require$$0; + function h(a, b) { + return a === b && (0 !== a || 1 / a === 1 / b) || a !== a && b !== b; + } + var k = "function" === typeof Object.is ? Object.is : h, + l = e.useState, + m = e.useEffect, + n = e.useLayoutEffect, + p = e.useDebugValue; + function q(a, b) { + var d = b(), + f = l({ + inst: { + value: d, + getSnapshot: b + } + }), + c = f[0].inst, + g = f[1]; + n(function () { + c.value = d; + c.getSnapshot = b; + r(c) && g({ + inst: c + }); + }, [a, d, b]); + m(function () { + r(c) && g({ + inst: c + }); + return a(function () { + r(c) && g({ + inst: c + }); + }); + }, [a]); + p(d); + return d; + } + function r(a) { + var b = a.getSnapshot; + a = a.value; + try { + var d = b(); + return !k(a, d); + } catch (f) { + return !0; + } + } + function t(a, b) { + return b(); + } + var u = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? t : q; + useSyncExternalStoreShim_production_min.useSyncExternalStore = void 0 !== e.useSyncExternalStore ? e.useSyncExternalStore : u; + return useSyncExternalStoreShim_production_min; +} + +var useSyncExternalStoreShim_development = {}; + +/** + * @license React + * use-sync-external-store-shim.development.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +var hasRequiredUseSyncExternalStoreShim_development; + +function requireUseSyncExternalStoreShim_development () { + if (hasRequiredUseSyncExternalStoreShim_development) return useSyncExternalStoreShim_development; + hasRequiredUseSyncExternalStoreShim_development = 1; + + if (process.env.NODE_ENV !== "production") { + (function () { + + /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ + if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart === 'function') { + __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error()); + } + var React = require$$0; + var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; + function error(format) { + { + { + for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { + args[_key2 - 1] = arguments[_key2]; + } + printWarning('error', format, args); + } + } + } + function printWarning(level, format, args) { + // When changing this logic, you might want to also + // update consoleWithStackDev.www.js as well. + { + var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame; + var stack = ReactDebugCurrentFrame.getStackAddendum(); + if (stack !== '') { + format += '%s'; + args = args.concat([stack]); + } // eslint-disable-next-line react-internal/safe-string-coercion + + var argsWithFormat = args.map(function (item) { + return String(item); + }); // Careful: RN currently depends on this prefix + + argsWithFormat.unshift('Warning: ' + format); // We intentionally don't use spread (or .apply) directly because it + // breaks IE9: https://github.com/facebook/react/issues/13610 + // eslint-disable-next-line react-internal/no-production-logging + + Function.prototype.apply.call(console[level], console, argsWithFormat); + } + } + + /** + * inlined Object.is polyfill to avoid requiring consumers ship their own + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is + */ + function is(x, y) { + return x === y && (x !== 0 || 1 / x === 1 / y) || x !== x && y !== y // eslint-disable-line no-self-compare + ; + } + + var objectIs = typeof Object.is === 'function' ? Object.is : is; + + // dispatch for CommonJS interop named imports. + + var useState = React.useState, + useEffect = React.useEffect, + useLayoutEffect = React.useLayoutEffect, + useDebugValue = React.useDebugValue; + var didWarnOld18Alpha = false; + var didWarnUncachedGetSnapshot = false; // Disclaimer: This shim breaks many of the rules of React, and only works + // because of a very particular set of implementation details and assumptions + // -- change any one of them and it will break. The most important assumption + // is that updates are always synchronous, because concurrent rendering is + // only available in versions of React that also have a built-in + // useSyncExternalStore API. And we only use this shim when the built-in API + // does not exist. + // + // Do not assume that the clever hacks used by this hook also work in general. + // The point of this shim is to replace the need for hacks by other libraries. + + function useSyncExternalStore(subscribe, getSnapshot, + // Note: The shim does not use getServerSnapshot, because pre-18 versions of + // React do not expose a way to check if we're hydrating. So users of the shim + // will need to track that themselves and return the correct value + // from `getSnapshot`. + getServerSnapshot) { + { + if (!didWarnOld18Alpha) { + if (React.startTransition !== undefined) { + didWarnOld18Alpha = true; + error('You are using an outdated, pre-release alpha of React 18 that ' + 'does not support useSyncExternalStore. The ' + 'use-sync-external-store shim will not work correctly. Upgrade ' + 'to a newer pre-release.'); + } + } + } // Read the current snapshot from the store on every render. Again, this + // breaks the rules of React, and only works here because of specific + // implementation details, most importantly that updates are + // always synchronous. + + var value = getSnapshot(); + { + if (!didWarnUncachedGetSnapshot) { + var cachedValue = getSnapshot(); + if (!objectIs(value, cachedValue)) { + error('The result of getSnapshot should be cached to avoid an infinite loop'); + didWarnUncachedGetSnapshot = true; + } + } + } // Because updates are synchronous, we don't queue them. Instead we force a + // re-render whenever the subscribed state changes by updating an some + // arbitrary useState hook. Then, during render, we call getSnapshot to read + // the current value. + // + // Because we don't actually use the state returned by the useState hook, we + // can save a bit of memory by storing other stuff in that slot. + // + // To implement the early bailout, we need to track some things on a mutable + // object. Usually, we would put that in a useRef hook, but we can stash it in + // our useState hook instead. + // + // To force a re-render, we call forceUpdate({inst}). That works because the + // new object always fails an equality check. + + var _useState = useState({ + inst: { + value: value, + getSnapshot: getSnapshot + } + }), + inst = _useState[0].inst, + forceUpdate = _useState[1]; // Track the latest getSnapshot function with a ref. This needs to be updated + // in the layout phase so we can access it during the tearing check that + // happens on subscribe. + + useLayoutEffect(function () { + inst.value = value; + inst.getSnapshot = getSnapshot; // Whenever getSnapshot or subscribe changes, we need to check in the + // commit phase if there was an interleaved mutation. In concurrent mode + // this can happen all the time, but even in synchronous mode, an earlier + // effect may have mutated the store. + + if (checkIfSnapshotChanged(inst)) { + // Force a re-render. + forceUpdate({ + inst: inst + }); + } + }, [subscribe, value, getSnapshot]); + useEffect(function () { + // Check for changes right before subscribing. Subsequent changes will be + // detected in the subscription handler. + if (checkIfSnapshotChanged(inst)) { + // Force a re-render. + forceUpdate({ + inst: inst + }); + } + var handleStoreChange = function () { + // TODO: Because there is no cross-renderer API for batching updates, it's + // up to the consumer of this library to wrap their subscription event + // with unstable_batchedUpdates. Should we try to detect when this isn't + // the case and print a warning in development? + // The store changed. Check if the snapshot changed since the last time we + // read from the store. + if (checkIfSnapshotChanged(inst)) { + // Force a re-render. + forceUpdate({ + inst: inst + }); + } + }; // Subscribe to the store and return a clean-up function. + + return subscribe(handleStoreChange); + }, [subscribe]); + useDebugValue(value); + return value; + } + function checkIfSnapshotChanged(inst) { + var latestGetSnapshot = inst.getSnapshot; + var prevValue = inst.value; + try { + var nextValue = latestGetSnapshot(); + return !objectIs(prevValue, nextValue); + } catch (error) { + return true; + } + } + function useSyncExternalStore$1(subscribe, getSnapshot, getServerSnapshot) { + // Note: The shim does not use getServerSnapshot, because pre-18 versions of + // React do not expose a way to check if we're hydrating. So users of the shim + // will need to track that themselves and return the correct value + // from `getSnapshot`. + return getSnapshot(); + } + var canUseDOM = !!(typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined'); + var isServerEnvironment = !canUseDOM; + var shim = isServerEnvironment ? useSyncExternalStore$1 : useSyncExternalStore; + var useSyncExternalStore$2 = React.useSyncExternalStore !== undefined ? React.useSyncExternalStore : shim; + useSyncExternalStoreShim_development.useSyncExternalStore = useSyncExternalStore$2; + /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ + if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop === 'function') { + __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error()); + } + })(); + } + return useSyncExternalStoreShim_development; +} + +var hasRequiredShim; + +function requireShim () { + if (hasRequiredShim) return shim.exports; + hasRequiredShim = 1; + + if (process.env.NODE_ENV === 'production') { + shim.exports = requireUseSyncExternalStoreShim_production_min(); + } else { + shim.exports = requireUseSyncExternalStoreShim_development(); + } + return shim.exports; +} + +/** + * @license React + * use-sync-external-store-shim/with-selector.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +var hasRequiredWithSelector_production_min; + +function requireWithSelector_production_min () { + if (hasRequiredWithSelector_production_min) return withSelector_production_min; + hasRequiredWithSelector_production_min = 1; + + var h = require$$0, + n = requireShim(); + function p(a, b) { + return a === b && (0 !== a || 1 / a === 1 / b) || a !== a && b !== b; + } + var q = "function" === typeof Object.is ? Object.is : p, + r = n.useSyncExternalStore, + t = h.useRef, + u = h.useEffect, + v = h.useMemo, + w = h.useDebugValue; + withSelector_production_min.useSyncExternalStoreWithSelector = function (a, b, e, l, g) { + var c = t(null); + if (null === c.current) { + var f = { + hasValue: !1, + value: null + }; + c.current = f; + } else f = c.current; + c = v(function () { + function a(a) { + if (!c) { + c = !0; + d = a; + a = l(a); + if (void 0 !== g && f.hasValue) { + var b = f.value; + if (g(b, a)) return k = b; + } + return k = a; + } + b = k; + if (q(d, a)) return b; + var e = l(a); + if (void 0 !== g && g(b, e)) return b; + d = a; + return k = e; + } + var c = !1, + d, + k, + m = void 0 === e ? null : e; + return [function () { + return a(b()); + }, null === m ? void 0 : function () { + return a(m()); + }]; + }, [b, e, l, g]); + var d = r(a, c[0], c[1]); + u(function () { + f.hasValue = !0; + f.value = d; + }, [d]); + w(d); + return d; + }; + return withSelector_production_min; +} + +var withSelector_development = {}; + +/** + * @license React + * use-sync-external-store-shim/with-selector.development.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +var hasRequiredWithSelector_development; + +function requireWithSelector_development () { + if (hasRequiredWithSelector_development) return withSelector_development; + hasRequiredWithSelector_development = 1; + + if (process.env.NODE_ENV !== "production") { + (function () { + + /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ + if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart === 'function') { + __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error()); + } + var React = require$$0; + var shim = requireShim(); + + /** + * inlined Object.is polyfill to avoid requiring consumers ship their own + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is + */ + function is(x, y) { + return x === y && (x !== 0 || 1 / x === 1 / y) || x !== x && y !== y // eslint-disable-line no-self-compare + ; + } + + var objectIs = typeof Object.is === 'function' ? Object.is : is; + var useSyncExternalStore = shim.useSyncExternalStore; + + // for CommonJS interop. + + var useRef = React.useRef, + useEffect = React.useEffect, + useMemo = React.useMemo, + useDebugValue = React.useDebugValue; // Same as useSyncExternalStore, but supports selector and isEqual arguments. + + function useSyncExternalStoreWithSelector(subscribe, getSnapshot, getServerSnapshot, selector, isEqual) { + // Use this to track the rendered snapshot. + var instRef = useRef(null); + var inst; + if (instRef.current === null) { + inst = { + hasValue: false, + value: null + }; + instRef.current = inst; + } else { + inst = instRef.current; + } + var _useMemo = useMemo(function () { + // Track the memoized state using closure variables that are local to this + // memoized instance of a getSnapshot function. Intentionally not using a + // useRef hook, because that state would be shared across all concurrent + // copies of the hook/component. + var hasMemo = false; + var memoizedSnapshot; + var memoizedSelection; + var memoizedSelector = function (nextSnapshot) { + if (!hasMemo) { + // The first time the hook is called, there is no memoized result. + hasMemo = true; + memoizedSnapshot = nextSnapshot; + var _nextSelection = selector(nextSnapshot); + if (isEqual !== undefined) { + // Even if the selector has changed, the currently rendered selection + // may be equal to the new selection. We should attempt to reuse the + // current value if possible, to preserve downstream memoizations. + if (inst.hasValue) { + var currentSelection = inst.value; + if (isEqual(currentSelection, _nextSelection)) { + memoizedSelection = currentSelection; + return currentSelection; + } + } + } + memoizedSelection = _nextSelection; + return _nextSelection; + } // We may be able to reuse the previous invocation's result. + + // We may be able to reuse the previous invocation's result. + var prevSnapshot = memoizedSnapshot; + var prevSelection = memoizedSelection; + if (objectIs(prevSnapshot, nextSnapshot)) { + // The snapshot is the same as last time. Reuse the previous selection. + return prevSelection; + } // The snapshot has changed, so we need to compute a new selection. + + // The snapshot has changed, so we need to compute a new selection. + var nextSelection = selector(nextSnapshot); // If a custom isEqual function is provided, use that to check if the data + // has changed. If it hasn't, return the previous selection. That signals + // to React that the selections are conceptually equal, and we can bail + // out of rendering. + + // If a custom isEqual function is provided, use that to check if the data + // has changed. If it hasn't, return the previous selection. That signals + // to React that the selections are conceptually equal, and we can bail + // out of rendering. + if (isEqual !== undefined && isEqual(prevSelection, nextSelection)) { + return prevSelection; + } + memoizedSnapshot = nextSnapshot; + memoizedSelection = nextSelection; + return nextSelection; + }; // Assigning this to a constant so that Flow knows it can't change. + + // Assigning this to a constant so that Flow knows it can't change. + var maybeGetServerSnapshot = getServerSnapshot === undefined ? null : getServerSnapshot; + var getSnapshotWithSelector = function () { + return memoizedSelector(getSnapshot()); + }; + var getServerSnapshotWithSelector = maybeGetServerSnapshot === null ? undefined : function () { + return memoizedSelector(maybeGetServerSnapshot()); + }; + return [getSnapshotWithSelector, getServerSnapshotWithSelector]; + }, [getSnapshot, getServerSnapshot, selector, isEqual]), + getSelection = _useMemo[0], + getServerSelection = _useMemo[1]; + var value = useSyncExternalStore(subscribe, getSelection, getServerSelection); + useEffect(function () { + inst.hasValue = true; + inst.value = value; + }, [value]); + useDebugValue(value); + return value; + } + withSelector_development.useSyncExternalStoreWithSelector = useSyncExternalStoreWithSelector; + /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ + if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop === 'function') { + __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error()); + } + })(); + } + return withSelector_development; +} + +if (process.env.NODE_ENV === 'production') { + withSelector.exports = requireWithSelector_production_min(); +} else { + withSelector.exports = requireWithSelector_development(); +} + +var withSelectorExports = withSelector.exports; +var useSyncExternalStoreExports = /*@__PURE__*/getDefaultExportFromCjs(withSelectorExports); + +const { + useSyncExternalStoreWithSelector +} = useSyncExternalStoreExports; +function useStore$1(api, selector = api.getState, equalityFn) { + const slice = useSyncExternalStoreWithSelector(api.subscribe, api.getState, api.getServerState || api.getState, selector, equalityFn); + useDebugValue(slice); + return slice; +} +const createImpl = createState => { + if ((import.meta.env && import.meta.env.MODE) !== "production" && typeof createState !== "function") { + console.warn("[DEPRECATED] Passing a vanilla store will be unsupported in a future version. Instead use `import { useStore } from 'zustand'`."); + } + const api = typeof createState === "function" ? createStore(createState) : createState; + const useBoundStore = (selector, equalityFn) => useStore$1(api, selector, equalityFn); + Object.assign(useBoundStore, api); + return useBoundStore; +}; +const create = createState => createState ? createImpl(createState) : createImpl; + +function createJSONStorage(getStorage, options) { + let storage; + try { + storage = getStorage(); + } catch (e) { + return; + } + const persistStorage = { + getItem: name => { + var _a; + const parse = str2 => { + if (str2 === null) { + return null; + } + return JSON.parse(str2, options == null ? void 0 : options.reviver); + }; + const str = (_a = storage.getItem(name)) != null ? _a : null; + if (str instanceof Promise) { + return str.then(parse); + } + return parse(str); + }, + setItem: (name, newValue) => storage.setItem(name, JSON.stringify(newValue, options == null ? void 0 : options.replacer)), + removeItem: name => storage.removeItem(name) + }; + return persistStorage; +} +const toThenable = fn => input => { + try { + const result = fn(input); + if (result instanceof Promise) { + return result; + } + return { + then(onFulfilled) { + return toThenable(onFulfilled)(result); + }, + catch(_onRejected) { + return this; + } + }; + } catch (e) { + return { + then(_onFulfilled) { + return this; + }, + catch(onRejected) { + return toThenable(onRejected)(e); + } + }; + } +}; +const oldImpl = (config, baseOptions) => (set, get, api) => { + let options = { + getStorage: () => localStorage, + serialize: JSON.stringify, + deserialize: JSON.parse, + partialize: state => state, + version: 0, + merge: (persistedState, currentState) => ({ + ...currentState, + ...persistedState + }), + ...baseOptions + }; + let hasHydrated = false; + const hydrationListeners = /* @__PURE__ */new Set(); + const finishHydrationListeners = /* @__PURE__ */new Set(); + let storage; + try { + storage = options.getStorage(); + } catch (e) {} + if (!storage) { + return config((...args) => { + console.warn(`[zustand persist middleware] Unable to update item '${options.name}', the given storage is currently unavailable.`); + set(...args); + }, get, api); + } + const thenableSerialize = toThenable(options.serialize); + const setItem = () => { + const state = options.partialize({ + ...get() + }); + let errorInSync; + const thenable = thenableSerialize({ + state, + version: options.version + }).then(serializedValue => storage.setItem(options.name, serializedValue)).catch(e => { + errorInSync = e; + }); + if (errorInSync) { + throw errorInSync; + } + return thenable; + }; + const savedSetState = api.setState; + api.setState = (state, replace) => { + savedSetState(state, replace); + void setItem(); + }; + const configResult = config((...args) => { + set(...args); + void setItem(); + }, get, api); + let stateFromStorage; + const hydrate = () => { + var _a; + if (!storage) return; + hasHydrated = false; + hydrationListeners.forEach(cb => cb(get())); + const postRehydrationCallback = ((_a = options.onRehydrateStorage) == null ? void 0 : _a.call(options, get())) || void 0; + return toThenable(storage.getItem.bind(storage))(options.name).then(storageValue => { + if (storageValue) { + return options.deserialize(storageValue); + } + }).then(deserializedStorageValue => { + if (deserializedStorageValue) { + if (typeof deserializedStorageValue.version === "number" && deserializedStorageValue.version !== options.version) { + if (options.migrate) { + return options.migrate(deserializedStorageValue.state, deserializedStorageValue.version); + } + console.error(`State loaded from storage couldn't be migrated since no migrate function was provided`); + } else { + return deserializedStorageValue.state; + } + } + }).then(migratedState => { + var _a2; + stateFromStorage = options.merge(migratedState, (_a2 = get()) != null ? _a2 : configResult); + set(stateFromStorage, true); + return setItem(); + }).then(() => { + postRehydrationCallback == null ? void 0 : postRehydrationCallback(stateFromStorage, void 0); + hasHydrated = true; + finishHydrationListeners.forEach(cb => cb(stateFromStorage)); + }).catch(e => { + postRehydrationCallback == null ? void 0 : postRehydrationCallback(void 0, e); + }); + }; + api.persist = { + setOptions: newOptions => { + options = { + ...options, + ...newOptions + }; + if (newOptions.getStorage) { + storage = newOptions.getStorage(); + } + }, + clearStorage: () => { + storage == null ? void 0 : storage.removeItem(options.name); + }, + getOptions: () => options, + rehydrate: () => hydrate(), + hasHydrated: () => hasHydrated, + onHydrate: cb => { + hydrationListeners.add(cb); + return () => { + hydrationListeners.delete(cb); + }; + }, + onFinishHydration: cb => { + finishHydrationListeners.add(cb); + return () => { + finishHydrationListeners.delete(cb); + }; + } + }; + hydrate(); + return stateFromStorage || configResult; +}; +const newImpl = (config, baseOptions) => (set, get, api) => { + let options = { + storage: createJSONStorage(() => localStorage), + partialize: state => state, + version: 0, + merge: (persistedState, currentState) => ({ + ...currentState, + ...persistedState + }), + ...baseOptions + }; + let hasHydrated = false; + const hydrationListeners = /* @__PURE__ */new Set(); + const finishHydrationListeners = /* @__PURE__ */new Set(); + let storage = options.storage; + if (!storage) { + return config((...args) => { + console.warn(`[zustand persist middleware] Unable to update item '${options.name}', the given storage is currently unavailable.`); + set(...args); + }, get, api); + } + const setItem = () => { + const state = options.partialize({ + ...get() + }); + return storage.setItem(options.name, { + state, + version: options.version + }); + }; + const savedSetState = api.setState; + api.setState = (state, replace) => { + savedSetState(state, replace); + void setItem(); + }; + const configResult = config((...args) => { + set(...args); + void setItem(); + }, get, api); + let stateFromStorage; + const hydrate = () => { + var _a, _b; + if (!storage) return; + hasHydrated = false; + hydrationListeners.forEach(cb => { + var _a2; + return cb((_a2 = get()) != null ? _a2 : configResult); + }); + const postRehydrationCallback = ((_b = options.onRehydrateStorage) == null ? void 0 : _b.call(options, (_a = get()) != null ? _a : configResult)) || void 0; + return toThenable(storage.getItem.bind(storage))(options.name).then(deserializedStorageValue => { + if (deserializedStorageValue) { + if (typeof deserializedStorageValue.version === "number" && deserializedStorageValue.version !== options.version) { + if (options.migrate) { + return options.migrate(deserializedStorageValue.state, deserializedStorageValue.version); + } + console.error(`State loaded from storage couldn't be migrated since no migrate function was provided`); + } else { + return deserializedStorageValue.state; + } + } + }).then(migratedState => { + var _a2; + stateFromStorage = options.merge(migratedState, (_a2 = get()) != null ? _a2 : configResult); + set(stateFromStorage, true); + return setItem(); + }).then(() => { + postRehydrationCallback == null ? void 0 : postRehydrationCallback(stateFromStorage, void 0); + stateFromStorage = get(); + hasHydrated = true; + finishHydrationListeners.forEach(cb => cb(stateFromStorage)); + }).catch(e => { + postRehydrationCallback == null ? void 0 : postRehydrationCallback(void 0, e); + }); + }; + api.persist = { + setOptions: newOptions => { + options = { + ...options, + ...newOptions + }; + if (newOptions.storage) { + storage = newOptions.storage; + } + }, + clearStorage: () => { + storage == null ? void 0 : storage.removeItem(options.name); + }, + getOptions: () => options, + rehydrate: () => hydrate(), + hasHydrated: () => hasHydrated, + onHydrate: cb => { + hydrationListeners.add(cb); + return () => { + hydrationListeners.delete(cb); + }; + }, + onFinishHydration: cb => { + finishHydrationListeners.add(cb); + return () => { + finishHydrationListeners.delete(cb); + }; + } + }; + if (!options.skipHydration) { + hydrate(); + } + return stateFromStorage || configResult; +}; +const persistImpl = (config, baseOptions) => { + if ("getStorage" in baseOptions || "serialize" in baseOptions || "deserialize" in baseOptions) { + if ((import.meta.env && import.meta.env.MODE) !== "production") { + console.warn("[DEPRECATED] `getStorage`, `serialize` and `deserialize` options are deprecated. Use `storage` option instead."); + } + return oldImpl(config, baseOptions); + } + return newImpl(config, baseOptions); +}; +const persist = persistImpl; + +var defaultSystemPrompt = "A chat between a curious user and a AI chatbot named SmartestChild on AIM who responds with lowercase, frequent emojis, and 2000s internet abbreviations."; +var useConversationStore = create()(persist(function (set, get) { + var initialConversation = { + id: v4(), + title: "Untitled", + updatedAt: new Date().getTime(), + systemPrompt: defaultSystemPrompt, + createdAt: new Date().getTime(), + messages: [], + }; + return { + conversations: [initialConversation], + currentConversationId: initialConversation.id, + createConversation: function (conversation) { + set(function (state) { + return { + currentConversationId: conversation.id, + conversations: __spreadArray(__spreadArray([], state.conversations, true), [conversation], false), + }; + }); + }, + setConversationTitle: function (conversationId, title) { + set(function (state) { + var conversation = state.conversations.find(function (c) { return c.id === conversationId; }); + if (!conversation) { + return state; + } + return { + conversations: __spreadArray(__spreadArray([], state.conversations.filter(function (c) { return c.id !== conversationId; }), true), [ + __assign(__assign({}, conversation), { title: title }), + ], false), + }; + }); + }, + deleteConversation: function (conversationId) { + set(function (state) { + return { + conversations: state.conversations.filter(function (c) { return c.id !== conversationId; }), + }; + }); + }, + setConversationId: function (conversationId) { + var conversationExists = get().conversations.some(function (c) { return c.id === conversationId; }); + if (!conversationExists) { + throw new Error("Invalid conversation id"); + } + set(function (state) { + return __assign(__assign({}, state), { currentConversationId: conversationId }); + }); + }, + deleteAllConversations: function () { + set(function (state) { + return { + conversations: [], + }; + }); + }, + deleteMessages: function (conversationId) { + set(function (state) { + var conversation = state.conversations.find(function (c) { return c.id === conversationId; }); + if (!conversation) { + return state; + } + return { + conversations: __spreadArray(__spreadArray([], state.conversations.filter(function (c) { return c.id !== conversationId; }), true), [ + __assign(__assign({}, conversation), { updatedAt: new Date().getTime(), messages: [] }), + ], false), + }; + }); + }, + getConversation: function (conversationId) { + return get().conversations.find(function (c) { return c.id === conversationId; }); + }, + getAllConversations: function () { + return get().conversations; + }, + addMessage: function (conversationId, message) { + set(function (state) { + var conversation = state.conversations.find(function (c) { return c.id === conversationId; }); + if (!conversation) { + return state; + } + var existingMessage = conversation.messages.find(function (m) { return m.id === message.id; }); + if (existingMessage) { + // Update message + return { + conversations: __spreadArray(__spreadArray([], state.conversations.filter(function (c) { return c.id !== conversationId; }), true), [ + __assign(__assign({}, conversation), { updatedAt: new Date().getTime(), messages: __spreadArray(__spreadArray([], conversation.messages.filter(function (m) { return m.id !== message.id; }), true), [ + message, + ], false) }), + ], false), + }; + } + // Add message + return { + conversations: __spreadArray(__spreadArray([], state.conversations.filter(function (c) { return c.id !== conversationId; }), true), [ + __assign(__assign({}, conversation), { updatedAt: new Date().getTime(), messages: __spreadArray(__spreadArray([], conversation.messages, true), [message], false) }), + ], false), + }; + }); + }, + }; +}, { + name: "chat-store", + getStorage: function () { return sessionStorage; }, +})); + +// https://github.com/pmndrs/zustand/blob/65d2bc0660ab0d542cf9f97a3b004754ffa73f3e/docs/integrations/persisting-store-data.md?plain=1#L471-L488 +var useStore = function (store, callback) { + var result = store(callback); + var _a = useState(), data = _a[0], setData = _a[1]; + useEffect(function () { + setData(result); + }, [result]); + return data; +}; + +var initialProgress = { + type: "init", + progress: 0, + timeElapsed: 0, + currentChunk: 0, + totalChunks: 0, + fetchedBytes: 0, + totalBytes: 0, +}; +var useLLMContext = function () { + var _a = useState(initialProgress), loadingStatus = _a[0], setLoadingStatus = _a[1]; + var _b = useState(false), isGenerating = _b[0], setIsGenerating = _b[1]; + var workerRef = useRef(); + var cStore = useStore(useConversationStore, function (state) { return state; }); + var _c = useState("user"), userRoleName = _c[0], setUserRoleName = _c[1]; + var _d = useState("assistant"), assistantRoleName = _d[0], setAssistantRoleName = _d[1]; + var _e = useState({ + adapter: null, + device: null, + adapterInfo: null, + checked: false, + unsupportedReason: null, + }), gpuDevice = _e[0], setGpuDevice = _e[1]; + useEffect(function () { + if (!gpuDevice || !gpuDevice.checked) { + detectGPUDevice() + .then(function (resp) { + if (resp) { + setGpuDevice({ + unsupportedReason: null, + checked: true, + adapter: resp.adapter, + device: resp.device, + adapterInfo: resp.adapterInfo, + }); + } + else { + setGpuDevice(__assign(__assign({}, gpuDevice), { checked: true, unsupportedReason: "GPU is not supported" })); + } + }) + .catch(function (err) { + setGpuDevice({ + adapter: null, + device: null, + adapterInfo: null, + checked: true, + unsupportedReason: err.message, + }); + }); + } + }, []); + var _f = useState(), onMessage = _f[0], setOnMessage = _f[1]; + var addMessage = useCallback(function (resp) { + if (resp.isFinished) { + setIsGenerating(false); + } + if (onMessage) + onMessage(resp); + cStore === null || cStore === void 0 ? void 0 : cStore.addMessage(cStore === null || cStore === void 0 ? void 0 : cStore.currentConversationId, { + id: resp.requestId, + createdAt: new Date().getTime(), + updatedAt: new Date().getTime(), + role: assistantRoleName, + text: resp.outputText, + }); + }, [cStore, cStore === null || cStore === void 0 ? void 0 : cStore.currentConversationId, onMessage, setOnMessage]); + useEffect(function () { + if (!workerRef.current) { + workerRef.current = wrap(new Worker(new URL("worker-cc79b531.js", import.meta.url))); + } + }, []); + var send = function (text, maxTokens, stopStrings) { + var _a; + if (maxTokens === void 0) { maxTokens = 100; } + if (stopStrings === void 0) { stopStrings = [userRoleName, assistantRoleName]; } + var currentConversation = cStore === null || cStore === void 0 ? void 0 : cStore.getConversation(cStore === null || cStore === void 0 ? void 0 : cStore.currentConversationId); + if (!currentConversation) { + throw new Error("Invalid conversation id"); + } + currentConversation === null || currentConversation === void 0 ? void 0 : currentConversation.messages.push({ + id: v4(), + createdAt: new Date().getTime(), + updatedAt: new Date().getTime(), + role: userRoleName, + text: text, + }); + setIsGenerating(true); + (_a = workerRef === null || workerRef === void 0 ? void 0 : workerRef.current) === null || _a === void 0 ? void 0 : _a.generate({ + conversation: currentConversation, + stopTexts: stopStrings, + maxTokens: maxTokens, + assistantRoleName: assistantRoleName, + }, proxy(addMessage)); + }; + return { + conversation: cStore === null || cStore === void 0 ? void 0 : cStore.getConversation(cStore === null || cStore === void 0 ? void 0 : cStore.currentConversationId), + allConversations: cStore === null || cStore === void 0 ? void 0 : cStore.conversations.sort(function (a, b) { return b.updatedAt - a.updatedAt; }), + createConversation: function (title, prompt) { + var id = v4(); + cStore === null || cStore === void 0 ? void 0 : cStore.createConversation({ + id: id, + title: title !== null && title !== void 0 ? title : "Untitled", + systemPrompt: prompt !== null && prompt !== void 0 ? prompt : defaultSystemPrompt, + messages: [], + createdAt: new Date().getTime(), + updatedAt: new Date().getTime(), + }); + }, + setConversationTitle: function (id, title) { + cStore === null || cStore === void 0 ? void 0 : cStore.setConversationTitle(id, title); + }, + setConversationId: function (id) { + cStore === null || cStore === void 0 ? void 0 : cStore.setConversationId(id); + }, + deleteConversation: function (id) { + cStore === null || cStore === void 0 ? void 0 : cStore.deleteConversation(id); + }, + deleteMessages: function () { return cStore === null || cStore === void 0 ? void 0 : cStore.deleteMessages(cStore === null || cStore === void 0 ? void 0 : cStore.currentConversationId); }, + onMessage: onMessage, + setOnMessage: setOnMessage, + loadingStatus: loadingStatus, + isGenerating: isGenerating, + userRoleName: userRoleName, + setUserRoleName: setUserRoleName, + assistantRoleName: assistantRoleName, + setAssistantRoleName: setAssistantRoleName, + gpuDevice: gpuDevice, + send: send, + init: function () { var _a; return (_a = workerRef === null || workerRef === void 0 ? void 0 : workerRef.current) === null || _a === void 0 ? void 0 : _a.init(proxy(setLoadingStatus)); }, + deleteAllConversations: function () { return cStore === null || cStore === void 0 ? void 0 : cStore.deleteAllConversations(); }, + }; +}; + +var ModelContext = createContext(null); +var ModelProvider = function (_a) { + var children = _a.children; + var LLMValue = useLLMContext(); + return (require$$0.createElement(ModelContext.Provider, { value: LLMValue }, children)); +}; +var useLLM = function () { + var context = useContext(ModelContext); + if (context === null) { + throw new Error("useLLMContext must be used within a LLMProvider"); + } + return context; +}; + +export { ModelProvider, useLLM as default }; diff --git a/packages/headless/dist/types/src/hooks/useConversationStore.d.ts b/packages/headless/dist/types/src/hooks/useConversationStore.d.ts new file mode 100644 index 0000000..f748e87 --- /dev/null +++ b/packages/headless/dist/types/src/hooks/useConversationStore.d.ts @@ -0,0 +1,27 @@ +import { Conversation, Message } from "../types/chat"; +export interface ConversationStore { + conversations: Conversation[]; + currentConversationId: string; + setConversationId: (conversationId: string) => void; + addMessage: (conversationId: string, message: Message) => void; + getConversation: (conversationId: string) => Conversation | undefined; + setConversationTitle: (conversationId: string, title: string) => void; + getAllConversations: () => Conversation[]; + deleteMessages: (conversationId: string) => void; + deleteConversation: (conversationId: string) => void; + createConversation: (conversation: Conversation) => void; + deleteAllConversations: () => void; +} +export declare const defaultSystemPrompt = "A chat between a curious user and a AI chatbot named SmartestChild on AIM who responds with lowercase, frequent emojis, and 2000s internet abbreviations."; +declare const useConversationStore: import("zustand").UseBoundStore, "persist"> & { + persist: { + setOptions: (options: Partial>) => void; + clearStorage: () => void; + rehydrate: () => void | Promise; + hasHydrated: () => boolean; + onHydrate: (fn: (state: ConversationStore) => void) => () => void; + onFinishHydration: (fn: (state: ConversationStore) => void) => () => void; + getOptions: () => Partial>; + }; +}>; +export default useConversationStore; diff --git a/packages/headless/dist/types/src/hooks/useLLM.d.ts b/packages/headless/dist/types/src/hooks/useLLM.d.ts new file mode 100644 index 0000000..0faba03 --- /dev/null +++ b/packages/headless/dist/types/src/hooks/useLLM.d.ts @@ -0,0 +1,36 @@ +/// +import { InitProgressReport } from "@/worker/lib/tvm/runtime"; +import { Conversation } from "../types/chat"; +import { GenerateTextResponse } from "../types/worker_message"; +export type UseLLMParams = { + autoInit?: boolean; +}; +export type GPUDeviceInfo = { + adapter: GPUAdapter | null; + device: GPUDevice | null; + adapterInfo: GPUAdapterInfo | null; + checked: boolean; + unsupportedReason: string | null; +}; +export type UseLLMResponse = { + conversation: Conversation | undefined; + allConversations: Conversation[] | undefined; + loadingStatus: InitProgressReport; + isGenerating: boolean; + createConversation: (title?: string, prompt?: string) => void; + setConversationId: (conversationId: string) => void; + deleteConversation: (conversationId: string) => void; + deleteAllConversations: () => void; + deleteMessages: () => void; + setConversationTitle: (conversationId: string, title: string) => void; + onMessage: (msg: GenerateTextResponse) => void; + setOnMessage: (cb: (msg: GenerateTextResponse) => void) => void; + userRoleName: string; + setUserRoleName: (roleName: string) => void; + assistantRoleName: string; + setAssistantRoleName: (roleName: string) => void; + gpuDevice: GPUDeviceInfo; + send: (text: string, maxToken: number, stopSequences: string[]) => void; + init: () => void; +}; +export declare const useLLMContext: () => UseLLMResponse; diff --git a/packages/headless/dist/types/src/hooks/useStore.d.ts b/packages/headless/dist/types/src/hooks/useStore.d.ts new file mode 100644 index 0000000..bed86fb --- /dev/null +++ b/packages/headless/dist/types/src/hooks/useStore.d.ts @@ -0,0 +1,2 @@ +declare const useStore: (store: (callback: (state: T) => unknown) => unknown, callback: (state: T) => F) => F | undefined; +export default useStore; diff --git a/packages/headless/dist/types/src/index.d.ts b/packages/headless/dist/types/src/index.d.ts new file mode 100644 index 0000000..10af5f2 --- /dev/null +++ b/packages/headless/dist/types/src/index.d.ts @@ -0,0 +1,3 @@ +import { ModelProvider, useLLM } from './providers/ModelProvider'; +export { ModelProvider }; +export default useLLM; diff --git a/packages/headless/dist/types/src/providers/ModelProvider.d.ts b/packages/headless/dist/types/src/providers/ModelProvider.d.ts new file mode 100644 index 0000000..338c69c --- /dev/null +++ b/packages/headless/dist/types/src/providers/ModelProvider.d.ts @@ -0,0 +1,8 @@ +import React from "react"; +import { UseLLMParams, UseLLMResponse } from "../hooks/useLLM"; +export interface ModelProviderProps { + children: React.ReactNode; + props?: UseLLMParams; +} +export declare const ModelProvider: React.FC; +export declare const useLLM: () => UseLLMResponse; diff --git a/packages/headless/dist/types/src/types/chat.d.ts b/packages/headless/dist/types/src/types/chat.d.ts new file mode 100644 index 0000000..acd9a1f --- /dev/null +++ b/packages/headless/dist/types/src/types/chat.d.ts @@ -0,0 +1,15 @@ +export interface Conversation { + id: string; + title: string; + systemPrompt: string; + createdAt: number; + updatedAt: number; + messages: Message[]; +} +export interface Message { + id: string; + role: string; + text: string; + createdAt: number; + updatedAt: number; +} diff --git a/packages/headless/dist/types/src/types/worker_message.d.ts b/packages/headless/dist/types/src/types/worker_message.d.ts new file mode 100644 index 0000000..62df271 --- /dev/null +++ b/packages/headless/dist/types/src/types/worker_message.d.ts @@ -0,0 +1,26 @@ +import * as Comlink from 'comlink'; +import { InitProgressCallback } from "../worker/lib/tvm/runtime"; +import { Conversation } from "./chat"; +export type ModelWorker = { + init(callback: Comlink.ProxyOrClone): void; + generate(request: GenerateTextRequest, callback: Comlink.ProxyOrClone): void; +}; +export type InitCallback = InitProgressCallback; +export type GenerateTextCallback = (data: GenerateTextResponse) => void; +export type GenerateTextRequest = { + conversation: Conversation; + stopTexts: string[]; + maxTokens: number; + assistantRoleName: string; +}; +export type GenerateTextResponse = { + requestId: string; + step: number; + outputText: string; + stats: { + totalDecodingSeconds: number; + totalDecodedTokens: number; + totalEncodedTokens: number; + }; + isFinished: boolean; +}; diff --git a/packages/headless/dist/types/src/worker/lib/tvm/compact.d.ts b/packages/headless/dist/types/src/worker/lib/tvm/compact.d.ts new file mode 100644 index 0000000..866a6af --- /dev/null +++ b/packages/headless/dist/types/src/worker/lib/tvm/compact.d.ts @@ -0,0 +1,10 @@ +/** NodeJS and Web compact layer */ +/** + * Get performance measurement. + */ +export declare function getPerformance(): Performance; +/** + * Create a new websocket for a given URL + * @param url The url. + */ +export declare function createWebSocket(url: string): WebSocket; diff --git a/packages/headless/dist/types/src/worker/lib/tvm/ctypes.d.ts b/packages/headless/dist/types/src/worker/lib/tvm/ctypes.d.ts new file mode 100644 index 0000000..fac9218 --- /dev/null +++ b/packages/headless/dist/types/src/worker/lib/tvm/ctypes.d.ts @@ -0,0 +1,180 @@ +/** + * Types for C API. + */ +/** A pointer to points to the raw address space. */ +export type Pointer = number; +/** A pointer offset, need to add a base address to get a valid ptr. */ +export type PtrOffset = number; +/** + * const char *TVMGetLastError(); + */ +export type FTVMGetLastError = () => Pointer; +/** + * int TVMModGetFunction(TVMModuleHandle mod, + * const char* func_name, + * int query_imports, + * TVMFunctionHandle *out); + */ +export type FTVMModGetFunction = (mod: Pointer, funcName: Pointer, queryImports: number, out: Pointer) => number; +/** + * int TVMModImport(TVMModuleHandle mod, + * TVMModuleHandle dep); + */ +export type FTVMModImport = (mod: Pointer, dep: Pointer) => number; +/** + * int TVMModFree(TVMModuleHandle mod); + */ +export type FTVMModFree = (mod: Pointer) => number; +/** + * int TVMFuncFree(TVMFunctionHandle func); + */ +export type FTVMFuncFree = (func: Pointer) => number; +/** + * int TVMFuncCall(TVMFunctionHandle func, + * TVMValue* arg_values, + * int* type_codes, + * int num_args, + * TVMValue* ret_val, + * int* ret_type_code); + */ +export type FTVMFuncCall = (func: Pointer, argValues: Pointer, typeCode: Pointer, nargs: number, retValue: Pointer, retCode: Pointer) => number; +/** + * int TVMCFuncSetReturn(TVMRetValueHandle ret, + * TVMValue* value, + * int* type_code, + * int num_ret); + */ +export type FTVMCFuncSetReturn = (ret: Pointer, value: Pointer, typeCode: Pointer, numRet: number) => number; +/** + * int TVMCbArgToReturn(TVMValue* value, int* code); + */ +export type FTVMCbArgToReturn = (value: Pointer, code: Pointer) => number; +/** + * int TVMFuncListGlobalNames(int* outSize, const char*** outArray); + */ +export type FTVMFuncListGlobalNames = (outSize: Pointer, outArray: Pointer) => number; +/** + * int TVMFuncRegisterGlobal( + * const char* name, TVMFunctionHandle f, int override); + */ +export type FTVMFuncRegisterGlobal = (name: Pointer, f: Pointer, override: number) => number; +/** + *int TVMFuncGetGlobal(const char* name, TVMFunctionHandle* out); + */ +export type FTVMFuncGetGlobal = (name: Pointer, out: Pointer) => number; +/** + * int TVMArrayAlloc(const tvm_index_t* shape, + * int ndim, + * int dtype_code, + * int dtype_bits, + * int dtype_lanes, + * int device_type, + * int device_id, + * TVMArrayHandle* out); + */ +export type FTVMArrayAlloc = (shape: Pointer, ndim: number, dtypeCode: number, dtypeBits: number, dtypeLanes: number, deviceType: number, deviceId: number, out: Pointer) => number; +/** + * int TVMArrayFree(TVMArrayHandle handle); + */ +export type FTVMArrayFree = (handle: Pointer) => number; +/** + * int TVMArrayCopyFromBytes(TVMArrayHandle handle, + * void* data, + * size_t nbytes); + */ +export type FTVMArrayCopyFromBytes = (handle: Pointer, data: Pointer, nbytes: number) => number; +/** + * int TVMArrayCopyToBytes(TVMArrayHandle handle, + * void* data, + * size_t nbytes); + */ +export type FTVMArrayCopyToBytes = (handle: Pointer, data: Pointer, nbytes: number) => number; +/** + * int TVMArrayCopyFromTo(TVMArrayHandle from, + * TVMArrayHandle to, + * TVMStreamHandle stream); + */ +export type FTVMArrayCopyFromTo = (from: Pointer, to: Pointer, stream: Pointer) => number; +/** + * int TVMSynchronize(int device_type, int device_id, TVMStreamHandle stream); + */ +export type FTVMSynchronize = (deviceType: number, deviceId: number, stream: Pointer) => number; +/** + * typedef int (*TVMBackendPackedCFunc)(TVMValue* args, + * int* type_codes, + * int num_args, + * TVMValue* out_ret_value, + * int* out_ret_tcode); + */ +export type FTVMBackendPackedCFunc = (argValues: Pointer, argCodes: Pointer, nargs: number, outValue: Pointer, outCode: Pointer) => number; +/** + * int TVMObjectFree(TVMObjectHandle obj); + */ +export type FTVMObjectFree = (obj: Pointer) => number; +/** + * int TVMObjectGetTypeIndex(TVMObjectHandle obj, unsigned* out_tindex); + */ +export type FTVMObjectGetTypeIndex = (obj: Pointer, out_tindex: Pointer) => number; +/** + * int TVMObjectTypeIndex2Key(unsigned tindex, char** out_type_key); + */ +export type FTVMObjectTypeIndex2Key = (type_index: number, out_type_key: Pointer) => number; +/** + * int TVMObjectTypeKey2Index(const char* type_key, unsigned* out_tindex); + */ +export type FTVMObjectTypeKey2Index = (type_key: Pointer, out_tindex: Pointer) => number; +/** void* TVMWasmAllocSpace(int size); */ +export type FTVMWasmAllocSpace = (size: number) => Pointer; +/** void TVMWasmFreeSpace(void* data); */ +export type FTVMWasmFreeSpace = (ptr: Pointer) => void; +/** + * int TVMWasmPackedCFunc(TVMValue* args, + * int* type_codes, + * int num_args, + * TVMRetValueHandle ret, + * void* resource_handle); + */ +export type FTVMWasmPackedCFunc = (args: Pointer, typeCodes: Pointer, nargs: number, ret: Pointer, resourceHandle: Pointer) => number; +/** + * int TVMWasmFuncCreateFromCFunc(void* resource_handle, + * TVMFunctionHandle *out); + */ +export type FTVMWasmFuncCreateFromCFunc = (resource: Pointer, out: Pointer) => number; +/** + * void TVMWasmPackedCFuncFinalizer(void* resource_handle); + */ +export type FTVMWasmPackedCFuncFinalizer = (resourceHandle: Pointer) => void; +/** + * Size of common data types. + */ +export declare const enum SizeOf { + U8 = 1, + U16 = 2, + I32 = 4, + I64 = 8, + F32 = 4, + F64 = 8, + TVMValue = 8, + DLDataType = 4, + DLDevice = 8 +} +/** + * Argument Type code in TVM FFI. + */ +export declare const enum ArgTypeCode { + Int = 0, + UInt = 1, + Float = 2, + TVMOpaqueHandle = 3, + Null = 4, + TVMDataType = 5, + DLDevice = 6, + TVMDLTensorHandle = 7, + TVMObjectHandle = 8, + TVMModuleHandle = 9, + TVMPackedFuncHandle = 10, + TVMStr = 11, + TVMBytes = 12, + TVMNDArrayHandle = 13, + TVMObjectRValueRefArg = 14 +} diff --git a/packages/headless/dist/types/src/worker/lib/tvm/environment.d.ts b/packages/headless/dist/types/src/worker/lib/tvm/environment.d.ts new file mode 100644 index 0000000..5e935f7 --- /dev/null +++ b/packages/headless/dist/types/src/worker/lib/tvm/environment.d.ts @@ -0,0 +1,26 @@ +import { LibraryProvider } from "./types"; +import * as ctypes from "./ctypes"; +/** + * Environment to impelement most of the JS library functions. + */ +export declare class Environment implements LibraryProvider { + logger: (msg: string) => void; + imports: Record; + /** + * Maintains a table of FTVMWasmPackedCFunc that the C part + * can call via TVMWasmPackedCFunc. + * + * We maintain a separate table so that we can have un-limited amount + * of functions that do not maps to the address space. + */ + packedCFuncTable: Array; + /** + * Free table index that can be recycled. + */ + packedCFuncTableFreeId: Array; + private libProvider?; + constructor(importObject?: Record, logger?: (msg: string) => void); + /** Mark the start of the instance. */ + start(inst: WebAssembly.Instance): void; + private environment; +} diff --git a/packages/headless/dist/types/src/worker/lib/tvm/index.d.ts b/packages/headless/dist/types/src/worker/lib/tvm/index.d.ts new file mode 100644 index 0000000..2196fda --- /dev/null +++ b/packages/headless/dist/types/src/worker/lib/tvm/index.d.ts @@ -0,0 +1,6 @@ +export { RPCServer } from "./rpc_server"; +export { DLDataType, DLDevice, Instance, Module, NDArray, Scalar, TVMArray, instantiate } from "./runtime"; +export type { PackedFunc } from "./runtime"; +export { assert, wasmPath } from "./support"; +export type { Disposable, LibraryProvider } from "./types"; +export { detectGPUDevice } from "./webgpu"; diff --git a/packages/headless/dist/types/src/worker/lib/tvm/memory.d.ts b/packages/headless/dist/types/src/worker/lib/tvm/memory.d.ts new file mode 100644 index 0000000..9c16d6b --- /dev/null +++ b/packages/headless/dist/types/src/worker/lib/tvm/memory.d.ts @@ -0,0 +1,144 @@ +/** + * Classes to manipulate Wasm memories. + */ +import { Pointer, PtrOffset } from "./ctypes"; +import { Disposable } from "./types"; +import * as ctypes from "./ctypes"; +/** + * Wasm Memory wrapper to perform JS side raw memory access. + */ +export declare class Memory { + memory: WebAssembly.Memory; + wasm32: boolean; + private buffer; + private viewU8; + private viewU16; + private viewI32; + private viewU32; + private viewF32; + private viewF64; + constructor(memory: WebAssembly.Memory); + loadU8(ptr: Pointer): number; + loadU16(ptr: Pointer): number; + loadU32(ptr: Pointer): number; + loadI32(ptr: Pointer): number; + loadI64(ptr: Pointer): number; + loadF32(ptr: Pointer): number; + loadF64(ptr: Pointer): number; + loadPointer(ptr: Pointer): Pointer; + loadUSize(ptr: Pointer): Pointer; + sizeofPtr(): number; + /** + * Load raw bytes from ptr. + * @param ptr The head address + * @param numBytes The number + */ + loadRawBytes(ptr: Pointer, numBytes: number): Uint8Array; + /** + * Load TVMByteArray from ptr. + * + * @param ptr The address of the header. + */ + loadTVMBytes(ptr: Pointer): Uint8Array; + /** + * Load null-terminated C-string from ptr. + * @param ptr The head address + */ + loadCString(ptr: Pointer): string; + /** + * Store raw bytes to the ptr. + * @param ptr The head address. + * @param bytes The bytes content. + */ + storeRawBytes(ptr: Pointer, bytes: Uint8Array): void; + /** + * Update memory view after the memory growth. + */ + private updateViews; +} +/** + * Auxiliary call stack for the FFI calls. + * + * Lifecyle of a call stack. + * - Calls into allocXX to allocate space, mixed with storeXXX to store data. + * - Calls into ptrFromOffset, no further allocation(as ptrFromOffset can change), + * can still call into storeXX + * - Calls into commitToWasmMemory once. + * - reset. + */ +export declare class CachedCallStack implements Disposable { + /** List of temporay arguments that can be disposed during reset. */ + tempArgs: Array; + private memory; + private cAllocSpace; + private cFreeSpace; + private buffer; + private viewU8; + private viewI32; + private viewU32; + private viewF64; + private stackTop; + private basePtr; + private addressToSetTargetValue; + constructor(memory: Memory, allocSpace: ctypes.FTVMWasmAllocSpace, freeSpace: ctypes.FTVMWasmFreeSpace); + dispose(): void; + /** + * Rest the call stack so that it can be reused again. + */ + reset(): void; + /** + * Commit all the cached data to WasmMemory. + * This function can only be called once. + * No further store function should be called. + * + * @param nbytes Number of bytes to be stored. + */ + commitToWasmMemory(nbytes?: number): void; + /** + * Allocate space by number of bytes + * @param nbytes Number of bytes. + * @note This function always allocate space that aligns to 64bit. + */ + allocRawBytes(nbytes: number): PtrOffset; + /** + * Allocate space for pointers. + * @param count Number of pointers. + * @returns The allocated pointer array. + */ + allocPtrArray(count: number): PtrOffset; + /** + * Get the real pointer from offset values. + * Note that the returned value becomes obsolete if alloc is called on the stack. + * @param offset The allocated offset. + */ + ptrFromOffset(offset: PtrOffset): Pointer; + storePtr(offset: PtrOffset, value: Pointer): void; + storeUSize(offset: PtrOffset, value: Pointer): void; + storeI32(offset: PtrOffset, value: number): void; + storeU32(offset: PtrOffset, value: number): void; + storeI64(offset: PtrOffset, value: number): void; + storeF64(offset: PtrOffset, value: number): void; + storeRawBytes(offset: PtrOffset, bytes: Uint8Array): void; + /** + * Allocate then set C-String pointer to the offset. + * This function will call into allocBytes to allocate necessary data. + * The address won't be set immediately(because the possible change of basePtr) + * and will be filled when we commit the data. + * + * @param offset The offset to set ot data pointer. + * @param data The string content. + */ + allocThenSetArgString(offset: PtrOffset, data: string): void; + /** + * Allocate then set the argument location with a TVMByteArray. + * Allocate new temporary space for bytes. + * + * @param offset The offset to set ot data pointer. + * @param data The string content. + */ + allocThenSetArgBytes(offset: PtrOffset, data: Uint8Array): void; + /** + * Update internal cache views. + */ + private updateViews; +} diff --git a/packages/headless/dist/types/src/worker/lib/tvm/rpc_server.d.ts b/packages/headless/dist/types/src/worker/lib/tvm/rpc_server.d.ts new file mode 100644 index 0000000..af9928a --- /dev/null +++ b/packages/headless/dist/types/src/worker/lib/tvm/rpc_server.d.ts @@ -0,0 +1,54 @@ +import * as runtime from "./runtime"; +declare enum RPCServerState { + InitHeader = 0, + InitHeaderKey = 1, + InitServer = 2, + WaitForCallback = 3, + ReceivePacketHeader = 4, + ReceivePacketBody = 5 +} +/** + * A websocket based RPC + */ +export declare class RPCServer { + url: string; + key: string; + socket: WebSocket; + state: RPCServerState; + logger: (msg: string) => void; + getImports: () => Record; + private ndarrayCacheUrl; + private ndarrayCacheDevice; + private initProgressCallback?; + private asyncOnServerLoad?; + private pendingSend; + private name; + private inst?; + private globalObjects; + private serverRecvData?; + private currPacketHeader?; + private currPacketLength; + private remoteKeyLength; + private pendingBytes; + private buffredBytes; + private messageQueue; + constructor(url: string, key: string, getImports: () => Record, logger?: (msg: string) => void, ndarrayCacheUrl?: string, ndarrayCacheDevice?: string, initProgressCallback?: runtime.InitProgressCallback | undefined, asyncOnServerLoad?: ((inst: runtime.Instance) => Promise) | undefined); + private onClose; + private onOpen; + /** Handler for raw message. */ + private onMessage; + /** Process ready events. */ + private processEvents; + /** State machine to handle each request */ + private onDataReady; + private onPacketReady; + /** Event handler during server initialization. */ + private onInitServer; + private log; + private handleInitHeader; + private handleInitHeaderKey; + private checkLittleEndian; + private requestBytes; + private readFromBuffer; +} +export {}; diff --git a/packages/headless/dist/types/src/worker/lib/tvm/runtime.d.ts b/packages/headless/dist/types/src/worker/lib/tvm/runtime.d.ts new file mode 100644 index 0000000..8a73838 --- /dev/null +++ b/packages/headless/dist/types/src/worker/lib/tvm/runtime.d.ts @@ -0,0 +1,700 @@ +/// +/** + * TVM JS Wasm Runtime library. + */ +import { Pointer, PtrOffset } from "./ctypes"; +import { Environment } from "./environment"; +import { CachedCallStack, Memory } from "./memory"; +import { Disposable } from "./types"; +import { WebGPUContext } from "./webgpu"; +/** + * Type for PackedFunc inthe TVMRuntime. + */ +export type PackedFunc = ((...args: any) => any) & Disposable & { + _tvmPackedCell: PackedFuncCell; +}; +/** + * @internal + * FFI Library wrapper, maintains most runtime states. + */ +declare class FFILibrary implements Disposable { + wasm32: boolean; + memory: Memory; + exports: Record; + webGPUContext?: WebGPUContext; + private wasmInstance; + private recycledCallStacks; + constructor(wasmInstance: WebAssembly.Instance, imports: Record); + dispose(): void; + sizeofPtr(): number; + checkCall(code: number): void; + getOrAllocCallStack(): CachedCallStack; + recycleCallStack(callstack: CachedCallStack): void; + private validateInstance; + private checkExports; + private detectWasmMemory; +} +/** + * @internal + * Manages extra runtime context for the runtime. + */ +declare class RuntimeContext implements Disposable { + arrayGetItem: PackedFunc; + arrayGetSize: PackedFunc; + arrayMake: PackedFunc; + getSysLib: PackedFunc; + arrayCacheGet: PackedFunc; + arrayCacheUpdate: PackedFunc; + arrayCacheRemove: PackedFunc; + arrayCacheClear: PackedFunc; + arrayDecodeStorage: PackedFunc; + paramModuleFromCache: PackedFunc; + makeShapeTuple: PackedFunc; + ndarrayCreateView: PackedFunc; + sampleTopPFromLogits: PackedFunc; + private autoDisposeScope; + constructor(getGlobalFunc: (name: string) => PackedFunc); + dispose(): void; + beginScope(): void; + endScope(): void; + /** + * Track object for dispose in current scope. + * + * @param obj The object to be tracked. + * @returns the same object. + * @note This function only needs to be called for raw system C API values. + * The return value of PackedFunc will be automatically tracked. + */ + attachToCurrentScope(obj: T): T; + moveToParentScope(obj: T): T; + detachFromCurrentScope(obj: T): T; +} +/** + * A typed scalar constant used to represent a typed number + * argument to PackedFunc calls. + */ +export declare class Scalar { + /** The value. */ + value: number; + /** The data type of the scalar. */ + dtype: string; + constructor(value: number, dtype: string); +} +/** + * Cell holds the PackedFunc object. + */ +declare class PackedFuncCell implements Disposable { + private handle; + private lib; + constructor(handle: Pointer, lib: FFILibrary); + dispose(): void; + getHandle(requireNotNull?: boolean): Pointer; +} +/** + * Represent a runtime context where a NDArray can reside. + */ +export declare class DLDevice { + /** The device type code of the device. */ + deviceType: number; + /** The device index. */ + deviceId: number; + private lib; + constructor(deviceType: number | string, deviceId: number, lib: FFILibrary); + /** + * Synchronize the device + */ + sync(): Promise; + toString(): string; +} +/** + * The data type code in DLDataType + */ +export declare const enum DLDataTypeCode { + Int = 0, + UInt = 1, + Float = 2, + OpaqueHandle = 3 +} +/** + * Runtime data type of NDArray. + */ +export declare class DLDataType { + /** The type code */ + code: number; + /** Number of bits in the data type. */ + bits: number; + /** Number of vector lanes. */ + lanes: number; + constructor(code: number, bits: number, lanes: number); + toString(): string; + numStorageBytes(): number; +} +/** + * n-dimnesional array. + */ +export declare class NDArray implements Disposable { + /** Internal array handle. */ + private handle; + /** Number of dimensions. */ + ndim: number; + /** Data type of the array. */ + dtype: string; + /** Shape of the array. */ + shape: Array; + /** Device of the array. */ + device: DLDevice; + /** Whether it is a temporary view that can become invalid after the call. */ + isView: boolean; + private byteOffset; + private dltensor; + private dataPtr; + private lib; + private ctx; + private dlDataType; + constructor(handle: Pointer, isView: boolean, lib: FFILibrary, ctx: RuntimeContext); + /** + * Create a view of the array. + * @param shape The shape of the view. + * @returns The new sliced ndarray. + */ + view(shape: Array): NDArray; + /** + * Get handle of ndarray, check it is not null. + * + * @param requireNotNull require handle is not null. + * @returns The handle. + */ + getHandle(requireNotNull?: boolean): Pointer; + /** + * Get dataPtr of NDarray + * + * @returns The handle. + */ + getDataPtr(): Pointer; + dispose(): void; + /** + * Copy data from another NDArray or javascript array. + * The number of elements must match. + * + * @param data The source data array. + * @returns this + */ + copyFrom(data: NDArray | Array | Float32Array): this; + /** + * Copy data from raw bytes. + * @param data Uint8Array of bytes. + * @returns this + */ + copyFromRawBytes(data: Uint8Array): this; + /** + * Return a copied Uint8Array of the raw bytes in the NDArray. + * @returns The result array. + */ + toRawBytes(): Uint8Array; + /** + * Return a TypedArray copy of the NDArray, the specific type depends on + * the dtype of the NDArray. + * @returns The result array. + */ + toArray(): Float32Array | Float64Array | Int32Array | Int8Array | Uint8Array; + private getDLTensorFromArrayHandle; +} +/** + * Runtime Module. + */ +export declare class Module implements Disposable { + private handle; + private lib; + private makePackedFunc; + constructor(handle: Pointer, lib: FFILibrary, makePackedFunc: (ptr: Pointer) => PackedFunc); + dispose(): void; + /** + * Get handle of module, check it is not null. + * + * @param requireNotNull require handle is not null. + * @returns The handle. + */ + getHandle(requireNotNull?: boolean): Pointer; + /** + * Get a function in the module. + * @param name The name of the function. + * @param queryImports Whether to also query imports + * @returns The result function. + */ + getFunction(name: string, queryImports?: boolean): PackedFunc; + /** + * Import another module into the current runtime module. + * @param mod The module to be imported. + */ + importModule(mod: Module): void; +} +/** + * Generic object base + */ +export declare class TVMObject implements Disposable { + private handle; + private lib; + protected ctx: RuntimeContext; + constructor(handle: Pointer, lib: FFILibrary, ctx: RuntimeContext); + dispose(): void; + /** + * Get handle of module, check it is not null. + * + * @param requireNotNull require handle is not null. + * @returns The handle. + */ + getHandle(requireNotNull?: boolean): Pointer; + /** get the type index of the object */ + typeIndex(): number; + /** get the type key of the object */ + typeKey(): string; +} +/** Objectconstructor */ +type FObjectConstructor = (handle: Pointer, lib: FFILibrary, ctx: RuntimeContext) => TVMObject; +/** All possible object types. */ +type TVMObjectBase = TVMObject | NDArray | Module | PackedFunc; +/** Runtime array object. */ +export declare class TVMArray extends TVMObject { + constructor(handle: Pointer, lib: FFILibrary, ctx: RuntimeContext); + /** + * @returns the size of the array. + */ + size(): number; + /** + * Get index-th element of the array + * @param index the array index. + * @returns The element. + */ + get(index: number): TVMObjectBase; +} +export declare const enum VMAllocatorKind { + NAIVE_ALLOCATOR = 1, + POOLED_ALLOCATOR = 2 +} +/** + * VirtualMachine Executor. + * + * This is a thin wrapper of the underlying TVM module. + * you can also directly call set_input, run, and get_output + * of underlying module functions + */ +export declare class VirtualMachine implements Disposable { + private mod; + /** + * Constructor + * @param mod The underlying module, need to be detached. + * @param device The main device ro run VM on. + */ + constructor(mod: Module, device: DLDevice); + dispose(): void; + /** + * Get a function in the VM module. + * @param name The name of the function. + * @returns The result function. + */ + getFunction(name: string): PackedFunc; + /** + * Get the internal module. + */ + getInternalModule(): Module; +} +export interface NDArrayCacheEntry { + name: string; + shape: Array; + dtype: string; + format: "f32-to-bf16" | "raw"; + byteOffset: number; + nbytes: number; +} +export interface NDArrayShardEntry { + dataPath: string; + format: "raw-shard"; + nbytes: number; + records: Array; +} +export interface InitProgressReport { + type: 'init'; + progress: number; + timeElapsed: number; + currentChunk: number; + totalChunks: number; + fetchedBytes: number; + totalBytes: number; +} +export type InitProgressCallback = (report: InitProgressReport) => void; +/** + * TVM runtime instance. + * + * All objects(NDArray, Module, PackedFunc) returned by TVM runtim function call + * and PackedFunc instance are tracked through a scope mechanism that will get + * auto-released when we call EndScope. + * + * This is necessarily to be able to release the underlying WASM and WebGPU memory that + * are not tracked through JS native garbage collection mechanism. + * + * This does mean that we have to get familar with the following functions: + * - {@link beginScope} + * - {@link endScope} + * - {@link withNewScope} + * - {@link attachToCurrentScope} + * - {@link detachFromCurrentScope} + */ +export declare class Instance implements Disposable { + memory: Memory; + exports: Record; + cacheMetadata: Record; + private lib; + private env; + private objFactory; + private ctx; + private initProgressCallback; + /** + * Internal function(registered by the runtime) + */ + private wasmCreateLibraryModule?; + /** + * Constructor + * + * importObject can also be a {@link LibraryProvider} object, + * a WASI object, or an object containing wasmLibraryProvider field. + * + * @param wasmModule The input module or instance. + * @param importObject The imports to initialize the wasmInstance if it is not provided. + * @param wasmInstance Additional wasm instance argument for deferred construction. + * @param env Directly specified environment module. + * + * @see Please use the async version {@link instantiate} when targeting browsers. + */ + constructor(wasmModule: WebAssembly.Module, importObject?: Record, wasmInstance?: WebAssembly.Instance, env?: Environment); + /** + * Benchmark stable execution of the run function. + * + * @params run The run function + * @params dev The device to sync during each run. + * @number The number of times to compute the average. + * @repeat The number of times to repeat the run. + */ + benchmark(run: () => void, dev: DLDevice, number?: number, repeat?: number): Promise; + dispose(): void; + /** + * Obtain the runtime information in readable format. + */ + runtimeStatsText(): string; + /** + * Begin a new scope for tracking object disposal. + */ + beginScope(): void; + /** + * End a scope and release all created TVM objects + * under the current scope. + * + * Exception: one can call {@link moveToParentScope} to move + * a value to parent scope. + */ + endScope(): void; + /** + * Perform action under a new scope. + * + * @param action The action function. + * @returns The result value. + * + * @note For action to return a valid value, + * we will need to call {@link moveToParentScope} + * for the objects that are created in the scope. + */ + withNewScope(action: () => T): T; + /** + * Attach a detached obj to the auto-release pool of the current scope. + * + * @param obj The input obj. + * @note Normally user do not need to call this function explicitly, as + * all library call return values are explicitly attached to + * the current scope. You only need to do so when you call + * {@link detachFromCurrentScope} to create a detached object. + */ + attachToCurrentScope(obj: T): T; + /** + * Move obj's attachment to the parent scope. + * + * This function is useful to make sure objects are still + * alive when exit the current scope. + * + * @param obj The object to be moved. + * @returns The input obj. + */ + moveToParentScope(obj: T): T; + /** + * Detach the object from the current scope + * so it won't be released via auto-release during endscope. + * + * User needs to either explicitly call obj.dispose(), or + * {@link attachToCurrentScope} to re-attach to the current scope. + * + * This function can be used to return values to the parent scope. + * @param obj The object. + */ + detachFromCurrentScope(obj: T): T; + /** + * Get system-wide library module in the wasm. + * System lib is a global module that contains self register functions in startup. + * @returns The system library module. + */ + systemLib(): Module; + /** + * List all the global function names registered in the runtime. + * @returns The name list. + */ + listGlobalFuncNames(): Array; + /** + * Register function to be global function in tvm runtime. + * @param name The name of the function. + * @param f function to be registered. + * @param override Whether overwrite function in existing registry. + */ + registerFunc(name: string, func: PackedFunc | Function, override?: boolean): void; + /** + * Get global PackedFunc from the runtime. + * @param name The name of the function. + * @param autoAttachToScope Whether to track it via autoDispose + * @returns The result function. + */ + getGlobalFunc(name: string): PackedFunc; + private getGlobalFuncInternal; + /** + * Check if func is PackedFunc. + * + * @param func The input. + * @returns The check result. + */ + isPackedFunc(func: unknown): boolean; + /** + * Convert func to PackedFunc + * + * @param func Input function. + * @returns The converted function. + */ + toPackedFunc(func: Function): PackedFunc; + private toPackedFuncInternal; + /** + * Setup a virtual machine module with given device. + * + * @param dev DLDevice the device. + * @returns The created virtual machime. + */ + createVirtualMachine(dev: DLDevice): VirtualMachine; + /** + * Register a call back for fetch progress. + * + * @param cb the fetch progress callback. + */ + registerInitProgressCallback(cb: InitProgressCallback): void; + /** + * Get parameters in the form of prefix_i + * + * @param prefix The parameter prefix. + * @param numParams Number of parameters. + * @returns + */ + getParamsFromCache(prefix: string, numParams: number): TVMObject; + /** + * Get NDArray from cache. + * @param name The name of array. + * @returns The result. + */ + ndarrayCacheGet(name: string): NDArray | undefined; + /** + * Get NDArray from cache. + * @param name The name of array. + * @returns The result. + */ + ndarrayCacheRemove(name: string): NDArray | undefined; + /** + * Update the ndarray cache. + * @param name The name of the array. + * @param arr The content. + */ + ndarrayCacheUpdate(name: string, arr: NDArray, override?: boolean): void; + /** + * Update the ndarray cache. + * @param name The name of the array. + * @param arr The content. + */ + ndarrayCacheClear(): void; + /** + * Fetch NDArray cache from url. + * + * @param ndarrayCacheUrl The cache url. + * @param device The device to be fetched to. + * @returns The meta data + */ + fetchNDArrayCache(ndarrayCacheUrl: string, device: DLDevice): Promise; + /** + * Fetch list of NDArray into the NDArrayCache. + * + * @param ndarrayCacheUrl The cache url. + * @param list The list of array data. + * @param device The device to store the data to. + */ + private fetchNDArrayCacheInternal; + /** + * Convert dtype to {@link DLDataType} + * + * @param dtype The input dtype string or DLDataType. + * @returns The converted result. + */ + toDLDataType(dtype: string | DLDataType): DLDataType; + /** + * Create a new {@link Scalar} that can be passed to a PackedFunc. + * @param value The number value. + * @param dtype The dtype string. + * @returns The created scalar. + */ + scalar(value: number, dtype: string): Scalar; + /** + * Create a new {@link DLDevice} + * @param deviceType The device type. + * @param deviceId The device index. + * @returns The created device. + */ + device(deviceType: number | string, deviceId?: number): DLDevice; + /** + * Create a new cpu {@link DLDevice} + * @param deviceId The device index. + */ + cpu(deviceId?: number): DLDevice; + /** + * Create a new webgpu {@link DLDevice} + * @param deviceId The device index. + */ + webgpu(deviceId?: number): DLDevice; + /** + * Create an empty {@link NDArray} with given shape and dtype. + * + * @param shape The shape of the array. + * @param dtype The data type of the array. + * @param dev The device of the ndarray. + * @returns The created ndarray. + */ + empty(shape: Array | number, dtype?: string | DLDataType, dev?: DLDevice): NDArray; + /** + * Create am uniform {@link NDArray} with given shape. + * + * @param shape The shape of the array. + * @param low The low value. + * @param high The high value. + * @param dev The device of the ndarray. + * @returns The created ndarray. + */ + uniform(shape: Array, low: number, high: number, dev: DLDevice): NDArray; + /** + * Sample index via top-p sampling. + * + * @param logits The input logits before normalization. + * @param temperature The temperature factor, will take argmax if temperature = 0.0 + * @param top_p The top_p + * @returns The sampled index. + */ + sampleTopPFromLogits(logits: NDArray, temperature: number, top_p: number): number; + /** + * Bind canvas to the current WebGPU context + * @param canvas The canvas. + */ + bindCanvas(canvas: HTMLCanvasElement): void; + /** + * Show image in canvas. + * + * @param dataRGBA Image array in height x width uint32 NDArray RGBA format on GPU. + */ + showImage(dataRGBA: NDArray): void; + /** + * Clear canvas + */ + clearCanvas(): void; + /** + * Create an tuple {@link TVMArray} input array. + * + * The input array can be passed to tvm runtime function + * and needs to b explicitly disposed. + * + * @param inputs The input array + * @returns The result array. + */ + makeTVMArray(inputs: Array): TVMArray; + /** + * Create a shape tuple to pass to runtime. + * @param shape The shape . + * @returns The created shape tuple. + */ + makeShapeTuple(shape: Array): TVMObject; + /** + * Get type index from type key. + * @param typeKey The type key. + * @returns The corresponding type index. + */ + typeKey2Index(typeKey: string): number; + /** + * Register an object constructor. + * @param typeKey The name of the function. + * @param func function to be registered. + * @param override Whether overwrite function in existing registry. + */ + registerObjectConstructor(typeKey: string, func: FObjectConstructor, override?: boolean): void; + /** + * Register an asyncfunction to be global function in the server. + * @param name The name of the function. + * @param func function to be registered. + * @param override Whether overwrite function in existing registry. + * + * @note The async function will only be used for serving remote calls in the rpc. + */ + registerAsyncServerFunc(name: string, func: Function, override?: boolean): void; + /** + * Asynchrously load webgpu pipelines when possible. + * @param mod The input module. + */ + asyncLoadWebGPUPiplines(mod: Module): Promise; + /** + * Initialize webgpu in the runtime. + * @param device The given GPU device. + */ + initWebGPU(device: GPUDevice): void; + /** Register all object factory */ + private registerObjectFactoryFuncs; + /** Register global packed functions needed by the backend to the env. */ + private registerEnvGlobalPackedFuncs; + private createPackedFuncFromCFunc; + /** + * Set packed function arguments into the location indicated by argsValue and argsCode. + * Allocate new temporary space from the stack if necessary. + * + * @parma stack The call stack + * @param args The input arguments. + * @param argsValue The offset of argsValue. + * @param argsCode The offset of argsCode. + */ + setPackedArguments(stack: CachedCallStack, args: Array, argsValue: PtrOffset, argsCode: PtrOffset): void; + private wrapJSFuncAsPackedCFunc; + private makePackedFunc; + /** + * Creaye return value of the packed func. The value us auto-tracked for dispose. + * @param rvaluePtr The location of rvalue + * @param tcode The type code. + * @param callbackArg Whether it is being used in callbackArg. + * @returns The JS value. + */ + private retValueToJS; +} +/** + * Asynchrously instantiate a new {@link Instance}. + * + * importObject can also be a {@link LibraryProvider} object, + * a WASI object, or an object containing wasmLibraryProvider field. + * We can take benefit of syslib implementations from the Emscripten + * by passing its generated js Module as the imports. + * + * @param bufferSource The source to be compiled. + * @param importObject The import objects. + * @param logger The system logger. + */ +export declare function instantiate(bufferSource: ArrayBuffer, importObject?: Record, logger?: (msg: string) => void): Promise; +export {}; diff --git a/packages/headless/dist/types/src/worker/lib/tvm/support.d.ts b/packages/headless/dist/types/src/worker/lib/tvm/support.d.ts new file mode 100644 index 0000000..d5d1b3e --- /dev/null +++ b/packages/headless/dist/types/src/worker/lib/tvm/support.d.ts @@ -0,0 +1,23 @@ +/** + * Convert string to Uint8array. + * @param str The string. + * @returns The corresponding Uint8Array. + */ +export declare function StringToUint8Array(str: string): Uint8Array; +/** + * Convert Uint8array to string. + * @param array The array. + * @returns The corresponding string. + */ +export declare function Uint8ArrayToString(arr: Uint8Array): string; +/** + * Internal assert helper + * @param condition condition The condition to fail. + * @param msg msg The message. + */ +export declare function assert(condition: boolean, msg?: string): asserts condition; +/** + * Get the path to the wasm library in nodejs. + * @return The wasm path. + */ +export declare function wasmPath(): string; diff --git a/packages/headless/dist/types/src/worker/lib/tvm/types.d.ts b/packages/headless/dist/types/src/worker/lib/tvm/types.d.ts new file mode 100644 index 0000000..c8986c5 --- /dev/null +++ b/packages/headless/dist/types/src/worker/lib/tvm/types.d.ts @@ -0,0 +1,33 @@ +/** Common type definitions. */ +/** + * Library interface provider that can provide + * syslibs(e.g. libs provided by WASI and beyond) for the Wasm runtime. + * + * It can be viewed as a generalization of imports used in WebAssembly instance creation. + * + * The {@link LibraryProvider.start} callback will be called + * to allow the library provider to initialize related resources during startup time. + * + * We can use Emscripten generated js Module as a { wasmLibraryProvider: LibraryProvider }. + */ +export interface LibraryProvider { + /** The imports that can be passed to WebAssembly instance creation. */ + imports: Record; + /** + * Callback function to notify the provider the created instance. + * @param inst The created instance. + */ + start: (inst: WebAssembly.Instance) => void; +} +/** + * Disposable classes that contains resources (WasmMemory, GPU buffer) + * which needs to be explicitly disposed. + */ +export interface Disposable { + /** + * Dispose the internal resource + * This function can be called multiple times, + * only the first call will take effect. + */ + dispose: () => void; +} diff --git a/packages/headless/dist/types/src/worker/lib/tvm/webgpu.d.ts b/packages/headless/dist/types/src/worker/lib/tvm/webgpu.d.ts new file mode 100644 index 0000000..1a53f4e --- /dev/null +++ b/packages/headless/dist/types/src/worker/lib/tvm/webgpu.d.ts @@ -0,0 +1,124 @@ +/// +import { Memory } from "./memory"; +/** A pointer to points to the raw address space. */ +export type GPUPointer = number; +export interface GPUDeviceDetectOutput { + adapter: GPUAdapter; + adapterInfo: GPUAdapterInfo; + device: GPUDevice; +} +/** + * DetectGPU device in the environment. + */ +export declare function detectGPUDevice(): Promise; +/** + * Function info from the API + */ +export interface FunctionInfo { + name: string; + arg_types: Array; + launch_param_tags: Array; +} +/** + * WebGPU context + * Manages all the webgpu resources here. + */ +export declare class WebGPUContext { + device: GPUDevice; + memory: Memory; + private bufferTable; + private bufferTableFreeId; + private podArgStagingBuffers; + private canvasRenderManager?; + private maxNumPodArgsStagingBuffers; + private peakAllocatedBytes; + private currAllocatedBytes; + private allAllocatedBytes; + private shaderSubmitCounter; + protected debugShaderSubmitLimit: number; + protected debugLogFinish: boolean; + constructor(memory: Memory, device: GPUDevice); + /** + * Dispose context. + */ + dispose(): void; + /** + * Wait for all pending GPU tasks to complete + */ + sync(): Promise; + /** + * Obtain the runtime information in readable format. + */ + runtimeStatsText(): string; + /** + * Draw image from data in storage buffer. + * @param ptr The GPU ptr + * @param height The height of the image. + * @param width The width of the image. + */ + drawImageFromBuffer(ptr: GPUPointer, height: number, width: number): void; + /** + * Copy raw bytes into buffer ptr. + * + * @param rawBytes The raw bytes + * @param toPtr The target gpu buffer ptr + * @param toOffset The beginning offset + * @param nbytes Number of bytes + */ + copyRawBytesToBuffer(rawBytes: Uint8Array, toPtr: GPUPointer, toOffset: number, nbytes: number): void; + /** + * Clear canvas + */ + clearCanvas(): void; + /** + * Bind a canvas element to the runtime. + * @param canvas The HTML canvas/ + */ + bindCanvas(canvas: HTMLCanvasElement): void; + /** + * Create a PackedFunc that runs the given shader + * via createComputePipeline + * + * @param info The function information already parsed as a record. + * @param code The shader data(in WGSL) + * @returns The shader + */ + createShader(finfo: FunctionInfo, code: string): Function; + /** + * Create a PackedFunc that runs the given shader asynchrously + * via createComputePipelineAsync + * + * @param info The function information already parsed as a record. + * @param code The shader data(in WGSL) + * @returns The shader + */ + createShaderAsync(finfo: FunctionInfo, code: string): Promise; + /** + * Get the pod arg staging buffer + * \param nbytes The minimum size. + * \return The allocated buffer + */ + private getPodArgsBuffer; + /** + * Internal impl of createShader for both async and sync mode. + * + * @param info The function information already parsed as a record. + * @param code The shader data(in WGSL) + * @param asyncMode Whether use async mode. + * @returns The shader function or promise of shader func. + */ + private createShadeInternal; + /** + * Get the device API according to its name + * @param The name of the API. + * @returns The corresponding device api. + */ + getDeviceAPI(name: string): Function; + private deviceAllocDataSpace; + private deviceFreeDataSpace; + private deviceCopyToGPU; + private deviceCopyFromGPU; + private deviceCopyWithinGPU; + private gpuBufferFromPtr; + private attachToBufferTable; +} diff --git a/packages/headless/dist/types/src/worker/llm.d.ts b/packages/headless/dist/types/src/worker/llm.d.ts new file mode 100644 index 0000000..bc02748 --- /dev/null +++ b/packages/headless/dist/types/src/worker/llm.d.ts @@ -0,0 +1,44 @@ +import { Conversation } from "../types/chat"; +import { GenerateTextCallback, GenerateTextRequest } from "../types/worker_message"; +import { InitProgressCallback } from "./lib/tvm/runtime"; +import { Config } from "./worker"; +export declare class LLMInstance { + config: Config; + tvm: any; + tokenizer: any; + model: any; + spp: any; + processing: boolean; + constructor(config: Config, sentencePieceProcessor: any); + isInitialized(): boolean; + init(cb: InitProgressCallback): Promise; + generate(request: GenerateTextRequest, cb: GenerateTextCallback): Promise; +} +export declare class LLMInstanceScope { + tvm: any; + tokenizer: any; + maxWindowSize: number; + device: any; + vm: any; + encoding: any; + decoding: any; + params: any; + bosTokenId: number; + eosTokenId: number; + fclearKVCaches: any; + kvCache: any; + fcreateCache: any; + logitsOnCPU: any; + kvCacheLength: number; + lastMessageId: string; + constructor(tvm: any, tokenizer: any, maxWindowSize?: number); + init(): Promise; + getTokensFromStart(conversation: Conversation, maxTokens: number): Promise; + getTokens(conversation: Conversation, maxTokens: number): Promise; + generate(request: GenerateTextRequest, cb: GenerateTextCallback): Promise; + dispose(): void; + clearKVCache(): void; + forward(inputs: any, curPos: number): any; + updateLogitsOnCPU(logits: any): void; + sampleTokenFromLogits(logits: any, temperature?: number, top_p?: number): Promise; +} diff --git a/packages/headless/dist/types/src/worker/worker.d.ts b/packages/headless/dist/types/src/worker/worker.d.ts new file mode 100644 index 0000000..2e47936 --- /dev/null +++ b/packages/headless/dist/types/src/worker/worker.d.ts @@ -0,0 +1,19 @@ +declare global { + var importScripts: (...url: string[]) => void; + var sentencepiece: { + sentencePieceProcessor: (url: string) => void; + }; +} +export type Config = { + kvConfig: { + numLayers: number; + shape: number[]; + dtype: string; + }; + wasmUrl: string; + cacheUrl: string; + tokenizerUrl: string; + sentencePieceJsUrl: string; + tvmRuntimeJsUrl: string; + maxWindowSize: number; +}; diff --git a/packages/headless/dist/v4-2119d9d5.js b/packages/headless/dist/v4-2119d9d5.js new file mode 100644 index 0000000..079fc3c --- /dev/null +++ b/packages/headless/dist/v4-2119d9d5.js @@ -0,0 +1,3784 @@ +/****************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ +/* global Reflect, Promise */ + +var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); +}; + +function __extends(d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +} + +var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; + +function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +} + +function __generator(thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +} + +function __spreadArray(to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +} + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/** NodeJS and Web compact layer */ +/** + * Get performance measurement. + */ +function getPerformance() { + return performance; +} + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/** + * Size of common data types. + */ +var SizeOf; +(function (SizeOf) { + SizeOf[SizeOf["U8"] = 1] = "U8"; + SizeOf[SizeOf["U16"] = 2] = "U16"; + SizeOf[SizeOf["I32"] = 4] = "I32"; + SizeOf[SizeOf["I64"] = 8] = "I64"; + SizeOf[SizeOf["F32"] = 4] = "F32"; + SizeOf[SizeOf["F64"] = 8] = "F64"; + SizeOf[SizeOf["TVMValue"] = 8] = "TVMValue"; + SizeOf[SizeOf["DLDataType"] = 4] = "DLDataType"; + SizeOf[SizeOf["DLDevice"] = 8] = "DLDevice"; +})(SizeOf || (SizeOf = {})); +/** + * Argument Type code in TVM FFI. + */ +var ArgTypeCode; +(function (ArgTypeCode) { + ArgTypeCode[ArgTypeCode["Int"] = 0] = "Int"; + ArgTypeCode[ArgTypeCode["UInt"] = 1] = "UInt"; + ArgTypeCode[ArgTypeCode["Float"] = 2] = "Float"; + ArgTypeCode[ArgTypeCode["TVMOpaqueHandle"] = 3] = "TVMOpaqueHandle"; + ArgTypeCode[ArgTypeCode["Null"] = 4] = "Null"; + ArgTypeCode[ArgTypeCode["TVMDataType"] = 5] = "TVMDataType"; + ArgTypeCode[ArgTypeCode["DLDevice"] = 6] = "DLDevice"; + ArgTypeCode[ArgTypeCode["TVMDLTensorHandle"] = 7] = "TVMDLTensorHandle"; + ArgTypeCode[ArgTypeCode["TVMObjectHandle"] = 8] = "TVMObjectHandle"; + ArgTypeCode[ArgTypeCode["TVMModuleHandle"] = 9] = "TVMModuleHandle"; + ArgTypeCode[ArgTypeCode["TVMPackedFuncHandle"] = 10] = "TVMPackedFuncHandle"; + ArgTypeCode[ArgTypeCode["TVMStr"] = 11] = "TVMStr"; + ArgTypeCode[ArgTypeCode["TVMBytes"] = 12] = "TVMBytes"; + ArgTypeCode[ArgTypeCode["TVMNDArrayHandle"] = 13] = "TVMNDArrayHandle"; + ArgTypeCode[ArgTypeCode["TVMObjectRValueRefArg"] = 14] = "TVMObjectRValueRefArg"; +})(ArgTypeCode || (ArgTypeCode = {})); + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/** + * Convert string to Uint8array. + * @param str The string. + * @returns The corresponding Uint8Array. + */ +function StringToUint8Array(str) { + var arr = new Uint8Array(str.length + 1); + for (var i = 0; i < str.length; ++i) { + arr[i] = str.charCodeAt(i); + } + arr[str.length] = 0; + return arr; +} +/** + * Internal assert helper + * @param condition condition The condition to fail. + * @param msg msg The message. + */ +function assert(condition, msg) { + if (!condition) { + throw new Error("AssertError:" + (msg || "")); + } +} + +/** + * Detect library provider from the importObject. + * + * @param importObject The import object. + */ +function detectLibraryProvider(importObject) { + if (importObject["wasmLibraryProvider"] && + importObject["wasmLibraryProvider"]["start"] && + importObject["wasmLibraryProvider"]["imports"] !== undefined) { + var item_1 = importObject; + // create provider so that we capture imports in the provider. + return { + imports: item_1.wasmLibraryProvider.imports, + start: function (inst) { + item_1.wasmLibraryProvider.start(inst); + }, + }; + } + else if (importObject["imports"] && importObject["start"] !== undefined) { + return importObject; + } + else if (importObject["wasiImport"] && importObject["start"] !== undefined) { + // WASI + return { + imports: { + "wasi_snapshot_preview1": importObject["wasiImport"], + }, + start: function (inst) { + importObject["start"](inst); + } + }; + } + else { + return undefined; + } +} +/** + * Environment to impelement most of the JS library functions. + */ +var Environment = /** @class */ (function () { + function Environment(importObject, logger) { + if (importObject === void 0) { importObject = {}; } + if (logger === void 0) { logger = console.log; } + /** + * Maintains a table of FTVMWasmPackedCFunc that the C part + * can call via TVMWasmPackedCFunc. + * + * We maintain a separate table so that we can have un-limited amount + * of functions that do not maps to the address space. + */ + this.packedCFuncTable = [ + undefined, + ]; + /** + * Free table index that can be recycled. + */ + this.packedCFuncTableFreeId = []; + this.logger = logger; + this.libProvider = detectLibraryProvider(importObject); + // get imports from the provider + if (this.libProvider !== undefined) { + this.imports = this.libProvider.imports; + } + else { + this.imports = importObject; + } + // update with more functions + this.imports.env = this.environment(this.imports.env); + } + /** Mark the start of the instance. */ + Environment.prototype.start = function (inst) { + if (this.libProvider !== undefined) { + this.libProvider.start(inst); + } + }; + Environment.prototype.environment = function (initEnv) { + var _this = this; + // default env can be be overriden by libraries. + var defaultEnv = { + "__cxa_thread_atexit": function () { }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + "emscripten_notify_memory_growth": function (index) { } + }; + var wasmPackedCFunc = function (args, typeCodes, nargs, ret, resourceHandle) { + var cfunc = _this.packedCFuncTable[resourceHandle]; + assert(cfunc !== undefined); + return cfunc(args, typeCodes, nargs, ret, resourceHandle); + }; + var wasmPackedCFuncFinalizer = function (resourceHandle) { + _this.packedCFuncTable[resourceHandle] = undefined; + _this.packedCFuncTableFreeId.push(resourceHandle); + }; + var newEnv = { + TVMWasmPackedCFunc: wasmPackedCFunc, + TVMWasmPackedCFuncFinalizer: wasmPackedCFuncFinalizer, + "__console_log": function (msg) { + _this.logger(msg); + } + }; + return Object.assign(defaultEnv, initEnv, newEnv); + }; + return Environment; +}()); + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/** + * Classes to manipulate Wasm memories. + */ +/** + * Wasm Memory wrapper to perform JS side raw memory access. + */ +var Memory = /** @class */ (function () { + function Memory(memory) { + this.wasm32 = true; + this.memory = memory; + this.buffer = this.memory.buffer; + this.viewU8 = new Uint8Array(this.buffer); + this.viewU16 = new Uint16Array(this.buffer); + this.viewI32 = new Int32Array(this.buffer); + this.viewU32 = new Uint32Array(this.buffer); + this.viewF32 = new Float32Array(this.buffer); + this.viewF64 = new Float64Array(this.buffer); + } + Memory.prototype.loadU8 = function (ptr) { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + return this.viewU8[ptr >> 0]; + }; + Memory.prototype.loadU16 = function (ptr) { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + return this.viewU16[ptr >> 1]; + }; + Memory.prototype.loadU32 = function (ptr) { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + return this.viewU32[ptr >> 2]; + }; + Memory.prototype.loadI32 = function (ptr) { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + return this.viewI32[ptr >> 2]; + }; + Memory.prototype.loadI64 = function (ptr) { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + var base = ptr >> 2; + // assumes little endian, for now truncate high. + return this.viewI32[base]; + }; + Memory.prototype.loadF32 = function (ptr) { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + return this.viewF32[ptr >> 2]; + }; + Memory.prototype.loadF64 = function (ptr) { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + return this.viewF64[ptr >> 3]; + }; + Memory.prototype.loadPointer = function (ptr) { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + if (this.wasm32) { + return this.loadU32(ptr); + } + else { + return this.loadI64(ptr); + } + }; + Memory.prototype.loadUSize = function (ptr) { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + if (this.wasm32) { + return this.loadU32(ptr); + } + else { + return this.loadI64(ptr); + } + }; + Memory.prototype.sizeofPtr = function () { + return this.wasm32 ? SizeOf.I32 : SizeOf.I64; + }; + /** + * Load raw bytes from ptr. + * @param ptr The head address + * @param numBytes The number + */ + Memory.prototype.loadRawBytes = function (ptr, numBytes) { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + var result = new Uint8Array(numBytes); + result.set(this.viewU8.slice(ptr, ptr + numBytes)); + return result; + }; + /** + * Load TVMByteArray from ptr. + * + * @param ptr The address of the header. + */ + Memory.prototype.loadTVMBytes = function (ptr) { + var data = this.loadPointer(ptr); + var length = this.loadUSize(ptr + this.sizeofPtr()); + return this.loadRawBytes(data, length); + }; + /** + * Load null-terminated C-string from ptr. + * @param ptr The head address + */ + Memory.prototype.loadCString = function (ptr) { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + // NOTE: the views are still valid for read. + var ret = []; + var ch = 1; + while (ch != 0) { + ch = this.viewU8[ptr]; + if (ch != 0) { + ret.push(String.fromCharCode(ch)); + } + ++ptr; + } + return ret.join(""); + }; + /** + * Store raw bytes to the ptr. + * @param ptr The head address. + * @param bytes The bytes content. + */ + Memory.prototype.storeRawBytes = function (ptr, bytes) { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + this.viewU8.set(bytes, ptr); + }; + /** + * Update memory view after the memory growth. + */ + Memory.prototype.updateViews = function () { + this.buffer = this.memory.buffer; + this.viewU8 = new Uint8Array(this.buffer); + this.viewU16 = new Uint16Array(this.buffer); + this.viewI32 = new Int32Array(this.buffer); + this.viewU32 = new Uint32Array(this.buffer); + this.viewF32 = new Float32Array(this.buffer); + this.viewF64 = new Float64Array(this.buffer); + }; + return Memory; +}()); +/** + * Auxiliary call stack for the FFI calls. + * + * Lifecyle of a call stack. + * - Calls into allocXX to allocate space, mixed with storeXXX to store data. + * - Calls into ptrFromOffset, no further allocation(as ptrFromOffset can change), + * can still call into storeXX + * - Calls into commitToWasmMemory once. + * - reset. + */ +var CachedCallStack = /** @class */ (function () { + function CachedCallStack(memory, allocSpace, freeSpace) { + /** List of temporay arguments that can be disposed during reset. */ + this.tempArgs = []; + this.stackTop = 0; + this.basePtr = 0; + this.addressToSetTargetValue = []; + var initCallStackSize = 128; + this.memory = memory; + this.cAllocSpace = allocSpace; + this.cFreeSpace = freeSpace; + this.buffer = new ArrayBuffer(initCallStackSize); + this.basePtr = this.cAllocSpace(initCallStackSize); + this.viewU8 = new Uint8Array(this.buffer); + this.viewI32 = new Int32Array(this.buffer); + this.viewU32 = new Uint32Array(this.buffer); + this.viewF64 = new Float64Array(this.buffer); + this.updateViews(); + } + CachedCallStack.prototype.dispose = function () { + if (this.basePtr != 0) { + this.cFreeSpace(this.basePtr); + this.basePtr = 0; + } + }; + /** + * Rest the call stack so that it can be reused again. + */ + CachedCallStack.prototype.reset = function () { + this.stackTop = 0; + assert(this.addressToSetTargetValue.length == 0); + while (this.tempArgs.length != 0) { + this.tempArgs.pop().dispose(); + } + }; + /** + * Commit all the cached data to WasmMemory. + * This function can only be called once. + * No further store function should be called. + * + * @param nbytes Number of bytes to be stored. + */ + CachedCallStack.prototype.commitToWasmMemory = function (nbytes) { + if (nbytes === void 0) { nbytes = this.stackTop; } + // commit all pointer values. + while (this.addressToSetTargetValue.length != 0) { + var _a = this.addressToSetTargetValue.pop(), targetOffset = _a[0], valueOffset = _a[1]; + this.storePtr(targetOffset, this.ptrFromOffset(valueOffset)); + } + this.memory.storeRawBytes(this.basePtr, this.viewU8.slice(0, nbytes)); + }; + /** + * Allocate space by number of bytes + * @param nbytes Number of bytes. + * @note This function always allocate space that aligns to 64bit. + */ + CachedCallStack.prototype.allocRawBytes = function (nbytes) { + // always aligns to 64bit + nbytes = ((nbytes + 7) >> 3) << 3; + if (this.stackTop + nbytes > this.buffer.byteLength) { + var newSize = Math.max(this.buffer.byteLength * 2, this.stackTop + nbytes); + var oldU8 = this.viewU8; + this.buffer = new ArrayBuffer(newSize); + this.updateViews(); + this.viewU8.set(oldU8); + if (this.basePtr != 0) { + this.cFreeSpace(this.basePtr); + } + this.basePtr = this.cAllocSpace(newSize); + } + var retOffset = this.stackTop; + this.stackTop += nbytes; + return retOffset; + }; + /** + * Allocate space for pointers. + * @param count Number of pointers. + * @returns The allocated pointer array. + */ + CachedCallStack.prototype.allocPtrArray = function (count) { + return this.allocRawBytes(this.memory.sizeofPtr() * count); + }; + /** + * Get the real pointer from offset values. + * Note that the returned value becomes obsolete if alloc is called on the stack. + * @param offset The allocated offset. + */ + CachedCallStack.prototype.ptrFromOffset = function (offset) { + return this.basePtr + offset; + }; + // Store APIs + CachedCallStack.prototype.storePtr = function (offset, value) { + if (this.memory.wasm32) { + this.storeU32(offset, value); + } + else { + this.storeI64(offset, value); + } + }; + CachedCallStack.prototype.storeUSize = function (offset, value) { + if (this.memory.wasm32) { + this.storeU32(offset, value); + } + else { + this.storeI64(offset, value); + } + }; + CachedCallStack.prototype.storeI32 = function (offset, value) { + this.viewI32[offset >> 2] = value; + }; + CachedCallStack.prototype.storeU32 = function (offset, value) { + this.viewU32[offset >> 2] = value; + }; + CachedCallStack.prototype.storeI64 = function (offset, value) { + // For now, just store as 32bit + // NOTE: wasm always uses little endian. + var low = value & 0xffffffff; + var base = offset >> 2; + this.viewI32[base] = low; + this.viewI32[base + 1] = 0; + }; + CachedCallStack.prototype.storeF64 = function (offset, value) { + this.viewF64[offset >> 3] = value; + }; + CachedCallStack.prototype.storeRawBytes = function (offset, bytes) { + this.viewU8.set(bytes, offset); + }; + /** + * Allocate then set C-String pointer to the offset. + * This function will call into allocBytes to allocate necessary data. + * The address won't be set immediately(because the possible change of basePtr) + * and will be filled when we commit the data. + * + * @param offset The offset to set ot data pointer. + * @param data The string content. + */ + CachedCallStack.prototype.allocThenSetArgString = function (offset, data) { + var strOffset = this.allocRawBytes(data.length + 1); + this.storeRawBytes(strOffset, StringToUint8Array(data)); + this.addressToSetTargetValue.push([offset, strOffset]); + }; + /** + * Allocate then set the argument location with a TVMByteArray. + * Allocate new temporary space for bytes. + * + * @param offset The offset to set ot data pointer. + * @param data The string content. + */ + CachedCallStack.prototype.allocThenSetArgBytes = function (offset, data) { + // Note: size of size_t equals sizeof ptr. + var headerOffset = this.allocRawBytes(this.memory.sizeofPtr() * 2); + var dataOffset = this.allocRawBytes(data.length); + this.storeRawBytes(dataOffset, data); + this.storeUSize(headerOffset + this.memory.sizeofPtr(), data.length); + this.addressToSetTargetValue.push([offset, headerOffset]); + this.addressToSetTargetValue.push([headerOffset, dataOffset]); + }; + /** + * Update internal cache views. + */ + CachedCallStack.prototype.updateViews = function () { + this.viewU8 = new Uint8Array(this.buffer); + this.viewI32 = new Int32Array(this.buffer); + this.viewU32 = new Uint32Array(this.buffer); + this.viewF64 = new Float64Array(this.buffer); + }; + return CachedCallStack; +}()); + +/** + * DetectGPU device in the environment. + */ +function detectGPUDevice() { + return __awaiter(this, void 0, void 0, function () { + var adapter, computeMB, requiedMaxBufferSize, requiredMaxStorageBufferBindingSize, requiredMaxComputeWorkgroupStorageSize, adapterInfo, device; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!(typeof navigator !== "undefined" && navigator.gpu !== undefined)) return [3 /*break*/, 4]; + return [4 /*yield*/, navigator.gpu.requestAdapter({ "powerPreference": "high-performance" })]; + case 1: + adapter = _a.sent(); + if (adapter == null) { + throw Error("Cannot find adapter that matches the request"); + } + computeMB = function (value) { + return Math.ceil(value / (1 << 20)) + "MB"; + }; + requiedMaxBufferSize = 1 << 30; + if (requiedMaxBufferSize > adapter.limits.maxBufferSize) { + throw Error("Cannot initialize runtime because of requested maxBufferSize " + + "exceeds limit. requested=".concat(computeMB(requiedMaxBufferSize), ", ") + + "limit=".concat(computeMB(adapter.limits.maxBufferSize), ". ") + + "This error may be caused by an older version of the browser (e.g. Chrome 112). " + + "You can try to upgrade your browser to Chrome 113 or later."); + } + requiredMaxStorageBufferBindingSize = 1 << 30; + if (requiredMaxStorageBufferBindingSize > adapter.limits.maxStorageBufferBindingSize) { + throw Error("Cannot initialize runtime because of requested maxStorageBufferBindingSize " + + "exceeds limit. requested=".concat(computeMB(requiredMaxStorageBufferBindingSize), ", ") + + "limit=".concat(computeMB(adapter.limits.maxStorageBufferBindingSize), ". ")); + } + requiredMaxComputeWorkgroupStorageSize = 32 << 10; + if (requiredMaxComputeWorkgroupStorageSize > adapter.limits.maxComputeWorkgroupStorageSize) { + throw Error("Cannot initialize runtime because of requested maxComputeWorkgroupStorageSize " + + "exceeds limit. requested=".concat(requiredMaxComputeWorkgroupStorageSize, ", ") + + "limit=".concat(adapter.limits.maxComputeWorkgroupStorageSize, ". ")); + } + return [4 /*yield*/, adapter.requestAdapterInfo()]; + case 2: + adapterInfo = _a.sent(); + return [4 /*yield*/, adapter.requestDevice({ + requiredLimits: { + maxBufferSize: requiedMaxBufferSize, + maxStorageBufferBindingSize: requiredMaxStorageBufferBindingSize, + maxComputeWorkgroupStorageSize: requiredMaxComputeWorkgroupStorageSize, + } + })]; + case 3: + device = _a.sent(); + return [2 /*return*/, { + adapter: adapter, + adapterInfo: adapterInfo, + device: device + }]; + case 4: return [2 /*return*/, undefined]; + } + }); + }); +} +var canvasRenderWGSL = "\n@group(0) @binding(0) var my_sampler : sampler;\n@group(0) @binding(1) var my_texture : texture_2d;\n\nstruct VertexOutput {\n @builtin(position) position : vec4,\n @location(0) uv : vec2,\n}\n\n@vertex\nfn vertex_main(@builtin(vertex_index) vidx : u32) -> VertexOutput {\n const pos = array(\n vec2( 1.0, 1.0),\n vec2( 1.0, -1.0),\n vec2(-1.0, -1.0),\n vec2( 1.0, 1.0),\n vec2(-1.0, -1.0),\n vec2(-1.0, 1.0),\n );\n\n const uv = array(\n vec2(1.0, 0.0),\n vec2(1.0, 1.0),\n vec2(0.0, 1.0),\n vec2(1.0, 0.0),\n vec2(0.0, 1.0),\n vec2(0.0, 0.0),\n );\n\n var output : VertexOutput;\n output.position = vec4(pos[vidx], 0.0, 1.0);\n output.uv = uv[vidx];\n return output;\n}\n\n@fragment\nfn fragment_main(@location(0) uv : vec2) -> @location(0) vec4 {\n return textureSample(my_texture, my_sampler, uv);\n}\n\n@fragment\nfn fragment_clear(@location(0) uv : vec2) -> @location(0) vec4 {\n return vec4(1.0, 1.0, 1.0, 1.0);\n}\n"; +var CanvaRenderManager = /** @class */ (function () { + function CanvaRenderManager(device, canvas) { + this.device = device; + var ctx = canvas.getContext("webgpu"); + if (ctx == null) { + throw Error("Cannot bind WebGPU context"); + } + // @ts-ignore + this.canvasContext = ctx; + this.canvasTextureFormat = navigator.gpu.getPreferredCanvasFormat(); + this.canvasContext.configure({ + device: this.device, + format: this.canvasTextureFormat, + alphaMode: "opaque", + }); + this.renderPipeline = device.createRenderPipeline({ + layout: "auto", + vertex: { + module: device.createShaderModule({ + code: canvasRenderWGSL, + }), + entryPoint: "vertex_main", + }, + fragment: { + module: device.createShaderModule({ + code: canvasRenderWGSL, + }), + entryPoint: "fragment_main", + targets: [{ + format: this.canvasTextureFormat, + }], + }, + primitive: { + topology: "triangle-list", + }, + }); + this.clearPipeline = device.createRenderPipeline({ + layout: "auto", + vertex: { + module: device.createShaderModule({ + code: canvasRenderWGSL, + }), + entryPoint: "vertex_main", + }, + fragment: { + module: device.createShaderModule({ + code: canvasRenderWGSL, + }), + entryPoint: "fragment_clear", + targets: [{ + format: this.canvasTextureFormat, + }], + }, + primitive: { + topology: "triangle-list", + }, + }); + this.renderSampler = device.createSampler({ + magFilter: "linear", + minFilter: "linear", + }); + // staging texture always be in RGBA + this.stagingTexture = device.createTexture({ + size: [canvas.height, canvas.width, 1], + format: "rgba8unorm", + usage: GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, + }); + } + CanvaRenderManager.prototype.clear = function () { + var commandEncoder = this.device.createCommandEncoder(); + var passEncoder = commandEncoder.beginRenderPass({ + //@ts-ignore + colorAttachments: [ + { + view: this.canvasContext.getCurrentTexture().createView(), + clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, + loadOp: "clear", + storeOp: "store", + }, + ], + }); + passEncoder.setPipeline(this.clearPipeline); + var renderBindingGroup = this.device.createBindGroup({ + layout: this.renderPipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: this.renderSampler }, + { binding: 1, resource: this.stagingTexture.createView() }, + ], + }); + passEncoder.setBindGroup(0, renderBindingGroup); + passEncoder.draw(6, 1, 0, 0); + passEncoder.end(); + this.device.queue.submit([commandEncoder.finish()]); + }; + CanvaRenderManager.prototype.draw = function (buffer, height, width) { + // resize the staging texture + if (height != this.stagingTexture.height || width != this.stagingTexture.width) { + this.stagingTexture.destroy(); + this.stagingTexture = this.device.createTexture({ + size: [height, width, 1], + format: "rgba8unorm", + usage: GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, + }); + } + var commandEncoder = this.device.createCommandEncoder(); + commandEncoder.copyBufferToTexture({ + buffer: buffer, + offset: 0, + bytesPerRow: this.stagingTexture.width * 4 + }, { + texture: this.stagingTexture + }, { + width: this.stagingTexture.width, + height: this.stagingTexture.height + }); + var passEncoder = commandEncoder.beginRenderPass({ + //@ts-ignore + colorAttachments: [ + { + view: this.canvasContext.getCurrentTexture().createView(), + clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, + loadOp: "clear", + storeOp: "store", + }, + ], + }); + passEncoder.setPipeline(this.renderPipeline); + var renderBindingGroup = this.device.createBindGroup({ + layout: this.renderPipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: this.renderSampler }, + { binding: 1, resource: this.stagingTexture.createView() }, + ], + }); + passEncoder.setBindGroup(0, renderBindingGroup); + passEncoder.draw(6, 1, 0, 0); + passEncoder.end(); + this.device.queue.submit([commandEncoder.finish()]); + }; + CanvaRenderManager.prototype.dispose = function () { + this.stagingTexture.destroy(); + }; + return CanvaRenderManager; +}()); +/** + * WebGPU context + * Manages all the webgpu resources here. + */ +var WebGPUContext = /** @class */ (function () { + function WebGPUContext(memory, device) { + // internal data + this.bufferTable = [undefined]; + this.bufferTableFreeId = []; + this.podArgStagingBuffers = []; + this.canvasRenderManager = undefined; + // number of pod arg staging buffers + this.maxNumPodArgsStagingBuffers = 2; + // flags for debugging + // stats of the runtime. + // peak allocation + this.peakAllocatedBytes = 0; + // current allocation + this.currAllocatedBytes = 0; + // all allocation(ignoring free) + this.allAllocatedBytes = 0; + // shader submit counter + this.shaderSubmitCounter = 0; + // limite number of shaders to be submitted, useful for debugging, default to -1 + this.debugShaderSubmitLimit = -1; + // log and sync each step + this.debugLogFinish = false; + this.memory = memory; + this.device = device; + } + /** + * Dispose context. + */ + WebGPUContext.prototype.dispose = function () { + var _a, _b, _c; + (_a = this.canvasRenderManager) === null || _a === void 0 ? void 0 : _a.dispose(); + this.bufferTableFreeId = []; + while (this.bufferTable.length != 0) { + (_b = this.bufferTable.pop()) === null || _b === void 0 ? void 0 : _b.destroy(); + } + while (this.podArgStagingBuffers.length != 0) { + (_c = this.podArgStagingBuffers.pop()) === null || _c === void 0 ? void 0 : _c.destroy(); + } + this.device.destroy(); + }; + /** + * Wait for all pending GPU tasks to complete + */ + WebGPUContext.prototype.sync = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.device.queue.onSubmittedWorkDone()]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + /** + * Obtain the runtime information in readable format. + */ + WebGPUContext.prototype.runtimeStatsText = function () { + var info = "peak-memory=" + Math.ceil(this.peakAllocatedBytes / (1 << 20)) + " MB"; + info += ", all-memory=" + Math.ceil(this.allAllocatedBytes / (1 << 20)) + " MB"; + info += ", shader-submissions=" + this.shaderSubmitCounter; + return info; + }; + /** + * Draw image from data in storage buffer. + * @param ptr The GPU ptr + * @param height The height of the image. + * @param width The width of the image. + */ + WebGPUContext.prototype.drawImageFromBuffer = function (ptr, height, width) { + if (this.canvasRenderManager == undefined) { + throw Error("Do not have a canvas context, call bindCanvas first"); + } + this.canvasRenderManager.draw(this.gpuBufferFromPtr(ptr), height, width); + }; + /** + * Copy raw bytes into buffer ptr. + * + * @param rawBytes The raw bytes + * @param toPtr The target gpu buffer ptr + * @param toOffset The beginning offset + * @param nbytes Number of bytes + */ + WebGPUContext.prototype.copyRawBytesToBuffer = function (rawBytes, toPtr, toOffset, nbytes) { + // Perhaps it would be more useful to use a staging buffer? + this.device.queue.writeBuffer(this.gpuBufferFromPtr(toPtr), toOffset, rawBytes, 0, nbytes); + }; + /** + * Clear canvas + */ + WebGPUContext.prototype.clearCanvas = function () { + var _a; + (_a = this.canvasRenderManager) === null || _a === void 0 ? void 0 : _a.clear(); + }; + /** + * Bind a canvas element to the runtime. + * @param canvas The HTML canvas/ + */ + WebGPUContext.prototype.bindCanvas = function (canvas) { + this.canvasRenderManager = new CanvaRenderManager(this.device, canvas); + }; + /** + * Create a PackedFunc that runs the given shader + * via createComputePipeline + * + * @param info The function information already parsed as a record. + * @param code The shader data(in WGSL) + * @returns The shader + */ + WebGPUContext.prototype.createShader = function (finfo, code) { + return this.createShadeInternal(finfo, code, false); + }; + /** + * Create a PackedFunc that runs the given shader asynchrously + * via createComputePipelineAsync + * + * @param info The function information already parsed as a record. + * @param code The shader data(in WGSL) + * @returns The shader + */ + WebGPUContext.prototype.createShaderAsync = function (finfo, code) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.createShadeInternal(finfo, code, true)]; + case 1: return [2 /*return*/, _a.sent()]; + } + }); + }); + }; + /** + * Get the pod arg staging buffer + * \param nbytes The minimum size. + * \return The allocated buffer + */ + WebGPUContext.prototype.getPodArgsBuffer = function (nbytes) { + var buffer = undefined; + if (this.podArgStagingBuffers.length >= this.maxNumPodArgsStagingBuffers) { + buffer = this.podArgStagingBuffers.shift(); + } + // minimum of 16 bytes + var allocSize = 16; + if (buffer !== undefined) { + allocSize = buffer.size; + if (buffer.size < nbytes) { + buffer.destroy(); + buffer = undefined; + } + } + while (allocSize < nbytes) { + allocSize *= 2; + } + if (buffer == undefined) { + // create uniform buffer + buffer = this.device.createBuffer({ + size: allocSize, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + } + assert(nbytes <= buffer.size); + return buffer; + }; + /** + * Internal impl of createShader for both async and sync mode. + * + * @param info The function information already parsed as a record. + * @param code The shader data(in WGSL) + * @param asyncMode Whether use async mode. + * @returns The shader function or promise of shader func. + */ + WebGPUContext.prototype.createShadeInternal = function (finfo, code, asyncMode) { + var _this = this; + var dispatchToDim = []; + var paramWriteAccess = []; + for (var i = 0; i < finfo.launch_param_tags.length; ++i) { + var tag = finfo.launch_param_tags[i]; + if (tag.startsWith("blockIdx.")) { + var target = tag.charCodeAt(tag.length - 1) - ("x".charCodeAt(0)); + assert(target >= 0 && target < 3); + dispatchToDim.push(target); + } + else if (tag.startsWith("threadIdx.")) { + var target = tag.charCodeAt(tag.length - 1) - ("x".charCodeAt(0)); + assert(target >= 0 && target < 3); + dispatchToDim.push(target + 3); + } + else if (tag.startsWith("paramWriteAccess:")) { + paramWriteAccess = JSON.parse(tag.substring(17)); + } + else { + throw new Error("Cannot handle thread_axis " + tag); + } + } + var layoutEntries = []; + var bufferArgIndices = []; + var podArgIndices = []; + for (var i = 0; i < finfo.arg_types.length; ++i) { + var dtype = finfo.arg_types[i]; + if (dtype == "handle") { + layoutEntries.push({ + binding: bufferArgIndices.length, + visibility: GPUShaderStage.COMPUTE, + buffer: { + type: paramWriteAccess[bufferArgIndices.length] ? "storage" : "read-only-storage" + } + }); + bufferArgIndices.push(i); + } + else if (dtype.startsWith("int") || dtype.startsWith("uint") || dtype.startsWith("float")) { + podArgIndices.push(i); + } + else { + throw new Error("Cannot handle argument type " + dtype + " in WebGPU shader"); + } + } + assert(paramWriteAccess.length == bufferArgIndices.length); + // POD arguments are pass in the end + layoutEntries.push({ + binding: bufferArgIndices.length, + visibility: GPUShaderStage.COMPUTE, + buffer: { + type: "uniform" + } + }); + var bindGroupLayout = this.device.createBindGroupLayout({ + entries: layoutEntries + }); + var pipelineLayout = this.device.createPipelineLayout({ + bindGroupLayouts: [bindGroupLayout] + }); + // Function to create the pipeline. + var createShaderFunc = function (pipeline) { + var submitShader = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + if (_this.debugShaderSubmitLimit != -1 && + _this.shaderSubmitCounter >= _this.debugShaderSubmitLimit) { + _this.shaderSubmitCounter += 1; + return; + } + var commandEncoder = _this.device.createCommandEncoder(); + var compute = commandEncoder.beginComputePass(); + compute.setPipeline(pipeline); + var bindGroupEntries = []; + var numBufferOrPodArgs = bufferArgIndices.length + podArgIndices.length; + assert(args.length == numBufferOrPodArgs + dispatchToDim.length); + var workDim = [1, 1, 1, 1, 1, 1]; + for (var i = 0; i < dispatchToDim.length; ++i) { + workDim[dispatchToDim[i]] = args[numBufferOrPodArgs + i]; + } + // get around 65535 restriction of blockIdx.x + if (workDim[2] != 1) { + throw Error("WebGPU: blockIdx.z is reserved for internal use"); + } + var packDimX = workDim[0]; + // spread thinsg out into blockIdx.z + if (workDim[0] >= (1 << 16)) { + var wl_x = workDim[0]; + var wl_z = workDim[2]; + while (wl_x >= (1 << 16)) { + if (wl_x % 2 == 0) { + wl_x = wl_x / 2; + } + else { + // pad up + wl_x = (wl_x + 1) / 2; + } + wl_z *= 2; + } + workDim[0] = wl_x; + workDim[2] = wl_z; + assert(wl_x * wl_z >= packDimX); + } + for (var i = 0; i < bufferArgIndices.length; ++i) { + bindGroupEntries.push({ + binding: i, + resource: { + buffer: _this.gpuBufferFromPtr(args[bufferArgIndices[i]]) + } + }); + } + // push pod buffer + var sizeOfI32 = 4; + var podArgBuffer = _this.getPodArgsBuffer((podArgIndices.length + 1) * sizeOfI32); + var i32View = new Int32Array(podArgIndices.length + 1); + var u32View = new Uint32Array(i32View.buffer); + var f32View = new Float32Array(i32View.buffer); + for (var i = 0; i < podArgIndices.length; ++i) { + var value = args[podArgIndices[i]]; + var dtype = finfo.arg_types[podArgIndices[i]]; + if (dtype.startsWith("int")) { + i32View[i] = value; + } + else if (dtype.startsWith("uint")) { + u32View[i] = value; + } + else if (dtype.startsWith("float")) { + f32View[i] = value; + } + else { + throw Error("Unknown pod dtype " + dtype); + } + } + // always pass in dim z launching grid size in + u32View[podArgIndices.length] = packDimX; + _this.device.queue.writeBuffer(podArgBuffer, 0, i32View.buffer); + bindGroupEntries.push({ + binding: bufferArgIndices.length, + resource: { + buffer: podArgBuffer, + size: i32View.buffer.byteLength + } + }); + compute.setBindGroup(0, _this.device.createBindGroup({ + layout: bindGroupLayout, + entries: bindGroupEntries + })); + compute.dispatchWorkgroups(workDim[0], workDim[1], workDim[2]); + compute.end(); + var command = commandEncoder.finish(); + _this.device.queue.submit([command]); + if (_this.debugLogFinish) { + _this.shaderSubmitCounter; + _this.device.queue.onSubmittedWorkDone().then(function () { + // console.log("[" + currCounter + "][Debug] finish shader" + finfo.name); + }); + } + _this.shaderSubmitCounter += 1; + }; + return submitShader; + }; + var shaderModule = this.device.createShaderModule({ + code: code, + hints: { + main: { + layout: pipelineLayout + } + } + }); + if (asyncMode) { + return this.device.createComputePipelineAsync({ + layout: pipelineLayout, + compute: { + module: shaderModule, + entryPoint: finfo.name + } + }).then(function (pipeline) { + return createShaderFunc(pipeline); + }); + } + else { + var pipeline = this.device.createComputePipeline({ + layout: pipelineLayout, + compute: { + module: shaderModule, + entryPoint: finfo.name + } + }); + return createShaderFunc(pipeline); + } + }; + /** + * Get the device API according to its name + * @param The name of the API. + * @returns The corresponding device api. + */ + WebGPUContext.prototype.getDeviceAPI = function (name) { + var _this = this; + if (name == "deviceAllocDataSpace") { + return function (nbytes) { + return _this.deviceAllocDataSpace(nbytes); + }; + } + else if (name == "deviceFreeDataSpace") { + return function (ptr) { + return _this.deviceFreeDataSpace(ptr); + }; + } + else if (name == "deviceCopyToGPU") { + return function (from, to, toOffset, nbytes) { + _this.deviceCopyToGPU(from, to, toOffset, nbytes); + }; + } + else if (name == "deviceCopyFromGPU") { + return function (from, fromOffset, to, nbytes) { + _this.deviceCopyFromGPU(from, fromOffset, to, nbytes); + }; + } + else if (name == "deviceCopyWithinGPU") { + return function (from, fromOffset, to, toOffset, nbytes) { + _this.deviceCopyWithinGPU(from, fromOffset, to, toOffset, nbytes); + }; + } + else { + throw new Error("Unknown DeviceAPI function " + name); + } + }; + // DeviceAPI + WebGPUContext.prototype.deviceAllocDataSpace = function (nbytes) { + // allocate 0 bytes buffer as 1 bytes buffer. + if (nbytes == 0) { + nbytes = 1; + } + var buffer = this.device.createBuffer({ + size: nbytes, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, + }); + this.currAllocatedBytes += nbytes; + this.allAllocatedBytes += nbytes; + if (this.currAllocatedBytes > this.peakAllocatedBytes) { + this.peakAllocatedBytes = this.currAllocatedBytes; + } + var ptr = this.attachToBufferTable(buffer); + return ptr; + }; + WebGPUContext.prototype.deviceFreeDataSpace = function (ptr) { + var idx = ptr; + var buffer = this.bufferTable[idx]; + this.bufferTable[idx] = undefined; + assert(buffer !== undefined); + this.bufferTableFreeId.push(idx); + this.currAllocatedBytes -= buffer.size; + buffer.destroy(); + }; + WebGPUContext.prototype.deviceCopyToGPU = function (from, to, toOffset, nbytes) { + // Perhaps it would be more useful to use a staging buffer? + var rawBytes = this.memory.loadRawBytes(from, nbytes); + this.device.queue.writeBuffer(this.gpuBufferFromPtr(to), toOffset, rawBytes, 0, nbytes); + }; + WebGPUContext.prototype.deviceCopyFromGPU = function (from, fromOffset, to, nbytes) { + var _this = this; + // Perhaps it would be more useful to resuse a staging buffer? + var gpuTemp = this.device.createBuffer({ + size: nbytes, + usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, + }); + var copyEncoder = this.device.createCommandEncoder(); + copyEncoder.copyBufferToBuffer(this.gpuBufferFromPtr(from), fromOffset, gpuTemp, 0, nbytes); + var copyCommands = copyEncoder.finish(); + this.device.queue.submit([copyCommands]); + gpuTemp.mapAsync(GPUMapMode.READ).then(function () { + var data = gpuTemp.getMappedRange(); + _this.memory.storeRawBytes(to, new Uint8Array(data)); + gpuTemp.destroy(); + }); + }; + WebGPUContext.prototype.deviceCopyWithinGPU = function (from, fromOffset, to, toOffset, nbytes) { + var copyEncoder = this.device.createCommandEncoder(); + copyEncoder.copyBufferToBuffer(this.gpuBufferFromPtr(from), fromOffset, this.gpuBufferFromPtr(to), toOffset, nbytes); + var copyCommands = copyEncoder.finish(); + this.device.queue.submit([copyCommands]); + }; + WebGPUContext.prototype.gpuBufferFromPtr = function (ptr) { + var buffer = this.bufferTable[ptr]; + assert(buffer !== undefined); + return buffer; + }; + WebGPUContext.prototype.attachToBufferTable = function (buffer) { + if (this.bufferTableFreeId.length != 0) { + var idx = this.bufferTableFreeId.pop(); + this.bufferTable[idx] = buffer; + return idx; + } + else { + var idx = this.bufferTable.length; + this.bufferTable.push(buffer); + return idx; + } + }; + return WebGPUContext; +}()); + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/** + * @internal + * FFI Library wrapper, maintains most runtime states. + */ +var FFILibrary = /** @class */ (function () { + function FFILibrary(wasmInstance, imports) { + this.recycledCallStacks = []; + this.wasmInstance = wasmInstance; + this.memory = new Memory(this.detectWasmMemory(this.wasmInstance, imports)); + assert(this.wasmInstance.exports !== undefined, "Expect the library module contains exports"); + this.exports = this.wasmInstance.exports; + this.wasm32 = this.memory.wasm32; + this.validateInstance(); + } + FFILibrary.prototype.dispose = function () { + var _a; + while (this.recycledCallStacks.length != 0) { + this.recycledCallStacks.pop().dispose(); + } + (_a = this.webGPUContext) === null || _a === void 0 ? void 0 : _a.dispose(); + }; + FFILibrary.prototype.sizeofPtr = function () { + return this.memory.sizeofPtr(); + }; + FFILibrary.prototype.checkCall = function (code) { + if (code != 0) { + var msgPtr = this.exports + .TVMGetLastError(); + throw new Error("TVMError: " + this.memory.loadCString(msgPtr)); + } + }; + FFILibrary.prototype.getOrAllocCallStack = function () { + if (this.recycledCallStacks.length != 0) { + return this.recycledCallStacks.pop(); + } + return new CachedCallStack(this.memory, this.exports.TVMWasmAllocSpace, this.exports.TVMWasmFreeSpace); + }; + FFILibrary.prototype.recycleCallStack = function (callstack) { + callstack.reset(); + this.recycledCallStacks.push(callstack); + }; + FFILibrary.prototype.validateInstance = function () { + this.checkExports(["TVMWasmAllocSpace", "TVMWasmFreeSpace", "TVMFuncFree"]); + }; + FFILibrary.prototype.checkExports = function (funcNames) { + var missList = []; + for (var _i = 0, funcNames_1 = funcNames; _i < funcNames_1.length; _i++) { + var name_1 = funcNames_1[_i]; + var f = this.exports[name_1]; + if (!(f instanceof Function)) { + missList.push(name_1); + } + } + if (missList.length != 0) { + throw new Error("Cannot find " + missList + " in exports"); + } + }; + FFILibrary.prototype.detectWasmMemory = function (instance, imports) { + if (instance.exports.memory instanceof WebAssembly.Memory) { + return instance.exports.memory; + } + if (imports.env && imports.env.memory instanceof WebAssembly.Memory) { + return imports.env.memory; + } + throw new Error("Cannt detect wasm memory from imports " + + imports + + " or exports" + + instance.exports); + }; + return FFILibrary; +}()); +/** + * @internal + * Manages extra runtime context for the runtime. + */ +var RuntimeContext = /** @class */ (function () { + function RuntimeContext(getGlobalFunc) { + this.autoDisposeScope = []; + this.arrayGetItem = getGlobalFunc("runtime.ArrayGetItem"); + this.arrayGetSize = getGlobalFunc("runtime.ArraySize"); + this.arrayMake = getGlobalFunc("runtime.Array"); + this.getSysLib = getGlobalFunc("runtime.SystemLib"); + this.arrayCacheGet = getGlobalFunc("vm.builtin.ndarray_cache.get"); + this.arrayCacheRemove = getGlobalFunc("vm.builtin.ndarray_cache.remove"); + this.arrayCacheUpdate = getGlobalFunc("vm.builtin.ndarray_cache.update"); + this.arrayCacheClear = getGlobalFunc("vm.builtin.ndarray_cache.clear"); + this.arrayDecodeStorage = getGlobalFunc("tvmjs.array.decode_storage"); + this.paramModuleFromCache = getGlobalFunc("vm.builtin.param_module_from_cache"); + this.makeShapeTuple = getGlobalFunc("runtime.ShapeTuple"); + this.ndarrayCreateView = getGlobalFunc("runtime.TVMArrayCreateView"); + this.sampleTopPFromLogits = getGlobalFunc("vm.builtin.sample_top_p_from_logits"); + } + RuntimeContext.prototype.dispose = function () { + // call array cache clear to clear all cached items + this.arrayCacheClear.dispose(); + this.arrayGetItem.dispose(); + this.arrayGetSize.dispose(); + this.arrayMake.dispose(); + this.arrayCacheGet.dispose(); + this.arrayCacheRemove.dispose(); + this.arrayCacheUpdate.dispose(); + this.arrayCacheClear.dispose(); + this.arrayDecodeStorage.dispose(); + this.paramModuleFromCache.dispose(); + this.makeShapeTuple.dispose(); + this.ndarrayCreateView.dispose(); + this.sampleTopPFromLogits.dispose(); + }; + RuntimeContext.prototype.beginScope = function () { + this.autoDisposeScope.push([]); + }; + RuntimeContext.prototype.endScope = function () { + if (this.autoDisposeScope.length == 0) { + throw Error("tvm.endScope called when the stack is empty."); + } + // automatically dispose all the tracked values in the current scope. + var currScope = this.autoDisposeScope.pop(); + for (var i = 0; i < currScope.length; ++i) { + var val = currScope[i]; + if (val !== undefined) { + val.dispose(); + } + } + }; + /** + * Track object for dispose in current scope. + * + * @param obj The object to be tracked. + * @returns the same object. + * @note This function only needs to be called for raw system C API values. + * The return value of PackedFunc will be automatically tracked. + */ + RuntimeContext.prototype.attachToCurrentScope = function (obj) { + if (this.autoDisposeScope.length == 0) { + throw Error("Must call beginScope to use functions that returns TVM objects"); + } + var currScope = this.autoDisposeScope[this.autoDisposeScope.length - 1]; + currScope.push(obj); + return obj; + }; + RuntimeContext.prototype.moveToParentScope = function (obj) { + this.detachFromCurrentScope(obj); + if (this.autoDisposeScope.length < 2) { + throw Error("moveToParentScope: Parent scope do not exist"); + } + var parentScope = this.autoDisposeScope[this.autoDisposeScope.length - 2]; + parentScope.push(obj); + return obj; + }; + RuntimeContext.prototype.detachFromCurrentScope = function (obj) { + var currScope = this.autoDisposeScope[this.autoDisposeScope.length - 1]; + var occurance = 0; + for (var i = 0; i < currScope.length; ++i) { + if (currScope[i] === obj) { + occurance += 1; + currScope[i] = undefined; + } + } + if (occurance == 0) { + throw Error("Cannot find obj in the current auto conversion pool"); + } + if (occurance > 1) { + throw Error("Value attached to scope multiple times"); + } + return obj; + }; + return RuntimeContext; +}()); +/** + * A typed scalar constant used to represent a typed number + * argument to PackedFunc calls. + */ +var Scalar = /** @class */ (function () { + function Scalar(value, dtype) { + this.value = value; + this.dtype = dtype; + } + return Scalar; +}()); +/** + * Cell holds the PackedFunc object. + */ +var PackedFuncCell = /** @class */ (function () { + function PackedFuncCell(handle, lib) { + this.handle = handle; + this.lib = lib; + } + PackedFuncCell.prototype.dispose = function () { + if (this.handle != 0) { + this.lib.checkCall(this.lib.exports.TVMFuncFree(this.handle)); + this.handle = 0; + } + }; + PackedFuncCell.prototype.getHandle = function (requireNotNull) { + if (requireNotNull === void 0) { requireNotNull = true; } + if (requireNotNull && this.handle == 0) { + throw Error("PackedFunc has already been disposed"); + } + return this.handle; + }; + return PackedFuncCell; +}()); +var DeviceEnumToStr = { + 1: "cpu", + 2: "cuda", + 4: "opencl", + 8: "metal", + 15: "webgpu" +}; +var DeviceStrToEnum = { + cpu: 1, + cuda: 2, + cl: 4, + opencl: 4, + vulkan: 7, + metal: 8, + webgpu: 15 +}; +/** + * Represent a runtime context where a NDArray can reside. + */ +var DLDevice = /** @class */ (function () { + function DLDevice(deviceType, deviceId, lib) { + var tp = typeof deviceType; + if (tp == "string") { + this.deviceType = DeviceStrToEnum[deviceType]; + if (this.deviceType == undefined) { + throw new Error("Cannot recogonize deviceType " + deviceType); + } + } + else if (tp == "number") { + this.deviceType = deviceType; + } + else { + throw new Error("Cannot take type " + tp + " as deviceType"); + } + this.deviceId = deviceId; + this.lib = lib; + } + /** + * Synchronize the device + */ + DLDevice.prototype.sync = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!(this.deviceType == DeviceStrToEnum.webgpu)) return [3 /*break*/, 2]; + assert(this.lib.webGPUContext !== undefined); + return [4 /*yield*/, this.lib.webGPUContext.sync()]; + case 1: + _a.sent(); + _a.label = 2; + case 2: return [2 /*return*/]; + } + }); + }); + }; + DLDevice.prototype.toString = function () { + return (DeviceEnumToStr[this.deviceType] + "(" + this.deviceId.toString() + ")"); + }; + return DLDevice; +}()); +/** + * The data type code in DLDataType + */ +var DLDataTypeCode; +(function (DLDataTypeCode) { + DLDataTypeCode[DLDataTypeCode["Int"] = 0] = "Int"; + DLDataTypeCode[DLDataTypeCode["UInt"] = 1] = "UInt"; + DLDataTypeCode[DLDataTypeCode["Float"] = 2] = "Float"; + DLDataTypeCode[DLDataTypeCode["OpaqueHandle"] = 3] = "OpaqueHandle"; +})(DLDataTypeCode || (DLDataTypeCode = {})); +var DLDataTypeCodeToStr = { + 0: "int", + 1: "uint", + 2: "float", + 3: "handle", +}; +/** + * Runtime data type of NDArray. + */ +var DLDataType = /** @class */ (function () { + function DLDataType(code, bits, lanes) { + this.code = code; + this.bits = bits; + this.lanes = lanes; + } + DLDataType.prototype.toString = function () { + var ret = DLDataTypeCodeToStr[this.code] + this.bits.toString(); + if (this.lanes != 1) { + return ret + "x" + this.lanes.toString(); + } + else { + return ret; + } + }; + DLDataType.prototype.numStorageBytes = function () { + return (this.bits * this.lanes + 7) >> 3; + }; + return DLDataType; +}()); +/** + * n-dimnesional array. + */ +var NDArray = /** @class */ (function () { + function NDArray(handle, isView, lib, ctx) { + this.handle = handle; + this.isView = isView; + this.lib = lib; + this.ctx = ctx; + if (this.isView) { + this.dltensor = handle; + } + else { + this.dltensor = this.getDLTensorFromArrayHandle(this.handle); + } + // constant offsets. + var arrayOffsetData = 0; + var arrayOffsetContext = arrayOffsetData + this.lib.sizeofPtr(); + var arrayOffsetDevType = arrayOffsetContext; + var arrayOffsetDevId = arrayOffsetContext + SizeOf.I32; + var arrayOffsetNdim = arrayOffsetContext + SizeOf.DLDevice; + var arrayOffsetDtype = arrayOffsetNdim + SizeOf.I32; + var arrayOffsetDtypeCode = arrayOffsetDtype; + var arrayOffsetDtypeBits = arrayOffsetDtype + SizeOf.U8; + var arrayOffsetDtypeLanes = arrayOffsetDtypeBits + SizeOf.U8; + var arrayOffsetShape = arrayOffsetDtype + SizeOf.DLDataType; + var arrayOffsetStrides = arrayOffsetShape + this.lib.sizeofPtr(); + var arrayOffsetByteOffset = arrayOffsetStrides + this.lib.sizeofPtr(); + // dataPtr + this.dataPtr = lib.memory.loadPointer(this.dltensor); + // ndim + this.ndim = lib.memory.loadI32(this.dltensor + arrayOffsetNdim); + // shape + var cshapePtr = lib.memory.loadPointer(this.dltensor + arrayOffsetShape); + this.shape = []; + for (var i = 0; i < this.ndim; ++i) { + this.shape.push(lib.memory.loadI64(cshapePtr + i * SizeOf.I64)); + } + // dtype + var code = lib.memory.loadU8(this.dltensor + arrayOffsetDtypeCode); + var bits = lib.memory.loadU8(this.dltensor + arrayOffsetDtypeBits); + var lanes = lib.memory.loadU16(this.dltensor + arrayOffsetDtypeLanes); + this.dlDataType = new DLDataType(code, bits, lanes); + this.dtype = this.dlDataType.toString(); + // device + var deviceType = lib.memory.loadI32(this.dltensor + arrayOffsetDevType); + var deviceId = lib.memory.loadI32(this.dltensor + arrayOffsetDevId); + this.device = new DLDevice(deviceType, deviceId, lib); + // byte_offset + this.byteOffset = lib.memory.loadI64(this.dltensor + arrayOffsetByteOffset); + } + /** + * Create a view of the array. + * @param shape The shape of the view. + * @returns The new sliced ndarray. + */ + NDArray.prototype.view = function (shape) { + var _a; + var shapeArray = shape.map(function (value) { return new Scalar(value, "int"); }); + return this.ctx.ndarrayCreateView(this, (_a = this.ctx).makeShapeTuple.apply(_a, shapeArray)); + }; + /** + * Get handle of ndarray, check it is not null. + * + * @param requireNotNull require handle is not null. + * @returns The handle. + */ + NDArray.prototype.getHandle = function (requireNotNull) { + if (requireNotNull === void 0) { requireNotNull = true; } + if (requireNotNull && this.handle == 0) { + throw Error("NDArray has already been disposed"); + } + return this.handle; + }; + /** + * Get dataPtr of NDarray + * + * @returns The handle. + */ + NDArray.prototype.getDataPtr = function () { + if (this.handle == 0) { + throw Error("NDArray has already been disposed"); + } + return this.dataPtr; + }; + NDArray.prototype.dispose = function () { + if (this.handle != 0 && !this.isView) { + this.lib.checkCall(this.lib.exports.TVMArrayFree(this.handle)); + this.handle = 0; + } + }; + /** + * Copy data from another NDArray or javascript array. + * The number of elements must match. + * + * @param data The source data array. + * @returns this + */ + NDArray.prototype.copyFrom = function (data) { + if (data instanceof NDArray) { + this.lib.checkCall(this.lib.exports.TVMArrayCopyFromTo(data.getHandle(), this.getHandle(), 0)); + return this; + } + else { + var size = this.shape.reduce(function (a, b) { + return a * b; + }, 1); + if (data.length != size) { + throw new Error("data size and shape mismatch data.length" + + data.length + + " vs " + + size); + } + var buffer = void 0; + if (this.dtype == "float32") { + buffer = Float32Array.from(data).buffer; + } + else if (this.dtype == "float64") { + buffer = Float64Array.from(data).buffer; + } + else if (this.dtype == "int32") { + buffer = Int32Array.from(data).buffer; + } + else if (this.dtype == "int8") { + buffer = Int8Array.from(data).buffer; + } + else if (this.dtype == "uint8") { + buffer = Uint8Array.from(data).buffer; + } + else { + throw new Error("Unsupported data type " + this.dtype); + } + return this.copyFromRawBytes(new Uint8Array(buffer)); + } + }; + /** + * Copy data from raw bytes. + * @param data Uint8Array of bytes. + * @returns this + */ + NDArray.prototype.copyFromRawBytes = function (data) { + var _a; + // short cut for gpu copy + if (this.device.deviceType == DeviceStrToEnum.webgpu) { + (_a = this.lib.webGPUContext) === null || _a === void 0 ? void 0 : _a.copyRawBytesToBuffer(data, this.getDataPtr(), 0, data.length); + return this; + } + // CPU copy + var size = this.shape.reduce(function (a, b) { + return a * b; + }, 1); + var nbytes = this.dlDataType.numStorageBytes() * size; + if (nbytes != data.length) { + throw new Error("Expect the data's length equals nbytes=" + nbytes); + } + var stack = this.lib.getOrAllocCallStack(); + var tempOffset = stack.allocRawBytes(nbytes); + var tempPtr = stack.ptrFromOffset(tempOffset); + this.lib.memory.storeRawBytes(tempPtr, data); + this.lib.checkCall(this.lib.exports.TVMArrayCopyFromBytes(this.getHandle(), tempPtr, nbytes)); + this.lib.recycleCallStack(stack); + return this; + }; + /** + * Return a copied Uint8Array of the raw bytes in the NDArray. + * @returns The result array. + */ + NDArray.prototype.toRawBytes = function () { + if (this.device.deviceType != DeviceStrToEnum.cpu) { + throw new Error("Can only sync copy CPU array, use cpu_arr.copyfrom(gpu_arr) then sync instead."); + } + var size = this.shape.reduce(function (a, b) { + return a * b; + }, 1); + var nbytes = this.dlDataType.numStorageBytes() * size; + var stack = this.lib.getOrAllocCallStack(); + var tempOffset = stack.allocRawBytes(nbytes); + var tempPtr = stack.ptrFromOffset(tempOffset); + this.lib.checkCall(this.lib.exports.TVMArrayCopyToBytes(this.getHandle(), tempPtr, nbytes)); + var ret = this.lib.memory.loadRawBytes(tempPtr, nbytes); + this.lib.recycleCallStack(stack); + return ret; + }; + /** + * Return a TypedArray copy of the NDArray, the specific type depends on + * the dtype of the NDArray. + * @returns The result array. + */ + NDArray.prototype.toArray = function () { + var stype = this.dtype; + if (stype == "float32") { + return new Float32Array(this.toRawBytes().buffer); + } + else if (stype == "float64") { + return new Float64Array(this.toRawBytes().buffer); + } + else if (stype == "int32") { + return new Int32Array(this.toRawBytes().buffer); + } + else if (stype == "int8") { + return new Int8Array(this.toRawBytes().buffer); + } + else if (stype == "uint8") { + return new Uint8Array(this.toRawBytes().buffer); + } + else { + throw new Error("Unsupported data type " + this.dtype); + } + }; + NDArray.prototype.getDLTensorFromArrayHandle = function (handle) { + // Note: this depends on the NDArray C ABI. + // keep this function in case of ABI change. + return handle; + }; + return NDArray; +}()); +/** + * Runtime Module. + */ +var Module = /** @class */ (function () { + function Module(handle, lib, makePackedFunc) { + this.handle = handle; + this.lib = lib; + this.makePackedFunc = makePackedFunc; + } + Module.prototype.dispose = function () { + if (this.handle != 0) { + this.lib.checkCall(this.lib.exports.TVMModFree(this.handle)); + this.handle = 0; + } + }; + /** + * Get handle of module, check it is not null. + * + * @param requireNotNull require handle is not null. + * @returns The handle. + */ + Module.prototype.getHandle = function (requireNotNull) { + if (requireNotNull === void 0) { requireNotNull = true; } + if (requireNotNull && this.handle == 0) { + throw Error("Module has already been disposed"); + } + return this.handle; + }; + /** + * Get a function in the module. + * @param name The name of the function. + * @param queryImports Whether to also query imports + * @returns The result function. + */ + Module.prototype.getFunction = function (name, queryImports) { + if (queryImports === void 0) { queryImports = true; } + if (this.handle == 0) { + throw Error("Module has already been disposed"); + } + var stack = this.lib.getOrAllocCallStack(); + var nameOffset = stack.allocRawBytes(name.length + 1); + stack.storeRawBytes(nameOffset, StringToUint8Array(name)); + var outOffset = stack.allocPtrArray(1); + var outPtr = stack.ptrFromOffset(outOffset); + stack.commitToWasmMemory(outOffset); + this.lib.checkCall(this.lib.exports.TVMModGetFunction(this.getHandle(), stack.ptrFromOffset(nameOffset), queryImports ? 1 : 0, outPtr)); + var handle = this.lib.memory.loadPointer(outPtr); + this.lib.recycleCallStack(stack); + if (handle == 0) { + throw Error("Cannot find function " + name); + } + var ret = this.makePackedFunc(handle); + return ret; + }; + /** + * Import another module into the current runtime module. + * @param mod The module to be imported. + */ + Module.prototype.importModule = function (mod) { + this.lib.checkCall(this.lib.exports.TVMModImport(this.getHandle(), mod.getHandle())); + }; + return Module; +}()); +/** + * Generic object base + */ +var TVMObject = /** @class */ (function () { + function TVMObject(handle, lib, ctx) { + this.handle = handle; + this.lib = lib; + this.ctx = ctx; + } + TVMObject.prototype.dispose = function () { + if (this.handle != 0) { + this.lib.checkCall(this.lib.exports.TVMObjectFree(this.handle)); + this.handle = 0; + } + }; + /** + * Get handle of module, check it is not null. + * + * @param requireNotNull require handle is not null. + * @returns The handle. + */ + TVMObject.prototype.getHandle = function (requireNotNull) { + if (requireNotNull === void 0) { requireNotNull = true; } + if (requireNotNull && this.handle == 0) { + throw Error("Module has already been disposed"); + } + return this.handle; + }; + /** get the type index of the object */ + TVMObject.prototype.typeIndex = function () { + if (this.handle == 0) { + throw Error("The current Object has already been disposed"); + } + var stack = this.lib.getOrAllocCallStack(); + var outOffset = stack.allocPtrArray(1); + var outPtr = stack.ptrFromOffset(outOffset); + this.lib.checkCall(this.lib.exports.TVMObjectGetTypeIndex(this.getHandle(), outPtr)); + var result = this.lib.memory.loadU32(outPtr); + this.lib.recycleCallStack(stack); + return result; + }; + /** get the type key of the object */ + TVMObject.prototype.typeKey = function () { + var type_index = this.typeIndex(); + var stack = this.lib.getOrAllocCallStack(); + var outOffset = stack.allocPtrArray(1); + var outPtr = stack.ptrFromOffset(outOffset); + this.lib.checkCall(this.lib.exports.TVMObjectTypeIndex2Key(type_index, outPtr)); + var result = this.lib.memory.loadCString(this.lib.memory.loadPointer(outPtr)); + this.lib.recycleCallStack(stack); + return result; + }; + return TVMObject; +}()); +/** Runtime array object. */ +var TVMArray = /** @class */ (function (_super) { + __extends(TVMArray, _super); + function TVMArray(handle, lib, ctx) { + return _super.call(this, handle, lib, ctx) || this; + } + /** + * @returns the size of the array. + */ + TVMArray.prototype.size = function () { + return this.ctx.arrayGetSize(this); + }; + /** + * Get index-th element of the array + * @param index the array index. + * @returns The element. + */ + TVMArray.prototype.get = function (index) { + return this.ctx.arrayGetItem(this, new Scalar(index, "int32")); + }; + return TVMArray; +}(TVMObject)); +var VMAllocatorKind; +(function (VMAllocatorKind) { + VMAllocatorKind[VMAllocatorKind["NAIVE_ALLOCATOR"] = 1] = "NAIVE_ALLOCATOR"; + VMAllocatorKind[VMAllocatorKind["POOLED_ALLOCATOR"] = 2] = "POOLED_ALLOCATOR"; +})(VMAllocatorKind || (VMAllocatorKind = {})); +/** + * VirtualMachine Executor. + * + * This is a thin wrapper of the underlying TVM module. + * you can also directly call set_input, run, and get_output + * of underlying module functions + */ +var VirtualMachine = /** @class */ (function () { + /** + * Constructor + * @param mod The underlying module, need to be detached. + * @param device The main device ro run VM on. + */ + function VirtualMachine(mod, device) { + this.mod = mod; + this.mod.getFunction("vm_initialization")(new Scalar(device.deviceType, "int"), new Scalar(device.deviceId, "int"), new Scalar(VMAllocatorKind.POOLED_ALLOCATOR, "int"), + // explicitly specify host device type + new Scalar(DeviceStrToEnum.cpu, "int"), new Scalar(0, "int"), new Scalar(VMAllocatorKind.POOLED_ALLOCATOR, "int")); + } + VirtualMachine.prototype.dispose = function () { + this.mod.dispose(); + }; + /** + * Get a function in the VM module. + * @param name The name of the function. + * @returns The result function. + */ + VirtualMachine.prototype.getFunction = function (name) { + return this.mod.getFunction(name); + }; + /** + * Get the internal module. + */ + VirtualMachine.prototype.getInternalModule = function () { + return this.mod; + }; + return VirtualMachine; +}()); +/** Code used as the first argument of the async callback. */ +var AyncCallbackCode; +(function (AyncCallbackCode) { + AyncCallbackCode[AyncCallbackCode["kReturn"] = 4] = "kReturn"; + AyncCallbackCode[AyncCallbackCode["kException"] = 5] = "kException"; +})(AyncCallbackCode || (AyncCallbackCode = {})); +/** + * TVM runtime instance. + * + * All objects(NDArray, Module, PackedFunc) returned by TVM runtim function call + * and PackedFunc instance are tracked through a scope mechanism that will get + * auto-released when we call EndScope. + * + * This is necessarily to be able to release the underlying WASM and WebGPU memory that + * are not tracked through JS native garbage collection mechanism. + * + * This does mean that we have to get familar with the following functions: + * - {@link beginScope} + * - {@link endScope} + * - {@link withNewScope} + * - {@link attachToCurrentScope} + * - {@link detachFromCurrentScope} + */ +var Instance = /** @class */ (function () { + /** + * Constructor + * + * importObject can also be a {@link LibraryProvider} object, + * a WASI object, or an object containing wasmLibraryProvider field. + * + * @param wasmModule The input module or instance. + * @param importObject The imports to initialize the wasmInstance if it is not provided. + * @param wasmInstance Additional wasm instance argument for deferred construction. + * @param env Directly specified environment module. + * + * @see Please use the async version {@link instantiate} when targeting browsers. + */ + function Instance(wasmModule, importObject, wasmInstance, env) { + if (importObject === void 0) { importObject = {}; } + var _this = this; + this.cacheMetadata = {}; + this.initProgressCallback = []; + if (wasmInstance instanceof WebAssembly.Instance) { + assert(env instanceof Environment, "env must be provided when passing in instance"); + } + else { + assert(env === undefined); + env = new Environment(importObject); + wasmInstance = new WebAssembly.Instance(wasmModule, env.imports); + } + env.start(wasmInstance); + this.env = env; + this.lib = new FFILibrary(wasmInstance, env.imports); + this.memory = this.lib.memory; + this.exports = this.lib.exports; + this.objFactory = new Map(); + this.ctx = new RuntimeContext(function (name) { + var autoAttachToScope = false; + // runtime context function do not auto-release. + return _this.getGlobalFuncInternal(name, autoAttachToScope); + }); + this.registerEnvGlobalPackedFuncs(); + this.registerObjectFactoryFuncs(); + } + /** + * Benchmark stable execution of the run function. + * + * @params run The run function + * @params dev The device to sync during each run. + * @number The number of times to compute the average. + * @repeat The number of times to repeat the run. + */ + Instance.prototype.benchmark = function (run, dev, number, repeat) { + if (number === void 0) { number = 10; } + if (repeat === void 0) { repeat = 1; } + return __awaiter(this, void 0, void 0, function () { + var perf, results, k, tstart, i, tend; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + perf = getPerformance(); + results = []; + // run with new scope + this.withNewScope(run); + return [4 /*yield*/, dev.sync()]; + case 1: + _a.sent(); + k = 0; + _a.label = 2; + case 2: + if (!(k < repeat)) return [3 /*break*/, 5]; + tstart = perf.now(); + for (i = 0; i < number; ++i) { + this.withNewScope(run); + } + return [4 /*yield*/, dev.sync()]; + case 3: + _a.sent(); + tend = perf.now(); + results.push((tend - tstart) / number); + _a.label = 4; + case 4: + ++k; + return [3 /*break*/, 2]; + case 5: return [2 /*return*/, results]; + } + }); + }); + }; + Instance.prototype.dispose = function () { + // order matters + // ctx release goes back into lib. + this.ctx.dispose(); + this.lib.dispose(); + }; + /** + * Obtain the runtime information in readable format. + */ + Instance.prototype.runtimeStatsText = function () { + if (this.lib.webGPUContext !== undefined) { + return this.lib.webGPUContext.runtimeStatsText(); + } + else { + return ""; + } + }; + /** + * Begin a new scope for tracking object disposal. + */ + Instance.prototype.beginScope = function () { + this.ctx.beginScope(); + }; + /** + * End a scope and release all created TVM objects + * under the current scope. + * + * Exception: one can call {@link moveToParentScope} to move + * a value to parent scope. + */ + Instance.prototype.endScope = function () { + this.ctx.endScope(); + }; + /** + * Perform action under a new scope. + * + * @param action The action function. + * @returns The result value. + * + * @note For action to return a valid value, + * we will need to call {@link moveToParentScope} + * for the objects that are created in the scope. + */ + Instance.prototype.withNewScope = function (action) { + this.beginScope(); + var val = action(); + this.endScope(); + return val; + }; + /** + * Attach a detached obj to the auto-release pool of the current scope. + * + * @param obj The input obj. + * @note Normally user do not need to call this function explicitly, as + * all library call return values are explicitly attached to + * the current scope. You only need to do so when you call + * {@link detachFromCurrentScope} to create a detached object. + */ + Instance.prototype.attachToCurrentScope = function (obj) { + return this.ctx.attachToCurrentScope(obj); + }; + /** + * Move obj's attachment to the parent scope. + * + * This function is useful to make sure objects are still + * alive when exit the current scope. + * + * @param obj The object to be moved. + * @returns The input obj. + */ + Instance.prototype.moveToParentScope = function (obj) { + return this.ctx.moveToParentScope(obj); + }; + /** + * Detach the object from the current scope + * so it won't be released via auto-release during endscope. + * + * User needs to either explicitly call obj.dispose(), or + * {@link attachToCurrentScope} to re-attach to the current scope. + * + * This function can be used to return values to the parent scope. + * @param obj The object. + */ + Instance.prototype.detachFromCurrentScope = function (obj) { + return this.ctx.detachFromCurrentScope(obj); + }; + /** + * Get system-wide library module in the wasm. + * System lib is a global module that contains self register functions in startup. + * @returns The system library module. + */ + Instance.prototype.systemLib = function () { + return this.ctx.getSysLib(); + }; + /** + * List all the global function names registered in the runtime. + * @returns The name list. + */ + Instance.prototype.listGlobalFuncNames = function () { + var stack = this.lib.getOrAllocCallStack(); + var outSizeOffset = stack.allocPtrArray(2); + var outSizePtr = stack.ptrFromOffset(outSizeOffset); + var outArrayPtr = stack.ptrFromOffset(outSizeOffset + this.lib.sizeofPtr()); + this.lib.checkCall(this.exports.TVMFuncListGlobalNames(outSizePtr, outArrayPtr)); + var size = this.memory.loadI32(outSizePtr); + var array = this.memory.loadPointer(outArrayPtr); + var names = []; + for (var i = 0; i < size; ++i) { + names.push(this.memory.loadCString(this.memory.loadPointer(array + this.lib.sizeofPtr() * i))); + } + this.lib.recycleCallStack(stack); + return names; + }; + /** + * Register function to be global function in tvm runtime. + * @param name The name of the function. + * @param f function to be registered. + * @param override Whether overwrite function in existing registry. + */ + Instance.prototype.registerFunc = function (name, func, override) { + var _this = this; + if (override === void 0) { override = false; } + this.withNewScope(function () { + var autoAttachToScope = true; + // packed func can be released once it is registered + var packedFunc = _this.toPackedFuncInternal(func, autoAttachToScope); + var ioverride = override ? 1 : 0; + var stack = _this.lib.getOrAllocCallStack(); + var nameOffset = stack.allocRawBytes(name.length + 1); + stack.storeRawBytes(nameOffset, StringToUint8Array(name)); + stack.commitToWasmMemory(); + _this.lib.checkCall(_this.lib.exports.TVMFuncRegisterGlobal(stack.ptrFromOffset(nameOffset), packedFunc._tvmPackedCell.getHandle(), ioverride)); + _this.lib.recycleCallStack(stack); + }); + }; + /** + * Get global PackedFunc from the runtime. + * @param name The name of the function. + * @param autoAttachToScope Whether to track it via autoDispose + * @returns The result function. + */ + Instance.prototype.getGlobalFunc = function (name) { + return this.getGlobalFuncInternal(name, true); + }; + Instance.prototype.getGlobalFuncInternal = function (name, autoAttachToScope) { + if (autoAttachToScope === void 0) { autoAttachToScope = true; } + var stack = this.lib.getOrAllocCallStack(); + var nameOffset = stack.allocRawBytes(name.length + 1); + stack.storeRawBytes(nameOffset, StringToUint8Array(name)); + var outOffset = stack.allocPtrArray(1); + var outPtr = stack.ptrFromOffset(outOffset); + stack.commitToWasmMemory(outOffset); + this.lib.checkCall(this.exports.TVMFuncGetGlobal(stack.ptrFromOffset(nameOffset), outPtr)); + var handle = this.memory.loadPointer(outPtr); + this.lib.recycleCallStack(stack); + if (handle == 0) { + throw Error("Cannot find global function " + name); + } + var ret = this.makePackedFunc(handle); + if (autoAttachToScope) + this.ctx.attachToCurrentScope(ret); + return ret; + }; + /** + * Check if func is PackedFunc. + * + * @param func The input. + * @returns The check result. + */ + Instance.prototype.isPackedFunc = function (func) { + // eslint-disable-next-line no-prototype-builtins + return typeof func == "function" && func.hasOwnProperty("_tvmPackedCell"); + }; + /** + * Convert func to PackedFunc + * + * @param func Input function. + * @returns The converted function. + */ + Instance.prototype.toPackedFunc = function (func) { + return this.toPackedFuncInternal(func, true); + }; + Instance.prototype.toPackedFuncInternal = function (func, autoAttachToScope) { + if (this.isPackedFunc(func)) + return func; + var ret = this.createPackedFuncFromCFunc(this.wrapJSFuncAsPackedCFunc(func)); + if (autoAttachToScope) + return this.ctx.attachToCurrentScope(ret); + return ret; + }; + /** + * Setup a virtual machine module with given device. + * + * @param dev DLDevice the device. + * @returns The created virtual machime. + */ + Instance.prototype.createVirtualMachine = function (dev) { + var mod = this.ctx.detachFromCurrentScope(this.systemLib().getFunction("vm_load_executable")()); + return this.ctx.attachToCurrentScope(new VirtualMachine(mod, dev)); + }; + //----------------------------------------------- + // Native NDArray Cache Support + //----------------------------------------------- + /** + * Register a call back for fetch progress. + * + * @param cb the fetch progress callback. + */ + Instance.prototype.registerInitProgressCallback = function (cb) { + this.initProgressCallback.push(cb); + }; + /** + * Get parameters in the form of prefix_i + * + * @param prefix The parameter prefix. + * @param numParams Number of parameters. + * @returns + */ + Instance.prototype.getParamsFromCache = function (prefix, numParams) { + return this.ctx.paramModuleFromCache(prefix, new Scalar(numParams, "int32")).getFunction("get_params")(); + }; + /** + * Get NDArray from cache. + * @param name The name of array. + * @returns The result. + */ + Instance.prototype.ndarrayCacheGet = function (name) { + return this.ctx.arrayCacheGet(name); + }; + /** + * Get NDArray from cache. + * @param name The name of array. + * @returns The result. + */ + Instance.prototype.ndarrayCacheRemove = function (name) { + return this.ctx.arrayCacheRemove(name); + }; + /** + * Update the ndarray cache. + * @param name The name of the array. + * @param arr The content. + */ + Instance.prototype.ndarrayCacheUpdate = function (name, arr, override) { + if (override === void 0) { override = false; } + this.ctx.arrayCacheUpdate(name, arr, this.scalar(override ? 1 : 0, "int32")); + }; + /** + * Update the ndarray cache. + * @param name The name of the array. + * @param arr The content. + */ + Instance.prototype.ndarrayCacheClear = function () { + this.ctx.arrayCacheClear(); + }; + /** + * Fetch NDArray cache from url. + * + * @param ndarrayCacheUrl The cache url. + * @param device The device to be fetched to. + * @returns The meta data + */ + Instance.prototype.fetchNDArrayCache = function (ndarrayCacheUrl, device) { + return __awaiter(this, void 0, void 0, function () { + var jsonUrl, request, cache, result, list; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + jsonUrl = new URL("ndarray-cache.json", ndarrayCacheUrl).href; + request = new Request(jsonUrl); + return [4 /*yield*/, caches.open("tvmjs")]; + case 1: + cache = _a.sent(); + return [4 /*yield*/, cache.match(request)]; + case 2: + result = _a.sent(); + if (!(result === undefined)) return [3 /*break*/, 5]; + return [4 /*yield*/, cache.add(request)]; + case 3: + _a.sent(); + return [4 /*yield*/, cache.match(request)]; + case 4: + result = _a.sent(); + _a.label = 5; + case 5: + if (!(result === undefined)) return [3 /*break*/, 9]; + this.env.logger("Error: Cannot cache " + jsonUrl + ", reloading will be slow"); + _a.label = 6; + case 6: + _a.trys.push([6, 8, , 9]); + return [4 /*yield*/, fetch(request)]; + case 7: + result = _a.sent(); + return [3 /*break*/, 9]; + case 8: + _a.sent(); + this.env.logger("Cannot fetch " + jsonUrl); + return [3 /*break*/, 9]; + case 9: + if (!(result instanceof Response)) return [3 /*break*/, 11]; + return [4 /*yield*/, result.json()]; + case 10: + list = _a.sent(); + _a.label = 11; + case 11: return [4 /*yield*/, this.fetchNDArrayCacheInternal(ndarrayCacheUrl, list["records"], device)]; + case 12: + _a.sent(); + this.cacheMetadata = __assign(__assign({}, this.cacheMetadata), list["metadata"]); + return [2 /*return*/]; + } + }); + }); + }; + /** + * Fetch list of NDArray into the NDArrayCache. + * + * @param ndarrayCacheUrl The cache url. + * @param list The list of array data. + * @param device The device to store the data to. + */ + Instance.prototype.fetchNDArrayCacheInternal = function (ndarrayCacheUrl, list, device) { + return __awaiter(this, void 0, void 0, function () { + var perf, tstart, totalBytes, i, fetchedBytes, timeElapsed, reportCallback, j, cache, i, dataUrl, request, buffer, result, err_2, shardRecords, _loop_1, this_1, j; + var _this = this; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + perf = getPerformance(); + tstart = perf.now(); + totalBytes = 0; + for (i = 0; i < list.length; ++i) { + totalBytes += list[i].nbytes; + } + fetchedBytes = 0; + timeElapsed = 0; + reportCallback = function (iter) { + // report + for (var j = 0; j < _this.initProgressCallback.length; ++j) { + _this.initProgressCallback[j]({ + type: 'init', + progress: fetchedBytes / totalBytes, + timeElapsed: timeElapsed, + currentChunk: iter, + totalChunks: list.length, + fetchedBytes: fetchedBytes, + totalBytes: totalBytes, + }); + } + }; + for (j = 0; j < this.initProgressCallback.length; ++j) { + this.initProgressCallback[j]({ + type: 'init', + progress: fetchedBytes / totalBytes, + timeElapsed: 0, + currentChunk: 0, + totalChunks: list.length, + fetchedBytes: fetchedBytes, + totalBytes: totalBytes, + }); + } + return [4 /*yield*/, caches.open("tvmjs")]; + case 1: + cache = _a.sent(); + i = 0; + _a.label = 2; + case 2: + if (!(i < list.length)) return [3 /*break*/, 18]; + reportCallback(i); + fetchedBytes += list[i].nbytes; + dataUrl = new URL(list[i].dataPath, ndarrayCacheUrl).href; + request = new Request(dataUrl); + buffer = void 0; + _a.label = 3; + case 3: + _a.trys.push([3, 11, , 12]); + return [4 /*yield*/, cache.match(request)]; + case 4: + result = _a.sent(); + if (!(result === undefined)) return [3 /*break*/, 7]; + return [4 /*yield*/, cache.add(request)]; + case 5: + _a.sent(); + return [4 /*yield*/, cache.match(request)]; + case 6: + result = _a.sent(); + _a.label = 7; + case 7: + if (!(result == undefined)) return [3 /*break*/, 9]; + this.env.logger("Error: Cannot cache " + dataUrl + ", reloading will be slow"); + return [4 /*yield*/, fetch(request)]; + case 8: + result = _a.sent(); + _a.label = 9; + case 9: return [4 /*yield*/, result.arrayBuffer()]; + case 10: + buffer = _a.sent(); + return [3 /*break*/, 12]; + case 11: + err_2 = _a.sent(); + this.env.logger("Error: Cannot fetch " + dataUrl + " err= " + err_2); + throw err_2; + case 12: + shardRecords = list[i].records; + _loop_1 = function (j) { + var rec, cpu_arr, recSource, gpu_arr; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + rec = shardRecords[j]; + cpu_arr = this_1.withNewScope(function () { + return _this.detachFromCurrentScope(_this.empty(rec.shape, rec.dtype, _this.cpu())); + }); + recSource = buffer.slice(rec.byteOffset, rec.byteOffset + rec.nbytes); + // first sync copy to cpu. + this_1.ctx.arrayDecodeStorage(cpu_arr, new Uint8Array(recSource), rec.format); + if (!(device.deviceType == DeviceStrToEnum.cpu)) return [3 /*break*/, 1]; + this_1.ndarrayCacheUpdate(rec.name, cpu_arr, false); + cpu_arr.dispose(); + return [3 /*break*/, 3]; + case 1: + gpu_arr = this_1.withNewScope(function () { + return _this.detachFromCurrentScope(_this.empty(rec.shape, rec.dtype, device)); + }); + gpu_arr.copyFrom(cpu_arr); + return [4 /*yield*/, device.sync()]; + case 2: + _b.sent(); + this_1.ndarrayCacheUpdate(rec.name, gpu_arr, false); + cpu_arr.dispose(); + gpu_arr.dispose(); + _b.label = 3; + case 3: return [2 /*return*/]; + } + }); + }; + this_1 = this; + j = 0; + _a.label = 13; + case 13: + if (!(j < shardRecords.length)) return [3 /*break*/, 16]; + return [5 /*yield**/, _loop_1(j)]; + case 14: + _a.sent(); + _a.label = 15; + case 15: + ++j; + return [3 /*break*/, 13]; + case 16: + timeElapsed = Math.ceil((perf.now() - tstart) / 1000); + _a.label = 17; + case 17: + ++i; + return [3 /*break*/, 2]; + case 18: + reportCallback(list.length); + return [2 /*return*/]; + } + }); + }); + }; + /** + * Convert dtype to {@link DLDataType} + * + * @param dtype The input dtype string or DLDataType. + * @returns The converted result. + */ + Instance.prototype.toDLDataType = function (dtype) { + if (dtype instanceof DLDataType) + return dtype; + if (typeof dtype == "string") { + var pattern = dtype; + var code = void 0, bits = 32, lanes = 1; + if (pattern.substring(0, 5) == "float") { + pattern = pattern.substring(5, pattern.length); + code = DLDataTypeCode.Float; + } + else if (pattern.substring(0, 3) == "int") { + pattern = pattern.substring(3, pattern.length); + code = DLDataTypeCode.Int; + } + else if (pattern.substring(0, 4) == "uint") { + pattern = pattern.substring(4, pattern.length); + code = DLDataTypeCode.UInt; + } + else if (pattern.substring(0, 6) == "handle") { + pattern = pattern.substring(5, pattern.length); + code = DLDataTypeCode.OpaqueHandle; + bits = 64; + } + else { + throw new Error("Unknown dtype " + dtype); + } + var arr = pattern.split("x"); + if (arr.length >= 1) { + var parsed = parseInt(arr[0]); + if (parsed + "" == arr[0]) { + bits = parsed; + } + } + if (arr.length >= 2) { + lanes = parseInt(arr[1]); + } + return new DLDataType(code, bits, lanes); + } + else { + throw new Error("Unknown dtype " + dtype); + } + }; + /** + * Create a new {@link Scalar} that can be passed to a PackedFunc. + * @param value The number value. + * @param dtype The dtype string. + * @returns The created scalar. + */ + Instance.prototype.scalar = function (value, dtype) { + return new Scalar(value, dtype); + }; + /** + * Create a new {@link DLDevice} + * @param deviceType The device type. + * @param deviceId The device index. + * @returns The created device. + */ + Instance.prototype.device = function (deviceType, deviceId) { + if (deviceId === void 0) { deviceId = 0; } + return new DLDevice(deviceType, deviceId, this.lib); + }; + /** + * Create a new cpu {@link DLDevice} + * @param deviceId The device index. + */ + Instance.prototype.cpu = function (deviceId) { + if (deviceId === void 0) { deviceId = 0; } + return this.device("cpu", deviceId); + }; + /** + * Create a new webgpu {@link DLDevice} + * @param deviceId The device index. + */ + Instance.prototype.webgpu = function (deviceId) { + if (deviceId === void 0) { deviceId = 0; } + return this.device("webgpu", deviceId); + }; + /** + * Create an empty {@link NDArray} with given shape and dtype. + * + * @param shape The shape of the array. + * @param dtype The data type of the array. + * @param dev The device of the ndarray. + * @returns The created ndarray. + */ + Instance.prototype.empty = function (shape, dtype, dev) { + if (dtype === void 0) { dtype = "float32"; } + if (dev === void 0) { dev = this.device("cpu", 0); } + dtype = this.toDLDataType(dtype); + shape = typeof shape == "number" ? [shape] : shape; + var stack = this.lib.getOrAllocCallStack(); + var shapeOffset = stack.allocRawBytes(shape.length * SizeOf.I64); + for (var i = 0; i < shape.length; ++i) { + stack.storeI64(shapeOffset + i * SizeOf.I64, shape[i]); + } + var outOffset = stack.allocPtrArray(1); + var outPtr = stack.ptrFromOffset(outOffset); + stack.commitToWasmMemory(outOffset); + this.lib.checkCall(this.exports.TVMArrayAlloc(stack.ptrFromOffset(shapeOffset), shape.length, dtype.code, dtype.bits, dtype.lanes, dev.deviceType, dev.deviceId, outPtr)); + var ret = this.ctx.attachToCurrentScope(new NDArray(this.memory.loadPointer(outPtr), false, this.lib, this.ctx)); + this.lib.recycleCallStack(stack); + return ret; + }; + /** + * Create am uniform {@link NDArray} with given shape. + * + * @param shape The shape of the array. + * @param low The low value. + * @param high The high value. + * @param dev The device of the ndarray. + * @returns The created ndarray. + */ + Instance.prototype.uniform = function (shape, low, high, dev) { + var ret = this.empty(shape, "float32", dev); + var size = shape.reduce(function (a, b) { + return a * b; + }, 1); + var scale = high - low; + var input = new Float32Array(size); + for (var i = 0; i < input.length; ++i) { + input[i] = low + Math.random() * scale; + } + return ret.copyFrom(input); + }; + /** + * Sample index via top-p sampling. + * + * @param logits The input logits before normalization. + * @param temperature The temperature factor, will take argmax if temperature = 0.0 + * @param top_p The top_p + * @returns The sampled index. + */ + Instance.prototype.sampleTopPFromLogits = function (logits, temperature, top_p) { + return this.ctx.sampleTopPFromLogits(logits, temperature, top_p, Math.random()); + }; + /** + * Bind canvas to the current WebGPU context + * @param canvas The canvas. + */ + Instance.prototype.bindCanvas = function (canvas) { + var _a; + (_a = this.lib.webGPUContext) === null || _a === void 0 ? void 0 : _a.bindCanvas(canvas); + }; + /** + * Show image in canvas. + * + * @param dataRGBA Image array in height x width uint32 NDArray RGBA format on GPU. + */ + Instance.prototype.showImage = function (dataRGBA) { + var _a; + if (dataRGBA.shape.length != 2) { + throw Error("Require a height x width uint32 NDArray in RGBA" + + "get shape=" + dataRGBA.shape.toString() + " instead."); + } + if (dataRGBA.device.deviceType != DeviceStrToEnum.webgpu) { + throw new Error("Can only run showImage on WebGPU array, " + + "get " + DeviceEnumToStr[dataRGBA.device.deviceType] + " instead."); + } + if (dataRGBA.dtype != "uint32") { + throw Error("Require a height x width uint32 NDArray in RGBA, " + + "get " + dataRGBA.dtype + " instead."); + } + (_a = this.lib.webGPUContext) === null || _a === void 0 ? void 0 : _a.drawImageFromBuffer(dataRGBA.getDataPtr(), dataRGBA.shape[0], dataRGBA.shape[1]); + }; + /** + * Clear canvas + */ + Instance.prototype.clearCanvas = function () { + var _a; + (_a = this.lib.webGPUContext) === null || _a === void 0 ? void 0 : _a.clearCanvas(); + }; + /** + * Create an tuple {@link TVMArray} input array. + * + * The input array can be passed to tvm runtime function + * and needs to b explicitly disposed. + * + * @param inputs The input array + * @returns The result array. + */ + Instance.prototype.makeTVMArray = function (inputs) { + var _a; + return (_a = this.ctx).arrayMake.apply(_a, inputs); + }; + /** + * Create a shape tuple to pass to runtime. + * @param shape The shape . + * @returns The created shape tuple. + */ + Instance.prototype.makeShapeTuple = function (shape) { + var _a; + var shapeArray = shape.map(function (value) { return new Scalar(value, "int"); }); + return (_a = this.ctx).makeShapeTuple.apply(_a, shapeArray); + }; + /** + * Get type index from type key. + * @param typeKey The type key. + * @returns The corresponding type index. + */ + Instance.prototype.typeKey2Index = function (typeKey) { + var stack = this.lib.getOrAllocCallStack(); + var typeKeyOffset = stack.allocRawBytes(typeKey.length + 1); + stack.storeRawBytes(typeKeyOffset, StringToUint8Array(typeKey)); + var outOffset = stack.allocPtrArray(1); + var outPtr = stack.ptrFromOffset(outOffset); + stack.commitToWasmMemory(outOffset); + this.lib.checkCall(this.lib.exports.TVMObjectTypeKey2Index(stack.ptrFromOffset(typeKeyOffset), outPtr)); + var typeIndex = this.memory.loadU32(outPtr); + this.lib.recycleCallStack(stack); + return typeIndex; + }; + /** + * Register an object constructor. + * @param typeKey The name of the function. + * @param func function to be registered. + * @param override Whether overwrite function in existing registry. + */ + Instance.prototype.registerObjectConstructor = function (typeKey, func, override) { + if (override === void 0) { override = false; } + var typeIndex = this.typeKey2Index(typeKey); + if (this.objFactory.has(typeIndex)) { + if (!override) { + throw new Error("Type " + typeKey + " already registered"); + } + } + this.objFactory.set(typeIndex, func); + }; + /** + * Register an asyncfunction to be global function in the server. + * @param name The name of the function. + * @param func function to be registered. + * @param override Whether overwrite function in existing registry. + * + * @note The async function will only be used for serving remote calls in the rpc. + */ + Instance.prototype.registerAsyncServerFunc = function (name, func, override) { + var _this = this; + if (override === void 0) { override = false; } + var asyncVariant = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var fargs = args.slice(0, args.length - 1); + // need to keep it alive until callback is fulfilled. + var callback = _this.detachFromCurrentScope(args[args.length - 1]); + var promise = func.apply(void 0, fargs); + promise.then(function (rv) { + callback(_this.scalar(AyncCallbackCode.kReturn, "int32"), rv); + callback.dispose(); + }); + }; + this.registerFunc("__async." + name, asyncVariant, override); + }; + /** + * Asynchrously load webgpu pipelines when possible. + * @param mod The input module. + */ + Instance.prototype.asyncLoadWebGPUPiplines = function (mod) { + return __awaiter(this, void 0, void 0, function () { + var webgpuContext, fmap_str, fmap, fGetShader, fUpdatePrebuild, perf, tstart, tlastReport, finishCounter, fmapEntries, allEvents, _loop_2, _i, fmapEntries_1, _a, key, finfo; + var _this = this; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (this.lib.webGPUContext == undefined) + throw Error("WebGPU not initialied"); + webgpuContext = this.lib.webGPUContext; + this.beginScope(); + fmap_str = mod.getFunction("webgpu.get_fmap", true)(); + fmap = JSON.parse(fmap_str); + fmap.length; + fGetShader = this.detachFromCurrentScope(mod.getFunction("webgpu.get_shader")); + fUpdatePrebuild = this.detachFromCurrentScope(mod.getFunction("webgpu.update_prebuild")); + this.endScope(); + perf = getPerformance(); + tstart = perf.now(); + tlastReport = tstart; + finishCounter = 0; + fmapEntries = Object.entries(fmap); + allEvents = Promise.resolve(); + _loop_2 = function (key, finfo) { + var code = fGetShader(key); + assert(key == finfo.name); + var event_1 = webgpuContext.createShaderAsync(finfo, code).then(function (func) { + _this.beginScope(); + fUpdatePrebuild(key, func); + _this.endScope(); + }).then(function () { + finishCounter += 1; + var tend = perf.now(); + // skip report if gap is smaller than 1000 + if ((tend - tlastReport) < 1000 && finishCounter != fmapEntries.length) { + return; + } + tlastReport = tend; + var timeElapsed = Math.ceil((perf.now() - tstart) / 1000); + // report + for (var j = 0; j < _this.initProgressCallback.length; ++j) { + var progress = finishCounter / fmapEntries.length; + var text = "Loading GPU shader modules[" + finishCounter + "/" + fmapEntries.length + "]: "; + text += Math.floor(progress * 100).toString() + "% completed, "; + text += timeElapsed + " secs elapsed."; + // this.initProgressCallback[j]({ + // progress: progress, + // timeElapsed: timeElapsed, + // text: text + // }); + } + }); + allEvents = Promise.all([allEvents, event_1]).then(function () { }); + }; + for (_i = 0, fmapEntries_1 = fmapEntries; _i < fmapEntries_1.length; _i++) { + _a = fmapEntries_1[_i], key = _a[0], finfo = _a[1]; + _loop_2(key, finfo); + } + return [4 /*yield*/, allEvents]; + case 1: + _b.sent(); + assert(finishCounter == fmapEntries.length); + return [2 /*return*/]; + } + }); + }); + }; + /** + * Initialize webgpu in the runtime. + * @param device The given GPU device. + */ + Instance.prototype.initWebGPU = function (device) { + var _this = this; + var webGPUContext = new WebGPUContext(this.memory, device); + this.registerFunc("wasm.WebGPUDeviceAPI", function (name) { + return webGPUContext.getDeviceAPI(name); + }); + this.registerFunc("wasm.WebGPUCreateShader", function (info, code) { + var finfo = JSON.parse(info); + return webGPUContext.createShader(finfo, code); + }); + this.registerAsyncServerFunc("wasm.WebGPUWaitForTasks", function () { return __awaiter(_this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, webGPUContext.sync()]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); }); + this.lib.webGPUContext = webGPUContext; + }; + /** Register all object factory */ + Instance.prototype.registerObjectFactoryFuncs = function () { + this.registerObjectConstructor("Array", function (handle, lib, ctx) { + return new TVMArray(handle, lib, ctx); + }); + }; + /** Register global packed functions needed by the backend to the env. */ + Instance.prototype.registerEnvGlobalPackedFuncs = function () { + var _this = this; + // Register the timer function to enable the time_evaluator. + var perf = getPerformance(); + // Helper function to time the finvoke + var timeExecution = function (finvoke, dev, nstep, repeat, minRepeatMs, limitZeroTimeIterations, cooldownIntervalMs, repeatsToCooldown) { return __awaiter(_this, void 0, void 0, function () { + var result, setupNumber, i, durationMs, absoluteZeroTimes, golden_ratio, tstart, tend, speed, ret; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + // detach and explicit dispose when tasks is fullfilled + // the promise will immediately return and we need to makesure + // finvoke do not get recycled. + this.ctx.detachFromCurrentScope(finvoke); + finvoke(this.scalar(1, "int32")); + return [4 /*yield*/, dev.sync()]; + case 1: + _a.sent(); + result = []; + setupNumber = nstep; + i = 0; + _a.label = 2; + case 2: + if (!(i < repeat)) return [3 /*break*/, 9]; + durationMs = 0.0; + absoluteZeroTimes = 0; + _a.label = 3; + case 3: + if (durationMs > 0.0) { + golden_ratio = 1.618; + setupNumber = Math.floor(Math.max(minRepeatMs / (durationMs / setupNumber) + 1, setupNumber * golden_ratio)); + } + tstart = perf.now(); + finvoke(this.scalar(setupNumber, "int32")); + return [4 /*yield*/, dev.sync()]; + case 4: + _a.sent(); + tend = perf.now(); + durationMs = tend - tstart; + if (durationMs == 0) { + absoluteZeroTimes++; + } + _a.label = 5; + case 5: + if (durationMs < minRepeatMs && absoluteZeroTimes < limitZeroTimeIterations) return [3 /*break*/, 3]; + _a.label = 6; + case 6: + speed = durationMs / setupNumber / 1000; + result.push(speed); + if (!(cooldownIntervalMs > 0.0 && (i % repeatsToCooldown) == 0)) return [3 /*break*/, 8]; + return [4 /*yield*/, new Promise(function (r) { return setTimeout(r, cooldownIntervalMs); })]; + case 7: + _a.sent(); + _a.label = 8; + case 8: + ++i; + return [3 /*break*/, 2]; + case 9: + ret = new Float64Array(result.length); + ret.set(result); + // dispose finvoke + finvoke.dispose(); + return [2 /*return*/, new Uint8Array(ret.buffer)]; + } + }); + }); }; + var addOne = function (x) { return __awaiter(_this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 100); })]; + case 1: + _a.sent(); + return [2 /*return*/, x + 1]; + } + }); + }); }; + this.registerAsyncServerFunc("wasm.TimeExecution", timeExecution); + this.registerAsyncServerFunc("testing.asyncAddOne", addOne); + }; + Instance.prototype.createPackedFuncFromCFunc = function (func) { + var findex = this.env.packedCFuncTable.length; + if (this.env.packedCFuncTableFreeId.length != 0) { + findex = this.env.packedCFuncTableFreeId.pop(); + } + else { + this.env.packedCFuncTable.push(undefined); + } + this.env.packedCFuncTable[findex] = func; + var stack = this.lib.getOrAllocCallStack(); + var outOffset = stack.allocPtrArray(1); + var outPtr = stack.ptrFromOffset(outOffset); + this.lib.checkCall(this.exports + .TVMWasmFuncCreateFromCFunc(findex, outPtr)); + var ret = this.makePackedFunc(this.memory.loadPointer(outPtr)); + this.lib.recycleCallStack(stack); + return ret; + }; + /** + * Set packed function arguments into the location indicated by argsValue and argsCode. + * Allocate new temporary space from the stack if necessary. + * + * @parma stack The call stack + * @param args The input arguments. + * @param argsValue The offset of argsValue. + * @param argsCode The offset of argsCode. + */ + Instance.prototype.setPackedArguments = function (stack, args, argsValue, argsCode) { + for (var i = 0; i < args.length; ++i) { + var val = args[i]; + var tp = typeof val; + var valueOffset = argsValue + i * SizeOf.TVMValue; + var codeOffset = argsCode + i * SizeOf.I32; + if (val instanceof NDArray) { + if (!val.isView) { + stack.storePtr(valueOffset, val.getHandle()); + stack.storeI32(codeOffset, ArgTypeCode.TVMNDArrayHandle); + } + else { + stack.storePtr(valueOffset, val.getHandle()); + stack.storeI32(codeOffset, ArgTypeCode.TVMDLTensorHandle); + } + } + else if (val instanceof Scalar) { + if (val.dtype.startsWith("int") || val.dtype.startsWith("uint")) { + stack.storeI64(valueOffset, val.value); + stack.storeI32(codeOffset, ArgTypeCode.Int); + } + else if (val.dtype.startsWith("float")) { + stack.storeF64(valueOffset, val.value); + stack.storeI32(codeOffset, ArgTypeCode.Float); + } + else { + assert(val.dtype == "handle", "Expect handle"); + stack.storePtr(valueOffset, val.value); + stack.storeI32(codeOffset, ArgTypeCode.TVMOpaqueHandle); + } + } + else if (val instanceof DLDevice) { + stack.storeI32(valueOffset, val.deviceType); + stack.storeI32(valueOffset + SizeOf.I32, val.deviceType); + stack.storeI32(codeOffset, ArgTypeCode.DLDevice); + } + else if (tp == "number") { + stack.storeF64(valueOffset, val); + stack.storeI32(codeOffset, ArgTypeCode.Float); + // eslint-disable-next-line no-prototype-builtins + } + else if (tp == "function" && val.hasOwnProperty("_tvmPackedCell")) { + stack.storePtr(valueOffset, val._tvmPackedCell.getHandle()); + stack.storeI32(codeOffset, ArgTypeCode.TVMPackedFuncHandle); + } + else if (val === null || val == undefined) { + stack.storePtr(valueOffset, 0); + stack.storeI32(codeOffset, ArgTypeCode.Null); + } + else if (tp == "string") { + stack.allocThenSetArgString(valueOffset, val); + stack.storeI32(codeOffset, ArgTypeCode.TVMStr); + } + else if (val instanceof Uint8Array) { + stack.allocThenSetArgBytes(valueOffset, val); + stack.storeI32(codeOffset, ArgTypeCode.TVMBytes); + } + else if (val instanceof Function) { + val = this.toPackedFuncInternal(val, false); + stack.tempArgs.push(val); + stack.storePtr(valueOffset, val._tvmPackedCell.getHandle()); + stack.storeI32(codeOffset, ArgTypeCode.TVMPackedFuncHandle); + } + else if (val instanceof Module) { + stack.storePtr(valueOffset, val.getHandle()); + stack.storeI32(codeOffset, ArgTypeCode.TVMModuleHandle); + } + else if (val instanceof TVMObject) { + stack.storePtr(valueOffset, val.getHandle()); + stack.storeI32(codeOffset, ArgTypeCode.TVMObjectHandle); + } + else { + throw new Error("Unsupported argument type " + tp); + } + } + }; + Instance.prototype.wrapJSFuncAsPackedCFunc = function (func) { + var _this = this; + var lib = this.lib; + return function (argValues, argCodes, nargs, ret, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _handle) { + var jsArgs = []; + // use scope to track js values. + _this.ctx.beginScope(); + for (var i = 0; i < nargs; ++i) { + var valuePtr = argValues + i * SizeOf.TVMValue; + var codePtr = argCodes + i * SizeOf.I32; + var tcode = lib.memory.loadI32(codePtr); + if (tcode == ArgTypeCode.TVMObjectHandle || + tcode == ArgTypeCode.TVMObjectRValueRefArg || + tcode == ArgTypeCode.TVMPackedFuncHandle || + tcode == ArgTypeCode.TVMNDArrayHandle || + tcode == ArgTypeCode.TVMModuleHandle) { + lib.checkCall(lib.exports.TVMCbArgToReturn(valuePtr, codePtr)); + } + tcode = lib.memory.loadI32(codePtr); + jsArgs.push(_this.retValueToJS(valuePtr, tcode, true)); + } + var rv = func.apply(void 0, jsArgs); + // recycle all js object value in function unless we want to retain them. + _this.ctx.endScope(); + if (rv !== undefined && rv !== null) { + var stack = lib.getOrAllocCallStack(); + var valueOffset = stack.allocRawBytes(SizeOf.TVMValue); + var codeOffset = stack.allocRawBytes(SizeOf.I32); + _this.setPackedArguments(stack, [rv], valueOffset, codeOffset); + var valuePtr = stack.ptrFromOffset(valueOffset); + var codePtr = stack.ptrFromOffset(codeOffset); + stack.commitToWasmMemory(); + lib.checkCall(lib.exports.TVMCFuncSetReturn(ret, valuePtr, codePtr, 1)); + lib.recycleCallStack(stack); + } + return 0; + }; + }; + Instance.prototype.makePackedFunc = function (handle) { + var _this = this; + var cell = new PackedFuncCell(handle, this.lib); + var packedFunc = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var stack = _this.lib.getOrAllocCallStack(); + var valueOffset = stack.allocRawBytes(SizeOf.TVMValue * args.length); + var tcodeOffset = stack.allocRawBytes(SizeOf.I32 * args.length); + _this.setPackedArguments(stack, args, valueOffset, tcodeOffset); + var rvalueOffset = stack.allocRawBytes(SizeOf.TVMValue); + var rcodeOffset = stack.allocRawBytes(SizeOf.I32); + var rvaluePtr = stack.ptrFromOffset(rvalueOffset); + var rcodePtr = stack.ptrFromOffset(rcodeOffset); + // commit to wasm memory, till rvalueOffset (the return value don't need to be committed) + stack.commitToWasmMemory(rvalueOffset); + _this.lib.checkCall(_this.exports.TVMFuncCall(cell.getHandle(), stack.ptrFromOffset(valueOffset), stack.ptrFromOffset(tcodeOffset), args.length, rvaluePtr, rcodePtr)); + var ret = _this.retValueToJS(rvaluePtr, _this.memory.loadI32(rcodePtr), false); + _this.lib.recycleCallStack(stack); + return ret; + }; + // Attach attributes to the function type. + // This is because javascript do not allow us to overload call. + var ret = packedFunc; + ret.dispose = function () { + cell.dispose(); + }; + ret._tvmPackedCell = cell; + return ret; + }; + /** + * Creaye return value of the packed func. The value us auto-tracked for dispose. + * @param rvaluePtr The location of rvalue + * @param tcode The type code. + * @param callbackArg Whether it is being used in callbackArg. + * @returns The JS value. + */ + Instance.prototype.retValueToJS = function (rvaluePtr, tcode, callbackArg) { + var _this = this; + switch (tcode) { + case ArgTypeCode.Int: + case ArgTypeCode.UInt: + return this.memory.loadI64(rvaluePtr); + case ArgTypeCode.Float: + return this.memory.loadF64(rvaluePtr); + case ArgTypeCode.TVMOpaqueHandle: { + return this.memory.loadPointer(rvaluePtr); + } + case ArgTypeCode.TVMNDArrayHandle: { + return this.ctx.attachToCurrentScope(new NDArray(this.memory.loadPointer(rvaluePtr), false, this.lib, this.ctx)); + } + case ArgTypeCode.TVMDLTensorHandle: { + assert(callbackArg); + // no need to attach as we are only looking at view + return new NDArray(this.memory.loadPointer(rvaluePtr), true, this.lib, this.ctx); + } + case ArgTypeCode.TVMPackedFuncHandle: { + return this.ctx.attachToCurrentScope(this.makePackedFunc(this.memory.loadPointer(rvaluePtr))); + } + case ArgTypeCode.TVMModuleHandle: { + return this.ctx.attachToCurrentScope(new Module(this.memory.loadPointer(rvaluePtr), this.lib, function (ptr) { + return _this.ctx.attachToCurrentScope(_this.makePackedFunc(ptr)); + })); + } + case ArgTypeCode.TVMObjectHandle: { + var obj = new TVMObject(this.memory.loadPointer(rvaluePtr), this.lib, this.ctx); + var func = this.objFactory.get(obj.typeIndex()); + if (func != undefined) { + return this.ctx.attachToCurrentScope(func(obj.getHandle(), this.lib, this.ctx)); + } + else { + return this.ctx.attachToCurrentScope(obj); + } + } + case ArgTypeCode.Null: return undefined; + case ArgTypeCode.DLDevice: { + var deviceType = this.memory.loadI32(rvaluePtr); + var deviceId = this.memory.loadI32(rvaluePtr + SizeOf.I32); + return this.device(deviceType, deviceId); + } + case ArgTypeCode.TVMStr: { + var ret = this.memory.loadCString(this.memory.loadPointer(rvaluePtr)); + return ret; + } + case ArgTypeCode.TVMBytes: { + return this.memory.loadTVMBytes(this.memory.loadPointer(rvaluePtr)); + } + default: + throw new Error("Unsupported return type code=" + tcode); + } + }; + return Instance; +}()); +/** + * Asynchrously instantiate a new {@link Instance}. + * + * importObject can also be a {@link LibraryProvider} object, + * a WASI object, or an object containing wasmLibraryProvider field. + * We can take benefit of syslib implementations from the Emscripten + * by passing its generated js Module as the imports. + * + * @param bufferSource The source to be compiled. + * @param importObject The import objects. + * @param logger The system logger. + */ +function instantiate(bufferSource, importObject, logger) { + if (importObject === void 0) { importObject = {}; } + if (logger === void 0) { logger = console.log; } + var env = new Environment(importObject, logger); + return WebAssembly.instantiate(bufferSource, env.imports).then(function (result) { + return new Instance(result.module, {}, result.instance, env); + }); +} + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var RPCServerState; +(function (RPCServerState) { + RPCServerState[RPCServerState["InitHeader"] = 0] = "InitHeader"; + RPCServerState[RPCServerState["InitHeaderKey"] = 1] = "InitHeaderKey"; + RPCServerState[RPCServerState["InitServer"] = 2] = "InitServer"; + RPCServerState[RPCServerState["WaitForCallback"] = 3] = "WaitForCallback"; + RPCServerState[RPCServerState["ReceivePacketHeader"] = 4] = "ReceivePacketHeader"; + RPCServerState[RPCServerState["ReceivePacketBody"] = 5] = "ReceivePacketBody"; +})(RPCServerState || (RPCServerState = {})); + +/** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ +const proxyMarker = Symbol("Comlink.proxy"); +const createEndpoint = Symbol("Comlink.endpoint"); +const releaseProxy = Symbol("Comlink.releaseProxy"); +const finalizer = Symbol("Comlink.finalizer"); +const throwMarker = Symbol("Comlink.thrown"); +const isObject = val => typeof val === "object" && val !== null || typeof val === "function"; +/** + * Internal transfer handle to handle objects marked to proxy. + */ +const proxyTransferHandler = { + canHandle: val => isObject(val) && val[proxyMarker], + serialize(obj) { + const { + port1, + port2 + } = new MessageChannel(); + expose(obj, port1); + return [port2, [port2]]; + }, + deserialize(port) { + port.start(); + return wrap(port); + } +}; +/** + * Internal transfer handler to handle thrown exceptions. + */ +const throwTransferHandler = { + canHandle: value => isObject(value) && throwMarker in value, + serialize({ + value + }) { + let serialized; + if (value instanceof Error) { + serialized = { + isError: true, + value: { + message: value.message, + name: value.name, + stack: value.stack + } + }; + } else { + serialized = { + isError: false, + value + }; + } + return [serialized, []]; + }, + deserialize(serialized) { + if (serialized.isError) { + throw Object.assign(new Error(serialized.value.message), serialized.value); + } + throw serialized.value; + } +}; +/** + * Allows customizing the serialization of certain values. + */ +const transferHandlers = new Map([["proxy", proxyTransferHandler], ["throw", throwTransferHandler]]); +function isAllowedOrigin(allowedOrigins, origin) { + for (const allowedOrigin of allowedOrigins) { + if (origin === allowedOrigin || allowedOrigin === "*") { + return true; + } + if (allowedOrigin instanceof RegExp && allowedOrigin.test(origin)) { + return true; + } + } + return false; +} +function expose(obj, ep = globalThis, allowedOrigins = ["*"]) { + ep.addEventListener("message", function callback(ev) { + if (!ev || !ev.data) { + return; + } + if (!isAllowedOrigin(allowedOrigins, ev.origin)) { + console.warn(`Invalid origin '${ev.origin}' for comlink proxy`); + return; + } + const { + id, + type, + path + } = Object.assign({ + path: [] + }, ev.data); + const argumentList = (ev.data.argumentList || []).map(fromWireValue); + let returnValue; + try { + const parent = path.slice(0, -1).reduce((obj, prop) => obj[prop], obj); + const rawValue = path.reduce((obj, prop) => obj[prop], obj); + switch (type) { + case "GET" /* MessageType.GET */: + { + returnValue = rawValue; + } + break; + case "SET" /* MessageType.SET */: + { + parent[path.slice(-1)[0]] = fromWireValue(ev.data.value); + returnValue = true; + } + break; + case "APPLY" /* MessageType.APPLY */: + { + returnValue = rawValue.apply(parent, argumentList); + } + break; + case "CONSTRUCT" /* MessageType.CONSTRUCT */: + { + const value = new rawValue(...argumentList); + returnValue = proxy(value); + } + break; + case "ENDPOINT" /* MessageType.ENDPOINT */: + { + const { + port1, + port2 + } = new MessageChannel(); + expose(obj, port2); + returnValue = transfer(port1, [port1]); + } + break; + case "RELEASE" /* MessageType.RELEASE */: + { + returnValue = undefined; + } + break; + default: + return; + } + } catch (value) { + returnValue = { + value, + [throwMarker]: 0 + }; + } + Promise.resolve(returnValue).catch(value => { + return { + value, + [throwMarker]: 0 + }; + }).then(returnValue => { + const [wireValue, transferables] = toWireValue(returnValue); + ep.postMessage(Object.assign(Object.assign({}, wireValue), { + id + }), transferables); + if (type === "RELEASE" /* MessageType.RELEASE */) { + // detach and deactive after sending release response above. + ep.removeEventListener("message", callback); + closeEndPoint(ep); + if (finalizer in obj && typeof obj[finalizer] === "function") { + obj[finalizer](); + } + } + }).catch(error => { + // Send Serialization Error To Caller + const [wireValue, transferables] = toWireValue({ + value: new TypeError("Unserializable return value"), + [throwMarker]: 0 + }); + ep.postMessage(Object.assign(Object.assign({}, wireValue), { + id + }), transferables); + }); + }); + if (ep.start) { + ep.start(); + } +} +function isMessagePort(endpoint) { + return endpoint.constructor.name === "MessagePort"; +} +function closeEndPoint(endpoint) { + if (isMessagePort(endpoint)) endpoint.close(); +} +function wrap(ep, target) { + return createProxy(ep, [], target); +} +function throwIfProxyReleased(isReleased) { + if (isReleased) { + throw new Error("Proxy has been released and is not useable"); + } +} +function releaseEndpoint(ep) { + return requestResponseMessage(ep, { + type: "RELEASE" /* MessageType.RELEASE */ + }).then(() => { + closeEndPoint(ep); + }); +} +const proxyCounter = new WeakMap(); +const proxyFinalizers = "FinalizationRegistry" in globalThis && new FinalizationRegistry(ep => { + const newCount = (proxyCounter.get(ep) || 0) - 1; + proxyCounter.set(ep, newCount); + if (newCount === 0) { + releaseEndpoint(ep); + } +}); +function registerProxy(proxy, ep) { + const newCount = (proxyCounter.get(ep) || 0) + 1; + proxyCounter.set(ep, newCount); + if (proxyFinalizers) { + proxyFinalizers.register(proxy, ep, proxy); + } +} +function unregisterProxy(proxy) { + if (proxyFinalizers) { + proxyFinalizers.unregister(proxy); + } +} +function createProxy(ep, path = [], target = function () {}) { + let isProxyReleased = false; + const proxy = new Proxy(target, { + get(_target, prop) { + throwIfProxyReleased(isProxyReleased); + if (prop === releaseProxy) { + return () => { + unregisterProxy(proxy); + releaseEndpoint(ep); + isProxyReleased = true; + }; + } + if (prop === "then") { + if (path.length === 0) { + return { + then: () => proxy + }; + } + const r = requestResponseMessage(ep, { + type: "GET" /* MessageType.GET */, + path: path.map(p => p.toString()) + }).then(fromWireValue); + return r.then.bind(r); + } + return createProxy(ep, [...path, prop]); + }, + set(_target, prop, rawValue) { + throwIfProxyReleased(isProxyReleased); + // FIXME: ES6 Proxy Handler `set` methods are supposed to return a + // boolean. To show good will, we return true asynchronously ¯\_(ツ)_/¯ + const [value, transferables] = toWireValue(rawValue); + return requestResponseMessage(ep, { + type: "SET" /* MessageType.SET */, + path: [...path, prop].map(p => p.toString()), + value + }, transferables).then(fromWireValue); + }, + apply(_target, _thisArg, rawArgumentList) { + throwIfProxyReleased(isProxyReleased); + const last = path[path.length - 1]; + if (last === createEndpoint) { + return requestResponseMessage(ep, { + type: "ENDPOINT" /* MessageType.ENDPOINT */ + }).then(fromWireValue); + } + // We just pretend that `bind()` didn’t happen. + if (last === "bind") { + return createProxy(ep, path.slice(0, -1)); + } + const [argumentList, transferables] = processArguments(rawArgumentList); + return requestResponseMessage(ep, { + type: "APPLY" /* MessageType.APPLY */, + path: path.map(p => p.toString()), + argumentList + }, transferables).then(fromWireValue); + }, + construct(_target, rawArgumentList) { + throwIfProxyReleased(isProxyReleased); + const [argumentList, transferables] = processArguments(rawArgumentList); + return requestResponseMessage(ep, { + type: "CONSTRUCT" /* MessageType.CONSTRUCT */, + path: path.map(p => p.toString()), + argumentList + }, transferables).then(fromWireValue); + } + }); + registerProxy(proxy, ep); + return proxy; +} +function myFlat(arr) { + return Array.prototype.concat.apply([], arr); +} +function processArguments(argumentList) { + const processed = argumentList.map(toWireValue); + return [processed.map(v => v[0]), myFlat(processed.map(v => v[1]))]; +} +const transferCache = new WeakMap(); +function transfer(obj, transfers) { + transferCache.set(obj, transfers); + return obj; +} +function proxy(obj) { + return Object.assign(obj, { + [proxyMarker]: true + }); +} +function toWireValue(value) { + for (const [name, handler] of transferHandlers) { + if (handler.canHandle(value)) { + const [serializedValue, transferables] = handler.serialize(value); + return [{ + type: "HANDLER" /* WireValueType.HANDLER */, + name, + value: serializedValue + }, transferables]; + } + } + return [{ + type: "RAW" /* WireValueType.RAW */, + value + }, transferCache.get(value) || []]; +} +function fromWireValue(value) { + switch (value.type) { + case "HANDLER" /* WireValueType.HANDLER */: + return transferHandlers.get(value.name).deserialize(value.value); + case "RAW" /* WireValueType.RAW */: + return value.value; + } +} +function requestResponseMessage(ep, msg, transfers) { + return new Promise(resolve => { + const id = generateUUID(); + ep.addEventListener("message", function l(ev) { + if (!ev.data || !ev.data.id || ev.data.id !== id) { + return; + } + ep.removeEventListener("message", l); + resolve(ev.data); + }); + if (ep.start) { + ep.start(); + } + ep.postMessage(Object.assign({ + id + }, msg), transfers); + }); +} +function generateUUID() { + return new Array(4).fill(0).map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16)).join("-"); +} + +// Unique ID creation requires a high quality random # generator. In the browser we therefore +// require the crypto API and do not support built-in fallback to lower quality random number +// generators (like Math.random()). +let getRandomValues; +const rnds8 = new Uint8Array(16); +function rng() { + // lazy load so that environments that need to polyfill have a chance to do so + if (!getRandomValues) { + // getRandomValues needs to be invoked in a context where "this" is a Crypto implementation. + getRandomValues = typeof crypto !== 'undefined' && crypto.getRandomValues && crypto.getRandomValues.bind(crypto); + if (!getRandomValues) { + throw new Error('crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported'); + } + } + return getRandomValues(rnds8); +} + +/** + * Convert array of 16 byte values to UUID string format of the form: + * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + */ + +const byteToHex = []; +for (let i = 0; i < 256; ++i) { + byteToHex.push((i + 0x100).toString(16).slice(1)); +} +function unsafeStringify(arr, offset = 0) { + // Note: Be careful editing this code! It's been tuned for performance + // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434 + return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + '-' + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + '-' + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + '-' + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + '-' + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); +} + +const randomUUID = typeof crypto !== 'undefined' && crypto.randomUUID && crypto.randomUUID.bind(crypto); +var native = { + randomUUID +}; + +function v4(options, buf, offset) { + if (native.randomUUID && !buf && !options) { + return native.randomUUID(); + } + options = options || {}; + const rnds = options.random || (options.rng || rng)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` + + rnds[6] = rnds[6] & 0x0f | 0x40; + rnds[8] = rnds[8] & 0x3f | 0x80; // Copy bytes to buffer, if provided + + if (buf) { + offset = offset || 0; + for (let i = 0; i < 16; ++i) { + buf[offset + i] = rnds[i]; + } + return buf; + } + return unsafeStringify(rnds); +} + +export { __spreadArray as _, __assign as a, __awaiter as b, __generator as c, detectGPUDevice as d, expose as e, instantiate as i, proxy as p, v4 as v, wrap as w }; diff --git a/packages/headless/dist/worker-cc79b531.js b/packages/headless/dist/worker-cc79b531.js new file mode 100644 index 0000000..d89f262 --- /dev/null +++ b/packages/headless/dist/worker-cc79b531.js @@ -0,0 +1,412 @@ +import { b as __awaiter, c as __generator, d as detectGPUDevice, i as instantiate, v as v4, e as expose } from './v4-2119d9d5.js'; + +var LLMInstance = /** @class */ (function () { + function LLMInstance(config, sentencePieceProcessor) { + this.config = config; + this.tvm = undefined; + this.tokenizer = undefined; + this.model = undefined; + this.spp = sentencePieceProcessor; + this.processing = false; + } + LLMInstance.prototype.isInitialized = function () { + return this.model != undefined; + }; + LLMInstance.prototype.init = function (cb) { + return __awaiter(this, void 0, void 0, function () { + var wasmSource, _a, output, err_1, _b; + var _this = this; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: + if (this.model) { + return [2 /*return*/]; + } + return [4 /*yield*/, fetch(this.config.wasmUrl)]; + case 1: return [4 /*yield*/, (_c.sent()).arrayBuffer()]; + case 2: + wasmSource = _c.sent(); + _a = this; + return [4 /*yield*/, instantiate(new Uint8Array(wasmSource), + //@ts-ignore + new EmccWASI(), console.log)]; + case 3: + _a.tvm = _c.sent(); + _c.label = 4; + case 4: + _c.trys.push([4, 6, , 7]); + return [4 /*yield*/, detectGPUDevice()]; + case 5: + output = _c.sent(); + if (output !== undefined) { + this.tvm.initWebGPU(output.device); + } + else { + throw Error("This browser env do not support WebGPU"); + } + return [3 /*break*/, 7]; + case 6: + err_1 = _c.sent(); + throw Error("Find an error initializing WebGPU: " + err_1.toString()); + case 7: + this.tvm.registerInitProgressCallback(cb); + return [4 /*yield*/, this.tvm.fetchNDArrayCache(this.config.cacheUrl, this.tvm.webgpu())]; + case 8: + _c.sent(); + _b = this; + return [4 /*yield*/, this.spp()(this.config.tokenizerUrl)]; + case 9: + _b.tokenizer = _c.sent(); + this.model = this.tvm.withNewScope(function () { + return new LLMInstanceScope(_this.tvm, _this.tokenizer, _this.config.maxWindowSize); + }); + return [2 /*return*/, this.model.init()]; + } + }); + }); + }; + LLMInstance.prototype.generate = function (request, cb) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (this.processing) { + return [2 /*return*/]; + } + this.processing = true; + return [4 /*yield*/, this.model.generate(request, cb)]; + case 1: + _a.sent(); + this.processing = false; + return [2 /*return*/]; + } + }); + }); + }; + return LLMInstance; +}()); +var LLMInstanceScope = /** @class */ (function () { + function LLMInstanceScope(tvm, tokenizer, maxWindowSize) { + if (maxWindowSize === void 0) { maxWindowSize = 2048; } + this.tvm = tvm; + this.tokenizer = tokenizer; + this.bosTokenId = 1; + this.eosTokenId = 2; + this.maxWindowSize = maxWindowSize; + this.device = this.tvm.webgpu(); + this.vm = this.tvm.detachFromCurrentScope(this.tvm.createVirtualMachine(this.device)); + this.encoding = this.tvm.detachFromCurrentScope(this.vm.getFunction("encoding")); + this.decoding = this.tvm.detachFromCurrentScope(this.vm.getFunction("decoding")); + this.params = this.tvm.detachFromCurrentScope(this.tvm.getParamsFromCache("param", this.tvm.cacheMetadata.ParamSize)); + var fcreateCache = this.vm.getFunction("create_kv_cache"); + this.fclearKVCaches = this.tvm.detachFromCurrentScope(this.tvm.getGlobalFunc("vm.builtin.attention_kv_cache_array_clear")); + // use extern config for now + this.kvCache = this.tvm.detachFromCurrentScope(fcreateCache()); + // fill with pad token + this.logitsOnCPU = undefined; + this.kvCacheLength = 0; + this.lastMessageId = ""; + } + LLMInstanceScope.prototype.init = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.tvm.asyncLoadWebGPUPiplines(this.vm.getInternalModule())]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + LLMInstanceScope.prototype.getTokensFromStart = function (conversation, maxTokens) { + return __awaiter(this, void 0, void 0, function () { + var tokens, i, message, text, messageTokens, _a, _b, _c, _d, _e, _f; + return __generator(this, function (_g) { + switch (_g.label) { + case 0: + this.clearKVCache(); + tokens = []; + i = conversation.messages.length - 1; + _g.label = 1; + case 1: + if (!(i >= 0)) return [3 /*break*/, 5]; + message = conversation.messages[i]; + text = "".concat(message.role, ": ").concat(message.text, "\n"); + return [4 /*yield*/, this.tokenizer.encodeIds(text)]; + case 2: + messageTokens = _g.sent(); + if (tokens.length + messageTokens.length + maxTokens > + this.maxWindowSize) { + return [3 /*break*/, 5]; + } + _b = (_a = tokens.unshift).apply; + _c = [tokens]; + return [4 /*yield*/, this.tokenizer.encodeIds(text)]; + case 3: + _b.apply(_a, _c.concat([(_g.sent())])); + _g.label = 4; + case 4: + i--; + return [3 /*break*/, 1]; + case 5: + _e = (_d = tokens.unshift).apply; + _f = [tokens]; + return [4 /*yield*/, this.tokenizer.encodeIds(conversation.systemPrompt)]; + case 6: + _e.apply(_d, _f.concat([(_g.sent())])); + tokens.unshift(this.bosTokenId); + return [2 /*return*/, tokens]; + } + }); + }); + }; + LLMInstanceScope.prototype.getTokens = function (conversation, maxTokens) { + return __awaiter(this, void 0, void 0, function () { + var startMsgIdx, i, tokens, i, message, text, messageTokens, _a, _b, _c; + return __generator(this, function (_d) { + switch (_d.label) { + case 0: + if (!(this.kvCacheLength == 0)) return [3 /*break*/, 2]; + return [4 /*yield*/, this.getTokensFromStart(conversation, maxTokens)]; + case 1: + // Case 1 + return [2 /*return*/, _d.sent()]; + case 2: + startMsgIdx = 0; + for (i = conversation.messages.length - 1; i >= 0; i--) { + if (conversation.messages[i].id == this.lastMessageId) { + startMsgIdx = i + 1; + break; + } + } + if (!(startMsgIdx == 0)) return [3 /*break*/, 4]; + return [4 /*yield*/, this.getTokensFromStart(conversation, maxTokens)]; + case 3: + // Case 2 + return [2 /*return*/, _d.sent()]; + case 4: + tokens = [this.eosTokenId]; + i = startMsgIdx; + _d.label = 5; + case 5: + if (!(i < conversation.messages.length)) return [3 /*break*/, 11]; + message = conversation.messages[i]; + text = "".concat(message.role, ": ").concat(message.text); + return [4 /*yield*/, this.tokenizer.encodeIds(text)]; + case 6: + messageTokens = _d.sent(); + if (!(tokens.length + messageTokens.length + maxTokens > + this.maxWindowSize)) return [3 /*break*/, 8]; + return [4 /*yield*/, this.getTokensFromStart(conversation, maxTokens)]; + case 7: + // Case 4 + return [2 /*return*/, _d.sent()]; + case 8: + _b = (_a = tokens.push).apply; + _c = [tokens]; + return [4 /*yield*/, this.tokenizer.encodeIds(text)]; + case 9: + _b.apply(_a, _c.concat([(_d.sent())])); + _d.label = 10; + case 10: + i++; + return [3 /*break*/, 5]; + case 11: + // Case 3 + return [2 /*return*/, tokens]; + } + }); + }); + }; + LLMInstanceScope.prototype.generate = function (request, cb) { + return __awaiter(this, void 0, void 0, function () { + var conversation, maxTokens, assistantRoleName, stopTexts, tokens, _a, _b, _c, _d, _e, _f, inputTokenLength, outputText, tstart, tend, step, id, input, logits, nextToken, outputTokens, stopPos, stop_1, i; + return __generator(this, function (_g) { + switch (_g.label) { + case 0: + conversation = request.conversation, maxTokens = request.maxTokens, assistantRoleName = request.assistantRoleName, stopTexts = request.stopTexts; + return [4 /*yield*/, this.getTokens(conversation, maxTokens)]; + case 1: + tokens = _g.sent(); + _b = (_a = tokens.push).apply; + _c = [tokens]; + return [4 /*yield*/, this.tokenizer.encodeIds("".concat(assistantRoleName, ":"))]; + case 2: + _b.apply(_a, _c.concat([(_g.sent())])); + _e = (_d = console).log; + _f = ["debug: "]; + return [4 /*yield*/, this.tokenizer.decodeIds(tokens)]; + case 3: + _e.apply(_d, _f.concat([_g.sent()])); + inputTokenLength = tokens.length; + outputText = ""; + tstart = 0, tend = 0, step = 0; + id = v4(); + _g.label = 4; + case 4: + if (!(step < maxTokens)) return [3 /*break*/, 7]; + this.tvm.beginScope(); + tstart = performance.now(); + if (step == 0) { + input = this.tvm.empty([1, tokens.length], "int32", this.device); + input.copyFrom(tokens); + } + else { + input = this.tvm.empty([1, 1], "int32", this.device); + input.copyFrom(tokens.slice(tokens.length - 1)); + } + logits = this.tvm.detachFromCurrentScope(this.forward(input, this.kvCacheLength + inputTokenLength + step)); + this.tvm.endScope(); + return [4 /*yield*/, this.sampleTokenFromLogits(logits)]; + case 5: + nextToken = _g.sent(); + logits.dispose(); + tokens.push(nextToken); + outputTokens = tokens.slice(inputTokenLength); + outputText = this.tokenizer.decodeIds(outputTokens); + tend = performance.now(); + if (nextToken == this.eosTokenId) + return [3 /*break*/, 7]; + stopPos = outputText.lastIndexOf(""); + if (stopPos != -1) { + outputText = outputText.substring(0, stopPos); + return [3 /*break*/, 7]; + } + stop_1 = false; + for (i = 0; i < stopTexts.length; i++) { + if (outputText.endsWith(stopTexts[i])) { + outputText = outputText.substring(0, outputText.length - stopTexts[i].length); + stop_1 = true; + break; + } + } + if (stop_1) + return [3 /*break*/, 7]; + if (step != 0) { + cb({ + requestId: id, + step: step, + outputText: outputText, + stats: { + totalDecodingSeconds: (tend - tstart) / 1000, + totalDecodedTokens: tokens.length - inputTokenLength, + totalEncodedTokens: inputTokenLength, + }, + isFinished: false, + }); + } + _g.label = 6; + case 6: + step++; + return [3 /*break*/, 4]; + case 7: + this.kvCacheLength += tokens.length - 1; + this.lastMessageId = id; + cb({ + requestId: id, + outputText: outputText, + step: step, + stats: { + totalDecodingSeconds: (tend - tstart) / 1000, + totalDecodedTokens: tokens.length - inputTokenLength, + totalEncodedTokens: inputTokenLength, + }, + isFinished: true, + }); + return [2 /*return*/]; + } + }); + }); + }; + LLMInstanceScope.prototype.dispose = function () { + // note: tvm instance is not owned by this class + this.params.dispose(); + this.decoding.dispose(); + this.encoding.dispose(); + this.vm.dispose(); + this.kvCache.dispose(); + this.fclearKVCaches.dispose(); + if (this.logitsOnCPU != undefined) { + this.logitsOnCPU.dispose(); + } + }; + LLMInstanceScope.prototype.clearKVCache = function () { + this.fclearKVCaches(this.kvCache); + this.kvCacheLength = 0; + this.lastMessageId = ""; + }; + LLMInstanceScope.prototype.forward = function (inputs, curPos) { + this.tvm.beginScope(); + var retValue; + var seqLenShape = this.tvm.makeShapeTuple([curPos]); + if (inputs.shape[1] > 1) { + retValue = this.encoding(inputs, seqLenShape, this.kvCache, this.params); + } + else { + retValue = this.decoding(inputs, seqLenShape, this.kvCache, this.params); + } + var logits = this.tvm.detachFromCurrentScope(retValue.get(0)); + this.tvm.endScope(); + this.tvm.attachToCurrentScope(logits); + return logits; + }; + // NOTE: caller must call device.sync() + LLMInstanceScope.prototype.updateLogitsOnCPU = function (logits) { + if (this.logitsOnCPU == undefined) { + this.logitsOnCPU = this.tvm.detachFromCurrentScope(this.tvm.empty(logits.shape, logits.dtype, this.tvm.cpu())); + } + else { + if (logits.shape[0] != this.logitsOnCPU.shape[0]) { + throw Error("We expect the size of logits to remain unchanged"); + } + } + this.logitsOnCPU.copyFrom(logits); + }; + LLMInstanceScope.prototype.sampleTokenFromLogits = function (logits, temperature, top_p) { + if (temperature === void 0) { temperature = 0.8; } + if (top_p === void 0) { top_p = 0.95; } + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + this.tvm.beginScope(); + this.updateLogitsOnCPU(logits); + this.tvm.endScope(); + return [4 /*yield*/, this.device.sync()]; + case 1: + _a.sent(); + return [2 /*return*/, this.tvm.sampleTopPFromLogits(this.logitsOnCPU, temperature, top_p)]; + } + }); + }); + }; + return LLMInstanceScope; +}()); + +var config = { + kvConfig: { + numLayers: 64, + shape: [32, 32, 128], + dtype: 'float32', + }, + wasmUrl: 'https://huggingface.co/mrick/react-llm/resolve/main/models/vicuna-7b-v1/vicuna-7b-v1_webgpu.wasm', + cacheUrl: 'https://huggingface.co/mrick/react-llm/resolve/main/models/vicuna-7b-v1/params/', + tokenizerUrl: 'https://huggingface.co/mrick/react-llm/resolve/main/models/vicuna-7b-v1/tokenizer.model', + sentencePieceJsUrl: 'https://cdn.matt-rickard.com/code/sentencepiece.js', + tvmRuntimeJsUrl: 'https://cdn.matt-rickard.com/code/tvmjs_runtime.wasi.js', + maxWindowSize: 2048, +}; +var instance = new LLMInstance(config, function () { return globalThis.sentencepiece.sentencePieceProcessor; }); +var worker = { + init: function (callback) { + instance.init(callback); + }, + generate: function (request, cb) { + instance.generate(request, cb); + } +}; +importScripts.apply(void 0, [ + config.sentencePieceJsUrl, config.tvmRuntimeJsUrl +]); +expose(worker); diff --git a/packages/headless/package.json b/packages/headless/package.json new file mode 100644 index 0000000..0fcdd52 --- /dev/null +++ b/packages/headless/package.json @@ -0,0 +1,63 @@ +{ + "name": "@react-llm/headless", + "version": "0.0.3", + "author": "Matt Rickard ", + "license": "MIT", + "module": "dist/index.js", + "type": "module", + "types": "dist/types/index.d.ts", + "keywords": [ + "chatgpt", + "llm", + "headless", + "react" + ], + "scripts": { + "build": "npm run clean && rollup -c", + "dev": "rollup -c -w", + "clean": "rm -rf dist" + }, + "dependencies": { + "@babel/plugin-transform-modules-commonjs": "^7.21.5", + "@surma/rollup-plugin-off-main-thread": "^2.2.3", + "@types/node": "20.1.1", + "@types/react": "18.2.6", + "@types/react-dom": "18.2.4", + "autoprefixer": "10.4.14", + "comlink": "^4.4.1", + "eslint": "8.40.0", + "eslint-config-next": "13.4.1", + "react": "18.2.0", + "react-dom": "18.2.0", + "typescript": "5.0.4", + "uuid": "^9.0.0", + "zustand": "^4.3.8" + }, + "devDependencies": { + "@babel/cli": "^7.21.5", + "@babel/core": "^7.21.8", + "@babel/preset-env": "^7.21.5", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.5", + "@rollup/plugin-alias": "^5.0.0", + "@rollup/plugin-babel": "^6.0.3", + "@rollup/plugin-commonjs": "^24.1.0", + "@rollup/plugin-json": "^6.0.0", + "@rollup/plugin-node-resolve": "^15.0.2", + "@types/uuid": "^9.0.1", + "@webgpu/types": "^0.1.32", + "@zerollup/ts-transform-paths": "^1.7.18", + "babel-plugin-module-resolver": "^5.0.0", + "rollup": "^3.21.6", + "rollup-plugin-dts": "^5.3.0", + "rollup-plugin-node-resolve": "^5.2.0", + "rollup-plugin-terser": "^7.0.2", + "rollup-plugin-tsconfig-paths": "^1.5.0", + "rollup-plugin-typescript2": "^0.34.1", + "rollup-plugin-web-worker-loader": "^1.6.1", + "tsconfig-paths-webpack-plugin": "^4.0.1", + "ttypescript": "^1.5.15", + "webpack": "^5.82.1", + "webpack-cli": "^5.1.1" + } +} diff --git a/packages/headless/rollup.config.js b/packages/headless/rollup.config.js new file mode 100644 index 0000000..8b376f3 --- /dev/null +++ b/packages/headless/rollup.config.js @@ -0,0 +1,35 @@ +import babel from "@rollup/plugin-babel"; +import commonjs from "@rollup/plugin-commonjs"; +import json from "@rollup/plugin-json"; +import resolve, { nodeResolve } from "@rollup/plugin-node-resolve"; +import OMT from "@surma/rollup-plugin-off-main-thread"; +import typescript from "rollup-plugin-typescript2"; + +export default [ + { + input: "src/index.ts", + output: { + dir: "dist", + format: "esm", + sourceMap: true, + }, + external: ["react", "react-dom"], + plugins: [ + babel({ + exclude: "node_modules/**", + presets: ["@babel/preset-react", "@babel/preset-typescript"], + babelHelpers: "bundled", + }), + nodeResolve(), + typescript({ + tsconfig: "tsconfig.json", + sourceMap: false, + useTsconfigDeclarationDir: true, + }), + OMT(), + commonjs(), + resolve({ preferBuiltins: true }), + json(), + ], + }, +]; diff --git a/packages/headless/src/hooks/useConversationStore.tsx b/packages/headless/src/hooks/useConversationStore.tsx new file mode 100644 index 0000000..bd066a6 --- /dev/null +++ b/packages/headless/src/hooks/useConversationStore.tsx @@ -0,0 +1,178 @@ +import { v4 as uuidv4 } from "uuid"; +import { create } from "zustand"; +import { persist } from "zustand/middleware"; +import { Conversation, Message } from "../types/chat"; + +export interface ConversationStore { + conversations: Conversation[]; + currentConversationId: string; + setConversationId: (conversationId: string) => void; + + addMessage: (conversationId: string, message: Message) => void; + getConversation: (conversationId: string) => Conversation | undefined; + + setConversationTitle: (conversationId: string, title: string) => void; + + getAllConversations: () => Conversation[]; + deleteMessages: (conversationId: string) => void; + + deleteConversation: (conversationId: string) => void; + createConversation: (conversation: Conversation) => void; + deleteAllConversations: () => void; +} + +export const defaultSystemPrompt = + "A chat between a curious user and a AI chatbot named SmartestChild on AIM who responds with lowercase, frequent emojis, and 2000s internet abbreviations."; + +const useConversationStore = create()( + persist( + (set, get) => { + const initialConversation = { + id: uuidv4(), + title: "Untitled", + updatedAt: new Date().getTime(), + systemPrompt: defaultSystemPrompt, + createdAt: new Date().getTime(), + messages: [] as Message[], + }; + + return { + conversations: [initialConversation], + currentConversationId: initialConversation.id, + createConversation: (conversation: Conversation) => { + set((state) => { + return { + currentConversationId: conversation.id, + conversations: [...state.conversations, conversation], + }; + }); + }, + setConversationTitle(conversationId, title) { + set((state) => { + const conversation = state.conversations.find( + (c) => c.id === conversationId + ); + if (!conversation) { + return state; + } + return { + conversations: [ + ...state.conversations.filter((c) => c.id !== conversationId), + { + ...conversation, + title, + }, + ], + }; + }); + }, + deleteConversation(conversationId: string) { + set((state) => { + return { + conversations: state.conversations.filter( + (c) => c.id !== conversationId + ), + }; + }); + }, + setConversationId: (conversationId: string) => { + const conversationExists = get().conversations.some( + (c) => c.id === conversationId + ); + if (!conversationExists) { + throw new Error("Invalid conversation id"); + } + + set((state) => { + return { + ...state, + currentConversationId: conversationId, + }; + }); + }, + deleteAllConversations: () => { + set((state) => { + return { + conversations: [], + }; + }); + }, + deleteMessages: (conversationId) => { + set((state) => { + const conversation = state.conversations.find( + (c) => c.id === conversationId + ); + if (!conversation) { + return state; + } + return { + conversations: [ + ...state.conversations.filter((c) => c.id !== conversationId), + { + ...conversation, + updatedAt: new Date().getTime(), + messages: [], + }, + ], + }; + }); + }, + getConversation(conversationId) { + return get().conversations.find((c) => c.id === conversationId); + }, + getAllConversations() { + return get().conversations; + }, + addMessage: (conversationId, message) => { + set((state) => { + const conversation = state.conversations.find( + (c) => c.id === conversationId + ); + if (!conversation) { + return state; + } + const existingMessage = conversation.messages.find( + (m) => m.id === message.id + ); + if (existingMessage) { + // Update message + return { + conversations: [ + ...state.conversations.filter((c) => c.id !== conversationId), + { + ...conversation, + updatedAt: new Date().getTime(), + messages: [ + ...conversation.messages.filter( + (m) => m.id !== message.id + ), + message, + ], + }, + ], + }; + } + // Add message + return { + conversations: [ + ...state.conversations.filter((c) => c.id !== conversationId), + { + ...conversation, + updatedAt: new Date().getTime(), + messages: [...conversation.messages, message], + }, + ], + }; + }); + }, + }; + }, + + { + name: "chat-store", + getStorage: () => sessionStorage, + } + ) +); + +export default useConversationStore; diff --git a/packages/headless/src/hooks/useLLM.tsx b/packages/headless/src/hooks/useLLM.tsx new file mode 100644 index 0000000..c66c58a --- /dev/null +++ b/packages/headless/src/hooks/useLLM.tsx @@ -0,0 +1,257 @@ +import { detectGPUDevice } from "@/worker/lib/tvm"; +import { InitProgressReport } from "@/worker/lib/tvm/runtime"; +import * as Comlink from "comlink"; +import { Remote } from "comlink"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { v4 as uuidv4 } from "uuid"; +import { Conversation } from "../types/chat"; +import { + GenerateTextRequest, + GenerateTextResponse, + ModelWorker, +} from "../types/worker_message"; +import useConversationStore, { + defaultSystemPrompt, +} from "./useConversationStore"; +import useStore from "./useStore"; + +export type UseLLMParams = { + autoInit?: boolean; +}; + +const initialProgress = { + type: "init" as const, + progress: 0, + timeElapsed: 0, + currentChunk: 0, + totalChunks: 0, + fetchedBytes: 0, + totalBytes: 0, +}; + +export type GPUDeviceInfo = { + adapter: GPUAdapter | null; + device: GPUDevice | null; + adapterInfo: GPUAdapterInfo | null; + checked: boolean; + unsupportedReason: string | null; +}; + +export type UseLLMResponse = { + // Conversation returns the current conversation object. + conversation: Conversation | undefined; + + // AllConversations returns all conversations sorted by updatedAt. + allConversations: Conversation[] | undefined; + + // LoadingStatus returns the current loading status. + loadingStatus: InitProgressReport; + + // IsGenerating returns whether the model is currently generating. Concurrent generation is not supported. + isGenerating: boolean; + + // CreateConversation creates a new conversation and sets it as the current conversation. + createConversation: (title?: string, prompt?: string) => void; + + // SetConversationId sets the current conversation id. + setConversationId: (conversationId: string) => void; + + // DeleteConversation deletes a conversation. + deleteConversation: (conversationId: string) => void; + + // DeleteAllConversations deletes all conversations. + deleteAllConversations: () => void; + + // DeleteMessages deletes all messages in the current conversation. + deleteMessages: () => void; + + // SetConversationTitle sets the title of a conversation. + setConversationTitle: (conversationId: string, title: string) => void; + + // OnMessage returns the current onMessage callback. + onMessage: (msg: GenerateTextResponse) => void; + + // SetOnMessage sets the onMessage callback. This callback is called whenever a new message is generated by the model. + setOnMessage: (cb: (msg: GenerateTextResponse) => void) => void; + + // UserRoleName returns the current user role name. The default is "user". + userRoleName: string; + + // SetUserRoleName sets the user role name. + setUserRoleName: (roleName: string) => void; + + // AssistantRoleName returns the current assistant role name. The default is "assistant". + assistantRoleName: string; + + // SetAssistantRoleName sets the assistant role name. + setAssistantRoleName: (roleName: string) => void; + + // GpuDevice returns the current GPU device info. If GPU is not supported, this will return an object with unsupportedReason set. + gpuDevice: GPUDeviceInfo; + + // Send sends a message to the model for generation. + send: (text: string, maxToken: number, stopSequences: string[]) => void; + + // Init initializes the model. + init: () => void; +}; + +export const useLLMContext = (): UseLLMResponse => { + const [loadingStatus, setLoadingStatus] = + useState(initialProgress); + const [isGenerating, setIsGenerating] = useState(false); + const workerRef = useRef>(); + const cStore = useStore(useConversationStore, (state) => state); + const [userRoleName, setUserRoleName] = useState("user"); + const [assistantRoleName, setAssistantRoleName] = + useState("assistant"); + + const [gpuDevice, setGpuDevice] = useState({ + adapter: null, + device: null, + adapterInfo: null, + checked: false, + unsupportedReason: null, + }); + + useEffect(() => { + if (!gpuDevice || !gpuDevice.checked) { + detectGPUDevice() + .then((resp) => { + if (resp) { + setGpuDevice({ + unsupportedReason: null, + checked: true, + adapter: resp.adapter, + device: resp.device, + adapterInfo: resp.adapterInfo, + }); + } else { + setGpuDevice({ + ...gpuDevice, + checked: true, + unsupportedReason: "GPU is not supported", + }); + } + }) + .catch((err) => { + setGpuDevice({ + adapter: null, + device: null, + adapterInfo: null, + checked: true, + unsupportedReason: err.message, + }); + }); + } + }, []); + + const [onMessage, setOnMessage] = useState(); + + const addMessage = useCallback( + (resp: GenerateTextResponse) => { + if (resp.isFinished) { + setIsGenerating(false); + } + if (onMessage) onMessage(resp); + cStore?.addMessage(cStore?.currentConversationId, { + id: resp.requestId, + createdAt: new Date().getTime(), + updatedAt: new Date().getTime(), + role: assistantRoleName, + text: resp.outputText, + }); + }, + [cStore, cStore?.currentConversationId, onMessage, setOnMessage] + ); + + useEffect(() => { + if (!workerRef.current) { + workerRef.current = Comlink.wrap( + new Worker(new URL("../worker/worker", import.meta.url)) + ); + } + }, []); + + const send = ( + text: string, + maxTokens = 100, + stopStrings = [userRoleName, assistantRoleName] as string[] + ) => { + const currentConversation = cStore?.getConversation( + cStore?.currentConversationId + ); + if (!currentConversation) { + throw new Error("Invalid conversation id"); + } + currentConversation?.messages.push({ + id: uuidv4(), + createdAt: new Date().getTime(), + updatedAt: new Date().getTime(), + role: userRoleName, + text, + }); + setIsGenerating(true); + workerRef?.current?.generate( + { + conversation: currentConversation, + stopTexts: stopStrings, + maxTokens, + assistantRoleName, + } as GenerateTextRequest, + Comlink.proxy(addMessage) + ); + }; + + return { + conversation: cStore?.getConversation(cStore?.currentConversationId), + + allConversations: cStore?.conversations.sort( + (a: Conversation, b: Conversation) => b.updatedAt - a.updatedAt + ), + + createConversation: (title?: string, prompt?: string) => { + const id = uuidv4(); + cStore?.createConversation({ + id, + title: title ?? "Untitled", + systemPrompt: prompt ?? defaultSystemPrompt, + messages: [], + createdAt: new Date().getTime(), + updatedAt: new Date().getTime(), + }); + }, + + setConversationTitle: (id: string, title: string) => { + cStore?.setConversationTitle(id, title); + }, + + setConversationId: (id: string) => { + cStore?.setConversationId(id); + }, + + deleteConversation: (id: string) => { + cStore?.deleteConversation(id); + }, + deleteMessages: () => cStore?.deleteMessages(cStore?.currentConversationId), + + onMessage, + setOnMessage, + + loadingStatus, + isGenerating, + + userRoleName, + setUserRoleName, + + assistantRoleName, + setAssistantRoleName, + + gpuDevice, + + send, + init: () => workerRef?.current?.init(Comlink.proxy(setLoadingStatus)), + + deleteAllConversations: () => cStore?.deleteAllConversations(), + }; +}; diff --git a/packages/headless/src/hooks/useStore.tsx b/packages/headless/src/hooks/useStore.tsx new file mode 100644 index 0000000..63272bc --- /dev/null +++ b/packages/headless/src/hooks/useStore.tsx @@ -0,0 +1,18 @@ +import { useEffect, useState } from "react"; + +// https://github.com/pmndrs/zustand/blob/65d2bc0660ab0d542cf9f97a3b004754ffa73f3e/docs/integrations/persisting-store-data.md?plain=1#L471-L488 +const useStore = ( + store: (callback: (state: T) => unknown) => unknown, + callback: (state: T) => F +) => { + const result = store(callback) as F; + const [data, setData] = useState(); + + useEffect(() => { + setData(result); + }, [result]); + + return data; +}; + +export default useStore; diff --git a/packages/headless/src/index.d.ts b/packages/headless/src/index.d.ts new file mode 100644 index 0000000..f869329 --- /dev/null +++ b/packages/headless/src/index.d.ts @@ -0,0 +1,6 @@ +export type { + ModelProvider, + ModelProviderProps +} from './providers/ModelProvider' +export type * from './types/chat' + diff --git a/packages/headless/src/index.ts b/packages/headless/src/index.ts new file mode 100644 index 0000000..6cf2936 --- /dev/null +++ b/packages/headless/src/index.ts @@ -0,0 +1,5 @@ + +import { ModelProvider, useLLM } from './providers/ModelProvider'; + +export { ModelProvider }; +export default useLLM; diff --git a/packages/headless/src/providers/ModelProvider.tsx b/packages/headless/src/providers/ModelProvider.tsx new file mode 100644 index 0000000..4be20c0 --- /dev/null +++ b/packages/headless/src/providers/ModelProvider.tsx @@ -0,0 +1,24 @@ +import React, { createContext, useContext } from "react"; +import { UseLLMParams, UseLLMResponse, useLLMContext } from "../hooks/useLLM"; + +export interface ModelProviderProps { + children: React.ReactNode; + props?: UseLLMParams; +} + +const ModelContext = createContext(null); + +export const ModelProvider: React.FC = ({ children }) => { + const LLMValue = useLLMContext(); + return ( + {children} + ); +}; + +export const useLLM = (): UseLLMResponse => { + const context = useContext(ModelContext); + if (context === null) { + throw new Error("useLLMContext must be used within a LLMProvider"); + } + return context; +}; diff --git a/packages/headless/src/types/chat.ts b/packages/headless/src/types/chat.ts new file mode 100644 index 0000000..4cab670 --- /dev/null +++ b/packages/headless/src/types/chat.ts @@ -0,0 +1,16 @@ +export interface Conversation { + id: string; + title: string; + systemPrompt: string; + createdAt: number; + updatedAt: number; + messages: Message[]; +} + +export interface Message { + id: string; + role: string; + text: string; + createdAt: number; + updatedAt: number; +} \ No newline at end of file diff --git a/packages/headless/src/types/worker_message.ts b/packages/headless/src/types/worker_message.ts new file mode 100644 index 0000000..c131941 --- /dev/null +++ b/packages/headless/src/types/worker_message.ts @@ -0,0 +1,32 @@ +import * as Comlink from 'comlink'; +import { InitProgressCallback } from "../worker/lib/tvm/runtime"; +import { Conversation } from "./chat"; + +export type ModelWorker = { + init(callback: Comlink.ProxyOrClone): void; + generate(request: GenerateTextRequest, callback: Comlink.ProxyOrClone): void; +} + +export type InitCallback = InitProgressCallback; +export type GenerateTextCallback = (data: GenerateTextResponse) => void; + +export type GenerateTextRequest = { + conversation: Conversation, + stopTexts: string[], + maxTokens: number, + assistantRoleName: string, +} + +export type GenerateTextResponse = { + requestId: string, + step: number, + outputText: string, + stats: { + totalDecodingSeconds: number, + totalDecodedTokens: number, + totalEncodedTokens: number, + } + isFinished: boolean, +} + + diff --git a/packages/headless/src/worker/lib/tvm/compact.d.ts b/packages/headless/src/worker/lib/tvm/compact.d.ts new file mode 100644 index 0000000..866a6af --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/compact.d.ts @@ -0,0 +1,10 @@ +/** NodeJS and Web compact layer */ +/** + * Get performance measurement. + */ +export declare function getPerformance(): Performance; +/** + * Create a new websocket for a given URL + * @param url The url. + */ +export declare function createWebSocket(url: string): WebSocket; diff --git a/packages/headless/src/worker/lib/tvm/compact.ts b/packages/headless/src/worker/lib/tvm/compact.ts new file mode 100644 index 0000000..0a0ec51 --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/compact.ts @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/** NodeJS and Web compact layer */ + +/** + * Get performance measurement. + */ +export function getPerformance(): Performance { + return performance; +} + +/** + * Create a new websocket for a given URL + * @param url The url. + */ +export function createWebSocket(url: string): WebSocket { + return new WebSocket(url); +} diff --git a/packages/headless/src/worker/lib/tvm/ctypes.d.ts b/packages/headless/src/worker/lib/tvm/ctypes.d.ts new file mode 100644 index 0000000..fac9218 --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/ctypes.d.ts @@ -0,0 +1,180 @@ +/** + * Types for C API. + */ +/** A pointer to points to the raw address space. */ +export type Pointer = number; +/** A pointer offset, need to add a base address to get a valid ptr. */ +export type PtrOffset = number; +/** + * const char *TVMGetLastError(); + */ +export type FTVMGetLastError = () => Pointer; +/** + * int TVMModGetFunction(TVMModuleHandle mod, + * const char* func_name, + * int query_imports, + * TVMFunctionHandle *out); + */ +export type FTVMModGetFunction = (mod: Pointer, funcName: Pointer, queryImports: number, out: Pointer) => number; +/** + * int TVMModImport(TVMModuleHandle mod, + * TVMModuleHandle dep); + */ +export type FTVMModImport = (mod: Pointer, dep: Pointer) => number; +/** + * int TVMModFree(TVMModuleHandle mod); + */ +export type FTVMModFree = (mod: Pointer) => number; +/** + * int TVMFuncFree(TVMFunctionHandle func); + */ +export type FTVMFuncFree = (func: Pointer) => number; +/** + * int TVMFuncCall(TVMFunctionHandle func, + * TVMValue* arg_values, + * int* type_codes, + * int num_args, + * TVMValue* ret_val, + * int* ret_type_code); + */ +export type FTVMFuncCall = (func: Pointer, argValues: Pointer, typeCode: Pointer, nargs: number, retValue: Pointer, retCode: Pointer) => number; +/** + * int TVMCFuncSetReturn(TVMRetValueHandle ret, + * TVMValue* value, + * int* type_code, + * int num_ret); + */ +export type FTVMCFuncSetReturn = (ret: Pointer, value: Pointer, typeCode: Pointer, numRet: number) => number; +/** + * int TVMCbArgToReturn(TVMValue* value, int* code); + */ +export type FTVMCbArgToReturn = (value: Pointer, code: Pointer) => number; +/** + * int TVMFuncListGlobalNames(int* outSize, const char*** outArray); + */ +export type FTVMFuncListGlobalNames = (outSize: Pointer, outArray: Pointer) => number; +/** + * int TVMFuncRegisterGlobal( + * const char* name, TVMFunctionHandle f, int override); + */ +export type FTVMFuncRegisterGlobal = (name: Pointer, f: Pointer, override: number) => number; +/** + *int TVMFuncGetGlobal(const char* name, TVMFunctionHandle* out); + */ +export type FTVMFuncGetGlobal = (name: Pointer, out: Pointer) => number; +/** + * int TVMArrayAlloc(const tvm_index_t* shape, + * int ndim, + * int dtype_code, + * int dtype_bits, + * int dtype_lanes, + * int device_type, + * int device_id, + * TVMArrayHandle* out); + */ +export type FTVMArrayAlloc = (shape: Pointer, ndim: number, dtypeCode: number, dtypeBits: number, dtypeLanes: number, deviceType: number, deviceId: number, out: Pointer) => number; +/** + * int TVMArrayFree(TVMArrayHandle handle); + */ +export type FTVMArrayFree = (handle: Pointer) => number; +/** + * int TVMArrayCopyFromBytes(TVMArrayHandle handle, + * void* data, + * size_t nbytes); + */ +export type FTVMArrayCopyFromBytes = (handle: Pointer, data: Pointer, nbytes: number) => number; +/** + * int TVMArrayCopyToBytes(TVMArrayHandle handle, + * void* data, + * size_t nbytes); + */ +export type FTVMArrayCopyToBytes = (handle: Pointer, data: Pointer, nbytes: number) => number; +/** + * int TVMArrayCopyFromTo(TVMArrayHandle from, + * TVMArrayHandle to, + * TVMStreamHandle stream); + */ +export type FTVMArrayCopyFromTo = (from: Pointer, to: Pointer, stream: Pointer) => number; +/** + * int TVMSynchronize(int device_type, int device_id, TVMStreamHandle stream); + */ +export type FTVMSynchronize = (deviceType: number, deviceId: number, stream: Pointer) => number; +/** + * typedef int (*TVMBackendPackedCFunc)(TVMValue* args, + * int* type_codes, + * int num_args, + * TVMValue* out_ret_value, + * int* out_ret_tcode); + */ +export type FTVMBackendPackedCFunc = (argValues: Pointer, argCodes: Pointer, nargs: number, outValue: Pointer, outCode: Pointer) => number; +/** + * int TVMObjectFree(TVMObjectHandle obj); + */ +export type FTVMObjectFree = (obj: Pointer) => number; +/** + * int TVMObjectGetTypeIndex(TVMObjectHandle obj, unsigned* out_tindex); + */ +export type FTVMObjectGetTypeIndex = (obj: Pointer, out_tindex: Pointer) => number; +/** + * int TVMObjectTypeIndex2Key(unsigned tindex, char** out_type_key); + */ +export type FTVMObjectTypeIndex2Key = (type_index: number, out_type_key: Pointer) => number; +/** + * int TVMObjectTypeKey2Index(const char* type_key, unsigned* out_tindex); + */ +export type FTVMObjectTypeKey2Index = (type_key: Pointer, out_tindex: Pointer) => number; +/** void* TVMWasmAllocSpace(int size); */ +export type FTVMWasmAllocSpace = (size: number) => Pointer; +/** void TVMWasmFreeSpace(void* data); */ +export type FTVMWasmFreeSpace = (ptr: Pointer) => void; +/** + * int TVMWasmPackedCFunc(TVMValue* args, + * int* type_codes, + * int num_args, + * TVMRetValueHandle ret, + * void* resource_handle); + */ +export type FTVMWasmPackedCFunc = (args: Pointer, typeCodes: Pointer, nargs: number, ret: Pointer, resourceHandle: Pointer) => number; +/** + * int TVMWasmFuncCreateFromCFunc(void* resource_handle, + * TVMFunctionHandle *out); + */ +export type FTVMWasmFuncCreateFromCFunc = (resource: Pointer, out: Pointer) => number; +/** + * void TVMWasmPackedCFuncFinalizer(void* resource_handle); + */ +export type FTVMWasmPackedCFuncFinalizer = (resourceHandle: Pointer) => void; +/** + * Size of common data types. + */ +export declare const enum SizeOf { + U8 = 1, + U16 = 2, + I32 = 4, + I64 = 8, + F32 = 4, + F64 = 8, + TVMValue = 8, + DLDataType = 4, + DLDevice = 8 +} +/** + * Argument Type code in TVM FFI. + */ +export declare const enum ArgTypeCode { + Int = 0, + UInt = 1, + Float = 2, + TVMOpaqueHandle = 3, + Null = 4, + TVMDataType = 5, + DLDevice = 6, + TVMDLTensorHandle = 7, + TVMObjectHandle = 8, + TVMModuleHandle = 9, + TVMPackedFuncHandle = 10, + TVMStr = 11, + TVMBytes = 12, + TVMNDArrayHandle = 13, + TVMObjectRValueRefArg = 14 +} diff --git a/packages/headless/src/worker/lib/tvm/ctypes.ts b/packages/headless/src/worker/lib/tvm/ctypes.ts new file mode 100644 index 0000000..282679f --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/ctypes.ts @@ -0,0 +1,251 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Types for C API. + */ + +/** A pointer to points to the raw address space. */ +export type Pointer = number; + +/** A pointer offset, need to add a base address to get a valid ptr. */ +export type PtrOffset = number; + +// -- TVM runtime C API -- +/** + * const char *TVMGetLastError(); + */ +export type FTVMGetLastError = () => Pointer; + +/** + * int TVMModGetFunction(TVMModuleHandle mod, + * const char* func_name, + * int query_imports, + * TVMFunctionHandle *out); + */ +export type FTVMModGetFunction = ( + mod: Pointer, funcName: Pointer, queryImports: number, out: Pointer) => number; +/** + * int TVMModImport(TVMModuleHandle mod, + * TVMModuleHandle dep); + */ +export type FTVMModImport = (mod: Pointer, dep: Pointer) => number; + +/** + * int TVMModFree(TVMModuleHandle mod); + */ +export type FTVMModFree = (mod: Pointer) => number; + +/** + * int TVMFuncFree(TVMFunctionHandle func); + */ +export type FTVMFuncFree = (func: Pointer) => number; + +/** + * int TVMFuncCall(TVMFunctionHandle func, + * TVMValue* arg_values, + * int* type_codes, + * int num_args, + * TVMValue* ret_val, + * int* ret_type_code); + */ +export type FTVMFuncCall = ( + func: Pointer, argValues: Pointer, typeCode: Pointer, + nargs: number, retValue: Pointer, retCode: Pointer) => number; + +/** + * int TVMCFuncSetReturn(TVMRetValueHandle ret, + * TVMValue* value, + * int* type_code, + * int num_ret); + */ +export type FTVMCFuncSetReturn = ( + ret: Pointer, value: Pointer, typeCode: Pointer, numRet: number) => number; + +/** + * int TVMCbArgToReturn(TVMValue* value, int* code); + */ +export type FTVMCbArgToReturn = (value: Pointer, code: Pointer) => number; + +/** + * int TVMFuncListGlobalNames(int* outSize, const char*** outArray); + */ +export type FTVMFuncListGlobalNames = (outSize: Pointer, outArray: Pointer) => number; + +/** + * int TVMFuncRegisterGlobal( + * const char* name, TVMFunctionHandle f, int override); + */ +export type FTVMFuncRegisterGlobal = ( + name: Pointer, f: Pointer, override: number) => number; + +/** + *int TVMFuncGetGlobal(const char* name, TVMFunctionHandle* out); + */ +export type FTVMFuncGetGlobal = (name: Pointer, out: Pointer) => number; + +/** + * int TVMArrayAlloc(const tvm_index_t* shape, + * int ndim, + * int dtype_code, + * int dtype_bits, + * int dtype_lanes, + * int device_type, + * int device_id, + * TVMArrayHandle* out); + */ +export type FTVMArrayAlloc = ( + shape: Pointer, ndim: number, + dtypeCode: number, dtypeBits: number, + dtypeLanes: number, deviceType: number, deviceId: number, + out: Pointer) => number; + +/** + * int TVMArrayFree(TVMArrayHandle handle); + */ +export type FTVMArrayFree = (handle: Pointer) => number; + +/** + * int TVMArrayCopyFromBytes(TVMArrayHandle handle, + * void* data, + * size_t nbytes); + */ +export type FTVMArrayCopyFromBytes = ( + handle: Pointer, data: Pointer, nbytes: number) => number; + +/** + * int TVMArrayCopyToBytes(TVMArrayHandle handle, + * void* data, + * size_t nbytes); + */ +export type FTVMArrayCopyToBytes = ( + handle: Pointer, data: Pointer, nbytes: number) => number; + +/** + * int TVMArrayCopyFromTo(TVMArrayHandle from, + * TVMArrayHandle to, + * TVMStreamHandle stream); + */ +export type FTVMArrayCopyFromTo = ( + from: Pointer, to: Pointer, stream: Pointer) => number; + +/** + * int TVMSynchronize(int device_type, int device_id, TVMStreamHandle stream); + */ +export type FTVMSynchronize = ( + deviceType: number, deviceId: number, stream: Pointer) => number; + +/** + * typedef int (*TVMBackendPackedCFunc)(TVMValue* args, + * int* type_codes, + * int num_args, + * TVMValue* out_ret_value, + * int* out_ret_tcode); + */ +export type FTVMBackendPackedCFunc = ( + argValues: Pointer, argCodes: Pointer, nargs: number, + outValue: Pointer, outCode: Pointer) => number; + + +/** + * int TVMObjectFree(TVMObjectHandle obj); + */ + export type FTVMObjectFree = (obj: Pointer) => number; + +/** + * int TVMObjectGetTypeIndex(TVMObjectHandle obj, unsigned* out_tindex); + */ +export type FTVMObjectGetTypeIndex = (obj: Pointer, out_tindex: Pointer) => number; + +/** + * int TVMObjectTypeIndex2Key(unsigned tindex, char** out_type_key); + */ +export type FTVMObjectTypeIndex2Key = (type_index: number, out_type_key: Pointer) => number; + +/** + * int TVMObjectTypeKey2Index(const char* type_key, unsigned* out_tindex); + */ +export type FTVMObjectTypeKey2Index = (type_key: Pointer, out_tindex: Pointer) => number; + +// -- TVM Wasm Auxiliary C API -- + +/** void* TVMWasmAllocSpace(int size); */ +export type FTVMWasmAllocSpace = (size: number) => Pointer; + +/** void TVMWasmFreeSpace(void* data); */ +export type FTVMWasmFreeSpace = (ptr: Pointer) => void; + +/** + * int TVMWasmPackedCFunc(TVMValue* args, + * int* type_codes, + * int num_args, + * TVMRetValueHandle ret, + * void* resource_handle); + */ +export type FTVMWasmPackedCFunc = ( + args: Pointer, typeCodes: Pointer, nargs: number, + ret: Pointer, resourceHandle: Pointer) => number; + +/** + * int TVMWasmFuncCreateFromCFunc(void* resource_handle, + * TVMFunctionHandle *out); + */ +export type FTVMWasmFuncCreateFromCFunc = ( + resource: Pointer, out: Pointer) => number; + +/** + * void TVMWasmPackedCFuncFinalizer(void* resource_handle); + */ +export type FTVMWasmPackedCFuncFinalizer = (resourceHandle: Pointer) => void; + +/** + * Size of common data types. + */ +export const enum SizeOf { + U8 = 1, + U16 = 2, + I32 = 4, + I64 = 8, + F32 = 4, + F64 = 8, + TVMValue = 8, + DLDataType = I32, + DLDevice = I32 + I32, +} + +/** + * Argument Type code in TVM FFI. + */ +export const enum ArgTypeCode { + Int = 0, + UInt = 1, + Float = 2, + TVMOpaqueHandle = 3, + Null = 4, + TVMDataType = 5, + DLDevice = 6, + TVMDLTensorHandle = 7, + TVMObjectHandle = 8, + TVMModuleHandle = 9, + TVMPackedFuncHandle = 10, + TVMStr = 11, + TVMBytes = 12, + TVMNDArrayHandle = 13, + TVMObjectRValueRefArg = 14 +} diff --git a/packages/headless/src/worker/lib/tvm/environment.d.ts b/packages/headless/src/worker/lib/tvm/environment.d.ts new file mode 100644 index 0000000..5e935f7 --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/environment.d.ts @@ -0,0 +1,26 @@ +import { LibraryProvider } from "./types"; +import * as ctypes from "./ctypes"; +/** + * Environment to impelement most of the JS library functions. + */ +export declare class Environment implements LibraryProvider { + logger: (msg: string) => void; + imports: Record; + /** + * Maintains a table of FTVMWasmPackedCFunc that the C part + * can call via TVMWasmPackedCFunc. + * + * We maintain a separate table so that we can have un-limited amount + * of functions that do not maps to the address space. + */ + packedCFuncTable: Array; + /** + * Free table index that can be recycled. + */ + packedCFuncTableFreeId: Array; + private libProvider?; + constructor(importObject?: Record, logger?: (msg: string) => void); + /** Mark the start of the instance. */ + start(inst: WebAssembly.Instance): void; + private environment; +} diff --git a/packages/headless/src/worker/lib/tvm/environment.ts b/packages/headless/src/worker/lib/tvm/environment.ts new file mode 100644 index 0000000..24126c0 --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/environment.ts @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/** + * Runtime environment that provide js libaries calls. + */ +import { Pointer } from "./ctypes"; +import { LibraryProvider } from "./types"; +import { assert } from "./support"; +import * as ctypes from "./ctypes"; + +/** + * Detect library provider from the importObject. + * + * @param importObject The import object. + */ +function detectLibraryProvider( + importObject: Record +): LibraryProvider | undefined { + if ( + importObject["wasmLibraryProvider"] && + importObject["wasmLibraryProvider"]["start"] && + importObject["wasmLibraryProvider"]["imports"] !== undefined + ) { + const item = importObject as { wasmLibraryProvider: LibraryProvider }; + // create provider so that we capture imports in the provider. + return { + imports: item.wasmLibraryProvider.imports, + start: (inst: WebAssembly.Instance): void => { + item.wasmLibraryProvider.start(inst); + }, + }; + } else if (importObject["imports"] && importObject["start"] !== undefined) { + return importObject as LibraryProvider; + } else if (importObject["wasiImport"] && importObject["start"] !== undefined) { + // WASI + return { + imports: { + "wasi_snapshot_preview1": importObject["wasiImport"], + }, + start: (inst: WebAssembly.Instance): void => { + importObject["start"](inst); + } + }; + } else { + return undefined; + } +} + +/** + * Environment to impelement most of the JS library functions. + */ +export class Environment implements LibraryProvider { + logger: (msg: string) => void; + imports: Record; + /** + * Maintains a table of FTVMWasmPackedCFunc that the C part + * can call via TVMWasmPackedCFunc. + * + * We maintain a separate table so that we can have un-limited amount + * of functions that do not maps to the address space. + */ + packedCFuncTable: Array = [ + undefined, + ]; + /** + * Free table index that can be recycled. + */ + packedCFuncTableFreeId: Array = []; + + private libProvider?: LibraryProvider; + + constructor( + importObject: Record = {}, + logger: (msg: string) => void = console.log + ) { + this.logger = logger; + this.libProvider = detectLibraryProvider(importObject); + // get imports from the provider + if (this.libProvider !== undefined) { + this.imports = this.libProvider.imports; + } else { + this.imports = importObject; + } + // update with more functions + this.imports.env = this.environment(this.imports.env); + } + + /** Mark the start of the instance. */ + start(inst: WebAssembly.Instance): void { + if (this.libProvider !== undefined) { + this.libProvider.start(inst); + } + } + + private environment(initEnv: Record): Record { + // default env can be be overriden by libraries. + const defaultEnv = { + "__cxa_thread_atexit": (): void => {}, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + "emscripten_notify_memory_growth": (index: number): void => {} + }; + const wasmPackedCFunc: ctypes.FTVMWasmPackedCFunc = ( + args: Pointer, + typeCodes: Pointer, + nargs: number, + ret: Pointer, + resourceHandle: Pointer + ): number => { + const cfunc = this.packedCFuncTable[resourceHandle]; + assert(cfunc !== undefined); + return cfunc(args, typeCodes, nargs, ret, resourceHandle); + }; + + const wasmPackedCFuncFinalizer: ctypes.FTVMWasmPackedCFuncFinalizer = ( + resourceHandle: Pointer + ): void => { + this.packedCFuncTable[resourceHandle] = undefined; + this.packedCFuncTableFreeId.push(resourceHandle); + }; + + const newEnv = { + TVMWasmPackedCFunc: wasmPackedCFunc, + TVMWasmPackedCFuncFinalizer: wasmPackedCFuncFinalizer, + "__console_log": (msg: string): void => { + this.logger(msg); + } + }; + return Object.assign(defaultEnv, initEnv, newEnv); + } +} diff --git a/packages/headless/src/worker/lib/tvm/index.d.ts b/packages/headless/src/worker/lib/tvm/index.d.ts new file mode 100644 index 0000000..2196fda --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/index.d.ts @@ -0,0 +1,6 @@ +export { RPCServer } from "./rpc_server"; +export { DLDataType, DLDevice, Instance, Module, NDArray, Scalar, TVMArray, instantiate } from "./runtime"; +export type { PackedFunc } from "./runtime"; +export { assert, wasmPath } from "./support"; +export type { Disposable, LibraryProvider } from "./types"; +export { detectGPUDevice } from "./webgpu"; diff --git a/packages/headless/src/worker/lib/tvm/index.ts b/packages/headless/src/worker/lib/tvm/index.ts new file mode 100644 index 0000000..4772e1b --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/index.ts @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { RPCServer } from "./rpc_server"; +export { DLDataType, DLDevice, Instance, Module, NDArray, Scalar, TVMArray, instantiate } from "./runtime"; +export type { PackedFunc } from "./runtime"; +export { assert, wasmPath } from "./support"; +export type { Disposable, LibraryProvider } from "./types"; +export { detectGPUDevice } from "./webgpu"; + diff --git a/packages/headless/src/worker/lib/tvm/memory.d.ts b/packages/headless/src/worker/lib/tvm/memory.d.ts new file mode 100644 index 0000000..9c16d6b --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/memory.d.ts @@ -0,0 +1,144 @@ +/** + * Classes to manipulate Wasm memories. + */ +import { Pointer, PtrOffset } from "./ctypes"; +import { Disposable } from "./types"; +import * as ctypes from "./ctypes"; +/** + * Wasm Memory wrapper to perform JS side raw memory access. + */ +export declare class Memory { + memory: WebAssembly.Memory; + wasm32: boolean; + private buffer; + private viewU8; + private viewU16; + private viewI32; + private viewU32; + private viewF32; + private viewF64; + constructor(memory: WebAssembly.Memory); + loadU8(ptr: Pointer): number; + loadU16(ptr: Pointer): number; + loadU32(ptr: Pointer): number; + loadI32(ptr: Pointer): number; + loadI64(ptr: Pointer): number; + loadF32(ptr: Pointer): number; + loadF64(ptr: Pointer): number; + loadPointer(ptr: Pointer): Pointer; + loadUSize(ptr: Pointer): Pointer; + sizeofPtr(): number; + /** + * Load raw bytes from ptr. + * @param ptr The head address + * @param numBytes The number + */ + loadRawBytes(ptr: Pointer, numBytes: number): Uint8Array; + /** + * Load TVMByteArray from ptr. + * + * @param ptr The address of the header. + */ + loadTVMBytes(ptr: Pointer): Uint8Array; + /** + * Load null-terminated C-string from ptr. + * @param ptr The head address + */ + loadCString(ptr: Pointer): string; + /** + * Store raw bytes to the ptr. + * @param ptr The head address. + * @param bytes The bytes content. + */ + storeRawBytes(ptr: Pointer, bytes: Uint8Array): void; + /** + * Update memory view after the memory growth. + */ + private updateViews; +} +/** + * Auxiliary call stack for the FFI calls. + * + * Lifecyle of a call stack. + * - Calls into allocXX to allocate space, mixed with storeXXX to store data. + * - Calls into ptrFromOffset, no further allocation(as ptrFromOffset can change), + * can still call into storeXX + * - Calls into commitToWasmMemory once. + * - reset. + */ +export declare class CachedCallStack implements Disposable { + /** List of temporay arguments that can be disposed during reset. */ + tempArgs: Array; + private memory; + private cAllocSpace; + private cFreeSpace; + private buffer; + private viewU8; + private viewI32; + private viewU32; + private viewF64; + private stackTop; + private basePtr; + private addressToSetTargetValue; + constructor(memory: Memory, allocSpace: ctypes.FTVMWasmAllocSpace, freeSpace: ctypes.FTVMWasmFreeSpace); + dispose(): void; + /** + * Rest the call stack so that it can be reused again. + */ + reset(): void; + /** + * Commit all the cached data to WasmMemory. + * This function can only be called once. + * No further store function should be called. + * + * @param nbytes Number of bytes to be stored. + */ + commitToWasmMemory(nbytes?: number): void; + /** + * Allocate space by number of bytes + * @param nbytes Number of bytes. + * @note This function always allocate space that aligns to 64bit. + */ + allocRawBytes(nbytes: number): PtrOffset; + /** + * Allocate space for pointers. + * @param count Number of pointers. + * @returns The allocated pointer array. + */ + allocPtrArray(count: number): PtrOffset; + /** + * Get the real pointer from offset values. + * Note that the returned value becomes obsolete if alloc is called on the stack. + * @param offset The allocated offset. + */ + ptrFromOffset(offset: PtrOffset): Pointer; + storePtr(offset: PtrOffset, value: Pointer): void; + storeUSize(offset: PtrOffset, value: Pointer): void; + storeI32(offset: PtrOffset, value: number): void; + storeU32(offset: PtrOffset, value: number): void; + storeI64(offset: PtrOffset, value: number): void; + storeF64(offset: PtrOffset, value: number): void; + storeRawBytes(offset: PtrOffset, bytes: Uint8Array): void; + /** + * Allocate then set C-String pointer to the offset. + * This function will call into allocBytes to allocate necessary data. + * The address won't be set immediately(because the possible change of basePtr) + * and will be filled when we commit the data. + * + * @param offset The offset to set ot data pointer. + * @param data The string content. + */ + allocThenSetArgString(offset: PtrOffset, data: string): void; + /** + * Allocate then set the argument location with a TVMByteArray. + * Allocate new temporary space for bytes. + * + * @param offset The offset to set ot data pointer. + * @param data The string content. + */ + allocThenSetArgBytes(offset: PtrOffset, data: Uint8Array): void; + /** + * Update internal cache views. + */ + private updateViews; +} diff --git a/packages/headless/src/worker/lib/tvm/memory.ts b/packages/headless/src/worker/lib/tvm/memory.ts new file mode 100644 index 0000000..ac737b7 --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/memory.ts @@ -0,0 +1,408 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/** + * Classes to manipulate Wasm memories. + */ +import { Pointer, PtrOffset, SizeOf } from "./ctypes"; +import { Disposable } from "./types"; +import { assert, StringToUint8Array } from "./support"; + +import * as ctypes from "./ctypes"; + +/** + * Wasm Memory wrapper to perform JS side raw memory access. + */ +export class Memory { + memory: WebAssembly.Memory; + wasm32 = true; + private buffer: ArrayBuffer | SharedArrayBuffer; + private viewU8: Uint8Array; + private viewU16: Uint16Array; + private viewI32: Int32Array; + private viewU32: Uint32Array; + private viewF32: Float32Array; + private viewF64: Float64Array; + + constructor(memory: WebAssembly.Memory) { + this.memory = memory; + this.buffer = this.memory.buffer; + this.viewU8 = new Uint8Array(this.buffer); + this.viewU16 = new Uint16Array(this.buffer); + this.viewI32 = new Int32Array(this.buffer); + this.viewU32 = new Uint32Array(this.buffer); + this.viewF32 = new Float32Array(this.buffer); + this.viewF64 = new Float64Array(this.buffer); + } + + loadU8(ptr: Pointer): number { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + return this.viewU8[ptr >> 0]; + } + + loadU16(ptr: Pointer): number { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + return this.viewU16[ptr >> 1]; + } + + loadU32(ptr: Pointer): number { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + return this.viewU32[ptr >> 2]; + } + + loadI32(ptr: Pointer): number { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + return this.viewI32[ptr >> 2]; + } + + loadI64(ptr: Pointer): number { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + const base = ptr >> 2; + // assumes little endian, for now truncate high. + return this.viewI32[base]; + } + + loadF32(ptr: Pointer): number { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + return this.viewF32[ptr >> 2]; + } + + loadF64(ptr: Pointer): number { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + return this.viewF64[ptr >> 3]; + } + + loadPointer(ptr: Pointer): Pointer { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + if (this.wasm32) { + return this.loadU32(ptr); + } else { + return this.loadI64(ptr); + } + } + loadUSize(ptr: Pointer): Pointer { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + if (this.wasm32) { + return this.loadU32(ptr); + } else { + return this.loadI64(ptr); + } + } + sizeofPtr(): number { + return this.wasm32 ? SizeOf.I32 : SizeOf.I64; + } + /** + * Load raw bytes from ptr. + * @param ptr The head address + * @param numBytes The number + */ + loadRawBytes(ptr: Pointer, numBytes: number): Uint8Array { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + const result = new Uint8Array(numBytes); + result.set(this.viewU8.slice(ptr, ptr + numBytes)); + return result; + } + /** + * Load TVMByteArray from ptr. + * + * @param ptr The address of the header. + */ + loadTVMBytes(ptr: Pointer): Uint8Array { + const data = this.loadPointer(ptr); + const length = this.loadUSize(ptr + this.sizeofPtr()); + return this.loadRawBytes(data, length); + } + /** + * Load null-terminated C-string from ptr. + * @param ptr The head address + */ + loadCString(ptr: Pointer): string { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + // NOTE: the views are still valid for read. + const ret = []; + let ch = 1; + while (ch != 0) { + ch = this.viewU8[ptr]; + if (ch != 0) { + ret.push(String.fromCharCode(ch)); + } + ++ptr; + } + return ret.join(""); + } + /** + * Store raw bytes to the ptr. + * @param ptr The head address. + * @param bytes The bytes content. + */ + storeRawBytes(ptr: Pointer, bytes: Uint8Array): void { + if (this.buffer != this.memory.buffer) { + this.updateViews(); + } + this.viewU8.set(bytes, ptr); + } + + /** + * Update memory view after the memory growth. + */ + private updateViews(): void { + this.buffer = this.memory.buffer; + this.viewU8 = new Uint8Array(this.buffer); + this.viewU16 = new Uint16Array(this.buffer); + this.viewI32 = new Int32Array(this.buffer); + this.viewU32 = new Uint32Array(this.buffer); + this.viewF32 = new Float32Array(this.buffer); + this.viewF64 = new Float64Array(this.buffer); + } +} + +/** + * Auxiliary call stack for the FFI calls. + * + * Lifecyle of a call stack. + * - Calls into allocXX to allocate space, mixed with storeXXX to store data. + * - Calls into ptrFromOffset, no further allocation(as ptrFromOffset can change), + * can still call into storeXX + * - Calls into commitToWasmMemory once. + * - reset. + */ +export class CachedCallStack implements Disposable { + /** List of temporay arguments that can be disposed during reset. */ + tempArgs: Array = []; + + private memory: Memory; + private cAllocSpace: ctypes.FTVMWasmAllocSpace; + private cFreeSpace: ctypes.FTVMWasmFreeSpace; + + private buffer: ArrayBuffer; + private viewU8: Uint8Array; + private viewI32: Int32Array; + private viewU32: Uint32Array; + private viewF64: Float64Array; + + private stackTop: PtrOffset = 0; + private basePtr: Pointer = 0; + + private addressToSetTargetValue: Array<[PtrOffset, PtrOffset]> = []; + + constructor( + memory: Memory, + allocSpace: ctypes.FTVMWasmAllocSpace, + freeSpace: ctypes.FTVMWasmFreeSpace + ) { + const initCallStackSize = 128; + this.memory = memory; + this.cAllocSpace = allocSpace; + this.cFreeSpace = freeSpace; + this.buffer = new ArrayBuffer(initCallStackSize); + this.basePtr = this.cAllocSpace(initCallStackSize); + this.viewU8 = new Uint8Array(this.buffer); + this.viewI32 = new Int32Array(this.buffer); + this.viewU32 = new Uint32Array(this.buffer); + this.viewF64 = new Float64Array(this.buffer); + this.updateViews(); + } + + dispose(): void { + if (this.basePtr != 0) { + this.cFreeSpace(this.basePtr); + this.basePtr = 0; + } + } + /** + * Rest the call stack so that it can be reused again. + */ + reset(): void { + this.stackTop = 0; + assert(this.addressToSetTargetValue.length == 0); + while (this.tempArgs.length != 0) { + (this.tempArgs.pop() as Disposable).dispose(); + } + } + + /** + * Commit all the cached data to WasmMemory. + * This function can only be called once. + * No further store function should be called. + * + * @param nbytes Number of bytes to be stored. + */ + commitToWasmMemory(nbytes: number = this.stackTop): void { + // commit all pointer values. + while (this.addressToSetTargetValue.length != 0) { + const [targetOffset, valueOffset] = this.addressToSetTargetValue.pop() as [ + number, + number + ]; + this.storePtr(targetOffset, this.ptrFromOffset(valueOffset)); + } + this.memory.storeRawBytes(this.basePtr, this.viewU8.slice(0, nbytes)); + } + + /** + * Allocate space by number of bytes + * @param nbytes Number of bytes. + * @note This function always allocate space that aligns to 64bit. + */ + allocRawBytes(nbytes: number): PtrOffset { + // always aligns to 64bit + nbytes = ((nbytes + 7) >> 3) << 3; + + if (this.stackTop + nbytes > this.buffer.byteLength) { + const newSize = Math.max( + this.buffer.byteLength * 2, + this.stackTop + nbytes + ); + const oldU8 = this.viewU8; + this.buffer = new ArrayBuffer(newSize); + this.updateViews(); + this.viewU8.set(oldU8); + if (this.basePtr != 0) { + this.cFreeSpace(this.basePtr); + } + this.basePtr = this.cAllocSpace(newSize); + } + const retOffset = this.stackTop; + this.stackTop += nbytes; + return retOffset; + } + + /** + * Allocate space for pointers. + * @param count Number of pointers. + * @returns The allocated pointer array. + */ + allocPtrArray(count: number): PtrOffset { + return this.allocRawBytes(this.memory.sizeofPtr() * count); + } + + /** + * Get the real pointer from offset values. + * Note that the returned value becomes obsolete if alloc is called on the stack. + * @param offset The allocated offset. + */ + ptrFromOffset(offset: PtrOffset): Pointer { + return this.basePtr + offset; + } + + // Store APIs + storePtr(offset: PtrOffset, value: Pointer): void { + if (this.memory.wasm32) { + this.storeU32(offset, value); + } else { + this.storeI64(offset, value); + } + } + + storeUSize(offset: PtrOffset, value: Pointer): void { + if (this.memory.wasm32) { + this.storeU32(offset, value); + } else { + this.storeI64(offset, value); + } + } + + storeI32(offset: PtrOffset, value: number): void { + this.viewI32[offset >> 2] = value; + } + + storeU32(offset: PtrOffset, value: number): void { + this.viewU32[offset >> 2] = value; + } + + storeI64(offset: PtrOffset, value: number): void { + // For now, just store as 32bit + // NOTE: wasm always uses little endian. + const low = value & 0xffffffff; + const base = offset >> 2; + this.viewI32[base] = low; + this.viewI32[base + 1] = 0; + } + + storeF64(offset: PtrOffset, value: number): void { + this.viewF64[offset >> 3] = value; + } + + storeRawBytes(offset: PtrOffset, bytes: Uint8Array): void { + this.viewU8.set(bytes, offset); + } + + /** + * Allocate then set C-String pointer to the offset. + * This function will call into allocBytes to allocate necessary data. + * The address won't be set immediately(because the possible change of basePtr) + * and will be filled when we commit the data. + * + * @param offset The offset to set ot data pointer. + * @param data The string content. + */ + allocThenSetArgString(offset: PtrOffset, data: string): void { + const strOffset = this.allocRawBytes(data.length + 1); + this.storeRawBytes(strOffset, StringToUint8Array(data)); + this.addressToSetTargetValue.push([offset, strOffset]); + } + /** + * Allocate then set the argument location with a TVMByteArray. + * Allocate new temporary space for bytes. + * + * @param offset The offset to set ot data pointer. + * @param data The string content. + */ + allocThenSetArgBytes(offset: PtrOffset, data: Uint8Array): void { + // Note: size of size_t equals sizeof ptr. + const headerOffset = this.allocRawBytes(this.memory.sizeofPtr() * 2); + const dataOffset = this.allocRawBytes(data.length); + this.storeRawBytes(dataOffset, data); + this.storeUSize(headerOffset + this.memory.sizeofPtr(), data.length); + + this.addressToSetTargetValue.push([offset, headerOffset]); + this.addressToSetTargetValue.push([headerOffset, dataOffset]); + } + + /** + * Update internal cache views. + */ + private updateViews(): void { + this.viewU8 = new Uint8Array(this.buffer); + this.viewI32 = new Int32Array(this.buffer); + this.viewU32 = new Uint32Array(this.buffer); + this.viewF64 = new Float64Array(this.buffer); + } +} diff --git a/packages/headless/src/worker/lib/tvm/rpc_server.d.ts b/packages/headless/src/worker/lib/tvm/rpc_server.d.ts new file mode 100644 index 0000000..af9928a --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/rpc_server.d.ts @@ -0,0 +1,54 @@ +import * as runtime from "./runtime"; +declare enum RPCServerState { + InitHeader = 0, + InitHeaderKey = 1, + InitServer = 2, + WaitForCallback = 3, + ReceivePacketHeader = 4, + ReceivePacketBody = 5 +} +/** + * A websocket based RPC + */ +export declare class RPCServer { + url: string; + key: string; + socket: WebSocket; + state: RPCServerState; + logger: (msg: string) => void; + getImports: () => Record; + private ndarrayCacheUrl; + private ndarrayCacheDevice; + private initProgressCallback?; + private asyncOnServerLoad?; + private pendingSend; + private name; + private inst?; + private globalObjects; + private serverRecvData?; + private currPacketHeader?; + private currPacketLength; + private remoteKeyLength; + private pendingBytes; + private buffredBytes; + private messageQueue; + constructor(url: string, key: string, getImports: () => Record, logger?: (msg: string) => void, ndarrayCacheUrl?: string, ndarrayCacheDevice?: string, initProgressCallback?: runtime.InitProgressCallback | undefined, asyncOnServerLoad?: ((inst: runtime.Instance) => Promise) | undefined); + private onClose; + private onOpen; + /** Handler for raw message. */ + private onMessage; + /** Process ready events. */ + private processEvents; + /** State machine to handle each request */ + private onDataReady; + private onPacketReady; + /** Event handler during server initialization. */ + private onInitServer; + private log; + private handleInitHeader; + private handleInitHeaderKey; + private checkLittleEndian; + private requestBytes; + private readFromBuffer; +} +export {}; diff --git a/packages/headless/src/worker/lib/tvm/rpc_server.ts b/packages/headless/src/worker/lib/tvm/rpc_server.ts new file mode 100644 index 0000000..f920c2c --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/rpc_server.ts @@ -0,0 +1,457 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as compact from "./compact"; +import { ArgTypeCode, SizeOf } from "./ctypes"; +import * as runtime from "./runtime"; +import { StringToUint8Array, Uint8ArrayToString, assert } from "./support"; +import { Disposable } from "./types"; +import { GPUDeviceDetectOutput, detectGPUDevice } from "./webgpu"; + +enum RPCServerState { + InitHeader, + InitHeaderKey, + InitServer, + WaitForCallback, + ReceivePacketHeader, + ReceivePacketBody, +} + +/** RPC magic header */ +const RPC_MAGIC = 0xff271; + +/** + * An utility class to read from binary bytes. + */ +class ByteStreamReader { + offset = 0; + bytes: Uint8Array; + + constructor(bytes: Uint8Array) { + this.bytes = bytes; + } + + readU32(): number { + const i = this.offset; + const b = this.bytes; + const val = b[i] | (b[i + 1] << 8) | (b[i + 2] << 16) | (b[i + 3] << 24); + this.offset += 4; + return val; + } + + readU64(): number { + const val = this.readU32(); + this.offset += 4; + return val; + } + + readByteArray(): Uint8Array { + const len = this.readU64(); + assert(this.offset + len <= this.bytes.byteLength); + const ret = new Uint8Array(len); + ret.set(this.bytes.slice(this.offset, this.offset + len)); + this.offset += len; + return ret; + } +} + +/** + * A websocket based RPC + */ +export class RPCServer { + url: string; + key: string; + socket: WebSocket; + state: RPCServerState = RPCServerState.InitHeader; + logger: (msg: string) => void; + getImports: () => Record; + private ndarrayCacheUrl: string; + private ndarrayCacheDevice: string; + private initProgressCallback?: runtime.InitProgressCallback; + private asyncOnServerLoad?: (inst: runtime.Instance) => Promise; + private pendingSend: Promise = Promise.resolve(); + private name: string; + private inst?: runtime.Instance = undefined; + private globalObjects: Array = []; + private serverRecvData?: (header: Uint8Array, body: Uint8Array) => void; + private currPacketHeader?: Uint8Array; + private currPacketLength = 0; + private remoteKeyLength = 0; + private pendingBytes = 0; + private buffredBytes = 0; + private messageQueue: Array = []; + + constructor( + url: string, + key: string, + getImports: () => Record, + logger: (msg: string) => void = console.log, + ndarrayCacheUrl: string = "", + ndarrayCacheDevice: string = "cpu", + initProgressCallback: runtime.InitProgressCallback | undefined = undefined, + asyncOnServerLoad: ((inst: runtime.Instance) => Promise) | undefined = undefined, + ) { + this.url = url; + this.key = key; + this.name = "WebSocketRPCServer[" + this.key + "]: "; + this.getImports = getImports; + this.logger = logger; + this.ndarrayCacheUrl = ndarrayCacheUrl; + this.ndarrayCacheDevice = ndarrayCacheDevice; + this.initProgressCallback = initProgressCallback; + this.asyncOnServerLoad = asyncOnServerLoad; + this.checkLittleEndian(); + this.socket = compact.createWebSocket(url); + this.socket.binaryType = "arraybuffer"; + + this.socket.addEventListener("open", (event: Event) => { + return this.onOpen(event); + }); + this.socket.addEventListener("message", (event: MessageEvent) => { + return this.onMessage(event); + }); + this.socket.addEventListener("close", (event: CloseEvent) => { + return this.onClose(event); + }); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private onClose(_event: CloseEvent): void { + if (this.inst !== undefined) { + this.globalObjects.forEach(obj => { + obj.dispose(); + }); + this.log(this.inst.runtimeStatsText()); + this.inst.dispose(); + } + if (this.state == RPCServerState.ReceivePacketHeader) { + this.log("Closing the server in clean state"); + this.log("Automatic reconnecting.."); + new RPCServer( + this.url, this.key, this.getImports, this.logger, + this.ndarrayCacheUrl, this.ndarrayCacheDevice, + this.initProgressCallback, this.asyncOnServerLoad); + } else { + this.log("Closing the server, final state=" + this.state); + } + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private onOpen(_event: Event): void { + // Send the headers + let bkey = StringToUint8Array("server:" + this.key); + bkey = bkey.slice(0, bkey.length - 1); + const intbuf = new Int32Array(1); + intbuf[0] = RPC_MAGIC; + this.socket.send(intbuf); + intbuf[0] = bkey.length; + this.socket.send(intbuf); + this.socket.send(bkey); + this.log("connected..."); + // request bytes: magic + keylen + this.requestBytes(SizeOf.I32 + SizeOf.I32); + this.state = RPCServerState.InitHeader; + } + + /** Handler for raw message. */ + private onMessage(event: MessageEvent): void { + const buffer = event.data; + this.buffredBytes += buffer.byteLength; + this.messageQueue.push(new Uint8Array(buffer)); + this.processEvents(); + } + /** Process ready events. */ + private processEvents(): void { + while (this.buffredBytes >= this.pendingBytes && this.pendingBytes != 0) { + this.onDataReady(); + } + } + /** State machine to handle each request */ + private onDataReady(): void { + switch (this.state) { + case RPCServerState.InitHeader: { + this.handleInitHeader(); + break; + } + case RPCServerState.InitHeaderKey: { + this.handleInitHeaderKey(); + break; + } + case RPCServerState.ReceivePacketHeader: { + this.currPacketHeader = this.readFromBuffer(SizeOf.I64); + const reader = new ByteStreamReader(this.currPacketHeader); + this.currPacketLength = reader.readU64(); + assert(this.pendingBytes == 0); + this.requestBytes(this.currPacketLength); + this.state = RPCServerState.ReceivePacketBody; + break; + } + case RPCServerState.ReceivePacketBody: { + const body = this.readFromBuffer(this.currPacketLength); + assert(this.pendingBytes == 0); + assert(this.currPacketHeader !== undefined); + this.onPacketReady(this.currPacketHeader, body); + break; + } + case RPCServerState.WaitForCallback: { + assert(this.pendingBytes == 0); + break; + } + default: { + throw new Error("Cannot handle state " + this.state); + } + } + } + + private onPacketReady(header: Uint8Array, body: Uint8Array): void { + if (this.inst === undefined) { + // initialize server. + const reader = new ByteStreamReader(body); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const code = reader.readU32(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const ver = Uint8ArrayToString(reader.readByteArray()); + const nargs = reader.readU32(); + const tcodes = []; + const args = []; + for (let i = 0; i < nargs; ++i) { + tcodes.push(reader.readU32()); + } + + for (let i = 0; i < nargs; ++i) { + const tcode = tcodes[i]; + if (tcode == ArgTypeCode.TVMStr) { + const str = Uint8ArrayToString(reader.readByteArray()); + args.push(str); + } else if (tcode == ArgTypeCode.TVMBytes) { + args.push(reader.readByteArray()); + } else { + throw new Error("cannot support type code " + tcode); + } + } + this.onInitServer(args, header, body); + } else { + assert(this.serverRecvData !== undefined); + this.serverRecvData(header, body); + this.requestBytes(SizeOf.I64); + this.state = RPCServerState.ReceivePacketHeader; + } + } + + /** Event handler during server initialization. */ + private onInitServer( + args: Array, + header: Uint8Array, + body: Uint8Array + ): void { + // start the server + assert(args[0] == "rpc.WasmSession"); + assert(this.pendingBytes == 0); + + const asyncInitServer = async (): Promise => { + assert(args[1] instanceof Uint8Array); + const inst = await runtime.instantiate( + args[1].buffer, + this.getImports(), + this.logger + ); + + try { + const output: GPUDeviceDetectOutput | undefined = await detectGPUDevice(); + if (output !== undefined) { + const label = "WebGPU: " + output.adapterInfo.description; + this.log("Initialize GPU device: " + label); + inst.initWebGPU(output.device); + } else { + this.log("Cannot find WebGPU device in the env"); + } + } catch (err: any) { + this.log("Cannnot initialize WebGPU, " + err.toString()); + } + + this.inst = inst; + // begin scope to allow handling of objects + this.inst.beginScope(); + if (this.initProgressCallback !== undefined) { + this.inst.registerInitProgressCallback(this.initProgressCallback); + } + + if (this.ndarrayCacheUrl.length != 0) { + if (this.ndarrayCacheDevice == "cpu") { + await this.inst.fetchNDArrayCache(this.ndarrayCacheUrl, this.inst.cpu()); + } else { + assert(this.ndarrayCacheDevice == "webgpu"); + await this.inst.fetchNDArrayCache(this.ndarrayCacheUrl, this.inst.webgpu()); + } + } + + assert(this.inst !== undefined); + if (this.asyncOnServerLoad !== undefined) { + await this.asyncOnServerLoad(this.inst); + } + const fcreate = this.inst.getGlobalFunc("rpc.CreateEventDrivenServer"); + const messageHandler = fcreate( + (cbytes: Uint8Array): runtime.Scalar => { + assert(this.inst !== undefined); + if (this.socket.readyState == 1) { + // WebSocket will automatically close the socket + // if we burst send data that exceeds its internal buffer + // wait a bit before we send next one. + const sendDataWithCongestionControl = async (): Promise => { + const packetSize = 4 << 10; + const maxBufferAmount = 4 * packetSize; + const waitTimeMs = 20; + for ( + let offset = 0; + offset < cbytes.length; + offset += packetSize + ) { + const end = Math.min(offset + packetSize, cbytes.length); + while (this.socket.bufferedAmount >= maxBufferAmount) { + await new Promise((r) => setTimeout(r, waitTimeMs)); + } + this.socket.send(cbytes.slice(offset, end)); + } + }; + // Chain up the pending send so that the async send is always in-order. + this.pendingSend = this.pendingSend.then( + sendDataWithCongestionControl + ); + // Directly return since the data are "sent" from the caller's pov. + return this.inst.scalar(cbytes.length, "int32"); + } else { + return this.inst.scalar(0, "int32"); + } + }, + this.name, + this.key + ); + // message handler should persist across RPC runs + this.globalObjects.push( + this.inst.detachFromCurrentScope(messageHandler) + ); + const writeFlag = this.inst.scalar(3, "int32"); + + this.serverRecvData = (header: Uint8Array, body: Uint8Array): void => { + if (messageHandler(header, writeFlag) == 0) { + this.socket.close(); + } + if (messageHandler(body, writeFlag) == 0) { + this.socket.close(); + } + }; + + // Forward the same init sequence to the wasm RPC. + // The RPC will look for "rpc.wasmSession" + // and we will redirect it to the correct local session. + // register the callback to redirect the session to local. + const flocal = this.inst.getGlobalFunc("wasm.LocalSession"); + const localSession = flocal(); + assert(localSession instanceof runtime.Module); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + this.inst.registerFunc( + "rpc.WasmSession", + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (_args: unknown): runtime.Module => { + return localSession; + } + ); + messageHandler(header, writeFlag); + messageHandler(body, writeFlag); + + this.log("Finish initializing the Wasm Server.."); + this.requestBytes(SizeOf.I64); + this.state = RPCServerState.ReceivePacketHeader; + // call process events in case there are bufferred data. + this.processEvents(); + // recycle all values. + this.inst.endScope(); + }; + + this.state = RPCServerState.WaitForCallback; + asyncInitServer(); + } + + private log(msg: string): void { + this.logger(this.name + msg); + } + + private handleInitHeader(): void { + const reader = new ByteStreamReader(this.readFromBuffer(SizeOf.I32 * 2)); + const magic = reader.readU32(); + if (magic == RPC_MAGIC + 1) { + throw new Error("key: " + this.key + " has already been used in proxy"); + } else if (magic == RPC_MAGIC + 2) { + throw new Error("RPCProxy do not have matching client key " + this.key); + } + assert(magic == RPC_MAGIC, this.url + " is not an RPC Proxy"); + this.remoteKeyLength = reader.readU32(); + assert(this.pendingBytes == 0); + this.requestBytes(this.remoteKeyLength); + this.state = RPCServerState.InitHeaderKey; + } + + private handleInitHeaderKey(): void { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const remoteKey = Uint8ArrayToString( + this.readFromBuffer(this.remoteKeyLength) + ); + assert(this.pendingBytes == 0); + this.requestBytes(SizeOf.I64); + this.state = RPCServerState.ReceivePacketHeader; + } + + private checkLittleEndian(): void { + const a = new ArrayBuffer(4); + const b = new Uint8Array(a); + const c = new Uint32Array(a); + b[0] = 0x11; + b[1] = 0x22; + b[2] = 0x33; + b[3] = 0x44; + assert(c[0] === 0x44332211, "RPCServer little endian to work"); + } + + private requestBytes(nbytes: number): void { + this.pendingBytes += nbytes; + } + + private readFromBuffer(nbytes: number): Uint8Array { + const ret = new Uint8Array(nbytes); + let ptr = 0; + while (ptr < nbytes) { + assert(this.messageQueue.length != 0); + const nleft = nbytes - ptr; + if (this.messageQueue[0].byteLength <= nleft) { + const buffer = this.messageQueue.shift() as Uint8Array; + ret.set(buffer, ptr); + ptr += buffer.byteLength; + } else { + const buffer = this.messageQueue[0]; + ret.set(buffer.slice(0, nleft), ptr); + this.messageQueue[0] = buffer.slice(nleft, buffer.byteLength); + ptr += nleft; + } + } + this.buffredBytes -= nbytes; + this.pendingBytes -= nbytes; + return ret; + } +} diff --git a/packages/headless/src/worker/lib/tvm/runtime.d.ts b/packages/headless/src/worker/lib/tvm/runtime.d.ts new file mode 100644 index 0000000..8a73838 --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/runtime.d.ts @@ -0,0 +1,700 @@ +/// +/** + * TVM JS Wasm Runtime library. + */ +import { Pointer, PtrOffset } from "./ctypes"; +import { Environment } from "./environment"; +import { CachedCallStack, Memory } from "./memory"; +import { Disposable } from "./types"; +import { WebGPUContext } from "./webgpu"; +/** + * Type for PackedFunc inthe TVMRuntime. + */ +export type PackedFunc = ((...args: any) => any) & Disposable & { + _tvmPackedCell: PackedFuncCell; +}; +/** + * @internal + * FFI Library wrapper, maintains most runtime states. + */ +declare class FFILibrary implements Disposable { + wasm32: boolean; + memory: Memory; + exports: Record; + webGPUContext?: WebGPUContext; + private wasmInstance; + private recycledCallStacks; + constructor(wasmInstance: WebAssembly.Instance, imports: Record); + dispose(): void; + sizeofPtr(): number; + checkCall(code: number): void; + getOrAllocCallStack(): CachedCallStack; + recycleCallStack(callstack: CachedCallStack): void; + private validateInstance; + private checkExports; + private detectWasmMemory; +} +/** + * @internal + * Manages extra runtime context for the runtime. + */ +declare class RuntimeContext implements Disposable { + arrayGetItem: PackedFunc; + arrayGetSize: PackedFunc; + arrayMake: PackedFunc; + getSysLib: PackedFunc; + arrayCacheGet: PackedFunc; + arrayCacheUpdate: PackedFunc; + arrayCacheRemove: PackedFunc; + arrayCacheClear: PackedFunc; + arrayDecodeStorage: PackedFunc; + paramModuleFromCache: PackedFunc; + makeShapeTuple: PackedFunc; + ndarrayCreateView: PackedFunc; + sampleTopPFromLogits: PackedFunc; + private autoDisposeScope; + constructor(getGlobalFunc: (name: string) => PackedFunc); + dispose(): void; + beginScope(): void; + endScope(): void; + /** + * Track object for dispose in current scope. + * + * @param obj The object to be tracked. + * @returns the same object. + * @note This function only needs to be called for raw system C API values. + * The return value of PackedFunc will be automatically tracked. + */ + attachToCurrentScope(obj: T): T; + moveToParentScope(obj: T): T; + detachFromCurrentScope(obj: T): T; +} +/** + * A typed scalar constant used to represent a typed number + * argument to PackedFunc calls. + */ +export declare class Scalar { + /** The value. */ + value: number; + /** The data type of the scalar. */ + dtype: string; + constructor(value: number, dtype: string); +} +/** + * Cell holds the PackedFunc object. + */ +declare class PackedFuncCell implements Disposable { + private handle; + private lib; + constructor(handle: Pointer, lib: FFILibrary); + dispose(): void; + getHandle(requireNotNull?: boolean): Pointer; +} +/** + * Represent a runtime context where a NDArray can reside. + */ +export declare class DLDevice { + /** The device type code of the device. */ + deviceType: number; + /** The device index. */ + deviceId: number; + private lib; + constructor(deviceType: number | string, deviceId: number, lib: FFILibrary); + /** + * Synchronize the device + */ + sync(): Promise; + toString(): string; +} +/** + * The data type code in DLDataType + */ +export declare const enum DLDataTypeCode { + Int = 0, + UInt = 1, + Float = 2, + OpaqueHandle = 3 +} +/** + * Runtime data type of NDArray. + */ +export declare class DLDataType { + /** The type code */ + code: number; + /** Number of bits in the data type. */ + bits: number; + /** Number of vector lanes. */ + lanes: number; + constructor(code: number, bits: number, lanes: number); + toString(): string; + numStorageBytes(): number; +} +/** + * n-dimnesional array. + */ +export declare class NDArray implements Disposable { + /** Internal array handle. */ + private handle; + /** Number of dimensions. */ + ndim: number; + /** Data type of the array. */ + dtype: string; + /** Shape of the array. */ + shape: Array; + /** Device of the array. */ + device: DLDevice; + /** Whether it is a temporary view that can become invalid after the call. */ + isView: boolean; + private byteOffset; + private dltensor; + private dataPtr; + private lib; + private ctx; + private dlDataType; + constructor(handle: Pointer, isView: boolean, lib: FFILibrary, ctx: RuntimeContext); + /** + * Create a view of the array. + * @param shape The shape of the view. + * @returns The new sliced ndarray. + */ + view(shape: Array): NDArray; + /** + * Get handle of ndarray, check it is not null. + * + * @param requireNotNull require handle is not null. + * @returns The handle. + */ + getHandle(requireNotNull?: boolean): Pointer; + /** + * Get dataPtr of NDarray + * + * @returns The handle. + */ + getDataPtr(): Pointer; + dispose(): void; + /** + * Copy data from another NDArray or javascript array. + * The number of elements must match. + * + * @param data The source data array. + * @returns this + */ + copyFrom(data: NDArray | Array | Float32Array): this; + /** + * Copy data from raw bytes. + * @param data Uint8Array of bytes. + * @returns this + */ + copyFromRawBytes(data: Uint8Array): this; + /** + * Return a copied Uint8Array of the raw bytes in the NDArray. + * @returns The result array. + */ + toRawBytes(): Uint8Array; + /** + * Return a TypedArray copy of the NDArray, the specific type depends on + * the dtype of the NDArray. + * @returns The result array. + */ + toArray(): Float32Array | Float64Array | Int32Array | Int8Array | Uint8Array; + private getDLTensorFromArrayHandle; +} +/** + * Runtime Module. + */ +export declare class Module implements Disposable { + private handle; + private lib; + private makePackedFunc; + constructor(handle: Pointer, lib: FFILibrary, makePackedFunc: (ptr: Pointer) => PackedFunc); + dispose(): void; + /** + * Get handle of module, check it is not null. + * + * @param requireNotNull require handle is not null. + * @returns The handle. + */ + getHandle(requireNotNull?: boolean): Pointer; + /** + * Get a function in the module. + * @param name The name of the function. + * @param queryImports Whether to also query imports + * @returns The result function. + */ + getFunction(name: string, queryImports?: boolean): PackedFunc; + /** + * Import another module into the current runtime module. + * @param mod The module to be imported. + */ + importModule(mod: Module): void; +} +/** + * Generic object base + */ +export declare class TVMObject implements Disposable { + private handle; + private lib; + protected ctx: RuntimeContext; + constructor(handle: Pointer, lib: FFILibrary, ctx: RuntimeContext); + dispose(): void; + /** + * Get handle of module, check it is not null. + * + * @param requireNotNull require handle is not null. + * @returns The handle. + */ + getHandle(requireNotNull?: boolean): Pointer; + /** get the type index of the object */ + typeIndex(): number; + /** get the type key of the object */ + typeKey(): string; +} +/** Objectconstructor */ +type FObjectConstructor = (handle: Pointer, lib: FFILibrary, ctx: RuntimeContext) => TVMObject; +/** All possible object types. */ +type TVMObjectBase = TVMObject | NDArray | Module | PackedFunc; +/** Runtime array object. */ +export declare class TVMArray extends TVMObject { + constructor(handle: Pointer, lib: FFILibrary, ctx: RuntimeContext); + /** + * @returns the size of the array. + */ + size(): number; + /** + * Get index-th element of the array + * @param index the array index. + * @returns The element. + */ + get(index: number): TVMObjectBase; +} +export declare const enum VMAllocatorKind { + NAIVE_ALLOCATOR = 1, + POOLED_ALLOCATOR = 2 +} +/** + * VirtualMachine Executor. + * + * This is a thin wrapper of the underlying TVM module. + * you can also directly call set_input, run, and get_output + * of underlying module functions + */ +export declare class VirtualMachine implements Disposable { + private mod; + /** + * Constructor + * @param mod The underlying module, need to be detached. + * @param device The main device ro run VM on. + */ + constructor(mod: Module, device: DLDevice); + dispose(): void; + /** + * Get a function in the VM module. + * @param name The name of the function. + * @returns The result function. + */ + getFunction(name: string): PackedFunc; + /** + * Get the internal module. + */ + getInternalModule(): Module; +} +export interface NDArrayCacheEntry { + name: string; + shape: Array; + dtype: string; + format: "f32-to-bf16" | "raw"; + byteOffset: number; + nbytes: number; +} +export interface NDArrayShardEntry { + dataPath: string; + format: "raw-shard"; + nbytes: number; + records: Array; +} +export interface InitProgressReport { + type: 'init'; + progress: number; + timeElapsed: number; + currentChunk: number; + totalChunks: number; + fetchedBytes: number; + totalBytes: number; +} +export type InitProgressCallback = (report: InitProgressReport) => void; +/** + * TVM runtime instance. + * + * All objects(NDArray, Module, PackedFunc) returned by TVM runtim function call + * and PackedFunc instance are tracked through a scope mechanism that will get + * auto-released when we call EndScope. + * + * This is necessarily to be able to release the underlying WASM and WebGPU memory that + * are not tracked through JS native garbage collection mechanism. + * + * This does mean that we have to get familar with the following functions: + * - {@link beginScope} + * - {@link endScope} + * - {@link withNewScope} + * - {@link attachToCurrentScope} + * - {@link detachFromCurrentScope} + */ +export declare class Instance implements Disposable { + memory: Memory; + exports: Record; + cacheMetadata: Record; + private lib; + private env; + private objFactory; + private ctx; + private initProgressCallback; + /** + * Internal function(registered by the runtime) + */ + private wasmCreateLibraryModule?; + /** + * Constructor + * + * importObject can also be a {@link LibraryProvider} object, + * a WASI object, or an object containing wasmLibraryProvider field. + * + * @param wasmModule The input module or instance. + * @param importObject The imports to initialize the wasmInstance if it is not provided. + * @param wasmInstance Additional wasm instance argument for deferred construction. + * @param env Directly specified environment module. + * + * @see Please use the async version {@link instantiate} when targeting browsers. + */ + constructor(wasmModule: WebAssembly.Module, importObject?: Record, wasmInstance?: WebAssembly.Instance, env?: Environment); + /** + * Benchmark stable execution of the run function. + * + * @params run The run function + * @params dev The device to sync during each run. + * @number The number of times to compute the average. + * @repeat The number of times to repeat the run. + */ + benchmark(run: () => void, dev: DLDevice, number?: number, repeat?: number): Promise; + dispose(): void; + /** + * Obtain the runtime information in readable format. + */ + runtimeStatsText(): string; + /** + * Begin a new scope for tracking object disposal. + */ + beginScope(): void; + /** + * End a scope and release all created TVM objects + * under the current scope. + * + * Exception: one can call {@link moveToParentScope} to move + * a value to parent scope. + */ + endScope(): void; + /** + * Perform action under a new scope. + * + * @param action The action function. + * @returns The result value. + * + * @note For action to return a valid value, + * we will need to call {@link moveToParentScope} + * for the objects that are created in the scope. + */ + withNewScope(action: () => T): T; + /** + * Attach a detached obj to the auto-release pool of the current scope. + * + * @param obj The input obj. + * @note Normally user do not need to call this function explicitly, as + * all library call return values are explicitly attached to + * the current scope. You only need to do so when you call + * {@link detachFromCurrentScope} to create a detached object. + */ + attachToCurrentScope(obj: T): T; + /** + * Move obj's attachment to the parent scope. + * + * This function is useful to make sure objects are still + * alive when exit the current scope. + * + * @param obj The object to be moved. + * @returns The input obj. + */ + moveToParentScope(obj: T): T; + /** + * Detach the object from the current scope + * so it won't be released via auto-release during endscope. + * + * User needs to either explicitly call obj.dispose(), or + * {@link attachToCurrentScope} to re-attach to the current scope. + * + * This function can be used to return values to the parent scope. + * @param obj The object. + */ + detachFromCurrentScope(obj: T): T; + /** + * Get system-wide library module in the wasm. + * System lib is a global module that contains self register functions in startup. + * @returns The system library module. + */ + systemLib(): Module; + /** + * List all the global function names registered in the runtime. + * @returns The name list. + */ + listGlobalFuncNames(): Array; + /** + * Register function to be global function in tvm runtime. + * @param name The name of the function. + * @param f function to be registered. + * @param override Whether overwrite function in existing registry. + */ + registerFunc(name: string, func: PackedFunc | Function, override?: boolean): void; + /** + * Get global PackedFunc from the runtime. + * @param name The name of the function. + * @param autoAttachToScope Whether to track it via autoDispose + * @returns The result function. + */ + getGlobalFunc(name: string): PackedFunc; + private getGlobalFuncInternal; + /** + * Check if func is PackedFunc. + * + * @param func The input. + * @returns The check result. + */ + isPackedFunc(func: unknown): boolean; + /** + * Convert func to PackedFunc + * + * @param func Input function. + * @returns The converted function. + */ + toPackedFunc(func: Function): PackedFunc; + private toPackedFuncInternal; + /** + * Setup a virtual machine module with given device. + * + * @param dev DLDevice the device. + * @returns The created virtual machime. + */ + createVirtualMachine(dev: DLDevice): VirtualMachine; + /** + * Register a call back for fetch progress. + * + * @param cb the fetch progress callback. + */ + registerInitProgressCallback(cb: InitProgressCallback): void; + /** + * Get parameters in the form of prefix_i + * + * @param prefix The parameter prefix. + * @param numParams Number of parameters. + * @returns + */ + getParamsFromCache(prefix: string, numParams: number): TVMObject; + /** + * Get NDArray from cache. + * @param name The name of array. + * @returns The result. + */ + ndarrayCacheGet(name: string): NDArray | undefined; + /** + * Get NDArray from cache. + * @param name The name of array. + * @returns The result. + */ + ndarrayCacheRemove(name: string): NDArray | undefined; + /** + * Update the ndarray cache. + * @param name The name of the array. + * @param arr The content. + */ + ndarrayCacheUpdate(name: string, arr: NDArray, override?: boolean): void; + /** + * Update the ndarray cache. + * @param name The name of the array. + * @param arr The content. + */ + ndarrayCacheClear(): void; + /** + * Fetch NDArray cache from url. + * + * @param ndarrayCacheUrl The cache url. + * @param device The device to be fetched to. + * @returns The meta data + */ + fetchNDArrayCache(ndarrayCacheUrl: string, device: DLDevice): Promise; + /** + * Fetch list of NDArray into the NDArrayCache. + * + * @param ndarrayCacheUrl The cache url. + * @param list The list of array data. + * @param device The device to store the data to. + */ + private fetchNDArrayCacheInternal; + /** + * Convert dtype to {@link DLDataType} + * + * @param dtype The input dtype string or DLDataType. + * @returns The converted result. + */ + toDLDataType(dtype: string | DLDataType): DLDataType; + /** + * Create a new {@link Scalar} that can be passed to a PackedFunc. + * @param value The number value. + * @param dtype The dtype string. + * @returns The created scalar. + */ + scalar(value: number, dtype: string): Scalar; + /** + * Create a new {@link DLDevice} + * @param deviceType The device type. + * @param deviceId The device index. + * @returns The created device. + */ + device(deviceType: number | string, deviceId?: number): DLDevice; + /** + * Create a new cpu {@link DLDevice} + * @param deviceId The device index. + */ + cpu(deviceId?: number): DLDevice; + /** + * Create a new webgpu {@link DLDevice} + * @param deviceId The device index. + */ + webgpu(deviceId?: number): DLDevice; + /** + * Create an empty {@link NDArray} with given shape and dtype. + * + * @param shape The shape of the array. + * @param dtype The data type of the array. + * @param dev The device of the ndarray. + * @returns The created ndarray. + */ + empty(shape: Array | number, dtype?: string | DLDataType, dev?: DLDevice): NDArray; + /** + * Create am uniform {@link NDArray} with given shape. + * + * @param shape The shape of the array. + * @param low The low value. + * @param high The high value. + * @param dev The device of the ndarray. + * @returns The created ndarray. + */ + uniform(shape: Array, low: number, high: number, dev: DLDevice): NDArray; + /** + * Sample index via top-p sampling. + * + * @param logits The input logits before normalization. + * @param temperature The temperature factor, will take argmax if temperature = 0.0 + * @param top_p The top_p + * @returns The sampled index. + */ + sampleTopPFromLogits(logits: NDArray, temperature: number, top_p: number): number; + /** + * Bind canvas to the current WebGPU context + * @param canvas The canvas. + */ + bindCanvas(canvas: HTMLCanvasElement): void; + /** + * Show image in canvas. + * + * @param dataRGBA Image array in height x width uint32 NDArray RGBA format on GPU. + */ + showImage(dataRGBA: NDArray): void; + /** + * Clear canvas + */ + clearCanvas(): void; + /** + * Create an tuple {@link TVMArray} input array. + * + * The input array can be passed to tvm runtime function + * and needs to b explicitly disposed. + * + * @param inputs The input array + * @returns The result array. + */ + makeTVMArray(inputs: Array): TVMArray; + /** + * Create a shape tuple to pass to runtime. + * @param shape The shape . + * @returns The created shape tuple. + */ + makeShapeTuple(shape: Array): TVMObject; + /** + * Get type index from type key. + * @param typeKey The type key. + * @returns The corresponding type index. + */ + typeKey2Index(typeKey: string): number; + /** + * Register an object constructor. + * @param typeKey The name of the function. + * @param func function to be registered. + * @param override Whether overwrite function in existing registry. + */ + registerObjectConstructor(typeKey: string, func: FObjectConstructor, override?: boolean): void; + /** + * Register an asyncfunction to be global function in the server. + * @param name The name of the function. + * @param func function to be registered. + * @param override Whether overwrite function in existing registry. + * + * @note The async function will only be used for serving remote calls in the rpc. + */ + registerAsyncServerFunc(name: string, func: Function, override?: boolean): void; + /** + * Asynchrously load webgpu pipelines when possible. + * @param mod The input module. + */ + asyncLoadWebGPUPiplines(mod: Module): Promise; + /** + * Initialize webgpu in the runtime. + * @param device The given GPU device. + */ + initWebGPU(device: GPUDevice): void; + /** Register all object factory */ + private registerObjectFactoryFuncs; + /** Register global packed functions needed by the backend to the env. */ + private registerEnvGlobalPackedFuncs; + private createPackedFuncFromCFunc; + /** + * Set packed function arguments into the location indicated by argsValue and argsCode. + * Allocate new temporary space from the stack if necessary. + * + * @parma stack The call stack + * @param args The input arguments. + * @param argsValue The offset of argsValue. + * @param argsCode The offset of argsCode. + */ + setPackedArguments(stack: CachedCallStack, args: Array, argsValue: PtrOffset, argsCode: PtrOffset): void; + private wrapJSFuncAsPackedCFunc; + private makePackedFunc; + /** + * Creaye return value of the packed func. The value us auto-tracked for dispose. + * @param rvaluePtr The location of rvalue + * @param tcode The type code. + * @param callbackArg Whether it is being used in callbackArg. + * @returns The JS value. + */ + private retValueToJS; +} +/** + * Asynchrously instantiate a new {@link Instance}. + * + * importObject can also be a {@link LibraryProvider} object, + * a WASI object, or an object containing wasmLibraryProvider field. + * We can take benefit of syslib implementations from the Emscripten + * by passing its generated js Module as the imports. + * + * @param bufferSource The source to be compiled. + * @param importObject The import objects. + * @param logger The system logger. + */ +export declare function instantiate(bufferSource: ArrayBuffer, importObject?: Record, logger?: (msg: string) => void): Promise; +export {}; diff --git a/packages/headless/src/worker/lib/tvm/runtime.ts b/packages/headless/src/worker/lib/tvm/runtime.ts new file mode 100644 index 0000000..af54b72 --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/runtime.ts @@ -0,0 +1,2280 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * TVM JS Wasm Runtime library. + */ +import { ArgTypeCode, Pointer, PtrOffset, SizeOf } from "./ctypes"; +import { Environment } from "./environment"; +import { CachedCallStack, Memory } from "./memory"; +import { StringToUint8Array, assert } from "./support"; +import { Disposable } from "./types"; +import { FunctionInfo, WebGPUContext } from "./webgpu"; + +import * as compact from "./compact"; +import * as ctypes from "./ctypes"; + +/** + * Type for PackedFunc inthe TVMRuntime. + */ +export type PackedFunc = ((...args: any) => any) & + Disposable & { _tvmPackedCell: PackedFuncCell }; + +/** + * @internal + * FFI Library wrapper, maintains most runtime states. + */ +class FFILibrary implements Disposable { + wasm32: boolean; + memory: Memory; + exports: Record; + webGPUContext?: WebGPUContext; + private wasmInstance: WebAssembly.Instance; + private recycledCallStacks: Array = []; + + constructor( + wasmInstance: WebAssembly.Instance, + imports: Record + ) { + this.wasmInstance = wasmInstance; + this.memory = new Memory(this.detectWasmMemory(this.wasmInstance, imports)); + assert( + this.wasmInstance.exports !== undefined, + "Expect the library module contains exports" + ); + this.exports = this.wasmInstance.exports as Record; + this.wasm32 = this.memory.wasm32; + this.validateInstance(); + } + + dispose(): void { + while (this.recycledCallStacks.length != 0) { + (this.recycledCallStacks.pop() as Disposable).dispose(); + } + this.webGPUContext?.dispose(); + } + + sizeofPtr(): number { + return this.memory.sizeofPtr(); + } + + checkCall(code: number): void { + if (code != 0) { + const msgPtr = (this.exports + .TVMGetLastError as ctypes.FTVMGetLastError)(); + throw new Error("TVMError: " + this.memory.loadCString(msgPtr)); + } + } + + getOrAllocCallStack(): CachedCallStack { + if (this.recycledCallStacks.length != 0) { + return this.recycledCallStacks.pop() as CachedCallStack; + } + return new CachedCallStack( + this.memory, + this.exports.TVMWasmAllocSpace as ctypes.FTVMWasmAllocSpace, + this.exports.TVMWasmFreeSpace as ctypes.FTVMWasmFreeSpace + ); + } + + recycleCallStack(callstack: CachedCallStack): void { + callstack.reset(); + this.recycledCallStacks.push(callstack); + } + + private validateInstance(): void { + this.checkExports(["TVMWasmAllocSpace", "TVMWasmFreeSpace", "TVMFuncFree"]); + } + + private checkExports(funcNames: Array): void { + const missList = []; + for (const name of funcNames) { + const f = this.exports[name]; + if (!(f instanceof Function)) { + missList.push(name); + } + } + if (missList.length != 0) { + throw new Error("Cannot find " + missList + " in exports"); + } + } + + private detectWasmMemory( + instance: WebAssembly.Instance, + imports: Record + ): WebAssembly.Memory { + if (instance.exports.memory instanceof WebAssembly.Memory) { + return instance.exports.memory; + } + if (imports.env && imports.env.memory instanceof WebAssembly.Memory) { + return imports.env.memory; + } + + throw new Error( + "Cannt detect wasm memory from imports " + + imports + + " or exports" + + instance.exports + ); + } +} + +/** + * @internal + * Manages extra runtime context for the runtime. + */ +class RuntimeContext implements Disposable { + arrayGetItem: PackedFunc; + arrayGetSize: PackedFunc; + arrayMake: PackedFunc; + getSysLib: PackedFunc; + arrayCacheGet: PackedFunc; + arrayCacheUpdate: PackedFunc; + arrayCacheRemove: PackedFunc; + arrayCacheClear: PackedFunc; + arrayDecodeStorage: PackedFunc; + paramModuleFromCache: PackedFunc; + makeShapeTuple: PackedFunc; + ndarrayCreateView: PackedFunc; + sampleTopPFromLogits: PackedFunc; + + private autoDisposeScope: Array> = []; + + constructor(getGlobalFunc: (name: string) => PackedFunc) { + this.arrayGetItem = getGlobalFunc("runtime.ArrayGetItem"); + this.arrayGetSize = getGlobalFunc("runtime.ArraySize"); + this.arrayMake = getGlobalFunc("runtime.Array"); + this.getSysLib = getGlobalFunc("runtime.SystemLib"); + this.arrayCacheGet = getGlobalFunc("vm.builtin.ndarray_cache.get"); + this.arrayCacheRemove = getGlobalFunc("vm.builtin.ndarray_cache.remove"); + this.arrayCacheUpdate = getGlobalFunc("vm.builtin.ndarray_cache.update"); + this.arrayCacheClear = getGlobalFunc("vm.builtin.ndarray_cache.clear"); + this.arrayDecodeStorage = getGlobalFunc("tvmjs.array.decode_storage"); + this.paramModuleFromCache = getGlobalFunc("vm.builtin.param_module_from_cache"); + this.makeShapeTuple = getGlobalFunc("runtime.ShapeTuple"); + this.ndarrayCreateView = getGlobalFunc("runtime.TVMArrayCreateView"); + this.sampleTopPFromLogits = getGlobalFunc("vm.builtin.sample_top_p_from_logits"); + } + + dispose(): void { + // call array cache clear to clear all cached items + this.arrayCacheClear.dispose(); + this.arrayGetItem.dispose(); + this.arrayGetSize.dispose(); + this.arrayMake.dispose(); + this.arrayCacheGet.dispose(); + this.arrayCacheRemove.dispose(); + this.arrayCacheUpdate.dispose(); + this.arrayCacheClear.dispose(); + this.arrayDecodeStorage.dispose(); + this.paramModuleFromCache.dispose(); + this.makeShapeTuple.dispose(); + this.ndarrayCreateView.dispose(); + this.sampleTopPFromLogits.dispose(); + } + + beginScope(): void { + this.autoDisposeScope.push([]); + } + + endScope(): void { + if (this.autoDisposeScope.length == 0) { + throw Error("tvm.endScope called when the stack is empty."); + } + // automatically dispose all the tracked values in the current scope. + const currScope = this.autoDisposeScope.pop() as Array; + for (let i = 0; i < currScope.length; ++i) { + const val = currScope[i]; + if (val !== undefined) { + val.dispose(); + } + } + } + + /** + * Track object for dispose in current scope. + * + * @param obj The object to be tracked. + * @returns the same object. + * @note This function only needs to be called for raw system C API values. + * The return value of PackedFunc will be automatically tracked. + */ + attachToCurrentScope(obj: T): T { + if (this.autoDisposeScope.length == 0) { + throw Error("Must call beginScope to use functions that returns TVM objects"); + } + const currScope = this.autoDisposeScope[this.autoDisposeScope.length - 1]; + currScope.push(obj); + return obj; + } + + moveToParentScope(obj: T): T { + this.detachFromCurrentScope(obj); + if (this.autoDisposeScope.length < 2) { + throw Error("moveToParentScope: Parent scope do not exist"); + } + const parentScope = this.autoDisposeScope[this.autoDisposeScope.length - 2]; + parentScope.push(obj); + return obj; + } + + detachFromCurrentScope(obj: T): T { + const currScope = this.autoDisposeScope[this.autoDisposeScope.length - 1]; + let occurance = 0; + for (let i = 0; i < currScope.length; ++i) { + if (currScope[i] === obj) { + occurance += 1; + currScope[i] = undefined; + } + } + if (occurance == 0) { + throw Error("Cannot find obj in the current auto conversion pool"); + } + if (occurance > 1) { + throw Error("Value attached to scope multiple times"); + } + return obj; + } +} + +/** + * A typed scalar constant used to represent a typed number + * argument to PackedFunc calls. + */ +export class Scalar { + /** The value. */ + value: number; + /** The data type of the scalar. */ + dtype: string; + + constructor(value: number, dtype: string) { + this.value = value; + this.dtype = dtype; + } +} + +/** + * Cell holds the PackedFunc object. + */ +class PackedFuncCell implements Disposable { + private handle: Pointer; + private lib: FFILibrary; + + constructor(handle: Pointer, lib: FFILibrary) { + this.handle = handle; + this.lib = lib; + } + + dispose(): void { + if (this.handle != 0) { + this.lib.checkCall( + (this.lib.exports.TVMFuncFree as ctypes.FTVMFuncFree)(this.handle) + ); + this.handle = 0; + } + } + + getHandle(requireNotNull: boolean = true): Pointer { + if (requireNotNull && this.handle == 0) { + throw Error("PackedFunc has already been disposed"); + } + return this.handle; + } +} + +const DeviceEnumToStr: Record = { + 1: "cpu", + 2: "cuda", + 4: "opencl", + 8: "metal", + 15: "webgpu" +}; + +const DeviceStrToEnum: Record = { + cpu: 1, + cuda: 2, + cl: 4, + opencl: 4, + vulkan: 7, + metal: 8, + webgpu: 15 +}; + +/** + * Represent a runtime context where a NDArray can reside. + */ +export class DLDevice { + /** The device type code of the device. */ + deviceType: number; + /** The device index. */ + deviceId: number; + + private lib: FFILibrary; + + constructor(deviceType: number | string, deviceId: number, lib: FFILibrary) { + const tp = typeof deviceType; + if (tp == "string") { + this.deviceType = DeviceStrToEnum[deviceType]; + if (this.deviceType == undefined) { + throw new Error("Cannot recogonize deviceType " + deviceType); + } + } else if (tp == "number") { + this.deviceType = deviceType as number; + } else { + throw new Error("Cannot take type " + tp + " as deviceType"); + } + this.deviceId = deviceId; + this.lib = lib; + } + + /** + * Synchronize the device + */ + async sync(): Promise { + if (this.deviceType == DeviceStrToEnum.webgpu) { + assert(this.lib.webGPUContext !== undefined); + await this.lib.webGPUContext.sync(); + } + } + + toString(): string { + return ( + DeviceEnumToStr[this.deviceType] + "(" + this.deviceId.toString() + ")" + ); + } +} +/** + * The data type code in DLDataType + */ +export const enum DLDataTypeCode { + Int = 0, + UInt = 1, + Float = 2, + OpaqueHandle = 3 +} + +const DLDataTypeCodeToStr: Record = { + 0: "int", + 1: "uint", + 2: "float", + 3: "handle", +}; + +/** + * Runtime data type of NDArray. + */ +export class DLDataType { + /** The type code */ + code: number; + /** Number of bits in the data type. */ + bits: number; + /** Number of vector lanes. */ + lanes: number; + + constructor(code: number, bits: number, lanes: number) { + this.code = code; + this.bits = bits; + this.lanes = lanes; + } + + toString(): string { + const ret = DLDataTypeCodeToStr[this.code] + this.bits.toString(); + if (this.lanes != 1) { + return ret + "x" + this.lanes.toString(); + } else { + return ret; + } + } + + numStorageBytes(): number { + return (this.bits * this.lanes + 7) >> 3; + } +} + +/** + * n-dimnesional array. + */ +export class NDArray implements Disposable { + /** Internal array handle. */ + private handle: Pointer; + /** Number of dimensions. */ + ndim: number; + /** Data type of the array. */ + dtype: string; + /** Shape of the array. */ + shape: Array; + /** Device of the array. */ + device: DLDevice; + /** Whether it is a temporary view that can become invalid after the call. */ + isView: boolean; + private byteOffset: number; + private dltensor: Pointer; + private dataPtr: Pointer; + private lib: FFILibrary; + private ctx: RuntimeContext; + private dlDataType: DLDataType; + + constructor(handle: Pointer, isView: boolean, lib: FFILibrary, ctx: RuntimeContext) { + this.handle = handle; + this.isView = isView; + this.lib = lib; + this.ctx = ctx; + + if (this.isView) { + this.dltensor = handle; + } else { + this.dltensor = this.getDLTensorFromArrayHandle(this.handle); + } + // constant offsets. + const arrayOffsetData = 0; + const arrayOffsetContext = arrayOffsetData + this.lib.sizeofPtr(); + const arrayOffsetDevType = arrayOffsetContext; + const arrayOffsetDevId = arrayOffsetContext + SizeOf.I32; + const arrayOffsetNdim = arrayOffsetContext + SizeOf.DLDevice; + const arrayOffsetDtype = arrayOffsetNdim + SizeOf.I32; + const arrayOffsetDtypeCode = arrayOffsetDtype; + const arrayOffsetDtypeBits = arrayOffsetDtype + SizeOf.U8; + const arrayOffsetDtypeLanes = arrayOffsetDtypeBits + SizeOf.U8; + const arrayOffsetShape = arrayOffsetDtype + SizeOf.DLDataType; + const arrayOffsetStrides = arrayOffsetShape + this.lib.sizeofPtr(); + const arrayOffsetByteOffset = arrayOffsetStrides + this.lib.sizeofPtr(); + // dataPtr + this.dataPtr = lib.memory.loadPointer(this.dltensor); + // ndim + this.ndim = lib.memory.loadI32(this.dltensor + arrayOffsetNdim); + // shape + const cshapePtr = lib.memory.loadPointer(this.dltensor + arrayOffsetShape); + this.shape = []; + for (let i = 0; i < this.ndim; ++i) { + this.shape.push(lib.memory.loadI64(cshapePtr + i * SizeOf.I64)); + } + // dtype + const code = lib.memory.loadU8(this.dltensor + arrayOffsetDtypeCode); + const bits = lib.memory.loadU8(this.dltensor + arrayOffsetDtypeBits); + const lanes = lib.memory.loadU16(this.dltensor + arrayOffsetDtypeLanes); + this.dlDataType = new DLDataType(code, bits, lanes); + this.dtype = this.dlDataType.toString(); + + // device + const deviceType = lib.memory.loadI32(this.dltensor + arrayOffsetDevType); + const deviceId = lib.memory.loadI32(this.dltensor + arrayOffsetDevId); + this.device = new DLDevice(deviceType, deviceId, lib); + + // byte_offset + this.byteOffset = lib.memory.loadI64(this.dltensor + arrayOffsetByteOffset); + } + + /** + * Create a view of the array. + * @param shape The shape of the view. + * @returns The new sliced ndarray. + */ + view(shape: Array): NDArray { + const shapeArray = shape.map((value) => new Scalar(value, "int")); + return this.ctx.ndarrayCreateView(this, this.ctx.makeShapeTuple(...shapeArray)); + } + + /** + * Get handle of ndarray, check it is not null. + * + * @param requireNotNull require handle is not null. + * @returns The handle. + */ + getHandle(requireNotNull: boolean = true): Pointer { + if (requireNotNull && this.handle == 0) { + throw Error("NDArray has already been disposed"); + } + return this.handle; + } + + /** + * Get dataPtr of NDarray + * + * @returns The handle. + */ + getDataPtr(): Pointer { + if (this.handle == 0) { + throw Error("NDArray has already been disposed"); + } + return this.dataPtr; + } + + dispose(): void { + if (this.handle != 0 && !this.isView) { + this.lib.checkCall( + (this.lib.exports.TVMArrayFree as ctypes.FTVMArrayFree)(this.handle) + ); + this.handle = 0; + } + } + /** + * Copy data from another NDArray or javascript array. + * The number of elements must match. + * + * @param data The source data array. + * @returns this + */ + copyFrom(data: NDArray | Array | Float32Array): this { + if (data instanceof NDArray) { + this.lib.checkCall( + (this.lib.exports.TVMArrayCopyFromTo as ctypes.FTVMArrayCopyFromTo)( + data.getHandle(), + this.getHandle(), + 0 + ) + ); + return this; + } else { + const size = this.shape.reduce((a, b) => { + return a * b; + }, 1); + if (data.length != size) { + throw new Error( + "data size and shape mismatch data.length" + + data.length + + " vs " + + size + ); + } + let buffer: ArrayBuffer; + if (this.dtype == "float32") { + buffer = Float32Array.from(data).buffer; + } else if (this.dtype == "float64") { + buffer = Float64Array.from(data).buffer; + } else if (this.dtype == "int32") { + buffer = Int32Array.from(data).buffer; + } else if (this.dtype == "int8") { + buffer = Int8Array.from(data).buffer; + } else if (this.dtype == "uint8") { + buffer = Uint8Array.from(data).buffer; + } else { + throw new Error("Unsupported data type " + this.dtype); + } + return this.copyFromRawBytes(new Uint8Array(buffer)); + } + } + /** + * Copy data from raw bytes. + * @param data Uint8Array of bytes. + * @returns this + */ + copyFromRawBytes(data: Uint8Array): this { + // short cut for gpu copy + if (this.device.deviceType == DeviceStrToEnum.webgpu) { + this.lib.webGPUContext?.copyRawBytesToBuffer(data, this.getDataPtr(), 0, data.length); + return this; + } + // CPU copy + const size = this.shape.reduce((a, b) => { + return a * b; + }, 1); + const nbytes = this.dlDataType.numStorageBytes() * size; + if (nbytes != data.length) { + throw new Error("Expect the data's length equals nbytes=" + nbytes); + } + + const stack = this.lib.getOrAllocCallStack(); + + const tempOffset = stack.allocRawBytes(nbytes); + const tempPtr = stack.ptrFromOffset(tempOffset); + this.lib.memory.storeRawBytes(tempPtr, data); + this.lib.checkCall( + (this.lib.exports.TVMArrayCopyFromBytes as ctypes.FTVMArrayCopyFromBytes)( + this.getHandle(), + tempPtr, + nbytes + ) + ); + + this.lib.recycleCallStack(stack); + return this; + } + /** + * Return a copied Uint8Array of the raw bytes in the NDArray. + * @returns The result array. + */ + toRawBytes(): Uint8Array { + if (this.device.deviceType != DeviceStrToEnum.cpu) { + throw new Error("Can only sync copy CPU array, use cpu_arr.copyfrom(gpu_arr) then sync instead."); + } + const size = this.shape.reduce((a, b) => { + return a * b; + }, 1); + + const nbytes = this.dlDataType.numStorageBytes() * size; + const stack = this.lib.getOrAllocCallStack(); + + const tempOffset = stack.allocRawBytes(nbytes); + const tempPtr = stack.ptrFromOffset(tempOffset); + this.lib.checkCall( + (this.lib.exports.TVMArrayCopyToBytes as ctypes.FTVMArrayCopyToBytes)( + this.getHandle(), + tempPtr, + nbytes + ) + ); + const ret = this.lib.memory.loadRawBytes(tempPtr, nbytes); + + this.lib.recycleCallStack(stack); + return ret; + } + + /** + * Return a TypedArray copy of the NDArray, the specific type depends on + * the dtype of the NDArray. + * @returns The result array. + */ + toArray(): Float32Array | Float64Array | Int32Array | Int8Array | Uint8Array { + const stype = this.dtype; + if (stype == "float32") { + return new Float32Array(this.toRawBytes().buffer); + } else if (stype == "float64") { + return new Float64Array(this.toRawBytes().buffer); + } else if (stype == "int32") { + return new Int32Array(this.toRawBytes().buffer); + } else if (stype == "int8") { + return new Int8Array(this.toRawBytes().buffer); + } else if (stype == "uint8") { + return new Uint8Array(this.toRawBytes().buffer); + } else { + throw new Error("Unsupported data type " + this.dtype); + } + } + + private getDLTensorFromArrayHandle(handle: Pointer): Pointer { + // Note: this depends on the NDArray C ABI. + // keep this function in case of ABI change. + return handle; + } +} + +/** + * Runtime Module. + */ +export class Module implements Disposable { + private handle: Pointer; + private lib: FFILibrary; + private makePackedFunc: (ptr: Pointer) => PackedFunc; + + constructor( + handle: Pointer, + lib: FFILibrary, + makePackedFunc: (ptr: Pointer) => PackedFunc + ) { + this.handle = handle; + this.lib = lib; + this.makePackedFunc = makePackedFunc; + } + + dispose(): void { + if (this.handle != 0) { + this.lib.checkCall( + (this.lib.exports.TVMModFree as ctypes.FTVMModFree)(this.handle) + ); + this.handle = 0; + } + } + + /** + * Get handle of module, check it is not null. + * + * @param requireNotNull require handle is not null. + * @returns The handle. + */ + getHandle(requireNotNull: boolean = true): Pointer { + if (requireNotNull && this.handle == 0) { + throw Error("Module has already been disposed"); + } + return this.handle; + } + + /** + * Get a function in the module. + * @param name The name of the function. + * @param queryImports Whether to also query imports + * @returns The result function. + */ + getFunction(name: string, queryImports: boolean = true): PackedFunc { + if (this.handle == 0) { + throw Error("Module has already been disposed"); + } + const stack = this.lib.getOrAllocCallStack(); + const nameOffset = stack.allocRawBytes(name.length + 1); + stack.storeRawBytes(nameOffset, StringToUint8Array(name)); + + const outOffset = stack.allocPtrArray(1); + const outPtr = stack.ptrFromOffset(outOffset); + + stack.commitToWasmMemory(outOffset); + + this.lib.checkCall( + (this.lib.exports.TVMModGetFunction as ctypes.FTVMModGetFunction)( + this.getHandle(), + stack.ptrFromOffset(nameOffset), + queryImports ? 1 : 0, + outPtr + ) + ); + const handle = this.lib.memory.loadPointer(outPtr); + this.lib.recycleCallStack(stack); + if (handle == 0) { + throw Error("Cannot find function " + name); + } + const ret = this.makePackedFunc(handle); + return ret; + } + + /** + * Import another module into the current runtime module. + * @param mod The module to be imported. + */ + importModule(mod: Module): void { + this.lib.checkCall( + (this.lib.exports.TVMModImport as ctypes.FTVMModImport)( + this.getHandle(), + mod.getHandle() + ) + ); + } +} + +/** + * Generic object base + */ +export class TVMObject implements Disposable { + private handle: Pointer; + private lib: FFILibrary; + protected ctx: RuntimeContext; + + constructor( + handle: Pointer, + lib: FFILibrary, + ctx: RuntimeContext + ) { + this.handle = handle; + this.lib = lib; + this.ctx = ctx; + } + + dispose(): void { + if (this.handle != 0) { + this.lib.checkCall( + (this.lib.exports.TVMObjectFree as ctypes.FTVMObjectFree)(this.handle) + ); + this.handle = 0; + } + } + + /** + * Get handle of module, check it is not null. + * + * @param requireNotNull require handle is not null. + * @returns The handle. + */ + getHandle(requireNotNull: boolean = true): Pointer { + if (requireNotNull && this.handle == 0) { + throw Error("Module has already been disposed"); + } + return this.handle; + } + + /** get the type index of the object */ + typeIndex(): number { + if (this.handle == 0) { + throw Error("The current Object has already been disposed"); + } + const stack = this.lib.getOrAllocCallStack(); + const outOffset = stack.allocPtrArray(1); + const outPtr = stack.ptrFromOffset(outOffset); + + this.lib.checkCall( + (this.lib.exports.TVMObjectGetTypeIndex as ctypes.FTVMObjectGetTypeIndex)( + this.getHandle(), + outPtr + ) + ); + const result = this.lib.memory.loadU32(outPtr); + this.lib.recycleCallStack(stack); + return result; + } + + /** get the type key of the object */ + typeKey(): string { + const type_index = this.typeIndex(); + const stack = this.lib.getOrAllocCallStack(); + const outOffset = stack.allocPtrArray(1); + const outPtr = stack.ptrFromOffset(outOffset); + this.lib.checkCall( + (this.lib.exports.TVMObjectTypeIndex2Key as ctypes.FTVMObjectTypeIndex2Key)( + type_index, + outPtr + ) + ); + const result = this.lib.memory.loadCString( + this.lib.memory.loadPointer(outPtr) + ); + this.lib.recycleCallStack(stack); + return result; + } +} + +/** Objectconstructor */ +type FObjectConstructor = (handle: Pointer, lib: FFILibrary, ctx: RuntimeContext) => TVMObject; + +/** All possible object types. */ +type TVMObjectBase = TVMObject | NDArray | Module | PackedFunc; + +/** Runtime array object. */ +export class TVMArray extends TVMObject { + constructor( + handle: Pointer, + lib: FFILibrary, + ctx: RuntimeContext + ) { + super(handle, lib, ctx); + } + + /** + * @returns the size of the array. + */ + size(): number { + return this.ctx.arrayGetSize(this) as number; + } + /** + * Get index-th element of the array + * @param index the array index. + * @returns The element. + */ + get(index: number): TVMObjectBase { + return this.ctx.arrayGetItem(this, new Scalar(index, "int32")) as TVMObjectBase; + } +} + +export const enum VMAllocatorKind { + NAIVE_ALLOCATOR = 1, + POOLED_ALLOCATOR = 2, +} + +/** + * VirtualMachine Executor. + * + * This is a thin wrapper of the underlying TVM module. + * you can also directly call set_input, run, and get_output + * of underlying module functions + */ +export class VirtualMachine implements Disposable { + private mod: Module; + /** + * Constructor + * @param mod The underlying module, need to be detached. + * @param device The main device ro run VM on. + */ + constructor(mod: Module, device: DLDevice) { + this.mod = mod; + this.mod.getFunction("vm_initialization")( + new Scalar(device.deviceType, "int"), + new Scalar(device.deviceId, "int"), + new Scalar(VMAllocatorKind.POOLED_ALLOCATOR, "int"), + // explicitly specify host device type + new Scalar(DeviceStrToEnum.cpu, "int"), + new Scalar(0, "int"), + new Scalar(VMAllocatorKind.POOLED_ALLOCATOR, "int"), + ); + } + + dispose(): void { + this.mod.dispose(); + } + /** + * Get a function in the VM module. + * @param name The name of the function. + * @returns The result function. + */ + getFunction(name: string): PackedFunc { + return this.mod.getFunction(name); + } + + /** + * Get the internal module. + */ + getInternalModule(): Module { + return this.mod; + } +} + +/** Code used as the first argument of the async callback. */ +const enum AyncCallbackCode { + kReturn = 4, + kException = 5, +} +export interface NDArrayCacheEntry { + name: string; + shape: Array; + dtype: string; + format: "f32-to-bf16" | "raw"; + byteOffset: number; + nbytes: number; +} + +export interface NDArrayShardEntry { + dataPath: string; + format: "raw-shard"; + nbytes: number; + records: Array; +} + +export interface InitProgressReport { + type: 'init'; + progress: number; + timeElapsed: number; + currentChunk: number; + totalChunks: number; + fetchedBytes: number; + totalBytes: number; +} + +export type InitProgressCallback = (report: InitProgressReport) => void; + +/** + * TVM runtime instance. + * + * All objects(NDArray, Module, PackedFunc) returned by TVM runtim function call + * and PackedFunc instance are tracked through a scope mechanism that will get + * auto-released when we call EndScope. + * + * This is necessarily to be able to release the underlying WASM and WebGPU memory that + * are not tracked through JS native garbage collection mechanism. + * + * This does mean that we have to get familar with the following functions: + * - {@link beginScope} + * - {@link endScope} + * - {@link withNewScope} + * - {@link attachToCurrentScope} + * - {@link detachFromCurrentScope} + */ +export class Instance implements Disposable { + memory: Memory; + exports: Record; + cacheMetadata: Record = {}; + private lib: FFILibrary; + private env: Environment; + private objFactory: Map; + private ctx: RuntimeContext; + private initProgressCallback: Array = []; + + /** + * Internal function(registered by the runtime) + */ + private wasmCreateLibraryModule?: PackedFunc & + ((getFunc: PackedFunc, getGlobal: PackedFunc) => PackedFunc); + + /** + * Constructor + * + * importObject can also be a {@link LibraryProvider} object, + * a WASI object, or an object containing wasmLibraryProvider field. + * + * @param wasmModule The input module or instance. + * @param importObject The imports to initialize the wasmInstance if it is not provided. + * @param wasmInstance Additional wasm instance argument for deferred construction. + * @param env Directly specified environment module. + * + * @see Please use the async version {@link instantiate} when targeting browsers. + */ + constructor( + wasmModule: WebAssembly.Module, + importObject: Record = {}, + wasmInstance?: WebAssembly.Instance, + env?: Environment + ) { + if (wasmInstance instanceof WebAssembly.Instance) { + assert( + env instanceof Environment, + "env must be provided when passing in instance" + ); + } else { + assert(env === undefined); + env = new Environment(importObject); + wasmInstance = new WebAssembly.Instance(wasmModule, env.imports); + } + env.start(wasmInstance); + this.env = env; + this.lib = new FFILibrary(wasmInstance, env.imports); + this.memory = this.lib.memory; + this.exports = this.lib.exports; + this.objFactory = new Map(); + this.ctx = new RuntimeContext( + (name: string) => { + const autoAttachToScope = false; + // runtime context function do not auto-release. + return this.getGlobalFuncInternal(name, autoAttachToScope); + } + ); + this.registerEnvGlobalPackedFuncs(); + this.registerObjectFactoryFuncs(); + } + + /** + * Benchmark stable execution of the run function. + * + * @params run The run function + * @params dev The device to sync during each run. + * @number The number of times to compute the average. + * @repeat The number of times to repeat the run. + */ + async benchmark(run: () => void, dev: DLDevice, number = 10, repeat = 1): Promise { + // Skip first run as it can involve GPU warmup and module loading time. + const perf = compact.getPerformance(); + const results = []; + + // run with new scope + this.withNewScope(run); + await dev.sync(); + + for (let k = 0; k < repeat; ++k) { + const tstart = perf.now(); + for (let i = 0; i < number; ++i) { + this.withNewScope(run); + } + await dev.sync(); + const tend = perf.now(); + results.push((tend - tstart) / number); + } + return results; + } + + dispose(): void { + // order matters + // ctx release goes back into lib. + this.ctx.dispose(); + this.lib.dispose(); + } + /** + * Obtain the runtime information in readable format. + */ + runtimeStatsText(): string { + if (this.lib.webGPUContext !== undefined) { + return this.lib.webGPUContext.runtimeStatsText(); + } else { + return ""; + } + } + + /** + * Begin a new scope for tracking object disposal. + */ + beginScope(): void { + this.ctx.beginScope(); + } + + /** + * End a scope and release all created TVM objects + * under the current scope. + * + * Exception: one can call {@link moveToParentScope} to move + * a value to parent scope. + */ + endScope(): void { + this.ctx.endScope(); + } + + /** + * Perform action under a new scope. + * + * @param action The action function. + * @returns The result value. + * + * @note For action to return a valid value, + * we will need to call {@link moveToParentScope} + * for the objects that are created in the scope. + */ + withNewScope(action: () => T): T { + this.beginScope(); + const val = action(); + this.endScope(); + return val; + } + + /** + * Attach a detached obj to the auto-release pool of the current scope. + * + * @param obj The input obj. + * @note Normally user do not need to call this function explicitly, as + * all library call return values are explicitly attached to + * the current scope. You only need to do so when you call + * {@link detachFromCurrentScope} to create a detached object. + */ + attachToCurrentScope(obj: T): T { + return this.ctx.attachToCurrentScope(obj); + } + + /** + * Move obj's attachment to the parent scope. + * + * This function is useful to make sure objects are still + * alive when exit the current scope. + * + * @param obj The object to be moved. + * @returns The input obj. + */ + moveToParentScope(obj: T): T { + return this.ctx.moveToParentScope(obj); + } + + /** + * Detach the object from the current scope + * so it won't be released via auto-release during endscope. + * + * User needs to either explicitly call obj.dispose(), or + * {@link attachToCurrentScope} to re-attach to the current scope. + * + * This function can be used to return values to the parent scope. + * @param obj The object. + */ + detachFromCurrentScope(obj: T): T { + return this.ctx.detachFromCurrentScope(obj); + } + + /** + * Get system-wide library module in the wasm. + * System lib is a global module that contains self register functions in startup. + * @returns The system library module. + */ + systemLib(): Module { + return this.ctx.getSysLib() as Module; + } + /** + * List all the global function names registered in the runtime. + * @returns The name list. + */ + listGlobalFuncNames(): Array { + const stack = this.lib.getOrAllocCallStack(); + + const outSizeOffset = stack.allocPtrArray(2); + + const outSizePtr = stack.ptrFromOffset(outSizeOffset); + const outArrayPtr = stack.ptrFromOffset( + outSizeOffset + this.lib.sizeofPtr() + ); + + this.lib.checkCall( + (this.exports.TVMFuncListGlobalNames as ctypes.FTVMFuncListGlobalNames)( + outSizePtr, + outArrayPtr + ) + ); + + const size = this.memory.loadI32(outSizePtr); + const array = this.memory.loadPointer(outArrayPtr); + const names: Array = []; + + for (let i = 0; i < size; ++i) { + names.push( + this.memory.loadCString( + this.memory.loadPointer(array + this.lib.sizeofPtr() * i) + ) + ); + } + + this.lib.recycleCallStack(stack); + return names; + } + + /** + * Register function to be global function in tvm runtime. + * @param name The name of the function. + * @param f function to be registered. + * @param override Whether overwrite function in existing registry. + */ + registerFunc( + name: string, + func: PackedFunc | Function, + override = false + ): void { + this.withNewScope(() => { + const autoAttachToScope = true; + // packed func can be released once it is registered + const packedFunc = this.toPackedFuncInternal(func, autoAttachToScope); + const ioverride = override ? 1 : 0; + + const stack = this.lib.getOrAllocCallStack(); + const nameOffset = stack.allocRawBytes(name.length + 1); + stack.storeRawBytes(nameOffset, StringToUint8Array(name)); + stack.commitToWasmMemory(); + + this.lib.checkCall( + (this.lib.exports.TVMFuncRegisterGlobal as ctypes.FTVMFuncRegisterGlobal)( + stack.ptrFromOffset(nameOffset), + packedFunc._tvmPackedCell.getHandle(), + ioverride + ) + ); + this.lib.recycleCallStack(stack); + }); + } + + /** + * Get global PackedFunc from the runtime. + * @param name The name of the function. + * @param autoAttachToScope Whether to track it via autoDispose + * @returns The result function. + */ + getGlobalFunc(name: string): PackedFunc { + return this.getGlobalFuncInternal(name, true); + } + + private getGlobalFuncInternal(name: string, autoAttachToScope: boolean = true): PackedFunc { + const stack = this.lib.getOrAllocCallStack(); + const nameOffset = stack.allocRawBytes(name.length + 1); + stack.storeRawBytes(nameOffset, StringToUint8Array(name)); + const outOffset = stack.allocPtrArray(1); + const outPtr = stack.ptrFromOffset(outOffset); + + stack.commitToWasmMemory(outOffset); + + this.lib.checkCall( + (this.exports.TVMFuncGetGlobal as ctypes.FTVMFuncGetGlobal)( + stack.ptrFromOffset(nameOffset), + outPtr + ) + ); + const handle = this.memory.loadPointer(outPtr); + this.lib.recycleCallStack(stack); + if (handle == 0) { + throw Error("Cannot find global function " + name); + } + const ret = this.makePackedFunc(handle); + if (autoAttachToScope) this.ctx.attachToCurrentScope(ret); + return ret; + } + + /** + * Check if func is PackedFunc. + * + * @param func The input. + * @returns The check result. + */ + isPackedFunc(func: unknown): boolean { + // eslint-disable-next-line no-prototype-builtins + return typeof func == "function" && func.hasOwnProperty("_tvmPackedCell"); + } + + /** + * Convert func to PackedFunc + * + * @param func Input function. + * @returns The converted function. + */ + toPackedFunc(func: Function): PackedFunc { + return this.toPackedFuncInternal(func, true); + } + + private toPackedFuncInternal(func: Function, autoAttachToScope: boolean): PackedFunc { + if (this.isPackedFunc(func)) return func as PackedFunc; + const ret = this.createPackedFuncFromCFunc(this.wrapJSFuncAsPackedCFunc(func)); + if (autoAttachToScope) return this.ctx.attachToCurrentScope(ret); + return ret; + } + + /** + * Setup a virtual machine module with given device. + * + * @param dev DLDevice the device. + * @returns The created virtual machime. + */ + createVirtualMachine(dev: DLDevice): VirtualMachine { + const mod = this.ctx.detachFromCurrentScope( + this.systemLib().getFunction("vm_load_executable")() + ); + return this.ctx.attachToCurrentScope( + new VirtualMachine(mod, dev) + ); + } + + //----------------------------------------------- + // Native NDArray Cache Support + //----------------------------------------------- + /** + * Register a call back for fetch progress. + * + * @param cb the fetch progress callback. + */ + registerInitProgressCallback(cb: InitProgressCallback) { + this.initProgressCallback.push(cb); + } + + /** + * Get parameters in the form of prefix_i + * + * @param prefix The parameter prefix. + * @param numParams Number of parameters. + * @returns + */ + getParamsFromCache(prefix: string, numParams: number): TVMObject { + return (this.ctx.paramModuleFromCache( + prefix, new Scalar(numParams, "int32")) as Module).getFunction("get_params")(); + } + + /** + * Get NDArray from cache. + * @param name The name of array. + * @returns The result. + */ + ndarrayCacheGet(name: string): NDArray | undefined { + return this.ctx.arrayCacheGet(name); + } + + /** + * Get NDArray from cache. + * @param name The name of array. + * @returns The result. + */ + ndarrayCacheRemove(name: string): NDArray | undefined { + return this.ctx.arrayCacheRemove(name); + } + + /** + * Update the ndarray cache. + * @param name The name of the array. + * @param arr The content. + */ + ndarrayCacheUpdate(name: string, arr: NDArray, override: boolean = false) { + this.ctx.arrayCacheUpdate(name, arr, this.scalar(override ? 1 : 0, "int32")); + } + + /** + * Update the ndarray cache. + * @param name The name of the array. + * @param arr The content. + */ + ndarrayCacheClear() { + this.ctx.arrayCacheClear(); + } + + /** + * Fetch NDArray cache from url. + * + * @param ndarrayCacheUrl The cache url. + * @param device The device to be fetched to. + * @returns The meta data + */ + async fetchNDArrayCache(ndarrayCacheUrl: string, device: DLDevice): Promise { + const jsonUrl = new URL("ndarray-cache.json", ndarrayCacheUrl).href; + const request = new Request(jsonUrl); + const cache = await caches.open("tvmjs"); + let result = await cache.match(request); + if (result === undefined) { + await cache.add(request); + result = await cache.match(request); + } + if (result === undefined) { + this.env.logger("Error: Cannot cache " + jsonUrl + ", reloading will be slow"); + try { + result = await fetch(request); + } catch (err) { + this.env.logger("Cannot fetch " + jsonUrl); + } + } + let list; + if (result instanceof Response) { + list = await result.json(); + } + await this.fetchNDArrayCacheInternal( + ndarrayCacheUrl, + list["records"] as Array, device); + this.cacheMetadata = { ...this.cacheMetadata, ...(list["metadata"] as Record) }; + } + + /** + * Fetch list of NDArray into the NDArrayCache. + * + * @param ndarrayCacheUrl The cache url. + * @param list The list of array data. + * @param device The device to store the data to. + */ + private async fetchNDArrayCacheInternal(ndarrayCacheUrl: string, list: Array, device: DLDevice) { + const perf = compact.getPerformance(); + let tstart = perf.now(); + + let totalBytes = 0; + for (let i = 0; i < list.length; ++i) { + totalBytes += list[i].nbytes; + }; + let fetchedBytes = 0; + let timeElapsed = 0; + + const reportCallback = (iter: number) => { + // report + for (let j = 0; j < this.initProgressCallback.length; ++j) { + this.initProgressCallback[j]({ + type: 'init', + progress: fetchedBytes / totalBytes, + timeElapsed: timeElapsed, + currentChunk: iter, + totalChunks: list.length, + fetchedBytes, + totalBytes, + }); + } + }; + + for (let j = 0; j < this.initProgressCallback.length; ++j) { + this.initProgressCallback[j]({ + type: 'init', + progress: fetchedBytes / totalBytes, + timeElapsed: 0, + currentChunk: 0, + totalChunks: list.length, + fetchedBytes, + totalBytes, + }); + } + const cache = await caches.open("tvmjs"); + + for (let i = 0; i < list.length; ++i) { + reportCallback(i); + fetchedBytes += list[i].nbytes; + const dataUrl = new URL(list[i].dataPath, ndarrayCacheUrl).href; + const request = new Request(dataUrl); + let buffer; + try { + // use native cache + let result = await cache.match(request); + if (result === undefined) { + await cache.add(request); + result = await cache.match(request); + } + if (result == undefined) { + this.env.logger("Error: Cannot cache " + dataUrl + ", reloading will be slow"); + result = await fetch(request); + } + buffer = await result.arrayBuffer(); + } catch (err) { + this.env.logger("Error: Cannot fetch " + dataUrl + " err= " + err); + throw err; + } + const shardRecords = list[i].records; + for (let j = 0; j < shardRecords.length; ++j) { + const rec = shardRecords[j]; + const cpu_arr = this.withNewScope(() => { + return this.detachFromCurrentScope( + this.empty(rec.shape, rec.dtype, this.cpu()) + ) + }); + const recSource = buffer.slice(rec.byteOffset, rec.byteOffset + rec.nbytes); + // first sync copy to cpu. + this.ctx.arrayDecodeStorage(cpu_arr, new Uint8Array(recSource), rec.format); + // then async stream into GPU if needed + if (device.deviceType == DeviceStrToEnum.cpu) { + this.ndarrayCacheUpdate(rec.name, cpu_arr, false); + cpu_arr.dispose(); + } else { + // allocate a gpu arr and async copy to it. + const gpu_arr = this.withNewScope(() => { + return this.detachFromCurrentScope( + this.empty(rec.shape, rec.dtype, device) + ) + }); + gpu_arr.copyFrom(cpu_arr); + await device.sync(); + this.ndarrayCacheUpdate(rec.name, gpu_arr, false); + cpu_arr.dispose(); + gpu_arr.dispose(); + } + } + timeElapsed = Math.ceil((perf.now() - tstart) / 1000); + } + reportCallback(list.length); + } + + /** + * Convert dtype to {@link DLDataType} + * + * @param dtype The input dtype string or DLDataType. + * @returns The converted result. + */ + toDLDataType(dtype: string | DLDataType): DLDataType { + if (dtype instanceof DLDataType) return dtype; + if (typeof dtype == "string") { + let pattern = dtype; + let code, + bits = 32, + lanes = 1; + if (pattern.substring(0, 5) == "float") { + pattern = pattern.substring(5, pattern.length); + code = DLDataTypeCode.Float; + } else if (pattern.substring(0, 3) == "int") { + pattern = pattern.substring(3, pattern.length); + code = DLDataTypeCode.Int; + } else if (pattern.substring(0, 4) == "uint") { + pattern = pattern.substring(4, pattern.length); + code = DLDataTypeCode.UInt; + } else if (pattern.substring(0, 6) == "handle") { + pattern = pattern.substring(5, pattern.length); + code = DLDataTypeCode.OpaqueHandle; + bits = 64; + } else { + throw new Error("Unknown dtype " + dtype); + } + + const arr = pattern.split("x"); + if (arr.length >= 1) { + const parsed = parseInt(arr[0]); + if (parsed + "" == arr[0]) { + bits = parsed; + } + } + if (arr.length >= 2) { + lanes = parseInt(arr[1]); + } + return new DLDataType(code, bits, lanes); + } else { + throw new Error("Unknown dtype " + dtype); + } + } + + /** + * Create a new {@link Scalar} that can be passed to a PackedFunc. + * @param value The number value. + * @param dtype The dtype string. + * @returns The created scalar. + */ + scalar(value: number, dtype: string): Scalar { + return new Scalar(value, dtype); + } + + /** + * Create a new {@link DLDevice} + * @param deviceType The device type. + * @param deviceId The device index. + * @returns The created device. + */ + device(deviceType: number | string, deviceId = 0): DLDevice { + return new DLDevice(deviceType, deviceId, this.lib); + } + + /** + * Create a new cpu {@link DLDevice} + * @param deviceId The device index. + */ + cpu(deviceId = 0): DLDevice { + return this.device("cpu", deviceId); + } + + /** + * Create a new webgpu {@link DLDevice} + * @param deviceId The device index. + */ + webgpu(deviceId = 0): DLDevice { + return this.device("webgpu", deviceId); + } + + /** + * Create an empty {@link NDArray} with given shape and dtype. + * + * @param shape The shape of the array. + * @param dtype The data type of the array. + * @param dev The device of the ndarray. + * @returns The created ndarray. + */ + empty( + shape: Array | number, + dtype: string | DLDataType = "float32", + dev: DLDevice = this.device("cpu", 0) + ): NDArray { + dtype = this.toDLDataType(dtype); + shape = typeof shape == "number" ? [shape] : shape; + + const stack = this.lib.getOrAllocCallStack(); + const shapeOffset = stack.allocRawBytes(shape.length * SizeOf.I64); + for (let i = 0; i < shape.length; ++i) { + stack.storeI64(shapeOffset + i * SizeOf.I64, shape[i]); + } + + const outOffset = stack.allocPtrArray(1); + const outPtr = stack.ptrFromOffset(outOffset); + stack.commitToWasmMemory(outOffset); + + this.lib.checkCall( + (this.exports.TVMArrayAlloc as ctypes.FTVMArrayAlloc)( + stack.ptrFromOffset(shapeOffset), + shape.length, + dtype.code, + dtype.bits, + dtype.lanes, + dev.deviceType, + dev.deviceId, + outPtr + ) + ); + const ret = this.ctx.attachToCurrentScope( + new NDArray(this.memory.loadPointer(outPtr), false, this.lib, this.ctx) + ); + this.lib.recycleCallStack(stack); + return ret; + } + + /** + * Create am uniform {@link NDArray} with given shape. + * + * @param shape The shape of the array. + * @param low The low value. + * @param high The high value. + * @param dev The device of the ndarray. + * @returns The created ndarray. + */ + uniform( + shape: Array, + low: number, + high: number, + dev: DLDevice + ): NDArray { + const ret = this.empty(shape, "float32", dev); + const size = shape.reduce((a, b) => { + return a * b; + }, 1); + const scale = high - low; + const input = new Float32Array(size); + for (let i = 0; i < input.length; ++i) { + input[i] = low + Math.random() * scale; + } + return ret.copyFrom(input); + } + + /** + * Sample index via top-p sampling. + * + * @param logits The input logits before normalization. + * @param temperature The temperature factor, will take argmax if temperature = 0.0 + * @param top_p The top_p + * @returns The sampled index. + */ + sampleTopPFromLogits(logits: NDArray, temperature: number, top_p: number): number { + return this.ctx.sampleTopPFromLogits(logits, temperature, top_p, Math.random()); + } + + /** + * Bind canvas to the current WebGPU context + * @param canvas The canvas. + */ + bindCanvas(canvas: HTMLCanvasElement) { + this.lib.webGPUContext?.bindCanvas(canvas); + } + + /** + * Show image in canvas. + * + * @param dataRGBA Image array in height x width uint32 NDArray RGBA format on GPU. + */ + showImage(dataRGBA: NDArray) { + if (dataRGBA.shape.length != 2) { + throw Error("Require a height x width uint32 NDArray in RGBA" + + "get shape=" + dataRGBA.shape.toString() + " instead." + ); + } + if (dataRGBA.device.deviceType != DeviceStrToEnum.webgpu) { + throw new Error("Can only run showImage on WebGPU array, " + + "get " + DeviceEnumToStr[dataRGBA.device.deviceType] + " instead."); + } + if (dataRGBA.dtype != "uint32") { + throw Error("Require a height x width uint32 NDArray in RGBA, " + + "get " + dataRGBA.dtype + " instead."); + } + this.lib.webGPUContext?.drawImageFromBuffer( + dataRGBA.getDataPtr(), dataRGBA.shape[0], dataRGBA.shape[1] + ); + } + + /** + * Clear canvas + */ + clearCanvas() { + this.lib.webGPUContext?.clearCanvas(); + } + + /** + * Create an tuple {@link TVMArray} input array. + * + * The input array can be passed to tvm runtime function + * and needs to b explicitly disposed. + * + * @param inputs The input array + * @returns The result array. + */ + makeTVMArray( + inputs: Array + ): TVMArray { + return this.ctx.arrayMake(...inputs) as TVMArray; + } + + /** + * Create a shape tuple to pass to runtime. + * @param shape The shape . + * @returns The created shape tuple. + */ + makeShapeTuple(shape: Array): TVMObject { + const shapeArray = shape.map((value) => new Scalar(value, "int")); + return this.ctx.makeShapeTuple(...shapeArray); + } + /** + * Get type index from type key. + * @param typeKey The type key. + * @returns The corresponding type index. + */ + typeKey2Index( + typeKey: string + ): number { + const stack = this.lib.getOrAllocCallStack(); + const typeKeyOffset = stack.allocRawBytes(typeKey.length + 1); + stack.storeRawBytes(typeKeyOffset, StringToUint8Array(typeKey)); + const outOffset = stack.allocPtrArray(1); + const outPtr = stack.ptrFromOffset(outOffset); + + stack.commitToWasmMemory(outOffset); + + this.lib.checkCall( + (this.lib.exports.TVMObjectTypeKey2Index as ctypes.FTVMObjectTypeKey2Index)( + stack.ptrFromOffset(typeKeyOffset), + outPtr + ) + ); + const typeIndex = this.memory.loadU32(outPtr); + this.lib.recycleCallStack(stack); + return typeIndex; + } + + /** + * Register an object constructor. + * @param typeKey The name of the function. + * @param func function to be registered. + * @param override Whether overwrite function in existing registry. + */ + registerObjectConstructor( + typeKey: string, + func: FObjectConstructor, + override = false + ): void { + const typeIndex = this.typeKey2Index(typeKey); + if (this.objFactory.has(typeIndex)) { + if (!override) { + throw new Error("Type " + typeKey + " already registered"); + } + } + this.objFactory.set(typeIndex, func); + } + /** + * Register an asyncfunction to be global function in the server. + * @param name The name of the function. + * @param func function to be registered. + * @param override Whether overwrite function in existing registry. + * + * @note The async function will only be used for serving remote calls in the rpc. + */ + registerAsyncServerFunc( + name: string, + func: Function, + override = false + ): void { + const asyncVariant = (...args: Array): void => { + const fargs = args.slice(0, args.length - 1); + // need to keep it alive until callback is fulfilled. + const callback = this.detachFromCurrentScope(args[args.length - 1] as PackedFunc); + const promise: Promise = func(...fargs); + promise.then((rv: any) => { + callback(this.scalar(AyncCallbackCode.kReturn, "int32"), rv); + callback.dispose(); + }); + }; + this.registerFunc("__async." + name, asyncVariant, override); + } + + /** + * Asynchrously load webgpu pipelines when possible. + * @param mod The input module. + */ + async asyncLoadWebGPUPiplines(mod: Module): Promise { + if (this.lib.webGPUContext == undefined) throw Error("WebGPU not initialied"); + const webgpuContext = this.lib.webGPUContext; + + this.beginScope(); + const fmap_str = mod.getFunction("webgpu.get_fmap", true)() as string; + let fmap: Record = JSON.parse(fmap_str); + const totalFuncs = fmap.length; + const fGetShader = this.detachFromCurrentScope( + mod.getFunction("webgpu.get_shader") + ); + const fUpdatePrebuild = this.detachFromCurrentScope( + mod.getFunction("webgpu.update_prebuild") + ); + this.endScope(); + + const perf = compact.getPerformance(); + const tstart = perf.now(); + let tlastReport = tstart; + let finishCounter = 0; + const fmapEntries = Object.entries(fmap); + + let allEvents = Promise.resolve(); + + for (const [key, finfo] of fmapEntries) { + const code = fGetShader(key); + assert(key == finfo.name); + const event = webgpuContext.createShaderAsync(finfo, code).then((func: Function) => { + this.beginScope(); + fUpdatePrebuild(key, func); + this.endScope(); + + }).then(() => { + finishCounter += 1; + const tend = perf.now(); + const timeReportGap = 1000; + // skip report if gap is smaller than 1000 + if ((tend - tlastReport) < 1000 && finishCounter != fmapEntries.length) { + return; + } + tlastReport = tend; + const timeElapsed = Math.ceil((perf.now() - tstart) / 1000); + // report + for (let j = 0; j < this.initProgressCallback.length; ++j) { + const progress = finishCounter / fmapEntries.length; + let text = "Loading GPU shader modules[" + finishCounter + "/" + fmapEntries.length + "]: "; + text += Math.floor(progress * 100).toString() + "% completed, " + text += timeElapsed + " secs elapsed."; + // this.initProgressCallback[j]({ + // progress: progress, + // timeElapsed: timeElapsed, + // text: text + // }); + } + }); + allEvents = Promise.all([allEvents, event]).then(() => { }); + } + await allEvents; + assert(finishCounter == fmapEntries.length); + } + + /** + * Initialize webgpu in the runtime. + * @param device The given GPU device. + */ + initWebGPU(device: GPUDevice): void { + const webGPUContext = new WebGPUContext( + this.memory, device + ); + this.registerFunc("wasm.WebGPUDeviceAPI", (name: string) => { + return webGPUContext.getDeviceAPI(name); + }); + this.registerFunc("wasm.WebGPUCreateShader", (info: string, code: string) => { + const finfo = JSON.parse(info) as FunctionInfo; + return webGPUContext.createShader(finfo, code); + }); + this.registerAsyncServerFunc("wasm.WebGPUWaitForTasks", async () => { + await webGPUContext.sync(); + }); + this.lib.webGPUContext = webGPUContext; + } + + /** Register all object factory */ + private registerObjectFactoryFuncs(): void { + this.registerObjectConstructor("Array", + (handle: number, lib: FFILibrary, ctx: RuntimeContext) => { + return new TVMArray(handle, lib, ctx); + }); + } + + /** Register global packed functions needed by the backend to the env. */ + private registerEnvGlobalPackedFuncs(): void { + // Register the timer function to enable the time_evaluator. + const perf = compact.getPerformance(); + + // Helper function to time the finvoke + const timeExecution = async ( + finvoke: PackedFunc, + dev: DLDevice, + nstep: number, + repeat: number, + minRepeatMs: number, + limitZeroTimeIterations: number, + cooldownIntervalMs: number, + repeatsToCooldown: number + ): Promise => { + // detach and explicit dispose when tasks is fullfilled + // the promise will immediately return and we need to makesure + // finvoke do not get recycled. + this.ctx.detachFromCurrentScope(finvoke); + + finvoke(this.scalar(1, "int32")); + await dev.sync(); + const result = []; + let setupNumber: number = nstep; + + for (let i = 0; i < repeat; ++i) { + let durationMs = 0.0; + let absoluteZeroTimes = 0; + do { + if (durationMs > 0.0) { + let golden_ratio = 1.618; + setupNumber = Math.floor( + Math.max(minRepeatMs / (durationMs / setupNumber) + 1, setupNumber * golden_ratio) + ); + } + const tstart: number = perf.now(); + finvoke(this.scalar(setupNumber, "int32")); + await dev.sync(); + const tend: number = perf.now(); + + durationMs = tend - tstart; + if (durationMs == 0) { + absoluteZeroTimes++; + } + } while (durationMs < minRepeatMs && absoluteZeroTimes < limitZeroTimeIterations); + const speed = durationMs / setupNumber / 1000; + result.push(speed); + if (cooldownIntervalMs > 0.0 && (i % repeatsToCooldown) == 0) { + await new Promise(r => setTimeout(r, cooldownIntervalMs)); + } + } + const ret = new Float64Array(result.length); + ret.set(result); + + // dispose finvoke + finvoke.dispose(); + return new Uint8Array(ret.buffer); + }; + + const addOne = async (x: number): Promise => { + await new Promise(resolve => setTimeout(resolve, 100)); + return x + 1; + }; + + this.registerAsyncServerFunc("wasm.TimeExecution", timeExecution); + this.registerAsyncServerFunc("testing.asyncAddOne", addOne); + } + + private createPackedFuncFromCFunc( + func: ctypes.FTVMWasmPackedCFunc + ): PackedFunc { + let findex = this.env.packedCFuncTable.length; + if (this.env.packedCFuncTableFreeId.length != 0) { + findex = this.env.packedCFuncTableFreeId.pop() as number; + } else { + this.env.packedCFuncTable.push(undefined); + } + this.env.packedCFuncTable[findex] = func; + + const stack = this.lib.getOrAllocCallStack(); + const outOffset = stack.allocPtrArray(1); + const outPtr = stack.ptrFromOffset(outOffset); + this.lib.checkCall( + (this.exports + .TVMWasmFuncCreateFromCFunc as ctypes.FTVMWasmFuncCreateFromCFunc)( + findex, + outPtr + ) + ); + const ret = this.makePackedFunc(this.memory.loadPointer(outPtr)); + this.lib.recycleCallStack(stack); + return ret; + } + + /** + * Set packed function arguments into the location indicated by argsValue and argsCode. + * Allocate new temporary space from the stack if necessary. + * + * @parma stack The call stack + * @param args The input arguments. + * @param argsValue The offset of argsValue. + * @param argsCode The offset of argsCode. + */ + setPackedArguments( + stack: CachedCallStack, + args: Array, + argsValue: PtrOffset, + argsCode: PtrOffset + ): void { + for (let i = 0; i < args.length; ++i) { + let val = args[i]; + const tp = typeof val; + const valueOffset = argsValue + i * SizeOf.TVMValue; + const codeOffset = argsCode + i * SizeOf.I32; + if (val instanceof NDArray) { + if (!val.isView) { + stack.storePtr(valueOffset, val.getHandle()); + stack.storeI32(codeOffset, ArgTypeCode.TVMNDArrayHandle); + } else { + stack.storePtr(valueOffset, val.getHandle()); + stack.storeI32(codeOffset, ArgTypeCode.TVMDLTensorHandle); + } + } else if (val instanceof Scalar) { + if (val.dtype.startsWith("int") || val.dtype.startsWith("uint")) { + stack.storeI64(valueOffset, val.value); + stack.storeI32(codeOffset, ArgTypeCode.Int); + } else if (val.dtype.startsWith("float")) { + stack.storeF64(valueOffset, val.value); + stack.storeI32(codeOffset, ArgTypeCode.Float); + } else { + assert(val.dtype == "handle", "Expect handle"); + stack.storePtr(valueOffset, val.value); + stack.storeI32(codeOffset, ArgTypeCode.TVMOpaqueHandle); + } + } else if (val instanceof DLDevice) { + stack.storeI32(valueOffset, val.deviceType); + stack.storeI32(valueOffset + SizeOf.I32, val.deviceType); + stack.storeI32(codeOffset, ArgTypeCode.DLDevice); + } else if (tp == "number") { + stack.storeF64(valueOffset, val); + stack.storeI32(codeOffset, ArgTypeCode.Float); + // eslint-disable-next-line no-prototype-builtins + } else if (tp == "function" && val.hasOwnProperty("_tvmPackedCell")) { + stack.storePtr(valueOffset, val._tvmPackedCell.getHandle()); + stack.storeI32(codeOffset, ArgTypeCode.TVMPackedFuncHandle); + } else if (val === null || val == undefined) { + stack.storePtr(valueOffset, 0); + stack.storeI32(codeOffset, ArgTypeCode.Null); + } else if (tp == "string") { + stack.allocThenSetArgString(valueOffset, val); + stack.storeI32(codeOffset, ArgTypeCode.TVMStr); + } else if (val instanceof Uint8Array) { + stack.allocThenSetArgBytes(valueOffset, val); + stack.storeI32(codeOffset, ArgTypeCode.TVMBytes); + } else if (val instanceof Function) { + val = this.toPackedFuncInternal(val, false); + stack.tempArgs.push(val); + stack.storePtr(valueOffset, val._tvmPackedCell.getHandle()); + stack.storeI32(codeOffset, ArgTypeCode.TVMPackedFuncHandle); + } else if (val instanceof Module) { + stack.storePtr(valueOffset, val.getHandle()); + stack.storeI32(codeOffset, ArgTypeCode.TVMModuleHandle); + } else if (val instanceof TVMObject) { + stack.storePtr(valueOffset, val.getHandle()); + stack.storeI32(codeOffset, ArgTypeCode.TVMObjectHandle); + } else { + throw new Error("Unsupported argument type " + tp); + } + } + } + + private wrapJSFuncAsPackedCFunc(func: Function): ctypes.FTVMWasmPackedCFunc { + const lib = this.lib; + return ( + argValues: Pointer, + argCodes: Pointer, + nargs: number, + ret: Pointer, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _handle: Pointer + ): number => { + const jsArgs = []; + // use scope to track js values. + this.ctx.beginScope(); + for (let i = 0; i < nargs; ++i) { + const valuePtr = argValues + i * SizeOf.TVMValue; + const codePtr = argCodes + i * SizeOf.I32; + let tcode = lib.memory.loadI32(codePtr); + + if ( + tcode == ArgTypeCode.TVMObjectHandle || + tcode == ArgTypeCode.TVMObjectRValueRefArg || + tcode == ArgTypeCode.TVMPackedFuncHandle || + tcode == ArgTypeCode.TVMNDArrayHandle || + tcode == ArgTypeCode.TVMModuleHandle + ) { + lib.checkCall( + (lib.exports.TVMCbArgToReturn as ctypes.FTVMCbArgToReturn)( + valuePtr, + codePtr + ) + ); + } + tcode = lib.memory.loadI32(codePtr); + jsArgs.push(this.retValueToJS(valuePtr, tcode, true)); + } + + const rv = func(...jsArgs); + // recycle all js object value in function unless we want to retain them. + this.ctx.endScope(); + + if (rv !== undefined && rv !== null) { + const stack = lib.getOrAllocCallStack(); + const valueOffset = stack.allocRawBytes(SizeOf.TVMValue); + const codeOffset = stack.allocRawBytes(SizeOf.I32); + this.setPackedArguments(stack, [rv], valueOffset, codeOffset); + const valuePtr = stack.ptrFromOffset(valueOffset); + const codePtr = stack.ptrFromOffset(codeOffset); + stack.commitToWasmMemory(); + lib.checkCall( + (lib.exports.TVMCFuncSetReturn as ctypes.FTVMCFuncSetReturn)( + ret, + valuePtr, + codePtr, + 1 + ) + ); + lib.recycleCallStack(stack); + } + return 0; + }; + } + + private makePackedFunc(handle: Pointer): PackedFunc { + const cell = new PackedFuncCell(handle, this.lib); + + const packedFunc = (...args: any): any => { + const stack = this.lib.getOrAllocCallStack(); + + const valueOffset = stack.allocRawBytes(SizeOf.TVMValue * args.length); + const tcodeOffset = stack.allocRawBytes(SizeOf.I32 * args.length); + + this.setPackedArguments(stack, args, valueOffset, tcodeOffset); + + const rvalueOffset = stack.allocRawBytes(SizeOf.TVMValue); + const rcodeOffset = stack.allocRawBytes(SizeOf.I32); + const rvaluePtr = stack.ptrFromOffset(rvalueOffset); + const rcodePtr = stack.ptrFromOffset(rcodeOffset); + + // commit to wasm memory, till rvalueOffset (the return value don't need to be committed) + stack.commitToWasmMemory(rvalueOffset); + + this.lib.checkCall( + (this.exports.TVMFuncCall as ctypes.FTVMFuncCall)( + cell.getHandle(), + stack.ptrFromOffset(valueOffset), + stack.ptrFromOffset(tcodeOffset), + args.length, + rvaluePtr, + rcodePtr + ) + ); + + const ret = this.retValueToJS(rvaluePtr, this.memory.loadI32(rcodePtr), false); + this.lib.recycleCallStack(stack); + return ret; + }; + // Attach attributes to the function type. + // This is because javascript do not allow us to overload call. + const ret: any = packedFunc; + ret.dispose = (): void => { + cell.dispose(); + }; + ret._tvmPackedCell = cell; + return ret as PackedFunc; + } + + /** + * Creaye return value of the packed func. The value us auto-tracked for dispose. + * @param rvaluePtr The location of rvalue + * @param tcode The type code. + * @param callbackArg Whether it is being used in callbackArg. + * @returns The JS value. + */ + private retValueToJS(rvaluePtr: Pointer, tcode: number, callbackArg: boolean): any { + switch (tcode) { + case ArgTypeCode.Int: + case ArgTypeCode.UInt: + return this.memory.loadI64(rvaluePtr); + case ArgTypeCode.Float: + return this.memory.loadF64(rvaluePtr); + case ArgTypeCode.TVMOpaqueHandle: { + return this.memory.loadPointer(rvaluePtr); + } + case ArgTypeCode.TVMNDArrayHandle: { + return this.ctx.attachToCurrentScope( + new NDArray(this.memory.loadPointer(rvaluePtr), false, this.lib, this.ctx) + ); + } + case ArgTypeCode.TVMDLTensorHandle: { + assert(callbackArg); + // no need to attach as we are only looking at view + return new NDArray(this.memory.loadPointer(rvaluePtr), true, this.lib, this.ctx); + } + case ArgTypeCode.TVMPackedFuncHandle: { + return this.ctx.attachToCurrentScope( + this.makePackedFunc(this.memory.loadPointer(rvaluePtr)) + ); + } + case ArgTypeCode.TVMModuleHandle: { + return this.ctx.attachToCurrentScope( + new Module( + this.memory.loadPointer(rvaluePtr), + this.lib, + (ptr: Pointer) => { + return this.ctx.attachToCurrentScope(this.makePackedFunc(ptr)); + } + ) + ); + } + case ArgTypeCode.TVMObjectHandle: { + const obj = new TVMObject( + this.memory.loadPointer(rvaluePtr), + this.lib, + this.ctx + ); + const func = this.objFactory.get(obj.typeIndex()) + if (func != undefined) { + return this.ctx.attachToCurrentScope( + func(obj.getHandle(), this.lib, this.ctx) + ); + } else { + return this.ctx.attachToCurrentScope(obj); + } + } + case ArgTypeCode.Null: return undefined; + case ArgTypeCode.DLDevice: { + const deviceType = this.memory.loadI32(rvaluePtr); + const deviceId = this.memory.loadI32(rvaluePtr + SizeOf.I32); + return this.device(deviceType, deviceId); + } + case ArgTypeCode.TVMStr: { + const ret = this.memory.loadCString(this.memory.loadPointer(rvaluePtr)); + return ret; + } + case ArgTypeCode.TVMBytes: { + return this.memory.loadTVMBytes(this.memory.loadPointer(rvaluePtr)); + } + default: + throw new Error("Unsupported return type code=" + tcode); + } + } +} + +/** + * Asynchrously instantiate a new {@link Instance}. + * + * importObject can also be a {@link LibraryProvider} object, + * a WASI object, or an object containing wasmLibraryProvider field. + * We can take benefit of syslib implementations from the Emscripten + * by passing its generated js Module as the imports. + * + * @param bufferSource The source to be compiled. + * @param importObject The import objects. + * @param logger The system logger. + */ +export function instantiate( + bufferSource: ArrayBuffer, + importObject: Record = {}, + logger: (msg: string) => void = console.log +): Promise { + const env = new Environment(importObject, logger); + + return WebAssembly.instantiate(bufferSource, env.imports).then( + (result: WebAssembly.WebAssemblyInstantiatedSource): Instance => { + return new Instance(result.module, {}, result.instance, env); + } + ); +} diff --git a/packages/headless/src/worker/lib/tvm/support.d.ts b/packages/headless/src/worker/lib/tvm/support.d.ts new file mode 100644 index 0000000..d5d1b3e --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/support.d.ts @@ -0,0 +1,23 @@ +/** + * Convert string to Uint8array. + * @param str The string. + * @returns The corresponding Uint8Array. + */ +export declare function StringToUint8Array(str: string): Uint8Array; +/** + * Convert Uint8array to string. + * @param array The array. + * @returns The corresponding string. + */ +export declare function Uint8ArrayToString(arr: Uint8Array): string; +/** + * Internal assert helper + * @param condition condition The condition to fail. + * @param msg msg The message. + */ +export declare function assert(condition: boolean, msg?: string): asserts condition; +/** + * Get the path to the wasm library in nodejs. + * @return The wasm path. + */ +export declare function wasmPath(): string; diff --git a/packages/headless/src/worker/lib/tvm/support.ts b/packages/headless/src/worker/lib/tvm/support.ts new file mode 100644 index 0000000..7e2b6e6 --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/support.ts @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Convert string to Uint8array. + * @param str The string. + * @returns The corresponding Uint8Array. + */ +export function StringToUint8Array(str: string): Uint8Array { + const arr = new Uint8Array(str.length + 1); + for (let i = 0; i < str.length; ++i) { + arr[i] = str.charCodeAt(i); + } + arr[str.length] = 0; + return arr; +} + +/** + * Convert Uint8array to string. + * @param array The array. + * @returns The corresponding string. + */ +export function Uint8ArrayToString(arr: Uint8Array): string { + const ret = []; + for (const ch of Array.from(arr)) { + ret.push(String.fromCharCode(ch)); + } + return ret.join(""); +} + +/** + * Internal assert helper + * @param condition condition The condition to fail. + * @param msg msg The message. + */ +export function assert(condition: boolean, msg?: string): asserts condition { + if (!condition) { + throw new Error("AssertError:" + (msg || "")); + } +} + +/** + * Get the path to the wasm library in nodejs. + * @return The wasm path. + */ +export function wasmPath(): string { + return "." + // return __dirname + "/wasm"; +} diff --git a/packages/headless/src/worker/lib/tvm/types.d.ts b/packages/headless/src/worker/lib/tvm/types.d.ts new file mode 100644 index 0000000..c8986c5 --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/types.d.ts @@ -0,0 +1,33 @@ +/** Common type definitions. */ +/** + * Library interface provider that can provide + * syslibs(e.g. libs provided by WASI and beyond) for the Wasm runtime. + * + * It can be viewed as a generalization of imports used in WebAssembly instance creation. + * + * The {@link LibraryProvider.start} callback will be called + * to allow the library provider to initialize related resources during startup time. + * + * We can use Emscripten generated js Module as a { wasmLibraryProvider: LibraryProvider }. + */ +export interface LibraryProvider { + /** The imports that can be passed to WebAssembly instance creation. */ + imports: Record; + /** + * Callback function to notify the provider the created instance. + * @param inst The created instance. + */ + start: (inst: WebAssembly.Instance) => void; +} +/** + * Disposable classes that contains resources (WasmMemory, GPU buffer) + * which needs to be explicitly disposed. + */ +export interface Disposable { + /** + * Dispose the internal resource + * This function can be called multiple times, + * only the first call will take effect. + */ + dispose: () => void; +} diff --git a/packages/headless/src/worker/lib/tvm/types.ts b/packages/headless/src/worker/lib/tvm/types.ts new file mode 100644 index 0000000..621375a --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/types.ts @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/** Common type definitions. */ + +/** + * Library interface provider that can provide + * syslibs(e.g. libs provided by WASI and beyond) for the Wasm runtime. + * + * It can be viewed as a generalization of imports used in WebAssembly instance creation. + * + * The {@link LibraryProvider.start} callback will be called + * to allow the library provider to initialize related resources during startup time. + * + * We can use Emscripten generated js Module as a { wasmLibraryProvider: LibraryProvider }. + */ +export interface LibraryProvider { + /** The imports that can be passed to WebAssembly instance creation. */ + imports: Record; + /** + * Callback function to notify the provider the created instance. + * @param inst The created instance. + */ + start: (inst: WebAssembly.Instance) => void; +} + +/** + * Disposable classes that contains resources (WasmMemory, GPU buffer) + * which needs to be explicitly disposed. + */ +export interface Disposable { + /** + * Dispose the internal resource + * This function can be called multiple times, + * only the first call will take effect. + */ + dispose: () => void; +} diff --git a/packages/headless/src/worker/lib/tvm/webgpu.d.ts b/packages/headless/src/worker/lib/tvm/webgpu.d.ts new file mode 100644 index 0000000..1a53f4e --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/webgpu.d.ts @@ -0,0 +1,124 @@ +/// +import { Memory } from "./memory"; +/** A pointer to points to the raw address space. */ +export type GPUPointer = number; +export interface GPUDeviceDetectOutput { + adapter: GPUAdapter; + adapterInfo: GPUAdapterInfo; + device: GPUDevice; +} +/** + * DetectGPU device in the environment. + */ +export declare function detectGPUDevice(): Promise; +/** + * Function info from the API + */ +export interface FunctionInfo { + name: string; + arg_types: Array; + launch_param_tags: Array; +} +/** + * WebGPU context + * Manages all the webgpu resources here. + */ +export declare class WebGPUContext { + device: GPUDevice; + memory: Memory; + private bufferTable; + private bufferTableFreeId; + private podArgStagingBuffers; + private canvasRenderManager?; + private maxNumPodArgsStagingBuffers; + private peakAllocatedBytes; + private currAllocatedBytes; + private allAllocatedBytes; + private shaderSubmitCounter; + protected debugShaderSubmitLimit: number; + protected debugLogFinish: boolean; + constructor(memory: Memory, device: GPUDevice); + /** + * Dispose context. + */ + dispose(): void; + /** + * Wait for all pending GPU tasks to complete + */ + sync(): Promise; + /** + * Obtain the runtime information in readable format. + */ + runtimeStatsText(): string; + /** + * Draw image from data in storage buffer. + * @param ptr The GPU ptr + * @param height The height of the image. + * @param width The width of the image. + */ + drawImageFromBuffer(ptr: GPUPointer, height: number, width: number): void; + /** + * Copy raw bytes into buffer ptr. + * + * @param rawBytes The raw bytes + * @param toPtr The target gpu buffer ptr + * @param toOffset The beginning offset + * @param nbytes Number of bytes + */ + copyRawBytesToBuffer(rawBytes: Uint8Array, toPtr: GPUPointer, toOffset: number, nbytes: number): void; + /** + * Clear canvas + */ + clearCanvas(): void; + /** + * Bind a canvas element to the runtime. + * @param canvas The HTML canvas/ + */ + bindCanvas(canvas: HTMLCanvasElement): void; + /** + * Create a PackedFunc that runs the given shader + * via createComputePipeline + * + * @param info The function information already parsed as a record. + * @param code The shader data(in WGSL) + * @returns The shader + */ + createShader(finfo: FunctionInfo, code: string): Function; + /** + * Create a PackedFunc that runs the given shader asynchrously + * via createComputePipelineAsync + * + * @param info The function information already parsed as a record. + * @param code The shader data(in WGSL) + * @returns The shader + */ + createShaderAsync(finfo: FunctionInfo, code: string): Promise; + /** + * Get the pod arg staging buffer + * \param nbytes The minimum size. + * \return The allocated buffer + */ + private getPodArgsBuffer; + /** + * Internal impl of createShader for both async and sync mode. + * + * @param info The function information already parsed as a record. + * @param code The shader data(in WGSL) + * @param asyncMode Whether use async mode. + * @returns The shader function or promise of shader func. + */ + private createShadeInternal; + /** + * Get the device API according to its name + * @param The name of the API. + * @returns The corresponding device api. + */ + getDeviceAPI(name: string): Function; + private deviceAllocDataSpace; + private deviceFreeDataSpace; + private deviceCopyToGPU; + private deviceCopyFromGPU; + private deviceCopyWithinGPU; + private gpuBufferFromPtr; + private attachToBufferTable; +} diff --git a/packages/headless/src/worker/lib/tvm/webgpu.ts b/packages/headless/src/worker/lib/tvm/webgpu.ts new file mode 100644 index 0000000..16b269f --- /dev/null +++ b/packages/headless/src/worker/lib/tvm/webgpu.ts @@ -0,0 +1,862 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { Pointer } from "./ctypes"; +import { Memory } from "./memory"; +import { assert } from "./support"; +import { Disposable } from "./types"; + + + +/** A pointer to points to the raw address space. */ +export type GPUPointer = number; + +export interface GPUDeviceDetectOutput { + adapter: GPUAdapter; + adapterInfo: GPUAdapterInfo; + device: GPUDevice; +} + +/** + * DetectGPU device in the environment. + */ +export async function detectGPUDevice(): Promise { + if (typeof navigator !== "undefined" && navigator.gpu !== undefined) { + const adapter = await navigator.gpu.requestAdapter({ "powerPreference": "high-performance" }); + if (adapter == null) { + throw Error("Cannot find adapter that matches the request"); + } + const computeMB = (value: number) => { + return Math.ceil(value / (1 << 20)) + "MB"; + } + + // more detailed error message + const requiedMaxBufferSize = 1 << 30; + if (requiedMaxBufferSize > adapter.limits.maxBufferSize) { + throw Error( + `Cannot initialize runtime because of requested maxBufferSize ` + + `exceeds limit. requested=${computeMB(requiedMaxBufferSize)}, ` + + `limit=${computeMB(adapter.limits.maxBufferSize)}. ` + + `This error may be caused by an older version of the browser (e.g. Chrome 112). ` + + `You can try to upgrade your browser to Chrome 113 or later.` + ); + } + + const requiredMaxStorageBufferBindingSize = 1 << 30; + if (requiredMaxStorageBufferBindingSize > adapter.limits.maxStorageBufferBindingSize) { + throw Error( + `Cannot initialize runtime because of requested maxStorageBufferBindingSize ` + + `exceeds limit. requested=${computeMB(requiredMaxStorageBufferBindingSize)}, ` + + `limit=${computeMB(adapter.limits.maxStorageBufferBindingSize)}. ` + ); + } + + const requiredMaxComputeWorkgroupStorageSize = 32 << 10; + if (requiredMaxComputeWorkgroupStorageSize > adapter.limits.maxComputeWorkgroupStorageSize) { + throw Error( + `Cannot initialize runtime because of requested maxComputeWorkgroupStorageSize ` + + `exceeds limit. requested=${requiredMaxComputeWorkgroupStorageSize}, ` + + `limit=${adapter.limits.maxComputeWorkgroupStorageSize}. ` + ); + } + + const adapterInfo = await adapter.requestAdapterInfo(); + const device = await adapter.requestDevice({ + requiredLimits: { + maxBufferSize: requiedMaxBufferSize, + maxStorageBufferBindingSize: requiredMaxStorageBufferBindingSize, + maxComputeWorkgroupStorageSize: requiredMaxComputeWorkgroupStorageSize, + } + }); + return { + adapter: adapter, + adapterInfo: adapterInfo, + device: device + }; + } else { + return undefined; + } +} + +const canvasRenderWGSL = ` +@group(0) @binding(0) var my_sampler : sampler; +@group(0) @binding(1) var my_texture : texture_2d; + +struct VertexOutput { + @builtin(position) position : vec4, + @location(0) uv : vec2, +} + +@vertex +fn vertex_main(@builtin(vertex_index) vidx : u32) -> VertexOutput { + const pos = array( + vec2( 1.0, 1.0), + vec2( 1.0, -1.0), + vec2(-1.0, -1.0), + vec2( 1.0, 1.0), + vec2(-1.0, -1.0), + vec2(-1.0, 1.0), + ); + + const uv = array( + vec2(1.0, 0.0), + vec2(1.0, 1.0), + vec2(0.0, 1.0), + vec2(1.0, 0.0), + vec2(0.0, 1.0), + vec2(0.0, 0.0), + ); + + var output : VertexOutput; + output.position = vec4(pos[vidx], 0.0, 1.0); + output.uv = uv[vidx]; + return output; +} + +@fragment +fn fragment_main(@location(0) uv : vec2) -> @location(0) vec4 { + return textureSample(my_texture, my_sampler, uv); +} + +@fragment +fn fragment_clear(@location(0) uv : vec2) -> @location(0) vec4 { + return vec4(1.0, 1.0, 1.0, 1.0); +} +` +class CanvaRenderManager implements Disposable { + private device: GPUDevice; + private canvasContext: GPUCanvasContext; + private stagingTexture: GPUTexture; + private renderSampler: GPUSampler; + private renderPipeline: GPURenderPipeline; + private clearPipeline: GPURenderPipeline; + private canvasTextureFormat: GPUTextureFormat; + + constructor(device: GPUDevice, canvas: HTMLCanvasElement) { + this.device = device; + const ctx = canvas.getContext("webgpu"); + if (ctx == null) { + throw Error("Cannot bind WebGPU context"); + } + // @ts-ignore + this.canvasContext = ctx; + this.canvasTextureFormat = navigator.gpu.getPreferredCanvasFormat(); + this.canvasContext.configure({ + device: this.device, + format: this.canvasTextureFormat, + alphaMode: "opaque", + }); + + this.renderPipeline = device.createRenderPipeline({ + layout: "auto", + vertex: { + module: device.createShaderModule({ + code: canvasRenderWGSL, + }), + entryPoint: "vertex_main", + }, + fragment: { + module: device.createShaderModule({ + code: canvasRenderWGSL, + }), + entryPoint: "fragment_main", + targets: [{ + format: this.canvasTextureFormat, + }], + }, + primitive: { + topology: "triangle-list", + }, + }); + + this.clearPipeline = device.createRenderPipeline({ + layout: "auto", + vertex: { + module: device.createShaderModule({ + code: canvasRenderWGSL, + }), + entryPoint: "vertex_main", + }, + fragment: { + module: device.createShaderModule({ + code: canvasRenderWGSL, + }), + entryPoint: "fragment_clear", + targets: [{ + format: this.canvasTextureFormat, + }], + }, + primitive: { + topology: "triangle-list", + }, + }); + + this.renderSampler = device.createSampler({ + magFilter: "linear", + minFilter: "linear", + }); + // staging texture always be in RGBA + this.stagingTexture = device.createTexture({ + size: [canvas.height, canvas.width, 1], + format: "rgba8unorm", + usage: + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, + }); + } + + clear() { + const commandEncoder = this.device.createCommandEncoder(); + const passEncoder = commandEncoder.beginRenderPass({ + //@ts-ignore + colorAttachments: [ + { + view: this.canvasContext.getCurrentTexture().createView(), + clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, + loadOp: "clear", + storeOp: "store", + }, + ], + }); + passEncoder.setPipeline(this.clearPipeline); + const renderBindingGroup = this.device.createBindGroup({ + layout: this.renderPipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: this.renderSampler }, + { binding: 1, resource: this.stagingTexture.createView() }, + ], + }); + passEncoder.setBindGroup(0, renderBindingGroup); + passEncoder.draw(6, 1, 0, 0); + passEncoder.end(); + this.device.queue.submit([commandEncoder.finish()]); + } + + draw(buffer: GPUBuffer, height: number, width: number) { + // resize the staging texture + if (height != this.stagingTexture.height || width != this.stagingTexture.width) { + this.stagingTexture.destroy(); + this.stagingTexture = this.device.createTexture({ + size: [height, width, 1], + format: "rgba8unorm", + usage: + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, + }); + } + + const commandEncoder = this.device.createCommandEncoder(); + commandEncoder.copyBufferToTexture({ + buffer: buffer, + offset: 0, + bytesPerRow: this.stagingTexture.width * 4 + }, { + texture: this.stagingTexture + }, { + width: this.stagingTexture.width, + height: this.stagingTexture.height + }); + + const passEncoder = commandEncoder.beginRenderPass({ + //@ts-ignore + colorAttachments: [ + { + view: this.canvasContext.getCurrentTexture().createView(), + clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, + loadOp: "clear", + storeOp: "store", + }, + ], + }); + passEncoder.setPipeline(this.renderPipeline); + const renderBindingGroup = this.device.createBindGroup({ + layout: this.renderPipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: this.renderSampler }, + { binding: 1, resource: this.stagingTexture.createView() }, + ], + }); + passEncoder.setBindGroup(0, renderBindingGroup); + passEncoder.draw(6, 1, 0, 0); + passEncoder.end(); + this.device.queue.submit([commandEncoder.finish()]); + } + + dispose(): void { + this.stagingTexture.destroy(); + } +} + +/** + * Function info from the API + */ +export interface FunctionInfo { + name: string; + arg_types: Array; + launch_param_tags: Array; +} + +/** + * WebGPU context + * Manages all the webgpu resources here. + */ +export class WebGPUContext { + device: GPUDevice; + memory: Memory; + // internal data + private bufferTable: Array = [undefined]; + private bufferTableFreeId: Array = []; + private podArgStagingBuffers: Array = []; + private canvasRenderManager?: CanvaRenderManager = undefined; + // number of pod arg staging buffers + private maxNumPodArgsStagingBuffers: number = 2; + // flags for debugging + // stats of the runtime. + // peak allocation + private peakAllocatedBytes: number = 0; + // current allocation + private currAllocatedBytes: number = 0; + // all allocation(ignoring free) + private allAllocatedBytes: number = 0; + // shader submit counter + private shaderSubmitCounter: number = 0; + // limite number of shaders to be submitted, useful for debugging, default to -1 + protected debugShaderSubmitLimit: number = -1; + // log and sync each step + protected debugLogFinish: boolean = false; + + constructor(memory: Memory, device: GPUDevice) { + this.memory = memory; + this.device = device; + } + + /** + * Dispose context. + */ + dispose() { + this.canvasRenderManager?.dispose(); + this.bufferTableFreeId = []; + while (this.bufferTable.length != 0) { + this.bufferTable.pop()?.destroy(); + } + while (this.podArgStagingBuffers.length != 0) { + this.podArgStagingBuffers.pop()?.destroy(); + } + this.device.destroy(); + } + + /** + * Wait for all pending GPU tasks to complete + */ + async sync(): Promise { + await this.device.queue.onSubmittedWorkDone(); + } + + /** + * Obtain the runtime information in readable format. + */ + runtimeStatsText(): string { + let info = "peak-memory=" + Math.ceil(this.peakAllocatedBytes / (1 << 20)) + " MB"; + info += ", all-memory=" + Math.ceil(this.allAllocatedBytes / (1 << 20)) + " MB"; + info += ", shader-submissions=" + this.shaderSubmitCounter; + return info; + } + + /** + * Draw image from data in storage buffer. + * @param ptr The GPU ptr + * @param height The height of the image. + * @param width The width of the image. + */ + drawImageFromBuffer(ptr: GPUPointer, height: number, width: number) { + if (this.canvasRenderManager == undefined) { + throw Error("Do not have a canvas context, call bindCanvas first"); + } + this.canvasRenderManager.draw(this.gpuBufferFromPtr(ptr), height, width); + } + + /** + * Copy raw bytes into buffer ptr. + * + * @param rawBytes The raw bytes + * @param toPtr The target gpu buffer ptr + * @param toOffset The beginning offset + * @param nbytes Number of bytes + */ + copyRawBytesToBuffer( + rawBytes: Uint8Array, + toPtr: GPUPointer, + toOffset: number, + nbytes: number + ): void { + // Perhaps it would be more useful to use a staging buffer? + this.device.queue.writeBuffer( + this.gpuBufferFromPtr(toPtr), + toOffset, + rawBytes, + 0, + nbytes + ); + } + /** + * Clear canvas + */ + clearCanvas() { + this.canvasRenderManager?.clear(); + } + + /** + * Bind a canvas element to the runtime. + * @param canvas The HTML canvas/ + */ + bindCanvas(canvas: HTMLCanvasElement) { + this.canvasRenderManager = new CanvaRenderManager(this.device, canvas); + } + + /** + * Create a PackedFunc that runs the given shader + * via createComputePipeline + * + * @param info The function information already parsed as a record. + * @param code The shader data(in WGSL) + * @returns The shader + */ + createShader(finfo: FunctionInfo, code: string): Function { + return this.createShadeInternal(finfo, code, false) as Function; + } + + /** + * Create a PackedFunc that runs the given shader asynchrously + * via createComputePipelineAsync + * + * @param info The function information already parsed as a record. + * @param code The shader data(in WGSL) + * @returns The shader + */ + async createShaderAsync(finfo: FunctionInfo, code: string): Promise { + return await (this.createShadeInternal(finfo, code, true) as Promise); + } + + /** + * Get the pod arg staging buffer + * \param nbytes The minimum size. + * \return The allocated buffer + */ + private getPodArgsBuffer(nbytes: number): GPUBuffer { + let buffer: GPUBuffer | undefined = undefined; + if (this.podArgStagingBuffers.length >= this.maxNumPodArgsStagingBuffers) { + buffer = this.podArgStagingBuffers.shift(); + } + // minimum of 16 bytes + let allocSize = 16; + if (buffer !== undefined) { + allocSize = buffer.size; + if (buffer.size < nbytes) { + buffer.destroy(); + buffer = undefined; + } + } + while (allocSize < nbytes) { + allocSize *= 2; + } + + if (buffer == undefined) { + // create uniform buffer + buffer = this.device.createBuffer({ + size: allocSize, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + } + assert(nbytes <= buffer.size); + return buffer; + } + + /** + * Internal impl of createShader for both async and sync mode. + * + * @param info The function information already parsed as a record. + * @param code The shader data(in WGSL) + * @param asyncMode Whether use async mode. + * @returns The shader function or promise of shader func. + */ + private createShadeInternal( + finfo: FunctionInfo, + code: string, + asyncMode: boolean + ): Function | Promise { + const dispatchToDim: Array = []; + let paramWriteAccess: Array = []; + + for (let i = 0; i < finfo.launch_param_tags.length; ++i) { + const tag: string = finfo.launch_param_tags[i]; + if (tag.startsWith("blockIdx.")) { + const target: number = tag.charCodeAt(tag.length - 1) - ("x".charCodeAt(0)); + assert(target >= 0 && target < 3); + dispatchToDim.push(target); + } else if (tag.startsWith("threadIdx.")) { + const target: number = tag.charCodeAt(tag.length - 1) - ("x".charCodeAt(0)); + assert(target >= 0 && target < 3); + dispatchToDim.push(target + 3); + } else if (tag.startsWith("paramWriteAccess:")) { + paramWriteAccess = JSON.parse(tag.substring(17)); + } else { + throw new Error("Cannot handle thread_axis " + tag); + } + } + + + const layoutEntries: Array = []; + const bufferArgIndices: Array = []; + const podArgIndices: Array = []; + + for (let i = 0; i < finfo.arg_types.length; ++i) { + const dtype = finfo.arg_types[i]; + if (dtype == "handle") { + layoutEntries.push({ + binding: bufferArgIndices.length, + visibility: GPUShaderStage.COMPUTE, + buffer: { + type: paramWriteAccess[bufferArgIndices.length] ? "storage" : "read-only-storage" + } + }); + bufferArgIndices.push(i); + } else if (dtype.startsWith("int") || dtype.startsWith("uint") || dtype.startsWith("float")) { + podArgIndices.push(i); + } else { + throw new Error("Cannot handle argument type " + dtype + " in WebGPU shader"); + } + } + + assert(paramWriteAccess.length == bufferArgIndices.length); + // POD arguments are pass in the end + layoutEntries.push({ + binding: bufferArgIndices.length, + visibility: GPUShaderStage.COMPUTE, + buffer: { + type: "uniform" + } + }); + + const bindGroupLayout = this.device.createBindGroupLayout({ + entries: layoutEntries + }); + const pipelineLayout = this.device.createPipelineLayout({ + bindGroupLayouts: [bindGroupLayout] + }); + + // Function to create the pipeline. + const createShaderFunc = (pipeline: GPUComputePipeline): Function => { + const submitShader = (...args: Array): void => { + if (this.debugShaderSubmitLimit != -1 && + this.shaderSubmitCounter >= this.debugShaderSubmitLimit) { + this.shaderSubmitCounter += 1; + return; + } + + const commandEncoder = this.device.createCommandEncoder(); + const compute = commandEncoder.beginComputePass(); + compute.setPipeline(pipeline); + const bindGroupEntries: Array = []; + const numBufferOrPodArgs = bufferArgIndices.length + podArgIndices.length; + + assert(args.length == numBufferOrPodArgs + dispatchToDim.length); + + const workDim: Array = [1, 1, 1, 1, 1, 1]; + for (let i = 0; i < dispatchToDim.length; ++i) { + workDim[dispatchToDim[i]] = args[numBufferOrPodArgs + i]; + } + + // get around 65535 restriction of blockIdx.x + if (workDim[2] != 1) { + throw Error("WebGPU: blockIdx.z is reserved for internal use"); + } + const packDimX = workDim[0]; + // spread thinsg out into blockIdx.z + if (workDim[0] >= (1 << 16)) { + let wl_x = workDim[0]; + let wl_z = workDim[2]; + + while (wl_x >= (1 << 16)) { + if (wl_x % 2 == 0) { + wl_x = wl_x / 2; + } else { + // pad up + wl_x = (wl_x + 1) / 2; + } + wl_z *= 2; + } + workDim[0] = wl_x; + workDim[2] = wl_z; + assert(wl_x * wl_z >= packDimX); + } + + for (let i = 0; i < bufferArgIndices.length; ++i) { + bindGroupEntries.push({ + binding: i, + resource: { + buffer: this.gpuBufferFromPtr(args[bufferArgIndices[i]]) + } + }); + } + + // push pod buffer + const sizeOfI32 = 4; + const podArgBuffer = this.getPodArgsBuffer((podArgIndices.length + 1) * sizeOfI32); + const i32View = new Int32Array(podArgIndices.length + 1); + const u32View = new Uint32Array(i32View.buffer); + const f32View = new Float32Array(i32View.buffer); + + for (let i = 0; i < podArgIndices.length; ++i) { + const value = args[podArgIndices[i]]; + const dtype = finfo.arg_types[podArgIndices[i]]; + if (dtype.startsWith("int")) { + i32View[i] = value; + } else if (dtype.startsWith("uint")) { + u32View[i] = value; + } else if (dtype.startsWith("float")) { + f32View[i] = value; + } else { + throw Error("Unknown pod dtype " + dtype); + } + } + // always pass in dim z launching grid size in + u32View[podArgIndices.length] = packDimX; + this.device.queue.writeBuffer(podArgBuffer, 0, i32View.buffer); + + bindGroupEntries.push({ + binding: bufferArgIndices.length, + resource: { + buffer: podArgBuffer, + size: i32View.buffer.byteLength + } + }); + + compute.setBindGroup(0, this.device.createBindGroup({ + layout: bindGroupLayout, + entries: bindGroupEntries + })); + + compute.dispatchWorkgroups(workDim[0], workDim[1], workDim[2]) + compute.end() + const command = commandEncoder.finish(); + this.device.queue.submit([command]); + + if (this.debugLogFinish) { + const currCounter = this.shaderSubmitCounter; + this.device.queue.onSubmittedWorkDone().then(() => { + // console.log("[" + currCounter + "][Debug] finish shader" + finfo.name); + }); + } + this.shaderSubmitCounter += 1; + }; + return submitShader; + }; + + const shaderModule = this.device.createShaderModule({ + code: code, + hints: { + main: { + layout: pipelineLayout + } + } + }); + + if (asyncMode) { + return this.device.createComputePipelineAsync({ + layout: pipelineLayout, + compute: { + module: shaderModule, + entryPoint: finfo.name + } + }).then((pipeline: GPUComputePipeline) => { + return createShaderFunc(pipeline); + }); + } else { + const pipeline = this.device.createComputePipeline({ + layout: pipelineLayout, + compute: { + module: shaderModule, + entryPoint: finfo.name + } + }); + return createShaderFunc(pipeline); + } + } + + /** + * Get the device API according to its name + * @param The name of the API. + * @returns The corresponding device api. + */ + getDeviceAPI(name: string): Function { + if (name == "deviceAllocDataSpace") { + return (nbytes: number): GPUPointer => { + return this.deviceAllocDataSpace(nbytes); + }; + } else if (name == "deviceFreeDataSpace") { + return (ptr: GPUPointer): void => { + return this.deviceFreeDataSpace(ptr); + }; + } else if (name == "deviceCopyToGPU") { + return ( + from: Pointer, + to: GPUPointer, + toOffset: number, + nbytes: number + ): void => { + this.deviceCopyToGPU(from, to, toOffset, nbytes); + }; + } else if (name == "deviceCopyFromGPU") { + return ( + from: GPUPointer, + fromOffset: number, + to: Pointer, + nbytes: number + ): void => { + this.deviceCopyFromGPU(from, fromOffset, to, nbytes); + }; + } else if (name == "deviceCopyWithinGPU") { + return ( + from: GPUPointer, + fromOffset: number, + to: Pointer, + toOffset: number, + nbytes: number + ): void => { + this.deviceCopyWithinGPU(from, fromOffset, to, toOffset, nbytes); + }; + } else { + throw new Error("Unknown DeviceAPI function " + name); + } + } + + // DeviceAPI + private deviceAllocDataSpace(nbytes: number): GPUPointer { + // allocate 0 bytes buffer as 1 bytes buffer. + if (nbytes == 0) { + nbytes = 1; + } + const buffer = this.device.createBuffer({ + size: nbytes, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, + }); + this.currAllocatedBytes += nbytes; + this.allAllocatedBytes += nbytes; + if (this.currAllocatedBytes > this.peakAllocatedBytes) { + this.peakAllocatedBytes = this.currAllocatedBytes; + } + const ptr = this.attachToBufferTable(buffer); + return ptr; + } + + private deviceFreeDataSpace(ptr: GPUPointer): void { + const idx = ptr; + const buffer = this.bufferTable[idx]; + this.bufferTable[idx] = undefined; + assert(buffer !== undefined); + this.bufferTableFreeId.push(idx); + this.currAllocatedBytes -= buffer.size; + buffer.destroy(); + } + + private deviceCopyToGPU( + from: Pointer, + to: GPUPointer, + toOffset: number, + nbytes: number + ): void { + // Perhaps it would be more useful to use a staging buffer? + const rawBytes = this.memory.loadRawBytes(from, nbytes); + this.device.queue.writeBuffer( + this.gpuBufferFromPtr(to), + toOffset, + rawBytes, + 0, + nbytes + ); + } + + private deviceCopyFromGPU( + from: GPUPointer, + fromOffset: number, + to: Pointer, + nbytes: number + ): void { + // Perhaps it would be more useful to resuse a staging buffer? + const gpuTemp = this.device.createBuffer({ + size: nbytes, + usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, + }); + + const copyEncoder = this.device.createCommandEncoder(); + copyEncoder.copyBufferToBuffer( + this.gpuBufferFromPtr(from), + fromOffset, + gpuTemp, + 0, + nbytes + ); + const copyCommands = copyEncoder.finish(); + this.device.queue.submit([copyCommands]); + + gpuTemp.mapAsync(GPUMapMode.READ).then(() => { + const data = gpuTemp.getMappedRange(); + this.memory.storeRawBytes(to, new Uint8Array(data)); + gpuTemp.destroy(); + }); + } + + private deviceCopyWithinGPU( + from: GPUPointer, + fromOffset: number, + to: Pointer, + toOffset: number, + nbytes: number + ): void { + const copyEncoder = this.device.createCommandEncoder(); + copyEncoder.copyBufferToBuffer( + this.gpuBufferFromPtr(from), + fromOffset, + this.gpuBufferFromPtr(to), + toOffset, + nbytes + ); + const copyCommands = copyEncoder.finish(); + this.device.queue.submit([copyCommands]); + } + + private gpuBufferFromPtr(ptr: GPUPointer): GPUBuffer { + const buffer = this.bufferTable[ptr]; + assert(buffer !== undefined); + return buffer; + } + + private attachToBufferTable(buffer: GPUBuffer): GPUPointer { + if (this.bufferTableFreeId.length != 0) { + const idx = this.bufferTableFreeId.pop() as number; + this.bufferTable[idx] = buffer; + return idx; + } else { + const idx = this.bufferTable.length; + this.bufferTable.push(buffer); + return idx; + } + } +} diff --git a/packages/headless/src/worker/llm.ts b/packages/headless/src/worker/llm.ts new file mode 100644 index 0000000..180e640 --- /dev/null +++ b/packages/headless/src/worker/llm.ts @@ -0,0 +1,337 @@ +import { v4 as uuidv4 } from "uuid"; +import { Conversation } from "../types/chat"; +import { GenerateTextCallback, GenerateTextRequest } from "../types/worker_message"; +import { detectGPUDevice, instantiate } from "./lib/tvm"; +import { InitProgressCallback } from "./lib/tvm/runtime"; +import { Config } from "./worker"; + +export class LLMInstance { + config: Config; + tvm: any; + tokenizer: any; + model: any; + spp: any; + processing: boolean; + + constructor(config: Config, sentencePieceProcessor: any) { + this.config = config; + this.tvm = undefined; + this.tokenizer = undefined; + this.model = undefined; + this.spp = sentencePieceProcessor; + this.processing = false; + } + + isInitialized() { + return this.model != undefined; + } + + async init(cb: InitProgressCallback) { + if (this.model) { + return; + } + const wasmSource = await (await fetch(this.config.wasmUrl)).arrayBuffer(); + this.tvm = await instantiate( + new Uint8Array(wasmSource), + //@ts-ignore + new EmccWASI(), + console.log, + ); + try { + const output = await detectGPUDevice(); + if (output !== undefined) { + this.tvm.initWebGPU(output.device); + } else { + throw Error("This browser env do not support WebGPU"); + } + } catch (err: any) { + throw Error("Find an error initializing WebGPU: " + err.toString()); + } + this.tvm.registerInitProgressCallback(cb); + await this.tvm.fetchNDArrayCache(this.config.cacheUrl, this.tvm.webgpu()); + + this.tokenizer = await this.spp()(this.config.tokenizerUrl); + this.model = this.tvm.withNewScope(() => { + return new LLMInstanceScope( + this.tvm, + this.tokenizer, + this.config.maxWindowSize + ); + }); + return this.model.init(); + } + + async generate(request: GenerateTextRequest, cb: GenerateTextCallback) { + if (this.processing) { + return; + } + this.processing = true; + await this.model.generate(request, cb); + this.processing = false; + } +} + +export class LLMInstanceScope { + tvm: any; + tokenizer: any; + maxWindowSize: number; + device: any; + vm: any; + encoding: any; + decoding: any; + params: any; + bosTokenId: number; + eosTokenId: number; + fclearKVCaches: any; + kvCache: any; + fcreateCache: any; + logitsOnCPU: any; + kvCacheLength: number; + lastMessageId: string; + + constructor(tvm: any, tokenizer: any, maxWindowSize = 2048) { + this.tvm = tvm; + this.tokenizer = tokenizer; + + this.bosTokenId = 1; + this.eosTokenId = 2; + + this.maxWindowSize = maxWindowSize; + + this.device = this.tvm.webgpu(); + + this.vm = this.tvm.detachFromCurrentScope( + this.tvm.createVirtualMachine(this.device) + ); + this.encoding = this.tvm.detachFromCurrentScope( + this.vm.getFunction("encoding") + ); + this.decoding = this.tvm.detachFromCurrentScope( + this.vm.getFunction("decoding") + ); + this.params = this.tvm.detachFromCurrentScope( + this.tvm.getParamsFromCache("param", this.tvm.cacheMetadata.ParamSize) + ); + const fcreateCache = this.vm.getFunction("create_kv_cache"); + this.fclearKVCaches = this.tvm.detachFromCurrentScope( + this.tvm.getGlobalFunc("vm.builtin.attention_kv_cache_array_clear") + ); + + // use extern config for now + this.kvCache = this.tvm.detachFromCurrentScope(fcreateCache()); + // fill with pad token + this.logitsOnCPU = undefined; + + this.kvCacheLength = 0; + this.lastMessageId = ""; + } + + async init() { + await this.tvm.asyncLoadWebGPUPiplines(this.vm.getInternalModule()); + } + + async getTokensFromStart(conversation: Conversation, maxTokens: number) { + this.clearKVCache(); + const tokens = []; + + for (let i = conversation.messages.length - 1; i >= 0; i--) { + const message = conversation.messages[i]; + const text = `${message.role}: ${message.text}\n`; + const messageTokens = await this.tokenizer.encodeIds(text); + if ( + tokens.length + messageTokens.length + maxTokens > + this.maxWindowSize + ) { + break; + } + tokens.unshift(...(await this.tokenizer.encodeIds(text))); + } + tokens.unshift( + ...(await this.tokenizer.encodeIds(conversation.systemPrompt)) + ); + tokens.unshift(this.bosTokenId); + + return tokens; + } + + async getTokens(conversation: Conversation, maxTokens: number) { + // Case 1. Attention Cache is empty, start from beginning + // Case 2. Attention Cache is not empty, but the last message we processed is not in the cache, start from beginning + // Case 3. Attention Cache is not empty, and the last message we processed is in the cache, start from the next message + // Case 4. Attention Cache is not empty, and the last message we processed is in the cache, but the cache is too long, start from beginning + if (this.kvCacheLength == 0) { + // Case 1 + return await this.getTokensFromStart(conversation, maxTokens); + } + + // Calculate the index of the last message we processed + let startMsgIdx = 0; + for (let i = conversation.messages.length - 1; i >= 0; i--) { + if (conversation.messages[i].id == this.lastMessageId) { + startMsgIdx = i + 1; + break; + } + } + + if (startMsgIdx == 0) { + // Case 2 + return await this.getTokensFromStart(conversation, maxTokens); + } + + const tokens = [this.eosTokenId]; + for (let i = startMsgIdx; i < conversation.messages.length; i++) { + const message = conversation.messages[i]; + const text = `${message.role}: ${message.text}`; + const messageTokens = await this.tokenizer.encodeIds(text); + if ( + tokens.length + messageTokens.length + maxTokens > + this.maxWindowSize + ) { + // Case 4 + return await this.getTokensFromStart(conversation, maxTokens); + } + tokens.push(...(await this.tokenizer.encodeIds(text))); + } + + // Case 3 + return tokens; + } + + async generate(request: GenerateTextRequest, cb: GenerateTextCallback) { + const { conversation, maxTokens, assistantRoleName, stopTexts } = request; + const tokens = await this.getTokens(conversation, maxTokens); + tokens.push(...(await this.tokenizer.encodeIds(`${assistantRoleName}:`))); + console.log("debug: ", await this.tokenizer.decodeIds(tokens)); + + const inputTokenLength = tokens.length; + let outputText = ""; + let tstart = 0, + tend = 0, step = 0; + + const id = uuidv4(); + for (; step < maxTokens; step++) { + this.tvm.beginScope(); + tstart = performance.now(); + var input; + if (step == 0) { + input = this.tvm.empty([1, tokens.length], "int32", this.device); + input.copyFrom(tokens); + } else { + input = this.tvm.empty([1, 1], "int32", this.device); + input.copyFrom(tokens.slice(tokens.length - 1)); + } + const logits = this.tvm.detachFromCurrentScope( + this.forward(input, this.kvCacheLength + inputTokenLength + step) + ); + this.tvm.endScope(); + const nextToken = await this.sampleTokenFromLogits(logits); + logits.dispose(); + + tokens.push(nextToken); + const outputTokens = tokens.slice(inputTokenLength); + outputText = this.tokenizer.decodeIds(outputTokens); + tend = performance.now(); + if (nextToken == this.eosTokenId) break; + const stopPos = outputText.lastIndexOf(""); + if (stopPos != -1) { + outputText = outputText.substring(0, stopPos); + break; + } + let stop = false; + for (let i = 0; i < stopTexts.length; i++) { + if (outputText.endsWith(stopTexts[i])) { + outputText = outputText.substring( + 0, + outputText.length - stopTexts[i].length + ); + stop = true; + break; + } + } + if (stop) break; + if (step != 0) { + cb({ + requestId: id, + step: step, + outputText, + stats: { + totalDecodingSeconds: (tend - tstart) / 1000, + totalDecodedTokens: tokens.length - inputTokenLength, + totalEncodedTokens: inputTokenLength, + }, + isFinished: false, + }); + } + } + this.kvCacheLength += tokens.length - 1; + this.lastMessageId = id; + + cb({ + requestId: id, + outputText, + step: step, + stats: { + totalDecodingSeconds: (tend - tstart) / 1000, + totalDecodedTokens: tokens.length - inputTokenLength, + totalEncodedTokens: inputTokenLength, + }, + isFinished: true, + }); + } + + dispose() { + // note: tvm instance is not owned by this class + this.params.dispose(); + this.decoding.dispose(); + this.encoding.dispose(); + this.vm.dispose(); + this.kvCache.dispose(); + this.fclearKVCaches.dispose(); + if (this.logitsOnCPU != undefined) { + this.logitsOnCPU.dispose(); + } + } + + clearKVCache() { + this.fclearKVCaches(this.kvCache); + this.kvCacheLength = 0; + this.lastMessageId = ""; + } + + forward(inputs: any, curPos: number) { + this.tvm.beginScope(); + var retValue; + const seqLenShape = this.tvm.makeShapeTuple([curPos]); + if (inputs.shape[1] > 1) { + retValue = this.encoding(inputs, seqLenShape, this.kvCache, this.params); + } else { + retValue = this.decoding(inputs, seqLenShape, this.kvCache, this.params); + } + const logits = this.tvm.detachFromCurrentScope(retValue.get(0)); + this.tvm.endScope(); + this.tvm.attachToCurrentScope(logits); + return logits; + } + + // NOTE: caller must call device.sync() + updateLogitsOnCPU(logits: any) { + if (this.logitsOnCPU == undefined) { + this.logitsOnCPU = this.tvm.detachFromCurrentScope( + this.tvm.empty(logits.shape, logits.dtype, this.tvm.cpu()) + ); + } else { + if (logits.shape[0] != this.logitsOnCPU.shape[0]) { + throw Error("We expect the size of logits to remain unchanged"); + } + } + this.logitsOnCPU.copyFrom(logits); + } + + async sampleTokenFromLogits(logits: any, temperature = 0.8, top_p = 0.95) { + this.tvm.beginScope(); + this.updateLogitsOnCPU(logits); + this.tvm.endScope(); + await this.device.sync(); + return this.tvm.sampleTopPFromLogits(this.logitsOnCPU, temperature, top_p); + } +} diff --git a/packages/headless/src/worker/worker.ts b/packages/headless/src/worker/worker.ts new file mode 100644 index 0000000..769ae3b --- /dev/null +++ b/packages/headless/src/worker/worker.ts @@ -0,0 +1,54 @@ +import * as Comlink from "comlink"; +import { GenerateTextCallback, GenerateTextRequest, ModelWorker } from "../types/worker_message"; +import { InitProgressCallback } from '../worker/lib/tvm/runtime'; +import { LLMInstance } from '../worker/llm'; + +declare global { + var importScripts: (...url: string[]) => void; + var sentencepiece: { + sentencePieceProcessor: (url: string) => void; + }; +} + +const config = { + kvConfig: { + numLayers: 64, + shape: [32, 32, 128], + dtype: 'float32', + }, + wasmUrl: 'https://huggingface.co/mrick/react-llm/resolve/main/models/vicuna-7b-v1/vicuna-7b-v1_webgpu.wasm', + cacheUrl: 'https://huggingface.co/mrick/react-llm/resolve/main/models/vicuna-7b-v1/params/', + tokenizerUrl: 'https://huggingface.co/mrick/react-llm/resolve/main/models/vicuna-7b-v1/tokenizer.model', + sentencePieceJsUrl: 'https://cdn.matt-rickard.com/code/sentencepiece.js', + tvmRuntimeJsUrl: 'https://cdn.matt-rickard.com/code/tvmjs_runtime.wasi.js', + maxWindowSize: 2048, +} as Config; + +export type Config = { + kvConfig: { + numLayers: number; + shape: number[]; + dtype: string; + }; + wasmUrl: string; + cacheUrl: string; + tokenizerUrl: string; + sentencePieceJsUrl: string; + tvmRuntimeJsUrl: string; + maxWindowSize: number; +} +const instance = new LLMInstance(config, () => globalThis.sentencepiece.sentencePieceProcessor); +const worker = { + init(callback: Comlink.ProxyOrClone) { + instance.init(callback); + }, + generate(request: GenerateTextRequest, cb: Comlink.ProxyOrClone) { + instance.generate(request, cb); + } +} as ModelWorker; + +importScripts(...[ + config.sentencePieceJsUrl, config.tvmRuntimeJsUrl +]); + +Comlink.expose(worker); diff --git a/packages/headless/tsconfig.json b/packages/headless/tsconfig.json new file mode 100644 index 0000000..3aeccbc --- /dev/null +++ b/packages/headless/tsconfig.json @@ -0,0 +1,42 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "noEmit": false, + "composite": true, + "allowJs": true, + "skipLibCheck": true, + "declaration": true, + "declarationDir": "./dist/types", + "strict": true, + "forceConsistentCasingInFileNames": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react", + "types": [ + "@webgpu/types" + ], + "typeRoots": [ + "./node_modules/@types" + ], + "paths": { + "@/*": [ + "./src/*" + ], + }, + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/packages/retro-ui/.eslintrc.json b/packages/retro-ui/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/packages/retro-ui/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/packages/retro-ui/.gitignore b/packages/retro-ui/.gitignore new file mode 100644 index 0000000..8f322f0 --- /dev/null +++ b/packages/retro-ui/.gitignore @@ -0,0 +1,35 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/packages/retro-ui/LICENSE b/packages/retro-ui/LICENSE new file mode 100644 index 0000000..052f695 --- /dev/null +++ b/packages/retro-ui/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Matt Rickard + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/retro-ui/README.md b/packages/retro-ui/README.md new file mode 100644 index 0000000..f4da3c4 --- /dev/null +++ b/packages/retro-ui/README.md @@ -0,0 +1,34 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/packages/retro-ui/next.config.js b/packages/retro-ui/next.config.js new file mode 100644 index 0000000..d95f1f8 --- /dev/null +++ b/packages/retro-ui/next.config.js @@ -0,0 +1,28 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + transpilePackages: ["react95"], + webpack(config, options) { + const { isServer } = options; + config.module.rules.push({ + test: /\.(ogg|mp3|wav|mpe?g)$/i, + exclude: config.exclude, + use: [ + { + loader: require.resolve("url-loader"), + options: { + limit: config.inlineImageLimit, + fallback: require.resolve("file-loader"), + publicPath: `${config.assetPrefix}/_next/static/images/`, + outputPath: `${isServer ? "../" : ""}static/images/`, + name: "[name]-[hash].[ext]", + esModule: config.esModule || false, + }, + }, + ], + }); + + return config; + }, +}; + +module.exports = nextConfig; diff --git a/packages/retro-ui/package.json b/packages/retro-ui/package.json new file mode 100644 index 0000000..db3c8cb --- /dev/null +++ b/packages/retro-ui/package.json @@ -0,0 +1,39 @@ +{ + "name": "retro-ui", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "keywords": [ + "chatgpt", + "llm", + "headless", + "react" + ], + "dependencies": { + "@react-llm/headless": "workspace:*", + "@types/node": "20.1.3", + "@types/react": "18.2.6", + "@types/react-dom": "18.2.4", + "eslint": "8.40.0", + "eslint-config-next": "13.4.2", + "next": "13.4.2", + "react": "18.2.0", + "react-dom": "18.2.0", + "react95": "^4.0.0", + "typescript": "5.0.4", + "use-sound": "^4.0.1" + }, + "devDependencies": { + "@types/styled-components": "^5.1.26", + "autoprefixer": "10.4.14", + "file-loader": "^6.2.0", + "postcss": "^8.4.23", + "tailwindcss": "^3.3.2", + "url-loader": "^4.1.1" + } +} diff --git a/packages/retro-ui/postcss.config.js b/packages/retro-ui/postcss.config.js new file mode 100644 index 0000000..4df9d3b --- /dev/null +++ b/packages/retro-ui/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + autoprefixer: {}, + tailwindcss: {}, + }, +}; diff --git a/packages/retro-ui/public/buddy88.gif b/packages/retro-ui/public/buddy88.gif new file mode 100644 index 0000000000000000000000000000000000000000..fed6cd60313ee8efa286ca7a028555847f0bbf30 GIT binary patch literal 7012 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 literal 0 HcmV?d00001 diff --git a/packages/retro-ui/public/favicon.ico b/packages/retro-ui/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7df2ee9f627d3df4be72872b3eae1fd8dc2ac431 GIT binary patch literal 1415 zcmaJ>eM}p57`_e0fQ1S3gD`^Lj%JRp-d$VzaZs?mw$N>52t^nQ>(L&xz}?lm!xr3( zHaJC%A@UUzQG;K-}1-oiGp~4zCQDf6V=o`+dB5p7(v; zk6cY=Mp{Vl?qC1_LX7FUEa43GeSrbO{@DB~Asi9BK9A34ig_2ol7Pm>6p^5jB1*_C zlCV`iJ4z-4fM2x3oX6*x4q{e@5)(d**iAVFHUK22x}AizjO4*0vcy5FA#dLR1UhVL zC^ylBn4CCiccfRbWOh}C*;-X*RoS4_6foJ12@WWdCqOqO zFH=KzMCF+>L7ZVpP%cIitOz0j6)G_*m&xQvJSagV2#h4cs3ZZEV~7kx72x`UglMd- z7|YTb)?*1THDu>`CkDeVmrLxDiW#;9MpY^mj7VUKBtbwVaFsMqxD#kDdc%W`H!{V0y!APcp-e;^-FP=o0hYXwEpyiuoy1dG__uwha}Cqoq)l>%4FP*jhr zl(+#$m8e3hOq5Ge!v@z#b38#?$qlaKE?0S9E{3xt!85FxVahkUmuY8shO;wHP=bmj z;J#c3ZDU+qjIXxqQc2eF9BDJK3T7^um zL$$aPTIbsCk@z}S_P<kbmRInakTgxOMTsq1LL8 z(=$At_QKf3r-l3Z*q zI{T_NFn_9g(p7Qrk>PjrBl(RD8mw)FBC1d?Stl?_@{|UeJNVha%9N4 zjDCyN2mLbU-*miI-f)yU*#Wc;%xb5WZ!S!E;yOy_?BPA}nxdgYKhP6@EomN3aZTJj zGv`c%+QsY@~B$F1lx&s05*n;^7!nz*v{Thw> K4BaPMi|1dHq!JPU literal 0 HcmV?d00001 diff --git a/packages/retro-ui/public/sounds/imrcv.wav b/packages/retro-ui/public/sounds/imrcv.wav new file mode 100644 index 0000000000000000000000000000000000000000..20cf23dd509868a8512da3819e848dffb23c2a35 GIT binary patch literal 18348 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 P+Kb3(7pnyQhpqns(y6j3 literal 0 HcmV?d00001 diff --git a/packages/retro-ui/public/sounds/imsend.wav b/packages/retro-ui/public/sounds/imsend.wav new file mode 100644 index 0000000000000000000000000000000000000000..a24b5e37aa131e50b16ce63b2d1abcfcbde18db6 GIT binary patch literal 17772 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 Bk3s+d literal 0 HcmV?d00001 diff --git a/packages/retro-ui/public/xp.jpeg b/packages/retro-ui/public/xp.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..3d4049e6b86ba3fdfb1c4dc718a600c2d30c893c GIT binary patch literal 278797 zcmb5Vc{J4j|2A$*2zeJ}mm&;?WF1Q)#K_i+twAbF$U0*gON*okWt;5FSZ5epVyq#_ zZfuimiNPRZ#x~LWe)YNU@A-c3`~3d-z2+Qq<~8Tc@O(U<*W-Fz*X7@@|9)rUGcz_d zW@2V$Vqyj!O#eU?+!cmH$!*E>wiEUbUu z`S%+WH!Cv}3m+>V@Yb};^_|BRM=P@XjJ{uIr$gJaDYJXaZ`a+2YKEq32pUB+r^HCK zz0>SAI^s>Fp=DlaZx-FhN#-dfQocJpq9to}=XlQMUQ^(L?mJp5y;2nY#Aym&x-#8g zYA3d*N#9x96-_)MUUiqS<8(z2_L|weR0r2Id=ui9cJ}U8egqcoP*Y_%uy4HXFL*)D z$*5TU;aa_8?p@VBsF7p|pWV)lUS@>v_J_o_H4T@5&b;}mI1Ekps7O9VJFdFAzH>?o zL)S#PbVVsWYI(6eA7XOeBrAN`omNqwZO*_rSQkgu<%rbL`?#FH%UDfv=S1Yx4O-f1 zMBFu9q;KE*+_ZG{;V>C=G3%2Wtm*Wn%O5m2Y2+KfBy^FdM6|Z7DeszMw1aHThjSoh zOW`Vs;3sDEb@r3A5g#-8n$p2X6vDDYQu^HMu@7fgbYDnVXzINh928d!&|dV`G}>5P zf4c%rvWQmMtBX|f7-F1`ULQAbzGj}RkbLH*sif5N|9-kI8&`KKSpi)kcNzVEzw`h3 z$}07G?1?a?``Q7AeplD${MIA3a*>Bm_jJUI8p(_FQjPl=)WuR4`R4ve-#x|0r>Vhy zNn_jS_yKgWp9G|vZh2|s1+&WEqdIui)VT4~^Ot-Q z_g;kxYqFF;B{w?<{F+N+#<%xp@dFy?=e&OXiC#g6+kObsRWVhQdJ>>rFosF)rE1PY zdsE9>tI5vo8s-gkY)wr%7Nv@w(h|Z`r-V-^`(AexXlxo?OcvvgH2cQ|@lM0EYZ)Dp zn-~RyG#($RC^<4#1RgollC{D!usvb7pCwpy{y|U9Ff{4mLDa>O>_PF!%0UmYW#gkS zuNi?Mogh1(*=1WA_N-QG&4ix-BsWEZWmkK4OKfyu?5oet*^brSKle4xwrgff?jE6r z#;fd@wgR*x7=EjF+q2f`b6Fq5EmPL$CzC9{>}IFvO1()oFy)l`pHCc8a%q>vj(*4U zEZ(U!?|~-Y>ixf}{ogf>akAkTa_fuv%I`iVjY#$~gvK-7!5B zvA=qXcPnq>NuU?ONaE6le3eo>d0fwraqZ2ks%!XX=N!=<7xqpHuk4YlhNsg8=@C&a zztYZ^K8I)(md5JxzIE4V$QXCw%`S{y`w?O%_ffV<3$*d&WJ(xaQGH&+2a4WL&=nUS zy0Z__F+G~MYiv7d*gGEy4q7ES`dv$V!lWJRln%e5HPzd>sl*BZl&Wvtjf!>dG+hJ3pO| zRQ8o?*WgZ9`gn7Y!w_$aoe!L+rv^R1IXT&i&b~PxXs9rB+S}!Y<>2c59=;x#_TjQ~ zl|8PW$j@d{3@v58oQ0J^@Vm8A#YI2EtG@qKVl>au(OPO>D<;k)oek;h%fzO>}EEEJIM-ZrpRit?)i){6ZTttal0|6QpOT* z3$H^IyRa{EZ@NQLMLdg5A2infKd0MN(uS}MC&2@M6csfKs>=mA-s8M;mi?7L^Yz^T>Mcb3`O@~fSED=oUy_U++ODRMmm*zw zh*zt0@)v#%hE6v3hLNv_x%7Iys;>_abrlCa%MKq6I zh)Z7Nr-g|gtNGB$h)8qBJLKi&2mIe=tRVIO46nOWRg{dQrZ_`d%Fza?N_ds3P_IC8 zz1Xj}{(cubil-XVN@FeB+U^|XKrS={kds;DLJ3>itf)O zxgK9Xs&n3ZBuezC$!92^`qm-O*+7^3-4yarBZuK)x)5?S?=e~*`X{M-|M!cqsNun% zUt2Uacjvpi`xxR;FE)9TjCIBH#IP?~>yCQ7yQUu%9#DG?8q$}Yg1g@T@M(_b)m(ky zFzOAqs7Ws-ZT!%_{b4gINxeSZgf{J?qzx%o3s#m@mKQ8$|3NV#!d>B3Hu-QP1$<## z#WO1|NF!(9a)Du5?3c~o656Ayi(WMPY@eQzd2kM;mZ8P4c&x1y6)B#VO!)enH!1UX z`|xp8%*C+qHuxX{DD5-YQzboO@~`#vp2&(u#@*yMnNqa?X#Q;(w!F`Yhw~a#(fKM` zi!s@yF41GnL$T}}Xa5rVUnY%B+TCUezbuOiF>$f1tcIk)={J>MTO)WgMuv!}uZ#Bn z4i)40bo&Icif76x#-?w}YR~!hz9}9Lr7zxai`9tu_-#x-Y~qTCRLKBUvXDOXxlUoo zbNNHe!B_hk@z*N}D5qG<6hf8QOxcMnnYCJln`tSV~6UVXc!H8L-MkzDfU?7^OQ)18)MXWgifp)dQ}2>sHfTCxZ_| z{TW2`*stS=xuGO!g0AS!(Heca7VV;OA_zjgGLLdlGYzL;K4&sCcV)Y$#Xafi6O4hoKeOQs+=gK#PL=tGBgUF-r5x->GAH?Rq6X zrYn^hs=kCvD3A5rDtYqZzf3Z|u!8+V-p}&)iYlHOLUl!yL@}PrMC8CmT2+52`FiLu zM4q#3v2S43Hu>|rqn@ln%$3q+@oX>2FD2UQPoL*^k7^CN099b{%(-}El@@rM^2>@e z%`@RKdob9m0O0D$NHumk++((d6-bXSAIkC8d{#0udi96r^Sdr<5=i86_f zk_e*wrtR;d^jxZvKJ95XuKg=tX zRuXscH@#Vi;1?gIB(G=V0=3zKMHN4_niHX`yqCMLNxy*hNOr;cFC)IWn^1k=fL|RDldgp z+K--@fdqXC2=2*cJzYiHFmvEGwjJT~Zsd0BE5Bd<-~<~~s3e)&=j>1sWRTT-X4K&{ zWCAOmduM#3a$(}-{n}4=D!QGR5X__LLBnn!)fHdg1P76xlH+VFZL6Vlz-N&#v0$PY zeCOjt*7%U47J_sAmL_@3V%c??yEOlT5gDxWJ7w~o*v<7sTfI#e3(W#j|Ju@fs%?om zLz$uFrEs1|dMy2p>=62%HtFjZ5E(#886Vu*3GA|XY?=EU?=Sme8AnM&Df(8NPhjXZ zyl7X#QpNsdO5FOz&|B%;R-;9Nw<8lx<_@RUEFY(v)qX8!|6Z|Gj~`y$*^0FI@esf$>dLv-4lJJMe4XY7&7dbQxeIe=g6Wp zT+E*mc=KA4KUc0|9z`c6-Np|5R?6C##V2l^ zEQOx^QimCsirzQ~L4~3Zw`wpdhdvC^B4iFB>0~|SXDgbuhx}?eQt9M+7(}c})1fWp zbhBT~FHPX{}Pym*7BX33@D5E5w68sLE_u zHDZ6dW5^C6OhJbt-h!M)W>Rcm#+iN)<#MZH>+aW@wA3ccEBDdsa>jAj)IL*L>m9EF^OCaSI>DBU&AuY=x_hN&z4{iD5Wls>U{tr48> zk{rm`46N|CO>eEuU&wU_+ClhHdkBW3T2~J>lQ|$#oNNG}NWNL?V0O}guMdw)H&qZq z8GE(N;>1KhIDx}8_tuqUx7X%kIX|GRv)X$IkKMj(997+2&v|?@>Jr*P<;Ujo`KmnE zSF=m2KjHYEv5b+2_ucFezY%ZJ{P6&BVon&&_%(lXF*51o0AsQD_a6z=_)ebwLDa%( zTST6@Pn%QGUo!r`S^eqPPm?wPK(&(whpHC zDE3aY6dBvmM{wdzi#*gGKQ!%WXkHE*KV+=;XGhN2u`JWTc!wAKO$?no6!O^jB?-mM z3k*%MS7nB5!k|evQ#<&D3NOOvXVENAUG1&lk_ude;_g`l$t=M>r`y!cpxkTi%>b`& z+*MVM#D}o;=hJJB{^Ih2(VvG*v(8PB&I}@JPA@j{a(Z^Wbu%CQP~JG68uM)ctvtSw zl0M^*s4ngiG}+)r>VgLv*zWqgS4OX>hj`E*q@E46a3cP`pP5754WxKb-RRo`a3}ve zVc)K_9)tDfmD~fgKZb6!CfZFN9$q@XN*hUgXyeE&%l#(Z=3#lVHJ0Z`S%E>|3#T(? z+!$y**Sn^Bzh|27l4qz#|1!Bm|3pXXpk3F>|7FsisH<7YTKc*Ai&iS1kQ!Bi3ZXH? zc{3eyawFB^-L*}nX8zAij6ap;HY%nWy35#T`(yT#GW}yS&_ZnKf6Q8^Uw2AOMOpb$ zqK&2SX*Lcv3pPgOA%=mhT&&<%H{_(M!2$|vnQ16D=yQA_ZuzN+es3^0(a?ws_y$;$ zDL9?4bE&Wl*N3@*ulCyPBO1Xc_;KlL6jM1fcz0jSEKG&-7F6U;bxnH4>9}NKMus|4 zTGlZA6g$N`P#n2_%WP*Tp!`5+7_I2{x$M1!uH3rLqt~w~`3-f#H%Ka`3%f?t}|6Xp1k7=7zy&H{?g-N-rk|>5sVyi2czeMq)_(M2GW0 z+rLbL(8?e1?&R+(6mF0iq#Q+W97}pM9acJA>Pm#F+!Xkedq()m zybnWsMW%QsQiLn1xA{uOn!izD3&*_P`^Cj+(y$CZjE>Br(XX&H+Sdzz>8=a-7U)G8 zS$g;L|40|0N-zL_5rK*A_Yd`BWIYSHiJqhpC5qsfU`@>N&N4Mtm zj?XMNr!!7I*+#RZtQ27%t+{2cc8lG~Sk7?x=D`WA_0n)xc3$4PQZ?ANueG(%CcCxF zs79vIyMIhOd59=kW^K?49<-ZC`YKZs6Cc!A37;(0nS3~d>{p0#pR9|&qh@U3QnWP5 zCBGXs`N+I#wqM7a7@Mm(?_AS-KDR+>;-lNdKr#+WG&Z$aGDd}Iw;Ufg$M%!`nmzV%ScRU9 zT8_xlLvPBsA!B$5E@rQB5sq*y#DfS5$pS`j&?>$x*+~JP4r>BYVEA|&DhMHcv)Zi8 zZI)xE7PnT6VCQF#tH8-}H{lA4%BA55GYl8T2=N>r1D`Mj5eNcOAfPaDS89RL#~KVv zEw&17=6yktRlewimj&NW^RhjwwwINODk+}8W3a+&12HS>4VD@vdO7EUg!=E;rn_g+ z8X#lLEfsFL>J`VQUfUI}%XzXH^CoG3@U4!;3g5|1shZlEK5A_yDr+Y`h5R$E)e*lQ z*SWOUBQJ4BsbRQ}C-gWfu%5SeanLXZ0Yl7N|Gn1H^RsCsk$Vw}bYgjCNU)i5~N2@5Z9)3K6w;L?CGI zI)+{U{_~p!*ma_NW&8I*_|G=$IlZ~Eh~)?u!<1<4`)zF!GK9$fBfs0}t;XPBJqQW= z<>bisZ4muv4FiW+aljuL6cs=|6n_$g8-n973W8Qb{`TC!D^j!eEkR(zZNbU~fTj@Ka3X7hyy=iVMSOccbZfc`zIymHaKa55tu# zAO$W?=H`MY->m*jAxb&$^aVF(YT3y-iB43X628p5VoR# z*2}Y4LXzME6me9$-h9%jJc7FTY76J?PPk+>uE>*MW_B? zjdKRp?q?qLq|!p1O@db4jB<>vm$Ufo=LREIeyxw__%4h=Sxv->g3tMy>vm|$jGNfQ z&|%IY*G4HbG_6(>`Kg{~ZE=xNQBiTc#4(jQUs9h#(+Pq5>CEL%L8sW*r^3Vt#Cy|MF7dIrCv$Z zpJRVjLYKiF{@K!_8T;807d`T-Mv(_4fc%G-xQf^K;4G1|O4&Hju?^SUy3IA@ZqajX ztV{TVe7oo+dD5r31i{uT<~xiq^LxipZg<{g7{yPzP0WnHd9IEqXNSr~4k8Ys7=%uexYmk#{CmxiA zhjf@Ye(6tDwPvA=kl;LG_A2(u`KlH<uTZHY-eRze< zt?-YXnM1B@q^~O3TE>x2_rg z*jP?fM(!{I(tKQ;ADs4S8K;xRMBf8k&ZC$&*@(#5~0d;f7da)4_4}sxY{K z)s(eC01N+nV7-xPMlj$9_$k1kR)qqe_1QNfPl(y820jyDm8$lM0mb18tW-M+h=Fiu z0g>NrHVp;9IZ7cFR!fMJgM%DwR?`$3l`(R@PSIQxL2ECBBgs-&%wc$VY-m($N_(m0Sr^6C%elS?r|(I+d95j9!%q_XbR)v9H=K^TP-7RRa8s%D zs{PQrXNEzbwT{A6m9@T@o62Q^HXSdnfHp~(GlYtAg>p$?DtL0yYHiGay|yrJUiIa~bxjQ#^>vfe z<7?PgtKDr%w~^a)e=6*LyP#nzU%u)+Zn?Ys^&ChZ0r+h;5#rw+$<+M3uuOz_oeQpI zm#In_ElAAo#NlM0$k_6T7R2zo!q_I-MHWr)B`jKpjUI)M#oKJ>+ARfc%R2~~fG!u= zvwS(xUO8NP)v|FjBJXjpe`MaUpM-jjf`rQoP7K-Cx7Jtqq#u|BULbDr6vO0+To85y zu;n{}R+|O@ky*JvPX3$`Q%PJ*4s!@6il1`M2mcm^GW35^cZOfCiD;85sQfmNs?(jb z%ZT#xNNKMiFYShgGt9pNe^_+JbniN)b8xzUgSM@L{PkOhcv64=$J@K>%q9xLlC`F? zu2ujuL3}8%h6kETRl6FAG?~Si!fde|wuB9iKoU0DDx0P60%?LL%}lvCE#!ik(V6r# zu;SVm2l5>~x&Gn_ix%91WEEaBWHBXWKEZkv2C6ME60~FKAfn~O{`gsfzd&ef$k5+9 zxrCtyYdiZ(Az9xjZ9K|;sS4#R{Q0rAueDC$_iftZ41shej*KSeWO4Y)_7gc z(C7xnh|^3Mk{^4bD_?Tfxq?ANZMdI5Xcay-7O(2xKS*>LySl&9zcF>tddW?T-mw_< zL_AmGg3Ck%`Rieu`ftQ=NSc^pYo1b+M!1RHGUK2%$7FHWy0@!UdY$-7&t>hg#_{>j zD`O|uLJh?1`95(3K1;MB+3<*285Rk`uv`^5kXhkN0Gy03Ou=EzAZV(qLBTh|=7pI! zb$7kTLBF zo~yCgB*k#jbfP1=>F$Z>3LXIm8+&CO4miT+ia)xJayPSxvR9-^-d2fAC#?0wO8=wH zvQ#dqPckSggf)(I0d1TWn?`&q!Bm($YtH$ zH2RCw*!fb(y8Gxhtr_h|={(*4%-{7@=aYo@%eG_7sq+ib1l>s&AxtR!&%?8`l!qST zUq?6Ha+_;t=?BN+M@Q#F2B&8*GUYq1hdLoOJAbxha(k$n?e{x|N={b|@ZmZz3LJm? z!&#i5p$&nR>y4`!hb>!Unvtv|oLdr#FEr&uaoPd&Q%aho5FI!R1+U35^D7W=1Ou~1 zVddI^hYp@ZL|8eJ!{9qK-H|O2S1-8HARIVeJQ3?0tYMzOUyJ)Yy~Z&Ag_O>h%7w3+ zL2=A(uoM1*Rt4Jnz~v;b(N~oNr6}?QlJlx^v)-#p!e>5ex=vU&peH%FCI{3f0x4* zCbmM`=C5VV$3(SXt1da+x4~C@Vx|B%hbYgP!s5P~<`@SvVNEdI`}1Ltfks?2P7no? z(eErQo`8Ya6PdY4{>3NK*fv&jkRE!6yYMSHT5efazv3@XdIfHrX`0{+U{5Rqf9{Jh z1;i48b6!N0@1jNc*7-be`0iojougyoz$#5w)So?-(RhM?(m-Iu`V|a**8nH{pt$oT zFxEH11uk^rM4CW}0IOt^uL*{w1u9@{dKCoi4B&yYGwI42wAJUo74;-6hqrG2J}Sd! zeQ}yS(eM6zRaovGHO)}URGN=lHM!Wn?*XoW4cAeKXGheQ6~O(IKfTFUj**hZamN7{q2DnFcj7sd1`PP%$#|8)j1?4!Mr&my145@ zYeXT90nx)GP^0`R7y)_mljOTS2_b&5c#SC0?75kG37)7S=N^~06O^B8rIKyFv(h`H z7K~Cs9=R+2^S%;2V5hKy4xD|f&X@c)WfrVPFb#hWn-_5`@cZ?&_}u-)i$k@x&x4nh z-zhkP1RDHw1MmjuyOOuA<5QJa1!3@hEBS&KXN7O|QJ>-B!-+JFd!}Kd-T_`gSzpOL zrRm~irFN-XH>pp~!Jl&B$||_z3<2boEhLqV%!G%bhAQ_(-)>+_v{F&iIq*;axLI}9 zGefEs3Qvd@PfdWu_+|LKD8TA>rSki8xiNaU zZ=Aui1GA=;FsQbyEZL|WWfS+xj`LFSFfg@=7PmEAs3ClQJ=RN||1#L_(3^W{;^U1K z1US+D%;qzGHVZ35KDXIuBctRWtzVbb`0tM~yEzWBf?mY&Yj73*r@b;4rt^G(3r@q3 zPkGB+zczO0C&S@iRZ(~Qt9|W{BdEir_F9->Lu1{8UP+XqcR{S3UT*vam*G8s6KwSA z!AE$@hvHd1L}E>kRoHJjDaG+{?sm_~s-(N2CQhms@WFF}_xATf zZR%FRhkx?)j(;CrYB>%fC5;>c-Aq`MO`b)-Q8{0^Ojx^kOqRnR0V%+oDSD1i1ASM{ zzlieeD)3+Hz&;_F3Hb{JN1HGK&nTHK>9oQsR9JWu*@$89l zg>kBO*LmY&tZjM?$Cvl#Wmf&0^av43Y~{ zolTR7;wL({)I3yfdT?Uo~p?Msoo1mCEuMmwbSh3UbbezI7UpE|l_E;{px;0&Cc2cN-G#tM&4Q^v3n zqbsEYtYhNuE!zBC3o(+w)Od!lnifd-RNQc(t}^7!;m-t?Oj24;e6QlPMy}CbyzqDV zk>#4xwX zT;VI5?|k{^-Jqoe{d0+BhB)y&NKIwv7azgfVH{QnEoh8_FoXr5F#P5I!jJ;&OFrCl z2Y~FeX+cZkfTPA=?O6ek+}UV1X)ZVZ%j(?nESr6-I6-TJDFf>B%c=^(T6bs9BnSxD zfsOfD9GC$yVO7wYC2>`{v-qoWSpv(@@wK8IiR%Xvcj}}L9^3qvW5FqPl9w^b|Mjd^ zoAvJDPg;bU%frL@nZsOnRlU;Zk`rm*kInCewJwmL218F{nE`DNczE?gHkICLhD_(& zE>#gydua#{tOOxIOJZd}SiZ9WemSjc-op;b#Z1k{|!pEAEY}{*AR4iB<+r zItu}Z5PYf*#|42;^`94l@vREW8%uR3j{TwCB5JAA z58=EOB8{+S7Jl|6P+S~_IR@DX%okQua5l~O5}s(EWSpRb9cY>0I3f1eLlC%eZ>wr} zg{=0F{?Br=_ziE(Pz~)h>#=^Z>D* zs(_Y9I@(A-5#rvXiIu>cg*+J9(uvOd_dJG2GKbGgU|oEpOf<^7HfUkLwpO>WFG7=Ala+ZNY7>^rt|$sILvH9`@uMg|+lnpchp~ z)Gxk=zbx%D(tCafmv-yfL-IYjiF+MCKTMiXy;0plxI6dT{B)PjjsqPO<&#Z2|dizIsRad&GByY-WY#>f6^}* z@h{WkjN;{>U}z`Glye0U~!S^z8^|^(M%l{ zs1N@#n+zUG_57|}&hB8}40#&E70a(sRKRP0&PKmBj*r4KlU$x`Rhz0@1iP4omT_TS zP2exUS9`9WA{ui2B`Qd>Mi@xyO*2Qyr(RS;UKm-Q_GaYaJc@9{NDMS5PeOIBMv z>k2j5wERDwBF{G#)u5&TTej~EaoK1Imu$B6P#9>u0O(tz2?8SqML4_nCU&U=3Ei~8=yllAU z*Sq>1J2r{xp!&3U6<9b0e%}~eNvktTmA*o-I1SYf{1s7EcCu=Cr~db2p3*(_dQvpba-S3t5^bdK3w%7n}r2-u$wd?t2*^ilHb`2e3M6v*1{QErl`lX(tN z9v^L~OJ7lpKqIvdV#j*?3}ZbCrj~}2EWLGn7RSbugggk>6vt?L(^;vxjpQ$Ld6wd+ zM>z+SP-|k#av*hTEf5`b`gUz_X;x|a3VO(+A(r*>z`WsSjP&dk6;C#Jn%}9~%%?&p zroM1JIL}M9wPI;z7wg>k&KGxHBFNPJSQNeR6RCT0aBsV7#C4mYC7S0x_wJCaF?X<4 zzR(ryJS@|K&tqMM16n6Qq;UY|7GR$^k!-#z7h_Fs0x~+66BHjubahJCcwq54`X2Jv zdY!GJo(t5sDlbgR0jL(fjQy5o(_zesl(CMMy``=Gi`nAskcGq@l^=jS0WE}2*w02A zs{v9ZB0t89ubh1e_y!U@nk&8dqfyr7YP%}Stg7@@qAtR~v|_v#-uY5mG1x=uemEn7 zM+c2m3lixmc3aPwp)O8rw>|B>^6kG-TwlT9Ils_3wAubS zyhic;VbMZg@>w4gfp0||hdQTVu?xr7@=%ekUSWSux4t7suQQ-yPq8(cTZxHu%&P_@ z=A`zBp$g?&hDC%sog^ur^khKJA+|^sk1CGY$D-Cm2-;1kNVY>*Cy5J;* zA+gA?gV~6N`Hn%~j68gGA8vgx4g zhnJznUr$5kBB(jRAHo{8snn_$!`HAiTCoX8`P@p6B7#L!_HUuX6*5Z>bp7>DuF1QO zA(?#?)H2Kcu^el7nh_V!Z~yu%fCXU$eF7-RHZKtDiL6GlZXb~sp=*%b(NN!Kb-I9r#jMNdoL(j3WyWLIIk^ztYcL>y8 zK|e_d6bh)|Hjpl4dW@tD{%~GHd0da;B_}>>j^OAD?W`k4$)PC%Eo@! z=+?+?6qqmzGDJgM`6v8NoqJKs)G>1=Rh2g0YJoXjzDVCK!*{>MDE^&I@Lqr&1;Q=Z ztm269Kmo9Tku6U@J71a!90scg3!qqPh`yZ@O)9o(lYRm%O{k>gP_liH7iH`sgp>Lj zPVm#2Irec1qd+I-lQ-7`acg~NibRNa4N_GW-~ks>*#)dRpO9?EiPuB!lA!DYvYkc3 za-O75XWjxMqV*>Zf6l=Ed++auWc0F8GoRO#t?nI_8V=5n?;e?iuY`x&VnSPO{*%oC z7DPaSPwN}eIS&-0rP~s&^xdy}wtHIs+ghoL~$VDIn{ zQ!eJulbFWg?{~>W;pFK$T1eLKtKTCZ2KoaFjE}|8=5M+*^wLy+&!43qX*mx@Zs&f} z986C`lV;UfSXRgl z0(?LCM^}B%0@z<=1_LU32^b%N&j7oSl`FG6usES3Q))6JyP!LlLJEuXHhsXcYRmDc zuh>sj1M=My?*NE;SUigus|jdPf9$g8N_yE6@UDGL>PT{gOYwhpIV}C8M6EVu;nZkgR8>6R@@Y$O zeBkQhn5AD9`4>ZtKUWFf`!}%nKco3vX<%OttQ@p)qy^jN^X1bX1p}UG%MHthpe3WV z9u@Ylv^4IQeo%IB-|Bt#mWomIniQ_q?T$~Yc;abWTaEGt`HvuFn8a?iG<14EC0Ey0 zhE_Es;ehiOhXrJyWC8$EF;FsnYkT)EO3TkHB(y#h{lds+$C5?MZYk0^mW|@%oYh82_>8)EyHs zhXOUnXWfG<#))C~BSU`G`%|asLJYcT=vDsg4%hzFS^HVn!bnvGJ z!`vLFLV+~`-ghHlv-)nFYP1)S?N8^Hed?y)t61(Y2#1^KlYr^?Z<~IpYV&OKBaqO0 z1%yfv>`o3+@qn~8+x>T@i#M*P72%!5vuwZzvVuPnLB$^>1;EJQ0BUEI z!}0*VYkg3)uUdAE6vGq)Al3N*V35G#L>lX*(?YClRfN2?%5PZVMxf~p=TL*Zog0)S zcehjUw6=f`Q^5^0A-oz}>I!fw@2{c@|W`4W6wE9Nf+wj#pf0wc2n z5iU`>ryYK1M=4oWR9#EyTHEznkylwyNSR&Pb<}z6lG`l0&?T2|x3-A2)1-ytR({gx zuZL{6e6<(bdV?T(z7(mU|B;m9`3%GMkfV*GLm>l%giCKia>8ueGG5j1b*b-ckkb}; zcb(-6x~j?u7Q^QYqC#qV_6}=2)|U5vt;kzgnw;W^RG8!O8*F=6WsN`|zWhetW`{l} z*>2k`SWROpV_igM`0WD!T04MnP_`-vtmJiGeFNcCTQ<;j3{n-zBj%{`gIo4Q8Y@sE zd%j{e5I~%TA@BcsT^%v{oj5^&gRz?YH5^yVfZRhzAr7!wWaH~nn*kY$HCWwDmJKjS zylwP-?HvOX)ymGEEOR@vS^pN0(VRv|_I!Z&#|^>(pOo)9!6POBj!)*l?uk}0U#}V8 z@#t!(s{##Qb1~Opci%lJVrLGe*|suj2xpEjOnuhF22`m#(YG{DdIu5och_&n5q_)b z)E?{8dlK8N5@(JkLafQ6S!A|C%cr9(O)dpWP0ot$ zZO<(XhK^MlI{N7vPtvx8*U!st6W-B=H9|e)96jsc9vSSBFR5>)^4yUH-y(Li=@{M1 zUhn2^tFCKhI&OT-{dg-NYt&*p!LNUe%i&VnIlTB+lEW;Qn8ppdMtWD)+IdW>OOQlW zo}M4iiW=Qgm_ojJI}dVdyuXPtqbA$aA6M7}tN>RWhR^L=0^$Rp-@RhP0r4pTMMR7# z$kbEA7S>qc#uQ^@0A#NOj1&R2OTeDrp96^nss$-1td`XOtKYE!#+trizUN;Pk_6{& z){hB(E($o)*b8GLnbWk|7n4mRPaN1H?bxL)3o|Kt1Oz?H9h7DHl+ zZYd5U_9siwMgy9w7|3$dH%*yg=T~#6qmcPFTY^~djdaxq&9`yMqrHyHXY9;h@_F$- zGNq=yE4}@If(@f$b9KY$YW|tQr;@IgF7pSkjFNg=)*ExZ57+%nFrzg3oCE71SxpQb z;mZ3Io66s(AEU?-z=8n9{Z)US7^JHDb96Xj{{GsgTLn~ zGDH?v7g}EqX{*Z{+&{VaGqErr-505SaNmpX${MvyVnFdLsi8+LCpR2-It}e zJZ%45ZQR{5ln_V*kTu%G_BDuI%#n}E!e0=>Cut-Msg|_o%ja8rw#f>}8O#7`TLF9X zKdPXSK_PAv_m|aRffN*6Qt-T-{Z%#Y$UevMIbHi!g~j>wxzsv=M=e`m@qxM_iTtdd zQ+@^>4t2;9wVtu2U4A#M4Xs(NibE8-m=Q6j*KB{Btvq;Gp9&;3Z~!{6DCA#~MQB~; zF1WFor@I~Bh}iFY?C!t*+3u0rXTp=x5h~&@GvduLSA_#gocsr&sXzwj?<|W1WK{lw!We^m$&ZpF z{2|a-m?|KNGs7Bv`dXYO25gn?HP&MS{n;(AJ0mTj&Q4d>J>FVxth_GUOX-^QYs}>; z-k+dtTNaRqqLr}ZL4)~HyZ-BXl`b-;qU!Z-p>|*G*hhM{>B)TUUo>vNcP$|;^5q{r z6Y`xM8L#8WJSp$T_NOy6{7Ui$QDl-#%m}&4QQkhf##(5Q&N;~ z#Uulizoy#UFO3v++DN;aHf@)@%!trN8sigp?ozR&9wsjjM3DMMl>C*oki3@L>yVw$ z$kEoQ$0wYlbGCNA$2t+82N=lT46nQ}+$U2M`|&?NF|;n2-Enk{L~)Wi1{7fY3n^JB&$|Xx%HrU zb*U;OzJUV?nv492``n5xVO|B--4ZCdgF?nVu$@K*dKYbK4BO_G&*^CBPXCF7=)J{K zx+Wt7=EcN@->)9LdbBTp5Jt~?RF&X8y$;_w&hY^D1bGs@PRm3O%`C6agj>ts7?l1R zD%EP*|8~Ms&(7u$&TBN(SRRN&2#ruC)u#0XX!Dg5iw?_F>ehhaq~)bY=$4t+rqrtb zs}A(B=W};9>~s%DXlUGeu7NHOvL}a1r$KA}gn<2Uq$Q0X`@eRK=emFOg#MJsskF4# zAuTibX~;#3K9itft6>-2L_!`0mlf9d^HdnBNBK9zjTV^zk1tTFx)x=dP~Y+1zB4(s#D!5YV)P`B7VFo{0M_bO;)yZa?5FE5W{OcK?Xq=mkTv%v zzeao7x=z+g+Rd|`*fE`q#JY5O zIPBvOz=XO@*6qO@k4f+4X2{CCrAQri%mUn&f-3)(#s$O27$b0_f7?6>w)v{EbA;ZmKlt7 z{zJ6CWI&t0vok#1oj?-4CJuohpeoH1DC%m63!Mm2T+5|uYdjX$Ksm}z zgZXm0vzK!8naQQ>&P`_Z=N(ntt}D}CI>FbT>*SAaeM954^#{i5_;Tfm#akChEmatsrmG?9s?J4P zvRb0(ZkQ<(Km{Wx7B*%LH3+V>EljqKl|&?QKsQH8l+_Y6W_H$8EK~>`gF#cq6!EeSciKy^vh4@4$SX@} z+mTxa%p<*hE)?{ubCMl&?adyN_QHC`G1igUBw{MqOP*+kLUm}7aaL-5rh3svan=^+ zwgrPlRa8Suhk7if+Zd|c>1yIu=cU`@2c-AiAD7*~V(I)Y?!QKMe=WwpTD$T_>A%%e z^B+rB^i22{OK-K$lyBAgK=^Kk^XVJM-KN=`v9`rf;6{DnIm|s=cIxNp(367cbsQYs^}r^jkkY-)LJNqB zM{K;Zj^{yn%O>=BntAEX9*+*3Bzhb@G0ao*`gxtLin}_oBIRmagpAjdXD#(zgHMOO zhJ3v-(8z>!$t`2wn^j5PMT&N&{%EBR!n$C^0sLdw_GincL4wy%z7T~?~m}^Mt zsEjPCtF&R#3@Tfysxb>00~s#?3yBqvi8Ysrqk&msjYmADgP{^gCM>h&RZZhD|msriuhY8Dd*1=IYXD zi#ei+B|N&Ma}RzlyqkEmt8?o_sLRt=PJ9{C*WVl&=#eihdPZSB*>UOTYp==g$}w-E zn(vOetLf+5=ZovMb1c)>m{s2Lk=WClUBJ!Y*FtvXn2^8XzTFh6tspWkuBtIGAqYUE z>S$T!+0ivxrZbhwLFzfU!nIJcS_O`-fMCdxAk`{xQlFgcO|><7x_Qis>MnvsXjZmW zQ&$X19Y)+*MKMf_k9AO`)>565{6=p!zp)rG`qo@dGEKaJbtza^(UEDxc zsaWq~Y)xq$T+>L_P}n+Ur`=_m!jVohG|-w6bfj4l1<1BbgN1NA5u<=gT5{f<-oJ{g zewoqvC~IGH>wa~cew^L;Qu^oht3N4pwSP^I=dP~r+-Jylbe~PX2fL*82j|bE^mpd; z<6Ga#PN#TybuVIZLwW8G3he1;&0Qx)PZ@c4C!cBBXEUS6ZN%(VDF@%4&X1XXHQVH? z6`T5=KbyJqi}_EUx9^So^csGF&GN57$?TDnp~qP_@+Vw+Jlxjs+*nTe5T|QCQjEWm zbc1lK3K}a_9-P*iDGH8+$}zsVu;>>hwr#AoxVK5p0Qt?@g-)o|G;;pc?YJRcaN|_7ZX-imOTo)w!6|Q|TrZL{`$pc{o+10cJsf zP0=ZF>LRzyxy8{M=QDn4Ks{8$$tdaB?6sNNs&0J(wM{S<%Uo5_sN@f9HF!nTR-DPu zR^~g^;C!6wYT(|YEUzOqY$G%t2tLkv~s`1ySx5MT; zc+^)nFKl(yjCGy(!nGT#7OPG8w_{IdH@S^!>~ZpN>FrnKCmnireCrond(S`S57Eo& zA(ub#Uj0Mlzb%84YzqmPWpfHLFvtXMRb?GavzQ%aRx8sCP0s?(txUUJ{JCYh$yYG*C+a7P)Ldv4sbQw)L7QJIge+{@Xk zoaM7oMpmpMG$mTxu*QJ3UF@T3I3D3E(FTu`jL1reX1TDUfijirW6s}h&4&SUnbvLmOTbCrEX zbhy{LYGba(NpZ!_p%#G{h=WXWH?I3l?=$G*DGK^h&iuBzWpBimK?Mt`x9k)wOac-I5Iv3OX@GcT0^3 z7*$nwbh5g#5Y(+a^={6mpPq@;=y|K{Jw~q|eK+g!d70>)`r#L+9-w%7Hm4=sv$px2 zYp@rtxY+ys&bj4vxp{eB;&6DE#_io| zTC*H^AG4-O8ykRS-JdP$Zc-&Y7W+`hYK=6g8hW&AQaLbnE(A6sHB{@|#a%TCt!RbQ zRY4v$0gcO1gBgpUCpPw7V-C6wZFH`<+5G_ObJw~_nzWh6*{qIQx<#g8-o9)nH1wHM zur0@bD>>uS6y+Y# z+~y;>pKJ^4DCR4ytuT*5w8u20OJAN*0X;4)gen+|U}yz$)T!^RE9eU1r&i*lKmlsC zB~+p$8n{4Fl|-W$6crpKT4rE)>L4F_XB~899k!Sz=~(MnnJZn^D&19%Xrn!84F(d1 z4O~MSrbU!o8VG{Ipt>ly6bmGkQb=5@B#@$@7&57j$~wCPE%lGr%^tI(eEaJ4m^#AXHxWboOWArI<5KND+M