Skip to main content

AfriLink SDK

Version: 0.9.3

One-line access to GPUs for training, finetuning, and inference — from any notebook.

pip install 'afrilink-sdk>=0.9.3'

Works in: Google Colab · Kaggle · Jupyter · VS Code · any Python 3.8+ environment

Pilot scope: This documentation covers the k3s backend only (the default). Other backends exist in the full SDK but are not part of this pilot and are not documented here. You do not need to set AFRILINK_BACKEND.


Contents

  1. 60-second quickstart
  2. Authentication
  3. Which method should I use?
  4. Known limitations
  5. Backend (k3s)
  6. Guides by taskPretrain · Finetune · Generate
  7. Working with your output model
  8. API reference
  9. Hardware & billing
  10. Model & dataset registry
  11. Troubleshooting
  12. Built-in help

60-second quickstart

from afrilink import AfriLinkClient

client = AfriLinkClient()
client.authenticate(api_key="afk_live_…")  # or set AFRILINK_API_KEY

job = client.pretrain(
    kind="image-classify",
    model="resnet18",
    data="./images/",
    params={"epochs": 5, "num_classes": 2},
    gpus=1,
)
result = job.run(wait=True)
print(result["status"])  # "completed" | "failed" | "timeout" | …
if result["status"] != "completed":
    print(result.get("error") or result.get("reason"))

finetune() and generate() use the same client → job → job.run() pattern. See Guides by task.


Authentication

Auth is a single stateless API key — no passwords, no certificate refresh, no SSH key management on your side.

Get a key

  1. Sign up at dataspires.com.
  2. Profile → AfriLink SDK keys → Create new key. Copy the afk_live_… value — it's shown once.
  3. Store it as AFRILINK_API_KEY.

Set the key

Environment How
Google Colab 🔑 sidebar → Add secret → name AFRILINK_API_KEY → enable for notebook
Kaggle Add-ons → Secrets → name AFRILINK_API_KEY → attach to notebook
Local Jupyter / VS Code os.environ["AFRILINK_API_KEY"] = "afk_live_…" before authenticate()
Anywhere client.authenticate(api_key="afk_live_…")
client = AfriLinkClient()
client.authenticate()   # resolves from argument / env / notebook secret, in that order

Errors

authenticate() raises Exception (message like API key auth failed: …) on a bad or revoked key. Wrap it if you want a soft failure:

try:
    client.authenticate()
except Exception as e:
    print("Auth failed:", e)

After a successful call, the short-lived session JWT lives in memory for the kernel lifetime — nothing is written to disk. Rotate a key by revoking it on the dashboard and minting a new one.


Which method should I use?

Your goal Call You provide
Train a YOLO / CNN / transformer from scratch client.pretrain(kind=…) kind, data, optional weights= / scratch=
Fine-tune an LLM / VLM foundation model client.finetune() model, training_mode, data, optional task=
Run a model without changing weights client.generate() model, prompts / image+question manifest

Rule of thumb: train from zero → pretrain(). Adapt existing weights → finetune(). Frozen answers → generate().

Deprecated APIs (removed in 1.0.0)

Old (0.8.x) Use instead
client.train(script=…, container="afrilink-yolo", …) client.pretrain(kind="yolo-detect", …) or pretrain(script=…)
client.adapt(recipe="yolo-detect", …) client.pretrain(kind="yolo-detect", weights=…)
client.initialize(recipe=…, …) client.pretrain(kind=…, scratch=True)
# Before (deprecated)
job = client.train(script="train_yolo.py", container="afrilink-yolo", data="./data/")

# After
job = client.pretrain(kind="yolo-detect", weights="yolo11n.pt", data="./data/", data_config="data.yaml")

Known limitations

Topic Reality on this pilot
Backend k3s only in this doc. Other backends are out of scope.
build_and_train() / custom Cloud Build images Not available on k3s — those APIs require a backend this pilot does not use. Prefer curated pretrain / finetune / generate.
Result billing field Often null on k3s until cluster billing is fully wired.
estimated_cost_usd() Still returns a ceiling estimate from gpus × time_limit × $0.60/GPU-hr. It is not the same as the billing field on the result.
Concurrent jobs No hard client-side cap; the cluster schedules by capacity. Abandoned jobs are cleaned up after ~3 days.
Wall-clock max time_limit may not exceed 24 hours. Past the limit, the job is stopped (timeout / cancelled).

Backend (k3s)

k3s (default)
How to select Default — nothing to set
What it targets DataSpires K3s GPU cluster
Job model Async — submit, then poll or list_jobs(), even from a new session
File I/O S3-compatible presigned URLs
Outputs Write under /workspace/job/output/ — see K3s output retrieval
Multi-GPU Supported when the cluster has capacity

status vs state (not a typo)

Two related schemas:

API Field Example values
job.run() return dict status submitted, completed, failed, timeout, cancelled
client.list_jobs() / client.get_job_status() state same lifecycle strings; may also include queued, running, plus optional reason / message / error
result = job.run(wait=True)
print(result["status"])                 # job.run() → status

for entry in client.list_jobs():
    print(entry["job_id"], entry["state"], entry.get("reason"))

print(client.get_job_status(result["job_id"])["state"])

Guides by task

Pretrain

(YOLO, CNN, or transformer recipes — platform owns the training loop)

job = client.pretrain(
    kind="image-classify",
    model="resnet18",
    data="./images/",
    params={"epochs": 5, "num_classes": 2},
    gpus=1,
    time_limit="02:00:00",   # HH:MM:SS string — see note below
)
result = job.run(wait=True)

YOLO:

job = client.pretrain(
    kind="yolo-detect",
    weights="yolo11n.pt",    # or scratch=True for random init
    data="./dataset/",
    data_config="data.yaml",
    gpus=1,
    time_limit="02:00:00",
)
result = job.run(wait=True)

Time limits: pretrain(), finetune(), and generate() take time_limit as a string HH:MM:SS. (Other SDK APIs outside this pilot use time_limit_hours as a float — do not mix them up.)

Curated containers from kind=:

kind prefix Container Frameworks
yolo-* afrilink-yolo Ultralytics, PyTorch, torchvision
image-classify afrilink-vision PyTorch, torchvision
transformer/ViT afrilink-pretrain Transformers, accelerate

K3s output retrieval

Write artefacts to /workspace/job/output/. The orchestrator syncs that prefix on completion.

from pathlib import Path
job.k8s_runner.download_output(job.job_id, Path("./my-outputs"))

Passing data= on pretrain / finetune / generate already uploads your dataset. You only need client.upload_dataset(...) for advanced, separate staging:

remote = client.upload_dataset("./my_data/", dataset_name="assignment2")

Finetune

(LoRA/QLoRA on an LLM/VLM — afrilink-finetune container)

import pandas as pd

data = pd.DataFrame({"text": ["Below is an instruction...\n\n### Response:\n..."]})

job = client.finetune(
    model="qwen2.5-0.5b",
    training_mode="low",     # low | medium | high
    data=data,
    gpus=1,
    time_limit="01:00:00",
)
result = job.run(wait=True)
if result["status"] == "completed":
    client.download_model(result["job_id"], "./my-model")
else:
    print(result.get("error") or result.get("reason"))
Mode Strategy Quantization
low QLoRA (rank 8) 4-bit
medium LoRA (rank 16) 8-bit / none
high Full LoRA (rank 64) none

For VLMs, pass task= (captioning / VQA) with image+text data. See afrilink/finetune in Built-in help.

Generate

(frozen inference — no weight updates)

job = client.generate(
    model="smolvlm-256m",
    data="./manifest_dir/",
    outputs=["text", "confidence"],   # add "hidden_states" for analysis
    max_new_tokens=64,
    temperature=0.0,
)
result = job.run(wait=True)

Job failures (all three verbs)

job.run(wait=True) does not raise on a failed training/generate job. It returns a dict:

{
  "job_id": "a1b2c3d4",
  "status": "failed",          # or "timeout", "cancelled", …
  "error": "<log tail>",       # often present
  "reason": "…",               # optional (k3s)
  "billing": null              # often null on this pilot
}

Check result["status"] == "completed" before downloading outputs. Auth and submit-time validation errors (bad key, not authenticated, invalid gpus / time_limit) do raise.


Working with your model

Convert to GGUF for Ollama / llama.cpp

1. Merge the adapter in Python (notebook):

from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel

base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-0.5B")
model = PeftModel.from_pretrained(base, "./my-model")
merged = model.merge_and_unload()
merged.save_pretrained("./my-model-merged")
AutoTokenizer.from_pretrained("Qwen/Qwen2.5-0.5B").save_pretrained("./my-model-merged")

2. Run these in a terminal (not a notebook cell):

python convert_hf_to_gguf.py ./my-model-merged --outfile my-model.gguf --outtype f16
./llama-quantize my-model.gguf my-model-q4.gguf Q4_K_M
ollama create my-model -f Modelfile && ollama run my-model

Publish to Hugging Face Hub

from huggingface_hub import HfApi

api = HfApi(token="hf_...")
api.create_repo("your-username/my-finetuned-model", exist_ok=True)
api.upload_folder(folder_path="./my-model", repo_id="your-username/my-finetuned-model")  # adapter only

API reference

AfriLinkClient (pilot-relevant)

Method Description
authenticate(api_key=None) Resolve key; raises on failure
pretrain(kind, data, gpus, time_limit, …) Returns a job object (PretrainJob / TrainJob) — call .run()
finetune(model, training_mode, data, gpus, time_limit, …) Returns FinetuneJob — call .run()
generate(model, data, outputs, …) Returns GenerateJob — call .run()
download_model(job_id, local_dir) Download synced output/ for a completed job
upload_dataset(local_path, dataset_name=None) Optional separate staging (usually unnecessary if you pass data=)
list_containers() / list_available_models() / list_available_datasets() Registry lookups
get_model_requirements(model, training_mode) GPU/memory recommendation
cancel_job(job_id) Stop a job
list_jobs(include_completed=False) List jobs → dicts with state
get_job_status(job_id) Poll by ID → dict with state (works across sessions)
get_job(job_id) In-session job object, if still tracked

Job objects (PretrainJob / FinetuneJob / GenerateJob)

Returned by pretrain() / finetune() / generate(). Not returned by build_and_train() (that API is out of pilot scope and returns a nested dict when used on its own backend).

Member Description
run(wait=True, poll_interval=30) Submit; with wait=True, poll until done and return a result dict with status
cancel() Stop the job
get_logs(tail=100) Recent log lines
estimated_cost_usd() Ceiling estimate: GPUs × time_limit × $0.60/GPU-hr (not live billing)
status, job_id Current tracked state / job ID
k8s_runner For download_output() on k3s

Async discovery:

result = job.run(wait=False)          # {"job_id", "status": "submitted", …}
status = client.get_job_status(result["job_id"])
print(status["state"], status.get("reason"))

list_jobs(include_completed=True) includes finished jobs for up to ~3 days.


Hardware & billing

Jobs run on the DataSpires k3s GPU cluster. Prefer small models and training_mode="low" (QLoRA) for workshop / assignment budgets.

Model size Mode Fits on 1 GPU?
0.5B – 3B low / medium yes
3B – 7B low (QLoRA 4-bit) yes
7B medium (LoRA 8-bit) yes
13B low (QLoRA 4-bit) tight
30B+ low (QLoRA 4-bit) unlikely (OOM risk)

Limits: up to 24h time_limit; hard max 32 GPUs (SDK warns above 8). Past time_limit, the run stops — check for status/state of timeout or cancelled.

Billing: invoices at dataspires.com/dashboard/billing. On this pilot, result["billing"] is often null; use job.estimated_cost_usd() only as a planning ceiling.


Model & dataset registry

client.list_available_models()
client.list_available_models(size="tiny")   # tiny | small | medium | large
client.list_available_datasets()
client.get_model_requirements("qwen2.5-0.5b", "low")
ID Type Params Min VRAM
qwen2.5-0.5b text 0.5B 4 GB
gemma-3-270m text 0.27B 2 GB
llama-3.2-1b text 1.0B 4 GB
deepseek-r1-1.5b text 1.5B 6 GB
ministral-3b text 3.3B 8 GB
florence-2-base vision 0.23B 4 GB
smolvlm-256m vision 0.26B 2 GB
moondream2 vision 1.9B 8 GB
internvl2-1b vision 1.0B 4 GB
llava-1.5-7b vision 7.0B 16 GB

Troubleshooting

Symptom Likely cause Fix
API key auth failed Bad / revoked key Mint a new key; set AFRILINK_API_KEY
Job stuck in queued Cluster at capacity client.list_jobs(); wait or try later
KeyError: 'status' on list_jobs() Wrong field Use entry["state"] for list/status APIs; result["status"] only for job.run()
KeyError: 'state' on job.run() result Wrong field Use result["status"]
Gated HF model fails Missing token Add HUGGINGFACE_TOKEN notebook secret
Empty download / no artefacts Script didn't write under /workspace/job/output/ Fix output path; job.k8s_runner.download_output(...)
Job ends as timeout Hit time_limit Raise time_limit (max 24h) or reduce work

Built-in help

Prefer afrilink.docs("topic") — it always works. The slash form is also supported when the topic is a quoted string.

import afrilink

afrilink.docs("help")       # recommended
afrilink.docs("finetune")
afrilink.docs("pretrain")
afrilink.docs("generate")
afrilink.docs("choose")
afrilink.docs("billing")

# Equivalent quoted slash form (module implements __truediv__):
afrilink / "help"
afrilink / "finetune"

afrilink/help (unquoted) works only because help is a Python builtin; we map that object to the "help" page. Unquoted names like afrilink/finetune usually raise NameError unless finetune is already bound in your scope — do not copy those bare forms from older docs.


Links

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

afrilink_sdk-0.9.3.tar.gz (165.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

afrilink_sdk-0.9.3-py3-none-any.whl (174.2 kB view details)

Uploaded Python 3

Release history Release notifications | RSS feed

0.9.4

2 files

This release

0.9.3 This release

2 files

0.9.2

2 files

0.9.1

2 files

0.9.0

2 files

0.8.28

2 files

0.8.27

2 files

0.8.26

2 files

0.8.25

2 files

0.8.24

2 files

0.8.23

2 files

0.8.22

2 files

0.8.21

2 files

0.8.20

2 files

0.8.19

2 files

0.8.18

2 files

0.8.17

2 files

0.8.16

2 files

0.8.15

2 files

0.8.14

2 files

0.8.13

2 files

0.8.12

2 files

0.8.11

2 files

0.8.10

2 files

0.8.9

2 files

0.8.8

2 files

0.8.7

2 files

0.8.6

2 files

0.8.5

2 files

0.8.4

2 files

0.8.3

2 files

0.8.2

2 files

0.8.1

2 files

0.8.0

2 files

0.7.5

2 files

0.7.4

2 files

0.7.3

2 files

0.7.2

2 files

0.7.1

2 files

0.7.0

2 files

0.6.0

2 files

0.5.9

2 files

0.5.8

2 files

0.5.7

2 files

0.5.6

2 files

0.5.5

2 files

0.5.4

2 files

0.5.3

2 files

0.5.2

2 files

0.5.1

2 files

0.5.0

2 files

0.4.0

2 files

0.3.1

2 files

0.3.0

2 files

0.2.3

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

0.1.9

2 files

0.1.8

2 files

0.1.7

2 files

0.1.6

2 files

0.1.5

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page