بازگشت به بلاگ
۲۷ مهر ۱۴۰۵Sergei Solod22 دقیقه مطالعه

چگونه با Whisper وWebAssembly تبدیل گفتار به متنِ خصوصی را به وب‌سایت اضافه کنیم

تایپ صوتی را کاملاً در سمت کاربر با Whisper، Transformers.js، ONNX Runtime Web، AudioWorklet و WebAssembly پیاده‌سازی کنید. با بهینه‌سازی عملکرد موبایل و رفع خطاهای محیط اجرا، کارگر و توکن‌ساز که مانع کار کردن تشخیص گفتار می‌شوند آشنا شوید.

تبدیل گفتار به متنWhisperWebAssemblyAudioWorkletJavaScript

اضافه کردن دکمهٔ میکروفون به یک وب‌سایت ساده به نظر می‌رسد: صدای کاربر را بگیرید، واژه‌ها را تشخیص دهید و آن‌ها را در یک کادر متنی قرار دهید. اما پیاده‌سازی قابل‌اعتماد این قابلیت در مرورگرهای دسکتاپ و موبایل، به‌ویژه زمانی که تمام فرایند تشخیص گفتار باید به‌صورت محلی و بدون بارگذاری صدای میکروفون روی سرور انجام شود، در عمل بسیار دشوارتر است.

چالش فقط انتخاب مدل تشخیص گفتار نیست. یک پیاده‌سازی درست باید مجوز میکروفون، دریافت صدا، بازنمونه‌برداری، کارگرهای مرورگر، WebAssembly، راه‌اندازی مدل ONNX، توکن‌سازی، حافظهٔ نهان و اتصال به ورودی متنی برنامه را به‌درستی مدیریت کند.

این مقاله توضیح می‌دهد چگونه با استفاده از OpenAI Whisper، Transformers.js، ONNX Runtime Web، AudioWorklet و یک Web Worker اختصاصی قابلیت کاربردی تبدیل گفتار به متن را با حفظ حریم خصوصی بسازید. همچنین چند خطای واقعی هنگام یکپارچه‌سازی را بررسی می‌کند؛ خطاهایی که می‌توانند حتی پس از دانلود موفق فایل‌های مدل، دکمهٔ میکروفون را در وضعیت بارگذاری نگه دارند.

هدف روشن است: بازدیدکننده دکمهٔ میکروفون را می‌زند، به‌طور طبیعی صحبت می‌کند و متن تشخیص‌داده‌شده را در کادر ورودی موجود می‌بیند. پیش از ارسال نیز می‌تواند نتیجه را بررسی یا ویرایش کند. خود فایل صوتی لازم نیست هرگز از مرورگر خارج شود.

تشخیص گفتار کاملاً سمت کاربر یعنی چه؟

برای افزودن ورودی صوتی به وب‌سایت دو رویکرد اساساً متفاوت وجود دارد.

رویکرد اول از سرویس تشخیص گفتار استفاده می‌کند. مرورگر صدا را دریافت و برای سرویسی راه‌دور ارسال می‌کند و متن تشخیص‌داده‌شده را تحویل می‌گیرد. این روش ممکن است پیاده‌سازی را ساده‌تر کند، اما وابستگی به شبکه، ملاحظات حریم خصوصی، دسترس‌پذیری ارائه‌دهنده و احتمالاً هزینهٔ مبتنی بر میزان استفاده را به همراه دارد.

در رویکرد دوم، استنتاج مستقیماً در مرورگر انجام می‌شود. وب‌سایت مدل تشخیص گفتار و محیط اجرای موردنیازش را دانلود می‌کند. مرورگر صدای میکروفون را می‌گیرد، آن را به نمونه‌های عددی تبدیل می‌کند، مدل را به‌صورت محلی اجرا می‌کند و بدون سرور تشخیص گفتار، متن نهایی را می‌سازد.

این مقاله فقط بر رویکرد دوم تمرکز دارد.

نباید تشخیص گفتار معمول مرورگر را با تشخیص تضمین‌شدهٔ محلی اشتباه گرفت. Web Speech API ممکن است از موتور تشخیص مبتنی بر سرور استفاده کند. بعضی پیاده‌سازی‌های جدیدتر گزینهٔ processLocally را دارند، اما پشتیبانی از آن میان مرورگرها و سیستم‌عامل‌ها متفاوت است. اگر پردازش کاملاً محلی الزامی باشد، نباید از SpeechRecognition عمومی به‌عنوان راه‌حل جایگزین خودکار استفاده کرد.

در عوض، برنامه کل خط لولهٔ استنتاج را کنترل می‌کند.

Microphone
    |
    v
AudioWorklet
    |
    v
Float32 PCM audio
    |
    v
Dedicated Web Worker
    |
    v
16 kHz resampling
    |
    v
Whisper + ONNX Runtime Web
    |
    v
Tokenizer and text decoding
    |
    v
React input state

وب‌سایت همچنان فایل‌های JavaScript، WebAssembly، فایل‌های پیکربندی و وزن‌های مدل را ارائه می‌کند. این‌ها دانلودهای ایستای معمولی هستند. تفاوت در این است که صدای میکروفون به‌جای بارگذاری برای تشخیص، به‌صورت محلی پردازش می‌شود. اگر کاربر بعداً متن حاصل را از طریق یک فرم معمولی ارسال کند، آن اقدام مستقلی در برنامه است.

انتخاب مدل کاربردی: Whisper Tiny یا Base

Whisper خانواده‌ای از مدل‌های چندزبانهٔ تشخیص گفتار است. توانایی تشخیص زبان‌های متعدد با یک مدل، آن را به‌خصوص برای وب‌سایت‌های چندزبانه مناسب می‌کند.

برای پیام‌های صوتی کوتاه، دو مدل توازن مناسبی میان کیفیت تشخیص و مصرف منابع مرورگر دارند: Whisper Tiny و Whisper Base.

مدلحجم تقریبی وزن‌های ONNX انتخاب‌شده با Q8کاربرد پیشنهادی
Whisper Tiny41 MBگزینهٔ محتاطانه برای موبایل و دستگاه‌های کم‌منبع
Whisper Base77 MBگزینهٔ باکیفیت‌تر برای دستگاه‌های توانمند و آزمایش‌شده

این اندازه‌ها مربوط به فایل‌های ONNX انتخاب‌شدهٔ رمزگذار و رمزگشا در خروجی‌های بررسی‌شده‌اند. اندازهٔ تمام فایل‌های پشتیبان را شامل نمی‌شوند و نباید با بیشینهٔ مصرف حافظهٔ مرورگر اشتباه گرفته شوند.

مزیت Whisper Tiny دانلود سریع‌تر، نیاز معمول به توان پردازشی کمتر و سازگاری بیشتر با محیط‌های موبایلی است که توان آن‌ها مشخص نیست. Whisper Base معمولاً دقت تشخیص بهتری دارد، به‌خصوص برای بعضی زبان‌ها که مدل کوچک‌تر در آن‌ها خطاهای بیشتری مرتکب می‌شود.

نباید مدل را فقط بر اساس مقدار RAM فیزیکی انتخاب کرد. ممکن است حافظهٔ در دسترس مرورگر بسیار کمتر از مقداری باشد که مشخصات دستگاه نشان می‌دهد. حافظهٔ مصرف‌شدهٔ صفحه، تخصیص‌های WebAssembly، تنسورهای موقت و فشار حافظهٔ سیستم‌عامل هم اهمیت دارند.

رویکرد محتاطانه این است که در موبایل‌هایی با توان نامشخص از Tiny شروع کنید، Base را برای رده‌های دستگاه واجد شرایط نگه دارید و اگر راه‌اندازی Base ناموفق بود، به Tiny برگردید.

قبل از بارگذاری Tiny باید نمونهٔ ناموفق Base کاملاً آزاد شود. نگه داشتن هر دو مدل در حافظه، هدف این سازوکار جایگزین را از بین می‌برد.

چرا WebAssembly پیش‌فرض مناسبی برای محیط عملیاتی است؟

ONNX Runtime Web اجرای شبکه‌های عصبی در مرورگر را پشتیبانی می‌کند. برای این نوع بار کاری، WebAssembly مسیر عملی استنتاج با CPU را در مرورگرهای اصلی دسکتاپ و موبایل فراهم می‌کند.

WebGPU می‌تواند مدل‌های پشتیبانی‌شده را روی سخت‌افزار مناسب سریع‌تر اجرا کند، اما در دسترس بودنش به معنای کار کردن خط لولهٔ تشخیص گفتار نیست. مرورگری که navigator.gpu را ارائه می‌کند لزوماً تضمین نمی‌کند که ONNX Runtime از آن پلتفرم پشتیبانی کند، گراف خاص Whisper به‌درستی راه‌اندازی شود یا استنتاج تکراری بدون مشکل منابع انجام گیرد.

بنابراین در یک پیاده‌سازی محتاطانه می‌توان WASM را به‌عنوان موتور اجرایی محیط عملیاتی به کار گرفت و WebGPU را پس از آزمون‌های سازگاری، دقت، حافظه و نشست‌های تکراری، جداگانه ارزیابی کرد.

در پیاده‌سازی اولیه، استفاده از WASM تک‌ریسمانی همچنین مانع آن می‌شود که کل وب‌سایت به جداسازی میان مبدأها (cross-origin isolation) و SharedArrayBuffer وابسته شود.

env.backends.onnx.wasm.numThreads = 1;
env.backends.onnx.wasm.proxy = false;

اجرای استنتاج در یک کارگر اختصاصی، محاسبات سنگین را از ریسهٔ اصلی JavaScript دور نگه می‌دارد. هنگام پردازش صوت توسط مدل، کادر ورودی، صفحه‌کلید مجازی، پیمایش و رابط برنامه پاسخ‌گو باقی می‌مانند.

تشخیص گفتار را کاملاً از بارگذاری اولیهٔ صفحه جدا کنید

قابلیت میکروفون نباید برای بازدیدکنندگانی که هرگز از آن استفاده نمی‌کنند هزینهٔ قابل‌توجه شبکه و حافظه ایجاد کند.

Transformers.js را از ابتدا در بستهٔ اولیهٔ برنامه وارد نکنید. هنگام باز شدن صفحه ONNX Runtime را راه‌اندازی نکنید و در زمان بیکاری هم وزن‌های مدل را خودکار دانلود نکنید.

در عوض، اجزای تشخیص را فقط پس از کلیک صریح کاربر روی دکمهٔ میکروفون بارگذاری کنید.

ترتیب مناسب برای نخستین استفاده چنین است:

  1. کلیک روی دکمهٔ میکروفون را دریافت کنید.
  2. با getUserMedia() مجوز میکروفون را درخواست کنید.
  3. اگر مجوز رد شد، بدون دانلود مدل خطا را نشان دهید.
  4. اگر مجوز صادر شد، جریان موقت میکروفون را هنگام آماده شدن مدل آزاد کنید.
  5. کارگر اختصاصی را به‌صورت پویا راه‌اندازی و مدل محلی منتخب را بارگذاری کنید.
  6. منتظر بمانید تا کارگر آماده بودن خط لولهٔ تشخیص را تأیید کند.
  7. اگر کاربر عملیات را لغو نکرده، ضبط را خودکار آغاز کنید.
  8. وضعیت شنیدن را به‌شکلی روشن نشان دهید.

درخواست مجوز باید به تعامل کاربر مربوط باشد. برنامه باید عملیات لغوشده و محدودیت‌های ضبط مخصوص هر مرورگر را هم مدیریت کند، نه اینکه فرض کند همیشه می‌توان بدون اقدام دیگری دوباره ضبط را آغاز کرد.

آماده‌سازی مدل باید وضعیت قابل‌مشاهده‌ای مانند در حال آماده‌سازی ورودی صوتی داشته باشد. نمایش یک نشانگر چرخان بدون تغییر وضعیت معنادار، حتی پس از دانلود موفق مدل، ممکن است این تصور را ایجاد کند که میکروفون خراب است.

پس از راه‌اندازی، می‌توان کارگر را مدت کوتاهی برای دیکتهٔ بعدی آماده نگه داشت. مهلت بیکاری حدود یک دقیقه نقطهٔ شروع معقولی است. پس از آن، کارگر را پایان دهید و مدل داخل حافظه‌اش را آزاد کنید. فایل‌های مدل که در حافظهٔ نهان مرورگر مانده‌اند، می‌توانند در بازدیدهای بعدی دوباره استفاده شوند.

دریافت صدای خام با AudioWorklet

Whisper به نمونه‌های صوتی عددی نیاز دارد. هرچند MediaRecorder برای ضبط فایل‌های صوتی عالی است، استفاده از آن در تشخیص محلی ممکن است یک مرحلهٔ غیرضروری کدگذاری و کدگشایی ایجاد کند.

مسیر پردازشی ساده‌تر این است:

getUserMedia()
    |
    v
AudioContext
    |
    v
AudioWorklet
    |
    v
Float32 PCM buffers
    |
    v
Dedicated Worker

AudioWorklet کد پردازش صوت را بیرون از ریسهٔ اصلی JavaScript برنامه اجرا می‌کند. می‌تواند نمونه‌های PCM را بگیرد، ورودی چندکاناله را به تک‌کاناله تبدیل کند، اندازه‌گیری‌های سادهٔ بلندی صدا را انجام دهد و بافرهای جمع‌شده را به کارگر بفرستد.

راه‌اندازی ساده‌شدهٔ میکروفون به این صورت است:

const stream = await navigator.mediaDevices.getUserMedia({
  audio: {
    channelCount: { ideal: 1 },
    echoCancellation: true,
    noiseSuppression: true,
    autoGainControl: true,
  },
});

const context = new AudioContext();

await context.audioWorklet.addModule(
  '/assets/speech/pcm-worklet.js'
);

const source = context.createMediaStreamSource(stream);
const processor = new AudioWorkletNode(
  context,
  'pcm-capture'
);

source.connect(processor);

// Connect the worklet through an inaudible output path
// if required to keep the audio processing graph active.

محدودیت‌های تعیین‌شده برای میکروفون ترجیح هستند، نه تضمین. مرورگرها ممکن است از الگوریتم‌های متفاوتی برای حذف پژواک، کاهش نویز و تنظیم خودکار بهرهٔ صدا استفاده کنند.

نکتهٔ مهم این است که نرخ نمونه‌برداری میکروفون الزاماً 16 kHz نیست. بسیاری از محیط‌های صوتی از 44.1 kHz یا 48 kHz استفاده می‌کنند. Whisper ورودی 16 kHz می‌خواهد؛ بنابراین نمونه‌های دریافت‌شده باید درست بازنمونه‌برداری شوند.

حذف سادهٔ هر نمونهٔ دوم یا سوم، به‌طور کلی راه‌حل درستی نیست. بازنمونه‌برداری مناسب با فیلتر پایین‌گذر، مانند پیاده‌سازی windowed-sinc، پدیدهٔ هم‌پوشانی فرکانسی را کاهش می‌دهد و هنگام پایین آوردن نرخ نمونه‌برداری، فرکانس‌های مفید گفتار را حفظ می‌کند.

بازنمونه‌برداری را در کارگر اختصاصی انجام دهید، نه در تابع فراخوانی بلادرنگ AudioWorklet.

انتقال بهینهٔ صدا بین ریسه‌های مرورگر

AudioWorklet ممکن است قطعه‌های صوتی کوچک بسیاری تولید کند. فرستادن هر واحد پردازش صوت به‌صورت پیامی جداگانه می‌تواند سربار غیرضروری ایجاد کند.

یک راهکار عملی، جمع کردن دسته‌های کوتاه PCM و انتقال هر دسته در قالب ArrayBuffer است.

worker.postMessage(
  {
    type: 'audio-chunk',
    buffer,
  },
  [buffer]
);

فهرست انتقال مهم است: مالکیت ArrayBuffer را منتقل می‌کند، به‌جای اینکه نسخهٔ کامل و غیرضروری از آن بسازد.

کارگر می‌تواند این قطعه‌ها را به انبارهٔ صوتی اضافه کند و هنگام توقف ضبط، آن‌ها را با هم ترکیب کند.

ثبت نرخ نمونه‌برداری واقعی، تعداد نمونه‌های گردآوری‌شده و مدت ضبط نیز مفید است. این فراداده‌ها بدون ثبت داده‌های حساس صوتی، به عیب‌یابی ضبط‌های بی‌صدا کمک می‌کنند.

