马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
worker.js
- 模型的团体推理结构仍与webassembly009 transformers.js 网页端侧推理 NLLB翻译模型类似。
worker.js 模型工厂类
- /* eslint-disable camelcase */
- import { pipeline, env } from "@xenova/transformers"; // 导入必要的模块
- // 禁用本地模型,确保仅使用在线资源
- env.allowLocalModels = false;
- // 定义模型工厂类
- class PipelineFactory {
- static task = null; // 静态变量:任务类型
- static model = null; // 静态变量:模型名称
- static quantized = null; // 静态变量:是否量化模型
- static instance = null; // 静态变量:模型实例
- constructor(tokenizer, model, quantized) {
- this.tokenizer = tokenizer;
- this.model = model;
- this.quantized = quantized;
- }
- // 获取单例模式的模型实例
- static async getInstance(progress_callback = null) {
- if (this.instance === null) {
- // pipeline函数:默认情况下,模型将从 Hugging Face Hub 下载并存储在 浏览器缓存 中,有关更多信息,请参见https://hugging-face.cn/docs/transformers.js/custom_usage。
- this.instance = pipeline(this.task, this.model, {
- quantized: this.quantized,
- progress_callback,
- // 对于中型模型,为了防止内存溢出,加载`no_attentions`版本
- revision: this.model.includes("/whisper-medium") ? "no_attentions" : "main"
- });
- }
- return this.instance;
- }
- }
- // 自动语音识别管道工厂类,继承自PipelineFactory
- class AutomaticSpeechRecognitionPipelineFactory extends PipelineFactory {
- static task = "automatic-speech-recognition"; // 任务类型为自动语音识别
- static model = null;
- static quantized = null;
- }
复制代码 处置处罚来自主线程的消息
- // 监听消息事件,处理来自主线程的消息
- self.addEventListener("message", async (event) => {
- const message = event.data;
- // 根据接收到的消息数据执行转录操作
- let transcript = await transcribe(
- message.audio,
- message.model,
- message.multilingual,
- message.quantized,
- message.subtask,
- message.language,
- );
- if (transcript === null) return;
- // 将转录结果发送回主线程
- self.postMessage({
- status: "complete",
- task: "automatic-speech-recognition",
- data: transcript,
- });
- });
复制代码 调用worker
- // 转录函数,根据提供的音频和其他参数进行转录
- const transcribe = async (
- audio,
- model,
- multilingual,
- quantized,
- subtask,
- language,
- ) => {
- const isDistilWhisper = model.startsWith("distil-whisper/"); // 判断是否是特定模型
- let modelName = model;
- if (!isDistilWhisper && !multilingual) {
- modelName += ".en"; // 如果不是多语言模型,则附加.en后缀
- }
- const p = AutomaticSpeechRecognitionPipelineFactory;
- if (p.model !== modelName || p.quantized !== quantized) {
- // 若模型或量化设置改变,则重新初始化
- p.model = modelName;
- p.quantized = quantized;
- if (p.instance !== null) {
- (await p.getInstance()).dispose();
- p.instance = null;
- }
- }
- // 加载转录器模型
- let transcriber = await p.getInstance((data) => {
- self.postMessage(data); // 发送进度更新
- });
- const time_precision =
- transcriber.processor.feature_extractor.config.chunk_length /
- transcriber.model.config.max_source_positions;
- // 初始化待处理块列表
- let chunks_to_process = [
- {
- tokens: [],
- finalised: false,
- },
- ];
- function chunk_callback(chunk) {
- let last = chunks_to_process[chunks_to_process.length - 1];
- // 更新最后一个块的信息,并在必要时创建新的块
- Object.assign(last, chunk);
- last.finalised = true;
- if (!chunk.is_last) {
- chunks_to_process.push({
- tokens: [],
- finalised: false,
- });
- }
- }
- // 回调函数,用于合并文本块
- function callback_function(item) {
- let last = chunks_to_process[chunks_to_process.length - 1];
- last.tokens = [...item[0].output_token_ids]; // 更新最后块的token信息
- // 解码并合并文本块
- let data = transcriber.tokenizer._decode_asr(chunks_to_process, {
- time_precision: time_precision,
- return_timestamps: true,
- force_full_sequences: false,
- });
- // 发送更新状态给主线程
- self.postMessage({
- status: "update",
- task: "automatic-speech-recognition",
- data: data,
- });
- }
- // 执行实际的转录过程
- let output = await transcriber(audio, {
- top_k: 0, do_sample: false, // 使用贪婪搜索策略
- chunk_length_s: isDistilWhisper ? 20 : 30, stride_length_s: isDistilWhisper ? 3 : 5, // 设置滑动窗口长度
- language: language, task: subtask, // 设置语言和子任务
- return_timestamps: true, force_full_sequences: false, // 返回时间戳
- callback_function: callback_function, // 每个生成步骤后的回调
- chunk_callback: chunk_callback, // 处理完每个块后的回调
- }).catch((error) => {
- // 错误处理
- self.postMessage({
- status: "error",
- task: "automatic-speech-recognition",
- data: error,
- });
- return null;
- });
- return output;
- };
复制代码 useWorker.ts
- 代码利用React Hook useState来确保Web Worker仅被创建一次,并提供了一个自定义Hook useWorker,用于简化在React组件中利用Web Worker的过程。
- import { useState } from "react";
- export interface MessageEventHandler {
- (event: MessageEvent): void;
- }
- export function useWorker(messageEventHandler: MessageEventHandler): Worker {
- // Create new worker once and never again
- const [worker] = useState(() => createWorker(messageEventHandler));
- return worker;
- }
- function createWorker(messageEventHandler: MessageEventHandler): Worker {
- const worker = new Worker(new URL("../worker.js", import.meta.url), {
- type: "module",
- });
- // Listen for messages from the Web Worker
- worker.addEventListener("message", messageEventHandler);
- return worker;
- }
复制代码
- useWorker 用来处置处罚与 Web Worker 的交互,Web Worker 的职责是处置处罚耗时的音频转录工作,避免壅闭主线程。通过监听来自 Worker 的消息,更新转录状态。
- “progress”:更新文件加载进度,修改 progressItems 状态。
- “update”:更新部分转录文本,将数据更新到 transcript。
- “complete”:转录完成,更新 transcript,并标记为不忙碌。
- “initiate”:开始加载模型文件,添加进度项。
- “ready”:模型加载完毕。
- “error”:错误处置处罚,提示用户。
- “done”:模型文件加载完成,移除进度项。
useTranscriber.ts
- 代码实现了一个自定义的 React 钩子 useTranscriber,用于处置处罚音频转录过程,结合 Web Worker 和后台模型进行推理。
定义的接口
- ProgressItem:用于表示文件加载的进度信息。包含文件名、加载的字节数、总字节数、进度百分比和状态。
- TranscriberUpdateData:表示部分转录更新数据,包含已经转录的文本和时间戳的 chunks。
- TranscriberCompleteData:表示完备转录数据,包含最终的转录文本和时间戳的 chunks。
- TranscriberData:表示转录的数据,包罗是否忙碌、转录的文本以及文本的时间戳 chunks。
- Transcriber:是 useTranscriber 钩子的返回范例,定义了用于控制转录和管理其状态的各种函数和状态。
- import { useCallback, useMemo, useState } from "react"; // 导入 React 钩子函数
- import { useWorker } from "./useWorker"; // 导入自定义的 Web Worker 钩子
- import Constants from "../utils/Constants"; // 导入常量配置
- // 定义进度项接口,表示文件加载的进度
- interface ProgressItem {
- file: string; // 文件名
- loaded: number; // 已加载字节数
- progress: number; // 加载进度百分比
- total: number; // 总字节数
- name: string; // 文件名
- status: string; // 当前状态(如正在加载等)
- }
- // 定义转录更新数据接口,表示部分转录的更新数据
- interface TranscriberUpdateData {
- data: [
- string, // 当前转录的文本
- { chunks: { text: string; timestamp: [number, number | null] }[] } // 文本分块和时间戳
- ];
- text: string; // 当前转录的完整文本
- }
- // 定义转录完成数据接口,表示转录完成时的完整数据
- interface TranscriberCompleteData {
- data: {
- text: string; // 完整转录的文本
- chunks: { text: string; timestamp: [number, number | null] }[]; // 分块的文本和时间戳
- };
- }
- // 定义转录数据接口,表示转录的状态和结果
- export interface TranscriberData {
- isBusy: boolean; // 是否正在转录
- text: string; // 当前转录的文本
- chunks: { text: string; timestamp: [number, number | null] }[]; // 分块的文本和时间戳
- }
- // 定义转录器接口,提供一系列 API 用于控制转录过程
- export interface Transcriber {
- onInputChange: () => void; // 输入变化时重置转录状态
- isBusy: boolean; // 是否正在转录
- isModelLoading: boolean; // 是否加载模型
- progressItems: ProgressItem[]; // 文件加载的进度
- start: (audioData: AudioBuffer | undefined) => void; // 启动转录
- output?: TranscriberData; // 转录的结果
- model: string; // 当前使用的模型
- setModel: (model: string) => void; // 设置使用的模型
- multilingual: boolean; // 是否启用多语言支持
- setMultilingual: (model: boolean) => void; // 设置是否启用多语言
- quantized: boolean; // 是否使用量化模型
- setQuantized: (model: boolean) => void; // 设置是否启用量化
- subtask: string; // 子任务(如语音识别等)
- setSubtask: (subtask: string) => void; // 设置子任务
- language?: string; // 语言设置
- setLanguage: (language: string) => void; // 设置语言
- }
复制代码 状态 useTranscriber 钩子
- 该钩子封装了转录逻辑。
- postRequest 用于发送音频数据到 Web Worker 进行转录。它担当一个 AudioBuffer 对象,检查其是否为立体声(2 个声道),并将其合并为单声道。如果音频数据是单声道,则直接利用第一声道数据。然后,它将数据发送到 Web Worker,并通报须要的配置选项(如模型、是否利用多语言、量化选项、子任务等)。
- // 自定义 React 钩子 `useTranscriber` 用于管理转录过程,返回值类型是 Transcriber
- export function useTranscriber(): Transcriber {
- const [transcript, setTranscript] = useState<TranscriberData | undefined>(undefined); // 存储转录结果
- const [isBusy, setIsBusy] = useState(false); // 转录是否正在进行
- const [isModelLoading, setIsModelLoading] = useState(false); // 是否正在加载模型
- const [progressItems, setProgressItems] = useState<ProgressItem[]>([]); // 存储加载进度项
复制代码
- useWorker 用来处置处罚与 Web Worker 的交互
- // 使用 Web Worker 进行后台转录任务
- const webWorker = useWorker((event) => {
- const message = event.data; // 获取消息数据
- // 根据消息的不同状态更新相应的状态
- switch (message.status) {
- case "progress":
- // 如果是文件加载进度更新
- setProgressItems((prev) =>
- prev.map((item) => {
- if (item.file === message.file) {
- return { ...item, progress: message.progress }; // 更新进度
- }
- return item; // 其他进度项不变
- }),
- );
- break;
- case "update":
- // 如果是转录的部分更新
- const updateMessage = message as TranscriberUpdateData;
- setTranscript({
- isBusy: true,
- text: updateMessage.data[0],
- chunks: updateMessage.data[1].chunks,
- });
- break;
- case "complete":
- // 如果是转录完成
- const completeMessage = message as TranscriberCompleteData;
- setTranscript({
- isBusy: false,
- text: completeMessage.data.text,
- chunks: completeMessage.data.chunks,
- });
- setIsBusy(false); // 标记不再忙碌
- break;
- case "initiate":
- // 如果是开始加载模型文件
- setIsModelLoading(true);
- setProgressItems((prev) => [...prev, message]); // 添加进度项
- break;
- case "ready":
- setIsModelLoading(false); // 模型加载完成
- break;
- case "error":
- setIsBusy(false); // 发生错误,转录结束
- alert(
- `${message.data.message} This is most likely because you are using Safari on an M1/M2 Mac. Please try again from Chrome, Firefox, or Edge.\n\nIf this is not the case, please file a bug report.`,
- );
- break;
- case "done":
- // 如果模型加载完成,移除进度项
- setProgressItems((prev) =>
- prev.filter((item) => item.file !== message.file),
- );
- break;
- default:
- // 默认处理其他消息
- break;
- }
- });
复制代码- // 初始化模型相关状态
- const [model, setModel] = useState<string>(Constants.DEFAULT_MODEL); // 当前使用的模型
- const [subtask, setSubtask] = useState<string>(Constants.DEFAULT_SUBTASK); // 当前子任务
- const [quantized, setQuantized] = useState<boolean>(Constants.DEFAULT_QUANTIZED); // 是否使用量化模型
- const [multilingual, setMultilingual] = useState<boolean>(Constants.DEFAULT_MULTILINGUAL); // 是否启用多语言支持
- const [language, setLanguage] = useState<string>(Constants.DEFAULT_LANGUAGE); // 当前语言设置
- // 当输入变化时,重置转录状态
- const onInputChange = useCallback(() => {
- setTranscript(undefined); // 清空当前转录数据
- }, []);
- // 发送音频数据到 Web Worker 进行处理
- const postRequest = useCallback(
- async (audioData: AudioBuffer | undefined) => {
- if (audioData) {
- setTranscript(undefined); // 清空当前转录
- setIsBusy(true); // 设置转录为忙碌状态
- let audio;
- if (audioData.numberOfChannels === 2) {
- // 如果是立体声,合并两个声道为单声道
- const SCALING_FACTOR = Math.sqrt(2);
- let left = audioData.getChannelData(0); // 获取左声道数据
- let right = audioData.getChannelData(1); // 获取右声道数据
- audio = new Float32Array(left.length); // 创建新的单声道数据
- for (let i = 0; i < audioData.length; ++i) {
- audio[i] = SCALING_FACTOR * (left[i] + right[i]) / 2; // 合并并标准化数据
- }
- } else {
- // 如果是单声道音频,直接使用第一个声道
- audio = audioData.getChannelData(0);
- }
- // 向 Web Worker 发送消息,开始处理音频数据
- webWorker.postMessage({
- audio,
- model,
- multilingual,
- quantized,
- subtask: multilingual ? subtask : null, // 如果是多语言,传递子任务
- language: multilingual && language !== "auto" ? language : null, // 语言设置(如果启用了多语言)
- });
- }
- },
- [webWorker, model, multilingual, quantized, subtask, language],
- );
- // 返回转录器对象,暴露相关 API
- const transcriber = useMemo(() => {
- return {
- onInputChange,
- isBusy,
- isModelLoading,
- progressItems,
- start: postRequest,
- output: transcript, // 转录结果
- model,
- setModel,
- multilingual,
- setMultilingual,
- quantized,
- setQuantized,
- subtask,
- setSubtask,
- language,
- setLanguage,
- };
- }, [
- isBusy,
- isModelLoading,
- progressItems,
- postRequest,
- transcript,
- model,
- multilingual,
- quantized,
- subtask,
- language,
- ]);
- return transcriber; // 返回转录器对象
- }
复制代码 CG
- useMemo 和 useCallback 优化
- useMemo:用于确保 transcriber 对象在依赖项(如 isBusy、model 等)变化时进行重新盘算,从而避免不须要的重新渲染。
- useCallback:用于优化 onInputChange 和 postRequest 方法的重渲染性能,确保它们只在依赖项变化时重新创建。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
|