MoreRSS

site iconKevinzhow | 周楷雯修改

日语学习APP开发者,PNChart作者,INTP-T,日本越谷。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Kevinzhow | 周楷雯的 RSS 预览

Play with ROCm, PyTorch, Ollama on Ubuntu 24.04 and 780m

2025-03-24 13:11:58

最近买了个 8845HS 的小主机,但因为 780m 的显卡并没有被 ROCm 列为官方支持的卡,所以目前需要很多 trick 来运行

最主要的就是通过 HSA_OVERRIDE_GFX_VERSION 来假装成受支持的显卡。虽然我用的都是 HSA_OVERRIDE_GFX_VERSION=11.0.2 但实际上因 ROCm 版本的不同,到底哪个能在你的显卡上工作需要自己测试下。

你可以通过 AMD 官网的 Supported GPUs 中的 Architecture 和 LLVM target 来查找,比如 gfx1101,那就是 HSA_OVERRIDE_GFX_VERSION=11.0.1

除此之外,ls /opt/rocm/lib/rocblas/library 命令也会列出一些没显示在官网上的支持,比如 11.0.2

ROCm

ROCm 是 AMD 显卡玩机器学习的基础组件,现在安装起来很简单,amdgpu-install 这个包就可以很好的解决

sudo apt install amdgpu-install
amdgpu-install --usecase=rocm

Ollama

Ollama 的运行可以参考下面的 PR Enable AMD iGPU 780M in Linux, Create amd-igpu-780m.md #5426

简而言之,直接通过这个命令就可以运行

HSA_OVERRIDE_GFX_VERSION=11.0.2 ollama serve

目前 Ollama 对 igpu 的显存支持有些问题,不能够将所有共享内存计算在内,解决方案可以参考这里 AMD integrated graphic on linux kernel 6.9.9+, GTT memory, loading freeze fix #6282

我的主要场景并非用 8845HS 来跑 LLM,所以就跑个简单的测试下吧,gemma3:12b 这个量化模型的速度大概是 8.44 tokens/s 可以说堪用

image.png

ollama ps

使用 ollama ps命令可以查看模型是分配在哪个设备上运行的。

image.png

PyTorch

PyTorch 直接通过AMD 官网提供的命令来安装即可,需要注意的是我使用 PyTorch 官网的命令安装稳定版并不能成功运行,AMD 官网给出的 nightly 版本可以。

pip3 install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/rocm6.2.4/

同样,需要使用 HSA_OVERRIDE_GFX_VERSION=11.0.2 来运行,可以创建一个 .env 文件在 ipynb 里动态载入,如果是 vscode 的话,.env 文件会自动加载,不需要下述步骤

pip install python-dotenv

在 ipynb 顶部加入一个 code block 每次运行一下即可

%load_ext dotenv
%dotenv

其它内容无需修改

radeontop 监控

配置完成后,如果有时候不确定有没有跑在 GPU 上,可以用 radeontop 来监控

sudo apt install radeontop
image.png

关于 NPU

8845HS 还带了个 16 TOPS 的 NPU,不过要等到 Linux 6.14 才会合并进去。

届时 ONNX Runtime 的 VitisAIExecutionProvider 和 HuggingFace 的 RyzenAI 应该都能开箱即用。

唯一的问题是兼容性如何。

暂时还没折腾,等到时候也会测试一下再写一篇折腾的博客

其他参考

Does ROCm 5.7 support Radeon 780M (gfx1103)? #2631

Feature: ROCm Support for AMD Ryzen 9 7940HS with Radeon 780M Graphics #3398

写在新的旅程开始前

2025-03-20 16:25:56

最近经常想起自己为什么离开公司自己做产品,都已经是 7 年前的事情了

当时为了能够按照自己的意志去做产品

做一款足够独特,有趣的产品

这些年事情虽然做的慢了些

但现在对这个目标有了更好的想法,更成熟的思考

7 年间发生了很多事情,失去了挚友

身份,家庭,关系也都在变化

但今天我真的迈入新的副本了

副本开始前,是应该好好休息一下的!

想念自然与青春

2025-03-08 11:07:40

昨天看介绍说乔布斯在设计苹果的新总部时希望有在公园里办公的感觉,因为在亲近自然的时候最有灵感。

想起了去年住的东越谷,东越谷是真的挺美的,河道,公园,家里的窗外。

因为不能养猫所以搬家了,离开了这个环境后经常会想念,所以也在考虑是不是应该再搬家,找一个能养猫又亲近自然的地方。

但还是等春天来了之后,看看这里万物复苏后是什么感觉,或许也会很美。

最近集中看了一些电影,「怪物」「去唱卡拉OK吧」「爱的接力棒」这些

感觉也许是自己真的步入中年,远离青春了

「去唱卡拉OK吧」是那种后劲很足的青春感

而且不是那种恋爱的青春

懒猫微服体验——自由协作的神器

2024-12-07 18:40:02

11 月中旬的时候,Andy 私信问我愿不愿意体验一下懒猫微服这款产品,我看了下懒猫微服的官网,然后几个疑问就冒了出来

  • 功能好像是个 NAS,但一个新团队能力如何?真的比那些老牌 NAS 有优势?
  • 宣传了不少 AI 技术,但是看 SOC 算力比起最新的那些动辄 100TOPS 的芯片,这个芯片似乎也不能力大管饱啊
  • 支持网络穿透,但这个比起用 Tailscale 等第三方技术会真的更好用吗?
  • 这个价格,我是不是应该考虑买个 Mac Mini 自己组装😂

于是我对 Andy 说,关注我的人应该大都懂技术,对参数也比较敏感,可能找一些数码博主来体验会更好。

但 Andy 还是说,你先体验下,如果喜欢的话就点评点评。

好吧,既然条件这么宽松,我也很好奇原 Deepin Linux CTO 带来的产品,会有什么不同,在这些问题中,我最好奇的是懒猫的网络穿透技术,是不是真的能构建出有用的使用场景。

因此本次的体验将跳过所有和外观以及参数相关的内容,只在于使用场景的探索。

IMG_9774.jpg

寻找我的场景是什么

懒猫到手后配置的流程很简单,打开 App 扫机器底部的二维码,几个下一步就可以配置好,和 HomeKit 的智能设备入网差不多。这个流畅简单的体验是让我觉得有些惊喜的,和我之前用的 NAS 相比是非常新手友好了。

与传统 NAS 不同的是,你暂时无法直接通过一个局域网的地址访问懒猫微服,想要正常使用懒猫,都需要启动他们的 App 进行组网,然后通过 App 管理和访问懒猫的功能。

image.png

比较省心的是,不论你身处何地,都可以通过这个懒猫微服的内网穿透能力,无缝的访问家里懒猫微服的应用和内容。

原理上来说,懒猫微服会通过内网穿透组网的能力,将你登陆了懒猫 App 的设备和你家里的懒猫微服组合到一个加密的私有网络里,在这之后,懒猫 App 会接管你设备上所有访问 heiyu.space 域名后缀的流量,而每个你安装的懒猫上的应用,都会有对应的 heiyu.space 域名,比如懒猫清单对应的就是 https://todolist.myoland.heiyu.space

image.png

当你访问这个域名的时候,无需经过第三方中转即可直连家中的懒猫微服,这意味着你既不需要租服务器,处理那些烦人的备案合规问题,也不需要担心带宽和流量问题,更无须担心自己的数据会被窥窃。

如果这个域名只有自己能访问,那就相当无趣了,懒猫让我开始觉得兴奋的地方是,你可以邀请最多 20 名用户加入到你的微服上,这意味着,虽然他们没有微服设备,但可以访问,操控,管理你的懒猫微服和上面的应用。

image.png

Mattermost 协作

在看到懒猫这种便利的组网能力后,我立刻想起了我之前和团队成员协作时一直在用的 Mattermost,一个 Slack 的开源替代品,出于数据安全的考虑,我并不想使用国内的协作产品聊天,但我又需要大家能稳定的连接到这个服务上,一个架设在自己家里的 Mattermost 就是很好的选择。

可惜在我使用的时候懒猫微服的应用商店还没有上架这个应用。

好吧,自己动手丰衣足食,这也是增进对懒猫了解的好机会,我着手移植了一个懒猫微服版本的 Mattermost,在这篇 Blog 发布的日期,我已经提交 Mattermost 到懒猫的应用商店,如果你想参考移植自己的项目,可以在这里找到项目代码 https://github.com/kevinzhow/mattermost-lzc

Mattermost 在安装后你可以访问 https://mattermost.myoland.heiyu.space 来打开 Mattermost,也可以直接通过这个域名在 App 上登录。

image.png
image.png

得益于懒猫 App 在电脑端不错的兼容性,懒猫微服不会和你电脑上其他的 VPN 服务冲突,Mattermost 用起来也非常无缝,在一些网络配置后,远程协助和电话功能也都能正常运行,这一刻我有些爱上了懒猫微服为团队协作提供的便利性。

Syncthing 进行文件同步

团队协作第二个最常见的需求就是点对点文件同步,以前一直用 Resilio 进行同步,但是感觉不论是权限管理还是后来的改动,都让我用的有些不开心,这次我尝试了 Syncthing 这款开源产品。

虽然懒猫的应用商店自带了 Syncthing 但是有两个问题导致我还是自己进行了一次移植

  • Syncthing 不应该是多实例的,需要通过单实例确保这个中心同步节点进行协调
  • Syncthing 的数据不应该存在自身的卷内,需要确保如果用户删除他也不会导致数据丢失,同时我将他的数据挂载到了用户文件夹内,这样你可以通过懒猫网盘访问查看上面的数据

你可以在这里找到我的移植项目 https://github.com/kevinzhow/syncthing-lzc

image.png

存储和备份

因为一开始我带入了 NAS 的概念,所以看到硬盘 1 和 硬盘 2 的使用量一样时,我以为是组了 RAID,但客服告诉我这是 btrfs 均匀存储的特性,多年 Mac 用户此刻表示受到了开源震撼。

image.png

数据备份

我另外一台 NAS 虽然用了 RAID 技术,但比起 RAID 让人摸不清头脑的规格,我个人更喜欢懒猫这种外接一个 USB 硬盘进行增量数据备份的方式,机器因此可以做小,更省电,自己也可以更自由的管理移动备份硬盘。

image.png

综合体验

其实在找到了团队协作这个场景之后,我认为懒猫微服已经给我提供了一个非常独特的使用场景,从一开始带着偏见的怀疑,变成了希望他们做大做强。

正如它名字里体现的一样,这是一个开箱即用的私有云服务主机,NAS 和 AI 都不是它打动我标签,懒猫微服隐藏了 NAS 产品过去的复杂细节,将微服这个概念交付到了用户手中。

优点

  • 系统运行稳定,底层技术不错
  • 网络穿透服务稳定
  • 技术支持响应迅速
  • 客服非常友好专业

缺点

  • 第一方应用尚需时间打磨
  • 价格是个门槛,但不折腾这一点可以值回票价
  • 宣传上让人会误以为是 NAS 或者 AI 主机,但其实他最差异化的私有云属性反而没有的到足够的宣传

谁适合购买这款产品?

希望不折腾,拥有开箱即用的私有云服务主机的人。 另外懒猫很快会推出海外版本,相信届时如果结合 Cloudflare Tunnel,这应该也会是一个非常好的公网服务器。

没有光纤的日子怎么上网?自制 Home WI-FI!

2024-10-10 18:47:53

