Skip to content

[NPU] Add Llama4_rope support on NPU - #1035

Merged
Tcc0403 merged 8 commits into
linkedin:mainfrom
lowdy1:llama4rope_npu
Jan 27, 2026
Merged

[NPU] Add Llama4_rope support on NPU#1035
Tcc0403 merged 8 commits into
linkedin:mainfrom
lowdy1:llama4rope_npu

Conversation

@lowdy1

@lowdy1 lowdy1 commented Jan 21, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR implements a fully executable Llama4 RoPE operator for Ascend NPU.

  1. Prevents UB overflow issues specific to NPU execution
  2. Implements interleaved complex layout compatible with NPU kernels

Testing Done

image Test done with `python -m pytest ./test/transformers/test_llama4_rope.py -v`

Verified on Atlas 800I A2(32G)

  • run make test to ensure correctness
  • run make checkstyle to ensure code style
  • run make test-convergence to ensure convergence

@lowdy1

lowdy1 commented Jan 21, 2026

Copy link
Copy Markdown
Contributor Author

Could you please help review this PR? @noemotiovon @TianHao324

Comment thread src/liger_kernel/ops/backends/_ascend/ops/llama4_rope.py Outdated
@noemotiovon

noemotiovon commented Jan 21, 2026

Copy link
Copy Markdown
Contributor

Thank you for your contribution! In the current tiling strategy, UB usage is computed based on the kernel implementation. If the UB usage exceeds the limit, we apply further tiling. With this approach, even when UB does not overflow, it may not be fully utilized.

A better approach might be to derive the block size from the available UB capacity and try to utilize the UB as much as possible. Based on this idea, we may need to adjust both the tiling strategy and the kernel implementation, although I am still exploring how to realize this in practice.

@noemotiovon

Copy link
Copy Markdown
Contributor

At the moment, llama4_rope does not run on the NPU yet. It would be better to first ensure functional support, and then follow up with more detailed performance optimizations.

@lowdy1

lowdy1 commented Jan 22, 2026

Copy link
Copy Markdown
Contributor Author

At the moment, llama4_rope does not run on the NPU yet. It would be better to first ensure functional support, and then follow up with more detailed performance optimizations.

Thanks for the suggestion. A naive implementation with a hardcoded memory_multiplier has already passed all benchmark tests and demonstrates generally better performance than the Hugging Face implementation on NPU, and more detailed performance optimizations to be explored in follow-up work. The benchmark results are included below.

**************************************
     BENCHMARKING SPEED for LLAMA4_ROPE
