Skip to content

Sequences, geometric series, frequency ladders

ModuleM00.3 · build · Python · Pass 2 · 2 to 3 h
You buildpython/tinyllm/num/series.py: geometric, geometric_sum, rope_inv_freq, alibi_slopes
Contractcourse/contracts/py/tinyllm/num/series.pyi
Testscourse/tests/M00.3/test_series.py (what they check: section 4)
Needsnothing to call. Reading: M00.1 (powers, log1p and expm1), lang.01
Used byM02.2 the EMA’s weights and bias correction call geometric and geometric_sum · later L7.3 RoPE inv_freq and L7.4 ALiBi slopes and YaRN’s per-frequency ramps · M10.4 learning-rate schedules (reading) · later: L5.4
MilestoneMS-P2 (the Pass 2 gate)
Optional depthOpenStax, Precalculus 2e (free), ch. 11 (sequences and series); Press, Smith, and Lewis, “Train Short, Test Long: Attention with Linear Biases” (ALiBi, 2022), section 3; Su et al., “RoFormer” (2021), section 3.3
  • A geometric sequence multiplies by the same ratio rr at every step: a,ar,ar2,…a, ar, ar^2, \ldots (test_geometric_terms).
  • Its first nn terms add up to a(1−rn)/(1−r)a(1 - r^n)/(1 - r), by subtracting rr times the sum from the sum (test_hand_example_geometric_sum, test_sum_matches_exact_rationals).
  • Near r=1r = 1 that formula cancels: it subtracts two nearly equal numbers and loses half its digits. Rewriting it with expm1⁡\operatorname{expm1} and log1p⁡\operatorname{log1p} keeps them all; Adam’s bias correction lives exactly there (test_sum_near_one_keeps_its_digits, test_ema_bias_correction_identity).
  • RoPE’s inverse frequencies b−2i/db^{-2i/d} form a geometric ladder from one radian per token down to about 1/b1/b, so pairs of dimensions see position at every scale from a few tokens to tens of thousands (test_rope_inv_freq_golden_hf).
  • ALiBi slopes are a geometric ladder of 2−8/n2^{-8/n}; head counts that are not powers of two interleave a second ladder (test_hand_example_alibi_six_heads, test_alibi_golden_hf).
Terminal window
ol start M00.3 # stubs python/tinyllm/num/series.py into your repo
ol tests M00.3 # read the test catalog first: rung R0, you write no tests here
ol check M00.3 # exit code is the verdict
ol diff M00.3 # after passing: your code against the reference

M00.2 gave you rotations, and RoPE rotates each pair of a query by an angle proportional to the token’s position. The open question is how fast each pair should turn. If every pair turned by 1 radian per token, positions 2π≈6.32\pi \approx 6.3 tokens apart would look identical; if every pair turned slowly, neighbours would be indistinguishable. RoPE answers with a ladder of speeds, a geometric sequence, and so does ALiBi, the other position scheme in L7.4. The same mathematics appears in training: the moving averages of Adam (M02.2, M10.3) weight past gradients by a geometric sequence, and correct for its partial sum. Your SmolLM2 loader (L7.9) will read rope_theta = 100000 from a config.json and must turn it into exactly the frequencies Hugging Face computes. This module builds the sequences, their sums (accurately, including the hard case), and both ladders.

SymbolMeaningType / shape
aathe first term of a sequencefloat
rrthe common ratiofloat
nnthe number of termsint
aja_jterm jj, counted from j=0j = 0float
SnS_nthe sum of the first nn terms, ∑j=0n−1arj\sum_{j=0}^{n-1} a r^jfloat
β\betathe decay of an exponential moving average, 0<β<10 < \beta < 1float
gt,mtg_t, m_tthe value seen at step tt and the moving average after itfloat
ddthe rotary dimension (d_rot): the number of coordinates RoPE rotates, evenint
bbthe RoPE base (rope_theta in a config.json)float
ωi\omega_iinverse frequency of pair ii, b−2i/db^{-2i/d}, in radians per positionfloat[d/2]
λi\lambda_iwavelength of pair ii, 2π/ωi2\pi/\omega_i, in positionsfloat
HHthe number of attention headsint
shs_hthe ALiBi slope of head hhfloat[H]
ε\varepsilonfloat64 unit roundoff, 2−53≈1.1×10−162^{-53} \approx 1.1 \times 10^{-16}

A sequence is a list of numbers indexed by position: a0,a1,a2,…a_0, a_1, a_2, \ldots. Two families matter here. An arithmetic sequence adds the same step, aj=a+jda_j = a + jd (positions 0,1,2,…0, 1, 2, \ldots are one). A geometric sequence multiplies by the same ratio, aj=arja_j = a r^j: 1,12,14,…1, \tfrac12, \tfrac14, \ldots has a=1a = 1, r=12r = \tfrac12. With 0<r<10 < r < 1 the terms shrink toward 0; with r>1r > 1 they grow without bound; with r<0r < 0 they alternate in sign. Compute term jj as a * r**j, not by multiplying the previous term by rr: a running product rounds once per step and its error grows with jj.

Let Sn=a+ar+⋯+arn−1S_n = a + ar + \cdots + ar^{n-1}. Multiply by rr: rSn=ar+⋯+arn−1+arnrS_n = ar + \cdots + ar^{n-1} + ar^n. Subtracting, every term but two cancels:

Sn−rSn=a−arn⟹Sn=a 1−rn1−r,r≠1,S_n - rS_n = a - ar^n \quad\Longrightarrow\quad S_n = a\,\frac{1 - r^n}{1 - r}, \qquad r \ne 1,

and Sn=naS_n = na when r=1r = 1 (every term is aa). When ∣r∣<1|r| < 1, rnr^n approaches 0 as nn grows, so the infinite sum converges: a+ar+ar2+⋯=a/(1−r)a + ar + ar^2 + \cdots = a/(1 - r). That is why 1+12+14+⋯=21 + \tfrac12 + \tfrac14 + \cdots = 2, and why a repeating decimal is a fraction: 0.2727…=27/100+27/1002+⋯=(27/100)/(1−1/100)=3/110.2727\ldots = 27/100 + 27/100^2 + \cdots = (27/100)/(1 - 1/100) = 3/11.

2.3 The exponential moving average is a geometric series

Section titled “2.3 The exponential moving average is a geometric series”

An exponential moving average (EMA) with decay β\beta updates mt=βmt−1+(1−β)gtm_t = \beta m_{t-1} + (1 - \beta) g_t, starting from m0=0m_0 = 0. Unrolling,

mt=(1−β)(gt+βgt−1+β2gt−2+⋯+βt−1g1),m_t = (1 - \beta)\left(g_t + \beta g_{t-1} + \beta^2 g_{t-2} + \cdots + \beta^{t-1} g_1\right),

so the weights are a geometric sequence (1−β)βj(1 - \beta)\beta^j, newest first, and they add up to (1−β)1−βt1−β=1−βt(1 - \beta)\frac{1 - \beta^t}{1 - \beta} = 1 - \beta^t, not 1. Early on the average is biased toward its starting value 0: after one step with β=0.999\beta = 0.999, m1=0.001 g1m_1 = 0.001\,g_1. Adam divides by 1−βt1 - \beta^t to undo exactly this (M02.2, M10.3). With β=0.999\beta = 0.999 the ratio is 1−10−31 - 10^{-3}, close to 1, which is the hard case of section 2.6.

RoPE rotates pair ii of a query or key at position pp by the angle p ωip\,\omega_i (M00.2, section 2.5), with

ωi=b−2i/d,i=0,1,…,d2−1.\omega_i = b^{-2i/d}, \qquad i = 0, 1, \ldots, \tfrac d2 - 1 .

