transformers icon indicating copy to clipboard operation
transformers copied to clipboard

There is no problem in the development environment, but the build deployment reports an error no available backend found. ERR: [webgpu]

Open x007xyz opened this issue 1 year ago • 2 comments

System Info

@huggingface/transformers is 3.0.0-alpha.7 and 3.0.0-alpha.6, Mac M1

Who can help?

@amyeroberts

Information

  • [ ] The official example scripts
  • [X] My own modified scripts

Tasks

  • [ ] An officially supported task in the examples folder (such as GLUE/SQuAD, ...)
  • [ ] My own task or dataset (give details below)

Reproduction

git: https://github.com/x007xyz/RMBG-webui demo: https://rmbg-webui.videocovert.online/ image

core code:

import { AutoModel, AutoProcessor, env, PreTrainedModel, Processor, RawImage } from '@huggingface/transformers';


// Since we will download the model from the Hugging Face Hub, we can skip the local model check
env.allowLocalModels = false;

// Proxy the WASM backend to prevent the UI from freezing
env.backends.onnx.wasm.proxy = true;

export class Model {
  static model: PreTrainedModel
  static processor: Processor

  static toDataURL(file: File): Promise<string> {
    return new Promise((resolve) => {
      const reader = new FileReader();
  
      // Set up a callback when the file is loaded
      reader.onload = e => resolve(e.target?.result as string);
  
      reader.readAsDataURL(file);
    })
  }

  static toImageURL (offscreen: OffscreenCanvas): Promise<string> {
    return new Promise((resolve, reject) => {
      // 将 OffscreenCanvas 转换为 Blob 对象
      offscreen.convertToBlob().then(blob => {

        resolve(URL.createObjectURL(blob))
      }).catch(error => {
        console.error('Error converting OffscreenCanvas to URL:', error);
        reject(error)
      });
    })
  }

  static async loadModel() {
    if (!this.model) {
      this.model = await AutoModel.from_pretrained('briaai/RMBG-1.4', {
        // Do not require config.json to be present in the repository
        config: { model_type: 'custom' },
        device: 'webgpu',
        dtype: 'fp32',
      });
    }
  
    if (!this.processor) {
      this.processor = await AutoProcessor.from_pretrained('briaai/RMBG-1.4', {
          // Do not require config.json to be present in the repository
          config: {
              do_normalize: true,
              do_pad: false,
              do_rescale: true,
              do_resize: true,
              image_mean: [0.5, 0.5, 0.5],
              feature_extractor_type: "ImageFeatureExtractor",
              image_std: [1, 1, 1],
              resample: 2,
              rescale_factor: 0.00392156862745098,
              size: { width: 1024, height: 1024 },
          }
      });
    }
  }

  static async processImage(file: string | File): Promise<string> {
    if (file instanceof File) {
      file = await this.toDataURL(file)
    }
    await this.loadModel();
    // Read image
    const image = await RawImage.fromURL(file);

    const start = performance.now();

    // Preprocess image
    const { pixel_values } = await this.processor(image);

    // Predict alpha matte
    const { output } = await this.model({ input: pixel_values });

    // Resize mask back to original size
    const mask = await RawImage.fromTensor(output[0].mul(255).to('uint8')).resize(image.width, image.height);

    console.log('消耗时间:', performance.now() - start);

    // Create new canvas
    const offscreenCanvas = new OffscreenCanvas(image.width, image.height)
    const ctx = offscreenCanvas.getContext('2d');

    if (!ctx) {
      console.error('不支持OffscreenCanvas')
      throw new Error("不支持OffscreenCanvas");
    }

    // Draw original image output to canvas
    ctx.drawImage(image.toCanvas(), 0, 0);

    console.log('消耗时间:', performance.now() - start);

    // Update alpha channel
    const pixelData = ctx.getImageData(0, 0, image.width, image.height);
    for (let i = 0; i < mask.data.length; ++i) {
        pixelData.data[4 * i + 3] = mask.data[i];
    }
    ctx.putImageData(pixelData, 0, 0);

    return this.toImageURL(offscreenCanvas);
  }
}

interface FileUploadOptions {
  accept: string
  multiple: boolean
  max?: string
}

