Tutorial: Run Cortex batch inference over a Snowflake table¶
This tutorial shows how to partition a Snowflake table across multiple sandboxes running in parallel, call SNOWFLAKE.CORTEX.COMPLETE inside each sandbox via SQL, and collect the results. Each sandbox authenticates using the session identity Snowflake injects automatically — no credentials are forwarded from your driver.
When you finish, you will have a working pattern for any workload that needs to apply an LLM to rows in a Snowflake table at scale.
Time to complete: about 20 minutes.
Prerequisites¶
- The
snowflake-sandbox-pythonSDK installed and a Snowflake connection configured. See Cortex Sandboxes quickstart. - No image build and no staging stage are involved: the sandboxes launch from the
sandbox-basecatalog image, andscorer.pyis uploaded into each one at run time. Every statement below names its database and schema explicitly, so your connection does not need a default database or schema. - Your role must have
USAGEon the CortexCOMPLETEfunction. Ask your account administrator if you are unsure.
Step 1: Set up the demo tables¶
Create a PRODUCT_REVIEWS table with a handful of sample reviews and an empty REVIEW_SENTIMENTS table to hold the results. Replace <your_database> and <your_schema> with your own database and schema.
Step 2: Write the scorer script¶
This is the code that runs inside each sandbox. Save it as scorer.py. It receives the database name, schema name, and row range as command-line arguments, queries PRODUCT_REVIEWS, calls SNOWFLAKE.CORTEX.COMPLETE per row, and returns results as JSON on stdout.
It connects to Snowflake with get_snowflake_connection(), which resolves the session token the platform provides — no credential configuration is required.
Replace llama3.1-8b with any Cortex model your role has access to. See COMPLETE (SNOWFLAKE.CORTEX) for the list of available models.
Step 3: Write the driver¶
The driver fans out three sandboxes in parallel (one per row range), sends scorer.py into each, and streams live output from each sandbox as rows are scored. Save this as main.py alongside scorer.py.
How to run this example¶
Save scorer.py and main.py in the same directory and run:
There is no image build step: every run starts the three sandboxes from the sandbox-base catalog image and uploads scorer.py into each of them.
Expected output:
All three sandboxes run concurrently, so the total time is roughly the time of one chunk — not three.
What this demonstrates¶
- No credentials are forwarded. Each sandbox connects to Snowflake using only the session token the platform binds at
/snowflake/session/token. Your PAT is not passed to the container. - The scorer is uploaded, not inlined.
sb.upload_file()putsscorer.pyat a writable path inside each sandbox, so the exec command stays a clean, readable path:["python", "/tmp/scorer.py", ...]. This avoids passing a large script as a shell-cargument. Each sandbox uploads its own copy, which takes well under a second and happens in parallel with the others. Note the/tmpprefix: the sandbox refuses writes to the filesystem root, so/scorer.pywould fail. The server answers with a403, which the SDK surfaces asSandboxAuthError— so a path mistake here reads as an authentication failure rather than a path one. - Cortex runs in-warehouse.
SNOWFLAKE.CORTEX.COMPLETEis a Snowflake SQL function — the LLM call is executed by Snowflake’s compute, not by the sandbox container itself. - Streaming output from parallel sandboxes.
exec_stream()yields lines as each sandbox produces them. The interleaved output in the expected output above — lines fromsb-1,sb-2, andsb-3arriving out of order — reflects the three sandboxes running genuinely concurrently. Useexec()when you only need the final result; useexec_stream()when you want to observe progress or handle long-running commands. - Governance applies. Each sandbox inherits your session identity. Row-level security and column masking policies apply to the data each sandbox reads, exactly as they would for a query you run directly. See Tutorial: Governance enforcement in a sandbox.
- Scale by adding chunks. Replace the three
pool.submitcalls with a loop over however many row ranges you need. Each sandbox is independent and the concurrency is bounded only by your account limits.