Creating SQL Indexes on Large Tables
Community wiki because the root of the answer (use an MLT to copy the data) was already in the comments. Shoutout to dnoeth and Der Kommissar for already explaining most of this in the comments.

Kendra Little has a great article on this topic. In general, the ALTER TABLE statement can't be assumed to be minimally logged, in particular if it is a size-of-data operation, meaning that

each record in the table must be updated

When something is a size-of-data operation is a little outside the scope of this question, and you can find good resources for it online, but in general, if changing the column metadata also requires updating the table itself (which making a column NOT NULL is) then you're going to hit this bucket. As Kendra mentions, this can be really bad:

Once you get to the point where you can alter the data, you run into a different problem:

Altering the column uses a huge amount of transaction log space
It’s reaaaalllll slow

This is because changing from INT to BIGINT is a “size-of-data” operation.
SQL Server has to go through and update Every. Single. Row.
And log it.
And SQL Server has to reserve extra space in the log in case it needs to roll back (because that will require additional logging).
If you get this far without thinking about how much log file you’re going to need and you run out, then things get extra slow, because most of that rollback’s going to be done by just one thread.

The solution is, not surprisingly, also called out in that article. You're likely going to need to take a downtime, and you're likely going to need to create a new table, copy all the data, and then swizzle everything to be pointing in the right direction.
The most important feature that this relies on is Minimal Logging (required reading). If/when you achieve Minimal Logging, you're likely going to be able to eliminate your batching (or at least get rid of a lot of it). Additionally, you can likely do it without too much of a downtime if you use Snapshot Isolation (also required reading). In that scenario, you just start dumping data into a copy of the table to get most of it in, then take a downtime, clean up any discrepancies that happened during that time, swap the tables, fix any foreign keys, etc etc, then come away from downtime.
If you find yourself still in need of batching, you're going to be better off first getting a list of keys to process, and looping over that instead of using TOP or other techniques (likely gets you better table access and will avoid any sorts).
You'll also want to keep your recovery model in mind; if you have a FULL recovery model then you will need a manual full-backup, then switch to SIMPLE, do the work, switch back, and backup again. This is because minimal logging requires SIMPLE recovery mode.