Fixing Transformer Code Compilation Errors
TransformerDemo.pas
PAS 842B
Transformer.pas
PAS 6.63KB
Optimizers.pas
PAS 2.19KB
MatrixOps.pas
PAS 14.83KB
Mask.pas
PAS 680B
Attention.pas
PAS 5.78KB
AdamOptimizer.pas
PAS 1.26KB
PositionalEncoding.pas
PAS 813B
Привет! В прошлом чате мы делали чат ИИ на FreePascal'е под Debian Sid GNU/Linux на x86-64. Сделали AdvancedChatAI, усовершенствовали NeuralNetwork, NeuralNetworkIntegration. Затем решили сделать ИИ мощнее и добавили LSTM. Когда стали делать Transformer чат закончился. Давай продолжим. Сейчас: fpc TransformerDemo.pas 
Free Pascal Compiler version 3.3.1 [2025/06/28] for x86_64
Copyright (c) 1993-2023 by Florian Klaempfl and others
Target OS: Linux for x86-64
Compiling TransformerDemo.pas
Compiling Transformer.pas
Compiling PositionalEncoding.pas
PositionalEncoding.pas(19,19) Warning: Function result variable of a managed type does not seem to be initialized
Compiling Mask.pas
Mask.pas(18,19) Warning: Function result variable of a managed type does not seem to be initialized
Mask.pas(28,19) Warning: Function result variable of a managed type does not seem to be initialized
Compiling AdamOptimizer.pas
Compiling Optimizers.pas
Transformer.pas(184,28) Error: Identifier idents no member "Embedding_Grad"
Transformer.pas(185,28) Error: Identifier idents no member "Embedding_AdamState"
Transformer.pas(196,92) Error: Identifier idents no member "Wq_AdamState"
Transformer.pas(201,4) Fatal: There were 3 errors compiling module, stopping

Давайте исправим ошибки в коде Transformer. Основные проблемы связаны с отсутствием некоторых полей в структуре TTransformer и TAttentionHead. Вот исправления:

    В файле Transformer.pas нужно добавить недостающие поля в структуры:

pascal

type
  TTransformer = record
    Layers: array of TTransformerLayer;
    Embedding: TDoubleMatrix;
    Embedding_Grad: TDoubleMatrix;        // Добавляем
    Embedding_AdamState: TAdamState;     // Добавляем
    PosEncoding: TDoubleMatrix;
    Config: TTransformerConfig;
  end;

  TAttentionHead = record
    Wq, Wk, Wv: TDoubleMatrix;
    Wo: TDoubleMatrix;
    dWq, dWk, dWv, dWo: TDoubleMatrix;
    Wq_AdamState, Wk_AdamState, Wv_AdamState, Wo_AdamState: TAdamState; // Добавляем
    cachedQ, cachedK, cachedV: TDoubleMatrix;
  end;

    В процедуре InitializeAttentionHead нужно инициализировать новые поля:

pascal

procedure InitializeAttentionHead(var head: TAttentionHead; inputSize, headSize: Integer);
begin
  head.Wq := RandomMatrix(headSize, inputSize, -0.1, 0.1);
  head.Wk := RandomMatrix(headSize, inputSize, -0.1, 0.1);
  head.Wv := RandomMatrix(headSize, inputSize, -0.1, 0.1);
  head.Wo := RandomMatrix(inputSize, headSize, -0.1, 0.1);
  
  // Инициализация состояний Adam
  InitAdamState(head.Wq_AdamState, headSize, inputSize);
  InitAdamState(head.Wk_AdamState, headSize, inputSize);
  InitAdamState(head.Wv_AdamState, headSize, inputSize);
  InitAdamState(head.Wo_AdamState, inputSize, headSize);
end;

    В процедуре InitTransformer нужно инициализировать новые поля:

pascal

procedure InitTransformer(var model: TTransformer; config: TTransformerConfig);
var
  i: Integer;
begin
  model.Config := config;
  
  // Инициализация эмбеддингов
  model.Embedding := RandomMatrix(config.InputSize, config.InputSize, -0.1, 0.1);
  InitAdamState(model.Embedding_AdamState, config.InputSize, config.InputSize);
  
  // Позиционное кодирование
  model.PosEncoding := GetPositionalEncoding(config.MaxSeqLength, config.InputSize);
  
  // Инициализация слоев
  SetLength(model.Layers, config.NumLayers);
  for i := 0 to config.NumLayers - 1 do
  begin
    InitializeMultiHeadAttention(model.Layers[i].SelfAttention,
                               config.InputSize,
                               config.InputSize div config.NumHeads,
                               config.NumHeads);
    
    // FFN с расширением до FFNDim и обратно
    SetLength(model.Layers[i].FFN, config.FFNDim, config.InputSize);
    InitAdamState(model.Layers[i].FFN_AdamState, config.FFNDim, config.InputSize);
    
    // LayerNorm параметры
    SetLength(model.Layers[i].Norm1_Gamma, config.InputSize);
    SetLength(model.Layers[i].Norm1_Beta, config.InputSize);
    FillArray(model.Layers[i].Norm1_Gamma, 1.0);
    FillArray(model.Layers[i].Norm1_Beta, 0.0);
    
    SetLength(model.Layers[i].Norm2_Gamma, config.InputSize);
    SetLength(model.Layers[i].Norm2_Beta, config.InputSize);
    FillArray(model.Layers[i].Norm2_Gamma, 1.0);
    FillArray(model.Layers[i].Norm2_Beta, 0.0);
  end;
end;

    В процедуре UpdateAttentionLayer нужно добавить обновление всех матриц:

pascal

procedure UpdateAttentionLayer(var attention: TMultiHeadAttention; learningRate: Double);
var
  i: Integer;
begin
  for i := 0 to High(attention.Heads) do
  begin
    UpdateMatrixWithAdam(attention.Heads[i].Wq, attention.Heads[i].dWq, attention.Heads[i].Wq_AdamState, learningRate);
    UpdateMatrixWithAdam(attention.Heads[i].Wk, attention.Heads[i].dWk, attention.Heads[i].Wk_AdamState, learningRate);
    UpdateMatrixWithAdam(attention.Heads[i].Wv, attention.Heads[i].dWv, attention.Heads[i].Wv_AdamState, learningRate);
    UpdateMatrixWithAdam(attention.Heads[i].Wo, attention.Heads[i].dWo, attention.Heads[i].Wo_AdamState, learningRate);
  end;
end;

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

Для полной функциональности вам также нужно будет реализовать процедуру BackwardTransformer, которая сейчас пустая, но это уже выходит за рамки текущего вопроса об ошибках компиляции.
Видимо нужно дописать функцию CreateRandomMatrix, сейчас: TransformerDemo.pas(23,12) Error: Identifier not found "CreateRandomMatrix"

Да, вам нужно добавить функцию CreateRandomMatrix в модуль MatrixOps.pas. Вот реализация:

    Добавьте объявление функции в интерфейсную часть MatrixOps.pas:

pascal

function CreateRandomMatrix(rows, cols: Integer; minVal: Double = -0.5; maxVal: Double = 0.5): TDoubleMatrix;

    Добавьте реализацию в раздел implementation:

pascal

function CreateRandomMatrix(rows, cols: Integer; minVal: Double; maxVal: Double): TDoubleMatrix;
var
  i, j: Integer;
begin
  SetLength(Result, rows, cols);
  for i := 0 to rows - 1 do
  begin
    for j := 0 to cols - 1 do
    begin
      Result[i][j] := minVal + (maxVal - minVal) * Random;
    end;
  end;
end;

    Убедитесь, что в начале модуля MatrixOps.pas добавлен Randomize в секцию инициализации (если его там нет):

pascal

initialization
  Randomize;

Это создаст матрицу указанного размера со случайными значениями в заданном диапазоне (по умолчанию от -0.5 до 0.5).

После этих изменений ошибка "Identifier not found 'CreateRandomMatrix'" должна исчезнуть, так как функция будет доступна в модуле MatrixOps, который используется в TransformerDemo.
Спасибо, собралась! Пишет при запуске: ./TransformerDemo
An unhandled exception occurred at $000055A1EA51C720:
Exception: Matrix dimensions mismatch in MatrixMultiply: A[64x64] * B[5x64]
  $000055A1EA51C720
  $000055A1EA51A30D
  $000055A1EA4F61D3

Ошибка возникает из-за несоответствия размеров матриц при умножении в ForwardTransformer. Проблема в том, что вы умножаете матрицу эмбеддингов (размером InputSize x InputSize, т.е. 64x64) на входную матрицу (размером 5x64), что математически невозможно.

Вот как нужно исправить:

    В файле Transformer.pas измените операцию умножения в ForwardTransformer:

pascal

procedure ForwardTransformer(var model: TTransformer;
                           const input: TDoubleMatrix;
                           out output: TDoubleMatrix;
                           mask: TDoubleMatrix = nil);
var
  i: Integer;
  x, attnOutput, ffnOutput: TDoubleMatrix;
begin
  // 1. Эмбеддинг + позиционное кодирование
  // Изменяем порядок умножения и транспонируем вход
  x := MatrixMultiply(input, TransposeMatrix(model.Embedding));
  x := AddPositionalEncoding(x, model.PosEncoding);
  ...

    Альтернативно, можно изменить размерность эмбеддингов при инициализации в InitTransformer:

pascal

// Вместо:
model.Embedding := RandomMatrix(config.InputSize, config.InputSize, -0.1, 0.1);
// Сделать:
model.Embedding := RandomMatrix(config.InputSize, config.InputSize, -0.1, 0.1);

    В демо-программе TransformerDemo.pas убедитесь, что входные данные имеют правильную размерность:

pascal

// Вместо:
input := CreateRandomMatrix(5, config.InputSize); // 5 токенов
// Можно сделать:
input := CreateRandomMatrix(config.InputSize, 5); // 5 токенов

Основное правило: при умножении матриц A (m×n) и B (p×q) должно выполняться условие n = p. В вашем случае было 64×64 * 5×64, что нарушает это правило.

После исправления порядок умножения будет правильным:

    Либо input (64×5) * Embedding (5×64) = (64×64)

    Либо Embedding (64×64) * input (64×5) = (64×5)

Выберите тот вариант, который соответствует вашей архитектуре модели. Обычно в трансформерах входные эмбеддинги имеют размерность [input_size × embedding_dim], а входные данные [sequence_length × input_size].
Я изменил порядок умножения и сделал input := CreateRandomMatrix(config.InputSize, 5); Теперь: ./TransformerDemo
An unhandled exception occurred at $00005609B7773730:
Exception: Matrix dimensions mismatch in MatrixMultiply: A[64x5] * B[64x64]
  $00005609B7773730
  $00005609B7771320
  $00005609B774D1D3

Ошибка всё ещё связана с несоответствием размерностей матриц при умножении. Давайте полностью разберём и исправим этот момент.

Проблема возникает в строке:
pascal

x := MatrixMultiply(input, TransposeMatrix(model.Embedding));

где:

    input имеет размер 64x5 (config.InputSize x sequence_length)

    model.Embedding имеет размер 64x64

После транспонирования Embedding становится 64x64, и умножение 64x5 * 64x64 невозможно (число столбцов первой матрицы (5) не равно числу строк второй (64)).
Правильное решение:

    Измените инициализацию эмбеддингов в InitTransformer:

pascal

// Было:
model.Embedding := RandomMatrix(config.InputSize, config.InputSize, -0.1, 0.1);
// Стало:
model.Embedding := RandomMatrix(config.InputSize, 5, -0.1, 0.1); // embedding_dim x sequence_length

    Или измените порядок умножения в ForwardTransformer:

pascal

// Было:
x := MatrixMultiply(input, TransposeMatrix(model.Embedding));
// Стало:
x := MatrixMultiply(model.Embedding, input); // (64x64) * (64x5) = (64x5)

    Соответственно в демо-программе используйте:

pascal

input := CreateRandomMatrix(config.InputSize, 5); // (64x5)

Полный исправленный код:

В Transformer.pas:
pascal

procedure ForwardTransformer(...);
begin
  // 1. Эмбеддинг + позиционное кодирование
  x := MatrixMultiply(model.Embedding, input); // [64x64] * [64x5] = [64x5]
  x := AddPositionalEncoding(x, model.PosEncoding);
  ...
end;

procedure InitTransformer(...);
begin
  ...
  model.Embedding := RandomMatrix(config.InputSize, config.InputSize, -0.1, 0.1); // [64x64]
  ...
end;

В TransformerDemo.pas:
pascal

input := CreateRandomMatrix(config.InputSize, 5); // [64x5] tokens

Альтернативный подход (более правильный):

Если вы хотите следовать классической архитектуре Transformer, где:

    Вход: [sequence_length x input_dim]

    Эмбеддинги: [input_dim x embedding_dim]

Тогда нужно:

    В демо:

pascal

input := CreateRandomMatrix(5, config.InputSize); // [5x64]

    В трансформере:

pascal

x := MatrixMultiply(input, model.Embedding); // [5x64] * [64x64] = [5x64]

Выберите подход, который лучше соответствует вашей архитектуре. Первый вариант проще для демонстрации, второй более каноничен для трансформеров.
Теперь ./TransformerDemo
An unhandled exception occurred at $000056424FD32720:
Exception: Matrix dimensions mismatch in MatrixMultiply: A[32x64] * B[5x64]
  $000056424FD32720
  $000056424FD7605D
  $000056424FD303A3
  $000056424FD0C1D3
Может я не так исправил? Сейчас у меня: procedure ForwardTransformer(var model: TTransformer;
                           const input: TDoubleMatrix;
                           out output: TDoubleMatrix;
                           mask: TDoubleMatrix = nil);
var
  i: Integer;
  x, attnOutput, ffnOutput: TDoubleMatrix;
begin
  // 1. Эмбеддинг + позиционное кодирование
//  x := MatrixMultiply(model.Embedding, input);
//  x := AddPositionalEncoding(x, model.PosEncoding);

  // 1. Эмбеддинг + позиционное кодирование
  // Изменяем порядок умножения и транспонируем вход
//  x := MatrixMultiply(input, TransposeMatrix(model.Embedding));
x := MatrixMultiply(input, model.Embedding); // [5x64] * [64x64] = [5x64]

  x := AddPositionalEncoding(x, model.PosEncoding);

  // 2. Проход через все слои
  for i := 0 to High(model.Layers) do
  begin
    // Self-Attention с маскированием
    MultiHeadAttentionForward(model.Layers[i].SelfAttention, x, attnOutput, mask);

    // Dropout и residual connection
    attnOutput := MatrixAdd(x, Dropout(attnOutput, model.Config.DropoutRate));

    // LayerNorm
    attnOutput := LayerNorm(attnOutput,
                          model.Layers[i].Norm1_Gamma,
                          model.Layers[i].Norm1_Beta);

    // FFN
    ffnOutput := MatrixMultiply(model.Layers[i].FFN, attnOutput);
    ffnOutput := ReLU(ffnOutput);

    // Dropout и residual connection
    x := MatrixAdd(attnOutput, Dropout(ffnOutput, model.Config.DropoutRate));

    // LayerNorm
    x := LayerNorm(x,
                 model.Layers[i].Norm2_Gamma,
                 model.Layers[i].Norm2_Beta);
  end;

  output := x;
