Spaces:
Running
Running
| import init, { Model } from "./build/m.js"; | |
| async function fetchArrayBuffer(url, cacheModel = true) { | |
| if (!cacheModel) | |
| return new Uint8Array(await (await fetch(url)).arrayBuffer()); | |
| const cacheName = "moondream-candle-cache"; | |
| const cache = await caches.open(cacheName); | |
| const cachedResponse = await cache.match(url); | |
| if (cachedResponse) { | |
| const data = await cachedResponse.arrayBuffer(); | |
| return new Uint8Array(data); | |
| } | |
| const res = await fetch(url, { cache: "force-cache" }); | |
| cache.put(url, res.clone()); | |
| return new Uint8Array(await res.arrayBuffer()); | |
| } | |
| async function concatenateArrayBuffers(urls) { | |
| const arrayBuffers = await Promise.all( | |
| urls.map((url) => fetchArrayBuffer(url)) | |
| ); | |
| let totalLength = arrayBuffers.reduce( | |
| (acc, arrayBuffer) => acc + arrayBuffer.byteLength, | |
| 0 | |
| ); | |
| let concatenatedBuffer = new Uint8Array(totalLength); | |
| let offset = 0; | |
| arrayBuffers.forEach((buffer) => { | |
| concatenatedBuffer.set(new Uint8Array(buffer), offset); | |
| offset += buffer.byteLength; | |
| }); | |
| return concatenatedBuffer; | |
| } | |
| class Moondream { | |
| static imageArrayHash = {}; | |
| static instance = {}; | |
| static currentModelID = null; | |
| static async getInstance(weightsURL, modelID, tokenizerURL, quantized) { | |
| // load individual modelID only once | |
| if (!this.instance[modelID]) { | |
| await init(); | |
| self.postMessage({ status: "loading", message: "Loading Model" }); | |
| const [weightsArrayU8, tokenizerArrayU8] = await Promise.all([ | |
| weightsURL instanceof Array | |
| ? concatenateArrayBuffers(weightsURL) | |
| : fetchArrayBuffer(weightsURL), | |
| fetchArrayBuffer(tokenizerURL), | |
| ]); | |
| this.instance[modelID] = new Model( | |
| weightsArrayU8, | |
| tokenizerArrayU8, | |
| quantized | |
| ); | |
| } | |
| this.currentModelID = modelID; | |
| return this.instance[modelID]; | |
| } | |
| // Remove the modelID parameter from setImageEmbeddings | |
| static setImageEmbeddings(imageArrayU8) { | |
| // check if image embeddings are already set for this image and model | |
| const imageArrayHash = this.getSimpleHash(imageArrayU8); | |
| if ( | |
| this.imageArrayHash[this.currentModelID] === imageArrayHash && | |
| this.instance[this.currentModelID] | |
| ) { | |
| self.postMessage({ | |
| status: "embedding", | |
| message: "Embeddings Already Set", | |
| }); | |
| return; | |
| } | |
| this.imageArrayHash[this.currentModelID] = imageArrayHash; | |
| this.instance[this.currentModelID].set_image_embeddings(imageArrayU8); | |
| self.postMessage({ status: "embedding", message: "Embeddings Set" }); | |
| } | |
| static getSimpleHash(imageArrayU8) { | |
| // get simple hash of imageArrayU8 | |
| let imageArrayHash = 0; | |
| for (let i = 0; i < imageArrayU8.length; i += 100) { | |
| imageArrayHash ^= imageArrayU8[i]; | |
| } | |
| return imageArrayHash.toString(16); | |
| } | |
| } | |
| let controller = null; | |
| self.addEventListener("message", (event) => { | |
| if (event.data.command === "start") { | |
| controller = new AbortController(); | |
| generate(event.data); | |
| } else if (event.data.command === "abort") { | |
| controller.abort(); | |
| } | |
| }); | |
| async function generate(data) { | |
| const { | |
| weightsURL, | |
| modelID, | |
| tokenizerURL, | |
| quantized, | |
| imageURL, | |
| prompt, | |
| seed, | |
| temp, | |
| top_p, | |
| repeatPenalty, | |
| maxSeqLen, | |
| verbose_prompt, | |
| } = data; | |
| try { | |
| self.postMessage({ status: "loading", message: "Starting Moondream" }); | |
| const model = await Moondream.getInstance( | |
| weightsURL, | |
| modelID, | |
| tokenizerURL, | |
| quantized | |
| ); | |
| self.postMessage({ status: "loading", message: "Initializing model" }); | |
| self.postMessage({ status: "loading", message: "Loading Image" }); | |
| const imageArrayU8 = await fetchArrayBuffer(imageURL, false); | |
| self.postMessage({ status: "embedding", message: "Creating Embeddings" }); | |
| Moondream.setImageEmbeddings(imageArrayU8); | |
| self.postMessage({ | |
| status: "complete-embedding", | |
| message: "Embeddings Complete", | |
| }); | |
| const { token, token_id } = model.init_with_image_prompt({ | |
| prompt, | |
| seed: BigInt(seed), | |
| temp: parseFloat(temp), | |
| top_p: parseFloat(top_p), | |
| repeat_penalty: parseFloat(repeatPenalty), | |
| repeat_last_n: 64, | |
| verbose_prompt, | |
| }); | |
| const seq_len = 2048; | |
| let sentence = token; | |
| let maxTokens = maxSeqLen ? maxSeqLen : seq_len - prompt.length - 1; | |
| let startTime = performance.now(); | |
| let tokensCount = 0; | |
| while (tokensCount < maxTokens) { | |
| await new Promise(async (resolve) => { | |
| if (controller && controller.signal.aborted) { | |
| console.log("Aborted"); | |
| self.postMessage({ | |
| status: "aborted", | |
| message: "Aborted", | |
| output: prompt + sentence, | |
| }); | |
| return; | |
| } | |
| const { token, token_id } = await model.next_token(); | |
| if (token_id === 50256) { | |
| // <|endoftext|> | |
| self.postMessage({ | |
| status: "complete", | |
| message: "complete", | |
| output: prompt + sentence, | |
| }); | |
| return; | |
| } | |
| const tokensSec = | |
| ((tokensCount + 1) / (performance.now() - startTime)) * 1000; | |
| sentence += token; | |
| self.postMessage({ | |
| status: "generating", | |
| message: "Generating token", | |
| token: token, | |
| sentence: sentence, | |
| totalTime: performance.now() - startTime, | |
| tokensSec, | |
| prompt: prompt, | |
| }); | |
| setTimeout(resolve, 0); | |
| }); | |
| tokensCount++; | |
| } | |
| self.postMessage({ | |
| status: "complete", | |
| message: "complete", | |
| output: prompt + sentence, | |
| }); | |
| } catch (e) { | |
| self.postMessage({ error: e }); | |
| } | |
| } | |