CANN-LLM WebUI:打造国产 LLM 推理的“驾驶舱

CANN-LLM WebUI:打造国产 LLM 推理的"驾驶舱"

cann组织链接:https://atomgit.com/cann

ops-nn仓库链接:https://atomgit.com/cann/ops-nn

🎯 目标

  • 实时显示:吞吐、延迟、显存、NPU 利用率
  • 可视化:各优先级队列状态、KV Cache 分布
  • 交互功能:查看/取消正在运行的请求
  • 日志流:滚动显示最新推理日志
  • 技术栈:纯 C++ 后端 + WebSocket + Vue3 前端(嵌入式)

✅ 所有数据来自 CANN Profiling API + 内部调度器状态


一、系统架构

Metrics
Request Events
Cancel Request
CANN-LLM Engine
Metrics Collector
Event Bus
WebSocket Server
Vue3 WebUI
HTTP DELETE


二、后端实现(C++)

1. 指标采集器(MetricsCollector)

cpp 复制代码
// metrics_collector.h
struct SystemMetrics {
    double tokens_per_sec = 0;
    double avg_latency_ms = 0;
    size_t gpu_memory_used = 0;
    float npu_utilization = 0.0f;
    
    struct QueueStats {
        int pending = 0;
        int running = 0;
    } high, medium, low;
    
    std::vector<std::string> recent_logs;
};

class MetricsCollector {
    SystemMetrics metrics_;
    std::mutex mtx_;

public:
    void update_from_engine(const EngineState& state) {
        std::lock_guard lock(mtx_);
        
        // 从调度器获取队列状态
        metrics_.high = state.scheduler.get_queue_stats(Priority::HIGH);
        
        // 从 hcll 获取显存
        metrics_.gpu_memory_used = hcllQueryUsedMemory();
        
        // 从 CANN Profiling API 获取 NPU 利用率
        metrics_.npu_utilization = cann::profiling::get_device_util(0);
        
        // 计算吞吐(滑动窗口)
        metrics_.tokens_per_sec = state.token_counter.rate_last_10s();
    }
    
    SystemMetrics get_snapshot() {
        std::lock_guard lock(mtx_);
        return metrics_;
    }
};

🔑 cann::profiling::get_device_util() 通过 aclprof API 实现


2. WebSocket 服务(嵌入式)

使用轻量级库(如 uWebSocketsBoost.Beast):

cpp 复制代码
// webui_server.cpp
void WebUIServer::start_websocket() {
    server.listen(8081, [&](auto* ws) {
        // 新客户端连接
        auto metrics_json = json_serializer(metrics_collector_.get_snapshot());
        ws->send(metrics_json);

        // 每 500ms 推送更新
        start_timer(500ms, [ws, this]() {
            if (ws->is_open()) {
                auto update = json_serializer(metrics_collector_.get_snapshot());
                ws->send(update);
            }
        });
    });
}

3. 活跃请求列表接口

cpp 复制代码
// 提供 /api/requests?status=running
std::vector<RequestInfo> get_active_requests() {
    std::vector<RequestInfo> list;
    for (auto& seq : scheduler_.get_all_sequences()) {
        if (!seq->is_finished() && !seq->is_cancelled()) {
            list.push_back({
                .id = seq->id(),
                .priority = to_string(seq->priority()),
                .prompt_len = seq->prompt_len(),
                .generated_len = seq->generated_len(),
                .kv_blocks = seq->block_table.size(),
                .created_at = seq->timestamp()
            });
        }
    }
    return list;
}

三、前端实现(嵌入式 Vue3)

将前端资源编译为 C++ 字符数组(或打包进 binary):

webui/index.html(简化)

