<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Roman Solodkyi</title>
    <description>The latest articles on DEV Community by Roman Solodkyi (@roman_solodkyi_a1bb297d8f).</description>
    <link>https://dev.to/roman_solodkyi_a1bb297d8f</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3781852%2F55360229-2db7-4047-9285-479d4c9f9149.jpg</url>
      <title>DEV Community: Roman Solodkyi</title>
      <link>https://dev.to/roman_solodkyi_a1bb297d8f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/roman_solodkyi_a1bb297d8f"/>
    <language>en</language>
    <item>
      <title>Zero-Cost AI: Running LLMs Locally in the Browser</title>
      <dc:creator>Roman Solodkyi</dc:creator>
      <pubDate>Fri, 10 Apr 2026 05:49:27 +0000</pubDate>
      <link>https://dev.to/roman_solodkyi_a1bb297d8f/zero-cost-ai-running-llms-locally-in-the-browser-o8n</link>
      <guid>https://dev.to/roman_solodkyi_a1bb297d8f/zero-cost-ai-running-llms-locally-in-the-browser-o8n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Any application that can be written in JavaScript will eventually be written in JavaScript."&lt;/em&gt; — Atwood's Law&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you think running AI models requires a billion-dollar company and a GPU cluster worth the GDP of a small country, I have good news. All you need is a browser and good old JavaScript! Almost.&lt;/p&gt;

&lt;p&gt;The tools shown in this article make it surprisingly easy to run AI inference offline in just a few lines of code, giving you full control to build or prototype neat features at zero cost.&lt;/p&gt;

&lt;p&gt;The pros on paper:&lt;/p&gt;

&lt;p&gt;🆓 No inference cost - runs on the user's device&lt;br&gt;
🔒 Privacy by default - data never leaves the browser&lt;br&gt;
📵 Offline support - works without a connection&lt;br&gt;
🔓 No vendor lock-in - you own the stack&lt;br&gt;
⚡ Low integration overhead - minimal code to get started&lt;/p&gt;

&lt;p&gt;Of course, there are catches. First, models must be downloaded to run on the user's device, so your AI features won't be available immediately on first page load. They will, however, be cached and ready on later visits.&lt;/p&gt;

&lt;p&gt;Second, not every device is created equal, and not every model can run everywhere. I'll discuss the limitations and how to work around them in detail further.&lt;/p&gt;

&lt;p&gt;Given the initial loading time, it's reasonable to assume a pretty narrow set of use cases. Most importantly, the user should interact with the feature repeatedly over time. It makes no sense to force someone to wait that long for the model to download just to use it once. A better approach is to load it in the background, enabling additional functionality while the user continues to use your app. For SaaS apps, this works naturally: users return, the model stays cached, and AI becomes part of the product rather than a per-request cost.&lt;/p&gt;

&lt;p&gt;Good candidates, in my opinion, include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smart autofill and suggestions&lt;/li&gt;
&lt;li&gt;Content summarization&lt;/li&gt;
&lt;li&gt;In-app Q&amp;amp;A over existing data&lt;/li&gt;
&lt;li&gt;Auto-tagging and classification&lt;/li&gt;
&lt;li&gt;Writing assistance&lt;/li&gt;
&lt;li&gt;Sentiment and intent detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, a quick look at what makes all of this possible: the technologies that power the tools I'll show you, even if you never interact with them directly.&lt;/p&gt;
&lt;h2&gt;
  
  
  Behind the Scenes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;WebGPU&lt;/strong&gt;&lt;br&gt;
A modern API for GPU compute in the browser. Think of it as the next-generation successor to WebGL. Designed to provide high-performance 3D graphics and compute capabilities for modern web browsers. It provides access to modern GPU hardware capabilities, enabling support for AI/machine learning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ONNX&lt;/strong&gt;&lt;br&gt;
A portable cross-platform model format. Instead of being tied to PyTorch or TensorFlow, models can be converted once and run anywhere, including the browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WebAssembly (Wasm)&lt;/strong&gt;&lt;br&gt;
A near-native execution layer for the web. When WebGPU isn't available, Wasm runs models on the CPU - slower, but reliable and widely supported.&lt;/p&gt;

&lt;p&gt;With that out of the way, on to the tools.&lt;/p&gt;
&lt;h2&gt;
  
  
  Swiss Knife: Transformers.js
&lt;/h2&gt;

