But it's pragmatically true that engineers will want to murder you if your compiler is non-deterministic. All sorts of build systems, benchmark harnesses, supply chain validation tools, and other bits of surrounding ecosystem will shit the bed if the compiler doesn't produce bitwise identical output on the same input and compiler flags.
There are several big projects that use PGO (like Chrome), and you can get a deterministic build at whatever revision using PGO as the profiles are checked in to the repository.
PGO works just fine in Rust and has support built into the compiler (https://doc.rust-lang.org/rustc/profile-guided-optimization....).
I was under the impression they had switched to AutoFDO across the board but maybe that’s just for their cloud stuff and Chrome continues to run a representative workload since that path is more mature. I would guess that if it’s not being used already, they’re exploring how to make Chrome run AutoFDO for the same reason everyone started using ThinLTO - it brought most of the advantages while fixing the disadvantages that hampered adoption.
And yes, while PGO is available natively, AutoFDO isn’t quite as smooth.
Chrome (and many other performance-critical workloads) is using instrumented PGO because it gives better performance gains, not because it's a more mature path. AutoFDO is only used in situations where collecting data with an instrumented build is difficult.
I’m just using an educated guess to say that at some point in the future Chrome will switch to AutoFDO, potentially using traces harvested from end user computers (potentially just from their employees even to avoid privacy complaints).
Performance is also pretty different on the scales that performance engineers are interested in for these sorts of production codes, but without the build system scalability problems that LTO has. The original AutoFDO paper shows an improvement of 10.5%->12.5% going from AutoFDO to instrumented PGO. That is pretty big. It's probably even bigger with newer instrumentation based techniques like CSPGO.
They also mention the exact reasons that AutoFDO will not perform as well, with issues in debug info and losing profile accuracy due to sampling inaccuracy.
I couldn't find any numbers for Chrome, but I am reasonably certain that they have tried both and continue to use instrumented PGO for the extra couple percent. There are other pieces of the Chrome ecosystem (specifically the ChromeOS kernel) that are already optimized using sampling-based profiling. It's been a while since I last talked to the Chromium toolchain people about this though. I also remember hearing them benchmark FEPGO vs IRPGO at some point and concluding that IRPGO was better.
Stable output is how we can verify that attacks like the one described in Reflections on Trusting Trust[0] don't happen.
[0] https://www.cs.cmu.edu/~rdriley/487/papers/Thompson_1984_Ref...
My bigger concern would be bugs in the machine code would be very, very difficult to track down.
If you hit a compiler bug, you could try a different seed to see what happens.
Or how about a code formatter with a random seed?
Tool developers could run unit tests with a different seed until they find a bug - or hide the problem by finding a lucky seed for which you have no provable bugs :)
I think that’s rather true nowadays, but hasn’t always been thus. Back in the 20th century, non-deterministic compiler output was very common - even if only due to the common practice of embedding the compilation timestamp in the resulting executable - and very few ever cared. Whereas nowadays, there is a much bigger culture of hermetic/reproducible build processes, in which stuff like embedding compilation timestamps in the executable or object files is viewed as an antipattern.
That sounds like a nightmare. Optimizing code to play nice with black-box heuristic compilers like V8's TurboFan is, already in fact, a continual maintenance nightmare.
If you don't care about performance, non-deterministic compilation is probably "good enough." See TurboFan.
- Yes, temperature 0.0 is less creative.
- Injecting pseudo-random noise to get deterministic creative outputs is "not even wrong", in the Wolfgang Pauli sense. It's fixing something that isn't broken, with something that can't fix it, that if it could, would be replicating the original behavior - more simply, it's proposing non-deterministic determinism.
- Temperature 0.0, in practice, is an LLM. There aren't emergent phenomena, in the sense "emergent phenomena" is used with LLMs, missing. Many, many, many, applications use this.
- In simplistic scenarios, on very small models, 0.0 could get stuck literally repeating the same token.
- There's a whole other layer of ex. repeat penalties/frequency penalties and such that are used during inference to limit this. Only OpenAI and llama.cpp expose repeat/frequency.
- Temperature 0.0 is still non-deterministic on ex. OpenAI, though substantially the same, and even the same most of the time. It's hard to notice differences. (Reproducible builds require extra engineering effort, the same way ensuring temperature = 0.0 is truly deterministic requires engineering effort.)
- Pedantically, only temperature 0.0 at the same seed (initial state) is deterministic.
That's not something you want in a compiler.
I do think the idea of a 90%+-ish forward and backward assembler LLM is pretty intriguing. There’s bound to be a lot of uses for it; especially if you’re of the mind that to get there it would have to have learned a lot about computers in the foundation model training phase.
Like, you’d definitely want to have those weights somehow baked into a typical coding assistant LLM, and of course you’d be able to automate round one of a lot of historical archiving projects that would like to get compilable modern code but only have a binary, you’d be able to turn some PDP-1 code into something that would compile on a modern machine, … you’d probably be able to leverage it into building chip simulations / code easily, it would be really useful for writing Verilog, (maybe), anyway, the use cases seem pretty broad to me.
With code size, you just need to run the code through the compiler and you have a deterministic measurement for evaluation.
Performance has no such metric. Benchmarks are expensive and noisy. Cost models seem like a promising direction, but they aren't really there yet.
You're confusing different concepts here. An llm is technically not unpredictable by itself (at least the ones we are talking about here, there are different problems with beasts like GPT4 [1]). The "randomness" of llms you are probably experiencing stems from the autoregressive completion, which samples from probabilities for a temperature T>0 (which is very common because it makes sense in chat applications). But there is nothing that prevents you from simply choosing greedy sampling, which would make your output 100% deterministic and reproducible. That is particularly useful for disassembling/decompiling and has the chance to vastly improve over existing tools, because it is common knowledge that they are often not the sharpest tools and humans are much better at piecing together working code.
The other question here is accuracy for compiling. For that it is important whether the llm can follow a specification correctly. Because once you write unspecified behaviour, your code is fair game for other compilers as well. So the real question is how well does it follow the spec how good is it at dealing with situations where normal compilers will flounder.
[1] https://152334h.github.io/blog/non-determinism-in-gpt-4/
Nailed it! Thank you for stating this better than I did. Maybe I should have used the word "trustworthy" instead of "predictable".
If I'm using an LLM as a calculator to solve math problems for my users, I may be able to say with certainty that my model is 99% accurate, but I'm not able to know ahead of time which questions it's going to miss, and which ones it's going to get correct. It's a near-infinite input space, so it's difficult to prove correctness via induction. The attention mechanism is (thus far) fairly inscrutable, and thus proving the correctness via deduction is (currently) not possible either.
It would feel weird to use an LLM for a calculator, just as it feels weird to use an LLM as a compiler. I want something that uses an algorithm that is provably and predictably correct -- not one that is "almost always" correct.
That unpredictability of accuracy isn't connected to the temperature used when sampling, so much as the inscrutable nature of the attention mechanism when verifying the trustworthiness over the range of all possible inputs.
Even that “random” sampling is deterministic, in that if you use the same PRNG algorithm with the same random seed, then (all else being equal) you should get the same results every time.
To get genuine nondeterminism, you need an external source of randomness, such as thermal noise, keystroke timing, etc. (Even whether that is really non-deterministic depends on a whole lot of contested issues in philosophy and physics, but at least we can say it is non-deterministic for all practical purposes.)
It's pretty important for compilers / decompilers to be reliable and accurate -- compilers behaving in a deterministic and predictable way is an important fundamental of pipelines.
LLMs are inherently unpredictable, and so using an LLM for compilation / decompilation -- even an LLM that has 99.99% accuracy -- feels a bit odd to include as a piece in my build pipeline.
That said, let's look at the paper and see what they did.
They essentially started with CodeLlama, and then went further to train the model on three tasks -- one primary, and two downstream.
The first task is compilation: given input code and a set of compiler flags, can we predict the output assembly? Given the inability to verify correctness without using a traditional compiler, this feels like it's of limited use on its own. However, training a model on this as a primary task enables a couple of downstream tasks. Namely:
The second task (and first downstream task) is compiler flag prediction / optimization to predict / optimize for smaller assembly sizes. It's a bit disappointing that they only seem to be able to optimize for assembly size (and not execution speed), but it's not without its uses. Because the output of this task (compiler flags) are then passed to a deterministic function (a traditional compiler), then the instability of the LLM is mitigated.
The third task (second downstream task) is decompilation. This is not the first time that LLMs have been trained to do better decompilation -- however, because of the pretraining that they did on the primary task, they feel that this provides some advantages over previous approaches. Sadly, they only compare LLM Compiler to Code Llama and GPT-4 Turbo, and not against any other LLMs fine-tuned for the decompilation task, so it's difficult to see in context how much better their approach is.
Regarding the verifiability of the disassembly approach, the authors note that there are issues regarding correctness. So the authors employ round-tripping -- recompiling the decompiled code (using the same compiler flags) to verify correctness / exact-match. This still puts accuracy in the 45% or so (if I understand their output numbers), so it's not entirely trustworthy yet, but it might be able to still be useful (especially if used alongside a traditional decompiler, and this model's outputs only used when they are verifiably correct).
Overall I'm happy to see this model be released as it seems like an interesting use-case. I may need to read more, but at first blush I'm not immediately excited by the possibilities that this unlocks. Most of all, I would like to see it explored if these methods could be extended to optimize for performance -- not just size of assembly.