end;     program TransformerDemo;

uses
  Transformer, MatrixOps;

var
  model: TTransformer;
  config: TTransformerConfig;
  input, output: TDoubleMatrix;
begin
  // Простая конфигурация для теста
  config.InputSize := 64;
  config.NumLayers := 2;
  config.NumHeads := 2;
  config.FFNDim := 128;
  config.MaxSeqLength := 50;
  config.DropoutRate := 0.1;

  // Инициализация
  InitTransformer(model, config);

  // Тестовые данные
  input := CreateRandomMatrix(5, config.InputSize); // 5 токенов
//input := CreateRandomMatrix(config.InputSize, 5); // 5 токенов

  // Прямой проход
  ForwardTransformer(model, input, output);

  // Очистка
  FreeTransformer(model);

  WriteLn('Transformer успешно выполнил прямой проход! Размер вывода: ',
          Length(output), 'x', Length(output[0]));
end.

Ошибка теперь возникает при умножении в слое FFN (Feed Forward Network). Давайте полностью разберём и исправим все размерности в вашем Transformer.
Основные проблемы:

    Несоответствие размерностей в FFN слое:

        У вас FFNDim = 128, а InputSize = 64

        При умножении FFN(128x64) * attnOutput(5x64) - это невозможно (64 ≠ 5)

    Архитектурные несоответствия:

        В Transformer размерность обычно сохраняется через все слои

        FFN обычно расширяет размерность и затем сжимает обратно

Полное исправление:

    В InitTransformer измените инициализацию FFN:

pascal

// Было:
SetLength(model.Layers[i].FFN, config.FFNDim, config.InputSize);
// Стало (две матрицы для FFN):
SetLength(model.Layers[i].FFN1, config.FFNDim, config.InputSize);  // Расширение
SetLength(model.Layers[i].FFN2, config.InputSize, config.FFNDim);  // Сжатие

    В ForwardTransformer измените FFN часть:

pascal

// FFN часть
ffnOutput := MatrixMultiply(model.Layers[i].FFN1, attnOutput);  // [128x64]*[5x64]T = [128x5]
ffnOutput := ReLU(ffnOutput);
ffnOutput := MatrixMultiply(model.Layers[i].FFN2, ffnOutput);   // [64x128]*[128x5] = [64x5]

    В структуре TTransformerLayer добавьте новые поля:

pascal

TTransformerLayer = record
  SelfAttention: TMultiHeadAttention;
  FFN1, FFN2: TDoubleMatrix;  // Две матрицы для FFN
  FFN1_Grad, FFN2_Grad: TDoubleMatrix;
  FFN1_AdamState, FFN2_AdamState: TAdamState;
  ...
end;

    В UpdateTransformer добавьте обновление новых параметров:

pascal

// Обновление FFN
UpdateMatrixWithAdam(model.Layers[i].FFN1, model.Layers[i].FFN1_Grad, 
                   model.Layers[i].FFN1_AdamState, learningRate);
UpdateMatrixWithAdam(model.Layers[i].FFN2, model.Layers[i].FFN2_Grad,
                   model.Layers[i].FFN2_AdamState, learningRate);

Почему это работает:

    Последовательность размерностей:

        Вход: [5x64]

        После эмбеддинга: [5x64]

        После внимания: [5x64]

        FFN1: [128x64] * [5x64]T = [128x5]

        FFN2: [64x128] * [128x5] = [64x5]

        Итоговый вывод: [5x64] (после residual и norm)

    Соответствие архитектуре Transformer:

        Первая матрица FFN расширяет размерность

        Вторая - сжимает обратно

        Между ними ReLU активация

Дополнительные рекомендации:

    Добавьте проверки размерностей в критических местах:

pascal

Assert(Length(input) = 5, 'Input should have 5 tokens');
Assert(Length(input[0]) = config.InputSize, 'Input dimension mismatch');

    Для отладки выводите размерности матриц:

pascal

WriteLn('Input size: ', Length(input), 'x', Length(input[0]));

После этих изменений трансформер должен работать корректно с входом [5x64] и конфигурацией, которую вы указали.
Всё равно: ./TransformerDemo
An unhandled exception occurred at $0000561A065C0890:
Exception: Matrix dimensions mismatch in MatrixMultiply: A[32x64] * B[5x64]
  $0000561A065C0890
  $0000561A066041CD
  $0000561A065BE413
  $0000561A0659A1D3

