Skip to main content

你的第一个 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:

# 阅读文档: https://huggingface.co/docs/hub/spaces-sdks-docker
# 你还会找到如何编写 Dockerfile 的最佳实践

FROM python:3.9

# 以下两行是 Dev Mode 功能的要求
# 了解更多关于 Dev Mode 的信息请访问 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 build 和 docker 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.html、style.css 和 script.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>使用 Flan T5 进行文本生成</h2>
<p>
模型:
<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">文本提示</label>
<input
id="text-gen-input"
type="text"
value="German: There are many ducks"
/>
<button id="text-gen-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 中加入如下内容:

# 切换到 "user" 用户
USER user

# 设置 home 为用户的 home 目录
ENV HOME=/home/user \
PATH=/home/user/.local/bin:$PATH

最终的 Dockerfile 应该类似这样:


# 阅读文档: https://huggingface.co/docs/hub/spaces-sdks-docker
# 你还会找到如何编写 Dockerfile 的最佳实践

FROM python:3.9

# 以下两行是 Dev Mode 功能的要求
# 了解更多关于 Dev Mode 的信息请访问 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 Endpoint 到 Shiny 应用,只要你能写出 Dockerfile,几乎都能跑起来。可以先看看这些官方示例。如果需要,也可以为 Space 升级到带 GPU 的硬件 😃

Debugging(调试)​

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

如果一切顺利,你会在 Build 标签页中看到 Pushing Image 和 Scheduling 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

阅读更多​