告别 GPU 空转!token 级调度,Qwen/Llama 高并发推理引擎

大模型推理正在从"云端独占"走向"端侧可用"。过去我们做推理优化,更多关注的是服务器上的吞吐和并发;现在越来越多场景要求在一块消费级 GPU、一台边缘设备,甚至本地开发机上跑通 LLM。

本文拆解一个面向生产环境的轻量级 CUDA 大模型推理引擎,它主打 vLLM 风格的连续批处理(Continuous Batching) ,用 C++20 和 CUDA 手写核心推理路径,支持 Llama-3.2 和 Qwen3 系列模型。重点讲清楚它如何在 Paged Attention、INT8 量化、GPU 算子优化、两阶段调度器 这些方向上做工程化落地。

一、它解决什么问题?

这个推理引擎的核心定位不是"再训练一个大模型",而是把大模型推理链路做得更轻、更快、更适合生产部署。

它主要面向几类需求:

  • 需要高并发推理服务,同时处理大量用户请求;
  • 想理解 vLLM 的 Paged Attention 和连续批处理在 C++ 里怎么落地;
  • 想研究 INT8 量化对推理速度和精度的影响;
  • 想搭建一个兼容 OpenAI API 的本地推理服务;
  • 想学习现代 C++20 在 AI 基础设施中的工程实践。

相比 PyTorch + Transformers 的完整管线,它更偏"精简实现 + 性能极致"。它没有追求覆盖所有模型家族,而是围绕 Llama 系列和 Qwen 系列,把推理里最关键的几个加速点打通。

性能数据

在 NVIDIA A100 上测试 Llama 3.2 1B 模型(INT8 量化)的表现:

场景 该引擎 llama.cpp 加速比
单请求推理 185 tok/s 252 tok/s 0.73x(单请求略慢)
批处理 Batch=4 410 tok/s 252 tok/s 1.63x
批处理 Batch=8 720 tok/s 255 tok/s 2.82x
批处理 Batch=16 787 tok/s 253 tok/s 3.07x
TTFT(100 token prompt) 387ms - -

可以看到,单请求场景下它比 llama.cpp 慢一些------因为它的重心不在单请求延迟,而在批处理吞吐。当并发请求数上来后,连续批处理的优势就明显了:batch=16 时达到 787 tok/s,是 llama.cpp 的 3 倍多。


二、行业技术知识点

1. 连续批处理(Continuous Batching):vLLM 的核心思想

传统批处理有个痛点:一批请求里,有的请求生成得快,有的生成得慢。等慢的请求生成完了才能开始下一批,GPU 利用率很低。

连续批处理的核心思想是:在 token 级别动态调度请求,而不是等整批完成才切换。

具体做法:

  1. 调度器维护一个请求队列,每个请求有状态(等待中、运行中、已完成);
  2. 每一轮推理,先继续所有运行中的请求(不管它们还差多少 token 生成完);
  3. 然后从等待队列里塞入新请求,填满剩余 GPU 容量;
  4. 新请求可以随时加入,不用等当前批次结束。

这就好比高速公路上的车流------传统批处理是"等一整车人到了才发车",连续批处理是"随时有人上车,车满了就走"。

2. Paged Attention:块级 KV Cache 管理

LLM 推理中,KV Cache 是显存杀手。序列越长,占的显存越多。传统做法是预分配最大长度的显存,浪费严重。

Paged Attention 借鉴了操作系统虚拟内存的思想:

  • 把 KV Cache 切成固定大小的块(Block),比如每个块 16 个 token;
  • 每个请求动态分配需要的块,不用时释放;
  • 用一个**块表(Block Table)**记录每个请求的块映射关系;
  • Attention 计算时通过块表间接访问 KV Cache,而不是连续内存。

这样做的收益:

  • 显存利用率大幅提升,不再预分配最大长度;
  • 支持请求的动态创建和销毁;
  • 块级操作可以批量执行,减少 kernel 启动开销。

3. INT8 量化:cuBLASLt 加速

