Skip to content

[2024/07/22/월] 모델 성능 비교 in LM Studio #15

Description

@SeoMiYoung

image
현재 LM Studio에 올라온 상쓰리팀의 모델은 위와 같이 2개이다.
2개의 성능을 LM Studio에서 비교해보려고 한다.

🔶 기존에 업로드 된, 두 모델 비교

일단 유정이가 요청한 프롬프트 명령어인

You are an assistant, and your task is to answer in Korean as if you are a patient visiting a doctor. The doctor is asking questions to understand your symptoms better. Answer the doctor's questions in a detailed and respectful manner in Korean.

을 적용해서 비교해보았습니다.

(1) meta_0706_gguf 모델 (16.07GB)

image
뭔가 대답이 이상하다.. 한국어인데 무슨소리인지 모르겠다..

(2) 0707_gguf 모델 (16.07GB)

image
image
image
이것도 답변이 제대로 잘 되는 건 아니지만, meta_0706_gguf보다는 성능이 괜찮은 것 같다.

🔶 다른 모델 모색

아마 첫번째 모델인 meta_0706_gguf모델은 meta-llama-instruct모델을 로라방식으로 파인튜닝한 것 같고, 두번째 모델인 0707_gguf모델은 가장 최근에 발표된 llama-3-open-KO-8B모델을 파인튜닝한 것 같다.

그치만, 두 베이스 모델 모두, 너무 용량이 커서, 파인튜닝 된 모델의 크기가 16GB를 넘어 응답 속도를 받는데 지장이 있다.

혹시 다른 더 나은 베이스 모델이 있을까 하여, 괜찮은 모델들을 탐색해서 LM Studio에서 구동해보려고 한다.

🔶 야놀자의 대형 언어 모델, EEVE 베이스

최근에 야놀자의 LLM인 EEVE가 "올해의 한국어 LLM"으로 선정되었습니다. EEVE 모델은 Efficient and Effective Vocabulary Expansion의 약자로, 효율적이고 효과적인 어휘 확장을 위한 파라미터 동결, 서브워드 기반 임베딩 초기화, 다단계 학습과 같은 접근방법을 통해 기존의 영어 언어 모델을 한국어 모델로 확장하고 새로운 언어 토큰을 통합한 모델입니다.

관련 글: https://fornewchallenge.tistory.com/entry/AI-%EB%85%BC%EB%AC%B8-%EC%98%AC%ED%95%B4%EC%9D%98-%ED%95%9C%EA%B5%AD%EC%96%B4-LLM%EC%97%90-%EC%84%A0%EC%A0%95%EB%90%9C-%EC%95%BC%EB%86%80%EC%9E%90-%EC%96%B8%EC%96%B4-%EB%AA%A8%EB%8D%B8-EEVE

그래서 EEVE모델이 베이스인 GGUF모델들을 돌려보려고 한다.
사실 다음 사진을 보시면 알겠지만, eeve모델이 베이스인 모델 엄청 많다.
image
그런데 그냥 나는 가장 사람들이 많이 다운받은 두개정도만 살펴보려고 한다.

🔸(1) heegyu/EEVE-Korean-Instruct-10.8B-v1.0-GGUF

image
해당 모델안에도 다양한 종류의 파일들이 있었기 때문에 다운 가능한 선에서 다운해보고 비교해보았다.
보면 Q다음에 4,5,6,7,8 이런식으로 숫자가 붙어있는데, 그건 양자화 수준이라고 보시면 됩니다. 즉, 숫자가 작으면 작을수록 압축이 많이 된 것 입니다. 그러다보면 어쩔 수 없이 정보 손실이 발생할 수 밖에 없겠죠? 숫자가 커질수록 성능은 좋지만, 그만큼 모델 사이즈도 커집니다. 보통 일반 컴퓨터에선, Q4나 Q5를 사용합니다.

일단 너무 많아서 모든 걸 다 테스트 해보지는 못하고, 몇개만 테스트 해보도록 하겠습니다.
당연히 모델 큰거를 감당할 수만 있다면, 성능이 좋은게 더 좋겠죠?

(1-1) heegyu/EEVE-Korean-Instruct-10.8B-v1.0-GGUF/ggml-model-Q5_K_M.gguf (7.65GB)

image
역시 응답은 굳이다!! 그치만, 아이들의 눈높이에는 대답이 적절하지 않아보인다. 그래서 프롬프트를 변경해보기로 했다.

[ 기존 프롬프트 ]

You are an assistant, and your task is to answer in Korean as if you are a patient visiting a doctor. The doctor is asking questions to understand your symptoms better. Answer the doctor's questions in a detailed and respectful manner in Korean.

