Slack AI 的多云之路:在企业级规模下可靠地服务大模型

2026-05-28 29 预计阅读时间:1 分钟
来源:slack.engineering AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:10 分钟

2023 年初,Slack 面对一个底层问题:如何在企业级规模上服务大语言模型,同时满足客户对安全、可靠性和性能的预期。三年间,他们从最基础的基础设施起步,逐步演进到一套成熟的多云编排架构。这不是追逐新模型的炫技,而是要构建一个能抵御区域级故障、在云厂商之间灵活调度的系统。

从单云到多云:为什么不能只靠一家

LLM 推理的负载特征和传统 Web 服务截然不同——单次请求耗 GPU 算力大、延迟敏感、流量波动剧烈。当 Slack 开始把 AI 功能(如摘要、搜索增强)推向所有企业客户时,单云部署很快暴露出两个硬伤:

  • 区域故障即全局故障。一家云的区域宕机,所有 AI 功能随之停摆,对 Slack 这种工作流中枢产品不可接受。
  • GPU 供给瓶颈。单一云厂商的 GPU 实例库存有限,高峰期拿不到机器,推理队列堆积。

多云不是"多花点钱买冗余"那么简单。它意味着推理请求要在不同云之间路由、模型权重要跨区域同步、监控和回滚策略要统一——编排复杂度陡然上升。

推理服务的编排:把模型当作可迁移的工作负载

Slack 的核心思路是:把 LLM 推理当作一种可跨云迁移的有状态工作负载,而不是绑定在某家云的托管服务上。这要求:

  1. 模型权重存储与分发独立于推理集群。用对象存储(如 S3/GCS)做权重仓库,推理节点启动时拉取,而非把权重 baked 进镜像。
  2. 请求路由层感知每朵云的健康与容量。不是简单轮询,而是根据延迟、可用 GPU 数、区域距离做加权调度。
  3. 统一的可观测性。不同云的日志、指标要汇聚到同一套监控栈,否则故障发生时你根本不知道问题在哪。

下面是一个简化版的多云推理路由配置思路,用 YAML 描述推理集群的拓扑,供参考和改造:

# multi_cloud_inference_topology.yaml
# 描述多云推理集群的端点与权重,供路由层消费

clusters:
  - name: aws-us-west-2
    provider: aws
    region: us-west-2
    endpoint: https://infer-usw2.internal.slack.ai/v1/chat
    gpu_type: a10g
    max_concurrency: 200
    weight: 40          # 正常状态时承担 40% 流量
    priority: 1         # 优先级,故障切换参考

  - name: gcp-us-central1
    provider: gcp
    region: us-central1
    endpoint: https://infer-usc1.internal.slack.ai/v1/chat
    gpu_type: l4
    max_concurrency: 180
    weight: 35
    priority: 2

  - name: aws-eu-west-1
    provider: aws
    region: eu-west-1
    endpoint: https://infer-euw1.internal.slack.ai/v1/chat
    gpu_type: a10g
    max_concurrency: 120
    weight: 25
    priority: 3

health_check:
  interval_seconds: 10
  timeout_seconds: 3
  failure_threshold: 3    # 连续 3 次失败则摘除

failover:
  strategy: weighted_rebalance   # 摘除后按剩余权重重新分配
  cooldown_seconds: 120          # 摘除后冷却 2 分钟再尝试恢复

路由层可以是一个轻量 Go 服务或 Python FastAPI 应用,定期读取这份拓扑配置,结合健康检查结果做实时调度。实际生产中,这份配置通常存在分布式 KV 存储(如 Consul)里,支持热更新。

模型权重的跨云同步:推理启动的零等待设计

推理节点冷启动时拉取模型权重是最大的延迟陷阱。一个 7B 参数模型的权重文件约 14 GB(FP16),跨区域下载动辄数分钟。Slack 的做法:

  • 预热缓存。在每朵云的对象存储中保持一份最新权重副本,推理节点从同云同区域的存储拉取,不走跨云链路。
  • 增量更新。当模型微调后只变更部分权重层(如 LoRA adapter),只同步 adapter 文件(几十 MB 到几百 MB),而非全量权重。
  • 启动探针。推理节点在权重加载完成前不接入路由,避免用户请求打到还没 ready 的实例上。

一个用 Python 实现的权重同步与推理启动检查脚本骨架:

#!/usr/bin/env python3
"""
权重预热与推理就绪检查脚本(简化版)
假设权重存储在同云 S3 兼容对象存储中
"""

import os
import time
import boto3
import requests

MODEL_BUCKET = os.getenv("MODEL_BUCKET", "slack-ai-models-usw2")
MODEL_KEY = os.getenv("MODEL_KEY", "llm/v2.1/full_weights.bin")
LOCAL_PATH = "/opt/models/full_weights.bin"
ROUTER_REGISTER_URL = os.getenv("ROUTER_REGISTER_URL", "http://router.internal:8080/register")
HEALTH_TIMEOUT = 300  # 权重下载+加载最长等待 5 分钟

def download_weights():
    """从同区域对象存储拉取权重"""
    s3 = boto3.client("s3", region_name=os.getenv("AWS_REGION", "us-west-2"))
    print(f"Downloading {MODEL_KEY} from bucket {MODEL_BUCKET}...")
    s3.download_file(MODEL_BUCKET, MODEL_KEY, LOCAL_PATH)
    size_mb = os.path.getsize(LOCAL_PATH) / (1024 * 1024)
    print(f"Download complete: {size_mb:.1f} MB")

def wait_for_inference_ready():
    """等待本地推理服务加载权重并就绪"""
    infer_url = os.getenv("INFER_LOCAL_URL", "http://localhost:8000/health")
    start = time.time()
    while time.time() - start < HEALTH_TIMEOUT:
        try:
            r = requests.get(infer_url, timeout=2)
            if r.status_code == 200 and r.json().get("model_loaded"):
                print("Inference service is ready.")
                return True
        except requests.RequestException:
            pass
        time.sleep(5)
    raise RuntimeError("Inference service did not become ready within timeout")

def register_with_router():
    """向路由层注册本节点,开始接收流量"""
    payload = {
        "instance_id": os.getenv("INSTANCE_ID", "infer-usw2-001"),
        "endpoint": os.getenv("PUBLIC_ENDPOINT", "https://infer-usw2.internal.slack.ai/v1/chat"),
        "capacity": int(os.getenv("MAX_CONCURRENCY", "200")),
    }
    r = requests.post(ROUTER_REGISTER_URL, json=payload, timeout=5)
    print(f"Registered with router: status={r.status_code}")

if __name__ == "__main__":
    download_weights()
    wait_for_inference_ready()
    register_with_router()

运行前设置环境变量:

export MODEL_BUCKET=slack-ai-models-usw2
export MODEL_KEY=llm/v2.1/full_weights.bin
export AWS_REGION=us-west-2
export ROUTER_REGISTER_URL=http://router.internal:8080/register
export INSTANCE_ID=infer-usw2-001
python3 weight_sync_and_register.py

多云的代价:运维复杂度与一致性博弈

多云不是免费的午餐。Slack 在演进过程中需要持续应对:

  • 配置漂移。两朵云上的推理框架版本、CUDA 驱动、网络策略稍有差异,行为就可能不一致。需要统一的 CI/CD 管线把同一套推理镜像部署到所有云。
  • 成本追踪。GPU 实例价格在不同云、不同区域波动大。没有细粒度的成本标签和流量归因,财务团队根本看不清钱花在哪。
  • 安全合规。企业客户的数据在哪个云、哪个区域被处理,必须可审计。跨云数据传输要满足 GDPR 等法规的边界约束。

Slack 的经验是:先在单云上把推理服务做到稳定可观测,再逐步引入第二朵云。跳过单云成熟阶段直接上多云,往往是在两个云上同时踩坑。

落地建议

如果你也在考虑 LLM 推理的多云架构,可以按这个节奏推进:

  1. 单云站稳——推理服务在单云上跑满一个季度,延迟 P99、错误率、GPU 利用率都有清晰基线。
  2. 抽象路由层——在用户请求和推理集群之间加一层路由,支持按权重和健康状态调度,这是多云的前提。
  3. 权重存储解耦——推理镜像不含权重,启动时从对象存储拉取,为跨云迁移扫清障碍。
  4. 引入第二朵云——先承载 10-20% 流量做影子验证,确认模型输出一致、延迟可接受后逐步提升权重。
  5. 统一监控与告警——所有云的指标汇聚到同一套 Prometheus/Grafana 或等效栈,故障发现和定位不能靠人肉跳板。

多云架构的目标不是"每个云都跑满",而是在任何一朵云出问题时,用户感知不到中断。Slack 三年的路径说明:这件事值得做,但必须一步步来,每一步都要有数据支撑。


相关推荐