INT8 量化把权重从 FP16(16 位浮点)压缩到 INT8(8 位整数),显存占用减半,理论计算吞吐量翻倍。

这个引擎的量化实现路径:

  • 使用分组量化(Group-wise Quantization),每组 32/64/128 个权重共享一个 scale 参数;
  • 利用 cuBLASLt 库的 INT8 GEMM 内核,调用 GPU 上的 Tensor Core 执行整数矩阵乘法;
  • 量化后 perplexity 下降控制在 1% 以内,实际效果可接受。

4. GPU 算子优化手段

这个引擎在算子层面做了不少优化:

  • float4 向量化加载:每次加载 4 个 float(16 字节),利用 GPU 内存带宽的 2-4 倍效率提升;
  • 融合操作(Fused Operations):把采样、RoPE、归一化等操作融合到更少 kernel 里,减少全局内存访问;
  • CUB BlockReduce 优化 Softmax:用 CUB 库的并行归约实现数值稳定的 softmax,避免溢出;
  • 零拷贝设计:大量使用 std::span 和 move 语义,减少不必要的内存拷贝。

三、架构设计思路

整体架构可以分为四层:

复制代码
+---------------------------------------------------------------+
|                    Web UI / HTTP API 层                        |
|  内置 Web 界面 + OpenAI 兼容 API 服务                           |
+---------------------------------------------------------------+
|                 C++ 推理引擎层(核心)                          |
|  ContinuousBatchEngine + Scheduler + LLaMAModel                |
+---------------------------------------------------------------+
|                 GPU 算子层                                     |
|  MHA(RoPE) | RMSNorm | SwiGLU | Matmul | Embedding | Add      |
+---------------------------------------------------------------+
|                 CUDA Kernel 层                                 |
|  PagedAttention | float4加载 | CUB BlockReduce | GPU采样        |
+---------------------------------------------------------------+

项目目录结构:

复制代码
project/
├── include/photon/
│   ├── arch/              # 模型架构定义(LLaMA、配置)
│   ├── core/              # 核心类型(Tensor、错误处理 Result<T,E>)
│   ├── io/                # 模型加载、分词器、checkpoint
│   ├── ops/               # 算子接口定义
│   │   └── kernels/       # CUDA kernel 实现
│   ├── runtime/           # 块管理器、KV缓存管理器
│   └── scheduler/         # 连续批处理调度器、引擎
├── src/
│   ├── arch/              # config.cpp, llama_model.cpp, transformer_block.cpp
│   ├── core/              # quant.cpp, tensor.cpp, thrust_allocator.cu
│   ├── io/                # checkpoint.cpp, model_loader.cpp, tokenizer.cpp
│   ├── ops/               # add.cpp, embedding.cpp, matmul.cpp,
│   │                       # mha.cpp, rmsnorm.cpp, rope.cpp, swiglu.cpp
│   │   └── kernels/       # CUDA kernel 源码
│   └── runtime/           # block_manager.cpp, block_table.cpp, kv_cache_manager.cpp
├── web/                   # Web UI 静态文件
├── docker/                # Docker 镜像配置
├── demo/                  # 演示程序
├── tests/                 # 单元测试
└── CMakeLists.txt         # CMake 构建配置

这种分层的好处很明确:

  • ops/ 层定义算子接口,arch/ 层组合算子构建模型,runtime/ 层管理内存,scheduler/ 层负责调度------职责清晰,各自独立演进;
  • 通过 PHOTON_USE_CUDA 编译宏可以切换 CPU/CUDA 后端,方便调试;
  • C++20 的 Concepts 和 Ranges 用在编译期约束算子接口,类型安全有保障。

四、核心代码实现原理

1. 连续批处理引擎:推理主循环

引擎的核心类是 ContinuousBatchEngine,它整合了调度器和模型,实现 token 级别的连续批处理。

构造函数接收模型引用、分词器、最大批次大小和 chunk size(预填充块大小,默认 256):

cpp 复制代码
class ContinuousBatchEngine {
public:
    ContinuousBatchEngine(
        model::LLaMAModel& model,
        const model::TikTokenizer& tokenizer,
        i32 max_batch_size,
        i32 chunk_size = 256);

