Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Latest commit

 

History

History
History
295 lines (219 loc) · 15.7 KB

File metadata and controls

295 lines (219 loc) · 15.7 KB
Copy raw file
Download raw file
Outline
Edit and raw actions

FlashRT Optimization Details

Pi0.5 on Jetson AGX Thor (SM110, 20 SMs, 225 GB/s DRAM, 32 MB L2) Baseline: mlir-tensorrt + Myelin compiler engine v1.6-L2 = 70.2 ms FlashRT: Hand-written C++/CUDA pipeline = 44 ms (CUDA Graph) Improvement: -26.2 ms (37.3% faster)


1. Optimization Taxonomy

1.1 Kernel Fusion (Memory-Bound Op Elimination)

FlashRT fuses memory-bound ops that the compiler treats as separate kernels. On Thor, all non-GEMM ops are bandwidth-bound (D=2048 or 1024, 20 SMs). Each standalone kernel incurs DRAM read + write + launch overhead.

Fused Kernel Replaces (Compiler) Passes Saved Where
rms_norm_fp8_static rms_norm + memset + amax_reduce + apply_scale + fp8_quantize 4→1 Encoder (18L)
residual_add_rms_norm_fp8_static elementwise_add + rms_norm + memset + amax + apply + quant 5→1 Encoder (18L), Decoder (18L×10)
fused_adarms_fp8_static adarms_modulate + memset + amax + quant + descale 4→1 Decoder (18L×10)
gate_res_adarms_fp8_static gate_mul + residual + adarms_mod + memset + amax + quant + descale 6→1 Decoder (18L×10)
gate_geglu_merged_fp8 gelu_approx + gate_mul + memset + amax + quant + descale 5→1 Encoder (18L), Decoder (18L×10)
siglip_layernorm_1read 3-pass layernorm (mean, var, normalize) 3→1 SigLIP (27L)

Total kernel launches eliminated: Compiler engine has ~5,300 layers / ~21,000 kernel launches per inference. FlashRT has ~13 kernels/decoder-layer × 180 blocks + ~500 encoder+siglip = ~2,840 kernel launches. Reduction: ~18,000 launches eliminated (85%).

1.2 FP8 Static Quantization (Dynamic→Static Conversion)

The compiler engine uses dynamic FP8 quantization at every GEMM boundary:

Compiler (per GEMM):
  activation → amax_reduce → compute_scale → quantize_fp8 → GEMM → dequantize
  = 4 extra kernels + 1 device-sync (amax) per GEMM

FlashRT (per GEMM):
  activation (already FP8 from fused preceding kernel) → GEMM(descale=precomputed)
  = 0 extra kernels, scale is a compile-time constant

Calibration: Run 1 forward pass with amax measurement per quantization point, store scales. All subsequent inference uses static scales fused into preceding kernels.

Impact: Eliminates ~630 standalone quantize/dequantize kernel launches across the full pipeline.

1.3 RMSNorm Weight Fusion (Graph-Level Precomputation)

Standard RMSNorm: y = x / rms(x) * (1 + weight). The (1 + weight) is constant per layer.

FlashRT: Fuse (1 + weight) into the following GEMM weight at load time:

# At weight load (once):
fused_qkv_weight = qkv_weight * (1 + rms_norm_weight)

# At inference:
x_norm = rms_norm_no_weight(x)  # simpler kernel, no weight multiply
qkv = x_norm @ fused_qkv_weight  # weight already absorbs norm scale

Applied to: Encoder QKV (18L), Encoder GateUp (18L), Pi0 Decoder QKV (18L), Pi0 Decoder GateUp (18L).

Impact: Eliminates per-element multiply in RMSNorm kernel. The rms_norm_fp8_noweight kernel is ~15% faster than weighted version.

1.4 AdaRMSNorm Dense Precomputation (Pi0.5 Decoder)

Pi0.5 decoder uses AdaRMSNorm with learned modulation: y = adarms(x, style) where style = time_emb @ mod_weight + mod_bias.

Compiler (v1.0-v1.4): 370 Dense GEMMs computed inside the engine every inference.

FlashRT + Compiler v1.6-L2: Precompute all style vectors on CPU at prompt-set time. Inject as constants → 370 Dense GEMMs removed from GPU graph.

Impact: -5.5 ms (compiler v1.4→v1.6-L2), replicated in FlashRT as Python precompute in set_prompt().

1.5 Vectorized Memory Access

