Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / components / functional / rotary_embedding.py: 23%

100 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-11-15 07:18 +0800

1# Copyright 2026 Huawei Technologies Co., Ltd 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14# ============================================================================ 

15"""NPU-accelerated Transformers-compatible rotary position embeddings.""" 

16 

17from __future__ import annotations 

18 

19import numpy as np 

20import torch # pylint: disable=forbidden-backend-import 

21import torch_npu 

22 

23 

24def apply_rotary_pos_emb( 

25 q: torch.Tensor, 

26 k: torch.Tensor, 

27 cos: torch.Tensor, 

28 sin: torch.Tensor, 

29 unsqueeze_dim: int = 1, 

30) -> tuple[torch.Tensor, torch.Tensor]: 

31 """Apply NPU RoPE using the Transformers ``apply_rotary_pos_emb`` contract. 

32 

33 Args: 

34 q: Query tensor. 

35 k: Key tensor. 

36 cos: Cosine part of the rotary embedding. 

37 sin: Sine part of the rotary embedding. 

38 unsqueeze_dim: Dimension used to make ``cos`` and ``sin`` broadcastable 

39 to ``q`` and ``k``. 

40 

41 Returns: 

42 Rotated query and key tensors with the same shapes as the inputs. 

43 

44 Raises: 

45 ValueError: If the rotary dimension does not cover the complete Q/K 

46 head dimension supported by the NPU operator interface. 

47 """ 

48 if cos.ndim == 2: 

49 cos = cos.unsqueeze(0) 

50 sin = sin.unsqueeze(0) 

51 if cos.shape[0] == 1 and q.shape[0] > 1: 

52 cos = cos.expand(q.shape[0], -1, -1) 

53 sin = sin.expand(q.shape[0], -1, -1) 

54 cos = cos.unsqueeze(unsqueeze_dim) 

55 sin = sin.unsqueeze(unsqueeze_dim) 

56 rotary_dim = cos.shape[-1] 

57 if rotary_dim != q.shape[-1] or rotary_dim != k.shape[-1]: 

58 raise ValueError( 

59 "NPU RoPE requires the rotary dimension to equal the Q/K head dimension" 

60 ) 

61 q_embed = torch_npu.npu_rotary_mul(q.clone(), cos, sin, rotary_mode="half") 

62 k_embed = torch_npu.npu_rotary_mul(k.clone(), cos, sin, rotary_mode="half") 

63 return q_embed, k_embed 

64 

65 

66def _apply_interleaved_rope( 

67 tensor: torch.Tensor, 

68 pass_through: torch.Tensor, 

69 cos: torch.Tensor, 

70 sin: torch.Tensor, 

71 unsqueeze_dim: int, 

72) -> torch.Tensor: 

73 """Apply interleaved RoPE and append dimensions that bypass rotation.""" 

74 sequence_first = ( 

75 tensor.permute(2, 0, 1, 3) 

76 if unsqueeze_dim == 1 

77 else tensor.permute(1, 0, 2, 3) 

78 ) 

79 seq_length, batch_size, num_heads, head_dim = sequence_first.shape 

80 if batch_size > 1 and seq_length > 1: 

81 rotated = torch_npu.npu_rotary_mul( 

82 sequence_first.reshape(batch_size * seq_length, 1, num_heads, head_dim), 

83 cos.reshape(batch_size * seq_length, 1, 1, head_dim), 

84 sin.reshape(batch_size * seq_length, 1, 1, head_dim), 

85 rotary_mode="interleave", 

86 ).reshape(seq_length, batch_size, num_heads, head_dim) 

87 else: 

88 rotated = torch_npu.npu_rotary_mul( 

89 sequence_first.clone(), 

90 cos.unsqueeze(-2), 

91 sin.unsqueeze(-2), 

92 rotary_mode="interleave", 

93 ) 

94 rotated = rotated.permute(1, 2, 0, 3) 

95 # Transpose pairs instead of strided slices to avoid scattered writes in backward. 

96 rotated = rotated.unflatten(-1, (-1, 2)).transpose(-1, -2).flatten(-2) 

97 if unsqueeze_dim == 2: 

98 rotated = rotated.transpose(1, 2) 

99 return torch.cat((rotated, pass_through), dim=-1) if pass_through.shape[-1] else rotated 

100 

101 

102def apply_rotary_pos_emb_interleave( 

103 q: torch.Tensor, 

104 k: torch.Tensor, 

105 cos: torch.Tensor, 

106 sin: torch.Tensor, 

107 position_ids: torch.Tensor | None = None, 

108 unsqueeze_dim: int = 1, 

109) -> tuple[torch.Tensor, torch.Tensor]: 

110 """Apply NPU RoPE using Transformers' interleaved RoPE contract. 

111 

112 Args: 

113 q: Interleaved-pair query tensor. 

114 k: Interleaved-pair key tensor. 

115 cos: Transformers ``cat(freqs, freqs)`` cosine tensor. 

116 sin: Sine tensor with the same shape and dtype as ``cos``. 

117 position_ids: Accepted for Transformers API compatibility. 

118 unsqueeze_dim: Dimension used to make ``cos`` and ``sin`` broadcastable 

119 to ``q`` and ``k``. 

120 

121 Returns: 

122 Rotated query and key tensors with the same shapes as the inputs. 

123 

124 Raises: 

125 ValueError: If ``unsqueeze_dim`` or the rotary dimension is unsupported 

126 by the NPU interleave kernel. 

127 """ 

128 del position_ids 

129 if unsqueeze_dim not in (1, 2): 

130 raise ValueError("NPU interleaved RoPE supports unsqueeze_dim 1 or 2") 

131 if cos.ndim == 2: 

132 cos = cos.unsqueeze(0) 

133 sin = sin.unsqueeze(0) 

134 rotary_dim = cos.shape[-1] 

135 if rotary_dim % 2: 

136 raise ValueError("interleaved RoPE requires an even rotary dimension") 

137 if rotary_dim > q.shape[-1] or rotary_dim > k.shape[-1]: 

138 raise ValueError("RoPE frequencies cannot exceed the Q/K head dimension") 

139 q_rot, q_pass = q.split((rotary_dim, q.shape[-1] - rotary_dim), dim=-1) 

140 k_rot, k_pass = k.split((rotary_dim, k.shape[-1] - rotary_dim), dim=-1) 

141 

142 # Transformers supplies cat(freqs, freqs). The NPU interleave mode expects 

143 # each frequency next to the corresponding real/imaginary input pair. 

144 cos = cos[..., : rotary_dim // 2].repeat_interleave(2, dim=-1) 

145 sin = sin[..., : rotary_dim // 2].repeat_interleave(2, dim=-1) 

146 if cos.shape[0] == 1 and q.shape[0] > 1: 

147 cos = cos.expand(q.shape[0], -1, -1) 

148 sin = sin.expand(q.shape[0], -1, -1) 

149 cos = cos.transpose(0, 1) 

150 sin = sin.transpose(0, 1) 

151 

152 return ( 

153 _apply_interleaved_rope(q_rot, q_pass, cos, sin, unsqueeze_dim), 

154 _apply_interleaved_rope(k_rot, k_pass, cos, sin, unsqueeze_dim), 

155 ) 

156 

157 

158def _rotate_half( 

159 x: torch.Tensor, 

160 rotary_interleaved: bool = False, 

161) -> torch.Tensor: 

162 """Rotate the last dimension of ``x`` for rotary position embedding. 

163 

164 Args: 

165 x: Input tensor whose last dimension is the rotary dimension. 

166 rotary_interleaved: If ``True``, rotate adjacent even/odd pairs 

167 (interleaved layout). If ``False``, split the last dimension 

168 into two halves and rotate across them (half layout). 

169 

170 Returns: 

171 A tensor with the same shape and dtype as ``x``. 

172 """ 

173 if not rotary_interleaved: 

174 x1, x2 = torch.chunk(x, 2, dim=-1) 

175 return torch.cat((-x2, x1), dim=-1) 

176 

177 dim = x.shape[-1] 

178 index1 = np.ones(dim) 

179 index1[::2] = 0 

180 index2 = np.zeros(dim) 

181 index2[::2] = -1 

182 rotation_matrix = np.eye(dim, k=1) * index1 + np.eye(dim, k=-1) * index2 

183 rotation_matrix = torch.from_numpy(rotation_matrix[None, None, :, :]).to(x.dtype).to(x.device) 

184 return torch.matmul(x, rotation_matrix) 

185 

186 

187def apply_rotary_pos_emb_single( 

188 t: torch.Tensor, 

189 cos: torch.Tensor, 

190 sin: torch.Tensor, 

191 rotary_interleaved: bool = False, 

192 use_fused_rotary_pos_emb: bool = False, 

193) -> torch.Tensor: 

194 """Apply interleaved RoPE and append dimensions that bypass rotation. 

195 

196 Args: 

197 tensor: Tensor slice to rotate (the rotary part of Q or K). 

198 pass_through: Tensor slice that bypasses rotation, concatenated 

199 back after the rotated part. 

200 cos: Cosine tensor for the interleaved rotary mode. 

201 sin: Sine tensor for the interleaved rotary mode. 

202 unsqueeze_dim: Dimension used to make ``cos`` and ``sin`` 

203 broadcastable to ``tensor``. Must be 1 or 2. 

204 

205 Returns: 

206 A tensor of shape ``..., rotary_dim + pass_through_dim`` formed by 

207 concatenating the rotated part and ``pass_through`` along the last 

208 dimension. 

209 """ 

210 rot_dim = cos.shape[-1] 

211 t_dim = t.shape[-1] 

212 t_pass = None 

213 if rot_dim != t_dim: 

214 t, t_pass = t[..., :rot_dim], t[..., rot_dim:] 

215 

216 cos = cos.to(t.dtype) 

217 sin = sin.to(t.dtype) 

218 

219 while cos.ndim < t.ndim: 

220 cos = cos.unsqueeze(-2) 

221 sin = sin.unsqueeze(-2) 

222 

223 if use_fused_rotary_pos_emb and t.device.type != "cpu": 

224 rotary_mode = "interleave" if rotary_interleaved else "half" 

225 sequence_length, batch_size, num_heads, head_dim = t.shape 

226 if rotary_interleaved and batch_size > 1 and sequence_length > 1: 

227 t = torch_npu.npu_rotary_mul( 

228 t.reshape(batch_size * sequence_length, 1, num_heads, head_dim), 

229 cos.reshape(batch_size * sequence_length, 1, cos.shape[-2], cos.shape[-1]), 

230 sin.reshape(batch_size * sequence_length, 1, sin.shape[-2], sin.shape[-1]), 

231 rotary_mode=rotary_mode, 

232 ).reshape(sequence_length, batch_size, num_heads, head_dim) 

233 else: 

234 t = torch_npu.npu_rotary_mul(t.clone(), cos, sin, rotary_mode=rotary_mode) 

235 else: 

236 t = (t * cos) + (_rotate_half(t, rotary_interleaved) * sin) 

237 

238 if rot_dim != t_dim: 

239 return torch.cat((t, t_pass), dim=-1) 

240 return t 

241 

242 

243def apply_rotary_pos_emb_single_freqs( 

244 t: torch.Tensor, 

245 freqs: torch.Tensor, 

246 rotary_interleaved: bool = False, 

247) -> torch.Tensor: 

248 """Apply rotary embeddings from raw frequency values. 

249 

250 Args: 

251 t: Input tensor to rotate. 

252 freqs: Frequency tensor whose last dimension is the rotary 

253 dimension. ``cos(freqs)`` and ``sin(freqs)`` are used 

254 internally. 

255 rotary_interleaved: If ``True``, use interleaved rotary mode; 

256 otherwise use half rotary mode. 

257 

258 Returns: 

259 A tensor with the same shape as ``t``. If ``freqs.shape[-1]`` is 

260 smaller than ``t.shape[-1]``, the remaining dimensions of ``t`` 

261 are passed through unchanged and concatenated back. 

262 """ 

263 rot_dim = freqs.shape[-1] 

264 t_dim = t.shape[-1] 

265 t_pass = None 

266 if rot_dim != t_dim: 

267 t, t_pass = t[..., :rot_dim], t[..., rot_dim:] 

268 

269 cos_ = torch.cos(freqs).to(t.dtype) 

270 sin_ = torch.sin(freqs).to(t.dtype) 

271 

272 rotary_mode = "interleave" if rotary_interleaved else "half" 

273 t_copy = t.clone() 

274 t = torch_npu.npu_rotary_mul(t_copy, cos_, sin_, rotary_mode=rotary_mode) 

275 t_copy.untyped_storage().resize_(0) 

276 

277 if t_pass is not None: 

278 return torch.cat((t, t_pass), dim=-1) 

279 return t