Crystal ML Library: Autograd, Tensors, Neural Networks, Optimizers Cogni-ML, a Crystal machine learning library with native Apple Silicon GPU acceleration, has been released. It provides tensors, autograd, neural network layers, optimizers, and native Metal inference for GGUF models including Qwen 3.5 and nomic-embed-text-v2-moe. The library targets production-oriented AI workloads on Apple Silicon with quantized kernel support and speculative decoding. Crystal machine learning library with native Apple Silicon GPU acceleration. Cogni-ML is currently two things: - A general Crystal ML toolkit: tensors, autograd, NN layers, optimizers, GGUF readers, and llama.cpp bindings. - A native Metal inference lab for GGUF models, with production-oriented work on nomic-embed-text-v2-moe embeddings and Qwen 3.5 text generation. - Native Metal embedding pipeline for nomic-embed-text-v2-moe . - Native Qwen 3.5 9B GGUF inference path for Apple Silicon Metal. - Q4 K/Q5 K/Q6 K/Q8 0 quantized matmul kernels. - Chunked Qwen 3.5 prefill, decode wave scheduling, prompt-state cache restore, and exact speculative decode harnesses. - ComputeGraph wave scheduling with offset-aware barrier optimization. - Crystal autograd engine, NN layers, and Adam/AdamW optimizers. - llama.cpp FFI bindings for general GGUF model access. src/ml/ core/ Tensor, Shape, MetalBuffer autograd/ Variable, GradFn backward pass nn/ Linear, LayerNorm, MultiHeadAttention, ViT optim/ Adam/AdamW llm/ llama.cpp FFI bindings gguf/ GGUF reader, tokenizer, dequantization, Qwen35, NomicBertMoE metal/ Device, ComputeEncoder, ComputeGraph, GraphEncoder The native Qwen path targets Qwen3.5-9B-Q4 K M.gguf on Apple Silicon. The code supports: - Qwen 3.5 GGUF metadata and tokenizer loading. - Q4 K, Q5 K, Q6 K, and Q8 0 quantized projections. - Full-attention layers with GQA, partial RoPE, KV cache writes, and fused output projection. - DeltaNet/recurrent layers with GPU-resident recurrent state and chunked prefill scan. - Chunked prefill with final-token top1 shortcut. - Decode wave scheduling to reduce command-buffer boundaries. - Native Qwen BPE tokenizer with an external llama-tokenize fallback for A/B. - Exact prompt-state save/restore, tokenized-prompt reuse, and longest-prefix prompt cache. - Exact speculative decode harnesses: - neural draft with Qwen 3.5 0.8B Q8 0, - n-gram/cache draft for repeated/generated-template text, - target-verifier chunks with row-batched top1 for larger accepted chunks. The 9B Q4 K M path is the primary verified target. Qwen 3.6 27B is a scale-up target, but it should be treated as experimental until local correctness and performance runs are completed. The developer CLIs default to local LM Studio / llama.cpp-style paths: ~/.cache/lm-studio/models/lmstudio-community/Qwen3.5-9B-GGUF/Qwen3.5-9B-Q4 K M.gguf ~/.cache/lm-studio/models/lmstudio-community/Qwen3.5-0.8B-GGUF/Qwen3.5-0.8B-Q8 0.gguf ~/SrcArchives/AI/llama.cpp/build/bin/llama-tokenize ~/SrcArchives/AI/llama.cpp/build/bin/llama-bench Most benchmark/probe CLIs also accept --model , --target , --draft , --tokenizer-bin , or environment overrides. bin/qwen35 generate.cr is intentionally a small demo and currently uses its constants at the top of the file. Build the CPU-only GGUF/Qwen metadata smoke on Linux, CUDA hosts, or any environment where Metal is unavailable: crystal build -Dcpu only bin/qwen35 gguf info.cr -o build/qwen35 gguf info ./build/qwen35 gguf info --model /path/to/Qwen3.5-9B-Q4 K M.gguf ./build/qwen35 gguf info --model /path/to/Qwen3.5-0.8B-Q8 0.gguf --load-weights This entrypoint intentionally does not run inference. It verifies GGUF parsing, Qwen 3.5/3.6 hparams, tensor inventory, and the structured Qwen35Weights loader without pulling the Metal bridge into a Linux build. Build the minimal Crystal CUDA Driver API smoke on NVIDIA/Linux hosts: crystal build bin/cuda driver smoke.cr -o build/cuda driver smoke ./build/cuda driver smoke 4096 The CUDA smoke is a backend boundary probe only: it links libcuda , loads embedded PTX, launches a vector-add kernel, and checks the result. CUDA probe code uses src/ml/cuda/driver.cr for reusable CUDA context, module/function, launch, copy, synchronize, and device-buffer ownership. This is intentionally small: it owns the raw CUDA Driver API lifecycle and calls, while higher-level layer execution is still probe-local until the CUDA backend split is promoted. It also provides ML::CUDA::ResidentSequenceRunner , a thin lifecycle facade for resident sequence probes with explicit upload weights , reset sequence , run sequence , and read outputs phases. src/ml/cuda/qwen recurrent layer runner.cr is the first Qwen-specific runner extraction: it owns one recurrent layer's CUDA modules, device buffers, kernel parameters, weight upload, sequence reset, token launch graph, and output readback. QwenRecurrentLayerRunner::Weights.load owns GGUF tensor lookup, tensor-shape/type validation, and raw weight reads for the runner, including recurrent-layer ffn down tensors stored as either Q4 K or Q6 K. CPU-reference comparison intentionally remains in the probe. Build the first quantized CUDA correctness probe on NVIDIA/Linux hosts: crystal build -Dcpu only bin/cuda q8 gemv probe.cr -o build/cuda q8 gemv probe ./build/cuda q8 gemv probe \ --model /path/to/Qwen3.5-0.8B-Q8 0.gguf \ --tensor blk.0.ffn up.weight \ --kernel warp4 \ --reps 100 \ --warmup 10 cuda q8 gemv probe loads a real GGUF Q8 0 tensor, launches a Crystal-driven CUDA Driver API GEMV kernel over the raw GGUF block layout, and compares against the existing CPU QuantMatmul reference. --kernel scalar keeps the first one-thread-per-output-row correctness kernel; the default --kernel warp4 maps four output rows to four warps per thread block and is the current faster probe shape. This is still a standalone backend-boundary probe, not an optimized Qwen CUDA inference path yet. The current full qwen35 generate CLI remains Metal-first. Build the first Q4 K CUDA correctness probe for Qwen 9B/27B-style target tensors: crystal build -Dcpu only bin/cuda q4k gemv probe.cr -o build/cuda q4k gemv probe ./build/cuda q4k gemv probe \ --model /path/to/Qwen3.5-9B-Q4 K M.gguf \ --tensor blk.0.attn gate.weight \ --kernel warp4 \ --reps 20 \ --warmup 3 cuda q4k gemv probe uses the raw GGUF Q4 K block layout d , dmin , 12-byte packed scales/mins, 128-byte packed nibbles and checks the CUDA output against the CPU QuantMatmul Q4 K reference. --kernel scalar keeps the first correctness kernel; the default --kernel warp4 maps four output rows to four warps per block and is the current faster probe shape. Build the Q6 K CUDA correctness/speed probe for Q4 K M tensors that remain in Q6 K: crystal build -Dcpu only bin/cuda q6k gemv probe.cr -o build/cuda q6k gemv probe ./build/cuda q6k gemv probe \ --model /path/to/Qwen3.5-9B-Q4 K M.gguf \ --tensor blk.0.ffn down.weight \ --kernel warp4 \ --reps 10 \ --warmup 2 cuda q6k gemv probe covers the GGUF Q6 K block layout ql , qh , signed scales, d used by output/value/down projections in mixed-quant target models. Like the Q4 K/Q8 0 probes, it is a standalone backend primitive check; full CUDA Qwen execution is still a separate backend split. Build the first GPU-resident FFN sequence probe: crystal build -Dcpu only bin/cuda ffn sequence probe.cr -o build/cuda ffn sequence probe ./build/cuda ffn sequence probe \ --model /path/to/Qwen3.5-9B-Q4 K M.gguf \ --layer 0 \ --reps 10 \ --warmup 2 cuda ffn sequence probe composes the checked CUDA primitives as Q4 K ffn gate + Q4 K ffn up - SwiGLU - Q6 K ffn down while keeping the input, intermediate activations, and output projection input GPU-resident. Only the final hidden vector is copied back for comparison against the CPU QuantMatmul FFN reference. Build the full-attention input projection bundle probe: crystal build -Dcpu only bin/cuda attn projection probe.cr -o build/cuda attn projection probe ./build/cuda attn projection probe \ --model /path/to/Qwen3.5-9B-Q4 K M.gguf \ --layer 3 \ --reps 10 \ --warmup 2 cuda attn projection probe runs Q4 K attn q + Q4 K attn k + Q6 K attn v from one GPU-resident hidden vector and copies Q/K/V back only after all projections complete. It targets full-attention layers such as blk.3 in Qwen3.5 9B. The probe now routes through ML::CUDA::QwenFullAttnProjectionRunner , supports --tokens N , and keeps Q/K/V outputs GPU-resident until the final correctness readback. It is the reusable input-projection boundary for future full-attention/KV CUDA work, not a complete full-attention layer runner yet. Build the full-attention Q/K normalization + RoPE + KV-cache boundary probe: crystal build -Dcpu only bin/cuda full attn kv probe.cr -o build/cuda full attn kv probe ./build/cuda full attn kv probe \ --model /path/to/Qwen3.5-9B-Q4 K M.gguf \ --layer 3 \ --tokens 4 \ --start-pos 2 \ --max-seq 12 cuda full attn kv probe now routes through ML::CUDA::QwenFullAttnLayerRunner , a residual-hidden-to-final-hidden wrapper around the projection runner and ML::CUDA::QwenFullAttnKVRunner . The projection runner can apply the initial attn norm on CUDA from residual hidden states before Q/K/V projection; Q is split into normalized/RoPE'd Q and gate, K is RMSNormed/RoPE'd, K/V rows are appended to a CUDA-resident cache at start pos , a correctness-first serial CUDA kernel computes GQA scores, softmax, value reduction, and Q-gate multiplication, the resident gated attention output is projected through attn output.weight , and the layer tail runs residual add, post-attention RMSNorm, FFN gate/up/SwiGLU/down, and final residual. It checks Q, gate, K, gated attention output, projected attention output, final hidden, K-cache, and V-cache against the CPU Qwen reference. This is now a clean one-layer semantics probe with device input/output hooks for mixed-stack composition, but it is not yet an end-to-end Linux decode path: full/recurrent stack scheduling, logits/top1, tokenizer/sampling, restored nonzero prefix KV, and a faster attention kernel remain separate gates. Build the Q5 K CUDA recurrent-QKV probe: crystal build -Dcpu only bin/cuda q5k gemv probe.cr -o build/cuda q5k gemv probe ./build/cuda q5k gemv probe \ --model /path/to/Qwen3.5-9B-Q4 K M.gguf \ --tensor blk.0.attn qkv.weight \ --reps 10 \ --warmup 2 cuda q5k gemv probe covers the GGUF Q5 K block layout used by recurrent-layer combined attn qkv.weight tensors in the current Qwen3.5 9B Q4 K M file. Build the recurrent-layer projection bundle probe: crystal build -Dcpu only bin/cuda recurrent projection probe.cr -o build/cuda recurrent projection probe ./build/cuda recurrent projection probe \ --model /path/to/Qwen3.5-9B-Q4 K M.gguf \ --layer 0 \ --reps 10 \ --warmup 2 cuda recurrent projection probe runs Q5 K attn qkv + Q4 K attn gate + Q4 K ssm alpha + Q4 K ssm beta from one GPU-resident hidden vector and copies the four outputs back only after all kernels complete. It is the first CUDA recurrent projection-bundle proof; DeltaNet recurrence, convolution, state updates, and ssm out remain separate work. Build the synthetic DeltaNet output slice probe: crystal build -Dcpu only bin/cuda deltanet output probe.cr -o build/cuda deltanet output probe ./build/cuda deltanet output probe \ --model /path/to/Qwen3.5-9B-Q4 K M.gguf \ --layer 0 \ --reps 10 \ --warmup 2 cuda deltanet output probe runs a synthetic CUDA DeltaNet state update, applies post RMSNorm/SiLU gating on GPU, and feeds the result directly into the real Q4 K ssm out.weight projection. It is a stateful boundary probe, not a full recurrent layer: recurrent conv prep, alpha/beta transforms, residuals, and FFN remain separate work. Build the recurrent prep/output slice probe: crystal build -Dcpu only bin/cuda recurrent prep output probe.cr -o build/cuda recurrent prep output probe ./build/cuda recurrent prep output probe \ --model /path/to/Qwen3.5-9B-Q4 K M.gguf \ --layer 0 \ --tokens 4 \ --reps 10 \ --warmup 2 cuda recurrent prep output probe now composes one full recurrent layer token slice: input RMSNorm, real recurrent projection bundle attn qkv , attn gate , ssm alpha , ssm beta , recurrent conv prep, alpha/beta transforms, DeltaNet, post RMSNorm/SiLU, Q4 K ssm out , residual add, post-attention RMSNorm, Q4 K FFN gate/up, SwiGLU, Q6 K FFN down, and final residual. --tokens N runs a GPU-resident sequence through persistent conv/SSM state and compares all token outputs plus final recurrent states against the CPU reference. The probe now separates one-time weight upload from per-sequence input/state reset and prints weight upload ms ; timed cuda ms per token excludes the persistent weight upload. GGUF recurrent-layer tensor loading is routed through QwenRecurrentLayerRunner::Weights.load , so the probe no longer manually passes every raw tensor into the runner constructor. It is still a standalone one-layer probe, not an end-to-end Linux decoder. Build the recurrent multi-layer stack scaffold: crystal build -Dcpu only bin/cuda recurrent stack probe.cr -o build/cuda recurrent stack probe ./build/cuda recurrent stack probe \ --model /path/to/Qwen3.5-9B-Q4 K M.gguf \ --layers 0,2,4 \ --tokens 2 cuda recurrent stack probe chains multiple QwenRecurrentLayerRunner instances and compares the final hidden sequence plus each layer's recurrent conv/SSM state against the CPU reference. The default path hands each recurrent layer's CUDA output buffer directly to the next layer's CUDA input; --host-handoff keeps the older host-copy route as a debug oracle. This is still a recurrent-only scaffold, not an end-to-end Linux decoder. Build the mixed recurrent/full-attention CUDA stack probe: crystal build -Dcpu only bin/cuda mixed stack probe.cr -o build/cuda mixed stack probe ./build/cuda mixed stack probe \ --model /path/to/Qwen3.5-9B-Q4 K M.gguf \ --layers 0,1,2,3,4 \ --tokens 2 \ --start-pos 2 \ --max-seq 12 cuda mixed stack probe composes QwenRecurrentLayerRunner and QwenFullAttnLayerRunner in model layer order with device-resident hidden handoff across recurrent/full-attention boundaries, then runs QwenOutputHeadRunner for output RMSNorm, quantized lm-head projection, and resident top1. The layer/head loop is now owned by ML::CUDA::QwenMixedStackRunner , which is the first model-slice decode-state object for CUDA. By default the probe copies back only the CUDA top1 id/value plus hidden/state debug outputs; pass --read-logits to also copy full logits for attribution, and --profile-phases to insert per-layer/head synchronizations and print attribution lines. It compares the final hidden sequence, top1, recurrent conv/SSM states, and full-attention KV cache rows against the CPU reference. This is the first mixed-stack CUDA correctness scaffold through resident top1; it still stops before tokenizer/sampling, repeated full-model decode ownership, and an optimized topK/sampling kernel. The current resident top1 is a simple two-phase partial-scan/reduce kernel: correct, but not yet promoted as a speed-optimized head. Experimental CUDA exact cache-replay fast lane: crystal build --release --no-debug -Dcpu only \ bin/cuda mixed stack probe.cr \ -o build/cuda mixed stack probe ./build/cuda mixed stack probe \ --model /path/to/Qwen3.5-9B-Q4 K M.gguf \ --all-layers \ --max-seq 256 \ --greedy-loop-tokens 64 \ --greedy-loop-probe-chunk-gamma 16 \ --greedy-loop-probe-chunk-active-verify \ --greedy-loop-probe-ngram \ --greedy-loop-probe-ngram-source-history "$SOURCE TOKEN IDS" \ --greedy-loop-probe-ngram-replay-start 1 \ --greedy-loop-probe-ngram-cursor-only \ --greedy-loop-probe-ngram-trusted-source \ --greedy-loop-probe-ngram-schedule 64 \ --skip-debug-readback This path is exact verification of a validated source/cache cursor, not a general-purpose sampler. It first checks that the live prefix matches the source history at the replay cursor. If the prefix gate fails, the active verifier path is disabled before any proposals are trusted and the probe falls back to plain greedy target decode. If the gate passes, proposal chunks are verified through the same resident CUDA stack and rejected chunks restore the exact target state. Use smaller schedules such as 4,4,8,16 for weaker proposal sources where early reject economics matter; use bulk schedules such as 64 only for artifact-level trusted replay where the source history was produced by the same model/cache contract and every proposed token is still target-verified before commit. Trusted artifact restore lower-bound: ./build/cuda mixed stack probe \ --model /path/to/Qwen3.5-9B-Q4 K M.gguf \ --all-layers \ --max-seq 256 \ --known-replay-history "$SOURCE TOKEN IDS" \ --known-replay-start 1 \ --known-replay-tokens 64 \ --known-replay-trusted-artifact-restore \ --skip-debug-readback For a conservative host-backed artifact simulation, add: --known-replay-trusted-artifact-host-restore To snapshot only KV rows up to the restored cursor instead of the full max seq cache, use: --known-replay-trusted-artifact-live-kv To include diagnostic artifact file IO and hash timing, add: --known-replay-trusted-artifact-io-probe \ --known-replay-trusted-artifact-io-path /path/to/artifact.bin This is a different contract from verified replay. It simulates a cache artifact that already contains the exact token span and the post-span decode state for the same model/config/source hash. The timed region restores that state and emits cached tokens; it does not recompute the verifier body. Use it only for session/cache artifacts whose model hash, tokenizer hash, prefix hash, token span, and state artifact hash have been validated. The default trusted-artifact probe snapshots/restores state inside device memory and is a pure lower bound. The host-backed variant copies the complete decode state to host memory, poisons the runner, then times host-to-device restore. On the RTX 5060 Ti Qwen3.5-9B snapshot with max seq=128 , that state is 61,079,552 bytes; restore costs 12.259ms for a 64-token artifact and 12.331ms for a non-zero start9/24-token artifact. This is still not full production IO: durable lookup, disk reads, hashing, and live-length KV trimming remain outside the measured region. The live-KV variant preserves exact output while reducing only the full-attention cache portion. On the same host/model, start1/gen64 stores 56,885,248 bytes and restores in 11.358ms ; start9/gen24 stores 54,788,096 bytes and restores in 10.984ms . The modest delta is a useful finding: recurrent DeltaNet state, not KV capacity, dominates this short-context artifact. On the persistent reefy storage path, the live-KV start1/gen64 artifact breaks down into 52,690,944 recurrent-state bytes and 4,194,304 KV bytes. The diagnostic file path measured write 17.695ms , read 42.046ms , SHA-256 verification 59.556ms , and H2D restore 13.003ms . For start9/gen24, the artifact is 52,690,944 recurrent bytes plus 2,097,152 KV bytes; write/read/ hash/restore measured 18.249/42.994/53.814/12.825ms . The SHA-256 timing uses the host sha256sum / shasum tool to avoid adding OpenSSL linkage to the CUDA probe, so treat it as a product-boundary diagnostic rather than a kernel metric. An experimental contiguous-read reconstruction reduced start1/gen64 read time from 42.046ms to 36.310ms while preserving 64/64 exact output, but start9/ gen24 remained noisy 43.618ms read . This points to some avoidable allocation/ fragmentation cost, but not enough to change the main conclusion: cache artifacts need async prefetch or a resident artifact service to stay off the decode critical path. A first scalar CPU recurrent-state block-INT8 codec diagnostic compresses the 52,690,944 recurrent bytes to 13.18-14.00MB 25.0-26.6% depending on block size. On start1/gen64, block sizes 64/256/1024/4096 measured relative RMSE 0.008798/0.012300/0.015476/0.019087 ; encode/decode stayed around 457-469ms / 121-125ms . A follow-up restore gate now decodes the block-INT8 recurrent buffers back into a host snapshot, restores that state, and runs one continuation token against an exact uncompressed restored state. On the RTX 5060 Ti host, block sizes 64/256/1024/4096 preserved the same next top1 on start9/gen24, and block 256/4096 also preserved the same next top1 on a later start33/gen8 cursor. The multi-step source-aligned gate is stronger: block4096 preserved 16/16 continuation top1 ids on start9/gen24, and block256/block4096 preserved 8/8 continuation top1 ids on start33/gen8. The free-run greedy gate now starts both exact-restored and decoded-INT8-restored states from the same token and feeds back each path's own generated top1; block256 preserved 8/8 free-run ids on start33/gen8, and block4096 preserved 16/16 free-run ids on start9/gen24. A wider six-cursor free-run sweep changed the conclusion: block256 preserved 16/16 parity on all six tested cursors, and block1024 also preserved 16/16 on those cursors, but block4096 drifted on later cursors 15/16 , 15/16 , and 6/16 . So block4096 is not a trusted restore format; block1024 is the current best compression/parity trade-off candidate. This is still not a production codec: the current scalar CPU codec is too slow for the critical path, and these gates cover one prompt/history, not the full prompt distribution. Build the Metal bridge once: make build/bridge.o Build the practical generation demo: crystal build --release --no-debug \ --link-flags="$ pwd /build/bridge.o -framework Metal -framework Foundation -lc++" \ bin/qwen35 generate.cr \ -o build/qwen35 generate Run greedy generation: ./build/qwen35 generate "The capital of France is" 64 Enable exact n-gram speculative decode for repeated text: QWEN35 NGRAM DECODE=1 ./build/qwen35 generate "The capital of France is" 64 Use the conservative automatic decode policy: QWEN35 DECODE POLICY=auto ./build/qwen35 generate "The capital of France is" 64 auto is the product-safe proposal-aware profile: it uses exact n-gram/cache proposals only when they are large enough to amortize verification, enables the candidate-shape risk gate plus a runtime corridor detector for untrusted suffix replay, and otherwise falls back to exact target decoding without invoking the neural draft model. Enable exact neural speculative decode with the Qwen 3.5 0.8B draft: QWEN35 SPECULATIVE DECODE=1 \ QWEN35 HEAD FULL ROWS GUARDED=1 \ ./build/qwen35 generate "The capital of France is" 64 Experimental same-weight proposal-route memory: crystal build --release --no-debug \ bin/qwen35 proposal route memory.cr \ -o build/qwen35 proposal route memory QWEN35 ROUTE CAL ROOT=/tmp/qwen35 route memory \ QWEN35 ROUTE CAL GEN=16 \ QWEN35 ROUTE CAL GAMMA=4 \ QWEN35 ROUTE CAL UPDOWN RANK=4 \ QWEN35 ROUTE CAL UPDOWN LAYERS=0,2,4 \ scripts/qwen35 proposal route calibrate.sh The route cache stores certified proposal-body choices such as baseline or pca updown for the GPU self-spec probe path. It is intentionally not a qwen35 generate decode-policy switch yet: the product generator currently has greedy, n-gram/cache, external-draft speculative, and GGUF-MTP paths, while the PCA-updown same-weight proposal body lives in bin/qwen35 deltanet fixed basis probe.cr . Use route memory to avoid repeating online route calibration in that probe corridor; do not count it as a product generation speedup until the same verifier/proposal corridor is wired into the normal generator. qwen35 generate can resolve the route table with QWEN35 SELF SPEC ROUTE MEMORY ROOT for diagnostics and future wiring, but it prints product self spec=unsupported and leaves the decode path unchanged. If QWEN35 SELF SPEC UPDOWN ADAPTERS PATH is also set, the generator validates the referenced PCA-updown adapter artifact against the selected route layers and rank through ML::GGUF::Qwen35SelfSpecPlan , then reports plan=pca updown|invalid adapter|baseline|route miss and adapter artifact=valid|invalid without executing the adapter. The PCA-updown FFN adapter data path is now shared in src/ml/gguf/qwen35 ffn updown adapter.cr : it owns the centered low-rank projection math, Hadamard/symmetric quant-dequant helpers, and the qwen35 ffn updown adapter v1 artifact dump/load format. The heavy self-spec scheduler is still probe-local. Manual route lookup/seed example: ./build/qwen35 proposal route memory \ --root /tmp/qwen35 route memory \ --prompt "def square x : return x x\n" \ --route-key code square \ --route pca updown \ --rank 4 \ --layers 0,2,4 Enable Qwen chat-template prompting and XML-style function calls: QWEN35 CHAT=1 \ QWEN35 CHAT SYSTEM="You are a tool-using assistant." \ QWEN35 TOOLS JSON=' {"type":"function","function":{"name":"get weather","description":"Get weather for a city","parameters":{"type":"object","properties":{"city":{"type":"string"}},"required": "city" }}} ' \ ./build/qwen35 generate "Weather in Paris?" 64 QWEN35 TOOLS JSON uses the tool schema shape embedded in the Qwen tokenizer.chat template . Qwen 3.5/3.6 GGUFs use an XML-ish tool-call format, for example