华为校招&实习机试在线OJ刷题系统 | 在线OJ注册使用教程
目录
题目
思路
Code
题目
题目内容:
在轻量化大模型推理引擎中,Transformer 的隐藏状态会在多个子模块之间不断传递。为了减少内存读写、提升推理效率,实际工程中常常会将多个操作融合到同一个算子中,例如:残差连接 Residual Add、RMSNorm 归一化、逐元素缩放 Scale、逐元素偏置 Bias、Padding Mask 处理。本题要求按照如下计算步骤,实现一个简化版的 Masked Fused RMSNorm 模块。
计算步骤:对于第 $k$ 个输入样本向量 $x_k$,给定残差向量 $r_k$、残差系数 $\alpha_k$、有效长度 $l_k$、缩放向量 $\gamma$、偏置向量 $\beta$、数值稳定项 $\epsilon$。
1. 残差融合:$h_{k,i} = x_{k,i} + \alpha_k \cdot r_{k,i}$
2. 有效部分的 RMS 和 energy:仅前 $l_k$ 个位置为有效位置,后续位置视为 padding,不参与 RMS 和 energy 计算。$energy_k = \sum_{i=1}^{l_k} h_{k,i}^2$,$RMS_k = \sqrt{energy_k / d_k + \epsilon}$,其中 $d_k = \max(l_k, 1)$
3. 归一化输出:对于输出向量 $y_k$:有效位置($1 \le i \le l_k$):$y_{k,i} = \gamma_i \cdot h_{k,i} / RMS_k + \beta_i$;无效位置($l_k < i \le n$):$y_{k,i} = 0$
任务说明:给定 $m$ 个样本,请对每个样本计算并输出:Masked Fused RMSNorm 后的结果向量 $y_k$、该样本有效部分的 $RMS_k$、该样本有效部分的平方和 $energy_k$。
输入格式:第一行三个数 m、n、eps,分别表示样本个数、每个向量的最大长度、RMSNorm 的数值稳定项;第二行 n 个浮点数,依次为缩放向量 $\gamma_1, \gamma_2, \dots, \gamma_n$;第三行 n 个浮点数,依次为偏置向量 $\beta_1, \beta_2, \dots, \beta_n$;随后 m 行,每行一个样本,格式为:l alpha x_1 x_2 … x_n r_1 r_2 … r_n,其中 l 是当前样本的有效长度,alpha 是残差系数,之后依次是输入向量 x 的 n 个值和残差向量 r 的 n 个值。
数据范围:$1 \le m \le 200$,$1 \le n \le 256$,$0 \le l_k \le n$,$0 \le \epsilon \le 1$,$-10^8 \le x_{k,i}, r_{k,i} \le 10^8$,$-10^3 \le \gamma_i, \beta_i \le 10^3$,$-10^3 \le \alpha_k \le 10^3$。
输出格式:共 m 行,每行依次是 n 个结果、一个 |、RMS、一个 |、energy。相邻数据之间只用一个空格,| 两侧也各有一个空格。全部浮点数保留两位小数,按标准四舍五入(恰好满五时远离零);绝对值小于 0.005 的数一律写成 0.00,不要输出 -0.00。
样例1
输入:
2 3 0.01 1.0 0.5 -1.0 0.0 1.0 0.0 3 1.0 3.0 4.0 0.0 0.0 0.0 0.0 2 0.5 -1.0 1.0 -1.0 2.0 -2.0 1.0 0.0输出:
1.04 1.69 0.00 | 2.89 | 25.00 0.00 1.00 0.00 | 0.10 | 0.00思路
整体思路:按题面公式对每个样本做三步模拟——残差融合、只算有效位的 energy 和 RMS、再按 mask 输出归一化结果,重点难点全在输出格式上(标准四舍五入、两位小数、去负零)。
1. 读入 m、n、eps,以及长度为 n 的缩放向量 gamma 和偏置向量 beta;之后每个样本一行,依次是有效长度 l、残差系数 alpha、输入向量 x 的 n 个数、残差向量 r 的 n 个数。
2. 残差融合:对每个位置算 h[i] = x[i] + alpha * r[i],得到融合后的隐藏状态。
3. energy 只累加前 l 个有效位置的 h[i] 平方,padding 位置一律不参与;分母 d = max(l, 1),l 为 0 时取 1 防止除零;RMS = sqrt(energy / d + eps)。
4. 输出向量:i >= l 的 padding 位固定输出 0,不能把 beta 加进去;有效位输出 y[i] = gamma[i] * h[i] / RMS + beta[i],gamma 和 beta 按位置下标取,不要误用样本编号。
5. 特殊分支:若 RMS 恰好为 0(有效位全零且 eps 为 0),此时 h[i]/RMS 没有定义,但每个有效 h[i] 必然是 0,归一化项按 0 处理,有效位只保留 beta[i]。
6. 格式:每个数保留两位小数,用标准四舍五入(恰好满五时远离零)。不要用 printf("%.2f")、Python 的 format 或 Java 的 String.format,它们走银行家舍入,会把 0.125、1.125 印成 0.12、1.12。做法是先放大 100 倍按"分"取整:正数 floor(x*100 + 0.5)、负数 ceil(x*100 - 0.5)(C/C++ 用 llround(x*100),Go 用 math.Round),这样恰好满五时远离零。绝对值小于 0.005 的数一律写成 0.00,这样不会出现 -0.00。同一行用单个空格分隔,| 两侧各有一个空格,输出形如 y1 y2 … yn | RMS | energy。
复杂度:每个样本只扫常数遍长度为 n 的向量,总时间 O(m*n),额外空间 O(n)。在 m ≤ 200、n ≤ 256 的范围下线性扫描绰绰有余。关键坑点:一是四舍五入必须满五远离零而不是银行家舍入;二是 |x| < 0.005 直接写 0.00 去掉负零;三是 energy 可达 1e24 量级,必须用 double 累加,塞进 32 位整数会溢出;四是 padding 位不能带 beta,l=0 时整行都是 0 但 RMS 仍要按 eps 输出。
Code
C++
#include <bits/stdc++.h> using namespace std; // 把数四舍五入到两位小数:恰好满五时远离零,|x|<0.005 一律 0.00 string fmt(double x) { if (fabs(x) < 0.005) return "0.00"; // llround 对 .5 一律远离零,不走银行家舍入;先放大 100 倍按分取整 long long cents = llround(x * 100.0); bool neg = cents < 0; unsigned long long a = neg ? (unsigned long long)(-cents) : (unsigned long long)cents; ostringstream oss; if (neg) oss << '-'; oss << a / 100 << '.' << setw(2) << setfill('0') << a % 100; return oss.str(); } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int m, n; double eps; cin >> m >> n >> eps; vector<double> g(n), b(n); for (int i = 0; i < n; i++) cin >> g[i]; // 缩放向量 gamma,按位置下标取 for (int i = 0; i < n; i++) cin >> b[i]; // 偏置向量 beta,按位置下标取 while (m--) { int l; double alpha; cin >> l >> alpha; vector<double> x(n), r(n); for (int i = 0; i < n; i++) cin >> x[i]; for (int i = 0; i < n; i++) cin >> r[i]; // 残差融合:h = x + alpha * r vector<double> h(n); for (int i = 0; i < n; i++) h[i] = x[i] + alpha * r[i]; // energy 只累加前 l 个有效位置的平方,padding 位不算 double energy = 0.0; for (int i = 0; i < l; i++) energy += h[i] * h[i]; // l 为 0 时分母取 1,防止除零 int d = l > 0 ? l : 1; double rms = sqrt(energy / d + eps); for (int i = 0; i < n; i++) { if (i) cout << ' '; if (i >= l) { cout << "0.00"; // padding 位固定 0,不能把 beta 加进来 } else if (rms == 0.0) { // 有效位全零且 eps 为 0:归一化项按 0,只留偏置 cout << fmt(b[i]); } else { cout << fmt(g[i] * h[i] / rms + b[i]); } } cout << " | " << fmt(rms) << " | " << fmt(energy) << '\n'; } return 0; }Java
import java.util.*; public class Solution { // fmt 把数四舍五入到两位小数:恰好满五时远离零,|x|<0.005 一律 0.00 static String fmt(double x) { if (Math.abs(x) < 0.005) return "0.00"; // 小到一定程度按 0.00 输出,顺便去掉负零 // 先放大 100 倍按"分"取整:正数向上取整、负数向下取整,满五远离零; // String.format("%.2f") 走银行家舍入,不能用 double cents = x * 100; long c = cents >= 0 ? (long) Math.floor(cents + 0.5) : (long) Math.ceil(cents - 0.5); String s = String.format("%d.%02d", Math.abs(c) / 100, Math.abs(c) % 100); return c < 0 ? "-" + s : s; } public static void main(String[] args) { Scanner sc = new Scanner(System.in); int m = sc.nextInt(); int n = sc.nextInt(); double eps = sc.nextDouble(); double[] g = new double[n]; // 缩放向量 gamma,按位置下标取 double[] b = new double[n]; // 偏置向量 beta,按位置下标取 for (int i = 0; i < n; i++) g[i] = sc.nextDouble(); for (int i = 0; i < n; i++) b[i] = sc.nextDouble(); StringBuilder sb = new StringBuilder(); for (int k = 0; k < m; k++) { int l = sc.nextInt(); double alpha = sc.nextDouble(); double[] x = new double[n]; double[] r = new double[n]; for (int i = 0; i < n; i++) x[i] = sc.nextDouble(); for (int i = 0; i < n; i++) r[i] = sc.nextDouble(); // 残差融合:h = x + alpha * r double[] h = new double[n]; for (int i = 0; i < n; i++) h[i] = x[i] + alpha * r[i]; // energy 只累加前 l 个有效位置的平方,padding 位不算 double energy = 0.0; for (int i = 0; i < l; i++) energy += h[i] * h[i]; // l 为 0 时分母取 1,防止除零 int d = l > 0 ? l : 1; double rms = Math.sqrt(energy / d + eps); for (int i = 0; i < n; i++) { if (i > 0) sb.append(' '); if (i >= l) { sb.append("0.00"); // padding 位固定 0,不能把 beta 加进来 } else if (rms == 0.0) { // 有效位全零且 eps 为 0:归一化项按 0,只留偏置 sb.append(fmt(b[i])); } else { sb.append(fmt(g[i] * h[i] / rms + b[i])); } } sb.append(" | ").append(fmt(rms)).append(" | ").append(fmt(energy)).append('\n'); } System.out.print(sb); } }Go
package main import ( "bufio" "fmt" "math" "os" ) // format2dp 把数四舍五入到两位小数:恰好满五时远离零,|x|<0.005 一律 0.00 func format2dp(x float64) string { if math.Abs(x) < 0.005 { return "0.00" // 小到一定程度按 0.00 输出,顺便去掉负零 } // math.Round 对 .5 一律远离零,不走银行家舍入;先放大 100 倍按分取整。 // 结果控制在 int64 范围内(测试数据保证),再拆成整数部分和小数部分 c := int64(math.Round(x * 100)) neg := c < 0 if neg { c = -c } s := fmt.Sprintf("%d.%02d", c/100, c%100) if neg { return "-" + s } return s } func main() { // 输入量可能较大,显式放大读入缓冲区 reader := bufio.NewReaderSize(os.Stdin, 1024*1024) var m, n int var eps float64 fmt.Fscan(reader, &m, &n, &eps) g := make([]float64, n) // 缩放向量 gamma,按位置下标取 b := make([]float64, n) // 偏置向量 beta,按位置下标取 for i := 0; i < n; i++ { fmt.Fscan(reader, &g[i]) } for i := 0; i < n; i++ { fmt.Fscan(reader, &b[i]) } w := bufio.NewWriter(os.Stdout) defer w.Flush() for k := 0; k < m; k++ { var l int var alpha float64 fmt.Fscan(reader, &l, &alpha) x := make([]float64, n) r := make([]float64, n) for i := 0; i < n; i++ { fmt.Fscan(reader, &x[i]) } for i := 0; i < n; i++ { fmt.Fscan(reader, &r[i]) } // 残差融合:h = x + alpha * r h := make([]float64, n) for i := 0; i < n; i++ { h[i] = x[i] + alpha*r[i] } // energy 只累加前 l 个有效位置的平方,padding 位不算 energy := 0.0 for i := 0; i < l; i++ { energy += h[i] * h[i] } // l 为 0 时分母取 1,防止除零 d := l if d <= 0 { d = 1 } rms := math.Sqrt(energy/float64(d) + eps) for i := 0; i < n; i++ { if i > 0 { w.WriteByte(' ') } switch { case i >= l: w.WriteString("0.00") // padding 位固定 0,不能把 beta 加进来 case rms == 0.0: // 有效位全零且 eps 为 0:归一化项按 0,只留偏置 w.WriteString(format2dp(b[i])) default: w.WriteString(format2dp(g[i]*h[i]/rms + b[i])) } } fmt.Fprintf(w, " | %s | %s\n", format2dp(rms), format2dp(energy)) } }C
#include <stdio.h> #include <stdlib.h> #include <math.h> /* 把数四舍五入到两位小数:恰好满五时远离零,|x|<0.005 一律 0.00 */ static void fmt(double x, char *buf) { if (fabs(x) < 0.005) { /* 不管正负,小到一定程度都按 0.00 输出,顺便去掉负零 */ sprintf(buf, "0.00"); return; } /* llround 对 .5 一律远离零,不走银行家舍入;先放大 100 倍按分取整 */ long long cents = llround(x * 100.0); if (cents < 0) sprintf(buf, "-%lld.%02lld", (-cents) / 100, (-cents) % 100); else sprintf(buf, "%lld.%02lld", cents / 100, cents % 100); } int main(void) { int m, n; double eps; if (scanf("%d %d %lf", &m, &n, &eps) != 3) return 0; double *g = malloc(sizeof(double) * n); /* 缩放向量 gamma,按位置下标取 */ double *b = malloc(sizeof(double) * n); /* 偏置向量 beta,按位置下标取 */ double *x = malloc(sizeof(double) * n); double *r = malloc(sizeof(double) * n); double *h = malloc(sizeof(double) * n); for (int i = 0; i < n; i++) scanf("%lf", &g[i]); for (int i = 0; i < n; i++) scanf("%lf", &b[i]); for (int k = 0; k < m; k++) { int l; double alpha; scanf("%d %lf", &l, &alpha); for (int i = 0; i < n; i++) scanf("%lf", &x[i]); for (int i = 0; i < n; i++) scanf("%lf", &r[i]); /* 残差融合:h = x + alpha * r */ for (int i = 0; i < n; i++) h[i] = x[i] + alpha * r[i]; /* energy 只累加前 l 个有效位置的平方,padding 位不算 */ double energy = 0.0; for (int i = 0; i < l; i++) energy += h[i] * h[i]; /* l 为 0 时分母取 1,防止除零 */ int d = l > 0 ? l : 1; double rms = sqrt(energy / d + eps); for (int i = 0; i < n; i++) { char buf[64]; if (i) putchar(' '); if (i >= l) { fputs("0.00", stdout); /* padding 位固定 0,不能把 beta 加进来 */ } else if (rms == 0.0) { /* 有效位全零且 eps 为 0:归一化项按 0,只留偏置 */ fmt(b[i], buf); fputs(buf, stdout); } else { fmt(g[i] * h[i] / rms + b[i], buf); fputs(buf, stdout); } } char buf1[64], buf2[64]; fmt(rms, buf1); fmt(energy, buf2); printf(" | %s | %s\n", buf1, buf2); } free(g); free(b); free(x); free(r); free(h); return 0; }Python
import sys import math def fmt(x): # 绝对值不足 0.005 的一律印成 0.00,顺带避免 -0.00 if abs(x) < 0.005: return "0.00" # 放大 100 倍按"分"取整:正数向上取整、负数向下取整, # 恰好满五时远离零,正是题目要的标准四舍五入 cents = x * 100 c = int(math.floor(cents + 0.5)) if cents >= 0 else int(math.ceil(cents - 0.5)) s = f"{abs(c) // 100}.{abs(c) % 100:02d}" return "-" + s if c < 0 else s def main(): data = sys.stdin.read().split() pos = 0 def nxt(): nonlocal pos v = data[pos] pos += 1 return v m = int(nxt()) n = int(nxt()) eps = float(nxt()) g = [float(nxt()) for _ in range(n)] # 缩放向量 gamma,按位置下标取 b = [float(nxt()) for _ in range(n)] # 偏置向量 beta,按位置下标取 out = [] for _ in range(m): l = int(nxt()) alpha = float(nxt()) x = [float(nxt()) for _ in range(n)] r = [float(nxt()) for _ in range(n)] # 残差融合:h = x + alpha * r h = [x[i] + alpha * r[i] for i in range(n)] # energy 只累加前 l 个有效位置的平方,padding 位不算 energy = 0.0 for i in range(l): energy += h[i] * h[i] # l 为 0 时没有有效位,分母取 1 防止除零 d = l if l > 0 else 1 rms = (energy / d + eps) ** 0.5 vals = [] for i in range(n): if i >= l: # padding 位固定输出 0,不能把 beta 加进来 vals.append("0.00") elif rms == 0.0: # 有效位全是 0 且 eps 为 0:RMS 为 0,归一化项按 0,只留偏置 vals.append(fmt(b[i])) else: vals.append(fmt(g[i] * h[i] / rms + b[i])) out.append(" ".join(vals) + " | " + fmt(rms) + " | " + fmt(energy)) sys.stdout.write("\n".join(out) + "\n") main()