**************************************
********** Benchmark Data **********
[
  {
    "kernel_name": "llama4_rope",
    "kernel_provider": "liger",
    "metric_name": "speed",
    "metric_unit": "ms",
    "gpu_name": "Ascend910B4",
    "x_name": "H",
    "x_label": "hidden size",
    "x_values": [
      512,
      2048,
      8192
    ],
    "y_values_50": [
      0.3495500087738037,
      0.3440999984741211,
      0.5970100164413452
    ],
    "y_values_20": [
      0.3393680155277252,
      0.33820000290870667,
      0.5955399870872498
    ],
    "y_values_80": [
      0.3635519742965698,
      0.35385599732398987,
      0.5985000133514404
    ],
    "timestamp": "2026-01-22 06:11:29",
    "kernel_operation_mode": "forward",
    "extra_benchmark_config_str": "{\"dtype\": \"torch.bfloat16\", \"seq_len\": 2048, \"num_q_heads\": 32, \"num_kv_heads\": 8}",
    "liger_version": "0.6.4"
  },
  {
    "kernel_name": "llama4_rope",
    "kernel_provider": "huggingface",
    "metric_name": "speed",
    "metric_unit": "ms",
    "gpu_name": "Ascend910B4",
    "x_name": "H",
    "x_label": "hidden size",
    "x_values": [
      512,
      2048,
      8192
    ],
    "y_values_50": [
      0.21660000085830688,
      0.23175999522209167,
      0.7504600286483765
    ],
    "y_values_20": [
      0.20934399962425232,
      0.22538399696350098,
      0.7480520009994507
    ],
    "y_values_80": [
      0.22602799534797668,
      0.23895999789237976,
      0.7531279921531677
    ],
    "timestamp": "2026-01-22 06:11:31",
    "kernel_operation_mode": "forward",
    "extra_benchmark_config_str": "{\"dtype\": \"torch.bfloat16\", \"seq_len\": 2048, \"num_q_heads\": 32, \"num_kv_heads\": 8}",
    "liger_version": "0.6.4"
  },
  {
    "kernel_name": "llama4_rope",
    "kernel_provider": "liger",
    "metric_name": "speed",
    "metric_unit": "ms",
    "gpu_name": "Ascend910B4",
    "x_name": "H",
    "x_label": "hidden size",
    "x_values": [
      512,
      2048,
      8192
    ],
    "y_values_50": [
      0.4119200110435486,
      0.3996399939060211,
      0.5986800193786621
    ],
    "y_values_20": [
      0.4016279876232147,
      0.3933599889278412,
      0.5977200269699097
    ],
    "y_values_80": [
      0.42577600479125977,
      0.407368004322052,
      0.5999400019645691
    ],
    "timestamp": "2026-01-22 06:11:32",
    "kernel_operation_mode": "backward",
    "extra_benchmark_config_str": "{\"dtype\": \"torch.bfloat16\", \"seq_len\": 2048, \"num_q_heads\": 32, \"num_kv_heads\": 8}",
    "liger_version": "0.6.4"
  },
  {
    "kernel_name": "llama4_rope",
    "kernel_provider": "huggingface",
    "metric_name": "speed",
    "metric_unit": "ms",
    "gpu_name": "Ascend910B4",
    "x_name": "H",
    "x_label": "hidden size",
    "x_values": [
      512,
      2048,
      8192
    ],
    "y_values_50": [
      0.4415600001811981,
      0.5250399708747864,
      1.806480050086975
    ],
    "y_values_20": [
      0.4260160028934479,
      0.5183079838752747,
      1.7919600009918213
    ],
    "y_values_80": [
      0.45458000898361206,
      0.5332199931144714,
      1.823199987411499
    ],
    "timestamp": "2026-01-22 06:11:34",
    "kernel_operation_mode": "backward",
    "extra_benchmark_config_str": "{\"dtype\": \"torch.bfloat16\", \"seq_len\": 2048, \"num_q_heads\": 32, \"num_kv_heads\": 8}",
    "liger_version": "0.6.4"
  },
  {
    "kernel_name": "llama4_rope",
    "kernel_provider": "liger",
    "metric_name": "speed",
    "metric_unit": "ms",
    "gpu_name": "Ascend910B4",
    "x_name": "H",
    "x_label": "hidden size",
    "x_values": [
      512,
      2048,
      8192
    ],
    "y_values_50": [
      0.9888200163841248,
      0.9929599761962891,
      1.1916999816894531
    ],
    "y_values_20": [
      0.9802200198173523,
      0.9864000082015991,
      1.1902400255203247
    ],
    "y_values_80": [
      1.0013400316238403,
      1.0059040784835815,
      1.1932320594787598
    ],
    "timestamp": "2026-01-22 06:11:35",
    "kernel_operation_mode": "full",
    "extra_benchmark_config_str": "{\"dtype\": \"torch.bfloat16\", \"seq_len\": 2048, \"num_q_heads\": 32, \"num_kv_heads\": 8}",
    "liger_version": "0.6.4"
  },
  {
    "kernel_name": "llama4_rope",
    "kernel_provider": "huggingface",
    "metric_name": "speed",
    "metric_unit": "ms",
    "gpu_name": "Ascend910B4",
    "x_name": "H",
    "x_label": "hidden size",
    "x_values": [
      512,
      2048,
      8192
    ],
    "y_values_50": [
      0.9132400155067444,
      0.9397799968719482,
      2.6154000759124756
    ],
    "y_values_20": [
      0.8844199776649475,
      0.8971520066261292,
      2.602060079574585
    ],
    "y_values_80": [
      0.9337000250816345,
      0.9751840233802795,
      2.63211989402771
    ],
    "timestamp": "2026-01-22 06:11:36",
    "kernel_operation_mode": "full",
    "extra_benchmark_config_str": "{\"dtype\": \"torch.bfloat16\", \"seq_len\": 2048, \"num_q_heads\": 32, \"num_kv_heads\": 8}",
    "liger_version": "0.6.4"
  }
]
**************************************
     BENCHMARKING MEMORY for LLAMA4_ROPE
**************************************
********** Benchmark Data **********
[
  {
    "kernel_name": "llama4_rope",
    "kernel_provider": "liger",
    "metric_name": "memory",
    "metric_unit": "MB",
    "gpu_name": "Ascend910B4",
    "x_name": "H",
    "x_label": "hidden size",
    "x_values": [
      512,
      2048,
      8192
    ],
    "y_values_50": [
      5.27099609375,
      21.02099609375,
      85.01953125
    ],
    "y_values_20": [
      5.27099609375,
      21.02099609375,
      85.01953125
    ],
    "y_values_80": [
      5.27099609375,
      21.02099609375,
      85.01953125
    ],
    "timestamp": "2026-01-22 06:11:37",
    "kernel_operation_mode": "full",
    "extra_benchmark_config_str": "{\"dtype\": \"torch.bfloat16\", \"seq_len\": 2048, \"num_q_heads\": 32, \"num_kv_heads\": 8}",
    "liger_version": "0.6.4"
  },
  {
    "kernel_name": "llama4_rope",
    "kernel_provider": "huggingface",
    "metric_name": "memory",
    "metric_unit": "MB",
    "gpu_name": "Ascend910B4",
    "x_name": "H",
    "x_label": "hidden size",
    "x_values": [
      512,
      2048,
      8192
    ],
    "y_values_50": [
      17.64697265625,
      70.52197265625,
      282.0224609375
    ],
    "y_values_20": [
      17.64697265625,
      70.52197265625,
      282.0224609375
    ],
    "y_values_80": [
      17.64697265625,
      70.52197265625,
      282.0224609375
    ],
    "timestamp": "2026-01-22 06:11:37",
    "kernel_operation_mode": "full",
    "extra_benchmark_config_str": "{\"dtype\": \"torch.bfloat16\", \"seq_len\": 2048, \"num_q_heads\": 32, \"num_kv_heads\": 8}",
    "liger_version": "0.6.4"
  }
]
**************************************
     BENCHMARKING SPEED for LLAMA4_ROPE
