Install Theme

[Attention conservation notice: machine learning framework shop talk / whining that will read like gibberish if you are lucky enough to have never used a thing called “tensorflow”]

I’ve probably probably spent 24 solid hours this week trying (for “fun,” not work) to get some simple tensorflow 1.x code to run on a cloud TPU in the Google-approved manner

By which I mean, it runs okay albeit slowly and inefficiently if I just throw it in a tf.Session() like I’m used to, but I wanted to actually utilize the TPU, so I’ve been trying to use all the correct™ stuff like, uh…

…“Datasets” and “TFRecords” containing “tf.Examples” (who knew serializing dicts of ints could be so painful?) and “Estimators” / “Strategies” (which do overlapping things but are mutually exclusive!) and “tf.functions” with “GradientTapes” because the “Strategies” apparently require lazily-defined eagerly-executed computations instead of eagerly-defined lazily-executed computations, and “object-based checkpoints” which are the new official™ thing to do instead of the old Saver checkpoints except the equally official™ “Estimators” do the old checkpoints by default, and oh by the way if you have code that just defines tensorflow ops directly instead of getting them via tf.keras objects (which do all sorts of higher-level management and thus can’t serve as safe drop-in equivalents for “legacy” code using raw ops, and by “legacy” I mean “early 2019″) then fuck you because every code example of a correct™ feature gets its ops from tf.keras, and aaaaaaaaaaaaaargh!!

This solidifies the impression I got last time I tried trusting Google and using fancy official™ tensorflow features.  That was with “tensorflow-probability,” a fancy new part of tensorflow which had been officially released and included cool stuff like Bayesian keras layers… which were impossible to save to disk and then load again… and this was a known issue, and the closest thing to an official reaction was from a dev who’d moved off the project and was now re-implementing the same thing in some newly-or-differently official™ tensorflow tentacle called “tensor2tensor,” and was like “uh yeah the version here doesn’t work, you can try tensor2tensor if you want”

(I still don’t know what “tensor2tensor” is.  I refuse to learn what “tensor2tensor” is.  They’re not going to get me again, dammit)

I don’t know whether the relevant category is “popular neural net frameworks,” or “large open-sourced projects from the big 5 tech companies,” or what, but there’s a certain category of currently popular software that is frustrating in this distinctive way.  (Cloud computing stuff that doesn’t involve ML is often kind of like this too.)  There’s a bundle of frustrating qualities like:

  • They keep releasing new abstractions that are hard to port old code into, and their documentation advocates constantly porting everything to keep up

  • The new abstractions always have (misleading) generic English names like “Example” or “Estimator” or “Dataset” or “Model,” giving them a spurious aura of legitimacy and standardization while also fostering namespace collisions in the user’s brain

  • The thing is massive and complicated but never feels done or even stable – a hallmark of such software is that there is no such thing as “an expert user” but merely “an expert user ca. 2017″ and the very different “an expert user ca. 2019,” etc

  • Everything is half-broken because it’s very new, and if it’s old enough to have a chance at not being half-broken, it’s no longer official™ (and possibly even deprecated)

  • Documentation is a chilly API reference plus a disorganized, decontextualized collection of demos/tutorials for specific features written in an excited “it’s so easy!” tone, lacking the conventional “User’s Manual” level that strings the features together into mature workflows

  • Built to do really fancy cutting-edge stuff and also to make common workflows look very easy, but without a middle ground, so either you are doing something very ordinary and your code is 2 lines that magically work, or you’re lost in cryptic error messages coming from mysterious middleware objects that, you learn 5 hours later, exist so the code can run on a steam-powered deep-sea quantum computer cluster or something

Actually, you know what it reminds me of, in some ways?  With the profusion of backwards-incompatible wheel-reinventing features, and the hard-won platform-specific knowledge you just know will be out of date in two years?  Microsoft Office.  I just want to make a neural net with something that doesn’t remind me of Microsoft Office.  Is that too much to ask?

from BASIC to the fae realm, and back again

When I was a kid, I learned to program in a few different flavors of BASIC.  When I was a teenager, I tried to graduate from BASIC, which I knew was a sort of training-wheels language, to a “real” language, which at the time seemed to mean C, C++, or maybe Java.

This was a confusing and difficult process.  The first time, at age 13, I tried to learn C++ by reading one of those “Learn [language] in [X days]” books.  I read all the way through the book, but found most of it baffling, and never actually sat down to write any C++.  Then later, at 17, I said “I am going to learn C, dammit,” Googled my way to the About.com tutorial on C, and actually started programming in C.

I was able to use the language well enough to write some things like a simple raytracer, and I found the process entertaining, but mostly as a demonstration of will and dedication; I still found C itself pretty bizarre, and I felt proud of myself for managing to get things done using these strange alien invocations, rather than really grokking the language and the demands it made of me. My code was probably exquisitely terrible, although it did compile and run.

I gather this is a pretty typical experience from that time period. What’s funny, looking back, is that the “serious, adult programming” (i.e. programming for $) I currently do feels much closer to the Eden of BASIC than C did.

This isn’t just a high-level vs. low-level thing.  I think it’s more fundamentally about clarity of abstraction boundaries.

————

A high-level language like Python tries hard to put you in an abstract world of entities that feel stable and part of the same world, without the lower-level implementation poking its head through the cracks.  But (as far as my limited understanding goes) this is also true of the lowest-level languages.  The user of assembly or byte code doesn’t engage directly with any specific physical machine; they use an abstract interface called the instruction set architecture which can be implemented in multiple ways, and they engage with abstract entities like “logical registers” that may get mapped to different physical registers on the fly.  The mapping is invisible, outside of your control – securely “lower-level” than the programmer’s world.

C is not like this.  It doesn’t define any stable world that makes sense on its own terms.  Or rather, the only stable world that exists in C exists on a lower level than the entities the language seems to be talking about, and its constructs only make full sense as convenient and elaborate macros for common lower-level operations.  You can only really get C when you’ve learned to view a, say, “int” as a collection of shorthands for things people often do with little groups of bytes in memory, and not as an abstract data type.

————

The clearest example for me is arrays.  Does C “have” arrays?  Well, it has an array type, and you can create an array in stack memory and it behaves the ways you expect.  (Indeed, C’s interface to stack memory looks deceptively like high-level programming, which was very confusing to me at first.)

All right, let’s pass our array to another function.  C passes by value, but you generally don’t want to pass arrays by value, and indeed, C does not do so.  Instead, if you try to pass an array, you get something that’s almost an array, but lacks a piece of data (the length).

What is this thing?  Well, it’s actually a reference – but not a reference to the array (which would be passing the array by reference), it’s a reference to the first element.  But if you try using array indexing like a[i] on it, it works as if you had the array itself.  Why?  Because the “array indexing” notation in C works on any object that’s a reference: it’s actually shorthand for getting the value out of a reference that is “i units away” from the original reference.

Why does the concept of “i units away from this reference” make sense in C?  After all, you wouldn’t look at a URL and say “now give me the one to its left.” It makes sense in C … because C’s implementation of a reference is something called a “pointer” which stores a location in an actual address space (the floor is leaking!), and C’s implementation of an array allocates memory so the individual objects are actually next to each other in the address space, as opposed to say a linked list (the floor is disintegrating!).

…and then that all applies only for stack memory.  Creating the same array/“array” in heap memory looks totally different, and involves importing functions from the standard library, and those functions don’t know how much memory the different C objects need, so to get an array with 50 ints you need to look up how big an int is and convert that to bytes.  If you do that right, you get another one of the pseudo-array things (first element references), but again the function doesn’t know anything about C types so you’ll need to convert it to an int reference.

(If you think about it, these are steps that also need to happen when you’re making a stack array, but in that case, this rote and predictable bundle of operations was conveniently automated for you.  Not here – now it’s your turn to do them.  It takes a village, it seems, to implement array functionality.)

————

Sometimes in C the floor can look solid.  If you have an object of type double, then it takes up the right amount of memory (whatever it happens to be) to represent a double-precision floating-point number, and if you use arithmetic operators on it, floating-point arithmetic happens.  And you can just trust it to happen without thinking about how a specific computer is doing it.  C connects you with an implementation of floating-point numbers, as a high-level language would understand that phrase.  But the presence of such complete implementations makes it, if anything, harder to work simultaneously with the incomplete ones.  You’re constantly asking yourself, “wait, what level am I on right now?”

