Skip to main content
Build · Deploy · Key generation · Query

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

It deploys a two-layer fully-connected MNIST digit classifier and measures encrypted-inference accuracy on batches of 100 test images. A 28×28 image goes in, an encrypted prediction comes back, and you decrypt it to read the answer. Plaintext pixels never leave your machine. The worker sees only ciphertext. The secret key stays on the client.
BuildDeployKey generationQuery

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

Copy this into a file such as 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.
mnist_e2e.pyPYTHON
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()
When the script runs through, the client prints progress like this. Your timings and ids will differ. The accuracy line is the result that matters.
Each stage is explained on its own page: Build, Deploy and Key generation, and Run Queries.

Things to try

Once it runs, change it. Breaking the example on purpose, then reading the error, teaches you how the platform responds. A few starting points, not a checklist:

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.

Build the Pipeline