जर्नल पर वापस जाएं
8 मिनट पठन

लोकल-फर्स्ट डेवलपर वर्कफ़्लो: इन-ब्राउज़र LLM फ़ाइन-ट्यूनिंग के लिए WebGPU और WebContainers का उपयोग

ब्राउज़र सैंडबॉक्स में सीधे LLMs को चलाने और अनुकूलित करने का एक गहन विश्लेषण। हम एक शून्य-लागत, निजी AI प्लेग्राउंड बनाने के लिए StackBlitz WebContainers को WebGPU और LoRA के साथ जोड़ते हैं।

WebGPUWebContainersLocal-First AIIn-Browser LLMsWASMWeb Engineering
लोकल-फर्स्ट डेवलपर वर्कफ़्लो: इन-ब्राउज़र LLM फ़ाइन-ट्यूनिंग के लिए WebGPU और WebContainers का उपयोग

विकास (development) के दौरान बड़े भाषा मॉडल (LLMs) चलाने के लिए आमतौर पर क्लाउड-आधारित एपीआई (APIs) या Ollama जैसे भारी स्थानीय डेमन्स (local daemons) की आवश्यकता होती है। यद्यपि क्लाउड एपीआई उच्च गति प्रदान करते हैं, वे बार-बार होने वाले सदस्यता शुल्क, इंटरनेट निर्भरता और संभावित डेटा गोपनीयता समस्याओं को जन्म देते हैं।

वेब प्लेटफ़ॉर्म की हालिया प्रगति के साथ, हम अब इन समझौता-स्थितियों (trade-offs) से पूरी तरह बच सकते हैं। StackBlitz WebContainers — जो ब्राउज़र टैब के भीतर सीधे एक पूर्ण Node.js ऑपरेटिंग वातावरण चलाते हैं — को स्थानीय हार्डवेयर त्वरण (hardware acceleration) के लिए WebGPU के साथ जोड़कर, हम एक पूरी तरह से सैंडबॉक्स, निजी और मुफ़्त AI डेवलपर प्लेग्राउंड बना सकते हैं।

इस गाइड में, हम इन-ब्राउज़र AI IDE की वास्तुकला (architecture) पर नज़र डालेंगे। हम WebGPU इनफेरेंस (inference) को प्रबंधित करने के लिए एक Web Worker को लागू करेंगे, क्लाइंट-साइड जावास्क्रिप्ट में सीधे Low-Rank Adaptation (LoRA) एडेप्टर को लागू करने के तरीकों की खोज करेंगे, और AI इंजन को WebContainer वर्चुअल फ़ाइल सिस्टम से जोड़ेंगे।


The Architecture of a Browser-Based Sandbox

To run both a Node.js development server and an LLM client-side, our architecture must isolate computation to prevent the browser's main thread from freezing.

+-------------------------------------------------------------------------+
|                               Browser Tab                               |
|                                                                         |
|  +---------------------------+             +-------------------------+  |
|  |       Main Thread         |             |   Web Worker (WebGPU)   |  |
|  |                           |             |                         |  |
|  | +-----------------------+ |  PostMessage|  +--------------------+ |  |
|  | | WebContainer Instance | | <---------> |  |   WebLLM Engine    | |  |
|  | | (Vite, Monaco Editor) | |             |  +----------+---------+ |  |
|  | +-----------+-----------+ |             |             |           |  |
|  +-------------|-------------+             +-------------|-----------+  |
|                v                                         v              |
|        [Virtual Filesystem]                       [IndexedDB Cache]     |
|                                                          |              |
|                                                          v              |
|                                                    [GPU VRAM]           |
+-------------------------------------------------------------------------+
  1. मुख्य थ्रेड (Main Thread): Renders the Monaco Editor UI and boots the WebContainer instance. The WebContainer compiles code, runs dev servers, and serves hot-module reloaded (HMR) assets via a virtual network.
  2. WebGPU वर्कर (Worker): Houses the AI inference engine. Running the LLM inside a Web Worker prevents heavy WebGPU tensor calculations from blocking user interactions (like typing or scrolling).
  3. IndexedDB कैश (Cache): Stores the quantized model weights (e.g., gemma-4-e2b-it-q4f16_1-MLC, Qwen2.5-Coder-1.5B-Instruct, or a distilled reasoning model like DeepSeek-R1-Distill-Qwen-1.5B). On first visit, the weights download; on subsequent visits, they load instantly from disk.

Implementing the WebGPU Worker

We will use the @mlc-ai/web-llm library to run our model inside the Web Worker. This library leverages WebGPU compiler backends to execute compiled weights directly on local silicon.

First, let's write the Web Worker code (llm.worker.ts) to handle model loading, inference, and token streaming:

import { CreateWebGPUEngine, WebGPUEngine } from "@mlc-ai/web-llm";

let engine: WebGPUEngine | null = null;

