Spaces:
Sleeping
Sleeping
File size: 3,488 Bytes
5e1b738 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
import {
AutoTokenizer,
AutoProcessor,
WhisperForConditionalGeneration,
TextStreamer,
full,
} from "@huggingface/transformers";
const MAX_NEW_TOKENS = 64;
/**
* This class uses the Singleton pattern to ensure that only one instance of the model is loaded.
*/
class AutomaticSpeechRecognitionPipeline {
static model_id = "onnx-community/whisper-base";
static tokenizer = null;
static processor = null;
static model = null;
static async getInstance(progress_callback = null) {
this.tokenizer ??= AutoTokenizer.from_pretrained(this.model_id, {
progress_callback,
});
this.processor ??= AutoProcessor.from_pretrained(this.model_id, {
progress_callback,
});
this.model ??= WhisperForConditionalGeneration.from_pretrained(
this.model_id,
{
dtype: {
encoder_model: "fp32", // 'fp16' works too
decoder_model_merged: "q4", // or 'fp32' ('fp16' is broken)
},
device: "webgpu",
progress_callback,
},
);
return Promise.all([this.tokenizer, this.processor, this.model]);
}
}
let processing = false;
async function generate({ audio, language }) {
if (processing) return;
processing = true;
// Tell the main thread we are starting
self.postMessage({ status: "start" });
// Retrieve the text-generation pipeline.
const [tokenizer, processor, model] =
await AutomaticSpeechRecognitionPipeline.getInstance();
let startTime;
let numTokens = 0;
let tps;
const token_callback_function = () => {
startTime ??= performance.now();
if (numTokens++ > 0) {
tps = (numTokens / (performance.now() - startTime)) * 1000;
}
};
const callback_function = (output) => {
self.postMessage({
status: "update",
output,
tps,
numTokens,
});
};
const streamer = new TextStreamer(tokenizer, {
skip_prompt: true,
skip_special_tokens: true,
callback_function,
token_callback_function,
});
const inputs = await processor(audio);
const outputs = await model.generate({
...inputs,
max_new_tokens: MAX_NEW_TOKENS,
language,
streamer,
});
const decoded = tokenizer.batch_decode(outputs, {
skip_special_tokens: true,
});
// Send the output back to the main thread
self.postMessage({
status: "complete",
output: decoded,
});
processing = false;
}
async function load() {
self.postMessage({
status: "loading",
data: "Loading model...",
});
// Load the pipeline and save it for future use.
const [tokenizer, processor, model] =
await AutomaticSpeechRecognitionPipeline.getInstance((x) => {
// We also add a progress callback to the pipeline so that we can
// track model loading.
self.postMessage(x);
});
self.postMessage({
status: "loading",
data: "Compiling shaders and warming up model...",
});
// Run model with dummy input to compile shaders
await model.generate({
input_features: full([1, 80, 3000], 0.0),
max_new_tokens: 1,
});
self.postMessage({ status: "ready" });
}
// Listen for messages from the main thread
self.addEventListener("message", async (e) => {
const { type, data } = e.data;
switch (type) {
case "load":
load();
break;
case "generate":
generate(data);
break;
}
});
|