# 멀티터넌트 서빙

멀티 테넌시(Multi-Tenancy)는 **기반 모델(Base Model)** 위에 사용 목적에 따라 **LoRA 어댑터(Adapter)** 를 선택하여 활용하는 기술입니다. 이번 예시에서는 중국어를 한국어로 번역하는 LoRA 모델 학습과 평가 과정을 다룹니다.

***

## 1. 데이터 구성

* 번역 학습용 데이터셋 구성
  * `zh` 열: 중국어

예시 데이터

```
0,"此时,急于直接向尹某了解冤狱的真相。"
1,"但是真正看过《极限逃生》的观众却说这部电影既是灾难片又是喜剧和动作片,是家庭剧,也是安全教育用的适合作品。"
2,primary和模特南宝拉9日在首尔清潭洞某结婚礼堂举行了婚礼。
3,韩国演歌歌手金秀灿将出演TV朝鲜的《明天是Mr.Trot》。
4,"《两天一夜》,以火花四溅的六个男人的胜负欲,来填满周日晚。"
5,"在只设计赢球的棋盘的艾古编造的棋盘上,用从他那里学到的技术玩耍的日出和完美结合的两人之间的团队配合,让人一刻也移不开视线。"
6,以下是名副其实的电影界最佳组合演员宋康昊和奉俊昊导演共同演绎的令人难忘的人生惊悚片《杀人回忆》和人生科幻大片《雪国列车》。
```

우선 정답셋을 구하기 위해서 번역 워크플로우를 생성합니다.

<figure><img src="/files/d0MUsXqb3jzVTi6oaMiQ" alt=""><figcaption></figcaption></figure>

모델 : GPT-4.1 프롬프트 : {{question}} 중국어를 한국어로 번역해주세요 한국어만 출력하고 다른 말은 하지 마세요.

배포를 하고 인증키를 받습니다.

인증키를 받는 자세한 설명은 [워크플로우 API 사용 가이드](/default/v1.7.5/advanced-tutorials/guides/api/workflow.md)를 참고하시면 됩니다.

인증키를 받은 후 파이썬 코드로 워크플로우를 호출하여 출력값을 저장합니다.

```python
import requests
import json
import os 
import pandas as pd
def run_genos_workflow(question: str):
    """
    GenOS 워크플로우를 실행하고 결과를 반환하는 함수
    """
    # --- 설정값 ---
    workflow_id = 워크플로우 아이디    #  여기에 실제 토큰을 꼭 넣어주세요!
    bearer_token = '워크플로우 인증키' 
    genos_url = 'https://genos.genon.ai:3443'

    # --- 요청 정보 ---
    url = f"{genos_url}/api/gateway/workflow/{workflow_id}/run/v2"
    
    headers = {
        'Authorization': f'Bearer {bearer_token}',
        'Content-Type': 'application/json'
    }
    
    body = {
        'question': question
    }

    # --- API 요청 ---
    try:
        # json=body를 사용하면 requests가 자동으로 JSON 문자열로 변환하고 헤더를 설정해줍니다.
        response = requests.post(url, headers=headers, json=body)
        
        # HTTP 에러가 발생했는지 확인 (4xx, 5xx 상태 코드)
        response.raise_for_status()
        
        result = response.json()
        # print('성공:', result)
        return result

    except requests.exceptions.HTTPError as http_err:
        # API 서버가 에러를 반환한 경우 (더 상세한 정보 포함)
        print(f"HTTP 에러 발생: {http_err}")
        print(f"응답 내용: {response.text}")
        return None
    except requests.exceptions.RequestException as req_err:
        # 네트워크 연결 실패 등 요청 자체에 문제가 생긴 경우
        print(f"요청 실패: {req_err}")
        return None

# --- 함수 실행 예시 ---
if __name__ == "__main__":
    # $question 변수 대신 실제 질문을 넣어 테스트합니다.

    save_result = {}

    df = pd.read_csv('중국어만 존재하는 csv 파일 주소')
    
    # 번역 결과를 저장할 리스트 초기화
    translations = []
    
    for question in df['zh']:
        workflow_result = run_genos_workflow(question)

        if workflow_result and isinstance(workflow_result, dict):
            text = workflow_result.get('data', {}).get('text')
            translations.append(text if text else "Error")
        else:
            translations.append("Error")
    
    # 결과를 새로운 열로 추가
    df['ko'] = translations
    
    # 현재 디렉터리에 저장
    df.to_csv('translate.csv', index=False)
```

* translate.csv 파일의 예시

```
Unnamed: 0,zh,ko
0,"此时,急于直接向尹某了解冤狱的真相。",이 시점에서 억울한 옥살이의 진실을 직접 윤모에게서 알아보고 싶었다.
1,"但是真正看过《极限逃生》的观众却说这部电影既是灾难片又是喜剧和动作片,是家庭剧,也是安全教育用的适合作品。","하지만 실제로 《엑시트》를 본 관객들은 이 영화가 재난 영화이면서도 코미디와 액션 영화이고, 가족 드라마이자 안전 교육에 적합한 작품이라고 말합니다."
2,primary和模特南宝拉9日在首尔清潭洞某结婚礼堂举行了婚礼。,프라이머리와 모델 남보라는 9일 서울 청담동의 한 결혼식장에서 결혼식을 올렸다.
3,韩国演歌歌手金秀灿将出演TV朝鲜的《明天是Mr.Trot》。,한국 연가 가수 김수찬이 TV조선의 '미스터트롯'에 출연할 예정이다.
4,"《两天一夜》,以火花四溅的六个男人的胜负欲,来填满周日晚。",《1박 2일》은 불꽃 튀는 여섯 남자의 승부욕으로 일요일 밤을 가득 채운다.
5,"在只设计赢球的棋盘的艾古编造的棋盘上,用从他那里学到的技术玩耍的日出和完美结合的两人之间的团队配合,让人一刻也移不开视线。","오직 승리를 위해 설계된 아이구가 만든 바둑판 위에서, 그에게서 배운 기술로 놀고 있는 일출과 완벽하게 조화를 이루는 두 사람의 팀워크는 한순간도 눈을 뗄 수 없게 만든다."
6,以下是名副其实的电影界最佳组合演员宋康昊和奉俊昊导演共同演绎的令人难忘的人生惊悚片《杀人回忆》和人生科幻大片《雪国列车》。,다음은 명실상부한 영화계 최고의 조합인 배우 송강호와 봉준호 감독이 함께한 잊을 수 없는 인생 스릴러 영화 《살인의 추억》과 인생 SF 대작 《설국열차》입니다.
7,"俱乐部说：""当时顾客很少,大多数都是宋旻浩的熟人。""","클럽은 ""당시 손님이 적었고, 대부분이 송민호의 지인이었다""고 말했다."
```

***

### 1.1 데이터셋 저장

훈련을 위해 생성한 데이터들을 저장해야합니다. 데이터는 `홈 > 데이터 > 데이터셋` 경로에 이전 단계에 만든 `.csv` 파일로 업로드합니다.

<figure><img src="/files/P61SZqBxzJHY2bMYzFU9" alt=""><figcaption></figcaption></figure>

데이터셋 목록에서 데이터 생성 버튼을 누르고 사진과 같이 입력합니다.

<figure><img src="/files/TngQ6Lnj2F4OHrg8DO7L" alt=""><figcaption></figcaption></figure>

다음으로 방금 생성한 translate.csv 파일을 업로드합니다.

생성 버튼을 클릭하면 데이터셋 생성은 완료 됩니다.

## 2. LoRA 어댑터 훈련

훈련을 위해 **SFT(Supervised Fine-Tuning)** 방식을 사용합니다.

트레이너>LLM 으로 이동하고 학습 LLM 생성 버튼을 누릅니다

### 2.1 훈련 설정

* 모델과 리비전 선택
* 데이터셋 선택 (`zh → ko` 매핑)
* 입력(`input`): `zh`
* 출력(`output`): `ko`

<figure><img src="/files/HmkeetnpeQzpmROalarm" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/JebSJ41by8AVoJsezvac" alt=""><figcaption></figcaption></figure>

***

### 2.2 하이퍼파라미터 설정

<figure><img src="/files/UNOPZGulnIflpv5gqkhW" alt=""><figcaption></figcaption></figure>

```json
{
  "max_length": 1024,
  "evaluation_strategy": "epoch",
  "save_strategy": "epoch",
  "num_train_epochs": 1,
  "per_device_train_batch_size": 1,
  "learning_rate": 0.00002,
  "bf16": 1,
  "lora_dim": 32,
  "only_optimize_lora": true,
  "save_adapter_only": 1,
  "seed": 42
}
```

**LoRA 파인튜닝을 하고 어댑터로 사용하기 위해서 아래 설정을 반드시 해야합니다.**

* **lora\_dim**: 랭크(rank) 설정
* **only\_optimize\_lora**: LoRA 파라미터만 학습
* **save\_adapter\_only**: LoRA 어댑터만 저장

학습 설정 정보는 별도로 수정하지 않아도 됩니다.

***

### 2.3 훈련 완료 확인

훈련이 완료되면 메타정보 항목에서 **기반 리비전(Base Revision)** 과 **어댑터 리비전(Adapter Revision)** 을 확인할 수 있습니다.

<figure><img src="/files/OObssiIFMVwDv7J5NsEy" alt=""><figcaption></figcaption></figure>

***

## 3. 서빙 설정 및 배포

훈련이 끝난 후, 모델을 배포하기 위해 **서빙 생성** 메뉴를 사용합니다.

서빙을 생성한 후 총 2개의 리비전을 추가해야합니다.

1. 서빙 리비전 추가
2. LoRA 어댑터 리비전 추가

### 3.1 기반 서빙 리비전 추가 및 설정

1. 서빙 리비전 추가 버튼을 누릅니다.
2. LoRA 를 사용하기 위해서 아래 설정들을 입력합니다.

* `gpu_memory_utilization`: 0.95
* `dtype`: half
* `enable_auto_tool_choice`: true
* `tool_call_parser`: hermes
* `enable_lora`: true
* `max_lora_rank`: 32

'enable\_lora'를 반드시 true로 설정해야 LoRA를 사용할 수 있습니다

<figure><img src="/files/1aGV1JKPCGrwhJ8HwiYm" alt=""><figcaption></figcaption></figure>

***

#### 3.1.1 리비전 배포 및 승인

1. 리비전 목록에서 방금 만든 리비전을 클릭하여 **배포** 버튼 클릭
2. **승인 탭**에서 승인 요청 승인하기

<figure><img src="/files/9xDvx8atVURCNlgPcsE2" alt=""><figcaption></figcaption></figure>

배포 완료 여부는 리비전 목록에서 확인할 수 있습니다.

### 3.2 어댑터 리비전 추가 및 설정

LoRA 어댑터 리비전 추가 버튼을 누릅니다

<figure><img src="/files/jfMFNp6UrPmntbpk0s6p" alt=""><figcaption></figcaption></figure>

기반 서빙 리비전 드롭다운에는 방금 배포한 리비전을 선택합니다.

LoRA 어댑터 드롭다운에서는 2장에서 훈련한 리비전을 선택합니다.

#### 3.2.1 리비전 배포 및 승인

1. 리비전 목록에서 방금 만든 리비전을 클릭하여 **배포** 버튼 클릭
2. **승인 탭**에서 승인 요청 승인하기

배포가 완료는 리비전 목록에서 확인할 수 있습니다.

### 3.3 트래픽 분배 설정

이제 대표 리비전을 설정하기 위해 각 리비전에 트래픽을 어떻게 분배할지 지정해야 합니다.

<figure><img src="/files/y7ME6XoIEGzNWzWAHMwE" alt=""><figcaption></figcaption></figure>

사진의 우측에 기반 리비전과 어댑터를 적용한 리비전 2개에 각각 트래픽을 절반 부여하겠습니다.

**서빙** 메뉴에서 대표 리비전에서 적용된것을 확인할 수 있습니다.

***

## 4. LoRA 어댑터 적용 전/후 평가

평가는 워크플로우 상에서 **GPT-4.1 평가 노드**를 통해 진행하겠습니다.

**(주의사항)** 평가를 위해 총 2개의 워크플로우가 필요합니다.

1. 어댑터 리비전을 적용하지 않은 워크플로우 (기반 모델)
2. 어댑터를 적용한 워크플로우

이 두 워크플로우는 반드시 동일한 구조로 구성해야 합니다.

두 워크플로우의 차이점은 Translate(zh→ko) 노드에서 리비전을 다르게 설정하는 부분만 차이가 있습니다.

### 4.1 워크플로우 구성

<figure><img src="/files/68UrMjDdWUxZBnfh2fGu" alt=""><figcaption></figcaption></figure>

#### 4.1.1 **Translate(zh→ko)** 노드

```
  - 프롬프트: `"{question}를 한국어로 번역해줘 다른 말은 할 필요 없어."`
```

<figure><img src="/files/nwkdOmw3W4dfME5NqRNc" alt=""><figcaption></figcaption></figure>

해당 테스트에서는 1117(기반모델)/ 1130(어댑터 적용)

각각 워크플로우에 설정하시면 됩니다.

#### 4.1.2 parsing(노드) **(선택사항)**

해당 Qwen3 - 0.6b는 답변을 할 때 추론 과정을 출력합니다.

```
<think> 추론과정</think>
```

위 방식으로 추가적으로 출력이 됩니다.

이 부분을 제거하는 함수를 중간 노드로 추가하겠습니다.

예시 코드

```js
const answer = $result.split('</think>').pop();

return answer;
```

$result는 Translate 노드에서 만들어진 결과입니다.

#### 4.1.3. **Evaluation(GPT-4.1)** 노드

```
  - 프롬프트 예시:
    
    다음 중국어 문장이 한국어로 잘 번역되었는지 평가해주세요.
    1. 정확성(Accuracy)
    2. 유창성(Fluency)
    3. 일관성(Consistency)
    4. 의미 보존(Semantic Fidelity)
    5. 오류 유형 분석(Error)

    각 항목을 종합하여 0~1 사이 float 점수로 평가하세요.
    다른 말은 출력하지 말고 점수만 출력하세요.

    번역전 : {question}
    번역후 : {llmAgentflow_0} 
    
```

## 5 **평가하기(평가>평가지표>워크플로우)**

### 5.1 평가셋 구성하기

테스트를 진행하기 위해 훈련한 데이터와 다른 중국어로만 구성된 100개의 데이터를 구성하고 데이터셋을 생성합니다.

<figure><img src="/files/c8laZodiveUv2KtygzQ3" alt=""><figcaption></figcaption></figure>

### 5.2 평가지표 생성하기

1. '평가>평가지표>워크플로우'로 이동합니다.
2. 평가지표 워크플로우 생성하기 버튼을 누릅니다.
3. 제목, 관리그룹, 데이터셋 리비전, 파이썬 코드를 작성해야합니다.

<figure><img src="/files/dbVRW7jCKgp2He2aqjx9" alt=""><figcaption></figcaption></figure>

예시 python 코드

```python
async def evaluate(row: dict, predict: callable) -> float:
    import ast

    question = row['zh']
    
    try:
        predicted_answer = await predict(question)

        text_output = predicted_answer.get('text') # .get()을 사용해 더 안전하게 접근
          
        return text_output

    except Exception as e:
        print(f"Evaluate 함수 오류 발생: {e}")
        return 0.0
    
```

***

### 5.3 리더보드 생성

<figure><img src="/files/E6b9YDjJuFge1orE1IPm" alt=""><figcaption></figcaption></figure>

1. '평가>리더보드>워크플로우'로 이동합니다.
2. 리더보드 워크플로우 생성하기 버튼을 누릅니다.
3. 제목, 관리그룹을 입력하고, 5.1장에서 생성한 평가지표(기반 모델용, 어댑터 적용)를 선택합니다.
4. 생성이 완료되면, 리더보드 목록에서 '평가 실행하기' 버튼을 클릭합니다.
5. 평가 실행 버튼을 누릅니다. (4.1에서 만든 평가 워크플로우 선택)

<figure><img src="/files/PpEf0kzPAO85DRGhQ8Wy" alt=""><figcaption></figcaption></figure>

## 결과

마지막으로 리더보드에서 2개의 워크플로우를 실행하고 결과를 확인하시면 됩니다.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://genos-docs.gitbook.io/default/v1.7.5/advanced-tutorials/guides/serving/multitenancy.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
