@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 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(inferletBytes, manifestToml);
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
- `max_total_pages`) in the boot config. `BootConfig` in
54
- `crates/browser/src/boot.rs` lists the keys.
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 `crates/browser`, runs wasm-bindgen into `pkg/`, and bundles
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
- | `crates/browser` | The wasm-bindgen host: boot, install, sessions speaking the MessagePack frames `pie serve`'s WebSocket carries, `pie_tick`. |
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, entry, source, description }) {
2550
- Object.assign(this, { fn, name, version, entry, source, description });
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
- manifestToml() {
2557
- const q = (s) => JSON.stringify(String(s));
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 programBytes = fs.readFileSync(wasmPath);
2878
- const manifest = fs.readFileSync(manifestPath, "utf-8");
2879
- return await this.installProgramBytes(programBytes, manifest, forceOverwrite);
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, manifest, forceOverwrite = false) {
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
- manifest,
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.manifestToml()
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(wasmBytes, manifestToml) {
3223
- const copy = new Uint8Array(wasmBytes).slice();
3224
- return await ready().call("install", [copy, manifestToml], { transfer: [copy.buffer] });
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
- return await ready().call("installLanguage", [language, copy], { transfer: [copy.buffer] });
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 instanceof Uint8Array) return 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, sku, weight bytes, kv pages. */
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
- async installLanguage(language, source) {
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
- return installLanguage(language, await bytesOf(source));
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
- /** An inferlet from its component bytes (or URL) and manifest text, replacing an installed version. */
3285
- async install(source, manifest) {
3300
+ async install(source, file = null, version = null) {
3286
3301
  this.#alive();
3287
- return install(await bytesOf(source), manifest);
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
 
Binary file