前言

大家好,我是Dream。转眼间已经研二了,本科学的计算机,说实话当时C++学得挺痛苦的。指针、内存管理、模板这些东西,考试前临时抱佛脚勉强过了。真正让我重新拾起C++,是因为今年上半年做课题需要跑大模型,导师给的服务器GPU不够用,我就想着能不能优化一下推理速度。
这一折腾,反而让我发现了C++在AI时代的价值。不夸张地说,现在主流的LLM推理框架,底层基本都是C++写的。作为一个研究生,我觉得这个方向挺值得深入的,就把自己这几个月的摸索记录下来。

一、我的C++入坑经历

大二学C++那会儿,最头疼的就是指针和内存管理。我记得有次写链表作业,程序老是莫名其妙崩溃,调试了一整晚才发现是忘了初始化指针。那时候就想,要是有Python那么简单就好了。

后来读研,课题组主要用Python做实验,我也就很少碰C++了。直到今年3月份,我在跑一个文本生成任务,用的Hugging Facetransformers库。模型不大,7B参数,但推理速度慢得离谱,生成一句话要好几秒。

导师说GPU资源紧张,让我想办法优化。我就开始到处找资料,发现了llama.cpp这个项目。这玩意儿挺神奇的,纯CPU跑大模型,速度比我用PyTorch快好几倍。仔细一看,全是C++写的。

二、偶然接触到的LLM世界

先说说我的理解。现在大家谈AI,基本都在用Python。PyTorch、TensorFlow这些框架确实方便,几行代码就能搭个模型。但深入看,这些框架的底层实现,全是C++。

比如PyTorch,你写的Python代码最后会调用到LibTorch,这是个C++库。为什么要这么设计?因为性能。
LLM 推理最核心的工作是矩阵运算,尤其是大规模矩阵乘法。一个 7B 参数的模型,每生成一个 token,就得完成几十亿次浮点运算。这种计算量极大的任务,Python 根本扛不住,必须靠 C++ 这种更贴近硬件的语言做深度优化。
我做过个简单测试:同样的矩阵乘法,Python 用 numpy 跑要 50 毫秒,直接用 C++ 写只要 5 毫秒;要是再加上 SIMD 指令优化,能压到 2 毫秒以内。这 10 倍甚至 20 倍的速度差距,放到实际应用里,就是真金白银的成本差异。
更关键的是,做推理服务的时候,不可能每个用户请求都等好几秒。工业界要求P99延迟控制在几十毫秒,这就必须用C++来榨干硬件性能。

三、动手实践:用C++加速矩阵运算

看了一堆资料后,我决定自己动手试试。第一步就是实现一个高效的矩阵乘法,这是LLM推理的基础操作。

先写个最朴素的版本:

#include <iostream>
#include <vector>
#include <chrono>
#include <random>

using namespace std;

// 朴素的矩阵乘法
void matmul_naive(const vector<vector<float>>& A, 
                  const vector<vector<float>>& B,
                  vector<vector<float>>& C) {
    int M = A.size();
    int K = A[0].size();
    int N = B[0].size();
    
    for (int i = 0; i < M; i++) {
        for (int j = 0; j < N; j++) {
            float sum = 0.0f;
            for (int k = 0; k < K; k++) {
                sum += A[i][k] * B[k][j];
            }
            C[i][j] = sum;
        }
    }
}

// 测试函数
void test_matmul() {
    int M = 512, K = 512, N = 512;
    
    // 初始化矩阵
    vector<vector<float>> A(M, vector<float>(K));
    vector<vector<float>> B(K, vector<float>(N));
    vector<vector<float>> C(M, vector<float>(N, 0.0f));
    
    random_device rd;
    mt19937 gen(rd());
    uniform_real_distribution<> dis(0.0, 1.0);
    
    for (int i = 0; i < M; i++)
        for (int j = 0; j < K; j++)
            A[i][j] = dis(gen);
    
    for (int i = 0; i < K; i++)
        for (int j = 0; j < N; j++)
            B[i][j] = dis(gen);
    
    // 计时
    auto start = chrono::high_resolution_clock::now();
    matmul_naive(A, B, C);
    auto end = chrono::high_resolution_clock::now();
    
    auto duration = chrono::duration_cast<chrono::milliseconds>(end - start);
    cout << "朴素版本耗时: " << duration.count() << " ms" << endl;
    cout << "结果示例 C[0][0] = " << C[0][0] << endl;
}

int main() {
    test_matmul();
    return 0;
}

编译运行:

g++ -O2 -o matmul matmul.cpp
./matmul

运行结果展示:
在这里插入图片描述

我的笔记本跑出来大概2000ms!这个速度对于LLM来说完全不够用。

然后我查资料学习了缓存优化技术,改进了一下:

// 分块矩阵乘法,提高缓存命中率
void matmul_blocked(const vector<vector<float>>& A,
                    const vector<vector<float>>& B,
                    vector<vector<float>>& C) {
    int M = A.size();
    int K = A[0].size();
    int N = B[0].size();
    int block_size = 64; // 分块大小
    
    for (int i0 = 0; i0 < M; i0 += block_size) {
        for (int j0 = 0; j0 < N; j0 += block_size) {
            for (int k0 = 0; k0 < K; k0 += block_size) {
                // 处理一个块
                int i_end = min(i0 + block_size, M);
                int j_end = min(j0 + block_size, N);
                int k_end = min(k0 + block_size, K);
                
                for (int i = i0; i < i_end; i++) {
                    for (int j = j0; j < j_end; j++) {
                        float sum = C[i][j];
                        for (int k = k0; k < k_end; k++) {
                            sum += A[i][k] * B[k][j];
                        }
                        C[i][j] = sum;
                    }
                }
            }
        }
    }
}

这个版本利用了CPU缓存的局部性原理,把大矩阵分成小块处理。同样的数据,耗时降到了120ms左右,快了一倍多。

但这还不够。真正的LLM推理引擎会用到更多技术,比如:

  • 向量化指令(SIMD):一次计算多个数据
  • 多线程并行:充分利用多核CPU
  • 量化技术:用int8代替float32,减少计算量

这些优化叠加起来,能让性能再提升5-10倍。

四、深入llama.cpp:看懂推理引擎

搞明白矩阵乘法后,llama.cpp 确实是个很适合深入学习 LLM 推理底层逻辑的项目,它把复杂的模型推理流程拆解得很清晰,从权重加载、token 处理到每一层的矩阵运算、激活函数计算,都能在代码里找到对应实现。

核心的推理逻辑在llama.cpp文件里,我挑几个关键点说说:

1. 模型加载与量化

LLM模型动辄几个GB,llama.cpp用了GGUF格式存储,支持多种量化方案。量化的意思是把float32的权重转成int8或者int4,模型大小能缩小4-8倍。

// 简化的量化示例
struct QuantizedWeight {
    vector<int8_t> data;  // 量化后的数据
    float scale;          // 缩放因子
    float zero_point;     // 零点
};

// 量化过程
void quantize(const vector<float>& weights, 
              QuantizedWeight& qweight) {
    float min_val = *min_element(weights.begin(), weights.end());
    float max_val = *max_element(weights.begin(), weights.end());
    
    qweight.scale = (max_val - min_val) / 255.0f;
    qweight.zero_point = min_val;
    
    qweight.data.resize(weights.size());
    for (size_t i = 0; i < weights.size(); i++) {
        float normalized = (weights[i] - min_val) / qweight.scale;
        qweight.data[i] = static_cast<int8_t>(round(normalized));
    }
}

// 反量化
float dequantize(int8_t value, float scale, float zero_point) {
    return value * scale + zero_point;
}

量化虽然会损失一点精度,但在大多数任务上影响不大,换来的是更快的速度和更小的内存占用。这对于在普通电脑上跑模型特别重要。

2. 注意力机制的实现

Transformer的核心是Self-Attention,也就是Attention(Q, K, V),如下面的图所示:
在这里插入图片描述

看起来简单,但实现起来有很多细节。llama.cpp里用了一些技巧,比如Flash Attention的思想,减少中间结果的内存占用。