Kernel Before After Speedup
GELU+gate+FP8 scalar fp16 load/store + per-element FP8 store half2 load + uint32 packed 4×FP8 store 2.3x
RMSNorm+FP8 2-pass (mean, normalize) + scalar FP8 store 1-pass register-cached + uint16 packed 2×FP8 1.4x
SigLIP LayerNorm 3-pass (mean, var, norm) with float32 gamma/beta 1-pass register-cached + bfloat16x2 gamma/beta 2.1x
FP8 quantize separate amax reduce + scale compute + cast fused into preceding norm/activation kernel eliminated

1.6 CUDA Graph (Zero Dispatch Overhead)

The entire pipeline (SigLIP + PostLN + Encoder + Decoder) is captured as CUDA Graph.

Compiler engine: Already uses CUDA Graph internally (TRT engine execution). However, the graph contains 21,000+ nodes with Myelin's fine-grained kernel scheduling.

FlashRT: Graph contains ~2,840 nodes (7.4x fewer). Fewer nodes = faster graph instantiation + less scheduling overhead.

Graph autotune: CUDA Graph instantiation on Thor is non-deterministic (±2ms variance). FlashRT recaptures until a fast schedule is found. Typical: 0-1 retries needed.


2. End-to-End Timing Breakdown (Pi0.5, 2-view LIBERO)

2.1 Component Comparison

Component Compiler v1.6-L2 FlashRT Delta Notes
SigLIP (27L vision) 1.0 ms 6.3 ms +5.3 ms See §2.2
Encoder (18L VLM) 36.9 ms 19.8 ms -17.1 ms See §2.3
Decoder (18L×10 steps) 36.1 ms 24.5 ms -11.6 ms See §2.4
Graph pipelining (included) -3.9 ms SigLIP overlaps with upload
Python/Host ~0 ms 2.1 ms +2.1 ms Image preprocessing + output
Total ~74 ms (nsys) / 70.2 ms (CUDA events) 44 ms -26 ms

Note: Compiler's "74 ms nsys" vs "70.2 ms CUDA events" gap is due to nsys profiling overhead. FlashRT's 44 ms is CUDA events measurement (wall-clock perf_counter with cudaStreamSynchronize).

2.2 SigLIP: Why Compiler is Faster (1.0 ms vs 6.3 ms)

The compiler engine benefits from Myelin's global fusion across SigLIP's 27 layers:

  • QKV GEMM + bias + reshape fused into single kernel
  • LayerNorm + GELU + bias fused as GEMM epilogues
  • Entire 27-layer stack shares L2 cache optimally (Myelin schedules for L2 residency)

