apishare-client 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- apishare_client-1.2.0/PKG-INFO +27 -0
- apishare_client-1.2.0/apishare_client/__init__.py +17 -0
- apishare_client-1.2.0/apishare_client/cli.py +234 -0
- apishare_client-1.2.0/apishare_client/commands/__init__.py +1 -0
- apishare_client-1.2.0/apishare_client/commands/login.py +77 -0
- apishare_client-1.2.0/apishare_client/commands/register.py +93 -0
- apishare_client-1.2.0/apishare_client/commands/report.py +119 -0
- apishare_client-1.2.0/apishare_client/commands/serve.py +195 -0
- apishare_client-1.2.0/apishare_client/commands/status.py +97 -0
- apishare_client-1.2.0/apishare_client/commands/tunnel.py +275 -0
- apishare_client-1.2.0/apishare_client/config.py +129 -0
- apishare_client-1.2.0/apishare_client/py.typed +0 -0
- apishare_client-1.2.0/apishare_client.egg-info/PKG-INFO +27 -0
- apishare_client-1.2.0/apishare_client.egg-info/SOURCES.txt +18 -0
- apishare_client-1.2.0/apishare_client.egg-info/dependency_links.txt +1 -0
- apishare_client-1.2.0/apishare_client.egg-info/entry_points.txt +3 -0
- apishare_client-1.2.0/apishare_client.egg-info/requires.txt +3 -0
- apishare_client-1.2.0/apishare_client.egg-info/top_level.txt +1 -0
- apishare_client-1.2.0/pyproject.toml +60 -0
- apishare_client-1.2.0/setup.cfg +4 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: apishare-client
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: APIShare Mining Client - Contribute GPU/CPU compute power, run AI models, earn credits via tunnel
|
|
5
|
+
Author-email: APIShare Team <apishare@proton.me>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://apishare.samai.cc
|
|
8
|
+
Project-URL: Repository, https://github.com/ctz168/apishare
|
|
9
|
+
Project-URL: Documentation, https://apishare.samai.cc/mining
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/ctz168/apishare/issues
|
|
11
|
+
Project-URL: PyPI, https://pypi.org/project/apishare-client/
|
|
12
|
+
Keywords: apishare,mining,ai,gpu,compute,tunnel,websockets,decentralized,blockchain
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Requires-Dist: websockets>=12.0
|
|
26
|
+
Requires-Dist: psutil>=5.9.0
|
|
27
|
+
Requires-Dist: requests>=2.31.0
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""APIShare Mining Client - 挖矿客户端
|
|
2
|
+
|
|
3
|
+
矿工通过此客户端连接 APIShare 平台,贡献GPU/CPU算力赚取AIC积分。
|
|
4
|
+
|
|
5
|
+
用法:
|
|
6
|
+
apishare login --email you@email.com
|
|
7
|
+
apishare register --name my-node --type gpu --model qwen2.5-0.5b
|
|
8
|
+
apishare serve --model qwen2.5-0.5b --port 8000
|
|
9
|
+
apishare tunnel --node NODE_ID --port 8000
|
|
10
|
+
apishare report --node NODE_ID
|
|
11
|
+
apishare status
|
|
12
|
+
apishare balance
|
|
13
|
+
apishare models
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
__version__ = "1.2.0"
|
|
17
|
+
__author__ = "APIShare Team"
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
APIShare Mining Client - 挖矿客户端 CLI
|
|
4
|
+
======================================
|
|
5
|
+
|
|
6
|
+
矿工通过此客户端连接 APIShare 平台,贡献GPU/CPU算力赚取AIC积分。
|
|
7
|
+
|
|
8
|
+
用法:
|
|
9
|
+
apishare login --email you@email.com # 登录平台
|
|
10
|
+
apishare register --name my-node --type gpu # 注册计算节点
|
|
11
|
+
apishare serve --model qwen2.5-0.5b --port 8000 # 启动本地推理服务
|
|
12
|
+
apishare tunnel --node NODE_ID --port 8000 # 建立WebSocket隧道
|
|
13
|
+
apishare report --node NODE_ID # 上报算力数据
|
|
14
|
+
apishare status # 查看挖矿状态
|
|
15
|
+
apishare balance # 查看积分余额
|
|
16
|
+
apishare models # 查看可用模型
|
|
17
|
+
apishare logout # 登出
|
|
18
|
+
|
|
19
|
+
完整挖矿流程:
|
|
20
|
+
1. 登录: apishare login --email you@email.com
|
|
21
|
+
2. 注册节点: apishare register --name my-gpu --type gpu --model qwen2.5-0.5b
|
|
22
|
+
3. 启动模型: apishare serve --model qwen2.5-0.5b --port 8000
|
|
23
|
+
4. 建隧道: apishare tunnel --node NODE_ID --model qwen2.5-0.5b --port 8000
|
|
24
|
+
5. 查状态: apishare status
|
|
25
|
+
|
|
26
|
+
Docker一键模式:
|
|
27
|
+
docker run -d --gpus all ctz168/apishare-client:latest \\
|
|
28
|
+
tunnel --node NODE_ID --model qwen2.5-0.5b --port 8000 --token YOUR_TOKEN
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import argparse
|
|
32
|
+
import sys
|
|
33
|
+
import json
|
|
34
|
+
import urllib.request
|
|
35
|
+
import urllib.error
|
|
36
|
+
|
|
37
|
+
from apishare_client import __version__
|
|
38
|
+
from apishare_client.config import (
|
|
39
|
+
get_token, get_platform_url, get_email, get_node_id,
|
|
40
|
+
logout as config_logout, get_supported_models, DEFAULT_PLATFORM_URL,
|
|
41
|
+
)
|
|
42
|
+
from apishare_client.commands.login import login as cmd_login
|
|
43
|
+
from apishare_client.commands.register import register_node as cmd_register
|
|
44
|
+
from apishare_client.commands.serve import start_serve as cmd_serve
|
|
45
|
+
from apishare_client.commands.tunnel import start_tunnel as cmd_tunnel
|
|
46
|
+
from apishare_client.commands.report import report_mining as cmd_report
|
|
47
|
+
from apishare_client.commands.status import show_status as cmd_status
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def cmd_balance(args):
|
|
51
|
+
"""查看积分余额"""
|
|
52
|
+
token = get_token()
|
|
53
|
+
if not token:
|
|
54
|
+
print("❌ 请先登录: apishare login --email you@email.com")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
base_url = get_platform_url()
|
|
58
|
+
try:
|
|
59
|
+
url = f"{base_url}/api/auth/me"
|
|
60
|
+
req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
|
|
61
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
62
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
63
|
+
print(f"💰 APIShare 积分余额: {data.get('balance', 0):.2f}")
|
|
64
|
+
print(f" 邮箱: {data.get('email', '-')}")
|
|
65
|
+
print(f" 角色: {data.get('role', '-')}")
|
|
66
|
+
except urllib.error.HTTPError as e:
|
|
67
|
+
if e.code == 401:
|
|
68
|
+
print("❌ Token已过期,请重新登录")
|
|
69
|
+
else:
|
|
70
|
+
print(f"❌ 查询失败: HTTP {e.code}")
|
|
71
|
+
except Exception as e:
|
|
72
|
+
print(f"❌ 查询错误: {e}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def cmd_models(args):
|
|
76
|
+
"""查看可用模型"""
|
|
77
|
+
models = get_supported_models()
|
|
78
|
+
print()
|
|
79
|
+
print("=" * 55)
|
|
80
|
+
print(" APIShare 支持的模型")
|
|
81
|
+
print("=" * 55)
|
|
82
|
+
print()
|
|
83
|
+
print(" 🖥️ GPU 模型:")
|
|
84
|
+
for m in models["gpu"]:
|
|
85
|
+
print(f" - {m}")
|
|
86
|
+
print()
|
|
87
|
+
print(" 💻 CPU 模型:")
|
|
88
|
+
for m in models["cpu"]:
|
|
89
|
+
print(f" - {m}")
|
|
90
|
+
print()
|
|
91
|
+
print(" 💡 也可以使用 --model 指定任何模型名称")
|
|
92
|
+
print()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def cmd_logout(args):
|
|
96
|
+
"""登出"""
|
|
97
|
+
config_logout()
|
|
98
|
+
print("✅ 已登出")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def main():
|
|
102
|
+
"""CLI入口"""
|
|
103
|
+
parser = argparse.ArgumentParser(
|
|
104
|
+
prog="apishare",
|
|
105
|
+
description="APIShare 挖矿客户端 - 贡献GPU/CPU算力赚取AIC积分",
|
|
106
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
107
|
+
epilog="""
|
|
108
|
+
完整挖矿流程:
|
|
109
|
+
1. 登录平台: apishare login --email you@email.com
|
|
110
|
+
2. 注册计算节点: apishare register --name my-gpu --type gpu --model qwen2.5-0.5b
|
|
111
|
+
3. 启动本地模型: apishare serve --model qwen2.5-0.5b --port 8000
|
|
112
|
+
4. 建立隧道连接: apishare tunnel --node NODE_ID --model qwen2.5-0.5b --port 8000
|
|
113
|
+
5. 查看挖矿状态: apishare status
|
|
114
|
+
|
|
115
|
+
更多帮助:
|
|
116
|
+
https://apishare.samai.cc/mining
|
|
117
|
+
""",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
parser.add_argument("--version", action="version", version=f"apishare {__version__}")
|
|
121
|
+
parser.add_argument("--platform", type=str, help="平台URL (默认: https://apishare.samai.cc)")
|
|
122
|
+
parser.add_argument("--ws-url", type=str, help="WebSocket隧道URL")
|
|
123
|
+
|
|
124
|
+
subparsers = parser.add_subparsers(dest="command", help="可用命令")
|
|
125
|
+
|
|
126
|
+
# login
|
|
127
|
+
p_login = subparsers.add_parser("login", help="登录 APIShare 平台")
|
|
128
|
+
p_login.add_argument("--email", required=True, help="登录邮箱")
|
|
129
|
+
p_login.add_argument("--password", type=str, help="密码 (不填则交互输入)")
|
|
130
|
+
|
|
131
|
+
# register
|
|
132
|
+
p_reg = subparsers.add_parser("register", help="注册计算节点")
|
|
133
|
+
p_reg.add_argument("--name", required=True, help="节点名称")
|
|
134
|
+
p_reg.add_argument("--type", default="gpu", choices=["gpu", "cpu"], help="计算类型")
|
|
135
|
+
p_reg.add_argument("--model", default="qwen2.5-0.5b", help="服务模型名称")
|
|
136
|
+
p_reg.add_argument("--gpu-info", default="", help="GPU信息")
|
|
137
|
+
p_reg.add_argument("--memory", type=int, default=0, help="内存(GB)")
|
|
138
|
+
|
|
139
|
+
# serve
|
|
140
|
+
p_serve = subparsers.add_parser("serve", help="启动本地推理服务")
|
|
141
|
+
p_serve.add_argument("--model", required=True, help="模型名称")
|
|
142
|
+
p_serve.add_argument("--port", type=int, default=8000, help="模型服务端口")
|
|
143
|
+
p_serve.add_argument("--backend", default="auto", choices=["auto", "vllm", "ollama", "openai-compatible"], help="推理后端")
|
|
144
|
+
p_serve.add_argument("--proxy-port", type=int, default=8001, help="代理服务端口")
|
|
145
|
+
p_serve.add_argument("--host", default="0.0.0.0", help="监听地址")
|
|
146
|
+
|
|
147
|
+
# tunnel
|
|
148
|
+
p_tunnel = subparsers.add_parser("tunnel", help="建立WebSocket隧道")
|
|
149
|
+
p_tunnel.add_argument("--node", required=True, help="节点ID")
|
|
150
|
+
p_tunnel.add_argument("--model", default="qwen2.5-0.5b", help="模型名称")
|
|
151
|
+
p_tunnel.add_argument("--port", type=int, default=8000, help="本地模型端口")
|
|
152
|
+
p_tunnel.add_argument("--token", type=str, help="JWT token (覆盖配置文件)")
|
|
153
|
+
|
|
154
|
+
# report
|
|
155
|
+
p_report = subparsers.add_parser("report", help="上报算力数据")
|
|
156
|
+
p_report.add_argument("--node", required=True, help="节点ID")
|
|
157
|
+
p_report.add_argument("--model", default="qwen2.5-0.5b", help="模型名称")
|
|
158
|
+
p_report.add_argument("--tokens-in", type=int, default=0, help="输入token数")
|
|
159
|
+
p_report.add_argument("--tokens-out", type=int, default=0, help="输出token数")
|
|
160
|
+
p_report.add_argument("--gpu-usage", type=float, default=0, help="GPU使用率")
|
|
161
|
+
p_report.add_argument("--auto", action="store_true", help="自动采集系统信息")
|
|
162
|
+
|
|
163
|
+
# status
|
|
164
|
+
p_status = subparsers.add_parser("status", help="查看挖矿状态")
|
|
165
|
+
|
|
166
|
+
# balance
|
|
167
|
+
p_balance = subparsers.add_parser("balance", help="查看积分余额")
|
|
168
|
+
|
|
169
|
+
# models
|
|
170
|
+
p_models = subparsers.add_parser("models", help="查看可用模型")
|
|
171
|
+
|
|
172
|
+
# logout
|
|
173
|
+
p_logout = subparsers.add_parser("logout", help="登出")
|
|
174
|
+
|
|
175
|
+
args = parser.parse_args()
|
|
176
|
+
|
|
177
|
+
if not args.command:
|
|
178
|
+
parser.print_help()
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
# Override platform URL if specified
|
|
182
|
+
if args.platform:
|
|
183
|
+
from apishare_client.config import save_config, load_config
|
|
184
|
+
config = load_config()
|
|
185
|
+
config["platform_url"] = args.platform
|
|
186
|
+
save_config(config)
|
|
187
|
+
|
|
188
|
+
# Dispatch commands
|
|
189
|
+
if args.command == "login":
|
|
190
|
+
cmd_login(email=args.email, password=args.password)
|
|
191
|
+
elif args.command == "register":
|
|
192
|
+
cmd_register(
|
|
193
|
+
name=args.name,
|
|
194
|
+
compute_type=args.type,
|
|
195
|
+
model=args.model,
|
|
196
|
+
gpu_info=args.gpu_info,
|
|
197
|
+
memory_gb=args.memory,
|
|
198
|
+
)
|
|
199
|
+
elif args.command == "serve":
|
|
200
|
+
cmd_serve(
|
|
201
|
+
model=args.model,
|
|
202
|
+
port=args.port,
|
|
203
|
+
backend=args.backend,
|
|
204
|
+
host=args.host,
|
|
205
|
+
proxy_port=args.proxy_port,
|
|
206
|
+
)
|
|
207
|
+
elif args.command == "tunnel":
|
|
208
|
+
cmd_tunnel(
|
|
209
|
+
node_id=args.node,
|
|
210
|
+
model=args.model,
|
|
211
|
+
port=args.port,
|
|
212
|
+
token=args.token,
|
|
213
|
+
)
|
|
214
|
+
elif args.command == "report":
|
|
215
|
+
cmd_report(
|
|
216
|
+
node_id=args.node,
|
|
217
|
+
model=args.model,
|
|
218
|
+
tokens_in=args.tokens_in,
|
|
219
|
+
tokens_out=args.tokens_out,
|
|
220
|
+
gpu_usage=args.gpu_usage,
|
|
221
|
+
auto=args.auto,
|
|
222
|
+
)
|
|
223
|
+
elif args.command == "status":
|
|
224
|
+
cmd_status()
|
|
225
|
+
elif args.command == "balance":
|
|
226
|
+
cmd_balance(args)
|
|
227
|
+
elif args.command == "models":
|
|
228
|
+
cmd_models(args)
|
|
229
|
+
elif args.command == "logout":
|
|
230
|
+
cmd_logout(args)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
if __name__ == "__main__":
|
|
234
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""APIShare Mining Client - 命令模块"""
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""APIShare Mining Client - 登录命令
|
|
2
|
+
|
|
3
|
+
apishare login --email you@email.com
|
|
4
|
+
apishare login --email you@email.com --platform https://apishare.samai.cc
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import getpass
|
|
8
|
+
import sys
|
|
9
|
+
import urllib.request
|
|
10
|
+
import urllib.error
|
|
11
|
+
import json
|
|
12
|
+
|
|
13
|
+
from apishare_client.config import set_login, get_platform_url, DEFAULT_PLATFORM_URL
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def login(email: str, platform_url: str = None, password: str = None) -> bool:
|
|
17
|
+
"""登录 APIShare 平台,获取 JWT token"""
|
|
18
|
+
base_url = platform_url or get_platform_url()
|
|
19
|
+
|
|
20
|
+
if not password:
|
|
21
|
+
password = getpass.getpass("请输入密码: ")
|
|
22
|
+
|
|
23
|
+
if not email or not password:
|
|
24
|
+
print("❌ 邮箱和密码不能为空")
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
print(f"正在登录 {base_url} ...")
|
|
28
|
+
|
|
29
|
+
# 调用平台登录API
|
|
30
|
+
url = f"{base_url}/api/auth/login"
|
|
31
|
+
payload = json.dumps({"email": email, "password": password}).encode("utf-8")
|
|
32
|
+
req = urllib.request.Request(
|
|
33
|
+
url,
|
|
34
|
+
data=payload,
|
|
35
|
+
headers={"Content-Type": "application/json"},
|
|
36
|
+
method="POST",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
41
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
42
|
+
|
|
43
|
+
token = data.get("token") or data.get("access_token")
|
|
44
|
+
user = data.get("user", {})
|
|
45
|
+
|
|
46
|
+
if token:
|
|
47
|
+
ws_url = base_url.replace("https://", "wss://").replace("http://", "ws://") + "/api/tunnel/connect"
|
|
48
|
+
set_login(token, email, base_url, ws_url)
|
|
49
|
+
balance = user.get("balance", 0)
|
|
50
|
+
role = user.get("role", "user")
|
|
51
|
+
print(f"✅ 登录成功!")
|
|
52
|
+
print(f" 邮箱: {email}")
|
|
53
|
+
print(f" 角色: {role}")
|
|
54
|
+
print(f" 积分余额: {balance:.2f} AIC")
|
|
55
|
+
print(f" 平台: {base_url}")
|
|
56
|
+
return True
|
|
57
|
+
else:
|
|
58
|
+
print(f"❌ 登录失败: 服务器未返回有效token")
|
|
59
|
+
print(f" 响应: {json.dumps(data, ensure_ascii=False)[:200]}")
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
except urllib.error.HTTPError as e:
|
|
63
|
+
body = e.read().decode("utf-8", errors="replace")[:200]
|
|
64
|
+
try:
|
|
65
|
+
err_data = json.loads(body)
|
|
66
|
+
msg = err_data.get("detail", body)
|
|
67
|
+
except json.JSONDecodeError:
|
|
68
|
+
msg = body
|
|
69
|
+
print(f"❌ 登录失败: {msg}")
|
|
70
|
+
return False
|
|
71
|
+
except urllib.error.URLError as e:
|
|
72
|
+
print(f"❌ 网络错误: {e.reason}")
|
|
73
|
+
print(f" 请检查网络连接或平台地址是否正确")
|
|
74
|
+
return False
|
|
75
|
+
except Exception as e:
|
|
76
|
+
print(f"❌ 登录异常: {e}")
|
|
77
|
+
return False
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""APIShare Mining Client - 注册计算节点
|
|
2
|
+
|
|
3
|
+
apishare register --name my-gpu-node --type gpu --model qwen2.5-0.5b
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
import urllib.request
|
|
9
|
+
import urllib.error
|
|
10
|
+
|
|
11
|
+
from apishare_client.config import get_token, get_platform_url, get_email, set_node_id
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def register_node(
|
|
15
|
+
name: str,
|
|
16
|
+
compute_type: str = "gpu",
|
|
17
|
+
model: str = "qwen2.5-0.5b",
|
|
18
|
+
gpu_info: str = "",
|
|
19
|
+
cpu_info: str = "",
|
|
20
|
+
memory_gb: int = 0,
|
|
21
|
+
) -> bool:
|
|
22
|
+
"""在平台上注册一个计算节点"""
|
|
23
|
+
|
|
24
|
+
token = get_token()
|
|
25
|
+
if not token:
|
|
26
|
+
print("❌ 未登录,请先运行: apishare login --email you@email.com")
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
email = get_email() or "unknown"
|
|
30
|
+
base_url = get_platform_url()
|
|
31
|
+
|
|
32
|
+
print(f"正在注册计算节点...")
|
|
33
|
+
print(f" 名称: {name}")
|
|
34
|
+
print(f" 类型: {compute_type.upper()}")
|
|
35
|
+
print(f" 模型: {model}")
|
|
36
|
+
|
|
37
|
+
# 调用平台注册API
|
|
38
|
+
url = f"{base_url}/api/compute/register"
|
|
39
|
+
payload = json.dumps({
|
|
40
|
+
"nodeName": name,
|
|
41
|
+
"computeType": compute_type,
|
|
42
|
+
"servingModel": model,
|
|
43
|
+
"gpuInfo": gpu_info or f"GPU for {model}",
|
|
44
|
+
"cpuInfo": cpu_info or ("CPU for " + model if compute_type == "cpu" else ""),
|
|
45
|
+
"memoryGB": memory_gb or 8,
|
|
46
|
+
}).encode("utf-8")
|
|
47
|
+
|
|
48
|
+
req = urllib.request.Request(
|
|
49
|
+
url,
|
|
50
|
+
data=payload,
|
|
51
|
+
headers={
|
|
52
|
+
"Content-Type": "application/json",
|
|
53
|
+
"Authorization": f"Bearer {token}",
|
|
54
|
+
},
|
|
55
|
+
method="POST",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
60
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
61
|
+
|
|
62
|
+
node_id = data.get("id") or data.get("nodeId")
|
|
63
|
+
if node_id:
|
|
64
|
+
set_node_id(node_id)
|
|
65
|
+
print(f"\n✅ 节点注册成功!")
|
|
66
|
+
print(f" 节点ID: {node_id}")
|
|
67
|
+
print(f" 节点名称: {name}")
|
|
68
|
+
print(f"\n下一步:")
|
|
69
|
+
print(f" 1. 启动本地模型服务:")
|
|
70
|
+
print(f" apishare serve --model {model} --port 8000")
|
|
71
|
+
print(f" 2. 建立隧道连接(让平台能调用你的模型):")
|
|
72
|
+
print(f" apishare tunnel --node {node_id} --model {model} --port 8000")
|
|
73
|
+
return True
|
|
74
|
+
else:
|
|
75
|
+
print(f"❌ 注册失败: 服务器未返回节点ID")
|
|
76
|
+
print(f" 响应: {json.dumps(data, ensure_ascii=False)[:200]}")
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
except urllib.error.HTTPError as e:
|
|
80
|
+
body = e.read().decode("utf-8", errors="replace")[:200]
|
|
81
|
+
try:
|
|
82
|
+
err_data = json.loads(body)
|
|
83
|
+
msg = err_data.get("detail", body)
|
|
84
|
+
except json.JSONDecodeError:
|
|
85
|
+
msg = body
|
|
86
|
+
print(f"❌ 注册失败: {msg}")
|
|
87
|
+
return False
|
|
88
|
+
except urllib.error.URLError as e:
|
|
89
|
+
print(f"❌ 网络错误: {e.reason}")
|
|
90
|
+
return False
|
|
91
|
+
except Exception as e:
|
|
92
|
+
print(f"❌ 注册异常: {e}")
|
|
93
|
+
return False
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""APIShare Mining Client - 算力上报
|
|
2
|
+
|
|
3
|
+
apishare report --node NODE_ID
|
|
4
|
+
apishare report --node NODE_ID --auto (自动采集系统信息)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
import urllib.request
|
|
10
|
+
import urllib.error
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
from apishare_client.config import get_token, get_platform_url
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def report_mining(
|
|
17
|
+
node_id: str,
|
|
18
|
+
tokens_in: int = 0,
|
|
19
|
+
tokens_out: int = 0,
|
|
20
|
+
inference_time: float = 0.0,
|
|
21
|
+
gpu_usage: float = 0.0,
|
|
22
|
+
model: str = "qwen2.5-0.5b",
|
|
23
|
+
compute_type: str = "gpu",
|
|
24
|
+
auto: bool = False,
|
|
25
|
+
) -> bool:
|
|
26
|
+
"""上报挖矿算力数据到平台"""
|
|
27
|
+
|
|
28
|
+
token = get_token()
|
|
29
|
+
if not token:
|
|
30
|
+
print("❌ 未登录,请先运行: apishare login")
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
base_url = get_platform_url()
|
|
34
|
+
|
|
35
|
+
if auto:
|
|
36
|
+
sys_info = _collect_system_info()
|
|
37
|
+
gpu_usage = gpu_usage or sys_info.get("gpu_usage", 0)
|
|
38
|
+
compute_type = compute_type or sys_info.get("compute_type", "gpu")
|
|
39
|
+
|
|
40
|
+
# 构建上报数据
|
|
41
|
+
report_data = {
|
|
42
|
+
"nodeId": node_id,
|
|
43
|
+
"tokensIn": tokens_in,
|
|
44
|
+
"tokensOut": tokens_out,
|
|
45
|
+
"inferenceTime": inference_time,
|
|
46
|
+
"gpuUsage": gpu_usage,
|
|
47
|
+
"modelName": model,
|
|
48
|
+
"computeType": compute_type,
|
|
49
|
+
"timestamp": time.time(),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
url = f"{base_url}/api/compute/report"
|
|
53
|
+
payload = json.dumps(report_data).encode("utf-8")
|
|
54
|
+
req = urllib.request.Request(
|
|
55
|
+
url,
|
|
56
|
+
data=payload,
|
|
57
|
+
headers={
|
|
58
|
+
"Content-Type": "application/json",
|
|
59
|
+
"Authorization": f"Bearer {token}",
|
|
60
|
+
},
|
|
61
|
+
method="POST",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
66
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
67
|
+
|
|
68
|
+
reward = data.get("reward") or data.get("miningReward", 0)
|
|
69
|
+
score = data.get("computeScore") or data.get("score", 0)
|
|
70
|
+
|
|
71
|
+
print(f"✅ 算力上报成功")
|
|
72
|
+
print(f" 节点: {node_id[:16]}...")
|
|
73
|
+
if score:
|
|
74
|
+
print(f" 算力分: {score:.2f}")
|
|
75
|
+
if reward:
|
|
76
|
+
print(f" 获得奖励: {reward}")
|
|
77
|
+
return True
|
|
78
|
+
|
|
79
|
+
except urllib.error.HTTPError as e:
|
|
80
|
+
body = e.read().decode("utf-8", errors="replace")[:200]
|
|
81
|
+
print(f"❌ 上报失败 (HTTP {e.code}): {body}")
|
|
82
|
+
return False
|
|
83
|
+
except urllib.error.URLError as e:
|
|
84
|
+
print(f"❌ 网络错误: {e.reason}")
|
|
85
|
+
return False
|
|
86
|
+
except Exception as e:
|
|
87
|
+
print(f"❌ 上报异常: {e}")
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _collect_system_info() -> dict:
|
|
92
|
+
"""采集系统信息"""
|
|
93
|
+
info = {"gpu_usage": 0, "compute_type": "cpu", "cpu_count": 0, "memory_gb": 0}
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
import psutil
|
|
97
|
+
info["cpu_count"] = psutil.cpu_count()
|
|
98
|
+
info["memory_gb"] = round(psutil.virtual_memory().total / (1024**3), 1)
|
|
99
|
+
except ImportError:
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
# Try to detect GPU
|
|
103
|
+
try:
|
|
104
|
+
import subprocess
|
|
105
|
+
result = subprocess.run(
|
|
106
|
+
["nvidia-smi", "--query-gpu=utilization.gpu,memory.used,memory.total",
|
|
107
|
+
"--format=csv,noheader,nounits"],
|
|
108
|
+
capture_output=True, text=True, timeout=5
|
|
109
|
+
)
|
|
110
|
+
if result.returncode == 0:
|
|
111
|
+
info["compute_type"] = "gpu"
|
|
112
|
+
lines = result.stdout.strip().split("\n")
|
|
113
|
+
if lines:
|
|
114
|
+
parts = lines[0].split(",")
|
|
115
|
+
info["gpu_usage"] = float(parts[0].strip())
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
return info
|