// 简化的attention计算
void compute_attention(const vector<float>& Q,  // [seq_len, d_model]
                       const vector<float>& K,
                       const vector<float>& V,
                       vector<float>& output,
                       int seq_len, int d_model) {
    float scale = 1.0f / sqrt(d_model);
    
    // QK^T
    vector<float> scores(seq_len * seq_len);
    for (int i = 0; i < seq_len; i++) {
        for (int j = 0; j < seq_len; j++) {
            float sum = 0.0f;
            for (int k = 0; k < d_model; k++) {
                sum += Q[i * d_model + k] * K[j * d_model + k];
            }
            scores[i * seq_len + j] = sum * scale;
        }
    }
    
    // Softmax
    for (int i = 0; i < seq_len; i++) {
        float max_score = -1e9;
        for (int j = 0; j < seq_len; j++) {
            max_score = max(max_score, scores[i * seq_len + j]);
        }
        
        float sum_exp = 0.0f;
        for (int j = 0; j < seq_len; j++) {
            scores[i * seq_len + j] = exp(scores[i * seq_len + j] - max_score);
            sum_exp += scores[i * seq_len + j];
        }
        
        for (int j = 0; j < seq_len; j++) {
            scores[i * seq_len + j] /= sum_exp;
        }
    }
    
    // 乘以V
    for (int i = 0; i < seq_len; i++) {
        for (int k = 0; k < d_model; k++) {
            float sum = 0.0f;
            for (int j = 0; j < seq_len; j++) {
                sum += scores[i * seq_len + j] * V[j * d_model + k];
            }
            output[i * d_model + k] = sum;
        }
    }
}

这段代码演示了attention的基本流程。实际项目里还会做很多优化,比如KV Cache(缓存之前的K和V,避免重复计算)、多头注意力的并行等等。

3. 推理流程

整个推理过程大概是这样的:

// 简化的推理流程
class SimpleLLM {
private:
    // 模型参数(简化)
    vector<vector<float>> embedding;  // token嵌入
    vector<vector<float>> W_q, W_k, W_v;  // attention权重
    vector<vector<float>> W_out;  // 输出层权重
    
public:
    vector<int> generate(const vector<int>& input_tokens, 
                        int max_new_tokens) {
        vector<int> output = input_tokens;
        
        for (int step = 0; step < max_new_tokens; step++) {
            // 1. Token embedding
            vector<float> hidden_states = embed_tokens(output);
            
            // 2. Transformer层(简化为单层)
            hidden_states = transformer_layer(hidden_states);
            
            // 3. 输出层,得到下一个token的概率分布
            vector<float> logits = output_layer(hidden_states);
            
            // 4. 采样(简单取最大值)
            int next_token = argmax(logits);
            output.push_back(next_token);
            
            // 5. 如果遇到结束符,停止生成
            if (next_token == EOS_TOKEN) break;
        }
        
        return output;
    }
    
private:
    vector<float> embed_tokens(const vector<int>& tokens) {
        // 简化实现
        return vector<float>();
    }
    
    vector<float> transformer_layer(const vector<float>& input) {
        // Self-attention + FFN
        return input;
    }
    
    vector<float> output_layer(const vector<float>& hidden) {
        // 线性层 + softmax
        return vector<float>();
    }
    
    int argmax(const vector<float>& vec) {
        return max_element(vec.begin(), vec.end()) - vec.begin();
    }
    
    const int EOS_TOKEN = 2;  // 结束符ID
};

这只是个框架,真实的LLM有几十层Transformer,还有RoPE位置编码、RMSNorm归一化、SwiGLU激活函数等等。但核心思路是一样的:不断用前面的tokens预测下一个token,直到生成结束。

五、自己写个简单的推理demo

看懂原理后,我尝试写了个超简化版的文本生成demo。不用真实模型,就模拟一下推理流程:

#include <iostream>
#include <vector>
#include <string>
#include <map>
#include <random>
#include <cmath>

using namespace std;

class TinyLLM {
private:
    map<string, int> vocab;  // 词表
    vector<string> id2token;
    int vocab_size;
    int embed_dim;
    
    // 简化的"模型参数"(随机初始化)
    vector<vector<float>> embedding;
    vector<vector<float>> output_weight;
    
