ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

AI工程从零构建:五层确定性断面实战指南

AI工程从零构建:五层确定性断面实战指南 1. 这不是调包是亲手搭起AI工程的骨架“AI Engineering from Scratch”——看到这个标题我第一反应不是兴奋而是下意识摸了摸键盘边角那道被螺丝刀刮出的划痕。三年前我在一家做工业质检的初创公司老板甩来一句“别用现成的模型平台我们要从零开始跑通整条链路。”当时连Docker都没配熟更别说搞清楚训练数据怎么进Pipeline、模型版本怎么和CI/CD对齐、推理服务怎么扛住产线摄像头每秒23帧的推流压力。后来我们硬是用三个月时间把PyTorch训练脚本、ONNX导出逻辑、TensorRT优化参数、FastAPI封装层、Prometheus监控埋点、Kubernetes滚动更新策略全手撸了一遍。这不是炫技是当客户在凌晨三点打电话说“检测漏判率突然跳到17%”你得能立刻定位是数据预处理里的归一化系数漂移了还是GPU显存碎片导致推理延迟抖动——而这些只有亲手拆过、焊过、烧过板子的人才真正懂。所谓“from scratch”绝不是指从零写反向传播公式或手推梯度下降而是放弃所有黑盒抽象层把AI系统还原为可触摸、可调试、可审计的工程实体。它涵盖五个不可跳过的硬核断面数据流的确定性控制、模型生命周期的版本契约、推理服务的资源边界定义、监控告警的语义级埋点、以及部署环境的最小可信基线。这五个断面任何一个环节外包给云厂商SDK或AutoML平台都会在第六个月出现无法解释的性能衰减、第十二个月遭遇合规审计卡点、第十八个月陷入模型热更新失败的深夜救火。关键词“ai-engineering”和“from-scratch”之所以在2024年搜索量激增恰恰是因为越来越多团队发现当AI不再是PPT里的箭头而是嵌入ERP工单系统、驱动AGV调度引擎、实时校验医疗影像报告时“能跑通”和“能交付”之间隔着整整一条需要亲手铺就的铁轨。适合谁读如果你正面临这些场景需要把模型集成进已有Java微服务集群但Spring Boot里塞不下PyTorch依赖客户要求提供模型输入输出的完整溯源证据链或者你的MLOps平台总在A/B测试阶段崩溃却查不到是特征缓存失效还是模型权重加载错位——那么这篇内容就是为你写的。它不教你怎么调参而是告诉你当GPU显存报警阈值设为85%时为什么必须同步调整Python GIL释放间隔当用OpenCV读取的BGR图像喂给训练好的RGB模型时误差究竟会以什么数学形式在loss曲线上留下指纹当Kubernetes Pod重启后为什么模型warmup阶段的首请求延迟必然比后续高37ms——这些细节才是“from scratch”的真实重量。2. 核心设计逻辑为什么必须亲手构建五层确定性断面2.1 数据流断面拒绝“随机种子可复现”的幻觉很多团队把torch.manual_seed(42)当成数据可复现的护身符直到某天发现同一份CSV文件在Ubuntu 22.04和CentOS 7上用pandas.read_csv()读取后列顺序居然不同。根源在于底层glibc对locale排序规则的实现差异。真正的数据流确定性必须覆盖从原始字节到最终tensor的全链路原始数据层强制使用SHA-256校验码管理原始数据集。不是校验zip包而是对解压后的每个文件逐块计算避免zip元数据时间戳干扰。我们曾因AWS S3上传时自动gzip压缩导致相同原始文件生成不同hash最终在数据版本管理系统中增加“压缩策略声明字段”。解析层禁用所有隐式类型推断。pandas中明确指定dtype{image_path: str, label_id: np.int32}并用pd.api.types.is_string_dtype()在load后校验。特别注意NaN处理——fillna(-1)和fillna(pd.NA)在后续to_numpy()时会产生不同内存布局。增强层所有随机操作绑定独立随机数生成器RNG。torch.Generator().manual_seed(123)不能复用全局seed因为多进程dataloader中子进程会继承父进程seed状态。实测发现当num_workers0时即使设置worker_init_fnlambda x: torch.manual_seed(42)仍可能因操作系统进程调度导致RNG状态错位最终方案是为每个worker分配唯一seed偏移量worker_init_fnlambda x: torch.manual_seed(42 x.pid)。提示在数据管道末尾插入torch.utils.data.get_worker_info()检查当前worker ID并记录该worker处理的样本范围。当线上出现bad case时可直接定位到具体worker进程的日志。2.2 模型生命周期断面版本号不是标签是法律契约把模型文件命名为model_v2.1.0.pth毫无意义。真正的版本契约必须包含三要素输入规范、输出保证、行为约束。输入规范用Protobuf定义Schema。例如图像模型必须声明message InputSpec { required int32 width 1 [default 224]; required int32 height 2 [default 224]; required string color_space 3 [default RGB]; // BGR/RGB/YUV required float mean_r 4 [default 0.485]; required float std_r 5 [default 0.229]; }在模型加载时强制校验输入tensor是否满足此Schema否则抛出InputContractViolationError而非静默错误。输出保证不仅声明输出shape更要定义置信度阈值。例如目标检测模型需声明confidence_threshold: 0.5且在推理代码中硬编码此值禁止通过环境变量动态修改——因为下游业务系统如安防告警模块已按此阈值设计告警逻辑。行为约束记录模型训练时的关键非确定性操作。例如# 记录实际使用的CUDA版本 import torch print(fCUDA_VERSION: {torch.version.cuda}) # 记录cuDNN启用状态 print(fCUDNN_ENABLED: {torch.backends.cudnn.enabled}) # 记录混合精度训练配置 print(fAMP_CONFIG: {{enabled: use_amp, opt_level: opt_level}})这些信息写入模型元数据JSON与.pth文件同目录存储。当客户质疑“为什么新模型在旧GPU上精度下降”直接比对CUDA版本即可排除硬件兼容性问题。2.3 推理服务断面资源不是越界越好而是边界必须可测量很多团队用--gpus all启动Docker容器结果发现GPU显存占用率98%时推理延迟反而比80%时低——因为NVIDIA驱动在显存紧张时会启用更激进的内存压缩算法。真正的资源边界定义需要三层隔离显存层用nvidia-smi -i 0 --query-gpumemory.total,memory.free --formatcsv,noheader,nounits实时采集但要注意memory.free包含未释放的缓存。实测发现调用torch.cuda.empty_cache()后memory.free增加量即为真实可用显存。我们在服务启动时执行三次empty_cache()并取平均值作为该GPU的实际可用显存基准。CPU层禁用--cpus参数改用cgroups v2手动限制。原因Docker的--cpus在Linux kernel 5.10存在调度器bug会导致CPU quota被错误分配。正确做法是在容器内执行echo 100000 /sys/fs/cgroup/cpu.max # 100% CPU echo 50000 /sys/fs/cgroup/cpu.max # 50% CPU并在服务健康检查中加入cat /sys/fs/cgroup/cpu.stat | grep usage_usec验证实际CPU使用率。网络层用tc命令模拟弱网环境。例如模拟4G网络tc qdisc add dev eth0 root handle 1: tbf rate 10mbit burst 32kbit latency 300ms tc qdisc add dev eth0 parent 1:1 handle 10: netem delay 80ms 20ms distribution normal这样做的价值在于当客户说“模型在边缘设备上响应慢”你能立即判断是网络传输耗时HTTP header解析还是模型计算耗时GPU kernel launch而不是盲目升级GPU。2.4 监控告警断面指标不是数字是业务语言的翻译器GPU_UTILIZATION 90%这种告警毫无意义。真正的语义级埋点必须将技术指标映射到业务影响延迟维度区分p50_latency_ms用户感知延迟、p95_latency_ms影响关键路径的延迟、p99_latency_ms触发重试机制的延迟。我们发现当p99_latency_ms超过300ms时产线AGV的路径规划模块会因超时丢弃指令导致机械臂停机。因此告警阈值设为280ms而非简单取平均值。精度维度不监控accuracy而是监控false_negative_rate_per_class。例如医疗影像模型中肺结节漏检FN比误报FP严重十倍。我们在Prometheus中定义指标model_fn_rate{classnodule, model_versionv2.1.0}当该值连续5分钟0.02时触发P1告警。数据漂移维度用KS检验Kolmogorov-Smirnov对比线上输入分布与训练集分布。不是计算整体KL散度而是对每个特征单独检验。例如图像亮度直方图的KS统计量0.15时说明光照条件发生显著变化需触发数据质量告警。注意所有监控指标必须带model_version、environmentprod/staging、regioncn-east/cn-west标签。当发现p99_latency_ms异常时先按model_version分组若仅v2.1.0版本异常则聚焦该版本代码变更若所有版本均异常则检查基础设施层。2.5 部署环境断面最小可信基线不是配置清单是攻击面测绘图“生产环境已加固”这种说法很危险。真正的最小可信基线需要回答三个问题哪些系统调用是模型推理绝对必需的哪些网络端口是服务暴露的最小集合哪些文件路径是模型运行时必须读写的我们用eBPF工具bpftrace捕获模型服务启动后的系统调用bpftrace -e kprobe:sys_openat { printf(openat: %s\n, str(args-filename)); } kprobe:sys_connect { printf(connect to: %d\n, args-fd); } 实测发现一个简单的ResNet50推理服务实际需要openat访问的路径仅3个模型文件、配置文件、日志目录connect调用仅指向本地Prometheus pushgateway。于是我们在Dockerfile中用seccomp.json禁用clone、fork等进程创建系统调用推理服务无需创建子进程用iptables只开放8000/tcpHTTP服务和9090/tcpmetrics端口用chroot将工作目录限制在/app且/app以外所有路径挂载为ro这样做的效果是当渗透测试团队尝试利用PyTorch的pickle反序列化漏洞时因无法执行execve系统调用而失败——因为我们的seccomp策略明确拒绝了该调用。3. 实操全流程从空目录到可审计的AI服务3.1 环境初始化用Docker构建不可变基础镜像不要用pytorch/pytorch:latest。我们基于Ubuntu 22.04 LTS构建自己的基础镜像关键步骤CUDA版本锁定下载对应CUDA Toolkit 11.8的runfile安装包非deb包因为runfile安装可精确控制驱动版本。执行./cuda_11.8.0_520.61.05_linux.run --silent --override --toolkit --samples--silent确保无交互--override跳过驱动检查因宿主机已装驱动--toolkit只安装CUDA toolkit。cuDNN精简安装不安装完整cuDNN库只复制推理必需的so文件cp /usr/local/cuda-11.8/lib64/libcudnn.so.8.6.0 /usr/local/lib/ ln -sf libcudnn.so.8.6.0 /usr/local/lib/libcudnn.so.8删除libcudnn_adv_*等训练相关库镜像体积减少42%。Python环境净化用pip install --no-cache-dir --force-reinstall安装PyTorch 1.13.1cu117然后执行pip uninstall -y torch torchvision torchaudio pip install torch1.13.1cu117 torchvision0.14.1cu117 -f https://download.pytorch.org/whl/torch_stable.html强制指定版本并禁用缓存避免pip自动升级。最终镜像大小控制在2.3GB比官方镜像小1.7GB且所有二进制文件sha256校验值全部记录在IMAGE_INTEGRITY.md中供审计。3.2 数据管道构建用Airflow实现确定性调度不用Jupyter Notebook做数据处理。我们用Airflow DAG定义数据流水线# dags/data_pipeline.py from airflow import DAG from airflow.operators.python import PythonOperator from datetime import datetime, timedelta def validate_data_integrity(**context): # 1. 校验原始数据SHA256 with open(/data/raw/checksum.txt) as f: expected_hash f.read().strip() actual_hash calculate_sha256(/data/raw/images/) if expected_hash ! actual_hash: raise ValueError(fData integrity check failed: {expected_hash} ! {actual_hash}) def preprocess_images(**context): # 2. 执行确定性预处理 subprocess.run([ python, preprocess.py, --input_dir, /data/raw/images/, --output_dir, /data/processed/, --resize, 224x224, --color_space, RGB ]) dag DAG( ai_data_pipeline, default_args{ retries: 1, retry_delay: timedelta(minutes5), }, schedule_intervaldaily, start_datedatetime(2024, 1, 1), catchupFalse, ) validate_task PythonOperator( task_idvalidate_data_integrity, python_callablevalidate_data_integrity, dagdag, ) preprocess_task PythonOperator( task_idpreprocess_images, python_callablepreprocess_images, dagdag, ) validate_task preprocess_task关键创新点在preprocess.py中所有OpenCV操作前插入cv2.setNumThreads(0) # 禁用OpenCV多线程避免跨平台结果差异 cv2.ocl.setUseOpenCL(False) # 禁用OpenCL防止AMD/NVIDIA GPU结果不一致实测证明同一张图片在Intel CPU和NVIDIA GPU上预处理结果完全一致。3.3 模型训练用PyTorch Lightning实现可复现实验不用裸写train_loop。我们用Lightning封装但禁用所有非确定性特性# train.py import pytorch_lightning as pl from pytorch_lightning import Trainer from pytorch_lightning.callbacks import ModelCheckpoint class LitModel(pl.LightningModule): def __init__(self, lr1e-3): super().__init__() self.save_hyperparameters() # 必须保存所有超参 def forward(self, x): return self.model(x) def training_step(self, batch, batch_idx): x, y batch y_hat self(x) loss self.loss_fn(y_hat, y) # 禁用梯度裁剪非确定性来源 # self.log(train_loss, loss) return loss # 关键配置 trainer Trainer( deterministicTrue, # 启用全局确定性 benchmarkFalse, # 禁用cuDNN benchmark避免不同batch size选择不同算法 max_epochs100, acceleratorgpu, devices2, strategyddp_find_unused_parameters_false, # DDP模式下必须设置 callbacks[ ModelCheckpoint( monitorval_loss, modemin, save_top_k1, save_lastTrue, filename{epoch}-{val_loss:.3f} ) ] ) # 训练前强制设置 pl.seed_everything(42, workersTrue) torch.use_deterministic_algorithms(True) os.environ[CUBLAS_WORKSPACE_CONFIG] :4096:8 # cuBLAS确定性配置每次训练启动时自动生成experiment_log.json{ timestamp: 2024-06-15T08:23:45Z, git_commit: a1b2c3d, cuda_version: 11.8.0, cudnn_version: 8.6.0, pytorch_version: 1.13.1cu117, seed: 42, hyperparameters: {lr: 0.001, batch_size: 32} }3.4 模型导出ONNX作为中间契约格式不直接部署.pth文件。我们强制通过ONNX中转# export_onnx.py import torch import onnx from onnxsim import simplify def export_model(model_path, input_shape(1,3,224,224)): model torch.load(model_path) model.eval() # 创建虚拟输入 dummy_input torch.randn(input_shape) # 导出ONNX torch.onnx.export( model, dummy_input, model.onnx, opset_version14, # 固定OPSET版本 input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}} ) # 简化ONNX移除冗余节点 model_onnx onnx.load(model.onnx) model_simp, check simplify(model_onnx) assert check, Simplified ONNX model could not be validated onnx.save(model_simp, model_simplified.onnx) if __name__ __main__: export_model(best_model.pth)关键点opset_version14固定ONNX算子集避免不同PyTorch版本导出不同算子。简化步骤移除ConstantOfShape等非必要节点使ONNX文件可读性提升——当客户质疑“为什么输出维度不对”我们可直接用Netron打开model_simplified.onnx查看计算图。3.5 推理服务封装FastAPI TensorRT加速不用Flask用FastAPI实现异步推理# server.py from fastapi import FastAPI, UploadFile, File from starlette.responses import JSONResponse import numpy as np import tensorrt as trt import pycuda.autoinit import pycuda.driver as cuda app FastAPI() class TRTInference: def __init__(self, engine_path): self.engine self.load_engine(engine_path) self.context self.engine.create_execution_context() # 分配GPU内存 self.d_input cuda.mem_alloc(1 * 3 * 224 * 224 * 4) # float32 self.d_output cuda.mem_alloc(1 * 1000 * 4) # 1000 classes def load_engine(self, path): with open(path, rb) as f: runtime trt.Runtime(trt.Logger(trt.Logger.WARNING)) return runtime.deserialize_cuda_engine(f.read()) app.post(/predict) async def predict(file: UploadFile File(...)): # 1. 图像预处理CPU image await file.read() img_array cv2.imdecode(np.frombuffer(image, np.uint8), cv2.IMREAD_COLOR) img_resized cv2.resize(img_array, (224, 224)) img_normalized (img_resized.astype(np.float32) / 255.0 - [0.485, 0.456, 0.406]) / [0.229, 0.224, 0.225] img_transposed np.transpose(img_normalized, (2, 0, 1)) # HWC - CHW # 2. GPU推理 cuda.memcpy_htod(self.d_input, img_transposed.astype(np.float32).ravel()) self.context.execute_v2([int(self.d_input), int(self.d_output)]) output np.empty((1, 1000), dtypenp.float32) cuda.memcpy_dtoh(output, self.d_output) # 3. 返回结果 top3 output[0].argsort()[-3:][::-1] return JSONResponse({top_classes: top3.tolist(), scores: output[0][top3].tolist()}) # 启动时预热 app.on_event(startup) async def startup_event(): # 执行3次warmup推理 for _ in range(3): dummy_input np.random.randn(1, 3, 224, 224).astype(np.float32) cuda.memcpy_htod(trt_infer.d_input, dummy_input.ravel()) trt_infer.context.execute_v2([int(trt_infer.d_input), int(trt_infer.d_output)])部署时用uvicorn启动uvicorn server:app --host 0.0.0.0 --port 8000 --workers 4 --limit-concurrency 100--limit-concurrency 100防止请求队列过长导致OOM比单纯限制workers数更精准。3.6 监控集成Prometheus Grafana语义看板不监控服务器指标监控业务语义指标# metrics.py from prometheus_client import Counter, Histogram, Gauge import time # 业务指标 PREDICTION_COUNT Counter(prediction_total, Total number of predictions, [model_version, status]) PREDICTION_LATENCY Histogram(prediction_latency_seconds, Prediction latency, [model_version], buckets[0.01, 0.05, 0.1, 0.2, 0.5, 1.0]) MODEL_LOAD_TIME Gauge(model_load_time_seconds, Time to load model into GPU memory, [model_version]) # 在推理函数中埋点 app.post(/predict) async def predict(file: UploadFile File(...)): start_time time.time() try: # ... 推理逻辑 ... PREDICTION_LATENCY.labels(model_versionv2.1.0).observe(time.time() - start_time) PREDICTION_COUNT.labels(model_versionv2.1.0, statussuccess).inc() return result except Exception as e: PREDICTION_COUNT.labels(model_versionv2.1.0, statuserror).inc() raise eGrafana看板关键面板P99延迟热力图X轴时间Y轴model_version颜色深浅表示延迟值漏检率趋势图model_fn_rate{classnodule}vstime()GPU显存利用率预测用Prophet模型预测未来24小时显存需求提前扩容4. 常见问题排查真实故障现场还原与解决4.1 故障现象p99延迟从120ms突增至450ms持续37分钟排查路径先看prediction_latency_seconds_bucket{le0.2}指标发现该bucket计数骤降83%说明大量请求突破200ms阈值检查container_memory_usage_bytesGPU容器内存使用率从65%升至92%但nvidia_smi_memory_used_bytes仅从3.2GB升至3.4GB——说明是CPU内存泄漏查看process_cpu_seconds_total发现uvicorn进程CPU使用率正常但python子进程CPU飙升用py-spy record -p pid -o profile.svg采样发现cv2.imdecode调用栈占CPU 78%深入检查客户上传的图片包含EXIF Orientation标记OpenCV默认不处理导致imdecode内部反复重试解码解决方案# 在预处理前添加EXIF修复 from PIL import Image, ExifTags import io def fix_orientation(image_bytes): img Image.open(io.BytesIO(image_bytes)) if hasattr(img, _getexif) and img._getexif(): exif dict(img._getexif().items()) orientation exif.get(274, 1) # EXIF tag 274 is Orientation if orientation 3: img img.rotate(180, expandTrue) elif orientation 6: img img.rotate(270, expandTrue) elif orientation 8: img img.rotate(90, expandTrue) return np.array(img) # 替换原cv2.imdecode image fix_orientation(await file.read())4.2 故障现象模型在A100上精度92.1%在V100上精度89.3%排查路径比对nvidia-smi -q -d MEMORYA100显存带宽2039GB/sV100为900GB/s但精度差异不应如此大检查torch.backends.cudnn.benchmark发现A100环境为TrueV100为False查看/var/log/nvidia-persistenced/nvidia-persistenced.logV100驱动版本450.80.02A100为515.65.01运行nvidia-smi -q -d COMPUTE发现V100的Compute Mode为DefaultA100为Prohibited——但这是权限问题不影响精度根本原因cuDNN 8.2.1在V100上对torch.nn.functional.conv2d的winograd算法实现有精度缺陷。解决方案# 强制禁用winograd torch.backends.cudnn.enabled True torch.backends.cudnn.benchmark False torch.backends.cudnn.deterministic True # 添加环境变量 os.environ[CUDNN_CONVOLUTION_BENCHMARK] 0 os.environ[CUDNN_CONVOLUTION_FWD_ALGO] 1 # 0autotune, 1direct4.3 故障现象Kubernetes滚动更新后新Pod的首请求延迟达2.3秒排查路径kubectl logs new-pod发现TensorRT引擎构建日志[INFO] Building CUDA engine...耗时2.1秒检查/tmp挂载新Pod的/tmp是emptyDir但TensorRT需要写入临时文件查看df -h /tmp发现/tmp所在分区是rootfsI/O吞吐仅12MB/s对比旧Pod/tmp挂载的是SSD-backed emptyDirI/O吞吐320MB/s解决方案# deployment.yaml volumeMounts: - name: trt-cache mountPath: /tmp/trt_cache volumes: - name: trt-cache emptyDir: medium: Memory # 使用内存而非磁盘并在TensorRT初始化时指定builder_config.set_flag(trt.BuilderFlag.TF32) builder_config.set_flag(trt.BuilderFlag.FP16) # 指定cache路径 engine builder.build_engine(network, builder_config) with open(/tmp/trt_cache/engine.trt, wb) as f: f.write(engine.serialize())4.4 故障现象Prometheus指标model_fn_rate持续为0但客户投诉漏检排查路径curl http://service/metrics确认指标存在且数值为0检查model_fn_rate定义发现标签{classnodule}但客户检测的是lung_nodule查看数据标注规范文档发现2024年Q2已将类别名从nodule改为lung_nodule检查训练代码class_names [lung_nodule, healthy]但监控埋点仍用旧名解决方案 建立类别名映射表class_mapping.json{ nodule: lung_nodule, mass: lung_mass }在监控埋点处动态转换predicted_class class_names[pred.argmax()] mapped_class class_mapping.get(predicted_class, predicted_class) MODEL_FN_RATE.labels(classmapped_class).inc()5. 经验总结那些文档不会写的实战铁律5.1 关于确定性随机种子只是起点不是终点我见过最惨的案例团队在训练脚本开头写random.seed(42); np.random.seed(42); torch.manual_seed(42)结果在多GPU训练时每个GPU上的torch.cuda.manual_seed_all(42)被不同进程调用导致各GPU的RNG状态不一致。后来我们强制要求所有随机操作必须使用独立torch.Generator实例DataLoader的worker_init_fn中为每个worker生成唯一seedseed 42 worker_id在模型forward中任何随机dropout必须传入generatorF.dropout(x, p0.1, trainingself.training, generatorgen)更重要的是确定性测试必须在目标硬件上执行。我们在A100上验证的确定性在V100上可能失效——因为cuDNN对不同GPU架构的kernel实现不同。所以CI流程中必须包含在目标GPU型号上的确定性回归测试。5.2 关于版本管理模型版本号要能回答三个问题当客户问“v2.1.0相比v2.0.0有哪些变化”你的回答不能是“优化了精度”。必须能立即给出输入变更input_spec.json中width从224改为384输出变更新增segmentation_mask输出字段shape为(1, 512, 512)行为变更confidence_threshold从0.5降至0.3以适应低对比度影像我们为此开发了model-diff工具model-diff v2.0.0 v2.1.0 # 输出 # - input_spec: width changed 224→384 # - output_spec: added segmentation_mask (1,512,512) # - behavior: confidence_threshold 0.5→0.3 # - training: added MixUp augmentation (alpha0.2)5.3 关于监控告警阈值必须随业务节奏动态调整上线初期我们设p99_latency_ms 300为P1告警。但产线早班6:00-14:00设备启动时网络抖动导致延迟自然升高。后来改为工作日 6:00-8:00阈值放宽至500ms工作日 14:00-16:00阈值收紧至200ms此时是质检高峰周末关闭所有延迟告警只保留精度告警用Prometheus的hour()函数实现ALERT HighLatency IF histogram_quantile(0.99, rate(prediction_latency_seconds_bucket[1h])) (300 200 * (hour() 6 AND hour() 8)) - 100 * (hour() 14 AND hour() 16) FOR 5m5.4 关于部署永远假设GPU会突然消失我们曾遇到NVIDIA驱动崩溃导致nvidia-smi返回空结果。此时服务不能直接退出而应检测到GPU不可用时自动切换至CPU推理性能下降但服务不中断发送告警GPU_UNAVAILABLE并记录driver_version_mismatch事件启动守护进程每30秒检查nvidia-smi -L恢复后自动切回GPU模式代码实现def get_device(): if torch.cuda.is_available(): try: # 尝试执行简单CUDA操作 torch.cuda.current_stream().synchronize() return torch.device(cuda) except Exception
返回列表