本地化AI部署与边缘计算完全教程

教程简介

本教程全面讲解本地化AI部署与边缘计算的核心技术,涵盖硬件选型、模型压缩(剪枝/蒸馏/量化)、移动端部署(TFLite/ONNX/Core ML)、浏览器端AI(WebGPU/Transformers.js)、边缘计算框架(TensorRT/OpenVINO)、Ollama本地大模型部署、本地RAG系统搭建、隐私保护计算等,附带完整代码示例和实战案例,帮助开发者构建自主可控的离线AI系统。

本地化AI部署与边缘计算完全教程

一、概述

随着大语言模型(LLM)和深度学习技术的飞速发展,AI应用已经渗透到我们生活和工作的方方面面。然而,大多数AI应用仍然依赖云端服务,这带来了隐私泄露风险、网络延迟、持续成本等诸多问题。本地化AI部署与边缘计算正是解决这些痛点的关键技术方向。

本教程将从硬件选型、模型压缩、移动端部署、浏览器端AI、边缘计算框架、本地大模型部署、本地RAG系统搭建、隐私保护计算等多个维度,全面讲解如何将AI能力从云端搬到本地和边缘设备上,构建真正自主可控的AI系统。

1.1 为什么需要本地化AI部署

隐私保护:在医疗、金融、法律等敏感领域,数据离开本地环境就存在泄露风险。欧盟GDPR、中国《个人信息保护法》等法规对数据跨境传输有严格限制。本地部署意味着数据从不离开用户设备,从根本上解决了隐私问题。

低延迟:云端推理需要经过网络传输,往返延迟通常在50-200ms。对于实时应用(如自动驾驶、工业质检、语音交互),这种延迟是不可接受的。本地推理可以将延迟降低到个位数毫秒级别。

成本控制:云端AI服务按调用次数或token计费,大规模使用成本高昂。以GPT-4为例,每百万token的输入成本约为30美元,输出约为60美元。对于需要频繁调用的场景,本地部署的一次性硬件投入远低于持续的云端费用。

离线可用:在没有网络或网络不稳定的环境中(如飞机、偏远地区、工厂车间),本地AI是唯一选择。

自主可控:依赖第三方API意味着受制于服务可用性、定价策略、功能变更等不可控因素。本地部署让企业完全掌控自己的AI能力。

1.2 本地化AI部署的挑战

尽管优势明显,本地化部署也面临诸多挑战:

  • 算力限制:本地设备的计算能力远不及云端GPU集群
  • 内存约束:大模型需要大量内存,移动设备和嵌入式设备资源有限
  • 模型优化:需要在精度和效率之间找到平衡
  • 生态碎片化:不同硬件平台有不同的优化工具链
  • 维护成本:需要自行管理模型更新、版本控制等

本教程将逐一解决这些问题,帮助你构建高效的本地AI系统。


二、硬件选型指南

选择合适的硬件是本地化AI部署的第一步。不同场景对算力、功耗、成本的要求各不相同。

2.1 GPU方案

NVIDIA GPU 仍然是本地AI推理的主力选择:

型号 显存 FP16算力 功耗 适用场景
RTX 4090 24GB 82.6 TFLOPS 450W 本地大模型推理、训练
RTX 4080 16GB 48.7 TFLOPS 320W 中等规模推理
RTX 4070 Ti 12GB 40.1 TFLOPS 285W 轻量推理、开发测试
RTX 3060 12GB 12.7 TFLOPS 170W 入门级部署
Tesla V100 32GB 125 TFLOPS 300W 企业级推理

选择建议

  • 7B参数模型:至少8GB显存(RTX 3070以上)
  • 13B参数模型:至少12GB显存(RTX 3060 12GB以上)
  • 70B参数模型:至少48GB显存(双RTX 4090或A6000)

2.2 NPU与专用加速器

Apple Neural Engine:M1/M2/M3系列芯片内置的NPU,提供高达15.8 TOPS的算力,特别适合macOS/iOS设备上的AI推理。

Google Coral TPU:USB加速棒形式,INT8算力达到4 TOPS,功耗仅2W,非常适合边缘设备。

Intel Movidius:专为视觉推理设计的VPU,功耗低至1W,常见于无人机和智能摄像头。

华为昇腾310:INT8算力达到16 TOPS,功耗仅8W,适合边缘AI场景。

2.3 移动端芯片

高通骁龙8 Gen 3:集成Hexagon NPU,AI算力达到45 TOPS,支持端侧大模型推理。

联发科天玑9300:APU 790提供46 TOPS的AI算力,支持生成式AI。

苹果A17 Pro:16核Neural Engine,35 TOPS算力。

2.4 树莓派与开发板

树莓派5:搭载BCM2712处理器,4GB/8GB内存,可通过USB连接Coral TPU加速。

NVIDIA Jetson系列

  • Jetson Orin Nano:40 TOPS,功耗7-15W
  • Jetson Orin NX:100 TOPS,功耗10-25W
  • Jetson AGX Orin:275 TOPS,功耗15-60W

RK3588开发板:内置6 TOPS NPU,性价比高,适合国产化场景。

2.5 硬件选型决策树

需求分析
├── 需要运行大模型(>7B参数)
│   ├── 预算充足 → RTX 4090 / A6000
│   └── 预算有限 → RTX 3060 12GB + 量化模型
├── 移动端/嵌入式
│   ├── 手机App → 骁龙8系列 / Apple芯片
│   └── 嵌入式设备 → Jetson Orin / RK3588
├── 浏览器端
│   └── WebGPU兼容设备(现代GPU即可)
└── 超低功耗
    └── Coral TPU / Movidius VPU

三、模型压缩技术

模型压缩是本地化部署的核心技术,目标是在尽量保持模型精度的前提下,减小模型体积、降低计算复杂度。

3.1 模型剪枝(Pruning)

剪枝通过移除模型中不重要的参数或结构来减小模型规模。

非结构化剪枝:将单个权重设为零,产生稀疏矩阵。

import torch
import torch.nn.utils.prune as prune

# 加载预训练模型
model = torch.load('model.pth')

# 对线性层进行L1非结构化剪枝,移除50%的权重
for name, module in model.named_modules():
    if isinstance(module, torch.nn.Linear):
        prune.l1_unstructured(module, name='weight', amount=0.5)

# 永久化剪枝(移除mask,直接修改权重)
for name, module in model.named_modules():
    if isinstance(module, torch.nn.Linear):
        prune.remove(module, 'weight')

# 查看稀疏率
total_params = 0
zero_params = 0
for param in model.parameters():
    total_params += param.numel()
    zero_params += (param == 0).sum().item()
print(f"稀疏率: {zero_params/total_params*100:.2f}%")

结构化剪枝:移除整个通道、注意力头或层,更适合硬件加速。

import torch.nn.utils.prune as prune

# 按通道剪枝(针对卷积层)
def structured_pruning(model, amount=0.3):
    for name, module in model.named_modules():
        if isinstance(module, torch.nn.Conv2d):
            # 按L2范数剪枝整个输出通道
            prune.ln_structured(
                module, 
                name='weight', 
                amount=amount, 
                n=2, 
                dim=0  # 沿输出通道维度剪枝
            )
    return model

