面灵AI→

腾讯 Agent Infra 面经:SGX 沙箱与推理性能排查

时间
2026-09
来源
牛客网

《面试题目》

  1. 自我介绍,重点讲你在蚂蚁实习做的 Agent 自进化。
  2. 你说的 Agent 自进化,具体怎么做的?进化指标怎么来的?
  3. Agent 怎么采集数据?你觉得最有价值的是什么?
  4. 如果线上场景要求 Agent 全流程自动化、不依赖人工审核,你怎么设计机制保证系统稳定性和结果可靠?
  5. 现在平台有一台 Intel SGX 机器,面向多租户提供计算服务,用户数据量约 1GB,你如何设计方案?
  6. EPC 资源只有 512MB,用户数据需要不断切入切出 EPC,如何保证这个过程中的安全性?
  7. 如果沙箱里的 Agent 试图访问宿主机的文件系统,怎么拦截?
  8. 容器重启后,会话状态怎么恢复?你存了什么?存在哪里?
  9. Agent 执行长任务,跑到一半服务重启了,怎么恢复?
  10. 你存了哪些状态?任务状态、工具调用记录、模型版本、中间产物,这些怎么组织?
  11. 恢复后怎么保证状态一致性?如果恢复失败了怎么办?
  12. 有没有做过状态快照?快照频率怎么定?
  13. 如果要设计一个通用 Agent Runtime,兼容通义千问、文心一言、GPT-4 等多个模型,你怎么抽象?
  14. 不同模型的 API 接口、上下文格式、工具调用方式都不一致,怎么统一?
  15. 模型路由的依据是什么?如果某个模型突然不可用,怎么自动切换?切换后上下文怎么适配?
  16. Agent 系统怎么做链路追踪?一次请求经过 Planner、Retriever、Tool、LLM,怎么定位是哪个环节慢?
  17. 如果某个工具调用平均耗时从 200ms 涨到 2s,你怎么排查?
  18. Trace 怎么设计?保存哪些字段?
  19. 如果数据量 4 倍增长但吞吐量没上升,可能是什么原因?瓶颈在哪?
  20. 你了解 KV Cache 吗?它是什么作用,为什么能加速大模型推理?
  21. KV Cache 的显存占用怎么估算?batch size 从 1 增加到 32,显存怎么变化?
  22. vLLM 的 PagedAttention 原理是什么?它怎么解决 KV Cache 的碎片化问题?
  23. 如果 vLLM 出现内存泄漏,你怎么定位?从哪些方向排查?
  24. 长序列训练中,激活值显存占用比模型参数还大,怎么优化?FlashAttention 能省多少?
  25. 你了解 ONNX 转 RKNN 的图优化吗?请列举至少三种。
  26. 如何判断一个算子是带宽瓶颈还是计算瓶颈?Roofline 模型是什么?
  27. CUDA Graph 适合什么场景?动态输入下怎么用 CUDA Graph?
  28. GPU 的 SIMT 架构有什么特点?遇到分支发散时会发生什么?
  29. 你最近在关注 Agent Infra 领域的什么新技术?
  30. 手撕:解析嵌套字符串。实现函数解析形如 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 内分支、让相邻线程访问相邻地址(合并访存)来规避。