{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://github.com/eoinjordan/westwind-pipelines/schema/westwind.schema.json","title":"Westwind pipeline","description":"A westwind.yaml file: where the data comes from, which model runs, where it runs, and the on-device mpipe graph that is turned into a Zephyr application.","type":"object","required":["apiVersion","kind","metadata","target","model"],"additionalProperties":false,"properties":{"apiVersion":{"const":"westwind/v1"},"kind":{"const":"Pipeline"},"metadata":{"type":"object","required":["name"],"additionalProperties":false,"properties":{"name":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,62}$"},"description":{"type":"string"},"labels":{"type":"object","additionalProperties":{"type":"string"}}}},"target":{"type":"object","required":["board"],"additionalProperties":false,"properties":{"board":{"type":"string","description":"Zephyr board target, for example arduino_uno_q/stm32u585xx. See catalog/boards.json."},"zephyr":{"type":"string","default":"main","description":"Zephyr revision. mpipe needs main (after v4.4.0)."},"executorch":{"type":"string","default":"v1.5.1"},"shield":{"type":"array","items":{"type":"string"}},"conf":{"type":"object","description":"Extra Kconfig symbols, written to the generated prj.conf.","additionalProperties":{"type":["string","integer","boolean"]}},"overlay":{"type":"string","description":"Path to an extra devicetree overlay."}}},"data":{"type":"array","items":{"$ref":"#/$defs/dataset"}},"model":{"$ref":"#/$defs/model"},"placement":{"$ref":"#/$defs/placement"},"federated":{"$ref":"#/$defs/federated"},"compute":{"$ref":"#/$defs/compute"},"graph":{"$ref":"#/$defs/graph"},"code":{"type":"array","description":"Hand-written sources added to the generated application.","items":{"type":"object","required":["lang","file"],"additionalProperties":false,"properties":{"lang":{"enum":["c","cpp","python","blockly"]},"file":{"type":"string"},"role":{"enum":["app","element","train","host"],"default":"app","description":"app: linked into the firmware. element: a custom mpipe element. train: a training recipe. host: runs on the edge or host side."}}}},"outputs":{"type":"array","items":{"enum":["elf","bin","hex","uf2","signed-bin","pte","header"]},"default":["elf","bin"]}},"$defs":{"dataset":{"type":"object","required":["name","source"],"additionalProperties":false,"properties":{"name":{"type":"string"},"source":{"enum":["huggingface","kaggle","edgeimpulse","url","local"]},"id":{"type":"string","description":"huggingface: owner/name. kaggle: owner/dataset. edgeimpulse: project id. url: an http(s) URL. local: a path."},"revision":{"type":"string"},"split":{"type":"string"},"category":{"enum":["training","testing","all"],"default":"all","description":"Edge Impulse data category."},"path":{"type":"string","description":"Local cache directory, relative to the pipeline file."},"secret":{"type":"string","description":"Name of the environment variable that holds the credential (HF_TOKEN, KAGGLE_KEY, EI_API_KEY). Never the credential itself."}}},"model":{"type":"object","required":["name","source","task"],"additionalProperties":false,"properties":{"name":{"type":"string","pattern":"^[a-z0-9_]+$"},"source":{"enum":["mediapipe","huggingface","edgeimpulse","builtin","file","torch"]},"id":{"type":"string","description":"mediapipe: an id from catalog/models.json. huggingface: owner/repo[:path]. edgeimpulse: project id. builtin: tiny_digit_net, anomaly_net, kws_net."},"file":{"type":"string","description":"file: a .tflite, .pte or .pt path. torch: a Python file that defines build_model()."},"task":{"enum":["classification","detection","segmentation","pose","keyword-spotting","anomaly","regression"]},"input":{"type":"object","additionalProperties":false,"properties":{"shape":{"type":"array","items":{"type":"integer","minimum":1}},"layout":{"enum":["NCHW","NHWC","NC","NFC"]},"dtype":{"enum":["float32","int8","uint8"]},"mean":{"type":"array","items":{"type":"number"}},"std":{"type":"array","items":{"type":"number"}}}},"labels":{"type":"array","items":{"type":"string"}},"quantize":{"enum":["none","int8"],"default":"none"},"train":{"type":"object","additionalProperties":false,"properties":{"recipe":{"type":"string","description":"builtin:<name> or file:<path.py>"},"dataset":{"type":"string","description":"Name of an entry in data."},"epochs":{"type":"integer","minimum":1},"batch_size":{"type":"integer","minimum":1},"lr":{"type":"number","exclusiveMinimum":0}}}}},"placement":{"type":"object","required":["mode"],"additionalProperties":false,"properties":{"mode":{"enum":["device","edge","cloud","split"],"description":"device: the model runs in the firmware. edge: frames go to a nearby Westwind node. cloud: frames go to a hosted endpoint. split: a small model on the device gates calls to edge or cloud."},"transport":{"enum":["uart","usb-cdc","http","mqtt"],"default":"uart","description":"How the device reaches the edge node. uart and usb-cdc go through westwind serve on the host."},"edge":{"type":"object","additionalProperties":false,"properties":{"url":{"type":"string","description":"Base URL of westwind serve, for example http://192.168.1.20:8765."},"runtime":{"enum":["executorch","torch","onnxruntime","litert"],"default":"torch"},"model":{"type":"string","description":"Model id the edge node runs, same forms as model.id."}}},"cloud":{"type":"object","additionalProperties":false,"properties":{"provider":{"enum":["hf-space","hf-endpoint","edgeimpulse"]},"id":{"type":"string","description":"hf-space: owner/space, for example eoinedge/sam3."},"api":{"type":"string","description":"Gradio api_name, for example /segment."},"params":{"type":"object"}}},"gate":{"type":"object","description":"split mode: when the on-device result triggers an offload call.","additionalProperties":false,"properties":{"label":{"type":"string"},"threshold":{"type":"number","minimum":0,"maximum":1},"min_interval_ms":{"type":"integer","minimum":0}}}}},"federated":{"type":"object","required":["framework"],"additionalProperties":false,"properties":{"framework":{"const":"flower"},"rounds":{"type":"integer","minimum":1,"default":3},"min_clients":{"type":"integer","minimum":1,"default":2},"strategy":{"enum":["fedavg","fedprox","fedadam"],"default":"fedavg"},"superlink":{"type":"string","description":"host[:port] of the Flower SuperLink HTTP API; the port defaults to 8000."},"deploy_after":{"type":"boolean","default":true,"description":"Export the aggregated model and rebuild the firmware when training ends."}}},"graph":{"type":"object","required":["nodes","links"],"additionalProperties":false,"properties":{"nodes":{"type":"array","minItems":1,"items":{"type":"object","required":["id","type"],"additionalProperties":false,"properties":{"id":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,30}$"},"type":{"type":"string","description":"An element type from catalog/elements.json."},"props":{"type":"object"},"ui":{"type":"object","description":"Editor-only layout, ignored by the generator.","properties":{"x":{"type":"number"},"y":{"type":"number"}}}}}},"links":{"type":"array","items":{"type":"array","prefixItems":[{"type":"string"},{"type":"string"}],"minItems":2,"maxItems":2,"description":"[from, to]. A node with two outputs (tee) is linked once per branch."}}}},"compute":{"type":"object","additionalProperties":false,"description":"Where the host-side work runs: data fetches, training, conversion, firmware builds, westwind serve and the Flower SuperLink. The firmware itself always runs on target.board.","properties":{"mode":{"enum":["local","compose","hybrid","kubernetes"],"default":"local","description":"local: the westwind CLI on this machine. compose: containers from deploy/compose.yaml on one machine. hybrid: devices, the serial bridge and Flower SuperNodes with local data on site in compose; training, firmware builds, westwind serve and the SuperLink on Kubernetes. kubernetes: everything on a cluster, for example EKS."},"image":{"type":"string","description":"Westwind runtime image. Default ghcr.io/eoinjordan/westwind:<version>."},"firmware_image":{"type":"string","description":"Image with the Zephyr workspace and SDK, used for the generate and build stages. Default the runtime image with a -firmware tag suffix."},"gpu_image":{"type":"string","description":"CUDA build of the runtime image, used for train jobs with kubernetes.gpu. Default the runtime image with a -cuda tag suffix."},"storage":{"type":"string","description":"Where jobs put their outputs: s3://bucket/prefix on Kubernetes, a directory for compose. Each run writes <storage>/<pipeline>/<request_id>/."},"kubernetes":{"type":"object","additionalProperties":false,"properties":{"context":{"type":"string","description":"kubectl context, for example the EKS cluster ARN. Default the current context."},"namespace":{"type":"string","default":"westwind"},"service_account":{"type":"string","default":"westwind","description":"Service account with access to storage, for example through EKS Pod Identity."},"region":{"type":"string","default":"eu-west-1","description":"AWS region for EKS, ECR and S3."},"gpu":{"type":"boolean","default":false,"description":"Run the train stage on a GPU node with a CUDA image."},"cpu":{"type":"string","default":"2"},"memory":{"type":"string","default":"8Gi"}}},"site":{"type":"object","additionalProperties":false,"description":"hybrid mode: what runs next to the devices.","properties":{"serial":{"type":"string","description":"Serial device of a board that offloads over WW1 lines, for example /dev/ttyACM0."},"upstream":{"type":"string","description":"URL of the westwind serve on the cluster that the site bridge forwards to."},"data_path":{"type":"string","description":"Directory with this site's training data for its Flower SuperNode."}}}}}}}