    random_device rd;
    mt19937 gen;
    
public:
    TinyLLM(int embed_dim = 64) : embed_dim(embed_dim), gen(rd()) {
        // 构建一个小词表
        vector<string> words = {
            "<pad>", "<eos>", "hello", "world", "I", "am", 
            "a", "student", "studying", "C++", "and", "AI"
        };
        
        vocab_size = words.size();
        for (int i = 0; i < vocab_size; i++) {
            vocab[words[i]] = i;
            id2token.push_back(words[i]);
        }
        
        // 随机初始化embedding和输出权重
        embedding.resize(vocab_size, vector<float>(embed_dim));
        output_weight.resize(embed_dim, vector<float>(vocab_size));
        
        uniform_real_distribution<> dis(-0.1, 0.1);
        for (int i = 0; i < vocab_size; i++)
            for (int j = 0; j < embed_dim; j++)
                embedding[i][j] = dis(gen);
        
        for (int i = 0; i < embed_dim; i++)
            for (int j = 0; j < vocab_size; j++)
                output_weight[i][j] = dis(gen);
    }
    
    // Token转ID
    vector<int> tokenize(const vector<string>& tokens) {
        vector<int> ids;
        for (const auto& t : tokens) {
            if (vocab.count(t)) {
                ids.push_back(vocab[t]);
            }
        }
        return ids;
    }
    
    // 简单的前向传播
    vector<float> forward(const vector<int>& input_ids) {
        // 1. 取最后一个token的embedding
        int last_token = input_ids.back();
        vector<float> hidden = embedding[last_token];
        
        // 2. 这里省略Transformer层,直接到输出层
        // 计算logits = hidden * output_weight
        vector<float> logits(vocab_size, 0.0f);
        for (int i = 0; i < vocab_size; i++) {
            for (int j = 0; j < embed_dim; j++) {
                logits[i] += hidden[j] * output_weight[j][i];
            }
        }
        
        // 3. Softmax
        float max_logit = *max_element(logits.begin(), logits.end());
        float sum = 0.0f;
        for (int i = 0; i < vocab_size; i++) {
            logits[i] = exp(logits[i] - max_logit);
            sum += logits[i];
        }
        for (int i = 0; i < vocab_size; i++) {
            logits[i] /= sum;
        }
        
        return logits;
    }
    
    // 生成文本
    string generate(const vector<string>& prompt, int max_len = 10) {
        vector<int> tokens = tokenize(prompt);
        string result;
        
        for (const auto& t : prompt) {
            result += t + " ";
        }
        
        for (int i = 0; i < max_len; i++) {
            vector<float> probs = forward(tokens);
            
            // 采样(这里用top-1,即贪心)
            int next_token = max_element(probs.begin(), probs.end()) - probs.begin();
            
            if (next_token == vocab["<eos>"]) break;
            
            tokens.push_back(next_token);
            result += id2token[next_token] + " ";
        }
        
        return result;
    }
    
    void show_vocab() {
        cout << "词表大小: " << vocab_size << endl;
        cout << "词表内容: ";
        for (const auto& t : id2token) {
            cout << t << " ";
        }
        cout << endl << endl;
    }
};

int main() {
    cout << "=== 简单LLM推理演示 ===" << endl << endl;
    
    TinyLLM model(64);
    model.show_vocab();
    
    vector<string> prompt = {"I", "am"};
    cout << "输入提示: ";
    for (const auto& t : prompt) cout << t << " ";
    cout << endl;
    
    string output = model.generate(prompt, 5);
    cout << "生成结果: " << output << endl;
    
    cout << endl << "注意:这是随机初始化的模型,输出没有实际意义" << endl;
    cout << "真实LLM需要在大规模语料上训练才能生成有意义的文本" << endl;
    
    return 0;
}

编译运行:

g++ -O2 -std=c++11 -o tiny_llm tiny_llm.cpp
./tiny_llm

输出结果如下:

词表大小: 12
词表内容: <pad> <eos> hello world I am a student studying C++ and AI 

输入提示: I am 
生成结果: I am a student studying C++ and 

注意:这是随机初始化的模型,输出没有实际意义
真实LLM需要在大规模语料上训练才能生成有意义的文本

