본문으로 건너뛰기

배치, 샘플, 그리고 배포

앞선 문서들에서는 하나의 실행(Run)을 학습, 평가 같은 단계(Stage)로 나누고, 그 안에서 메트릭과 아티팩트를 기록했습니다.

이 정도만으로도 모델 평가의 정확도를 묻는 질문에는 답할 수 있습니다.


하지만 실제 작업에서는 실행 전체의 최종 점수만으로 충분하지 않은 경우가 많습니다.

  • 학습 손실이 어느 에포크에서 갑자기 나빠졌나요?
  • 전체 정확도는 괜찮지만 실패한 입력은 무엇이었나요?
  • 검토한 체크포인트 중 실제로 스테이징에 올린 것은 어느 실행의 결과물인가요?

배치, 샘플, 배포는 이런 질문에 답하기 위해 실행 기록의 범위를 더 세밀하게 나누는 컨텍스트입니다.


Project
├─ Run
│ ├─ Stage
│ │ ├─ Batch
│ │ │ └─ Sample
│ │ └─ Sample
│ └─ Artifact
└─ Deployment ── optional link ──> Run / Artifact
컨텍스트쉽게 말하면기록하면 알 수 있는 것
Batch한 단계 안에서 반복 처리한 묶음어느 에포크, 파일, 청크에서 값이 변했는지
Sample직접 관측한 하나의 입력 또는 결과어떤 개별 사례가 성공하거나 실패했는지
Deployment결과물을 특정 환경에 올리거나 선택한 활동어떤 실행 또는 아티팩트가 운영 흐름으로 이어졌는지

두 가지 사용 흐름


이 문서에는 목적이 다른 두 API 흐름이 함께 나옵니다.

  • 런타임 수집
    • 관련 API : with ctx.run(...), stage.batch(...), stage.sample(...), ctx.deployment(...)
    • 모델 학습이나 평가가 진행되는 순간에 기록 또는 아티팩트 정보를 .contexta/cache/capture/*.jsonl에 남깁니다.
  • 저장된 데이터 조사
    • 관련 API : ctx.metadata_store.*.put_*, ctx.list_batches(...), ctx.list_samples(...), ctx.list_deployments(...), ctx.build_snapshot_report(...)
    • 표준 메타데이터 스토어에 저장된 실행 구조를 나중에 조회하고, 리포트 또는 진단으로 해석합니다,

배치 (Batch)


배치는 한 단계 안에서 반복되는 처리 단위를 구분합니다.

단계 전체에 하나의 값만 기록하면 학습 종료 사실은 알 수 있지만, 처리 과정 중 어느 구간에서 문제가 시작되었는지는 알기 어렵습니다.


예를 들어 CNN 학습의 train 단계가 두 에포크로 이루어져 있다면, 각 에포크를 배치로 기록할 수 있습니다.

run:digits-cnn.tiny-cnn
└─ stage:digits-cnn.tiny-cnn.train
├─ batch:digits-cnn.tiny-cnn.train.epoch-1 loss=1.472
└─ batch:digits-cnn.tiny-cnn.train.epoch-2 loss=0.618

이 기록은 단순히 최종 모델이 있다는 사실보다 더 많은 정보를 줍니다.

loss1.472에서 0.618로 내려갔다면, 이 짧은 실행에서는 학습이 진행되면서 손실이 감소했다고 읽을 수 있습니다.

반대로 특정 에포크부터 손실이 크게 오르거나 배치 상태가 failed로 남는다면, 실행 전체를 다시 보기 전에 그 구간을 먼저 조사할 수 있습니다.


배치는 다음과 같은 작업에 적합합니다.

  • 학습의 에포크 또는 미니배치
  • 데이터 변환 파이프라인의 파일 또는 청크
  • 대량 평가에서 동일한 방식으로 처리한 입력 묶음
  • 반복 호출 작업의 한 윈도우

런타임에서 배치 기록하기


실제 실행 중에는 단계에서 batch() 스코프를 열고, 그 스코프에 메트릭이나 이벤트를 기록합니다.

with ctx.run("tiny-cnn") as run:
with run.stage("train") as stage:
for epoch, loss in enumerate((1.472, 0.618), start=1):
with stage.batch(f"epoch-{epoch}") as batch:
batch.metric("loss", loss)

이때 batch.metric("loss", loss)로 생성되는 기록에는 run_ref, stage_execution_ref, batch_execution_ref가 함께 붙습니다.

그러므로 나중에 같은 이름의 loss 메트릭이 여러 개 있어도, 각각 어느 에포크에서 기록된 값인지 구분할 수 있습니다.


표준 배치 저장과 조회


이미 조사 가능한 표준 실행 구조를 구성할 때는 BatchExecution을 메타데이터 스토어에 저장할 수 있습니다.

from contexta.contract import BatchExecution

batch = BatchExecution(
batch_execution_ref="batch:my-proj.run-01.train.epoch-0",
run_ref="run:my-proj.run-01",
stage_execution_ref="stage:my-proj.run-01.train",
batch_name="epoch-0",
status="completed",
started_at="2025-01-01T00:01:00Z",
ended_at="2025-01-01T00:02:00Z",
order_index=0,
)

ctx.metadata_store.batches.put_batch_execution(batch)

배치 참조는 부모 단계 참조에 배치 이름을 붙인 형태입니다.

batch:{project}.{run}.{stage}.{batch_name}
batch:my-proj.run-01.train.epoch-0

statusopen, completed, failed, cancelled 중 하나이며, 종료된 상태인 completed, failed, cancelled에는 종료 시각이 필요합니다. order_index는 반복 순서를 보존할 때 사용합니다.


저장된 배치는 실행 전체 또는 특정 단계 범위로 조회할 수 있습니다.

batches = ctx.list_batches(
"run:my-proj.run-01",
stage_id="stage:my-proj.run-01.train",
)

for batch in batches:
print(batch.name, batch.status, batch.started_at)

예를 들어 다음 출력이 보인다면 두 학습 반복이 모두 저장되었고, 두 번째 반복은 실패한 상태로 종료되었다는 뜻입니다.

epoch-0 completed 2025-01-01T00:01:00Z
epoch-1 failed 2025-01-01T00:02:00Z

메트릭을 함께 살펴보면 실패가 발생하기 전 손실 또는 처리량이 달라졌는지 확인할 수 있습니다.

진단에서는 실패한 배치를 failed_batch 이슈로 확인할 수 있습니다.


샘플 (Sample)


샘플은 실행 중 관측한 개별 항목입니다.

배치가 평가 데이터의 한 묶음이라면, 샘플은 그 묶음 속의 한 행, 한 이미지, 한 프롬프트 응답에 해당합니다.


전체 점수는 개별 실패를 숨길 수 있습니다.

예를 들어 pass.rate=0.98이더라도, 실패한 두 요청이 중요한 안전성 질문이나 특정 고객 요청이었다면 반드시 따로 확인해야 합니다.


샘플 기록은 집계 메트릭만으로는 보이지 않는 사례를 찾아가는 출발점입니다.

샘플은 단계 바로 아래에 놓을 수도 있고, 배치 아래에 놓을 수도 있습니다.

Stage-owned sample
stage:mock-openai-eval.mock-chat-evaluation.evaluate
└─ sample:mock-openai-eval.mock-chat-evaluation.evaluate.workspace-question

Batch-owned sample
batch:iris-svm.linear-svm.evaluate.holdout-split
└─ sample:iris-svm.linear-svm.evaluate.holdout-split.first-prediction

따라서 샘플 참조 형식은 소유 범위에 따라 달라집니다.

sample:{project}.{run}.{stage}.{sample_name}
sample:{project}.{run}.{stage}.{batch_name}.{sample_name}

sample_name은 참조의 구성 요소로 사용되므로 점(.)을 포함할 수 없습니다.


런타임에서 샘플 기록하기


평가 단계에서 각 프롬프트 응답의 성공 여부와 지연 시간을 남기는 코드는 다음처럼 구성할 수 있습니다.

with ctx.run("mock-chat-evaluation") as run:
with run.stage("evaluate") as stage:
with stage.sample("workspace-question") as sample:
sample.metric("correct", 1.0, unit="ratio")
sample.metric("latency.ms", 0.42, unit="ms")
sample.event("response.received", message="Contexta stores local evidence...")

이 예제를 통해서 correct=1.0workspace-question이라는 개별 사례의 관측 결과라는 점이 기록에 남습니다.

전체 통과율은 별도의 단계 메트릭으로 남기고, 이상한 값이 보이면 샘플 기록으로 내려가 입력별 결과를 살펴보는 방식이 일반적입니다.


샘플에는 입력 또는 응답과 관련된 민감한 정보가 들어갈 수 있습니다.

표준 모델은 retention_classredaction_profile 필드를 제공합니다.

따라서 샘플을 오래 저장할지 또는 어떤 마스킹 정책이 적용되었는지에 대해서도 함께 나타낼 수 있습니다.


표준 샘플 저장과 조회


다음은 evaluate 단계에 직접 속한 샘플을 표준 메타데이터 스토어에 저장하는 예입니다.

from contexta.contract import SampleObservation

sample = SampleObservation(
sample_observation_ref="sample:my-proj.run-01.evaluate.s-0001",
run_ref="run:my-proj.run-01",
stage_execution_ref="stage:my-proj.run-01.evaluate",
sample_name="s-0001",
observed_at="2025-01-01T00:03:30Z",
retention_class="review",
redaction_profile="pii-masked",
)

ctx.metadata_store.samples.put_sample_observation(sample)

배치 안에서 관측한 샘플이라면 batch_execution_ref를 추가하고, 샘플 참조에도 배치 이름이 들어갑니다.

sample = SampleObservation(
sample_observation_ref="sample:my-proj.run-01.evaluate.holdout-split.s-0001",
run_ref="run:my-proj.run-01",
stage_execution_ref="stage:my-proj.run-01.evaluate",
batch_execution_ref="batch:my-proj.run-01.evaluate.holdout-split",
sample_name="s-0001",
observed_at="2025-01-01T00:03:30Z",
)

저장된 샘플은 실행, 단계 또는 배치 범위로 좁혀 읽을 수 있습니다.

samples = ctx.list_samples(
"run:my-proj.run-01",
batch_id="batch:my-proj.run-01.evaluate.holdout-split",
)

for sample in samples:
print(sample.name, sample.observed_at, sample.redaction_profile)

이 조회는 단순히 샘플의 개수보다도 '문제가 의심되는 샘플 묶음에서 어떤 항목을 보관했고, 민감 정보 처리가 표시되어 있는가'를 확인할 때 유용합니다.


배포 (Deployment)


배포는 실행 결과가 실제 사용 또는 승격 흐름으로 이어진 순간을 나타냅니다.

실행과 평가는 후보 결과를 만들지만, 그중 실제로 스테이징이나 운영 엔드포인트로 올라간 결과는 따로 추적해야 합니다.


예를 들어 학습 실행 세 개가 모두 좋은 정확도를 기록했더라도, 운영 이슈를 조사할 때 중요한 질문은 '그 중 무엇이 실제로 배포되었는가?'입니다.

배포 기록이 실행 또는 아티팩트 참조와 연결되어 있으면 운영 상태에서 다시 학습 근거까지 거슬러 올라갈 수 있습니다.

deployment:digits-cnn.tiny-cnn-candidate
├─ run_ref -> run:digits-cnn.tiny-cnn
└─ artifact_ref -> artifact:digits-cnn.tiny-cnn.checkpoint

배포는 실행 내부의 반복 구간이 아니라 프로젝트 수준의 활동입니다.

생성 실행과 아티팩트 연결은 선택 사항이지만, 모델 결과의 추적성을 위해 가능한 경우 함께 기록하는 편이 좋습니다.


런타임에서 배포 기록하기


런타임 수집 흐름에서는 ctx.deployment()로 배포 범위를 열고, 그 안에 배포 결정이나 상태 변경 이벤트를 기록할 수 있습니다.

with ctx.deployment("tiny-cnn-candidate", run_ref=run.ref) as deployment:
deployment.event(
"checkpoint.selected",
message="Selected trained checkpoint for review",
)

이 이벤트는 단순한 문자열 로그가 아니라, deployment_execution_ref와 선택적으로 연결된 run_ref를 가진 기록이 됩니다.

따라서 '어떤 체크포인트를 검토 대상으로 골랐는가?'와 '그 체크포인트를 만든 학습 실행은 무엇인가?'를 연결해서 읽을 수 있습니다.


표준 배포 저장과 조회


조사용 표준 데이터를 구성할 때는 DeploymentExecution을 저장합니다.

from contexta.contract import DeploymentExecution

deployment = DeploymentExecution(
deployment_execution_ref="deployment:my-proj.model-v1",
project_ref="project:my-proj",
deployment_name="model-v1",
status="completed",
started_at="2025-01-01T00:09:00Z",
ended_at="2025-01-01T00:10:00Z",
run_ref="run:my-proj.run-01",
artifact_ref="artifact:my-proj.run-01.model",
)

ctx.metadata_store.deployments.put_deployment_execution(deployment)

배포 참조는 프로젝트와 배포 이름으로 구성됩니다.

deployment:{project}.{deployment_name}
deployment:my-proj.model-v1

저장된 배포를 프로젝트 기준으로 조회하면 어떤 후보가 어느 실행과 연결되어 있는지 확인할 수 있습니다.

deployments = ctx.list_deployments("my-proj")

for deployment in deployments:
print(deployment.name, deployment.status, deployment.run_id)
model-v1 completed run:my-proj.run-01

이 출력은 model-v1이라는 배포 기록이 완료되었으며, 그 대상이 run-01의 결과와 연결되어 있다는 뜻입니다.

성능 회귀나 운영 이슈가 발생했을 때, 이 참조를 get_run_snapshot() 또는 compare_runs()에 전달해 배포 전후의 근거를 조사할 수 있습니다.


스냅샷과 진단에서 읽기


표준 메타데이터 스토어에 배치, 샘플, 배포 데이터가 저장되어 있다면 아래의 메소드들은 이를 실행 주변의 근거로 함께 조립하는 것을 돕습니다.

  • ctx.get_run_snapshot()
  • ctx.build_snapshot_report()

snapshot = ctx.get_run_snapshot("run:my-proj.run-01")

print(f"Batches: {len(snapshot.batches)}")
print(f"Samples: {len(snapshot.samples)}")
print(f"Deployments: {len(snapshot.deployments)}")

report = ctx.build_snapshot_report("run:my-proj.run-01")
for section in report.sections:
print(section.title)

예를 들어 다음 결과는 실행에 학습 배치 두 개, 검토 가능한 샘플 하나, 연결된 배포 하나가 저장되어 있으며, 리포트에도 해당 범위가 포함된다는 뜻입니다.

Batches: 2
Samples: 1
Deployments: 1
Run Summary
Stages
Artifacts
Batches
Deployments
Samples
Diagnostics
Completeness Notes

진단은 배치 또는 배포의 상태에서 즉시 조사할 가치가 있는 문제를 찾습니다.

조건심각도이슈 코드해석
BatchExecution.status == "failed"errorfailed_batch반복 처리 단위 하나가 실패했습니다.
해당 배치의 이벤트와 메트릭부터 확인합니다.
배치가 완료 또는 열림 이외의 불완전 상태로 종료됨warningincomplete_batch기록이 정상 완료로 확정되지 않았습니다.
결과를 전체 성능에 포함할지 주의해야 합니다.
DeploymentExecution.status == "failed"errorfailed_deployment실행 결과가 배포 흐름에서 실패했습니다.
운영 전달 과정에 문제가 있습니다.

진단 결과는 모델이 좋은지 나쁜지를 대신 판정하지 않습니다.

예를 들어 평가 정확도가 높더라도 배포가 실패할 수 있고, 전체 정확도가 괜찮더라도 특정 샘플만 잘못 처리될 수 있습니다.

배치, 샘플, 배포는 이런 서로 다른 종류의 문제를 결과 메트릭과 함께 읽을 수 있게 해 줍니다.


실행 가능한 런타임 수집 예제


아래 예제는 실제 작업을 수행하면서 Contexta 런타임 스코프에 관측 기록을 남기는 예입니다.

"""Train two real SVM candidates and compare their captured evaluation results."""

import pickle
from pathlib import Path

from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score, f1_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC

from contexta import Contexta
from contexta.capture import LocalJsonlSink


features, targets = load_iris(return_X_y=True)
train_x, test_x, train_y, test_y = train_test_split(
features, targets, test_size=0.3, stratify=targets, random_state=7
)
candidates = {
"linear-svm": SVC(kernel="linear"),
"rbf-svm": SVC(kernel="rbf", gamma="scale"),
}
workspace = Path(".contexta")
ctx = Contexta(workspace=str(workspace), config={"project_name": "iris-svm"})
local_sink = next(sink for sink in ctx.sinks if isinstance(sink, LocalJsonlSink))
scores = {}
run_refs = {}

for name, estimator in candidates.items():
with ctx.run(name, dataset_ref="dataset:sklearn.iris") as run:
with run.stage("train"):
model = make_pipeline(StandardScaler(), estimator)
model.fit(train_x, train_y)

with run.stage("evaluate") as stage:
predictions = model.predict(test_x)
accuracy = accuracy_score(test_y, predictions)
macro_f1 = f1_score(test_y, predictions, average="macro")
with stage.batch("holdout-split") as batch:
batch.metric("accuracy", accuracy, unit="ratio")
batch.metric("macro.f1", macro_f1, unit="ratio")
with batch.sample("first-prediction") as sample:
sample.metric("correct", float(predictions[0] == test_y[0]), unit="ratio")

model_path = workspace / "models" / f"{name}.pkl"
model_path.parent.mkdir(parents=True, exist_ok=True)
model_path.write_bytes(pickle.dumps(model))
run.register_artifact("model", str(model_path), attributes={"candidate": name})
scores[name] = accuracy
run_refs[name] = run.ref

best_name = max(scores, key=scores.get)
delta = scores["rbf-svm"] - scores["linear-svm"]
records_path = local_sink.file_path_for("RECORD").relative_to(Path.cwd())
artifacts_path = local_sink.file_path_for("ARTIFACT").relative_to(Path.cwd())

print(f"Compared runs: {run_refs['linear-svm']} vs {run_refs['rbf-svm']}")
print(f"Accuracy: {scores['linear-svm']:.3f} -> {scores['rbf-svm']:.3f}")
print(f"Delta: {delta:+.3f}")
print(f"Selected run: {run_refs[best_name]}")
print(f"Records: {records_path.as_posix()}")
print(f"Artifacts: {artifacts_path.as_posix()}")

코드를 runtime_batch_sample_deployment.py로 저장한 뒤, Contexta가 설치된 환경에서 실행하세요.

uv run runtime_batch_sample_deployment.py

실행 후에는 .contexta/cache/capture/record.jsonl에서 배치 또는 샘플 참조가 포함된 기록을 확인할 수 있습니다.

딥러닝과 LLM 예제에서는 배포 이벤트도 확인할 수 있으며, 모델 파일을 등록하는 머신러닝과 딥러닝 예제는 아티팩트 기록도 함께 생성합니다.


이 예제의 JSONL 수집 기록을 list_batches()build_snapshot_report()에서 바로 읽는 것으로 이해하면 안 됩니다.

해당 조회와 리포트 API는 표준 스토어에 저장된 조사 데이터에 대한 경로입니다.

관련 문서