Add the Language Translation Example (#94)

Signed-off-by: zehao-intel <zehao.huang@intel.com>
Signed-off-by: chensuyue <suyue.chen@intel.com>
This commit is contained in:
zehao-intel
2024-04-30 17:54:29 +08:00
committed by GitHub
parent f1b4aef062
commit 5c71489921
11 changed files with 617 additions and 0 deletions

61
.github/workflows/Translation.yml vendored Normal file
View File

@@ -0,0 +1,61 @@
# Copyright (c) 2024 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Translation-test
on:
pull_request:
branches: [main]
types: [opened, reopened, ready_for_review, synchronize] # added `ready_for_review` since draft is skipped
paths:
- Translation/**
- "!**.md"
- "!**/ui/**"
- .github/workflows/Translation.yml
workflow_dispatch:
# If there is a new commit, the previous jobs will be canceled
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
Translation:
runs-on: aise-cluster
strategy:
matrix:
job_name: ["langchain"]
fail-fast: false
steps:
- name: Clean Up Working Directory
run: sudo rm -rf ${{github.workspace}}/*
- name: Checkout out Repo
uses: actions/checkout@v4
with:
ref: "refs/pull/${{ github.event.number }}/merge"
- name: Run Test
env:
HUGGINGFACEHUB_API_TOKEN: ${{ secrets.HUGGINGFACEHUB_API_TOKEN }}
run: |
cd ${{ github.workspace }}/Translation/tests
bash test_${{ matrix.job_name }}_inference.sh
- name: Publish pipeline artifact
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.job_name }}
path: ${{ github.workspace }}/Translation/tests/*.log

51
Translation/README.md Normal file
View File

@@ -0,0 +1,51 @@
# Language Translation
Language Translation is the communication of the meaning of a source-language text by means of an equivalent target-language text.
The workflow falls into the following architecture:
![architecture](https://i.imgur.com/5f9hoAW.png)
# Start Backend Service
1. Start the TGI service to deploy your LLM
```sh
cd serving/tgi_gaudi
bash build_docker.sh
bash launch_tgi_service.sh
```
`launch_tgi_service.sh` by default uses `8080` as the TGI service's port. Please replace it if there are any port conflicts.
2. Start the Language Translation service
```sh
cd langchain/docker
bash build_docker.sh
docker run -it --name translation_server --net=host --ipc=host -e TGI_ENDPOINT=${TGI_ENDPOINT} -e HUGGINGFACEHUB_API_TOKEN=${HUGGINGFACEHUB_API_TOKEN} -e SERVER_PORT=8000 -e http_proxy=${http_proxy} -e https_proxy=${https_proxy} translation:latest bash
```
Here is the explanation of some of the above parameters:
- `TGI_ENDPOINT`: The endpoint of your TGI service, usually equal to `<ip of your machine>:<port of your TGI service>`.
- `HUGGINGFACEHUB_API_TOKEN`: Your HuggingFace hub API token, usually generated [here](https://huggingface.co/settings/tokens).
- `SERVER_PORT`: The port of the Translation service on the host.
3. Quick test
```sh
curl http://localhost:8000/v1/translation \
-X POST \
-d '{"language_from": "Chinese","language_to": "English","source_language": "我爱机器翻译。"}' \
-H 'Content-Type: application/json'
```
The shortcodes of languages are also supported:
```sh
curl http://localhost:8000/v1/translation \
-X POST \
-d '{"language_from": "de","language_to": "en","source_language": "Maschinelles Lernen"}' \
-H 'Content-Type: application/json'
```

View File

@@ -0,0 +1,50 @@
# Copyright (c) 2024 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# SCRIPT USAGE NOTICE: By downloading and using any script file included
# with the associated software package (such as files with .bat, .cmd, or
# .JS extensions, Docker files, or any other type of file that, when executed,
# automatically downloads and/or installs files onto your system) (the “Script File”),
# it is your obligation to review the Script File to understand what files (e.g.,
# other software, AI models, AI Datasets) the Script File will download to your system
# (“Downloaded Files”). Furthermore, by downloading and using the Downloaded Files,
# even if they are installed through a silent install, you agree to any and all
# terms and conditions associated with such files, including but not limited to,
# license terms, notices, or disclaimers.
FROM intel/intel-optimized-pytorch:2.2.0-pip-jupyter
RUN apt-get update -y && apt-get install -y --no-install-recommends --fix-missing \
libgl1-mesa-glx \
libjemalloc-dev \
vim
RUN useradd -m -s /bin/bash user && \
mkdir -p /home/user && \
chown -R user /home/user/
USER user
COPY requirements.txt /tmp/requirements.txt
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r /tmp/requirements.txt
ENV PYTHONPATH=/home/user:/home/user/translation-app/app
WORKDIR /home/user/translation-app
COPY --chown=user:user translation-app /home/user/translation-app
ENTRYPOINT ["python", "server.py"]

View File

@@ -0,0 +1,17 @@
#!/bin/bash
# Copyright (c) 2024 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
docker build . -t translation:latest --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy

View File

@@ -0,0 +1,4 @@
fastapi
huggingface_hub
langchain
uvicorn

View File

@@ -0,0 +1,25 @@
# Copyright (c) 2024 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from langchain.prompts import PromptTemplate
prompt_template = """
Translate this from {language_from} to {language_to}:
{language_from}:
{source_language}
{language_to}:
"""
translation_prompt_template = PromptTemplate.from_template(prompt_template)

View File

@@ -0,0 +1,179 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2024 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
from fastapi import APIRouter, FastAPI, HTTPException, Request
from fastapi.responses import StreamingResponse
from langchain_community.llms import HuggingFaceEndpoint
from prompts import translation_prompt_template
from starlette.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
TGI_ENDPOINT = os.getenv("TGI_ENDPOINT", "http://localhost:8080")
SERVICE_PORT = os.getenv("SERVER_PORT", 8000)
short_cut_mapping = {
"en": "English",
"de": "German",
"fr": "French",
"es": "Spanish",
"it": "Italian",
"pt": "Portuguese",
"ru": "Russian",
"zh": "Chinese",
"ja": "Japanese",
"ko": "Korean",
"sv": "Swedish",
"nl": "Dutch",
"no": "Norwegian",
"da": "Danish",
"ar": "Arabic",
"hi": "Hindi",
"tr": "Turkish",
"pl": "Polish",
"fi": "Finnish",
"el": "Greek",
"cs": "Czech",
"hu": "Hungarian",
"id": "Indonesian",
"is": "Icelandic",
"ms": "Malay",
"th": "Thai",
"uk": "Ukrainian",
"vi": "Vietnamese",
"ro": "Romanian",
"he": "Hebrew",
"bn": "Bengali",
"bg": "Bulgarian",
"ca": "Catalan",
"hr": "Croatian",
"pirate": "Pirate",
"yoda": "Yoda",
"minion": "Minion",
}
class TranslationAPIRouter(APIRouter):
"""The router for Language Translation example."""
def __init__(self, entrypoint: str, prompt_template: str) -> None:
super().__init__()
self.entrypoint = entrypoint
# setup TGI endpoint
self.llm = HuggingFaceEndpoint(
endpoint_url=entrypoint,
max_new_tokens=1024,
top_k=10,
top_p=0.95,
typical_p=0.95,
temperature=0.01,
repetition_penalty=1.03,
streaming=True,
)
self.prompt_template = prompt_template
def handle_translation(self, language_from: str, language_to: str, source_language: str):
if language_from in short_cut_mapping.keys():
language_from = short_cut_mapping[language_from]
if language_to in short_cut_mapping.keys():
language_to = short_cut_mapping[language_to]
prompt = self.prompt_template.format(
language_from=language_from, language_to=language_to, source_language=source_language
)
print(f"[translation - nonstream] prompt:{prompt}")
try:
response = self.llm(prompt)
response = {"target_language": response.replace("</s>", "").lstrip()}
except Exception as e:
print(f"[translation - nonstream] Error occurred: {e}")
raise Exception(f"[translation - nonstream] {e}")
print(f"[translation - nonstream] response:\n{response}")
return response
async def handle_translation_stream(self, language_from: str, language_to: str, source_language: str):
if language_from in short_cut_mapping.keys():
language_from = short_cut_mapping[language_from]
if language_to in short_cut_mapping.keys():
language_to = short_cut_mapping[language_to]
prompt = self.prompt_template.format(
language_from=language_from, language_to=language_to, source_language=source_language
)
print(f"[translation - stream] prompt:{prompt}")
async def stream_generator():
async for chunk in self.llm.astream_log(prompt):
print(f"[translation - stream] data: {chunk}")
yield f"data: {chunk}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(stream_generator(), media_type="text/event-stream")
router = TranslationAPIRouter(entrypoint=TGI_ENDPOINT, prompt_template=translation_prompt_template)
@router.post("/v1/translation")
async def translation(request: Request):
params = await request.json()
print(f"[translation - nonstream] POST request: /v1/translation, params:{params}")
language_from = params["language_from"]
language_to = params["language_to"]
source_language = params["source_language"]
try:
return router.handle_translation(
language_from=language_from, language_to=language_to, source_language=source_language
)
except Exception as e:
print(f"[translation - nonstream] Error occurred: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/v1/translation_stream")
async def translation_stream(request: Request):
params = await request.json()
print(f"[translation - stream] POST request: /v1/translation_stream, params:{params}")
language_from = params["language_from"]
language_to = params["language_to"]
source_language = params["source_language"]
try:
return await router.handle_translation_stream(
language_from=language_from, language_to=language_to, source_language=source_language
)
except Exception as e:
print(f"[translation - stream] Error occurred: {e}")
raise HTTPException(status_code=500, detail=str(e))
app.include_router(router)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=int(SERVICE_PORT))

View File

@@ -0,0 +1,3 @@
FROM ghcr.io/huggingface/tgi-gaudi:1.2.1
RUN pip install peft==0.6.2

View File

@@ -0,0 +1,16 @@
#!/bin/bash
# Copyright (c) 2024 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
docker build . -t tgi-gaudi-translation:1.2.1 --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy

View File

@@ -0,0 +1,50 @@
#!/bin/bash
# Copyright (c) 2024 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Set default values
default_port=8080
default_model="haoranxu/ALMA-13B"
default_num_cards=1
# Check if all required arguments are provided
if [ "$#" -lt 0 ] || [ "$#" -gt 3 ]; then
echo "Usage: $0 [num_cards] [port_number] [model_name]"
exit 1
fi
# Assign arguments to variables
num_cards=${1:-$default_num_cards}
port_number=${2:-$default_port}
model_name=${3:-$default_model}
# Check if num_cards is within the valid range (1-8)
if [ "$num_cards" -lt 1 ] || [ "$num_cards" -gt 8 ]; then
echo "Error: num_cards must be between 1 and 8."
exit 1
fi
# Set the volume variable
volume=$PWD/data
# Build the Docker run command based on the number of cards
if [ "$num_cards" -eq 1 ]; then
docker_cmd="docker run -d --name tgi-gaudi-server-translation -p $port_number:80 -v $volume:/data --runtime=habana -e HABANA_VISIBLE_DEVICES=all -e OMPI_MCA_btl_vader_single_copy_mechanism=none --cap-add=sys_nice --ipc=host -e HTTPS_PROXY=$https_proxy -e HTTP_PROXY=$https_proxy tgi-gaudi-translation:1.2.1 --model-id $model_name"
else
docker_cmd="docker run -d --name tgi-gaudi-server-translation -p $port_number:80 -v $volume:/data --runtime=habana -e PT_HPU_ENABLE_LAZY_COLLECTIVES=true -e HABANA_VISIBLE_DEVICES=all -e OMPI_MCA_btl_vader_single_copy_mechanism=none --cap-add=sys_nice --ipc=host -e HTTPS_PROXY=$https_proxy -e HTTP_PROXY=$https_proxy tgi-gaudi-translation:1.2.1 --model-id $model_name --sharded true --num-shard $num_cards"
fi
# Execute the Docker run command
eval $docker_cmd

View File

@@ -0,0 +1,161 @@
#!/bin/bash
# Copyright (c) 2024 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -xe
function test_env_setup() {
WORKPATH=$(dirname "$PWD")
LOG_PATH="$WORKPATH/tests/langchain.log"
TGI_CONTAINER_NAME="test-tgi-gaudi-server"
LANGCHAIN_CONTAINER_NAME="test-translation-gaudi"
}
function rename() {
# Rename the docker container/image names to avoid conflict with local test
cd ${WORKPATH}
sed -i "s/tgi-gaudi-server-translation/${TGI_CONTAINER_NAME}/g" serving/tgi_gaudi/launch_tgi_service.sh
}
function launch_tgi_gaudi_service() {
local card_num=1
local port=8870
local model_name="haoranxu/ALMA-13B"
cd ${WORKPATH}/serving/tgi_gaudi
bash build_docker.sh
bash launch_tgi_service.sh $card_num $port $model_name
sleep 2m
}
function launch_langchain_service() {
cd $WORKPATH
local port=8875
cd langchain/docker
docker build . --build-arg http_proxy=${http_proxy} --build-arg https_proxy=${http_proxy} -t intel/gen-ai-examples:${LANGCHAIN_CONTAINER_NAME}
docker run -d --name=${LANGCHAIN_CONTAINER_NAME} --net=host -e TGI_ENDPOINT=http://localhost:8870 -e HUGGINGFACEHUB_API_TOKEN=${HUGGINGFACEHUB_API_TOKEN} \
-e SERVER_PORT=${port} -e http_proxy=${http_proxy} -e https_proxy=${https_proxy} --ipc=host intel/gen-ai-examples:${LANGCHAIN_CONTAINER_NAME}
sleep 2m
}
function run_tests() {
cd $WORKPATH
local port=8875
# response: {"target_language":"I love machine translation"}
curl http://localhost:${port}/v1/translation \
-X POST \
-d '{"language_from": "zh","language_to": "en","source_language": "我爱机器翻译。"}' \
-H 'Content-Type: application/json' > $LOG_PATH
#response: {"target_language":"我是一名翻译"}
curl http://localhost:${port}/v1/translation \
-X POST \
-d '{"language_from": "English","language_to": "Chinese","source_language": "I am a translator"}' \
-H 'Content-Type: application/json' >> $LOG_PATH
# response: {"target_language":"Hallo Welt"}
curl http://localhost:${port}/v1/translation \
-X POST \
-d '{"language_from": "en","language_to": "de","source_language": "hello world"}' \
-H 'Content-Type: application/json' >> $LOG_PATH
# response: {"target_language":"Machine learning"}
curl http://localhost:${port}/v1/translation \
-X POST \
-d '{"language_from": "German","language_to": "English","source_language": "Maschinelles Lernen"}' \
-H 'Content-Type: application/json' >> $LOG_PATH
# response: {"target_language":"Ég er glöð"}
curl http://localhost:${port}/v1/translation \
-X POST \
-d '{"language_from": "en","language_to": "is","source_language": "I am happy"}' \
-H 'Content-Type: application/json' >> $LOG_PATH
# response: {"target_language":"Hello world"}
curl http://localhost:${port}/v1/translation \
-X POST \
-d '{"language_from": "Icelandic","language_to": "English","source_language": "Halló heimur"}' \
-H 'Content-Type: application/json' >> $LOG_PATH
# response: {"target_language":"Velká jazyková model"}
curl http://localhost:${port}/v1/translation \
-X POST \
-d '{"language_from": "en","language_to": "cs","source_language": "Large Language Model"}' \
-H 'Content-Type: application/json' >> $LOG_PATH
# response: {"target_language":"I'm glad to see you"}
curl http://localhost:${port}/v1/translation \
-X POST \
-d '{"language_from": "Czech","language_to": "English","source_language": "rád tě vidím"}' \
-H 'Content-Type: application/json' >> $LOG_PATH
# response: {"target_language":"Хотите танцевать"}
curl http://localhost:${port}/v1/translation \
-X POST \
-d '{"language_from": "English","language_to": "ru","source_language": "Shall we dance?"}' \
-H 'Content-Type: application/json' >> $LOG_PATH
# response: {"target_language":"operating system"}
curl http://localhost:${port}/v1/translation \
-X POST \
-d '{"language_from": "Russian","language_to": "English","source_language": "операционная система"}' \
-H 'Content-Type: application/json' >> $LOG_PATH
}
function check_response() {
cd $WORKPATH
echo "Checking response"
local status=false
if [[ -f $LOG_PATH ]] && [[ $(grep -c "I love machine translation" $LOG_PATH) != 0 ]] && \
[[ $(grep -c "我是一名翻译" $LOG_PATH) != 0 ]] && [[ $(grep -c "Hallo Welt" $LOG_PATH) != 0 ]] && \
[[ $(grep -c "Machine learning" $LOG_PATH) != 0 ]] && [[ $(grep -c "Ég er glöð" $LOG_PATH) != 0 ]] && \
[[ $(grep -c "Velká jazyková model" $LOG_PATH) != 0 ]] && [[ $(grep -c "I'm glad to see you" $LOG_PATH) != 0 ]] && \
[[ $(grep -c "operating system" $LOG_PATH) != 0 ]]; then
status=true
fi
if [ $status == false ]; then
echo "Response check failed, please check the logs in artifacts!"
exit 1
else
echo "Response check succeed!"
fi
}
function docker_stop() {
local container_name=$1
cid=$(docker ps -aq --filter "name=$container_name")
if [[ ! -z "$cid" ]]; then docker stop $cid && docker rm $cid; fi
}
function main() {
test_env_setup
rename
docker_stop $TGI_CONTAINER_NAME && docker_stop $LANGCHAIN_CONTAINER_NAME && sleep 5s
launch_tgi_gaudi_service
launch_langchain_service
run_tests
check_response
docker_stop $TGI_CONTAINER_NAME && docker_stop $LANGCHAIN_CONTAINER_NAME && sleep 5s
echo y | docker system prune
}
main