html 复制代码
<template>
  <div class="dashboard">
    <!-- 核心指标卡片 -->
    <div class="metric-card">
      <h3>吞吐</h3>
      <p>{{ metrics.tokens_per_sec.toFixed(1) }} tokens/s</p>
    </div>
    
    <!-- 队列状态 -->
    <div class="queue-bar">
      <div class="bar high" :style="{width: highRatio + '%'}">High</div>
      <div class="bar medium" :style="{width: mediumRatio + '%'}">Medium</div>
      <div class="bar low" :style="{width: lowRatio + '%'}">Low</div>
    </div>

    <!-- 活跃请求表 -->
    <table>
      <tr v-for="req in activeRequests" :key="req.id">
        <td>{{ req.id }}</td>
        <td>{{ req.priority }}</td>
        <td>{{ req.generated_len }}</td>
        <td>
          <button @click="cancelRequest(req.id)">❌ Cancel</button>
        </td>
      </tr>
    </table>

    <!-- 实时日志 -->
    <div class="log-panel">
      <div v-for="log in logs" :key="log">{{ log }}</div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return { metrics: {}, activeRequests: [], logs: [] };
  },
  mounted() {
    // 连接 WebSocket
    const ws = new WebSocket('ws://localhost:8081');
    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      this.metrics = data.metrics;
      this.activeRequests = data.requests;
      this.logs = data.logs.slice(-50); // 保留最近 50 行
    };
  },
  methods: {
    cancelRequest(id) {
      fetch(`/requests/${id}`, { method: 'DELETE' })
        .then(() => this.refresh()); // 重新拉取列表
    }
  }
}
</script>

💡 使用 xxd -i index.html > webui_embedded.h 将前端嵌入 C++ 二进制


四、关键可视化组件

1. KV Cache 热力图

  • X 轴:Block ID
  • Y 轴:Layer ID
  • 颜色:引用计数(红色=高共享,蓝色=独占)
  • 点击 block 可查看所属请求

2. NPU 利用率时间序列

  • 折线图显示过去 5 分钟利用率
  • 阈值线(80%)标红预警

3. 请求生命周期甘特图

  • 每个请求显示:排队时间 + 推理时间 + 生成 token 数
  • 支持按 priority 着色

五、部署与访问

启动服务后:

bash 复制代码
./llm_server --enable-webui --port 8080 --webui-port 8081

浏览器访问:

复制代码
http://<server-ip>:8081

无需额外依赖,所有静态资源由 C++ 二进制提供。


六、安全与生产建议

  • 认证 :添加 --webui-token=xxx,前端需携带 Token
  • 只读模式 :可通过 --webui-readonly 禁用取消按钮
  • 日志脱敏:自动过滤 prompt 中的敏感词(如 API Key)

七、结语:透明即信任

一个优秀的推理系统,不仅要跑得快 ,更要看得清

通过内建 WebUI 控制台,CANN-LLM 实现了:

  • 运维友好:快速定位瓶颈
  • 开发友好:直观验证调度策略
  • 用户友好:自助管理请求

这标志着国产 AI 软件栈从 "黑盒加速" 走向 "白盒可控" 的重要一步。


🚀 CANN-LLM WebUI 将随 v1.0 正式版一同开源

是否希望下一篇深入 如何用 CANN Profiler 生成火焰图并嵌入 WebUI ,或提供 完整的 CI/CD 流水线配置(GitLab CI + Ascend 云)?欢迎告诉我!

相关推荐
PNP Robotics8 分钟前
领军军者|PNP机器人包文涛:以具身智能定义机器人的“生命直觉”
人工智能·深度学习·学习·机器学习·机器人
stereohomology13 分钟前
2026年人工智能技术趋势浅度解析
人工智能
2601_9583205721 分钟前
【小白易懂版】OpenClaw 飞书机器人绑定配置详细教程(含安装包)
人工智能·机器人·飞书·open claw·小龙虾·open claw安装
AI创界者26 分钟前
《2026 视觉革命:深度测评 GPT-Image-2,基于 DMXAPI 实现 4K 超分与批量生图实战》
人工智能
云上码厂29 分钟前
2023年之前物理信息神经网络PINN papers
人工智能·深度学习·神经网络
aini_lovee32 分钟前
多目标粒子群优化(MOPSO)双适应度函数MATLAB实现
人工智能·算法·matlab
Cosolar33 分钟前
提示词工程面试题系列 - Zero-Shot Prompting 和 Few-Shot Prompting 的核心区别是什么?
人工智能·设计模式·架构
灵机一物37 分钟前
灵机一物AI原生电商小程序、PC端(已上线)-【无标Anthropic 研究深度解析:AI 对就业市场的实际冲击与高危职业排行题】
人工智能·ai·程序员·职业发展·anthropic·就业市场
电子科技圈39 分钟前
芯科科技在蓝牙亚洲大会展示汽车与边缘AI前沿蓝牙创新技术, 解锁车用、家居、健康及工商业等应用场景
人工智能·科技·嵌入式硬件·mcu·物联网·网络安全·汽车
redreamSo1 小时前
让AI Agent自动接Issue、写代码、上线:我用200行代码搭了一个全自动开发流水线
人工智能·开源·github