LLaVA-o1: 시각-언어 모델의 구조적 추론 고도화
LLaVA-o1가 구조화된 문제 해결 방식으로 멀티모달 AI의 추론 과제를 어떻게 해결하는지 알아보세요. 데이터셋, 기능, 그리고 W&B Weave를 활용한 성능 분석을 소개합니다. 이 글은 AI 번역본입니다. 오역이 있을 경우 댓글로 알려주세요.
Created on September 10|Last edited on September 10
Comment
비전-언어 모델 분야는 멀티모달 입력을 이해하고 해석하는 능력에서 큰 진전을 이뤄 왔습니다. 그럼에도 불구하고, 많은 기존 VLM은 체계적이고 구조적인 추론이 필요한 과제에서 여전히 상당한 어려움에 직면합니다. GPT-4o 같은 모델들이OpenAI스 GPT-4o Vision 이와 같은 시스템들은 직접적인 답변 생성에는 강점을 보이지만, 논리적 사고, 세밀한 분석, 수학적 추론이 필요한 복잡한 과제에서는 자주 한계를 드러냅니다.
이러한 한계는 특히 고도의 추론이 요구되는 상황에서 오류로 이어질 수 있습니다.
이러한 과제에 대응하기 위해 연구자들은 개발했습니다 LLaVA-o1. LLaVA-o1는 구조적이고 다단계의 추론 형식으로 응답하도록 명시적으로 학습되어, 기존 멀티모달 시스템의 한계를 보완하도록 설계되었습니다. 이 글에서는 해당 모델을 활용해 실험을 진행하며 Weave 평가, LLaVA-o1가 어떻게 작동하는지 더 깊이 파악하기 위해서입니다.

목차
LLaVA-o1가 비전-언어 모델로 동작하는 방식LLaVA-o1 데이터셋LLaVA-o1의 단계 수준 빔 서치LLaVA-o1과 기반 모델 비교LLaVA-o1의 한계LLaVA-o1로 추론 실행하기Weave Evaluations로 LLaVA-o1 분석하기Weave로 결과 분석하기Weave로 버그 잡기결론: LLaVA는 비전-언어 모델의 진전을 이끈 다음 단계입니다
LLaVA-o1가 비전-언어 모델로 동작하는 방식
학습 과정에서 LLaVA-o1는 응답을 네 가지 명확한 단계로 구성하도록 학습되어, 명료성과 체계적인 문제 해결을 보장합니다.
네 단계는 다음과 같습니다:
- 첫 번째 단계 (요약)은(는) 해결해야 할 내용을 큰 틀에서 파악할 수 있도록 과제를 개요하고 문제의 맥락을 설정합니다.
- 다음으로,캡션이 단계에서 모델은 이미지를 분석하여 관련된 시각 요소를 식별하고, 과제에 핵심적인 구성 요소들을 자세히 설명합니다.
- 그추론이어서 추론 단계가 진행되며, 모델은 단계별 논리 분석을 통해 중간 통찰을 도출하고, 응답이 체계적인 사고에 근거하도록 보장합니다.
- 마지막으로,결론단계에서 모델은 이전 단계의 결과를 종합해 일관되고 정확한 답변을 제시하며, 과제나 질문을 직접적으로 해결합니다.
다음은 논문에 실린 스크린샷으로, 베이스 모델과 LLaVA-o1 모델의 응답을 함께 비교한 것입니다:

LLaVA-o1 데이터셋
LLaVA-o1는 추론 집약적 과제를 강화하기 위해 정교하게 설계된 멀티모달 데이터셋인 LLaVA-o1-100k로 학습됩니다. 논문에 따르면, 이 데이터셋은 MMStar, MathVista, MMVet 등 다양한 고난도 시각 질의응답(VQA) 벤치마크에서 예제를 수집·통합하고 주석을 추가해 구성되었습니다. 이러한 데이터셋들은 비주얼 콘텐츠를 체계적으로 해석하고, 경향을 분석하며, 논리적 문제 해결을 수행해야 하는 추론 중심 질문에 초점을 맞춘 점을 기준으로 선정되었습니다.
LLaVA-o1-100k 데이터셋을 구축하기 위해, 저자들은 GPT-4o 각 과제에 대해 단계별로 자세한 주석을 생성하기 위해서입니다. 이 구조적 주석 과정은 LLaVA-o1에서 사용하는 추론 틀을 반영하여 요약, 캡션, 추론, 결론의 네 가지 뚜렷한 단계로 나뉜 추론 체인을 만들어 냈습니다. 이러한 주석은 모델이 학습 과정에서 익힐 수 있는 명확하고 체계적인 문제 해결 템플릿을 제공합니다.
저자들이 데이터셋을 구성할 때 사용한 프롬프트는 다음과 같습니다:
"I have an image and a question that I want you to answer. I need you to strictly follow the format with fourspecific sections: SUMMARY, CAPTION, REASONING, and CONCLUSION. It is crucial that you adhere to this structure exactlyas outlined and that the final answer in the CONCLUSION matches the standard correct answer precisely.To explain further: In SUMMARY, briefly explain what steps you'll take to solve the problem. In CAPTION,describe the contents of the image, specifically focusing on details relevant to the question.In REASONING, outline a step-by-step thought process you would use to solve the problem based on the image.In CONCLUSION, give the final answer in a direct format, and it must match the correct answer exactly.If it's a multiple choice question, the conclusion should only include the option without repeating what the option is.Here's how the format should look: <SUMMARY> [Summarize how you will approach the problem and explain the steps you willtake to reach the answer.] </SUMMARY> <CAPTION> [Provide a detailed description of the image, particularly emphasizingthe aspects related to the question.] </CAPTION> <REASONING> [Provide a chain-of-thought, logical explanation of the problem.This should outline step-by-step reasoning.] </REASONING> <CONCLUSION> [State the final answer in a clear and direct format.It must match the correct answer exactly.] </CONCLUSION> (Do not forget </CONCLUSION>!) Please apply this format meticulouslyto analyze the given image and answer the related question, ensuring that the answer matches the standard one perfectly."
이 데이터셋은 ShareGPT4V와 A-OKVQA 같은 범용 VQA 벤치마크의 예제와 ScienceQA, AI2D 같은 과학 분야 특화 데이터셋을 결합합니다. 또한 ChartQA와 CLEVR-Math처럼 추론 비중이 높은 특수 데이터셋의 과제도 포함해 다양한 추론 상황을 폭넓게 포괄합니다. 이렇게 도메인 전반의 과제를 통합함으로써, LLaVA-o1-100k 데이터셋은 견고한 학습 환경을 제공하고, 모델이 복잡한 실제 멀티모달 추론 과제를 효과적으로 다룰 수 있도록 합니다.
이렇게 엄선된 데이터셋은 LLaVA-o1이 체계적이고 구조적 추론 과제에서 뛰어난 성능을 발휘하도록 하는 기반을 이루며, 전통적인 VQA 데이터셋으로 학습된 모델들과 뚜렷이 구분되게 합니다.
LLaVA-o1의 단계 수준 빔 서치
학습 측면의 혁신과 더불어, LLaVA-o1는 추론 단계에서 새로운 단계 수준 빔 서치 방법을 도입합니다. 이 기법은 문제 해결 과정의 각 단계에서 여러 개의 추론 경로를 생성·평가하여, 가장 일관되고 정확한 경로를 선택합니다. 이 방법은 응답을 정교화함으로써 이론적으로 신뢰성과 정확도를 향상시키지만, 우리는 평가에서는 이를 사용하지 않기로 했습니다.
제 분석과 논문의 결과를 종합하면, LLaVA-o1의 성능 향상 대부분은 단계 수준 빔 서치보다는 LLaVA-o1-100k 데이터셋과 구조적 추론 과정에서 비롯된 것으로 보입니다. 예를 들어, 논문에 따르면 구조적 추론 틀만으로도 벤치마크 전반에서 평균 6.9%의 향상을 달성했으며, 단계 수준 빔 서치를 도입했을 때는 MMVet, MathVista처럼 추론 집중형 과제에서 고작 1~2%의 미미한 개선만 추가되었습니다.
게다가단계 수준 빔 서치는 계산 비용을 크게 증가시킵니다실시간 응용에는 실용성이 떨어집니다. 데이터셋과 구조적 추론 틀이 성능 향상의 대부분을 차지하고, 빔 서치가 가져오는 추가 이득이 복잡성 증가를 정당화하지 못하기 때문에, 우리는 평가에서 LLaVA-o1의 구조적 추론과 데이터셋 중심 학습에 집중하기로 했습니다.
LLaVA-o1과 기반 모델 비교
LLaVA-o1는 Llama-3.2-11B-Vision-Instruct 모델을 기반으로 구축되었으며, 이를 토대로 합니다. 이 기반 모델은 멀티모달 입력을 처리하고 직접 답변을 제공하도록 설계된 견고한 비전-언어 시스템입니다. 다만 일반 과제에서는 효과적이지만, 비교적 단순한 예측 기법에 의존하기 때문에 추론이 집중적으로 요구되는 상황에서는 한계를 보입니다.
LLaVA-o1의 실제 영향을 평가하기 위해, 저자들은 원래의 기반 모델뿐 아니라 동일한 LLaVA-o1-100k 데이터셋으로 학습하되 구조적 추론 단계를 포함하지 않고 직접 질의응답에만 집중하도록 미세조정(fine-tuning)된 기준 모델과도 비교를 수행했습니다.
이 미세조정 기준 모델은 LLaVA-o1에서 도입한 구조적 추론 틀의 효과를 분리하여 확인할 수 있게 해 주므로, 공정한 평가를 위해 필요합니다.
💡
미세조정 기준 모델도 추가 데이터의 이점을 얻지만, LLaVA-o1을 규정짓는 구조적 추론 과정은 갖추고 있지 않습니다. 반면 LLaVA-o1은 요약·캡션·추론·결론으로 이루어진 다단계 추론 틀을 적용하여, 기준 모델로는 어려운 복잡한 문제도 체계적으로 처리할 수 있습니다.
MMStar, MathVista, MMVet, AI2D 같은 벤치마크 평가에서 LLaVA-o1은 원래 기반 모델과 미세조정된 질의응답(Q/A) 기준 모델 모두를 크게 앞질렀습니다. 이러한 벤치마크 결과는 구조적이고 해석 가능한 출력을 생성하는 능력을 바탕으로, 논리적 추론, 수학적 문제 해결, 과학적 분석에서의 우수한 성능을 입증합니다.
LLaVA-o1의 한계
그러나 LLaVA-o1은 고도의 추론이 요구되는 과제에서는 강력한 성능을 보였지만, 구조적 추론이 필수적이지 않은 단순 과제에서는 효과가 떨어졌습니다. 예를 들어 비교적 직관적인 질의를 다루는 MMBench 같은 일반 시각 질의응답 과제에서는, LLaVA-o1의 구조적 추론 접근이 개선 폭이 미미했으며 경우에 따라 성능이 다소 저하되기도 했습니다.
마찬가지로 기본 도해 해석에 초점을 둔 AI2D 벤치마크에서도, 이 모델은 직접 학습 방식에 비해 성능이 떨어졌습니다. 다단계 추론 틀은 이러한 단순한 문제들을 불필요하게 복잡하게 만드는 경향을 보여, 즉각적인 답변이 요구되는 과제에서는 추가 추론 단계가 항상 유리하지 않을 수 있음을 시사합니다. 이러한 사례는 비전-언어 모델을 설계할 때 대상 문제의 복잡도에 맞춰 조정하는 것이 얼마나 중요한지 강조하며, 구조적 추론이 복잡한 시나리오에서는 강점을 보이지만, 직관적이고 단순한 과제에서는 오히려 성능을 저해할 수 있음을 보여 줍니다.
LLaVA-o1로 추론 실행하기
LLaVA-o1로 추론을 실행하려면, 먼저 Python 3.10과 필요한 Python 라이브러리를 설치하세요.
pip install torch==2.4.0 transformers==4.45.0 pillow==10.4.0 datasets==3.1.0 weave==0.51.18 openai==1.55.1
코드가 사용자 시스템에서 원활히 실행되도록 패키지의 정확한 버전을 명시하는 편을 선호합니다. 다만 이 튜토리얼 작성 시점보다 훨씬 뒤에 읽고 있다면, 패키지를 더 최신 버전으로 업그레이드해야 할 수도 있습니다.
💡
이제 다음 스크립트를 사용해 LLaVA-o1로 추론을 실행할 수 있습니다.
import torchfrom transformers import MllamaForConditionalGeneration, AutoProcessorfrom PIL import Imagefrom datasets import load_datasetimport weaveweave.init("llava_single_inference")@weave.op()def predict(question: str, image):"""Run inference with LLaVA on a single sample."""# Load model and processor inside the functionMODEL_ID = "Xkev/Llama-3.2V-11B-cot"model = MllamaForConditionalGeneration.from_pretrained(MODEL_ID, torch_dtype=torch.bfloat16, device_map="auto")processor = AutoProcessor.from_pretrained(MODEL_ID)pil_image = Image.open(image) if isinstance(image, str) else image# Prepare inputsmessages = [{"role": "user", "content": [{"type": "image"},{"type": "text", "text": question}]}]input_text = processor.apply_chat_template(messages, add_generation_prompt=True)inputs = processor(pil_image,input_text,add_special_tokens=False,return_tensors="pt").to(model.device)# Generate outputoutput = model.generate(**inputs,max_new_tokens=2048,return_dict_in_generate=True,output_scores=True)# Decode outputoutput_text = processor.decode(output.sequences[0], skip_special_tokens=True)return {"model_output": output_text}def create_single_sample(dataset_name: str, split: str = "test"):"""Load a single sample from the dataset."""dataset = load_dataset(dataset_name, split=split, cache_dir="./cache")example = dataset[0]question = example["qa"][0]["query"]image = example["image"]pil_image = Image.open(image) if isinstance(image, str) else imagereturn {"question": f"Based on the image, answer the following question: {question}","image": pil_image}if __name__ == "__main__":# Load a single sample and perform inferencesample = create_single_sample("TeeA/ChartQA")result = predict(sample["question"], sample["image"])# Print the resultprint(f"Question: {sample['question']}")print(f"Model Output: {result['model_output']}")
이 예시에서는, 저희는 MllamaForConditionalGeneration transformers의 클래스를 사용해 LLaVA-o1 모델을 로드하며, 이는 미세조정된 시각 정보와 텍스트 입력이 모두 필요한 추론 과제의 경우
그 AutoProcessor 클래스는 모델 입력을 준비하는 데 사용됩니다. 텍스트를 토크나이즈하고, 이미지를 호환되는 형식으로 변환하며, 원시 데이터와 모델 요구사항 간의 원활한 연계를 위해 모델 준비 입력을 생성합니다. 이 스크립트는 또한 datasets 라이브러리를 사용하여 머신러닝 작업에 필요한 데이터셋 로딩과 관리를 단순화하며, ChartQA 같은 벤치마크에도 접근할 수 있게 합니다.
W&B Weave 는 추론 과정을 추적하고 분석하기 위해 워크플로에 통��됩니다. @weave.op 데코레이터가 predict 함수를 감싸 추론 중 입력과 출력을 자동으로 로깅합니다. 이 로깅은 디버깅, 모델 평가, 특정 작업에서의 모델 동작에 대한 심층 분석에 유용합니다. 스크립트를 실행하면 weave 안에 새로운 트레이스가 생성됩니다:

