Package Arrow conversion helpers (row-based path + ANDData bridge); bump 0.51.1#77
Package Arrow conversion helpers (row-based path + ANDData bridge); bump 0.51.1#77atalyaalon wants to merge 5 commits into
Conversation
Services that install s2and (without the scripts/ tree) need to convert an in-memory ANDData into the Arrow IPC artifacts that predict_from_arrow_paths consumes. Move write_feature_block_arrow_from_anddata + feature_block_from_anddata into the packaged s2and.arrow_service_io; scripts/arrow_conversion_helpers.py re-exports for backwards compatibility.
|
Wait, can't we call rust directly without ever going through ANDData? |
|
I think this packaging move makes sense as a compatibility bridge for callers that already have an If ADS can stay on the Arrow/Rust path, it should produce the feature-block Arrow artifacts directly from its service-native rows and call pred_clusters, pred_distance_matrices = clusterer.predict_from_arrow_paths(
block_dict,
{
signatures: .../signatures.arrow,
papers: .../papers.arrow,
paper_authors: .../paper_authors.arrow,
signatures_batch_index: .../signatures.signatures_batch_index.bin,
papers_batch_index: .../papers.papers_batch_index.bin,
paper_authors_batch_index: .../paper_authors.paper_authors_batch_index.bin,
# Required when the loaded model uses embeddings:
specter: .../specter2.arrow,
specter_batch_index: .../specter2.specter_batch_index.bin,
# Required when the loaded model uses name-count features:
name_counts_index: .../name_counts_index,
# Optional for seeded/incremental requests:
# cluster_seeds: .../cluster_seeds.arrow,
# cluster_seed_disallows: .../cluster_seed_disallows.arrow,
},
load_name_counts=True,
name_tuples=filtered,
)So I would frame |
write_feature_block_arrow_from_rows builds the FeatureBlock Arrow tables directly from service-native rows (no ANDData) -- the preferred ADS production path per review. The from_anddata helper stays as a compatibility bridge. Bump 0.51.1.
Covers the previously-untested preferred path: test_feature_block_from_rows_matches_anddata (rows extracted from the ANDData-built FeatureBlock rebuild an identical one via feature_block_from_rows) and test_write_feature_block_arrow_from_rows_round_trips (rows -> Arrow tables + batch indexes, read back and assert schema/values incl. the has_abstract sentinel).
Funnel the row-based service path and FeatureBlock.to_arrow_tables through one schema builder (build_feature_block_arrow_tables) and one validator (validate_feature_block_columns), so write_feature_block_arrow_from_rows builds Arrow columns directly from request rows instead of materializing per-row dataclasses (~4.5x faster on l_li). The shared validator now enforces non-empty signature/paper ids on both paths, so the columnar path rejects the same malformed inputs the dataclass path does.
9fc94a9 to
e808c7d
Compare
Why
Services like the author-disambiguation service (ADS) must produce the Arrow IPC artifacts that
Clusterer.predict_from_arrow_pathsconsumes. The conversion helpers lived inscripts/arrow_conversion_helpers.py, butscripts/is not part of the installeds2andpackage (packages.find include = ["s2and*"]), so a pip-installed service cannot import them.What
s2and/arrow_service_io.py.write_feature_block_arrow_from_rows— build the FeatureBlock Arrow tables directly from service-native row dicts, noANDData. This is the preferred production path (per review): the caller groups pairwise cluster-seeds into require (signature_id -> cluster_id) + disallow pairs and passes rows in.write_feature_block_arrow_from_anddata(+feature_block_from_anddata) — compatibility bridge for callers that already hold anANDData.scripts/arrow_conversion_helpers.pyre-exports the public helpers.0.51.0 -> 0.51.1(synced across VERSION / pyproject / Cargo via the git hooks).runtime.min_supported_rust_extension_version_string()) instead of hardcoding it, so a version bump can't leave a stale fixture tripping the lockstep version check.Verification
tests/test_feature_block.py: 78 passed (row path +from_anddatapath).predictis exercised in CI).Stacked on
codex/anddata-featureblock-plan. Draft — paired with the ADS arrow-ingest integration (direct-from-rows).Columnar row→Arrow build (last commit)
The earlier
write_feature_block_arrow_from_rowsbuilt oneFeatureBlockSignature/Paper/PaperAuthordataclass per row, then calledFeatureBlock.to_arrow_tables(). On large blocks that per-row object materialization dominates the conversion (profile onl_li, 251k papers / 7.1M paper_authors: ~54s of dataclass construction).The last commit makes the row path columnar: it builds the Arrow columns directly from the request rows — no per-row dataclass objects — for a ~4.5× speedup on
l_li(~54s → ~11s), with peak RSS held flat (validator consumes a one-shot generator rather than materializing the position keys).To avoid a second, drifting copy of the Arrow schema, both entry points now funnel through one shared schema builder and one shared validator:
build_feature_block_arrow_tables(...)— single source of truth for the Arrow schema. BothFeatureBlock.to_arrow_tables(columns extracted from row objects) and the columnar service path feed columns through it, so thepa.array(..., type=...)definitions live in exactly one place.validate_feature_block_columns(...)— single source of truth for the structural FeatureBlock invariants (uniquesignature_id/paper_id, no duplicate(paper_id, position),query_signature_idssubset, cluster-seed require checks, non-empty ids). BothFeatureBlock.__post_init__and the columnar path call it, so the columnar path rejects exactly the same malformed inputs the dataclass path always did (including empty-string ids, which the columnar path would otherwise have let through).