Instructions to use mlboydaisuke/Timestral-209M-LiteRT with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LiteRT
How to use mlboydaisuke/Timestral-209M-LiteRT with LiteRT:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
Timestral-209M — LiteRT
Forecast one time series on a phone with Timestral-209M: 21 quantiles for each of up to 1,024 future steps, read from up to 8,192 past steps, on the CPU, the GPU or a Qualcomm NPU through the LiteRT CompiledModel API. The graphs were converted with litert-torch; on a Galaxy S26 (LiteRT 2.2.0, measured 2026-10-22), one forecast's graph call takes 15.6 ms on the NPU and 27.0 ms on the GPU with timestral-209m-c2048-h256.tflite (medians; conditions under Speed on phones).
The series is the last 2,048 hours of a synthetic hourly temperature series (made with NumPy, no outside data). The forecast is the Kotlin code below with timestral-209m-c2048-h1024.tflite on the GPU of a Galaxy S26 (FP16, LiteRT 2.2.0).
Timestral is Mistral AI's family of time-series foundation models (5M to 209M parameters). This repository holds the 209M checkpoint converted to LiteRT: graphs that run the patch embedding, the 24 transformer layers and the quantile head in one call, and host code in Python and Kotlin that runs the official forecaster's steps around them; they are the files the Android sample timestral in the LiteRT-Models zoo loads. The forecasts follow the official TimestralForecaster.forecast() defaults for a single series without covariates: the series and its negation are forecast together and averaged (symmetric averaging), the quantiles are capped at the context's range widened by 10⁴ times the scale of its last patch, and a series with no negative values gets no negative quantiles.
Files
| File | Context | Horizon per call | Weights | Accelerators | Bytes | SHA-256 |
|---|---|---|---|---|---|---|
timestral-209m-c2048-h256.tflite |
up to 2,048 steps | up to 256 steps | float16 | GPU, NPU | 405,041,792 | c016500177c84c6f03cf746d59d690baf35c9fafb9a4c720cba99e669fb66fca |
timestral-209m-c2048-h1024.tflite |
up to 2,048 steps | up to 1,024 steps | float16 | GPU, NPU | 405,041,792 | 5e825e884bf96f6acb5ee40553cbac453d85cfb53f9eae9a0630c31b6e0c1ae5 |
timestral-209m-c8192-h256.tflite |
up to 8,192 steps | up to 256 steps | float16 | GPU | 405,041,792 | 62a92115fb81e16f7b235d402c74df6175c568540b6c0ac14f354d92567763d7 |
timestral-209m-c8192-h1024.tflite |
up to 8,192 steps | up to 1,024 steps | float16 | GPU | 405,041,792 | 95f327839f76cf78ded74e022a5a598b017dee83ceac50719e3d24cb0bdc522d |
timestral-209m-c2048-h256-int8.tflite |
up to 2,048 steps | up to 256 steps | int8 in the fully connected layers of the first 23 of the 24 blocks, float16 elsewhere | CPU | 222,336,912 | c32773bc277b6dafb4e34561b6f900fa8a6c63ec4dc4ed603d4d563d1f2f5fc5 |
timestral-209m-c2048-h1024-int8.tflite |
up to 2,048 steps | up to 1,024 steps | int8 in the fully connected layers of the first 23 of the 24 blocks, float16 elsewhere | CPU | 222,336,912 | 790935a69c9f1a4cd41f5678d59cda0e2dfde73ef139f0e76d2d2d3cba637264 |
timestral-209m-c8192-h256-int8.tflite |
up to 8,192 steps | up to 256 steps | int8 in the fully connected layers of the first 23 of the 24 blocks, float16 elsewhere | CPU | 222,336,912 | cd4ef83f6104cb381c5dad741063b131da30b8fa10bbf39cdc60358ee8e0b1e6 |
timestral-209m-c8192-h1024-int8.tflite |
up to 8,192 steps | up to 1,024 steps | int8 in the fully connected layers of the first 23 of the 24 blocks, float16 elsewhere | CPU | 222,336,912 | 97bd95c154ef25671dd3dc9732b217690f3413b6ee72dc9503dc4c873523a03a |
forecast_query.f32 |
the decoder token (768 float32) | 3,072 | fb8bf83c6788fac02c8bac975235921135b234d96e3c948da4d654b589b167bd |
|||
rope_cos.f32 |
RoPE cos rows of positions −1,023 to 1,055 (2,079 × 64 float32) | 532,224 | b15457cc67269e6c65fdfc2a77e96af24cbd4b9c03f45e0e379f5c8f0afb6abc |
|||
rope_sin.f32 |
RoPE sin rows of positions −1,023 to 1,055 (2,079 × 64 float32) | 532,224 | 47f2ab89a9d6fb487ca22d6e5483f358321808051787707132d72ae916044247 |
The graphs come in four sizes, named in the files: a context of up to 2,048 or 8,192 steps (c2048, c8192) and a horizon of up to 256 or 1,024 steps per call (h256, h1024). The host pads a shorter series in front with missing steps, which the attention mask leaves out, and keeps only the requested steps of the graph's horizon; the smallest graph that holds the series and the horizon takes the least time, and the Python class and GraphSpec.choose in android/GraphSpec.kt take it. Each size has a float16 file for the GPU (the c2048 ones also for the NPU) and an -int8 file for the CPU; the CPU runs the float16 files too, more slowly:
- On the Galaxy S26 CPU (4 threads),
timestral-209m-c2048-h1024-int8.tflitetakes 39.3 ms per forecast andtimestral-209m-c2048-h1024.tflite93.8 ms (Speed on phones).
| Other files | What they are |
|---|---|
forms.json |
the graphs the Python class and GraphSpec.choose pick from (context and decoder patches, rows per call, the attention mask value, accelerators) and each accelerator's settings |
host_forecast.py |
the host steps in Python (NumPy): input preparation and the forecast's finish around a graph call |
timestral_litert.py |
a Python class that picks the graph, runs it with CompiledModel and calls host_forecast.py |
android/TimestralHost.kt, android/GraphSpec.kt |
the same host steps and graph choice in Kotlin, with no Android dependency (package com.timestral) |
LICENSE |
the Modified MIT License of the weights (Mistral AI); it covers the graph files and host tables |
LICENSE-APACHE-2.0, NOTICE |
the licence of the code files, which port the official inference code (Apache-2.0) |
Inputs and output
Each graph has one signature, serving_default, with five float32 inputs and one float32 output. A patch is 32 steps; L is the graph's number of context patches and H its number of decoder patches (the forecast's 32-step blocks), S = L + H, and R = 2 rows (the series and its negation).
| Name | Shape | Content |
|---|---|---|
patches |
[R, L, 64] | per patch, 32 normalized values and then 32 missing flags (1 = missing); patches in front of the series are padding (values 0, all missing) |
dec |
[R, H, 768] | the checkpoint's forecast_query vector (forecast_query.f32) for every decoder token |
bias |
[1, 1, S, S] | additive attention mask: 0 where a token may attend, −3·10⁴ where it may not |
cos, sin |
[1, 1, S, 64] | RoPE rows for the positions counted from the first observed patch, cut from the two RoPE tables |
output_0 |
[R, H, 32, 21] | the normalized quantiles of every decoder step, in order of level |
The host builds the inputs and finishes the forecast the way the official forecaster does:
- Missing steps (NaN or infinite) are marked; at most the last 8,192 steps are kept (2,048 when only the
c2048graphs are used) and the front is padded to whole patches of 32 steps. - Causal scaling per row: a float64 running mean and Bessel standard deviation over the observed steps (floored at 10⁻⁶); each patch is scaled with the statistics at its last step; observed steps become asinh((x − mean) / scale) in float32.
- The mask lets each context patch attend to itself and to the valid patches before it, and each decoder token to every valid context patch, to the decoder tokens before it and to itself (a patch is valid from the first patch with an observed step on).
- After the call: the first ⌈horizon / 32⌉ decoder patches are mapped back (sinh, times the scale, plus the mean of the last context patch), moved to the official advancing refine frame when there is more than one (in float64, each decoder patch after the first is mapped back with the mean and scale of the context extended by the medians of the forecast's earlier patches), capped at the context's minimum − 10⁴ × scale and maximum + 10⁴ × scale and checked for NaN.
- The negation's quantiles are reversed, negated and averaged with the series' quantiles; when every observed value is ≥ 0, the quantiles are clipped at 0.
Minimal usage
Python (desktop, LiteRT CompiledModel)
Tested with Python 3.12 on macOS (Apple M4 Max) after this install line:
pip install ai-edge-litert==2.2.0 numpy==2.5.4 huggingface_hub==2.2.0
import sys
import numpy as np
from huggingface_hub import snapshot_download
from ai_edge_litert.compiled_model import CompiledModel, CpuOptions, HardwareAccelerator, Options
GRAPH = "timestral-209m-c2048-h1024.tflite" # contexts up to 2,048 steps, horizons up to 1,024
d = snapshot_download("mlboydaisuke/Timestral-209M-LiteRT",
allow_patterns=["*.py", "*.json", "*.f32", GRAPH])
sys.path.insert(0, d)
import host_forecast as hf # the official forecaster's host steps, in NumPy
f32 = lambda name: np.fromfile(f"{d}/{name}", "<f4")
inputs = hf.GraphInputs(f32("forecast_query.f32"), f32("rope_cos.f32").reshape(-1, 64),
f32("rope_sin.f32").reshape(-1, 64), rope_p_min=-1023)
cpu = Options(hardware_accelerators=HardwareAccelerator.CPU, cpu_options=CpuOptions(num_threads=4))
model = CompiledModel.from_file(f"{d}/{GRAPH}", options=cpu)
ins = {n: model.create_input_buffer_by_name("serving_default", n) for n in ("patches", "dec", "bias", "cos", "sin")}
out = model.create_output_buffer_by_name("serving_default", "output_0")
def run_graph(scaled, observed, h): # normalized context [2, T] -> normalized quantiles [2, h * 32, 21]
for n, x in zip(ins, inputs.build(scaled, observed, 64, 32, neg=-3e4)):
ins[n].write(x)
model.run_by_name("serving_default", ins, {"output_0": out})
return out.read(2 * 32 * 32 * 21, np.float32).reshape(2, 1024, 21)[:, : h * 32]
t = np.arange(1500) # an hourly series; NaN marks a missing value
series = 14 + 6 * np.sin(2 * np.pi * (t - 9) / 24) + np.random.default_rng(0).normal(0, 0.7, t.size)
q = hf.forecast(series[-2048:], 48, run_graph) # [48, 21] quantiles of the next 48 steps
print(q[:3, [2, 10, 18]]) # the 0.1, 0.5 and 0.9 quantiles of the first three steps
timestral_litert.py wraps the same steps and picks the graph as GraphSpec.choose does: from forms.json, the graph with the fewest patches that holds the series and the horizon among the files present, the -int8 graphs on the CPU and the float16 graphs on the desktop GPU (accelerator="gpu"; Metal on macOS). It refuses a series shorter than 16 values:
from timestral_litert import TimestralLiteRT
d = snapshot_download("mlboydaisuke/Timestral-209M-LiteRT", allow_patterns=["*.py", "*.json", "*.f32", "*-int8.tflite"])
q = TimestralLiteRT(d).forecast(series, horizon=48) # [48, 21], on the CPU with an int8 graph
Kotlin (Android, LiteRT CompiledModel)
Add implementation("com.google.ai.edge.litert:litert:2.2.0"), copy android/TimestralHost.kt into the app, and put the graph files and the three .f32 tables into the app's files directory (an app downloads them; for a test, adb push them to /data/local/tmp and copy them with adb shell run-as <package> cp). The two blocks below ran as written on a Galaxy S26 (Android 16) with each of the files and accelerators the comment in the second block names; on the phone's CPU, the float16 file and the -int8 file each gave quantiles bit-identical to those of the same file in Python on a desktop CPU for the same series.
import com.google.ai.edge.litert.Accelerator
import com.google.ai.edge.litert.CompiledModel
import com.google.ai.edge.litert.Environment
import com.google.ai.edge.litert.TensorBuffer
import com.timestral.Bucket
import com.timestral.HostTables
import com.timestral.TimestralHost
import java.io.File
import java.nio.ByteBuffer
import java.nio.ByteOrder
/**
* [horizon] steps (1 to 1,024) after [series] (NaN = missing) as [horizon × 21] quantiles, step after step, from
* [graph] in [dir]: a graph for contexts up to 2,048 steps and horizons up to 1,024 (`timestral-209m-c2048-h1024*`).
*/
fun timestralForecast(dir: File, graph: String, series: FloatArray, horizon: Int, options: CompiledModel.Options, env: Environment): FloatArray {
fun floats(name: String) = ByteBuffer.wrap(File(dir, name).readBytes()).order(ByteOrder.LITTLE_ENDIAN)
.asFloatBuffer().let { b -> FloatArray(b.remaining()).also { b.get(it) } }
val tables = HostTables(floats("forecast_query.f32"), floats("rope_cos.f32"), floats("rope_sin.f32"), firstPosition = -1023)
// Bucket = a graph's shape: context patches and horizon patches
val bucket = Bucket("c2048-h1024", contextPatches = 64, horizonPatches = 32)
val prepared = TimestralHost.prepare(series.copyOfRange(maxOf(0, series.size - 2048), series.size), horizon)
val inputs = TimestralHost.buildInputs(prepared, bucket, tables, mask = -3e4f)
val model = CompiledModel.create(File(dir, graph).absolutePath, options, env)
val buffers = ArrayList<TensorBuffer>()
try {
val ins = inputs.byName().mapValues { (name, _) -> model.createInputBuffer(name, "serving_default").also { buffers += it } }
val output = model.createOutputBuffer("output_0", "serving_default").also { buffers += it }
for ((name, values) in inputs.byName()) ins.getValue(name).writeFloat(values)
model.run(ins, mapOf("output_0" to output), "serving_default")
return TimestralHost.finish(prepared, output.readFloat(), bucket) // median of step i: [i * 21 + 10]
} finally {
buffers.forEach { it.close() }
model.close()
}
}
The accelerator is an option of the compile, and each accelerator has its file of the same shape; the host code stays the same:
val gpu = CompiledModel.Options(Accelerator.GPU).apply {
gpuOptions = CompiledModel.GpuOptions(precision = CompiledModel.GpuOptions.Precision.FP16)
}
val cpu = CompiledModel.Options(Accelerator.CPU).apply { cpuOptions = CompiledModel.CpuOptions(numThreads = 4) }
val npu = CompiledModel.Options(Accelerator.NPU, Accelerator.CPU).apply { // the Qualcomm NPU (HTP); needs the libraries below
qualcommOptions = CompiledModel.QualcommOptions(htpPerformanceMode = CompiledModel.QualcommOptions.HtpPerformanceMode.BURST)
}
// One Environment for the process; with the Qualcomm libraries in the APK it names their directory for the NPU.
val libs = context.applicationInfo.nativeLibraryDir
val dirs = mapOf(Environment.Option.DispatchLibraryDir to libs, Environment.Option.CompilerPluginLibraryDir to libs)
val env = Environment.create(context, if (File(libs, "libLiteRtDispatch_Qualcomm.so").isFile) dirs else emptyMap())
// gpu or npu: this float16 file; cpu: "timestral-209m-c2048-h1024-int8.tflite".
val quantiles = timestralForecast(context.filesDir, "timestral-209m-c2048-h1024.tflite", series, 24, gpu, env)
- The function compiles the graph on every call to keep the example short; an app keeps the
CompiledModeland its buffers between forecasts. Run the compile, the calls andclose()on one thread. - The float16 files are made for the GPU's FP16 precision (see How the graphs were made); set it explicitly, as above.
- On the NPU, the graph is compiled on the phone at its first load; LiteRT keeps the result in the app's cache when the
Environmentis created with aContext, and the next process of the app loads it from there (logcat:Flatbuffer model initialized from cached model). When the compiler plugin is missing, LiteRT runs the graph on the CPU without an error: look forReplacing 1 out of 1 node(s) with delegate (DispatchDelegate)in logcat. - LiteRT starts the NPU runtime once per process, with the options of the first graph it compiles on any accelerator: compile the NPU graph first, or give every graph the same
qualcommOptions, or a CPU or GPU graph compiled before it leaves the NPU without BURST. - The NPU libraries go into
app/src/main/jniLibs/arm64-v8a/, withpackaging { jniLibs { useLegacyPackaging = true } }so that the DSP-side librarylibQnnHtpV81Skel.sois extracted as a real file, which the NPU needs. They are not in this repository: the Qualcomm runtime may be distributed only inside an app.
- In the run of these two blocks on a Galaxy S26 with
timestral-209m-c2048-h1024.tflite, the first process on the NPU took 18.1 s for the compile on the phone, one forecast andclose(); the next process took 0.3 s with the compiled graph from the app's cache.
| Library | From |
|---|---|
libLiteRtDispatch_Qualcomm.so |
litert_npu_runtime_libraries.zip, an asset of the LiteRT v2.2.0 GitHub release (the v2.3.0 release carries no such asset) |
libLiteRtCompilerPlugin_Qualcomm.so |
litert_npu_runtime_libraries_jit.zip, an asset of the LiteRT v2.2.0 GitHub release (the v2.3.0 release carries no such asset) |
libQnnHtp.so, libQnnSystem.so, libQnnHtpPrepare.so, libQnnIr.so, libQnnSaver.so, libQnnHtpV81Stub.so, libQnnHtpV81CalculatorStub.so |
Qualcomm AI Runtime (QAIRT) SDK, lib/aarch64-android/ |
libQnnHtpV81Skel.so |
Qualcomm AI Runtime (QAIRT) SDK, lib/hexagon-v81/unsigned/ |
The NPU runs in this card used QAIRT 2.47.0 (the version its libQnnHtp.so reports).
V81 is the Hexagon version of the Snapdragon SM8850 (Galaxy S26); other chips take their own version (SM8550 v73, SM8650 v75, SM8750 v79).
The Android sample timestral in the LiteRT-Models zoo is a full app around the Kotlin above: paste a series, open a CSV file or pick a bundled example, choose the horizon and the accelerator, and it draws the end of the series, the forecast's median and its 10–90 % band. It loads the eight graphs and the three .f32 tables of this repository: its scripts/install_to_device.sh copies them from a directory on the computer into the app's files directory.
Demo
The Android sample on a Galaxy S26: 56 daily order counts pasted in (synthetic, the last 56 days of the sample's bundled example), a 14-day forecast on the NPU with timestral-209m-c2048-h256.tflite. The screen gives 17.7 ms for the graph call (writing the inputs, run(), reading the output back) and 4.1 ms for the host steps. It is the app's second forecast after the install; the first compiled the graph for the NPU in 16.3 s in that run. The clip has one cut, in a pause before the Forecast tap where the screen did not change.
Speed on phones
One forecast is one call of a two-row graph (the series and its negation): writing the five inputs, run() and reading the output back. The host steps before and after the call are not included. Each cell is the median of the timed calls (their minimum in brackets) with the file of that size and kind.
| Phone, accelerator | c2048-h256 |
c2048-h1024 |
c8192-h256 |
c8192-h1024 |
|---|---|---|---|---|
| Galaxy S26 NPU (Qualcomm HTP), float16 files | 15.6 ms (15.5) | Not measured | not used | not used |
| Galaxy S26 GPU FP16, float16 files | 27.0 ms (26.0) | 28.8 ms (27.9) | 68.7 ms (68.0) | 73.4 ms (72.5) |
Galaxy S26 CPU, 4 threads, -int8 files |
30.3 ms (29.3) | 39.3 ms (37.4) | 128.5 ms (126.3) | 144.2 ms (140.8) |
| Galaxy S26 CPU, 4 threads, float16 files | 76.2 ms (73.6) | 93.8 ms (88.2) | 294.2 ms (282.0) | 321.0 ms (309.9) |
| Pixel 8a GPU FP16, float16 files | Not measured | Not measured | Not measured | Not measured |
Pixel 8a CPU, 4 threads, -int8 files |
Not measured | Not measured | Not measured | Not measured |
- Galaxy S26 NPU (Qualcomm HTP), float16 files: LiteRT 2.2.0 C API in a native test program, not an app, with the QAIRT 2.47.0 libraries, compiled on the phone (JIT), HTP performance mode BURST; a fresh process, Android thermal status 0 (no throttling) at the start; 20 timed calls after 5 warm-up calls. First JIT compile 19.5 s.
- Galaxy S26 GPU FP16, float16 files: LiteRT 2.2.0 Kotlin API (a benchmark app), GPU precision FP16; a fresh process, Android thermal status 0 (no throttling) at the start; 20 timed calls after 5 warm-up calls. Compile 1.6 s (
c2048-h256), 1.4 s (c2048-h1024), 1.6 s (c8192-h256), 1.5 s (c8192-h1024); peak resident memory (VmHWM) 1.83 GiB; the GPU's clock ceiling stayed at 1,300 MHz. - Galaxy S26 CPU, 4 threads,
-int8files: LiteRT 2.2.0 Kotlin API (a benchmark app), 4 threads; a fresh process, Android thermal status 0 (no throttling) at the start; 20 timed calls after 5 warm-up calls. Compile 0.2 s; peak resident memory (VmHWM) 0.61 GiB (c2048-h256), 0.61 GiB (c2048-h1024), 0.63 GiB (c8192-h256), 0.64 GiB (c8192-h1024). - Galaxy S26 CPU, 4 threads, float16 files: LiteRT 2.2.0 Kotlin API (a benchmark app), 4 threads; a fresh process, Android thermal status 0 (no throttling) at the start; 20 timed calls after 5 warm-up calls. Compile 0.4 s; peak resident memory (VmHWM) 1.29 GiB (
c2048-h256), 1.30 GiB (c2048-h1024), 1.31 GiB (c8192-h256), 1.32 GiB (c8192-h1024). - The
c8192-h256andc8192-h1024files are not used on the NPU: on the Galaxy S26, other forms of these graphs (one row per call or float32 weights) took 82.0 to 96.5 ms per forecast on the NPU, against 68.7 to 73.4 ms for these files on its GPU.
Accuracy against the official forecaster
The reference is the official forecaster (PyTorch, float32, CPU, every default) on 84 windows: 72 from public datasets (UCI Air Quality, Datadog BOOM, UCI ElectricityLoadDiagrams20112014; contexts of up to 2,048 and of 8,192 steps, horizons of 24 to 720 steps) and 12 synthetic series. Each run below forecasts these windows with the graphs and the host code: on the desktop, all 84 windows, each with the smallest graph that holds it; on the phones, each file the windows of its size (the Windows column: a context of up to 2,048 steps for c2048, of 2,049 to 8,192 steps for c8192, and for h256 a horizon of up to 256 steps).
MASE is the mean absolute error of the median over the seasonal error of the context; CRPS is here the weighted quantile loss over the nine deciles 0.1 to 0.9 (the form the fev-bench and GIFT-Eval benchmarks use). Both are averaged over the 22 groups of windows (dataset, context length, horizon; MASE over 21, as the constant series has no seasonal error), and the ratios divide the run's values by the reference's. A ratio below 1 is not a better model: it is how far the float16 or int8 arithmetic moved the forecast on these windows, in either direction. The check for every run: both ratios at most 1.01, no NaN or infinite value, and the 21 quantiles in order at every step.
| Run | Accelerator | Files | Windows | MASE ratio | CRPS ratio | NaN / inf | Quantile order broken |
|---|---|---|---|---|---|---|---|
| Desktop (Apple M4 Max), LiteRT 2.2.0 | CPU, 4 threads | the four -int8 files |
84 | ×1.0006 | ×0.9952 | 0 | 0 |
| Desktop (Apple M4 Max), LiteRT 2.2.0 | GPU (Metal), default precision FP16 | the four float16 files | 84 | ×1.0008 | ×0.9999 | 0 | 0 |
| Galaxy S26, LiteRT 2.2.0 | NPU (Qualcomm HTP) | timestral-209m-c2048-h256.tflite |
55 | ×1.0049 | ×0.9991 | 0 | 0 |
| Galaxy S26 | NPU (Qualcomm HTP) | timestral-209m-c2048-h1024.tflite |
Not measured | ||||
| Galaxy S26, LiteRT 2.2.0 | GPU FP16 | timestral-209m-c2048-h256.tflite |
55 | ×0.9954 | ×0.9993 | 0 | 0 |
| Galaxy S26, LiteRT 2.2.0 | GPU FP16 | timestral-209m-c2048-h1024.tflite |
71 | ×0.9924 | ×0.9992 | 0 | 0 |
| Galaxy S26, LiteRT 2.2.0 | GPU FP16 | timestral-209m-c8192-h256.tflite |
5 | ×1.0011 | ×1.0015 | 0 | 0 |
| Galaxy S26, LiteRT 2.2.0 | GPU FP16 | timestral-209m-c8192-h1024.tflite |
13 | ×0.9997 | ×1.0002 | 0 | 0 |
| Galaxy S26, LiteRT 2.2.0 | CPU, 4 threads | timestral-209m-c2048-h256-int8.tflite |
55 | ×1.0010 | ×0.9920 | 0 | 0 |
| Galaxy S26, LiteRT 2.2.0 | CPU, 4 threads | timestral-209m-c2048-h1024-int8.tflite |
71 | ×1.0007 | ×0.9938 | 0 | 0 |
| Galaxy S26, LiteRT 2.2.0 | CPU, 4 threads | timestral-209m-c8192-h256-int8.tflite |
5 | ×0.9999 | ×0.9994 | 0 | 0 |
| Galaxy S26, LiteRT 2.2.0 | CPU, 4 threads | timestral-209m-c8192-h1024-int8.tflite |
13 | ×0.9992 | ×1.0001 | 0 | 0 |
| Galaxy S26, LiteRT 2.2.0 | CPU, 4 threads | timestral-209m-c2048-h256.tflite |
55 | ×0.9999 | ×1.0000 | 0 | 0 |
| Galaxy S26, LiteRT 2.2.0 | CPU, 4 threads | timestral-209m-c2048-h1024.tflite |
71 | ×0.9999 | ×1.0000 | 0 | 0 |
| Galaxy S26, LiteRT 2.2.0 | CPU, 4 threads | timestral-209m-c8192-h256.tflite |
5 | ×1.0000 | ×1.0000 | 0 | 0 |
| Galaxy S26, LiteRT 2.2.0 | CPU, 4 threads | timestral-209m-c8192-h1024.tflite |
13 | ×1.0000 | ×1.0000 | 0 | 0 |
| Pixel 8a | GPU FP16 | the float16 files | Not measured | ||||
| Pixel 8a | CPU, 4 threads | the -int8 files |
Not measured |
Limits
- One series per forecast, without covariates: the multivariate rows, past-only and known-future covariates and categorical covariates of the official model are not part of these graphs.
- A horizon of up to 1,024 steps (one decoder block of 32 patches). The official forecaster decodes longer horizons block by block, with each block's median appended to the context;
host_forecast.pykeeps that loop, but no result here covers it (the evaluation windows have horizons of up to 720 steps), and the Python class and the Kotlin host stop at 1,024 steps. - A context of up to 8,192 steps: of a longer series the last 8,192 steps are read, which is the official forecaster with
max_context=8192(its default is 32,768). - Float16 and int8: the GPU runs the float16 files at FP16, the Qualcomm HTP computes in float16 and the
-int8files compute most of their fully connected layers in int8, so their quantiles differ slightly from float32 (Accuracy above). - The NPU path was run on one chip, the Snapdragon SM8850 of a Galaxy S26.
- The Python class refuses fewer than 16 values, or fewer than 16 observed ones; the Kotlin host in
android/and the official forecaster have no minimum.
How the graphs were made
The graphs are a re-implementation of the official PyTorch model (mistralai/timestral at commit a534bdbd244d) with fixed shapes, one per size, converted with litert-torch 0.9.4; the weights of the float16 files are cast to float16 with ai-edge-quantizer 0.9.0, and the inputs and output of every file stay float32. With float32 weights they give the official model's numbers (first line below).
- The time attention takes the mask as an additive input instead of boolean masks, and both products are
einsum, which keeps the batch matrix multiplications at rank 4. - With one series, the variate attention sees one key per query (its softmax is 1), so it is computed as the output projection of the value projection times the sigmoid gate.
- The fused projections (
wQKV, the SwiGLU'sw12) are split into separate fully connected layers. - The decoder tokens and the RoPE rows are inputs that the host fills from the checkpoint's
forecast_queryand the official RoPE module, so no position arithmetic, gather or cast is in the graph. - The monotone quantile head (softplus, cumulative sums, flips) is one 42 × 21 fully connected layer over the softplus and raw outputs, with softplus written as relu(x) + log(1 + exp(−|x|)).
- In the last layer only the decoder tokens go through the queries, the attention output, the variate attention and the MLP (the keys and values still come from every token): the head reads only those rows, so the function is the same.
- Float16 range: Timestral-209M's attention logits and RMSNorm sums of squares leave the float16 range (third line below). Each RMSNorm input is pre-scaled by a power of two, the logits are computed at 1/16 with the row maximum subtracted before the scale is restored, the softmax input is clamped at −3·10⁴ and the mask value is −3·10⁴. In float32 these rewrites leave the results unchanged (first line below).
- The
-int8files come from the float32 graphs through ai-edge-quantizer's dynamic-range int8 (second line below); their accuracy is in the CPU rows of the table above. - To skip the compile at the first load on the NPU, a float16 file can be compiled ahead of time for one Snapdragon chip with
aot_compile(ai_edge_litert.aot.aot_compile) and the Qualcomm target of ai-edge-litert 2.2.0, on Linux x86-64 with Qualcomm's AOT package (it does not run on macOS); the result runs on that chip only. This repository holds no such file.
- The float16 files' graphs with float32 weights on a desktop CPU (LiteRT 2.2.0), over the 84 windows: MASE ratio ×1.0000002, CRPS ratio ×1.0000001, largest relative quantile difference 7.5e-06, NaN / inf 0, quantile order broken 0.
- The
-int8files: dynamic-range int8 from ai-edge-quantizer 0.9.0 (int8 weights per channel, inputs quantized at run time) on 199 of the 217 fully connected layers, 91.6 % of their weights: all those of the first 23 of the 24 transformer blocks. The other 18 keep float16 weights (3 in the patch embedding, 11 in the last block, 4 in the quantile head). - Activation ranges of the model in float32 over the same windows (each window and its negation, 168 calls): attention logits up to 242,875, RMSNorm sums of squares up to 164,602,176, the residual stream up to 9,534; the largest float16 value is 65,504.
timestral-209m-c2048-h256.tflite: 1,883 ops, tensors of rank 4 at most; ops the LiteRT GPU path rejects (gather, select, cast, cumsum and similar): none.timestral-209m-c2048-h1024.tflite: 1,883 ops, tensors of rank 4 at most; ops the LiteRT GPU path rejects (gather, select, cast, cumsum and similar): none.timestral-209m-c8192-h256.tflite: 1,883 ops, tensors of rank 4 at most; ops the LiteRT GPU path rejects (gather, select, cast, cumsum and similar): none.timestral-209m-c8192-h1024.tflite: 1,883 ops, tensors of rank 4 at most; ops the LiteRT GPU path rejects (gather, select, cast, cumsum and similar): none.- Galaxy S26 NPU,
timestral-209m-c2048-h256.tflite:Partitioned subgraph<0>, selected 1883 ops, from a total of 1883 ops. resulted in 1 partitions;Replacing 1 out of 1 node(s) with delegate (DispatchDelegate) node, yielding 1 partitions - Galaxy S26 GPU,
timestral-209m-c2048-h256.tflite:Replacing 1883 out of 1883 node(s) with delegate (LITERT_CL) node, yielding 1 partitions - Galaxy S26 GPU,
timestral-209m-c2048-h1024.tflite:Replacing 1883 out of 1883 node(s) with delegate (LITERT_CL) node, yielding 1 partitions - Galaxy S26 GPU,
timestral-209m-c8192-h256.tflite:Replacing 1883 out of 1883 node(s) with delegate (LITERT_CL) node, yielding 1 partitions - Galaxy S26 GPU,
timestral-209m-c8192-h1024.tflite:Replacing 1883 out of 1883 node(s) with delegate (LITERT_CL) node, yielding 1 partitions - Galaxy S26 CPU,
timestral-209m-c2048-h256.tflite:Replacing 1883 out of 1883 node(s) with delegate (TfLiteXNNPackDelegate) node, yielding 1 partitions - Galaxy S26 CPU,
timestral-209m-c2048-h1024.tflite:Replacing 1883 out of 1883 node(s) with delegate (TfLiteXNNPackDelegate) node, yielding 1 partitions - Galaxy S26 CPU,
timestral-209m-c8192-h256.tflite:Replacing 1883 out of 1883 node(s) with delegate (TfLiteXNNPackDelegate) node, yielding 1 partitions - Galaxy S26 CPU,
timestral-209m-c8192-h1024.tflite:Replacing 1883 out of 1883 node(s) with delegate (TfLiteXNNPackDelegate) node, yielding 1 partitions - Galaxy S26 CPU,
timestral-209m-c2048-h256-int8.tflite:Replacing 1684 out of 1684 node(s) with delegate (TfLiteXNNPackDelegate) node, yielding 1 partitions - Galaxy S26 CPU,
timestral-209m-c2048-h1024-int8.tflite:Replacing 1684 out of 1684 node(s) with delegate (TfLiteXNNPackDelegate) node, yielding 1 partitions - Galaxy S26 CPU,
timestral-209m-c8192-h256-int8.tflite:Replacing 1684 out of 1684 node(s) with delegate (TfLiteXNNPackDelegate) node, yielding 1 partitions - Galaxy S26 CPU,
timestral-209m-c8192-h1024-int8.tflite:Replacing 1684 out of 1684 node(s) with delegate (TfLiteXNNPackDelegate) node, yielding 1 partitions
Other ports
Timestral has also been converted to ONNX (amine40/Timestral-209M-ONNX and the other sizes, mrfakename/Timestral-209M-ONNX, onnx-community/Timestral-28M-ONNX and the 5M size, konfi-labs/Timestral-28M-batched-ONNX), MLX (jbilcke/timestral-mlx, vikiival/timestral-mlx) and GGUF (mudler/timestral-cpp-gguf, EvoAwaken-Workshop/Timestral-209M-gguf).
Upstream
- Model: mistralai/Timestral-209M, revision
9874d252ab0aae16f7f7c999f98f98d6ad7d8365. Its card reports the model's zero-shot results on GIFT-Eval, TIME and fev-bench; this port does not re-run them. - Code: github.com/mistralai/timestral, commit
a534bdbd244d64f3d9d743f46c7bf6fc330ed4ff, Apache-2.0.
License
The weights of Timestral-209M are licensed by Mistral AI under the Modified MIT License, and so are the graph files and the three .f32 tables of this repository, which are derived from them. The full text is in LICENSE. Attribution notice: 2026 - Mistral AI. Its condition 2 reads:
- You are not authorized to exercise any rights under this license if the global consolidated monthly revenue of your company (or that of your employer) exceeds $20 million (or its equivalent in another currency) for the preceding month. This restriction in (b) applies to the Model and any derivatives, modifications, or combined works based on it, whether provided by Mistral AI or by a third party. You may contact Mistral AI (sales@mistral.ai) to request a commercial license, which Mistral AI may grant you at its sole discretion, or choose to use the Model on Mistral AI's hosted services available at https://mistral.ai/.
Mistral AI's card adds: you must not use this model in a manner that infringes, misappropriates, or otherwise violates any third party's rights, including intellectual property rights.
The code files (host_forecast.py, timestral_litert.py, android/*.kt) port the official inference code and are licensed under the Apache License 2.0 (LICENSE-APACHE-2.0, NOTICE).
- Downloads last month
- -
Model tree for mlboydaisuke/Timestral-209M-LiteRT
Base model
mistralai/Timestral-209M