Coverage for /opt/cloud/slavespace/usr1/096471637100f3de0fcfc01072822a80/dttest/api/python/llm_datadist_v1/llm_utils.py: 22%

236 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-27 10:02 +0800

1#!/usr/bin/env python3 

2# -*- coding: utf-8 -*- 

3# ----------------------------------------------------------------------------------------------------------- 

4# Copyright (c) 2025 Huawei Technologies Co., Ltd. 

5# This program is free software, you can redistribute it and/or modify it under the terms and conditions of 

6# CANN Open Software License Agreement Version 2.0 (the "License"). 

7# Please refer to the License for details. You may not use this file except in compliance with the License. 

8# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, 

9# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. 

10# See LICENSE in the root of the software repository for the full text of the License. 

11# ----------------------------------------------------------------------------------------------------------- 

12from dataclasses import dataclass 

13from threading import Thread 

14from typing import Dict, List, Optional, Tuple, Union 

15 

16from llm_datadist_v1 import llm_wrapper 

17 

18from .data_type import DataType, python_dtype_2_dwrapper_dtype 

19from .llm_types import ( 

20 BlocksCacheKey, 

21 CacheDesc, 

22 CacheKey, 

23 CacheKeyByIdAndIndex, 

24 CacheTask, 

25 KvCache, 

26 LayerSynchronizer, 

27 MemInfo, 

28 Placement, 

29 PushType, 

30 TransferConfig, 

31 TransferWithCacheKeyConfig, 

32) 

33from .status import ( 

34 LLMException, 

35 LLMStatusCode, 

36 code_2_status, 

37 raise_if_false, 

38 raise_if_true, 

39) 

40from .utils import log 

41from .utils.utils import ( 

42 check_isinstance, 

43 check_list_uint64, 

44 check_type, 

45 check_uint32, 

46 check_uint64, 

47) 

48 

49# UINT64_MAX 

50_INVALID_ID = 2**64 - 1 

51_MAX_DISPLAYED_REQ_ID_COUNT = 8 

52_NUM_TENSORS_PER_LAYER = 2 

53 

54 

55def clone_cache_desc(cache_desc: CacheDesc) -> CacheDesc: 

56 return CacheDesc( 

57 cache_desc.num_tensors, 

58 cache_desc.shape[:], 

59 cache_desc.data_type, 

60 cache_desc.placement, 

61 cache_desc.batch_dim, 

62 cache_desc.seq_len_dim_index, 

63 cache_desc.kv_tensor_format, 

64 ) 

65 

66 

67def verify_cache_shape(shape: List[int]) -> None: 

68 raise_if_false(len(shape) > 0, "scalar is not supported") 

69 raise_if_false(0 not in shape, f"empty dimension is not supported, shape = {shape}") 

70 for dim in shape: 

71 if dim < -1: 

72 raise LLMException(f"dim {dim} is not supported, shape = {shape}") 

73 

74 

75def to_data_type(data_type_str: str) -> DataType: 

76 for dtype in DataType: 

77 if str(dtype.value) == data_type_str: 

78 return dtype 

79 raise LLMException( 

80 f"unsupported dtype: {data_type_str}", 

81 status_code=LLMStatusCode.LLM_PARAM_INVALID, 

82 ) 

83 

84 

85def is_invalid_id(req_id_or_prefix_id: int) -> bool: 

86 return (req_id_or_prefix_id < 0) or (req_id_or_prefix_id == _INVALID_ID) 

87 

88 

89def is_valid_id(req_id_or_prefix_id: int) -> bool: 

90 return not is_invalid_id(req_id_or_prefix_id) 

91 

92 

93def calc_tensor_size(shape: List[int], data_type: DataType) -> int: 

94 ge_data_type = python_dtype_2_dwrapper_dtype.get(data_type) 

95 tensor_size = llm_wrapper.calc_tensor_size(shape, ge_data_type) 

96 raise_if_false( 

97 tensor_size >= 0, 

98 "Failed to calc tensor size, shape = {0}, data_type = {1}", 

99 shape, 

100 data_type, 

101 ) 

102 return tensor_size 

103 

104 

105class CacheDescParser(object): 

106 @staticmethod 

107 def parse_by_options(options: Dict[str, str]) -> CacheDesc: 

108 raise_if_false("llm.RefInputShapes" in options, "llm.RefInputShapes not set") 

109 kv_shape_str = options["llm.RefInputShapes"] 

110 kv_shapes = kv_shape_str.split(";") 

111 raise_if_false( 

112 len(set(kv_shapes)) == 1, 

113 f"zero or multiple different shapes is not supported, llm.RefInputShapes = {kv_shapes}", 

114 ) 

115 

116 raise_if_false("llm.RefInputDtypes" in options, "llm.RefInputDtypes not set") 

117 kv_data_type_str = options["llm.RefInputDtypes"] 

118 kv_data_types = kv_data_type_str.split(";") 

119 raise_if_false( 

120 len(set(kv_data_types)) == 1, 

121 f"zero or multiple different data types is not supported, llm.RefInputDtypes = {kv_data_types}", 

122 ) 

123 raise_if_false( 

124 len(kv_shapes) == len(kv_data_types), 

125 f"kv_shapes num ({len(kv_shapes)}) mismatches that of kv_data_type ({len(kv_data_types)})", 

126 ) 

127 kv_shape = list((int(dim) for dim in kv_shapes[0].split(","))) 

128 verify_cache_shape(kv_shape) 

129 if "llm.RefInputSeqLenDimIndex" in options: 

130 seq_len_dim_index = int(options["llm.RefInputSeqLenDimIndex"]) 

131 log.info(f"get seq_len_dim_index from option, value = {seq_len_dim_index}") 

132 raise_if_false( 

133 0 <= seq_len_dim_index < len(kv_shape), 

134 f"seq_len_dim_index ({seq_len_dim_index}) out of range, kv_shape = {kv_shape}", 

135 ) 

136 elif -1 in kv_shape[1:]: 

137 raise_if_false( 

138 kv_shape[1:].count(-1) == 1, 

139 f"can only have one dynamic dim apart from batch_dim(0), " 

140 f"but got {kv_shape[1:].count(-1)}, shape = {kv_shape}", 

141 ) 

142 seq_len_dim_index = kv_shape[1:].index(-1) + 1 

143 log.info(f"seq_len_dim_index inferred by shape, value = {seq_len_dim_index}") 

144 else: 

145 seq_len_dim_index = -1 

146 log.info( 

147 f"llm.RefInputSeqLenDimIndex not set and can not infer by shape,seq_len_dim_index = {seq_len_dim_index}" 

148 ) 

149 kv_tensor_format = options.get("llm.kvTensorFormat", None) 

150 kv_data_type = to_data_type(kv_data_types[0]) 

151 cache_desc = CacheDesc( 

152 len(kv_shapes), 

153 kv_shape, 

154 kv_data_type, 

155 Placement.DEVICE, 

156 0, 

157 seq_len_dim_index, 

158 kv_tensor_format, 

159 ) 

160 log.info(f"parse cache_desc from option, value = {cache_desc}") 

161 return cache_desc 

162 

163 

164def build_npu_tensors(kv_cache: KvCache, device_index: int = 0) -> List[int]: 

165 return llm_wrapper.build_npu_tensors( 

166 kv_cache.cache_desc.shape, 

167 kv_cache.cache_desc.data_type.value, 

168 kv_cache.cache_desc.size, 

169 kv_cache.per_device_tensor_addrs[device_index], 

170 ) 

171 

172 

173def pack_cache_desc(cache_desc: CacheDesc) -> Tuple[int, int, int, List[int], int, int]: 

174 return ( 

175 cache_desc.num_tensors, 

176 cache_desc.data_type.value, 

177 cache_desc.seq_len_dim_index, 

178 cache_desc.shape, 

179 cache_desc.placement.value, 

180 cache_desc._is_blocks, 

181 ) 

182 

183 

184def pack_cache_key(cache_key: CacheKey) -> Tuple[int, int, int, int, int, int, bool]: 

185 return ( 

186 cache_key.prompt_cluster_id, 

187 -1, 

188 0, 

189 cache_key.req_id, 

190 cache_key.prefix_id, 

191 cache_key.model_id, 

192 False, 

193 ) 

194 

195 

196def pack_cache_key_by_id( 

197 cache_key: CacheKeyByIdAndIndex, 

198) -> Tuple[int, int, int, int, int, int, bool]: 

199 return ( 

200 cache_key.cluster_id, 

201 cache_key.cache_id, 

202 cache_key.batch_index, 

203 _INVALID_ID, 

204 _INVALID_ID, 

205 0, 

206 False, 

207 ) 

208 

209 

210def pack_block_cache_key( 

211 cache_key: BlocksCacheKey, 

212) -> Tuple[int, int, int, int, int, int, bool]: 

213 return ( 

214 cache_key.prompt_cluster_id, 

215 -1, 

216 0, 

217 _INVALID_ID, 

218 _INVALID_ID, 

219 cache_key.model_id, 

220 True, 

221 ) 

222 

223 

224def pack_mem_info(mem_info: MemInfo) -> Tuple[int, int, int]: 

225 return (mem_info.mem_type.value, mem_info.addr, mem_info.size) 

226 

227 

228@dataclass 

229class TransferCacheParameters: 

230 src_cache: "Union[Cache, KvCache]" 

231 transfer_configs: Union[ 

232 List[Union[TransferConfig, TransferWithCacheKeyConfig]], 

233 Tuple[Union[TransferConfig, TransferWithCacheKeyConfig]], 

234 ] 

235 src_block_indices: Optional[List[int]] = None 

236 dst_block_indices: Optional[List[int]] = None 

237 dst_block_memory_size: Optional[int] = None 

238 

239 

240class TransferCacheJob: 

241 task_id = 0 

242 

243 def __init__( 

244 self, 

245 params: TransferCacheParameters, 

246 layer_synchronizer: LayerSynchronizer, 

247 transfer_cache_func, 

248 ): 

249 self._transfer_configs = params.transfer_configs 

250 self._src_block_indices = params.src_block_indices 

251 self._dst_block_indices = params.dst_block_indices 

252 self._dst_block_memory_size = params.dst_block_memory_size 

253 self._cache_id = params.src_cache.cache_id 

254 self._cache_desc = params.src_cache.cache_desc 

255 self._num_layers = 0 

256 self._rets: Dict[int, LLMStatusCode] = {} 

257 self._timeout_in_millis: Optional[int] = None 

258 self._layer_synchronizer = layer_synchronizer 

259 self._transfer_cache_func = transfer_cache_func 

260 

261 def init(self): 

262 raise_if_false( 

263 self._cache_desc.num_tensors % 2 == 0, 

264 "cache_desc.num_tensors ({0}) is not even", 

265 self._cache_desc.num_tensors, 

266 ) 

267 self._num_layers = self._cache_desc.num_tensors // 2 

268 for transfer_config in self._transfer_configs: 

269 if transfer_config.src_layer_range is None: 

270 transfer_config.src_layer_range = range(0, self._num_layers) 

271 self.check_transfer_config(transfer_config) 

272 

273 def transfer_layers(self): 

274 for layer_i, src_layer_index in enumerate(range(self._num_layers)): 

275 to_transfer = [config for config in self._transfer_configs if src_layer_index in config.src_layer_range] 

276 if not to_transfer: 

277 log.info("src_layer %d sends to no destination", src_layer_index) 

278 continue 

279 if not self._layer_synchronizer.synchronize_layer(src_layer_index, self._timeout_in_millis): 

280 log.error(f"Failed to synchronize layer {src_layer_index}") 

281 for config in to_transfer: 

282 # 错误码待添加 

283 self._rets[config.dst_cluster_id] = LLMStatusCode.LLM_PARAM_INVALID 

284 return 

285 for config in to_transfer: 

286 dst_layer_index = ( 

287 src_layer_index if config.dst_layer_range is None else list(config.dst_layer_range)[layer_i] 

288 ) 

289 ret = self.transfer_layer(src_layer_index, dst_layer_index, config) 

290 if ret != LLMStatusCode.LLM_SUCCESS: 

291 log.error(f"Failed to transfer layer {src_layer_index} to dst_cluster_id={config.dst_cluster_id}") 

292 self._rets[config.dst_cluster_id] = ret 

293 return 

294 log.info(f"transfer layer {src_layer_index} to dst_cluster_id={config.dst_cluster_id} success") 

295 if src_layer_index == config.src_layer_range.stop - 1: 

296 self._rets[config.dst_cluster_id] = LLMStatusCode.LLM_SUCCESS 

297 log.info(f"transfer all layers to dst_cluster_id={config.dst_cluster_id} finished") 

298 log.info("transfer all layers finished") 

299 

300 def transfer_layer( 

301 self, 

302 src_layer_index: int, 

303 dst_layer_idx, 

304 transfer_config: Union[TransferConfig, TransferWithCacheKeyConfig], 

305 ) -> LLMStatusCode: 

306 if check_type(transfer_config, TransferConfig): 

307 dst_layer_index = src_layer_index - transfer_config.src_layer_range.start 

308 dst_addrs = transfer_config.dst_addrs[ 

309 dst_layer_index * _NUM_TENSORS_PER_LAYER : dst_layer_index * _NUM_TENSORS_PER_LAYER 

310 + _NUM_TENSORS_PER_LAYER 

311 ] 

312 transfer_config = ( 

313 self._cache_id, 

314 transfer_config.src_batch_index, 

315 src_layer_index, 

316 dst_addrs, 

317 transfer_config.dst_cluster_id, 

318 0, 

319 0, 

320 PushType.NO_CACHE_KEY.value, 

321 src_layer_index, 

322 2, 

323 ) 

324 else: 

325 if check_type(transfer_config.cache_key, BlocksCacheKey): 

326 transfer_config = ( 

327 self._cache_id, 

328 0, 

329 src_layer_index, 

330 [], 

331 transfer_config.cache_key.cluster_id, 

332 transfer_config.cache_key.model_id, 

333 0, 

334 PushType.BLOCKS_CACHE_KEY.value, 

335 dst_layer_idx, 

336 2, 

337 ) 

338 elif check_type(transfer_config.cache_key, CacheKeyByIdAndIndex): 

339 transfer_config = ( 

340 self._cache_id, 

341 transfer_config.src_batch_index, 

342 src_layer_index, 

343 [], 

344 transfer_config.cache_key.cluster_id, 

345 transfer_config.cache_key.cache_id, 

346 transfer_config.cache_key.batch_index, 

347 PushType.CACHE_KEY_BY_ID.value, 

348 dst_layer_idx, 

349 2, 

350 ) 

351 block_config = ( 

352 self._dst_block_memory_size if self._dst_block_memory_size is not None else 0, 

353 self._src_block_indices if self._src_block_indices is not None else [], 

354 self._dst_block_indices if self._dst_block_indices is not None else [], 

355 ) 

356 ret = self._transfer_cache_func(TransferCacheJob.task_id, transfer_config, block_config) 

357 TransferCacheJob.task_id += 1 

358 return code_2_status(ret) 

359 

360 def check_transfer_config(self, transfer_config: Union[TransferConfig, TransferWithCacheKeyConfig]): 

361 if self._src_block_indices: 

362 raise_if_false( 

363 transfer_config.src_batch_index == 0, 

364 "Invalid TransferConfig, src_batch_index ({0}) != 0 while src is blocks", 

365 transfer_config.src_batch_index, 

366 ) 

367 raise_if_false( 

368 0 <= transfer_config.src_batch_index < self._cache_desc.batch_size, 

369 "Invalid TransferConfig, src_batch_index ({0}) out of range: [0, {1})", 

370 transfer_config.src_batch_index, 

371 self._cache_desc.batch_size, 

372 ) 

373 raise_if_false( 

374 0 <= transfer_config.src_layer_range.start < transfer_config.src_layer_range.stop <= self._num_layers, 

375 "src_layer_range: {0} out of range, src_layer_num = {1}", 

376 transfer_config.src_layer_range, 

377 self._num_layers, 

378 ) 

379 num_tensors_to_transfer = ( 

380 transfer_config.src_layer_range.stop - transfer_config.src_layer_range.start 

381 ) * _NUM_TENSORS_PER_LAYER 

382 if check_type(transfer_config, TransferConfig): 

383 raise_if_false( 

384 len(transfer_config.dst_addrs) == num_tensors_to_transfer, 

385 "expect {0} dst_addrs, but len(dst_addrs) = {1}, range = {2}", 

386 num_tensors_to_transfer, 

387 len(transfer_config.dst_addrs), 

388 transfer_config.src_layer_range, 

389 ) 

390 

391 def get_results(self) -> List[LLMStatusCode]: 

392 rets = [] 

393 for config in self._transfer_configs: 

394 ret = self._rets.get(config.dst_cluster_id) 

395 rets.append(ret) 

396 return rets 

397 

398 def num_transfer_configs(self): 

399 return len(self._transfer_configs) 

400 

401 

402class TransferAsyncThread(Thread): 

403 def __init__(self, transfer_job: TransferCacheJob, default_err_code=LLMStatusCode.LLM_TIMEOUT): 

404 super().__init__() 

405 self._transfer_job = transfer_job 

406 self._rets = [] 

407 self._default_err_code = default_err_code 

408 

409 def run(self): 

410 self._transfer_job.transfer_layers() 

411 

412 def get_results(self, timeout) -> List[LLMStatusCode]: 

413 self.join(timeout) 

414 if self.is_alive(): 

415 return [self._default_err_code] * self._transfer_job.num_transfer_configs() 

416 return self._transfer_job.get_results() 

417 

418 def get(self, timeout) -> LLMStatusCode: 

419 self.join(timeout) 

420 if self.is_alive(): 

421 return self._default_err_code 

422 rets = self._transfer_job.get_results() 

423 for ret in rets: 

424 if ret != LLMStatusCode.LLM_SUCCESS and ret is not None: 

425 return ret 

426 return LLMStatusCode.LLM_SUCCESS 

427 

428 

429def _check_block_indices(arg_name, arg_value): 

430 if arg_value is not None: 

431 check_isinstance(arg_name, arg_value, [list, tuple], int) 

432 check_list_uint64(arg_name, arg_value) 

433 

434 

435def transfer_cache_async( 

436 params: TransferCacheParameters, 

437 layer_synchronizer: LayerSynchronizer, 

438 transfer_cache_func, 

439 default_error_code=LLMStatusCode.LLM_TIMEOUT, 

440 enable_remote_cache=False, 

441) -> CacheTask: 

442 _check_block_indices("dst_block_indices", params.dst_block_indices) 

443 _check_block_indices("src_block_indices", params.src_block_indices) 

444 if params.dst_block_memory_size is not None: 

445 check_uint64("dst_block_memory_size", params.dst_block_memory_size) 

446 if params.src_block_indices: # src is blocks 

447 raise_if_false(params.dst_block_indices, "transfer from blocks to cache is not supported") 

448 raise_if_false( 

449 len(params.src_block_indices) == len(params.dst_block_indices), 

450 "num_block_indices mismatches, src_num = {0}, dst_num = {1}", 

451 len(params.src_block_indices), 

452 len(params.dst_block_indices), 

453 ) 

454 else: # src is cache 

455 raise_if_true( 

456 (transfer_cache_func == llm_wrapper.transfer_cache) and (params.dst_block_indices is not None), 

457 "transfer from cache to blocks is not supported", 

458 ) 

459 if params.dst_block_indices: 

460 raise_if_false( 

461 params.dst_block_memory_size is not None, 

462 "dst_block_memory_size must be set when transfer from cache to blocks", 

463 ) 

464 check_isinstance("layer_synchronizer", layer_synchronizer, LayerSynchronizer, allow_none=False) 