Some of my confusions when I first tried to learn C/C++ can be read as confusions about what I should expect to be “implemented” for me, exacerbated by a conflation between “fully implemented for you” and “possible without vast effort.”  One of the later chapter of the C++ book was an extended walkthrough of making a linked list class.  “Why am I supposed to be excited?”, I thought.  “At the end of the day this is basically an array, and I already have those.”  Rather than showing off nontrivial things you could do with C++, the example was about a trivial thing you couldn’t do with C++ without adding it to the language by hand.

In retrospect, these languages don’t look any less strange.  Now I’m used to hearing everything framed in terms of “APIs,” “information hiding,” “loose coupling” – an aspiration to make the expectation of my young self, of a single stable floor extending everywhere, as close to a truth as possible.

deusvulture replied to your post

“nightpool replied to your photo “As someone who keeps running into…”

fwiw I feel like I pretty frequently see those kinds of errors in rtf and doc parsing (a profusion of Âs is the classic example)

although maybe that’s a different thing

The Â thing in particular is a symptom of reading text in one character encoding as if it’s in another character encoding, see here.

Character encoding problems are common, but they’re at a lower level than what I’m talking about – the level of “what sequence of characters do these bytes even represent” rather than “given this sequence of characters, which ones are special control sequences.”  So they have the potential to come up in any kind of text processing, even in the hypothetical utopia where I have my ideal tabular data file format.

The direct analogue to the problems I have with CSVs would be something like interpreting an RTF as having lots of bold text because I typed “\b” somewhere in it, or conversely interpreting actual bold text in an RTF as regular text surrounded with “\b” and related sequences.  Or the like with margin sizes, other layout information, etc.

nightpool

replied to your photo

“As someone who keeps running into data issues at work that can be…”

isn’t there just a setting you can change in google sheets for this? I know there’s one in Excel although it may have downsides (computer-level instead of worksheet-level, etc)

Yeah, I think so.  IIRC, the issues I’ve had with Google Sheets aren’t with “smart” and not-strictly-necessary features like date parsing, but with escapes for field and line delimiters not getting handled correctly at some point in a larger pipeline.

This isn’t really an issue with Google Sheets per se, it’s an issue with the relative lack of investment in good exchange formats, and trustworthy software, for tabular data.  It’s conventional to talk as though a “CSV file” is a well-defined thing, and to provide “CSV” import/export options that don’t tell you exactly what they do, even though there are numerous distinct CSV standards (and non-standard implementations).  Google Sheets’ CSV functionality usually works, but by that I mean “its assumptions about CSVs usually turn out to agree with those in the other software I’m plugging it into,” which is not the same thing as being trustworthy in a predicable way.

I’ve actually concluded it’s safer to use Excel’s file format (really) when moving tabular data through software with poorly documented file parsers, because there’s a kind of dogfooding that works in my favor: Excel has to handle delimiters at least consistently enough to load its own files after saving them.  But for just handing around a table of strings this feels a bit ridiculous.

Contrast this with the formats that exist for formatted, but generally non-tabular, text.  This situation there isn’t perfect either, with plenty of non-open formats and incompatible version formats, yet somehow I’m able to open RTF and DOC files without ever seeing formatting escapes interpreted as text or vice versa.  And that’s a much more complicated case, with a much wider range of features to support.

dragon-in-a-fez:

today I learned that an estimated 20% of genetics papers may have errors because Excel automatically converted the names of genes into calendar dates

image

As someone who keeps running into data issues at work that can be traced back to the data going through Google Sheets at some point, I’m disturbed but not surprised that this type of issue is causing widespread havoc in academia too. (And I’m pessimistic that switching to Google Sheets is a good general solution, although it may fix this specific problem with date parsing.)

Literally just yesterday I was thinking, in response to the latest such incident, that there’s an oddly unfilled need for a simple, accessible table editor that just renders text organized into rows and columns, nothing else, with no parsing except for cell separators (and with that very carefully specified and controllable).

It’s easy to imagine this being a standard tool that came with every major OS, the way they all still have simple GUI text editors even though “word processors” also exist. But we don’t live in that parallel universe, and in ours there is this very simple, very common, apparently unfulfilled need. (*Gestures suggestively in the direction of any bored software developers who happen to be reading this*)

(via afloweroutofstone)

szhmidty:

nostalgebraist:

It’s weird to me how people keep re-implementing a set of standard vector operations in new numerical computation packages, often giving them the same or very similar names, and yet (to my knowledge) no one has tried to write a formal specification of this set of operations.  Or at least no one has done it and gotten it off the ground?

