AI
Innovation Hub

AI Innovation Hub · Cyberjaya, Malaysia

Building AI at the
Edge and Beyond

The Dell AI Innovation Hub in Cyberjaya builds real AI applications — not demos. From a walking-route optimiser deployed for a Penang heritage city, to a fully sovereign chat platform running on Grace Blackwell with no cloud dependency, to a voice assistant that speaks fluent Bahasa Malaysia. Every project is in production or active development.

Projects

Coolest Path

Deployed
Geospatial · XGBoost · K3s

Walking route optimiser for George Town, Penang that ranks paths by thermal comfort — not just distance. Built for Think City to make heritage-city walking bearable in the midday heat.

Network EMU

Active
NetOps · LLM · Docker

Turns raw network alerts into plain-English incident summaries with remediation steps and customer communication drafts — automatically, using a local LLM. No cloud, no manual triage.

IntelliSalesWidget

Edge / NPU
Sales AI · Qualcomm NPU · Edge

Sales assistant that knows your product catalogue and CRM data, answers in seconds, and runs entirely on a Snapdragon X Elite laptop — no cloud, no network, no data leaving the device.

Sovereign AI Stack

Live
LibreChat · LiteLLM · vLLM · RAG · Searxng

A fully sovereign AI chat platform — answers questions about the lab, searches the web live, renders diagrams and dashboards, and speaks Bahasa Malaysia. Gemma 4 26B runs on-premises, zero cloud dependency.

Nova Voice Avatar

Live
Whisper STT · Piper TTS · Gemma 4 · VAD

Walk up and speak — Nova answers in ~2 seconds, in English or Bahasa Malaysia, with no cloud. Built for kiosk and demo deployments; wake-word mode means she only responds when called.

Platform Services
Quick Links
ProjectLive URLGit RepoStackStatus
Coolest Path coolpath.lab.local coolest-path-api FastAPI · XGBoost · K3s Deployed
Network EMU network-emu FastAPI · Qwen · Docker Active
IntelliSalesWidget IntelliSalesWidget Qwen3-4B · QNN · ChromaDB Edge / NPU
Sovereign AI Stack chat.lab.local LibreChat · LiteLLM · vLLM · Gemma 4 · RAG Live
Nova Voice Avatar avatar.lab.local Whisper · Piper TTS · Mesolitica VITS · Gemma 4 Live
Searxng searxng.lab.local Anonymous metasearch (web augmentation for chat) Live
LLM API llm.lab.local LiteLLM proxy · strip-shim · OpenAI-compatible Live
Geospatial · ML

Coolest Path

AI-powered walking route optimizer for George Town, Penang. Returns Fastest, Coolest, and Balanced routes ranked by thermal comfort score.

The Problem

George Town, Penang is a UNESCO World Heritage city — popular with tourists and residents on foot. But walking its streets in the midday heat is genuinely uncomfortable, sometimes dangerous. The obvious "shortest path" from A to B ignores shade, heat-trapped alleyways, and real-time temperature. No existing navigation app accounts for thermal comfort.

The client, Think City — a George Town urban regeneration organisation — wanted a routing tool that would encourage walking by making it cooler, not just faster.

What Was Built

An AI-powered walking route optimiser that returns three options for any A→B journey: Fastest, Coolest, and Balanced. The coolness score is computed per road segment using satellite-derived Land Surface Temperature (LST), real-time weather, shade analysis from Landsat imagery, and an XGBoost model trained on 22 geospatial features. Results are displayed on an interactive Leaflet map with segment-level heat overlays.

Key Outcomes
MetricResult
ML model accuracy (RMSE)3.88 °C on LST prediction (XGBoost GPU v3)
Model improvement over baselinev3 GPU XGBoost outperformed v1 LightGBM and v2 CPU XGBoost in production quality
Inference speedGPU-accelerated on GB10 Grace Blackwell — sub-second per route batch
DeploymentLive at coolpath.lab.local on K3s; signed Docker Compose deployment guide delivered to Think City
ClientThink City, George Town, Penang — signed deployment guide, approved Jan 2026
Architecture
Web UI (Nginx)Orchestrator API :8080 │ ┌───────────────────────┬─┴──────────────────┐ ▼ ▼ ▼ Google Routes API Valhalla :8002 PostGIS (route alternatives) (OSM map-match) (shade data) │ ▼ WeatherLink API (MBPP station S31) │ ▼ Intelligence API :8000 (XGBoost GPU — GB10 aarch64) │ ▼ Ranked Routes by Coolness
Components
Web UINginx · port 80

Vanilla HTML/JS + Leaflet map. Click origin and destination on the George Town street map to get three ranked routes — Fastest, Coolest, and Balanced — with per-segment coolness overlays and a summary card showing estimated shade, temperature delta, and walk time.

Orchestrator APIFastAPI · port 8080 (K3s) / 8000 (Docker)

Coordinates all route calculation steps: calls Google Routes API for pedestrian alternatives, sends each to Valhalla for OSM map-matching, queries PostGIS for shade/LST features per segment, fetches live weather from WeatherLink, runs XGBoost inference on the Intelligence API, and returns a ranked JSON payload. Also exposes route search logging and analytics endpoints.

Intelligence APIFastAPI + XGBoost GPU · port 8000 (K3s) / 5000 (Docker)

Predicts Land Surface Temperature per road segment using a 22-feature XGBoost v3 model. GPU-accelerated on GB10 aarch64 (CUDA 13.0). Loads model from the MLflow registry (coolest-path-lst, alias production) on startup — no model file bundled in the image.

ValhallaOSM routing · port 8002

Pedestrian routing engine loaded with a Penang OSM tile extract. Map-matches Google route alternatives to actual street-level segments so shade, shade NDVI, and ML features can be joined per segment geometry.

PostGISPostgreSQL + PostGIS · port 5432

Stores the full Penang road network with pre-computed shade percentages (from Landsat imagery), coolness ML features, and route search logs. Database mail01db, schema cooler_path. Key tables: edges_with_shade, edges_coolness_for_ml, cp_shade_info, route_search_log.

ML Model — XGBoost LST Predictor
VersionFrameworkDeviceRMSE (°C)Status
v1LightGBMCPU3.80Archived
v2XGBoostCPU3.71Archived
v3XGBoost GPUGB10 Grace Blackwell (aarch64)3.88Production

22 features per road segment: OSM attributes, time of day, segment geometry, NDVI, shade coverage percentage, live weather (temperature, humidity, UV), neighbour segment averages, and lag features. Tracked in MLflow experiment coolest-path-lst.

K3s Deployment
Pod / ServiceNodeImage
Web UI + Orchestratormailt03u (.7) — arch: amd64nginx + FastAPI (amd64)
Valhallamailt03u (.7) — arch: amd64valhalla OSM (amd64)
PostGISmailt03u (.7) — arch: amd64postgis (amd64)
Intelligence APImailgb01 (.8) — GB10 GPU workercp-intel-api (aarch64+CUDA)

Ingress: /coolpath/ → Traefik → UI nginx. GPU pod uses nodeSelector: kubernetes.io/arch: arm64 + NVIDIA_VISIBLE_DEVICES=all (no device plugin — GB10 unified memory workaround).

API Endpoints
POST/route-osm-intelFull coolest route pipeline — Google Routes + Valhalla + PostGIS + Weather + XGBoost
POST/route-osm-weatherRoutes + weather only, no ML inference (debug / fallback)
POST/log-route-selectionLog which route the user selected (analytics)
GET/route-search-logRetrieve route selection history
GET/healthHealth check — returns service + model status
External APIs Required
ServicePurposeNotes
Google Maps Routes v2Pedestrian route alternativesRequires API key with Routes API enabled
WeatherLink v2Real-time weather — MBPP station S31Station ID 174967; temp, humidity, UV index
NASA EarthdataECOSTRESS + Landsat imageryUsed for LST and NDVI pre-computation (offline batch, not live)
Client Deployment

The public release (for Think City, George Town) uses Docker Compose on Ubuntu 22.04. Minimum spec: 16 CPU cores, 64 GB RAM, 1 TB storage.

git clone https://github.com/innovationhubmy-svg/coolest-path.git
cd coolest-path
cp .env.template .env  # fill in Google Maps, WeatherLink, NASA Earthdata keys
docker-compose -f docker-compose.production.yml up -d
./scripts/health-check.sh
Note: Client ports differ from K3s: Orchestrator :8000, Intelligence API :5000. The K3s manifests use :8080/:8000 — do not change the K3s manifests to match the client guide.
Stack
Python / FastAPIXGBoost GPU Valhalla OSMPostGIS Leaflet.jsMLflow K3sHarbor Google Routes APIWeatherLink v2 NASA EarthdataDocker Compose
NetOps · AI

Network EMU

End-to-end network failure simulation pipeline. From Zabbix alert to LLM-generated incident summary in a live NOC dashboard.

● Active git repo ↗
The Problem

Network Operations Centres (NOCs) deal with a flood of alerts from monitoring systems like Zabbix. Most of these alerts are raw — they say a switch went down, but not which customer is affected, what the likely cause is, or what the operator should do first. Tier-1 engineers spend significant time correlating device → customer → location → history before they can act.

This project simulates that workflow end-to-end and demonstrates how an LLM can compress the triage time by generating a ready-to-use incident summary with remediation steps and a customer communication draft — automatically.

What Was Built

A four-container Docker Compose pipeline that takes a Zabbix-style alert webhook, enriches it against a CMDB, calls a local LLM (Qwen 2.5 7B) to generate a natural-language incident summary and recommended actions, and displays everything in a live NOC dashboard. The LLM also generates a communication action — a drafted message to both the device owner and the affected customer. All running on-premises, no cloud dependency.

Architecture
Zabbix Alert │ ▼ Enrichment API :8001 ← CMDB lookup (PostgreSQL) │ creates incident record ▼ Summary API :8002LLM (Qwen 2.5 7B / llm-small) │ polls enriched transactions │ generates AI summary + recommendations ▼ NOC Dashboard :8080 │ live incident view, open / close ▼ Operator
Services
network-emu-dbPostgreSQL 16 · :5432

Central data store for incidents, enriched transactions, and the CMDB. Pre-seeded with 5 switches (SW001–SW005), 5 customers (Petronas, Tenaga, CIMB, TNB, Maxis) across locations KUL, PNG, JHR, and a realistic device→customer ownership map. Pipeline record statuses: received → enriched → summarized.

network-emu-enrichmentFastAPI · :8001

Receives raw Zabbix-style alert webhooks, performs a CMDB lookup to find the affected device, customer, and location, then writes an enriched incident record to PostgreSQL. Designed as a drop-in Zabbix webhook target.

network-emu-summaryFastAPI · :8002

Polls PostgreSQL for enriched transactions, sends each to the LLM with full incident context (device, customer, CMDB history), and writes back a natural-language summary plus recommended remediation actions and a communication action (notifies device owner and customer). History-aware: previous summaries for the same device are included as context.

network-emu-uiFastAPI + HTML · :8080

