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 云)?欢迎告诉我!

相关推荐
小雨下雨的雨8 小时前
井字棋AI机器人实现详解 - Minimax算法实战-鸿蒙PC Electron框架完成
前端·人工智能·算法·华为·electron·鸿蒙
我没胡说八道11 小时前
高校论文AI检测优化工具对比研究与实测分析(2026)
人工智能·深度学习·机器学习·计算机视觉·aigc·论文
秦亚伟11 小时前
AI浪潮重塑融资租赁行业新格局
人工智能
love530love11 小时前
LiveTalking 数字人项目 Windows 部署完全指南(EPGF 架构)
人工智能·windows·python·架构·livetalking·epgf
元启数宇11 小时前
喷淋AI布点实战:8小时人工布点→20分钟自动出图
人工智能
哈哈,柳暗花明11 小时前
人工智能专业术语详解(H)
人工智能·专业术语
圣殿骑士-Khtangc11 小时前
AI 编程工具 2026 实战横评:Cursor 3 vs Claude Code vs Copilot,开发者选型完全指南
人工智能·copilot
云器科技11 小时前
云器Lakehouse 2026年5月版本发布:拥抱 AI Agent,重塑数据智能开发新范式
人工智能
小鹰-上海鹰谷-电子实验记录本11 小时前
第六届党建引领科创生态座谈会 | 邓光辉博士出席分享AI赋能创新药科研新范式
人工智能·ai·电子实验记录本·药企合规
极客老王说Agent11 小时前
2026电信IDC机房巡检深度报告:人工巡检频次和深度够吗?实在Agent重塑智慧运维新范式
人工智能·ai·chatgpt