0%

随着持续的版本演进,VS Code 的功能也越来越多,其中一个比较好用的功能是 VS Code 配置(VS Code Profiles)。

Visual Studio Code有数百种设置、数千种扩展和无数种调整UI布局的方法来自定义编辑器。VS Code Profiles允许您创建自定义设置,并在它们之间快速切换或与他人共享。

VS Code 配置总体上使用比较简单,可以参考官方文档

Read more »

目前最常见的运行大语言模型的方式是使用 Python 的transformers库。只需要数行代码就可以加载并运行诸多 hugingface 平台上托管的语言模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from transformers import AutoTokenizer
import transformers
import torch

model = "meta-llama/Llama-2-7b-chat-hf"

tokenizer = AutoTokenizer.from_pretrained(model)
pipeline = transformers.pipeline(
"text-generation",
model=model,
torch_dtype=torch.float16,
device_map="auto",
)

sequences = pipeline(
'I liked "Breaking Bad" and "Band of Brothers". Do you have any recommendations of other shows I might like?\n',
do_sample=True,
top_k=10,
num_return_sequences=1,
eos_token_id=tokenizer.eos_token_id,
max_length=200,
)
for seq in sequences:
print(f"Result: {seq['generated_text']}")

不过使用 transformers 库还是有一些先决条件的,包括:

  • 配置 Python 虚拟环境;
  • 安装 pytorch 库;
  • 安装 cuda 环境等;

Mozilla 团队推出了一个新项目llamafile,可以使用单个文件分发并运行大语言模型。

下载 llamafile 模型文件

llamafile项目的github主页上提供了若干个不同规模的语言模型的llamafile格式文件下载链接,我选择的是LLaVa 1.5,参数规模适中(3.97GB),能够运行在家里8G显存的PC上,并且本身是一个多模态模型,可以分析图片。

models

Read more »

上个季度加入了公司的 AI 团队,负责的工作除了常规的服务端开发之外,还包括了在端上运行机器学习模型。考虑到性能因素,没有使用 Python 或 Node.js 等运行时,而是直接使用 C++来执行模型推理逻辑。在这里分享一些经验和教训。

模型格式

我们使用的模型格式是 onnx。onnx 是一种开放的模型格式,使用一个二进制文件存储模型的结构和参数,可以在不同的平台上使用不同的运行时加载和执行。目前主流的深度学习框架都支持导出 onnx 模型,例如 PyTorch、TensorFlow 等。

模型运行时

对于常见的网络结构的模型,可以使用 OpenCV 的 dnn 模块来加载和执行。OpenCV 是一个开源的计算机视觉库,提供了很多计算机视觉相关的功能,其中 dnn 模块提供了加载和执行深度学习模型的功能。

对于一些使用了自定义算子的模型,OpenCV 的 dnn 模块可能无法加载,这时候可以使用 onnxruntime 来加载和执行模型。onnxruntime 是微软开源的一个 onnx 运行时,支持加载和执行 onnx 模型,支持 CPU 和 GPU 加速。小小的吐槽一下,与 Python 的 onnxruntime sdk 相比,C++ 的 onnxruntime sdk 的接口有够难用。

Windows 上使用 onnxruntime 的一个坑

Windows 10 和 Windows 11 在系统库里内置了一个较旧版本的 onnx runtime,如果使用了较新的 onnxruntime,可能会出现加载模型失败的问题。一个解决方式是在代码中指定加载动态链接库的路径,并通过 onnx runtime 的 api 指定加载的版本。

1
2
3
4
5
6
7
8
9
10
11
12
#include "Windows.h"
#define ORT_API_MANUAL_INIT // 定义这个宏可以禁止 onnxruntime 自动加载
#include <onnxruntime_cxx_api.h>

int main(){
auto mod = LoadLibrary(L"onnxruntime1.16.1.dll");
auto OrtGetApiBase =
(const OrtApiBase *(*)(void))GetProcAddress(mod, "OrtGetApiBase");
Ort::InitApi(OrtGetApiBase()->GetApi(ORT_API_VERSION));

// ...
}
Read more »

近期简单学习了一下向量数据库 qdrant 与 sentence-transformers 库,两者结合可以构建一个简单的自然语言搜索引擎。顺着官方的教程实操了一遍之后,稍微调整一番,我在中文数据集上构建了一个自然语言搜索引擎。

数据采集

教程中的数据集是一些美国的初创公司的数据(来自 startups-list.com),我打算自己从互联网上采集一些中文数据集,正好近期一直在使用微信读书,决定采集微信读书平台上计算机分类下的书籍数据。

观察了微信读书网页版的请求之后,我发现可以通过 ‘https://weread.qq.com/web/bookListInCategory/{type}?maxIndex={maxIndex}‘ 获取指定分类下的书籍列表。
在实验过程中,我发现即使分类下有更多的书籍,maxIndex 超过 480 之后就不再返回新的数据了,这应该是微信读书官方的一些限制。不过计算机分类下一共有七个子分类,每个分类都采集四百多本书籍,总共有 3000 多本书籍,也足够使用了。

首先定义一个函数用于获取制定分类指定页数的书籍列表。

1
2
3
4
5
6
url = 'https://weread.qq.com/web/bookListInCategory/'
def fetch_page(param):
type, page = param
_url = f"{url}{type}?maxIndex={page*20}"
resp = requests.get(_url)
return resp.json()["books"]

为了提高采集的效率,我创建了一个线程池用于批量执行采集任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from itertools import product
with ThreadPoolExecutor(max_workers=10) as executor:
pages = list(
executor.map(
fetch_page,
product(
[700001, 700002, 700003, 700004, 700005, 700006, 700007],
range(1, 25),
),
)
)

books = []
for page in pages:
books.extend(page)

最终的执行时间是 11 秒,相当不错的速度。

数据处理

参考教程里的流程,我使用 pandas 进行了初步的数据处理,并用 sentence-transformers 库生成书籍描述的向量表示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from sentence_transformers import SentenceTransformer
import numpy as np
import pandas as pd

model = SentenceTransformer(
"paraphrase-multilingual-MiniLM-L12-v2", device="cuda"
)

df = pd.DataFrame([book['bookInfo'] for book in books])

vectors = model.encode(
[row.title + ". " + row.author + "." + row.intro for row in df.itertuples()],
show_progress_bar=True,
)

vectors.shape # (3244, 384)

np.save("wx_books.npy", vectors, allow_pickle=False)
Read more »

近期在研究开源的 rust 实现的向量数据库 qdrant。顾名思义,向量数据是用于存储和查询向量的数据库,而向量本质上是一个多维空间中的点。如果要用向量数据库处理文本数据,就需要将文本转换为向量表示,机器学习术语叫做文本嵌入(Text Embedding)。

传统的文本嵌入方法是基于统计的,比如 TF-IDF,Word2Vec 等。随着 transformer 架构的出现和发展,基于 transformer 的文本嵌入方法也越来越流行,并且在很多任务上取得了很好的效果。sentence-transformers 就是一个基于 transformer 的文本嵌入工具包,可以用于生成句子的向量表示。

安装 sentence-transformers

安装 pytorch

可以前往 pytorch 官网 根据自己的环境选择合适的安装方式,我的设备是 RTX 2060s 的 Windows PC,安装了 windows 的 cuda 版本。

1
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

安装好 pytorch 后可以验证一下。

1
2
3
4
5
6
7
8
9
10
11
import torch

# Check if PyTorch is installed
print("PyTorch version:", torch.__version__)

# Check if CUDA is available
print("CUDA available:", torch.cuda.is_available())


x = torch.rand(5, 3)
print(x)

安装 sentence-transformers

sentence-transformers 可以直接使用 pip 安装。

1
pip3 install sentence-transformers

使用 sentence-transformers

sentence-transformers 提供了很多预训练模型,可以直接使用。我们这里使用的是 paraphrase-multilingual-MiniLM-L12-v2 模型,支持多语言,模型尺寸也比较大(480M)。只处理英文文本的话,可以使用 all-MiniLM-L6-v2 模型(80M)。

1
2
3
4
5
6
7
8
9
10
from sentence_transformers import SentenceTransformer, util

model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")

#Sentences are encoded by calling model.encode()
emb1 = model.encode("This is a red cat with a hat.")
emb2 = model.encode("Have you seen my red cat?")

cos_sim = util.cos_sim(emb1, emb2)
print("Cosine-Similarity:", cos_sim) # Cosine-Similarity: tensor([[0.7097]])

上述代码中,我们使用 sentence-transformers 加载了 paraphrase-multilingual-MiniLM-L12-v2 模型,并使用该模型将两个句子转换为向量表示,然后计算了两个向量的余弦相似度。

余弦相似度是一个常用的相似度度量方法,其值域为 [-1, 1],值越大表示两个向量越相似。其他的相似度度量方法还有欧氏距离、曼哈顿距离等。

Read more »

shuttle 平台简介

shuttle 是一个 PaaS 平台,用于部署 rust 后端服务,支持多个流行的 rust web 框架,如 axum,actix-web、rocket 等。作为对比的话,类似于 heroku(python) 和 deno deploy 平台。

部署 shuttle 服务

将一个现有的 rust web 服务改造并部署到 shuttle 平台上非常简单。拿 axum 框架距离只需要在 cargo.toml 中添加 shuttle-runtime 与 shuttle-axum 依赖,并简单地修改 main 函数即可。

1
2
3
4
5
6
7
#[shuttle_runtime::main]
async fn axum() -> shuttle_axum::ShuttleAxum {

let router = Router::new().route("/", get(hello_world));

Ok(router.into())
}

然后通过命令行工具即可将服务部署到 shuttle 平台上。

1
2
3
4
# cargo install cargo-shuttle ## 安装cargo shuttle
# cargo shuttle login ## 登录shuttle
# cargp shuttle project start ## 创建项目
cargo shuttle deploy # 部署服务

实践

花了一点时间,我将之前开发的二维码生成服务qr-web-rs部署到了 shuttle 平台上,可以通过以下两个链接访问。

总结

shuttle 平台体验相当不错,并且有不少的免费额度,可以用于个人 rust 服务的部署。

前缀树(Trie 树,也称为字典树、单词查找树)是一种树形数据结构,用于高效地存储和检索字符串集合中的键。前缀树的主要优势在于能够快速地查找具有相同前缀的字符串,并且对于大量的字符串集合,它可以提供较高的检索效率。

前缀树的应用非常广泛,包括:

  • 字符串检索:通过前缀树可以快速查找是否存在某个字符串,或者查找具有相同前缀的所有字- 符串。
  • 自动完成:前缀树可以用于实现自动完成功能,根据用户输入的前缀提供可能的建议。
  • IP 路由:在路由表中,前缀树用于快速匹配最长前缀。

前缀树可以通过多种方式实现,在 Python 中最简单且直观的方式是用嵌套的 dict 实现。

首先定义一下前缀树的接口,应该包括 insert、search 和 startswith 三个方法。

1
2
3
4
5
6
7
8
9
10
trie = Trie()
trie.insert("app")
trie.insert("apple")
trie.insert("banana")
assert trie.startswith("a")
assert trie.startswith("ba")
assert not trie.startswith("c")
assert trie.search("app")
assert not trie.search("appl")
assert trie.search("apple")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Trie:
def __init__(self):
self.root = {-1: False}

def insert(self, word: str):
parent = self.root
for ch in word:
node = parent.get(ch, {-1: False})
parent[ch] = node
parent = node
parent[-1] = True

def __find(self, word:str):
node = self.root
for ch in word:
if ch not in node:
return None
node = node[ch]
return node

