Skip to content

feat(compression): add-encoding-partition Phase 2 — per-block dispatch - #109

Merged
jascal merged 1 commit into
mainfrom
impl/encoding-partition-phase-2
May 21, 2026
Merged

feat(compression): add-encoding-partition Phase 2 — per-block dispatch#109
jascal merged 1 commit into
mainfrom
impl/encoding-partition-phase-2

Conversation

@jascal

@jascal jascal commented May 21, 2026

Copy link
Copy Markdown
Owner

Summary

Wires the per-block compress + stitch logic that Phase 1 (#107) deferred. Compressor.apply now actually runs per-block dispatch when an encoding_partition is supplied, instead of refusing with NotImplementedError.

After this lands + polygram tags v0.14.0, sae-forge's add-block-structured-sae Phase 2 (per-block dispatch in auto_materialise.py / sweep.py / forge.py) can begin against the now-functional partition surface.

What ships (~280 new lines)

Five new private helpers in polygram/compression/compressor.py:

helper role
_partition_global_plan_into_blocks Split global plan's clusters per block. Cross-block clusters are dropped (semantically invalid — two encodings can't merge a cluster spanning blocks).
_build_local_plan_for_block Re-index cluster members/rep/zeroed to block-local indices for the strategy dispatch. Cluster ids stay GLOBAL.
_slice_state_to_block Column-slice W_dec/W_enc/b_enc to block.feature_ids. b_dec invariant — shared by reference.
_stitch_block_into_state Write per-block rewritten rows back into global state at block.feature_ids positions.
_build_block_report Build the BlockReport with per-feature cluster_assignments (local cluster ids, -1 for unclustered).
_apply_partitioned Top-level driver. Calls the helpers per block, returns (rewritten_state, merged_norms, block_reports).

Compressor.apply integration:

  • Coverage validation fires before any I/O so a bad partition fails fast.
  • Top-level plan rebuilt to drop cross-block clusters; downstream rank_ratio/post_A reflect the post-drop plan.
  • CompressionReport.blocks populated.
  • Defensive: when the dropped-cross-block plan is empty, the downstream rebuilt-Dictionary debugging artifact seeds from the lowest-fid features rather than crashing.

BlockReport.n_features_kept semantic aligned with top-level CompressionReport.n_features_kept: count of cluster representatives (= n_clusters), NOT total survivors.

Tests (9 new + 1 Phase 1 update)

test covers
single-block partition matches top-level counts sanity
2-block partition stitches correctly per-block + top-level counts agree
2-block partition zeroes correct rows in output end-to-end safetensors verification (with explicit n_fires for deterministic rep selection)
cross-block clusters are dropped pair (3,4) bridging blocks → both features survive as singletons
coverage validation fires on incomplete partition early failure at apply() time
coverage validation fires on overlapping partition duplicate id named
block_report cluster_assignments are local to block -1 sentinels for unclustered features
full CompressionReport with blocks round-trips v3 schema
no partition uses single-encoding path Phase 1 regression guard
test_compressor_apply_runs_partition_path_in_phase_2 Phase 1 → Phase 2 transition — the NotImplementedError block is gone

1070 → 1079 tests passing on full suite. Ruff clean.

Phase 2 v1 caveats (documented enhancements, not blockers)

  • Per-block rank_ratio / post_A / forge_mse — deferred to a separate enhancement. Each block's diagnostic floats serialise as None. Top-level still computes (across the post-drop plan).
  • merged_norms namespace — global cluster_ids preserved through local-plan construction; per-block sub_merged_norms is keyed on GLOBAL ids → clean union at top level. No namespace collisions.
  • merge strategy — works the same as zero (strategy called per-block on each sub-state, merge_mode honored). Tests above cover zero explicitly; merge support is implicit through dispatch_strategy.

Coordination

After polygram tags v0.14.0, sae-forge can:

  1. Bump polygram>=0.14.0.
  2. Begin add-block-structured-sae Phase 2 — the --encoding-partition CLI flag + AutoMaterialiseSpec.encoding_partition field + per-row partition_label in ParetoFrontierRow. The downstream change's spec is already filed and locked.

Test plan

  • 9 new Phase 2 tests pass locally.
  • Phase 1 transition test updated and passes.
  • Full polygram suite: 1070 → 1079 (no regressions).
  • Ruff clean.
  • CI verifies on Linux runners.

🤖 Generated with Claude Code

Wires the per-block compress + stitch logic deferred from Phase 1
(PR #107). Compressor.apply now actually runs per-block dispatch
when an encoding_partition is supplied, instead of refusing with
NotImplementedError.

What ships

  polygram/compression/compressor.py (~280 new lines)

    Five new private helpers for the per-block path:

      _partition_global_plan_into_blocks(global_plan, partition)
        Splits a globally-computed CompressionPlan into per-block
        cluster lists. Clusters whose members span more than one
        block are DROPPED (their features end up as singletons —
        cross-block merges are semantically invalid since the two
        blocks use different encodings). Returns (per_block_clusters,
        n_cross_block_dropped).

      _build_local_plan_for_block(block_clusters, block)
        Re-indexes a block's ClusterPlan members + representative
        + zeroed to LOCAL indices into the block's sliced W_dec
        (positions 0..len(block.feature_ids)-1). Cluster ids stay
        GLOBAL so the BlockReport's cluster_assignments and the
        top-level CompressionReport.plan reference the same id space.

      _slice_state_to_block(source_state, block)
        Returns a copy of source_state with W_dec/W_enc/b_enc
        column-sliced to block.feature_ids. b_dec is shared (invariant
        under feature-axis slicing; strategies don't touch it).

      _stitch_block_into_state(global_state, sub_rewritten, block)
        Writes per-block rewritten rows back into global_state at
        the positions named by block.feature_ids. Mutates
        global_state in place.

      _build_block_report(block, block_clusters, sub_rewritten_w_dec,
                          sub_source_w_dec, sub_merged_norms)
        Builds the BlockReport for one block with per-feature
        cluster_assignments (LOCAL cluster ids in
        [0, n_clusters_in_block); -1 for features not in any
        cluster). v1 leaves per-block rank_ratio/post_A/forge_mse
        as None — those diagnostic floats are a separate enhancement.

      _apply_partitioned(source_state, global_plan, partition, ...)
        The top-level driver. Calls the helpers above per block,
        stitches the rewritten state, builds block_reports.
        Returns (rewritten_state, merged_norms, block_reports,
        n_cross_block_dropped).

    Compressor.apply integration:
      - Replaces the Phase 1 NotImplementedError refusal with the
        actual per-block dispatch path.
      - Coverage validation runs immediately (before any I/O) so an
        under- or over-specified partition fails fast.
      - The top-level CompressionReport's plan is rebuilt to drop
        cross-block clusters; the apply()'s downstream rank_ratio /
        post_A / scale_compression_ratio diagnostics then reflect
        the cross-block-dropped clusters, not the original global ones.
      - CompressionReport.blocks is populated with the BlockReport
        tuple.
      - Defensive: when plan.feature_ids is empty (all clusters
        cross-block dropped), the downstream rebuild seeds from the
        lowest-fid features so the rebuilt Dictionary still surfaces
        as a debugging aid.

    BlockReport.n_features_kept semantic aligned with top-level
    CompressionReport.n_features_kept: count of cluster representatives
    (= n_clusters), not all surviving features in the block.
    Singletons stay singletons; only multi-feature clusters contribute
    to the kept count.

Tests (9 new cases, 1 Phase 1 case updated)

  tests/compression/test_encoding_partition_phase2.py (new)
    - test_single_block_partition_runs_strategy_correctly: 1-block
      partition covering all features matches the top-level
      CompressionReport's counts.
    - test_two_block_partition_stitches_correctly: 2 blocks with
      intra-block clusters; per-block + top-level counts agree.
    - test_two_block_partition_zeroes_correct_rows_in_output:
      verifies the stitched output W_dec has the right rows zeroed
      (uses explicit n_fires for deterministic rep selection).
    - test_cross_block_clusters_are_dropped: confirmed pair
      bridging two blocks is dropped; both features survive as
      singletons.
    - test_coverage_validation_fires_on_incomplete_partition:
      PartitionCoverageError fires at apply() time before I/O.
    - test_coverage_validation_fires_on_overlapping_partition:
      overlap raises with the duplicate id named.
    - test_block_report_cluster_assignments_local_to_block:
      cluster_assignments uses local indices 0..n_clusters_in_block-1
      with -1 sentinels for features not in any cluster.
    - test_full_compression_report_with_blocks_round_trips:
      v3 report with blocks round-trips through to_json / from_json.
    - test_no_partition_uses_single_encoding_path: regression
      guard — when encoding_partition=None the historical path
      runs and CompressionReport.blocks is None.

  tests/compression/test_add_encoding_partition.py:
    test_compressor_apply_refuses_encoding_partition (Phase 1)
    replaced with test_compressor_apply_runs_partition_path_in_phase_2
    — pins the Phase 1 → Phase 2 transition; the Phase 1
    NotImplementedError refusal is gone.

Tests: 1070 → 1079 (+9). Full suite green. Ruff clean.

Phase 2 v1 caveats (documented enhancements, not blockers)

  - Per-block rank_ratio / post_A / forge_mse: deferred to a
    separate enhancement. Each block's diagnostic floats currently
    serialise as None in the BlockReport. The top-level
    CompressionReport's rank_ratio / post_A still compute (across
    the dropped-cross-block plan).
  - merged_norms aggregation: the per-block sub_merged_norms maps
    use the GLOBAL cluster_id (preserved through the local-plan
    construction in _build_local_plan_for_block), so the top-level
    merged_norms dict is a clean union of per-block dicts. No
    namespace collisions.
  - merge strategy: works the same as zero — the strategy is
    called per-block on each sub-state, so merge_mode is honored.
    Tests above cover only zero strategy explicitly; merge support
    is implicit through the existing dispatch_strategy call.

Coordination

  After this lands, polygram tags v0.14.0 → sae-forge can begin
  add-block-structured-sae Phase 2 (per-block dispatch in
  auto_materialise.py, sweep.py, forge.py) against the now-functional
  partition surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jascal
jascal merged commit 322e9a9 into main May 21, 2026
2 checks passed
@jascal
jascal deleted the impl/encoding-partition-phase-2 branch May 21, 2026 15:05
jascal added a commit that referenced this pull request May 21, 2026
Tags the polygram release that ships add-encoding-partition Phase 2
(#109): the actual per-block dispatch + stitching logic that
Phase 1 (v0.13.0) deferred behind a NotImplementedError.

What's new in 0.14.0

  Compressor.apply now runs per-block compression when
  encoding_partition is set:
    - Per-block W_dec/W_enc/b_enc slicing
    - Cross-block clusters dropped (semantically invalid)
    - CompressionReport.blocks populated end-to-end
    - Coverage validation fires before any I/O

After this tag publishes, sae-forge can begin
add-block-structured-sae Phase 2 — the --encoding-partition CLI
flag, AutoMaterialiseSpec.encoding_partition field, and per-row
partition_label in ParetoFrontierRow — against a now-functional
polygram partition surface.

Phase 2 v1 caveats (already documented in PR #109's body and the
CHANGELOG entry): per-block rank_ratio/post_A/forge_mse deferred
to a separate enhancement; merge strategy works implicitly through
the existing dispatch_strategy call.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant