From 23f7889edb5b6e6865016597935707d688939834 Mon Sep 17 00:00:00 2001 From: Roman Kashitsyn Date: Sun, 1 Sep 2024 22:28:24 +0200 Subject: [PATCH] post: transposing tensor files --- blogware/render.go | 7 + blogware/symtab.go | 1 + css/tufte.css | 258 +++++++++------ images/33-chunked-metadata.svg | 186 +++++++++++ images/33-floating-metadata.svg | 172 ++++++++++ images/33-safetensors-structure.svg | 428 +++++++++++++++++++++++++ images/33-source.afdesign | Bin 0 -> 34697 bytes images/33-tensorsafe-structure.svg | 437 ++++++++++++++++++++++++++ posts/33-transposing-tensor-files.tex | 251 +++++++++++++++ 9 files changed, 1646 insertions(+), 94 deletions(-) create mode 100644 images/33-chunked-metadata.svg create mode 100644 images/33-floating-metadata.svg create mode 100644 images/33-safetensors-structure.svg create mode 100644 images/33-source.afdesign create mode 100644 images/33-tensorsafe-structure.svg create mode 100644 posts/33-transposing-tensor-files.tex diff --git a/blogware/render.go b/blogware/render.go index 52cb462..109c714 100644 --- a/blogware/render.go +++ b/blogware/render.go @@ -636,6 +636,13 @@ func renderGenericEnv(rc *RenderingCtx, buf *strings.Builder, env Env) error { return err } buf.WriteString(``) + case SymChecklist: + newRc.parent = UnorderedListCtx + buf.WriteString(``) case SymVerbatim: fmt.Fprintf(buf, `
`, optsToCSSClasses(env.opts))
 		for _, n := range env.body {
diff --git a/blogware/symtab.go b/blogware/symtab.go
index def6e29..4f08527 100644
--- a/blogware/symtab.go
+++ b/blogware/symtab.go
@@ -98,6 +98,7 @@ var (
 	SymAbstract    = BuiltinEnv("abstract")
 	SymEnumerate   = BuiltinEnv("enumerate")
 	SymItemize     = BuiltinEnv("itemize")
+	SymChecklist   = BuiltinEnv("checklist")
 	SymFigure      = BuiltinEnv("figure")
 	SymTabular     = BuiltinEnv("tabular")
 	SymTabularS    = BuiltinEnv("tabular*")
diff --git a/css/tufte.css b/css/tufte.css
index a8e19bb..a14f2dc 100644
--- a/css/tufte.css
+++ b/css/tufte.css
@@ -6,7 +6,8 @@
     font-family: "Yanone Kaffeesatz";
     font-style: normal;
     font-weight: 400;
-    src: local(Yanone Kaffeesatz),
+    src:
+        local(Yanone Kaffeesatz),
         url("/fonts/YanoneKaffeesatz-Regular.otf") format("opentype");
     font-display: swap;
 }
@@ -15,7 +16,8 @@
     font-family: "Yanone Kaffeesatz";
     font-style: normal;
     font-weight: 800;
-    src: local(Yanone Kaffeesatz),
+    src:
+        local(Yanone Kaffeesatz),
         url("/fonts/YanoneKaffeesatz-Bold.otf") format("opentype");
     font-display: swap;
 }
@@ -24,7 +26,8 @@
     font-family: "Libertinus Serif";
     font-style: normal;
     font-weight: 400;
-    src: local(Libertinum Serif),
+    src:
+        local(Libertinum Serif),
         local(Linux Libertine O),
         url("/fonts/LibertinusSerif-Regular.otf") format("opentype"),
         url("/fonts/LibertinusSerif-Regular.woff2") format("woff2");
@@ -35,7 +38,8 @@
     font-family: "Libertinus Serif";
     font-style: italic;
     font-weight: 400;
-    src: local(Libertinus Serif Italic),
+    src:
+        local(Libertinus Serif Italic),
         url("/fonts/LibertinusSerif-Italic.otf") format("opentype"),
         url("/fonts/LibertinusSerif-Italic.woff2") format("woff2");
     font-display: swap;
@@ -45,7 +49,8 @@
     font-family: "Libertinus Serif";
     font-style: italic;
     font-weight: 400;
-    src: local(Libertinus Serif Italic),
+    src:
+        local(Libertinus Serif Italic),
         url("/fonts/LibertinusSerif-Italic.otf") format("opentype"),
         url("/fonts/LibertinusSerif-Italic.woff2") format("woff2");
     font-display: swap;
@@ -55,7 +60,8 @@
     font-family: "Libertinus Sans";
     font-style: normal;
     font-weight: normal;
-    src: local("Libertinus Sans"),
+    src:
+        local("Libertinus Sans"),
         local("Linux Biolinum O"),
         url("/fonts/LibertinusSans-Regular.otf") format("opentype"),
         url("/fonts/LibertinusSans-Regular.woff2") format("woff2");
@@ -66,7 +72,8 @@
     font-family: "Libertinus Sans";
     font-style: italic;
     font-weight: normal;
-    src: local("Libertinus Sans Italic"),
+    src:
+        local("Libertinus Sans Italic"),
         local("Linux Biolinum O"),
         url("/fonts/LibertinusSans-Italic.otf") format("opentype"),
         url("/fonts/LibertinusSans-Italic.woff2") format("woff2");
@@ -76,7 +83,8 @@
 @font-face {
     font-family: "Libertinus Sans";
     font-weight: bold;
-    src: local("Libertinus Sans Bold"),
+    src:
+        local("Libertinus Sans Bold"),
         local("Linux Biolinum O"),
         url("/fonts/LibertinusSans-Bold.otf") format("opentype"),
         url("/fonts/LibertinusSans-Bold.woff2") format("woff2");
@@ -87,7 +95,8 @@
     font-family: "Libertinus Keyboard";
     font-style: normal;
     font-weight: normal;
-    src: local("Libertinus Keyboard"),
+    src:
+        local("Libertinus Keyboard"),
         url("/fonts/LibertinusKeyboard-Regular.otf") format("opentype"),
         url("/fonts/LibertinusKeyboard-Regular.woff2") format("woff2");
     font-display: swap;
@@ -105,7 +114,8 @@
     font-family: "JetBrains Mono";
     font-style: normal;
     font-weight: normal;
-    src: local(JetBrains Mono),
+    src:
+        local(JetBrains Mono),
         url("/fonts/JetBrainsMono-Regular.woff2") format("woff2");
     font-display: swap;
 }
@@ -114,7 +124,8 @@
     font-family: "JetBrains Mono";
     font-style: normal;
     font-weight: bold;
-    src: local(JetBrains Mono),
+    src:
+        local(JetBrains Mono),
         url("/fonts/JetBrainsMono-Bold.woff2") format("woff2");
     font-display: swap;
 }
@@ -123,17 +134,18 @@
     font-family: "JetBrains Mono";
     font-style: italic;
     font-weight: normal;
-    src: local(JetBrains Mono),
+    src:
+        local(JetBrains Mono),
         url("/fonts/JetBrainsMono-Italic.woff2") format("woff2");
     font-display: swap;
 }
 
-
 @font-face {
     font-family: "JetBrains Mono";
     font-style: italic;
     font-weight: bold;
-    src: local(JetBrains Mono),
+    src:
+        local(JetBrains Mono),
         url("/fonts/JetBrainsMono-SemiBoldItalic.woff2") format("woff2");
     font-display: swap;
 }
@@ -142,14 +154,15 @@
     font-family: "Comic Sans Mono";
     font-style: normal;
     font-weight: 400;
-    src: url("/fonts/ComicMono.ttf") format("truetype")
+    src: url("/fonts/ComicMono.ttf") format("truetype");
 }
 
 @font-face {
     font-family: "APL385 Unicode";
     font-style: normal;
     font-weight: 400;
-    src: local(APL385 Unicode),
+    src:
+        local(APL385 Unicode),
         url("/fonts/Apl385.ttf") format("truetype");
     font-display: swap;
 }
@@ -203,16 +216,16 @@ a.icon-link:visited {
     font-family: "Libertinus Sans", sans;
 }
 