def search(self, word: str) -> bool:
node = self.__find(word)
return node is not None and node[-1]

def startswith(self, prefix: str) -> bool:
node = self.__find(prefix)
return node is not None
Read more »

我在最近的前端项目中用到了 vite 与 swc 作为构建工具和 JavaScript 编译器,二者都比较年轻,但提供更优秀的前端开发体验。当下前端生态的新选手层出不穷,本文就简单介绍一下最近几年前端生态涌现的诸多新兴的技术与工具。

Javascript 运行时

Deno

Deno是一个使用 rust 开发,基于 V8 引擎的简单、现代、安全的 Javascript 和 TypeScript 运行时。
我在 2021 年的时候写过一篇文章介绍deno。最近两年 Deno 的发展比较迅速,引入了一些新的 API 与能力,提高了稳定性以及与 Node.js 的兼容性。
顺便提一下Deno Deploy平台,可以方便地部署 Deno 项目为 severless 应用。

Bun

Bun是一个使用 zig 语言开发的基于 JavaScriptCore 引擎的全功能 JavaScript 和 TypeScript 工具集。

JavaScript 编译器与打包器

SWC

SWC是一个可扩展的基于 Rust 的下一代快速 Web 开发工具,同时支持 JavaScript 的编译与打包。Deno 和 Next.js 都使用了 SWC 作为编译器。

esbuild

esbuild是一个极快的 web 打包工具,使用 go 语言编写。

bun bundler

bun 对 JavaScript 模块的打包有原生的支持(官方的 benchmark 中比 esbuild 要快很多)。

Node.js 版本管理工具

volta

volta是一个使用 Rust 开发的新兴的 Node.js 版本管理工具。我目前正在使用 volta 管理 Node.js 版本,可以简单地根据项目切换 Node.js(包括 npm 与 yarn)的版本,安装不同版本的 Node.js 也很快。

fnm

fnm是一个快速、简单的 Node.js 版本管理工具,使用 Rust 开发。

Node.js 包管理工具

yarn v2+

新版本yarn做了很多改进,比如使用 Plug'n'Play 模式进行依赖项解析,提高性能与可靠性;支持零安装模式;对 monorepo 的支持更好等。

pnpm

pnpm是一个快速、节省空间的包管理器。

构建工具

parcel

parcel是一个快速、零配置的 Web 应用程序打包工具,支持 JavaScript、TypeScript、CSS、HTML、JSON 等文件的打包。parcel 2.0 版本使用了 swc 作为编译器,速度快了很多。

vite

vite是一个新兴的前端构建工具,在开发时使用 HMR 技术实现秒级的热更新,编译时使用 esbuild 和 Rollup 进行编译与打包,速度非常快。vite 也是 vue3 的官方构建工具。

tuborpack

tuborpack是一个使用 rust 开发的针对 JavaScript 和 TypeScript 优化的渐进式打包工具,由 Webpack 的原作者开发。

前端框架(React.js 生态)

next.js

next.js是一个 React 框架,提供了 SSR、SSG、CSR 等多种渲染模式,同时提供了很多优秀的功能,比如图片优化、代码分割、预取、预加载、路由、静态资源优化等。

Remix

Remix是一个全栈 web 框架,支持 SSR 与 CSR。

最近因工作需要,需要开发一个后台管理系统。考虑到开发效率和技术成熟度,决定使用 React 和 Ant Design 的技术栈。

之前我一直使用 create-react-app 来创建 React 项目,但是 create-react-app 已经很久没有更新了,新版的react 文档也不再推荐使用 create-react-app,而是推荐使用更成熟的框架,比如 next.js 与 remix。next.js 与 remix 都是比较大的 SSR 框架,对于简单的后台管理系统来说,可能有点大材小用。

作为一个新兴的前端构建工具,vite 的成熟度日益提升,对于 React.js 的支持也已经很完善,所以我决定使用 vite 来开发这个后台管理系统。

Read more »