FlashRT uses cuBLASLt FP8 GEMM + separate custom kernels for norm/activation:

  • 27 layers × ~6 kernels/layer = ~162 kernel launches (vs compiler's ~138 with deeper fusion)
  • FP8 weight reduces DRAM traffic (15 MB/layer < 32 MB L2), partially compensating

This is the one area where the compiler approach wins. SigLIP's large batch (S=512) and simple attention (no RoPE, no cross-attention) is ideal for Myelin's fusion.

2.3 Encoder: FlashRT 17.1 ms Faster

Sub-component Compiler v1.6-L2 FlashRT Delta Root Cause
FFN GEMMs (gate_up + down) 15.8 ms 9.2 ms -6.6 ms cuBLASLt tactic vs Myelin CUTLASS
Attention (QKV + softmax + attn@V) 7.1 ms 4.3 ms -2.8 ms cuBLAS attention vs Myelin kgen+GEMM
RMSNorm + QDQ + RoPE 5.4 ms 1.4 ms -4.0 ms Fused norm+FP8, eliminated QDQ
Data movement (transpose, concat) 6.9 ms 0 ms -6.9 ms No explicit transposes needed
Other kgen 1.7 ms 0 ms -1.7 ms Fused into preceding kernels
Kernel dispatch overhead 4.9 ms +4.9 ms ~290 kernel launches × ~17μs gap
Total 36.9 ms 19.8 ms -17.1 ms

Key wins:

  1. No data movement: Compiler inserts MoveConcat/Transpose for layout conversion between ops. FlashRT uses consistent row-major layout throughout — zero explicit transposes.
  2. Fused norm+FP8: Compiler has separate RMSNorm → QDQ → Quantize → GEMM. FlashRT: rms_norm_fp8_static produces FP8 output directly.
  3. cuBLASLt vs Myelin tactic: For Se=522 (encoder sequence), cuBLASLt selects better CUTLASS configurations than Myelin's tactic search for some shapes.

2.4 Decoder (AE): FlashRT 11.6 ms Faster

Sub-component Compiler v1.6-L2 FlashRT Delta Root Cause
FP8 GEMMs (QKV+O+GateUp+Down) 24.2 ms 15.8 ms -8.4 ms cuBLASLt + static descale vs Myelin FP8
Norm + activation + fusion kgen 11.9 ms 1.8 ms -10.1 ms All fused into 3 custom kernels/layer
Kernel dispatch overhead 6.9 ms +6.9 ms 2,340 graph nodes × ~3μs
Total 36.1 ms 24.5 ms -11.6 ms

Key wins:

  1. 10.1 ms from fusion: Compiler's ~9,700 kgen launches (norm, GELU, softmax, QDQ) across 180 blocks. FlashRT replaces with 3 fused kernels per layer: fused_adarms_fp8_static + gate_res_adarms_fp8_static + gate_geglu_merged_fp8_fp16. Despite GPU overlap making kgen "free" in the compiler, FlashRT's fused approach reduces total graph nodes and improves scheduling.
  2. 8.4 ms from GEMM: At S=10 (decoder batch), cuBLASLt FP8 with pre-baked descale outperforms Myelin's CUTLASS FP8 + separate descale. Myelin's per-GEMM tactic search advantage diminishes at very small batch sizes where launch overhead dominates.

3. Per-Layer Kernel Comparison (Decoder, 1 Layer)

Compiler v1.6-L2 (per decoder layer, ×180 total)

#   Kernel                              Time     Type
1.  AdaRMSNorm kgen                     6 μs     elementwise (Myelin fused)
2.  QKV proj FP8 GEMM (CUTLASS)        17 μs    compute
3.  RoPE kgen                           5 μs     elementwise
4.  RoPE kgen                           5 μs     elementwise
5.  Q@K^T GEMM                          4 μs     compute
6.  Transpose+Select kgen               3 μs     data movement
7.  Softmax kgen                        4 μs     elementwise
8.  Attn@V GEMM                         4 μs     compute
9.  QDQ kgen                            3 μs     quantize
10. Transpose kgen                      3 μs     data movement
11. O proj FP8 GEMM                    12 μs    compute
12. Gate mul + residual kgen            5 μs     elementwise
13. AdaRMSNorm kgen                     6 μs     elementwise
14. Gate+Up FP8 GEMM (Wab, CUTLASS)   17 μs    compute
15. GELU kgen                           8 μs     elementwise
16. Down FP8 GEMM (CUTLASS)           17 μs    compute
17. Gate mul + residual kgen            5 μs     elementwise
18-22. QDQ/Cast kernels (various)      15 μs    quantize/cast
────────────────────────────────
     ~22 kernels/layer                ~139 μs   total
     ×180 layers                      ~25 ms    (+ GPU overlap → 36ms wall)

FlashRT (per decoder layer, ×180 total)

#   Kernel                              Time     Type
1.  fused_adarms_fp8_static             8 μs     AdaRMSNorm+FP8 quantize (fused)
2.  fp8_gemm_descale (QKV)             14 μs    cuBLASLt FP8 + static descale
3.  qkv_split_rope_kvcache             5 μs     split Q/K/V + RoPE + KV cache write
4.  cublasGemmEx (Q@K^T)               4 μs     cuBLAS attention
5.  softmax_fp16                        3 μs     vectorized softmax
6.  cublasGemmEx (attn@V)              4 μs     cuBLAS attention
7.  quantize_fp8_static                 2 μs     O proj input quantize
8.  fp8_gemm_descale (O proj)          12 μs    cuBLASLt FP8
9.  gate_res_adarms_fp8_static          9 μs     gate×res + AdaRMSNorm + FP8 (fused)
10. fp8_gemm_descale (Gate+Up)         14 μs    cuBLASLt FP8
11. gate_geglu_merged_fp8_fp16          4 μs     GELU(gate)×up + FP8 (fused)
12. fp8_gemm_descale (Down)            14 μs    cuBLASLt FP8
13. gate_res_adarms_fp8_static          9 μs     residual + cross-layer norm (fused)
────────────────────────────────
     13 kernels/layer                  ~102 μs   per layer
     ×180 layers                       ~18.4 ms  (+ dispatch → 24.5ms wall)

Key Differences

Metric Compiler FlashRT Ratio
Kernels per layer 22 13 0.59x
Compute time per layer ~139 μs ~102 μs 0.73x
Total kernel launches (decoder) 16,480 2,340 0.14x
Data movement kernels 4/layer (transpose, QDQ) 0/layer eliminated
Standalone quantize kernels 3-4/layer 0/layer (fused) eliminated

4. Optimization Progression

Compiler Path (mlir-tensorrt + Myelin)

Version Latency Delta Method
v1.0 (initial FP8) 85.3 ms JAX export → MLIR → TRT FP8, dynamic quantize
v1.2-OPT4 (GQA + skip QDQ) 78.5 ms -6.8 ms Implicit GQA broadcast, skip small-GEMM QDQ, SigLIP FP8
v1.4-Wab (gate+up merge) 75.7 ms -2.8 ms 2 GEMM → 1 wide GEMM, halve input QDQ
v1.6-L2 (Dense precompute) 70.2 ms -5.5 ms 370 Dense removed, graph simplification → better Myelin tactic

FlashRT Path (C++/CUDA hand-written)

Phase Latency Delta Method
Phase 2 (initial port) 83.0 ms Replace Myelin with cuBLASLt + manual kernels
Phase 3 (kernel fusion) 64.9 ms -18.1 ms Fused norms, 1-pass register cache, packed FP8, SigLIP 1-read LN
Phase 4 (SigLIP FP8) 63.6 ms -1.3 ms BF16→FP8 weight (30→15 MB/layer, L2 residency)
Phase 5 (FP16 + FMHA) 62.0 ms -1.6 ms BF16→FP16 throughout, CUTLASS strided FMHA
Phase 6 (full static) 47.8 ms -14.2 ms Calibrate→static FP8, PostLN in graph, fused encoder
+ CUDA Graph autotune 44 ms -3.8 ms Recapture for optimal graph schedule

5. Where the 26 ms Comes From

Summary of FlashRT's advantage over compiler v1.6-L2 (70→44 ms):

Category Savings Mechanism
Encoder norm+QDQ elimination -4.0 ms rms_norm_fp8_static replaces 4 separate kernels
Encoder data movement elimination -6.9 ms No MoveConcat/Transpose (consistent layout)
Encoder GEMM improvement -6.6 ms cuBLASLt tactic advantage at Se=522
Decoder kgen→fused kernels -10.1 ms 3 fused kernels replace ~10 kgen per layer
Decoder GEMM improvement -8.4 ms cuBLASLt static descale at S=10
SigLIP regression +5.3 ms Myelin's 27-layer global fusion is unbeatable
Host/Python overhead +2.1 ms Image preprocessing, output download
Graph pipelining -3.9 ms SigLIP graph overlaps with image upload
Graph autotune -3.8 ms Eliminates instantiation variance
Net improvement -26.2 ms 70.2 → 44 ms (37.3% faster)

6. Why the Compiler Cannot Match FlashRT

Structural Limitation: Opaque Boundary

Any TRT Plugin inserted into the Myelin engine creates an "opaque boundary" — Myelin cannot fuse across it. Tested with RoPE Plugin:

  • Plugin alone: -18 ms (RoPE kernel 10x faster)
  • Myelin fusion regression: +36 ms (surrounding GEMMs lose cooperative scheduling)
  • Net effect: +10 ms slower

S=10 Latency Regime

The decoder operates at S=10 (10 action tokens). At this batch size:

  • All GEMMs are launch-bound, not compute-bound
  • Kernel fusion savings (memory traffic) are negligible (< 1 KB intermediate data)
  • Dispatch overhead dominates: compiler's 16,480 launches × ~2μs = ~33 ms dispatch cost
  • FlashRT's 2,340 launches × ~3μs = ~7 ms dispatch cost
  • Dispatch savings alone: ~26 ms (matches the total improvement)

Myelin Tactic Search vs cuBLASLt

For large shapes (encoder Se=522), cuBLASLt consistently selects better CUTLASS configurations than Myelin's tactic search. This is because:

  1. cuBLASLt has access to NVIDIA's latest tactic database (updated with driver)
  2. Myelin's search space is constrained by the engine builder's time budget
  3. For very small shapes (S=10 decoder), both are launch-bound and the difference is minimal

The Fundamental Trade-off

Compiler (Myelin):  Global fusion view → fewer kernels → but limited to Myelin's patterns
FlashRT:           No global fusion   → more kernels → but each kernel is purpose-built

At large batch (SigLIP S=512):  Myelin's global fusion wins (1 ms vs 6 ms)
At small batch (Decoder S=10):  FlashRT's fewer launches wins (24 ms vs 36 ms)
At medium batch (Encoder S=522): FlashRT's cuBLASLt + fusion wins (20 ms vs 37 ms)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.