host =
if repo_name = System.get_env("SPACE_REPO_NAME") do
"#{System.get_env("SPACE_AUTHOR_NAME")}-#{repo_name}.hf.space"
|> String.replace("_", "-")
|> String.downcase(:ascii)
else
"localhost"
end
Application.put_env(:phoenix, :json_library, Jason)
Application.put_env(:phoenix_demo, PhoenixDemo.Endpoint,
url: [host: host],
http: [
ip: {0, 0, 0, 0, 0, 0, 0, 0},
port: String.to_integer(System.get_env("PORT") || "4000"),
transport_options: [socket_opts: [:inet6]]
],
server: true,
live_view: [signing_salt: :crypto.strong_rand_bytes(8) |> Base.encode16()],
secret_key_base: :crypto.strong_rand_bytes(32) |> Base.encode16(),
pubsub_server: PhoenixDemo.PubSub
)
Mix.install([
{:plug_cowboy, "~> 2.6"},
{:jason, "~> 1.4"},
{:phoenix, "~> 1.7.0-rc.0", override: true},
{:phoenix_live_view, "~> 0.18.3"},
{:bumblebee, "~> 0.1.0"},
{:nx, "~> 0.4.1"},
{:exla, "~> 0.4.1"}
])
Application.put_env(:nx, :default_backend, EXLA.Backend)
defmodule PhoenixDemo.Layouts do
use Phoenix.Component
def render("live.html", assigns) do
~H"""
<%= @inner_content %>
"""
end
end
defmodule PhoenixDemo.ErrorView do
def render(_, _), do: "error"
end
defmodule PhoenixDemo.SampleLive do
use Phoenix.LiveView, layout: {PhoenixDemo.Layouts, :live}
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(label: nil, running: false, task_ref: nil)
|> allow_upload(:image,
accept: :any,
max_entries: 1,
max_file_size: 300_000,
progress: &handle_progress/3,
auto_upload: true
)}
end
def render(assigns) do
~H"""
Elixir image classification demo
Powered by Bumblebee,
an Nx/Axon library for pre-trained and transformer NN models with 🤗 integration.
Deployed on Hugging Face CPU Basic (2vCPU · 16 GiB RAM)
Label:
<%= if @running do %>
<.spinner />
<% else %>
<%= @label || "?" %>
<% end %>
Drag an image file here or click to open file browser
"""
end
defp spinner(assigns) do
~H"""
"""
end
def handle_progress(:image, entry, socket) do
if entry.done? do
socket
|> consume_uploaded_entries(:image, fn meta, _ -> {:ok, File.read!(meta.path)} end)
|> case do
[binary] ->
image = decode_as_tensor(binary)
task = Task.async(fn -> Nx.Serving.batched_run(PhoenixDemo.Serving, image) end)
{:noreply, assign(socket, running: true, task_ref: task.ref)}
[] ->
{:noreply, socket}
end
else
{:noreply, socket}
end
end
defp decode_as_tensor(<>) do
data |> Nx.from_binary(:u8) |> Nx.reshape({height, width, 3})
end
# We need phx-change and phx-submit on the form for live uploads
def handle_event("noop", %{}, socket) do
{:noreply, socket}
end
def handle_info({ref, result}, %{assigns: %{task_ref: ref}} = socket) do
Process.demonitor(ref, [:flush])
%{predictions: [%{label: label}]} = result
{:noreply, assign(socket, label: label, running: false)}
end
end
defmodule PhoenixDemo.Router do
use Phoenix.Router
import Phoenix.LiveView.Router
pipeline :browser do
plug(:accepts, ["html"])
end
scope "/", PhoenixDemo do
pipe_through(:browser)
live("/", SampleLive, :index)
end
end
defmodule PhoenixDemo.Endpoint do
use Phoenix.Endpoint, otp_app: :phoenix_demo
socket("/live", Phoenix.LiveView.Socket)
plug(PhoenixDemo.Router)
end
# Application startup
{:ok, model_info} = Bumblebee.load_model({:hf, "microsoft/resnet-50"})
{:ok, featurizer} = Bumblebee.load_featurizer({:hf, "microsoft/resnet-50"})
serving =
Bumblebee.Vision.image_classification(model_info, featurizer,
top_k: 1,
compile: [batch_size: 10],
defn_options: [compiler: EXLA]
)
# Dry run for copying cached mix install from builder to runner
if System.get_env("EXS_DRY_RUN") == "true" do
System.halt(0)
else
{:ok, _} =
Supervisor.start_link(
[
{Phoenix.PubSub, name: PhoenixDemo.PubSub},
PhoenixDemo.Endpoint,
{Nx.Serving, serving: serving, name: PhoenixDemo.Serving, batch_timeout: 100}
],
strategy: :one_for_one
)
Process.sleep(:infinity)
end