&lt;p&gt;The easiest entry point is &lt;a href="https://huggingface.co/docs/transformers.js" rel="noopener noreferrer"&gt;Transformers.js&lt;/a&gt; - essentially Hugging Face's Python library, but for JavaScript.&lt;/p&gt;

&lt;p&gt;It uses WebGPU when available and falls back to Wasm (CPU) otherwise, and supports a wide range of tasks: classification, summarization, translation, speech recognition, image classification, and more.&lt;/p&gt;

&lt;p&gt;The core abstraction is the pipeline: load a model, pass input, get output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@huggingface/transformers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;classifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sentiment-analysis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I love how easy it is to run ML models in the browser!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// [{ label: 'POSITIVE', score: 0.9998 }]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl6vcuctbp3lsj94tjmis.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl6vcuctbp3lsj94tjmis.png" alt="Transformers.js - Setniment example" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same approach for a different model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@huggingface/transformers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;classifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zero-shot-classification&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Large language models can now run directly in the browser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;technology&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sports&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;politics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc5ny85vx1q8nvd2dvhpg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc5ny85vx1q8nvd2dvhpg.png" alt="Transformers.js - Zero-shot classification" width="800" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Models are downloaded once from a CDN and then cached in the browser for the next visit. After that, inference is effectively instant.&lt;/p&gt;

&lt;p&gt;For typical NLP tasks, Transformers.js models are generally browser-friendly: lightweight tasks like text embeddings or entity extraction are around 30 MB, sentiment and text classification are closer to 80 MB, while NER sits in a similar range. Larger tasks like summarization, zero-shot classification, speech recognition, or image classification can reach hundreds of megabytes or even gigabytes. You can also run these in Node.js, where file size is less of a concern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;600 MB summarization model example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summarizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;summarization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Xenova/distilbart-cnn-6-6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nx"&gt;summary_text&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;summarizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;longArticle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpaovz466rwlunxmlhcwu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpaovz466rwlunxmlhcwu.png" alt="Transformers.js - Summarization" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What stands out is responsiveness. You can run inference on every keystroke and still feel real-time for small models. To keep the experience non-blocking, I recommend running inference in a Web Worker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// worker.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@huggingface/transformers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summarizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;summarization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Xenova/distilbart-cnn-6-6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nx"&gt;summary_text&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;summarizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;max_new_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;summary_text&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my opinion, Transformers.js is best suited for focused NLP tasks in the browser. You can run LLMs with it, but that's not where it truly shines. Classification, summarization, tagging, and other lightweight NLP tasks are examples that can fit naturally into almost any product.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebLLM: Your True AI Browser Assistant
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://webllm.mlc.ai/" rel="noopener noreferrer"&gt;WebLLM&lt;/a&gt; runs full instruction-tuned LLMs (Llama, Mistral, Qwen, etc.) directly in the browser using WebGPU. Under the hood, it uses &lt;a href="https://llm.mlc.ai/" rel="noopener noreferrer"&gt;MLC-LLM&lt;/a&gt; to compile models into an optimized WebGPU/Wasm target.&lt;/p&gt;

&lt;p&gt;The API is intentionally OpenAI-compatible. If you've built against the OpenAI chat completions API, the mental model transfers directly. Beyond basic chat, WebLLM supports &lt;strong&gt;streaming&lt;/strong&gt; via &lt;code&gt;AsyncGenerator&lt;/code&gt;, &lt;strong&gt;JSON mode&lt;/strong&gt; for structured output, &lt;strong&gt;seeding&lt;/strong&gt; for reproducible results, and preliminary &lt;strong&gt;function calling&lt;/strong&gt; (still WIP). It also supports &lt;strong&gt;Web Workers&lt;/strong&gt; and &lt;strong&gt;Service Workers&lt;/strong&gt;, meaning a loaded model persists across page reloads and works offline indefinitely.&lt;/p&gt;

&lt;p&gt;The setup is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;webllm&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mlc-ai/web-llm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;webllm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CreateMLCEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Llama-3.2-3B-Instruct-q4f32_1-MLC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;initProgressCallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Loading: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;% - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Explain WebGPU briefly&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main tradeoff is the initial download. Models are large (hundreds of MBs to several GBs), so progress feedback is essential. After that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model is cached&lt;/li&gt;
&lt;li&gt;Loads fairly quickly on revisit&lt;/li&gt;
&lt;li&gt;Can work offline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo9dghonzfv64awtqsbm2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo9dghonzfv64awtqsbm2.png" alt="WebLLM - Chat example" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The API mirrors OpenAI's, so it's easy to adopt.&lt;/p&gt;