I’m thinking of operations like “reshape,” “squeeze,” “concatenate,” “stack,” “zeros/ones/eye,” etc.  There are versions of this shared language in MATLAB, in numpy, in pandas (although pandas uses database-like indices so it’s a little different from the others), in tensorflow, in torch/pytorch, and presumably in other things I’ve never used.

Yet each one of the dialects has its quirks.  The same operation (concatenate arrays along an existing dimension) is called “concatenate” in numpy, “concat” in pandas and tensorflow, and “cat” in torch/pytorch and in MATLAB.  In MATLAB the call signature puts the axis before the arrays, while for all the others it comes after the arrays.  (Everyone agrees about what “stack” should be called, for some reason, although it’s just concatenation along a new dimension instead of an existing one.)  Meanwhile, the thing called “reshape” in numpy, MATLAB, and tensorflow is called “view” in torch.  Things represented as f(A, B) in the other dialects are often A.f(B) in pandas, but not always.  And so on!

The process of “learning” one of these software packages, once you know any of the others, is largely about learning the arbitrary differences in the dialect.  Stack Overflow questions about pytorch frequently take the form “is there a torch equivalent of tf.whatever?”, and IIRC the tensorflow questions tend to be like “is there a tensorflow equivalent of np.whatever?”  (And there are probably older ones about numpy equivalents for MATLAB stuff.)

It all feels a bit silly and wasteful, and seems distinctively more chaotic than the situation with SQL, possibly (or possibly not) because SQL at least has a standard.  There are differences between the major SQL dialects, but there’s at least an intuitive core that people feel they can expect, and people would get mad if you told them you supported “SQL queries” but it turned out you had renamed JOIN to, like, JOINIFICATE or MASHUP or JO or something.  (New Relic’s query language is basically like this – it has SELECT and WHERE, but renames GROUP BY to FACET – but they know not to call it “SQL.”)

https://xkcd.com/927/

I was thinking about linking to that in my post, actually – as evidence that people like to propose universal standards, indeed more than is strictly reasonable, which is why I am surprised to see no such attempts in this case.

(There are a lot of versions of this language, but none that even aspires to universality.  The closest thing I’m aware of is Keras’ “backend API,” but that’s just a new dialect for encoding calls to one of several other dialects, not a standard with a specification you can write down.)

It’s weird to me how people keep re-implementing a set of standard vector operations in new numerical computation packages, often giving them the same or very similar names, and yet (to my knowledge) no one has tried to write a formal specification of this set of operations.  Or at least no one has done it and gotten it off the ground?

I’m thinking of operations like “reshape,” “squeeze,” “concatenate,” “stack,” “zeros/ones/eye,” etc.  There are versions of this shared language in MATLAB, in numpy, in pandas (although pandas uses database-like indices so it’s a little different from the others), in tensorflow, in torch/pytorch, and presumably in other things I’ve never used.

Yet each one of the dialects has its quirks.  The same operation (concatenate arrays along an existing dimension) is called “concatenate” in numpy, “concat” in pandas and tensorflow, and “cat” in torch/pytorch and in MATLAB.  In MATLAB the call signature puts the axis before the arrays, while for all the others it comes after the arrays.  (Everyone agrees about what “stack” should be called, for some reason, although it’s just concatenation along a new dimension instead of an existing one.)  Meanwhile, the thing called “reshape” in numpy, MATLAB, and tensorflow is called “view” in torch.  Things represented as f(A, B) in the other dialects are often A.f(B) in pandas, but not always.  And so on!

The process of “learning” one of these software packages, once you know any of the others, is largely about learning the arbitrary differences in the dialect.  Stack Overflow questions about pytorch frequently take the form “is there a torch equivalent of tf.whatever?”, and IIRC the tensorflow questions tend to be like “is there a tensorflow equivalent of np.whatever?”  (And there are probably older ones about numpy equivalents for MATLAB stuff.)

It all feels a bit silly and wasteful, and seems distinctively more chaotic than the situation with SQL, possibly (or possibly not) because SQL at least has a standard.  There are differences between the major SQL dialects, but there’s at least an intuitive core that people feel they can expect, and people would get mad if you told them you supported “SQL queries” but it turned out you had renamed JOIN to, like, JOINIFICATE or MASHUP or JO or something.  (New Relic’s query language is basically like this – it has SELECT and WHERE, but renames GROUP BY to FACET – but they know not to call it “SQL.”)