의미: 당신은 조수이고, 당신의 임무는 당신이 의사를 방문하는 환자인 것처럼 한국어로 대답하는 것이다. 의사는 당신의 증상을 더 잘 이해하기 위해 질문을 하고 있다. 의사의 질문에 한국어로 상세하고 정중하게 대답한다.

[ 변경 프롬프트 ]

You are an assistant, and your task is to answer only in Korean as if you were a visiting patient. The doctor is asking questions to better understand your symptoms. You answer the doctor's questions with simple and easy expressions.

의미: 당신은 조수이고, 당신의 임무는 당신이 의사를 방문하는 환자인 것처럼 한국어로만 대답하는 것이다. 의사는 당신의 증상을 더 잘 이해하기 위해 질문을 하고 있다. 당신은 의사의 질문에 간단하고 쉬운 표현으로 대답한다.

image
image
image
훨씬 나은 것 같다.
역시...1등한 모델이라서 그런지.. 용량 크기도 적어서 응답도 빠르고 좋긴 좋은듯!

(1-2) heegyu/EEVE-Korean-Instruct-10.8B-v1.0-GGUF/ggml-model-Q4_K_M.gguf (6.51GB)

image

🔸(2) teddylee777/EEVE-Korean-Instruct-10.8B-v1.0-gguf

teddylee777분의 모델에는 heegyu모델보다 좀 더 다양한 양자화 수준들이 있어서, 조금 더 성능이 높은 걸 시도해보고자 돌려보았다.

(2-1) teddylee777/EEVE-Korean-Instruct-10.8B-v1.0-gguf/EEVE-Korean-Instruct-10.8B-v1.0-Q6_K.gguf (8.87GB)

image
image
image
조금 크기가 커져서 그런가 앞에 Q5보단 응답속도가 사~~~알짝 느린듯하다(크게 신경 쓸 정도는 아님).

(2-2) teddylee777/EEVE-Korean-Instruct-10.8B-v1.0-gguf/EEVE-Korean-Instruct-10.8B-v1.0-Q8_0.gguf (11.48GB)

image
image

🔶 결론

모든 모델을 다 실험해본 건 아니지만, 위에서 실험해 본 모델중에서는 (1-1) heegyu/EEVE-Korean-Instruct-10.8B-v1.0-GGUF/ggml-model-Q5_K_M.gguf (7.65GB)이 가장 나아보였다. 그래서 이걸 테스트 페이지에 적용해보기로 했다.

🔶 실제 적용해보기

(1) 프론트엔드 - Chat.jsx

import React, { useState } from 'react';
import Layout from '../../components/Layout/Layout';
import { useNavigate } from 'react-router-dom';
import Alert from '../../components/Alert/Alert';
import styles from './Chat.module.scss';

function Chat() {
    const [messages, setMessages] = useState([]);
    const [userMessage, setUserMessage] = useState('');
    const [loading, setLoading] = useState(false);
    const [showAlert, setShowAlert] = useState(false);
    const [isListening, setIsListening] = useState(false);
    const navigate = useNavigate();

    
    const handleInputChange = (e) => {
        setUserMessage(e.target.value);
    };

    const speakText = (text) => {
        const utterance = new SpeechSynthesisUtterance(text);
        utterance.lang = 'ko-KR';
        window.speechSynthesis.speak(utterance);
    };

    const handleSendMessage = async () => {
        if (!userMessage.trim()) return;

        const newMessages = [...messages, { sender: '사용자', text: userMessage }];
        setMessages(newMessages);
        setUserMessage('');
        setLoading(true);

        try {
            const response = await fetch('http://localhost:8000/chat', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ question: userMessage })
            });

            const data = await response.json();
            const computerMessage = { sender: '컴퓨터', text: data.response };
            setMessages([...newMessages, computerMessage]);
            speakText(computerMessage.text); // TTS 호출
        } catch (error) {
            console.error('Error:', error);
            setMessages([...newMessages, { sender: '컴퓨터', text: 'Error: 응답을 가져올 수 없습니다.' }]);
        } finally {
            setLoading(false);
        }
    };

    const handleStartListening = () => {
        if (!('webkitSpeechRecognition' in window)) {
            alert('STT를 지원하지 않는 브라우저입니다.');
            return;
        }

        const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
        recognition.lang = 'ko-KR';
        recognition.interimResults = false;
        recognition.onresult = (event) => {
            const transcript = event.results[0][0].transcript;
            setUserMessage(transcript);
            handleSendMessage();
        };

        recognition.onend = () => {
            setIsListening(false);
        };

        recognition.start();
        setIsListening(true);
    };

    const handleStopClick = () => {
        setShowAlert(true);
    };

    const handleCloseAlert = () => {
        setShowAlert(false);
    };

    const handleConfirmExit = () => {
        navigate('/result');
    };

    return (
        <Layout>
            <div className={styles.chatContainer}>
                <div className={styles.chatWrapper}>
                    <div className={styles.chatHeader}>
                        <h1>역할놀이 챗봇</h1>
                        <button className={styles.stopButton} onClick={handleStopClick}>
                            X
                        </button>
                    </div>
                    <div className={styles.chatBody}>
                        {messages.map((msg, index) => (
                            <div key={index} className={`${styles.message} ${msg.sender === '사용자' ? styles.userMessage : styles.computerMessage}`}>
                                <strong>{msg.sender}:</strong> {msg.text}
                            </div>
                        ))}
                    </div>
                    <div className={styles.userInput}>
                        <input
                            type="text"
                            id="user-message"
                            value={userMessage}
                            onChange={handleInputChange}
                            placeholder="메시지를 입력하세요..."
                            className={styles.userMessageInput}
                        />
                        <button className={styles.sendButton} onClick={handleSendMessage}>
                            전송
                        </button>
                        <button className={styles.sendButton} onClick={handleStartListening} disabled={isListening}>
                            {isListening ? '음성 인식 중...' : '음성 입력'}
                        </button>
                    </div>
                    {loading && <div className={styles.loading}>로딩 중...</div>}
                </div>
                {showAlert && (
                    <Alert
                        message="정말로 종료하시겠습니까?"
                        onConfirm={handleConfirmExit}
                        onCancel={handleCloseAlert}
                    />
                )}
            </div>
        </Layout>
    );
}

