@pie-project/server 0.5.1 → 0.5.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/README.md +34 -27
- package/index.d.ts +23 -7
- package/index.mjs +62 -8
- package/languages.d.ts +1 -0
- package/languages.mjs +3 -0
- package/package.json +17 -7
- package/web.mjs +3 -0
package/README.md
CHANGED
|
@@ -1,39 +1,37 @@
|
|
|
1
1
|
# @pie-project/server
|
|
2
2
|
|
|
3
3
|
What `pie serve` boots (controller, gateway, worker, the built-in
|
|
4
|
-
inferlets), inside your Node.js
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
inferlets), inside your process. Under Node.js it runs on the native GPU
|
|
5
|
+
engines; under a bundler, the same import is pie compiled for the browser,
|
|
6
|
+
on WebGPU. Clients connect as they would to a `pie serve`: with
|
|
7
|
+
`@pie-project/client` over the server's socket, or (Node) over the OpenAI /
|
|
8
|
+
Anthropic / Gemini HTTP routes.
|
|
7
9
|
|
|
8
10
|
```js
|
|
9
11
|
import { Server } from '@pie-project/server';
|
|
12
|
+
import '@pie-project/language-python'; // Python inferlets run; one line per language
|
|
10
13
|
|
|
11
14
|
const server = await Server.start({
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
model: 'Qwen/Qwen3.5-0.8B', // Node: what `pie model import` fetched
|
|
16
|
+
// model: '/models/qwen3.5-0.8b.wgpu.zt', // browser: the artifact's URL, served with Range support
|
|
17
|
+
config: { server: { port: 0 } }, // the rest of `pie serve`'s config
|
|
14
18
|
});
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
await server.install(inferletBytes, manifestToml); // an inferlet of your own, from bytes or a URL
|
|
21
|
+
const client = await server.connect(); // a PieClient
|
|
17
22
|
const process = await client.launchProcess('text-completion', { prompt: 'Hello' });
|
|
18
23
|
console.log(await process.result());
|
|
19
|
-
|
|
20
|
-
const reply = await fetch(server.httpUrl + '/v1/chat/completions', {
|
|
21
|
-
method: 'POST',
|
|
22
|
-
headers: { 'content-type': 'application/json' },
|
|
23
|
-
body: JSON.stringify({ messages: [{ role: 'user', content: 'Hi' }] }),
|
|
24
|
-
});
|
|
25
|
-
console.log((await reply.json()).choices[0].message.content);
|
|
26
|
-
|
|
27
24
|
await server.shutdown();
|
|
28
25
|
```
|
|
29
26
|
|
|
30
|
-
`Server.start`
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
`
|
|
27
|
+
`Server.start` resolves once the model is loaded and the server is
|
|
28
|
+
listening, with every registered language component installed; `config` is
|
|
29
|
+
the file `pie serve --config` reads, minus the model, as an object. A bare
|
|
30
|
+
`Server.start(config)` with `model.model` inside, as an object or TOML text,
|
|
31
|
+
is accepted too. Under Node, `server.httpUrl` is where the HTTP routes live.
|
|
32
|
+
A handle garbage-collected without `shutdown()` still stops the engine.
|
|
34
33
|
|
|
35
|
-
The Python twin is the `pie-server` wheel (`python/server`)
|
|
36
|
-
build of pie is `javascript/browser`.
|
|
34
|
+
The Python twin is the `pie-server` wheel (`python/server`).
|
|
37
35
|
|
|
38
36
|
## Installing
|
|
39
37
|
|
|
@@ -41,16 +39,24 @@ build of pie is `javascript/browser`.
|
|
|
41
39
|
npm install @pie-project/server
|
|
42
40
|
```
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
(`@pie-project/server-linux-x64`, `-linux-arm64`, `-darwin-arm64`,
|
|
42
|
+
Under Node the engine is a native addon, one optional dependency per
|
|
43
|
+
platform (`@pie-project/server-linux-x64`, `-linux-arm64`, `-darwin-arm64`,
|
|
46
44
|
`-win32-x64`), each carrying every engine that platform supports (Linux:
|
|
47
|
-
CUDA, Vulkan, wgpu; macOS: Metal, wgpu; Windows: CUDA, Vulkan); the
|
|
48
|
-
`engine.type` picks one at boot.
|
|
45
|
+
CUDA, Vulkan, wgpu; macOS: Metal, wgpu; Windows: CUDA, Vulkan); the
|
|
46
|
+
config's `engine.type` picks one at boot. Under a bundler (Vite, webpack 5)
|
|
47
|
+
the `browser` export condition resolves to `@pie-project/server-web`: the
|
|
48
|
+
runtime and the WebGPU engine as wasm, run in a Web Worker. The page must be
|
|
49
|
+
cross-origin isolated and the browser needs WebGPU and JSPI (Chrome 137+);
|
|
50
|
+
`javascript/server-web/README.md` has the details.
|
|
51
|
+
|
|
52
|
+
Language components (`@pie-project/language-python`,
|
|
53
|
+
`@pie-project/language-javascript`) register themselves on import; a
|
|
54
|
+
registration after `start()` lands on the running server too.
|
|
49
55
|
|
|
50
56
|
## Building
|
|
51
57
|
|
|
52
|
-
From source, the addon is built from this directory with the engine
|
|
53
|
-
for your GPU (`cuda` by default, `metal` on macOS):
|
|
58
|
+
From source, the Node addon is built from this directory with the engine
|
|
59
|
+
features for your GPU (`cuda` by default, `metal` on macOS):
|
|
54
60
|
|
|
55
61
|
```bash
|
|
56
62
|
npm run build # release, default features
|
|
@@ -59,4 +65,5 @@ bash build.sh dev --no-default-features # no engine: loads, refuses to serve
|
|
|
59
65
|
```
|
|
60
66
|
|
|
61
67
|
It lands beside `index.mjs` as `pie-server.<platform>-<arch>.node`, which
|
|
62
|
-
the loader prefers over the platform package.
|
|
68
|
+
the loader prefers over the platform package. The browser bundle is
|
|
69
|
+
`javascript/server-web/build.sh`.
|
package/index.d.ts
CHANGED
|
@@ -3,19 +3,35 @@ import type { PieClient } from '@pie-project/client';
|
|
|
3
3
|
/** The native addon file this platform loads, or null when none is built. */
|
|
4
4
|
export function addonPath(): string | null;
|
|
5
5
|
|
|
6
|
-
/**
|
|
6
|
+
/** `pie serve`'s config: the file's shape, as an object or TOML text. */
|
|
7
|
+
export type Config = string | Record<string, unknown>;
|
|
8
|
+
|
|
9
|
+
export interface StartOptions {
|
|
10
|
+
/** What to serve: a Hugging Face repo, a path, an imported artifact's name. In the browser: the artifact's URL. */
|
|
11
|
+
model: string;
|
|
12
|
+
/** The rest of the config (server, engine, sandbox, …), as an object; TOML text is accepted for a bare `start(config)`. */
|
|
13
|
+
config?: Record<string, unknown> | string;
|
|
14
|
+
/** Browser only: download progress of a lazy boot. */
|
|
15
|
+
onProgress?: (served: number, total: number, from: 'network' | 'cache') => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A running pie engine in this process. The same class serves in Node (native engines) and in the browser (WebGPU). */
|
|
7
19
|
export class Server {
|
|
8
20
|
/**
|
|
9
|
-
* Boot the engine
|
|
10
|
-
*
|
|
21
|
+
* Boot the engine; resolves once it is serving, with every registered
|
|
22
|
+
* language component (`import "@pie-project/language-python"`) installed.
|
|
11
23
|
*/
|
|
12
|
-
static start(
|
|
13
|
-
/** `ws://host:port` the engine listens on. */
|
|
24
|
+
static start(options: StartOptions | Config): Promise<Server>;
|
|
25
|
+
/** `ws://host:port` the engine listens on; `pie://local` in the browser. */
|
|
14
26
|
readonly url: string;
|
|
15
|
-
/** `http://host:port`, where the OpenAI / Anthropic / Gemini routes live. */
|
|
16
|
-
readonly httpUrl
|
|
27
|
+
/** Node: `http://host:port`, where the OpenAI / Anthropic / Gemini routes live. */
|
|
28
|
+
readonly httpUrl?: string;
|
|
17
29
|
/** True until `shutdown()` resolves. */
|
|
18
30
|
readonly running: boolean;
|
|
31
|
+
/** A language component (`python`, `javascript`) from bytes or a URL. */
|
|
32
|
+
installLanguage(language: 'python' | 'javascript' | string, source: Uint8Array | ArrayBuffer | URL | string): Promise<string>;
|
|
33
|
+
/** An inferlet from its component bytes (or URL) and manifest text, replacing an installed version; resolves to `name@version`. */
|
|
34
|
+
install(source: Uint8Array | ArrayBuffer | URL | string, manifest: string): Promise<string>;
|
|
19
35
|
/** A `PieClient` connected to this engine; closed by `shutdown()`. */
|
|
20
36
|
connect(options?: { identity?: string }): Promise<PieClient>;
|
|
21
37
|
/** Stop the engine and release it. Idempotent. */
|
package/index.mjs
CHANGED
|
@@ -15,8 +15,10 @@ import { createRequire } from 'node:module';
|
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
16
|
import { dirname, join } from 'node:path';
|
|
17
17
|
import { existsSync } from 'node:fs';
|
|
18
|
+
import { readFile } from 'node:fs/promises';
|
|
18
19
|
import WebSocket from 'ws';
|
|
19
20
|
import { PieClient } from '@pie-project/client';
|
|
21
|
+
import { attachLanguages } from '@pie-project/server-web/languages';
|
|
20
22
|
|
|
21
23
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
22
24
|
const require = createRequire(import.meta.url);
|
|
@@ -48,25 +50,59 @@ function load() {
|
|
|
48
50
|
return native;
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
/** Bytes from what `install*` accept: bytes, or a file/http URL to read. */
|
|
54
|
+
async function bytesOf(source) {
|
|
55
|
+
if (source instanceof Uint8Array) return source;
|
|
56
|
+
if (source instanceof ArrayBuffer) return new Uint8Array(source);
|
|
57
|
+
const url = source instanceof URL ? source : new URL(String(source), 'file://');
|
|
58
|
+
if (url.protocol === 'file:') return new Uint8Array(await readFile(fileURLToPath(url)));
|
|
59
|
+
const response = await fetch(url);
|
|
60
|
+
if (!response.ok) throw new Error(`${url}: ${response.status} ${response.statusText}`);
|
|
61
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Under Node a language package that is installed need not be imported by hand: importing it here registers it. */
|
|
65
|
+
async function importInstalledLanguages() {
|
|
66
|
+
for (const language of ['python', 'javascript']) {
|
|
67
|
+
try {
|
|
68
|
+
await import(`@pie-project/language-${language}`);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error?.code !== 'ERR_MODULE_NOT_FOUND') throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
51
75
|
/** A running pie engine in this process. */
|
|
52
76
|
export class Server {
|
|
53
77
|
#handle;
|
|
54
78
|
#clients = [];
|
|
79
|
+
#detach = null;
|
|
55
80
|
|
|
56
81
|
constructor(handle) {
|
|
57
82
|
this.#handle = handle;
|
|
58
83
|
}
|
|
59
84
|
|
|
60
85
|
/**
|
|
61
|
-
* Boot the engine
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
86
|
+
* Boot the engine. `{ model, config }` names the model (`Qwen/Qwen3.5-0.8B`,
|
|
87
|
+
* a path, an artifact) and gives the rest of `pie serve`'s config as an
|
|
88
|
+
* object; a bare config — TOML text, or an object of the file's shape with
|
|
89
|
+
* `model.model` inside — is accepted as well. `server.port = 0` asks the OS
|
|
90
|
+
* for a free port; `url` says which. Resolves once the engine is serving,
|
|
91
|
+
* with every registered language component installed.
|
|
65
92
|
*/
|
|
66
|
-
static async start(
|
|
93
|
+
static async start(options) {
|
|
94
|
+
let config = options;
|
|
95
|
+
if (options && typeof options === 'object' && typeof options.model === 'string') {
|
|
96
|
+
const { model, config: rest = {} } = options;
|
|
97
|
+
if (typeof rest !== 'object') throw new Error('Server.start: with `model`, `config` is an object');
|
|
98
|
+
config = { ...rest, model: { name: 'default', ...(rest.model ?? {}), model } };
|
|
99
|
+
}
|
|
67
100
|
const api = load();
|
|
68
101
|
const handle = typeof config === 'string' ? await api.startToml(config) : await api.start(config);
|
|
69
|
-
|
|
102
|
+
const server = new Server(handle);
|
|
103
|
+
await importInstalledLanguages();
|
|
104
|
+
server.#detach = await attachLanguages(server);
|
|
105
|
+
return server;
|
|
70
106
|
}
|
|
71
107
|
|
|
72
108
|
/** `ws://host:port` the engine listens on. */
|
|
@@ -84,10 +120,21 @@ export class Server {
|
|
|
84
120
|
return this.#handle.running;
|
|
85
121
|
}
|
|
86
122
|
|
|
123
|
+
/** A language component (`python`, `javascript`) from bytes or a URL; a script inferlet in that language runs once this resolves. */
|
|
124
|
+
async installLanguage(language, source) {
|
|
125
|
+
const handle = this.#alive();
|
|
126
|
+
return handle.installLanguage(language, Buffer.from(await bytesOf(source)));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** An inferlet from its component bytes (or URL) and manifest text, replacing an installed version. Resolves to `name@version`. */
|
|
130
|
+
async install(source, manifest) {
|
|
131
|
+
const handle = this.#alive();
|
|
132
|
+
return handle.install(Buffer.from(await bytesOf(source)), manifest);
|
|
133
|
+
}
|
|
134
|
+
|
|
87
135
|
/** A `PieClient` connected to this engine (over Node's `ws`, which can send the identity header). Closed by `shutdown()`. */
|
|
88
136
|
async connect({ identity = 'default/node' } = {}) {
|
|
89
|
-
|
|
90
|
-
const client = new PieClient(this.url, { WebSocket, identity });
|
|
137
|
+
const client = new PieClient(this.#alive().url, { WebSocket, identity });
|
|
91
138
|
await client.connect();
|
|
92
139
|
this.#clients.push(client);
|
|
93
140
|
return client;
|
|
@@ -95,6 +142,8 @@ export class Server {
|
|
|
95
142
|
|
|
96
143
|
/** Close the clients from `connect()`, then stop the engine. Idempotent. */
|
|
97
144
|
async shutdown() {
|
|
145
|
+
this.#detach?.();
|
|
146
|
+
this.#detach = null;
|
|
98
147
|
for (const client of this.#clients.splice(0)) {
|
|
99
148
|
try {
|
|
100
149
|
await client.close();
|
|
@@ -104,4 +153,9 @@ export class Server {
|
|
|
104
153
|
}
|
|
105
154
|
await this.#handle.shutdown();
|
|
106
155
|
}
|
|
156
|
+
|
|
157
|
+
#alive() {
|
|
158
|
+
if (!this.running) throw new Error('the server is shut down');
|
|
159
|
+
return this.#handle;
|
|
160
|
+
}
|
|
107
161
|
}
|
package/languages.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@pie-project/server-web/languages';
|
package/languages.mjs
ADDED
package/package.json
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pie-project/server",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "What `pie serve` boots, inside
|
|
3
|
+
"version": "0.5.2",
|
|
4
|
+
"description": "What `pie serve` boots, inside your process: on the native GPU engines under Node.js, on WebGPU under a bundler for the browser.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.mjs",
|
|
7
7
|
"types": "index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./index.d.ts",
|
|
11
|
-
"
|
|
11
|
+
"browser": "./web.mjs",
|
|
12
|
+
"node": "./index.mjs",
|
|
13
|
+
"default": "./index.mjs"
|
|
14
|
+
},
|
|
15
|
+
"./languages": {
|
|
16
|
+
"types": "./languages.d.ts",
|
|
17
|
+
"default": "./languages.mjs"
|
|
12
18
|
}
|
|
13
19
|
},
|
|
14
20
|
"files": [
|
|
15
21
|
"index.mjs",
|
|
16
22
|
"index.d.ts",
|
|
23
|
+
"web.mjs",
|
|
24
|
+
"languages.mjs",
|
|
25
|
+
"languages.d.ts",
|
|
17
26
|
"README.md"
|
|
18
27
|
],
|
|
19
28
|
"scripts": {
|
|
@@ -31,13 +40,14 @@
|
|
|
31
40
|
],
|
|
32
41
|
"dependencies": {
|
|
33
42
|
"@pie-project/client": "^0.5.0",
|
|
43
|
+
"@pie-project/server-web": "0.5.2",
|
|
34
44
|
"ws": "^8.18.0"
|
|
35
45
|
},
|
|
36
46
|
"optionalDependencies": {
|
|
37
|
-
"@pie-project/server-linux-x64": "0.5.
|
|
38
|
-
"@pie-project/server-linux-arm64": "0.5.
|
|
39
|
-
"@pie-project/server-darwin-arm64": "0.5.
|
|
40
|
-
"@pie-project/server-win32-x64": "0.5.
|
|
47
|
+
"@pie-project/server-linux-x64": "0.5.2",
|
|
48
|
+
"@pie-project/server-linux-arm64": "0.5.2",
|
|
49
|
+
"@pie-project/server-darwin-arm64": "0.5.2",
|
|
50
|
+
"@pie-project/server-win32-x64": "0.5.2"
|
|
41
51
|
},
|
|
42
52
|
"license": "Apache-2.0",
|
|
43
53
|
"repository": {
|
package/web.mjs
ADDED