개요
•
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|>: 메시지의 끝을 의미하는 토큰이다.
예를 들어, Messages에 Role: "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로 렌더링되며, 마지막 메시지인 경우 어시스턴트의 응답을 대기하는 구조로 마무리된다.