// 文件上传
export const selectFile = (options: FileUploadOptions): Promise<File[]> => {
  return new Promise((resolve, reject) => {
    // 创建input[file]元素
    const input = document.createElement('input')
    // 设置相应属性
    input.setAttribute('type', 'file')
    input.setAttribute('accept', options.accept)
    options.multiple
      ? input.setAttribute('multiple', 'multiple')
      : input.removeAttribute('multiple')
    // 绑定事件
    input.onchange = function (event) {

      const input = event.target as HTMLInputElement;
      // 获取文件列表
      if (input?.files) {
        const files = Array.from(input.files)
        resolve(files)
      } else {
        reject(new Error('No files selected'))
      }
    }

    input.oncancel = function () {
      reject(new Error('No files selected'))
    }
    input.click()
  })
}

view code

import { AspectRatio } from "@/components/ui/aspect-ratio"
import { Button } from "@/components/ui/button"
import { Switch } from "@/components/ui/switch"
import { ImageDown, ImageUp } from "lucide-react"
import { selectFile, Model } from "@/utils"
import { useEffect, useMemo, useState } from "react"
import Loading from "./components/Loading"

const HomePage = () => {

  const [loadingModel, setLoadingModel] = useState(true)
  
  const [sourceImage, setSourceImage] = useState('')

  const [processImage, setProcessImage] = useState('')

  const [mode, setMode] = useState('source')

  const canSwitch = useMemo(() => !!processImage, [processImage])

  useEffect(() => {
    Model.loadModel().then(() => {
      // 模型加载完成
      setLoadingModel(false)
    })
  }, [])
  function onUpload () {
    setSourceImage("")
    setProcessImage("")
    setMode('source')
    selectFile({ accept: 'image/*', multiple: false }).then(files => {
      Model.toDataURL(files[0]).then(url => {
        setSourceImage(url)
      })
      return Model.processImage(files[0])
    }).then(url => {
      setProcessImage(url)
      setMode('process')
    })
  }

  function onCheckedChange(checked: boolean) {
    setMode(checked ? 'process' : 'source')
  }

  function onDownLoad() {
    const a = document.createElement('a');
    a.href = processImage;
    a.download = `${Date.now()}.png`; // 指定下载的文件名

    a.click()
  }

  return (
    <div className="mx-auto max-w-2xl p-6">
      <AspectRatio ratio={4 / 3} className="bg-muted rounded-lg">
        {mode === 'source' && sourceImage && <img className="object-contain w-full h-full absolute" src={sourceImage} alt="" />}
        {processImage && mode === 'process' && <img className="object-contain w-full h-full absolute" src={processImage} alt="" />}
        <div className="absolute bottom-4 right-4">
          <Switch id="airplane-mode" disabled={!canSwitch} checked={mode === 'process'} onCheckedChange={onCheckedChange}/>
        </div>
      </AspectRatio>
      <div className="mt-4 flex items-center">
        <Button onClick={onUpload} disabled={loadingModel}>
          { loadingModel ? <Loading></Loading> : <ImageUp className="mr-4"/> }
          上传图片
        </Button>
        <Button className="ml-6" variant={'outline'} disabled={!processImage} onClick={onDownLoad}>
          <ImageDown className="mr-4"/>
          下载图片
        </Button>
      </div>
    </div>
  )
}

export default HomePage

Expected behavior

Deployed to production environment normally

x007xyz avatar Aug 20 '24 11:08 x007xyz

Hi @x007xyz, please make sure to follow the bug report template and provide all the necessary information so that we can help you.

At the moment there is no reproducible code snippet, explanation of what you're trying to do, what you've already done to try and fix it, to expected and observed behaviour or all relevant system information.

amyeroberts avatar Aug 20 '24 14:08 amyeroberts

use @xenova/transformers is no problem,error form onnxruntime-web image

x007xyz avatar Aug 21 '24 06:08 x007xyz

Hi there @x007xyz 👋 - this has been fixed in the latest version (was an issue arising from proxying). In future, can you please open the issue in the Transformers.js repository (http://github.com/xenova/transformers.js)? Thanks!

If you still have any questions/issues, feel free to open a new issue in the above repo and I'd be happy to help!

xenova avatar Aug 30 '24 22:08 xenova