// Listen for commands from the main thread
self.onmessage = async (event: MessageEvent) => {
  const { type, payload } = event.data;

  switch (type) {
    case "INIT_ENGINE":
      try {
        // payload.model can be a prebuilt model ID string or a custom model configuration object
        engine = await CreateWebGPUEngine(payload.model, {
          initProgressCallback: (report) => {
            self.postMessage({ type: "INIT_PROGRESS", payload: report.text });
          }
        });
        self.postMessage({ type: "ENGINE_READY" });
      } catch (err) {
        self.postMessage({ type: "ERROR", payload: (err as Error).message });
      }
      break;

    case "GENERATE":
      if (!engine) {
        self.postMessage({ type: "ERROR", payload: "Engine not initialized" });
        return;
      }

      try {
        const { prompt, systemPrompt } = payload;
        const messages = [
          { role: "system", content: systemPrompt || "You are a helpful coding assistant." },
          { role: "user", content: prompt }
        ];

        const completion = await engine.chat.completions.create({
          messages: messages as any,
          stream: true,
          temperature: 0.2,
          max_tokens: 1024
        });

        for await (const chunk of completion) {
          const content = chunk.choices[0]?.delta?.content || "";
          if (content) {
            self.postMessage({ type: "TOKEN", payload: content });
          }
        }
        self.postMessage({ type: "GENERATE_COMPLETE" });
      } catch (err) {
        self.postMessage({ type: "ERROR", payload: (err as Error).message });
      }
      break;

    default:
      console.warn(`Unknown message type: ${type}`);
  }
};

Applying In-Browser LoRA Adapters

ब्राउज़र-आधारित मॉडल को विशिष्ट कोडबेस के लिए उपयोगी बनाने के लिए, हम एक Low-Rank Adaptation (LoRA) एडेप्टर लोड कर सकते हैं। LoRA मॉडल के कुल भारों (weights) के एक बहुत छोटे हिस्से (अक्सर 1% से भी कम) को अपडेट करके आउटपुट व्यवहार को बदल देता है, जिससे रनटाइम पर इसे डाउनलोड करना और लागू करना बेहद तेज़ हो जाता है।

आधुनिक WebLLM वातावरण में, एडेप्टर को अलग .bin फ़ाइलों के रूप में पैक किया जाता है और बेस मॉडल भारों के ऊपर स्तरित (layered) किया जाता है। यहाँ बताया गया है कि हम बेस मॉडल (Qwen2.5-Coder-1.5B-Instruct-q4f16_1-MLC) को लोड करने और एक कस्टम कोडबेस-विशिष्ट एडेप्टर लागू करने के लिए WebLLM को कैसे कॉन्फ़िगर करते हैं:

const modelConfig = {
  model_id: "Qwen2.5-Coder-1.5B-Instruct-q4f16_1-MLC",
  model_lib: "https://raw.githubusercontent.com/mlc-ai/binary-mlc-llm-libs/main/web-packages/Qwen2.5-Coder-1.5B-Instruct-q4f16_1-MLC-webgpu.wasm",
  // Load custom adapter weights from an HTTP endpoint
  lora_config: [
    {
      lora_name: "project-api-adapter",
      lora_url: "https://cdn.myportfolio.com/adapters/project-api-lora.bin",
      scale: 1.2
    }
  ]
};

जब WebLLM प्रारंभ (initialize) होता है, तो यह बेस भारों को खींचता है और WebGPU शेडर्स को संकलित (compile) करता है। फ़ॉरवर्ड पास के दौरान, GPU मुख्य अटेंशन ब्लॉक्स के समानांतर LoRA डेल्टा मैट्रिक्स गणनाओं को लागू करता है। यह डेवलपर्स को अपनी मशीन पर एक मॉडल को फ़ाइन-ट्यून करने, 10MB का एडेप्टर अपलोड करने और टीम के ब्राउज़रों में कस्टम कोडिंग पैटर्न को तुरंत वितरित करने की अनुमति देता है।


Bridging WebContainers and WebGPU

अब जबकि हमारे पास एक बैकग्राउंड वर्कर है जो हमारे मॉडल को चला रहा है, हम इसे अपने WebContainer वर्कस्पेस से जोड़ सकते हैं। मुख्य थ्रेड WebContainer के वर्चुअल फ़ाइल सिस्टम से एक फ़ाइल को पढ़ने, सामग्री को WebGPU वर्कर को भेजने और AI के संशोधित आउटपुट को वापस लिखने का समन्वय (coordinate) करेगा।

यहाँ इस संचार प्रवाह (communication flow) को प्रबंधित करने वाली समन्वयक श्रेणी (coordinator class) दी गई है:

import { WebContainer } from "@webcontainer/api";

export class SandboxAIManager {
  private worker: Worker;
  private webcontainer: WebContainer;
  private onTokenCallback?: (token: string) => void;

  constructor(webcontainer: WebContainer) {
    this.webcontainer = webcontainer;
    
    // Initialize Web Worker
    this.worker = new Worker(
      new URL("./llm.worker.ts", import.meta.url), 
      { type: "module" }
    );

    this.worker.onmessage = this.handleWorkerMessage.bind(this);
  }

  public initModel(model: string | object) {
    this.worker.postMessage({
      type: "INIT_ENGINE",
      payload: { model }
    });
  }

