Не нужна NVIDIA! Часть 2: 8 нейросетей на FPGA за $50 — и всё упёрлось в память DDR3

Прокомментировать Просмотры: 7

Это я, пока 512 умножителей простаивают в ожидании весов от DDR3.

В первой части я рассказывал, как запустил Qwen на списанной FPGA-плате за $50 с открытым TPU (openTPU). Читатели справедливо отметили, что получился скорее анонс, чем полноценная статья. Где сборка проекта, где разбор кристалла, где замеры, откуда взялись 133 МГц и при чём тут КЕДР?

Сегодня мы вместе соберём прошивку в Vivado, раскрасим кристалл по модулям, распределим 9,7 ватта по блокам и разберём 165 запусков восьми языковых моделей. Главный вывод оказался совсем не тем, которого я ждал: матрица из 512 умножителей выдаёт 121 GOPS из теоретических 136,5, но при генерации текста ей достаётся меньше четверти этой мощности. Причина в том, что память DDR3 выжата на 94 % от своего предела и просто не успевает доставлять веса быстрее.

Коротко

  • Железо: Inspur YPCB-00338, Kintex-7 XC7K480T-2, 2 × 2 ГиБ DDR3-1066, PCIe Gen2 x8.

  • Моя сборка в Vivado: тактовая частота ядра 133,33 МГц, запас по таймингу 19 пикосекунд (я сторонник положительного ШСЛАКА, мало ли кто привык жить с отрицательным). Задействовано 59 % LUT, 63 % BRAM, 37 % DSP и 76 % слайсов.

  • Проверка на плате: 138 аппаратных тестов, три прогона подряд. Все 165 запусков LLM завершились успешно.

  • Скорость: Qwen3.5-2B выдаёт 12,5 ток/с, Qwen3-0.6B — 35, LFM2.5-230M — 95 (веса в 4 битах).

  • Узкое место: при генерации DDR3 загружена на 92–96 %. Поэтому переход с PCIe x8 на x1 почти ничего не меняет (разве что замедляет загрузку модели в память с компьютера), а 4-битные веса вместо 8-битных ускоряют работу в 1,4–1,8 раза.

Спойлер

По итогам опроса больше всего читателей интересовала сборка с нуля (59 %) и устройство TPU (51 %), затем шли подводные камни (37 %), стоимость (35 %) и сравнение моделей (29 %). Всё это здесь есть, причём в порядке от железа к цифрам.

Пссс…⚡ Если вы быстры, как Flash, вот куда прыгать:

  • Хочу собрать проект сам: 2 (hw) и 3 (sw)

  • Просто поглазеть и понять, как это устроено → 4 и 5

  • Цифры → 6

  • Нужен вывод, что тут вообще происходит → 7

  • Помечтать о будущем → 8

Ну а тем, кто устроился надолго и готов погрузиться с головой, — поехали) ↓


1. Железо и сколько стоит эксперимент с КЕДРом

Плата

 Плата с обеих сторон. Фото  https://www.tiferking.cn/index.php/2024/12/19/650
Плата с обеих сторон. Фото https://www.tiferking.cn/index.php/2024/12/19/650

Inspur YPCB-00338 — это списанная серверная карта, которую распродают на нашем любимом AliExpress и на eBay. Для наших задач в ней важны три вещи:

  • Kintex-7 XC7K480T-2 (алмазик): 299 тыс. LUT, 1920 умножителей DSP48E1 и 955 блоков памяти по 36 Кбит (около 34 Мбит на весь кристалл).

  • Два канала DDR3-1066 (теперь и ты алмазик) по 2 ГБ, у каждого 64 бита данных плюс 8 бит ECC. ECC защищает от ошибок, но скорости не добавляет: полезных бит всё равно 64.

  • PCIe x8, по которому плата общается с компьютером.

34 Мбит на кристалле для LLM — капля в море: даже Qwen3-0.6B в 4 битах весит примерно в сто раз больше. Поэтому веса лежат в DDR3, и, забегая вперёд, именно DDR3 задаёт потолок скорости. Вы и сами уже догадываетесь, так что умничать не стану.

Стенды

Плата побывала в нескольких компьютерах, что позволило сравнить разные варианты подключения PCIe.

Стенд

Описание

PCIe

ПК

Windows, Intel Core i9-14900KF, 64GB DDR, 4T SSD, Vivado 2024.2.2

—

A

