Hippius
maincontainerPublic

mistlane/gen-941dd4328099

sha256:fee06d8557f60024aa7fcf312adda4a033229d230b0863e27d38b3e74a6d33c5·Indexed 27d ago

Layers

4

Total size

64.0 KB

Files

4

Quantization

README.md

15.2 KB

cascade-broad-v1 — wall-saturating, cluster-broad candidate generator

cascade SN91 submission repo. Three files are the contract (generator.py, config.json, requirements.txt); this README is documentation.

/tmp/.../venv/bin/cascade verify /workspace/cascade-rig/gen/candidate
/tmp/.../venv/bin/python /workspace/cascade-rig/rig/bench_throughput.py \
        /workspace/cascade-rig/gen/candidate --seconds 20

Measured (this box, single core, seed 9993167447787770989)

metric value
cascade verify OK — [deterministic], corpus_digest (seed 0) 4fdbf700a0b32b0c…
emitted throughput 13,468,137 points/s = 364.0 % of the 3,700,000 pts/s wall (two 20 s runs: 13.47 M / 13.91 M, i.e. 364–376 %)
series rate 5,479 series/s (saturation needs 1,712 at this mean length)
mean length 2,161 — 5 distinct lengths, all multiples of 32
32-align waste 0.00 % (every emitted point is trained on)
tokens_frac heat / final 1.000 / 1.000
first-series latency 0.06 s (per-frame stall timeout is 1800 s)
peak memory under RLIMIT_AS = 4 GiB VmPeak 906 MB, RSS 162 MB — 120k series streamed clean
exact byte-duplicate series 0.007 % (cap is 5 %)
non-finite after the trainer's causal_standardize + arcsinh 0 / 43.2 M tokens, max |z| = 4.85

Why it is fast

generate() never touches a single series in Python. It draws a block of B series of one identical, 32-aligned length L as one (B, L) float32 array, entirely with vectorised numpy plus scipy.signal.lfilter for the recursive parts, then yields the rows one at a time. Every recursion is a filter along the time axis of the whole batch (C-level), never a Python loop over time or over series.

  • points_per_block = 1,048,576B = 1M / L (256 rows at L=4096, 4096 rows at L=256), rounded to a multiple of 64 = the trainer's batch_size, so p-buckets flush without long-lived partials.
  • float32 blocks. check_series accepts any floating dtype and the trainer canonicalises to float64 losslessly. float32 halves memory traffic and takes the SIMD math path: cos is 2.5 ns/pt vs 21.7 ns/pt in float64 on this box.
  • interleave_slots = 6. Rows are drawn round-robin from 6 concurrently-open blocks, so a trainer minibatch is a mixture of families instead of 64 rows cut from one block. Costs ~nothing; removes the intra-batch correlation that block-sequential emission would otherwise hand the optimiser.
  • Per-row gates. Optional components (trend, level shift, bump, outage, stuck sensor, negative-price regime, …) are switched by a (B,1) 0/1 mask folded into the amplitude, not by an if around the whole block — full per-series diversity at the cost of one multiply.
  • Cheap smooth components. _slow() draws control knots at L/k resolution and linearly interpolates instead of filtering white noise at full rate (~10× cheaper for the same low-frequency content).

Family cost, measured at B=512, L=2048 (ns per emitted point → equivalent Mpts/s if that family ran alone):

family ns/pt Mpts/s alone
power_load 44 22.6
fin_walk 48 20.7
step_plateau 55 18.3
hydro_burst 59 17.0
geo_smooth 70 14.3
dock_counts 74 13.5
kernel_mix 94 10.6
daily_business 102 9.8
energy_price 127 7.9
degenerate 30 33.2
weighted mixture 77 13.0

Dominant remaining costs are rng.standard_normal (≈16 ns/pt even in float32) and the float64 round-trip that lfilter needs. If more headroom is ever wanted: switch the AR innovations of energy_price/kernel_mix from gaussian to the uniform innovation path (_innov kind 1, 4.3 ns/pt) — but there is 3.8× of slack today, and past the wall further speed is worth exactly zero.

Length choice

lengths = [4096, 2048, 1024, 512, 256] — all multiples of 32, so the trainer's p = min(L//32, 128) slice s[-p*32:] consumes 100 % of every emitted point (measured 32-align waste 0.00 %). Only 5 distinct lengths ⇒ only 5 p-buckets, each filling to 64 rows fast.

length_weights are the fraction of series at each length (the generator divides by that length's rows-per-block to get the block-draw probability). Defaults [0.30, 0.26, 0.22, 0.14, 0.08] put 63 % of emitted points at L = 4096, the exact context width the evaluator feeds, while keeping shorter contexts in the mix. Per-family overrides skew minute-cadence families long and daily families short, matching how long those feeds actually are.

The mixture — families, weights, target clusters

A family's weight is its block-draw probability, which equals its share of emitted points (every block is ~1 M points regardless of family/length), i.e. its share of the training token budget and therefore of the loss. Measured point share over 200k series is within sampling noise of the configured weight.

Pool facts these weights are aimed at (pool/metadata_0726.json, 1746 series, 214 source clusters):

  • cluster share (what the duel LCB counts): energy 33.2 %, econ_fin 19.6 %, nature 17.3 %, healthcare 10.7 %, transport 7.9 %, web_cloudops 7.5 %, sales 3.7 %
  • cluster share by cadence: 15min/sp 96 → 29 %, D/sp 7 → 28.5 %, min/sp 60 → 17.8 %, H/sp 24 → 13 %, 5min/sp 288 → 5 %, 30min/sp 48 → 3.3 %
  • window share (what the heat ranks): transport 68.3 %, of which GBFS bike-share is 57 % of all windows from just 9 clusters

So the mixture has to serve two different objectives at once: heat = minute-cadence bounded counts, duel = breadth over energy / econ_fin / nature.

family weight target cluster group (live pool) what it emits key params
dock_counts 0.16 transport — 17 GBFS *_station_status clusters (57 % of heat windows), tfl_arrivals; sp 60, minute cadence Bounded integer counts. Smooth latent (diurnal + 2nd harmonic + hourly rhythm + slow drift + sparse rebalancing steps) rounded to integers and clipped to [0, cap]. Gives exactly the real shape: long exact plateaus, ±1 transitions (measured 90 % of all non-zero steps are exactly ±1), 12–15 % exact zeros, ac(1) 0.99, dead-station runs pinned at 0 or cap. No affine rescale — integers stay integers cap_lo/hi 5–64, day_periods [1440,720,480,2880,240], hour_period 60, rebalance_rate 0.0016, dead_prob 0.04
kernel_mix 0.16 general prior / web_cloudops / anything unmodelled 3 random components from {grouped-AR(1) with r∈[0.30,0.995] over gaussian, uniform or heavy-tailed innovations; interpolated smooth GP; harmonic pair} + per-row trend and level shift. The stationary-kernel-mixture backbone n_components 3, ar_r_lo/hi, slow_cells, periods [7…336], innov_weights
energy_price 0.12 energy price feeds (~30 of 71 energy clusters): *_day_ahead_price, nordpool, caiso_lmp_5min, octopus_agile, eia_gas_spot; sp 96/48/24/288 Positive base level + daily + half-daily + weekly harmonics, heavy-tailed AR noise, one-sided scarcity spikes with exponential decay, per-row negative-price regime, per-row regulated cap/floor. Measured kurtosis 60, 23 % negative prints periods+period_weights (96 weighted 0.42), spike_rate 0.0025, spike_mag 2–25, neg_prob 0.35, cap_prob 0.30
daily_business 0.12 D/sp 7 clusters — 61 of 214: rki_covid_hospitalizations, npm_downloads, wikimedia_pageviews, crw_sst, apnic_ipv6, daily econ_fin Weekly harmonics plus a hard non-sinusoidal weekend notch, trend or logistic adoption S-curve, sparse level shifts, gaussian bump (outbreak/campaign), holiday dips, multiplicative log-normal noise, optional integer counts. Measured ac(lag 7) = 0.90 period 7, annual_period 365, logistic_prob 0.20, bump_prob 0.35, holiday_rate 0.008, count_prob 0.45
power_load 0.10 energy non-price (~35 clusters): *_public_power, *_co2_intensity, eia_hourly_demand, energinet_power_system, cross_border_flows Smooth positive load/generation: solar bell clipped at zero overnight (max(0,sin)^e) or double-peak demand, weekly modulation, slow weather drift, small noise, per-row curtailment outage to exact zero. ac(1) 0.98, ac(sp) 0.76 solar_prob 0.40, solar_exp 1.0–2.2, weather_cells 12, outage_prob 0.10
geo_smooth 0.09 nature (37 clusters): metar_us_airports (sp 288), datalakes_eawag (144), noaa_tides_coops (240), newcastle_urban_obs (48), usgs_streamflow (96) Strong diurnal + slow seasonal drift + tiny AR noise; a tide mode sums 4 incommensurate harmonics (M2/S2/K1/O1 ratios) so the envelope beats; per-row frozen-sensor runs. ac(1) 0.996 — the smoothest family periods+weights, tide_prob 0.30, beat_ratios, ar_r 0.90–0.999, stuck_prob 0.06
fin_walk 0.08 econ_fin (42 clusters): coinbase_spot_1m/15m, FX, yields, spreads Stochastic-volatility random walk: interpolated log-vol × heavy-tailed innovations, cumsum or mean-reverting, geometric (positive price) or additive (yield/spread), optional tick-grid quantisation → microstructure plateaus. ac(1) 0.998 (near unit root) vol_cells 32, base_vol 8e-4–2e-2, geometric_prob 0.55, tick_prob 0.35, meanrev_prob 0.25
hydro_burst 0.06 nature bursty: ea_uk_rainfall_15min, usgs_streamflow_iv; doubles as intermittent-demand shape Rain mode = zero-inflated clustered bursts (mostly exact zeros, exponential magnitudes); flow mode = sparse impulses through a slow recession filter (r∈[0.90,0.9985]) → fast rise / slow decay asymmetry, plus baseflow. Measured kurtosis 72 rain_prob 0.45, recession_lo/hi, event_rate 8e-4–2e-2, mag_scale 1–12
degenerate 0.06 the log-floor cliff, every domain 6 modes: flat (continuous level, one micro-blip), flat_step, intermittent (exact zeros + rare spikes), collapse (live → frozen tail), frozen_head (frozen → live), outlier (flat + one extreme). Measured longest flat run 74 % of L, kurtosis 3950, ptp/sd 64 mode_weights (6 keys), cut_frac 0.15–0.85, spike_rate 0.002–0.08
step_plateau 0.05 econ_fin policy rates / tariffs, energy set-points, thermostats Sparse jumps → cumsum → grid quantisation = long exactly-constant plateaus with rare jumps; or a duty-cycle square wave. Per-row slow ramp and per-row observation noise. 3.7 % of rows are exactly constant end-to-end jump_rate 3e-4–6e-3, grid 0.05–1.0, square_prob 0.25, duty 0.15–0.85

Coverage check against the eight shapes the brief asked for: bounded integer counts → dock_counts; degenerate/frozen/intermittent → degenerate (+ the pad family, + power_load outages, + geo_smooth stuck sensors); daily-seasonal business with trend and level shifts → daily_business; 15min/5min electricity with heavy tails, negatives and spikes → energy_price; smooth geophysical → geo_smooth; step functions with long plateaus → step_plateau; GP/kernel mixture and OU → kernel_mix; plus power_load, hydro_burst and fin_walk for the rest of the energy / nature / econ_fin cluster mass.

The pad family (eval-geometry match)

cascade's evaluator left-pads every history with h[0] out to the full 4096 before the forward pass (toto2_trainer.py:596-613); training otherwise never sees that shape, and causal_standardize pins scale = 1e-5 on a flat context — a log-space cliff.

pad in config.json is applied orthogonally to every family after the affine step: a fraction of rows get their first R samples overwritten with s[R], i.e. a genuine constant head equal to the first live value.

"pad": { "fraction": 0.12, "run_frac_lo": 0.05, "run_frac_hi": 0.80, "run_frac_beta": 1.6 }

run_frac_beta > 1 skews R/L short, < 1 skews it long; fraction is the sweep knob. Measured: 16.3 % of corpus series carry a flat head ≥ 5 % of L (pad + naturally-frozen families), and 7.9 % of all standardized steps sit on the eps scale floor — the model trains on the cliff instead of meeting it first at eval. Verified end to end: an eval-style pad (512 real samples inflated to 4096) goes through causal_standardize finite, max |z| 4.8.

Safety rails

One bad series aborts the whole round (sandbox_stream_failed), so _finalize runs on every block before any row is yielded:

  1. np.nan_to_num(..., copy=False) — NaN → 0, ±inf → ±max_abs_value.
  2. np.clip(±max_abs_value) — default 1e9, far under CAST_SAFE_MAX_FLOAT32 (3.4e38); measured clip rate 0.000 % on every family, so the rail never distorts the prior.
  3. finite_check — one np.isfinite pass, any survivor zeroed.
  4. Lengths are drawn from a fixed 32-aligned table inside [64, 4096] and validated in __init__ (raises if a config edit breaks the band).
  5. Flat series always carry a continuous level, so two of them can never be byte-identical — the corpus dup cap is 5 % and measured duplicates are 0.007 %.
  6. A malformed config.json is swallowed and the built-in DEFAULTS are used; an unknown family name in config is ignored rather than fatal.

Determinism

  • np.random.default_rng(seed) only, one generator, drawn in a fixed order. np.random.seed appears nowhere — production's generation_seed is a full 64-bit blake2b value (e.g. 9993167447787770989) and np.random.seed rejects anything ≥ 2³², which would pass cascade verify (seed 0) and die in the round.
  • Verified: cascade verify prints [deterministic]; two Generators at 9993167447787770989 produce identical arrays, and seed±1 produces different ones.
  • Grouped lfilter iterates group ids 0..n-1 in fixed order; no reduction-order-dependent parallelism, no threads, no wall-clock, no os.urandom.
  • generate(n) depends only on (seed, n) and its first k outputs are independent of n — with the interleave pool, blocks are still built in a fixed rng-driven order.

Sweeping

Everything in the table above is in config.json; nothing needs a code edit:

  • mixture: families.<name>.weight (= point/token share), length_weights
  • eval geometry: pad.fraction, pad.run_frac_lo/hi/beta
  • magnitudes: affine.log10_scale_lo/hi, offset_sigma, sign_flip_prob
  • throughput/quality trade: points_per_block, interleave_slots, ar_groups
  • every per-family parameter listed in the table

Note the ratchet: config-only forks are kept today (dedup_config_only_enforce=false) but that is the named next change, so the code is written to make real edits cheap too — a new family is one _fam_*(rng, B, L, p) function returning a (B, L) float32 array plus one entry in _FAMILY_FN and one block in DEFAULTS.

Dependencies

generator.py imports only numpy and scipy.signal.lfilter (plus stdlib json/math/pathlib), both allowlisted in chain.toml [dependencies] and both baked into deploy/Dockerfile (numpy 2.4.6, scipy 1.17.1) — remember requirements.txt is never installed server-side. No numba is needed: the mixture already runs at 3.8× the wall on pure numpy + scipy. Nothing on the static-guard blocklist is imported, and no weight file of any kind ships.

Files

4 items
  • generator.py

    acaee7f06288

    41.2 KB

  • README.md

    1ac7c53b6de2

    15.2 KB

  • config.json

    3b8139717fb3

    6.9 KB

  • requirements.txt

    5ca09b1f8850

    778 B