I got a lot of interesting responses to my earlier post asking why game development works as well as it does (including one via email rather than tumblr).  Thanks.

There were some clear recurring themes in the responses:

1. Engines and other game development middleware / tools are very sophisticated, and selling these things (rather than games themselves) is lucrative so they have the polish of full-blown products (as opposed to projects someone hacked together to make their life easier while doing something else)

2. Games are all similar enough to one another, even on a 10+ year timescale, that nontrivial problems can be definitely solved and the solutions stick around (as tools or best practices) rather than becoming obsolete; good game programmers often have deep expertise acquired over that same 10+ year timescale, which simply isn’t possible to have with the latest web framework or whatever

3. Game studios do a relatively large amount of manual QA, and do it relatively early and often

4. Advantages related to the relative “frivolity” (not sure if that’s the right word) of games: they take place in invented worlds that can be modified for maximal convenience to the programmer, and the “cost function” associated with bugs is a lot fuzzier and more forgiving – how much a bug matters is quantified by how much it detracts from the entire experience, which will frequently be “not that much,” and there aren’t many components that simply must work exactly as specified or you’re in legal hot water or w/e

5.  Simple abundance of resources: lots of programmers per game, game programmers (compared to other programmers) accept lower wages and are more committed to the job as an all-consuming lifestyle

Some comments below.


1+2 feel like a coherent package, and 3+4 feel like a different coherent package.  (I’m not especially interested in 5, although I’m sure it’s true, because it doesn’t provide any transferable insights for non-game software work, which is what I’m looking for here)

1+2 are about game development having mature tools at a high level of abstraction, sort of like the difference between higher- and lower-level programming languages.  If I’m understanding correctly: game developers can work with relatively high-level primitives right out of the box, and so there’s less of an abstraction gap between design and development – the designers think in terms of a sort of palette of high-level abstractions like “there’s a character, they have these stats, they have the ability to walk around and equip swords” and the developer can, to a relatively large extent, just plop things like Characters™ with Walk Actions™ and Equip Actions™ into place, with all the customizability the designer expects from such things.

This sounds true and explains a lot if so, but I still feel confused as to why it’s true.  Are games really this much more standardized than, like, I dunno, shopping websites?

3+4 feel like they’re about treating the thing you’re creating as a messy emergent system, letting yourself be relatively free to throw in new pieces that affect or depend on existing pieces without explicitly thinking through the combinatorial explosion of possible states and interactions, and instead determining whether the whole thing works by just having people constantly try to use it as you’re developing it.  I can see why this would be unusually compatible with the game domain, since there are fewer parts that simply cannot be allowed to fail, and you have more freedom to fix bugs by changing the design (“things broke when character X equipped item Y, so we’ll make up some reason they can’t do that”).

Still, I do find myself wondering if other things couldn’t be a little more like this.  In my (limited! extremely limited!) experience it seems like the median experienced programmer has heavily internalized the idea that freedom will always bite you in the end, that you should bind yourself with every constraint you can think of that doesn’t literally render the current task impossible.  There is a lot of wisdom in this attitude, but it can sometimes amount to a restrictive obsession with defending against every mistake and worst-case-scenario you can currently think of – when in reality, a lot of the dangers you anticipate will simply never happen (or won’t end up mattering), and a little freedom might end up being a blessing when the dangers you haven’t anticipated inevitably arrive.

nostalgebraist:

Here’s something I want to understand better:

How do computer/video game studios do it?  Where “it,” here, is

  • successfully build and deliver very complex software at a relatively fast pace
  • … when (I would imagine) a bug in virtually any component of that software has a fairly high probability of tanking the whole product (being ‘game-breaking’)
  • … and the components have to be not just individually fast but capable of running reliably fast as a whole (some having to wait for the others’ outputs and so forth)
  • .. and do all that while building, not just the simplest bare-bones version of whatever core gameplay they’re trying to deliver, but (frequently) something that is packed with bells and whistles (“oh yeah there’ll be a crafting system and emergent NPC behavior patterns and random weather patterns and optional sidequests and … ”)?

Like, I keep having this experience where I go to work and talk to people about the design challenges involved in, I dunno, adding a new button to a UI or making the user interaction flow slightly more complicated, and there’s a general (and understandable) fear of making anything more complex or flexible than it has to be, because you lose guarantees and create new edge cases and stuff … 