    i64 add_request(const std::string& prompt, i32 max_new_tokens);
    Result<void> run_until_complete(bool print_progress = false);
    std::string get_result(i64 request_id);

private:
    Result<void> execute_batch_step(const SchedulerBatch& batch);

    model::LLaMAModel& model_;
    const model::TikTokenizer& tokenizer_;
    i32 max_batch_size_;
    i32 chunk_size_;
    ContinuousBatchScheduler scheduler_;

#ifdef PHOTON_USE_CUDA
    i32* sampled_tokens_gpu_;
#endif
};

run_until_complete 的主循环逻辑:

cpp 复制代码
Result<void> run_until_complete(bool print_progress = false) {
    i32 step = 0;

    while (scheduler_.has_work()) {
        // 1. 调度下一批
        auto batch = scheduler_.schedule_next_batch();
        if (batch.empty()) break;

        // 2. 执行批次
        auto result = execute_batch_step(batch);
        if (!result) return Err<void>(result.error());

        step++;
    }
    return Ok();
}

2. execute_batch_step:变长批处理的核心

这是整个引擎最核心的函数,实现了 vLLM 风格的变长批处理:

cpp 复制代码
Result<void> execute_batch_step(const SchedulerBatch& batch) {
    i32 num_seqs = batch.batch_size();

    // 构建变长批次:把不同长度的请求flatten成统一表示
    std::vector<i32> all_tokens;
    std::vector<i32> all_positions;
    std::vector<i32> seq_ids;
    std::vector<i32> seq_lens;

    for (i32 i = 0; i < num_seqs; ++i) {
        auto& req = batch.requests[i];

        // 获取下一个chunk(prefill请求处理chunk_size个token,decode请求处理1个)
        auto chunk_tokens = req->get_next_chunk_tokens(chunk_size_);
        auto chunk_positions = req->get_next_chunk_positions(chunk_size_);
        i32 chunk_len = static_cast<i32>(chunk_tokens.size());
        seq_lens.push_back(chunk_len);

        // 追加到扁平化批次
        all_tokens.insert(all_tokens.end(), chunk_tokens.begin(), chunk_tokens.end());
        all_positions.insert(all_positions.end(), chunk_positions.begin(), chunk_positions.end());

        // 重复seq_id用于chunk中的每个token
        for (i32 j = 0; j < chunk_len; ++j) {
            seq_ids.push_back(static_cast<i32>(req->request_id()));
        }
    }

    i32 total_tokens = static_cast<i32>(all_tokens.size());

    // 分配logits张量 [total_tokens, vocab_size]
    auto logits_result = Tensor::create(
        {total_tokens, model_.config().vocab_size},
        DataType::Float32, DeviceType::CUDA);
    // ...

    // 前向传播(变长批次)
    auto forward_result = model_.forward_batched(
        all_tokens, all_positions, seq_ids, logits);
    // ...

#ifdef PHOTON_USE_CUDA
    cudaDeviceSynchronize();

    // GPU采样 - 只对decode请求的最后一个token
    std::vector<i32> sampled_tokens(num_seqs);

    i32 token_offset = 0;
    for (i32 i = 0; i < num_seqs; ++i) {
        auto& req = batch.requests[i];
        i32 chunk_len = seq_lens[i];

        if (req->is_decode()) {
            // 采样:从最后一个token的logits中取argmax
            i32 logits_idx = token_offset + chunk_len - 1;

            auto sampling_result = kernels::cuda::argmax_sampling_launch(
                logits.ptr<f32>() + logits_idx * model_.config().vocab_size,
                sampled_tokens_gpu_ + i,
                1,
                model_.config().vocab_size,
                nullptr);
            // ...
        }

        token_offset += chunk_len;
    }

    // 拷贝采样结果到CPU
    cudaMemcpy(sampled_tokens.data(), sampled_tokens_gpu_,
               num_seqs * sizeof(i32), cudaMemcpyDeviceToHost);

    // 更新请求
    std::vector<i64> finished_ids;
    for (i32 i = 0; i < num_seqs; ++i) {
        auto& req = batch.requests[i];
        i32 chunk_len = seq_lens[i];

        if (req->is_prefill()) {
            // Prefill:只标记token为已计算
            req->add_computed_tokens(chunk_len);
        } else {
            // Decode:添加生成的token
            bool should_continue = req->add_token(
                sampled_tokens[i], tokenizer_.eos_id());
            if (!should_continue) {
                finished_ids.push_back(req->request_id());
                // 释放KV缓存
                auto* cache_mgr = model_.paged_cache_manager();
                cache_mgr->free_sequence(static_cast<i32>(req->request_id()));
            }
        }
    }

    // 更新调度器
    scheduler_.update_after_step(finished_ids);
#endif

    return Ok();
}