# 针对Transformer的注意力头剪枝
def prune_attention_heads(model, layer_idx, heads_to_prune):
    """
    heads_to_prune: dict {layer_idx: [head_indices]}
    """
    for layer, heads in heads_to_prune.items():
        # 将需要剪枝的注意力头的权重置零
        attention = model.transformer.layers[layer].attention
        for head_idx in heads:
            head_dim = attention.head_dim
            start = head_idx * head_dim
            end = start + head_dim
            with torch.no_grad():
                attention.out_proj.weight[:, start:end] = 0
    return model

3.2 知识蒸馏(Knowledge Distillation)

知识蒸馏通过训练一个小模型(学生模型)来模仿大模型(教师模型)的行为。

import torch
import torch.nn as nn
import torch.optim as optim

class DistillationLoss(nn.Module):
    def __init__(self, temperature=4.0, alpha=0.7):
        super().__init__()
        self.temperature = temperature
        self.alpha = alpha
        self.ce_loss = nn.CrossEntropyLoss()
        self.kl_loss = nn.KLDivLoss(reduction='batchmean')
    
    def forward(self, student_logits, teacher_logits, labels):
        # 硬标签损失
        hard_loss = self.ce_loss(student_logits, labels)
        
        # 软标签损失(KL散度)
        soft_student = nn.functional.log_softmax(
            student_logits / self.temperature, dim=-1
        )
        soft_teacher = nn.functional.softmax(
            teacher_logits / self.temperature, dim=-1
        )
        soft_loss = self.kl_loss(soft_student, soft_teacher) * (self.temperature ** 2)
        
        # 组合损失
        return self.alpha * soft_loss + (1 - self.alpha) * hard_loss


def distill_training(teacher_model, student_model, train_loader, epochs=10):
    teacher_model.eval()
    student_model.train()
    
    optimizer = optim.AdamW(student_model.parameters(), lr=1e-4)
    criterion = DistillationLoss(temperature=4.0, alpha=0.7)
    
    for epoch in range(epochs):
        total_loss = 0
        for batch in train_loader:
            inputs, labels = batch
            
            with torch.no_grad():
                teacher_logits = teacher_model(inputs)
            
            student_logits = student_model(inputs)
            loss = criterion(student_logits, teacher_logits, labels)
            
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            total_loss += loss.item()
        
        print(f"Epoch {epoch+1}, Loss: {total_loss/len(train_loader):.4f}")

LLM蒸馏实战:使用大模型生成训练数据来微调小模型。

from transformers import AutoTokenizer, AutoModelForCausalLM
import json

def generate_distillation_data(teacher_model_path, prompts, output_file):
    """使用教师模型生成蒸馏数据"""
    tokenizer = AutoTokenizer.from_pretrained(teacher_model_path)
    model = AutoModelForCausalLM.from_pretrained(
        teacher_model_path, 
        torch_dtype=torch.float16,
        device_map="auto"
    )
    
    results = []
    for prompt in prompts:
        inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
        with torch.no_grad():
            outputs = model.generate(
                **inputs,
                max_new_tokens=512,
                temperature=0.7,
                do_sample=True
            )
        response = tokenizer.decode(outputs[0], skip_special_tokens=True)
        results.append({
            "instruction": prompt,
            "response": response.replace(prompt, "").strip()
        })
    
    with open(output_file, 'w', encoding='utf-8') as f:
        json.dump(results, f, ensure_ascii=False, indent=2)
    
    return results

3.3 模型量化(Quantization)

量化将浮点数权重转换为低精度表示(INT8、INT4等),是最常用的模型压缩技术。

训练后量化(PTQ)

import torch

# PyTorch动态量化
model_dynamic = torch.quantization.quantize_dynamic(
    model,
    {torch.nn.Linear},  # 量化目标层
    dtype=torch.qint8   # 量化到INT8
)

# 静态量化(需要校准数据)
model_static = model
model_static.qconfig = torch.quantization.get_default_qconfig('fbgemm')
torch.quantization.prepare(model_static, inplace=True)

# 使用校准数据
with torch.no_grad():
    for calibration_batch in calibration_loader:
        model_static(calibration_batch)

torch.quantization.convert(model_static, inplace=True)

GPTQ量化(INT4)

from transformers import AutoModelForCausalLM, AutoTokenizer, GPTQConfig

def quantize_with_gptq(model_id, output_dir, bits=4):
    """使用GPTQ进行INT4量化"""
    tokenizer = AutoTokenizer.from_pretrained(model_id)
    
    # 配置量化参数
    quantization_config = GPTQConfig(
        bits=4,
        dataset="c4",  # 校准数据集
        tokenizer=tokenizer,
        group_size=128,
        desc_act=True,  # 按激活值降序处理
    )
    
    model = AutoModelForCausalLM.from_pretrained(
        model_id,
        quantization_config=quantization_config,
        device_map="auto",
    )
    
    model.save_pretrained(output_dir)
    tokenizer.save_pretrained(output_dir)
    print(f"量化模型已保存到 {output_dir}")
    
    # 验证量化效果
    import os
    original_size = sum(
        os.path.getsize(os.path.join(model_id, f))
        for f in os.listdir(model_id) if f.endswith('.safetensors')
    ) / (1024**3)
    
    quantized_size = sum(
        os.path.getsize(os.path.join(output_dir, f))
        for f in os.listdir(output_dir) if f.endswith('.safetensors')
    ) / (1024**3)
    
    print(f"原始模型大小: {original_size:.2f} GB")
    print(f"量化模型大小: {quantized_size:.2f} GB")
    print(f"压缩率: {original_size/quantized_size:.2f}x")

# 使用示例
quantize_with_gptq("meta-llama/Llama-2-7b-hf", "./llama2-7b-gptq-4bit")

GGUF量化(llama.cpp格式)

import subprocess

def convert_to_gguf(model_path, output_path, quant_type="Q4_K_M"):
    """
    将模型转换为GGUF格式
    quant_type选项: Q2_K, Q3_K_M, Q4_0, Q4_K_M, Q5_K_M, Q6_K, Q8_0
    数字越小压缩率越高,精度损失越大
    """
    # 转换为GGML格式
    cmd = f"python convert_hf_to_gguf.py {model_path} --outfile {output_path}.gguf"
    subprocess.run(cmd, shell=True, check=True)
    
    # 量化
    quant_cmd = f"./llama-quantize {output_path}.gguf {output_path}-{quant_type}.gguf {quant_type}"
    subprocess.run(quant_cmd, shell=True, check=True)
    
    print(f"量化完成: {output_path}-{quant_type}.gguf")

# 常用量化类型对比
QUANT_TYPES = {
    "Q2_K": "极小体积,质量损失明显",
    "Q3_K_M": "小体积,质量可接受",
    "Q4_K_M": "平衡选择,推荐使用",
    "Q5_K_M": "高质量,体积适中",
    "Q6_K": "接近原始质量",
    "Q8_0": "几乎无损,体积较大"
}

AWQ量化

from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

def quantize_with_awq(model_path, output_path, quant_config=None):
    """使用AWQ进行INT4量化,通常比GPTQ更快"""
    model = AutoAWQForCausalLM.from_pretrained(model_path)
    tokenizer = AutoTokenizer.from_pretrained(model_path)
    
    if quant_config is None:
        quant_config = {
            "zero_point": True,
            "q_group_size": 128,
            "w_bit": 4,
            "version": "GEMM"  # GEMM kernel更快
        }
    
    model.quantize(
        tokenizer,
        quant_config=quant_config,
        calib_data="pileval",  # 校准数据集
    )
    
    model.save_quantized(output_path)
    tokenizer.save_pretrained(output_path)
    print(f"AWQ量化完成: {output_path}")

四、移动端部署

4.1 TensorFlow Lite

TensorFlow Lite是Google专为移动和嵌入式设备设计的推理框架。

import tensorflow as tf

def convert_to_tflite(saved_model_dir, output_path, quantize=False):
    """将TensorFlow模型转换为TFLite格式"""
    converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
    
    if quantize:
        # 启用INT8量化
        converter.optimizations = [tf.lite.Optimize.DEFAULT]
        
        # 提供校准数据集
        def representative_dataset():
            for _ in range(100):
                data = tf.random.uniform([1, 224, 224, 3], 0, 1)
                yield [data]
        
        converter.representative_dataset = representative_dataset
        converter.target_spec.supported_ops = [
            tf.lite.OpsSet.TFLITE_BUILTINS_INT8
        ]
        converter.inference_input_type = tf.uint8
        converter.inference_output_type = tf.uint8
    
    tflite_model = converter.convert()
    
    with open(output_path, 'wb') as f:
        f.write(tflite_model)
    
    import os
    size_mb = os.path.getsize(output_path) / (1024 * 1024)
    print(f"TFLite模型已保存: {output_path} ({size_mb:.2f} MB)")

# Python端推理测试
def run_tflite_inference(model_path, input_data):
    interpreter = tf.lite.Interpreter(model_path=model_path)
    interpreter.allocate_tensors()
    
    input_details = interpreter.get_input_details()
    output_details = interpreter.get_output_details()
    
    interpreter.set_tensor(input_details[0]['index'], input_data)
    interpreter.invoke()
    
    return interpreter.get_tensor(output_details[0]['index'])

4.2 ONNX Runtime

ONNX Runtime是微软开源的高性能推理引擎,支持多种硬件后端。

import onnxruntime as ort
import numpy as np

def create_onnx_session(model_path, providers=None):
    """创建ONNX推理会话"""
    if providers is None:
        providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
    
    sess_options = ort.SessionOptions()
    sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
    sess_options.intra_op_num_threads = 4
    
    session = ort.InferenceSession(
        model_path,
        sess_options=sess_options,
        providers=providers
    )
    
    return session

def optimize_onnx_model(input_path, output_path):
    """优化ONNX模型"""
    from onnxruntime.transformers import optimizer
    
    optimized_model = optimizer.optimize_model(
        input_path,
        model_type='bert',  # 或 'gpt2', 't5' 等
        num_heads=12,
        hidden_size=768
    )
    optimized_model.save_model_to_file(output_path)
    print(f"优化模型已保存: {output_path}")

# 量化ONNX模型
def quantize_onnx_model(input_path, output_path):
    from onnxruntime.quantization import quantize_dynamic, QuantType
    
    quantize_dynamic(
        input_path,
        output_path,
        weight_type=QuantType.QInt8
    )
    print(f"量化模型已保存: {output_path}")

4.3 Core ML(Apple设备)

import coremltools as ct

def convert_to_coreml(pytorch_model, input_shape, output_path):
    """将PyTorch模型转换为Core ML格式"""
    import torch
    
    # 示例输入
    example_input = torch.randn(input_shape)
    
    # 追踪模型
    traced_model = torch.jit.trace(pytorch_model, example_input)
    
    # 转换为Core ML
    mlmodel = ct.convert(
        traced_model,
        inputs=[ct.TensorType(shape=input_shape)],
        compute_precision=ct.precision.FLOAT16,  # 使用FP16
        minimum_deployment_target=ct.target.iOS16,
    )
    
    # 量化为INT8
    mlmodel_int8 = ct.models.neural_network.quantization_utils.quantize_weights(
        mlmodel, nbits=8
    )
    
    mlmodel_int8.save(output_path)
    print(f"Core ML模型已保存: {output_path}")

4.4 MLC LLM(移动端大模型)

MLC LLM是一个让大语言模型在各种设备上高效运行的编译器。

# 安装: pip install mlc-ai mlc-llm

from mlc_llm import MLCEngine

def run_local_llm(model_lib, model_path):
    """使用MLC LLM运行本地大模型"""
    engine = MLCEngine(model_lib)
    
    # 流式输出
    for response in engine.chat.completions.create(
        messages=[{"role": "user", "content": "你好,请介绍一下自己"}],
        model=model_path,
        stream=True,
        max_tokens=256
    ):
        for choice in response.choices:
            if choice.delta.content:
                print(choice.delta.content, end="", flush=True)
    print()

# 在Android/iOS上部署
# 1. 使用MLC LLM编译模型为目标平台格式
# 2. 将编译后的模型打包到App中
# 3. 通过MLC LLM的移动端SDK进行推理

五、浏览器端AI

浏览器端AI让用户无需安装任何软件即可使用AI能力,且数据完全在本地处理。

5.1 WebGPU

WebGPU是下一代Web图形和计算API,为浏览器中的AI推理提供了硬件加速能力。

// WebGPU基础:在GPU上执行矩阵乘法
async function initWebGPU() {
    if (!navigator.gpu) {
        throw new Error("WebGPU不支持");
    }
    
    const adapter = await navigator.gpu.requestAdapter();
    const device = await adapter.requestDevice();
    
    return device;
}

// WGSL着色器代码(WebGPU着色语言)
const shaderCode = `
@group(0) @binding(0) var<storage, read> a: array<f32>;
@group(0) @binding(1) var<storage, read> b: array<f32>;
@group(0) @binding(2) var<storage, read_write> result: array<f32>;

@compute @workgroup_size(16, 16)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
    let row = id.x;
    let col = id.y;
    let N: u32 = 128u;
    var sum: f32 = 0.0;
    for (var k: u32 = 0u; k < N; k++) {
        sum += a[row * N + k] * b[k * N + col];
    }
    result[row * N + col] = sum;
}
`;

async function matrixMultiply(device, a, b, size) {
    const bufferSize = size * size * 4; // f32 = 4 bytes
    
    const bufferA = device.createBuffer({
        size: bufferSize,
        usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
    });
    const bufferB = device.createBuffer({
        size: bufferSize,
        usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
    });
    const bufferResult = device.createBuffer({
        size: bufferSize,
        usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
    });
    
    device.queue.writeBuffer(bufferA, 0, a);
    device.queue.writeBuffer(bufferB, 0, b);
    
    const shaderModule = device.createShaderModule({ code: shaderCode });
    const pipeline = device.createComputePipeline({
        layout: 'auto',
        compute: { module: shaderModule, entryPoint: 'main' },
    });
    
    const bindGroup = device.createBindGroup({
        layout: pipeline.getBindGroupLayout(0),
        entries: [
            { binding: 0, resource: { buffer: bufferA } },
            { binding: 1, resource: { buffer: bufferB } },
            { binding: 2, resource: { buffer: bufferResult } },
        ],
    });
    
    const commandEncoder = device.createCommandEncoder();
    const passEncoder = commandEncoder.beginComputePass();
    passEncoder.setPipeline(pipeline);
    passEncoder.setBindGroup(0, bindGroup);
    passEncoder.dispatchWorkgroups(size / 16, size / 16);
    passEncoder.end();
    
    const readBuffer = device.createBuffer({
        size: bufferSize,
        usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
    });
    commandEncoder.copyBufferToBuffer(bufferResult, 0, readBuffer, 0, bufferSize);
    
    device.queue.submit([commandEncoder.finish()]);
    await readBuffer.mapAsync(GPUMapMode.READ);
    
    return new Float32Array(readBuffer.getMappedRange());
}

5.2 Transformers.js

Transformers.js让在浏览器中运行Hugging Face模型变得简单。

<!DOCTYPE html>
<html>
<head>
    <title>浏览器端AI</title>
</head>
<body>
    <div id="output"></div>
    <script type="module">
        import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.0.0';
        
        // 文本分类
        async function sentimentAnalysis() {
            const classifier = await pipeline(
                'sentiment-analysis',
                'Xenova/distilbert-base-uncased-finetuned-sst-2-english'
            );
            const result = await classifier('I love this product!');
            console.log(result);
            // [{label: 'POSITIVE', score: 0.9998}]
        }
        
        // 文本生成
        async function textGeneration() {
            const generator = await pipeline(
                'text-generation',
                'Xenova/gpt2'
            );
            const result = await generator('Once upon a time', {
                max_new_tokens: 50,
                temperature: 0.7
            });
            console.log(result[0].generated_text);
        }
        
        // 图像分类
        async function imageClassification() {
            const classifier = await pipeline(
                'image-classification',
                'Xenova/vit-base-patch16-224'
            );
            const url = 'https://example.com/cat.jpg';
            const result = await classifier(url);
            console.log(result);
        }
        
        // 语音识别
        async function speechRecognition() {
            const transcriber = await pipeline(
                'automatic-speech-recognition',
                'Xenova/whisper-tiny'
            );
            const result = await transcriber('audio.wav');
            console.log(result.text);
        }
        
        // 文本嵌入(向量化)
        async function textEmbedding() {
            const extractor = await pipeline(
                'feature-extraction',
                'Xenova/all-MiniLM-L6-v2'
            );
            const result = await extractor('Hello world', {
                pooling: 'mean',
                normalize: true
            });
            console.log(result.data); // 384维向量
        }
        
        sentimentAnalysis();
    </script>
</body>
</html>

5.3 ONNX.js / onnxruntime-web

import * as ort from 'onnxruntime-web';

// 启用WebGPU后端
ort.env.wasm.numThreads = 4;

async function runONNXModel() {
    const session = await ort.InferenceSession.create(
        './model.onnx',
        { executionProviders: ['webgpu', 'wasm'] }
    );
    
    // 准备输入
    const inputData = new Float32Array(1 * 3 * 224 * 224);
    const inputTensor = new ort.Tensor('float32', inputData, [1, 3, 224, 224]);
    
    // 推理
    const results = await session.run({ input: inputTensor });
    const output = results.output.data;
    
    console.log('推理结果:', output);
}

5.4 WebNN

WebNN是W3C标准的神经网络API,直接调用硬件NPU。

async function webNNInference() {
    if (!('ml' in navigator)) {
        console.log('WebNN不支持');
        return;
    }
    
    const context = await navigator.ml.createContext();
    const builder = new MLGraphBuilder(context);
    
    // 定义计算图
    const input = builder.input('float32', [1, 3, 224, 224]);
    const weights = builder.constant(
        new Float32Array(/* ... */)
    );
    const conv = builder.conv2d(input, weights, {
        padding: [1, 1, 1, 1],
        strides: [1, 1]
    });
    const relu = builder.relu(conv);
    
    // 编译并执行
    const graph = await builder.build({ output: relu });
    const results = await context.compute(graph, {
        input: inputData
    });
    
    console.log(results.output);
}

六、边缘计算框架

6.1 TensorRT(NVIDIA)

TensorRT是NVIDIA的高性能推理优化器和运行时。

import tensorrt as trt
import numpy as np

def build_trt_engine(onnx_path, engine_path, fp16=True, int8=False):
    """构建TensorRT引擎"""
    logger = trt.Logger(trt.Logger.WARNING)
    builder = trt.Builder(logger)
    
    # 解析ONNX模型
    network = builder.create_network(
        1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
    )
    parser = trt.OnnxParser(network, logger)
    
    with open(onnx_path, 'rb') as f:
        if not parser.parse(f.read()):
            for error in range(parser.num_errors):
                print(parser.get_error(error))
            return None
    
    # 配置优化参数
    config = builder.create_builder_config()
    config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30)  # 1GB
    
    if fp16:
        config.set_flag(trt.BuilderFlag.FP16)
    
    if int8:
        config.set_flag(trt.BuilderFlag.INT8)
        # 需要提供校准器
        config.int8_calibrator = MyCalibrator(calibration_data)
    
    # 构建引擎
    engine = builder.build_serialized_network(network, config)
    
    with open(engine_path, 'wb') as f:
        f.write(engine)
    
    print(f"TensorRT引擎已保存: {engine_path}")
    return engine

def run_trt_inference(engine_path, input_data):
    """使用TensorRT引擎进行推理"""
    import pycuda.driver as cuda
    import pycuda.autoinit
    
    logger = trt.Logger(trt.Logger.WARNING)
    runtime = trt.Runtime(logger)
    
    with open(engine_path, 'rb') as f:
        engine = runtime.deserialize_cuda_engine(f.read())
    
    context = engine.create_execution_context()
    
    # 分配GPU内存
    d_input = cuda.mem_alloc(input_data.nbytes)
    output_shape = (1, 1000)  # 假设输出形状
    output = np.empty(output_shape, dtype=np.float32)
    d_output = cuda.mem_alloc(output.nbytes)
    
    # 执行推理
    stream = cuda.Stream()
    cuda.memcpy_htod(d_input, input_data)
    context.execute_v2(bindings=[int(d_input), int(d_output)])
    cuda.memcpy_dtoh(output, d_output)
    
    return output

6.2 OpenVINO(Intel)

OpenVINO是Intel的推理优化工具套件,特别适合Intel CPU和集成GPU。

from openvino.runtime import Core
import numpy as np

def openvino_inference(model_path, input_data):
    """使用OpenVINO进行推理"""
    core = Core()
    
    # 读取并编译模型
    model = core.read_model(model_path)
    compiled_model = core.compile_model(model, "CPU")  # 或 "GPU"
    
    # 获取输入输出
    input_layer = compiled_model.input(0)
    output_layer = compiled_model.output(0)
    
    # 执行推理
    result = compiled_model({input_layer: input_data})
    
    return result[output_layer]

def optimize_model_for_openvino(pytorch_model, example_input, output_dir):
    """将PyTorch模型优化为OpenVINO格式"""
    import openvino as ov
    import torch
    
    # 导出为ONNX
    onnx_path = f"{output_dir}/model.onnx"
    torch.onnx.export(
        pytorch_model,
        example_input,
        onnx_path,
        opset_version=13
    )
    
    # 转换为OpenVINO IR
    ov_model = ov.convert_model(onnx_path)
    ov.save_model(ov_model, f"{output_dir}/model.xml")
    
    print(f"OpenVINO模型已保存到 {output_dir}")
    
    # 量化模型(INT8)
    from openvino.tools import mo
    from openvino.tools.pot import IEEngine, compress_model_weights, save_model
    
    # 简化的量化流程
    core = Core()
    model = core.read_model(f"{output_dir}/model.xml")
    
    # 使用NNCF进行量化
    import nncf
    quantized_model = nncf.quantize(
        model,
        calibration_dataset=nncf.Dataset(calibration_data),
        target_device=nncf.TargetDevice.CPU
    )
    
    ov.save_model(quantized_model, f"{output_dir}/model_int8.xml")
    print("INT8量化完成")

6.3 NCNN(腾讯)

NCNN是腾讯开源的高性能神经网络推理框架,特别适合移动端和嵌入式设备。

# Python接口示例
import ncnn
import numpy as np

def ncnn_inference(param_path, bin_path, input_data):
    """使用NCNN进行推理"""
    net = ncnn.Net()
    net.load_param(param_path)
    net.load_model(bin_path)
    
    # 创建输入Mat
    mat_in = ncnn.Mat(input_data)
    
    # 创建提取器
    ex = net.create_extractor()
    ex.input("input", mat_in)
    
    # 提取输出
    ret, mat_out = ex.extract("output")
    
    return np.array(mat_out)

# 模型转换(从ONNX到NCNN)
# 命令行工具:onnx2ncnn model.onnx model.param model.bin

七、Ollama与LM Studio本地部署实战

7.1 Ollama部署

Ollama是最简单的本地大模型部署工具,一行命令即可运行。

# 安装Ollama(Linux/macOS)
curl -fsSL https://ollama.com/install.sh | sh

# 拉取并运行模型
ollama run llama3:8b
ollama run qwen2:7b
ollama run deepseek-coder:6.7b
ollama run mistral:7b

# 列出已安装模型
ollama list

# 查看模型信息
ollama show llama3:8b

Python API调用

import requests
import json

def ollama_generate(prompt, model="llama3:8b", stream=False):
    """调用Ollama API进行文本生成"""
    url = "http://localhost:11434/api/generate"
    
    payload = {
        "model": model,
        "prompt": prompt,
        "stream": stream,
        "options": {
            "temperature": 0.7,
            "top_p": 0.9,
            "num_predict": 512
        }
    }
    
    if stream:
        response = requests.post(url, json=payload, stream=True)
        for line in response.iter_lines():
            if line:
                data = json.loads(line)
                print(data["response"], end="", flush=True)
                if data["done"]:
                    print()
    else:
        response = requests.post(url, json=payload)
        return response.json()["response"]


def ollama_chat(messages, model="llama3:8b"):
    """调用Ollama Chat API"""
    url = "http://localhost:11434/api/chat"
    
    payload = {
        "model": model,
        "messages": messages,
        "stream": False
    }
    
    response = requests.post(url, json=payload)
    return response.json()["message"]["content"]


# 使用OpenAI兼容接口
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama"  # 任意值即可
)

response = client.chat.completions.create(
    model="llama3:8b",
    messages=[
        {"role": "system", "content": "你是一个有帮助的AI助手"},
        {"role": "user", "content": "请解释什么是量子计算"}
    ],
    temperature=0.7,
    max_tokens=500
)

print(response.choices[0].message.content)

自定义模型(Modelfile)

# Modelfile
FROM llama3:8b

# 系统提示词
SYSTEM """
你是一个专业的中文AI助手,擅长技术问答。
请用中文回答所有问题,回答要简洁准确。
"""

# 参数设置
PARAMETER temperature 0.7
PARAMETER top_p 0.9
PARAMETER num_ctx 4096
PARAMETER repeat_penalty 1.1

# 停止词
PARAMETER stop "<|eot_id|>"
# 创建自定义模型
ollama create my-assistant -f Modelfile

# 运行自定义模型
ollama run my-assistant

7.2 LM Studio

LM Studio提供图形化界面,适合不想使用命令行的用户。

# LM Studio也提供OpenAI兼容API
from openai import OpenAI

# 默认端口1234
client = OpenAI(
    base_url="http://localhost:1234/v1",
    api_key="lm-studio"
)

response = client.chat.completions.create(
    model="local-model",
    messages=[
        {"role": "user", "content": "用Python实现一个快速排序算法"}
    ]
)

print(response.choices[0].message.content)

八、本地RAG系统搭建

RAG(检索增强生成)让大模型能够基于本地知识库回答问题,是构建企业级AI应用的关键技术。

8.1 完整的本地RAG系统

import os
from pathlib import Path
from typing import List, Dict
import chromadb
from chromadb.config import Settings
from sentence_transformers import SentenceTransformer
import requests
import json

class LocalRAGSystem:
    def __init__(self, 
                 embedding_model="BAAI/bge-small-zh-v1.5",
                 llm_model="qwen2:7b",
                 collection_name="knowledge_base",
                 persist_dir="./chroma_db"):
        """初始化本地RAG系统"""
        
        # 嵌入模型(本地运行)
        self.embedder = SentenceTransformer(embedding_model)
        
        # 向量数据库
        self.chroma_client = chromadb.Client(Settings(
            chroma_db_impl="duckdb+parquet",
            persist_directory=persist_dir,
            anonymized_telemetry=False
        ))
        
        self.collection = self.chroma_client.get_or_create_collection(
            name=collection_name,
            metadata={"hnsw:space": "cosine"}
        )
        
        # 本地LLM(通过Ollama)
        self.llm_model = llm_model
        self.llm_url = "http://localhost:11434/api/generate"
    
    def load_documents(self, doc_dir: str, chunk_size=500, chunk_overlap=50):
        """加载并分块文档"""
        documents = []
        
        for file_path in Path(doc_dir).glob("**/*"):
            if file_path.suffix in ['.txt', '.md', '.pdf']:
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                
                # 分块
                chunks = self._split_text(content, chunk_size, chunk_overlap)
                for i, chunk in enumerate(chunks):
                    documents.append({
                        "id": f"{file_path.stem}_chunk_{i}",
                        "text": chunk,
                        "metadata": {
                            "source": str(file_path),
                            "chunk_index": i
                        }
                    })
        
        # 生成嵌入并存储
        texts = [doc["text"] for doc in documents]
        embeddings = self.embedder.encode(texts).tolist()
        
        self.collection.add(
            ids=[doc["id"] for doc in documents],
            embeddings=embeddings,
            documents=texts,
            metadatas=[doc["metadata"] for doc in documents]
        )
        
        print(f"已加载 {len(documents)} 个文档块")
        return len(documents)
    
    def _split_text(self, text: str, chunk_size: int, overlap: int) -> List[str]:
        """文本分块"""
        chunks = []
        start = 0
        while start < len(text):
            end = start + chunk_size
            chunk = text[start:end]
            chunks.append(chunk)
            start = end - overlap
        return chunks
    
    def retrieve(self, query: str, top_k: int = 3) -> List[Dict]:
        """检索相关文档"""
        query_embedding = self.embedder.encode([query]).tolist()
        
        results = self.collection.query(
            query_embeddings=query_embedding,
            n_results=top_k
        )
        
        retrieved = []
        for i in range(len(results["documents"][0])):
            retrieved.append({
                "text": results["documents"][0][i],
                "source": results["metadatas"][0][i]["source"],
                "distance": results["distances"][0][i]
            })
        
        return retrieved
    
    def generate_answer(self, query: str, context_docs: List[Dict]) -> str:
        """基于检索结果生成回答"""
        context = "\n\n".join([doc["text"] for doc in context_docs])
        
        prompt = f"""基于以下参考资料回答用户问题。如果参考资料中没有相关信息,请说明。

参考资料:
{context}

用户问题:{query}

回答:"""
        
        response = requests.post(self.llm_url, json={
            "model": self.llm_model,
            "prompt": prompt,
            "stream": False,
            "options": {
                "temperature": 0.3,
                "top_p": 0.9
            }
        })
        
        return response.json()["response"]
    
    def query(self, question: str, top_k: int = 3) -> Dict:
        """完整的RAG查询流程"""
        # 1. 检索
        docs = self.retrieve(question, top_k)
        
        # 2. 生成
        answer = self.generate_answer(question, docs)
        
        return {
            "question": question,
            "answer": answer,
            "sources": docs
        }


# 使用示例
rag = LocalRAGSystem()
rag.load_documents("./my_documents")
result = rag.query("什么是机器学习?")
print(f"问题: {result['question']}")
print(f"回答: {result['answer']}")
print(f"参考来源: {[s['source'] for s in result['sources']]}")

8.2 高级RAG优化技术

class AdvancedRAG(LocalRAGSystem):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
    
    def hybrid_search(self, query: str, top_k: int = 5) -> List[Dict]:
        """混合检索:结合语义搜索和关键词搜索"""
        # 语义搜索
        semantic_results = self.retrieve(query, top_k=top_k)
        
        # 关键词搜索(BM25风格)
        keyword_results = self._keyword_search(query, top_k=top_k)
        
        # 融合结果(RRF - Reciprocal Rank Fusion)
        fused = self._rrf_fusion(semantic_results, keyword_results)
        
        return fused[:top_k]
    
    def _keyword_search(self, query: str, top_k: int) -> List[Dict]:
        """简单的关键词匹配搜索"""
        import re
        
        keywords = set(re.findall(r'[\w\u4e00-\u9fff]+', query.lower()))
        
        all_docs = self.collection.get()
        scored = []
        
        for i, doc in enumerate(all_docs["documents"]):
            score = sum(1 for kw in keywords if kw in doc.lower())
            if score > 0:
                scored.append({
                    "text": doc,
                    "source": all_docs["metadatas"][i]["source"],
                    "score": score
                })
        
        scored.sort(key=lambda x: x["score"], reverse=True)
        return scored[:top_k]
    
    def _rrf_fusion(self, semantic: List, keyword: List, k=60) -> List:
        """RRF排序融合"""
        scores = {}
        
        for rank, doc in enumerate(semantic):
            key = doc["text"][:100]  # 用文本前100字符作为key
            scores[key] = scores.get(key, 0) + 1 / (k + rank + 1)
        
        for rank, doc in enumerate(keyword):
            key = doc["text"][:100]
            scores[key] = scores.get(key, 0) + 1 / (k + rank + 1)
        
        # 重新排序
        all_docs = {doc["text"][:100]: doc for doc in semantic + keyword}
        sorted_keys = sorted(scores.keys(), key=lambda x: scores[x], reverse=True)
        
        return [all_docs[key] for key in sorted_keys]
    
    def rerank(self, query: str, documents: List[Dict], top_k: int = 3) -> List[Dict]:
        """重排序:使用交叉编码器对结果精排"""
        from sentence_transformers import CrossEncoder
        
        reranker = CrossEncoder('BAAI/bge-reranker-base')
        
        pairs = [(query, doc["text"]) for doc in documents]
        scores = reranker.predict(pairs)
        
        for i, doc in enumerate(documents):
            doc["rerank_score"] = float(scores[i])
        
        documents.sort(key=lambda x: x["rerank_score"], reverse=True)
        return documents[:top_k]
    
    def query_with_reflection(self, question: str) -> Dict:
        """带反思的RAG:检查回答质量并迭代改进"""
        result = self.query(question)
        
        # 检查回答是否包含"不知道"或信息不足
        check_prompt = f"""请评估以下回答的质量,判断是否需要补充信息。

问题:{question}
回答:{result['answer']}

请回答:
1. 这个回答是否充分回答了问题?(是/否)
2. 如果否,需要补充什么信息?

评估:"""
        
        check_response = requests.post(self.llm_url, json={
            "model": self.llm_model,
            "prompt": check_prompt,
            "stream": False
        })
        
        evaluation = check_response.json()["response"]
        
        if "否" in evaluation:
            # 扩大检索范围重新生成
            more_docs = self.retrieve(question, top_k=5)
            result = {
                "question": question,
                "answer": self.generate_answer(question, more_docs),
                "sources": more_docs,
                "refined": True
            }
        
        return result

九、隐私保护计算

9.1 联邦学习

联邦学习让多方在不共享原始数据的情况下协作训练模型。

import torch
import torch.nn as nn
import copy
from typing import List

class FederatedLearning:
    def __init__(self, global_model: nn.Module, num_clients: int):
        self.global_model = global_model
        self.num_clients = num_clients
        self.client_models = [copy.deepcopy(global_model) for _ in range(num_clients)]
    
    def client_update(self, client_id: int, local_data, epochs: int = 5, lr: float = 0.01):
        """客户端本地训练"""
        model = self.client_models[client_id]
        model.train()
        optimizer = torch.optim.SGD(model.parameters(), lr=lr)
        criterion = nn.CrossEntropyLoss()
        
        for epoch in range(epochs):
            for batch in local_data:
                inputs, labels = batch
                optimizer.zero_grad()
                outputs = model(inputs)
                loss = criterion(outputs, labels)
                loss.backward()
                optimizer.step()
        
        return model.state_dict()
    
    def aggregate(self, client_weights: List[dict]):
        """FedAvg聚合算法"""
        global_dict = self.global_model.state_dict()
        
        for key in global_dict:
            global_dict[key] = torch.stack(
                [client_weights[i][key].float() for i in range(len(client_weights))]
            ).mean(dim=0)
        
        self.global_model.load_state_dict(global_dict)
        
        # 同步到所有客户端
        for model in self.client_models:
            model.load_state_dict(global_dict)
    
    def train_round(self, client_data: List, epochs: int = 5):
        """一轮联邦训练"""
        client_weights = []
        
        for client_id in range(self.num_clients):
            weights = self.client_update(client_id, client_data[client_id], epochs)
            client_weights.append(weights)
        
        self.aggregate(client_weights)
        
        return self.global_model


# 差分隐私
class DPFederatedLearning(FederatedLearning):
    def __init__(self, global_model, num_clients, noise_multiplier=1.0, max_grad_norm=1.0):
        super().__init__(global_model, num_clients)
        self.noise_multiplier = noise_multiplier
        self.max_grad_norm = max_grad_norm
    
    def add_noise(self, weights: dict) -> dict:
        """添加差分隐私噪声"""
        noisy_weights = {}
        for key, value in weights.items():
            # 裁剪梯度
            grad_norm = torch.norm(value)
            clip_factor = min(1, self.max_grad_norm / (grad_norm + 1e-8))
            clipped = value * clip_factor
            
            # 添加高斯噪声
            noise = torch.randn_like(clipped) * self.noise_multiplier * self.max_grad_norm
            noisy_weights[key] = clipped + noise
        
        return noisy_weights
    
    def client_update(self, client_id, local_data, epochs=5, lr=0.01):
        weights = super().client_update(client_id, local_data, epochs, lr)
        return self.add_noise(weights)

9.2 同态加密

# 使用SEAL库进行同态加密推理
# pip install tenseal

import tenseal as ts
import numpy as np

def homomorphic_inference_example():
    """同态加密推理示例"""
    # 创建加密上下文
    context = ts.context(
        ts.SCHEME_TYPE.CKKS,
        poly_modulus_degree=8192,
        coeff_mod_bit_sizes=[60, 40, 40, 60]
    )
    context.global_scale = 2**40
    context.generate_galois_keys()
    
    # 加密输入数据
    data = [0.1, 0.2, 0.3, 0.4, 0.5]
    encrypted_data = ts.ckks_vector(context, data)
    
    # 加密权重(模拟服务端)
    weights = [0.5, 0.3, 0.2, 0.1, 0.4]
    encrypted_weights = ts.ckks_vector(context, weights)
    
    # 同态加密下的计算(不需要解密)
    encrypted_result = encrypted_data * encrypted_weights
    encrypted_sum = encrypted_result.sum()
    
    # 只有数据拥有者可以解密结果
    result = encrypted_sum.decrypt()
    print(f"同态加密计算结果: {result}")

# 安全多方计算
def secure_multiparty_example():
    """安全多方计算示例(加法秘密共享)"""
    def share_secret(secret, num_parties=3):
        """将秘密分割为多个份额"""
        shares = [np.random.random() for _ in range(num_parties - 1)]
        shares.append(secret - sum(shares))
        return shares
    
    def reconstruct(shares):
        """从份额重建秘密"""
        return sum(shares)
    
    # 两个参与方各自持有秘密
    alice_secret = 42
    bob_secret = 58
    
    # 分割秘密
    alice_shares = share_secret(alice_secret)
    bob_shares = share_secret(bob_secret)
    
    # 各方在本地计算份额之和
    result_shares = [a + b for a, b in zip(alice_shares, bob_shares)]
    
    # 重建结果
    result = reconstruct(result_shares)
    print(f"安全计算结果: {result}")  # 应为100

十、实战案例:构建完全离线的AI助手

10.1 系统架构

我们将构建一个完全离线的AI助手,具备以下能力:

  • 本地大模型对话
  • 本地知识库问答(RAG)
  • 本地文档处理
  • 语音识别与合成(可选)
  • 完全不依赖网络

10.2 完整实现

"""
离线AI助手 - 完全本地运行
依赖:ollama, chromadb, sentence-transformers
"""

import os
import json
import time
from pathlib import Path
from typing import List, Dict, Optional
from dataclasses import dataclass
from datetime import datetime

import chromadb
from chromadb.config import Settings
from sentence_transformers import SentenceTransformer
import requests


@dataclass
class ChatMessage:
    role: str  # "user" 或 "assistant"
    content: str
    timestamp: str = None
    sources: List[str] = None
    
    def __post_init__(self):
        if self.timestamp is None:
            self.timestamp = datetime.now().isoformat()


