Skip to content

Ling-3.0-tiny on a Qualcomm board: a Ling family on Metal first, then an ARM CPU backend #1247

Open

Description

@ThinkOffApp

Why

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

  1. 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.
  2. 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.
  3. Living Weights on the board.

Activity

  1. jschmied commented on Oct 25, 2026

    @jschmied
    Contributor

    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.
    • No Arm CPU, Adreno or Hexagon backend.
    • The board has about 14 GB visible and tiny in bf16 is 15.8 GB. ling: Ling-3.1-flash on CUDA at TP=1 on #804's kernels (draft, stacked on #804) #874 reads bf16, block FP8 and ModelOpt NVFP4. The 4/6/8-bit expert formats you list, and a 4.8 GB Q4-class file, are not read by it; EXL3 waits on EXL3 checkpoints on the native engine (tracker) #832. The official FP8 tiny has not been run on real weights (only synthetic tiny-shaped files).
    • 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.
    • ling: Ling-3.1-flash on CUDA at TP=1 on #804's kernels (draft, stacked on #804) #874 is a draft with conflicts against main; the tiny changes are five commits on top of it, on ling-tiny.
  2. ashhart commented on Oct 27, 2026

    @ashhart
    Owner

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions