Repository navigation
[FlyDSL] feat(2stageGR) Two-stage Hyper-Connection Gated Residual kernel - #5913
Conversation
🏷️ CI GuideRuns automatically on every PR:
Extended tests (opt-in via labels):
One backend per PR: PR title tags & labels: |
Co-authored-by: Mikko Tukiainen <Mikko.Tukiainen@amd.com>
Co-authored-by: Mikko Tukiainen <Mikko.Tukiainen@amd.com>
…ny decode file Co-authored-by: Mikko Tukiainen <Mikko.Tukiainen@amd.com>
|
@aiter-bot review |
ROCm/aiter PR #5913 — [FlyDSL] feat(2stageGR) Two-stage Hyper-Connection Gated Residual kernelAdds a new two-stage FlyDSL Hyper-Connection gated-residual op for gfx942/gfx950 bf16 — K1 fuses combine + grouped RMSNorm + down/inject GEMM, K2 fuses SiLU + up GEMM + gated mean with xn re-formed in registers so it never touches HBM — plus a fused skinny decode path, tuned configs, an oracle test, a bench and a tuner. Review (advisory):
|
Additional review findingsI reviewed the current head ( 1.
|
…and skinny counters
READY FOR HUMAN REVIEWPR Self-ReviewRevision Author self-review accepted. Readiness report: Author answers
Show the author's self-review answersBehavior and intentValidation and unverified scopePR-specific questionChanged op testsPR state
Copilot findings
Code and op-test hygiene
Required CI
Shadow mode reports what would block but keeps the status successful. Set |
…OCm/aiter into dlejeune/2stage-gated_residual
… publication, tuner
…pers v0.1.27 moved communication_ops_utils.py onto FlyDSL's generic pointer APIs, so the net diff of ROCm#5913 kept av_store_b128 and atomic_add_agent_acq_rel_av_none without the ir and llvm dialect imports they use, and _to_ptr_global now returns a FlyDSL pointer instead of !llvm.ptr<1>. The skinny HC decode kernel failed to trace with "NameError: name 'ir' is not defined". The two helpers now cast through _llvm_ptr_global, ROCm#5913's original cast. Signed-off-by: Mikko Tukiainen <Mikko.Tukiainen@amd.com> Co-authored-by: Cursor <cursoragent@cursor.com>
|
@junhaha666 thanks for the detailed review and repros. All four findings, and the oracle-coverage point, are addressed:
Validated on gfx950 (full suite, FlyDSL 0.4.1) and on gfx942 (MI300X, FlyDSL 0.4.1); details are in the PR Self-Review section. |
|
@aiter-bot findings from the Nov 2 review, and how each was addressed:
The Ruff F821 findings reported by reviewdog on |
Motivation
Implement a two-stage Hyper Connection Gated Residual kernel in order to improve performance on the decode case. The current baseline is Triton/Hipblaslt 5-stage kernel.
Architecture: gfx950 and gfx942
Note: this kernel on gfx942 needs a BF16 GEMM, which is not available for now on FlyDSL. An adaptation of the gfx950 GEMM is provided temporarily but we aim to remove this implementation as soon a more efficient GEMM is ready as a drop-in replacement.
Technical Details
Paper: https://arxiv.org/abs/2409.19606
In the context of Hyper-Connections, let
R ∈ ℝ^{HC×HS}be the incoming streams (flattenedR ∈ ℝ^{K},K=10240),y ∈ ℝ^{HS}the block output to inject,inj ∈ ℝ^{HC}the incoming injection logits (from the previous layer),σsigmoid and⊙elementwise product.HCis the number of parallel residual streams andHSis the hidden width of a single stream (2560, in our case).Kis the flatten residual width, or equivalently, the down-GEMM contraction dimensionxnis the single normalized input reused by all three projections below.Wd : ℝ^{K} → ℝ^{r}, then SiLU. This GEMM (G1) encodes the low rank space.Wu : ℝ^{r} → ℝ^{K}, then sigmoid. This GEMM (G2) decoder expands the low rank code back to the input vector K dimension size, per stream.The 2-stage kernel is split between:
h = (xn·Wdᵀ)/HCR2, the reciprocal-RMS scalerrms, the down-GEMM outputlora(pre-SiLU), and the next layer's injection logitsinj_nextinto HBMxn = R2 * rrms * (1+Wnorm)on the fly, such thatxnandgate(i.e. up-GEMM output before sigmoid) are never stored in HBMThe 5-stage kernel is split between:
Every intermediate (
R2,xn,gate, etc) incurs an HBM roundtripTest Plan
Unit tests have been provided. They should pass on both target architectures
Performance
gfx950 performance vs the vLLM baseline
Setup: MI355X (gfx950), single GPU, ROCm 7.2.4, PyTorch 2.10.0, Triton 3.6.0, FlyDSL 0.4.1, head 03121cd.
(1+w)folded in, as shipped by vLLM.qwen4_exphcTriton kernels (_hc_combine_norm,_hc_silu,_hc_gate_mix) plus hipBLASLtF.linearfor the down/inject and up projections, i.e. the 5-stage path. vLLM commit 114abd1.flydsl_gr_two_stage_combine_and_mix(..., fold_w=True).HIP_VISIBLE_DEVICES=2 PYTHONPATH=<aiter>:<vllm> python op_tests/flydsl_tests/_bench_vs_triton_graph.py --tokens 1 2 4 8 16 32 512 1024 2048 4096 8192 16384 32768 65536 --mode both --iters 60In eager mode the decode gain grows to 2.4–3.1×, because the 5-stage baseline is bound by launch overhead there.
Run-to-run spread over the 3 sweeps:
The GPU was shared with other processes during the runs.
PR Self-Review
Reviewed revision
03121cd
Behavior and intent
Adds a new FlyDSL two-stage Hyper-Connection gated-residual op (
aiter/ops/flydsl/kernels/hyper_connection_gated_residual/) for gfx950 (the target) and gfx942 (functional support), bf16. Today this layer runs as a 5-stage Triton/hipBLASLt pipeline that round-trips every intermediate (R2,xn,gate) through HBM.xnfromR2andrrmsin registers so that neitherxnnorgateis ever stored.flydsl_gr_two_stage_combine_and_mix,flydsl_gr_two_stage_mixandflydsl_gr_two_stage_combine;flydsl_k1_combine_norm_downandflydsl_up_gate_mix_norm.NotImplementedError), widths the kernels cannot tile, token counts no tile divides (pad withgemm_pad), mismatched streams and devices,stages < 2, row tails on direct K2, and malformed or aliased caller output buffers (ValueError)._llvm_ptr_global,_av_scope,av_store_b128,atomic_add_agent_acq_rel_av_none) and one import toaiter/ops/flydsl/kernels/communication_ops_utils.py. Existing functions there are untouched, and the helpers are used only by the skinny decode.Validation and unverified scope
gfx950 (target): MI355X, single GPU, ROCm 7.2.4, FlyDSL 0.4.1 (as pinned by
main), at head 03121cd.HIP_VISIBLE_DEVICES=2 python op_tests/test_hc_gated_residual.pypassed: 292 checks, all against an independent float32 PyTorch oracle. Coverage:n_pad=336from M=1 to 8192;gemm_pad=128;main):n_padcases;buffer_wbl2/buffer_invinstructions, and the LLVM IR keeps the acq_relamdgcn-av:nonearrival atomic and thellvm.amdgcn.av.store.b128partial stores.gfx942 (functional support): AMD Instinct MI300X (304 CUs), single GPU, ROCm 7.2.4, PyTorch 2.10.0, FlyDSL 0.4.1, at head 8393037.
python op_tests/test_hc_gated_residual.pypassed: 265 checks against the float32 oracle. Coverage:amdgcn-av:nonearrival atomic, rawsc1loads);n_pad=8case;n_pad=336from M=1 to 8192.Unverified:
ValueErrorin K1 and adds checks that run only on gfx950.block_k=256exceeds its 64 KiB LDS;block_k=128needs ≥ 5 stages, beyond the GEMM's vmcnt budget), so_run_pipeline_depthskips there._run_stream_device_mismatchskips itself with only one visible GPU.n_pad=336are not covered there.PR-specific question
The only consumer of
aiter/configs/model_configs/hc_gated_residual_tuned.jsonis this op'stuned.py; the tunerop_tests/tuners/tune_hc_gated_residual.pywrites it. No other op reads the file, so no unrelated workload's selection can change.Workload keys (all for HC=4, HS=2560, r=320):
gfx950/k1gfx950/k1_n336gfx950/up_gate_mixgfx950/up_gate_mix_n336gfx942/k1How lookups behave:
method: "heuristic", the kernel keeps its heuristic.up_gate_mix_n*entries apply only to exactly measured token counts._k1_tuned_plandiscards any plan that cannot tile the actual shape, such as a different hidden size or too few K tiles for the pipeline depth, and falls back to the heuristic._run_untuned_hidden(HC=2) and_run_wide_stream_lds(HS=8192 and 40960) test this.Changed op tests
op_tests/test_hc_gated_residual.py(new) is the correctness suite for the new op; nothing in aiter covers it. It checks every public entry point and every kernel path (split-K, decoupled, skinny decode, gfx942 GEMM) against a float32 oracle, including the tuned token range. It also covers the error contract and the skinny cross-wave counter protocol:stages, arch and stream device;op_tests/flydsl_tests/bench_hc_gated_residual.py(new) is a standalone latency benchmark for the op. It timescombine_and_mixwith a pre-folded weight across a token sweep and checks each shape against the oracle before timing. It doesn't duplicate the test, because the test never measures latency. The comparison against vLLM in the Performance section uses a separate script, outside this PR, because it needs a vLLM checkout.op_tests/tuners/tune_hc_gated_residual.py(new) generateshc_gated_residual_tuned.json.heuristicwhen the untuned path wins.Submission Checklist