You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Ling-3.0-tiny on a Qualcomm board: a Ling family on Metal first, then an ARM CPU backend #1247
We want to run Ling-3.0-tiny on an Arduino VENTUNO Q (Qualcomm QCS8275: Cortex-A cores, Adreno GPU, Hexagon NPU, 16 GB LPDDR5, about 14 GB of it visible to Linux) as the model for a car assistant. Today it runs there on llama.cpp. TensorFold runs on Metal, CUDA and Intel Arc, and has neither a Qualcomm backend nor a Ling model, so Living Weights cannot run on that board. Following the rule for new model families, this issue proposes the shape before any PR.
The model
inclusionAI/Ling-3.0-tiny at commit 9a98e35f, MIT licence. 7.9 B parameters, about 1.1 to 1.4 B active per token.
24 layers: 6 MLA (layers 3, 7, 11, 15, 19, 23) and 18 KDA. Layer 0 is a dense MLP; every later layer is MoE with 128 experts, 8 active plus 1 shared.
No MTP head in the checkpoint.
bf16 is 15.8 GB, GGUF Q4_K_M 4.8 GB, and an official FP8 exists.
Proposed order
A Ling family on Metal, in eight one-job PRs: config; tokenizer; grouped routing with its fp64 reference; the KDA gate; MLA RoPE and per-head gate; the bf16 forward, exact against a float64 reference; 4/6/8-bit experts; serving with receipts.
The closest existing code is the Kimi K3 family. Its MLA kernel has Ling's exact sizes, and its KDA kernel already takes Ling's full-width gate. Missing: RoPE and a per-head gate in MLA, grouped expert routing on Metal, and a served engine.
Exactness follows the existing rules: against the engine's own plain output, with outside references (a float64 forward and mlx-lm, each checked once against the HF model on a GB10) reported.
A backend for the board, ARM CPU first. NEON needs no SDK. Adreno over OpenCL, modelled on the Intel backend, could follow. The Hexagon NPU needs Qualcomm's SDK and is the most work. On this board llama.cpp measured Ling-3.0-tiny at 49 tok/s prompt and 20 tok/s decode on the CPU (4 threads), and 211 and 14 on the NPU, so the NPU looks worth it for prompts only. Those llama.cpp numbers are the baseline every TensorFold number gets compared with.
Thanks for the pointer. I ran Ling-3.0-tiny on #874's CUDA path, and q_lora_rank 256 was the only config value it refused (plus null SwiGLU limit lists). The change is a 256-wide RMS-norm instance for q_a_layernorm; the q projections are ordinary lane matmuls. The rest of tiny's config (24 layers in groups of 4, 6 MLA and 18 KDA, dense layer 0, 128 experts in 8 groups of 16 keeping 4, 16 heads, hidden 1536) already went through the existing kernels.
What #874's CUDA path now covers for tiny, on one GB10 (bf16, TP=1, 9a98e35f):
Whole model, served through the lane core. Logits of 480 real tokens are 1.05e-2 (rel L2) from a float64 forward on the same routing, as far as the HF model's own bf16 rounding is (1.04e-2); the transformers model itself is 3.3e-2 from float64. Greedy chat matches transformers token for token over 64 tokens. Chunked == whole, drafted == plain, resumed == fresh, and concurrent == solo at 1, 2 and 4 streams.
76.6 tok/s decode at one stream, 149-157 tok/s aggregate at four, 1.74 s for an 8,189-token prompt. These are GB10 numbers; they are not comparable with your board's 20 tok/s (CPU) or 14 tok/s (NPU).
Float64 reference (ling_forward.py, reads the sharded checkpoint), a logits dump for external references (ling-ids-logits), tiny's config and index names as fixtures, and a synthetic tiny-shaped checkpoint writer (ling-synth --tiny) for bf16, FP8 and NVFP4. These are what a Metal or Arm port can check against without the 15.8 GB.
What it does not cover:
No Metal. The CUDA kernels (KDA, MLA with RoPE and head gate, group-limited routing) are CUDA C; your plan's eight Metal PRs are still needed, and the fp64 reference and fixtures above would serve them.
bf16 experts have no grouped kernel; the speed above comes from running each expert on just the rows that picked it, which needs a host sync per layer. A Metal or NEON port would want a real grouped kernel.
The server needs possessive-quantifier support in the tokenizer's regex, which main has since 79ba3f58c.
Thanks, @ThinkOffApp, and @jschmied for running tiny through the CUDA path. Yes to Ling-3.0-tiny on Metal: an MIT licence and a float64 reference are what a new family needs. Please build it as one family package that CUDA can share, starting from the config, weight map, float64 reference and fixtures on jschmied's ling-tiny branch, with credit, rather than writing them again, and keep each PR to one job with its gate. Weights are read as published (bf16, the official FP8, or an existing MLX quantisation); we don't ship conversions. The board backend (Arm CPU, Adreno, Hexagon) and Living Weights on Ling are separate proposals for after Ling serves on Metal. Your first PR is the next step.
Activity
jschmied commentedon Oct 25, 2026
Thanks for the pointer. I ran Ling-3.0-tiny on #874's CUDA path, and
q_lora_rank256 was the only config value it refused (plusnullSwiGLU limit lists). The change is a 256-wide RMS-norm instance forq_a_layernorm; the q projections are ordinary lane matmuls. The rest of tiny's config (24 layers in groups of 4, 6 MLA and 18 KDA, dense layer 0, 128 experts in 8 groups of 16 keeping 4, 16 heads, hidden 1536) already went through the existing kernels.What #874's CUDA path now covers for tiny, on one GB10 (bf16, TP=1,
9a98e35f):ling_forward.py, reads the sharded checkpoint), a logits dump for external references (ling-ids-logits), tiny's config and index names as fixtures, and a synthetic tiny-shaped checkpoint writer (ling-synth --tiny) for bf16, FP8 and NVFP4. These are what a Metal or Arm port can check against without the 15.8 GB.What it does not cover:
79ba3f58c.ling-tiny.ashhart commentedon Oct 27, 2026
Thanks, @ThinkOffApp, and @jschmied for running tiny through the CUDA path. Yes to Ling-3.0-tiny on Metal: an MIT licence and a float64 reference are what a new family needs. Please build it as one family package that CUDA can share, starting from the config, weight map, float64 reference and fixtures on jschmied's ling-tiny branch, with credit, rather than writing them again, and keep each PR to one job with its gate. Weights are read as published (bf16, the official FP8, or an existing MLX quantisation); we don't ship conversions. The board backend (Arm CPU, Adreno, Hexagon) and Living Weights on Ling are separate proposals for after Ling serves on Metal. Your first PR is the next step.