forgo.cloud
Sign in
Repo workspace

forkjoin-ai/gnosis

Resonance Knot — Phase 3 Module Contracts

distributed-inference/RKNOT_PHASE3_CONTRACTS.md
forkjoin-ai/gnosis

Resonance Knot — Phase 3 Module Contracts

Six modules, dispatched in parallel. Each subagent writes exactly one file. Phase 1 + Phase 2 modules are SSOT — read them before writing.

Phase 3 goal

Take Llama-70B's actual Q4_K_M weights from R2 → dequantize → spectral encode against real weights → upload back. Plus the first stub of the Paris probe (Phase 4 will wire real inference; Phase 3 ships the loader + smoke).

Module 1 — src/rknot/dense_reader.rs (Phase 3 update)

Owner: subagent-1.

Two changes to the existing file:

A. Body-origin reconciliation (task #14)

Production .knot files written by the existing pipeline use 4-byte aligned body origin + 1 codec marker byte:

let aligned = (10 + meta_len + 3) & !3;
let body_origin = aligned + 1;  // +1 for codec marker

Replace the current body_origin = 10 + meta_len with the production formula. Read loader.rs::HttpBackend::open for the canonical implementation reference. Add a unit test that exercises both an unaligned meta_len (e.g. 17) and an aligned one (e.g. 20) — both must read the same body bytes that the production loader would.

B. K-quant dequantization

Port the Q4_0, Q8_0, Q4_K_M decoder logic from loader.rs (or its implementation in model.rs) into a new dequantize_kquant_block function. Wire load_tensor to dispatch on tensor_info.tensor_type:

  • "f32" → existing fp32 path
  • "f16" → existing fp16 path
  • "q4_0" → 32-element blocks: 1 fp16 scale + 16 bytes (32 × 4-bit)
  • "q8_0" → 32-element blocks: 1 fp16 scale + 32 bytes (32 × 8-bit)
  • "q4_k" / "q4_k_m" → 256-element blocks: super-block scale + 12 scales/mins + 128 bytes (256 × 4-bit). This is the Llama-70B format.

Be honest: if Q4_K_M is too painful, implement Q4_0 and Q8_0 fully and leave Q4_K_M as UnsupportedQuantType with a clear message ("Q4_K_M decoder lands in Phase 3.5"). Llama-70B Q4_K_M is the goal, but if pulling the decoder cleanly requires more than half a day of work, partial coverage is honest.

Tests:

  • body_origin_matches_production_loader_at_unaligned_meta_len
  • body_origin_matches_production_loader_at_aligned_meta_len
  • dequantize_q4_0_round_trip_within_scale_error — synthesize a Q4_0 block with known scale and codes, dequantize, verify floats are within scale × 0.5 of expected.
  • dequantize_q8_0_round_trip_within_scale_error
  • (optional) dequantize_q4_k_known_block — if Q4_K_M is implemented

Module 2 — src/rknot/encoder.rs (Phase 3 update)

Owner: subagent-2.

Add an amplitude-graded quantizer that uses the manifest's phase_alignment vector to allocate bits per row.

/// Encode a tensor with per-row amplitude-graded bit widths.
/// Each standing row's bits = base_bits + (extra_bits × phase_alignment[row]).round()
/// Clamped to [1, 16]. Mirrors Lean `recommended_quantization` from
/// AttentionQKVDecomposition.
pub fn encode_tensor_graded(
    tensor: &DenseTensor,
    manifest: &SpectralManifest,
    base_bits: u8,
    extra_bits: u8,  // 0..=8; max additional bits for highest-aligned rows
) -> QuantBlock;

For the FFN equivalent: encode_ffn_tensor_graded.

The output QuantBlock carries a single bits_per_entry field — for graded mode, store the MAX bits used (so the decoder can size the unpack buffer correctly). Per-row actual bit widths are encoded into the payload as a small header table at the start of payload:

  • [u8; k]: per-row bit width
  • followed by per-row variable-bit-width packed codes

Decoder side update (in decoder.rs): decode_block_graded reads the per-row table and unpacks accordingly. Add a flag in the block that indicates "uniform" vs "graded" mode — easiest is to use bits 7 of bits_per_entry as a "graded" flag (bits 0..6 = max bit width). Or add a pub mode: BlockMode { Uniform, Graded } enum to QuantBlock — this changes the format struct, so think carefully.

Lower-friction option: keep the format unchanged, add a parallel GradedQuantBlock struct that wraps QuantBlock + per-row widths, serialize the per-row widths into the block payload prefix.

Tests:

  • graded_falls_back_to_uniform_when_extra_bits_zero
  • graded_uses_more_bits_for_high_alignment
  • graded_round_trip_preserves_high_alignment_better

Module 3 — src/bin/probe-rknot.rs

Owner: subagent-3.

New binary: load a .rknot, decode every layer, run a SMOKE forward pass that just verifies dimensions + non-NaN outputs. Phase 4 will add real inference; Phase 3 just proves the .rknot can be loaded and decoded end-to-end.

probe-rknot --rknot <path.rknot> [--probe "What is the capital of France?"]

Output:

  • per-layer decode time
  • per-layer max abs value (sanity: NaN check)
  • summary: total decoded bytes, total decode time, mean / max / non-zero fraction across all decoded blocks
  • Phase 4 placeholder: print "would generate text from probe" with the decoded weights as a stub

This is the smoke binary that lets us run encode → decode → check on Llama-70B end-to-end. Real inference is Phase 4.

Add [[bin]] name = "probe-rknot" to Cargo.toml.

Tests (unit only — can't run on a real .rknot in CI):

  • decode_summary_dimensions_match_layer_count
  • decode_detects_nan — synthetic block with NaN payload triggers warning

Module 4 — cloudbuild-llama-70b-end-to-end.yaml

Owner: subagent-4.

New cloudbuild that does the FULL pipeline in one yaml: HF download → dense .knot encode → R2 upload (existing pipeline) PLUS rknot repack

  • verify + R2 upload (Phase 2 pipeline) chained. Single submission, both .knot and .rknot land in R2.

Reference: cloudbuild-llama-70b-to-knot.yaml (dense steps) and cloudbuild-llama-70b-to-rknot.yaml (rknot steps). Combine.

Use waitFor to parallelize where possible:

  • build-encoder (cargo build) runs in parallel with fetch-from-hf
  • repack-rknot waits for both verify-knot and build-encoder
  • upload-knot and upload-rknot upload in parallel

Wall-clock estimate: ~3 hours total (vs ~5 hours sequential).

Verify YAML parses:

python3 -c "import yaml; yaml.safe_load(open('./cloudbuild-llama-70b-end-to-end.yaml'))"

Module 5 — src/rknot/calibration.rs (Phase 3 update)

Owner: subagent-5.

Add a real attention probe stub that hooks the existing inference kernel. Phase 3 ships the wiring and a synthetic "fake forward pass" that returns deterministic measurements. Phase 4 wires the real model forward pass.

/// Attention probe interface — anything that can produce per-layer
/// AttentionMeasurement vectors from a probe corpus. Phase 3 ships
/// FakeAttentionProbe; Phase 4 swaps in RealKernelProbe.
pub trait AttentionProbe {
    fn probe(&mut self, corpus: &[u32]) -> Result<Vec<LayerMeasurement>, String>;
}

/// Phase 3 stub: returns deterministic measurements derived from the
/// corpus token IDs. Not a real attention probe — the surface exists
/// so encode-rknot can have a `--calibration-probe <fake|real>` flag
/// in Phase 4 without rewiring.
pub struct FakeAttentionProbe {
    pub d: u32,
    pub num_layers: u32,
}

impl AttentionProbe for FakeAttentionProbe { ... }

/// Phase 4 placeholder: returns NotImplementedError. Module 6's
/// integration will swap this in once the model.rs forward pass tap
/// lands.
pub struct RealKernelProbe<'a> {
    pub model_path: &'a std::path::Path,
}

impl AttentionProbe for RealKernelProbe<'_> {
    fn probe(&mut self, _corpus: &[u32]) -> Result<Vec<LayerMeasurement>, String> {
        Err("RealKernelProbe lands in Phase 4 — wire model.rs forward-pass tap first".to_string())
    }
}

