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

相关推荐
每日新鲜事9 小时前
热销复盘:招商林屿缦岛203套售罄背后的客户逻辑分析
大数据·人工智能
Coder_Boy_9 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
挖坑的张师傅9 小时前
对 AI Native 架构的一些思考
人工智能
LinQingYanga10 小时前
极客时间多模态大模型训练营毕业总结(2026年2月8日)
人工智能
pccai-vip10 小时前
过去24小时AI创业趋势分析
人工智能
SEO_juper10 小时前
AI SEO实战:整合传统技术与AI生成搜索的优化框架
人工智能·chatgpt·facebook·seo·geo·aeo
pp起床10 小时前
Gen_AI 补充内容 Logit Lens 和 Patchscopes
人工智能·深度学习·机器学习
方见华Richard10 小时前
自指-认知几何架构 可行性边界白皮书(务实版)
人工智能·经验分享·交互·原型模式·空间计算
冬奇Lab10 小时前
AI时代的"工具自由":我是如何进入细糠时代的
人工智能·ai编程
CODECOLLECT10 小时前
技术解析|MDM移动设备管理系统无终身买断制度的底层逻辑
人工智能