Phase 15 / Deployment, interoperability & edge AI
Containerize a model service and verify its boundary
Revision v0.7.0 · Updated since your last visit. Review the changes; your progress is saved.
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.
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements-cpu.txtpy -3 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements-cpu.txtAfter activation, run the complete worked example from that same folder:
python phases/15-deployment/07-containerize-a-model-service/code/main.py.\.venv\Scripts\python.exe phases/15-deployment/07-containerize-a-model-service/code/main.pyFor 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.
Download this lesson’s notebook. Open it with a Python kernel using the course environment. Run cells from the top after restarting the kernel; experiments depend on earlier definitions.
For local Jupyter, first install the course requirements using the Local Python steps, then use your environment’s Python to start Jupyter:
python -m pip install jupyterlab
python -m jupyter lab.\.venv\Scripts\python.exe -m pip install jupyterlab
.\.venv\Scripts\python.exe -m jupyter labSelect the downloaded deployment-07.ipynb file in JupyterLab. The notebook contains reference solutions; make your prediction and attempt each practice task before opening them. Restart the kernel before changing logical device setup.
The worked reference for this lesson is CPU-validated. Some references explicitly configure logical CPU devices; setting JAX_PLATFORMS does not adapt them to a TPU. Follow the execution matrix and reviewed multi-host example before attempting a target run.
For accelerator preparation, study TPU setup, observed runtime receipts, checkpointed jobs and precision and retained traces. These target paths remain unqualified on physical TPU hardware until an actual receipt is recorded. Cloud provisioning is optional for CPU learning.
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.
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.
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
# Run train and retain the model first using the course Python environment
python projects/engineering-release/mlflow_lab.py --output ./engineering-runExpected:
Creates model.json and report.json using actual tracked training.
Build the local teaching image
# Build the local teaching image
docker build -t jaxpathways-engineering:course projects/engineering-releaseExpected:
Build succeeds from the pinned base digest and copies only the inference service.
Run the container qualification lab
# 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.jsonExpected:
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 to 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?
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: successful prediction cases and 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.
Open full-size figureDownload data and provenance
The notebook contains the data experiment, plotting code, and recorded figure output. Rerun it to test your changes.
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.
# 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']==5Code building blocks in this step
assert condition— Verify that the observed output shape, status, or numerical value satisfies the contract.
The independent probe computes . 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.
# 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(...)— Callopenwith your updated parameters or inputs from this lesson's workspace.read(...)— Callreadwith 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.
# 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(...)— Calltempfile.TemporaryDirectorywith your updated parameters or inputs from this lesson's workspace.Path(...)— CallPathwith 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.
# 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.pyAbout 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
# 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.
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.
Change the batch without changing the model
# 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
# 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.
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(...)— Calljson.dumpswith your updated parameters or inputs from this lesson's workspace.encode(...)— Callencodewith 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:
- Set up the changed input or configuration parameters requested in the prompt.
- Run the target function or transformation and assert that the output matches your prediction.
# 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
# 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(...)— Callrequestwith your updated parameters or inputs from this lesson's workspace.tempfile.TemporaryDirectory(...)— Calltempfile.TemporaryDirectorywith 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:
- 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:
- Configure environment variable before initializing the runtime.
# 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 (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 , keep bias , and use its new matching digest. Check the result for input , 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(...)— Callreplacementwith your updated parameters or inputs from this lesson's workspace.tempfile.TemporaryDirectory(...)— Calltempfile.TemporaryDirectorywith 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:
- Create an isolated temporary directory to run and inspect artifacts safely:
- Compute deterministic cryptographic digest
digestfor provenance verification. - Configure environment variable before initializing the runtime.
# 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 (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 ; the new one produces . 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.
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.
Carry forward
- Separate training from inference
- Prove the same request survives the boundary
- Ship the artifact you actually checked
Keep your evidence
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.
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).