普通视图

发现新文章,点击刷新页面。
昨天以前掘金专栏-字节跳动技术团队

DeepSeek + Function Call:基于 Eino 的“计划——执行”多智能体范式实战

DeepSeek-R1(以下简称 DeepSeek)以其优秀的复杂问题推理能力和规划能力脱颖而出,然而其原生函数调用(Function Call)功能的缺失,无法让大模型去选择不同的工具和程序,以获取对应的信息,使其难以完成以下关键动作:

  • 实时数据获取(天气 / 票务 / 交通)

  • 外部服务交互(地图 API / 支付接口)

  • 复杂任务拆解执行(多步骤自动化)

这就导致它的应用场景受到限制,大多只能用于简单的对话式问答。有没有一个解决办法,能实现让 DeepSeek 做 Function Call?

答案是肯定的,我们提出 " 计划——执行” 多智能体的协同范式

由 DeepSeek 负责 “指挥”,由擅长 Function Call 的其他大模型去听指挥进行函数调用。这需要利用“计划——执行” 多智能体范式,由 “计划” 智能体负责推理和生成计划,由 “执行” 智能体负责执行计划:

“计划——执行” 多智能体范式的三大优势:

  1. 专业的 “智能体” 干专业的事情:比如 DeepSeek 负责推理和计划,豆包大模型负责 Function Call。

  2. “智能体” 层面的单一职责原则:每个智能体的职责是明确的,解耦的,方便 Prompt 调优和评测。

  3. 在提供解决问题整体方案的同时,保持灵活性:符合人类解决问题的通用模式。