تشخیص مکث‌ها بدون افزودن شبکهٔ عصبی دیگر

دیکتهٔ پیام‌های متنی کوتاه لزوماً به آشکارساز عصبی فعالیت صوتی نیاز ندارد.

یک آشکارساز تطبیقی RMS می‌تواند تخمینی سبک از فعالیت گفتار ارائه دهد. RMS دامنهٔ سیگنال را اندازه می‌گیرد و به برنامه کمک می‌کند تشخیص دهد گوینده‌ای که پیش‌تر صحبت می‌کرد، چه زمانی ساکت شده است.

رابط کاربری باید همیشه امکان توقف دستی با کلیک دوباره روی میکروفون را فراهم کند. تشخیص خودکار سکوت یک قابلیت رفاهی است، نه شرط موفقیت تشخیص گفتار.

مقادیر اولیهٔ معقول برای دیکتهٔ کوتاه عبارت‌اند از:

پارامترمقدار آغازین
فاصلهٔ تحلیل20 ms
کالیبراسیون نویز300 ms
تأیید گفتار140 ms
سکوت تا توقف خودکار1.4 ثانیه
مهلت نبود گفتار8 ثانیه
بیشینهٔ مدت ضبط30 ثانیه

این‌ها نقطهٔ شروع پیکربندی‌اند، نه آستانه‌های بهینهٔ همگانی. گفتار آرام، موسیقی پس‌زمینه و پردازش نویز میکروفون می‌توانند بر اندازه‌گیری‌های RMS اثر بگذارند.

بهتر است کاربر خودش ضبط را متوقف کند تا اینکه برنامه به‌طور خودکار واژه‌های آخر جمله را قطع کند.

برای ورودی متنی کوتاه معمولی، تبدیل یک‌بارهٔ گفتار به متن غالباً ساده‌تر از اجرای مکرر Whisper برای نتایج موقت است. عبارت را ضبط کنید، ضبط را متوقف کنید، مدل را یک بار اجرا کنید و متن نهایی را در ورودی قرار دهید.

خط لولهٔ کامل Whisper به چیزی فراتر از مدل ONNX نیاز دارد

این یکی از مهم‌ترین جزئیات پیاده‌سازی است.

استنتاج Whisper فقط فراخوانی رمزگذار ONNX و سپس رمزگشای ONNX نیست. خط لولهٔ کامل تشخیص خودکار گفتار به استخراج ویژگی‌های صوتی، توکن‌سازی، پیکربندی تولید خروجی و تبدیل توکن‌های پیش‌بینی‌شده به متن خوانا هم نیاز دارد.

زنجیرهٔ کامل پردازش چنین است:

Raw microphone PCM
        |
        v
Resampling to 16 kHz
        |
        v
Whisper feature extractor
        |
        v
ONNX encoder
        |
        v
ONNX decoder and token generation
        |
        v
Whisper tokenizer
        |
        v
Recognized text

راه‌اندازی موفق نشست ONNX فقط ثابت می‌کند که گراف مدل قابل‌بارگذاری است؛ ثابت نمی‌کند کل خط لولهٔ تشخیص کار می‌کند.

در یکی از موارد آزمایش‌شدهٔ یکپارچه‌سازی Transformers.js، نشست‌های مدل راه‌اندازی می‌شدند، اما تبدیل گفتار به متن همچنان شکست می‌خورد؛ زیرا پردازشگر و توکن‌ساز موردنیاز در خط لوله‌ای که به‌صورت محلی ساخته شده بود درست پیکربندی نشده بودند.

پیاده‌سازی سازگار، این اجزا را با استفاده از فرادادهٔ محلی مدل به‌صورت صریح راه‌اندازی کرد.

نمونهٔ زیر توضیح خلاصه‌ای از این رویکرد وابسته به نسخه است، نه یک کارگر مستقل و کامل:

import {
  WhisperTokenizer,
  WhisperFeatureExtractor,
  WhisperProcessor,
} from '@huggingface/transformers';

// These values come from local model configuration files.
const tokenizer = new WhisperTokenizer(
  tokenizerJson,
  tokenizerConfig
);

const featureExtractor = new WhisperFeatureExtractor(
  preprocessorConfig
);

const processor = new WhisperProcessor(
  {},
  {
    tokenizer,
    feature_extractor: featureExtractor,
  },
  ''
);

// In the affected version-specific integration:
recognizer.tokenizer = tokenizer;
recognizer.processor = processor;

امضای دقیق سازنده‌ها و روش یکپارچه‌سازی باید با نسخهٔ نصب‌شدهٔ Transformers.js بررسی شود. بعضی نسخه‌ها و خروجی‌های مدل می‌توانند این اجزا را خودکار بسازند؛ نباید ساخت دستی آن‌ها را الزامی همگانی دانست.

پس از آماده شدن خط لولهٔ کامل، می‌توان تبدیل گفتار کوتاه به متن را با مشخص کردن صریح زبان و نوع کار اجرا کرد:

const result = await recognizer(audio16k, {
  language: 'en',
  task: 'transcribe',
  return_timestamps: false,
});

وقتی برنامه از قبل زبان کاربر را می‌داند، ارسال صریح آن از تشخیص زبان غیرضروری جلوگیری می‌کند و ممکن است نتیجهٔ ضبط‌های کوتاه را پیش‌بینی‌پذیرتر کند.

نسخه‌های سازگار Transformers.js و ONNX Runtime را ثابت نگه دارید

دومین نوع مهم خطا به رابطهٔ بین Transformers.js و ONNX Runtime Web مربوط است.

Transformers.js خط لولهٔ تشخیص در سطح بالاتر را فراهم می‌کند، در حالی که ONNX Runtime Web گراف‌های شبکهٔ عصبی را اجرا می‌کند. این بسته‌ها باید هم با یکدیگر و هم با خروجی‌های دقیق مدل ONNX که استفاده می‌شوند سازگار باشند.

در پیاده‌سازی بررسی‌شده، نسخهٔ توسعه‌ای ONNX Runtime Web رمزگذار Whisper را راه‌اندازی کرد، اما هنگام ساخت نشست رمزگشای کوانتیزه‌شده شکست خورد. خطا به نبود مقیاس کوانتیزاسیون در عملیات DequantizeLinear مربوط بود.

این مشکل از مجوز میکروفون، دانلود یا قالب صوت نبود. مرورگر فایل‌های مدل را دریافت می‌کرد، اما محیط اجرا نمی‌توانست یکی از گراف‌های ضروری مدل را راه‌اندازی کند.

آزمایش مستقیم رمزگذار و رمزگشا با نسخهٔ پایدار و ثابت‌شدهٔ ONNX Runtime Web، همان ناسازگاری مشخص را برطرف کرد.

آزمون سازگاری به‌ویژه ارزشمند بود، چون دو ادعای متفاوت را از هم جدا کرد:

  • فایل مدل با موفقیت دانلود شده است.
  • محیط اجرا توانسته از فایل مدل یک نشست استنتاج قابل‌استفاده بسازد.

این دو یکسان نیستند.

برای خروجی‌های مدلِ آزمایش‌شده، ترکیب سازگار از Transformers.js 4.2.0 و ONNX Runtime Web 1.24.3 استفاده می‌کرد. یک محیط اجرای توسعه‌ای جدیدتر روی گراف رمزگشای کوانتیزه‌شده شکست خورد. این مشاهده مختص نسخه‌های مورد آزمایش است و نشان نمی‌دهد نسخه‌های قدیمی‌تر همیشه بهترند.

هنگام بازنویسی نسخهٔ یک وابستگی غیرمستقیمِ محیط اجرا، نسخهٔ دقیق را در فایل قفل بسته‌ها ثابت کنید و دلیل این کار را بنویسید. ارتقای محیط اجرا باید به آزمون کامل بازگشت خطا در استنتاج مرورگر نیاز داشته باشد، نه صرفاً نصب موفق بسته.

پیش از عیب‌یابی Whisper، قالب بستهٔ کارگر را بررسی کنید

یک JavaScript Web Worker را می‌توان با قالب‌های ماژولی متفاوت ساخت.

در یکی از خطاهای یکپارچه‌سازی، برنامه کارگر را به‌صورت ماژول ES ایجاد می‌کرد، در حالی که بستهٔ تولیدشدهٔ کارگر از سازوکار کارگر کلاسیک importScripts() استفاده می‌کرد.

این دو مدل اجرا قابل‌جایگزینی نیستند. یک کارگر ماژولی نمی‌تواند به‌سادگی کدی را اجرا کند که مخصوص کارگر کلاسیک نوشته شده است.

نتیجهٔ عملی این است که سازندهٔ Worker را با خروجی واقعی بسته‌بندی تطبیق دهید، نه اینکه گزینهٔ سازنده را از آموزشی عمومی کپی کنید.

برای مثال، گزینه‌های زیر لزوماً معادل نیستند:

// Module worker: requires a compatible module bundle.
new Worker(workerUrl, { type: 'module' });

// Classic worker: requires a compatible classic bundle.
new Worker(workerUrl);

انتخاب درست به ابزار بسته‌بندی و JavaScript تولیدشده بستگی دارد.

هنگام عیب‌یابی خطای راه‌اندازی کارگر، Console مرورگر، فایل تولیدشدهٔ کارگر و پنل Network را بررسی کنید. مشخص کنید آیا خطا حتی پیش از شروع راه‌اندازی ONNX Runtime رخ می‌دهد یا نه.

بعضی کتابخانه‌های بسته‌بندی‌شدهٔ مرورگر نشانی‌های URL را نیز نسبت به محل ماژول خود محاسبه می‌کنند. هنگام قرار دادن آن‌ها در کارگرها، مسیرهای نهایی فایل‌های محیط اجرا را آزمایش کنید و فرض نکنید نشانی‌های نسبیِ بستهٔ اصلی همچنان درست کار می‌کنند.

ارائهٔ ماژول‌های WASM وJavaScript با نوع MIME درست

حتی فایل‌های JavaScript و مدل‌های ONNX کاملاً سازگار نیز ممکن است در صورت ارسال سرآیندهای پاسخ نادرست از وب‌سرور شکست بخورند.

در یک خطای واقعی، ماژول JavaScript با پسوند .mjs با نوع محتوای application/octet-stream ارائه می‌شد. مرورگر آن را رد کرد، چون نوع پاسخ برای اجرای ماژول JavaScript مناسب نبود.

نوع پاسخ معمولاً باید به‌شکل زیر باشد:

پسوند فایلContent-Type
.mjsapplication/javascript
.wasmapplication/wasm

پاسخ واقعی سرور در محیط عملیاتی را بررسی کنید، نه فقط فایل پیکربندی سرور را.

curl -I https://example.com/runtime/speech/v2/runtime.mjs
curl -I https://example.com/runtime/speech/v2/runtime.wasm

نشانی‌های URL بالا صرفاً نمونه‌اند. نام فایل‌های واقعیِ نسخه‌دار را از ساخت محیط اجرای انتخاب‌شده به کار ببرید.

درست بودن کد وضعیت HTTP به‌تنهایی کافی نیست. مرورگر همچنان ممکن است فایل را به دلیل نوع MIME، سیاست امنیت محتوا (Content Security Policy)، شیوهٔ اجرای ماژول یا نشانی URL نادرست رد کند.

فراموش نکنید درخواست یکسان ممکن است در زبانهٔ مرورگر به‌صورت مستقیم موفق باشد، اما هنگام بارگذاری به‌عنوان ماژول اجرایی یا وابستگی WebAssembly شکست بخورد.

استفاده از نشانی‌های تغییرناپذیر بدون گرفتار کردن کاربران با فایل‌های خراب محیط اجرا

فایل‌های مدل Whisper آن‌قدر بزرگ هستند که کش مرورگر اهمیت پیدا کند.

مسیرهای تغییرناپذیر و نسخه‌دار اجازه می‌دهند مرورگر بدون دانلود دوباره در هر نوبت صحبت کاربر، از فایل‌های مدل و محیط اجرایی که تغییر نکرده‌اند استفاده کند.

یک سیاست معمول کش چنین است:

Cache-Control: public, max-age=31536000, immutable

اما این کار شرطی برای استقرار ایجاد می‌کند: وقتی یک URL تغییرناپذیر اعلام شد، نباید محتوایش بی‌سروصدا عوض شود.

اگر فایل باینری محیط اجرا باید تغییر کند، آن را با URL نسخه‌دار جدید منتشر کنید.

/models/speech/v1/
/runtime/speech/v1/
/runtime/speech/v2/

هدف تغییر مسیر محیط اجرا صرفاً ظاهری نیست. اگر برنامه پس از تغییر محتوا دوباره از URL تغییرناپذیر پیشین استفاده کند، مرورگر ممکن است همچنان فایل باینری WebAssembly ناسازگار و قدیمیِ کش‌شده را اجرا کند.

بهتر است فایل‌های واسط JavaScript و فایل‌های WebAssembly نیز از یک توزیع یکسان محیط اجرا باشند. مخلوط کردن فایل‌های نسخه‌های مختلف ممکن است خطاهای راه‌اندازی ایجاد کند که عیب‌یابی آن‌ها دشوار است.

فهرست فایل‌هایی که هنگام ساخت تولید می‌شود و نام، اندازه و چک‌سام رمزنگاری‌شدهٔ فایل‌های مورد انتظار را دارد، می‌تواند پیش از استقرار فایل‌های مفقود یا ناسازگار را پیدا کند.

کتابخانهٔ تشخیص را طوری پیکربندی کنید که از فایل‌های میزبانی‌شده توسط خود سایت استفاده کند و بارگذاری مدل از راه دور را غیرفعال کنید. در این صورت نبود مدل محلی، به‌جای شروع دانلود پیش‌بینی‌نشده از میزبان خارجی مدل، خطایی روشن ایجاد می‌کند.

چرا نشانگر میکروفون می‌چرخد اما هیچ‌چیز کار نمی‌کند؟

یکی از نشانه‌های رایج هنگام توسعه این است که دکمهٔ میکروفون مجوز می‌خواهد، انیمیشن بارگذاری نشان می‌دهد و در نهایت بدون وارد کردن متن به وضعیت خطا برمی‌گردد.

مرورگر حتی ممکن است نشان دهد که میکروفون در حال استفاده است.

این موضوع کار کردن تشخیص گفتار را ثابت نمی‌کند؛ فقط نشان می‌دهد در مقطعی اجازهٔ دریافت صدای میکروفون داده شده یا دریافت صدا فعال بوده است.

چند خطای مستقل می‌توانند نشانه‌هایی تقریباً یکسان ایجاد کنند:

  1. نوع MIME نادرست ماژول JavaScript. مرورگر ماژول محیط اجرا را دانلود می‌کند، اما از اجرای آن خودداری می‌کند.
  2. ناسازگاری قالب اجرای کارگر. کارگر کامپایل‌شده از APIهای کارگر کلاسیک استفاده می‌کند اما به‌صورت کارگر ماژولی اجرا می‌شود، یا برعکس.
  3. ناسازگاری رمزگشای ONNX. محیط اجرا نشست رمزگذار را ایجاد می‌کند، اما روی گراف رمزگشای کوانتیزه‌شده شکست می‌خورد.
  4. خط لولهٔ تشخیص ناقص. نشست‌های ONNX وجود دارند، اما توکن‌ساز یا پردازشگر صوت درست راه‌اندازی نشده است.

این مشکلات باید مستقل از یکدیگر عیب‌یابی شوند. رفع یکی ممکن است مشکل بعدی را آشکار کند.

مهم‌ترین اصل عیب‌یابی این است که HTTP 200 به معنای راه‌اندازی موفق مدل نیست و راه‌اندازی موفق مدل هم به معنای تبدیل موفق گفتار به متن نیست.

حتی تبدیل موفق گفتار به متن نیز ثابت نمی‌کند نتیجه به کادر متنی واقعی برنامه رسیده است.

هر مرز میان مراحل به یک علامت تشخیصی معنادار نیاز دارد.

افزودن مشاهده‌پذیری بدون جمع‌آوری گفتار خصوصی

برای ثبت اطلاعات عیب‌یابی مفید، نیازی به ذخیرهٔ گفته‌های کاربر نیست.

