Systems Specialization · CPU ReferenceCPU reference; target qualification separateLesson 6 of 8 in Phase 15

Phase 15 / Deployment, interoperability & edge AI

Containerize a model service and verify its boundary

Revision v0.7.0 ·

Lesson · about 110 minutes · CPU practice

Package an inference service and verify model identity, request validation and readiness across process and container boundaries.

Phase study guide: preparation, milestones and project evidence

Before you start

Before this: Choose weight, activation and accumulation precision
Hardware: CPU; Docker lab requires a local engine

Setup help: Python, notebook, or Cloud TPU VM

Use the tested CPU packages (the recorded environment uses Python 3.14.3). Other Python versions and accelerator environments require separate checks. The optional Cloud TPU specialization explains the target-specific execution boundaries. Logical CPU devices practice partitioning; they do not simulate TPU performance.

Download the repository ZIP or use your existing checkout. Extract it and open a terminal in the folder containing requirements-cpu.txt. The beginner ZIP is separate; these lessons use the full repository folder.

macOS / Linux terminal · repository folder
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements-cpu.txt

PowerShell · repository folder
py -3 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements-cpu.txt

After activation, run the complete worked example from that same folder:

macOS / Linux terminal · activated environment
python phases/15-deployment/07-containerize-a-model-service/code/main.py

PowerShell command · repository folder
.\.venv\Scripts\python.exe phases/15-deployment/07-containerize-a-model-service/code/main.py

For an incremental build, create main.py in your own practice folder, append each build block in order, and run python main.py with this environment active. Keep that file separate from the downloaded reference solution.

Check: Fresh-process valid requests: 2 rejected boundaries: 3 This companion tests the process/artifact contract. Run the Docker lab for container evidence.

Revisit the guided CPU setup · Open 19: TPU Setup. Package instructions are CPU instructions; Windows and accelerator environments need their own validation.

Phase 15.06 Stage 1 of 5 · Understand
Stage 1 of 5 · Understand the MechanismCore mental model, equations & visual walkthrough

What you will be able to do

  • Separate training from inference
  • Understand the build context
  • Keep runtime inputs explicit
  • Prove the same request survives the boundary

The problem

Your model works in a notebook, but another machine has a different interpreter, library set and working directory. Let’s export a small prediction artifact, verify it in a fresh process, then run the same boundary in a real Docker container.

The idea

Containerization packages a runtime environment; a running container executes a process; a model artifact supplies versioned numerical content. Keeping these identities separate makes deployment and debugging reproducible.

An image, a process and an artifact have different identities

A container image can start successfully while loading the wrong model path or configuration. Conversely, a valid model artifact can fail because the runtime lacks a compatible dependency. Record the image identity and model/configuration identities together.

Trace the build context into the image, then configuration and artifact mounts into the running process. Mark the request boundary where decoding, validation, inference and response encoding happen. A readiness signal should reflect the actual service contract, not merely process existence.

The current subprocess experiment demonstrates a process boundary. It is not evidence that a Docker build or container benchmark ran. Use that distinction when extending the lesson to your own container execution and keep the resulting receipt.

What each deployment receipt proves

Predict: Which evidence would still be missing after the Python subprocess tests pass?

Each row identifies an object and the evidence needed to make a claim about it. The current companion supplies artifact checks and fresh-process predictions. An actual container run must add image, user and runtime evidence; device qualification adds hardware and workload measurements. Do not fill later rows with results from an earlier boundary.
Architecture and dataflow mechanism diagram.

Each row identifies an object and the evidence needed to make a claim about it. The current companion supplies artifact checks and fresh-process predictions. An actual container run must add image, user and runtime evidence; device qualification adds hardware and workload measurements. Do not fill later rows with results from an earlier boundary.

Open full-size diagram
Gate(B)=I ⁣[ϵparity≤τ  ∧  Lp95≤LSLO  ∧  hash(B)=hmanifest]\text{Gate}(\mathcal{B}) = \mathbb{I}\!\left[\epsilon_{\text{parity}} \le \tau \;\wedge\; L_{p95} \le L_{\text{SLO}} \;\wedge\; \text{hash}(\mathcal{B}) = h_{\text{manifest}}\right]

