Compare commits

..

20 Commits

Author SHA1 Message Date
b4205049cb
merge: branch 'main' into lab/pred 2025-04-05 06:13:23 +08:00
509c10ded0
merge: branch 'feat/frontend' into feat/backend 2025-04-05 06:11:45 +08:00
99c7a34833
add: speed in benchmark result 2025-04-02 23:37:43 +08:00
d808f36c58
improve: number display in benchmark result 2025-04-02 23:32:26 +08:00
984484cc3f
improve: eliminate UI flickering between tests 2025-04-02 23:30:49 +08:00
46578db3e6
update: lower the difficulty of benchmark 2025-04-02 23:24:52 +08:00
704b5106c6
fix: difficulty too low in benchmark 2025-04-02 23:15:45 +08:00
2e702f23de
improve: UI of showing benchmark result 2025-04-02 23:13:53 +08:00
cb5e24e542
improve: UI of VDF benchmark 2025-04-02 22:55:55 +08:00
b1e071930c
fix: incorrect build config 2025-04-02 22:48:19 +08:00
c3f13cc6e3
update: VDF benchmark 2025-04-02 22:14:32 +08:00
dd829b203d
test: argon2id tester 2025-04-02 21:28:23 +08:00
41f8b42f1c
improve: the registration API 2025-04-01 10:30:44 +08:00
8d4edd43bf
update: link in about content 2025-03-31 06:39:52 +08:00
2aead46b51
update: title for frontend page 2025-03-31 06:36:36 +08:00
79af12e526
update: README 2025-03-31 06:35:44 +08:00
81847cc090
merge: branch 'main' into lab/pred 2025-03-31 05:38:17 +08:00
0d18c921cb
merge: branch 'main' into lab/pred 2025-03-29 18:59:09 +08:00
ba6b8bd5b3
temp: try to modify some features for the pred model 2025-03-20 00:53:59 +08:00
2ed909268e
add: a short-term feature in pred model 2025-03-16 15:10:32 +08:00
15 changed files with 342 additions and 55 deletions

View File

@ -2,6 +2,11 @@
「中V档案馆」是一个旨在收录与展示「中文歌声合成作品」及有关信息的网站。
## 新闻 - 测试版本上线
目前中V档案馆上线了用于测试的前端网页和API接口它们分别位于[projectcvsa.com](https://projectcvsa.com)和[api.projectcvsa.com](https://api.projectcvsa.com)。
API调用方法请参见[接口文档](https://docs.projectcvsa.com/api-doc/)。
## 创建背景与关联工作
纵观整个互联网对于「中文歌声合成」或「中文虚拟歌手」常简称为中V或VC相关信息进行较为系统、全面地整理收集的主要有以下几个网站
@ -31,7 +36,7 @@
## 技术架构
参见[CVSA文档](https://cvsa.gitbook.io/)。
参见[CVSA文档](https://docs.projectcvsa.com/)。
## 开放许可

View File

@ -20,7 +20,7 @@ class VideoPlayDataset(Dataset):
self.valid_series = [s for s in self.series_dict.values() if len(s['abs_time']) > 1]
self.term = term
# Set time window based on term
self.time_window = 1000 * 24 * 3600 if term == 'long' else 7 * 24 * 3600
self.time_window = 1000 * 24 * 3600 if term == 'long' else 3 * 24 * 3600
MINUTE = 60
HOUR = 3600
DAY = 24 * HOUR
@ -37,6 +37,7 @@ class VideoPlayDataset(Dataset):
]
else:
self.feature_windows = [
#( 5 * MINUTE, 0 * MINUTE),
( 15 * MINUTE, 0 * MINUTE),
( 40 * MINUTE, 0 * MINUTE),
( 1 * HOUR, 0 * HOUR),
@ -45,7 +46,7 @@ class VideoPlayDataset(Dataset):
( 3 * HOUR, 0 * HOUR),
#( 6 * HOUR, 3 * HOUR),
( 6 * HOUR, 0 * HOUR),
(18 * HOUR, 12 * HOUR),
#(18 * HOUR, 12 * HOUR),
#( 1 * DAY, 6 * HOUR),
( 1 * DAY, 0 * DAY),
#( 2 * DAY, 1 * DAY),

View File

@ -4,20 +4,20 @@ from model import CompactPredictor
import torch
def main():
model = CompactPredictor(10).to('cpu', dtype=torch.float32)
model.load_state_dict(torch.load('./pred/checkpoints/long_term.pt'))
model = CompactPredictor(15).to('cpu', dtype=torch.float32)
model.load_state_dict(torch.load('./pred/checkpoints/model_20250320_0045.pt'))
model.eval()
# inference
initial = 997029
initial = 999704
last = initial
start_time = '2025-03-17 00:13:17'
for i in range(1, 120):
hour = i / 0.5
start_time = '2025-03-19 22:00:42'
for i in range(1, 48):
hour = i / 6
sec = hour * 3600
time_d = np.log2(sec)
data = [time_d, np.log2(initial+1), # time_delta, current_views
6.111542, 8.404707, 10.071566, 11.55888, 12.457823,# grows_feat
0.009225, 0.001318, 28.001814# time_feat
4.857981, 6.29067, 6.869476, 6.58392, 6.523051, 8.242355, 8.841574, 10.203909, 11.449314, 12.659556, # grows_feat
0.916956, 0.416708, 28.003162 # time_feat
]
np_arr = np.array([data])
tensor = torch.from_numpy(np_arr).to('cpu', dtype=torch.float32)
@ -25,7 +25,7 @@ def main():
num = output.detach().numpy()[0][0]
views_pred = int(np.exp2(num)) + initial
current_time = datetime.datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S') + datetime.timedelta(hours=hour)
print(current_time.strftime('%m-%d %H:%M:%S'), views_pred, views_pred - last)
print(current_time.strftime('%m-%d %H:%M'), views_pred, views_pred - last)
last = views_pred
if __name__ == '__main__':

View File

@ -38,7 +38,7 @@ def train(model, dataloader, device, epochs=100):
scheduler = torch.optim.lr_scheduler.OneCycleLR(optimizer, max_lr=1e-3,
total_steps=len(dataloader)*30)
# Huber loss
criterion = asymmetricHuberLoss(delta=1.0, beta=2.1)
criterion = asymmetricHuberLoss(delta=1.0, beta=2.2)
model.train()
global_step = 0
@ -100,7 +100,7 @@ if __name__ == "__main__":
device = 'mps'
# Initialize dataset and model
dataset = VideoPlayDataset('./data/pred', './data/pred/publish_time.csv', 'short')
dataset = VideoPlayDataset('./data/pred', './data/pred/publish_time.csv', 'short', 712)
dataloader = DataLoader(dataset, batch_size=128, shuffle=True, collate_fn=collate_fn)
# Get feature dimension

View File

@ -1,27 +1,65 @@
import { createHandlers } from "./utils.ts";
import Argon2id from "@rabbit-company/argon2id";
import { object, string, ValidationError } from "yup";
import type { Context } from "hono";
import type { Bindings, BlankEnv, BlankInput } from "hono/types";
import type { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
const RegistrationBodySchema = object({
username: string().trim().required("Username is required").max(50, "Username cannot exceed 50 characters"),
password: string().required("Password is required"),
nickname: string().optional(),
});
type ContextType = Context<BlankEnv & { Bindings: Bindings }, "/user", BlankInput>;
export const userExists = async (username: string, client: Client) => {
const query = `
SELECT * FROM users WHERE username = $1
`;
const result = await client.queryObject(query, [username]);
return result.rows.length > 0;
}
export const registerHandler = createHandlers(async (c: ContextType) => {
const client = c.get("dbCred");
export const registerHandler = createHandlers(async (c) => {
try {
const client = c.get("dbCred");
const body = await c.req.json();
const username = body.username;
const password = body.password;
const body = await RegistrationBodySchema.validate(await c.req.json());
const { username, password, nickname } = body;
if (await userExists(username, client)) {
return c.json({
message: `User "${username}" already exists.`,
}, 400);
}
const hash = await Argon2id.hashEncoded(password);
const query = `
INSERT INTO users (username, password) VALUES ($1, $2)
INSERT INTO users (username, password, nickname) VALUES ($1, $2, $3)
`;
await client.queryObject(query, [username, hash]);
await client.queryObject(query, [username, hash, nickname || null]);
return c.json({
success: true,
message: "Registered",
});
message: `User "${username}" registered successfully.`,
}, 201);
} catch (e) {
if (e instanceof SyntaxError) {
return c.json({ error: "Invalid JSON" }, 400);
}
else {
return c.json({ error: (e as Error).message }, 500);
}
if (e instanceof ValidationError) {
return c.json({
message: "Invalid registration data.",
errors: e.errors,
}, 400);
} else if (e instanceof SyntaxError) {
return c.json({
message: "Invalid JSON in request body.",
}, 400);
} else {
console.error("Registration error:", e);
return c.json({
message: "An unexpected error occurred during registration.",
error: (e as Error).message,
}, 500);
}
}
});

View File

@ -19,6 +19,6 @@ export default defineConfig({
allow: [".", "../../"],
},
},
plugins: [tsconfigPaths()],
plugins: [tsconfigPaths()]
},
});

View File

@ -1 +1 @@
export const VERSION = "1.2.6";
export const VERSION = "1.2.7";

View File

@ -1,23 +1,25 @@
{
"name": "frontend",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/tailwind": "^6.0.2",
"astro": "^5.5.5",
"autoprefixer": "^10.4.21",
"pg": "^8.11.11",
"postcss": "^8.5.3",
"tailwindcss": "^3.0.24",
"vite-tsconfig-paths": "^5.1.4"
},
"devDependencies": {
"@types/pg": "^8.11.11"
}
"name": "frontend",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/tailwind": "^6.0.2",
"argon2id": "^1.0.1",
"astro": "^5.5.5",
"autoprefixer": "^10.4.21",
"pg": "^8.11.11",
"postcss": "^8.5.3",
"tailwindcss": "^3.0.24",
"vite-tsconfig-paths": "^5.1.4"
},
"devDependencies": {
"@rollup/plugin-wasm": "^6.2.2",
"@types/pg": "^8.11.11"
}
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,219 @@
<script lang="ts">
import { N_ARRAY } from "src/const"; // 假设你的常量文件现在导出 N_ARRAY
function generateRandomBigInt(min: bigint, max: bigint) {
const range = max - min;
const bitLength = range.toString(2).length;
const byteLength = Math.ceil(bitLength / 8);
const mask = (1n << BigInt(bitLength)) - 1n; // 用于截断的掩码
let result;
do {
const randomBytes = new Uint8Array(byteLength);
crypto.getRandomValues(randomBytes);
result = 0n;
for (let i = 0; i < byteLength; i++) {
result = (result << 8n) | BigInt(randomBytes[i]);
}
result = result & mask; // 确保不超过 bitLength 位
} while (result > range);
return min + result;
}
function generateValidG(N: bigint) {
if (N <= 4n) throw new Error("N must be > 4");
while (true) {
const r = generateRandomBigInt(2n, N - 1n);
const g = (r * r) % N;
if (g !== 1n && g !== 0n && g !== N - 1n) {
return g;
}
}
}
const workerContent = `addEventListener("message", async (event) => {
const { g, N, difficulty } = event.data;
function pow(base, exponent, mod) {
let result = 1n;
base = base % mod;
while (exponent > 0n) {
if (exponent % 2n === 1n) {
result = (result * base) % mod;
}
base = (base * base) % mod;
exponent = exponent / 2n;
}
return result;
}
function computeVDFWithProgress(g, N, T, postProgress) {
let result = g;
let latestTime = performance.now();
for (let i = 0n; i < T; i++) {
result = (result * result) % N;
if (performance.now() - latestTime > 16) {
postProgress(Number(i * 100n) / Number(T));
latestTime = performance.now();
}
}
postProgress(100);
return result;
}
const startTime = performance.now();
const result = computeVDFWithProgress(g, N, difficulty, (progress) => {
postMessage({ type: "progress", N: N.toString(), difficulty: difficulty.toString(), progress });
});
const endTime = performance.now();
const timeTaken = endTime - startTime;
postMessage({ type: "result", N: N.toString(), difficulty: difficulty.toString(), time: timeTaken, result });
});
`;
let isBenchmarking = false;
interface BenchmarkResult {
N: bigint;
difficulty: bigint;
time: number;
}
let benchmarkResults: BenchmarkResult[] = [];
let currentProgress = 0;
let currentN: bigint | null = null;
let currentDifficulty: bigint | null = null;
let worker: Worker | null = null;
let currentTestIndex = 0;
const difficulties = [BigInt(20000), BigInt(200000)];
const testCombinations: { N: bigint; difficulty: bigint }[] = [];
// 创建需要测试的 N 和难度的组合
N_ARRAY.forEach((n) => {
difficulties.forEach((difficulty) => {
testCombinations.push({ N: n, difficulty });
});
});
async function startBenchmark() {
if (testCombinations.length === 0) {
alert("No N values provided in src/const N_ARRAY.");
return;
}
isBenchmarking = true;
benchmarkResults = [];
currentTestIndex = 0;
const { N, difficulty } = testCombinations[currentTestIndex];
const g = generateValidG(N);
let blob = new Blob([workerContent], { type: "text/javascript" });
worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = (event) => {
const { type, N: resultNStr, difficulty: resultDifficultyStr, time, progress } = event.data;
const resultN = BigInt(resultNStr);
const resultDifficulty = BigInt(resultDifficultyStr);
if (type === "progress") {
currentProgress = progress;
currentN = resultN;
currentDifficulty = resultDifficulty;
} else if (type === "result") {
benchmarkResults = [...benchmarkResults, { N: resultN, difficulty: resultDifficulty, time }];
currentProgress = 0;
currentTestIndex++;
if (currentTestIndex < testCombinations.length) {
// 继续下一个测试组合
const nextTest = testCombinations[currentTestIndex];
const nextG = generateValidG(nextTest.N);
worker?.postMessage({ g: nextG, N: nextTest.N, difficulty: nextTest.difficulty });
} else {
// 所有测试完毕
isBenchmarking = false;
worker?.terminate();
worker = null;
currentN = null;
currentDifficulty = null;
}
}
};
// 开始第一个测试
worker.postMessage({ g, N, difficulty });
}
function getAccumulatedTime() {
return benchmarkResults.reduce((acc, result) => acc + result.time, 0);
}
function getAccumulatedDifficulty() {
return benchmarkResults.reduce((acc, result) => acc + Number(result.difficulty), 0);
}
function getSpeed() {
return (getAccumulatedDifficulty() / getAccumulatedTime()) * 1000;
}
</script>
<div
class="md:bg-zinc-50 md:dark:bg-zinc-800 p-6 rounded-md md:border dark:border-zinc-700 mb-6 mt-8 md:w-2/3 lg:w-1/2 xl:w-[37%] md:mx-auto"
>
<h2 class="text-xl font-bold mb-4 text-zinc-800 dark:text-zinc-200">VDF Benchmark</h2>
{#if !isBenchmarking}
<button
class="bg-blue-500 hover:bg-blue-600 duration-100 text-white font-bold py-2 px-4 rounded"
on:click={startBenchmark}
>
Start Benchmark
</button>
{/if}
{#if isBenchmarking}
<p class="mb-8 text-zinc-700 dark:text-zinc-300">
Benchmarking in progress... ({currentTestIndex + 1}/{testCombinations.length})
</p>
{#if currentN !== null && currentDifficulty !== null}
<p class="mb-2 text-zinc-700 dark:text-zinc-300">N Bits: {currentN.toString(2).length}</p>
<p class="mb-2 text-zinc-700 dark:text-zinc-300">Difficulty: {currentDifficulty}</p>
<div class="w-full bg-zinc-300 dark:bg-neutral-700 rounded-full h-1 relative overflow-hidden">
<div
class="bg-black dark:bg-white h-full rounded-full relative"
style="width: {currentProgress}%"
></div>
</div>
{/if}
{/if}
{#if benchmarkResults.length > 0 && !isBenchmarking}
<h3 class="text-lg font-bold mt-4 mb-2 text-zinc-800 dark:text-zinc-200">Benchmark Results</h3>
<p class="mb-4 text-zinc-700 dark:text-zinc-300 text-sm">
<b>Summary:</b>
{getAccumulatedDifficulty()}
calculations done in {getAccumulatedTime().toFixed(1)}ms,
speed: {getSpeed().toFixed(2)} op/s
</p>
<table class="w-full text-sm text-left rtl:text-right text-zinc-500 dark:text-zinc-400">
<thead
class="text-xs text-zinc-700 uppercase dark:text-zinc-400 border-b border-zinc-400 dark:border-zinc-500"
>
<tr>
<th scope="col" class="px-6 py-3">Time (ms)</th>
<th scope="col" class="px-6 py-3">N (bits)</th>
<th scope="col" class="px-6 py-3">T (log10)</th>
</tr>
</thead>
<tbody>
{#each benchmarkResults as result}
<tr class="border-b dark:border-zinc-700 border-zinc-200">
<td class="px-6 py-4 font-medium text-zinc-900 whitespace-nowrap dark:text-white"
>{result.time.toFixed(2)}</td
>
<td class="px-6 py-4 font-medium text-zinc-900 whitespace-nowrap dark:text-white"
>{result.N.toString(2).length}</td
>
<td class="px-6 py-4 font-medium text-zinc-900 whitespace-nowrap dark:text-white"
>{Math.log10(Number(result.difficulty)).toFixed(2)}</td
>
</tr>
{/each}
</tbody>
</table>
{/if}
</div>

View File

@ -0,0 +1,13 @@
const N_1024 = BigInt("129023318876534346704360951712586568674758913224876821534686030409476129469193481910786173836188085930974906857867802234113909470848523288588793477904039083513378341278558405407018889387577114155572311708428733260891448259786041525189132461448841652472631435226032063278124857443496954605482776113964107326943")
const N_2048 = BigInt("23987552118069940970878653610463005981599204778388399885550631951871084945075866571231062435627294546200946516668493107358732376187241747090707087544153108117326163500579370560400058549184722138636116585329496684877258304519458316233517215780035360354808658620079068489084797380781488445517430961701007542207001544091884001098497324624368085682074645221148086075871342544591022944384890014176612259729018968864426602901247715051556212559854689574013699665035317257438297910516976812428036717668766321871780963854649899276251822244719887233041422346429752896925499321431273560130952088238625622570366815755926694833109")
const N_1792 = BigInt("23987552118069940970878653610463005981599204778388399885550631951871084945075866571231062435627294546200946516668493107358732376187241747090707087544153108117326163500579370560400058549184722138636116585329496684877258304519458316233517215780035360354808658620079068489084797380781488445517430961701007542207001544091884001098497324624368085682074645221148086075871342544591022944384890014176612259729018968864426602901247715051556212559854689574013699665035317257438297910516976812428036717668766321871780963854649899276251822244719887233041422346429752896925499321431273560130952088238625622570366815755926694833109")
const N_1536 = BigInt("1694330250214463438908848400950857073137355630337290254958754184668036770489801447652464038218330711288158361242955860326168191830448553710492926795708495297280933502917598985378231124113971732841791156356676046934277122699383776036675381503510992810963611269045078440132744168908318454891211962146563551929591147663448816841024591820348784855441153716551049843185172472891407933214238000452095646085222944171689449292644270516031799660928056315886939284985905227")
const N_3072 = BigInt("4432919939296042464443862503456460073874727648022810391370558006281079088795179408238989283371442564716849343712703672836423961818025813387453469700639513190304802553045342607888612037304066433501317127429264242784608682213025490491212489901736408833027611579294436675682774458141490718959615677971745638214649336218217578937534746160749039668886450447773018369168258067682196337978245372237157696236362344796867228581553446331915147012787367438751646936429739232247148712001806846526947508445039707404287951727838234648917450736371192435665040644040487427986702098273581288935278964444790007953559851323281510927332862225214878776790605026472021669614552481167977412450477230442015077669503312683966631454347169703030544483487968842349634064181183599641180349414682042575010303056241481622837185325228233789954078775053744988023738762706404546546146837242590884760044438874357295029411988267287001033032827035809135092270843")
const N_4096 = BigInt("703671044356805218391078271512201582198770553281951369783674142891088501340774249238173262580562112786670043634665390581120113644316651934154746357220932310140476300088580654571796404198410555061275065442553506658401183560336140989074165998202690496991174269748740565700402715364422506782445179963440819952745241176450402011121226863984008975377353558155910994380700267903933205531681076494639818328879475919332604951949178075254600102192323286738973253864238076198710173840170988339024438220034106150475640983877458155141500313471699516670799821379238743709125064098477109094533426340852518505385314780319279862586851512004686798362431227795743253799490998475141728082088984359237540124375439664236138519644100625154580910233437864328111620708697941949936338367445851449766581651338876219676721272448769082914348242483068204896479076062102236087066428603930888978596966798402915747531679758905013008059396214343112694563043918465373870648649652122703709658068801764236979191262744515840224548957285182453209028157886219424802426566456408109642062498413592155064289314088837031184200671561102160059065729282902863248815224399131391716503171191977463328439766546574118092303414702384104112719959325482439604572518549918705623086363111")
export const N_ARRAY = [N_1024, N_1536, N_1792, N_2048, N_3072, N_4096];

View File

@ -31,7 +31,8 @@
## 技术架构
参见[CVSA文档](https://cvsa.gitbook.io/)。
参见[CVSA文档](https://docs.projectcvsa.com/)。
## 开放许可

View File

@ -7,7 +7,7 @@ import "../styles/global.css";
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CVSA 前端</title>
<title>中V档案馆</title>
</head>
<body class="dark:bg-zinc-900 dark:text-zinc-100">
<slot />

View File

@ -0,0 +1,8 @@
---
import VDFtester from "@components/VDFtester.svelte";
import Layout from "@layouts/Layout.astro";
---
<Layout>
<VDFtester client:load />
</Layout>