Live NOC dashboard. Shows incident list with status badges, AI-generated summaries, open/close controls, communication action log, and operational_status field per incident. Also exposes a REST API for the dashboard data.

Database Schema
TablePurposeKey Fields
cmdb_devicesNetwork device inventorydevice_id, hostname, customer, location, device_type
incidentsAlert records from Zabbixincident_id, device_id, severity, status, operational_status
transactionsPipeline processing recordsincident_id, status (received/enriched/summarized), ai_summary, recommendation, communication_action
LLM Backend

Pluggable via .env — no rebuild needed to switch provider.

BackendLLM_BASE_URLLLM_MODELNotes
llm-small (recommended)http://192.168.1.6:11434/v1qwen2.5:7bQwen 2.5 7B via Ollama on mailt02u
Lab vLLM#unavailable"td-muted">NIM on GB10http://192.168.1.8:8000/v1meta/llama-3.1-8b-instructDirect NVIDIA NIM endpoint
Note: Use the host IP (192.168.1.6), not localhost — the summary container and llm-small run in separate Docker Compose networks.
Planned Integrations
GNS3 — containerized GNS3 server (git repo) will be the upstream alert source, simulating real switch failures in a virtual topology before Zabbix integration.
Zabbix — direct webhook integration so real network alerts from lab switches trigger the enrichment pipeline.
GNS3 topology sync — CMDB auto-population from GNS3 device inventory.
Quick Start
git clone http://git.lab.local/AI_Innovation_Hub/network-emu.git
cd network-emu
cp .env.example .env   # set LLM_BASE_URL and LLM_MODEL
docker compose up -d
# Dashboard: http://<host>:8080
# Simulate an alert:
curl -X POST http://localhost:8001/alert \
  -H "Content-Type: application/json" \
  -d '{"device_id":"SW001","problem":"Interface GigE0/1 down","severity":"high"}'
Sales AI · Edge · NPU

IntelliSalesWidget

On-device sales chat assistant powered by Qualcomm NPU. Qwen3-4B runs entirely offline on Snapdragon X Elite — no cloud, no latency.

● Edge / NPU git repo ↗ Developer: shanand_reddy
The Problem

Sales teams often need quick answers during client meetings — product specs, pricing, deal history, competitive comparisons. Reaching for a laptop and waiting on a cloud AI tool breaks the flow of conversation. And for organisations with sensitive client data, sending that data to a cloud LLM raises privacy concerns.

The ask: a sales assistant that knows your product catalogue and CRM data, runs entirely on the sales rep's laptop, answers in seconds, and never sends data outside the device.

What Was Built

A chat assistant that runs Qwen3-4B fully on the Qualcomm NPU (Snapdragon X Elite) — no GPU, no cloud, no internet required during chat. Product docs and client data are auto-indexed into a local ChromaDB on startup. CRM connectors (HubSpot, Salesforce) pull live deal context at query time. The whole thing ships as a signed one-click Windows installer for instant deployment on any Snapdragon X Elite device.

Key Outcomes
MetricResult
LLM startup time on NPU~11 s (model loaded into Hexagon NPU once, cached for session)
InferenceFully on-device — Snapdragon X Elite Hexagon NPU, no GPU required
Data sovereigntyZero data leaves the device during chat — no cloud API calls for inference
DistributionSigned Windows installer with bundled runtime — works on any Snapdragon X Elite laptop
Architecture
Qualcomm NPU (Snapdragon X Elite) │ └─ Genie Runtime ← Qwen3-4B W4A16 QNN DLC (4-part split) │ ~11s warm-up on startup ▼ FastAPI Backend │ ├─ RAG Pipeline │ ├─ ChromaDB (vector store) │ ├─ Product/Services docs (auto-indexed) │ └─ Client data docs (auto-indexed) │ ├─ Data Connectors │ ├─ HubSpot │ ├─ Salesforce │ └─ Generic REST │ └─ Chat UI (served statically)
Key Features
On-Device LLM

Qwen3-4B compiled to a 4-part QNN DLC bundle via Qualcomm AI Hub. The model is split across 4 DLC files (prefill context 4096, auto-regressive 128) and loaded into the Snapdragon X Elite NPU at startup (~11 s warm-up). All inference runs locally — no network required during chat sessions.

Auto-Indexed RAG

On startup, the app watches two directories: product/services docs and client data docs. Any new .docx, .pdf, or .xlsx file is automatically chunked, embedded, and indexed into ChromaDB. Retrieved chunks are injected into the LLM context at query time — no redeployment needed when docs change.

CRM Connectors

Pluggable data connectors for HubSpot, Salesforce, and a generic REST endpoint. CRM data (contacts, deal history, product catalog) is fetched at query time and merged into the RAG context, giving the assistant live sales data without a cloud LLM call.

Windows Installer

Ships as a signed Windows installer built with Inno Setup. The installer bundles the Python runtime, all dependencies, the QNN DLC model weights, and a code-signed certificate. One-click deploy on any Snapdragon X Elite device — no developer setup required.

Fully Offline

Once installed, zero network dependency for chat. Internet is only needed for CRM sync (optional) and initial model download. Ideal for sales calls in low-connectivity environments.

