Перейти к содержанию

Концепции

Фреймворк, а не библиотека

Библиотеку вы вызываете. Фреймворк вызывает вас. Эта инверсия — вся суть mlango, и именно она покупает все удобства из этой документации.

Когда вы запускаете:

python manage.py train reviews.Sentiment -p C=2.0

фреймворк находит ваш класс в реестре, открывает ран, ставит сид всем генераторам, режет данные, вызывает ваш build(), ведёт цикл, пишет метрики, фиксирует git-коммит, сохраняет артефакт и регистрирует версию. Вы предоставили метод build() и несколько объявлений полей.

Будь mlango библиотекой, вы бы всё ещё писали цикл обучения, схему трекинга, админку и CLI. Именно потому, что это фреймворк, их писать не нужно.

Четыре декларации

Всё, что вы пишете, относится к одному из четырёх семейств:

Семейство Объявляет Живёт в
Dataset Как выглядит запись и откуда записи берутся <app>/datasets.py
Model Гиперпараметры и как собрать оценщик <app>/models.py
Agent Модель, системный промпт, инструменты, память <app>/agents.py
Eval Что значит «хорошо» и как это измерить <app>/evals.py

У них общая механика: метакласс собирает ваши поля, строит объект _meta и регистрирует класс.

_meta — это контракт

Каждый объявленный класс получает _meta, описывающий его:

>>> Reviews._meta.label
'reviews.Reviews'
>>> Reviews._meta.field_names
['id', 'text', 'label']
>>> Reviews._meta.target_fields
[<LabelField: label>]
>>> Reviews._meta.fingerprint()
'11b053a7043a'

Это важнее, чем кажется. Всё универсальное во фреймворке — админка, миграции, CLI, схемы API — написано против _meta, а не против Dataset или Model конкретно. Именно этот приём позволяет одной админке отображать четыре разных типа объектов, и поэтому добавление пятого семейства не означало бы переписывание админки.

Поля делают четыре работы

Одна система полей обслуживает схему записи датасета, гиперпараметры модели, конфигурацию агента и контракт входа inference-эндпоинта:

text = fields.TextField(max_length=5000)               # колонка датасета
learning_rate = fields.FloatField(default=1e-3)        # гиперпараметр
tone = fields.ChoiceField(["formal", "friendly"])      # конфигурация агента

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

Приложения

Приложение — импортируемый пакет, группирующий декларации одной предметной области: reviews, recsys, support. Приложения — единица переиспользования и единица миграций.

INSTALLED_APPS = ["reviews", "support"]

При старте реестр импортирует у каждого приложения datasets.py, models.py, agents.py, evals.py, admin.py и signals.py — поэтому объявить класс достаточно, чтобы он существовал. Регистрировать вручную ничего не нужно.

from mlango.core import apps

apps.get_dataset("reviews.Reviews")
apps.get_registered("model")
apps.summary()

Добавьте apps.py, когда нужно человекочитаемое имя или код настройки при старте:

from mlango.core import AppConfig

class ReviewsConfig(AppConfig):
    name = "reviews"
    verbose_name = "Отзывы покупателей"

    def ready(self) -> None:
        from mlango.core.signals import run_finished
        run_finished.connect(notify_slack)

Наследование Meta

Поля наследуются от абстрактных базовых классов. Meta-опции — тоже, и это намеренное отличие от Django:

class TextClassifier(Model):
    class Meta:
        abstract = True
        trainer = "transformers"
        task = "classification"


class Sentiment(TextClassifier):
    class Meta:
        dataset = Reviews      # trainer и task унаследованы
        features = ["text"]

В Python тело class Meta дочернего класса не наследует родительский Meta, так что без явного слияния переиспользуемый базовый класс написать невозможно — именно на этом стоят пресеты. abstract намеренно исключён из наследования, иначе ни один класс никогда не стал бы конкретным.

Настройки

Один модуль, разрешаемый из MLANGO_SETTINGS_MODULE. Все настройки и их значения по умолчанию задокументированы в mlango.conf.global_settings; переопределяйте только то, что отличается. Опечатка — ошибка, а не молчаливое игнорирование.

Бэкенды меняются настройкой, а не переписыванием кода:

METASTORE = {"URL": "postgresql://user@host/mlango"}
STORAGE = {"BACKEND": "myproject.storage.S3Storage"}
TRAINERS = {"lightgbm": "myproject.trainers.LightGBMTrainer"}
PROVIDERS = {"vllm": "myproject.providers.VLLMProvider"}

См. Настройки.

Метастор

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

Именно поэтому на вопрос «что породило эту цифру?» всегда есть ответ. Строка рана несёт свои параметры, отпечаток данных, git-коммит, сид и устройство.

from mlango.training import recent_runs, get_run

run = get_run("7c8f1020")
run.params["_data_fingerprint"]
run.git_commit

Сигналы

Хуки для инструментирования, в форме Django — fn(sender, **kwargs):

from mlango.core.signals import epoch_finished, run_finished, tool_called

@receiver(run_finished)
def on_finish(sender, run, status, **kwargs):
    ...

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

Две идентичности данных

mlango различает две вещи, которые легко спутать:

  • Отпечаток схемы — хеш объявленных полей. Меняется, когда меняется декларация.
  • Хеш содержимого — хеш самих строк. Меняется, когда меняются данные.

Материализованная версия датасета записывает оба, и именно это позволяет через полгода отличить «та же схема, другие данные» от «те же данные, другая схема».

Где что лежит

myproject/
├── manage.py              # командная строка этого проекта
├── myproject/
│   ├── settings.py        # один модуль настроек
│   └── routes.py          # маршруты inference API
└── reviews/               # приложение
    ├── apps.py            # необязательная конфигурация приложения
    ├── datasets.py
    ├── models.py
    ├── agents.py
    ├── evals.py
    ├── admin.py
    └── migrations/
        └── 0001_initial.py