Running some sort of ML model locally is actually easy if you know Elixir and Phoenix.

The only dependencies you need to add to a Phoenix app are bumblebee and exla. After adding those, add the backend config for nx in config.exs:

config :nx, default_backend: EXLA.Backend

Loading a Model

defmodule MyApp.Model do
  def sentiment_serving() do
    {:ok, bertweet} =
      Bumblebee.load_model({:hf, "finiteautomata/bertweet-base-sentiment-analysis"})

    {:ok, tokenizer} = Bumblebee.load_tokenizer({:hf, "vinai/bertweet-base"})

    Bumblebee.Text.text_classification(bertweet, tokenizer)
  end
end

Loading a model from huggingface is done with Bumblebee. Here we’re loading a simple sentiment analysis model and a matching tokenizer. We then give those to Bumblebee.Text.text_classification/3 to get our Nx.Serving.

The serving is what does the work so let’s add it as a child process of our Phoenix application. Over in application.ex, add this child to the list of children:

{Nx.Serving, name: SentimentServing, serving: MyApp.Model.sentiment_serving()}

Using the Model

To use the serving process, add this function to MyApp.Model:

def get_sentiment(text) do
  Nx.Serving.batched_run(SentimentServing, text)
end

It simply passes the arg(assuming it’s a string) to Nx.Serving.batched_run/3 along with the name of the Nx.Serving child process.

With that function in place, the loaded model can now be used within the Phoenix app. For example, a POST route can be added for this action:

  def sentiment(conn, %{"q" => q}) do
    result = MyApp.Model.get_sentiment(q)
    render(conn, :sentiment, result: result)
  end

It takes a q param, passes it to MyApp.Model.get_sentiment/1, and renders the resulting map as JSON.

Something to keep in mind

Multiple models can be loaded like this as long as the hardware allows.