برنامه می‌تواند وضعیت‌های فنی زیر را ثبت کند:

permission-granted
worker-created
runtime-loading
runtime-ready
encoder-ready
decoder-ready
recording-started
pcm-received
recording-stopped
inference-started
inference-completed
result-inserted

هنگام بروز خطا، مرحلهٔ وقوع، کد خطای فنی ثابت و فرادادهٔ پاک‌سازی‌شدهٔ خطا را ثبت کنید.

خطای راه‌اندازی مدل باید از رد مجوز میکروفون یا ضبط خالی قابل‌تشخیص باشد. خطای کارگر نیز باید در محیط توسعه دیده شود، نه اینکه مدیریت‌کنندهٔ عمومی خطا آن را پنهان کند.

نمونه‌های صدای میکروفون، جمله‌های تشخیص‌داده‌شده، دنبالهٔ توکن‌ها یا هیچ محتوای گفتاری دیگری را ثبت نکنید. داده‌های سنجش عملکرد می‌توانند فقط شامل بازه‌های زمانی، ردهٔ مدل انتخابی، زمان راه‌اندازی، زمان استنتاج و دسته‌های خطا باشند.

این کار عیب‌یابی میکروفون ازکارافتاده را بسیار ساده‌تر می‌کند، بدون آنکه الزامات حریم خصوصی تضعیف شوند.

قرار دادن متن تشخیص‌داده‌شده با استفاده از وضعیت React

تشخیص گفتار فقط زمانی مفید است که متن نهایی به کادر فرمِ موجود برسد.

در یک برنامهٔ React، این کادر اغلب با وضعیت مؤلفه کنترل می‌شود. مقداردهی مستقیم به input.value ممکن است با شیوهٔ رندر چارچوب کاری تداخل پیدا کند.

در عوض، متن تشخیص‌داده‌شده باید از همان مسیر به‌روزرسانی وضعیت عبور کند که تایپ معمولی از آن استفاده می‌کند.

یکپارچه‌سازی باید رفتار ناهم‌زمان را هم در نظر بگیرد. ممکن است تشخیص زمانی تمام شود که کاربر حروف بیشتری نوشته یا مکان‌نما را جابه‌جا کرده است.

استفاده از مقدار قدیمی ورودی که هنگام آغاز ضبط ذخیره شده، ممکن است ناخواسته ویرایش‌های جدیدتر را پاک کند.

الگوی ساده‌شدهٔ یکپارچه‌سازی این است:

const insertion = insertTranscript({
  currentValue: latestValue,
  transcript: recognizedText,
  selection: currentSelection,
  maxLength: inputLimit,
});

setMessage(insertion.value);
restoreCaret(insertion.caret);

تابع کمکی درج متن باید متن فعلی را حفظ کند، در صورت نیاز بخش انتخاب‌شده را جایگزین کند، فاصله‌گذاری مناسب انجام دهد و حداکثر طول کادر را رعایت کند.

Unicode نیازمند توجه است، چون طول رشته در JavaScript با واحدهای کد UTF-16 شمرده می‌شود. بریدن رشته از موقعیتی دلخواه ممکن است یک جفت جانشین (surrogate pair) را از هم جدا کند.

مهم‌تر از همه، تشخیص گفتار هرگز نباید فرم را خودکار ارسال کند. کاربر باید بتواند متن را پیش از ارسال بررسی و اصلاح کند.

مدیریت خطاها بدون مختل کردن ورودی متنی معمولی

زیرسامانهٔ گفتار باید اختیاری باشد.

اگر مجوز میکروفون رد شود، مرورگر نتواند حافظهٔ مدل را تخصیص دهد، کارگر از کار بیفتد یا ضبط شامل گفتار نباشد، کادر ورودی معمولی باید همچنان کار کند.

چرخهٔ عمر میکروفون را با وضعیت‌های صریحی مانند موارد زیر نمایش دهید:

idle
preparing
listening
recognizing
error

از برچسب‌های دسترس‌پذیر و بازخورد واضح وضعیت استفاده کنید. میکروفونی که پس از بارگذاری صرفاً قرمز یا صورتی می‌شود، تقریباً هیچ اطلاعاتی به کاربر نمی‌دهد.

لغو عملیات باید مسیرهای صدا، محیط صوتی، بافرهای معلق و نشست ضبط فعال را پاک‌سازی کند. رابط کاربری همچنین باید مانع شود که نتیجهٔ قدیمیِ ضبط لغوشده، وضعیت جدیدتر ورودی را بازنویسی کند.

اگر مدل بزرگ به دلیل محدودیت حافظه یا راه‌اندازی شکست خورد، پیش از امتحان مدل کوچک‌تر آن را کاملاً آزاد کنید. تعداد تلاش‌های مجدد را محدود کنید تا مرورگر بارها مدل‌هایی را که قادر به اجرا نیستند دانلود یا راه‌اندازی نکند.

اگر تشخیص محلی شکست خورد، بی‌اطلاع کاربر به سرویس ابری تشخیص گفتار منتقل نشوید. اگر پردازش صرفاً محلی الزامی است، شکست تشخیص محلی نیز باید همان‌جا پایان یابد.

آزمایش کل خط لوله در مرورگر

موفقیت بررسی TypeScript یا ساخت نسخهٔ نهایی به‌تنهایی ثابت نمی‌کند ورودی صوتی کار می‌کند.

همچنین اینکه مرورگر بتواند فایل‌های ONNX را دانلود کند، موفقیت راه‌اندازی هر دو نشست مدل را ثابت نمی‌کند.

