Skip to content

sycl: fused nodes for SmolLM / Qwen2 / InternViT / DiT layers - #53

Open
sandlbn wants to merge 1 commit into
VinRobotics:mainfrom
sandlbn:sycl-fused-nodes
Open

sandlbn wants to merge 1 commit into
VinRobotics:mainfrom
sandlbn:sycl-fused-nodes

Conversation

@sandlbn

@sandlbn sandlbn commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

SYCL: fused nodes for the SmolLM / Qwen2 / InternViT / DiT layers

Shared groundwork for the per-model SYCL PRs that follow (SmolVLA, Evo-1, VLA-Adapter, GR00T). Nothing here changes an existing model's graph: π0.5's actions are bit-identical to main, default and with VLA_PI05_W8A8=all.

New in layers/fused_ew.h, each with a CPU reference and a SYCL kernel in src/sycl/vla_sycl_fused.cpp:

  • fused_rms: RMSNorm into F32 or BF16 rows. fused_rms_q8 only wrote INT8 blobs.
  • fused_swiglu: the GLU node with SiLU (the Llama / Qwen MLP), into BF16 rows or an INT8 blob. It is fused_geglu with a flag.
  • An exact-GELU epilogue: fused_linear(..., FUSED_GELU_ERF) and the same for qlinear, using oneDNN eltwise_gelu_erf. The old bool gelu argument still means tanh.
  • fused_lnorm without an affine (null weight and bias).
  • fused_rope_kv_table: RoPE from per-token cos / sin tables (the rope_2d form), with an optional BF16 output, so a batch of views can be read in place by the attention.

Shared modules:

  • modules/fused_lm.h: a Qwen2 / Llama decoder layer as 8 fused nodes. The steps are RMSNorm, q|k|v (+ bias), RoPE into F16 K/V buffers, attention, o + residual, RMSNorm, gate|up, SwiGLU, and down + residual. q|k|v and gate|up are concatenated at load, and the residual stream stays F32.
  • EncCfg::gelu_erf for the fused SigLIP-style blocks.
  • fold_layer_scale (modules/encoder.h): folds LayerScale into the resident BF16 weights, so the residual add can be a GEMM epilogue.

Why

Adds the five fused nodes the models were missing. A layer goes from ~20 kernels to 7-9, it's key because at 50 rows each kernel launch costs more than reading the weights.

Verified

  • Builds clean under -Wall -Wextra (first-party code)
  • ctest passes
  • Numeric output unchanged (vla_predict_check diff), or the change is
    meant to move it and a LIBERO sweep is below
  • vla_predict_check π0.5 (pi05-libero.gguf, 2 views): bit-identical to main, default and W8A8.

Archs and backends tested:

x86 Linux / PTL

layers/fused_ew.h, with CPU references and SYCL kernels:
- fused_rms: RMSNorm into F32 / BF16 rows (fused_rms_q8 wrote only q8 blobs)
- fused_swiglu: the GLU node with SiLU, into BF16 rows or a q8 blob
- FUSED_GELU_ERF: an exact-GELU epilogue for fused_linear and qlinear
  (oneDNN eltwise_gelu_erf); the old bool argument still means tanh
- fused_lnorm without affine (null weight and bias)
- fused_rope_kv_table: RoPE from per-token cos/sin tables (rope_2d), with a
  BF16 output so a batch of views can be read in place by the attention

modules/fused_lm.h: a Qwen2 / Llama decoder layer as 8 fused nodes.
modules/encoder: EncCfg::gelu_erf, and fold_layer_scale for folding
LayerScale into resident BF16 weights.

tests/test_fused_sycl_ops checks every new node against the CPU reference.
pi0.5's actions are bit-identical to before (default and W8A8).
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