Coverage for /opt/cloud/slavespace/usr1/096471637100f3de0fcfc01072822a80/dttest/api/python/llm_datadist_v1/config.py: 54%
124 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-27 10:02 +0800
« 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# -----------------------------------------------------------------------------------------------------------
5# Copyright (c) 2025 Huawei Technologies Co., Ltd.
6# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
7# CANN Open Software License Agreement Version 2.0 (the "License").
8# Please refer to the License for details. You may not use this file except in compliance with the License.
9# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
10# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
11# See LICENSE in the root of the software repository for the full text of the License.
12# -----------------------------------------------------------------------------------------------------------
14import json
15import os
16from typing import Dict
18from .configs import LlmConfig, LLMRole, trans_str_ip
19from .status import raise_if_false
20from .utils import log
21from .utils.utils import check_isinstance
24class ClusterConfig:
25 def __init__(self, rank_id_to_device_id: Dict[int, int]):
26 self.rank_id_to_device_id = rank_id_to_device_id
28 @classmethod
29 def from_engine_options(cls, engine_options: Dict[str, str]) -> "ClusterConfig":
30 logical_device_id_to_rank_id = cls._parse_rank_mapping(engine_options)
31 logical_device_id_to_device_id = cls._parse_numa_config(engine_options)
32 local_rank_ids = []
33 if "ge.exec.rankId" in engine_options:
34 rank_id = int(engine_options["ge.exec.rankId"])
35 log.info(f"get rank_id by option ge.exec.rankId={rank_id}")
36 local_rank_ids.append(rank_id)
37 else:
38 for logical_device_id in logical_device_id_to_device_id.keys():
39 if logical_device_id in logical_device_id_to_rank_id:
40 rank_id = logical_device_id_to_rank_id[logical_device_id]
41 log.info(f"append rank_id = {rank_id}, logical_device_id = {logical_device_id}")
42 local_rank_ids.append(rank_id)
43 rank_id_to_device_id: Dict[int, int] = {}
44 for logical_device_id, rank_id in logical_device_id_to_rank_id.items():
45 if rank_id in local_rank_ids:
46 device_id = logical_device_id_to_device_id[logical_device_id]
47 rank_id_to_device_id[rank_id] = device_id
48 raise_if_false(
49 len(rank_id_to_device_id) > 0,
50 f"rank_id_to_device_id is empty, "
51 f"logical_device_id_to_rank_id = {logical_device_id_to_rank_id}, "
52 f"logical_device_id_to_device_id = {logical_device_id_to_device_id}",
53 )
54 return cls(rank_id_to_device_id)
56 @staticmethod
57 def _parse_rank_mapping(engine_options: Dict[str, str]) -> Dict[str, int]:
58 raise_if_false(
59 "llm.ClusterInfo" in engine_options,
60 "option 'llm.ClusterInfo' is not defined",
61 )
62 cluster_info = json.loads(engine_options["llm.ClusterInfo"])
63 logical_device_id_to_rank_id = {}
64 if "ge.exec.deviceId" in engine_options and "ge.exec.rankId" in engine_options:
65 rank_ids = [int(engine_options["ge.exec.rankId"])]
66 log.info(f"both ge.exec.rankId and ge.exec.deviceId are defined, rank_id = {rank_ids[0]}")
67 else:
68 rank_ids = [i for i in range(len(cluster_info["logic_device_id"]))]
69 for rank_id, logical_device_id in zip(rank_ids, cluster_info["logic_device_id"]):
70 logical_device_id_to_rank_id[logical_device_id] = rank_id
71 return logical_device_id_to_rank_id
73 @staticmethod
74 def _parse_numa_config(engine_options) -> Dict[str, int]:
75 raise_if_false(
76 "ge.resourceConfigPath" in engine_options,
77 "option 'ge.resourceConfigPath' is not defined",
78 )
79 numa_config_path = engine_options["ge.resourceConfigPath"]
80 with open(numa_config_path) as f:
81 numa_config = json.load(f)
82 logical_device_id_to_device_id: Dict[str, int] = {}
83 for custer_idx, cluster in enumerate(numa_config["cluster"]):
84 for node_idx, cluster_node in enumerate(cluster["cluster_nodes"]):
85 is_local = cluster_node.get("is_local", False)
86 if not is_local:
87 continue
88 for item_idx, item in enumerate(cluster_node["item_list"]):
89 device_id = int(item["item_id"])
90 logical_device_id = ":".join([str(custer_idx), str(node_idx), str(item_idx), "0"])
91 logical_device_id_to_device_id[logical_device_id] = device_id
92 return logical_device_id_to_device_id
95class EngineConfig:
96 def __init__(self, is_prompt: bool, cluster_config: ClusterConfig) -> None:
97 self.is_prompt = is_prompt
98 self.cluster_config = cluster_config
100 @classmethod
101 def from_engine_options(cls, is_prompt: bool, engine_options: Dict[str, str]) -> "EngineConfig":
102 cluster_config = ClusterConfig.from_engine_options(engine_options)
103 return cls(is_prompt, cluster_config)
105 @staticmethod
106 def gen_numa_config(device_id, deploy_res_path: str) -> str:
107 node_type = "FakeNodeType"
108 item_type = "FakeItemType"
109 item_list = []
110 for dev_id in device_id.split(";"):
111 item_list.append(
112 {
113 "item_id": int(dev_id),
114 "device_id": int(dev_id),
115 "ipaddr": "192.168.0.1",
116 }
117 )
118 numa_config = {
119 "cluster": [
120 {
121 "cluster_nodes": [
122 {
123 "node_id": 0,
124 "node_type": node_type,
125 "ipaddr": "127.0.0.1",
126 "port": -1,
127 "is_local": True,
128 "data_panel": {"avail_ports": "65000~65535"},
129 "item_list": item_list,
130 }
131 ],
132 }
133 ],
134 "node_def": [
135 {
136 "node_type": node_type,
137 "resource_type": "Aarch",
138 "support_links": "[HCCS,PCIE,ROCE]",
139 "item_type": item_type,
140 }
141 ],
142 "item_def": [
143 {
144 "item_type": item_type,
145 "resource_type": "Ascend",
146 "memory": "[DDR:64GB]",
147 "aic_type": "[FakeAicType]",
148 }
149 ],
150 }
151 if deploy_res_path is not None:
152 check_isinstance("llm.deployResPath", deploy_res_path, str)
153 numa_config["cluster"][0]["cluster_nodes"][0]["deploy_res_path"] = deploy_res_path
154 return json.dumps(numa_config)
156 @staticmethod
157 def gen_cluster_info_if_not_exist(cluster_id: int, role: LLMRole, engine_options: Dict[str, str]) -> None:
158 if "llm.ClusterInfo" in engine_options:
159 return
160 device_num = len(engine_options["ge.exec.deviceId"].split(";"))
161 cluster_info = {
162 "cluster_id": cluster_id,
163 "logic_device_id": [f"0:0:{i}:0" for i in range(device_num)],
164 }
165 if role == LLMRole.PROMPT:
166 raise_if_false(
167 "llm.listenIpInfo" in engine_options,
168 "neither llm.ClusterInfo nor llm.listenIp was specified",
169 )
170 listen_ip_info = engine_options["llm.listenIpInfo"]
171 check_isinstance("listen_ip_info", listen_ip_info, [str])
172 sub_ip_infos = listen_ip_info.split(";")
173 raise_if_false(
174 len(sub_ip_infos) == device_num,
175 f"listen ip info num:{len(sub_ip_infos)} in llm.listenIpInfo is not equal to device num:{device_num}.",
176 )
177 cluster_info["listen_ip_info"] = []
178 for sub_ip_info in sub_ip_infos:
179 ip_and_port = sub_ip_info.split(":")
180 raise_if_false(
181 len(ip_and_port) == 2,
182 f'llm.listenIpInfo "{ip_and_port}" is invalid, format should be "ip:port"',
183 )
184 cluster_info["listen_ip_info"].append({"ip": ip_and_port[0], "port": int(ip_and_port[1])})
185 llm_config = LlmConfig()
186 llm_config.cluster_info = json.dumps(cluster_info)
187 converted_options = llm_config.gen_options()
188 engine_options["llm.ClusterInfo"] = converted_options["llm.ClusterInfo"]
189 # create numa config if needed
190 if "RESOURCE_CONFIG_PATH" in os.environ:
191 numa_config_path = os.getenv("RESOURCE_CONFIG_PATH", "")
192 else:
193 raise_if_false(
194 "ge.exec.deviceId" in engine_options,
195 "neither llm.ClusterInfo nor ge.exec.deviceId was specified",
196 )
197 device_id = engine_options["ge.exec.deviceId"]
198 check_isinstance("ge.exec.deviceId", device_id, [str])
199 for dev_id in device_id.split(";"):
200 raise_if_false(
201 dev_id.isdigit(),
202 f'ge.exec.deviceId is invalid, value="{device_id}",'
203 f" it should be composed of numbers, separated by semicolons.",
204 )
205 raise_if_false(
206 len(device_id.split(";")) > 0,
207 f'ge.exec.deviceId is invalid, value="{device_id}", At least one device id is required.',
208 )
209 deploy_res_path = engine_options.get("llm.deployResPath", None)
210 numa_config_str = EngineConfig.gen_numa_config(device_id, deploy_res_path)
211 numa_config_path = f"/tmp/stub_numa_config_{role.name.lower()}_{'_'.join(device_id.split(';'))}.json"
212 with open(numa_config_path, "w") as f:
213 f.write(numa_config_str)
214 engine_options["ge.resourceConfigPath"] = numa_config_path
215 log.info("using numa config: %s", numa_config_path)
217 @staticmethod
218 def parse_listen_ip_info(listen_ip_info: str) -> (int, int):
219 check_isinstance("listen_ip_info", listen_ip_info, [str])
220 ip_and_port = listen_ip_info.split(":")
221 raise_if_false(
222 len(ip_and_port) == 2,
223 f'llm.listenIpInfo "{ip_and_port}" is invalid, format should be "ip:port"',
224 )
225 ip_int = trans_str_ip(ip_and_port[0])
226 port = int(ip_and_port[1])
227 return ip_int, port