AfriLink SDK
Version: 0.9.2
One-line access to GPUs for training, finetuning, and inference — from any notebook.
pip install afrilink-sdk
Works in: Google Colab · Kaggle · Jupyter · VS Code · any Python 3.8+ environment
Contents
- 60-second quickstart
- Authentication
- Which method should I use?
- Backend (k3s)
- Guides by task — Pretrain · Finetune · Generate · Custom containers
- Working with your output model
- API reference
- Hardware & billing
- Model & dataset registry
- Troubleshooting
- Built-in help
60-second quickstart
This example uses the default k3s backend — no environment variable needed.
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"])
Every other capability (finetune, generate, custom containers) follows this 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
- Sign up at dataspires.com.
- Profile → AfriLink SDK keys → Create new key. Copy the
afk_live_…value — it's shown once. - 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 secret / env / argument, in that order
What happens at auth time
Your API key is exchanged at api.dataspires.com for a short-lived Supabase JWT used for billing writes. The 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 |
| Need a custom framework/version not curated | client.build_and_train() |
image spec + script |
Rule of thumb: train from zero →
pretrain(). Adapt existing weights →finetune(). Frozen answers →generate(). Need a bespoke environment →build_and_train().
client.train(), adapt(), and initialize() are deprecated in 0.9 and will be removed in 1.0.0.
Backend (k3s)
The pilot and student path uses the DataSpires K3s GPU cluster (default). You do not need to set AFRILINK_BACKEND.
k3s (default) |
|
|---|---|
| How to select | Default — nothing to set |
| What it targets | DataSpires K3s GPU cluster (multi-node, multi-GPU capable) |
| Job model | Async by default — submit, then poll or list jobs, even from a new session |
| File I/O | S3-compatible presigned URLs |
| Output retrieval | Sync from /workspace/job/output/ — see K3s output retrieval |
| Billing | Not yet wired (billing: None in result) |
| Multi-GPU | Supported by the cluster |
Guides by task
Pretrain
(train from scratch — YOLO, CNN, or transformer)
Image classification:
job = client.pretrain(
kind="image-classify",
model="resnet18",
data="./images/",
params={"epochs": 5, "num_classes": 2},
gpus=1,
)
result = job.run(wait=True)
YOLO detection:
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)
Curated containers resolved from kind=:
kind prefix |
Container | Frameworks |
|---|---|---|
yolo-* |
afrilink-yolo |
Ultralytics, PyTorch, torchvision |
image-classify |
afrilink-vision |
PyTorch, torchvision |
| transformer/ViT | afrilink-pretrain |
Transformers, accelerate |
Need a stack outside these? → Custom containers.
K3s output retrieval
On k3s, your script writes to /workspace/job/output/; the orchestrator syncs it to S3 on completion.
from pathlib import Path
job.k8s_runner.download_output(job.job_id, Path("./my-outputs"))
Requires a deployed orchestrator exposing GET /api/v1/jobs. Full spec: dataspires.com/docs.
Finetune
(LoRA/QLoRA adaptation of an LLM/VLM — runs in the 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 — see table below
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")
| 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= (e.g. captioning / VQA) with image+text data. See afrilink/finetune in the 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)
Custom containers
For frameworks or model versions the curated containers don't cover.
spec = dict(
base_image="pytorch", # preset — see table below
pip_packages=["transformers>=4.45", "accelerate>=0.34", "peft>=0.13"],
apt_packages=["git"],
model_source={"kind": "huggingface", "id": "Qwen/Qwen2.5-0.5B-Instruct"},
)
# check cache first — avoids a ~5 min rebuild for a spec you've already built
hit = client.find_existing_image(**spec)
result = client.build_and_train(
**spec,
script="my_train.py",
gpus=1,
time_limit_hours=0.5,
reuse_existing_image=True, # default
)
client.download_model(result["run"]["job_id"], "./output")
base_image presets:
| Preset | Resolves to | Notes |
|---|---|---|
pytorch |
pytorch/pytorch:2.5.0-cuda12.4-cudnn9-runtime |
GPU default |
pytorch-2.4 |
pytorch/pytorch:2.4.0-cuda12.4-cudnn9-runtime |
|
pytorch-cpu |
pytorch/pytorch:2.5.0-cpu-runtime |
CPU-only, smaller |
cuda-12.4 |
nvidia/cuda:12.4.0-runtime-ubuntu22.04 |
bring-your-own-Python |
ultralytics |
ultralytics/ultralytics:latest |
YOLOv8 ready |
model_source kinds: huggingface (id, optional revision/subfolder), url, git, gs, s3, or omit to load the model yourself in-script. Models are fetched at runtime, not baked into the image — keeps images ~2GB instead of 7+GB. Gated HF models (Llama, Gemma) work automatically if you add HUGGINGFACE_TOKEN as a notebook secret.
Cache key includes base_image, pip_packages, apt_packages, index URLs, and model_source. It excludes script, env, extra_files, and job/user IDs — so two runs with an identical environment spec but different training scripts share a cached image.
Lifecycle: the built image lives permanently in Artifact Registry (that's what cache hits read from); the local copy on the compute node is deleted at the end of each build_and_train() call unless cleanup_image_after=False.
Working with your model
Convert to GGUF for Ollama / llama.cpp
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")
# 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
| Method | Description |
|---|---|
authenticate(api_key=None) |
Resolve key (arg / env / Colab / Kaggle secrets), exchange for session |
pretrain(kind, data, gpus, ...) |
From-scratch / recipe training job |
finetune(model, training_mode, data, gpus, ...) |
LoRA/QLoRA job in afrilink-finetune |
generate(model, data, outputs, ...) |
Frozen inference job |
find_existing_image(...) |
Check cache before a custom build |
build_image(...) |
Build a custom image only |
build_and_train(...) |
Cache-check → build (or skip) → run → cleanup |
delete_built_image(job_id_or_image) |
Remove image from the compute node's local cache |
download_model(job_id, local_dir) |
Download output/ directory |
upload_dataset(local_path, dataset_name) |
Upload to job-scoped staging |
list_containers() / list_available_models() / list_available_datasets() |
Registry lookups |
get_model_requirements(model, training_mode) |
GPU/memory recommendation |
cancel_job(job_id) |
Stop and remove a job |
list_jobs(include_completed=False) |
List your jobs |
get_job_status(job_id) |
Poll by ID — works across sessions on k3s |
get_job(job_id) |
In-session job object, if still tracked |
TrainJob / FinetuneJob / GenerateJob
Returned by pretrain() / finetune() / generate() / build_and_train().
| Member | Description |
|---|---|
run(wait=True, poll_interval=30) |
Submit; wait=True polls to completion |
cancel() |
Stop the job |
get_logs(tail=100) |
Recent log lines |
estimated_cost_usd() |
Estimate from GPUs × time limit |
status, job_id |
Current state / 8-char job ID |
k8s_runner |
Use for download_output() on k3s |
Result shape — k3s:
{"job_id": "a1b2c3d4", "status": "completed", "billing": null}
On failure, also includes "error": "<last 200 log lines>".
Async job discovery:
result = job.run(wait=False)
for entry in client.list_jobs():
print(entry["job_id"], entry["state"], entry.get("reason"))
status = client.get_job_status(result["job_id"]) # works even after a notebook restart
list_jobs(include_completed=True) includes completed 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) |
Billing: wall-clock GPU usage is charged against your DataSpires balance when billing is enabled for the job path. Invoices: dataspires.com/dashboard/billing. Cloud Build time for custom images is absorbed by the platform — you only pay for GPU time. k3s result objects currently return billing: null until cluster billing is fully wired.
Model & dataset registry
client.list_available_models() # all
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 |
Anything else → custom containers with model_source=.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Job stuck in queued |
Cluster at capacity | client.list_jobs() to check state; jobs auto-clear after 3 days if abandoned |
| Fresh build every time despite unchanged deps | A field outside the cache key changed (e.g. script) — this is expected |
Only base_image, pip_packages, apt_packages, index URLs, and model_source are hashed |
| Gated HF model fails to download | Missing HUGGINGFACE_TOKEN |
Add it as a notebook secret |
download_model() / output empty |
Script didn't write to /workspace/job/output/ |
Confirm output path in your training script; use k8s_runner.download_output() |
Built-in help
Query the inline reference manual from any cell — no internet required:
import afrilink
afrilink/help # index
afrilink/quickstart
afrilink/auth
afrilink/choose
afrilink/finetune
afrilink/pretrain
afrilink/generate
afrilink/training # deprecated train() notes
afrilink/specs
afrilink/datasets
afrilink/billing
Links
- Docs: dataspires.com/docs
- Repository / Issues: github.com/DataSpires/afrilink-sdk
- License: MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file afrilink_sdk-0.9.2.tar.gz.
File metadata
- Download URL: afrilink_sdk-0.9.2.tar.gz
- Upload date:
- Size: 164.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67a19134c27e662defdc0e223b00959699867dccdefa67a547b8150369459594
|
|
| MD5 |
44527f4e6202593af00aaaf483b62439
|
|
| BLAKE2b-256 |
68a65793cb03706bff79474521f485d70ed0598444d9f0b8cb521cde26d5308a
|
File details
Details for the file afrilink_sdk-0.9.2-py3-none-any.whl.
File metadata
- Download URL: afrilink_sdk-0.9.2-py3-none-any.whl
- Upload date:
- Size: 173.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
22ff080cd04ccfe5cf02bc6e519b35958d8415f07f5ae48a0a6a0d8be09e4536
|
|
| MD5 |
3c24b1417dbc173c641c634b3b8636c7
|
|
| BLAKE2b-256 |
c3009cb83b1ac2a272b450961991cbd4935e07a002594bc3c429f3ab6b0a28fd
|