Skip to content

Commit 5e870f0

Browse files
authored
Make snippets more robust, correct line numbers (#40)
* Snippets can now shift code left while keeping other indentation. * If lines are specified, a hash needs to be specified. * Update line numbers so they are correct.
1 parent 5d90014 commit 5e870f0

File tree

6 files changed

+97
-54
lines changed

6 files changed

+97
-54
lines changed

blog/2024-11-21-optimizing-matrix-mul/index.md

+9-2
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,9 @@ import { WebGpuKernel } from './snippets/naive.tsx';
128128
With Rust GPU, we specify the inputs as arguments to the kernel and configure them with
129129
[procedural macros](https://doc.Rust-lang.org/reference/procedural-macros.html):
130130

131-
import { RustNaiveInputs } from './snippets/naive.tsx';
131+
import { RustNaiveKernel } from './snippets/naive.tsx';
132132

133-
<RustNaiveInputs/>
133+
<RustNaiveKernel/>
134134

135135
This code looks like normal Rust code but _runs entirely on the GPU._
136136

@@ -301,6 +301,13 @@ improvement over the last kernel.
301301
To stay true to the spirit of Zach's original blog post, we'll wrap things up here and
302302
leave the "fancier" experiments for another time.
303303

304+
### A note on performance
305+
306+
I didn't include performance numbers as I have a different machine than Zach. The
307+
complete runnable code can be [found on
308+
GitHub](https://github.com/Rust-GPU/rust-gpu.github.io/tree/main/blog/2024-11-21-optimizing-matrix-mul/code)
309+
and you can run the benchmarks yourself with `cargo bench`.
310+
304311
## Reflections on porting to Rust GPU
305312

306313
Porting to Rust GPU went quickly, as the kernels Zach used were fairly simple. Most of

blog/2024-11-21-optimizing-matrix-mul/snippets/naive.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,10 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
4242
</CodeBlock>
4343
);
4444

45-
export const RustNaiveInputs: React.FC = () => (
45+
export const RustNaiveKernel: React.FC = () => (
4646
<Snippet
4747
language="rust"
4848
className="text-xs"
49-
metastring="1-5,7"
5049
showLineNumbers
5150
title="Naive kernel with Rust GPU"
5251
>
@@ -59,6 +58,7 @@ export const RustNaiveWorkgroupCount: React.FC = () => (
5958
language="rust"
6059
className="text-xs"
6160
lines="26-34"
61+
hash="8abb43d"
6262
title="Calculating on the CPU how many workgroup dispatches are needed"
6363
>
6464
{RustWorkgroupCount}
@@ -69,7 +69,8 @@ export const RustNaiveDispatch: React.FC = () => (
6969
<Snippet
7070
language="rust"
7171
className="text-xs"
72-
lines="145,147"
72+
lines="152,154"
73+
hash="cbb5295"
7374
strip_leading_spaces
7475
title="Using wgpu on the CPU to dispatch workgroups to the GPU"
7576
>
@@ -78,7 +79,7 @@ export const RustNaiveDispatch: React.FC = () => (
7879
);
7980

8081
export const RustNaiveWorkgroup: React.FC = () => (
81-
<Snippet language="rust" className="text-xs" lines="7">
82+
<Snippet language="rust" className="text-xs" lines="7" hash="7762339">
8283
{RustKernelSource}
8384
</Snippet>
8485
);

blog/2024-11-21-optimizing-matrix-mul/snippets/party.tsx

+13-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import RustWgpuBackend from "!!raw-loader!../code/crates/cpu/matmul/src/backends
77
import RustCpuBackendSource from "!!raw-loader!../code/crates/cpu/matmul/src/backends/cpu.rs";
88

99
export const RustPartySettings: React.FC = () => (
10-
<Snippet language="rust" className="text-xs" lines="3,9,11">
10+
<Snippet language="rust" className="text-xs" lines="3,9,11" hash="47bb656">
1111
{RustKernelSource}
1212
</Snippet>
1313
);
@@ -19,21 +19,28 @@ export const RustIsomorphic: React.FC = () => (
1919
);
2020

2121
export const RustIsomorphicGlam: React.FC = () => (
22-
<Snippet language="rust" lines="15-19" className="text-xs">
22+
<Snippet language="rust" lines="15-19" hash="a3dbf2f" className="text-xs">
2323
{RustIsomorphicSource}
2424
</Snippet>
2525
);
2626

2727
export const RustIsomorphicDeps: React.FC = () => (
28-
<Snippet language="rust" lines="9-20" className="text-xs" title="Cargo.toml">
28+
<Snippet
29+
language="rust"
30+
lines="9-20"
31+
hash="72c14d7"
32+
className="text-xs"
33+
title="Cargo.toml"
34+
>
2935
{RustIsomorphicCargoToml}
3036
</Snippet>
3137
);
3238

3339
export const RustWgpuDimensions: React.FC = () => (
3440
<Snippet
3541
language="rust"
36-
lines="98-111"
42+
lines="108-118"
43+
hash="cbb5295"
3744
className="text-xs"
3845
title="Creating the Dimensions struct on the CPU and writing it to the GPU"
3946
>
@@ -42,13 +49,13 @@ export const RustWgpuDimensions: React.FC = () => (
4249
);
4350

4451
export const RustCpuBackendHarness: React.FC = () => (
45-
<Snippet language="rust" className="text-xs" lines="30-72">
52+
<Snippet language="rust" className="text-xs" lines="30-79" hash="7ad7cab">
4653
{RustCpuBackendSource}
4754
</Snippet>
4855
);
4956

5057
export const RustCpuBackendTest: React.FC = () => (
51-
<Snippet language="rust" className="text-xs" lines="155-172">
58+
<Snippet language="rust" className="text-xs" lines="174-194" hash="7ad7cab">
5259
{RustCpuBackendSource}
5360
</Snippet>
5461
);

blog/2024-11-21-optimizing-matrix-mul/snippets/workgroup_256.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import RustKernelSource from "!!raw-loader!../code/crates/gpu/workgroup_256/src/
44
import VariantsSource from "!!raw-loader!../code/crates/cpu/matmul/src/variants.rs";
55

66
export const RustWorkgroup256Workgroup: React.FC = () => (
7-
<Snippet language="rust" className="text-xs" lines="7">
7+
<Snippet language="rust" className="text-xs" lines="7" hash="56b3ae8">
88
{RustKernelSource}
99
</Snippet>
1010
);
@@ -13,7 +13,8 @@ export const RustWorkgroup256WorkgroupCount: React.FC = () => (
1313
<Snippet
1414
language="rust"
1515
className="text-xs"
16-
lines="51-64"
16+
lines="51-65"
17+
hash="8abb43d"
1718
title="Calculating how many workgroup dispatches are needed on the CPU"
1819
>
1920
{VariantsSource}

blog/2024-11-21-optimizing-matrix-mul/snippets/workgroup_2d.tsx

+1-31
Original file line numberDiff line numberDiff line change
@@ -15,44 +15,14 @@ export const RustWorkgroup2d: React.FC = () => (
1515
</Snippet>
1616
);
1717

18-
/*
19-
export const RustWorkgroup2d: React.FC = () => (
20-
<Snippet
21-
language="rust"
22-
className="text-xs"
23-
lines="7-8,15-16"
24-
title="2D workgroup kernel with Rust GPU"
25-
>
26-
{RustKernelSource}
27-
</Snippet>
28-
);
29-
*/
30-
31-
export const RustWorkgroup2dWorkgroup: React.FC = () => (
32-
<Snippet language="rust" className="text-xs" lines="7">
33-
{RustKernelSource}
34-
</Snippet>
35-
);
36-
3718
export const RustWorkgroup2dWorkgroupCount: React.FC = () => (
3819
<Snippet
3920
language="rust"
4021
className="text-xs"
4122
lines="82-94"
23+
hash="8abb43d"
4224
title="Calculating how many workgroup dispatches are needed on the CPU"
4325
>
4426
{VariantsSource}
4527
</Snippet>
4628
);
47-
48-
export const RustWorkgroup2dWgpuDispatch: React.FC = () => (
49-
<Snippet
50-
language="rust"
51-
className="text-xs"
52-
lines="144,145,147"
53-
strip_leading_spaces
54-
title="Using wgpu on the CPU to dispatch to the GPU"
55-
>
56-
{WgpuBackendSource}
57-
</Snippet>
58-
);

src/components/Snippet/index.tsx

+66-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from "react";
1+
import React, { useEffect, useState } from "react";
22
import CodeBlock from "@theme/CodeBlock";
33

44
interface SnippetProps extends React.ComponentProps<typeof CodeBlock> {
@@ -8,27 +8,73 @@ interface SnippetProps extends React.ComponentProps<typeof CodeBlock> {
88
lines?: string;
99
omitted_placeholder?: string;
1010
strip_leading_spaces?: boolean;
11+
/**
12+
* Optional short hash of the content (first N characters of SHA-256),
13+
* required only when `lines` is specified.
14+
*/
15+
hash?: string;
1116
}
1217

1318
/**
1419
* A component for rendering a snippet of code, optionally filtering lines,
15-
* showing ellipses for omissions, and stripping all leading spaces.
20+
* showing ellipses for omissions, stripping leading spaces, and validating hash.
1621
*/
1722
const Snippet: React.FC<SnippetProps> = ({
1823
children,
1924
lines,
2025
omitted_placeholder = "...",
2126
strip_leading_spaces = false,
27+
hash,
2228
...props
2329
}) => {
30+
const [error, setError] = useState<string | null>(null);
31+
2432
if (typeof children !== "string") {
25-
console.error(
33+
throw new Error(
2634
"Snippet expects children to be a string containing the file content."
2735
);
28-
return null;
2936
}
3037

31-
// Parse the `linesToInclude` metadata string into an array of line numbers.
38+
/**
39+
* Utility function to compute the SHA-256 hash of a string.
40+
* @param content The input string
41+
* @returns Promise resolving to a hex-encoded hash
42+
*/
43+
const computeHash = async (content: string): Promise<string> => {
44+
const encoder = new TextEncoder();
45+
const data = encoder.encode(content);
46+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
47+
return Array.from(new Uint8Array(hashBuffer))
48+
.map((byte) => byte.toString(16).padStart(2, "0"))
49+
.join("");
50+
};
51+
52+
useEffect(() => {
53+
if (lines) {
54+
computeHash(children).then((computedHash) => {
55+
const shortHash = computedHash.slice(0, 7); // Use 7 characters for the short hash
56+
57+
if (!hash) {
58+
setError(
59+
`The \`hash\` prop is required when \`lines\` is specified.\n` +
60+
`Provide the following hash as the \`hash\` prop: ${shortHash}`
61+
);
62+
} else if (shortHash !== hash) {
63+
setError(
64+
`Snippet hash mismatch.\n` +
65+
`Specified: ${hash}, but content is: ${shortHash} (full hash: ${computedHash}).\n` +
66+
`Check if the line numbers are still relevant and update the hash.`
67+
);
68+
}
69+
});
70+
}
71+
}, [children, lines, hash]);
72+
73+
if (error) {
74+
throw new Error(error);
75+
}
76+
77+
// Parse the `lines` metadata string into an array of line numbers.
3278
const parseLineRanges = (metaString?: string): number[] => {
3379
if (!metaString) return [];
3480
return metaString.split(",").flatMap((range) => {
@@ -46,16 +92,27 @@ const Snippet: React.FC<SnippetProps> = ({
4692
if (lines.length === 0) return content; // If no specific lines are specified, return full content.
4793

4894
const includedContent: string[] = [];
95+
96+
// Filter lines and find the minimum indentation
97+
const selectedLines = lines
98+
.map((line) => allLines[line - 1] || "")
99+
.filter((line) => line.trim().length > 0); // Ignore blank lines
100+
101+
const minIndent = selectedLines.reduce((min, line) => {
102+
const indentMatch = line.match(/^(\s*)\S/);
103+
const indentLength = indentMatch ? indentMatch[1].length : 0;
104+
return Math.min(min, indentLength);
105+
}, Infinity);
106+
49107
lines.forEach((line, index) => {
50108
if (index > 0 && lines[index - 1] < line - 1) {
51109
includedContent.push(omitted_placeholder); // Add placeholder for omitted lines
52110
}
53111

54112
const rawLine = allLines[line - 1] || "";
55-
const formattedLine = strip_leading_spaces
56-
? rawLine.trimStart()
57-
: rawLine;
58-
includedContent.push(formattedLine);
113+
const trimmedLine =
114+
rawLine.trim().length > 0 ? rawLine.slice(minIndent) : rawLine;
115+
includedContent.push(trimmedLine);
59116
});
60117

61118
// Add placeholder if lines at the end are omitted

0 commit comments

Comments
 (0)