2024年4月

1f45bd1e8577af66a05f5e3fadb0b29
1f45bd1e8577af66a05f5e3fadb0b29

通过ORPO对llama进行微调

前言

ORPO是一种新颖的微调技术,它将传统的监督微调和偏好对齐阶段整合到一个过程中。这减少了训练所需的计算资源和时间。此外,经验结果表明,ORPO在各种模型大小和基准测试中都超过了其他对齐方法。 在本文中,我们将使用ORPO和TRL库来微调新的Llama 3 8B模型。代码可以在Google Colab(https://colab.research.google.com/drive/1eHNWg9gnaXErdAa8_mcvjMupbSS6rDvi?usp=sharing)和GitHub上的LLM(https://github.com/mlabonne/llm-course)课程中找到。

⚖️ ORPO

指令调整和偏好对齐是将大型语言模型(LLMs)适应特定任务的关键技术。传统上,这涉及到一个多阶段的过程:

  • 对指令进行监督式微调(SFT)以使模型适应目标领域
  • 像人类反馈的强化学习(RLHF)或直接优选优化(DPO)这样的偏好对齐方法,以增加生成优选响应而非被拒绝响应的可能性。

微信图片_20240423001958
微信图片_20240423001958

然而,研究人员发现这种方法的一个局限性。就是监督微调(SFT)可以有效地让模型适应特定领域,这也就是为什么需要偏好对齐阶段RLHF,扩大受欢迎输出和不受欢迎输出之间概率的差距。

image
image

SFT过程中,不受欢迎概率增加实证 from ORPO论文

2024年Hong和Lee提出的ORPO通过将SFT和RLHF统一为一个完整训练过程,为这个问题提供了一个优雅的解决方案。ORPO修改了标准language model的训练目标,将负对数似然损失与odds ratio(OR)项结合起来。这种OR损失对不受欢迎的输出施加了轻微的惩罚,同时加大奖励受欢迎的输出,允许模型同时学习目标任务并与人类偏好对齐。

91e1091deacae95fb17f1b6995b94c2
91e1091deacae95fb17f1b6995b94c2

ORPO已经在主要的微调库中得到实现,比如TRL、Axolotl和LLaMA-Factory。在下一节中,我们将看到如何使用TRL进行操作。



Dapr Outbox 是1.12中的功能。
本文只介绍Dapr Outbox 执行流程,Dapr Outbox基本用法请阅读
官方文档

。本文中appID=order-processor,topic=orders

本文前提知识:熟悉
Dapr状态管理

Dapr发布订阅

Outbox 模式

Outbox 模式
的核心是在同一个数据库事务中保存业务数据和待发布的事件消息,再由某个“定时任务”读取待发布的事件消息并发布事件(并删除数据库中事件消息)
相关文章:
.NET中实现Outbox模式的框架CAP,作者Savorboard
使用 dotnetcore/CAP 的本地消息表模式,圣杰

先在内部发布一个主题(topic)

要使用Dapr Outbox,在.NET中就是调用
DaprClient

ExecuteStateTransactionAsync(...)
方法(得先完成Outbox相关的配置!),调用此方法会完成事务操作(保存业务数据和待发布的事件消息)并发布事件消息。

string DAPR_STORE_NAME = "statestoresql";
var client = new DaprClientBuilder().Build();
var orderId = 1;
var order = new Order(orderId);

var bytes = JsonSerializer.SerializeToUtf8Bytes(order);
var upsert = new List<StateTransactionRequest>()
{
    new StateTransactionRequest(orderId.ToString(), bytes, StateOperationType.Upsert)
};

// 保存状态,并发布事件消息
await client.ExecuteStateTransactionAsync(DAPR_STORE_NAME, upsert);

public record Order([property: JsonPropertyName("orderId")] int orderId);
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: orderpubsub # 发布订阅组件
spec:
  type: pubsub.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestoresql  # 状态组件
spec:
  type: state.mysql
  version: v1
  metadata:
    - name: connectionString
      value: "root:mysecret@tcp(localhost:3306)/?allowNativePasswords=true"
    - name: outboxPublishPubsub
      value: orderpubsub
    - name: outboxPublishTopic
      value: orders

调用
ExecuteStateTransactionAsync(...)
方法时,此方法把请求转发给sidecar,sidecar会发布一个
内部主题
。所谓内部,就是供Dapr使用,用户不用操作;所谓主题(Topic)就是一个事件;此
主题格式
为:namespace + appID + topic + "outbox" ,假设appID=order-processor,topic=orders,则内部主题(Topic)名就是
order-processorordersoutbox
(namespace 是与k8s有关),此主题用于判断事务是否执行成功。

注:该内部主题(topic)默认和事件消息使用同一个Dapr
发布/订阅组件
,可以通过配置状态组件的元数据(metadata配置)字段
outboxPubsub
单独指定内部主题所使用的发布/订阅组件。相关配置请看
官方文档

主题内容

CloudEvent
格式,发布的事件数据如下(真正的待发布事件消息就是json中的data字段,后面就是读取的此值):

{
    "data":"{\"orderId\":1}",
    "datacontenttype":"text/plain",
    "id":"outbox-a53e45f3-d646-4e4e-bcbf-0692ec7b9dd0",
    "pubsubname":"orderpubsub",
    "source":"order-processor",
    "specversion":"1.0",
    "time":"2024-01-25T17:12:31+08:00",
    "topic":"",
    "traceid":"",
    "traceparent":"",
    "tracestate":"",
    "type":"com.dapr.event.sent"
}

有了事件的发布者,那事件的订阅者是谁呢?
appID=order-processor的Dapr sidecar实例
。可以是执行保存状态的sidecar程序,或者是appID=order-processor的其他sidecar。

在同一事务中保存状态和事件消息

  • 在内部主题(Topic)发布
    成功

    ,会在同一事务中保存状态和事件消息,也就是将方法
    client.ExecuteStateTransactionAsync(...)
    中的数据保存到数据库。id为
    outbox-a53e45f3-d646-4e4e-bcbf-0692ec7b9dd0
    的表示需待发布事件消息,id为
    order-processor||1
    表示状态数据。事件消息和状态数据保存在同一张表
    state
    中,在mysql中其表结构和数据如下所示。

  • 如果此内部主题(Topic)发布
    失败
    ,调用方直接抛异常,不会执行事务操作!
    state
    表不会有下面两条数据。

  • "eyJvcmRlcklkIjoxfQ=="既是状态数据又是待发布的事件数据;经过Base64解码,得到该值为json格式,即:
    {"orderId":1}

CREATE TABLE `state`  (
  `id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `value` json NOT NULL,
  `isbinary` tinyint(1) NOT NULL,
  `insertDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updateDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `eTag` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `expiredate` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `expiredate_idx`(`expiredate` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
id value isbinary insertDate updateDate eTag expiredate
outbox-a53e45f3-d646-4e4e-bcbf-0692ec7b9dd0 "0" 0 2024-01-25 09:22:14 2024-01-25 09:22:14 07884eed-eb5d-4887-8399-051c71206ed5
order-processor||1 "eyJvcmRlcklkIjoxfQ==" 1 2024-01-25 09:12:31 2024-01-25 09:22:14 3d1e368f-f6d8-4ccd-946d-c10090c7cc42

内部主题(Topic)的订阅者发布事件消息

数据库事务执行成功后,什么时候把事件消息发布出去呢?

事件消息
发布出去是在
内部主题(Topic)的订阅者
中实现的,具体如下:

步骤X

appID

order-processor
的sidecar接收到内部主题(Topic)发送的事件,然后通过查询判断id为
outbox-a53e45f3-d646-4e4e-bcbf-0692ec7b9dd0
的数据是否存在?

  • 如果存在,表示状态数据和事件消息都已保存在mysql中,则发布
    事件消息
    (事件数据就前面提到的data字段)。事件发布成功后,则删除id为
    outbox-a53e45f3-d646-4e4e-bcbf-0692ec7b9dd0
    的记录。
  • 如果不存在就直接退出,停止后续操作;事件的订阅者会多次收到订阅消息,即重复
    步骤X
    过程。

这里会有一个问题:接收到内部主题(Topic)后,状态和事件消息可能没有持久化到mysql(前面提到过,Dapr sidecar是先发布一个内部主题,再在同一事务中保存状态和事件消息)。所以获取状态执行以下重试策略。删除状态时也是此重试策略。

bo := &backoff.ExponentialBackOff{
    InitialInterval:     time.Millisecond * 500,// 初始间隔
    MaxInterval:         time.Second * 3,       // 最大间隔。重试时间超过此值时,以此值为准
    MaxElapsedTime:      time.Second * 10,      // 累计重试时间
    Multiplier:          3,                     // 递增倍数  
    Clock:               backoff.SystemClock,
    RandomizationFactor: 0.1,                   // 随机因子  
}

总结

Dapr Outbox 执行流程简单说就是:先发布一个内部事件,再执行保存业务数据和事件消息,内部事件的订阅者再发布真正的事件消息。Dapr轮询数据库中待发布
事件消息
是通过订阅一个内部主题(Topic)实现的。
因为状态保存和事件发布是在sidecar中执行,所以
业务代码和事件消息不在同一个事务中
!!!Dapr Outbox是把
业务的状态数据和事件消息在同一个事务中保存
,也就是代码
client.ExecuteStateTransactionAsync(...)
;并且状态数据和事件消息是保存到同一张表
state
中。

参考:

代码

Enable the transactional outbox pattern

outbox.go

Outbox issues

引言

在前几章我们深度讲解了单元测试和集成测试的基础知识,这一章我们来讲解一下
代码覆盖率
,代码覆盖率是单元测试运行的
度量值
,覆盖率通常以百分比表示,用于衡量代码被测试覆盖的程度,帮助开发人员评估测试用例的质量和代码的健壮性。常见的覆盖率包括语句覆盖率(
Line Coverage
)、分支覆盖率(
Branch Coverage
)、路径覆盖率(
Path Coverage
)等,不同类型的覆盖率可以帮助开发人员更全面地了解测试用例对代码的覆盖情况,从而改进测试策略和提高代码质量。

核心组件

本文介绍如何通过
Coverlet
在单元测试中使用代码覆盖率和使用
ReportGenerator
生成报表。

什么是
Coverlet

Coverlet

GitHub
上的开源项目,可为
C#
提供跨平台代码覆盖率框架,
Coverlet

.NET Foundation
的一部分,
Coverlet
收集
Cobertura
覆盖率测试运行数据,用于生成报表。

什么是
ReportGenerator
?

ReportGenerator
将由
Cobertura
生成的覆盖率报表转换为各种格式的用户可读的报表。

代码覆盖率实战

我们在之前的项目中创建了
Sample.Api

Sample.Repository
用作我们单元测试和集成测试的
Demo
项目,接下来我们用这个项目来做一下单元测试覆盖。

  • 创建
    xUnit
    测试项目
    然后看一下
    csproj

    XML
    文件
  <ItemGroup>
    <PackageReference Include="coverlet.collector" Version="6.0.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
    <PackageReference Include="xunit" Version="2.5.3" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
  </ItemGroup>

从上面可以看出
xUnit
的测试项目已经默认对
coverlet.collector
添加了支持

我们在讲解在单元测试中使用依赖注入时,用
Sample.Repository
做的单元测试,我们把之前的代码
Copy
一份,到新的测试项目,添加和讲解依赖注入时一样的配置。

然后添加对我们
Sample.Repository
的项目引用

NuGet\Install-Package Xunit.DependencyInjection -Version 9.1.0
NuGet\Install-Package Xunit.DependencyInjection.Logging -Version 9.0.0

image

可以看到我们项目基本成型了,但是
Copy
过来的代码还是我们
dotNetParadise.DependencyInjection
测试依赖注入时那个类库。

一个小 tips

鼠标点击我们的测试项目
dotNetParadise.CodeCoverage
->右键->同步命名空间

image

可以看到我们的命名空间已经被正确替换

同步命名空间功能

  • 使用时机: 你想要通过将文件拖动到新文件夹重构部分解决方案。

  • 操作原因: 你想要确保你的命名空间中的新文件夹结构保持最新。

开始统计

我们可以通过
CMD
命令进入测试项目类库的内

image

然后执行下面这个命令

dotnet test --collect:"XPlat Code Coverage"

"XPlat Code Coverage"
参数是与
Coverlet
中的数据收集器对应的易记名称。 此名称是必需的,但不区分大小写。 若要使用
.NET
的内置代码覆盖率数据收集器,请使用
"Code Coverage"

看一下执行完控制台输出

image

通过控制台反馈我们得知了,生成了一个附件路径在
~\TestResults\2515646c-5845-4bfb-ae14-7e0bf4617b84\coverage.cobertura.xml

作为
dotnet test
运行的一部分,生成的
coverage.cobertura.xml
文件输出到
TestResults
目录 。 该
XML
文件包含结果。 这是一个依赖于
.NET CLI
的跨平台选项,非常适用于不可使用
MSBuild
的生成系统。

image

简单解读一下
cobertura.xml
代码覆盖率报告,提供了关于代码单元测试覆盖率的详细信息。其中包含了各种指标和数据,如以下几点:

  • line-rate

    branch-rate
    分别表示语句覆盖率和分支覆盖率,均为 0.5,表示被测试覆盖的比例为 50%。
  • lines-covered

    lines-valid
    分别表示被覆盖的代码行数和总代码行数。
  • branches-covered

    branches-valid
    分别表示被覆盖的分支数和总分支数。
  • 在具体的类和方法级别,提供了各自的覆盖率、复杂度等信息,以及每行代码的覆盖情况(
    hits
    表示执行次数,
    branch
    表示是否为分支语句,
    condition-coverage
    表示条件覆盖率)。

生成报告

上面提到了
coverlet.collector
是用来收集单元测试的指标的真正的报告是用
ReportGenerator
生成。

第一步要将
ReportGenerator
NuGet
包安装为
.NET
全局工具

dotnet tool install -g dotnet-reportgenerator-globaltool

image

有很多种生成报告的方法如 命令行,添加
netconfig
等大家可以自行选择

下面用命令行来演示一下

导航到
coverage.cobertura.xml
xml 报告文件下

reportgenerator -reports:"coverage.cobertura.xml" -targetdir:"coveragereport" -reporttypes:html

输出:

image

查看测试报告:

我的路径:
TestResults\b5595744-2bb9-42ab-8c2e-60d544b4b04f\coveragereport

image

最后

通过创建测试项目并使用
Coverlet
进行代码覆盖率统计,我们可以得到详细的覆盖率报告。生成的
coverage.cobertura.xml
文件提供了各种指标和数据,如语句覆盖率、分支覆盖率等,帮助开发团队评估代码的测试覆盖情况。

随后,通过
ReportGenerator
工具生成报表,我们可以将覆盖率数据转换为易读的报表形式,帮助团队更直观地了解测试覆盖情况,进一步优化测试策略和提高代码质量。

通过以上实践,我们能够更有效地利用代码覆盖率工具,提升单元测试的质量和效率,从而构建更健壮的软件系统。


近日,Meta(原 Facebook)开源了他们公司的新一代大模型 Llama 3,虽然目前只放出了 8B 和 70B 两个版本,但是在评估结果上已经优于 Claude 3 Sonnet、Mistral Medium 和 GPT-3.5 等大模型。Meta 官方还表示,这些只是开胃菜,更强的 400B 参数的模型已经在训练中了,预计几个月后将和大家见面(开源与否尚不明确)。

说回上周的热门开源项目,最近基于 LLM 构建知识库的开源项目很火,但我一个都没收录。因为如果是本地起大模型效果不好,请求大模型的 API 又不免费,所以我找到了一个 OpenAI API 反向代理开源项目,可用来实现免费白嫖 OpenAI API。内容也是知识库的关键,Reader 能够将网页内容转化成 LLM 友好的文本。对于没有编程基础的小伙伴,这有一个 30-Days-Of-Python 的开源教程,学它!当然,学习之余也可以娱乐一下,比如试试‘无名杀’,这是一款类似于三国杀的开源卡牌游戏。

  • 本文目录
    • 1. 开源新闻
      • 1.1 Meta 开源 Llama 3 大模型
    • 2. 开源热搜项目
      • 2.1 OpenAI API 免费反向代理:ChatGPT
      • 2.2 一门新兴的系统级编程语言:Zig
      • 2.3 将网页内容转化成 LLM 友好的文本:Reader
      • 2.4 三国杀类型的卡牌游戏:noname
      • 2.5 为期 30 天的 Python 编程挑战:30-Days-Of-Python
    • 3. HelloGitHub 热评
      • 3.1 自定义 Windows 任务栏透明度的小工具:TranslucentTB
      • 3.2 跨平台的手写笔记和绘图应用:Rnote
    • 4. 结尾

1. 开源新闻

1.1 Meta 开源 Llama 3 大模型

今年初,扎克伯格就曾公开解释过:​Meta 为什么开源 Llama 模型?

  1. 改进模型:开源可以借助社区的力量持续提升模型的质量,因为社区的反馈和审查有助于安全性和运行效率的提升,而这对每个人都有益。
  2. 产品发展:虽然开源并不排除将模型转化为商业产品的可能性,开源模型的领导者可以将社区创新整合进自家产品中,提高产品竞争力。
  3. 行业标准:开源软件有潜力成为行业标准,从而促进技术发展和统一性。
  4. 吸引人才:由于开发者和研究人员更倾向于参与开源项目,开源策略可以帮助公司吸引和招聘到行业内的优秀人才。

最新发布的 Llama 3 相较于 Llama 2 在参数规模、训练数据集、模型架构(GQA)、性能、多语言支持、推理和代码生成方面都有所提升,但现在对中文支持的不是很好,而且 meta.ai 上用的还是 Llama 2。

GitHub 地址:
github.com/meta-llama/llama3

2. 开源热搜项目

2.1 OpenAI API 免费反向代理:ChatGPT

主语言:TypeScript

Star:3.7k

周增长:1k

虽然现在无需登陆就可以免费使用 ChatGPT(gpt-3.5-turbo 模型),但如果是想用接口的话还要收费的。该项目就是基于免费的 ChatGPT 网站服务,将其转化成免费的 ChatGPT API,接口返回和官方一致,支持 Docker 部署。需要注意的是部署的服务器,要在 OpenAI 服务支持的国家和地区。

GitHub 地址→
github.com/PawanOsman/ChatGPT

2.2 一门新兴的系统级编程语言:Zig

主语言:Zig

Star:30k

周增长:300

这是一种命令式、通用、静态类型、编译的系统编程语言,注重性能、安全和可读性。它支持编译时泛型与反射、交叉编译以及手动存储器管理,目标为改进 C 语言,可以轻松地和 C 语言的代码库配合工作。Zig 简洁且直接,没有隐式控制流、没有隐式内存分配、没有预处理器、没有宏,特别适合用于开发编译器、操作系统内核、桌面应用、性能敏感的应用、嵌入式系统等。

const std = @import("std");
const parseInt = std.fmt.parseInt;

test "parse integers" {
    const input = "123 67 89,99";
    const ally = std.testing.allocator;

    var list = std.ArrayList(u32).init(ally);
    // Ensure the list is freed at scope exit.
    // Try commenting out this line!
    defer list.deinit();

    var it = std.mem.tokenizeAny(u8, input, " ,");
    while (it.next()) |num| {
        const n = try parseInt(u32, num, 10);
        try list.append(n);
    }

    const expected = [_]u32{ 123, 67, 89, 99 };

    for (expected, list.items) |exp, actual| {
        try std.testing.expectEqual(exp, actual);
    }
}

GitHub 地址→
github.com/ziglang/zig

2.3 将网页内容转化成 LLM 友好的文本:Reader

主语言:TypeScript

Star:3k

周增长:2.5k

该项目可以将指定的 URL 内容转化为干净、LLM 友好的文本,从而提高 Agent 和 RAG 系统的输入质量,可作为构建知识库的一环。我试用了一下,内容提取效果不错,但是对于需要登陆才能访问的 URL 就不行了。

GitHub 地址→
github.com/jina-ai/reader

2.4 三国杀类型的卡牌游戏:noname

主语言:JavaScript

Star:1.9k

周增长:100

「无名杀」是一款以三国为背景的卡牌策略游戏,它完全免费、无广告,玩法和三国杀一样,但自由度高很多,有海量武将可供玩家选择,还支持自制武将和技能,提供了身份、国战、斗地主、塔防、单挑、联机等游戏模式。

GitHub 地址→
github.com/libccy/noname

2.5 为期 30 天的 Python 编程挑战:30-Days-Of-Python

主语言:Python

Star:31k

周增长:600

该项目是帮助人们在 30 天内学会 Python 编程语言,通过每天的练习和学习,逐渐掌握 Python 的基础和进阶知识,全部完成可能需要超过 100 天。该教程适合想要快速入门 Python 的初学者,或者对 Python 有一定了解,想通过实践深入理解 Python 的开发者。

GitHub 地址→
github.com/Asabeneh/30-Days-Of-Python

3. HelloGitHub 热评

在这个章节,将会分享下本周 HelloGitHub 网站上的热门开源项目,欢迎与我们分享你上手这些开源项目后的使用体验。

3.1 自定义 Windows 任务栏透明度的小工具:TranslucentTB

主语言:C++

该项目是采用 C++ 开发的用于调整 Windows 任务栏透明度的工具,它体积小、免费、简单易用,支持 5 种任务栏状态、6 种动态模式、Windows 10/11 操作系统。

项目详情→
hellogithub.com/repository/48c9ce6373cd4e108d74bcb4f8ac7a41

3.2 跨平台的手写笔记和绘图应用:Rnote

主语言:Rust

这是一款用 Rust 和 GTK4 编写的绘图应用,可用于绘制草图、手写笔记和注释文档等。它支持导入/导出 PDF 和图片文件,以及无限画布、拖放、自动保存等功能。适用于 Windows、Linux 和 macOS 系统,需要搭配手写板使用。

项目详情→
hellogithub.com/repository/1d768cfa742c4fcd9c0a0bd4374cb425

4. 结尾

在结束本周「GitHub 热点速递」的精彩内容后,希望这些开源项目能够对大家有所启发,帮助你们找到新的工具、学习资源或是娱乐项目。如果看完这些还不过瘾,可以通过阅读「往期回顾」的内容,找到更多热门开源项目。

往期回顾

以上为本周的「GitHub 热点速递」全部内容,如果你发现其他好玩、有趣的 GitHub 项目,就来
HelloGitHub
和大家一起分享吧。