**************************************
********** Benchmark Data **********
[
  {
    "kernel_name": "llama4_rope",
    "kernel_provider": "liger",
    "metric_name": "speed",
    "metric_unit": "ms",
    "gpu_name": "Ascend910B4",
    "x_name": "T",
    "x_label": "sequence length",
    "x_values": [
      1024,
      2048,
      4096,
      8192,
      16384
    ],
    "y_values_50": [
      0.3733200132846832,
      0.5968999862670898,
      1.164039969444275,
      2.309580087661743,
      4.591000080108643
    ],
    "y_values_20": [
      0.3651079833507538,
      0.5960800051689148,
      1.1621440649032593,
      2.306756019592285,
      4.588043689727783
    ],
    "y_values_80": [
      0.3812679946422577,
      0.5977200269699097,
      1.1663399934768677,
      2.311511993408203,
      4.594267845153809
    ],
    "timestamp": "2026-01-22 06:11:39",
    "kernel_operation_mode": "forward",
    "extra_benchmark_config_str": "{\"dtype\": \"torch.bfloat16\", \"hidden_size\": 8192, \"num_q_heads\": 32, \"num_kv_heads\": 8}",
    "liger_version": "0.6.4"
  },
  {
    "kernel_name": "llama4_rope",
    "kernel_provider": "huggingface",
    "metric_name": "speed",
    "metric_unit": "ms",
    "gpu_name": "Ascend910B4",
    "x_name": "T",
    "x_label": "sequence length",
    "x_values": [
      1024,
      2048,
      4096,
      8192,
      16384
    ],
    "y_values_50": [
      0.3193399906158447,
      0.7357800006866455,
      1.5193400382995605,
      2.9961400032043457,
      5.828180313110352
    ],
    "y_values_20": [
      0.31828001141548157,
      0.7327200174331665,
      1.5162479877471924,
      2.9918880462646484,
      5.821280002593994
    ],
    "y_values_80": [
      0.32043999433517456,
      0.7396000027656555,
      1.5238399505615234,
      3.000056028366089,
      5.834980010986328
    ],
    "timestamp": "2026-01-22 06:11:41",
    "kernel_operation_mode": "forward",
    "extra_benchmark_config_str": "{\"dtype\": \"torch.bfloat16\", \"hidden_size\": 8192, \"num_q_heads\": 32, \"num_kv_heads\": 8}",
    "liger_version": "0.6.4"
  },
  {
    "kernel_name": "llama4_rope",
    "kernel_provider": "liger",
    "metric_name": "speed",
    "metric_unit": "ms",
    "gpu_name": "Ascend910B4",
    "x_name": "T",
    "x_label": "sequence length",
    "x_values": [
      1024,
      2048,
      4096,
      8192,
      16384
    ],
    "y_values_50": [
      0.4818900227546692,
      0.5988199710845947,
      1.1668599843978882,
      2.312299966812134,
      4.595779895782471
    ],
    "y_values_20": [
      0.47614797949790955,
      0.5978599786758423,
      1.1656999588012695,
      2.3102400302886963,
      4.592780113220215
    ],
    "y_values_80": [
      0.49437999725341797,
      0.6001200079917908,
      1.1683520078659058,
      2.3147799968719482,
      4.599740028381348
    ],
    "timestamp": "2026-01-22 06:11:44",
    "kernel_operation_mode": "backward",
    "extra_benchmark_config_str": "{\"dtype\": \"torch.bfloat16\", \"hidden_size\": 8192, \"num_q_heads\": 32, \"num_kv_heads\": 8}",
    "liger_version": "0.6.4"
  },
  {
    "kernel_name": "llama4_rope",
    "kernel_provider": "huggingface",
    "metric_name": "speed",
    "metric_unit": "ms",
    "gpu_name": "Ascend910B4",
    "x_name": "T",
    "x_label": "sequence length",
    "x_values": [
      1024,
      2048,
      4096,
      8192,
      16384
    ],
    "y_values_50": [
      0.8887799978256226,
      1.8043699264526367,
      3.431459903717041,
      6.22859001159668,
      15.008779525756836
    ],
    "y_values_20": [
      0.8820639848709106,
      1.7928200960159302,
      3.414016008377075,
      6.2163920402526855,
      14.94115161895752
    ],
    "y_values_80": [
      0.896776020526886,
      1.8147599697113037,
      3.4526920318603516,
      6.242807865142822,
      15.130931854248047
    ],
    "timestamp": "2026-01-22 06:11:46",
    "kernel_operation_mode": "backward",
    "extra_benchmark_config_str": "{\"dtype\": \"torch.bfloat16\", \"hidden_size\": 8192, \"num_q_heads\": 32, \"num_kv_heads\": 8}",
    "liger_version": "0.6.4"
  },
  {
    "kernel_name": "llama4_rope",
    "kernel_provider": "liger",
    "metric_name": "speed",
    "metric_unit": "ms",
    "gpu_name": "Ascend910B4",
    "x_name": "T",
    "x_label": "sequence length",
    "x_values": [
      1024,
      2048,
      4096,
      8192,
      16384
    ],
    "y_values_50": [
      1.053879976272583,
      1.1915199756622314,
      2.3213701248168945,
      4.623779773712158,
      9.188169479370117
    ],
    "y_values_20": [
      1.0393480062484741,
      1.1899800300598145,
      2.319200038909912,
      4.62037992477417,
      9.18437671661377
    ],
    "y_values_80": [
      1.069659948348999,
      1.193011999130249,
      2.323728084564209,
      4.62608003616333,
      9.192972183227539
    ],
    "timestamp": "2026-01-22 06:11:48",
    "kernel_operation_mode": "full",
    "extra_benchmark_config_str": "{\"dtype\": \"torch.bfloat16\", \"hidden_size\": 8192, \"num_q_heads\": 32, \"num_kv_heads\": 8}",
    "liger_version": "0.6.4"
  },
  {
    "kernel_name": "llama4_rope",
    "kernel_provider": "huggingface",
    "metric_name": "speed",
    "metric_unit": "ms",
    "gpu_name": "Ascend910B4",
    "x_name": "T",
    "x_label": "sequence length",
    "x_values": [
      1024,
      2048,
      4096,
      8192,
      16384
    ],
    "y_values_50": [
      1.4656000137329102,
      2.744339942932129,
      5.148839950561523,
      9.560999870300293,
      21.033218383789062
    ],
    "y_values_20": [
      1.432136058807373,
      2.6262240409851074,
      5.117976188659668,
      9.355159759521484,
      20.880983352661133
    ],
    "y_values_80": [
      1.4930200576782227,
      2.7709360122680664,
      5.1736040115356445,
      9.63263988494873,
      21.097015380859375
    ],
    "timestamp": "2026-01-22 06:11:51",
    "kernel_operation_mode": "full",
    "extra_benchmark_config_str": "{\"dtype\": \"torch.bfloat16\", \"hidden_size\": 8192, \"num_q_heads\": 32, \"num_kv_heads\": 8}",
    "liger_version": "0.6.4"
  }
]
**************************************
     BENCHMARKING MEMORY for LLAMA4_ROPE
