# 첫 번째 Docker Space: T5를 활용한 텍스트 생성

다음 섹션에서는 Docker Space를 만들고, 설정하며, 코드를 배포하는 기본 사항을 학습합니다. FastAPI를 서버로 사용하여 일부 입력 텍스트를 주면 텍스트를 생성할 수 있는 [google/flan-t5-small](https://huggingface.co/google/flan-t5-small) 모델을 데모하기 위해 Docker 기반 **Text Generation** Space를 만들 것입니다.

호스팅된 이 문서의 완성된 버전을 [여기](https://huggingface.co/spaces/DockerTemplates/fastapi_t5)에서 찾을 수 있습니다.

## 새로운 Docker Space 만들기

[새로운 Space를 생성](https://huggingface.co/new-space)하고 SDK로 **Docker**를 선택하는 것부터 시작하겠습니다.

Hugging Face Spaces는 Git 저장소이므로 커밋을 푸시하여 Space를 점진적으로(그리고 협업하여) 작업할 수 있습니다. 계속하기 전에 파일을 생성하고 편집하는 방법에 대해 알아보려면 [Getting Started with Repositories](./repositories-getting-started) 가이드를 참고하세요. UI를 사용하여 작업하는 것을 선호한다면 브라우저에서 직접 작업을 수행할 수도 있습니다.

`README.md` 파일의 YAML 블록에서 `sdk` 속성을 `docker`으로 설정하여 [새로운 Space를 생성](https://huggingface.co/new-space)할 때 **Docker**를 SDK로 선택하면 Docker Space가 초기화됩니다.

```yaml
sdk: docker
```

`README.md` 파일의 YAML 블록에서 `app_port` 속성을 설정하여 Space의 기본 애플리케이션 포트를 변경할 수 있습니다. 기본 포트는 `7860`입니다.

```yaml
app_port: 7860
```

## 의존성을 추가

**Text Generation** Space의 경우, 텍스트 생성 모델인 Flan T5를 소개하는 FastAPI 앱을 구축합니다. 모델 추론을 위해 모델을 사용하려면 [🤗 Transformers pipeline](https://huggingface.co/docs/transformers/pipeline_tutorial)을 사용합니다. 먼저 몇 가지 의존성을 설치해야 합니다. 이를 위해 저장소에 **requirements.txt** 파일을 생성하고, 아래 의존성을 추가합니다:

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

이 의존성들은 나중에 생성할 Dockerfile에 설치됩니다.

## 앱 만들기

가짜 FastAPI 앱을 만들어 엔드포인트가 정상적으로 작동하는지 확인하면서 프로세스를 시작해 보겠습니다. 첫 단계는 앱 파일을 만드는 것이며, 이 경우 파일 이름을 `main.py`로 지정합니다.

```python
from fastapi import FastAPI

app = FastAPI()

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

## Dockerfile 생성

Docker Space의 핵심 단계는 Dockerfile을 작성하는 것입니다. Dockerfile에 대한 자세한 내용은 [여기](https://docs.docker.com/get-started/)에서 확인할 수 있습니다. 이 튜토리얼에서는 FastAPI를 사용하지만, Dockerfile은 사용자에게 높은 유연성을 제공하여 새로운 세대의 ML 데모를 구축할 수 있게 합니다. 이제 우리 애플리케이션을 위한 Dockerfile을 작성해 보겠습니다.

```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가 다시 빌드되며 몇 초 후 데모가 실행됩니다! [여기](https://huggingface.co/spaces/DockerTemplates/fastapi_dummy)는 이 시점의 예시 결과입니다.

### 로컬에서 테스트

**고급 사용자 팁(건너뛰어도 됩니다):** 로컬에서 개발 중이라면, `docker build`와 `docker run`를 실행하여 로컬에서 디버깅하기에 좋은 시점입니다. 하지만 변경 사항을 Hub에 push하고 그 모습을 확인하는 것이 더 쉽습니다!

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

[Secrets](spaces-sdks-docker#secret-management)가 있는 경우 `docker buildx`를 사용하여 시크릿을 빌드 인수로 전달할 수 있습니다

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

`docker run`를 실행하고 시크릿을 환경 변수로 전달합니다

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

## 애플리케이션에 ML 추가하기

앞서 언급했듯이, 텍스트 생성을 위해 Flan T5 모델을 사용하는 것이 핵심 아이디어입니다. 입력 필드를 위해 HTML과 CSS를 추가해야 하므로, `index.html`, `style.css`, `script.js` 파일이 포함된 static이라는 이름의 디렉터리를 생성해 보겠습니다. 이 시점에서 파일 구조는 다음과 같아야 합니다:

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

이 작업을 완료하기 위한 모든 단계를 살펴보겠습니다. CSS와 HTML의 일부 세부 사항은 생략합니다. 전체 코드는 [DockerTemplates/fastapi_t5](https://huggingface.co/spaces/DockerTemplates/fastapi_t5) Space의 Files and versions 탭에서 확인할 수 있습니다.

1. FastAPI 엔드포인트를 작성하여 추론을 수행합니다

`transformers`의 `pipeline`을 사용하여 [google/flan-t5-small](https://huggingface.co/google/flan-t5-small) 모델을 로드합니다. 입력을 받아 추론 호출의 결과를 출력하는 `infer_t5`라는 엔드포인트를 설정합니다.

```python
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"]}
```

2. 페이지 코드를 포함하는 간단한 양식을 `index.html`에 작성합니다.

```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>
```

3. `main.py` 파일에서 정적 파일을 마운트하고 루트 경로에서 html 파일을 표시합니다

```python
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")
```

4. `script.js` 파일에서 요청을 처리하도록 만듭니다.

```javascript
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);
});
```

5. 적절한 디렉토리에 권한 부여

[Permissions Section](./spaces-sdks-docker#permissions)에서 논의했듯이, 컨테이너는 사용자 ID 1000으로 실행됩니다. 즉, Space에서 권한 관련 문제가 발생할 수 있습니다. 예를 들어, `transformers`는 `HF_HOME` 경로 아래에 모델을 다운로드하고 캐시합니다. 이를 해결하는 가장 쉬운 방법은 올바른 권한을 가진 사용자를 생성하고, 해당 사용자로 컨테이너 애플리케이션을 실행하는 것입니다. `Dockerfile`에 다음 줄을 추가하여 이를 수행할 수 있습니다.

```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`은 다음과 같아야 합니다:

```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](https://huggingface.co/spaces/DockerTemplates/fastapi_t5)를 살펴보세요.

정말 흥미로운 여정이었죠! Docker Spaces는 다양한 자유도를 제공하므로 FastAPI 사용에 국한되지 않습니다. [Go Endpoint](https://huggingface.co/spaces/DockerTemplates/test-docker-go)부터 [Shiny App](https://huggingface.co/spaces/DockerTemplates/shiny-with-python)까지, 한계는 달입니다! [공식 예제](./spaces-sdks-docker-examples)를 확인해 보세요. 필요 시 Space를 GPU로 업그레이드할 수도 있습니다 😃

## 디버깅

Space를 디버깅하려면 **Build** 및 **Container** 로그를 확인하세요. **Open Logs** 버튼을 클릭하여 모달을 엽니다.

모든 과정이 순조롭게 진행되었다면 **Build** 탭에서 `Pushing Image`와 `Scheduling Space`를 확인할 수 있습니다

**Container** 탭에서 애플리케이션 상태를 확인할 수 있으며, 이 경우 `Uvicorn running on http://0.0.0.0:7860`입니다.

추가로, Space에서 Dev Mode를 활성화할 수 있습니다. Dev Mode를 사용하면 실행 중인 Space에 VSCode 또는 SSH를 통해 연결할 수 있습니다. 자세한 내용은 여기에서 확인하세요: https://huggingface.co/dev-mode-explorers

## 자세히 보기

- [Docker Spaces](spaces-sdks-docker)
- [Docker Spaces 예시 목록](spaces-sdks-docker-examples)

