Skip to content

Latest commit

 

History

25 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Plumbline

The dimensioning and budget model for Trellis-180M.

CI License Python Runtime dependencies Ruff


A plumbline is the weighted string a builder hangs before laying anything: it establishes what true is, so every later measurement has something to be taken against. This package does that job for Trellis-180M, a 180M-parameter code-reasoning language model. It declares the typed configuration tree that names every dimension the model uses, and it computes — closed-form, from those fields alone — the parameter, FLOP, memory, bandwidth, and training-cost accounting the architecture was published with.

It is the project's root unit. It consumes nothing, it gates dimensioning before any layer, kernel, or data code exists, and every other unit imports its dimensions from here rather than re-declaring them. There is no tensor library anywhere in it and no model is ever instantiated: the whole thing is integers, floats, and arithmetic. A configuration that violates a named rule never becomes a config object, and a report that disagreed with the 180.72M serving total would fail the run that produced it.


Install

pip install -e .          # or: pip install -e ".[test]"

Zero runtime dependencies, Python 3.9 or newer.

Use

budget.py report --preset default        # both report forms, from one computation
budget.py check  --preset default        # recompute and compare against the published table
budget.py diff   default proxy_50m       # two configurations, side by side
budget.py list-presets

python -m trellis.config.budget is equivalent. As a library:

from trellis.config import get_preset, build_report, render_table

config = get_preset("default")           # validated on the way out, or it raises
report = build_report(config)
print(render_table(report))

report.value("parameters.serving_total_m")   # 180.72
report.failures                              # () -- nothing unexplained

What it reproduces

Every figure below is derived from configuration fields and checked against the published value at that value's own precision, under round-half-up. budget.py check exits zero only when all of them still hold.

Reproduced from config
Parameter components attention layer 6.09M · GDN layer 7.70M · prelude/coda 13.79M · tied core 44.58M · per-loop LoRA 2.06M · embeddings 37.75M · bank 68.68M
Totals 180.72M serving · 186.72M training · embeddings 20.9% of budget · bank 38%
Decode FLOPs (MFLOPs/token) 452 / 628 / 1232 average and 584 / 760 / 1364 worst case, at 4k / 32k / 131k
KV + state (MB) 12.8 / 42.1 / 142.8 bf16 at 4k / 32k / 131k, falling to 17.0 / 42.1 at 32k / 131k with 4-bit global KV, against 352 / 2819 / 11274 for a dense MHA twin
Footprint weights 361MB · bank 137MB, of which the 134MB value store is host-residable · ~420MB on device at 32k
Decode bandwidth 245.2 core re-read + 75.5 head + 55.2 untied + cache ≈ 396MB/token, and ceilings of 4050 / 2278 / 152 tok/s
Prefill 14.6 TFLOPs and 0.12s for 32k at K=2 on one A100 at 40% MFU
Training 7.3e19 FLOPs · 7.3h ideal on 8×H100 at 35% MFU · 1.9GB/GPU activations

The dense-160M comparator columns are computed by running the same calculators over a baseline configuration, never transcribed. If the FLOP model is wrong, both sides of the comparison move together.

Published figures are engineering bounds, not measured claims, and every emitted report says so.

What it does not reproduce

Three published figures cannot be derived by the model that derives all the others exactly. Each is reported as a named reconciliation with the computed value treated as authoritative, rather than absorbed into a widened tolerance:

  • dense-160M full attention at 131k computes to 11,594 MFLOPs against a published 12,000+. The same arithmetic reproduces that baseline's 4k (672) and 32k (3,138) cells exactly, so the coefficient is pinned.
  • dense-160M SWA 5:1 at 131k computes to 2,492 MFLOPs against ~2,700. The 4,096-token window is fixed by the exactly-reproduced 32k cell, so no consistent window reaches the published figure.
  • the 4-bit KV column at 4k is published as 9.8MB, which requires quantisation below the stated 8,192-token threshold and contradicts the property that the quantised and full-precision columns agree below it. The threshold rule wins.

