@pie-project/server-web 0.5.2 → 0.6.0
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 +17 -6
- package/dist/pie.mjs +61 -41
- package/dist/pie_browser_bg.wasm +0 -0
- package/dist/precompile.mjs +1238 -0
- package/dist/worker.mjs +33 -29
- package/index.d.ts +13 -6
- package/package.json +6 -5
- package/src/pie.mjs +35 -17
- package/src/precompile.mjs +37 -0
- package/src/worker.mjs +4 -4
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ import { Server } from "@pie-project/server"; // resolves here under a bund
|
|
|
22
22
|
import "@pie-project/language-python";
|
|
23
23
|
|
|
24
24
|
const server = await Server.start({ model: "/models/qwen3.5-0.8b.wgpu.zt" }); // served with Range support
|
|
25
|
-
const program = await server.install(
|
|
25
|
+
const program = await server.install("/inferlets/beam_search.wasm"); // a URL, or bytes with a file name
|
|
26
26
|
const client = await server.connect(); // a PieClient, as for `pie serve`
|
|
27
27
|
const proc = await client.launchProcess(program, { prompt: "The capital of France is", max_tokens: 16 });
|
|
28
28
|
for (;;) {
|
|
@@ -50,8 +50,19 @@ well; `tests/browser` drives them directly.
|
|
|
50
50
|
- The engine cannot ask WebGPU how much device memory there is and assumes
|
|
51
51
|
8 GiB; a large model with the default pools is refused ("raise the device
|
|
52
52
|
weight budget") — pass `device_memory_mb = 20480` (or a smaller
|
|
53
|
-
`
|
|
54
|
-
`crates/
|
|
53
|
+
`device_kv_budget`) in the boot config. `Settings` in
|
|
54
|
+
`crates/worker/src/embedded.rs` lists the keys, plus `device_memory_mb` and
|
|
55
|
+
`power_preference` for the WebGPU engine.
|
|
56
|
+
- A tab compiles a script language's component (Python's is 37 MB) on the
|
|
57
|
+
first inferlet in that language, about 8 s, and again after every reload.
|
|
58
|
+
Precompile it once at build time with this same build,
|
|
59
|
+
`node node_modules/@pie-project/server-web/dist/precompile.mjs python.wasm
|
|
60
|
+
python.pulley.cwasm` (or `precompileComponent` from
|
|
61
|
+
`@pie-project/server-web/precompile`; no GPU needed), serve the result
|
|
62
|
+
beside the source, and pass it as
|
|
63
|
+
`installLanguage("python", source, { precompiled })`. It fits only the
|
|
64
|
+
`pie_browser_bg.wasm` it was made with; one that does not is ignored with
|
|
65
|
+
a warning and the source compiled instead.
|
|
55
66
|
- Rust, JavaScript and Python inferlets run. Not available in a tab: MoE
|
|
56
67
|
host-tier expert streaming, Python inferlet snapshots, telemetry, and
|
|
57
68
|
outbound HTTP or files from inferlets (the WASI interfaces link, a call
|
|
@@ -63,10 +74,10 @@ well; `tests/browser` drives them directly.
|
|
|
63
74
|
```bash
|
|
64
75
|
rustup target add wasm32-unknown-unknown
|
|
65
76
|
cargo install wasm-bindgen-cli --version 0.2.128 --locked # must match Cargo.lock
|
|
66
|
-
./javascript/server-web/build.sh # release; `min` for the 23 MB shipping build, `dev` for debug
|
|
77
|
+
./packages/javascript/server-web/build.sh # release; `min` for the 23 MB shipping build, `dev` for debug
|
|
67
78
|
```
|
|
68
79
|
|
|
69
|
-
`build.sh` builds `
|
|
80
|
+
`build.sh` builds the crate in `rust/`, runs wasm-bindgen into `pkg/`, and bundles
|
|
70
81
|
`src/` with esbuild into `dist/` (`npm run bundle`). The package's own
|
|
71
82
|
dependencies are installed on the first run. Tests, benchmarks and CI gates
|
|
72
83
|
live under `tests/browser` (see its README).
|
|
@@ -75,7 +86,7 @@ live under `tests/browser` (see its README).
|
|
|
75
86
|
|
|
76
87
|
| piece | what it does |
|
|
77
88
|
|---|---|
|
|
78
|
-
| `
|
|
89
|
+
| `rust/` (crate `pie-browser`) | The wasm-bindgen host: boot, install, sessions speaking the MessagePack frames `pie serve`'s WebSocket carries, `pie_tick`. |
|
|
79
90
|
| `crates/wasmtime-web` | wasmtime in a tab: heap-backed "virtual memory", TLS slots, fibers implemented by the page's JSPI glue (`src/platform.mjs`), and the WASI host for the inferlet's imports (cli, io, clocks, random; 0.2 linked by hand, the 0.3 clocks from `crates/inferlet/wit`). The 0.2 filesystem and http interfaces a JavaScript or Python guest runtime imports regardless are stubbed per component, so they link and only a call fails. |
|
|
80
91
|
| `crates/web-std` | The executor the page ticks, timers over `performance.now()`, crossbeam-shaped channels, and green threads on fibers. The runtime's scheduler and engine lanes run on those unchanged; `crates/runtime`'s `rt` facade names it on wasm32. |
|
|
81
92
|
| `crates/engine-wgpu` | The same engine on both hosts, written for the browser: no GPU wait blocks, every step lands by callback, guests run from the landing. What differs by host sits in `device/host.rs`. |
|
package/dist/pie.mjs
CHANGED
|
@@ -2502,6 +2502,19 @@ var CHUNK_SIZE = 256 * 1024;
|
|
|
2502
2502
|
function hashBytes(bytes) {
|
|
2503
2503
|
return bytesToHex(blake3(bytes));
|
|
2504
2504
|
}
|
|
2505
|
+
var PROGRAM_EXTENSION = { wasm: "wasm", py: "py", js: "js", mjs: "js" };
|
|
2506
|
+
function programFile(path) {
|
|
2507
|
+
const text = path instanceof URL ? decodeURIComponent(path.pathname) : String(path);
|
|
2508
|
+
const parts = text.replace(/\\/g, "/").replace(/\/+$/, "").split("/");
|
|
2509
|
+
const base = parts.pop();
|
|
2510
|
+
const dot = base.lastIndexOf(".");
|
|
2511
|
+
if (dot <= 0) throw new Error(`${text} has no extension; a program is a .wasm, .py or .js file`);
|
|
2512
|
+
const extension = PROGRAM_EXTENSION[base.slice(dot + 1)];
|
|
2513
|
+
if (!extension) throw new Error(`${text} is not a program pie can install; a program is a .wasm component or a .py / .js script`);
|
|
2514
|
+
const stem = base.slice(0, dot);
|
|
2515
|
+
const name = extension !== "wasm" && (stem === "main" || stem === "index") ? parts.pop() || stem : stem;
|
|
2516
|
+
return `${name.replace(/_/g, "-")}.${extension}`;
|
|
2517
|
+
}
|
|
2505
2518
|
var Bytes = globalThis.Buffer ?? Uint8Array;
|
|
2506
2519
|
function concatBytes(chunks) {
|
|
2507
2520
|
if (globalThis.Buffer) return Buffer.concat(chunks);
|
|
@@ -2546,19 +2559,15 @@ var ReceivedFile = class _ReceivedFile extends Bytes {
|
|
|
2546
2559
|
}
|
|
2547
2560
|
};
|
|
2548
2561
|
var Inferlet = class {
|
|
2549
|
-
constructor({ fn, name, version,
|
|
2550
|
-
Object.assign(this, { fn, name, version,
|
|
2562
|
+
constructor({ fn, name, version, source }) {
|
|
2563
|
+
Object.assign(this, { fn, name, version, source });
|
|
2551
2564
|
}
|
|
2552
2565
|
/** `name@version`, the id the server launches by. */
|
|
2553
2566
|
get program() {
|
|
2554
2567
|
return `${this.name}@${this.version}`;
|
|
2555
2568
|
}
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
const lines = ["[package]", `name = ${q(this.name)}`, `version = ${q(this.version)}`];
|
|
2559
|
-
if (this.description) lines.push(`description = ${q(this.description)}`);
|
|
2560
|
-
lines.push("", "[runtime]", 'language = "javascript"', `entry = ${q(this.entry)}`);
|
|
2561
|
-
return lines.join("\n") + "\n";
|
|
2569
|
+
get file() {
|
|
2570
|
+
return `${this.name}.js`;
|
|
2562
2571
|
}
|
|
2563
2572
|
};
|
|
2564
2573
|
var Process = class {
|
|
@@ -2867,25 +2876,16 @@ var PieClient = class {
|
|
|
2867
2876
|
// =========================================================================
|
|
2868
2877
|
// Program Upload
|
|
2869
2878
|
// =========================================================================
|
|
2870
|
-
|
|
2871
|
-
* Installs a program to the server in chunks.
|
|
2872
|
-
* @param {string} wasmPath Path to the WASM binary file (Node.js only).
|
|
2873
|
-
* @param {string} manifestPath Path to the manifest TOML file (Node.js only).
|
|
2874
|
-
*/
|
|
2875
|
-
async installProgram(wasmPath, manifestPath, forceOverwrite = false) {
|
|
2879
|
+
async installProgram(path, version = null, forceOverwrite = false) {
|
|
2876
2880
|
const fs = await import("fs");
|
|
2877
|
-
const
|
|
2878
|
-
const
|
|
2879
|
-
return await this.installProgramBytes(programBytes,
|
|
2881
|
+
const { resolve } = await import("path");
|
|
2882
|
+
const programBytes = fs.readFileSync(path);
|
|
2883
|
+
return await this.installProgramBytes(programBytes, programFile(resolve(path)), version, forceOverwrite);
|
|
2880
2884
|
}
|
|
2881
2885
|
/**
|
|
2882
|
-
* Installs a program from its artifact bytes (a component, or a script's
|
|
2883
|
-
* source when the manifest names a `[runtime] language`) and its
|
|
2884
|
-
* manifest TOML.
|
|
2885
2886
|
* @param {Uint8Array} programBytes
|
|
2886
|
-
* @param {string} manifest
|
|
2887
2887
|
*/
|
|
2888
|
-
async installProgramBytes(programBytes,
|
|
2888
|
+
async installProgramBytes(programBytes, file, version = null, forceOverwrite = false) {
|
|
2889
2889
|
if (typeof Buffer !== "undefined" && !Buffer.isBuffer(programBytes)) {
|
|
2890
2890
|
programBytes = Buffer.from(programBytes.buffer, programBytes.byteOffset, programBytes.byteLength);
|
|
2891
2891
|
}
|
|
@@ -2902,7 +2902,8 @@ var PieClient = class {
|
|
|
2902
2902
|
type: "add_program",
|
|
2903
2903
|
corr_id,
|
|
2904
2904
|
program_hash: programHash,
|
|
2905
|
-
|
|
2905
|
+
file,
|
|
2906
|
+
version,
|
|
2906
2907
|
force_overwrite: forceOverwrite,
|
|
2907
2908
|
chunk_index: i,
|
|
2908
2909
|
total_chunks: totalChunks,
|
|
@@ -2914,6 +2915,7 @@ var PieClient = class {
|
|
|
2914
2915
|
if (!ok) {
|
|
2915
2916
|
throw new Error(`Program install failed: ${result}`);
|
|
2916
2917
|
}
|
|
2918
|
+
return result;
|
|
2917
2919
|
}
|
|
2918
2920
|
// =========================================================================
|
|
2919
2921
|
// File Transfer (fire-and-forget)
|
|
@@ -2944,14 +2946,16 @@ var PieClient = class {
|
|
|
2944
2946
|
* @param {string} inferlet The inferlet name (e.g., "text-completion@0.1.0").
|
|
2945
2947
|
* @param {Object} [input={}] Input parameters object, serialized to JSON.
|
|
2946
2948
|
* @param {boolean} [captureOutputs=true] Stream outputs to client.
|
|
2949
|
+
* @param {Object<string, Uint8Array>} [files={}] Files the inferlet reads with `session.file(name)`.
|
|
2947
2950
|
* @returns {Promise<Process>}
|
|
2948
2951
|
*/
|
|
2949
|
-
async launchProcess(inferlet, input = {}, captureOutputs = true) {
|
|
2952
|
+
async launchProcess(inferlet, input = {}, captureOutputs = true, files = {}) {
|
|
2950
2953
|
const msg = {
|
|
2951
2954
|
type: "launch_process",
|
|
2952
2955
|
inferlet,
|
|
2953
2956
|
input: JSON.stringify(input),
|
|
2954
|
-
capture_outputs: captureOutputs
|
|
2957
|
+
capture_outputs: captureOutputs,
|
|
2958
|
+
files
|
|
2955
2959
|
};
|
|
2956
2960
|
const { ok, result } = await this._sendMsgAndWait(msg);
|
|
2957
2961
|
if (!ok) {
|
|
@@ -2983,7 +2987,8 @@ var PieClient = class {
|
|
|
2983
2987
|
if (!await this.checkProgram(program.program)) {
|
|
2984
2988
|
await this.installProgramBytes(
|
|
2985
2989
|
new TextEncoder().encode(program.source),
|
|
2986
|
-
program.
|
|
2990
|
+
program.file,
|
|
2991
|
+
program.version
|
|
2987
2992
|
);
|
|
2988
2993
|
}
|
|
2989
2994
|
program = program.program;
|
|
@@ -3219,13 +3224,16 @@ async function connect() {
|
|
|
3219
3224
|
await c.connect();
|
|
3220
3225
|
return c;
|
|
3221
3226
|
}
|
|
3222
|
-
async function install(
|
|
3223
|
-
const copy = new Uint8Array(
|
|
3224
|
-
return await ready().call("install", [copy,
|
|
3227
|
+
async function install(bytes, file, version = null) {
|
|
3228
|
+
const copy = new Uint8Array(bytes).slice();
|
|
3229
|
+
return await ready().call("install", [copy, file, version], { transfer: [copy.buffer] });
|
|
3225
3230
|
}
|
|
3226
|
-
async function installLanguage(language, wasmBytes) {
|
|
3231
|
+
async function installLanguage(language, wasmBytes, precompiled) {
|
|
3227
3232
|
const copy = new Uint8Array(wasmBytes).slice();
|
|
3228
|
-
|
|
3233
|
+
const ready_ = precompiled ? new Uint8Array(precompiled).slice() : void 0;
|
|
3234
|
+
return await ready().call("installLanguage", [language, copy, ready_], {
|
|
3235
|
+
transfer: ready_ ? [copy.buffer, ready_.buffer] : [copy.buffer]
|
|
3236
|
+
});
|
|
3229
3237
|
}
|
|
3230
3238
|
async function memoryBytes() {
|
|
3231
3239
|
return await ready().call("memoryBytes");
|
|
@@ -3236,9 +3244,11 @@ async function bootLazy(url, config, onProgress) {
|
|
|
3236
3244
|
async function awaitCache() {
|
|
3237
3245
|
return await ready().call("awaitCache", []);
|
|
3238
3246
|
}
|
|
3247
|
+
function isBytes2(source) {
|
|
3248
|
+
return source instanceof Uint8Array || source instanceof ArrayBuffer;
|
|
3249
|
+
}
|
|
3239
3250
|
async function bytesOf(source) {
|
|
3240
|
-
if (source
|
|
3241
|
-
if (source instanceof ArrayBuffer) return new Uint8Array(source);
|
|
3251
|
+
if (isBytes2(source)) return new Uint8Array(source);
|
|
3242
3252
|
const response = await fetch(source);
|
|
3243
3253
|
if (!response.ok) throw new Error(`pie: ${source}: ${response.status} ${response.statusText}`);
|
|
3244
3254
|
return new Uint8Array(await response.arrayBuffer());
|
|
@@ -3264,7 +3274,7 @@ var Server = class _Server {
|
|
|
3264
3274
|
server.#detach = await attachLanguages(server);
|
|
3265
3275
|
return server;
|
|
3266
3276
|
}
|
|
3267
|
-
/** What the boot found: model,
|
|
3277
|
+
/** What the boot found: model, deployment, weight bytes, kv pages. */
|
|
3268
3278
|
get summary() {
|
|
3269
3279
|
return this.#summary;
|
|
3270
3280
|
}
|
|
@@ -3276,15 +3286,24 @@ var Server = class _Server {
|
|
|
3276
3286
|
get running() {
|
|
3277
3287
|
return this.#open && backend !== null;
|
|
3278
3288
|
}
|
|
3279
|
-
/** A language component (`python`, `javascript`) from bytes or a URL
|
|
3280
|
-
|
|
3289
|
+
/** A language component (`python`, `javascript`) from bytes or a URL,
|
|
3290
|
+
* and optionally the same component precompiled for this build. A
|
|
3291
|
+
* precompiled one that fails to fetch is skipped, not fatal. */
|
|
3292
|
+
async installLanguage(language, source, { precompiled } = {}) {
|
|
3281
3293
|
this.#alive();
|
|
3282
|
-
|
|
3294
|
+
const [bytes, ready_] = await Promise.all([
|
|
3295
|
+
bytesOf(source),
|
|
3296
|
+
precompiled ? bytesOf(precompiled).catch(() => void 0) : void 0
|
|
3297
|
+
]);
|
|
3298
|
+
return installLanguage(language, bytes, ready_);
|
|
3283
3299
|
}
|
|
3284
|
-
|
|
3285
|
-
async install(source, manifest) {
|
|
3300
|
+
async install(source, file = null, version = null) {
|
|
3286
3301
|
this.#alive();
|
|
3287
|
-
|
|
3302
|
+
if (!file) {
|
|
3303
|
+
if (isBytes2(source)) throw new Error("Server.install: bytes need `file`, the program's file name (`x.wasm`, `x.py`, `x.js`)");
|
|
3304
|
+
file = programFile(new URL(source, location.href));
|
|
3305
|
+
}
|
|
3306
|
+
return install(await bytesOf(source), file, version);
|
|
3288
3307
|
}
|
|
3289
3308
|
/** A `PieClient` connected in-page. */
|
|
3290
3309
|
async connect() {
|
|
@@ -3324,7 +3343,8 @@ export {
|
|
|
3324
3343
|
install,
|
|
3325
3344
|
installLanguage,
|
|
3326
3345
|
load,
|
|
3327
|
-
memoryBytes
|
|
3346
|
+
memoryBytes,
|
|
3347
|
+
programFile
|
|
3328
3348
|
};
|
|
3329
3349
|
/*! Bundled license information:
|
|
3330
3350
|
|
package/dist/pie_browser_bg.wasm
CHANGED
|
Binary file
|