Ошибка указывает на несоответствие размеров матриц в операции умножения: вы пытаетесь умножить матрицу 32x64 на матрицу 5x64, что невозможно (число столбцов первой матрицы (64) не равно числу строк второй матрицы (5)).
Глубокий анализ проблемы:

    Источник ошибки:

        Ошибка возникает в MultiHeadAttentionForward

        Размер 32x64 соответствует матрице внимания (head_size = 32, input_size = 64)

        Входная матрица имеет размер 5x64 (5 токенов, 64 признака)

    Коренная причина:

        В InitializeMultiHeadAttention вы устанавливаете headSize = config.InputSize div config.NumHeads = 64/2 = 32

        Но матрицы проекции (Wq, Wk, Wv) инициализируются как [headSize x inputSize] = [32x64]

        При умножении Wq * input = [32x64] * [5x64] - это невозможно

Полное решение:

    Измените инициализацию внимания в Attention.pas:

pascal

procedure InitializeAttentionHead(var head: TAttentionHead; inputSize, headSize: Integer);
begin
  // Изменяем порядок размерностей для правильного умножения
  head.Wq := RandomMatrix(inputSize, headSize, -0.1, 0.1);  // [64x32]
  head.Wk := RandomMatrix(inputSize, headSize, -0.1, 0.1);  // [64x32]
  head.Wv := RandomMatrix(inputSize, headSize, -0.1, 0.1);  // [64x32]
  head.Wo := RandomMatrix(headSize, inputSize, -0.1, 0.1);  // [32x64]
end;

    Обновите умножение в MultiHeadAttentionForward:

pascal

procedure MultiHeadAttentionForward(...);
begin
  // Проецируем входы с правильным порядком умножения
  Q := MatrixMultiply(input, mha.Heads[i].Wq);  // [5x64] * [64x32] = [5x32]
  K := MatrixMultiply(input, mha.Heads[i].Wk);  // [5x64] * [64x32] = [5x32]
  V := MatrixMultiply(input, mha.Heads[i].Wv);  // [5x64] * [64x32] = [5x32]
  
  // Вычисляем внимание
  headOutputs[i] := ScaledDotProductAttention(Q, K, V);  // [5x32]
  
  // Конкатенируем и проецируем обратно
  mha.cachedOutput := ConcatMatrices(headOutputs);  // [5x64] (если 2 головы по 32)
  output := MatrixMultiply(mha.cachedOutput, mha.Heads[0].Wo);  // [5x64] * [64x64] = [5x64]
end;

    Проверьте конфигурацию:

pascal

config.InputSize := 64;
config.NumHeads := 2;  // headSize будет 32 (64/2)
config.FFNDim := 128;  // Должно быть >= InputSize

Ключевые изменения:

    Порядок матриц:

        Матрицы проекции Wq,Wk,Wv теперь [inputSize x headSize] (64x32)

        Вход умножается на них слева: [5x64] * [64x32] = [5x32]

    Конкатенация голов:

        2 головы по 32 = 64 (соответствует inputSize)

        Выход attention [5x64] умножается на Wo [64x64] = [5x64]

    Сохранение размерности:

        На всех этапах сохраняется размерность [5x64]

        Это соответствует ожидаемому поведению Transformer

Дополнительные проверки:

    Добавьте asserts для проверки размерностей:

pascal

Assert(Length(input[0]) = config.InputSize, 'Input dimension mismatch');
Assert(Length(Q[0]) = config.InputSize div config.NumHeads, 'Head size mismatch');

    Для отладки выводите размерности:

pascal

WriteLn('Q size: ', Length(Q), 'x', Length(Q[0]));

После этих изменений архитектура будет правильно обрабатывать входные данные размером [5x64] через все слои трансформера.
Теперь: ./TransformerDemo
An unhandled exception occurred at $0000563FA08C9890:
Exception: Matrix dimensions mismatch in MatrixMultiply: A[5x64] * B[32x64]
  $0000563FA08C9890
  $0000563FA090D32C
  $0000563FA08C7413
  $0000563FA08A31D3

Ошибка указывает на попытку умножения матриц размером [5x64] * [32x64], что математически невозможно (число столбцов первой матрицы (64) не равно числу строк второй (32)). Давайте полностью разберём и исправим архитектуру.
Полное решение:

    Измените структуру MultiHeadAttention:

pascal

procedure MultiHeadAttentionForward(...);
var
  i: Integer;
  Q, K, V: TDoubleMatrix;
  headOutputs: array of TDoubleMatrix;
begin
  SetLength(headOutputs, mha.NumHeads);
  
  for i := 0 to mha.NumHeads - 1 do
  begin
    // Правильный порядок умножения (вход [5x64], веса [64x32])
    Q := MatrixMultiply(input, mha.Heads[i].Wq);  // [5x64]*[64x32]=[5x32]
    K := MatrixMultiply(input, mha.Heads[i].Wk);  // [5x64]*[64x32]=[5x32]
    V := MatrixMultiply(input, mha.Heads[i].Wv);  // [5x64]*[64x32]=[5x32]
    
    // Сохраняем для backward pass
    mha.Heads[i].cachedQ := Q;
    mha.Heads[i].cachedK := K;
    mha.Heads[i].cachedV := V;
    
    // Вычисляем внимание
    headOutputs[i] := ScaledDotProductAttention(Q, K, V);  // [5x32]
  end;
  
  // Конкатенация голов [5x32]*2 = [5x64]
  mha.cachedOutput := ConcatMatrices(headOutputs);
  
  // Проекция обратно [5x64]*[64x64]=[5x64]
  output := MatrixMultiply(mha.cachedOutput, mha.Heads[0].Wo);