要实现 “计划 —— 执行” 多智能体,我们必须要解决几个问题:多模型、多工具集成,复杂流程编排,上下文管理以及中间步骤追踪。Eino(文档 cloudwego.io/zh/docs/ein… 项目页 github.com/cloudwego/e… 框架通过提供开箱即用的模型组件实现和工具执行器、面向抽象接口的灵活流程编排能力、完备的全局状态管理以及回调机制,确保了上述问题的有效解决。

接下来,文章将直观的解释 “计划 —— 执行” 多智能体范式,介绍如何借助 Eino 框架来实现基于 DeepSeek 的‘计划 —— 执行’多智能体,最后通过一个有趣且靠谱的主题乐园行程规划助手的实战案例,带大家从 0 到 1 搭建一个完整的应用。

“计划——执行” 多智能体

基本的 ReAct 单智能体,是由一个 Agent 既负责计划拆解,也负责 Function Call:

可能存在的问题有三个:

  1. 对 LLM 的要求高:既要擅长推理规划,也要擅长做 Function Call。

  2. LLM 的 prompt 复杂:既要能正确规划,又要正确的做 Function Call,还要能输出正确的结果。

  3. 没有计划:每次 Function Call 之后,LLM 需要重新推理,没有整体的可靠计划。

解决的思路,首先是把单个的 LLM 节点拆分成两个,一个负责 “计划”,一个负责 “执行”:

这样就解决了上面的问题 3,Planner 会给出完整计划,Executor 依据这个完整计划来依次执行。部分解决了问题 1、2,Planner 只需要擅长推理规划,Executor 则需要擅长做 Function Call 和总结,各自的 prompt 都是原先的一个子集。但同时带来一个新的问题:

  1. 缺少纠错能力:最开始的计划,在执行后,是否真的符合预期、能够解决问题?

继续优化多智能体结构,在 Executor 后面增加一个 LLM 节点,负责 “反思和调整计划”:

这样就彻底解决了上面列出的问题,Executor 只需要按计划执行 Function Call,Reviser 负责反思和总结。

这就是 “计划——执行” 多智能体:通过将任务解决过程拆解为负责计划的 Planner 和 Reviser,以及负责执行的 Executor,实现了智能体的单一职责以及任务的有效计划与反思,同时也能够充分发挥 DeepSeek 这种推理模型的长项、规避其短板(Function Call)。

基于 Eino 框架实现 “计划——

执****行” 多智能体

实现一个 “计划——执行” 多智能体,需要:

  • 能够快速简单的集成 DeepSeek、豆包等各种大模型。

  • 能够快速简单的集成和执行各种 Tool。

  • 能够快速实现流程编排,把多个智能体以及工具按设计的流程串联起来,并能随时快速调整。

  • 能够及时的输出各智能体的执行过程,包括 DeepSeek 的推理过程。

  • 能够有效的管理和传递上下文。

Eino 是字节跳动开源的基于 Golang 的大模型应用开发框架,已在豆包、抖音、扣子等多个业务线广泛使用。我们选择 Eino 作为框架来进行全码开发,因为:

  • Eino 可以用几行代码完成对各种大模型的调用,包括 DeepSeek。

  • Eino 可以用几行代码快速把一个本地 Function 封装成 Tool,且有开箱即用的 Tool 执行器。

  • Eino 的流程编排能力可靠且灵活:分支判断,循环,运行时参数配置等。

  • Eino 的数据流处理能力为大模型应用场景而设计,可配合完整的回调机制实时输出中间结果。

  • Eino 可以通过在图编排时配置和读写全局状态来实现有效的上下文管理和传递。

Eino 的详细信息参见:文档 cloudwego.io/zh/docs/ein…

GitHub 项目页 github.com/cloudwego/e…

实战:主题乐园行程规划助手

我们通过实现一个主题乐园行程规划助手,来探索如何用 Eino 实现基于 DeepSeek 的 “计划——执行” 多智能体。这个多智能体的功能是根据用户的游园需求,规划出具体、符合要求、可操作的行程安排。完整代码仓库地址:github.com/cloudwego/e…

定义多智能体

首先定义多智能体以及需要的配置:

package plan_execute
import (
    "github.com/cloudwego/eino/components/model"
    "github.com/cloudwego/eino/compose"
    "github.com/cloudwego/eino/schema"
)
// Config “计划——执行”多智能体的配置.
type Config struct {
    PlannerModel        model.ChatModel // planner 智能体使用的大模型
    PlannerSystemPrompt string          // planner 智能体的 system prompt
    ExecutorModel        model.ChatModel         // executor 智能体使用的大模型
    ToolsConfig          compose.ToolsNodeConfig // executor 智能体使用的工具执行器配置
    ExecutorSystemPrompt string                  // executor 智能体的 system prompt
    ReviserModel        model.ChatModel // reviser 智能体使用的大模型
    ReviserSystemPrompt string          // reviser 智能体的 system prompt
    MaxStep int // 多智能体的最大执行步骤数,避免无限循环
}
// PlanExecuteMultiAgent “计划——执行”多智能体.
type PlanExecuteMultiAgent struct {
    // 图编排后的可执行体,输入是 Message 数组,输出是单条 Message
    runnable compose.Runnable[[]*schema.Message, *schema.Message]
}

多智能体编排逻辑

Eino 的流程编排有 “节点(Node)”、“边(Edge)” 和“分支 (Branch)” 组成,数据流转时要求严格的类型对齐。完整的数据流转图如下:

上图中,Planner,Executor,Reviser 都是输入为 []*Message,输出为 * Message 的 ChatModel 节点,Branch1 判断 Executor 是否完成了本轮次所有的 Function Call,Branch2 判断 Reviser 是否输出了最终答案,各个 ToList 节点负责连接两个 ChatModel,将输出的 *Message 转化为 []*Message,从而满足类型校验要求。

我们实现一个 NewMultiAgent 方法来实现上述编排逻辑:

// NewMultiAgent 根据配置编排一个“计划——执行”多智能体.
func NewMultiAgent(ctx context.Context, config *Config) (*PlanExecuteMultiAgent, error) {
    var (
       toolInfos      []*schema.ToolInfo
       toolsNode      *compose.ToolsNode
       err            error
       plannerPrompt  = config.PlannerSystemPrompt
       executorPrompt = config.ExecutorSystemPrompt
       reviserPrompt  = config.ReviserSystemPrompt
       maxStep        = config.MaxStep
    )
    if len(plannerPrompt) == 0 {
       plannerPrompt = defaultPlannerPrompt
    }
    if len(executorPrompt) == 0 {
       executorPrompt = defaultExecutorPrompt
    }
    if len(reviserPrompt) == 0 {
       reviserPrompt = defaultReviserPrompt
    }
    if maxStep == 0 {
       maxStep = defaultMaxStep
    }
    if toolInfos, err = genToolInfos(ctx, config.ToolsConfig); err != nil {
       return nil, err
    }
    // 为 Executor 配置工具
    if err = config.ExecutorModel.BindTools(toolInfos); err != nil {
       return nil, err
    }
    // 初始化 Tool 执行器节点,传入可执行的工具
    if toolsNode, err = compose.NewToolNode(ctx, &config.ToolsConfig); err != nil {
       return nil, err
    }
    // 创建一个待编排的 graph,规定整体的输入输出类型
    graph := compose.NewGraph[[]*schema.Message, *schema.Message]()
    // 定义 Executor 后的分支判断用的条件函数。该函数的输出是运行时选中的 NodeKey
    executorPostBranchCondition := func(_ context.Context, msg *schema.Message) (endNode string, err error) {
        if len(msg.ToolCalls) == 0 {
           return nodeKeyExecutorToList, nil
        }
        return nodeKeyTools, nil
    }
    // 定义 Reviser 后的分支判断用的条件函数。
    reviserPostBranchCondition := func(_ context.Context, sr *schema.StreamReader[*schema.Message]) (endNode string, err error) {
       defer sr.Close()
       var content string
       for {
          msg, err := sr.Recv()
          if err != nil {
             if err == io.EOF {
                return nodeKeyReviserToList, nil
             }
             return "", err
          }
          content += msg.Content
          if strings.Contains(content, "最终答案") {
             return compose.END, nil
          }
          if len(content) > 20 {
             return nodeKeyReviserToList, nil
          }
       }
    }
    // 添加 Planner 节点
    _ = graph.AddChatModelNode(nodeKeyPlanner, config.PlannerModel, compose.WithNodeName(nodeKeyPlanner))
    // 添加 Executor 节点
    _ = graph.AddChatModelNode(nodeKeyExecutor, config.ExecutorModel, compose.WithNodeName(nodeKeyExecutor))
    // 添加 Reviser 节点
    _ = graph.AddChatModelNode(nodeKeyReviser, config.ReviserModel, compose.WithNodeName(nodeKeyReviser))
    // 添加 Tool 执行器节点
    _ = graph.AddToolsNode(nodeKeyTools, toolsNode)
    // 添加三个 ToList 转换节点
    _ = graph.AddLambdaNode(nodeKeyPlannerToList, compose.ToList[*schema.Message]())
    _ = graph.AddLambdaNode(nodeKeyExecutorToList, compose.ToList[*schema.Message]())
    _ = graph.AddLambdaNode(nodeKeyReviserToList, compose.ToList[*schema.Message]())
    // 添加节点之间的边和分支
    _ = graph.AddEdge(compose.START, nodeKeyPlanner)
    _ = graph.AddEdge(nodeKeyPlanner, nodeKeyPlannerToList)
    _ = graph.AddEdge(nodeKeyPlannerToList, nodeKeyExecutor)
    _ = graph.AddBranch(nodeKeyExecutor, compose.NewStreamGraphBranch(executorPostBranchCondition, map[string]bool{
       nodeKeyTools:          true,
       nodeKeyExecutorToList: true,
    }))
    _ = graph.AddEdge(nodeKeyTools, nodeKeyExecutor)
    _ = graph.AddEdge(nodeKeyExecutorToList, nodeKeyReviser)
    _ = graph.AddBranch(nodeKeyReviser, compose.NewStreamGraphBranch(reviserPostBranchCondition, map[string]bool{
       nodeKeyReviserToList: true,
       compose.END:          true,
    }))
    _ = graph.AddEdge(nodeKeyReviserToList, nodeKeyExecutor)
    // 编译 graph,将节点、边、分支转化为面向运行时的结构。由于 graph 中存在环,使用 AnyPredecessor 模式,同时设置运行时最大步数。
    runnable, err := graph.Compile(ctx, compose.WithNodeTriggerMode(compose.AnyPredecessor), compose.WithMaxRunSteps(maxStep))
    if err != nil {
       return nil, err
    }
    return &PlanExecuteMultiAgent{
       runnable: runnable,
    }, nil
}

Tool 实现

我们的主题乐园行程规划助手,需要用到下列工具:

  • query_theme_park_opening_hour: 查询乐园 A 的整体营业时间

  • query_park_ticket_price: 查询乐园 A 的门票价格

  • list_locations: 列出乐园 A 中的所有区域,每个游乐设施都归属于一个区域

  • query_location_adjacency_info: 查询乐园 A 中的一个区域到其他相邻区域的步行时间,以分钟为单位

  • query_attraction_queue_time: 查询游乐设施的排队时间,以分钟为单位

  • query_attraction_info: 查询游乐设施的具体信息

  • query_performance_info: 查询演出的具体信息

  • query_restaurant_info: 查询餐厅的具体信息

  • validate_performance_time_table: 校验安排的表演场次是否符合事实

  • arrange_performances: 根据选中的表演名称,自动根据表演的时间表排程

  • validate_plan_items: 根据一个一日日程安排提案,校验各个计划项内部及之间是否自洽

首先定义核心的领域模型:

type ActivityType string
const (
    ActivityTypeAttraction  ActivityType = "attraction"
    ActivityTypePerformance ActivityType = "performance"
    ActivityTypeRestaurant  ActivityType = "restaurant"
    ActivityTypeOther       ActivityType = "other"
)
// Activity 主题乐园中的一个项目,可以是游乐设施、表演或餐厅.
type Activity struct {
    Name               string       `json:"name"`
    Desc               string       `json:"desc"`
    Type               ActivityType `json:"type"`
    Location           string       `json:"location" jsonschema:"description:项目所属的区域"`
    MinHeight          int          `json:"min_height,omitempty" jsonschema:"description:参加游乐设施需要的最小身高,单位是厘米。如果为空,则没有身高要求"`
    Duration           int          `json:"duration,omitempty" jsonschema:"description:一个项目参加一次需要的时间,注意不包括排队的时间。如果为空,则缺少具体的时间信息"`
    TimeTable          []string     `json:"time_table,omitempty" jsonschema:"description:一个演出的时间表。如果为空,则使用 OpenTime 和 CloseTime 来表示这个项目的运营时间范围"`
    OpenTime           string       `json:"open_time,omitempty" jsonschema:"description:一个项目开始运营的时间"`
    CloseTime          string       `json:"close_time,omitempty" jsonschema:"description:一个项目结束运营的时间"`
    RequireBooking     bool         `json:"require_booking,omitempty" jsonschema:"description:一个餐厅是否需要提前预约"`
    HasPriorityAccess  bool         `json:"has_priority_access,omitempty" jsonschema:"description:一个项目是否有高速票服务"`
    PriorityAccessCost int          `json:"priority_access_cost,omitempty" jsonschema:"description:一个项目如果有高速票服务,则一个人的高速票需要花多少钱"`
    QueueTime          int          `json:"queue_time,omitempty" jsonschema:"description:一个项目常规需要的排队时间,单位是分钟。如果为空,则这个项目一般不需要排队"`
}

注意大多数字段中都有 jsonschema:"description:xxx"go struct tag。Eino 框架可抽取这个信息以及其他的 tag 给到大模型。

实现工具列表中需要的本地 function,如:

// GetAttractionInfo 获取游乐设施信息.
func GetAttractionInfo(_ context.Context, in *ListAttractionRequest) (out *ListAttractionResponse, err error) {
    if len(in.Name) > 0 && in.Name != "all" {
       for _, a := range attractions {
          if a.Name == in.Name {
             return &ListAttractionResponse{
                Attractions: []Activity{
                   a,
                },
             }, nil
          }
       }
    }
    if len(in.Location) > 0 {
       locationAttractions := make([]Activity, 0)
       for _, a := range attractions {
          if a.Location == in.Location {
             locationAttractions = append(locationAttractions, a)
             return &ListAttractionResponse{
                Attractions: locationAttractions,
             }, nil
          }
       }
    }
    return &ListAttractionResponse{
       Attractions: attractions,
    }, nil
}

完整的领域模型及服务定义参见代码链接 (github.com/cloudwego/e…

数据来源:可以是主题乐园提供的 API,也可以是外置的数据库,在我们的场景中,直接在项目中维护结构化的信息(完整代码链接 github.com/cloudwego/e…

将本地 function 封装成 Tool:

func GetTools(ctx context.Context) (tools []tool.BaseTool, err error) {    queryTimeTool, err := utils.InferTool("query_theme_park_opening_hour", "查询乐园 A 的整体营业时间", GetParkHour)    if err != nil {       return nil, err    }    tools = append(tools, queryTimeTool)        // 以下省略多个 Tool        return}

完整的 Tool 封装代码参见代码链接 (github.com/cloudwego/e…

上下文管理

针对每个智能体的一次执行,它的上下文应当包括:

  • 用户输入的任务。

  • 之前执行的智能体(包括自身)的输出。

  • 之前执行的智能体的 Function Call,以及对应的结果。

  • 自身的 System Prompt。

为了保存多智能体的上下文,我们为 graph 增加全局状态,并在各智能体执行前以及 Tool 执行前,向这个全局状态中读写上下文:

// state 以多智能体一次运行为 scope 的全局状态,用于记录上下文
type state struct {
    messages            []*schema.Message
}
func NewMultiAgent(ctx context.Context, config *Config) (*PlanExecuteMultiAgent, error) {
    // ... 省略 N 行 ... 
    // 创建一个待编排的 graph,规定整体的输入输出类型,配置全局状态的初始化方法
    graph := compose.NewGraph[[]*schema.Message, *schema.Message](compose.WithGenLocalState(func(ctx context.Context) *state {
       return &state{}
    }))
    // 在大模型执行之前,向全局状态中保存上下文,并组装本次的上下文
modelPreHandle := func(systemPrompt string, isDeepSeek bool) compose.StatePreHandler[[]*schema.Message, *state] {
    return func(ctx context.Context, input []*schema.Message, state *state) ([]*schema.Message, error) {
       for _, msg := range input {
          state.messages = append(state.messages, msg)
       }
       if isDeepSeek {
          return append([]*schema.Message{schema.SystemMessage(systemPrompt)}, convertMessagesForDeepSeek(state.messages)...), nil
       }
       return append([]*schema.Message{schema.SystemMessage(systemPrompt)}, state.messages...), nil
    }
}
    // ... 省略 N 行 ...
    // 添加 Planner 节点,同时添加 StatePreHandler 读写上下文
    _ = graph.AddChatModelNode(nodeKeyPlanner, config.PlannerModel, compose.WithStatePreHandler(modelPreHandle(plannerPrompt, true)), compose.WithNodeName(nodeKeyPlanner))
    // 添加 Executor 节点,同时添加 StatePreHandler 读写上下文
    _ = graph.AddChatModelNode(nodeKeyExecutor, config.ExecutorModel, compose.WithStatePreHandler(modelPreHandle(executorPrompt, false)), compose.WithNodeName(nodeKeyExecutor))
    // 添加 Reviser 节点,同时添加 StatePreHandler 读写上下文
    _ = graph.AddChatModelNode(nodeKeyReviser, config.ReviserModel, compose.WithStatePreHandler(modelPreHandle(reviserPrompt, true)), compose.WithNodeName(nodeKeyReviser))
    // 添加 Tool 执行器节点,同时添加 StatePreHandler 读写上下文
    _ = graph.AddToolsNode(nodeKeyTools, toolsNode, compose.WithStatePreHandler(func(ctx context.Context, in *schema.Message, state *state) (*schema.Message, error) {
        state.messages = append(state.messages, in)
        return in, nil
    }))
    // ... 省略 N 行 ...
}

完整编排代码见链接 github.com/cloudwego/e…

main 函数:多智能体执行

多智能体执行逻辑需要实现下列功能:

  • 实例化 DeepSeek 和豆包的模型,并放到多智能体的配置中。

  • 获取 Tool 列表。

  • 依据配置编排和初始化多智能体。

  • 将多智能体的各中间步骤及时输出。

在 main 函数中:利用 Eino 框架提供的组件实现,实例化需要的大模型,获取 Tool,初始化多智能体:

func main() {
    ctx := context.Background()
    deepSeekModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{
       Model:   os.Getenv("DEEPSEEK_MODEL_ID"),
       APIKey:  os.Getenv("DEEPSEEK_API_KEY"),
       BaseURL: os.Getenv("DEEPSEEK_BASE_URL"),
    })
    if err != nil {
       log.Fatalf("new DeepSeek model failed: %v", err)
    }
    arkModel, err := ark.NewChatModel(ctx, &ark.ChatModelConfig{
       APIKey: os.Getenv("ARK_API_KEY"),
       Model:  os.Getenv("ARK_MODEL_ID"),
    })
    if err != nil {
       log.Fatalf("new Ark model failed: %v", err)
    }
    toolsConfig, err := tools.GetTools(ctx)
    if err != nil {
       log.Fatalf("get tools config failed: %v", err)
    }
    // 创建多智能体的配置,system prompt 都用默认值
    config := &Config{
       // planner 在调试时大部分场景不需要真的去生成,可以用 mock 输出替代
       PlannerModel: &debug.ChatModelDebugDecorator{
          Model: deepSeekModel,
       },
       ExecutorModel: arkModel,
       ToolsConfig:   compose.ToolsNodeConfig{Tools: toolsConfig},
       ReviserModel: &debug.ChatModelDebugDecorator{
          Model: deepSeekModel,
       },
    }
    planExecuteAgent, err := NewMultiAgent(ctx, config)
    if err != nil {
       log.Fatalf("new plan execute multi agent failed: %v", err)
    }
    printer := newIntermediateOutputPrinter() // 创建一个中间结果打印器
    printer.printStream()                     // 开始异步输出到 console
    handler := printer.toCallbackHandler()    // 转化为 Eino 框架的 callback handler
    // 以流式方式调用多智能体,实际的 OutputStream 不再需要关注,因为所有输出都由 intermediateOutputPrinter 处理了
    _, err = planExecuteAgent.Stream(ctx, []*schema.Message{schema.UserMessage("我们一家三口去乐园玩,孩子身高 120 cm,园内预算 2000 元,最爱的是各种表演,游乐设施比较偏爱刺激项目,希望能在一天内尽可能多体验不同的活动,帮忙规划一个行程。我们会在园区开门之后立刻入园,在园区关闭之后再离开。")},
       agent.WithComposeOptions(compose.WithCallbacks(handler)), // 将中间结果打印的 callback handler 注入进来
       // 给 planner 指定 mock 输出
       //agent.WithComposeOptions(compose.WithChatModelOption(debug.WithDebugOutput(schema.AssistantMessage(debug.PlannerOutput, nil))).DesignateNode(nodeKeyPlanner)),
       // 给 reviser 指定 mock 输出
       //agent.WithComposeOptions(compose.WithChatModelOption(debug.WithDebugOutput(schema.AssistantMessage("最终答案", nil))).DesignateNode(nodeKeyReviser)),
    )
    if err != nil {
       log.Fatalf("stream error: %v", err)
    }
    printer.wait() // 等待所有输出都处理完再结束
}

完整 main 函数代码实现链接 github.com/cloudwego/e…

流式输出中间过程

在上面的 main 函数中可以看到,我们通过 printer 这个 “中间结果打印器”,把各智能体的流式输出异步打印了出来。这利用了 Eino 框架的 callback 机制,在 ChatModel 输出时和 Tool 起止时触发执行切面逻辑。

调试与优化

在上面的 main 函数中,通过 ChatModelDebugDecorator 把 Planner 智能体封装起来,这是因为调试过程中,我们经常需要固定 Planner 的输出,单独调试后续流程。在 Eino 框架中,能够很方便的实现类似的装饰者,因为所有的组件如 ChatModel 等都是 interface,从编排角度看,原始的 ChatModel 实现和对应的装饰者可以无缝替换。完整调试代码实现见链接 github.com/cloudwego/e…

如果你更习惯可视化调试,可以安装 Eino Dev IDE 插件 www.cloudwego.io/zh/docs/ein… Golang 和 VSCode),把多智能体的编排拓扑在 IDE 中直观的展示出来,还可以指定中间的某个智能体开始调试。

在实战过程中,对 system prompt 的优化占据了相当多的时间,有关优化的最佳实践还在总结之中,后续会在 Eino 项目中发布,可以关注我们的 Github 项目 github.com/cloudwego/e… 获取最新的进展。

实际效果

以下为 Reviser 智能体的最终输出:

最终答案:

总费用:门票 1100 元 + 高速票 540 元 + 餐饮 420 元 = **2060 元 **(超支 60 元,可通过午餐降级为蓝莓熊餐盒节省 60 元)

关键优化点:

  1. 高速票精准投放:仅在 “抱抱熊飞天赛车” 午间时段使用(性价比最高),其他项目利用排队低峰期

  2. 表演全覆盖:新增 “复仇者小分队培训行动”,保留所有核心表演场次

  3. 动线优化:区域集中化(未来世界→玩具的故事→幻想世界→奇幻园林→入口大街)

  4. 预算控制:通过调整午餐为快餐(蓝莓熊餐盒人均 50 元)可使总费用降至 2000 元以内

  5. 刺激项目最大化:包含 3 个过山车类项目(抱抱熊 / 矿山车 / 飞吧地平线)+ 2 个黑暗骑乘(家勒比海贼 / 派斯音速)

执行效果:在保证所有热门表演的前提下,实现 5 个高刺激项目体验,时间利用率达 92%,区域移动时间占比仅 8%

完整执行过程见链接 github.com/cloudwego/e…

相关链接

Eino 框架 Github 仓库地址:

项目官网:www.cloudwego.io

项目文档:cloudwego.io/zh/docs/ein…

让AI代码从能用变好用! Trae+火山引擎数智平台, 打造"会进化"的智能应用

在AI编程工具高速发展的今天,Cursor、Trae等工具凭借自然语言生成代码、跨语言支持等能力,极大提升了开发效率。

工具生成的代码注重功能实现。打造一款受欢迎的产品,有了能实现功能的代码并不够,还需要追踪后续数据(Track)并验证效果,才能使开发者在优化用户体验和商业决策时消除盲区。

如何让AI生成的代码真正融入业务场景,实现从“能用”到“好用”的跨越?火山引擎数智平台的DataTester(A/B测试平台)与DataFinder(增长分析工具)的深度集成,为这一难题提供了科学答案。

图片

AI 生成代码的痛点:功能完善≠效果最优

当前主流AI编程工具(如Cursor、Trae)虽能快速生成应用框架,但存在两大短板:

  1. 产品分析 数据缺失:生成的App缺乏埋点设计,无法追踪用户点击、转化路径等关键行为,导致优化无据可依。
  2. 实验验证能力不足:功能上线后难以通过A/B测试验证不同版本的效果差异,只能依赖主观判断或事后分析,试错成本高。

以电商场景为例,AI生成的促销页面可能因按钮位置、文案差异影响转化率,但若无埋点与实验能力,开发者无法量化哪种设计更优,最终导致资源浪费。


火山引擎 DataTester  +  DataFinder  :补齐 AI 工具的最后一环

在Trae中结合火山引擎数智平台(VeDI)的产品,将能获得比使用单一的AI编程工具更好的使用体验;通过数据产品的辅助,AI编程结果可以更好地进化迭代。

火山引擎数智平台(VeDI)的两大核心产品——DataTesterDataFinder,通过“数据采集+智能实验”的组合,为AI生成的代码注入全链路优化能力:

  1. 行为 数据 追踪:从“功能实现”到“数据驱动”
  • DataFinder提供轻量级SDK,支持一键集成到Trae生成的代码中,自动采集用户点击、停留时长、转化漏斗等行为数据,并生成可视化报告。
  • 例如,开发者通过Trae生成的购物车页面,可借助DataFinder分析用户从加购到支付的流失节点,定位体验瓶颈。
  1. A/B实验验证:科学决策取代经验主义
  • DataTester提供三类实验能力,适配多场景优化需求:
    • 策略迭代实验:测试不同UI设计、算法策略的效果差异,例如推荐算法模型A/B测试。
    • 功能发布实验:结合Feature Flag功能,实现代码功能的无感下发与灰度发布,降低线上风险。
    • 增长营销实验:针对AI生成的广告素材、落地页,快速验证点击率与转化率,优化投放ROI。
  • 例如,Trae生成的应用可通过DataTester对比不同的用户注册界面,结合DataFinder采集转化数据,选出转化率最优方案。
  1. 全链路闭环:从生成到优化,  AI 全程参与
  • Trae 生成代码 → DataFinder 埋点 追踪 → DataTester 实验验证 → AI 模型反馈调优,形成完整闭环。
  • 火山引擎DataTester支持与大模型联动,例如通过实验数据反哺Prompt优化,让AI生成的代码更贴合业务目标。

案例实践:  AI 工具+  火山引擎  ,释放业务增长潜能

场景1:社交App弹窗优化

  • 问题:AI生成的弹窗样式单一,用户关闭率高。
  • 方案
    • 使用DataTester创建多个弹窗设计版本(如按钮位置、文案语气)。
    • 通过DataFinder分析各版本的点击率与留存率。
    • 实验结果显示“底部按钮+趣味文案”组合的转化率提升32%,全量上线。

场景2:  电商推荐 算法迭代

  • 问题:AI生成的推荐模型效果不稳定。
  • 方案
    • DataTester并行运行新旧算法版本,划分流量对比GMV指标。
    • 结合DataFinder的用户路径分析,定位高价值群体的偏好差异。
    • 实验数据反馈至Trae的AI模型,优化后续代码生成逻辑。

未来展望:  AI 开发者的“科学工具箱”

随着火山引擎DataTester与DataFinder的深度整合,AI编程工具正从“代码生成器”进化为“业务增长引擎”。开发者可专注于创新设计,而数据埋点、实验验证等繁琐环节交由平台自动化处理。这一模式不仅适用于互联网行业,在金融、零售、汽车等领域的数字化场景中同样潜力巨大。

立即行动

  • 访问Trae官网(trae.com.cn)体验AI代码生成。
  • 扫描二维码,获取DataTester与DataFinder的定制化解决方案,让每一行代码都精准命中业务目标。

通过“AI生成+数据智能”的双轮驱动,开发者将真正实现从功能开发价值创造的跨越,开启效率与效果并重的新时代。

图片

AIBrix 深度解读:字节跳动大模型推理的云原生实践

AIBrix 项目目前已经开源,本文为 AIBrix 技术解析。详见:
🔗 vLLM 博客:blog.vllm.ai/2025/02/21/…
🔗 代码仓库:github.com/vllm-projec…
🔗 技术详解博客:aibrix.github.io/posts/2025-…

01

前言

随着 LLaMA、DeepSeek、Qwen 等开源大模型的快速崛起,企业在模型部署的灵活性、成本与自主可控性方面迎来了新的机遇。然而,仅靠对模型本身的优化尚不足以将这些模型部署成高效且可扩展的生产级 API。大模型推理往往引入诸多独特的系统挑战,如 GPU 弹性伸缩指标的非线性问题,长尾模型和精调模型流量过低的问题,多机推理时的角色编排以及 GPU 卡型的异构管理等,都对易用性和成本控制提出了更高要求。因此,我们需要从推理引擎到底层基础设施进行全栈系统设计,才能真正让大模型在生产环境中长期稳定且高效地运行。

AIBrix 作为首个基于 Kubernetes 的企业级推理系统项目,正好填补了业界在 “系统层” 上的空白。它通过优化资源调度、自适应扩缩容、缓存感知路由以及异构计算管理等多项能力,为企业级大模型的大规模部署提供高效、低成本、可扩展的解决方案。AIBrix 与 vLLM 等推理引擎深度协同,持续优化推理效率,并融合多项前沿研究成果,推动大模型推理走向更加高效、可落地的生产化阶段。

02

AIBrix 的项目背景与设计理念

在规划 AIBrix 项目的过程中,我们始终站在基础架构的角度,思考如何在大规模场景下为推理引擎提供更好支持。结合字节跳动内部的业务实践,我们发现,大模型往往会带来一系列与传统微服务截然不同的系统挑战,包括:

  • 模型权重的下载 / 加载:如何快速分发和加载体积庞大的模型文件,降低冷启动延迟。

  • GPU 弹性伸缩指标的非线性:大模型对 GPU 的利用率并非线性关系,传统的指标收集与弹性策略常常滞后或不精准。

  • 长尾模型或精调模型流量低:针对这些流量低但又需要及时响应的模型,如何做到有效的资源利用和成本控制。

  • 多机推理的角色编排:在分布式推理场景下,如何更高效地在多个节点之间分配和调度任务。

  • GPU 卡型异构:不同型号、不同性能的 GPU 共同部署时,如何协同工作并优化利用率。

  • 单服务跨 Region:多数据中心与跨区域部署的需求增大了同步管理模型与推理任务的难度,同时对容灾与可用性提出了更高要求。

传统微服务框架(如 KNative)或服务网格(如 Istio)在鉴权、流量管控、版本升级等通用能力上已经相当成熟,但对于大模型服务而言仍然显得过于臃肿,且缺少针对性的优化。此外,市面上大多数项目往往将推理引擎视作一个 “黑盒”,无法进行深度协同优化。

设计理念

为应对上述挑战,AIBrix 的核心理念在于通过 “引擎层” 与“系统层”的紧密协同,搭建一个轻量化、云原生的方案。具体而言,我们将部分通用的引擎功能卸载到系统层面进行管理,并对模型推理常用能力进行封装,向外提供统一的引擎接口层。这种模式能够在大规模场景下同时兼顾性能、成本和易用性,帮助企业级大模型部署实现更高的弹性和可控性。

系统架构

AIBrix 包含控制平面组件与数据平面组件,并完全基于 Kubernetes 进行开发,采用完整的云原生设计来确保系统的可扩展性、可靠性以及资源效率。AIBrix 充分利用了 Kubernetes 的现有功能,包括自定义资源 (CRD)、控制器机制以及动态服务发现等,为大规模 LLM 推理服务提供了稳健的基础设施。

控制平面组件主要负责管理模型元数据注册、自动扩缩容、模型适配器注册,并执行各种策略。数据平面组件则提供可配置的请求派发、调度与推理服务能力,实现灵活且高性能的模型推理执行。下图为 AIBrix 的系统架构

AIBrix 项目已发布了 v0.1.0 和 v0.2.0 两个版本。在 v0.1.0 阶段,我们主要针对 Serverless 场景进行了一系列优化,着重解决冷启动、弹性伸缩和高密度部署的问题。而在 v0.2.0 阶段,我们则聚焦于分布式与解耦化,通过多机推理、KV-Cache 管理以及异构计算管理等特性,让大模型的规模化部署更加高效可控。

03

AIBrix v0.1.0:Serverless 与高密度部署

Serverless 与弹性伸缩

AIBrix v0.1.0 的主要思路是将大模型在生产环境中面临的核心难题,与 Serverless 领域的几项关键技术(冷启动、快速伸缩与高密度部署)相结合。我们并不追求让大模型像 FaaS 一样彻底 “无服务器化”,因为这在现实中尚难达到理想效果,也并非企业级生产环境的最佳形态;更可行的路线是借鉴并改进 Serverless 的相关思路,对大模型的部署环节进行有针对性的优化。

线上观察:Autoscaling 与指标挑战

在实际应用中,Autoscaling 最大的难点是:流量波峰和推理实例利用率之间通常存在显著的时间滞后(常见在 2~5 分钟),导致高并发场景下容易出现短时过载,从而拉升长尾延迟。此外,传统的 GPU 性能指标(如 DCGM 暴露的 DCGM_FI_DEV_GPU_UTIL 或 DCGM_FI_PROF_SM_ACTIVE)严重依赖引擎自身实现,也很难体现 GPU 空间利用率,导致扩缩容决策往往不够精确。

多种伸缩方案探索

为此,我们尝试过将引擎 KV_CACHE 利用率 与队列中待处理请求的输入 / 输出指标结合起来,做出更精细的扩缩容判断。然而在实际业务中,保障 SLO(而非 GPU 利用率)通常是更高优先级的目标,这使得传统基于资源利用率的 Autoscaling 策略效果有限。为了应对这一挑战,我们又探索了 基于 Profiling 并以 SLO 驱动的扩缩容方案,通过对历史与实时流量分布进行分析,动态确定扩缩容时机,减少过载并降低尾部延迟。

目前,AIBrix 在此方向上仍在持续迭代研究,包括尝试更具前瞻性的 LLM 专用指标,以及 Proactive 主动式弹性策略,让系统在应对突发流量时更加游刃有余。

在架构设计中,v0.1.0 主要引入了 Gateway API Plugin (Envoy) + Runtime 这两个组件,以适配大模型通常面对的两类路由方式:应用层路由(app router) 和 代理层路由(proxy router)。在大模型社区,如 vLLM 正不断丰富自身 API(含 token、transcription、score 等),保持与引擎原生接口一致是一项不小的挑战。为此,我们采用了高性能标准化的 envoy gateway 配合 extension server 来实现定制化,来进行高性能且可定制化的流量管理:

  • 只在必要处做 request head/body 的修改,尽量避免重复实现类似 OpenAI 的 API;

  • 同时支持对请求进行缓存感知的调度,包括 kv cache least used、least of prompt、prefix-cache aware 等策略,以进一步缩短长尾 TTFT(Time to First Token) 等性能指标。

冷启动与模型加载优化

在冷启动问题上,我们重点考察了不同机型在 网络带宽、本地盘 I/O、RDMA 等方面的性能差异。虽然云原生社区已有如 Fluid 等项目可在 “1 -> N” 场景下发挥缓存加速作用,但在 “0 -> 1” 阶段,磁盘 I/O 并不总能比网络更快,有时通过 远程流式加载 直接将权重加载进 GPU memory 反而效率更高。

为此,AIBrix 在 v0.1.0 中实现了 GPU 流式加载 方案,支持在 Tensor 层面更细粒度地控制下载速度和顺序,为开发者提供灵活的组合策略。需要注意的是,若机型配有本地 NVMe 磁盘,则本地加载可能仍优于远程;而在分布式文件系统场景下,单机自我读取也能减轻对共享文件系统的集中访问压力。AIBrix 将这些能力进一步封装,开发者可基于自有机型和带宽状况,自行选择最佳加载方式。

高密度模型部署

对于精调模型(如 LoRA),实现高密度部署是释放其竞争力的关键。我们在 vLLM 项目中做了大量改动来支持 LoRA 的动态部署与度量,血缘关系追踪、LoRA metrics 单独计量等关键特征,方便与 AIBrix 控制面深度集成。但这其中依然存在若干未解决的挑战,我们正在逐步完善并计划在后续版本中支持更多功能:

  • 单容器混合部署:目前基本模型(Base Model)和精调模型(LoRA)常被打包在同一容器,虽然能减少部署节点,但也打破了容器隔离以及不可变性的原则,某些场景会因过载触发部署失败。

  • Adaptive LoRA batch、dynamic merge 等高级功能还在持续研发当中,旨在进一步提高同一 GPU 上运行多个模型或微调版本的效率。

  • 定制化内存分配器(memory allocator):在固定 GPU 资源中快速换入换出不同基础模型,利用引擎原生的 CUDA 虚拟内存(visual memory)管理能力,使多模型部署具备更好的鲁棒性与伸缩性。

04

AIBrix v0.2.0:分布式与解耦系统

分布式编排和多机推理

AIBrix v0.2.0 的核心工作在于构建分布式与解耦(Distributed and Disaggregated)系统,其中分布式部分主要关注多机推理的编排。我们在对 DeepSeek-R1 671B 模型、16 卡满配场景下进行验证后,已经实现了较为稳定的分布式推理方案。具体来说,AIBrix 采用 Ray 来编排分布式推理任务,原因包括:

  • vLLM 自带分布式 runtime:默认支持 Ray 与多进程架构,为分布式推理奠定良好基础。

  • KubeRay 场景经验积累:AIBrix 项目的核心成员曾主导 KubeRay 的开源工作,对如何在 Kubernetes 与 Ray 之间实现高效整合有着丰富的实践。目前,KubeRay 是行业通用的 Ray on Kubernetes 编排方案,被广泛应用于包括字节跳动在内的多家企业生产环境。

  • 云原生的多角色编排:在一个 CRD 中灵活编排不同容器或角色(如 TP/PP 等)并非易事,而多机调度策略也可能因具体业务场景(例如 P&D、Splitwise 论文提出的 Router/CLS、Mixed Pool 或 vLLM xPyD 等)而改变。通过 “混合编排(Hybrid Orchestration)” 理念,让 Ray 负责应用内部的角色管理,Kubernetes 则专注于升级、伸缩等通用工作,双方分工明确且更具灵活性。

   在实际实现中,我们将一个多容器推理实例视作一个 Ray 应用,用 RayCluster 来进行描述,再由   RayClusterFleet 负责升级与扩缩容等通用任务。除此之外,我们还在 vLLM 中加入了额外的弹性功能,允许集群节点在资源不足时先行等待,触发 Pod 调度与自动扩缩容后,再承接推理负载;这一改进在生产环境中显著提升了容错与鲁棒性。

KV Cache 组件管理

在 Prefix/Session Cache、P&D Disaggregation、跨机请求迁移等场景中,KV Cache 组件扮演至关重要的角色。如果仅放在推理引擎内部,诸如跨机分享 KV Cache 等操作就会非常复杂。为此,AIBrix 通过分布式 KV 缓存来应对这些挑战,不仅实现了跨引擎的 KV 复用,同时也在网络与内存效率方面进行了优化。我们的方案采用了一种可防扫描(scan-resistant)的淘汰策略,有选择地保留热点 KV 张量,从而最大程度地减少不必要的数据传输;此外,通过异步方式维护元数据更新进而降低系统开销,并在缓存与引擎的协同部署(colocation)中利用共享内存进行更快速的数据传输。

在实际部署场景中,我们发现:

  • 内存层次优化:在 prefix cache 等场景中,  如果低端 GPU 型号模型加载已经占用大部分 HBM 显存,留给 KV Cache 的空间十分有限;此时可借助空闲的 CPU DRAM 做 “二级” 缓存,能实现一定程度上的容量扩展。需要注意的是,从绝对性能角度,这种方案不可避免地会带来从 CPU DRAM 到 GPU HBM 间数据交换的额外开销,但在容量与性能间取得平衡对于某些业务仍然十分必要。

  • 灵活的淘汰策略:AIBrix 还在基于 vLLM v1 的全新架构做进一步调整,向上游社区贡献更多 KV Cache 淘汰策略的实现,敬请期待后续更新。

异构计算与成本优化

在异构资源环境中,并非所有用户都能在同一集群内获取一致的 GPU 规格,常常需要混合不同型号的 GPU 来支持同一业务。而异构卡的性能差异也会影响控制面的调度与数据面的路由。

AIBrix 针对这种需求,通过 **Profiling + ILP (整数线性规划)**的组合,找到了成本最优的机型分配和部署方案。对于异构路由策略层面的能力,目前相关功能和特性也正在开发中。

故障诊断与模拟工具

故障诊断与模拟工具链接:aibrix.github.io/posts/2025-…

AI Accelerator 故障诊断与模拟工具是 AIBrix 的系统组件,基于火山引擎容器服务 (VKE) 的经验开发,针对的是 GPU 故障和性能下降在大规模 AI 部署中构成重大挑战 -- 静默错误、过热、内存泄漏和间歇性等故障可导致模型性能下降、延迟增加,甚至系统崩溃;而在异构 AI accelerator 环境中,不同 GPU 型号在不同工作负载下表现不一致,故障诊断和自动化运维更加棘手。

  • 故障检测:目前针对不同厂商的卡型能够完成自动化故障检测, 帮助用户在影响负载之前识别性能问题。

  • 故障模拟:该工具可以模拟 GPU 的性能下降或硬件故障,方便开发者测试和构建高容错能力的 AI 系统。一旦故障发生,系统能平滑恢复,降低对整体服务的影响。

  • 硬件支持:目前已支持 NVIDIA GPU 等主流 AI 芯片,后续也将持续扩展兼容更多类型的加速器。

04

AIBrix On VKE

火山引擎容器服务已实现了 AIBrix 的组件化接入,在一系列 GenAI 场景下的基准测试中,弹性伸缩性能与 token 吞吐量提升超 10%,LoRA 应用成本最高降低 4.7 倍,模型加载提速可超 50%。收益详情如下:

在上述核心特性中,弹性伸缩是连接云上应用与云服务的桥梁。接下来,我们将着重聚焦 LLM 弹性伸缩,深入探究其在 GenAI 场景中发挥的作用以及与 VKE 结合所带来的价值。

Autocsaling On VKE

资源准备与镜像预置

VKE 通过节点池统一管理实例资源,使用节点池创建 8 台 A10 单卡实例,作为实验环境。

节点池支持包年包月、按量付费、弹性预约、Spot 等多种实例交付方式,满足不同场景下的成本与可用性需求

容器镜像方面,通过预加载的方式在实例上提前拉取 deepseek-coder-7b 模型镜像,加快 Pod 拉起速度。

端到端可观测性

VKE 集成了对网络请求流入流出、各类资源状态与利用率、Kubernetes 资源对象以及应用自身运行指标的端到端观测,并且支持应用的自定义指标透出,借助这些能力,可以全面观测 LLM 应用的运行状态。对于弹性伸缩场景,观测指标一方面用于工作负载伸缩,一方面用于观察 AIBrix 的弹性伸缩效果。

实验与结论

AIBrix 集成了多种 Pod 伸缩方法,在本例中,使用 Kubernetes 原生的水平 Pod 自动扩缩器(HPA)与 AIBrix 实现的 Kubernetes Pod 自动扩缩器(KPA,可参考 KPA)进行对比。

LLM 应用负载,使用 vllm 运行 deepseek-coder-7b,弹性伸缩指标使用 vllm:gpu_cache_usage_perc,访问请求从 ShareGPT 中随机抽取,并以指定的并发数将这些请求分发给该服务。对于 HPA,AIBrix 会创建一个 Kubernetes 原生的 HPA 实例,以扩展指标的方式进行伸缩。对于 KPA,AIBrix 实现了其完整的流程,包括指标收集、对目标部署状态的定期监控以及伸缩操作。

实验数据如下所示。AIBrix 支持直接从 Pod 中拉取关键指标,因此伸缩响应速度获得显著提升,大模型应用首次伸缩响应耗时 12 秒, 相比 HPA 的 67 秒耗时加速 82%。AIBrix 的完整扩容周期为 120 秒,而 HPA 为 320 秒,加速 62.5%,并且震动频次降低 33%。

05

写在最后

AIBrix 的目标是将大模型推理的 “系统侧” 能力与 “引擎侧” 创新完美结合,提供从资源调度、网络流量控制到分布式推理的端到端解决方案。通过与 vLLM 开源社区的深度协作,我们希望不断迭代并完善在云原生环境下的大模型部署架构,让企业能够更加轻量、弹性地构建面向生产的 LLM 推理服务。

在 AIBrix 开发过程中,我们的很多创新想法都受到了学术研究的启发,比如 Preble、Melange、QLM 和 MoonCake 等,在这里我们真诚地感谢这些成果背后的研究人员。我们也非常感谢 vLLM 社区的支持,使 AIBrix 成为了 vLLM 的控制面,进一步增强了我们构建可扩展和高效 AI 基础设施的使命感。

AIBrix 由字节跳动开源,现在正在开源社区的支持下成为一个完全开源的项目——目前项目已经吸引了来自密歇根大学、伊利诺伊大学厄巴纳 - 香槟分校、华盛顿大学、Google、DaoCloud 等学术界和工业界的开源伙伴。未来,我们也希望 AIBrix 能通过开放、协作的方法塑造 AI-Infra 的未来,持续将顶尖学术研究和行业内的生产级实践结合起来。也欢迎更多开发者和企业加入我们,为开放、可扩展的 AI 基础设施的未来做出贡献:github.com/vllm-projec…

vArmor:云原生容器安全的多场景应用实践

English Version: vArmor: Multi-scenario Application Practices for Cloud-Native Container Security

项目地址:github.com/bytedance/v…

项目官网:varmor.org

简介

vArmor 是字节跳动开源的云原生容器沙箱系统,它借助 Linux 的 AppArmor LSMBPF LSMSeccomp 技术进行容器加固。用户可以通过 vArmor 的 CRD API 在 Kubernetes 集群中管理安全策略,对指定工作负载的容器进行加固。vArmor 旨在降低利用现有技术加固容器的门槛和成本,从而平衡安全风险与防护成本。

本文将介绍我们推出 vArmor 项目的目的,然后从技术角度出发介绍其在不同场景的应用。本文将向您展示如何凭借vArmor 的技术特性来解决特定问题,从而实现技术与业务目标,助力企业构建云原生环境下的安全防线。

为什么推出 vArmor

容器运行时组件和 Kubernetes 早已增加了对 LSM、Seccomp 的支持,其中 Seccomp 在 Kubernetes v1.19 GA,AppArmor LSM 在 Kubernetes v1.30 GA。用户可以自行编写和管理 AppArmor、SELinux、Seccomp Profiles,并在工作负载中配置安全策略对其进行加固。几乎所有的容器运行时组件都附带了默认的 AppArmor 和 Seccomp 安全策略,但默认的 Seccomp 策略需要显式设置才会为容器开启,而默认的 AppArmor 策略需要操作系统支持才会为容器自动开启。

充分利用 Linux 系统的安全机制可以有效加固容器。例如通过 LSM、Seccomp 等技术对容器进程进行强制访问控制,可以减少内核攻击面、增加容器逃逸或横向移动攻击的难度与成本。它们的基本原理如下图所示。

然而,编写和管理安全策略则面临诸多挑战:

  • 容器运行时组件的默认安全策略存在局限性,无法防御某些漏洞、错误配置风险,也不能限制攻击者在容器内的渗透行为。

  • 构建 AppArmor、Seccomp、SELinux Profile 需要专业知识。

  • 为复杂且快速迭代的容器化应用制定健壮的安全策略(尤其是 Deny-by-Default 模式的策略)难度较大。

  • AppArmor 或 SELinux LSM 依赖操作系统发行版,存在一定局限性。

  • 在 Kubernetes 环境中,自动化管理和应用不同的安全策略比较复杂。

为了解决这些问题,vArmor 应运而生。它提供了多种策略模式、内置规则和配置选项,vArmor 会根据策略对象的定义,管理安全策略(AppArmor Profile、BPF Profile、Seccomp Profile)对不同工作负载的容器进行加固。vArmor 还基于 BPF 和 Audit 技术实现了行为建模功能,可以对不同应用进行行为采集并生成行为模型,从而辅助构建安全策略。

例如,用户可以按需配置策略对象,实现违规拦截、拦截并告警、只告警不拦截三种效果,并使用内置规则和自定义规则动态更新策略对象,从而满足不同应用场景的需要。下面我们将用几个实际应用场景来展示 vArmor 如何助力企业提升云原生环境中的容器安全防护能力。

vArmor 的应用场景

多租户隔离

多租户应用的风险

现代 SaaS 应用程序大多采用多租户模式,严重的漏洞及相应的利用链,极有可能致使恶意用户得以访问其他租户的数据。随着大语言模型时代来临,云服务的使用量还会进一步增长。因此,构建此类服务的人员更需关注多租户隔离风险并采取防范举措,以降低跨租户攻击的风险。

下图是 Wiz 在 PEACH 框架中描绘的一个典型跨租户攻击序列 [1]:

大量案例表明,跨租户漏洞、漏洞利用链的根因主要包括:

  • 用户接口复杂度较高,接口中的无害 bugs、features 加剧风险

  • 多租户共享组件实现不当。

  • 多租户独占组件安全边界实现不当。

针对这些问题,可采取以下缓解措施:

  • 减少用户接口复杂度

  • 将共享组件转变成租户独占组件

  • 提升租户独占组件的隔离性

如何选择加固方案

Wiz 在 PEACH 框架中指出,针对多租户应用,应根据安全建模结果,综合合规、数据敏感度、成本等因素选择租户隔离技术方案。企业可以通过选择不同类型的安全边界和防御技术,将不可控风险转化为可控成本。

租户隔离用于弥补由于接口的复杂性而带来的多租户隔离安全风险。而接口复杂度则与漏洞出现概率正相关,下表描述了接口复杂度的简单评估方法 [1]。

Interface type Typical input (example) Typical process Complexity level
Arbitrary code execution environment Arbitrary Execution High
Database client SQL query Database operation High
Arbitrary file scanner Arbitrary Parsing Medium
Binary data parsing Protobuf Parsing Medium
Web crawler JavaScript Rendering Medium
Port scanner Metadata Parsing Low
Reverse proxy Arbitrary Proxy Low
Queue message upload Arbitrary Proxy Low
Data entry form String Parsing Low
Bucket file upload Arbitrary Storage Low

因此,对于复杂接口(如支持租户执行任意代码的组件),建议选择高隔离等级的安全边界来保障租户数据安全,例如基于轻量级虚拟机技术的容器。对于不复杂的租户场景和接口,如文件解析、数据解析、网页渲染、文件上传等,可考虑使用 vArmor 等技术方案进行加固。

还需要做什么

由于 runc + vArmor 的隔离等级不及硬件虚拟化容器(如 Kata Container 等轻量级虚拟机容器),因而无法防御所有容器逃逸漏洞。因此,在使用 vArmor 加固多租户应用时,需假设高级攻击者可能会利用漏洞逃逸到宿主机。我们建议您配合以下安全实践,来增加攻击者逃逸后进一步攻击的难度和成本,并及时发现攻击行为。

  • 租户负载应满足 Pod Security Standard 的 Baseline 或 Restricted 标准 [2],并使用 NetworkPolicy 等技术实施网络微隔离。

  • 制定合理的调度策略,避免不同租户负载调度到同一个节点。

  • 不同租户使用独占命名空间,以最小权限原则授予租户负载有限的 Kubernetes RBAC 和 IAM 权限,避免授予敏感权限。敏感 RBAC 权限列表可参考 Palo Alto Networks 发布的白皮书 [3]。

  • 制定合理的调度策略,将具有敏感 Kubernetes RBAC 和 IAM 权限的系统组件负载调度到专用节点池,确保租户负载所在节点不存在可被滥用的服务账号和用户账号。

  • 系统组件的敏感接口应开启身份认证和鉴权,避免未授权漏洞。

  • 引入入侵检测系统,在主机、Kubernetes 层面进行入侵检测和防御,及时发现并响应入侵行为。

核心业务加固

加固的收益

虽然业内已经推出了一些基于硬件虚拟化技术和用户态内核的强隔离方案(例如 Kata、gVisor 等),但由于其技术门槛和成本较高,使得 runc 容器仍将是大部分业务场景的主流。用户在享受 runc 容器带来的性能与便捷时,也面临着诸如容器隔离性较弱的安全问题。例如近年来 Linux 内核、runc 组件、容器运行时组件的漏洞频发,每隔一段时间就会有新的漏洞可被用于容器逃逸等攻击;许多企业在容器化应用设计、开发、部署时,也易因错误设计和配置引入逃逸风险。

Verizon 发布的研究报告 [4] 表明,企业在补丁可用后平均需 55 天才能解决 50% 的关键漏洞,而影响基础设施的漏洞修复时间可能更长。当某个高危漏洞被全量修复后,可能又有新的漏洞出现并等待修复。在漏洞修复期间,企业往往缺乏除了入侵检测以外的防御措施。

使用 vArmor 的理由

vArmor 的以下特性,使其成为加固核心业务的选择:

  • 云原生:遵循 Kubernetes Operator 设计模式,贴近云原生应用开发和运维习惯,从业务视角加固容器化应用,因此易于理解和上手。

  • 灵活性:策略支持多种运行模式(例如 AlwaysAllow、RuntimeDefault、EnhanceProtect 模式),可动态切换且无需重启工作负载。支持拦截、拦截并告警、仅告警不拦截三种特性,有助于策略调试和安全监控。

  • 开箱即用:基于字节跳动在容器安全领域的攻防实践,提供了一系列内置规则,用户可按需在策略对象中选择使用。vArmor 会根据策略对象的配置,生成和管理 Allow-by-Default 模式的 AppArmor、BPF、Seccomp Profile,降低了对专业知识的要求。

  • 易用性:提供了行为建模功能、策略顾问工具,从而辅助策略制定,进一步降低了使用门槛。

常见用法

vArmor 丰富的特性为安全策略的制定和运营提供了多样的选择,以下是一些常见的使用方式:

  • 仅告警不拦截模式(观察模式) :将沙箱策略配置为仅告警不拦截模式,通过采集告警日志来分析安全策略对目标应用的影响。

    •     spec:
            policy:
              enforcer: BPF
              mode: EnhanceProtect
              enhanceProtect:
                # AuditViolations determines whether to audit the actions that violate the mandatory access control rules. Any detected violation will be logged to /var/log/varmor/violations.log file in the host.
                # It's disabled by default.
                auditViolations: true
                # AllowViolations determines whether to allow the actions that are against the mandatory access control rules.
                # It's disabled by default.
                allowViolations: true
      
  • 拦截并告警模式:沙箱策略制定完成后,可调整为拦截并告警模式运行,并持续采集告警日志。从而实现对目标工作负载的强制访问控制,并及时发现违规行为。

    •     spec:
            policy:
              enforcer: BPF
              mode: EnhanceProtect
              enhanceProtect:
                # AuditViolations determines whether to audit the actions that violate the mandatory access control rules. Any detected violation will be logged to /var/log/varmor/violations.log file in the host.
                # It's disabled by default.
                auditViolations: true
      
  • 高危漏洞应对:当出现高危漏洞时,您可以基于漏洞类型或利用向量分析对应的缓解方案,并通过更新策略对象(添加内置规则、自定义规则),在漏洞修复前进行防御。

spec:
  policy:
    enforcer: BPF
    mode: EnhanceProtect
    enhanceProtect:
      # The custom AppArmor rules:
      appArmorRawRules:
      - rules: |
          audit deny /etc/hosts r,
          audit deny /etc/shadow r,
      - rules: "audit deny /etc/hostname r,"
        targets:
        - "/bin/bash"
      # The custom BPF LSM rules:
      bpfRawRules:
        processes:
        - pattern: "**ping"
          permissions:
          - exec
        network:
          egresses:
          - ip: fdbd:dc01:ff:307:9329:268d:3a27:2ca7
          - ipBlock: 192.168.1.1/24
            port: 80
          sockets:
          - protocols:
            - "udp"
      # The custom Seccomp rules:
      syscallRawRules:
      - names:
        - fchmodat
        action: SCMP_ACT_ERRNO
        args:
        - index: 2
          value: 0x40     # S_IXUSR
          valueTwo: 0x40
          op: SCMP_CMP_MASKED_EQ
        - index: 2
          value: 0x8      # S_IXGRP
          valueTwo: 0x8
          op: SCMP_CMP_MASKED_EQ
        - index: 2
          value: 1        # S_IXOTH
          valueTwo: 1
          op: SCMP_CMP_MASKED_EQ
  • 策略影响排查:当用户怀疑沙箱策略影响目标应用正常执行时,可将策略模式动态切换为 AlwaysAllow、RuntimeDefault 模式排查(注:已启动容器的 Seccomp Profile 不支持动态更新)。
kubectl patch vcpol $POLICY_NAME --type='json' -p='[{"op": "replace", "path": "/spec/policy/mode", "value":"AlwaysAllow"}]'
  • 行为建模模式:使用实验功能 —— 行为建模模式,对目标应用进行建模。建模完成后使用策略顾问来生成沙箱策略模版,辅助沙箱策略的制定。
spec:
  policy:
    enforcer: AppArmorSeccomp
    mode: BehaviorModeling
    modelingOptions:
      # The duration in minutes to modeling
      duration: 30

特权容器加固

特权容器的定义

特权容器通常指包含 .securityContext.privileged=true 设置的容器,此类容器被授予全部 capabilities,可访问宿主机所有设备和内核接口。本文将所有拥有打破隔离性配置的容器称为 “特权容器” ,包括但不限于 privileged container、sensitive capabilities、sensitive mounts、shared namespaces、sensitive RBAC permissions。

许多企业因历史遗留问题、系统设计需求、安全意识不足等原因,在生产环境的业务负载和系统组件中引入了 “特权容器”。然而,这些容器的风险配置容易被攻击者利用,从而导致容器逃逸、横向移动等攻击。例如在 Wiz 披露的 BrokenSesame [5] 漏洞利用链中,容器间共享 PID ns、管理容器具有特权等风险设计和错误配置,就可被攻击者利用进行横向移动和权限提升攻击。

降低特权容器的风险

我们建议企业优先以最小权限原则评估并移除导致 “特权容器” 的风险配置。并在无法移除时,使用强隔离级别的安全边界来加固容器。

vArmor 可以作为补充,在彻底消除 “特权容器” 的安全风险前提供一定的加固能力。用户可利用 vArmor 提供的内置规则自定义规则来限制潜在攻击者的行为,阻断已知的攻击手法,提升攻击成本和入侵检测几率。vArmor 内置了 “容器加固”、“攻击防护” 和 “漏洞缓解” 三类规则,并且还在不断更新。在 “容器加固” 类规则中,vArmor 专门为 “特权容器” 安全风险内置了一系列规则,可用于阻断一些已知的攻击手法。

例如,在拥有 CAP_SYS_ADMIN capability 的容器中,通过改写宿主机的 core_pattern 来逃逸容器是常见的攻击手法。如下所示,攻击者可以通过挂载新的 procfs、重新挂载 procfs、移动 procfs 挂载点等方式获取宿主机 core_pattern 文件的写权限。

# mount a new procfs
mkdir /tmp/proc
mount -t proc tmpproc /tmp/proc
echo "xxx" > /tmp/proc/sys/kernel/core_pattern


# bind mount a procfs
mount --bind /proc/sys /tmp/proc
mount -o remount,rw /tmp/proc /tmp/proc
echo "xxx" > /tmp/proc/sys/kernel/core_pattern

使用 vArmor 的内置规则disallow-mount-procfs可阻断此利用向量。

policy:
  enforcer: BPF
  mode: EnhanceProtect
  enhanceProtect:
    hardeningRules:
    - disallow-mount-procfs
    # Privileged is used to identify whether the policy is for the privileged container.
    # Default is false.
    privileged: true

辅助特权容器降权

企业生产环境中往往存在许多“特权容器”,虽然大量研究报告和案例都阐明过使用“特权容器”的危害,但企业可能仍然难以对已有的“特权容器”进行降权,也无法按照最小权限原则授予新增容器必要的 capabilities。

vArmor 提供了实验功能 —— 行为建模模式。用户可以创建此模式的安全策略,并在指定时间范围内收集和处理目标工作负载的行为。建模结束后,vArmor 会生成一个 ArmorProfileModel 对象,用来保存目标工作负载的行为模型。当行为数据较多时,行为数据会被缓存在数据卷中,用户可以通过对应接口将其导出。


spec:
  policy:
    enforcer: AppArmorSeccomp
    # Switching the mode from BehaviorModeling to others is prohibited, and vice versa.
    # You need recraete the policy to switch the mode from BehaviorModeling to DefenseInDepth.
    mode: BehaviorModeling
    modelingOptions:
      # The duration in minutes to modeling
      duration: 30

行为数据包括目标应用所需的 capability、执行的进程、读写的文件、调用的 syscall 等,用户可以利用这些信息来辅助降权。请参考使用说明进一步了解如何使用 vArmor 的行为建模功能。注:当前仅 AppArmor 和 Seccomp enforcer 支持行为建模功能。

兼容性说明

vArmor 依托 Linux 系统的安全机制(AppArmor LSM、BPF LSM、Seccomp)来实现容器加固,其中

  • AppArmor enforcer 需系统启用 AppArmor LSM
  • BPF enforcer 需 Linux 5.10+ 内核版本支持

目前 vArmor 兼容 Kubernetes v1.19+ 版本,支持包括 AWS EKS、Azure AKS、Google GKE、火山引擎 VKE、阿里云 ACK 等主流云厂商的托管 Kubernetes 服务。其轻量化设计具备四大优势:

  1. 原生级性能损耗:依托内核安全子系统,不显著增加上下文切换和数据拷贝开销

  2. 无需额外硬件:纯软件实现

  3. 无环境绑定:不依赖特定操作系统发行版

  4. 零侵入性:保持集群及容器运行时组件的默认配置

总结

vArmor 针对当前容器安全领域在安全策略编写与管理方面的难题,提供了有效的解决方案。在多租户隔离场景下,尽管无法达到硬件虚拟化容器的隔离级别,但通过配合一系列安全实践,可降低跨租户攻击风险;在核心业务加固方面,vArmor 凭借云原生、灵活、开箱即用和易用等特性,为企业在享受 runc 容器性能与便捷的同时,提供了有效的安全防护手段;对于特权容器,vArmor 既能通过内置和自定义规则加固,阻断常见攻击手法,又能利用行为建模功能辅助降权。

vArmor 以其丰富的特性和灵活的应用方式,为容器安全提供了全面且实用的保障,助力企业在云原生环境中平衡安全与业务发展的需求。真诚欢迎社区开发者和企业加入社区,与我们一起参与项目共建。

引用

  1. PEACH: A Tenant Isolation Framework for Cloud Applications
  2. Kubernetes Privilege Escalation: Excessive Permissions in Popular Platforms
  3. Pod Security Standards
  4. 2024 Data Breach Investigations Report
  5. #BrokenSesame: Accidental ‘write’ permissions to private registry allowed potential RCE to Alibaba Cloud Database Services

285 学科全覆盖!豆包大模型团队开源基准测试集 SuperGPQA

近日,豆包大模型团队开源 SuperGPQA,一个领域全面且具备高区分度的知识推理基准测试。

该数据集构建了覆盖 285 个研究生级学科、包含 26529 道专业问题的评估体系,不仅涵盖主流学科,更将轻工业、农业、服务科学等长尾学科纳入其中,展现出全面学科的覆盖广度,填补了长尾知识评估领域的空白。

如今,SuperGPQA 已被用于揭示开源模型与闭源方案之间的显著性能差距,为 AI 发展提供了关键评估工具和跨学科分析框架。

随着大语言模型在通用学科中的表现逐渐接近人类水平,研究焦点也随之转向其在真实世界专业领域的应用。然而涉及人类研究领域的长尾学科时,由于有效评估的缺乏,LLM 的能力边界依然模糊不清。

为了全面衡量 LLM 的泛化能力与推理上限,字节跳动豆包大模型团队联合 M-A-P 开源社区推出基准测试 SuperGPQA,这一基准不仅覆盖了二百余个研究生级学科,还确保 42.33% 的题目需要数学计算或形式推理,构建了兼具广泛学科覆盖与复杂问题设计的评估新范式。

实验结果显示,DeepSeek-R1 在 SuperGPQA 上的准确率为 61.82%,在不同知识领域中,当前大语言模型性能仍有很大提升空间,这也进一步凸显 SuperGPQA 在评估模型真实能力方面的重要性和必要性。

⽬前论⽂成果和数据代码仓库均已对外公开,欢迎开源使用!

SuperGPQA: Scaling LLM Evaluation across 285 Graduate Disciplines

论文链接: arxiv.org/pdf/2502.14…

数据链接: huggingface.co/datasets/m-…

代码链接: github.com/SuperGPQA/S…

1. 现有评测基准学科占比失衡,长尾学科覆盖不足 5%

现有大语言模型评估体系主要面临两大核心困境:学科覆盖的严重失衡与评测基准的挑战性失效。

以 MMLU 和 GPQA 为代表的传统基准尽管在数学、物理等主流学科中建立了标准化测试框架,但其覆盖的学科数量通常不足 50 个,仅占人类知识体系的冰山一角。据统计,现有基准对轻工业、农业、服务科学等长尾学科的覆盖率甚至不足 5%。

多基准多维度对比雷达图

不同基准下最新模型的性能对比

更为严峻的是,现有评测体系失去区分度,无法有效衡量模型在真实复杂场景中的推理上限。比如,主流模型如 GPT-4o、DeepSeek-R1 在传统基准上准确率已突破 90%。

这主要源于传统基准构建范式的单一化数据来源与粗放化质量筛选。比如,不加辨别地依赖教科书例题或在线题库(例如 GPQA 中 42% 的问题来自维基百科),导致题目缺乏专业深度,且易被模型通过记忆机制 “破解”。实验发现,GPT-4o 对在线练习网站答案的重复率高达 67.3%,暗示其性能提升可能源于题目数据泄露而非真实推理能力。

此外,众包标注的专业水平参差和主观性问题评估难度进一步加剧了基准的不可靠性——早期尝试中,仅 37% 的众包标注问题通过专家审核,导致超过 60% 的标注资源浪费。

这使得我们无法准确评估模型的泛化能力和推理能力,严重阻碍了模型性能的进一步提升。

2. 首次全覆盖 285 个学科,探索 LLMs 真实能力边界

为突破以上限制,豆包大模型团队和 M-A-P 历时半年推出 SuperGPQA,一项全面的基准测试,实现 285 个研究生级学科全覆盖,旨在探索最先进的大语言模型潜力边界。

  • 全面学科覆盖 SuperGPQA 覆盖 13 个门类、72 个一级学科和 285 个二级学科,共 26,529 个问题,远超现有的 GPQA(448 题)和 MMLU-Pro(12,032 题),平均每题将会提供 9.67 个选项,挑战性显著高于传统的 4 选项格式。同时,它突破传统评测集仅侧重 STEM 学科的局限,兼顾科学、工程、医学等 STEM 学科与哲学、文学、历史等非 STEM 学科问题,且具有较高区分度。

  • 多样的难度分布: 问题难度在各学科间均衡分布,尤其在工程和科学领域,难题比例较高。42.33% 的问题需要数学计算或严谨推理,确保模型在高难度任务中的表现。

  • 丰富的语义结构: 通过 t-SNE 可视化,评测集 SuperGPQA 展示了跨学科的聚类模式,工程和科学类问题在语义上高度相似,人文学科则保持独特的知识中心,体现了领域特定的语言特色。

  • 一致的题目设计: 平均问题长度为 58.42 字,选项长度一致,增强了迷惑性和挑战性,确保评测的公平性与可靠性。

3. 专家 - LLM 协同,提高题库质量

SuperGPQA 的核心架构由三个关键阶段组成:源筛选、转录和质量检验。该过程涉及 80 多名专家标注员、交互式专家 - LLM 协作系统,为未来类似规模的研究项目提供了方法指导。

SuperGPQA 数据收集处理流程

  • 源筛选

为确保题目的高标准质量,团队摒弃了众包注释员收集资源的方式,转而由专家注释员负责从可信来源(如教科书和权威练习网站)筛选和收集原始问题,并要求提供来源截图。这一策略避免了早期大量无效问题的产生,提升了质量检查的效率和准确性。

  • 转录

在转录阶段,专家注释员对收集的原始问题进行语言规范化和格式转换,确保所有问题具备统一的学术语言和标准的多项选择题格式。团队发现,即使是最先进的语言模型(LLMs)在生成干扰项时也存在漏洞,因此需要专家统一重写,以提高干扰项的准确性和有效性,确保题目的挑战性和区分度。

  • 质量检验

团队在质量检验阶段采用三层检查机制,以保证数据集的整体质量:

1)基于规则的初步过滤: 识别并过滤格式明显不合规范的题目。

2)基于 LLM 的质量检查: 利用多个先进的 LLMs,如 GPT-4、Gemini-flash 等,进行有效性、负面和极端询问检测、多模态排除、领域相关性评估和区分度标记。通过多模型协作,不仅提升效率,还降低数据泄漏风险。

3)专家复审: 由专家注释员对可疑题目进行二次审核,确保最终题库的高可靠性和高区分度。

