跳到主要内容

你的第一个 Docker Space:使用 T5 做文本生成

在接下来的内容中,你将学习如何创建一个 Docker Space、对它进行配置,并把代码部署上去。我们会用 Docker 创建一个文本生成 Space,基于 google/flan-t5-small 模型,通过 FastAPI 作为服务端,对输入文本进行生成。

最终完成版示例可以在这里查看。

创建一个新的 Docker Space

我们先创建一个全新的 Space,并选择 Docker 作为 SDK。

Hugging Face Spaces 本质上是 Git 仓库,这意味着你可以通过推送 commit 以增量方式(并与他人协作)开发 Space。开始之前,你可以先阅读 Getting Started with Repositories 指南,了解如何创建和编辑文件。如果你更习惯图形界面,也可以直接在浏览器里完成这些操作。

创建新 Space 时选择 Docker 作为 SDK,会在 README.md 的 YAML 区块中自动把 sdk 属性设置为 docker,从而初始化 Docker Space:

sdk: docker

你可以通过在 README.md 的 YAML 区块中设置 app_port 属性来修改 Space 的默认应用端口。默认端口为 7860

app_port: 7860

添加依赖

在这个 文本生成 Space 中,我们会构建一个 FastAPI 应用,展示名为 Flan T5 的文本生成模型。推理部分会使用 🤗 Transformers pipeline 来调用模型。第一步是安装若干依赖,可以在仓库中创建一个 requirements.txt 文件,并将以下内容写入其中:

fastapi==0.74.*
requests==2.27.*
sentencepiece==0.1.*
torch==1.11.*
transformers==4.*
uvicorn[standard]==0.17.*

这些依赖会在我们稍后创建的 Dockerfile 中被安装。

创建应用

我们先从一个简单的 FastAPI 应用开始,确认基本接口能够正常运行。第一步是创建应用文件,这里命名为 main.py

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
return {"Hello": "World!"}

创建 Dockerfile

对于 Docker Space 来说,核心工作就是编写 Dockerfile。你可以在这里了解更多 Dockerfile 相关知识。虽然本教程使用的是 FastAPI,但 Dockerfile 极其灵活,可以让你构建各类新型 ML Demo。下面我们为这个应用写一个 Dockerfile:

# read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
# you will also find guides on how best to write your Dockerfile

FROM python:3.9

# The two following lines are requirements for the Dev Mode to be functional
# Learn more about the Dev Mode at https://huggingface.co/dev-mode-explorers
RUN useradd -m -u 1000 user
WORKDIR /app

COPY --chown=user ./requirements.txt requirements.txt
RUN pip install --no-cache-dir --upgrade -r requirements.txt

COPY --chown=user . /app
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]

保存修改之后,Space 会自动重新构建,几秒钟后你的 Demo 就能跑起来!此时的效果示例可以参考这个 Space:fastapi_dummy

本地测试

给进阶用户的小贴士(可跳过): 如果你在本地开发,现在就可以使用 docker builddocker run 来调试;当然,最简单的方式依然是推送到 Hub 上直接观察效果。

docker build -t fastapi .
docker run -it -p 7860:7860 fastapi

如果你的 Space 配置了 Secrets,可以使用 docker buildx 并以构建参数的方式传入 secret:

export SECRET_EXAMPLE="my_secret_value"
docker buildx build --secret id=SECRET_EXAMPLE,env=SECRET_EXAMPLE -t fastapi .

运行时可以通过 docker run 以环境变量形式传入 secret:

export SECRET_EXAMPLE="my_secret_value"
docker run -it -p 7860:7860 -e SECRET_EXAMPLE=$SECRET_EXAMPLE fastapi

为应用加入机器学习能力

如前所述,我们的目标是使用 Flan T5 模型做文本生成。我们还希望给页面加上一些 HTML 与 CSS,让用户可以在前端输入文本。因此先创建一个 static 目录,并在其中添加 index.htmlstyle.cssscript.js 这三个文件。此时你的项目结构大致如下:

/static
/static/index.html
/static/script.js
/static/style.css
Dockerfile
main.py
README.md
requirements.txt