-figure>div.source-container {
+figure > div.source-container {
     width: 100%;
 }
 
-div.source-container>pre.good>code,
-div.source-container>pre.bad>code {
+div.source-container > pre.good > code,
+div.source-container > pre.bad > code {
     padding: 1rem;
 }
 
-div.source-container>pre>code {
+div.source-container > pre > code {
     padding: 1rem 0;
 }
 
@@ -224,7 +237,7 @@ ul.posts {
     margin-bottom: 0.1em;
 }
 
-ul.posts>li {
+ul.posts > li {
     list-style: none;
 }
 
@@ -264,21 +277,25 @@ figure.p75 img {
     width: 75%;
 }
 
-ul.arrows>li:before {
+ul.arrows > li:before {
     position: absolute;
     margin-left: -2.5rem;
     content: "➞ ";
 }
 
-ul.arrows>li {
-    list-style: none;
+ul.checklist > li:before {
+    position: absolute;
+    margin-left: -2.5rem;
+    content: "☐ ";
 }
 
-ol.circled>li {
+ul.arrows > li,
+ul.checklist > li,
+ol.circled > li {
     list-style: none;
 }
 
-ol.circled>li:before {
+ol.circled > li:before {
     position: absolute;
     margin-left: -2.5rem;
     content: attr(data-num-glyph);
@@ -311,7 +328,7 @@ ul.toc-level-1 {
     padding-left: 0px;
 }
 
-ul.toc-level-1>li>a {
+ul.toc-level-1 > li > a {
     font-weight: bold;
 }
 
@@ -323,14 +340,43 @@ a:link,
 .tufte-underline,
 .hover-tufte-underline:hover {
     text-decoration: none;
-    background: -webkit-linear-gradient(#fffff8, #fffff8), -webkit-linear-gradient(#fffff8, #fffff8), -webkit-linear-gradient(currentColor, currentColor);
-    background: linear-gradient(#fffff8, #fffff8), linear-gradient(#fffff8, #fffff8), linear-gradient(currentColor, currentColor);
-    -webkit-background-size: 0.05em 1px, 0.05em 1px, 1px 1px;
-    -moz-background-size: 0.05em 1px, 0.05em 1px, 1px 1px;
-    background-size: 0.05em 1px, 0.05em 1px, 1px 1px;
+    background:
+        -webkit-linear-gradient(#fffff8, #fffff8),
+        -webkit-linear-gradient(#fffff8, #fffff8),
+        -webkit-linear-gradient(currentColor, currentColor);
+    background: linear-gradient(#fffff8, #fffff8),
+        linear-gradient(#fffff8, #fffff8),
+        linear-gradient(currentColor, currentColor);
+    -webkit-background-size:
+        0.05em 1px,
+        0.05em 1px,
+        1px 1px;
+    -moz-background-size:
+        0.05em 1px,
+        0.05em 1px,
+        1px 1px;
+    background-size:
+        0.05em 1px,
+        0.05em 1px,
+        1px 1px;
     background-repeat: no-repeat, no-repeat, repeat-x;
-    text-shadow: 0.03em 0 #fffff8, -0.03em 0 #fffff8, 0 0.03em #fffff8, 0 -0.03em #fffff8, 0.06em 0 #fffff8, -0.06em 0 #fffff8, 0.09em 0 #fffff8, -0.09em 0 #fffff8, 0.12em 0 #fffff8, -0.12em 0 #fffff8, 0.15em 0 #fffff8, -0.15em 0 #fffff8;
-    background-position: 0% 93%, 100% 93%, 0% 93%;
+    text-shadow:
+        0.03em 0 #fffff8,
+        -0.03em 0 #fffff8,
+        0 0.03em #fffff8,
+        0 -0.03em #fffff8,
+        0.06em 0 #fffff8,
+        -0.06em 0 #fffff8,
+        0.09em 0 #fffff8,
+        -0.09em 0 #fffff8,
+        0.12em 0 #fffff8,
+        -0.12em 0 #fffff8,
+        0.15em 0 #fffff8,
+        -0.15em 0 #fffff8;
+    background-position:
+        0% 93%,
+        100% 93%,
+        0% 93%;
 }
 
 a.anchor {
@@ -339,7 +385,7 @@ a.anchor {
     text-decoration: none;
 }
 
-a>code {
+a > code {
     text-transform: none;
 }
 
@@ -353,7 +399,7 @@ div.advice {
     padding: 5px 0px;
 }
 
-div.advice>p {
+div.advice > p {
     width: 100%;
     font-style: italic;
     text-align: center;
@@ -381,15 +427,15 @@ footer {
     float: right;
 }
 
-pre.good>code {
+pre.good > code {
     background-color: #dcedc8;
 }
 
-pre.bad>code {
+pre.bad > code {
     background-color: #ffe6d8;
 }
 
-pre.apl>code {
+pre.apl > code {
     font-family: "APL385 Unicode";
     font-size: 1.5rem;
 }
@@ -435,11 +481,11 @@ body {
         color: #ddd;
     }
 
-    pre.good>code {
-        background-color: rgba(38, 205, 77, 0.3)
+    pre.good > code {
+        background-color: rgba(38, 205, 77, 0.3);
     }
 
-    pre.bad>code {
+    pre.bad > code {
         background-color: rgba(255, 106, 105, 0.15);
     }
 
@@ -550,21 +596,21 @@ p.hanging-quote {
 
 /* Chapter Epigraphs */
 
-div.epigraph>blockquote {
+div.epigraph > blockquote {
     margin-top: 1.5em;
     margin-bottom: 3em;
 }
 
-div.epigraph>blockquote,
-div.epigraph>blockquote>p {
+div.epigraph > blockquote,
+div.epigraph > blockquote > p {
     font-style: italic;
 }
 
-div.epigraph>blockquote>footer {
+div.epigraph > blockquote > footer {
     font-style: normal;
 }
 
-div.epigraph>blockquote>footer>cite {
+div.epigraph > blockquote > footer > cite {
     font-style: italic;
 }
 
@@ -586,19 +632,19 @@ blockquote footer {
     font-variant-numeric: oldstyle-nums;
 }
 
-div.container>p,
-section>p,
-section>div.source-container,
-section>div.advice,
-section>footer,
-section>table {
+div.container > p,
+section > p,
+section > div.source-container,
+section > div.advice,
+section > footer,
+section > table {
     width: 55%;
 }
 
 /* 50 + 5 == 55, to be the same width as paragraph */
-section>dl,
-section>ol,
-section>ul {
+section > dl,
+section > ol,
+section > ul {
     width: 50%;
     -webkit-padding-start: 5%;
 }
@@ -643,7 +689,6 @@ a:visited {
 }
 
 @media screen and (-webkit-min-device-pixel-ratio: 0) {
-
     a:link,
     .tufte-underline,
     .hover-tufte-underline:hover {
@@ -653,17 +698,40 @@ a:visited {
 
 /* Adds dark mode */
 @media (prefers-color-scheme: dark) {
-
     a:link,
     .tufte-underline,
     .hover-tufte-underline:hover {
-        text-shadow: 0.03em 0 #151515, -0.03em 0 #151515, 0 0.03em #151515, 0 -0.03em #151515, 0.06em 0 #151515, -0.06em 0 #151515, 0.09em 0 #151515, -0.09em 0 #151515, 0.12em 0 #151515, -0.12em 0 #151515, 0.15em 0 #151515, -0.15em 0 #151515;
+        text-shadow:
+            0.03em 0 #151515,
+            -0.03em 0 #151515,
+            0 0.03em #151515,
+            0 -0.03em #151515,
+            0.06em 0 #151515,
+            -0.06em 0 #151515,
+            0.09em 0 #151515,
+            -0.09em 0 #151515,
+            0.12em 0 #151515,
+            -0.12em 0 #151515,
+            0.15em 0 #151515,
+            -0.15em 0 #151515;
     }
 }
 
 a:link::selection,
 a:link::-moz-selection {
-    text-shadow: 0.03em 0 #b4d5fe, -0.03em 0 #b4d5fe, 0 0.03em #b4d5fe, 0 -0.03em #b4d5fe, 0.06em 0 #b4d5fe, -0.06em 0 #b4d5fe, 0.09em 0 #b4d5fe, -0.09em 0 #b4d5fe, 0.12em 0 #b4d5fe, -0.12em 0 #b4d5fe, 0.15em 0 #b4d5fe, -0.15em 0 #b4d5fe;
+    text-shadow:
+        0.03em 0 #b4d5fe,
+        -0.03em 0 #b4d5fe,
+        0 0.03em #b4d5fe,
+        0 -0.03em #b4d5fe,
+        0.06em 0 #b4d5fe,
+        -0.06em 0 #b4d5fe,
+        0.09em 0 #b4d5fe,
+        -0.09em 0 #b4d5fe,
+        0.12em 0 #b4d5fe,
+        -0.12em 0 #b4d5fe,
+        0.15em 0 #b4d5fe,
+        -0.15em 0 #b4d5fe;
     background: #b4d5fe;
 }
 
@@ -742,9 +810,9 @@ td {
     text-align: left;
 }
 
-table>thead>tr:last-child>td,
-table>thead>tr:last-child>th,
-table>tbody>tr:last-child>th {
+table > thead > tr:last-child > td,
+table > thead > tr:last-child > th,
+table > tbody > tr:last-child > th {
     padding-bottom: 0.5rem;
     font-variant: all-small-caps;
 }
@@ -771,19 +839,20 @@ table>tbody>tr:last-child>th {
 
 .align-r {
     text-align: right;
-    padding-right: 4em;
+    padding-right: 1em;
 }
 
 .align-l {
     text-align: left;
+    padding-left: 1em;
 }
 
 .align-c {
     text-align: center;
 }
 
-table>tbody>tr:first-child>td,
-table>tbody>tr:first-child>th {
+table > tbody > tr:first-child > td,
+table > tbody > tr:first-child > th {
     padding-top: 0.5rem;
 }
 
@@ -796,8 +865,9 @@ tr.border-bot {
 }
 
 .sans {
-    font-family: "Libertinus Sans", "Gill Sans", "Gill Sans MT", Calibri, sans-serif;
-    letter-spacing: .03em;
+    font-family: "Libertinus Sans", "Gill Sans", "Gill Sans MT", Calibri,
+        sans-serif;
+    letter-spacing: 0.03em;
 }
 
 .fun {
@@ -805,9 +875,9 @@ tr.border-bot {
 }
 
 code,
-pre>code {
-    font-family: 'JetBrains Mono', monospace;
-    font-feature-settings: 'ss02', 'zero';
+pre > code {
+    font-family: "JetBrains Mono", monospace;
+    font-feature-settings: "ss02", "zero";
     font-size: 0.75em;
     line-height: 1.42;
     font-variant-numeric: normal;
@@ -815,28 +885,28 @@ pre>code {
     /* Prevent adjustments of font size after orientation changes in iOS. See https://github.com/edwardtufte/tufte-css/issues/81#issuecomment-261953409 */
 }
 
-.sans>code {
+.sans > code {
     font-size: 0.75em;
 }
 
-.marginnote>code,
-.marginnote>a>code,
-.sidenote>code,
-.sidenote>a>code {
+.marginnote > code,
+.marginnote > a > code,
+.sidenote > code,
+.sidenote > a > code {
     font-size: 0.95rem;
 }
 
-pre>code a>code {
+pre > code a > code {
     font-size: 0.9rem;
 }
 
-pre>code {
+pre > code {
     font-size: 0.9rem;
     overflow-x: auto;
     display: block;
 }
 
-pre.fullwidth>code {
+pre.fullwidth > code {
     width: 90%;
 }
 
@@ -910,24 +980,24 @@ p.svg {
     }
 
     hr,
-    section>p,
-    article>footer,
-    section>div.container,
-    div.container>p,
-    section>div.advice,
-    section>div.source-container,
-    section>footer,
-    section>table {
+    section > p,
+    article > footer,
+    section > div.container,
+    div.container > p,
+    section > div.advice,
+    section > div.source-container,
+    section > footer,
+    section > table {
         width: 100%;
     }
 
-    pre>code {
+    pre > code {
         width: 97%;
     }
 
-    section>dl,
-    section>ol,
-    section>ul {
+    section > dl,
+    section > ol,
+    section > ul {
         width: 90%;
     }
 
@@ -964,8 +1034,8 @@ p.svg {
         text-decoration: underline;
     }
 
-    .margin-toggle:checked+.sidenote,
-    .margin-toggle:checked+.marginnote {
+    .margin-toggle:checked + .sidenote,
+    .margin-toggle:checked + .marginnote {
         display: block;
         float: left;
         left: 1rem;
@@ -988,4 +1058,4 @@ p.svg {
     img {
         width: 100%;
     }
-}
\ No newline at end of file
+}
diff --git a/images/33-chunked-metadata.svg b/images/33-chunked-metadata.svg
new file mode 100644
index 0000000..4691221
--- /dev/null
+++ b/images/33-chunked-metadata.svg
@@ -0,0 +1,186 @@
+
+
+
+    
+        
+        
+        
+        
+        
+            
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+            
+            
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+            
+            
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+            
+            
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+            
+            
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+            
+            
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+            
+        
+    
+
diff --git a/images/33-floating-metadata.svg b/images/33-floating-metadata.svg
new file mode 100644
index 0000000..525a0be
--- /dev/null
+++ b/images/33-floating-metadata.svg
@@ -0,0 +1,172 @@
+
+
+
+    
+        
+        
+        
+        
+        
+        
+        
+            
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+            
+            
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+            
+            
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+            
+            
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+            
+            
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+            
+            
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+            
+        
+        
+            
+            
+        
+    
+
diff --git a/images/33-safetensors-structure.svg b/images/33-safetensors-structure.svg
new file mode 100644
index 0000000..98f67b4
--- /dev/null
+++ b/images/33-safetensors-structure.svg
@@ -0,0 +1,428 @@
+
+
+
+    
+        
+            
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                        
+                    
+                
+                
+                    
+                
+            
+            
+                
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+            
+            
+                
+                    
+                        
+                            
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                        
+                            
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                        
+                            
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                        
+                            
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                        
+                            
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                        
+                            
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+            
+            
+                
+                
+                    
+                
+            
+            
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+            
+            
+                
+                
+                    
+                
+            
+            
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+            
+            
+                
+                
+                    
+                
+            
+            
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+            
+            
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+            
+            
+                
+                    
+                    
+                
+            
+        
+    
+
diff --git a/images/33-source.afdesign b/images/33-source.afdesign
new file mode 100644
index 0000000000000000000000000000000000000000..f87f235caed548f191060953d467709cc660fff5
GIT binary patch
literal 34697
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
+
+
+    
+        
+            
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+                
+                    
+                
+            
+            
+                
+                    
+                    
+                
+            
+            
+                
+                    
+                        
+                    
+                    
+                        
+                            
+                        
+                        
+                            
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                            
+                        
+                    
+                
+                
+                    
+                        
+                    
+                    
+                        
+                            
+                        
+                        
+                            
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                            
+                        
+                        
+                            
+                        
+                    
+                
+                
+                    
+                        
+                    
+                
+            
+            
+                
+                    
+                    
+                        
+                            
+                        
+                        
+                            
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                            
+                        
+                        
+                            
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                            
+                        
+                        
+                            
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                            
+                        
+                        
+                            
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                            
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                            
+                        
+                        
+                            
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                            
+                        
+                        
+                            
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                            
+                        
+                        
+                            
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                    
+                        
+                    
+                
+                
+                    
+                    
+                        
+                    
+                
+                
+                    
+                    
+                        
+                    
+                
+            
+            
+                
+                    
+                        
+                            
+                                
+                            
+                        
+                        
+                            
+                        
+                    
+                    
+                        
+                            
+                                
+                            
+                        
+                        
+                            
+                        
+                    
+                    
+                        
+                            
+                                
+                            
+                        
+                        
+                            
+                        
+                    
+                    
+                        
+                            
+                                
+                            
+                        
+                        
+                            
+                        
+                    
+                    
+                        
+                            
+                                
+                            
+                        
+                        
+                            
+                        
+                    
+                    
+                        
+                            
+                                
+                            
+                        
+                        
+                            
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                    
+                        
+                    
+                
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                        
+                    
+                
+            
+            
+                
+                
+                    
+                        
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+                    
+                        
+                    
+                
+            
+        
+    
+
diff --git a/posts/33-transposing-tensor-files.tex b/posts/33-transposing-tensor-files.tex
new file mode 100644
index 0000000..5584303
--- /dev/null
+++ b/posts/33-transposing-tensor-files.tex
@@ -0,0 +1,251 @@
+\documentclass{article}
+
+\title{Transposing tensor files}
+\subtitle{Where does file metadata belong?}
+\date{2024-11-22}
+\modified{2024-11-22}
+
+\keyword{programming}
+\keyword{maching-learning}
+
+\begin{document}
+
+\epigraph{
+  Here is a problem related to yours and solved before.
+  Could you use it?
+}{G. Pólya, ``How to Solve It'', second edition, p. 9}
+
+\section*
+
+I recently spent much time working with machine learning serialization formats,
+especially \href{https://onnx.ai/}{\textsc{onnx}}.
+This file format uses \href{https://protobuf.dev/}{Protocol Buffers} for its binary representation
+and inherits the \href{https://protobuf.dev/programming-guides/proto-limits/#total}{two-gigabyte restriction on the file format size}.
+Bypassing this restriction requires storing raw tensor bytes
+in another file and referencing them from the \textsc{onnx} file.
+
+But what should the tensor file format be?
+The \href{https://huggingface.co/docs/safetensors/index}{safetensors} library from Huggingface is popular for representing tensors on disk,
+and its data layout is fully compatible with the \textsc{onnx} raw tensor data format.
+
+This article describes the safetensors file structure,
+points out its minor design flaws,
+and explains how changing the metadata location can address them.
+
+\section{safe-tensors}{The safetensors file format}
+
+A \code{safetensors} file stores a collection of multi-dimensional arrays.
+Its first eight bytes indicate the header size as an unsigned 64-bit integer,
+follows the header describing each tensor's type and shape,
+and then comes the data section containing flat arrays.
+
+\begin{figure}[grayscale-diagram]
+  \marginnote{mn-safetensors-structure}{
+    The structure of a safetensors file.
+    The first eight bytes indicate the header size in bytes.
+    The header is a \textsc{json} object describing the tensor metadata.
+    The last section contains raw array elements.
+  }
+  \includegraphics{/images/33-safetensors-structure.svg}
+\end{figure}
+
+The header is a \textsc{json} object,
+where the key is the tensor name, and the value is an object describing the tensor shape, element type, and offsets from the start of the data section.
+
+\begin{figure}
+\marginnote{mn-safetensors-header-example}{
+  An example of the safetensors file header.
+  The header is a \textsc{json} object mapping tensor names to their metadata: shape, element type, and offsets from the beginning of the data section.
+}
+\begin{code}[json]
+{ "fc.weight": {
+    "dtype": "F32",
+    "shape": [10, 784],
+    "offsets": [0, 31360]
+  },
+  "fc.bias": {
+    "dtype": "F32",
+    "shape": [10],
+    "offsets": [31360, 31400]
+  }
+}
+\end{code}
+\end{figure}
+
+This simple file organization makes it easy to implement and use in most environments.
+Unfortunately, it also has a few flaws:
+\begin{enumerate}
+\item
+  Constructing a safetensors file requires two passes over the dataset:
+  one pass to gather the tensor metadata and write the header
+  and another to append the raw tensor data to the file.
+\item
+  The tensor data offsets described in the metadata section are relative to the data section, not absolute within a file.
+  This design choice makes working with these files more cumbersome:
+  We must add the header size (and the size of this size, eight bytes) to tensor offsets before we can read the tensor data.
+  The reason for this inconvenience is a chicken-and-egg problem:
+  the absolute offsets depend on the header size,
+  and the header size depends on the offsets.
+  Safetensors designers used relative offsets in the header to break this cycle.
+\item
+  The work required to add a new tensor or change the header is proportional to the entire file size,
+  not the change size.
+\end{enumerate}
+
+\section{tensor-safes}{Tensor safes}
+
+I like to imagine a tensor file as a safe with gold ingots (tensors) inside.
+The metadata section is a slip of paper describing each ingot's weight, purity, and location in the safe.
+The safetensors layout requires us to fill out the paper first,
+place it at the back of the safe,
+and assemble the ingots as described.
+
+Luckily, there is an easier way.
+Put the ingots first, jotting down the size and location on the paper as you go.
+Once all the gold is in the safe, put the paper in front of it and seal it.
+In the world of binary files, this idea corresponds to placing the metadata block at the end of the file\sidenote{sn-leveldb}{
+  I learned this trick from the \href{https://github.com/google/leveldb/blob/main/doc/table_format.md}{LevelDB table format}.
+}.
+Let's call this derived format \emph{tensorsafe}.
+
+The format makes two minor adjustments to the \code{safetensors} structure:
+\begin{enumerate}
+\item The metadata block lives at the end of the file, followed by the metadata size\sidenote{sn-metadata-size-u32}{
+  I limited the metadata section size to four bytes on the figure,
+  enough to store more than 10,000,000 tensors in a single file, assuming a single entry is under 400 bytes.
+}.
+\item The file starts with a fixed-size header containing \href{https://en.wikipedia.org/wiki/Magic_number_(programming)#In_files}{magic bytes} and the version.
+  All self-respecting file formats need versioning.
+\end{enumerate}
+\newline
+
+\begin{figure}[grayscale-diagram]
+  \marginnote{mn-tensorsafe-structure}{
+    The structure of a tensorsafe file.
+    The header has a fixed size and includes magic bytes and the version.
+    The variable-size metadata block moved to the end of the file.
+  }
+  \includegraphics{/images/33-tensorsafe-structure.svg}
+\end{figure}
+
+These changes address all my issues with \code{safetensors}:
+\begin{enumerate}
+\item The encoder needs only one pass over the data.
+  It can accumulate the metadata while writing tensors to the file and add the metadata section at the end.
+\item
+  The metadata section becomes self-contained and can use absolute offsets for tensor boundaries.
+  The reader doesn't need to massage the offsets anymore.
+\item
+  We don't have to move the data to append new tensors:
+  We can write over the old metadata section and append the new one.
+\end{enumerate}
+
+Are there any new downsides to the \code{tensorsafe} approach?
+None that I can think of.
+
+\section{alternative-designs}{Alternative designs}
+
+Dumping the entire metadata section at the beginning or the end of a file are not the only options available.
+This section explores other popular approaches for metadata encoding,
+such as spreading it across the file or making it float freely.
+
+\subsection{chunked-metadata}{Chunked metadata}
+
+We can represent a collection of items by packaging each item's metadata and data into a distinct chunk.
+For example,
+\href{https://en.wikipedia.org/wiki/PNG#File_format}{\textsc{png}} encodes various aspects of the image as separate chunks,
+and the \href{https://webassembly.github.io/spec/core/binary/index.html}{WebAssembly binary format} represents a module as a collection of sections (types, imports, memory, etc.).
+
+When we apply this approach to tensor encoding,
+we interleave tensor attributes with the tensor data.
+
+\begin{figure}[grayscale-diagram]
+\marginnote{mn-chunked-metadata}{
+  The chunked metadata approach: the tensor file contains a self-contained section for each tensor.
+}
+\includegraphics{/images/33-chunked-metadata.svg}
+\end{figure}
+
+This approach also addresses the original design issues:
+\begin{itemize}
+\item The encoder no longer needs to accumulate metadata;
+  producing a file requires a single straightforward loop over the tensors.
+\item The metadata section doesn't need to contain tensor offsets:
+  The decoder can compute them by scanning the file.
+\item We can append new tensors to the file without touching existing items.
+\end{itemize}
+
+Unfortunately, this design also has a severe disadvantage:
+The decoder has to scan the entire file even if the caller is interested in accessing only a specific tensor.
+Furthermore, metadata decoding becomes slower because it involves many file seeks.
+
+\subsection{floating-metadata}{Floating metadata}
+
+We explored options where metadata occupies the file's beginning and end
+and is spread thinly across it.
+Are there any more options?
+There are!
+The metadata block can float freely within the file\sidenote{sn-floating-wad}{
+I first encountered this idea when dealing with \href{https://doomwiki.org/wiki/WAD}{\textsc{wad}} files.
+I later learned that \href{https://en.wikipedia.org/wiki/PDF#File_format}{\textsc{pdf}} uses a similar trick for its cross-reference tables.
+}.
+
+With this design, a fixed-size file header contains an offset of the metadata block location.
+The encoder writes a default header at first,
+encodes the entire dataset,
+appends the metadata block,
+and finally goes back to the header to update the metadata offset.
+
+\begin{figure}[grayscale-diagram]
+\marginnote{mn-floating-metadata}{
+  The floating metadata approach: a fixed-size header contains the metadata offset.
+}
+\includegraphics{/images/33-floating-metadata.svg}
+\end{figure}
+
+This design has all the benefits of the \href{#tensor-safes}{tensorsafe} format,
+and the extra level of indirection adds another feature: \emph{atomic in-place updates}.
+You can add, remove, or edit tensors using the following procedure:
+\begin{enumerate}
+\item Append the new data and a new metadata section to the file.
+\item \href{https://www.man7.org/linux/man-pages/man2/fsync.2.html}{Sync} the changes to disk.
+\item Update the header to point to the new metadata entry.
+\end{enumerate}
+
+This approach guarantees that the file will stay consistent
+even if the writing process crashes before completing the update,
+but it can lead to extra space usage.
+The writer can detect and reuse that space in subsequent updates.
+
+Given modern advances in hardware and file systems,
+atomic updates might not be worth the extra complexity.
+
+\section{conclusion}{Conclusion}
+
+This article covered a few alternative designs for the \code{safetensors} file format
+and argued that moving the metadata section at the end of the file would make the format easier to use.
+The following table summarizes the design space.
+
+\begin{figure}[fullwidth]
+\begin{tabular}{r c c c c}
+& safetensors & tensorsafe & chunked & floating \\
+\hrule
+Zero-copy decoding & \dingbat{heavy-check} & \dingbat{heavy-check} & \dingbat{heavy-check} & \dingbat{heavy-check} \\
+Fast metadata decoding & \dingbat{heavy-check} & \dingbat{heavy-check} & \dingbat{heavy-ballot-x} & \dingbat{heavy-check} \\
+Data passes required for encoding & 2 & 1 & 1 & 1 \\
+Absolute file offsets in metadata & \dingbat{heavy-ballot-x} & \dingbat{heavy-check} & n/a & \dingbat{heavy-check} \\
+Atomic in-place updates & \dingbat{heavy-ballot-x} & \dingbat{heavy-ballot-x} & \dingbat{heavy-ballot-x} & \dingbat{heavy-check} \\
+\end{tabular}
+\end{figure}
+
+\section{checklist}{Appendix: binary format checklist}
+
+\begin{checklist}
+\item I can explain why I need to design a new binary format.
+\item I studied design choices that similar formats made.
+\item The file metadata has a version number.
+\item I requested a design review from the community.
+\item I wrote a detailed specification of my format.
+\end{checklist}
+
+\end{document}