465 if not enable_remote_cache: 

466 check_isinstance( 

467 "transfer_configs", 

468 params.transfer_configs, 

469 [list, tuple], 

470 TransferConfig, 

471 allow_none=False, 

472 ) 

473 else: 

474 check_isinstance( 

475 "transfer_configs", 

476 params.transfer_configs, 

477 [list, tuple], 

478 TransferWithCacheKeyConfig, 

479 "While enable_remote_cache_accessible is True, ", 

480 allow_none=False, 

481 ) 

482 raise_if_false( 

483 params.dst_block_indices or params.dst_block_memory_size in (None, 0), 

484 "dst_block_memory_size ({0}) is neither None nor 0 while dst is not blocks", 

485 params.dst_block_memory_size, 

486 ) 

487 transfer_job = TransferCacheJob(params, layer_synchronizer, transfer_cache_func) 

488 transfer_job.init() 

489 transfer_thread = TransferAsyncThread(transfer_job, default_error_code) 

490 transfer_thread.start() 

491 log.info("[transfer_cache_async] async task start") 

492 cache_task = CacheTask(transfer_thread) 

493 return cache_task 

494 

495 

496def layer_range_to_tensor_indices(src_layer_range: range, dst_layer_range: range, tensor_num_per_layer: int = 2): 

497 check_isinstance("src_layer_range", src_layer_range, range) 

498 check_isinstance("dst_layer_range", dst_layer_range, range) 

499 raise_if_true( 

500 (src_layer_range is not None) and (src_layer_range.step != 1), 

501 "param check failed, src_layer_range step must be 1.", 

502 ) 

503 raise_if_true( 

504 (dst_layer_range is not None) and (dst_layer_range.step != 1), 

505 "param check failed, dst_layer_range step must be 1.", 

506 ) 

507 src_layer_indices = [] if src_layer_range is None else list(src_layer_range) 

508 dst_layer_indices = [] if dst_layer_range is None else list(dst_layer_range) 

509 

510 # 默认一层有两个tensor 

511 one_layer_tensor_num = tensor_num_per_layer 

512 src_tensor_indices = [] 

513 if len(src_layer_indices) != 0: 

514 raise_if_true( 

515 src_layer_indices[0] < 0, 

516 "src_layer_range is invalid, the start value:{0} is < 0", 

517 src_layer_indices[0], 

518 ) 

519 check_uint32("src_layer_range", src_layer_indices[0]) 

520 check_uint32("src_layer_range", src_layer_indices[-1]) 

521 src_tensor_start_index = src_layer_indices[0] * one_layer_tensor_num 

522 for i in range(len(src_layer_indices) * one_layer_tensor_num): 

523 src_tensor_indices.append(src_tensor_start_index + i) 

524 

525 dst_tensor_indices = [] 

526 if len(dst_layer_indices) != 0: 

527 raise_if_true( 

528 dst_layer_indices[0] < 0, 

529 "dst_layer_range is invalid, the start value:{0} is < 0", 

530 dst_layer_indices[0], 

531 ) 

532 check_uint32("dst_layer_range", dst_layer_indices[0]) 

533 check_uint32("dst_layer_range", dst_layer_indices[-1]) 

534 dst_tensor_start_index = dst_layer_indices[0] * one_layer_tensor_num 

535 for i in range(len(dst_layer_indices) * one_layer_tensor_num): 

536 dst_tensor_indices.append(dst_tensor_start_index + i) 

537 

538 return src_tensor_indices, dst_tensor_indices 

539 

540 

541def parse_listen_ip_info(listen_ip_info: str) -> (str, int): 

542 check_isinstance("listen_ip_info", listen_ip_info, [str]) 

543 ip_and_port = listen_ip_info.split(":") 

544 raise_if_false( 

545 len(ip_and_port) == 2, 

546 f'llm.listenIpInfo "{ip_and_port}" is invalid, format should be "ip:port"', 

547 ) 

548 ip = ip_and_port[0] 

549 port = int(ip_and_port[1]) 

550 return ip, port