Changyu Lee

Ollama Template 파헤치기 - Go Template 문법을 중심으로

Published at
2024/10/04
Last edited time
2024/10/04 04:34
Created
2024/10/04 01:39
Section
ML
Status
Done
Series
Tags
Basic
AI summary
Keywords
LLM
Ollama
Template
Language

개요

Ollama를 사용하여 모델의 파라미터나 템플릿을 수정하는 와중에 관련 자료들을 여러가지 찾아보고 정리할 필요가 있어서 정리하고자 한다.
주로, Ollama Modelfile의 “Template” 요소에 대한 설명을 다루며 llama 3.1. 템플릿까지 살펴본다.

Template

Go Template

문서에서는 Go 템플릿이 뭔지 먼저 설명하고 있다. Go Template는 Go 언어에서 제공하는 텍스트/HTML 템플릿 엔진으로, 데이터를 템플릿에 삽입하고 이를 처리하여 동적인 웹 페이지, 이메일, 보고서 등을 생성하는 데 사용된다.
기본 템플릿 구조 by Ollama
다만, 만약 템플릿을 직접 만들어서 해보고 싶다면 이 설명만으로는 부족할 수 있어 Go Template 문법을 기반으로 조금 더 파헤쳐보자.
Go Template 문법
이 템플릿 코드를 바탕으로, Go Template에서 Llama LLM 모델의 프롬프트 포맷팅을 위한 템플릿을 어떻게 정의하는지 살펴보자.

템플릿 예시 분석

FROM llama3.1 TEMPLATE """{{- if .System }}<|start_header_id|>system<|end_header_id|> {{ .System }}<|eot_id|> {{- end }} {{- range .Messages }}<|start_header_id|>{{ .Role }}<|end_header_id|> {{ .Content }}<|eot_id|> {{- end }}<|start_header_id|>assistant<|end_header_id|> """
Go
복사
이 템플릿은 주로 두 가지 중요한 데이터 요소를 처리한다.
1.
System 메시지: 모델에게 시스템 명령을 내리는 메시지.
2.
대화 메시지 (User / Assistant) : 사용자와 모델 간의 주고받은 대화 내용.
Ollama는 이 두 가지를 템플릿으로 포맷하여 모델이 사용할 수 있게끔 데이터를 정리해준다. 이제 이 템플릿의 각 부분을 자세히 살펴보자.
1. {{- if .System }}{{- end }}
이 부분은 조건문으로, .System이라는 값이 있는지 확인한다. 만약 .System이 존재한다면, 해당 메시지를 렌더링한다.
{{ .System }}: .System은 템플릿이 바인딩할 데이터 중 시스템 메시지를 담고 있는 필드이다. 이를 통해 시스템 메시지를 출력한다.
<|start_header_id|>system<|end_header_id|>: system 메시지가 시작된다는 신호를 나타내는 특수한 토큰이다. 이 토큰은 LLM 모델에게 "시스템 메시지가 시작된다"라는 정보를 전달한다.
<|eot_id|>: 메시지가 끝났다는 의미로 사용되는 토큰이다. End of Transmission (EOT)의 약자로, 메시지의 끝을 나타냅니다.
{{- end }} 는 공백/줄바꿈 제거와 조건문/반복문의 끝을 의미하며, 두 가지로 구성된다
- 기호는 템플릿에서 그 앞에 오는 공백이나 줄바꿈을 제거하는 역할
{{ end }} 기호는 조건문이나 반복문의 끝을 나타냄
예를 들어, .System 값이 "HELLO"이라면, 이 부분은 다음과 같이 렌더링됩니다:
<|start_header_id|>system<|end_header_id|> HELLO<|eot_id|>
Plain Text
복사
2. {{- range .Messages }}
이 부분은 반복문.
.Messages는 대화 메시지들이 담긴 배열 또는 슬라이스로, 템플릿은 이 배열을 순회하며 각 메시지를 출력하게 된다.
{{ .Role }}: 각 메시지에서 Role 필드는 메시지의 발신자 역할을 나타낸다.
예를 들어, user 또는 assistant 같은 값이 있을 수 있습니다. 이 값에 따라 메시지가 누구로부터 온 것인지 구분한다.
{{ .Content }}: Content 필드는 실제 메시지 내용을 의미한다. 이 값이 템플릿에서 출력된다.
<|start_header_id|>{{ .Role }}<|end_header_id|>: Role에 해당하는 메시지가 시작되었다는 것을 나타내는 특수한 토큰이다.
<|eot_id|>: 메시지의 끝을 의미하는 토큰이다.
예를 들어, MessagesRole: "user", Content: "Hello"라는 메시지가 있다면, 다음과 같이 렌더링됨:
<|start_header_id|>user<|end_header_id|> Hello<|eot_id|>
Plain Text
복사
3. assistant 출력
마지막으로, 이 부분은 LLM 모델이 assistant 역할로 응답할 차례임을 알려준다.
<|start_header_id|>assistant<|end_header_id|>: 이 부분은 템플릿이 assistant의 응답이 올 위치임을 모델에게 알려주는 신호로서 사용된다.
즉, 모델이 이 부분 이후에 응답을 채우게 된다.
전체 흐름 정리
1.
시스템 메시지가 있으면 먼저 출력되고, 없다면 건너뜁니다.
2.
대화 메시지 목록을 순회하며, 각 메시지의 발신자와 내용을 출력합니다.
3.
마지막으로 assistant의 응답 위치를 명시한 후 템플릿이 끝납니다.
예시 데이터
이 템플릿이 실제로 어떻게 렌더링되는지 예시 데이터를 바탕으로 살펴보자.
{ "System": "Initialize model", "Messages": [ {"Role": "user", "Content": "Hello"}, {"Role": "assistant", "Content": "Hi! How can I help you today?"} ] }
JSON
복사
위의 데이터를 바탕으로 이 템플릿이 렌더링된다면, 결과는 다음과 같다.
<|start_header_id|>system<|end_header_id|> Initialize model<|eot_id|> <|start_header_id|>user<|end_header_id|> Hello<|eot_id|> <|start_header_id|>assistant<|end_header_id|> Hi! How can I help you today?<|eot_id|> <|start_header_id|>assistant<|end_header_id|>
Plain Text
복사

공개된 llama 3.1 템플릿 살펴보기

이번에는 Ollama에 올라와있는 llama3.1:8B 의 템플릿의 구조를 살펴보자.
{{- if or .System .Tools }}<|start_header_id|>system<|end_header_id|> {{- if .System }} {{ .System }} {{- end }} {{- if .Tools }} Cutting Knowledge Date: December 2023 When you receive a tool call response, use the output to format an answer to the orginal user question. You are a helpful assistant with tool calling capabilities. {{- end }}<|eot_id|> {{- end }} {{- range $i, $_ := .Messages }} {{- $last := eq (len (slice $.Messages $i)) 1 }} {{- if eq .Role "user" }}<|start_header_id|>user<|end_header_id|> {{- if and $.Tools $last }} Given the following functions, please respond with a JSON for a function call with its proper arguments that best answers the given prompt. Respond in the format {"name": function name, "parameters": dictionary of argument name and its value}. Do not use variables. {{ range $.Tools }} {{- . }} {{ end }} Question: {{ .Content }}<|eot_id|> {{- else }} {{ .Content }}<|eot_id|> {{- end }}{{ if $last }}<|start_header_id|>assistant<|end_header_id|> {{ end }} {{- else if eq .Role "assistant" }}<|start_header_id|>assistant<|end_header_id|> {{- if .ToolCalls }} {{ range .ToolCalls }} {"name": "{{ .Function.Name }}", "parameters": {{ .Function.Arguments }}}{{ end }} {{- else }} {{ .Content }} {{- end }}{{ if not $last }}<|eot_id|>{{ end }} {{- else if eq .Role "tool" }}<|start_header_id|>ipython<|end_header_id|> {{ .Content }}<|eot_id|>{{ if $last }}<|start_header_id|>assistant<|end_header_id|> {{ end }} {{- end }} {{- end }}
Go
복사
조금 더 복잡해진 템플릿이다. 차근차근 따라가보자.
1. {{- if or .System .Tools }}
이 조건문은 .System 또는 .Tools가 존재할 때에만 해당 블록을 실행하도록 한다. 즉, 시스템 메시지나 도구 관련 데이터가 있을 때만 그 내용을 렌더링한다.
2. 시스템 메시지 처리
<|start_header_id|>system<|end_header_id|> {{- if .System }} {{ .System }} {{- end }}
Go
복사
{{ .System }}은 시스템 메시지가 있을 경우 출력하는 역할을 합니다. 여기서 .System은 LLM의 시스템 명령이나 메타데이터와 관련된 메시지입니다.
{{- end }}는 조건문을 닫는 역할을 하며, 앞에 붙은 는 불필요한 공백이나 줄바꿈을 제거합니다.
3. 도구 메시지 처리
{{- if .Tools }} Cutting Knowledge Date: December 2023 When you receive a tool call response, use the output to format an answer to the original user question. You are a helpful assistant with tool calling capabilities. {{- end }} <|eot_id|>
Go
복사
.Tools가 있으면 도구와 관련된 추가 정보를 출력합니다. 이 부분에서는 도구 호출에 대한 설명과 툴을 사용하여 사용자 질문에 답변하는 방식에 대한 메시지를 전달합니다.
<|eot_id|>는 "End of Transmission"을 나타내는 특수 토큰으로, 메시지가 끝났음을 알립니다.
여기서는 이중 조건문으로 생각하면 된다.
4. 메시지 순회 처리
{{- range $i, $_ := .Messages }}
Go
복사
.Messages는 사용자와 어시스턴트 간의 대화 내용을 담은 배열입니다. range를 사용하여 각 메시지를 순회하며 렌더링합니다.
$i는 순회 중인 메시지의 인덱스를, $_는 각 메시지 요소를 의미하는 순회중인 값을 저장하는 변수이다.
:= Go에서 변수를 선언하고 초기화할 때 사용하는 구문으로 $i$_.Messages의 인덱스와 값을 각각 할당하는 역할을 한다.
그런데, 밑에 있는 줄에서 메세지를 따로 처리하므로, _ 라는 명시적이지 않은 변수를 사용한다
{{- $last := eq (len (slice $.Messages $i)) 1 }}
Go
복사
Go Template에서 특정 조건을 만족하는지 확인하기 위해 마지막 메시지인지 여부를 계산하는 구문이다.
lice $.Messages $i
$.Messages라는 배열(또는 슬라이스)의 특정 구간을 슬라이스하여 반환합니다. 여기서 $i는 반복문에서의 현재 인덱스입니다.
$.Messages전체 메시지 배열입니다. 앞의 $는 최상위 데이터 구조를 나타냅니다.
slice $.Messages $i.Messages 배열에서 인덱스 $i 이후의 요소들을 슬라이스하여 반환하는 역할을 합니다. 즉, $i부터 끝까지의 부분 배열을 반환합니다.
보통 _ 을 사용하는 경우 (나의 경우) 변수를 무시하는 역할로 사용하는데, 이곳에서는 변수명을 명시적으로 사용하지 않겠다는 뜻이 더 적합한 의도인 것으로 보인다.
왜냐면, 템플릿이 추후 메세지들을 사용하여 Content나 Role을 할당하기 때문이다. 템플릿의 다른 부분에서 해당 메세지에 접근하는 것은 당연히 가능하고, 인덱스만 $i 로 사용하고 내용 자체는 필요할 때만 다른 방식으로 접근하는 방식을 사용하는 것으로 보인다.
5. 사용자 메시지 처리
{{- if eq .Role "user" }}<|start_header_id|>user<|end_header_id|>
Go
복사
{{ eq .Role "user" }}는 현재 메시지가 사용자의 메시지인지 확인하는 조건이다. 사용자의 메시지라면 <|start_header_id|>user<|end_header_id|>로 시작하는 블록을 렌더링한다.
{{- if and $.Tools $last }}
Go
복사
이 조건문은 도구 호출($.Tools)과 마지막 메시지($last)가 있는지 확인하는 역할을 한다. 마지막 메시지가 사용자에 의해 전송된 것이라면, 도구 호출과 관련된 특수한 설명이 추가한다.
Given the following functions, please respond with a JSON for a function call with its proper arguments that best answers the given prompt.
Go
복사
도구 호출을 처리할 때, 함수 이름과 매개변수 값을 JSON 형식으로 출력하도록 요청하는 부분입니다.
{{ .Content }}<|eot_id|>
Go
복사
{{ .Content }}는 사용자가 보낸 메시지의 내용을 출력하고, <|eot_id|>로 해당 메시지의 끝을 나타냅니다.
6. 어시스턴트 메시지 처리
{{- else if eq .Role "assistant" }}<|start_header_id|>assistant<|end_header_id|>
Go
복사
메시지의 역할이 어시스턴트인 경우, 해당 블록을 실행합니다.
{{- if .ToolCalls }} {{ range .ToolCalls }} {"name": "{{ .Function.Name }}", "parameters": {{ .Function.Arguments }}} {{ end }} {{- else }} {{ .Content }} {{- end }}
Go
복사
어시스턴트가 도구 호출을 했다면, 그에 대한 응답을 JSON 형식으로 출력합니다. 각 호출된 함수 이름과 인자 목록을 출력합니다.
도구 호출이 없다면, 어시스턴트의 응답 내용을 그대로 출력합니다.
7. 도구 역할 처리
{{- else if eq .Role "tool" }}<|start_header_id|>ipython<|end_header_id|>
Go
복사
메시지의 역할이 tool일 경우, 도구 호출 결과를 처리하는 부분입니다. 여기서는 IPython과 같은 도구의 출력을 렌더링한다.
{{ .Content }}<|eot_id|>{{ if $last }}<|start_header_id|>assistant<|end_header_id|> {{ end }}
Go
복사
도구의 출력 내용은 .Content로 렌더링되며, 마지막 메시지인 경우 어시스턴트의 응답을 대기하는 구조로 마무리된다.

Reference