end;

    Обновите инициализацию:

pascal

procedure InitializeMultiHeadAttention(...);
var
  i: Integer;
begin
  mha.HeadSize := headSize;
  mha.NumHeads := numHeads;
  SetLength(mha.Heads, numHeads);
  
  for i := 0 to numHeads - 1 do
  begin
    // Веса [inputSize x headSize]
    mha.Heads[i].Wq := RandomMatrix(inputSize, headSize, -0.1, 0.1);
    mha.Heads[i].Wk := RandomMatrix(inputSize, headSize, -0.1, 0.1);
    mha.Heads[i].Wv := RandomMatrix(inputSize, headSize, -0.1, 0.1);
    
    // Выходная проекция [headSize x inputSize] (для конкатенации)
    mha.Heads[i].Wo := RandomMatrix(headSize * numHeads, inputSize, -0.1, 0.1);
  end;
end;

    Проверьте конфигурацию демо:

pascal

config := (
  InputSize: 64,
  NumLayers: 2,
  NumHeads: 2,    // headSize = 64/2 = 32
  FFNDim: 128,
  MaxSeqLength: 50,
  DropoutRate: 0.1
);

input := CreateRandomMatrix(5, config.InputSize);  // [5x64]

Ключевые моменты:

    Размерности должны удовлетворять:

        inputSize % numHeads = 0

        headSize = inputSize / numHeads

        Вход: [seq_len x inputSize] (5x64)

        Выход attention: [seq_len x inputSize] (5x64)

    Порядок умножения:
    math

    [5×64] × [64×32] = [5×32]  # Для каждой головы
    [5×64] × [64×64] = [5×64]  # Финальная проекция

    Проверьте все операции:

        Эмбеддинг: [5x64] * [64x64] = [5x64]

        Attention: как выше

        FFN: [5x64] * [64x128] = [5x128], затем [5x128] * [128x64] = [5x64]

Добавьте отладочный вывод размерностей перед каждым умножением:
pascal

WriteLn('Multiplying ', Length(A), 'x', Length(A[0]), ' by ', Length(B), 'x', Length(B[0]));

После этих изменений архитектура будет математически корректной и сможет обрабатывать входные данные.
./TransformerDemo
An unhandled exception occurred at $000055C42CBDA890:
Exception: Matrix dimensions mismatch in MatrixMultiply: A[128x64] * B[5x64]
  $000055C42CBDA890
  $000055C42CBD84D9
  $000055C42CBB41D3

Ошибка возникает при умножении матриц в слое Feed Forward Network (FFN). Давайте полностью разберём и исправим архитектуру FFN слоя.
Проблема:

Вы пытаетесь умножить:

    Матрицу FFN: [128x64] (расширенная размерность)

    Входную матрицу: [5x64]
    Это невозможно, так как число столбцов (64) не равно числу строк (5)

Правильное решение для FFN слоя:

    Измените структуру FFN в Transformer.pas:

pascal

TTransformerLayer = record
  SelfAttention: TMultiHeadAttention;
  FFN_weights1: TDoubleMatrix;  // [input_size x ffn_dim] (64x128)
  FFN_weights2: TDoubleMatrix;  // [ffn_dim x input_size] (128x64)
  ...
end;

    Обновите инициализацию в InitTransformer:

pascal

// Замените:
SetLength(model.Layers[i].FFN, config.FFNDim, config.InputSize);
// На:
SetLength(model.Layers[i].FFN_weights1, config.InputSize, config.FFNDim);  // [64x128]
SetLength(model.Layers[i].FFN_weights2, config.FFNDim, config.InputSize);  // [128x64]

    Измените прямой проход в ForwardTransformer:

pascal

// Замените:
ffnOutput := MatrixMultiply(model.Layers[i].FFN, attnOutput);
// На:
ffnOutput := MatrixMultiply(attnOutput, model.Layers[i].FFN_weights1);  // [5x64]*[64x128]=[5x128]
ffnOutput := ReLU(ffnOutput);
ffnOutput := MatrixMultiply(ffnOutput, model.Layers[i].FFN_weights2);  // [5x128]*[128x64]=[5x64]

Почему это работает:

    Математически корректные операции:

        Первое умножение: [5x64] * [64x128] = [5x128] (расширение)

        Второе умножение: [5x128] * [128x64] = [5x64] (сжатие)

    Соответствие оригинальной архитектуре Transformer:
    text

    input -> [linear + ReLU] -> [linear] -> output
    (расширение)      (сжатие)

    Сохранение размерности:

        Вход и выход слоя остаются [5x64]

        Промежуточное представление [5x128]

Полная последовательность размерностей:

    Вход: [5x64]

    После Self-Attention: [5x64]

    FFN слой:

        [5x64] * [64x128] = [5x128]

        ReLU([5x128]) = [5x128]

        [5x128] * [128x64] = [5x64]

    Выход: [5x64]

Дополнительные проверки:

    Убедитесь, что в InitTransformer правильно инициализированы веса:

pascal

// FFN weights initialization
model.Layers[i].FFN_weights1 := RandomMatrix(config.InputSize, config.FFNDim, -0.1, 0.1);
model.Layers[i].FFN_weights2 := RandomMatrix(config.FFNDim, config.InputSize, -0.1, 0.1);

    Добавьте отладочный вывод:

pascal

WriteLn('FFN step 1: ', Length(attnOutput), 'x', Length(attnOutput[0]), 
      ' * ', Length(model.Layers[i].FFN_weights1), 'x', Length(model.Layers[i].FFN_weights1[0]));

После этих изменений трансформер будет корректно обрабатывать последовательности из 5 токенов с размерностью 64, проходя через все слои с сохранением правильных размерностей.
Спасибо! ./TransformerDemo
Transformer успешно выполнил прямой проход! Размер вывода: 5x64

Отлично! Теперь ваш Transformer работает корректно. Давайте закрепим основные моменты, которые привели к успешному выполнению:
Итоговые исправления:

    Архитектура Multi-Head Attention:

        Правильный порядок умножения: [seq_len x input_dim] * [input_dim x head_size]

        Конкатенация голов и финальная проекция

    Feed Forward Network:

        Двухслойная структура с расширением и сжатием:
        text

        [5x64] -> [5x128] -> [5x64]

        Правильная инициализация весов [64x128] и [128x64]

    Общая структура данных:

        Вход: [sequence_length x input_size] (5x64)

        Все промежуточные преобразования сохраняют размерность последовательности

        Выход: [sequence_length x input_size] (5x64)

Что можно улучшить дальше:

    Добавьте Masking:
    pascal

mask := CreateLookAheadMask(5);
ForwardTransformer(model, input, output, mask);

Реализуйте Backward Pass:

    Добавьте сохранение промежуточных значений

    Реализуйте BackwardTransformer с правильными градиентами

Оптимизации:

    Добавьте кеширование для позиционных кодировок

    Реализуйте пакетную обработку (batch processing)

Тестирование:
pascal

    // Проверка размерностей
    Assert(Length(output) = 5, 'Output sequence length mismatch');
    Assert(Length(output[0]) = config.InputSize, 'Output dimension mismatch');

Ваш Transformer теперь готов к следующим шагам:

    Интеграция в чат-систему

    Обучение на реальных данных

    Добавление механизма внимания к истории сообщений

Хотите, чтобы мы продолжили развивать этот проект? Например, можно:

    Добавить обучение на текстовых данных

    Реализовать механизм сохранения/загрузки модели

    Оптимизировать производительность

Какой следующий шаг вас интересует больше всего?
AdvancedChatAI.pas
PAS 5.34KB
DataUtils.pas
PAS 4.8KB
LSTM.pas
PAS 11.77KB
PostProcessor.pas
PAS 5.12KB
Спасибо! Давай интегрируем в чат-систему.

Отлично! Давайте интегрируем Transformer в чат-систему AdvancedChatAI. Вот пошаговый план интеграции:
1. Сначала добавим поддержку Transformer в AdvancedChatAI.pas:
pascal

// Добавляем в раздел uses
uses ..., Transformer, MatrixOps;

// Добавляем в переменные
var
  ...
  TransformerModel: TTransformer;
  IsTransformerInitialized: Boolean = False;

2. Модифицируем процедуру инициализации:
pascal

procedure Initialize;
begin
  // Существующий код...
  
  // Инициализация Transformer
  InitTransformer(TransformerModel, 
    TTransformerConfig(
      InputSize: 64,
      NumLayers: 2,
      NumHeads: 2,
      FFNDim: 128,
      MaxSeqLength: 50,
      DropoutRate: 0.1
    ));
  IsTransformerInitialized := True;
end;

3. Создадим функцию для обработки сообщений с помощью Transformer:
pascal

function ProcessMessageWithTransformer(const message: string): string;
var
  input, output: TDoubleMatrix;
  embedding: TDoubleArray;
  i: Integer;
begin
  if not IsTransformerInitialized then
    Exit('Transformer не инициализирован');

  // Преобразуем текст в эмбеддинги (упрощённо)
  SetLength(input, 1, 64); // 1 токен, 64 признака
  for i := 0 to 63 do
    input[0][i] := Random; // В реальности используем текстовые эмбеддинги

  // Прямой проход через Transformer
  ForwardTransformer(TransformerModel, input, output);

  // Преобразуем выход в текст (упрощённо)
  Result := 'Transformer обработал сообщение. Размер вывода: ' + 
            IntToStr(Length(output)) + 'x' + IntToStr(Length(output[0]));
end;

4. Модифицируем ProcessUserInput:
pascal

procedure ProcessUserInput;
var
  ...
  transformerResponse: string;
