ONNX версия модели

#1
by hiauiarau - opened

Привет, будут ли выпущены ONNX версии модели? например O3, O4?

Да это возможно, возьмем в проработку. Спасибо,что пользуетесь нашим решением.

А GGUF не планируете сделать ? при попытке прогнать через gguf-my-repo ругается на отсутствие токенайзера:
INFO:hf-to-gguf:Set model tokenizer
Traceback (most recent call last):
File "/home/user/app/./llama.cpp/convert_hf_to_gguf.py", line 5140, in
main()
File "/home/user/app/./llama.cpp/convert_hf_to_gguf.py", line 5134, in main
model_instance.write()
File "/home/user/app/./llama.cpp/convert_hf_to_gguf.py", line 440, in write
self.prepare_metadata(vocab_only=False)
File "/home/user/app/./llama.cpp/convert_hf_to_gguf.py", line 433, in prepare_metadata
self.set_vocab()
File "/home/user/app/./llama.cpp/convert_hf_to_gguf.py", line 4316, in set_vocab
raise FileNotFoundError(f"File not found: {tokenizer_path}")
FileNotFoundError: File not found: downloads/tmpbc6nm1xl/FRIDA/spiece.model

поддерживаю насчёт gguf
ни конвертнуть, ни в ollama не запустить

@hiauiarau , @ai-forever , модель, в целом, и так конвертируется в onnx.

  1. Конвертация:
import torch

from transformers import T5EncoderModel, AutoTokenizer
from pathlib import Path

MODEL_SOURCE_PATH = "ai-forever_frida" # Директория, в которой содержится модель 
MODEL_TARGET_PATH = Path("ai-forever_frida-onnx")
if not MODEL_TARGET_PATH.exists():
    MODEL_TARGET_PATH.mkdir()

tokenizer = AutoTokenizer.from_pretrained(MODEL_SOURCE_PATH)
model = T5EncoderModel.from_pretrained(MODEL_SOURCE_PATH)
model.eval()

dummy_input = tokenizer(
    "Инпут для конвертации", max_length=512, padding=True, truncation=True, return_tensors="pt"
)["input_ids"]

torch.onnx.export(
    model,
    dummy_input,
    (MODEL_TARGET_PATH / "t5_encoder.onnx").as_posix(),
    input_names=["input_ids"],
    output_names=["hidden_states"],
    dynamic_axes={
        "input_ids": {0: "batch_size", 1: "sequence_length"},
        "hidden_states": {0: "batch_size", 1: "sequence_length"},
    }
)
tokenizer.save_pretrained(MODEL_TARGET_PATH)
  1. Инференс:
import numpy as np
import onnxruntime as ort
from transformers import AutoTokenizer
from pathlib import Path

MODEL_PATH = Path("ai-forever_frida-onnx/t5_encoder.onnx")
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH.parent.as_posix())
model = ort.InferenceSession(MODEL_PATH)

my_input = tokenizer(
    "search_query: Проверяем, работает ли onnx.", max_length=512, padding=True, truncation=True, return_tensors="np"
)["input_ids"]

outputs = model.run(None, {"input_ids": my_input})[0][:, 0][0]
embeddings = outputs / np.linalg.norm(outputs)
print(embeddings)

Косинусое расстояние между полученными векторами на 130 семплах (Клиент SentenceTransformers и ONNX) в среднем 0.99624.
Главное учитывать передачу в энкодер prompt_name.

@aveitsme ваш код не воспроизводится. В папке не создается файл model.onnx.data а создается множество мелких файлов каждого слоя, всего 224 файла. Удачный экспорт происходит при указании dynamo=True. Но, тогда ошибки инференса 1. модель ждет ждет входной тензор в формате int64 2. модель ждет входной тензор формой [1, '8']
torch 2.6.0, onnx 1.16.1.

update: окей, такая версия все равно работает.

@hiauiarau , @ai-forever , модель, в целом, и так конвертируется в onnx.

  1. Конвертация:
import torch

from transformers import T5EncoderModel, AutoTokenizer
from pathlib import Path

MODEL_SOURCE_PATH = "ai-forever_frida" # Директория, в которой содержится модель 
MODEL_TARGET_PATH = Path("ai-forever_frida-onnx")
if not MODEL_TARGET_PATH.exists():
    MODEL_TARGET_PATH.mkdir()

tokenizer = AutoTokenizer.from_pretrained(MODEL_SOURCE_PATH)
model = T5EncoderModel.from_pretrained(MODEL_SOURCE_PATH)
model.eval()

dummy_input = tokenizer(
    "Инпут для конвертации", max_length=512, padding=True, truncation=True, return_tensors="pt"
)["input_ids"]

torch.onnx.export(
    model,
    dummy_input,
    (MODEL_TARGET_PATH / "t5_encoder.onnx").as_posix(),
    input_names=["input_ids"],
    output_names=["hidden_states"],
    dynamic_axes={
        "input_ids": {0: "batch_size", 1: "sequence_length"},
        "hidden_states": {0: "batch_size", 1: "sequence_length"},
    }
)
tokenizer.save_pretrained(MODEL_TARGET_PATH)
  1. Инференс:
import numpy as np
import onnxruntime as ort
from transformers import AutoTokenizer
from pathlib import Path

MODEL_PATH = Path("ai-forever_frida-onnx/t5_encoder.onnx")
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH.parent.as_posix())
model = ort.InferenceSession(MODEL_PATH)

my_input = tokenizer(
    "search_query: Проверяем, работает ли onnx.", max_length=512, padding=True, truncation=True, return_tensors="np"
)["input_ids"]

outputs = model.run(None, {"input_ids": my_input})[0][:, 0][0]
embeddings = outputs / np.linalg.norm(outputs)
print(embeddings)

Косинусое расстояние между полученными векторами на 130 семплах (Клиент SentenceTransformers и ONNX) в среднем 0.99624.
Главное учитывать передачу в энкодер prompt_name.

Подскажите почему используете только "input_ids" без "attention_mask" или инференс где нужно передавать два входа избыточен для FRIDA?
Вот моя реализация с примером для тестирования:

import torch
from transformers import T5EncoderModel, AutoTokenizer
from pathlib import Path
import onnxruntime as ort
import numpy as np


MODEL_SOURCE_ID = "ai-forever/FRIDA"
MODEL_TARGET_PATH = Path("onnx/frida-onnx")
ONNX_FILE_NAME = "FRIDA.onnx"

print("="*50)
print(f"Подготовка директории: {MODEL_TARGET_PATH}")
MODEL_TARGET_PATH.mkdir(parents=True, exist_ok=True)

# 1. Загружаем модель и токенизатор
print(f"Загрузка модели и токенизатора из '{MODEL_SOURCE_ID}'...")
tokenizer = AutoTokenizer.from_pretrained(MODEL_SOURCE_ID)
model = T5EncoderModel.from_pretrained(MODEL_SOURCE_ID)
model.eval()

# 2. Создаем тестовые входы
print("Создание тестовых входных данных...")
test_texts = [
    "paraphrase: В Ярославской области разрешили работу бань, но без посетителей",
    "search_query: Сколько программистов нужно, чтобы вкрутить лампочку?",
    "categorize_entailment: Женщину доставили в больницу, за ее жизнь сейчас борются врачи."
]

dummy_inputs = tokenizer(
    test_texts[0],
    max_length=512,
    padding="max_length",
    truncation=True,
    return_tensors="pt"
)

# 3. Экспорт с двумя входами
onnx_model_path = MODEL_TARGET_PATH / ONNX_FILE_NAME
print(f"Экспорт модели в ONNX формат: {onnx_model_path}")

torch.onnx.export(
    model,
    (dummy_inputs["input_ids"], dummy_inputs["attention_mask"]),
    onnx_model_path.as_posix(),
    input_names=["input_ids", "attention_mask"],
    output_names=["last_hidden_state"],
    opset_version=14,
    dynamic_axes={
        "input_ids": {0: "batch_size", 1: "sequence_length"},
        "attention_mask": {0: "batch_size", 1: "sequence_length"},
        "last_hidden_state": {0: "batch_size", 1: "sequence_length"}
    },
    verbose=False
)

# 4. Сохраняем токенизатор
print(f"Сохранение токенизатора в '{MODEL_TARGET_PATH}'...")
tokenizer.save_pretrained(MODEL_TARGET_PATH)

print("Конвертация завершена успешно!")

# 5. Тестирование и сравнение результатов
print("\n" + "="*50)
print("ТЕСТИРОВАНИЕ РЕЗУЛЬТАТОВ")

def cls_pooling(hidden_state, attention_mask):
    """CLS pooling для получения эмбеддингов"""
    return hidden_state[:, 0]  

def normalize_embeddings(embeddings):
    """Нормализация эмбеддингов"""
    return embeddings / np.linalg.norm(embeddings, axis=1, keepdims=True)

# Тест с оригинальной моделью
print("Тестирование оригинальной модели...")
with torch.no_grad():
    original_inputs = tokenizer(
        test_texts, 
        max_length=512, 
        padding=True, 
        truncation=True, 
        return_tensors="pt"
    )
    original_outputs = model(**original_inputs)
    original_embeddings = cls_pooling(
        original_outputs.last_hidden_state, 
        original_inputs["attention_mask"]
    )
    original_embeddings = torch.nn.functional.normalize(original_embeddings, p=2, dim=1)

# Тест с ONNX моделью
print("Тестирование ONNX модели...")
onnx_session = ort.InferenceSession(onnx_model_path.as_posix())

onnx_inputs = tokenizer(
    test_texts,
    max_length=512,
    padding=True,
    truncation=True,
    return_tensors="np"
)


onnx_inputs_int64 = {
    "input_ids": onnx_inputs["input_ids"].astype(np.int64),
    "attention_mask": onnx_inputs["attention_mask"].astype(np.int64)
}

onnx_outputs = onnx_session.run(None, onnx_inputs_int64)[0]

onnx_embeddings = onnx_outputs[:, 0]
onnx_embeddings = normalize_embeddings(onnx_embeddings)

cosine_similarity = np.sum(original_embeddings.numpy() * onnx_embeddings, axis=1)
print(f"\nCosine similarity между оригинальной и ONNX моделью:")
for i, sim in enumerate(cosine_similarity):
    print(f"  Текст {i+1}: {sim:.6f}")
print(f"Средняя схожесть: {np.mean(cosine_similarity):.6f}")

print("\n" + "="*50)
print("ГОТОВО! Модель успешно конвертирована и протестирована.")
print(f"Путь к модели: {MODEL_TARGET_PATH.resolve()}")

Sign up or log in to comment