Model Compilation Details
SettingValue
Base modelQwen3-4B Instruct
QuantizationW4A16 — weights 4-bit, activations 16-bit
RuntimeQNN DLC → Qualcomm Genie
Target SoCSnapdragon X Elite CRD (Oryon CPU + Hexagon NPU)
Split4 DLC parts — context prefill (4096 tokens) + auto-regressive (128 tokens)
Warm-up time~11 s on first load; subsequent runs from cache
Compiled viaQualcomm AI Hub (cloud compile jobs — see qwen3-4b-genie memory)
Repo Layout
IntelliSalesWidget/
├── source_code/
│   ├── run.py          # entrypoint (~15 KB)
│   ├── app/            # FastAPI routes, LLM, RAG pipeline
│   ├── config/         # model paths, CRM config
│   ├── data/           # product docs, client docs (watched dirs)
│   ├── qnn_backup/     # QNN DLC weight files
│   ├── ui/             # chat UI (served statically)
│   └── tests/
├── installer/
│   ├── installer.iss   # Inno Setup script
│   ├── build_installer.py
│   └── sign_installer.ps1  # code-signing
└── pyproject.toml
Stack
Python / FastAPIQwen3-4B QNN DLCGenie Runtime ChromaDBQualcomm AI Hub HubSpot APISalesforce API Inno SetupWindows Snapdragon X Elite
Ownership note: This project is maintained by shanand_reddy (colleague). The repo is at git.lab.local/shanand_reddy/IntelliSalesWidget — treat as read-only unless you are taking ownership.
Chat · RAG · LLM

Sovereign AI Stack

Fully self-hosted chat platform with RAG over the lab KB, live web search via Searxng, and artifact rendering. No cloud APIs — Gemma 4 26B-A4B runs entirely on the GB10 Grace Blackwell.

The Problem

Most AI chat tools are cloud-dependent — your conversations, your lab data, and your queries go to OpenAI, Anthropic, or Google. For a government-adjacent AI lab in Malaysia, that's a sovereignty concern. The team also needed the chat tool to know the lab — to answer questions about hardware specs, project details, and internal services without having to train a custom model.

And practically: the team wanted to be able to ask "what's the weather in KL today?" and "how does Coolest Path's ML model work?" from the same interface, with accurate answers to both.

What Was Built

A fully self-hosted AI chat platform — LibreChat as the UI, Gemma 4 26B as the LLM (running on the GB10 Grace Blackwell in-lab), with a custom strip-shim proxy that automatically routes each query to the right augmentation: RAG over the lab knowledge base (via pgvector), live web search (via Searxng), or straight to the model. No cloud APIs. The same LLM powers a voice avatar (Nova) at avatar.lab.local.

Key Outcomes
MetricResult
LLM inference speed~32 tokens/sec (Gemma 4 26B-A4B FP8, single stream on GB10)
RAG corpus44 sources, ~352 chunks — lab KB, project READMEs, memory files
Chat end-to-end latencyFirst token in ~1 s; full reply in 2–4 s for typical queries
Voice avatar latency~2 s total round-trip (STT + LLM + TTS) — measured on warm path
Sovereignty100% on-premises — no external API calls for inference, RAG, or search
LanguagesEnglish, Bahasa Malaysia, Indonesian, Chinese (chat + voice)
Architecture
Browser │ ├── chat.lab.local ──► LibreChat (ns librechat) │ │ └── avatar.lab.local ──► avatar-backend (ns avatar) │ STT + LLM + TTS │ ▼ ▼ vllm-whisper litellm.vllm.svc:4000 (strip-shim) (Whisper Large v3) │ ┌──────┴──────────┐ ▼ ▼ RAG (pgvector) Searxng BGE-M3 embed web search └──────┬──────────┘ ▼ litellm-direct → vLLM on .9 • gemma4-26b (default, MoE 4B active) • qwen3vl-32b (vision, scale-to-zero) • bge-m3 (embeddings) • whisper (STT)
Live Endpoints
SurfaceURLPurpose
Chat UIchat.lab.localLibreChat — daily chat, RAG, artifacts (HTML/SVG/Mermaid/React)
Voice Avataravatar.lab.localNova — speech-in / speech-out, hands-free VAD, adaptive language
LLM APIllm.lab.local/v1OpenAI-compatible endpoint (strip-shim + LiteLLM), key sk-lab-master
Searxngsearxng.lab.localAnonymous metasearch UI & JSON API (also queried by strip-shim)
Models on mailgb02 (.9)
PodModelGPU utilRole
vllm-gemma4-26bGemma 4 26B-A4B FP8 (MoE, 4B active)0.35Default chat — ~32 t/s
vllm-qwen3vl-32bQwen3-VL-32B FP80.40Vision demos (scale-to-zero by default)
vllm-bge-m3BGE-M3 (1024-dim)0.05RAG embeddings
vllm-whisperWhisper Large v30.08STT — ~0.5s for 10s audio
Strip-Shim — How It Works

The strip-shim at litellm.vllm.svc:4000 is the most critical custom piece. It sits between LibreChat/avatar-backend and the real LiteLLM proxy, augmenting every request transparently. Processing pipeline per request:

