pllm
SDK

Pipeline

Combine model, privacy protocol, preparation, runtime, and deployment choices in one configuration.

PLLM documentationView Markdown ↗

A Pipeline records what you intend to run. Profiles provide visible defaults, and component overrides keep their versions. The configuration digest covers normalized values, not live connections or provider state.

Creating a pipeline does not run anything. Model lowering, research transforms, compilation, deployment, preparation, and session creation are separate steps. Each step returns a record or a structured error. The parties and offline work overview explains why preparation and live inference remain distinct.

Run pllm config show to inspect examples/pllm.yaml without loading a model or runtime.

How profiles fit together

A profile is the safest starting point because it fills the protocol, preparation, role, kernel, and optional verification slots with a compatible set. Choose first by who owns the transformer weights, then by the protection and checking required at the deployment boundary. Constructing a profile is still configuration only; Experiment.resolve() checks the complete composition, and compilation separately checks operator coverage.

API: Profile objects and signatures

Python SDK example

Each example below is a complete experiment.py for the same generated tiny model, so the pipeline is the only important difference. Serve any runtime-backed profile through the trusted local gateway after placing that example in experiment.py:

pllm gateway --local \
  --experiment experiment.py:experiment \
  --trust-python

The gateway resolves the exact experiment, starts the required provider roles, and serves Responses and Chat Completions requests on http://127.0.0.1:8080/v1 with the default local API key local. To measure the same file instead, pass it to pllm benchmark run. Generated tiny weights test protocol behavior only; use a pinned real checkpoint for quality or performance work.

Prepare public-weight stages with MaskedLinearCpu

Use MaskedLinearCpu when the transformer body is public but inference must not receive plaintext client activations. It pairs prepared masking, offline model-aware corrections, inference placement, and the native CPU kernel.

from pllm import Deployment, ExecutionBudget, Experiment
from pllm.profiles import MaskedLinearCpu
from pllm.sources import TinyModel

experiment = Experiment(
    name="masked-linear",
    pipeline=MaskedLinearCpu(TinyModel(model_id="masked-linear-tiny")),
    deployment=Deployment.local(root="local://masked-linear"),
    budget=ExecutionBudget(requests=1, max_input_tokens=64, max_new_tokens=1),
)
resolved = experiment.resolve()
assert resolved.profile == "baseline.masked_linear_cpu"
assert resolved.requires_preparation

Verify prepared outputs with VerifiedMaskedLinearCpu

Use VerifiedMaskedLinearCpu when the trusted client must also check bounded integer stage outputs. It adds FreivaldsVerify material to the prepared path; it does not reduce online traffic. This is PLLM's trusted-client adaptation of Slalom, not a TEE reproduction.

from pllm import Deployment, ExecutionBudget, Experiment
from pllm.profiles import VerifiedMaskedLinearCpu
from pllm.sources import TinyModel
from pllm.verification import FreivaldsVerify

experiment = Experiment(
    name="verified-masked-linear",
    pipeline=VerifiedMaskedLinearCpu(
        TinyModel(model_id="verified-tiny"),
        verification=FreivaldsVerify(target_failure_bits=48),
    ),
    deployment=Deployment.local(root="local://verified-masked-linear"),
    budget=ExecutionBudget(requests=1, max_input_tokens=64, max_new_tokens=1),
)
resolved = experiment.resolve()
assert resolved.verification_component == "pllm/freivalds-verify/v1"
assert resolved.verification_target_failure_bits == 48

Encrypt linear inputs with DirectFHEProfile

DirectFHEProfile selects the separate direct-FHE method. It uses ciphertext transport rather than the masked-linear correction inventory and requires the optional HE runtime for live execution.

from pllm import Deployment, ExecutionBudget, Experiment
from pllm.profiles import DirectFHEProfile
from pllm.sources import TinyModel

experiment = Experiment(
    name="direct-fhe",
    pipeline=DirectFHEProfile(TinyModel(model_id="direct-fhe-tiny")),
    deployment=Deployment.local(root="local://direct-fhe"),
    budget=ExecutionBudget(requests=1, max_input_tokens=64, max_new_tokens=1),
)
resolved = experiment.resolve()
assert resolved.profile == "runtime.direct_fhe"
assert not resolved.requires_preparation

Guard provider-owned weights with ProprietaryGuarded

Use ProprietaryGuarded when the provider owns the weights and explicit request limits match the deployment policy. GuardedLinear adds quotas and bounds, not cryptographic activation confidentiality.

from pllm import Deployment, ExecutionBudget, Experiment
from pllm.profiles import ProprietaryGuarded
from pllm.protocols import GuardedLinear
from pllm.sources import TinyModel

experiment = Experiment(
    name="proprietary-guarded",
    pipeline=ProprietaryGuarded(
        TinyModel(model_id="guarded-tiny"),
        linear=GuardedLinear(max_rows_per_request=32),
    ),
    deployment=Deployment.local(root="local://proprietary-guarded"),
    budget=ExecutionBudget(requests=1, max_input_tokens=64, max_new_tokens=1),
)
resolved = experiment.resolve()
assert resolved.profile == "runtime.proprietary_guarded"
assert resolved.proprietary_protocol == "guarded"

Blind provider-owned stages with ProprietaryBlinded

ProprietaryBlinded selects the provider-owned blinded-linear transport. Its support and leakage claims remain scoped to that runtime arm and deployment.

from pllm import Deployment, ExecutionBudget, Experiment
from pllm.profiles import ProprietaryBlinded
from pllm.sources import TinyModel

experiment = Experiment(
    name="proprietary-blinded",
    pipeline=ProprietaryBlinded(TinyModel(model_id="blinded-tiny")),
    deployment=Deployment.local(root="local://proprietary-blinded"),
    budget=ExecutionBudget(requests=1, max_input_tokens=64, max_new_tokens=1),
)
resolved = experiment.resolve()
assert resolved.profile == "runtime.proprietary_blinded"
assert resolved.proprietary_protocol == "blinded"

After choosing a profile, use configuration to place it in an experiment and the compiler guide to understand the separate executable-coverage gate.

Loading documentation index…

Search runs locally in this browser.