This is a geometric sequence with first term b0=1b^0 = 1 and ratio b−2/db^{-2/d}. Pair 0 turns one radian per token and repeats every λ0=2π≈6.3\lambda_0 = 2\pi \approx 6.3 positions; the last pair turns b−(d−2)/db^{-(d-2)/d} radians per token, slightly faster than 1/b1/b, and repeats only after about 2πb2\pi b positions. Like the hands of a clock (seconds, minutes, hours), fast pairs resolve neighbouring tokens and slow pairs tell far-apart ones apart, and the geometric spacing gives every scale the same number of pairs. The exponent steps by 2/d2/d because there are d/2d/2 pairs spread over the exponents from 0 to almost 1. Models with partial rotary embeddings rotate only the first dd coordinates of each head; the formula is the same with that dd. The SmolLM2-135M config.json says hidden_size = 576 over num_attention_heads = 9 (a head size of 64) and rope_theta = 100000; your rope_inv_freq(64, 1e5) must match what Hugging Face computes from those, which is what the golden test checks.

ALiBi (Press et al.) adds no rotation at all. Head hh subtracts shs_h times the distance between query and key from each attention score, so far tokens are penalized linearly, and each head has its own slope. For HH a power of two the slopes are the geometric sequence with first term and ratio 2−8/H2^{-8/H}:

sh=2−8h/H,h=1,…,H,s_h = 2^{-8h/H}, \qquad h = 1, \ldots, H,

from 2−8/H2^{-8/H} (steep: the head looks at the last few tokens) down to 2−8=1/2562^{-8} = 1/256 (flat: it looks hundreds of tokens back). For other HH, with pp the largest power of two below HH, the paper’s code takes the pp slopes for pp heads, then fills the remaining H−pH - p heads with every other slope of the 2p2p-head ladder, 2−8⋅1/(2p),2−8⋅3/(2p),…2^{-8 \cdot 1/(2p)}, 2^{-8 \cdot 3/(2p)}, \ldots, which fall between the existing ones. Find pp with integer arithmetic (1 << (H.bit_length() - 1)), never with a floating-point log⁡2\log_2.

For rr close to 1 the closed form divides one small difference by another. 1−r1 - r is computed exactly (subtracting nearby floats is exact), but rnr^n is rounded to about ε\varepsilon relative error first, and 1−rn1 - r^n then subtracts two numbers that agree in most of their digits, keeping the rounding error and losing the rest. With r=1−2−40r = 1 - 2^{-40} and n=10n = 10, 1−rn≈9×10−121 - r^n \approx 9 \times 10^{-12} carries an absolute error near 10−1610^{-16}: about 5 correct digits out of 16.

The cure is to never form rnr^n near 1. Since rn=enln⁡rr^n = e^{n \ln r},

1−rn1−r=expm1⁡(n⋅log1p⁡(r−1))r−1,\frac{1 - r^n}{1 - r} = \frac{\operatorname{expm1}(n \cdot \operatorname{log1p}(r - 1))}{r - 1},

where log1p⁡(u)=ln⁡(1+u)\operatorname{log1p}(u) = \ln(1 + u) and expm1⁡(v)=ev−1\operatorname{expm1}(v) = e^v - 1 are computed accurately for small arguments (math.log1p, math.expm1; M00.1). Every quantity in this form is small and accurate, and the result keeps nearly all 16 digits. For r>0r > 0 this form is used everywhere; for r≤0r \le 0 the closed form has no cancellation (1−r≥11 - r \ge 1). When the sum overflows (huge rr and nn), the answer is ±∞\pm\infty, by the sign of the last term.

A geometric sum. 1+12+14+181 + \tfrac12 + \tfrac14 + \tfrac18: a=1a = 1, r=12r = \tfrac12, n=4n = 4.

S4=1−(1/2)41−1/2=15/161/2=158=1.875,S_4 = \frac{1 - (1/2)^4}{1 - 1/2} = \frac{15/16}{1/2} = \frac{15}{8} = 1.875 ,

which agrees with adding 1+0.5+0.25+0.1251 + 0.5 + 0.25 + 0.125. With a=3a = 3 every term triples, so S4=45/8S_4 = 45/8. These are test_hand_example_geometric_sum.

The RoPE ladder for d=8d = 8, b=10000b = 10000. Four pairs, exponents −2i/8-2i/8:

iiexponentωi=10000exponent\omega_i = 10000^{\text{exponent}}wavelength 2π/ωi2\pi/\omega_i
0016.28 positions
1−1/4-1/40.10.162.8
2−1/2-1/20.010.01628
3−3/4-3/40.0010.0016283

(100001/4=1010000^{1/4} = 10 because 104=1000010^4 = 10000.) The ratio is 0.1=10000−2/80.1 = 10000^{-2/8}: test_hand_example_rope_ladder.

ALiBi with 6 heads. 66 is not a power of two, so p=4p = 4. The 4-head ladder has first term and ratio 2−8/4=2−22^{-8/4} = 2^{-2}: 2−2,2−4,2−6,2−8=14,116,164,12562^{-2}, 2^{-4}, 2^{-6}, 2^{-8} = \tfrac14, \tfrac1{16}, \tfrac1{64}, \tfrac1{256}. The 8-head ladder is 2−1,2−2,…,2−82^{-1}, 2^{-2}, \ldots, 2^{-8}; every other entry from the first is 2−1,2−3,2−5,…2^{-1}, 2^{-3}, 2^{-5}, \ldots, and the two extra heads take the first two:

s=[14,116,164,1256,12,18],s = \left[\tfrac14, \tfrac1{16}, \tfrac1{64}, \tfrac1{256}, \tfrac12, \tfrac18\right],

test_hand_example_alibi_six_heads. The extra slopes 12\tfrac12 and 18\tfrac18 sit between the existing ones, so the six heads still cover the range evenly.

python/tinyllm/num/series.py
def geometric(a: float, r: float, n: int) -> NDArray: ... # [a, a r, ..., a r^(n-1)]
def geometric_sum(a: float, r: float, n: int) -> float: ... # accurate near r = 1
def rope_inv_freq(d_rot: int, base: float) -> NDArray: ... # base ** (-2 i / d_rot), [d_rot/2]
def alibi_slopes(n_heads: int) -> NDArray: ... # Press et al., any head count

Everything is float64. Bad counts (negative, fractional, or True), an odd or non-positive d_rot, a base that is not finite and above 1, and a head count below 1 raise ValueError.

TestKINDChecksWhy it matters downstream
test_hand_example_geometric_sumunit, smokesection 3: 15/815/8, 45/845/8, and the four termsyou and the tests agree on what nn counts
test_hand_example_rope_ladderunit, smoked=8d = 8, b=10000b = 10000 gives [1,0.1,0.01,0.001][1, 0.1, 0.01, 0.001]the exponent is −2i/d-2i/d
test_hand_example_alibi_six_headsunit, smokethe 6-head slopes of section 3the non-power-of-two rule
test_geometric_termsunitnegative and unit ratios, and n=0n = 0 (empty)term 0 is aa
test_sum_matches_exact_rationalsdifferential66 sums against exact Fraction arithmetic on the same inputsany correct formula passes, any wrong count fails
test_sum_near_one_keeps_its_digitsboundaryr=1±2−40r = 1 \pm 2^{-40} and 1−10−91 - 10^{-9}: relative error below 10−1210^{-12}pitfall 1
test_ema_bias_correction_identityproperty∑j<t(1−β)βj=1−βt\sum_{j<t} (1 - \beta)\beta^j = 1 - \beta^t for β\beta up to 0.9990.999Adam’s bias correction (M02.2)
test_sum_overflows_to_infinityboundary±∞\pm\infty for sums beyond float64, by the sign of the last termno OverflowError escapes
test_bad_n_rejectedboundaryn=−1n = -1, 2.52.5, True raise ValueErrorcounts are whole numbers
test_rope_inv_freq_golden_hfgoldenHugging Face’s default RoPE for SmolLM2-135M, Llama 2, Llama 3, and a partial rotary sizeexactly what L7.9 loads
test_rope_ladder_is_geometricpropertyfirst rung 1, constant ratio b−2/db^{-2/d}, last rung above 1/b1/bthe ladder’s shape for any dd and bb
test_rope_bad_argumentsboundaryodd or zero dd, bases ≤1\le 1 or infinite raise ValueErrora flat or climbing ladder is a bug
test_alibi_powers_of_two_exactunit8 heads: 1/2,…,1/2561/2, \ldots, 1/256; 16 heads: 2−j/22^{-j/2}; 1 head: 1/2561/256the power-of-two rule
test_alibi_golden_hfgoldenBLOOM’s build_alibi_tensor for 20 head counts from 1 to 128twelve of them are not powers of two
test_alibi_bad_head_countboundary0, −4-4, 2.52.5 heads raise ValueErrora model has whole heads
PitfallSymptomCaught by
1. the textbook closed form near r=1r = 1Adam’s bias correction with β2=0.999\beta_2 = 0.999 correct to 7 digits instead of 16; tests at 10−1210^{-12} failtest_sum_near_one_keeps_its_digits (mutant s03)
2. the RoPE exponent −i/d-i/d instead of −2i/d-2i/dfrequencies fall only to b−1/2b^{-1/2}: long-range pairs turn far too fast and checkpoints from HF load with the wrong positionstest_hand_example_rope_ladder (mutant s04)
3. starting the ladder at i=1i = 1every frequency shifted one rung; the 1-radian pair is missingtest_rope_inv_freq_golden_hf (mutant s05)
4. ignoring the non-power-of-two rule, or taking the odd entries12-head and 40-head models get slopes no published model usestest_hand_example_alibi_six_heads (mutants s08, s09, s10)
5. one term too manySnS_n includes arnar^n; every bias correction is offtest_hand_example_geometric_sum (mutant s02)
6. starting the sequence at arargeometric returns ar,…,arnar, \ldots, ar^n; ALiBi slopes shift by one rungtest_geometric_terms (mutant s01)

| Forward | L5.4 | Registered call site uses this module. |

DirectionModuleHow it uses this
BackM00.1powers, ee, and log1p/expm1 (reading)
Backlang.01numpy arange and power (reading)
ForwardL7.3rope_inv_freq(d_rot, rope_theta) builds RoPE’s angles, positions * inv_freq
ForwardL7.4ALiBi biases from alibi_slopes; YaRN and NTK scaling bend the same ladder per frequency
ForwardM02.2the EMA’s weights, geometric(1 - beta, beta, t), and its bias correction, geometric_sum(1 - beta, beta, t)
ForwardM10.4step-decay schedules are geometric sequences of learning rates (reading)
ForwardM00.2the ladder feeds the angles that rotate_pairs applies (reading)
Your pieceProduction equivalentWhat it addsWhere to look
rope_inv_freqHugging Face ROPE_INIT_FUNCTIONS and compute_default_rope_parametersone function per scaling kind (linear, dynamic, yarn, llama3, longrope) that bends this ladder for longer contextstransformers/modeling_rope_utils.py
alibi_slopesBLOOM and MPT attention biasesslopes built once per model, biases fused into the attention kerneltransformers/models/bloom/modeling_bloom.py (build_alibi_tensor)
geometric_sum near r=1r = 1PyTorch torch.optim.Adamthe bias corrections 1 - beta1 ** step and 1 - beta2 ** step (in float32, where the cancellation is worse)torch/optim/adam.py
log1p and expm1C99 log1p, expm1the accurate small-argument functions every math library shipsman 3 expm1