Strip junk — removes tools: [] (empty array causes vLLM 400) and web_search_options (LibreChat sends these, vLLM rejects them)
Skip short requests — title generation and max_tokens ≤ 20 requests bypass augmentation entirely
Classify intent — lab / web / artifact / general (see table below)
Detect voice mode[VOICE MODE prefix in user text → use compact voice-prompt templates (1–3 sentences, no markdown)
Detect artifact request — diagram/chart/html/svg keywords → inject :::artifact{...}::: directive so LibreChat renders the side-panel
Augment — web: Searxng top-5 bullets; lab: BGE-M3 embed → pgvector cosine top-8 chunks with source URL + similarity score
Trim history — reduces context to [system_with_results, latest_user_msg] so stale "I can't access the internet" replies don't anchor the model
Stream back unchanged — SSE bytes flushed as they arrive from vLLM, no buffering
IntentActionSample triggers (English + Bahasa Malaysia)
labpgvector cosine search, top-8 chunks (BGE-M3 embed)gb10, grace blackwell, coolest path, network emu, k3s, harbor, vllm, litellm, keycloak, cl01, cl02…
webSearxng top-5 results injected as bulletstoday, latest, news, weather, near me, hari ini, terkini, berita, cuaca, dekat sini…
artifactInject LibreChat artifact directive (HTML/SVG/Mermaid/React side-panel)diagram, chart, mermaid, svg, plot, dashboard, "build a html page", "generate svg"…
generalPass through unchanged — no augmentation(everything else)
Priority: Web wins over lab when both match — e.g. "what's the lab weather today" → web search. See architecture.html for the full regex list and shim internals.
RAG Corpus — 44 Sources, ~352 Chunks
SourceTypeNotes
innovation.lab.local (5 pages)Static HTMLindex.html split into 13 sub-sections by <div id="..."> for precise citation
ai-wiki.lab.localStatic HTMLAI API & serving fundamentals knowledge base
coolpath.lab.localStatic HTMLCoolest Path project landing page
7× Forgejo READMEsMarkdowncoolest-path-api, network-emu, reasoning-api, LLM-Small, gns3, ai-fundamentals-wiki, IntelliSalesWidget
18× lab memory filesMarkdownCurated from Claude memory dir — hardware, architecture, project notes, gotchas

Embedding model: BGE-M3 (1024-dim) via vLLM. Chunk size ~800 chars, 120 char overlap, sentence-boundary aware. Re-ingest after content changes: rebuild rag-memory-files ConfigMap on .7, then delete and re-apply the rag-ingest Job in ns vllm.

Why Custom Shim — Not LibreChat Agents

LibreChat agents use vLLM's /v1/responses endpoint which does not apply tool-call parsers. Even with --tool-call-parser gemma4 configured, agent tool calls leaked as raw text and LibreChat saw no structured tool_calls — returning empty chat bubbles. The shim bypasses this entirely by working on the regular /v1/chat/completions path where the tool-call parser works, and triggers transparently regardless of which model is active.

Gotcha: The MongoDB agent config must have use_responses_api: false set to force the completions path, and the LibreChat pod needs /app/api/data/auth.json to exist (containing {}) — its absence causes per-chunk errors that abort the agent stream.
Stack
LibreChatLiteLLM vLLMGemma 4 26B-A4B FP8 BGE-M3pgvector Searxngaiohttp (strip-shim) Whisper Large v3K3s MongoDBPostgreSQL
Voice AI · STT · TTS

Nova — Voice Avatar

Hands-free voice assistant running entirely on the lab GB10. Push-to-talk or VAD-triggered. Supports English, Bahasa Malaysia, Indonesian, and Chinese. End-to-end latency ~2 seconds.

Latency Breakdown (warm)
StageTime
ffmpeg webm→wav transcode~0.05 s
Whisper Large v3 STT (10 s audio)~0.5 s
Strip-shim classify + RAG embed + pgvector0.1–0.15 s
Gemma 4 26B-A4B reply0.8–1.5 s
Piper / VITS TTS (3 sentences)~0.3 s
Network + browser playback~0.2 s
Total round trip~2 s
TTS Voices
LanguageEngineModel
🇬🇧 EnglishPiper (speaches)piper-en_US-ryan-medium
🇲🇾 Bahasa MalaysiaMesolitica VITS (sovereign, on GB10)VITS-osman (22 kHz, Malaysian-trained)
🇮🇩 IndonesianPiper (speaches)piper-id_ID-news_tts-medium
🇨🇳 ChinesePiper (speaches)piper-zh_CN-huayan-medium
The Problem

Chat interfaces are great at a desk. But for kiosk demos, factory floors, or accessibility scenarios, a keyboard is a barrier. The team wanted a voice-first interface to the lab's AI stack — something a visitor could walk up to, speak to naturally in English or Bahasa Malaysia, and get a spoken answer within two seconds, without touching anything.

What Was Built

Nova — a hands-free voice assistant running entirely on the lab's GB10. Speech is transcribed by Whisper Large v3 (on-device, ~0.5 s), routed through the same Sovereign AI Stack strip-shim (RAG + web search), answered by Gemma 4 26B, and spoken back using Piper TTS for English or Mesolitica's VITS-osman for authentic Bahasa Malaysia. Total round trip: approximately 2 seconds. No cloud, no external APIs, supports four languages.

Nova also has a wake-word mode — when enabled, she only responds if her name is spoken first, making her suitable for ambient/kiosk deployments where background noise would otherwise trigger false activations.

Key Outcomes
MetricResult
End-to-end latency (warm)~2 s total — STT 0.5 s, LLM 1.0–1.5 s, TTS 0.3 s
STT accuracyWhisper Large v3 — ~20× realtime on GB10 GPU
Languages supportedEnglish, Bahasa Malaysia (sovereign VITS model), Indonesian, Chinese
Malay TTS qualityMesolitica VITS-osman — 22 kHz, Malaysian-trained, genuinely sounds like BM
Sovereignty100% on-premises — Whisper, Gemma 4, Piper, and VITS all run locally
Components (ns: avatar)
vllm-whisperSTT · GPU · mailgb02 (.9)

Serves Whisper Large v3 (HF format, OpenAI-compatible /v1/audio/transcriptions). ~0.5 s for 10 s of audio (~20× realtime). First request after cold start is ~22 s (CUDA graph compile). Uses gpu-memory-utilization: 0.08.

whisper-stt (speaches)TTS only · Piper voices

Runs Piper TTS for English, Indonesian, and Chinese voices. Also has faster-whisper but it falls back to CPU on aarch64 (no CUDA in that image), so STT is handled by vllm-whisper instead.

vits-msMalay TTS · Mesolitica VITS-osman · mailgb02 (.9)

Malaysian-trained VITS model (mesolitica/VITS-osman) — 22 kHz, genuinely sounds like Bahasa Malaysia. Replaces the earlier MMS (robotic 16 kHz) and Indonesian Piper (wrong accent). ~0.31× realtime once warm (~2 s/reply). Uses a stub TF shim to load malaya_speech on aarch64.

avatar-backendFastAPI orchestrator + UI server

Orchestrates STT → shim+LLM → TTS pipeline. Serves the browser UI from a ConfigMap-mounted index.html. Sends [VOICE MODE: ...] prefix on user messages so the strip-shim switches to compact, no-markdown response templates.

API Endpoints
POST/api/converse?lang=en|ms|id|zh|autoRaw audio → JSON {transcript, reply, audio_base64, timings}
POST/api/stt?lang=Raw audio → {text, language, detected, elapsed_s}
POST/api/chat{text, language?} → {reply, language, length_hint, elapsed_s}
POST/api/tts{text, language?} → audio/mpeg MP3 stream
GET/healthzHealth check
Adaptive Response Length

avatar-backend estimates a length hint from the user's text and passes it in the [VOICE MODE] prefix so Gemma 4 calibrates reply length for speech:

ConditionLength hint
≤ 4 words"1 very short sentence (greeting/yes-no)"
what/where/when/who/how much + ≤ 12 words"1-2 short sentences"
tell me about / explain / describe / why / compare…"3-5 sentences"
everything else"2-3 sentences"
Hands-Free VAD Thresholds
VOICE_DBFS−38 dBFS

Signal must exceed this level sustained for 150 ms to start recording. Two-stage: mic is always open but MediaRecorder only starts when voice is confirmed.

SILENCE_DBFS−50 dBFS

Anything quieter than this for 1.2 s triggers end-of-utterance and send. The "dead zone" between VOICE_DBFS and SILENCE_DBFS (room tone) is treated as silence to prevent stuck-recording.

MIN_SPEECH_MS800 ms

Minimum accumulated voice content required before sending. Clips with less than 800 ms of real voice are discarded — prevents Whisper hallucinations ("Thank you", "Terima kasih") on near-silent inputs.

Wake-Word Mode

Toggle in the header. When ON + hands-free ON, Nova only replies if "Nova" is detected in the Whisper transcript. Regex tolerates known Whisper mis-transcriptions: Nova, Novah, Noah, Knower, Nowa. Unrecognised clips show a 🔇 debug bubble with what was heard. Implemented client-side — STT runs regardless, the chat+TTS calls are conditional.

Language pinning: Whisper auto-detect is noisy on short clips (often returns ms for clear English). The UI defaults to pinned en — override via the 🇬🇧/🇲🇾/🇮🇩/🇨🇳/🌐 dropdown. Pinned language is forwarded to the Whisper endpoint as language=en, skipping detection entirely.
Stack
Whisper Large v3vLLM (STT) Piper TTSMesolitica VITS-osman speachesGemma 4 26B-A4B FastAPIaiohttp Web Audio API (VAD)ffmpeg K3s (ns: avatar)GB10 Grace Blackwell

Architecture Overview

AI Innovation Hub platform on Grace Blackwell + K3s infrastructure · Cyberjaya, Malaysia

*.lab.local (dnsmasq → 192.168.1.7) │ ▼ Traefik Ingress (TLS wildcard via cert-manager) │ ├─ innovation.lab.local ──→ This Portal ├─ coolpath.lab.local ────→ Coolest Path App ├─ chat.lab.local ────────→ LibreChat (Sovereign AI Chat) ├─ avatar.lab.local ──────→ Nova Voice Avatar ├─ llm.lab.local ─────────→ LiteLLM API (strip-shim + vLLM) ├─ searxng.lab.local ─────→ Searxng Metasearch ├─ ai-wiki.lab.local ─────→ AI Knowledge Wiki ├─ mlflow.lab.local ──────→ MLflow Tracking Server ├─ harbor.lab.local ──────→ Harbor Container Registry ├─ git.lab.local ─────────→ Forgejo Git Server ├─ grafana.lab.local ─────→ Grafana Monitoring ├─ argocd.lab.local ──────→ ArgoCD GitOps ├─ keycloak.lab.local ────→ Keycloak IAM └─ k8s.lab.local ─────────→ Headlamp (K8s Dashboard) K3s Cluster ├─ mailt03u (192.168.1.7) — Control Plane, Tower 2 ├─ mailgb01 (192.168.1.8) — GPU Worker, GB10 └─ mailgb02 (192.168.1.9) — GPU Worker, GB10

Network Topology

Control Plane

Tower 2 — mailt03u

192.168.1.7 · K3s server, Traefik, all platform services

GPU Worker 1

GB10 — mailgb01

192.168.1.8 · K3s agent, GPU inference workloads

GPU Worker 2

GB10 — mailgb02

192.168.1.9 · K3s agent, GPU inference workloads

Dev Node

Tower 1 — mailt02u

192.168.1.6 · Development workstation, workbench projects

DNS + Host

mailt01

192.168.1.5 · dnsmasq, PostgreSQL, Claude Code host

Hardware Inventory

AI Innovation Hub hardware specifications — Cyberjaya lab

NodeHostnameIPHardwareGPURAMRole
Tower 1mailt02u192.168.1.6Dell Precision 5860RTX 4000 Ada128 GBDev
Tower 2mailt03u192.168.1.7Dell Precision 5860RTX 4000 Ada128 GBK3s Control Plane
CL01-1mailgb01192.168.1.8NVIDIA GB10 Grace BlackwellBlackwell (unified)128 GB unifiedK3s GPU Worker
CL01-2mailgb02192.168.1.9NVIDIA GB10 Grace BlackwellBlackwell (unified)128 GB unifiedK3s GPU Worker
CL02-1mailgb03192.168.1.10NVIDIA GB10 Grace BlackwellBlackwell (unified)128 GB unifiedEdge/Demo
CL02-2mailgb04192.168.1.11NVIDIA GB10 Grace BlackwellBlackwell (unified)128 GB unifiedEdge/Demo

GB10 Grace Blackwell Specifications

Architecture

aarch64 (ARM)

Grace CPU + Blackwell GPU on a single chip. CPU and GPU share the same 128 GB LPDDR5x memory pool at 273 GB/s — no PCIe penalty.

Software

DGX OS 7.4.0

NVIDIA driver 580.126.09, CUDA 13.0. Docker + NVIDIA container runtime pre-installed. SM 12.1 — requires NGC vLLM image.

Compute

Native FP8 Tensor Cores

Blackwell FP8 tensor cores execute matrix ops without dequantisation overhead. ~half the memory of BF16 with <1% quality loss.

Services & URLs

All services accessible via Traefik ingress with wildcard TLS (*.lab.local)

ServiceURLNamespacePurpose
Innovation Hubhttps://innovation.lab.localai-innovation-hubThis portal — projects + infrastructure docs
Coolest Path/coolpath/coolest-pathRoute intelligence web app
Chat (LibreChat)/chat/librechatSovereign AI chat — RAG, web search, artifacts (Gemma 4 26B)
Nova Voice Avatar/avatar/avatarHands-free voice assistant — Whisper STT + Piper/VITS TTS + Gemma 4
LLM API#unavailable"table-wrap">
ServicePortNamespaceAccess Method
PostGIS5432coolest-pathSSH tunnel: ssh -L 15432:<ClusterIP>:5432 aihubmyadmin@192.168.1.7
MinIO9000platformSSH tunnel: ssh -L 19000:<ClusterIP>:9000 aihubmyadmin@192.168.1.7
Tip: To get ClusterIP addresses, run kubectl get svc -n <namespace> on Tower 2.

K3s Cluster Setup

3-node cluster: Tower 2 (control plane) + 2× GB10 (GPU workers)

NodeIPRoleLabels
mailt03u192.168.1.7control-plane, etcd, master
mailgb01192.168.1.8workernvidia.com/gpu.present=true
mailgb02192.168.1.9workernvidia.com/gpu.present=true

Adding GPU Worker Nodes

1. Get token from Tower 2

sudo cat /var/lib/rancher/k3s/server/node-token

2. Install K3s agent on GB10

curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="agent" sh -

3. Configure K3s agent — /etc/rancher/k3s/config.yaml

server: https://192.168.1.7:6443
token: <token-from-step-1>
default-runtime: nvidia
Important: default-runtime: nvidia in config.yaml is the correct way to enable GPU. Do NOT touch config.toml.tmpl in containerd — it will crash containerd and prevent the agent from starting.

4. Configure Harbor registry — /etc/rancher/k3s/registries.yaml

mirrors:
  harbor.lab.local:
    endpoint:
      - "#unavailable"
configs:
  "harbor.lab.local":
    tls:
      insecure_skip_verify: true

5. Add DNS + restart

echo "192.168.1.7 harbor.lab.local" | sudo tee -a /etc/hosts
sudo systemctl restart k3s-agent

# On Tower 2 — label the new node:
kubectl label node <node-name> nvidia.com/gpu.present=true

GB10 Grace Blackwell — Notes & Gotchas

Critical findings from deploying on NVIDIA GB10 DGX Spark

Read this before deploying on GB10. Several incompatibilities exist on this platform that are not documented upstream.

Architecture: aarch64

GB10 uses ARM64 (Grace CPU). All Docker images must be built for aarch64. Build images directly on the GB10 or use multi-arch builds.

Unified Memory

CPU and GPU share the same 128 GB memory pool. This means:

  • NVML memory queries return "Not Supported"
  • Standard GPU monitoring tools may not report memory usage correctly
  • nvidia-smi works but shows limited memory info

NVIDIA K8s Device Plugin — Does NOT Work

The standard NVIDIA k8s-device-plugin crashes on GB10 with "error getting device memory: Not Supported" due to NVML not supporting unified memory queries. Known issue #1482.

Do NOT attempt: installing nvidia-device-plugin-daemonset, using nvidia.com/gpu: 1 resource limits, or installing the NVML unified memory shim.

Workaround: nodeSelector + Environment Variables

spec:
  nodeSelector:
    nvidia.com/gpu.present: "true"    # Schedule on GPU nodes
  containers:
    - name: my-gpu-app
      env:
        - name: NVIDIA_VISIBLE_DEVICES
          value: "all"                 # Grant GPU access
        - name: NVIDIA_DRIVER_CAPABILITIES
          value: "compute,utility"
      # Do NOT set nvidia.com/gpu resource limits!

Containerd Configuration

Do NOT create or edit config.toml.tmpl for containerd on GB10. Both the Go template approach and a full config copy will crash containerd and prevent K3s agent from starting. Use default-runtime: nvidia in /etc/rancher/k3s/config.yaml only.

Building Docker Images for GB10

ssh aihubmyadmin@192.168.1.8
docker build -f Dockerfile.my-gpu-app -t harbor.lab.local/ai-lab/my-app:gpu-arm64 .
docker push harbor.lab.local/ai-lab/my-app:gpu-arm64

GPU Dockerfile Template (aarch64)

FROM nvcr.io/nvidia/cuda:12.8.0-runtime-ubuntu24.04
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
    python3 python3-pip python3-venv \
    libgomp1 gdal-bin libgdal-dev gcc g++ python3-dev \
    && rm -rf /var/lib/apt/lists/*
RUN python3 -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY my-app ./my-app
WORKDIR /app/my-app
ENV PYTHONUNBUFFERED=1
ENV NVIDIA_VISIBLE_DEVICES=all
ENV NVIDIA_DRIVER_CAPABILITIES=compute,utility
CMD ["python", "my_app.py"]

GPU Workloads on K3s

Deployment template for GPU containers on GB10 worker nodes

GPU Deployment Template

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-gpu-app
  namespace: my-namespace
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-gpu-app
  template:
    metadata:
      labels:
        app: my-gpu-app
    spec:
      runtimeClassName: nvidia
      enableServiceLinks: false
      nodeSelector:
        nvidia.com/gpu.present: "true"
      containers:
        - name: my-gpu-app
          image: harbor.lab.local/ai-lab/my-image:gpu-arm64
          env:
            - name: NVIDIA_VISIBLE_DEVICES
              value: "all"
            - name: NVIDIA_DRIVER_CAPABILITIES
              value: "compute,utility"
          resources:
            requests:
              memory: "1Gi"
              cpu: "500m"
            limits:
              memory: "4Gi"
              cpu: "4000m"
          # Do NOT add nvidia.com/gpu resource requests/limits

Verifying GPU Access in a Pod

kubectl run gpu-test --rm -it \
  --image=nvcr.io/nvidia/cuda:12.8.0-runtime-ubuntu24.04 \
  --overrides='{
    "spec": {
      "runtimeClassName": "nvidia",
      "nodeSelector": {"nvidia.com/gpu.present": "true"},
      "containers": [{
        "name": "gpu-test",
        "image": "nvcr.io/nvidia/cuda:12.8.0-runtime-ubuntu24.04",
        "command": ["nvidia-smi"],
        "env": [
          {"name": "NVIDIA_VISIBLE_DEVICES", "value": "all"},
          {"name": "NVIDIA_DRIVER_CAPABILITIES", "value": "compute,utility"}
        ]
      }]
    }
  }' -- nvidia-smi

Harbor Container Registry

Private container image registry at harbor.lab.local

Access

  • URL: #unavailable"#unavailable" configs: "harbor.lab.local": tls: insecure_skip_verify: true

    Then restart: sudo systemctl restart k3s-agent

    Important: All worker nodes must have registries.yaml. If a pod gets scheduled on a node without it, you'll get ImagePullBackOff. Check which node a pod is on with kubectl get pods -o wide.

MLflow

Experiment tracking and model registry

Access

  • External: #unavailable"kubectl get svc -n platform | grep minio" # Port-forward MinIO and PostGIS ssh -f -N -L 19000:<minio-clusterip>:9000 aihubmyadmin@192.168.1.7 ssh -f -N -L 15432:<postgres-clusterip>:5432 aihubmyadmin@192.168.1.7 # Environment export MLFLOW_TRACKING_URI=#unavailable"callout warn">MLflow client v3.x vs server v2.19.0: mlflow.register_model() calls search_logged_models which doesn't exist on server 2.19.0. Use MlflowClient.create_model_version() instead.
client = mlflow.tracking.MlflowClient()
try:
    client.create_registered_model(MODEL_NAME)
except mlflow.exceptions.MlflowException:
    pass  # already exists
mv = client.create_model_version(MODEL_NAME, model_uri, run.info.run_id)

Model Promotion

from mlflow.tracking import MlflowClient
client = MlflowClient()
client.set_registered_model_alias("my-model", "production", version="3")

Troubleshooting

Common issues and their solutions

ImagePullBackOff on GB10

Symptom

Pod stuck in ImagePullBackOff on a GB10 node

Cause: Node missing /etc/rancher/k3s/registries.yaml or /etc/hosts entry for Harbor.
Fix: Add both files, restart k3s-agent, delete the stuck pod to force reschedule.

K3s Agent Won't Start

Symptom

k3s-agent.service fails after configuration changes

Cause: Broken config.toml.tmpl in /var/lib/rancher/k3s/agent/etc/containerd/.
Fix:

sudo rm /var/lib/rancher/k3s/agent/etc/containerd/config.toml.tmpl
sudo systemctl restart k3s-agent

NVML "Not Supported"

Symptom

NVIDIA k8s-device-plugin fails: "error getting device memory: Not Supported"

Cause: GB10 unified memory not supported by NVML. Known issue #1482.
Fix: Don't use the device plugin. Use nodeSelector + NVIDIA_VISIBLE_DEVICES=all.

nvidia-smi Version Mismatch

Symptom

nvidia-smi fails with driver/library version mismatch

Cause: NVML unified shim replaced system libnvidia-ml.so.1.
Fix: sudo apt reinstall libnvidia-compute-580

MLflow Registration 404

Symptom

mlflow.register_model() returns 404 on search_logged_models

Cause: MLflow client v3.x API not available on server v2.19.0.
Fix: Use MlflowClient.create_model_version(). See MLflow section.

MinIO / PostGIS Not Reachable

Symptom

Can't connect to MinIO or PostGIS from outside the cluster

Cause: Both are ClusterIP services, not exposed externally.
Fix: SSH port-forward through Tower 2. See MLflow section for commands.

AI Innovation Hub · Cyberjaya © 2026