Full Python 3.14 runtime for iOS/iPadOS with 30+ offline libraries including real PyTorch + HuggingFace transformers + Rust tokenizers. No JIT, App Store safe.
New: Full
import torch(v2.1),import transformers(v4.41), andimport tokenizers(v0.19, real Rust cross-compile) all work on-device. Train and fine-tune transformer models on an iPad with zero network. Full integration test: 24/24 passing.
GPU (Metal): Two of the Metal-accelerated pieces are also published as standalone repos — cairometal (a pycairo-compatible GPU cairo on Metal) and torchmetal (routes PyTorch's hot inference ops to Metal/MPS, designed for iOS). Both are wired into this package's
Package.swiftas theCairoMetalandTorchMetalproducts. The fullcairo(metal)engine used by manim's GPU path stays bundled in this repo (app_packages), unchanged.
- Monaco code editor with IntelliSense running in a WKWebView — Python keyword snippets, signature help (~70-entry SIG_DB), hover docs, and resolve-from-Python for numpy / scipy / sklearn / matplotlib / sympy completions. See
CodeBench/MonacoEditorView.swift. - Auto-save: edits persist to disk on every keystroke (debounced ~600 ms) plus on run, tab-switch, view-disappear, and app-backgrounding. Fixes the "edit
a.tex, reopen, 0 B" bug. - Tombstone system — files deleted via the file browser trash icon,
rm/rmdirin the shell, or ncdu'sdkey are recorded in<Workspace>/.offlinai_deletedso the starter-script seeder (pip_demo.py,torch_test_all.py, etc.) no longer re-creates them on next launch. - LaTeX bundle expanded — 33 MB texmf tree now ships with full Latin Modern Type 1 fonts, expl3 code (1.3 MB), firstaid, graphics-def, hyphenation, stringenc, unicode-data, and pdftex.map. Math-mode rendering via SwiftMath is unlimited and reliable; the native
pdflatexbuiltin is gated off pending replacement of the 2019-erapdftex.xcframework(see Media docs). - Shell builtins:
pdflatex/latex/tex/pdftex/xelatex/latex-diagnose,ncduwith raw arrow-key navigation and real-ncdu styling,topwith Apple-chip detection,git clonevia zipball fetch, and universal--help/-hinterception. - matplotlib shim hardening — user scripts no longer crash on chained attribute access like
ax.xaxis.line.set_color(...)orax.title.set_text(...). The plotly-backed matplotlib compatibility shim now uses a chainable_NoopChainablesingleton for unshimmed attributes (across bothpyplot.pyand the five module stubs inmpl_toolkits/mplot3d/__init__.py—_Art3DModule,_Proj3DModule,_Axis3DModule,_ProjectionModule, plus the_art3d_getattr_fallback), so any chain into a missing attribute degrades silently toNoneinstead of raising'function' object has no attribute Xmid-chain. Plus a swathe of type-mismatch fixes:get_xticks/get_yticksnewly added (were missing on_Axesand crashing via the fallback),get_legend_handles_labelsactually discovers labelled artists fromlines/patches/collections/containers,get_arrayreturnsnp.array([])notNone, an_AxisLinestub for the axis-baseline styling pattern,set_title(loc=, pad=, y=)andset_xticks(minor=)no longer silently swallow upstream kwargs, and the__figure__sentinel that was leaking intofig.update_layout(raisingInvalid property '__figure__'and aborting the whole layout) is now unpacked into the top level. Result: full styling — titles, axis ranges, background colors — actually applies to plotly-rendered charts. - Manim 4K/8K rendering is now memory-safe — the animated-GIF assembly buffer is bounded (capped + downsampled at capture) and full-resolution frames stream to the mp4 instead of being accumulated, so memory no longer scales with
resolution × frame_count. With a resolution-tiered RAM pre-flight andmallocpressure relief on top, manim renders up to 8K (7680×4320) without tripping iOS's jetsam limit; quality is selectable 480p→8K. Encode usesh264_videotoolbox; manim's rasterization stays on cairo's CPU backend — a GPU cairo (CairoMetal) exists but doesn't accelerate manim (its bottleneck is Python interpolation, not cairo). See docs/manim.md and python-ios-lib issue #1.
This SPM package only ships the Python libraries (numpy, manim, scipy, sklearn, matplotlib, …). The CPython 3.14 C runtime itself comes from BeeWare's Python-Apple-support. Below is the full one-time recipe.
mkdir -p _vendor/beeware && cd _vendor/beeware
gh release download 3.14-b9 -R beeware/Python-Apple-support \
-p "Python-3.14-iOS-support.b9.tar.gz"
tar -xzf Python-3.14-iOS-support.b9.tar.gz
# You now have: Python.xcframework (~124 MB, slices: ios-arm64,
# ios-arm64_x86_64-simulator, plus a shared lib/ tree)- Drag
_vendor/beeware/Python.xcframeworkinto the Xcode sidebar (uncheck "Copy items if needed" — leave it where it is) - Target → General → Frameworks, Libraries, and Embedded Content → set its Embed column to Embed & Sign
- File → Add Package Dependencies →
https://github.com/yu314-coder/python-ios-lib - Tick Manim — every transitive dep (NumPy, SciPy, Sklearn, Matplotlib, Pillow, Cairo, FFmpegPyAV, SymPy, NetworkX, Pygments, Click, Cloup, Decorator, Tqdm, Rich, Mapbox_earcut, Isosurfaces, Jinja2, Markupsafe, Pydub, Psutil, Watchdog, Screeninfo, Moderngl, Moderngl_window, Typing_extensions, BeautifulSoup, LaTeXEngine, Plotly) gets pulled in automatically — ~30 bundles in total
- After Xcode resolves, each product lands at
<App>.app/python-ios-lib_<Product>.bundle/<package_name>/
BeeWare keeps the stdlib outside the xcframework slices (at
Python.xcframework/lib/python3.14/). Xcode does NOT auto-bundle it.
Also, SwiftPM strips Mach-O code signatures when copying .so/.dylib
resources, and iOS dlopen rejects unsigned dylibs — so a re-sign loop
is mandatory.
In Build Settings, set ENABLE_USER_SCRIPT_SANDBOXING = NO (the
script writes into the .app bundle).
Add a Run Script build phase (Build Phases → +) with shell /bin/bash:
#!/bin/bash
set -e
BEEWARE="${SRCROOT}/_vendor/beeware/Python.xcframework"
PYIOSLIB_FW="${SRCROOT}/_vendor/python-ios-lib/Frameworks" # adjust if needed
APP="${BUILT_PRODUCTS_DIR}/${CONTENTS_FOLDER_PATH}"
DST="$APP/python-stdlib"
FRAMEWORKS="${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}"
IDENT="${EXPANDED_CODE_SIGN_IDENTITY:--}"
case "$PLATFORM_NAME" in
iphoneos) SLICE="ios-arm64" ;;
iphonesimulator) SLICE="ios-arm64_x86_64-simulator" ;;
esac
ARCH="${CURRENT_ARCH:-arm64}"; [ "$ARCH" = "undefined_arch" ] && ARCH="arm64"
# 1. Copy stdlib + per-arch lib-dynload + sysconfigdata.
mkdir -p "$DST"
rsync -a --delete --exclude '__pycache__' --exclude 'lib-dynload' \
"$BEEWARE/lib/python3.14/" "$DST/"
rsync -a --delete \
"$BEEWARE/$SLICE/lib-$ARCH/python3.14/lib-dynload/" "$DST/lib-dynload/"
find "$BEEWARE/$SLICE/lib-$ARCH/python3.14" -maxdepth 1 \
-name "_sysconfigdata__*.py" -exec cp -f {} "$DST/" \;
# 2. Copy standalone dylibs from python-ios-lib/Frameworks/ into app
# Frameworks/ (libfortran_io_stubs.dylib for scipy.special, ffmpeg dylibs
# for PyAV, etc.).
mkdir -p "$FRAMEWORKS"
if [ -d "$PYIOSLIB_FW" ]; then
while IFS= read -r -d '' src; do
base=$(basename "$src")
cp -f "$src" "$FRAMEWORKS/$base"
codesign --force --sign "$IDENT" --timestamp=none "$FRAMEWORKS/$base" 2>/dev/null || true
done < <(find "$PYIOSLIB_FW" -maxdepth 2 -name "*.dylib" -print0)
fi
# 3. Re-sign every .so/.dylib in stdlib + python-ios-lib_*.bundle.
sign_lib() {
codesign --force --sign "$IDENT" --timestamp=none \
--preserve-metadata=identifier,entitlements,flags "$1" 2>/dev/null \
|| codesign --force --sign "$IDENT" --timestamp=none "$1"
}
find "$DST/lib-dynload" -name "*.so" -print0 | while IFS= read -r -d '' so; do sign_lib "$so"; done
for b in "$APP"/python-ios-lib_*.bundle; do
[ -d "$b" ] || continue
while IFS= read -r -d '' lib; do sign_lib "$lib"; done \
< <(find "$b" \( -name "*.so" -o -name "*.dylib" \) -print0)
done
# 4. Rewrite PyAV's hardcoded /tmp/ffmpeg-ios/ install_names → @rpath.
AV="$APP/python-ios-lib_FFmpegPyAV.bundle"
fix_names() {
for old in $(otool -L "$1" 2>/dev/null | awk '/\/tmp\/ffmpeg-ios/{print $1}'); do
install_name_tool -change "$old" "@rpath/$(basename "$old")" "$1" 2>/dev/null || true
done
for old in $(otool -D "$1" 2>/dev/null | awk 'NR==2 && /\/tmp\/ffmpeg-ios/{print $1}'); do
install_name_tool -id "@rpath/$(basename "$old")" "$1" 2>/dev/null || true
done
}
for lib in "$FRAMEWORKS"/libav*.dylib "$FRAMEWORKS"/libsw*.dylib; do
[ -f "$lib" ] && { fix_names "$lib"; sign_lib "$lib"; }
done
if [ -d "$AV/av" ]; then
while IFS= read -r -d '' so; do fix_names "$so"; sign_lib "$so"; done \
< <(find "$AV/av" -name "*.so" -print0)
fi
# 5. (Optional) Bundle Monaco editor with directory structure preserved.
# Synchronized-folder mode flattens subdirs and Monaco has duplicate file
# names across language subfolders — so rsync into the .app at build time
# instead of adding it as a normal resource.
MONACO_SRC="${SRCROOT}/_vendor/python-ios-lib/Monaco"
if [ -d "$MONACO_SRC" ]; then
rsync -a --delete "$MONACO_SRC/" "$APP/Monaco/"
fiSet these env vars before Py_Initialize() — note the _PYTHON_SYSCONFIGDATA_NAME
choice has to match the slice/arch you built for, otherwise pydoc
(transitively imported by scipy) crashes with AttributeError 'installed_base':
let bundleURL = Bundle.main.bundleURL
let stdlib = bundleURL.appendingPathComponent("python-stdlib")
let dynload = stdlib.appendingPathComponent("lib-dynload")
// Auto-discover every python-ios-lib_*.bundle sibling.
var libBundles: [String] = []
if let entries = try? FileManager.default.contentsOfDirectory(atPath: bundleURL.path) {
for n in entries where n.hasPrefix("python-ios-lib_") && n.hasSuffix(".bundle") {
libBundles.append(bundleURL.appendingPathComponent(n).path)
}
}
let pythonPath = ([stdlib.path, dynload.path] + libBundles).joined(separator: ":")
setenv("PYTHONHOME", stdlib.path, 1)
setenv("PYTHONPATH", pythonPath, 1)
setenv("PYTHONNOUSERSITE", "1", 1)
setenv("PYTHONDONTWRITEBYTECODE","1", 1)
setenv("PYTHONMALLOC", "malloc", 1) // CRITICAL — must be before Py_Initialize
setenv("TMPDIR", NSTemporaryDirectory(), 1)
// BeeWare per-arch sysconfigdata — pick the matching name for your build:
// sim arm64: "_sysconfigdata__ios_arm64-iphonesimulator"
// sim x86_64: "_sysconfigdata__ios_x86_64-iphonesimulator"
// device: "_sysconfigdata__ios_arm64-iphoneos"
setenv("_PYTHON_SYSCONFIGDATA_NAME", "_sysconfigdata__ios_arm64-iphoneos", 1)
Py_Initialize()
PyEval_SaveThread() // release GIL so manim's worker threads can runsetenv("OFFLINAI_LATEX_BACKEND", "busytex", 1)
setenv("OFFLINAI_LATEX_FORCE_BUSYTEX", "1", 1)
setenv("OFFLINAI_LATEX_USE_PDFTEX", "0", 1)(Bundle CodeBench's Resources/Busytex/ and BusytexEngine.swift +
LaTeXEngine.swift per the LaTeX section earlier in this README.)
import importlib.metadata
for d in importlib.metadata.distributions():
print(d.metadata["Name"], d.version)You should see ~30 distributions listed. If empty, your PYTHONPATH
isn't including the python-ios-lib_*.bundle paths — re-check step 5's
auto-discovery loop.
import manim, numpy, scipy, sklearn, sympy, networkx, av, cairo
print("all imports ok")If any line fails with Trying to load an unsigned library or
Library not loaded: /tmp/ffmpeg-ios/..., the Run Script in step 4
didn't run. Check Build Settings → ENABLE_USER_SCRIPT_SANDBOXING = NO
and that the script is in the build phases list.
After build, the .app contains:
YourApp.app/
├── YourApp ← executable
├── Frameworks/
│ ├── Python.framework/Python ← BeeWare CPython dylib
│ ├── libfortran_io_stubs.dylib ← from step 4.2
│ ├── libsf_error_state.dylib ← from step 4.2
│ ├── libscipy_blas_stubs.dylib ← dcabs1 helper (scipy.linalg)
│ └── libav*.dylib, libsw*.dylib ← FFmpeg, install_name fixed
├── python-stdlib/ ← step 4.1 (BeeWare stdlib)
│ ├── os.py, json/, encodings/, …
│ ├── _sysconfigdata__ios_*.py
│ └── lib-dynload/*.so ← signed
├── python-ios-lib_NumPy.bundle/numpy/
├── python-ios-lib_Manim.bundle/{manim, manimpango, offlinai_latex,
│ svgelements, pathops, *-dist-info}
├── python-ios-lib_SciPy.bundle/scipy/
├── python-ios-lib_Sklearn.bundle/sklearn/
├── python-ios-lib_Matplotlib.bundle/matplotlib/
├── python-ios-lib_FFmpegPyAV.bundle/{av, ffmpeg/*.dylib}
├── python-ios-lib_CairoGraphics.bundle/{cairo, pango, harfbuzz}
├── python-ios-lib_Pillow.bundle/PIL/
└── python-ios-lib_*.bundle/ ← 24+ more, one per ticked product
PYTHONPATH should include python-stdlib/, python-stdlib/lib-dynload/,
and every python-ios-lib_*.bundle/ directory. Step 5's auto-discovery
loop handles that automatically.
Submitting an app with this package to App Store Connect surfaces
~15 distinct categories of ITMS-9xxxx validation errors,
because Apple's bundle rules don't accept the typical Python-iOS
layout that BeeWare's install_python produces.
This section documents every category we hit and how they're fixed. There are two classes of rejection, and which fix you need depends on how you consume this package:
- (A) Loose Mach-O →
ITMS-90171"binary file is not permitted". Apple forbids any.so/.dylibthat isn't inside a.framework. Every app hits this; the fix depends on your layout:- Swift Package Manager (you get
python-ios-lib_*.bundle/+python-stdlib/lib-dynload/): usescripts/appstore/wrap-binaries-as-frameworks.shpython_ios_lib_import_hook.py. It wraps every.soinlib-dynload/and the SPM bundles intoFrameworks/*.framework, rewrites the cross-LC_LOAD_DYLIBreferences, and the import hook re-routesimport numpy._core…to the framework at runtime.
- Manual
app_packages/layout (the CodeBench reference app, where BeeWare's binary-module signing already emits.framework+.fworkplaceholders): usescripts/appstore/wrap-loose-dylibs.sh, which fixes up the.fworkpaths and wraps the remaining loose dylibs.
- Swift Package Manager (you get
- (B) Content rejections (private API, static
.a,MinimumOSVersion, bitcode,MH_BUNDLE, macOS-only dylibs, standalone executables) depend on which libraries you bundle.wrap-loose-dylibs.shcarries a fix for each (table below); the same cleanups apply to either layout.
Note: earlier revisions marked
wrap-binaries-as-frameworks.shas "no longer recommended" — that was wrong for SwiftPM consumers and is the root cause of theITMS-90171reports. It is the supported path for the resource-bundle layout. Step-by-step in App Store Connect submission below.
TestFlight + dev (
Run) builds work WITHOUT any of this. App Store submission is the only path that triggers Apple's strict validator.
For a Swift Package Manager app (the common case), these are the steps that
take an archive from ITMS-90171-rejected to accepted:
1. Framework-wrap the loose .so (build phase). Build Phases → + → New Run
Script Phase, shell /bin/bash, placed AFTER "Copy Bundle Resources" / your
stdlib-copy phase and BEFORE Xcode's implicit code-sign:
WRAP="${BUILD_ROOT}/../../SourcePackages/checkouts/python-ios-lib/scripts/appstore/wrap-binaries-as-frameworks.sh"
[ -f "$WRAP" ] && bash "$WRAP"It wraps python-stdlib/lib-dynload/*.so and every
python-ios-lib_*.bundle/**/*.so into Frameworks/<name>.framework/, rewrites
cross-references, flips each binary from MH_BUNDLE to MH_DYLIB and inserts
LC_ID_DYLIB, drops a PrivacyInfo.xcprivacy into the OpenSSL-linked
frameworks, re-signs, and writes python-ios-lib_extension_manifest.txt
for the runtime hook.
The
MH_BUNDLE → MH_DYLIBflip is mandatory. Python.soextensions are Mach-O typeMH_BUNDLE, and Apple's validator rejects a.frameworkwhose executable isMH_BUNDLE— "has type 'BUNDLE'… Only 'EXECUTE' is permitted" (90124), "missing load commands" (90210), "standalone library… not permitted" (90171). The wrap script callsscripts/appstore/fix-macho-type.pyautomatically to do it, so keep that file alongsidewrap-binaries-as-frameworks.sh(both ship inscripts/appstore/) and ensurepython3is on the build machine. Wrapping the.sointo a framework without this flip is the #1 cause of a still-rejected archive.
Privacy manifest (
ITMS-91061). Python's_ssl/_hashlibstatically link OpenSSL/BoringSSL, a "commonly used third-party SDK" — so the wrappedstdlib_ssl.framework/stdlib_hashlib.frameworkmust each carry aPrivacyInfo.xcprivacyor App Store Connect rejects with "Missing privacy manifest" (ITMS-91061). The wrap script copies the minimal manifest atscripts/appstore/PrivacyInfo.xcprivacy(no tracking, no data collection) into every OpenSSL-linked framework before re-signing — keep that file beside the script. If a later upload returnsITMS-91053("missing required-reason API"), add the named reason code to the manifest'sNSPrivacyAccessedAPITypesarray.
2. Install the runtime import hook. Copy python_ios_lib_import_hook.py
into your bundled python-stdlib/, then in your embedding bootstrap —
immediately after Py_Initialize() and BEFORE any user import:
setenv("PYTHON_IOS_LIB_HOOK_DIR",
[[[NSBundle.mainBundle.bundleURL
URLByAppendingPathComponent:@"python-stdlib"] path] UTF8String], 1);
PyRun_SimpleString(
"import sys, os\n"
"sys.path.insert(0, os.environ['PYTHON_IOS_LIB_HOOK_DIR'])\n"
"import python_ios_lib_import_hook\n"
"python_ios_lib_import_hook.install()\n");The hook is fail-safe: if the manifest is absent (a dev Run build that
skipped step 1), install() is a no-op — so the same code path works for both
dev and release.
3. C BLAS stub (only if you used the in-app stub route). Adding
blas_compat_stubs.c to Compile Sources can fail with "Module
'ObjectiveC.NSObject' requires feature 'objc'" / "Could not build module
'Foundation'" — the .c file is inheriting the app's Objective-C prefix
header. Rename it to blas_compat_stubs.m (Objective-C is a superset of C),
or exclude it from the prefix header.
4. Build Settings & Info.plist (full list in the next table):
ENABLE_USER_SCRIPT_SANDBOXING = NO, ENABLE_BITCODE = NO,
IPHONEOS_DEPLOYMENT_TARGET ≥ 17.0, ExecuTorch xcframeworks set to "Do Not
Embed", ITSAppUsesNonExemptEncryption = false, and — if UIBackgroundModes
contains processing — a BGTaskSchedulerPermittedIdentifiers array.
5. Content rejections (category B). Depending on the libraries you bundle
you may still trip the issues in the table below (private API, static .a,
MinimumOSVersion, MH_BUNDLE, …); apply the matching fix for each.
6. dSYM warning. "Upload Symbols Failed: archive did not include a dSYM for
Python.framework" is a non-blocking warning, not a rejection — the prebuilt
Python.framework ships without a dSYM. Untick "Upload your app's symbols"
in the Distribute flow, or ignore it.
Only an actual archive → App Store Connect (or TestFlight) upload runs Apple's full validator, so verify there.
| Issue | Old script | New wrap-loose-dylibs.sh |
|---|---|---|
Loose .so in app_packages/ |
wrapped via Frameworks/<name>.framework/ + import hook |
same wrapping; also rewrites BeeWare's .fwork files to @executable_path/Frameworks/... (absolute paths) so iOS hardened-mode dyld accepts them |
Loose .dylib in Frameworks/ (FFmpeg, libtorch_python, libshm) |
wrapped, refs rewritten | same |
Static .a archives in Frameworks/ (PyTorch ExecuTorch) |
not handled | hard-deleted; ExecuTorch's xcframeworks must be set to "Do Not Embed" in Xcode (linker pulls symbols at compile time, not runtime) |
MinimumOSVersion mismatch (BeeWare writes 13.0, binary is 17.0) |
not handled | rewrites every wrapped framework's Info.plist MinimumOSVersion to match IPHONEOS_DEPLOYMENT_TARGET |
armv7 slice in fat binaries (e.g. ios_system) |
not handled | strips armv7 via lipo -remove |
MH_BUNDLE filetype on Cython .so |
not handled | flips byte 12 of Mach-O header to MH_DYLIB (0x6); inserts LC_ID_DYLIB load command into padding |
| Bitcode segments left in vendored xcframeworks | not handled | xcrun bitcode_strip -r on every binary |
_CTFontCopyDefaultCascadeList private API in manimpango._register_font |
not handled | deletes the C extension; fontTools-backed pure-Python manimpango/__init__.py shim replaces all functionality (bundled in this package) |
_IOPSCopyPowerSourcesInfo private API in psutil._psutil_osx |
not handled | deletes the macOS-only C extension |
_xerbla_array__ flagged as private API in scipy.linalg.cython_lapack |
not handled | renames symbol to _xerbla_arr_io_ via Mach-O byte patching, adds an LC_LOAD_DYLIB to a runtime-built stub .framework, AND patches the bind opcodes + symbol-table n_desc to use flat-namespace lookup |
macOS-arm64 dylibs in scipy/.dylibs/ (libgfortran, libgcc_s, libquadmath) |
not handled | hard-deleted (scipy never actually loads them on iOS — verified via nm -u symbol audit; Apple Accelerate provides the real BLAS/LAPACK) |
torch_shm_manager standalone Mach-O executable |
not handled | replaced with non-executable text placeholder (PyTorch's path-existence check passes; iOS forbids fork() so the real binary couldn't function anyway) |
LC_ENCRYPTION_INFO missing on macOS dylibs |
not handled | resolved indirectly by deleting the macOS dylibs |
Native manimpango Cython .so files use Cairo's toy API which can't extract CJK glyph paths on iOS |
not handled (CJK fell back to PIL rasterizer = opacity fade only, no stroke trace) | fontTools shim reads OTF outlines directly — produces real bezier <path> data per glyph; manim's Write traces strokes natively for CJK exactly like Latin |
torch._C not injected into torch module namespace at import time |
not handled | one-line patch in torch/__init__.py (import torch._C as _C) makes torch._C a regular module attribute regardless of what PyInit__C did |
manim.animation.animation.Animation.interpolate_mobject ignored lag_ratio for ImageMobject children |
not handled (CJK Text faded as a single block) | manim's iOS image-fallback patch now uses get_sub_alpha(...) per-child, producing proper typewriter-style staggered reveal |
After the existing stdlib-copy Run Script (step 4 of the Setup), add
a NEW Run Script Phase. Build Phases → + → New Run Script Phase,
shell /bin/bash:
WRAP="${BUILD_ROOT}/../../SourcePackages/checkouts/python-ios-lib/scripts/appstore/wrap-loose-dylibs.sh"
[ -f "$WRAP" ] && bash "$WRAP"Place AFTER "Copy Bundle Resources" + the stdlib-copy phase, BEFORE Xcode's final implicit code-sign step.
The script also needs two helpers in the same dir, which the wrap
script auto-locates via ${PROJECT_DIR}/scripts/:
fix-macho-type.py— flips MH_BUNDLE → MH_DYLIB and inserts LC_ID_DYLIBpatch-cython-lapack.py— renames_xerbla_array__symbol + adds LC_LOAD_DYLIB + patches bind opcodes for flat-namespace lookup
For convenience, copy them next to the wrap script in your project's
scripts/ directory.
| Setting | Value | Why |
|---|---|---|
ENABLE_USER_SCRIPT_SANDBOXING |
NO |
wrap script needs to write outside its own Build dir |
ENABLE_BITCODE |
NO (or unset; default in Xcode 14+) |
bitcode is deprecated and our strip step assumes it's gone |
IPHONEOS_DEPLOYMENT_TARGET |
17.0 (or higher) |
wrap script harmonizes framework MinimumOSVersion to this value |
ExecuTorch xcframeworks (executorch, backend_xnnpack, backend_coreml, kernels_optimized, threadpool) |
"Do Not Embed" in Frameworks, Libraries, and Embedded Content | static .a archives — link at compile time, never embed |
Info.plist → ITSAppUsesNonExemptEncryption |
<false/> |
skips the encryption-questionnaire prompt every upload (only standard TLS via OpenSSL) |
=== wrap-loose-dylibs.sh starting ===
BUILT_PRODUCTS_DIR=...
CODESIGNING_FOLDER_PATH=...
wrap-loose-dylibs: Step 0 — hard cleanup of App Store-rejected files
removed .../numpy/_core/lib/libnpymath.a
removed .../torch/bin/torch_shm_manager (then re-created as text placeholder below)
... (10-15 files)
wrap-loose-dylibs: Step 0b — harmonizing MinimumOSVersion to 17.0
fixed MinimumOSVersion in 262 framework Info.plists
wrap-loose-dylibs: Step 0b2 — stripping armv7 from fat framework binaries
stripped armv7 from ios_system.framework
1 framework(s) thinned to arm64-only
wrap-loose-dylibs: Step 0b3 — rewriting .fwork files to absolute paths
rewrote 264 .fwork files (skipped 0 already-absolute)
wrap-loose-dylibs: Step 0c — patching scipy cython_lapack for App Store
built libscipy_lapack_stubs.dylib
renamed 2 occurrence(s) of _xerbla_array__ → _xerbla_arr_io_
added LC_LOAD_DYLIB → @rpath/libscipy_lapack_stubs.framework/...
rewrote 2 bind entry(s) to flat-namespace lookup
wrap-loose-dylibs: 11 loose dylibs to wrap
wrap-loose-dylibs: stripping bitcode from binaries
stripped bitcode from 275 binaries
wrap-loose-dylibs: flipping MH_BUNDLE → MH_DYLIB
fix-macho-type summary: 140 already_dylib, 133 patched, 1 patched_fat
LC_ID_DYLIB: 133 added, 140 already present, 1 failed
=== wrap-loose-dylibs: final App Store readiness check ===
✓ static .a archives clean
✓ loose .dylib in Frameworks/ clean
✓ loose .dylib in app_packages/ clean
✓ torch_shm_manager is text placeholder (App Store-safe)
✓ manimpango native .so (CTFontCopyDefaultCascadeList) clean
✓ psutil._psutil_osx (IOPSCopyPowerSources*) clean
✓ App Store readiness OK
If any check fails, the script exits non-zero and Xcode flags the build phase as failed — preventing you from accidentally archiving a known-bad bundle.
The repo bundles already-patched copies of three site-packages
files — these replace the upstream versions and ship inside the
package's app_packages/site-packages/ tree:
| Patched file | What it fixes |
|---|---|
manimpango/__init__.py |
Native _register_font.so and cmanimpango.so (deleted by wrap script for ITMS-90338) — replaced with fontTools-based pure-Python text2svg that reads OTF/TTF glyph outlines directly via SVGPathPen. CJK Text in manim now produces real <path d="..."> per glyph (typewriter-style Write animation works). Bundled fontTools/ (~5 MB pure Python) provides the implementation. |
manim/animation/animation.py |
The iOS image-fallback in interpolate_mobject now staggers ImageMobject opacity by lag_ratio (matching what Write's smart lag_ratio = min(4.0/N, 0.2) expects) — produces proper typewriter reveal across per-character ImageMobject children. Was previously fading all chars at the same alpha. |
torch/__init__.py |
Two iOS-specific patches: (1) import torch._C as _C after from torch._C import * so torch._C is in module namespace even when PyInit__C's re-entrant setattr silently fails on iOS. (2) manager_path() returns the torch_shm_manager text-placeholder path instead of raising RuntimeError("Unable to find...") when the binary was replaced. Lets full PyTorch import succeed; only torch.multiprocessing's fork-based APIs are unavailable (iOS platform limit). |
The fontTools/ directory is bundled at app_packages/site-packages/fontTools/ — required by the patched manimpango shim. It's ~4.8 MB pure Python (no C extensions needed; the SVGPathPen + ttLib paths we use don't require the optional Cython accelerators).
bash scripts/appstore/wrap-loose-dylibs.sh # dry run requires Xcode env vars
# Better: actually archive and inspect the build log for "✓ App Store readiness OK"The script's final assertion fails the build if any rejected file remains, so you can never accidentally upload a broken bundle.
| Symptom | Cause | Fix |
|---|---|---|
Trying to load an unsigned library on first numpy/scipy import |
SwiftPM strips signatures from .so resources |
Run Script in step 4 must re-sign all .so |
symbol not found in flat namespace '_dcabs1_' importing scipy.signal / scipy.linalg on a real device |
Apple's Accelerate doesn't export the reference-BLAS helper dcabs1 (only cython_blas needs it — lsame IS in Accelerate) |
ships Frameworks/scipy_aux/libscipy_blas_stubs.dylib providing it + an LC_LOAD_DYLIB on cython_blas.so so the flat lookup resolves. Already baked into the bundled .so; re-run scripts/appstore/build-blas-stubs.sh after rebuilding scipy. |
Library not loaded: /tmp/ffmpeg-ios/install/lib/libavcodec.62.dylib |
PyAV .so has hardcoded build-time install_names |
install_name_tool -change step in step 4.4 |
AttributeError: 'installed_base' from inside pydoc |
BeeWare per-arch _sysconfigdata not on sys.path |
Step 4.1 copies it; step 5 sets _PYTHON_SYSCONFIGDATA_NAME |
ModuleNotFoundError: No module named 'cloup' (or click, tqdm, rich, …) |
Transitive dep wasn't auto-linked from Manim |
Verify with the bundle layout — every product in step 3 should have a corresponding python-ios-lib_*.bundle dir; if missing, tick it explicitly in Xcode |
Error importing numpy: you should not try to import numpy from its source directory |
numpy's _multiarray_umath.so failed to load (usually unsigned) |
Re-check the codesign loop output in build log |
Empty importlib.metadata.distributions() |
Your earlier Package.swift didn't ship *.dist-info dirs as resources |
Fixed in current Package.swift |
| Render appears to hang | iOS jetsam killing for memory pressure | PYTHONMALLOC=malloc is set in step 5; lower preview quality, avoid long MathTex chains |
App Store: Invalid bundle structure. The 'X.so' binary file is not permitted. (ITMS-90171) |
Apple rejects loose Mach-O outside .framework/; SwiftPM ships .so loose in python-ios-lib_*.bundle/ + python-stdlib/lib-dynload/ |
Run wrap-binaries-as-frameworks.sh + load python_ios_lib_import_hook.py — see App Store Connect submission |
Library not loaded: @rpath/<lib>.dylib (e.g. libfortran_io_stubs.dylib) crashing on a real device after wrapping (field report #2) |
A bare @rpath/<basename>.dylib cross-dep wasn't rewritten to its wrapped loose_<name>.framework, so dyld can't find it |
rewrite_cross_refs now also probes Frameworks/ on disk for the wrapped loose_<stem> framework as a fallback — pull the latest scripts/appstore/wrap-binaries-as-frameworks.sh. Also confirm the loose dylib lands in <App>.app/Frameworks/ before the wrap phase runs, and that the referencing binary carries @executable_path/Frameworks + @loader_path/.. rpaths (the script adds both). |
IndentationError: unexpected indent at ~line 27 of python_ios_lib_import_hook.py (field report #2) |
The file's leading """docstring""" got mangled during copy (delimiters lost), so its indented usage example parses as code |
Copy scripts/appstore/python_ios_lib_import_hook.py verbatim (don't hand-retype or reflow it), or delete the leading docstring block (it's purely documentation). Install it with the \n-joined PyRun_SimpleString("import sys, os\n" …) form in step 2 — not an indented """…""" heredoc, which itself triggers this error. |
App Store: Missing Info.plist value. The Info.plist key 'BGTaskSchedulerPermittedIdentifiers' must contain a list of identifiers when 'UIBackgroundModes' has a value of 'processing'. |
Required when UIBackgroundModes includes processing |
Add BGTaskSchedulerPermittedIdentifiers array to your Info.plist |
Upload Symbols Failed: archive did not include a dSYM for X.dylib |
Release archive is missing debug symbols | Build Settings → Debug Information Format = DWARF with dSYM File for Release; not a rejection — just no crash symbolication |
Pick whichever combination you need. Dependencies auto-resolve.
| Package | What you get | Doc |
|---|---|---|
| CInterpreter | C89/C99/C23 tree-walking interpreter (~3,661 lines) | doc |
| CppInterpreter | C++ interpreter — classes, STL, templates, inheritance (~4,287 lines) | doc |
| FortranInterpreter | Fortran via ofort (Beliavsky, MIT) — modules, allocatable arrays, 45+ intrinsics | doc |
| NumPy | NumPy 2.3.5 — arrays, linalg, FFT, random (native iOS) | doc |
| SymPy | SymPy 1.14 — symbolic math, calculus, solving | doc |
| Plotly | Plotly 6.6 — interactive charts, 3D plots | doc |
| NetworkX | NetworkX 3.6 — graph theory, algorithms | doc |
| Pillow | Pillow 12.2 — image processing (native iOS) | doc |
| BeautifulSoup | BeautifulSoup4 — HTML/XML parsing | doc |
| Requests | HTTP client (GET, POST, sessions, JSON) | doc |
| PyYAML | YAML parser (native iOS) | doc |
| Rich | Rich text, tables, progress bars | doc |
| Tqdm | Progress bars for loops | doc |
| Click | CLI framework | doc |
| Pygments | Syntax highlighting (500+ languages) | doc |
| Mpmath | Arbitrary-precision arithmetic | doc |
| Pydub | Audio manipulation | doc |
| JsonSchema | JSON Schema validation | doc |
| CairoGraphics | Cairo + Pango + HarfBuzz (2D graphics, native iOS) | doc |
| FFmpegPyAV | FFmpeg (7 dylibs) + PyAV video encoding (native iOS) | doc |
| Decorator | Michele Simionato's full decorator package (5.3.1, pure Python) — decorator / contextmanager / dispatch_on |
doc |
| PyWebView | pywebview shim — embed HTML/CSS/JS UI in your iOS app from Python via the host's preview pane (full cookie API + file IPC) | doc |
| Werkzeug | WSGI utilities + dev server (3.1.x, 52 modules). iOS-patched: multiprocessing.Value fallback, reloader auto-disable, preview-signal hook, clean-shutdown hook |
doc · stack |
| Tornado | Async HTTP/WebSocket framework (6.5.x, 73 modules). Pure-Python; macOS speedups.abi3.so stripped, transparent Python fallback. Streamlit's transport, usable standalone |
doc · stack |
| Package | What you get | Auto-includes | Doc |
|---|---|---|---|
| Sklearn | scikit-learn (40 modules, 12K+ lines) | + NumPy | doc |
| SciPy | SciPy (optimize, integrate, signal, stats) | + NumPy | doc |
| Matplotlib | matplotlib (64 modules, Plotly backend) | + Plotly | doc |
| Manim | manim (145+ mobjects, 73 animations) | + NumPy, Matplotlib, FFmpegPyAV, CairoGraphics | doc |
| LaTeXEngine | pdftex.xcframework + 33 MB bundled texmf tree (Latin Modern, amsmath, hyperref, expl3, …). \documentclass{article} end-to-end. |
+ CairoGraphics | doc |
| Flask | Web framework (3.x, 24 modules) on Werkzeug — routes, sessions, templates, blueprints | + Werkzeug, Jinja2, Markupsafe, Click | doc · stack |
| Dash | Plotly-based reactive dashboards (3.x, 211 modules) — callbacks, charts, tables, dcc + html components | + Flask, Werkzeug, Jinja2, Markupsafe, Click, Plotly | doc · stack |
| Streamlit | Script-style dashboards (1.50.x, 213 modules) — declarative widgets, @st.cache_data. iOS-patched: signal-handler skip, preview hook, clean shutdown |
+ Tornado, Click, Watchdog, Typing_extensions, PyArrow | doc · stack |
First public iOS builds of each. Once added, import torch, import transformers, import tokenizers all work on-device with no extra setup.
| Package | What you get | Auto-includes | Doc |
|---|---|---|---|
| PyTorch | PyTorch 2.1.2 native iOS — tensors, autograd, nn, optim, JIT, FFT, LAPACK via Accelerate. 95/95 correctness asserts. Ships libtorch_python.dylib as a ~14 MB LZMA blob (Sources/PyTorch/torch_dylib/libtorch_python.dylib.applzma); the PyTorch Swift package decompresses it to ~99 MB at first launch via Compression.framework. No Git LFS needed. |
regex shim | doc |
| Tokenizers | HuggingFace tokenizers 0.19.1 — real Rust BPE/WordPiece/Unigram trainers cross-compiled for iOS arm64 (PyO3). First public iOS build. | (none) | doc |
| Transformers | HuggingFace transformers 4.41.2 — BERT, GPT-2, T5, BART, Llama, Qwen. Construct + train + .generate() + save/load on-device. |
+ PyTorch, Tokenizers, huggingface_hub, filelock, safetensors, accelerate, peft |
doc |
| Accelerate | HuggingFace Accelerate 0.30.1 — Trainer hard-imports it for device placement, gradient accumulation, mixed precision. Pure Python. |
(none extra) | upstream docs |
| PEFT | HuggingFace PEFT 0.12.0 — LoraConfig + get_peft_model for LoRA / IA3 / prefix tuning. Pure Python. |
+ Transformers | upstream docs |
The safetensors shim is bidirectional: both load_file and save_file work, so model.save_pretrained() and peft.save_pretrained() write valid .safetensors files that load back bit-identical (verified via roundtrip test with fp16/bf16/fp32/int64/bool tensors).
Not yet bundled (require C extensions we haven't cross-compiled): datasets (needs pyarrow + pandas), sentencepiece (needed only for Llama/T5/BART tokenizers — Qwen/GPT-2/most modern HF models work without it), protobuf, evaluate. Workaround for datasets: write a tiny torch.utils.data.Dataset subclass yourself.
app_packages/site-packages/_torch_metal_bridge.py patches torch.matmul, torch.mm, torch.bmm, torch.addmm, F.linear, and F.scaled_dot_product_attention to route matmul-heavy ops onto the iPad GPU via MPSMatrixMultiplication. Auto-installed at Python startup by sitecustomize.py — no code changes required. Supports float32 and float16 natively; bfloat16 is cast to fp32 internally. Typical speedup at transformer training sizes: 2–10× on iPad (CPU on iPad has no AMX, unlike Mac Apple Silicon). The Swift bridge (CodeBench/MetalMatmulBridge.swift) exposes one C entry point via @_cdecl and ctypes reaches it through dlopen(NULL) — OTHER_LDFLAGS exports the symbol explicitly so it survives TestFlight / App Store stripping.
iOS PyTorch was compiled with USE_NUMPY=0 to keep the binary self-contained, so torch.from_numpy() and tensor.numpy() raise. sitecustomize.py installs pure-Python replacements using torch.frombuffer — drop-in equivalents with correct dtype mapping for fp16/bf16/uint16-64. safetensors (normally Rust + PyO3, not cross-compiled for iOS) is also re-implemented in pure Python over mmap for read paths, which is what transformers.from_pretrained actually uses.
The whole point of this stack is that a training script written for Mac/Windows runs on iPad without modification. Two automatic patches in sitecustomize.py make this work:
1. iOS background-time extension — applied at every Python startup. UIApplication.beginBackgroundTask is requested via the Swift @_cdecl bridge in CodeBench/BackgroundTimeManager.swift, so when the user backgrounds the app mid-training, iOS grants extra time instead of suspending immediately. No-op on macOS/Linux (no UIKit), so the same Python script behaves correctly cross-platform. Disable with CODEBENCH_AUTO_BACKGROUND=0.
2. transformers.TrainingArguments defaults — patched via a sys.meta_path import hook (zero startup cost; only fires when the user imports transformers). Injects iPad-friendly defaults:
| Field | HF default | iPad default | Why |
|---|---|---|---|
save_steps |
500 | 100 | Checkpoint more often so iOS suspension loses less progress |
save_total_limit |
None (unlimited) | 3 | Cap disk use |
Trainer.train(resume_from_checkpoint=) |
None | auto-detect | If output_dir has checkpoint-*/ subdirs, resume from the latest one without requiring an explicit argument |
User-specified values always win (setdefault semantics; explicit resume_from_checkpoint=False is also respected). Disable with CODEBENCH_AUTO_CHECKPOINT=0.
The net effect: a vanilla HF training script —
from transformers import TrainingArguments, Trainer
args = TrainingArguments(output_dir="./run-1")
trainer = Trainer(model=model, args=args, train_dataset=ds)
trainer.train() # on iPad: auto-checkpoint every 100 steps,
# auto-resume on crash/relaunch— runs unchanged on Mac/Windows (with whatever HF defaults that platform implies) and on iPad (with the patched defaults plus auto-resume on relaunch).
For training loops that don't use HF Trainer, five extra utilities are available via explicit import:
OOMGuard— auto-halves batch size and retries on out-of-memory.MemoryProfiler— RSS snapshots between named checkpoints.KVCache— per-layer key/value cache for autoregressive inference.TrainingMonitor— terminal dashboard: loss/it-s/ETA/RAM/GPU dispatches.AutoCheckpointer— periodic save + auto-resume for hand-rolled loops.
These are NOT auto-installed — vanilla HF scripts don't need them. Import them only if you're writing a training loop without Trainer.
Closes the train→deploy loop. After training a LoRA via PyTorch + HF Trainer (with the Metal bridge for speed), convert the resulting .pt to a GGUF adapter for fast Metal-accelerated inference in llama.cpp. Pure-Python writer, no external deps:
python -m _cb_gguf_export --pt ./run-1/checkpoint-500/adapter_model.pt \
--gguf ./qwen-lora.gguf --arch qwen2 --alpha 16Supports Qwen / Llama / Mistral / Phi-family modules (attn_q/k/v/output, ffn_gate/up/down). For other architectures, extend _MODULE_MAP.
The Python files are bundled as resources. Copy them to your app's site-packages/ at runtime:
import PythonSklearn // or whichever package
// Get the bundled resource path
if let path = PythonIOSLib.resourcePath {
// Copy to your Python site-packages directory
let sitePackages = documentsURL.appendingPathComponent("site-packages")
try? FileManager.default.copyItem(atPath: path + "/sklearn",
toPath: sitePackages.path + "/sklearn")
}TL;DR — if you actually want LaTeX to work, swap in busytex. The bundled
LaTeXEngine(offlinai_latex + native pdftex.xcframework) is not a complete LaTeX implementation and will fail on most real-world documents. CodeBench itself does NOT rely on it — it ships busytex for exactly this reason. Section below tells you how to do the same.
What ships in this package's LaTeXEngine product:
pdftex.xcframework— a stale-ish (2019-era) native pdftex binarykpathsea.xcframework+ios_system.xcframework— POSIX shim layer- a minimal 48 MB texmf tree (Latin Modern, amsmath, hyperref, graphics-def, hyphenation, expl3, …)
offlinai_latexPython bridge — what manim'sMathTex/Texcalls
Why it's not "full":
pdflatex foo.texfor a\documentclass{article}document is gated off (the bundled pdftex.xcframework crashes on modernlatex.ltx)MathTexworks for simple math (\sin x,\frac{a}{b}, AMS symbols, matrices) but breaks on tikz, chemistry, fancy font substitutions, anything needing texlive packages outside the ~48 MB tree- The
ios_systemPTY emulator that pdftex shells through breaks outright on Mac "Designed for iPad" / Mac Catalyst (macOS sandbox blocks the fork-like syscalls)
What CodeBench uses instead — and what you should use for full LaTeX: busytex, a WebAssembly build of pdflatex that runs inside a hidden WKWebView with the full texlive tree (basic + latex-base + latex-extra
- latex-recommended + fonts-recommended + science). Compiles real
\documentclass{...}documents end-to-end, handles tikz, chem, biblatex, the works.
| Bundled offlinai_latex / native pdftex | Busytex (recommended) | |
|---|---|---|
MathTex(r"\sin x") |
works | works |
MathTex(r"\begin{tikzpicture}…") |
fails | works |
pdflatex foo.tex (full document) |
gated off (pdftex crashes) | works |
| Texlive package coverage | ~48 MB minimal | full distribution |
| Mac "Designed for iPad" / Catalyst | broken (sandbox kills ios_system) | works |
| Real iOS device | partial | works |
| Cold start | ~50 ms | ~1 s |
| Per-render speed | ~80 ms (when it works) | ~500 ms |
| Bundle cost | ~48 MB | ~58 MB minimum / ~237 MB full texlive |
-
Grab the busytex assets from CodeBench's repo:
git clone --depth 1 https://github.com/yu314-coder/CodeBench /tmp/cb
-
Copy
/tmp/cb/CodeBench/Resources/Busytex/into your iOS app target's resources. Two sizing options:Full (~237 MB) — handles every texlive package CodeBench ships (basic + latex-base + latex-extra + latex-recommended + fonts + science). Recommended if you want the same "full LaTeX works" UX as CodeBench:
cp -R /tmp/cb/CodeBench/Resources/Busytex YourApp/Resources/Minimum viable (~58 MB) — covers MathTex + simple
\documentclass{article}docs. Drop tikz/chem/fancy fonts. Copy only:busytex.html / busytex.js / busytex.wasm busytex_pipeline.js / busytex_worker.js dvipdfmx.cfg / texmf.cnf / updmap.cfg / versions.txt offlinai-texmf.{data,js} ← Computer Modern + AMS ubuntu-texlive-latex-base.{data,js} ← article/standalone classes -
Copy
/tmp/cb/CodeBench/BusytexEngine.swiftand the relevant pieces ofLaTeXEngine.swift(thecheckForMathCompileRequestpoller — see line 686+) into your app's Swift source. These wire Python's compile signals to the WKWebView running busytex. -
At app launch, call
BusytexEngine.shared.preload()so the WASM engine boots before the first MathTex. -
Tell
offlinai_latexto route to busytex:import os os.environ["OFFLINAI_LATEX_BACKEND"] = "busytex"
The full texlive bundle is 237 MB; even the minimum viable subset is ~58 MB. Forcing every SPM consumer to download that — including people who just want NumPy or Sklearn — would bloat the package unreasonably. Keeping busytex as a manual copy step lets you opt in only when you actually need real LaTeX.
If you only care about programmatic Math display (no MathTex,
no full documents), LaTeXEngine doesn't have to be linked at all
— just don't tick it in Xcode's product picker.
import CInterpreter
let interp = occ_create()
defer { occ_destroy(interp) }
occ_execute(interp, """
#include <stdio.h>
int main() {
printf("Hello from C on iOS!\\n");
return 0;
}
""")
print(String(cString: occ_get_output(interp)))PythonManim
├── FFmpegPyAV (video encoding)
├── CairoGraphics (2D rendering)
├── LaTeXEngine (math typesetting)
└── PythonMatplotlib (plotting)
└── plotly (pure Python, install separately)
PythonSklearn → numpy (iOS wheel)
PythonScipy → numpy (iOS wheel)
PythonMatplotlib → plotly (pure Python)
CInterpreter → (standalone)
PythonRequests → (standalone)
Transformers
├── PyTorch (14 MB LZMA blob → 99 MB dylib at runtime)
│ └── regex (shim)
└── Tokenizers (5 MB Rust .so)
Transformers also bundles huggingface_hub, filelock, safetensors
The app_packages/site-packages/ bundle ships ~180 Python packages — every entry below is importable on-device with no pip install needed. Anything you'd typically pip install for an HF training script is already here, except for a short list at the end that depends on un-cross-compileable C/Rust extensions.
| Library | Version | Notes |
|---|---|---|
| torch | 2.1.2 (patched iOS) | Native arm64 + Metal GPU bridge. See What works / what doesn't below. |
| transformers | 4.41.2 | HF — train + generate + save. See What works / what doesn't below. |
| accelerate | 0.30.1 | Required by HF Trainer (hard import at module load). Pure Python. |
| peft | 0.12.0 | LoRA / IA3 / prefix tuning. get_peft_model() + LoraConfig work as-is. |
| tokenizers | 0.19.1 | Real Rust BPE/WordPiece/Unigram cross-compiled for iOS arm64 via PyO3. First public iOS build. |
| safetensors | 0.4.5 (pure-Python shim) | Bidirectional (read + write). model.save_pretrained() writes valid files. |
| huggingface_hub | 0.24.7 | HF Hub client — from_pretrained works over network. |
| sklearn | shim | 40 pure-NumPy modules: classification, regression, clustering, preprocessing, metrics. |
✅ Works on iPad:
torch.Tensor,nn.Module,nn.Linear/Conv/..., fullautogradtorch.optim(SGD, AdamW, Adam, RMSprop, Adagrad, …)torch.jit.script+trace(eager-mode tracing, no JIT codegen)- LAPACK via Apple's Accelerate framework:
solve/eigh/svd/qr/lu/cholesky - FFT via Accelerate:
torch.fft.* - GPU-accelerated via the Metal bridge:
torch.matmul,torch.mm,torch.bmm,torch.addmm,F.linear,F.scaled_dot_product_attention(fp32 / fp16 / bf16) - Mixed precision:
bf16andfp16dtypes (cast paths through the GPU bridge) torch.save/torch.load(pickle-based)torch.frombufferfor byte-buffer→tensor (used by the safetensors / numpy shims)
❌ Doesn't work (with workarounds where they exist):
| Op / feature | Why broken | Workaround |
|---|---|---|
torch.cuda.* |
No CUDA on iOS | None — use the Metal bridge for matmul-heavy ops |
torch.backends.mps |
MPS backend not in this build | Metal bridge replaces it |
torch.distributed, torch.multiprocessing |
iOS forbids fork() |
Single-process training only |
torch.compile |
Needs Triton JIT, iOS forbids JIT | Eager mode + GPU bridge |
torch.from_numpy(), tensor.numpy() |
Built with USE_NUMPY=0 |
Auto-patched in sitecustomize — drop-in pure-Python equivalents using torch.frombuffer |
DataLoader(num_workers > 0) |
Worker processes use fork() |
Set num_workers=0 (only iPad limitation that needs code awareness) |
| TensorBoard writer | No background server | Use _cb_training.TrainingMonitor for terminal output |
bitsandbytes, flash-attn, xformers |
CUDA-only / Triton-only | Skip; GPU bridge handles attention |
✅ Works on iPad:
AutoModel,AutoTokenizer,AutoModelForCausalLM,AutoModelForSeq2SeqLM, etc.from_pretrained(...)— local files or HF Hub URLs (latter needs network access)model.generate()— full autoregressive generation, beam search, samplingTrainer.train()— auto-checkpoint + auto-resume via sitecustomize patchesmodel.save_pretrained(...)— writespytorch_model.bin+.safetensors(both work)peft.get_peft_model()+ LoRA / IA3 training- Mixed precision:
bf16=Trueorfp16=TrueinTrainingArguments - Model families verified: BERT, GPT-2, T5, BART, Llama, Qwen, Mistral, Phi
❌ Doesn't work:
| Op / feature | Why | Workaround |
|---|---|---|
datasets.load_dataset(...) |
datasets not bundled (needs pyarrow + pandas) |
Subclass torch.utils.data.Dataset — 5-10 lines |
| Sentencepiece-only tokenizers | sentencepiece C++ not cross-compiled |
GPT-2 / Qwen / Mistral / Phi tokenizers use BPE and work without it. Llama / T5 / BART tokenizer formats blocked. |
FlashAttention2 |
No flash_attn package; no Triton |
Falls back to SDPA, which IS GPU-accelerated via the bridge |
DeepSpeed, FSDP |
Multi-device / multi-process | Single-device; not applicable to iPad |
BitsAndBytes quantization |
CUDA-only | Use llama.cpp's GGUF quantization for inference instead |
evaluate.load(...) |
evaluate package not bundled |
Compute metrics inline; or pip install if pure-Python wheel exists |
| Multi-GPU training | iOS = one device | N/A |
| Library | Version | Type |
|---|---|---|
| numpy | 2.3.5 | Native arm64 (cross-compiled from source) |
| scipy | 1.15.0 | Native + Python shim |
| pandas | 2.2.3 | Native arm64 (cross-compiled) — DataFrames |
| statsmodels | 0.14.4 | Native arm64 — statistical models / tests |
| sympy | 1.14.0 | Pure Python |
| mpmath | 1.4.1 | Pure Python |
| networkx | 3.6.1 | Pure Python |
| pint | 0.25.3 | Pure Python — physical units & dimensional analysis |
| uncertainties | 3.2.3 | Pure Python — error propagation (numpy-aware) |
| periodictable | 2.1.0 | Pure Python — chemistry element / isotope data (masses, densities) |
| Library | Version | Notes |
|---|---|---|
| matplotlib | 3.9.0 | Plotly-backend shim (no native renderer on iOS) |
| plotly | 6.6.0 | Renders in WKWebView preview pane |
| seaborn | bundled | Statistical plotting on matplotlib |
| fonttools | bundled | Font subsetting + metadata |
| mpl_toolkits | bundled | matplotlib 3-D / axes_grid |
| narwhals | 1.16.0 | DataFrame-agnostic helpers (matplotlib internal) |
| Library | Version |
|---|---|
| manim | 0.19.0 |
| manimpango | 0.6.1 (iOS shim) |
| mapbox_earcut | 1.0.3 |
| isosurfaces | 0.1.2 |
| moderngl | 5.12.0 |
| moderngl_window | 2.4.6 |
| screeninfo | 0.8.1 |
| svgelements | 1.9.6 |
| pathops | bundled |
| Library | Notes |
|---|---|
| PIL (Pillow) | Image processing — native iOS build |
| av (PyAV) | FFmpeg bindings; 7 native dylibs (libav*) |
| cairo + cairocffi + cairosvg | 2D vector graphics + SVG |
| pydub | Audio manipulation (uses audioop) |
| audioop | LTS backport — removed from Python 3.13 stdlib |
| pypdf | PDF read |
| fpdf | PDF write |
| reportlab | PDF generation (full vector + text) |
| openpyxl + xlsxwriter + et_xmlfile | Excel .xlsx read / write |
| qrcode | QR-code generation (PNG via the bundled PIL, or pure-Python SVG) |
| Library | Notes |
|---|---|
| offlinai_latex | Math-mode renderer (SwiftMath) + 33 MB bundled texmf tree |
| Library | Notes |
|---|---|
| requests | 2.33.1 — HTTP client |
| urllib3 | 2.6.3 — HTTP transport |
| httpx | Async HTTP |
| anyio + sniffio | Async runtime backends |
| charset_normalizer | 3.4.7 — encoding detection |
| certifi | 2026.2.25 — CA bundle |
| idna | 3.11 — IDN |
| bs4 (BeautifulSoup4) + soupsieve | HTML / XML parsing |
| defusedxml | Safe XML parsing |
| jwt (PyJWT) | JSON Web Tokens |
| webview (PyWebView shim) | Embed HTML in host preview pane |
All five are iOS-patched (multiprocessing.Value fallback, signal-handler
skip on worker thread, preview-panel auto-load, clean Ctrl+C shutdown).
Tick any of these in Xcode → SPM pulls every transitive dependency.
| Library | Version | Use case |
|---|---|---|
| Werkzeug | 3.1.x | WSGI utilities + dev server. Standalone, or the foundation Flask is built on |
| Flask | 3.x | Classic web framework — routes, sessions, templates, blueprints |
| Dash | 3.x | Plotly-based reactive dashboards — dcc, html, dash_table, pattern-matching callbacks |
| Streamlit | 1.50.x | Script-style dashboards — declarative widgets, @st.cache_data, fragments |
| Tornado | 6.5.x | Async HTTP/WebSocket framework. Streamlit's transport, usable standalone |
Per-lib docs: werkzeug.md · flask.md · dash.md · streamlit.md · tornado.md. Cross-stack iOS-patch story: web-stack.md.
| Library | Notes |
|---|---|
| yaml (PyYAML) | YAML — native build |
| jsonschema 4.26.0 + jsonschema_specifications + referencing + rpds | JSON Schema validation |
| fsspec | Filesystem abstraction (HF transformers / hub dep) |
| filelock 3.28.0 | Cross-process file locking |
| tinydb 4.8.2 | Zero-dependency JSON document database (offline) |
| Library | Notes |
|---|---|
| rich 13.7.0 | Tables / progress / coloured output |
| click 8.1.7 + typer + cloup 3.0.5 + shellingham | CLI frameworks |
| textual | TUI framework |
| tqdm 4.67.3 | Progress bars |
| colorama | ANSI on every platform |
| markdown_it + mdurl | Markdown parser |
| pygments 2.18.0 | Syntax highlighting (500+ languages) |
| plotext 5.3.2 | Plot charts directly in the terminal (no matplotlib) |
| ftfy 6.3.1 | Fix mojibake / broken unicode text |
| Library | Notes |
|---|---|
| pytest + _pytest + pluggy + iniconfig | Test framework |
| hypothesis + sortedcontainers | Property-based testing |
| black + blib2to3 + isort + mypy + pyflakes | Formatters / linters / type checker |
| tomli + tomli_w + pytokens + pathspec + annotated_doc | Common dev-tool deps |
| Library | Notes |
|---|---|
| jinja2 3.1.6 + markupsafe 3.0.3 | Templating (HF Trainer chat templates use this) |
| regex 2024.11.6 | Extended regex (tokenizers use this) |
| packaging 26.0 | Version / requirement parsing |
| more_itertools + lark | Iteration + grammar parsing |
| dill 0.4.1 | Extended pickling — closures, lambdas, whole sessions |
| pyparsing 3.3.2 | PEG-style grammar parsing |
| wcwidth 0.7.0 | Terminal display width of unicode characters |
| dateutil + pytz + pendulum | Date / time |
| attr / attrs 24.2.0 + cattrs | Attribute classes / converters |
| platformdirs | OS-specific paths |
| humanize + tabulate | Output formatting |
| watchdog 4.0.0 | File system watcher |
| psutil 5.9.8 (iOS shim) | Process / system info |
| pycparser 2.22 | C parser (cffi dep) |
| pip 26.0.1 + wheel + setuptools + pkg_resources + _distutils_hack | Package management — pip patched to install pure-Python pkgs on-device |
| Module | Purpose |
|---|---|
| offlinai_ai | RAG + embedding utilities |
| _torch_metal_bridge | PyTorch → Metal GPU dispatch (auto-installed) |
| _cb_training | OOMGuard, MemoryProfiler, KVCache, TrainingMonitor, AutoCheckpointer (opt-in, for manual loops) |
| _cb_background | iOS background-time extension (auto-enabled) |
| _cb_gguf_export | PyTorch LoRA → GGUF converter for llama.cpp inference |
| Language | Lines | Description |
|---|---|---|
| C | ~3,450 | C89/C99/C23, 48 operators, structs, pointers, preprocessor |
| C++ | ~4,200 | Classes, STL, templates, inheritance |
| Fortran | ~4,100 | Modules, allocatable arrays, 45+ intrinsics |
CodeBench ships a patched pip that installs pure-Python wheels on-device into the per-workspace site-packages. Two categories:
✅ Pure-Python — typically installs fine via pip install <name>:
evaluate— HF metrics (BLEU / ROUGE / accuracy / etc.)diffusers— Stable Diffusion / etc. (note: inference may be slow; better via ExecuTorch)trl— RLHF / DPO trainers built on transformers + accelerate (both bundled)wandb-corePython parts — but no network sync without internetbertopic,umap-learn,gensim— most NLP utility libs- Generally: anything whose wheel contains only
.pyfiles
❌ Has C / Rust / CUDA extensions not cross-compiled for iOS arm64 — pip install will FAIL or import will crash:
| Package | Reason | Workaround |
|---|---|---|
datasets |
needs pyarrow (Apache Arrow C++); pandas itself is now bundled |
Subclass torch.utils.data.Dataset (5-10 lines) |
pyarrow |
Apache Arrow C++ core | None — write your own loader using json / csv stdlib modules |
sentencepiece |
C++ subword tokenizer | Only needed for Llama / T5 / BART tokenizer formats; GPT-2 / Qwen / Mistral / Phi use BPE and work without it |
protobuf |
C-based binary serialization | Use JSON / msgpack where possible |
bitsandbytes |
CUDA-only quantization kernels | Use llama.cpp GGUF Q4/Q8 quantization for inference |
flash-attn, xformers |
CUDA-only attention kernels | Bridge's F.scaled_dot_product_attention patch IS GPU-accelerated |
triton |
LLVM JIT codegen | iOS forbids JIT; no equivalent |
deepspeed, fairscale |
Multi-GPU training infrastructure | Single-device on iPad; not applicable |
polars |
Rust DataFrame core | Use the bundled pandas instead |
onnxruntime |
C++ ONNX runtime | Use ExecuTorch (already bundled separately as an XCFramework) for inference |
Every Python library + native engine + interpreter shipped here has a
documentation file in docs/. Each file covers usage examples, iOS-specific
notes, limitations, troubleshooting, and build provenance.
| Lang | Doc |
|---|---|
| C | docs/c-interpreter.md |
| C++ | docs/cpp-interpreter.md |
| Fortran | docs/fortran-interpreter.md + docs/fortran-runtime.md |
| All three (overview) | docs/libs/interpreters.md |
| Engine | Doc |
|---|---|
| CairoGraphics (Cairo + Pango + HarfBuzz + FreeType + GLib + libffi) | docs/cairographics.md |
| FFmpeg + PyAV (video encode/decode, VideoToolbox H.264) | docs/ffmpeg-pyav.md |
| LaTeXEngine (pdftex.xcframework + 33 MB texmf) | docs/latex-engine.md |
| Library | Doc |
|---|---|
| NumPy | docs/numpy.md — also docs/libs/numpy.md |
| SciPy | docs/scipy-ios.md — also docs/libs/scipy.md |
| SymPy | docs/sympy.md — also docs/libs/sympy.md |
| mpmath | docs/mpmath.md |
| NetworkX | docs/networkx.md |
| scikit-learn | docs/sklearn.md — also docs/libs/sklearn.md |
| Library | Doc |
|---|---|
| PyTorch (14 MB LZMA blob → 99 MB dylib at runtime) | docs/torch.md — also docs/libs/pytorch.md |
| transformers | docs/transformers.md — also docs/libs/transformers.md |
| tokenizers (Rust via PyO3) | docs/tokenizers.md — also docs/libs/tokenizers.md |
| safetensors | docs/safetensors.md |
| huggingface_hub | docs/huggingface-hub.md |
| Library | Doc |
|---|---|
| matplotlib (Plotly-backend shim) | docs/matplotlib.md — also docs/libs/matplotlib.md |
| Plotly | docs/plotly.md |
| manim | docs/manim.md — also docs/libs/manim.md |
| manim deps (pathops + mapbox_earcut + isosurfaces) | docs/manim-deps.md |
| manimpango | docs/manimpango.md |
| Library | Doc |
|---|---|
| Pillow / PIL | docs/pillow.md |
| PyAV | docs/av-pyav.md — also docs/ffmpeg-pyav.md |
| pydub (audio, uses audioop) | docs/pydub.md |
| audioop (LTS backport — removed from Python 3.13 stdlib) | docs/audioop.md |
| svgelements | docs/svgelements.md |
| Media overview | docs/libs/media.md |
| Library | Doc |
|---|---|
| requests | docs/requests.md |
| urllib3 | docs/urllib3.md |
| BeautifulSoup4 | docs/beautifulsoup.md |
| certifi (CA bundle for HTTPS) | docs/certifi.md |
| charset_normalizer | docs/charset-normalizer.md |
| idna | docs/idna.md |
| PyWebView (CodeBench shim) — full cookie API + verbose logging | docs/pywebview.md |
| Library | Doc |
|---|---|
| Werkzeug (WSGI utilities + dev server) | docs/werkzeug.md |
| Flask (web framework on Werkzeug) | docs/flask.md |
| Dash (Plotly-based reactive dashboards) | docs/dash.md |
| Streamlit (script-style dashboards) | docs/streamlit.md |
| Tornado (async HTTP/WebSocket — Streamlit's transport) | docs/tornado.md |
| Cross-stack iOS patches + Ctrl+C behavior | docs/web-stack.md |
| Library | Doc |
|---|---|
| PyYAML | docs/pyyaml.md |
| jsonschema | docs/jsonschema.md |
| Library | Doc |
|---|---|
| rich (colors, tables, markdown rendering) | docs/rich.md |
| click | docs/click.md |
| tqdm (progress bars) | docs/tqdm.md |
| pygments (syntax highlighting) | docs/pygments.md |
| markdown_it + mdurl | docs/markdown-it.md |
| Library | Doc |
|---|---|
| psutil + filelock + watchdog (combined) | docs/process-and-io.md |
| moderngl + moderngl_window + screeninfo (all stubbed on iOS) | docs/moderngl.md |
| Library | Doc |
|---|---|
| cffi + pycparser | docs/cffi.md |
| regex + typing_extensions | docs/regex-and-typing.md |
| decorator (full upstream package, pure Python) | docs/decorator.md |
| Library | Doc |
|---|---|
| pip (with the in-shell wrapper) | docs/pip.md |
| Lib | Doc |
|---|---|
| attrs | docs/attrs.md |
| packaging | docs/packaging.md |
| narwhals | docs/narwhals.md |
| referencing | docs/referencing.md |
| cloup | docs/cloup.md |
| srt | docs/srt.md |
| mdurl | docs/mdurl.md |
| typing_extensions | docs/typing-extensions.md |
| isosurfaces | docs/isosurfaces.md |
| mapbox_earcut | docs/mapbox-earcut.md |
| pathops (skia-pathops) | docs/pathops.md |
| pycairo | docs/pycairo.md |
| rpds (iOS stub) | docs/rpds.md |
| pylab | docs/pylab.md |
| torchgen | docs/torchgen.md |
| setuptools | docs/setuptools.md |
| wheel | docs/wheel.md |
| pkg_resources | docs/pkg-resources.md |
| _distutils_hack | docs/distutils-hack.md |
| psutil | docs/psutil.md |
| filelock | docs/filelock.md |
| watchdog | docs/watchdog.md |
| Module | Doc |
|---|---|
| offlinai_shell (108 builtins, Ctrl+C watchdog, convenience-imports, HF git clone, AI mode) | docs/offlinai-shell.md |
| offlinai_ai (chat REPL backed by bundled llama.cpp) | docs/offlinai-ai.md |
| offlinai_latex (math/doc bridge to SwiftMath + busytex) | docs/offlinai-latex.md |
If you're looking for a specific package and forgot which doc it's in:
import X |
Doc |
|---|---|
attrs |
attrs.md |
audioop |
audioop.md |
av |
av-pyav.md · ffmpeg-pyav.md |
bs4 |
beautifulsoup.md |
cairo (pycairo) |
pycairo.md · cairographics.md |
certifi |
certifi.md |
cffi / pycparser |
cffi.md |
charset_normalizer |
charset-normalizer.md |
click |
click.md |
cloup |
cloup.md |
dash |
dash.md · web-stack.md |
decorator |
decorator.md |
_distutils_hack |
distutils-hack.md |
filelock |
filelock.md |
flask |
flask.md · web-stack.md |
huggingface_hub |
huggingface-hub.md |
idna |
idna.md |
isosurfaces |
isosurfaces.md |
jsonschema / jsonschema_specifications |
jsonschema.md |
manim |
manim.md |
manimpango |
manimpango.md |
mapbox_earcut |
mapbox-earcut.md |
markdown_it |
markdown-it.md |
matplotlib / mpl_toolkits |
matplotlib.md |
mdurl |
mdurl.md |
moderngl / moderngl_window / screeninfo |
moderngl.md |
mpmath |
mpmath.md |
narwhals |
narwhals.md |
networkx |
networkx.md |
numpy |
numpy.md |
offlinai_ai |
offlinai-ai.md |
offlinai_latex |
offlinai-latex.md |
offlinai_shell |
offlinai-shell.md |
packaging |
packaging.md |
pathops (skia-pathops) |
pathops.md |
PIL (Pillow) |
pillow.md |
pip (and the shell wrapper) |
pip.md |
pkg_resources |
pkg-resources.md |
plotly / _plotly_utils |
plotly.md |
psutil |
psutil.md |
pydub |
pydub.md |
pygments |
pygments.md |
pylab |
pylab.md |
referencing |
referencing.md |
regex |
regex.md |
requests |
requests.md |
rich |
rich.md |
rpds |
rpds.md |
safetensors |
safetensors.md |
scipy |
scipy-ios.md |
setuptools |
setuptools.md |
sklearn |
sklearn.md |
soupsieve |
(ships with bs4 — see beautifulsoup.md) |
srt |
srt.md |
streamlit |
streamlit.md · web-stack.md |
svgelements |
svgelements.md |
sympy |
sympy.md |
tokenizers |
tokenizers.md |
torch |
torch.md |
torchgen |
torchgen.md |
tornado |
tornado.md · web-stack.md |
tqdm |
tqdm.md |
transformers |
transformers.md |
typing_extensions |
typing-extensions.md |
urllib3 |
urllib3.md |
watchdog |
watchdog.md |
webview (PyWebView shim) |
pywebview.md |
werkzeug |
werkzeug.md · web-stack.md |
wheel |
wheel.md |
yaml (PyYAML) |
pyyaml.md |
- iOS 17.0+ / iPadOS 17.0+
- Python 3.14 (BeeWare Python-Apple-support)
- Xcode 15+
MIT