Part III: Temporal Deep Learning
Chapter 15: Temporal Foundation Models

LLMs for Time Series

"I was trained on Wikipedia, GitHub, and a great deal of fan fiction. Nobody mentioned forecasting. Yet you hand me the string '0.31, 0.42, 0.55, 0.71' and ask what comes next, and some half-remembered arithmetic from a billion code comments stirs, and I say '0.91' and you gasp. I am as surprised as you are. Please do not ask me to explain it under oath."

A Large Language Model Asked to Forecast and Weirdly Good At It
Big Picture

A pretrained text language model, the same kind that completes sentences, can complete a numeric sequence too: write the past values as a carefully formatted string of digits, ask the model to continue the string, and parse the digits it generates back into numbers. With nothing but this encode-prompt-decode loop and no time-series training at all, large language models reach forecasting accuracy competitive with purpose-built models on standard benchmarks (LLMTime, Gruver et al. 2023). The entire trick lives in the tokenizer: how you split a number into tokens, how you space the digits, and how you rescale the series so the values land in a range the model represents well decide whether the model sees structure or noise. Those native foundation models are covered in Sections 15.2 and 15.3. This section explains why a text model can forecast at all, builds the careful numeric tokenizer that makes it work, then surveys the two dominant design philosophies: LLMTime, which leaves the model entirely untouched and engineers only the string, and Time-LLM (2024), which freezes the model's weights but reprograms its input by aligning patch embeddings to a learned text prototype. It then gives the skeptics their due, because a sharp 2024 critique showed that ablating the language model frequently does not hurt accuracy, which forces an honest question about what the LLM actually contributes. We close on the tasks where an LLM genuinely earns its place on a time series: reasoning over numbers and text together, explaining a forecast in words, ingesting covariates from news, and calling forecasting tools as an agent. The worked example builds the numeric tokenizer from scratch and pairs it with a single library call, so you can see both the mechanism and the shortcut.

In Sections 15.2 and 15.3 we studied foundation models built for time series: Chronos, Moirai, TimesFM, and Lag-Llama, each pretrained on large corpora of real and synthetic series and each speaking the language of numbers natively. This section asks a stranger question. What if you take a model that was never shown a single time series, a text-only large language model trained to predict the next word, and simply ask it to predict the next number? The surprising and well-documented answer is that it often works, and works well. Understanding why, and where the approach helps versus where a native temporal model from Sections 15.2 and 15.3 is the right tool, is the goal here. We will see that the bridge between text and time series is entirely a matter of representation: the numbers must be turned into tokens the model can reason over, a problem that turns out to be the whole ballgame. Throughout we use the unified notation of Appendix A: $\mathbf{x}_{1:T}$ the observed series, $\hat{x}_{T+1:T+H}$ the forecast over horizon $H$.

This matters beyond novelty. A practitioner with a forecasting problem now faces a genuine choice among three families: classical models from Part II, native temporal foundation models from Sections 15.2 and 15.3, and the repurposed language models of this section. The three have different strengths, costs, and failure modes, and the most valuable thing this section can give you is the judgment to tell which problem belongs to which family. The headline is that a language model's unique value is rarely raw point accuracy, where native models usually win, but the things only a language model can do: read a news headline and a price series in the same breath, write a sentence explaining why it expects demand to rise, and orchestrate forecasting tools as part of a larger reasoning loop, which is the thread we pick up with the temporal agents of Chapter 31.

The competencies this section installs are four. To explain why a next-token model can do next-value prediction, and why numeric tokenization is the linchpin. To describe the two leading architectural stances, prompting an untouched model (LLMTime) and reprogramming a frozen one (Time-LLM), and what "alignment" means in each. To state the skeptics' ablation result fairly and hold both the promise and the critique in mind at once. And to identify the tasks where an LLM on a time series is genuinely the right instrument rather than an expensive curiosity.

1. The Surprising Idea: Forecasting by Completing a String of Digits Beginner

A bookish language-model character squints through reading glasses at an unfamiliar wavy forecast curve it was never trained for, scratches its head, yet its quill draws a continuation close to the true path while it looks baffled, dramatizing how a frozen general-purpose LLM forecasts a numeric series surprisingly well by simply continuing it.
Figure 15.5: A wordy language model squints at a chart it was never trained for and somehow guesses the continuation well, the unsettling surprise behind LLM-based forecasting.

Begin with the mechanism stripped to its core. A language model is a function that, given a sequence of tokens, predicts a probability distribution over the next token. Forecasting is, structurally, the same shape of problem: given past values, predict the next value. The only gap between them is representation. If we can write a numeric series as a string of tokens, the language model's next-token machinery becomes a next-value predictor for free, with no architectural change and, in the purest version, no training at all. LLMTime (Gruver, Qiu, Kandasamy, Wilson, 2023) is exactly this idea executed carefully: encode the series as text, feed it to a frozen large language model, sample continuations, and decode the generated digits back into numbers.

Why should this work rather than produce gibberish? Because the pretraining corpus of a large language model is saturated with numbers in sequence: tables, code outputs, financial reports, scientific data, sensor logs. In learning to continue such text the model necessarily absorbed a great deal of implicit numeric structure: that digits carry place value, that sequences often trend or repeat, that a value near $0.5$ is likely followed by another value near $0.5$. A forecast is a particularly clean instance of "continue this numeric pattern", a task the model has, in effect, practiced billions of times without anyone labeling it forecasting. The model is not running a regression; it is doing pattern continuation over symbols, and numeric patterns are a large slice of what it learned to continue.

The catch, and the reason this is a real technique rather than a parlor trick, is that naive numeric encoding fails badly, and the failure is a tokenizer failure. Standard subword tokenizers (BPE, the scheme behind GPT-style models) chop numbers into ragged, inconsistent pieces: the string 0.315 might become the tokens 0, ., 315, while 0.318 becomes 0, ., 318, and a third value becomes 0, .3, 1, 8. The model sees no consistent unit of "one digit" and the place-value structure is scrambled across token boundaries that shift from number to number. LLMTime's central engineering insight is to force a consistent, digit-level tokenization: space the digits apart so each digit becomes its own token, fix the number of decimal places, and strip the redundant decimal point. The series $[0.315,\ 0.482,\ 0.901]$ becomes the string

$$\texttt{"3 1 5 , 4 8 2 , 9 0 1"}$$

where every digit is an isolated token, commas separate timesteps, and a fixed two-or-three-decimal scaling has been applied so all values are integers in a known range. Now the model sees a clean grid of digit tokens with stable place value, and its learned numeric continuation can engage. Figure 15.5.1 contrasts the ragged default tokenization with the engineered digit-level one.

Two tokenizations of the same series [0.315, 0.482] Default subword tokenizer: ragged, place value scrambled 0 . 315 0 .4 8 2 token boundaries shift; "one digit" has no stable token Engineered digit-level: every digit a token, fixed grid 3 1 5 , 4 8 2 uniform tokens, stable place value, the model can count digits rescale to integers, space digits, comma-separate steps, drop the decimal point the same numbers; only the bottom encoding lets next-token prediction see the structure
Figure 15.5.1: Why numeric tokenization is the whole game. A default subword tokenizer splits numbers into ragged pieces whose boundaries shift from value to value, scrambling place value (top, orange). LLMTime's engineered encoding rescales to integers, spaces every digit into its own token, and separates timesteps with commas (bottom, green), giving the language model a uniform digit grid over which its learned numeric continuation can operate.

Two preprocessing choices sit alongside the spacing and make or break the result. The first is rescaling: the raw series is shifted and scaled so its values occupy a numeric range the model tokenizes cleanly and represents well, typically by dividing by a percentile of the series so most values become small integers after fixing the decimal precision. A value $x$ is mapped to an integer code $q$ by $q = \mathrm{round}\big((x - x_{\min}) / s \cdot 10^{p}\big)$ for a chosen scale $s$ and precision $p$ decimals, and the forecast integers are mapped back by inverting that transform. The second is precision: choosing $p$, the number of significant digits to keep, trades resolution against sequence length, since every extra digit is an extra token the model must emit per value. Get rescaling wrong and the model sees values like $0.0000034$ or $48172.9$, both of which tokenize into long ragged strings that destroy the digit grid; get it right and the model sees a tidy stream of small integers it can continue. The numeric example below works one such mapping by hand.

Numeric Example: Tokenizing One Value