&lt;h3&gt;
  
  
  The VRAM reality
&lt;/h3&gt;

&lt;p&gt;The real constraint is VRAM. Every token in the context occupies GPU memory on the user's device, competing with the model weights themselves. A 3B model at 4-bit quantization takes ~2–3 GB just to load - whatever remains is your conversation budget. Precompiled MLC models typically ship with 4K–8K token context windows.&lt;/p&gt;

&lt;p&gt;What "decent hardware" means in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Integrated graphics&lt;/strong&gt;: usually fine for office work, weak for local AI, because they rely on shared system memory and limited bandwidth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget laptop dGPUs&lt;/strong&gt;: usable for lightweight inference, but often constrained by 4–8 GB VRAM and quickly hit limits on larger models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Midrange GPUs&lt;/strong&gt;: 8–12 GB VRAM is a practical sweet spot for local 3B–7B models and casual experimentation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-end GPUs&lt;/strong&gt;: 16–24 GB VRAM makes bigger models and longer contexts much more manageable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apple Silicon&lt;/strong&gt;: unified memory means the model can use the machine's shared memory pool, which makes 16 GB and up much more useful for local AI than raw specs alone suggest.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical bar is roughly 6+ GB for smaller models and 8–12 GB for anything larger. A midrange gaming laptop from the last couple of years clears that. A typical thin-and-light with integrated graphics likely doesn't, which is exactly why this kind of feature should be opt-in.&lt;/p&gt;

&lt;p&gt;WebLLM requires a WebGPU-capable browser (Chrome/Edge 113+, Firefox 141+, Safari 26+) and degrades with a clear error rather than silently hanging, so feature detection and graceful fallback are straightforward.&lt;/p&gt;

&lt;p&gt;Where it fits best: an in-app chat assistant with zero per-token cost, contextual onboarding that adapts to app state, draft generation for emails or reports, or full AI functionality that survives going offline. It's also the fastest way to prototype. No backend, no API keys, no cost to iterate.&lt;/p&gt;

&lt;p&gt;Next, let's explore what a "truly native" browser AI could look like.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Zero-Setup Option: Chrome Built-in AI
&lt;/h2&gt;

&lt;p&gt;The fourth path doesn't involve any npm packages, CDN downloads, or model management at all. Chrome already ships with AI capabilities built directly into the browser - powered by Gemini Nano running on-device.&lt;/p&gt;

&lt;p&gt;Chrome 138 shipped the first batch of stable APIs - &lt;strong&gt;Summarizer&lt;/strong&gt;, &lt;strong&gt;Translator&lt;/strong&gt;, and &lt;strong&gt;Language Detector&lt;/strong&gt;. Since these are Chromium features, they work in Edge, Opera, Brave, and other Chromium-based browsers too. More are in the pipeline: a &lt;strong&gt;Writer&lt;/strong&gt; and &lt;strong&gt;Rewriter&lt;/strong&gt; for content generation, a &lt;strong&gt;Proofreader&lt;/strong&gt;, and a &lt;strong&gt;Prompt API&lt;/strong&gt; for open-ended Gemini Nano access (already stable for Chrome Extensions). No libraries to install, no models to host, no loading spinners. The model is part of the browser itself - if the user has a compatible browser, they have the model (or the browser downloads it transparently in the background, ~2 GB once). The full list and current status of each API is tracked on Chrome's &lt;a href="https://developer.chrome.com/docs/ai/built-in-apis" rel="noopener noreferrer"&gt;Built-in AI page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Summarizer API is minimal. You create an instance, pass text, get a summary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summarizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Summarizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key-points&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// or 'tldr', 'teaser', 'headline'&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;markdown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// 'short', 'medium', 'long'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;summarizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;summarize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;longArticle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Floe66mo3guebd5qqr9vw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Floe66mo3guebd5qqr9vw.png" alt="Chrome AI - Summarizer" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Four summary types cover the most common needs: key-points for bullet lists, tldr for a condensed paragraph, teaser for a hook, and headline for a single line. Combined with three length options, you get a reasonable amount of control without any prompt engineering.&lt;/p&gt;

