feat: initial tfjs-native implementation (M0-M5) - #1
Merged
Merged
Conversation
Bun (PM/runtime/tests), tsdown dual ESM+CJS, Biome for JS/TS/JSON and clang-format for C++, Node 22+, Python via uv.
English-canonical docs under docs/ with Japanese localizations (README-ja, CONTRIBUTING-ja, docs/ja/*).
node-addon-api binding of the libtensorflow C API: tensor <-> TFE_TensorHandle conversion, a process-wide eager context, generic TFE_Execute with attribute setting, and TF_GetAllOpList for codegen.
Single dtype table (TF <-> JS TypedArray, int64 as BigInt) and the sole node-gyp-build entry point that loads the addon and types it as NativeBinding.
Handle-backed Tensor (data/array/dispose), tensor/scalar/zeros/ones, pure shape/convert utilities, and scope-based tidy(). Pure parts unit-tested.
runOp/runOp1 bridge to native execute; element-wise, reduction, linalg, and shape/dtype ops with friendly signatures.
Decode TF_GetAllOpList with protobufjs and a minimal op_def.proto; render typed wrappers (auto type/size attrs, generic options).
1295 typed wrappers generated from the TF op registry, including ops tfjs lacks (scatterNd, gatherNd, nonMaxSuppressionV2, conv2D, ...). Committed build artifact; regenerate on libtensorflow bump.
Barrel re-exports generated + hand-written ops (manual wins by name); index.ts exposes tensor, ops, tidy, and version().
Download and verify the pinned libtensorflow (2.10.0) per platform; self-heal missing C headers from the matching source tag; LIBTENSORFLOW_ROOT / mirror / skip overrides.
CI runs biome/typecheck/bun test on Linux/macOS/Windows plus clang-format on Linux; release builds per-OS prebuilds and publishes via npm trusted publishing (OIDC).
TensorFlow has no Python 3.14 wheels; 3.12 serves both node-gyp and TF Python (used to generate SavedModel test fixtures).
y = x * 2 exported by scripts/fixtures/make_saved_model.py (TF Python is a fixture-only tool). Fixture files are marked binary.
TF_LoadSessionFromSavedModel plus TF_SessionRun, feeding eager handles as TF_Tensors and returning outputs as eager handles. Session and graph are freed together, once.
loadSavedModel/predict/run keyed by signature names. Signatures are parsed from the MetaGraphDef by a small hand-rolled protobuf reader, so the runtime keeps zero extra dependencies.
Windows has no rpath, so a published prebuild could not resolve its libtensorflow dependency. Prepend the fetched (or LIBTENSORFLOW_ROOT) lib dir to PATH before loading the addon; no-op on Linux/macOS which embed an rpath.
Adds a native job that fetches libtensorflow (cached), builds via node-gyp with a real Node + Python 3.12, and runs the full suite. Uses macos-13 because the official libtensorflow C library has no arm64 build.
Set up Node + Python for node-gyp, smoke-test each prebuild before upload, and build on macos-13 (no official arm64 libtensorflow). Ship README-ja.md so the README language link resolves.
checkout v7, setup-node v7, setup-python v7, cache v6, upload-artifact v7, download-artifact v8, setup-uv v9, and disable uv's dependency cache (nothing to key on, warned every run).
macos-13 was fully unsupported on 2025-12-08, so that job never got a runner and hung. macos-15-intel is the remaining x86_64 image (until Aug 2027) and is required because the official libtensorflow C library has no arm64 build.
astral-sh/setup-uv publishes no floating v9 major tag, so @v9 was unresolvable and the cpp-format job failed during job setup.
Installs JS deps, resolves the pinned Python through uv, fetches libtensorflow, builds the addon, and verifies it loads. Plain Node with no third-party imports so it runs before bun install, and spawns without a shell so Windows paths and arguments are never mangled.
tensorflow/core/platform/google/* only compiles under PLATFORM_GOOGLE and is absent from the source tree, so every run warned about the same 404s.
Keeps a single source of truth instead of repeating 3.12 in each workflow.
bun run format:cpp/check:cpp called uvx directly, which fails when uv is installed but neither uv nor uvx is on PATH (the common Windows case). Resolve a uv binary from PATH or the standard install locations and use 'uv tool run'.
runOp records every op when a tape is active (one null check otherwise), so all ops are traceable with no per-op work. Ops that build short-lived index tensors hand them to disposeTemporary, which defers their release until after a replay while recording.
grads/valueAndGrads replay the tape into a TF_Graph and call TF_AddGradients, reusing TensorFlow's own gradient definitions. Handles all three outcomes: a gradient, a null (REGISTER_NO_GRADIENT_OP), and a thrown Status (unreachable / unregistered).
Section 8 wrongly claimed a TFE_* gradient tape; the C API has none. Describe the actual record-and-replay design and the three TF_AddGradients outcomes.
A mutable holder around an immutable Tensor; assign() swaps the value and frees the old handle. grads/minimize differentiate w.r.t. its current value.
Optimizer.minimize differentiates the loss w.r.t. variables and applies the update. Rules are plain eager ops run outside any tape; per-variable state is kept as Tensors and freed on dispose(), with intermediates wrapped in tidy().
Checks SGD (with and without momentum) and Adam against a hand-computed step, plus an end-to-end linear regression that converges to y = 3x + 2.
Split gradients into compile/run/dispose. Optimizer.minimize compiles the loss graph once per (loss function, input signature) and reuses it, so a training loop no longer rebuilds the graph every step; sessions are freed on optimizer.dispose(). Standalone grads/valueAndGrads stay per-call to avoid leaking sessions for inline closures.
500 steps must finish well under a rebuild-per-step budget.
The README carried a stale status line (still said milestone M1). Keep the READMEs evergreen and track progress on a dedicated, milestone-by-milestone Status page (en/ja), linked from Documentation.
Match the otnc/hono-feed release shape: workflow_dispatch takes a version (a semver bump or explicit), bumps package.json, publishes to npm via trusted publishing (OIDC), then commits, tags v<semver>, and creates the GitHub Release. Keeps the 3-OS prebuild matrix so a partial prebuild set is never published; macos-15-intel because libtensorflow has no arm64 build.
Update RULES, DESIGN, and CONTRIBUTING to the workflow_dispatch flow and note the npm trusted-publisher prerequisite for the first release.
README.md/README-ja.md link to CONTRIBUTING.md/CONTRIBUTING-ja.md, but neither was in the published files list. Verified with npm pack --dry-run plus a link check against the exact shipped set.
Native builds, prebuilds, and the release workflow are in place and green on CI. Removes the now-empty Planned section from FUNCTIONS.md and fixes a heading-level bug there (Eager ops/SavedModel/Training were nested under Tensors instead of being their own top-level sections).
Adapted from otnc/hono-feed's format: a bug report and feature request issue form, plus a PR template. The bug report asks for the tfjs-native version, the libtensorflow version, and the runtime/platform; the PR checklist references this repo's actual bun commands.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
…alone bun run build:native called node-gyp directly, which only finds a working Python if PYTHON is already exported — it doesn't fall back to uv the way bun run setup does. Broken/absent system Pythons (as in a real bug report: five stub installs, all reporting no version) then fail node-gyp's own probing entirely. Extract the uv-based Python resolution into scripts/lib/python.mjs and a new scripts/build-native.mjs that both build:native and setup now go through, so either command resolves Python the same way and build:native works on its own.
Now that uv is a documented, required PATH prerequisite, drop the PATH-plus-common-install-locations search fallback and call uvx directly, matching how CI already gets it (astral-sh/setup-uv puts it on PATH for the job).
Add uv to the Prerequisites table (it resolves Python for node-gyp and runs clang-format), note that scripts assume it resolves via PATH, and simplify the manual build:native example now that Python resolution happens automatically.
…de-gyp resolvePython silently fell back to node-gyp's own Python discovery when uv wasn't found or failed — the exact path that produced the original bug report's wall of 'could not be run' errors across five broken system Pythons. uv is now a documented, required prerequisite, so treat it as one: throw a single clear message pointing to CONTRIBUTING.md instead of deferring. Verified both paths: a real build succeeds, and stripping uv from PATH now fails with one clean line instead of node-gyp's cascade.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Initial implementation of tfjs-native: a TypeScript wrapper around the original TensorFlow C API (via an N-API addon), going beyond
@tensorflow/tfjs's ~300 mapped ops to the full op registry.Delivers milestones M0–M5:
version().Tensor(dtype/shape,data/array,dispose),tensor/scalar/zeros/ones,tidy.int64is first-class viaBigInt64Array.execute()+TFE_Context; ~1,295 typed op wrappers generated fromTF_GetAllOpListvia protobufjs (including ops tfjs lacks:scatterNd,gatherNd,nonMaxSuppressionV2, ...), plus a hand-written set with friendlier signatures.loadSavedModel/predict/run, with signatures parsed from the MetaGraphDef by a small hand-rolled protobuf reader (no added runtime dependency).grads/valueAndGrads): the C API has no eager gradient tape, so a traced computation is rebuilt as aTF_Graphand differentiated withTF_AddGradients, reusing TensorFlow's own 140 registered gradients instead of hand-written ones.Variable,sgd/adam/rmsprop, andminimize(with a cached loss graph so a training loop doesn't rebuild it every step).node-gyp-build), automatic libtensorflow fetch with checksum verification, and a manual-dispatch release workflow (npm trusted publishing, noNPM_TOKEN).Tooling: Bun (package manager/runtime/test runner), tsdown (dual ESM+CJS build), Biome (JS/TS/JSON) + clang-format via uv (C++), Node 22+, Python via uv for
node-gyp. A singlebun run setupdoes install → fetch libtensorflow → build the addon → verify it loads, including Windowstensorflow.dllresolution with no manualPATHsetup.Docs:
docs/DESIGN.md(architecture),docs/RULES.md(conventions),docs/FUNCTIONS.md(API reference),docs/STATUS.md(milestone tracker) — English-canonical with Japanese translations underdocs/ja/.Why?
To let Node.js reach the full TensorFlow op registry and train/run real models without depending on
@tensorflow/tfjs's narrower op coverage or a Python runtime.Related issue(s)
closes
Check
CONTRIBUTING.md(rules)bun run check(lint & format)bun run typecheckbun test(73 pass, native addon built and exercised)bun run build(dist/)bun run check:cpp(clang-format)