**************************************
********** Benchmark Data **********
[
  {
    "kernel_name": "llama4_rope",
    "kernel_provider": "liger",
    "metric_name": "memory",
    "metric_unit": "MB",
    "gpu_name": "Ascend910B4",
    "x_name": "T",
    "x_label": "sequence length",
    "x_values": [
      1024,
      2048,
      4096,
      8192,
      16384
    ],
    "y_values_50": [
      42.013671875,
      85.01953125,
      170.0341796875,
      336.068359375,
      672.130859375
    ],
    "y_values_20": [
      42.013671875,
      85.01953125,
      170.0341796875,
      336.068359375,
      672.130859375
    ],
    "y_values_80": [
      42.013671875,
      85.01953125,
      170.0341796875,
      336.068359375,
      672.130859375
    ],
    "timestamp": "2026-01-22 06:11:51",
    "kernel_operation_mode": "full",
    "extra_benchmark_config_str": "{\"dtype\": \"torch.bfloat16\", \"hidden_size\": 8192, \"num_q_heads\": 32, \"num_kv_heads\": 8}",
    "liger_version": "0.6.4"
  },
  {
    "kernel_name": "llama4_rope",
    "kernel_provider": "huggingface",
    "metric_name": "memory",
    "metric_unit": "MB",
    "gpu_name": "Ascend910B4",
    "x_name": "T",
    "x_label": "sequence length",
    "x_values": [
      1024,
      2048,
      4096,
      8192,
      16384
    ],
    "y_values_50": [
      141.0146484375,
      282.0224609375,
      564.0380859375,
      1128.0693359375,
      2256.1318359375
    ],
    "y_values_20": [
      141.0146484375,
      282.0224609375,
      564.0380859375,
      1128.0693359375,
      2256.1318359375
    ],
    "y_values_80": [
      141.0146484375,
      282.0224609375,
      564.0380859375,
      1128.0693359375,
      2256.1318359375
    ],
    "timestamp": "2026-01-22 06:11:51",
    "kernel_operation_mode": "full",
    "extra_benchmark_config_str": "{\"dtype\": \"torch.bfloat16\", \"hidden_size\": 8192, \"num_q_heads\": 32, \"num_kv_heads\": 8}",
    "liger_version": "0.6.4"
  }
]

@Tcc0403 Tcc0403 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some nit changes about api and doc. tl.split exploration can be a follow-up PR.