이 트레이스는 프로덕션 환경에서 모델의 성능을 시각화하고, 로깅 메커니즘을 사용하지 않을 때보다 눈에 띄지 않을 수 있는 버그를 잡는 데에도 도움이 됩니다. 또한 트레이스에서는 LLaVA-o1이 답을 생성할 때 사용하는 추론 과정을 확인할 수 있습니다.
Weave Evaluations로 LLaVA-o1 분석하기
두 모델의 정성적 응답 차이를 살펴보기 위해, 전체 MMVet 데이터셋을 사용해 LLaVA-o1과 기반 모델인 Llama-3.2-11B-Vision-Instruct를 비교하는 종합 평가를 수행했습니다. 목적은 LLaVA-o1의 구조적 추론 프레임워크가, 직접 응답 방식의 기반 모델과 비교해, 추론 난도가 높은 멀티모달 과제에서 성능에 어떤 영향을 미치는지 분석하는 것이었습니다.
이상적인 비교는 모델을 포함해 미세조정된 구조적 추론 단계를 배제하고 직접 Q/A 쌍에만 집중해 MMVet 데이터셋에서 평가한 모델은 아직 공개되지 않았습니다. 따라서 본 평가는 LLaVA-o1과 기반 모델 간의 성능 차이를 파악하는 데 초점을 맞춥니다.
이 비교가 완전히 공정하지 않다는 점을 유의해야 합니다. 기반 모델은 MMVet 데이터셋의 과제나 Q/A 쌍을 전혀 접하지 않았기 때문에, LLaVA-o1은 구조적 추론 프레임워크와 데이터셋 특화 최적화를 모두 활용하는 이점을 갖습니다. 이러한 한계에도 불구하고, 본 평가는 구조적 추론이 모델 성능에 미치는 영향을 유의미하게 보여주며, 성공과 실패가 두드러지는 구체적 영역을 조명합니다.
💡
이번 평가에서는 여섯 가지 핵심 멀티모달 능력을 측정하는 전체 MMVet 데이터셋을 사용했습니다:
- 인식
- 지식 추론
- 자연어 생성
- 공간 추론
- 수학
평가는 다음을 사용하여 수행되었습니다 Weave 평가, 이를 통해 입력, 출력, 성능 지표를 나란히 비교할 수 있었습니다. Weave의 상세 비교 뷰는 LLaVA-o1의 구조적 추론 프레임워크가 특정 과제를 해결하는 데 어떻게 기여했는지, 그리고 베이스 모델의 단순 직접 응답 방식이 어디에서 한계를 드러냈는지를 파악하는 데 매우 유용했습니다.
평가는 두 모델 모두에게 MMVet의 전체 이미지-질문 쌍에 대해 예측을 생성하도록 요구하여, 다양한 추론 능력을 입증하도록 했습니다. Weave를 사용해 각 예측을 해당 입력과 정답과 함께 기록했습니다. 이 과정은 Weave Evaluations 프레임워크를 통해 전적으로 자동화되었으며, 파이프라인에 모델과 데이터셋을 통합하는 작업만 필요했습니다.
Weave의 기본 제공 도구 덕분에 모든 입력, 출력, 예측이 꼼꼼하게 추적되었습니다. 이 설정은 정량 분석과 모델 동작에 대한 정성적 탐색을 모두 가능하게 했습니다. Weave의 비교 뷰를 통해 LLaVA-o1의 구조적 추론 방식이 뛰어난 성과를 보인 사례와, 베이스 모델이 직접 응답 방식의 한계로 인해 어려움을 겪은 지점을 식별할 수 있었습니다. 전체 MMVet 데이터셋은 두 모델이 멀티모달 추론 과제를 어떻게 처리하는지 평가하기에 풍부하고 다양한 테스트베드를 제공했습니다.
LLaVA-o1과 그 베이스 모델인 Llama-3.2-11B-Vision-Instruct는 각각의 구현에서 가져왔으며 Hugging Face, 각 논문에서 명시한 설정과 가중치를 그대로 적용해 일관성을 확보했습니다. 복합적이고 통합적인 비전-언어 추론을 평가하도록 설계된 벤치마크인 MMVet은 LLaVA-o1의 구조적 추론을 베이스 모델의 단순 직접 응답 방식과 비교 검증하기에 견고한 시험틀을 제공했습니다. 여기에 Weave의 자동화된 평가 기능을 결합해 두 모델을 공정하고 통찰력 있게 비교할 수 있었습니다.
import osimport jsonimport asyncioimport loggingfrom datasets import load_datasetfrom weave import Evaluation, Modelimport weavefrom PIL import Imagefrom io import BytesIOimport base64import torchfrom transformers import MllamaForConditionalGeneration, AutoProcessorfrom typing import Dict, Any, ClassVarfrom openai import OpenAIimport time# Set up logginglogging.basicConfig(level=logging.INFO)logger = logging.getLogger(__name__)weave.init("llava_model_eval")def get_pil_image(image):"""Convert various image formats to PIL Image"""try:if isinstance(image, str):return Image.open(image)elif isinstance(image, Image.Image):return imageelif isinstance(image, bytes):return Image.open(BytesIO(image))elif isinstance(image, str) and image.startswith('data:image'):base64_data = image.split(',')[1] if ',' in image else imageimage_bytes = base64.b64decode(base64_data)return Image.open(BytesIO(image_bytes))else:raise ValueError(f"Unsupported image type: {type(image)}")except Exception as e:logger.error(f"Error processing image: {e}")raisedef evaluate_chart_answer(question: str, ground_truth: str, predicted_answer: str):"""Use GPT-4 to evaluate model answers"""client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))# # Quick check for exact match in prediction# if str(ground_truth).lower() in str(predicted_answer).lower():# return {"correct": 1}messages = [{"role": "user","content": f"""You are a judge evaluating answers to chart questions. Return ONLY a JSON object with no additional text.Question: {question}Ground Truth Answer: {ground_truth} ###Predicted Answer: {predicted_answer}Compare these answers and return a JSON object in this exact format:{{"correct": <0 or 1>}}Rules for scoring:- Score 1 if the answer is correct (or correct within reasonable error margin)- Score 1 if the correct answer is contained somewhere in the model response- Score 1 if the answer is right but the formatting is not exactly the same- Score 0 if the answer is clearly incorrect"""}]retries = 0max_retries = 10base_delay = 10while retries < max_retries:try:response = client.chat.completions.create(model="gpt-4o-2024-08-06",messages=messages,max_tokens=1024,temperature=0)response_text = response.choices[0].message.content.strip()try:return json.loads(response_text)except json.JSONDecodeError:import rejson_match = re.search(r'\{.*\}', response_text, re.DOTALL)if json_match:try:return json.loads(json_match.group())except:logger.warning("Failed to parse JSON match")logger.warning("Failed to parse response as JSON")return {"correct": 0}except Exception as e:logger.error(f"Error during evaluation (attempt {retries + 1}): {e}")retries += 1if retries < max_retries:delay = base_delay * (2 ** (retries - 1))logger.info(f"Retrying in {delay} seconds...")time.sleep(delay)else:logger.error("Max retries reached")return {"correct": 0}@weave.opdef chart_qa_scorer(question: str, ground_truth: str, model_output: dict) -> Dict[str, Any]:"""Weave operation for scoring model outputs"""try:if not isinstance(model_output, dict) or 'model_output' not in model_output:logger.error("Invalid model output format")return {'score': 0}result = evaluate_chart_answer(question=question,ground_truth=ground_truth,predicted_answer=model_output['model_output'])return {'score': result.get('correct', 0)}except Exception as e:logger.error(f"Scoring error: {e}")return {'score': 0}def get_model_prediction(model, question: str, image):"""Get prediction from model for given question and image"""try:if not model.model or not model.processor:raise ValueError("Model or processor not initialized")pil_image = get_pil_image(image)messages = [{"role": "user", "content": [{"type": "image"},{"type": "text", "text": question}]}]try:input_text = model.processor.apply_chat_template(messages, add_generation_prompt=True)except Exception as e:logger.error(f"Error applying chat template: {e}")raiseinputs = model.processor(pil_image,input_text,add_special_tokens=False,return_tensors="pt").to(model.model.device)input_tokens = len(inputs.input_ids[0])output = model.model.generate(**inputs,max_new_tokens=2048,return_dict_in_generate=True,output_scores=True)output_tokens = len(output.sequences[0]) - input_tokensoutput_text = model.processor.decode(output.sequences[0], skip_special_tokens=True)return {'model_output': str(output_text),'model': model.__class__.__name__,'usage': {'prompt_tokens': input_tokens,'completion_tokens': output_tokens,'total_tokens': input_tokens + output_tokens}}except Exception as e:logger.error(f"Error during prediction: {e}")return {'model_output': f"Error during prediction: {str(e)}",'model': model.__class__.__name__,'usage': {'prompt_tokens': 0,'completion_tokens': 0,'total_tokens': 0}}class BaseModel(Model):model: ClassVar[MllamaForConditionalGeneration] = Noneprocessor: ClassVar[AutoProcessor] = None@staticmethoddef load_model():logger.info("Loading BaseModel...")model_id = "meta-llama/Llama-3.2-11B-Vision-Instruct"try:BaseModel.model = MllamaForConditionalGeneration.from_pretrained(model_id,torch_dtype=torch.bfloat16,device_map="auto",)BaseModel.processor = AutoProcessor.from_pretrained(model_id)if not BaseModel.model or not BaseModel.processor:raise ValueError("Failed to load model or processor")logger.info("BaseModel loaded successfully")except Exception as e:logger.error(f"Error loading BaseModel: {e}")raise@staticmethoddef clear_model():logger.info("Clearing BaseModel from memory...")BaseModel.model = NoneBaseModel.processor = Nonetorch.cuda.empty_cache()@weave.opdef predict(self, question: str, image):if not BaseModel.model or not BaseModel.processor:raise ValueError("BaseModel is not loaded.")out = get_model_prediction(BaseModel, question, image)try:if "assistant" in str(out['model_output']):out['model_output'] = str(out['model_output']).split("assistant")[1].strip()except Exception as e:logger.warning(f"Error splitting output on 'assistant': {e}")return outclass LlavaModel(Model):model: ClassVar[MllamaForConditionalGeneration] = Noneprocessor: ClassVar[AutoProcessor] = None@staticmethoddef load_model():logger.info("Loading LlavaModel...")model_id = "Xkev/Llama-3.2V-11B-cot"try:LlavaModel.model = MllamaForConditionalGeneration.from_pretrained(model_id,torch_dtype=torch.bfloat16,device_map="auto",)LlavaModel.processor = AutoProcessor.from_pretrained(model_id)if not LlavaModel.model or not LlavaModel.processor:raise ValueError("Failed to load model or processor")logger.info("LlavaModel loaded successfully")except Exception as e:logger.error(f"Error loading LlavaModel: {e}")raise@staticmethoddef clear_model():logger.info("Clearing LlavaModel from memory...")LlavaModel.model = NoneLlavaModel.processor = Nonetorch.cuda.empty_cache()@weave.opdef predict(self, question: str, image):if not LlavaModel.model or not LlavaModel.processor:raise ValueError("LlavaModel is not loaded.")out = get_model_prediction(LlavaModel, question, image)try:if "<CONCLUSION>" in str(out['model_output']):out['model_output'] = str(out['model_output']).split("<CONCLUSION>")[1].strip()except Exception as e:logger.warning(f"Error splitting output on '<CONCLUSION>': {e}")return outdef create_evaluation_dataset(dataset_name: str, split: str = "test", eval_size: int = 100):"""Create evaluation dataset from the HuggingFace MMVet dataset."""try:# Load and shuffle datasetdataset = load_dataset(dataset_name, split=split, cache_dir="./cache").shuffle(seed=42)# Select the last `eval_size` examplesstart_idx = max(0, len(dataset) - eval_size)eval_data = dataset.select(range(0, len(dataset)))evaluation_dataset = []for example in eval_data:question = example["question"]ground_truth = example["answer"]image = example["image"]# Ensure image is in PIL formatif isinstance(image, str):pil_image = Image.open(image)if pil_image.mode == "RGBA":pil_image = pil_image.convert("RGB")elif isinstance(image, Image.Image):pil_image = imageelse:raise ValueError(f"Unexpected image type: {type(image)}")# Append formatted example to evaluation datasetevaluation_dataset.append({"question": "Based on the image, answer the following question: " + question,"ground_truth": ground_truth,"image": pil_image})if not evaluation_dataset:raise ValueError("No examples were processed from the dataset")logger.info(f"Created evaluation dataset with {len(evaluation_dataset)} examples")return evaluation_datasetexcept Exception as e:logger.error(f"Error creating evaluation dataset: {e}")raiseasync def run_evaluations():"""Run evaluations on both models"""try:eval_dataset = create_evaluation_dataset("lmms-lab/MMVet")results = {}# Evaluate BaseModellogger.info("\nEvaluating BaseModel...")base_model = BaseModel()base_model.load_model()evaluation = Evaluation(dataset=eval_dataset,scorers=[chart_qa_scorer],name="BaseModel Evaluation")results["base_model"] = await evaluation.evaluate(base_model)base_model.clear_model()# Evaluate LlavaModellogger.info("\nEvaluating LlavaModel...")llava_model = LlavaModel()llava_model.load_model()evaluation = Evaluation(dataset=eval_dataset,scorers=[chart_qa_scorer],name="LlavaModel Evaluation")results["llava_model"] = await evaluation.evaluate(llava_model)llava_model.clear_model()return resultsexcept Exception as e:logger.error(f"Error during evaluation: {e}")raiseif __name__ == "__main__":try:results = asyncio.run(run_evaluations())logger.info("Evaluation completed successfully")logger.info(f"Results: {results}")except Exception as e:logger.error(f"Failed to run evaluations: {e}")
이 평가를 수행하기 위해 MMVet 전체 데이터셋을 준비하고, 인식, OCR, 지식 추론, 언어 생성, 공간 추론, 수학 등 여섯 가지 핵심 멀티모달 능력을 아우르는 과제가 모두 포함되도록 했습니다. 평가 프레임워크는 LLaVA-o1과 베이스 모델 각각에 맞춘 커스텀 래퍼를 사용해 예측을 일관된 형식으로 생성·기록하도록 구성했습니다. 이 래퍼들은 전처리를 담당하여 필요 시 이미지를 PIL 형식으로 변환하고, 질문을 토크나이즈해 모델과의 호환성을 보장했습니다.
GPT-4o를 활용해 맞춤형 채점 메커니즘을 구축했으며, 모델 출력이 정답과 일치하는지를 평가했습니다. 이 채점기는 부분 정답, 형식 불일치, 허용 오차 범위 내의 답변 등 예외 상황을 처리하기 위한 구조적 규칙을 구현했습니다. GPT-4o의 평가 결과는 각 예측마다 JSON 객체로 반환되며, 응답의 정오를 표시합니다. 이 자동화된 채점 파이프라인은 일관성을 보장할 뿐만 아니라, 모델 출력의 미묘한 추론과 논리적 일관성까지 반영했습니다.
결과는 Weave에 기록·시각화되었으며, 이 도구를 통해 입력, 예측, 점수를 나란히 비교할 수 있었습니다. Weave의 강력한 비교 뷰는 모델 성능에 대한 정성·정량 분석을 정밀하게 수행할 수 있게 해 주어, LLaVA-o1의 구조적 추론 프레임워크가 뛰어난 영역과 베이스 모델의 직접 응답 방식이 효과적이었거나 미흡했던 지점을 선명하게 드러냈습니다. 이러한 종합적인 구성은 구조적 추론과 계산 효율성의 상호 작용에 대한 통찰을 제공하고, 다양한 멀티모달 과제 전반에서의 모델 동작을 보다 정교하게 이해할 수 있는 관점을 제시합니다.
Weave로 결과 분석하기
Weave Evaluations를 활용해 각 모델이 어디에서, 왜 성공하거나 어려움을 겪었는지 명확히 파악할 수 있었고, 구조적 추론이 성능에 미치는 영향을 정교하게 조망할 수 있었습니다. 이번 실험에서는 LLaVA-o1 100k 데이터셋으로 어떠한 형태의 미세조정도(fine-tuning) 적용하지 않은 베이스 모델에 비해, LLaVA-o1 모델이 소폭 더 나은 성능을 보였습니다. 전반적으로 이 기법은 여러분의 모델 성능을 끌어올릴 잠재력이 있다고 보지만, 실제로 베이스라인 대비 성능이 향상되었는지 확인하기 위해서는 철저한 평가를 강력히 권장합니다. 아래는 두 모델의 결과를 확인할 수 있는 Weave 대시보드의 스크린샷입니다:


Weave Evaluations를 사용하면 각 모델이 생성한 정확한 응답을 직접 확인하고, 이를 정답과 빠르게 비교할 수 있습니다. 이를 통해 답의 정오뿐 아니라 해당 답에 도달하기까지 모델이 거친 추론 경로까지 분석할 수 있습니다. 예를 들어 공간 이해나 수학적 추론이 필요한 복합 멀티모달 과제에서, LLaVA-o1의 구조적 추론이 올바른 응답으로 이어지는 패턴을 식별하는 한편, 이 같은 프레임워크가 단순한 과제에서는 오히려 과도하게 복잡해질 수 있는 지점도 동시에 관찰할 수 있습니다.
Weave의 정밀한 시각화 도구는 예측 답변과 정답 간의 불일치를 쉽게 찾아내어 실패 사례를 정확히 짚어낼 수 있게 해 줍니다. 오류가 맥락 이해의 부족에서 비롯됐는지, 추론 깊이의 한계인지, 아니면 이미지나 텍스트 입력을 올바르게 파싱하는 능력의 문제인지까지 평가할 수 있습니다. 또한 토큰 사용량과 지연(latency) 지표를 함께 살펴보면, 두 모델 모두에 대해 정확도와 계산 효율성 사이의 트레이드오프를 정량적으로 판단할 수 있습니다.
Weave로 버그 잡기
LLaVA-o1과 베이스 모델의 성능을 평가하는 동안, 저는 대규모 언어 모델 (LLM) 채점용 판별자로 사용했습니다. 그런데 Weave에서 분석하는 동안, 베이스 모델의 정답 예측이 오답으로 잘못 표시되는 문제가 있음을 발견했습니다. 아래 시각화에서 보이듯, 베이스 모델이 정답을 맞혔는데도 채점기가 이를 오답으로 표시하는 사례가 나타났습니다.

문제의 원인은 LLM 채점기의 채점 로직에 있었습니다. 정답을 정확히 식별하기보다, 답의 타당성과 무관한 사소한 표현 방식이나 형식 차이에 과도하게 민감하게 반응했습니다. 예를 들어 숫자 정답이 “2004”인 경우에도, 모델이 부가적인 문맥을 덧붙이거나 답변을 다른 표현으로 제시하면 기술적으로는 정답임에도 오답으로 판정되는 일이 있었습니다.
Weave의 입력·출력·채점 지표를 나란히 비교하는 화면 덕분에 이 문제는 금세 드러났습니다. 판별자가 정답 예측을 오답으로 잘못 라벨링하는 이런 오류는, 수백 개 예제를 다룰 때 ‘건초 더미에서 바늘 찾기’만큼 발견하기 어렵습니다. 일반적인 평가 파이프라인이었다면 그대로 넘어갔을 가능성이 큽니다. 하지만 Weave는 돋보기처럼 작동해, 하나의 인터페이스에서 예측 결과, 정답, 판별자의 점수를 선명하게 들여다볼 수 있게 했습니다. 시각적으로 불일치를 드러냄으로써, 문제의 원인이 모델 출력이 아니라 평가 메커니즘에 있음을 즉각 파악하게 해 주었습니다. 이러한 명확성은 원인 규명과 문제 해결에 결정적이었고, 평가 과정이 정확하고 공정해지도록 보장했습니다.
이 문제를 해결하기 위해, 기술적으로는 정답인 보다 유연한 응답을 허용하도록 프롬프트를 일부 조정해 평가 로직을 개선했습니다. 이 간단한 조정만으로도 단순한 사례에서 LLM에 대한 불필요한 의존을 없앴고, 채점 과정의 전반적인 정확도를 크게 높일 수 있었습니다. 이러한 기본 로직에 Weave의 시각화 기능을 결합해 평가 파이프라인을 정교화하고, 모델의 벤치마킹이 공정하고 신뢰할 수 있도록 보장했습니다.
결론: LLaVA는 비전-언어 모델의 진전을 이끈 다음 단계입니다
LLaVA-o1의 개발은 실용적인 비전-언어 모델을 향한 긴 여정에서 또 하나의 발전 단계입니다. 구조적 추론을 명시적으로 설계에 반영함으로써, LLaVA-o1은 기존 모델의 핵심 한계를 보완하고 단순한 질의응답 단순한 과제부터 복잡하고 추론이 많이 필요한 과제까지 폭넓게 다룹니다. 문제를 명확한 단계로 체계적으로 분해하는 능력은 구조적 사고 과정이 논리, 수학, 과학적 분석에서 AI 시스템의 역량을 어떻게 강화할 수 있는지를 잘 보여줍니다.
LLaVA-o1의 구조적 추론 프레임워크는 흥미로운 접근이지만, 단순한 과제에서의 한계는 어떤 단일 접근도 보편적으로 최적일 수 없음을 상기시킵니다. 이러한 실험에서 얻은 통찰을 바탕으로 연구자들은 방법론을 더욱 정교화하고, AI 기반 추론의 한계를 넓혀 갈 수 있습니다.
Training a KANFormer: KAN's Are All You Need?
We will dive into a new experimental architecture, replacing the MLP layers in transformers with KAN layers!
Building a real-time answer engine with Llama 3.1 405B and W&B Weave
Infusing llama 3.1 405B with internet search capabilities!!
YOLOv9 object detection tutorial
How to use one of the worlds fastest and most accurate object detectors to run inference, display on your webcam using OpenCV and tracking your results.
Add a comment