Документация

Быстрые бэкенды

Переносимость TileLang

Все пять ядер в laya/tl_kernels.py могут выполняться на CPU-цели (c) TileLang через compile_cpu. Это явная скалярная специализация fp32, а не CPU-бэкенд ускорения для Agent.accelerate. Она требует TileLang и локальный компилятор C++. Она не вводит зависимости от службы времени выполнения.

import torch
from laya import tl_kernels as K

A = torch.randn(17, 67)
W = torch.randn(70, 67)
b = torch.randn(70)
C = torch.empty(17, 70)
kernel = K.compile_cpu(K.gemm_kernel, 70, 67, bias=True, act="gelu")
kernel(A, W, b, C)

compile_cpu(factory, *args, **kwargs) принимает те же опции формы и операции, что и пять GPU-фабрик. Он выбирает cpu=True, dtype="float32", цель c и отключает векторизацию. Все вещественные входы и выходы должны быть непрерывными CPU-тензорами float32; длины внимания остаются int32. Преобразуйте 16-битные активации с помощью .cpu().float().contiguous() перед вызовом. LayerNorm по-прежнему обновляет свой остаточный тензор на месте, а RoPE по-прежнему обновляет столбцы Q/K на месте. Сохраняйте скомпилированное ядро, чтобы переиспользовать его динамические размеры.

CPU-специализация заменяет выделения fragment и shared локальными буферами, использует последовательные циклы T.grid и опускает GPU-аннотацию swizzle для внимания. GEMM используют скалярную CPU-реализацию TileLang, а редукции используют локальные буферы. Исходные блочные алгоритмы, предикаты padding, маски внимания и online softmax остаются общими с GPU-реализацией. Промежуточные результаты CPU остаются fp32, включая вероятности внимания; промежуточные результаты GPU сохраняют исходный dtype. Никакого ускорения на CPU или CPU-бэкенда для всей модели здесь не заявляется.

Повторная проверка на TileLang 0.1.14

Наблюдалось на Linux, Python 3.12.13, torch 2.11.0+cu130, TileLang 0.1.14 и RTX 4070 Ti SUPER. Прежнее опасение о переносимости относится к компиляции неизменённой GPU-специализации, а не к возможности понижения для CPU. На базовом коммите fa9a2a7 этой страницы документации не было в checkout.

Тест компилирует factory.get_tir(...) с target="c" и конфигурацией прохода FAST для GPU. Это точные тексты диагностики (расположения в исходниках и трассировки стека опущены). Для каждого ядра проверялись и bf16, и fp16.

Ядро и размеры проверки Первый сбой bf16 Первый сбой fp16
gemm_kernel(128, 64) Check failed: layout_map.count(buffer) != 0 (0 vs. 0) : The layout for fragment C_l can not be inferred correctly. Тот же
gemm_geglu_kernel(64, 64) CPU fill only supports local and global buffers, but got dst scope `local.fragment`. Тот же
add_ln_kernel(128) CPU reduce only supports local src and local/local.var dst buffers, got src scope `local.fragment` and dst scope `local.fragment`. Тот же
rope_kernel(2, 64) Cannot convert type bfloat16 to C type Компилятор C++: error: no matching function for call to ‘vec_type<float, 4>::vec_type(half4&)’
attn_kernel(1, 64, 2, 64) Check failed: layout_map.count(buffer) != 0 (0 vs. 0) : The layout for fragment s_c can not be inferred correctly. Тот же

Первые четыре сбоя fragment/редукции — это tvm.error.InternalError. Сбой генерации кода bf16 тоже InternalError. RoPE во FP16 возбуждает RuntimeError: Compilation Failed!, за которым следуют вызов компилятора и исходный код; приведённая выше диагностика выводится в stderr. Его сгенерированные векторные приведения также не могут преобразовать векторы float обратно в векторы half.

Чтобы отделить поддержку dtype от поддержки fragment, тесты компилируют каждое ядро снова с cpu=True (локальные буферы и последовательные циклы), сохраняя bf16 и отключая векторизацию. Тогда все пять падают в точности с:

Cannot convert type bfloat16 to C type

Таким образом, фрагменты — первый блокиратор для GEMM, GEGLU и внимания, редукции фрагментов — для LayerNorm, а bf16 независимо блокирует все пять. У RoPE нет ни выделения фрагмента, ни редукции; у GEMM и GEGLU нет явной операции T.reduce_*. Редукции внимания поначалу замаскированы его сбоем раскладки. Отдельные проверки только fp32-фрагментов воспроизводят ошибку заполнения и ошибку редукции как для reduce_sum, так и для reduce_max, без всякого GEMM или bf16. Замена только scope также дала эту семантическую ошибку для буфера GEMM (другие затронутые буферы — Ci, x и s):

[Tilelang Semantic Check] Local buffer `C_l` is indexed by T.Parallel loop variable `i`. Local buffers are thread-private and do not participate in parallel layout inference. Use T.serial/T.vectorized/T.unroll for per-thread local indexing, or T.alloc_fragment when the indexed dimension should be distributed across threads.

Последовательная специализация fp32 устраняет эти блокираторы. Диагностические утверждения привязаны к версии 0.1.14 и пропускаются на другой версии, где ошибки следует проверить заново. Числовые CPU-тесты продолжают выполняться на других версиях.

Числовые проверки

Запустите python -m pytest tests/test_fast_cpu.py -q -s. В указанном выше окружении: 58 прошли. Проверки только на CPU не требуют CUDA; без неё пропускаются лишь сравнения с GPU. Покрытие включает неравномерные тайлы GEMM M/N/K, все эпилоги GEMM, GEGLU, все комбинации residual/bias, большие значения residual, зацикливание позиции RoPE и нетронутые столбцы V, статические/динамические формы внимания, скользящие окна, частичные тайлы, неравные длины, пустые последовательности и конечные выходы padding.

Зерно — 1234. Сравнения с GPU используют идентичные входные значения, округлённые до bf16 или fp16, а затем повышенные до fp32 для выполнения на CPU. Это абсолютные допуски для ограниченных фикстур, а не гарантия для произвольных величин или глубины модели. Сравнения внимания используют допустимые строки query, как в tests/test_fast.py.

Ядро Макс. CPU против эталона fp32 Макс. CPU против GPU (оба dtype) Допуск CPU/GPU
GEMM 2.38419e-7 0.00770831 0.05
GEGLU 2.98023e-8 0.000208303 0.05
LayerNorm 1.07288e-6 0.0156183 0.05
RoPE 0 0.0130053 0.05
Attention 5.96046e-7 0.00377572 0.02

Допуск CPU/эталон — 2e-5 (2e-6 для RoPE). Обновления остаточного потока в точности равны, включая случай без residual.

Свидетельства сохранения GPU

Ключевое слово cpu по умолчанию равно false. Существующие значения по умолчанию bf16/fp16, scope выделения GPU, параллельные циклы, swizzle и опции FAST не изменены. Вызовы GPU в fp32 по умолчанию по-прежнему возбуждают ValueError.

Прогоны «до/после» использовали соответственно исходный модуль, извлечённый через git show fa9a2a7:laya/tl_kernels.py, и изменённый модуль. Исходный загружался как laya.tl_kernels через importlib для базовых прогонов; весь остальной код Laya и окружение Python остались прежними.

  • python -m pytest tests/test_fast.py -q, при этом full-forward тесты настроены загружать указанный ниже кэшированный английский чекпойнт: 13 прошли до; 13 прошли после. Изначально без чекпойнта было 11 прошли / 2 пропущены. Оба полных прогона выдали существующее предупреждение чекпойнта об ограничении температуры (choice:11+=0.10058280825614929 -> 0.5).
  • Захват с зерном существующих тестов ядер: 24 выходных тензора бит-в-бит идентичны, включая обновления residual; максимальная разница до/после 0.
  • Сгенерированный исходный код CUDA для всех пяти проверочных форм выше, в обоих dtype: 10/10 побайтово идентичны. Это также покрывает RoPE, отсутствующий в исходном fast-наборе.
  • python benchmarks/parity_fast.py --model "$MODEL" --dtype bf16 --json ... и эквивалентная команда для fp16: 288 вопросов по 60 состояниям на каждый dtype. Все записи JSON до/после (вероятности fp32, stock и fast) сравниваются в точности равными; максимальная разница вероятностей до/после 0.

MODEL — это кэшированный английский снимок convaiinnovations/laya 55cf4c4ebb4ebe31b2550e8bdf3bd21b99753851; прогоны использовали HF_HUB_OFFLINE=1. Сравнение с многоязычным чекпойнтом или чекпойнтом типизированных решений здесь не заявляется.

dtype type n Макс. fast-stock, до = после Согласие argmax fast/stock, до = после
bf16 choice 48 0.0310 47/48
bf16 noul 180 0.0756 180/180
bf16 score 60 0.0152 60/60
fp16 choice 48 0.0069 48/48
fp16 noul 180 0.0092 180/180
fp16 score 60 0.0040 60/60

Проверки репозитория

Все команды использовали /home/ckl/projects/S/laya/.venv/bin/python; ruff и zensical брались из каталога bin этого virtualenv.

Команда Результат
ruff check laya/ --select=E9,F63,F7,F82,F401,F811 --line-length=120 All checks passed!
python -m compileall -q laya/ tests/ Код выхода 0, без вывода
python tests/test_router.py 703 прошли, 0 провалено
python tests/test_criteria.py 198 прошли, 0 провалено
python tests/test_hooks.py 240 прошли, 0 провалено
python tests/test_hooks_api.py 415 прошли, 0 провалено
python tests/test_packaging.py 131 прошли, 0 провалено
uv pip install --python /home/ckl/projects/S/laya/.venv/bin/python -r requirements-docs.txt Проверено 3 пакета (уже установлены); в virtualenv нет pip
zensical build --strict --clean No issues found; строк griffe: нет

Новый набор тестов зарегистрирован как требующий необязательного extra TileLang и компилятора C++ в существующих исключениях теста упаковки. Тест контракта API фиксирует аддитивный CPU-селектор только по ключевому слову, неизменный dtype GPU по умолчанию и сигнатуру compile_cpu, не импортируя TileLang в базовое окружение CI.

AOTInductor

DecisionModel можно экспортировать, скомпилировать в пакет .pt2 и загрузить с помощью torch._inductor.aoti_load_package. Пакет возвращает как логиты решения, так и логиты действия. Токенизация, padding, температурная калибровка и форматирование ответов остаются задачей вызывающего; это не добавляет бэкенд Agent и не меняет его путь выполнения по умолчанию.

Закрытый PR #472 задокументировал прежний блокиратор dtype. Объединённое состояние и признаки уверенности головы действий вычисляются в fp32, даже когда веса модели явно bf16. Без autocast первый линейный слой действия поэтому получал вход fp32 и веса bf16. Теперь прямой проход приводит конкатенированный вход к dtype весов головы только вне autocast. Softmax, энтропия и возвращаемые логиты решения сохраняют вычисления в fp32. Существующий eager-инференс с параметрами fp32, включая fp16/bf16 AMP, сохраняет свою численную схему; смешанные dtype весов/autocast тоже сохраняют исходное преобразование autocast.

Экспортируйте отдельную оценочную копию, преобразованную в bf16, вне autocast. Объявите ось token как 16 * Dim("tokens16", ...), чтобы удовлетворить защиты выравнивания внимания. Строки и количества маркеров могут быть динамическими независимо. Не предполагайте, что экспорт, снятый под autocast, можно упаковать вне этого контекста: для этого рецепта используйте явные dtype весов.

Воспроизведение офлайн

Проверка на основе assert по умолчанию использует крошечный случайно инициализированный ModernBERT, без доступа к сети. Передайте локальный каталог чекпойнта для измерения с реальной моделью. Отсутствие CUDA, API AOTInductor или компилятора C++ даёт явный SKIP; сбои компиляции и паритета на поддерживаемой установке приводят к сбою проверки.

python scripts/check_aoti.py --output-dir /tmp/laya-aoti-smoke
HF_HUB_OFFLINE=1 TORCHINDUCTOR_CACHE_DIR=/tmp/laya-aoti/cache \
  python scripts/check_aoti.py --model /path/to/local/multilingual \
  --output-dir /tmp/laya-aoti

Выходной каталог содержит decision.pt2, results.json, inputs.pt и eager.pt. JSON включает полные снимки nvidia-smi, время экспорта/упаковки/загрузки, байты пакета, задержку и максимальные абсолютные дельты логитов/вероятностей для обеих голов. Бинарные артефакты и кэши компилятора намеренно не хранятся в репозитории.

Измерено на RTX 4070 Ti SUPER

2026-10-03, Linux x86-64, Python 3.12.13, torch 2.11.0+cu130, transformers 5.17.0, драйвер NVIDIA 615.71.09, 16 376 MiB VRAM. Чекпойнт: convaiinnovations/laya, подкаталог multilingual на ревизии 1c5edc17a7acd8701df6fc341c0d179f1c62c982. Базовая линия — main fa9a2a7. Полные снимки машины и неокруглённые измерения — в aoti_multilingual_rtx4070.json.

Измерение До После
Экспорт 5.00 s 3.89 s
Упаковка сбой после 16.59 s 60.02 s
Размер пакета артефакта нет 645 729 621 байт (615.82 MiB)
Загрузка в уже инициализированном процессе недоступно 0.447 s
Загрузка в свежем процессе с пустым кэшем недоступно 4.867 s

Воспроизведённый сбой происходит на упаковке, после успешного экспорта: mat1 and mat2 must have the same dtype, but got Float and BFloat16. Каждый прогон использовал отдельный изначально пустой кэш Inductor; компиляция AMP выполнялась до упаковки, поэтому время упаковки — не измерение холодного старта свежего интерпретатора. Проверка в свежем процессе загружала только пакет и сохранённые входы (без чекпойнта), при этом точки входа torch.compile и упаковки были заблокированы. Она воспроизвела ответы обеих голов. PyTorch всё же скомпилировал небольшую проверку возможностей CPU AVX в изначально пустой кэш; ни граф модели, ни ядро CUDA не компилировались. Поэтому обобщённое утверждение «при загрузке нет активности компилятора» было бы на этой среде выполнения неточным.

Те же сохранённые входы содержат семь строк решений в трёх батчах, с реальным токенизированным текстом о выставлении счетов/возвратах, вопросами choice/score/noul, padding и неравными количествами допустимых маркеров. Каждая задержка — медиана пяти групп по 30 прямых проходов после десяти прогревов, с синхронизированным измерением по настенным часам. torch.compile(dynamic=True) использует режим по умолчанию. В эти числа задержки не входят явные графы CUDA, токенизация, экспорт или компиляция.

Строки × токены × слоты маркеров AMP eager до → после (ms) AMP compiled до → после (ms) Явный bf16 eager (ms) Явный bf16 compiled (ms) AOTI bf16 (ms)
2 × 128 × 3 15.1173 → 14.1200 6.7117 → 6.9076 13.9635 4.8835 2.2674
1 × 256 × 3 15.4687 → 14.4156 6.7274 → 5.4772 14.7994 4.7250 2.4154
4 × 160 × 5 14.8452 → 14.5384 7.3872 → 5.5934 14.7115 5.1393 3.6018

Здесь AMP означает параметры fp32 с bf16 autocast. Явный bf16 означает, что веса и остаточный поток — bf16, без autocast; используйте эти столбцы для наиболее близкого к пакету сравнения выполнения. Явный bf16 eager/compiled и AOTI были недоступны до этого исправления.

Сравнение, максимум по всем семи строкам Логиты решения Вероятности решения Логиты действия Вероятности действия
Существующий eager до против после, fp32 и fp16/bf16 AMP 0 0 0 0
AOTI против явного bf16 eager 0.5625 0.00659859 0 0
AOTI против существующего bf16 AMP eager 0.4375 0.00769910 8.0 0

И argmax решения, и argmax действия совпадают на 7/7 строках для обоих сравнений AOTI. Вероятности — это сырые выходы softmax, без калибровки. Распределение действия насыщено на этих входах, поэтому его нулевая дельта вероятности не означает идентичных лежащих в основе логитов против AMP. Явный bf16 compiled прямой проход тоже отличается от явного bf16 eager (максимальная дельта логита решения 0.5, дельта вероятности 0.00659859). Компиляция с пониженной точностью не бит-в-бит.

GPU использовался совместно с настольными приложениями и другими задачами Python; компиляция на CPU тоже была совместной, а частоты не фиксировались. Это наблюдаемые времена, а не заявление об изолированном ускорении. Снимки nvidia-smi, обрамляющие прогоны:

Прогон / снимок Загрузка GPU Использовано VRAM Мощность Температура / состояние
До / начало 28% 2,817 MiB 12 W 33°C / P8
До / конец 10% 8,560 MiB 23 W 36°C / P3
После / начало 0% 2,634 MiB 12 W 33°C / P8
После / конец 73% 6,476 MiB 160 W 42°C / P2

Оставшиеся ограничения

  • Артефакт специфичен для этого чекпойнта, точности, стека PyTorch/среды выполнения и цели GPU; переносимость на другое оборудование или версии PyTorch не проверялась.
  • Эта проверка экспортирует строки 1–8, токены 32–512 с шагом 16 и слоты маркеров 2–8. Она задействует три формы, включая формы, отличные от примера экспорта, а не каждую точку в этих диапазонах. Один допустимый вариант может использовать дополненные слоты маркеров; настоящий однослотовый тензор идёт по отдельной ветви одного варианта и требует отдельного экспорта. Произвольные длины токенов и производственный слой бакетирования/диспетчеризации — вне этого изменения.
  • Семь строк проверяют регрессию упаковки, а не широкую точность чекпойнта или паритет калиброванной уверенности. Приведение экспортной копии к bf16 отличается от сохранения весов fp32 под autocast; сам существующий eager-путь остаётся неизменным.
  • Скрипт требует сборки PyTorch с поддержкой CUDA и локального набора инструментов компилятора, чтобы создать артефакт. .pt2 встраивает модель и ядра CUDA; никакой размещённый сервис не задействован.