A further nine reconciliations annotate figures that do reproduce, where two accountings of the same quantity circulate and the report says which one a number belongs to — the three-block parameter split that sums 1.97M below the component total, the mixture's 56B seen tokens against a headline 60B, and the bank's per-read-point FLOP cost against its whole-bank presentation.

Building trellis.bank against these figures surfaced two more, both recorded in docs/modules/memory-bank.md rather than smoothed over. The published 137MB is the whole booked bank — all 68,681,728 parameters at bf16 — while the value store alone is 134.2MB; the difference is the two read points' projections, which belong in the compute path, so what is actually host-residable is the smaller figure. And the value dimension is published as 512 "in all cases" while this repository's own sweep_25m preset sets 256; the module reads it from config and hardcodes nothing.

Layout

trellis/config/
  schema.py        eleven frozen config groups; derived quantities are properties
  validate.py      49 named cross-field rules, each with a field path
  presets.py       13 configurations: default, proxy, sweep, twins, C-9 growth points
  loader.py        strict document loading; unknown fields and major versions rejected
  errors.py        the error taxonomy
  budget/
    params.py      parameter accounting and the block-view reconciliation
    flops.py       decode and prefill FLOPs
    memory.py      KV, recurrent state, footprint, activations
    bandwidth.py   per-token bytes and device ceilings
    training.py    6NT compute, mixture and stage accounting
    baselines.py   dense comparators, via the same calculators
    goldens.py     the published table -- read by the report, never by a calculator
    report.py      one computation, two deterministic renderings
    __main__.py    the report / check / diff entry point
trellis/structure/ the code-structure analysis core: the system's only tree-sitter
                   integration, and the declarative lexical-boundary tables the
                   tokenizer scans at encode time with no parser present
trellis/bank/     the product-key parametric memory bank: 131,072 value slots on a
                  512x256 composite key grid, exact top-32 selection, index-before-gather
  select.py        the exact top-k procedure and its brute-force oracle
  store.py         the shared value store, both codebooks, muP and warm-start init
  read_point.py    per-read-point W_q, query BatchNorm, W_up
  stats.py         windowed utilisation statistics and both regulariser terms
  bank.py          MemoryBank: select / gather / forward, and the reports
trellis/model/    the backbone: ten unique layers at effective depth 4 + 6K, a
                  weight-tied six-layer core entered K in [1,4] times, and the
                  parameter audit that reproduces 180.72M / 186.72M exactly
  router.py        the expert-choice capacity router; exact counts, nested, deterministic
  lora.py          per-loop adapters and norm gains, switched by tensor selection
  loop.py          the loop engine: the recurrence, the routing, the segmentation
  heads.py         the tied embedding/output head, the exit heads, the readout path
  aux.py           the four training-only heads, and their detachment
  state.py         the model-level state bundle: 22 GDN instances, 6.5 MB
  backbone.py      TrellisModel: forward / step, the graph, the bank read points
  audit.py         constructed tensors against the budget report, component by component
trellis/tokenizer/
  vocab.py         the 49,152-id partition and its frozen reserved blocks
  specials.py      26 reserved protocol tokens, seven frames, both FIM layouts
  pretokenize.py   newline and indentation anchors; the parser-free segmenter
  train.py         the boundary-constrained BPE trainer and keyword-coverage pass
  encode.py        the four-stage encoder and the Tokenizer facade
  decode.py        batch decode and the incremental streaming decoder
  artifact.py      the versioned artifact: manifest, metadata table, mismatch detection
  settings.py      the one place the `tokenizer` config group is read and checked
  variants.py      the three A18 arms
  bench.py         bytes/token against the generic reference, throughput, latency
trellis/verify/    the deterministic verification pipeline: the sandbox, the
                  toolchain adapters, the cascade, the failure classifier, the budget
                  controller, and the verdict record
trellis/repoindex/ the M3 external repository index and its context-assembly layer:
                  the only memory tier that grows with repository size, on disk and
                  mmap-backed, and the system's single content-hash authority
  codec.py         the fixed-width record layouts and the closed-enum codes
  container.py     the mmap region container and the binary searches over it
  strings.py       the interned string table whose ids are sort ranks
  extsort.py       bounded-memory sorting with a deterministic merge
  facts.py         the one place Prompt 2's facts enter the index
  resolve.py       specifier-to-file and callee-to-symbol resolution
  compact.py       the four compaction passes; the only writer of the stores
  stores.py        the read side: one handle over the seven containers
  build.py         build and update, and the skip policy
  query.py         defs / uses / imports / validate_pointer
  scorer.py        BM25 plus graph proximity, and the declared embedding slot
  assemble.py      budgeted, dependency-ordered context assembly
  frames.py        repo-map, retrieval and pointer frame content
  header.py        the recorded header and the rebuild-required signal
  metrics.py       the A14 hooks: build time, query latency, index size
trellis/data/curation/
                  the offline data plane: licence filtering, exact and MinHash
                  near-dedup at corpus scale, quality and complexity filtering,
                  dual-path benchmark decontamination, and the nine versioned
                  slice manifests the training mixture is built from
  normalize.py     the one normalisation applied before hashing and matching
  licensing.py     SPDX resolution with a total evidence precedence; unresolved excludes
  spill.py         bounded-memory sorting with a fan-in-capped deterministic merge
  dedup/minhash.py shingling, seeded permutations, signatures and banding
  dedup/lsh.py     the on-disk signature store, verified candidates, clustering
  quality.py       ten filters, each recording the measurement that decided
  complexity.py    structural signals from Prompt 2's facts; weighting, never a gate
  decontaminate/   string keys, AST fingerprints, and the sort-merge join over both
  slices.py        the nine slots, exclusive assignment and token accounting
  manifest.py      the versioned contract, and the generations it refuses to mix
  report.py        the governance report: attrition, attribution, and measurements
  pipeline.py      the ordered, streaming, resumable driver
docs/              module, API contract, and architecture references
tools/             dump_tree.py, train_tokenizer.py, bench_repoindex.py
tests/             2,215 tests, including byte-stable golden reports, id sequences,
                   fixed-seed model goldens in both train and step mode, and
                   planted duplicate and benchmark-leak corpora for the data plane

Scope

trellis.config computes; it does not build. Layer and kernel code, loss computation, stage orchestration, runtime caches and quantisation, repository index measurement, and the execution of ablation arms all belong to units that consume this one's dimensions. Its I/O is limited to reading configuration documents and writing reports.

trellis.structure accepts bytes and returns values: it holds no persistence and walks no repository. It is the system's only tree-sitter integration, and every consumer takes structural facts from it rather than parsing source itself.

trellis.bank is the storage tier and is self-contained: it imports trellis.config at build time and no other unit in the package, so it instantiates without the model. It is frozen at inference by design -- there is no write path other than gradients, and the test suite asserts the absence of one by name. Read-point placement, prefetch orchestration and loss weighting all belong to units that consume its interface.

trellis.repoindex is the only tier that grows with the repository, and it grows on disk. It parses nothing -- exactly one parse per file happens in trellis.structure and its facts are consumed here -- hashes nothing, and chunks nothing; it holds the facts, the graph, the chunk index and the content-hash map, and it answers. It owns the single hash-validity surface in the system, validate_pointer, and it answers rather than repairs: a stale pointer comes back stale with the caller's own fields untouched. Session frames, pointer compaction, LRU and rehydration control flow belong to the harness that consumes it.

trellis.model is the graph and nothing below or above it. It composes trellis.layers through the Layer/state contract and trellis.bank through the Bank interface, and it owns no loss, no threshold, no decode-time cache layout and no grammar mask. The sharpest case is the exit heads: the model reports a halting logit for every token at every executed loop and never reads one back, because acting on it is a serving policy and a serving policy inside the graph is one nobody can change.

