腾讯 Agent Infra 面经:SGX 沙箱与推理性能排查
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 自我介绍,重点讲你在蚂蚁实习做的 Agent 自进化。
- 你说的 Agent 自进化,具体怎么做的?进化指标怎么来的?
- Agent 怎么采集数据?你觉得最有价值的是什么?
- 如果线上场景要求 Agent 全流程自动化、不依赖人工审核,你怎么设计机制保证系统稳定性和结果可靠?
- 现在平台有一台 Intel SGX 机器,面向多租户提供计算服务,用户数据量约 1GB,你如何设计方案?
- EPC 资源只有 512MB,用户数据需要不断切入切出 EPC,如何保证这个过程中的安全性?
- 如果沙箱里的 Agent 试图访问宿主机的文件系统,怎么拦截?
- 容器重启后,会话状态怎么恢复?你存了什么?存在哪里?
- Agent 执行长任务,跑到一半服务重启了,怎么恢复?
- 你存了哪些状态?任务状态、工具调用记录、模型版本、中间产物,这些怎么组织?
- 恢复后怎么保证状态一致性?如果恢复失败了怎么办?
- 有没有做过状态快照?快照频率怎么定?
- 如果要设计一个通用 Agent Runtime,兼容通义千问、文心一言、GPT-4 等多个模型,你怎么抽象?
- 不同模型的 API 接口、上下文格式、工具调用方式都不一致,怎么统一?
- 模型路由的依据是什么?如果某个模型突然不可用,怎么自动切换?切换后上下文怎么适配?
- Agent 系统怎么做链路追踪?一次请求经过 Planner、Retriever、Tool、LLM,怎么定位是哪个环节慢?
- 如果某个工具调用平均耗时从 200ms 涨到 2s,你怎么排查?
- Trace 怎么设计?保存哪些字段?
- 如果数据量 4 倍增长但吞吐量没上升,可能是什么原因?瓶颈在哪?
- 你了解 KV Cache 吗?它是什么作用,为什么能加速大模型推理?
- KV Cache 的显存占用怎么估算?batch size 从 1 增加到 32,显存怎么变化?
- vLLM 的 PagedAttention 原理是什么?它怎么解决 KV Cache 的碎片化问题?
- 如果 vLLM 出现内存泄漏,你怎么定位?从哪些方向排查?
- 长序列训练中,激活值显存占用比模型参数还大,怎么优化?FlashAttention 能省多少?
- 你了解 ONNX 转 RKNN 的图优化吗?请列举至少三种。
- 如何判断一个算子是带宽瓶颈还是计算瓶颈?Roofline 模型是什么?
- CUDA Graph 适合什么场景?动态输入下怎么用 CUDA Graph?
- GPU 的 SIMT 架构有什么特点?遇到分支发散时会发生什么?
- 你最近在关注 Agent Infra 领域的什么新技术?
- 手撕:解析嵌套字符串。实现函数解析形如
3[ab2[c]]的字符串,输出abccabccabcc,支持多层嵌套与多位数;递归实现即可并分析复杂度。追问:嵌套深度超过 1 万层怎么办?数字特别大(如1000000[ab])内存扛不住怎么办?
《参考解析》
SGX 多租户:EPC 只有 512MB 时的方案与安全边界
SGX 的模型是「Enclave 内的代码和数据受 CPU 保护,宿主 OS 与 Hypervisor 都不能读」,所以方案要围绕两件事设计:进入 Enclave 的数据必须最小化且加密传输,以及换出 EPC 的页面绝不能明文落在宿主可见的内存或磁盘上。1GB 数据配 512MB EPC,必然频繁换页,标准做法是:数据始终以密文形式驻留在普通内存或磁盘(由 Enclave 内的密钥解密),EPC 只当作高速缓存用;页的换入换出交给 SGX 驱动,但 Enclave 侧要对换出的页做加密 + 完整性保护(MAC 或 Merkle 树),否则宿主可以回滚旧页、篡改页内容或重放,这三类攻击对应的是机密性、完整性、新鲜性,光加密不够。密钥不能放在 Enclave 外面,要用 SGX 的密封(sealing)机制绑定到平台和 Enclave 身份;同时远程证明(remote attestation)让租户确认跑的是预期代码。多租户还要注意侧信道:共享 EPC 带来的页级访问模式泄漏很现实,敏感场景要么单租户独占,要么把访问模式也做混淆。
沙箱隔离:拦宿主文件系统访问的几层
单靠「审计 + 事后告警」不够,要按纵深防御来答:① 命名空间与挂载隔离(容器 + 只读 rootfs + 独立 mount namespace),从根上不给宿主路径;② 系统调用过滤(seccomp-bpf 白名单,直接挡掉 open 宿主路径的能力;配合 AppArmor/SELinux 的强制访问控制策略);③ 权限降维(非 root、drop capabilities、user namespace 映射、禁止 ptrace 和 mount);④ 网络与文件出站白名单(只允许访问代理,代理做鉴权与审计);⑤ 更高安全等级上微虚拟机(Firecracker/Kata/gVisor)把内核也隔开,或用我们自己的 SGX Enclave 做「最小可信计算基」。最后一定要有审计日志:记录每次敏感系统调用的来源(是用户指令还是外部抓来的网页内容触发的),这是 Agent 场景特有的威胁——prompt injection 会诱导 Agent 自己发起越权访问。
长任务的状态恢复与快照一致性
Agent 的状态比普通服务多:任务状态机、已完成步骤及其幂等 key、工具调用入参出参、模型与 prompt 版本、中间产物引用(文件/向量/临时表)。存储选型按「需要多快恢复」定:热状态进 Redis 或关系库,大中间产物进对象存储并在状态里存引用,事件流进日志/消息队列便于重放。恢复的正确姿势是幂等重放:以最后一条持久化的状态为起点,重放未确认的步骤,写操作靠幂等 key 去重,读操作可以直接重跑;绝不能「从头再执行一遍」。一致性靠状态机加乐观锁(版本号 CAS)保证,避免两个恢复进程同时改同一任务;恢复失败要落到明确的 failed 终态并告警,不允许静默重跑。快照频率是「恢复成本」和「快照开销」的权衡:通常按步数或时间双阈值触发(每 N 步或每 T 秒),并在关键节点(写操作前、跨服务调用前)强制打一次,因为正是这些点失败代价最高。
通用 Agent Runtime 的多模型抽象
抽象的关键是找最小公共接口,而不是把各家 API 求并集。核心接口大致是四个:chat(messages, tools, params)(统一的消息与角色模型)、stream()(统一成 token 增量事件流)、tool_calls(统一成「名字 + JSON 参数 + 调用 ID」,把 OpenAI 的 function calling、Anthropic 的 tool use、国内厂商的差异都在适配层抹平)、以及 token 计数与用量回传。上层不许出现任何厂商专有字段,全部在适配器里翻译。模型路由的依据分四类:能力(任务类型 → 模型能力标签)、成本、延迟、可用性(健康检查 + 熔断);路由决策要可观测、可回放。
不可用时自动切换的工程要点:健康探测(主动拨测 + 被动错误率)、熔断与半开、按 provider 维度的并发与配额隔离、切换要幂等且可回退(同一次请求不能在两个模型上产生副作用)。切换后上下文适配有两件事要做:① 工具调用格式回填成新模型认识的格式(历史里已经存在的 tool 消息要能翻译);② 上下文窗口与计费口径不同时,按新模型预算重新裁剪。最忌讳切了模型但对话历史还带着上一个模型的专有格式,那必然报错。
链路追踪与「工具变慢」的排查
Trace 设计的最小可用字段:trace_id / span_id / parent_span_id、阶段名(planner/retriever/tool/llm)、起止时间戳与耗时、输入输出摘要(脱敏,不落原文)、token 用量与模型版本、错误码与重试次数、以及关联的业务 ID。有了它,「哪个环节慢」就是按 span 耗时排序的问题。
「某个工具从 200ms 涨到 2s」要分四层查:① 先确认是不是工具本身(直接压测该工具的独立接口,排除 Agent 侧影响);② 看它的下游(第三方 API 限流/降级、DNS 解析、TLS 握手、连接池耗尽);③ 看调用侧(重试次数上升会拉高均值、并发升高导致排队、超时设置变化);④ 看数据侧(入参变大、返回体变大、目标服务数据量增长)。排查时一定要区分均值 vs 分位数:均值从 200ms 涨到 2s,可能是 10% 的请求变成了 20s,也可能是全部均匀变慢,这两种的根因完全不同。
KV Cache、PagedAttention 与推理优化
KV Cache 缓存的是每层注意力的 Key/Value 张量,避免自回归生成时对整个前缀反复重算,把每步复杂度从 O(n²) 降到 O(n)。代价是显存:单序列占用约 2 × 层数 × 头数 × 头维 × 序列长度 × 每元素字节数 ×(张量并行切分),与序列长度线性相关;batch 从 1 增到 32,如果序列长度相同,KV Cache 显存基本线性 ×32(权重不变,激活略增),所以显存很快成为并发上限——这也解释了为什么高并发推理要限制上下文长度、做前缀缓存复用。
vLLM 的 PagedAttention 借鉴了操作系统虚拟内存分页:把 KV Cache 切成固定大小的 block,用 block table 把逻辑上连续的序列映射到不连续的物理块,于是内部碎片被压到「最后一个块内的浪费」,不同序列之间还能共享块(前缀共享、并行采样、beam search 场景收益巨大),显存利用率从传统预分配的几成提升到九成以上,吞吐随之提升。vLLM 出现内存泄漏时,排查方向是:显存是否随请求数单调上涨(区分「缓存不释放」和「真泄漏」)、block 是否被正确回收(看 block manager 的空闲块计数)、长序列/超长上下文请求是否有残留、以及是否有未释放的 CUDA Graph 或采样器状态;用 nvidia-smi、torch.cuda.memory_summary() 和定量的并发压测做对照。
长序列训练里激活值显存超过参数,主要优化手段是重计算(activation checkpointing)、序列/张量并行、以及把注意力本身做成显存友好的实现——FlashAttention 通过分块(tiling)在 SRAM 里算注意力、不落 O(n²) 的中间矩阵,显存从 O(n²) 降到 O(n),同时因减少 HBM 往返而更快;能省多少取决于序列长度与实现,长序列下常常是一个数量级的显存下降。
判断算子瓶颈先算两个上限:计算上限(峰值算力 ÷ 实际 FLOPs 得到的最短时间)与带宽上限(数据量 ÷ 显存带宽)。roofline 模型就是把二者画在一张图上,横轴是算术强度(FLOPs/Byte),纵轴是可达到的性能,拐点就是机器平衡点——落在拐点左侧是带宽瓶颈(如逐元素加法、LayerNorm、大部分 attention 的 decode 阶段),右侧是计算瓶颈(如大矩阵乘)。诊断手段很直接:用 Nsight Compute 看 SM 利用率与 DRAM 吞吐,带宽跑满而算力空闲就是带宽瓶颈,解决办法是算子融合、量化、减少中间张量搬运;算力跑满就是计算瓶颈,解决办法是更好的算法(如 FlashAttention)或更低精度(FP8/INT8)。
CUDA Graph 适合「kernel 序列固定、启动开销占比高」的场景(小 batch 推理、大量小 kernel 的流水),把整段执行录成一个图一次性提交,消掉 CPU 侧的逐个启动开销。动态输入(batch 或序列长度变化)的用法是:按几个典型形状各录一张图,运行时按实际 shape 选最接近的图并做 padding,或用 conditional node / 图更新(graph update)替换参数,避免每次输入变化都重新捕获。GPU 的 SIMT 意味着 32 个线程(一个 warp)共享取指与调度,代价是遇到 if/else 分支时硬件可能串行执行两条路径(分支发散),有效并行度下降;写 kernel 时要用谓词化、避免 warp 内分支、让相邻线程访问相邻地址(合并访存)来规避。