Tests:

  • fake_probe_produces_deterministic_measurements
  • fake_probe_layer_count_matches_config
  • real_probe_returns_phase_4_error

Module 6 — src/rknot/bitwise_fp48.rs (Phase 3 update)

Owner: subagent-6.

Replace the 8-byte codebook RLE-style packer with real fp48 bi-sided splitting from open-source/bitwise/. The existing crate has the "lifted (low-48) + contracted (high-16)" pattern documented in feedback_bw_dimensional_decomposition.

Read open-source/bitwise/src/ (esp bisided-bit.ts — there may be a Rust port; if not, port the algorithm). The pattern:

For 8-byte words:
  lifted   = word & 0x0000_ffff_ffff_ffff  (low 48 bits = payload)
  contracted = (word >> 48) & 0xffff       (high 16 bits = parity/index)

The compression bonus comes from observing that on standing residuals, contracted is near-constant (the standing dimensions cluster in parity-space). So the encoder:

  1. Splits each 8-byte word into (lifted, contracted)
  2. Run-length encodes the contracted stream (huge bonus when constant)
  3. Emits lifted as raw fp48 bytes (or further codebook-compressed)
pub struct Fp48BiSidedPacked {
    pub lifted_payload: Vec<u8>,    // packed 6-byte values
    pub contracted_runs: Vec<(u16, u32)>, // (value, run_length)
    pub original_byte_len: u32,
}

