Run Modal-shaped code at AWS scale, unchanged.
Modal is where inference/batch code is prototyped — great inner loop, pay-nothing-when-idle.
AWS is where the same code scales — you own the rectangle and the economics flip at volume.
calque is the loan-translation between the two: the same script that ran over 10 items on Modal
runs over 10,000,000 on AWS without a logic rewrite (only a mechanical gpu= substitution).
The thesis: groups hit cases where Modal doesn't scale for them — capacity, cost at volume, control — and need an unchanged migration path off it. calque is that path: it widens its supported Modal-idiom surface toward real-world scripts (not a fixed test corpus), and for anything it doesn't yet support, it says so loudly (a structured leak) rather than silently dropping semantics or crashing mysteriously. See the capability matrix below for exactly where that frontier is today.
A calque is a structure-preserving translation between languages (English "flea market" ← French marché aux puces). That is the job: translate Modal's idioms onto AWS term-by-term, structure intact, so the author doesn't notice the translation.
This is a spike. It exists to prove one thing and fake everything else:
- Prove: the plumbing carries Modal's semantics onto AWS — a real Modal script's decorators, execution shape, and payload code run unchanged against real AWS hardware.
- Fake behind the seam: all card-selection / cost-optimization intelligence. The recommender
is a plumbing pass-through, not a decision — it carries the script's own requested card through
unchanged, falling back to one constant (
RTX PRO 6000) only when the script declares nogpu=at all. No real phase-detection, right-sizing, or cost/latency optimization exists behind the interface (calque#134). Seeinternal/target.
Everything here runs locally and launches no AWS GPU — no credentials required.
Prerequisites: uv either way. AWS credentials are needed
only for real (billable) runs, not for anything below.
Install a release (macOS / Linux via Homebrew, Windows via Scoop — sets CALQUE_PYAST_DIR
automatically):
brew install spore-host/tap/calquescoop bucket add spore-host https://github.com/spore-host/scoop-bucket
scoop install calqueOr build from source (also needs Go 1.26, matching go.mod):
git clone https://github.com/spore-host/calque && cd calque
go build -o calque ./cmd/calque # control plane
(cd tools/pyast && uv sync) # Python AST helper deps
Analyze a Modal script (static passes — GPU-swap legality, Bedrock gate, leaks):
./calque analyze examples/map_batch_inference.py
gpu[Batcher]: clean_swap requested="H100" -> RTX PRO 6000 (single-card, no coupling signal; memory-bound B=1 substitution is legal)
volume: "weights" -> volumes/weights/ (mount /weights, delta-sync => warm-cache reuse)
...
--- leak report (§10) ---
LEAKS: 1 emitted across 1 primitives
Run the full pipeline locally (dry-run — the default, no AWS spend):
./calque run --n 100 --dry-run examples/map_batch_inference.py
[DRY-RUN] not launching a billable instance; driving warm worker locally on a synthetic sample
[DRY-RUN] warm unit ran 50 items, 0 failed; @enter x1 (0.305s), mean 0.0542s/item
...
--- leak report (§10) ---
LEAKS: 3 emitted across 3 primitives
calque run --dry-run always executes the picked unit's body via uv run, never the
ambient shell's own python3/site-packages — so you never need to pip install a
script's own third-party dependencies (e.g. modal, google-cloud-storage,
earth2studio) locally first. calque injects them into uv's ephemeral environment
per invocation, using the exact pip_install(...)/uv_pip_install(...) packages its
own .image chain already declares (plus modal itself, unconditionally, since a real
script's body routinely references modal.Secret/modal.Volume directly even when
Modal's own SDK isn't itself a declared dependency). Set CALQUE_PYTHON=/path/to/python3
to bypass uv entirely and point dry-run at a specific interpreter instead.
That's the whole idea in two commands: a mechanical gpu= swap, then every pipeline stage
running end to end against the unchanged script — parse, gate, plan, warm-execute, collect.
See examples/ for seven annotated journeys (analyze, dry-run, Bedrock
route-away, an unsupported-workload refusal, Volume-cached reuse, a permanent
non-goal honestly leaked, and a real billable AWS run), each with real output.
Ready to run something for real? docs/guide/getting-started.md
picks up exactly where this section leaves off — smoke test, first real (billable) AWS
run, then driving your own script's own real body on real hardware.
Notes.
analyze/runreach two best-effort network sources (the Bedrock catalog and thehf-bedrock-mapAPI); offline they print awarn:line and fall back — a networkless run is not a failure. The binary finds the Python helper relative to the repo; to run a copied/go installed binary out of tree, setCALQUE_PYAST_DIRto thetools/pyastpath.
Experimental, versioned releases shipping — see the latest release (packaged installs via Homebrew/Scoop). Tracking lives on GitHub (Issues / Projects / milestones), not local files. Phase 2 (Modal-idiom porting, milestones M5–M10) is merged.
The single most direct answer to "does calque support my script" is
docs/modal-compatibility-matrix.md — a
construct-by-construct census against Modal's real API surface and a corpus of
real-world usage, not a hand-picked list. Everything not yet supported is either
recognized-and-leaked (detected, explained, never silently dropped) or tracked as an
open gap; see docs/behind-the-seam-register.md
for the deliberate non-goals.
Verification below is layered — offline (unit-tested, no spend), live (run on a real acquired GPU), and at scale (larger N, more instances). The matrix reads them apart:
| Capability | Offline tested | Live verified | Scale verified |
|---|---|---|---|
| Parse → IR, leak reporting | ✅ | n/a | ✅ corpus census |
gpu= swap guard (§7) |
✅ | ✅ | ✅ corpus |
| Bedrock gate + route-away (§11) | ✅ | ✅ (live catalog) | n/a |
| Idiom pass-through (§C) | ✅ | n/a | n/a |
| Single-instance warm run (§6) | ✅ | ✅ (L4 + RTX PRO 6000, Qwen2.5-1.5B) | ✅ N=1000 |
| Volume sync + commit write-back (§3/§15) | ✅ | ✅ (sync) | — |
Multi-instance .map() fan-out (§15) |
✅ | ⏳ pending capacity | ⏳ pending N=100k (#18) |
| Serve entrypoints (§F) | detect + leak only | ❌ not built | ❌ |
✅ done · ⏳ blocked/pending · ❌ deliberately not built (see docs/behind-the-seam-register.md).
The per-capability detail follows; nothing is removed, only sequenced under the matrix.
Built, tested, and verified (no spend):
parse → IR → seam— pyast helper, six-primitive IR,StubRecommender(constant behind an interface).- Bedrock gate + route-away (§11) — live catalog,
exact/near/nonetiers, proven in both directions. A model that's already an exact Bedrock API call yields a structuredReplacementOffer(BedrockmodelId+ region invoke hint) and short-circuits the runnable path before a GPU is acquired — renting a GPU for a served model is the wrong answer. Near matches surface as candidates to verify, with the axes of difference and no quality claim (exact-match discipline). gpu=guard (§7) — clean-swap vs multi-GPU/coupled flags, adversarial fixture.- idiom pass-through (§C) — image verbs (incl.
from_aws_ecr); portable kwargs (cpu=/memory=recorded-not-acted behind the seam,retries=wired to the warm supervisor); synchronous invocation kinds.map/.starmap/.for_each/.remote;.map.aiorecognized-and-leaked;.spawn()+.get()block-and-wait fan-out has a real driver (calque spawn-run), live-verified on real AWS. - warm worker (
warmd+runner.py, §6) —@enteronce, crash-restart re-drive, partial-failure. - plan (§5) — truffle resolve+price,
Acquireras a thin adapter overlagotto/pkg/snipe.Snipe(AZ-sweep, capacity retry/backoff, deadline). - image / exec — Dockerfile+digest cache; S3 sink/collector; on-instance
warmdentrypoint. - volumes (§3/§15) —
Volume.from_name→ stable S3 prefix, delta-synced to the mount path before@enter(warm-cache reuse; image/volume separation).volume.commit()persists as an end-of-run write-back (reverse S3 sync after@methoddrains); mid-run.reload()/.commit()is leaked as a semantic gap the spike doesn't reproduce. - multi-instance
.mapfan-out (§15) — shard N items across S single-node instances acquired in parallel, drive each shard'swarmdindependently, collect the union back into one globally ordered result set + one globalmissing[], re-drive a dead shard once. Embarrassingly parallel across independent boxes — not §1's forbidden multi-node/gang scheduling. Core is unit-tested offline;calque real --shards Nwires it. - serve entrypoints (§F) —
@web_endpoint/@asgi_app/@wsgi_appare detected and leaked as a deferred shape (batch is the shape the spike runs); a served Bedrock model still routes away. The long-lived server is not built — seedocs/serve-architecture.md. - full pipeline —
calque run --dry-runruns every stage locally;calque rampacquires one GPU and runs an N-ramp on it;calque real --shards Nfans out across a fleet.
Real inference, verified on live GPUs. Getting real inference end-to-end surfaced
five genuine deployment findings, each caught fast and fixed: worker dir /opt→/tmp,
docker needs sudo, IMDSv2 hop-limit 2 for container creds, 200 GiB root volume for the
vLLM image, and vLLM's stdout logs colliding with the warm-worker JSON protocol (the §6
"socket draws blood" edge — now isolated + regression-tested).
Corpus census (§16.4) across the test scripts: Bedrock 1 exact-eligible / 1 self-hosted / 4 identity-hidden; gpu guard 4 clean-swaps / 1 multi-GPU flag / 1 coupled flag / 1 no-gpu.
script.py
└─ parse decorators → IR (shallow AST; bodies extracted verbatim)
└─ gate Bedrock exact match? (route away: print offer & stop BEFORE any GPU)
└─ recommend IR → Target (STUB: constant behind Recommender interface)
└─ plan truffle: Card → candidate g7e instances (+ live price)
acquire: block-and-wait via lagotto/pkg/snipe.Snipe (AZ-sweep, retry/backoff)
image: .image DSL → Dockerfile → ECR (cache by digest)
└─ exec spawn.Provision launches + brings up the instance(s)
[worker] warmd supervises warm Python: @enter once, drain → S3
[--shards N] shard items → N instances in parallel → union collect
└─ collect gather from S3, ordered by GLOBAL input index (+ global missing[])
└─ measure per-item cost + occupancy (tach hook)
└─ report leaks
The Go control plane understands decorators (configuration). It does not parse function bodies (payload) — those ship to the worker verbatim and run under Python exactly as on Modal.
Acquisition seam:
spawn.ProvisionownsRunInstances(acquire + bring-up in one shot). The block-and-wait retry/backoff/AZ-sweep loop calque needs on top of that now lives inlagotto/pkg/snipe.Snipe—internal/plan.Acquireris a thin adapter over it, not the owner of that logic itself (a deliberate migration off a hand-rolled local copy, lagotto#106).
calque analyze <script.py>— static passes only (parse,gpu=guard, Bedrock gate, leak report). Zero AWS calls, zero cost.calque run <script.py>— the full pipeline, driven locally against a synthetic sample (--dry-run, the default — never launches a billable instance).calque smoke— the first billable action: an acquire-only de-risking test (acquire → bring up → run a trivial job → collect → terminate) before trusting the plumbing with real inference.calque real— real inference/compute on acquired hardware — a single instance, or--shards Nto fan out across a fleet.--script your_app.pydrives that script's own real body (not a stand-in), with flags for whatever its real signature needs (--function,--secret,--item-file/--arg-file/--arg-json,--pip,--stage-file, ...).calque ramp— acquire one instance patiently, hold it, run an N-item ramp across it over SSM (efficient repeated-N testing without re-paying acquisition).calque pool— a warm, shared model pool that survives across separate runs/claims.calque spawn-run— block-and-wait fan-out for a script using Modal's.spawn()/.get()idiom.calque session— check a MIG slice or MPS client-slot in/out on an already-running instance (institutional multi-tenancy).
smoke/real/ramp/pool create/pool scale/spawn-run acquire billable AWS
hardware and refuse to run without an explicit --i-understand-this-spends-money
flag. The route-away gate runs on run/real/ramp too: if the model is already an
exact Bedrock API call, calque prints the offer and stops before acquiring
anything.
Not sure which command fits your workload? See
docs/guide/which-verb.md. Every flag, for every
command, exactly as the code accepts it: see
docs/guide/cli-reference.md — that page is the
single source of truth for flag syntax; this README intentionally doesn't duplicate it.
A second, related use case: once a workload is on infrastructure you control, an institution
can make utilization and trust decisions Modal reasonably can't make for arbitrary public
tenants. calque has real, tested primitives for this — a warm model pool, and two ways to
divide one physical GPU across concurrent users. Full design detail for all three, in one
place: docs/institutional-sharing.md.
- Warm pools (
calque pool) — a model-scoped SQS queue with resident workers that keep a loaded model warm across separate claims, instead of paying@enter's load cost per run. Seedocs/pool-queue-contract.md. - MIG slice provisioning (
internal/mig) — hardware-partitions a supported card (g7/g7e's RTX PRO 4500/6000, both Server Edition on AWS) into isolated slices, live-verified against real hardware rather than assumed from datasheets. Seedocs/gpu-sharing-support-matrix.md. - MPS trusted-tenant sharing (
internal/mps) — cooperative multi-process sharing for cards without MIG (g6/g6e's L4/L40S), for a known/bounded population rather than arbitrary tenants; requires a separate--i-understand-shared-gpu-has-no-isolationconsent flag distinct from the ordinary spend confirmation, since the risk (no hardware isolation) is a different kind than "this costs money."
See docs/tenancy-vs-session.md for the check-out/check-in
lifecycle design.
calque session binds one user to one MIG slice or MPS client-slot on an instance
someone else already acquired — it never launches or terminates EC2 instances itself,
matching docs/tenancy-vs-session.md's explicit scope
boundary. mig (hardware-isolated) needs no extra confirmation; mps (cooperative, no
isolation) requires a separate consent flag from the ordinary spend gate, since the risk
is a different kind. Checkout returns a slice ID and a session token; checkin requires
that exact token back. Full subcommand/flag detail:
docs/guide/cli-reference.md.
Decisions that shape what the spike doesn't do live in docs/ as durable records:
docs/serve-architecture.md— why serve entrypoints are detected + leaked but the long-lived server is not built.docs/behind-the-seam-register.md— every §1/§4/§18 non-goal with the attach point a future build would touch, so "won't-port" stays a documented decision, not a silent gap.
Directory tree follows the spike spec §12, with one rename: the spec's worker
supervisor is called spored, but that name is already the spore.host lifecycle
daemon (systemd service on every instance). Ours is warmd (worker/warm-runner/),
which runs under the real spored. Project tracking lives on GitHub (Issues /
Projects / milestones), not in local files.
go build ./... # control plane
cd tools/pyast && uv sync # Python AST helper deps
See .goreleaser.yaml for the packaged-release archive
layout (Homebrew/Scoop install instructions are at the top of this README,
under "Quick start") if you're installing from a plain tarball/zip instead.
calque is an independent project. It is not endorsed by, affiliated with, or supported by Amazon Web Services, Inc. or Modal (the company behind modal.com). "AWS," "Modal," and any other product or company names mentioned in this repository are trademarks of their respective owners.
Apache 2.0. See LICENSE.
