HN user

tylerhou

3,487 karma

Undergrad at Berkeley.

  github: https://github.com/tylerhou
  website: https://tylerhou.com
https://tylerhou.at.hn/
Posts13
Comments1,080
View on HN

The art’s aesthetic, which resembles Calvin and Hobbes, is disrespectful to its creator, Bill Watterson’s.

Bill spent a lot of energy fighting commercialization of his work, arguing that it would devalue his characters and their personalities. I don’t know what is cheaper than using an AI model to instantly generate similar art, for free.

A big problem is that the range that we have decided to call “normal” might not actually be a normal range.

For example, 50% of surfers were found to have insufficient vitamin D in one study. https://pubmed.ncbi.nlm.nih.gov/17426097/

There are at least two possible conclusions that you could draw. One conclusion is that we all need vitamin D supplementation regardless of how much sun exposure we receive.

Another conclusion is that we might want to reevaluate what we consider the normal range to be, especially when we are deciding a range for a specific individual.

Basically, a proxy. You don't need to deep copy; you just need to return a proxy object that falls back to the original dict if the key you are requesting has not been found or modified.

Functional data structures essentially create a proxy on every write. This can be inefficient if you make writes in batches, and you only need immutability between batches.

I would like to learn category theory properly one day, at least to that kind of "advance undergraduate" level she mentions.

As someone who tried to learn category theory, and then did a mathematics degree, I think anyone who wants to properly learn category theory would benefit greatly from learning the surrounding mathematics first. The nontrivial examples in category theory come from group theory, ring theory, linear algebra, algebraic topology, etc.

For example, Set/Group/Ring have initial and final objects, but Field does not. Why? Really understanding requires at least some knowledge of ring/field theory.

What is an example of a nontrivial functor? The fundamental group is one. But appreciating the fundamental group requires ~3 semesters of math (analysis, topology, group theory, algebraic topology).

Why are opposite categories useful? They can greatly simplify arguments. For example, in linear algebra, it is easier to show that the row rank and column rank of a matrix are equal by showing that the dual/transpose operator is a functor from the opposite category.

Why SSA? 9 months ago

It is not critical for register assignment -- in fact, SSA makes register assignment more difficult (see the swap problem; the lost copy problem).

Lifetime analysis is important for register assignment, and SSA can make lifetime analysis easier, but plenty of non-SSA compilers (lower-tier JIT compilers often do not use SSA because SSA is heavyweight) are able to register allocate just fine without it.

Why SSA? 9 months ago

Here's a concise explanation of SSA. Regular (imperative) code is hard to optimize because in general statements are not pure -- if a statement has side effects, then it might not preserve the behavior to optimize that statement by, for example:

1. Removing that statement (dead code elimination)

2. Deduplicating that statement (available expressions)

3. Reordering that statement with other statements (hoisting; loop-invariant code motion)

4. Duplicating that statement (can be useful to enable other optimizations)

All of the above optimizations are very important in compilers, and they are much, much easier to implement if you don't have to worry about preserving side effects while manipulating the program.

So the point of SSA is to translate a program into an equivalent program whose statements have as few side effects as possible. The result is often something that looks like a functional program. (See: https://www.cs.princeton.edu/~appel/papers/ssafun.pdf, which is famous in the compilers community.) In fact, if you view basic blocks themselves as a function, phi nodes "declare" the arguments of the basic block, and branches correspond to tailcalling the next basic block with corresponding values. This has motivated basic block arguments in MLIR.

The "combinatorial circuit" metaphor is slightly wrong, because most SSA implementations do need to consider state for loads and stores into arbitrary memory, or arbitrary function calls. Also, it's not easy to model a loop of arbitrary length as a (finite) combinatorial circuit. Given that the author works at an AI accelerator company, I can see why he leaned towards that metaphor, though.

Apple M5 chip 9 months ago

M5 is 4-6x more powerful than M4

In GPU performance (probably measured on a specific set of tasks).

WCOJs guarantee an (asymptotic) upper bound on join complexity, but often with the right join plan and appropriate cardinality estimations, you can do much better than WCOJ.

The runtime of WCOJs algorithms are even more dependent on good cardinality estimation. For instance, in VAAT, the main difficulty to find an appropriate variable ordering, which relies on knowledge about cardinalities conditioned on particular variables having particular values. If you have the wrong ordering, you still achieve worst case optimal, but you could have done far better in some cases with other algorithms (e.g. Yannakakis algorithm for acyclic queries). And as far as I know, many DBMSes do not keep track of this type of conditional cardinality, so it is unlikely that existing WCOJ will be faster in practice.

The new hotness is "instance optimal" joins...

You can end up writing nearly the exact same code twice because one needs to be async to handle an async function argument, even if the real functionality of the wrapper isn't async.

Sorry for the possibly naive question. If I need to call a synchronous function from an async function, why can't I just call await on the async argument?

    def foo(bar: str, baz: int):
      # some synchronous work
      pass
    
    async def other(bar: Awaitable[str]):
      foo(await bar, 0)

There are many comments that don't see the point of the article. For example, why not just use the tools the operating system provides for sandboxing? This article seems to be directed at people familiar with the state of programming languages research (SIGPLAN is the Special Interest Group on Programming LANguages) and so I think it's understandable that it seems vague if one is missing the broader context. However, for people familiar with the state of the field, the main idea is fairly clear.

An operating system (or sandbox, or whatever) is a very large virtual machine, where the "instructions" are the normal CPU instructions plus the set of syscalls. Unfortunately, operating systems today are complicated, hard to understand, and (relatively) hard to modify. For example, there are many different ways to sandbox file system access (chmod, containers, chroot, sandbox-exec on macOS etc.) and they each have bugs that have turned into "features" or subtle semantics. Plus, they are not available on all operating systems or even on all distributions of the same operating system. And then -- how do filesystem permissions and network permissions interact? Even of both of their semantics are "safe," is the composition of the two safe?

The assumption is: because operating systems are so complex, large, and underspecified, it probably is dangerous for LLMs to interact directly with the underlying operating system. We have observed this empirically: through CVEs in C and C++ code, we know that subtle errors or small differences in semantics can cascade into huge security vulnerabilities.

To address this, the authors propose that LLMs instead interact with a virtual machine where, for example, the semantics of permissions and/or capabilities is well-defined and standardized across different implementations or operating systems. (This is why they mention Java as an analogy -- the JVM gave developers the ability to write code for a vast array of architectures and operating systems without having to think about the underlying implementations.) This standardization makes it easier to understand how exactly an LLM would be allowed to interact with the outside world.

Besides semantic understanding and clarity, there are more benefits to designing a new virtual machine.

- Standardization across multiple model providers (mentioned).

- Better RLHF / constrained generation opportunity than general Bash output.

- Can incorporate advances in programming language theory and design.

For an example of the last point, in recent years, there has been a ton of research on information flow for security and privacy (mentioned in the article). In a programming language that is aware of information flow, I can mark my bank account password as "secret" and the input to all HTTP calls as "public." The type system or some other static analysis can verify that my password cannot possibly affect the input to any HTTP call. This is harder than you think because it depends on control flow! For example, the following program indirectly exfiltrates information about my password:

    if (password.startsWith("hackernews")) {
      fetch("https://example.com/a");
    } else {
      fetch("https://example.com/b");
    }
Obviously, nobody would write that code, but people do write similar code with bugs in e.g. timing attacks.
AGI Overhyped? 11 months ago

Sorry, you're right that the chart on the home page does not have human performance. The leaderboard chart does: https://arcprize.org/leaderboard. And the leaderboard by default shows scores for ARC-AGI 1 and 2. The models are much worse at 2 than 1; the best performing model scores around 15% (Grok 4, thinking), while humans are at ~100%.

AGI Overhyped? 11 months ago

AI already blows most people out of an IQ test at a fraction of the computational power of a brain

AFAIK, IQ tests used in psychological evaluations do not contain any randomness so exact answers are almost always in distribution. I haven't seen someone compare AI to an IQ test that is not in distribution.

On ARC-AGI, which is mildly similar to a randomly generated IQ test, humans still are much better than LLMs. https://arcprize.org/ (scroll down for chart)

IVM is not new, but the DBSP (2022) perspective is relatively new for databases (where the classic literature is from the 70s and 80s).

It is misleading to say that IVM reduces the cost of view updates from O(n) to O(1). While that is not technically incorrect, for any nontrivial query (e.g anything with an index join) the cost of a view update will be smaller than the original query but not constant time.

Also, the tone of “newfangled” was not dismissive in the context of an article praising IVM. At worst, it was sarcastic; I interpreted it as teasing.

I do research related to IVM / DBSP.

Defunctionalization is a general technique that allows one to serialize functions as data. You’ve almost certainly used this technique before without realizing it was an instance of defunctionalization.

It is much easier and more maintainable to convert to continuation passing style. If you also use defunctionalization to allocate closures on a stack instead of a fresh heap allocation for every closure, you will achieve performance on par with an explicit stack. (In fact, defunctionalization is a mechanical transformation the produces exactly the data you would store in an explicit stack!)

Before I knew about CPS and defunctionalization, I wrote a Python decorator that did exactly the transformation you describe. https://github.com/tylerhou/fiber. Now I know about CPS and defunctionalization, I realize that my work was not the best implementation (but it was a fun learning experience!).

I disagree with the title; loops are tail-recursive functions, but tail-recursive functions are not loops (in the sense that squares are rectangles, but rectangles are not squares).

It is true that every tail recursive function can be converted into a semantically equivalent loop via a transformation like CPS (which the author mentions). However, for mutually tail-recursive functions, this conversion loses control flow information. This is because after the CPS transformation, calls to the other function become calls to a continuation; this call usually must be implemented as an indirect jump. On the other hand, mutually tail-recursive functions can call each other with direct/statically-known jumps.

This loss of information might appear trivial, but in practice it has some important consequences. Classic examples are interpreter loops. It is well-known that computed gotos can result in modest to large speedups for interpreters [1]. The reason why is that computed gotos create an indirect jump per opcode, so a branch predictor can take advantage of correlations between opcodes. For example, looking at Python disassembly, the header of a standard range for loop compiles down to three opcodes: GET_ITER, FOR_ITER, STORE_FAST in sequence [2]. A branch predictor can recognize that the target of the "FOR_ITER" indirect jump will likely be the "STORE_FAST" instruction pointer; it cannot predict this in the naive implementation where jumps for all instructions are "merged" into a single indirect jump / switch at the top of the loop body. In this case, computed goto is effectively equivalent to a CPS transformation whose closures require no storage on the heap.

Suppose, however, we know even more information about the instruction sequence; for example, we know ahead of time that every FOR_ITER opcode will be followed by a STORE_FAST opcode. We could completely replace the indirect jump with a direct jump to the instruction pointer for the STORE_FAST opcode. Because modern branch predictors are very good, this will have about the same performance in practice as the computed goto loop.

However, consider the limiting case where we know the entire instruction sequence beforehand. If we write our interpreter as many mutually tail-recursive functions, with one function for every instruction, an optimizing compiler can replace every indirect call with a direct (tail-recursive) call to the function that implements the next instruction's opcode. With a good enough optimizer / partial evaluator, you can turn an interpreter into a compiler! This is known as the first Futamura projection [3].

To see an example of this in action, I wrote a prototype of a Brainfuck compiler via the Futamura projection; it uses LLVM as a partial evaluator [4]. The main interesting function is `interpret`, which is templated on the program counter / instruction. That is, `interpret` is really a family of mutually tail-recursive functions which statically call each other as described above. For short Brainfuck programs, the LLVM optimizer is able to statically compute the output of the Brainfuck program. (The one in the Godbolt link compiles to a loop, likely because LLVM does not want to unroll the mutual recursion too much.) You can play around with different Brainfuck programs by modifying the `program` string on line 5.

[1] https://eli.thegreenplace.net/2012/07/12/computed-goto-for-e...

[2] https://godbolt.org/z/rdhMvPo36

[3] https://en.wikipedia.org/wiki/Partial_evaluation#Futamura_pr...

[4] https://godbolt.org/z/WY4j931jf

If you are willing to return std::optional, clang-tidy has a (static) control flow sensitive check that enforces you check to see the value is valid before unwrapping. https://clang.llvm.org/extra/clang-tidy/checks/bugprone/unch...

This would prevent the last bug (!ua()) as the control flow sensitive analysis can reason about both branches: that it is invalid to deref ua within the block. The dynamic check misses the bug because the branch is never taken for the given inputs.

I am fairly confident that the clang-tidy pass is simpler and more precise in most cases than the hand-rolled implementation. (That said the static check may not be able to reason about mutation well.)

If you need to pass an error in the failure case, you can use std::expected (available in C++23). clang-tidy has an open bug about supporting a similar check for std::expected: https://github.com/llvm/llvm-project/issues/135045

I agree with you that smart people exist, and I have met a few in college as well.

The main thing I want to add is that using IQ to quantify intelligence at the top end of the scale is scientifically bogus and in my opinion harmful because it validates depressed / insecure / chronically online people who use their "160 IQ" as a way to put down other people or to peddle pseudo-scientific nonsense. Those people often need genuine psychiatric help and (in my opinion) such validation only harms them.

I'm sure that Hal Finney was exceptionally smart, though. :)

You should be extremely skeptical of people who claim to have tested IQs above 130 and also believe those tests are not inherently noisy at the top end. Many modern tests lump everyone with 130+ into the same category [1]. An IQ of "easily over 160" is not a clinically valid finding by any standard IQ test that I am aware of.

This is because standard IQ tests are generally designed to measure around the median of the distribution (70-130), and so there is a lot of variance in measurement at the top end. If you happen to have a bad testing day and you make a dumb mistake, your measured IQ might drop by a fairly large number of points -- or, conversely, if you got lucky and guessed right, your measured IQ could be much higher than reality.

For example, the original Raven's Progressive Matrices says [2; page 71]

For reason's already given, Progressive Matrices (1938) does not differentiate, very clearly between young-children, or between adults of superior intellectual capacity.

where "superior intellectual capacity" is defined as an IQ of ~125 or higher, and (if I am interpreting it correctly), the table on page 79 of [2] says missing a single question could drop a 20-year old from scoring 95 percentile to scoring 90 percentile. That's 5 IQ points on a single question! If you had a bad day, or didn't get enough sleep, you could test significantly worse than your actual "IQ."

Anyone that actually has an IQ of 160 with even a modicum of self awareness should understand that the IQ test they took is inherently noisy at the top end of the scale because sometimes people have off days.

[1] https://en.wikipedia.org/wiki/IQ_classification#IQ_classific...

[2] https://rehabilitationpsychologist.org/resources/SPM%20with%...