مفیدترین آزمون بازگشت خطا باید همان کارگر، فایل‌های محیط اجرا، مدل، پردازشگر و توکن‌سازی را به کار بگیرد که در محیط عملیاتی استفاده می‌شوند.

یک ضبط کوتاه PCM با محتوای مشخص را به مسیر واقعی استنتاج در مرورگر بدهید و بررسی کنید متنی غیرخالی برمی‌گردد.

سپس تعامل کامل میکروفون را در یک مرورگر واقعی آزمایش کنید.

  • بارگذاری اولیهٔ صفحه: مطمئن شوید پیش از استفاده از میکروفون، هیچ مدل گفتار یا فایل محیط اجرا دانلود نمی‌شود.
  • راه‌اندازی سرد: مجوز میکروفون، درخواست‌های مدل، نوع MIME فایل‌های JavaScript و WASM و پاسخ واقعیِ آماده بودن کارگر را بررسی کنید.
  • راه‌اندازی ONNX: مطمئن شوید نشست‌های رمزگذار و رمزگشا هر دو ساخته می‌شوند.
  • دریافت صدا: بررسی کنید AudioWorklet بافرهای PCM غیرخالی تولید می‌کند.
  • بازنمونه‌برداری: اطمینان یابید نرخ واقعی نمونه‌برداری به کارگر می‌رسد و صدای معتبر 16 kHz تولید می‌شود.
  • توقف دستی: مطمئن شوید کلیک دوم روی میکروفون ضبط را با اطمینان متوقف می‌کند.
  • توقف خودکار: بررسی کنید مکث‌های طبیعی ضبط را بدون قطع گفتار آرام پایان می‌دهند.
  • تبدیل گفتار به متن: اطمینان یابید پردازشگر، مدل و توکن‌ساز در کنار هم متن قابل‌تشخیص تولید می‌کنند.
  • اتصال به ورودی: بررسی کنید متن تشخیص‌داده‌شده در همان کادر کنترل‌شدهٔ واقعی قرار می‌گیرد و ویرایش‌های کاربر را از بین نمی‌برد.
  • بازیابی پس از خطا: رد مجوز، نبود فایل‌ها، خطاهای کارگر، شکست راه‌اندازی مدل، لغو و بازگشت به مدل کوچک‌تر را آزمایش کنید.
  • حریم خصوصی: درخواست‌های شبکه را بررسی کنید و مطمئن شوید صدای میکروفون به سرور یا سرویس تحلیل داده بارگذاری نمی‌شود.
  • سازگاری با موبایل: آزمایش‌ها را روی گوشی‌های واقعی، شامل راه‌اندازی سرد، راه‌اندازی مجدد سریع و نشست‌های تکراری دیکته انجام دهید.

آزمون‌های PCM ضبط‌شده برای بررسی اجزای استنتاج عالی‌اند، اما کار کردن دریافت صدا از میکروفون را ثابت نمی‌کنند. آزمون پذیرش نهایی باید شامل میکروفون واقعی و رابط کاربری واقعی باشد.

معماری عملی برای تایپ صوتی قابل‌اعتماد در مرورگر

برای وب‌سایتی که به ورودی صوتی کوتاه، چندزبانه و حافظ حریم خصوصی نیاز دارد، عملی‌ترین معماری عمداً ساده است:

React microphone button
        |
        v
Permission request
        |
        v
Lazy model initialization
        |
        v
AudioWorklet PCM capture
        |
        v
Adaptive RMS / manual stop
        |
        v
Dedicated Worker
        |
        v
16 kHz audio resampling
        |
        v
Whisper Tiny or Base Q8
        |
        v
Compatible ONNX Runtime WASM
        |
        v
Whisper processor + tokenizer
        |
        v
Final transcript
        |
        v
Controlled React input

برنامه باید فایل‌های نسخه‌دار مدل و محیط اجرا را روی زیرساخت خودش میزبانی کند، استنتاج را از ریسهٔ اصلی دور نگه دارد، از دانلود غیرضروری مدل‌ها بپرهیزد، منابع را پس از استفاده آزاد کند و هرگز صدای میکروفون را برای تشخیص روی سرور بارگذاری نکند.

گزینه‌های پیچیده‌تر، مانند شتاب‌دهی WebGPU، تشخیص عصبی فعالیت گفتار، تبدیل پیوستهٔ بخش‌های میانی به متن یا رده‌های بیشتر مدل را می‌توان بعداً و زمانی بررسی کرد که اندازه‌گیری‌ها چنین پیچیدگی‌ای را توجیه کنند.

ارزشمندترین درس، نسخهٔ یک کتابخانه یا اندازهٔ یک مدل خاص نیست؛ بلکه اهمیت آزمایش تمام مرزهای خط لولهٔ تشخیص گفتار در مرورگر است.

ظاهر شدن موفق درخواست مجوز میکروفون به معنای تبدیل صدا به متن نیست. دانلود شدن مدل به معنای راه‌اندازی رمزگشای آن نیست. نشست موفق ONNX هم به معنای آماده بودن توکن‌ساز نیست. حتی تولید موفق متن ثابت نمی‌کند که برنامه آن را درست در ورودی قرار داده است.

این قابلیت فقط زمانی کامل است که بازدیدکننده بتواند دکمهٔ میکروفون را بزند، طبیعی صحبت کند، ضبط را متوقف کند و واژه‌های تشخیص‌داده‌شده را در کادر قابل‌ویرایش ببیند، بدون اینکه ضبط را به سرور تشخیص گفتار بفرستد.