Пользовательские модели¶
В данном документе описаны соглашения и правила для создания пользовательских моделей для решения задач детектирования объектов, сегментации и классификации.
Оглавление¶
- Быстрый старт
- Структура директории пользовательской модели
- Структура файлов
- Соглашения на именование файлов
- Требование к расположению Python-модулей
- Описание содержимого файлов пользовательской модели
- Описание файла model.py
- Описание файла model_info.json
- Описание файла coco_categories.json
- Ограничения
- Ограничения на используемые бэкенды
- Ограничения на размер батча и динамические размерности
- Ограничения на используемые Python-пакеты
- Приложение A: примеры реализации пользовательских моделей
- Интерфейс пользовательской модели
- Пример пользовательской модели классификатора
- Пример пользовательской модели детектора
- Пример пользовательской модели сегментатора
- Приложение Б: архитектура выполнения пользовательской модели
- Описание внутреннего механизма работы системы с пользовательской моделью
Быстрый старт¶
- Создайте директорию модели c файлами:
coco_categories.json,model_info.json,model.py,__init__.pyи весамиweights*.*. - Заполните
model_info.json: укажитеdeploy_profile,task_type,img_size,weights_path,categories_path, при необходимости —model_attributes. - Реализуйте класс
Modelвmodel.pyпо протоколу (CustomModelProtocol/CustomTorchModelProtocol), задайте корректныеinput_names/output_names. - Все вспомогательные модули поместите в
custom_modules/и используйте абсолютные импортыfrom custom_modules import .... - Назовите файлы весов по шаблонам ниже. В системе поддерживается только
batch_size=1— зафиксируйте это в именах и при экспорте. - Проверьте загрузку модели и корректность
preprocess/postprocessна тестовом изображении.
Структура директории пользовательской модели¶
Структура файлов¶
Директория с моделью должна содержать следующие обязательные файлы:
__init__.py— Пустой или служебный файл, обозначающий директорию модели как Python-пакет. Необходим для корректной загрузки модели и поддержки относительных импортов внутри model.py и связанных модулей.- coco_categories.json — JSON-файл с описанием категорий (классов), которые модель способна распознавать.
- model_info.json — JSON-файл с метаданными и параметрами модели (например, версия, разрешение входного изображения, и т.д.).
- model.py — Python-модуль, содержащий код для инициализации и работы модели.
- Файл весов — Один или набор файлов с весами модели. Имя файла должно начинаться с префикса
weightsи иметь одно из допустимых расширений:.pt, .pth, .engine, .tar или .onnx.
Опциональная директория:
custom_modules/ — Директория для пользовательских Python-модулей (например, реализация нестандартного NMS, утилиты для предобработки данных и т.д.). Модули из этой директории должны импортироваться в основном файле model.py.
Пример корректной структуры директории:
├── coco_categories.json
├── model_info.json
├── model.py
├── weights.pt
└── custom_modules/ # Опциональная директория со вспомогательным кодом
├── __init__.py # (Рекомендуется) Для упрощения импорта
├── nms.py # Пользовательская реализация Non-Maximum Suppression
└── utils.py # Вспомогательные утилиты (постобработка, аннотации и т.д.)
Примечания
- Все Python-модули внутри директории модели должны использовать относительные импорты.
- Директория модели рассматривается как изолированный Python-пакет и загружается в собственном runtime-namespace, что исключает конфликты между разными моделями.
- Отсутствие любого из обязательных файлов приводит к ошибке инициализации модели.
Соглашения на именование файлов¶
Для обеспечения однозначной идентификации параметров модели приняты строгие правила именования файлов с весами.
- Общее правило для всех форматов
Имя файла весов должно начинаться с префикса weights. Необходимо использовать только строчные буквы.
Веса модели ONNX¶
Имя весов содержит информацию о размерности входа модели, версии onnx и точности модели:
weights_h{height}_w{width}_b{batch}_c{channels}_onnx{onnx_version}_{precision}.onnx
| Параметр | Описание | Пример значения |
|---|---|---|
height |
Высота входного изображения (тензора) модели в пикселях. | 640 |
width |
Ширина входного изображения (тензора) модели в пикселях. | 640 |
batch |
Размер батча (пакета), на который настроена модель. | 1 |
channels |
Количество каналов входного изображения (тензора). | 3 |
onnx_version |
Версия спецификации ONNX, использованная для экспорта модели. | 1.17.0 |
precision |
Числовая точность вычислений в модели. | fp16 (FP16), fp32 (FP32) |
Пример корректно сформированного имени файла:
weights_h224_w224_b1_c3_onnx1.17.0_fp16.onnx
Важно: Данный шаблон именования применяется исключительно к файлам с расширением .onnx.
Веса модели Torch¶
Особенных требований для именования весов для моделей в формате torch не предъявляется. Рекомендуется следовать общему правилу (п.1)
Примеры корректно сформированных имен файлов:
weights.pt, weights_resnet50-0676ba61.pth
Веса модели TensorRT¶
Имя файла должно содержать информацию о размерности входа модели, версии compute capability целевой GPU архитектуры, версии CUDA Toolkit, версии TensorRT и числовой точности модели:
weights_h{height}_w{width}_b{batch}_cc{compute_capability}_cuda{cuda_version}_trt{tensorrt_version}_{precision}.engine
Расшифровка параметров в имени файла:
| Параметр | Описание | Пример значения |
|---|---|---|
height |
Высота входного тензора модели в пикселях | 224 |
width |
Ширина входного тензора модели в пикселях | 224 |
batch |
Размер батча (пакета), на который оптимизирована модель | 1 |
compute_capability |
Версия вычислительной возможности целевой GPU архитектуры (compute capability) | 8.6, 7.5, 8.9 |
cuda_version |
Версия CUDA Toolkit, использованная для сборки TensorRT runtime | 11.8, 12.2, 12.4 |
tensorrt_version |
Версия TensorRT, использованная для построения engine файла | 8.6.1, 10.4.0 |
precision |
Числовая точность вычислений в модели | fp16, fp32, int8 |
Пример корректно сформированного имени файла:
weights_h224_w224_b1_cc8.6_cuda11.8_trt8.6.1_fp16.engine
Важно: Данный шаблон именования применяется исключительно к файлам с расширением .engine.
Веса модели Tensorflow¶
Имя файла для весов в формате tensorflow ограничено наименованием и расширением.
Пример корректно сформированного имени файла:
weights_tf.tar
Внутри архива с весами обычно содержится
├── weights_tf
│ ├── assets # (Опционально) Дополнительные ресурсы, используемые графом
│ ├── fingerprint.pb # (Опционально) Фингерпринт для контроля версий
│ ├── saved_model.pb # Основной файл графа модели и метаданных
│ └── variables # Директория с обученными весами модели
│ ├── variables.data-00000-of-00001 # Файл с данными весов
│ └── variables.index # Индексный файл для отображения тензоров
Описание содержимого:
- saved_model.pb: Содержит определение архитектуры модели (граф операций) и метаинформацию (сигнатуры для inference).
- assets/: Может содержать вспомогательные файлы, такие как словари для токенизации или пользовательские операторы.
- variables/: Содержит непосредственно значения обученных параметров модели:
- variables.index: Служебный файл, сопоставляющий имена тензоров с их местоположением в файле данных.
- variables.data-00000-of-00001: Бинарный файл с сохраненными значениями весов (может иметь шаблон имени variables.data-XXXXX-of-YYYYY при шардировании).
- fingerprint.pb: Используется фреймворком для проверки целостности и совместимости модели.
Важно: Данный шаблон именования и структура применяются исключительно к файлам с расширением .tar, содержащим модель в формате SavedModel.
Требование к расположению Python-модулей¶
Каждая модель загружается системой как изолированный Python-пакет с собственным runtime-namespace. Это обеспечивает корректную работу относительных импортов и полностью исключает конфликты между разными моделями.
artifacts/
├── __init__.py # ОБЯЗАТЕЛЬНО: обозначает директорию модели как Python-пакет (может быть пустым)
├── model.py # Основной модуль модели
├── coco_categories.json
├── model_info.json
├── custom_modules/ # Рекомендуемая директория для вспомогательного кода
│ ├── __init__.py # ОБЯЗАТЕЛЬНО
│ ├── nms.py # Пользовательская реализация Non-Maximum Suppression
│ ├── utils.py # Вспомогательные утилиты (постобработка, аннотации и т.д.)
│ └── ...
└── weights*.*
Общие правила размещения кода
- Все Python-модули модели находятся внутри одного пакета модели (artifacts).
- Вспомогательный и переиспользуемый код рекомендуется размещать в директории custom_modules/.
- Размещение вспомогательных модулей в корне директории модели допускается, но не рекомендуется, за исключением model.py.
Такое разделение улучшает читаемость структуры и упрощает сопровождение моделей.
Строгое требование к импортам:
Все импорты внутри модели ДОЛЖНЫ использовать относительную нотацию, так как модель загружается как изолированный Python-пакет.
В model.py:
# КОРРЕКТНО
from .custom_modules import nms, utils
from .custom_modules.yolo_utils import letterbox
# НЕКОРРЕКТНО
from custom_modules import nms, utils
import custom_modules.yolo_utils
Импорты внутри custom_modules/ (например, в nms.py):
# КОРРЕКТНО
from .utils import some_function
# НЕКОРРЕКТНО
from custom_modules.utils import some_function
Важно: Данное требование является обязательным. Модели, не следующие этому стандарту, не будут корректно работать в нашей системе. Это обусловлено фундаментальными ограничениями механизма импорта Python и архитектурными решениями системы загрузки моделей.
Описание содержимого файлов пользовательской модели¶
Описание файла model.py¶
Модуль model.py предназначен для реализации пользовательской модели. Он должен соответствовать одному из протоколов:
CustomModelProtocol— базовый протокол для моделей;CustomTorchModelProtocol— расширение базового протокола для моделей на PyTorch.
Эти протоколы описывают контракт, которому должна соответствовать пользовательская модель:
какие методы и атрибуты она должна иметь (например, preprocess, postprocess, input_names, output_names).
Протоколы представлены в python пакете vlmodels_types
Класс модели¶
Ниже представлен код модуля model.py с реализацией пользовательского классификатора ResNet-18:
-
Класс модели должен называться
Model. -
В конструктор модели
__init__передаются именованные аргументы из: -
пользовательского интерфейса Node-RED (через model_attributes),
-
или файла model_info.json (если model_attributes отсутствует или пуст).
-
Класс обязан реализовать методы препроцессинга и постпроцессинга данных, как это описано в протоколе.
-
Класс может быть параметризирован дженериком через протокол.
-
Каждая пользовательская модель должна обязательно определять атрибуты
input_namesиoutput_names, пример:input_names = ["input"] output_names = ["logits"]
Эти списки фиксируют, какие тензоры подаются на вход и какие выдаются на выход. Таким образом система знает:
- какие данные ожидать от вычислительного графа
- и как связать результаты с последующим этапом
postprocess.
Правила заполнения
input_names — список строк, описывающий все входные тензоры модели.
Для классификатора это может быть один элемент ["input"],
а для моделей с несколькими входами список может быть длиннее например, ["image", "metadata"].
output_names — список строк, описывающий все выходные тензоры.
Для классификатора это, как правило, ["logits"],
для детекции может быть, например, ["boxes", "scores", "labels"].
Имена должны совпадать с ключами словарей, которые возвращаются из метода preprocess.
Пример
Ниже приведён пример реализации ResNet-18 в файле model.py.
В нём класс Model параметризован типом данных Classification, так как модель возвращает один класс и его вероятность.
class Model(CustomModelProtocol[Classification]):
input_names = ["input"]
output_names = ["logits"]
def __init__(self, **model_attributes: AllowedModelAttributeValue) -> None:
"""
Инициализирует модель ResNet18 из torchvision с заданными параметрами.
Аргументы:
**model_attributes: Словарь с возможными ключами:
- 'conf_thres' (float): Порог уверенности для детекций.
Параметры model_attributes могут поступать либо из пользовательского интерфейса Node-RED,
либо из файла model_info.json (если model_attributes не передан или пуст).
"""
self.conf_thres: float = model_attributes["conf_thres"]
self.img_size = 224
self.imagenet_mean = [0.485, 0.456, 0.406]
self.imagenet_std = [0.229, 0.224, 0.225]
def preprocess(self, data: np.ndarray) -> Mapping[str, torch.Tensor]:
img_rgb = cv2.cvtColor(data, cv2.COLOR_BGR2RGB)
t = torch.from_numpy(img_rgb).permute(2, 0, 1).float() / 255.0 # (3,H,W), [0..1]
t = TF.resize(t, 256, interpolation=transforms.InterpolationMode.BILINEAR) # короткая сторона -> 256
t = TF.center_crop(t, [self.img_size, self.img_size]) # 224x224
t = TF.normalize(t, mean=self.imagenet_mean, std=self.imagenet_std)
return {self.input_names[0]: t.unsqueeze(0)}
def postprocess(self, outputs: Mapping[str, torch.Tensor], categories: Sequence[str]) -> Classification:
if self.output_names != list(outputs.keys()):
raise ValueError("Output names mismatch")
for name, tensor in outputs.items():
if tensor.ndim == 1:
tensor = tensor.unsqueeze(0)
score, class_id = torch.max(F.softmax(tensor, dim=1), dim=1)
return Classification(
label=categories[int(class_id.item())],
score=float(score.item()),
)
Примечание: с полным кодом модели можно ознакомиться в приложении А.
Описание файла model_info.json¶
Файл model_info.json содержит метаинформацию и параметры модели, необходимые для её корректной загрузки и выполнения.
| Поле | Тип | Обязательность | Описание | Пример значения | Допустимые значения |
|---|---|---|---|---|---|
model_attributes |
object |
Нет | Словарь с настраиваемыми параметрами модели. Каждый параметр должен содержать поля type и default, а также может включать дополнительные метаданные для интерфейса. |
"conf_thres": {"type": "float", "default": 0.4, "ru_name": "Порог уверенности"} |
Любые пользовательские параметры следующих типов: int, float, str, bool, None. Поддерживаемые поля для каждого атрибута:- type (обязательно): тип данных- default (обязательно): значение по умолчанию- ru_name (опционально): название параметра на русском языке для отображения в интерфейсе Node-RED |
deploy_profile |
object |
Да | Профили развертывания, поддерживаемые моделью. | "deploy_profile": {"default_profile": "inline_cuda_trt_fp16", "profiles": ["inline_cuda_trt_fp16"]} |
|
→ default_profile |
string |
Да | Профиль развертывания по умолчанию. Должен быть одним из значений из списка profiles. |
"inline_cuda_trt_fp16" |
Должен соответствовать одному из значений в profiles |
→ profiles |
array[string] |
Да | Список поддерживаемых профилей развертывания. | ["inline_cuda_trt_fp16", "inline_cpu_onnx_fp32"] |
["inline_cuda_trt_fp16", "inline_cuda_trt_fp32", "inline_cuda_onnx_fp32", "inline_cpu_onnx_fp32", "inline_cuda_torch_fp32", "inline_cuda_torch_fp16", "inline_cpu_torch_fp32", "inline_cpu_torch_fp16", "inline_cuda_tf_fp32", "inline_cpu_tf_fp32"] |
task_type |
string |
Да | Тип задачи, которую решает модель. | "image_detection" |
"image_detection", "image_instance_segmentation", "image_classification", "object_detection" |
model_name |
string |
Да | Уникальное имя модели. | "yolov7tiny_coco_custom" |
Любая строка (латинские буквы, цифры, нижние подчеркивания) |
model_version |
string |
Да | Версия модели. | "2" |
Строковое представление версии |
img_size |
object |
Да | Размер входного изображения модели. | "img_size": {"height": 640, "width": 640} |
|
→ height |
integer |
Да | Высота входного изображения в пикселях. | 640 |
Положительные целые числа (обычно степени двойки: 224, 320, 416, 512, 640, 1280) |
→ width |
integer |
Да | Ширина входного изображения в пикселях. | 640 |
Положительные целые числа (обычно степени двойки: 224, 320, 416, 512, 640, 1280) |
weights_path |
string |
Да | Путь к файлу весов модели. Должен соответствовать шаблону weights*.*. |
"weights*.*" |
|
categories_path |
string |
Да | Путь к файлу с описанием категорий (классов), которые модель способна распознавать. | "categories_path": "coco_categories.json" |
Примечание
- Для
model_attributes: Полеru_nameиспользуется для локализации интерфейса в Node-RED. Если указано, значение из этого поля будет отображаться как название параметра вместо имени ключа. - Профиль развертывания указывает системе устройство, бэкенд и точность с которой нужно инициализировать и выполнять модель.
Например
inline_cuda_trt_fp16: - Устройство: CUDA (GPU)
- Бэкенд: TensorRT
- Точность: Float16
Пример содержимого файла model_info.json:
{
"model_type": "custom_model",
"model_attributes": {
"conf_thres": {
"type": "float",
"default": 0.4,
"ru_name": "Порог уверенности"
},
"iou_thres": {
"type": "float",
"default": 0.45
}
},
"deploy_profile": {
"default_profile": "inline_cuda_trt_fp16",
"profiles": [
"inline_cuda_trt_fp16"
]
},
"task_type": "image_detection",
"model_name": "custom_yolov7tiny_coco",
"model_version": "2",
"img_size": {
"height": 640,
"width": 640
},
"weights_path": "weights*.*",
"categories_path": "coco_categories.json"
}
Описание файла coco_categories.json¶
Файл coco_categories.json содержит описание категорий (классов), которые модель способна распознавать, в формате COCO с дополнительными полями для расширенной функциональности.
{
"categories": [
{
"supercategory": "person",
"id": 1,
"name": "person",
"is_violation": false,
"is_primary": true,
"exclude": false,
"threshold": 0.65,
"color": [0, 255, 0],
"keypoints": [],
"skeleton": [],
"skeleton_color": [],
"ru_name": "Человек",
"full_ru_name": "Человек в кузове"
}
]
}
| Поле | Тип | Обязательность | Описание | Пример значения |
|---|---|---|---|---|
supercategory |
string |
Да | Родительская категория (общая группа). | "person", "vehicle", "animal" |
id |
integer |
Да | Уникальный числовой идентификатор категории. Должен соответствовать ID, используемым моделью при инференсе. | 1, 2, 3 |
name |
string |
Да | Уникальное имя категории на английском языке (латинские символы, нижний регистр). | "person", "car", "dog" |
is_violation |
boolean |
Нет | Флаг, указывающий, является ли категория нарушением. | true, false |
is_primary |
boolean |
Нет | Флаг, указывающий, является ли категория основной (приоритетной) для отображения. | true, false |
exclude |
boolean |
Да | Флаг, указывающий, следует ли исключать категорию из обработки и отображения. | true, false |
threshold |
float |
Да | Порог уверенности для данной категории (переопределяет глобальный порог). | 0.65, 0.5, 0.7 |
color |
array[int] |
Да | Цвет для визуализации категории в формате [R, G, B]. | [0, 255, 0], [255, 0, 0] |
keypoints |
array[string] |
Нет | Список ключевых точек для pose estimation моделей. | |
skeleton |
array[array[int]] |
Нет | Соединения между ключевыми точками для pose estimation. | [[16, 14], [14, 12]] |
skeleton_color |
array[array[int]] |
Нет | Цвета для соединений скелета в формате [R, G, B]. | [[0, 255, 0], [255, 0, 0]] |
ru_name |
string |
Да | Локализованное короткое название категории на русском языке для отображения в интерфейсах. | "Человек", "Автомобиль" |
full_ru_name |
string |
Да | Локализованное полное название категории на русском языке для отображения в интерфейсах. | "Человек в кузове", "Автомобиль с кузовом" |
Ограничения¶
Ограничения на используемые бэкенды¶
Пользовательские модели ограничены следующими бэкендами: ONNX, TORCH, TF, TRT. Выбор бэкенда определяет требования к формату весов модели и доступный функционал для инференса.
Ограничение на используемые версии и операторы¶
Пользовательские модели при использовании выбранного бэкенда будут ограничены установленными версиями библиотек, а также набором операторов, поддерживаемых выбранным фреймворком.
Поддерживаемая системой версия CUDA Toolkit - 12.6.0.
1. TensorRT - Версия: Поддерживается TensorRT 10.4.0 - Совместимость: Модели должны быть совместимы с compute capability целевых GPU - Операторы: Поддерживаются операторы из официальной документации NVIDIA - Ограничения: Некоторые custom operators могут требовать дополнительной реализации - Документация: NVIDIA TensorRT Documentation
2. ONNX
- Opset: Поддерживается opset min: 7, max: 15
- Версия: ONNX Runtime 1.17.0
- Операторы: Полный список поддерживаемых операторов доступен в документации
- Ограничения: Динамические размерности могут иметь ограниченную поддержку
- Документация: ONNX Operators
3. Torch - Версия: PyTorch 2.7.0+cu126 - Форматы: Поддерживаются .pt и .pth файлы - Ограничения: Модели должны использовать только стандартные операторы PyTorch - Документация: PyTorch Documentation
4. TensorFlow - Версия: TensorFlow 2.20.0 - Формат: Поддерживается только SavedModel format - Ограничения: Модели должны использовать TF2.x compatible operations - Совместимость: Не поддерживаются устаревшие API TensorFlow 1.x - Документация: TensorFlow SavedModel Guide
Общие ограничения¶
- Память: Модели должны укладываться в доступную GPU/CPU память
- Размеры входов: Максимальные размеры входных тензоров ограничены возможностями бэкенда
- Custom operators: Требуют дополнительной реализации и могут иметь ограниченную поддержку
- Производительность: Скорость инференса зависит от совместимости модели с выбранным бэкендом
Рекомендация: Перед развертыванием проверяйте модель на совместимость с целевым бэкендом используя соответствующие инструменты валидации в системе.
Ограничения на размер батча и динамические размерности¶
Размер батча¶
- Строгое ограничение: Поддерживается только размер батча равный 1
- Причина: Архитектура системы оптимизирована для поточной обработки одиночных изображений в реальном времени
- Требование к модели: Все модели должны быть сконвертированы и оптимизированы для работы с
batch_size=1
Динамические размерности¶
- Полное ограничение: Динамические размерности входных и выходных тензоров не поддерживаются
- Требования к входным тензорам:
- Все размерности должны быть фиксированными
- Размеры входного изображения должны быть четко заданы в model_info.json
-
Формат:
"img_size": {"height": Y, "width": X} -
Конкретные ограничения:
- ❌ Не поддерживается:
None,-1, динамические размеры в любых размерностях - ✅ Обязательно: Фиксированные значения для всех размерностей тензоров
Пример:
{
"img_size": {
"height": 640, // Фиксированная высота
"width": 640 // Фиксированная ширина
}
}
Ограничения на используемые Python-пакеты¶
Доступные пакеты в системном контейнере¶
Пользовательские модели могут использовать только те Python-пакеты, которые уже установлены в системном контейнере. Попытки импорта отсутствующих пакетов приведут к ошибке ModuleNotFoundError во время загрузки модели.
Базовый набор предустановленных пакетов:
Базовый набор предустановленных пакетов формируется при сборке контейнера и включает наиболее распространенные библиотеки для машинного обучения и обработки данных. Полный список доступных пакетов можно посмотреть в интерфейсе системы по адресу:
http://адрес_сервера/administrating/inference-packages
Строгие ограничения
- Запрещено: Установка дополнительных пакетов через pip/apt в коде пользовательской модели
- Запрещено: Импорт любых пакетов, отсутствующих в системном контейнере
- Запрещено: Модификация системных путей Python
sys.pathдля обхода ограничений
Приложение A: примеры реализации пользовательских моделей¶
В данном приложении показаны примеры реализаций пользовательский моделей для различных задач компьютерного зрения.
Интерфейс пользовательской модели¶
Интерфейс представляет собой формальный контракт (Protocol), который обеспечивает полиморфное взаимодействие между системой выполнения и пользовательскими моделями независимо от используемого бэкенда (ONNX, TensorRT, TensorFlow). Данный протокол гарантирует единообразное API для всех типов моделей, обеспечивая корректную интеграцию в inference pipeline.
Актуальные версии
Актуальные сигнатуры протоколов и типов находятся в библиотеке vlmodels_types.protocol. Реализации должны точно соответствовать актуальной версии интерфейсов, указанных в этой библиотеке.
Канонические определения типов и интерфейсов поддерживаются в пакете vlmodels_types. Для обеспечения совместимости необходимо использовать именно эти эталонные реализации.
Ниже приведены протоколы пользовательских моделей из библиотеки vlmodels_types v1.5.0
# Тип для возможных значений атрибутов модели
type AllowedModelAttributeValue = int | float | str | bool | None
class CustomModelProtocol[T_co: NamedTuple](Protocol):
"""
Протокол `CustomModelProtocol` описывает интерфейс, который должна реализовать
пользовательская модель onnx, tensorflow, tensorrt для интеграции с библиотекой.
Атрибуты:
input_names (Sequence[str]): Список имён входных тензоров, ожидаемых моделью.
output_names (Sequence[str]): Список имён выходных тензоров, которые возвращает модель.
Методы:
__init__(**model_attributes: AllowedModelAttributeValue):
Конструктор пользовательской модели.
При инициализации в него автоматически передаётся набор параметров `model_attributes`,
полученных либо из пользовательского интерфейса Node-RED, либо из файла `model_info.json`,
если параметры явно не заданы.
Каждый параметр должен соответствовать допустимым типам, описанным в `AllowedModelAttributeValue`.
preprocess(data: NDArray | Sequence[NDArray]) -> Mapping[str, NDArray | torch.Tensor]:
Выполняет предобработку исходного изображения или массива данных перед подачей в модель.
На выходе должен вернуть словарь, где ключи соответствуют `input_names`, а значения —
подготовленные входные тензоры в формате NumPy или PyTorch.
По-умолчанию тензоры должны быть расположены на устройстве CPU. В обратном случае, могут
возникать проблемы при использовании CPU модели вместо GPU.
postprocess(outputs: Mapping[str, torch.Tensor], categories: Sequence[str]) -> T_co:
Выполняет постобработку результатов работы модели.
Аргумент `outputs` — словарь, где ключи соответствуют `output_names`, а значения —
выходные тензоры модели в формате PyTorch.
Аргумент `categories` — набор доступных классов/меток.
Метод должен вернуть результат типа `T_co` (например, NamedTuple или другую согласованную структуру данных),
содержащий итоговые детекции или предсказания.
"""
input_names: Sequence[str]
output_names: Sequence[str]
def __init__(self, **model_attributes: AllowedModelAttributeValue) -> None: ...
def preprocess(self, data: "NDArray | Sequence[NDArray]") -> "Mapping[str, NDArray | torch.Tensor]": ...
def postprocess(self, outputs: "Mapping[str, torch.Tensor]", categories: Sequence[str]) -> T_co: ...
Для поддержки PyTorch моделей необходимо использовать CustomTorchModelProtocol, который расширяет возможности CustomModelProtocol добавлением методов для инициализации пользовательской PyTorch модели и постпроцессингом:
class CustomTorchModelProtocol[T_co: NamedTuple](CustomModelProtocol[T_co], Protocol):
"""
Протокол `CustomTorchModelProtocol` расширяет базовый `CustomModelProtocol`
и описывает интерфейс пользовательской модели, реализованной с использованием PyTorch.
Дополнительно к методам и атрибутам базового протокола, модель должна
предоставлять метод `build_torch_model` для инициализации и загрузки
архитектуры модели в формате PyTorch.
Методы:
build_torch_model(artifacts_path: Path, device: str) -> torch.nn.Module:
Создаёт и возвращает экземпляр PyTorch-модели, готовой к выполнению инференса.
Аргументы:
artifacts_path (Path): Путь к директории с весами модели в формате PyTorch.
device (str): Устройство, на которое будет загружена модель (например, "cuda:0" или "cpu").
Возвращает:
torch.nn.Module: Инициализированная модель PyTorch, готовая к запуску.
postprocess_torch(outputs: Any, categories: Sequence[str]) -> T_co:
Выполняет постобработку результатов работы PyTorch-модели модели.
Аргумент `outputs` — результат инференса модели.
Аргумент `categories` — набор доступных классов/меток.
Метод должен вернуть результат типа `T_co` (например, NamedTuple или другую согласованную структуру данных),
содержащий итоговые детекции или предсказания.
"""
def build_torch_model(self, artifacts_path: "Path", device: str) -> "torch.nn.Module": ...
def postprocess_torch(self, outputs: Any, categories: Sequence[str]) -> T_co: ...
Требования к реализации для различных бэкендов¶
Для ONNX:
- input_names/output_names должны точно соответствовать именам в ONNX графе
- preprocess может возвращать как NumPy arrays, так и torch.Tensors. Рекомендуется использовать torch.Tensors для предотвращения промежуточных конвертаций при использовании профиля развертывания inline_cuda_onnx_fp32. При использовании профиля развертывания inline_cpu_onnx_fp32 рекомендуется использовать NumPy arrays.
- postprocess получает outputs как torch.Tensor
- не поддерживаются динамические размерности модели
Для PyTorch:
- Поддерживаются только eager mode модели
- preprocess может возвращать как NumPy arrays, так и torch.Tensors. Рекомендуется использовать torch.Tensors для предотвращения промежуточных конвертаций.
- Гарантируется выполнение на том же device, что и модель
- не использовать аргумент map_location при загрузке модели. Замечены проблемы совместимости при использовании профиля развертывания inline_cpu_torch_fp32
Для TensorRT:
- input_names/output_names должны соответствовать именам в TensorRT engine
- preprocess может возвращать как NumPy arrays, так и torch.Tensors. Рекомендуется использовать torch.Tensors для предотвращения промежуточных конвертаций.
- не поддерживаются динамические размерности модели
Для TensorFlow:
- Поддерживается только SavedModel format
- input_names/output_names должны соответствовать сигнатуре SavedModel
- preprocess может возвращать как NumPy arrays, так и torch.Tensors. Рекомендуется использовать NumPy arrays для предотвращения промежуточных конвертаций.
Гарантии системы:
- Автоматическая обработка device placement (CPU/GPU)
- Консистентность типов данных между preprocess/postprocess
- Изоляция выполнения между различными моделями
- Автоматическая валидация соответствия указанных input_names/output_name в модели
Пример пользовательской модели классификатора¶
Ниже представлена реализация пользовательской модели классификатора, которая использует предобученную на ImageNet модель ResNet18 из torchvision и работает на бэкендах ONNX, Torch, TensorRT.
Модель имеет один вход ["input"], и возвращает ["logits"] который после постпроцессинга упаковывается в выходной тип данных Classification
Структура файлов директории пользовательской модели:
├── coco_categories.json # JSON-файл с описанием категорий
├── model_info.json # JSON-файл с метаданными и параметрами модели
├── model.py # Python-модуль, содержащий код для инициализации и работы модели.
├── weights_h224_w224_b1_c3_onnx1.17.0_fp16.onnx # сконвертированные веса ONNX в fp16
├── weights_h224_w224_b1_c3_onnx1.17.0_fp32.onnx # сконвертированные веса ONNX в fp32
├── weights_h224_w224_b1_cc8.6_cuda11.8_trt8.6.1_fp16.engine # сконвертированные веса TensorRT для целевой GPU архитектуры в fp16
├── weights_h224_w224_b1_cc8.6_cuda11.8_trt8.6.1_fp32.engine # сконвертированные веса TensorRT для целевой GPU архитектуры в fp32
└── weights.pt # веса модели для PyTorch
model.py:
from pathlib import Path
from collections.abc import Sequence, Mapping
import cv2
from numpy.typing import NDArray
import torch
import torch.nn.functional as F
import torchvision.transforms.functional as TF
from torchvision.models import resnet18
from torchvision import transforms
from vlmodels_types import Classification
from vlmodels_types.protocol.custom_model_protocol import AllowedModelAttributeValue, CustomTorchModelProtocol
class Model(CustomTorchModelProtocol[Classification]):
input_names = ["input"]
output_names = ["logits"]
def __init__(self, **model_attributes: AllowedModelAttributeValue) -> None:
"""
Инициализирует модель ResNet18 из torchvision с заданными параметрами.
Аргументы:
**model_attributes: Словарь с возможными ключами:
- 'conf_thres' (float): Порог уверенности для детекций.
Параметры model_attributes могут поступать либо из пользовательского интерфейса Node-RED,
либо из файла model_info.json (если model_attributes не передан или пуст).
"""
self.conf_thres: float = model_attributes["conf_thres"]
self.img_size = 224
self.imagenet_mean = [0.485, 0.456, 0.406]
self.imagenet_std = [0.229, 0.224, 0.225]
# ==== Доп. метод для torch-бэкенда (опционально) ====
def build_torch_model(self, artifacts_path: Path, device: str) -> torch.nn.Module:
model = resnet18(weights=None)
checkpoint = torch.load(artifacts_path / "weights.pt") # не используем map_location
model.load_state_dict(checkpoint)
model.to(device)
model.eval()
return model
def preprocess(self, data: NDArray) -> Mapping[str, torch.Tensor]:
img_rgb = cv2.cvtColor(data, cv2.COLOR_BGR2RGB)
t = torch.from_numpy(img_rgb).permute(2, 0, 1).float() / 255.0 # (3,H,W), [0..1]
t = TF.resize(t, 256, interpolation=transforms.InterpolationMode.BILINEAR) # короткая сторона -> 256
t = TF.center_crop(t, [self.img_size, self.img_size]) # 224x224
t = TF.normalize(t, mean=self.imagenet_mean, std=self.imagenet_std)
return {self.input_names[0]: t.unsqueeze(0)}
def postprocess(self, outputs: Mapping[str, torch.Tensor], categories: Sequence[str]) -> Classification:
if self.output_names != list(outputs.keys()):
raise ValueError("Output names mismatch")
for name, tensor in outputs.items():
if tensor.ndim == 1:
tensor = tensor.unsqueeze(0)
score, class_id = torch.max(F.softmax(tensor, dim=1), dim=1)
return Classification(
label=categories[int(class_id.item())],
score=float(score.item()),
)
def postprocess_torch(self, outputs: torch.Tensor, categories: Sequence[str]) -> Classification:
if outputs.ndim == 1:
outputs = outputs.unsqueeze(0)
score, class_id = torch.max(F.softmax(outputs, dim=1), dim=1)
return Classification(
label=categories[int(class_id.item())],
score=float(score.item()),
)
model_info.json:
{
"model_type": "custom_model",
"model_attributes": {
"conf_thres": {
"type": "float",
"default": 0.4,
"ru_name": "Порог уверенности"
}
},
"deploy_profile": {
"default_profile": "inline_cuda_trt_fp16",
"profiles": [
"inline_cuda_trt_fp16",
"inline_cuda_trt_fp32",
"inline_cuda_onnx_fp32",
"inline_cpu_onnx_fp32",
"inline_cuda_torch_fp32",
"inline_cpu_torch_fp32"
]
},
"task_type": "image_classification",
"model_name": "custom_resnet18_imagenet",
"model_version": "1",
"img_size": {
"height": 224,
"width": 224
},
"weights_path": "weights*.*",
"categories_path": "coco_categories.json"
}
Пример пользовательской модели детектора¶
Ниже представлена реализация пользовательской модели детектора, которая использует предобученную на MS COCO модель YoloV7 tiny из https://github.com/WongKinYiu/yolov7/releases и работает на бэкендах ONNX, Torch, TensorRT, Tensorflow. Модель имеет один вход ["images"], и возвращает ["output"] который после постпроцессинга упаковывается в выходной тип данных Detections
Структура файлов директории пользовательской модели:
├── coco_categories.json # JSON-файл с описанием категорий
├── custom_modules # Опциональная директория со вспомогательным кодом
│ ├── autoanchor.py # https://github.com/WongKinYiu/yolov7/blob/main/utils/autoanchor.py
│ ├── common.py # https://github.com/WongKinYiu/yolov7/blob/main/models/common.py
│ ├── datasets.py # https://github.com/WongKinYiu/yolov7/blob/main/utils/datasets.py
│ ├── experimental.py # https://github.com/WongKinYiu/yolov7/blob/main/models/experimental.py
│ ├── general.py # https://github.com/WongKinYiu/yolov7/blob/main/utils/general.py
│ ├── __init__.py
│ ├── loss.py # https://github.com/WongKinYiu/yolov7/blob/main/utils/loss.py
│ ├── torch_utils.py # https://github.com/WongKinYiu/yolov7/blob/main/utils/torch_utils.py
│ ├── yolo.py # https://github.com/WongKinYiu/yolov7/blob/main/models/yolo.py
│ └── yolo_utils.py # вспомогательный файл для реализации letterbox, non_max_suppression, scale_coords
├── model_info.json # JSON-файл с метаданными и параметрами модели
├── model.py # Python-модуль, содержащий код для инициализации и работы модели
├── weights_h640_w640_b1_c3_onnx1.17.0_fp32.onnx # сконвертированные веса ONNX в fp32
├── weights_h640_w640_b1_cc8.6_cuda11.8_trt8.6.1_fp16.engine # сконвертированные веса TensorRT для целевой GPU архитектуры в fp16
├── weights_h640_w640_b1_cc8.6_cuda11.8_trt8.6.1_fp32.engine # сконвертированные веса TensorRT для целевой GPU архитектуры в fp32
├── weights.pt # веса модели для PyTorch
└── weights_tf.tar # веса модели для Tensorflow
model.py:
from pathlib import Path
from collections.abc import Sequence, Mapping
from numpy.typing import NDArray
import torch
# модуль с вспомогательными функциями находится на одном уровне с моделью
from .custom_modules.yolo_utils import letterbox, non_max_suppression, scale_coords
from .custom_modules.experimental import attempt_load
from vlmodels_types import CustomTorchModelProtocol, Detections
from vlmodels_types.protocol.custom_model_protocol import AllowedModelAttributeValue
class Model(CustomTorchModelProtocol[Detections]):
input_names = ["images"]
output_names = ["output"]
def __init__(self, **model_attributes: AllowedModelAttributeValue) -> None:
"""
Инициализирует модель с заданными параметрами.
Аргументы:
**model_attributes: Словарь с возможными ключами:
- 'iou_thres' (float): Порог Intersection over Union для non-max suppression.
- 'conf_thres' (float): Порог уверенности для детекций.
Параметры model_attributes могут поступать либо из пользовательского интерфейса Node-RED,
либо из файла model_info.json (если model_attributes не передан или пуст).
Атрибуты:
height (int): Высота изображения в пикселях входного изображения.
width (int): Ширина изображения в пикселях входного изображения.
iou_thres (float): Порог IoU для non-max suppression.
conf_thres (float): Порог уверенности для детекций.
current_img_size (tuple[int, int] | None): Текущий размер изображения в формате (height, width).
"""
self.iou_thres: float = model_attributes["iou_thres"]
self.conf_thres: float = model_attributes["conf_thres"]
self.height: int = 640
self.width: int = 640
self.current_img_size: tuple[int, int] | None = None # (height, width)
# ==== Доп. метод для torch-бэкенда (опционально) ====
def build_torch_model(self, artifacts_path: Path, device: str) -> torch.nn.Module:
model = attempt_load(artifacts_path / "weights.pt") # не используем map_location
model.to(device)
model.eval()
return model
def preprocess(self, data: NDArray) -> Mapping[str, torch.Tensor]:
self.current_img_size = (data.shape[0], data.shape[1])
image, _, _ = letterbox(data, (self.height, self.width), auto=False)
tensor = torch.from_numpy(image)
tensor = (tensor.permute(2, 0, 1).flip(0).unsqueeze(0) / 255).contiguous()
return {self.input_names[0]: tensor}
def postprocess(self, outputs: Mapping[str, torch.Tensor], categories: Sequence[str]) -> Detections:
if self.output_names != list(outputs.keys()):
raise ValueError("Output names mismatch")
for name, tensor in outputs.items():
pred = non_max_suppression(tensor, self.conf_thres, self.iou_thres)[0]
pred[:, :4] = scale_coords(
(self.height, self.width),
pred[:, :4],
(self.current_img_size[0], self.current_img_size[1]),
).round()
bboxes = pred[:, :4].cpu().numpy().astype(int)
labels = tuple(map(lambda x: categories[int(x)], pred[:, 5].cpu().numpy()))
scores = pred[:, 4].cpu().numpy()
return Detections(bboxes, labels, scores)
def postprocess_torch(self, outputs: torch.Tensor, categories: Sequence[str]) -> Detections:
pred = non_max_suppression(outputs, self.conf_thres, self.iou_thres)[0]
pred[:, :4] = scale_coords(
(self.height, self.width),
pred[:, :4],
(self.current_img_size[0], self.current_img_size[1]),
).round()
bboxes = pred[:, :4].cpu().numpy().astype(int)
labels = tuple(map(lambda x: categories[int(x)], pred[:, 5].cpu().numpy()))
scores = pred[:, 4].cpu().numpy()
return Detections(bboxes, labels, scores)
model_info.json:
{
"model_type": "custom_model",
"model_attributes": {
"conf_thres": {
"type": "float",
"default": 0.4,
"ru_name": "Порог уверенности"
},
"iou_thres": {
"type": "float",
"default": 0.45,
"ru_name": "Порог IoU"
}
},
"deploy_profile": {
"default_profile": "inline_cuda_trt_fp16",
"profiles": [
"inline_cuda_trt_fp16",
"inline_cuda_trt_fp32",
"inline_cuda_onnx_fp32",
"inline_cpu_onnx_fp32",
"inline_cuda_torch_fp32",
"inline_cuda_torch_fp16",
"inline_cpu_torch_fp32",
"inline_cpu_torch_fp16",
"inline_cuda_tf_fp32",
"inline_cpu_tf_fp32"
]
},
"task_type": "image_detection",
"model_name": "custom_yolov7tiny_coco",
"model_version": "2",
"img_size": {
"height": 640,
"width": 640
},
"weights_path": "weights*.*",
"categories_path": "coco_categories.json"
}
Пример пользовательской модели сегментатора¶
Ниже представлена реализация пользовательской модели instance segmentation, которая использует предобученную на MS COCO модель MaskRCNN из torchvision и работает на бэкенде Torch.
Модель имеет один вход ["images"], и возвращает ["boxes", "labels", "scores", "masks"] которые упаковываются в выходной тип данных Detections
Структура файлов директории пользовательской модели:
├── coco_categories.json # JSON-файл с описанием категорий
├── model_info.json # JSON-файл с метаданными и параметрами модели
├── model.py # Python-модуль, содержащий код для инициализации и работы модели
├── weights.pt # веса модели maskrcnn
└── weights_resnet50-0676ba61.pth # веса модели для backbone resnet50
model.py:
from collections.abc import Sequence, Mapping
from pathlib import Path
from numpy.typing import NDArray
import numpy as np
import torch
from PIL import Image
from torchvision.models.detection import maskrcnn_resnet50_fpn, MaskRCNN_ResNet50_FPN_Weights
from vlmodels_types import Detections
from vlmodels_types.protocol.custom_model_protocol import AllowedModelAttributeValue, CustomTorchModelProtocol
class Model(CustomTorchModelProtocol[Detections]):
input_names = ["images"]
output_names = ["boxes", "labels", "scores", "masks"]
def __init__(self, **model_attributes: AllowedModelAttributeValue) -> None:
"""
Инициализирует модель MaskRCNN из torchvision с заданными параметрами.
Аргументы:
**model_attributes: Словарь с возможными ключами:
- 'iou_thres' (float): Порог Intersection over Union для non-max suppression.
- 'conf_thres' (float): Порог уверенности для детекций.
Параметры model_attributes могут поступать либо из пользовательского интерфейса Node-RED,
либо из файла model_info.json (если model_attributes не передан или пуст).
"""
self.iou_thres: float = model_attributes["iou_thres"]
self.conf_thres: float = model_attributes["conf_thres"]
weights = MaskRCNN_ResNet50_FPN_Weights.DEFAULT
self.preprocess_func = weights.transforms()
# ==== Доп. метод для torch-бэкенда (опционально) ====
def build_torch_model(self, artifacts_path: Path, device: str) -> torch.nn.Module:
model = maskrcnn_resnet50_fpn(
weights=None,
weights_backbone=None,
box_score_thresh=self.conf_thres,
box_nms_thresh=self.iou_thres
)
# Загружаем веса backbone (ResNet50)
backbone_state_dict = torch.load(artifacts_path / "weights_resnet50-0676ba61.pth") # не используем map_location
# Удаляем ключи, связанные с FC слоем
keys_to_remove = [k for k in backbone_state_dict.keys() if k.startswith('fc.')]
for key in keys_to_remove:
backbone_state_dict.pop(key)
checkpoint = torch.load(artifacts_path / "weights.pt") # не используем map_location
# Загружаем веса в модель
model.backbone.load_state_dict(backbone_state_dict, strict=False)
model.load_state_dict(checkpoint, strict=False)
model.to(device)
model.eval()
return model
def preprocess(self, data: NDArray) -> Mapping[str, torch.Tensor]:
img = self.preprocess_func(Image.fromarray(data))
return {self.input_names[0]: img.unsqueeze(0).cpu()}
def postprocess_torch(self, outputs: torch.Tensor, categories: Sequence[str]) -> Detections:
for out in outputs:
# Избавиться от канала в каждой маске
boxes = out["boxes"].cpu().numpy().astype(int)
masks = np.squeeze(out["masks"].cpu().numpy(), axis=1) # [N, 1, H, W] -> [N, H, W]
# Применяем порог
binary_masks = (masks > self.conf_thres).astype(np.uint8) * 255.0
# Обрезаем маски
binary_masks = [bin_mask[y1:y2, x1:x2] for bin_mask, (x1,y1,x2,y2) in zip(binary_masks, boxes, strict=True)]
return Detections(
boxes=boxes,
labels=tuple(map(lambda x: categories[int(x)], out["labels"].cpu().numpy())),
scores=out["scores"].cpu().numpy(),
masks=binary_masks,
)
Приложение Б: архитектура выполнения пользовательской модели¶
В данном приложении детально описывается внутренний механизм работы системы с экземплярами классов пользовательских моделей. Это поможет разработчикам понять, как система управляет жизненным циклом модели, обеспечивает изоляцию выполнения и автоматически передает все необходимые контексты и параметры.
Описание внутреннего механизма работы системы с пользовательской моделью¶
Для эффективной разработки и отладки пользовательских моделей важно понимать внутренний механизм их взаимодействия с системой. Ниже представлен упрощенный участок кода, демонстрирующий полный цикл инференса для бэкенда TensorRT.
Система автоматически управляет всем жизненным циклом модели:
1. Загружает модуль model.py пользовательской модели
2. Инициализирует экземпляр класса Model
3. Обеспечивает выполнение полного пайплайна предсказания
4. Автоматически передает все необходимые аргументы в методы модели
5. Обеспечивает очистку ресурсов модели и её выгрузку из памяти
```python def predict(self, image: "NDArray") -> T_co: """ Выполняет полный цикл предсказания модели на входном изображении.
Данный метод представляет собой унифицированный интерфейс, который
система использует для взаимодействия с пользовательской моделью
независимо от бэкенда (ONNX, Torch, TensorRT, TensorFlow).
Этапы выполнения:
1. **Препроцессинг**: Система вызывает метод `preprocess()` пользовательской модели,
передавая входное изображение. Пользовательская модель отвечает за преобразование
данных в формат, ожидаемый конкретным бэкендом (нормализация, ресайз, преобразование
в тензор и т.п.).
2. **Инференс**: Подготовленные данные передаются во внутренний метод `forward()`
выбранного бэкенда, который выполняет предсказание на акселераторе (GPU/CPU).
3. **Постпроцессинг**: Система автоматически передает raw output от модели и
дополнительные параметры (категории) в метод `postprocess()`
пользовательской модели для формирования финального результата.
:param image: NDArray
Входное изображение в формате numpy array. Система гарантирует корректность
типа и формы данных перед передачей в модель.
:return: T_co
Итоговый объект с результатами предсказания. Тип возвращаемого значения
определяется пользовательской моделью в Generic параметре T_co (обычно
это NamedTuple с четко описанной структурой).
Важно: Разработчику не нужно беспокоиться о передаче аргументов в методы - система
автоматически обеспечивает передачу всех необходимых параметров:
- `categories` загружаются из coco_categories.json
- `outputs` содержат raw tensors непосредственно от inference движка
"""
# Этап 1: Пользовательский препроцессинг
inputs: Mapping[str, NDArray | torch.Tensor] = self.custom_model_instance.preprocess(image)
# Этап 2: Нативный инференс выбранного бэкенда
outputs: Mapping[str, torch.Tensor] = self.forward(inputs)
# Этап 3: Пользовательский постпроцессинг с автоматической передачей аргументов
return self.custom_model_instance.postprocess(
outputs=outputs,
categories=self.labels # Автоматически загружается системой
)