export default Chat;

[작동]

npm start

(2) fastAPI 서버 코드

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from langchain_openai import ChatOpenAI
from langchain_core.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser

app = FastAPI()

# CORS 설정
origins = [
    "http://localhost",         # 개발 중인 클라이언트 주소
    "http://localhost:3000",    # React 개발 서버 주소
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["POST"],
    allow_headers=["Content-Type"],
)

llm = ChatOpenAI(
    base_url="http://localhost:5000/v1",  # LM Studio의 URL
    api_key="lm-studio",
    model="teddylee777/Llama-3-Open-Ko-8B-gguf",
    streaming=True,
    callbacks=[StreamingStdOutCallbackHandler()],
)

class Message(BaseModel):
    question: str  # 클라이언트에서 question 필드로 전송될 것으로 기대

@app.post("/chat")
async def chat_with_bot(message: Message):
    try:
        prompt = PromptTemplate.from_template(
            """You are an assistant, and your task is to answer only in Korean as if you were a visiting patient. The doctor is asking questions to better understand your symptoms. You answer the doctor's questions with simple and easy expressions.
            
            #Question:
            {question}

            #Answer: """
        )

        chain = prompt | llm | StrOutputParser()

        response = chain.invoke({"question": message.question})  # 클라이언트로부터 받은 질문 사용
        return {"response": response}  # JSON 형태로 응답

    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

[작동]

uvicorn server1:app --reload

(3) LM Studio

5000번 포트로 연결
image

(4) 결과

Animation
흠ㅁㅁㅁㅁ 뭔가 흐름이 조금 삐그덕 거리긴 하지만, eeve를 쓴게 응답 속도도 그렇고 훨씬 나은 것 같다.

🤔 앞으로 뭘 해야할까?

(1) base-model을 다르게

유정이한테 혹시 베이스 모델을 eeve모델로 바꿔서 재파인튜닝할 수 있는지 요청해보아야겠다.

(2) 연결 재확인

뭔가 LM Studio에서만 확인했을때가 응답이 잘 이뤄지는 것 같았다.
혹시 fastAPI 연결에 문제가 있는건지 확인해보아야겠다.

(3) 최적의 프롬프트 찾기

처음에 유정이가 프롬프트를 이렇게 해보라고 했는데

You are an assistant, and your task is to answer in Korean as if you are a patient visiting a doctor. The doctor is asking questions to understand your symptoms better. Answer the doctor's questions in a detailed and respectful manner in Korean.

이렇게 바꾸어서 시도하였다.

You are an assistant, and your task is to answer only in Korean as if you were a visiting patient. The doctor is asking questions to better understand your symptoms. You answer the doctor's questions with simple and easy expressions.

혹시 최적의 프롬프트가 있는지 연구해봐야겠다.

(4) 프롬프트만 바꿔끼어서 다른 상황의 역할놀이가 가능한지 확인하기 => 저는 다음에 이걸 해보도록 하겠습니다!

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions