A complete example, start to finish
One script that builds a small MNIST classifier, deploys it, creates a query token, generates keys, and runs encrypted queries against it. Copy it, run it, see the result. Then change things and watch what happens.
What the script does
Before you run it
Python 3.11 or newer. The example uses the current studio and query packages.
Install Lattica Studio. pip install lattica-studio. Also pip install torchvision so the script can download MNIST. See Installation & Setup.
Get your License. Sign up and take your License from the console. Set LICENSE_KEY in the script, or load it from LATTICA_LICENSE_KEY. Guide
Have some credits. Running the worker uses credits. Add them in the console if your balance is low. Pricing
Network access to the Lattica backend (default https://api.lattica.ai). Override with LATTICA_BE_URL if you point at another environment. The first run also downloads the MNIST test set. Pipeline weights come from example_mnist_fc. You do not copy a checkpoint by hand.
The script
mnist_e2e.py. Set LICENSE_KEY or export LATTICA_LICENSE_KEY, then run python mnist_e2e.py. The flags at the top let you rerun only deploy, only key generation, or only queries. Running deploy again with the same MODEL_NAME redeploys into the existing model.import os from torch.utils.data import DataLoader from torchvision import datasets, transforms from lattica_build.examples import example_mnist_fc from lattica_studio import LatticaStudio from lattica_query import QueryClient # Set explicitly, or keep empty to read LATTICA_LICENSE_KEY from the environment. LICENSE_KEY = "" if not LICENSE_KEY: LICENSE_KEY = os.getenv("LATTICA_LICENSE_KEY", "") if not LICENSE_KEY: raise ValueError("Set LICENSE_KEY or LATTICA_LICENSE_KEY before running this script") MODEL_NAME = "MY_MNIST_MODEL" # Run stages selectively during development. RUN_DEPLOY_AND_COMPILE = True RUN_CREATE_QUERY_TOKEN_AND_GENERATE_KEYS = True RUN_ENCRYPTED_QUERY = True print('Loading MNIST test data for a single batch to query the model...') test_dataset = datasets.MNIST( "../data", train=False, download=True, transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)), ]) ) loader = DataLoader(test_dataset, batch_size=example_mnist_fc.BATCH, shuffle=True) input_data = iter(loader) def main() -> None: studio = LatticaStudio(LICENSE_KEY) if RUN_DEPLOY_AND_COMPILE: model_id = studio.deploy_pipeline( example_mnist_fc.build_pipeline(), example_mnist_fc.build_params(), MODEL_NAME, display_graph=True, ) else: model_id = studio.models.get_id_by_name(MODEL_NAME) if RUN_CREATE_QUERY_TOKEN_AND_GENERATE_KEYS: token = studio.tokens.create(model_id, save_as=MODEL_NAME) with studio.workers.running(model_id, stop_on_exit=not RUN_ENCRYPTED_QUERY): query_client = QueryClient(token) query_client.generate_key(load_if_exists=False) else: token = studio.tokens.load(MODEL_NAME) query_client = QueryClient(token) if RUN_ENCRYPTED_QUERY: with studio.workers.running(model_id, stop_on_exit=True): sk = query_client.generate_key(load_if_exists=True) for _ in range(3): pt, ground_truth = next(input_data) res = query_client.run_query(sk, pt) y_pred = res.argmax(dim=-1) print(f"Accuracy: {(y_pred == ground_truth).sum().item() / example_mnist_fc.BATCH * 100:.1f}%") if __name__ == "__main__": main()
[━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━] LATTICA BUILDER │ registering model ✓ └─ duration: 1.61s └─ Model 'MY_MNIST_MODEL' already exists, redeploying into model_id='4b5b220e-4955-4da5-a9b6-3737a874476c' └─ stopping all active workers of model 'MY_MNIST_MODEL' [━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━] LATTICA BUILDER │ uploading model ✓ └─ duration: 2.22s └─ model size: 170.7 KB └─ upload status: Success [━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━] LATTICA BUILDER │ compiling model ✓ └─ duration: 6.05s └─ model ID: 4b5b220e-4955-4da5-a9b6-3737a874476c [━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━] LATTICA BUILDER │ generate token ✓ └─ duration: 971ms [━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━] LATTICA BUILDER │ starting worker ✓ └─ duration: 3.78s └─ worker status: {'workerSessionId': '514ee064-ad99-4566-a5e6-990bc3679dbc', 'status': 'UP'} [━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━] LATTICA QUERY │ retrieving initialization data ✓ └─ duration: 913ms └─ Timing summary • get_user_init_data Step Time Percent ──────── ────────── ──────── download 788.0 ms 86.4% instance 100.0 ms 11.0% logic 20.0 ms 2.2% upload 4.0 ms 0.4% worker 0.0 ms 0.0% ──────── ────────── ──────── Total 912.0 ms 100.0% [━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━] LATTICA QUERY │ generating FHE keys ✓ └─ duration: 8ms └─ key artifacts will be stored at /home/rotem/.lattica/keys/query_key_artifacts_fe53624a-0d7a-4389-9d0f-616f08ba29e0.json └─ saved key to /home/rotem/.lattica/keys/query_key_artifacts_fe53624a-0d7a-4389-9d0f-616f08ba29e0.json [━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━] LATTICA QUERY │ registering evaluation key ✓ └─ duration: 2.94s └─ evaluation key size: 465.1 KB └─ upload complete: Success └─ Timing summary • preprocess_pk Step Time Percent ──────── ────────── ──────── download 508.0 ms 81.0% worker 72.0 ms 11.5% instance 28.0 ms 4.5% logic 16.0 ms 2.6% upload 3.0 ms 0.5% ──────── ────────── ──────── Total 627.0 ms 100.0% [━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━] LATTICA QUERY │ loading local key artifacts ✓ └─ duration: 1ms └─ loading key from /home/rotem/.lattica/keys/query_key_artifacts_fe53624a-0d7a-4389-9d0f-616f08ba29e0.json └─ loaded cached evaluation key; skipping online registration [━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━] LATTICA QUERY │ running encrypted query ✓ └─ duration: 2.77s └─ ciphertext 6.1 MB └─ Timing summary • apply_hom_pipeline Step Time Percent ──────── ────────── ──────── upload 1.269 s 47.9% download 1.250 s 47.2% instance 93.0 ms 3.5% logic 32.0 ms 1.2% worker 7.0 ms 0.3% ──────── ────────── ──────── Total 2.651 s 100.0% Accuracy: 96.0% [━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━] LATTICA BUILDER │ stop worker ✓ └─ duration: 2.01s
Things to try
Change the parameters
Adjust n, the scale, or the precision levels and redeploy. See what still compiles and how results shift.
Swap an operator
Try a different activation or layer in the body and see how it affects compilation and accuracy.
Build your own MNIST
Write the pipeline yourself. If it does not compile, compare it against this one to find the difference.
Start a different model
Once the flow makes sense, swap in your own weights and a body that matches your network.
These are examples. The script is a sandbox, change what you want and rerun.
When it clicks
Build your own pipeline
Once this example feels clear, you are ready to build a pipeline for your own model. Start from the operators and the structure.