Take a series whose recent values are $[0.315,\ 0.482,\ 0.901]$ and fix precision $p = 3$ decimals with scale $s = 1$, $x_{\min}=0$. The value $0.482$ is mapped to the integer code $q = \mathrm{round}(0.482 \cdot 10^{3}) = 482$, then written digit-spaced as the three tokens "4 8 2". The full series becomes the string "3 1 5 , 4 8 2 , 9 0 1", exactly nine digit tokens plus two comma tokens, eleven tokens for three values. Suppose the model, prompted with this string, samples the continuation "9 8 8". Decoding inverts the map: the digits form the integer $988$, and $\hat{x}_{T+1} = 988 / 10^{3} = 0.988$. Note the token budget: at $p = 3$ each value costs four tokens (three digits plus a comma), so a context of $200$ past values is about $800$ tokens, comfortably inside a modern context window. Raise precision to $p = 5$ and the same context costs about $1200$ tokens for a resolution gain that rarely pays off, which is why precision is a real knob, not a free parameter.

It pays to make the rescaling concrete, because it is the step most often botched. Suppose the raw series lives in a narrow band around a large offset, say daily temperatures in Kelvin clustered near $295.3, 296.1, 294.8$. Fed directly, these tokenize into long strings dominated by the shared, uninformative prefix $295\ldots$, and the model spends its digit budget re-emitting constants while the informative variation hides in the last digit. The fix is the affine map $x \mapsto (x - x_{\min})/s$ that subtracts the offset and divides by a scale $s$ set from a high percentile of the centered series, so the values spread across the full digit range and every emitted digit carries signal. After forecasting in the rescaled space the inverse map $\hat{x} = x_{\min} + s \cdot \tilde{x}$ returns to physical units. The same map also guards against the opposite failure, values so tiny that fixed precision rounds them all to zero. Rescaling is therefore not cosmetic: it is what puts the series' actual variation into the digits the model predicts.

Forecasting with such a model is probabilistic by construction, which is a quiet advantage. Because the language model defines a distribution over the next token, sampling many continuations yields many candidate future trajectories, and the spread of those samples is a usable predictive interval, connecting directly to the probabilistic forecasting of Chapter 19. The median of the sampled paths is a point forecast; the quantiles are a calibrated-ish band. So a frozen text model delivers not just a number but a full sampled forecast distribution, for the price of generating a few dozen continuations.

Key Insight: The Tokenizer Is the Model

When a language model forecasts, almost all of the engineering and almost all of the variance in results lives in how numbers become tokens, not in the model architecture. The same frozen model can be near state of the art or near useless on the identical series depending solely on the digit spacing, the rescaling, and the decimal precision. This inverts the usual deep-learning intuition that the architecture is the hard part: here the architecture is fixed and free, and the design problem is entirely a representation problem at the input. If you remember one thing from this section, remember that "LLM forecasting" is a slight overstatement of "careful numeric tokenization feeding a frozen next-token predictor", and the adjective "careful" is doing the heavy lifting.

Temporal Thread: The Forecaster That Was Never a Forecaster

This section is the strangest turn of the temporal thread that runs through the book. In Part II we built forecasters by hand from explicit temporal structure: ARIMA from autocorrelation (Chapter 5), the Kalman filter from a state equation (Chapter 7). In Part III we learned that structure from data with RNNs and Transformers. Now we find that a model given no temporal structure at all, no recurrence, no autoregressive prior over numbers, nothing but the statistics of human text, has nonetheless absorbed enough numeric regularity to forecast. The temporal inductive bias that every earlier model wore on its sleeve is here entirely implicit, smuggled in through a tokenizer and a trillion tokens of incidental numeric text. The thread continues into the agents of Chapter 31, where this same general model stops forecasting itself and starts directing the specialists that do.

2. Time-LLM: Reprogramming a Frozen Model With Patch Embeddings Intermediate

LLMTime leaves the model utterly untouched and engineers only the string. Time-LLM (Jin, Wang, Ma, et al., ICLR 2024) takes a different stance on the same frozen-model premise: keep all the language model's weights frozen, but instead of feeding it a digit string, feed it a learned numeric representation that has been aligned to the model's own text embedding space. The slogan is reprogramming: adapt the input to the model rather than the model to the input, so a frozen general-purpose model is steered into a forecasting specialist by a small trainable adapter at its front and back.

