Перейти к основному содержимому

Поиск оптимизации ядер

После алгебраического упрощения каждому ядру нужны решения по планированию: как тайлить циклы, где параллелизовать, использовать ли tensor cores. Morok предлагает две стратегии: быстрые эвристики и тщательный beam search.

Выполняется на Стадии 7 пайплайна кодогенерации.

Исходники Tinygrad: tinygrad/codegen/opt/. Исходники Morok: schedule/src/optimizer/.


Пространство действий

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

ДействиеЭффектЦелевое оборудование
UPCAST(axis, amount)Векторизация размерности (SIMD)Все
UNROLL(axis, amount)Развёртка размерности циклаВсе
LOCAL(axis, amount)Использование GPU shared memoryGPU (LDS) / CPU (L1)
GROUP(axis, amount)Двухстадийная редукцияВсе
GROUPTOP(axis, amount)Grouped reduction для tensor coresGPU
THREAD(axis, amount)Параллелизация на основе потоков CPUCPU
SWAP(axis1, axis2)Перестановка глобальных размерностейВсе
PADTO(axis, amount)Паддинг для выравниванияВсе
NOLOCALSОтключение локальной памятиВсе (ограничение)
TCВключение использования tensor coresGPU NVIDIA

Суммарное пространство действий — ~162 базовых действия (зависит от структуры ядра и доступного параллелизма).


Эвристики (по умолчанию)

Эвристический оптимизатор применяет оптимизации в фиксированном порядке (упрощённый псевдокод):

// Pseudocode — simplified from optimizer/heuristics.rs
fn hand_coded_optimizations(scheduler: &mut Scheduler) {
// 1. Tensor cores (if matmul pattern detected)
if let Some(tc) = detect_tensor_core_pattern(scheduler) {
apply_tensor_core(scheduler, tc);
return; // TC handles everything
}

// 2. Grouped reductions (two-stage for large reductions)
apply_grouped_reduction_if_needed(scheduler);

// 3. Vectorization (UPCAST output dimensions)
apply_upcast(scheduler, 4);

// 4. GPU local memory (workgroup dimensions)
apply_local_dims(scheduler);

// 5. CPU threading
apply_threading(scheduler);
}

Плюсы: Быстро (~50ms на ядро), предсказуемо, не требует аппаратных замеров.

Минусы: Может упустить возможности оптимизации, фиксированные эвристики не адаптируются к нагрузке.


Beam search (опционально)

Для продакшн-нагрузок beam search находит лучшие расписания, компилируя и замеряя кандидатов (упрощённый псевдокод):

// Pseudocode — simplified from optimizer/beam.rs
// Actual API: beam_search_cached(scheduler, config, compile_and_time) -> Result<BeamResult>
fn beam_search(scheduler: Scheduler, config: BeamConfig) -> Scheduler {
let mut beam = vec![scheduler];
let deadline = Instant::now() + config.time_limit;

while Instant::now() < deadline {
let mut candidates = vec![];

for state in &beam {
for action in generate_actions(state) {
if let Ok(next) = state.apply(action) {
candidates.push(next);
}
}
}

// Compile and time each candidate
let timed: Vec<_> = candidates.par_iter()
.map(|c| (c, measure_kernel_time(c)))
.collect();

// Keep top K by execution time
beam = timed.into_iter()
.sorted_by_key(|(_, time)| *time)
.take(config.beam_width)
.map(|(c, _)| c)
.collect();
}

beam.into_iter().next().unwrap()
}

Плюсы: Находит близкие к оптимальным расписания, адаптируется к железу.

Минусы: Минуты на ядро (но результаты кэшируются по хэшу AST).


Конфигурация

# Disable optimization (debugging)
MOROK_NOOPT=1 cargo run

# Enable beam search with width 8
BEAM=8 cargo run

Или программно:

let config = PrepareConfig::builder()
.strategy(OptStrategy::Beam { width: 8 })
.build();

tensor.realize_with(config)?;

Сравнение: как оптимизируют другие компиляторы

АспектXLATVM/AnsorTritonMorok
ФилософияФиксированные эвристикиПоискУправление программистомНа основе паттернов
ФьюзингКонсервативные правилаTile-and-fuseBlock-levelПерезапись графа
АвтотюнингНетЭволюционный + cost modelGrid searchBeam search
Стоимость тюнинга0ЧасыМинутыМинуты (кэшируется)
ГибкостьНизкаяВысокаяСредняяВысокая
ПрозрачностьНизкая (C++-проходы)Средняя (Python)Средняя (DSL)Высокая (декларативные паттерны)

XLA использует фиксированные эвристики для решений по фьюзингу. Безопасно и предсказуемо, но оставляет производительность на столе. Правила фьюзинга захардкожены в C++.

TVM/Ansor разделяет что вычислять и как вычислять. Ansor использует эволюционный поиск с обучаемой cost model. Лучшая в классе производительность, но тюнинг занимает часы на модель.

Triton предоставляет Python-подобный DSL для блочных алгоритмов. Хороший баланс контроля и автоматизации, но требует экспертизы в GPU-программировании.

Morok выражает оптимизации как компонуемые паттерны. Beam search добавляет автотюнинг при необходимости, с кэшированием результатов по хэшу AST для повторного использования.