Skip to content

Commit 358786f

Browse files
committed
tflite-runtime: Add version 2.14.0
Builds the TensorFlow Lite interpreter's pybind11 extension for riscv64 the way upstream's own pip recipe does, tensorflow/lite/tools/pip_package/build_pip_package_with_bazel.sh: one bazel target, //tensorflow/lite/python/interpreter_wrapper:_pywrap_tensorflow_interpreter_wrapper, copied next to interpreter.py and the metrics shims and packaged with setup_with_binary.py. No TensorFlow core, no MLIR converter, no flex delegate. 2.14.0 is the last tflite-runtime release, so the build is the 2023 monorepo's: bazel 6.1.0 in .bazelversion (7.5.0 is bootstrapped instead, since nothing on the TensorFlow side gates it beyond a versions.check("1.0.0") minimum), XNNPACK b9d4073a, whose riscv64 production microkernels are scalar-only, so neither RVV nor its zvfh fp16 variant is compiled, and pybind11 2.10.4, which is what caps the matrix at cp311. cp310 is the floor because the manylinux riscv64 image ships no older interpreter. The one riscv64 gap is hermetic Python: TensorFlow 2.14 takes both the Python and the numpy headers from rules_python 0.23.1's downloaded interpreter, which has no riscv64 build - and would be the wrong interpreter for the wheel even if it did. Both repositories are overridden with the container's own CPython and its numpy through bazel's --override_repository, so no patch to the checkout is needed. Tested by running add.bin through the installed wheel's Interpreter and comparing the result against three times the input.
1 parent afa5c8f commit 358786f

2 files changed

Lines changed: 326 additions & 0 deletions