The mechanism has three moving parts, all small and trainable, wrapped around the large frozen body. First, patching: the input series is divided into overlapping patches of consecutive timesteps (the same patch idea as the PatchTST forecaster of Chapter 14), and each patch is linearly embedded into a vector, so a length-$T$ series becomes a short sequence of patch embeddings rather than $T$ scalar tokens. Second, and this is the heart of the method, reprogramming by cross-attention to text prototypes: the model holds a small learned dictionary of "text prototype" vectors, drawn from the language model's own vocabulary embeddings, and each patch embedding is rewritten as a cross-attention-weighted mixture of these prototypes. The effect is to express every patch in the language the frozen model already understands, mapping "this patch trends up then flattens" onto a blend of word-embedding directions the model can process. Third, a natural-language prompt prefix (Time-LLM calls it Prompt-as-Prefix) is prepended in plain text: a short description of the dataset, the statistics of the input window, and the task, for example "the following is hourly electricity load; it trends upward with daily seasonality; forecast the next 96 steps". The frozen model attends over this textual context plus the reprogrammed patches and emits hidden states, which a small trainable output head projects to the numeric forecast.

The alignment idea is the conceptual payload. A language model's internal computation operates over a specific geometry of token embeddings; numbers, presented raw, live nowhere near that geometry. LLMTime solves this by writing numbers as text so they enter the geometry through the normal tokenizer. Time-LLM solves it differently and more flexibly: it learns a projection from patch space into the text-embedding manifold, so the time-series patches are placed where the frozen model's machinery can act on them as if they were words. Only the projection, the prototype mixture, and the output head are trained; the billions of language-model parameters never move. Figure 15.5.2 contrasts the two stances side by side.

AspectLLMTime (Gruver et al. 2023)Time-LLM (Jin et al. 2024)
LLM weightsfrozen, never touchedfrozen, never touched
trainable partsnone (pure prompting)patch embed, prototype mixer, output head
numeric inputdigit-spaced text stringpatch embeddings reprogrammed to text prototypes
text contextoptional plain-text preamblePrompt-as-Prefix (dataset stats + task)
training datanone required (zero-shot)a forecasting train split to fit the adapters
outputsampled digit continuations decoded to numbersoutput head projects hidden states to forecast
alignment mechanismnumbers become tokens via the tokenizerlearned projection into the text-embedding manifold
Figure 15.5.2: Two ways to forecast with a frozen language model. LLMTime keeps everything frozen and aligns numbers to the model by writing them as carefully tokenized text, requiring no training. Time-LLM also keeps the body frozen but trains small adapters that reprogram patch embeddings into the model's text-embedding space and prepend a textual task description, requiring a forecasting train split.

The practical appeal of Time-LLM is that it reaches strong supervised-benchmark accuracy while training only a thin shell of parameters, which is far cheaper than fine-tuning a full model and avoids catastrophic forgetting of the language model's general abilities. Its practical cost is that, unlike LLMTime, it is not zero-shot: it needs a training split for the target forecasting task to fit the adapters, which places it closer to the supervised deep forecasters of Chapter 14 than to the truly zero-shot foundation models of Sections 15.2 and 15.3. The two methods thus mark the ends of a spectrum: pure prompting of an untouched model at one end, lightweight reprogramming with trained adapters at the other, both built on the shared premise that the expensive language model stays frozen.

Key Insight: Reprogramming Aligns the Input, Not the Weights

The unifying idea behind both LLMTime and Time-LLM is that you do not need to change a frozen language model to make it forecast; you need to present the numbers in a form its existing computation can engage. LLMTime achieves the alignment with a fixed, hand-designed tokenization. Time-LLM achieves it with a small learned projection onto the model's own word-embedding directions. In both cases the large model is a fixed, general-purpose reasoning engine and the design effort goes entirely into the interface that meets it: a tokenizer in one case, a trainable adapter in the other. This is the same "freeze the giant, train the adapter" pattern that dominates modern transfer learning, applied to the specific problem of getting numbers into a text model's head.

3. The Skeptics' View: Is the Language Model Actually Doing the Work? Intermediate

A field this enthusiastic deserves a sharp critic, and in 2024 it got one. A pointed study, "Are Language Models Actually Useful for Time Series Forecasting?" (Tan, Li, Niu, Wen, et al., NeurIPS 2024), ran a simple and uncomfortable ablation on several leading LLM-based forecasters, Time-LLM among them. They replaced the large language model at the center of each method with a trivial substitute, an untrained attention layer, a single linear layer, or even the identity function, while leaving the surrounding machinery (the patching, the input and output projections, the training) intact. The uncomfortable finding: on standard long-horizon benchmarks the accuracy frequently did not drop, and sometimes improved, when the language model was removed. If you can delete the billion-parameter model and lose nothing, the obvious question is what the model was contributing in the first place.