这次搬家是彻底失算了,没想到日本 2022 年的新筑 Terrace (日语叫 メゾンネット,中文可以理解为联排)竟然没有光纤,这就导致在各家装宽带的网页上,公寓和一户建都不太能准确描述这个地方。

各种波折导致我没能顺利搬迁之前的宽带,也没能很快装上新的宽带。这也是为什么我会写这篇文章,主要是抒发下没有宽带的苦闷。

为什么不用运营商的 Home WI-FI

日本很多运营商都提供了一种叫做 Home Wi-Fi 的家庭热点,原理和手机热点一样,都是通过一张 SIM 卡连接到移动网络,然后再分享出来。

Speed Wi-Fi HOME 5G L13

但与手机不同的是,Home WI-FI 这种设备把技能点都点在了下载上。

Speed Wi-Fi HOME 5G L13

比如在我这里通过 5G 连接 Au 的线路,下载可以到 200M,但上传可能只有 10M - 20M,同样的卡切换成我的 Pixel 7a 之后,下载是 120M 左右,上传是 60 - 80M. 下图可以看到 Pixel 7a 的上传性能要好很多。

Pixel 7a’s Exynos 5300

有了这个数据的对比后,我决定把 Android 手机的网通过 RJ45 网线提供给路由器上网。

为什么不用手机热点?

在日本 Android 手机只能分享 2.4G 的 WI-FI,这导致 WI-FI 的速度和稳定性,带机量都相当有限。

自制 Home WI-FI 的简单方式

具备下面 3 个物品就可以方便的自制一个 Home WI-FI 了

  1. 流量卡
  2. 支持以太网共享的 Android 手机
  3. USB to RJ45 的转接设备
  4. 一个路由器(应该都有吧!)

流量卡

在日本的话 Povo 和乐天这两个放题卡都是不错的选择,需要注意的是自己所在的区域是否有对应的 5G 信号支持,5G 通常可以提供一个 20ms 左右的低延迟体验,即使人多的时候速度不会降的离谱。

Android 手机

虽然 小米,三星,Google Pixel 的手机大都支持以太网网络共享,但这一点还是需要自己先确认下。

USB to RJ45

你可以选择一个带有 Type C 供电的转接头,比如我购买的支持 PD 电源输入的这款

image 3.png

除了专门买个转接头外,也可以用自己手头带有 RJ45 接口的扩展坞,但扩展坞和手机的兼容性可能不太好,比如我手里的 Anker 扩展坞可以和小米手机一起用,但 Pixel 使用则会一直掉线。

在购买前需要确认转接设备和 Android 手机的兼容性,一般如果商家写了支持 Android 设备那么通常没问题。

路由器

正常的家用 WI-FI 路由器即可 :)

使用方式

IMG_8949.jpg
  1. Android 手机插好自己的流量卡
  2. 将 RJ45 转接头插到手机上
  3. 在 Android 手机的热点中打开「以太网网络共享」这个选项,如果转接头不兼容,那么这个会是灰色的无法开启
  4. 用一根网线连接 Android 手机转出来的 RJ45 口和路由器的 WAN 口
  5. 确保路由器的 WAN 口上网模式是「IP 动态分配」

好了,可以享受网络了!我用了一个星期后感觉还是挺稳定的,不过还是祝你早日装上宽带!

Swift on Server Tour 6 关联 User 和 Post

2023-10-30 13:28:30

在上一章中我们创建了 UserModel,并构建了 UserController,但尚未构建起 UserPost 之间的关系。在这一章中,我们将在 Vapor 中完成一对多的关系构建。

本章代码可以在 Github 中找到。

修改 Post 的数据结构

要修 Post 表结构,我们首先需要创建一个新的 Migration 文件。

创建 Sources/App/Migrations/3_AddUserIDToPost.swift 文件,添加如下代码:

import Fluent
struct AddUserIDToPost: AsyncMigration {
func prepare(on database: Database) async throws {
try await database.schema(Post.schema)
.field("user_id", .uuid, .references(User.schema, "id"))
.update()
}
func revert(on database: Database) async throws {
try await database.schema(Post.schema)
.deleteField("user_id")
.update()
}
}

这种方式将会给 Post 表添加一个可为空的 user_id 字段,虽然这种方式简单直接,但同时也意味着,我们容忍了 Post 可能不属于任何用户的情况。

prepare 方法中,我们使用 references 方法来创建了一个外键约束,这个外键将会引用 User 表中的 id 字段,确保了 user_id 在数据库层面的正确性,避免我们意外插入一个不存在的 user_id

revert 方法中,我们使用 deleteField 方法来删除 user_id 字段,这个方法会同时删除它的外键约束。

随后,我们应当修改 configure.swift 将这条 Migration 添加到其中

app.migrations.add([CreatePost(), CreateUser(), AddUserIDToPost()])

现在,运行 vapor run migrate 即可进行数据库的迁移。

修改 Post Model

完成了数据库 Schema 的修改后,我们需要进一步修改 Post Model

编辑 Sources/App/Models/Post.swift 文件如下:

final class Post: Model, Content {
static let schema = "posts"
@ID(key: .id)
var id: UUID?
@OptionalParent(key: "user_id")
var user: User?
@Field(key: "content")
var content: String
@Timestamp(key: "created_at", on: .create)
var createdAt: Date?
init() { }
init(id: UUID? = nil, content: String) {
self.id = id
self.content = content
}
}

因为我们在给 Post 的 Schema 添加 user_id 字段时允许其为空,因此在 Post Model 中,我们使用 @OptionalParent 来表明 user 是不一定存在的。

与此同时,@OptionalParent(key: "user_id") 表明了 user_id 和 User Model @ID 之间的对应关系,这与 Schema 中的 .field("user_id", .uuid, .references(User.schema, "id")) 形成对应关系。

修改 User Model

import Vapor
import Fluent
final class User: Model, Content {
static let schema = "users"
@ID(key: .id)
var id: UUID?
@Field(key: "username")
var username: String
@Field(key: "password_hash")
var passwordHash: String
@Children(for: \.$user)
var posts: [Post]
@Timestamp(key: "created_at", on: .create)
var createdAt: Date?
init() {}
init(id: UUID? = nil, username: String, passwordHash: String) {
self.id = id
self.username = username
self.passwordHash = passwordHash
}
}

在 User Model 中,@Children(for: \.$user) 表明了 User 和 Post 之间的一对多关系,同时也说明了,这种关系是通过 Post Model 中的 user 属性来进行关联的。

现在,我们有两种方式可以创建 Post 了,一种是通过 Post Model 的 user 属性:

let user = User(username: "hellouser", passwordHash: try await app.password.async.hash("123456"))
try await user.save(on: app.db)
let post = Post(content: "Hello, World!")
post.$user.id = try user.requiredID()

另一种是通过 User Model 的 posts 属性

let user = User(username: "hellouser", passwordHash: try await app.password.async.hash("123456"))
try await user.save(on: app.db)
let post = Post(content: "Hello, World!")
user.$posts.create(post, on: app.db)

不管采用哪一种,我们都需要在保存 Post 前知道 user_id

使用 BasicAuthorization 进行用户验证

在 PostController 中,我们需要修改 create 方法,使其能够接收 user_id 参数,并将其与 Post 关联起来。

那么如何将 user_id 传入到 create 方法中呢?

直接让客户端提供 user_id 参数肯定是不可以的,因为这样的话,客户端只需要伪造 user_id 就可以假装成别的用户发布,这显然是不安全的。

我们暂且通过最简单的 BasicAuthorization 来解决这个问题,BasicAuthorization 需要客户端提供 username 和 password 两个字段,并将其进行 Base64 编码后放在 HTTP Header 中:

计算格式如下

base64(username:password)

