@aryacode/dsh-local-llm 0.0.0-stage → 0.8.2
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.
- package/LEGACY-CONTROL-CENTER.md +161 -0
- package/README.md +89 -3
- package/blue.png +0 -0
- package/cordis.patch.yml +4 -0
- package/icon.svg +11 -0
- package/lib/client.js +2272 -0
- package/lib/client.js.map +1 -0
- package/lib/event-stream-BkIJ4cYy.js +80 -0
- package/lib/event-stream-CiG-8VvD.js +80 -0
- package/lib/index.d.ts +2411 -0
- package/lib/index.js +10263 -0
- package/lib/openai-completions-B6T4-oyq.js +11710 -0
- package/lib/openai-completions-C58ZnIdi.js +11710 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +73 -6
- package/scripts/freetoken-control.py +82 -0
- package/scripts/ninfer-control.py +120 -0
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,73 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@aryacode/dsh-local-llm",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@aryacode/dsh-local-llm",
|
|
3
|
+
"version": "0.8.2",
|
|
4
|
+
"description": "AryaStrata engine controls with the local NInfer Qwen3.8 dense model for AryaAI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./client": "./lib/client.js",
|
|
14
|
+
"./package.json": "./package.json",
|
|
15
|
+
"./locale/*.json": "./locale/*.json"
|
|
16
|
+
},
|
|
17
|
+
"dsh": {
|
|
18
|
+
"bundle": {
|
|
19
|
+
"patch": "./cordis.patch.yml"
|
|
20
|
+
},
|
|
21
|
+
"client": {
|
|
22
|
+
"platform": "web",
|
|
23
|
+
"inject": [
|
|
24
|
+
"@deepseek-ai/dsh-client-locale",
|
|
25
|
+
"@deepseek-ai/dsh-client-ui-layout",
|
|
26
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
27
|
+
"@deepseek-ai/dsh-client-ui-slots"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsdown",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"prebuild": "node scripts/theme.mjs",
|
|
35
|
+
"test": "node --test tests/*.test.mjs",
|
|
36
|
+
"test:ui": "node tests/control-center-ui.mjs"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
40
|
+
"@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.2",
|
|
41
|
+
"@deepseek-ai/dsh-llm-pi-ai": "^0.2.0-rc.2",
|
|
42
|
+
"@types/node": "^22.10.0",
|
|
43
|
+
"@types/react": "~18.3.1",
|
|
44
|
+
"react": "^18.2.0",
|
|
45
|
+
"tsdown": "^0.22.14",
|
|
46
|
+
"typescript": "^5.6.0"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"icon.svg",
|
|
50
|
+
"locale/*.json",
|
|
51
|
+
"lib/index.js",
|
|
52
|
+
"lib/index.d.ts",
|
|
53
|
+
"lib/client.js",
|
|
54
|
+
"lib/client.js.map",
|
|
55
|
+
"lib/openai-completions-*.js",
|
|
56
|
+
"lib/event-stream-*.js",
|
|
57
|
+
"cordis.patch.yml",
|
|
58
|
+
"blue.png",
|
|
59
|
+
"README.md",
|
|
60
|
+
"LEGACY-CONTROL-CENTER.md",
|
|
61
|
+
"scripts/freetoken-control.py",
|
|
62
|
+
"scripts/ninfer-control.py"
|
|
63
|
+
],
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@earendil-works/pi-ai": "^0.87.1",
|
|
66
|
+
"saxes": "^6.0.0",
|
|
67
|
+
"systeminformation": "5.33.15"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"@deepseek-ai/dsh-llm-pi-ai": "^0.2.0-rc.2"
|
|
71
|
+
},
|
|
72
|
+
"icon": "./icon.svg"
|
|
73
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Own one FreeToken process group inside WSL; refuse unrelated processes."""
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import signal
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
directory = Path(sys.argv[1])
|
|
11
|
+
action = sys.argv[2]
|
|
12
|
+
request = json.load(sys.stdin)
|
|
13
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
14
|
+
owner_file = directory / 'owner.json'
|
|
15
|
+
log_file = directory / 'server.log'
|
|
16
|
+
|
|
17
|
+
def identity(pid):
|
|
18
|
+
try:
|
|
19
|
+
fields = Path('/proc/' + str(pid) + '/stat').read_text().rsplit(')', 1)[1].split()
|
|
20
|
+
return None if fields[0] == 'Z' else fields[19]
|
|
21
|
+
except (FileNotFoundError, ProcessLookupError):
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
def owner():
|
|
25
|
+
if not owner_file.exists():
|
|
26
|
+
return None
|
|
27
|
+
record = json.loads(owner_file.read_text())
|
|
28
|
+
return record if identity(record['pid']) == record['started'] else None
|
|
29
|
+
|
|
30
|
+
def stop():
|
|
31
|
+
record = owner()
|
|
32
|
+
if record:
|
|
33
|
+
os.killpg(record['pid'], signal.SIGTERM)
|
|
34
|
+
deadline = time.monotonic() + 20
|
|
35
|
+
while identity(record['pid']) is not None and time.monotonic() < deadline:
|
|
36
|
+
time.sleep(.2)
|
|
37
|
+
if identity(record['pid']) == record['started']:
|
|
38
|
+
os.killpg(record['pid'], signal.SIGKILL)
|
|
39
|
+
deadline = time.monotonic() + 5
|
|
40
|
+
while identity(record['pid']) is not None and time.monotonic() < deadline:
|
|
41
|
+
time.sleep(.1)
|
|
42
|
+
if identity(record['pid']) is not None:
|
|
43
|
+
raise RuntimeError('FreeToken has not stopped yet.')
|
|
44
|
+
owner_file.unlink(missing_ok=True)
|
|
45
|
+
|
|
46
|
+
if action == 'status':
|
|
47
|
+
record = owner()
|
|
48
|
+
print(json.dumps({'owned': bool(record), 'pid': record['pid'] if record else None}))
|
|
49
|
+
elif action == 'logs':
|
|
50
|
+
if log_file.exists():
|
|
51
|
+
with log_file.open('rb') as stream:
|
|
52
|
+
stream.seek(max(0, log_file.stat().st_size - 16000))
|
|
53
|
+
text = stream.read().decode(errors='replace')
|
|
54
|
+
else:
|
|
55
|
+
text = ''
|
|
56
|
+
print(json.dumps({'text': text}))
|
|
57
|
+
elif action == 'stop':
|
|
58
|
+
stop()
|
|
59
|
+
print(json.dumps({'ok': True, 'detail': 'FreeToken stopped.'}))
|
|
60
|
+
elif action == 'start':
|
|
61
|
+
if owner():
|
|
62
|
+
raise RuntimeError('FreeToken is already running.')
|
|
63
|
+
args = [request['runtime'] + '/bin/ft', 'serve', '--model-path', request['modelPath'],
|
|
64
|
+
'--served-model-name', request['alias'], '--host', '127.0.0.1', '--port', str(request['port']),
|
|
65
|
+
'--max-seq-len-override', str(request['context']), '--max-running-requests', '1',
|
|
66
|
+
'--max-prefill-length', str(request['prefill']), '--memory-ratio', str(request['memoryRatio']),
|
|
67
|
+
'--moe-strategy', request['strategy'], '--moe-cpu-threads', str(request['threads']),
|
|
68
|
+
'--moe-cpu-layers', 'auto', '--moe-cache-auto', '--kv-reserve-tokens', str(request['context']),
|
|
69
|
+
'--nvfp4-backend', 'triton', '--expert-load', 'serial',
|
|
70
|
+
'--mm-encoder-weights', 'host', '--image-max-tokens', '1024']
|
|
71
|
+
if not request['vision']:
|
|
72
|
+
args += ['--mm-disable', 'vision']
|
|
73
|
+
with log_file.open('ab', buffering=0) as log:
|
|
74
|
+
process = subprocess.Popen(args, stdin=subprocess.DEVNULL, stdout=log, stderr=log,
|
|
75
|
+
start_new_session=True, cwd=directory)
|
|
76
|
+
started = identity(process.pid)
|
|
77
|
+
if started is None:
|
|
78
|
+
raise RuntimeError('FreeToken exited during launch; inspect its logs.')
|
|
79
|
+
owner_file.write_text(json.dumps({'pid': process.pid, 'started': started}))
|
|
80
|
+
print(json.dumps({'ok': True, 'detail': 'FreeToken is loading. Check server status.', 'pid': process.pid}))
|
|
81
|
+
else:
|
|
82
|
+
raise ValueError('Unsupported FreeToken operation.')
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Manage one identified NInfer process group without Docker or shell commands."""
|
|
2
|
+
import json
|
|
3
|
+
import errno
|
|
4
|
+
import os
|
|
5
|
+
import signal
|
|
6
|
+
import socket
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def identity(pid):
|
|
14
|
+
try:
|
|
15
|
+
fields = Path(f'/proc/{pid}/stat').read_text().rsplit(')', 1)[1].split()
|
|
16
|
+
return None if fields[0] == 'Z' else fields[19]
|
|
17
|
+
except (FileNotFoundError, ProcessLookupError):
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def owned_record(owner_file, binary):
|
|
22
|
+
if not owner_file.exists():
|
|
23
|
+
return None
|
|
24
|
+
record = json.loads(owner_file.read_text())
|
|
25
|
+
pid = record['pid']
|
|
26
|
+
if not isinstance(pid, int) or pid <= 1 or identity(pid) != record['started']:
|
|
27
|
+
return None
|
|
28
|
+
try:
|
|
29
|
+
executable = Path(f'/proc/{pid}/exe').resolve(strict=True)
|
|
30
|
+
args = Path(f'/proc/{pid}/cmdline').read_bytes().split(b'\0')
|
|
31
|
+
expected = [arg.encode() for arg in record['args']]
|
|
32
|
+
matches = executable == binary.resolve() and args[:-1] == expected and os.getpgid(pid) == pid
|
|
33
|
+
except (FileNotFoundError, ProcessLookupError):
|
|
34
|
+
return None
|
|
35
|
+
if not matches:
|
|
36
|
+
raise RuntimeError('NInfer process identity changed; no process was stopped.')
|
|
37
|
+
return record
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def stop(owner_file, binary):
|
|
41
|
+
record = owned_record(owner_file, binary)
|
|
42
|
+
if not record:
|
|
43
|
+
return
|
|
44
|
+
os.killpg(record['pid'], signal.SIGTERM)
|
|
45
|
+
deadline = time.monotonic() + 20
|
|
46
|
+
while owned_record(owner_file, binary) and time.monotonic() < deadline:
|
|
47
|
+
time.sleep(.1)
|
|
48
|
+
if owned_record(owner_file, binary):
|
|
49
|
+
os.killpg(record['pid'], signal.SIGKILL)
|
|
50
|
+
deadline = time.monotonic() + 5
|
|
51
|
+
while owned_record(owner_file, binary) and time.monotonic() < deadline:
|
|
52
|
+
time.sleep(.1)
|
|
53
|
+
if owned_record(owner_file, binary):
|
|
54
|
+
raise RuntimeError('NInfer has not stopped yet.')
|
|
55
|
+
owner_file.unlink(missing_ok=True)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def ensure_port_available(port):
|
|
59
|
+
with socket.socket() as probe:
|
|
60
|
+
probe.settimeout(1)
|
|
61
|
+
result = probe.connect_ex(('127.0.0.1', port))
|
|
62
|
+
if result != errno.ECONNREFUSED:
|
|
63
|
+
raise RuntimeError('NInfer port has a listener or could not be checked; no server was stopped.')
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def main():
|
|
67
|
+
directory, action = Path(sys.argv[1]), sys.argv[2]
|
|
68
|
+
request = json.load(sys.stdin)
|
|
69
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
70
|
+
binary = Path(request['binary'])
|
|
71
|
+
owner_file = directory / 'wsl-owner.json'
|
|
72
|
+
log_file = directory / 'wsl-server.log'
|
|
73
|
+
record = owned_record(owner_file, binary)
|
|
74
|
+
if action == 'status':
|
|
75
|
+
print(json.dumps({'owned': bool(record), 'pid': record['pid'] if record else None,
|
|
76
|
+
'available': binary.is_file() and os.access(binary, os.X_OK)}))
|
|
77
|
+
elif action == 'logs':
|
|
78
|
+
text = ''
|
|
79
|
+
if log_file.exists():
|
|
80
|
+
with log_file.open('rb') as stream:
|
|
81
|
+
stream.seek(max(0, log_file.stat().st_size - 16000))
|
|
82
|
+
text = stream.read().decode(errors='replace')
|
|
83
|
+
print(json.dumps({'text': text}))
|
|
84
|
+
elif action == 'stop':
|
|
85
|
+
stop(owner_file, binary)
|
|
86
|
+
print(json.dumps({'ok': True, 'detail': 'NInfer stopped.'}))
|
|
87
|
+
elif action == 'start':
|
|
88
|
+
if record:
|
|
89
|
+
print(json.dumps({'ok': True, 'detail': 'NInfer is already started.'}))
|
|
90
|
+
return
|
|
91
|
+
if not binary.is_file() or not os.access(binary, os.X_OK):
|
|
92
|
+
raise RuntimeError('NInfer binary is unavailable. Finish the WSL build first.')
|
|
93
|
+
ensure_port_available(request['port'])
|
|
94
|
+
args = [str(binary), request['artifact'], '--host', '127.0.0.1', '--port', str(request['port']),
|
|
95
|
+
'--max-context', str(request['context']), '--kv-capacity', str(request['context']),
|
|
96
|
+
'--kv-dtype', 'rk4v4-e8', '--max-concurrency', '1', '--max-pending-requests', '16',
|
|
97
|
+
'--prefill-chunk', str(request['prefill']), '--host-kv-mib', str(request['hostKvMiB']),
|
|
98
|
+
'--default-max-tokens', str(request.get('outputTokens', 8192)),
|
|
99
|
+
'--spec', 'mtp', '--draft-tokens', str(request['draftTokens']), '--lm-head-draft',
|
|
100
|
+
'--ngram', request['ngram'], '--preserve-thinking']
|
|
101
|
+
if request['vision']:
|
|
102
|
+
args.append('--vision')
|
|
103
|
+
environment = dict(os.environ)
|
|
104
|
+
environment['LD_LIBRARY_PATH'] = request['libraryPath']
|
|
105
|
+
with log_file.open('ab', buffering=0) as log:
|
|
106
|
+
process = subprocess.Popen(args, stdin=subprocess.DEVNULL, stdout=log, stderr=log,
|
|
107
|
+
start_new_session=True, cwd=directory, env=environment)
|
|
108
|
+
started = identity(process.pid)
|
|
109
|
+
if started is None:
|
|
110
|
+
raise RuntimeError('NInfer exited during launch; inspect its logs.')
|
|
111
|
+
marker = owner_file.with_suffix('.tmp')
|
|
112
|
+
marker.write_text(json.dumps({'pid': process.pid, 'started': started, 'args': args}))
|
|
113
|
+
marker.replace(owner_file)
|
|
114
|
+
print(json.dumps({'ok': True, 'detail': 'NInfer is loading in WSL. Check readiness and logs.', 'pid': process.pid}))
|
|
115
|
+
else:
|
|
116
|
+
raise ValueError('Unsupported NInfer operation.')
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
if __name__ == '__main__':
|
|
120
|
+
main()
|