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.
Build→Deploy→Key generation→Query

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. It includes the build and query clients, the packaged mnist_fc pipeline, and the lightweight mnist data helper used below. 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 data helper caches the MNIST test set on first use. Pipeline weights come from lattica_build.examples.advanced.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 token + key registration, or only queries. Token + key registration does not start a worker; the query flag owns the worker session. Running deploy again with the same MODEL_NAME redeploys into the existing model.
mnist_e2e.pyPYTHON
import os
import mnist
import torch

from lattica_build.examples.advanced import 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

def load_mnist_test_data() -> tuple[torch.Tensor, torch.Tensor]:
    mnist.datasets_url = "https://raw.githubusercontent.com/fgnt/mnist/master/"
    images = mnist.test_images()
    labels = mnist.test_labels()

    x = torch.tensor(images, dtype=torch.float32).reshape((-1, *mnist_fc.INPUT_SHAPE))
    x = ((x / 255.0) - 0.1307) / 0.3081
    y = torch.tensor(labels, dtype=torch.long).reshape((-1, mnist_fc.BATCH))
    return x, y


def main() -> None:
    x, y = load_mnist_test_data()
    studio = LatticaStudio(LICENSE_KEY)

    if RUN_DEPLOY_AND_COMPILE:
        model_id = studio.deploy_pipeline(
            mnist_fc.build_pipeline(),
            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, name=MODEL_NAME, save=True)
        with QueryClient(token) as client:
            client.keys.ensure()  # registers the EVK without a worker

    if RUN_ENCRYPTED_QUERY:
        token = studio.tokens.load(MODEL_NAME)
        with studio.workers.running(model_id, stop_on_exit=True):
            with QueryClient(token) as client:
                key = client.keys.ensure()
                for i in range(3):
                    result = client.query.encrypted(x[i], key=key)
                    prediction = result.argmax(dim=-1)
                    accuracy = (prediction == y[i]).sum().item() / mnist_fc.BATCH
                    print(f"Accuracy: {accuracy * 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