Team Ai
Apppublic

LiquidAI/LFM2.5-VL-3B-WebGPU

sourceHugging Faceupdated 2mo agoView on Hugging Face
36likes
runtime.js117 linesDownload Raw Back to runtime
1import { selectEmbeddingPrecision } from '../model-precision.js';2 3/**4 * Format-neutral runtime boundary.5 *6 * An ONNX or GGUF implementation must satisfy the same adapter contract. The7 * product UI intentionally has no dependency on either runtime.8 */9class Runtime extends EventTarget {10  adapter = null;11  status = 'idle';12  backend = 'Engine not selected';13  webgpu = null;14  embeddingPrecision = null;15 16  emit(type, detail) {17    this.dispatchEvent(new CustomEvent(type, { detail }));18  }19 20  log(level, message, detail = '') {21    if (level !== 'error' && level !== 'warn') return;22    console[level](`[LFM edge runtime] ${message}`, detail || '');23  }24 25  async probe() {26    if (!globalThis.isSecureContext) throw new Error('WebGPU requires HTTPS or localhost.');27    if (!navigator.gpu) throw new Error('WebGPU is unavailable in this browser.');28    const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' });29    if (!adapter) throw new Error('The browser did not return a WebGPU adapter.');30    const info = adapter.info || {};31    this.webgpu = {32      adapter,33      name: info.description || info.device || info.architecture || 'WebGPU adapter',34      vendor: info.vendor || 'Not exposed',35      architecture: info.architecture || 'Not exposed',36      features: [...adapter.features].sort(),37      limits: adapter.limits,38    };39    return this.webgpu;40  }41 42  async load() {43    if (this.status === 'loading') return;44    this.status = 'loading';45    this.emit('status', { status: this.status, backend: this.backend });46    try {47      await this.probe();48      const baseManifest = await fetch('/model-manifest.json', { cache: 'no-store' }).then(response => response.json());49      const selection = selectEmbeddingPrecision(baseManifest, this.webgpu.features);50      const manifest = selection.manifest;51      this.embeddingPrecision = selection.precision;52      this.emit('status', { status: this.status, backend: this.backend });53      const loaders = {54        'onnx-transformers': () => import('../engines/onnx-transformers-engine.js'),55      };56      const loadEngine = loaders[manifest.engine?.adapter];57      if (!loadEngine) throw new Error(`Unknown or unselected inference adapter: ${manifest.engine?.adapter || 'none'}.`);58      const module = await loadEngine();59      this.adapter = await module.createEngine({ manifest, telemetry: event => this.log(event.level, event.message, event.detail) });60      await this.adapter.load(progress => this.emit('progress', progress));61      this.backend = this.adapter.backend;62      this.status = 'ready';63    } catch (error) {64      this.status = 'error';65      this.log('error', 'Engine load stopped', error.message);66      throw error;67    } finally {68      this.emit('status', { status: this.status, backend: this.backend });69    }70  }71 72  /** Returns { text, toolCalls, finishReason } for every inference adapter. */73  async generate(messages, options) {74    if (!this.adapter || this.status !== 'ready') throw new Error('No inference engine is configured.');75    this.status = 'generating';76    this.emit('status', { status: this.status, backend: this.backend });77    try {78      return await this.adapter.generate(messages, options);79    } finally {80      this.status = 'ready';81      this.emit('status', { status: this.status, backend: this.backend });82    }83  }84 85  async clearCache() {86    const result = this.adapter?.clearCache ? await this.adapter.clearCache() : { cleared: false, entriesDeleted: 0 };87    const cleared = typeof result === 'object' ? result.cleared : Boolean(result);88    return cleared;89  }90 91  async cacheInfo() {92    if (this.adapter?.cacheInfo) return this.adapter.cacheInfo();93    if (globalThis.caches) {94      const cacheNames = await caches.keys();95      const modelCacheNames = cacheNames.filter(name => name === 'liquid-lfm-models-v4');96      let used = 0;97      for (const cacheName of modelCacheNames) {98        const cache = await caches.open(cacheName);99        for (const request of await cache.keys()) {100          const response = await cache.match(request);101          used += Number(response?.headers.get('content-length') || 0);102        }103      }104      const estimate = await navigator.storage?.estimate?.();105      return { used, available: estimate?.quota || 0 };106    }107    const estimate = await navigator.storage?.estimate?.();108    return { used: 0, available: estimate?.quota || 0 };109  }110 111  clearConversationCache() {112    this.adapter?.clearConversationCache?.();113  }114}115 116export const runtime = new Runtime();117