以上面的用户信息 happyuser:123456 为例,计算后的 Header 结果如下:

Authorization: Basic aGFwcHl1c2VyOjEyMzQ1Ng==

服务器端验证用户名和密码后,才会允许客户端创建对应用户的 Post。

BasicAuthorization 作为一种业界通用实践,Vapor 已经提供了 Model Authenticatable 来帮助我们完成这个功能

修改 Sources/App/Models/User.swift 在底部增加如下代码:

extension User: ModelAuthenticatable {
static let usernameKey = \User.$username
static let passwordHashKey = \User.$passwordHash
func verify(password: String) throws -> Bool {
try Bcrypt.verify(password, created: self.passwordHash)
}
}

usernameKeypasswordHashKey 分别表明了 User Model 中的哪两个属性用于存储用户名和密码。当客户端提供用户名和密码时,Vapor 会自动将其与 User Model 中的 usernameKeypasswordHashKey 进行对应,从而完成用户验证。

接下来我们需要确保 Post 在创建前,用户已经通过了验证,这可以通过在 PostController 的路由中,加入权限验证相关的 Middleware 来完成,Vapor 已经提供了 User.authenticator() 来帮助我们完成 BasicAuthorization 验证的功能。

修改 Sources/App/Controllers/PostController.swift 中 boot 代码如下

func boot(routes: RoutesBuilder) throws {
routes.group("posts") { posts in
posts.get(use: index)
posts.grouped(User.authenticator()).post(use: create)
}
}

posts 路由组中,当客户端请求 posts 路由组中的 post 方法前,User.authenticator() 会进行 BasicAuthorization 验证,如果没有通过验证,Vapor 会返回 401 错误。

修改 PostController

修改 Sources/App/Controllers/PostController.swift 中的 create 方法如下:

func create(req: Request) async throws -> Post {
let user = try req.auth.require(User.self)
let postData = try req.content.decode(Post.CreateDTO.self)
let post = Post(content: postData.content)
post.$user.id = try user.requireID()
try await post.create(on: req.db)
return post
}

create 方法中,我们首先通过 req.auth.require(User.self) 获取到了已经通过验证的用户,然后通过 req.content.decode(Post.CreateDTO.self) 获取到了客户端传入的 Post 数据,最后通过 post.$user.id = try user.requireID() 将 Post 和 User 关联起来。

完善 Post 的单元测试

修改 Tests/AppTests/PostTests.swift 如下:

@testable import App
import XCTVapor
final class PostTests: XCTestCase {
var app: Application!
override func setUp() async throws {
app = Application(.testing)
try configure(app)
try await app.autoRevert()
try await app.autoMigrate()
}
override func tearDown() async throws {
app.shutdown()
}
func testCreatePost() async throws {
let user = User(username: "hellouser", passwordHash: try await app.password.async.hash("123456"))
try await user.save(on: app.db)
let postDTO = Post.CreateDTO(content: "Post created from test")
try app.test(.POST, "posts", beforeRequest: { req in
try req.content.encode(postDTO)
req.headers.basicAuthorization = BasicAuthorization(username: user.username, password: "123456")
}, afterResponse: { res in
XCTAssertEqual(res.status, .ok)
let post = try res.content.decode(Post.self)
XCTAssertEqual(postDTO.content, post.content)
XCTAssertEqual(try user.requireID(), post.$user.id)
})
}
}

testCreatePost 中,我们首先创建了一个用户,然后创建了一个 Post DTO,接着通过 app.test 方法,模拟了客户端的请求,最后验证了 Post 的 User 来判断是否创建成功。

总结

在这一章中,我们学习了如何在 Vapor 中构建一对多的关系,同时也学习了如何使用 BasicAuthorization 来进行用户验证。但这种方式虽然简单,但也有很多缺点,在下一章中,我们会学习使用 BearerAuthentication 来对用户进行验证,并进一步修复 User Model 的安全隐患。

本章代码可以在 Github 中找到。