File tree

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
# SPDX-FileCopyrightText: 2026 The RISE Project
2+
# SPDX-License-Identifier: MIT
3+
#
4+
# This workflow is based on:
5+
# https://github.com/tensorflow/tensorflow/blob/v2.14.0/tensorflow/lite/tools/pip_package/build_pip_package_with_bazel.sh
6+
---
7+
name: Build tflite-runtime wheels (riscv64)
8+
9+
on:
10+
workflow_dispatch:
11+
inputs:
12+
version:
13+
description: 'Version glob to (re)build; empty builds every version of docs/packages/tflite-runtime.yaml not released yet'
14+
required: false
15+
default: ''
16+
pull_request:
17+
branches: [main]
18+
paths:
19+
- '.github/workflows/build-tflite-runtime.yml'
20+
- 'docs/packages/tflite-runtime.yaml'
21+
push:
22+
branches: [main]
23+
paths:
24+
- '.github/workflows/build-tflite-runtime.yml'
25+
- 'docs/packages/tflite-runtime.yaml'
26+
27+
run-name: build-tflite-runtime ${{ inputs.version && format('- {0}', inputs.version) || '' }}
28+
29+
concurrency:
30+
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
31+
cancel-in-progress: true
32+
33+
permissions:
34+
contents: read
35+
36+
env:
37+
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64
38+
# No bazel release publishes a riscv64 binary, so it is bootstrapped from source;
39+
# 7.5.0 is the version this repo bootstraps, and TensorFlow's only version gate is
40+
# workspace2.bzl's versions.check("1.0.0").
41+
BAZEL_VERSION: '7.5.0'
42+
RULES_PYTHON_VERSION: '0.33.2'
43+
RULES_JAVA_VERSION: '7.6.5'
44+
45+
jobs:
46+
setup:
47+
uses: $/.github/workflows/_setup.yml
48+
with:
49+
package: tflite-runtime
50+
version: ${{ inputs.version }}
51+
52+
bazel:
53+
needs: [setup]
54+
if: needs.setup.outputs.versions != '[]'
55+
name: Bootstrap bazel (riscv64)
56+
runs-on: ubuntu-24.04-riscv
57+
timeout-minutes: 720
58+
59+
steps:
60+
- name: Restore bazel binary
61+
id: cache
62+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
63+
with:
64+
path: bazel-bin
65+
key: bazel-${{ env.BAZEL_VERSION }}-manylinux_riscv64
66+
67+
- name: Bootstrap bazel ${{ env.BAZEL_VERSION }}
68+
if: steps.cache.outputs.cache-hit != 'true'
69+
run: |
70+
mkdir -p bazel-bin
71+
docker run --rm -i --network=host \
72+
-v "${GITHUB_WORKSPACE}:/work" \
73+
-w /work \
74+
-e BAZEL_VERSION \
75+
-e RULES_PYTHON_VERSION \
76+
-e RULES_JAVA_VERSION \
77+
"${MANYLINUX_RISCV64_IMAGE}" \
78+
bash <<'SCRIPT'
79+
set -eux
80+
81+
dnf install -y --setopt=install_weak_deps=False java-21-openjdk-devel zip unzip
82+
JAVA_HOME="$(dirname "$(dirname "$(readlink -f "$(command -v javac)")")")"
83+
export JAVA_HOME
84+
85+
# rules_python 0.33.2's PLATFORMS has no riscv64 entry, aborting the bootstrap
86+
# (bazelbuild/bazel#23018). Any linux entry is a safe stand-in: the toolchain it names
87+
# is never selected on a riscv64 host. Fixed in bazel 8.2.0; the 7.x backport is open.
88+
mkdir -p /tmp/rules_python
89+
curl -fsSLo /tmp/rules_python.tar.gz "https://github.com/bazel-contrib/rules_python/releases/download/${RULES_PYTHON_VERSION}/rules_python-${RULES_PYTHON_VERSION}.tar.gz"
90+
tar -xzf /tmp/rules_python.tar.gz -C /tmp/rules_python --strip-components=1
91+
sed -i 's|fail("No platform declared for host OS {} on arch {}".format(os_name, arch))|return "x86_64-unknown-linux-gnu"|' \
92+
/tmp/rules_python/python/private/toolchains_repo.bzl
93+
94+
# rules_java 7.x maps riscv64 to a stray-colon include path, so a JNI library
95+
# can't find jni_md.h. Fixed in rules_java 8.x, never backported.
96+
mkdir -p /tmp/rules_java
97+
curl -fsSLo /tmp/rules_java.tar.gz "https://github.com/bazelbuild/rules_java/releases/download/${RULES_JAVA_VERSION}/rules_java-${RULES_JAVA_VERSION}.tar.gz"
98+
tar -xzf /tmp/rules_java.tar.gz -C /tmp/rules_java
99+
sed -i 's|\[":include/linux"\]|["include/linux"]|g' /tmp/rules_java/toolchains/BUILD
100+
101+
mkdir -p /tmp/bazel-src
102+
cd /tmp/bazel-src
103+
curl -fsSLo dist.zip "https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel-${BAZEL_VERSION}-dist.zip"
104+
unzip -q dist.zip
105+
106+
EXTRA_BAZEL_ARGS="--tool_java_runtime_version=local_jdk \
107+
--override_module=rules_python=/tmp/rules_python \
108+
--override_module=rules_java=/tmp/rules_java" \
109+
bash ./compile.sh
110+
install -m 0755 output/bazel /work/bazel-bin/bazel
111+
SCRIPT
112+
113+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
114+
with:
115+
name: bazel-${{ env.BAZEL_VERSION }}-riscv64
116+
path: bazel-bin/bazel
117+
if-no-files-found: error
118+
119+
build_wheels:
120+
name: Build tflite-runtime ${{ matrix.version }} ${{ matrix.tag }}-manylinux_riscv64
121+
runs-on: ubuntu-24.04-riscv
122+
timeout-minutes: 720
123+
needs: [setup, bazel]
124+
if: needs.setup.outputs.versions != '[]'
125+
126+
strategy:
127+
fail-fast: false
128+
matrix:
129+
version: ${{ fromJSON(needs.setup.outputs.versions) }}
130+
# Upstream builds 3.8-3.11 and 2.14.0 is the last release; TensorFlow 2.14
131+
# pins pybind11 2.10.4, which predates CPython 3.12. 3.8/3.9 are out because
132+
# the manylinux riscv64 image ships no interpreter older than 3.10.
133+
# tag is a real dimension: legs introduced only through include collapse
134+
# into a single job, keeping the last one (gotcha 402).
135+
tag: [cp310, cp311]
136+
include:
137+
- {tag: cp310, python: '3.10'}
138+
- {tag: cp311, python: '3.11'}
139+
140+
env:
141+
TFLITE_RUNTIME_VERSION: ${{ matrix.version }}
142+
143+
steps:
144+
- name: Checkout tensorflow/tensorflow ${{ matrix.version }}
145+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
146+
with:
147+
repository: tensorflow/tensorflow
148+
ref: v${{ env.TFLITE_RUNTIME_VERSION }}
149+
path: tensorflow
150+
fetch-depth: 1
151+
persist-credentials: false
152+
153+
- name: Download bazel
154+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
155+
with:
156+
name: bazel-${{ env.BAZEL_VERSION }}-riscv64
157+
path: bazel-bin
158+
159+
- name: Build wheel
160+
env:
161+
PYTHON_VERSION: ${{ matrix.python }}
162+
PIP_EXTRA_INDEX_URL: https://pypi.riseproject.dev/simple/
163+
run: |
164+
mkdir -p wheelhouse
165+
set -o pipefail
166+
docker run --rm -i --network=host \
167+
-v "${GITHUB_WORKSPACE}:/work" \
168+
-w /work \
169+
-e PYTHON_VERSION \
170+
-e PIP_EXTRA_INDEX_URL \
171+
-e TFLITE_RUNTIME_VERSION \
172+
"${MANYLINUX_RISCV64_IMAGE}" \
173+
bash <<'SCRIPT' 2>&1 | tee build.log
174+
set -eux
175+
176+
dnf install -y --setopt=install_weak_deps=False java-21-openjdk-devel zip unzip
177+
JAVA_HOME="$(dirname "$(dirname "$(readlink -f "$(command -v javac)")")")"
178+
export JAVA_HOME
179+
install -m 0755 /work/bazel-bin/bazel /usr/local/bin/bazel
180+
181+
PYTAG="cp${PYTHON_VERSION/./}"
182+
PYTHON_BIN="/opt/python/${PYTAG}-${PYTAG}/bin/python"
183+
export PATH="/opt/python/${PYTAG}-${PYTAG}/bin:${PATH}"
184+
"${PYTHON_BIN}" -m pip install -q -U --only-binary=:all: pip setuptools wheel auditwheel numpy
185+
186+
cd /work/tensorflow
187+
188+
test "$(awk -F"'" '/^_VERSION = /{print $2}' tensorflow/tools/pip_package/setup.py)" \
189+
= "${TFLITE_RUNTIME_VERSION}"
190+
191+
# TensorFlow 2.14 takes both the Python and the numpy headers from rules_python's
192+
# hermetic interpreter, which has no riscv64 build at the pinned 0.23.1 (and would
193+
# be the wrong interpreter for the wheel anyway). Stand in the container's own
194+
# CPython and its numpy for the two repositories that provide them.
195+
mkdir -p /tmp/override/python /tmp/override/pypi_numpy
196+
cp -r "$("${PYTHON_BIN}" -c 'import sysconfig; print(sysconfig.get_paths()["include"])')" \
197+
/tmp/override/python/include
198+
cp -r "$("${PYTHON_BIN}" -c 'import numpy; print(numpy.get_include())')" \
199+
/tmp/override/pypi_numpy/include
200+
: > /tmp/override/python/WORKSPACE
201+
: > /tmp/override/pypi_numpy/WORKSPACE
202+
ln -s "${PYTHON_BIN}" /tmp/override/python/python3
203+
echo 'interpreter = "@python//:python3"' > /tmp/override/python/defs.bzl
204+
cat > /tmp/override/python/BUILD <<'BUILD_FILE'
205+
package(default_visibility = ["//visibility:public"])
206+
207+
exports_files(["python3"])
208+
209+
cc_library(
210+
name = "python_headers",
211+
hdrs = glob(["include/**/*.h"]),
212+
includes = ["include"],
213+
)
214+
BUILD_FILE
215+
cat > /tmp/override/pypi_numpy/BUILD <<'BUILD_FILE'
216+
package(default_visibility = ["//visibility:public"])
217+
218+
filegroup(name = "pkg")
219+
220+
cc_library(
221+
name = "numpy_headers",
222+
hdrs = glob(["include/**/*.h"]),
223+
includes = ["include"],
224+
)
225+
BUILD_FILE
226+
227+
# --nosubcommands undoes the script's -s, which prints every one of the tens of
228+
# thousands of compile commands; no remote JDK is published for riscv64.
229+
export CUSTOM_BAZEL_FLAGS="--nosubcommands --noenable_bzlmod \
230+
--java_runtime_version=local_jdk --tool_java_runtime_version=local_jdk \
231+
--override_repository=python=/tmp/override/python \
232+
--override_repository=pypi_numpy=/tmp/override/pypi_numpy \
233+
--curses=no --show_progress_rate_limit=60"
234+
bash tensorflow/lite/tools/pip_package/build_pip_package_with_bazel.sh
235+
236+
auditwheel repair --plat manylinux_2_39_riscv64 -w /work/wheelhouse \
237+
tensorflow/lite/tools/pip_package/gen/tflite_pip/*/dist/*.whl
238+
SCRIPT
239+
240+
- name: Upload build log
241+
if: failure()
242+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
243+
with:
244+
name: tflite-runtime-${{ env.TFLITE_RUNTIME_VERSION }}-${{ matrix.tag }}-build-log
245+
path: build.log
246+
247+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
248+
with:
249+
name: tflite-runtime-${{ env.TFLITE_RUNTIME_VERSION }}-${{ matrix.tag }}-manylinux_riscv64
250+
path: wheelhouse/*.whl
251+
if-no-files-found: error
252+
253+
- name: Test wheel
254+
env:
255+
PYTHON_VERSION: ${{ matrix.python }}
256+
PIP_EXTRA_INDEX_URL: https://pypi.riseproject.dev/simple/
257+
run: |
258+
docker run --rm -i --network=host \
259+
-v "${GITHUB_WORKSPACE}:/work" \
260+
-w /work \
261+
-e PYTHON_VERSION \
262+
-e PIP_EXTRA_INDEX_URL \
263+
-e TFLITE_RUNTIME_VERSION \
264+
"${MANYLINUX_RISCV64_IMAGE}" \
265+
bash <<'SCRIPT'
266+
set -eux
267+
268+
PYTAG="cp${PYTHON_VERSION/./}"
269+
PYTHON_BIN="/opt/python/${PYTAG}-${PYTAG}/bin/python"
270+
"${PYTHON_BIN}" -m pip install --only-binary=:all: /work/wheelhouse/*.whl
271+
272+
# /work/tensorflow would shadow the installed package with the checkout
273+
cd /tmp
274+
MODEL=/work/tensorflow/tensorflow/lite/testdata/add.bin \
275+
"${PYTHON_BIN}" - <<'PY'
276+
import os
277+
278+
import numpy as np
279+
280+
import tflite_runtime
281+
from tflite_runtime import interpreter as tflite
282+
283+
assert tflite_runtime.__version__ == os.environ["TFLITE_RUNTIME_VERSION"]
284+
assert tflite._interpreter_wrapper.__file__.endswith(
285+
".so"
286+
), tflite._interpreter_wrapper.__file__
287+
288+
# add.bin adds its input to itself and then adds the input once more
289+
interpreter = tflite.Interpreter(model_path=os.environ["MODEL"], num_threads=2)
290+
interpreter.allocate_tensors()
291+
inp = interpreter.get_input_details()[0]
292+
assert tuple(inp["shape"]) == (1, 8, 8, 3), inp["shape"]
293+
assert inp["dtype"] == np.float32, inp["dtype"]
294+
data = np.arange(192, dtype=np.float32).reshape(1, 8, 8, 3)
295+
interpreter.set_tensor(inp["index"], data)
296+
interpreter.invoke()
297+
out = interpreter.get_tensor(interpreter.get_output_details()[0]["index"])
298+
np.testing.assert_array_equal(out, data * 3.0)
299+
PY
300+
SCRIPT
301+
302+
publish:
303+
name: Publish tflite-runtime ${{ matrix.version }}
304+
needs: [setup, build_wheels]
305+
if: needs.setup.outputs.versions != '[]'
306+
strategy:
307+
fail-fast: false
308+
matrix:
309+
version: ${{ fromJSON(needs.setup.outputs.versions) }}
310+
permissions:
311+
contents: write
312+
pull-requests: write
313+
uses: $/.github/workflows/_publish-wheel.yml
314+
secrets:
315+
app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }}
316+
with:
317+
artifact-pattern: tflite-runtime-${{ matrix.version }}-*-manylinux_riscv64

‎docs/packages/tflite-runtime.yaml‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package-name: tflite-runtime
2+
source-code: https://github.com/tensorflow/tensorflow
3+
license: Apache-2.0
4+
comment: |
5+
Built for CPython 3.10 and 3.11 only. 2.14.0 is the last tflite-runtime release
6+
and TensorFlow 2.14 pins pybind11 2.10.4, which predates CPython 3.12; the
7+
successor project is ai-edge-litert.
8+
versions:
9+
- version: 2.14.0

0 commit comments

Comments
 (0)