Update commit

This commit is contained in:
kyy
2025-01-08 15:06:58 +09:00
parent 8eb945687d
commit 4ced8db541
9 changed files with 78 additions and 95 deletions

View File

@@ -13,7 +13,6 @@ FastAPI를 이용해 클라이언트가 RESTful API를 통해 서비스와 상
- 비동기 요청을 처리하고 데이터 유효성 검사를 수행. - 비동기 요청을 처리하고 데이터 유효성 검사를 수행.
- **주요 엔드포인트**: - **주요 엔드포인트**:
- `/start-inference/`: CSV 및 모델 리스트 파일 업로드 후 추론 작업 시작. - `/start-inference/`: CSV 및 모델 리스트 파일 업로드 후 추론 작업 시작.
- `/merge-results/`: 배치별로 나뉜 결과를 단일 파일로 결합
- `/download-latest/`: 가장 최근에 완료된 결과 파일 다운로드. - `/download-latest/`: 가장 최근에 완료된 결과 파일 다운로드.
### (2) 작업 처리 계층 ### (2) 작업 처리 계층

View File

@@ -9,6 +9,12 @@ WORKDIR /opt/workspace
# Use a faster mirror for apt-get # Use a faster mirror for apt-get
RUN sed -i 's/archive.ubuntu.com/mirror.kakao.com/g' /etc/apt/sources.list RUN sed -i 's/archive.ubuntu.com/mirror.kakao.com/g' /etc/apt/sources.list
# Install tzdata and set the timezone to Asia/Seoul
RUN apt-get update && apt-get install -y tzdata && \
ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime && \
echo "Asia/Seoul" > /etc/timezone && \
dpkg-reconfigure -f noninteractive tzdata
# Install system dependencies (including dependencies for Hugging Face login) # Install system dependencies (including dependencies for Hugging Face login)
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
git \ git \

View File

@@ -47,14 +47,6 @@ http://localhost:8000/docs
- CSV 데이터와 모델 리스트를 업로드하여 추론 작업 시작 - CSV 데이터와 모델 리스트를 업로드하여 추론 작업 시작
- 결과 파일은 자동으로 저장됨 - 결과 파일은 자동으로 저장됨
2. **결과 병합** (`GET /merge-results/`): 2. **최신 결과 다운로드** (`GET /download-latest`):
- `processed` 디렉토리에 저장된 모든 `_result.csv` 파일을 병합하여 하나의 CSV 파일로 통합합니다.
- 병합된 최종 결과는 `processed/final_result.csv`로 저장되며, 이 파일의 경로가 응답으로 반환됩니다.
- **사용 사례**:
- 다중 모델 추론 결과를 한곳에 통합하여 분석하려는 경우.
- 배치별로 나뉜 결과를 단일 파일로 결합하려는 경우.
3. **최신 결과 다운로드** (`GET /download-latest`):
- 가장 최근에 완료된 추론 결과를 다운로드 - 가장 최근에 완료된 추론 결과를 다운로드
--- ---

View File

@@ -9,6 +9,7 @@ services:
volumes: volumes:
- ./workspace:/opt/workspace/ - ./workspace:/opt/workspace/
- ./cache:/root/.cache/ - ./cache:/root/.cache/
- ../model:/opt/model/
environment: environment:
PYTORCH_CUDA_ALLOC_CONF: expandable_segments:True PYTORCH_CUDA_ALLOC_CONF: expandable_segments:True
PYTHONPATH: /opt/workspace/ PYTHONPATH: /opt/workspace/
@@ -47,6 +48,7 @@ services:
volumes: volumes:
- ./workspace:/opt/workspace/ - ./workspace:/opt/workspace/
- ./cache:/root/.cache/ - ./cache:/root/.cache/
- ../model:/opt/model/
environment: environment:
PYTORCH_CUDA_ALLOC_CONF: expandable_segments:True PYTORCH_CUDA_ALLOC_CONF: expandable_segments:True
PYTHONPATH: /opt/workspace/ PYTHONPATH: /opt/workspace/

0
workspace/setting.json → setting.json Executable file → Normal file
View File

View File

@@ -11,7 +11,7 @@ import torch
from tqdm import tqdm from tqdm import tqdm
import sys import sys
sys.path.append("/opt/workspace/") sys.path.append("/workspace/LLM_asyncio")
from template import LLMInference from template import LLMInference
app = FastAPI() app = FastAPI()
@@ -27,11 +27,13 @@ logger = logging.getLogger(__name__)
# FastAPI 엔드포인트: CSV 파일 및 모델 리스트 업로드 처리 # FastAPI 엔드포인트: CSV 파일 및 모델 리스트 업로드 처리
@app.post("/start-inference/") @app.post("/start-inference/")
async def process_csv(input_csv: UploadFile, model_list_txt: UploadFile, background_tasks: BackgroundTasks): async def process_csv(input_csv: UploadFile, model_list_txt: UploadFile, background_tasks: BackgroundTasks):
logger.info(f"file_name: {input_csv},model_list_file: {model_list_txt}") # 파일 형식 확인
# 파일 형식 확인 및 저장 if not input_csv.filename.endswith(".csv"):
if not input_csv.filename.endswith(".csv") or not model_list_txt.filename.endswith(".txt"): return JSONResponse(content={"error": "Uploaded file is not a CSV."}, status_code=400)
return JSONResponse(content={"error": "Invalid file format."}, status_code=400) if not model_list_txt.filename.endswith(".txt"):
return JSONResponse(content={"error": "Uploaded model list is not a TXT file."}, status_code=400)
# 파일 저장
file_path = f"uploaded/{input_csv.filename}" file_path = f"uploaded/{input_csv.filename}"
model_list_path = f"uploaded/{model_list_txt.filename}" model_list_path = f"uploaded/{model_list_txt.filename}"
os.makedirs("uploaded", exist_ok=True) os.makedirs("uploaded", exist_ok=True)
@@ -42,41 +44,32 @@ async def process_csv(input_csv: UploadFile, model_list_txt: UploadFile, backgro
with open(model_list_path, "wb") as f: with open(model_list_path, "wb") as f:
f.write(await model_list_txt.read()) f.write(await model_list_txt.read())
df = pd.read_csv(file_path, encoding="euc-kr") logger.info(f"Files uploaded: {file_path}, {model_list_path}")
batch_size = 10
job_ids = []
# 데이터를 batch_size로 나누어 작업 큐에 추가 # 작업 큐에 추가
for i in range(0, len(df), batch_size): job = queue.enqueue(run_inference, file_path, model_list_path, job_timeout=1800)
batch_file_path = file_path.replace(".csv", f"_batch_{i}_{i+batch_size}.csv") logger.info(f"Job enqueued: {job.id}")
df.iloc[i:i+batch_size].to_csv(batch_file_path, index=False, encoding="utf-8") return {"job_id": job.id, "status": "queued"}
job = queue.enqueue(run_inference, batch_file_path, model_list_path, job_timeout=1800)
job_ids.append(job.id)
logger.info(f"Jobs enqueued: {job_ids}")
return {"job_ids": job_ids, "status": "queued"}
def chat_formating(input_sentence: str, model_name: str): def chat_formating(input_sentence: str, model_name: str):
try:
if "llama" in model_name:
hidden_prompt = LLMInference.llama_template()
if "gemma" in model_name:
hidden_prompt = LLMInference.gemma_template()
if "exaone" in model_name:
hidden_prompt = LLMInference.exaone_template()
if "llama" in model_name: formated_sentence = hidden_prompt.format(input_sent=input_sentence)
hidden_prompt = LLMInference.llama_template() return formated_sentence
elif "gemma" in model_name: except Exception as e:
hidden_prompt = LLMInference.gemma_template() logger.error(f"Not formatting input sentence: {e}")
elif "exaone" in model_name: return input_sentence
hidden_prompt = LLMInference.exaone_template()
else:
raise ValueError("Unknown model name: " + model_name)
formated_sentence = hidden_prompt.format(input_sent=input_sentence)
logger.info(f"Sentence: {formated_sentence}")
return formated_sentence
# 모델 추론 함수 # 모델 추론 함수
def run_inference(batch_file_path: str, model_list_path: str): def run_inference(file_path: str, model_list_path: str, batch_size: int = 32):
try: try:
# 워커 ID 확인 logger.info(f"Starting inference for file: {file_path} using models from {model_list_path}")
worker_id = os.environ.get("HOSTNAME", "Unknown Worker")
logger.info(f"Worker {worker_id} started inference for batch file: {batch_file_path}")
# 모델 리스트 읽기 # 모델 리스트 읽기
with open(model_list_path, "r") as f: with open(model_list_path, "r") as f:
@@ -85,59 +78,62 @@ def run_inference(batch_file_path: str, model_list_path: str):
if not model_list: if not model_list:
raise ValueError("The model list file is empty.") raise ValueError("The model list file is empty.")
# 배치 데이터 읽기 # CSV 읽기
df = pd.read_csv(batch_file_path, encoding="utf-8") df = pd.read_csv(file_path, encoding="euc-kr")
if "input" not in df.columns: if "input" not in df.columns:
raise ValueError("The input CSV must contain a column named 'input'.") raise ValueError("The input CSV must contain a column named 'input'.")
# 에러 발생한 행 저장용 DataFrame 초기화 # 에러 발생한 행 저장용 DataFrame 초기화
error_rows = pd.DataFrame(columns=df.columns) error_rows = pd.DataFrame(columns=df.columns)
# 추론 수행 # 각 모델로 추론
for model in model_list: for model in model_list:
logger.info(f"Worker {worker_id} loading model: {model}") model_name = model.split("/")[-1]
try: try:
logger.info(f"Loading model: {model}")
llm = LLM(model) llm = LLM(model)
torch.cuda.empty_cache() torch.cuda.empty_cache()
logger.info(f"Worker {worker_id} loaded model {model} successfully.") logger.info(f"Model {model} loaded successfully.")
except Exception as e: except Exception as e:
logger.error(f"Worker {worker_id} error loading model {model}: {e}") logger.error(f"Error loading model {model}: {e}")
continue continue
sampling_params = SamplingParams(max_tokens=50, temperature=0.7, top_p=0.9, top_k=50) sampling_params = SamplingParams(max_tokens=50, temperature=0.7, top_p=0.9, top_k=50)
responses = []
# tqdm 추가: 워커별 모델 진행 상태 표시 # 추론 수행
with tqdm(total=len(df), desc=f"[{worker_id}] Model: {model}") as pbar: responses = []
model_name = model.split("/")[-1] for i in tqdm(range(0, len(df), batch_size), desc=f"Processing {model}"):
for _, row in df.iterrows(): batch = df.iloc[i:i+batch_size]
batch_responses = []
for _, row in batch.iterrows():
try: try:
input_text = chat_formating(input_sentence=row["input"], model_name=model_name) original_input = row["input"]
response = llm.generate(input_text, sampling_params)[0].outputs[0].text.strip() formating_input = chat_formating(input_sentence=row["input"], model_name=model_name.lower())
logger.info(f"Model: {model}, Input: {input_text}, Output: {response}") response = llm.generate(formating_input, sampling_params)[0].outputs[0].text.strip()
responses.append(response) logger.info(f"Model: {model}, Input: {original_input}, Output: {response}")
batch_responses.append(response)
except Exception as e: except Exception as e:
logger.error(f"Worker {worker_id} error during inference for model {model}, row {row.name}: {e}") logger.error(f"Error during inference for model {model}, row {row.name}: {e}")
error_rows = pd.concat([error_rows, pd.DataFrame([row])], ignore_index=True) error_rows = pd.concat([error_rows, pd.DataFrame([row])], ignore_index=True)
responses.append(None) batch_responses.append(None)
finally: responses.extend(batch_responses)
pbar.update(1)
# 결과 추가 # 결과 추가
df[model_name] = responses df[model_name] = responses
del llm del llm
torch.cuda.empty_cache() torch.cuda.empty_cache()
gc.collect() gc.collect()
# 배치 결과 저장 # 결과 저장
output_path = batch_file_path.replace("uploaded", "processed").replace(".csv", "_result.csv") output_path = file_path.replace("uploaded", "processed").replace(".csv", "_result.csv")
os.makedirs("processed", exist_ok=True) os.makedirs("processed", exist_ok=True)
df.to_csv(output_path, index=False, encoding="utf-8") df.to_csv(output_path, index=False, encoding="utf-8")
logger.info(f"Worker {worker_id} inference completed for batch. Result saved to: {output_path}") logger.info(f"Inference completed. Result saved to: {output_path}")
# 에러 행 저장 # 에러 행 저장
if not error_rows.empty: if not error_rows.empty:
error_path = batch_file_path.replace("uploaded", "errors").replace(".csv", "_errors.csv") error_path = file_path.replace("uploaded", "errors").replace(".csv", "_errors.csv")
os.makedirs("errors", exist_ok=True) os.makedirs("errors", exist_ok=True)
error_rows.to_csv(error_path, index=False, encoding="utf-8") error_rows.to_csv(error_path, index=False, encoding="utf-8")
logger.info(f"Error rows saved to: {error_path}") logger.info(f"Error rows saved to: {error_path}")
@@ -145,32 +141,15 @@ def run_inference(batch_file_path: str, model_list_path: str):
return output_path return output_path
except Exception as e: except Exception as e:
logger.error(f"Worker {worker_id} error during inference: {e}") logger.error(f"Error during inference: {e}")
raise raise
@app.get("/merge-results/")
def merge_results():
try:
processed_dir = "processed"
all_files = [os.path.join(processed_dir, f) for f in os.listdir(processed_dir) if f.endswith("_result.csv")]
combined_df = pd.concat([pd.read_csv(f, encoding="utf-8") for f in all_files], ignore_index=True)
final_output_path = os.path.join(processed_dir, "final_result.csv")
combined_df.to_csv(final_output_path, index=False, encoding="utf-8")
logger.info(f"Final merged result saved to: {final_output_path}")
return {"final_result_path": final_output_path}
except Exception as e:
logger.error(f"Error during merging results: {e}")
return JSONResponse(content={"error": "Failed to merge results."}, status_code=500)
# 결과 파일 다운로드 # 결과 파일 다운로드
@app.get("/download-latest", response_class=FileResponse) @app.get("/download-latest", response_class=FileResponse)
def download_latest_file(): def download_latest_file():
try: try:
# processed 디렉토리 경로 # processed 디렉토리 경로
directory = "processed" directory = "processed"
csv_files = [os.path.join(directory, f) for f in os.listdir(directory) if f.endswith(".csv")] csv_files = [os.path.join(directory, f) for f in os.listdir(directory) if f.endswith(".csv")]
if not csv_files: if not csv_files:
@@ -179,7 +158,6 @@ def download_latest_file():
latest_file = max(csv_files, key=os.path.getctime) latest_file = max(csv_files, key=os.path.getctime)
logger.info(f"Downloading latest file: {latest_file}") logger.info(f"Downloading latest file: {latest_file}")
return FileResponse(latest_file, media_type="application/csv", filename=os.path.basename(latest_file)) return FileResponse(latest_file, media_type="application/csv", filename=os.path.basename(latest_file))
except Exception as e: except Exception as e:
logger.error(f"Error during file download: {e}") logger.error(f"Error during file download: {e}")

View File

@@ -6,7 +6,13 @@ from vllm import LLM
이렇게 올라온 모델을 사용해 이제 텍스트를 생성하는 text generate를 실행해보겠습니다. 다음과 같이 실행하면 됩니다. 이렇게 올라온 모델을 사용해 이제 텍스트를 생성하는 text generate를 실행해보겠습니다. 다음과 같이 실행하면 됩니다.
""" """
llm = LLM(model="yanolja/EEVE-Korean-Instruct-2.8B-v1.0", max_model_len=2048, tensor_parallel_size=1) # 모델로드 llm = LLM(
model="yanolja/EEVE-Korean-Instruct-2.8B-v1.0",
max_model_len=2048,
tensor_parallel_size=1,
) # 모델로드
requestoutput = llm.generate("안녕하십니까. 기상 캐스터 어시스턴트입니다. 오늘의 날씨는") # 입력문장 requestoutput = llm.generate(
"안녕하십니까. 기상 캐스터 어시스턴트입니다. 오늘의 날씨는"
) # 입력문장
print(requestoutput) print(requestoutput)