pub fn fp48_bisided_pack(payload: &[u8]) -> Fp48BiSidedPacked;
pub fn fp48_bisided_unpack(packed: &Fp48BiSidedPacked) -> Vec<u8>;
pub fn fp48_bisided_bonus_ratio(payload: &[u8]) -> f32;

Keep the existing fp48_pack / fp48_unpack (codebook variant) — add the bi-sided version as a sibling. --apply-fp48 in encode-rknot can later choose between them via a sub-flag.

Tests:

  • bisided_pack_unpack_round_trip
  • bisided_bonus_constant_contracted_high — payload where every 8-byte word has the same high-16 bits should achieve ~3-5× bonus
  • bisided_bonus_random_under_one_ok
  • bisided_bonus_on_standing_residual_synthetic — synthetic standing residual (peaky value distribution, low-entropy contracted axis) achieves 2-4× bonus

Anchors

After all 6 modules land:

  • cargo build --release produces 3 binaries (encode-rknot, verify-rknot, probe-rknot)
  • cargo test --lib rknot:: passes everything
  • cloudbuild-llama-70b-end-to-end.yaml parses
  • Smoke: encode-rknot --synthetic ... still works
  • Smoke: probe-rknot --rknot /tmp/rknot-phase2-smoke.rknot runs and reports per-layer decode

Common pitfalls

  • Don't break the existing format byte layout — QuantBlock's field order is canon. New format extensions go in NEW structs.
  • K-quant block sizes are 32 or 256 elements — make sure the dispatcher uses the right one. Q4_K's block size is 256, not 32.
  • Body-origin bug WILL bite if not fixed before running on real R2 knots — flag prominently in the encode-rknot error path.
  • fp48 bi-sided: (u16, u32) for run-length encoding caps run length at 4G — fine for any layer; just be honest if you cap it lower.
  • No new crate dependencies — only what's already in Cargo.toml.