下面我们依次完成必要的步骤;CSS 和 HTML 的部分会略去一些细节,完整代码可以在 DockerTemplates/fastapi_t5 的 Files and versions 页签中查看。

  1. 编写用于推理的 FastAPI 接口

我们使用 transformers 中的 pipeline 来加载 google/flan-t5-small 模型,并定义一个名为 infer_t5 的接口,接收输入文本并返回推理结果:

from transformers import pipeline

pipe_flan = pipeline("text2text-generation", model="google/flan-t5-small")

@app.get("/infer_t5")
def t5(input):
output = pipe_flan(input)
return {"output": output[0]["generated_text"]}
  1. 编写 index.html,构建一个简单的表单页面
<main>
<section id="text-gen">
<h2>Text generation using Flan T5</h2>
<p>
Model:
<a
href="https://huggingface.co/google/flan-t5-small"
rel="noreferrer"
target="_blank"
>google/flan-t5-small
</a>
</p>
<form class="text-gen-form">
<label for="text-gen-input">Text prompt</label>
<input
id="text-gen-input"
type="text"
value="German: There are many ducks"
/>
<button id="text-gen-submit">Submit</button>
<p class="text-gen-output"></p>
</form>
</section>
</main>
  1. main.py 中挂载静态文件,并在根路由返回 HTML 页面
app.mount("/", StaticFiles(directory="static", html=True), name="static")

@app.get("/")
def index() -> FileResponse:
return FileResponse(path="/app/static/index.html", media_type="text/html")
  1. script.js 中编写前端逻辑,发起请求并展示结果
const textGenForm = document.querySelector(".text-gen-form");

const translateText = async (text) => {
const inferResponse = await fetch(`infer_t5?input=${text}`);
const inferJson = await inferResponse.json();

return inferJson.output;
};

textGenForm.addEventListener("submit", async (event) => {
event.preventDefault();

const textGenInput = document.getElementById("text-gen-input");
const textGenParagraph = document.querySelector(".text-gen-output");

textGenParagraph.textContent = await translateText(textGenInput.value);
});
  1. 为相关目录赋予合适的权限

正如在权限章节中提到的,容器默认以用户 ID 1000 运行,因此可能遇到权限问题。比如,transformers 会在 HF_HOME 路径下下载并缓存模型。最简单的解决方式是创建一个拥有适当权限的用户,并使用该用户来运行容器应用。可以在 Dockerfile 中加入如下内容:

# Switch to the "user" user
USER user

# Set home to the user's home directory
ENV HOME=/home/user \
PATH=/home/user/.local/bin:$PATH

最终的 Dockerfile 应该类似这样:


# read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
# you will also find guides on how best to write your Dockerfile

FROM python:3.9

# The two following lines are requirements for the Dev Mode to be functional
# Learn more about the Dev Mode at https://huggingface.co/dev-mode-explorers
RUN useradd -m -u 1000 user
WORKDIR /app

COPY --chown=user ./requirements.txt requirements.txt
RUN pip install --no-cache-dir --upgrade -r requirements.txt

COPY --chown=user . /app

USER user

ENV HOME=/home/user \
PATH=/home/user/.local/bin:$PATH

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]

大功告成!此时你的应用应该已经可以正常运行。可以访问 DockerTemplates/fastapi_t5 查看最终效果。

一路走下来是不是收获不少?要记住 Docker Spaces 非常灵活,你并不局限于使用 FastAPI。从 Go EndpointShiny 应用,只要你能写出 Dockerfile,几乎都能跑起来。可以先看看这些官方示例。如果需要,也可以为 Space 升级到带 GPU 的硬件 😃

Debugging(调试)

你可以通过查看 BuildContainer 日志来调试 Space。点击 Open Logs 按钮即可打开日志弹窗。

如果一切顺利,你会在 Build 标签页中看到 Pushing ImageScheduling Space 等状态。

Container 标签页中,你可以看到应用的运行状态,例如本例中的 Uvicorn running on http://0.0.0.0:7860

此外,你还可以为 Space 启用 Dev Mode。Dev Mode 允许你通过 VSCode 或 SSH 连接到正在运行的 Space。详情见:https://huggingface.co/dev-mode-explorers

Read More