Separate training from inference

Our service evaluates an exported scalar affine model using the Python standard library. The connected lab actually trains that model in JAX first. Inference does not need the optimizer, training data or MLflow server. This small portable contract makes it possible to compare Python-process, MLflow pyfunc and container outputs independently. Larger JAX exports need a compatible runtime and their own serialization checks; copying two coefficients does not demonstrate arbitrary graph portability.

Understand the build context

Docker only sees the build context sent to the engine. The supplied .dockerignore excludes everything except Dockerfile and service.py, so local data, notebooks, environment files and training artifacts do not enter the image accidentally. COPY names only the service source. The base image is pinned by its observed digest, not just a moving tag. Pinning records an identity; you still need a deliberate dependency update process and image scanning.

Keep runtime inputs explicit

MODEL_PATH selects a mounted artifact and MODEL_SHA256 verifies its bytes before serving. Configuration can be supplied at runtime; credentials belong in a runtime secret mechanism rather than build arguments or image layers. The container runs as a numeric non-root user, with a read-only filesystem and model mount. The default network-free check needs no published port. The optional HTTP mode binds a service port, enforces a small request-body and batch limit, and exposes readiness. It is a teaching server, not a production concurrency stack.

Prove the same request survives the boundary

The CPU companion writes an artifact and starts a fresh Python process for every request. Singleton and changed-size batches must match independent arithmetic. An empty batch, boolean input and changed artifact digest are rejected. These are process checks; the connected Docker checker additionally builds the actual image, mounts the artifact, checks non-root execution, performs inference and tests readiness inside the running container. Keep those two evidence scopes distinct.

Train and retain the model first

bash
# Run train and retain the model first using the course Python environment
python projects/engineering-release/mlflow_lab.py --output ./engineering-run

Expected:

Creates model.json and report.json using actual tracked training.

Build the local teaching image

bash
# Build the local teaching image
docker build -t jaxpathways-engineering:course projects/engineering-release

Expected:

Build succeeds from the pinned base digest and copies only the inference service.

Run the container qualification lab

bash
# Run run the container qualification lab using the course Python environment
python projects/engineering-release/container_check.py --model ./engineering-run/model.json --output ./container-report.json

Expected:

Checks actual container predictions, digest rejection, non-root identity and HTTP readiness. Requires a running local Docker engine.

Readiness, liveness and startup are different

A process can be alive while its model failed to load. Readiness should require a verified model and a completed smoke prediction; liveness asks whether the service is stuck. Docker HEALTHCHECK reports health but does not itself implement a rollout controller or restart policy. A Kubernetes startup probe can protect a slow load, readiness can remove an instance from service traffic, and liveness may restart it. Benchmark cold startup separately from warmed inference, then add concurrency and overload limits.

Ship the artifact you actually checked

CI should run tests, build once, record the resulting image identity, verify the model and request contract, and attach evaluation evidence. Promote that immutable bundle into staging before selecting production. Rebuilding from a tag at deployment time can change the environment after testing. On accelerators, also verify drivers, runtime libraries, device discovery and kernel support. Even a passing CPU container receipt would not establish GPU or edge qualification.

Matching bytes and valid model state are separate checks

A digest answers whether the bytes match an expected artifact. It does not answer whether those bytes encode a valid model. The service therefore checks the model's schema, version and finite coefficients after checking its digest. A valid artifact can still be the wrong model for a task, so the known request is a third check.

Change the weight from 22 to 33 without updating the expected digest: loading should fail. Update the digest as well: the artifact can now load, but the old expected scores must fail. This separates artifact identity from behavioral compatibility. A digest by itself is also not proof of who approved an artifact; record the approval and provenance separately.

Observe the process boundary before containerizing it

Predict before looking: Should invalid requests produce a prediction or a failed process?

Two local process prediction cases pass and three invalid cases are rejected.
Plot from a recorded CPU computation · JAX 0.9.2 · 1 CPU device

Read the figure

The horizontal categories separate the two valid request shapes from three deliberately invalid boundaries. Bar height is the number of cases observed by the local subprocess checker: 22 successful prediction cases and 33 expected rejections. These counts do not represent throughput, latency or a failure probability. Each success is also checked against independent affine arithmetic.

Connect it to the computation

A container must preserve this contract. The Docker lab supplies additional engine, user and readiness evidence; this plot comes from ordinary Python processes and cannot prove image behavior by itself.

The notebook contains the data experiment, plotting code, and recorded figure output. Rerun it to test your changes.

Stage 2 of 5 · Build & Run the ReferenceStep-by-step code construction & verified execution output

Where to put & run this code: Work in phases/15-deployment/07-containerize-a-model-service/code/main.py (or downloaded exercises/deployment-07.py / exercises/deployment-07.ipynb) and execute from the repository root with python3 phases/15-deployment/07-containerize-a-model-service/code/main.py.

1 Freeze a tiny model independently of training

Create main.py in your lesson workspace and run it with the active course Python environment. Write an inference artifact with a schema, one coefficient and one bias.

main.py · append build step 1
# Step 1 — Freeze a tiny model independently of training: The independent probe computes 2\times2+1=5.
import hashlib, json, os, subprocess, sys, tempfile
from pathlib import Path
MODEL = {'schema': 1, 'weight': 2., 'bias': 1.}

assert MODEL['weight']*2+MODEL['bias']==5

Code building blocks in this step

  • assert condition — Verify that the observed output shape, status, or numerical value satisfies the contract.

The independent probe computes 2×2+1=52\times2+1=5. No training library is needed to state this model.

2 Write the service boundary

Append this block to the same main.py and rerun the whole file. Define the separate service program. Read the order: digest, model schema, request validation, prediction and JSON encoding.

main.py · append build step 2
# The deployment contract uses exported coefficients, not a training environment.
SERVICE = '''import hashlib,json,math,os,sys
raw=open(os.environ['MODEL_PATH'],'rb').read()
if hashlib.sha256(raw).hexdigest()!=os.environ['MODEL_SHA256']:
    raise ValueError('artifact mismatch')
m=json.loads(raw)
if not isinstance(m,dict) or set(m)!={'schema','weight','bias'}:
    raise ValueError('model schema')
if type(m['schema']) is not int or m['schema']!=1:
    raise ValueError('model schema version')
if any(type(m[k]) not in (int,float) or not math.isfinite(m[k]) for k in ('weight','bias')):
    raise ValueError('invalid coefficient')
x=json.load(sys.stdin)['inputs']
if not isinstance(x,list) or not 1<=len(x)<=32:
    raise ValueError('batch limit')
if any(type(v) not in (int,float) or not math.isfinite(v) for v in x):
    raise ValueError('invalid input')
print(json.dumps({'predictions':[m['weight']*v+m['bias'] for v in x]},allow_nan=False))
'''

Code building blocks in this step

  • open(...) — Call open with your updated parameters or inputs from this lesson's workspace.
  • read(...) — Call read with your updated parameters or inputs from this lesson's workspace.

The string is source code for a new process. This step defines it; the next step actually executes it. Notice that booleans are rejected even though Python treats bool as an integer subclass.

3 Launch fresh processes and check both outcomes

Append this block to the same main.py and rerun the whole file. Materialize the artifact and program, then send valid and invalid requests.

main.py · append build step 3
# Step 3 — Launch fresh processes and check both outcomes: Two valid batches pass and three deliberately invalid cases fail.
accepted, rejected = 0, 0
# Create an isolated temporary directory to run and inspect artifacts safely:
with tempfile.TemporaryDirectory(prefix='container-contract-') as folder:
    root = Path(folder)
    artifact = root / 'model.json'
    service = root / 'service.py'
    artifact.write_text(json.dumps(MODEL, sort_keys=True))
    service.write_text(SERVICE)
    # Compute deterministic cryptographic digest `sha` for provenance verification.
    sha = hashlib.sha256(artifact.read_bytes()).hexdigest()
    # Configure environment variable before initializing the runtime.
    env = dict(os.environ, MODEL_PATH=str(artifact), MODEL_SHA256=sha)
    for values in [[-.5], [-2., 0., 1.5]]:
        completed = subprocess.run([sys.executable, str(service)], input=json.dumps({'inputs': values}), text=True, capture_output=True, env=env, check=True)
        actual = json.loads(completed.stdout)['predictions']
        assert actual == [2 * value + 1 for value in values]
        # Accumulate the next contribution into `accepted`.
        accepted += 1
    for payload, changes in [({'inputs': []}, {}), ({'inputs': [True]}, {}), ({'inputs': [0.]}, {'MODEL_SHA256': '0' * 64})]:
        completed = subprocess.run([sys.executable, str(service)], input=json.dumps(payload), text=True, capture_output=True, env=dict(env, **changes))
        # Assert that the computed values satisfy the numerical and structural contract.
        # Accumulate the next contribution into `rejected`.
        assert completed.returncode != 0
        rejected += 1
print('Fresh-process valid requests:', accepted, 'rejected boundaries:', rejected)
print('This companion tests the process/artifact contract. Run the Docker lab for container evidence.')

Code building blocks in this step

  • tempfile.TemporaryDirectory(...) — Call tempfile.TemporaryDirectory with your updated parameters or inputs from this lesson's workspace.
  • Path(...) — Call Path with your updated parameters or inputs from this lesson's workspace.
  • assert condition — Verify that the observed output shape, status, or numerical value satisfies the contract.

Two valid batches pass and three deliberately invalid cases fail. These are process checks. Use the separate Docker lab to gather image and runtime evidence.

View the cumulative build in main.py

These blocks append in order to the same file.

main.py · cumulative Python build
# Step 1 — Freeze a tiny model independently of training: The independent probe computes 2\times2+1=5.
import hashlib, json, os, subprocess, sys, tempfile
from pathlib import Path
MODEL = {'schema': 1, 'weight': 2., 'bias': 1.}

assert MODEL['weight']*2+MODEL['bias']==5

# The deployment contract uses exported coefficients, not a training environment.
SERVICE = '''import hashlib,json,math,os,sys
raw=open(os.environ['MODEL_PATH'],'rb').read()
if hashlib.sha256(raw).hexdigest()!=os.environ['MODEL_SHA256']:
    raise ValueError('artifact mismatch')
m=json.loads(raw)
if not isinstance(m,dict) or set(m)!={'schema','weight','bias'}:
    raise ValueError('model schema')
if type(m['schema']) is not int or m['schema']!=1:
    raise ValueError('model schema version')
if any(type(m[k]) not in (int,float) or not math.isfinite(m[k]) for k in ('weight','bias')):
    raise ValueError('invalid coefficient')
x=json.load(sys.stdin)['inputs']
if not isinstance(x,list) or not 1<=len(x)<=32:
    raise ValueError('batch limit')
if any(type(v) not in (int,float) or not math.isfinite(v) for v in x):
    raise ValueError('invalid input')
print(json.dumps({'predictions':[m['weight']*v+m['bias'] for v in x]},allow_nan=False))
'''

# Step 3 — Launch fresh processes and check both outcomes: Two valid batches pass and three deliberately invalid cases fail.
accepted, rejected = 0, 0
# Create an isolated temporary directory to run and inspect artifacts safely:
with tempfile.TemporaryDirectory(prefix='container-contract-') as folder:
    root = Path(folder)
    artifact = root / 'model.json'
    service = root / 'service.py'
    artifact.write_text(json.dumps(MODEL, sort_keys=True))
    service.write_text(SERVICE)
    # Compute deterministic cryptographic digest `sha` for provenance verification.
    sha = hashlib.sha256(artifact.read_bytes()).hexdigest()
    # Configure environment variable before initializing the runtime.
    env = dict(os.environ, MODEL_PATH=str(artifact), MODEL_SHA256=sha)
    for values in [[-.5], [-2., 0., 1.5]]:
        completed = subprocess.run([sys.executable, str(service)], input=json.dumps({'inputs': values}), text=True, capture_output=True, env=env, check=True)
        actual = json.loads(completed.stdout)['predictions']
        assert actual == [2 * value + 1 for value in values]
        # Accumulate the next contribution into `accepted`.
        accepted += 1
    for payload, changes in [({'inputs': []}, {}), ({'inputs': [True]}, {}), ({'inputs': [0.]}, {'MODEL_SHA256': '0' * 64})]:
        completed = subprocess.run([sys.executable, str(service)], input=json.dumps(payload), text=True, capture_output=True, env=dict(env, **changes))
        # Assert that the computed values satisfy the numerical and structural contract.
        # Accumulate the next contribution into `rejected`.
        assert completed.returncode != 0
        rejected += 1
print('Fresh-process valid requests:', accepted, 'rejected boundaries:', rejected)
print('This companion tests the process/artifact contract. Run the Docker lab for container evidence.')

Run the example

python3 phases/15-deployment/07-containerize-a-model-service/code/main.py
About this command

This shortcut uses an activated macOS/Linux course environment in the repository folder. For Windows, use the explicit interpreter command in “Setup help” above. The script includes worked examples and reference solutions.

Inspect the complete assembled example
main.py · complete Python example
# Containerize a model service and verify its boundary: Containerization packages a runtime environment; a running...
import hashlib, json, os, subprocess, sys, tempfile
from pathlib import Path
MODEL = {'schema': 1, 'weight': 2., 'bias': 1.}
# The deployment contract uses exported coefficients, not a training environment.
SERVICE = '''import hashlib,json,math,os,sys
raw=open(os.environ['MODEL_PATH'],'rb').read()
if hashlib.sha256(raw).hexdigest()!=os.environ['MODEL_SHA256']:
    raise ValueError('artifact mismatch')
m=json.loads(raw)
if not isinstance(m,dict) or set(m)!={'schema','weight','bias'}:
    raise ValueError('model schema')
if type(m['schema']) is not int or m['schema']!=1:
    raise ValueError('model schema version')
if any(type(m[k]) not in (int,float) or not math.isfinite(m[k]) for k in ('weight','bias')):
    raise ValueError('invalid coefficient')
x=json.load(sys.stdin)['inputs']
if not isinstance(x,list) or not 1<=len(x)<=32:
    raise ValueError('batch limit')
if any(type(v) not in (int,float) or not math.isfinite(v) for v in x):
    raise ValueError('invalid input')
print(json.dumps({'predictions':[m['weight']*v+m['bias'] for v in x]},allow_nan=False))
'''
accepted, rejected = 0, 0
# Create an isolated temporary directory to run and inspect artifacts safely:
with tempfile.TemporaryDirectory(prefix='container-contract-') as folder:
    root = Path(folder)
    artifact = root / 'model.json'
    service = root / 'service.py'
    artifact.write_text(json.dumps(MODEL, sort_keys=True))
    service.write_text(SERVICE)
    # Compute deterministic cryptographic digest `sha` for provenance verification.
    sha = hashlib.sha256(artifact.read_bytes()).hexdigest()
    # Configure environment variable before initializing the runtime.
    env = dict(os.environ, MODEL_PATH=str(artifact), MODEL_SHA256=sha)
    for values in [[-.5], [-2., 0., 1.5]]:
        completed = subprocess.run([sys.executable, str(service)], input=json.dumps({'inputs': values}), text=True, capture_output=True, env=env, check=True)
        actual = json.loads(completed.stdout)['predictions']
        assert actual == [2 * value + 1 for value in values]
        # Accumulate the next contribution into `accepted`.
        accepted += 1
    for payload, changes in [({'inputs': []}, {}), ({'inputs': [True]}, {}), ({'inputs': [0.]}, {'MODEL_SHA256': '0' * 64})]:
        completed = subprocess.run([sys.executable, str(service)], input=json.dumps(payload), text=True, capture_output=True, env=dict(env, **changes))
        # Assert that the computed values satisfy the numerical and structural contract.
        # Accumulate the next contribution into `rejected`.
        assert completed.returncode != 0
        rejected += 1
print('Fresh-process valid requests:', accepted, 'rejected boundaries:', rejected)
print('This companion tests the process/artifact contract. Run the Docker lab for container evidence.')

Expected result: Fresh-process valid requests: 2 rejected boundaries: 3 This companion tests the process/artifact contract. Run the Docker lab for container evidence.

Recorded reference execution

Reference output captured from executing the complete lesson script (code/main.py) and its assertions in the course CPU environment.

2026-10-20T10:50:42.914381+00:00 · Python 3.14.3 · JAX 0.9.2 · 1 CPU device

Fresh-process valid requests: 2 rejected boundaries: 3
This companion tests the process/artifact contract. Run the Docker lab for container evidence.
Fresh-process valid requests: 2 rejected boundaries: 3
This companion tests the process/artifact contract. Run the Docker lab for container evidence.
Independent three-input predictions: 0.5, 2.5, 5.0
Matching digest did not bypass model validation.
Changed artifact requires a new recorded digest.
Nonfinite input rejected before output.
New artifact loads, but the old behavioral expectation no longer passes.
PASS: deployment-07

Download execution record · The notebook includes executed cells and figure outputs. Compare your run with these values; timings depend on the machine.

Stage 3 of 5 · Controlled ExperimentsPredict before running & test boundary conditions

Change the batch without changing the model

Change the batch without changing the model · Python experiment
# Experiment — Change the batch without changing the model: Batching changes transport shape, not the per-observation...
batch=[-.25,.75,2.]
assert [MODEL['weight']*x+MODEL['bias'] for x in batch]==[.5,2.5,5.]
print('Independent three-input predictions: 0.5, 2.5, 5.0')

Expected: Independent predictions are 0.5, 2.5 and 5.0.

Batching changes transport shape, not the per-observation prediction contract. A stateful or preprocessing-dependent service needs additional checks.

Rehash an invalid model and watch validation reject it

Rehash an invalid model and watch validation reject it · Python experiment
# Experiment — Rehash an invalid model and watch validation reject it: Python accepts the nonstandard Infinity token in this fixture...
invalid_model = dict(MODEL, weight=float('inf'))
# Create an isolated temporary directory to run and inspect artifacts safely:
with tempfile.TemporaryDirectory() as directory:
    invalid_path = Path(directory)/'model.json'
    runner_path = Path(directory)/'service.py'
    # Write the serialized artifact payload to disk.
    invalid_path.write_text(json.dumps(invalid_model))
    runner_path.write_text(SERVICE)
    # Compute deterministic cryptographic digest `matching_digest` for provenance verification.
    matching_digest = hashlib.sha256(invalid_path.read_bytes()).hexdigest()
    # Configure environment variable before initializing the runtime.
    invalid_result = subprocess.run([sys.executable,str(runner_path)], input=json.dumps({'inputs':[0.]}), text=True,capture_output=True,env=dict(os.environ,MODEL_PATH=str(invalid_path),MODEL_SHA256=matching_digest),timeout=30)
    assert invalid_result.returncode != 0 and 'invalid coefficient' in invalid_result.stderr
print('Matching digest did not bypass model validation.')

Expected: The service rejects the nonfinite coefficient after the digest check passes.

Python accepts the nonstandard Infinity token in this fixture JSON; the explicit numerical contract still rejects it. This test is about process behavior, not evidence that a Docker image was built or exercised.

Stage 4 of 5 · Hands-On Exercise & CheckpointModify the code yourself, solve transfer tasks & test your understanding
Hands-On Exercise · Modify & Verify Run in the declared lesson environment

Make it yours

Where to put your code: Append your solution at the bottom of phases/15-deployment/07-containerize-a-model-service/code/main.py (or inside the Make it yours cell of exercises/deployment-07.ipynb) and run python3 phases/15-deployment/07-containerize-a-model-service/code/main.py to verify your prediction before opening the reference solution.

Reject an artifact whose bytes changed after its expected digest was recorded.

Show a writing guide and starter scaffold

How to write this exercise code: step-by-step recipe & starter scaffold

1. Key functions & syntax to use:

  • json.dumps(...) — Call json.dumps with your updated parameters or inputs from this lesson's workspace.
  • encode(...) — Call encode with your updated parameters or inputs from this lesson's workspace.
  • assert condition — Verify that the observed output shape, status, or numerical value satisfies the contract.

2. Step-by-step implementation plan:

  1. Set up the changed input or configuration parameters requested in the prompt.
  2. Run the target function or transformation and assert that the output matches your prediction.
Starter code scaffold · copy into your file & fill in the TODOs
# Exercise solution: Reject an artifact whose bytes changed after its expected digest was...
original = json.dumps(...)  # TODO: compute original
mutated = json.dumps(...)  # TODO: compute mutated
assert hashlib.sha256(original).hexdigest()  # TODO: complete assertion check
print('Changed artifact requires a new recorded digest.')
Reveal the reference solution
Reference solution
# Exercise solution: Reject an artifact whose bytes changed after its expected digest was...
original=json.dumps(MODEL,sort_keys=True).encode()
mutated=json.dumps(dict(MODEL,bias=2.),sort_keys=True).encode()
assert hashlib.sha256(original).hexdigest()!=hashlib.sha256(mutated).hexdigest()
print('Changed artifact requires a new recorded digest.')

Reject a nonfinite request

transfer

Send an infinite input to the process contract and verify the rejection.

Show a writing guide and starter scaffold

How to write this practice solution: step-by-step recipe & starter scaffold

1. Key functions & syntax to use:

  • request(...) — Call request with your updated parameters or inputs from this lesson's workspace.
  • tempfile.TemporaryDirectory(...) — Call tempfile.TemporaryDirectory with your updated parameters or inputs from this lesson's workspace.
  • assert condition — Verify that the observed output shape, status, or numerical value satisfies the contract.

2. Step-by-step implementation plan:

  1. Reject a nonfinite request (transfer): A request can parse as JSON in Python while still violating...
  2. Create an isolated temporary directory to run and inspect artifacts safely:
  3. Configure environment variable before initializing the runtime.
Starter code scaffold · copy into your file & fill in the TODOs
# Reject a nonfinite request (transfer): A request can parse as JSON in Python while still violating...
# Create an isolated temporary directory to run and inspect artifacts safely:
with tempfile.TemporaryDirectory() as tmp:
    p = Path(...)  # TODO: compute p
    (p/'model').write_text(json.dumps(MODEL))
    (p/'service.py').write_text(SERVICE)
    # Configure environment variable before initializing the runtime.
    env = dict(...)  # TODO: compute env
    bad = subprocess.run(...)  # TODO: compute bad
    assert bad.returncode  # TODO: complete assertion check
print('Nonfinite input rejected before output.')
Show a hint

The validation boundary should reject before producing JSON predictions.

Reveal reference solution and reasoning
Reject a nonfinite request · Python solution
# Reject a nonfinite request (transfer): A request can parse as JSON in Python while still violating...
# Create an isolated temporary directory to run and inspect artifacts safely:
with tempfile.TemporaryDirectory() as tmp:
    p=Path(tmp)
    (p/'model').write_text(json.dumps(MODEL))
    (p/'service.py').write_text(SERVICE)
    # Configure environment variable before initializing the runtime.
    env=dict(os.environ,MODEL_PATH=str(p/'model'),MODEL_SHA256=hashlib.sha256((p/'model').read_bytes()).hexdigest())
    bad=subprocess.run([sys.executable,str(p/'service.py')],input=json.dumps({'inputs':[float('inf')]}),text=True,capture_output=True,env=env)
    assert bad.returncode != 0
print('Nonfinite input rejected before output.')

A request can parse as JSON in Python while still violating the numerical service contract.

Check changed behavior after a legitimate artifact replacement

Transfer / diagnosis

Create an artifact with weight 33, keep bias 11, and use its new matching digest. Check the result for input 22, then compare with the previous model.

Show a writing guide and starter scaffold

How to write this practice solution: step-by-step recipe & starter scaffold

1. Key functions & syntax to use:

  • replacement(...) — Call replacement with your updated parameters or inputs from this lesson's workspace.
  • tempfile.TemporaryDirectory(...) — Call tempfile.TemporaryDirectory with your updated parameters or inputs from this lesson's workspace.
  • assert condition — Verify that the observed output shape, status, or numerical value satisfies the contract.

2. Step-by-step implementation plan:

  1. Create an isolated temporary directory to run and inspect artifacts safely:
  2. Compute deterministic cryptographic digest digest for provenance verification.
  3. Configure environment variable before initializing the runtime.
Starter code scaffold · copy into your file & fill in the TODOs
# Check changed behavior after a legitimate artifact replacement (Transfer / diagnosis): The old model produced 5; the new one produces 7.
# Create an isolated temporary directory to run and inspect artifacts safely:
with tempfile.TemporaryDirectory() as directory:
    model_path = Path(...)  # TODO: compute model_path
    runner_path = Path(...)  # TODO: compute runner_path
    model_path.write_text(json.dumps(dict(MODEL,weight = ...  # TODO: compute model_path.write_text(json.dumps(dict(MODEL,weight
    runner_path.write_text(SERVICE)
    # Compute deterministic cryptographic digest `digest` for provenance verification.
    digest = hashlib.sha256(...)  # TODO: compute digest
    # Configure environment variable before initializing the runtime.
    changed = subprocess.run(...)  # TODO: compute changed
    values = json.loads(...)  # TODO: compute values
    assert values  # TODO: complete assertion check
print('New artifact loads, but the old behavioral expectation no longer passes.')
Show a hint

A legitimate new digest permits loading. Independent expected predictions decide whether the changed behavior is acceptable.

Reveal reference solution and reasoning
Check changed behavior after a legitimate artifact replacement · Python solution
# Check changed behavior after a legitimate artifact replacement (Transfer / diagnosis): The old model produced 5; the new one produces 7.
# Create an isolated temporary directory to run and inspect artifacts safely:
with tempfile.TemporaryDirectory() as directory:
    model_path=Path(directory)/'model.json'
    runner_path=Path(directory)/'service.py'
    model_path.write_text(json.dumps(dict(MODEL,weight=3.)))
    runner_path.write_text(SERVICE)
    # Compute deterministic cryptographic digest `digest` for provenance verification.
    digest=hashlib.sha256(model_path.read_bytes()).hexdigest()
    # Configure environment variable before initializing the runtime.
    changed=subprocess.run([sys.executable,str(runner_path)],input=json.dumps({'inputs':[2.]}),text=True,capture_output=True,check=True,env=dict(os.environ,MODEL_PATH=str(model_path),MODEL_SHA256=digest),timeout=30)
    values=json.loads(changed.stdout)['predictions']
    assert values==[7.] and values!=[5.]
print('New artifact loads, but the old behavioral expectation no longer passes.')

The old model produced 55; the new one produces 77. Neither a successful build nor a matching digest decides whether that change is desirable. Retain both identities and compare the release against the intended task criteria.

Check your understanding

The image built successfully. What should happen before serving traffic?

Choose one answer

Diagnose the result

A missing model mount is a runtime configuration error, not a reason to rebuild the model into every image. Permission errors call for mount/user inspection. A successful local process check does not prove Docker is running. Keep engine and target failures in their own receipt.

Stage 5 of 5 · Keep Your EvidenceRecord your verification receipt & carry takeaways forward

Carry forward

  • Separate training from inference
  • Prove the same request survives the boundary
  • Ship the artifact you actually checked

Keep your evidence

Download evidence template

Keep the artifact digest, valid and rejected requests, fresh-process output and, when Docker is available, the actual image identity, runtime user and container qualification receipt.

Save your modified code, its output, and an explanation of your prediction. Checkpoint answers and exercise evidence are tracked separately.

Saved in this browser. Export or back up your portfolio anytime in My learning.

Save work for this lesson

Verified on local CPU. Stage to a Cloud TPU VM via Cloud TPU setup to record accelerator execution receipts (TODO: add automated Cloud TPU CI execution receipts).

Primary references