Skip to content

gr00t: fused DiT and vision towers on SYCL (N1.5, N1.6; N1.7 opt-in) - #57

Open
sandlbn wants to merge 2 commits into
VinRobotics:mainfrom
sandlbn:gr00t-sycl-fused
Open

sandlbn wants to merge 2 commits into
VinRobotics:mainfrom
sandlbn:gr00t-sycl-fused

Conversation

@sandlbn

@sandlbn sandlbn commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Depends on the SYCL fused-nodes PR. On SYCL with BF16 weights and view-taking flash attention, without FoldQuant:

  • DiT (modules/dit_head, shared by N1.5, N1.6 and N1.7): each block runs as 7 nodes.
    • The adaLN is one LayerNorm whose weight is 1 + scale and bias is the shift. FlowTimes stores 1 + scale when the head is fused.
    • q|k|v (or q, with the cross-attention K/V hoisted out of the solver loop as BF16) feeds an attention that reads the GEMM outputs in place.
    • o carries the residual, then the affine-free norm, ff0 with its GELU, and ff2 with the residual. proj_out is a LayerNorm plus one GEMM.
    • dit_enable_fused is the single switch all three models call.
  • N1.5 / N1.6 vision: SigLIP runs EncStack's fused blocks, every view in one pass. N1.5 previously encoded one view per compute.
  • N1.7 vision: the Qwen3-VL tower is fused too, with 2D RoPE from the cos/sin tables and views batched. It is opt-in (VLA_GR00T_N17_FUSE=1) because it has no LIBERO run yet; the eval client's tokenizer repo, nvidia/Cosmos-Reason2-2B, is gated. N1.7's default graph is unchanged: actions identical to main.

--flash-attn 0 or VLA_GR00T_NO_FUSE=1 keep the per-op graph.

Results

Core Ultra 7 356H (Panther Lake), 2 views, vla-bench p50 (per-op → fused, interleaved runs):

p50 vision max |Δa| vs F32 weights, per-op BF16 → fused
N1.5 (224 px) 671 → 421 ms 194 → 41 ms 4.5e-3 → 6.9e-3
N1.6 (224 px) 455 → 214 ms 196 → 41 ms 2.7e-3 → 3.4e-3
N1.7 (256 px, opt-in) 449 → 227 ms 188 → 39 ms 3.7e-3 → 1.3e-2

N1.7's larger error comes from reduced-precision attention in the tower: the per-op graph with --flash-attn 1 measures the same 1.3e-2.

LIBERO-object, 10 tasks x 10 episodes per arm, same binary:

success Wilson 95% median ms / request
N1.5 per-op (VLA_GR00T_NO_FUSE=1) 98/100 93-99% 710
N1.5 this PR 95/100 89-98% 463
N1.5 this PR + --flash-attn 1 (not default) 90/100 83-94% 276
N1.6 per-op 89/100 81-94% 479
N1.6 this PR 90/100 83-94% 235

N1.5's 95 vs 98 is within noise, and the misses are spread over four tasks. With --flash-attn 1 on top (LM and VL self-attention), N1.5 drops to 90/100 with misses on six tasks. That is why flash attention stays opt-in for GR00T.

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

Archs and backends tested:

Linux x86 / PTL

sandlbn and others added 2 commits October 24, 2026 17:53
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).
On SYCL with BF16 weights and view-taking flash attention, without
FoldQuant (--flash-attn 0 or VLA_GR00T_NO_FUSE=1 keep the per-op graph):
- DitHead (shared by N1.5 / N1.6 / N1.7): each block as 7 fused nodes, the
  adaLN as a LayerNorm with weight 1 + scale (FlowTimes keeps it) and bias
  shift, attention reading the q|k|v / hoisted cross K/V outputs in place,
  residual adds and GELU as GEMM epilogues; dit_enable_fused is the switch
- N1.5 / N1.6: SigLIP as EncStack's fused blocks, every view in one pass
- N1.7: the Qwen3-VL tower fused too (RoPE from tables, views batched);
  opt-in (VLA_GR00T_N17_FUSE=1) until it has a LIBERO run

Core Ultra 7 356H, 2 views, vla-bench p50: N1.5 671 -> 421 ms, N1.6 455 ->
214 ms, N1.7 (opt-in) 449 -> 227 ms. LIBERO-object x100: N1.5 95/100 (per-op
98/100), N1.6 90/100 (per-op 89/100).

Co-Authored-By: Claude Opus 5.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