You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The large-payload transport release — the second half of the scientific data plane. A result that exceeds a single JSONL line no longer has to fit on one line: the subprocess bridge splits it into frames and reassembles it byte-for-byte. The wire protocol is unchanged (`tywrap/1`); framing is a separate, additive `tywrap-frame/1` protocol negotiated at startup, so a 0.7.x bridge and a 0.8.0 client still talk, and an oversize payload sent to a bridge that cannot chunk fails loudly instead of silently truncating.
6
+
7
+
### Features
8
+
9
+
-**Chunked transport for large payloads (`tywrap-frame/1`, [#231](https://github.com/bbopen/tywrap/issues/231)).** When a request or response exceeds the JSONL line ceiling, the subprocess bridge fragments it into frames and reassembles them. It is negotiated through the `meta` handshake (no protocol-version bump), subprocess-only (HTTP and Pyodide stay single-frame), and slices on UTF-8 codepoint boundaries — no base64 inflation. `NodeBridge` enables chunking by default; it engages only above the frame ceiling, so typical small-payload traffic is unchanged, and raising `codec.maxPayloadBytes` is what carries genuinely large results. Reassembly is bounded — declared and accumulated bytes (default 10 MiB, tracking the codec cap), concurrent streams, and the timed-out-id set — so an oversized or buggy payload fails loud rather than exhausting memory.
10
+
-**Scientific envelopes fail clearly ([#234](https://github.com/bbopen/tywrap/issues/234)).** SciPy, Torch, and Sklearn envelopes now reject unsupported cases explicitly — complex/sparse/quantized/meta tensors, non-CPU or non-contiguous tensors without `TYWRAP_TORCH_ALLOW_COPY`, and non-JSON-safe sklearn params — with matching JS-side re-validation. Lossy and device-transfer paths stay opt-in; nothing silently degrades.
11
+
12
+
### Internal
13
+
14
+
- Expanded scientific-codec validation and a dedicated `data-plane-perf` CI job that gates the chunked large-payload paths with same-run-relative perf budgets seeded from the 0.7.0 baselines. ([#233](https://github.com/bbopen/tywrap/issues/233))
15
+
-`TransportCapabilities.supportsChunking` now reports the **configured** capability (static, like `supportsArrow`); whether the connected bridge actually negotiated framing is a separate runtime fact on `BridgeInfo.transport.supportsChunking`.
16
+
- Request cancellation is identity-exact: a timed-out or aborted request is skipped at every write point — including the stdin backpressure queue and mid-burst frames — bound to the exact pending entry, so an abandoned call never executes on the Python side.
The foundation half of the scientific data plane. It lands the measurement, capability, and Arrow-ergonomics groundwork the large-payload transport work (0.8.0) builds on, and captures Python class members the IR used to drop. The wire protocol is unchanged, so a 0.6.x bridge and a 0.7.0 client still talk.
|**Opt-in (lossy/transfer)**| Non-CPU device **or** non-contiguous layout → rejected **unless**`TYWRAP_TORCH_ALLOW_COPY=1` (then a CPU / contiguous copy is made). Default is rejection. |
34
+
|**Explicit failure (Python), NOT opt-in-able**| Sparse (any non-strided layout: COO/CSR/CSC/BSR/BSC), quantized, `meta`, and complex tensors are rejected categorically — `TYWRAP_TORCH_ALLOW_COPY` does **not** bypass them (they have no faithful dense-CPU JSON/Arrow representation). |
35
+
|**Explicit failure (JS re-validation)**|`shape` dim negative/non-integer; `shape` element-count disagrees with the nested ndarray `shape`; `device` an empty string; nested `value` not an ndarray envelope. |
36
+
37
+
### Sklearn estimators
38
+
39
+
|| Behavior |
40
+
|---|---|
41
+
|**Supported**| Metadata only: `className`, `module`, `version`, and `get_params(deep=False)` when every param value is plain JSON (primitives / arrays / plain objects). |
42
+
|**Explicit failure (Python)**| Any non-JSON param (callable, nested estimator/object, numpy array, NaN/Infinity) → rejected with the **offending param name** and a reminder that estimators are metadata-only (no pickle/joblib). |
43
+
|**Explicit failure (JS re-validation)**|`params` not a plain JSON object; any nested param value that is a function / symbol / bigint / class instance / non-finite number. |
- Provide predictable, versioned envelopes for common scientific objects.
@@ -71,9 +106,10 @@ Envelope (current):
71
106
```
72
107
73
108
Notes:
74
-
- Only `csr`/`csc`/`coo` formats are supported.
109
+
- Only `csr`/`csc`/`coo` formats are supported; any other format is rejected with a message naming the supported set.
75
110
- Complex sparse matrices are rejected (explicit failure; no silent coercion).
76
111
- No dense fallback: callers should convert explicitly if needed.
112
+
- The JS decoder re-validates structural consistency (array lengths vs shape, index ranges) and rejects a corrupt envelope rather than passing it through.
77
113
78
114
## Torch (tensors)
79
115
@@ -106,7 +142,8 @@ Envelope (current):
106
142
Notes:
107
143
- Default to CPU tensors; require opt-in for `.cpu()` conversion.
- Sparse / quantized / `meta` / complex tensors are rejected categorically and are **not** bypassable by `TYWRAP_TORCH_ALLOW_COPY`; convert explicitly (`to_dense()` / `dequantize()` / materialize / split real-imag) before returning.
110
147
- Future: GPU-native transport (DLPack/Arrow CUDA) to avoid implicit device transfers.
111
148
112
149
## Sklearn (models + outputs)
@@ -132,6 +169,7 @@ Envelope (current):
132
169
Notes:
133
170
- Avoid `pickle` or `joblib` without explicit opt-in due to security and size.
134
171
- Keep model serialization as an advanced, explicit feature.
172
+
- Every `get_params(deep=False)` value must be plain JSON; a callable, nested estimator, or other non-JSON param is rejected with the offending param name (no silent drop, no pickle). The JS decoder re-validates `params` is a plain JSON object.
These numbers are stable run-to-run on this machine (single-digit-percent
74
74
spread), which is what matters for using them as a regression tripwire.
75
+
76
+
## Perf gates (0.8.0, #233)
77
+
78
+
0.8.0 layers actual **perf gates** on top of the measure-first harness above.
79
+
They live in [`test/data-plane-perf.test.ts`](../test/data-plane-perf.test.ts)
80
+
(also gated behind `TYWRAP_PERF_BUDGETS=1`) and assert budgets, where the
81
+
measure-first benchmarks only print. The suite first proves correctness at
82
+
scale (chunked 20 MiB + 80 MiB responses and a 20 MiB request echo, all forced
83
+
through `tywrap-frame/1` frames against a 1 MiB ceiling), then checks budgets:
84
+
85
+
| Gate | Budget |
86
+
|------|--------|
87
+
| Chunked 20 MiB response median vs same-run high-ceiling single frame | calibrated overhead ratio (inherent fragmentation cost, not the doc numbers) |
88
+
|`PooledTransport` small-call throughput (4 workers) | >= 70% of a same-run single-worker baseline |
89
+
| Arrow ndarray/DataFrame + 100k-row decode | <= 2.0x of a same-run warm baseline |
90
+
| Retained heap after an 80 MiB chunked response | <= `payload * 4 + fixed` (no quadratic growth) |
91
+
92
+
> **CI baselines are stored and compared SEPARATELY from this local doc.** The
93
+
> Apple-Silicon numbers in the table above are indicative only and are **not**
94
+
> used as CI thresholds. Every gate in `test/data-plane-perf.test.ts` is
95
+
> **same-run relative**: it measures a baseline in the same process on the same
96
+
> runner (median of 5, after warmup) and compares the subject against *that*,
97
+
> never against a hardcoded absolute. This keeps the gates portable across the
98
+
> Apple-Silicon dev machine and the pinned Linux CI runner without re-tuning.
99
+
100
+
The gates run in a dedicated `data-plane-perf` CI job (pinned Node 22 / Python
101
+
3.11, `TYWRAP_PERF_BUDGETS=1`, `NODE_OPTIONS=--expose-gc`, serial Vitest, no
102
+
coverage so instrumentation does not skew timings). That job is part of the
103
+
`required` gate; release publishing additionally re-runs the full suite with
104
+
`TYWRAP_PERF_BUDGETS=1`, so the data-plane gates also fence the npm publish.
105
+
106
+
```bash
107
+
TYWRAP_PERF_BUDGETS=1 NODE_OPTIONS=--expose-gc npx vitest run \
0 commit comments