Comment thread src/liger_kernel/ops/backends/_ascend/ops/llama4_rope.py Outdated
Comment thread src/liger_kernel/ops/backends/_ascend/ops/llama4_rope.py Outdated
Comment thread src/liger_kernel/ops/backends/_ascend/ops/llama4_rope.py Outdated
Comment thread src/liger_kernel/ops/backends/_ascend/ops/llama4_rope.py Outdated
Comment thread src/liger_kernel/ops/backends/_ascend/ops/llama4_rope.py
Comment on lines +113 to +117
# Interleaved offsets within a single head: [real0, imag0, real1, imag1, ...]
head_ptr = q_base + qh_idx[:, None] * q_head_stride
base = d_idx[None, :] * 2
q_real = tl.load(head_ptr + base, mask=block_mask, other=0.0)
q_imag = tl.load(head_ptr + base + 1, mask=block_mask, other=0.0)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to be in this PR, but we can try tl.split in follow-up PR for potential improvement.
https://triton-lang.org/main/python-api/generated/triton.language.split.html#triton.language.split

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried with tl.split, and is this what you expected.

Comment thread src/liger_kernel/ops/backends/_ascend/ops/llama4_rope.py Outdated
Comment on lines +108 to +130
for qh_block in range(0, n_qh, BLOCK_Q):
qh_idx = tl.arange(0, BLOCK_Q) + qh_block
qh_mask = qh_idx < n_qh
block_mask = qh_mask[:, None] & d_mask[None, :]

head_ptr = q_base + qh_idx[:, None] * q_head_stride
base = d_idx[None, :] * 2

lane = tl.arange(0, 2)[None, None, :]

q_pair = tl.load(
head_ptr[:, :, None] + base[:, :, None] + lane,
mask=block_mask[:, :, None],
other=0.0,
)

q_real, q_imag = tl.split(q_pair)

new_real = tl.math.fma(q_real, freqs_real, -(q_imag * freqs_imag))
new_imag = tl.math.fma(q_real, freqs_imag, q_imag * freqs_real)

tl.store(head_ptr + base, new_real, mask=block_mask)
tl.store(head_ptr + base + 1, new_imag, mask=block_mask)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes kind of, but I would also leverage tl.reshape, tl.split, tl.interleave to simplify the code while ensuring efficient memory access.

