Skip to content

Hold MLOps artifacts in Snowflake stages and score registry versions with run_batch - #115

Open
Vedaant-J wants to merge 58 commits into
vedaant/mlops-score-sketchfrom
vedaant/mlops-sf-stage
Open

Vedaant-J wants to merge 58 commits into
vedaant/mlops-score-sketchfrom
vedaant/mlops-sf-stage

Conversation

@Vedaant-J

@Vedaant-J Vedaant-J commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Stack: #53 → #112 → #50 → #60 → #64; #114 on #112; #115 on #64. Merge bottom to top.

Summary

Teams whose data lives only in Snowflake have nowhere to keep Kumo artifacts and cannot score a registered Kumo model inside Snowflake. This PR adds both:

  • Stages as a file store. A @DB.SCHEMA.STAGE/... path is a file destination of SnowflakeConnector (Let any sink hold files: FileStoreSink protocol and a chunked filesystem #112's FileStoreSink, as Hold MLOps artifacts on Unity Catalog volumes through the Files API #114 does for volumes): bundles, snapshot and prediction exports, and the MLOps artifact root. Writes use PUT; reads use presigned ranged URLs, falling back to one GET per file. Files up to 4 GiB are plain objects; larger ones are parts only kumo reads.
  • Runnable registry versions. With kumo-engine installed, a version registered in the Snowflake Model Registry is a KumoModel that scores entity ids against a stage snapshot in Snowpark Container Services (ModelVersion.run_batch on a GPU pool). kumo-snowflake finds the builder through the kumo_snowflake.registry_models entry point. Registering a bundle again reuses its version when the serving environment matches.

In the container, the snapshot and the stage assets are read with the container's own Snowflake identity (_snowflake_serving.py).

Usage

sf = kumo.SnowflakeConnector(warehouse="WH", database="ML", schema_name="MODELS")
cfg = kumo.MLOps((sf, "@ML.MODELS.KUMO"), tracking=SnowflakeTracking(sf, experiment_name="CHURN"),
                 registry=kumo.ModelRegistryLocation("CHURN_GNN", alias="champion"))
with kumo.mlops(cfg):                    # the version serves the fit's snapshot on the stage
    result = trainer.fit(train_table, snapshot=snapshot)

mv = Registry(session, database_name="ML", schema_name="MODELS").get_model("CHURN_GNN").version("champion")
mv.run_batch(session.sql("SELECT ID::STRING AS ENTITY FROM ML.MODELS.USERS"), compute_pool="KUMO_GPU_POOL",
             output_spec=OutputSpec(stage_location="@ML.MODELS.KUMO_OUT/churn/"))

Back-compat

Situation Behaviour
Table name on a SnowflakeConnector Unchanged.
@DB.SCHEMA.STAGE/... path Was refused; now written to the stage.
Export into a stage directory that already holds files Refused before scoring by default or with APPEND; OVERWRITE empties it first, as on every file store.
SNOWFLAKE_FULL stage Writes work; reads raise OSError naming SNOWFLAKE_SSE.
Registry publish without kumo-engine, or if the builder fails The non-scoring carrier, as before (with a warning).
Same bundle registered again Existing version if the serving environment matches; else a new V<n>.
ModelOutputConfig, SnowflakeTracking New optional serving_snapshot / serving_options; existing calls unchanged.
run_batch output Scores only; not a Kumo prediction run for evaluate or score_drift.

Testing

  • kumo-snowflake/tests/test_stage_fs.py (in-memory stage): routing, bundles and Parquet through a stage path, listings, exact removes, ranged and GET reads, the occupied-directory rule, refusals, no signed URL in errors.
  • kumo-snowflake/tests/test_registry.py: version reuse, builder arguments, environment refusal, carrier fallback, bundle-only fetch.
  • kumo-engine/test/test_snowflake_model.py and test_model_agnostic_mlops.py: a version packaged by snowflake-ml scores like Trainer.predict; serving options; staged assets load through the loading connector; a stage root registers versions serving its snapshot.
  • Live on a Snowflake test account with synthetic data: stage file store and an MLOps root on a stage; run_batch binary (300/300 rows, labels equal to local predict) and regression on a GPU pool; all steps as a role with only the documented grants.

Change map: see the kumo-pr-change-map comment.

🤖 Generated with Claude Code

@Vedaant-J Vedaant-J self-assigned this Oct 24, 2026
@copy-pr-bot

copy-pr-bot Bot commented Oct 24, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@Vedaant-J
Vedaant-J temporarily deployed to gha-kumo-workflow-tests October 24, 2026 19:29 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@Vedaant-J

Vedaant-J commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor Author

Change map

File Function / scope What changed Why
packages/kumo-snowflake/src/kumo_snowflake/stage_fs.py new: StageFileSystemHandler, stage_filesystem Chunked filesystem over a named stage: PUT, LIST, REMOVE, presigned ranged reads, GET fallback Stages have no multipart upload
packages/kumo-snowflake/src/kumo_snowflake/sink.py SnowflakeSinkBackend: holds_files, filesystem, writers, publish_model, fetch_model Stage paths go through FilesystemSink and its shared preflight; tables and registry unchanged Exports and bundles on @DB.SCHEMA.STAGE/...
packages/kumo-snowflake/src/kumo_snowflake/artifacts.py StageArtifactLocation.restore Imports rebased lazily Public connector image has no kumo_core.diskgraph
packages/kumo-snowflake/src/kumo_snowflake/registry.py publish_model, fetch_model, _model_args, _version_holding, _backfill, _export Runnable versions via an entry-point builder; reuse a version holding the bundle for the same serving environment, recording missing metrics on it; parallel fetch Score in Snowflake; no duplicate versions; faster alias load
packages/kumo-engine/kumo/engine/integrations/_snowflake_model.py new: KumoModel, log_model_args, pip_requirements, embedded_code, signature CustomModel scoring entity ids; registration arguments and environment Run Kumo models with run_batch
packages/kumo-engine/kumo/engine/integrations/_snowflake_serving.py new: score_registered, load_snapshot Scores rows in the container against a stage snapshot, reading assets with the container identity Serving path for registered versions
packages/kumo-engine/kumo/engine/integrations/snowflake.py SnowflakeTracking.serving_options New serving_options= argument Registry versions follow the tracker
packages/kumo-engine/kumo/engine/_tracking.py TrackingBase.serving_options Optional field trackers set Carries options to registration
packages/kumo-engine/kumo/engine/trainer/output_config.py ModelOutputConfig.serving_snapshot, serving_options Two optional fields; unknown options raise Configure the Snowflake serving version
packages/kumo-engine/kumo/engine/trainer/trainer.py _connector_registry, _publish, _store_and_register Serving settings into publish metadata; a stage root serves its snapshot Carry serving settings through publish_model
packages/kumo-engine/pyproject.toml entry points kumo_snowflake.registry_models → log_model_args kumo-snowflake cannot import kumo-engine
packages/kumo-snowflake/tests/fakes.py FakeStage, _stage_session In-memory stage: LIST, PUT, GET, REMOVE, presigned Range Tests need no account
packages/kumo-snowflake/tests/test_stage_fs.py new tests Paths, bundles, Parquet, ranged and GET reads, removes, occupied directory Covers the stage file store
packages/kumo-snowflake/tests/test_registry.py publish and fetch tests Reuse, builder arguments, environment refusal, carrier fallback, fetch Covers registry changes
packages/kumo-engine/test/test_trainer_registry.py export config fakes Carry serving_snapshot and serving_options Follows ModelOutputConfig
packages/kumo-engine/test/test_snowflake_model.py new tests Packaged version scores like the trainer; options; requirements Covers the Snowflake model
packages/kumo-engine/test/test_model_agnostic_mlops.py stage asset and registration tests Staged assets load through the loading connector; stage root registers versions serving its snapshot Covers Snowflake serving
packages/kumo-snowflake/README.md stage and registry sections Stage paths, grants, runnable registry versions Setup the code cannot show
packages/kumo-engine/docs/mlops.md Snowflake serving, Assets, Grants Batch inference in Snowflake; asset identity per loading call User docs for the new paths
examples/mlops/snowflake/run_batch_smoke.py new script Fit, register, run_batch, compare with local scores Live check of Snowflake serving

@Vedaant-J
Vedaant-J force-pushed the vedaant/mlops-prediction-tracking branch from 5b51082 to 1d3acd6 Compare October 24, 2026 19:42
@Vedaant-J
Vedaant-J force-pushed the vedaant/mlops-sf-stage branch from 86d7e79 to b9ba641 Compare October 24, 2026 20:30
@Vedaant-J
Vedaant-J temporarily deployed to gha-kumo-workflow-tests October 24, 2026 20:30 — with GitHub Actions Inactive
@Vedaant-J
Vedaant-J temporarily deployed to gha-kumo-workflow-tests October 24, 2026 21:02 — with GitHub Actions Inactive
@Vedaant-J
Vedaant-J temporarily deployed to gha-kumo-workflow-tests October 24, 2026 21:25 — with GitHub Actions Inactive
@Vedaant-J
Vedaant-J force-pushed the vedaant/mlops-sf-stage branch from 42acd3d to 28cdf93 Compare October 26, 2026 18:29
@Vedaant-J
Vedaant-J temporarily deployed to gha-kumo-workflow-tests October 26, 2026 18:29 — with GitHub Actions Inactive
@Vedaant-J
Vedaant-J changed the base branch from vedaant/mlops-prediction-tracking to vedaant/mlops-filestore-sink October 26, 2026 18:29
@Vedaant-J
Vedaant-J force-pushed the vedaant/mlops-filestore-sink branch from d2153fe to db52381 Compare October 27, 2026 07:30
@Vedaant-J
Vedaant-J force-pushed the vedaant/mlops-sf-stage branch from 28cdf93 to 0a24e45 Compare October 27, 2026 07:30
@Vedaant-J
Vedaant-J temporarily deployed to gha-kumo-workflow-tests October 27, 2026 07:30 — with GitHub Actions Inactive
@Vedaant-J
Vedaant-J force-pushed the vedaant/mlops-sf-stage branch from 0a24e45 to 348c337 Compare October 27, 2026 09:51
@Vedaant-J
Vedaant-J temporarily deployed to gha-kumo-workflow-tests October 27, 2026 09:51 — with GitHub Actions Inactive
@Vedaant-J
Vedaant-J force-pushed the vedaant/mlops-filestore-sink branch from db52381 to db74c6c Compare October 27, 2026 18:58
@Vedaant-J
Vedaant-J force-pushed the vedaant/mlops-sf-stage branch from 348c337 to ff5a9c6 Compare October 27, 2026 18:58
@Vedaant-J
Vedaant-J temporarily deployed to gha-kumo-workflow-tests October 27, 2026 18:59 — with GitHub Actions Inactive
@Vedaant-J
Vedaant-J changed the base branch from vedaant/mlops-filestore-sink to vedaant/mlops-score-sketch October 27, 2026 20:36
Vedaant-J and others added 30 commits October 31, 2026 01:13
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… package tests can import

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… only table writes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_quoted already names identifier quoting in kumo_snowflake.session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	packages/kumo-engine/docs/mlops.md
#	packages/kumo-engine/kumo/engine/trainer/trainer.py
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… older ones

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
gha-kumo-workflow-tests — c9f10fc6 Deployed Nov 1, 2026 by Vedaant-J via import #1169
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.

2 participants