Как стать автором
Обновить

Dockerfile, только для LLM

Время на прочтение7 мин
Количество просмотров3.8K

При разработке агентов я часто сталкивался с необходимостью настройки и оптимизации моделей для дальнейшей передачи разработчикам. Помимо этого, требовалось возможность переиспользовать "предпоготовленные" модели и под разные задачи между проектами

Чаще всего я настраивал это все на уровне кода, но, некоторый период назад наткнулся на Modelfile и решил изучить и попробовать его, в ходе экспериментов наткнулся на реально большое число неожиданностей и решил таки написать о нем тут подробнее :)

Modelfile позволяет упаковывать модели и формировать готовых агентов. В нем оказалось много реально неожиданного поведения, и одновременно с этим есть сложности и странности. Однако с его помощью можно взять множество разных моделей из того же Hugging Face и запустить под Ollama, а самое важное, удобства в тюнинге (или же наоборот отупливании модели :D )

Как устроен Modelfile?

Структура Modelfile ну очень похожа на Dockerfile, что делает её интуитивно понятной любому бекендеру. Каждая строка в файле начинается с определенной команды и следует классическим правилам еще из Dockerfile:

<INSTRUCTION> <optional property> arg

Здесь все просто

INSTRUCTION - это основная инструкция для Modelfile, которая определяет конкретное действие или настройку. Примеры таких команд включают FROM, SYSTEM, TEMPLATE и другие базовые инструкции для конфигурации модели

optional property - это доп параметр или свойство команды, которые позволяют более точно настроить поведение инструкции. Они есть не у всех команд, предоставляют гибкость в настройке, в осночном используются с настройками параметров моделей

arg - это главный аргумент команды, определяющий конкретное значение или действие, которое должно быть выполнено

Для лучшего понимания рассмотрим несколько практических примеров использования этого синтаксиса:

  • FROM llama2 - это базовая команда с одним аргументом, указывающая на использование модели llama2 в качестве основы

  • PARAMETER temperature 0.7 - здесь мы видим команду с property "temperature" и числовым значением, определяющим уровень креативности модели

  • MESSAGE user "Hello" - в этом случае команда включает property "user" и текстовый аргумент, определяющий содержание сообщения, подробнее об этом чуть ниже

Такая четкая и логичная структура делает Modelfile особенно удобным инструментом для работы. Разработчики, имеющие опыт работы с Docker, найдут этот формат очень знакомым, что существенно сокращает время на освоение инструмента и позволяет быстро приступить к созданию собственных конфигураций моделей.

Фактически, если есть опыт работы с Dockerfile, то Modelfile интуититивно будет легко понять (кроме ряда настроек, которые нормально не разберешь)

Настройки

FROM - required

Единственный обязательный параметр, фактически есть два варианта использования с использованием “базовогой образа модели” для упаковки

Первый вариант подразумевает использования модели из ollama hub, где уже заполнено множеством разных моделей, здесь все как обычно

FROM smollm2:1.7b

И она просто подкачается и дальше все изменения что вы будете вносить “замещают” исходные из образа, то есть если вы возьмете smollm2 и начнете работать с ним, то вы возьмете все его исходные параметры (SYSTEM, LICENCE, etc)

Но, далеко не всегда есть нужная тебе модель в ollama models , а вот что делать если нужно свою упаковать? Да, это возможно также, есть лишь небольшое ограничение, модель должна быть одного из двух вариантов safetensor или gguf, в этом случае вы можете взять модель с локального диска

FROM ./medicine-llm.Q5_K_M.gguf

SYSTEM

Буквально system сообщение для LLM, но, дьявол кроется в деталях

SYSTEM You are Mario from super mario bros, acting as an assistant.

После установки этого промпта - на мое первое же сообщение получаю крайне неожиданный ответ…

После чего, я минимум раз 5 попробовал разные варианты, но кажыдй раз сталкивался с одной и той же проблемой, он не видит system prompt, моя рука уже тянулась к github issue для описания “ребята, че за фигня?” как в итоге я тааааааак осознал, что дело не в Ollama, а в UI-ке

Видите ли, почти все инструменты, что я использовал, будь то enchanted или же ollamac подставлять могут свой system prompt, но я его оставлял пустым, а у них похоже под капотом реализация устроена так, что когда вы устанавливаете пустой system prompt, то они его и шлют как первое сообщение все равно, но тут возникает вопрос, а куда делся созданными нами system prompt тогда? Да он попросту затирается, буквально на пустое сообщение - суть в том, что SYSTEM инструкция работает ровно в одном случае, если на вход не прилетело сообщение с типом system, в этом случае наша инструкция из файла попросту перезатирается, то есть приоритетность такая

Как видно из схемы, если приходит system prompt через API - он имеет наивысший приоритет и перезаписывает все остальные настройки. Если API prompt отсутствует, используется SYSTEM из Modelfile, а если и он не определен - берется system prompt из базового образа.

MESSAGES

А вот это очень интересная деталь, суть в том, что мы с вами можем проставлять заранее подготовленный диалог, который будет использоваться дальше при работе агента, то есть мы буквально можем “предподготовить” некоторые части диалога, к примеру

MESSAGE assistant I'm Mario!
MESSAGE user Hi!
MESSAGE assistant It's-a me, Mario!
MESSAGE assistant I love pasta and mushrooms!
MESSAGE assistant Wahoo! I can jump really high!
MESSAGE user Tell me about your friends
MESSAGE assistant Princess Peach is amazing!
MESSAGE assistant And my brother Luigi is the best!
MESSAGE assistant Even Yoshi helps me on adventures!
MESSAGE user What about enemies?
MESSAGE assistant Bowser is my arch-nemesis!
MESSAGE assistant And those pesky Koopa Troopas!
MESSAGE assistant But I can handle them all!

И да, как вы заметили, можно даже несколько сообщений подряд указывать с одним типом assistant либо user, под капотом они попросту мержаться между собой, далее на запрос

Messages: []api.Message{
    {
        Role:    "user",
        Content: "Tell me exactly how many things you told me about yourself and about Bowser",
    },
}
Well...let me see. So far, we've talked about:

1. My name
2. What kind of characters I'm often associated with (like Mario)
3. My love for jumping in a fun way 
4. The fact that I say "Wahoo!" a lot
5. That Bowser is an enemy character that I sometimes fight.

То есть дальнейшая коммуникация “наслаивается” на предыдущую, а не замещает, как в случае с system , из-за чего ты можешь загатавливать агентов с учетом “продолжения” переписки, однако нужно учитывать, что если system может перезатираться, то диалог же уже нет, и это можно создать неприятные инциденты (проходил, потом дебажиться будете часами)

TEMPLATE

TEMPLATE в Modelfile используется для определения формата диалога между пользователем и моделью. Ollama использует стандартные Go templates (text/template), так что инструментарий для Go разработчиков вполне понятен

Простой шаблон может выглядеть так:

TEMPLATE """### User:
{{ .Prompt }}

**Assistant:**

{{ .Response }}"""

Здесь используются базовые переменные:

  • {{ .System }} - системное сообщение

  • {{ .Prompt }} - сообщение пользователя

  • {{ .Response }} - ответ модели

Но, тут важно учитывать, что для сохранения "диалога" нужно немного иначе работать с шаблонами

{{- range $i, $_ := .Messages }}
  {{- $last := eq (len (slice $.Messages $i)) 1 }}
    {{- if or (eq .Role "user") (eq .Role "system") }}<start_of_turn>user
      {{ .Content }}<end_of_turn>
        {{ if $last }}<start_of_turn>model
        {{ end }}
    {{- else if eq .Role "assistant" }}<start_of_turn>model
      {{ .Content }}{{ if not $last }}<end_of_turn>
    {{ end }}
  {{- end }}
{{- end }}

Без корректной обработки цикла, диалог может поломаться, так что нужно не забывать корректно формировать через template диалог

PARAMETER

Проклятый параметр, позволяет тонко настроить поведение модели, влияет на креативность, связность, длину ответа и другие аспекты работы. Почему проклятый? Да потому что любые попытки изменитье его - просто превращали модель в просто хаотичного клоуна генерирующего что попало, могут быть полезны, если знать точный баланс и правила, а у меня с ними опыта мало, все попытки поломали модель, так что я просто пожалуй приложу исходники из документации :)

mirostat

Enable Mirostat sampling for controlling perplexity. (default: 0, 0 = disabled, 1 = Mirostat, 2 = Mirostat 2.0)

int

mirostat 0

mirostat_eta

Influences how quickly the algorithm responds to feedback from the generated text. A lower learning rate will result in slower adjustments, while a higher learning rate will make the algorithm more responsive. (Default: 0.1)

float

mirostat_eta 0.1

mirostat_tau

Controls the balance between coherence and diversity of the output. A lower value will result in more focused and coherent text. (Default: 5.0)

float

mirostat_tau 5.0

num_ctx

Sets the size of the context window used to generate the next token. (Default: 2048)

int

num_ctx 4096

repeat_last_n

Sets how far back for the model to look back to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx)

int

repeat_last_n 64

repeat_penalty

Sets how strongly to penalize repetitions. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. (Default: 1.1)

float

repeat_penalty 1.1

temperature

The temperature of the model. Increasing the temperature will make the model answer more creatively. (Default: 0.8)

float

temperature 0.7

seed

Sets the random number seed to use for generation. Setting this to a specific number will make the model generate the same text for the same prompt. (Default: 0)

int

seed 42

stop

Sets the stop sequences to use. When this pattern is encountered the LLM will stop generating text and return. Multiple stop patterns may be set by specifying multiple separate stop parameters in a modelfile.

string

stop "AI assistant:"

num_predict

Maximum number of tokens to predict when generating text. (Default: -1, infinite generation)

int

num_predict 42

top_k

Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)

int

top_k 40

top_p

Works together with top-k. A higher value (e.g., 0.95) will lead to more diverse text, while a lower value (e.g., 0.5) will generate more focused and conservative text. (Default: 0.9)

float

top_p 0.9

min_p

Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)

float

min_p 0.05

ADAPTER

А вот тут очень интересно - фактически он позволяет настроить LoRA адаптор для доапгрейда модели

ADAPTER <path to safetensor adapter>

Но о нем уже во второй части :)


Также не могу не порекламировать свой канал, в нем я чаще пишу интересные новости из мира Go/AI , а также публикую подкасты с интересными гостями :)

Теги:
Хабы:
+11
Комментарии0

Публикации

Работа

Go разработчик
78 вакансий

Ближайшие события