这段代码的关键设计点:

  1. 变长批次扁平化:不同请求可以有不同的 token 数量,通过 all_tokens/all_positions/seq_ids 三个数组把它们flatten成一个批次,seq_ids 标记每个 token 属于哪个请求;
  2. Prefill 和 Decode 分流:Prefill 请求一次性处理 chunk_size(默认 256)个 token,Decode 请求每次只处理 1 个 token;
  3. GPU 采样:只对 Decode 请求的最后一个 token 做 argmax 采样,Prefill 请求不采样(它们的 token 是已知的);
  4. KV Cache 管理:请求完成后立即释放 KV 缓存块,供新请求复用。

3. LLaMA 模型:前向传播流水线

LLaMAModel 类封装了完整的 LLaMA 架构前向传播:

cpp 复制代码
class LLaMAModel {
public:
    LLaMAModel(const TransformerConfig& config);

    // 单token前向(用于prefill后的decode阶段)
    Result<void> forward(i32 token, i32 pos, Tensor& logits);

    // 批处理前向(用于连续批处理的变长批次)
    Result<void> forward_batched(
        const std::vector<i32>& tokens,
        const std::vector<i32>& positions,
        const std::vector<i32>& seq_ids,
        Tensor& logits);

    // 初始化Paged Attention缓存
    Result<void> init_paged_cache(i32 num_blocks, i32 block_size);

    // INT8量化
    Result<void> quantize_weights(i32 group_size);

private:
    TransformerConfig config_;
    EmbeddingOp embedding_;
    RMSNormOp final_norm_;
    LinearOp classifier_;
    std::vector<std::unique_ptr<TransformerBlock>> blocks_;

    // KV Cache(非paged模式)
    std::vector<Tensor> key_cache_;
    std::vector<Tensor> value_cache_;

    // Paged Attention
    std::unique_ptr<KVCacheManager> paged_cache_manager_;
    bool use_paged_cache_ = false;

    // 批处理缓冲区
    Tensor tokens_batch_;
    Tensor x_batch_;
    Tensor norm_batch_;

    // CUDA资源
    void* cublas_handle_ = nullptr;
    i32* positions_gpu_ = nullptr;
    i32* seq_ids_gpu_ = nullptr;
    i32* cache_offsets_gpu_ = nullptr;

#ifdef PHOTON_USE_CUDA
    i32 batched_buffers_capacity_ = 0;
#endif
};

单 token 前向传播流程(forward 函数):

cpp 复制代码
Result<void> LLaMAModel::forward(i32 token, i32 pos, Tensor& logits) {
    // 1. Embedding Lookup
    auto token_tensor = Tensor::create({1}, DataType::Int32, config_.device);
    // ... 拷贝token到GPU ...
    auto emb_result = embedding_.forward(token_tensor.value(), emb_out_);
    // ... 拷贝emb到x_ ...

    // 2. 逐层前向传播
    for (i32 layer_idx = 0; layer_idx < config_.n_layers; ++layer_idx) {
        auto block_result = blocks_[layer_idx]->forward(
            x_, pos, key_cache_[layer_idx], value_cache_[layer_idx]);
        // ...
    }

    // 3. Final RMSNorm
    auto norm_result = final_norm_.forward(x_, norm_out_);
    // ...

    // 4. Classifier Projection
    // ...
    return Ok();
}

批处理前向传播(forward_batched 函数)是性能关键路径:

cpp 复制代码
Result<void> LLaMAModel::forward_batched(
    const std::vector<i32>& tokens,
    const std::vector<i32>& positions,
    const std::vector<i32>& seq_ids,
    Tensor& logits) {

    // 确保预分配缓冲区足够
    auto buffer_result = ensure_batched_buffers(total_tokens);
    // ...

    // 1. 拷贝输入到GPU(复用预分配缓冲区)
    cudaMemcpy(tokens_batch_.data(), tokens.data(),
               total_tokens * sizeof(i32), cudaMemcpyHostToDevice);
    cudaMemcpy(positions_gpu_, positions.data(),
               total_tokens * sizeof(i32), cudaMemcpyHostToDevice);
    cudaMemcpy(seq_ids_gpu_, seq_ids.data(),
               total_tokens * sizeof(i32), cudaMemcpyHostToDevice);

    // 确保所有序列已分配KV缓存
    if (use_paged_cache_) {
        for (size_t i = 0; i < seq_ids.size(); ++i) {
            i32 seq_id = seq_ids[i];
            i32 pos = positions[i];
            if (!paged_cache_manager_->is_sequence_allocated(seq_id)) {
                auto alloc_result = paged_cache_manager_->allocate_sequence(
                    seq_id, pos + 256);
                // ...
            } else {
                // 检查是否需要扩展
                auto cap_result = paged_cache_manager_->get_sequence_capacity(seq_id);
                if (cap_result && pos >= cap_result.value()) {
                    auto extend_result = paged_cache_manager_->extend_sequence(
                        seq_id, 128);
                    // ...
                }
            }
        }
    }

    // 2. Batched Embedding Lookup
    auto emb_result = embedding_.forward(tokens_batch_, x_batch_);
    // ...

    // 3. 逐层前向(BATCHED版本!使用Paged Attention)
    if (use_paged_cache_) {
        for (i32 layer_idx = 0; layer_idx < config_.n_layers; ++layer_idx) {
            Tensor& key_cache = paged_cache_manager_->get_key_cache(layer_idx);
            Tensor& value_cache = paged_cache_manager_->get_value_cache(layer_idx);

            auto block_result = blocks_[layer_idx]->forward_batched(
                x_batch_, positions_gpu_, positions, seq_ids, total_tokens,
                key_cache, value_cache, paged_cache_manager_->get());
            // ...
        }
    }

    // 4. Batched Final RMSNorm
    auto norm_result = final_norm_.forward(x_batch_, norm_batch_);
    // ...

    // 5. Batched Classifier
    auto cls_result = classifier_.forward(norm_batch_, logits);
    // ...

    return Ok();
}

初始化 cuBLAS handle 用于 Tensor Core 优化:

cpp 复制代码
Result<void> LLaMAModel::init() {
    // 创建cuBLAS handle用于FP16 Tensor Core优化
    if (config_.device == DeviceType::CUDA && cublas_handle_ == nullptr) {
        cublasHandle_t handle;
        cublasStatus_t status = cublasCreate(&handle);
        // ...
        cublas_set_math_mode(handle, CUBLAS_TENSOR_OP_MATH);
        cublas_handle_ = static_cast<void*>(handle);

        // 为所有transformer block设置cuBLAS handle
        for (auto& block : blocks_) {
            block->set_cublas_handle(cublas_handle_);
        }
    }
    // ...
}

4. 多头注意力算子(MHA)

MHAOp 类实现了多头注意力的核心计算,支持 GQA(Grouped Query Attention):

cpp 复制代码
class MHAOp : public OperatorBase<MHAOp> {
public:
    explicit MHAOp(i32 dim, i32 kv_dim, i32 head_num, i32 head_size,
                   i32 seq_len, bool use_naive = false);

    Result<void> forward(const Tensor& query, const Tensor& key_cache,
                        const Tensor& value_cache, Tensor& output, i32 pos);