这个demo很粗糙,但展示了推理的基本流程:embedding → 计算 → 输出概率 → 采样。真实模型复杂得多,但底层逻辑是相通的。

六、踩过的坑和收获

这几个月折腾下来,踩了不少坑,也有一些收获。

1.内存管理依然很头疼

用C++写LLM相关的代码,内存管理是个大问题。一不小心就内存泄漏或者访问越界。我现在养成习惯,尽量用智能指针和STL容器,少用裸指针。但有些底层优化还是得手动管理内存,这时候valgrind就派上用场了。

2.浮点数精度问题

实现softmax的时候,我直接算exp,结果数值溢出了。后来学到了数值稳定的技巧:先减去最大值再算exp。这种细节在做数值计算时特别重要。

3.性能优化是个无底洞

开始我以为加个多线程就能快很多,结果发现线程同步的开销反而让程序变慢了。后来才明白,性能优化要先profile找瓶颈,不能盲目优化。而且很多优化技巧是相互冲突的,需要权衡。

以前觉得C++又老又难学,现在发现它在系统底层和高性能计算领域是真香。Python适合快速原型开发,但要做生产级的推理服务,C++几乎是唯一选择。

推荐几个我常看的:

  • llama.cpp项目:代码质量高,注释详细
  • GGML库:专门做tensor运算的C库,很轻量
  • Andrej Karpathy的视频:讲解深入浅出
  • 3Blue1Brown的动画:可视化理解attention

展望

说实话,AI这个领域变化太快了,一个月不看论文就感觉跟不上了。但底层的东西,比如矩阵运算、内存优化、并行计算这些,是不会过时的。扎实的C++功底,会是一个长期的竞争优势。

最后分享一个感受:学技术不要贪多,先把一个方向弄透。我现在的策略是,Python用来做实验验证想法,C++用来做性能优化和系统实现。两条腿走路,比只会一种语言要强。

好书推荐:从单点模型到自主智能系统——聚焦5大核心模块×3大高潜场景×4大技术前沿,构建具备感知、决策与进化能力的AI智能体。
在这里插入图片描述
1.全链路系统性讲解:从AI Agent的定义与分类、核心特征,到感知、决策、规划、推理和学习等具体模块的设计与实现,内容覆盖全面,结构层次清晰,兼顾理论深度与实践指导。
2.模块化与场景驱动:详细阐述分层架构设计、模块间的数据流优化及容错机制,并结合智能客服、自动驾驶、量化交易等多个实际案例,展示如何在不同应用场景中灵活选型和搭建AI Agent系统。
3.工具链与技术生态整合:介绍包括TensorFlow/PyTorch扩展、ROS集成、OpenAI Gym等在内的主流开发工具和仿真与测试环境,帮助开发者构建从研发到部署的完整工程体系,体现了技术研发与工程实践的无缝衔接。
4.前沿技术与未来趋势探索:不仅聚焦当前的核心技术,还对多Agent协作、深度强化学习、在线学习与自我改进等前沿技术展开深入探讨,为读者迈向更高水平AI系统开发提供理论和实践指导。
5.图解方式和案例实践:通过丰富的图示、流程图和代码实例,将复杂的底层架构原理直观化、具体化,降低理解门槛,既适合研究人员深入学习,也为从业者提供了实用的工程参考。
内容简介
本书系统阐述了统一AI Agent底层架构的设计与实现,内涵涵盖从基本理论到实际应用的多个层面,内容丰富且深入。全书共11章,从通用AI Agent的定义、分类与核心特征入手,逐步展开至开发工具链、仿真测试环境以及各类典型架构模式的详细解析。书中不仅探讨了感知、决策、规划、推理和学习等关键模块的技术原理,还深入剖析了多Agent协同与群体智能等复杂场景下的设计策略。通过对智能客服、自动驾驶和量化交易等多个实际案例的详细讲解,展现了统一AI Agent在不同应用领域的广泛潜力和技术演进路径。全书理论与实践并重,既适合人工智能研究人员深入学习,也为从业者提供了详尽的设计指南和工程实现方案,为迈向更高水平的AI系统开发奠定坚实基础。
自取链接:传送门

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