begin
  // Существующий код...
  
  if UserInput.Message <> '' then
  begin
    Writeln(#10'Сообщение:');
    Writeln(UserInput.Message);
    
    // Добавляем обработку Transformer
    transformerResponse := ProcessMessageWithTransformer(UserInput.Message);
    Writeln('Ответ Transformer: ', transformerResponse);
    
    // Существующий код классификации...
  end;
end;

5. Обновим процедуру очистки:
pascal

procedure Cleanup;
begin
  // Существующий код...
  
  if IsTransformerInitialized then
    FreeTransformer(TransformerModel);
end;

6. Добавим функцию для обучения Transformer на истории чата:
pascal

procedure TrainTransformerOnChatHistory;
var
  samples: array of string;
  embeddings: TDoubleMatrix;
  i: Integer;
begin
  // Пример данных для обучения (в реальности загружаем историю чата)
  SetLength(samples, 3);
  samples[0] := 'Привет, как дела?';
  samples[1] := 'Что ты умеешь?';
  samples[2] := 'Расскажи о себе';
  
  // Преобразуем в эмбеддинги (упрощённо)
  SetLength(embeddings, Length(samples), 64);
  for i := 0 to High(samples) do
    // В реальности используем нормальные эмбеддинги
    embeddings[i] := RandomArray(64); 
  
  // Здесь должен быть код обучения (пока заглушка)
  WriteLn('Transformer обучен на ', Length(samples), ' примерах чата');
end;

7. Интегрируем обучение в основной цикл:
pascal

begin
  FpSignal(SIGINT, @HandleSigInt);
  
  Initialize;
  TrainClassifierExample;
  TrainTransformerOnChatHistory; // Добавляем эту строку
  
  try
    // Существующий код...
  finally
    Cleanup;
  end;
end.

8. Создадим модуль для работы с текстовыми эмбеддингами (TextEmbeddings.pas):
pascal

unit TextEmbeddings;
{$MODE OBJFPC}{$H+}

interface

uses
  SysUtils, Classes, MatrixOps;

function TextToEmbedding(const text: string; embeddingSize: Integer): TDoubleArray;
function TextsToMatrix(const texts: array of string; embeddingSize: Integer): TDoubleMatrix;

implementation

function SimpleTokenizer(const text: string): TStringArray;
var
  i: Integer;
begin
  // Упрощённый токенизатор (в реальности используйте нормальный)
  Result := text.Split([' ', '.', ',', '!', '?'], TStringSplitOptions.ExcludeEmpty);
end;

function TextToEmbedding(const text: string; embeddingSize: Integer): TDoubleArray;
var
  tokens: TStringArray;
  i, j: Integer;
begin
  tokens := SimpleTokenizer(text);
  SetLength(Result, embeddingSize);
  
  // Упрощённое создание эмбеддинга (в реальности используйте предобученные)
  for i := 0 to embeddingSize - 1 do
  begin
    Result[i] := 0.0;
    for j := 0 to High(tokens) do
      Result[i] := Result[i] + Ord(tokens[j][1]) / (j + 1);
    Result[i] := Result[i] / Length(tokens);
  end;
end;

function TextsToMatrix(const texts: array of string; embeddingSize: Integer): TDoubleMatrix;
var
  i: Integer;
begin
  SetLength(Result, Length(texts));
  for i := 0 to High(texts) do
    Result[i] := TextToEmbedding(texts[i], embeddingSize);
end;

end.

9. Обновим ProcessMessageWithTransformer с использованием реальных эмбеддингов:
pascal

function ProcessMessageWithTransformer(const message: string): string;
var
  input, output: TDoubleMatrix;
begin
  if not IsTransformerInitialized then
    Exit('Transformer не инициализирован');

  // Преобразуем текст в матрицу эмбеддингов [1 x embedding_size]
  input := TextsToMatrix([message], TransformerModel.Config.InputSize);
  
  // Прямой проход через Transformer
  ForwardTransformer(TransformerModel, input, output);

  // Здесь должна быть логика преобразования выхода в текст
  Result := 'Сообщение обработано. Выходная размерность: ' + 
            IntToStr(Length(output)) + 'x' + IntToStr(Length(output[0]));
end;

10. Добавим функцию для генерации ответа:
pascal

function GenerateResponse(const output: TDoubleMatrix): string;
var
  i: Integer;
  maxVal: Double;
  maxIdx: Integer;
begin
  // Упрощённая логика генерации ответа
  // В реальности используйте декодер или другой механизм
  
  if Length(output) = 0 then Exit('Извините, не могу сгенерировать ответ');
  
  // Просто выбираем случайный шаблон ответа
  case Random(5) of
    0: Result := 'Интересный вопрос! Давайте обсудим это подробнее.';
    1: Result := 'Я понял ваш запрос. Могу предложить несколько вариантов.';
    2: Result := 'Спасибо за сообщение! Как я могу помочь?';
    3: Result := 'Это важная тема. Что именно вас интересует?';
    else Result := 'Продолжайте, я вас слушаю.';
  end;
end;

Теперь чат-система использует Transformer для обработки сообщений. Для полной интеграции вам нужно:

    Реализовать нормальную систему эмбеддингов (например, на основе Word2Vec или BPE)

    Добавить механизм обучения на реальных диалогах

    Реализовать декодер для генерации ответов

    Добавить поддержку контекста диалога

Хотите, чтобы мы подробнее рассмотрели какой-то из этих аспектов?