class OfflineAIAssistant:
    def __init__(self, config_path: str = "config.json"):
        """初始化离线AI助手"""
        self.config = self._load_config(config_path)
        
        print("正在初始化离线AI助手...")
        print(f"  LLM模型: {self.config['llm_model']}")
        print(f"  嵌入模型: {self.config['embedding_model']}")
        print(f"  向量数据库: {self.config['vector_db_path']}")
        
        # 初始化嵌入模型
        print("加载嵌入模型...")
        self.embedder = SentenceTransformer(self.config['embedding_model'])
        
        # 初始化向量数据库
        print("初始化向量数据库...")
        self.chroma_client = chromadb.PersistentClient(
            path=self.config['vector_db_path']
        )
        self.collection = self.chroma_client.get_or_create_collection(
            name="knowledge_base",
            metadata={"hnsw:space": "cosine"}
        )
        
        # 对话历史
        self.conversation_history: List[ChatMessage] = []
        self.max_history = self.config.get('max_history', 10)
        
        print("离线AI助手初始化完成!")
    
    def _load_config(self, config_path: str) -> dict:
        """加载配置"""
        default_config = {
            "llm_model": "qwen2:7b",
            "embedding_model": "BAAI/bge-small-zh-v1.5",
            "vector_db_path": "./chroma_db",
            "ollama_url": "http://localhost:11434",
            "chunk_size": 500,
            "chunk_overlap": 50,
            "top_k": 3,
            "temperature": 0.7,
            "max_history": 10,
            "system_prompt": "你是一个专业的AI助手,请用中文回答问题。基于提供的参考资料回答,如果没有相关资料,请根据你的知识回答。"
        }
        
        if os.path.exists(config_path):
            with open(config_path, 'r', encoding='utf-8') as f:
                user_config = json.load(f)
                default_config.update(user_config)
        
        return default_config
    
    def load_knowledge_base(self, doc_dir: str):
        """加载知识库文档"""
        doc_path = Path(doc_dir)
        if not doc_path.exists():
            print(f"目录不存在: {doc_dir}")
            return
        
        documents = []
        supported_formats = ['.txt', '.md', '.py', '.json', '.csv', '.log']
        
        for file_path in doc_path.rglob("*"):
            if file_path.suffix.lower() in supported_formats:
                try:
                    with open(file_path, 'r', encoding='utf-8') as f:
                        content = f.read()
                    
                    if len(content.strip()) < 10:
                        continue
                    
                    chunks = self._chunk_text(content)
                    for i, chunk in enumerate(chunks):
                        documents.append({
                            "id": f"{file_path.stem}_{i}",
                            "text": chunk,
                            "metadata": {
                                "source": str(file_path),
                                "filename": file_path.name,
                                "chunk_idx": i
                            }
                        })
                except Exception as e:
                    print(f"跳过文件 {file_path}: {e}")
        
        if not documents:
            print("未找到可加载的文档")
            return
        
        # 批量生成嵌入
        print(f"正在为 {len(documents)} 个文档块生成嵌入...")
        texts = [doc["text"] for doc in documents]
        embeddings = self.embedder.encode(texts, show_progress_bar=True).tolist()
        
        # 存入向量数据库
        self.collection.add(
            ids=[doc["id"] for doc in documents],
            embeddings=embeddings,
            documents=texts,
            metadatas=[doc["metadata"] for doc in documents]
        )
        
        print(f"已加载 {len(documents)} 个文档块到知识库")
    
    def _chunk_text(self, text: str) -> List[str]:
        """智能文本分块"""
        chunk_size = self.config['chunk_size']
        overlap = self.config['chunk_overlap']
        
        # 按段落分割
        paragraphs = text.split('\n\n')
        chunks = []
        current_chunk = ""
        
        for para in paragraphs:
            if len(current_chunk) + len(para) > chunk_size and current_chunk:
                chunks.append(current_chunk.strip())
                # 保留overlap
                current_chunk = current_chunk[-overlap:] + para
            else:
                current_chunk += "\n\n" + para if current_chunk else para
        
        if current_chunk.strip():
            chunks.append(current_chunk.strip())
        
        return chunks if chunks else [text[:chunk_size]]
    
    def retrieve(self, query: str, top_k: int = None) -> List[Dict]:
        """检索相关文档"""
        if top_k is None:
            top_k = self.config['top_k']
        
        query_embedding = self.embedder.encode([query]).tolist()
        
        results = self.collection.query(
            query_embeddings=query_embedding,
            n_results=top_k
        )
        
        docs = []
        if results["documents"] and results["documents"][0]:
            for i in range(len(results["documents"][0])):
                docs.append({
                    "text": results["documents"][0][i],
                    "source": results["metadatas"][0][i].get("source", "未知"),
                    "distance": results["distances"][0][i] if results["distances"] else 0
                })
        
        return docs
    
    def chat(self, user_input: str, use_rag: bool = True) -> str:
        """与AI助手对话"""
        # 添加用户消息到历史
        self.conversation_history.append(ChatMessage(role="user", content=user_input))
        
        # 检索相关文档
        context = ""
        sources = []
        if use_rag:
            docs = self.retrieve(user_input)
            if docs:
                context = "\n\n".join([f"[参考{i+1}] {d['text']}" for i, d in enumerate(docs)])
                sources = [d['source'] for d in docs]
        
        # 构建提示词
        messages = [{"role": "system", "content": self.config['system_prompt']}]
        
        # 添加历史对话
        for msg in self.conversation_history[-self.max_history:]:
            messages.append({"role": msg.role, "content": msg.content})
        
        # 添加RAG上下文
        if context:
            messages[-1]["content"] = f"参考资料:\n{context}\n\n用户问题:{user_input}"
        
        # 调用本地LLM
        response = self._call_llm(messages)
        
        # 添加助手消息到历史
        assistant_msg = ChatMessage(
            role="assistant",
            content=response,
            sources=sources
        )
        self.conversation_history.append(assistant_msg)
        
        return response
    
    def _call_llm(self, messages: List[Dict]) -> str:
        """调用本地LLM"""
        url = f"{self.config['ollama_url']}/api/chat"
        
        payload = {
            "model": self.config['llm_model'],
            "messages": messages,
            "stream": False,
            "options": {
                "temperature": self.config['temperature'],
                "num_predict": 1024
            }
        }
        
        try:
            response = requests.post(url, json=payload, timeout=60)
            return response.json()["message"]["content"]
        except Exception as e:
            return f"LLM调用失败: {str(e)}"
    
    def save_session(self, path: str):
        """保存会话历史"""
        data = [{
            "role": msg.role,
            "content": msg.content,
            "timestamp": msg.timestamp,
            "sources": msg.sources
        } for msg in self.conversation_history]
        
        with open(path, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
    
    def load_session(self, path: str):
        """加载会话历史"""
        with open(path, 'r', encoding='utf-8') as f:
            data = json.load(f)
        
        self.conversation_history = [ChatMessage(**msg) for msg in data]


def main():
    """主函数"""
    assistant = OfflineAIAssistant()
    
    # 加载知识库
    if os.path.exists("./documents"):
        assistant.load_knowledge_base("./documents")
    
    print("\n离线AI助手已就绪!输入 'quit' 退出,'clear' 清除历史")
    print("-" * 50)
    
    while True:
        user_input = input("\n你: ").strip()
        
        if not user_input:
            continue
        
        if user_input.lower() == 'quit':
            print("再见!")
            break
        
        if user_input.lower() == 'clear':
            assistant.conversation_history.clear()
            print("对话历史已清除")
            continue
        
        if user_input.lower() == 'save':
            assistant.save_session("session.json")
            print("会话已保存")
            continue
        
        start_time = time.time()
        response = assistant.chat(user_input)
        elapsed = time.time() - start_time
        
        print(f"\n助手: {response}")
        print(f"\n[耗时: {elapsed:.2f}秒]")


if __name__ == "__main__":
    main()

十一、最佳实践

11.1 模型选择策略

  1. 根据任务选择模型大小:简单任务(分类、提取)用小模型,复杂任务(推理、生成)用大模型
  2. 优先考虑量化模型:INT4量化通常能保持90%以上的精度,同时大幅减少资源需求
  3. 测试多个模型:不同模型在不同任务上表现差异很大,需要实际测试

11.2 性能优化

  1. 批处理:合并多个请求一起处理,提高GPU利用率
  2. 异步推理:使用异步API避免阻塞
  3. 缓存策略:缓存常用查询的嵌入向量和生成结果
  4. 模型预热:服务启动时先做一次空推理,避免首次请求延迟

11.3 资源管理

  1. 内存监控:实时监控GPU/CPU内存使用,避免OOM
  2. 模型卸载:不常用的模型及时卸载,释放资源
  3. 请求限流:限制并发请求数,防止系统过载

11.4 安全考虑

  1. 输入验证:过滤恶意输入,防止提示注入
  2. 输出过滤:检查模型输出是否包含敏感信息
  3. 日志审计:记录所有请求和响应,便于追溯
  4. 访问控制:对API接口进行认证和授权

十二、常见问题

Q1:本地模型效果不如云端大模型怎么办?

A:可以尝试以下方法:

  • 使用更大的量化模型(如Qwen2-72B的INT4版本)
  • 通过RAG增强模型的知识
  • 针对具体任务微调模型
  • 使用模型集成(多个模型投票)

Q2:如何评估本地模型的效果?

A:建议从以下维度评估:

  • 准确率:使用标准测试集
  • 延迟:测量端到端响应时间
  • 吞吐量:测量每秒处理的请求数
  • 资源占用:监控CPU/GPU/内存使用

Q3:如何处理模型更新?

A:建议:

  • 定期检查模型发布渠道
  • 使用版本管理工具管理模型文件
  • 灰度发布新模型,对比效果后再全面切换

Q4:多用户并发场景如何优化?

A:关键策略:

  • 使用vLLM等高性能推理引擎
  • 部署多个模型实例负载均衡
  • 实现请求队列和优先级管理

十三、总结

本地化AI部署与边缘计算是AI技术民主化的关键路径。通过本教程,我们学习了:

  1. 硬件选型:根据需求选择合适的GPU、NPU、开发板等硬件
  2. 模型压缩:通过剪枝、蒸馏、量化等技术减小模型规模
  3. 移动端部署:使用TFLite、ONNX Runtime、Core ML等框架
  4. 浏览器端AI:利用WebGPU、Transformers.js等技术
  5. 边缘计算框架:TensorRT、OpenVINO、NCNN等优化推理
  6. 本地大模型:Ollama、LM Studio等工具快速部署
  7. RAG系统:构建本地知识库增强的AI系统
  8. 隐私保护:联邦学习、同态加密等技术
  9. 实战案例:构建完全离线的AI助手

随着硬件性能的提升和模型压缩技术的进步,本地化AI部署将成为越来越多场景的首选方案。希望本教程能帮助你构建自己的本地AI系统,享受隐私安全、低延迟、低成本的AI体验。


本教程持续更新中,欢迎反馈和建议。

内容声明

本文内容为AI技术学习教程,仅供学习参考。如涉及技术问题,欢迎通过 xurj005@163.com 与我们交流。

目录