Skip to content

bf16-native DSL → StableHLO export + pluggable per-target optimization#791

Merged
michalharakal merged 4 commits into
developfrom
feature/bf16-dsl-export
Jul 5, 2026
Merged

bf16-native DSL → StableHLO export + pluggable per-target optimization#791
michalharakal merged 4 commits into
developfrom
feature/bf16-dsl-export

Conversation

@michalharakal

@michalharakal michalharakal commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a bf16-native path from the SKaiNET DSL to StableHLO, the plumbing for per-target
optimization/emission, and two numerical/emission correctness fixes. Together this is the
export path used to compile DSL models (e.g. Moonshine) for accelerator backends while
keeping the core hardware-agnostic.

What's in this branch (bottom → top)

  1. bf16-native DSL → StableHLO export path (1a1cedbc) — trace and emit bf16 models to
    StableHLO directly.
  2. Pluggable per-phase, per-target optimization mechanism (199a9c9f) — TargetOptimizer
    / TargetOptimizers registry so a backend registers its own DAG passes from outside
    core; the core queries by target string and stays agnostic.
  3. rank-0 tensor type fix (6d036a88) — emit tensor<elem> (not tensor<xelem>) for
    scalars.
  4. LayerNorm in f32 (006c5ab4) — compute mean/variance/std/divide in f32 for non-f32
    models, cast back before the affine (LayerNormalization.forward + convertLayerNorm).
    Matches PyTorch/JAX; a bf16 variance can overflow sqrt(var+eps) to NaN.
  5. Target + op-granularity emission seam (b2a1e167) — thread an optional target and
    OpGranularityPolicy through toStableHlo → StableHloConverter → ConversionContext, and
    extend TargetOptimizer with granularity(). Defaults to null (decompose all) →
    emission byte-identical today. This is the seam for "target legalization" (choosing
    fused vs decomposed emission per target) without leaking hardware knowledge into core.
    Design writeup under docs/skainet-target-legalization/.

Design notes for review

  • OpGranularityPolicy lives in skainet-compile-dag (shared by the emitter and the
    optimizer registry) → no new dependency edges, no cycle; the emitter never depends on the
    optimizer registry. The caller resolves the policy (TargetOptimizers.granularityFor) and
    passes it into toStableHlo, so the target string / vendor specifics stay out of core.
  • Public API additions are backward-compatible (new params default to null/empty).

Testing

  • skainet-compile-opt and skainet-compile-hlo compile; LayerNormConverterTest,
    ReductionOperationsConverterTest pass.
  • LayerNorm f32 verified against an f32 reference (cosine 1.0) where the bf16 form NaN'd.
  • Exercised end-to-end by the Moonshine DSL encoder export.

Review notes / risk

  • LayerNorm f32 is always-on for non-f32 models — changes their emitted StableHLO (extra
    converts) and adds a small compute cost. Deliberate correctness choice; if the team prefers
    it gated, the op-granularity seam in commit 5 is the mechanism to make it per-target. Happy
    to split 006c5ab4 into its own PR if you'd rather land it independently.
  • Emission changes → regenerate any golden-MLIR snapshots.
  • Commit 5 consumes nothing yet (Phase 2+ in PLAN.md: fused/composite emission, C-emitter
    realization) — seam only.

michalharakal and others added 4 commits July 3, 2026 09:00
Add the seam that keeps hardware knowledge OUT of the agnostic compiler core:
backends register their own passes from outside core and the pipeline asks the
registry which passes to run for the selected target at each phase.

- CompilePhase { TAPE, DAG, STABLE_HLO }
- TargetOptimizer: per-phase pass provider (dagPasses() today; tape / StableHLO
  hooks follow the same shape)
- TargetOptimizers: pluggable registry — register(...) / registerDagPasses(target){}
  (callback) / forTarget(target)
- dagPipelineFor(target, corePasses): builds a GraphOptimizationPipeline of the
  agnostic core passes plus whatever is plugged in for the target

The transforms a target plugs in still emit standard, portable StableHLO — the
shared IR emitter and model definitions carry no target-specific code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mapTensorType/createTensorType unconditionally inserted the `x` shape separator,
so an empty (rank-0 scalar) shape produced the malformed `tensor<xbf16>` that
iree-compile rejects. Emit `tensor<elem>` when the shape is empty.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A bf16 variance (a sum of many bf16 squares) loses enough precision that
sqrt(var + eps) can overflow to NaN, and some accelerator backends miscompile
the decomposed low-precision reduce/normalize outright. Upcast the mean /
variance / std / divide to f32 (standard PyTorch/JAX LayerNorm practice); the
affine (gamma/beta) stays in the model dtype. No-op for f32 models.
Thread an optional target id and OpGranularityPolicy through
toStableHlo -> StableHloConverterFactory -> StableHloConverter ->
ConversionContext, so per-target emission decisions (fused vs decomposed
ops = target legalization) are possible without any hardware knowledge in
the agnostic core.

OpGranularityPolicy (+ FusedOpAllowList) lives in skainet-compile-dag, the
module both the emitter (skainet-compile-hlo) and the TargetOptimizer
registry (skainet-compile-opt) already depend on, so no new dependency
edges and no cycle. TargetOptimizer gains granularity(); TargetOptimizers
gains granularityFor(target). The caller resolves the policy and passes it
in, keeping the emitter decoupled from the registry. Everything defaults to
null (decompose all), so emission is byte-identical today.
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

📖 Documentation Preview

The documentation has been built successfully for this PR.

Generated Files:

  • Operator documentation: docs/modules/operators/_generated_/
  • JSON schema output: operators.json

Artifacts:

  • Download the documentation-preview-791 artifact to view the complete documentation locally.

This comment will be updated automatically when the PR is updated.

@michalharakal michalharakal changed the title RoPE: compute in f32 and use the full-head interleaved rotation bf16-native DSL → StableHLO export + pluggable per-target optimization Jul 5, 2026
@michalharakal michalharakal merged commit c92d587 into develop Jul 5, 2026
9 checks passed
@michalharakal michalharakal deleted the feature/bf16-dsl-export branch July 5, 2026 09:19
michalharakal added a commit that referenced this pull request Jul 5, 2026
…nt 2.07×

Bumps VERSION_NAME 0.33.0 -> 0.34.0. Bundles the develop changes since 0.33.0:
the new skainet-data-source module (URI-backed sources, HF auth, raw format
parsers, suspend data pipeline DSL) + dataset operation views and richer
batches (#784/#785), the bf16-native DSL -> StableHLO export path and the
pluggable per-phase/per-target compile-optimization seam (#788/#791), NEON
K-quant matmul perf (block-outer order + fused Q8 int8 dot, 2.07x Q4_K on
Cortex-A55) with aarch64 board verification (#786/#787), LayerNorm f32
normalization + rank-0 tensor-type emission fixes, macOS host build fix
(#789), Code of Conduct (#790), and the offline markup-antora docs image (#781).

Minor bump (not patch): new published module skainet-data-source; all data-api
additions are default-bearing (no source-incompatible changes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant