【笔记】用agent手搓agent(一)

今天我们从cursor手搓cursor进入下一个篇章,开始向泛化的agent去开发agent,进一步开始研究agent,向RSI进发。

首先想把llama.cpp的技术都熟悉一遍。于是挑了一个相对好写一些的语言golang,准备针对性地单独跑模型;现在用得最多的快速模型还是q wen3.6:35b,那就写一个引擎运行这个模型吧。本来打算用gemini直接写,这货给我写了个request来了,用if然后生成template返回static string!于是用qwen3.8:flash-next让它从0开始抄llama.cpp和ds4的代码,手搓metal和golang,大概搓了两天,先把bge-m3和embedding跑通了,qwen3.6:35b只是开了个头。之后勉强用gemini 3.8这种也比较弱的模型fix了一些收尾调试验证工作,于是qwen3.6:35b MXFP4 (qwen35moe)最终是跑起来了。

一开始我以为MXFP4和Q4都很纯粹,让它只实现Q4, MXFP4, BF16,结果跑不起来。实现了gguf的dump以后,各个层可能用的量化都不一样,所以要从Q4, Q5, Q6, Q8, ...全部要实现一下。最初的时候是只用 GEMV,速度是 prefill 20 token/s, generate 20 token/s。跑了下llama.cpp,多少?prefill 400 token/s, generate 55 token/s。这差的有点多了吧,于是让gemini读llama.cpp的代码,它改进了下prefill和generate都能到40 token/s。但是到这里似乎就卡住了。于是正好测下自己的agent,用llama.cpp运行的qwen3.6:35b去读我们的代码和llama.cpp的代码,让它分析下瓶颈在哪里。附录给出了它的结论,虽然比较粗,但是还是可以说明些问题的,之后当然是把这个结论直接交给qwen3.8:flash-next,让它边读代码,边改进;于是它将一部分GEMV实现变成了GEMM的batch实现,这个时候prefill变成了80 token/s,generate却减到了30 token/s。看来metal的调试后面是要自己参与学习和深入调优了。现在的引擎还没到能彻底实战的时候。

下面的计划是,死磕一段时间performance,把prefill和generate都提高到和llama.cpp差不多的水平,之后开始研究下hook LoRA训练一下router+专项动态激活LoRA,主要也是想沿着engram的思路,尝试从参数层面探索外置知识。

其实整个大模型的世界可以归结为两种递归收敛:一个是训练过程,或者程序员更熟悉的compile time,在训练过程中,使用loss,让它收敛到一个比较泛化的智能水准。第二种当然是runtime的收敛,就是比如我用thinking,一直think一直产生tokens,这些tokens会将整个vector拉向一个方向从而加强某些方面的信号;agent或者说harness/loop其实也是这个思路,在runtime使用逐步的形式加强某些方面的信号,让它收敛到我们预期的水准输出。这个就是大模型使用的控制。所以我们要compile time和runtime都体验下,感觉下下一个方向是什么。现在的大模型思路太快了,比如我们写代码,我们是可以问问题然后一步一步说我要写什么函数如何实现整体功能,大模型目前是看到目标,直接想一步就完成,最后造成眼高手低的崩坏。

附录:

Performance Analysis: Golang Metal vs. llama.cpp Metal

The significant performance disparity between your Golang implementation (~40 t/s for both prefill and generation) and llama.cpp (~400 t/s prefill, ~55 t/s generation) is caused by inefficient GPU kernel design in the Golang Metal shaders. Specifically, the Golang implementation suffers from low GPU occupancy , lack of shared memory optimization , and inefficient kernel dispatch strategies.

Below is the detailed technical breakdown of the bottlenecks in both phases.


1. Generation Phase (40 t/s vs. 55 t/s)

Primary Bottleneck: Low GPU Occupancy and Inefficient GEMV Kernels

Golang Implementation (metal/shaders/40_gemv.metal)

  • Tiny Thread Groups: The GEMV kernel uses a thread group size of 32 threads to compute only 1 output row. Apple Silicon (M-series) GPUs have wide SIMD widths (16 lanes). A 32-thread group only utilizes 2 SIMD groups, leaving the majority of the GPU's compute units idle.
  • No Shared Memory: The kernel does not use threadgroup memory to cache weights or inputs. Every weight chunk and input vector element is fetched from global memory for every row, saturating memory bandwidth.
  • Inefficient Reduction: It relies solely on simd_sum within the small 32-thread group, which does not effectively aggregate results across the larger GPU chip.

llama.cpp Implementation (local/llama.cpp/ggml/src/ggml-metal/kernels/mul_mv.metal)

  • High Occupancy: llama.cpp uses large thread groups configured via function constants (e.g., processing NR0 rows simultaneously with multiple SIMD groups). This allows hundreds of threads per kernel launch, maximizing GPU utilization.
  • Shared Memory Reduction: It uses threadgroup memory (shmem) and a dedicated reduction helper (helper_mv_reduce_and_write) to cache intermediate results and efficiently reduce across SIMD groups, minimizing global memory writes.
  • Vectorized Dequantization: It employs optimized inline dequantization functions that use bitwise operations and select instructions, which are much faster and more branch-efficient than the generic dequantization calls in the Golang code.

2. Prefill Phase (40 t/s vs. 400 t/s)

Primary Bottleneck: Single-Query Dispatch and Lack of KV Tiling

Golang Implementation (metal/shaders/60_attn.metal)

  • Single-Query Kernel Dispatch: The attention kernel computes one (query head, query token) pair per thread group launch. For a prefill sequence of NNN tokens and HHH heads, this results in N×HN \times HN×H separate kernel launches. This creates massive kernel launch overhead and serialization bottlenecks.
  • Low Occupancy: Like the generation phase, it uses only 32 threads per thread group, resulting in extremely low GPU occupancy.
  • No KV Cache Tiling: While the Query (Q) is loaded into shared memory, the Key (K) and Value (V) matrices are accessed directly from global memory in a sequential loop (for (int j = 0; j < kv_len; ++j)). This leads to strided, uncoalesced global memory accesses and poor bandwidth utilization.
  • No Quantization Support: The kernel assumes float inputs, bypassing any potential optimizations for quantized KV caches (GGUF format) that llama.cpp handles efficiently.

llama.cpp Implementation (local/llama.cpp/ggml/src/ggml-metal/kernels/fa.metal)

  • Modular and Fused Kernels: llama.cpp uses a modular pipeline with separate, optimized kernels for dequantization (kernel_flash_attn_ext_kv_f16), padding (kernel_flash_attn_ext_pad), and mask scanning (kernel_flash_attn_ext_blk). This allows specialized processing for each step.
  • Shared Memory Tiling: The main attention kernel (kernel_flash_attn_ext_impl) extensively uses threadgroup memory (shmem_f16) to tile Q, K, V, and intermediate results. This minimizes global memory accesses and maximizes memory bandwidth reuse.
  • High Occupancy Block Processing: It processes blocks of queries and KV cache items simultaneously in large thread groups, ensuring high GPU occupancy and efficient parallelism.
  • Quantization Optimization: It has native, efficient support for GGUF quantized types (Q4_K, Q5_K, etc.) with specialized dequantization kernels that feed directly into the attention calculation.

Summary of Key Differences

Feature Golang Implementation llama.cpp Impact
Thread Group Size 32 threads (Very Low) 256+ threads (High) Major: Golang leaves GPU cores idle.
Occupancy Low (1-4 rows per group) High (Multiple rows/blocks) Major: Poor utilization of M-series silicon.
Shared Memory None for KV/Weights Extensive Tiling (Q,K,V,O,S) Major: Golang saturates memory bandwidth; llama.cpp hides latency.
Dispatch Strategy Per-Token/Per-Row (Many launches) Batched/Block-based (Few launches) High: Golang has high CPU/GPU kernel launch overhead.
Dequantization Generic, branch-heavy Bitwise, SIMD-optimized Moderate: llama.cpp processes quantized data faster.
Attention Algo Sequential KV access Tiled KV access High: llama.cpp maximizes bandwidth reuse during prefill.

Recommendation

To bridge the performance gap, the Golang implementation needs to be refactored to match llama.cpp's Metal shader architecture:

  1. Increase Thread Group Sizes: Increase thread groups from 32 to at least 256+ threads to improve occupancy.
  2. Implement Shared Memory Tiling: Use threadgroup memory to cache K and V blocks during attention and weights during GEMV.
  3. Batch Kernel Dispatch: Change the prefill kernel to process multiple tokens/heads per launch rather than one-by-one.
  4. Optimize Dequantization: Adopt bitwise and SIMD-optimized dequantization techniques similar to llama.cpp.
相关推荐
waterfeeling4 小时前
用 Context7 做一次点云配准的调研—测试—报告闭环:鲁棒 ICP
人工智能
Data-Miner7 小时前
面对 AI 大模型智能涌现,如何从技术层面保护好隐私与数据安全?
大数据·人工智能
hpoenixf8 小时前
当 AI 开始谈“买什么”,研究助手离投顾和交易还有多远?
人工智能
大模型真好玩8 小时前
如何让视频模型对你言听计从?结构化提示词助你生成电影级画面
人工智能·agent·deepseek
zhang133830890758 小时前
传统机械三杯式风速传感器易磨损冻堵?CG-88金属超声波风速风向传感器给出免运维方案
运维·服务器·网络·人工智能·自动化
特立独行的猫a8 小时前
会说确认叫车才下单-启元机器人官方滴滴打车技能源码详解
人工智能·机器人·技能·启元机器人
老金带你玩AI8 小时前
GPT-6.1 Terra上线:更便宜了,干活到底怎么样?
人工智能
会议咨询8 小时前
2027年电力电子、人工智能与大数据国际会议(PEABD 2027)
大数据·人工智能·电力电子
Jsundoku9 小时前
第五章 深度学习 -- 卷积神经网络
人工智能·深度学习·cnn
水如烟9 小时前
孤能子视角:SEP《人工智能的认识论》EIS读法·理解——理解幻觉与解压缩幻觉:同一缺口,两份报告
人工智能