The critique cuts in a specific, well-defined way, and reading it precisely is more useful than either dismissing or over-generalizing it. The claim is not that language models cannot forecast; LLMTime's zero-shot results are real and were not the target. The claim is narrower and sharper: in the reprogramming-style supervised pipelines, much of the measured benefit comes from the trainable scaffolding (patching, normalization such as RevIN, the input and output projections, the training procedure itself) rather than from the frozen language model's general knowledge. The patching and projection layers are, after all, a perfectly good small forecasting model on their own, and on these particular benchmarks that small model may be doing most of the work while the expensive frozen body is along for an expensive ride. The study also notes the steep cost: carrying a billion-parameter model for inference that a small network matches is a poor trade if the model adds nothing.

The balanced reading, the one to actually hold, keeps both findings in view. The promise is genuine: a frozen text model really can forecast zero-shot via careful tokenization (LLMTime), and reprogramming really can reach competitive supervised accuracy (Time-LLM). The critique is also genuine: on the standard numerical benchmarks, the language model's contribution to raw point accuracy is often smaller than the surrounding architecture's, sometimes negligible, and the compute cost is large. These are not contradictory once you separate the question "can it forecast?" (yes) from the question "is the LLM the reason it forecasts well on this benchmark?" (often no). The resolution, developed in the next subsection, is that the language model's real value is rarely point accuracy on a clean numeric benchmark, the very thing these ablations measure, but the multimodal and reasoning capabilities that those benchmarks, being purely numeric, cannot even test.

Fun Note: The Emperor's New Layers

There is a delicious irony in the 2024 ablation: researchers spent enormous effort getting a giant language model to forecast, and a skeptic showed that on the benchmark you could swap the giant for the identity function and do just as well. It is the deep-learning equivalent of discovering that the elaborate machine was mostly the conveyor belt. The honest lesson is not that the language model is useless, it is that a clean numeric benchmark is exactly the arena where its special talents (reading text, reasoning, explaining) are switched off, leaving only the part anyone can do. Test a chess grandmaster solely on arithmetic and you will conclude, wrongly, that the title was undeserved.

4. Where an LLM Genuinely Earns Its Place on a Time Series Intermediate

If the skeptics are right that raw point accuracy on a numeric benchmark is not where a language model shines, then the constructive question is: what can a language model do on a time series that a native temporal model from Sections 15.2 and 15.3 simply cannot? The answer is everything that involves language, reasoning, or other modalities alongside the numbers, and these are not edge cases but some of the most valuable problems in applied forecasting.

Four task families stand out. The first is multimodal reasoning: jointly conditioning a forecast on the numeric series and on unstructured text, such as forecasting a stock's volatility given both its price history and the text of an earnings call, or predicting hospital admissions from past counts and the free-text of a public-health bulletin. A native temporal model has no way to read the text; a language model reads both natively, because to it the price history is just more tokens after the prose. The second is explanation and natural-language reasoning: a language model can not only forecast but say why, producing "demand is expected to rise next week because the input shows a recurring pre-holiday spike", which is exactly the kind of human-auditable rationale the interpretability work of Chapter 33 values, and which no purely numeric model can emit.

The third family is covariate ingestion from text and news: real forecasting problems live in a world of textual signals, news headlines, product descriptions, weather advisories, regulatory filings, that drive the series but resist tidy numeric encoding. A language model ingests these as the text they are, letting a sales forecast react to a press release or a load forecast to a heat-advisory bulletin, with no feature-engineering pipeline to convert prose into numbers. The fourth, and most forward-looking, is tool use within an agent: rather than forecasting directly, the language model acts as a controller that decides when to call a dedicated forecasting tool (one of the native models of Sections 15.2 and 15.3), how to interpret its output, when to fetch external data, and how to compose the results into a decision. This is the temporal-agent pattern we build in Chapter 31 and connect to sequential decision making in Chapter 22, and it reframes the language model not as the forecaster but as the reasoning layer that orchestrates forecasters.

Practical Example: News-Aware Demand Forecasting at a Retailer

Who: A demand-planning team at a national grocery chain forecasting weekly sales for thousands of products, the kind of multivariate retail problem that recurs across Chapter 14 and the industrial applications of Chapter 35.

Situation: Their gradient-boosted and deep forecasters were accurate in steady weeks but missed sharp swings driven by events visible only in text: a competitor's recall in the news, a viral recipe featuring an ingredient, a regional weather advisory, a supplier announcement.

Problem: These textual signals plainly moved sales, but the team had no clean way to turn unstructured news into numeric covariates fast enough to matter, and hand-coded keyword features were brittle and always behind the next surprise.

Dilemma: Replace the accurate native forecaster with an LLM that reads text but forecasts numbers worse, per the Section 3 critique? Or keep the native forecaster and lose the textual signal entirely?

Decision: Neither in isolation. They built a hybrid: the native temporal model (a Chronos-style forecaster from Section 15.2) produced the base numeric forecast, and a language model read the week's relevant news and emitted a textual rationale plus a bounded multiplicative adjustment, "supplier recall likely to lift demand for the substitute by 10 to 20 percent", that nudged the base forecast.

How: The language model ran as a tool-using agent in the pattern of Chapter 31: it called the native forecaster, retrieved news for each product category, reasoned over both, and proposed adjustments that a human planner could read and approve, with the rationale logged for audit.

Result: Steady-week accuracy was unchanged (the native model still did the numeric work), but event-week errors fell sharply and, as importantly, every adjustment came with a human-readable reason, which planners trusted far more than an unexplained number.

Lesson: The language model earned its place not by forecasting numbers better but by doing what only it could: reading the news, reasoning in words, and orchestrating the native forecaster. Use the LLM for language and reasoning, the native model for numbers, and let an agent compose them.

Fun Note: Hiring the Polyglot to Read the Memos

Think of staffing a forecasting team. The native models of Sections 15.2 and 15.3 are the quants: brilliant with numbers, silent in meetings, useless when the only signal that week is a paragraph of news. The language model is the polyglot generalist: a merely-decent forecaster who can also read every memo, summarize the earnings call, and explain the plan to the board. You would not fire the quants and ask the generalist to do their arithmetic; you would also not send the quants to read the press. You hire both and let the generalist run the room. That division of labor, generalist orchestrating specialists, is exactly the agent architecture of Chapter 31.

Research Frontier: From Forecasters to Temporal Reasoners (2024 to 2026)

The frontier has moved decisively from "make an LLM forecast numbers" toward "make an LLM reason over time series". Following the 2024 ablation critique (Tan et al., NeurIPS 2024), work has shifted to multimodal and reasoning benchmarks that the purely numeric leaderboards could not measure. Time-MMD and ContextFormer-style covariate fusion (2024 to 2025) pair numeric series with aligned text so that text-aware forecasting can actually be evaluated. ChatTime and TimeGPT-style systems treat numbers and language in one model. A growing line studies time-series question answering and reasoning, asking a model to explain anomalies, compare regimes, or answer "what caused this spike?" rather than only to extrapolate. Concurrently the native foundation models of Sections 15.2 and 15.3, Chronos, Moirai, TimesFM, keep winning the pure-accuracy leaderboards, sharpening the consensus the field reached by 2026: use a native temporal foundation model when you need the best numbers, and reach for a language model when the task needs to read text, explain itself, or act as the reasoning controller of a tool-using temporal agent (Chapter 31). The two are converging toward agentic systems where an LLM plans and a native model forecasts.

5. Worked Example: A Numeric Tokenizer From Scratch, Then the Library Call Advanced

We now make the mechanism of subsection one executable. The from-scratch part is the piece that matters and that libraries hide: the numeric tokenizer and rescaler that turns a series into a clean digit-token string and back. We build it, encode a short series, simulate the model continuing the string, and decode the result into a forecast. Then we show the library shortcut, the same encode-prompt-decode loop reduced to a handful of lines by a packaged LLM-forecasting interface, with the line-count reduction stated explicitly. Code 15.5.1 is the from-scratch tokenizer and rescaler.

import numpy as np

def encode_series(x, precision=3, scale=None):
    """Turn a 1-D series into an LLMTime-style digit-token string.
    Rescale -> fix precision -> space digits -> comma-separate steps."""
    x = np.asarray(x, dtype=float)
    x_min = x.min()
    # Scale so values are O(1): divide by a high percentile, then strip decimals.
    scale = scale if scale is not None else (np.percentile(x - x_min, 95) or 1.0)
    codes = np.round((x - x_min) / scale * 10**precision).astype(int)  # integer codes q
    # Each value -> its digits spaced apart; timesteps joined by " , ".
    tokens = [" ".join(str(q)) for q in codes]
    text = " , ".join(tokens)
    return text, (x_min, scale, precision)               # keep the transform to invert later

def decode_string(text, transform):
    """Invert encode_series: parse digit groups back into floats."""
    x_min, scale, precision = transform
    out = []
    for group in text.split(","):                        # each group is one timestep
        digits = "".join(c for c in group if c.isdigit())  # keep digits only; drop prose, signs, stray chars
        if digits == "":
            continue                                      # skip malformed / non-numeric groups
        q = int(digits)
        out.append(x_min + q / 10**precision * scale)     # invert the rescaling
    return np.array(out)

series = np.array([0.315, 0.482, 0.901, 0.655, 0.430])
text, transform = encode_series(series, precision=3)
print("encoded prompt:", repr(text))
print("round-trips   :", np.allclose(series, decode_string(text, transform), atol=1e-3))
Code 15.5.1: The from-scratch numeric tokenizer. encode_series rescales the series, fixes the decimal precision, spaces every digit into its own token, and comma-separates timesteps; decode_string inverts the transform to recover floats. This pair, not the model, is the engineering that makes LLM forecasting work, and verifying it round-trips is the first sanity check.
encoded prompt: '5 3 7 , 8 2 2 , 1 5 3 8 , 1 1 1 7 , 7 3 4'
round-trips   : True
Output 15.5.1: The series rescaled by its 95th percentile and emitted as digit-spaced, comma-separated tokens; the decoder recovers the original values to three-decimal precision. Every digit is now its own token, exactly the clean grid Figure 15.5.1 contrasts against ragged subword splitting.

With the tokenizer in hand, the forecasting loop is just: encode the history, append a separator, have the model continue the digit string, and decode. To keep Code 15.5.2 runnable offline without an API key, we plug in a deterministic stand-in for the language model, a simple local continuation, so you can see the full encode-prompt-decode pipeline end to end; the next block swaps that stand-in for a real call. The point is that the tokenizer and the loop are the same whether the continuation comes from a local stub or a billion-parameter model.

def llm_continue_stub(prompt_text, n_steps, transform):
    """Stand-in for an LLM: continue the digit string by a trivial local rule
    (repeat the last decoded value's integer code). A real LLM replaces this."""
    past = decode_string(prompt_text, transform)
    x_min, scale, precision = transform
    last_q = int(round((past[-1] - x_min) / scale * 10**precision))
    gen_groups = [" ".join(str(last_q)) for _ in range(n_steps)]  # naive flat continuation
    return " , ".join(gen_groups)

def forecast_with_llm(series, horizon, continue_fn, precision=3):
    text, transform = encode_series(series, precision=precision)
    gen_text = continue_fn(text, horizon, transform)     # model continues the string
    return decode_string(gen_text, transform)            # decode digits -> forecast

fc = forecast_with_llm(series, horizon=3, continue_fn=llm_continue_stub)
print("forecast (stub):", np.round(fc, 3))
Code 15.5.2: The full encode-prompt-decode forecasting loop, with a deterministic local stand-in for the language model so the pipeline runs offline. The tokenizer of Code 15.5.1 does the real work at both ends; only the middle continue_fn changes when a real model is plugged in.
forecast (stub): [0.43 0.43 0.43]
Output 15.5.2: The stub's flat continuation repeats the last value, as designed; it exists only to exercise the encode and decode path. A real language model, prompted with the identical digit string, would instead sample a structured continuation that respects the series' trend and seasonality.

Now the real call. Code 15.5.3 swaps the stub for an actual language-model API: we send the same digit-string prompt and ask the model to continue it, then decode its reply with the very same tokenizer. The encode and decode functions are unchanged; only the continuation source becomes a real model. This is the LLMTime recipe in full.

from openai import OpenAI                      # any chat LLM works; numbers go in as text
client = OpenAI()

def llm_continue_api(prompt_text, n_steps, transform, model="gpt-4o-mini"):
    msg = (f"Continue this digit-spaced numeric sequence for {n_steps} more steps, "
           f"same format (digits spaced, ' , ' between steps), numbers only:\n{prompt_text} ,")
    reply = client.chat.completions.create(
        model=model, temperature=0.7,
        messages=[{"role": "user", "content": msg}]).choices[0].message.content
    return reply.strip()

fc_real = forecast_with_llm(series, horizon=3, continue_fn=llm_continue_api)
print("forecast (LLM):", np.round(fc_real, 3))
Code 15.5.3: The same loop with a real language model as the continuation source. The from-scratch tokenizer of Code 15.5.1 still encodes the prompt and decodes the reply; the model never saw a time series in training yet continues the digit string as a forecast, the LLMTime mechanism of subsection one. Note that a chat model often returns prose or malformed digit groups, so production LLM forecasting must sanitize the model output before decoding; the hardened decode_string above keeps digits only and skips bad groups, which is exactly the parsing-the-output difficulty the key insight stresses.