… and then I go home, and play a video game, and I’m interacting with a way fancier UI that works perfectly, with little contextual sub-menus popping up in battle and stuff, and I’m interacting with a bunch of complex nested application logics from the ones that determine who’s alive in battle and what they can do, to the ones that determine what will happen when I talk to NPC X based on the fact that I have item Y and haven’t completed quest Z, and the ones that layer a scripted animation on top of the custom body and clothes I’ve given to my character and the weapon they’re holding (which could be any one of 30), and I’m just like, how did people build this?

Is the answer just “re-usable and extensible game engines”?  That still feels too much like a free lunch, though; any engine flexible enough to give you this much creative freedom is (presumably?) too generic to give you any guarantees that things won’t blow up in your face.

Maybe it’s “re-usable and extensible game engines” plus “tools for managing and automatically testing things you build in those engines”?  Like really sophisticated integration tests that work for arbitrary components placed into a pre-established framework?

Maybe it’s also the fact that (many, if not all) games basically get released once and then aren’t incrementally developed much after that?  Maybe every large game builds up more and more technical debt as release nears, but that’s okay as long as you still make it past the finish line?

I know the game industry is infamous for working people inhumanely hard, and I do imagine that plays some role, but I don’t think it can explain that much here.  Overworked people can write more code per week, sure, but they’re going to be worse, not better, at complex cross-team collaboration.

Another question, which I’m realizing is kind of latent in this one, is: how do game engines get created in the first place, as a matter of process?

The usual lore (see e.g. these two posts) advises against building things like engines in the course of building an application, not just because it’ll take more work, but because it’s hard to predict in advance what kind of flexibility you will need and which assumptions will stop being reliable. Which makes sense for building individual applications, but then when does the engine get made and extended, and by whom, and what procedures are in place to ensure this happens? Or do big game studios ignore this “usual lore” entirely? (In the terms of one of those posts, they are building the space shuttle, I guess.)

Here’s something I want to understand better:

How do computer/video game studios do it?  Where “it,” here, is

  • successfully build and deliver very complex software at a relatively fast pace
  • … when (I would imagine) a bug in virtually any component of that software has a fairly high probability of tanking the whole product (being ‘game-breaking’)
  • … and the components have to be not just individually fast but capable of running reliably fast as a whole (some having to wait for the others’ outputs and so forth)
  • .. and do all that while building, not just the simplest bare-bones version of whatever core gameplay they’re trying to deliver, but (frequently) something that is packed with bells and whistles (“oh yeah there’ll be a crafting system and emergent NPC behavior patterns and random weather patterns and optional sidequests and … ”)?

Like, I keep having this experience where I go to work and talk to people about the design challenges involved in, I dunno, adding a new button to a UI or making the user interaction flow slightly more complicated, and there’s a general (and understandable) fear of making anything more complex or flexible than it has to be, because you lose guarantees and create new edge cases and stuff … 

… and then I go home, and play a video game, and I’m interacting with a way fancier UI that works perfectly, with little contextual sub-menus popping up in battle and stuff, and I’m interacting with a bunch of complex nested application logics from the ones that determine who’s alive in battle and what they can do, to the ones that determine what will happen when I talk to NPC X based on the fact that I have item Y and haven’t completed quest Z, and the ones that layer a scripted animation on top of the custom body and clothes I’ve given to my character and the weapon they’re holding (which could be any one of 30), and I’m just like, how did people build this?

Is the answer just “re-usable and extensible game engines”?  That still feels too much like a free lunch, though; any engine flexible enough to give you this much creative freedom is (presumably?) too generic to give you any guarantees that things won’t blow up in your face.

Maybe it’s “re-usable and extensible game engines” plus “tools for managing and automatically testing things you build in those engines”?  Like really sophisticated integration tests that work for arbitrary components placed into a pre-established framework?

Maybe it’s also the fact that (many, if not all) games basically get released once and then aren’t incrementally developed much after that?  Maybe every large game builds up more and more technical debt as release nears, but that’s okay as long as you still make it past the finish line?

I know the game industry is infamous for working people inhumanely hard, and I do imagine that plays some role, but I don’t think it can explain that much here.  Overworked people can write more code per week, sure, but they’re going to be worse, not better, at complex cross-team collaboration.