Here's what I would imagine for code structure: (modified from src/liger_kernel/ops/llama4_rope.py)

    # refactor to distinguish freq and qk access pattern
    # freq original shape: (sl, hd // 2)
    # freq tile shape: (padded_hd // 2) <- handle non-power-of-2 hidden size edge case
    freq_idx  = tl.arange(padded_hd // 2)
    freq_mask = freq_idx < hd // 2

    # q/k original shape: (bs, sl, n_heads, hd)
    # q/k tile shape: (BLOCK_Q, padded_hd) <- interleaved complex last-dim layout
    hd_idx = tl.arange(padded_hd)
    hd_mask = hd_idx < hd

    for qh_block in range(0, n_qh, BLOCK_Q):
        # Load real, imag pairs from q_head
        qh_idx = tl.arange(0, BLOCK_Q) + qh_block
        qh_mask = qh_idx < n_qh
        block_mask = qh_mask[:, None] & hd_mask[None, :]

       # tile shape: (Q_BLOCK, padded_hd)
        q_pair = tl.load(
            q_base + qh_idx * q_head_stride + hd_idx,
            mask=block_mask,
            other=0.0,
        )
        # reshape: (Q_BLOCK, padded_hd) -> (Q_BLOCK, padded_hd // 2, 2) 
        # I'm not sure if `can_reorder` would affect the correctness and performance, need investigation
        q_pair = q_pair.reshape(Q_BLOCK, padded_hd // 2, 2, can_reorder=True)
        q_real, q_imag = tl.split(q_pair)

        new_real = tl.math.fma(q_real, freqs_real, -(q_imag * freqs_imag))
        new_imag = tl.math.fma(q_real, freqs_imag, q_imag * freqs_real)

        # interleave new_q_real, new_q_imag to reconstruct new_q for 1 coalesced store as well
        new_q_pair = tl.interleave(new_real, new_imag)
        tl.store(q_base + qh_idx * q_head_stride + hd_idx, new_q_pair, mask=block_mask)

    # Same for k
    ...

note: I don't have npu access, so I can't gaurantee the correctness and performance gain. The code probably needs some modification to make it work on your machine.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really appreciate your professional and dedicated guidance. This part has already been refactored and tested.

Comment on lines +8 to +47
def _prepare_freqs(freqs_cis: torch.Tensor, seq_len: int, head_dim_half: int):
"""
Canonicalize freqs to (seq_len, head_dim_half) real/imag tensors.

Supports:
- complex freqs: (..., head_dim_half) complex -> real/imag
- packed freqs: (..., 2*head_dim_half) real -> split into real/imag
"""
if freqs_cis.is_complex():
freqs_real = freqs_cis.real
freqs_imag = freqs_cis.imag
else:
if freqs_cis.shape[-1] == 2 * head_dim_half:
freqs_real = freqs_cis[..., :head_dim_half]
freqs_imag = freqs_cis[..., head_dim_half:]
else:
raise ValueError(
f"Unexpected freqs_cis shape for non-complex input: {freqs_cis.shape}, "
f"expected last dim = {2 * head_dim_half}"
)

if freqs_real.shape[-1] != head_dim_half:
raise ValueError(f"Unexpected last dim for freqs: {freqs_real.shape[-1]} (expected {head_dim_half})")

# Flatten leading dims -> (N, head_dim_half)
freqs_real = freqs_real.reshape(-1, head_dim_half)
freqs_imag = freqs_imag.reshape(-1, head_dim_half)

# Broadcast/slice to (seq_len, head_dim_half)
if freqs_real.shape[0] < seq_len:
if freqs_real.shape[0] == 1:
freqs_real = freqs_real.expand(seq_len, -1)
freqs_imag = freqs_imag.expand(seq_len, -1)
else:
raise ValueError(f"Insufficient rows in freqs: {freqs_real.shape[0]} < seq_len={seq_len}")
elif freqs_real.shape[0] > seq_len:
freqs_real = freqs_real[:seq_len]
freqs_imag = freqs_imag[:seq_len]

return freqs_real, freqs_imag

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, _prepare_freqs should be able to fuse into kernel similarly to cut host-side .reshape() cost.

However, I strongly suggest putting such change in another PR as a follow-up, so we can have a workable baseline and optimize upon it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completely agree — this can be implemented as another PR after the current one is merged.

Comment thread src/liger_kernel/ops/backends/_ascend/ops/llama4_rope.py Outdated
Comment thread test/transformers/test_llama4_rope.py
Comment on lines +101 to +102
hd_idx = tl.arange(0, hd)
hd_mask = hd_idx < (hd)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this kernel work where hidden_size is not a power of two?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it does. There is no requirement for hidden_size to be padded to a power of two.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In triton-ascend tl.arange(), I thought start and end still have to be power of two. If it works without any issues, we can ignore it.

https://ascend.github.io/triton-ascend/sources/python-api/generated/triton.language.arange.html#triton.language.arange

@Tcc0403 Tcc0403 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Feel free create an issue regarding frequencies fusion and work on it

@Tcc0403
Tcc0403 merged commit 0ea0b8f into linkedin:main Jan 27, 2026
3 of 7 checks passed
@lowdy1

lowdy1 commented Jan 29, 2026

Copy link
Copy Markdown
Contributor Author

Thank you! Feel free create an issue regarding frequencies fusion and work on it

For sure. But it seems that we can not pass a torch.complex tensor(freqs_cis) into a triton kernel directly?

@Tcc0403

Tcc0403 commented Jan 29, 2026

Copy link
Copy Markdown
Collaborator

For sure. But it seems that we can not pass a torch.complex tensor(freqs_cis) into a triton kernel directly?

Try torch.vew_as_real() before passing it into a triton code?

It's more of an exploration of improvement. It's totally fine if it turns out not available for this approach.

Tcc0403 pushed a commit that referenced this pull request Feb 9, 2026
## Summary
This PR is a descendant of
#1035
It removes `_prepare_freqs` for simplicity and directly uses a single
`freq_complex_ptr `for llama4_rope frequencies inside the Triton kernel.
By avoiding extra preprocessing and reducing load, this approach
simplifies the code path and improves performance. Benchmark results
show better performance compared to the previous implementation.

## Testing Done
Test done with `python -m pytest ./test/transformers/test_llama4_rope.py
-v`
Verified on Atlas 800I A2

- [ ] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
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.

3 participants