    // GQA支持:kv_mul = dim / kv_dim 控制KV head数与Q head数的比例
    constexpr i32 kv_mul() const noexcept { return kv_mul_; }

private:
    i32 dim_;          // 总query维度 (head_num * head_size)
    i32 kv_dim_;       // 总key/value维度 (for GQA)
    i32 head_num_;     // Query head数
    i32 head_size_;    // 每个head的维度
    i32 seq_len_;      // 最大序列长度
    i32 kv_mul_;       // Q head数 / KV head数
    bool use_naive_;   // 是否使用朴素实现
};

内部计算流程:

  1. 对每个 head h 和位置 t < pos:scoreht = (Qh · Kht) / sqrt(head_size)
  2. 对 scores 应用 softmax:attenh = softmax(scoreh0:pos+1)
  3. 聚合 values:outputh = sum(attenht · Vht for t in 0:pos+1)

其中 kv_mul_ 参数控制 GQA:当 kv_mul_=1 时就是标准 Multi-Head Attention;当 kv_mul_>1 时多个 Q head 共享一个 KV head,减少 KV Cache 大小和 Attention 计算量。

5. KV Cache 管理器与块表

KVCacheManager 实现块级 KV 缓存分配:

cpp 复制代码
class KVCacheManager {
public:
    KVCacheManager(i32 num_blocks, i32 block_size, i32 n_layers,
                   i32 n_kv_heads, i32 head_size, DeviceType device);

    Result<void> allocate_sequence(i32 seq_id, i32 required_tokens);
    Result<void> extend_sequence(i32 seq_id, i32 additional_tokens);
    void free_sequence(i32 seq_id);
    bool is_sequence_allocated(i32 seq_id) const;
    i32 get_sequence_capacity(i32 seq_id) const;

    Tensor& get_key_cache(i32 layer_idx);
    Tensor& get_value_cache(i32 layer_idx);
    BlockTable& get_block_table();

private:
    i32 num_blocks_;
    i32 block_size_;
    // ...
};

BlockTable 维护每个请求到物理块的映射:

cpp 复制代码
class BlockTable {
public:
    void add_block(i32 seq_id, i32 block_idx);
    void free_blocks(i32 seq_id);
    std::vector<i32> get_block_indices(i32 seq_id) const;
    i32 allocate_block();
    void free_block(i32 block_idx);

private:
    std::unordered_map<i32, std::vector<i32>> block_tables_;  // seq_id -> blocks
    std::vector<bool> free_blocks_;
    i32 next_free_block_ = 0;
    // ...
};

6. 连续批处理调度器

ContinuousBatchScheduler 实现两阶段调度:

cpp 复制代码
class ContinuousBatchScheduler {
public:
    ContinuousBatchScheduler(i32 max_batch_size, i32 max_seqs,
                            i32 chunk_size,
                            SchedulerPolicy policy);

    i64 add_request(const std::string& prompt,
                   const std::vector<i32>& tokens,
                   i32 max_new_tokens);

    SchedulerBatch schedule_next_batch();
    void update_after_step(const std::vector<i64>& finished_ids);

    bool has_work() const;
    SchedulerStats get_stats() const;

private:
    // 请求状态机
    enum class RequestState { WAITING, RUNNING, FINISHED, PREEMPTED };

    // 两阶段调度:
    // Phase 1: 继续所有RUNNING请求
    // Phase 2: 接纳WAITING请求填充剩余容量
    // ...
};

调度器维护的请求状态转换:

复制代码
WAITING ──→ RUNNING ──→ FINISHED
   │           │
   │           └──→ PREEMPTED ──→ WAITING (预empt后重新进入等待队列)
   └──────────────────────────→ (新请求直接进入WAITING)

Token 级别的 num_computed_tokens 追踪确保:

  • Prefill 阶段:每次处理 chunk_size 个 token,num_computed_tokens 增加 chunk_size;
  • Decode 阶段:每次生成 1 个 token,num_computed_tokens 增加 1;
  • 当 num_computed_tokens >= max_new_tokens 时,请求标记为 FINISHED;
  • 调度器根据 num_computed_tokens 精确决定每个请求下一个 step 需要处理多少 token。

