Repository navigation
[Performance]: Optimize native Rust/CUDA Laya inference #66
Description
Proposal to improve performance
Track focused performance improvements to the native Rust/CUDA Laya implementation as a child of #14, starting from the eager encoder in #49. Follow the approach used in #55: profile a frozen baseline, change one bottleneck at a time, preserve precision/rounding, and validate each change with controlled comparisons.
Use #39 for the shared benchmark methodology and eventual comparison against the original Laya runtime. This issue owns implementation optimizations and their focused validation.
Starting evidence
A local experiment switches both kernel loading and dispatch in src/models/laya/src/encoder.rs from the existing rope_original entry point to rope, using the same compiled bundle and arithmetic.
Warm Encoder::run results on the same reserved GPU:
| Workload | Padded B × L | Original mean | Selected RoPE mean | Reduction |
|---|---|---|---|---|
| Short, one question | 1 × 48 | 2.740 ms | 1.957 ms | 28.6% |
| Short, three questions | 4 × 64 | 3.101 ms | 2.235 ms | 27.9% |
| Long, one question | 1 × 464 | 3.775 ms | 2.831 ms | 25.0% |
| Long, three questions | 4 × 464 | 7.247 ms | 6.526 ms | 10.0% |
| Mixed, sixteen questions | 16 × 512 | 25.188 ms | 24.548 ms | 2.5% |
One feasibility sequence was excluded. Each configuration reused one worker, with two measured passes in AB/BA order; each block had ten excluded warmups and 50 measured requests. The two-pass mean ranges were disjoint for all five workloads. These are repeated frozen fixtures, with limited run-to-run coverage.
The timing includes host packing, input uploads, 28 encoder layers, two decision transformer layers, and completion synchronization. Tokenization, scoring/decoding, loading, compilation, warmup, hidden readback, and HTTP are outside this measurement.
Native hidden outputs were bitwise identical in 26/26 comparisons across 13 fixtures. Direct RoPE comparisons passed at 160 supported shapes with both global/local tables (320 comparisons), preserving V bytes.
Validation prerequisite: the official Laya 0.3.20 oracle fails in both unchanged #49 and the candidate at the same score/encoder27 comparison: nRMS 0.00725476 exceeds the existing 0.005 limit. Resolve the baseline/reference discrepancy before claiming full upstream parity or merge readiness; retain the existing tolerances.
Implementation priorities
- Reproduce and resolve the baseline/reference parity discrepancy with pinned dependencies and kernel provenance.
- Integrate the selected RoPE dispatch with supported-shape checks and published performance evidence.
- Tune small-shape GEMM geometry, starting with down/output projections. After the RoPE change, the four main encoder GEMMs account for approximately 72–74% of traced GPU kernel time on the short/long single-question cases. Test smaller tiles while preserving accumulation and BF16 rounding; Nsight Compute counters were unavailable, so occupancy/bandwidth causes remain unverified.
- Add shape-specific CUDA Graph capture/replay for the encoder and decision transformer, following [RFC]: Native Laya inference with Rust and CUDA #14's cache and lifecycle design. Measure capture/first-shape costs separately from replay latency.
- Reduce repeated upload synchronization and per-launch host work where profiling supports it: group uploads with safe host-buffer lifetimes and reuse resolved kernel/weight handles.
Keep changes in focused PRs consistent with #14's delivery plan. Place model dispatch/execution under src/models/laya/ and reusable CUDA operations under src/backends/cuda/. Further optimization priorities remain hypotheses until measured.
Validation and acceptance
- For each comparison, freeze source/binary/checkpoint/input hashes; declare the hypothesis, independent variable, success criterion, and run limit before execution. Keep exact GPU, affinity, precision, inputs, readiness, and cache conditions fixed.
- Run a CPU preflight, excluded feasibility run, then two measured runs per configuration by default. Publish commands, raw samples, traces, warmup/capture costs, and variability; report inconclusive results when appropriate.
- Preserve supported question types, output semantics, and established numerical tolerances. Cover changing inputs, padding, reused workspaces, and shape boundaries; validate eager/Graph agreement when Graphs are added.
- Report encoder, complete engine, and HTTP timings with explicit boundaries. Use [Performance]: Reproducible Laya benchmarks against the original runtime #39 for original-runtime quality/parity and end-to-end comparisons once the native serving path is runnable.
Recorded baseline and environment
- Encoder: Run Laya encoder and decision layers on CUDA #49 at
ff4d8fa13b2c8d52027aa2c565a0b97940d1c0ca; main base354704b6dafeb2dfc3cacd2fc2700da63a9b0b6f. - Kernel builder:
linear3735/system1-omni@5ff41a5; both configurations reused the same bundle. - Checkpoint:
convaiinnovations/laya@55cf4c4ebb4ebe31b2550e8bdf3bd21b99753851, English 421M, maximum 512 tokens. - GPU 2, UUID
GPU-cbf66259-f4ab-0ede-1811-82037dde5924, NUMA 0 / CPUs 0–15. Reserved CUDA probing reports SM90, 132 SMs, and about 140 GB; NVML's reported L20X/8.9 label conflicts with that probe. Record both when reproducing. - Torch 2.13.0+cu129, Transformers 5.10.2, TileLang 0.1.12. The cause of the oracle discrepancy is unresolved.
- Raw results and profiling artifacts are retained locally and should accompany the follow-up PR; the initial RoPE candidate is committed locally and has not been pushed.
Before submitting
- Searched existing/open and past issues and reviewed relevant implementation documentation; related work is linked above.
Activity
Levius-Fubuki commentedon Oct 3, 2026
I'd like to take on the native Laya execution and CUDA Graph work: preparing reusable kernel/weight handles, grouping input uploads with safe buffer lifetimes, adding encoder + decision-transformer capture/replay, and integrating the bounded shape cache from #14.
I'll build on #49 and the existing prototype, split the implementation into focused PRs, and coordinate around the RoPE work and baseline parity blocker. Small-shape GEMM tuning can follow separately.