&lt;p&gt;The Translator API follows the same pattern. You specify a language pair, and Chrome handles everything, including downloading language-specific packs on demand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;translator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Translator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;sourceLanguage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetLanguage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;translated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;translator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The browser is the platform.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// "El navegador es la plataforma."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flyz1kpai6ohgjdp1a66o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flyz1kpai6ohgjdp1a66o.png" alt="Chrome AI - Translator" width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can check availability before creating an instance. If you haven't used it before, you'll need to download it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;availability&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Translator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;availability&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;sourceLanguage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetLanguage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ja&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// 'available' | 'downloadable' | 'downloading' | 'unavailable'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both APIs support a &lt;code&gt;monitor&lt;/code&gt; callback for tracking download progress, useful when Chrome needs to fetch the base model or a language pack for the first time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summarizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Summarizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tldr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;monitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;downloadprogress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loaded&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;%`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The major trade-off is obvious: it's Chromium-only. There's no Firefox support, no Safari support, no polyfill. If you're building for a broad audience, this is a non-starter as a primary feature. But for internal tools, Chrome-first SaaS apps, or progressive enhancements where you can feature-detect and fall back gracefully, it's hard to beat the simplicity. If your users are on Chrome and you need quick summarization or translation with zero setup, this is the path of least resistance.&lt;/p&gt;

&lt;p&gt;The other constraint is flexibility. You get what Chrome gives you: there's no model selection, no fine-tuning, no custom tasks. Summarizer summarizes. Translator translates. If you need more, you're back to the other tools in this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: When the Browser Hits Its Ceiling - Ollama
&lt;/h2&gt;

&lt;p&gt;The approaches discussed above have many limitations. The model is too large. The context window is too small. The performance requirements are too demanding. You need stricter guarantees around structured output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; is the obvious next step. And yes, it's still easy to set up.&lt;/p&gt;

&lt;p&gt;It spins up a local server and exposes an OpenAI-compatible API. You can call it from your Node.js or any other backend, or even directly from the browser with a plain &lt;code&gt;fetch&lt;/code&gt; when running on your machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Ollama&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ollama&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ollama&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Ollama&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;llama3.2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Summarize this incident report in 2 sentences: ...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fylg4upx9p91ac1w9fxba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fylg4upx9p91ac1w9fxba.png" alt="Ollama - Chat example" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unlike WebLLM, which is limited to smaller quantized models that fit within a browser's VRAM budget, Ollama can run the full spectrum: from nimble 2B models to 70B ones capable of significantly richer reasoning and output quality. And since you control the hardware, context isn't constrained by end-user VRAM. Models like Llama 3.1 and Qwen 2.5 support up to 128K tokens natively.&lt;/p&gt;

&lt;p&gt;Feature-wise, it goes well beyond what a browser can offer. Structured output via JSON Schema, function/tool calling, multimodal input for vision models, embedding generation for semantic search, and a thinking mode for reasoning models like DeepSeek R1. The model library is extensive - from Gemma 3 1B (~1 GB) to Llama 3.3 70B (~43 GB at 4-bit quantization).&lt;/p&gt;

&lt;p&gt;Nothing comes free, and you need to consider the cost. Cloud AI providers charge per token, and at scale, that compounds. At some point, self-hosting becomes cheaper. How soon depends on the model tier and volume. For a SaaS product with consistent AI usage, owning the inference layer is worth modeling out.&lt;/p&gt;

&lt;p&gt;Think internal tools, document processing pipelines, RAG over private data, or anything where the model or context length outgrows what a browser can handle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reality Check: Performance and Constraints
&lt;/h2&gt;

&lt;p&gt;Performance varies by model, task, and hardware. A sentiment analysis model feels instant. A local LLM might take a few seconds per response. The user's device needs to be decent, and "decent" is doing real work in that sentence.&lt;/p&gt;

&lt;p&gt;These tools are best suited for &lt;strong&gt;desktop browsers&lt;/strong&gt;. On mobile, WebGPU support is limited and inconsistent, and even where it works, the hardware simply isn't built for this kind of workload. Expect slow inference, higher battery drain, and thermal throttling. Transformers.js with very small task models may work on mobile, but WebLLM should be considered desktop-only. Treating mobile as an unsupported tier for these features is a reasonable default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side by Side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Transformers.js&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;WebLLM&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Chrome Built-in AI&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Node.js + Ollama&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runtime&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Browser (Wasm default, WebGPU optional)&lt;/td&gt;
&lt;td&gt;Browser (WebGPU required, no CPU fallback)&lt;/td&gt;
&lt;td&gt;Browser (Chromium 138+)&lt;/td&gt;
&lt;td&gt;Server / local machine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Model types&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Task-specific (NLP, CV, audio)&lt;/td&gt;
&lt;td&gt;Full LLMs (Llama, Mistral, Phi…)&lt;/td&gt;
&lt;td&gt;Summarizer, Translator, Language Detector&lt;/td&gt;
&lt;td&gt;Full LLMs + task models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Model size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~100 MB – ~500 MB&lt;/td&gt;
&lt;td&gt;~500 MB – ~8 GB+&lt;/td&gt;
&lt;td&gt;Ships with browser (~2 GB)&lt;/td&gt;
&lt;td&gt;~300 MB – ~240 GB+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Context window&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;N/A (task models)&lt;/td&gt;
&lt;td&gt;4K–8K typical (VRAM-bound)&lt;/td&gt;
&lt;td&gt;N/A (task APIs)&lt;/td&gt;
&lt;td&gt;Up to 128K+ (configurable)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hardware req.&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Modest - Wasm fallback&lt;/td&gt;
&lt;td&gt;Modern GPU required&lt;/td&gt;
&lt;td&gt;Chrome with Gemini Nano&lt;/td&gt;
&lt;td&gt;GPU helpful, CPU works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Privacy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100% local&lt;/td&gt;
&lt;td&gt;100% local&lt;/td&gt;
&lt;td&gt;100% local&lt;/td&gt;
&lt;td&gt;Local (server-controlled)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Offline&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (after first load)&lt;/td&gt;
&lt;td&gt;Yes (after first load)&lt;/td&gt;
&lt;td&gt;Yes (after model ready)&lt;/td&gt;
&lt;td&gt;Yes (with local Ollama)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Classification, summarization, embeddings&lt;/td&gt;
&lt;td&gt;Conversational AI, text generation&lt;/td&gt;
&lt;td&gt;Quick summarization, translation&lt;/td&gt;
&lt;td&gt;Dev tools, internal apps, heavier workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What's Coming: WebNN and the NPU Era
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.w3.org/TR/webnn/" rel="noopener noreferrer"&gt;WebNN API&lt;/a&gt; (Web Neural Network) is in the W3C spec pipeline, and it's the most significant upcoming piece. Unlike WebGPU (GPU-only) or Wasm (CPU-only), WebNN is hardware-agnostic: it routes inference to whichever accelerator is available - CPU / GPU / NPU. It's currently the &lt;strong&gt;only browser API that exposes NPU access&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That matters as AI PCs with dedicated neural accelerators become mainstream: Intel Core Ultra, Qualcomm Snapdragon X, Apple Silicon's Neural Engine. WebNN reached W3C Candidate Recommendation status in 2024, Chromium-based browsers have experimental support behind flags, and ONNX Runtime Web (which powers Transformers.js) already has a WebNN execution provider in development.&lt;/p&gt;

&lt;p&gt;Neither WebLLM nor Transformers.js tap into WebNN yet in any meaningful way, but the connection is coming. When it lands, even the "modest hardware" bar drops significantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Client-side AI is coming and will become increasingly accessible, finding its niche in web applications. Next time you reach for an API key to add a simple text classification feature, consider whether a couple-dozen-MB model cached in the user's browser might do the job - for free, forever, with no data leaving their device.&lt;/p&gt;

&lt;p&gt;Personally, I've been using lightweight NLP features like tag suggestions based on user input in my apps - Transformers.js makes that effortless. Ollama is my go-to for demos and prototyping: no API keys, no cost, instant iteration. WebLLM and Chrome AI are impressive in their own ways, but still very niche in practice. If you're serious about shipping, be mindful of the constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;To experiment with all four approaches in one place, I built a companion demo - a React playground that lets you switch between Transformers.js, WebLLM, Chrome AI, and Ollama side by side.&lt;/p&gt;

&lt;p&gt;The Transformers.js tab runs live sentiment analysis, zero-shot classification, and text summarization - all in-browser via Web Workers. WebLLM provides a full chat interface with model selection, streaming responses, and configurable system prompts and context. Chrome AI demonstrates the built-in Summarizer and Translator APIs with no setup. Ollama connects to a local server with auto-discovery of installed models. Each tab demonstrates the real trade-offs firsthand: download sizes, loading times, response quality, and hardware requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/SolodkyRoman/browser-llm" rel="noopener noreferrer"&gt;→ Source on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The code examples in this article use &lt;code&gt;@huggingface/transformers&lt;/code&gt; v4, &lt;code&gt;@mlc-ai/web-llm&lt;/code&gt; v0.2.x, and &lt;code&gt;ollama&lt;/code&gt; v0.5.x. APIs evolve quickly - check the official docs for the latest.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>llm</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