trellis.tokenizer owns the vocabulary and the token protocol, and nothing else may re-derive either. It reads and writes tokenizer artifacts and nothing more. No parser runs in its encode or decode path, so the whole unit works with no tree-sitter installed at all — CI runs its full suite in an environment with no parser present, because a constraint checked only by grep is one that erodes.

No unit here opens a socket or shells out, and only trellis.layers, trellis.bank and trellis.model import a tensor library or instantiate anything. CI asserts every one of those boundaries rather than trusting them -- including that the other three units' suites still pass in an environment with no torch installed at all.

Documentation

Document Contents
docs/modules/config-and-budget.md design of the schema tree and the calculators; how the entry point is invoked
docs/api/config-schema.md the normative contract: every field, type, unit, default, rule, and the report JSON schema
docs/modules/code-structure-core.md design of the single-parse architecture, the extraction pipelines, and the boundary scanner
docs/api/structural-facts.md the normative contract: every fact record, hash kind, and version constant
docs/modules/tokenizer.md design of the four-stage pipeline, the constrained trainer, the artifact, and the A18 arms
docs/api/token-protocol.md the normative contract: the id-space partition, every protocol token and frame, and the artifact manifest schema
docs/modules/memory-bank.md design of the product-key addressing scheme, the exactness argument, the collapse countermeasures, and the invariants
docs/api/bank-interface.md the normative contract: select / gather / forward, the statistic definitions, and the published figures
docs/modules/backbone.md design of the ten-layer graph, the loop engine, the capacity router, per-loop specialisation, the heads, and the parameter audit
docs/api/model-interfaces.md the normative contract: the training forward, the step-mode API, the router's masks, the bank read points' exposed indices, and the guarantee table
docs/modules/verifier-infrastructure.md design of the six verification layers, the sandbox, the cascade, the classifier, and the budget controller
docs/api/verdict-schema.md the normative contract: the verdict record, the failure taxonomy, the verifier tags, and the budget-controller API
docs/development/sandbox-and-toolchains.md how to provision and pin each language toolchain, verify the isolation locally, and run the security and fuzz suites
docs/modules/repo-index.md design of the five stores and the disk/mmap layout, incremental update and invalidation, retrieval scoring, assembly and frame formatting, and the invariants
docs/api/index-api.md the normative contract: defs / uses / imports, retrieval, statement-aligned window assembly, validate_pointer, the frame formats, and the on-disk header fields
docs/modules/corpus-curation.md design of the curation pipeline: stage order and the streaming/resumability model, the licence policy, the dedup algorithms and their parameters, the quality and complexity signals, dual-path decontamination, the nine-slice table, and the manifest invariants
docs/development/data-pipeline.md how to configure and run a curation job, resume one after an interruption, read the governance report, and what each downstream stage reads from the artifacts
docs/architecture/budget-and-dimensions.md the authoritative dimension vocabulary and the full budget tables

docs/api/* is normative. The implementation is expected to match it exactly, and for three of the contracts that expectation is a test: tests/test_tokenizer_docs.py extracts every id, range, enumerated tuple and exported name from the token protocol and compares it against the module, tests/test_bank_docs.py does the same for the Bank interface's exported names, validation field paths, statistic definitions, FLOP terms and byte figures, and tests/test_model_docs.py does the same for the Model interfaces' exports, output fields, forward signature and audit table -- and additionally checks that the three-part skipped-token policy is worded identically in every document that states it.

Development

python -m pytest tests/ -q
python -m ruff check .
python -m trellis.config.budget check --all-presets

The bank's own gate is exactness: tests/test_bank_units.py compares its top-32 selection against a scan of all 131,072 composites over 1,000 random queries, including planted ties. tests/test_bank_docs.py holds the module to its normative contract.

Golden reports under tests/goldens/ are compared byte for byte. Regenerating them is deliberate:

TRELLIS_UPDATE_GOLDENS=1 python -m pytest tests/test_regression.py

Read the resulting diff before committing it. A changed golden is either an intended dimension change or a regression, and the diff is how you tell.

License

Apache License 2.0.