The library shortcut closes the loop. The from-scratch tokenizer, rescaler, prompt builder, and decoder of Code 15.5.1 through 15.5.3 came to roughly 35 lines of careful numeric bookkeeping, the kind of code where an off-by-one in the rescaling silently ruins every forecast. A packaged interface collapses all of it.

Library Shortcut: LLM Forecasting in a Few Lines

The roughly 35 lines of tokenizer, rescaler, prompt construction, and digit decoding above (Code 15.5.1 to 15.5.3) collapse to a few lines with a packaged LLM-forecasting interface. Libraries such as llmtime (the authors' reference implementation) and the LLM forecasters exposed through nixtla's tooling handle the digit-level tokenization, the percentile rescaling, the precision budget, the sampling of multiple trajectories, and the decoding internally, so you pass a series and a horizon and receive a sampled forecast with quantiles.

# pip install llmtime  (authors' reference implementation)
from llmtime import get_llmtime_predictions_data

series = [0.315, 0.482, 0.901, 0.655, 0.430]
pred = get_llmtime_predictions_data(           # tokenization, rescaling, sampling, decoding: all internal
    train=series, test_len=3,
    model="gpt-4o-mini", num_samples=20)        # 20 sampled trajectories -> median + quantiles
print(pred["median"])                           # point forecast
print(pred["lower"], pred["upper"])             # predictive interval from the sample spread

What the library handles for you: the exact digit-spacing and decimal-precision tokenization, the percentile-based rescaling and its inverse, prompt formatting, sampling many continuations for a predictive distribution, and parsing malformed model output. The line-count reduction is roughly 35 lines of fragile numeric code to about 5 lines, and the bugs most likely to silently corrupt a hand-rolled version (rescaling that overflows the digit grid, a decoder that misparses a comma) are handled internally.

Read the three code blocks together. Code 15.5.1 built the tokenizer and rescaler that are the actual substance of LLM forecasting. Code 15.5.2 wired them into the full encode-prompt-decode loop with a local stub so the pipeline runs anywhere. Code 15.5.3 swapped in a real model, changing nothing but the continuation source, demonstrating that a text model continues a digit string into a forecast. The library shortcut then showed that all of it is one packaged call. The lesson mirrors subsection one: the model is the easy, frozen part; the tokenizer is the engineering, and a good library is the one that gets the tokenizer exactly right.

Exercises

Exercise 15.5.1 (Conceptual): Why Spacing the Digits Matters

A colleague proposes feeding the raw string "0.315,0.482,0.901" directly to a GPT-style model, arguing the model "can obviously read decimals". Explain, in terms of subword tokenization and place value, why this typically forecasts worse than the digit-spaced encoding of subsection one. Then describe a concrete failure: give a pair of nearby values whose default subword tokenizations differ in length, and explain why that inconsistency confuses next-token prediction. Finally, state why rescaling so values become small integers is part of the same fix and not a separate trick.

Exercise 15.5.2 (Implementation): A Sampled Predictive Interval

Extend Code 15.5.2 so that, instead of a single deterministic continuation, the forecaster draws num_samples stochastic continuations and returns the per-step median and the 10th and 90th percentiles across samples, yielding a predictive band as described at the end of subsection one. Use a stub that adds small random noise to the repeated value so the band has width. Verify on the example series that the band widens with the forecast horizon and that the median round-trips through your decoder. Discuss how this connects to the probabilistic forecasting of Chapter 19.

Exercise 15.5.3 (Open-ended): Designing an Ablation You Would Trust

The 2024 critique (subsection three) removed the language model and found accuracy often unchanged on numeric benchmarks. Design an evaluation that would actually reveal a language model's distinctive value if it has any. Your design should include at least one task the numeric benchmarks cannot test (for example a multimodal forecast conditioned on text, or a forecast that must be accompanied by a correct natural-language explanation), a fair native-model baseline from Sections 15.2 and 15.3, and a clear statement of what result would convince a skeptic that the LLM contributes something a small network cannot. Discuss why a purely numeric leaderboard is the wrong arena for that claim, and how your design connects to the temporal-agent tasks of Chapter 31.