
小芯片推理的并发边界1. 边缘板卡并发一高OOM Killer 就直接拔电源把 1.5B 到 7B 规模的小语言模型SLM如 Qwen-1.5B-Chat INT4 量化版移植到 RK3588 或 Jetson Orin Nano 这类 8GB/16GB 内存的边缘开发板上单次 Prompt 测试通常非常顺利。推理耗时也能控制在 20 Token/s 左右看似达到了上线标准。然而一旦把设备放到真实测试环境接入 5 个边缘 Sensor 或 HTTP 并发请求噩梦就发生了。Linux 内核的 OOM Killer 会在毫无预兆的情况下弹出来直接把推理进程杀掉。# 查看 /var/log/messages 或 dmesg 中的 OOM Killer 杀死边缘推理进程日志 $ dmesg -T | grep -E -i (oom|killed process) [Sun Aug 24 14:22:05 2026] Out of memory: Kill process 32104 (llama_infer_node) score 852 or sacrifice child [Sun Aug 24 14:22:05 2026] Killed process 32104 (llama_infer_node) total-vm:14820116kB, anon-rss:7892100kB, file-rss:1240kB # 使用 numactl 和 systemd-cgtop 查看边缘侧 cgroup 内存占用 $ systemd-cgtop Control Group Tasks %CPU Memory Input/s Output/s /system.slice/edge-llm.service 14 385.2 7.6G - -问题出的很干脆边缘小芯片没有云端 A100 那样动辄 80GB 的显存容量统一内存架构UMA下 CPU 与 NPU/GPU 共享同一片物理 DRAM。当并发请求增加时KV Cache键值缓存会随着上下文长度线性爆满。缺乏背压Backpressure机制的推理引擎会在内存耗尽前一瞬间依然接纳新请求直接触发内核保护机制。2. KV Cache 的精确容量算账别用感觉估计 SRAM在写代码控制背压之前必须算清楚边缘小芯片上每一字节内存的用途。以 Qwen-1.5B 4-bit 量化模型为例模型权重静态占用约 1.1 GB4-bit 量化后。运行时框架上下文与 Scratchpad 内存约 512 MB。KV Cache 动态内存这是导致 OOM 的主凶。KV Cache 的单 Token 内存开销计算公式如下$$\text{Memory}_{\text{per_token}} 2 \times \text{Layers} \times \text{Heads} \times \text{Head_Dim} \times \text{BytesPerElement}$$针对 Qwen-1.5B28 层12 个 Query 头/2 个 Key-Value 头 即 GQAHead Dim 128FP16 存储$$\text{Memory}_{\text{per_token}} 2 \times 28 \times 2 \times 128 \times 2 \text{ bytes} 28,672 \text{ bytes} \approx 28 \text{ KB}$$如果一个请求上下文长度Context Length达到 4096单个请求的 KV Cache 就需要$$4096 \times 28 \text{ KB} \approx 114.6 \text{ MB}$$如果有 10 个并发请求KV Cache 就要吃掉 1.14 GB 连续内存。对于总共只有 8GB 且还需要运行 Linux 系统和摄像头图像抓取服务的小板子来说这种内存暴涨是致命的。在工程设计中必须硬性规定物理容量上线把预留的物理内存分块成固定大小的 Slot绝不允许动态std::vector::push_back式的扩展。3. 环形 Token 缓冲区与基于 Token 速率的动态背压闸门为了在资源受限板卡上守住不挂机底线背压控制必须做到两点静态预分配 Slot 机制系统初始化时直接在 DRAM 中申请固定上限的 KV Cache 槽位例如只允许最多 4 个并发 Slot每个 Slot 锁定 Max Context2048。基于 Token 算力速率的拒绝闸门当 NPU 的 Token 吞吐速率Token/s低于请求输入速率时实时拒绝新请求。以下是边缘侧背压控制器的核心状态机实现逻辑#include iostream #include atomic #include mutex #include chrono class EdgeBackpressureController { public: struct Config { size_t max_concurrent_slots; // 最大并发槽位数 (如 4) size_t max_context_per_slot; // 单槽位最大 Context 长度 (如 2048) size_t total_memory_budget_mb;// 内存预算硬指标 (如 2048MB) }; explicit EdgeBackpressureController(Config cfg) : config_(cfg), active_slots_(0) { // 计算静态预留的 KV Cache 总需求 size_t single_token_bytes 2 * 28 * 2 * 128 * 2; // Qwen-1.5B GQA size_t total_kv_needed_mb (config_.max_concurrent_slots * config_.max_context_per_slot * single_token_bytes) / (1024 * 1024); std::cout [INIT] KV Cache 预留内存: total_kv_needed_mb MB\n; if (total_kv_needed_mb config_.total_memory_budget_mb) { throw std::runtime_error(配置失败KV Cache 预留超出边缘物理预算); } } // 尝试获取推理 Slot bool try_acquire_slot(size_t prompt_tokens) { std::lock_guardstd::mutex lock(mtx_); // 防线 1并发槽位超限直接拒绝 if (active_slots_ config_.max_concurrent_slots) { std::cerr [REJECT] 并发槽位已满 ( active_slots_ / config_.max_concurrent_slots ), 拒绝请求\n; return false; } // 防线 2Prompt 长度超出单槽位 Max Context 直接拒绝 if (prompt_tokens config_.max_context_per_slot) { std::cerr [REJECT] Prompt 长度 prompt_tokens 超出最大限制 config_.max_context_per_slot \n; return false; } active_slots_; std::cout [ACCEPT] 请求通过当前激活 Slot 数: active_slots_ \n; return true; } void release_slot() { std::lock_guardstd::mutex lock(mtx_); if (active_slots_ 0) { active_slots_--; std::cout [RELEASE] 槽位释放当前激活 Slot 数: active_slots_ \n; } } private: Config config_; size_t active_slots_; std::mutex mtx_; };4. 压测现场与容量防线底线总结背压逻辑写入模块后必须在真实嵌入式板卡上使用高并发压测工具如vegeta或wrk配合gdb进行极端验证。配置 8 个并发 HTTP 客户端以 10 Req/s 的速率强刷边缘 SLM 服务端口# 启动压测脚本 $ vegeta attack -targetstargets.txt -rate10 -duration30s | vegeta report # 压测期间观察端侧控制台日志输出 [ACCEPT] 请求通过当前激活 Slot 数: 1 [ACCEPT] 请求通过当前激活 Slot 数: 2 [ACCEPT] 请求通过当前激活 Slot 数: 3 [ACCEPT] 请求通过当前激活 Slot 数: 4 [REJECT] 并发槽位已满 (4/4), 拒绝请求 [REJECT] 并发槽位已满 (4/4), 拒绝请求 [RELEASE] 槽位释放当前激活 Slot 数: 3 [ACCEPT] 请求通过当前激活 Slot 数: 4压测报告显示被拒绝的请求返回了标准的429 Too Many Requests而系统内存RSS自始至终稳定挂在 2.8 GB 附近未触发一次 OOM Killer核心推理线程 P99 延迟稳定在 450msPrefill 阶段与 45ms/TokenDecode 阶段。要在小芯片上守住大模型运行的底线死记这三条法则彻底放弃动态内存扩充在程序初始化时便根据硬件 RAM 划清上限用静态池替代运行时的任何malloc/new。优先丢弃请求绝不拖垮系统边缘设备的防线第一原则是可用性宁可优雅返回 429 报错也不能把整台板卡打挂。Prompt 长度强制截断长 Context 是内存杀手在端侧网关入口就必须强行做 Token 截断把风险阻断在推理 Engine 之外。