4. 最优推理模型仍有进步空间

发布评测基准的同时,研究团队也基于 SuperGPQA 对全球 6 个推理模型、28 个聊天模型和 17 个基础模型进行了评测,涵盖闭源、开源和完全开源三类模型。

其中,推理模型和聊天模型采用零样本评估,基础模型采用五样本评估(方法与 MMLU-Pro 类似),并将温度参数设置为 0,推理模型最大生成 token 数为 32K,其他模型为 4K。

我们的实验结果表明,在不同的知识领域中,当前最先进的大语言模型性能仍有很大提升空间,如当前最优模型 DeepSeek-R1 在 SuperGPQA 上的准确率仅为 61.82%。具体评测结果如下图所示:

LLMs 在不同划分层级的表现

LLMs 在不同学科的表现

  • 指令微调显著提升性****能

DeepSeek-V3 和 Qwen2.5-72B-Instruct 的得分(47.40 和 40.75),远超其基础版本得分(32.14 和 34.33),验证了指令微调的有效性。

  • 大模型表现更均衡

DeepSeek-R1 在简单(63.59)、中等(63.63)和困难(56.87)题目上均表现优异。相比之下,Qwen2.5-14B-Instruct 在同类别题目上的表现差距较大(44.82、37.90、19.97)。

  • 推理模型训练范式仍有待优化

DeepSeek-R1 与 DeepSeek-R1-Zero 性能差距不大,尤其在科学与工程领域,后者稍占优势,表明最佳训练方法尚未确定。

  • 预训练语料库的持续优化

LLM 系列如 Qwen-max、GPT-4o 模型系列在 SuperGPQA 上的表现随着时间显著提升,显示开发者高度重视长期知识的融入。

  • 开源模型面临挑战

尽管透明 LLM 如 MAP-Neo-7B 和 OLMo-2-1124-13B 表现尚可,但与业界的非透明开源和闭源模型相比,尤其在困难题上仍显不足。

  • 不同能力的模型表现差异

其中,Doubao-1.5-pro 以 55.09% 的准确率在 Chat Models 中位列第一,我们发现,通用大语言模型(如 Doubao 系列)在常见专业问题的知识回忆方面表现不错,但在长尾领域的推理方面存在困难。

o3-mini 系列在简单和中等难度题目的分数低于 Doubao-1.5-pro ,但在困难问题上却明显超过它,说明推理模型在难题上表现突出,却在广度知识覆盖方面存在不足。

5. 历时半年,探索模型真实能力边界

SuperGPQA 评测集搭建历时半年,近百位学界学者及硕博同学、业界工程师参与标注。通过 LLM - 专家协作的构建流程、285 学科全面覆盖和多样难度分布设计,SuperGPQA 填补了长尾领域专业评估的空白,有望成为衡量 LLM 泛化能力与推理上限的关键工具。

其实验结果不仅揭示了当前模型能力与通用人工智能之间仍存在巨大差距,也为 AGI 发展提供了跨学科分析框架。未来我们也将进一步扩展数据集范围、改进人类与模型协作标注模式,以应对快速演进的人工智能技术挑战。

AI 与星辰大海:2025,从新手到开挂勇士的奇幻旅程

作者:Data-TnS-Engineering-FE 团队

前言

曾几何时,代码敲击声回荡在深夜的办公室,你是否也曾幻想过有一个全能助手替你分担工作?如今,这个美好的愿景不再是空中楼阁。

想象一下,当你正为产品设计苦思冥想时,突然耳边传来 AI 的灵感火花;

开发过程中,AI 像是个比你还了解自己的最佳拍档,为你提供独到的建议;

当繁琐的测试工作如排山倒海而来,它早已帮你先行解决那些隐秘的 Bug;

交付环节则如同一个老练的质检专家,在每一个细节上都帮你擦亮眼睛;

而在运维阶段,它更是你的“夜间守卫者”,早早预警潜在问题;

有人说,AI 的到来让开发者的身份发生了质的飞跃,从“头发稀疏的代码独行侠”变成了“开挂勇士”。在 AI 的协助下,谁不想在产品设计上满怀创意、在代码编写上行云流水、在测试中无懈可击,在运维中高枕无忧呢?

在这个充满奇思妙想的科技时代,AI 既是你的忠实伙伴,又是你的全能助手,更是一位风趣的导师。它不知疲倦地助你不断攀登职业高峰,让每一步开发都像是一场精彩绝伦的探险。是时候跳出你的舒适区,体验 AI 如何点亮开发者们的星辰大海。

准备好了吗?接下来,我们将揭开这段 AI 助力开发的奇妙旅程,从产品设计到运维管理,为你重新定义“效率”、“质量”和“体验”。 每一刻都充满了独特的惊喜和乐趣,相信这将是一段你不想错过的神奇之旅。让我们一起踏上这段充满无限可能的旅程,重新发现开发的独特魅力与无穷乐趣。

在 AI 的魔法世界里,开发者不再是孤军奋战的英雄”

助力业务发展

随着 LLM 能力的迭代和更新,越来越多过去 LLM 无法很好解决的问题重新进入了大家的视野。在人审领域下人们一般都会围绕着 LLM 是否可以完全替代审核员对内容进行审核进行讨论与探索。在进一步进行详细的 LLM 赋能人工审核流程相关的例子前我们需要先了解一下“人类审核员”面对的一些挑战和要求:

  • 能处理复杂的内容(e.g.可能既可以是 A,也可以是 B,还可以是 C)并给出最合适的答案
  • 质量 & 稳定性:对于相同类型的内容要给出相同的结果
  • 效率:在不损伤质量的前提下需要达成一些数量上的要求

先说结论目前 LLM 的能力还无法完全替代审核员,更多的是在各方面提供辅助从而提升审核员的审核质量和效率。所以在产品结合的思路上,我们主要关注在如何利用 LLM 简化或加速审核员对于单一任务的操作并完成了一些功能的落地。

前置思考

说到 LLM 最出名的当属于 ChatGPT 了,如 GPT-4o

当我们思考 LLM 能为审核员带来什么样的辅助时我们首先想到的就是如何利用这些现有的天花板模型。这些模型一般都有良好的指令执行能力,并且在处理一些通用文字相关的问题时一般具有非常好的表现。

例如我们可以提供一段文字内容给模型,并告诉模型我们希望它帮助我们从中提取出不符合某些规则的内容如:潜在的语言攻击,歧视内容,潜在色情内容等等。

当有了模型识别出来的内容后可以通过一些特殊的形式展示这些信息如高亮等。 这可以帮助审核员在审核过程中快速捕捉到内容中存在的潜在风险并加速其对当前内容的审核。听起来是不是很简单?实际上将一个如此简单的辅助能力从离线验证到最终上线需要考虑的远远不止这些:

  • 成本:模型是以 token 来进行计算的。一段文字会先被转换成 token 然后再传给模型,同时模型输出的也是一堆 token 然后会再被转换成我们看得懂的语言。所以如何无损高效的压缩传给模型的内容是一个非常重要的课题。(如删除重复的内容等)
  • 合规:因为模型在迭代过程中需要大量的训练数据,厂商都会收集模型在实际使用过程中的数据以补充其训练数据集。如果将一些敏感或隐私数据不经过处理的直接传给模型可能会带来合规风险。(如公司的保密数据,或者用户的不公开信息等)我们可不希望当其他人问模型你的银行卡密码时模型能准确无误的回答上来hhh。
  • 时延:模型能力强大是有取舍的。可以姑且先理解为(在计算资源不变的前提下)模型的规模越大->模型的能力越强 -> 每次回答你问题的速度就越慢。同时给模型输入越多时返回的时长也会相应的延长。
  • 其他 n+ 问题:服务可用性,模型拒绝回答,模型选型等等...

但是由于模型能力的局限性,我们只能对标准文字内容审核提供符合标准的辅助能力。那对于像视频或者音频中出现的文字,或者话语或者一些复杂的问题怎么办?

例如我们想对于一个歌曲中的歌词进行风险识别。

这个时候我们就要引入分步的解决思路,歌曲中歌词的风险识别可以被拆分成如下工作流

暂时无法在飞书文档外展示此内容

举例来说,在预处理环节我们可能会需要对 ASR 转换的文字内容进行整理如加入标点符号,分句断句等。模型对于长文字内容的处理能力会随着内容变长而下降。我们需要根据业务的诉求和实际情况进行灵活调整。抛去 ASR 环节不说,后边三步可以有两种实现思路:

  1. 在一个 Prompt 中通过分步的方式指导模型进行处理

    1. 会更好的保留整体的上下文,但是可能会碰到如内容太长超出 context window size 的情况并且随着内容的变长模型的完成时间也会变慢。
  1. 将每一步拆分开然后通过串行调用的方式完成多次模型调用。

    1. 可能会丢失一些上下文内容,但是因为进行拆分后每一部分的长度都是相近的所以在模型响应时长和 context window 上限的问题上则有比较好的表现。

如果我们想让模型帮我们翻译火星文呢(没办法,业务上就是有这个诉求 hhh)

对于一些有高定制化诉求的场景普遍厂商也会开放对现有模型就行二次训练的能力。比如对于将内容从 A 语言翻译到B 语言并且对语言风格/用词有明确需求时,可以考虑对基本模型做一次 SFT(Supervised Fine Tuning) 。也就是对自身特殊诉求收集数据集并使用这个数据集对模型进行二次训练以达到更好的表现。

如果你说识别什么的还是太复杂了,有没有更简单的应用场景?

在实际产品开发过程中,我们经常性的需要对圈定/给定的一组数据进行频繁的离线验证以确保目前的能力表现是符合我们预期的。又或者在产品迭代的过程中我们时常需要一个指标/分数来量化当前的能力/体验。举例来说,在进行多目标语言混合翻译的能力开发过程中,我们在 prompt enginnering 过程中需要时刻关注模型的表现并确保每一次修改都不会对能力造成较大的退步。Multidimensional Quality Metrics (MQM) 是一个多维度指标翻译质量分析框架。我们可以通过自然语言的方式向模型输入我们的对于(译文与原文对比)不同维度上的要求如:

  • 翻译准确性

    • 是否丢失一些信息
    • 是否凭空捏造了一些信息
    • 翻译错误/未准确翻译
    • 未翻译
    • ...
  • 流畅度

    • 语法是否正确
    • 标点是否正确
    • 拼写
    • ...
  • 风格

    • 是否用了一些抽象的词

    • 是否是正式文风

    • ...

得益于 LLM 优秀的自然语言处理能力和跨语言能力,LLM 可以基于我们输入的多个维度来对译文进行评估并最终给出评估结果。需要注意的是为了保证模型输出结果的准确性我们一般不会直接要求模型输出如:0-10 分的打分。我们会尽量让模型在一个给定的状态下进行枚举。如:严重程度(严重, 普通, 可忽略不计)并通过代码对枚举进行映射后计算出最终的得分。这样可以最大程度上避免 LLM 输出不稳定和幻觉等问题。

实际上 MQM 这种多维度评估体系也可以应用在翻译之外的领域,如润色、故事生成等。甚至可以被用在图片打分。他们背后的原理都是类似的,都是通过发挥 LLM 出色的自然语言理解能力 + 通过自然语言描述框架来实现一些复杂的打分工作。

功能落地

上边我们只是利用了 LLM 的自然语言处理能力。如果我们想让模型帮我们回答一些不是通识性知识点的问题时该怎么办呢?

暂时无法在飞书文档外展示此内容

模型也可以像人一样,碰到不会的东西时可以先去查询然后再基于查询结果进行判断和回答

比如说我们想为审核员提供一个问答机器人回答一些审核领域相关的问题,或者基于以往的审核结果进行回答。

又或者我们想基于某一个数据库中的结果对审核员的问题进行回答

但是对于不同类型的内容,知识库在生产过程中采用的分割策略,结构等会大大影响最终问答质量的表现。如:

  • Chunking: 如当对于大段内容进行拆分时要拆分的多细,拆分后的信息是否应该保留整体上下文方便后续参考时使用等。
  • Embedding Model: 需要针对需要支持的语言来选择向量化模型,不同的模型在召回准确度上也有不小的区别。

当我们检索到的相关知识后可以通过将这些信息连同原始问题一并作为输入给模型并让模型基于相关知识点尝试回答。

当我们积累了一系列能力后,如果快速的向其他方向上推广和扩展?

随着越来越多基于 LLM 能力的需求落地,我们发现其实所有的 LLM 能力都可以被总结为:一个带顺序的分步流程。其中模型调用只是工作流中的一个步骤/节点。如果我们能快速的复用这个分步流程就可以快速的对取得成功的能力进行推广。

(分步流程可视化示意图)

随着我们对现有的一些优秀 LLM 编排能力库/平台的深入了解,我们发现现有的方案都无法很好的对我们的业务场景提供100%的支持。甚至大多数都无法通过合规这一环节。更不用说可能业务有自己的模型、数据库、基础能力等等。我们需要在业务下实现一套定制的流程引擎

流程引擎本质上可以被拆解为一下两种图的类型:

  • DAG 有向无环图。一般用作承载经典 Workflow 场景。需要注意的是这种图不能包含循环所以一般被用作实现单一 Agent 能力。
  • FSM 有限状态机。用作实现如 Multi-Agent 场景或有环的场景。是 DAG 的一个补充。但是需要注意的是因为状态机同时之后有一个激活状态所以并发分支等能力无法通过 FSM 实现。

当我们实现了上述两种流程引擎后可以进行组合实现更复杂的能力如:FSM 中嵌套 DAG,DAG 中嵌套 FSM 等等。

当我们有了上述流程引擎和对应的 DSL 之后。我们就可以在业务间快速复用能力(只要复制一下 DSL 或基于现有的 DSL 做二次开发就好了)。

可能你会问,为什么不根据需求直接把这些逻辑写在代码里呢?实际上在日常开发过程中我们发现大部分功能都是通过对有限能力的组合来实现的。如果不做流程引擎的建设会带来很大效率上的降低以及多余的开发量。

其他实践

Hornbill 是内部用于多个平台的 Oncall 工单管理工具,拥有三种升级策略。我们处理工单的团队包括用户运营团队和研发工程师,并在创建工单时自动拉入相关人员进行协作。由于审核员需要快速处理大量审核任务,Hornbill 通过 24 小时的用户运营团队快速解决问题,技术问题会被升级至研发解决。

Hornbill SDK 可整合到平台中,帮助用户在提交工单前通过 FAQ 寻找解决方案,从而减少不必要的工单。每周约有 几百个由用户运营团队处理的工单,因此减少工单数量可以让团队将时间用于更高价值的任务。

为了提高工单解决的效率并减少工单数量,我们引入了 AI 功能。

首先,相似工单检测能够有效识别并链接具有相同问题的工单,通过创建问题描述的嵌入向量,并使用向量相似性计算如余弦相似度,系统在用户提交新工单时可提示已有相似工单,推荐用户加入现有工单而非新建,或在工单提交后提醒用户操作团队进行链接,从而减少重复工单处理的工作量。

其次,AI 摘要生成功能则通过自动生成问题的总结,在群聊中梳理出问题描述、原因和结论,提供给不同班次的用户操作团队以保持问题处理的一致性。这一功能通过将所有的群聊内容传递给大型语言模型生成总结,从而避免因班次交接导致的上下文丢失,提高工单解决的连续性和效率。这些先进的 AI 功能减少了用户操作团队在票务处理上的重复性工作,让他们能够将更多时间投入到更具价值的任务中,不仅提升了团队的工作效率,也提高了用户问题解决的速度和质量,为用户提供了更优质的体验。

另外,Hornbill SDK FAQ 搜索功能旨在解决用户自行反馈且可通过非技术知识解决问题的工单。 通过将常见问题生成 FAQ,用户可以搜索相关知识库,并根据输入查询定制 FAQ 内容,使其更易理解。方法是为常见的可自解决问题创建 FAQ,并对 FAQ 问题进行向量嵌入,将其存储在向量数据库中。当用户输入问题时,我们利用余弦相似度比对向量嵌入,以找到匹配的 FAQ,并通过大型语言模型总结 FAQ 内容,展示给用户。这一功能减少了不必要的工单,提升了用户的自助解决效率。

总结

当我们完成了对流程引擎的落地,同时在流程中有技巧性的使用 LLM。基本上领域下的产品/业务诉求都可以基于这一套框架来实现。LLM 除了可以为我们做分类,识别等功能以外也可以帮助我们做离线验证/数据评估等工作。 与可视化编排界面配合可以大幅降低使用门槛。对于一个想要使用 LLM 能力来实现业务诉求的业务方,不论是 PM 同学还是运营同学都可以进行尝试可以大大解决研发的人力缺口。

提升开发体验

AI 编程在今年有了比较大的发展,因为出现了 Cursor、Windsurf、v0、bolt.new 这些,在不同场景下,成为了能指数级提升生产力的工具。这不仅仅得益于越来越强的模型能力,也得益于许多在应用/交互上的创新与探索。

工具形态

传统工具的替代品

搜索、文档工具、设计稿代码生成工具等

多智能体自然语言编程

代表工具:gpt-pilot、gpt-engineer、MetaGPT

这类工具直接通过自然语言与一个多智能体系统进行交互,多智能体系统会在内部划分多个角色/任务,如:程序员/开发/调试、架构师/设计、产品经理/拆解、项目经理/任务管理等。

但这类工具要将整个工程从新建到功能推进完全交给智能体维护,用户只能通过指令和自然语言对话对工程进行控制,并且受限于上下文窗口,也无法构建复杂的平台系统。使得这些工具都没有办法用于程序员日常的生产和实际的项目里。只能作为非专业人士的玩具,或者 AI 研究的尝试。

blog.pythagora.ai/2023/09/04/…

github.com/geekan/Meta…

代码辅助工具

代表工具:Github Copilot、Continue.dev、MarsCode

以 continue.dev 为例,这类工具的核心功能大概就是上述几类:以代码块/文件为上下文 Chat、Tab 自动补全、选中代码块进行自然语言编辑、在聊天框中对代码块进行应用。

这些功能已经在开发中被普遍使用了,开源的模型、插件对这些功能的支持也非常成熟了,不同公司内部也有类似的解决方案。

好用,但还不够强大,伴随着这些功能的组合和深度优化,期待更具生产力的工具逐步被开发出来并具备商业价值。

🆕 复合型编程IDE/插件

代表工具:Cursor、Windsurf、Cline

暂时无法在飞书文档外展示此内容

Windsurf 的 Cascade 工具,与 Cursor 的 Composer 类似

暂时无法在飞书文档外展示此内容

在代码辅助工具的基础上进一步增强代码整合能力,包括:

  • 能够有效汇总上下文
  • 能够将代码块转化为文件变更,以支持同时编辑多个文件
  • 能够管理AI批量编辑后文件的暂存状态,并灵活跳转到不同历史编辑版本

这些能力大大提高了使用体验和生产效率。

Cursor 在刚出来的时候也收获了大量非程序员以及博主们的力捧,但我们在日常使用中依然不多。主要还是因为无法直接用于我们日常工作的工程,但随着一些使用方法的探索,以及模型能力/上下文检索能力的进一步提升,越来越多的编程人员开始在日常工作中使用,以提升生产效率。

而下面的最佳实践也提供一种日常使用的工作流,该工作流基于文档,构建长期 AI 可维护的大型项目。

🆕 快速原型构建机

代表工具:v0、Bolt.New

其实和上面的 IDE 差不多,但更多的结合了 WebIDE 和 WebContainer 的优势。

常规的使用场景是:对于完全没有技术背景的角色来说,他们不知道如何为自己的需求创建一个工程并完成前期的原型验证工作,而这类 WebIDE 工具则提供了一个完美的平台让他们来得到一个可供开箱即用的工程原型。

在此之后,则可以将这个工程下载到本地,使用 Cursor 继续进行工程的后续维护和开发。

最佳实践

虽然新的工具将功能可用性提升了,但存在短板,需要结合一些使用的条件和方法:

  • 需要应用一整套闭环的工作流。其重点在各种文档的记录上(项目记忆力),让人和文档交互,完善设计,才能最终让 LLM 能更好的结合上下文生产出可靠的代码。
  • 不是所有场景都可以。逻辑性代码完全没问题,但无法还原视觉设计的细节。因此,在工作流中,通过 AI 完成逻辑代码的部分后,样式工作还得需要前端工程师来编写。
  • 技术栈和工程结构有限制。需要选择用比较老,且主流的技术栈和版本,社区训练物料比较多。
  • 合规和数据安全问题

工作流

暂时无法在飞书文档外展示此内容

这个工作流以文档为核心,适时的对项目和变更进行总结,以便在新增需求的时候,工具可以获得足够且精简的上下文,来精确生成新需求所需要的设计和代码。这些文档包括:

  1. 整个项目的描述,包括:项目简介、技术栈、文档结构、项目架构等全局信息
  2. Feature 文档,以及每个 feature 的技术实现细节
  3. 模块文档,在每个模块下,对于该模块代码的总结和索引

本质上讲,现阶段,如果我们将 AI Coding 工具拟人化,它有很多缺点:记忆力差,没有从代码仓库中持续积累特定于此仓库的经验,相当于每次找了一个新人来开发

所以,一个好的工具、开发者,应该是能够合理组织和提供一个指令足够的上下文的;一个完全适配的项目应该是,简单化模块化的原子能力 + 清晰的模块声明。类似于微前端、微服务这种组织形式可能更有利于文档的组织

未来展望

工作模式

这有一篇来自于红杉资本的文章中文版)描述了他们对生成式AI发展的展望:

虽然我对这个发展路径以及时间节点存疑,但也同样让我在想,结合AI,未来的编程是怎么样的呢?什么样的目的、什么样的实现方式、什么样的产品形态呢?往大了想,这些都太难回答了

但在一些小点上,站在程序员的角度,还是有一些想象的:

  • 近几年,就可以看见程序员的能力模型要求会有一些变化,更有 AI 辅助经验的,在生产力上会比纯手写更有优势

    • 就像在应用层,高级语言编程替代汇编语言(wiki: 编程语言世代
  • 语言本身的学习变得简单,当 JS 开发工程师想使用其他语言时,变得没什么门槛

  • 没有合规模型工具的公司,在生产力上会落后,人也一样

  • 会出现便宜的 AI 辅助编程解决方案,例如:

    • 针对特定技术栈的小参数 LLM ,本地 32G 内存的笔记本,就可以足够提升生产力了

更远的未来,如果产品形态和生产方式发生质的改变:

  • 那人可能可以专注于新的领域的扩展,而不用纠结于现有生产工具的熟练度
  • 模型越来越强大,实现这件事,真的可能会变成**「念咒语」**

工具链

现在的技术栈、工程化等方式还是基于人来建设的,但当 AI 编程占领主导之后,会有什么样的工具链来驱动呢?什么样的工具链对于 AI 编程更友好。

例如上面提到的最佳实践的工作流来说,整体围绕文档驱动,在生成文档的时候,我们也需要将项目代码转换成上下文提供给大语言模型,而 github.com/yamadashy/r… 就是一种可以将仓库代码打包成 LLM 上下文的工具。

这块还处于特别早期,因为具体的工作流还没有固化下来,但可以预见,会有越来越多的工具链产生。

推动测试进程

为什么做单元测试

LLM 生成单元测试代码,在 22 年底就已经取得了非常惊艳的效果:用例工整,分支覆盖详尽,mock 数据齐全。生成的用例如果可用,提交到仓库后会成为代码资产的一部分。如果用例有问题,这部分代码也不会直接影响到生产环境。所以,AI 单元测试是大语言模型第一次尝试落地工业生产环境的完美试验场景。

另外,单元测试本身就是研发环节中非常重要的一部分。全面的单元测试可以辅助发现很多变更引起的风险和问题。业界知名的开源软件必定包含大量自动化运行的单元测试,这在多人协作开发过程中至关重要。

我们在 23 年也尝试过使用 AI 生成单测。但是当时代码报错多、人工修复成本大,初步尝试的结果不尽如人意,于是暂且搁置了。24 年中,在业务痛点的驱使下,我们重启了 AI 单测的调研。这一次我们找到了新的角度,解决了报错多和人工修复成本大的问题,让大家看到了 AI 单测落地的可能性。

AI 单元测试效果如何

衡量标准

在讨论效果如何之前,首先要讨论如何衡量效果。怎样算效果好,怎样算不好?

在 23 年的调研中,我们没有具体的评判标准,只通过看到的结果得出一些主观判断。在 24 年的实践中,我们尝试以客观的评判标准为主,主观的感受为辅。

由于评判标准是为了服务于我们的目标,所以我们先花了点时间思考我们到底希望 AI 在单测这件事上做什么?我们希望通过引入 LLM,对编写单测提效,同时通过大量单测代码的引入,提高 bug 召回率,提升代码质量,减少线上bug。

基于这个目标,最终我们梳理出这样的指标体系:

  • 核心指标:bug 召回率

  • 准入指标:单测可执行率,单测覆盖率

  • 过程指标

    • 千行代码用例数、测试用例独立性
    • 单测执行时长
    • 项目渗透率、单测可维护性、研发接受度
    • 单测生成速度

实际效果

所以实际效果如何呢?

横向观测下,我们对比生成 case 数、case 可执行率以及单测覆盖率。大部分模型都基本满足了准入要求,小部分模型可能由于发布时间较早或提示词适配度低等工程原因,没有取得可用的表现。

在解决了一些难点问题后,我们在试点仓库做了推进。对于千行以下源码的小批量生成,单测可用率保持在 100%,覆盖率保持在 80% 左右。随着源码复杂度增加,可用率和覆盖率均略有下降,但整体表现已经进入了值得期待的状态。

除了客观数据,研发接受度(主观感受)也很重要。通过阅读我们看到,纯逻辑类函数 AI 可以做到考虑各种情形、针对特定情形 mock 数据并给出断言;React 组件 AI 可以做到考虑多种情况,并且试图在渲染的 dom 结构中寻找关键元素做断言,配合人工矫正生成部分快照可以低成本、高覆盖率完成单测编写;同时AI在边界条件测试上比人类更加严谨,也会通过函数名、注释等信息发现人类考虑不周之处。

从效率和 bug 召回的角度,我们都看到了希望,因此,我们开始在部门内推进 AI 单元测试。

为何会取得不错的效果

首先,我们标准化了基础设施,解决了一些基建问题。如果一类 case 有 3 种写法,那么我们选择其中一种我们最希望的写法让 AI 固定下来,同时帮 AI 打通任何调用 API 上的难题。

其次,人类程序员要懂单元测试。比如 Arrange-Act-Assert 单测组织方式,如何 mock 数据,如何模拟交互,如何合理断言等等。如果人本身不擅长做这件事,也就没办法更好地评判 AI 做的好还是不好。这和使用 LLM 学习其他领域、进行创意启发等场景是不同的,人类必须是相比 AI 更专业的角色。

最后,靠业界优秀论文和公司内团队支撑。从 23 年到 24 年,在 AI 单元测试领域出现了不少靠谱的论文,比如 Meta 的 arxiv.org/pdf/2402.09… 团队也是在调研之后,和公司内 Codeverse 团队一起,在我们的业务上落地了 AI 单元测试能力。

AIGC 落地的最后一公里

AI 单测真正的落地,还需要业务仓库研发同学的协助,不只是基础配置和存量单测代码的合入,还有对后续日常研发流程的改变。

接入AI单测能力,我们提供了3个模块:基础依赖包、流水线配置、ut-helper 本地工具。由于模型在纯函数上表现更好,在组件上略有欠缺,所以我们的推进策略是优先覆盖 P0 仓库的所有工具函数和通用组件,对于业务属性较强的代码暂不推进。 通过收集流水线上报的单测执行数据,我们建立了数据看板,展示仓库接入率、全量覆盖率、增量覆盖率和单测执行失败明细。

对于研发日常的影响,大家问的最多的问题是:生成了 case 之后还需要人类 review 吗?AI 有帮助人类纠错的能力吗?我们希望是不需要人工介入且能帮助发现潜在风险,而且也看到了一些希望。随着存量代码的覆盖完成,增量代码的覆盖更是对人类和 AI 的协作的考验。 AI 是否真的能在研发需求迭代过程中帮助研发规避潜在风险,部门的 bug 估分比是否能切实下降,这都是我们拭目以待的事情。

总结

在今年的实践中,我们对于 AI 落地这件事又有了更多的认识。它不是人类驱使一个远远强大于自己的怪物,落地工业生产场景仍然是靠人类往前迈一步,指挥 AI 在指定范围内做事。另外,AI 在真实落地中的挑战远不止模型训练,AI 和 AI、AI 和基建之间,有很多工程化的工作,它们可以在很大程度上改变最终的效果,有时会比实验室的大模型 pk 榜有趣得多。

保障交付质量

交付质量(Delivery Quality)是指在软件开发过程中,最终交付给客户或用户的软件产品所具备的质量水平。它涵盖了多个方面,包括功能性、可靠性、性能、安全性、易用性、可维护性等。交付质量的高低直接影响到用户满意度、产品市场竞争力以及企业的声誉。

交付质量对于软件开发非常关键,交付质量的劣化会带来用户满意度下降、维护成本增加、品牌声誉下降等问题,甚至会缩短产品的生存周期。交付质量的保障覆盖软件开发过程的所有环节,包括需求设计、开发、测试、发布上线、运维阶段,本文将从各个软件开发环节展开聊一下质量保障的手段和能力。

质量保障传统手段

完整的质量保障策略,需要各个阶段的努力,常见的质量保障框架包括基础的规范、工具、质量防控手段和质量度量:

暂时无法在飞书文档外展示此内容

保障交付质量是一个比较大的话题,交付质量问题可能出现在软件的开发生命周期任意一个环节,需求设计环节中的需求逻辑问题、开发阶段的代码质量问题和测试阶段的测试漏放问题最终都会导致交付质量的下降,常见的质量保障手段和理论基础:

  • 持续集成/持续交付(CI/CD) :持续集成(Continuous Integration, CI)和持续交付(Continuous Delivery, CD)是一种自动化软件交付流程的方法,通过频繁地集成代码、自动化测试和部署,确保软件始终处于可发布状态。

  • 测试驱动开发(TDD) :测试驱动开发(Test-Driven Development, TDD)是一种开发方法,要求开发者在编写功能代码之前先编写测试用例。通过这种方式,确保代码在开发过程中始终符合预期,提高代码质量和可维护性。

  • 行为驱动开发(BDD) :行为驱动开发(Behavior-Driven Development, BDD)是一种协作开发方法,通过自然语言描述系统行为,确保开发团队、测试团队和业务团队对需求有共同的理解。BDD 强调从用户的角度出发,编写可执行的测试用例。

  • DevOps:DevOps 是一种文化和实践,旨在通过开发(Development)和运维(Operations)团队之间的紧密协作,实现快速、可靠的软件交付。DevOps 强调自动化、监控和反馈,确保软件在整个生命周期中保持高质量。

  • 质量保证(QA)自动化:质量保证自动化工具和框架可以帮助开发团队自动化测试流程,确保软件在不同环境和配置下的稳定性和可靠性。

大模型如何赋能

LLM 的越加成熟为交付质量的提升带来了更多的可能,在整个软件开发生命周期过程中,当前阶段下 LLM 想要替代某一个角色的所有工作还不太可能,但是 LLM 已经可以在各个阶段为各个不同角色带来正向的作用,以下是一些相关的实践参考:

代码生成与重构

LLM 可以根据上下文生成高质量的代码片段,或者重构现有代码以提高其可读性和性能。代码生成能力在 LLM 上的应用和探索已经有较久的时间,随着模型能力的增强和各种工具的诞生和强化,代码生成和重构能力已经达到一个基本可用的状态,同时也有更多的专门为代码而生的代码模型逐步问世,例如 Claude 3.5 Sonnet、CodeGemma、Code Llama、Codex。

相关实践:

  1. MarsCode:智能编程助手,提供以智能代码补全为代表的核心能力,支持主流编程语言及 IDE,能在编码过程中提供单行或整个函数的建议;同时提供代码解释、单测生成、问题修复、AI Chat等辅助功能,提升编码效率与质量;

代码审查自动化

LLM 可以用于自动化代码审查,帮助开发者在代码提交前发现潜在的问题。例如,LLM 可以检测代码中的潜在错误、不规范的编码风格、安全漏洞等。

测试用例生成

LLM 可以根据代码逻辑生成测试用例,帮助开发者覆盖更多的代码路径,提高测试的全面性和有效性。例如,LLM 可以生成边界条件测试、异常处理测试等。

自动化部署与监控

LLM 可以辅助自动化部署流程,确保代码在不同环境中的正确性和一致性。此外,LLM 还可以帮助监控系统状态,及时发现和处理潜在问题。

相关实践:

  • 基于日志系统的自动化归因和排障能力,能实现在大范围故障中实现智能归因,找到根因,在实践中已经取得较好的效果;

团队协作与沟通

LLM 可以促进团队成员之间的协作与沟通,例如通过自动生成会议纪要、任务分配建议等,帮助团队更高效地协同工作。

相关实践:

  1. 飞书智能伙伴:飞书智能伙伴在群聊、会议、邮件等多个办公场景提供智能化能力,极大的提高了工作效率和沟通协作的质量;

展望大模型可探索方向

LLM 能力在交付质量保障中展现了较为强大的能力,在代码生成、代码审查、测试用例等方向已经存在一定的成熟度,但仍存在较大的潜力:

  1. 准确性提升:通过不断优化模型、训练策略、评估指标完善等策略,不断提升模型能力的准确性,降低心智负担
  2. 业务定制化:与现有的完善保障体系进行工具和流程的集合,融入业务特性,建设个性化 Agent,降低 LLM 使用成本,提升 LLM 能力覆盖度
  3. 全流程自动化:探索 LLM 自动化保障体系,管控整个需求开发周期,能够在各环节产物进行自动化质量保障,例如需求文档质量保障、代码质量问题回捞、用例质量保障、发布过程保障、线上运维监控等功能串联,形成全套的自动化方案

当前的发展现状来看,LLM 的能力还是在融入当前成熟的质量保障框架能力中,提升软件研发生命周期的的效率和质量,长期展望来看,LLM 会逐步接管质量保障的各个环节,实现高度自动化

运维管理优化

为什么需要更智能的运维

研发同学的一天,可能大部分时间不是用于开发,而是在处理各种各样的信息,比如告警、oncall、指标异常等等。

从这个角度讲,日常的运维管理比单纯的开发占据了研发的更多时间。从时间分配的角度上来说,对运维提效,可能比对开发提效相比带来的体感提升更大。

传统运维从触达渠道上可以分为两种方式:

  1. 系统日志上报,总结出各种各样的指标,当指标出现异常时,发送告警给研发
  2. 某系统 fatal 故障,发送卡片让研发检查自己负责的服务是否有问题

在这种模式下,主要的痛点在于:

  1. 收到这些运维管理的信息后,研发需要查询大量的上下文,然后定位问题
  2. 对于没有达到告警阈值的一些 case,缺乏触达能力

在这个基础上,我们需要更智能的定位分析能力和数据处理总结能力

AI 落地的思路

  • 如何处理海量的数据

用 AI 来分析海量数据,一个痛点就是受限于现在AI的语境,无法直接把所有数据都扔进去分析。

但是如果用知识/向量库的方法,用 RAG 的方法去分析,又无法让 AI 站在所有数据的角度去分析。

所以其中的一个思路是把分析的步骤拆解,一次给出局部的数据,然后通过对局部数据的分析,一步步缩小数据范围,在这个范围内给出更大体量的数据,然后让其做出更具体的分析。

  • 更多的上下文

在AI对于数据分析的基础上,查找出对应数据的上下文,然后让其总结分析。

比起单纯的数据类告警,这种分析帮助研发节省下在不同平台里查询上下文的时间。举个例子,当我们收到监控平台报警,可能会需要去埋点上报、流量监控等多个平台再去找一些次级数据,验证一些初步的判断。

在这一步,如果可以自动化的取到数据,然后给出初步的分析,就可以提高处理的效率。

  • 趋势性的数据分析

把尽可能多的数据提供给 AI,相当于 AI 帮我们观察了各种 dashboard,比如很多需要人去分析得出的尖刺,就可以让 AI 去查找。相比写死代码去分析,更加的智能弹性和节省开发人效。

数据分析的实践

目前 PAI 的数据分析,即遵循了这样的步骤:

  1. 观察整体PAI数据,分析出有尖刺的时间点
  2. 在有尖刺的时间点内,给出PAI的全部次级数据,深入分析
  3. 基于次级数据查找监控平台的对应错误,提供上下文以供分析
  4. 总结所有分析,并结构化数据发出推送卡片

暂时无法在飞书文档外展示此内容

出现演练事故,PAI的报警给出了准确的时间段,并在这个基础上给出了具体的usecase和场景,甚至具体的API。LLM能较大提升分析的效率,仅目前的分析看时间抓取的准确率达到100%,后续增加多数据源的输入(事故通报,上线记录,更多slardar错误抓取等)能增强分析的深度。

总结

一个很深入的感受是,随着 AI 能力的进化,对 AI 的使用反而应该更加的精细。

使用他要像对待一位新加入的同事,如果要让他负责你日常中的运维管理工作,你需要注意:

  1. 整理好并清晰的告诉他做这件事的步骤
  2. 准确的提供数据和保持语义化
  3. 尽量充分的上下文

结语

回顾过去的一年,AI 技术的飞速进步已深刻改变了团队的工作方式,也让我们逐渐认识到,AI 早已不再是遥不可及的梦想,而是我们日常工作中的得力助手。

团队在过去一年中对 AI 进行了大量的探索和研究。AI 单测的引入如及时雨,不仅提升了测试覆盖率和代码质量,还显著减少了人工修复的成本。AI 在内容审核领域极大地提升了审核员的工作效率和质量。在开发效率方面,AI 提供了全方位的智能代码补全、自动化代码审查、代码生成与重构、代码审查自动化等能力,涵盖了开发的方方面面,这极大的提升了我们开发效率。尽管目前 AI 相关工具还有很多不足之处,但 AI 发展的速度已让我们不得不正视起来。未来,当模型的准确性进一步提高,AI 有望在开发和生产的各个环节中提供更加全面和高效的解决方案。

技术的进步和应用场景的拓展,预示着 AI 将在我们日常开发中扮演愈发重要的角色。通过与 AI 的协作,我们相信技术生产力将达到新高度,为用户带来更好的体验。在这场技术革命中,我们迎风破浪,勇敢前行。以更开放的心态拥抱变革,用创新推动技术进步。每次新场景的落地和应用,都是团队智慧与汗水的结晶。

未来已来,我们早已做好准备,你呢?

仅需3步,稳定快速!火山引擎边缘大模型网关全面支持DeepSeek系列模型

DeepSeek 作为大模型新锐,凭借其在算法、架构及系统等核心领域的创新突破,迅速获得业界瞩目。在巨大的热度下,面对海量请求,越来越多用户遇到了请求失败、调用超时、结果无法返回等稳定性问题。

火山引擎边缘大模型网关通过一个 API 接入多家模型服务,利用全球边缘节点就近调用,提升响应速度;支持故障自动切换、重试和超时控制,确保服务可靠性;兼容 OpenAI 接口标准,可快速集成 DeepSeek 等模型,降低接入成本。

目前,火山引擎边缘大模型网关已全面支持 DeepSeek 系列模型,可通过两种方式进行模型使用:

  • 一是通过平台预置模型, 边缘大模型网关新增由火山方舟提供的 DeepSeek R1、DeepSeek V3、DeepSeek-R1-Distill-Qwen-7B/32B,您可直接使用并对其创建网关访问密钥,无需与三方模型提供商交互;

  • 二是通过自有三方模型, 边缘大模型网关新增由 DeepSeek 开放平台提供的 DeepSeek R1、DeepSeek V3 以及火山方舟提供的 DeepSeek R1、DeepSeek V3、DeepSeek-R1-Distill-Qwen-7B/32B,您可以将您在第三方模型平台的密钥纳管至边缘大模型网关,以实现通过边缘大模型网关签发的网关访问密钥进行对应模型的访问与调用。

01 3 步快速调用 DeepSeek

火山引擎边缘大模型网关支持通过一个 API 接口访问多家大模型提供商的模型与智能体,在端侧基于遍布全球的边缘计算节点就近调用。利用边缘云基础架构优势,提高模型访问速度,为终端用户提供更快速、可靠的 AI 服务体验。

在接入大模型的同时,通过配置调用顺序、自动重试、请求超时等能力,能够实现模型调用失败自动请求备用模型、单次请求失败自动重试、单次调用响应时间配置。通过产品化的配置,您可以迅速创建出与 OpenAI 的 API 和 SDK 完全兼容的网关访问密钥(API),并通过选配 DeepSeek 模型进行调用,节省大量适配成本,快速完成业务接入。

Step1 选择 DeepSeek 调用方式

调用平台预置 DeepSeek

边缘大模型网关的平台预置模型中上新了由火山方舟提供的 DeepSeek 模型,您可通过登录产品控制台查看支持模型,并通过点击创建网关访问密钥进行勾选。使用平台预置的模型 DeepSeek,您无需与模型提供商进行交互,可以直接通过边缘大模型网关进行模型配置与调用。

调用自有三方 DeepSeek

如果希望使用在火山方舟平台或 DeepSeek 开放平台购买的 DeepSeek 模型调用额度,您需要通过在边缘大模型网关平台创建对应模型提供商的调用渠道,在创建调用渠道时,需要提供您在第三方模型平台的密钥,同时勾选大模型以明确当前调用渠道可进行调用的模型配置。

完成调用渠道配置后,您可通过创建网关访问密钥勾选对应的 DeepSeek 模型,实现大模型的快速调用。

Step2 创建网关访问密钥

完成前序的 DeepSeek 模型选择后,您可在网关访问密钥创建的第二步进行模型调用配置,以更好地保障在终端业务调用时的稳定性。

  • 通过设置调用顺序,您可以手动调整上一步选择的模型调用顺序,可以根据不同厂商的容灾策略以及不同尺寸模型的降级进行设置,在前一个模型调用失败后,大模型网关将依次调用后续模型,直到成功调用一个模型。如果所有模型都调用失败,则返回错误响应。

  • 通过重试次数,您可以设置对一个模型进行调用的最大重试次数。当一个模型调用失败后,大模型网关将重新尝试调用此模型,直到重试次数耗尽。

  • 通过启用缓存,大模型网关会就近调用结果返回在边缘节点,从而加快重复查询、缩短响应时间并降低成本。

  • 通过设置**缓存的保留时长,**一旦超过指定时长,缓存将被清除。

  • 通过请求超时定义,您可以设置单次模型调用的超时时长,模型请求发出后,若在超时时长内未收到响应,则判定该请求失败。

Step3 服务调用与观测

当您根据上述流程完成网关访问密钥创建,您可以在网关访问密钥列表中查看已完成创建的信息。在详情页面,可以看到基本信息、用量统计、请求方式等详细信息。

通过详情页调用示例,您可以获得由边缘大模型网关提供的请求示例代码,包含 Curl 和 Python。当您从网关访问密钥绑定的模型中选择一个模型后,代码中的model参数值将自动替换成模型对应的值。如果网关访问密钥绑定了多个同一类型的模型,那么当选择一个模型后,可以通过单击右侧的图标查看模型故障转移的预览效果。当前模型调用失败时,大模型网关将依次调用后续的模型。在调用时,您需要将详情页 - 请求方式中的密钥替换示例代码中的$VEI_API_KEY

边缘大模型网关可根据您通过网关向模型发出的请求以及模型的响应来统计您的用量。不同模型提供商对模型用量的计量方式有所不同,根据模型调用计量方式,您的调用详情可以在用量统计中进行查看。

同时,通过云监控 - 大模型网关模块,您可以查询以网关访问密钥为维度的总用量(已消耗的 tokens 总量)与用量速率(每秒消耗的 tokens 额度)。

02 200 万 tokens 免费额度,体验边缘大模型网关

当前,火山引擎边缘大模型网关已适配 15+ 种主流大模型厂商及多个智能体提供商,点击www.volcengine.com/docs/6893/1… 了解并咨询 DeepSeek 模型~ 了解更多边缘大模型网关产品详情。

文档详情:

www.volcengine.com/docs/6893/1…

Jeddak星火计划-开启申报

摘要:抖音集团安全研究团队发起Jeddak星火计划,致力于支持安全计算领域科学研究与技术攻坚,解决机密计算、密态计算、隐私数据保护等领域的关键问题。即日起开启课题征集申报,诚邀高校及研究机构的学者报名参与。

1. Jeddak星火计划介绍

当下,数字化、智能化已然成为推动经济发展、科技创新的核心驱动力。尤其是近年来大模型应用的爆发式增长,对于垂直领域的数据需求呈指数级攀升。但同时,对于数据与计算的安全也提出了更高的要求。随着各行业数字化智能化水平的不断加深,隐私计算与安全计算领域正在发挥愈发重要的作用,确保数据在被使用、共享和分析过程中,其机密性、完整性和可用性得以有效保障。

抖音集团重视安全领域的研究与落地,其安全研究团队研发的Jeddak可信隐私计算平台具备多方安全计算、联邦学习、可信执行环境、差分隐私和同态加密等多项能力。主要面向端到端数据安全和计算过程中的数据隐私保护,助力实现安全合规的数据流通交易与融合共享。

抖音集团安全研究团队发起Jeddak星火计划,致力于支持高校及科研机构学者深入开展安全计算领域技术攻坚,解决该领域关键问题。

即日起,将开启Jeddak星火计划课题征集申报,希望助力机密计算、密态计算、隐私数据保护等方面的研究,结合大模型给安全计算领域带来的新要求,通过软硬件结合等多种路线与方式,推动相关技术向前发展。

现诚邀高校及研究机构的学者报名参与,和我们共同攻克安全计算关键问题,在这一前沿领域开拓创新,为构建安全可靠的软硬件生态贡献智慧与力量。我们将为每个课题提供40~70万元经费支持。

2. 征集研究方向:

机密计算基础设施

在大模型应用爆发式增长的时代,机密计算基于其底层可信硬件的安全性,为数据隐私和模型资产安全提供了一种灵活、高效、实用的解决方案。在大模型实际生产应用场景中,课题计划进一步完善机密计算基础设施建设,降低可信服务的部署使用门槛、提升执行效率。

建议的研究方向(可任选其中一个或多个结合):

  1. 大规模机密容器服务基础设施

  2. 高性能的TEE I/O(网络、存储)

  3. 跨TEE节点(CPU+GPU)的高性能计算

  4. 大模型推理计算加速(例如,prefill / decode分离等优化技术)

机密计算信任体系:

机密计算可有效保护敏感数据与模型,使其免受泄露、窃取与滥用风险。除了可信硬件体系提供的机密性与完整性保障以外,课题计划进一步对其中运行的基础软件栈与应用负载提供可信构建、度量、分析证明能力,以实现更加完整的信任体系,提升用户对企业服务的信任度。

  建议的研究方向(可任选其中一个或多个结合):

  1. 可信编译系统构建方法:探索可信编译系统构建方法,确保软件供应链发布的代码和待运行负载具有对应性,并进一步建设面向机密计算服务的CICD流程;

  2. 可度量体系构建方法:针对目前CVM远程证明缺乏应用层度量的问题,以及无法全面开源的问题,建立一套安全评估体系,用来确定更加合理的度量域,并且完善相应的度量方案。

  3. 可信程序分析方法:探索高效可用的程序分析框架,来判断应用本身的安全性,规避运行代码中隐私泄露、安全合规等风险,兼容多种代码语言和应用场景。

高性能安全多方计算

安全多方计算由于具有可证明的安全性,被广泛应用于数据分析、机器学习等场景,为敏感数据提供安全保护能力。在实际生产环境中,面对十亿甚至百亿规模的海量数据以及大模型动辄数十B的参数,课题计划设计更为高效的安全多方计算协议,使其在海量数据分析和大模型训练推理场景中更加实用。

  建议的研究方向(可任选其中一个或多个结合):

  1. 在百亿至千亿量级的数据查询分析场景下,时间、空间、通信最优的安全多方计算数据分析算法、范式以及系统框架(如存储、通信、调度等)

  2. 面向十亿以上参数规模大模型的高性能安全多方计算训练算法与协议,包括基础算子与迭代算法实现、跨层优化设计,以及推理平台实现等;

  3. 基于安全多方计算、同态加密等技术的隐私保护向量数据库的索引结构、检索算法与查询优化。

软硬件融合密态计算:

结合硬件设备与密码学实现高性能密态计算,是安全领域一直以来的核心探索方向。高速计算硬件可以有效加速密码学运算,可信硬件设备可以减少密码学算法中的复杂交互步骤,从而达到安全与效率兼具的密态计算目标,又弥补了单纯依靠硬件或者密码学实现密态计算的局限。课题计划针对大数据计算以及大模型训练推理场景,探索可实用的软硬件融合密态计算技术。

建议的研究方向(可任选其中一个或多个结合):

  1. 结合可信硬件设备(例如,TEE、TPM等)的密码学信任模型构建,以及安全多方计算或同态加密等算法与协议的高性能实现;

  2. 基于专用或通用硬件设备的高性能密码算法与协议实现,包括但不限于同态加密、安全多方计算等,以及低成本、低功耗、高可靠、易维护的配套加速硬件方案。

隐私保护数据合成

在大模型技术不断发展的今天,数据成为最重要的资源。如何尽可能广泛地采集数据并将其应用于大模型训练以获得性能提升,但是同时又要严格保护用户隐私、避免敏感数据违规使用,是企业面临的主要安全难题之一。课题计划探索面向大模型的隐私保护数据合成技术,既能保持原有数据的分布特性,同时能够消除隐私与敏感信息,为数据在模型训练中的合规使用提供新路径。

  建议的研究方向(可任选其中一个或多个结合):

  1. 基于生成对抗网络、扩散模型、概率合成等方法的隐私保护多模态数据(文本、图像或声音)合成技术

  2. 面向多模态数据的隐私信息度量方法与评估指标,为合成数据的隐私保护程度建立科学合理、可操作的评估手段

3. 申请条件:

面向高校/科研院所的学者开放申请,申请者须符合以下条件:

  1. 工作于具有独立法人资格的高校/研究机构,从事可信计算、安全计算、隐私计算、密码学等相关领域的科学研究。

  2. 在上述领域具备优秀的研究基础、创新能力,以及高度投入的科研热情,有能力带领团队参与并完成课题研究与实践。

4. 申报方式:

  • 点击“阅读原文”,在飞书文档中获取《Jeddak星火计划申请表》
  • 填写《申请表》后,发送至邮箱Jeddak-education@bytedance.com;

注:报名截止时间为北京时间2025年2月28日24点。

5. 计划里程碑

时间 事项
2024年1月24日 开启申报
2025年2月28日 截止申报
2025年3月31日 结果公布&启动签约
2025年10月31日 中期评审
2026年4月30日 结题评估

向AI未知之境出发,字节跳动启动 Seed Edge 研究计划!

为支持以更长周期攻坚 AI 课题,豆包大模型团队正式启动 Seed Edge 研究计划!

Seed Edge 以寻找通用智能的新方法为目标,专注于对智能边界的探索和长期研究挑战。

Seed Edge 鼓励探索更长周期、具有不确定性和大胆的 AI 研究课题,也鼓励跨模态、跨方向的交叉合作,为项目成员提供宽松的研究环境,并实行更长周期的考核方式,让大家可以放手去挑战真正颠覆性的 AI 课题!

目前,Seed Edge 初步确定了五大研究方向,将为所有入选课题提供单独的算力资源保障。 Seed Edge 初步研究方向包括:

探索推理能力的边界

以 o1 为代表的技术路线证明了推理能力可以推动智能边界提升,我们也在实践中发现对推理能力的研究才刚刚开始,有大量未探索的问题值得去深耕。无论是预训练算力级别的大规模强化学习,还是预训练和强化学习的迭代,或是可泛化的 Test-Time Scaling ,每一项技术突破都会推动智能边界前进一步。

探索感知能力的边界

我们相信智能和交互是相辅相成的,通用模型除了有极高的智能水平,还需要具备极强的和人类沟通交流的能力。我们会统一生成和理解表示,探索世界模型建模;从真实世界原始数据直接进行压缩,探索比语言更好的对世界进行表示的建模,突破智能的边界受限于语言的约束。

探索软硬一体的下一代模型设计

Transformer 的高效很大程度上取决于它与 GPU 的适配性,能充分发挥 GPU 的性能。我们希望面向下一代训练和推理硬件的结构特点设计下一代模型,从软硬件一体的角度思考未来的模型结构特性,达到训练效率、推理效率、模型性能的多目标同时优化,并进一步压榨下一代硬件的能力,突破模型能力的边界。

探索下一代学习范式

探索对未来的学习范式有变化的方向,挑战现有范式的“共识”,给更高效地实现通用智能提供基础和可能性。通过可解释性研究理解模型学习能力的来源,并进一步提出新的学习视角,探索模型在学习过程中的表现,和生物启发的学习过程对比,发现现有模型学习范式可以改进的空间:探索比 Next-Token Prediction 更高效的学习目标;探索比 Backpropagation 更高效的学习方法;探索比大数据 Pretraining + Alignment 更高效的学习范式。

探索下一个 Scaling 方向

继 Pretraining Scaling 和 Test-Time Scaling 之后,下一个 Scaling 方向对未来智能边界的演化会起到重要的作用。探索下一个 Scaling 方向,并推动智能边界的进步。

点击“链接”,加入豆包大模型团队

大语言模型应用开发框架 —— Eino 正式开源!

图片

作者|Eino 项目组

今天,经过字节跳动内部半年多的使用和迭代,基于 Golang 的大模型应用综合开发框架 —— Eino,已在 CloudWeGo 正式开源啦!

Eino 基于明确的“组件”定义,提供强大的流程“编排”,覆盖开发全流程,旨在帮助开发者以最快的速度实现最有深度的大模型应用。

你是否曾有这种感受:想要为自己的应用添加大模型的能力,但面对这个较新的领域,不知如何入手;想持续的站在研究的最前沿,应用最新的业界成果,但使用的应用开发框架却已经数月没有更新;想看懂项目里的用 Python 写的代码,想确定一个变量或者参数的类型,需要反复查看上下文确认;不确定模型生成的效果是否足够好,想用又不太敢用;在调试、追踪、评测等开发之外的必要环节,还需要额外探索学习其他配套的工具。如果是,欢迎了解和尝试 Eino,因为 Eino 作为旨在覆盖 devops 全流程的大模型应用开发框架,具有如下特点:

  • 内核稳定,API 简单易懂,有明确的上手路径,平滑的学习曲线。
  • 极致的扩展性,研发工作高度活跃,长期可持续。
  • 基于强类型语言 Golang,代码能看懂,易维护,高可靠。
  • 背靠字节跳动核心业务线的充分实践经验。
  • 提供开箱即用的配套工具。

Eino 已成为字节跳动内部大模型应用的首选全代码开发框架,已有包括豆包、抖音、扣子等多条业务线、数百个服务接入使用。

项目地址:github.com/cloudwego/e…

未来,我们将以 Eino 开源库为核心代码仓库,坚持内外用一套代码,与社区共建最优秀的大模型应用开发框架。

0 1
快速认识 Eino

Eino 是覆盖 devops 全流程的大模型应用开发框架,从最佳实践样例的 Eino Examples,到各环节的工具链,都是 Eino 的领域:

图片

Composition of Eino Framework from a devops perspective

那么 Eino 具体能做什么?首先,Eino 由一个个大模型领域的“组件”组成,比如最核心的是与大模型交互的 Chat Model:

model, _ := ark.NewChatModel(ctx, config) // 创建一个豆包大模型message, _ := model.Generate(ctx, []*Message{    SystemMessage("you are a helpful assistant."),    UserMessage("what does the future AI App look like?")}

像上面这样一个个的直接使用组件,当然没问题,Eino 提供了大量有用的组件实现供选择。但是,大模型应用有它们自身的特点和规律,比如:

  • 核心是大模型,业务逻辑围绕“如何给大模型充分、有效的上下文”以及“如何让大模型的输出可靠的影响环境”,核心的组件类型、数据类型和交互模式是可以枚举的,整体可以由有向图来描述。
  • 大模型输出的特点是流式输出,意味着模型的下游都需要有效的处理流式数据,包括流的实时处理、流的复制、多个流的合并、单个流的拼接等。
  • 以有向图为基础,衍生出并发处理、扇入扇出、通用横切面、option 分配等一系列子问题。

Eino 的编排能力,是上述通用问题的充分解决方案。

以 ReAct Agent 为例:一个 ChatModel(大模型),“绑定”了 Tool(工具),接收输入的 Message,由 ChatModel 自主判断是否调用 Tool 或输出最终结果。Tool 执行结果会再次成为给到 ChatModel 的 Message,并作为下一轮自主判断的上下文。

图片

上述基于 ChatModel 进行自主决策和选路的 ReAct Agent,便是基于 Eino 的 组件 和 Graph 编排 来实现, 代码清晰简洁,可与流程图清晰对应。

  • 代码实现详见:「flow/agent/react 」的实现

  • ReAct Agent 用户手册详见:「react_agent_manual」

「flow/agent/react」github.com/cloudwego/e…

「react_agent_manual」www.cloudwego.io/zh/docs/ein…

在 Eino 中,这是几十行代码的图编排:

// 构建一个 ReAct Agent,编译为一个输入为 []*Message,输出为 *Message 的 Runnable
// 创建包含 state 的 Graph,用户存储请求维度的 Message 上下文graph = NewGraph[[]*Message, *Message](   WithGenLocalState(func(ctx context.Context) *state {      return &state{Messages: make([]*Message, 0, config.MaxStep+1)}   }))
// 将一个轮次中的上下文和响应,存储到 Graph 的临时状态中modelPreHandle = func(ctx context.Context, input []*Message, state *state) ([]*Message, error) {    state.Messages = append(state.Messages, input...)    return state.Messages, nil}
_ = graph.AddChatModelNode(nodeKeyModel, chatModel, WithStatePreHandler(modelPreHandle))
_ = graph.AddEdge(START, nodeKeyModel)
_ = graph.AddToolsNode(nodeKeyTools, toolsNode)
// chatModel 的输出可能是多个 Message 的流// 这个 StreamGraphBranch 根据流的首个包即可完成判断,降低延迟modelPostBranch = NewStreamGraphBranch(   func(_ context.Context, sr *schema.StreamReader[*Message]) (endNode string, err error) {      defer sr.Close()
      if msg, err := sr.Recv(); err != nil {         return "", err      } else if len(msg.ToolCalls) == 0 {         return END, nil      }
      return nodeKeyTools, nil   }, map[string]bool{nodeKeyTools: true, END: true})
_ =  graph.AddBranch(nodeKeyModel, modelPostBranch)
// toolsNode 执行结果反馈给 chatModel_ = graph.AddEdge(nodeKeyTools, nodeKeyModel)
// 编译 Graph:类型检查、callback 注入、自动流式转换、生成执行器agent, _ := graph.Compile(ctx, WithMaxRunSteps(config.MaxStep))

在上面这几十行代码的背后,Eino 自动做了一些事情:

  • 类型检查,在 compile 时确保相邻的节点的类型对齐。

  • 流式封装,编译出的 Runnable 既可以 Invoke 调用,也可以 Stream 调用,无论内部的 Tool 是否支持流。

  • 并发管理,对 state 这个公共状态的读写是并发安全的。

  • 横切面注入,如果某个组件(比如一个 tool)没有实现 callbacks 注入,则 Eino 自动注入。

  • Option 分配,编译出的 Runnable 可以灵活接收并把 option 分配给指定的节点。

02
Eino 的独特优势

基于大语言模型的软件应用正处于快速发展阶段,新技术、新思路、新实践不断涌现,我们作为应用开发者,一方面需要高效、可靠的把业界共识的最佳实践应用起来,另一方面需要不断学习和提升认知,从而能够整体理解这个新领域的可能性。因此,一个优秀的大模型应用开发框架,既需要封装领域内“不变”的通用核心要素,又需要基于最新进展敏捷的横向和纵向扩展

另一方面,目前较为主流的框架如 LangChain,LlamaIndex 等,都基于 Python,虽然能借助 Python 较为丰富的生态快速实现多样的功能,但是同时也继承了 Python 作为动态语言所带来的“弱类型检验”和“长期维护成本高”等问题。在大模型应用快速进入大规模线上运行阶段的当下,基于 Golang 这一强类型语言而实现的高可靠性高可维护性,逐渐具有更大的价值。

基于大模型的应用开发是相对较新的领域,有时需要摸着石头过河,靠实践来检验认知。依托字节跳动高频应用豆包、抖音等的多样场景、快速迭代和海量反馈,Eino 在实践驱动设计方面有独特的优势。

最后,生产级的框架需要面对真实、复杂的业务场景,因此,除了直观易用的 API 设计之外,提供有针对性设计的开发工具可以有效的帮助开发者理解和应对复杂性、加速开发过程。

内核稳定

我们认为,存在一个常见的组件列表,共同构成了大模型应用的常见组成部分。每类组件作为一个 interface,有完善、稳定的定义:具体的输入输出类型,明确的运行时 option,以及明确的流处理范式。

在明确的组件定义基础之上,我们认为,大模型应用开发存在通用的基座性质的能力,包括但不限于:处理模型输出的流式编程能力;支持横切面功能以及透出组件内部状态的 Callback 能力;组件具体实现超出组件 interface 定义范围的 option 扩展能力。

在组件定义和通用基座能力的基础上,我们认为,大模型应用开发存在相对固定的数据流转和流程编排范式:以 ChatModel(大模型)为核心,通过 ChatTemplate 注入用户输入和系统 prompt,通过 Retriever、Document Loader & Transformer 等注入上下文,经过 ChatModel 生成,输出 Tool Call 并执行,或输出最终结果。基于此,Eino 提供了上述组件的不同编排范式:Chain,链式有向无环图;Graph,有向图或有向无环图;Workflow,有字段映射能力的有向无环图。

上述设计和功能共同构成了 Eino 的稳定内核:

图片

敏捷扩展

每类组件都可以横向扩展出不同的实现,比如 ChatModel 组件可以有 OpenAI、Gemini、Claude 等不同的实现等。这些具体的实现,在实现组件 interface 从而可作为组件参与编排的基础上,可以实现和持续扩展自身的特殊功能。

当实际业务场景中,出现需要进入编排但是不对应任何组件定义的功能时,Eino 支持将自定义 function 声明为 Lambda 类型。Lambda 有用户声明的输入输出以及 option 类型,可支持全部的流处理范式,具备完整的 Callback 能力,在编排视角等价于官方组件。

在大模型应用开发领域,存在并且持续会涌现多个组件的特定编排范式,这些范式封装了验证有效的研究成果或实践经验,比如 ReAct Agent,Host Multi-Agent 等。这些开箱即用的封装,浓缩了大模型应用开发领域的最佳实践,会随着我们认知的提升持续纵向扩展。

在组件和图执行过程中,开发者可以在固定的时机嵌入自定义的回调逻辑,用于注入横切面功能。

综上所述,Eino 框架具备充分的可扩展性:

图片

高可靠易维护

基于 Golang 写 Eino 代码时,开发者可以充分利用 Golang 的强类型特性,为所有的组件、Lambda、编排产物等声明具体类型。这像是为代码绘制了一幅精确的地图,开发者可以沿着清晰的路径进行维护和扩展,即使在项目规模不断扩大、功能持续迭代的情况下,依然能够保有较高的可维护性。

同时,Eino 编排能力也充分利用了强类型系统的编译时校验能力,尽可能将类型匹配问题暴露的时机提前到 graph 的编译时,而不是 graph 的运行时。尽早并明确的暴露类型匹配问题,有助于开发者迅速定位和修复,减少因类型错误在运行时引发的难以排查的故障和性能问题。

另一方面,Eino 遵循模块化设计,核心库以及各组件实现是单独的 go module,每个 go module 做到依赖最小化。同时,API 设计以“精简”、"直观"和“同构性”为原则,辅以由浅入深的全面文档,尽可能让学习曲线更平滑。最重要的是,Eino 采用清晰的分层设计,每层职责明确、功能内聚,在提升维护性的同时能更好的保证稳定性。

Eino 框架结构图:

图片

实践驱动

Eino 框架的设计开发过程,扎根于 “满足真实需求” 与 “实践驱动设计” 这两大基石之上。功能的演进过程与字节跳动各业务线的接入过程紧密结合,始终倾听开发者的声音,并通过实际使用效果来检验设计的合理性。比如我们收到来自抖音的“希望能够以字段为粒度在图中映射和传递数据”的需求,以此为基础设计了 Workflow;倾听来自豆包的使用痛点,增强作为模型输入输出类型的 Message 结构体。在未来的开源生态共建过程中,我们会继续坚持上述原则,满足更广大的用户和开发者的真实需求,并在更大的范围内认真实践和精进。

图片

工具生态

链路追踪、调试、可视化,是编排引擎的三个重要辅助工具。Eino 内置了 tracing callback,并与 Langfuse 平台做了集成。同时提供了 IDE 插件,可以在写代码的过程中随时可视化查看编排出的 graph,并进行调试运行,甚至可以通过 UI 拖拽的方式快速构建 graph 并导出为 Eino 代码。

图片

03
快速上手

针对 Eino 的学习和使用,我们提供了完善的 Eino 用户手册,帮助大家快速理解 Eino 中的概念,掌握基于 Eino 开发设计 AI 应用的技能,赶快通过「Eino: 快速开始」尝试使用吧~

如有任何问题,可通过下方的飞书群或者「Eino Issues」和我们沟通、反馈~

「Eino: 快速开始」www.cloudwego.io/zh/docs/ein… Issues」github.com/cloudwego/e…

相关链接

项目地址:

github.com/cloudwego/e…

项目官网:www.cloudwego.io

图片

扫描二维码加入飞书社群

字节跳动观测数据埋点标准化实践

来源|字节跳动基础架构-可观测团队

背景

随着字节跳动业务规模不断扩大,对存量和新增业务的服务质量承诺变得越发关键。稳定性治理方面:怎样支持保障服务线上的高可用性,或者在出现故障/事故时,如何高效且迅速地止损、定位分析影响面已成为一个重要议题。

稳定性建设所涉及的话题十分广泛,涵盖流程梳理与标准化、数据标准化、SLO 定义、故障自愈、事故复盘和演练等方面,字节跳动基础架构可观测团队提供的稳定性平台建设思路是“事前预防、事中处理、事后复盘、事后补救/反哺事前”这四个阶段。

其中, 观测数据标准化以及一系列配套的数据链路,如:数据埋点、数据消费、数据存储、数据查询、离线数仓等,都是后续稳定性建设的重要数据基石。

并且,由此引申出排障/止损效率的问题,由于字节的服务/基础设施是分层建设的,包括端侧客户体验层、网络接入层、应用服务层、基础设施层、IDC\资源层等,不同层面的统计/描述口径是否一致、能否对应,以达到在跨层间能上卷下钻和平层内过滤聚合的“车同轨书同文”效果,这对于大幅提升整体排查效率, 让 SRE/GOC 同学能够自助完成端到端的问题排查就显得尤为重要。

img_v3_02ik_a98c1a06-a522-493e-a443-78169a1b9f3g.png

拥有统一的观测数据标准, 能够在很大程度上提升团队间的排障效率,从人工分析的方式提升至更大程度的自助/自动化排障的阶段。

埋点标准化的重要性

提高研发效率 & 降低研发协同成本

  • 面向排障方面:跨层间的上下文过滤便捷,术语统一。
  • 进行历史数仓分析(容量优化)时,整体数据处理逻辑的适配成本会大幅降低。
  • 用户的学习曲线陡峭,理解心智负担沉重。

为 AIOps 提供强有力的数据支撑

观测数据属于 AIOps 的五大基石(数据、知识、算法、代码联动、人机协同)之一。在清华裴丹老师的《AIOps 落地的 15 条原则》里,也都提及了数据的重要性。

拥有数据标准化和统一的访问体验,为后续稳定性的终极目标 MTTR 1-5-10(1 分钟发现,5 分钟响应以及 10 分钟快恢复)提供了数据层面的保障。包括同层数据的聚合 / 过滤,以及跨层数据的下钻和上卷,都会有统一的使用方式。

名词解释

名词 解释
Metrics 2.0 字节跳动内部使用广泛的时序数据处理引擎,提供了时序数据收集、存储和聚合查询的功能。2.0 版本提供引入多值概念,打平prometheus 4类指标类型语义、支持秒级打点& 存储周期定制化等多租户特性、 端到端高性能优化的分布式时序集群版本。
BytedTrace BytedTrace是字节跳动使用的一套集成了 Tracing/Logging/Metrics 三种能力的可观测性解决方案,提供了从采集、传输、存储、检索到前端产品化交互的整套能力。它定义了统一的数据模型(Trace 、Span 、Event、Metrics 等),提供了各语言配套 SDK,并与公司各主流框架组件实现默认集成。
观测埋点 TagKV Metrics TagKV 是一种用于标记和管理度量数据的键值对(Key-Value Pair)格式。通常用于监控系统、分布式追踪系统和日志管理系统等领域,TagKV 提供了一种灵活且高效的方法来分类和筛选数据。
Measurement 可观测对象的某个指标,如服务的上游调用延时,物理机的 CPU 使用率。Measurement 是带有可观测对象的 context的,是语义化的,同时能识别在不同条件下应该使用哪个版本的指标以及对应的 TagKV。而且可以根据观测对象的元数据条件,同时关联多个时序数据源,便于按需时序数据源切换。
SLO Service Level Objectives,服务级目标是指服务提供方对所提供服务的某些性能或质量指标所设定的目标值。这些指标通常用于衡量服务的可用性、性能和其他关键属性,旨在确保服务达到预期的质量水平。
TCE Toutiao Cloud Engine,为字节跳动内部提供的高度可用、弹性扩展的容器服务。
PSM Product Subsys Module,是字节跳动内部服务的唯一标识。
GOC Global Operations Center,基于字节跳动各类研发,运维体系下的高可用产品能力,结合稳定性保障策略及运营机制,提供字节跳动全线基础产品的可靠性服务与设施稳定性保障,达成字节跳动全线业务各类场景下的端到端高可用性。

字节埋点标准化挑战与拆解思路

挑战: 历史上可观测性埋点质量偏低

首先,我们对埋点标准化进行定义,包括但不仅限于如下的标准定义,包括覆盖完整、定义统一、计量准确、面向引擎友好等四大方面。

img_v3_02ik_56f19e79-13a2-4bdf-aa70-2d6d49e02b8g.png

简而言之,在 2020 年以前,从覆盖完整定义统一计量准确面向引擎友好等维度来看,字节整体的观测数据埋点存在一定的差距。

具体如下:

  • 负载均衡 埋点

    • 计量准确:中等水平

      • 存在较严重的打点丢失问题
    • 面向引擎友好:较低水平

      • 指标打点对于配置预计算不友好
      • 指标名膨胀也比较严重
  • 微服务 埋点

    • 覆盖完整:中等水平

      • 20 年前 Tracing 方案还在 V1 版本
    • 计量准确:中等水平

      • 遇到高基数的指标会被封禁
    • 面向引擎友好:较低水平

      • 指标打点对于配置预计算不友好
      • 指标名膨胀也比较严重
      • 加权计算也不好实现
  • 语言 运行时 埋点

    • 定义统一:较低水平

      • Golang & C++ 框架 不同的版本定义的指标格式都不太一样
    • 面向引擎友好:较低水平

      • 指标打点对于配置预计算不友好
  • 容器指标 埋点

    • 覆盖完整:较低水平

      • 没有日志采集覆盖
    • 计量准确:中等水平

      • 遇到高基数的指标会被封禁
    • 面向引擎友好:较低水平

      • 指标打点对于配置预计算不友好
  • 基础架构 存储 & 数据库 埋点

    • 覆盖完整:较低水平

      • 存储、数据库、MQ 客户端没有黄金指标打点
      • 没有日志采集覆盖
    • 计量准确:较低水平

      • 不同存储、数据库、MQ 产品打点格式 都不一
    • 面向引擎友好:较低水平

      • 指标打点对于配置预计算不友好

思路: 分层&向后兼容推进埋点标准化

总结来说,之前的字节服务端观测数据质量大致存在三类问题。

  • 同层数据/跨层数据不一致。
  • 观测的多模态数据类型(指标、日志、链路)的数据定义不统一。
  • 观测数据格式对引擎不够友好,例如所有数据都在 default 租户的一个大仓里,再比如很多观测指标的定义对于预计算不友好。

针对上述问题,我们采取了如下的多个思路逐一进行解决。

实施思路

一方面,在埋点侧就尽可能统一埋点 TagKV 定义,而且平台级 TagKV 都通过环境变量或者请求上下文自动注入对应的 Tag Value, 以防止由业务手工注入带来的人工错误。

另一方面,对于指标、链路和日志侵入式 SDK,我们通过字节内部的远程过程调用框架以及存储、数据库、消息中间件的客户端 SDK 搭载嵌入中间件,对于业务来说,能相对透明地升级到最新特性的版本。另外, 对于远远低于 SDK 基线版本的服务, 我们也通过字节软件供应链安全治理平台通过编译卡点的不同程度[warning 提示/发布卡点]推动业务升级。

在 负载均衡、应用、中间件、存储计算组件等各个纵向方面, 我们也主动与对应的平台对接,推动指标、日志、链路的埋点注入。

最后,在指标埋点上也额外关注对于多租户的声明,以达到一定的分库分表功能,以及多值声明,以最大程度减少数据消费和存储成本。如下所示, 就是团队在各个不同观测对象的埋点方面所做的业务推进情况。

img_v3_02ik_82b1a459-030a-43f1-8db0-808d5cc209eg.jpg

难点: 识别和解决

类似观测数据标准化的工作历经多年,牵涉的团队众多,整个过程并非毫无波折。遇到问题时要解决问题并思考能否将其标准化或者平台化,同时也要考虑能否尽可能地复用其他团队的能力和工具来助力我们进一步推广。当时如何高效地推动业务升级是我们的主要目标。

[业务推进] 高效推动业务升级观测SDK

在 Metrics SDK 需要升级到基线版本的情况下,以前的做法是在字节软件供应链安全治理平台上配置版本拦截,提醒用户升级,但是整体升级效率比较低,我们也无法跟踪用户的升级进展。因此我们联合字节软件供应链安全治理平台团队实现 SDK 自动升级功能。

Metrics ****SDK 自动升级

Metrics ****SDK 自动升级功能可以自动实现在当前业务代码库的代码提交期间,如果检测到对应集成的metrics SDK 低于基线版本,则会向用户推送代码提交失败的通知,提醒用户需要主动升级到metrics SDK基线版本或以上的操作。

远程过程调用 框架 & 基础组件客户端 集成 ****BytedTrace ****SDK 集成

观测团队多年来持续推动公司的远程过程调用 框架以及基础组件客户端 集成 BytedTrace SDK **** ****借助字节软件供应链安全治理平台进行递进式卡点推广,依靠代码血缘平台来推动框架、组件的基础库版本实现升级。在存有流量的微服务上,BytedTrace SDK的覆盖比例按照 TCE pod 接入情况来计算,当前已达到 95%。

从服务的优先级角度而言,公司当前96% 的 P0 服务中已接入 Bytedtrace SDK 。

[业务推进] 提升基础组件观测埋点质量

TCE 调度 / 运行时 打点格式设计思路

前文提到,提升业务层、应用层、容器层等多层间指标的跨层关联和下钻能力是指标标准化的一个重要目标,而实现跨层关联的关键动作在于保证同一含义的指标 TagKV 在各层上的定义保持统一,为实现这一点,我们对各个层次上的核心组件进行了统一的设计,具体如下:

层次 核心组件/着手点 埋点标准化设计思路
业务层 Metrics 2.0 SDK - 内置统一的平台级TagKV,提供横向跨语言、跨服务的TagKV统一
应用层 运行时 指标、远程过程调用 指标 - 横向上,提供统一的、跨语言的指标名定义
  • 纵向上,对齐Metrics 2.0 SDK 平台级TagKV规范 | | 容器层 | 与调度合作,对容器指标采集agent(TCE调度)进行标准化改造 | - 对齐Metrics 2.0 SDK 平台级TagKV规范 |
  1. 首先,我们在 Metrics 2.0 SDK 内置定义了一套平台级 TagKV,这样所有使用 Metrics 2.0 SDK 的业务打点都会携带标准的预定义的 TagKV。这些共同TagKV包括: _cluster、_psm、_pod_name、_ipv4 等。

  2. 在应用层,挑选了对业务排障、应用观测常用且通用的两类指标(运行时、远程过程调用)进行标准化,目标是在横向上,提供跨语言、统一的指标名、TagKV语义定义等;在纵向上,对齐 Metrics 2.0 SDK 平台级 TagKV 规范,以便于跨层关联。以 运行时 指标为例,其定义规范如下:

    1. 不同语言的指标采用统一命名约定:runtime. {runtime} . {metric}[ . {field}]
    2. 不同语言类似含义指标采用统一命名风格:如 go、java 中统计堆对象申请量的指标都命名为memory.allocated_bytes
    3. 必须包含 Metrics 2.0 SDK TagKV 规范的平台级 TagKV,如 _psm、_pod_name 等
  3. 在容器层,与调度团队共同推动其 TCE 容器指标采集 agent(TCE调度) 的指标标准化改造,指标 TagKV 对齐Metrics 2.0 SDK TagKV 规范。

通过将这些核心组件进行标准化改造,为跨层的指标关联和下钻提供了能力基础。同时,在每个核心组件的指标定义上,我们还通过以下两个方式进一步提升埋点的性能和成本收益,第一点是对各个组件使用独立租户,实现资源的隔离,保障写入稳定性和查询性能;

指标 租户名 集群类型
运行时 apm.runtime 独立集群
远程过程调用 框架 apm.rpc 独立集群
TCE 容器指标 computation.tce 独立集群

第二点是在语义明确的前提下,尽量使用多值格式定义指标,降低存储成本。以 TCE调度 指标为例,将原来 mem 相关的四个指标合并为一个多值指标的四个字段,存储成本大致可以被认为降低至四分之一。

原指标 改造后多值指标名 改造后多值字段
tce.host.mem_total inf.tce.host.mem total
tce.host.mem_free free
tce.host.mem_available available
tce.host.mem_used used

[配套工具] 帮助平滑迁移观测数据

[工具1] 语义 化指标替换

我们提供语义化指标替换,称为Measurement,其能力就是对原始 Metrics 打点的语义化封装;同时能识别在不同条件下应该使用哪个版本的指标以及对应的 TagKV。这两个关键能力能够促使在做数据迁移时,观测大盘和报警基本达到比较平滑的状态。

原始 Metrics 打点:直接写入时序数据库(可以是 metrics \ influxdb \ prometheus)的数据。

语义 封装:用标准的语义化来包装原始的 metrics 打点数据。 比如 go 服务的 gc 数量的 metrics 打点是 go.{{.psm}}.numGcs,其中{{.psm}}为具体的 psm, 我们会定制一个语义化指标名叫 "runtime.go.gc_num"来表达 go 服务的 gc 数量,包括用统一的 TagKV 来封装对应的原始 TagKV。 不管是 open api 还是前端调用, 都用指标 "runtime.go.gc_num" 对measurement 服务进行调用。

不同条件下的查询 路由:需要这个能力是因为在字节内部原始 Metrics 的打点会不断的升级, 比如 golang 运行时 历史上会有 v1 、v2 、v3 多个版本,我们需要能够在给定的输入信息条件下去查询到对应的指标版本。这个判断条件实现的逻辑一般为可用输入的 psm 名字构成 Metrics go v1 的指标名,再根据指标名的数据是否存在来判断是 runtime v1、runtime v2 或者 runtime v3 的版本,指标判断也以此类推。或者可以通过 psm 的 scm 编译信息确定该 psm 编译的 golang 运行时 版本是 v1、v2 或者 v3。 通过对应条件的判断来做到对应数据的查询路由。

img_v3_02ik_5c2f9415-8b2f-4dd5-a031-dff8ce63af6g.png

在有了 Measurement 能力后,我们抽象出了 Measurement 服务,该服务作为观测大盘和报警的一个数据源。在尽量不需要用户介入的情况下完成数据打点的迁移和替换。

当前借助 Measurement 能力,针对公司的 远程过程调用、HTTP 等框架,容器引擎、FaaS、机器学习推理等平台,还有负载均衡、缓存、数据库、消息队列等基础组件,以及golang 运行时 等,均进行了统一的标准化语义封装,这些语义化封装在观测平台上均有所展现。

[工具2] Metrics 前缀分流

怎样帮助业务顺利地迁移到新租户,同时确保新老指标的查询方式均可使用,是我们在推动业务租户迁移时所面临的较大挑战。

针对上述问题,观测团队起初推进引导用户主动迁移至新租户,旨在实现租户隔离,提供更优的稳定性保障,进行精细化容量治理以降低成本。然而,后来发现主动迁移的速度太慢,赶不上打点量的自然增长。于是,推出了让用户无感知的被动租户迁移方案。大致思路是依据某些特定的指标前缀,主要涵盖一级 / 二级前缀,通过特定配置把这些指标分别路由到不同的新租户,并且在新租户上支持查询翻译,即便用户不修改查询租户,继续用 Default 租户查询仍能正常获取数据。该方案具有以下优势:

  1. 业务在读写两侧无需进行代码变更,就能将流量迁移到新租户集群。
  2. 最大程度减少不同租户间因集群变更和读写流量变化对线上稳定性产生的相互影响,提供更出色的稳定性保障。
  3. 精准对接业务线租户,便于后续进行打点流量治理、容量规划以及资源充值等操作。

具体的实现由 Metrics 组件中各模块的相互配合完成,包括写入、控制面、查询、数仓等方面,大致的实现流程如下:

前缀分流租户的整个过程存在众多细节,为减少过程中的过多人为操作,防止出现某些环节被遗忘的情况,观测团队设计了分流流程工单以及白屏化运维平台,尽可能让整个操作流程实现自动化,提高分流租户的效率。此外,前缀分流迁移新租户的整个过程对于业务来说成本为零,同时对于 观测团队而言不像依赖业务方主动迁移那样周期漫长,其周期短、生效时间快,能够收敛团队人力的持续投入。

总的来说,观测团队提供了一种让用户无感知、实现无缝迁移新租户的方案,用户的核心观测大盘和报警也无需修改,最大程度降低了埋点标准化对用户的打扰。

埋点标准化字节的实践与效果

观测数据质量前后对比

经过 2020-2022 年推进 BytedTrace SDK 覆盖率、2023 年推动云基础组件和应用层指标租户迁移之后, 从埋点标准化的 4 个维度看,都有不同程度的质量提升。

  • 负载均衡

    • 计量准确:较高水平 [2020年为中等水平]

      • 通过 2.0 SDK 三个特性, 基本消除丢点的问题:

        • 打点本地聚合
        • 面向字节流的 codec 编码
        • Agentless 投递
    • 面向引擎友好:较高水平 [2020年为较低水平]

      • 实现面向预计算友好的效果
    • 成本收益:

      • Metrics 2. 0 打点商品成本相对 1.0 下降 94%
      • Metrics 2. 0 很好地解决了打点封禁问题,特别是在一些配置量巨大的核心集群,解决了其超过 90%打点无法查询的情况
      • Metrics2. 0 TLB 机器成本初步统计主容器和 adaptor 打平,同时相对 1.0 节约了 ms2 的 15000 核资源
  • 微服务

    • 覆盖完整:较高水平 [2020年为中等水平]

      • 80%以上 PSM 覆盖到 BytedTrace SDK 集成
    • 计量准确:中等偏上水平 [2020年为中等水平]

      • 高基数的指标封禁问题 由于迁移到了新租户 可以做封禁阈值定制化
      • [计划中] 升级 bytedTrace 内的 metrics 2.0 SDK 降低丢点的风险
    • 面向引擎友好:较高水平 [2020年为较低水平]

      • 实现面向预计算友好的效果
    • 成本收益:

      • 以计算关键组件 Consumer 为例,新租户只需要老租户 20%的资源,就可以完成相同数据的写入计算;其他写入计算类组件也类似
      • 以存储关键组件 tsdc 为例,新租户只需要老租户 55%的资源,就可以完成数据的写入、存储
  • 语言 运行时

    • 定义统一:较高水平 [2020年为较低水平]

      • 统一了不同语言和框架的 运行时 打点格式
  • 容器指标

    • 覆盖完整:中等水平 [2020年为较低水平]

      • TCE调度 接入日志租户
    • 计量准确:较高水平 [2020年为中等水平]

      • 引入多值 降低指标名数量

      • 高基数的指标封禁问题 由于迁移到了新租户 可以做封禁阈值定制化

      • 通过 2.0 SDK 三个特性, 基本消除丢点的问题

        • 打点本地聚合
        • 面向字节流的 codec 编码
        • Agentless 投递
    • 面向引擎友好:较高水平 [2020年为较低水平]

      • 实现面向预计算友好的效果
  • 基础架构 存储 & 数据库

    • 计量准确:较高水平 [2020年为中等水平]

      • 引入多值 降低指标名数量

      • 高基数的指标封禁问题 由于迁移到了新租户 可以做封禁阈值定制化

      • 通过 2.0 SDK 三个特性, 基本消除丢点的问题

        • 打点本地聚合
        • 面向字节流的 codec 编码
    • 面向引擎友好:中等水平 [2020年为较低水平]

      • 打点格式调整的 支持预计算配置
    • 成本收益:

      • 以 mysql 迁移为例

        • Mysql 租户 成本节省 45.7%
        • Mysql 租户 带宽节省了 80%

截止到今年年初, Metrics 在中国国内区域已经接入 60+ 租户,占总流量的 70% 左右。

赋能效果总结

加速微服务端到端根因定位

通过指标标准化 & 多模观测数据 [指标, 日志,链路]标签术语的标准化, 我们实现面向微服务的上卷 & 下钻关联分析。

也使得使得跨层问题根因分析有了可能性:

目前端到端根因定位覆盖了60%以上的报警场景,日均触发根因定位 50余万 次,用户对定位结果的正反馈率超过80%。

简化服务性能离线数仓构建

在实现了在线观测数据的标准化,并将其导入统一的存储介质之后,构建字节整体关于服务性能、容量、吞吐量的数仓大盘就更加便捷。比如 展现某服务的单核 QPS 分时热力图 如下:

目前基于微服务应用性能数仓已覆盖公司超97%的微服务量化,有效支持字节跳动各业务线服务性能、服务应用健康度度量,由此带动一系列精准的成本优化。

观测底座自身收益

  • 从稳定性角度看,由于引入metrics多租户概念,所以我们能够通过逻辑租户映射到物理资源,从而降低故障半径,减少不同租户间流量的相互干扰。
  • 从成本角度看,我们能够依据每个租户的副本数、存储时长 TTL、打点的最小精度以及多值定义,最大程度地降低写入流量和存储容量的成本。metrics 多租户迁移前后对比,成本节省幅度在 20% ~ 80% 不等。

总结

历经上述观测埋点套件 BytedTrace SDK推广、Metrics 指标标准化迁移和推广、部分业务接入日志多租户,字节后端观测数据的质量在覆盖完整度定义统一计量准确面向引擎友好四个方面上取得了显著的质量提升。这也为后续的全景全栈高效排障奠定了坚实的基础,帮助更多业务团队在业务稳定性方向持续建设。


依托字节跳动内部可观测团队大规模技术实践,通过内外合力,在火山引擎上推出了应用性能监控全链路版(APMPlus)、托管 Prometheus(VMP)、云监控等可观测产品,致力于为用户提供全面、智能、高效、易用且安全的全栈可观测解决方案。

目前 APMPlus Server 端监控已正式 GA 并支持最新的大模型链路追踪相关能力,欢迎咨询了解。

🔗 相关链接

APMPlus www.volcengine.com/product/apm…

VMP www.volcengine.com/product/pro…

云监控 www.volcengine.com/product/clo…

详解veImageX助力卓特视觉智能、高效生成设计素材

前言

设计素材行业为设计师和创意工作者提供丰富的视觉和创意资源。数字媒体和互联网的迅猛发展,促使这一行业市场规模不断扩大,用户对设计素材的个性化和定制化需求与日俱增。卓特视觉,作为Adobe Stock中国区官方合作伙伴,自2014年成立以来,始终致力于推动中国创意产业的繁荣发展。在AI的技术浪潮中,卓特视觉选择与火山引擎veImageX(一站式图片解决方案)携手合作,旨在通过AIGC加成,更加智能和高效的生成设计素材,进一步拓宽创意表达的边界。

卓特视觉(Droit Vision),Adobe Stock中国区官方合作伙伴,全面整合全球范围内的高质量图片、矢量插画、高清视频及音效音乐等素材资源,专注于为新媒体、设计、广告、各类垂直行业及个人用户,提供一站式的视觉素材和解决方案,助力创意人士和企业提升其视觉作品的品质和影响力。

至今,卓特视觉在线销售高清正版图片总数超5.6亿和超3,600万条高清视频。自2014年成立以来,卓特视觉成功为众多知名企业提供了安全、高效、优质的视觉创意解决方案,赢得了广泛的企业级客户信任。

场景概述

在设计素材行业,传统的商业模式通常由创作者提供内容并上传至平台,平台负责销售和分发,同时负责版权等问题,用户通过付费获取平台的高质量素材资源,平台则根据销售情况与创作者分成。而在AI的技术推动下,平台会提供一系列的AIGC工具,帮助用户实现图片生成、放大、扩展、风格转换等效果,同时收取使用这些功能的费用。

图片来自卓特视觉官网

方案介绍

火山引擎veImageX基于字节跳动的图像领域最佳应用实践,提供端到端的一站式图片解决方案。

整体架构

一套方案解决上传、存储、图像处理、分发、解码、QoS&QoE监控的全链路方案,覆盖从内容生产端到图像消费端。

veImageX的服务端具备强大的实时处理能力,不仅包含了裁剪、缩放、格式转换等基础图像处理功能,还提供了画质评估、画质增强、智能裁剪、超分、盲水印等丰富的AI组件能力。

卓特视觉接入了veImageX的哪些能力

一、画质评估

画质评估组件支持模仿人类对于图像的视觉感受,从而对图像的各方面进行评分。评分指标有大众美学评分、噪声强度评分、纹理丰富度评分和色调均衡程度评分等。veImageX通过抖音集团内部的大量线上业务实验发现,图片画质优劣对点击率、停留时长等消费类指标有正相关影响,间接影响用户收益指标。卓特视觉通过画质评估组件,对线上的海量素材文件进行了广泛的评估,在网站尽量展示评分较高的图片,并在用户查询图片时,优先推荐同类型中评分高的图片。这一系列举措不仅提升了网站整体的图片质量及用户的满意度,还促进了业务增长,并获得了良好的用户口碑。

二、智能裁剪

智能裁剪是 veImageX 提供的全新图片裁剪附加能力,支持对输入图片进行指定尺寸变换,能够自动判断主体区域的位置,并支持自动化适配不同尺寸图片内容的裁剪。卓特视觉的用户分布在各行各业,用途包含宣传页、海报、杂志、电商平台、户外广告等,对图片的尺寸和表现侧重点都有个性化的要求,卓特视觉通过智能裁剪能力批量对原图进行裁剪,自动化适配用户对于不同尺寸的要求,同时确保在任何尺寸下,图片主体都能处于最佳位置。快速高效满足客户需求的同时,也拓宽了产品的适用边界。

三、存储

卓特视觉目前拥有超过5.6亿的正版素材,并且数量仍在持续高速增长,占用的存储空间日益庞大,成本也与日俱增,veImageX提供存储服务,同时支持根据上传时间变更存储类型的智能降冷策略,有效节省存储的成本。此外, 为了进一步帮助企业降低存储成本,veImageX通过自研BVC算法,提供全球领先的极限图片压缩比,对比JPEG压缩率提升8-10倍,在不降低图片质量的前提下,在保持图片清晰度基本不变的情况下,单张图片体积节约超过70%,可以实现显著的成本节约。

四、分发

veImageX作为端到端的图片解决方案,除了强大的AI图像处理能力,还提供存储和分发能力,在分发阶段,veImageX利用自建 CDN 节点进行灵活的智能调度,为国内外用户提供极致的观看体验。卓特视觉通过使用veImageX的高效分发方案,确保了全球用户访问的快速和稳定。

设计素材行业其他需求的能力

一、智能生图能力

用户在平台可能会遇到不符合设计标准的素材,不仅影响了创作效率,同时也会影响平台的口碑,因此,引入AIGC智能生图能力显得尤为重要,当现有素材无法满足需求时,可以通过AIGC快速生成。veImageX结合豆包的AI生图方案,最新上线了智能生图能力,封装了文生图、图生图一站式解决方案。支持将豆包生成的图片进行后处理,包含存储、压缩、二次处理、超分辨率、盲水印、裁剪、适配、分发等。典型功能如下图展示:

  • 文生图场景

  • 图生图场景

此外,veImageX智能生图能力还支持桥接第三方模型文生图、图生图服务,直接对接veImageX进行上传、编码、存储与管理,并支持完善的后处理服务。大大扩展了方案的灵活性。

二、智能审核

设计素材平台如果遇到涉黄、涉暴的素材上传,不仅涉嫌法律风险,而且对平台的品牌可信度将会是极大的折损,而面对每天数以十万计的素材,人工审核显然无法满足。veImageX 提供了图片智能审核功能,支持分类型智能检测图片中涉黄、涉暴恐、违法违规等十几种禁用行为,并返回最终识别结果。识别并预警用户上传的不合规图片,协助平台快速定位处理。

三、盲水印

在设计素材行业,素材的版权归属一贯容易产生争议。在版权意识和版权法逐渐完善的今天,稍有不慎可能就会产生法律纠纷。veImageX兼顾版权追踪和图片美观,支持对图片添加盲水印,同时支持对图像提取盲水印信息,方便追踪溯源。盲水印是一种肉眼不可见的水印方式,可以在保持原图图片美观的同时,又可以保护资源版权。对原图进行解码后,可以得到盲水印信息证明图像的版权归属,避免未经授权的复制和拷贝而造成的版权问题。

四、超分辨率

设计素材平台的用户在制作海报、广告牌等场景时,往往需要对原始素材进行放大,同时需要保持放大后图像的清晰度,即所谓的“无损放大”。veImageX支持将图像做2-8倍智能放大,并保持处理后图像的清晰度,使图像更加清晰、锐利、干净,给用户带来良好的视觉体验。

五、智能背景移除

用户在使用平台提供的设计素材时,如果发现图片中的主体部分符合需求,但是为了配合使用场景、符合品牌调性等原因,需要对原始图片中的背景进行移除。veImageX的智能背景组件,支持保留图像的主体并抠除其复杂的背景,从而生成保留主体的透明底图片。veImageX提供了多种图像处理模型,支持精细化图像主体轮廓处理,可大幅度提升图像处理效率,降低人工成本。

结语

在AI的技术浪潮中,传统的设计素材行业正在向AI时代迈进,以满足客户日益个性化、精细化、创意化的诉求。火山引擎veImageX凭借夯实的技术底座和强大的AI能力,与卓特视觉携手合作,共同迈入设计素材行业AI新纪元,助力我国视觉版权服务市场的蓬勃发展。

了解更多:www.volcengine.com/product/ima…

半空:LLM 辅助的 Go2Rust 项目迁移

试想一下:将一个 Golang 项目(大象)改写为(装进) Rust(冰箱) 总共需要几步?

“Gopher in 冰箱” by DALLE3

背景

当 Rust 语言为我们展示出在「性能」、「安全」、「协作」等方面诱人的特性之后,却因为其陡峭的学习/上手曲线拒人千里之外。是否存在一种科技,能够帮助我们的同学在语言学习项目迁移上完美并行,最终真正将 Rust 项目迁移这个看似美好的荆棘之果转变为触手可得的「低垂果实」呢?

为了将美好的愿望转变为实际,我们结合 LLMs 做了一些尝试,利用 LLMs 在编程语言上体现出的「涌现」能力,设计了一套基于 LLMs 的应用开发基座(ABCoder),在这个基座之上进一步演进出了我们本篇的主角:「半空」。

ABCoder 是字节内部一个编程向 LLMs 应用开发基座,包含自研的 LLMs 原生解析器、工具(Tools)以及工作流(Workflows),对编程项目本身进行深度解析、理解和压缩,并将其制作为源码知识库(Source code as Knowledge),之后利用这类知识库实现对 LLMs 推理过程中所需上下文进行补齐,从而构建出高质量、低幻觉、稳定的编程类 LLMs 应用。有关 ABCoder 更多的介绍可以参考这里

半空

TL;DR 传送门

按照 ABCoder 的设想,让 LLMs 理解编程项目的入口就是结合对项目的解析、理解、压缩后的知识关联和构建,这对于一个轻量化的应用来说可能足够(ABCoder 当前已经能够实现将一个标准 Hertz 项目“转述”为一个 Volo-HTTP 项目),但对应到实际场景中的业务项目来说(增加大量业务属性且复杂度更高),要想真正让 LLMs 完整理解整个项目,并且在有需要的时候让 LLMs 完整的将整个项目“转述”为另外一个语言的项目时我们就需要对我们的解析、理解、压缩、应用流程进行更加细粒度的设计和优化了。

「半空」主要讨论的就是对于复杂项目的理解提升辅助 LLMs 渐进式多轮迭代构建出一个复杂项目的可行性。核心需要解决的是因为项目规模提升所带来的复杂度以及上下文规模提升和 ABCoder 所制作的对应知识库知识密度跟不上的矛盾。

内核简述

罗马不是一日建成的,参考软件工程标准的项目迭代方式,迭代一个越庞大的项目,引入的标准作业流程和所花费的迭代周期和人力就越多。ABCoder 要想深刻的解析并理解一个大型项目,一口永远吃不成一个胖子。

好消息是构建一个复杂项目的过程是有迹可循的的,ABCoder 需要做的其实就是逆着项目构建的路径,反向解析出项目构建过程中涉及到的不同粒度的知识库。

之后将这些知识库输入 LLMs 驱动的 Workflows,通过构建渐进式的多轮迭代流,将原来的项目以任意编程语言又输出出来,基于对知识库的持续构建,甚至实现为其他语言的项目:语言翻译

意译 or 直译?

相较于给 LLMs 一段代码,让他直接翻译为另外一个语言(直译),「半空」所做的类比下来更像是:帮助 LLMs 理解代码,之后经过抽象和设计结合我们希望它采纳的知识,重写出另外一个语言实现的版本(意译)。

理解和设计

按照 ABCoder 的通用处理流,一个任意庞大的项目我们几乎都可以通过解析、级联压缩的方式构建函数、方法、结构体、变量的语义化映射。但仅仅通过这些散落的信息 LLMs 是没有办法高效的建立一个对这个项目系统深刻的理解。因此我们在做 LLMs 辅助的项目文档梳理应用的时候,就已经开始下意识的做一些单元聚合工作了:通过将某个包(文件/模块)中的函数、方法、结构体、变量语义化含义进一步抽象,得到关于这个包(文件/模块)的语言和框架无关的高层次语义化抽象,按照这个思路,我们可以自底向上抽象,到最终项目维度。

举个直观的例子,对于 Hertz 的项目,任意一个 Hertz 项目在项目维度都能够抽象为形如:这个项目是一个基于 HTTP 应用框架的应用,它或许注册了/a/b/c 路由 (Route)的 GET 方法(Method),关联了某个对应的逻辑(Handler)

仔细分析这个抽象,尝试对其中蕴含的细节进行总结:

  1. 一个基于 Hertz 的 Golang 项目,在经过某个维度的抽象之后,丢掉了大量细节,留下了一些在当前维度的关键信息。在上述例子中,我们得到的抽象已经不关心这个项目具体采用的语言实现和具体涉及到的应用框架了,仅仅需要关注的是 HTTP 框架应用以及 HTTP 应用必备的信息:注册了某个路由,处理了某个业务逻辑。

  2. 通过这层抽象,我们可以将任意一个复杂项目映射出了一个最简单的迭代入口:启动一个 HTTP 应用框架,并注册处理某个 URL 的某个逻辑函数。

  3. 对整个复杂项目的理解过程被我们巧妙的转换为对一个项目自底向上的逐层抽象的过程,如果我们能将这个抽象过程做的足够清晰和准确,对于一个完成抽象的项目来说,我们反过来也得到了一个支持我们至顶向下层层细化的项目构建流。

  4. 理论上通过增加、减少、优化各层级抽象,我们就能不断提升对这个项目深度理解的效果。

多轮的抽象和迭代的本质是项目在不同维度上多语言实现和 ABCoder 抽象语义的不断对齐:

配合语言对应的知识库建设,按照标准抽象块(已归一化逻辑)进行知识检索,分层分模块持续迭代,填充核心逻辑,辅助业务完成项目构建。

实施和测试

当我们通过上述解析和抽象,得到了关于一个项目完整的理解知识,之后就可以至顶向下辅助 LLMs 逐层实现项目的渐进式迭代了。同样,接着上一小结里提到例子来说,我们在这层抽象上做的事情就是:

  1. 根据「HTTP 应用框架」匹配目标语言对应的知识,比如检索出 Volo-HTTP 库的知识(如果我们的目标是将这个应用实现为一个 Rust 项目),之后结合 Volo-HTTP 提供的框架初始化逻辑,拉起一个 Volo-HTTP 的项目
  2. 之后按照本层抽象剩下的描述信息,完成**「/a/b/c** 路由 **和对应处理函数」**的注册
  3. 由于本层抽象并不具备这个处理函数的详细描述信息,因此仅仅需要生成一个空实现的桩函数即可
  4. 之后我们所做的所有变成,二次确认完成了具体实现和对应语义化抽象的对齐

以上即是对一轮迭代核心流程的描述,完成本轮迭代之后即可开启下一层抽象的对齐。之后按照这个流程持续的迭代这个项目。

因为抽象本身会丢掉本层部分细节,而丢掉的这部分细节其实还是保留在抽象前的层级中的,对应迭代路径来说,上一层丢掉的细节一定会在下一层迭代中被补充回来。因此,通过多轮的迭代构建出来的项目,理论上也并不会丢失具体的实现细节。

每一层迭代后都会有一次人工介入时机 —— 即可以及时人工介入修改代码并反馈到后续的翻译轮次中,这也是「半空」的核心能力之一 —— 在这个切面上能够按需的扩展任意的软件测试解决方案,包括时下流行的:LLMs 辅助 UT 生成等技术。等到所有的修改和测试通过之后,即可开启下一层的迭代或者选择直接退出手动接管剩余的翻译工作。

交付内容

作为用户最为关心的部分,「半空」究竟在项目 Go2Rust 转换(存量 Golang 项目改写为 Rust)上帮助我们做到哪些事情呢?其实非常简单,好比将大象装进冰箱,「半空」辅助下的 Go2Rust 自动化迁移也是三个核心步骤:

  1. 打开冰箱门:基于 ABCoder 对存量 Go 项目完成系统解析,产出函数粒度的项目理解原料

  2. 把大象放进去:基于项目理解原料产出将该项目改写为 ****Rust 对应的项目设计文档

  3. 关上冰箱门:基于设计文档中指引的迭代顺序,全自动可控地,产出各层迭代代码

实际上,结合简介中的描述,聪明的小伙伴也许已经发现:「半空」作为一套通用框架,应用面其实并不仅仅局限在 Go2Rust 上,对于任意语言之间的相互转换逻辑上都是完全一致的,区别在于对语言特异性处理和特定语言的知识库构建。「半空」一期重点针对 Go2Rust 场景完成内场的适配和持续打磨,后续如果有对更多语言栈(Python2Go/Java2Go/...)的切换诉求也非常欢迎勾搭~

项目实战举例

一个使用「半空」做 Go2Rust 项目转换的示例

项目介绍

Easy_note 是 CloudWeGo 社区对外提供的一个基于 Hertz 和 KiteX 的实现复杂、功能覆盖度高的业务实战示例项目;其使用 Hertz 提供了若干 API 接口,并在接口实现中通过 KiteX client 发起对下游 KiteX Server RPC 接口的调用。

本次使用「半空」翻译的是其 API 模块,其主要功能列表如下:

  • 用户管理

    • 用户注册 (HTTP 接口 -> RPC 调用)
    • 用户登录 (HTTP 接口 -> RPC 调用)
  • 笔记管理

    • 创建笔记 (HTTP 接口 -> RPC 调用)

    • 查询笔记 (HTTP 接口 -> RPC 调用)

    • 更新笔记 (HTTP 接口 -> RPC 调用)

    • 删除笔记 (HTTP 接口 -> RPC 调用)

涉及到的 Hertz/KiteX 框架相关的核心能力如下:

  • 初始化 Hertz Server
  • 注册 Hertz 路由和 handler
  • 实现 Hertz 自定义中间件(JWT、服务发现)
  • 实现 Hertz 的 handler 逻辑
  • 使用 KiteX Client 调用下游接口

流程说明

从输入原始项目产出 ABCoder 理解知识原料开始,「半空」会结合函数粒度知识原料,自底向上完成整个项目的逐层抽象和理解,之后至顶向下完成重构设计的制定,同时确定项目渐进式构建顺序:从粗粒度 知识映射细粒度 知识映射到最后逐个 Package 的实现,最终完成 Golang 项目到 Rust 项目的渐进式构建(意译)。这个过程中项目构建进度完全由用户掌控,结合人工修改反馈辅助协同,推动项目完成 Go2Rust 迁移落地。

上图提到的 Golang AST / Rust AST 是 ABCoder 在分析仓库代码,将函数、方法、结构体、变量等定义以树形关联出来的数据结构体集合,是一个能够与项目一比一映射的 LLMs 原生 抽象语法树

设计阶段:Package 翻译顺序

根据 ABCoder 解析后的项目原料,「半空」自动化根据 Package 的依赖关系完成了使用 Rust 重构这个项目所需的设计文档的编写,自顶向下得到如下迭代顺序:

  1. "github.com/cloudwego/b…":项目的二进制入口和基础框架搭建
  2. "github.com/cloudwego/b…":HTTP 通用 handler 的实现
  3. "github.com/cloudwego/b…":HTTP 通用 router 的注册
  4. "github.com/cloudwego/b…":HTTP 业务 router 的注册
  5. "github.com/cloudwego/b…":HTTP 业务 handler 的实现
  6. "github.com/cloudwego/b…":请求下游的 RPC 封装
  7. "github.com/cloudwego/b…":通用/业务中间件具体实现

实施阶段:根据设计文档顺序逐步展开

  1. " easy_note/cmd/api "

对应 MR: github.com/cloudwego/b…

main package,主要实现了 HTTP server 的初始化、路由注册调用等能力

Golang 原始实现 「半空」意译效果
main() main()
customizedRegister() customized_register()
常量定义[本轮不实现,只mock] 常量定义[mock实现]
  • 结果评估

    • 目录:

      • 所有 main package 的内容,都生成到 Rust 项目的 /src/bin/main.rs下;后续支持细粒度的文件模块映射
    • 内容:

      • 翻译的函数内容逻辑,基本正确;会将函数的具体过程用顺序表示出来,便于进行修改
    • 错误:

      • Opentelemetry 相关的使用报错;原因:目前还没有注入相关知识;不影响正常逻辑,先注释掉
    • Mock:

      • Main package 会依赖其他包的内容,因此会将其他 package 下的内容进行 mock,确保可以正确编译,但是 mock 的内容不一定完全准确,会在后续迭代完成最终实现;具体 mock 内容可参考上面的示例
  • 修改记录

    • 对 main/init 中涉及 Opentelemetry 的代码注释掉
  • 优化方式

    • 通过补充内场 Opentelemetry 相关缺失知识可以进一步提升完备率和可编译度
  • 数据统计

    • 生成节点完备率=无需改造的节点/生成节点总数

      可编译度=1-修改的代码行数/生成的代码总行数

    • 生成节点完备率: 50%

    • 生成代码可编译度:73%

  1. " easy_note/cmd/api/hertz_handler "

对应 MR: github.com/cloudwego/b…

hertz_handler package 主要实现了一个 ping handler,用于处理 ping-pong 请求

Golang 原始实现 Rust 意译效果
Ping() ping()
  • 结果评估

    • 目录:

      • 所有 Golang cmd/api/hertz_handler包的内容,都生成到 Rust 项目的 /src/cmd/api/hertz_handler/mod.rs
    • 内容:

      • 翻译的函数内容逻辑完全正确
    • 错误:

      • Cargo.toml 里没有加入 "serde_json" 依赖,导致报错
    • Mock:

      • 没有尝试参考 hertz 去 mock 状态码和 utils.H,而是自行利用 volo-http 框架的能力完成响应返回
  • 修改记录

    • 增加 "serde_json"
  • 优化方式

    • 在 cargo.toml 知识里增加通用、常用的依赖
  • 数据统计

    • 生成节点完备率=无需改造的节点/生成节点总数

      可编译度=1-修改的代码行数/生成的代码总行数

    • 生成节点完备率:100%

    • 生成代码可编译度:95%

  1. " easy_note/cmd/api/hertz_router "

对应 MR: github.com/cloudwego/b…

hertz_router 包主要实现 Hertz 路由的总体注册逻辑,调用 idl 生成的路由

Golang 原始实现 「半空」意译效果
GeneratedRegister() generated_register()
Register()[本轮不实现,只mock] register()[mock]
  • 结果评估

    • 目录:

      • 所有 Golang cmd/api/hertz_``router包的内容,都生成到 Rust 项目的 /src/cmd/api/hertz_router/mod.rs
    • 内容:

      • 翻译的函数内容逻辑完全正确
    • 错误:

      • 没有正确地将下层依赖 pub 出来,而是直接使用了依赖路径
    • Mock:

      • IDL 生成的路由注册部分,将其 mock 出来
  • 修改记录

    • 将 "hertz_router/demoapi" mod pub 出来
  • 优化方式

    • 在生成代码后对新增内容做一次解析和关联
  • 数据统计

    • 生成节点完备率=无需改造的节点/生成节点总数

      可编译度=1-修改的代码行数/生成的代码总行数

    • 生成节点完备率: 100%

    • 生成代码可编译度:88%

  1. " easy_note/cmd/api/hertz_router/demoapi "

对应 MR: github.com/cloudwego/b…

hertz_router/demoapi package 主要实现了具体了路由注册(idl 映射)以及 Hertz 中间件的定义

Golang 原始实现 「半空」意译效果
Register() register()[路由注册有问题,需要 check & 修改]
rootMw() root_mw()[包含了中间件里的 mock 实现]
mw 定义 mw 定义
CreateUser[本轮不实现,只mock] create_user[mock]
  • 结果评估

    • 目录:

      • 所有 Golang cmd/api/hertz_``router/demoapi包的内容,都生成到 Rust 项目的 /src/cmd/api/hertz_router/demoapi/mod.rs
    • 内容:

      • register(): 路由注册的逻辑对应上了,但是实现不对;生成的路由没有和原始的路由一比一映射成功,是根据函数的描述自行生成的路由:需要用户手动将路由修改正确,参照生成的用法很快就可以实现

      • root_mw():

        • 能够以注释的形式描述出来 root_mw 里所需要做的内容,但是没有正确实现。因为 volo 里没有这样把多个中间件组成一个切片的操作:需要用户自行补充实现
        • 没能实现 recovery、RequestId、Gzip 的中间件逻辑;主要原因是无法推测出这些功能在 rust 里的实现方式:需要用户自行补充实现
      • 其余的中间件均正常

    • 错误:

      • register() 的路由注册逻辑不对,优化思路如下:

        • 这部分是 IDL 映射的内容,本身就被拆的比较细;后续会做框架之间的 IDL 映射
        • 增强函数的细节描述
    • Mock:

      • Mock 实现了所有的handler内容,这部分没什么问题
  • 修改记录

    • 对路由逻辑进行重新梳理和注册
    • 对 recovery/request_id/jwt 中间件的逻辑进行实现(ps. 示例还未实现,暂时注释掉)
    • 删除/添加一些依赖信息
  • 优化方式

    • 增强细节逻辑的总结&实现能力
  • 数据统计

    • 生成节点完备率=无需改造的节点/生成节点总数

      可编译度=1-修改的代码行数/生成的代码总行数

    • 生成节点完备率:62%

    • 生成代码可编译度:76%

  1. " easy_note/cmd/api/hertz_handler/demoapi "

对应 MR: github.com/cloudwego/b…

  • Hertz_handler/demoapi package 主要实现了具体的 HTTP 接口实现,下面使用 "create_note" 作为展示
Golang 原始实现 「半空」意译效果
CreateNote() create_note()
SendResponse() send_response()
rpc CreateNote[本轮不实现,只mock] rpc create_note[mock]
ErrNo[本轮不实现,只 mock] ErrNo[mock]

handler 这轮翻译完,出现的代码报错较多,主要原因如下:

  1. 是代码量本身比较大,同样错误报错多次

  2. handler 里涉及了一些业务逻辑以及业务在 golang 里的特定的用法,LLMs 不能很好转换

以下都以"create_note" 接口为例,进行结果评估

  • 结果评估

    • 目录:

      • 所有 Golang cmd/api/hertz_``handler/demoapi包的内容,都生成到 Rust 项目的 /src/cmd/api/hertz_handlers/demoapi/mod.rs
    • 内容:

      • create_note(): 能把原 create_note 的逻辑按顺序进行实现,包括 获取参数、发起调用、返回响应等
      • send_response(): 基本能实现出原接口的含义,但是错误较多,图里展示的是手动修改过的
    • 错误:

      • create_note(): 逻辑是正确的,主要有以下错误

        • mock 的结构体,没有带 #[derive(Debug, Deserialize)]需要用户补
        • send_response() 的调用无法对齐,一直报错
        • 获取请求上下文的时候,可能会有误传参
      • send_response(): 整体逻辑是对的,但是不会用 volo-http 的写响应方式

    • Mock:

      • 直接 mock 的内容基本都正确不需要修改
      • 没有去对二级依赖进行mock,导致会有些编译错误;例如,当前接口依赖了 "rpc/create_note",其又依赖了 "NoteDate" 类型,这个没有进行实现
  • 修改记录

    • send_response 的逻辑重新实现
    • 修改 handler 的调用逻辑,以及一些 ctx 上下文传参的问题
    • 增加/删除一些依赖信息
  • 优化方式

    • 补充 volo-http 的请求/响应相关操作示例,指导 LLMs 生成更准确的 SDK 使用姿势
  • 数据统计

    • 生成节点完备率=无需改造的节点/生成节点总数

      可编译度=1-修改的代码行数/生成的代码总行数

    • 生成节点完备率:14%

    • 生成代码可编译度:88%

至此,我们就完成了 "github.com/cloudwego/biz-demo/easy_note/cmd/api" 这个 moudle 的全部翻译,用户在 check 完整个项目后,即可以编译 & 运行项目。

总结

整体意译效果说明

  • 函数翻译完备性

完备性说明:完全无需人工介入的函数统计为完备函数

package 生成函数的个数 完备函数的个数 完备率
easy_note/cmd/api 4 2 50%
easy_note/cmd/api/hertz_handler 1 1 100%
easy_note/cmd/api/hertz_router 1 1 100%
easy_note/cmd/api/hertz_router/demoapi 13 8 62%
easy_note/cmd/api/hertz_handler/demoapi 7 1 14%
  • 代码可编译度

可编译说明:相对于整体生成代码行数,人工介入修改的代码行数占比,需要修改的代码越少,可编译度越高

package 生成函数的行数 人工修改的代码行数 可编译度
easy_note/cmd/api 106 28 73%
easy_note/cmd/api/hertz_handler 19 1 95%
easy_note/cmd/api/hertz_router 9 1 88%
easy_note/cmd/api/hertz_router/demoapi 173 38 76%
easy_note/cmd/api/hertz_handler/demoapi 254 30 88%

整体上,通过知识库的持续建设和关键知识的补齐,「半空」在完备性和可编译度上也会随之持续提升。

语言学习和项目迁移

在这个过程中,结合「半空」为我们生成的 Rust 项目设计文档,从整体项目的角度出发,逐步对每个包进行深入理解、翻译与确认。这一过程条理清晰、循序渐进地将一个 Golang 项目从零构建为一个 Rust 项目。同时,我们一同参与项目构建的每一个迭代,「半空」每一个迭代生成的代码完全遵循内场和业内 Rust 项目编写的最佳实践,这不仅帮助我们深刻理解整个项目,同时也为学习一门新语言提供了极大的支持。通过这种逐步渐进迁移的方式,我们能够不断深入学习并掌握 Rust 语言及项目本身,最终成功完成项目的转型。

ByteHouse技术详解:基于OLAP构建高性能GIS地理空间能力

在数字化时代,地理空间分析(Geospatial Analytics)成为辅助企业市场策略洞察的重要手段。无论是精准广告投放,还是电商物流的效率优化,都离不开对地理空间数据的查询、分析和可视化处理,以便助力企业更好决策。

以一家连锁咖啡店为例:

该店想要在新城市开设分店,并希望确保新店铺的位置能够最大化利润。

首先,商家通过收集新城市的地理数据,包括人口分布、交通流量等,建立了一个详细的地理信息数据库。然后,商家利用空间数据分析工具,对这些数据进行了深入分析。

通过人口分布数据,商家发现新城市的一些区域人口密集,潜在顾客群体较大。同时,交通流量数据显示,某些区域的交通流量较大,意味着这些区域的顾客流动性较高,有利于店铺的曝光和吸引顾客。

此外,商家还分析了同行情况竞争对手的位置,以避免在已有众多同类型店铺的区域开设分店。空间数据分析帮助商家识别了那些既有足够潜在顾客,又相对较少竞争者的区域。

基于这些分析结果,商家最终确定了新店铺的位置。开设分店后,由于选址精准,店铺迅速吸引了大量顾客,销售额和利润均实现了预期目标。

以上案例离不开对地理空间数据库的支持。一些传统的地理信息系统数据库具备丰富的地理空间对象结构、成熟的空间索引能力,在导航、旅游、智能城市等典型应用场景中被广泛使用。

但随着实时分析报表等OLAP市场的扩大,地理空间分析也作为新的增值特性被业界几大OLAP主流产品所推广。OLAP+GIS能力在满足用户地理空间数据分析的基础上,还能在数据体量大、实效性要求高的情况下,满足业务高性能查询的需求。

作为火山引擎推出的一款OLAP引擎,ByteHouse近期发布了高性能地理空间分析GIS能力,为位置洞察、人群圈选等场景提供高性能地理数据分析服务。本篇内容将从技术实现角度,详细介绍ByteHouse如何集成GIS能力,并通过benchmark测试,展示ByteHouse与市场同类产品相比(ClickHouse、StarRocks、PostGIS、DuckDB)的性能情况。

应用场景和价值

位置洞察: 例如,在给定中心点的情况下,展示半径X公里内的圆内其他商家的同一商品的客流分布、经营情况等,有助于帮助商家客户洞察竞争对手情况,为定价策略和市场定位提供数据支持。

作战地图: 给定特定多边形,观察多边形内部商家的供给和客流量,为即时零售业务的配送优化提供决策依据。例如:生活服务的即时零售业务需要观察实时的配给。

经过我们对行业上相关业务场景的需求分析,商家或者销售代理等客户需要的是一种“对某个地理空间(多边形/圆)内的对象进行多种业务维度的分析和决策能力”。从整个执行链路来看,链路不仅含GIS的二维空间数据筛选,还有经典OLAP的聚合和关联分析等逻辑,因此可以总结出一层GIS+OLAP链路的抽象。从性能优化角度来看,OLAP优化器有必要去结合GIS的特性来进行适配,提升端到端的总体性能。

详细介绍

在关键性能层面,ByteHouse GIS在列式小批组织的数据结构上引入RTree等二维空间索引能力,并在CPU硬件层面实现了二维空间函数的性能优化,整体提升了端到端性能。在功能层面,兼容OGC标准,支持导入标准GIS文件格式,目前已支持超过50个主流的空间函数。更值得一提的是,我们还在探索在我们自研的优化器上结合GIS特性适配,如在高效的多表关联上适配GIS等,以及GPU硬件层面优化二维空间函数。

二维空间索引

回顾业务场景:给定一个查询窗口(通常是一个多边形或圆),返回包含在该查询窗口中的物体。

如果要提升查询性能,读取的数据量通常是比较关键的,那取决于:

1)数据的排序方式 2)数据读取的粒度 3)索引

社区ClickHouse数据组织

ByteHouse 是火山引擎基于开源 ClickHouse 进行了深度优化和改造的版本。 ClickHouse 社区版直接按照Order By latitude, longtitude里面的纬度进行排序,再按照经度排序。

因为经度上相距很远的数据可能被放到一个mark,而查询是一个多边形和圆,查询的模式和数据的组织不匹配从而造成严重的读放大问题,导致数据局部性较弱。

微信截图_20241226100633.png

ByteHouse空间索引:Google S2 + R tree

ByteHouse GIS 通过使用Google S2 [3]库将所有的经纬度点从二维转换转换成一维,并排序。排序后的经纬度点效果如下图:

图片来源:[3]

由于ByteHouse的数据是按照列式存储,相比于传统的行级别索引,我们会对S2排序后的经纬度数据,先按照小块粒度切分,再利用RTree来索引每个小块数据。这样,基于小块粒度的RTree索引的存储开销更小,加载和查询效率更高。给定一个查询的多边形或圆,RTree能快速索引到匹配的数据块。由于每个数据块内的经纬度数据是按照二维层面聚集,这样使得相邻的点在二维空间上更加紧密,数据局部性更好。

ByteHouse GIS索引结构

针对某个具体场景中给出的一个圈选范围,需要返回范围内的所有POI (Point of Interest)点。下面两幅图分别展示了传统经纬度排序方式(Order By latitude, longitude)和ByteHouse GIS索引排序方式(Order By point)的圈选效果。其中,图中黑色的框代表了所有数据块,红色部分代表了圈选命中的数据块。

从结果中看出,传统经纬度排序命中的范围会横跨很广的纬度,造成读取许多无用的数据。而按照ByteHouse GIS索引搜索出的数据块只集中在北京地域,正好满足圈选所需的最小数据块集合。

传统经纬度排序方式的搜索效果

ByteHouse GIS排序方式的搜索效果

兼容OGC标准

数据类型

按照OGC标准,新增7种几何类型,包括Point、LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, GeometryCollection。

存储层面上,传统GIS数据库(例如,PostGIS)将几何数据序列化为Blob类型,读取时需要额外花费反序列化的开销。而ByteHouse GIS则按照数值数组和列式方式存储,减少存储量、序列化和反序列化开销。

空间函数

功能上,ByteHouse GIS目前已支持超过50个通用的空间函数,下面表格列举了几大函数分类。另外,我们针对个别高频使用的空间函数进行了基于列式数据存储格式的性能优化。

微信截图_20241226100839.png

存量数据迁移

同时,ByteHouse GIS也支持常见数据格式的导入与导出,包括WKT、WKB、GeoJson、ShapeFile、Parquet、CSV和Arrow等文件格式。

Benchmark 测试

标准NYC Taxi数据集

为了说明性能效果,我们基于两个关键的 GIS 函数,使用 NYC Taxi 数据集,选取纽约的 3 个地理区域,将ByteHouse、ClickHouse、StarRocks、PostGIS、DuckDB进行了性能对比(以上对比的版本参照发文日期的最新版本)。

在本次测试中,我们选取了两个关键的 GIS 函数:ST_DistanceSphereST_Within;并使用 NYC Taxi 数据集(Size:21GB;条数:169,001,162),数据集将纽约拆分成多个地理区域(比如 Brooklyn,Manhattan),本实验选取其中 3 个不同大小的地理区域(按照过滤度区分:zone 1、zone 2、zone 3)进行了性能对比。

  1. ST_Within 函数性能对比:在 ST_Within 函数的测试中,从查询延迟来看,OLAP引擎的整体查询延时低于1s,由于二维空间索引和向量化的数据处理方式,ByteHouse查询延时最低;当前版本的DuckDB由于没有空间索引,同时采用了BLOB的存储方式,数据扫描和反序列化开销比较大,查询性能不好;采用行存的PostGIS在大范围搜索的情况下(zone3),虽然有索引加持,依然会有较重的读放大,查询延时超过6s。从每秒吞吐量来看,ByteHouse通过索引降低了数据读取和反序列化开销,展现出明显优势,其次为PostGIS,在小范围搜索(zone1和zone2)情况下表现优秀。

ST_Within函数性能对比

ST_Within每秒处理空间查询数

  1. ST_DistanceSphere 函数性能对比:在 ST_DistanceSphere 函数的测试中,在处理相同数据集和查询时,ByteHouse具备二维空间索引过滤和向量化计算的优势,性能控制在0.1s以内。ClickHouse和StarRocks同样具备较好的0.1s-1s内的较好性能表现。

ST_DistanceSphere 函数性能对比

基于标准数据集的测试结果来看,对比传统的PostGIS:

  • ByteHouse GIS将OLAP和GIS结合了起来。在OLAP层面,ByteHouse对比PostGIS已经有计算优势。
  • 在GIS层面,空间数据对象按照列的方式存储,而非序列化成字节数组,在存储上能够做到更加紧凑并节省空间,在计算上能够充分发挥向量化的优势。
  • 特别是在空间函数层面,可以利用硬件的并行化能力提速。

对比社区ClickHouse:

  • ByteHouse GIS兼容OGC标准,场景上能够水平替换之前PostGIS的场景。
  • 另外,空间索引能力可以大大减少ClickHouse的读放大的现象。
  • 还有,ByteHouse自研的优化器同样具备适配GIS特性的能力。

业务数据集

在电商场景中,ByteHouse GIS能力不仅满足平台商家运营快速分析商家经营状态、管理商家的需求,还将数据读取量减少超过50%,进一步降低了磁盘IO以及计算带来的CPU开销。

总结

本文具体拆解了ByteHouse GIS能力的技术实现方案,并将ByteHouse、ClickHouse、StarRocks、PostGIS、DuckDB五款数据库产品的性能进行分析和比较。

结论总结如下:ByteHouse在ST_DistanceSphere 函数及ST_Within 函数的查询延迟低于其他产品,查询吞吐量更高,具备比较明显的性能优势。

需要注意的是,性能测试结果取决于多个因素,在实际应用中,需要综合考虑各种因素,如数据规模、可扩展性、易用性、稳定性、安全性以及是否需要与其他系统集成等其他因素进行综合选择,并对数据库进行合理的配置和优化,以获得最佳的性能表现。

对于专注于地理空间数据分析的项目,PostGIS能提供了全面的地理空间功能支持,是一个比较好的选择。然而,如果地理空间数据只是大数据分析的一部分,且如果性能是首要考虑因素,那么ByteHouse、ClickHouse、StarRocks、DuckDB是合适的选择,其中ByteHouse GIS 功能不仅提供了高性能的地理空间分析能力,还具有易于使用、实时分析和云原生等特点,这使得企业可以更灵活、更高效地利用地理空间数据。

参考

  1. PostGIS: postgis.net/
  2. OGC: www.ogc.org/standard/sf…
  3. Google S2: s2geometry.io/
  4. Geos: libgeos.org/
  5. clickhouse.com/docs/en/sql…
  6. Cuda: developer.nvidia.com/cuda-toolki…
  7. github.com/rapidsai/cu…
  8. github.com/arctern-io/…
  9. halfrost.com/go_spatial_…

ROG:高性能 Go 实现

本文根据字节跳动服务框架团队研发工程师在 CloudWeGo 技术沙龙暨三周年庆典中演讲内容《ROG——高性能 Go 实现》整理。

作者|不愿意透露姓名的小刘市民

ROG 之缘起

ROG 的诞生是因为我们一部分业务使用 Rust 重写之后,获得了非常好的收益,比如 AVG、CPU、MEM、P99,这些数据表现非常好,大约节省了接近 50%的 CPU,内存大大降低。

这个性能数据让人眼红,因此团队考虑既然 Rust 有这么好的性能,我们有没有办法提升一下用户在 Go 上的性能?

图片

在和一些用户的对接中我们发现,让用户把 Go 业务通过 Rust 重写,难度其实非常大。很多用户会抱怨 Rust 的一些问题让他们很痛苦,比如,Rust 生命周期太复杂,泛型系统太复杂,报错看不懂,编程速度慢等等。因为这一系列问题,所以让用户把原来的 Go 项目通过 Rust 重写,对于用户来说是很难推动的事情。

于是,我们就有了一个大胆的想法,如果我们可以像使用 Rust 那样的编译技术去生成性能更好的可执行文件,同时使用 Rust 重写 Go 的 Runtime 和 GC 这两个核心组件,再通过几乎零开销的 FFI(Foreign Function Interface) 方式来支持 Rust 和 Go 之间的互调,是不是可以让用户 Go 的源码也能达到接近 Rust 的性能。这就是我们的初衷,因此有了 ROG 这个项目。

ROG 进展

我们目前测试了一些简单的场景,比如快排和二分、Simple Lisp。这些都是通过 time 命令来计算两个二进制文件执行所需要的时间。目前在快排、二分上,Go 的执行需要 5.97s,ROG 的执行需要 4.12s,在 Simple Lisp 这个项目上,Go 需要 8.17s,ROG 执行只需要 7.09s。

图片

从以上几个基本数字来看,在一些简单的场景下,ROG 会比 Go 性能好很多。但这只是一些非常简单的 case。如果面对一些非常复杂的 case 呢?比如在复杂的微服务场景下,ROG 会有怎样的性能领先?ROG 在上个季度刚好能够支撑 Kitex Benchmark 跑起来,目前我们完成了一次压测。

图片

我们使用 Kitex 官方的 Benchmark 工具完成了简单的 RPC 调用测试(github.com/cloudwego/k… 100,测试包大小 1024kb 的体积。在这个测试中,Go 的 QPS 可以达到 27W,ROG 28W。虽然 ROG 的 QPS 只比 Go 领先了一点,但是 P99 上有很大的提升。我们在测试过程中发现了 ROG 还有很多可以挖掘能力,只是还需要进一步优化。

架构设计

通过刚才几个性能场景测试,我们发现 ROG 相比 Go 在不同的场景下,多多少少有一些领先。但是为什么 ROG 相比于 Go 会有这样的领先呢?早期我们其实也经历过 ROG 测试结果比 Go 还差 50%的状态。所以想先给大家介绍一下我们的设计架构。

图片

从图中可以看出,首先会有一个 ROG 的前端来处理用户的 Go 源码,在前端经历 Parser 解析后生成 AST(Abstract Syntax Tree),做符号解析,每个函数,每个类型的符号。然后进行类型检查,分析出函数的签名以及每个变量的类型。这是一套非常常见的前端处理流程。

在经历这个过程之后,会产生一个中间语言叫做 MIR(Rust's Mid-level Intermediate Representation),之后会基于 MIR 去做一些前端时的优化,比如编译时计算、常量传播计算、逃逸分析(能够分析出哪些变量应该被逃逸到堆上去)、Inliner、SROA,以及对于特定 Go 函数的优化。

在这些优化算法处理之后,会生成一份 LLVM IR(Intermediate Representation),之后把它交给 ROG 后端。ROG 后端是我们自己魔改的一个 LLVM 版本。在 LLVM codegen 阶段我们给每个函数插入了一些对应的 Stack Check 以及对应的 STW(Stop The World) Checkpoint 指令,同时生成相应的 GC Barrier。

优化好之后就生成一份比较高质量的二进制代码了。这是对于 Go 语言的处理,而对于 Go 的 Runtime & GC 这部分,我们基本上完全是重写的。通过 Rust 重写之后,我们把这些代码通过自己维护的一个 Rust 版本去构建、打包好,调成对应的 LLVM 文件,最后和用户的 Go 代码连接起来,形成一个最终的二进制文件。这就是我们的编译流程。

收益来源

这个编译架构为什么相比 Go 或多或少有些性能优化呢?有哪些领先点?其实领先点主要来源于三个部分。

图片

第一部分,编译优化。因为 ROG 利用了 LLVM 积累多年的编译优化算法,能够生成一些性能更好的代码,而 Go 的编译优化会为编译速度做出一定牺牲。

第二部分,ROG 提供了跨语言 LTO(Link Time Optimization) 以及 FFI,通过几乎零开销的方式调用 Rust 提供的方法,因此在一些需要更高性能的场景,用户可以使用 Rust 开发,由 ROG 进行编译并进行调用。而 Go 对于 FFI 会使用 CGO,并且 CGO 会存在一些 overhead。

第三部分,Runtime&GC。ROG 完全使用 Rust 重写,再通过上面提供的 FFI 来保证调用的性能,而 Go 的 Runtime & GC 则是完全使用 Go 原生实现的。单纯从语言的表达能力上限来说,Go 远不如 Rust,所以如果我们通过 Rust 来重写 Runtime & GC 这两部分组件,理论上会比 Go 拥有更好的性能。

面临的挑战

介绍完性能来源之后,可能很多人会有疑问,貌似我们的主要性能受益都是来自于 LLVM。LLVM 本身优化已经做得很好了,我们做的是不是就是非常简单地把一个 Go 源码翻译到 LLVM 就行了呢?

其实整个事情并没有那么简单,在这一年里,我们踩过非常多的坑。以下举几个简单的例子。

Go Runtime

如果大家之前了解过 TinyGo,就会发现 TinyGo 的思路和 ROG 非常接近——TinyGo 也是把 Go 的源码给翻译到 LLVM。我们可以回想下在使用 TinyGo 的时候遇到过什么问题。

首先,TinyGo 需要用户手动通过 runtime.Gosched 这个函数来进行协作调度,所以它对用户代码是有影响的。如果用户没有在关键的地方去插入这个函数调度,会对它的调度产生影响。另外,TinyGo 本身也不支持多线程,并且缺少相应的 channel timer reflect 等 lib 的支持。

而 ROG 把这些问题都解决了,ROG 会在编译阶段插入代码,完成协作式调度,并且 ROG 设计的本身也是为了高性能,所以自然会对多线程进行支持,并且 ROG 对于 channel timer reflect 全部都重写。对于我们来说,解决 TinyGo 的不足的过程也相当艰难,毕竟重写整个 Runtime & GC 等是一个非常大的工作量。

图片

Safety FFI

假设如果我们要在 Go 提供 FFI,当用户写出这样的代码会发生什么事情?

图片

左边这张图是用户写的一份 Go 代码,里面有函数。rog_test(a *int32) 这个函数可能就是 FFI 提供的一个外部函数。如果用户直接去调用这个外部函数,而 rog test 本身是由 Rust 实现的,如右图,当我们写出这样的代码的时候,会发生什么事情?

因为 rust_tup 被 Rust Allocator 管理,Go GC 无法扫描到这个变量,所以这个变量 a 也无法被 GC 扫描到,而 “a” 这个变量是被 Go 的 Allocator 管理的,所以如果  a 无法被 GC 扫描到,那么 a 就会被 free 掉。但是这个时候,`` rust_tup 仍然会持有变量 a 的指针,在 Go 那边相当于是一个对外内存引用了 Go 的一个对象,但是因为 Go 扫不到这个对象,所以这个对象就被 free 掉了,但是对外内存仍然引用这个指针。

当我们提供 FFI 的时候,很有可能会面临这样的情况。这种情况该怎么处理?在 ROG 这边,我们就会通过一个模改的 Rust 编译器,提供一个 Managed Chekcer 去限制用户写出这样的代码,在编译器阶段保证用户不会写出这样的代码,保证 FFI 的安全性。这是 ROG 解决这个问题的思路。

Roadmap & 未来规划

CGO

目前 ROG 虽然能跑过 Kitex Benchmark,并在内部一些服务上做了测试,但它仍有很多功能需要改进,比如 CGO。CGO 是 Go 语言用来提供 FFI 的一种方案,但 ROG 的 FFI 是通过一种非常简单粗暴的方式提供的。目前 ROG 的 FFI 需要用户手工去标记 ROG,写上 rog:linkname 标记。这样我们在链接时才能链接上对应的符号。而 CGO 可以让用户简单的直接在 Go 文件的一个注释里写上 C 代码 import C,通过 import c 这个 package 来进行调用。

从 FFI 来说,CGO 会比现在的 ROG 方便很多,而且已有很多的开源库,以及字节内部一些服务,他们也在使用 CGO。我们在未来会支持 CGO,兼容 CGO 的表达方式,提供 ROG 需要的 FFI,生成 ROG 需要的 FFI 代码进行调用。

宏/编译器生成代码

Rust 宏在我看来是一个非常强的功能,因为 Rust 宏可以简单地在每个 Rust 进行标记,申明这个结构可以提供 Serialize(序列化)和 Deserialize(反序列化)这两种方法。这样就可以在编译时为它生成序列化和反序列化的代码,直接进行调用,而不需要像 Go 原始的 JSON,它有反射开销。而这种反射开销在需要高性能的序列化场景会有很大的性能开销。为了解决 Go 的反射开销,sonic 做出了 JIT 方案,而 JIT 对开发 sonic 的开发者来说,负担是非常大的。

图片

那么如果我们可以把 Rust 宏的理念引入到 ROG 中,会有什么样的体验?

首先,更好的开发体验。以 Kitex 举例,我们可以直接在编译时,通过宏为每个 IDL 生成 clint 的代码,这样就不需要用户去手动调用一些 main 去生成。

其次,更高效的序列化。像 JSON 这种序列化,我们可以通过类似 Rust 宏的方式在编译时生成好序列化和反序列化所需要的代码,直接调用,这样就可以省掉反射的开销。

图片

关于宏带来的案例,我们还在继续探索中,之后我们会基于宏做一些更好更方便的尝试。这也是我们对于宏的规划。但是不得不提到的是,宏的出现会对 Go 本身有一定的影响,因此可能只会通过注释的方式去提供,保证对 Go 语法的兼容性;并且只会在 JSON 等序列化这些地方进行一些替换,保证用户的开发体验不会受到影响。

开源

ROG 未来肯定会进行开源,并且贡献到社区。目前我们的想法是 2024 年先在公司内部完成一些业务的试用,能够稳定地上生态环境,并且能够取得一定的收益。在这些都稳定并且处理好 Go 本身大部分特性问题之后,才会将其开源。因此如果顺利,最早可能会需要等到 2025 年的第二季度才会去准备开源工作。欢迎大家保持关注~

-END-

项目地址

GitHub:github.com/cloudwego

官网:www.cloudwego.io

三周年演讲 PPT 下载链接:github.com/cloudwego/c…

NDSS 2025|Prompt泄露风险:抖音集团安全研究团队揭露多租户KV缓存共享漏洞

抖音集团安全研究团队和南方科技大学可信系统安全实验室合作的研究论文揭示了大语言模型安全领域服务框架的侧信道漏洞,利用多租户场景下的KV缓存共享机制精确恢复了用户提示词。本工作成果《I Know Wha
❌
❌