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
k3sbackend 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 setAFRILINK_BACKEND.
Contents
- 60-second quickstart
- Authentication
- Which method should I use?
- Known limitations
- Backend (k3s)
- Guides by task — Pretrain · Finetune · Generate
- Working with your output model
- API reference
- Hardware & billing
- Model & dataset registry
- Troubleshooting
- 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
- 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 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(), andgenerate()taketime_limitas a stringHH:MM:SS. (Other SDK APIs outside this pilot usetime_limit_hoursas 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
- 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.3.tar.gz.
File metadata
- Download URL: afrilink_sdk-0.9.3.tar.gz
- Upload date:
- Size: 165.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4643ac79189f643639b5a323e4b2459919c258ccad1fe8228c815435b7bf1f38
|
|
| MD5 |
b33da3e409b3baf9be9f9dfa66a7f6ef
|
|
| BLAKE2b-256 |
d04d9e15b8c77ada771541732d32aa22015b4bd3e5388d7ec95b9c4d20911cc7
|
File details
Details for the file afrilink_sdk-0.9.3-py3-none-any.whl.
File metadata
- Download URL: afrilink_sdk-0.9.3-py3-none-any.whl
- Upload date:
- Size: 174.2 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 |
a881c992958d72a768bf438c107a96079a946ed8de3f2a688fa7e0db6cad3bc6
|
|
| MD5 |
0b1f3c0b82bd7aff9bb4b6bf932c6ed2
|
|
| BLAKE2b-256 |
22053a684f3ce853d1f4cad80d01a53676bd643b730dc3d1d78f928f0cace492
|