  public async refactorFile(filePath: string, instruction: string, onToken: (t: string) => void) {
    this.onTokenCallback = onToken;

    // 1. Read file contents from WebContainer virtual filesystem
    const fileBytes = await this.webcontainer.fs.readFile(filePath, "utf-8");

    // 2. Format context prompt
    const prompt = `
Refactor the following file according to this instruction: "${instruction}"

File Path: ${filePath}
Source Code:
\`\`\`typescript
${fileBytes}
\`\`\`

Return only the refactored code inside a single code block. Do not include markdown introductions.
`;

    // 3. Trigger GPU inference in worker
    this.worker.postMessage({
      type: "GENERATE",
      payload: { prompt }
    });
  }

  private async handleWorkerMessage(event: MessageEvent) {
    const { type, payload } = event.data;

    if (type === "TOKEN" && this.onTokenCallback) {
      this.onTokenCallback(payload);
    } else if (type === "GENERATE_COMPLETE") {
      console.log("Code generation finished.");
    } else if (type === "INIT_PROGRESS") {
      console.log(`Loading model progress: ${payload}`);
    }
  }
}

Performance & VRAM Constraints in the Browser

क्लाइंट-साइड LLMs चलाना एक बार डाउनलोड हो जाने के बाद अत्यधिक कुशल होता है, लेकिन यह ब्राउज़र-आवंटित सैंडबॉक्स सीमाओं के भीतर संचालित होता है।

1. VRAM आवंटन सीमाएँ (VRAM Allocation Limits)

WebGPU सीधे होस्ट मशीन के ग्राफिक्स कार्ड पर मेमोरी ब्लॉक आवंटित करता है। हालाँकि, ब्राउज़र व्यक्तिगत GPU आवंटन पर सख्त सीमाएँ लगाते हैं (आमतौर पर ऑपरेटिंग सिस्टम और हार्डवेयर टियर के आधार पर 2GB से 4GB तक सीमित)।

  • निवारण (Mitigation): मॉडलों को कड़ाई से क्वांटाइज़ किया जाना चाहिए। Qwen2.5-Coder-Instruct जैसे 1.5-बिलियन पैरामीटर वाले मॉडल को q4f16_1 (4-बिट) क्वांटाइजेशन स्कीम का उपयोग करते समय ~1.3GB VRAM की आवश्यकता होती है, और DeepSeek-R1-Distill-Qwen-1.5B जैसे डिस्टिल्ड रीजनिंग मॉडल ~1.4GB VRAM के भीतर चलते हैं, जो ब्राउज़र के सुरक्षा मार्जिन के भीतर अच्छी तरह से फिट बैठता है।

2. टैब मेमोरी क्रैशिंग (OOM)

जबकि WebGPU मेमोरी GPU पर रहती है, Web Assembly (WASM) और जावास्क्रिप्ट रनटाइम अभी भी ऑर्केस्ट्रेशन परतों को प्रबंधित करते हैं। यदि कोई मॉडल GPU पर भेजने से पहले बड़ी मात्रा में भारों को सीधे CPU मेमोरी में लोड करने का प्रयास करता है, तो Chrome "पेज आउट ऑफ मेमोरी" (OOM) क्रैश को ट्रिगर कर सकता है।

  • निवारण: स्ट्रीमिंग एरे बफ़र्स (streaming array buffers) का उपयोग करें। डाउनलोड किए गए भारों को सीधे IndexedDB में स्ट्रीम करना, और फिर उन्हें सीधे GPU बनावट (textures) में ब्लॉक-बाय-ब्लॉक पार्स करना, जावास्क्रिप्ट हीप मेमोरी में संपूर्ण मॉडल फ़ाइल को संग्रहीत करने से बचाता है।

3. फ़ॉलबैक (Fallbacks)

यदि उपयोगकर्ता का ब्राउज़र WebGPU का समर्थन नहीं करता है (जैसे पुराने मोबाइल ब्राउज़र या अक्षम फ़्लैग वाले कॉन्फ़िगरेशन), तो ऑर्केस्ट्रेटर को आसानी से WASM CPU निष्पादन (execution) पर वापस आ जाना चाहिए। हालाँकि इनफेरेंस गति ~30 टोकन/सेकंड से गिरकर ~3 टोकन/सेकंड हो जाएगी, यह फ़ॉलबैक गारंटी देता है कि सैंडबॉक्स कार्यात्मक बना रहे।


सारांश

StackBlitz WebContainers के साथ WebGPU का लाभ उठाकर, वेब एप्लिकेशन सरल प्रस्तुति पृष्ठों से पूर्ण, स्व-निहित डेवलपर वातावरण में परिवर्तित हो सकते हैं। कस्टम, फ़ाइन-ट्यून किए गए डेवलपर वर्कफ़्लो को वितरित करना अब CDN पर एक छोटी LoRA एडेप्टर फ़ाइल परोसने जितना आसान है, जिससे टीमों को सैंडबॉक्स में सहयोग करने की अनुमति मिलती है जो शून्य बुनियादी ढांचा लागत के साथ पूरी तरह से क्लाइंट मशीनों पर चलते हैं।

Share this article