7. INT8 量化实现

量化流程在模型层统一调用:

cpp 复制代码
Result<void> LLaMAModel::quantize_weights(i32 group_size) {
    std::cout << "Quantizing model weights to INT8 (group_size="
              << group_size << ")..." << std::endl;

    // 量化所有transformer blocks
    for (size_t i = 0; i < blocks_.size(); ++i) {
        std::cout << "  Layer " << i << "/" << blocks_.size() << "..." << std::flush;
        auto result = blocks_[i]->quantize_weights(group_size);
        if (!result) {
            return Err<void>(result.error().code(),
                           "Failed to quantize layer " + std::to_string(i) + ": " +
                           result.error().message());
        }
        std::cout << " done" << std::endl;
    }

    // 量化classifier
    std::cout << "  Classifier..." << std::flush;
    auto classifier_result = classifier_.quantize_weights(group_size);
    // ...

    std::cout << "Done! Model memory reduced by ~3.8x" << std::endl;
    return Ok();
}

量化后模型内存减少约 3.8 倍(因为除了 embedding 和 classifier 层保持 FP16,中间层全部量化为 INT8)。


五、环境配置与运行测试教程

If you need the complete source code, please add the WeChat number (c17865354792)

1. 基础依赖

依赖 版本要求 说明
编译器 GCC 12+ 需要完整的 C++20 支持
CMake 3.20+ 构建系统
CUDA Toolkit 12.0+ 已在 12.5 上测试通过
GPU Compute Capability 7.0+ Volta/Turing/Ampere/Hopper 架构

2. 从源码编译

bash 复制代码
# 配置构建(开启CUDA)
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release -DPHOTON_BUILD_CUDA=ON ..

# 编译
cmake --build . -j$(nproc)

# 安装(可选)
sudo cmake --install .

安装后文件分布:

文件 安装路径
photon_web_server /usr/local/bin/
静态Web文件 /该项目/web/static/
核心库 /usr/local/lib/

卸载:

bash 复制代码
cd build
sudo cmake --build . --target uninstall

3. 下载模型

需要从 HuggingFace 下载量化后的模型权重。以 Llama 3.2 1B 为例:

bash 复制代码
# 安装huggingface-cli
pip install huggingface-cli

# 下载模型
huggingface-cli download Lummy666/llama-3.2-1B-Instruct \
    --local-dir ./models/llama-3.2-1B-Instruct

4. 运行 Web 服务器

bash 复制代码
photon_web_server \
    --port 5728 \
    --model /path/to/llama-3.2-1B-Instruct \
    --tokenizer /path/to/llama-3.2-1B-Instruct/tokenizer.json

启动后访问 本地Web界面 即可使用内置 Web UI 进行对话。

5. Docker 方式运行(推荐)

如果不想手动配置环境,可以使用预构建镜像:

bash 复制代码
# 拉取镜像
docker pull 镜像仓库/该项目:latest

# 运行容器(需要GPU支持)
docker run --rm --gpus all -p 5728:5728 -e PORT=5728 镜像仓库/该项目:latest

浏览器访问 本地Web界面 即可使用。

6. 性能测试

可以通过调整 batch size 测试批处理吞吐:

bash 复制代码
# 使用Web UI同时发起多个请求,观察吞吐变化
# 或使用内置benchmark工具(如果有)

预期结果(A100 + Llama 3.2 1B + INT8):

Batch Size 预期吞吐 对比 llama.cpp
1 185 tok/s 0.73x
4 410 tok/s 1.63x
8 720 tok/s 2.82x
16 787 tok/s 3.07x

7. 常见问题排查

问题 可能原因 解决方案
编译报错 C++20 不支持 GCC 版本过低 升级 GCC 到 12+
CUDA kernel 编译失败 CUDA Toolkit 版本不匹配 检查 nvcc --version,确保 12.0+
显存溢出 模型太大或 batch 太大 减小 max_batch_size 或使用量化模型
推理结果异常 模型文件不完整 重新下载模型,检查文件完整性
Web UI 无法访问 端口被占用 使用 --port 参数指定其他端口

六、落地用途

1. 高并发推理服务

连续批处理的核心优势就是高并发场景。如果你需要一个同时服务几十个甚至上百个用户的 LLM API 服务,这个引擎的调度器设计就是为了这个场景优化的。batch=16 时 787 tok/s 的吞吐意味着单卡可以服务大量并发请求。

2. 生产环境部署

C++ 编译出的二进制文件没有 Python 运行时依赖,部署简单。配合 Docker 可以一键部署到任何支持 GPU 的服务器。INT8 量化进一步降低了显存需求,让中等规格 GPU 也能跑大模型。

3. 算子研究与优化实验

引擎的算子层(ops/)设计清晰,每个算子(MHA、RMSNorm、SwiGLU、Matmul 等)都是独立的类,方便替换和实验。比如你想试一个新的 Attention 实现,只需要改 ops/ 下的代码,不影响其他层。

4. 教学与学习

代码全部开源,C++20 风格清晰,是学习 LLM 推理引擎内部实现的好材料。从 Tensor 类到 Attention 算子到调度器,整个管线一目了然。特别是连续批处理和 Paged Attention 的实现,比看论文伪代码直观得多。

5. 边缘设备原型验证

虽然目前主要面向 A100 等高端 GPU,但 INT8 量化的支持意味着在算力有限的设备上也有用武之地。可以先在这个引擎上做算法验证,再移植到更受限的嵌入式平台。


七、总结

这个推理引擎的价值不在于堆模型数量,而在于把生产级 LLM 推理的关键技术串成了一条完整的管线:

  • 算子层:用 C++20 实现高效的 MHA、RMSNorm、SwiGLU 等算子,配合 float4 向量化加载和 CUB 归约优化;
  • 模型层:完整实现 LLaMA 架构,支持 FP32/INT8 精度;
  • 内存层:Paged Attention 块级 KV Cache 管理,显存利用率最大化;
  • 调度层:vLLM 风格的连续批处理,token 级动态调度,生产级并发能力;
  • 工程层:CMake 构建、Docker 部署、Web UI + OpenAI API 双接口。

未来路线图包括 Flash Attention 2(IO 感知的长序列注意力)、多 GPU 张量并行、FP16/BF16 混合精度、以及投机解码(Speculative Decoding)等方向。

如果你正在做 LLM 推理相关的研究或产品开发,建议亲自跑通一次编译、推理、量化的完整流程。很多细节只有看到实际的 tok/s 数字和显存占用时,才会真正变得清晰。

Welcome to follow WeChat official account【程序猿编码】

相关推荐
全栈陈序员1 小时前
【Python】基础语法入门(二十八)—— 数据库操作入门:用 SQLite 持久化数据
数据库·人工智能·python
马剑威(威哥爱编程)1 小时前
【HarmonyOS 7 小艺智能体实战】09:上架前先把“关”过一遍——合规自查与素材准备
人工智能·华为·harmonyos
moonsims1 小时前
AiBrainBox-UGV 通 · 感 · 算 · 控一体化地面机器人边缘智能平台 规格书
大数据·网络·人工智能·无人机
m0_674571971 小时前
手写能跑的AI agent循环 (纯 Python,无框架)原理+示例讲解
人工智能
大力财经1 小时前
山东山大华天软件与阿里云达成合作,旗下产品CrownCAD已接入Qoder
人工智能·阿里云·云计算
徐小夕@趣谈前端1 小时前
老旧Word格式全支持!JitWord协同AI文档4.1,让Word文档无缝迁移到Web端
前端·人工智能·word
AiNightVision1 小时前
星光级 是什么?——星光级摄像机是什么?0.001Lux 照度门槛与选型真相
人工智能·算法·车载系统·自动驾驶·无人机·视频·智能硬件
飞猫的边缘AI1 小时前
机器人基础模型的两种赌注:VLA与VFA
人工智能·机器人·vla·ai模型·边缘ai·视觉语言动作模型·vfa