Ноутбук (Notebook), Ubuntu 24.04, плата снаружи через USB4 с туннелем PCIe (TH3P4G3), 16GB DDR, 2T SSD, i7-1185G7, блок питания (METALFISH Black Diamond 550M), кабель USB4 Type-C (идёт в комплекте с TH3P4G3)

Gen2x1(Gen2x4)

B

MINISFORUM MS-A2, Ubuntu 22.04, плата в слоте, AMD Ryzen 9 9955HX, 64GB DDR, 2T SSD

Gen2x8

Все замеры в статье сделаны на стенде B, где PCIe работает на полной скорости.

Сколько это стоит

$50 из заголовка — цена самой платы (сам чип, как справедливо заметили в комментариях к прошлой статье, стоит гораздо дороже). Указываю цены, по которым купил сам.
На AliExpress лучше искать по XC7K480T и ориентироваться на фотографии и отзывы: вместо платы вполне могут прислать болт, у меня такое однажды случилось :xD.
Полный стенд обойдётся дороже:

Позиция

Цена

Плата YPCB-00338

~$50

Доставка

от 0 до бесконечности (у меня вышло ~$10)

JTAG-программатор:

~$40

USB4 с туннелем PCIe (для конфигурации А)

~$140

Блок питания (для конфигурации А)

~$85

ПК

уже был

В сумме выходит около $100, если плата стоит в слоте (плата 50 + доставка 10 + JTAG 40). Для стенда A с USB4 понадобится ещё примерно $225 на адаптер и блок питания.

Отдельная засада — TH3P4G3: во время прошивки платы устройство PCIe пропадает, и адаптер отключает ей питание, из-за чего прошить плату не получается. Для этого проекта его не рекомендую (возможно, это поведение можно отключить, но я не копал).


2. Собираем прошивку для кристалла с нуля

Что внутри прошивки

В Vivado собирается не один только TPU. Прошивка состоит из четырёх подсистем на одном кристалле:

  • Ядро openTPU на Verilog: матрица, векторный блок, рабочая память, DMA, квантизатор и секвенсор.

  • Мост PCIe — IP-блок Xilinx XDMA.

  • Два контроллера DDR3 из проекта LiteDRAM вместо привычного Xilinx MIG. Внутри работает маленький процессор RISC-V (VexRiscv), который при старте сам подбирает задержки для DDR3. Получается, что рядом с TPU крутится ещё один процессор, единственная задача которого — договориться с памятью.

  • Обвязка: мост от ядра к двум каналам памяти, регистры управления и аппаратный журнал событий.

Сборка

git clone https://github.com/FeSens/openTPU.git
cd openTPU
git checkout 8eaa480

Ревизию я зафиксировал намеренно: репозиторий активно развивается, и на свежем main цифры могут отличаться.

Перед стартом нужно установить Vivado 2024.2 или новее (новее я не проверял, возможны нюансы).

На Windows всё делает один скрипт PowerShell: он создаёт проект, генерирует IP, выполняет синтез и разводку и записывает bitstream:

run_vivado.ps1
param(
    [int]$DDR = 1066,
    [int]$JOBS = 8,  # как нагрузить процессор 
    [int]$MCOLS = 4,
    [int]$VPU_CL = 2,
    [int]$LANES = 8,
    [int]$ACT_ROWS = 0,
    [int]$DSTEP = 1,
    [double]$CORE_MHZ = 0,
    [int]$PCIE_GEN = 2,
    [switch]$FAST,
    [switch]$ImplOnly,
    [string]$IMPL_STRATEGY = "",
    [string]$BUILD_ID = "00000000",

    # Измени этот путь под свою установку
    [string]$Vivado = "C:\Xilinx\Vivado\2024.2\bin\vivado.bat"
)

$ErrorActionPreference = "Stop"

$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$root = (Resolve-Path (Join-Path $here "..\..")).Path
$out  = Join-Path $root "build\vivado"

if ($DDR -ne 1066) {
    throw "DDR3-$DDR is unsupported. LiteDRAM is generated for DDR3-1066."
}

if ($ACT_ROWS -eq 0) {
    $ACT_ROWS = $MCOLS
}

if ($CORE_MHZ -eq 0) {
    if ($FAST) {
        $CORE_MHZ = 100
    } else {
        $CORE_MHZ = 133.33
    }
}

if (!(Test-Path $Vivado)) {
    throw "Vivado not found: $Vivado"
}

New-Item -ItemType Directory -Force -Path $out | Out-Null

# Environment read by create_project.tcl / bd_native.tcl / build.tcl
$env:PCIE_GEN = "$PCIE_GEN"

if ($FAST) {
    $env:OTPU_FAST = "1"
} else {
    $env:OTPU_FAST = "0"
}

# Optional IP cache
$cache = Join-Path $env:LOCALAPPDATA "openTPU\vivado-ip-cache"
New-Item -ItemType Directory -Force -Path $cache | Out-Null
$env:OTPU_IP_CACHE = $cache

$createTcl = Join-Path $here "vivado\create_project.tcl"
$buildTcl  = Join-Path $here "vivado\build.tcl"

Write-Host ""
Write-Host "openTPU Vivado build"
Write-Host "-------------------"
Write-Host "Root       : $root"
Write-Host "Output     : $out"
Write-Host "DDR        : $DDR"
Write-Host "CORE_MHZ   : $CORE_MHZ"
Write-Host "MCOLS      : $MCOLS"
Write-Host "VPU_CL     : $VPU_CL"
Write-Host "LANES      : $LANES"
Write-Host "ACT_ROWS   : $ACT_ROWS"
Write-Host "DSTEP      : $DSTEP"
Write-Host "PCIe Gen   : $PCIE_GEN"
Write-Host "FAST       : $FAST"
Write-Host ""

if (!$ImplOnly) {

    Write-Host "=== CREATE PROJECT ==="

    & $Vivado `
        -mode batch `
        -nojournal `
        -log (Join-Path $out "create_project.log") `
        -source $createTcl `
        -tclargs `
            $DDR `
            $out `
            $MCOLS `
            $CORE_MHZ `
            $BUILD_ID `
            $VPU_CL `
            $LANES `
            $ACT_ROWS `
            $DSTEP

    if ($LASTEXITCODE -ne 0) {
        throw "Vivado create_project failed: $LASTEXITCODE"
    }

    Write-Host ""
    Write-Host "=== FULL BUILD ==="

    & $Vivado `
        -mode batch `
        -nojournal `
        -log (Join-Path $out "build.log") `
        -source $buildTcl `
        -tclargs `
            $out `
            $JOBS `
            "full" `
            $IMPL_STRATEGY

} else {

    Write-Host "=== IMPLEMENTATION ONLY ==="

    & $Vivado `
        -mode batch `
        -nojournal `
        -log (Join-Path $out "build_impl.log") `
        -source $buildTcl `
        -tclargs `
            $out `
            $JOBS `
            "impl" `
            $IMPL_STRATEGY
}

if ($LASTEXITCODE -ne 0) {
    throw "Vivado build failed: $LASTEXITCODE"
}

Write-Host ""
Write-Host "================================"
Write-Host "DONE"
Write-Host "Bitstream: $out\otpu.bit"
Write-Host "MCS      : $out\otpu.mcs"
Write-Host "Reports  : $out\reports"
Write-Host "================================"

Положите run_vivado.ps1 (его текст приведён под спойлером выше) в папку boards\ypcb-00338.
В 17-й строке нужно заменить путь на тот, где у вас установлен Vivado ( [string]$Vivado = “C:\Xilinx\Vivado\2024.2\bin\vivado.bat”).

Запускаем всё сразу (создание проекта, синтез, имплементация) и терпеливо ждём; время зависит от мощности вашей машины.

cd boards\ypcb-00338
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
Unblock-File .\run_vivado.ps1
.\run_vivado.ps1

Параметр Bypass разрешает запуск скачанного скрипта без подписи, причём только в текущем окне.

Параметры конфигурации: DDR = 1066, MCOLS = 4 (четыре колонки матрицы по 128 ячеек), LANES = 8 (дорожки векторного блока), CORE_MHZ = 133.333, PCIE_GEN = 2.

Этап

Что происходит

Создание проекта Vivado и IP

блок-дизайн, XDMA, генерация IP

Синтез

RTL → нетлист

Размещение и оптимизация

place, phys_opt

Трассировка

route

Проект появится в папке build\vivado.
Когда скрипт закончит работу, откройте в этой папке файл проекта с расширением *.xpr.
У меня это выглядит так:

После открытия переходим в Project Summary:

 Открываем Project Summary: Window → Project Summary
Открываем Project Summary: Window → Project Summary

Вот так выглядит окно с победой: разводка завершена, тайминг сошёлся.

Победа
Победа
Победа крупным планом
Победа крупным планом

Запас составляет 19 пикосекунд (0.019ns), то есть 0,25 % такта. Почему получилось так впритык, разберу в разделе про кристалл.

Теперь нажимаем Gener

 

Источник

Похожие статьи

Поиск по играм, новостям и статьям…

Введите не менее двух символов

Введите не менее двух символов