دموهای هوش مصنوعی همهجا هستند. یک اسکریپت ساده Python میتواند چهرهای را در یک عکس ثابت عوض کند و نتیجه خیرهکننده به نظر میرسد. اما ساختن چیزی که کاربران واقعی بتوانند واقعاً در آن فایل آپلود کنند، از آن صرفنظر کنند و بعداً دوباره برگردند؟ این یک کار کاملاً متفاوت است. من اخیراً یک ابزار تحت وب ساختم که یک GIF متحرک و یک چهره مرجع را میگیرد و سپس همان انیمیشن را با چهرهای که در تمام فریمها جایگزین شده است، برمیگرداند. مدل بخش اصلی پردازشهای بصری را انجام میدهد، اما تلاش مهندسی واقعی صرف معماری پیرامون آن شده است: زنده نگه داشتن اتصالات، مقاومت در برابر رفرش شدن صفحه، و اطمینان از اینکه یک عملیات inference دو دقیقهای در خطای 504 Gateway Timeout ناپدید نشود.
این همان شکاف بین یک پروتوتایپ و یک محصول است. مهندسان عاشق صحبت درباره معماریهای diffusion و پارامترهای inference هستند. اما وقتی کاربر یک GIF سنگین با دویست فریم آپلود میکند، اگر تب مرورگر بعد از سی ثانیه از کار بیفتد، هیچکس به مدل شما اهمیت نمیدهد. زیرساخت پیرامون inference به اندازه خودِ inference اهمیت دارد.
مشکل انتظار
معماری استاندارد وب بر پاسخهای سریع استوار است. کاربر روی دکمهای کلیک میکند، سرور پاسخ میدهد و صفحه بهروز میشود. تعویض چهره در دهها فریم GIF بلافاصله این فرض را از بین میبرد. مدل روی GPUهای Replicate اجرا میشود، نه روی سرور من، و پردازش یک GIF طولانی میتواند به راحتی یک دقیقه یا بیشتر زمان ببرد. اگر سعی کنید یک درخواست HTTP را تا این مدت باز نگه دارید، خودتان را به دردسر انداختهاید. لود بالانسرها اتصالات بیکار را قطع میکنند. مرورگرها فرض میکنند شبکه قطع شده و دوباره تلاش میکنند. کاربران به یک spinner یخزده خیره میشوند و تصور میکنند اپلیکیشن خراب شده است.
من با جدا کردن فرآیند ارسال از دریافت نتیجه، کاملاً از این مشکل دوری کردم. وقتی کاربر یک GIF و یک تصویر چهره را آپلود میکند، بکاند Next.js من یک prediction را در Replicate شروع کرده و بلافاصله یک job ID برمیگرداند. کاربر تأییدیه فوری دریافت میکند. نتیجه واقعی، پس از اتمام کار GPU، از طریق یک webhook ارسال میشود. این الگو چیز عجیبی نیست، اما برای کارهای رسانهای طولانیمدت کاملاً ضروری است. این کار یک انتظار غیرقابلپیشبینی را به یک دستدادن مطمئن تبدیل میکند: کار پذیرفته شده است و وقتی تمام شد به شما اطلاع داده خواهد شد.
ساخت یک ماشین وضعیت (State Machine) قابل اعتماد
وقتی به سراغ روش ناهمگام (asynchronous) میروید، به قابلیت مشاهده (visibility) نیاز دارید. کاربران صفحه را رفرش میکنند. تب را میبندند و دوباره باز میکنند. لینک را کپی میکنند و برای همکارشان میفرستند که دو ساعت بعد آن را چک میکند. بدون داشتن یک سابقه ماندگار از آنچه اتفاق افتاده، با هرجومرج روبرو خواهید بود.
من از Supabase به عنوان تنها منبع حقیقت (single source of truth) استفاده کردم. هر آپلود یک ردیف با یک job ID منحصربهفرد ایجاد میکند و آن ردیف از میان وضعیتهای مشخصی عبور میکند: queued، processing، succeeded، failed، یا expired. وقتی کاربر برای اولین بار روی submit کلیک میکند، ردیف در حالت queued قرار میگیرد. لحظهای که Replicate پیشبینی را میپذیرد، وضعیت به processing تغییر میکند. webhook آن را به succeeded یا failed تغییر میدهد. من وضعیت expired را برای کارهایی که بدون دریافت callback خیلی طولانی میمانند اضافه کردم تا سیستم تا ابد به دنبال سرابها نگردد.
فرانتاند که با TypeScript ساخته شده، در فواصل زمانی کوتاه Supabase را polling میکند و هر آنچه وضعیت فعلی میگوید را رندر میکند. این روش polling ابتدایی به نظر میرسد، اما مشکل رفرش شدن را کاملاً حل میکند. کاربر میتواند لپتاپ خود را ببندد، فردا آن را باز کند و دقیقاً ببیند کار در چه مرحلهای است، زیرا پایگاه داده — و نه حافظه مرورگر — مالک پیشرفت کار است. Supabase همچنین اعتبارها (credits) را ردیابی میکند، بنابراین حسابداری با همان رکورد کاری که وضعیت را دنبال میکند، مرتبط میماند. همه چیز در یک جا قرار دارد.
مدیریت فایلها بدون فشار آوردن به مرورگر
فایلهای GIF سنگینتر از آن چیزی هستند که مردم فکر میکنند. فایلی که اندازه آن برای خط لوله (pipeline) هوش مصنوعی کاملاً مناسب است، ممکن است همچنان چندین مگابایت وزن داشته باشد. رندر کردن آن به عنوان یک پیشنمایش تکرار شونده در داخل مرورگر، عملکرد را مختل میکند، به خصوص در دستگاههای ضعیفتر. من به دو خط لوله فایل کاملاً مجزا نیاز داشتم: یکی برای هوش مصنوعی و دیگری برای رابط کاربری.
برای پیشنمایشها، من GIFهای بزرگ را با استفاده از FFmpeg که به WebAssembly کامپایل شده است، به WebP متحرک تبدیل میکنم. این کار کاملاً در سمت کلاینت و در مرورگر انجام میشود. نتیجه، یک پیشنمایش سبک است که بدون دست زدن به فایل اصلی، رابط کاربری را سریع و روان نگه میدارد. GIF ارسال شده به Replicate دستنخورده باقی میماند. این جداسازی اهمیت دارد. شما نمیخواهید آرتیفکتهای فشردهسازیِ یک پیشنمایش به دادههای آموزشی یا رندر نهایی شما نفوذ کند، و نمیخواهید رابط کاربری در حین فکر کردن هوش مصنوعی، روی یک فایل چند مگابایتی متوقف شود.
FFmpeg WASM همچنین وظایف دیگر مربوط به GIF را قبل از اینکه آپلود از مرورگر خارج شود، انجام میدهد. من از آن برای تجزیه تعداد فریمها، اعتبارسنجی ابعاد و شناسایی زودهنگام فایلهای خراب استفاده میکنم. شناسایی مشکل قبل از اینکه اعتبار GPU را مصرف کند، هم در هزینهها و هم در صبر کاربر صرفهجویی میکند.
تبدیل یک دموی اولیه به نرمافزاری که مردم به آن اعتماد میکنند
درس بزرگتری در اینجا نهفته است که تقریباً برای هر ابزار هوش مصنوعی مولد در بازار صدق میکند. خودِ مدل ممکن است تنها سی درصد از کار باشد. هفتاد درصد باقیمانده، زیرساختهای فنی و کمزرقوبرقی است که کسی دربارهشان توییت نمیزند: بازیابی وضعیت (state recovery)، امضاهای وبهوک (webhook signatures)، تبدیل فایل، ردیابی اعتبار و مدیریت هوشمندانه خطا (graceful failure).
پشته تکنولوژی (stack) من ساده و حسابشده است. Next.js و TypeScript رابط کاربری و مسیرهای API را مدیریت میکنند. Replicate مدلها را اجرا میکند. Supabase وضعیتها، دادهها و اعتبارها را مدیریت میکند. FFmpeg WASM کارهای رسانهای سمت کاربر را انجام میدهد. Animated WebP باعث میشود رابط کاربری سریع باقی بماند. هر بخش وظیفه مشخصی دارد و آنها بهجای استفاده از درخواستهای شکننده و طولانیمدت، از طریق انتقالهای صریح وضعیت (explicit state transitions) به هم متصل میشوند.
وقتی کاربری اعتبار خود را برای تعویض چهره (face swap) خرج میکند، انتظار قابلیت اطمینان دارد، نه یک مقاله پژوهشی. اگر پیشبینی با خطا مواجه شد، سیستم باید متوجه شود و آن را اعلام کند. اگر کاربر منتظر بماند، باید یک پیشنمایش سبک برای مشاهده داشته باشد و وضعیتی را ببیند که با بستن و باز کردن مجدد مرورگر از بین نرود. این جزئیات وقتی درست کار میکنند نامرئی هستند و وقتی کار نمیکنند، بحرانی خواهند بود.
خودِ تعویض چهره یک ترفند جالب است. اما این ابزار تنها زمانی واقعی به نظر میرسد که کاربر بتواند فایل را آپلود کند، صفحه را ترک کند و بدون از دست دادن روند کار خود، بازگردد. این همان چیزی است که یک فراخوانی API را به نرمافزاری تبدیل میکند که مردم واقعاً به آن اعتماد میکنند.
